[jetty9] 01/01: Imported Upstream version 9.2.13

Emmanuel Bourg ebourg-guest at moszumanska.debian.org
Thu Dec 17 14:53:40 UTC 2015


This is an automated email from the git hooks/post-receive script.

ebourg-guest pushed a commit to annotated tag upstream/9.2.13
in repository jetty9.

commit 0a3240cf8ace6732e54d6effd153a532447fbe67
Author: Jakub Adam <jakub.adam at ktknet.cz>
Date:   Tue Aug 4 23:14:43 2015 +0200

    Imported Upstream version 9.2.13
---
 .gitattributes                                     |    4 +
 .gitignore                                         |   22 +-
 .travis.yml                                        |    4 -
 BUILDING.txt                                       |    9 -
 LICENSE-APACHE-2.0.txt                             |  202 -
 LICENSE-ECLIPSE-1.0.html                           |  320 --
 LICENSE-eplv10-aslv20.html                         |  576 ++
 NOTICE.txt                                         |   61 +-
 README.TXT                                         |   21 +
 README.md                                          |   24 +
 README.txt                                         |   22 -
 VERSION.txt                                        | 5523 +++++++++++++-------
 .../2015-02-24-httpparser-error-buffer-bleed.md    |  125 +
 aggregates/jetty-all/pom.xml                       |  230 +
 aggregates/jetty-websocket-all/pom.xml             |  163 +
 apache-jsp/pom.xml                                 |  120 +
 .../main/config/modules/jsp-impl/apache-jsp.mod    |   10 +
 .../jetty/apache/jsp/JettyJasperInitializer.java   |  119 +
 .../java/org/eclipse/jetty/apache/jsp/JuliLog.java |  188 +
 .../org/eclipse/jetty/jsp/JettyJspServlet.java     |  111 +
 .../javax.servlet.ServletContainerInitializer      |    1 +
 .../META-INF/services/org.apache.juli.logging.Log  |    1 +
 apache-jstl/pom.xml                                |   49 +
 .../main/config/modules/jsp-impl/apache-jstl.mod   |    8 +
 apache-jstl/src/main/resources/readme.txt          |    4 +
 dists/jetty-deb/pom.xml                            |  103 +
 dists/jetty-deb/src/main/unix/scripts/postinst     |   20 +
 dists/jetty-deb/src/main/unix/scripts/postrm       |   48 +
 dists/jetty-deb/src/main/unix/scripts/preinst      |   61 +
 dists/pom.xml                                      |   26 +
 eclipse-jetty-templates.xml                        |    8 -
 example-async-rest/async-rest-jar/pom.xml          |   24 -
 .../example/asyncrest/AbstractRestServlet.java     |  120 -
 .../jetty/example/asyncrest/AsyncRestServlet.java  |  211 -
 .../jetty/example/asyncrest/SerialRestServlet.java |  106 -
 .../src/main/resources/META-INF/web-fragment.xml   |   22 -
 example-async-rest/async-rest-webapp/pom.xml       |   34 -
 .../src/main/webapp/WEB-INF/web.xml                |    9 -
 .../async-rest-webapp/src/main/webapp/index.html   |   38 -
 .../jetty/example/asyncrest/DemoServer.java        |   48 -
 example-async-rest/pom.xml                         |   17 -
 example-jetty-embedded/pom.xml                     |   70 -
 .../org/eclipse/jetty/embedded/DumpServlet.java    |   53 -
 .../org/eclipse/jetty/embedded/FileServer.java     |   63 -
 .../org/eclipse/jetty/embedded/FileServerXml.java  |   44 -
 .../org/eclipse/jetty/embedded/HelloHandler.java   |   63 -
 .../org/eclipse/jetty/embedded/HelloServlet.java   |   49 -
 .../org/eclipse/jetty/embedded/LikeJettyXml.java   |  170 -
 .../org/eclipse/jetty/embedded/ManyConnectors.java |   67 -
 .../org/eclipse/jetty/embedded/ManyContexts.java   |   77 -
 .../org/eclipse/jetty/embedded/ManyHandlers.java   |  124 -
 .../jetty/embedded/ManyServletContexts.java        |   61 -
 .../eclipse/jetty/embedded/MinimalServlets.java    |   62 -
 .../org/eclipse/jetty/embedded/OneContext.java     |   48 -
 .../org/eclipse/jetty/embedded/OneHandler.java     |   33 -
 .../eclipse/jetty/embedded/OneServletContext.java  |   48 -
 .../java/org/eclipse/jetty/embedded/OneWebApp.java |   64 -
 .../org/eclipse/jetty/embedded/ProxyServer.java    |   58 -
 .../jetty/embedded/SecuredHelloHandler.java        |   70 -
 .../org/eclipse/jetty/embedded/SimplestServer.java |   34 -
 .../src/main/resources/fileserver.xml              |   35 -
 .../eclipse/jetty/embedded/GzipHandlerTest.java    |  131 -
 .../src/test/resources/realm.properties            |   22 -
 examples/async-rest/async-rest-jar/pom.xml         |   30 +
 .../example/asyncrest/AbstractRestServlet.java     |  129 +
 .../jetty/example/asyncrest/AsyncRestServlet.java  |  206 +
 .../jetty/example/asyncrest/SerialRestServlet.java |  106 +
 .../resources/META-INF/resources/asyncrest.html    |    0
 .../META-INF/resources/asyncrest/green.png         |  Bin
 .../resources/META-INF/resources/asyncrest/red.png |  Bin
 .../src/main/resources/META-INF/web-fragment.xml   |   28 +
 examples/async-rest/async-rest-webapp/pom.xml      |   33 +
 .../src/main/webapp/META-INF/MANIFEST.MF           |    0
 .../src/main/webapp/WEB-INF/jetty-web.xml          |   15 +
 .../src/main/webapp/WEB-INF/web.xml                |    9 +
 .../async-rest-webapp/src/main/webapp/index.html   |   44 +
 .../jetty/example/asyncrest/DemoServer.java        |   43 +
 examples/async-rest/pom.xml                        |   17 +
 examples/embedded/pom.xml                          |  109 +
 .../embedded}/prodDb.properties                    |    0
 .../embedded}/prodDb.script                        |    0
 examples/embedded/src/main/java/HelloWorld.java    |   59 +
 .../eclipse/jetty/embedded/AsyncEchoServlet.java   |  107 +
 .../org/eclipse/jetty/embedded/DumpServlet.java    |   59 +
 .../org/eclipse/jetty/embedded/ExampleServer.java  |   51 +
 .../eclipse/jetty/embedded/ExampleServerXml.java   |   40 +
 .../org/eclipse/jetty/embedded/FastFileServer.java |  194 +
 .../org/eclipse/jetty/embedded/FileServer.java     |   59 +
 .../org/eclipse/jetty/embedded/FileServerXml.java  |   48 +
 .../org/eclipse/jetty/embedded/HelloHandler.java   |   71 +
 .../org/eclipse/jetty/embedded/HelloServlet.java   |   53 +
 .../java/org/eclipse/jetty/embedded/JarServer.java |   51 +
 .../org/eclipse/jetty/embedded/LikeJettyXml.java   |  221 +
 .../org/eclipse/jetty/embedded/ManyConnectors.java |  127 +
 .../org/eclipse/jetty/embedded/ManyContexts.java   |   55 +
 .../org/eclipse/jetty/embedded/ManyHandlers.java   |  150 +
 .../jetty/embedded/ManyServletContexts.java        |   63 +
 .../eclipse/jetty/embedded/MinimalServlets.java    |   78 +
 .../org/eclipse/jetty/embedded/OneConnector.java   |   50 +
 .../org/eclipse/jetty/embedded/OneContext.java     |   43 +
 .../org/eclipse/jetty/embedded/OneHandler.java     |   33 +
 .../eclipse/jetty/embedded/OneServletContext.java  |   45 +
 .../jetty/embedded/OneServletContextJmxStats.java  |   52 +
 .../java/org/eclipse/jetty/embedded/OneWebApp.java |   82 +
 .../eclipse/jetty/embedded/OneWebAppWithJsp.java   |  109 +
 .../org/eclipse/jetty/embedded/ProxyServer.java    |   51 +
 .../jetty/embedded/SecuredHelloHandler.java        |  104 +
 .../jetty/embedded/ServerWithAnnotations.java      |   82 +
 .../org/eclipse/jetty/embedded/ServerWithJMX.java  |   56 +
 .../org/eclipse/jetty/embedded/ServerWithJNDI.java |  116 +
 .../org/eclipse/jetty/embedded/SimplestServer.java |   35 +
 .../org/eclipse/jetty/embedded/SpdyConnector.java  |  114 +
 .../org/eclipse/jetty/embedded/SpdyServer.java     |  210 +
 .../eclipse/jetty/embedded/SplitFileServer.java    |   93 +
 .../eclipse/jetty/embedded/WebSocketJsrServer.java |   68 +
 .../eclipse/jetty/embedded/WebSocketServer.java    |   80 +
 examples/embedded/src/main/resources/content.jar   |  Bin 0 -> 767 bytes
 .../embedded/src/main/resources/exampleserver.xml  |   39 +
 .../embedded/src/main/resources/fileserver.xml     |   36 +
 .../src/main/resources/jetty-logging.properties    |   12 +
 .../src/main/resources/jetty-otherserver.xml       |   51 +
 .../eclipse/jetty/embedded/GzipHandlerTest.java    |  125 +
 .../java/org/eclipse/jetty/embedded/TestXml.java   |   36 +
 .../embedded/src/test/resources/dir0/test0.txt     |    1 +
 .../embedded/src/test/resources/dir1/test1.txt     |    1 +
 .../embedded/src/test/resources/realm.properties   |   21 +
 examples/pom.xml                                   |   51 +
 jetty-aggregate/jetty-all-server/pom.xml           |  213 -
 jetty-aggregate/jetty-all/pom.xml                  |  176 -
 jetty-aggregate/jetty-client/pom.xml               |   87 -
 jetty-aggregate/jetty-plus/pom.xml                 |   91 -
 jetty-aggregate/jetty-server/pom.xml               |   97 -
 jetty-aggregate/jetty-servlet/pom.xml              |   96 -
 jetty-aggregate/jetty-webapp/pom.xml               |  103 -
 jetty-aggregate/jetty-websocket/pom.xml            |   91 -
 jetty-aggregate/pom.xml                            |   44 -
 jetty-ajp/pom.xml                                  |   83 -
 jetty-ajp/src/main/config/etc/jetty-ajp.xml        |   18 -
 .../org/eclipse/jetty/ajp/Ajp13Connection.java     |  250 -
 .../java/org/eclipse/jetty/ajp/Ajp13Generator.java |  782 ---
 .../java/org/eclipse/jetty/ajp/Ajp13Packet.java    |   68 -
 .../org/eclipse/jetty/ajp/Ajp13PacketMethods.java  |   74 -
 .../java/org/eclipse/jetty/ajp/Ajp13Parser.java    |  890 ----
 .../java/org/eclipse/jetty/ajp/Ajp13Request.java   |  124 -
 .../org/eclipse/jetty/ajp/Ajp13RequestHeaders.java |   67 -
 .../org/eclipse/jetty/ajp/Ajp13RequestPacket.java  |   90 -
 .../eclipse/jetty/ajp/Ajp13ResponseHeaders.java    |   48 -
 .../eclipse/jetty/ajp/Ajp13SocketConnector.java    |  132 -
 .../org/eclipse/jetty/ajp/Ajp13ConnectionTest.java |  331 --
 .../java/org/eclipse/jetty/ajp/TestAjpParser.java  |  608 ---
 jetty-alpn/jetty-alpn-client/pom.xml               |   76 +
 .../jetty/alpn/client/ALPNClientConnection.java    |   81 +
 .../alpn/client/ALPNClientConnectionFactory.java   |   51 +
 jetty-alpn/jetty-alpn-server/pom.xml               |   94 +
 .../src/main/config/etc/protonego-alpn.xml         |   19 +
 .../modules/protonego-impl/alpn-1.7.0_40.mod       |    8 +
 .../modules/protonego-impl/alpn-1.7.0_45.mod       |    8 +
 .../modules/protonego-impl/alpn-1.7.0_51.mod       |    8 +
 .../modules/protonego-impl/alpn-1.7.0_55.mod       |    8 +
 .../modules/protonego-impl/alpn-1.7.0_60.mod       |    8 +
 .../modules/protonego-impl/alpn-1.7.0_65.mod       |    8 +
 .../modules/protonego-impl/alpn-1.7.0_67.mod       |    8 +
 .../modules/protonego-impl/alpn-1.7.0_71.mod       |    8 +
 .../modules/protonego-impl/alpn-1.7.0_72.mod       |    8 +
 .../modules/protonego-impl/alpn-1.7.0_75.mod       |    8 +
 .../modules/protonego-impl/alpn-1.7.0_76.mod       |    8 +
 .../modules/protonego-impl/alpn-1.7.0_79.mod       |    8 +
 .../modules/protonego-impl/alpn-1.7.0_80.mod       |    8 +
 .../config/modules/protonego-impl/alpn-1.8.0.mod   |    8 +
 .../modules/protonego-impl/alpn-1.8.0_05.mod       |    8 +
 .../modules/protonego-impl/alpn-1.8.0_11.mod       |    8 +
 .../modules/protonego-impl/alpn-1.8.0_20.mod       |    8 +
 .../modules/protonego-impl/alpn-1.8.0_25.mod       |    8 +
 .../modules/protonego-impl/alpn-1.8.0_31.mod       |    8 +
 .../modules/protonego-impl/alpn-1.8.0_40.mod       |    8 +
 .../modules/protonego-impl/alpn-1.8.0_45.mod       |    8 +
 .../modules/protonego-impl/alpn-1.8.0_51.mod       |    8 +
 .../main/config/modules/protonego-impl/alpn.mod    |   42 +
 .../jetty/alpn/server/ALPNServerConnection.java    |   82 +
 .../alpn/server/ALPNServerConnectionFactory.java   |   61 +
 jetty-alpn/pom.xml                                 |   17 +
 jetty-annotations/pom.xml                          |   22 +-
 .../src/main/config/etc/jetty-annotations.xml      |   27 +-
 .../src/main/config/modules/annotations.mod        |   17 +
 .../AbstractDiscoverableAnnotationHandler.java     |   49 +-
 .../jetty/annotations/AnnotationConfiguration.java | 1065 +++-
 .../jetty/annotations/AnnotationDecorator.java     |  126 +-
 .../jetty/annotations/AnnotationParser.java        | 1159 ++--
 .../jetty/annotations/ClassInheritanceHandler.java |   56 +-
 .../ContainerInitializerAnnotationHandler.java     |   72 +-
 .../annotations/DeclareRolesAnnotationHandler.java |   12 +-
 .../MultiPartConfigAnnotationHandler.java          |    2 +-
 .../PostConstructAnnotationHandler.java            |   12 +-
 .../annotations/PreDestroyAnnotationHandler.java   |   16 +-
 .../annotations/ResourceAnnotationHandler.java     |   82 +-
 .../jetty/annotations/RunAsAnnotationHandler.java  |   17 +-
 .../ServletContainerInitializerListener.java       |  144 -
 .../ServletContainerInitializersStarter.java       |   76 +
 .../ServletSecurityAnnotationHandler.java          |   57 +-
 .../java/org/eclipse/jetty/annotations/Util.java   |   38 +
 .../jetty/annotations/WebFilterAnnotation.java     |   58 +-
 .../annotations/WebFilterAnnotationHandler.java    |   48 +-
 .../jetty/annotations/WebListenerAnnotation.java   |   29 +-
 .../annotations/WebListenerAnnotationHandler.java  |   44 +-
 .../jetty/annotations/WebServletAnnotation.java    |   85 +-
 .../annotations/WebServletAnnotationHandler.java   |   61 +-
 .../eclipse/jetty/annotations/package-info.java    |   23 +
 .../org/eclipse/jetty/annotations/FilterC.java     |   12 +-
 .../org/eclipse/jetty/annotations/ServletC.java    |   10 +-
 .../annotations/TestAnnotationConfiguration.java   |   13 +-
 .../annotations/TestAnnotationInheritance.java     |   96 +-
 .../jetty/annotations/TestAnnotationParser.java    |  193 +-
 .../TestSecurityAnnotationConversions.java         |  187 +-
 .../jetty/annotations/TestServletAnnotations.java  |   67 +-
 .../jetty/annotations/resources/ResourceA.java     |   35 +-
 .../resources/TestResourceAnnotations.java         |    7 +-
 jetty-ant/pom.xml                                  |   72 +
 .../org/eclipse/jetty/ant/AntWebAppContext.java    |  766 +++
 .../eclipse/jetty/ant/AntWebInfConfiguration.java  |  170 +
 .../eclipse/jetty/ant/AntWebXmlConfiguration.java  |   65 +
 .../java/org/eclipse/jetty/ant/JettyRunTask.java   |  327 ++
 .../java/org/eclipse/jetty/ant/JettyStopTask.java  |  127 +
 .../org/eclipse/jetty/ant/ServerProxyImpl.java     |  514 ++
 .../java/org/eclipse/jetty/ant/package-info.java   |   23 +
 .../org/eclipse/jetty/ant/types/Attribute.java     |   47 +
 .../org/eclipse/jetty/ant/types/Attributes.java    |   38 +
 .../org/eclipse/jetty/ant/types/Connector.java     |   62 +
 .../org/eclipse/jetty/ant/types/Connectors.java    |   86 +
 .../eclipse/jetty/ant/types/ContextHandlers.java   |   45 +
 .../jetty/ant/types/FileMatchingConfiguration.java |   98 +
 .../org/eclipse/jetty/ant/types/LoginServices.java |   47 +
 .../eclipse/jetty/ant/types/SystemProperties.java  |   67 +
 .../org/eclipse/jetty/ant/types/package-info.java  |   23 +
 .../org/eclipse/jetty/ant/utils/ServerProxy.java   |   42 +
 .../java/org/eclipse/jetty/ant/utils/TaskLog.java  |   58 +
 .../org/eclipse/jetty/ant/utils/package-info.java  |   23 +
 jetty-ant/src/main/resources/tasks.properties      |    2 +
 jetty-ant/src/test/config/build.xml                |   42 +
 .../test/java/org/eclipse/jetty/ant/AntBuild.java  |  294 ++
 .../org/eclipse/jetty/ant/JettyAntTaskTest.java    |   72 +
 jetty-ant/src/test/resources/connector-test.xml    |   19 +
 .../src/test/resources/foo/WEB-INF/acme-taglib.tld |   28 +
 .../test/resources/foo}/WEB-INF/acme-taglib2.tld   |    0
 .../src/test/resources/foo}/WEB-INF/tags/panel.tag |    0
 jetty-ant/src/test/resources/foo/WEB-INF/web.xml   |   28 +
 jetty-ant/src/test/resources/foo/index.html        |    5 +
 .../src/test/resources/foo}/jsp/bean1.jsp          |    0
 .../src/test/resources/foo}/jsp/bean2.jsp          |    0
 .../src/test/resources/foo/jsp}/dump.jsp           |    0
 .../src/test/resources/foo}/jsp/expr.jsp           |    0
 .../src/test/resources/foo}/jsp/foo/foo.jsp        |    0
 .../src/test/resources/foo}/jsp/index.html         |    0
 .../src/test/resources/foo}/jsp/jstl.jsp           |    0
 .../src/test/resources/foo}/jsp/tag.jsp            |    0
 .../src/test/resources/foo}/jsp/tag2.jsp           |    0
 .../src/test/resources/foo}/jsp/tagfile.jsp        |    0
 jetty-ant/src/test/resources/webapp-test.xml       |   17 +
 jetty-cdi/pom.xml                                  |   49 +
 jetty-cdi/src/main/config/etc/jetty-cdi.xml        |   19 +
 jetty-cdi/src/main/config/modules/cdi.mod          |   26 +
 .../eclipse/jetty/cdi/WeldDeploymentBinding.java   |   77 +
 jetty-client/pom.xml                               |  239 +-
 jetty-client/src/main/config/modules/client.mod    |    6 +
 .../jetty/client/AbstractHttpClientTransport.java  |  172 +
 .../jetty/client/AbstractHttpConnection.java       |  571 --
 .../java/org/eclipse/jetty/client/Address.java     |   96 -
 .../eclipse/jetty/client/AsyncContentProvider.java |   45 +
 .../eclipse/jetty/client/AsyncHttpConnection.java  |  269 -
 .../client/AuthenticationProtocolHandler.java      |  195 +
 .../jetty/client/BlockingHttpConnection.java       |  314 --
 .../org/eclipse/jetty/client/CachedExchange.java   |   75 -
 .../org/eclipse/jetty/client/ConnectionPool.java   |  410 ++
 .../org/eclipse/jetty/client/ContentDecoder.java   |   86 +
 .../org/eclipse/jetty/client/ContentExchange.java  |  135 -
 .../jetty/client/ContinueProtocolHandler.java      |  120 +
 .../eclipse/jetty/client/GZIPContentDecoder.java   |  359 ++
 .../jetty/client/HttpAuthenticationStore.java      |   93 +
 .../java/org/eclipse/jetty/client/HttpChannel.java |  142 +
 .../java/org/eclipse/jetty/client/HttpClient.java  | 1379 ++---
 .../eclipse/jetty/client/HttpClientTransport.java  |   71 +
 .../org/eclipse/jetty/client/HttpConnection.java   |  175 +
 .../java/org/eclipse/jetty/client/HttpContent.java |  217 +
 .../eclipse/jetty/client/HttpContentResponse.java  |  138 +
 .../org/eclipse/jetty/client/HttpConversation.java |  149 +
 .../org/eclipse/jetty/client/HttpDestination.java  |  781 +--
 .../eclipse/jetty/client/HttpEventListener.java    |   68 -
 .../jetty/client/HttpEventListenerWrapper.java     |  167 -
 .../org/eclipse/jetty/client/HttpExchange.java     | 1358 +----
 .../java/org/eclipse/jetty/client/HttpProxy.java   |  205 +
 .../org/eclipse/jetty/client/HttpReceiver.java     |  583 +++
 .../org/eclipse/jetty/client/HttpRedirector.java   |  335 ++
 .../java/org/eclipse/jetty/client/HttpRequest.java |  815 +++
 .../eclipse/jetty/client/HttpRequestException.java |   37 +
 .../org/eclipse/jetty/client/HttpResponse.java     |  111 +
 .../jetty/client/HttpResponseException.java        |   37 +
 .../java/org/eclipse/jetty/client/HttpSender.java  |  908 ++++
 .../jetty/client/LeakTrackingConnectionPool.java   |   92 +
 .../jetty/client/MultiplexHttpDestination.java     |  166 +
 .../main/java/org/eclipse/jetty/client/Origin.java |  124 +
 .../jetty/client/PoolingHttpDestination.java       |  235 +
 .../org/eclipse/jetty/client/ProtocolHandler.java  |   29 +
 .../client/ProxyAuthenticationProtocolHandler.java |   65 +
 .../eclipse/jetty/client/ProxyConfiguration.java   |  170 +
 .../org/eclipse/jetty/client/RedirectListener.java |  212 -
 .../jetty/client/RedirectProtocolHandler.java      |   56 +
 .../org/eclipse/jetty/client/RequestNotifier.java  |  260 +
 .../org/eclipse/jetty/client/ResponseNotifier.java |  291 ++
 .../org/eclipse/jetty/client/SelectConnector.java  |  449 --
 .../org/eclipse/jetty/client/SocketConnector.java  |  110 -
 .../java/org/eclipse/jetty/client/Socks4Proxy.java |  241 +
 .../org/eclipse/jetty/client/Synchronizable.java   |   45 +
 .../jetty/client/TimeoutCompleteListener.java      |   84 +
 .../client/WWWAuthenticationProtocolHandler.java   |   63 +
 .../eclipse/jetty/client/api/Authentication.java   |  138 +
 .../jetty/client/api/AuthenticationStore.java      |   78 +
 .../org/eclipse/jetty/client/api/Connection.java   |   46 +
 .../eclipse/jetty/client/api/ContentProvider.java  |   54 +
 .../eclipse/jetty/client/api/ContentResponse.java  |   46 +
 .../org/eclipse/jetty/client/api/Destination.java  |   64 +
 .../java/org/eclipse/jetty/client/api/Request.java |  574 ++
 .../org/eclipse/jetty/client/api/Response.java     |  270 +
 .../java/org/eclipse/jetty/client/api/Result.java  |  120 +
 .../org/eclipse/jetty/client/api/package-info.java |   23 +
 .../jetty/client/http/HttpChannelOverHTTP.java     |  129 +
 .../client/http/HttpClientTransportOverHTTP.java   |   65 +
 .../jetty/client/http/HttpConnectionOverHTTP.java  |  221 +
 .../jetty/client/http/HttpDestinationOverHTTP.java |   38 +
 .../jetty/client/http/HttpReceiverOverHTTP.java    |  317 ++
 .../jetty/client/http/HttpSenderOverHTTP.java      |  250 +
 .../org/eclipse/jetty/client/package-info.java     |   51 +
 .../jetty/client/security/Authentication.java      |   33 -
 .../jetty/client/security/BasicAuthentication.java |   57 -
 .../client/security/DigestAuthentication.java      |  141 -
 .../jetty/client/security/HashRealmResolver.java   |   45 -
 .../jetty/client/security/ProxyAuthorization.java  |   57 -
 .../org/eclipse/jetty/client/security/Realm.java   |   32 -
 .../jetty/client/security/RealmResolver.java       |   28 -
 .../jetty/client/security/SecurityListener.java    |  276 -
 .../jetty/client/security/SimpleRealmResolver.java |   44 -
 .../client/util/AbstractTypedContentProvider.java  |   37 +
 .../jetty/client/util/BasicAuthentication.java     |  109 +
 .../client/util/BufferingResponseListener.java     |  192 +
 .../client/util/ByteBufferContentProvider.java     |   94 +
 .../jetty/client/util/BytesContentProvider.java    |   87 +
 .../jetty/client/util/DeferredContentProvider.java |  341 ++
 .../jetty/client/util/DigestAuthentication.java    |  282 +
 .../jetty/client/util/FormContentProvider.java     |   77 +
 .../jetty/client/util/FutureResponseListener.java  |  121 +
 .../client/util/InputStreamContentProvider.java    |  263 +
 .../client/util/InputStreamResponseListener.java   |  349 ++
 .../client/util/OutputStreamContentProvider.java   |  154 +
 .../jetty/client/util/PathContentProvider.java     |  161 +
 .../jetty/client/util/StringContentProvider.java   |   51 +
 .../eclipse/jetty/client/util/package-info.java    |   23 +
 .../eclipse/jetty/client/webdav/MkcolExchange.java |   64 -
 .../jetty/client/webdav/PropfindExchange.java      |   57 -
 .../jetty/client/webdav/WebdavListener.java        |  332 --
 .../client/webdav/WebdavSupportedExchange.java     |   75 -
 .../jetty/client/AbstractConnectionTest.java       |  443 --
 .../jetty/client/AbstractHttpClientServerTest.java |  104 +
 .../client/AbstractHttpExchangeCancelTest.java     |  500 --
 .../client/AsyncCallbackHttpExchangeTest.java      |  110 -
 .../jetty/client/AsyncSelectConnectionTest.java    |   73 -
 .../jetty/client/AsyncSslHttpExchangeTest.java     |   53 -
 .../jetty/client/AsyncSslSecurityListenerTest.java |   36 -
 .../client/BlockingHttpExchangeCancelTest.java     |   60 -
 .../jetty/client/CachedHeadersIsolationTest.java   |  140 -
 .../eclipse/jetty/client/ContentExchangeTest.java  |  389 --
 .../eclipse/jetty/client/ContentResponseTest.java  |  124 +
 .../test/java/org/eclipse/jetty/client/Curl.java   |  200 -
 .../eclipse/jetty/client/EmptyServerHandler.java   |   37 +
 .../org/eclipse/jetty/client/ErrorStatusTest.java  |  276 -
 .../ExpirationWithLimitedConnectionsTest.java      |  197 -
 .../java/org/eclipse/jetty/client/ExpireTest.java  |  159 -
 .../ExternalKeyStoreAsyncSslHttpExchangeTest.java  |   37 -
 .../org/eclipse/jetty/client/ExternalSiteTest.java |  157 +-
 .../jetty/client/GZIPContentDecoderTest.java       |  292 ++
 .../jetty/client/HostnameVerificationTest.java     |  172 +
 .../eclipse/jetty/client/Http100ContinueTest.java  |  245 -
 .../java/org/eclipse/jetty/client/HttpAsserts.java |   47 -
 .../jetty/client/HttpClientAsyncContentTest.java   |  118 +
 .../jetty/client/HttpClientAuthenticationTest.java |  323 ++
 .../jetty/client/HttpClientChunkedContentTest.java |  216 +
 .../jetty/client/HttpClientContinueTest.java       |  724 +++
 .../jetty/client/HttpClientCustomProxyTest.java    |  246 +
 .../eclipse/jetty/client/HttpClientDuplexTest.java |  385 --
 .../client/HttpClientExplicitConnectionTest.java   |  101 +
 .../jetty/client/HttpClientFailureTest.java        |  270 +
 .../eclipse/jetty/client/HttpClientGZIPTest.java   |  209 +
 .../eclipse/jetty/client/HttpClientLoadTest.java   |  317 ++
 .../eclipse/jetty/client/HttpClientProxyTest.java  |  160 +
 .../jetty/client/HttpClientRedirectTest.java       |  490 ++
 .../eclipse/jetty/client/HttpClientStreamTest.java | 1127 ++++
 .../client/HttpClientSynchronizationTest.java      |  108 +
 .../org/eclipse/jetty/client/HttpClientTest.java   | 1487 ++++++
 .../jetty/client/HttpClientTimeoutTest.java        |  509 ++
 .../eclipse/jetty/client/HttpClientURITest.java    |  474 ++
 .../HttpClientUploadDuringServerShutdown.java      |  277 +
 .../jetty/client/HttpConnectionLifecycleTest.java  |  528 ++
 .../org/eclipse/jetty/client/HttpCookieTest.java   |  154 +
 .../jetty/client/HttpDestinationQueueTest.java     |  225 -
 .../org/eclipse/jetty/client/HttpExchangeTest.java |  695 ---
 .../eclipse/jetty/client/HttpGetRedirectTest.java  |  237 -
 .../org/eclipse/jetty/client/HttpHeadersTest.java  |  193 -
 .../eclipse/jetty/client/HttpRequestAbortTest.java |  561 ++
 .../jetty/client/HttpResponseAbortTest.java        |  259 +
 .../client/HttpResponseConcurrentAbortTest.java    |  192 +
 .../jetty/client/HttpsProxyAuthenticationTest.java |  123 -
 .../jetty/client/HttpsViaBrokenHttpProxyTest.java  |  141 -
 .../org/eclipse/jetty/client/IdleTimeoutTest.java  |  147 -
 .../client/NonBlockingHttpExchangeCancelTest.java  |   64 -
 .../jetty/client/ProxyConfigurationTest.java       |   66 +
 .../eclipse/jetty/client/ProxyFakeTunnelTest.java  |  237 -
 .../eclipse/jetty/client/ProxyTunnellingTest.java  |  414 --
 .../jetty/client/SecuredContentExchangeTest.java   |  108 -
 .../jetty/client/SecuredErrorStatusTest.java       |  193 -
 .../eclipse/jetty/client/SecurityListenerTest.java |  355 --
 .../eclipse/jetty/client/SelectConnectionTest.java |   30 -
 .../test/java/org/eclipse/jetty/client/Siege.java  |  229 -
 .../eclipse/jetty/client/SluggishServerTest.java   |  264 -
 .../eclipse/jetty/client/SocketConnectionTest.java |  101 -
 .../org/eclipse/jetty/client/Socks4ProxyTest.java  |  182 +
 .../eclipse/jetty/client/SslBytesClientTest.java   |  166 -
 .../eclipse/jetty/client/SslBytesServerTest.java   | 1783 -------
 .../org/eclipse/jetty/client/SslBytesTest.java     |  371 --
 .../jetty/client/SslCertSecuredExchangeTest.java   |  174 -
 .../jetty/client/SslContentExchangeTest.java       |   74 -
 .../eclipse/jetty/client/SslHttpExchangeTest.java  |  110 -
 .../client/SslSecuredContentExchangeTest.java      |  114 -
 .../jetty/client/SslSecuredErrorStatusTest.java    |  191 -
 .../jetty/client/SslSecurityListenerTest.java      |  269 -
 .../client/SslSelectChannelValidationTest.java     |   30 -
 .../jetty/client/SslSocketValidationTest.java      |   30 -
 .../jetty/client/SslValidationTestBase.java        |  116 -
 .../eclipse/jetty/client/TimeoutExchangeTest.java  |  379 --
 .../java/org/eclipse/jetty/client/TimeoutTest.java |  438 --
 .../eclipse/jetty/client/UnexpectedDataTest.java   |  181 -
 .../eclipse/jetty/client/WebSocketUpgradeTest.java |  265 -
 .../eclipse/jetty/client/WebdavListenerTest.java   |  144 -
 .../java/org/eclipse/jetty/client/api/Usage.java   |  347 ++
 .../AbstractSslServerAndClientCreator.java         |   60 -
 .../AsyncSslServerAndClientCreator.java            |   42 -
 ...rnalKeyStoreAsyncSslServerAndClientCreator.java |   45 -
 .../client/helperClasses/GenericServerHandler.java |  109 -
 .../helperClasses/HttpServerAndClientCreator.java  |   54 -
 .../helperClasses/ServerAndClientCreator.java      |   29 -
 .../helperClasses/SslServerAndClientCreator.java   |   37 -
 .../client/http/HttpDestinationOverHTTPTest.java   |  275 +
 .../client/http/HttpReceiverOverHTTPTest.java      |  250 +
 .../jetty/client/http/HttpSenderOverHTTPTest.java  |  302 ++
 .../client/security/SecurityResolverTest.java      |   51 -
 .../jetty/client/ssl/SslBytesClientTest.java       |  366 ++
 .../jetty/client/ssl/SslBytesServerTest.java       | 1978 +++++++
 .../org/eclipse/jetty/client/ssl/SslBytesTest.java |  385 ++
 .../client/util/DeferredContentProviderTest.java   |  156 +
 .../util/InputStreamContentProviderTest.java       |  185 +
 .../client/util/TypedContentProviderTest.java      |  141 +
 jetty-client/src/test/resources/foo.txt            |    1 -
 .../src/test/resources/jetty-logging.properties    |    4 +-
 .../src/test}/resources/keystore.jks               |  Bin
 jetty-client/src/test/resources/realm.properties   |   25 +-
 .../src/test}/resources/truststore.jks             |  Bin
 jetty-continuation/pom.xml                         |   26 +-
 .../eclipse/jetty/continuation/Continuation.java   |    5 -
 .../jetty/continuation/ContinuationFilter.java     |   20 +-
 .../jetty/continuation/ContinuationListener.java   |   16 +-
 .../jetty/continuation/ContinuationSupport.java    |   49 +-
 .../jetty/continuation/FauxContinuation.java       |   99 +-
 .../jetty/continuation/Jetty6Continuation.java     |  267 -
 .../jetty/continuation/Servlet3Continuation.java   |  131 +-
 .../eclipse/jetty/continuation/package-info.java   |   23 +
 jetty-deploy/pom.xml                               |   11 +-
 .../src/main/config/etc/jetty-contexts.xml         |   23 -
 jetty-deploy/src/main/config/etc/jetty-deploy.xml  |   88 +-
 jetty-deploy/src/main/config/etc/jetty-webapps.xml |   26 -
 jetty-deploy/src/main/config/modules/deploy.mod    |   21 +
 .../main/java/org/eclipse/jetty/deploy/App.java    |    1 +
 .../java/org/eclipse/jetty/deploy/AppProvider.java |    2 -
 .../org/eclipse/jetty/deploy/ContextDeployer.java  |  473 --
 .../eclipse/jetty/deploy/DeploymentManager.java    |   40 +-
 .../jetty/deploy/FileConfigurationManager.java     |   75 -
 .../deploy/PropertiesConfigurationManager.java     |   92 +
 .../org/eclipse/jetty/deploy/WebAppDeployer.java   |  328 --
 .../deploy/bindings/GlobalWebappConfigBinding.java |   23 +-
 .../jetty/deploy/bindings/StandardStarter.java     |   12 +-
 .../jetty/deploy/bindings/StandardStopper.java     |   12 +-
 .../jetty/deploy/bindings/StandardUndeployer.java  |    2 +
 .../jetty/deploy/bindings/package-info.java        |   23 +
 .../eclipse/jetty/deploy/graph/package-info.java   |   23 +
 .../org/eclipse/jetty/deploy/jmx/package-info.java |   23 +
 .../org/eclipse/jetty/deploy/package-info.java     |   23 +
 .../jetty/deploy/providers/ContextProvider.java    |  150 -
 .../deploy/providers/ScanningAppProvider.java      |  100 +-
 .../jetty/deploy/providers/WebAppProvider.java     |  370 +-
 .../jetty/deploy/providers/package-info.java       |   23 +
 .../eclipse/jetty/deploy/util/package-info.java    |   23 +
 .../deploy/jmx/ContextDeployer-mbean.properties    |    9 -
 .../deploy/jmx/DeploymentManager-mbean.properties  |   10 -
 .../deploy/jmx/WebAppDeployer-mbean.properties     |   14 -
 .../providers/jmx/ContextProvider-mbean.properties |    1 -
 .../jmx/ScanningAppProvider-mbean.properties       |    4 -
 .../providers/jmx/WebAppProvider-mbean.properties  |    7 -
 .../org/eclipse/jetty/deploy/AppLifeCycleTest.java |    6 +-
 .../deploy/DeploymentManagerLifeCyclePathTest.java |   14 +-
 .../jetty/deploy/DeploymentManagerTest.java        |    9 +-
 .../eclipse/jetty/deploy/JmxServiceConnection.java |  154 +
 .../bindings/GlobalWebappConfigBindingTest.java    |   17 +-
 .../org/eclipse/jetty/deploy/graph/GraphTest.java  |   32 +-
 .../ScanningAppProviderRuntimeUpdatesTest.java     |   22 +-
 .../providers/ScanningAppProviderStartupTest.java  |    7 +-
 .../jetty/deploy/providers/WebAppProviderTest.java |   19 +-
 .../jetty/deploy/test/XmlConfiguredJetty.java      |  129 +-
 .../src/test/resources/binding-test-contexts-1.xml |   27 +-
 .../src/test/resources/context-binding-test-1.xml  |    4 +-
 jetty-deploy/src/test/resources/contexts/foo.xml   |    8 -
 jetty-deploy/src/test/resources/etc/webdefault.xml |    4 +-
 .../src/test/resources/jetty-deploy-wars.xml       |    8 +-
 .../test/resources/jetty-deploymgr-contexts.xml    |   41 +-
 jetty-deploy/src/test/resources/jetty-http.xml     |   41 +
 .../src/test/resources/jetty-logging.properties    |    3 +
 jetty-deploy/src/test/resources/jetty.xml          |  190 +-
 jetty-deploy/src/test/resources/webapps/foo.xml    |    8 +
 jetty-distribution/pom.xml                         |  651 ++-
 jetty-distribution/src/main/resources/README.TXT   |   68 +
 jetty-distribution/src/main/resources/README.txt   |   70 -
 .../main/resources/bin/README.jetty-cygwin.txt.txt |   11 -
 .../src/main/resources/bin/jetty-cygwin.sh         |  677 ---
 .../src/main/resources/bin/jetty-xinetd.sh         |   14 -
 jetty-distribution/src/main/resources/bin/jetty.sh |  354 +-
 .../main/resources/contexts-available/README.TXT   |    3 -
 .../resources/contexts-available/resources.xml     |   27 -
 .../src/main/resources/contexts/README.TXT         |   15 -
 .../src/main/resources/contexts/javadoc.xml        |   27 -
 .../src/main/resources/demo-base/etc/keystore      |  Bin 0 -> 1416 bytes
 .../main/resources/demo-base/webapps/README.TXT    |   12 +
 .../demo-base/webapps/ROOT/images/jetty-header.jpg |  Bin 0 -> 103289 bytes
 .../demo-base/webapps/ROOT/images/webtide_logo.jpg |  Bin 0 -> 17678 bytes
 .../resources/demo-base/webapps/ROOT/index.html    |   72 +
 .../resources/demo-base/webapps/ROOT/jetty.css     |  351 ++
 .../resources/demo-base/webapps/example-moved.xml  |   12 +
 .../src/main/resources/etc/hawtio.xml              |   16 +
 .../src/main/resources/etc/jamon.xml               |   32 +
 .../src/main/resources/etc/jetty-started.xml       |    2 +-
 .../src/main/resources/etc/jetty.conf              |    4 +-
 .../src/main/resources/etc/jminix.xml              |   12 +
 .../src/main/resources/etc/jolokia.xml             |   16 +
 .../resources/{webapps => modules}/.donotdelete    |    0
 .../src/main/resources/modules/hawtio.mod          |   28 +
 .../src/main/resources/modules/jamon.mod           |   30 +
 .../src/main/resources/modules/jminix.mod          |   41 +
 .../src/main/resources/modules/jolokia.mod         |   19 +
 .../src/main/resources/modules/jsp.mod             |   21 +
 .../src/main/resources/modules/jstl.mod            |   14 +
 .../src/main/resources/modules/protonego.mod       |   24 +
 .../src/main/resources/modules/setuid.mod          |   19 +
 jetty-distribution/src/main/resources/start.ini    |   80 +-
 .../src/main/resources/webapps/README.TXT          |   33 +
 jetty-fcgi/fcgi-client/pom.xml                     |   40 +
 .../src/main/java/org/eclipse/jetty/fcgi/FCGI.java |  136 +
 .../fcgi/client/http/HttpChannelOverFCGI.java      |  176 +
 .../client/http/HttpClientTransportOverFCGI.java   |   85 +
 .../fcgi/client/http/HttpConnectionOverFCGI.java   |  425 ++
 .../fcgi/client/http/HttpDestinationOverFCGI.java  |   38 +
 .../fcgi/client/http/HttpReceiverOverFCGI.java     |   71 +
 .../jetty/fcgi/client/http/HttpSenderOverFCGI.java |  128 +
 .../http/MultiplexHttpDestinationOverFCGI.java     |   38 +
 .../jetty/fcgi/generator/ClientGenerator.java      |  166 +
 .../org/eclipse/jetty/fcgi/generator/Flusher.java  |  143 +
 .../eclipse/jetty/fcgi/generator/Generator.java    |  155 +
 .../jetty/fcgi/generator/ServerGenerator.java      |  134 +
 .../fcgi/parser/BeginRequestContentParser.java     |  127 +
 .../eclipse/jetty/fcgi/parser/ClientParser.java    |  110 +
 .../eclipse/jetty/fcgi/parser/ContentParser.java   |   53 +
 .../jetty/fcgi/parser/EndRequestContentParser.java |  130 +
 .../eclipse/jetty/fcgi/parser/HeaderParser.java    |  158 +
 .../jetty/fcgi/parser/ParamsContentParser.java     |  259 +
 .../java/org/eclipse/jetty/fcgi/parser/Parser.java |  161 +
 .../jetty/fcgi/parser/ResponseContentParser.java   |  327 ++
 .../eclipse/jetty/fcgi/parser/ServerParser.java    |   54 +
 .../jetty/fcgi/parser/StreamContentParser.java     |  117 +
 .../jetty/fcgi/generator/ClientGeneratorTest.java  |  191 +
 .../jetty/fcgi/parser/ClientParserTest.java        |  255 +
 .../src/test/resources/jetty-logging.properties    |    3 +
 jetty-fcgi/fcgi-server/pom.xml                     |   73 +
 .../fcgi-server/src/main/config/modules/fcgi.mod   |   15 +
 .../jetty/fcgi/server/HttpChannelOverFCGI.java     |  206 +
 .../jetty/fcgi/server/HttpTransportOverFCGI.java   |  127 +
 .../jetty/fcgi/server/ServerFCGIConnection.java    |  196 +
 .../fcgi/server/ServerFCGIConnectionFactory.java   |   49 +
 .../fcgi/server/proxy/FastCGIProxyServlet.java     |  188 +
 .../jetty/fcgi/server/proxy/TryFilesFilter.java    |  141 +
 .../fcgi/server/AbstractHttpClientServerTest.java  |  123 +
 .../jetty/fcgi/server/EmptyServerHandler.java      |   36 +
 .../fcgi/server/ExternalFastCGIServerTest.java     |   55 +
 .../eclipse/jetty/fcgi/server/HttpClientTest.java  |  703 +++
 .../server/proxy/DrupalSPDYFastCGIProxyServer.java |   77 +
 .../fcgi/server/proxy/FastCGIProxyServletTest.java |  174 +
 .../fcgi/server/proxy/TryFilesFilterTest.java      |  107 +
 .../proxy/WordPressSPDYFastCGIProxyServer.java     |   85 +
 .../src/test/resources/jetty-logging.properties    |    3 +
 .../fcgi-server}/src/test/resources/keystore.jks   |  Bin
 .../fcgi-server}/src/test/resources/truststore.jks |  Bin
 jetty-fcgi/pom.xml                                 |   28 +
 jetty-http-spi/pom.xml                             |    6 +-
 .../jetty/http/spi/DelegatingThreadPool.java       |  139 +
 .../jetty/http/spi/HttpSpiContextHandler.java      |   23 +-
 .../eclipse/jetty/http/spi/JettyHttpContext.java   |    4 +-
 .../eclipse/jetty/http/spi/JettyHttpExchange.java  |   32 +-
 .../eclipse/jetty/http/spi/JettyHttpServer.java    |  534 +-
 .../jetty/http/spi/JettyHttpServerProvider.java    |   19 +-
 .../jetty/http/spi/ThreadPoolExecutorAdapter.java  |  124 -
 jetty-http/pom.xml                                 |   15 +-
 .../org/eclipse/jetty/http/AbstractGenerator.java  |  534 --
 .../java/org/eclipse/jetty/http/DateGenerator.java |  164 +
 .../java/org/eclipse/jetty/http/DateParser.java    |  109 +
 .../org/eclipse/jetty/http/EncodedHttpURI.java     |  183 -
 .../java/org/eclipse/jetty/http/Generator.java     |   96 -
 .../java/org/eclipse/jetty/http/HttpBuffers.java   |  108 -
 .../org/eclipse/jetty/http/HttpBuffersImpl.java    |  238 -
 .../java/org/eclipse/jetty/http/HttpContent.java   |  107 +-
 .../java/org/eclipse/jetty/http/HttpCookie.java    |  187 +-
 .../java/org/eclipse/jetty/http/HttpException.java |   94 -
 .../java/org/eclipse/jetty/http/HttpField.java     |   89 +
 .../java/org/eclipse/jetty/http/HttpFields.java    | 1238 ++---
 .../java/org/eclipse/jetty/http/HttpGenerator.java | 1734 +++---
 .../java/org/eclipse/jetty/http/HttpHeader.java    |  178 +
 .../org/eclipse/jetty/http/HttpHeaderValue.java    |  104 +
 .../org/eclipse/jetty/http/HttpHeaderValues.java   |   88 -
 .../java/org/eclipse/jetty/http/HttpHeaders.java   |  241 -
 .../java/org/eclipse/jetty/http/HttpMethod.java    |  187 +
 .../java/org/eclipse/jetty/http/HttpMethods.java   |   64 -
 .../java/org/eclipse/jetty/http/HttpParser.java    | 2393 +++++----
 .../java/org/eclipse/jetty/http/HttpScheme.java    |   79 +
 .../java/org/eclipse/jetty/http/HttpSchemes.java   |   38 -
 .../java/org/eclipse/jetty/http/HttpTester.java    |  367 ++
 .../java/org/eclipse/jetty/http/HttpTokens.java    |   14 +-
 .../main/java/org/eclipse/jetty/http/HttpURI.java  |  155 +-
 .../java/org/eclipse/jetty/http/HttpVersion.java   |  173 +
 .../java/org/eclipse/jetty/http/HttpVersions.java  |   47 -
 .../java/org/eclipse/jetty/http/MimeTypes.java     |  475 +-
 .../main/java/org/eclipse/jetty/http/Parser.java   |   47 -
 .../main/java/org/eclipse/jetty/http/PathMap.java  |  257 +-
 .../jetty/http/gzip/AbstractCompressedStream.java  |  388 --
 .../jetty/http/gzip/CompressedResponseWrapper.java |  487 --
 .../java/org/eclipse/jetty/http/package-info.java  |   23 +
 .../eclipse/jetty/http/ssl/SslContextFactory.java  |   41 -
 .../org/eclipse/jetty/http/mime.properties         |  365 +-
 .../org/eclipse/jetty/http/HttpFieldsTest.java     |  363 +-
 .../jetty/http/HttpGeneratorClientTest.java        |  576 +-
 .../jetty/http/HttpGeneratorServerHTTPTest.java    |  368 ++
 .../jetty/http/HttpGeneratorServerTest.java        |  452 ++
 .../org/eclipse/jetty/http/HttpGeneratorTest.java  |  313 --
 .../org/eclipse/jetty/http/HttpParserTest.java     | 2099 ++++++--
 .../org/eclipse/jetty/http/HttpStatusCodeTest.java |    2 +-
 .../java/org/eclipse/jetty/http/HttpURITest.java   |   60 +-
 .../java/org/eclipse/jetty/http/MimeTypesTest.java |   59 +-
 .../java/org/eclipse/jetty/http/PathMapTest.java   |   77 +-
 jetty-io/pom.xml                                   |    7 +-
 .../java/org/eclipse/jetty/io/AbstractBuffer.java  |  728 ---
 .../java/org/eclipse/jetty/io/AbstractBuffers.java |  168 -
 .../org/eclipse/jetty/io/AbstractConnection.java   |  566 +-
 .../org/eclipse/jetty/io/AbstractEndPoint.java     |  205 +
 .../org/eclipse/jetty/io/ArrayByteBufferPool.java  |  133 +
 .../java/org/eclipse/jetty/io/AsyncEndPoint.java   |   83 -
 .../src/main/java/org/eclipse/jetty/io/Buffer.java |  380 --
 .../java/org/eclipse/jetty/io/BufferCache.java     |  169 -
 .../java/org/eclipse/jetty/io/BufferDateCache.java |   61 -
 .../main/java/org/eclipse/jetty/io/BufferUtil.java |  359 --
 .../main/java/org/eclipse/jetty/io/Buffers.java    |   38 -
 .../java/org/eclipse/jetty/io/BuffersFactory.java  |   29 -
 .../java/org/eclipse/jetty/io/ByteArrayBuffer.java |  439 --
 .../org/eclipse/jetty/io/ByteArrayEndPoint.java    |  403 +-
 .../java/org/eclipse/jetty/io/ByteBufferPool.java  |   51 +
 .../java/org/eclipse/jetty/io/ChannelEndPoint.java |  232 +
 .../eclipse/jetty/io/ClientConnectionFactory.java  |   90 +
 .../org/eclipse/jetty/io/ConnectedEndPoint.java    |   25 -
 .../main/java/org/eclipse/jetty/io/Connection.java |  117 +-
 .../main/java/org/eclipse/jetty/io/EndPoint.java   |  267 +-
 .../java/org/eclipse/jetty/io/FillInterest.java    |  135 +
 .../java/org/eclipse/jetty/io/IdleTimeout.java     |  189 +
 .../jetty/io/LeakTrackingByteBufferPool.java       |  125 +
 .../org/eclipse/jetty/io/MappedByteBufferPool.java |  134 +
 .../jetty/io/NegotiatingClientConnection.java      |  130 +
 .../io/NegotiatingClientConnectionFactory.java     |   35 +
 .../eclipse/jetty/io/NetworkTrafficListener.java   |   11 +-
 .../io/NetworkTrafficSelectChannelEndPoint.java    |  154 +
 .../java/org/eclipse/jetty/io/PooledBuffers.java   |  122 -
 .../eclipse/jetty/io/SelectChannelEndPoint.java    |  211 +
 .../java/org/eclipse/jetty/io/SelectorManager.java | 1064 ++++
 .../java/org/eclipse/jetty/io/SimpleBuffers.java   |  117 -
 .../org/eclipse/jetty/io/ThreadLocalBuffers.java   |  135 -
 .../org/eclipse/jetty/io/UncheckedIOException.java |   48 -
 .../org/eclipse/jetty/io/UncheckedPrintWriter.java |    3 +-
 .../src/main/java/org/eclipse/jetty/io/View.java   |  251 -
 .../java/org/eclipse/jetty/io/WriteFlusher.java    |  508 ++
 .../org/eclipse/jetty/io/WriterOutputStream.java   |    5 +-
 .../org/eclipse/jetty/io/bio/SocketEndPoint.java   |  286 -
 .../org/eclipse/jetty/io/bio/StreamEndPoint.java   |  325 --
 .../org/eclipse/jetty/io/bio/StringEndPoint.java   |   94 -
 .../org/eclipse/jetty/io/nio/AsyncConnection.java  |   28 -
 .../org/eclipse/jetty/io/nio/ChannelEndPoint.java  |  509 --
 .../org/eclipse/jetty/io/nio/DirectNIOBuffer.java  |  354 --
 .../eclipse/jetty/io/nio/IndirectNIOBuffer.java    |   62 -
 .../java/org/eclipse/jetty/io/nio/NIOBuffer.java   |   37 -
 .../nio/NetworkTrafficSelectChannelEndPoint.java   |  148 -
 .../jetty/io/nio/RandomAccessFileBuffer.java       |  196 -
 .../jetty/io/nio/SelectChannelEndPoint.java        |  868 ---
 .../org/eclipse/jetty/io/nio/SelectorManager.java  | 1034 ----
 .../org/eclipse/jetty/io/nio/SslConnection.java    |  902 ----
 .../java/org/eclipse/jetty/io/package-info.java    |   23 +
 .../jetty/io/ssl/SslClientConnectionFactory.java   |   73 +
 .../org/eclipse/jetty/io/ssl/SslConnection.java    |  942 ++++
 .../org/eclipse/jetty/io/ssl/package-info.java     |   23 +
 .../eclipse/jetty/io/ArrayByteBufferPoolTest.java  |  147 +
 .../java/org/eclipse/jetty/io/BufferCacheTest.java |  154 -
 .../test/java/org/eclipse/jetty/io/BufferTest.java |  265 -
 .../java/org/eclipse/jetty/io/BufferUtilTest.java  |  122 -
 .../eclipse/jetty/io/ByteArrayEndPointTest.java    |  338 ++
 .../org/eclipse/jetty/io/ChannelEndPointTest.java  |   60 +
 .../java/org/eclipse/jetty/io/EndPointTest.java    |   88 +-
 .../src/test/java/org/eclipse/jetty/io/IOTest.java |  392 +-
 .../java/org/eclipse/jetty/io/IdleTimeoutTest.java |  158 +
 .../eclipse/jetty/io/MappedByteBufferPoolTest.java |  131 +
 .../test/java/org/eclipse/jetty/io/NIOTest.java    |  136 +
 .../io/SelectChannelEndPointInterestsTest.java     |  213 +
 .../jetty/io/SelectChannelEndPointSslTest.java     |  348 ++
 .../jetty/io/SelectChannelEndPointTest.java        |  646 +++
 .../org/eclipse/jetty/io/SelectorManagerTest.java  |  161 +
 .../org/eclipse/jetty/io/SslConnectionTest.java    |  391 ++
 .../eclipse/jetty/io/ThreadLocalBuffersTest.java   |  228 -
 .../org/eclipse/jetty/io/WriteFlusherTest.java     |  735 +++
 .../eclipse/jetty/io/bio/SocketEndPointTest.java   |   56 -
 .../eclipse/jetty/io/nio/ChannelEndPointTest.java  |   61 -
 .../java/org/eclipse/jetty/io/nio/NIOTest.java     |  136 -
 .../jetty/io/nio/SelectChannelEndPointSslTest.java |  221 -
 .../jetty/io/nio/SelectChannelEndPointTest.java    |  457 --
 .../src/test/resources/jetty-logging.properties    |    2 +
 jetty-jaas/pom.xml                                 |   84 +
 jetty-jaas/src/main/config/etc/jetty-jaas.xml      |   17 +
 jetty-jaas/src/main/config/modules/jaas.mod        |   16 +
 .../java/org/eclipse/jetty/jaas/JAASGroup.java     |  152 +
 .../org/eclipse/jetty/jaas/JAASLoginService.java   |  334 ++
 .../java/org/eclipse/jetty/jaas/JAASPrincipal.java |   89 +
 .../main/java/org/eclipse/jetty/jaas/JAASRole.java |   42 +
 .../org/eclipse/jetty/jaas/JAASUserPrincipal.java  |   79 +
 .../org/eclipse/jetty/jaas/RoleCheckPolicy.java    |   36 +
 .../eclipse/jetty/jaas/StrictRoleCheckPolicy.java  |   63 +
 .../jaas/callback/AbstractCallbackHandler.java     |   60 +
 .../jaas/callback/DefaultCallbackHandler.java      |   97 +
 .../jetty/jaas/callback/ObjectCallback.java        |   67 +
 .../jaas/callback/RequestParameterCallback.java    |   61 +
 .../eclipse/jetty/jaas/callback/package-info.java  |   23 +
 .../java/org/eclipse/jetty/jaas/package-info.java  |   23 +
 .../jaas/spi/AbstractDatabaseLoginModule.java      |  143 +
 .../jetty/jaas/spi/AbstractLoginModule.java        |  303 ++
 .../jetty/jaas/spi/DataSourceLoginModule.java      |   90 +
 .../eclipse/jetty/jaas/spi/JDBCLoginModule.java    |  127 +
 .../eclipse/jetty/jaas/spi/LdapLoginModule.java    |  689 +++
 .../jetty/jaas/spi/PropertyFileLoginModule.java    |  135 +
 .../java/org/eclipse/jetty/jaas/spi/UserInfo.java  |   73 +
 .../org/eclipse/jetty/jaas/spi/package-info.java   |   23 +
 jetty-jaspi/pom.xml                                |   38 +-
 jetty-jaspi/src/main/config/modules/jaspi.mod      |   10 +
 .../jetty/security/jaspi/JaspiAuthenticator.java   |   16 +-
 .../security/jaspi/JaspiAuthenticatorFactory.java  |    7 +-
 .../security/jaspi/ServletCallbackHandler.java     |    4 +-
 .../security/jaspi/callback/package-info.java      |   23 +
 .../security/jaspi/modules/BaseAuthModule.java     |    9 +-
 .../security/jaspi/modules/BasicAuthModule.java    |    8 +-
 .../jaspi/modules/ClientCertAuthModule.java        |    2 +-
 .../security/jaspi/modules/DigestAuthModule.java   |   34 +-
 .../security/jaspi/modules/FormAuthModule.java     |   17 +-
 .../jetty/security/jaspi/modules/package-info.java |   23 +
 .../eclipse/jetty/security/jaspi/package-info.java |   23 +
 .../jetty/security/jaspi/HttpHeaderAuthModule.java |  125 +
 .../eclipse/jetty/security/jaspi/JaspiTest.java    |  167 +
 jetty-jaspi/src/test/resources/jaspi.xml           |   39 +
 jetty-jmx/pom.xml                                  |    7 +-
 jetty-jmx/src/main/config/etc/jetty-jmx-remote.xml |   29 +
 jetty-jmx/src/main/config/etc/jetty-jmx.xml        |   90 +-
 jetty-jmx/src/main/config/modules/jmx-remote.mod   |   18 +
 jetty-jmx/src/main/config/modules/jmx.mod          |   13 +
 .../org/eclipse/jetty/jmx/ConnectorServer.java     |   50 +-
 .../java/org/eclipse/jetty/jmx/MBeanContainer.java |  300 +-
 .../java/org/eclipse/jetty/jmx/ObjectMBean.java    |  622 ++-
 .../java/org/eclipse/jetty/jmx/package-info.java   |   23 +
 .../org/eclipse/jetty/util/log/jmx/LogMBean.java   |   15 +-
 .../eclipse/jetty/util/log/jmx/package-info.java   |   23 +
 .../jmx/AggregateLifeCycle-mbean.properties        |    2 -
 .../util/component/jmx/Dumpable-mbean.properties   |    2 -
 .../util/component/jmx/LifeCycle-mbean.properties  |    9 -
 .../jetty/util/log/jmx/Log-mbean.properties        |    8 -
 .../thread/jmx/QueuedThreadPool-mbean.properties   |   16 -
 .../util/thread/jmx/ThreadPool-mbean.properties    |    4 -
 jetty-jmx/src/test/java/com/acme/Derived.java      |   37 +-
 jetty-jmx/src/test/java/com/acme/Managed.java      |   46 +
 jetty-jmx/src/test/java/com/acme/SuperManaged.java |   29 +
 .../src/test/java/com/acme/jmx/DerivedMBean.java   |   52 +
 .../src/test/java/com/acme/jmx/ManagedMBean.java   |   47 +
 .../org/eclipse/jetty/jmx/ObjectMBeanTest.java     |  203 +-
 .../com/acme/jmx/Derived-mbean.properties          |    0
 .../src/test/resources/jetty-logging.properties    |    2 +
 jetty-jndi/pom.xml                                 |   53 +-
 jetty-jndi/src/main/config/modules/jndi.mod        |   11 +
 .../org/eclipse/jetty/jndi/BindingEnumeration.java |    4 +-
 .../org/eclipse/jetty/jndi/ContextFactory.java     |   90 +-
 .../org/eclipse/jetty/jndi/DataSourceCloser.java   |   23 +-
 .../eclipse/jetty/jndi/InitialContextFactory.java  |   20 +-
 .../java/org/eclipse/jetty/jndi/NamingContext.java |  338 +-
 .../java/org/eclipse/jetty/jndi/NamingUtil.java    |   28 +-
 .../jetty/jndi/factories/MailSessionReference.java |   52 +-
 .../eclipse/jetty/jndi/factories/package-info.java |   23 +
 .../eclipse/jetty/jndi/java/javaNameParser.java    |    2 +-
 .../jetty/jndi/java/javaRootURLContext.java        |   61 +-
 .../jetty/jndi/java/javaURLContextFactory.java     |   10 +-
 .../org/eclipse/jetty/jndi/java/package-info.java  |   23 +
 .../eclipse/jetty/jndi/local/localContextRoot.java |  347 +-
 .../org/eclipse/jetty/jndi/local/package-info.java |   23 +
 .../java/org/eclipse/jetty/jndi/package-info.java  |   23 +
 .../jndi/factories/TestMailSessionReference.java   |    9 +-
 .../java/org/eclipse/jetty/jndi/java/TestJNDI.java |   35 +-
 .../org/eclipse/jetty/jndi/java/TestLocalJNDI.java |   88 +-
 jetty-jsp/pom.xml                                  |   83 +-
 .../main/config/modules/jsp-impl/glassfish-jsp.mod |    8 +
 .../config/modules/jsp-impl/glassfish-jstl.mod     |    6 +
 .../org/eclipse/jetty/jsp/JettyJspServlet.java     |  107 +
 jetty-jspc-maven-plugin/pom.xml                    |  115 +
 .../org/eclipse/jetty/jspc/plugin/JspcMojo.java    |  617 +++
 .../eclipse/jetty/jspc/plugin/package-info.java    |   23 +
 .../META-INF/m2e/lifecycle-mapping-metadata.xml    |   30 +
 jetty-maven-plugin/.gitignore                      |    5 +
 jetty-maven-plugin/pom.xml                         |  168 +
 .../jetty/maven/plugin/AbstractJettyMojo.java      |  842 +++
 .../eclipse/jetty/maven/plugin/ConsoleScanner.java |  161 +
 .../eclipse/jetty/maven/plugin/JettyDeployWar.java |   81 +
 .../jetty/maven/plugin/JettyEffectiveWebXml.java   |  156 +
 .../jetty/maven/plugin/JettyRunForkedMojo.java     |  853 +++
 .../eclipse/jetty/maven/plugin/JettyRunMojo.java   |  630 +++
 .../maven/plugin/JettyRunWarExplodedMojo.java      |  181 +
 .../jetty/maven/plugin/JettyRunWarMojo.java        |  156 +
 .../eclipse/jetty/maven/plugin/JettyServer.java    |  156 +
 .../eclipse/jetty/maven/plugin/JettyStartMojo.java |   59 +
 .../eclipse/jetty/maven/plugin/JettyStopMojo.java  |  136 +
 .../jetty/maven/plugin/JettyWebAppContext.java     |  526 ++
 .../maven/plugin/MavenQuickStartConfiguration.java |  132 +
 .../jetty/maven/plugin/MavenServerConnector.java   |  277 +
 .../maven/plugin/MavenWebInfConfiguration.java     |  302 ++
 .../org/eclipse/jetty/maven/plugin/Overlay.java    |   74 +
 .../eclipse/jetty/maven/plugin/OverlayConfig.java  |  341 ++
 .../org/eclipse/jetty/maven/plugin/PluginLog.java  |   44 +
 .../jetty/maven/plugin/ScanTargetPattern.java      |   88 +
 .../jetty/maven/plugin/SelectiveJarResource.java   |  232 +
 .../org/eclipse/jetty/maven/plugin/Starter.java    |  529 ++
 .../jetty/maven/plugin/SystemProperties.java       |   79 +
 .../eclipse/jetty/maven/plugin/SystemProperty.java |  103 +
 .../eclipse/jetty/maven/plugin/WarPluginInfo.java  |  203 +
 .../eclipse/jetty/maven/plugin/package-info.java   |   23 +
 jetty-monitor/{README.txt => README.TXT}           |    0
 jetty-monitor/pom.xml                              |   11 +-
 .../src/main/config/etc/jetty-monitor.xml          |    4 +-
 jetty-monitor/src/main/config/modules/monitor.mod  |   13 +
 .../org/eclipse/jetty/monitor/ThreadMonitor.java   |    6 +-
 .../monitor/integration/JavaMonitorAction.java     |   32 +-
 .../monitor/integration/JavaMonitorTools.java      |   18 +-
 .../monitor/integration/JavaMonitorTrigger.java    |    1 -
 .../jetty/monitor/integration/package-info.java    |   23 +
 .../org/eclipse/jetty/monitor/jmx/MonitorTask.java |    2 +-
 .../eclipse/jetty/monitor/jmx/NotifierGroup.java   |    1 -
 .../jetty/monitor/jmx/ServiceConnection.java       |    2 +-
 .../eclipse/jetty/monitor/jmx/package-info.java    |   23 +
 .../org/eclipse/jetty/monitor/package-info.java    |   23 +
 .../monitor/thread/ThreadMonitorException.java     |    1 -
 .../jetty/monitor/thread/ThreadMonitorInfo.java    |    1 -
 .../eclipse/jetty/monitor/thread/package-info.java |   23 +
 .../monitor/triggers/AggregateEventTrigger.java    |    1 -
 .../jetty/monitor/triggers/AndEventTrigger.java    |    2 +-
 .../jetty/monitor/triggers/AttrEventTrigger.java   |    1 -
 .../monitor/triggers/EqualToAttrEventTrigger.java  |    1 -
 .../triggers/GreaterThanAttrEventTrigger.java      |    1 -
 .../GreaterThanOrEqualToAttrEventTrigger.java      |    1 -
 .../monitor/triggers/LessThanAttrEventTrigger.java |    1 -
 .../LessThanOrEqualToAttrEventTrigger.java         |    1 -
 .../monitor/triggers/RangeAttrEventTrigger.java    |    1 -
 .../triggers/RangeInclAttrEventTrigger.java        |    1 -
 .../jetty/monitor/triggers/package-info.java       |   23 +
 .../jmx/JavaMonitorTools-mbean.properties          |   12 -
 .../monitor/jmx/ThreadMonitor-mbean.properties     |    1 -
 .../jetty/monitor/AttrEventTriggerTest.java        |   75 +-
 .../org/eclipse/jetty/monitor/RequestCounter.java  |    8 +
 .../eclipse/jetty/monitor/ThreadMonitorTest.java   |    1 -
 .../src/test/resources/jetty-logging.properties    |    3 +
 .../monitor/jmx/RequestCounter-mbean.properties    |    3 -
 jetty-nested/pom.xml                               |   64 -
 .../org/eclipse/jetty/nested/NestedConnection.java |  121 -
 .../org/eclipse/jetty/nested/NestedConnector.java  |   90 -
 .../org/eclipse/jetty/nested/NestedEndPoint.java   |   77 -
 .../org/eclipse/jetty/nested/NestedGenerator.java  |  308 --
 .../org/eclipse/jetty/nested/NestedParser.java     |   71 -
 .../org/eclipse/jetty/nested/NestedRequest.java    |   45 -
 jetty-nosql/pom.xml                                |   31 +-
 jetty-nosql/src/main/config/modules/nosql.mod      |    9 +
 .../java/org/eclipse/jetty/nosql/NoSqlSession.java |   56 +-
 .../eclipse/jetty/nosql/NoSqlSessionManager.java   |  195 +-
 .../jetty/nosql/mongodb/MongoSessionIdManager.java |  323 +-
 .../jetty/nosql/mongodb/MongoSessionManager.java   |  316 +-
 .../mongodb/jmx/MongoSessionManagerMBean.java      |    3 +-
 .../jetty/nosql/mongodb/jmx/package-info.java      |   23 +
 .../eclipse/jetty/nosql/mongodb/package-info.java  |   23 +
 .../java/org/eclipse/jetty/nosql/package-info.java |   23 +
 .../jmx/MongoSessionManager-mbean.properties       |    6 -
 jetty-osgi/jetty-osgi-alpn/pom.xml                 |   50 +
 jetty-osgi/jetty-osgi-boot-jsp/pom.xml             |  145 +-
 .../boot/jasper/ContainerTldBundleDiscoverer.java  |  263 +
 .../osgi/boot/jasper/JSTLBundleDiscoverer.java     |  295 ++
 .../PluggableWebAppRegistrationCustomizerImpl.java |  187 -
 .../jasper/WebappRegistrationCustomizerImpl.java   |  271 -
 .../jetty/osgi/boot/jsp/FragmentActivator.java     |   47 +-
 .../osgi/boot/jsp/TagLibOSGiConfiguration.java     |  161 -
 jetty-osgi/jetty-osgi-boot-logback/pom.xml         |  127 -
 .../jetty/osgi/boot/logback/FragmentActivator.java |   92 -
 .../boot/logback/internal/LogbackInitializer.java  |  102 -
 jetty-osgi/jetty-osgi-boot-warurl/pom.xml          |    4 +-
 .../jettyhome/etc/jetty-deployer.xml               |   41 +-
 .../jetty-osgi-boot/jettyhome/etc/jetty-nested.xml |   34 -
 .../jettyhome/etc/jetty-selector.xml               |   23 +-
 jetty-osgi/jetty-osgi-boot/jettyhome/etc/jetty.xml |   47 +-
 .../jetty-osgi-boot/jettyhome/etc/webdefault.xml   |  404 --
 jetty-osgi/jetty-osgi-boot/pom.xml                 |   88 +-
 .../osgi/annotations/AnnotationConfiguration.java  |   99 +-
 .../jetty/osgi/annotations/AnnotationParser.java   |   58 +-
 .../jetty/osgi/boot/AbstractContextProvider.java   |  126 +-
 .../eclipse/jetty/osgi/boot/AbstractOSGiApp.java   |   99 +-
 .../jetty/osgi/boot/AbstractWebAppProvider.java    |  164 +-
 .../jetty/osgi/boot/BundleContextProvider.java     |   18 +-
 .../eclipse/jetty/osgi/boot/BundleProvider.java    |    5 +
 .../jetty/osgi/boot/BundleWebAppProvider.java      |    1 -
 .../jetty/osgi/boot/JettyBootstrapActivator.java   |  197 +-
 .../org/eclipse/jetty/osgi/boot/OSGiDeployer.java  |   35 +-
 .../jetty/osgi/boot/OSGiMetaInfConfiguration.java  |  116 -
 .../jetty/osgi/boot/OSGiServerConstants.java       |    2 +
 .../eclipse/jetty/osgi/boot/OSGiUndeployer.java    |   26 +-
 .../jetty/osgi/boot/OSGiWebInfConfiguration.java   |   83 +-
 .../jetty/osgi/boot/OSGiWebappConstants.java       |   11 +-
 .../jetty/osgi/boot/ServiceContextProvider.java    |   10 +-
 .../eclipse/jetty/osgi/boot/ServiceProvider.java   |    5 +
 .../jetty/osgi/boot/ServiceWebAppProvider.java     |    1 -
 .../internal/jsp/TldLocatableURLClassloader.java   |   65 -
 ...URLClassloaderWithInsertedJettyClassloader.java |   69 -
 .../DefaultJettyAtJettyHomeHelper.java             |  280 +-
 .../serverfactory/IManagedJettyServerRegistry.java |   34 -
 .../serverfactory/JettyServerServiceTracker.java   |   37 +-
 .../serverfactory/ServerInstanceWrapper.java       |  323 +-
 .../webapp/BundleFileLocatorHelperFactory.java     |   58 -
 .../osgi/boot/internal/webapp/BundleWatcher.java   |  317 ++
 .../internal/webapp/IWebBundleDeployerHelper.java  |   88 -
 .../webapp/JettyContextHandlerServiceTracker.java  |  211 -
 .../internal/webapp/LibExtClassLoaderHelper.java   |   57 +-
 .../internal/webapp/OSGiWebappClassLoader.java     |   61 +-
 .../osgi/boot/internal/webapp/ServiceWatcher.java  |  230 +
 .../webapp/WebBundleTrackerCustomizer.java         |  250 -
 .../osgi/boot/utils/BundleClassLoaderHelper.java   |    4 +
 .../boot/utils/BundleClassLoaderHelperFactory.java |    4 +
 .../osgi/boot/utils/BundleFileLocatorHelper.java   |   10 +-
 .../boot/utils/BundleFileLocatorHelperFactory.java |   57 +
 .../eclipse/jetty/osgi/boot/utils/EventSender.java |   39 +-
 .../jetty/osgi/boot/utils/FakeURLClassLoader.java  |   83 +
 .../jetty/osgi/boot/utils/OSGiClassLoader.java     |    3 +-
 .../jetty/osgi/boot/utils/TldBundleDiscoverer.java |   43 +
 .../org/eclipse/jetty/osgi/boot/utils/Util.java    |  118 +
 .../boot/utils/WebappRegistrationCustomizer.java   |   61 -
 .../internal/DefaultBundleClassLoaderHelper.java   |  339 +-
 .../utils/internal/DefaultFileLocatorHelper.java   |   80 +-
 .../utils/internal/PackageAdminServiceTracker.java |    8 +-
 .../jetty/osgi/nested/NestedConnectorListener.java |  268 -
 .../nested/NestedConnectorServletDelegate.java     |   51 -
 jetty-osgi/jetty-osgi-equinoxtools/pom.xml         |  121 -
 .../equinoxtools/WebEquinoxToolsActivator.java     |  133 -
 .../console/EquinoxChattingSupport.java            |  156 -
 .../console/EquinoxConsoleContinuationServlet.java |  253 -
 .../console/EquinoxConsoleSyncServlet.java         |   82 -
 .../console/EquinoxConsoleWebSocketServlet.java    |  182 -
 .../equinoxtools/console/WebConsoleSession.java    |  189 -
 .../console/WebConsoleWriterOutputStream.java      |   93 -
 .../contexts/httpservice.xml                       |   10 +-
 jetty-osgi/jetty-osgi-httpservice/pom.xml          |   16 +-
 .../httpservice/HttpServiceErrorHandlerHelper.java |   22 +-
 .../HttpServiceErrorPageErrorHandler.java          |   80 +-
 jetty-osgi/jetty-osgi-npn/pom.xml                  |   47 +
 jetty-osgi/pom.xml                                 |   57 +-
 jetty-osgi/test-jetty-osgi-context/pom.xml         |   47 +-
 .../src/main/context/acme.xml                      |    2 +-
 .../src/main/java/com/acme/osgi/Activator.java     |   11 +-
 jetty-osgi/test-jetty-osgi-webapp/pom.xml          |   40 +-
 .../src/main/java/com/acme/osgi/Activator.java     |   21 +-
 jetty-osgi/test-jetty-osgi/pom.xml                 |  417 +-
 .../src/main/resources/jetty-logging.properties    |    2 +
 .../src/main}/resources/keystore.jks               |  Bin
 .../src/main}/resources/truststore.jks             |  Bin
 .../src/test/config/etc/jetty-deployer.xml         |   24 +-
 .../src/test/config/etc/jetty-https.xml            |   47 +
 .../src/test/config/etc/jetty-selector.xml         |   23 +-
 .../src/test/config/etc/jetty-spdy.xml             |  115 +
 .../src/test/config/etc/jetty-ssl.xml              |   41 +
 .../src/test/config/etc/jetty-testrealm.xml        |    7 +-
 .../test-jetty-osgi/src/test/config/etc/jetty.xml  |   47 +-
 .../test-jetty-osgi/src/test/config/etc/keystore   |  Bin 0 -> 1416 bytes
 .../src/test/config/etc/webdefault.xml             |  404 ++
 .../osgi/boot/JettyOSGiBootContextAsService.java   |  194 -
 .../jetty/osgi/boot/TestJettyOSGiBootCore.java     |  218 -
 .../boot/TestJettyOSGiBootWebAppAsService.java     |  186 -
 .../jetty/osgi/boot/TestJettyOSGiBootWithJsp.java  |  195 -
 .../test/TestJettyOSGiBootContextAsService.java    |  151 +
 .../jetty/osgi/test/TestJettyOSGiBootCore.java     |  175 +
 .../jetty/osgi/test/TestJettyOSGiBootSpdy.java     |  120 +
 .../test/TestJettyOSGiBootWebAppAsService.java     |  158 +
 .../test/TestJettyOSGiBootWithAnnotations.java     |  167 +
 .../jetty/osgi/test/TestJettyOSGiBootWithJsp.java  |  153 +
 .../org/eclipse/jetty/osgi/test/TestOSGiUtil.java  |  189 +
 .../src/test/resources/log4j.properties            |   13 +
 jetty-overlay-deployer/logs/jtrac.log              |   24 -
 jetty-overlay-deployer/pom.xml                     |   10 +-
 .../src/main/config/modules/overlay.mod            |   12 +
 .../eclipse/jetty/overlays/TemplateContext.java    |    4 +-
 .../org/eclipse/jetty/overlays/package-info.java   |   23 +
 .../org/eclipse/jetty/overlays/OverlayServer.java  |    9 +-
 .../jetty/overlays/OverlayedAppProviderTest.java   |    8 +-
 .../instances/myfoo=blue/WEB-INF/overlay.xml       |    2 +-
 .../instances/myfoo=blue/WEB-INF/web-overlay.xml   |    2 +-
 .../instances/myfoo=green/WEB-INF/overlay.xml      |    2 +-
 .../instances/myfoo=green/WEB-INF/web-overlay.xml  |    2 +-
 .../instances/myfoo=red/WEB-INF/overlay.xml        |    2 +-
 .../instances/myfoo=red/WEB-INF/web-overlay.xml    |    2 +-
 .../instances/root=root/WEB-INF/overlay.xml        |    2 +-
 .../overlays/nodes/nodeA/WEB-INF/web-overlay.xml   |    2 +-
 .../home/overlays/nodes/nodeB/WEB-INF/web.xml      |    2 +-
 .../templates/myfoo=foo/WEB-INF/jetty-web.xml      |    2 +-
 .../templates/myfoo=foo/WEB-INF/template.xml       |    2 +-
 .../templates/myfoo=foo/WEB-INF/web-default.xml    |    2 +-
 .../templates/myfoo=foo/WEB-INF/web-overlay.xml    |    2 +-
 .../overlays/templates/root/WEB-INF/overlay.xml    |    2 +-
 .../home/overlays/webapps/foo/WEB-INF/web.xml      |    2 +-
 jetty-plus/pom.xml                                 |   26 +-
 jetty-plus/src/main/config/etc/jetty-plus.xml      |  108 +-
 jetty-plus/src/main/config/modules/plus.mod        |   15 +
 .../plus/annotation/ContainerInitializer.java      |  162 +-
 .../eclipse/jetty/plus/annotation/Injection.java   |   34 +-
 .../jetty/plus/annotation/LifeCycleCallback.java   |   39 +-
 .../annotation/LifeCycleCallbackCollection.java    |   49 +
 .../org/eclipse/jetty/plus/annotation/RunAs.java   |    6 +-
 .../jetty/plus/annotation/RunAsCollection.java     |   24 +-
 .../jetty/plus/annotation/package-info.java        |   23 +
 .../org/eclipse/jetty/plus/jaas/JAASGroup.java     |  152 -
 .../eclipse/jetty/plus/jaas/JAASLoginService.java  |  336 --
 .../org/eclipse/jetty/plus/jaas/JAASPrincipal.java |   89 -
 .../java/org/eclipse/jetty/plus/jaas/JAASRole.java |   42 -
 .../eclipse/jetty/plus/jaas/JAASUserPrincipal.java |   79 -
 .../eclipse/jetty/plus/jaas/RoleCheckPolicy.java   |   36 -
 .../jetty/plus/jaas/StrictRoleCheckPolicy.java     |   63 -
 .../jaas/callback/AbstractCallbackHandler.java     |   60 -
 .../plus/jaas/callback/DefaultCallbackHandler.java |   97 -
 .../jetty/plus/jaas/callback/ObjectCallback.java   |   67 -
 .../jaas/callback/RequestParameterCallback.java    |   61 -
 .../plus/jaas/spi/AbstractDatabaseLoginModule.java |  144 -
 .../jetty/plus/jaas/spi/AbstractLoginModule.java   |  303 --
 .../jetty/plus/jaas/spi/DataSourceLoginModule.java |   89 -
 .../jetty/plus/jaas/spi/JDBCLoginModule.java       |  127 -
 .../jetty/plus/jaas/spi/LdapLoginModule.java       |  689 ---
 .../plus/jaas/spi/PropertyFileLoginModule.java     |  135 -
 .../org/eclipse/jetty/plus/jaas/spi/UserInfo.java  |   73 -
 .../eclipse/jetty/plus/jndi/NamingEntryUtil.java   |   58 +-
 .../org/eclipse/jetty/plus/jndi/package-info.java  |   23 +
 .../plus/security/DataSourceLoginService.java      |  245 +-
 .../eclipse/jetty/plus/security/package-info.java  |   23 +
 .../eclipse/jetty/plus/servlet/ServletHandler.java |   76 -
 .../jetty/plus/webapp/EnvConfiguration.java        |   62 +-
 .../jetty/plus/webapp/PlusConfiguration.java       |   28 +-
 .../eclipse/jetty/plus/webapp/PlusDecorator.java   |  101 +-
 .../jetty/plus/webapp/PlusDescriptorProcessor.java |  230 +-
 .../eclipse/jetty/plus/webapp/package-info.java    |   23 +
 .../eclipse/jetty/plus/jndi/TestNamingEntries.java |   34 +-
 .../jetty/plus/jndi/TestNamingEntryUtil.java       |   13 +-
 .../plus/webapp/PlusDescriptorProcessorTest.java   |   63 +-
 .../jetty/plus/webapp/TestConfiguration.java       |   12 +-
 jetty-plus/src/test/resources/web-fragment-1.xml   |    4 +-
 jetty-plus/src/test/resources/web-fragment-2.xml   |    6 +-
 jetty-plus/src/test/resources/web-fragment-3.xml   |    4 +-
 jetty-plus/src/test/resources/web-fragment-4.xml   |   16 +
 jetty-plus/src/test/resources/web.xml              |    2 +-
 jetty-policy/pom.xml                               |  139 -
 jetty-policy/src/main/config/etc/jetty-policy.xml  |   23 -
 .../src/main/config/lib/policy/global.policy       |   43 -
 .../src/main/config/lib/policy/jetty-jmx.policy    |   54 -
 .../src/main/config/lib/policy/jetty-start.policy  |   40 -
 .../src/main/config/lib/policy/jetty-work.policy   |   19 -
 .../src/main/config/lib/policy/jetty.policy        |   96 -
 .../src/main/config/lib/policy/temp-dirs.policy    |   30 -
 .../java/org/eclipse/jetty/policy/JettyPolicy.java |  462 --
 .../jetty/policy/JettyPolicyConfigurator.java      |   58 -
 .../java/org/eclipse/jetty/policy/PolicyBlock.java |  102 -
 .../org/eclipse/jetty/policy/PolicyContext.java    |  200 -
 .../org/eclipse/jetty/policy/PolicyException.java  |   45 -
 .../org/eclipse/jetty/policy/PolicyMonitor.java    |  328 --
 .../eclipse/jetty/policy/entry/AbstractEntry.java  |   52 -
 .../org/eclipse/jetty/policy/entry/GrantEntry.java |  209 -
 .../eclipse/jetty/policy/entry/KeystoreEntry.java  |   98 -
 .../jetty/policy/entry/PermissionEntry.java        |  226 -
 .../eclipse/jetty/policy/entry/PrincipalEntry.java |  115 -
 .../jetty/policy/loader/DefaultPolicyLoader.java   |  102 -
 .../jetty/policy/loader/PolicyFileScanner.java     |  459 --
 .../jetty/policy/JettyPolicyRuntimeTest.java       |  265 -
 .../org/eclipse/jetty/policy/JettyPolicyTest.java  |  424 --
 .../eclipse/jetty/policy/PolicyContextTest.java    |  120 -
 .../eclipse/jetty/policy/PolicyMonitorTest.java    |  138 -
 .../context/jetty-certificate-alias.policy         |    6 -
 .../resources/context/jetty-certificate.policy     |    6 -
 .../monitor-test-1/global-all-permission.policy    |    3 -
 .../monitor-test-2/global-all-permission.policy    |    3 -
 .../resources/monitor-test-2/template1.template    |    3 -
 .../monitor-test-3/global-all-permission.policy    |    3 -
 .../global-file-read-only-tmp-permission.policy    |   10 -
 .../monitor-test-3/jetty-bad-certificate.policy    |   25 -
 .../monitor-test-3/jetty-certificate.policy        |   31 -
 .../multiple-codebase-file-permission.policy       |   13 -
 .../multiple-codebase-mixed-permission.policy      |   16 -
 .../single-codebase-file-permission-2.policy       |    3 -
 .../single-codebase-file-permission-3.policy       |    3 -
 .../single-codebase-file-permission.policy         |    3 -
 .../resources/monitor-test-3/template1.template    |    3 -
 .../policy-test-1/global-all-permission.policy     |    3 -
 .../single-codebase-file-permission.policy         |    3 -
 .../multiple-codebase-file-permission.policy       |   13 -
 .../multiple-codebase-mixed-permission.policy      |   16 -
 .../single-codebase-file-permission-2.policy       |    3 -
 .../single-codebase-file-permission.policy         |    3 -
 .../single-codebase-file-permission-2.policy       |    3 -
 .../single-codebase-file-permission-3.policy       |    3 -
 .../single-codebase-file-permission.policy         |    3 -
 .../runtime-test-1/global-all-permission.policy    |    3 -
 .../runtime-test-2/a/global-all-permission.policy  |    3 -
 .../b/global-file-read-only-tmp-permission.policy  |   10 -
 .../global-file-read-only-tmp-permission.policy    |   10 -
 .../runtime-test-4/jetty-certificate.policy        |   31 -
 .../runtime-test-5/jetty-bad-certificate.policy    |   25 -
 jetty-proxy/pom.xml                                |  106 +
 jetty-proxy/src/main/config/etc/jetty-proxy.xml    |   35 +
 jetty-proxy/src/main/config/modules/proxy.mod      |   22 +
 .../eclipse/jetty/proxy/AbstractProxyServlet.java  |  731 +++
 .../jetty/proxy/AfterContentTransformer.java       |  494 ++
 .../eclipse/jetty/proxy/AsyncMiddleManServlet.java |  786 +++
 .../org/eclipse/jetty/proxy/AsyncProxyServlet.java |  303 ++
 .../org/eclipse/jetty/proxy/BalancerServlet.java   |  314 ++
 .../org/eclipse/jetty/proxy/ConnectHandler.java    |  634 +++
 .../org/eclipse/jetty/proxy/ProxyConnection.java   |  158 +
 .../java/org/eclipse/jetty/proxy/ProxyServlet.java |  343 ++
 .../java/org/eclipse/jetty/proxy/package-info.java |   23 +
 .../jetty/proxy/AbstractConnectHandlerTest.java    |   79 +
 .../jetty/proxy/AsyncMiddleManServletTest.java     | 1703 ++++++
 .../jetty/proxy/AsyncProxyServletLoadTest.java     |  236 +
 .../eclipse/jetty/proxy/BalancerServletTest.java   |  201 +
 .../eclipse/jetty/proxy/ConnectHandlerSSLTest.java |  201 +
 .../eclipse/jetty/proxy/ConnectHandlerTest.java    |  782 +++
 .../org/eclipse/jetty/proxy/EchoHttpServlet.java   |   36 +
 .../org/eclipse/jetty/proxy/EmptyHttpServlet.java  |   33 +
 .../java/org/eclipse/jetty/proxy/ProxyServer.java  |   51 +
 .../jetty/proxy/ProxyServletFailureTest.java       |  419 ++
 .../org/eclipse/jetty/proxy/ProxyServletTest.java  | 1234 +++++
 .../eclipse/jetty/proxy/ProxyTunnellingTest.java   |  434 ++
 .../org/eclipse/jetty/proxy/ReverseProxyTest.java  |  153 +
 .../src/test/resources/jetty-logging.properties    |    4 +
 .../src/test/resources/keystore                    |  Bin
 jetty-quickstart/README.txt                        |    6 +
 jetty-quickstart/pom.xml                           |  104 +
 .../src/main/config/etc/example-quickstart.xml     |   27 +
 .../src/main/config/modules/quickstart.mod         |   12 +
 .../PreconfigureDescriptorProcessor.java           |   88 +
 .../quickstart/PreconfigureQuickStartWar.java      |  129 +
 .../jetty/quickstart/QuickStartConfiguration.java  |  154 +
 .../quickstart/QuickStartDescriptorGenerator.java  |  688 +++
 .../quickstart/QuickStartDescriptorProcessor.java  |  218 +
 .../eclipse/jetty/quickstart/QuickStartWebApp.java |  182 +
 jetty-rewrite/pom.xml                              |   18 +-
 .../src/main/config/etc/jetty-rewrite.xml          |  122 +-
 jetty-rewrite/src/main/config/modules/rewrite.mod  |   12 +
 .../jetty/rewrite/handler/CompactPathRule.java     |   56 +
 .../jetty/rewrite/handler/CookiePatternRule.java   |   14 +
 .../rewrite/handler/ForwardedSchemeHeaderRule.java |    1 +
 .../jetty/rewrite/handler/HeaderPatternRule.java   |    2 +
 .../jetty/rewrite/handler/HeaderRegexRule.java     |  128 +
 .../eclipse/jetty/rewrite/handler/HeaderRule.java  |    1 +
 .../eclipse/jetty/rewrite/handler/MsieSslRule.java |   18 +-
 .../eclipse/jetty/rewrite/handler/PatternRule.java |    2 +
 .../eclipse/jetty/rewrite/handler/ProxyRule.java   |  503 --
 .../jetty/rewrite/handler/RedirectPatternRule.java |    2 +
 .../eclipse/jetty/rewrite/handler/RegexRule.java   |    2 +
 .../jetty/rewrite/handler/ResponsePatternRule.java |    2 +
 .../jetty/rewrite/handler/RewriteHandler.java      |   37 +-
 .../jetty/rewrite/handler/RewritePatternRule.java  |   55 +-
 .../jetty/rewrite/handler/RewriteRegexRule.java    |   11 +-
 .../org/eclipse/jetty/rewrite/handler/Rule.java    |    5 +-
 .../jetty/rewrite/handler/RuleContainer.java       |   14 +-
 .../rewrite/handler/VirtualHostRuleContainer.java  |    4 +-
 .../jetty/rewrite/handler/package-info.java        |   23 +
 .../rewrite/handler/AbstractRuleTestCase.java      |   78 +-
 .../rewrite/handler/CookiePatternRuleTest.java     |   47 +-
 .../handler/ForwardedSchemeHeaderRuleTest.java     |    6 +-
 .../rewrite/handler/HeaderPatternRuleTest.java     |    4 +-
 .../jetty/rewrite/handler/HeaderRegexRuleTest.java |  131 +
 .../jetty/rewrite/handler/LegacyRuleTest.java      |    4 +-
 .../jetty/rewrite/handler/MsieSslRuleTest.java     |   76 +-
 .../jetty/rewrite/handler/PatternRuleTest.java     |    8 +-
 .../jetty/rewrite/handler/ProxyRuleTest.java       |  136 -
 .../rewrite/handler/RedirectPatternRuleTest.java   |    8 +-
 .../rewrite/handler/RedirectRegexRuleTest.java     |   12 +-
 .../jetty/rewrite/handler/RegexRuleTest.java       |    8 +-
 .../rewrite/handler/ResponsePatternRuleTest.java   |    4 +-
 .../jetty/rewrite/handler/RewriteHandlerTest.java  |   44 +-
 .../rewrite/handler/RewritePatternRuleTest.java    |   96 +-
 .../rewrite/handler/RewriteRegexRuleTest.java      |   14 +-
 .../jetty/rewrite/handler/ValidUrlRuleTest.java    |    5 +-
 .../handler/VirtualHostRuleContainerTest.java      |   11 +-
 .../jetty-rewrite.xml                              |   42 +-
 jetty-rhttp/README.TXT                             |   33 +
 jetty-rhttp/jetty-rhttp-client/pom.xml             |   95 +
 .../eclipse/jetty/rhttp/client/AbstractClient.java |  270 +
 .../eclipse/jetty/rhttp/client/ApacheClient.java   |  156 +
 .../eclipse/jetty/rhttp/client/ClientListener.java |   67 +
 .../eclipse/jetty/rhttp/client/JettyClient.java    |  306 ++
 .../eclipse/jetty/rhttp/client/RHTTPClient.java    |  133 +
 .../eclipse/jetty/rhttp/client/RHTTPListener.java  |   36 +
 .../eclipse/jetty/rhttp/client/RHTTPRequest.java   |  266 +
 .../eclipse/jetty/rhttp/client/RHTTPResponse.java  |  256 +
 .../jetty/rhttp/client/RetryingApacheClient.java   |  112 +
 .../jetty/rhttp/client/ApacheClientTest.java       |   75 +
 .../org/eclipse/jetty/rhttp/client/ClientTest.java |  299 ++
 .../jetty/rhttp/client/JettyClientTest.java        |   52 +
 .../eclipse/jetty/rhttp/client/RequestTest.java    |   85 +
 .../eclipse/jetty/rhttp/client/ResponseTest.java   |   85 +
 jetty-rhttp/jetty-rhttp-connector/pom.xml          |   96 +
 .../src/main/config/etc/jetty-rhttp.xml            |   23 +
 .../rhttp/connector/ReverseHTTPConnector.java      |  170 +
 .../rhttp/connector/ReverseHTTPConnectorTest.java  |  187 +
 .../jetty/rhttp/connector/TestReverseServer.java   |   58 +
 jetty-rhttp/jetty-rhttp-gateway/pom.xml            |   99 +
 .../jetty/rhttp/gateway/ClientDelegate.java        |   92 +
 .../jetty/rhttp/gateway/ConnectorServlet.java      |  224 +
 .../jetty/rhttp/gateway/ExternalRequest.java       |   54 +
 .../jetty/rhttp/gateway/ExternalServlet.java       |   88 +
 .../org/eclipse/jetty/rhttp/gateway/Gateway.java   |   99 +
 .../jetty/rhttp/gateway/GatewayProxyServer.java    |  228 +
 .../eclipse/jetty/rhttp/gateway/GatewayServer.java |  171 +
 .../jetty/rhttp/gateway/HostTargetIdRetriever.java |   54 +
 .../java/org/eclipse/jetty/rhttp/gateway/Main.java |  128 +
 .../rhttp/gateway/StandardClientDelegate.java      |  172 +
 .../rhttp/gateway/StandardExternalRequest.java     |  189 +
 .../jetty/rhttp/gateway/StandardGateway.java       |  131 +
 .../rhttp/gateway/StandardTargetIdRetriever.java   |   40 +
 .../jetty/rhttp/gateway/TargetIdRetriever.java     |   40 +
 .../org/eclipse/jetty/rhttp/gateway/Utils.java     |   40 +
 .../jetty/rhttp/gateway/ClientTimeoutTest.java     |  115 +
 .../jetty/rhttp/gateway/DisconnectClientTest.java  |   93 +
 .../jetty/rhttp/gateway/DuplicateClientTest.java   |   84 +
 .../gateway/ExternalRequestNotSuspendedTest.java   |  186 +
 .../jetty/rhttp/gateway/ExternalTimeoutTest.java   |  126 +
 .../jetty/rhttp/gateway/GatewayEchoServer.java     |  109 +
 .../jetty/rhttp/gateway/GatewayEchoTest.java       |   77 +
 .../jetty/rhttp/gateway/GatewayLoadTest.java       |  202 +
 .../jetty/rhttp/gateway/GatewayTimeoutTest.java    |  130 +
 .../jetty/rhttp/gateway/HandshakeClientTest.java   |   75 +
 .../rhttp/gateway/HostTargetIdRetrieverTest.java   |  107 +
 .../src/test/resources/log4j.properties            |   13 +
 jetty-rhttp/jetty-rhttp-loadtest/pom.xml           |   58 +
 .../org/eclipse/jetty/rhttp/loadtest/Loader.java   |  429 ++
 .../org/eclipse/jetty/rhttp/loadtest/Server.java   |   69 +
 .../src/main/resources/log4j.properties            |   13 +
 jetty-rhttp/pom.xml                                |  107 +
 jetty-runner/pom.xml                               |  101 +
 .../main/java/org/eclipse/jetty/runner/Runner.java |  566 ++
 .../org/eclipse/jetty/runner/package-info.java     |   23 +
 jetty-security/pom.xml                             |    4 +-
 .../src/main/config/modules/security.mod           |    9 +
 .../jetty/security/AbstractUserAuthentication.java |   98 +
 .../org/eclipse/jetty/security/Authenticator.java  |   46 +-
 .../eclipse/jetty/security/ConstraintAware.java    |   16 +
 .../jetty/security/ConstraintSecurityHandler.java  |  516 +-
 .../jetty/security/CrossContextPsuedoSession.java  |    1 +
 .../jetty/security/DefaultIdentityService.java     |   12 +-
 .../jetty/security/DefaultUserIdentity.java        |   17 +-
 .../security/HashCrossContextPsuedoSession.java    |    7 +-
 .../eclipse/jetty/security/HashLoginService.java   |    4 +
 .../eclipse/jetty/security/IdentityService.java    |   19 +-
 .../eclipse/jetty/security/JDBCLoginService.java   |   81 +-
 .../eclipse/jetty/security/MappedLoginService.java |   57 +-
 .../eclipse/jetty/security/PropertyUserStore.java  |   24 +-
 .../java/org/eclipse/jetty/security/RoleInfo.java  |   27 +-
 .../eclipse/jetty/security/SecurityHandler.java    |  228 +-
 .../eclipse/jetty/security/SpnegoLoginService.java |   54 +-
 .../eclipse/jetty/security/SpnegoUserIdentity.java |    6 +-
 .../jetty/security/SpnegoUserPrincipal.java        |   12 +-
 .../eclipse/jetty/security/UserAuthentication.java |   25 +-
 .../authentication/BasicAuthenticator.java         |   23 +-
 .../authentication/ClientCertAuthenticator.java    |   21 +-
 .../authentication/DeferredAuthentication.java     |  114 +-
 .../authentication/DigestAuthenticator.java        |   78 +-
 .../security/authentication/FormAuthenticator.java |  230 +-
 .../authentication/LoginAuthenticator.java         |   57 +-
 .../security/authentication/LoginCallback.java     |   10 +-
 .../security/authentication/LoginCallbackImpl.java |    2 +-
 .../authentication/SessionAuthentication.java      |   57 +-
 .../authentication/SpnegoAuthenticator.java        |   47 +-
 .../security/authentication/package-info.java      |   23 +
 .../org/eclipse/jetty/security/package-info.java   |   23 +
 .../jetty/security/AliasedConstraintTest.java      |  348 +-
 .../org/eclipse/jetty/security/ConstraintTest.java |  846 +--
 .../jetty/security/DataConstraintsTest.java        |  150 +-
 .../jetty/security/PropertyUserStoreTest.java      |   24 +-
 .../jetty/security/SpecExampleConstraintTest.java  |   61 +-
 jetty-server/pom.xml                               |   19 +-
 .../src/main/config/etc/home-base-warning.xml      |    8 +
 jetty-server/src/main/config/etc/jetty-bio-ssl.xml |   25 -
 jetty-server/src/main/config/etc/jetty-bio.xml     |   23 -
 jetty-server/src/main/config/etc/jetty-debug.xml   |    4 +-
 .../src/main/config/etc/jetty-fileserver.xml       |   37 -
 jetty-server/src/main/config/etc/jetty-http.xml    |   47 +
 jetty-server/src/main/config/etc/jetty-https.xml   |   53 +
 .../src/main/config/etc/jetty-ipaccess.xml         |   18 +-
 .../src/main/config/etc/jetty-lowresources.xml     |   22 +
 jetty-server/src/main/config/etc/jetty-proxy.xml   |   64 -
 .../src/main/config/etc/jetty-requestlog.xml       |   48 +-
 jetty-server/src/main/config/etc/jetty-ssl.xml     |   66 +-
 jetty-server/src/main/config/etc/jetty-stats.xml   |   21 +-
 jetty-server/src/main/config/etc/jetty-xinetd.xml  |   16 +-
 jetty-server/src/main/config/etc/jetty.xml         |  125 +-
 jetty-server/src/main/config/etc/keystore.pkf      |   20 +
 .../src/main/config/modules/continuation.mod       |    6 +
 jetty-server/src/main/config/modules/debug.mod     |    9 +
 jetty-server/src/main/config/modules/ext.mod       |   11 +
 .../src/main/config/modules/home-base-warning.mod  |    7 +
 jetty-server/src/main/config/modules/http.mod      |   27 +
 jetty-server/src/main/config/modules/https.mod     |   19 +
 jetty-server/src/main/config/modules/ipaccess.mod  |    9 +
 jetty-server/src/main/config/modules/jvm.mod       |   23 +
 .../src/main/config/modules/lowresources.mod       |   18 +
 .../src/main/config/modules/requestlog.mod         |   30 +
 jetty-server/src/main/config/modules/resources.mod |   10 +
 jetty-server/src/main/config/modules/server.mod    |   49 +
 jetty-server/src/main/config/modules/ssl.mod       |   40 +
 jetty-server/src/main/config/modules/stats.mod     |    9 +
 jetty-server/src/main/config/modules/xinetd.mod    |   17 +
 .../jetty/server/AbstractConnectionFactory.java    |   92 +
 .../eclipse/jetty/server/AbstractConnector.java    | 1477 ++----
 .../jetty/server/AbstractHttpConnection.java       | 1273 -----
 .../jetty/server/AbstractNCSARequestLog.java       |  482 ++
 .../jetty/server/AbstractNetworkConnector.java     |  125 +
 .../eclipse/jetty/server/AsyncContextEvent.java    |  164 +
 .../eclipse/jetty/server/AsyncContextState.java    |  185 +
 .../eclipse/jetty/server/AsyncContinuation.java    | 1160 ----
 .../eclipse/jetty/server/AsyncHttpConnection.java  |  220 -
 .../eclipse/jetty/server/AsyncNCSARequestLog.java  |   21 +-
 .../org/eclipse/jetty/server/Authentication.java   |    9 +
 .../jetty/server/BlockingHttpConnection.java       |  138 -
 .../jetty/server/ByteBufferQueuedHttpInput.java    |   53 +
 .../org/eclipse/jetty/server/ClassLoaderDump.java  |   66 +
 .../eclipse/jetty/server/ConnectionFactory.java    |   57 +
 .../java/org/eclipse/jetty/server/Connector.java   |  367 +-
 .../eclipse/jetty/server/ConnectorStatistics.java  |  308 ++
 .../org/eclipse/jetty/server/CookieCutter.java     |   39 +-
 .../java/org/eclipse/jetty/server/Dispatcher.java  |  306 +-
 .../eclipse/jetty/server/EncodingHttpWriter.java   |   70 +
 .../jetty/server/ForwardedRequestCustomizer.java   |  289 +
 .../java/org/eclipse/jetty/server/Handler.java     |   31 +-
 .../org/eclipse/jetty/server/HandlerContainer.java |    5 +
 .../org/eclipse/jetty/server/HomeBaseWarning.java  |   75 +
 .../eclipse/jetty/server/HostHeaderCustomizer.java |   71 +
 .../java/org/eclipse/jetty/server/HttpChannel.java |  898 ++++
 .../org/eclipse/jetty/server/HttpChannelState.java |  742 +++
 .../eclipse/jetty/server/HttpConfiguration.java    |  342 ++
 .../org/eclipse/jetty/server/HttpConnection.java   |  761 +++
 .../jetty/server/HttpConnectionFactory.java        |   64 +
 .../java/org/eclipse/jetty/server/HttpInput.java   |  525 +-
 .../eclipse/jetty/server/HttpInputOverHTTP.java    |  146 +
 .../java/org/eclipse/jetty/server/HttpOutput.java  | 1149 +++-
 .../org/eclipse/jetty/server/HttpTransport.java    |   40 +
 .../java/org/eclipse/jetty/server/HttpWriter.java  |  244 +-
 .../eclipse/jetty/server/InclusiveByteRange.java   |    7 +-
 .../eclipse/jetty/server/Iso88591HttpWriter.java   |   75 +
 .../org/eclipse/jetty/server/LocalConnector.java   |  280 +-
 .../eclipse/jetty/server/LowResourceMonitor.java   |  350 ++
 .../org/eclipse/jetty/server/NCSARequestLog.java   |  507 +-
 .../jetty/server/NegotiatingServerConnection.java  |  162 +
 .../server/NegotiatingServerConnectionFactory.java |  103 +
 .../org/eclipse/jetty/server/NetworkConnector.java |   72 +
 .../server/NetworkTrafficServerConnector.java      |   92 +
 .../org/eclipse/jetty/server/QueuedHttpInput.java  |  146 +
 .../jetty/server/QuietServletException.java        |   53 +
 .../java/org/eclipse/jetty/server/Request.java     | 1272 ++---
 .../org/eclipse/jetty/server/ResourceCache.java    |  142 +-
 .../java/org/eclipse/jetty/server/Response.java    | 1623 +++---
 .../jetty/server/SecureRequestCustomizer.java      |  172 +
 .../main/java/org/eclipse/jetty/server/Server.java |  603 ++-
 .../org/eclipse/jetty/server/ServerConnector.java  |  524 ++
 .../jetty/server/ServletRequestHttpWrapper.java    |   76 +-
 .../jetty/server/ServletResponseHttpWrapper.java   |   37 +-
 .../org/eclipse/jetty/server/SessionIdManager.java |   13 +
 .../org/eclipse/jetty/server/SessionManager.java   |   17 +-
 .../org/eclipse/jetty/server/ShutdownMonitor.java  |  243 +-
 .../org/eclipse/jetty/server/Slf4jRequestLog.java  |   69 +
 .../eclipse/jetty/server/SslConnectionFactory.java |  102 +
 .../org/eclipse/jetty/server/UserIdentity.java     |   23 +-
 .../org/eclipse/jetty/server/Utf8HttpWriter.java   |  188 +
 .../eclipse/jetty/server/bio/SocketConnector.java  |  325 --
 .../jetty/server/handler/AbstractHandler.java      |   31 +-
 .../server/handler/AbstractHandlerContainer.java   |   65 +-
 .../server/handler/AllowSymLinkAliasChecker.java   |   97 +
 .../jetty/server/handler/AsyncDelayHandler.java    |  153 +
 .../jetty/server/handler/ConnectHandler.java       | 1025 ----
 .../jetty/server/handler/ContextHandler.java       | 1313 +++--
 .../server/handler/ContextHandlerCollection.java   |  363 +-
 .../eclipse/jetty/server/handler/DebugHandler.java |   64 +-
 .../jetty/server/handler/DefaultHandler.java       |   87 +-
 .../eclipse/jetty/server/handler/ErrorHandler.java |  115 +-
 .../eclipse/jetty/server/handler/GzipHandler.java  |  356 --
 .../jetty/server/handler/HandlerCollection.java    |  209 +-
 .../eclipse/jetty/server/handler/HandlerList.java  |    6 +-
 .../jetty/server/handler/HandlerWrapper.java       |   67 +-
 .../jetty/server/handler/HotSwapHandler.java       |   34 +-
 .../jetty/server/handler/IPAccessHandler.java      |  213 +-
 .../jetty/server/handler/IdleTimeoutHandler.java   |   38 +-
 .../jetty/server/handler/MovedContextHandler.java  |   20 +-
 .../eclipse/jetty/server/handler/ProxyHandler.java |   51 -
 .../jetty/server/handler/RequestLogHandler.java    |  148 +-
 .../jetty/server/handler/ResourceHandler.java      |  311 +-
 .../jetty/server/handler/ScopedHandler.java        |   89 +-
 .../server/handler/SecuredRedirectHandler.java     |   76 +
 .../jetty/server/handler/ShutdownHandler.java      |  170 +-
 .../jetty/server/handler/StatisticsHandler.java    |  259 +-
 .../server/handler/jmx/AbstractHandlerMBean.java   |   30 +-
 .../server/handler/jmx/ContextHandlerMBean.java    |   21 +-
 .../jetty/server/handler/jmx/package-info.java     |   23 +
 .../eclipse/jetty/server/handler/package-info.java |   23 +
 .../jetty/server/jmx/AbstractConnectorMBean.java   |   41 +
 .../org/eclipse/jetty/server/jmx/ServerMBean.java  |    5 +
 .../org/eclipse/jetty/server/jmx/package-info.java |   23 +
 .../jetty/server/nio/AbstractNIOConnector.java     |   53 -
 .../jetty/server/nio/BlockingChannelConnector.java |  366 --
 .../server/nio/InheritedChannelConnector.java      |   75 -
 .../org/eclipse/jetty/server/nio/NIOConnector.java |   31 -
 .../nio/NetworkTrafficSelectChannelConnector.java  |   57 +-
 .../jetty/server/nio/SelectChannelConnector.java   |  334 --
 .../org/eclipse/jetty/server/nio/package-info.java |   23 +
 .../org/eclipse/jetty/server/package-info.java     |   23 +
 .../jetty/server/session/AbstractSession.java      |  304 +-
 .../server/session/AbstractSessionIdManager.java   |  224 +-
 .../server/session/AbstractSessionManager.java     |  523 +-
 .../jetty/server/session/HashSessionIdManager.java |   85 +-
 .../jetty/server/session/HashSessionManager.java   |  328 +-
 .../jetty/server/session/HashedSession.java        |  104 +-
 .../jetty/server/session/JDBCSessionIdManager.java | 1407 +++--
 .../jetty/server/session/JDBCSessionManager.java   |  728 +--
 .../eclipse/jetty/server/session/MemSession.java   |  146 +
 .../jetty/server/session/SessionHandler.java       |   57 +-
 .../jetty/server/session/jmx/package-info.java     |   23 +
 .../eclipse/jetty/server/session/package-info.java |   23 +
 .../org/eclipse/jetty/server/ssl/ServletSSL.java   |   88 -
 .../eclipse/jetty/server/ssl/SslCertificates.java  |  182 -
 .../org/eclipse/jetty/server/ssl/SslConnector.java |  348 --
 .../server/ssl/SslSelectChannelConnector.java      |  653 ---
 .../jetty/server/ssl/SslSocketConnector.java       |  712 ---
 .../handler/jmx/AbstractHandler-mbean.properties   |    1 -
 .../handler/jmx/ContextHandler-mbean.properties    |   24 -
 .../jmx/ContextHandlerCollection-mbean.properties  |    2 -
 .../handler/jmx/HandlerCollection-mbean.properties |    2 -
 .../handler/jmx/HandlerWrapper-mbean.properties    |    2 -
 .../handler/jmx/StatisticsHandler-mbean.properties |   28 -
 .../server/jmx/AbstractConnector-mbean.properties  |   19 -
 .../jetty/server/jmx/Connector-mbean.properties    |   29 -
 .../jetty/server/jmx/Handler-mbean.properties      |    3 -
 .../server/jmx/HandlerContainer-mbean.properties   |    3 -
 .../server/jmx/NCSARequestLog-mbean.properties     |    6 -
 .../jetty/server/jmx/Server-mbean.properties       |    9 -
 .../jmx/SelectChannelConnector-mbean.properties    |    2 -
 .../jmx/AbstractSessionManager-mbean.properties    |   19 -
 .../jetty/server/AbstractConnectorTest.java        |  259 -
 .../org/eclipse/jetty/server/AbstractHttpTest.java |  144 +
 .../eclipse/jetty/server/AsyncRequestReadTest.java |  365 +-
 .../org/eclipse/jetty/server/AsyncStressTest.java  |   36 +-
 .../jetty/server/BlockingChannelCloseTest.java     |   34 -
 .../jetty/server/BlockingChannelServerTest.java    |   34 -
 .../jetty/server/BlockingChannelTimeoutTest.java   |   46 -
 .../jetty/server/CheckReverseProxyHeadersTest.java |   51 +-
 .../jetty/server/ConnectionOpenCloseTest.java      |  235 +
 .../jetty/server/ConnectorCloseTestBase.java       |   70 +-
 .../eclipse/jetty/server/ConnectorTimeoutTest.java |  219 +-
 .../java/org/eclipse/jetty/server/DumpHandler.java |  117 +-
 .../eclipse/jetty/server/EncodedHttpURITest.java   |   50 -
 .../eclipse/jetty/server/ExtendedServerTest.java   |  159 +
 .../org/eclipse/jetty/server/GracefulStopTest.java |  134 +
 .../eclipse/jetty/server/HalfCloseRaceTest.java    |   86 -
 .../org/eclipse/jetty/server/HalfCloseTest.java    |  214 +
 .../jetty/server/HostHeaderCustomizerTest.java     |   97 +
 .../eclipse/jetty/server/HttpConnectionTest.java   |  509 +-
 .../HttpManyWaysToAsyncCommitBadBehaviourTest.java |  124 +
 .../server/HttpManyWaysToAsyncCommitTest.java      |  817 +++
 .../jetty/server/HttpManyWaysToCommitTest.java     |  593 +++
 .../org/eclipse/jetty/server/HttpOutputTest.java   |  760 +++
 .../eclipse/jetty/server/HttpServerTestBase.java   | 1416 ++---
 .../jetty/server/HttpServerTestFixture.java        |   92 +-
 .../java/org/eclipse/jetty/server/HttpURITest.java |   90 +-
 .../org/eclipse/jetty/server/HttpWriterTest.java   |  209 +-
 .../jetty/server/LocalAsyncContextTest.java        |   99 +-
 .../eclipse/jetty/server/LocalConnectorTest.java   |  173 +
 .../jetty/server/LowResourcesMonitorTest.java      |  197 +
 .../jetty/server/NetworkTrafficListenerTest.java   |  131 +-
 .../eclipse/jetty/server/PartialRFC2616Test.java   |  627 +++
 .../eclipse/jetty/server/QueuedHttpInputTest.java  |   33 +
 .../java/org/eclipse/jetty/server/RFC2616Test.java |  852 ---
 .../java/org/eclipse/jetty/server/RequestTest.java |  693 ++-
 .../eclipse/jetty/server/ResourceCacheTest.java    |  132 +-
 .../org/eclipse/jetty/server/ResponseTest.java     |  742 +--
 .../server/SelectChannelAsyncContextTest.java      |   64 +-
 .../server/SelectChannelConnectorCloseTest.java    |   11 +-
 .../jetty/server/SelectChannelServerTest.java      |   25 +-
 .../jetty/server/SelectChannelStatisticsTest.java  |  266 +
 .../jetty/server/SelectChannelTimeoutTest.java     |   27 +-
 .../eclipse/jetty/server/ServerConnectorTest.java  |  197 +
 .../java/org/eclipse/jetty/server/ServerTest.java  |   60 -
 .../eclipse/jetty/server/ShutdownMonitorTest.java  |  202 +-
 .../server/SlowClientWithPipelinedRequestTest.java |   42 +-
 .../jetty/server/SocketConnectorCloseTest.java     |   36 -
 .../org/eclipse/jetty/server/SocketServerTest.java |   36 -
 .../eclipse/jetty/server/SocketTimeoutTest.java    |   34 -
 .../java/org/eclipse/jetty/server/StressTest.java  |   82 +-
 .../org/eclipse/jetty/server/SuspendHandler.java   |    1 +
 .../server/handler/AbstractConnectHandlerTest.java |  217 -
 .../jetty/server/handler/AllowAllVerifier.java     |   31 +
 .../server/handler/BadRequestLogHandlerTest.java   |  193 +
 .../server/handler/ConnectHandlerSSLTest.java      |  362 --
 .../jetty/server/handler/ConnectHandlerTest.java   |  676 ---
 .../server/handler/ConnectHandlerUnitTest.java     |   73 -
 .../handler/ContextHandlerCollectionTest.java      |  334 +-
 .../handler/ContextHandlerGetResourceTest.java     |  405 ++
 .../jetty/server/handler/ContextHandlerTest.java   |  368 +-
 .../jetty/server/handler/IPAccessHandlerTest.java  |  485 +-
 .../server/handler/RequestLogHandlerTest.java      |  569 +-
 .../server/handler/ResourceHandlerRangeTest.java   |  112 +
 .../jetty/server/handler/ResourceHandlerTest.java  |  201 +-
 .../jetty/server/handler/ScopedHandlerTest.java    |    5 -
 .../server/handler/SecuredRedirectHandlerTest.java |  288 +
 .../jetty/server/handler/ShutdownHandlerTest.java  |   26 +-
 .../server/handler/StatisticsHandlerTest.java      |  377 +-
 .../server/session/HashSessionManagerTest.java     |   91 +-
 .../jetty/server/session/SessionCookieTest.java    |  189 +-
 .../jetty/server/session/SessionHandlerTest.java   |  865 ---
 .../org/eclipse/jetty/server/ssl/SSLCloseTest.java |  109 +-
 .../eclipse/jetty/server/ssl/SSLEngineTest.java    |  119 +-
 .../ssl/SSLSelectChannelConnectorLoadTest.java     |   38 +-
 .../server/ssl/SelectChannelServerSslTest.java     |  161 +-
 .../jetty/server/ssl/SslRenegotiateTest.java       |  269 -
 .../server/ssl/SslSelectChannelTimeoutTest.java    |   37 +-
 .../jetty/server/ssl/SslSocketServerTest.java      |  110 -
 .../jetty/server/ssl/SslSocketTimeoutTest.java     |   69 -
 .../eclipse/jetty/server/ssl/SslUploadTest.java    |   39 +-
 .../src/test/resources/jetty-logging.properties    |    4 +-
 jetty-server/src/test/resources/simple/big.txt     |  400 ++
 jetty-servlet/pom.xml                              |   22 +-
 jetty-servlet/src/main/config/modules/servlet.mod  |    9 +
 .../java/org/eclipse/jetty/servlet/BaseHolder.java |  209 +
 .../org/eclipse/jetty/servlet/DefaultServlet.java  |  467 +-
 .../jetty/servlet/ErrorPageErrorHandler.java       |   40 +-
 .../org/eclipse/jetty/servlet/FilterHolder.java    |   73 +-
 .../org/eclipse/jetty/servlet/FilterMapping.java   |  109 +-
 .../java/org/eclipse/jetty/servlet/Holder.java     |  186 +-
 .../java/org/eclipse/jetty/servlet/Invoker.java    |  114 +-
 .../jetty/servlet/JspPropertyGroupServlet.java     |    2 -
 .../org/eclipse/jetty/servlet/ListenerHolder.java  |   65 +
 .../org/eclipse/jetty/servlet/NoJspServlet.java    |    4 +-
 .../jetty/servlet/ServletContextHandler.java       |  483 +-
 .../org/eclipse/jetty/servlet/ServletHandler.java  | 1039 ++--
 .../org/eclipse/jetty/servlet/ServletHolder.java   |  501 +-
 .../org/eclipse/jetty/servlet/ServletMapping.java  |    8 +-
 .../org/eclipse/jetty/servlet/ServletTester.java   |  259 +
 .../eclipse/jetty/servlet/StatisticsServlet.java   |  121 +-
 .../eclipse/jetty/servlet/jmx/package-info.java    |   23 +
 .../jetty/servlet/listener/ELContextCleaner.java   |   52 +-
 .../jetty/servlet/listener/package-info.java       |   23 +
 .../org/eclipse/jetty/servlet/package-info.java    |   23 +
 .../servlet/jmx/FilterMapping-mbean.properties     |    4 -
 .../jetty/servlet/jmx/Holder-mbean.properties      |    5 -
 .../jmx/ServletContextHandler-mbean.properties     |    4 -
 .../servlet/jmx/ServletHandler-mbean.properties    |    5 -
 .../servlet/jmx/ServletHolder-mbean.properties     |    4 -
 .../servlet/jmx/ServletMapping-mbean.properties    |    3 -
 .../AsyncContextDispatchWithQueryStrings.java      |  115 +-
 .../jetty/servlet/AsyncContextListenersTest.java   |  282 +
 .../eclipse/jetty/servlet/AsyncContextTest.java    |  101 +-
 .../eclipse/jetty/servlet/AsyncIOServletTest.java  |  560 ++
 .../eclipse/jetty/servlet/AsyncServletIOTest.java  |  596 +++
 .../jetty/servlet/AsyncServletLongPollTest.java    |  166 +
 .../eclipse/jetty/servlet/AsyncServletTest.java    |  343 +-
 .../jetty/servlet/DefaultServletRangesTest.java    |  265 +
 .../eclipse/jetty/servlet/DefaultServletTest.java  |  164 +-
 .../jetty/servlet/DispatcherForwardTest.java       |  518 ++
 .../org/eclipse/jetty/servlet/DispatcherTest.java  |  146 +-
 .../org/eclipse/jetty/servlet/ErrorPageTest.java   |    9 +-
 .../java/org/eclipse/jetty/servlet/HolderTest.java |   56 +-
 .../org/eclipse/jetty/servlet/InvokerTest.java     |    6 +-
 .../eclipse/jetty/servlet/RequestHeadersTest.java  |  125 +
 .../org/eclipse/jetty/servlet/RequestURITest.java  |  238 +
 .../eclipse/jetty/servlet/ResponseHeadersTest.java |  167 +
 .../jetty/servlet/SSLAsyncIOServletTest.java       |  179 +
 .../jetty/servlet/ServletContextHandlerTest.java   |  183 +-
 .../eclipse/jetty/servlet/ServletHandlerTest.java  |   19 +-
 .../jetty/servlet/ServletRequestLogTest.java       |  681 +++
 .../jetty/servlet/StatisticsServletTest.java       |  105 -
 .../src/test/resources/jetty-logging.properties    |    5 +
 .../src/test}/resources/keystore.jks               |  Bin
 .../src/test}/resources/truststore.jks             |  Bin
 jetty-servlets/pom.xml                             |   46 +-
 .../src/main/config/modules/servlets.mod           |   10 +
 .../eclipse/jetty/servlets/AsyncGzipFilter.java    |  572 ++
 .../eclipse/jetty/servlets/BalancerServlet.java    |  422 --
 .../main/java/org/eclipse/jetty/servlets/CGI.java  |  236 +-
 .../eclipse/jetty/servlets/CloseableDoSFilter.java |   22 +-
 .../org/eclipse/jetty/servlets/ConcatServlet.java  |  149 +-
 .../eclipse/jetty/servlets/CrossOriginFilter.java  |  120 +-
 .../jetty/servlets/DataRateLimitedServlet.java     |  315 ++
 .../java/org/eclipse/jetty/servlets/DoSFilter.java |  392 +-
 .../org/eclipse/jetty/servlets/EventSource.java    |  108 +
 .../eclipse/jetty/servlets/EventSourceServlet.java |  240 +
 .../org/eclipse/jetty/servlets/GzipFilter.java     |  371 +-
 .../jetty/servlets/IncludableGzipFilter.java       |   25 +-
 .../eclipse/jetty/servlets/MultiPartFilter.java    |  149 +-
 .../org/eclipse/jetty/servlets/ProxyServlet.java   |  908 ----
 .../java/org/eclipse/jetty/servlets/PutFilter.java |   70 +-
 .../java/org/eclipse/jetty/servlets/QoSFilter.java |  327 +-
 .../eclipse/jetty/servlets/UserAgentFilter.java    |    1 +
 .../org/eclipse/jetty/servlets/WelcomeFilter.java  |   13 +-
 .../servlets/gzip/AbstractCompressedStream.java    |  401 ++
 .../servlets/gzip/CompressedResponseWrapper.java   |  485 ++
 .../jetty/servlets/gzip/DeflatedOutputStream.java  |  101 +
 .../eclipse/jetty/servlets/gzip/GzipFactory.java   |   38 +
 .../eclipse/jetty/servlets/gzip/GzipHandler.java   |  404 ++
 .../jetty/servlets/gzip/GzipHttpOutput.java        |  405 ++
 .../jetty/servlets/gzip/GzipOutputStream.java      |   72 +
 .../eclipse/jetty/servlets/gzip/package-info.java  |   23 +
 .../org/eclipse/jetty/servlets/package-info.java   |   23 +
 .../jetty/servlets/jmx/DoSFilter-mbean.properties  |   18 -
 .../jetty/servlets/jmx/QoSFilter-mbean.properties  |    4 -
 .../servlets/AbstractBalancerServletTest.java      |  162 -
 .../jetty/servlets/AbstractDoSFilterTest.java      |   91 +-
 .../eclipse/jetty/servlets/AsyncProxyServer.java   |   53 -
 .../jetty/servlets/BalancerServletTest.java        |  135 -
 .../jetty/servlets/CloseableDoSFilterTest.java     |    7 +
 .../eclipse/jetty/servlets/ConcatServletTest.java  |  175 +
 .../jetty/servlets/CrossOriginFilterTest.java      |   76 +-
 .../jetty/servlets/DataRateLimitedServletTest.java |  109 +
 .../eclipse/jetty/servlets/DoSFilterJMXTest.java   |    7 +-
 .../org/eclipse/jetty/servlets/DoSFilterTest.java  |   25 +-
 .../jetty/servlets/EventSourceServletTest.java     |  350 ++
 .../servlets/GzipFilterContentLengthTest.java      |  339 +-
 .../GzipFilterDefaultNoRecompressTest.java         |   77 +-
 .../jetty/servlets/GzipFilterDefaultTest.java      |  586 ++-
 .../jetty/servlets/GzipFilterLayeredTest.java      |  211 +
 .../servlets/IncludableGzipFilterMinSizeTest.java  |   16 +-
 .../jetty/servlets/IncludableGzipFilterTest.java   |   71 +-
 .../jetty/servlets/MultipartFilterTest.java        |  386 +-
 .../org/eclipse/jetty/servlets/PipelineHelper.java |  306 --
 .../eclipse/jetty/servlets/ProxyServletTest.java   |  293 --
 .../org/eclipse/jetty/servlets/PutFilterTest.java  |   88 +-
 .../org/eclipse/jetty/servlets/QoSFilterTest.java  |  114 +-
 .../jetty/servlets/TransparentProxyTest.java       |  140 -
 .../jetty/servlets/gzip/AsyncManipFilter.java      |  103 +
 .../servlets/gzip/AsyncScheduledDispatchWrite.java |  121 +
 .../servlets/gzip/AsyncTimeoutCompleteWrite.java   |  137 +
 .../servlets/gzip/AsyncTimeoutDispatchWrite.java   |  121 +
 .../eclipse/jetty/servlets/gzip/GzipTester.java    |  511 +-
 .../jetty/servlets/gzip/PassThruInputStream.java   |   36 +
 .../jetty/servlets/gzip/TestDirContentServlet.java |    2 +-
 .../servlets/gzip/TestMinGzipSizeServlet.java      |    9 +-
 .../gzip/TestServletBufferTypeLengthWrite.java     |   67 +
 .../gzip/TestServletLengthStreamTypeWrite.java     |    8 +-
 .../gzip/TestServletLengthTypeStreamWrite.java     |    8 +-
 .../gzip/TestServletStreamLengthTypeWrite.java     |    8 +-
 .../TestServletStreamLengthTypeWriteWithFlush.java |    2 -
 .../gzip/TestServletStreamTypeLengthWrite.java     |    8 +-
 .../gzip/TestServletTypeLengthStreamWrite.java     |    8 +-
 .../gzip/TestServletTypeStreamLengthWrite.java     |    8 +-
 .../servlets/gzip/TestStaticMimeTypeServlet.java   |   13 +-
 .../src/test/resources/jetty-logging.properties    |    7 +
 .../src/test/resources/lots-of-fantasy-names.txt   | 3000 -----------
 .../test/resources/lots-of-fantasy-names.txt.sha1  |    1 -
 jetty-servlets/src/test/resources/test.svg         | 2101 ++++++++
 jetty-servlets/src/test/resources/test.svg.sha1    |    1 +
 jetty-servlets/src/test/resources/test.svgz        |  Bin 0 -> 6916 bytes
 jetty-servlets/src/test/resources/test.svgz.sha1   |    1 +
 jetty-spdy/pom.xml                                 |  271 +-
 jetty-spdy/spdy-alpn-tests/pom.xml                 |   93 +
 .../jetty/spdy/server/ALPNNegotiationTest.java     |  198 +
 .../jetty/spdy/server/ALPNSynReplyTest.java        |  149 +
 .../jetty/spdy/server/AbstractALPNTest.java        |   78 +
 .../src/test/resources/jetty-logging.properties    |    2 +
 .../src/test}/resources/keystore.jks               |  Bin
 .../src/test}/resources/truststore.jks             |  Bin
 jetty-spdy/spdy-client/pom.xml                     |   66 +
 .../spdy/client/FlowControlStrategyFactory.java    |   43 +
 .../jetty/spdy/client/NPNClientConnection.java     |   82 +
 .../spdy/client/NPNClientConnectionFactory.java    |   50 +
 .../org/eclipse/jetty/spdy/client/SPDYClient.java  |  440 ++
 .../spdy/client/SPDYClientConnectionFactory.java   |   98 +
 .../eclipse/jetty/spdy/client/SPDYConnection.java  |  191 +
 jetty-spdy/spdy-core/pom.xml                       |   35 +-
 .../org/eclipse/jetty/spdy/ByteBufferPool.java     |   51 -
 .../java/org/eclipse/jetty/spdy/Controller.java    |    6 +-
 .../main/java/org/eclipse/jetty/spdy/Flusher.java  |  266 +
 .../main/java/org/eclipse/jetty/spdy/ISession.java |   17 +-
 .../main/java/org/eclipse/jetty/spdy/IStream.java  |    8 +-
 .../main/java/org/eclipse/jetty/spdy/Promise.java  |  102 -
 .../java/org/eclipse/jetty/spdy/PushSynInfo.java   |    5 +-
 .../jetty/spdy/SPDYv3FlowControlStrategy.java      |    3 +-
 .../eclipse/jetty/spdy/StandardByteBufferPool.java |  102 -
 .../org/eclipse/jetty/spdy/StandardSession.java    |  829 ++-
 .../org/eclipse/jetty/spdy/StandardStream.java     |  246 +-
 .../eclipse/jetty/spdy/api/ByteBufferDataInfo.java |   17 +-
 .../org/eclipse/jetty/spdy/api/BytesDataInfo.java  |   21 +-
 .../java/org/eclipse/jetty/spdy/api/DataInfo.java  |   93 +-
 .../org/eclipse/jetty/spdy/api/GoAwayInfo.java     |   37 +-
 .../eclipse/jetty/spdy/api/GoAwayResultInfo.java   |   57 +
 .../java/org/eclipse/jetty/spdy/api/Handler.java   |   61 -
 .../java/org/eclipse/jetty/spdy/api/Headers.java   |  305 --
 .../org/eclipse/jetty/spdy/api/HeadersInfo.java    |   50 +-
 .../main/java/org/eclipse/jetty/spdy/api/Info.java |   52 +
 .../java/org/eclipse/jetty/spdy/api/PingInfo.java  |   24 +-
 .../org/eclipse/jetty/spdy/api/PingResultInfo.java |   44 +
 .../java/org/eclipse/jetty/spdy/api/PushInfo.java  |  101 +
 .../java/org/eclipse/jetty/spdy/api/ReplyInfo.java |   34 +-
 .../java/org/eclipse/jetty/spdy/api/RstInfo.java   |   22 +-
 .../java/org/eclipse/jetty/spdy/api/Session.java   |  112 +-
 .../jetty/spdy/api/SessionFrameListener.java       |   22 +-
 .../java/org/eclipse/jetty/spdy/api/Settings.java  |   17 +-
 .../org/eclipse/jetty/spdy/api/SettingsInfo.java   |   14 +-
 .../java/org/eclipse/jetty/spdy/api/Stream.java    |  151 +-
 .../jetty/spdy/api/StreamFrameListener.java        |   27 +
 .../org/eclipse/jetty/spdy/api/StringDataInfo.java |   10 +-
 .../java/org/eclipse/jetty/spdy/api/SynInfo.java   |   54 +-
 .../org/eclipse/jetty/spdy/frames/DataFrame.java   |    7 +-
 .../eclipse/jetty/spdy/frames/HeadersFrame.java    |    8 +-
 .../eclipse/jetty/spdy/frames/SynReplyFrame.java   |    8 +-
 .../eclipse/jetty/spdy/frames/SynStreamFrame.java  |    8 +-
 .../spdy/generator/ControlFrameGenerator.java      |    2 +-
 .../jetty/spdy/generator/CredentialGenerator.java  |    6 +-
 .../jetty/spdy/generator/DataFrameGenerator.java   |    7 +-
 .../eclipse/jetty/spdy/generator/Generator.java    |    3 +-
 .../jetty/spdy/generator/GoAwayGenerator.java      |    6 +-
 .../spdy/generator/HeadersBlockGenerator.java      |   28 +-
 .../jetty/spdy/generator/HeadersGenerator.java     |    6 +-
 .../jetty/spdy/generator/NoOpGenerator.java        |    6 +-
 .../jetty/spdy/generator/PingGenerator.java        |    6 +-
 .../jetty/spdy/generator/RstStreamGenerator.java   |    6 +-
 .../jetty/spdy/generator/SettingsGenerator.java    |    6 +-
 .../jetty/spdy/generator/SynReplyGenerator.java    |    6 +-
 .../jetty/spdy/generator/SynStreamGenerator.java   |    6 +-
 .../spdy/generator/WindowUpdateGenerator.java      |    6 +-
 .../jetty/spdy/parser/ControlFrameParser.java      |   38 +-
 .../jetty/spdy/parser/HeadersBlockParser.java      |   17 +-
 .../jetty/spdy/parser/HeadersBodyParser.java       |    6 +-
 .../java/org/eclipse/jetty/spdy/parser/Parser.java |   71 +-
 .../jetty/spdy/parser/SynReplyBodyParser.java      |    6 +-
 .../jetty/spdy/parser/SynStreamBodyParser.java     |   39 +-
 .../org/eclipse/jetty/spdy/AsyncTimeoutTest.java   |   79 +-
 .../eclipse/jetty/spdy/StandardSessionTest.java    |  439 +-
 .../org/eclipse/jetty/spdy/StandardStreamTest.java |  178 +-
 .../eclipse/jetty/spdy/api/ClientUsageTest.java    |  240 +-
 .../eclipse/jetty/spdy/api/ServerUsageTest.java    |   37 +-
 .../spdy/frames/CredentialGenerateParseTest.java   |    6 +-
 .../jetty/spdy/frames/DataGenerateParseTest.java   |    8 +-
 .../jetty/spdy/frames/GoAwayGenerateParseTest.java |    6 +-
 .../spdy/frames/HeadersGenerateParseTest.java      |   26 +-
 .../jetty/spdy/frames/NoOpGenerateParseTest.java   |    6 +-
 .../jetty/spdy/frames/PingGenerateParseTest.java   |    6 +-
 .../spdy/frames/RstStreamGenerateParseTest.java    |    6 +-
 .../spdy/frames/SettingsGenerateParseTest.java     |    6 +-
 .../spdy/frames/SynReplyGenerateParseTest.java     |   12 +-
 .../spdy/frames/SynStreamGenerateParseTest.java    |   12 +-
 .../spdy/frames/WindowUpdateGenerateParseTest.java |    6 +-
 .../spdy/generator/DataFrameGeneratorTest.java     |  110 +
 .../eclipse/jetty/spdy/parser/BrokenFrameTest.java |  287 +
 .../spdy/parser/ParseVersusCacheBenchmarkTest.java |   13 +-
 .../jetty/spdy/parser/UnknownControlFrameTest.java |    8 +-
 .../src/test/resources/jetty-logging.properties    |    2 +
 .../spdy-core/src/test/resources/log4j.properties  |   14 -
 jetty-spdy/spdy-example-webapp/pom.xml             |   84 +
 .../src/main/config/example-jetty-spdy-proxy.xml   |  147 +
 .../src/main/config/example-jetty-spdy.xml         |  138 +
 .../src/main/resources/jetty-logging.properties    |    2 +
 .../src/main/resources/keystore.jks                |  Bin
 .../src/main/resources/truststore.jks              |  Bin
 .../src/main/webapp/WEB-INF/web.xml                |    6 +
 .../src/main/webapp/form.jsp                       |    0
 .../src/main/webapp/included.jsp                   |    0
 .../src/main/webapp/index.jsp                      |    0
 .../src/main/webapp/logo.jpg                       |  Bin
 .../src/main/webapp/stylesheet.css                 |    0
 jetty-spdy/spdy-http-client-transport/pom.xml      |   71 +
 .../spdy/client/http/HttpChannelOverSPDY.java      |   78 +
 .../client/http/HttpClientTransportOverSPDY.java   |  107 +
 .../spdy/client/http/HttpConnectionOverSPDY.java   |   82 +
 .../spdy/client/http/HttpDestinationOverSPDY.java  |   38 +
 .../spdy/client/http/HttpReceiverOverSPDY.java     |  152 +
 .../jetty/spdy/client/http/HttpSenderOverSPDY.java |  118 +
 .../client/http/AbstractHttpClientServerTest.java  |  107 +
 .../jetty/spdy/client/http/EmptyServerHandler.java |   37 +
 .../client/http/HttpClientCustomProxyTest.java     |  262 +
 .../jetty/spdy/client/http/HttpClientTest.java     |  467 ++
 .../src/test/resources/jetty-logging.properties    |    4 +
 .../src/test}/resources/keystore.jks               |  Bin
 .../src/test}/resources/truststore.jks             |  Bin
 jetty-spdy/spdy-http-common/pom.xml                |   48 +
 .../eclipse/jetty/spdy/http/HTTPSPDYHeader.java    |   82 +
 jetty-spdy/spdy-http-server/pom.xml                |  120 +
 .../src/main/config/etc/jetty-spdy-proxy.xml       |  158 +
 .../src/main/config/etc/jetty-spdy.xml             |  139 +
 .../src/main/config/etc/protonego-npn.xml          |   21 +
 .../config/modules/protonego-impl/npn-1.7.0_04.mod |    8 +
 .../config/modules/protonego-impl/npn-1.7.0_05.mod |    8 +
 .../config/modules/protonego-impl/npn-1.7.0_06.mod |    8 +
 .../config/modules/protonego-impl/npn-1.7.0_07.mod |    8 +
 .../config/modules/protonego-impl/npn-1.7.0_09.mod |    8 +
 .../config/modules/protonego-impl/npn-1.7.0_10.mod |    8 +
 .../config/modules/protonego-impl/npn-1.7.0_11.mod |    8 +
 .../config/modules/protonego-impl/npn-1.7.0_13.mod |    8 +
 .../config/modules/protonego-impl/npn-1.7.0_15.mod |    8 +
 .../config/modules/protonego-impl/npn-1.7.0_17.mod |    8 +
 .../config/modules/protonego-impl/npn-1.7.0_21.mod |    8 +
 .../config/modules/protonego-impl/npn-1.7.0_25.mod |    8 +
 .../config/modules/protonego-impl/npn-1.7.0_40.mod |    8 +
 .../config/modules/protonego-impl/npn-1.7.0_45.mod |    8 +
 .../config/modules/protonego-impl/npn-1.7.0_51.mod |    8 +
 .../config/modules/protonego-impl/npn-1.7.0_55.mod |    8 +
 .../config/modules/protonego-impl/npn-1.7.0_60.mod |    8 +
 .../config/modules/protonego-impl/npn-1.7.0_65.mod |    8 +
 .../config/modules/protonego-impl/npn-1.7.0_67.mod |    8 +
 .../config/modules/protonego-impl/npn-1.7.0_71.mod |    8 +
 .../config/modules/protonego-impl/npn-1.7.0_72.mod |    8 +
 .../config/modules/protonego-impl/npn-1.7.0_75.mod |    8 +
 .../config/modules/protonego-impl/npn-1.7.0_76.mod |    8 +
 .../config/modules/protonego-impl/npn-1.7.0_79.mod |    8 +
 .../config/modules/protonego-impl/npn-1.7.0_80.mod |    8 +
 .../src/main/config/modules/protonego-impl/npn.mod |   37 +
 .../src/main/config/modules/spdy.mod               |   26 +
 .../http/HTTPSPDYServerConnectionFactory.java      |  167 +
 .../spdy/server/http/HTTPSPDYServerConnector.java  |   82 +
 .../spdy/server/http/HttpChannelOverSPDY.java      |  246 +
 .../jetty/spdy/server/http/HttpInputOverSPDY.java  |   49 +
 .../spdy/server/http/HttpTransportOverSPDY.java    |  423 ++
 .../jetty/spdy/server/http/PushStrategy.java       |   55 +
 .../spdy/server/http/ReferrerPushStrategy.java     |  342 ++
 .../jetty/spdy/server/proxy/HTTPProxyEngine.java   |  276 +
 .../server/proxy/HTTPSPDYProxyServerConnector.java |   63 +
 .../jetty/spdy/server/proxy/ProxyEngine.java       |  129 +
 .../spdy/server/proxy/ProxyEngineSelector.java     |  203 +
 .../server/proxy/ProxyHTTPConnectionFactory.java   |   57 +
 .../spdy/server/proxy/ProxyHTTPSPDYConnection.java |  385 ++
 .../jetty/spdy/server/proxy/SPDYProxyEngine.java   |  631 +++
 .../spdy/server/http/AbstractHTTPSPDYTest.java     |  136 +
 .../spdy/server/http/ConcurrentStreamsTest.java    |  128 +
 .../server/http/HttpTransportOverSPDYTest.java     |  288 +
 .../server/http/PushStrategyBenchmarkTest.java     |  397 ++
 .../spdy/server/http/ReferrerPushStrategyTest.java | 1138 ++++
 .../server/http/ReferrerPushStrategyUnitTest.java  |  149 +
 .../jetty/spdy/server/http/SPDYTestUtils.java      |   51 +
 .../spdy/server/http/SSLExternalServerTest.java    |  108 +
 .../jetty/spdy/server/http/ServerHTTPSPDYTest.java | 1625 ++++++
 .../spdy/server/http/SimpleHTTPBenchmarkTest.java  |  160 +
 .../spdy/server/proxy/ProxyHTTPToSPDYTest.java     |  408 ++
 .../spdy/server/proxy/ProxySPDYToHTTPLoadTest.java |  319 ++
 .../spdy/server/proxy/ProxySPDYToHTTPTest.java     |  545 ++
 .../spdy/server/proxy/ProxySPDYToSPDYLoadTest.java |  275 +
 .../spdy/server/proxy/ProxySPDYToSPDYTest.java     |  553 ++
 .../src/test/resources/big_script.js               |  791 +++
 .../src/test/resources/jetty-logging.properties    |   10 +
 .../src/test}/resources/keystore.jks               |  Bin
 .../src/test}/resources/truststore.jks             |  Bin
 jetty-spdy/spdy-jetty-http-webapp/pom.xml          |  104 -
 .../src/main/config/etc/jetty-spdy-proxy.xml       |   98 -
 .../src/main/config/etc/jetty-spdy.xml             |   86 -
 .../src/main/resources/log4j.properties            |   16 -
 .../src/main/webapp/WEB-INF/web.xml                |    6 -
 jetty-spdy/spdy-jetty-http/pom.xml                 |   82 -
 .../spdy/http/AbstractHTTPSPDYServerConnector.java |   66 -
 .../eclipse/jetty/spdy/http/HTTPSPDYHeader.java    |   78 -
 .../jetty/spdy/http/HTTPSPDYServerConnector.java   |   69 -
 .../org/eclipse/jetty/spdy/http/PushStrategy.java  |   43 -
 .../jetty/spdy/http/ReferrerPushStrategy.java      |  285 -
 .../http/ServerHTTPAsyncConnectionFactory.java     |   49 -
 .../spdy/http/ServerHTTPSPDYAsyncConnection.java   |  794 ---
 .../http/ServerHTTPSPDYAsyncConnectionFactory.java |  188 -
 .../jetty/spdy/proxy/HTTPSPDYProxyConnector.java   |   44 -
 .../org/eclipse/jetty/spdy/proxy/ProxyEngine.java  |   99 -
 .../jetty/spdy/proxy/ProxyEngineSelector.java      |  187 -
 .../proxy/ProxyHTTPAsyncConnectionFactory.java     |   46 -
 .../spdy/proxy/ProxyHTTPSPDYAsyncConnection.java   |  343 --
 .../eclipse/jetty/spdy/proxy/SPDYProxyEngine.java  |  519 --
 .../jetty/spdy/http/AbstractHTTPSPDYTest.java      |  124 -
 .../jetty/spdy/http/ConcurrentStreamsTest.java     |  118 -
 .../jetty/spdy/http/ProtocolNegotiationTest.java   |  254 -
 .../jetty/spdy/http/PushStrategyBenchmarkTest.java |  400 --
 .../spdy/http/ReferrerPushStrategyUnitTest.java    |  124 -
 .../spdy/http/ReferrerPushStrategyV2Test.java      |  802 ---
 .../spdy/http/ReferrerPushStrategyV3Test.java      |   31 -
 .../jetty/spdy/http/SSLExternalServerTest.java     |  101 -
 .../jetty/spdy/http/ServerHTTPSPDYv2Test.java      | 1277 -----
 .../jetty/spdy/http/ServerHTTPSPDYv3Test.java      |   31 -
 .../jetty/spdy/proxy/ProxyHTTPSPDYv2Test.java      |  769 ---
 .../src/test/resources/log4j.properties            |   14 -
 jetty-spdy/spdy-jetty/pom.xml                      |   82 -
 .../eclipse/jetty/spdy/AsyncConnectionFactory.java |   30 -
 .../eclipse/jetty/spdy/EmptyAsyncConnection.java   |   68 -
 .../org/eclipse/jetty/spdy/EmptyAsyncEndPoint.java |  234 -
 .../jetty/spdy/FlowControlStrategyFactory.java     |   42 -
 .../eclipse/jetty/spdy/SPDYAsyncConnection.java    |  247 -
 .../java/org/eclipse/jetty/spdy/SPDYClient.java    |  494 --
 .../eclipse/jetty/spdy/SPDYServerConnector.java    |  330 --
 .../spdy/ServerSPDYAsyncConnectionFactory.java     |  125 -
 .../java/org/eclipse/jetty/spdy/AbstractTest.java  |  128 -
 .../org/eclipse/jetty/spdy/ClosedStreamTest.java   |  269 -
 .../org/eclipse/jetty/spdy/FlowControlTest.java    |  490 --
 .../java/org/eclipse/jetty/spdy/GoAwayTest.java    |  230 -
 .../java/org/eclipse/jetty/spdy/HeadersTest.java   |   85 -
 .../org/eclipse/jetty/spdy/IdleTimeoutTest.java    |  253 -
 .../test/java/org/eclipse/jetty/spdy/PingTest.java |   93 -
 .../eclipse/jetty/spdy/ProtocolViolationsTest.java |  181 -
 .../org/eclipse/jetty/spdy/PushStreamTest.java     |  560 --
 .../org/eclipse/jetty/spdy/ResetStreamTest.java    |  202 -
 .../eclipse/jetty/spdy/SPDYClientFactoryTest.java  |   69 -
 .../jetty/spdy/SPDYServerConnectorTest.java        |   69 -
 .../org/eclipse/jetty/spdy/SSLEngineLeakTest.java  |   78 -
 .../org/eclipse/jetty/spdy/SSLSynReplyTest.java    |   50 -
 .../java/org/eclipse/jetty/spdy/SettingsTest.java  |  167 -
 .../jetty/spdy/SynDataReplyDataLoadTest.java       |  224 -
 .../java/org/eclipse/jetty/spdy/SynReplyTest.java  |  372 --
 .../eclipse/jetty/spdy/UnsupportedVersionTest.java |   97 -
 .../spdy-jetty/src/test/resources/log4j.properties |   14 -
 jetty-spdy/spdy-npn-tests/pom.xml                  |   93 +
 .../eclipse/jetty/spdy/server/AbstractNPNTest.java |   77 +
 .../eclipse/jetty/spdy/server/NPNModuleTest.java   |  193 +
 .../jetty/spdy/server/NPNNegotiationTest.java      |  207 +
 .../jetty/spdy/server/SSLEngineLeakTest.java       |   71 +
 .../eclipse/jetty/spdy/server/SSLSynReplyTest.java |  150 +
 .../server/proxy/NPNProxySPDYToHTTPLoadTest.java   |   29 +
 .../spdy/server/proxy/NPNProxySPDYToHTTPTest.java  |   27 +
 .../server/proxy/NPNProxySPDYToSPDYLoadTest.java   |   27 +
 .../spdy/server/proxy/NPNProxySPDYToSPDYTest.java  |   27 +
 .../src/test/resources/jetty-logging.properties    |    2 +
 .../src/test}/resources/keystore.jks               |  Bin
 .../src/test}/resources/truststore.jks             |  Bin
 jetty-spdy/spdy-server/pom.xml                     |   72 +
 .../jetty/spdy/server/NPNServerConnection.java     |   68 +
 .../spdy/server/NPNServerConnectionFactory.java    |   61 +
 .../spdy/server/SPDYServerConnectionFactory.java   |  245 +
 .../jetty/spdy/server/SPDYServerConnector.java     |   52 +
 .../eclipse/jetty/spdy/server/AbstractTest.java    |  150 +
 .../jetty/spdy/server/ClosedStreamTest.java        |  273 +
 .../eclipse/jetty/spdy/server/FlowControlTest.java |  493 ++
 .../org/eclipse/jetty/spdy/server/GoAwayTest.java  |  234 +
 .../org/eclipse/jetty/spdy/server/HeadersTest.java |   86 +
 .../eclipse/jetty/spdy/server/IdleTimeoutTest.java |  257 +
 .../jetty/spdy/server/MaxConcurrentStreamTest.java |  121 +
 .../org/eclipse/jetty/spdy/server/PingTest.java    |  106 +
 .../jetty/spdy/server/ProtocolViolationsTest.java  |  185 +
 .../eclipse/jetty/spdy/server/PushStreamTest.java  |  591 +++
 .../eclipse/jetty/spdy/server/ResetStreamTest.java |  204 +
 .../jetty/spdy/server/SPDYClientFactoryTest.java   |   75 +
 .../jetty/spdy/server/SPDYServerConnectorTest.java |   70 +
 .../eclipse/jetty/spdy/server/SettingsTest.java    |  168 +
 .../spdy/server/SynDataReplyDataLoadTest.java      |  288 +
 .../eclipse/jetty/spdy/server/SynReplyTest.java    |  375 ++
 .../jetty/spdy/server/UnsupportedVersionTest.java  |  100 +
 .../src/test/resources/jetty-logging.properties    |    2 +
 jetty-spring/pom.xml                               |   66 +
 jetty-spring/src/main/config/etc/jetty-spring.xml  |   61 +
 jetty-spring/src/main/config/modules/spring.mod    |   16 +
 .../main/java/org/eclipse/jetty/spring/Main.java   |   34 +
 .../jetty/spring/SpringConfigurationProcessor.java |  164 +
 .../SpringConfigurationProcessorFactory.java       |   42 +
 .../org/eclipse/jetty/spring/package-info.java     |   23 +
 ...eclipse.jetty.xml.ConfigurationProcessorFactory |    1 +
 .../jetty/spring/SpringXmlConfigurationTest.java   |  162 +
 .../eclipse/jetty/spring/TestConfiguration.java    |  155 +
 .../org/eclipse/jetty/spring/configure.xml         |   34 +
 .../resources/org/eclipse/jetty/spring/jetty.xml   |   42 +
 jetty-start/pom.xml                                |    2 +-
 .../java/org/eclipse/jetty/start/BaseHome.java     |  475 ++
 .../java/org/eclipse/jetty/start/Classpath.java    |  189 +-
 .../eclipse/jetty/start/CommandLineBuilder.java    |  139 +-
 .../main/java/org/eclipse/jetty/start/Config.java  | 1002 ----
 .../src/main/java/org/eclipse/jetty/start/FS.java  |  166 +
 .../main/java/org/eclipse/jetty/start/FileArg.java |  127 +
 .../eclipse/jetty/start/FilenameComparator.java    |   70 -
 .../java/org/eclipse/jetty/start/JarVersion.java   |   59 +-
 .../main/java/org/eclipse/jetty/start/Main.java    | 1565 +++---
 .../main/java/org/eclipse/jetty/start/Module.java  |  494 ++
 .../org/eclipse/jetty/start/ModuleGraphWriter.java |  260 +
 .../main/java/org/eclipse/jetty/start/Modules.java |  678 +++
 .../java/org/eclipse/jetty/start/NaturalSort.java  |   70 +
 .../java/org/eclipse/jetty/start/PathFinder.java   |  169 +
 .../java/org/eclipse/jetty/start/PathMatchers.java |  259 +
 .../main/java/org/eclipse/jetty/start/Props.java   |  376 ++
 .../org/eclipse/jetty/start/PropsException.java    |   41 +
 .../main/java/org/eclipse/jetty/start/README.TXT   |   48 +
 .../main/java/org/eclipse/jetty/start/README.txt   |   47 -
 .../main/java/org/eclipse/jetty/start/RawArgs.java |   97 +
 .../java/org/eclipse/jetty/start/StartArgs.java    | 1046 ++++
 .../java/org/eclipse/jetty/start/StartIni.java     |   74 +
 .../java/org/eclipse/jetty/start/StartLog.java     |  195 +
 .../java/org/eclipse/jetty/start/TextFile.java     |  127 +
 .../org/eclipse/jetty/start/UsageException.java    |   50 +
 .../main/java/org/eclipse/jetty/start/Version.java |  125 +-
 .../start/config/CommandLineConfigSource.java      |  245 +
 .../eclipse/jetty/start/config/ConfigSource.java   |   75 +
 .../eclipse/jetty/start/config/ConfigSources.java  |  164 +
 .../jetty/start/config/DirConfigSource.java        |  256 +
 .../jetty/start/config/JettyBaseConfigSource.java  |   36 +
 .../jetty/start/config/JettyHomeConfigSource.java  |   36 +
 .../java/org/eclipse/jetty/start/package-info.java |   23 +
 .../org/eclipse/jetty/start/base-home-warning.txt  |   16 +
 .../resources/org/eclipse/jetty/start/start.config |  161 -
 .../resources/org/eclipse/jetty/start/usage.txt    |  247 +-
 .../java/org/eclipse/jetty/start/BaseHomeTest.java |  211 +
 .../jetty/start/CommandLineBuilderTest.java        |   12 +-
 .../java/org/eclipse/jetty/start/ConfigTest.java   |  649 ---
 .../eclipse/jetty/start/ConfigurationAssert.java   |  281 +
 .../test/java/org/eclipse/jetty/start/FSTest.java  |   62 +
 .../java/org/eclipse/jetty/start/FileArgTest.java  |  100 +
 .../eclipse/jetty/start/IncludeJettyDirTest.java   |  559 ++
 .../org/eclipse/jetty/start/JarVersionTest.java    |   54 +
 .../java/org/eclipse/jetty/start/LicenseTest.java  |  181 +
 .../java/org/eclipse/jetty/start/MainTest.java     |  221 +-
 .../eclipse/jetty/start/ModuleGraphWriterTest.java |   74 +
 .../java/org/eclipse/jetty/start/ModuleTest.java   |   70 +
 .../java/org/eclipse/jetty/start/ModulesTest.java  |  334 ++
 .../org/eclipse/jetty/start/PathFinderTest.java    |   88 +
 .../jetty/start/PathMatchersAbsoluteTest.java      |   82 +
 .../jetty/start/PathMatchersSearchRootTest.java    |   89 +
 .../java/org/eclipse/jetty/start/PropertyDump.java |   74 +
 .../eclipse/jetty/start/PropertyPassingTest.java   |  214 +
 .../java/org/eclipse/jetty/start/PropsTest.java    |  140 +
 .../eclipse/jetty/start/RebuildTestResources.java  |  190 +
 .../eclipse/jetty/start/SystemExitAsException.java |   79 +
 .../org/eclipse/jetty/start/TestBadUseCases.java   |   85 +
 .../test/java/org/eclipse/jetty/start/TestEnv.java |   52 +
 .../java/org/eclipse/jetty/start/TestUseCases.java |  135 +
 .../java/org/eclipse/jetty/start/VersionTest.java  |    4 +-
 .../jetty/start/config/ConfigSourcesTest.java      |  599 +++
 .../src/test/resources/assert-home-with-jvm.txt    |   44 +
 .../src/test/resources/assert-home-with-spaces.txt |   11 +
 .../src/test/resources/assert-home-with-spdy.txt   |   72 +
 jetty-start/src/test/resources/assert-home.txt     |   37 +
 .../src/test/resources/bad-libs/no-manifest.jar    |  Bin 0 -> 3286 bytes
 jetty-start/src/test/resources/bad-libs/not-a.jar  |  Bin 0 -> 3142 bytes
 .../lib/JSR.ZIP => bad-libs/zero-length.jar}       |    0
 jetty-start/src/test/resources/bogus.xml           |    2 +
 .../etc/example-quickstart.xml}                    |    0
 .../lib/example.jar => dist-home/etc/hawtio.xml}   |    0
 .../etc/home-base-warning.xml}                     |    0
 .../lib/spec.zip => dist-home/etc/jamon.xml}       |    0
 .../resources/dist-home/etc/jetty-annotations.xml  |    0
 .../src/test/resources/dist-home/etc/jetty-cdi.xml |    0
 .../test/resources/dist-home/etc/jetty-debug.xml   |    0
 .../test/resources/dist-home/etc/jetty-deploy.xml  |    0
 .../test/resources/dist-home/etc/jetty-http.xml    |    0
 .../test/resources/dist-home/etc/jetty-https.xml   |    0
 .../resources/dist-home/etc/jetty-ipaccess.xml     |    0
 .../test/resources/dist-home/etc/jetty-jaas.xml    |    0
 .../resources/dist-home/etc/jetty-jmx-remote.xml   |    0
 .../src/test/resources/dist-home/etc/jetty-jmx.xml |    0
 .../test/resources/dist-home/etc/jetty-logging.xml |    0
 .../resources/dist-home/etc/jetty-lowresources.xml |    0
 .../test/resources/dist-home/etc/jetty-monitor.xml |    0
 .../test/resources/dist-home/etc/jetty-plus.xml    |    0
 .../test/resources/dist-home/etc/jetty-proxy.xml   |    0
 .../resources/dist-home/etc/jetty-requestlog.xml   |    0
 .../test/resources/dist-home/etc/jetty-rewrite.xml |    0
 .../test/resources/dist-home/etc/jetty-setuid.xml  |    0
 .../resources/dist-home/etc/jetty-spdy-proxy.xml   |    0
 .../test/resources/dist-home/etc/jetty-spdy.xml    |    0
 .../test/resources/dist-home/etc/jetty-spring.xml  |    0
 .../src/test/resources/dist-home/etc/jetty-ssl.xml |    0
 .../test/resources/dist-home/etc/jetty-started.xml |    0
 .../test/resources/dist-home/etc/jetty-stats.xml   |    0
 .../test/resources/dist-home/etc/jetty-xinetd.xml  |    0
 .../src/test/resources/dist-home/etc/jetty.xml     |    0
 .../src/test/resources/dist-home/etc/jminix.xml    |    0
 .../src/test/resources/dist-home/etc/jolokia.xml   |    0
 .../resources/dist-home/etc/protonego-alpn.xml     |    0
 .../test/resources/dist-home/etc/protonego-npn.xml |    0
 .../test/resources/dist-home/etc/webdefault.xml    |    0
 .../dist-home/lib/annotations/asm-5.0.1.jar        |    0
 .../lib/annotations/asm-commons-5.0.1.jar          |    0
 .../lib/annotations/javax.annotation-api-1.2.jar   |    0
 .../org.eclipse.jetty.apache-jsp-TEST.jar          |    0
 ....orbit.org.eclipse.jdt.core-3.8.2.v20130121.jar |    0
 .../org.mortbay.jasper.apache-el-8.0.9.M3.jar      |    0
 .../org.mortbay.jasper.apache-jsp-8.0.9.M3.jar     |    0
 ....apache.taglibs.taglibs-standard-impl-1.2.1.jar |    0
 ....apache.taglibs.taglibs-standard-spec-1.2.1.jar |    0
 .../dist-home/lib/fcgi/fcgi-client-TEST.jar        |    0
 .../dist-home/lib/fcgi/fcgi-server-TEST.jar        |    0
 ...x.security.auth.message-1.0.0.v201108011116.jar |    0
 .../dist-home/lib/jetty-alpn-client-TEST.jar       |    0
 .../dist-home/lib/jetty-alpn-server-TEST.jar       |    0
 .../dist-home/lib/jetty-annotations-TEST.jar       |    0
 .../resources/dist-home/lib/jetty-cdi-TEST.jar     |    0
 .../resources/dist-home/lib/jetty-client-TEST.jar  |    0
 .../dist-home/lib/jetty-continuation-TEST.jar      |    0
 .../resources/dist-home/lib/jetty-deploy-TEST.jar  |    0
 .../resources/dist-home/lib/jetty-http-TEST.jar    |    0
 .../test/resources/dist-home/lib/jetty-io-TEST.jar |    0
 .../resources/dist-home/lib/jetty-jaas-TEST.jar    |    0
 .../resources/dist-home/lib/jetty-jaspi-TEST.jar   |    0
 .../resources/dist-home/lib/jetty-jmx-TEST.jar     |    0
 .../resources/dist-home/lib/jetty-jndi-TEST.jar    |    0
 .../resources/dist-home/lib/jetty-plus-TEST.jar    |    0
 .../resources/dist-home/lib/jetty-proxy-TEST.jar   |    0
 .../dist-home/lib/jetty-quickstart-TEST.jar        |    0
 .../resources/dist-home/lib/jetty-rewrite-TEST.jar |    0
 .../resources/dist-home/lib/jetty-schemas-3.1.jar  |    0
 .../dist-home/lib/jetty-security-TEST.jar          |    0
 .../resources/dist-home/lib/jetty-server-TEST.jar  |    0
 .../resources/dist-home/lib/jetty-servlet-TEST.jar |    0
 .../dist-home/lib/jetty-servlets-TEST.jar          |    0
 .../resources/dist-home/lib/jetty-util-TEST.jar    |    0
 .../resources/dist-home/lib/jetty-webapp-TEST.jar  |    0
 .../resources/dist-home/lib/jetty-xml-TEST.jar     |    0
 .../javax.mail.glassfish-1.4.1.v201005082020.jar   |    0
 .../lib/jndi/javax.transaction-api-1.2.jar         |    0
 .../resources/dist-home/lib/jsp/javax.el-3.0.0.jar |    0
 .../dist-home/lib/jsp/javax.servlet.jsp-2.3.2.jar  |    0
 .../lib/jsp/javax.servlet.jsp-api-2.3.1.jar        |    0
 .../lib/jsp/javax.servlet.jsp.jstl-1.2.2.jar       |    0
 .../dist-home/lib/jsp/jetty-jsp-jdt-2.3.3.jar      |    0
 .../jsp/org.eclipse.jdt.core-3.8.2.v20130121.jar   |    0
 ....javax.servlet.jsp.jstl-1.2.0.v201105211821.jar |    0
 .../dist-home/lib/monitor/jetty-monitor-TEST.jar   |    0
 .../resources/dist-home/lib/servlet-api-3.1.jar    |    0
 .../lib/setuid/jetty-setuid-java-1.0.1.jar         |    0
 .../dist-home/lib/spdy/spdy-client-TEST.jar        |    0
 .../dist-home/lib/spdy/spdy-core-TEST.jar          |    0
 .../dist-home/lib/spdy/spdy-http-common-TEST.jar   |    0
 .../dist-home/lib/spdy/spdy-http-server-TEST.jar   |    0
 .../dist-home/lib/spdy/spdy-server-TEST.jar        |    0
 .../dist-home/lib/spring/jetty-spring-TEST.jar     |    0
 .../websocket/javax-websocket-client-impl-TEST.jar |    0
 .../websocket/javax-websocket-server-impl-TEST.jar |    0
 .../lib/websocket/javax.websocket-api-1.0.jar      |    0
 .../dist-home/lib/websocket/websocket-api-TEST.jar |    0
 .../lib/websocket/websocket-client-TEST.jar        |    0
 .../lib/websocket/websocket-common-TEST.jar        |    0
 .../lib/websocket/websocket-server-TEST.jar        |    0
 .../lib/websocket/websocket-servlet-TEST.jar       |    0
 .../resources/dist-home/modules/annotations.mod    |   17 +
 .../src/test/resources/dist-home/modules/cdi.mod   |   26 +
 .../test/resources/dist-home/modules/client.mod    |    6 +
 .../resources/dist-home/modules/continuation.mod   |    6 +
 .../src/test/resources/dist-home/modules/debug.mod |    9 +
 .../test/resources/dist-home/modules/deploy.mod    |   21 +
 .../src/test/resources/dist-home/modules/ext.mod   |   11 +
 .../src/test/resources/dist-home/modules/fcgi.mod  |   15 +
 .../test/resources/dist-home/modules/hawtio.mod    |   28 +
 .../dist-home/modules/home-base-warning.mod        |    7 +
 .../src/test/resources/dist-home/modules/http.mod  |   27 +
 .../src/test/resources/dist-home/modules/https.mod |   19 +
 .../test/resources/dist-home/modules/ipaccess.mod  |    9 +
 .../src/test/resources/dist-home/modules/jaas.mod  |   16 +
 .../src/test/resources/dist-home/modules/jamon.mod |   30 +
 .../src/test/resources/dist-home/modules/jaspi.mod |   10 +
 .../test/resources/dist-home/modules/jminix.mod    |   41 +
 .../resources/dist-home/modules/jmx-remote.mod     |   18 +
 .../src/test/resources/dist-home/modules/jmx.mod   |   13 +
 .../src/test/resources/dist-home/modules/jndi.mod  |   11 +
 .../test/resources/dist-home/modules/jolokia.mod   |   19 +
 .../dist-home/modules/jsp-impl/apache-jsp.mod      |   10 +
 .../dist-home/modules/jsp-impl/apache-jstl.mod     |    8 +
 .../dist-home/modules/jsp-impl/glassfish-jsp.mod   |    8 +
 .../dist-home/modules/jsp-impl/glassfish-jstl.mod  |    6 +
 .../src/test/resources/dist-home/modules/jsp.mod   |   21 +
 .../src/test/resources/dist-home/modules/jstl.mod  |   14 +
 .../src/test/resources/dist-home/modules/jvm.mod   |   23 +
 .../test/resources/dist-home/modules/logging.mod   |   31 +
 .../resources/dist-home/modules/lowresources.mod   |   18 +
 .../test/resources/dist-home/modules/monitor.mod   |   13 +
 .../src/test/resources/dist-home/modules/plus.mod  |   15 +
 .../modules/protonego-impl/alpn-1.7.0_40.mod       |    8 +
 .../modules/protonego-impl/alpn-1.7.0_45.mod       |    8 +
 .../modules/protonego-impl/alpn-1.7.0_51.mod       |    8 +
 .../modules/protonego-impl/alpn-1.7.0_55.mod       |    8 +
 .../modules/protonego-impl/alpn-1.7.0_60.mod       |    8 +
 .../modules/protonego-impl/alpn-1.7.0_65.mod       |    8 +
 .../modules/protonego-impl/alpn-1.7.0_67.mod       |    8 +
 .../modules/protonego-impl/alpn-1.7.0_71.mod       |    8 +
 .../modules/protonego-impl/alpn-1.7.0_72.mod       |    8 +
 .../modules/protonego-impl/alpn-1.7.0_75.mod       |    8 +
 .../modules/protonego-impl/alpn-1.7.0_76.mod       |    8 +
 .../modules/protonego-impl/alpn-1.7.0_79.mod       |    8 +
 .../modules/protonego-impl/alpn-1.7.0_80.mod       |    8 +
 .../modules/protonego-impl/alpn-1.8.0.mod          |    8 +
 .../modules/protonego-impl/alpn-1.8.0_05.mod       |    8 +
 .../modules/protonego-impl/alpn-1.8.0_11.mod       |    8 +
 .../modules/protonego-impl/alpn-1.8.0_20.mod       |    8 +
 .../modules/protonego-impl/alpn-1.8.0_25.mod       |    8 +
 .../modules/protonego-impl/alpn-1.8.0_31.mod       |    8 +
 .../modules/protonego-impl/alpn-1.8.0_40.mod       |    8 +
 .../modules/protonego-impl/alpn-1.8.0_45.mod       |    8 +
 .../modules/protonego-impl/alpn-1.8.0_51.mod       |    8 +
 .../dist-home/modules/protonego-impl/alpn.mod      |   42 +
 .../modules/protonego-impl/npn-1.7.0_04.mod        |    8 +
 .../modules/protonego-impl/npn-1.7.0_05.mod        |    8 +
 .../modules/protonego-impl/npn-1.7.0_06.mod        |    8 +
 .../modules/protonego-impl/npn-1.7.0_07.mod        |    8 +
 .../modules/protonego-impl/npn-1.7.0_09.mod        |    8 +
 .../modules/protonego-impl/npn-1.7.0_10.mod        |    8 +
 .../modules/protonego-impl/npn-1.7.0_11.mod        |    8 +
 .../modules/protonego-impl/npn-1.7.0_13.mod        |    8 +
 .../modules/protonego-impl/npn-1.7.0_15.mod        |    8 +
 .../modules/protonego-impl/npn-1.7.0_17.mod        |    8 +
 .../modules/protonego-impl/npn-1.7.0_21.mod        |    8 +
 .../modules/protonego-impl/npn-1.7.0_25.mod        |    8 +
 .../modules/protonego-impl/npn-1.7.0_40.mod        |    8 +
 .../modules/protonego-impl/npn-1.7.0_45.mod        |    8 +
 .../modules/protonego-impl/npn-1.7.0_51.mod        |    8 +
 .../modules/protonego-impl/npn-1.7.0_55.mod        |    8 +
 .../modules/protonego-impl/npn-1.7.0_60.mod        |    8 +
 .../modules/protonego-impl/npn-1.7.0_65.mod        |    8 +
 .../modules/protonego-impl/npn-1.7.0_67.mod        |    8 +
 .../modules/protonego-impl/npn-1.7.0_71.mod        |    8 +
 .../modules/protonego-impl/npn-1.7.0_72.mod        |    8 +
 .../modules/protonego-impl/npn-1.7.0_75.mod        |    8 +
 .../modules/protonego-impl/npn-1.7.0_76.mod        |    8 +
 .../modules/protonego-impl/npn-1.7.0_79.mod        |    8 +
 .../modules/protonego-impl/npn-1.7.0_80.mod        |    8 +
 .../dist-home/modules/protonego-impl/npn.mod       |   37 +
 .../test/resources/dist-home/modules/protonego.mod |   24 +
 .../src/test/resources/dist-home/modules/proxy.mod |   22 +
 .../resources/dist-home/modules/quickstart.mod     |   12 +
 .../resources/dist-home/modules/requestlog.mod     |   30 +
 .../test/resources/dist-home/modules/resources.mod |   10 +
 .../test/resources/dist-home/modules/rewrite.mod   |   12 +
 .../test/resources/dist-home/modules/security.mod  |    9 +
 .../test/resources/dist-home/modules/server.mod    |   49 +
 .../test/resources/dist-home/modules/servlet.mod   |    9 +
 .../test/resources/dist-home/modules/servlets.mod  |   10 +
 .../test/resources/dist-home/modules/setuid.mod    |   19 +
 .../src/test/resources/dist-home/modules/spdy.mod  |   26 +
 .../test/resources/dist-home/modules/spring.mod    |   16 +
 .../src/test/resources/dist-home/modules/ssl.mod   |   40 +
 .../src/test/resources/dist-home/modules/stats.mod |    9 +
 .../test/resources/dist-home/modules/webapp.mod    |   10 +
 .../test/resources/dist-home/modules/websocket.mod |   12 +
 .../test/resources/dist-home/modules/xinetd.mod    |   17 +
 .../src/test/resources/empty.home/start.ini        |    3 +
 .../extra-jetty-dirs/logging/etc/jetty-logging.xml |    0
 .../logging/lib/logging/logback.jar                |    0
 .../resources/extra-jetty-dirs/logging/start.ini   |    1 +
 .../extra-jetty-dirs/more-startd/start.d/more.ini  |    0
 .../{jetty.home/lib => extra-libs}/example.jar     |    0
 .../example.properties                             |    0
 .../src/test/resources/hb.1/base/start.d/jmx.ini   |    0
 .../test/resources/hb.1/base/start.d/logging.ini   |    1 +
 .../src/test/resources/hb.1/base/start.d/myapp.ini |    0
 jetty-start/src/test/resources/hb.1/base/start.ini |    7 +
 .../src/test/resources/hb.1/home/start.d/jmx.ini   |    0
 .../src/test/resources/hb.1/home/start.d/jndi.ini  |    0
 .../src/test/resources/hb.1/home/start.d/jsp.ini   |    0
 .../test/resources/hb.1/home/start.d/logging.ini   |    1 +
 .../src/test/resources/hb.1/home/start.d/ssl.ini   |    0
 jetty-start/src/test/resources/hb.1/home/start.ini |   11 +
 .../lib/example of a library with spaces.jar       |    0
 .../jetty home with spaces/modules/base.mod        |    2 +
 .../resources/jetty home with spaces/start.ini     |    6 +
 .../jetty.home/etc/test-jetty-security.xml         |    1 -
 .../resources/jetty.home/etc/test-jetty-ssl.xml    |    1 -
 .../test/resources/jetty.home/etc/test-jetty.xml   |    1 -
 .../src/test/resources/jetty.home/lib/core.jar     |  Bin 1177 -> 0 bytes
 .../resources/jetty.home/lib/foo/bar/foobar.jar    |  Bin 1172 -> 0 bytes
 .../src/test/resources/jetty.home/lib/http.jar     |  Bin 1172 -> 0 bytes
 .../src/test/resources/jetty.home/lib/io.jar       |  Bin 1167 -> 0 bytes
 .../src/test/resources/jetty.home/lib/readme.txt   |    5 -
 .../src/test/resources/jetty.home/lib/server.jar   |  Bin 1175 -> 0 bytes
 .../src/test/resources/jetty.home/lib/util.jar     |  Bin 1189 -> 0 bytes
 .../src/test/resources/jetty.home/lib/xml.jar      |  Bin 1206 -> 0 bytes
 .../test/resources/jetty.home/start.d/10-jmx.ini   |   22 -
 .../resources/jetty.home/start.d/20-websocket.ini  |   13 -
 .../resources/jetty.home/start.d/90-testrealm.ini  |    1 -
 .../src/test/resources/jetty.home/start.ini        |   65 -
 jetty-start/src/test/resources/test-alt.xml        |    1 -
 .../test/resources/usecases/assert-barebones.txt   |   16 +
 .../assert-enable-spdy-bad-npn-version.txt         |   16 +
 .../test/resources/usecases/assert-enable-spdy.txt |   49 +
 .../usecases/assert-include-jetty-dir-logging.txt  |   24 +
 .../src/test/resources/usecases/assert-jmx.txt     |   18 +
 .../test/resources/usecases/assert-jsp-apache.txt  |   26 +
 .../resources/usecases/assert-jsp-glassfish.txt    |   28 +
 .../src/test/resources/usecases/assert-logging.txt |   32 +
 .../usecases/assert-missing-npn-version.txt        |   28 +
 .../test/resources/usecases/assert-props.agent.txt |   20 +
 .../test/resources/usecases/assert-props.basic.txt |   17 +
 .../src/test/resources/usecases/assert-with-db.txt |   31 +
 .../usecases/assert-with-module-persistence.txt    |   44 +
 .../test/resources/usecases/assert-with.ext.txt    |   26 +
 .../resources/usecases/base.barebones/start.ini    |    5 +
 .../base.enable.spdy.bad.npn.version/start.ini     |   12 +
 .../resources/usecases/base.enable.spdy/start.ini  |   12 +
 .../src/test/resources/usecases/base.jmx/start.ini |    4 +
 .../base.logging/lib/logging/jul-to-slf4j.jar      |    0
 .../base.logging/lib/logging/logback-classic.jar   |    0
 .../base.logging/lib/logging/logback-core.jar      |    0
 .../base.logging/lib/logging/slf4j-api.jar         |    0
 .../usecases/base.logging/modules/logging.mod      |   20 +
 .../resources/jetty-logging.properties             |    2 +
 .../usecases/base.logging/resources/logback.xml    |    0
 .../test/resources/usecases/base.logging/start.ini |    7 +
 .../usecases/base.missing.npn.version/start.ini    |   12 +
 .../base.props.agent/lib/agent-jdk-1.5.jar         |    0
 .../base.props.agent/lib/agent-jdk-1.6.jar         |    0
 .../base.props.agent/lib/agent-jdk-1.7.jar         |    0
 .../usecases/base.props.agent/modules/agent.mod    |    8 +
 .../resources/usecases/base.props.agent/start.ini  |    4 +
 .../resources/usecases/base.props.basic/start.ini  |    5 +
 .../usecases/base.with.db/etc/jetty-db.xml         |    1 +
 .../usecases/base.with.db/lib/db/bonecp.jar        |    0
 .../usecases/base.with.db/lib/db/mysql-driver.jar  |    0
 .../resources/usecases/base.with.db/modules/db.mod |   11 +
 .../test/resources/usecases/base.with.db/start.ini |    7 +
 .../usecases/base.with.ext/lib/ext/agent.jar       |    0
 .../base.with.ext/lib/ext/jdbc/mariadb-jdbc.jar    |    0
 .../base.with.ext/lib/ext/logging/jul-to-slf4j.jar |    0
 .../lib/ext/logging/logback-classic.jar            |    0
 .../base.with.ext/lib/ext/logging/logback-core.jar |    0
 .../base.with.ext/lib/ext/logging/slf4j-api.jar    |    0
 .../usecases/base.with.ext/lib/jetty-util-alt.jar  |    0
 .../resources/usecases/base.with.ext/start.ini     |    6 +
 .../base.with.include.jetty.dirs/start.ini         |    5 +
 .../usecases/base.with.jsp.apache/start.ini        |    7 +
 .../resources/usecases/base.with.jsp.bad/start.ini |    7 +
 .../usecases/base.with.jsp.default/start.ini       |    6 +
 .../usecases/base.with.jsp.glassfish/start.ini     |    7 +
 .../test/resources/usecases/home/etc/README.spnego |    0
 .../usecases/home/etc/jdbcRealm.properties         |    0
 .../usecases/home/etc/jetty-annotations.xml        |    0
 .../resources/usecases/home/etc/jetty-contexts.xml |    0
 .../resources/usecases/home/etc/jetty-debug.xml    |    0
 .../resources/usecases/home/etc/jetty-demo.xml     |    0
 .../resources/usecases/home/etc/jetty-deploy.xml   |    0
 .../resources/usecases/home/etc/jetty-http.xml     |    0
 .../resources/usecases/home/etc/jetty-https.xml    |    0
 .../resources/usecases/home/etc/jetty-ipaccess.xml |    0
 .../resources/usecases/home/etc/jetty-jaas.xml     |    0
 .../test/resources/usecases/home/etc/jetty-jmx.xml |    0
 .../resources/usecases/home/etc/jetty-logging.xml  |    0
 .../usecases/home/etc/jetty-lowresources.xml       |    0
 .../resources/usecases/home/etc/jetty-monitor.xml  |    0
 .../resources/usecases/home/etc/jetty-plus.xml     |    0
 .../resources/usecases/home/etc/jetty-proxy.xml    |    0
 .../usecases/home/etc/jetty-requestlog.xml         |    0
 .../resources/usecases/home/etc/jetty-rewrite.xml  |    0
 .../resources/usecases/home/etc/jetty-setuid.xml   |    0
 .../usecases/home/etc/jetty-spdy-proxy.xml         |    0
 .../resources/usecases/home/etc/jetty-spdy.xml     |    0
 .../test/resources/usecases/home/etc/jetty-ssl.xml |    0
 .../resources/usecases/home/etc/jetty-started.xml  |    0
 .../resources/usecases/home/etc/jetty-stats.xml    |    0
 .../usecases/home/etc/jetty-testrealm.xml          |    0
 .../resources/usecases/home/etc/jetty-webapps.xml  |    0
 .../usecases/home/etc/jetty-websockets.xml         |    0
 .../resources/usecases/home/etc/jetty-xinetd.xml   |    0
 .../test/resources/usecases/home/etc/jetty.conf    |    0
 .../src/test/resources/usecases/home/etc/jetty.xml |    0
 .../src/test/resources/usecases/home/etc/keystore  |    0
 .../src/test/resources/usecases/home/etc/krb5.ini  |    0
 .../resources/usecases/home/etc/protonego-alpn.xml |    0
 .../resources/usecases/home/etc/protonego-npn.xml  |    0
 .../resources/usecases/home/etc/realm.properties   |    0
 .../test/resources/usecases/home/etc/spnego.conf   |    0
 .../resources/usecases/home/etc/spnego.properties  |    0
 .../resources/usecases/home/etc/test-realm.xml     |    0
 .../resources/usecases/home/etc/webdefault.xml     |    0
 .../lib/annotations/javax.annotation-api-1.2.jar   |    0
 .../lib/annotations/org.objectweb.asm-TEST.jar     |    0
 ...avax.servlet.jsp.javax.servlet.jsp-api-TEST.jar |    0
 .../org.eclipse.jetty.apache-jsp-TEST.jar          |    0
 ...lipse.jetty.orbit.org.eclipse.jdt.core-TEST.jar |    0
 .../org.mortbay.jasper.apache-el-TEST.jar          |    0
 .../org.mortbay.jasper.apache-jsp-TEST.jar         |    0
 ...g.apache.taglibs.taglibs-standard-impl-TEST.jar |    0
 ...g.apache.taglibs.taglibs-standard-spec-TEST.jar |    0
 .../test/resources/usecases/home/lib/ext/.nodelete |    0
 .../usecases/home/lib/jetty-annotations-TEST.jar   |    0
 .../usecases/home/lib/jetty-client-TEST.jar        |    0
 .../usecases/home/lib/jetty-continuation-TEST.jar  |    0
 .../usecases/home/lib/jetty-deploy-TEST.jar        |    0
 .../usecases/home/lib/jetty-http-TEST.jar          |    0
 .../resources/usecases/home/lib/jetty-io-TEST.jar  |    0
 .../usecases/home/lib/jetty-jaas-TEST.jar          |    0
 .../resources/usecases/home/lib/jetty-jmx-TEST.jar |    0
 .../usecases/home/lib/jetty-jndi-TEST.jar          |    0
 .../resources/usecases/home/lib/jetty-jsp-TEST.jar |    0
 .../usecases/home/lib/jetty-plus-TEST.jar          |    0
 .../usecases/home/lib/jetty-proxy-TEST.jar         |    0
 .../usecases/home/lib/jetty-rewrite-TEST.jar       |    0
 .../usecases/home/lib/jetty-schemas-3.1.RC0.jar    |    0
 .../usecases/home/lib/jetty-schemas-3.1.jar        |    0
 .../usecases/home/lib/jetty-security-TEST.jar      |    0
 .../usecases/home/lib/jetty-server-TEST.jar        |    0
 .../usecases/home/lib/jetty-servlet-TEST.jar       |    0
 .../usecases/home/lib/jetty-servlets-TEST.jar      |    0
 .../usecases/home/lib/jetty-util-TEST.jar          |    0
 .../usecases/home/lib/jetty-webapp-TEST.jar        |    0
 .../resources/usecases/home/lib/jetty-xml-TEST.jar |    0
 .../home/lib/jndi/javax.activation-1.1.jar         |    0
 .../home/lib/jndi/javax.transaction-api-1.2.jar    |    0
 .../usecases/home/lib/jsp/javax.el-TEST.jar        |    0
 .../home/lib/jsp/javax.servlet.jsp-TEST.jar        |    0
 .../home/lib/jsp/javax.servlet.jsp-api-TEST.jar    |    0
 .../home/lib/jsp/javax.servlet.jsp.jstl-TEST.jar   |    0
 .../usecases/home/lib/jsp/jetty-jsp-jdt-TEST.jar   |    0
 .../home/lib/jsp/org.eclipse.jdt.core-TEST.jar     |    0
 ...pse.jetty.orbit.javax.servlet.jsp.jstl-TEST.jar |    0
 .../home/lib/monitor/jetty-monitor-TEST.jar        |    0
 .../usecases/home/lib/servlet-api-3.1.jar          |    0
 .../home/lib/setuid/jetty-setuid-java-1.0.1.jar    |    0
 .../usecases/home/lib/setuid/libsetuid-linux.so    |    0
 .../usecases/home/lib/setuid/libsetuid-osx.so      |    0
 .../usecases/home/lib/spdy/spdy-client-TEST.jar    |    0
 .../usecases/home/lib/spdy/spdy-core-TEST.jar      |    0
 .../home/lib/spdy/spdy-http-common-TEST.jar        |    0
 .../home/lib/spdy/spdy-http-server-TEST.jar        |    0
 .../usecases/home/lib/spdy/spdy-server-TEST.jar    |    0
 .../websocket/javax-websocket-client-impl-TEST.jar |    0
 .../websocket/javax-websocket-server-impl-TEST.jar |    0
 .../home/lib/websocket/javax.websocket-api-1.0.jar |    0
 .../home/lib/websocket/websocket-api-TEST.jar      |    0
 .../home/lib/websocket/websocket-client-TEST.jar   |    0
 .../home/lib/websocket/websocket-common-TEST.jar   |    0
 .../home/lib/websocket/websocket-server-TEST.jar   |    0
 .../home/lib/websocket/websocket-servlet-TEST.jar  |    0
 .../usecases/home/modules/annotations.mod          |   17 +
 .../test/resources/usecases/home/modules/base.mod  |   11 +
 .../resources/usecases/home/modules/client.mod     |    7 +
 .../test/resources/usecases/home/modules/debug.mod |    9 +
 .../resources/usecases/home/modules/deploy.mod     |   14 +
 .../test/resources/usecases/home/modules/ext.mod   |   10 +
 .../test/resources/usecases/home/modules/http.mod  |    9 +
 .../test/resources/usecases/home/modules/https.mod |   10 +
 .../resources/usecases/home/modules/ipaccess.mod   |    9 +
 .../test/resources/usecases/home/modules/jaas.mod  |   14 +
 .../test/resources/usecases/home/modules/jmx.mod   |   11 +
 .../test/resources/usecases/home/modules/jndi.mod  |   11 +
 .../usecases/home/modules/jsp-impl/apache-jsp.mod  |   10 +
 .../home/modules/jsp-impl/glassfish-jsp.mod        |    8 +
 .../test/resources/usecases/home/modules/jsp.mod   |   20 +
 .../resources/usecases/home/modules/logging.mod    |   31 +
 .../usecases/home/modules/lowresources.mod         |    9 +
 .../resources/usecases/home/modules/monitor.mod    |   13 +
 .../test/resources/usecases/home/modules/plus.mod  |   15 +
 .../home/modules/protonego-impl/alpn-1.7.0_40.mod  |    8 +
 .../home/modules/protonego-impl/alpn-1.7.0_45.mod  |    8 +
 .../home/modules/protonego-impl/alpn-1.7.0_51.mod  |    8 +
 .../home/modules/protonego-impl/alpn-1.7.0_55.mod  |    8 +
 .../home/modules/protonego-impl/alpn-1.7.0_60.mod  |    8 +
 .../home/modules/protonego-impl/alpn-1.8.0.mod     |    8 +
 .../home/modules/protonego-impl/alpn-1.8.0_05.mod  |    8 +
 .../usecases/home/modules/protonego-impl/alpn.mod  |   36 +
 .../home/modules/protonego-impl/npn-1.7.0_04.mod   |    8 +
 .../home/modules/protonego-impl/npn-1.7.0_05.mod   |    8 +
 .../home/modules/protonego-impl/npn-1.7.0_06.mod   |    8 +
 .../home/modules/protonego-impl/npn-1.7.0_07.mod   |    8 +
 .../home/modules/protonego-impl/npn-1.7.0_09.mod   |    8 +
 .../home/modules/protonego-impl/npn-1.7.0_10.mod   |    8 +
 .../home/modules/protonego-impl/npn-1.7.0_11.mod   |    8 +
 .../home/modules/protonego-impl/npn-1.7.0_13.mod   |    8 +
 .../home/modules/protonego-impl/npn-1.7.0_15.mod   |    8 +
 .../home/modules/protonego-impl/npn-1.7.0_17.mod   |    8 +
 .../home/modules/protonego-impl/npn-1.7.0_21.mod   |    8 +
 .../home/modules/protonego-impl/npn-1.7.0_25.mod   |    8 +
 .../home/modules/protonego-impl/npn-1.7.0_40.mod   |    8 +
 .../home/modules/protonego-impl/npn-1.7.0_45.mod   |    8 +
 .../home/modules/protonego-impl/npn-1.7.0_51.mod   |    8 +
 .../home/modules/protonego-impl/npn-1.7.0_55.mod   |    8 +
 .../home/modules/protonego-impl/npn-1.7.0_60.mod   |    8 +
 .../usecases/home/modules/protonego-impl/npn.mod   |   31 +
 .../resources/usecases/home/modules/protonego.mod  |   15 +
 .../test/resources/usecases/home/modules/proxy.mod |   14 +
 .../resources/usecases/home/modules/requestlog.mod |    9 +
 .../resources/usecases/home/modules/resources.mod  |   10 +
 .../resources/usecases/home/modules/rewrite.mod    |   13 +
 .../resources/usecases/home/modules/security.mod   |    9 +
 .../resources/usecases/home/modules/server.mod     |   21 +
 .../resources/usecases/home/modules/servlet.mod    |    9 +
 .../test/resources/usecases/home/modules/spdy.mod  |   26 +
 .../test/resources/usecases/home/modules/ssl.mod   |   35 +
 .../test/resources/usecases/home/modules/stats.mod |    9 +
 .../resources/usecases/home/modules/webapp.mod     |    9 +
 .../resources/usecases/home/modules/websocket.mod  |   17 +
 .../resources/usecases/home/modules/xinetd.mod     |    9 +
 .../test/resources/usecases/home/modules/xml.mod   |   10 +
 .../resources/usecases/home/resources/.nodelete    |    0
 .../src/test/resources/usecases/home/start.ini     |    2 +
 jetty-util-ajax/pom.xml                            |   91 +
 .../java/org/eclipse/jetty/util/ajax/JSON.java     | 1640 ++++++
 .../jetty/util/ajax/JSONCollectionConvertor.java   |    0
 .../eclipse/jetty/util/ajax/JSONDateConvertor.java |  105 +
 .../eclipse/jetty/util/ajax/JSONEnumConvertor.java |    0
 .../jetty/util/ajax/JSONObjectConvertor.java       |    0
 .../eclipse/jetty/util/ajax/JSONPojoConvertor.java |    0
 .../jetty/util/ajax/JSONPojoConvertorFactory.java  |    0
 .../org/eclipse/jetty/util/ajax/package-info.java  |   23 +
 .../util/ajax/JSONCollectionConvertorTest.java     |    0
 .../util/ajax/JSONPojoConvertorFactoryTest.java    |  417 ++
 .../jetty/util/ajax/JSONPojoConvertorTest.java     |  441 ++
 .../java/org/eclipse/jetty/util/ajax/JSONTest.java |  469 ++
 jetty-util/pom.xml                                 |   13 +-
 jetty-util/src/main/config/etc/jetty-logging.xml   |   12 +-
 jetty-util/src/main/config/modules/logging.mod     |   31 +
 .../java/org/eclipse/jetty/util/AbstractTrie.java  |   85 +
 .../java/org/eclipse/jetty/util/ArrayQueue.java    |   33 +-
 .../org/eclipse/jetty/util/ArrayTernaryTrie.java   |  511 ++
 .../java/org/eclipse/jetty/util/ArrayTrie.java     |  477 ++
 .../java/org/eclipse/jetty/util/ArrayUtil.java     |  142 +
 .../main/java/org/eclipse/jetty/util/Atomics.java  |   20 +-
 .../java/org/eclipse/jetty/util/AttributesMap.java |  138 +-
 .../main/java/org/eclipse/jetty/util/B64Code.java  |  117 +-
 .../org/eclipse/jetty/util/BlockingArrayQueue.java |  872 +--
 .../org/eclipse/jetty/util/BlockingCallback.java   |  105 +
 .../java/org/eclipse/jetty/util/BufferUtil.java    | 1088 ++++
 .../eclipse/jetty/util/ByteArrayISO8859Writer.java |   13 +-
 .../eclipse/jetty/util/ByteArrayOutputStream2.java |    5 +
 .../main/java/org/eclipse/jetty/util/Callback.java |   79 +
 .../jetty/util/ClassLoadingObjectInputStream.java  |  105 +
 .../eclipse/jetty/util/CompletableCallback.java    |  168 +
 .../eclipse/jetty/util/ConcurrentArrayQueue.java   |  573 ++
 .../org/eclipse/jetty/util/CountingCallback.java   |   98 +
 .../java/org/eclipse/jetty/util/DateCache.java     |  275 +-
 .../main/java/org/eclipse/jetty/util/Fields.java   |  336 ++
 .../org/eclipse/jetty/util/FutureCallback.java     |  157 +
 .../java/org/eclipse/jetty/util/FuturePromise.java |  159 +
 .../org/eclipse/jetty/util/HttpCookieStore.java    |  114 +
 .../src/main/java/org/eclipse/jetty/util/IO.java   |  146 +-
 .../org/eclipse/jetty/util/IteratingCallback.java  |  543 ++
 .../jetty/util/IteratingNestedCallback.java        |   67 +
 .../main/java/org/eclipse/jetty/util/Jetty.java    |   39 +
 .../main/java/org/eclipse/jetty/util/LazyList.java |   98 +-
 .../java/org/eclipse/jetty/util/LeakDetector.java  |  208 +
 .../main/java/org/eclipse/jetty/util/Loader.java   |   70 +-
 .../java/org/eclipse/jetty/util/MemoryUtils.java   |   71 +
 .../org/eclipse/jetty/util/MultiException.java     |  103 +-
 .../main/java/org/eclipse/jetty/util/MultiMap.java |  440 +-
 .../eclipse/jetty/util/MultiPartInputStream.java   |  851 ---
 .../jetty/util/MultiPartInputStreamParser.java     |  827 +++
 .../eclipse/jetty/util/MultiPartOutputStream.java  |   50 +-
 .../org/eclipse/jetty/util/MultiPartWriter.java    |   24 +-
 .../main/java/org/eclipse/jetty/util/Promise.java  |   65 +
 .../eclipse/jetty/util/QuotedStringTokenizer.java  |   88 +-
 .../eclipse/jetty/util/ReadLineInputStream.java    |    7 +-
 .../main/java/org/eclipse/jetty/util/Scanner.java  |   50 +-
 .../eclipse/jetty/util/SharedBlockingCallback.java |  302 ++
 .../eclipse/jetty/util/SocketAddressResolver.java  |  176 +
 .../java/org/eclipse/jetty/util/StringMap.java     |  695 ---
 .../java/org/eclipse/jetty/util/StringUtil.java    |  355 +-
 .../main/java/org/eclipse/jetty/util/TreeTrie.java |  358 ++
 .../src/main/java/org/eclipse/jetty/util/Trie.java |  124 +
 .../main/java/org/eclipse/jetty/util/TypeUtil.java |  260 +-
 .../main/java/org/eclipse/jetty/util/URIUtil.java  |  186 +-
 .../main/java/org/eclipse/jetty/util/Uptime.java   |  132 +
 .../java/org/eclipse/jetty/util/UrlEncoded.java    |  554 +-
 .../org/eclipse/jetty/util/Utf8Appendable.java     |   18 +
 .../org/eclipse/jetty/util/Utf8LineParser.java     |   99 +
 .../java/org/eclipse/jetty/util/ajax/JSON.java     | 1640 ------
 .../eclipse/jetty/util/ajax/JSONDateConvertor.java |  107 -
 .../jetty/util/annotation/ManagedAttribute.java    |   82 +
 .../jetty/util/annotation/ManagedObject.java       |   45 +
 .../jetty/util/annotation/ManagedOperation.java    |   60 +
 .../org/eclipse/jetty/util/annotation/Name.java    |   49 +
 .../jetty/util/annotation/package-info.java        |   23 +
 .../jetty/util/component/AbstractLifeCycle.java    |   79 +-
 .../jetty/util/component/AggregateLifeCycle.java   |  441 --
 .../eclipse/jetty/util/component/Container.java    |  315 +-
 .../jetty/util/component/ContainerLifeCycle.java   |  813 +++
 .../org/eclipse/jetty/util/component/Dumpable.java |    6 +
 .../jetty/util/component/FileDestroyable.java      |   14 +-
 .../component/FileNoticeLifeCycleListener.java     |    7 +-
 .../org/eclipse/jetty/util/component/Graceful.java |   29 +
 .../eclipse/jetty/util/component/LifeCycle.java    |    6 +
 .../eclipse/jetty/util/component/package-info.java |   23 +
 .../org/eclipse/jetty/util/log/AbstractLogger.java |    9 +
 .../org/eclipse/jetty/util/log/JavaUtilLog.java    |   15 +-
 .../main/java/org/eclipse/jetty/util/log/Log.java  |  293 +-
 .../java/org/eclipse/jetty/util/log/Logger.java    |    9 +
 .../java/org/eclipse/jetty/util/log/LoggerLog.java |   16 +
 .../java/org/eclipse/jetty/util/log/Slf4jLog.java  |    6 +
 .../eclipse/jetty/util/log/StacklessLogging.java   |   69 +
 .../java/org/eclipse/jetty/util/log/StdErrLog.java |  240 +-
 .../org/eclipse/jetty/util/log/package-info.java   |   23 +
 .../java/org/eclipse/jetty/util/package-info.java  |   23 +
 .../jetty/util/preventers/AWTLeakPreventer.java    |    3 +-
 .../util/preventers/AppContextLeakPreventer.java   |    3 +-
 .../preventers/DriverManagerLeakPreventer.java     |    3 +-
 .../util/preventers/GCThreadLeakPreventer.java     |    2 +-
 .../jetty/util/preventers/package-info.java        |   23 +
 .../eclipse/jetty/util/resource/BadResource.java   |    9 -
 .../eclipse/jetty/util/resource/EmptyResource.java |  130 +
 .../eclipse/jetty/util/resource/FileResource.java  |  283 +-
 .../jetty/util/resource/JarFileResource.java       |   61 +-
 .../eclipse/jetty/util/resource/JarResource.java   |  193 +-
 .../eclipse/jetty/util/resource/PathResource.java  |  354 ++
 .../org/eclipse/jetty/util/resource/Resource.java  |  137 +-
 .../jetty/util/resource/ResourceCollection.java    |   49 +-
 .../eclipse/jetty/util/resource/URLResource.java   |   55 +-
 .../eclipse/jetty/util/resource/package-info.java  |   23 +
 .../org/eclipse/jetty/util/security/B64Code.java   |   33 -
 .../eclipse/jetty/util/security/Constraint.java    |   30 +-
 .../eclipse/jetty/util/security/Credential.java    |   16 +-
 .../org/eclipse/jetty/util/security/Password.java  |   74 +-
 .../eclipse/jetty/util/security/package-info.java  |   23 +
 .../util/ssl/AliasedX509ExtendedKeyManager.java    |    2 +-
 .../jetty/util/ssl/AliasedX509KeyManager.java      |    2 +-
 .../eclipse/jetty/util/ssl/SslContextFactory.java  |  584 +--
 .../org/eclipse/jetty/util/ssl/package-info.java   |   23 +
 .../jetty/util/statistic/CounterStatistic.java     |   31 +-
 .../jetty/util/statistic/SampleStatistic.java      |    7 +
 .../eclipse/jetty/util/statistic/package-info.java |   23 +
 .../jetty/util/thread/ExecutorThreadPool.java      |    8 +
 .../jetty/util/thread/NonBlockingThread.java       |   59 +
 .../jetty/util/thread/QueuedThreadPool.java        |  593 ++-
 .../util/thread/ScheduledExecutorScheduler.java    |  141 +
 .../org/eclipse/jetty/util/thread/Scheduler.java   |   33 +
 .../eclipse/jetty/util/thread/ShutdownThread.java  |    8 +-
 .../org/eclipse/jetty/util/thread/Sweeper.java     |  194 +
 .../org/eclipse/jetty/util/thread/ThreadPool.java  |   16 +-
 .../org/eclipse/jetty/util/thread/Timeout.java     |  380 --
 .../eclipse/jetty/util/thread/TimerScheduler.java  |  129 +
 .../eclipse/jetty/util/thread/package-info.java    |   23 +
 .../org/eclipse/jetty/util/ArrayQueueTest.java     |   26 +-
 .../java/org/eclipse/jetty/util/B64CodeTest.java   |   14 +-
 .../eclipse/jetty/util/BlockingArrayQueueTest.java |  342 +-
 .../eclipse/jetty/util/BlockingCallbackTest.java   |  123 +
 .../org/eclipse/jetty/util/BufferUtilTest.java     |  338 ++
 .../org/eclipse/jetty/util/CollectionAssert.java   |  137 +
 .../jetty/util/ConcurrentArrayQueueTest.java       |  172 +
 .../java/org/eclipse/jetty/util/DateCacheTest.java |   92 +-
 .../org/eclipse/jetty/util/FutureCallbackTest.java |  212 +
 .../org/eclipse/jetty/util/IPAddressMapTest.java   |   62 +-
 .../eclipse/jetty/util/IteratingCallbackTest.java  |  300 ++
 .../java/org/eclipse/jetty/util/LazyListTest.java  |  384 +-
 .../org/eclipse/jetty/util/LeakDetectorTest.java   |   92 +
 .../org/eclipse/jetty/util/MultiExceptionTest.java |   37 +-
 .../java/org/eclipse/jetty/util/MultiMapTest.java  |  225 +-
 .../jetty/util/MultiPartInputStreamTest.java       |  285 +-
 .../org/eclipse/jetty/util/QueueBenchmarkTest.java |  222 +
 .../jetty/util/QuotedStringTokenizerTest.java      |   52 +-
 .../jetty/util/ReadLineInputStreamTest.java        |  247 +
 .../java/org/eclipse/jetty/util/ScannerTest.java   |   97 +-
 .../jetty/util/SharedBlockingCallbackTest.java     |  259 +
 .../java/org/eclipse/jetty/util/StringMapTest.java |  320 --
 .../org/eclipse/jetty/util/StringUtilTest.java     |   79 +-
 .../eclipse/jetty/util/TestIntrospectionUtil.java  |   52 +-
 .../test/java/org/eclipse/jetty/util/TrieTest.java |  245 +
 .../java/org/eclipse/jetty/util/TypeUtilTest.java  |   31 +-
 .../test/java/org/eclipse/jetty/util/URITest.java  |  244 -
 .../java/org/eclipse/jetty/util/URIUtilTest.java   |  274 +
 .../org/eclipse/jetty/util/URLEncodedTest.java     |  159 +-
 .../java/org/eclipse/jetty/util/UptimeTest.java    |   31 +
 .../org/eclipse/jetty/util/UrlEncodedUtf8Test.java |  165 +
 .../org/eclipse/jetty/util/Utf8LineParserTest.java |  191 +
 .../eclipse/jetty/util/Utf8StringBufferTest.java   |   11 +-
 .../util/Utf8StringBuilderInvalidUtfTest.java      |   71 +
 .../eclipse/jetty/util/Utf8StringBuilderTest.java  |   76 +-
 .../util/ajax/JSONPojoConvertorFactoryTest.java    |  418 --
 .../jetty/util/ajax/JSONPojoConvertorTest.java     |  441 --
 .../java/org/eclipse/jetty/util/ajax/JSONTest.java |  473 --
 .../util/component/AggregateLifeCycleTest.java     |  288 -
 .../util/component/ContainerLifeCycleTest.java     |  581 ++
 .../component/LifeCycleListenerNestedTest.java     |  280 +
 .../util/component/LifeCycleListenerTest.java      |   28 +-
 .../eclipse/jetty/util/log/JavaUtilLogTest.java    |   60 +-
 .../java/org/eclipse/jetty/util/log/LogTest.java   |    5 +-
 .../org/eclipse/jetty/util/log/StdErrCapture.java  |    2 +-
 .../org/eclipse/jetty/util/log/StdErrLogTest.java  |  224 +-
 .../util/resource/AbstractFSResourceTest.java      |  535 ++
 .../jetty/util/resource/FileResourceTest.java      |   61 +-
 .../jetty/util/resource/PathResourceTest.java      |   38 +
 .../jetty/util/resource/ResourceAliasTest.java     |  109 +
 .../util/resource/ResourceCollectionTest.java      |   41 +-
 .../eclipse/jetty/util/resource/ResourceTest.java  |  188 +-
 .../eclipse/jetty/util/security/PasswordTest.java  |   52 +
 .../jetty/util/ssl/SslContextFactoryTest.java      |   90 +-
 .../jetty/util/statistic/SampleStatisticTest.java  |   24 +-
 .../jetty/util/thread/QueuedThreadPoolTest.java    |  159 +-
 .../eclipse/jetty/util/thread/SchedulerTest.java   |  334 ++
 .../org/eclipse/jetty/util/thread/SweeperTest.java |  124 +
 .../org/eclipse/jetty/util/thread/TimeoutTest.java |  270 -
 .../src/test/resources/TestData/WindowsDir.zip     |  Bin 0 -> 2388 bytes
 .../resources/TestData/test/META-INF/MANIFEST.MF   |    0
 .../src/test/resources/TestData/test/alphabet      |    0
 .../src/test/resources/TestData/test/numbers       |    0
 .../test/resources/TestData/test/subdir/alphabet   |    0
 .../test/resources/TestData/test/subdir/numbers    |    0
 .../TestData/test/subdir/subsubdir/alphabet        |    0
 .../TestData/test/subdir/subsubdir/numbers         |    0
 .../src/test/resources/jetty-logging.properties    |    3 +-
 .../org/eclipse/jetty/util/resource/four/four      |    1 +
 .../org/eclipse/jetty/util/resource/four/four.txt  |    1 +
 .../org/eclipse/jetty/util/resource/resource.txt   |    1 +
 jetty-webapp/pom.xml                               |    8 +-
 jetty-webapp/src/main/config/etc/webdefault.xml    |  155 +-
 jetty-webapp/src/main/config/modules/webapp.mod    |   10 +
 .../org/eclipse/jetty/webapp/ClasspathPattern.java |   46 +-
 .../org/eclipse/jetty/webapp/Configuration.java    |  112 +-
 .../java/org/eclipse/jetty/webapp/Descriptor.java  |   13 +-
 .../jetty/webapp/FragmentConfiguration.java        |   25 +-
 .../jetty/webapp/JettyWebXmlConfiguration.java     |   50 +-
 .../java/org/eclipse/jetty/webapp/MetaData.java    |  410 +-
 .../eclipse/jetty/webapp/MetaInfConfiguration.java |  340 +-
 .../jetty/webapp/StandardDescriptorProcessor.java  |  807 +--
 .../eclipse/jetty/webapp/TagLibConfiguration.java  |  530 --
 .../eclipse/jetty/webapp/WebAppClassLoader.java    |  131 +-
 .../org/eclipse/jetty/webapp/WebAppContext.java    |  437 +-
 .../org/eclipse/jetty/webapp/WebDescriptor.java    |  265 +-
 .../eclipse/jetty/webapp/WebInfConfiguration.java  |  532 +-
 .../eclipse/jetty/webapp/WebXmlConfiguration.java  |    7 +-
 .../org/eclipse/jetty/webapp/package-info.java     |   23 +
 .../webapp/jmx/WebAppContext-mbean.properties      |   14 -
 .../org/eclipse/jetty/webapp/OrderingTest.java     |  263 +-
 .../jetty/webapp/WebAppClassLoaderTest.java        |   90 +-
 .../eclipse/jetty/webapp/WebAppContextTest.java    |   60 +-
 jetty-websocket/{README.txt => README.TXT}         |    0
 .../javax-websocket-client-impl/pom.xml            |  100 +
 .../jetty/websocket/jsr356/AbstractJsrRemote.java  |  195 +
 .../websocket/jsr356/BasicEndpointConfig.java      |   63 +
 .../jetty/websocket/jsr356/ClientContainer.java    |  390 ++
 .../jetty/websocket/jsr356/Configurable.java       |   29 +
 .../websocket/jsr356/ConfigurationException.java   |   36 +
 .../jetty/websocket/jsr356/DecoderFactory.java     |  183 +
 .../jetty/websocket/jsr356/EncoderFactory.java     |  177 +
 .../jetty/websocket/jsr356/InitException.java      |   42 +
 .../jsr356/JettyClientContainerProvider.java       |   43 +
 .../jetty/websocket/jsr356/JsrAsyncRemote.java     |  197 +
 .../jetty/websocket/jsr356/JsrBasicRemote.java     |  121 +
 .../jetty/websocket/jsr356/JsrExtension.java       |  112 +
 .../jetty/websocket/jsr356/JsrExtensionConfig.java |   35 +
 .../websocket/jsr356/JsrHandshakeResponse.java     |   42 +
 .../jetty/websocket/jsr356/JsrPongMessage.java     |   39 +
 .../eclipse/jetty/websocket/jsr356/JsrSession.java |  383 ++
 .../jetty/websocket/jsr356/JsrSessionFactory.java  |   59 +
 .../jetty/websocket/jsr356/JsrUpgradeListener.java |   79 +
 .../websocket/jsr356/MessageHandlerFactory.java    |  100 +
 .../websocket/jsr356/MessageHandlerWrapper.java    |   82 +
 .../jetty/websocket/jsr356/MessageType.java        |   31 +
 .../annotations/AnnotatedEndpointMetadata.java     |  134 +
 .../annotations/AnnotatedEndpointScanner.java      |  213 +
 .../websocket/jsr356/annotations/IJsrMethod.java   |   83 +
 .../websocket/jsr356/annotations/IJsrParamId.java  |   43 +
 .../websocket/jsr356/annotations/JsrCallable.java  |  175 +
 .../websocket/jsr356/annotations/JsrEvents.java    |  302 ++
 .../jsr356/annotations/JsrParamIdBase.java         |   51 +
 .../jsr356/annotations/JsrParamIdBinary.java       |   71 +
 .../jsr356/annotations/JsrParamIdDecoder.java      |   75 +
 .../jsr356/annotations/JsrParamIdOnClose.java      |   49 +
 .../jsr356/annotations/JsrParamIdOnError.java      |   47 +
 .../jsr356/annotations/JsrParamIdOnMessage.java    |   40 +
 .../jsr356/annotations/JsrParamIdOnOpen.java       |   49 +
 .../jsr356/annotations/JsrParamIdPong.java         |   49 +
 .../jsr356/annotations/JsrParamIdText.java         |  158 +
 .../jsr356/annotations/OnCloseCallable.java        |   89 +
 .../jsr356/annotations/OnErrorCallable.java        |   74 +
 .../annotations/OnMessageBinaryCallable.java       |   73 +
 .../annotations/OnMessageBinaryStreamCallable.java |   73 +
 .../jsr356/annotations/OnMessageCallable.java      |  175 +
 .../jsr356/annotations/OnMessagePongCallable.java  |   59 +
 .../jsr356/annotations/OnMessageTextCallable.java  |   72 +
 .../annotations/OnMessageTextStreamCallable.java   |   72 +
 .../jsr356/annotations/OnOpenCallable.java         |   69 +
 .../jetty/websocket/jsr356/annotations/Param.java  |  135 +
 .../client/AnnotatedClientEndpointConfig.java      |  112 +
 .../client/AnnotatedClientEndpointMetadata.java    |   62 +
 .../jsr356/client/EmptyClientEndpointConfig.java   |   85 +
 .../websocket/jsr356/client/EmptyConfigurator.java |   42 +
 .../jsr356/client/JsrClientEndpointImpl.java       |  102 +
 .../jsr356/client/SimpleEndpointMetadata.java      |   72 +
 .../websocket/jsr356/decoders/AbstractDecoder.java |   35 +
 .../websocket/jsr356/decoders/BooleanDecoder.java  |   49 +
 .../jsr356/decoders/ByteArrayDecoder.java          |   43 +
 .../jsr356/decoders/ByteBufferDecoder.java         |   41 +
 .../websocket/jsr356/decoders/ByteDecoder.java     |   62 +
 .../jsr356/decoders/CharacterDecoder.java          |   52 +
 .../websocket/jsr356/decoders/DoubleDecoder.java   |   62 +
 .../websocket/jsr356/decoders/FloatDecoder.java    |   66 +
 .../jsr356/decoders/InputStreamDecoder.java        |   45 +
 .../websocket/jsr356/decoders/IntegerDecoder.java  |   63 +
 .../websocket/jsr356/decoders/LongDecoder.java     |   61 +
 .../jsr356/decoders/PongMessageDecoder.java        |   61 +
 .../decoders/PrimitiveDecoderMetadataSet.java      |   76 +
 .../websocket/jsr356/decoders/ReaderDecoder.java   |   45 +
 .../websocket/jsr356/decoders/ShortDecoder.java    |   62 +
 .../websocket/jsr356/decoders/StringDecoder.java   |   43 +
 .../websocket/jsr356/encoders/AbstractEncoder.java |   35 +
 .../websocket/jsr356/encoders/BooleanEncoder.java  |   38 +
 .../jsr356/encoders/ByteArrayEncoder.java          |   46 +
 .../jsr356/encoders/ByteBufferEncoder.java         |   46 +
 .../websocket/jsr356/encoders/ByteEncoder.java     |   38 +
 .../jsr356/encoders/CharacterEncoder.java          |   38 +
 .../jsr356/encoders/DefaultBinaryEncoder.java      |   33 +
 .../encoders/DefaultBinaryStreamEncoder.java       |   37 +
 .../jsr356/encoders/DefaultTextEncoder.java        |   31 +
 .../jsr356/encoders/DefaultTextStreamEncoder.java  |   35 +
 .../websocket/jsr356/encoders/DoubleEncoder.java   |   38 +
 .../jsr356/encoders/EncodeFailedFuture.java        |   71 +
 .../websocket/jsr356/encoders/FloatEncoder.java    |   37 +
 .../websocket/jsr356/encoders/IntegerEncoder.java  |   38 +
 .../websocket/jsr356/encoders/LongEncoder.java     |   38 +
 .../encoders/PrimitiveEncoderMetadataSet.java      |   62 +
 .../websocket/jsr356/encoders/ShortEncoder.java    |   38 +
 .../websocket/jsr356/encoders/StringEncoder.java   |   34 +
 .../jsr356/endpoints/AbstractJsrEventDriver.java   |  114 +
 .../jsr356/endpoints/EndpointInstance.java         |   58 +
 .../jsr356/endpoints/JsrAnnotatedEventDriver.java  |  396 ++
 .../jsr356/endpoints/JsrEndpointEventDriver.java   |  283 +
 .../jsr356/endpoints/JsrEndpointImpl.java          |   57 +
 .../jsr356/endpoints/JsrEventDriverFactory.java    |   52 +
 .../jsr356/messages/BinaryPartialMessage.java      |   80 +
 .../jsr356/messages/BinaryPartialOnMessage.java    |   64 +
 .../jsr356/messages/BinaryWholeMessage.java        |   67 +
 .../jsr356/messages/SendHandlerWriteCallback.java  |   46 +
 .../jsr356/messages/TextPartialMessage.java        |   59 +
 .../jsr356/messages/TextPartialOnMessage.java      |   65 +
 .../jsr356/messages/TextWholeMessage.java          |   63 +
 .../websocket/jsr356/metadata/CoderMetadata.java   |   67 +
 .../jsr356/metadata/CoderMetadataSet.java          |  255 +
 .../websocket/jsr356/metadata/DecoderMetadata.java |   34 +
 .../jsr356/metadata/DecoderMetadataSet.java        |   92 +
 .../jsr356/metadata/DuplicateCoderException.java   |   39 +
 .../websocket/jsr356/metadata/EncoderMetadata.java |   34 +
 .../jsr356/metadata/EncoderMetadataSet.java        |   92 +
 .../jsr356/metadata/EndpointMetadata.java          |   28 +
 .../jsr356/metadata/MessageHandlerMetadata.java    |   71 +
 .../jetty/websocket/jsr356/utils/Primitives.java   |   77 +
 .../services/javax.websocket.ContainerProvider     |    1 +
 .../websocket/jsr356/AnnotatedEchoClient.java      |   60 +
 .../jetty/websocket/jsr356/AnnotatedEchoTest.java  |   89 +
 .../websocket/jsr356/AnnotatedEndpointClient.java  |   60 +
 .../jsr356/AnnotatedEndpointConfigTest.java        |  189 +
 .../jsr356/AnnotatedEndpointConfigurator.java      |   34 +
 .../jetty/websocket/jsr356/ConfiguratorTest.java   |  133 +
 .../jetty/websocket/jsr356/CookiesTest.java        |  179 +
 .../jetty/websocket/jsr356/DecoderFactoryTest.java |  107 +
 .../jsr356/DecoderReaderManySmallTest.java         |  219 +
 .../jetty/websocket/jsr356/DecoderReaderTest.java  |  292 ++
 .../jetty/websocket/jsr356/EchoCaptureHandler.java |   32 +
 .../jetty/websocket/jsr356/EchoHandler.java        |   42 +
 .../jetty/websocket/jsr356/EncoderFactoryTest.java |   86 +
 .../jetty/websocket/jsr356/EncoderTest.java        |  316 ++
 .../jetty/websocket/jsr356/EndpointEchoClient.java |   66 +
 .../jetty/websocket/jsr356/EndpointEchoTest.java   |  147 +
 .../jetty/websocket/jsr356/JettyEchoSocket.java    |   75 +
 .../jetty/websocket/jsr356/JsrSessionTest.java     |  115 +
 .../jsr356/MessageHandlerFactoryTest.java          |   76 +
 .../jetty/websocket/jsr356/MessageQueue.java       |   59 +
 .../jsr356/annotations/DateTextSocket.java         |   57 +
 .../jsr356/annotations/JsrParamIdDecoderTest.java  |   57 +
 .../websocket/jsr356/decoders/BadDualDecoder.java  |  122 +
 .../websocket/jsr356/decoders/DateDecoder.java     |   62 +
 .../websocket/jsr356/decoders/DateTimeDecoder.java |   62 +
 .../jsr356/decoders/IntegerDecoderTest.java        |   37 +
 .../decoders/PrimitiveDecoderMetadataSetTest.java  |   54 +
 .../websocket/jsr356/decoders/TimeDecoder.java     |   62 +
 .../jsr356/decoders/ValidDualDecoder.java          |   65 +
 .../jetty/websocket/jsr356/demo/ExampleClient.java |   99 +
 .../jetty/websocket/jsr356/demo/ExampleSocket.java |   71 +
 .../websocket/jsr356/encoders/BadDualEncoder.java  |   54 +
 .../websocket/jsr356/encoders/DateEncoder.java     |   48 +
 .../websocket/jsr356/encoders/DateTimeEncoder.java |   48 +
 .../websocket/jsr356/encoders/DualEncoder.java     |   58 +
 .../websocket/jsr356/encoders/TimeEncoder.java     |   48 +
 .../jsr356/encoders/ValidDualEncoder.java          |   64 +
 ...nnotatedEndpointScanner_GoodSignaturesTest.java |  169 +
 ...tatedEndpointScanner_InvalidSignaturesTest.java |  113 +
 .../websocket/jsr356/endpoints/OnCloseTest.java    |  126 +
 .../websocket/jsr356/endpoints/TrackingSocket.java |  127 +
 .../BasicBinaryMessageByteBufferSocket.java        |   37 +
 .../endpoints/samples/BasicErrorSessionSocket.java |   35 +
 .../samples/BasicErrorSessionThrowableSocket.java  |   36 +
 .../jsr356/endpoints/samples/BasicErrorSocket.java |   34 +
 .../samples/BasicErrorThrowableSessionSocket.java  |   36 +
 .../samples/BasicErrorThrowableSocket.java         |   35 +
 .../endpoints/samples/BasicInputStreamSocket.java  |   47 +
 .../BasicInputStreamWithThrowableSocket.java       |   39 +
 .../samples/BasicOpenCloseSessionSocket.java       |   46 +
 .../endpoints/samples/BasicOpenCloseSocket.java    |   41 +
 .../endpoints/samples/BasicOpenSessionSocket.java  |   35 +
 .../jsr356/endpoints/samples/BasicOpenSocket.java  |   34 +
 .../endpoints/samples/BasicPongMessageSocket.java  |   36 +
 .../samples/BasicTextMessageStringSocket.java      |   35 +
 .../endpoints/samples/InvalidCloseIntSocket.java   |   37 +
 .../endpoints/samples/InvalidErrorErrorSocket.java |   37 +
 .../samples/InvalidErrorExceptionSocket.java       |   37 +
 .../endpoints/samples/InvalidErrorIntSocket.java   |   37 +
 .../samples/InvalidOpenCloseReasonSocket.java      |   38 +
 .../endpoints/samples/InvalidOpenIntSocket.java    |   37 +
 .../samples/InvalidOpenSessionIntSocket.java       |   38 +
 .../samples/close/CloseEndpointConfigSocket.java   |   36 +
 .../samples/close/CloseReasonSessionSocket.java    |   37 +
 .../endpoints/samples/close/CloseReasonSocket.java |   36 +
 .../samples/close/CloseSessionReasonSocket.java    |   37 +
 .../samples/close/CloseSessionSocket.java          |   36 +
 .../endpoints/samples/close/CloseSocket.java       |   35 +
 .../jsr356/handlers/BaseMessageHandler.java        |   30 +
 .../jsr356/handlers/ByteArrayPartialHandler.java   |   30 +
 .../jsr356/handlers/ByteArrayWholeHandler.java     |   30 +
 .../jsr356/handlers/ByteBufferPartialHandler.java  |   32 +
 .../jsr356/handlers/ByteBufferWholeHandler.java    |   32 +
 .../jsr356/handlers/ComboMessageHandler.java       |   41 +
 .../jsr356/handlers/ExtendedMessageHandler.java    |   32 +
 .../jsr356/handlers/InputStreamWholeHandler.java   |   32 +
 .../jsr356/handlers/LongMessageHandler.java        |   29 +
 .../jsr356/handlers/ReaderWholeHandler.java        |   32 +
 .../jsr356/handlers/StringPartialHandler.java      |   30 +
 .../jsr356/handlers/StringWholeHandler.java        |   30 +
 .../jsr356/metadata/DecoderMetadataSetTest.java    |  134 +
 .../jsr356/metadata/EncoderMetadataSetTest.java    |  134 +
 .../jsr356/misbehaving/AnnotatedRuntimeOnOpen.java |   65 +
 .../jsr356/misbehaving/EndpointRuntimeOnOpen.java  |   63 +
 .../jsr356/misbehaving/MisbehavingClassTest.java   |  127 +
 .../jsr356/samples/AbstractStringEndpoint.java     |   60 +
 .../websocket/jsr356/samples/DummyConnection.java  |  156 +
 .../websocket/jsr356/samples/DummyEndpoint.java    |   32 +
 .../jsr356/samples/EchoStringEndpoint.java         |   36 +
 .../jetty/websocket/jsr356/samples/ExtDecoder.java |   29 +
 .../jetty/websocket/jsr356/samples/Fruit.java      |   25 +
 .../jsr356/samples/FruitBinaryEncoder.java         |   68 +
 .../websocket/jsr356/samples/FruitDecoder.java     |   82 +
 .../websocket/jsr356/samples/FruitTextEncoder.java |   42 +
 .../jetty/websocket/jsr356/samples/IntSocket.java  |   46 +
 .../websocket/jsr356/utils/ReflectUtilsTest.java   |  138 +
 .../jetty/websocket/jsr356/utils/TypeTree.java     |  120 +
 .../src/test/resources/jetty-logging.properties    |    5 +
 .../src/test/resources/quotes-ben.txt              |    4 +
 .../src/test/resources/quotes-twain.txt            |    5 +
 .../javax-websocket-server-impl/pom.xml            |   92 +
 .../src/main/config/modules/websocket.mod          |   12 +
 .../server/AnnotatedServerEndpointConfig.java      |  209 +
 .../server/AnnotatedServerEndpointMetadata.java    |  108 +
 .../jsr356/server/BasicServerEndpointConfig.java   |  125 +
 .../server/BasicServerEndpointConfigurator.java    |  108 +
 .../jetty/websocket/jsr356/server/JsrCreator.java  |  162 +
 .../jsr356/server/JsrHandshakeRequest.java         |   86 +
 .../jsr356/server/JsrHandshakeResponse.java        |   42 +
 .../websocket/jsr356/server/JsrPathParamId.java    |   49 +
 .../jsr356/server/JsrServerEndpointImpl.java       |  110 +
 .../server/JsrServerExtendsEndpointImpl.java       |   71 +
 .../server/PathParamServerEndpointConfig.java      |   51 +
 .../websocket/jsr356/server/ServerContainer.java   |  190 +
 .../jsr356/server/ServerEndpointMetadata.java      |   30 +
 .../server/SimpleServerEndpointMetadata.java       |   61 +
 .../WebSocketServerContainerInitializer.java       |  290 +
 .../jsr356/server/pathmap/WebSocketPathSpec.java   |  344 ++
 .../javax.servlet.ServletContainerInitializer      |    1 +
 ...socket.server.ServerEndpointConfig$Configurator |    1 +
 .../java/examples/GetHttpSessionConfigurator.java  |   34 +
 .../test/java/examples/GetHttpSessionSocket.java   |   47 +
 .../test/java/examples/MyAuthedConfigurator.java   |   48 +
 .../src/test/java/examples/MyAuthedSocket.java     |   33 +
 .../test/java/examples/StreamingEchoSocket.java    |   46 +
 .../jsr356/server/AnnotatedServerEndpointTest.java |  142 +
 .../websocket/jsr356/server/BasicEndpointTest.java |   89 +
 .../websocket/jsr356/server/BinaryStreamTest.java  |  177 +
 .../websocket/jsr356/server/ConfiguratorTest.java  |  508 ++
 .../websocket/jsr356/server/DummyConnection.java   |  162 +
 .../websocket/jsr356/server/DummyCreator.java      |   39 +
 .../jetty/websocket/jsr356/server/EchoCase.java    |  181 +
 .../websocket/jsr356/server/EchoClientSocket.java  |  123 +
 .../jetty/websocket/jsr356/server/EchoTest.java    |  302 ++
 .../server/ExtensionStackProcessingTest.java       |  184 +
 .../websocket/jsr356/server/IdleTimeoutTest.java   |  140 +
 .../websocket/jsr356/server/JettyEchoSocket.java   |  137 +
 .../JettyServerEndpointConfiguratorTest.java       |   53 +
 .../websocket/jsr356/server/JsrBatchModeTest.java  |  181 +
 .../jsr356/server/LargeAnnotatedTest.java          |   92 +
 .../jsr356/server/LargeContainerTest.java          |   92 +
 .../websocket/jsr356/server/MemoryUsageTest.java   |  134 +
 .../jsr356/server/OnMessageReturnTest.java         |   82 +
 .../websocket/jsr356/server/OnPartialTest.java     |  111 +
 .../websocket/jsr356/server/PingPongTest.java      |  199 +
 ...nnotatedEndpointScanner_GoodSignaturesTest.java |  202 +
 ...tatedEndpointScanner_InvalidSignaturesTest.java |  111 +
 .../websocket/jsr356/server/SessionAltConfig.java  |   55 +
 .../jsr356/server/SessionInfoEndpoint.java         |  102 +
 .../websocket/jsr356/server/SessionInfoSocket.java |   83 +
 .../jetty/websocket/jsr356/server/SessionTest.java |  244 +
 .../jetty/websocket/jsr356/server/StreamTest.java  |  348 ++
 .../websocket/jsr356/server/TextStreamTest.java    |  179 +
 .../websocket/jsr356/server/TrackingSocket.java    |  127 +
 .../jetty/websocket/jsr356/server/WSServer.java    |  199 +
 .../server/browser/JsrBrowserConfigurator.java     |   52 +
 .../jsr356/server/browser/JsrBrowserDebugTool.java |   98 +
 .../jsr356/server/browser/JsrBrowserSocket.java    |  228 +
 .../jsr356/server/pathmap/PathMappingsTest.java    |  110 +
 .../pathmap/WebSocketPathSpecBadSpecsTest.java     |   87 +
 .../server/pathmap/WebSocketPathSpecTest.java      |  286 +
 .../BasicBinaryMessageByteBufferSocket.java        |   37 +
 .../samples/BasicCloseReasonSessionSocket.java     |   37 +
 .../server/samples/BasicCloseReasonSocket.java     |   36 +
 .../samples/BasicCloseSessionReasonSocket.java     |   37 +
 .../jsr356/server/samples/BasicCloseSocket.java    |   35 +
 .../server/samples/BasicErrorSessionSocket.java    |   35 +
 .../samples/BasicErrorSessionThrowableSocket.java  |   36 +
 .../jsr356/server/samples/BasicErrorSocket.java    |   34 +
 .../samples/BasicErrorThrowableSessionSocket.java  |   36 +
 .../server/samples/BasicErrorThrowableSocket.java  |   35 +
 .../samples/BasicOpenCloseSessionSocket.java       |   46 +
 .../server/samples/BasicOpenCloseSocket.java       |   41 +
 .../server/samples/BasicOpenSessionSocket.java     |   35 +
 .../jsr356/server/samples/BasicOpenSocket.java     |   34 +
 .../server/samples/BasicPongMessageSocket.java     |   36 +
 .../samples/BasicTextMessageStringSocket.java      |   35 +
 .../server/samples/InvalidCloseIntSocket.java      |   37 +
 .../server/samples/InvalidErrorErrorSocket.java    |   37 +
 .../samples/InvalidErrorExceptionSocket.java       |   37 +
 .../server/samples/InvalidErrorIntSocket.java      |   37 +
 .../samples/InvalidOpenCloseReasonSocket.java      |   38 +
 .../server/samples/InvalidOpenIntSocket.java       |   37 +
 .../samples/InvalidOpenSessionIntSocket.java       |   38 +
 .../samples/StatelessTextMessageStringSocket.java  |   36 +
 .../jsr356/server/samples/beans/DateDecoder.java   |   62 +
 .../jsr356/server/samples/beans/DateEncoder.java   |   48 +
 .../server/samples/beans/DateTextSocket.java       |   70 +
 .../server/samples/beans/DateTimeDecoder.java      |   62 +
 .../server/samples/beans/DateTimeEncoder.java      |   48 +
 .../jsr356/server/samples/beans/TimeDecoder.java   |   62 +
 .../jsr356/server/samples/beans/TimeEncoder.java   |   48 +
 .../server/samples/binary/ByteBufferSocket.java    |   51 +
 .../server/samples/echo/BasicEchoEndpoint.java     |   46 +
 .../BasicEchoEndpointConfigContextListener.java    |   57 +
 .../echo/BasicEchoEndpointContextListener.java     |   57 +
 .../server/samples/echo/BasicEchoSocket.java       |   37 +
 .../echo/BasicEchoSocketConfigContextListener.java |   55 +
 .../echo/BasicEchoSocketContextListener.java       |   50 +
 .../server/samples/echo/ConfiguredEchoSocket.java  |  131 +
 .../server/samples/echo/EchoReturnEndpoint.java    |   64 +
 .../samples/echo/EchoSocketConfigurator.java       |   35 +
 .../samples/echo/LargeEchoConfiguredSocket.java    |   47 +
 .../samples/echo/LargeEchoContextListener.java     |   42 +
 .../samples/echo/LargeEchoDefaultSocket.java       |   37 +
 .../idletimeout/IdleTimeoutContextListener.java    |   54 +
 .../idletimeout/OnOpenIdleTimeoutEndpoint.java     |   44 +
 .../idletimeout/OnOpenIdleTimeoutSocket.java       |   40 +
 .../samples/partial/PartialTextSessionSocket.java  |   55 +
 .../server/samples/partial/PartialTextSocket.java  |   63 +
 .../samples/partial/PartialTrackingSocket.java     |   36 +
 .../server/samples/pong/PongContextListener.java   |   66 +
 .../server/samples/pong/PongMessageEndpoint.java   |   51 +
 .../jsr356/server/samples/pong/PongSocket.java     |   56 +
 .../primitives/BooleanObjectTextParamSocket.java   |   67 +
 .../primitives/BooleanObjectTextSocket.java        |   66 +
 .../samples/primitives/BooleanTextParamSocket.java |   60 +
 .../samples/primitives/BooleanTextSocket.java      |   59 +
 .../samples/primitives/ByteObjectTextSocket.java   |   66 +
 .../server/samples/primitives/ByteTextSocket.java  |   59 +
 .../server/samples/primitives/CharTextSocket.java  |   59 +
 .../primitives/CharacterObjectTextSocket.java      |   66 +
 .../samples/primitives/DoubleObjectTextSocket.java |   67 +
 .../samples/primitives/DoubleTextSocket.java       |   60 +
 .../samples/primitives/FloatObjectTextSocket.java  |   67 +
 .../server/samples/primitives/FloatTextSocket.java |   60 +
 .../samples/primitives/IntParamTextSocket.java     |   60 +
 .../server/samples/primitives/IntTextSocket.java   |   59 +
 .../primitives/IntegerObjectParamTextSocket.java   |   67 +
 .../primitives/IntegerObjectTextSocket.java        |   66 +
 .../samples/primitives/LongObjectTextSocket.java   |   66 +
 .../server/samples/primitives/LongTextSocket.java  |   59 +
 .../samples/primitives/ShortObjectTextSocket.java  |   66 +
 .../server/samples/primitives/ShortTextSocket.java |   59 +
 .../samples/streaming/InputStreamSocket.java       |   52 +
 .../samples/streaming/ReaderParamSocket.java       |   65 +
 .../server/samples/streaming/ReaderSocket.java     |   51 +
 .../streaming/StringReturnReaderParamSocket.java   |   56 +
 .../resources/basic-echo-endpoint-config-web.xml   |   12 +
 .../src/test/resources/data/larger.png             |  Bin 0 -> 134654 bytes
 .../src/test/resources/data/larger.png.sha         |    1 +
 .../src/test/resources/data/largest.jpg            |  Bin 0 -> 4443937 bytes
 .../src/test/resources/data/largest.jpg.sha        |    1 +
 .../src/test/resources/data/medium.png             |  Bin 0 -> 45443 bytes
 .../src/test/resources/data/medium.png.sha         |    1 +
 .../src/test/resources/data/small.png              |  Bin 0 -> 3142 bytes
 .../src/test/resources/data/small.png.sha          |    1 +
 .../src/test/resources/empty-web.xml               |    8 +
 .../src/test/resources/idle-timeout-config-web.xml |   12 +
 .../src/test/resources/jetty-logging.properties    |   11 +
 .../resources/jsr-browser-debug-tool/index.html    |   37 +
 .../test/resources/jsr-browser-debug-tool/main.css |   33 +
 .../resources/jsr-browser-debug-tool/websocket.js  |  139 +
 .../src/test/resources/large-echo-config-web.xml   |   12 +
 .../src/test/resources/pong-config-web.xml         |   12 +
 jetty-websocket/pom.xml                            |   99 +-
 .../eclipse/jetty/websocket/AbstractExtension.java |  149 -
 .../jetty/websocket/DeflateFrameExtension.java     |  164 -
 .../org/eclipse/jetty/websocket/Extension.java     |   31 -
 .../org/eclipse/jetty/websocket/FixedMaskGen.java  |   44 -
 .../eclipse/jetty/websocket/FragmentExtension.java |   80 -
 .../eclipse/jetty/websocket/IdentityExtension.java |   27 -
 .../java/org/eclipse/jetty/websocket/MaskGen.java  |   24 -
 .../org/eclipse/jetty/websocket/RandomMaskGen.java |   45 -
 .../org/eclipse/jetty/websocket/WebSocket.java     |  275 -
 .../eclipse/jetty/websocket/WebSocketBuffers.java  |   65 -
 .../eclipse/jetty/websocket/WebSocketClient.java   |  620 ---
 .../jetty/websocket/WebSocketClientFactory.java    |  596 ---
 .../jetty/websocket/WebSocketConnection.java       |   36 -
 .../jetty/websocket/WebSocketConnectionD00.java    |  514 --
 .../jetty/websocket/WebSocketConnectionD06.java    |  734 ---
 .../jetty/websocket/WebSocketConnectionD08.java    |  854 ---
 .../websocket/WebSocketConnectionRFC6455.java      |  995 ----
 .../eclipse/jetty/websocket/WebSocketFactory.java  |  465 --
 .../jetty/websocket/WebSocketGenerator.java        |   33 -
 .../jetty/websocket/WebSocketGeneratorD00.java     |  173 -
 .../jetty/websocket/WebSocketGeneratorD06.java     |  234 -
 .../jetty/websocket/WebSocketGeneratorD08.java     |  308 --
 .../jetty/websocket/WebSocketGeneratorRFC6455.java |  312 --
 .../eclipse/jetty/websocket/WebSocketHandler.java  |   57 -
 .../eclipse/jetty/websocket/WebSocketParser.java   |   53 -
 .../jetty/websocket/WebSocketParserD00.java        |  212 -
 .../jetty/websocket/WebSocketParserD06.java        |  310 --
 .../jetty/websocket/WebSocketParserD08.java        |  394 --
 .../jetty/websocket/WebSocketParserRFC6455.java    |  394 --
 .../eclipse/jetty/websocket/WebSocketServlet.java  |  128 -
 .../websocket/WebSocketServletConnection.java      |   28 -
 .../websocket/WebSocketServletConnectionD00.java   |  105 -
 .../websocket/WebSocketServletConnectionD06.java   |   63 -
 .../websocket/WebSocketServletConnectionD08.java   |   69 -
 .../WebSocketServletConnectionRFC6455.java         |   69 -
 .../org/eclipse/jetty/websocket/ZeroMaskGen.java   |   28 -
 .../jetty/websocket/SafariWebsocketDraft0Test.java |  129 -
 .../org/eclipse/jetty/websocket/TestClient.java    |  312 --
 .../org/eclipse/jetty/websocket/TestServer.java    |  448 --
 .../jetty/websocket/TomcatServerQuirksTest.java    |  125 -
 .../jetty/websocket/WebSocketClientTest.java       |  790 ---
 .../eclipse/jetty/websocket/WebSocketCommTest.java |  130 -
 .../jetty/websocket/WebSocketGeneratorD00Test.java |   94 -
 .../jetty/websocket/WebSocketGeneratorD06Test.java |  216 -
 .../jetty/websocket/WebSocketGeneratorD08Test.java |  215 -
 .../websocket/WebSocketGeneratorRFC6455Test.java   |  254 -
 .../jetty/websocket/WebSocketLoadD08Test.java      |  252 -
 .../jetty/websocket/WebSocketLoadRFC6455Test.java  |  247 -
 .../jetty/websocket/WebSocketMessageD00Test.java   |  816 ---
 .../jetty/websocket/WebSocketMessageD06Test.java   |  862 ---
 .../jetty/websocket/WebSocketMessageD08Test.java   | 1319 -----
 .../websocket/WebSocketMessageRFC6455Test.java     | 1665 ------
 .../jetty/websocket/WebSocketMinVersionTest.java   |  116 -
 .../jetty/websocket/WebSocketOverSSLTest.java      |  216 -
 .../jetty/websocket/WebSocketParserD00Test.java    |  159 -
 .../jetty/websocket/WebSocketParserD06Test.java    |  330 --
 .../jetty/websocket/WebSocketParserD08Test.java    |  370 --
 .../websocket/WebSocketParserRFC6455Test.java      |  409 --
 .../jetty/websocket/WebSocketRedeployTest.java     |  178 -
 .../jetty/websocket/WebSocketServletRFCTest.java   |  285 -
 .../eclipse/jetty/websocket/dummy/DummyServer.java |  309 --
 .../examples/EchoClientSocketExample.java          |  145 -
 .../examples/EchoServerSocketExample.java          |  140 -
 .../jetty/websocket/helper/CaptureSocket.java      |   57 -
 .../jetty/websocket/helper/MessageSender.java      |  104 -
 .../eclipse/jetty/websocket/helper/SafariD00.java  |  185 -
 .../websocket/helper/WebSocketCaptureServlet.java  |   49 -
 .../src/test/resources/jetty-logging.properties    |    4 -
 jetty-websocket/websocket-api/pom.xml              |   58 +
 .../jetty/websocket/api/BadPayloadException.java   |   44 +
 .../org/eclipse/jetty/websocket/api/BatchMode.java |   47 +
 .../jetty/websocket/api/CloseException.java        |   49 +
 .../eclipse/jetty/websocket/api/CloseStatus.java   |   81 +
 .../websocket/api/InvalidWebSocketException.java   |   43 +
 .../websocket/api/MessageTooLargeException.java    |   43 +
 .../websocket/api/PolicyViolationException.java    |   43 +
 .../jetty/websocket/api/ProtocolException.java     |   41 +
 .../jetty/websocket/api/RemoteEndpoint.java        |  137 +
 .../org/eclipse/jetty/websocket/api/Session.java   |  178 +
 .../eclipse/jetty/websocket/api/StatusCode.java    |  147 +
 .../eclipse/jetty/websocket/api/SuspendToken.java  |   30 +
 .../jetty/websocket/api/UpgradeException.java      |   62 +
 .../jetty/websocket/api/UpgradeRequest.java        |  362 ++
 .../jetty/websocket/api/UpgradeResponse.java       |  206 +
 .../jetty/websocket/api/WebSocketAdapter.java      |   82 +
 .../jetty/websocket/api/WebSocketBehavior.java     |   31 +
 .../jetty/websocket/api/WebSocketException.java    |   46 +
 .../jetty/websocket/api/WebSocketListener.java     |   80 +
 .../jetty/websocket/api/WebSocketPolicy.java       |  358 ++
 .../websocket/api/WebSocketTimeoutException.java   |   42 +
 .../eclipse/jetty/websocket/api/WriteCallback.java |   48 +
 .../api/annotations/OnWebSocketClose.java          |   44 +
 .../api/annotations/OnWebSocketConnect.java        |   43 +
 .../api/annotations/OnWebSocketError.java          |   45 +
 .../api/annotations/OnWebSocketFrame.java          |   44 +
 .../api/annotations/OnWebSocketMessage.java        |   58 +
 .../jetty/websocket/api/annotations/WebSocket.java |   43 +
 .../websocket/api/annotations/package-info.java    |   23 +
 .../jetty/websocket/api/extensions/Extension.java  |   89 +
 .../websocket/api/extensions/ExtensionConfig.java  |  250 +
 .../websocket/api/extensions/ExtensionFactory.java |   81 +
 .../jetty/websocket/api/extensions/Frame.java      |  118 +
 .../websocket/api/extensions/IncomingFrames.java   |   38 +
 .../websocket/api/extensions/OutgoingFrames.java   |   44 +
 .../websocket/api/extensions/package-info.java     |   23 +
 .../eclipse/jetty/websocket/api/package-info.java  |   23 +
 .../jetty/websocket/api/util/QuoteUtil.java        |  511 ++
 .../eclipse/jetty/websocket/api/util/WSURI.java    |  145 +
 .../jetty/websocket/api/util/package-info.java     |   23 +
 .../api/extensions/ExtensionConfigTest.java        |  124 +
 .../jetty/websocket/api/util/QuoteUtilTest.java    |  156 +
 .../websocket/api/util/QuoteUtil_QuoteTest.java    |   84 +
 .../jetty/websocket/api/util/WSURITest.java        |   88 +
 jetty-websocket/websocket-client/pom.xml           |  118 +
 .../websocket/client/ClientUpgradeRequest.java     |  273 +
 .../websocket/client/ClientUpgradeResponse.java    |   52 +
 .../jetty/websocket/client/WebSocketClient.java    |  584 +++
 .../jetty/websocket/client/io/ConnectPromise.java  |  107 +
 .../websocket/client/io/ConnectionManager.java     |  253 +
 .../websocket/client/io/UpgradeConnection.java     |  352 ++
 .../jetty/websocket/client/io/UpgradeListener.java |   32 +
 .../client/io/WebSocketClientConnection.java       |  116 +
 .../client/io/WebSocketClientSelectorManager.java  |  149 +
 .../jetty/websocket/client/io/package-info.java    |   23 +
 .../jetty/websocket/client/masks/FixedMasker.java  |   47 +
 .../jetty/websocket/client/masks/Masker.java       |   37 +
 .../jetty/websocket/client/masks/RandomMasker.java |   46 +
 .../jetty/websocket/client/masks/ZeroMasker.java   |   40 +
 .../jetty/websocket/client/masks/package-info.java |   23 +
 .../jetty/websocket/client/package-info.java       |   34 +
 .../src/test/java/examples/SimpleEchoClient.java   |   70 +
 .../src/test/java/examples/SimpleEchoSocket.java   |   87 +
 .../jetty/websocket/client/BadNetworkTest.java     |  131 +
 .../jetty/websocket/client/ClientCloseTest.java    |  635 +++
 .../jetty/websocket/client/ClientConnectTest.java  |  398 ++
 .../jetty/websocket/client/ClientWriteThread.java  |  107 +
 .../websocket/client/ConnectionManagerTest.java    |   82 +
 .../eclipse/jetty/websocket/client/CookieTest.java |  181 +
 .../websocket/client/JettyTrackingSocket.java      |  177 +
 .../jetty/websocket/client/MaxMessageSocket.java   |  102 +
 .../jetty/websocket/client/ServerReadThread.java   |  132 +
 .../jetty/websocket/client/ServerWriteThread.java  |   97 +
 .../jetty/websocket/client/SessionTest.java        |  111 +
 .../jetty/websocket/client/SlowClientTest.java     |  119 +
 .../jetty/websocket/client/SlowServerTest.java     |  160 +
 .../websocket/client/TomcatServerQuirksTest.java   |  128 +
 .../client/WebSocketClientBadUriTest.java          |  102 +
 .../websocket/client/WebSocketClientTest.java      |  377 ++
 .../websocket/client/examples/TestClient.java      |  312 ++
 .../src/test/resources/jetty-logging.properties    |   21 +
 jetty-websocket/websocket-common/pom.xml           |   82 +
 .../eclipse/jetty/websocket/common/AcceptHash.java |   61 +
 .../websocket/common/BlockingWriteCallback.java    |   86 +
 .../eclipse/jetty/websocket/common/CloseInfo.java  |  204 +
 .../jetty/websocket/common/ConnectionState.java    |   58 +
 .../eclipse/jetty/websocket/common/Generator.java  |  442 ++
 .../jetty/websocket/common/LogicalConnection.java  |  169 +
 .../org/eclipse/jetty/websocket/common/OpCode.java |  113 +
 .../org/eclipse/jetty/websocket/common/Parser.java |  660 +++
 .../jetty/websocket/common/SessionFactory.java     |   33 +
 .../jetty/websocket/common/SessionListener.java    |   31 +
 .../jetty/websocket/common/WebSocketFrame.java     |  388 ++
 .../websocket/common/WebSocketRemoteEndpoint.java  |  467 ++
 .../jetty/websocket/common/WebSocketSession.java   |  523 ++
 .../websocket/common/WebSocketSessionFactory.java  |   50 +
 .../common/events/AbstractEventDriver.java         |  252 +
 .../jetty/websocket/common/events/EventDriver.java |   65 +
 .../common/events/EventDriverFactory.java          |  147 +
 .../websocket/common/events/EventDriverImpl.java   |   58 +
 .../common/events/JettyAnnotatedEventDriver.java   |  234 +
 .../common/events/JettyAnnotatedImpl.java          |   65 +
 .../common/events/JettyAnnotatedMetadata.java      |   53 +
 .../common/events/JettyAnnotatedScanner.java       |  171 +
 .../common/events/JettyListenerEventDriver.java    |  136 +
 .../websocket/common/events/JettyListenerImpl.java |   44 +
 .../jetty/websocket/common/events/ParamList.java   |   33 +
 .../annotated/AbstractMethodAnnotationScanner.java |  194 +
 .../common/events/annotated/CallableMethod.java    |  144 +
 .../common/events/annotated/EventMethod.java       |  154 +
 .../common/events/annotated/EventMethods.java      |  105 +
 .../annotated/InvalidSignatureException.java       |   78 +
 .../annotated/OptionalSessionCallableMethod.java   |   91 +
 .../websocket/common/events/package-info.java      |   23 +
 .../common/extensions/AbstractExtension.java       |  209 +
 .../common/extensions/ExtensionStack.java          |  454 ++
 .../common/extensions/FrameDebugExtension.java     |  142 +
 .../extensions/WebSocketExtensionFactory.java      |   78 +
 .../extensions/compress/ByteAccumulator.java       |   79 +
 .../extensions/compress/CompressExtension.java     |  409 ++
 .../extensions/compress/DeflateFrameExtension.java |   72 +
 .../compress/PerMessageDeflateExtension.java       |  182 +
 .../compress/XWebkitDeflateFrameExtension.java     |   32 +
 .../common/extensions/compress/package-info.java   |   23 +
 .../extensions/fragment/FragmentExtension.java     |  217 +
 .../common/extensions/fragment/package-info.java   |   23 +
 .../extensions/identity/IdentityExtension.java     |   93 +
 .../common/extensions/identity/package-info.java   |   23 +
 .../websocket/common/extensions/package-info.java  |   23 +
 .../jetty/websocket/common/frames/BinaryFrame.java |   56 +
 .../jetty/websocket/common/frames/CloseFrame.java  |   48 +
 .../websocket/common/frames/ContinuationFrame.java |   54 +
 .../websocket/common/frames/ControlFrame.java      |  131 +
 .../jetty/websocket/common/frames/DataFrame.java   |   85 +
 .../jetty/websocket/common/frames/PingFrame.java   |   50 +
 .../jetty/websocket/common/frames/PongFrame.java   |   50 +
 .../jetty/websocket/common/frames/TextFrame.java   |   54 +
 .../common/io/AbstractWebSocketConnection.java     |  722 +++
 .../jetty/websocket/common/io/FrameFlusher.java    |  428 ++
 .../jetty/websocket/common/io/FramePipes.java      |   84 +
 .../websocket/common/io/FutureWriteCallback.java   |   48 +
 .../eclipse/jetty/websocket/common/io/IOState.java |  607 +++
 .../websocket/common/io/WriteCallbackWrapper.java  |   58 +
 .../io/http/HttpResponseHeaderParseListener.java   |   32 +
 .../common/io/http/HttpResponseHeaderParser.java   |  143 +
 .../jetty/websocket/common/io/package-info.java    |   23 +
 .../common/io/payload/DeMaskProcessor.java         |   80 +
 .../common/io/payload/PayloadProcessor.java        |   41 +
 .../websocket/common/io/payload/package-info.java  |   23 +
 .../websocket/common/message/MessageAppender.java  |   47 +
 .../common/message/MessageInputStream.java         |  197 +
 .../common/message/MessageOutputStream.java        |  219 +
 .../websocket/common/message/MessageReader.java    |   52 +
 .../websocket/common/message/MessageWriter.java    |  225 +
 .../common/message/SimpleBinaryMessage.java        |   70 +
 .../common/message/SimpleTextMessage.java          |   71 +
 .../websocket/common/message/Utf8CharBuffer.java   |  112 +
 .../websocket/common/message/package-info.java     |   23 +
 .../jetty/websocket/common/package-info.java       |   29 +
 .../jetty/websocket/common/util/ReflectUtils.java  |  395 ++
 .../jetty/websocket/common/util/TextUtil.java      |   84 +
 .../doc-files/websocket-stack-extensions.png       |  Bin 0 -> 71200 bytes
 .../doc-files/websocket-stack-extensions.svg       |  434 ++
 .../common/doc-files/websocket-stack-simple.png    |  Bin 0 -> 59981 bytes
 .../common/doc-files/websocket-stack-simple.svg    |  384 ++
 ...clipse.jetty.websocket.api.extensions.Extension |    5 +
 .../java/examples/AdapterConnectCloseSocket.java   |   40 +
 .../java/examples/AnnotatedBinaryArraySocket.java  |   51 +
 .../java/examples/AnnotatedBinaryStreamSocket.java |   56 +
 .../test/java/examples/AnnotatedFramesSocket.java  |   51 +
 .../java/examples/AnnotatedStreamingSocket.java    |   79 +
 .../test/java/examples/AnnotatedTextSocket.java    |   57 +
 .../java/examples/AnnotatedTextStreamSocket.java   |   52 +
 .../test/java/examples/ListenerBasicSocket.java    |   58 +
 .../test/java/examples/echo/AdapterEchoSocket.java |   47 +
 .../java/examples/echo/AnnotatedEchoSocket.java    |   41 +
 .../java/examples/echo/ListenerEchoSocket.java     |   65 +
 .../jetty/websocket/common/AcceptHashTest.java     |   62 +
 .../jetty/websocket/common/CloseInfoTest.java      |  166 +
 .../websocket/common/ClosePayloadParserTest.java   |   67 +
 .../common/GeneratorParserRoundtripTest.java       |  124 +
 .../jetty/websocket/common/GeneratorTest.java      |  319 ++
 .../eclipse/jetty/websocket/common/ParserTest.java |  255 +
 .../websocket/common/PingPayloadParserTest.java    |   58 +
 .../common/RFC6455ExamplesGeneratorTest.java       |  191 +
 .../common/RFC6455ExamplesParserTest.java          |  252 +
 .../websocket/common/TextPayloadParserTest.java    |  236 +
 .../jetty/websocket/common/WebSocketFrameTest.java |  140 +
 .../common/WebSocketRemoteEndpointTest.java        |   91 +
 .../jetty/websocket/common/ab/TestABCase1_1.java   |  517 ++
 .../jetty/websocket/common/ab/TestABCase1_2.java   |  536 ++
 .../jetty/websocket/common/ab/TestABCase2.java     |  323 ++
 .../jetty/websocket/common/ab/TestABCase3.java     |   92 +
 .../jetty/websocket/common/ab/TestABCase4.java     |  165 +
 .../jetty/websocket/common/ab/TestABCase7_3.java   |  349 ++
 .../annotations/BadBinarySignatureSocket.java      |   39 +
 .../annotations/BadDuplicateBinarySocket.java      |   49 +
 .../annotations/BadDuplicateFrameSocket.java       |   45 +
 .../common/annotations/BadTextSignatureSocket.java |   39 +
 .../websocket/common/annotations/FrameSocket.java  |   36 +
 .../common/annotations/MyEchoBinarySocket.java     |   45 +
 .../websocket/common/annotations/MyEchoSocket.java |   78 +
 .../common/annotations/MyStatelessEchoSocket.java  |   41 +
 .../websocket/common/annotations/NoopSocket.java   |   30 +
 .../websocket/common/annotations/NotASocket.java   |   36 +
 .../websocket/common/events/EventCapture.java      |   93 +
 .../common/events/EventDriverFactoryTest.java      |   85 +
 .../websocket/common/events/EventDriverTest.java   |  182 +
 .../common/events/JettyAnnotatedScannerTest.java   |  342 ++
 .../common/extensions/AbstractExtensionTest.java   |   44 +
 .../common/extensions/DummyIncomingFrames.java     |   56 +
 .../common/extensions/DummyOutgoingFrames.java     |   62 +
 .../common/extensions/ExtensionStackTest.java      |  189 +
 .../websocket/common/extensions/ExtensionTool.java |  136 +
 .../common/extensions/FragmentExtensionTest.java   |  313 ++
 .../common/extensions/IdentityExtensionTest.java   |  101 +
 .../extensions/compress/CapturedHexPayloads.java   |   49 +
 .../compress/DeflateFrameExtensionTest.java        |  437 ++
 .../compress/PerMessageDeflateExtensionTest.java   |  409 ++
 .../jetty/websocket/common/io/IOStateTest.java     |  245 +
 .../common/io/LocalWebSocketConnection.java        |  254 +
 .../websocket/common/io/LocalWebSocketSession.java |   58 +
 .../websocket/common/io/TrackingCallback.java      |   81 +
 .../io/http/HttpResponseHeaderParserTest.java      |  196 +
 .../common/io/http/HttpResponseParseCapture.java   |   75 +
 .../common/io/payload/DeMaskProcessorTest.java     |  109 +
 .../websocket/common/message/DummySocket.java      |   29 +
 .../websocket/common/message/MessageDebug.java     |   60 +
 .../common/message/MessageInputStreamTest.java     |  235 +
 .../common/message/MessageOutputStreamTest.java    |  139 +
 .../common/message/MessageWriterTest.java          |  139 +
 .../common/message/TrackingInputStreamSocket.java  |  112 +
 .../websocket/common/message/TrackingSocket.java   |  170 +
 .../common/message/Utf8CharBufferTest.java         |  125 +
 .../websocket/common/test/BlockheadClient.java     |  769 +++
 .../test/BlockheadClientConstructionTest.java      |   72 +
 .../websocket/common/test/BlockheadServer.java     |  696 +++
 .../websocket/common/test/ByteBufferAssert.java    |   68 +
 .../jetty/websocket/common/test/Fuzzed.java        |   32 +
 .../jetty/websocket/common/test/Fuzzer.java        |  343 ++
 .../jetty/websocket/common/test/HttpResponse.java  |   94 +
 .../common/test/IncomingFramesCapture.java         |  163 +
 .../common/test/LeakTrackingBufferPoolRule.java    |   61 +
 .../common/test/OutgoingFramesCapture.java         |   97 +
 .../common/test/OutgoingNetworkBytesCapture.java   |   77 +
 .../websocket/common/test/RawFrameBuilder.java     |  110 +
 .../jetty/websocket/common/test/UnitGenerator.java |  134 +
 .../jetty/websocket/common/test/UnitParser.java    |   73 +
 .../eclipse/jetty/websocket/common/util/Hex.java   |   80 +
 .../websocket/common/util/MaskedByteBuffer.java    |   50 +
 .../jetty/websocket/common/util/StackUtil.java     |   42 +
 .../src/test/resources/jetty-logging.properties    |    7 +
 .../src/test/resources/keystore                    |  Bin
 .../src/test/webapp/index.html                     |    0
 jetty-websocket/websocket-server/pom.xml           |  105 +
 .../jetty/websocket/server/HandshakeRFC6455.java   |   66 +
 .../websocket/server/MappedWebSocketCreator.java   |   33 +
 .../websocket/server/ServletWebSocketRequest.java  |   37 +
 .../websocket/server/ServletWebSocketResponse.java |   35 +
 .../jetty/websocket/server/WebSocketHandler.java   |  114 +
 .../jetty/websocket/server/WebSocketHandshake.java |   35 +
 .../server/WebSocketServerConnection.java          |   82 +
 .../websocket/server/WebSocketServerFactory.java   |  581 ++
 .../websocket/server/WebSocketUpgradeFilter.java   |  345 ++
 .../server/WebSocketUpgradeHandlerWrapper.java     |   99 +
 .../jetty/websocket/server/package-info.java       |   23 +
 .../websocket/server/pathmap/PathMappings.java     |  190 +
 .../jetty/websocket/server/pathmap/PathSpec.java   |  167 +
 .../websocket/server/pathmap/PathSpecGroup.java    |   82 +
 .../websocket/server/pathmap/RegexPathSpec.java    |  176 +
 .../websocket/server/pathmap/ServletPathSpec.java  |  291 ++
 ...jetty.websocket.servlet.WebSocketServletFactory |    1 +
 .../server/AnnotatedMaxMessageSizeTest.java        |  147 +
 .../jetty/websocket/server/BatchModeTest.java      |  103 +
 .../eclipse/jetty/websocket/server/ChromeTest.java |   84 +
 .../jetty/websocket/server/FirefoxTest.java        |   73 +
 .../websocket/server/FragmentExtensionTest.java    |  103 +
 .../server/FrameCompressionExtensionTest.java      |   94 +
 .../websocket/server/IdentityExtensionTest.java    |   83 +
 .../jetty/websocket/server/IdleTimeoutTest.java    |  107 +
 .../server/PerMessageDeflateExtensionTest.java     |  101 +
 .../jetty/websocket/server/RequestHeadersTest.java |  135 +
 .../websocket/server/SimpleServletServer.java      |  164 +
 .../jetty/websocket/server/TooFastClientTest.java  |  111 +
 .../jetty/websocket/server/WebSocketCloseTest.java |  217 +
 .../server/WebSocketInvalidVersionTest.java        |   71 +
 .../websocket/server/WebSocketOverSSLTest.java     |  189 +
 .../websocket/server/WebSocketProtocolTest.java    |   91 +
 .../server/WebSocketServerSessionTest.java         |  103 +
 .../websocket/server/WebSocketServletRFCTest.java  |  352 ++
 .../jetty/websocket/server/ab/ABServlet.java       |   44 +
 .../jetty/websocket/server/ab/ABSocket.java        |   83 +
 .../jetty/websocket/server/ab/AbstractABCase.java  |  228 +
 .../jetty/websocket/server/ab/TestABCase1.java     |  472 ++
 .../jetty/websocket/server/ab/TestABCase2.java     |  331 ++
 .../jetty/websocket/server/ab/TestABCase3.java     |  211 +
 .../jetty/websocket/server/ab/TestABCase4.java     |  284 +
 .../jetty/websocket/server/ab/TestABCase5.java     |  617 +++
 .../jetty/websocket/server/ab/TestABCase6.java     |  410 ++
 .../websocket/server/ab/TestABCase6_BadUTF.java    |  177 +
 .../websocket/server/ab/TestABCase6_GoodUTF.java   |  150 +
 .../jetty/websocket/server/ab/TestABCase7.java     |  346 ++
 .../server/ab/TestABCase7_BadStatusCodes.java      |  138 +
 .../server/ab/TestABCase7_GoodStatusCodes.java     |  132 +
 .../jetty/websocket/server/ab/TestABCase9.java     |  760 +++
 .../websocket/server/browser/BrowserDebugTool.java |  177 +
 .../websocket/server/browser/BrowserSocket.java    |  281 +
 .../websocket/server/examples/BasicEchoSocket.java |   65 +
 .../server/examples/MyCustomCreationServlet.java   |   79 +
 .../websocket/server/examples/MyEchoServlet.java   |   35 +
 .../websocket/server/examples/MyEchoSocket.java    |   54 +
 .../server/examples/echo/BigEchoSocket.java        |   67 +
 .../examples/echo/EchoBroadcastPingSocket.java     |  101 +
 .../server/examples/echo/EchoBroadcastSocket.java  |   68 +
 .../server/examples/echo/EchoCreator.java          |   68 +
 .../server/examples/echo/EchoFragmentSocket.java   |   79 +
 .../server/examples/echo/ExampleEchoServer.java    |  151 +
 .../websocket/server/examples/echo/LogSocket.java  |   93 +
 .../websocket/server/helper/CaptureSocket.java     |   61 +
 .../jetty/websocket/server/helper/EchoServlet.java |   46 +
 .../jetty/websocket/server/helper/EchoSocket.java  |   73 +
 .../jetty/websocket/server/helper/RFCServlet.java  |   32 +
 .../jetty/websocket/server/helper/RFCSocket.java   |   76 +
 .../jetty/websocket/server/helper/SafariD00.java   |  151 +
 .../websocket/server/helper/SessionServlet.java    |   32 +
 .../websocket/server/helper/SessionSocket.java     |  125 +
 .../server/helper/WebSocketCaptureServlet.java     |   44 +
 .../AnnotatedRuntimeOnConnectSocket.java           |   70 +
 .../server/misbehaving/BadSocketsServlet.java      |   56 +
 .../ListenerRuntimeOnConnectSocket.java            |   74 +
 .../server/misbehaving/MisbehavingClassTest.java   |  133 +
 .../server/pathmap/PathMappingsBenchmarkTest.java  |  226 +
 .../websocket/server/pathmap/PathMappingsTest.java |  119 +
 .../server/pathmap/RegexPathSpecTest.java          |  135 +
 .../server/pathmap/ServletPathSpecTest.java        |  188 +
 .../test/resources/browser-debug-tool/index.html   |   58 +
 .../src/test/resources/browser-debug-tool/main.css |   29 +
 .../test/resources/browser-debug-tool/websocket.js |  141 +
 .../src/test/resources/jetty-logging.properties    |   24 +
 .../websocket-server}/src/test/resources/keystore  |  Bin
 jetty-websocket/websocket-servlet/pom.xml          |   69 +
 .../websocket/servlet/ServletUpgradeRequest.java   |  303 ++
 .../websocket/servlet/ServletUpgradeResponse.java  |  128 +
 .../servlet/UpgradeHttpServletRequest.java         |  591 +++
 .../jetty/websocket/servlet/WebSocketCreator.java  |   42 +
 .../jetty/websocket/servlet/WebSocketServlet.java  |  169 +
 .../websocket/servlet/WebSocketServletFactory.java |  109 +
 .../jetty/websocket/servlet/package-info.java      |   31 +
 .../test/java/examples/MyAdvancedEchoCreator.java  |   57 +
 .../test/java/examples/MyAdvancedEchoServlet.java  |   39 +
 .../src/test/java/examples/MyAuthedCreator.java    |   60 +
 .../src/test/java/examples/MyAuthedServlet.java    |   32 +
 .../src/test/java/examples/MyBinaryEchoSocket.java |   39 +
 .../src/test/java/examples/MyEchoServlet.java      |   39 +
 .../src/test/java/examples/MyEchoSocket.java       |   37 +
 jetty-xml/pom.xml                                  |    6 +-
 .../java/org/eclipse/jetty/xml/XmlAppendable.java  |  166 +
 .../org/eclipse/jetty/xml/XmlConfiguration.java    |  525 +-
 .../main/java/org/eclipse/jetty/xml/XmlParser.java |  131 +-
 .../java/org/eclipse/jetty/xml/package-info.java   |   23 +
 .../org/eclipse/jetty/xml/configure_9_0.dtd        |  288 +
 .../jetty/xml/AnnotatedTestConfiguration.java      |   78 +
 .../eclipse/jetty/xml/ConstructorArgTestClass.java |    2 +-
 .../jetty/xml/DefaultTestConfiguration.java        |   74 +
 .../org/eclipse/jetty/xml/TestConfiguration.java   |   19 +-
 .../org/eclipse/jetty/xml/XmlAppendableTest.java   |   70 +
 .../eclipse/jetty/xml/XmlConfigurationTest.java    |  457 +-
 .../src/test/resources/jetty-logging.properties    |    3 +
 .../resources/org/eclipse/jetty/xml/configure.xml  |    5 +-
 .../resources/org/eclipse/jetty/xml/mortbay.xml    |    2 +-
 pom.xml                                            |  770 ++-
 settings.xml                                       |   21 -
 test-continuation-jetty6/pom.xml                   |   82 -
 .../jetty/continuation/ContinuationBase.java       |  428 --
 .../jetty/continuation/FauxContinuationTest.java   |   83 -
 .../Jetty6ContinuationBioFauxTest.java             |  156 -
 .../continuation/Jetty6ContinuationBioTest.java    |  156 -
 .../Jetty6ContinuationNioFauxTest.java             |  157 -
 .../continuation/Jetty6ContinuationNioTest.java    |  157 -
 .../jetty/continuation/TestProxyServer.java        |   57 -
 test-continuation/pom.xml                          |   38 -
 .../jetty/continuation/test/ContinuationBase.java  |  515 --
 .../jetty/continuation/ContinuationTest.java       |  186 -
 .../jetty/continuation/FauxContinuationTest.java   |  160 -
 test-jetty-nested/pom.xml                          |   30 -
 .../main/java/org/eclipse/jetty/nested/Dump.java   | 1021 ----
 .../eclipse/jetty/nested/NestedJettyServlet.java   |  143 -
 .../java/org/eclipse/jetty/nested/TestServlet.java |  203 -
 .../src/main/webapp/WEB-INF/jetty.xml              |   34 -
 test-jetty-nested/src/main/webapp/WEB-INF/web.xml  |   52 -
 test-jetty-nested/src/main/webapp/index.html       |    1 -
 .../src/main/webapp/nested/WEB-INF/web.xml         |   35 -
 .../src/main/webapp/nested/index.html              |    7 -
 .../org/eclipse/jetty/nested/NestedServer.java     |   47 -
 test-jetty-servlet/pom.xml                         |   24 -
 .../src/main/java/Jetty400Repro.java               |  146 -
 .../java/org/eclipse/jetty/testing/HttpTester.java |  619 ---
 .../org/eclipse/jetty/testing/ServletTester.java   |  385 --
 .../org/eclipse/jetty/testing/HttpTesterTest.java  |   79 -
 .../org/eclipse/jetty/testing/ServletTest.java     |  347 --
 test-jetty-webapp/pom.xml                          |  225 -
 .../assembly/embedded-jetty-web-for-webbundle.xml  |   93 -
 test-jetty-webapp/src/main/assembly/web-bundle.xml |   35 -
 .../config/contexts-available/move-context.xml     |   11 -
 .../main/config/contexts/test.d/override-web.xml   |   60 -
 .../src/main/config/contexts/test.xml              |   97 -
 .../src/main/config/etc/jetty-testrealm.xml        |   23 -
 .../src/main/config/etc/realm.properties           |   21 -
 .../src/main/java/com/acme/ChatServlet.java        |  192 -
 .../src/main/java/com/acme/CookieDump.java         |  143 -
 .../src/main/java/com/acme/Counter.java            |   41 -
 .../src/main/java/com/acme/Date2Tag.java           |   53 -
 .../src/main/java/com/acme/DateTag.java            |   70 -
 .../src/main/java/com/acme/DispatchServlet.java    |  282 -
 test-jetty-webapp/src/main/java/com/acme/Dump.java | 1020 ----
 .../src/main/java/com/acme/HelloWorld.java         |   70 -
 .../src/main/java/com/acme/LoginServlet.java       |   92 -
 .../src/main/java/com/acme/RegTest.java            |  194 -
 .../src/main/java/com/acme/RewriteServlet.java     |   74 -
 .../src/main/java/com/acme/SecureModeServlet.java  |  382 --
 .../src/main/java/com/acme/SessionDump.java        |  185 -
 .../src/main/java/com/acme/TestFilter.java         |  129 -
 .../src/main/java/com/acme/TestListener.java       |  180 -
 .../main/java/com/acme/WebSocketChatServlet.java   |   99 -
 .../src/main/webapp/META-INF/MANIFEST.MF           |   23 -
 .../src/main/webapp/WEB-INF/acme-taglib.tld        |   28 -
 .../src/main/webapp/WEB-INF/jetty-web.xml          |   14 -
 test-jetty-webapp/src/main/webapp/WEB-INF/web.xml  |  387 --
 test-jetty-webapp/src/main/webapp/auth.html        |   36 -
 test-jetty-webapp/src/main/webapp/chat/index.html  |   85 -
 test-jetty-webapp/src/main/webapp/index.html       |   62 -
 test-jetty-webapp/src/main/webapp/remote.html      |   32 -
 .../src/main/webapp/rewrite/index.html             |   13 -
 .../org/eclipse/jetty/DispatchServletTest.java     |  142 -
 .../test/java/org/eclipse/jetty/TestServer.java    |  209 -
 tests/pom.xml                                      |    8 +-
 tests/test-cdi/cdi-client/pom.xml                  |   37 +
 tests/test-cdi/cdi-webapp-it/pom.xml               |  215 +
 .../test/java/org/eclipse/jetty/tests/HelloIT.java |   41 +
 .../java/org/eclipse/jetty/tests/ServerInfoIT.java |   37 +
 .../cdi-webapp-it/src/test/scripts/setup-jetty.sh  |   19 +
 .../cdi-webapp-it/src/test/scripts/start-jetty.sh  |   17 +
 .../cdi-webapp-it/src/test/scripts/stop-jetty.sh   |   11 +
 tests/test-cdi/cdi-webapp/pom.xml                  |   56 +
 .../java/org/eclipse/jetty/tests/HelloServlet.java |   42 +
 .../org/eclipse/jetty/tests/ServerInfoServlet.java |   54 +
 .../cdi-webapp/src/main/webapp/WEB-INF/beans.xml   |    0
 .../cdi-webapp/src/main/webapp/WEB-INF/web.xml     |   18 +
 tests/test-cdi/pom.xml                             |   35 +
 tests/test-continuation/pom.xml                    |   62 +
 .../jetty/continuation/ContinuationBase.java       |  509 ++
 .../jetty/continuation/ContinuationTest.java       |  203 +
 .../jetty/continuation/FauxContinuationTest.java   |  156 +
 tests/test-integration/pom.xml                     |    7 +-
 .../org/eclipse/jetty/test/DefaultHandlerTest.java |   55 +-
 .../org/eclipse/jetty/test/DigestPostTest.java     |  136 +-
 .../test/jsp/JspAndDefaultWithAliasesTest.java     |   11 +-
 .../test/jsp/JspAndDefaultWithoutAliasesTest.java  |   12 +-
 .../test/monitor/JavaMonitorIntegrationTest.java   |  171 -
 .../eclipse/jetty/test/monitor/JmxServiceTest.java |  165 -
 .../jetty/test/monitor/ProgramConfigTest.java      |  184 -
 .../eclipse/jetty/test/monitor/XmlConfigTest.java  |  167 -
 .../jetty/test/rfcs/RFC2616BIOHttpTest.java        |   49 -
 .../jetty/test/rfcs/RFC2616BIOHttpsTest.java       |   50 -
 .../eclipse/jetty/test/rfcs/RFC2616BaseTest.java   |  514 +-
 .../jetty/test/rfcs/RFC2616NIOHttpTest.java        |    4 +-
 .../jetty/test/rfcs/RFC2616NIOHttpsTest.java       |    7 +-
 .../jetty/test/support/TestableJettyServer.java    |   27 +-
 .../test/support/rawhttp/HttpRequestTester.java    |  228 -
 .../support/rawhttp/HttpRequestTesterTest.java     |   31 +-
 .../test/support/rawhttp/HttpResponseTester.java   |  461 --
 .../support/rawhttp/HttpResponseTesterTest.java    |   66 +-
 .../jetty/test/support/rawhttp/HttpTesting.java    |  150 +-
 .../src/test/resources/BIOHttp.xml                 |   22 -
 .../src/test/resources/BIOHttps.xml                |   29 -
 .../src/test/resources/DefaultHandler.xml          |   51 +-
 .../src/test/resources/NIOHttp.xml                 |   38 +-
 .../src/test/resources/NIOHttps.xml                |   50 +-
 .../src/test/resources/RFC2616Base.xml             |  163 +-
 .../src/test/resources/RFC2616_Filters.xml         |    4 +-
 .../src/test/resources/RFC2616_Redirects.xml       |   16 +-
 .../src/test/resources/monitor/etc/jetty-jmx.xml   |   82 -
 .../src/test/resources/monitor/start.ini           |    7 -
 .../jetty/monitor/java-monitor-integration.xml     |   62 -
 .../jetty/monitor/jetty-monitor-service.xml        |   68 -
 .../eclipse/jetty/monitor/jetty-monitor-test.xml   |   65 -
 tests/test-integration/src/test/resources/ssl.xml  |   33 +
 .../webapp-contexts/RFC2616/rfc2616-webapp.xml     |    4 +-
 .../src/test/resources/webdefault.xml              |    2 +-
 tests/test-jmx/jmx-webapp-it/pom.xml               |  207 +
 .../java/org/eclipse/jetty/test/jmx/JmxIT.java     |  144 +
 .../java/org/eclipse/jetty/test/jmx/PingIT.java    |   41 +
 .../jmx-webapp-it/src/test/scripts/setup-jetty.sh  |   19 +
 .../jmx-webapp-it/src/test/scripts/start-jetty.sh  |   17 +
 .../jmx-webapp-it/src/test/scripts/stop-jetty.sh   |   11 +
 tests/test-jmx/jmx-webapp/pom.xml                  |   65 +
 .../eclipse/jetty/test/jmx/CommonComponent.java    |   49 +
 .../java/org/eclipse/jetty/test/jmx/Echoer.java    |   43 +
 .../jetty/test/jmx/MyContainerInitializer.java     |   46 +
 .../org/eclipse/jetty/test/jmx/PingServlet.java    |   64 +
 .../java/org/eclipse/jetty/test/jmx/Pinger.java    |   40 +
 .../eclipse/jetty/test/jmx/jmx/EchoerMBean.java    |   38 +
 .../eclipse/jetty/test/jmx/jmx/PingerMBean.java    |   51 +
 .../javax.servlet.ServletContainerInitializer      |    1 +
 .../jmx-webapp/src/main/webapp/WEB-INF/web.xml     |   34 +
 tests/test-jmx/pom.xml                             |   34 +
 tests/test-loginservice/pom.xml                    |   86 +-
 .../eclipse/jetty/DataSourceLoginServiceTest.java  |  204 +
 .../jetty/DatabaseLoginServiceTestServer.java      |  250 +
 .../org/eclipse/jetty/JdbcLoginServiceTest.java    |  467 +-
 .../src/test/resources/droptables.sql              |    6 +
 .../src/test/resources/jdbcrealm.properties        |    2 +-
 .../src/test/resources/jetty-logging.properties    |    3 +
 tests/test-quickstart/pom.xml                      |  182 +
 .../jetty/quickstart/PreconfigureJNDIWar.java      |   49 +
 .../jetty/quickstart/PreconfigureSpecWar.java      |   58 +
 .../quickstart/PreconfigureStandardTestWar.java    |   61 +
 .../jetty/quickstart/QuickStartJNDIWar.java        |   31 +
 .../jetty/quickstart/QuickStartSpecWar.java        |   30 +
 .../quickstart/QuickStartStandardTestWar.java      |   30 +
 .../eclipse/jetty/quickstart/QuickStartTest.java   |  179 +
 .../org/eclipse/jetty/quickstart/Quickstart.java   |   73 +
 .../src/test/resources/realm.properties            |   21 +
 .../src/test/resources/test-jndi.xml               |   60 +
 .../src/test/resources/test-spec.xml               |   39 +
 tests/test-quickstart/src/test/resources/test.xml  |   47 +
 tests/test-sessions/pom.xml                        |    3 +-
 tests/test-sessions/test-hash-sessions/pom.xml     |   20 +-
 .../jetty/server/session/HashTestServer.java       |    2 -
 .../jetty/server/session/IdleSessionTest.java      |   96 +-
 .../server/session/ProxySerializationTest.java     |   78 +
 .../jetty/server/session/SessionRenewTest.java     |   81 +
 tests/test-sessions/test-jdbc-sessions/pom.xml     |   19 +-
 .../session/ClientCrossContextSessionTest.java     |   16 +
 .../jetty/server/session/DirtyAttributeTest.java   |  225 +
 .../jetty/server/session/ImmortalSessionTest.java  |   16 +
 .../server/session/InvalidationSessionTest.java    |   17 +
 .../jetty/server/session/JdbcTestServer.java       |  111 +-
 .../jetty/server/session/LastAccessTimeTest.java   |   18 +-
 .../server/session/LocalSessionScavengingTest.java |   16 +
 .../server/session/MaxInactiveMigrationTest.java   |   36 +-
 .../session/ModifyMaxInactiveIntervalTest.java     |  145 +
 .../jetty/server/session/NewSessionTest.java       |   16 +
 .../jetty/server/session/OrphanedSessionTest.java  |   16 +
 .../server/session/ProxySerializationTest.java     |   58 +
 .../session/ReentrantRequestSessionTest.java       |   16 +
 .../session/ReloadedSessionMissingClassTest.java   |   32 +-
 .../jetty/server/session/SaveIntervalTest.java     |   49 +-
 .../session/ServerCrossContextSessionTest.java     |   16 +
 .../jetty/server/session/SessionExpiryTest.java    |  133 +-
 .../session/SessionInvalidateAndCreateTest.java    |   38 +
 .../jetty/server/session/SessionMigrationTest.java |   16 +
 .../jetty/server/session/SessionRenewTest.java     |   54 +
 .../server/session/SessionValueSavingTest.java     |   27 +-
 .../StopSessionManagerPreserveSessionTest.java     |   81 +
 .../server/session/WebAppObjectInSessionTest.java  |   18 +
 tests/test-sessions/test-mongodb-sessions/pom.xml  |   19 +-
 .../jetty/nosql/mongodb/AttributeNameTest.java     |  163 +
 .../jetty/nosql/mongodb/InvalidateSessionTest.java |   54 +
 .../jetty/nosql/mongodb/LastAccessTimeTest.java    |    1 -
 .../nosql/mongodb/LocalSessionScavengingTest.java  |   41 +
 .../jetty/nosql/mongodb/MongoTestServer.java       |    5 +-
 .../nosql/mongodb/PurgeInvalidSessionTest.java     |   57 +-
 .../jetty/nosql/mongodb/PurgeValidSessionTest.java |   27 +-
 .../mongodb/ServerCrossContextSessionTest.java     |    1 +
 .../jetty/nosql/mongodb/SessionExpiryTest.java     |   45 +
 .../mongodb/SessionInvalidateAndCreateTest.java    |   41 +
 .../jetty/nosql/mongodb/SessionMigrationTest.java  |   39 +
 .../jetty/nosql/mongodb/SessionRenewTest.java      |   39 +
 .../nosql/mongodb/SessionSavingValueTest.java      |   72 +-
 .../StopSessionManagerDeleteSessionTest.java       |  164 +
 .../StopSessionManagerPreserveSessionTest.java     |   98 +
 tests/test-sessions/test-sessions-common/pom.xml   |    2 +-
 .../AbstractClientCrossContextSessionTest.java     |   46 +-
 .../session/AbstractImmortalSessionTest.java       |   46 +-
 .../session/AbstractInvalidationSessionTest.java   |   72 +-
 .../server/session/AbstractLastAccessTimeTest.java |   85 +-
 .../server/session/AbstractLightLoadTest.java      |   57 +-
 .../AbstractLocalSessionScavengingTest.java        |   71 +-
 .../server/session/AbstractNewSessionTest.java     |   43 +-
 .../session/AbstractOrphanedSessionTest.java       |   42 +-
 .../session/AbstractProxySerializationTest.java    |  132 +
 .../AbstractReentrantRequestSessionTest.java       |   36 +-
 .../server/session/AbstractRemoveSessionTest.java  |   61 +-
 .../AbstractServerCrossContextSessionTest.java     |   25 +-
 .../server/session/AbstractSessionCookieTest.java  |   75 +-
 .../server/session/AbstractSessionExpiryTest.java  |   96 +-
 .../AbstractSessionInvalidateAndCreateTest.java    |  104 +-
 .../session/AbstractSessionMigrationTest.java      |   49 +-
 .../server/session/AbstractSessionRenewTest.java   |  162 +
 .../session/AbstractSessionValueSavingTest.java    |  104 +-
 ...tractStopSessionManagerPreserveSessionTest.java |  116 +
 .../jetty/server/session/AbstractTestServer.java   |    3 +-
 .../session/AbstractWebAppObjectInSessionTest.java |   53 +-
 .../src/main/resources/proxy-serialization.jar     |  Bin 0 -> 5378 bytes
 tests/test-webapps/pom.xml                         |    9 +-
 tests/test-webapps/test-dispatch-webapp/pom.xml    |   46 +
 .../src/main/java/com/acme/DispatchServlet.java    |  123 +
 .../src/main/webapp/WEB-INF/web.xml                |   24 +
 .../src/main/webapp/images}/jetty_banner.gif       |  Bin
 .../src/main/webapp/images/small_powered_by.gif    |  Bin 0 -> 4787 bytes
 .../src/main/webapp/index.html                     |   48 +
 .../src/main/webapp/stylesheet.css                 |    7 +
 tests/test-webapps/test-jaas-webapp/pom.xml        |   70 +
 .../test-jaas-webapp/src/main/assembly/config.xml  |   19 +
 .../src/main/config/demo-base/etc/login.conf       |    5 +
 .../src/main/config/demo-base/etc/login.properties |    1 +
 .../main/config/demo-base/webapps/test-jaas.xml    |   25 +
 .../src/main/webapp/WEB-INF/jetty-web.xml          |    8 +
 .../src/main/webapp/WEB-INF/web.xml                |   41 +
 .../test-jaas-webapp/src/main/webapp/auth.html     |   18 +
 .../test-jaas-webapp/src/main/webapp/authfail.html |   11 +
 .../src/main/webapp/images}/jetty_banner.gif       |  Bin
 .../src/main/webapp/images/small_powered_by.gif    |  Bin 0 -> 4787 bytes
 .../test-jaas-webapp/src/main/webapp/index.html    |   42 +
 .../test-jaas-webapp/src/main/webapp/login.html    |   18 +
 .../test-jaas-webapp/src/main/webapp/logout.jsp    |   21 +
 .../src/main/webapp/stylesheet.css                 |    7 +
 .../test-webapps/test-jetty-webapp}/jetty-chat.jmx |    0
 tests/test-webapps/test-jetty-webapp/pom.xml       |  270 +
 .../assembly/embedded-jetty-web-for-webbundle.xml  |   93 +
 .../src/main/assembly/web-bundle.xml               |   35 +
 .../config/demo-base/etc/demo-rewrite-rules.xml    |  104 +
 .../src/main/config/demo-base/etc/realm.properties |   21 +
 .../src/main/config/demo-base/etc/test-realm.xml   |   24 +
 .../src/main/config/demo-base/start.ini            |   23 +
 .../demo-base/webapps/test.d/override-web.xml      |   56 +
 .../src/main/config/demo-base/webapps/test.xml     |  112 +
 .../src/main/java/com/acme/ChatServlet.java        |  232 +
 .../src/main/java/com/acme/CookieDump.java         |  142 +
 .../src/main/java/com/acme/Counter.java            |   41 +
 .../src/main/java/com/acme/Date2Tag.java           |   53 +
 .../src/main/java/com/acme/DateTag.java            |   71 +
 .../src/main/java/com/acme/DispatchServlet.java    |  278 +
 .../src/main/java/com/acme/Dump.java               | 1038 ++++
 .../src/main/java/com/acme/HelloWorld.java         |   61 +
 .../src/main/java/com/acme/JavaxWebSocketChat.java |   88 +
 .../src/main/java/com/acme/LoginServlet.java       |   80 +
 .../src/main/java/com/acme/RegTest.java            |  194 +
 .../src/main/java/com/acme/RewriteServlet.java     |   77 +
 .../src/main/java/com/acme/SecureModeServlet.java  |  381 ++
 .../src/main/java/com/acme/SessionDump.java        |  184 +
 .../src/main/java/com/acme/TagListener.java        |    0
 .../src/main/java/com/acme/TestFilter.java         |  128 +
 .../src/main/java/com/acme/TestListener.java       |  226 +
 .../main/java/com/acme/WebSocketChatServlet.java   |  122 +
 .../src/main/resources/jetty-logging.properties    |    2 +
 .../src/main/webapp/WEB-INF/acme-taglib.tld        |   28 +
 .../src/main/webapp/WEB-INF/acme-taglib2.tld       |    0
 .../src/main/webapp/WEB-INF/jetty-web.xml          |   17 +
 .../src/main/webapp/WEB-INF/tags/panel.tag         |    0
 .../src/main/webapp/WEB-INF/web.xml                |  342 ++
 .../test-jetty-webapp/src/main/webapp/auth.html    |   36 +
 .../src/main/webapp/auth/file.txt                  |    0
 .../src/main/webapp/auth/relax.txt                 |    0
 .../src/main/webapp/auth2/index.html               |    0
 .../src/main/webapp/cgi-bin/hello.sh               |    0
 .../src/main/webapp/chat/index.html                |  165 +
 .../test-jetty-webapp}/src/main/webapp/d.txt       |    0
 .../test-jetty-webapp}/src/main/webapp/da.txt      |    0
 .../test-jetty-webapp}/src/main/webapp/da.txt.gz   |  Bin
 .../test-jetty-webapp}/src/main/webapp/dat.txt     |    0
 .../test-jetty-webapp}/src/main/webapp/data.txt    |    0
 .../test-jetty-webapp}/src/main/webapp/data.txt.gz |  Bin
 .../src/main/webapp/error404.html                  |    0
 .../test-jetty-webapp}/src/main/webapp/favicon.ico |  Bin
 .../test-jetty-webapp/src/main/webapp/index.html   |   72 +
 .../src/main/webapp/javax.websocket/index.html     |  112 +
 .../src/main/webapp/jetty_banner.gif               |  Bin
 .../src/main/webapp/jsp/bean1.jsp                  |    0
 .../src/main/webapp/jsp/bean2.jsp                  |    0
 .../src/main/webapp/jsp/dump.jsp                   |    0
 .../src/main/webapp/jsp/expr.jsp                   |    0
 .../src/main/webapp/jsp/foo/foo.jsp                |    0
 .../src/main/webapp/jsp/index.html                 |    0
 .../src/main/webapp/jsp/jstl.jsp                   |    0
 .../test-jetty-webapp}/src/main/webapp/jsp/tag.jsp |    0
 .../src/main/webapp/jsp/tag2.jsp                   |    0
 .../src/main/webapp/jsp/tagfile.jsp                |    0
 .../test-jetty-webapp}/src/main/webapp/logon.html  |    0
 .../src/main/webapp/logonError.html                |    0
 .../test-jetty-webapp/src/main/webapp/remote.html  |   32 +
 .../src/main/webapp/rewrite/index.html             |   13 +
 .../src/main/webapp/rewrite/info.html              |    0
 .../src/main/webapp/small_powered_by.gif           |  Bin 0 -> 4787 bytes
 .../src/main/webapp/ws/index.html                  |    0
 .../java/org/eclipse/jetty/ChatServletTest.java    |   94 +
 .../org/eclipse/jetty/DispatchServletTest.java     |  145 +
 .../test/java/org/eclipse/jetty/TestServer.java    |  219 +
 tests/test-webapps/test-jndi-webapp/pom.xml        |  149 +
 .../test-jndi-webapp/src/main/assembly/config.xml  |   34 +
 .../src/main/java/com/acme/JNDITest.java           |  160 +
 .../src/main/templates/env-definitions.xml         |   47 +
 .../src/main/templates/jetty-test-jndi-header.xml  |   42 +
 .../src/main/templates/plugin-context-header.xml   |   15 +
 .../src/main/webapp/WEB-INF/jetty-env.xml          |   34 +
 .../src/main/webapp/WEB-INF/jetty-web.xml          |    8 +
 .../src/main/webapp/WEB-INF/web.xml                |   61 +
 .../src/main/webapp/images}/jetty_banner.gif       |  Bin
 .../src/main/webapp/images/small_powered_by.gif    |  Bin 0 -> 4787 bytes
 .../test-jndi-webapp/src/main/webapp/index.html    |   46 +
 .../src/main/webapp/stylesheet.css                 |    7 +
 tests/test-webapps/test-mock-resources/pom.xml     |   90 +
 .../src/main/java/com/acme/MockDataSource.java     |  101 +
 .../src/main/java/com/acme/MockTransport.java      |   55 +
 .../main/java/com/acme/MockUserTransaction.java    |   82 +
 .../main/resources/META-INF/javaxmail.providers    |    1 +
 tests/test-webapps/test-proxy-webapp/pom.xml       |  104 +
 .../src/main/webapp/META-INF/MANIFEST.MF           |   21 +
 .../src/main/webapp/WEB-INF/jetty-web.xml          |    6 +
 .../src/main/webapp/WEB-INF/web.xml                |   49 +
 .../eclipse/jetty/TestTransparentProxyServer.java  |  140 +
 tests/test-webapps/test-servlet-spec/pom.xml       |   30 +
 .../test-container-initializer/pom.xml             |   69 +
 .../src/main/java/com/acme/initializer/Foo.java    |   33 +
 .../java/com/acme/initializer/FooInitializer.java  |  102 +
 .../javax.servlet.ServletContainerInitializer      |    1 +
 .../test-servlet-spec/test-spec-webapp/pom.xml     |  246 +
 .../test-spec-webapp/src/etc/realm.properties      |   21 +
 .../test-spec-webapp/src/main/assembly/config.xml  |   34 +
 .../src/main/assembly/web-bundle.xml               |   18 +
 .../main/java/com/acme/test/AnnotatedListener.java |  149 +
 .../main/java/com/acme/test/AnnotationTest.java    |  346 ++
 .../java/com/acme/test/AsyncListenerServlet.java   |  127 +
 .../src/main/java/com/acme/test/Bar.java           |   28 +
 .../src/main/java/com/acme/test/MultiPartTest.java |  120 +
 .../java/com/acme/test/RoleAnnotationTest.java     |   95 +
 .../main/java/com/acme/test/SecuredServlet.java    |   60 +
 .../src/main/java/com/acme/test/TestListener.java  |  235 +
 .../main/templates/annotations-context-header.xml  |   53 +
 .../src/main/templates/env-definitions.xml         |   19 +
 .../src/main/templates/plugin-context-header.xml   |   17 +
 .../src/main/webapp/WEB-INF/jetty-env.xml          |   17 +
 .../src/main/webapp/WEB-INF/jetty-web.xml          |    8 +
 .../src/main/webapp/WEB-INF/web.xml                |  104 +
 .../test-spec-webapp/src/main/webapp/authfail.html |   10 +
 .../src/main/webapp/images}/jetty_banner.gif       |  Bin
 .../src/main/webapp/images/small_powered_by.gif    |  Bin 0 -> 4787 bytes
 .../test-spec-webapp/src/main/webapp/index.html    |   66 +
 .../test-spec-webapp/src/main/webapp/login.html    |   19 +
 .../test-spec-webapp/src/main/webapp/logout.jsp    |   21 +
 .../src/main/webapp/stylesheet.css                 |    7 +
 .../src/main/webapp/stylesheet.css~                |    7 +
 .../test-spec-webapp/src/test/jetty-plugin-env.xml |   43 +
 .../test-servlet-spec/test-web-fragment/pom.xml    |   68 +
 .../java/com/acme/fragment/FragmentServlet.java    |   78 +
 .../META-INF/resources/fragmentA/index.html        |    8 +
 .../src/main/resources/META-INF/web-fragment.xml   |   39 +
 tests/test-webapps/test-webapp-rfc2616/pom.xml     |   22 +-
 3918 files changed, 316297 insertions(+), 166086 deletions(-)

diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..362c7b1
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,4 @@
+*.sh	eol=lf
+*.bat	eol=crlf
+*.txt	eol=lf
+*.js    eol=lf
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 815d296..52260df 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,14 +7,25 @@
 target/
 */src/main/java/META-INF/
 *.versionsBackup
+*.releaseBackup
+bin/
 
 # common junk
 *.log
-*.swp
 *.diff
 *.patch
+*.sw[a-p]
+*.bak
+*.backup
+*.debug
+*.dump
+
+# vim 
+.*.sw[a-p]
+*~
+~*
 
-# intellij
+# intellij / android studio
 *.iml
 *.ipr
 *.iws
@@ -30,12 +41,5 @@ target/
 # netbeans
 /nbproject
 
-# vim
-.*.sw[a-p]
-
 # merge tooling
 *.orig
-
-#maven
-*.versionsBackup
-*.releaseBackup
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 80b6f4b..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-language: java
-jdk:
-  - openjdk7
-  - oraclejdk7
diff --git a/BUILDING.txt b/BUILDING.txt
deleted file mode 100644
index ff16813..0000000
--- a/BUILDING.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-BUILDING JETTY
---------------
-
-Jetty is built with maven 3.x+
-
-  $ cd /my/work/directory/jetty-7
-  $ mvn clean install
-
-
diff --git a/LICENSE-APACHE-2.0.txt b/LICENSE-APACHE-2.0.txt
deleted file mode 100644
index d645695..0000000
--- a/LICENSE-APACHE-2.0.txt
+++ /dev/null
@@ -1,202 +0,0 @@
-
-                                 Apache License
-                           Version 2.0, January 2004
-                        http://www.apache.org/licenses/
-
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-   1. Definitions.
-
-      "License" shall mean the terms and conditions for use, reproduction,
-      and distribution as defined by Sections 1 through 9 of this document.
-
-      "Licensor" shall mean the copyright owner or entity authorized by
-      the copyright owner that is granting the License.
-
-      "Legal Entity" shall mean the union of the acting entity and all
-      other entities that control, are controlled by, or are under common
-      control with that entity. For the purposes of this definition,
-      "control" means (i) the power, direct or indirect, to cause the
-      direction or management of such entity, whether by contract or
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
-      outstanding shares, or (iii) beneficial ownership of such entity.
-
-      "You" (or "Your") shall mean an individual or Legal Entity
-      exercising permissions granted by this License.
-
-      "Source" form shall mean the preferred form for making modifications,
-      including but not limited to software source code, documentation
-      source, and configuration files.
-
-      "Object" form shall mean any form resulting from mechanical
-      transformation or translation of a Source form, including but
-      not limited to compiled object code, generated documentation,
-      and conversions to other media types.
-
-      "Work" shall mean the work of authorship, whether in Source or
-      Object form, made available under the License, as indicated by a
-      copyright notice that is included in or attached to the work
-      (an example is provided in the Appendix below).
-
-      "Derivative Works" shall mean any work, whether in Source or Object
-      form, that is based on (or derived from) the Work and for which the
-      editorial revisions, annotations, elaborations, or other modifications
-      represent, as a whole, an original work of authorship. For the purposes
-      of this License, Derivative Works shall not include works that remain
-      separable from, or merely link (or bind by name) to the interfaces of,
-      the Work and Derivative Works thereof.
-
-      "Contribution" shall mean any work of authorship, including
-      the original version of the Work and any modifications or additions
-      to that Work or Derivative Works thereof, that is intentionally
-      submitted to Licensor for inclusion in the Work by the copyright owner
-      or by an individual or Legal Entity authorized to submit on behalf of
-      the copyright owner. For the purposes of this definition, "submitted"
-      means any form of electronic, verbal, or written communication sent
-      to the Licensor or its representatives, including but not limited to
-      communication on electronic mailing lists, source code control systems,
-      and issue tracking systems that are managed by, or on behalf of, the
-      Licensor for the purpose of discussing and improving the Work, but
-      excluding communication that is conspicuously marked or otherwise
-      designated in writing by the copyright owner as "Not a Contribution."
-
-      "Contributor" shall mean Licensor and any individual or Legal Entity
-      on behalf of whom a Contribution has been received by Licensor and
-      subsequently incorporated within the Work.
-
-   2. Grant of Copyright License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      copyright license to reproduce, prepare Derivative Works of,
-      publicly display, publicly perform, sublicense, and distribute the
-      Work and such Derivative Works in Source or Object form.
-
-   3. Grant of Patent License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      (except as stated in this section) patent license to make, have made,
-      use, offer to sell, sell, import, and otherwise transfer the Work,
-      where such license applies only to those patent claims licensable
-      by such Contributor that are necessarily infringed by their
-      Contribution(s) alone or by combination of their Contribution(s)
-      with the Work to which such Contribution(s) was submitted. If You
-      institute patent litigation against any entity (including a
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
-      or a Contribution incorporated within the Work constitutes direct
-      or contributory patent infringement, then any patent licenses
-      granted to You under this License for that Work shall terminate
-      as of the date such litigation is filed.
-
-   4. Redistribution. You may reproduce and distribute copies of the
-      Work or Derivative Works thereof in any medium, with or without
-      modifications, and in Source or Object form, provided that You
-      meet the following conditions:
-
-      (a) You must give any other recipients of the Work or
-          Derivative Works a copy of this License; and
-
-      (b) You must cause any modified files to carry prominent notices
-          stating that You changed the files; and
-
-      (c) You must retain, in the Source form of any Derivative Works
-          that You distribute, all copyright, patent, trademark, and
-          attribution notices from the Source form of the Work,
-          excluding those notices that do not pertain to any part of
-          the Derivative Works; and
-
-      (d) If the Work includes a "NOTICE" text file as part of its
-          distribution, then any Derivative Works that You distribute must
-          include a readable copy of the attribution notices contained
-          within such NOTICE file, excluding those notices that do not
-          pertain to any part of the Derivative Works, in at least one
-          of the following places: within a NOTICE text file distributed
-          as part of the Derivative Works; within the Source form or
-          documentation, if provided along with the Derivative Works; or,
-          within a display generated by the Derivative Works, if and
-          wherever such third-party notices normally appear. The contents
-          of the NOTICE file are for informational purposes only and
-          do not modify the License. You may add Your own attribution
-          notices within Derivative Works that You distribute, alongside
-          or as an addendum to the NOTICE text from the Work, provided
-          that such additional attribution notices cannot be construed
-          as modifying the License.
-
-      You may add Your own copyright statement to Your modifications and
-      may provide additional or different license terms and conditions
-      for use, reproduction, or distribution of Your modifications, or
-      for any such Derivative Works as a whole, provided Your use,
-      reproduction, and distribution of the Work otherwise complies with
-      the conditions stated in this License.
-
-   5. Submission of Contributions. Unless You explicitly state otherwise,
-      any Contribution intentionally submitted for inclusion in the Work
-      by You to the Licensor shall be under the terms and conditions of
-      this License, without any additional terms or conditions.
-      Notwithstanding the above, nothing herein shall supersede or modify
-      the terms of any separate license agreement you may have executed
-      with Licensor regarding such Contributions.
-
-   6. Trademarks. This License does not grant permission to use the trade
-      names, trademarks, service marks, or product names of the Licensor,
-      except as required for reasonable and customary use in describing the
-      origin of the Work and reproducing the content of the NOTICE file.
-
-   7. Disclaimer of Warranty. Unless required by applicable law or
-      agreed to in writing, Licensor provides the Work (and each
-      Contributor provides its Contributions) on an "AS IS" BASIS,
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-      implied, including, without limitation, any warranties or conditions
-      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-      PARTICULAR PURPOSE. You are solely responsible for determining the
-      appropriateness of using or redistributing the Work and assume any
-      risks associated with Your exercise of permissions under this License.
-
-   8. Limitation of Liability. In no event and under no legal theory,
-      whether in tort (including negligence), contract, or otherwise,
-      unless required by applicable law (such as deliberate and grossly
-      negligent acts) or agreed to in writing, shall any Contributor be
-      liable to You for damages, including any direct, indirect, special,
-      incidental, or consequential damages of any character arising as a
-      result of this License or out of the use or inability to use the
-      Work (including but not limited to damages for loss of goodwill,
-      work stoppage, computer failure or malfunction, or any and all
-      other commercial damages or losses), even if such Contributor
-      has been advised of the possibility of such damages.
-
-   9. Accepting Warranty or Additional Liability. While redistributing
-      the Work or Derivative Works thereof, You may choose to offer,
-      and charge a fee for, acceptance of support, warranty, indemnity,
-      or other liability obligations and/or rights consistent with this
-      License. However, in accepting such obligations, You may act only
-      on Your own behalf and on Your sole responsibility, not on behalf
-      of any other Contributor, and only if You agree to indemnify,
-      defend, and hold each Contributor harmless for any liability
-      incurred by, or claims asserted against, such Contributor by reason
-      of your accepting any such warranty or additional liability.
-
-   END OF TERMS AND CONDITIONS
-
-   APPENDIX: How to apply the Apache License to your work.
-
-      To apply the Apache License to your work, attach the following
-      boilerplate notice, with the fields enclosed by brackets "[]"
-      replaced with your own identifying information. (Don't include
-      the brackets!)  The text should be enclosed in the appropriate
-      comment syntax for the file format. We also recommend that a
-      file or class name and description of purpose be included on the
-      same "printed page" as the copyright notice for easier
-      identification within third-party archives.
-
-   Copyright [yyyy] [name of copyright owner]
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
diff --git a/LICENSE-ECLIPSE-1.0.html b/LICENSE-ECLIPSE-1.0.html
deleted file mode 100644
index 9320c9f..0000000
--- a/LICENSE-ECLIPSE-1.0.html
+++ /dev/null
@@ -1,320 +0,0 @@
-<html xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:w="urn:schemas-microsoft-com:office:word" xmlns="http://www.w3.org/TR/REC-html40"><head>
-
-
-<meta http-equiv="Content-Type" content="text/html; charset=windows-1252">
-<meta name="ProgId" content="Word.Document">
-<meta name="Generator" content="Microsoft Word 9">
-<meta name="Originator" content="Microsoft Word 9">
-<link rel="File-List" href="http://www.eclipse.org/legal/Eclipse%20EPL%202003_11_10%20Final_files/filelist.xml">
-<title>Eclipse Public License - Version 1.0</title>
-<!--[if gte mso 9]><xml>
- <o:DocumentProperties>
-  <o:Revision>2</o:Revision>
-  <o:TotalTime>3</o:TotalTime>
-  <o:Created>2004-03-05T23:03:00Z</o:Created>
-  <o:LastSaved>2004-03-05T23:03:00Z</o:LastSaved>
-  <o:Pages>4</o:Pages>
-  <o:Words>1626</o:Words>
-  <o:Characters>9270</o:Characters>
-   <o:Lines>77</o:Lines>
-  <o:Paragraphs>18</o:Paragraphs>
-  <o:CharactersWithSpaces>11384</o:CharactersWithSpaces>
-  <o:Version>9.4402</o:Version>
- </o:DocumentProperties>
-</xml><![endif]--><!--[if gte mso 9]><xml>
- <w:WordDocument>
-  <w:TrackRevisions/>
- </w:WordDocument>
-</xml><![endif]-->
-<style>
-<!--
- /* Font Definitions */
- at font-face
-	{font-family:Tahoma;
-	panose-1:2 11 6 4 3 5 4 4 2 4;
-	mso-font-charset:0;
-	mso-generic-font-family:swiss;
-	mso-font-pitch:variable;
-	mso-font-signature:553679495 -2147483648 8 0 66047 0;}
- /* Style Definitions */
-p.MsoNormal, li.MsoNormal, div.MsoNormal
-	{mso-style-parent:"";
-	margin:0in;
-	margin-bottom:.0001pt;
-	mso-pagination:widow-orphan;
-	font-size:12.0pt;
-	font-family:"Times New Roman";
-	mso-fareast-font-family:"Times New Roman";}
-p
-	{margin-right:0in;
-	mso-margin-top-alt:auto;
-	mso-margin-bottom-alt:auto;
-	margin-left:0in;
-	mso-pagination:widow-orphan;
-	font-size:12.0pt;
-	font-family:"Times New Roman";
-	mso-fareast-font-family:"Times New Roman";}
-p.BalloonText, li.BalloonText, div.BalloonText
-	{mso-style-name:"Balloon Text";
-	margin:0in;
-	margin-bottom:.0001pt;
-	mso-pagination:widow-orphan;
-	font-size:8.0pt;
-	font-family:Tahoma;
-	mso-fareast-font-family:"Times New Roman";}
- at page Section1
-	{size:8.5in 11.0in;
-	margin:1.0in 1.25in 1.0in 1.25in;
-	mso-header-margin:.5in;
-	mso-footer-margin:.5in;
-	mso-paper-source:0;}
-div.Section1
-	{page:Section1;}
--->
-</style>
-</head><body style="" lang="EN-US">
-
-<div class="Section1">
-
-<p style="text-align: center;" align="center"><b>Eclipse Public License - v 1.0</b>
-</p>
-
-<p><span style="font-size: 10pt;">THE ACCOMPANYING PROGRAM IS PROVIDED UNDER
-THE TERMS OF THIS ECLIPSE PUBLIC LICENSE ("AGREEMENT"). ANY USE,
-REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE
-OF THIS AGREEMENT.</span> </p>
-
-<p><b><span style="font-size: 10pt;">1. DEFINITIONS</span></b> </p>
-
-<p><span style="font-size: 10pt;">"Contribution" means:</span> </p>
-
-<p class="MsoNormal" style="margin-left: 0.5in;"><span style="font-size: 10pt;">a)
-in the case of the initial Contributor, the initial code and documentation
-distributed under this Agreement, and<br clear="left">
-b) in the case of each subsequent Contributor:</span></p>
-
-<p class="MsoNormal" style="margin-left: 0.5in;"><span style="font-size: 10pt;">i)
-changes to the Program, and</span></p>
-
-<p class="MsoNormal" style="margin-left: 0.5in;"><span style="font-size: 10pt;">ii)
-additions to the Program;</span></p>
-
-<p class="MsoNormal" style="margin-left: 0.5in;"><span style="font-size: 10pt;">where
-such changes and/or additions to the Program originate from and are distributed
-by that particular Contributor. A Contribution 'originates' from a Contributor
-if it was added to the Program by such Contributor itself or anyone acting on
-such Contributor's behalf. Contributions do not include additions to the
-Program which: (i) are separate modules of software distributed in conjunction
-with the Program under their own license agreement, and (ii) are not derivative
-works of the Program. </span></p>
-
-<p><span style="font-size: 10pt;">"Contributor" means any person or
-entity that distributes the Program.</span> </p>
-
-<p><span style="font-size: 10pt;">"Licensed Patents " mean patent
-claims licensable by a Contributor which are necessarily infringed by the use
-or sale of its Contribution alone or when combined with the Program. </span></p>
-
-<p><span style="font-size: 10pt;">"Program" means the Contributions
-distributed in accordance with this Agreement.</span> </p>
-
-<p><span style="font-size: 10pt;">"Recipient" means anyone who
-receives the Program under this Agreement, including all Contributors.</span> </p>
-
-<p><b><span style="font-size: 10pt;">2. GRANT OF RIGHTS</span></b> </p>
-
-<p class="MsoNormal" style="margin-left: 0.5in;"><span style="font-size: 10pt;">a)
-Subject to the terms of this Agreement, each Contributor hereby grants Recipient
-a non-exclusive, worldwide, royalty-free copyright license to<span style="color: red;"> </span>reproduce, prepare derivative works of, publicly
-display, publicly perform, distribute and sublicense the Contribution of such
-Contributor, if any, and such derivative works, in source code and object code
-form.</span></p>
-
-<p class="MsoNormal" style="margin-left: 0.5in;"><span style="font-size: 10pt;">b)
-Subject to the terms of this Agreement, each Contributor hereby grants
-Recipient a non-exclusive, worldwide,<span style="color: green;"> </span>royalty-free
-patent license under Licensed Patents to make, use, sell, offer to sell, import
-and otherwise transfer the Contribution of such Contributor, if any, in source
-code and object code form. This patent license shall apply to the combination
-of the Contribution and the Program if, at the time the Contribution is added
-by the Contributor, such addition of the Contribution causes such combination
-to be covered by the Licensed Patents. The patent license shall not apply to
-any other combinations which include the Contribution. No hardware per se is
-licensed hereunder. </span></p>
-
-<p class="MsoNormal" style="margin-left: 0.5in;"><span style="font-size: 10pt;">c)
-Recipient understands that although each Contributor grants the licenses to its
-Contributions set forth herein, no assurances are provided by any Contributor
-that the Program does not infringe the patent or other intellectual property
-rights of any other entity. Each Contributor disclaims any liability to Recipient
-for claims brought by any other entity based on infringement of intellectual
-property rights or otherwise. As a condition to exercising the rights and
-licenses granted hereunder, each Recipient hereby assumes sole responsibility
-to secure any other intellectual property rights needed, if any. For example,
-if a third party patent license is required to allow Recipient to distribute
-the Program, it is Recipient's responsibility to acquire that license before
-distributing the Program.</span></p>
-
-<p class="MsoNormal" style="margin-left: 0.5in;"><span style="font-size: 10pt;">d)
-Each Contributor represents that to its knowledge it has sufficient copyright
-rights in its Contribution, if any, to grant the copyright license set forth in
-this Agreement. </span></p>
-
-<p><b><span style="font-size: 10pt;">3. REQUIREMENTS</span></b> </p>
-
-<p><span style="font-size: 10pt;">A Contributor may choose to distribute the
-Program in object code form under its own license agreement, provided that:</span>
-</p>
-
-<p class="MsoNormal" style="margin-left: 0.5in;"><span style="font-size: 10pt;">a)
-it complies with the terms and conditions of this Agreement; and</span></p>
-
-<p class="MsoNormal" style="margin-left: 0.5in;"><span style="font-size: 10pt;">b)
-its license agreement:</span></p>
-
-<p class="MsoNormal" style="margin-left: 0.5in;"><span style="font-size: 10pt;">i)
-effectively disclaims on behalf of all Contributors all warranties and
-conditions, express and implied, including warranties or conditions of title
-and non-infringement, and implied warranties or conditions of merchantability
-and fitness for a particular purpose; </span></p>
-
-<p class="MsoNormal" style="margin-left: 0.5in;"><span style="font-size: 10pt;">ii)
-effectively excludes on behalf of all Contributors all liability for damages,
-including direct, indirect, special, incidental and consequential damages, such
-as lost profits; </span></p>
-
-<p class="MsoNormal" style="margin-left: 0.5in;"><span style="font-size: 10pt;">iii)
-states that any provisions which differ from this Agreement are offered by that
-Contributor alone and not by any other party; and</span></p>
-
-<p class="MsoNormal" style="margin-left: 0.5in;"><span style="font-size: 10pt;">iv)
-states that source code for the Program is available from such Contributor, and
-informs licensees how to obtain it in a reasonable manner on or through a
-medium customarily used for software exchange.<span style="color: blue;"> </span></span></p>
-
-<p><span style="font-size: 10pt;">When the Program is made available in source
-code form:</span> </p>
-
-<p class="MsoNormal" style="margin-left: 0.5in;"><span style="font-size: 10pt;">a)
-it must be made available under this Agreement; and </span></p>
-
-<p class="MsoNormal" style="margin-left: 0.5in;"><span style="font-size: 10pt;">b) a
-copy of this Agreement must be included with each copy of the Program. </span></p>
-
-<p><span style="font-size: 10pt;">Contributors may not remove or alter any
-copyright notices contained within the Program. </span></p>
-
-<p><span style="font-size: 10pt;">Each Contributor must identify itself as the
-originator of its Contribution, if any, in a manner that reasonably allows
-subsequent Recipients to identify the originator of the Contribution. </span></p>
-
-<p><b><span style="font-size: 10pt;">4. COMMERCIAL DISTRIBUTION</span></b> </p>
-
-<p><span style="font-size: 10pt;">Commercial distributors of software may
-accept certain responsibilities with respect to end users, business partners
-and the like. While this license is intended to facilitate the commercial use
-of the Program, the Contributor who includes the Program in a commercial
-product offering should do so in a manner which does not create potential
-liability for other Contributors. Therefore, if a Contributor includes the
-Program in a commercial product offering, such Contributor ("Commercial
-Contributor") hereby agrees to defend and indemnify every other
-Contributor ("Indemnified Contributor") against any losses, damages and
-costs (collectively "Losses") arising from claims, lawsuits and other
-legal actions brought by a third party against the Indemnified Contributor to
-the extent caused by the acts or omissions of such Commercial Contributor in
-connection with its distribution of the Program in a commercial product
-offering. The obligations in this section do not apply to any claims or Losses
-relating to any actual or alleged intellectual property infringement. In order
-to qualify, an Indemnified Contributor must: a) promptly notify the Commercial
-Contributor in writing of such claim, and b) allow the Commercial Contributor
-to control, and cooperate with the Commercial Contributor in, the defense and
-any related settlement negotiations. The Indemnified Contributor may participate
-in any such claim at its own expense.</span> </p>
-
-<p><span style="font-size: 10pt;">For example, a Contributor might include the
-Program in a commercial product offering, Product X. That Contributor is then a
-Commercial Contributor. If that Commercial Contributor then makes performance
-claims, or offers warranties related to Product X, those performance claims and
-warranties are such Commercial Contributor's responsibility alone. Under this
-section, the Commercial Contributor would have to defend claims against the
-other Contributors related to those performance claims and warranties, and if a
-court requires any other Contributor to pay any damages as a result, the
-Commercial Contributor must pay those damages.</span> </p>
-
-<p><b><span style="font-size: 10pt;">5. NO WARRANTY</span></b> </p>
-
-<p><span style="font-size: 10pt;">EXCEPT AS EXPRESSLY SET FORTH IN THIS
-AGREEMENT, THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT
-WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING,
-WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT,
-MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely
-responsible for determining the appropriateness of using and distributing the
-Program and assumes all risks associated with its exercise of rights under this
-Agreement , including but not limited to the risks and costs of program errors,
-compliance with applicable laws, damage to or loss of data, programs or
-equipment, and unavailability or interruption of operations. </span></p>
-
-<p><b><span style="font-size: 10pt;">6. DISCLAIMER OF LIABILITY</span></b> </p>
-
-<p><span style="font-size: 10pt;">EXCEPT AS EXPRESSLY SET FORTH IN THIS
-AGREEMENT, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR
-ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
-(INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY
-OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
-NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF
-THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF
-THE POSSIBILITY OF SUCH DAMAGES.</span> </p>
-
-<p><b><span style="font-size: 10pt;">7. GENERAL</span></b> </p>
-
-<p><span style="font-size: 10pt;">If any provision of this Agreement is invalid
-or unenforceable under applicable law, it shall not affect the validity or
-enforceability of the remainder of the terms of this Agreement, and without
-further action by the parties hereto, such provision shall be reformed to the
-minimum extent necessary to make such provision valid and enforceable.</span> </p>
-
-<p><span style="font-size: 10pt;">If Recipient institutes patent litigation
-against any entity (including a cross-claim or counterclaim in a lawsuit)
-alleging that the Program itself (excluding combinations of the Program with
-other software or hardware) infringes such Recipient's patent(s), then such
-Recipient's rights granted under Section 2(b) shall terminate as of the date
-such litigation is filed. </span></p>
-
-<p><span style="font-size: 10pt;">All Recipient's rights under this Agreement
-shall terminate if it fails to comply with any of the material terms or
-conditions of this Agreement and does not cure such failure in a reasonable
-period of time after becoming aware of such noncompliance. If all Recipient's
-rights under this Agreement terminate, Recipient agrees to cease use and
-distribution of the Program as soon as reasonably practicable. However,
-Recipient's obligations under this Agreement and any licenses granted by
-Recipient relating to the Program shall continue and survive. </span></p>
-
-<p><span style="font-size: 10pt;">Everyone is permitted to copy and distribute
-copies of this Agreement, but in order to avoid inconsistency the Agreement is
-copyrighted and may only be modified in the following manner. The Agreement
-Steward reserves the right to publish new versions (including revisions) of
-this Agreement from time to time. No one other than the Agreement Steward has
-the right to modify this Agreement. The Eclipse Foundation is the initial
-Agreement Steward. The Eclipse Foundation may assign the responsibility to
-serve as the Agreement Steward to a suitable separate entity. Each new version
-of the Agreement will be given a distinguishing version number. The Program
-(including Contributions) may always be distributed subject to the version of
-the Agreement under which it was received. In addition, after a new version of
-the Agreement is published, Contributor may elect to distribute the Program
-(including its Contributions) under the new version. Except as expressly stated
-in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to
-the intellectual property of any Contributor under this Agreement, whether
-expressly, by implication, estoppel or otherwise. All rights in the Program not
-expressly granted under this Agreement are reserved.</span> </p>
-
-<p><span style="font-size: 10pt;">This Agreement is governed by the laws of the
-State of New York and the intellectual property laws of the United States of
-America. No party to this Agreement will bring a legal action under this
-Agreement more than one year after the cause of action arose. Each party waives
-its rights to a jury trial in any resulting litigation.</span> </p>
-
-<p class="MsoNormal"><!--[if !supportEmptyParas]--> <!--[endif]--><o:p></o:p></p>
-
-</div>
-
-</body></html>
\ No newline at end of file
diff --git a/LICENSE-eplv10-aslv20.html b/LICENSE-eplv10-aslv20.html
new file mode 100644
index 0000000..48addaa
--- /dev/null
+++ b/LICENSE-eplv10-aslv20.html
@@ -0,0 +1,576 @@
+<html xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:w="urn:schemas-microsoft-com:office:word" xmlns="http://www.w3.org/TR/REC-html40"><head>
+
+
+<meta http-equiv="Content-Type" content="text/html; charset=windows-1252">
+<meta name="ProgId" content="Word.Document">
+<meta name="Generator" content="Microsoft Word 9">
+<meta name="Originator" content="Microsoft Word 9">
+<link rel="File-List" href="http://www.eclipse.org/legal/Eclipse%20EPL%202003_11_10%20Final_files/filelist.xml">
+<title>Eclipse Public License - Version 1.0 / Apache License - Version 2.0</title>
+<!--[if gte mso 9]><xml>
+ <o:DocumentProperties>
+  <o:Revision>2</o:Revision>
+  <o:TotalTime>3</o:TotalTime>
+  <o:Created>2004-03-05T23:03:00Z</o:Created>
+  <o:LastSaved>2004-03-05T23:03:00Z</o:LastSaved>
+  <o:Pages>4</o:Pages>
+  <o:Words>1626</o:Words>
+  <o:Characters>9270</o:Characters>
+   <o:Lines>77</o:Lines>
+  <o:Paragraphs>18</o:Paragraphs>
+  <o:CharactersWithSpaces>11384</o:CharactersWithSpaces>
+  <o:Version>9.4402</o:Version>
+ </o:DocumentProperties>
+</xml><![endif]--><!--[if gte mso 9]><xml>
+ <w:WordDocument>
+  <w:TrackRevisions/>
+ </w:WordDocument>
+</xml><![endif]-->
+<style>
+<!--
+ /* Font Definitions */
+ at font-face
+	{font-family:Tahoma;
+	panose-1:2 11 6 4 3 5 4 4 2 4;
+	mso-font-charset:0;
+	mso-generic-font-family:swiss;
+	mso-font-pitch:variable;
+	mso-font-signature:553679495 -2147483648 8 0 66047 0;}
+ /* Style Definitions */
+p.MsoNormal, li.MsoNormal, div.MsoNormal
+	{mso-style-parent:"";
+	margin:0in;
+	margin-bottom:.0001pt;
+	mso-pagination:widow-orphan;
+	font-size:12.0pt;
+	font-family:"Times New Roman";
+	mso-fareast-font-family:"Times New Roman";}
+p
+	{margin-right:0in;
+	mso-margin-top-alt:auto;
+	mso-margin-bottom-alt:auto;
+	margin-left:0in;
+	mso-pagination:widow-orphan;
+	font-size:12.0pt;
+	font-family:"Times New Roman";
+	mso-fareast-font-family:"Times New Roman";}
+p.BalloonText, li.BalloonText, div.BalloonText
+	{mso-style-name:"Balloon Text";
+	margin:0in;
+	margin-bottom:.0001pt;
+	mso-pagination:widow-orphan;
+	font-size:8.0pt;
+	font-family:Tahoma;
+	mso-fareast-font-family:"Times New Roman";}
+ at page Section1
+	{size:8.5in 11.0in;
+	margin:1.0in 1.25in 1.0in 1.25in;
+	mso-header-margin:.5in;
+	mso-footer-margin:.5in;
+	mso-paper-source:0;}
+div.Section1
+	{page:Section1;}
+-->
+</style>
+</head><body style="" lang="EN-US">
+
+<div class="Section1">
+
+<p style="text-align: center;" align="center"><b>Eclipse Public License - v 1.0</b>
+</p>
+
+<p><span style="font-size: 10pt;">THE ACCOMPANYING PROGRAM IS PROVIDED UNDER
+THE TERMS OF THIS ECLIPSE PUBLIC LICENSE ("AGREEMENT"). ANY USE,
+REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE
+OF THIS AGREEMENT.</span> </p>
+
+<p><b><span style="font-size: 10pt;">1. DEFINITIONS</span></b> </p>
+
+<p><span style="font-size: 10pt;">"Contribution" means:</span> </p>
+
+<p class="MsoNormal" style="margin-left: 0.5in;"><span style="font-size: 10pt;">a)
+in the case of the initial Contributor, the initial code and documentation
+distributed under this Agreement, and<br clear="left">
+b) in the case of each subsequent Contributor:</span></p>
+
+<p class="MsoNormal" style="margin-left: 0.5in;"><span style="font-size: 10pt;">i)
+changes to the Program, and</span></p>
+
+<p class="MsoNormal" style="margin-left: 0.5in;"><span style="font-size: 10pt;">ii)
+additions to the Program;</span></p>
+
+<p class="MsoNormal" style="margin-left: 0.5in;"><span style="font-size: 10pt;">where
+such changes and/or additions to the Program originate from and are distributed
+by that particular Contributor. A Contribution 'originates' from a Contributor
+if it was added to the Program by such Contributor itself or anyone acting on
+such Contributor's behalf. Contributions do not include additions to the
+Program which: (i) are separate modules of software distributed in conjunction
+with the Program under their own license agreement, and (ii) are not derivative
+works of the Program. </span></p>
+
+<p><span style="font-size: 10pt;">"Contributor" means any person or
+entity that distributes the Program.</span> </p>
+
+<p><span style="font-size: 10pt;">"Licensed Patents " mean patent
+claims licensable by a Contributor which are necessarily infringed by the use
+or sale of its Contribution alone or when combined with the Program. </span></p>
+
+<p><span style="font-size: 10pt;">"Program" means the Contributions
+distributed in accordance with this Agreement.</span> </p>
+
+<p><span style="font-size: 10pt;">"Recipient" means anyone who
+receives the Program under this Agreement, including all Contributors.</span> </p>
+
+<p><b><span style="font-size: 10pt;">2. GRANT OF RIGHTS</span></b> </p>
+
+<p class="MsoNormal" style="margin-left: 0.5in;"><span style="font-size: 10pt;">a)
+Subject to the terms of this Agreement, each Contributor hereby grants Recipient
+a non-exclusive, worldwide, royalty-free copyright license to<span style="color: red;"> </span>reproduce, prepare derivative works of, publicly
+display, publicly perform, distribute and sublicense the Contribution of such
+Contributor, if any, and such derivative works, in source code and object code
+form.</span></p>
+
+<p class="MsoNormal" style="margin-left: 0.5in;"><span style="font-size: 10pt;">b)
+Subject to the terms of this Agreement, each Contributor hereby grants
+Recipient a non-exclusive, worldwide,<span style="color: green;"> </span>royalty-free
+patent license under Licensed Patents to make, use, sell, offer to sell, import
+and otherwise transfer the Contribution of such Contributor, if any, in source
+code and object code form. This patent license shall apply to the combination
+of the Contribution and the Program if, at the time the Contribution is added
+by the Contributor, such addition of the Contribution causes such combination
+to be covered by the Licensed Patents. The patent license shall not apply to
+any other combinations which include the Contribution. No hardware per se is
+licensed hereunder. </span></p>
+
+<p class="MsoNormal" style="margin-left: 0.5in;"><span style="font-size: 10pt;">c)
+Recipient understands that although each Contributor grants the licenses to its
+Contributions set forth herein, no assurances are provided by any Contributor
+that the Program does not infringe the patent or other intellectual property
+rights of any other entity. Each Contributor disclaims any liability to Recipient
+for claims brought by any other entity based on infringement of intellectual
+property rights or otherwise. As a condition to exercising the rights and
+licenses granted hereunder, each Recipient hereby assumes sole responsibility
+to secure any other intellectual property rights needed, if any. For example,
+if a third party patent license is required to allow Recipient to distribute
+the Program, it is Recipient's responsibility to acquire that license before
+distributing the Program.</span></p>
+
+<p class="MsoNormal" style="margin-left: 0.5in;"><span style="font-size: 10pt;">d)
+Each Contributor represents that to its knowledge it has sufficient copyright
+rights in its Contribution, if any, to grant the copyright license set forth in
+this Agreement. </span></p>
+
+<p><b><span style="font-size: 10pt;">3. REQUIREMENTS</span></b> </p>
+
+<p><span style="font-size: 10pt;">A Contributor may choose to distribute the
+Program in object code form under its own license agreement, provided that:</span>
+</p>
+
+<p class="MsoNormal" style="margin-left: 0.5in;"><span style="font-size: 10pt;">a)
+it complies with the terms and conditions of this Agreement; and</span></p>
+
+<p class="MsoNormal" style="margin-left: 0.5in;"><span style="font-size: 10pt;">b)
+its license agreement:</span></p>
+
+<p class="MsoNormal" style="margin-left: 0.5in;"><span style="font-size: 10pt;">i)
+effectively disclaims on behalf of all Contributors all warranties and
+conditions, express and implied, including warranties or conditions of title
+and non-infringement, and implied warranties or conditions of merchantability
+and fitness for a particular purpose; </span></p>
+
+<p class="MsoNormal" style="margin-left: 0.5in;"><span style="font-size: 10pt;">ii)
+effectively excludes on behalf of all Contributors all liability for damages,
+including direct, indirect, special, incidental and consequential damages, such
+as lost profits; </span></p>
+
+<p class="MsoNormal" style="margin-left: 0.5in;"><span style="font-size: 10pt;">iii)
+states that any provisions which differ from this Agreement are offered by that
+Contributor alone and not by any other party; and</span></p>
+
+<p class="MsoNormal" style="margin-left: 0.5in;"><span style="font-size: 10pt;">iv)
+states that source code for the Program is available from such Contributor, and
+informs licensees how to obtain it in a reasonable manner on or through a
+medium customarily used for software exchange.<span style="color: blue;"> </span></span></p>
+
+<p><span style="font-size: 10pt;">When the Program is made available in source
+code form:</span> </p>
+
+<p class="MsoNormal" style="margin-left: 0.5in;"><span style="font-size: 10pt;">a)
+it must be made available under this Agreement; and </span></p>
+
+<p class="MsoNormal" style="margin-left: 0.5in;"><span style="font-size: 10pt;">b) a
+copy of this Agreement must be included with each copy of the Program. </span></p>
+
+<p><span style="font-size: 10pt;">Contributors may not remove or alter any
+copyright notices contained within the Program. </span></p>
+
+<p><span style="font-size: 10pt;">Each Contributor must identify itself as the
+originator of its Contribution, if any, in a manner that reasonably allows
+subsequent Recipients to identify the originator of the Contribution. </span></p>
+
+<p><b><span style="font-size: 10pt;">4. COMMERCIAL DISTRIBUTION</span></b> </p>
+
+<p><span style="font-size: 10pt;">Commercial distributors of software may
+accept certain responsibilities with respect to end users, business partners
+and the like. While this license is intended to facilitate the commercial use
+of the Program, the Contributor who includes the Program in a commercial
+product offering should do so in a manner which does not create potential
+liability for other Contributors. Therefore, if a Contributor includes the
+Program in a commercial product offering, such Contributor ("Commercial
+Contributor") hereby agrees to defend and indemnify every other
+Contributor ("Indemnified Contributor") against any losses, damages and
+costs (collectively "Losses") arising from claims, lawsuits and other
+legal actions brought by a third party against the Indemnified Contributor to
+the extent caused by the acts or omissions of such Commercial Contributor in
+connection with its distribution of the Program in a commercial product
+offering. The obligations in this section do not apply to any claims or Losses
+relating to any actual or alleged intellectual property infringement. In order
+to qualify, an Indemnified Contributor must: a) promptly notify the Commercial
+Contributor in writing of such claim, and b) allow the Commercial Contributor
+to control, and cooperate with the Commercial Contributor in, the defense and
+any related settlement negotiations. The Indemnified Contributor may participate
+in any such claim at its own expense.</span> </p>
+
+<p><span style="font-size: 10pt;">For example, a Contributor might include the
+Program in a commercial product offering, Product X. That Contributor is then a
+Commercial Contributor. If that Commercial Contributor then makes performance
+claims, or offers warranties related to Product X, those performance claims and
+warranties are such Commercial Contributor's responsibility alone. Under this
+section, the Commercial Contributor would have to defend claims against the
+other Contributors related to those performance claims and warranties, and if a
+court requires any other Contributor to pay any damages as a result, the
+Commercial Contributor must pay those damages.</span> </p>
+
+<p><b><span style="font-size: 10pt;">5. NO WARRANTY</span></b> </p>
+
+<p><span style="font-size: 10pt;">EXCEPT AS EXPRESSLY SET FORTH IN THIS
+AGREEMENT, THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING,
+WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT,
+MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely
+responsible for determining the appropriateness of using and distributing the
+Program and assumes all risks associated with its exercise of rights under this
+Agreement , including but not limited to the risks and costs of program errors,
+compliance with applicable laws, damage to or loss of data, programs or
+equipment, and unavailability or interruption of operations. </span></p>
+
+<p><b><span style="font-size: 10pt;">6. DISCLAIMER OF LIABILITY</span></b> </p>
+
+<p><span style="font-size: 10pt;">EXCEPT AS EXPRESSLY SET FORTH IN THIS
+AGREEMENT, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY
+OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF
+THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF
+THE POSSIBILITY OF SUCH DAMAGES.</span> </p>
+
+<p><b><span style="font-size: 10pt;">7. GENERAL</span></b> </p>
+
+<p><span style="font-size: 10pt;">If any provision of this Agreement is invalid
+or unenforceable under applicable law, it shall not affect the validity or
+enforceability of the remainder of the terms of this Agreement, and without
+further action by the parties hereto, such provision shall be reformed to the
+minimum extent necessary to make such provision valid and enforceable.</span> </p>
+
+<p><span style="font-size: 10pt;">If Recipient institutes patent litigation
+against any entity (including a cross-claim or counterclaim in a lawsuit)
+alleging that the Program itself (excluding combinations of the Program with
+other software or hardware) infringes such Recipient's patent(s), then such
+Recipient's rights granted under Section 2(b) shall terminate as of the date
+such litigation is filed. </span></p>
+
+<p><span style="font-size: 10pt;">All Recipient's rights under this Agreement
+shall terminate if it fails to comply with any of the material terms or
+conditions of this Agreement and does not cure such failure in a reasonable
+period of time after becoming aware of such noncompliance. If all Recipient's
+rights under this Agreement terminate, Recipient agrees to cease use and
+distribution of the Program as soon as reasonably practicable. However,
+Recipient's obligations under this Agreement and any licenses granted by
+Recipient relating to the Program shall continue and survive. </span></p>
+
+<p><span style="font-size: 10pt;">Everyone is permitted to copy and distribute
+copies of this Agreement, but in order to avoid inconsistency the Agreement is
+copyrighted and may only be modified in the following manner. The Agreement
+Steward reserves the right to publish new versions (including revisions) of
+this Agreement from time to time. No one other than the Agreement Steward has
+the right to modify this Agreement. The Eclipse Foundation is the initial
+Agreement Steward. The Eclipse Foundation may assign the responsibility to
+serve as the Agreement Steward to a suitable separate entity. Each new version
+of the Agreement will be given a distinguishing version number. The Program
+(including Contributions) may always be distributed subject to the version of
+the Agreement under which it was received. In addition, after a new version of
+the Agreement is published, Contributor may elect to distribute the Program
+(including its Contributions) under the new version. Except as expressly stated
+in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to
+the intellectual property of any Contributor under this Agreement, whether
+expressly, by implication, estoppel or otherwise. All rights in the Program not
+expressly granted under this Agreement are reserved.</span> </p>
+
+<p><span style="font-size: 10pt;">This Agreement is governed by the laws of the
+State of New York and the intellectual property laws of the United States of
+America. No party to this Agreement will bring a legal action under this
+Agreement more than one year after the cause of action arose. Each party waives
+its rights to a jury trial in any resulting litigation.</span> </p>
+
+<p class="MsoNormal"><!--[if !supportEmptyParas]--> <!--[endif]--><o:p></o:p></p>
+
+</div>
+
+<div class="Section2">
+
+<p style="text-align: center;" align="center"><b>Apache License</b></br>
+Version 2.0, January 2004<br/>
+http://www.apache.org/licenses/<br/>
+</p>
+<p><span style="font-size: 10pt;">TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION</spam></p>
+<p><b><span style="font-size: 10pt;">1. Definitions.</span></b> </p>
+<p><span style="font-size: 10pt;">
+    "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.</span></p>
+<p><span style="font-size: 10pt;">
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.</span></p>
+<p><span style="font-size: 10pt;">
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.</span></p>
+<p><span style="font-size: 10pt;">
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.</span></p>
+<p><span style="font-size: 10pt;">
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.</span></p>
+<p><span style="font-size: 10pt;">
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.</span></p>
+<p><span style="font-size: 10pt;">
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).</span></p>
+<p><span style="font-size: 10pt;">
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.</span></p>
+<p><span style="font-size: 10pt;">
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."</span></p>
+<p><span style="font-size: 10pt;">
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.</span></p>
+
+<p><b><span style="font-size: 10pt;">2. Grant of Copyright License.</span></b> </p>
+<p><span style="font-size: 10pt;">
+Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.</span></p>
+      
+      
+         <p><b><span style="font-size: 10pt;">3. Grant of Patent License.</span></b><p>
+
+<p><span style="font-size: 10pt;">         
+         Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+</span></p>
+<p><b><span style="font-size: 10pt;">
+   4. Redistribution. 
+      </span></b></p>
+   
+   <p><span style="font-size: 10pt;">You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:</span><p>
+<ul>
+<li><span style="font-size: 10pt;">
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and</span><p>
+</li>
+<li><span style="font-size: 10pt;">
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and</span><p>
+</li>
+</li><span style="font-size: 10pt;">
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and</span><p>
+</li>
+<li><span style="font-size: 10pt;">
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.</span><p>
+</li>
+</ul>
+</span></p>
+
+<p><span style="font-size: 10pt;">
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+</span></p>
+<p><b><span style="font-size: 10pt;">
+   5. Submission of Contributions. 
+      </span></b></p>
+   
+   <p><span style="font-size: 10pt;">Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+</span></p>
+<p><b><span style="font-size: 10pt;">
+   6. Trademarks. 
+      </span></b></p>
+   
+   <p><span style="font-size: 10pt;">This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+</span></p>
+<p><b><span style="font-size: 10pt;">
+   7. Disclaimer of Warranty. 
+      </span></b></p>
+   
+   <p><span style="font-size: 10pt;">Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+</span></p>
+<p><b><span style="font-size: 10pt;">
+   8. Limitation of Liability. 
+      </span></b></p>
+   
+   <p><span style="font-size: 10pt;">In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+</span></p>
+<p><b><span style="font-size: 10pt;">
+   9. Accepting Warranty or Additional Liability. 
+   </span></b></p>
+   <p><span style="font-size: 10pt;">While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+</span></p>
+
+<p><span style="font-size: 10pt;">
+   END OF TERMS AND CONDITIONS
+</span></p>
+
+<p><span style="font-size: 10pt;">
+   APPENDIX: How to apply the Apache License to your work.
+</span></p>
+
+<p><span style="font-size: 10pt;">
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+</span></p>
+
+<p><span style="font-size: 10pt;">
+   Copyright [yyyy] [name of copyright owner]
+</span></p>
+
+<p><span style="font-size: 10pt;">
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+</span></p>
+
+<p><span style="font-size: 10pt;">
+       http://www.apache.org/licenses/LICENSE-2.0
+</span></p>
+
+<p><span style="font-size: 10pt;">
+   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.
+</span></p>
+
+</div>
+</body></html>
\ No newline at end of file
diff --git a/NOTICE.txt b/NOTICE.txt
index 0555240..454c3d4 100644
--- a/NOTICE.txt
+++ b/NOTICE.txt
@@ -18,9 +18,63 @@ Jetty is dual licensed under both
 
 Jetty may be distributed under either license.
 
-The javax.servlet package used was sourced from the Apache
-Software Foundation and is distributed under the apache 2.0
-license.
+
+------
+Oracle
+
+The following artifacts are CDDL + GPLv2 with classpath exception.
+
+https://glassfish.dev.java.net/nonav/public/CDDL+GPL.html
+
+javax.servlet:javax.servlet-api
+javax.servlet.jsp:javax.servlet.jsp-api
+org.glassfish.web:javax.servlet.jsp
+org.glassfish.web:javax.servlet.jsp
+org.glassfish.web:javax.servlet.jsp.jstl
+org.eclipse.jetty.orbit.javax.servlet.jsp.jstl
+
+
+------
+Apache
+
+The following artifacts are ASL2 licensed.
+
+org.apache.taglibs:taglibs-standard-spec
+org.apache.taglibs:taglibs-standard-impl
+
+
+------
+MortBay
+
+The following artifacts are ASL2 licensed.  Based on selected classes from 
+following Apache Tomcat jars, all ASL2 licensed.
+
+org.mortbay.jasper:apache-jsp
+  org.apache.tomcat:tomcat-jasper
+  org.apache.tomcat:tomcat-juli
+  org.apache.tomcat:tomcat-jsp-api
+  org.apache.tomcat:tomcat-el-api
+  org.apache.tomcat:tomcat-jasper-el
+  org.apache.tomcat:tomcat-api
+  org.apache.tomcat:tomcat-util-scan
+  org.apache.tomcat:tomcat-util
+
+org.mortbay.jasper:apache-el
+  org.apache.tomcat:tomcat-jasper-el
+  org.apache.tomcat:tomcat-el-api
+
+
+------
+Mortbay
+
+The following artifacts are CDDL + GPLv2 with classpath exception.
+
+https://glassfish.dev.java.net/nonav/public/CDDL+GPL.html
+
+org.eclipse.jetty.toolchain:jetty-schemas
+
+------
+Assorted
 
 The UnixCrypt.java code implements the one way cryptography used by
 Unix systems for simple password protection.  Copyright 1996 Aki Yoshida,
@@ -28,4 +82,3 @@ modified April 2001  by Iris Van den Broeke, Daniel Deville.
 Permission to use, copy, modify and distribute UnixCrypt
 for non-commercial or commercial purposes and without fee is
 granted provided that the copyright notice appears in all copies.
-
diff --git a/README.TXT b/README.TXT
new file mode 100644
index 0000000..9157b3e
--- /dev/null
+++ b/README.TXT
@@ -0,0 +1,21 @@
+This is a source checkout of the Jetty webserver.
+
+To build, use:
+
+  mvn clean install
+
+The jetty distribution will be built in
+
+  jetty-distribution/target/distribution
+
+The first build may take a long time as Maven downloads all the
+dependencies.
+
+The tests do a lot of stress testing, and on some machines it is
+necessary to set the file descriptor limit to greater than 2048
+for the tests to all pass successfully.
+
+Bypass tests by building with -Dmaven.test.skip=true but note
+that this will not produce some test jars that are leveraged
+in other places in the build.
+
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..2e9bf5c
--- /dev/null
+++ b/README.md
@@ -0,0 +1,24 @@
+Project description
+============
+
+Jetty is a lightweight highly scalable java based web server and servlet engine.
+Our goal is to support web protocols like HTTP, SPDY and WebSocket in a high
+volume low latency way that provides maximum performance while retaining the ease
+of use and compatibility with years of servlet development. Jetty is a modern
+fully async web server that has a long history as a component oriented technology
+easily embedded into applications while still offering a solid traditional
+distribution for webapp deployment.
+
+- [https://projects.eclipse.org/projects/rt.jetty](https://projects.eclipse.org/projects/rt.jetty)
+
+Documentation
+============
+
+Project documentation is located on our Eclipse website.
+
+- [http://www.eclipse.org/jetty/documentation](http://www.eclipse.org/jetty/documentation)
+
+Professional Services
+============
+
+Expert advice and production support are available through [http://webtide.com](Webtide.com).
diff --git a/README.txt b/README.txt
deleted file mode 100644
index 412eb3a..0000000
--- a/README.txt
+++ /dev/null
@@ -1,22 +0,0 @@
-
-This is a source checkout of the Jetty webserver.
-
-To build, use:
-
-  mvn clean install
-
-The jetty distribution will be built in
-
-  jetty-distribution/target/distribution
-
-The first build may take a long time as Maven downloads all the
-dependencies.
-
-The tests do a lot of stress testing, and on some machines it is
-necessary to set the file descriptor limit to greater than 2048
-for the tests to all pass successfully.
-
-Bypass tests by building with -Dmaven.test.skip=true but note
-that this will not produce some test jars that are leveraged
-in other places in the build.
-
diff --git a/VERSION.txt b/VERSION.txt
index b6e8601..23d75df 100644
--- a/VERSION.txt
+++ b/VERSION.txt
@@ -1,11 +1,291 @@
-jetty-8.1.17.v20150415 - 15 April 2015
- + 409788 Large POST body causes java.lang.IllegalStateException: SENDING =>
-   HEADERS.
- + 433802 check EOF in send1xx
- + 442839 highly fragmented websocket messages can result in corrupt binary
-   messages
- + 445953 fixed loop closing stream write
- + 456741 Eof check in flush
+jetty-9.2.13.v20150730 - 30 July 2015
+ + 472859 ConcatServlet may expose protected resources.
+ + 473006 Encode addPath in URLResource
+ + 473243 Delay resource close for async default content
+ + 473266 Better handling of MultiException
+ + 473322 GatherWrite limit handling
+ + 473624 ProxyServlet.Transparent / TransparentDelegate add trailing slash
+   before query when using prefix.
+ + 473832 SslConnection flips back buffers on handshake exception
+
+jetty-9.2.12.v20150709 - 09 July 2015
+ + 469414 Proxied redirects expose upstream server name.
+ + 469936 Remove usages of SpinLock.
+ + 470184 Send the proxy-to-server request more lazily.
+
+jetty-9.2.11.v20150529 - 29 May 2015
+ + 461499 ConnectionPool may leak connections.
+ + 463579 Add support for 308 status code.
+ + 464292 Implement stream-based transformer for AsyncMiddleManServlet.
+ + 464438 ClassFileTransformer support in
+   org.eclipse.jetty.webapp.WebAppClassLoader broken
+ + 464740 DosFilter whiteList check improvement
+ + 464869 PathResource.addPath allows absolute resolution.
+ + 464989 AbstractSessionManager.removeEventListener() should remove
+   HttpSessionIdListener
+ + 465053 Prevent gzip buffer overflow on complete
+ + 465181 HttpParser parse full end chunk.
+ + 465202 Forked Mojo does not extract war overlays/dependencies
+ + 465359 Resource.newResource(String res, boolean useCache) does not use
+   useCache argument
+ + 465360 URLResource.addPath should use _useCaches setting to create new
+   Resource
+ + 465700 NullPointerException in ResourceHandler with welcome files
+ + 465734 DosFilter whitelist bit pattern fix
+ + 465747 Jetty is failing to process all HTTP OPTIONS requests.
+ + 466329 Fixed local only TestFilter
+ + 467276 NPE protection in SslContextFactory
+ + 467603 Response 401 from server hangs client.
+ + 467936 w Check HttpOutput aggregateSize is < bufferSize
+ + 468008 Scanner ignores directory length
+ + 468421 HttpClient#send fails with IllegalArgumentException on non-lowercase
+   schemes.
+ + 468714 SelectorManager updateKey race without submit
+ + 468747 XSS vulnerability in HttpSpiContextHandler
+
+jetty-9.2.11.M0 - 25 March 2015
+ + 454934 WebSocketClient / connectToServer can block indefinitely during
+   upgrade failure
+ + 459273 Redundant license notices
+ + 461499 ConnectionPool may leak connections.
+ + 461919 Use osgi-friendly serviceloader mechanism for WebSocketServletFactory
+ + 461941 JMX Remote host:port set from start properties
+ + 462546 ShutdownMonitor should bind to jetty.host
+ + 462616 Race between finishing a connect and timing it out.
+
+jetty-9.2.10.v20150310 - 10 March 2015
+ + 445518 Provide different error callbacks to ProxyServlet.
+ + 456521 ShutdownHandler should shut down more gracefully
+ + 458140 Added DispatcherType support to RewriteHandler
+ + 460769 ClientUpgradeRequest sends cookies in the wrong format
+ + 460905 Make sure TimeoutCompleteListener is cancelled if the request cannot
+   be sent.
+ + 461070 Handle setReadListener on request with no content
+ + 461133 allow stop port to reuse address
+ + 461452 Double release of buffer by HttpReceiverOverHTTP
+ + 461499 ConnectionPool may leak connections.
+ + 461623 BufferUtil.writeTo does not update position consistently
+ + 461643 HttpContent.advance() race.
+
+jetty-9.2.9.v20150224 - 24 February 2015
+ + 459273 Redundant license notices
+ + 460176 When checking for precompiled jsp, ensure classname is present
+ + 460180 Jaas demo has wrong doco in html
+ + 460291 AsyncGzipFilter Mappings
+ + 460371 AsyncMiddleManServlet.GZipContentTransformer fails if last transform
+   has no output
+ + 460372 if web.xml does not contain jspc maven plugin insertionMarker
+   behavior is wrong
+ + 460443 Race condition releasing the response buffer.
+ + 460642 HttpParser error 400 can expose previous buffer contents in HTTP
+   status reason message
+
+jetty-9.2.8.v20150217 - 17 February 2015
+ + 451092 Connector will fail if HeaderListener return false.
+ + 455436 ProxyServlet sends two User-Agent values.
+ + 457893 Close temp jar resource
+ + 458101 added test for maxFormContentSize
+ + 458174 Example Jar Server
+ + 458175 multipart annotation on lazily loaded servlet does not work
+ + 458209 Length check for HttpMethod MOVE lookahead
+ + 458354 ALPNServerConnection.select negotiation.
+ + 458495 CompletableCallback may not notify failures.
+ + 458527 Implement an async proxy servlet that can perform content
+   transformations.
+ + 458568 JDBCLoginService javadoc incorrectly references HashLoginService
+ + 458849 org.eclipse.jetty.util.Uptime.DefaultImpl() not available on GAE
+ + 459006 master branch does not build on norwegian locale
+ + 459125 GzipHandler default mimeType behavior incorrect
+ + 459352 AsyncMiddleManServlet should set "Host:" header correctly in proxy to
+   remote request headers.
+ + 459490 Defining a duplicate error page in webdefault.xml and web.xml results
+   in an error
+ + 459542 AsyncMiddleManServlet race condition on first download content.
+ + 459560 jetty.sh handles start.d and no start.ini
+ + 459769 AsyncMiddleManServlet race condition on last download content.
+ + 459845 Support upgrade
+ + 459963 Failure writing content of a committed request leaks connections.
+
+jetty-9.2.7.v20150116 - 16 January 2015
+ + 420944 Hot Deployment of WAR when Context XML exists doesn't trigger
+   redeploy
+ + 448944 Provide m2e lifecycle mapping metadata for jetty-jspc-maven-plugin
+ + 452201 Set the container classloader for osgi during webbundle undeploy
+ + 454291 Added busy threads JMX attribute to QueuedThreadPool
+ + 454773 SSLConnection use on Android client results in loop
+ + 454954 Jetty osgi should skip fragment and required bundles that are in the
+   uninstalled state
+ + 454955 OSGi AnnotationParser should skip resources that are not in the
+   classpath and close the class inputstream when done scanning it
+ + 454983 Source bundles should not be singleton
+ + 455047 Update JASPI
+ + 455174 jetty-plus JNDI tests should use unique JNDI paths
+ + 455330 Multiple Jetty-ContextFilePath entries separated by commas doesn't
+   work
+ + 455476 Persist updated session expiry time for MongoSessionManager
+ + 455655 ensure multipart form-data parsing exception thrown to servlet
+ + 455863 Fixed jetty.sh handling of multiple JETTY_ARGS
+ + 456426 Exception on context undeploy from EnvConfiguration
+ + 456486 Jar containing ServiceContainerInitializer impl not found in TCCL in
+   osgi
+ + 456956 Reduce ThreadLocal.remove() weak reference garbage
+ + 457017 Reflective call to websocket methods that fail have ambiguous
+   exceptions
+ + 457032 Request sent from a failed CompleteListener due to connect timeout is
+   failed immediately.
+ + 457130 HTTPS request with IP host and HTTP proxy throws
+   IllegalArgumentException.
+ + 457696 JMX implementation should not be overridden by WebApp classes
+
+jetty-9.2.6.v20141205 - 05 December 2014
+ + 383207 Use BundleFileLocatorHelperFactory to obtain BundleFileLocatorHelper
+ + 443652 Remove dependency on java.lang.management classes
+ + 447472 Clear async context timeout on async static content
+ + 451529 Change sentinel class for finding jstl on classpath to
+   org.apache.taglibs.standard.tag.rt.core.WhenTag
+ + 451634 DefaultServlet: useFileMappedBuffer javadoc is misleading
+ + 452188 Delay dispatch until content optimisation.
+ + 452201 EnvConfiguration.destroy() should set the classloader
+ + 452246 Fixed SSL hang on last chunk
+ + 452261 Multiple servlets map to path *.jsp when using jsp-property-group
+ + 452424 Do not add Date header if already set
+ + 452516 Make HttpOutput aggregation size configurable.
+ + 453386 Jetty not working when configuring QueuedThreadPool with
+   minThreads=0.
+ + 453629 Fixed big write test
+ + 453793 _maxHeaderBytes>0 is not verified in parseNext() when in
+   State.CLOSED.
+ + 453801 Jetty does not check for already registered services when
+   bootstrapping
+ + 454157 HttpInput.consumeAll spins if input is in async mode.
+
+jetty-9.2.5.v20141112 - 12 November 2014
+ + 448446 org.eclipse.jetty.start.Main create classloader duplicate
+ + 449594 Handle ArrayTrie overflow with false return
+ + 449811 handle unquoted etags when gzipping
+ + 450467 Integer overflow in Session expiry calculation in MongoSessionManager
+ + 450483 Missing parameterization of etc/jetty-deploy.xml.
+ + 450484 Missing parameterization of etc/jetty-http[s].xml.
+ + 450855 GzipFilter MIGHT_COMPRESS exception
+ + 450873 Disable tests that downcaste wrapped GzipFilterResponses
+ + 450894 jetty.sh does not delete JETTY_STATE at start
+
+jetty-9.2.4.v20141103 - 03 November 2014
+ + 376365 "jetty.sh start" returns 0 on failure
+ + 396569 'bin/jetty.sh stop' reports 'OK' even when jetty was not running
+ + 396572 Starting jetty from cygwin is not working properly
+ + 438387 NullPointerException after ServletUpgradeResponse.sendForbidden is
+   called during WebSocketCreator.createWebSocket
+ + 440729 SSL requests often fail with EOFException or IllegalStateException.
+ + 440925 NPE when using relative paths for --start-log-file
+ + 442419 CrossOriginFilter javadoc says "exposeHeaders", but should be
+   "exposedHeaders"
+ + 442495 Bad Context ClassLoader in JSR356 WebSocket onOpen
+ + 442942 Content sent with status 204 (No Content)
+ + 443529 CrossOriginFilter does not accept wildcard for allowedHeaders
+ + 443530 CrossOriginFilter does not set the Vary header
+ + 443550 improved FileResource encoded alias checking
+ + 444031 Ensure exceptions do not reduce threadpool below minimum
+ + 444124 JSP include with <servlet><jsp-file> can cause infinite recursion
+ + 444214 Socks4Proxy fails when reading less than 8 bytes.
+ + 444222 replace CRLF in header values with whitespace rather than ?
+ + 444415 iterative WriteFlusher
+ + 444416 AsyncProxyServlet recursion.
+ + 444517 Ensure WebSocketUpgradeFilter is always first in filter chain
+ + 444547 Format exception in ResourceCache.Content.toString()
+ + 444595 nosql/mongodb - Cleanup process/Refreshing does not respect encoding
+   of attribute keys
+ + 444617 Expose local and remote socket address to applications
+ + 444676 Goal jetty:deploy-war produces errors with version 9.2.3
+ + 444722 Fixed order of setReuseAddress call
+ + 444748 WebSocketClient.stop() does not unregister from ShutdownThread
+ + 444764 HttpClient notifies callbacks for last chunk of content twice.
+ + 444771 JSR356 / EndPointConfig.userProperties are not unique per endpoint
+   upgrade
+ + 444863 ProxyServlet does not filter headers listed by the Connection header.
+ + 444896 Overriding of web-default servlet mapping in web.xml not working with
+   quickstart
+ + 445157 First redeployed servlet leaks WebAppContext
+ + 445239 Rename weld.mod to cdi.mod to be consistent with past module namings
+ + 445258 STOP.WAIT is not really respected
+ + 445374 Reevaluate org.eclipse.jetty.websocket.jsr356 enablement concepts
+ + 445495 Improve Exception message when no jndi resource to bind for a name in
+   web.xml
+ + 445542 Add SecuredRedirectHandler for embedded jetty use to redirect to
+   secure port/scheme
+ + 445821 Error 400 should be logged with RequestLog
+ + 445823 RequestLogHandler at end of HandlerCollection doesn't work
+ + 445830 Support setting environment variables on forked jetty with
+   jetty:run-forked
+ + 445979 jetty.sh fails to start when start-stop-daemon does not exist and the
+   user is not root
+ + 446033 org.eclipse.jetty.websocket.server.WebSocketServerFactory not
+   available in OSGi
+ + 446063 ALPN Fail SSL Handshake if no supported Application Protocols.
+ + 446107 NullPointerException in ProxyServlet when extended by Servlet without
+   a package
+ + 446425 Oracle Sql error on JettySessions table when this table do not exist
+   already
+ + 446506 getAsyncContext ISE before startAsync on async dispatches
+ + 446563 Null HttpChannel.getCurrentHttpChannel() in
+   ServletHandler.doFilter().
+ + 446672 NPN Specification issue in the case no protocols are selected.
+ + 446923 SharedBlockingCallback does not handle connector max idle time of
+   Long.MAX_VALUE; BlockerTimeoutException not serializable
+ + 447381 Disable SSLv3 by default.
+ + 447472 test harness for slow large writes
+ + 447627 MultiPart file always created when "filename" set in
+   Content-Disposition
+ + 447629 getPart()/getParts() fails on Multipart request if getParameter is
+   called in a filter first
+ + 447746 HttpClient is always going to send User-Agent header even though I do
+   not want it to.
+ + 447979 Refactor to make MetaData responsible for progressively ordering
+   web-inf jars
+ + 448156 Fixed INACTIVE race in IteratingCallback
+ + 448225 Removed unnecessary synchronize on initParser
+ + 448841 Clarified selectors==0 javadoc 448840 Clarified ServerConnector
+   javadoc 448839 Fixed javadoc typo in ServerConnector
+ + 449001 Remove start.d directory from JETTY_HOME
+ + 449003 WARNING: Cannot enable requested module [protonego-impl]: not a valid
+   module name
+ + 449038 WebSocketUpgradeFilter must support async.
+ + 449175 Removed extra space in NCSA log
+ + 449291 create-files downloads without license
+ + 449372 Make jvmArgs of jetty:run-forked configurable from command line
+ + 449603 OutputStreamContentProvider hangs when host is not available.
+
+jetty-9.2.3.v20140905 - 05 September 2014
+ + 347110 renamed class transformer methods
+ + 411163 Add embedded jetty code example with JSP enabled
+ + 435322 Added a idleTimeout to the SharedBlockerCallback
+ + 435533 Handle 0 sized async gzip
+ + 435988 ContainerLifeCycle: beans never stopped on remove
+ + 436862 Update jetty-osgi to asm-5 and spifly-1.0.1
+ + 438500 Odd NoClassDef errors when shutting down the jetty-maven-plugin via
+   the stop goal
+ + 440255 ensure 500 is logged on thrown Errors
+ + 441073 isEarlyEOF on HttpInput
+ + 441475 org.eclipse.jetty.server.ResourceCache exceptions under high load
+ + 441479 Jetty hangs due to deadlocks in session manager
+ + 441649 Update to jsp and el Apache Jasper 8.0.9
+ + 441756 Ssl Stackoverflow on renegotiate
+ + 441897 Fixed etag handling in gzipfilter
+ + 442048 fixed sendRedirect %2F encoding
+ + 442383 Improved insufficient threads message
+ + 442628 Update example xml file for second server instance to extract wars
+ + 442642 Quickstart generates valid XML
+ + 442759 Allow specific ServletContainerInitializers to be excluded
+ + 442950 Embedded Jetty client requests to localhost hangs with high cpu usage
+   (NIO OP_CONNECT Solaris/Sparc).
+ + 443049 Improved HttpParser illegal character messages
+ + 443158 Fixed HttpOutput spin
+ + 443172 web-fragment.xml wrongly parsed for applications running in serlvet
+   2.4 mode
+ + 443231 java.lang.NullPointerException on scavenge scheduling when session id
+   manager declared before shared scheduler
+ + 443262 Distinguish situation where jetty looks for tlds in META-INF but
+   finds none vs does not look
 
 jetty-8.1.16.v20140903 - 03 September 2014
  + 409788 Large POST body causes java.lang.IllegalStateException: SENDING =>
@@ -25,6 +305,177 @@ jetty-7.6.16.v20140903 - 03 September 2014
  + 442839 highly fragmented websocket messages can result in corrupt binary
    messages
 
+jetty-9.2.2.v20140723 - 23 July 2014
+ + 411323 DosFilter/QoSFilter should use AsyncContext rather than
+   Continuations.
+ + 432815 Fixed selector stop race
+ + 434536 Improved Customizer javadoc
+ + 435322 Fixed Iterating Callback close
+ + 435653 encode async dispatched requestURI
+ + 435895 jetty spring module is not in distribution
+ + 436874 WebSocket client throwing a NullPointer when handling a pong
+ + 436894 GzipFilter code cleanup
+ + 436916 CGI: "Search docroot for a matching execCmd" logic is wrong
+ + 436987 limited range of default acceptors and selectors
+ + 437051 Refactor Filter chain handling of Request.isAsyncSupported
+ + 437395 Start / Properties in template sections should be default applied for
+   enabled modules
+ + 437419 Allow scanning of META-INF for resources,fragments,tlds for unpacked
+   jars
+ + 437430 jettyXml not consistent between jetty:run and jetty:run-forked
+ + 437462 consistent test failure in jetty-start under windows
+ + 437706 ServletTester calls LocalConnector method with hardcoded timeout
+ + 437800 URLs with single quote and spaces return 404
+ + 437996 avoid async status race by not setting 200 on handled
+ + 438079 Review garbage creation in 9.2.x series.
+ + 438190 findbug improvements
+ + 438204 leave IPv6 addresses [] wrapped in getServerName
+ + 438327 Remove hard coded Allow from OPTIONS *
+ + 438331 AbstractLogger.debug(String,long) infinite loop
+ + 438434 ResourceHandler checks aliases
+ + 438895 Add mvn jetty:effective-web-xml goal
+ + 439066 javadoc setStopAtShutdown
+ + 439067 Improved graceful stop timeout handling
+ + 439194 Do not configure fake server for jetty:run-forked
+ + 439201 GzipFilter and AsyncGzipFilter should strip charset from Content-Type
+   before making exclusion comparison in doFilter
+ + 439369 Deprecate CrossContextPseudoSession
+ + 439387 Ensure empty servlet-class never generated for quickstart
+ + 439390 Ensure jsp scratchdir is created same way for quickstart and
+   non-quickstart
+ + 439394 load-on-startup with value 0 not preserved for quickstart
+ + 439399 Scan tlds for apache jasper standard taglib with jetty-maven-plugin
+ + 439438 DataSourceLoginService does not refresh passwords when changed in
+   database
+ + 439507 Possible timing side-channel when comparing MD5-Credentials
+ + 439540 setReuseAddress() in ServerConnector.java is not coded properly
+ + 439652 GzipHandler super.doStart
+ + 439663 Allow mappings to be declared before servlet/filter
+ + 439672 support using Apache commons daemon for managing Jetty
+ + 439753 ConstraintSecurityHandler has dead code for processing constraints
+ + 439788 CORS filter headers gone between 9.2.0.M0 and 9.2.1 .v20140609 for
+   ProxyServlet requests.
+ + 439809 mvn jetty:jspc cannot find taglibs in dependency jars
+ + 439895 No event callback should be invoked after the "failure" callback.
+ + 440020 Abort bad proxy responses with sendError(-1)
+ + 440038 Content decoding may fail.
+ + 440114 ContextHandlerCollection does not skip context wrappers
+ + 440122 Remove usages of ForkInvoker.
+
+jetty-9.2.1.v20140609 - 09 June 2014
+ + 347110 Supprt ClassFileTransormers in WebAppClassLoader
+ + 432192 jetty-start / Allow JETTY_LOGS use for start-log-file
+ + 432321 jetty-start / Allow defining extra start directories for common
+   configurations
+ + 435322 Improved debug
+ + 436029 GzipFilter errors on asynchronous methods with message to
+   AsyncGzipFilter
+ + 436345 Refactor AbstractSession to minimize burden on subclasses to
+   implement behaviour
+ + 436388 Allow case-insensitive STOP.KEY and STOP.PORT use
+ + 436405 ${jetty.base}/resources not on classpath with default configuration
+ + 436520 Start / Allow https and file urls in jetty-start's module download
+   mechanism
+ + 436524 Start / Downloadable [files] references in modules cannot use ":"
+   themselves
+
+jetty-9.2.0.v20140526 - 26 May 2014
+ + 429390 Decoders and Encoders are not registered for non-annotated
+   ClientEndpoint
+ + 434810 better handling of bad messages
+ + 435086 ${jetty.base}/resources not on classpath when using
+   --module=resources
+ + 435088 lib/npn packaging of jetty-distribution is off
+ + 435206 Can't add Cookie header on websocket ClientUpgradeRequest
+ + 435217 Remove deprecated TagLibConfiguration
+ + 435223 High cpu usage in
+   FCGIHttpParser.parseContent(ResponseContentParser.java:314).
+ + 435338 Incorrect handling of asynchronous content.
+ + 435412 Make AbstractSession.access() more amenable to customization
+
+jetty-9.2.0.RC0 - 15 May 2014
+ + 419972 Support sending forms (application/x-www-form-urlencoded).
+ + 420368 Default content types for ContentProviders.
+ + 428966 Per-request cookie support.
+ + 430418 Jetty 9.1.3 and Chrome 33 permessage-deflate do not work together
+ + 431333 NPE In logging of WebSocket ExtensionConfig
+ + 432321 jetty-start / Allow defining extra start directories for common
+   configurations
+ + 432939 Jetty Client ContentResponse should have methods such as
+   getContentType() and getMediaType().
+ + 433089 Client should provide Request.accept() method, like JAX-RS 2.0
+   Invocation.Builder.accept().
+ + 433405 Websocket Session.setMaxIdleTimeout fails with zero
+ + 433689 Evict old HttpDestinations from HttpClient.
+ + 434386 Request Dispatcher extracts args and prevents asyncIO.
+ + 434395 WebSocket / memory leak, WebSocketSession not cleaned up in abnormal
+   closure cases
+ + 434447 Able to create a session after a response.sendRedirect
+ + 434505 Allow property files on start.jar command line Signed-off-by: Tom
+   Zeller<tzeller at dragonacea.biz>
+ + 434578 Complete listener not called if redirected to an invalid URI.
+ + 434679 Log static initialization via jetty-logging.properties fails
+   sometimes
+ + 434685 WebSocket read/parse does not discard remaining network buffer after
+   unrecoverable error case
+ + 434715 Avoid call to ServletHolder.getServlet() during handle() iff servlet
+   is available and instantiated
+
+jetty-9.2.0.M1 - 08 May 2014
+ + 367680 jsp-file with load-on-startup not precompiled
+ + 404511 removed deprecated StringMap
+ + 409105 Upgrade jetty-osgi build/test to use more recent pax junit test
+   framework
+ + 424982 improved PID check in jetty.sh
+ + 425421 ContainerLifeCycle does not start added beans in started state
+ + 428904 Add logging of which webapp has path with uncovered http methods
+ + 431094 Consistent handling of utf8 decoding errors
+ + 431459 Jetty WebSocket compression extensions fails to handle big messages
+   properly
+ + 431519 Fixed NetworkTrafficListener
+ + 431642 Implement ProxyServlet using Servlet 3.1 async I/O.
+ + 432145 Pending request is not failed when HttpClient is stopped.
+ + 432270 Slow requests with response content delimited by EOF fail.
+ + 432321 jetty-start / Allow defining extra start directories for common
+   configurations
+ + 432468 Improve command CGI path handling
+ + 432473 web.xml declaration order of filters not preserved on calls to init()
+ + 432483 make osgi.serviceloader support for
+   javax.servlet.ServletContainerInitializer optional (cherry picked from
+   commit 31043d25708edbea9ef31948093f4eaf2247919b)
+ + 432528 IllegalStateException when using DeferredContentProvider.
+ + 432777 Async Write Loses Data with HTTPS Server.
+ + 432901 ensure a single onError callback only in pending and unready states
+ + 432993 Improve handling of ProxyTo and Prefix parameters in
+   ProxyServlet.Transparent.
+ + 433244 Security manager lifecycle cleanup
+ + 433262 WebSocket / Advanced close use cases
+ + 433365 No such servlet:
+   __org.eclipse.jetty.servlet.JspPropertyGroupServlet__
+ + 433370 PATCH method does not work with ProxyServlet.
+ + 433431 Support ServletHandler fall through
+ + 433479 Improved resource javadoc
+ + 433483 sync log initialize
+ + 433512 Jetty throws RuntimeException when webapp compiled with jdk8
+   -parameters
+ + 433563 Jetty fails to startup on windows - InvalidPathException
+ + 433572 default to sending date header
+ + 433656 Change to Opcode.ASM5 breaks jetty-osgi
+ + 433692 improved buffer resizing
+ + 433708 Improve WebAppClassLoader.addClassPath() IllegalStateException
+   message
+ + 433793 WebSocket / empty protocol list in ServerEndpointConfig.Configurator
+   when using non-exact header name
+ + 433841 Resource.newResource() declares an exception it does not throw
+ + 433849 FileResource string compare fix
+ + 433916 HttpChannelOverHttp handles HTTP 1.0 connection reuse incorrectly.
+ + 434009 Improved javadoc for accessing HttpChannel and HttpConnection
+ + 434027 ReadListener.onError() not invoked in case of read failures.
+ + 434056 Support content consumed asynchronously.
+ + 434074 Avoid double dispatch by returning false from messageComplete
+ + 434077 AnnotatedServerEndpointTest emits strange exception
+ + 434247 Redirect loop in FastCGI proxying for HTTPS sites.
+
 jetty-8.1.15.v20140411 - 11 April 2014
  + 397167 Remote Access documentation is wrong
  + 419799 complete after exceptions thrown from async error pages
@@ -50,6 +501,304 @@ jetty-7.6.15.v20140411 - 11 April 2014
  + 425551 Memory Leak in SelectConnector$ConnectTimeout.expired.
  + 432452 ConnectHandler does not timeout sockets in FIN_WAIT2.
 
+jetty-9.2.0.M0 - 09 April 2014
+ + 419801 Upgrade to asm5 for jdk8
+ + 423392 Fix buffer overflow in AsyncGzipFilter
+ + 425736 jetty-start / Jetty 9 fails to startup with --exec option if Java
+   path contain
+ + 426920 jetty-start / BaseHome.listFilesRegex() and .recurseDir() do not
+   detect filesystem loops
+ + 427188 Re-enable automatic detection of logging-dependencies with
+   logging-module
+ + 429734 Implemented the HA ProxyProtocol
+ + 430341 use apache jsp/jstl for maven plugins
+ + 430747 jetty-start / Allow --lib and module [lib] to recursively add jars
+ + 430825 jetty-start / use of jetty-jmx.xml prevents configuration of
+   ThreadPool in jetty.xml
+ + 431279 jetty-start / Unable to start jetty if no properties are defined.
+ + 431892 DefaultFileLocatorHelper.getBundleInstallLocation fails for equinox
+   3.10
+ + 432122 ignore frequently failing test
+ + 432145 Pending request is not failed when HttpClient is stopped.
+ + 432270 Slow requests with response content delimited by EOF fail.
+
+jetty-9.1.5.v20140505 - 05 May 2014
+ + 431459 Jetty WebSocket compression extensions fails to handle big messages
+   properly
+ + 431519 Fixed NetworkTrafficListener
+ + 432145 Pending request is not failed when HttpClient is stopped.
+ + 432270 Slow requests with response content delimited by EOF fail.
+ + 432473 web.xml declaration order of filters not preserved on calls to init()
+ + 432483 make osgi.serviceloader support for
+   javax.servlet.ServletContainerInitializer optional (cherry picked from
+   commit 31043d25708edbea9ef31948093f4eaf2247919b)
+ + 432528 IllegalStateException when using DeferredContentProvider.
+ + 432777 Async Write Loses Data with HTTPS Server.
+ + 432901 ensure a single onError callback only in pending and unready states
+ + 432993 Improve handling of ProxyTo and Prefix parameters in
+   ProxyServlet.Transparent.
+ + 433365 No such servlet:
+   __org.eclipse.jetty.servlet.JspPropertyGroupServlet__ (cherry picked from
+   commit e2ed934978b958d6fccb28a8a5d04768f7c0432d)
+ + 433370 PATCH method does not work with ProxyServlet.
+ + 433483 sync log initialize
+ + 433692 improved buffer resizing
+ + 433916 HttpChannelOverHttp handles HTTP 1.0 connection reuse incorrectly.
+ + 434027 ReadListener.onError() not invoked in case of read failures.
+
+jetty-9.1.4.v20140401 - 01 April 2014
+ + 414206 Rewrite rules re-encode requestURI
+ + 414885 Don't expose JDT classes by default
+ + 417022 Access current HttpConnection from Request not ThreadLocal
+ + 423619 set Request timestamp on startRequest
+ + 423982 removed duplicate UrlResource toString
+ + 424107 Jetty should not finish chunked encoding on exception.
+ + 425991 added qml mime type
+ + 426897 improved ContainerLifeCycle javadoc
+ + 427185 Add org.objectweb.asm. as serverClass
+ + 427204 jetty-start / startup incorrectly requires directory in jetty.base
+ + 427368 start.sh fails quietly on command line error
+ + 428594 File upload with onMessage and InputStream fails
+ + 428595 JSR-356 / ClientContainer does not support SSL
+ + 428597 javax-websocket-client-impl and javax-websocket-server-impl jars
+   Manifests do not export packages for OSGI
+ + 428817 jetty-start / Allow for property to configure deploy manager
+   `webapps` directory
+ + 429180 Make requestlog filename parameterized
+ + 429357 JDBCSessionManager.Session.removeAttribute don't set dirty flag if
+   attribute already removed
+ + 429409 osgi] jetty.websocket.servlet must import jetty.websocket.server
+ + 429487 Runner code cleanups
+ + 429616 Use UTF-8 encoding for XML
+ + 429779 masked zero length websocket frame gives NullPointerException during
+   streaming read
+ + 430088 OnMessage*Callable decoding of streaming binary or text is not thread
+   safe
+ + 430242 added SharedBlockingCallback to support threadsafe blocking
+ + 430273 Cancel async timeout breaks volatile link to avoid race with slow
+   expire
+ + 430341 add apache jsp and jstl optional modules
+ + 430490 Added JETTY_SHELL 426738 Fixed JETTY_HOME comments
+ + 430649 test form encoding
+ + 430654 closing client connections can hang worker threads.
+ + 430808 OutputStreamContentProvider violates OutputStream contract.
+ + 430822 jetty-start / make soLingerTime configurable via property
+ + 430823 jetty-start / make NeedClientAuth (ssl) configurable via property
+ + 430824 jetty-start / use of jetty-logging.xml prevents configuration of
+   ThreadPool in jetty.xml
+ + 431103 Complete listener not called if request times out before processing
+   exchange.
+ + 431592 do not resolved forwarded-for address
+
+jetty-9.1.3.v20140225 - 25 February 2014
+ + 373952 Ensure MongoSessionManager un/binds session attributes on refresh
+   only if necessary
+ + 424899 Initialize GzipHandler mimeTypes
+ + 426490 HttpServletResponse.setBufferSize(0) results in tight loop (100% cpu
+   hog)
+ + 427700 Outgoing extensions that create multiple frames should flush them in
+   order and atomically.
+ + 427738 fixed XSS in async-rest demo
+ + 428157 Methods of anonymous inner classes can't be called via xml
+ + 428232 Rework batch mode / buffering in websocket.
+ + 428238 Test HEAD request with async IO
+ + 428266 HttpRequest mangles URI query string.
+ + 428383 limit white space between requests
+ + 428418 JettyStopMojo prints some messages on System.err
+ + 428435 Large streaming message fails in MessageWriter.
+ + 428660 Delay closing async HttpOutput until after UNREADY->READY
+ + 428710 JDBCSession(Id)Manager use read committed isolation level
+ + 428859 Do not auto initialise jsr356 websocket if no annotations or
+   EndPoints discovered
+
+jetty-9.1.2.v20140210 - 10 February 2014
+ + 408167 Complex object as session attribute not necessarily persisted.
+ + 423421 remove org.slf4j and org.ow2.asm from jetty-all artifact
+ + 424171 Old javax.activation jar interferes with email sending
+ + 424562 JDBCSessionManager.setNodeIdInSessionId(true) does not work
+ + 425275 
+   org.eclipse.jetty.osgi.annotations.AnnotationConfiguration.BundleParserTask.getStatistic()
+   returns null when debug is enabled.
+ + 425638 Fixed monitor module/xml typos
+ + 425696 start.jar --add-to-start={module} results in error
+ + 425703 Review [Queued]HttpInput.
+ + 425837 Upgrade to jstl 1.2.2
+ + 425930 JDBC Session Manager constantly reloading session if save intervall
+   expired once
+ + 425998 JDBCSessionIdManager fails to create maxinterval column
+ + 426250 jetty-all should be deployed on release
+ + 426358 NPE generating temp dir name if no resourceBase or war
+ + 426481 fix < java 1.7.0_10 npn files
+ + 426739 Response with Connection: keep-alive truncated.
+ + 426750 isReady() returns true at EOF
+ + 426870 HTTP 1.0 Request with Connection: keep-alive and response content
+   hangs.
+ + 427068 ServletContext.getClassLoader should only check privileges if a
+   SecurityManager exists
+ + 427128 Cookies are not sent to the server.
+ + 427245 StackOverflowError when session cannot be de-idled from disk
+ + 427254 Cookies are not sent to the client.
+ + 427512 ReadPendingException in case of HTTP Proxy tunnelling.
+ + 427570 externalize common http config to start.ini
+ + 427572 Default number of acceptors too big.
+ + 427587 MessageInputStream must copy the payload.
+ + 427588 WebSocket Parser leaks ByteBuffers.
+ + 427690 Remove Mux Extension and related support.
+ + 427699 WebSocket upgrade response sends Sec-WebSocket-Protocol twice.
+
+jetty-9.1.1.v20140108 - 08 January 2014
+ + 408912 JDBCSessionIdManager should allow configuration of schema
+ + 410750 NPE Protection in Mongo save session
+ + 417202 Start / command line arguments with ${variable} should be expanded
+ + 418622 WebSocket / When rejecting old WebSocket protocols, log client
+   details
+ + 418769 Allow resourceBases in run-forked Mojo
+ + 418888 Added strict mode to HttpGenerator
+ + 419309 encode alias URIs from File.toURI
+ + 419911 Empty chunk causes ArrayIndexOutOfBoundsException in
+   InputStreamResponseListener.
+ + 421189 WebSocket / AbstractExtension's WebSocketPolicy is not
+   Session-specific
+ + 421314 Websocket / Connect attempt with Chrome 32+ fails with "Some
+   extension already uses the compress bit"
+ + 421697 IteratingCallback improvements
+ + 421775 CookiePatternRule only sets cookie if not set already
+ + 421794 Iterator from InputStreamProvider is not implemented properly.
+ + 421795 ContentProvider should have a method to release resources.
+ + 422192 ClientContainer.getOpenSessions() always returns null
+ + 422264 OutputStreamContentProvider does not work with Basic Authentication.
+ + 422308 Change all session/sessionid managers to use shared Scheduler
+ + 422386 Comma-separated <param-value>s not trimmed in GzipFilter
+ + 422388 Test for GzipFilter apply to resources with charset appended to the
+   MIME type
+ + 422398 moved jmx remote config to jmx-remote.mod
+ + 422427 improved TestConnection
+ + 422703 Support reentrant HttpChannel and HttpConnection
+ + 422723 Dispatch failed callbacks to avoid blocking selector
+ + 422734 messages per second in ConnectorStatistics
+ + 422807 fragment large written byte arrays to protect from JVM OOM bug
+ + 423005 reuse gzipfilter buffers
+ + 423048 Receiving a PING while sending a message kills the connection
+ + 423060 Allow ${jetty.base}/work
+ + 423118 ServletUpgradeRequest.getUserPrincipal() does not work
+ + 423185 Update permessage-deflate for finalized spec
+ + 423255 MBeans of SessionIdManager can leak memory on redeploy
+ + 423361 Ensure ServletContainerInitializers called before injecting Listeners
+ + 423373 Correct namespace use for JEE7 Schemas
+ + 423392 GzipFilter without wrapping or blocking
+ + 423395 Ensure @WebListeners are injected
+ + 423397 Jetty server does not run on Linux server startup because of  a bug
+   in jetty.sh script.
+ + 423476 WebSocket / JSR / @OnMessage(maxMessageSize=20000000) not properly
+   supported
+ + 423556 HttpSessionIdListener should be resource injectable
+ + 423646 WebSocket / JSR / WebSocketContainer (Client) should have its
+   LifeCycle stop on standalone use
+ + 423692 use UrlEncoded.ENCODING for merging forwarded query strings
+ + 423695 <HT> Horizontal-tab used as HTTP Header Field separator unsupported
+ + 423724 WebSocket / Rename MessageAppender.appendMessage to .appendFrame
+ + 423739 Start checks module files.
+ + 423804 WebSocket / JSR improper use of
+   ServerEndpointConfig.Configurator.getNegotiatedSubprotocol()
+ + 423875 Update jetty-distro build to use jetty-toolchain jetty-schemas 3.1.M0
+ + 423915 WebSocket / Active connection from IOS that goes into airplane mode
+   not disconnected on server side
+ + 423926 Remove code duplication in class IdleTimeout.
+ + 423930 SPDY streams are leaked.
+ + 423948 Cleanup and consolidate testing utilities in WebSocket
+ + 424014 PathContentProvider does not close its internal SeekableByteChannel.
+ + 424043 IteratingCallback Idle race.
+ + 424051 Using --list-config can result in NPE
+ + 424168 Module [ext] should load libraries recursively from lib/ext/
+ + 424180 extensible bad message content
+ + 424183 Start does not find LIB (Classpath) when on non-English locale
+ + 424284 Identify conflicts in logging when error "Multiple servlets map to
+   {pathspec}" occurs
+ + 424303 @ServletSecurity not applied on non load-on-startup servlets
+ + 424307 obfuscate unicode
+ + 424380 Augment class / Jar scanning timing log events
+ + 424390 Allow enabling modules via regex
+ + 424398 Servlet load-on-startup ordering is not obeyed
+ + 424497 Allow concurrent async sends
+ + 424498 made bytebufferendpoint threadsafe
+ + 424588 org.eclipse.jetty.ant.AntWebInfConfiguration does not add
+   WEB-INF/classes for annotation scanning
+ + 424598 Module [npn] downloads wrong npn jar
+ + 424651 org.eclipse.jetty.spdy.Flusher use of non-growable ArrayQueue yield
+   java.lang.IllegalStateException: Full.
+ + 424682 Session cannot be deserialized with form authentication
+ + 424706 The setMaxIdleTimeout of javax.websocket.Session does not take any
+   affect
+ + 424734 WebSocket / Expose Locale information from ServletUpgradeRequest
+ + 424735 WebSocket / Make ServletUpgradeRequest expose its HttpServletRequest
+ + 424743 Verify abort behavior in case the total timeout expires before the
+   connect timeout.
+ + 424762 ShutdownHandler hardcodes "127.0.0.1" and cannot be used with IPv6
+ + 424847 Deadlock in deflate-frame (webkit binary)
+ + 424863 IllegalStateException "Unable to find decoder for type
+   <javax.websocket.PongMessage>"
+ + 425038 WebSocketClient leaks file handles when exceptions are thrown from
+   open()
+ + 425043 Track whether pools are used correctly.
+ + 425049 add json mime mapping to mime.properties.
+
+jetty-9.1.0.v20131115 - 15 November 2013
+ + 397167 Remote Access documentation is wrong
+ + 416477 QueuedThreadPool does not reuse interrupted threads
+ + 420776 complete error pages after startAsync
+ + 421362 When using the jetty.osgi.boot ContextHandler service feature the
+   wrong ContextHandler can be undeployed
+
+jetty-9.1.0.RC2 - 07 November 2013
+ + 410656 WebSocketSession.suspend() hardcoded to return null
+ + 417223 removed deprecated ThreadPool.dispatch
+ + 418741 Threadlocal cookie buffer in response
+ + 420359 fixed thread warnings
+ + 420572 IOTest explicitly uses 127.0.0.1
+ + 420692 set soTimeout to try to avoid hang
+ + 420844 Connection:close on exceptional errors
+ + 420930 Use Charset to specify character encoding
+ + 421197 synchronize gzip output finish
+ + 421198 onComplete never call onComplete in BufferingResponseListener in 9.1.
+
+jetty-9.0.7.v20131107 - 07 November 2013
+ + 407716 fixed logs
+ + 416597 Allow classes and jars on the webappcontext extraclasspath to be
+   scanned for annotations by jetty-maven-plugin
+ + 418636 Name anonymous filter and holders with classname-hashcode
+ + 418732 Add whiteListByPath mode to IPAccessHandler
+ + 418767 run-forked goal ingores test scope dependencies with
+   useTestScope=true
+ + 418792 Session getProtocolVersion always returns null
+ + 418892 SSL session caching so unreliable it effectively does not work.
+ + 419309 Added symlink checker to test webapp
+ + 419333 treat // as an alias in path
+ + 419344 NPNServerConnection does not close the EndPoint if it reads -1.
+ + 419350 Do not borrow space from passed arrays
+ + 419655 AnnotationParser throws NullPointerException when scanning files from
+   jar:file urls
+ + 419687 HttpClient's query parameters must be case sensitive.
+ + 419799 Async timeout dispatches to error page
+ + 419814 Annotation properties maxMessageSize and inputBufferSize don't work
+ + 419846 JDBCSessionManager doesn't determine dirty state correctly
+ + 419901 Client always adds extra user-agent header.
+ + 419937 Request isSecure cleared on recycle
+ + 419950 Provide constructor for StringContentProvider that takes Charset.
+ + 419964 InputStreamContentProvider does not close provided InputStream.
+ + 420033 AsyncContext.onTimeout exceptions passed to onError
+ + 420039 BufferingResponseListener continues processing after aborting
+   request.
+ + 420048 DefaultServlet alias checks configured resourceBase
+ + 420142 reimplemented graceful shutdown
+ + 420362 Response/request listeners called too many times.
+ + 420374 Call super.close() in a finally block
+ + 420530 AbstractLoginModule never fails a login
+ + 420572 IOTest explicitly uses 127.0.0.1
+ + 420776 complete error pages after startAsync
+ + 420844 Connection:close on exceptional errors
+ + 420930 Use Charset to specify character encoding
+ + 421197 synchronize gzip output finish
+
 jetty-8.1.14.v20131031 - 31 October 2013
  + 417772 fixed low resources idle timeout
  + 418636 Name anonymous filter and holders with classname-hashcode
@@ -64,6 +813,323 @@ jetty-7.6.14.v20131031 - 31 October 2013
  + 420048 DefaultServlet alias checks configured resourceBase
  + 420530 AbstractLoginModule never fails a login
 
+jetty-9.1.0.RC1 - 31 October 2013
+ + 294531 Unpacking webapp twice to the same directory name causes problems
+   with updated jars in WEB-INF/lib
+ + 397049 Cannot Provide Custom Credential to JDBCLoginService
+ + 403591 improve the Blocking Q implementation.
+ + 407716 fixed logs
+ + 410840 Change SSLSession.getPeerCertificateChain() to
+   SSLSession.getPeerCertificates().
+ + 415118 WebAppClassLoader.getResource(name) should strip .class from name
+ + 415609 spdy replace SessionInvoker with IteratingCallback. Introduce Flusher
+   class to separate queuing/flushing logic from StandardSession
+ + 416300 Order ServletContainerInitializer callbacks
+ + 416597 Allow classes and jars on the webappcontext extraclasspath to be
+   scanned for annotations by jetty-maven-plugin
+ + 417356 Add SOCKS support to jetty client.
+ + 417932 resources.mod should make ${jetty.base}/resources/ directory
+ + 417933 logging.mod ini template should include commented log.class settings
+ + 418212 org.eclipse.jetty.spdy.server.http.SSLExternalServerTest hangs.
+ + 418441 Use of OPTIONS= in Jetty 9.1 should display WARNING message
+ + 418596 Faults in JARs during class scanning should report the jar that
+   caused the problem
+ + 418603 cannot specify a custom ServerEndpointConfig.Configurator
+ + 418625 WebSocket / Jsr RemoteEndpoint.sendObject(java.nio.HeapByteBuffer)
+   doesn't find encoder
+ + 418632 WebSocket / Jsr annotated @OnMessage with InputStream fails to be
+   called
+ + 418636 Name anonymous filter and holders with classname-hashcode
+ + 418732 Add whiteListByPath mode to IPAccessHandler
+ + 418767 run-forked goal ingores test scope dependencies with
+   useTestScope=true
+ + 418792 Session getProtocolVersion always returns null
+ + 418892 SSL session caching so unreliable it effectively does not work.
+ + 418922 Missing parameterization of etc/jetty-xinetd.xml
+ + 418923 Missing parameterization of etc/jetty-proxy.xml
+ + 419146 Parameterize etc/jetty-requestlog.xml values
+ + 419309 Added symlink checker to test webapp
+ + 419330 Allow access to setters on jetty-jspc-maven-plugin
+ + 419333 treat // as an alias in path
+ + 419344 NPNServerConnection does not close the EndPoint if it reads -1.
+ + 419350 Do not borrow space from passed arrays
+ + 419655 AnnotationParser throws NullPointerException when scanning files from
+   jar:file urls
+ + 419687 HttpClient's query parameters must be case sensitive.
+ + 419799 Async timeout dispatches to error page
+ + 419814 Annotation properties maxMessageSize and inputBufferSize don't work
+ + 419846 JDBCSessionManager doesn't determine dirty state correctly
+ + 419899 Do not wrap SSL Exception as EoFException
+ + 419901 Client always adds extra user-agent header.
+ + 419904 Data corruption on proxy PUT requests.
+ + 419914 QueuedThreadPool uses nanoTime
+ + 419937 Request isSecure cleared on recycle
+ + 419950 Provide constructor for StringContentProvider that takes Charset.
+ + 419964 InputStreamContentProvider does not close provided InputStream.
+ + 420012 Improve ProxyServlet.Transparent configuration in case prefix="/".
+ + 420033 AsyncContext.onTimeout exceptions passed to onError
+ + 420034 Removed threads/timers from Date caching
+ + 420039 BufferingResponseListener continues processing after aborting
+   request.
+ + 420048 DefaultServlet alias checks configured resourceBase
+ + 420103 Split out jmx-remote module from existing jmx module
+ + 420142 reimplemented graceful shutdown
+ + 420362 Response/request listeners called too many times.
+ + 420364 Bad synchronization in HttpConversation.
+ + 420374 Call super.close() in a finally block
+ + 420530 AbstractLoginModule never fails a login
+ + 420687 XML errors in jetty-plus/src/test/resources/web-fragment-*.xml
+ + 420776 complete error pages after startAsync
+
+jetty-9.1.0.RC0 - 30 September 2013
+ + 412469 make module for jetty-jaspi
+ + 416453 Add comments to embedded SplitFileServer example
+ + 416577 enhanced shutdown handler to send shutdown at startup
+ + 416674 run all jetty-ant tests on random ports
+ + 416940 avoid download of spring-beans.dtd
+ + 417152 WebSocket / Do all setup in websocket specific
+   ServletContainerInitializer
+ + 417239 re-implemented Request.getContentRead()
+ + 417284 Precompiled regex in HttpField
+ + 417289 SPDY replace use of direct buffers with indirect buffers or make it
+   configurable
+ + 417340 Upgrade JDT compiler to one that supports source/target of Java 1.7
+ + 417382 Upgrade to asm 4.1 and refactor annotation parsing
+ + 417475 Do not null context Trie during dynamic deploy
+ + 417490 WebSocket / @PathParam annotated parameters are null when the servlet
+   mapping uses a wildcard
+ + 417561 Refactor annotation related code: change log messages
+ + 417574 Setting options with _JAVA_OPTIONS breaks run-forked with
+   <waitForChild>true</waitForChild>
+ + 417831 Remove jetty-logging.properties from distro/resources
+ + 417938 Startup / Sort properties presented in --list-config alphabetically
+ + 418014 Handle NTFS canonical exceptions during alias check
+ + 418068 WebSocketClient has lazy or injected Executor
+ + 418212 org.eclipse.jetty.spdy.server.http.SSLExternalServerTest hangs
+ + 418227 Null cookie value test
+
+jetty-9.0.6.v20130930 - 30 September 2013
+ + 411069 better set compiler defaults to 1.7, including webdefault.xml for jsp
+ + 411934 War overlay configuration assumes src/main/webapp exists
+ + 413484 setAttribute in nosql session management better handles _dirty status
+ + 413684 deprecated unsafe alias checkers
+ + 413737 hide stacktrace in ReferrerPushStrategyTest
+ + 414431 Avoid debug NPE race
+ + 414898 Only upgrade v0 to v1 cookies on dquote , ; backslash space and tab
+   in the value
+ + 415192 <jsp-file> maps to JspPropertyGroupServlet instead of JspServlet
+ + 415194 Deployer gives management of context to context collection
+ + 415302 
+ + 415330 Avoid multiple callbacks at EOF
+ + 415401 Add initalizeDefaults call to SpringConfigurationProcessor
+ + 415548 migrate ProxyHTTPToSPDYTest to use HttpClient to avoid intermittent
+   NPE part 2
+ + 415605 fix status code logging for async requests
+ + 415999 Fix some of FindBugs warnings
+ + 416015 Handle null Accept-Language and other headers
+ + 416096 DefaultServlet leaves open file descriptors with file sizes greater
+   than response buffer
+ + 416102 Clean up of async sendContent process
+ + 416103 Added AllowSymLinkAliasChecker.java
+ + 416251 ProxyHTTPToSPDYConnection now sends a 502 to the client if it
+   receives a rst frame from the upstream spdy server
+ + 416266 HttpServletResponse.encodeURL() encodes on first request when only
+   SessionTrackingMode.COOKIE is used
+ + 416314 jetty async client wrong behaviour for HEAD Method + Redirect.
+ + 416321 handle failure during blocked committing write
+ + 416453 Add comments to embedded SplitFileServer example
+ + 416477 Improved consumeAll error handling
+ + 416568 Simplified servlet exception logging
+ + 416577 enhanced shutdown handler to send shutdown at startup
+ + 416585 WebInfConfiguration examines webapp classloader first instead of its
+   parent when looking for container jars
+ + 416597 Allow classes and jars on the webappcontext extraclasspath to be
+   scanned for annotations
+ + 416663 Content-length set by resourcehandler
+ + 416674 run all jetty-ant tests on random ports
+ + 416679 Change warning to debug if no transaction manager present
+ + 416787 StringIndexOutOfBounds with a pathMap of ""
+ + 416940 avoid download of spring-beans.dtd
+ + 416990 JMX names statically unique
+ + 417110 Demo / html body end tag missing in authfail.html
+ + 417225 added Container.addEventListener method
+ + 417260 Protected targets matched as true URI path segments
+ + 417289 SPDY replace use of direct buffers with indirect buffers or make it
+   configurable
+ + 417475 Do not null context Trie during dynamic deploy
+ + 417574 Setting options with _JAVA_OPTIONS breaks run-forked with
+   <waitForChild>true</waitForChild>
+ + 417831 Remove jetty-logging.properties from distro/resources
+ + 418014 Handle NTFS canonical exceptions during alias check
+ + 418212 org.eclipse.jetty.spdy.server.http.SSLExternalServerTest hangs
+ + 418227 Null cookie value test
+
+jetty-9.1.0.M0 - 16 September 2013
+ + 393473 Add support for JSR-356 (javax.websocket) draft
+ + 395444 Websockets not working with Chrome (deflate problem)
+ + 396562 Add an implementation of RequestLog that supports Slf4j
+ + 398467 Servlet 3.1 Non Blocking IO
+ + 402984 WebSocket Upgrade must honor case insensitive header fields in
+   upgrade request
+ + 403280 Update to javax.el 2.2.4
+ + 403380 Introduce WebSocketTimeoutException to differentiate between EOF on
+   write and Timeout
+ + 403510 HttpSession maxInactiveInterval is not serialized in HashSession
+ + 403591 do not use the ConcurrentArrayBlockingQueue for thread pool, selector
+   and async request log
+ + 403817 Use of WebSocket Session.close() results in invalid status code
+ + 405188 HTTP 1.0 with GET returns internal IP address.
+ + 405422 Implement servlet3.1 spec sections 4.4.3 and 8.1.4 for new
+   HttpSessionIdListener class
+ + 405432 Check implementation of section 13.4.1 @ServletSecurity for
+   @HttpConstraint and HttpMethodConstraint clarifications
+ + 405435 Implement servlet3.1 section 13.6.3 for 303 redirects for Form auth
+ + 405437 Implement section 13.8.4 Uncovered HTTP methods
+ + 405525 Throw IllegalArgumentException if filter or servlet name is null or
+   empty string in ServletContext.addXXX() methods
+ + 405526 Deployment must fail if more than 1 servlet maps to same url pattern
+ + 405531 Implement Part.getSubmittedFileName()
+ + 405533 Implement special role ** for security constraints
+ + 405535 Implement Request.isUserInRole(role) check security-role-refs
+   defaulting to security-role if no matching ref
+ + 405944 Check annotation and resource injection is supported for
+   AsyncListener
+ + 406759 supressed stacktrace in ReferrerPushStrategyTest
+ + 407708 HttpUpgradeHandler must support injection
+ + 408782 Transparent Proxy - rewrite URL is ignoring query strings.
+ + 408904 Enhance CommandlineBuilder to not escape strings inside single quotes
+ + 409403 fix IllegalStateException when SPDY is used and the response is
+   written through BufferUtil.writeTo byte by byte
+ + 409796 fix and cleanup ReferrerPushStrategy. There's more work to do here,
+   so it remains @Ignore for now
+ + 409953 return buffer.slice() instead of buffer.asReadOnlyBuffer() in
+   ResourceCache to avoid using inefficent path in BufferUtil.writeTo
+ + 410083 Jetty clients submits incomplete URL to proxy.
+ + 410098 inject accept-encoding header for all http requests through SPDY as
+   SPDY clients MUST support spdy. Also remove two new tests that have been to
+   implementation agnostic and not needed anymore due to recent code changes
+ + 410246 HttpClient with proxy does not tunnel HTTPS requests.
+ + 410341 suppress stacktraces that happen during test setup shutdown after
+   successful test run
+ + 410800 Make RewritePatternRule queryString aware
+ + 411069 better set compiler defaults to 1.7, including webdefault.xml for jsp
+ + 411934 War overlay configuration assumes src/main/webapp exists
+ + 412205 SSL handshake failure leads to unresponsive UpgradeConnection
+ + 412418 HttpTransportOverSPDY fix race condition while sending push streams
+   that could cause push data not to be sent. Fixes intermittent test issues in
+   ReferrerPushStrategyTest
+ + 412729 SPDYClient needs a Promise-based connect() method.
+ + 412829 Allow any mappings from web-default.xml to be overridden by web.xml
+ + 412830 Error Page match ServletException then root cause
+ + 412840 remove Future in SPDYClient.connect() and return Session instead in
+   blocking version
+ + 412934 Ignore any re-definition of an init-param within a descriptor
+ + 412935 setLocale is not an explicit set of character encoding
+ + 412940 minor threadsafe fixes
+ + 413018 ServletContext.addListener() should throw IllegalArgumentException if
+   arg is not correct type of listener
+ + 413020 Second call to HttpSession.invalidate() should throw exception 413019
+   HttpSession.getCreateTime() should throw exception after session is
+   invalidated
+ + 413291 Avoid SPDY double dispatch
+ + 413387 onResponseHeaders is not called multiple times when multiple
+   redirects occur.
+ + 413484 setAttribute in nosql session management better handles _dirty status
+ + 413531 Introduce pluggable transports for HttpClient.
+ + 413684 deprecated unsafe alias checkers
+ + 413737 hide stacktrace in ReferrerPushStrategyTest
+ + 413901 isAsyncStarted remains true while original request is dispatched
+ + 414167 WebSocket handshake upgrade from FireFox fails due to keep-alive
+ + 414431 Avoid debug NPE race
+ + 414635 Modular start.d and jetty.base property
+ + 414640 HTTP header value encoding
+ + 414725 Annotation Scanning should exclude webapp basedir from path
+   validation checks
+ + 414731 Request.getCookies() should return null if there are no cookies
+ + 414740 Removed the parent peeking Loader
+ + 414891 Errors thrown by ReadListener and WriteListener not handled
+   correctly.
+ + 414898 Only upgrade v0 to v1 cookies on dquote , ; backslash space and tab
+   in the value
+ + 414913 WebSocket / Performance - reduce ByteBuffer allocation/copying during
+   generation/writing
+ + 414923 CompactPathRule needs to also compact the uri
+ + 415047 Create URIs lazily in HttpClient.
+ + 415062 SelectorManager wakeup optimisation.
+ + 415131 Avoid autoboxing on debug
+ + 415192 <jsp-file> maps to JspPropertyGroupServlet instead of JspServlet
+ + 415194 Deployer gives management of context to context collection
+ + 415302 
+ + 415314 Jetty should not commit response on output if <
+   Response.setBufferSize() bytes are written
+ + 415330 Avoid multiple callbacks at EOF
+ + 415401 WebAppProvider: override XmlConfiguration.initializeDefaults
+ + 415548 migrate ProxyHTTPToSPDYTest to use HttpClient to avoid intermittent
+   NPE part 2
+ + 415605 fix status code logging for async requests
+ + 415641 Remove remaining calls to deprecated HttpTranspoert.send
+ + 415656 SPDY - add IdleTimeout per Stream functionality
+ + 415744 Reduce Future usage in websocket
+ + 415745 Include followed by forward using a PrintWriter incurs unnecessary
+   delay
+ + 415780 fix StreamAlreadyCommittedException in spdy build
+ + 415825 fix stop support in modular start setup
+ + 415826 modules initialised with --add-to-start and --add-to-startd
+ + 415827 jetty-start / update --help text for new command line options
+ + 415830 jetty-start / add more TestUseCases for home + base + modules
+   configurations
+ + 415831 rename ini keyword from MODULES= to --module=
+ + 415832 jetty-start / fix ClassNotFound exception when starting from empty
+   base directory
+ + 415839 jetty-start / warning about need for --exec given when not needed by
+   default configuration
+ + 415899 jetty-start / add --lib=<cp> capability from Jetty 7/8
+ + 415913 support bootlib and download in modules
+ + 415999 Fix some of FindBugs warnings
+ + 416015 Handle null Accept-Language and other headers
+ + 416026 improve error handlig in SPDY parsers
+ + 416096 DefaultServlet leaves open file descriptors with file sizes greater
+   than response buffer
+ + 416102 Clean up of async sendContent process
+ + 416103 Added AllowSymLinkAliasChecker.java
+ + 416143 mod file format uses [type]
+ + 416242 respect persistence headers in ProxyHTTPSPDYConnection
+ + 416251 ProxyHTTPToSPDYConnection now sends a 502 to the client if it
+   receives a rst frame from the upstream spdy server
+ + 416266 HttpServletResponse.encodeURL() encodes on first request when only
+   SessionTrackingMode.COOKIE is used
+ + 416314 jetty async client wrong behaviour for HEAD Method + Redirect.
+ + 416321 handle failure during blocked committing write
+ + 416477 Improved consumeAll error handling
+ + 416568 Simplified servlet exception logging
+ + 416585 WebInfConfiguration examines webapp classloader first instead of its
+   parent when looking for container jars
+ + 416597 Allow classes and jars on the webappcontext extraclasspath to be
+   scanned for annotations
+ + 416663 Content-length set by resourcehandler
+ + 416674 run all jetty-ant tests on random ports
+ + 416679 Change warning to debug if no transaction manager present
+ + 416680 remove uncovered constraint warning
+ + 416681 Remove unnecessary security constraints in test-jetty-webapp
+ + 416763 WebSocket / Jsr Session.getPathParameters() is empty
+ + 416764 WebSocket / Jsr Session.getRequestURI() is missing scheme + host +
+   port + query parameters
+ + 416787 StringIndexOutOfBounds with a pathMap of ""
+ + 416812 Don't start WebSocketClient for every context
+ + 416990 JMX names statically unique
+ + 417022 Request attribute access to Server,HttpChannel & HttpConnection
+ + 417023 Add Default404Servlet if no default servlet set
+ + 417108 demo-base uses HTTPS
+ + 417109 Demo / Jaas test fails to find etc/login.conf
+ + 417110 Demo / html body end tag missing in authfail.html
+ + 417111 Demo / login with admin/admin fails
+ + 417133 WebSocket / deflate-frame should accumulate decompress byte buffers
+   properly
+ + 417134 WebSocket / Jsr
+   ServerEndpointConfig.Configurator.getNegotiatedExtensions() is never used
+ + 417225 added Container.addEventListener method
+ + 417260 Protected targets matched as true URI path segments
+
 jetty-8.1.13.v20130916 - 16 September 2013
  + 412629 PropertyFileLoginModule doesn't cache user configuration file even
    for refreshInterval=0
@@ -107,6 +1173,391 @@ jetty-7.6.13.v20130916 - 16 September 2013
    parent when looking for container jars
  + 416990 JMX names statically unique
 
+jetty-9.0.5.v20130815 - 15 August 2013
+ + 414898 Only upgrade v0 to v1 cookies on dquote , ; backslash space and tab
+   in the value
+ + 404468 Ported jetty-http-spi to Jetty-9
+ + 405424 add X-Powered-By and Server header to SPDY
+ + 405535 implement Request.isUserInRole(role) check security-role-refs
+   defaulting to security-role if no matching ref
+ + 408235 SPDYtoHTTP proxy fix: remove hop headers from upstream server
+ + 409028 Jetty HttpClient does not work with proxy CONNECT method.
+ + 409282 fix intermittently failing MaxConcurrentStreamTest
+ + 409845 add test that makes sure that DataFrameGenerator correctly prepends
+   the header information
+ + 410498 ignore type of exception in
+   GoAwayTest.testDataNotProcessedAfterGoAway
+ + 410668 HTTP client should support the PATCH method.
+ + 410800 Make RewritePatternRule queryString aware
+ + 410805 StandardSession: remove all frameBytes for a given stream from queue
+   if the stream is reset
+ + 411216 RequestLogHandler handles async completion
+ + 411458 MultiPartFilter getParameterMap doesn't preserve multivalued
+   parameters 411459  MultiPartFilter.Wrapper getParameter should use charset
+   encoding of part
+ + 411538 Use Replacement character for bad parameter % encodings
+ + 411545 SslConnection.DecryptedEndpoint.fill() sometimes misses a few network
+   bytes
+ + 411755 MultiPartInputStreamParser fails on base64 encoded content
+ + 411844 ArrayIndexOutOfBoundsException on wild URL.
+ + 411909 GzipFilter flushbuffer() results in erroneous finish() call
+ + 412234 fix bug where NetworkTrafficSelectChannelEndpoint counted bytes wrong
+   on incomplete writes
+ + 412318 HttpChannel fix multiple calls to _transport.completed() if handle()
+   is called multiple times while the channel is COMPLETED
+ + 412418 HttpTransportOverSPDY fix race condition while sending push streams
+   that could cause push data not to be sent. Fixes intermittent test issues in
+   ReferrerPushStrategyTest
+ + 412442 Avoid connection timeout after FIN-FIN close
+ + 412466 Improved search for unset JETTY_HOME
+ + 412608 EOF Chunk not sent on inputstream static content
+ + 412629 PropertyFileLoginModule doesn't cache user configuration file even
+   for refreshInterval=0
+ + 412637 ShutdownMonitorThread already started
+ + 412712 HttpClient does not send the terminal chunk after partial writes.
+ + 412713 add dumpOnStart configuration to jetty-maven-plugin
+ + 412750 HttpClient close expired connections fix
+ + 412814 HttpClient calling CompleteListener.onComplete() twice.
+ + 412846 jetty Http Client Connection through Proxy is failing with Timeout.
+ + 412938 Request.setCharacterEncoding now throws UnsupportedEncodingException
+   instead of UnsupportedCharsetException
+ + 413034 Multiple webapps redeploy returns NamingException with AppDynamics
+   javaagent
+ + 413066 accept lower case method: head
+ + 413108 HttpClient hardcodes dispatchIO=false when using SSL.
+ + 413113 Inconsistent Request.getURI() when adding parameters via
+   Request.param().
+ + 413154 ContextHandlerCollection defers virtual host handling to
+   ContextHandler
+ + 413155 HttpTransportOverSPDY remove constructor argument for version and get
+   version from stream.getSession instead
+ + 413371 Default JSON.Converters for List and Set.
+ + 413372 JSON Enum uses name rather than toString()
+ + 413393 better logging of bad URLs in Resources
+ + 413486 SessionCookieConfig setters should throw IllegalStateException if
+   called after context started
+ + 413568 Made AJP worker name generic
+ + 413684 Trailing slash shows JSP source
+ + 413901 isAsyncStarted remains true while original request is dispatched
+ + 414085 Add jetty-continuations to plugin dependencies
+ + 414101 Do not escape special characters in cookies
+ + 414235 RequestLogHandler configured on a context fails to handle forwarded
+   requests
+ + 414393 StringIndexOutofBoundsException with > 8k multipart content without
+   CR or LF
+ + 414449 Added HttpParser strict mode for case sensitivity
+ + 414507 Ensure AnnotationParser ignores parent dir hierarchy when checking
+   for hidden dirnames
+ + 414625 final static version fields
+ + 414640 HTTP header value encoding
+ + 414652 WebSocket's sendMessage() may hang on congested connections.
+ + 414727 Ensure asynchronously flushed resources are closed
+ + 414763 Added org.eclipse.jetty.util.log.stderr.ESCAPE option
+ + 414833 HttpSessionListener.destroy must be invoked in reverse order
+ + 414840 Request.login() throws NPE if username is null
+ + 414951 QueuedThreadPool fix constructor that missed to pass the idleTimeout
+ + 414972 HttpClient may read bytes with pre-tunnelled connection.
+
+jetty-9.0.4.v20130625 - 25 June 2013
+ + 396706 CGI support parameters
+ + 397051 Make JDBCLoginService data members protected to facilitate
+   subclassing
+ + 397193 MongoSessionManager refresh updates last access time
+ + 398467 Servlet 3.1 Non Blocking IO
+ + 400503 WebSocket - squelch legitimate Exceptions during testing to avoid
+   false positives
+ + 401027 javadoc JMX annotations
+ + 404508 enable overlay deployer
+ + 405188 HTTP 1.0 with GET returns internal IP address.
+ + 405313 Websocket client SSL hostname verification is broken, always defaults
+   to raw IP as String
+ + 406759 supressed stacktrace in ReferrerPushStrategyTest
+ + 406923 Accept CRLF or LF but not CR as line termination
+ + 407246 Test harness checked results in callbacks ignored.
+ + 407325 Test Failure:
+   org.eclipse.jetty.servlets.EventSourceServletTest.testEncoding
+ + 407326 Test Failure:
+   org.eclipse.jetty.client.HttpClientStreamTest.testInputStreamResponseListenerFailedBeforeResponse[0].
+ + 407342 ReloadedSessionMissingClassTest uses class compiled with jdk7
+ + 407386 Cookies not copied in ServletWebSocketRequest
+ + 407469 Method parameters for @OnWebSocketError should support Throwable
+ + 407470 Javadoc for @OnWebSocketFrame incorrectly references WebSocketFrame
+   object
+ + 407491 Better handle empty Accept-Language
+ + 407614 added excludedMimeTypes to gzipFilter
+ + 407812 jetty-maven-plugin can not handle whitespaces in equivalent of
+   WEB-INF/classes paths
+ + 407931 Add toggle for failing on servlet availability
+ + 407976 JDBCSessionIdManager potentially leaves server in bad state after
+   startup
+ + 408077 HashSessionManager leaves file handles open after being stopped
+ + 408117 isAsyncStarted is false on redispatch
+ + 408118 NullPointerException when parsing request cookies
+ + 408167 JDBCSessionManager don't mark session as dirty if same attribute
+   value set
+ + 408281 Inconsistent start/stop handling in ContainerLifeCycle
+ + 408446 Multipart parsing issue with boundry and charset in ContentType
+   header
+ + 408529 Etags set in 304 response
+ + 408600 set correct jetty.url in all pom files
+ + 408642 setContentType from addHeader
+ + 408662 In pax-web servlet services requests even if init() has not finished
+   running
+ + 408709 refactor test-webapp's chat application. Now there's only a single
+   request for user login and initial chat message.
+ + 408720 NPE in AsyncContext.getRequest()
+ + 408723 Jetty Maven plugin reload ignores web.xml listeners
+ + 408768 JSTL jars not scanned by jetty-ant
+ + 408771 Problem with ShutdownMonitor for jetty-ant
+ + 408782 Transparent Proxy - rewrite URL is ignoring query strings.
+ + 408806 getParameter returns null on Multipart request if called before
+   request.getPart()/getParts()
+ + 408904 Enhance CommandlineBuilder to not escape strings inside single quotes
+ + 408909 GzipFilter setting of headers when reset and/or not compressed
+ + 408910 META-INF/jetty-webapp-context.xml file should be able to refer to
+   bundle-relative locations
+ + 408923 Need to be able to configure the ThreadPool for the default jetty
+   server in osgi
+ + 408945 XML Args ignored without DTD
+ + 409012 added reference to example rewrite rules
+ + 409133 Empty <welcome-file> causes StackOverflowError
+ + 409228 Set jetty.home property so config files work even if deployed inside
+   a bundle
+ + 409403 fix IllegalStateException when SPDY is used and the response is
+   written through BufferUtil.writeTo byte by byte
+ + 409436 NPE on context restart using dynamic servlet registration
+ + 409441 jetty.xml threadpool arg injection
+ + 409449 Ensure servlets, filters and listeners added via dynamic
+   registration, annotations or descriptors are cleaned on context restarts
+ + 409545 Change HttpChannel contract
+ + 409556 Resource files not closed
+ + 409598 spdy: Fix NPE when a broken client tried to create duplicate stream
+   IDs
+ + 409684 Ids and properties not set for execution of jetty xml config files
+   with mvn plugin
+ + 409796 fix intermittent test issue in
+   ReferrerPushStrategy.testResourceOrder. Happened when the client got closed
+   before the server finished sending all data frames. Client waits now until
+   all data is received.
+ + 409801 Jetty should allow webdefault to be specified using a relative
+   location when running in OSGi
+ + 409842 Suspended request completed by a request thread does not set read
+   interest.
+ + 409953 return buffer.slice() instead of buffer.asReadOnlyBuffer() in
+   ResourceCache to avoid using inefficent path in BufferUtil.writeTo
+ + 409978 Websocket shouldn't create HttpSession if not present
+ + 410083 Jetty clients submits incomplete URL to proxy.
+ + 410098 inject accept-encoding header for all http requests through SPDY as
+   SPDY clients MUST support spdy. Also remove two new tests that have been to
+   implementation agnostic and not needed anymore due to recent code changes
+ + 410175 WebSocketSession#isSecure() doesn't return true for SSL session on
+   the server side
+ + 410246 HttpClient with proxy does not tunnel HTTPS requests.
+ + 410337 throw EofException instead of EOFException in HttpOutput.write() if
+   HttpOutpyt is closed
+ + 410341 suppress stacktraces that happen during test setup shutdown after
+   successful test run
+ + 410370 WebSocketCreator.createWebSocket() should use servlet specific
+   parameters
+ + 410372 Make SSL client certificate information available to server
+   websockets
+ + 410386 WebSocket Session.getUpgradeRequest().getRequestURI() returns bad URI
+   on server side
+ + 410405 Avoid NPE for requestDispatcher(../)
+ + 410469 UpgradeRequest is sent twice when using SSL, one fails warning about
+   WritePendingException
+ + 410522 jetty start broken for command line options
+ + 410537 Exceptions during @OnWebSocketConnect not reported to
+   @OnWebSocketError
+ + 410559 Removed FillInterest race
+ + 410630 MongoSessionManager conflicting session update op
+ + 410693 ServletContextHandler.setHandler does not relink handlers - check for
+   null
+ + 410750 NoSQLSessions: implement session context data persistence across
+   server restarts
+ + 410799 errors while creating push streams in HttpTransportOverSPDY are now
+   logged to debug instead of warn
+ + 410893 async support defaults to false for spec created servlets and filters
+ + 410911 Continuation isExpired handling.
+ + 410995 Avoid reverse DNS lookups when creating SSLEngines.
+ + 411061 fix cookie handling in spdy. If two different HTTP headers with the
+   same name are set, they should be translated to a single multiheader value
+   according to:
+   http://www.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3#TOC-2.6.10-Name-Value-Header-Block.
+   That applies for Set-Cookie headers for example. Before this changed
+   duplicate header names have overwritten the previous one
+ + 411135 HttpClient may send proxied https requests to the proxy instead of
+   the target server.
+ + 411340 add comment why executeOnFillable defaults to true
+ + 411545 SslConnection.DecryptedEndpoint.fill() sometimes misses a few network
+   bytes
+
+jetty-9.0.3.v20130506 - 06 May 2013
+ + 404010 fix cast exception in mongodb session manager
+ + 404911 WebSocketCloseTest fails spuriously
+ + 405281 allow filemappedbuffers to not be used
+ + 405327 Modular Start.ini
+ + 405530 Wrap AsyncContext to throw ISE after complete
+ + 405537 NPE in rendering JSP using SPDY and wrapped ServletRequest
+ + 405570 spdy push: resource ordering and sequential push.
+ + 405631 Plugin gives error when its started twice
+ + 405925 Redeploy with jetty-maven-plugin fails
+ + 406015 Query parameters and POST queries. Fixed proxy case where the path is
+   rewritten to be absolute.
+ + 406202 re-enabled connector statistics
+ + 406214 fix constructor for PushSynInfo ignores timeout, remove timeout for
+   creating push streams in HttpTransportOverSPDY
+ + 406272 Security constraints with multiple http-method-omissions can be
+   incorrectly applied
+ + 406390 406617 removed tiny race from handling of suspend and complete
+ + 406437 Digest Auth supports out of order nc
+ + 406449 Session's disconnect not detected
+ + 406617 Spin in Request.recycle
+ + 406618 Jetty startup in OSGi Equinox fails when using option
+   jetty.home.bundle=org.eclipse.jetty.osgi.boot
+ + 406753 jetty-runner contains invalid signature files
+ + 406768 Improved handling of static content resources
+ + 406861 IPv6 redirects fail.
+ + 406923 Accept CRLF or LF but not CR as line termination
+ + 406962 Improve attribute names in Request
+ + 407075 Do not dispatch from complete
+ + 407135 Unauthorized response causes retry loop.
+ + 407136 @PreDestroy called after Servlet.destroy()
+ + 407173 java.lang.IllegalStateException: null when using JDBCSessionManager
+ + 407214 Reduce build logging of OSGi modules
+
+jetty-9.0.2.v20130417 - 17 April 2013
+ + 364921 FIN WAIT sockets
+ + 402885 reuse Deflaters in GzipFilter
+ + 403591 do not use the ConcurrentArrayBlockingQueue for thread pool, selector
+   and async request log
+ + 404511 fixed poor methods in ArrayTernaryTrie
+ + 405119 Tidy up comments and code formatting for osgi
+ + 405352 Servlet init-param always overridden by WebServlet annotation
+ + 405364 spdy imeplement MAX_CONCURRENT_STREAMS
+ + 405449 spdy improve handling of duplicate stream Ids
+ + 405540 ServletContextListeners call in reverse in doStop
+ + 405551 InputStreamResponseListener.await returns null when request fails.
+ + 405679 example other server for documentation
+
+jetty-9.0.1.v20130408 - 08 April 2013
+ + 384552 add comment to jetty-https.xml describing keymanager password
+ + 385488 non existing resources in collection are just warnings
+ + 392129 fixed merged of handling of timeouts after startAsync
+ + 393971 Improve setParentLoaderPriorty javadoc
+ + 393972 Improve WebAppContext classloading javadoc
+ + 395620 do not managed inherited life cycle listeners
+ + 396562 Add an implementation of RequestLog that supports Slf4j
+ + 399967 Destroyables destroyed on undeploy and shutdown hook
+ + 400142 ConcurrentModificationException in JDBC SessionManger
+ + 400144 When loading a session fails the JDBCSessionManger produces duplicate
+   session IDs
+ + 400689 Add support for Proxy authentication.
+ + 401150 close input stream used from cached resource
+ + 401806 spdy push properly pass through request and response headers for
+   pushed resources
+ + 402397 InputStreamResponseListener early close inputStream cause hold lock.
+ + 402485 reseed secure random
+ + 402626 Do not required endpoint host checking by default in server and
+   configure in client
+ + 402666 Improve handling of TLS exceptions due to raw socket close.
+ + 402694 setuid as LifeCycle listener
+ + 402706 HttpSession.setMaxInactiveInterval(int) does not change JDBCSession
+   expiry
+ + 402726 WebAppContext references old WebSocket packages in system and server
+   classes
+ + 402735 jetty.sh to support status which is == check
+ + 402757 WebSocket client module can't be used with WebSocket server module in
+   the same WAR.
+ + 402833 Test harness for global error page and hide exception message from
+   reason string
+ + 402844 STOP.PORT & STOP.KEY behaviour has changed
+ + 402982 Premature initialization of Servlets
+ + 402984 WebSocket Upgrade must honor case insensitive header fields in
+   upgrade request
+ + 403122 Session replication fails with ClassNotFoundException when session
+   attribute is Java dynamic proxy
+ + 403280 Update to javax.el 2.2.4
+ + 403281 jetty.sh waits for started or failure before returning
+ + 403360 Named connectors
+ + 403370 move frameBytes.fail() call in StandardSession.flush() outside the
+   synchronized block to avoid deadlock
+ + 403373 WebSocket change timeout log level from warn -> info
+ + 403380 Introduce WebSocketTimeoutException to differentiate between EOF on
+   write and Timeout
+ + 403451 Review synchronization in SslConnection.
+ + 403510 HttpSession maxInactiveInterval is not serialized in HashSession
+ + 403513 jetty:run goal cannot be executed twice during the maven build
+ + 403570 Asynchronous Request Logging
+ + 403591 do not use the ConcurrentArrayBlockingQueue for thread pool, selector
+   and async request log
+ + 403817 Use of WebSocket Session.close() results in invalid status code
+ + 404029 port jetty-monitor to jetty-9 and activate it
+ + 404036 JDBCSessionIdManager.doStart() method should not call
+   cleanExpiredSessions() because Listeners can't be notified
+ + 404067 If cannot connect to db fail startup of JDBCSessionIdManager
+ + 404128 Add Vary headers rather than set them
+ + 404176 Jetty's AnnotationConfiguration class does not scan non-jar resources
+   on the container classpath
+ + 404204 Exception from inputstream cause hang or timeout.
+ + 404283 org.eclipse.jetty.util.Scanner.scanFile() dies with an NPE if
+   listFiles() returns null
+ + 404323 Improved parameterization of https and SPDY
+ + 404325 data constraint redirection does send default port
+ + 404326 set status when Request.setHandled(true) is called
+ + 404511 Replaced all StringMap usage with Tries
+ + 404517 Close connection if request received after half close
+ + 404610 Reintroduce ability to disallow TLS renegotiation.
+ + 404757 SPDY can only be built with the latest JDK version.
+ + 404789 Support IPv6 addresses in DoSFilter white list.
+ + 404881 Allow regexs for SslContextFactory.setIncludeCipherSuites() and
+   .setExcludeCipherSuites()
+ + 404889 SelectorManager accepts attachments with sockets
+ + 404906 servlets with load-on-startup = 0 are not fired up on jetty 9 startup
+ + 404958 Fixed Resource.newSystemResource striped / handling
+ + 405044 Query parameters lost for non GET or POST.
+
+jetty-9.0.0.v20130308 - 08 March 2013
+ + 399070 add updated version of npn-boot jar to start.ini
+ + 399799 do not hold lock while calling invalidation listeners
+ + 399967 Destroyables destroyed on undeploy and shutdown hook
+ + 400312 ServletContextListener.contextInitialized() is not called when added
+   in ServletContainerInitializer.onStartup
+ + 401495 removed unused getOutputStream
+ + 401531 StringIndexOutOfBoundsException for "/*" <url-pattern> of
+   <jsp-property-group> fix for multiple mappings to *.jsp
+ + 401641 Fixed MBean setter for String[]
+ + 401642 Less verbose INFOs
+ + 401643 Improved Authentication exception messages and provided quiet servlet
+   exception
+ + 401644 Dump does not login user already logged in
+ + 401651 Abort request if maxRequestsQueuedPerDestination is reached.
+ + 401777 InputStreamResponseListener CJK byte (>=128) cause EOF.
+ + 401904 fixed getRemoteAddr to return IP instead of hostname
+ + 401908 Enhance DosFilter to allow dynamic configuration of attributes.
+ + 401966 Ensure OSGI WebApp as Service (WebAppContext) can be deployed only
+   through ServiceWebAppProvider
+ + 402008 Websocket blocking write hangs when remote client dies (or is killed)
+   without going thru Close handshake
+ + 402048 org.eclipse.jetty.server.ShutdownMonitor doesn't stop after the jetty
+   server is stopped
+ + 402075 Massive old gen growth when hit by lots of non persistent
+   connections.
+ + 402090 httpsender PendingState cause uncertain data send to server.
+ + 402106 fixed URI resize in HttpParser
+ + 402148 Update Javadoc for WebSocketServlet for new API
+ + 402154 WebSocket / Session.setIdleTimeout(ms) should support in-place idle
+   timeout changes
+ + 402185 updated javascript mime-type
+ + 402277 spdy proxy: fix race condition in nested push streams initiated by
+   upstream server. Fix several other small proxy issues
+ + 402316 HttpReceiver and null pointer exception.
+ + 402341 Host with default port causes redirects loop.
+ + 402726 WebAppContext references old WebSocket packages in system and server
+   classes
+ + 402757 WebSocket client module can't be used with WebSocket server module in
+   the same WAR
+
 jetty-8.1.12.v20130726 - 26 July 2013
  + 396706 CGI support parameters
  + 397193 MongoSessionManager refresh updates last access time
@@ -297,6 +1748,204 @@ jetty-7.6.10.v20130312 - 12 March 2013
  + 402833 Test harness for global error page and hide exception message from
    reason string
 
+jetty-9.0.0.RC2 - 24 February 2013
+ + Fix etc/jetty.xml TimerScheduler typo that is preventing normal startup
+ + Fix etc/jetty-https.xml ExcludeCipherSuites typo that prevents SSL startup
+ + Fix websocket memory use
+
+jetty-9.0.0.RC1 - 22 February 2013
+ + 227244 Remove import of backport-util-concurrent Arrays class
+ + 362854 Continuation implementations may deadlock.
+ + 376273 Early EOF because of SSL Protocol Error on
+   https://api-3t.paypal.com/nvp.
+ + 381521 allow compress methods to be configured
+ + 388103 Add API for tracking down upload progress.
+ + 394064 ensure that JarFile instances are closed on JarFileResource.release()
+ + 398649 ServletContextListener.contextDestroyed() is not called on
+   ContextHandler unregistration
+ + 399463 add start.ini documentation for OPTIONS. Remove reference to
+   start_config
+ + 399520 Websocket Server Connection needs session idle timeouts
+ + 399535 Websocket-client connect should have configurable connect timeout
+ + 400014 Http async client DNS performance.
+ + 400040 NullPointerException in HttpGenerator.prepareBuffers
+ + 400184 SslContextFactory change. Disable hostname verification if trustAll
+   is set
+ + 400255 Using WebSocket.maxMessageSize results in IllegalArgumentException
+ + 400434 Add support for an OutputStream ContentProvider.
+ + 400457 Thread context classloader hierarchy not searched when finding
+   webapp's java:comp/env
+ + 400512 ClientUpgradeRequet.addExtension() should fail if extension is not
+   installed
+ + 400555 HttpProxyEngine: Add http version header in response
+ + 400631 Calling flush() on HttpServletResponse.getOutputStream() after last
+   byte of body causes EofException.
+ + 400734 NPE for redirects with relative location.
+ + 400738 ResourceHandler doesn't support range requests
+ + 400848 Redirect fails with non-encoded location URIs.
+ + 400849 Conversation hangs if non-first request fails when queued.
+ + 400859 limit max size of writes from cached content
+ + 400864 Added LowResourcesMonitor
+ + 401177 Make org.eclipse.jetty.websocket.api.WebSocketAdapter threadsafe
+ + 401183 Handle push streams in new method StreamFrameListener.onPush()
+   instead of SessionFrameListener.syn()
+ + 401211 Remove requirement for jetty-websocket.jar in WEB-INF/lib
+ + 401317 Make Safari 5.x websocket support minVersion level error more clear
+ + 401382 Prevent parseAvailable from parsing next chunk when previous has not
+   been consumed. Handle no content-type in chunked request.
+ + 401414 Hostname verification fails.
+ + 401427 WebSocket messages sent from onConnect fail to be read by jetty
+   websocket-client
+ + 401474 Performance problem in org.eclipse.jetty.annotation.AnnotationParser
+ + 401485 zip file closed exception
+
+jetty-9.0.0.RC0 - 01 February 2013
+ + 362226 HttpConnection "wait" call causes thread resource exhaustion
+ + 370384 jetty-aggregate not used in jetty-distribution
+ + 381351 defaults for keymanager and trustmanager come from their factories
+   and not hardcoded
+ + 381521 Only set Vary header when content could be compressed
+ + 381689 Allow jetty-runner to specify listen host along with listen port
+ + 382237 support non java JSON classes
+ + 385306 added getURI method
+ + 391248 fixing localhost checking in statistics servlet
+ + 391249 fix for invalid XML node dispatchedTimeMean in statistics servlet
+ + 391345 fix missing br tag in statistics servlet
+ + 393933 remove deprecated classes/methods and consolidate some static methods
+   to SslContextFactory
+ + 393968 fix typo in javadoc
+ + 394541 remove continuation jar from distro, add as dep to test-jetty-webapp
+ + 395232 UpgradeRequest object passed to createWebSocket() has null Session
+ + 395444 Disabling Websocket Compress Extensions (not working with Chrome /
+   deflate problem)
+ + 396428 Test for WebSocket masking on client fragments per RFC 6455 Sec 5.1
+ + 396574 add JETTY_HOME as a location for pid to be found
+ + 396606 make spdy proxy capable of receiving SPDY and talk HTTP to the
+   upstream server
+ + 397168 backed of test timing
+ + 397769 TimerScheduler does not relinquish cancelled tasks.
+ + 398872 SslConnection should not be notified of idle timeouts. First
+   solution. Merge branch 'ssl_idle_timeout_ignored'.
+ + 399132 check parent dir of session store against file to be removed
+ + 399173 UpgradeRequest.getParameterMap() should never return null
+ + 399242 Reduce/eliminate false sharing in BlockingArrayQueue.
+ + 399319 Request.getURI() may return negative ports.
+ + 399324 HttpClient does not handle correctly UnresolvedAddressException.
+ + 399343 OnWebSocketConnect should use api.Session parameter instead.
+ + 399344 Add missing @OnWebSocketError annotation
+ + 399397 websocket-client needs better upgrade failure checks
+ + 399421 Add websocket.api.Session.disconnect() for harsh low level connection
+   disconnect
+ + 399515 Websocket-client connect issues should report to websocket onError
+   handlers
+ + 399516 Websocket UpgradeException should contain HTTP Request/Response
+   information
+ + 399566 Running org.eclipse.jetty.server.session.MaxInactiveMigrationTest
+   produces stack trace
+ + 399568 OSGi tests can't find websocket classes
+ + 399576 Server dumpStdErr throws exception if server is stopping
+ + 399669 Remove WebSocketConnection in favor of websocket.api.Session
+ + 399689 Websocket RFC6455 extension handshake fails if server doesn't have
+   extension
+ + 399703 made encoding error handling consistent
+ + 399721 Change <Ref id= ...> to <Ref refid= ...>
+
+jetty-9.0.0.M5 - 19 January 2013
+ + 367638 throw exception for excess form keys
+ + 381521 Only set Vary header when content could be compressed
+ + 391623 Making --stop with STOP.WAIT perform graceful shutdown
+ + 393158 java.lang.IllegalStateException when sending an empty InputStream
+ + 393220 remove dead code from ServletHandler and log ServletExceptions in
+   warn instead of debug
+ + 393733 WebSocketClient interface should support multiple connections
+ + 395885 ResourceCache should honor useFileMappedBuffer if set
+ + 396253 FilterRegistration wrong order
+ + 396459 Log specific message for empty request body for multipart mime
+   requests
+ + 396500 HttpClient Exchange takes forever to complete when less content sent
+   than Content-Length
+ + 396886 MultiPartFilter strips bad escaping on filename="..."
+ + 397110 Accept %uXXXX encodings in URIs
+ + 397111 Tolerate empty or excessive whitespace preceeding MultiParts
+ + 397112 Requests with byte-range throws NPE if requested file has no mimetype
+   (eg no file extension)
+ + 397114 run-forked with waitForChild=false can lock up
+ + 397130 maxFormContentSize set in jetty.xml is ignored
+ + 397190 improve ValidUrlRule to iterate on codepoints
+ + 397321 Wrong condition in default start.config for annotations
+ + 397535 Support pluggable alias checking to support symbolic links
+ + 397769 TimerScheduler does not relinquish cancelled tasks.
+ + 398105 Clean up WebSocketPolicy
+ + 398285 ProxyServlet mixes cookies from different clients.
+ + 398337 UTF-16 percent encoding in UTF-16 form content
+ + 398582 Move lib/jta jar into lib/jndi
+ + JETTY-1533 handle URL with no path
+
+jetty-9.0.0.M4 - 21 December 2012
+ + 392417 Prevent Cookie parsing interpreting unicode chars
+ + 393220 remove dead code from ServletHandler and log ServletExceptions in
+   warn instead of debug
+ + 393770 Error in ContextHandler.setEventListeners(EventListener[])
+ + 394210 spdy api rename stream.syn() to stream.push()
+ + 394211 spdy: Expose RemoteServerAddress and LocalServerAddress in
+   StandardSession
+ + 394294 Start web-bundles started before jetty
+ + 394370 Add integration test for client resetting SPDY push SYN's
+ + 394514 Preserve URI parameters in sendRedirect
+ + 394552 HEAD requests don't work for jetty-client.
+ + 394719 remove regex from classpath matching
+ + 394829 Session can not be restored after SessionManager.setIdleSavePeriod
+   has saved the session
+ + 394839 Allow multipart mime with no boundary
+ + 394854 optimised promise implementation
+ + 394870 Make enablement of remote access to test webapp configurable in
+   override-web.xml
+ + 395168 fix unavailable attributes when return type has annotation on super
+   class
+ + 395215 Multipart mime with just LF and no CRLF: add test for legacy filter
+ + 395220 New InputStream extension to allow a mix of EOL styles between
+   headers and content
+ + 395312 log.warn if a SPDY stream gets committed twice
+ + 395313 HttpTransportOverSPDY.send() does not rethrow exceptions, but call
+   Callback.failed() only
+ + 395314 Add missing flush() call after StandardSession.complete() has been
+   called. Some test cleanup.
+ + 395344 Move JSR-356 (Java WebSocket API) work off to Jetty 9.1.x
+ + 395380 add ValidUrlRule to jetty-rewrite
+ + 395394 allow logging from boot classloader
+ + 395574 port jetty-runner and StatisticsServlet to jetty-9
+ + 395605 class cast exception in XMLConfiguration fixed
+ + 395649 add jetty-setuid back into jetty 9 and distribution
+ + 395794 slightly modified fix for empty file extenstion to mime type mapping.
+   Added a default, so it will also work with unknown file extensions
+ + 396036 SPDY send controlFrames even if Stream is reset to avoid breaking the
+   compression context
+ + 396193 spdy remove timeout parameters from api and move them to the Info*
+   classes
+ + 396459 Log specific message for empty request body for multipart mime
+   requests
+ + 396460 Make ServerConnector configurable with jetty-maven-plugin
+ + 396472 org.eclipse.jetty.websocket needs to be removed from serverclasses as
+   it should only be a systemclass
+ + 396473 JettyWebXMlConfiguration does not reset serverclasses
+ + 396474 add websocket server classes to jetty-maven-plugin classpath
+ + 396475 Remove unneeded websocket-server dependency from test-jetty-webapp
+ + 396518 Websocket AB Tests should test for which side disconnected and
+   closed.wasClean
+ + 396687 missing jetty-io dependency in jetty-servlets
+ + JETTY-796 jetty ant plugin improvements
+
+jetty-9.0.0.M3 - 20 November 2012
+ + 391623 Add option to --stop to wait for target jetty to stop
+ + 392237 Port test-integration to jetty-9
+ + 392492 expect headers only examined for requests>=HTTP/1.1
+ + 392850 ContextLoaderListener not called in 9.0.0.M1 and M2
+ + 393075 1xx, 204, 304 responses ignore headers that suggest content
+ + 393832 start connectors last
+ + 393947 additional tests
+ + 394143 add jetty-all aggregate via release profile
+ + 394144 add jetty-jaspi
+
 jetty-8.1.9.v20130131 - 31 January 2013
  + 362226 HttpConnection "wait" call causes thread resource exhaustion
  + 367638 throw exception for excess form keys
@@ -392,6 +2041,44 @@ jetty-7.6.9.v20130131 - 31 January 2013
  + 398337 UTF-16 percent encoding in UTF-16 form content
  + 399132 check parent dir of session store against file to be removed
  + JETTY-1533 handle URL with no path
+ + 394215 Scheduled tasks throwing exceptions kill java.util.Timer thread.
+ + 394232 add jetty-ant into jetty9
+ + 394357 Make JarResource constructors protected
+ + 394370 Add unit tests for HttpTransportOverSPDY.send()
+ + 394383 add logging of the SSLEngine
+ + 394545 Add jetty-jaas dependency to jetty-maven-plugin
+ + 394671 Fix setting loglevel on commandline, organize import, fix javadoc
+ + JETTY-846 Support maven-war-plugin configuration for jetty-maven-plugin; fix
+   NPE
+
+jetty-9.0.0.M2 - 06 November 2012
+ + 371170 MongoSessionManager LastAccessTimeTest fails
+ + 391877 org.eclipse.jetty.webapp.FragmentDescriptor incorrectly reporting
+   duplicate others for after ordering
+ + 392237 Split jaas from jetty-plus into jetty-jaas and port the
+   test-jaas-webapp from codehaus
+ + 392239 Allow no error-code or exception for error-pages
+ + 392304 fixed intermittent client SSL failure. Correctly compact in flip2fill
+ + 392525 Add option to --stop-wait to specify timeout
+ + 392641 JDBC Sessions not scavenged if expired during downtime
+ + 392812 MongoSessionIDManager never purges old sessions
+ + 392959 Review HttpClient.getConversation(long).
+ + 393014 Mongodb purgevalid using query for purgeinvalid
+ + 393015 Mongodb purge not rescheduled
+ + 393075 Jetty WebSocket client cannot connect to Tomcat WebSocket Server
+ + 393218 add xsd=application/xml mime mapping to defaults
+ + 393291 Confusing log entry about (non) existing webAppSourceDirectory
+ + 393303 use jetty-web.xml to explicitly add the jetty packages that need
+   visability.   This commit also sucked in some changes made to help with the
+   documentation process (improving deployer configuration management
+ + 393363 Use Locale.ENGLISH for all toUpperCase and toLowerCase calls
+ + 393368 min websocket version
+ + 393383 delay onClose call until closeOut is done
+ + 393494 HashSessionManager can't delete unrestorable sessions on Windows
+ + JETTY-1547 Jetty does not honor web.xml
+   web-app/jsp-config/jsp-property-group/default-content-type
+ + JETTY-1549 jetty-maven-plugin fails to reload the LoginService properly
+ + JETTY-1550 virtual WEB-INF not created if project has overlays
 
 jetty-8.1.8.v20121106 - 06 November 2012
  + 371170 MongoSessionManager LastAccessTimeTest fails
@@ -468,55 +2155,156 @@ jetty-7.6.8.v20121106 - 06 November 2012
  + 393383 delay onClose call until closeOut is done
  + 393494 HashSessionManager can't delete unrestorable sessions on Windows
 
-jetty-8.1.7.v20120910 - 10 September 2012
- + 388895 Update dependencies for jetty-jndi
- + fix busy logging statement re: sessions
-
-jetty-8.1.6.v20120903 - 03 September 2012
- + 347130 Empty getResourcePaths due to ZipFileClosedException
- + 367591 Support Env variables in XmlConfiguration.
- + 377055 Prevent webapp classloader leaks
- + 379207 backported fixes from jetty-9 to make hierarchy work
- + 379423 Jetty URL Decoding fails for certain international characters
- + 383304 Reset PrintWriter on response recycle
- + 384847 better name
+jetty-9.0.0.M1 - 15 October 2012
+ + 369349 directory with spaces --dry-run fix
  + 385049 fix issue with pipelined connections when switching protocols
- + 385651 Message 'Address already in use' not specific enough
- + 386010 JspRuntimeContext rewraps System.err
- + 386591 add UnixCrypt note to about.html
- + 386714 used deferred auth for form login and error pages
  + 387896 populate session in SessionAuthentication as a valueBound in addition
    to activation so it is populate when needed
+ + 387919 throw EOFException on early eof from client on http requests
  + 387943 Catch CNFE when no jstl jars are installed
  + 387953 jstl does not work with jetty-7 in osgi
  + 388072 GZipFilter incorrectly gzips when Accept-Encoding: gzip; q=0
  + 388073 null session id from cookie causes NPE fixed
+ + 388079 AbstractHttpConnection. Flush the buffer before shutting output down
+   on error condition
  + 388102 Jetty HttpClient memory leaks when sending larger files
  + 388393 WebAppProvider doesn't work alongside OSGi deployer
  + 388502 handle earlyEOF with 500
  + 388652 Do not flush on handle return if request is suspended
- + JETTY-1501 Setting custom error response message changes page title
- + JETTY-1515 Include cookies on 304 responses from DefaultServlet.
- + JETTY-1527 handle requests with URIs like http://host  (ie no / )
- + JETTY-1529 Ensure new session that has just been authenticated does not get
-   renewed
- + JETTY-1532 HTTP headers decoded with platform's default encoding
- + JETTY-1541 fixed different behaviour for single byte writes
- + 385925 make SslContextFactory.setProtocols and
-   SslContextFactory.setCipherSuites preserve the order of the given parameters
-
-jetty-8.1.5.v20120716 - 16 June 2012
- + 376717 Balancer Servlet with round robin support, contribution, added
-   missing license
- + 379250 Server is added to shutdown hook twice
- + 380866 maxIdleTime set to 0 after session migration
- + 381399 Unable to stop a jetty instance that has not finished starting
- + 381401 Print log warning when stop attempt made with incorrect STOP.KEY
- + 381402 Make ContextHandler take set of protected directories
- + 381521 set Vary:Accept-Encoding header for content that might be compressed
- + 381639 CrossOriginFilter does not support Access-Control-Expose-Headers.
- + 381712 Support all declared servlets that implement
-   org.apache.jasper.servlet.JspServlet
+ + 388675 Non utf8 encoded query strings not decoded to parameter map using
+   queryEncoding
+ + 388706 Avoid unnecessary indirection through Charset.name
+ + 388895 Update dependencies for jetty-jndi
+ + 389390 AnnotationConfiguration is ignored if the metadata-complete attribute
+   is present in an override descriptor regardless of the value
+ + 389452 if web-fragment metadata-complete==true still scan its related jar if
+   there there is a ServletContainerInitializer, ensure webapp restarts work
+ + 389686 Fix reference to org.eclipse.jetty.util.log.stderr.LONG system
+   property in javadoc for StdErrLog
+ + 389956 Bad __context set in WebAppContext.start sequence with respect to ENC
+   setup
+ + 389965 OPTIONS should allow spaces in comma separated list
+ + 390108 Servlet 3.0 API for programmatic login doesn't appear to work
+ + 390161 Apply DeferredAuthentication fix to jaspi
+ + 390163 Implement ServletRegistration.Dynamic.setServletSecurity
+ + 390256 Remove Jetty6 Support
+ + 390263 Sec-WebSocket-Extensions from Chrome and Safari badly handled
+ + 390503 http-method-omission element not being processed
+ + 390560 The method AnnotationParser.getAnnotationHandlers(String) always
+   returns a empty collection.
+ + 391080 Multipart temp files can be left on disk from Request.getPart and
+   getParts
+ + 391082 No exception if multipart input stream incomplete
+ + 391140 Implement x-webkit-deflate-frame extension as-used by Chrome/Safari
+ + 391188 Files written with Request.getPart().write(filename) should not be
+   auto-deleted
+ + 391483 fix bad javadoc example in shutdown handler
+ + 391588 WebSocket Client does not set masking on close frames
+ + 391590 WebSocket client needs ability to set requested extensions
+ + 391591 WebSocket client should support x-webkit-deflate-frame
+ + 391622 Be lenient on RFC6265 restriction on duplicate cookie names in same
+   response
+ + 391623 Add option to --stop to wait for target jetty to stop
+ + JETTY-1515 Include cookies on 304 responses from DefaultServlet.
+ + JETTY-1532 HTTP headers decoded with platform's default encoding
+ + JETTY-1541 fixed different behaviour for single byte writes
+ + JETTY-1547 Jetty does not honor web.xml
+   web-app/jsp-config/jsp-property-group/default-content-type
+
+jetty-9.0.0.M0 - 21 September 2012
+ + 380924 xmlconfiguration <Configure and <New supports named constructors,
+   including dynamic ordering of parameters
+ + 380928 Implement new websocket close code
+ + 385448 migrate jetty jmx usage to be annotation based
+ + 387928 retire jetty-ajp
+ + 389639 set plugin version for jetty-jspc-maven-plugin
+
+jetty-8.1.7.v20120910 - 10 September 2012
+ + 388895 Update dependencies for jetty-jndi
+ + fix busy logging statement re: sessions
+
+jetty-7.6.7.v20120910 - 10 September 2012
+ + 388895 Update dependencies for jetty-jndi
+ + fix busy logging statement re: sessions
+
+jetty-8.1.6.v20120903 - 03 September 2012
+ + 347130 Empty getResourcePaths due to ZipFileClosedException
+ + 367591 Support Env variables in XmlConfiguration.
+ + 377055 Prevent webapp classloader leaks
+ + 379207 backported fixes from jetty-9 to make hierarchy work
+ + 379423 Jetty URL Decoding fails for certain international characters
+ + 383304 Reset PrintWriter on response recycle
+ + 384847 better name
+ + 385049 fix issue with pipelined connections when switching protocols
+ + 385651 Message 'Address already in use' not specific enough
+ + 385925 make SslContextFactory.setProtocols and
+   SslContextFactory.setCipherSuites preserve the order of the given parameters
+ + 386010 JspRuntimeContext rewraps System.err
+ + 386591 add UnixCrypt note to about.html
+ + 386714 used deferred auth for form login and error pages
+ + 387896 populate session in SessionAuthentication as a valueBound in addition
+   to activation so it is populate when needed
+ + 387943 Catch CNFE when no jstl jars are installed
+ + 387953 jstl does not work with jetty-7 in osgi
+ + 388072 GZipFilter incorrectly gzips when Accept-Encoding: gzip; q=0
+ + 388073 null session id from cookie causes NPE fixed
+ + 388102 Jetty HttpClient memory leaks when sending larger files
+ + 388393 WebAppProvider doesn't work alongside OSGi deployer
+ + 388502 handle earlyEOF with 500
+ + 388652 Do not flush on handle return if request is suspended
+ + JETTY-1501 Setting custom error response message changes page title
+ + JETTY-1515 Include cookies on 304 responses from DefaultServlet.
+ + JETTY-1527 handle requests with URIs like http://host  (ie no / )
+ + JETTY-1529 Ensure new session that has just been authenticated does not get
+   renewed
+ + JETTY-1532 HTTP headers decoded with platform's default encoding
+ + JETTY-1541 fixed different behaviour for single byte writes
+
+jetty-7.6.6.v20120903 - 03 September 2012
+ + 347130 Empty getResourcePaths due to ZipFileClosedException
+ + 367591 Support Env variables in XmlConfiguration.
+ + 377055 Prevent webapp classloader leaks
+ + 379207 backported fixes from jetty-9 to make hierarchy work
+ + 379423 Jetty URL Decoding fails for certain international characters
+ + 383304 Reset PrintWriter on response recycle
+ + 384847 better name
+ + 385049 fix issue with pipelined connections when switching protocols
+ + 385651 Message 'Address already in use' not specific enough
+ + 386010 JspRuntimeContext rewraps System.err
+ + 386591 add UnixCrypt note to about.html
+ + 386714 used deferred auth for form login and error pages
+ + 387896 populate session in SessionAuthentication as a valueBound in addition
+   to activation so it is populate when needed
+ + 387943 Catch CNFE when no jstl jars are installed
+ + 387953 jstl does not work with jetty-7 in osgi
+ + 388072 GZipFilter incorrectly gzips when Accept-Encoding: gzip; q=0
+ + 388073 null session id from cookie causes NPE fixed
+ + 388102 Jetty HttpClient memory leaks when sending larger files
+ + 388393 WebAppProvider doesn't work alongside OSGi deployer
+ + 388502 handle earlyEOF with 500
+ + 388652 Do not flush on handle return if request is suspended
+ + JETTY-1501 Setting custom error response message changes page title
+ + JETTY-1515 Include cookies on 304 responses from DefaultServlet.
+ + JETTY-1527 handle requests with URIs like http://host  (ie no / )
+ + JETTY-1529 Ensure new session that has just been authenticated does not get
+   renewed
+ + JETTY-1532 HTTP headers decoded with platform's default encoding
+ + JETTY-1541 fixed different behaviour for single byte writes
+ + 385925 make SslContextFactory.setProtocols and
+   SslContextFactory.setCipherSuites preserve the order of the given parameters
+
+jetty-8.1.5.v20120716 - 16 June 2012
+ + 376717 Balancer Servlet with round robin support, contribution, added
+   missing license
+ + 379250 Server is added to shutdown hook twice
+ + 380866 maxIdleTime set to 0 after session migration
+ + 381399 Unable to stop a jetty instance that has not finished starting
+ + 381401 Print log warning when stop attempt made with incorrect STOP.KEY
+ + 381402 Make ContextHandler take set of protected directories
+ + 381521 set Vary:Accept-Encoding header for content that might be compressed
+ + 381639 CrossOriginFilter does not support Access-Control-Expose-Headers.
+ + 381712 Support all declared servlets that implement
+   org.apache.jasper.servlet.JspServlet
  + 381825 leave URI params in forwarded requestURI
  + 381876 Monitor should wait for child to finish before exiting.
  + 382343 Jetty XML support for Map is broken.
@@ -536,6 +2324,32 @@ jetty-8.1.5.v20120716 - 16 June 2012
  + JETTY-1525 Show handle status in response debug message
  + JETTY-1530 refine search control on ldap login module
 
+jetty-7.6.5.v20120716 - 16 July 2012
+ + 376717 Balancer Servlet with round robin support, contribution, added
+   missing license
+ + 379250 Server is added to shutdown hook twice
+ + 380866 maxIdleTime set to 0 after session migration
+ + 381399 Unable to stop a jetty instance that has not finished starting
+ + 381401 Print log warning when stop attempt made with incorrect STOP.KEY
+ + 381402 Make ContextHandler take set of protected directories
+ + 381521 set Vary:Accept-Encoding header for content that might be compressed
+ + 381639 CrossOriginFilter does not support Access-Control-Expose-Headers.
+ + 381712 Support all declared servlets that implement
+   org.apache.jasper.servlet.JspServlet
+ + 381825 leave URI params in forwarded requestURI
+ + 381876 Monitor should wait for child to finish before exiting.
+ + 382343 Jetty XML support for Map is broken.
+ + 383251 500 for SocketExceptions
+ + 383881 WebSocketHandler sets request as handled
+ + 384254 revert change to writable when not dispatched
+ + 384847 CrossOriginFilter is not working.
+ + 384896 JDBCSessionManager fails to load existing sessions on oracle when
+   contextPath is /
+ + 384980 Jetty client unable to recover from Time outs when connection count
+   per address hits max.
+ + JETTY-1525 Show handle status in response debug message
+ + JETTY-1530 refine search control on ldap login module
+
 jetty-8.1.4.v20120524 - 24 May 2012
  + 367608 ignore the aysncrequestreadtest as it is known to fail and is waiting
    for a fix
@@ -569,6 +2383,38 @@ jetty-8.1.4.v20120524 - 24 May 2012
  + 380212 Clear buffer if parsing fails due to full buffer
  + 380222 JettyPolicyRuntimeTest failure
 
+jetty-7.6.4.v20120524 - 24 May 2012
+ + 367608 ignore the aysncrequestreadtest as it is known to fail and is waiting
+   for a fix
+ + 371853 Support bundleentry: protocol for webapp embedded as directory in
+   osgi bundle
+ + 373620 Add ch.qos.logback.access.jetty to the Import-Package for
+   jetty-osgi-boot-logback bundle
+ + 376152 apply context resources recursively
+ + 376801 Make JAAS login modules useable without jetty infrastructure
+ + 377391 Manifest updates to jetty-osgi-boot-logback
+ + 377492 NPE when deploying a Web Application Bundle with unresolved
+   Require-TldBundle
+ + 377550 set charset when content type is set
+ + 377587 ConnectHandler write will block on partial write
+ + 377610 New session not timed out if an old session is invalidated in scope
+   of same request
+ + 377709 Support for RequestParameterCallback missing
+ + 378242 Re-extract war on restart if incomplete extraction
+ + 378273 Remove default Bundle-Localization header
+ + 378487 Null out contextPath on Request.recycle
+ + 379015 Use factored jetty xml config files for defaults
+ + 379046 avoid closing idle connections from selector thread
+ + 379089 DefaultServlet ignores its resourceBase and uses context's
+   ResourceCollection when listing diretories
+ + 379194 ProxyServlet enhancement to enable easy creation of alternative
+   HttpClient implementations
+ + 379909 FormAuthenticator Rembers only the URL of first Request before
+   authentication
+ + 380034 last modified times taken from JarEntry for JarFile resources
+ + 380212 Clear buffer if parsing fails due to full buffer
+ + 380222 JettyPolicyRuntimeTest failure
+
 jetty-8.1.3.v20120416 - 16 April 2012
  + 349110 MultiPartFilter records the content-type in request params
  + 367172 Remove detection for slf4j NOPLogger
@@ -611,6 +2457,38 @@ jetty-8.1.3.v20120416 - 16 April 2012
    request.getParameter
  + JETTY-1504 HttpServletResponseWrapper ignored when using asyncContext?
 
+jetty-7.6.3.v20120416 - 16 April 2012
+ + 367172 Remove detection for slf4j NOPLogger
+ + 373269 Make ServletHandler.notFound() method impl do nothing - override to
+   send back 404.
+ + 373421 address potential race condition related to the nonce queue removing
+   the same nonce twice
+ + 373952 bind called too frequently on refresh
+ + 374018 correctly handle requestperminuted underflow
+ + 374252 SslConnection.onClose() does not forward to nested connection.
+ + 374258 SPDY leaks SSLEngines. Made the test more reliable.
+ + 374367 NPE in QueuedThreadPool.dump() with early java6 jvms
+ + 374475 Response.sendRedirect does not encode UTF-8 characters properly
+ + 374881 Set copyWebInf to false by default
+ + 374891 enhancement to how ProxyServlet determines the proxy target
+ + 375009 Filter initialization error will throw MultiException
+ + 375083 Flow control should take in account window size changes from
+   concurrent SETTINGS
+ + 375096 If starting a server instance fails in osgi it is cleaned up.
+ + 375490 NPE with --help on command line
+ + 375509 Stalled stream stalls other streams or session control frames. Now
+   using a "death pill" instead of a boolean in order to avoid race conditions
+   where DataInfos were read from the queue (but the boolean not updated yet),
+   and viceversa.
+ + 375594 fixed SSL tests so they are not order dependent
+ + 375709 Ensure resolveTempDirectory failure does not deadlock; improve error
+   message
+ + 375970 HttpServletRequest.getRemoteAddr() returns null when HTTP is over
+   SPDY.
+ + 376201 HalfClosed state not handled properly. Addendum to restore previous
+   behavior, where a closed stream was also half closed.
+ + JETTY-1504 HttpServletResponseWrapper ignored when using asyncContext?
+
 jetty-8.1.2.v20120308 - 08 March 2012
  + 370387 SafariWebsocketDraft0Test failure during build.
  + 371168 Update ClientCrossContextSessionTest
@@ -633,6 +2511,27 @@ jetty-8.1.2.v20120308 - 08 March 2012
  + JETTY-1489 WebAppProvider attempts to deploy .svn folder
  + JETTY-1494 .
 
+jetty-7.6.2.v20120308 - 08 March 2012
+ + 370387 SafariWebsocketDraft0Test failure during build.
+ + 371168 Update ClientCrossContextSessionTest
+ + 372093 handle quotes in Require-Bundle manifest string
+ + 372457 Big response + slow clients + pipelined requests cause Jetty spinning
+   and eventually closing connections. Added a TODO for a method renaming that
+   will happen in the next major release (to avoid break implementers).
+ + 372487 JDBCSessionManager does not work with Oracle
+ + 372806 Command line should accept relative paths for xml config files
+ + 373037 jetty.server.Response.setContentLength(int) should not close a Writer
+   when length=0
+ + 373162 add improved implementation for getParameterMap(), needs a test
+   though and the existing setup doesn't seem like it would easily support the
+   needed test so need to do that still
+ + 373306 Set default user agent extraction pattern for UserAgentFilter
+ + 373567 cert validation issue with ocsp and crldp always being enabled when
+   validating turned on fixed
+ + JETTY-1409 GzipFilter will double-compress application/x-gzip content
+ + JETTY-1489 WebAppProvider attempts to deploy .svn folder
+ + JETTY-1494 .
+
 jetty-8.1.1.v20120215 - 15 February 2012
  + 369121 simplified test
  + 370120 jvm arguments added via start.ini and --exec are missing spaces
@@ -647,12 +2546,32 @@ jetty-8.1.1.v20120215 - 15 February 2012
  + JETTY-1484 Add option for HashSessionManager to delete session files if it
    can't restore them
 
+jetty-7.6.1.v20120215 - 15 February 2012
+ + 369121 simplified test
+ + 370120 jvm arguments added via start.ini and --exec are missing spaces
+ + 370137 SslContextFactory does not respect order for
+   [included|excluded]Protocols() and [included|excluded]CipherSuites().
+ + 370368 resolve stack overflow in mongo db session manager
+ + 370386 Remove META-INF from jetty distro
+ + 371040 nosqlsession needs to call correct super contructor for new sessions
+ + 371041 valid was not being set to new mongo db sessions, and the call to
+   mongodb api was wrong in isIdInUse
+ + 371162 NPE protection for nested security handlers
+ + JETTY-1484 Add option for HashSessionManager to delete session files if it
+   can't restore them
+
 jetty-8.1.0.v20120127 - 27 January 2012
  + 368773 allow authentication to be set by non securityHandler handlers
  + 368992 avoid update key while flushing during a write
  + 369216 turned off the shared resource cache
  + 369349 replace quotes with a space escape method
 
+jetty-7.6.0.v20120127 - 27 January 2012
+ + 368773 allow authentication to be set by non securityHandler handlers
+ + 368992 avoid update key while flushing during a write
+ + 369216 turned off the shared resource cache
+ + 369349 replace quotes with a space escape method
+
 jetty-8.1.0.RC5 - 20 January 2012
  + 359329 Prevent reinvocation of LoginModule.login with jaspi for already
    authed user
@@ -668,20 +2587,37 @@ jetty-8.1.0.RC5 - 20 January 2012
  + JETTY-1475 made output state fields volatile to provide memory barrier for
    non dispatched thread IO
 
-jetty-8.1.0.RC4 - 13 January 2012
- + 365048 jetty Http client does not send proxy authentication when requesting
-   a Https-resource through a web-proxy.
- + 366774 removed XSS vulnerbility
- + 367099 Upgrade jetty-websocket for RFC 6455 - Addendum.
- + 367433 added tests to investigate
- + 367435 improved D00 test harness
- + 367485 HttpExchange canceled before response do not release connection.
- + 367502 WebSocket connections should be closed when application context is
-   stopped.
- + 367548 jetty-osgi-boot must not import the nested package twice
- + 367591 corrected configuration.xml version to 7.6
- + 367635 Added support for start.d directory
- + 367716 simplified maxIdleTime logic
+jetty-7.6.0.RC5 - 20 January 2012
+ + 359329 Prevent reinvocation of LoginModule.login with jaspi for already
+   authed user
+ + 368632 Remove superfluous removal of org.apache.catalina.jsp_file
+ + 368633 fixed configure.dtd resource mappings
+ + 368635 moved lifecycle state reporting from toString to dump
+ + 368773 process data constraints without realm
+ + 368787 always set token view to new header buffers in httpparser
+ + 368821 improved test harness
+ + 368920 JettyAwareLogger always formats the arguments.
+ + 368948 POM for jetty-jndi references unknown version for javax.activation.
+ + 368992 avoid non-blocking flush when writing to avoid setting !_writable
+   without _writeblocked
+ + JETTY-1475 made output state fields volatile to provide memory barrier for
+   non dispatched thread IO
+
+jetty-8.1.0.RC4 - 13 January 2012
+ + 365048 jetty Http client does not send proxy authentication when requesting
+   a Https-resource through a web-proxy.
+ + 366774 removed XSS vulnerbility
+ + 367099 Upgrade jetty-websocket for RFC 6455 - Addendum.
+ + 367433 added tests to investigate
+ + 367435 improved D00 test harness
+ + 367485 HttpExchange canceled before response do not release connection.
+ + 367502 WebSocket connections should be closed when application context is
+   stopped.
+ + 367548 jetty-osgi-boot must not import the nested package twice
+ + 367591 corrected configuration.xml version to 7.6
+ + 367635 Added support for start.d directory
+ + 367638 limit number of form parameters to avoid DOS
+ + 367716 simplified idleTimeout logic
  + 368035 WebSocketClientFactory does not invoke super.doStop().
  + 368060 do not encode sendRedirect URLs
  + 368112 NPE on <jsp-config><taglib> element parsing web.xml
@@ -694,9 +2630,23 @@ jetty-8.1.0.RC4 - 13 January 2012
    field.
  + 368291 Change warning to info for NoSuchFieldException on
    BeanELResolver.properties
- + 367638 limit number of form parameters to avoid DOS
  + JETTY-1467 close half closed when idle
 
+jetty-7.6.0.RC4 - 13 January 2012
+ + 365048 jetty Http client does not send proxy authentication when requesting
+   a Https-resource through a web-proxy.
+ + 366774 removed XSS vulnerbility
+ + 367099 Upgrade jetty-websocket for RFC 6455 - Addendum.
+ + 367716 simplified maxIdleTime logic
+ + 368035 WebSocketClientFactory does not invoke super.doStop().
+ + 368060 do not encode sendRedirect URLs
+ + 368114 Protect against non-Strings in System properties for Log
+ + 368189 WebSocketClientFactory should not manage external thread pool.
+ + 368215 Remove debug from jaspi
+ + 368240 Improve AggregateLifeCycle handling of shared lifecycles
+ + 368291 Change warning to info for NoSuchFieldException on
+   BeanELResolver.properties
+
 jetty-8.1.0.RC2 - 22 December 2011
  + 359329 jetty-jaspi must exports its packages. jetty-plus must import
    javax.security
@@ -721,6 +2671,37 @@ jetty-8.1.0.RC2 - 22 December 2011
  + JETTY-1463 websocket D0 parser should return progress even if no fill done
  + JETTY-1465 NPE in ContextHandler.toString
 
+jetty-7.6.0.RC3 - 05 January 2012
+ + 367433 added tests to investigate
+ + 367435 improved D00 test harness
+ + 367485 HttpExchange canceled before response do not release connection.
+ + 367502 WebSocket connections should be closed when application context is
+   stopped.
+ + 367591 corrected configuration.xml version to 7.6
+ + 367635 Added support for start.d directory
+ + 367638 limit number of form parameters to avoid DOS
+ + JETTY-1467 close half closed when idle
+
+jetty-7.6.0.RC2 - 22 December 2011
+ + 364638 HttpParser closes if data received while seeking EOF. Tests fixed to
+   cope
+ + 364921 Made test less time sensitive for ssl
+ + 364936 use Resource for opening URL streams
+ + 365267 NullPointerException in bad Address
+ + 365375 ResourceHandler should be a HandlerWrapper
+ + 365750 Support WebSocket over SSL, aka wss://
+ + 365932 Produce jetty-websocket aggregate jar for android use
+ + 365947 Set headers for Auth failure and retry in http-spi
+ + 366316 Superfluous printStackTrace on 404
+ + 366342 Dont persist DosFilter trackers in http session
+ + 366730 pass the time idle to onIdleExpire
+ + 367048 test harness for guard on suspended requests
+ + 367175 SSL 100% CPU spin in case of blocked write and RST.
+ + 367219 WebSocketClient.open() fails when URI uses default ports.
+ + JETTY-1460 suppress PrintWriter exceptions
+ + JETTY-1463 websocket D0 parser should return progress even if no fill done
+ + JETTY-1465 NPE in ContextHandler.toString
+
 jetty-8.1.0.RC1 - 06 December 2011
  + 360245 The version of the javax.servlet packages to import is 2.6 instead of
    3.0
@@ -760,7 +2741,7 @@ jetty-7.6.0.RC4 - 13 January 2012
    a Https-resource through a web-proxy.
  + 366774 removed XSS vulnerbility
  + 367099 Upgrade jetty-websocket for RFC 6455 - Addendum.
- + 367716 simplified maxIdleTime logic
+ + 367716 simplified idleTimeout logic
  + 368035 WebSocketClientFactory does not invoke super.doStop().
  + 368060 do not encode sendRedirect URLs
  + 368114 Protect against non-Strings in System properties for Log
@@ -816,7 +2797,6 @@ jetty-7.6.0.RC1 - 04 December 2011
  + 365370 ServletHandler can fall through to nested handler
 
 jetty-7.6.0.RC0 - 29 November 2011
- + Refactored NIO layer for better half close handling
  + 349110 fixed bypass chunk handling
  + 360546 handle set count exceeding max integer
  + 362111 StdErrLog.isDebugEnabled() returns true too often
@@ -844,6 +2824,7 @@ jetty-7.6.0.RC0 - 29 November 2011
    result in close rather than a shutdown output.
  + 364657 Support HTTP only cookies from standard API
  + JETTY-1442 add _hostHeader setter for ProxyRule
+ + Refactored NIO layer for better half close handling
 
 jetty-8.0.4.v20111024 - 24 October 2011
  + 358263 JDBCSessionIdManager add setDatasource(DataSource) method
@@ -1018,8 +2999,8 @@ jetty-8.0.0.v20110901 - 01 September 2011
  + 356421 Upgraded websocket to draft 13 support
 
 jetty-7.5.0.v20110901 - 01 September 2011
- + 356421 Upgraded websocket to draft 13 support
  + 353073 better warnings
+ + 356421 Upgraded websocket to draft 13 support
 
 jetty-7.5.0.RC2 - 30 August 2011
  + 293739 Hide stacks in named log testing. Various other minor log cleanups in
@@ -1052,10 +3033,10 @@ jetty-7.5.0.RC1 - 19 August 2011
    filename is not an absolute platform specific value
 
 jetty-8.0.0.RC0 - 16 August 2011
- + Merge from jetty-7.4.3
- + Enable annotations by default
  + 352565 cookie httponly flag ignored
  + 353285 ServletSecurity annotation ignored
+ + Enable annotations by default
+ + Merge from jetty-7.4.3
 
 jetty-8.0.0.M3 - 27 May 2011
  + 324505 Implement API login
@@ -1128,7 +3109,7 @@ jetty-7.4.3.v20110701 - 01 July 2011
    String) does not yield an empty map
  + 349738 set buffer sizes for http client in proxy servlet
  + 349870 proxy servlet protect continuation against fast failing exchanges
- + 349896 SCEP supports zero maxIdleTime
+ + 349896 SCEP supports zero idleTimeout
  + 349897 draft -09 websockets
  + 349997 MBeanContainer uses weak references
  + 350533 Add "Origin" to the list of allowed headers in CrossOriginFilter
@@ -1145,8 +3126,8 @@ jetty-7.4.2.v20110526
  + 336220 tmp directory is not set if you reload a webapp with
    jetty-maven-plugin
  + 338364 Fixed expires header for set cookies
- + 345729 binding for managing server and system classes globally
  + 345615 Enable SSL Session caching
+ + 345729 binding for managing server and system classes globally
  + 345763 Source file is updated during the build
  + 345873 Update jetty-ssl.xml to new style
  + 345900 Handle IPv6 with default port
@@ -1155,10 +3136,10 @@ jetty-7.4.2.v20110526
    shares
  + 346179 o.e.j.util.ScannerTest fails on MacOS X platform
  + 346181 o.e.j.server.StressTest stalls on MacOS X platform
- + 346998 AbstractLifeCycle.isRunning() returns false if state changes from
-   STARTING to STARTED during call
  + 346614 HttpConnection.handle() spins in case of SSL truncation attacks
  + 346764 OrderedGroupBinding deployment binding
+ + 346998 AbstractLifeCycle.isRunning() returns false if state changes from
+   STARTING to STARTED during call
  + 347137 Allow SSL renegotiations by default in HttpClient
  + 374174 Consistent mbean names
  + JETTY-1146 Encode jsessionid in sendRedirect
@@ -1236,15 +3217,15 @@ jetty-7.4.0.RC0
  + 341439 Blocking HttpClient does not use soTimeout for timeouts
  + 341561 Exception when adding o.e.j.s.DoSFilter as managed attribute
  + 341692 Fixed deadlock if stopped while starting
- + 341736 Split jetty-nested out of war module
- + 341726 JSONPojoConverter handles characters
- + 341992 Overlayed context deployer
  + 341694 Disable AJP buffer resizing
+ + 341726 JSONPojoConverter handles characters
+ + 341736 Split jetty-nested out of war module
  + 341850 Protect QTP dump from bad stacks
+ + 341992 Overlayed context deployer
  + JETTY-1245 Pooled Buffers implementation
  + JETTY-1354 Added jetty-nested
- + Ensure generated fragment names are unique
  + Added extra session removal test
+ + Ensure generated fragment names are unique
 
 jetty-8.0.0.M2 - 16 November 2010
  + 320073 Reconsile configuration mechanism
@@ -1295,14 +3276,12 @@ jetty-7.3.1.v20110307 - 07 March 2011
  + JETTY-1304 Allow quoted boundaries in Multipart filter
  + JETTY-1317 More elegent handling of bad URIs in requests
  + JETTY-1331 Allow alternate XML configuration processors (eg spring)
- + JETTY-1335 HttpClient's SelectConnector clean-up
  + JETTY-1333 HttpClient _timeout and _soTimeout is messed up
+ + JETTY-1335 HttpClient's SelectConnector clean-up
  + JETTY-1337 Workname cannot contain '.'
  + JETTY-1338 Trust default SecureRandom seed
 
 jetty-7.3.0.v20110203 - 03 February 2011
- + JETTY-1259 NullPointerException in JDBCSessionIdManager when invalidating
-   session (further update)
  + 296978 standardizing various Testing Util Classes to jetty-test-helper
  + 319178 test failure fix in jetty-util on windows
  + 320457 add SPNEGO support
@@ -1342,20 +3321,16 @@ jetty-7.3.0.v20110203 - 03 February 2011
    undispatched
  + 335681 Improve ChannelEndPoint.close() to avoid spinning
  + 335836 Race when updating SelectChannelEndPoint._dispatched
+ + JETTY-1259 NullPointerException in JDBCSessionIdManager when invalidating
+   session (further update)
 
 jetty-7.2.2.v20101205 - 05 December 2010
- + JETTY-1308 327109 (re)fixed AJP handling of empty packets
- + 331703 Fixed failing OSGI test TestJettyOSGiBootWithJsp.java on MacOSX
- + 331567 IPAccessHandlerTest failed on MacOS fix
  + 328789 Clean up tmp files from test harnesses
- + 331230 Fixed low thread warnings when acceptors>threadpool
- + 331461 Fixed idle timeout for unflushed HTTP/1.0
- + JETTY-1307 Check that JarFileResource directories end with /
- + 330210 Improve performance of writing large bytes arrays
- + 330208 Support new wording on servlet-mapping and filter-mapping merging
-   from servlet3.0a
  + 330188 Reject web-fragment.xml with same <name> as another already loaded
    one
+ + 330208 Support new wording on servlet-mapping and filter-mapping merging
+   from servlet3.0a
+ + 330210 Improve performance of writing large bytes arrays
  + 330229 Jetty tries to parse META-INF/*.tld when jsp-api is not on classpath,
    causing DTD entity resoluton to fail
  + 330265 start.jar --stop kills --exec subprocess
@@ -1365,16 +3340,22 @@ jetty-7.2.2.v20101205 - 05 December 2010
    org.apache.jasper.glassfish
  + 330732 Removed System.err debugging
  + 330764 Command line properties passed to start.jar --exec
+ + 331230 Fixed low thread warnings when acceptors>threadpool
+ + 331461 Fixed idle timeout for unflushed HTTP/1.0
+ + 331567 IPAccessHandlerTest failed on MacOS fix
+ + 331703 Fixed failing OSGI test TestJettyOSGiBootWithJsp.java on MacOSX
  + JETTY-1297 Improved matching of vhosts so that a vhost match has priority
+ + JETTY-1307 Check that JarFileResource directories end with /
+ + JETTY-1308 327109 (re)fixed AJP handling of empty packets
 
 jetty-7.2.1.v20101111 - 11 November 2010
  + 324679 Fixed dedection of write before static content
+ + 328008 Handle update to Servlet Spec 3 Section 8.2.3.h.ii
  + 328199 Ensure blocking connectors always close socket
  + 328205 Improved SelectManager stopping
  + 328306 Serialization of FormAuthentication
  + 328332 Response.getContentType works with setHeader
  + 328523 Fixed overloaded setters in AppProvider
- + 328008 Handle update to Servlet Spec 3 Section 8.2.3.h.ii
  + 328778 Improved javadoc for secure session cookies
  + 328782 allow per connection max idle time to be set
  + 328885 web overrides do not override
@@ -1397,6 +3378,9 @@ jetty-6.1.26 - 10 November 2010
  + JETTY-1296 Always clear changes list in selectManager
 
 jetty-6.1.26.RC0 - 20 October 2010
+ + 325468 Clean work webapp dir before unpack
+ + 327109 Fixed AJP handling of empty packets
+ + 327562 Implement all X-Forwarded headers in ProxyServlet
  + JETTY-547 Improved usage of shutdownOutput before close.
  + JETTY-912 add per exchange timeout
  + JETTY-1051 offer jetty.skip flag for maven plugin
@@ -1410,9 +3394,6 @@ jetty-6.1.26.RC0 - 20 October 2010
  + JETTY-1288 info when atypical classloader set to WebAppContext
  + JETTY-1289 MRU cache for filter chains
  + JETTY-1292 close input streams after keystore.load()
- + 325468 Clean work webapp dir before unpack
- + 327109 Fixed AJP handling of empty packets
- + 327562 Implement all X-Forwarded headers in ProxyServlet
 
 jetty-7.2.0.v20101020 - 20 October 2010
  + 289540 added javadoc into distribution
@@ -1492,8 +3473,8 @@ jetty-7.2.0.RC0 - 01 October 2010
    default
  + JETTY-1281 Create new session after authentication
  + JETTY-1283 JSONPojoConvertorFactory can turn off fromJSON
- + Fix jetty-plus.xml for new configuration names
  + Added ignore to Logger interface
+ + Fix jetty-plus.xml for new configuration names
  + Improved debug dump
 
 jetty-7.1.6.v20100715
@@ -1514,33 +3495,33 @@ jetty-8.0.0.M1 - 12 July 2010
  + Ensure servlet-api jar class inheritance hierarchy is scanned
 
 jetty-7.1.5.v20100705
- + Update ecj to 3.6 Helios release drop
  + 288194 Add blacklist/whitelist to ProxyServlet and ProxyHandler
  + 296570 EOFException for HttpExchange when HttpClient.stop called.
  + 311550 The WebAppProvider should allow setTempDirectory
  + 316449 Websocket disconnect fix
  + 316584 Exception on startup if temp path has spaces and extractWAR=false
  + 316597 Removed null check and fixed name in Resource#hrefEncodeURI
+ + 316909 CNFE: org.xml.sax.SAXException on org.eclipse.jetty.osgi.boot start
+   with jsp fragment
  + 316970 jetty.sh fails to find JETTY_HOME in standard directories
  + 316973 jetty.sh claims java installation is invalid
  + 316976 removed quotes of JAVA_OPTIONS in jetty.sh
+ + 317007 Unable to run Jetty OSGi when
+   -Dosgi.compatibility.bootdelegation=false
  + 317019 Date HTTP header not sent for HTTP/1.0 requests
+ + 317231 Ability to configure jetty with a fragment bundle that contains
+   etc/jetty.xml
  + 317759 Allow roles and constraints to be added after init
  + 317906 OPTIONS correctly handles TRACE
  + 318308 Correct quoting of unicode control characters
  + 318470 unboxing NPE protection in HttpConnection
  + 318551 Optional uncheck Printwriter
- + JETTY-1237 Save local/remote address to be available after close
- + 317007 Unable to run Jetty OSGi when
-   -Dosgi.compatibility.bootdelegation=false
- + 316909 CNFE: org.xml.sax.SAXException on org.eclipse.jetty.osgi.boot start
-   with jsp fragment
- + 317231 Ability to configure jetty with a fragment bundle that contains
-   etc/jetty.xml
  + 319060 Support web-bundles that are not expanded (bundle is zipped)
+ + JETTY-1237 Save local/remote address to be available after close
+ + Update ecj to 3.6 Helios release drop
 
 jetty-6.1.25 - 26 July 2010
- + Jetty-6 is now in maintenance mode.
+ + 320264 Removed duplicate mime.property entries
  + JETTY-1212 Long content lengths
  + JETTY-1214 Avoid ISE when scavenging invalid session
  + JETTY-1223 DefaultServlet: NPE when setting relativeResourceBase and
@@ -1550,15 +3531,15 @@ jetty-6.1.25 - 26 July 2010
  + JETTY-1251 protected against closed selector
  + COMETD-112 if two threads create the same channel, then create events may
    occur after subscribe events
- + 320264 Removed duplicate mime.property entries
+ + Jetty-6 is now in maintenance mode.
 
 jetty-7.1.4.v20100610
- + 298551 SslSocketConnector does not need keystore stream
- + 295715 AbstractSessionManager decoupled from Context
  + 292326 Stop continuations if server is stopped.
  + 292814 Make QoSFilter and DoSFilter JMX manageable
  + 293222 Improve request log to handle/show asynchronous latency
  + 294212 Can not customize session cookie path
+ + 295715 AbstractSessionManager decoupled from Context
+ + 298551 SslSocketConnector does not need keystore stream
  + 301608 Deregister shutdown hooks
  + 302350 org.eclipse.jetty.server.NCSARequestLog is missing JavaDoc
  + 303661 jetty.sh failes if JETTY_HOME is not writeable
@@ -1573,7 +3554,7 @@ jetty-7.1.4.v20100610
  + 315748 Removed --fromDaemon from start.jar (replaced with --daemon)
  + 315925 Improved context xml configuration handling
  + 315995 Incorrect package name in system classes list
- + 316119 Fixed maxIdleTime for SocketEndPoint
+ + 316119 Fixed idleTimeout for SocketEndPoint
  + 316254 Implement @DeclareRoles
  + 316334 Breaking change on org.eclipse.jetty.client.HttpExchange
  + 316399 Debug output in MultiPartFilter
@@ -1588,8 +3569,8 @@ jetty-7.1.3.v20100526
  + 305898 Websocket handles query string in URI
  + 307457 Exchanges are left unhandled when connection is lost
  + 313205 Unable to run test-jdbc-sessions tests
- + 314177 JSTL support is broken
  + 314009 jetty.xml configuration file on command line
+ + 314177 JSTL support is broken
  + 314459 support maven3 for builds
 
 jetty-7.1.2.v20100523
@@ -1606,17 +3587,17 @@ jetty-7.1.1.v20100517
    optional
  + 304803 Remove TypeUtil Integer and Long caches
  + 306226 HttpClient should allow changing the keystore and truststore type
- + 308857 Update test suite to JUnit4 - Module jetty-jndi
- + 308856 Update test suite to JUnit4 - Module jetty-jmx
- + 308860 Update test suite to JUnit4 - Module jetty-rewrite
  + 308850 Update test suite to JUnit4 - Module jetty-annotations
  + 308853 Update test suite to JUnit4 - Module jetty-deploy
  + 308854 Update test suite to JUnit4 - Module jetty-http
- + 308859 Update test suite to JUnit4 - Module jetty-policy
- + 308858 Update test suite to JUnit4 - Module jetty-plus
- + 308863 Update test suite to JUnit4 - Module jetty-servlet
  + 308855 Update test suite to JUnit4 - Module jetty-io
+ + 308856 Update test suite to JUnit4 - Module jetty-jmx
+ + 308857 Update test suite to JUnit4 - Module jetty-jndi
+ + 308858 Update test suite to JUnit4 - Module jetty-plus
+ + 308859 Update test suite to JUnit4 - Module jetty-policy
+ + 308860 Update test suite to JUnit4 - Module jetty-rewrite
  + 308862 Update test suite to JUnit4 - Module jetty-server
+ + 308863 Update test suite to JUnit4 - Module jetty-servlet
  + 308867 Update test suite to JUnit4 - Module jetty-webapp
  + 310918 Fixed write blocking for client HttpConnection
  + 312526 Protect shutdown thread initialization during shutdown
@@ -1672,60 +3653,51 @@ jetty-7.1.0.RC0 - 27 April 2010
  + 310467 Allow SocketConnector to create generic Connection objects
  + 310603 Make Logger interface consistent
  + 310605 Make a clean room implementation of the JSP logger bridge
- + Add AnnotationConfiguration to jetty-plus.xml
- + Fix jetty-plus.xml reference to addLifeCycle
+ + JETTY-903 Stop both caches
  + JETTY-1200 SSL NIO Endpoint wraps non NIO buffers
  + JETTY-1202 Use platform default algorithm for SecureRandom
- + Merged 7.0.2.v20100331
- + Add NPE protection to ContainerInitializerConfiguration
- + Temporarily remove jetty-osgi module to clarify jsp version compatibility
  + JETTY-1212 handle long content lengths
  + JETTY-1214 avoid ISE when scavenging invalid session
- + JETTY-903 Stop both caches
+ + Add AnnotationConfiguration to jetty-plus.xml
+ + Add NPE protection to ContainerInitializerConfiguration
+ + Fix jetty-plus.xml reference to addLifeCycle
+ + Merged 7.0.2.v20100331
+ + Temporarily remove jetty-osgi module to clarify jsp version compatibility
 
 jetty-7.0.2.v20100331 - 31 March 2010
  + 297552 Don't call Continuation timeouts from acceptor tick
  + 298236 Additional unit tests for jetty-client
+ + 306782 httpbis interpretation of 100 continues. Body never skipped
  + 306783 NPE in StdErrLog when Throwable is null
  + 306840 Suppress content-length in requests with no content
  + 306880 Support for UPGRADE in HttpClient
  + 306884 Suspend with timeout <=0 never expires
- + 306782 httpbis interpretation of 100 continues. Body never skipped
  + 307589 updated servlet 3.0 continuations for final API
- + Take excess logging statements out of startup
- + Ensure webapps with no WEB-INF don't scan WEB-INF/lib
  + Allow Configuration array to be set on Server instance for all web apps
+ + Ensure webapps with no WEB-INF don't scan WEB-INF/lib
+ + Take excess logging statements out of startup
 
 jetty-6.1.24 - 21 April 2010
+ + 308925 Protect the test webapp from remote access
  + JETTY-903 Stop both caches
  + JETTY-1198 reset idle timeout on request body chunks
  + JETTY-1200 SSL NIO Endpoint wraps non NIO buffers
  + JETTY-1211 SetUID loadlibrary name and debug
- + 308925 Protect the test webapp from remote access
- + COMETD-99 ClientImpl logs exceptions in listeners with "debug" level
  + COMETD-100 ClientImpl logs "null" as clientId
  + COMETD-107 Reloading the application with reload extension does not fire
    /meta/connect handlers until long poll timeout expires
+ + COMETD-99 ClientImpl logs exceptions in listeners with "debug" level
  + Upgraded to cometd 1.1.1 client
 
 jetty-6.1.23 - 02 April 2010
- + JSON parses NaN as null
- + Updated JSP to 2.1.v20091210
- + COMETD-28 Improved concurrency usage in Bayeux and channel handling
- + COMETD-46 reset ContentExchange content on resend
- + COMETD-58 Extension.rcv() return null causes NPE in
-   AbstractBayeux.PublishHandler.publish
- + COMETD-59 AcknowledgeExtension does not handle null channel in Message
- + COMETD-62 Delay add listeners until after client construction
- + 296569 removeLifeCycleListener() has no effect
  + 292800 ContextDeployer - recursive setting is undone by FilenameFilter
+ + 296569 removeLifeCycleListener() has no effect
  + 300178 HttpClients opens too many connections that are immediately closed
  + 304658 Inconsistent Expires date format in Set-Cookie headers with maxAge=0
  + 304698 org.eclipse.jetty.http.HttpFields$DateGenerator.formatCookieDate()
    uses wrong (?) date format
  + 306331 Session manager is kept after call to doScope
  + 306840 suppress content-length in requests without content
- + Remove references to old content in HttpClient client tests for www.sun.com
  + JETTY-875 Allow setting of advice field in response to Handshake
  + JETTY-983 Range handling cleanup
  + JETTY-1133 Handle multiple URL ; parameters
@@ -1760,12 +3732,20 @@ jetty-6.1.23 - 02 April 2010
  + JETTY-1196 Enable TCP_NODELAY by default in client connectors
  + JETTY-1197 SetUID module test fails when using Java 1.6 to build
  + JETTY-1199 FindBugs cleanups
+ + JETTY-1202 Use platfrom default algorithm for SecureRandom
  + JETTY-1205 Memory leak in browser-to-client mapping
  + JETTY-1207 NPE protection in FormAuthenticator
- + JETTY-1202 Use platfrom default algorithm for SecureRandom
+ + COMETD-28 Improved concurrency usage in Bayeux and channel handling
+ + COMETD-46 reset ContentExchange content on resend
+ + COMETD-58 Extension.rcv() return null causes NPE in
+   AbstractBayeux.PublishHandler.publish
+ + COMETD-59 AcknowledgeExtension does not handle null channel in Message
+ + COMETD-62 Delay add listeners until after client construction
+ + JSON parses NaN as null
+ + Remove references to old content in HttpClient client tests for www.sun.com
+ + Updated JSP to 2.1.v20091210
 
 jetty-7.0.2.RC0
- + JSON parses NaN as null
  + 290765 Reset input for HttpExchange retry.
  + 292799 WebAppDeployer - start a started context?
  + 292800 ContextDeployer - recursive setting is undone by FilenameFilter
@@ -1802,34 +3782,35 @@ jetty-7.0.2.RC0
    uses wrong (?) date format
  + 304781 Reset HttpExchange timeout on slow request content.
  + 304801 SSL connections FULL fix
+ + 305997 Coalesce buffers in ChannelEndPoint.flush()
+ + 306028 Enable TCP_NODELAY by default in client connectors
  + 306330 Flush filter chain cache after Invoker servlet
  + 306331 Session manager is kept after call to doScope
  + JETTY-776 Make new session-tests module to concentrate all reusable session
    clustering test code
  + JETTY-910 Allow request listeners to access session
  + JETTY-983 Range handling cleanup
+ + JETTY-1133 Handle multiple URL ; parameters
  + JETTY-1151 JETTY-1098 allow UTF-8 with 0 carry bits
  + JETTY-1153 System property for UrlEncoded charset
  + JETTY-1155 HttpConnection.close notifies HttpExchange
  + JETTY-1156 SSL blocking close with JVM Bug busy key fix
  + JETTY-1157 Don't hold array passed in write(byte[])
  + JETTY-1163 AJP13 forces 8859-1 encoding
+ + JETTY-1174 Close rather than finish Gzipstreams to avoid JVM leak
  + JETTY-1177 Allow error handler to set cacheControl
  + JETTY-1179 Persistant session tables created on MySQL use wrong datatype
  + JETTY-1184 shrink thread pool even with frequent small jobs
- + JETTY-1133 Handle multiple URL ; parameters
- + JETTY-1174 Close rather than finish Gzipstreams to avoid JVM leak
  + JETTY-1192 Fixed Digested POST
  + JETTY-1199 FindBugs cleanups
- + COMETD-46 reset ContentExchange response content on resend
  + Added IPAccessHandler
+ + COMETD-46 reset ContentExchange response content on resend
+ + JSON parses NaN as null
  + Updated Servlet3Continuation to final 3.0.20100224
- + 305997 Coalesce buffers in ChannelEndPoint.flush()
- + 306028 Enable TCP_NODELAY by default in client connectors
 
 jetty-8.0.0.M0 - 28 February 2010
- + Updated servlet 3.0 spec 20100224
  + Merged 7.0.1.v20091116
+ + Updated servlet 3.0 spec 20100224
  + Updated to cometd 1.0.1
 
 jetty-7.0.1.v20091125 - 25 November 2009
@@ -1843,9 +3824,9 @@ jetty-7.0.1.v20091125 - 25 November 2009
  + 291340 Race condition in onException() notifications
  + 291543 make bin/*.sh scripts executable in distribution
  + 291589 Update jetty-rewrite demo
+ + 292546 Proactively enforce HttpClient idle timeout
  + 292642 Fix errors in embedded Jetty examples
  + 292825 Continuations ISE rather than ignore bad transitions
- + 292546 Proactively enforce HttpClient idle timeout
  + 293222 Improved StatisticsHandler for async
  + 293506 Unable to use jconsole with Jetty when running with security manager
  + 293557 Add "jad" mime mapping
@@ -1853,6 +3834,10 @@ jetty-7.0.1.v20091125 - 25 November 2009
  + 294224 HttpClient timeout setting has no effect when connecting to host
  + 294345 Support for HTTP/301 + HTTP/302 response codes
  + 294563 Initial websocket implementation
+ + 295421 Cannot reset() a newly created HttpExchange: IllegalStateException 0
+   => 0
+ + 295562 CrossOriginFilter does not work with default values in Chrome and
+   Safari
  + JETTY-937 More JVM bug work arounds. Insert pause if all else fails
  + JETTY-983 Send content-length with multipart ranges
  + JETTY-1114 unsynchronised WebAppClassloader.getResource(String)
@@ -1865,24 +3850,24 @@ jetty-7.0.1.v20091125 - 25 November 2009
  + JETTY-1144 fixed multi-byte character overflow
  + JETTY-1148 Reset partially read request reader.
  + COMETD-34 Support Baeyux MBean
+ + CQ-3581 jetty OSGi contribution
+ + CVE-2009-3555 Prevent SSL renegotiate for SSL vulnerability
+ + Fixed client abort asocciation
  + Fixed XSS issue in CookieDump demo servlet.
  + Improved start.jar usage text for properties
+ + Moved centralized logging and verifier back to sandbox
  + Promoted Jetty Centralized Logging from Sandbox
  + Promoted Jetty WebApp Verifier from Sandbox
  + Refactored continuation test harnessess
- + Fixed client abort asocciation
- + CQ-3581 jetty OSGi contribution
- + Moved centralized logging and verifier back to sandbox
- + CVE-2009-3555 Prevent SSL renegotiate for SSL vulnerability
- + 295421 Cannot reset() a newly created HttpExchange: IllegalStateException 0
-   => 0
- + 295562 CrossOriginFilter does not work with default values in Chrome and
-   Safari
 
 jetty-7.0.0.v20091005 - 05 October 2009
  + 291340 Race condition in onException() notifications
 
 jetty-6.1.21 - 22 September 2009
+ + 282543 HttpClient SSL buffer size fix
+ + 288055 fix jetty-client for failed listener state machine
+ + 288153 reset exchange when resending
+ + 288182 PUT request fails during retry
  + JETTY-719 Document state machine of jetty http client
  + JETTY-933 State == HEADER in client
  + JETTY-936 Improved servlet matching and optimized
@@ -1905,17 +3890,27 @@ jetty-6.1.21 - 22 September 2009
  + JETTY-1113 IllegalStateException when adding servlet filters
    programmatically
  + JETTY-1114 Unsynchronize webapp classloader getResource
- + 282543 HttpClient SSL buffer size fix
- + 288055 fix jetty-client for failed listener state machine
- + 288153 reset exchange when resending
- + 288182 PUT request fails during retry
  + Fix DefaultServletTest for windows
- + Update Jetty implementation of com.sun.net.httpserver.*
  + Include tmp directory sweeper in build
  + Streamline jetty-jboss build, update sar to QueuedThreadPool
+ + Update Jetty implementation of com.sun.net.httpserver.*
 
 jetty-7.0.0.RC6 - 21 September 2009
- + Fixed XSS issue in CookieDump demo servlet.
+ + 280723 Add non blocking statistics handler
+ + 282543 HttpClient SSL buffer size fix
+ + 283357 org.eclipse.jetty.server.HttpConnectionTest exceptions
+ + 288055 jetty-client fails to resolve failed resolution attempts correctly
+ + 288153 jetty-client resend doesn't reset exchange
+ + 288182 PUT request fails during retry
+ + 288466 LocalConnector is not thread safe
+ + 288514 AbstractConnector does not handle InterruptedExceptions on shutdown
+ + 288772 Failure to connect does not set status to EXCEPTED
+ + 289146 formalize reload policy functionality
+ + 289156 jetty-client: no longer throw runtime exception for bad authn details
+ + 289221 HttpExchange does not timeout when using blocking connector
+ + 289285 org.eclipse.jetty.continuation 7.0.0.RC5 imports the
+   org.mortbay.util.ajax package
+ + 289686 HttpExchange.setStatus() has too coarse synchronization
  + 289958 StatisticsServlet incorrectly adds StatisticsHandler
  + 289960 start.jar assumes command line args are configs
  + 290081 Eager consume LF after CR
@@ -1937,26 +3932,17 @@ jetty-7.0.0.RC6 - 21 September 2009
  + JETTY-1112 Response fails if header exceeds buffer size
  + JETTY-1113 IllegalStateException when adding servlet filters
    programmatically
- + 280723 Add non blocking statistics handler
- + 282543 HttpClient SSL buffer size fix
- + 283357 org.eclipse.jetty.server.HttpConnectionTest exceptions
- + 288055 jetty-client fails to resolve failed resolution attempts correctly
- + 288153 jetty-client resend doesn't reset exchange
- + 288466 LocalConnector is not thread safe
- + 288514 AbstractConnector does not handle InterruptedExceptions on shutdown
- + 288772 Failure to connect does not set status to EXCEPTED
- + 289146 formalize reload policy functionality
- + 289156 jetty-client: no longer throw runtime exception for bad authn details
- + 288182 PUT request fails during retry
- + 289221 HttpExchange does not timeout when using blocking connector
- + 289285 org.eclipse.jetty.continuation 7.0.0.RC5 imports the
-   org.mortbay.util.ajax package
- + 289686 HttpExchange.setStatus() has too coarse synchronization
- + Tweak DefaultServletTest under windows
  + Copy VERSION.txt to distro
+ + Fixed XSS issue in CookieDump demo servlet.
  + Remove printlns from jetty-plus
+ + Tweak DefaultServletTest under windows
 
 jetty-6.1.20 - 27 August 2009
+ + 283513 Check endp.isOpen when blocking read
+ + 283818 fixed merge of forward parameters
+ + 285006 Fixed NPE in AbstractConnector during shutdown
+ + 286535 ContentExchange status code
+ + 286911 Clean out cache when recycling HTTP fields
  + JETTY-838 Don't log and throw
  + JETTY-874 Better error on full header.
  + JETTY-960 Support ldaps
@@ -1981,22 +3967,19 @@ jetty-6.1.20 - 27 August 2009
  + JETTY-1086 Added UncheckedPrintWriter to avoid ignored EOFs
  + JETTY-1087 Chunked SSL non blocking input
  + JETTY-1098 Upgrade jsp to SJSAS-9_1_1-B60F-07_Jan_2009
- + 283513 Check endp.isOpen when blocking read
- + 283818 fixed merge of forward parameters
- + 285006 Fixed NPE in AbstractConnector during shutdown
- + 286535 ContentExchange status code
- + 286911 Clean out cache when recycling HTTP fields
- + COMETD-7 max latency config for lazy messages
+ + Added DebugHandler
  + Added getSubscriptions to cometd client
+ + Clarified cometd interval timeout and allow per client intervals
+ + COMETD-7 max latency config for lazy messages
  + Made unSubscribeAll public on cometd client
  + Removed clearing of queue in unSubscribeAll for cometd client
- + Update test-jndi and test-annotation examples for atomikos 3.5.5
- + Clarified cometd interval timeout and allow per client intervals
  + Update Main.main method to call setWar
- + Added DebugHandler
+ + Update test-jndi and test-annotation examples for atomikos 3.5.5
 
 jetty-7.0.0.RC5 - 27 August 2009
  + 286911 Clean out cache when recycling HTTP fields
+ + 287496 Use start.ini always and added --exec
+ + 287632 FilterContinuations for blocking jetty6
  + JETTY-838 Don't log and throw
  + JETTY-874 Better header full warnings
  + JETTY-960 Support for ldaps
@@ -2005,8 +3988,6 @@ jetty-7.0.0.RC5 - 27 August 2009
  + JETTY-1085 Allow url sessionID if cookie invalid
  + JETTY-1086 Added UncheckedPrintWriter to avoid ignored EOFs
  + JETTY-1087 Chunked SSL non blocking input
- + 287496 Use start.ini always and added --exec
- + 287632 FilterContinuations for blocking jetty6
 
 jetty-6.1.19 - 01 July 2009
  + JETTY-799 shell script for jetty on cygwin
@@ -2038,55 +4019,55 @@ jetty-6.1.19 - 01 July 2009
  + JETTY-1062 Don't filter cometd message without data
 
 jetty-7.0.0.RC4 - 18 August 2009
+ + 279820 Fixed HotSwapHandler
+ + 285891 SessionAuthentication is serializable
  + 286185 Implement ability for JSON implementation to automatically register
    convertors
- + Added discoverable start options
  + 286535 ContentExchange status code
- + 285891 SessionAuthentication is serializable
+ + JETTY-1057 XSS error page
  + JETTY-1079 ResourceCollection.toString
- + 279820 Fixed HotSwapHandler
  + JETTY-1080 Ignore files that would be extracted outside the destination
    directory when unpacking WARs
- + JETTY-1057 XSS error page
+ + Added discoverable start options
 
 jetty-7.0.0.RC3 - 07 August 2009
  + 277403 remove system properties
- + JETTY-1074 JMX thread manipulation
- + Improved deferred authentication handling
- + 285697 extract parameters if dispatch has query
  + 282447 concurrent destinations in HttpClient
  + 283172 fix Windows build, broken on directory creation with the
    DefaultServlet
  + 283375 additional error-checking on SSL connector passwords to prevent NPE
  + 283513 Check endp.isOpen when blocking read
+ + 285697 extract parameters if dispatch has query
+ + JETTY-1074 JMX thread manipulation
+ + Improved deferred authentication handling
 
 jetty-7.0.0.RC2 - 29 June 2009
- + 283844 Webapp / TLD errors are not clear
  + 283375 improved extensibility of SSL connectors
  + 283818 fixed merge of forward parameters
- + backport jetty-8 annotation parsing to jetty-7
- + Disassociate method on IdentityService
- + 284510 Enhance jetty-start for diagnosis and unit testing
+ + 283844 Webapp / TLD errors are not clear
  + 284475 update jetty.sh for new OPTIONS syntax
+ + 284510 Enhance jetty-start for diagnosis and unit testing
+ + 284981 Implement a cross-origin filter
+ + 285006 fix AbstractConnector NPE during shutdown.
  + Added DebugHandler
  + Added JavaUtilLog for Jetty logging to java.util.logging framework
- + 284981 Implement a cross-origin filter
+ + backport jetty-8 annotation parsing to jetty-7
+ + Disassociate method on IdentityService
  + Improved handling of overlays and resourceCollections
- + 285006 fix AbstractConnector NPE during shutdown.
 
 jetty-7.0.0.RC1 - 15 June 2009
+ + 283344 Startup on windows is broken
  + JETTY-1066 283357 400 response for bad URIs
  + JETTY-1068 Avoid busy flush of async SSL
- + 283344 Startup on windows is broken
 
 jetty-7.0.0.RC0 - 08 June 2009
- + JETTY-967 create standalone build for PKCS12Import at codehaus
- + JETTY-1056 update jetty-ant module for Jetty 7 at codehaus trunk
- + JETTY-1058 Handle trailing / with aliases
- + 280843 Buffer pool uses isHeader
  + 271535 Adding integration tests, and enabling RFC2616 tests
+ + 280843 Buffer pool uses isHeader
  + 281287 Handle date headers before 1 Jan 1970
  + 282807 Better handling of 100 continues if response committed.
+ + JETTY-967 create standalone build for PKCS12Import at codehaus
+ + JETTY-1056 update jetty-ant module for Jetty 7 at codehaus trunk
+ + JETTY-1058 Handle trailing / with aliases
 
 jetty-7.0.0.M4 - 01 June 2009
  + 281059 NPE in QTP with debug on
@@ -2101,29 +4082,30 @@ jetty-7.0.0.M4 - 01 June 2009
  + JETTY-1057 Error page stack trace XSS
 
 jetty-7.0.0.M3 - 20 June 2009
- + fixed race with expired async listeners
- + refactored configuration mechanism
- + added WebAppContext.setConfigurationDiscovered for servlet 3.0 features
  + 274251 Allow dispatch to welcome files that are servlets (configurable)
+ + 276545 Quoted cookie paths
  + 277403 Cleanup system property usage.
  + 277798 Denial of Service Filter
- + Portable continuations for jetty6 and servlet3
- + Refactored continuations to only support response wrapping
- + Added ContinuationThrowable
- + 276545 Quoted cookie paths
  + 279725 Support 100 and 102 expectations
- + Refactored AbstractBuffers to HttpBuffers for performance
- + Numerous cleanups from static code analysis
  + 280707 client.HttpConnection does not catch and handle non-IOExceptions
  + 281470 Handle the case where request.PathInfo() should be "/*"
+ + Added ContinuationThrowable
+ + added WebAppContext.setConfigurationDiscovered for servlet 3.0 features
+ + fixed race with expired async listeners
+ + Numerous cleanups from static code analysis
+ + Portable continuations for jetty6 and servlet3
+ + Refactored AbstractBuffers to HttpBuffers for performance
+ + refactored configuration mechanism
+ + Refactored continuations to only support response wrapping
 
 jetty-7.0.0.M2 - 18 May 2009
+ + 273767 Update to use geronimo annotations spec 1.1.1
+ + 275396 Added ScopedHandler to set servlet scope before security handler
  + JETTY-937 Work around Sun JVM bugs
  + JETTY-941 Linux chkconfig hint
  + JETTY-959 CGI servlet doesn't kill the CGI in case the client disconnects
  + JETTY-980 Fixed ResourceHandler ? handling, and bad URI creation in listings
  + JETTY-996 Make start-stop-daemon optional
- + 273767 Update to use geronimo annotations spec 1.1.1
  + JETTY-1003 java.lang.IllegalArgumentException: timeout can't be negative
  + JETTY-1004 CERT VU#402580 Canonical path handling includes ? in path segment
  + JETTY-1013 MySql Error with JDBCUserRealm
@@ -2132,7 +4114,6 @@ jetty-7.0.0.M2 - 18 May 2009
  + JETTY-1015 Reduce BayeuxClient and HttpClient lock contention
  + JETTY-1020 ZipException in org.mortbay.jetty.webapp.TagLibConfiguration
    prevents all contexts from being loaded
- + 275396 Added ScopedHandler to set servlet scope before security handler
 
 jetty-6.1.18 - 16 May 2009
  + JETTY-937 Improved work around sun JVM selector bugs
@@ -2161,13 +4142,13 @@ jetty-6.1.17 - 30 April 2009
  + JETTY-980 Security / Directory Listing XSS present
  + JETTY-982 Make test-jaas-webapp run with jetty:run
  + JETTY-983 Default Servlet sets accept-ranges for cached/gzipped content
+ + JETTY-985 Allow listeners to implement both interfaces
  + JETTY-988 X-Forwarded-Host has precedence over X-Forwarded-Server
  + JETTY-989 GzipFilter handles addHeader
  + JETTY-990 Async HttpClient connect
  + JETTY-992 URIUtil.encodePath encodes markup characters
  + JETTY-996 Make start-stop-daemon optional
  + JETTY-997 Remove jpackage-utils dependency on rpm install
- + JETTY-985 Allow listeners to implement both interfaces
  + JETTY-1000 Avoided needless 1.5 dependency
  + JETTY-1002 cometd-api to 1.0.beta8
  + JETTY-1003 java.lang.IllegalArgumentException: timeout can't be negative
@@ -2176,16 +4157,16 @@ jetty-6.1.17 - 30 April 2009
 
 jetty-7.0.0.M1 - 22 April 2009
  + 271258 FORM Authentication dispatch handling avoids caching
+ + 271536 Add support to IO for quietly closing Readers / Writers
+ + 273011 JETTY-980 JETTY-992 Security / Directory Listing XSS present
+ + 273101 Fix DefaultServletTest XSS test case
+ + 273153 Test for Nested references in DispatchServlet
+ + JETTY-695 Handler dump
+ + JETTY-983 DefaultServlet generates accept-ranges for cached/gzip content
  + Initial support for LoginService.logout
  + Removed HTTPConnection specifics from connection dispatching
- + JETTY-695 Handler dump
  + Reworked authentication for deferred authentication
  + Reworked JMX for new layout
- + JETTY-983 DefaultServlet generates accept-ranges for cached/gzip content
- + 273011 JETTY-980 JETTY-992 Security / Directory Listing XSS present
- + 271536 Add support to IO for quietly closing Readers / Writers
- + 273101 Fix DefaultServletTest XSS test case
- + 273153 Test for Nested references in DispatchServlet
 
 jetty-6.1.16 - 01 April 2009
  + JETTY-702 Create "jetty-tasks.xml" for the Ant plugin
@@ -2279,20 +4260,20 @@ jetty-7.0.0.M0 - 27 March 2009
    relative URL
  + JETTY-871 jetty-client expires() NPE race condition fixed
  + JETTY-876 Added new BlockingArrayQueue and new QueuedThreadPool
+ + JETTY-890 merge jaspi branch to trunk
  + JETTY-894 Add android .apk to mime types
  + JETTY-897 Remove swing dependency in GzipFilter
  + JETTY-898 Allow jetty debs to start with custom java args provided by users
  + JETTY-899 Standardize location and build process for configuration files
    which go into etc
- + JETTY-890 merge jaspi branch to trunk
  + JETTY-909 Update useragents cache
  + JETTY-917 Change for JETTY-811 breaks systemProperties config parameter in
    maven-jetty-plugin
  + JETTY-922 Fixed NPE on getRemoteHost when socket closed
  + JETTY-923 Client supports attributes
  + JETTY-926 default location for generatedClasses of jspc plugin is incorrect
- + JETTY-939 NPE in AbstractConfiguration.callPreDestroyCallbacks
  + JETTY-938 Deadlock in the TerracottaSessionManager
+ + JETTY-939 NPE in AbstractConfiguration.callPreDestroyCallbacks
  + JETTY-946 Redeploys with maven jetty plugin of webapps with overlays don't
    work
  + JETTY-950 Fix double-printing of request URI in request log
@@ -2303,14 +4284,14 @@ jetty-7.0.0.M0 - 27 March 2009
  + simplified HandlerContainer API
 
 jetty-6.1.15 - 04 March 2009
- + JETTY-931 Fix issue with jetty-rewrite.xml
- + JETTY-934 fixed stop/start of Bayeux Client
- + JETTY-938 Deadlock in the TerracottaSessionManager
- + JETTY-939 NPE in AbstractConfiguration.callPreDestroyCallbacks
  + JETTY-923 BayeuxClient uses message pools to reduce memory footprint
  + JETTY-924 Improved BayeuxClient disconnect handling
  + JETTY-925 Lazy bayeux messages
  + JETTY-926 default location for generatedClasses of jspc plugin is incorrect
+ + JETTY-931 Fix issue with jetty-rewrite.xml
+ + JETTY-934 fixed stop/start of Bayeux Client
+ + JETTY-938 Deadlock in the TerracottaSessionManager
+ + JETTY-939 NPE in AbstractConfiguration.callPreDestroyCallbacks
 
 jetty-6.1.15 - 02 March 2009
  + JETTY-923 BayeuxClient uses message pools to reduce memory footprint
@@ -2322,21 +4303,21 @@ jetty-6.1.15.rc4 - 19 February 2009
  + JETTY-496 Support inetd/xinetd through use of System.inheritedChannel()
  + JETTY-713 Expose additional AbstractConnector methods via MBean
  + JETTY-749 Improved ack extension
+ + JETTY-802 Modify the default error pages to make association with Jetty
+   clearer
  + JETTY-811 Allow configuration of system properties for the maven plugin
    using a file
+ + JETTY-815 Add comet support to jQuery javascript library
  + JETTY-840 add default mime types to *.htc and *.pps
  + JETTY-848 Temporary folder not fully cleanup after stop (via Sweeper)
- + JETTY-872 Handshake handler calls wrong extension callback
- + JETTY-879 Support extra properties in jQuery comet implementation
- + JETTY-802 Modify the default error pages to make association with Jetty
-   clearer
  + JETTY-869 NCSARequestLog locale config
  + JETTY-870 NullPointerException in Response when performing redirect to wrong
    relative URL
+ + JETTY-872 Handshake handler calls wrong extension callback
  + JETTY-878 Removed printStackTrace from WaitingContinuation
+ + JETTY-879 Support extra properties in jQuery comet implementation
  + JETTY-882 ChannelBayeuxListener called too many times
  + JETTY-884 Use hashcode for threadpool ID
- + JETTY-815 Add comet support to jQuery javascript library
  + JETTY-887 Split configuration and handshaking in jquery comet
  + JETTY-888 Fix abort in case of multiple outstanding connections
  + JETTY-894 Add android .apk to mime types
@@ -2350,15 +4331,15 @@ jetty-6.1.15.rc3 - 28 January 2009
  + JETTY-866 jetty-client test case fix
 
 jetty-6.1.15.rc2 - 23 January 2009
- + adjustment to jetty-client assembly packaging
  + JETTY-567 Delay in initial TLS Handshake With FireFox 3 beta5 and
    SslSelectChannelConnector
+ + adjustment to jetty-client assembly packaging
 
 jetty-6.1.15.pre0 - 20 January 2009
  + JETTY-600 Automated tests of WADI integration + upgrade to WADI 2.0
  + JETTY-749 Reliable message delivery
- + JETTY-794 WADI integration tests fail intermittently.
  + JETTY-781 Add "mvn jetty:deploy-war" for deploying a pre-assembled war
+ + JETTY-794 WADI integration tests fail intermittently.
  + JETTY-795 NullPointerException in SocketConnector.java
  + JETTY-798 Jboss session manager incompatible with LifeCycle.Listener
  + JETTY-801 Bring back 2 arg EnvEntry constructor
@@ -2571,7 +4552,6 @@ jetty-6.1.12rc2 - 12 September 2008
    complete
 
 jetty-7.0.0pre3 - 06 August 2008
- + Upgrade jsp 2.1 to SJSAS-9_1_02-B04-11_Apr_2008
  + JETTY-30 Externalize servlet-api to own project
  + JETTY-182 Support setting explicit system classpath for jasper
    Jsr199JavaCompiler
@@ -2620,9 +4600,9 @@ jetty-7.0.0pre3 - 06 August 2008
    with byte value 0)
  + JETTY-675 ServletContext.getRealPath("") returns null instead of returning
    the root dir of the webapp
+ + Upgrade jsp 2.1 to SJSAS-9_1_02-B04-11_Apr_2008
 
 jetty-6.1.12rc1 - 01 August 2008
- + Upgrade jsp 2.1 to SJSAS-9_1_02-B04-11_Apr_2008
  + JETTY-319 Get unavailable exception and added startWithUnavailable option
  + JETTY-381 JETTY-622 Multiple Web Application Source Directory
  + JETTY-442 Accessors for mimeType on ResourceHandler
@@ -2671,14 +4651,15 @@ jetty-6.1.12rc1 - 01 August 2008
  + JETTY-667 HttpClient handles chunked content
  + JETTY-669 Http methods other than GET and POST should not have error page
    content
+ + Upgrade jsp 2.1 to SJSAS-9_1_02-B04-11_Apr_2008
 
 jetty-7.0.0pre2 - 30 June 2008
  + JETTY-336 413 error for header buffer full
  + JETTY-425 race in stopping SelectManager
  + JETTY-568 Avoid freeing DirectBuffers. New locking NIO ResourceCache.
  + JETTY-569 Stats for suspending requests
- + JETTY-576 servlet dtds and xsds not being loaded locally
  + JETTY-572 Unique cometd client ID
+ + JETTY-576 servlet dtds and xsds not being loaded locally
  + JETTY-578 OSGI Bundle-RequiredExcutionEnvironment set to J2SE-1.5
  + JETTY-579 OSGI resolved management and servlet.resources import error
  + JETTY-580 Fixed SSL shutdown
@@ -2722,7 +4703,6 @@ jetty-6.1.11 - 06 June 2008
  + JETTY-598 Added more reliable cometd message flush option
 
 jetty-6.1.10 - 20 May 2008
- + Use QueuedThreadPool as default
  + JETTY-440 allow file name patterns for jsp compilation for jspc plugin
  + JETTY-529 CNFE when deserializing Array from session resolved
  + JETTY-537 JSON handles Locales
@@ -2735,32 +4715,22 @@ jetty-6.1.10 - 20 May 2008
  + JETTY-566 allow for non-blocking behavior in jetty maven plugin
  + JETTY-572 unique cometd client ID
  + JETTY-579 osgi fixes with management and servlet resources
+ + Use QueuedThreadPool as default
 
 jetty-7.0.0pre1 - 03 May 2008
- + Allow annotations example to be built regularly, copy to contexts-available
- + Make annotations example consistent with servlet 3.0
- + Refactor JNDI impl to simplify
- + Improved suspend examples
- + address osgi bundling issue relating to build resources
+ + JETTY-440 allow file name patterns for jsp compilation for jspc plugin
  + JETTY-529 CNFE when deserializing Array from session resolved
  + JETTY-558 optional handling of X-Forwarded-For/Host/Server
  + JETTY-559 ignore unsupported shutdownOutput
  + JETTY-566 allow for non-blocking behavior in jetty maven plugin
- + JETTY-440 allow file name patterns for jsp compilation for jspc plugin
+ + address osgi bundling issue relating to build resources
+ + Allow annotations example to be built regularly, copy to contexts-available
+ + Improved suspend examples
+ + Make annotations example consistent with servlet 3.0
+ + Refactor JNDI impl to simplify
 
 jetty-7.0.0pre0 - 21 April 2008
- + Jetty-6.1.8 Changes
- + Refactor of Continuation towards servlet 3.0 proposal
  + JETTY-282 Support manually-triggered reloading by maven plugin
- + QueuedThreadPool default
- + RetryRequest exception now extends ThreadDeath
- + Added option to dispatch to suspended requests.
- + Delay 100 continues until getInputStream
- + HttpClient supports pipelined request
- + BayeuxClient use a single connection for polling
- + Make javax.servlet.jsp optional osgi import for jetty module
- + Ensure Jotm tx mgr can be found in jetty-env.xml
- + Renamed modules management and naming to jmx and jndi.
  + JETTY-341 100-Continues sent only after getInputStream called.
  + JETTY-386 backout fix and replaced with
    ContextHandler.setCompactPath(boolean)
@@ -2797,10 +4767,19 @@ jetty-7.0.0pre0 - 21 April 2008
  + JETTY-556 Encode all URI fragments
  + JETTY-557 Allow ServletContext.setAttribute before start
  + JETTY-560 Allow decoupling of jndi names in web.xml
+ + Added option to dispatch to suspended requests.
+ + BayeuxClient use a single connection for polling
+ + Delay 100 continues until getInputStream
+ + Ensure Jotm tx mgr can be found in jetty-env.xml
+ + HttpClient supports pipelined request
+ + Jetty-6.1.8 Changes
+ + Make javax.servlet.jsp optional osgi import for jetty module
+ + QueuedThreadPool default
+ + Refactor of Continuation towards servlet 3.0 proposal
+ + Renamed modules management and naming to jmx and jndi.
+ + RetryRequest exception now extends ThreadDeath
 
 jetty-6.1.9 - 26 March 2008
- + Make javax.servlet.jsp optional osgi import for jetty module
- + Ensure Jotm tx mgr can be found in jetty-env.xml
  + JETTY-399 update OpenRemoteServiceServlet to gwt 1.4
  + JETTY-471 LDAP JAAS Realm
  + JETTY-475 AJP connector in RPMs
@@ -2813,27 +4792,10 @@ jetty-6.1.9 - 26 March 2008
  + JETTY-535 Fixed Bayeux server side client memory leak
  + JETTY-538 test harness fix for windows
  + JETTY-541 Cometd per client timeouts
+ + Ensure Jotm tx mgr can be found in jetty-env.xml
+ + Make javax.servlet.jsp optional osgi import for jetty module
 
 jetty-6.1.8 - 28 February 2008
- + Added QueuedThreadPool
- + Optimized QuotedStringTokenizer.quote()
- + further Optimizations and improvements of Cometd
- + Optimizations and improvements of Cometd, more pooled objects
- + Improved Cometd timeout handling
- + Added BayeuxService
- + Cookie support in BayeuxClient
- + Improved Bayeux API
- + add removeHandler(Handler) method to HandlerContainer interface
- + Added JSON.Convertor and non static JSON instances
- + Long cache for JSON
- + Fixed JSON negative numbers
- + JSON unquotes /
- + Add "mvn jetty:stop"
- + allow sessions to be periodically persisted to disk
- + grizzly fixed for posts
- + Remove duplicate commons-logging jars and include sslengine in jboss sar
- + Allow code ranges on ErrorPageErrorHandler
- + AJP handles bad mod_jk methods
  + JETTY-350 log ssl errors on SslSocketConnector
  + JETTY-417 JETTY_LOGS environment variable not queried by jetty.sh
  + JETTY-433 ContextDeployer constructor fails unnecessarily when using a
@@ -2854,17 +4816,27 @@ jetty-6.1.8 - 28 February 2008
  + JETTY-513 Terracotta session replication does not work when the initial page
    on each server does not set any attributes
  + JETTY-515 Timer is missing scavenging Task in HashSessionManager
-
-jetty-6.1.7 - 22 December 2007
+ + Add "mvn jetty:stop"
  + Added BayeuxService
  + Added JSON.Convertor and non static JSON instances
- + Add "mvn jetty:stop"
+ + Added QueuedThreadPool
+ + add removeHandler(Handler) method to HandlerContainer interface
+ + AJP handles bad mod_jk methods
+ + Allow code ranges on ErrorPageErrorHandler
  + allow sessions to be periodically persisted to disk
  + Cookie support in BayeuxClient
+ + Fixed JSON negative numbers
+ + further Optimizations and improvements of Cometd
  + grizzly fixed for posts
- + jetty-6.1 branch created from 6.1.6 and r593 of jetty-contrib trunk
+ + Improved Bayeux API
+ + Improved Cometd timeout handling
+ + JSON unquotes /
+ + Long cache for JSON
  + Optimizations and improvements of Cometd, more pooled objects
- + Update java5 patch
+ + Optimized QuotedStringTokenizer.quote()
+ + Remove duplicate commons-logging jars and include sslengine in jboss sar
+
+jetty-6.1.7 - 22 December 2007
  + JETTY-386 CERT-553235 backout fix and replaced with
    ContextHandler.setCompactPath(boolean)
  + JETTY-467 allow URL rewriting to be disabled.
@@ -2872,75 +4844,52 @@ jetty-6.1.7 - 22 December 2007
  + JETTY-474 Fixed case sensitivity issue with HttpFields
  + JETTY-486 Improved jetty.sh script
  + JETTY-487 Handle empty chunked request
+ + Add "mvn jetty:stop"
+ + Added BayeuxService
+ + Added JSON.Convertor and non static JSON instances
+ + allow sessions to be periodically persisted to disk
+ + Cookie support in BayeuxClient
+ + grizzly fixed for posts
+ + jetty-6.1 branch created from 6.1.6 and r593 of jetty-contrib trunk
+ + Optimizations and improvements of Cometd, more pooled objects
+ + Update java5 patch
 
 jetty-6.1.6 - 18 November 2007
- + rudimentary debian packaging
- + updated grizzly connector to 1.6.1
  + JETTY-455 Optional cometd id
  + JETTY-459 Unable to deploy from Eclipse into the root context
  + JETTY-461 fixed cometd unknown channel
  + JETTY-464 typo in ErrorHandler
  + JETTY-465 System.exit() in constructor exception for MultiPartOutputStream
+ + rudimentary debian packaging
+ + updated grizzly connector to 1.6.1
 
 jetty-6.1.6rc1 - 05 November 2007
- + Upgrade jsp 2.1 to SJSAS-9_1-B58G-FCS-08_Sept_2007
- + Housekeeping on poms
- + CERT VU#38616 handle single quotes in cookie names.
- + Improved JSON parsing from Readers
- + Moved some impl classes from jsp-api-2.1 to jsp-2.1
- + Added configuration file for capturing stderr and stdout
- + Updated for dojo 1.0(rc) cometd
- + Give bayeux timer name
- + Give Terracotta session scavenger a name
- + Jetty Eclipse Plugin 1.0.1: force copy of context file on redeploy
  + JETTY-388 Handle utf-16 and other multibyte non-utf-8 form content.
  + JETTY-409 String params that denote files changed to File
  + JETTY-438 handle trailing . in vhosts
  + JETTY-439 Fixed 100 continues clash with Connection:close
- + JETTY-451 Concurrent modification of session during invalidate
  + JETTY-443 windows bug causes Acceptor thread to die
  + JETTY-445 removed test code
  + JETTY-448 added setReuseAddress on AbstractConnector
  + JETTY-450 Bad request for response sent to server
+ + JETTY-451 Concurrent modification of session during invalidate
  + JETTY-452 CERT VU#237888 Dump Servlet - prevent cross site scripting
  + JETTY-453 updated Wadi to 2.0-M7
  + JETTY-454 handle exceptions with themselves as root cause
  + JETTY-456 allow null keystore for osX
  + JETTY-457 AJP certificate chains
+ + Added configuration file for capturing stderr and stdout
+ + CERT VU#38616 handle single quotes in cookie names.
+ + Give bayeux timer name
+ + Give Terracotta session scavenger a name
+ + Housekeeping on poms
+ + Improved JSON parsing from Readers
+ + Jetty Eclipse Plugin 1.0.1: force copy of context file on redeploy
+ + Moved some impl classes from jsp-api-2.1 to jsp-2.1
+ + Updated for dojo 1.0(rc) cometd
+ + Upgrade jsp 2.1 to SJSAS-9_1-B58G-FCS-08_Sept_2007
 
 jetty-6.1.6rc0 - 03 October 2007
- + Added jetty.lib system property to start.config
- + AJP13 Fix on chunked post
- + Fix cached header optimization for extra characters
- + SetUID option to support setgid
- + Make mx4j used only if runtime uses jdk<1.5
- + Moved Grizzly to contrib
- + Give deployment file Scanner threads a unique name
- + Fix Host header for async client
- + Fix typo in async client onResponsetHeader method name
- + Tweak OSGi manifests to remove unneeded imports
- + Allow scan interval to be set after Scanner started
- + Add jetty.host system property
- + Allow properties files on the XmlConfiguration command line.
- + Prevent infinite loop on stopping with temp dir
- + Ensure session is completed only when leaving context.
- + Update terracotta to 2.4.1 and exclude ssl classes
- + Update jasper2.1 to tag SJSAS-9_1-B58C-FCS-22_Aug_2007
- + Removal of unneeded dependencies from management, maven-plugin, naming &
-   plus poms
- + Adding setUsername,setGroupname to setuid and mavenizing native build
- + UTF-8 for bayeux client
- + CVE-2007-5615 Added protection for response splitting with bad headers.
- + Cached user agents strings in the /org/mortbay/jetty/useragents resource
- + Make default time format for RequestLog match NCSA default
- + Use terracotta repo for build; make jetty a terracotta module
- + Fix patch for java5 to include cometd module
- + Added ConcatServlet to combine javascript and css
- + Add ability to persist sessions with HashSessionManager
- + Avoid FULL exception in window between blockForOutput and remote close
- + Added JPackage RPM support
- + Added JSON.Convertable
- + Updated README, test index.html file and jetty-plus.xml file
  + JETTY-259 SystemRoot set for windows CGI
  + JETTY-311 avoid json keywords
  + JETTY-376 allow anything but CRLF in reason string
@@ -2959,28 +4908,46 @@ jetty-6.1.6rc0 - 03 October 2007
  + JETTY-425 Handle duplicate stop calls better
  + JETTY-430 improved cometd logging
  + JETTY-431 HttpClient soTimeout
+ + Add ability to persist sessions with HashSessionManager
+ + Added ConcatServlet to combine javascript and css
+ + Added jetty.lib system property to start.config
+ + Added JPackage RPM support
+ + Added JSON.Convertable
+ + Adding setUsername,setGroupname to setuid and mavenizing native build
+ + Add jetty.host system property
+ + AJP13 Fix on chunked post
+ + Allow properties files on the XmlConfiguration command line.
+ + Allow scan interval to be set after Scanner started
+ + Avoid FULL exception in window between blockForOutput and remote close
+ + Cached user agents strings in the /org/mortbay/jetty/useragents resource
+ + CVE-2007-5615 Added protection for response splitting with bad headers.
+ + Ensure session is completed only when leaving context.
+ + Fix cached header optimization for extra characters
+ + Fix Host header for async client
+ + Fix patch for java5 to include cometd module
+ + Fix typo in async client onResponsetHeader method name
+ + Give deployment file Scanner threads a unique name
+ + Make default time format for RequestLog match NCSA default
+ + Make mx4j used only if runtime uses jdk<1.5
+ + Moved Grizzly to contrib
+ + Prevent infinite loop on stopping with temp dir
+ + Removal of unneeded dependencies from management, maven-plugin, naming &
+   plus poms
+ + SetUID option to support setgid
+ + Tweak OSGi manifests to remove unneeded imports
+ + Updated README, test index.html file and jetty-plus.xml file
+ + Update jasper2.1 to tag SJSAS-9_1-B58C-FCS-22_Aug_2007
+ + Update terracotta to 2.4.1 and exclude ssl classes
+ + Use terracotta repo for build; make jetty a terracotta module
+ + UTF-8 for bayeux client
 
 jetty-6.1.5 - 19 July 2007
- + Upgrade to Jasper 2.1 tag SJSAS-9_1-B50G-BETA3-27_June_2007
+ + JETTY-392 updated LikeJettyXml example
  + Fixed GzipFilter for dispatchers
  + Fixed reset of reason
- + JETTY-392 updated LikeJettyXml example
+ + Upgrade to Jasper 2.1 tag SJSAS-9_1-B50G-BETA3-27_June_2007
 
 jetty-6.1.5rc0 - 15 July 0200
- + update terracotta session clustering to terracotta 2.4
- + SetUID option to only open connectors before setUID.
- + Protect SslSelectChannelConnector from exceptions during close
- + Improved Request log configuration options
- + Added GzipFilter and UserAgentFilter
- + make OSGi manifests for jetty jars
- + update terracotta configs for tc 2.4 stable1
- + remove call to open connectors in jetty.xml
- + update links on website
- + make jetty plus example webapps use ContextDeployer
- + Dispatch SslEngine expiry (non atomic)
- + Make SLF4JLog impl public, add mbean descriptors
- + SPR-3682 - dont hide forward attr in include.
- + Upgrade to Jasper 2.1 tag SJSAS-9_1-B50G-BETA3-27_June_2007
  + JETTY-253 Improved graceful shutdown
  + JETTY-373 Stop all dependent lifecycles
  + JETTY-374 HttpTesters handles large requests/responses
@@ -2992,20 +4959,28 @@ jetty-6.1.5rc0 - 15 July 0200
  + JETTY-380 handle pipelines of more than 4 requests!
  + JETTY-385 EncodeURL for new sessions from dispatch
  + JETTY-386 Allow // in file resources
+ + Added GzipFilter and UserAgentFilter
+ + Dispatch SslEngine expiry (non atomic)
+ + Improved Request log configuration options
+ + make jetty plus example webapps use ContextDeployer
+ + make OSGi manifests for jetty jars
+ + Make SLF4JLog impl public, add mbean descriptors
+ + Protect SslSelectChannelConnector from exceptions during close
+ + remove call to open connectors in jetty.xml
+ + SetUID option to only open connectors before setUID.
+ + SPR-3682 - dont hide forward attr in include.
+ + update links on website
+ + update terracotta configs for tc 2.4 stable1
+ + update terracotta session clustering to terracotta 2.4
+ + Upgrade to Jasper 2.1 tag SJSAS-9_1-B50G-BETA3-27_June_2007
 
 jetty-6.1.4 - 15 June 2007
- + fixed early open() call in NIO connectors
- + JETTY-370 ensure maxIdleTime<=0 means connections never expire
+ + JETTY-370 ensure idleTimeout<=0 means connections never expire
  + JETTY-371 Fixed chunked HEAD response
  + JETTY-372 make test for cookie caching more rigorous
+ + fixed early open() call in NIO connectors
 
 jetty-6.1.4rc1 - 10 June 2007
- + Work around IBM JVM socket close issue
- + moved documentation for jetty and jspc maven plugins to wiki
- + async client improvements
- + fixed handling of large streamed files
- + Fixed synchronization conflict SslSelectChannel and SelectChannel
- + Optional static content cache
  + JETTY-310 better exception when no filter file for cometd servlet
  + JETTY-323 handle htaccess without a user realm
  + JETTY-346 add wildcard support to extra scan targets for maven plugin
@@ -3017,24 +4992,14 @@ jetty-6.1.4rc1 - 10 June 2007
  + JETTY-362 More object locks
  + JETTY-365 make needClientAuth work on SslSelectChannelConnector
  + JETTY-366 JETTY-368 Improved bayeux disconnect
+ + async client improvements
+ + fixed handling of large streamed files
+ + Fixed synchronization conflict SslSelectChannel and SelectChannel
+ + moved documentation for jetty and jspc maven plugins to wiki
+ + Optional static content cache
+ + Work around IBM JVM socket close issue
 
 jetty-6.1.4rc0 - 01 June 2007
- + Reorganized import of contrib modules
- + Unified JMX configuration
- + Updated slf4j version to 1.3.1
- + Updated junit to 3.8.2
- + Allow XmlConfiguration properties to be configured
- + Add (commented out) jspc precompile to test-webapp
- + Add slf4j-api for upgraded version
- + Change scope of fields for Session
- + Add ability to run cometd webapps to maven plugin
- + Delay ssl handshake until after dispatch in sslSocketConnector
- + Set so_timeout during ssl handshake as an option on SslSocketConnector
- + Optional send Date header. Server.setSendDateHeader(boolean)
- + update etc/jetty-ssl.xml with new handshake timeout setting
- + fixed JSP close handling
- + improved date header handling
- + fixed waiting continuation reset
  + JETTY-257 fixed comet cross domain
  + JETTY-309 fix applied to sslEngine
  + JETTY-317 rollback inclusion of cometd jar for maven plugin
@@ -3050,30 +5015,46 @@ jetty-6.1.4rc0 - 01 June 2007
  + JETTY-345 fixed lost content with blocked NIO.
  + JETTY-347 Fixed type util init
  + JETTY-352 Object locks
+ + Add (commented out) jspc precompile to test-webapp
+ + Add ability to run cometd webapps to maven plugin
+ + Add slf4j-api for upgraded version
+ + Allow XmlConfiguration properties to be configured
+ + Change scope of fields for Session
+ + Delay ssl handshake until after dispatch in sslSocketConnector
+ + fixed JSP close handling
+ + fixed waiting continuation reset
+ + improved date header handling
+ + Optional send Date header. Server.setSendDateHeader(boolean)
+ + Reorganized import of contrib modules
+ + Set so_timeout during ssl handshake as an option on SslSocketConnector
+ + Unified JMX configuration
+ + Updated junit to 3.8.2
+ + Updated slf4j version to 1.3.1
+ + update etc/jetty-ssl.xml with new handshake timeout setting
 
 jetty-6.1.3 - 04 May 2007
- + Handle CRLF for content in header optimization
  + JETTY-309 don't clear writable status until dispatch
  + JETTY-315 suppressed warning
  + JETTY-322 AJP13 cping and keep alive
+ + Handle CRLF for content in header optimization
 
 jetty-6.1.2 - 01 May 2007
- + Improved unavailabile handling
- + sendError resets output state
- + Fixed session invalidation error in WadiSessionManager
- + Updated Wadi to version 2.0-M3
- + Added static member definition in WadiSessionManager
  + JETTY-322 fix ajp cpong response and close handling
  + JETTY-324 fix ant plugin
  + JETTY-328 updated jboss
+ + Added static member definition in WadiSessionManager
+ + Fixed session invalidation error in WadiSessionManager
+ + Improved unavailabile handling
+ + sendError resets output state
+ + Updated Wadi to version 2.0-M3
 
 jetty-6.1.2rc5 - 24 April 2007
- + set default keystore for SslSocketConnector
- + removed some compile warnings
- + Allow jsp-file to be / or /*
  + JETTY-305 delayed connection destroy
  + JETTY-309 handle close in multivalue connection fields.
  + JETTY-314 fix for possible NPE in Request.isRequestedSessionIdValid
+ + Allow jsp-file to be / or /*
+ + removed some compile warnings
+ + set default keystore for SslSocketConnector
 
 jetty-6.1.2rc4 - 19 April 2007
  + JETTY-294 Fixed authentication reset
@@ -3084,12 +5065,6 @@ jetty-6.1.2rc4 - 19 April 2007
  + JETTY-304 Fixed authentication reset
 
 jetty-6.1.2rc3 - 16 April 2007
- + Improved performance and exclusions for TLD scanning
- + MBean properties assume writeable unless marked RO
- + refactor of SessionManager and SessionIdManager for clustering
- + Improvements to allow simple setting of Cache-Control headers
- + AJP redirects https requests correctly
- + Fixed writes of unencoded char arrays.
  + JETTY-283 Parse 206 and 304 responses in client
  + JETTY-285 enable jndi for mvn jetty:run-war and jetty:run-exploded
  + JETTY-289 fixed javax.net.ssl.SSLException on binary file upload
@@ -3100,20 +5075,14 @@ jetty-6.1.2rc3 - 16 April 2007
  + JETTY-296 Close direct content inputstreams
  + JETTY-297 Recreate tmp dir on stop/start
  + JETTY-298 Names in JMX ObjectNames for context, servlets and filters
+ + AJP redirects https requests correctly
+ + Fixed writes of unencoded char arrays.
+ + Improved performance and exclusions for TLD scanning
+ + Improvements to allow simple setting of Cache-Control headers
+ + MBean properties assume writeable unless marked RO
+ + refactor of SessionManager and SessionIdManager for clustering
 
 jetty-6.1.2rc2 - 27 March 2007
- + Enable the SharedStoreContextualiser for the WadiSessionManager(Database
-   store for clustering)
- + AJP13 CPING request and CPONG response implemented
- + AJP13 Shutdown Request from peer implemented
- + AJP13 remoteUser, contextPath, servletPath requests implemented
- + Change some JNDI logging to debug level instead of info
- + Update jasper to glassfish tag SJSAS-9_1-B39-RC-14_Mar_2007
- + Optimized multi threaded init on startup servlets
- + Removed unneeded specialized TagLibConfiguration class from maven plugin
- + Refactor Scanner to increase code reuse with maven/ant plugins
- + Added RestFilter for PUT and DELETE from Aleksi Kallio
- + Make annotations work for maven plugin
  + JETTY-125 maven plugin: ensure test dependencies on classpath for
    <useTestClasspath>
  + JETTY-246 path encode cookies rather than quote
@@ -3135,23 +5104,20 @@ jetty-6.1.2rc2 - 27 March 2007
  + JETTY-284 Fixed stop connector race
  + JETTY-286 isIntegral and isConfidential methods overridden in
    SslSelectChannelConnector
+ + Added RestFilter for PUT and DELETE from Aleksi Kallio
+ + AJP13 CPING request and CPONG response implemented
+ + AJP13 remoteUser, contextPath, servletPath requests implemented
+ + AJP13 Shutdown Request from peer implemented
+ + Change some JNDI logging to debug level instead of info
+ + Enable the SharedStoreContextualiser for the WadiSessionManager(Database
+   store for clustering)
+ + Make annotations work for maven plugin
+ + Optimized multi threaded init on startup servlets
+ + Refactor Scanner to increase code reuse with maven/ant plugins
+ + Removed unneeded specialized TagLibConfiguration class from maven plugin
+ + Update jasper to glassfish tag SJSAS-9_1-B39-RC-14_Mar_2007
 
 jetty-6.1.2rc1 - 08 March 2007
- + TagLibConfiguration uses resource input stream
- + Improved handling of early close in AJP
- + add ajp connector jar to jetty-jboss sar
- + Improved Context setters for wadi support
- + fix Dump servlet to handle primitive array types
- + handle comma separated values for the Connection: header
- + Added option to allow null pathInfo within context
- + BoundedThreadPool queues rather than blocks excess jobs.
- + Support null pathInfo option for webservices deployed to jetty/jboss
- + Workaround to call SecurityAssocation.clear() for jboss webservices calls to
-   ejbs
- + Ensure jetty/jboss uses servlet-spec classloading order
- + call preDestroy() after servlet/filter destroy()
- + Fix constructor for Constraint to detect wildcard role
- + Added support for lowResourcesIdleTime to SelectChannelConnector
  + JETTY-157 make CGI handle binary data
  + JETTY-175 JDBCUserRealm use getInt instead of getObject
  + JETTY-188 Use timer for session scavaging
@@ -3165,6 +5131,21 @@ jetty-6.1.2rc1 - 08 March 2007
  + JETTY-250 protect attribute enumerations from modification
  + JETTY-252 Fixed stats handling of close connection
  + JETTY-254 prevent close of jar file by bad JVMs
+ + add ajp connector jar to jetty-jboss sar
+ + Added option to allow null pathInfo within context
+ + Added support for lowResourcesIdleTime to SelectChannelConnector
+ + BoundedThreadPool queues rather than blocks excess jobs.
+ + call preDestroy() after servlet/filter destroy()
+ + Ensure jetty/jboss uses servlet-spec classloading order
+ + Fix constructor for Constraint to detect wildcard role
+ + fix Dump servlet to handle primitive array types
+ + handle comma separated values for the Connection: header
+ + Improved Context setters for wadi support
+ + Improved handling of early close in AJP
+ + Support null pathInfo option for webservices deployed to jetty/jboss
+ + TagLibConfiguration uses resource input stream
+ + Workaround to call SecurityAssocation.clear() for jboss webservices calls to
+   ejbs
 
 jetty-6.1.2rc0 - 15 February 2007
  + JETTY-223 Fix disassociate of UserPrincipal on dispatches
@@ -3173,24 +5154,20 @@ jetty-6.1.2rc0 - 15 February 2007
  + JETTY-236 Buffer leak
  + JETTY-237 AJPParser Buffer Data Handling
  + JETTY-238 prevent form truncation
- + Patches from sybase for ClientCertAuthenticator
  + Coma separated cookies
  + Cometd timeout clients
+ + Patches from sybase for ClientCertAuthenticator
 
 jetty-6.1.2pre1 - 05 February 2007
  + JETTY-224 run build up to process-test before invoking jetty:run
  + Added error handling for incorrect keystore/truststore password in
    SslSelectChannelConnector
- + fixed bug with virtual host handling in ContextHandlerCollection
  + added win32service to standard build
- + refactored cometd to be continuation independent
  + allow ResourceHandler to use resource base from an enclosing ContextHandler
+ + fixed bug with virtual host handling in ContextHandlerCollection
+ + refactored cometd to be continuation independent
 
 jetty-6.1.2pre0 - 01 February 2007
- + Fixed 1.4 method in jetty plus
- + Fixed generation of errors during jsp compilation for jsp-2.1
- + Added cometd jsonp transport from aabeling
- + Added terracotta cluster support for cometd
  + JETTY-213 request.isUserInRole(String) fixed
  + JETTY-215 exclude more transitive dependencies from tomcat jars for jsp-2.0
  + JETTY-216 handle AJP packet fragmentation
@@ -3198,113 +5175,128 @@ jetty-6.1.2pre0 - 01 February 2007
  + JETTY-219 fixed trailing encoded chars in cookies
  + JETTY-220 fixed AJP content
  + JETTY-222 fix problem parsing faces-config.xml
+ + Added cometd jsonp transport from aabeling
+ + Added terracotta cluster support for cometd
  + add support for Annotations in servlet, filter and listener sources
- + improved writer buffering
- + moved JSON parser to util to support reuse
- + handle virtual hosts in ContextHandlerCollection
  + enable SslSelectChannelConnector to modify the SslEngine's client
    authentication settings
+ + Fixed 1.4 method in jetty plus
+ + Fixed generation of errors during jsp compilation for jsp-2.1
+ + handle virtual hosts in ContextHandlerCollection
+ + improved writer buffering
+ + moved JSON parser to util to support reuse
 
 jetty-6.1.1 - 15 January 2007
 
 jetty-6.1.1rc1 - 12 January 2007
- + Use timers for Rollover logs and scanner
  + JETTY-210 Build jsp-api-2.0 for java 1.4
+ + Use timers for Rollover logs and scanner
 
 jetty-6.1.1rc0 - 10 January 2007
- + Fixed unpacking WAR
- + extras/win32service download only if no JavaServiceWrapper exist
- + MultiPartFilter deleteFiles option
- + CGI servlet fails without exception
  + JETTY-209 Added ServletTester.createSocketConnector
  + JETTY-210 Build servlet-api-2.5 for java 1.4
  + JETTY-211 fixed jboss build
+ + CGI servlet fails without exception
  + ensure response headers on AjaxFilter messsages turn off caching
+ + extras/win32service download only if no JavaServiceWrapper exist
+ + Fixed unpacking WAR
+ + MultiPartFilter deleteFiles option
+ + simplified chat demo
  + start webapps on deployment with jboss, use isDistributed() method from
    WebAppContext
- + simplified chat demo
 
 jetty-6.1.0 - 09 January 2007
  + Fixed unpacking WAR
 
 jetty-6.1.0 - 05 January 2007
- + Improved config of java5 threadpool
- + Protect context deployer from Errors
+ + JETTY-206 fixed AJP getServerPort and getRemotePort
+ + Added extras/win32service
  + Added WebAppContext.setCopyWebDir to avoid JVM jar caching issues.
  + GERONIMO-2677 refactor of session id handling for clustering
+ + Improved config of java5 threadpool
+ + Protect context deployer from Errors
  + ServletTester sets content length
- + Added extras/win32service
- + JETTY-206 fixed AJP getServerPort and getRemotePort
 
 jetty-6.1.0rc3 - 02 January 2007
  + JETTY-195 fixed ajp ssl_cert handling
  + JETTY-197 fixed getRemoteHost
  + JETTY-203 initialize ServletHandler if no Context instance
  + JETTY-204 setuid fix
+ + extras/servlet-tester
+ + implement resource injection and lifecycle callbacks declared in web.xml
  + setLocale does not use default content type
  + Use standard releases of servlet and jsp APIs.
- + implement resource injection and lifecycle callbacks declared in web.xml
- + extras/servlet-tester
 
 jetty-6.1.0rc2 - 20 December 2006
+ + JETTY-167 cometd refactor
+ + JETTY-194 doubles slashes are significant in URIs
+ + JETTY-201 make run-as work for both web container and ejb container in jboss
  + AJP13Parser, throw IllegalStateException on unimplemented AJP13 Requests
  + ContextHandlerCollection is noop with no handlers
+ + ensure classpath passed to jspc contains file paths not urls
+ + ensure com.sun.el.Messages.properties included in jsp-2.1 jar
  + ensure servlets initialized if only using ServletHandler
  + fixed Jetty-197 AJP13 getRemoteHost()
  + Refactored AbstractSessionManager for ehcache
- + ensure classpath passed to jspc contains file paths not urls
- + JETTY-194 doubles slashes are significant in URIs
- + JETTY-167 cometd refactor
  + remove code to remove SecurityHandler if no constraints present
- + JETTY-201 make run-as work for both web container and ejb container in jboss
- + ensure com.sun.el.Messages.properties included in jsp-2.1 jar
 
 jetty-6.1.0rc1 - 14 December 2006
- + simplified idle timeout handling
  + JETTY-193 MailSessionReference without authentication
  + JETTY-199 newClassPathResource
- + ensure unique name for ServletHolder instances
  + added cache session manager(pre-alpha)
+ + ensure unique name for ServletHolder instances
+ + simplified idle timeout handling
 
 jetty-6.1.0rc0 - 08 December 2006
+ + JETTY-123 fix improved
  + JETTY-181 Allow injection of a java:comp Context
  + JETTY-182 Optionally set JSP classpath initparameter
- + Dispatcher does not protect javax.servlet attributes
+ + JETTY-184 cometd connect non blocking
+ + JETTY-185 tmp filename generation
+ + JETTY-189 ProxyConnection
+ + 403 for BASIC authorization failure
+ + Added extras/gwt
+ + Added org.mortbay.thread.concurrent.ThreadPool
+ + Added spring ejb3 demo example
  + DefaultHandler links virtual hosts.
+ + Dispatcher does not protect javax.servlet attributes
  + Fixed cachesize on invalidate
+ + Fixed idle timeout
+ + flush if content-length written
+ + forward query attribute fix
+ + Handle request content encodings
+ + null for unknown named dispatches
  + Optimization of writers
  + ServletHandler allows non REQUEST exceptions to propogate
- + TCK fixes from Sybase:
- + Handle request content encodings
- + forward query attribute fix
- + session attribute listener
  + Servlet role ref
- + flush if content-length written
- + 403 for BASIC authorization failure
- + null for unknown named dispatches
- + JETTY-184 cometd connect non blocking
+ + session attribute listener
  + Support for RFC2518 102-processing response
- + JETTY-123 fix improved
- + Added org.mortbay.thread.concurrent.ThreadPool
- + Added extras/gwt
- + Fixed idle timeout
- + JETTY-189 ProxyConnection
- + Added spring ejb3 demo example
+ + TCK fixes from Sybase:
  + update jasper to glassfish SJSAS-9_1-B27-EA-07_Dec_2006
- + JETTY-185 tmp filename generation
 
 jetty-6.1.0pre3 - 22 November 2006
- + fixed NIO endpoint flush. Avoid duplicate sends
- + CVE-2006-6969 Upgraded session ID generation to use SecureRandom
- + updated glassfish jasper to tag SJSAS-9_1-B25-EA-08_Nov_2006
- + Support TLS_DHE_RSA_WITH_AES_256_CBC_SHA
- + JETTY-180 XBean support for context deploy
  + JETTY-154 Cookies are double quotes only
+ + JETTY-180 XBean support for context deploy
+ + CVE-2006-6969 Upgraded session ID generation to use SecureRandom
  + Expose isResumed on Continuations
+ + fixed NIO endpoint flush. Avoid duplicate sends
  + Refactored AJP generator
+ + Support TLS_DHE_RSA_WITH_AES_256_CBC_SHA
+ + updated glassfish jasper to tag SJSAS-9_1-B25-EA-08_Nov_2006
 
 jetty-6.0.2 - 22 November 2006
- + Moved all modules updates from 6.1pre2 to 6.0
+ + JETTY-118 ignore extra content after close.
+ + JETTY-119 cleanedup Security optimizatoin
+ + JETTY-123 handle windows UNC paths
+ + JETTY-126 handle content > Integer.MAX_VALUE
+ + JETTY-129 ServletContextListeners called after servlets are initialized
+ + JETTY-151 Idle timeout only applies to blocking operations
+ + JETTY-154 Cookies are double quotes only
+ + JETTY-171 Fixed filter mapping
+ + JETTY-172 use getName() instead of toString
+ + JETTY-173 restore servletpath after dispatch
+ + (re)make JAAS classes available to webapp classloader
+ + add <Property> replacement in jetty xml config files
  + Added concept of bufferred endpoint
  + Added conversion Object -> ObjectName for the result of method calls made on
    MBeans
@@ -3315,9 +5307,8 @@ jetty-6.0.2 - 22 November 2006
  + Added ID constructor to AbstractSessionManager.Session
  + added isStopped() in LifeCycle and AbstractLifeCycle
  + Added override descriptor for deployment of RO webapps
- + add <Property> replacement in jetty xml config files
- + alternate optimizations of writer (use -Dbuffer.writers=true)
  + Allow session cookie to be refreshed
+ + alternate optimizations of writer (use -Dbuffer.writers=true)
  + Apply queryEncoding to getQueryString
  + CGI example in test webapp
  + change examples/test-jndi-webapp so it can be regularly built
@@ -3332,405 +5323,395 @@ jetty-6.0.2 - 22 November 2006
  + Fixed tld parsing for maven plugin
  + HttpGenerator can generate requests
  + Improved *-mbean.properties files and specialized some MBean
- + JETTY-118 ignore extra content after close.
- + JETTY-119 cleanedup Security optimizatoin
- + JETTY-123 handle windows UNC paths
- + JETTY-126 handle content > Integer.MAX_VALUE
- + JETTY-129 ServletContextListeners called after servlets are initialized
- + JETTY-151 Idle timeout only applies to blocking operations
- + JETTY-154 Cookies are double quotes only
- + JETTY-171 Fixed filter mapping
- + JETTY-172 use getName() instead of toString
- + JETTY-173 restore servletpath after dispatch
  + Major refactor of SelectChannel EndPoint for client selector
  + make .tag files work in packed wars
+ + Moved all modules updates from 6.1pre2 to 6.0
  + Plugin shutdown context before stopping it.
  + Refactored session lifecycle and additional tests
  + release resource lookup in Default servlet
- + (re)make JAAS classes available to webapp classloader
  + Reverted UnixCrypt to use coersions (that effected results)
  + Session IDs can change worker ID
  + Simplified ResourceCache and Default servlet
  + SocketConnector closes all connections in doStop
- + Upgraded session ID generation to use SecureRandom
- + updated glassfish jasper to tag SJSAS-9_1-B25-EA-08_Nov_2006
  + Support TLS_DHE_RSA_WITH_AES_256_CBC_SHA
+ + updated glassfish jasper to tag SJSAS-9_1-B25-EA-08_Nov_2006
+ + Upgraded session ID generation to use SecureRandom
 
 jetty-5.1.14 - 09 August 2007
- + patched with correct version
  + JETTY-155 force close with content length.
  + JETTY-369 failed state in Container
+ + patched with correct version
 
 jetty-5.1.13
  + Sourceforge 1648335: problem setting version for AJP13
 
 jetty-5.1.12 - 22 November 2006
+ + JETTY-154 Cookies ignore single quotes
  + Added support for TLS_DHE_RSA_WITH_AES_256_CBC_SHA
- + Upgraded session ID generation to use SecureRandom
- + Quote single quotes in cookies
  + AJP protected against bad requests from mod_jk
- + JETTY-154 Cookies ignore single quotes
+ + Quote single quotes in cookies
+ + Upgraded session ID generation to use SecureRandom
 
 jetty-4.2.27 - 22 November 2006
- + Upgraded session ID generation to use SecureRandom
  + AJP protected against bad requests from mod_jk
+ + Upgraded session ID generation to use SecureRandom
 
 jetty-6.1.0pre2 - 20 November 2006
  + Added extraClassPath to WebAppContext
- + Fixed resource cache flushing
  + Clean up jboss module licensing
+ + Fixed resource cache flushing
 
 jetty-6.1.0pre1 - 19 November 2006
- + Use ContextDeployer as main deployer in jetty.xml
- + Added extras/jboss
- + Major refactor of SelectChannel EndPoint for client selector
- + Fixed NPE in bio.SocketEndPoint.getRemoteAddr()
- + Reverted UnixCrypt to use coersions (that effected results)
  + JETTY-151 Idle timeout only applies to blocking operations
- + alternate optimizations of writer (use -Dbuffer.writers=true)
  + JETTY-171 Fixed filter mapping
  + JETTY-172 use getName() instead of toString
  + JETTY-173 restore servletpath after dispatch
+ + Added extras/jboss
+ + Added hierarchical destroy of mbeans
+ + Added override descriptor for deployment of RO webapps
+ + alternate optimizations of writer (use -Dbuffer.writers=true)
+ + Fixed NPE in bio.SocketEndPoint.getRemoteAddr()
+ + Major refactor of SelectChannel EndPoint for client selector
  + release resource lookup in Default servlet
+ + Reverted UnixCrypt to use coersions (that effected results)
  + Simplified ResourceCache and Default servlet
- + Added override descriptor for deployment of RO webapps
- + Added hierarchical destroy of mbeans
+ + Use ContextDeployer as main deployer in jetty.xml
 
 jetty-6.1.0pre0 - 21 October 2006
- + add <Property> replacement in jetty xml config files
- + make .tag files work in packed wars
- + add hot deployment capability
- + ensure setContextPath() works when invoked from jetty-web.xml
- + ensure sessions nulled out on request recycle; ensure session null after
-   invalidate
- + ensure "" returned for ServletContext.getContextPath() for root context
- + Fixed tld parsing for maven plugin
- + Improved *-mbean.properties files and specialized some MBean
- + Added conversion Object -> ObjectName for the result of method calls made on
-   MBeans
- + JETTY-129 ServletContextListeners called after servlets are initialized
- + change examples/test-jndi-webapp so it can be regularly built
- + added isStopped() in LifeCycle and AbstractLifeCycle
- + fixed isUserInRole checking for JAASUserRealm
- + fixed ClassCastException in JAASUserRealm.setRoleClassNames(String[])
- + add a maven-jetty-jspc-plugin to do jspc precompilation
- + added examples/test-jaas-webapp
- + (re)make JAAS classes available to webapp classloader
- + CGI example in test webapp
- + Plugin shutdown context before stopping it.
- + Added concept of bufferred endpoint
- + Factored ErrorPageErrorHandler out of WebAppContext
- + Refactored ErrorHandler to avoid statics
- + Transforming classloader does not transform resources.
- + SocketConnector closes all connections in doStop
- + Improved charset handling in URLs
- + minor optimization of bytes to UTF8 strings
  + JETTY-112 ContextHandler checks if started
  + JETTY-113 support optional query char encoding on requests
  + JETTY-114 removed utf8 characters from code
  + JETTY-115 Fixed addHeader
- + added cometd chat demo
+ + JETTY-118 ignore extra content after close.
  + JETTY-119 cleanedup Security optimizatoin
- + Refactored session lifecycle and additional tests
  + JETTY-121 init not called on externally constructed servlets
+ + JETTY-123 handle windows UNC paths
  + JETTY-124 always initialize filter caches
  + JETTY-126 handle content > Integer.MAX_VALUE
- + JETTY-123 handle windows UNC paths
- + JETYY-120 SelectChannelConnector closes all connections on stop
- + Added ID constructor to AbstractSessionManager.Session
- + Allow session cookie to be refreshed
+ + JETTY-129 ServletContextListeners called after servlets are initialized
+ + (re)make JAAS classes available to webapp classloader
+ + add <Property> replacement in jetty xml config files
+ + add a maven-jetty-jspc-plugin to do jspc precompilation
+ + added cometd chat demo
+ + Added concept of bufferred endpoint
+ + Added conversion Object -> ObjectName for the result of method calls made on
+   MBeans
  + Added DataFilter configuration to cometd
+ + added examples/test-jaas-webapp
  + Added extras/setuid to support start as root
+ + Added ID constructor to AbstractSessionManager.Session
+ + added isStopped() in LifeCycle and AbstractLifeCycle
+ + add hot deployment capability
+ + AJP Connector
+ + Allow session cookie to be refreshed
  + Apply queryEncoding to getQueryString
- + JETTY-118 ignore extra content after close.
+ + CGI example in test webapp
+ + change examples/test-jndi-webapp so it can be regularly built
+ + Default soLinger is -1 (disabled)
+ + ensure "" returned for ServletContext.getContextPath() for root context
+ + ensure sessions nulled out on request recycle; ensure session null after
+   invalidate
+ + ensure setContextPath() works when invoked from jetty-web.xml
+ + Factored ErrorPageErrorHandler out of WebAppContext
+ + fixed ClassCastException in JAASUserRealm.setRoleClassNames(String[])
+ + fixed isUserInRole checking for JAASUserRealm
+ + Fixed tld parsing for maven plugin
  + HttpGenerator can generate requests
+ + Improved *-mbean.properties files and specialized some MBean
+ + Improved charset handling in URLs
+ + JETYY-120 SelectChannelConnector closes all connections on stop
+ + make .tag files work in packed wars
+ + minor optimization of bytes to UTF8 strings
+ + Plugin shutdown context before stopping it.
  + Ported HtAccessHandler
- + Start of a client API
+ + Refactored ErrorHandler to avoid statics
+ + Refactored session lifecycle and additional tests
  + Session IDs can change worker ID
- + Default soLinger is -1 (disabled)
- + AJP Connector
+ + SocketConnector closes all connections in doStop
+ + Start of a client API
+ + Transforming classloader does not transform resources.
 
 jetty-5.1.11 - 08 October 2006
- + fixed ByteBufferOutputStream capacity calculation
- + Fixed AJP handling of certificate length (1494939)
+ + Default servlet only uses setContentLength on wrapped responses
  + Fixed AJP chunk header (1507377)
+ + Fixed AJP handling of certificate length (1494939)
+ + fixed ByteBufferOutputStream capacity calculation
  + Fixed order of destruction event calls
  + Fix to HttpOutputStream from M.Traverso
- + Default servlet only uses setContentLength on wrapped responses
 
 jetty-4.2.26 - 08 October 2006
  + Backport of AJP fixes
 
 jetty-6.0.1 - 24 September 2006
- + fixed isUserInRole checking for JAASUserRealm
- + fixed ClassCastException in JAASUserRealm.setRoleClassNames(String[])
- + Improved charset handling in URLs
- + Factored ErrorPageErrorHandler out of WebAppContext
- + Refactored ErrorHandler to avoid statics
  + JETTY-112 ContextHandler checks if started
+ + JETTY-113 support optional query char encoding on requests
  + JETTY-114 removed utf8 characters from code
  + JETTY-115 Fixed addHeader
  + JETTY-121 init not called on externally constructed servlets
- + minor optimization of bytes to UTF8 strings
- + JETTY-113 support optional query char encoding on requests
  + JETTY-124 always initialize filter caches
+ + Factored ErrorPageErrorHandler out of WebAppContext
+ + fixed ClassCastException in JAASUserRealm.setRoleClassNames(String[])
+ + fixed isUserInRole checking for JAASUserRealm
+ + Improved charset handling in URLs
  + JETYY-120 SelectChannelConnector closes all connections on stop
+ + minor optimization of bytes to UTF8 strings
+ + Refactored ErrorHandler to avoid statics
 
 jetty-6.0.0 - 10 September 2006
- + SocketConnector closes all connections in doStop
  + Conveniance builder methods for listeners and filters
- + Transforming classloader does not transform resources.
  + Plugin shutdown context before stopping it.
+ + SocketConnector closes all connections in doStop
+ + Transforming classloader does not transform resources.
 
 jetty-6.0.0rc4 - 05 September 2006
- + bind jetty-env.xml entries to java:comp/env
  + JETTY-107 Poor cast in SessionDump demo.
+ + bind jetty-env.xml entries to java:comp/env
  + Set charset on error pages
 
 jetty-6.0.0rc3 - 01 September 2006
- + pulled 6.0.0 branch
- + JETTY-103
- + Move MailSessionReference to org.mortbay.naming.factories
- + Less verbose handling of BadResources from bad URLs
+ + JETTY-68 Complete request after sendRedirect
+ + JETTY-104 (raised glassfish ISSUE-1044) hide JSP forced path attribute
  + Avoid double error handling of Bad requests
  + don't warn for content length on head requests
- + JETTY-104 (raised glassfish ISSUE-1044) hide JSP forced path attribute
- + JETTY-68 Complete request after sendRedirect
+ + JETTY-103
+ + Less verbose handling of BadResources from bad URLs
+ + Move MailSessionReference to org.mortbay.naming.factories
+ + pulled 6.0.0 branch
  + Transferred the sslengine patch from the patches directory to extras
 
 jetty-6.0.0rc2 - 25 August 2006
- + use mvn -Dslf4j=false jetty:run to disable use of slf4j logging with
-   jdk1.4/jsp2.0
  + added org.apache.commons.logging package to system classes that can't be
    overridden by a webapp classloader
+ + Destroy HttpConnection to improve buffer pooling
+ + Direct buffer useage is optional
+ + Fixed NPE when no resource cache
+ + Moved more utility packagtes to the util jar
  + mvn -Djetty.port=x jetty:run uses port number given for the default
    connector
- + Fixed NPE when no resource cache
  + Refactored WebXmlConfiguration to allow custom web.xml resource
- + Moved more utility packagtes to the util jar
- + Direct buffer useage is optional
- + Destroy HttpConnection to improve buffer pooling
  + Timestamp in StdErrLog
+ + use mvn -Dslf4j=false jetty:run to disable use of slf4j logging with
+   jdk1.4/jsp2.0
 
 jetty-6.0.0rc1 - 16 August 2006
- + Support for binding References and Referenceables and javax.mail.Sessions in
-   JNDI
- + Added TransformingWebAppClassLoader for spring 2.0 byte code modification
-   support
- + JETTY-90
- + Fixed FD leak for bad TCP acks. JETTY-63
- + JETTY-87
- + Change path mapping so that a path spec of /foo/* does not match /foo.bar :
-   JETTY-88
- + add <requestLog> config param to jetty plugin
  + JETTY-85 JETTY-86 (TrustManager and SecureRandom are now configurable;
    better handling of null/default values)
- + parse jsp-property-group in web.xml for additional JSP servlet mappings
- + protected setContentType from being set during include
- + JETTY-91
+ + add <requestLog> config param to jetty plugin
  + added modules/spring with XmlBeanFactory configuration
- + removed support for lowResources from SelectChannelConnector
+ + Added simple ResourceHandler and FileServer example
  + added start of cometd implementation (JSON only)
  + added start of grizzly connector
- + removed org.mortbay. from context system classes configuration
+ + Added TransformingWebAppClassLoader for spring 2.0 byte code modification
+   support
+ + Allow direct filling of buffers for uncached static content.
+ + Change path mapping so that a path spec of /foo/* does not match /foo.bar :
+   JETTY-88
  + -DSTOP.PORT must be specified.
- + moved optional modules to extras
  + fixed bug that caused Response.setStatus to ignore the provided message
+ + Fixed FD leak for bad TCP acks. JETTY-63
+ + JETTY-87
+ + JETTY-90
+ + JETTY-91
+ + moved optional modules to extras
+ + parse jsp-property-group in web.xml for additional JSP servlet mappings
+ + protected setContentType from being set during include
  + refactored resource cache
- + Allow direct filling of buffers for uncached static content.
- + Added simple ResourceHandler and FileServer example
+ + removed org.mortbay. from context system classes configuration
+ + removed support for lowResources from SelectChannelConnector
+ + Support for binding References and Referenceables and javax.mail.Sessions in
+   JNDI
 
 jetty-6.0.0rc0 - 07 July 2006
- + change prefix from "jetty6" to just "jetty" for plugin: eg is now mvn
-   jetty:run
- + allow <key> or <name> in <systemProperty> for plugin
- + simplified jetty.xml with new constructor injections
- + added setters and getters on SessionManager API for session related config:
-   cookie name, url parameter name, domain, max age and path.
  + add ability to have a lib/ext dir from which to recursively add all jars and
    zips to the classpath
- + patch to allow Jetty to use JSP2.1 from Glassfish instead of Jasper from
-   Tomcat
- + fixed classesDirectory param for maven plugin to be configurable
- + ensure explicitly set tmp directory called "work" is not deleted on exit
- + ensure war is only unpacked if war is newer than "work" directory
- + change name of generated tmp directory to be
-   "Jetty_"+host+"_"+port+"_"+contextpath+"_"+virtualhost
- + Cleaned up idle expiry.
- + Ssl algorithm taken from system property
  + Added 8 random letters&digits to Jetty-generated tmp work dir name to ensure
    uniqueness
- + Simplify runtime resolution of JSP library for plugin
- + Ensure mvn clean cleans the build
- + Do not wrap EofException with EofException
- + reverse order for destroy event listeners
- + added StatisticsHandler and statistics on Connector.
- + Simplified Servlet Context API
+ + added html module from jetty 5 - but deprecated until maintainer found
  + Added maximum limit to filter chain cache.
- + refactor HttpChannelEndPoint in preparation for SslEngine
- + ContextHandlerCollection addContext and setContextClass
- + Discard excess bytes in header buffer if connection is closing
- + Updated javax code from
-   http://svn.apache.org/repos/asf/tomcat/tc6.0.x/trunk/java/javax@417727
- + Threadpool does not need to be a LifeCycle
- + support graceful shutdown
+ + added setters and getters on SessionManager API for session related config:
+   cookie name, url parameter name, domain, max age and path.
+ + added StatisticsHandler and statistics on Connector.
  + Added WebAppContextClassLoader.newInstance to better support exensible
    loaders.
- + immutable getParameterMap()
- + support <load-on-startup> for SingleThreadModel
+ + allow <key> or <name> in <systemProperty> for plugin
  + changed ServletContext.getResourcePaths()  to not return paths containing
    double slashes
+ + change name of generated tmp directory to be
+   "Jetty_"+host+"_"+port+"_"+contextpath+"_"+virtualhost
+ + change prefix from "jetty6" to just "jetty" for plugin: eg is now mvn
+   jetty:run
+ + Cleaned up idle expiry.
+ + ContextHandlerCollection addContext and setContextClass
+ + Discard excess bytes in header buffer if connection is closing
+ + Do not wrap EofException with EofException
+ + ensure explicitly set tmp directory called "work" is not deleted on exit
+ + Ensure mvn clean cleans the build
+ + ensure war is only unpacked if war is newer than "work" directory
+ + fixed classesDirectory param for maven plugin to be configurable
  + fixed HttpGenerator convertion of non UTF-8: JETTY-82
- + added html module from jetty 5 - but deprecated until maintainer found
+ + immutable getParameterMap()
+ + patch to allow Jetty to use JSP2.1 from Glassfish instead of Jasper from
+   Tomcat
+ + refactor HttpChannelEndPoint in preparation for SslEngine
+ + reverse order for destroy event listeners
+ + simplified jetty.xml with new constructor injections
+ + Simplified Servlet Context API
+ + Simplify runtime resolution of JSP library for plugin
+ + Ssl algorithm taken from system property
+ + support <load-on-startup> for SingleThreadModel
+ + support graceful shutdown
+ + Threadpool does not need to be a LifeCycle
+ + Updated javax code from
+   http://svn.apache.org/repos/asf/tomcat/tc6.0.x/trunk/java/javax@417727
 
 jetty-6.0.0beta17 - 01 June 2006
+ + Added clover reports and enough tests to get >50% coverage
  + Added config to disable file memory mapped buffers for windows
  + Added Request.isHandled()
- + Refactored Synchronization of SelectChannelConnector
- + Recovered repository from Codehaus crash
- + ContextHandler.setConnectors replace setHosts
+ + BoundedThreadPool.doStop waits for threads to complete
  + Connector lowResourceMaxIdleTime  implemented.
+ + ContextHandler.setConnectors replace setHosts
  + Default servlet checks for aliases resources
- + Added clover reports and enough tests to get >50% coverage
+ + don't reset headers during forward
  + Fixed IE SSL issue.
- + Implemented runAs on servlets
  + Flush will flush all bytes rather than just some.
+ + Implemented runAs on servlets
  + Protected WEB-INF and META-INF
- + don't reset headers during forward
- + BoundedThreadPool.doStop waits for threads to complete
+ + Recovered repository from Codehaus crash
+ + Refactored Synchronization of SelectChannelConnector
 
 jetty-6.0.0beta16 - 12 May 2006
  + remove a couple of System.err.printlns
  + replace backwards compativle API in UrlEncoded
 
 jetty-6.0.0beta15 - 11 May 2006
- + Added Server attribute org.mortbay.jetty.Request.maxFormContentSize
- + Renamed NotFoundHandler to DefaultHandler
- + Added automatic scan of all WEB-INF/jetty-*.xml files for plugin
  + Added <scanTargets> parameter to allow other locations to scan for plugin
- + Major refactor to simplify Server and handler hierarchy
- + setSendServerVersion method added to Server to control sending of Server:
-   http header
- + removed SelectBlockingChannelConnector (unmaintained)
- + Improved HttpException
- + Moved more resources to resources
- + Added ThrottlingFilter and fixed race in Continuations
+ + Added automatic scan of all WEB-INF/jetty-*.xml files for plugin
+ + Added embedded examples
+ + Added Server attribute org.mortbay.jetty.Request.maxFormContentSize
  + Added taglib resources to 2.1 jsp api jar
- + Reset of timer task clears expiry
- + improved MBeanContainer object removal
+ + Added ThrottlingFilter and fixed race in Continuations
+ + Added --version to start.jar
  + ContextHandler.setContextPath can be called after start.
+ + don't accept partial authority in request line.
+ + enforce 204 and 304 have no content
  + Fixed handling of params after forward
- + Added --version to start.jar
- + Added embedded examples
- + Simplified DefaultServlet static content buffering
- + readded BoundedThreadPool shrinking (and then fixed resulting deadlock)
+ + Improved HttpException
+ + improved MBeanContainer object removal
  + improved MBean names
  + improved support for java5 jconsole
+ + Major refactor to simplify Server and handler hierarchy
+ + Moved more resources to resources
+ + readded BoundedThreadPool shrinking (and then fixed resulting deadlock)
+ + removed SelectBlockingChannelConnector (unmaintained)
+ + Renamed NotFoundHandler to DefaultHandler
+ + Reset of timer task clears expiry
  + Session scavenger threads from threadpool
+ + setSendServerVersion method added to Server to control sending of Server:
+   http header
+ + Simplified DefaultServlet static content buffering
  + Thread names include URI if debug set
- + don't accept partial authority in request line.
- + enforce 204 and 304 have no content
 
 jetty-6.0.0beta14 - 09 April 2006
- + ignore dirs and files that don't exist in plugin scanner
- + added support for stopping jetty using "java -jar start.jar --stop"
  + added configurability for webdefault.xml in maven plugin
- + adding InvokerServlet
+ + Added Jasper 2.1 as jesper (jasper without JCL)
+ + added jetty-util.jar module
+ + Added JSP 2.1 APIs from apache
  + added ProxyServlet
- + stop JDBCUserRealm coercing all credentials to String
+ + added reset to Continuation
+ + added support for stopping jetty using "java -jar start.jar --stop"
+ + adding InvokerServlet
  + Change tmp dir of plugin to work to be in line with jetty convention
- + Modify plugin to select JSP impl at runtime
- + Use start.config to select which JSP impl at runtime based on jdk version
- + Added JSP 2.1 APIs from apache
- + Added Jasper 2.1 as jesper (jasper without JCL)
- + Started readding logging to jesper using jdk logging
+ + fixed forward bug (treated as include)
+ + fixed HttpField iterator
  + fixed priority of port from url over host header
+ + ignore dirs and files that don't exist in plugin scanner
  + implemented request.isUserInRole
- + securityHandler removed if not used.
- + moved test webapps to examples directory
  + improved contentType handling and test harness
- + fixed forward bug (treated as include)
- + fixed HttpField iterator
- + added jetty-util.jar module
- + added reset to Continuation
+ + Modify plugin to select JSP impl at runtime
+ + moved test webapps to examples directory
+ + securityHandler removed if not used.
+ + Started readding logging to jesper using jdk logging
+ + stop JDBCUserRealm coercing all credentials to String
+ + Use start.config to select which JSP impl at runtime based on jdk version
 
 jetty-6.0.0beta12 - 16 March 2006
- + Fixed maven plugin JNDI for redeploys
- + Fixed tld discovery for plugin (search dependencies)
- + Fixed JettyPlus for root contexts
- + Fixed error handling in error page
  + Added JSP2.0 demos to test webapp
- + Upgraded jasper to 5.5.15
  + Added provider support to SslListener
+ + Fixed error handling in error page
+ + Fixed JettyPlus for root contexts
+ + Fixed maven plugin JNDI for redeploys
+ + Fixed tld discovery for plugin (search dependencies)
  + Log ERROR for runtimeExceptions
+ + Upgraded jasper to 5.5.15
 
 jetty-6.0.0beta11 - 14 March 2006
+ + Added HttpURI and improved UTF-8 parsing.
  + added JAAS
- + added webapp-specific JNDI entries
  + added missing Configurations for maven plugin
+ + added patch to use joda-time
+ + added webapp-specific JNDI entries
+ + fixed ; decoding in URIs
  + fixed FORM authentication
  + moved dtd and xsd to standard javax location
- + added patch to use joda-time
- + refactored session ID management
  + refactored configuration files and start()
- + fixed ; decoding in URIs
- + Added HttpURI and improved UTF-8 parsing.
+ + refactored session ID management
  + refactored writers and improved UTF-8 generation.
 
 jetty-6.0.0beta10 - 25 February 2006
+ + added getLocalPort() to connector
  + Added support for java:comp/env
  + Added support for pluggable transaction manager
- + Forward masks include attributes and vice versa
- + Fixed default servlet handling of includes
  + Additional accessors for request logging
- + added getLocalPort() to connector
  + Fixed content-type for range requests
- + Fix for sf1435795 30sec delay from c taylor
+ + Fixed default servlet handling of includes
  + Fix for myfaces and include with close
- + Fix sf1431936 don't chunk the chunk
+ + Fix for sf1435795 30sec delay from c taylor
  + Fix http://jira.codehaus.org/browse/JETTY-6. hi byte reader
+ + Fix sf1431936 don't chunk the chunk
+ + Forward masks include attributes and vice versa
  + Updates javax to MR2 release
 
 jetty-6.0.0beta9 - 09 February 2006
- + PathMap for direct context mapping.
- + Refactored chat demo and upgraded prototype.js
+ + Added CGI servlet.
+ + Added request log.
+ + Added TLD tag listener handling.
  + Continuation cleanup
- + Fixed unraw decoding of query string
  + Fixed dispatch of wrapped requests.
  + Fixed double flush of short content.
- + Added request log.
- + Added CGI servlet.
+ + fixed setLocale bug sf1426940
+ + Fixed unraw decoding of query string
  + Force a tempdir to be set.
  + Force jasper scratch dir.
- + fixed setLocale bug sf1426940
- + Added TLD tag listener handling.
+ + PathMap for direct context mapping.
+ + Refactored chat demo and upgraded prototype.js
 
 jetty-6.0.0beta8 - 24 January 2006
- + fixed dispatch of new session problem. sf:1407090
- + reinstated rfc2616 test harness
- + Handle pipeline requests without hangs
- + Removed queue from thread pool.
- + improved caching of content types
+ + conveniance addHandler removeHandler methods
  + fixed bug in overloaded write method on HttpConnection (reported against
    Tapestry4.0)
+ + fixed dispatch of new session problem. sf:1407090
+ + Handle pipeline requests without hangs
  + hid org.apache.commons.logging and org.slf4j packages from webapp
- + maven-jetty6-plugin stopped transitive inclusion of log4j and
-   commons-logging from commons-el for jasper
- + patch to remove spurious ; in HttpFields
  + improve buffer return mechanism.
- + conveniance addHandler removeHandler methods
+ + improved caching of content types
  + maven-jetty6-plugin: ensure compile is done before invoking jetty
  + maven-jetty6-plugin: support all types of artifact dependencies
+ + maven-jetty6-plugin stopped transitive inclusion of log4j and
+   commons-logging from commons-el for jasper
+ + patch to remove spurious ; in HttpFields
+ + reinstated rfc2616 test harness
+ + Removed queue from thread pool.
 
 jetty-6.0.0Beta7
- + Fixed infinite loop with chunk handling
  + Faster header name lookup
- + removed singleton Container
- + reduced info verbosity
- + null dispatch attributes not in names
+ + Fixed infinite loop with chunk handling
  + maven-jetty6-plugin added tmpDirectory property
  + maven-jetty6-plugin stopped throwing an error if there is no target/classes
    directory
+ + null dispatch attributes not in names
+ + reduced info verbosity
+ + removed singleton Container
 
 jetty-6.0.0Beta6
  + Fixed issue with blocking reads
@@ -3738,45 +5719,45 @@ jetty-6.0.0Beta6
  + optimizations
 
 jetty-6.0.0Beta5
- + Moved to SVN
- + Fixed writer char[] creations
  + Added management module for mbeans
+ + Fixed writer char[] creations
+ + Moved to SVN
 
 jetty-6.0.0Beta4
- + System property support in plugin
  + CVE-2006-2758 Fixed JSP visibility security issue.
  + Improved jetty-web.xml access to org.mortbay classes.
  + Jasper 5.5.12
+ + System property support in plugin
 
 jetty-6.0.0Beta3
+ + Fixed classloader issue with server classes
  + Fixed error in block read
  + Named dispatch.
- + Fixed classloader issue with server classes
 
 jetty-6.0.0Beta2
- + merged util jar back into jetty jar
- + Simpler continuation API
+ + Improved buffer return
+ + Improved reuse of HttpField values and cookies.
  + loosely coupled with JSP servlet
  + loosely coupled with SLF4J
- + Improved reuse of HttpField values and cookies.
- + Improved buffer return
+ + merged util jar back into jetty jar
+ + Simpler continuation API
 
 jetty-6.0.0Beta1
- + Servlet 2.5 API
- + SSL connector
+ + Error pages
+ + Implemented all listeners
  + maven2 plugin
- + shutdown hook
+ + Multiple select sets
  + refactored start/stop
- + Implemented all listeners
- + Error pages
+ + Servlet 2.5 API
+ + shutdown hook
+ + SSL connector
  + Virtual hosts
- + Multiple select sets
 
 jetty-6.0.0Beta0
- + Maven 2 build
  + Dispatcher parameters
- + UTF-8 encoding for URLs
  + Fixed blocking read
+ + Maven 2 build
+ + UTF-8 encoding for URLs
 
 jetty-6.0.0APLPA3
  + Added demo for Continuations
@@ -3792,41 +5773,41 @@ jetty-6.0.0ALPHA1
  + web.xml handling
 
 jetty-6.0.0ALPHA0
- + Totally rearchitected and rebuilt, so 10 years of cruft could be removed!
+ + file may be sent as sent is a single operation.
  + Improved "dependancy injection" and "inversion of control" design of
    components
  + Improved "interceptor" design of handlers
- + Smart split buffer design allows large buffers to only be allocated to
-   active connections. The resulting memory savings allow very large buffers to
-   be used, which increases the chance of efficient asynchronous flushing and
-   of avoiding chunking.
+ + Missing Request Dispatchers
+ + Missing Security
+ + Missing war support
+ + Missing web.xml based configuration
  + Optional use of NIO Buffering so that efficient direct buffers and memory
    mapped files can be used.
- + Optional use of NIO non-blocking scheduling so that threads are not
-   allocated per connection.
  + Optional use of NIO gather writes, so that for example a HTTP header and a
    memory mapped
- + file may be sent as sent is a single operation.
- + Missing Security
- + Missing Request Dispatchers
- + Missing web.xml based configuration
- + Missing war support
+ + Optional use of NIO non-blocking scheduling so that threads are not
+   allocated per connection.
+ + Smart split buffer design allows large buffers to only be allocated to
+   active connections. The resulting memory savings allow very large buffers to
+   be used, which increases the chance of efficient asynchronous flushing and
+   of avoiding chunking.
+ + Totally rearchitected and rebuilt, so 10 years of cruft could be removed!
 
 jetty-5.1.11RC0 - 05 April 2006
- + stop JDBCUserRealm forcing all credentials to be String
- + force close with shutdownOutput for win32
- + NPE protection if desirable client certificates
  + Added provider support to SslListener
- + logging improvements for servlet and runtime exceptions
  + Fixed AJP handling of ;jsessionid.
+ + force close with shutdownOutput for win32
  + improved contentType param handling
+ + logging improvements for servlet and runtime exceptions
+ + NPE protection if desirable client certificates
+ + stop JDBCUserRealm forcing all credentials to be String
 
 jetty-5.1.10 - 05 January 2006
  + Fixed path aliasing with // on windows.
- + Fix for AJP13 with multiple headers
  + Fix for AJP13 with encoded path
- + Remove null dispatch attributes from getAttributeNames
+ + Fix for AJP13 with multiple headers
  + Put POST content default back to iso_8859_1. GET is UTF-8 still
+ + Remove null dispatch attributes from getAttributeNames
 
 jetty-4.2.25 - 04 January 2006
  + Fixed aliasing of // for win32
@@ -3843,14 +5824,14 @@ jetty-5.1.8 - 07 December 2005
 jetty-5.1.7 - 07 December 2005
 
 jetty-5.1.7rc0 - 06 December 2005
- + improved server stats
+ + better support for URI character encodings
  + char encoding for MultiPartRequest
  + fixed merging of POST params in dispatch query string.
- + protect from NPE in dispatcher getValues
- + Updated to 2.6.2 xerces
+ + improved server stats
  + JSP file servlet mappings copy JspServlet init params.
  + Prefix servlet context logs with org.mortbay.jetty.context
- + better support for URI character encodings
+ + protect from NPE in dispatcher getValues
+ + Updated to 2.6.2 xerces
  + use commons logging jar instead of api jar.
 
 jetty-5.1.6 - 18 November 2005
@@ -3858,53 +5839,53 @@ jetty-5.1.6 - 18 November 2005
  + Improved jetty-web.xml access to org.mortbay classes.
 
 jetty-5.1.5 - 10 November 2005
+ + Improved mapping of JSP files.
  + Improved shutdown hook
  + Improved URL Decoding
- + Improved mapping of JSP files.
 
 jetty-5.1.5rc2 - 07 October 2005
- + Reverted dispatcher params to RI rather than spec behaviour.
  + ProxyHandler can handle chained proxies
- + unsynchronized ContextLoader
- + ReFixed merge of Dispatcher params
  + public ServerMBean constructor
- + UTF-8 encoding for URLs
+ + ReFixed merge of Dispatcher params
  + Response.setLocale will set locale even if getWriter called.
+ + Reverted dispatcher params to RI rather than spec behaviour.
+ + unsynchronized ContextLoader
+ + UTF-8 encoding for URLs
 
 jetty-5.1.5rc1 - 23 August 2005
- + upgraded to commons logging 1.0.4
- + Release commons logging factories when stopping context.
+ + Encoded full path in ResourceHandler directory listing
+ + Fixed 100-continues with chunking and early commit
  + Fixed illegal state with chunks and 100 continue - Tony Seebregts
- + Fixed PKCS12Import input string method
  + Fixed merge of Dispatcher parameters
- + Encoded full path in ResourceHandler directory listing
+ + Fixed PKCS12Import input string method
  + handle extra params after charset in header
- + Fixed 100-continues with chunking and early commit
+ + Release commons logging factories when stopping context.
+ + upgraded to commons logging 1.0.4
 
 jetty-5.1.5rc0 - 16 August 2005
- + Fixed component remove memory leak for stop/start cycles
- + Facade over commons LogFactory so that discovery may be avoided.
  + Applied ciphersuite patch from tonyj
  + Authenticators use servlet sendError
  + CGI sets SCRIPT_FILENAME
+ + Expect continues only sent if input is read.
+ + Facade over commons LogFactory so that discovery may be avoided.
+ + Fixed component remove memory leak for stop/start cycles
  + HttpTunnel timeout
  + NPE protection for double stop in ThreadedServer
- + Expect continues only sent if input is read.
 
 jetty-5.1.4 - 05 June 2005
+ + Change JAAS impl to be more flexible on finding roles
  + Fixed FTP close issue.
- + setup MX4J with JDK1.5 in start.config
- + set classloader during webapp doStop
- + NPE protection in ThreadedServer
  + ModelMBean handles null signatures
- + Change JAAS impl to be more flexible on finding roles
+ + NPE protection in ThreadedServer
+ + set classloader during webapp doStop
+ + setup MX4J with JDK1.5 in start.config
 
 jetty-5.1.4rc0 - 19 April 2005
- + ServletHttpContext correctly calls super.doStop.
- + HttpServer delegates component handling to Container.
  + Allow ServletHandler in normal HttpContext again.
- + Stop start.jar putting current directory on classpath.
+ + HttpServer delegates component handling to Container.
  + More protection from null classloaders.
+ + ServletHttpContext correctly calls super.doStop.
+ + Stop start.jar putting current directory on classpath.
  + Turn off web.xml validation for JBoss.
 
 jetty-5.1.3 - 07 April 2005
@@ -3913,114 +5894,114 @@ jetty-5.1.3 - 07 April 2005
 jetty-4.2.24 - 07 April 2005
 
 jetty-5.1.3rc4 - 31 March 2005
+ + Allow XmlConfiguration to start with no object.
+ + make java:comp/env immutable for webapps as per J2EE spec
  + Moved servlet request wrapping to enterContextScope for geronimo security
  + refixed / mapping for filters
- + Allow XmlConfiguration to start with no object.
- + updated to mx4j 3.0.1
  + rework InitialContextFactory to use static 'default' namespace
- + make java:comp/env immutable for webapps as per J2EE spec
+ + updated to mx4j 3.0.1
 
 jetty-5.1.3rc3 - 20 March 2005
+ + fixed "No getter or setter found" mbean errors
  + removed accidental enablement of DEBUG for JettyPlus jndi in
    log4j.properties
- + fixed "No getter or setter found" mbean errors
 
 jetty-5.1.3rc2 - 16 March 2005
- + Updated JSR154Filter for ERROR dispatch
  + Fixed context to _context refactory error
+ + Updated JSR154Filter for ERROR dispatch
 
 jetty-5.1.3rc1 - 13 March 2005
- + Fixed typo in context-param handling.
- + update to demo site look and feel.
  + Fixed principal naming in FormAuthenticator
+ + Fixed typo in context-param handling.
  + JettyPlus updated to JOTM 2.0.5, XAPool 1.4.2
+ + update to demo site look and feel.
 
 jetty-4.2.24rc1
  + Fixed principal naming in FormAuthenticator
 
 jetty-5.1.3rc0 - 08 March 2005
- + Flush filter chain caches on servlet/filter change
- + Fixed rollover filename format bug
- + Fixed JSR154 error dispatch with explicit pass of type.
- + Allow system and server classes to be configured for context loader.
- + IOException if EOF read during chunk.
- + Fixed HTAccess crypt salt handling.
+ + Added logCookie and logLatency support to NCSARequestLog
+ + Added new JAAS callback to allow extra login form fields in authentication
  + Added simple xpath support to XmlParser
- + Added TagLibConfiguration to search for listeners in TLDs.
  + Added SslListener for 1.4 JSSE API.
+ + Added TagLibConfiguration to search for listeners in TLDs.
+ + Allow system and server classes to be configured for context loader.
+ + Fixed HTAccess crypt salt handling.
+ + Fixed JSR154 error dispatch with explicit pass of type.
  + Fixed moderate load preventing ThreadPool shrinking.
- + Added logCookie and logLatency support to NCSARequestLog
- + Added new JAAS callback to allow extra login form fields in authentication
+ + Fixed rollover filename format bug
+ + Flush filter chain caches on servlet/filter change
+ + IOException if EOF read during chunk.
 
 jetty-4.2.24rc0 - 08 March 2005
- + Back ported Jetty 5 ThreadedServer and ThreadPool
  + Added logCookie and logLatency support to NCSARequestLog
+ + Back ported Jetty 5 ThreadedServer and ThreadPool
 
 jetty-5.1.2 - 18 January 2005
  + Added id and ref support to XmlConfiguration
+ + Apply patch #1103953
  + Cleaned up AbstractSessionManager synchronization.
  + Fixed potential concurrent login problem with JAAS
- + Apply patch #1103953
 
 jetty-4.2.23 - 16 January 2005
  + Cleaned up AbstractSessionManager synchronization.
  + Fixed potential concurrent login problem with JAAS
 
 jetty-5.1.2pre0 - 22 December 2004
+ + Added global invalidation to AbstractSessionManager
  + Fixed case of Cookie parameters
- + Support Secure and HttpOnly in session cookies
+ + Fixed suffix filters
  + Modified useRequestedID handling to only use IDs from other contexts
- + Added global invalidation to AbstractSessionManager
+ + Support Secure and HttpOnly in session cookies
  + UnavailableException handling from handle
- + Fixed suffix filters
 
 jetty-4.2.23RC0 - 17 December 2004
- + LineInput handles readers with small internal buffer
  + Added LogStream to capture stderr and stdout to logging
- + Support Secure and HttpOnly in session cookies
  + Build unsealed jars
+ + LineInput handles readers with small internal buffer
+ + Support Secure and HttpOnly in session cookies
 
 jetty-5.1.1 - 01 December 2004
 
 jetty-5.1.1RC1
- + Some minor findbugs code cleanups
- + Made more WebApplicationHandle configuration methods public.
- + Fixed ordering of filters with multiple interleaved mappings.
  + Allow double // within URIs
  + Applied patch for MD5 hashed credentials for MD5
+ + Fixed ordering of filters with multiple interleaved mappings.
+ + Made more WebApplicationHandle configuration methods public.
+ + Some minor findbugs code cleanups
 
 jetty-5.1.1RC0 - 17 November 2004
- + fix for adding recognized EventListeners
- + fix commons logging imports to IbmJsseListener
  + added new contributed shell start/stop script
  + excluded ErrorPageHandler from standard build in extra/jdk1.2 build
+ + fix commons logging imports to IbmJsseListener
+ + fix for adding recognized EventListeners
 
 jetty-5.1.0 - 14 November 2004
 
 jetty-5.1.RC1 - 24 October 2004
  + Allow JSSE listener to be just confidential or just integral.
- + Fixed NPE for null contenttype
- + improved clean targets
- + when committed setHeader is a noop rather than IllegalStateException
- + Partially flush writers on every write so content length can be detected.
+ + Allow multiple accepting threads
  + Build unsealed jars
  + default / mapping does not apply to Filters
+ + Fixed NPE for null contenttype
+ + improved clean targets
  + many minor cleanups suggested from figbug utility
- + Allow multiple accepting threads
+ + Partially flush writers on every write so content length can be detected.
+ + when committed setHeader is a noop rather than IllegalStateException
 
 jetty-5.1.RC0 - 11 October 2004
- + Fixed many minor issues from J2EE 1.4 TCK testing See sf.net bugs 1031520 -
-   1032205
- + Refactored, simplified and optimized HttpOutputStream
- + LineInput handles readers with small internal buffer
- + Added LogStream to capture stderr and stdout to logging
  + Added filter chain cache
  + Added JSR77 servlet statistic support
- + Refactored webapp context configurations
  + Added LifeCycle events and generic container.
- + Upgraded to ant-1.6 for jasper
+ + Added LogStream to capture stderr and stdout to logging
  + Fixed HTAccessHandler
+ + Fixed many minor issues from J2EE 1.4 TCK testing See sf.net bugs 1031520 -
+   1032205
  + JBoss 4.0.0 support
+ + LineInput handles readers with small internal buffer
+ + Refactored, simplified and optimized HttpOutputStream
+ + Refactored webapp context configurations
+ + Upgraded to ant-1.6 for jasper
 
 jetty-5.0.0 - 10 September 2004
 
@@ -4030,648 +6011,648 @@ jetty-5.0.RC4 - 05 September 2004
    security-domain from jboss-web.xml
 
 jetty-5.0.RC3 - 28 August 2004
- + DIGEST auth handles qop, stale and maxNonceAge.
- + Less verbose warning for non validating xml parser.
- + fixed jaas logout for jetty-jboss
- + fixed deployment of ejb-link elements in web.xml with jboss
- + Update to jasper 5.0.27
  + Added parameters for acceptQueueSize and lowResources level.
+ + Always say close for HTTP/1.0 non keep alive.
  + Changed default URI encoding to UTF-8
+ + DIGEST auth handles qop, stale and maxNonceAge.
+ + fixed deployment of ejb-link elements in web.xml with jboss
+ + fixed jaas logout for jetty-jboss
  + Fixes to work with java 1.5
- + JettyPlus upgrade to XAPool 1.3.3. and HSQLDB 1.7.2
  + JettyPlus addition of pluggable DataSources
- + Always say close for HTTP/1.0 non keep alive.
+ + JettyPlus upgrade to XAPool 1.3.3. and HSQLDB 1.7.2
+ + Less verbose warning for non validating xml parser.
+ + Update to jasper 5.0.27
 
 jetty-4.2.22
- + fixed jaas logout for jetty-jboss integration
- + fixed deployment of ejb-link elements in web.xml for jboss
  + Added parameters for acceptQueueSize and lowResources level.
+ + fixed deployment of ejb-link elements in web.xml for jboss
+ + fixed jaas logout for jetty-jboss integration
 
 jetty-5.0.RC2 - 02 July 2004
- + Fixed DIGEST challenge delimiters
- + HTAccess calls UnixCrypt correctly
- + integrated jetty-jboss with jboss-3.2.4
- + Error dispatchers are always GET requests.
- + OPTIONS works for all URLs on default servlet
  + add JMX support for JettyPlus
  + add listing of java:comp/env for webapp with JMX
- + make choice of override of JNDI ENC entries: config.xml or web.xml
  + Default servlet may use only pathInfo for resource
- + Fixed session leak in j2ee
+ + Error dispatchers are always GET requests.
+ + Fixed DIGEST challenge delimiters
+ + Fixed JAAS logout
  + Fixed no-role security constraint combination.
+ + Fixed session leak in j2ee
  + Fix to use runas roles during servlet init and destroy
- + Fixed JAAS logout
+ + HTAccess calls UnixCrypt correctly
  + HttpContext sendError for authentication errors
+ + integrated jetty-jboss with jboss-3.2.4
+ + make choice of override of JNDI ENC entries: config.xml or web.xml
+ + OPTIONS works for all URLs on default servlet
 
 jetty-4.2.21 - 02 July 2004
- + integrated jetty-jboss with jboss-3.2.4
  + add JMX support for JettyPlus
  + add listing of java:comp/env for webapp with JMX
- + make choice of override of JNDI ENC entries: config.xml or web.xml
  + Fixed JAAS logout
+ + integrated jetty-jboss with jboss-3.2.4
+ + make choice of override of JNDI ENC entries: config.xml or web.xml
 
 jetty-5.0.RC1 - 24 May 2004
- + Changed to apache 2.0 license
  + added extra/etc/start-plus.config to set up main.class for jettyplus
- + maxFormContentLength may be unlimited with <0 value
+ + Changed to apache 2.0 license
  + Fixed HTTP tunnel timeout setting.
- + Improved handling of exception from servlet init.
  + FORM auth redirects to context on a re-auth
  + Handle multiple virutal hosts from JBoss 3.2.4RC2
+ + Improved handling of exception from servlet init.
+ + maxFormContentLength may be unlimited with <0 value
 
 jetty-4.2.20 - 22 May 2004
- + maxFormContentLength may be unlimited with <0 value
  + Fixed HTTP tunnel timeout setting.
- + Improved handling of exception from servlet init.
  + FORM auth redirects to context on a re-auth
+ + Improved handling of exception from servlet init.
+ + maxFormContentLength may be unlimited with <0 value
 
 jetty-5.0.0RC0 - 07 April 2004
- + Updated JettyPlus to JOTM 1.4.3 (carol-1.5.2, xapool-1.3.1)
- + ServletContext attributes wrap HttpContext attributes.
+ + Changed dist naming convention to lowercase
+ + Default servlet respectes servlet path
  + Factored out XML based config from WebApplicationContext
- + Improved RequestLog performance
+ + Fixed Default servlet for non empty servlet paths
+ + Fixed DOS problem
  + Fixed j2se 1.3 problem with HttpFields
- + Default servlet respectes servlet path
  + Fixed setCharacterEncoding for parameters.
- + Fixed DOS problem
- + Worked around bad jboss URL handler in XMLParser
  + Forced close of connections over stop/start
+ + Improved RequestLog performance
  + ProxiedFor field support added to NCSARequestLog
- + Fixed Default servlet for non empty servlet paths
- + Updated mx4j to V2
+ + ServletContext attributes wrap HttpContext attributes.
  + Updated jasper to 5.0.19
- + Changed dist naming convention to lowercase
+ + Updated JettyPlus to JOTM 1.4.3 (carol-1.5.2, xapool-1.3.1)
+ + Updated mx4j to V2
+ + Worked around bad jboss URL handler in XMLParser
 
 jetty-4.2.20RC0 - 07 April 2004
- + Worked around bad jboss URL handler in XMLParser
+ + Changed dist naming convention to lowercase
+ + Fixed Default servlet for non empty servlet paths
  + Forced close of connections over stop/start
  + HttpFields protected headers
  + ProxiedFor field support added to NCSARequestLog
- + Fixed Default servlet for non empty servlet paths
- + Changed dist naming convention to lowercase
+ + Worked around bad jboss URL handler in XMLParser
 
 jetty-4.2.19 - 19 March 2004
  + Fixed DOS attack problem
 
 jetty-5.0.beta2 - 12 February 2004
+ + Added experimental NIO listeners again.
+ + Added log4j context repository to jettyplus
  + Added skeleton JMX MBean for jetty plus
+ + FileResource better handles non sun JVM
+ + Fixed busy loop in threadpool run
+ + fixed filter dispatch configuration.
  + Fixed HEAD with empty chunk bug.
  + Fixed jetty.home/work handling
- + Fixed setDate thread safety
+ + fixed lazy authentication with FORMs
  + Fixed SessionManager init
+ + Fixed setDate thread safety
  + Improved low thread handling
- + FileResource better handles non sun JVM
  + Monitor closes socket before exit
- + Updated to Japser 5.0.16
- + RequestDispatcher uses request encoding for query params
- + Fixed busy loop in threadpool run
- + Reorganized ServletHolder init
- + Added log4j context repository to jettyplus
  + NPE guard for no-listener junit deployment
- + Added experimental NIO listeners again.
- + fixed filter dispatch configuration.
- + fixed lazy authentication with FORMs
+ + Reorganized ServletHolder init
+ + RequestDispatcher uses request encoding for query params
+ + Updated to Japser 5.0.16
 
 jetty-4.2.18 - 01 March 2004
  + Added log4j context repository to jettyplus
- + NPE guard for no-listener junit deployment
- + Improved log performance
+ + Default servlet respectes servlet path
  + Fixed j2se 1.3 problem with HttpFields
+ + Improved log performance
+ + NPE guard for no-listener junit deployment
  + Suppress some more IOExceptions
- + Default servlet respectes servlet path
 
 jetty-4.2.17 - 01 February 2004
  + Fixed busy loop in threadpool run
  + Reorganized ServletHolder init
 
 jetty-4.2.16 - 30 January 2004
- + Fixed setDate multi-cpu race
- + Improved low thread handling
  + FileResource better handles non sun JVM
  + Fixed HttpTunnel for JDK 1.2
+ + Fixed setDate multi-cpu race
+ + Improved low thread handling
  + Monitor closes socket before exit
  + RequestDispatcher uses request encoding for query params
  + Update jasper to 4.1.29
 
 jetty-5.0.beta1 - 24 December 2003
- + SecurityConstraints not reset by stop() on custom context
- + Fixed UnixCrypt handling in HTAccessHandler
  + Added patch for JBoss realm single sign on
- + Reorganized FAQ
  + Env variables for CGI
+ + Fixed UnixCrypt handling in HTAccessHandler
  + Removed support for old JBoss clustering
+ + Reorganized FAQ
+ + SecurityConstraints not reset by stop() on custom context
 
 jetty-4.2.15 - 24 December 2003
- + SecurityConstraints not reset by stop() on custom context
- + Fixed UnixCrypt handling in HTAccessHandler
  + Added patch for JBoss realm single sign on
  + Environment variables for CGI
+ + Fixed UnixCrypt handling in HTAccessHandler
  + Removed support for old JBoss clustering
+ + SecurityConstraints not reset by stop() on custom context
 
 jetty-5.0.beta0 - 22 November 2003
- + Removed support for HTTP trailers
- + PathMap uses own Map.Entry impl for IBM JVMs
- + Use ${jetty.home}/work or WEB-INF/work for temp directories if present
- + Protect ThreadPool.run() from interrupted exceptions
- + Added org.mortbay.http.ErrorHandler for error pages.
- + Fixed init race in HttpFields cache
- + Allow per listener handlers
  + Added MsieSslHandler to handle browsers that don't grok persistent SSL (msie
    5)
- + Respect content length when decoding form content.
- + JBoss integration uses writer rather than stream for XML config handling
+ + Added org.mortbay.http.ErrorHandler for error pages.
+ + Allow per listener handlers
  + Expire pages that contain set-cookie as per RFC2109 recommendation
- + Updated jasper to 5.0.14beta
+ + Fixed init race in HttpFields cache
+ + JBoss integration uses writer rather than stream for XML config handling
+ + PathMap uses own Map.Entry impl for IBM JVMs
+ + Protect ThreadPool.run() from interrupted exceptions
+ + Removed support for HTTP trailers
  + Removed the CMR/CMP distributed session implementation
+ + Respect content length when decoding form content.
+ + Updated jasper to 5.0.14beta
+ + Use ${jetty.home}/work or WEB-INF/work for temp directories if present
 
 jetty-4.2.15rc0 - 22 November 2003
- + PathMap uses own Map.Entry impl for IBM JVMs
- + Race in HttpFields cache
- + Use ${jetty.home}/work or WEB-INF/work for temp directories if present
- + Protect ThreadPool.run() from interrupted exceptions
  + Added org.mortbay.http.ErrorHandler for error pages.
  + JsseListener checks UserAgent for browsers that can't grok persistent SSL
    (msie5)
+ + PathMap uses own Map.Entry impl for IBM JVMs
+ + Protect ThreadPool.run() from interrupted exceptions
+ + Race in HttpFields cache
  + Removed the CMR/CMP distributed session implementation
+ + Use ${jetty.home}/work or WEB-INF/work for temp directories if present
 
 jetty-4.2.14 - 04 November 2003
- + respect content length when decoding form content.
- + JBoss integration uses writer rather than stream for XML config handling
- + Fixed NPE in SSO
  + Expire pages that contain set-cookie as per RFC2109 recommendation
+ + Fixed NPE in SSO
+ + JBoss integration uses writer rather than stream for XML config handling
+ + respect content length when decoding form content.
 
 jetty-5.0.alpha3 - 19 October 2003
- + Reworked Dispatcher to better support cross context sessions.
- + Use File.toURI().toURL() when jdk 1.2 alternative is available.
- + Priority added to ThreadPool
- + replaced win32 service with http://wrapper.tanukisoftware.org
+ + Allow customization of HttpConnections
+ + Failed requests excluded from duration stats
  + FileClassPath derived from walk of classloader hierarchy.
- + Implemented security constraint combinations
- + Set TransactionManager on JettyPlus datasources and pools
  + Fixed null pointer if no sevices configured for JettyPlus
- + Updated jasper and examples to 5.0.12
+ + Implemented security constraint combinations
  + Lazy authentication if no auth constraint.
+ + Priority added to ThreadPool
+ + replaced win32 service with http://wrapper.tanukisoftware.org
  + Restore servlet handler after dispatch
- + Allow customization of HttpConnections
- + Failed requests excluded from duration stats
+ + Reworked Dispatcher to better support cross context sessions.
+ + Set TransactionManager on JettyPlus datasources and pools
+ + Updated jasper and examples to 5.0.12
+ + Use File.toURI().toURL() when jdk 1.2 alternative is available.
 
 jetty-4.2.14RC1 - 19 October 2003
- + Reworked Dispatcher to better support cross context sessions.
  + Added UserRealm.logout and arrange for form auth
  + Allow customization of HttpConnections
  + Failed requests excluded from
+ + Reworked Dispatcher to better support cross context sessions.
 
 jetty-4.2.14RC0 - 07 October 2003
+ + Build fileclasspath from a walk of the classloaders
+ + cookie timestamps are in GMT
  + Correctly setup context classloader in cross context dispatch.
- + Put a semi busy loop into proxy tunnels for IE problems
+ + Fixed comments with embedded double dashes on jettyplus.xml file
  + Fixed handling of error pages for IO and Servlet exceptions
- + updated extra/j2ee to jboss 3.2.1+
- + Use File.toURI().toURL() when jdk 1.2 alternative is available.
- + cookie timestamps are in GMT
+ + Fixed null pointer if no sevices configured for JettyPlus
  + Priority on ThreadedServer
+ + Put a semi busy loop into proxy tunnels for IE problems
  + replaced win32 service with http://wrapper.tanukisoftware.org
- + Build fileclasspath from a walk of the classloaders
  + Set TransactionManager on JettyPlus datasources and pools
- + Fixed null pointer if no sevices configured for JettyPlus
- + Fixed comments with embedded double dashes on jettyplus.xml file
+ + updated extra/j2ee to jboss 3.2.1+
+ + Use File.toURI().toURL() when jdk 1.2 alternative is available.
 
 jetty-5.0.alpha2 - 19 September 2003
- + Use commons logging.
- + Use log4j if extra is present.
- + Improved JMX start.
- + Update jakarta examples
  + Correctly setup context classloader in cross context dispatch.
- + Turn off validation without non-xerces errors
+ + Fixed error page handling of IO and Servlet exceptions.
+ + Implemented ServletRequestListeners as optional filter.
+ + Improved JMX start.
  + minor doco updates.
+ + Moved error page mechanism to be webapp only.
  + moved mailing lists to sourceforge.
- + Put a semi busy loop into proxy tunnels for IE problems
  + MultipartRequest supports multi value headers.
+ + Put a semi busy loop into proxy tunnels for IE problems
+ + Turn off validation without non-xerces errors
+ + Update jakarta examples
+ + Use commons logging.
+ + Use log4j if extra is present.
  + XML entity resolution uses URLs not Resources
- + Implemented ServletRequestListeners as optional filter.
- + Moved error page mechanism to be webapp only.
- + Fixed error page handling of IO and Servlet exceptions.
 
 jetty-5.0.alpha1 - 12 August 2003
- + Switched to mx4j
- + Improve combinations of Security Constraints
  + Implemented locale encoding mapping.
+ + Improve combinations of Security Constraints
+ + Server javadoc from war
+ + Switched to mx4j
  + Synced with 4.2.12
  + Updated to Jasper 5.0.7
- + Server javadoc from war
 
 jetty-5.0.alpha0 - 16 July 2003
  + Compiled against 2.4 servlet spec.
+ + Implemented Dispatcher forward attributes.
+ + Implemented filter-mapping <dispatcher> element
  + Implemented remote/local addr/port methods
+ + Implemented setCharaterEncoding
  + Updated authentication so that a normal Principal is used.
  + updated to jasper 5.0.3
- + Implemented setCharaterEncoding
- + Implemented filter-mapping <dispatcher> element
- + Implemented Dispatcher forward attributes.
 
 jetty-4.2.12 - 12 August 2003
- + Restore max inactive interval for session manager
- + Removed protection of org.mortbay.http attributes
- + Fixed parameter ordering for a forward request.
- + Fixed up HTAccessHandler
- + Improved error messages from ProxyHandler
  + Added missing S to some OPTIONS strings
  + Added open method to threaded server.
- + FORMAuthenticator does 403 with empty error page.
  + Fixed MIME types for chemicals
+ + Fixed parameter ordering for a forward request.
+ + Fixed up HTAccessHandler
+ + FORMAuthenticator does 403 with empty error page.
+ + Improved error messages from ProxyHandler
  + Padding for IE in RootNotFoundHandler
+ + Removed protection of org.mortbay.http attributes
+ + Restore max inactive interval for session manager
 
 jetty-4.2.11 - 12 July 2003
- + Fixed race in servlet initialization code.
+ + Branched for Jetty 5 development.
  + Cookie params all in lower case.
- + Simplified AJP13 connection handling.
+ + Fixed race in servlet initialization code.
  + Prevent AJP13 from reordering query.
+ + Simplified AJP13 connection handling.
  + Support separate Monitor class for start
- + Branched for Jetty 5 development.
 
 jetty-4.2.10 - 07 July 2003
  + Updates to JettyPlus documentation
  + Updates to Jetty tutorial for start.jar, jmx etc
 
 jetty-4.2.10pre2 - 04 July 2003
- + Improvement to JettyPlus config of datasources and connection pools
  + Addition of mail service for JettyPlus
- + Move to Service-based architecture for JettyPlus features
- + Re-implementation of JNDI
- + Many improvements in JettyPlus java:comp handling
  + Allow multiple security-role-ref elements per servlet.
- + Handle Proxy-Connection better
  + Cleaned up alias handling.
  + Confidential redirection includes query
- + handle multiple security role references
  + Fixed cookie handling for old cookies and safari
+ + handle multiple security role references
+ + Handle Proxy-Connection better
+ + Improvement to JettyPlus config of datasources and connection pools
+ + Many improvements in JettyPlus java:comp handling
+ + Move to Service-based architecture for JettyPlus features
+ + Re-implementation of JNDI
  + Restricted ports in ProxyHandler.
- + URI always encodes %
  + Session statistics
+ + URI always encodes %
  + XmlConfiguration can get/set fields.
 
 jetty-4.2.10pre1 - 02 June 2003
- + Fixed JSP code visibility problem introduced in Jetty-4.2.10pre0
- + Added stop.jar
  + Added SSO implementation for FORM authentication.
- + WebApplicationContext does not reassign defaults descriptor value.
+ + Added stop.jar
+ + Deprecated forced chunking.
  + Fixed AJP13 protocol so that request/response header enums are correct.
  + Fixed form auth success redirect after retry, introduced in 4.2.9rc1
- + Trace support is now optional (in AbstractHttpHandler).
- + Deprecated forced chunking.
+ + Fixed JSP code visibility problem introduced in Jetty-4.2.10pre0
+ + Fixed problem with shared session for inter context dispatching.
  + Form authentication remembers URL over 403
  + ProxyHandler has improved test for request content
  + Removed support of org.mortbay.http.User role.
- + Fixed problem with shared session for inter context dispatching.
+ + Trace support is now optional (in AbstractHttpHandler).
+ + WebApplicationContext does not reassign defaults descriptor value.
 
 jetty-4.2.10pre0 - 05 May 2003
- + Moved Log4JLogSink into JettyPlus
  + Added ability to override jetty startup class by using -Djetty.server on
    runline
- + Incorporate JettyPlus jotm etc into build.
- + Massive reorg of the CVS tree.
+ + Allow params in form auth URLs
+ + Allow query params in error page URL.
+ + Apply the append flag of RolloverFileOutputStream constructor.
+ + Fixed CRLF bug in MultiPartRequest
+ + Fixed table refs in JDBCUserRealm.
+ + FORM Authentication is serializable for session distribution.
+ + getAuthType maps the HttpServletRequest final strings.
+ + getAuthType returns CLIENT_CERT instead of CLIENT-CERT.
  + Incorporate jetty extra and plus into build
+ + Incorporate JettyPlus jotm etc into build.
  + Integrate with JAAS
- + Apply the append flag of RolloverFileOutputStream constructor.
- + RolloverFileOutputStream manages Rollover thread.
+ + Massive reorg of the CVS tree.
+ + Merge multivalued parameters in dispatcher.
+ + Moved Log4JLogSink into JettyPlus
  + New look and feel for www site.
- + Fixed table refs in JDBCUserRealm.
- + Allow params in form auth URLs
- + Updated to jasper jars from tomcat 4.1.24
- + Allow query params in error page URL.
  + ProxyHandler checks black and white lists for Connect.
- + Merge multivalued parameters in dispatcher.
- + Fixed CRLF bug in MultiPartRequest
+ + RolloverFileOutputStream manages Rollover thread.
+ + Updated to jasper jars from tomcat 4.1.24
  + Warn if max form content size is reached.
- + getAuthType returns CLIENT_CERT instead of CLIENT-CERT.
- + getAuthType maps the HttpServletRequest final strings.
- + FORM Authentication is serializable for session distribution.
 
 jetty-4.2.9 - 19 March 2003
  + Conditional headers check after /dir to /dir/ redirection.
 
 jetty-4.2.9rc2 - 16 March 2003
+ + Added X-Forwarded-For header in ProxyHandler
+ + Allow dispatch to j_security_check
+ + Defaults descriptor has context classloader set.
  + Fixed build.xml for source release
  + Made rfc2068 PUT/POST Continues support optional.
- + Defaults descriptor has context classloader set.
- + Allow dispatch to j_security_check
- + Added X-Forwarded-For header in ProxyHandler
  + Updated included jmx jars
 
 jetty-4.2.9rc1 - 06 March 2003
- + Work around URLClassloader not handling leading /
- + Dump servlet can load resources for testing now.
- + Added trust manager support to SunJsseListener.
+ + Added requestlog to HttpContext.
  + Added support for client certs to AJP13.
+ + Added trust manager support to SunJsseListener.
+ + Allow delegated creation of WebApplication derivations.
+ + Check Data contraints before Auth constraints
  + Cleaned up includes
- + Removed checking for single valued headers.
+ + Dump servlet can load resources for testing now.
  + Optional 2.4 behaviour for sessionDestroyed notification.
+ + ProxyHandler has black and white host list.
+ + Reduced default context cache sizes (Total 1MB file 100KB).
+ + Removed checking for single valued headers.
  + Stop proxy url from doing user interaction.
  + Turn request log buffering off by default.
- + Reduced default context cache sizes (Total 1MB file 100KB).
- + ProxyHandler has black and white host list.
- + Added requestlog to HttpContext.
- + Allow delegated creation of WebApplication derivations.
- + Check Data contraints before Auth constraints
+ + Work around URLClassloader not handling leading /
 
 jetty-4.2.8_01 - 18 February 2003
- + Patched first release of 4.2.8 with correct version number
- + Fixed CGI servlet to handle multiple headers.
  + Added a SetResponseHeadersHandler, can set P3P headers etc.
- + ProxyHandler can handle multiple cookies.
- + Fixed AdminServlet to handle changed getServletPath better.
- + Default servlet can have own resourceBase.
- + Rolled back SocketChannelListener to 4.2.5 version
- + Added option to resolve remote hostnames.  Defaults to off.
  + Added MBeans for Servlets and Filters
+ + Added option to resolve remote hostnames.  Defaults to off.
+ + Default servlet can have own resourceBase.
+ + Fixed AdminServlet to handle changed getServletPath better.
+ + Fixed CGI servlet to handle multiple headers.
  + Moved ProxyHandler to the src1.4 tree
+ + Patched first release of 4.2.8 with correct version number
+ + ProxyHandler can handle multiple cookies.
+ + Rolled back SocketChannelListener to 4.2.5 version
 
 jetty-4.2.7 - 04 February 2003
- + Upgraded to JSSE 1.0.3_01 to fix security problem.
+ + Changed PathMap to conform to / getServletPath handling.
  + Fixed proxy tunnel for non persistent connections.
  + Relative sendRedirect handles trailing / correctly.
- + Changed PathMap to conform to / getServletPath handling.
+ + Upgraded to JSSE 1.0.3_01 to fix security problem.
 
 jetty-4.2.6 - 24 January 2003
- + Improved synchronization on AbstractSessionManager.
+ + Added HttpContext.setHosts to restrict context by real interface.
+ + Added MBeans for session managers
+ + Added version to HttpServerMBean.
  + Allow AJP13 buffers to be resized.
- + Fixed LineInput problem with expanded buffers.
  + ClientCertAuthentication updates request.
+ + Fixed LineInput problem with expanded buffers.
  + Fixed rel sendRedirects for root context.
- + Added HttpContext.setHosts to restrict context by real interface.
- + Added MBeans for session managers
  + Improved SocketChannelListener contributed.
- + Added version to HttpServerMBean.
+ + Improved synchronization on AbstractSessionManager.
 
 jetty-4.2.5 - 14 January 2003
- + Fixed pathParam bug for ;jsessionid
- + Don't process conditional headers and ranges for includes
  + Added Log4jSink in the contrib directory.
+ + Don't process conditional headers and ranges for includes
+ + Fixed pathParam bug for ;jsessionid
  + Fixed requestedSessionId null bug.
 
 jetty-4.2.4 - 04 January 2003
+ + Added MBeans for handlers
+ + Clear context attributes after stop.
+ + Clear context listeners after stop.
  + Fixed stop/start handling of servlet context
- + Reuse empty LogSink slots.
  + HTAccessHandler checks realm as well as htpassword.
- + Clear context listeners after stop.
- + Clear context attributes after stop.
- + Use requestedSessionId as default session ID.
- + Added MBeans for handlers
+ + Reuse empty LogSink slots.
  + Upgraded jasper to 4.1.18
+ + Use requestedSessionId as default session ID.
 
 jetty-4.2.4rc0 - 12 December 2002
- + Simplified ThreadedServer
- + Use ThreadLocals for ByteArrayPool to avoid synchronization.
- + Use Version to reset HttpFields
- + Cheap clear for HttpFields
- + Fixed setBufferSize NPE.
- + Cleaned up some unused listener throws.
- + Handle chunked form data.
+ + Added gzip content encoding support to Default and ResourceHandler
+ + Added HttpContext.flushCache
  + Allow empty host header.
  + Avoid optional 100 continues.
- + Limit form content size.
- + Handle = in param values.
- + Added HttpContext.flushCache
- + Configurable root context.
- + RootNotFoundHandler to help when no context found.
- + Update jasper to 4.1.16beta
- + Fixed dir listing from jars.
- + Dir listings in UTF8
+ + Better access to session manager.
  + Character encoding handling for GET requests.
- + Removed container transfer encoding handling.
- + Improved setBufferSize handling
+ + Cheap clear for HttpFields
+ + Cleaned up some unused listener throws.
  + Code logs objects rather than strings.
- + Better access to session manager.
+ + Configurable root context.
+ + Dir listings in UTF8
+ + Fixed dir listing from jars.
  + Fixed isSecure and getScheme for SSL over AJP13
- + Improved ProxyHandler to the point is works well for non SSL.
+ + Fixed setBufferSize NPE.
+ + Handle = in param values.
+ + Handle chunked form data.
  + Implemented RFC2817 CONNECT in ProxyHandler
- + Added gzip content encoding support to Default and ResourceHandler
+ + Improved ProxyHandler to the point is works well for non SSL.
+ + Improved setBufferSize handling
+ + Limit form content size.
+ + Removed container transfer encoding handling.
+ + RootNotFoundHandler to help when no context found.
+ + Simplified ThreadedServer
+ + Update jasper to 4.1.16beta
+ + Use ThreadLocals for ByteArrayPool to avoid synchronization.
+ + Use Version to reset HttpFields
 
 jetty-4.2.3 - 02 December 2002
- + Removed aggressive threadpool shrinkage to avoid deadlock on SMP machines.
- + Fixed some typos
  + Added links to Jetty Powered page
- + Clean up of ThreadedServer.stop()
- + Updated bat scripts
- + Added PKCS12Import class to import PKCS12 key directly
- + removed old HttpContext.setDirAllowed()
  + added main() to org.mortbay.http.Version
+ + Added PKCS12Import class to import PKCS12 key directly
  + Check form authentication config for leading /
  + Cleaner servlet stop to avoid extra synchronization on handle
+ + Clean up of ThreadedServer.stop()
+ + Fixed some typos
  + org.mortbay.http.HttpContext.FileClassPathAttribute
+ + Removed aggressive threadpool shrinkage to avoid deadlock on SMP machines.
+ + removed old HttpContext.setDirAllowed()
+ + Updated bat scripts
 
 jetty-4.2.2 - 20 November 2002
- + Fixed sendRedirect for non http URLS
- + Fixed URI query recycling for persistent connections
- + Fixed handling of empty headers
  + Added EOFException to reduce log verbosity on closed connections.
  + Avoided bad buffer status after closed connection.
+ + Fixed handling of empty headers
+ + Fixed sendRedirect for non http URLS
+ + Fixed URI query recycling for persistent connections
 
 jetty-4.2.1 - 18 November 2002
  + Fixed bad optimization in UrlEncoding
  + Re-enabled UrlEncoding test harnesses
 
 jetty-4.2.0 - 16 November 2002
+ + Added definitions for RFC2518 WebDav response codes.
+ + Added upload demo to dump servlet.
  + Fixed AJP13 buffer size.
- + Fixed remove listener bug.
  + Fixed include of Invoker servlet.
- + Restrict 304 responses to seconds time resolution.
- + Use IE date formatting for speed.
- + Removed jasper source and just include jars from 4.1.12
- + Worked around JVM1.3 bug for JSPs
+ + Fixed remove listener bug.
  + Lowercase jsessionid for URLs only.
  + Made NCSARequestLog easier to extend.
- + Added definitions for RFC2518 WebDav response codes.
- + Removed remaining non portable getBytes() calls
- + Added upload demo to dump servlet.
  + Many more optimizations.
+ + Removed jasper source and just include jars from 4.1.12
+ + Removed remaining non portable getBytes() calls
+ + Restrict 304 responses to seconds time resolution.
+ + Use IE date formatting for speed.
+ + Worked around JVM1.3 bug for JSPs
 
 jetty-4.1.4 - 16 November 2002
  + Fixed ContextLoader parent delegation bug
- + Fixed remove SocketListener bug.
  + Fixed Invoker servlet for RD.include
- + Use IE date formatting for last-modified efficiency
+ + Fixed remove SocketListener bug.
  + Last modified handling uses second resolution.
  + Made NCSARequestLog simpler to extend.
+ + Use IE date formatting for last-modified efficiency
 
 jetty-4.2.0rc1 - 02 November 2002
- + Support default mime mapping defined by *
- + Recycling of HttpFields class.
- + Renamed Filter application methods.
- + Fixed firstWrite after commit.
  + Fixed ContextLoader parent delegation bug.
+ + Fixed directory resource bug in JarFileResource.
+ + Fixed firstWrite after commit.
  + Fixed problem setting the size of chunked buffers.
- + Removed unused Servlet and Servlet-Engine headers.
  + Fixed servletpath on invoker for named servlets.
- + Fixed directory resource bug in JarFileResource.
  + Improved handling of 2 byte encoded characters within forms.
+ + Recycling of HttpFields class.
+ + Removed unused Servlet and Servlet-Engine headers.
+ + Renamed Filter application methods.
+ + Support default mime mapping defined by *
 
 jetty-4.2.0rc0 - 24 October 2002
- + Greg's birthday release!
+ + Added authenticator to admin.xml
  + Added embedded iso8859 writer to HttpOutputStream.
- + Removed duplicate classes from jar
  + Fixed RolloverFileOutputStream without date.
  + Fixed SessionManager initialization
- + Added authenticator to admin.xml
  + Fixed Session timeout NPE.
+ + Greg's birthday release!
+ + Removed duplicate classes from jar
 
 jetty-4.1.3 - 24 October 2002
+ + Added authenticator to admin.xml
  + Fixed RolloverFileOutputStream without date.
  + Fixed SessionManager initialization
- + Added authenticator to admin.xml
  + Fixed Session timeout NPE.
 
 jetty-4.0.6 - 24 October 2002
  + Clear interrupted status in ThreadPool
- + Fixed forward query string handling
  + fixed forward attribute handling for jsp-file servlets
- + Fixed setCharacterEncoding to work with getReader
+ + Fixed forward query string handling
  + Fixed handling of relative sendRedirect after forward.
+ + Fixed setCharacterEncoding to work with getReader
  + Fixed virtual hosts temp directories.
 
 jetty-4.2.0beta0 - 13 October 2002
- + New ThreadPool implementation.
- + New Buffering implementation.
+ + 404 instead of 403 for WEB-INF requests
+ + Allow %3B encoded ; in URLs
+ + Allow anonymous realm
+ + Build without jmx
+ + Fixed bad log dir detection
+ + Fixed caching of directories to avoid shared buffers.
+ + Fix Session invalidation bug
+ + FORM authentication sets 403 error page
+ + getNamedDispatcher(null) returns containers default servlet.
  + New AJP13 implementation.
+ + New Buffering implementation.
+ + New ThreadPool implementation.
  + Removed Dispatcher dependancy on ServletHttpContext
- + getNamedDispatcher(null) returns containers default servlet.
- + unquote charset in content type
  + Stop/Start filters in declaration order.
+ + unquote charset in content type
+ + Update jasper to 4.1.12 tag
  + Use "standard" names for default,jsp & invoker servlets.
- + Fixed caching of directories to avoid shared buffers.
- + Fixed bad log dir detection
- + Fix Session invalidation bug
- + Build without jmx
+
+jetty-4.1.2 - 13 October 2002
  + 404 instead of 403 for WEB-INF requests
- + FORM authentication sets 403 error page
  + Allow %3B encoded ; in URLs
  + Allow anonymous realm
- + Update jasper to 4.1.12 tag
-
-jetty-4.1.2 - 13 October 2002
- + Some AJP13 optimizations.
- + getNamedDispatcher(null) returns containers default servlet.
- + unquote charset in content type
- + Stop/Start filters in declaration order.
- + Use "standard" names for default,jsp & invoker servlets.
- + Fixed caching of directories to avoid shared buffers.
+ + Build without jmx
  + Fixed bad log dir detection
+ + Fixed caching of directories to avoid shared buffers.
  + Fix Session invalidation bug
- + Build without jmx
- + 404 instead of 403 for WEB-INF requests
  + FORM authentication sets 403 error page
- + Allow %3B encoded ; in URLs
- + Allow anonymous realm
+ + getNamedDispatcher(null) returns containers default servlet.
+ + Some AJP13 optimizations.
+ + Stop/Start filters in declaration order.
+ + unquote charset in content type
  + Update jasper to 4.1.12 tag
+ + Use "standard" names for default,jsp & invoker servlets.
 
 jetty-4.1.1 - 30 September 2002
+ + Avoid setting sotimeout for optimization.
+ + Cache directory listings.
+ + Deprecated maxReadTime.
  + Fixed client scripting vulnerability with jasper2.
- + Merged LimitedNCSARequestLog into NCSARequestLog
+ + Fixed infinite recursion in JDBCUserRealm
  + Fixed space in resource name handling for jdk1.4
+ + Merged LimitedNCSARequestLog into NCSARequestLog
  + Moved launcher/src to src/org/mortbay/start
- + Fixed infinite recursion in JDBCUserRealm
- + Avoid setting sotimeout for optimization.
  + String comparison of If-Modified-Since headers.
  + Touch files when expanding jars
- + Deprecated maxReadTime.
- + Cache directory listings.
 
 jetty-4.1.0 - 22 September 2002
- + Fixed CGI+windows security hole.
+ + Added LimitedNCSARequestLog
+ + ClientCertAuthenticator protected from null subjectDN
+ + Context Initparams to control session cookie domain, path and age.
  + Fixed AJP13 handling of mod_jk loadbalancing.
- + Stop servlets in opposite order to start.
+ + Fixed CGI+windows security hole.
+ + Handle unremovable tempdir.
  + NCSARequest log buffered default
- + WEB-INF/classes before WEB-INF/lib
  + Sorted directory listings.
- + Handle unremovable tempdir.
- + Context Initparams to control session cookie domain, path and age.
- + ClientCertAuthenticator protected from null subjectDN
- + Added LimitedNCSARequestLog
+ + Stop servlets in opposite order to start.
  + Use javac -target 1.2 for normal classes
+ + WEB-INF/classes before WEB-INF/lib
 
 jetty-4.1.0RC6 - 14 September 2002
+ + Added logon.jsp for no cookie form authentication.
+ + Added redirect to welcome file option.
+ + Cleaned up old debug.
  + Don't URL encode FileURLS.
- + Improved HashUserRealm doco
- + FormAuthenticator uses normal redirections now.
  + Encode URLs of Authentication redirections.
- + Added logon.jsp for no cookie form authentication.
  + Extended Session API to pass request for jvmRoute handling
  + Fixed problem with AJP 304 responses.
+ + FormAuthenticator uses normal redirections now.
+ + Improved HashUserRealm doco
  + Improved look and feel of demo
- + Cleaned up old debug.
- + Added redirect to welcome file option.
 
 jetty-4.1.0RC5 - 08 September 2002
- + AJP13Listener caught up with HttpConnection changes.
  + Added commandPrefix init param to CGI
- + More cleanup in ThreadPool for idle death.
- + Improved errors for misconfigured realms.
+ + AJP13Listener caught up with HttpConnection changes.
  + Implemented security-role-ref for isUserInRole.
+ + Improved errors for misconfigured realms.
+ + More cleanup in ThreadPool for idle death.
 
 jetty-4.1.0RC4 - 30 August 2002
+ + Created statsLock sync objects to avoid deadlock when stopping.
  + Included IbmJsseListener in the contrib directory.
- + Updated jasper2 to 4.1.10 tag.
  + Reverted to 302 for all redirections as all clients do not understand 303
- + Created statsLock sync objects to avoid deadlock when stopping.
+ + Updated jasper2 to 4.1.10 tag.
 
 jetty-4.1.0RC3 - 28 August 2002
- + Fixed security problem for suffix matching with trailing "/"
- + addWebApplications encodes paths to allow for spaces in file names.
- + Improved handling of PUT,DELETE & MOVE.
- + Improved handling of path encoding in Resources for bad JVMs
  + Added buffering to request log
- + Created and integrated the Jetty Launcher
- + Made Resource canonicalize it's base path for directories
- + Allow WebApplicationHandler to be used with other handlers.
  + Added defaults descriptor to addWebApplications.
+ + addWebApplications encodes paths to allow for spaces in file names.
  + Allow FORM auth pages to be within security constraint.
+ + Allow WebApplicationHandler to be used with other handlers.
+ + Created and integrated the Jetty Launcher
+ + Fixed security problem for suffix matching with trailing "/"
+ + Improved handling of path encoding in Resources for bad JVMs
+ + Improved handling of PUT,DELETE & MOVE.
+ + Made Resource canonicalize it's base path for directories
 
 jetty-4.1.0RC2 - 20 August 2002
- + Conveninace setClassLoaderJava2Compliant method.
+ + Added HttpListener.bufferReserve
+ + Build ant, src and zip versions with the release
  + Clear interrupted status in ThreadPool
+ + Conveninace setClassLoaderJava2Compliant method.
  + Fixed HttpFields cache overflow
  + Improved ByteArrayPool to handle multiple sizes.
- + Added HttpListener.bufferReserve
- + Use system line separator for log files.
  + Updated to Jasper2 (4_1_9 tag)
- + Build ant, src and zip versions with the release
+ + Use system line separator for log files.
 
 jetty-4.1.0RC1 - 11 August 2002
  + Fixed forward query string handling
- + Fixed setCharacterEncoding to work with getReader
+ + Fixed forward to jsp-file servlet
  + Fixed getContext to use canonical contextPathSpec
+ + Fixed handling of relative sendRedirect after forward.
+ + Fixed setCharacterEncoding to work with getReader
  + Improved the return codes for PUT
  + Made HttpServer serializable
  + Updated international URI doco
  + Updated jasper to CVS snapshot 200208011920
- + Fixed forward to jsp-file servlet
- + Fixed handling of relative sendRedirect after forward.
 
 jetty-4.1.0RC0 - 31 July 2002
- + Fixed getRealPath for packed war files.
- + Changed URI default charset back to ISO_8859_1
- + Restructured Password into Password and Credentials
  + Added DigestAuthenticator
- + Added link to a Jetty page in Korean.
  + Added ExpiryHandler which can set a default Expires header.
+ + Added link to a Jetty page in Korean.
+ + Changed URI default charset back to ISO_8859_1
+ + Fixed getRealPath for packed war files.
+ + Restructured Password into Password and Credentials
 
 jetty-4.0.5 - 31 July 2002
  + Fixed getRealPath for packed war files.
- + Reversed order of ServletContextListener.contextDestroyed calls
  + Fixed getRequestURI for RD.forward to return new URI.
+ + Reversed order of ServletContextListener.contextDestroyed calls
 
 jetty-4.1.B1 - 19 July 2002
- + Updated mini.http.jar target
- + CGI Servlet, pass all HTTP headers through.
+ + Added 2.4 Filter dispatching support.
+ + Added PUT,DELETE,MOVE support to webapps.
  + CGI Servlet, catch and report program invocation failure status.
  + CGI Servlet, fixed suffix mapping problem.
+ + CGI Servlet, pass all HTTP headers through.
  + CGI Servlet, set working directory for exec
- + Support HTTP/0.9 requests again
- + Reversed order of ServletContextListener.contextDestroyed calls
  + Moved dynamic servlet handling to Invoker servlet.
  + Moved webapp resource handling to Default servlet.
+ + Reversed order of ServletContextListener.contextDestroyed calls
  + Sessions create attribute map lazily.
- + Added PUT,DELETE,MOVE support to webapps.
- + Added 2.4 Filter dispatching support.
+ + Support HTTP/0.9 requests again
+ + Updated mini.http.jar target
 
 jetty-3.1.9 - 15 July 2002
  + Allow doHead requests to be forwarded.
@@ -4679,29 +6660,29 @@ jetty-3.1.9 - 15 July 2002
 
 jetty-4.1.B0 - 13 July 2002
  + Added work around of JDK1.4 bug with NIO listener
- + Moved 3rd party jars to $JETTY_HOME/ext
- + Fixed ThreadPool bug when minThreads <= CPUs
- + close rather than disable stream after forward
  + Allow filter init to access servlet context methods.
+ + close rather than disable stream after forward
+ + Fixed close problem with load balancer.
+ + Fixed ThreadPool bug when minThreads <= CPUs
  + Keep notFoundContext out of context mapping lists.
  + mod_jk FAQ
- + Fixed close problem with load balancer.
- + Stopped RD.includes closing response.
- + RD.forward changes getRequestURI.
+ + Moved 3rd party jars to $JETTY_HOME/ext
  + NCSARequestLog can log to stderr
+ + RD.forward changes getRequestURI.
+ + Stopped RD.includes closing response.
 
 jetty-4.1.D2 - 24 June 2002
- + Support trusted external authenticators.
- + Moved jmx classes from JettyExtra to here.
- + Set contextloader during webapplicationcontext.start
  + Added AJP13 listener for apache integration.
- + Fixed ChunkableOutputStream close propagation
- + Better recycling of HttpRequests.
- + Protect session.getAttributeNames from concurrent modifications.
  + Allow comma separated cookies and headers
  + Back out Don't chunk 30x empty responses.
+ + Better recycling of HttpRequests.
  + Conditional header tested against welcome file not directory.
+ + Fixed ChunkableOutputStream close propagation
  + Improved ThreadedServer stopping on bad networks
+ + Moved jmx classes from JettyExtra to here.
+ + Protect session.getAttributeNames from concurrent modifications.
+ + Set contextloader during webapplicationcontext.start
+ + Support trusted external authenticators.
  + Use ThreadLocals to avoid unwrapping in Dispatcher.
 
 jetty-4.0.4 - 23 June 2002
@@ -4710,846 +6691,846 @@ jetty-4.0.4 - 23 June 2002
  + Improved ThreadedServer stopping on bad networks
 
 jetty-4.0.3 - 20 June 2002
- + WebapplicationContext.start sets context loader
+ + Allow comma separated cookies and headers
+ + Allow session manager to be initialized when set.
+ + Better recycling of HttpRequests.
  + Fixed close propagation of on-chunked output streams
+ + Fixed japanese locale
  + Force security disassociation.
- + Better recycling of HttpRequests.
  + Protect session.getAttributeNames from concurrent modifications.
- + Allow session manager to be initialized when set.
- + Fixed japanese locale
- + Allow comma separated cookies and headers
+ + WebapplicationContext.start sets context loader
 
 jetty-4.1.D1 - 08 June 2002
- + Recycle servlet requests and responses
  + Added simple buffer pool.
- + Reworked output buffering to keep constant sized buffers.
  + Don't chunk 30x empty responses.
- + Fixed "" contextPaths in Dispatcher.
- + Removed race for the starting of session scavaging
  + Fixed /foo/../bar// bug in canonical path.
+ + Fixed "" contextPaths in Dispatcher.
  + Merged ResourceBase and SecurityBase into HttpContext
+ + Recycle servlet requests and responses
+ + Removed race for the starting of session scavaging
+ + Reworked output buffering to keep constant sized buffers.
 
 jetty-4.0.2 - 06 June 2002
- + Fixed web.dtd references.
- + Fixed handler/context start order.
  + Added OptimizeIt plug
- + Fixed /foo/../bar// bug in canonical path.
  + Don't chunk 30x empty responses.
+ + Fixed /foo/../bar// bug in canonical path.
  + Fixed "" contextPaths in Dispatcher.
+ + Fixed handler/context start order.
+ + Fixed web.dtd references.
  + Removed race for the starting of session scavaging
 
 jetty-3.1.8 - 06 June 2002
- + Made SecurityConstraint.addRole() require authentication.
- + Fixed singled threaded dynamic servlets
- + Fixed no slash context redirection.
  + Fixed /foo/../bar// bug in canonical path.
+ + Fixed no slash context redirection.
+ + Fixed singled threaded dynamic servlets
+ + Made SecurityConstraint.addRole() require authentication.
 
 jetty-4.1.D0 - 05 June 2002
- + The 4.1 Series started looking for even more performance within the 2.3
-   specification.
- + Removed the HttpMessage facade mechanism
- + BRAND NEW WebApplicationHandler & WebApplicationContext
+ + Added OptimizeIt plug.
  + Added TypeUtil to reduce Integer creation.
- + General clean up of the API for for MBean getters/setters.
+ + BRAND NEW WebApplicationHandler & WebApplicationContext
  + Experimental CLIENT-CERT Authenticator
- + Restructured ResourceHandler into ResourceBase
- + Fixed web.dtd references.
  + Fixed handler/context start order.
- + Added OptimizeIt plug.
+ + Fixed web.dtd references.
+ + General clean up of the API for for MBean getters/setters.
+ + Removed the HttpMessage facade mechanism
+ + Restructured ResourceHandler into ResourceBase
+ + The 4.1 Series started looking for even more performance within the 2.3
+   specification.
 
 jetty-4.0.1 - 22 May 2002
- + Fixed contextclassloader on ServletContextEvents.
- + Support graceful stopping of context and server.
  + Fixed "null" return from getRealPath
+ + Fixed contextclassloader on ServletContextEvents.
  + OutputStreamLogSink config improvements
+ + Support graceful stopping of context and server.
  + Updated jasper to 16 May snapshot
 
 jetty-4.0.1RC2 - 14 May 2002
- + Better error for jre1.3 with 1.4 classes
- + Cleaned up RD query string regeneration.
  + 3DES Keylength was being reported as 0. Now reports 168 bits.
- + Implemented the run-as servlet tag.
  + Added confidential and integral redirections to HttpListener
+ + Better error for jre1.3 with 1.4 classes
+ + Cleaned up RD query string regeneration.
  + Fixed ServletResponse.reset() to resetBuffer.
+ + Implemented the run-as servlet tag.
 
 jetty-4.0.1RC1 - 29 April 2002
- + Improved flushing of chunked responses
+ + Avoid flushes during RequestDispatcher.includes
  + Better handling if no realm configured.
  + Expand ByteBuffer full limit with capacity.
  + Fixed double filtering of welcome files.
  + Fixed FORM authentication auth of login page bug.
  + Fixed setTempDirectory creation bug
- + Avoid flushes during RequestDispatcher.includes
+ + Improved flushing of chunked responses
 
 jetty-4.0.1RC0 - 18 April 2002
- + Updated Jasper to CVS snapshot from Apr 18 18:50:59 BST 2002
- + Pass pathParams via welcome file forward for jsessionid
- + Extended facade interfaces to HttpResponse.sendError
- + Moved basic auth handling to HttpRequest
  + AbstractSessionManager sets contextClassLoader for scavanging
- + Set thread context classloader for webapp load-on-startup inits
  + Added extract arg to addWebApplications
+ + DTD allows static "Get" and "Set" methods to be invoked.
+ + Extended facade interfaces to HttpResponse.sendError
  + Fixed delayed response bug: Stopped HttpConnection consuming input from
    timedout connection.
- + DTD allows static "Get" and "Set" methods to be invoked.
+ + Moved basic auth handling to HttpRequest
+ + Pass pathParams via welcome file forward for jsessionid
+ + Set thread context classloader for webapp load-on-startup inits
+ + Updated Jasper to CVS snapshot from Apr 18 18:50:59 BST 2002
 
 jetty-4.0.0 - 22 March 2002
- + Updated tutorial configure version
  + Added IPAddressHandler for IP restrictions
- + Updated contributors.
- + Minor documentation updates.
  + Jetty.sh cygwin support
+ + Minor documentation updates.
+ + Updated contributors.
+ + Updated tutorial configure version
 
 jetty-4.0.RC3 - 20 March 2002
+ + Changed html attribute order for mozilla quirk.
+ + ContextInitialized notified before load-on-startup servlets.
  + Fixed ZZZ offset format to +/-HHMM
- + Updated history
  + JDBCUserRealm instantiates JDBC driver
- + ContextInitialized notified before load-on-startup servlets.
  + Suppress WriterOutputStream warning.
- + Changed html attribute order for mozilla quirk.
+ + Updated history
 
 jetty-4.0.RC2 - 12 March 2002
- + Fixed security constraint problem with //
- + Fixed version for String XmlConfigurations
- + Fixed empty referrer in NCSA log.
- + Dont try to extract directories
  + Added experimental nio SocketChannelListener
  + Added skeleton load balancer
+ + Disabled the Password EXEC mechanism by default
+ + Dont try to extract directories
  + Fixed column name in JDBCUserRealm
- + Remove last of the Class.forName calls.
+ + Fixed empty referrer in NCSA log.
+ + Fixed security constraint problem with //
+ + Fixed version for String XmlConfigurations
  + Removed redundant sessionID check.
+ + Remove last of the Class.forName calls.
  + Security FAQ
- + Disabled the Password EXEC mechanism by default
 
 jetty-3.1.7 - 12 March 2002
  + Fixed security problem with constraints being bypassed with // in URLs
 
 jetty-4.0.RC1 - 06 March 2002
  + Added ContentEncodingHandler for compression.
- + Fixed filter vs forward bug.
- + Improved efficiency of quality list handling
- + Simplified filter API to chunkable streams
- + XmlParser is validating by default. use o.m.x.XmlParser.NotValidating
-   property to change.
- + contextDestroyed event sent before destruction.
- + Minor changes to make HttpServer work on J2ME CVM
- + Warn if jdk 1.4 classes used on JVM <1.4
- + WebApplication will use ContextLoader even without WEB-INF directory.
- + FileResource depends less on FilePermissions.
  + Call response.flushBuffer after service to flush wrappers.
- + Empty suffix for temp directory.
+ + contextDestroyed event sent before destruction.
  + Contributors list as an image to prevent SPAM!
+ + Empty suffix for temp directory.
+ + FileResource depends less on FilePermissions.
+ + Fixed filter vs forward bug.
  + Fixed recursive DEBUG loop in Logging.
+ + Improved efficiency of quality list handling
+ + Minor changes to make HttpServer work on J2ME CVM
+ + Simplified filter API to chunkable streams
  + Updated jetty.sh to always respect arguments.
+ + Warn if jdk 1.4 classes used on JVM <1.4
+ + WebApplication will use ContextLoader even without WEB-INF directory.
+ + XmlParser is validating by default. use o.m.x.XmlParser.NotValidating
+   property to change.
 
 jetty-3.1.6 - 28 February 2002
- + Implemented 2.3 clarifications to security constraint semantics PLEASE
-   REVIEW YOUR SECURITY CONSTRAINTS (see README).
+ + Dispatcher.forward dispatches directly to ServletHolder to avoid premature
+   exception handling.
  + Empty suffix for temp directory.
  + Fixed HttpFields remove bug
- + Set Listeners default scheme
- + LineInput can handle any sized marks
  + HttpResponse.sendError makes a better attempt at finding an error page.
- + Dispatcher.forward dispatches directly to ServletHolder to avoid premature
-   exception handling.
+ + Implemented 2.3 clarifications to security constraint semantics PLEASE
+   REVIEW YOUR SECURITY CONSTRAINTS (see README).
+ + LineInput can handle any sized marks
+ + Set Listeners default scheme
 
 jetty-4.0.B2 - 25 February 2002
- + Minor Jasper updates
- + Improve handling of unknown URL protocols.
- + Improved default jetty.xml
- + Adjust servlet facades for welcome redirection
- + User / mapping rather than /* for servlet requests to static content
  + Accept jetty-web.xml or web-jetty.xml in WEB-INF
- + Added optional JDK 1.4 src tree
- + o.m.u.Frame uses JDK1.4 stack frame handling
  + Added LoggerLogSink to direct Jetty Logs to JDK1.4 Log.
- + Start ServletHandler as part of the FilterHandler start.
- + Simplified addWebApplication
- + Added String constructor to XmlConfiguration.
+ + Added optional JDK 1.4 src tree
  + Added org.mortbay.http.JDBCUserRealm
+ + Added String constructor to XmlConfiguration.
+ + Adjust servlet facades for welcome redirection
+ + Improved default jetty.xml
+ + Improve handling of unknown URL protocols.
  + Init classloader for JspServlet
+ + Minor Jasper updates
+ + o.m.u.Frame uses JDK1.4 stack frame handling
+ + Simplified addWebApplication
  + Slightly more agressive eating unused input from non persistent connection.
+ + Start ServletHandler as part of the FilterHandler start.
+ + User / mapping rather than /* for servlet requests to static content
 
 jetty-4.0.B1 - 13 February 2002
- + WriterOutputStream so JSPs can include static resources.
- + Suppress error only for IOExceptions not derivitives.
- + HttpConnection always eats unused bodies
- + Merged HttpMessage and Message
- + LineInput waits for LF after CF if seen CRLF before.
  + Added setClassLoader and moved getFileClassPath to HttpContext
- + Updated examples webapp from tomcat
  + getRequestURI returns encoded path
+ + HttpConnection always eats unused bodies
+ + LineInput waits for LF after CF if seen CRLF before.
+ + Merged HttpMessage and Message
  + Servlet request destined for static content returns paths as default servlet
+ + Suppress error only for IOExceptions not derivitives.
+ + Updated examples webapp from tomcat
+ + WriterOutputStream so JSPs can include static resources.
 
 jetty-4.0.B0 - 04 February 2002
- + Implemented 2.3 security constraint semantics PLEASE REVIEW YOUR SECURITY
-   CONSTRAINTS (see README).
- + Stop and remove NotFound context for HttpServer
- + HttpContext destroy
- + Release process builds JettyExtra
- + Welcome files may be relative
- + Fixed HttpFields remove bug
+ + Added AbstractSessionManager
  + Added Array element to XMLConfiguration
- + Allow listener schemes to be set.
- + Added index links to tutorial
- + Renamed getHttpServers and added setAnonymous
- + Updated crimson to 1.1.3
  + Added hack for compat tests in watchdog for old tomcat stuff
- + Added AbstractSessionManager
- + Support Random Session IDs in HashSessionManager.
+ + Added index links to tutorial
+ + Allow listener schemes to be set.
  + Common handling of TRACE
- + Updated tutorial and FAQ
- + Reduce object count and add hash width to StringMap
  + Factor out RolloverFileOutputStream from OutputStreamLogSink
+ + Fixed HttpFields remove bug
+ + Handle special characters in resource file names better.
+ + HttpContext destroy
+ + Implemented 2.3 security constraint semantics PLEASE REVIEW YOUR SECURITY
+   CONSTRAINTS (see README).
+ + Reduce object count and add hash width to StringMap
+ + Release process builds JettyExtra
+ + Removed triggers from Code.
  + Remove request logSink and replace with RequestLog using
    RolloverFileOutputStream
- + Handle special characters in resource file names better.
+ + Renamed getHttpServers and added setAnonymous
+ + Stop and remove NotFound context for HttpServer
+ + Support Random Session IDs in HashSessionManager.
+ + Updated crimson to 1.1.3
+ + Updated tutorial and FAQ
  + Welcome file dispatch sets requestURI.
- + Removed triggers from Code.
+ + Welcome files may be relative
 
 jetty-4.0.D4 - 14 January 2002
- + Prevent output after forward
- + Handle ServletRequestWrappers for Generic Servlets
- + Improved handling of UnavailableException
- + Extract WAR files to standard temp directory
- + URI uses UTF8 for % encodings.
  + Added BlueRibbon campaign.
- + RequestDispatcher uses cached resources for include
- + Improved HttpResponsse.sendError error page matching.
+ + Added isAuthenticated to UserPrincipal
+ + Extract WAR files to standard temp directory
  + Fixed noaccess auth demo.
  + FORM auth caches UserPrincipal
- + Added isAuthenticated to UserPrincipal
+ + Handle ServletRequestWrappers for Generic Servlets
+ + Improved handling of UnavailableException
+ + Improved HttpResponsse.sendError error page matching.
+ + Prevent output after forward
+ + RequestDispatcher uses cached resources for include
+ + URI uses UTF8 for % encodings.
 
 jetty-4.0.D3 - 31 December 2001
+ + cookies with maxAge==0 expire on 1 jan 1970
+ + Corrected name to HTTP_REFERER in CGI Servlet.
+ + DateCache handles misses better.
  + Fixed cached filter wrapping.
+ + Fixed ContextLoader lib handling.
  + Fixed getLocale again
- + Patch jasper to 20011229101000
- + Removed limits on mark in LineInput.
- + Corrected name to HTTP_REFERER in CGI Servlet.
  + Fixed UrlEncoding for % + combination.
  + Generalized temp file handling
- + Fixed ContextLoader lib handling.
- + DateCache handles misses better.
  + HttpFields uses DateCache more.
- + Moved admin port to 8081 to avoid JBuilder
  + Made Frame members private and fixed test harness
- + cookies with maxAge==0 expire on 1 jan 1970
+ + Moved admin port to 8081 to avoid JBuilder
+ + Patch jasper to 20011229101000
+ + Removed limits on mark in LineInput.
  + setCookie always has equals
 
 jetty-3.1.5 - 11 December 2001
- + setCookie always has equals for cookie value
+ + Allow POSTs to static resources.
+ + Branched at Jetty_3_1
  + cookies with maxage==0 expired 1 jan 1970
- + Fixed formatting of redirectURLs for NS4.08
  + Fixed ChunableInputStream.resetStream bug.
+ + Fixed formatting of redirectURLs for NS4.08
  + Ignore IO errors when trying to persist connections.
- + Allow POSTs to static resources.
+ + setCookie always has equals for cookie value
  + stopJob/killStop in ThreadPool to improve stopping ThreadedServer on some
    platforms.
- + Branched at Jetty_3_1
 
 jetty-4.0.D2 - 02 December 2001
- + Removed most of the old doco, which needs to be rewritten and added again.
- + Restructured for demo and test hierarchies
- + Fixed formatting of redirect URLs.
- + Removed ForwardHandler.
- + Removed Demo.java (until updated).
- + Made the root context a webapplication.
- + Moved demo docroot/servlets to demo directory
  + added addWebApplications auto discovery
- + Disabled last forwarding by setPath()
- + Removed Request set methods (will be replaced)
- + New event model to decouple from beans container.
- + Better handling of charset in form encoding.
  + Allow POSTs to static resources.
+ + Better handling of charset in form encoding.
+ + Disabled last forwarding by setPath()
  + Fixed ChunableInputStream.resetStream bug.
+ + Fixed formatting of redirect URLs.
  + Ignore IO errors when trying to persist connections.
+ + Made the root context a webapplication.
+ + Moved demo docroot/servlets to demo directory
+ + New event model to decouple from beans container.
+ + Removed Demo.java (until updated).
+ + Removed ForwardHandler.
+ + Removed most of the old doco, which needs to be rewritten and added again.
+ + Removed Request set methods (will be replaced)
+ + Restructured for demo and test hierarchies
  + stopJob/killStop in ThreadPool to improve stopping ThreadedServer on some
    platforms.
 
 jetty-4.0.D1 - 14 November 2001
- + Fixed ServletHandler with no servlets
- + Fixed bug with request dispatcher parameters
- + New ContextLoader implementation.
- + New Dispatcher implementation
  + Added Context and Session Event Handling
- + Added FilterHolder
  + Added FilterHandler
+ + Added FilterHolder
  + Changed HandlerContext to HttpContext
- + Simplified ServletHandler
+ + Fixed bug with request dispatcher parameters
+ + Fixed ServletHandler with no servlets
+ + New ContextLoader implementation.
+ + New Dispatcher implementation
  + Removed destroy methods
  + Simplified MultiMap
+ + Simplified ServletHandler
 
 jetty-4.0.D0 - 06 November 2001
- + Branched from Jetty_3_1 == Jetty_3_1_4
- + 2.3 Servlet API
  + 1.2 JSP API
- + Jasper from tomcat4
- + Start SessionManager abstraction.
+ + 2.3 Servlet API
  + Added examples webapp from tomcat4
  + Branched at Jetty_3_1
+ + Branched from Jetty_3_1 == Jetty_3_1_4
+ + Jasper from tomcat4
+ + Start SessionManager abstraction.
 
 jetty-3.1.4 - 06 November 2001
  + Added RequestLogFormat to allow extensible request logs.
- + Support the ZZZ timezone offset format in DateCache
- + HTAccessHandler made stricter on misconfiguration
- + Generate session unbind events on a context.stop()
  + Default PathMap separator changed to ":,"
+ + Generate session unbind events on a context.stop()
+ + getRealPath accepts \ URI separator on platforms using \ file separator.
+ + HTAccessHandler made stricter on misconfiguration
  + PathMap now ignores paths after ; or ? characters.
  + Remove old stuff from contrib that had been moved to extra
- + getRealPath accepts \ URI separator on platforms using \ file separator.
+ + Support the ZZZ timezone offset format in DateCache
 
 jetty-3.1.3 - 26 October 2001
- + Fix security problem with trailing special characters. Trailing %00 enabled
-   JSP source to be viewed or other servlets to be bypassed.
+ + Allow a per context UserRealm instance.
+ + Correct dispatch to error pages with javax attributes set.
+ + Fixed binary files in CVS
  + Fixed several problems with external role authentication. Role
    authentication in JBoss was not working correctly and there were possible
    object leaks. The fix required an API change to UserPrinciple and UserRealm.
- + Allow a per context UserRealm instance.
- + Upgraded JSSE to 1.0.2
+ + Fixed Virtual hosts to case insensitive.
+ + Fix security problem with trailing special characters. Trailing %00 enabled
+   JSP source to be viewed or other servlets to be bypassed.
  + Improved FORM auth handling of role failure.
  + Improved Jasper debug output.
  + Improved ThreadedServer timeout defaults
- + Fixed binary files in CVS
- + Fixed Virtual hosts to case insensitive.
  + PathMap spec separator changed from ',' to ':'. May be set with
    org.mortbay.http.PathMap.separators system property.
- + Correct dispatch to error pages with javax attributes set.
+ + Upgraded JSSE to 1.0.2
 
 jetty-3.1.2 - 13 October 2001
- + Fixed double entry on PathMap.getMatches
- + Fixed servlet handling of non session url params.
- + Fixed attr handling in XmlParser.toString
- + Fixed request log date formatting
- + Fixed NotFoundHandler handling of unknown methods
- + Fixed FORM Authentication username.
- + Fixed authentication role handling in FORM auth.
- + FORM authentication passes query params.
- + Added short delay to shutdown hook for JVM bug.
- + Added ServletHandler.sessionCount()
  + Added run target to ant
+ + Added ServletHandler.sessionCount()
+ + Added short delay to shutdown hook for JVM bug.
  + Changed 304 responses for Opera browser.
  + Changed JSESSIONID to jsessionid
- + Log OK state after thread low warnings.
  + Changed unsatisfiable range warnings to debug.
+ + Fixed attr handling in XmlParser.toString
+ + Fixed authentication role handling in FORM auth.
+ + Fixed double entry on PathMap.getMatches
+ + Fixed FORM Authentication username.
+ + Fixed NotFoundHandler handling of unknown methods
+ + Fixed request log date formatting
+ + Fixed servlet handling of non session url params.
+ + FORM authentication passes query params.
  + Further improvements in handling of shutdown.
+ + Log OK state after thread low warnings.
 
 jetty-3.1.1 - 27 September 2001
+ + Correctly ignore auth-constraint descriptions.
  + Fixed jar manifest format - patched 28 Sep 2001
- + Removed JDK 1.3 dependancy
  + Fixed ServletRequest.getLocale().
- + Removed incorrect warning for WEB-INF/lib jar files.
  + Handle requestdispatcher during init.
- + Use lowercase tags in html package to be XHTML-like.
- + Correctly ignore auth-constraint descriptions.
  + Reduced verbosity of bad URL errors from IIS virus attacks
+ + Removed incorrect warning for WEB-INF/lib jar files.
+ + Removed JDK 1.3 dependancy
+ + Use lowercase tags in html package to be XHTML-like.
 
 jetty-3.1.0 - 21 September 2001
+ + Added HandlerContext.registerHost
  + Added long overdue Tutorial documentation.
- + Improved some other documentation.
- + Fix ResourceHandler cache invalidate.
- + Fix ServletResponse.setLocale()
- + Fix reuse of Resource
- + Fix Jetty.bat for spaces.
  + Fix .. handling in URI
- + Fix REFFERER in CGI
- + Fix FORM authentication on exact patterns
  + Fix flush on stop bug in logs.
+ + Fix FORM authentication on exact patterns
+ + Fix Jetty.bat for spaces.
  + Fix param reading on CGI servlet
- + New simplified jetty.bat
+ + Fix REFFERER in CGI
+ + Fix ResourceHandler cache invalidate.
+ + Fix reuse of Resource
+ + Fix ServletResponse.setLocale()
  + Improved closing of listeners.
+ + Improved some other documentation.
+ + New simplified jetty.bat
  + Optimized List creation
  + Removed win32 service.exe
- + Added HandlerContext.registerHost
 
 jetty-3.1.rc9 - 02 September 2001
  + Added bin/orgPackage.sh script to change package names.
- + Changed to org.mortbay domain names.
- + Form auth login and error pages relative to context path.
- + Fixed handling of rel form authentication URLs
- + Added support for Nonblocking listener.
+ + Added handlerContext.setClassPaths
  + Added lowResourcePersistTimeMs for more graceful degradation when we run out
    of threads.
- + Patched Jasper to 3.2.3.
- + Added handlerContext.setClassPaths
+ + Added support for Nonblocking listener.
+ + Changed to org.mortbay domain names.
  + Fixed bug with non cookie sessions.
+ + Fixed handling of rel form authentication URLs
  + Format cookies in HttpFields.
+ + Form auth login and error pages relative to context path.
+ + Patched Jasper to 3.2.3.
 
 jetty-3.1.rc8 - 22 August 2001
- + Support WEB-INF/web-jetty.xml configuration extension for webapps
- + Allow per context log files.
- + Updated sponsors page
  + Added HttpServer statistics
+ + Allow contextpaths without leading /
+ + Allow per context log files.
+ + Buffer allocation
  + Don't add notfound context.
- + Many major and minor optimizations:
+ + Fixed handling of default mime types
  + ISO8859 conversion
- + Buffer allocation
- + URI pathAdd
- + StringMap
- + URI canonicalPath
+ + Many major and minor optimizations:
  + OutputStreamLogSink replaces WriterLogSink
- + Separation of URL params in HttpHandler API.
- + Fixed handling of default mime types
- + Allow contextpaths without leading /
  + Removed race from dynamic servlet initialization.
+ + Separation of URL params in HttpHandler API.
+ + StringMap
+ + Support WEB-INF/web-jetty.xml configuration extension for webapps
+ + Updated sponsors page
+ + URI canonicalPath
+ + URI pathAdd
 
 jetty-3.1.rc7 - 09 August 2001
- + Fix bug in sendRedirect for HTTP/1.1
  + Added doco for Linux port redirection.
- + Don't persist connections if low on threads.
- + Added shutdown hooks to Jetty.Server to trap Ctl-C
- + Fixed bug with session ID generation.
  + Added FORM authentication.
- + Remove old context path specs
+ + Added method handling to HTAccessHandler.
+ + Added shutdown hooks to Jetty.Server to trap Ctl-C
  + Added UML diagrams to Jetty architecture documentation.
- + Use Enumerations to reduce conversions for servlet API.
- + Optimized HttpField handling to reduce object creatiyon.
- + ServletRequest SSL attributes in line with 2.2 and 2.3 specs.
+ + Added utility methods to ServletHandler for wrapping req/res pairs.
+ + Don't persist connections if low on threads.
  + Dump Servlet displays cert chains
+ + Fix bug in sendRedirect for HTTP/1.1
+ + Fixed bug with session ID generation.
  + Fixed redirect handling by the CGI Servlet.
  + Fixed request.getPort for redirections from 80
- + Added utility methods to ServletHandler for wrapping req/res pairs.
- + Added method handling to HTAccessHandler.
+ + Optimized HttpField handling to reduce object creatiyon.
+ + Remove old context path specs
+ + ServletRequest SSL attributes in line with 2.2 and 2.3 specs.
  + ServletResponse.sendRedirect puts URLs into absolute format.
+ + Use Enumerations to reduce conversions for servlet API.
 
 jetty-3.1.rc6 - 10 July 2001
+ + Added Client authentication to the JsseListener
+ + Added debug and logging config example to demo.xml
+ + Added Get element to the XmlConfiguration class.
+ + Added getResource to HandleContext.
+ + Added Static calls to the XmlConfiguration class.
  + Avoid script vulnerability in error pages.
+ + Cleaned up destroy handling of listeners and contexts.
+ + Cleaned up Win32 Service server creation.
  + Close persistent HTTP/1.0 connections on missing Content-Length
- + Use exec for jetty.sh run
+ + Fixed a problem with Netscape and the acrobat plugin.
+ + Fixed bug in B64Code. Optimised B64Code.
+ + Fixed XmlParser to handle xerces1.3 OK
+ + Improved debug output for IOExceptions.
  + Improved SSL debugging information.
  + KeyPairTool can now load cert chains.
  + KeyPairTool is more robust to provider setup.
- + Fixed bug in B64Code. Optimised B64Code.
- + Added Client authentication to the JsseListener
- + Fixed a problem with Netscape and the acrobat plugin.
- + Improved debug output for IOExceptions.
- + Updated to JSSE-1.0.2, giving full strength crypto.
- + Win32 Service uses Jetty.Server instead of HttpServer.
- + Added getResource to HandleContext.
- + WebApps initialize resourceBase before start.
- + Fixed XmlParser to handle xerces1.3 OK
- + Added Get element to the XmlConfiguration class.
- + Added Static calls to the XmlConfiguration class.
- + Added debug and logging config example to demo.xml
+ + Moved gimp image files to Jetty3Extra
  + Moved mime types and encodings to property bundles.
+ + Removed getConfiguration from LifeCycleThread to avoid JMX clash.
  + RequestDispatch.forward() uses normal HandlerContext.handle() path if
    possible.
- + Cleaned up destroy handling of listeners and contexts.
- + Removed getConfiguration from LifeCycleThread to avoid JMX clash.
- + Cleaned up Win32 Service server creation.
- + Moved gimp image files to Jetty3Extra
+ + Updated to JSSE-1.0.2, giving full strength crypto.
+ + Use exec for jetty.sh run
+ + WebApps initialize resourceBase before start.
+ + Win32 Service uses Jetty.Server instead of HttpServer.
 
 jetty-3.1.rc5 - 01 May 2001
  + Added build target for mini.jetty.jar - see README.
- + Major restructing of packages to separate servlet dependancies. c.m.XML  -
-   moved XML dependant classes from c.m.Util c.m.HTTP - No servlet or XML
-   dependant classes: c.m.Jetty.Servlet - moved from c.m.HTTP.Handler.Servlet
-   c.m.Servlet - received some servlet dependant classes from HTTP.
- + Added UnixCrypt support to c.m.U.Password
  + Added HTaccessHandler to authenitcate against apache .htaccess files.
  + Added query param handling to ForwardHandler
  + Added ServletHandler().setUsingCookies().
- + Optimized canonical path calculations.
- + Warn and close connections if content-length is incorrectly set.
- + Request log contains bytes actually returned.
- + Fixed handling of empty responses at header commit.
- + Fixed ResourceHandler handling of ;JSESSIONID
+ + Added UnixCrypt support to c.m.U.Password
+ + Fixed EOF handling in MultiPartRequest.
  + Fixed forwarding to null pathInfo requests.
+ + Fixed handling of empty responses at header commit.
  + Fixed handling of multiple cookies.
- + Fixed EOF handling in MultiPartRequest.
- + Fixed sync of ThreadPool idleSet.
  + Fixed jetty.bat classpath problems.
+ + Fixed ResourceHandler handling of ;JSESSIONID
+ + Fixed sync of ThreadPool idleSet.
+ + Major restructing of packages to separate servlet dependancies. c.m.XML  -
+   moved XML dependant classes from c.m.Util c.m.HTTP - No servlet or XML
+   dependant classes: c.m.Jetty.Servlet - moved from c.m.HTTP.Handler.Servlet
+   c.m.Servlet - received some servlet dependant classes from HTTP.
+ + Optimized canonical path calculations.
+ + Request log contains bytes actually returned.
+ + Warn and close connections if content-length is incorrectly set.
 
 jetty-3.0.6 - 26 April 2001
+ + Fixed EOF handlding in MultiPartRequest.
+ + Fixed forwarding to null pathInfo requests.
  + Fixed handling of empty responses at header commit.
  + Fixed ResourceHandler handling of ;JSESSIONID
- + Fixed forwarding to null pathInfo requests.
- + Fixed EOF handlding in MultiPartRequest.
  + Fixed sync of ThreadPool idleSet.
  + Load-on-startup the JspServlet so that precompiled servlets work.
 
 jetty-3.1.rc4 - 14 April 2001
- + Include full versions of JAXP and Crimson
  + Added idle thread getter to ThreadPool.
+ + Include full versions of JAXP and Crimson
  + Load-on-startup the JspServlet so that precompiled servlets work.
  + Removed stray debug println from the Frame class.
 
 jetty-3.0.5 - 14 April 2001
  + Branched from 3.1 trunk to fix major errors
- + Fixed LineInput bug EOF
- + Improved flush ordering for forwarded requests.
- + Turned off range handling by default until bugs resolved
+ + Created better random session ID
  + Don't chunk if content length is known.
  + fixed getLocales handling of quality params
- + Created better random session ID
- + Resource handler strips URL params like JSESSION.
+ + Fixed LineInput bug EOF
  + Fixed session invalidation unbind notification to conform with spec
+ + Improved flush ordering for forwarded requests.
  + Load-on-startup the JspServlet so that precompiled servlets work.
+ + Resource handler strips URL params like JSESSION.
+ + Turned off range handling by default until bugs resolved
 
 jetty-3.1.rc3 - 09 April 2001
+ + Added ContentHandler Observer to XmlParser.
+ + Allow webapp XmlParser to be observed for ejb-ref tags etc.
+ + Cleaned up handling of exceptions thrown by servlets.
+ + Created better random session ID
+ + Frame handles more JIT stacks.
+ + Handle zero length POSTs
  + Implemented multi-part ranges so that acrobat is happy.
- + Simplified multipart response class.
  + Improved flush ordering for forwarded requests.
  + Improved ThreadPool stop handling
- + Frame handles more JIT stacks.
- + Cleaned up handling of exceptions thrown by servlets.
- + Handle zero length POSTs
+ + Simplified multipart response class.
  + Start session scavenger if needed.
- + Added ContentHandler Observer to XmlParser.
- + Allow webapp XmlParser to be observed for ejb-ref tags etc.
- + Created better random session ID
 
 jetty-3.1.rc2 - 30 March 2001
- + Lifecycle.start() may throw Exception
  + Added MultiException to throw multiple nested exceptions.
+ + added options to turn off ranges and chunking to support acrobat requests.
+ + fixed getLocales handling of quality params
+ + fixed getParameter(name) handling for multiple values.
+ + Improved handling of Primitive classes in XmlConfig
  + Improved logging of nested exceptions.
+ + Lifecycle.start() may throw Exception
  + Only one instance of default MIME map.
+ + Renamed getConnection to getHttpConnection
  + Use reference JAXP1.1 for XML parsing.y
  + Version 1.1 of configuration dtd supports New objects.
- + Improved handling of Primitive classes in XmlConfig
- + Renamed getConnection to getHttpConnection
- + fixed getLocales handling of quality params
- + fixed getParameter(name) handling for multiple values.
- + added options to turn off ranges and chunking to support acrobat requests.
 
 jetty-3.1.rc1 - 18 March 2001
- + Moved JMX and SASL handling to Jetty3Extra release
- + Fixed problem with ServletContext.getContext(uri)
  + Added Jetty documentation pages from JettyWiki
  + Cleaned up build.xml script
+ + Fixed problem with ServletContext.getContext(uri)
  + Minimal handling of Servlet.log before initialization.
- + Various SSL cleanups
+ + Moved JMX and SASL handling to Jetty3Extra release
  + Resource handler strips URL params like JSESSION.
+ + Various SSL cleanups
 
 jetty-3.1.rc0 - 23 February 2001
  + Added JMX management framework.
- + Use Thread context classloader as default context loader parent.
+ + Changed getter and setter methods that did not conform to beans API.
+ + Dynamic servlets may be restricted to Context classloader.
  + Fixed init order for unnamed servlets.
  + Fixed session invalidation unbind notification to conform with spec
  + Improved handling of primitives in utilities.
- + Socket made available via HttpConnection.
  + Improved InetAddrPort and ThreadedServer to reduce DNS lookups.
- + Dynamic servlets may be restricted to Context classloader.
  + Reoganized packages to allowed sealed Jars
- + Changed getter and setter methods that did not conform to beans API.
+ + Socket made available via HttpConnection.
+ + Use Thread context classloader as default context loader parent.
 
 jetty-3.0.4 - 23 February 2001
  + Fixed LineInput bug with split CRLF.
 
 jetty-3.0.3 - 03 February 2001
+ + Allow Log to be disabled before initialization.
+ + Fixed handling of directories without trailing /
  + Fixed pipelined request buffer bug.
  + Handle empty form content without exception.
- + Allow Log to be disabled before initialization.
- + Included new Jetty Logo
  + Implemented web.xml servlet mapping to a JSP
- + Fixed handling of directories without trailing /
+ + Included new Jetty Logo
 
 jetty-3.0.2 - 13 January 2001
- + Replaced ResourceHandler FIFO cache with LRU cache.
+ + Added etc/jetty.policy as example policy file.
+ + Allow '+' in path portion of a URL.
+ + Context specific security permissions.
  + Greatly improved buffering in ChunkableOutputStream
- + Padded error bodies for IE bug.
+ + Handle unknown status reasons in HttpResponse
+ + Ignore included response updates rather than IllegalStateException
  + Improved HTML.Block efficiency
  + Improved jetty.bat
  + Improved jetty.sh
- + Handle unknown status reasons in HttpResponse
- + Ignore included response updates rather than IllegalStateException
+ + Padded error bodies for IE bug.
  + Removed classloading stats which were causing circular class loading
    problems.
- + Allow '+' in path portion of a URL.
- + Try ISO8859_1 encoding if can't find ISO-8859-1
+ + Replaced ResourceHandler FIFO cache with LRU cache.
  + Restructured demo site pages.
- + Context specific security permissions.
- + Added etc/jetty.policy as example policy file.
+ + Try ISO8859_1 encoding if can't find ISO-8859-1
 
 jetty-3.0.1 - 20 December 2000
  + Fixed value unbind notification for session invalidation.
  + Removed double null check possibility from ServletHolder
 
 jetty-3.0.0 - 17 December 2000
- + Improved jetty.sh logging
- + Improved dtd resolution in XML parser.
- + Fixed taglib parsing
  + Fixed rel path handling in default configurations.
- + Optional extract war files.
+ + Fixed rollover bug in WriterLogSink
+ + Fixed taglib parsing
  + Fixed WriterLogSink init bug
+ + Improved dtd resolution in XML parser.
+ + Improved jetty.sh logging
+ + Optional extract war files.
  + Use inner class to avoid double null check sync problems
- + Fixed rollover bug in WriterLogSink
 
 jetty-3.0.0.rc8 - 13 December 2000
+ + Added ForwardHandler
+ + Change PathMap handling of /* to give precedence over suffix mapping.
+ + Default log options changed if in debug mode.
+ + Forward to welcome pages rather than redirect.
+ + getSecurityHandler creates handler at position 0.
+ + Improved exit admin handling
+ + Jetty.Server catches init exceptions per server
+ + Mapped *.jsp,*.jsP,*.jSp,*.jSP,*.Jsp,*.JsP,*.JSp,*.JSP
  + Optional alias checking added to FileResource.  Turned on by default on all
    platforms without the "/" file separator.
- + Mapped *.jsp,*.jsP,*.jSp,*.jSP,*.Jsp,*.JsP,*.JSp,*.JSP
- + Tidied handling of ".", ".." and "//" in resource paths
+ + Patched jasper to tomcat 3.2.1
  + Protected META-INF as well as WEB-INF in web applications.
- + Jetty.Server catches init exceptions per server
- + getSecurityHandler creates handler at position 0.
- + SysV unix init script
- + Improved exit admin handling
- + Change PathMap handling of /* to give precedence over suffix mapping.
- + Forward to welcome pages rather than redirect.
- + Removed special characters from source.
- + Default log options changed if in debug mode.
- + Removed some unused variables.
- + Added ForwardHandler
  + Removed security constraint on demo admin server.
- + Patched jasper to tomcat 3.2.1
+ + Removed some unused variables.
+ + Removed special characters from source.
+ + SysV unix init script
+ + Tidied handling of ".", ".." and "//" in resource paths
 
 jetty-3.0.0.rc7 - 02 December 2000
- + Fixed security problem with lowercase WEB-INF uris on windows.
- + Extended security constraints (see README and WebApp Demo).
- + Set thread context classloader during handler start/stop calls.
- + Don't set MIME-Version in response.
- + Allow dynamic servlets to be served from /
- + Handle multiple inits of same servlet class.
- + Auto add a NotFoundHandler if needed.
+ + Added Com.mortbay.HTTP.Handler.Servlet.Context.LogSink attribute to Servlet
+   Context. If set, it is used in preference to the system log.
  + Added NotFoundServlet
  + Added range handling to ResourceHandler.
+ + Allow dynamic servlets to be served from /
+ + Auto add a NotFoundHandler if needed.
  + CGI servlet handles not found better.
- + WEB-INF protected by NotFoundServlet rather than security constraint.
- + PUT, MOVE disabled in WebApplication unless defaults file is passed.
+ + Changed log options to less verbose defaults.
  + Conditionals apply to puts, dels and moves in ResourceHandler.
- + URIs accept all characters < 0xff.
- + Set the AcceptRanges header.
  + Depreciated RollOverLogSink and moved functionality to an improved
    WriterLogSink.
- + Changed log options to less verbose defaults.
- + ThreadedServer.forceStop() now makes a connection to itself to handle
-   non-premptive close.
+ + Don't set MIME-Version in response.
  + Double null lock checks use ThreadPool.__nullLockChecks.
+ + Extended security constraints (see README and WebApp Demo).
+ + Fixed security problem with lowercase WEB-INF uris on windows.
+ + Handle multiple inits of same servlet class.
+ + PUT, MOVE disabled in WebApplication unless defaults file is passed.
+ + Set the AcceptRanges header.
+ + Set thread context classloader during handler start/stop calls.
  + Split Debug servlet out of Admin Servlet.
- + Added Com.mortbay.HTTP.Handler.Servlet.Context.LogSink attribute to Servlet
-   Context. If set, it is used in preference to the system log.
+ + ThreadedServer.forceStop() now makes a connection to itself to handle
+   non-premptive close.
+ + URIs accept all characters < 0xff.
+ + WEB-INF protected by NotFoundServlet rather than security constraint.
 
 jetty-3.0.0.rc6 - 20 November 2000
- + RequestDispatcher.forward() only resets buffer, not headers.
  + Added ServletWriter that can be disabled.
- + Resource gets systemresources from it's own classloader.
- + don't include classes in release.
+ + Added Win32 service support
+ + Admin servlet uses unique links for IE.
+ + Allow HttpMessage state to be manipulated.
  + Allow load-on-startup with no content.
+ + Allow multiple set cookies.
+ + Corrected a few of the many spelling mistakes.
+ + don't include classes in release.
+ + Don't set connection:close for normal HTTP/1.0 responses.
+ + Don't start HttpServer log sink on add.
  + Fixed RollOverFileLogSink bug with extra log files.
+ + Implemented customizable error pages.
+ + Implemented resource aliases in HandlerContext - used by Servlet Context
  + Improved Log defaults
- + Don't start HttpServer log sink on add.
- + Admin servlet uses unique links for IE.
- + Added Win32 service support
+ + Javadoc improvements.
+ + Map tablib configuration to resource aliases.
+ + Prevent reloading dynamic servlets at different paths.
+ + Put extra server and servlet info in header.
  + Reduced risk of double null check sync problem.
- + Don't set connection:close for normal HTTP/1.0 responses.
+ + RequestDispatcher.forward() only resets buffer, not headers.
  + RequestDispatcher new queries params replace old.
+ + Resource gets systemresources from it's own classloader.
  + Servlet init order may be negative.
- + Corrected a few of the many spelling mistakes.
- + Javadoc improvements.
- + Webapps serve dynamics servlets by default.
- + Warn for missing WEB-INF or web.xml
- + Sessions try version 1 cookies in set-cookie2 header.
  + Session cookies are given context path
- + Put extra server and servlet info in header.
+ + Sessions try version 1 cookies in set-cookie2 header.
+ + Simple stats in ContextLoader.
  + Version details in header can be suppressed with System property
    java.com.mortbay.HTTP.Version.paranoid
- + Prevent reloading dynamic servlets at different paths.
- + Implemented resource aliases in HandlerContext - used by Servlet Context
- + Map tablib configuration to resource aliases.
- + Implemented customizable error pages.
- + Simple stats in ContextLoader.
- + Allow HttpMessage state to be manipulated.
- + Allow multiple set cookies.
+ + Warn for missing WEB-INF or web.xml
+ + Webapps serve dynamics servlets by default.
 
 jetty-3.0.0.rc5 - 12 November 2000
- + Default writer encoding set by mime type if not explicitly set.
- + Relax webapp rules, accept no web.xml or no WEB-INF
- + Pass flush through ServletOut
- + Avoid jprobe race warnings in DateCache
- + Allow null cookie values
- + Servlet exceptions cause 503 unavailable rather than 500 server error
- + RequestDispatcher can dispatch static resources.
- + Merged DynamicHandler into ServletHandler.
  + Added debug form to Admin servlet.
+ + Allow null cookie values
+ + Avoid jprobe race warnings in DateCache
+ + Default writer encoding set by mime type if not explicitly set.
  + Implemented servlet load ordering.
+ + Many javadoc cleanups.
+ + Merged DynamicHandler into ServletHandler.
  + Moved JSP classpath hack to ServletHolder
+ + Pass flush through ServletOut
+ + Relax webapp rules, accept no web.xml or no WEB-INF
  + Removed Makefile build system.
- + Many javadoc cleanups.
+ + RequestDispatcher can dispatch static resources.
+ + Servlet exceptions cause 503 unavailable rather than 500 server error
 
 jetty-2.4.9 - 12 November 2000
- + HttpListener ignore InterruptedIOExceptions
- + HttpListener default max idle time = 20s
  + HtmlFilter handles non default encodings
- + Writing HttpRequests encodes path
+ + HttpListener default max idle time = 20s
+ + HttpListener ignore InterruptedIOExceptions
  + HttpRequest.write uses ISO8859_1 encoding.
+ + Writing HttpRequests encodes path
 
 jetty-3.0.0.rc4 - 06 November 2000
- + Provide default JettyIndex.properties
- + Fixed mis-synchronization in ThreadPool.stop()
  + Fixed mime type mapping bug introduced in RC3
+ + Fixed mis-synchronization in ThreadPool.stop()
  + Ignore more IOExceptions (still visible with debug).
+ + Provide default JettyIndex.properties
 
 jetty-3.0.0.rc3 - 05 November 2000
- + Changed ThreadPool.stop for IBM 1.3 JVM
  + Added bin/jetty.sh run script.
- + upgraded build.xml to ant v1.2
- + Set MaxReadTimeMs in all examples
+ + Added context class path dynamic servlet demo
+ + Added gz tgz tar.gz .z mime mappings.
+ + Added HandlerContext.setHttpServerAccess for trusted contexts.
+ + Changed ThreadPool.stop for IBM 1.3 JVM
+ + Fixed default mimemap initialization bug
  + Further clean up of the connection close actions
+ + Handle mime suffixes containing dots.
+ + Implemented mime mapping in webapplications.
  + Moved unused classes from com.mortbay.Util to com.mortbay.Tools in new
    distribution package.
- + Handle mime suffixes containing dots.
- + Added gz tgz tar.gz .z mime mappings.
- + Fixed default mimemap initialization bug
  + Optimized persistent connections by recycling objects
- + Added HandlerContext.setHttpServerAccess for trusted contexts.
- + Set the thread context class loader in HandlerContext.handle
  + Prevent servlet setAttribute calls to protected context attributes.
  + Removed redundant context attributes.
- + Implemented mime mapping in webapplications.
+ + Set MaxReadTimeMs in all examples
+ + Set the thread context class loader in HandlerContext.handle
  + Strip ./ from relative resources.
- + Added context class path dynamic servlet demo
+ + upgraded build.xml to ant v1.2
 
 jetty-3.0.0.rc2 - 29 October 2000
- + Replaced ISO-8859-1 literals with StringUtil static
- + Pass file based classpath to JspServlet (see README).
- + Prevented multiple init of ServletHolder
- + ErlEncoding treats params without values as empty rather than null.
+ + Accept HTTP/1. as HTTP/1.0 (for netscape bug).
  + Accept public DTD for XmlConfiguration (old style still supported).
  + Cleaned up non persistent connection close.
- + Accept HTTP/1. as HTTP/1.0 (for netscape bug).
+ + ErlEncoding treats params without values as empty rather than null.
  + Fixed thread name problem in ThreadPool
+ + Pass file based classpath to JspServlet (see README).
+ + Prevented multiple init of ServletHolder
+ + Replaced ISO-8859-1 literals with StringUtil static
 
 jetty-3.0.0.rc1 - 22 October 2000
- + Added simple admin servlet.
  + Added CGI to demo
  + Added HashUserRealm and cleaned up security constraints
  + Added Multipart request and response classes from Jetty2
- + Moved and simplified ServletLoader to ContextLoader.
- + Initialize JSP with classloader.
+ + Added simple admin servlet.
  + All attributes in javax. java. and com.mortbay. name spaces to be set.
+ + Cleaned up exception handling.
+ + Initialize JSP with classloader.
+ + Moved and simplified ServletLoader to ContextLoader.
  + Partial handling of 0.9 requests.
  + removed Thread.destroy() calls.
- + Cleaned up exception handling.
 
 jetty-2.4.8 - 23 October 2000
  + Fixed bug with 304 replies with bodies.
- + Improved win32 make files.
  + Fixed closing socket problem
+ + Improved win32 make files.
 
 jetty-3.0.B05 - 18 October 2000
- + Improved null returns to get almost clean watchdog test.
+ + Added default webapp servlet mapping /servlet/name/*
  + Cleaned up response committing and flushing
+ + Fixed JarFileResource to handle jar files without directories.
  + Handler RFC2109 cookies (like any browser handles them!)
- + Added default webapp servlet mapping /servlet/name/*
- + Improved path spec interpretation by looking at 2.3 spec
  + Implemented security-role-ref for servlets
- + Protected servletConfig from downcast security problems
- + Made test harnesses work with ant.
- + improved ant documentation.
- + Removed most deprecation warnings
- + Fixed JarFileResource to handle jar files without directories.
  + Implemented war file support
- + Java2 style classloading
+ + improved ant documentation.
  + Improved default log format for clarity.
+ + Improved null returns to get almost clean watchdog test.
+ + Improved path spec interpretation by looking at 2.3 spec
+ + Java2 style classloading
+ + Made test harnesses work with ant.
+ + Protected servletConfig from downcast security problems
+ + Removed most deprecation warnings
  + Separated context attributes and initParams.
 
 jetty-3.0.B04 - 12 October 2000
- + Restricted context mapping to simple model for servlets.
- + Fixed problem with session ID in paths
  + Added modified version of JasperB3.2 for JSP
- + Moved FileBase to docroot
- + Merged and renamed third party jars.
+ + Added webdefault.xml for web applications.
  + Do not try multiple servlets for a request.
+ + Filthy hack to teach jasper JspServer Jetty classpath
+ + Fixed problem with session ID in paths
  + Implemented Context.getContext(uri)
- + Added webdefault.xml for web applications.
+ + Merged and renamed third party jars.
+ + Moved FileBase to docroot
  + Redirect to index files, so index.jsp works.
- + Filthy hack to teach jasper JspServer Jetty classpath
+ + Restricted context mapping to simple model for servlets.
 
 jetty-3.0.B03 - 09 October 2000
+ + Added append mode in RolloverFileLogSink
+ + Added release script
+ + Catch stop and destroy exceptions in HttpServer.stop()
  + Expanded import package.*; lines
  + Expanded leading tabs to spaces
+ + Handle ignorable spaces in WebApplication
+ + Handle ignorable spaces in XmlConfiguration
+ + Implemented request dispatching.
  + Improved Context to Handler contract.
- + Parse but not handler startup ordering in web applications.
- + Send request log via a LogSink
- + Added append mode in RolloverFileLogSink
- + Made LogSink a Lifecycle interface
  + Improved handler toString
- + Redirect context only paths.
+ + Improved Log rollover.
+ + Made LogSink a Lifecycle interface
+ + Parse but not handler startup ordering in web applications.
  + Pass object to LogSink
- + Implemented request dispatching.
+ + Redirect context only paths.
  + Redo dynamic servlets handling
- + Improved Log rollover.
- + Simplified path translation and real path calculation.
- + Catch stop and destroy exceptions in HttpServer.stop()
- + Handle ignorable spaces in XmlConfiguration
- + Handle ignorable spaces in WebApplication
- + Warn about explicit sets of WebApplication
  + Remove 411 checks as IE breaks this rule after redirect.
  + Removed last remnants JDK 1.1 support
- + Added release script
+ + Send request log via a LogSink
+ + Simplified path translation and real path calculation.
+ + Warn about explicit sets of WebApplication
 
 jetty-2.4.7 - 06 October 2000
- + Allow Objects to be passed to LogSink
- + Set content length on errors for keep alive.
  + Added encode methods to URI
- + Improved win32 build
+ + Allow Objects to be passed to LogSink
  + fixes to SSL doco
+ + Improved win32 build
+ + Set content length on errors for keep alive.
  + Support key and keystore passwords
  + Various improvements to  ServletDispatch, PropertyTree and associated
    classes.
 
 jetty-3.0.B02 - 24 August 2000
- + Fixed LineInput bug with SSL giving CR pause LF.
- + Fixed HTTP/1.0 input close bug
+ + Added CGI servlet
  + Fixed bug in TestRFC2616
+ + Fixed HTTP/1.0 input close bug
+ + Fixed LineInput bug with SSL giving CR pause LF.
  + Improved ThreadedServer stop and destroy
  + Use resources in WebApplication
- + Added CGI servlet
 
 jetty-3.0.B01 - 21 August 2000
- + SSL implemented with JsseListener
- + Partial implementation of webapp securitycontraints
  + Implemented more webapp configuration
+ + Partial implementation of webapp securitycontraints
+ + SSL implemented with JsseListener
  + Switched to the aelfred XML parser from microstar, which is only partially
    validating, but small and lightweight
 
 jetty-2.4.6 - 16 August 2000
- + Turn Linger off before closing sockets, to allow restart.
- + JsseListener & SunJsseListener added and documented
+ + Added passive mode methods to FTP
  + com.mortbay.Util.KeyPairTool added to handle openSSL SSL keys.
+ + JsseListener & SunJsseListener added and documented
  + Minor changes to compile with jikes.
- + Added passive mode methods to FTP
+ + Turn Linger off before closing sockets, to allow restart.
 
 jetty-3.0.A99 - 10 August 2000
- + Implemented jetty.xml configuration
- + Added Xmlconfiguration utility
- + ServletLoader simplied and uses ResourcePath
- + Replaced FileHandler with ResourceHandler
- + Use SAX XML parsing instead of DOM for space saving.
- + Removed FileBase. Now use ResourceBase instead
  + Added Resource abstraction
+ + Added Xmlconfiguration utility
+ + Implemented jetty.xml configuration
  + Make it compile cleanly with jikes.
  + Re-added commented out imports for JDK-1.1 compile
+ + Removed FileBase. Now use ResourceBase instead
+ + Replaced FileHandler with ResourceHandler
+ + ServletLoader simplied and uses ResourcePath
+ + Use SAX XML parsing instead of DOM for space saving.
 
 jetty-3.0.A98 - 20 July 2000
+ + Allow HttpRequest.toString() handles bad requests.
+ + Fixed constructor to RolloverFileLogSink
  + Implemented Jetty demos and Site as Web Application.
  + Implemented WebApplicationContext
- + Switched to JDK1.2 only
- + ServletRequest.getServerPort() returns 80 rather than 0
- + Fixed constructor to RolloverFileLogSink
  + Improved synchronization on LogSink
- + Allow HttpRequest.toString() handles bad requests.
+ + ServletRequest.getServerPort() returns 80 rather than 0
+ + Switched to JDK1.2 only
 
 jetty-3.0.A97 - 13 July 2000
- + Tempory request log implementation
- + Less verbose debug
- + Better tuned SocketListener parameters
- + Started RequestDispatcher implementation.
+ + Added error handling to LifeCycleThread
  + Added WML mappings
+ + Better tuned SocketListener parameters
  + Fixed makefiles for BSD ls
  + Fixed persistent commits with no content (eg redirect+keep-alive).
- + Implemented servlet isSecure().
- + Implemented servlet getLocale(s).
  + Formatted version in server info string.
- + Protect setContentLength from a late set in default servlet HEAD handling.
- + Added error handling to LifeCycleThread
  + implemented removeAttribute on requests
+ + Implemented servlet getLocale(s).
+ + Implemented servlet isSecure().
+ + Less verbose debug
+ + Protect setContentLength from a late set in default servlet HEAD handling.
+ + Started RequestDispatcher implementation.
+ + Tempory request log implementation
 
 jetty-2.4.5 - 09 July 2000
- + Don't mark a session invalid until after values unbound.
- + Formatted version in server info.
  + Added HtmlExpireFilter and removed response cache revention from HtmlFilter.
+ + Don't mark a session invalid until after values unbound.
  + Fixed transaction handling in JDBC wrappers
+ + Formatted version in server info.
 
 jetty-3.0.A96 - 27 June 2000
  + Fixed bug with HTTP/1.1 Head reqests to servlets.
@@ -5560,217 +7541,217 @@ jetty-3.0.A95 - 24 June 2000
  + Handle spaces in file names in FileHandler.
 
 jetty-3.0.A94 - 19 June 2000
- + Implemented Sessions.
- + PathMap exact matches can terminate with ; or # for URL sessions and
-   targets.
  + Added HandlerContext to allow grouping of handlers into units with the same
    file, resource and class configurations.
  + Cleaned up commit() and added complete() to HttpResponse
+ + Implemented Sessions.
+ + PathMap exact matches can terminate with ; or # for URL sessions and
+   targets.
  + Updated license to clarify that commercial usage IS OK!
 
 jetty-3.0.A93 - 14 June 2000
- + Major rethink! Moved to 2.2 servlet API
  + Lots of changes and probably unstable
+ + Major rethink! Moved to 2.2 servlet API
 
 jetty-3.0.A92 - 07 June 2000
  + Added HTML classes to jar
  + Fixed redirection bug in FileHandler
 
 jetty-2.4.4 - 03 June 2000
- + Many debug call optimizations
+ + Added build-win32.mak
+ + Added HTML.Composite.replace
  + Added RolloverFileLogSink
- + Improved LogSink configuration
- + Support System.property expansions in PropertyTrees.
  + Added uk.org.gosnell.Servlets.CgiServlet to contrib
- + HttpRequest.setRequestPath does not null pathInfo.
  + BasicAuthHandler uses getResourcePath so it can be used behind request
    dispatching
- + Added HTML.Composite.replace
  + FileHandler implements IfModifiedSince on index files.
- + Added build-win32.mak
+ + HttpRequest.setRequestPath does not null pathInfo.
+ + Improved LogSink configuration
+ + Many debug call optimizations
+ + Support System.property expansions in PropertyTrees.
 
 jetty-3.0.A91 - 03 June 2000
- + Improved LogSink mechanism
- + Implemented realPath and getResource methods for servlets.
  + Abstracted ServletHandler
- + Simplified HttpServer configuration methods and arguments
- + Simplified class loading
  + Added HTML classes from Jetty2
+ + Implemented realPath and getResource methods for servlets.
+ + Improved LogSink mechanism
+ + Simplified class loading
+ + Simplified HttpServer configuration methods and arguments
 
 jetty-3.0.A9 - 07 May 2000
- + Improvided finally handling of output end game.
- + Fixed double chunking bug in SocketListener.
  + File handler checks modified headers on directory indexes.
+ + Fixed double chunking bug in SocketListener.
+ + Improvided finally handling of output end game.
  + ServletLoader tries unix then platform separator for zip separator.
 
 jetty-3.0.A8 - 04 May 2000
- + Servlet2_1 class loading re-acrchitected. See README.
- + Moved Sevlet2_1 handler to com.mortbay.Servlet2_1
  + addCookie takes an int maxAge rather than a expires date.
  + Added LogSink extensible log architecture.
- + Code.ignore only outputs when debug is verbose.
  + Added Tenlet class for reverse telnet.
+ + Code.ignore only outputs when debug is verbose.
+ + Moved Sevlet2_1 handler to com.mortbay.Servlet2_1
+ + Servlet2_1 class loading re-acrchitected. See README.
 
 jetty-2.4.3 - 04 May 2000
- + Pass Cookies with 0 max age to browser.
  + Allow CRLF in UrlEncoded
+ + Pass Cookies with 0 max age to browser.
 
 jetty-2.4.2 - 23 April 2000
+ + Added GNUJSP to JettyServer.prp file.
  + Added LogSink and FileLogSink classes to allow extensible Log handling.
  + Handle nested RequestDispatcher includes.
  + Modified GNUJSP to prevent close in nested requests.
- + Added GNUJSP to JettyServer.prp file.
 
 jetty-3.0.A7 - 15 April 2000
- + Include java 1.2 source hierarchy
- + removed excess ';' from source
- + fixed flush problem with chunked output for IE5
  + Added InetGateway to help debug IE5 problems
  + added removeValue method to MultiMap
+ + fixed flush problem with chunked output for IE5
+ + Include java 1.2 source hierarchy
+ + removed excess ';' from source
 
 jetty-2.4.1 - 09 April 2000
+ + Fixed bug in HtmlFilter for tags split between writes.
  + Removed debug println from ServletHolder.
  + Set encoding before exception in FileHandler.
- + Fixed bug in HtmlFilter for tags split between writes.
 
 jetty-3.0.A6 - 09 April 2000
- + Integrated skeleton 2.1 Servlet container
- + Improved portability of Frame and Debug.
+ + added bin/useJava2Collections to convert to JDK1.2
  + Dates forced to use US locale
+ + Improved portability of Frame and Debug.
+ + Integrated skeleton 2.1 Servlet container
  + Removed Converter utilities and InetGateway.
- + added bin/useJava2Collections to convert to JDK1.2
 
 jetty-2.4.0 - 24 March 2000
- + Upgraded to gnujsp 1.0.0
- + Added per servlet resourceBase configuration.
  + Absolute URIs are returned by getRequestURI (if sent by browser).
- + Improved parsing of stack trace in debug mode.
- + Implemented full handling of cookie max age.
- + Moved SetUID native code to contrib hierarchy
- + Form parameters only decoded for POSTs
- + RequestDispatcher handles URI parameters
+ + Added doc directory with a small start
+ + Added per servlet resourceBase configuration.
+ + Added VirtualHostHandler for virtual host handling
  + Fixed bug with RequestDispatcher.include()
  + Fixed caste problem in UrlEncoded
  + Fixed null pointer in ThreadedServer with stopAll
- + Added VirtualHostHandler for virtual host handling
- + Added doc directory with a small start
+ + Form parameters only decoded for POSTs
+ + Implemented full handling of cookie max age.
+ + Improved parsing of stack trace in debug mode.
+ + Moved SetUID native code to contrib hierarchy
+ + RequestDispatcher handles URI parameters
+ + Upgraded to gnujsp 1.0.0
 
 jetty-2.3.5 - 25 January 2000
- + Fixed nasty bug with HTTP/1.1 redirects.
- + ProxyHandler sends content for POSTs etc.
- + Force locale of date formats to US.
- + Fixed expires bug in Cookies
  + Added configuration option to turn off Keep-Alive in HTTP/1.0
+ + Added contrib/com/kiwiconsulting/jetty JSSE SSL adaptor to release.
  + Allow configured servlets to be auto reloaded.
  + Allow properties to be configured for dynamic servlets.
- + Added contrib/com/kiwiconsulting/jetty JSSE SSL adaptor to release.
+ + Fixed expires bug in Cookies
+ + Fixed nasty bug with HTTP/1.1 redirects.
+ + Force locale of date formats to US.
+ + ProxyHandler sends content for POSTs etc.
 
 jetty-2.3.4 - 18 January 2000
- + include from linux rather than genunix for native builds
- + Fixed IllegalStateException handling in DefaultExceptionHandler
- + MethodTag.invoke() is now public.
- + Improved HtmlFilter.activate header modifications.
  + Cookie map keyed on domain as well as name and path.
  + DictionaryConverter handles null values.
- + URI decodes applies URL decoding to the path.
- + Servlet properties allow objects to be stored.
+ + Fixed IllegalStateException handling in DefaultExceptionHandler
  + Fixed interaction with resourcePaths and proxy demo.
+ + Improved HtmlFilter.activate header modifications.
+ + include from linux rather than genunix for native builds
+ + MethodTag.invoke() is now public.
+ + Servlet properties allow objects to be stored.
+ + URI decodes applies URL decoding to the path.
 
 jetty-3.0.A5 - 19 October 1999
- + Use ISO8859_1 instead of UTF8 for headers etc.
- + Use char array in UrlEncoded.decode
  + Do our own URL string encoding with 8859-1
  + Replaced LF wait in LineInput with state boolean.
+ + Use char array in UrlEncoded.decode
+ + Use ISO8859_1 instead of UTF8 for headers etc.
 
 jetty-2.3.3 - 19 October 1999
- + Replaced UTF8 encoding with ISO-8859-1 for headers.
- + Use UrlEncoded for form parameters.
  + Do our own URL encoding with ISO-8859-1
  + HTTP.HTML.EmbedUrl uses contents encoding.
+ + Replaced UTF8 encoding with ISO-8859-1 for headers.
+ + Use UrlEncoded for form parameters.
 
 jetty-2.3.2 - 17 October 1999
  + Fixed getReader bug with HttpRequest.
  + Updated UrlEncoded with Jetty3 version.
 
 jetty-3.0.A4 - 16 October 1999
- + Request attributes
- + Basic Authentication Handler.
  + Added LF wait after CR to LineInput.
+ + Basic Authentication Handler.
+ + Request attributes
  + UTF8 in UrlDecoded.decodeString.
 
 jetty-2.3.1 - 14 October 1999
+ + Added assert with no message to Code
+ + Added Oracle DB adapter
+ + Changed demo servlets to use writers in preference to outputstreams
+ + Fixed GNUJSP 1.0 resource bug.
  + Force UTF8 for FTP commands
  + Force UTF8 for HTML
- + Changed demo servlets to use writers in preference to outputstreams
+ + HTTP/1.0 Keep-Alive (about time!).
  + NullHandler/Server default name.name.PROPERTIES to load
    prefix/name.name.properties
- + Use UTF8 in HTTP headers
- + Added Oracle DB adapter
- + Added assert with no message to Code
+ + Prevented thread churn on idle server.
  + ThreadedServer calls setSoTimeout(_maxThreadIdleMs) on accepted sockets.
    Idle reads will timeout.
- + Prevented thread churn on idle server.
- + HTTP/1.0 Keep-Alive (about time!).
- + Fixed GNUJSP 1.0 resource bug.
+ + Use UTF8 in HTTP headers
 
 jetty-3.0.A3 - 14 October 1999
  + Added LifeCycle interface to Utils implemented by ThreadPool,
    ThreadedServer, HttpListener & HttpHandler
- + StartAll, stopAll and destroyAll methods added to HttpServer.
- + MaxReadTimeMs added to ThreadedServer.
  + Added service method to HttpConnection for specialization.
+ + MaxReadTimeMs added to ThreadedServer.
+ + StartAll, stopAll and destroyAll methods added to HttpServer.
 
 jetty-3.0.A2 - 13 October 1999
- + UTF8 handling on raw output stream.
- + Reduced flushing on writing response.
- + Fixed LineInput problem with repeated CRs
- + Cleaned up Util TestHarness.
- + Prevent entity content for responses 100-199,203,304
  + Added cookie support and demo.
+ + Cleaned up Util TestHarness.
+ + Fixed LineInput problem with repeated CRs
+ + HEAD handling.
  + HTTP/1.0 Keep-alive (about time!)
- + Virtual Hosts.
  + NotFound Handler
  + OPTION * Handling.
+ + Prevent entity content for responses 100-199,203,304
+ + Reduced flushing on writing response.
  + TRACE handling.
- + HEAD handling.
+ + UTF8 handling on raw output stream.
+ + Virtual Hosts.
 
 jetty-3.0.A1 - 12 October 1999
- + LineInput uses own buffering and uses character encodings.
+ + Added HttpHandler interface with start/stop/destroy lifecycle
  + Added MultiMap for common handling of multiple valued parameters.
  + Added parameters to HttpRequest
- + Quick port of FileHandler
- + Setup demo pages.
  + Added PathMap implementing mapping as defined in the 2.2 API specification
    (ie. /exact, /prefix/*, *.extention & default ).
- + Added HttpHandler interface with start/stop/destroy lifecycle
- + Updated HttpListener is start/stop/destroy lifecycle.
  + Implemented simple extension architecture in HttpServer.
+ + LineInput uses own buffering and uses character encodings.
+ + Quick port of FileHandler
+ + Setup demo pages.
+ + Updated HttpListener is start/stop/destroy lifecycle.
 
 jetty-3.0.A0 - 09 October 1999
- + Started fresh repository in CVS
- + Moved com.mortbay.Base classes to com.mortbay.Util
- + Cleanup of UrlEncoded, using 1.2 Collections.
- + Cleanup of URI, using 1.2 Collections.
- + Extended URI to handle absolute URLs
- + Cleanup of LineInput, using 1.2 Collections.
- + Moved HttpInput/OutputStream to ChunkableInput/OutputStream.
- + Cleaned up chunking code to use LineInput and reduce buffering.
- + Added support for transfer and content encoding filters.
- + Added support for servlet 2.2 outbut buffer control.
- + Generalized notification of outputStream events.
- + Split HttpHeader into HttpFields and HttpMessage.
- + HttpMessage supports chunked trailers.
- + HttpMessage supports message states.
  + Added generalized HTTP Connection.
- + Cleanup of HttpRequest and decoupled from Servlet API
+ + Added support for servlet 2.2 outbut buffer control.
+ + Added support for transfer and content encoding filters.
+ + Cleaned up chunking code to use LineInput and reduce buffering.
  + Cleanup and abstraction of ThreadPool.
- + ThreadedServer based on ThreadPool.
+ + Cleanup of HttpRequest and decoupled from Servlet API
  + Cleanup of HttpResponse and decoupled from Servlet API
+ + Cleanup of LineInput, using 1.2 Collections.
+ + Cleanup of URI, using 1.2 Collections.
+ + Cleanup of UrlEncoded, using 1.2 Collections.
  + Created RFC2616 test harness.
+ + Extended URI to handle absolute URLs
+ + Generalized notification of outputStream events.
  + gzip and deflate request transfer encodings
- + TE field coding and trailer handler
  + HttpExceptions now produce error pages with specific detail of the
    exception.
+ + HttpMessage supports chunked trailers.
+ + HttpMessage supports message states.
+ + Moved com.mortbay.Base classes to com.mortbay.Util
+ + Moved HttpInput/OutputStream to ChunkableInput/OutputStream.
+ + Split HttpHeader into HttpFields and HttpMessage.
+ + Started fresh repository in CVS
+ + TE field coding and trailer handler
+ + ThreadedServer based on ThreadPool.
 
 jetty-2.3.0 - 05 October 1999
  + Added SetUID class with native Unix call to set the effective User ID.
@@ -5778,227 +7759,227 @@ jetty-2.3.0 - 05 October 1999
  + FTP uses InetAddress of command socket for data socket.
 
 jetty-2.3.0A - 22 September 1999
- + Added GNUJSP 1.0 for the JSP 1.0 API.
- + Use javax.servlet classes from JWSDK1.0
  + Added "Powered by Jetty" button.
- + ServerContext available to HtmlFilters via context param
- + Made session IDs less predictable and removed race.
  + Added BuildJetty.java file.
+ + Added GNUJSP 1.0 for the JSP 1.0 API.
  + Expanded tabs to spaces in source.
+ + Made session IDs less predictable and removed race.
+ + ServerContext available to HtmlFilters via context param
+ + Use javax.servlet classes from JWSDK1.0
 
 jetty-2.2.8 - 15 September 1999
- + Fixed bug in Element.attribute with empty string values.
- + Made translation of getRequestURI() optional.
- + Removed recursion from TranslationHandler
  + Added disableLog() to turn off logging.
  + Allow default table attributes to be overriden.
+ + Fixed bug in Element.attribute with empty string values.
  + Improved quoting in HTML element values
+ + Made translation of getRequestURI() optional.
+ + Removed recursion from TranslationHandler
 
 jetty-2.2.7 - 09 September 1999
- + Reverted semantics of getRequestURI() to return untranslated URI.
- + Added GzipFilter for content encoding.
  + Added default row, head and cell elements to Table.
+ + Added GzipFilter for content encoding.
  + FileHandler passes POST request through if the file does not exist.
+ + Reverted semantics of getRequestURI() to return untranslated URI.
 
 jetty-2.2.6 - 05 September 1999
- + New implementation of ThreadPool, avoids a thread leak problem.
- + Fixed Cookie max age order of magnitude bug.
+ + Added destroy() method on all HttpHandlers.
+ + Added ServletRunnerHandler to the contrib directories.
+ + Allow the handling of getPathTranslated to be configured in ServletHandler.
+ + class StyleLink added.
  + Cookies always available from getCookies.
  + Cookies parameter renamed to CookiesAsParameters
+ + cssClass, cssID and style methods added to element.
+ + FileHandler does not server files ending in '/'
+ + Fixed Cookie max age order of magnitude bug.
  + HttpRequest.getSession() always returns a session as per the latest API
    spec.
- + Added destroy() method on all HttpHandlers.
- + ServletHandler.destroy destroys all servlets.
- + FileHandler does not server files ending in '/'
  + Ignore duplicate single valued headers, rather than reply with bad request,
    as IE4 breaks the rules.
- + Allow the handling of getPathTranslated to be configured in ServletHandler.
+ + media added to Style
+ + New implementation of ThreadPool, avoids a thread leak problem.
  + Removed JRUN options from ServletHandler configuration.
- + Added ServletRunnerHandler to the contrib directories.
- + Updated HTML package to better support CSS:
- + cssClass, cssID and style methods added to element.
+ + ServletHandler.destroy destroys all servlets.
  + SPAN added to Block
- + media added to Style
- + class StyleLink added.
+ + Updated HTML package to better support CSS:
 
 jetty-2.2.5 - 19 August 1999
- + Fixed bug with closing connections in ThreadedServer
- + Made start and stop non final in ThreadedServer
- + Better default handling of ServletExceptions
  + Always close connection after a bad request.
- + Set Expires header in HtmlFilter.
- + Don't override the cookie as parameter option.
- + Limited growth in MultiPartResponse boundary.
- + Improved error messages from Jetty.Server.
+ + Better default handling of ServletExceptions
  + Close loaded class files so Win32 can overwrite them before GC (what a silly
    file system!).
+ + Don't override the cookie as parameter option.
+ + Fixed bug with closing connections in ThreadedServer
+ + Improved error messages from Jetty.Server.
+ + Limited growth in MultiPartResponse boundary.
+ + Made start and stop non final in ThreadedServer
+ + Set Expires header in HtmlFilter.
 
 jetty-2.2.4 - 02 August 1999
- + ThreadedServer can use subclasses of Thread.
  + Better help on Jetty.Server
- + HttpRequests may be passed to HttpFilter constructors.
- + HtmlFilter blanks IfModifiedSince headers on construction
  + Fixed bugs in HtmlFilter parser and added TestHarness.
+ + HtmlFilter blanks IfModifiedSince headers on construction
+ + HttpRequests may be passed to HttpFilter constructors.
  + Improved cfg RCS script.
+ + ThreadedServer can use subclasses of Thread.
 
 jetty-2.2.3 - 27 July 1999
+ + Added stop call to HttpServer, used by Exit Servlet.
+ + FileHandler defaults to allowing directory access.
  + Fixed parser bug in HtmlFilter
- + Made setInitialize public in ServletHolder
  + Improved performance of com.mortbay.HTML.Heading
- + Added stop call to HttpServer, used by Exit Servlet.
+ + JDBC tests modified to use cloudscape as DB.
+ + Made setInitialize public in ServletHolder
  + Simplified JDBC connection handling so that it works with Java1.2 - albeit
    less efficiently.
- + FileHandler defaults to allowing directory access.
- + JDBC tests modified to use cloudscape as DB.
 
 jetty-2.2.2 - 22 July 1999
+ + File handler passes through not allowed options for non existant files.
+ + Fixed bug in com.mortbay.Util.IO with thread routines.
  + Fixed bug in HtmlFilter that prevented single char buffers from being
    written.
- + Implemented getResourceAsStream in FileJarServletLoader
  + Fixed bug with CLASSPATH in FileJarServletLoader after attempt to load from
    a jar.
- + Fixed bug in com.mortbay.Util.IO with thread routines.
- + Moved more test harnesses out of classes.
- + File handler passes through not allowed options for non existant files.
- + NotFoundHandler can repond with SC_METHOD_NOT_ALLOWED.
+ + Implemented getResourceAsStream in FileJarServletLoader
  + Improved com.mortbay.Base.Log handling of different JVMs
  + Minor fixes to README
+ + Moved more test harnesses out of classes.
+ + NotFoundHandler can repond with SC_METHOD_NOT_ALLOWED.
 
 jetty-2.2.1 - 18 July 1999
+ + Added optional resourceBase property to HttpConfiguration. This is used as a
+   URL prefix in the getResource API and was suggested by the JSERV and Tomcat
+   implementors.
+ + Added TerseExceptionHandler
  + Comma separate header fields.
- + Protect against duplicate single valued headers.
- + Less verbose debug in PropertyTree
- + Ignore IOException in ThreadedServer.run() when closing.
- + Limit maximum line length in HttpInputStream.
- + Response with SC_BAD_REQUEST rather than close in more circumstances
- + Handle continuation lines in HttpHeader.
- + HtmlFilter resets last-modified and content-length headers.
- + Implemented com.mortbay.Util.IO as a ThreadPool
  + Decoupled ExceptionHandler configuration from Handler stacks. Old config
    style will produce warning and Default behavior. See new config file format
    for changes.
- + Added TerseExceptionHandler
- + Added optional resourceBase property to HttpConfiguration. This is used as a
-   URL prefix in the getResource API and was suggested by the JSERV and Tomcat
-   implementors.
+ + Handle continuation lines in HttpHeader.
+ + HtmlFilter resets last-modified and content-length headers.
+ + Ignore IOException in ThreadedServer.run() when closing.
+ + Implemented com.mortbay.Util.IO as a ThreadPool
+ + Less verbose debug in PropertyTree
+ + Limit maximum line length in HttpInputStream.
+ + Protect against duplicate single valued headers.
+ + Response with SC_BAD_REQUEST rather than close in more circumstances
 
 jetty-2.2.0 - 01 July 1999
- + Improved feature description page.
  + Added Protekt SSL HttpListener
- + Moved GNUJSP and Protekt listener to a contrib hierarchy.
- + ThreadedServer.stop() closes socket before interrupting threads.
  + Exit servlet improved (a little).
  + Fixed some of the javadoc formatting.
+ + Improved feature description page.
+ + Moved GNUJSP and Protekt listener to a contrib hierarchy.
+ + ThreadedServer.stop() closes socket before interrupting threads.
 
 jetty-2.2.Beta4 - 29 June 1999
- + FileHandler flushes files from cache in DELETE method.
- + ThreadedServer.stop() now waits until all threads are stopped.
- + Options "allowDir" added to FileHandler.
- + Added getGlobalProperty to Jetty.Server and used this to configure default
-   page type.
- + Updated README.txt
- + Restructured com.mortbay.Jetty.Server for better clarity and documentation.
  + Added comments to configuration files.
- + Made ServerSocket and accept call generic in ThreadedServer for SSL
-   listeners.
- + Altered meaning of * in PropertyTree to assist in abbreviated configuration
-   files.
+ + Added getGlobalProperty to Jetty.Server and used this to configure default
+   page type.
  + Added JettyMinimalDemo.prp as an example of an abbreviated configuration.
- + Expanded Mime.prp file
  + Added property handling to ServletHandler to read JRUN servlet configuration
    files.
+ + Altered meaning of * in PropertyTree to assist in abbreviated configuration
+   files.
+ + Expanded Mime.prp file
+ + FileHandler flushes files from cache in DELETE method.
+ + Made ServerSocket and accept call generic in ThreadedServer for SSL
+   listeners.
+ + Options "allowDir" added to FileHandler.
+ + Restructured com.mortbay.Jetty.Server for better clarity and documentation.
+ + ThreadedServer.stop() now waits until all threads are stopped.
+ + Updated README.txt
 
 jetty-2.2.Beta3 - 22 June 1999
- + Re-implemented ThreadedServer to improve and balance performance.
+ + Added alternate constructors to HTML.Include for InputStream.
  + Added file cache to FileHandler
+ + Applied contributed patch of spelling and typo corrections
+ + Fixed bug in HttpResponse flush.
+ + Fixed file and socket leaks in Include and Embed tags.
  + Implemented efficient version of ServletContext.getResourceAsStream() that
    does not open a new socket connection (as does getResource()).
+ + Improved Block.write.
  + LookAndFeelServlet uses getResourceAsStream to get the file to wrap. This
    allows it to benefit from any caching done and to wrap arbitrary content
    (not just files).
+ + Ran dos2unix on all text files
+ + Re-implemented ThreadedServer to improve and balance performance.
  + Restructure demo so that LookAndFeel content comes from simple handler
    stack.
- + Fixed file and socket leaks in Include and Embed tags.
- + Ran dos2unix on all text files
- + Applied contributed patch of spelling and typo corrections
- + Added alternate constructors to HTML.Include for InputStream.
  + Server.shutdown() clears configuration so that server may be restarted in
    same virtual machine.
- + Improved Block.write.
- + Fixed bug in HttpResponse flush.
 
 jetty-2.2.Beta2 - 12 June 1999
  + Added all write methods to HttpOutputStream$SwitchOutputStream
  + Added com.mortbay.Jetty.Server.shutdown() for gentler shutdown of server.
    Called from Exit servlet
- + HttpRequest.getParameterNames() no longer alters the order returned by
-   getQueryString().
  + Handle  path info of a dynamic loaded servlets and correctly set the servlet
    path.
+ + HttpRequest.getParameterNames() no longer alters the order returned by
+   getQueryString().
  + Standardized date format in persistent cookies.
 
 jetty-2.2.Beta1 - 07 June 1999
+ + Allow configuration of MinListenerThreads, MaxListenerThreads,
+   MaxListenerThreadIdleMs
+ + Close files after use to avoid "file leak" under heavy load.
  + Defined abstract ServletLoader, derivations of which can be specified in
    HttpConfiguration properties.
+ + Destroy requests and responses to help garbage collector.
+ + Don't warn about IOExceptions unless Debug is on.
+ + Fixed cache in FileJarServletLoader
+ + Fixed incorrect version numbers in a few places.
+ + Fixed missing copyright messages from some contributions
+ + HtmlFilter optimized for being called by a buffered writer.
  + Implemented all HttpServer attribute methods by mapping to the
    HttpConfiguration properties.  Dynamic reconfiguration is NOT supported by
    these methods (but we are thinking about it).
- + Close files after use to avoid "file leak" under heavy load.
- + Fixed missing copyright messages from some contributions
- + Fixed incorrect version numbers in a few places.
  + Improved ThreadPool synchronization and added minThreads.
- + Allow configuration of MinListenerThreads, MaxListenerThreads,
-   MaxListenerThreadIdleMs
- + HtmlFilter optimized for being called by a buffered writer.
- + Don't warn about IOExceptions unless Debug is on.
- + Limit the job queue only grow to the max number of threads.
  + Included GNUJSP 0.9.9
+ + Limit the job queue only grow to the max number of threads.
  + Optional use of DateCache in log file format
- + Fixed cache in FileJarServletLoader
- + Destroy requests and responses to help garbage collector.
  + Restructure ThreadedServer to reduce object creation.
 
 jetty-2.2.Beta0 - 31 May 1999
- + Servlet loader handles jar files with different files separator.
- + ThreadedServer gently shuts down.
- + Handle malformed % characters in URLs.
- + Included and improved version of ThreadPool for significant performance
-   improvement under high load.
- + HttpRequest.getCookies returns empty array rather than null for no cookies.
+ + Added "Initialize" attribute to servlet configuration to allow servlet to be
+   initialized when loaded.
  + Added HttpResponse.requestHandled() method to avoid bug with servlet doHead
    method.
  + Added Page.rewind() method to allow a page to be written multiple times
- + Added "Initialize" attribute to servlet configuration to allow servlet to be
-   initialized when loaded.
- + LogHandler changed to support only a single outfile and optional append.
+ + Handle malformed % characters in URLs.
+ + HttpRequest.getCookies returns empty array rather than null for no cookies.
+ + Included and improved version of ThreadPool for significant performance
+   improvement under high load.
  + Included contributed com.mortbay.Jetty.StressTester class
- + Token effort to keep test files out of the jar
+ + LogHandler changed to support only a single outfile and optional append.
  + Removed support for STF
+ + Servlet loader handles jar files with different files separator.
+ + ThreadedServer gently shuts down.
+ + Token effort to keep test files out of the jar
 
 jetty-2.2.Alpha1 - 07 May 1999
- + ServletHolder can auto reload servlets
- + Dynamic servlets can have autoReload configured
- + Wait for requests to complete before reloading.
  + Call destroy on old servlets when reloading.
- + Made capitalization of config file more consistent(ish)
+ + Dynamic servlets can have autoReload configured
  + Fixed bug in SessionDump
+ + Made capitalization of config file more consistent(ish)
+ + ServletHolder can auto reload servlets
+ + Wait for requests to complete before reloading.
 
 jetty-2.2.Alpha0 - 06 May 1999
- + Improved PropertyTree implementation
- + Old Jetty.Server class renamed to Jetty.Server21
- + New Server class using PropertyTree for configuration
+ + Added reload method to ServletHolder, but no way to call it yet.
+ + Added ServletLoader implementation if ClassLoader.
+ + Changed options for FileServer
+ + Dynamic loading of servlets.
+ + Fixed date overflow in Cookies
  + HttpHandlers given setProperties method to configure via Properties.
  + HttpListener class can be configured
- + Mime suffix mapping can be configured.
- + Removed historic API from sessions
- + Improved SessionDump servlet
- + Fixed date overflow in Cookies
  + HttpResponse.sendError avoids IllegalStateException
- + Added ServletLoader implementation if ClassLoader.
- + Dynamic loading of servlets.
- + Added reload method to ServletHolder, but no way to call it yet.
- + Changed options for FileServer
  + Implemented ServletServer
+ + Improved PropertyTree implementation
+ + Improved SessionDump servlet
+ + Mime suffix mapping can be configured.
+ + New Server class using PropertyTree for configuration
+ + Old Jetty.Server class renamed to Jetty.Server21
+ + Removed historic API from sessions
  + Removed SimpleServletServer
 
 jetty-2.1.7 - 22 April 1999
@@ -6007,42 +7988,42 @@ jetty-2.1.7 - 22 April 1999
  + HttpFilter uses package interface to get HttpOutputStream
 
 jetty-2.1.6 - 21 April 1999
+ + Added additional date formats for HttpHeader.getDateHeader
+ + New simpler version of PropertyTree
  + Reduced initial size of most hashtables to reduce default memory overheads.
+ + Return EOF from HttpInputStream that has a content length.
  + Throw IllegalStateException as required from gets of
    input/output/reader/writer in requests/responses.
- + New simpler version of PropertyTree
  + Updated PropertyTreeEditor
- + Return EOF from HttpInputStream that has a content length.
- + Added additional date formats for HttpHeader.getDateHeader
 
 jetty-2.1.5 - 15 April 1999
- + Session URL encoding fixed for relative URLs.
- + Reduced session memory overhead of sessions
- + Form parameters protected against multiple decodes when redirected.
  + Added setType methods to com.mortbay.FTP.Ftp
+ + Fixed alignment bug in TableForm
+ + Fixed bug in ServletDispatch for null pathInfo
  + Fixed bugs with invalid sessions
- + Page factory requires response for session encoding
- + Moved SessionHandler to front of stacks
+ + Form parameters protected against multiple decodes when redirected.
  + HtmlFilter now expands <!=SESSION> to the URL encoded session if required.
- + Instrumented most of the demo to support URL session encoding.
  + Implemented HttpRequest.getReader()
+ + Instrumented most of the demo to support URL session encoding.
+ + Moved SessionHandler to front of stacks
+ + Page factory requires response for session encoding
+ + Reduced session memory overhead of sessions
+ + Removed RFCs from package
  + Servlet log has been diverted to com.mortbay.Base.Log.event() Thus debug
    does not need to be turned on to see servlet logs.
- + Fixed alignment bug in TableForm
- + Removed RFCs from package
- + Fixed bug in ServletDispatch for null pathInfo
+ + Session URL encoding fixed for relative URLs.
 
 jetty-2.1.4 - 26 March 1999
+ + fixed bug in getRealPath
  + Fixed problem compiling PathMap under some JDKs.
- + Reduced HTML dependence in HTTP package to allow minimal configuration
- + Tightened license agreement so that binary distributions are required to
-   include the license file.
+ + getPathTranslated now call getRealPath with pathInfo (as per spec).
  + HttpRequest attributes implemented.
- + Session max idle time implemented.
  + pathInfo returns null for zero length pathInfo (as per spec). Sorry if this
    breaks your servlets - it is a pain!
- + fixed bug in getRealPath
- + getPathTranslated now call getRealPath with pathInfo (as per spec).
+ + Reduced HTML dependence in HTTP package to allow minimal configuration
+ + Session max idle time implemented.
+ + Tightened license agreement so that binary distributions are required to
+   include the license file.
 
 jetty-2.1.3 - 19 March 1999
  + Added support for suffixes to PathMap
@@ -6050,170 +8031,170 @@ jetty-2.1.3 - 19 March 1999
  + Use Java2 javadoc
 
 jetty-2.1.2 - 09 March 1999
- + JSDK 2.1.1
  + API documentation for JSDK 2.1.1
  + Cascading style sheet HTML element added.
- + Fixed trailing / bug in FileHandler (again!).
  + Converted most servlets to HttpServlets using do Methods.
+ + Fixed trailing / bug in FileHandler (again!).
+ + JSDK 2.1.1
 
 jetty-2.1.1 - 05 March 1999
- + Reduced number of calls to getRemoteHost for optimization
- + Faster version of HttpInputStream.readLine().
  + com.mortbay.Base.DateCache class added and used to speed date handling.
- + Handle '.' in configured paths (temp fix until PropertyTrees)
  + Fast char buffer handling in HttpInputStream
  + Faster version of HttpHeader.read()
+ + Faster version of HttpInputStream.readLine().
  + Faster version of HttpRequest
+ + Handle '.' in configured paths (temp fix until PropertyTrees)
+ + Reduced number of calls to getRemoteHost for optimization
  + Size all StringBuffers
 
 jetty-2.1.0 - 22 February 1999
- + Session URL Encoding
- + PropertyTrees (see new Demo page)
- + ServletDispatch (see new Demo page)
- + image/jpg -> image/jpeg
  + Deprecated com.mortbay.Util.STF
  + getServlet methods return null.
+ + image/jpg -> image/jpeg
+ + PropertyTrees (see new Demo page)
+ + ServletDispatch (see new Demo page)
+ + Session URL Encoding
 
 jetty-2.1.B1 - 13 February 1999
- + Fixed bug with if-modified-since in FileHandler
  + Added video/quicktime to default MIME types.
+ + Fixed bug with if-modified-since in FileHandler
  + Fixed bug with MultipartRequest.
- + Updated DefaultExceptionHandler.
- + Updated InetAddrPort.
- + Updated URI.
+ + Implemented getResource and getResourceAsStream (NOT Tested!).
  + Implemented Handler translations and getRealPath.
- + Improved handling of File.separator in FileHandler.
  + Implemented RequestDispatcher (NOT Tested!).
- + Implemented getResource and getResourceAsStream (NOT Tested!).
+ + Improved handling of File.separator in FileHandler.
  + Replace package com.mortbay.Util.Gateway with class
    com.mortbay.Util.InetGateway
+ + Updated DefaultExceptionHandler.
+ + Updated InetAddrPort.
+ + Updated URI.
 
 jetty-2.1.B0 - 30 January 1999
- + Uses JSDK2.1 API, but not all methods implemented.
+ + Added plug gateway classes com.mortbay.Util.Gateway
  + Added support for PUT, MOVE, DELETE in FileHandler
  + FileHandler now sets content length.
- + Added plug gateway classes com.mortbay.Util.Gateway
  + Fixed command line bug with SimpleServletConfig
  + Minor changes to support MS J++ and its non standard language extensions -
    MMMmmm should have left it unchanged!
+ + Uses JSDK2.1 API, but not all methods implemented.
 
 jetty-2.0.5 - 15 December 1998
- + Temp fix to getCharacterEncoding
  + added getHeaderNoParams
+ + Temp fix to getCharacterEncoding
 
 jetty-2.0.4 - 10 December 1998
- + Use real release of JSDK2.0 (rather than beta).
- + Portability issues solved for Apple's
+ + Implement getCharacterEncoding
+ + Improved default Makefile behaviour
  + Improved error code returns
+ + Portability issues solved for Apple's
  + Removed MORTBAY_HOME support from Makefiles
- + Improved default Makefile behaviour
- + Implement getCharacterEncoding
+ + Use real release of JSDK2.0 (rather than beta).
 
 jetty-2.0.3 - 13 November 1998
- + Limit threads in ThreadedServer and low priority listener option greatly
-   improve performance under worse case loads.
  + Fix bug with index files for Jetty.Server. Previously servers configured
    with com.mortbay.Jetty.Server would not handle index.html files.  Need to
    make this configurable in the prp file.
  + Fixed errors in README file: com.mortbay.Jetty.Server was called
    com.mortbay.HTTP.Server
+ + Limit threads in ThreadedServer and low priority listener option greatly
+   improve performance under worse case loads.
 
 jetty-2.0.2 - 01 November 1998
- + Use JETTY_HOME rather than MORTBAY_HOME for build environment
  + Add thread pool to threaded server for significant performance improvement.
  + Buffer files during configuration
  + Buffer HTTP Response headers.
+ + Use JETTY_HOME rather than MORTBAY_HOME for build environment
 
 jetty-2.0.1 - 27 October 1998
  + Released under an Open Source license.
 
 jetty-2.0.0 - 25 October 1998
- + Removed exceptional case from FileHandler redirect.
- + Removed Chat demo (too many netscape dependencies).
- + Fixed Code.formatObject handling of null objects.
  + Added multipart/form-data demo.
+ + Fixed Code.formatObject handling of null objects.
+ + Removed Chat demo (too many netscape dependencies).
+ + Removed exceptional case from FileHandler redirect.
 
 jetty-2.0.Beta3 - 29 September 1998
- + Send 301 for directories without trailing / in FileHandler
- + Ignore exception from HttpListener
- + Properly implemented multiple listening addresses
+ + Added com.mortbay.HTTP.MultiPartRequest to handle file uploads
  + Added com.mortbay.Jetty.Server (see README.Jetty)
  + Demo converted to an instance of com.mortbay.Jetty.Server
  + Fixed Log Handler again.
- + Added com.mortbay.HTTP.MultiPartRequest to handle file uploads
+ + Ignore exception from HttpListener
+ + Properly implemented multiple listening addresses
+ + Send 301 for directories without trailing / in FileHandler
 
 jetty-2.0Beta2 - 01 July 1998
  + Fixed Log Handler for HTTP/1.1
  + Slight improvement in READMEEs
 
 jetty-2.0Beta1 - 01 June 1998
+ + Fixed bug with calls to service during initialization of servlet
+ + Handle full URLs in HTTP requests (to some extent)
  + Improved performance of Code.debug() calls, significantly in the case of non
    matching debug patterns.
- + Fixed bug with calls to service during initialization of servlet
+ + Improved performance with special asciiToLowerCase
  + Provided addSection on com.mortbay.HTML.Page
  + Provided reset on com.mortbay.HTML.Composite.
  + Proxy demo in different server instance
- + Handle full URLs in HTTP requests (to some extent)
- + Improved performance with special asciiToLowerCase
  + Warn if MSIE used for multi part MIME.
 
 jetty-2.0Alpha2 - 01 May 1998
- + JDK1.2 javax.servlet API
  + Added date format to Log
  + Added timezone to Log
  + Handle params in getIntHeader and getDateHeader
+ + Handle Single Threaded servlets with servlet pool
+ + JDK1.2 javax.servlet API
  + Removed HttpRequest.getByteContent
- + Use javax.servlet.http.HttpUtils.parsePostData
  + Use javax.servlet.http.Cookie
  + Use javax.servlet.http.HttpSession
- + Handle Single Threaded servlets with servlet pool
+ + Use javax.servlet.http.HttpUtils.parsePostData
 
 jetty-1.3.5 - 01 May 1998
- + Fixed socket inet bug in FTP
- + Debug triggers added to com.mortbay.Base.Code
  + Added date format to Log
  + Correct handling of multiple parameters
+ + Debug triggers added to com.mortbay.Base.Code
+ + Fixed socket inet bug in FTP
 
 jetty-2.0Alpha1 - 08 April 1998
- + Fixed forward bug with no port number
- + Removed HttpRequestHeader class
- + Debug triggers added to com.mortbay.Base.Code
- + Handle HTTP/1.1 Host: header
- + Correct formatting of Date HTTP headers
- + HttpTests test harness
+ + accept chunked data
  + Add HTTP/1.1 Date: header
+ + Correct formatting of Date HTTP headers
+ + Debug triggers added to com.mortbay.Base.Code
+ + Fixed forward bug with no port number
+ + handle extra spaces in HTTP headers
  + Handle file requests with If-Modified-Since: or If-Unmodified-Since:
  + Handle HEAD properly
- + Send Connection: close
- + Requires Host: header for 1.1 requests
- + Sends chunked data for 1.1 responses of unknown length.
- + handle extra spaces in HTTP headers
+ + Handle HTTP/1.1 Host: header
+ + HttpTests test harness
+ + persistent connections
  + Really fixed handling of multiple parameters
- + accept chunked data
+ + Removed HttpRequestHeader class
+ + Requires Host: header for 1.1 requests
  + Send 100 Continue for HTTP/1.1 requests (concerned about push???)
- + persistent connections
+ + Send Connection: close
+ + Sends chunked data for 1.1 responses of unknown length.
 
 jetty-1.3.4 - 15 March 1998
+ + Dump servlet enhanced to exercise these changes.
  + Fixed handling of multiple parameters in query and form content.
    "?A=1%2C2&A=C%2CD" now returns two values ("1,2" & "C,D") rather than 4.
  + ServletHandler now takes an optional file base directory name which is used
    to set the translated path for pathInfo in servlet requests.
- + Dump servlet enhanced to exercise these changes.
 
 jetty-1.3.3
+ + Closed exception window in HttpListener.java
  + Fixed TableForm.addButtonArea bug.
  + TableForm.extendRow() uses existing cell
- + Closed exception window in HttpListener.java
 
 jetty-1.3.2
- + Fixed proxy bug with no port number
  + Added per Table cell composite factories
+ + Fixed proxy bug with no port number
 
 jetty-1.3.1
- + Minor fixes in SmtpMail
+ + Better handling of InvocationTargetException in debug
  + ForwardHandler only forwards as http/1.0 (from Tobias.Miller)
  + Improved parsing of stack traces
- + Better handling of InvocationTargetException in debug
+ + Minor fixes in SmtpMail
  + Minor release adjustments for Tracker
 
 jetty-1.3.0
@@ -6221,11 +8202,11 @@ jetty-1.3.0
  + Beta release of Tracker
 
 jetty-1.2.0
- + Reintroduced STF
- + Fixed install bug for nested classes
+ + Alternate look and feel for Jetty
  + Better Debug configuration
  + DebugServlet
- + Alternate look and feel for Jetty
+ + Fixed install bug for nested classes
+ + Reintroduced STF
 
 jetty-1.1.1
  + Improved documentation
diff --git a/advisories/2015-02-24-httpparser-error-buffer-bleed.md b/advisories/2015-02-24-httpparser-error-buffer-bleed.md
new file mode 100644
index 0000000..550552d
--- /dev/null
+++ b/advisories/2015-02-24-httpparser-error-buffer-bleed.md
@@ -0,0 +1,125 @@
+HttpParser Error Buffer Bleed Vulnerability
+===========================================
+
+Published Date:
+---------------
+
+2015, Feb 24
+
+CVE:
+----
+
+CVE-2015-2080
+
+Discovered and Reported By:
+---------------------------
+
+[Gotham Digital Science](http://www.gdssecurity.com/) and Stephen Komal.
+
+[JetLeak Vulnerability Remote Leakage of Shared Buffers in Jetty / blogs.gdsecurity.com](http://blog.gdssecurity.com/labs/2015/2/25/jetleak-vulnerability-remote-leakage-of-shared-buffers-in-je.html)
+
+
+Affected Versions of Jetty:
+---------------------------
+
+  * 9.2.3.v20140905
+  * 9.2.4.v20141103
+  * 9.2.5.v20141112
+  * 9.2.6.v20141205
+  * 9.2.7.v20150116
+  * 9.2.8.v20150217
+  * 9.3.0.M0
+  * 9.3.0.M1
+
+Versions of Jetty Containing Fix:
+--------------------------------
+
+  * 9.2.9.v20150224
+
+Patched version of jetty-http.jar:
+----------------------------------
+
+Patched version of the affected jetty-http jars are available as attachments on https://bugs.eclipse.org/460642
+
+Statement:
+----------
+
+Jetty versions 9.2.3.v20140905 through 9.2.8.v20150217 have a ByteBuffer reuse and information bleed vulnerability surrounding bad HTTP request header parsing error responses.
+
+History:
+--------
+
+Back in Jetty 9.2.3, a feature requesting more detailed logging messages surrounding problems parsing bad HTTP request headers ( https://bugs.eclipse.org/443049 ) was implemented.
+
+The feature request was to include better debug information in the Jetty logs (at WARN level) to help diagnose and resolve HTTP parsing errors.
+
+However, the implementation incorrectly exposes this debug information back on the HTTP 400 response reason phrase, potentially exposing parts of server side buffers used from prior request processing on the same server.
+
+The following bash shell script demonstrates the problem using netcat on linux against the Jetty Distribution's demo-base.
+
+```
+#!/bin/bash
+
+RESOURCEPATH="/test/dump/info"
+BAD=$'\a'
+
+function normalRequest {
+echo "-- Normal Request --"
+
+nc localhost 8080 << NORMREQ
+POST $RESOURCEPATH HTTP/1.1
+Host: localhost
+Content-Type: application/x-www-form-urlencoded;charset=utf-8
+Connection: close
+Content-Length: 16
+
+Username=Joakim
+NORMREQ
+}
+
+function badCookie {
+echo "-- Bad Cookie --"
+
+nc localhost 8080 << BADCOOKIE
+GET $RESOURCEPATH HTTP/1.1
+Host: localhost
+Coo${BAD}kie: ${BAD}
+
+BADCOOKIE
+}
+
+normalRequest
+echo ""
+echo ""
+badCookie
+```
+
+The results are often seen in the HTTP response such as ...
+
+```
+HTTP/1.1 400 Illegal character 0x7 in state=HEADER_IN_NAME in 'GET /dummy/ HTTP/... localhost\nCoo\x07<<<kie: \x07\n\n>>>e: application/x-...\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+Content-Length: 0
+Connection: close
+Server: Jetty(9.2.8.v20150217)
+```
+
+What you are seeing is a http response phrase that includes raw ByteBuffer details on what happened during the parsing failure.
+
+The parts of the output are in the general form
+`{what_has_been_parsed}<<<{left_to_parse}>>>{old_buffer_seen_past_limit}`
+
+The part at `{old_buffer_seen_past_limit}` is where this exposure of past buffers comes from.  It is this information where an exploit could be made to present random prior buffers from the server buffer pool.  This information can contain anything seen in a past handled request.
+
+We have this problem already patched in Jetty 9.2.9.v20150224, and the same test as above results in ...
+
+```
+HTTP/1.1 400 Illegal character 0x7
+Content-Length: 0
+Connection: close
+Server: Jetty(9.2.9.v20150224)
+```
+
+Everyone is strongly encouraged to upgrade to Jetty 9.2.9.v20150224 immediately.
+
+
+
diff --git a/aggregates/jetty-all/pom.xml b/aggregates/jetty-all/pom.xml
new file mode 100644
index 0000000..2c5d134
--- /dev/null
+++ b/aggregates/jetty-all/pom.xml
@@ -0,0 +1,230 @@
+<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">
+  <parent>
+    <groupId>org.eclipse.jetty</groupId>
+    <artifactId>jetty-project</artifactId>
+    <version>9.2.13.v20150730</version>
+    <relativePath>../../pom.xml</relativePath>
+  </parent>
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>org.eclipse.jetty.aggregate</groupId>
+  <artifactId>jetty-all</artifactId>
+  <name>Jetty :: Aggregate :: All core Jetty</name>
+  <url>http://www.eclipse.org/jetty</url>
+  <build>
+    <sourceDirectory>${project.build.directory}/sources</sourceDirectory>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-dependency-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>unpack-dependencies</id>
+            <goals>
+              <goal>unpack-dependencies</goal>
+            </goals>
+            <configuration>
+              <excludes>**/MANIFEST.MF,javax/**</excludes>
+              <excludeArtifactIds>javax</excludeArtifactIds>
+              <excludeGroupIds>javax,org.eclipse.jetty.orbit,org.mortbay.jetty.npn,org.slf4j,org.ow2.asm</excludeGroupIds>
+              <outputDirectory>${project.build.directory}/classes</outputDirectory>
+              <overWriteReleases>false</overWriteReleases>
+              <overWriteSnapshots>true</overWriteSnapshots>
+            </configuration>
+          </execution>
+          <execution>
+            <id>unpack-source</id>
+            <phase>generate-sources</phase>
+            <goals>
+              <goal>unpack-dependencies</goal>
+            </goals>
+            <configuration>
+              <classifier>sources</classifier>
+              <includes>**/*</includes>
+              <excludes>META-INF/**,**/Servlet3Continuation*,**/Jetty6Continuation*</excludes>
+              <includeGroupIds>org.eclipse.jetty,org.eclipse.jetty.websocket</includeGroupIds>
+              <excludeArtifactIds>javax</excludeArtifactIds>
+              <excludeGroupIds>javax,org.eclipse.jetty.orbit,org.mortbay.jetty.npn,org.slf4j,org.ow2.asm</excludeGroupIds>
+              <outputDirectory>${project.build.directory}/sources</outputDirectory>
+              <overWriteReleases>true</overWriteReleases>
+              <overWriteSnapshots>true</overWriteSnapshots>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins
+        </groupId>
+        <artifactId>maven-jar-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>package</id>
+            <phase>package</phase>
+            <goals>
+              <goal>jar</goal>
+            </goals>
+            <configuration>
+              <archive>
+                <manifest>
+                </manifest>
+                <manifestEntries>
+                  <mode>development</mode>
+                  <url>http://eclipse.org/jetty</url>
+                  <Built-By>${user.name}</Built-By>
+                  <package>org.eclipse.jetty</package>
+                  <Bundle-License>http://git.eclipse.org/c/jetty/org.eclipse.jetty.project.git/tree/NOTICE.txt</Bundle-License>
+                  <Bundle-Name>Jetty</Bundle-Name>
+                </manifestEntries>
+              </archive>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-javadoc-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>javadoc-jar</id>
+            <phase>compile</phase>
+            <goals>
+              <goal>jar</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+    <pluginManagement>
+      <plugins>
+        <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-pmd-plugin</artifactId>
+          <configuration>
+            <skip>true</skip>
+          </configuration>
+        </plugin>
+      </plugins>
+    </pluginManagement>
+  </build>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-client</artifactId>
+      <version>${project.version}</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-deploy</artifactId>
+      <version>${project.version}</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.websocket</groupId>
+      <artifactId>websocket-servlet</artifactId>
+      <version>${project.version}</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.websocket</groupId>
+      <artifactId>javax-websocket-server-impl</artifactId>
+      <version>${project.version}</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.websocket</groupId>
+      <artifactId>websocket-client</artifactId>
+      <version>${project.version}</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.spdy</groupId>
+      <artifactId>spdy-http-server</artifactId>
+      <version>${project.version}</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-jmx</artifactId>
+      <version>${project.version}</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-plus</artifactId>
+      <version>${project.version}</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-annotations</artifactId>
+      <version>${project.version}</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-util</artifactId>
+      <version>${project.version}</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-jaspi</artifactId>
+      <version>${project.version}</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-jndi</artifactId>
+      <version>${project.version}</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-rewrite</artifactId>
+      <version>${project.version}</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-servlets</artifactId>
+      <version>${project.version}</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-quickstart</artifactId>
+      <version>${project.version}</version>
+      <scope>provided</scope>
+    </dependency>
+    <!-- dependencies that jetty-all needs (some optional) -->
+    <dependency>
+      <groupId>javax.websocket</groupId>
+      <artifactId>javax.websocket-api</artifactId>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>javax.servlet</groupId>
+      <artifactId>javax.servlet-api</artifactId>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>javax.transaction</groupId>
+      <artifactId>javax.transaction-api</artifactId>
+      <scope>compile</scope>
+      <optional>true</optional>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.orbit</groupId>
+      <artifactId>javax.mail.glassfish</artifactId>
+      <scope>compile</scope>
+      <optional>true</optional>
+    </dependency>
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-api</artifactId>
+      <scope>compile</scope>
+      <optional>true</optional>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/aggregates/jetty-websocket-all/pom.xml b/aggregates/jetty-websocket-all/pom.xml
new file mode 100644
index 0000000..26138ee
--- /dev/null
+++ b/aggregates/jetty-websocket-all/pom.xml
@@ -0,0 +1,163 @@
+<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">
+  <parent>
+    <groupId>org.eclipse.jetty</groupId>
+    <artifactId>jetty-project</artifactId>
+    <version>9.1.0-SNAPSHOT</version>
+    <relativePath>../../pom.xml</relativePath>
+  </parent>
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>org.eclipse.jetty.aggregate</groupId>
+  <artifactId>jetty-websocket-all</artifactId>
+  <name>Jetty :: Aggregate :: All WebSocket Server + Client Classes</name>
+  <url>http://www.eclipse.org/jetty</url>
+  <build>
+    <sourceDirectory>${project.build.directory}/sources</sourceDirectory>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-dependency-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>unpack-dependencies</id>
+            <goals>
+              <goal>unpack-dependencies</goal>
+            </goals>
+            <configuration>
+              <excludes>**/MANIFEST.MF</excludes>
+              <excludeGroupIds>org.slf4j,org.eclipse.jetty.orbit,org.mortbay.jetty.npn</excludeGroupIds>
+              <outputDirectory>${project.build.directory}/classes</outputDirectory>
+              <overWriteReleases>false</overWriteReleases>
+              <overWriteSnapshots>true</overWriteSnapshots>
+            </configuration>
+          </execution>
+          <execution>
+            <id>unpack-source</id>
+            <phase>generate-sources</phase>
+            <goals>
+              <goal>unpack-dependencies</goal>
+            </goals>
+            <configuration>
+              <classifier>sources</classifier>
+              <includes>**/*</includes>
+              <excludes>META-INF/**,**/Servlet3Continuation*,**/Jetty6Continuation*</excludes>
+              <includeGroupIds>org.eclipse.jetty,org.eclipse.jetty.websocket</includeGroupIds>
+              <excludeArtifactIds>javax</excludeArtifactIds>
+              <excludeGroupIds>javax,org.eclipse.jetty.orbit,org.mortbay.jetty.npn</excludeGroupIds>
+              <outputDirectory>${project.build.directory}/sources</outputDirectory>
+              <overWriteReleases>true</overWriteReleases>
+              <overWriteSnapshots>true</overWriteSnapshots>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins
+        </groupId>
+        <artifactId>maven-jar-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>package</id>
+            <phase>package</phase>
+            <goals>
+              <goal>jar</goal>
+            </goals>
+            <configuration>
+              <archive>
+                <manifest>
+                </manifest>
+                <manifestEntries>
+                  <mode>development</mode>
+                  <url>http://eclipse.org/jetty</url>
+                  <Built-By>${user.name}</Built-By>
+                  <package>org.eclipse.jetty</package>
+                  <Bundle-License>http://git.eclipse.org/c/jetty/org.eclipse.jetty.project.git/tree/NOTICE.txt</Bundle-License>
+                  <Bundle-Name>Jetty</Bundle-Name>
+                </manifestEntries>
+              </archive>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-javadoc-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>javadoc-jar</id>
+            <phase>compile</phase>
+            <goals>
+              <goal>jar</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+    <pluginManagement>
+      <plugins>
+        <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-pmd-plugin</artifactId>
+          <configuration>
+            <skip>true</skip>
+          </configuration>
+        </plugin>
+      </plugins>
+    </pluginManagement>
+  </build>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.eclipse.jetty.websocket</groupId>
+      <artifactId>websocket-servlet</artifactId>
+      <version>${project.version}</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.websocket</groupId>
+      <artifactId>javax-websocket-server-impl</artifactId>
+      <version>${project.version}</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.websocket</groupId>
+      <artifactId>websocket-client</artifactId>
+      <version>${project.version}</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-plus</artifactId>
+      <version>${project.version}</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-annotations</artifactId>
+      <version>${project.version}</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-util</artifactId>
+      <version>${project.version}</version>
+      <scope>provided</scope>
+    </dependency>
+    <!-- dependencies that jetty-all needs (some optional) -->
+    <dependency>
+      <groupId>javax.websocket</groupId>
+      <artifactId>javax.websocket-api</artifactId>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>javax.servlet</groupId>
+      <artifactId>javax.servlet-api</artifactId>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-api</artifactId>
+      <scope>runtime</scope>
+      <optional>true</optional>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/apache-jsp/pom.xml b/apache-jsp/pom.xml
new file mode 100644
index 0000000..fcad7c6
--- /dev/null
+++ b/apache-jsp/pom.xml
@@ -0,0 +1,120 @@
+<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">
+  <parent>
+    <groupId>org.eclipse.jetty</groupId>
+    <artifactId>jetty-project</artifactId>
+    <version>9.2.13.v20150730</version>
+  </parent>
+  <modelVersion>4.0.0</modelVersion>
+  <artifactId>apache-jsp</artifactId>
+  <name>Jetty :: Apache JSP Implementation</name>
+  <url>http://www.eclipse.org/jetty</url>
+  <packaging>jar</packaging>
+  <properties>
+    <bundle-symbolic-name>${project.groupId}.${project.artifactId}</bundle-symbolic-name>
+  </properties>
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>maven-bundle-plugin</artifactId>
+        <extensions>true</extensions>
+        <executions>
+          <execution>
+            <id>generate-manifest</id>
+            <goals>
+              <goal>manifest</goal>
+            </goals>
+            <configuration>
+              <instructions>
+                <Bundle-Description>Jetty-specific ServletContainerInitializer for Jasper</Bundle-Description>
+                <Export-Package>org.eclipse.jetty.apache.jsp.*;version="${parsedVersion.majorVersion}.${parsedVersion.minorVersion}.${parsedVersion.incrementalVersion}", 
+                                org.eclipse.jetty.jsp.*;version="${parsedVersion.majorVersion}.${parsedVersion.minorVersion}.${parsedVersion.incrementalVersion}"
+                </Export-Package>
+                <Require-Capability>osgi.extender; filter:="(osgi.extender=osgi.serviceloader.registrar)"</Require-Capability>
+                <Provide-Capability>osgi.serviceloader; osgi.serviceloader=javax.servlet.ServletContainerInitializer</Provide-Capability>
+                <_nouses>true</_nouses>
+              </instructions>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-jar-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>artifact-jar</id>
+            <goals>
+              <goal>jar</goal>
+            </goals>
+          </execution>
+          <execution>
+            <id>test-jar</id>
+            <goals>
+              <goal>test-jar</goal>
+            </goals>
+          </execution>
+        </executions>
+        <configuration>
+          <archive>
+            <manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
+          </archive>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-assembly-plugin</artifactId>
+        <executions>
+          <execution>
+            <phase>package</phase>
+            <goals>
+              <goal>single</goal>
+            </goals>
+            <configuration>
+              <descriptorRefs>
+                <descriptorRef>config</descriptorRef>
+              </descriptorRefs>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+
+  <dependencies>
+    <dependency>
+        <groupId>org.eclipse.jetty</groupId>
+        <artifactId>jetty-util</artifactId>
+        <version>${project.version}</version>
+    </dependency>
+    <dependency>
+        <groupId>org.eclipse.jetty</groupId>
+        <artifactId>jetty-server</artifactId>
+        <version>${project.version}</version>
+    </dependency>
+    
+    <!-- Schemas -->
+    <dependency>
+      <groupId>org.eclipse.jetty.toolchain</groupId>
+      <artifactId>jetty-schemas</artifactId>
+    </dependency>
+
+    <!-- servlet api -->
+    <dependency>
+       <groupId>javax.servlet</groupId>
+       <artifactId>javax.servlet-api</artifactId>
+    </dependency>
+
+    <!-- JSP Impl -->
+    <dependency>
+      <groupId>org.mortbay.jasper</groupId>
+      <artifactId>apache-jsp</artifactId>
+    </dependency>
+
+    <!-- Eclipse Java Compiler (for JSP Compilation) -->
+    <dependency>
+      <groupId>org.eclipse.jetty.orbit</groupId>
+      <artifactId>org.eclipse.jdt.core</artifactId>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/apache-jsp/src/main/config/modules/jsp-impl/apache-jsp.mod b/apache-jsp/src/main/config/modules/jsp-impl/apache-jsp.mod
new file mode 100644
index 0000000..aed547c
--- /dev/null
+++ b/apache-jsp/src/main/config/modules/jsp-impl/apache-jsp.mod
@@ -0,0 +1,10 @@
+#
+# Apache JSP Module
+#
+
+[name]
+jsp-impl
+
+[lib]
+lib/apache-jsp/*.jar
+
diff --git a/apache-jsp/src/main/java/org/eclipse/jetty/apache/jsp/JettyJasperInitializer.java b/apache-jsp/src/main/java/org/eclipse/jetty/apache/jsp/JettyJasperInitializer.java
new file mode 100644
index 0000000..500cd35
--- /dev/null
+++ b/apache-jsp/src/main/java/org/eclipse/jetty/apache/jsp/JettyJasperInitializer.java
@@ -0,0 +1,119 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.apache.jsp;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import javax.servlet.ServletContext;
+
+import org.apache.jasper.servlet.JasperInitializer;
+import org.apache.jasper.servlet.TldPreScanned;
+import org.apache.jasper.servlet.TldScanner;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.xml.sax.SAXException;
+
+/**
+ * JettyJasperInitializer
+ *
+ */
+public class JettyJasperInitializer extends JasperInitializer
+{
+   private static final Logger LOG = Log.getLogger(JettyJasperInitializer.class);
+    
+    /**
+     * NullTldScanner
+     *
+     * Does nothing. Used when we can tell that all jsps have been precompiled, in which case
+     * the tlds are not needed.
+     */
+    private final class NullTldScanner extends TldScanner
+    {
+        /**
+         * @param context
+         * @param namespaceAware
+         * @param validation
+         * @param blockExternal
+         */
+        private NullTldScanner(ServletContext context, boolean namespaceAware, boolean validation, boolean blockExternal)
+        {
+            super(context, namespaceAware, validation, blockExternal);
+        }
+
+        /**
+         * @see org.apache.jasper.servlet.TldScanner#scan()
+         */
+        @Override
+        public void scan() throws IOException, SAXException
+        {
+            return; //do nothing
+        }
+
+        /**
+         * @see org.apache.jasper.servlet.TldScanner#getListeners()
+         */
+        @Override
+        public List<String> getListeners()
+        {
+            return Collections.emptyList();
+        }
+
+        /**
+         * @see org.apache.jasper.servlet.TldScanner#scanJars()
+         */
+        @Override
+        public void scanJars()
+        {
+           return; //do nothing
+        }
+    }
+
+    /**
+     * Make a TldScanner, and prefeed it the tlds that have already been discovered in jar files
+     * by the MetaInfConfiguration.
+     * 
+     * @see org.apache.jasper.servlet.JasperInitializer#prepareScanner(javax.servlet.ServletContext, boolean, boolean, boolean)
+     */
+    @Override
+    public TldScanner newTldScanner(ServletContext context, boolean namespaceAware, boolean validate, boolean blockExternal)
+    {  
+        String tmp = context.getInitParameter("org.eclipse.jetty.jsp.precompiled");
+        if (tmp!=null && !tmp.equals("") && Boolean.valueOf(tmp))
+        {
+            if (LOG.isDebugEnabled()) LOG.debug("Jsp precompilation detected");
+            return new NullTldScanner(context, namespaceAware, validate, blockExternal);
+        }
+        
+        Collection<URL> tldUrls = (Collection<URL>)context.getAttribute("org.eclipse.jetty.tlds");
+        if (tldUrls != null)
+        {
+            if (LOG.isDebugEnabled()) LOG.debug("Tld pre-scan detected");
+            return new TldPreScanned(context,namespaceAware,validate,blockExternal,tldUrls);
+        }
+        
+        if (LOG.isDebugEnabled()) LOG.debug("Defaulting to jasper tld scanning");
+        return super.newTldScanner(context, namespaceAware, validate, blockExternal);
+    }
+    
+
+}
diff --git a/apache-jsp/src/main/java/org/eclipse/jetty/apache/jsp/JuliLog.java b/apache-jsp/src/main/java/org/eclipse/jetty/apache/jsp/JuliLog.java
new file mode 100644
index 0000000..03ecbb4
--- /dev/null
+++ b/apache-jsp/src/main/java/org/eclipse/jetty/apache/jsp/JuliLog.java
@@ -0,0 +1,188 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.apache.jsp;
+
+public class JuliLog implements org.apache.juli.logging.Log 
+{
+    public static org.apache.juli.logging.Log getInstance(String name)
+    {
+        return new JuliLog(name);
+    }
+    
+    private final org.eclipse.jetty.util.log.Logger _logger;
+    private final org.eclipse.jetty.util.log.StdErrLog _stdErrLog;
+
+    public JuliLog()
+    {    
+        _logger=org.eclipse.jetty.util.log.Log.getRootLogger();
+        _stdErrLog=(_logger instanceof org.eclipse.jetty.util.log.StdErrLog) ? (org.eclipse.jetty.util.log.StdErrLog)_logger:null;
+    }
+    
+    public JuliLog(String name)
+    {
+        _logger=org.eclipse.jetty.util.log.Log.getLogger(name);
+        _stdErrLog=(_logger instanceof org.eclipse.jetty.util.log.StdErrLog) ? (org.eclipse.jetty.util.log.StdErrLog)_logger:null;
+    }
+    
+    @Override
+    public boolean isDebugEnabled()
+    {
+        return _logger.isDebugEnabled();
+    }
+
+    @Override
+    public boolean isErrorEnabled()
+    {
+        return _stdErrLog==null?true:_stdErrLog.getLevel()<=org.eclipse.jetty.util.log.StdErrLog.LEVEL_WARN;
+    }
+
+    @Override
+    public boolean isFatalEnabled()
+    {
+        return _stdErrLog==null?true:_stdErrLog.getLevel()<=org.eclipse.jetty.util.log.StdErrLog.LEVEL_WARN;
+    }
+
+    @Override
+    public boolean isInfoEnabled()
+    {
+        return _stdErrLog==null?true:_stdErrLog.getLevel()<=org.eclipse.jetty.util.log.StdErrLog.LEVEL_INFO;
+    }
+
+    @Override
+    public boolean isTraceEnabled()
+    {
+        return _stdErrLog==null?true:_stdErrLog.getLevel()<=org.eclipse.jetty.util.log.StdErrLog.LEVEL_DEBUG;
+    }
+
+    @Override
+    public boolean isWarnEnabled()
+    {
+        return _stdErrLog==null?true:_stdErrLog.getLevel()<=org.eclipse.jetty.util.log.StdErrLog.LEVEL_WARN;
+    }
+
+    @Override
+    public void trace(Object message)
+    {
+        if (message instanceof String)
+            _logger.debug((String)message);
+        else
+            _logger.debug("{}",message);
+    }
+
+    @Override
+    public void trace(Object message, Throwable t)
+    {
+        if (message instanceof String)
+            _logger.debug((String)message,t);
+        else
+            _logger.debug("{}",message,t);
+    }
+
+    @Override
+    public void debug(Object message)
+    {
+        if (message instanceof String)
+            _logger.debug((String)message);
+        else
+            _logger.debug("{}",message);
+    }
+
+    @Override
+    public void debug(Object message, Throwable t)
+    {
+        if (message instanceof String)
+            _logger.debug((String)message,t);
+        else
+            _logger.debug("{}",message,t);
+    }
+
+    @Override
+    public void info(Object message)
+    {
+        if (message instanceof String)
+            _logger.info((String)message);
+        else
+            _logger.info("{}",message);
+    }
+
+    @Override
+    public void info(Object message, Throwable t)
+    {
+        if (message instanceof String)
+            _logger.info((String)message,t);
+        else
+            _logger.info("{}",message,t);
+    }
+
+    @Override
+    public void warn(Object message)
+    {
+        if (message instanceof String)
+            _logger.warn((String)message);
+        else
+            _logger.warn("{}",message);
+    }
+
+    @Override
+    public void warn(Object message, Throwable t)
+    {
+        if (message instanceof String)
+            _logger.warn((String)message,t);
+        else
+            _logger.warn("{}",message,t);
+    }
+
+    @Override
+    public void error(Object message)
+    {
+        if (message instanceof String)
+            _logger.warn((String)message);
+        else
+            _logger.warn("{}",message);
+    }
+
+    @Override
+    public void error(Object message, Throwable t)
+    {
+        if (message instanceof String)
+            _logger.warn((String)message,t);
+        else
+            _logger.warn("{}",message,t);
+    }
+
+    @Override
+    public void fatal(Object message)
+    {
+        if (message instanceof String)
+            _logger.warn((String)message);
+        else
+            _logger.warn("{}",message);
+    }
+
+    @Override
+    public void fatal(Object message, Throwable t)
+    {
+        if (message instanceof String)
+            _logger.warn((String)message,t);
+        else
+            _logger.warn("{}",message,t);
+    }
+}
+
+
diff --git a/apache-jsp/src/main/java/org/eclipse/jetty/jsp/JettyJspServlet.java b/apache-jsp/src/main/java/org/eclipse/jetty/jsp/JettyJspServlet.java
new file mode 100644
index 0000000..332f651
--- /dev/null
+++ b/apache-jsp/src/main/java/org/eclipse/jetty/jsp/JettyJspServlet.java
@@ -0,0 +1,111 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.jsp;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.jasper.servlet.JspServlet;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.resource.Resource;
+
+/**
+ * JettyJspServlet
+ *
+ * Wrapper for the jsp servlet that handles receiving requests mapped from 
+ * jsp-property-groups. Mappings could be wildcard urls like "/*", which would
+ * include welcome files, but we need those to be handled by the DefaultServlet.
+ */
+public class JettyJspServlet extends JspServlet
+{
+
+    /**
+     * 
+     */
+    private static final long serialVersionUID = -5387857473125086791L;
+
+    
+    
+    
+    
+    @Override
+    public void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+    {
+        HttpServletRequest request = null;
+        if (req instanceof HttpServletRequest)
+            request = (HttpServletRequest)req;
+        else
+            throw new ServletException("Request not HttpServletRequest");
+
+        String servletPath=null;
+        String pathInfo=null;
+        if (request.getAttribute("javax.servlet.include.request_uri")!=null)
+        {
+            servletPath=(String)request.getAttribute("javax.servlet.include.servlet_path");
+            pathInfo=(String)request.getAttribute("javax.servlet.include.path_info");
+            if (servletPath==null)
+            {
+                servletPath=request.getServletPath();
+                pathInfo=request.getPathInfo();
+            }
+        }
+        else
+        {
+            servletPath = request.getServletPath();
+            pathInfo = request.getPathInfo();
+        }
+        
+        String pathInContext = URIUtil.addPaths(servletPath,pathInfo);
+    
+        String jspFile = getInitParameter("jspFile");
+
+        //if this is a forced-path from a jsp-file, we want the jsp servlet to handle it,
+        //otherwise the default servlet might handle it
+        if (jspFile == null)
+        {
+            if (pathInContext.endsWith("/"))
+            {
+                //dispatch via forward to the default servlet
+                getServletContext().getNamedDispatcher("default").forward(req, resp);
+                return;
+            }
+            else
+            {      
+                //check if it resolves to a directory
+                Resource resource = ((ContextHandler.Context)getServletContext()).getContextHandler().getResource(pathInContext);    
+
+                if (resource!=null && resource.isDirectory())
+                {
+                    //dispatch via forward to the default servlet
+                    getServletContext().getNamedDispatcher("default").forward(req, resp);
+                    return;
+                }
+            }
+        }
+        
+        //fall through to the normal jsp servlet handling
+        super.service(req, resp);
+    }
+
+    
+}
diff --git a/apache-jsp/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer b/apache-jsp/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer
new file mode 100644
index 0000000..6346705
--- /dev/null
+++ b/apache-jsp/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer
@@ -0,0 +1 @@
+org.eclipse.jetty.apache.jsp.JettyJasperInitializer
diff --git a/apache-jsp/src/main/resources/META-INF/services/org.apache.juli.logging.Log b/apache-jsp/src/main/resources/META-INF/services/org.apache.juli.logging.Log
new file mode 100644
index 0000000..efa397b
--- /dev/null
+++ b/apache-jsp/src/main/resources/META-INF/services/org.apache.juli.logging.Log
@@ -0,0 +1 @@
+org.eclipse.jetty.apache.jsp.JuliLog
diff --git a/apache-jstl/pom.xml b/apache-jstl/pom.xml
new file mode 100644
index 0000000..8f01093
--- /dev/null
+++ b/apache-jstl/pom.xml
@@ -0,0 +1,49 @@
+<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">
+  <parent>
+    <groupId>org.eclipse.jetty</groupId>
+    <artifactId>jetty-project</artifactId>
+    <version>9.2.13.v20150730</version>
+  </parent>
+  <modelVersion>4.0.0</modelVersion>
+  <artifactId>apache-jstl</artifactId>
+  <name>Apache :: JSTL module</name>
+  <url>http://tomcat.apache.org/taglibs/standard/</url>
+  <packaging>jar</packaging>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-assembly-plugin</artifactId>
+        <executions>
+          <execution>
+            <phase>package</phase>
+            <goals>
+              <goal>single</goal>
+            </goals>
+            <configuration>
+              <descriptorRefs>
+                <descriptorRef>config</descriptorRef>
+              </descriptorRefs>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+
+  <dependencies>
+    <!-- JSTL Api -->
+    <dependency>
+       <groupId>org.apache.taglibs</groupId>
+       <artifactId>taglibs-standard-spec</artifactId>
+    </dependency>
+
+    <!-- JSTL Impl -->
+    <dependency>
+       <groupId>org.apache.taglibs</groupId>
+       <artifactId>taglibs-standard-impl</artifactId>
+    </dependency>
+  </dependencies>
+
+</project>
diff --git a/apache-jstl/src/main/config/modules/jsp-impl/apache-jstl.mod b/apache-jstl/src/main/config/modules/jsp-impl/apache-jstl.mod
new file mode 100644
index 0000000..804b191
--- /dev/null
+++ b/apache-jstl/src/main/config/modules/jsp-impl/apache-jstl.mod
@@ -0,0 +1,8 @@
+#
+# Apache JSTL 
+#
+[name]
+jstl-impl
+
+[lib]
+lib/apache-jstl/*.jar
diff --git a/apache-jstl/src/main/resources/readme.txt b/apache-jstl/src/main/resources/readme.txt
new file mode 100644
index 0000000..a516023
--- /dev/null
+++ b/apache-jstl/src/main/resources/readme.txt
@@ -0,0 +1,4 @@
+This empty jar file is purely to work around a problem with the Maven Dependency plugin.
+Several modules in jetty use the Dependency plugin to copy or unpack the dependencies of  other modules.
+However, the Dependency plugin is not capable of unpacking or copying a dependency of type 'pom', which
+this module is, as it consists purely of external dependencies needed to run jsp.
diff --git a/dists/jetty-deb/pom.xml b/dists/jetty-deb/pom.xml
new file mode 100644
index 0000000..9ce00e1
--- /dev/null
+++ b/dists/jetty-deb/pom.xml
@@ -0,0 +1,103 @@
+<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>
+  <parent>
+    <groupId>org.eclipse.jetty.dist</groupId>
+    <artifactId>dist-parent</artifactId>
+    <version>9.0.0-SNAPSHOT</version>
+  </parent>
+  <artifactId>jetty-deb</artifactId>
+  <name>Jetty :: Unix Distributions :: Debian</name>
+  <packaging>deb</packaging>
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.mortbay.jetty.toolchain</groupId>
+        <artifactId>unix-maven-plugin</artifactId>
+        <version>1.0-alpha-6.1</version>
+        <extensions>true</extensions>
+        <configuration>
+          <contact>Jetty Project</contact>
+          <contactEmail>jetty-dev at eclipse.org</contactEmail>
+          <name>Core Jetty ${project.version} Distribution</name>
+          <description>Jetty provides an Web server and javax.servlet
+            container, plus support for Web Sockets, OSGi, JMX, JNDI,
+            JASPI, AJP and many other integrations. These components are
+            open source and available for commercial use and
+            distribution.</description>
+          <deb>
+            <useFakeroot>false</useFakeroot>
+            <priority>optional</priority>
+            <section>java</section>
+          </deb>
+           <packages>
+            <package>
+              <id>jetty-server</id>
+              <assembly>
+                <extractArtifact>
+                  <artifact>org.eclipse.jetty:jetty-distribution:zip</artifact>
+                  <to>/usr/share/jetty9</to>
+                  <pattern>/jetty-distribution-${project.version}(.*)</pattern>
+                  <replacement>$1</replacement>
+                  <excludes>
+                    <exclude>jetty-distribution-*/javadoc</exclude>
+                    <exclude>jetty-distribution-*/javadoc/**</exclude>
+                    <exclude>jetty-distribution-*/logs/**</exclude>
+                    <exclude>jetty-distribution-*/bin/**</exclude>
+                    <exclude>jetty-distribution-*/etc/**</exclude>
+                    <exclude>jetty-distribution-*/webapps/**</exclude>                  
+                    <exclude>jetty-distribution-*/*.html</exclude>
+                    <exclude>jetty-distribution-*/*.txt</exclude>             
+                  </excludes>
+                 </extractArtifact>
+                 <extractArtifact>
+                  <artifact>org.eclipse.jetty:jetty-distribution:zip</artifact>
+                  <to>/usr/share/doc/jetty9</to>
+                  <pattern>/jetty-distribution-${project.version}(.*)</pattern>
+                  <replacement>$1</replacement>
+                  <excludes>
+                    <include>jetty-distribution-*/*.html</include>
+                    <include>jetty-distribution-*/*.txt</include> 
+                    <exclude>jetty-distribution-*/**</exclude>           
+                  </excludes>
+                 </extractArtifact>
+                 <extractArtifact>
+                  <artifact>org.eclipse.jetty:jetty-distribution:zip</artifact>
+                  <to>/etc/jetty9</to>
+                  <pattern>/jetty-distribution-${project.version}(.*)</pattern>
+                  <replacement>$1</replacement>
+                  <excludes>
+                    <include>jetty-distribution-*/etc/**</include>
+                    <!-- exclude>jetty-distribution-*/**</exclude-->           
+                  </excludes>
+                 </extractArtifact>
+              </assembly>
+            </package>
+            <package>
+              <id>jetty-test-webapp</id>
+              <classifier>test-webapp</classifier>
+              <assembly>
+                <extractArtifact>
+                  <artifact>org.eclipse.jetty:jetty-distribution:zip</artifact>
+                  <to>/var/lib/jetty9/webapps</to>
+                  <pattern>/jetty-distribution-${project.version}(.*)</pattern>
+                  <replacement>$1</replacement>
+                  <includes>
+                    <include>jetty-distribution-*/webapps/**</include>
+                  </includes>
+                </extractArtifact>
+              </assembly>
+            </package>
+          </packages>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+  <dependencies>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-distribution</artifactId>
+      <version>${project.version}</version>
+      <type>zip</type>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/dists/jetty-deb/src/main/unix/scripts/postinst b/dists/jetty-deb/src/main/unix/scripts/postinst
new file mode 100644
index 0000000..f6d7813
--- /dev/null
+++ b/dists/jetty-deb/src/main/unix/scripts/postinst
@@ -0,0 +1,20 @@
+#!/bin/bash
+
+LOG_DIR=/var/lib/jetty9/logs
+WEBAPP_DIR=/var/lib/jetty9/webapps
+
+# copy the jetty start script into place
+cp /usr/share/jetty9/bin/jetty.sh /etc/init.d/jetty
+
+# make it generally executable
+chmod 755 /etc/init.d/jetty
+
+# ensure we have a logging directory
+if [ ! -d "$LOG_DIR" ]; then
+  mkdir $LOG_DIR
+fi
+
+# ensure we have a webapps directory
+if [ ! -d "$WEBAPP_DIR" ]; then
+  mkdir $WEBAPP_DIR
+fi
diff --git a/dists/jetty-deb/src/main/unix/scripts/postrm b/dists/jetty-deb/src/main/unix/scripts/postrm
new file mode 100644
index 0000000..1f7f3e3
--- /dev/null
+++ b/dists/jetty-deb/src/main/unix/scripts/postrm
@@ -0,0 +1,48 @@
+#!/bin/bash
+
+#rm -f /etc/init.d/jetty
+
+ case "$1" in
+  purge)
+ [...]
+    # find first and last SYSTEM_UID numbers
+    for LINE in `grep SYSTEM_UID /etc/adduser.conf | grep -v "^#"`; do
+       case $LINE in
+          FIRST_SYSTEM_UID*)
+            FIST_SYSTEM_UID=`echo $LINE | cut -f2 -d '='`
+            ;;
+          LAST_SYSTEM_UID*)
+            LAST_SYSTEM_UID=`echo $LINE | cut -f2 -d '='`
+            ;;
+          *)
+            ;;
+          esac
+    done
+    # Remove system account if necessary
+    CREATEDUSER="jetty"
+    if [ -n "$FIST_SYSTEM_UID" ] && [ -n "$LAST_SYSTEM_UID" ]; then
+     if USERID=`getent passwd $CREATEDUSER | cut -f 3 -d ':'`; then
+       if [ -n "$USERID" ]; then
+         if [ "$FIST_SYSTEM_UID" -le "$USERID" ] && \
+            [ "$USERID" -le "$LAST_SYSTEM_UID" ]; then
+               echo -n "Removing $CREATEDUSER system user.."
+               deluser --quiet $CREATEDUSER || true
+               echo "..done"
+         fi
+       fi
+     fi
+   fi
+   # Remove system group if necessary
+   CREATEDGROUP="jetty"
+   FIRST_USER_GID=`grep ^USERS_GID /etc/adduser.conf | cut -f2 -d '='`
+   if [ -n "$FIST_USER_GID" ] then
+     if GROUPGID=`getent group $CREATEDGROUP | cut -f 3 -d ':'`; then
+       if [ -n "$GROUPGID" ]; then
+         if [ "$FIST_USER_GID" -gt "$GROUPGID" ]; then
+           echo -n "Removing $CREATEDGROUP group.."
+           delgroup --only-if-empty $CREATEDGROUP || true
+           echo "..done"
+         fi
+       fi
+     fi
+   fi
\ No newline at end of file
diff --git a/dists/jetty-deb/src/main/unix/scripts/preinst b/dists/jetty-deb/src/main/unix/scripts/preinst
new file mode 100644
index 0000000..423b675
--- /dev/null
+++ b/dists/jetty-deb/src/main/unix/scripts/preinst
@@ -0,0 +1,61 @@
+#!/bin/bash
+
+ case "$1" in
+   install|upgrade)
+ 
+   # If the package has default file it could be sourced, so that
+   # the local admin can overwrite the defaults
+ 
+   [ -f "/etc/default/jetty9" ] && . /etc/default/jetty9
+ 
+   # Sane defaults:
+ 
+   [ -z "$SERVER_HOME" ] && SERVER_HOME=/usr/share/jetty9
+   [ -z "$SERVER_USER" ] && SERVER_USER=jetty
+   [ -z "$SERVER_NAME" ] && SERVER_NAME="Jetty-9 Http and Servlet Engine"
+   [ -z "$SERVER_GROUP" ] && SERVER_GROUP=jetty
+ 
+   # Groups that the user will be added to, if undefined, then none.
+   ADDGROUP=""
+ 
+   # create user to avoid running server as root
+   # 1. create group if not existing
+   if ! getent group | grep -q "^$SERVER_GROUP:" ; then
+      echo -n "Adding group $SERVER_GROUP.."
+      addgroup --quiet --system $SERVER_GROUP 2>/dev/null ||true
+      echo "..done"
+   fi
+   # 2. create homedir if not existing
+   test -d $SERVER_HOME || mkdir $SERVER_HOME
+   # 3. create user if not existing
+   if ! getent passwd | grep -q "^$SERVER_USER:"; then
+     echo -n "Adding system user $SERVER_USER.."
+     adduser --quiet \
+             --system \
+             --ingroup $SERVER_GROUP \
+             --no-create-home \
+             --disabled-password \
+             $SERVER_USER 2>/dev/null || true
+     echo "..done"
+   fi
+   # 4. adjust passwd entry
+   usermod -c "$SERVER_NAME" \
+           -d $SERVER_HOME   \
+           -g $SERVER_GROUP  \
+              $SERVER_USER
+   # 5. adjust file and directory permissions
+   if ! dpkg-statoverride --list $SERVER_HOME >/dev/null
+   then
+       chown -R $SERVER_USER:$SERVER_GROUP $SERVER_HOME
+       chmod u=rwx,g=rxs,o= $SERVER_HOME
+   fi
+   # 6. Add the user to the ADDGROUP group
+   if test -n $ADDGROUP
+   then
+       if ! groups $SERVER_USER | cut -d: -f2 | \
+          grep -qw $ADDGROUP; then
+            adduser $SERVER_USER $ADDGROUP
+       fi
+   fi
+   ;;
+   configure)
\ No newline at end of file
diff --git a/dists/pom.xml b/dists/pom.xml
new file mode 100644
index 0000000..c6ad38d
--- /dev/null
+++ b/dists/pom.xml
@@ -0,0 +1,26 @@
+<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/xsd/maven-4.0.0.xsd">
+	<parent>
+		<artifactId>jetty-project</artifactId>
+		<groupId>org.eclipse.jetty</groupId>
+		<version>9.0.0-SNAPSHOT</version>
+	</parent>
+	<modelVersion>4.0.0</modelVersion>
+	<groupId>org.eclipse.jetty.dist</groupId>
+	<artifactId>dist-parent</artifactId>
+	<packaging>pom</packaging>
+	<name>Jetty :: Distribution :: Parent</name>
+	<profiles>
+	  <profile>
+	    <id>linux-packaging</id>
+	    <!-- activation>
+	      <os>
+	        <name>Linux</name>
+	      </os>
+	    </activation-->
+	    <modules>
+	      <module>jetty-deb</module>
+	      <!--module>jetty-rpm</module-->
+	    </modules>
+	   </profile>
+	</profiles>
+</project>
diff --git a/eclipse-jetty-templates.xml b/eclipse-jetty-templates.xml
deleted file mode 100644
index 1d077d0..0000000
--- a/eclipse-jetty-templates.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<templates>
-<template autoinsert="true" context="java" deleted="false" description="Create Jetty Logger constant" enabled="true" name="logjetty">${:import(
-  org.eclipse.jetty.util.log.Log,
-  org.eclipse.jetty.util.log.Logger)
-}private static final Logger LOG = Log.getLogger(${enclosing_type}.class);
-</template>
-</templates>
diff --git a/example-async-rest/async-rest-jar/pom.xml b/example-async-rest/async-rest-jar/pom.xml
deleted file mode 100644
index 9966787..0000000
--- a/example-async-rest/async-rest-jar/pom.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<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">
-  <parent>
-    <groupId>org.eclipse.jetty</groupId>
-    <artifactId>example-async-rest</artifactId>
-    <version>8.1.17.v20150415</version>
-  </parent>
-  <modelVersion>4.0.0</modelVersion>
-  <groupId>org.eclipse.jetty.example-async-rest</groupId>
-  <artifactId>example-async-rest-jar</artifactId>
-  <packaging>jar</packaging>
-  <name>Example Async Rest :: Jar</name>
-  <dependencies>
-    <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-client</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-    <dependency>
-         <groupId>org.eclipse.jetty.orbit</groupId>
-         <artifactId>javax.servlet</artifactId>
-         <scope>provided</scope>
-       </dependency>
-  </dependencies>
-</project>
diff --git a/example-async-rest/async-rest-jar/src/main/java/org/eclipse/jetty/example/asyncrest/AbstractRestServlet.java b/example-async-rest/async-rest-jar/src/main/java/org/eclipse/jetty/example/asyncrest/AbstractRestServlet.java
deleted file mode 100644
index 43044ed..0000000
--- a/example-async-rest/async-rest-jar/src/main/java/org/eclipse/jetty/example/asyncrest/AbstractRestServlet.java
+++ /dev/null
@@ -1,120 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.example.asyncrest;
-
-import java.io.IOException;
-import java.math.BigDecimal;
-import java.math.RoundingMode;
-import java.net.URLEncoder;
-import java.util.Map;
-import java.util.Queue;
-
-import javax.servlet.ServletConfig;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-/**
- * Abstract Servlet implementation class AsyncRESTServlet.
- * Enquires ebay REST service for auctions by key word.
- * May be configured with init parameters: <dl>
- * <dt>appid</dt><dd>The eBay application ID to use</dd>
- * </dl>
- * Each request examines the following request parameters:<dl>
- * <dt>items</dt><dd>The keyword to search for</dd>
- * </dl>
- */
-public class AbstractRestServlet extends HttpServlet
-{
-    protected final static String __DEFAULT_APPID = "Webtide81-adf4-4f0a-ad58-d91e41bbe85";
-    protected final static String STYLE = 
-        "<style type='text/css'>"+
-        "  img.thumb:hover {height:50px}"+
-        "  img.thumb {vertical-align:text-top}"+
-        "  span.red {color: #ff0000}"+
-        "  span.green {color: #00ff00}"+
-        "  iframe {border: 0px}"+
-        "</style>";
-
-    protected final static String ITEMS_PARAM = "items";
-    protected final static String APPID_PARAM = "appid";
-
-    protected String _appid;
-
-    public void init(ServletConfig servletConfig) throws ServletException
-    {
-        if (servletConfig.getInitParameter(APPID_PARAM) == null)
-            _appid = __DEFAULT_APPID;
-        else
-            _appid = servletConfig.getInitParameter(APPID_PARAM);
-    }
-    
-    protected String restURL(String item) 
-    {
-        try
-        {
-            return ("http://open.api.ebay.com/shopping?MaxEntries=3&appid=" + _appid + 
-                    "&version=573&siteid=0&callname=FindItems&responseencoding=JSON&QueryKeywords=" + 
-                    URLEncoder.encode(item,"UTF-8"));
-        }
-        catch(Exception e)
-        {
-            throw new RuntimeException(e);
-        }
-    }
-    
-    protected String generateThumbs(Queue<Map<String,String>> results)
-    {
-        StringBuilder thumbs = new StringBuilder();
-        for (Map<String, String> m : results)
-        {
-            if (!m.containsKey("GalleryURL"))
-                continue;
-                
-            thumbs.append("<a href=\""+m.get("ViewItemURLForNaturalSearch")+"\">");
-            thumbs.append("<img class='thumb' border='1px' height='25px'"+
-                        " src='"+m.get("GalleryURL")+"'"+
-                        " title='"+m.get("Title")+"'"+
-                        "/>");
-            thumbs.append("</a> ");
-        }
-        return thumbs.toString();
-    }
-
-    protected String ms(long nano)
-    {
-        BigDecimal dec = new BigDecimal(nano);
-        return dec.divide(new BigDecimal(1000000L)).setScale(1,RoundingMode.UP).toString();
-    }
-    
-    protected int width(long nano)
-    {
-        int w=(int)((nano+999999L)/5000000L);
-        if (w==0)
-            w=2;
-        return w;
-    }
-    
-    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
-    {
-        doGet(request, response);
-    }
-
-}
diff --git a/example-async-rest/async-rest-jar/src/main/java/org/eclipse/jetty/example/asyncrest/AsyncRestServlet.java b/example-async-rest/async-rest-jar/src/main/java/org/eclipse/jetty/example/asyncrest/AsyncRestServlet.java
deleted file mode 100644
index 16b6d03..0000000
--- a/example-async-rest/async-rest-jar/src/main/java/org/eclipse/jetty/example/asyncrest/AsyncRestServlet.java
+++ /dev/null
@@ -1,211 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.example.asyncrest;
-
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.util.Map;
-import java.util.Queue;
-import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import javax.servlet.AsyncContext;
-import javax.servlet.ServletConfig;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jetty.client.ContentExchange;
-import org.eclipse.jetty.client.HttpClient;
-import org.eclipse.jetty.util.ajax.JSON;
-
-/**
- * Servlet implementation class AsyncRESTServlet.
- * Enquires ebay REST service for auctions by key word.
- * May be configured with init parameters: <dl>
- * <dt>appid</dt><dd>The eBay application ID to use</dd>
- * </dl>
- * Each request examines the following request parameters:<dl>
- * <dt>items</dt><dd>The keyword to search for</dd>
- * </dl>
- */
-public class AsyncRestServlet extends AbstractRestServlet
-{
-    final static String RESULTS_ATTR = "org.eclipse.jetty.demo.client";
-    final static String DURATION_ATTR = "org.eclipse.jetty.demo.duration";
-    final static String START_ATTR = "org.eclispe.jetty.demo.start";
-
-    HttpClient _client;
-
-    public void init(ServletConfig servletConfig) throws ServletException
-    {
-        super.init(servletConfig);
-        
-        _client = new HttpClient();
-        _client.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);
-
-        try
-        {
-            _client.start();
-        }
-        catch (Exception e)
-        {
-            throw new ServletException(e);
-        }
-    }
-
-    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
-    {
-        Long start=System.nanoTime();
-        
-        // Do we have results yet?
-        Queue<Map<String, String>> results = (Queue<Map<String, String>>) request.getAttribute(RESULTS_ATTR);
-        
-        // If no results, this must be the first dispatch, so send the REST request(s)
-        if (results==null)
-        {
-            // define results data structures
-            final Queue<Map<String, String>> resultsQueue = new ConcurrentLinkedQueue<Map<String,String>>();
-            request.setAttribute(RESULTS_ATTR, results=resultsQueue);
-            
-            // suspend the request
-            // This is done before scheduling async handling to avoid race of 
-            // dispatch before startAsync!
-            final AsyncContext async = request.startAsync();
-            async.setTimeout(30000);
-
-            // extract keywords to search for
-            String[] keywords=request.getParameter(ITEMS_PARAM).split(",");
-            final AtomicInteger outstanding=new AtomicInteger(keywords.length);
-            
-            // Send request each keyword
-            for (final String item:keywords)
-            {
-                _client.send(
-                        new AsyncRestRequest(item)
-                        {
-                            void onAuctionFound(Map<String,String> auction)
-                            {
-                                resultsQueue.add(auction);
-                            }
-                            void onComplete()
-                            {
-                                if (outstanding.decrementAndGet()<=0)
-                                    async.dispatch();
-                            }
-                        });
-            }
-            
-            // save timing info and return
-            request.setAttribute(START_ATTR, start);
-            request.setAttribute(DURATION_ATTR, new Long(System.nanoTime() - start));
-
-            return;
-        }
-
-        // We have results!
-        
-        // Generate the response
-        String thumbs = generateThumbs(results);
-        
-        response.setContentType("text/html");
-        PrintWriter out = response.getWriter();
-        out.println("<html><head>");
-        out.println(STYLE);
-        out.println("</head><body><small>");
-
-        long initial = (Long) request.getAttribute(DURATION_ATTR);
-        long start0 = (Long) request.getAttribute(START_ATTR);
-
-        long now = System.nanoTime();
-        long total=now-start0;
-        long generate=now-start;
-        long thread=initial+generate;
-        
-        out.print("<b>Asynchronous: "+request.getParameter(ITEMS_PARAM)+"</b><br/>");
-        out.print("Total Time: "+ms(total)+"ms<br/>");
-
-        out.print("Thread held (<span class='red'>red</span>): "+ms(thread)+"ms (" + ms(initial) + " initial + " + ms(generate) + " generate )<br/>");
-        out.print("Async wait (<span class='green'>green</span>): "+ms(total-thread)+"ms<br/>");
-        
-        out.println("<img border='0px' src='asyncrest/red.png'   height='20px' width='"+width(initial)+"px'>"+
-                    "<img border='0px' src='asyncrest/green.png' height='20px' width='"+width(total-thread)+"px'>"+
-                    "<img border='0px' src='asyncrest/red.png'   height='20px' width='"+width(generate)+"px'>");
-        
-        out.println("<hr />");
-        out.println(thumbs);
-        out.println("</small>");
-        out.println("</body></html>");
-        out.close();
-    }
-
-    private abstract class AsyncRestRequest extends ContentExchange
-    {
-        AsyncRestRequest(final String item)
-        {
-            // send the exchange
-            setMethod("GET");
-            setURL(restURL(item));   
-        }
-        
-        abstract void onAuctionFound(Map<String,String> details);
-        abstract void onComplete();
-        
-        protected void onResponseComplete() throws IOException
-        {
-            // extract auctions from the results
-            Map<String,?> query = (Map<String,?>) JSON.parse(this.getResponseContent());
-            Object[] auctions = (Object[]) query.get("Item");
-            if (auctions != null)
-            {
-                for (Object o : auctions)
-                    onAuctionFound((Map<String,String>)o);
-            }
-
-            onComplete();
-        }
-
-        /* ------------------------------------------------------------ */
-        protected void onConnectionFailed(Throwable ex)
-        {
-            getServletContext().log("onConnectionFailed: ",ex);
-            onComplete();
-        }
-
-        /* ------------------------------------------------------------ */
-        protected void onException(Throwable ex)
-        {
-            getServletContext().log("onConnectionFailed: ",ex);
-            onComplete();
-        }
-
-        /* ------------------------------------------------------------ */
-        protected void onExpire()
-        {
-            getServletContext().log("onConnectionFailed: expired");
-            onComplete();
-        }
-    }
-
-    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
-    {
-        doGet(request, response);
-    }
-
-}
diff --git a/example-async-rest/async-rest-jar/src/main/java/org/eclipse/jetty/example/asyncrest/SerialRestServlet.java b/example-async-rest/async-rest-jar/src/main/java/org/eclipse/jetty/example/asyncrest/SerialRestServlet.java
deleted file mode 100644
index 9dce73e..0000000
--- a/example-async-rest/async-rest-jar/src/main/java/org/eclipse/jetty/example/asyncrest/SerialRestServlet.java
+++ /dev/null
@@ -1,106 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.example.asyncrest;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.PrintWriter;
-import java.net.HttpURLConnection;
-import java.net.URL;
-import java.util.LinkedList;
-import java.util.Map;
-import java.util.Queue;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jetty.util.ajax.JSON;
-
-/**
- * Servlet implementation class SerialRestServlet
- */
-public class SerialRestServlet extends AbstractRestServlet
-{   
-    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
-    {
-        long start = System.nanoTime();
-        
-
-        String[] keywords=request.getParameter(ITEMS_PARAM).split(",");
-        Queue<Map<String,String>> results = new LinkedList<Map<String,String>>();
-        
-        // make all requests serially
-        for (String itemName : keywords)
-        {
-            URL url = new URL(restURL(itemName));
-            
-            HttpURLConnection connection = (HttpURLConnection)url.openConnection();
-            connection.setRequestMethod("GET");
-            
-            Map query = (Map)JSON.parse(new BufferedReader(new InputStreamReader(connection.getInputStream())));
-            Object[] auctions = (Object[]) query.get("Item");
-            if (auctions != null)
-            {
-                for (Object o : auctions)
-                    results.add((Map) o);
-            }
-        }
-        
-
-        // Generate the response
-        String thumbs=generateThumbs(results);
-        
-        response.setContentType("text/html");
-        PrintWriter out = response.getWriter();
-        out.println("<html><head>");
-        out.println(STYLE);
-        out.println("</head><body><small>");
-
-        long now = System.nanoTime();
-        long total=now-start;
-
-        out.print("<b>Blocking: "+request.getParameter(ITEMS_PARAM)+"</b><br/>");
-        out.print("Total Time: "+ms(total)+"ms<br/>");
-        out.print("Thread held (<span class='red'>red</span>): "+ms(total)+"ms<br/>");
-        
-        out.println("<img border='0px' src='asyncrest/red.png'   height='20px' width='"+width(total)+"px'>");
-        
-        out.println("<hr />");
-        out.println(thumbs);
-        out.println("</small>");
-        out.println("</body></html>");
-        out.close();
-        
-        
-
-    }
-
-    /**
-     * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse
-     *      response)
-     */
-    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
-    {
-        doGet(request, response);
-    }
-
-}
diff --git a/example-async-rest/async-rest-jar/src/main/resources/META-INF/web-fragment.xml b/example-async-rest/async-rest-jar/src/main/resources/META-INF/web-fragment.xml
deleted file mode 100644
index 9876d99..0000000
--- a/example-async-rest/async-rest-jar/src/main/resources/META-INF/web-fragment.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<web-fragment>
-	<servlet>
-		<display-name>SerialRestServlet</display-name>
-		<servlet-name>SerialRestServlet</servlet-name>
-		<servlet-class>org.eclipse.jetty.example.asyncrest.SerialRestServlet</servlet-class>
-	</servlet>
-	<servlet-mapping>
-		<servlet-name>SerialRestServlet</servlet-name>
-		<url-pattern>/testSerial</url-pattern>
-	</servlet-mapping>
-	
-	<servlet>
-		<display-name>AsyncRestServlet</display-name>
-		<servlet-name>AsyncRestServlet</servlet-name>
-		<servlet-class>org.eclipse.jetty.example.asyncrest.AsyncRestServlet</servlet-class>
-		<async-supported>true</async-supported>
-	</servlet>
-	<servlet-mapping>
-		<servlet-name>AsyncRestServlet</servlet-name>
-		<url-pattern>/testAsync</url-pattern>
-	</servlet-mapping>	
-</web-fragment>
\ No newline at end of file
diff --git a/example-async-rest/async-rest-webapp/pom.xml b/example-async-rest/async-rest-webapp/pom.xml
deleted file mode 100644
index 477f4dc..0000000
--- a/example-async-rest/async-rest-webapp/pom.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<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">
-  <parent>
-    <groupId>org.eclipse.jetty</groupId>
-    <artifactId>example-async-rest</artifactId>
-    <version>8.1.17.v20150415</version>
-  </parent>
-  <modelVersion>4.0.0</modelVersion>
-  <groupId>org.eclipse.jetty.example-async-rest</groupId>
-  <artifactId>example-async-rest-webapp</artifactId>
-  <name>Example Async Rest :: Webapp</name>
-  <url>http://www.eclipse.org/jetty</url>
-  <packaging>war</packaging>
-  <build>
-    <finalName>async-rest</finalName>
-  </build>
-  <dependencies>
-    <dependency>
-      <groupId>org.eclipse.jetty.example-async-rest</groupId>
-      <artifactId>example-async-rest-jar</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-    <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-webapp</artifactId>
-      <version>${project.version}</version>
-      <scope>test</scope>
-    </dependency>
-      <dependency>
-         <groupId>org.eclipse.jetty.orbit</groupId>
-         <artifactId>javax.servlet</artifactId>
-         <scope>provided</scope>
-       </dependency>
-  </dependencies>
-</project>
diff --git a/example-async-rest/async-rest-webapp/src/main/webapp/WEB-INF/web.xml b/example-async-rest/async-rest-webapp/src/main/webapp/WEB-INF/web.xml
deleted file mode 100644
index 8916bdb..0000000
--- a/example-async-rest/async-rest-webapp/src/main/webapp/WEB-INF/web.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0"?>
-<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_3_0.xsd" 
-   version="3.0">
-
-	<display-name>Async REST Webservice Example</display-name>
-
-</web-app>
diff --git a/example-async-rest/async-rest-webapp/src/main/webapp/index.html b/example-async-rest/async-rest-webapp/src/main/webapp/index.html
deleted file mode 100644
index f92f7f6..0000000
--- a/example-async-rest/async-rest-webapp/src/main/webapp/index.html
+++ /dev/null
@@ -1,38 +0,0 @@
-<html>
- <head>
-  <style type='text/css'>
-    iframe {border: 0px}
-    table, tr, td {border: 0px}
-  </style>
-</head>
-<body>
-<h1>Blocking vs Asynchronous REST</h1>
-<p>
-This demo calls the EBay WS API both synchronously and asynchronously,
-to obtain items matching each of the keywords passed on the query
-string.  The time the request thread is head is displayed for both.
-</p>
-
-<table width='100%'>
-	
-<tr>
-<td>
-  <iframe id="f1" width='100%' height='175px' src="testSerial?items=kayak"></iframe>
-</td>
-<td>
-  <iframe id="f3" width='100%' height='175px' src="testSerial?items=mouse,beer,gnome"></iframe>
-</td>
-</tr>
- 
-<tr>
-<td>
-  <iframe id="f2" width='100%' height='175px' src="testAsync?items=kayak"/></iframe>
-</td>
-<td>
-  <iframe id="f4" width='100%' height='175px' src="testAsync?items=mouse,beer,gnome"/></iframe>
-</td>
-</tr>
-
-</table>
-</body>
-</html>
diff --git a/example-async-rest/async-rest-webapp/src/test/java/org/eclipse/jetty/example/asyncrest/DemoServer.java b/example-async-rest/async-rest-webapp/src/test/java/org/eclipse/jetty/example/asyncrest/DemoServer.java
deleted file mode 100644
index 4881ba3..0000000
--- a/example-async-rest/async-rest-webapp/src/test/java/org/eclipse/jetty/example/asyncrest/DemoServer.java
+++ /dev/null
@@ -1,48 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.example.asyncrest;
-
-import org.eclipse.jetty.server.Connector;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
-import org.eclipse.jetty.webapp.WebAppContext;
-
-public class DemoServer
-{
-    public static void main(String[] args)
-        throws Exception
-    {
-        String jetty_home = System.getProperty("jetty.home",".");
-
-        Server server = new Server();
-        
-        Connector connector=new SelectChannelConnector();
-        connector.setPort(Integer.getInteger("jetty.port",8080).intValue());
-        server.setConnectors(new Connector[]{connector});
-        
-        WebAppContext webapp = new WebAppContext();
-        webapp.setContextPath("/");
-        webapp.setWar(jetty_home+"/target/example-async-rest-webapp-8.0.0.M0-SNAPSHOT");
-        
-        server.setHandler(webapp);
-        
-        server.start();
-        server.join();
-    }
-}
diff --git a/example-async-rest/pom.xml b/example-async-rest/pom.xml
deleted file mode 100644
index b39073e..0000000
--- a/example-async-rest/pom.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-<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">
-  <parent>
-    <groupId>org.eclipse.jetty</groupId>
-    <artifactId>jetty-project</artifactId>
-    <version>8.1.17.v20150415</version>
-  </parent>
-  <modelVersion>4.0.0</modelVersion>
-  <groupId>org.eclipse.jetty</groupId>
-  <artifactId>example-async-rest</artifactId>
-  <packaging>pom</packaging>
-  <name>Example Async Rest</name>
-  <url>http://www.eclipse.org/jetty</url>
-  <modules>
-    <module>async-rest-jar</module>
-    <module>async-rest-webapp</module>
-  </modules>
-</project>
diff --git a/example-jetty-embedded/pom.xml b/example-jetty-embedded/pom.xml
deleted file mode 100644
index e9b953b..0000000
--- a/example-jetty-embedded/pom.xml
+++ /dev/null
@@ -1,70 +0,0 @@
-<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">
-  <parent>
-    <groupId>org.eclipse.jetty</groupId>
-    <artifactId>jetty-project</artifactId>
-    <version>8.1.17.v20150415</version>
-  </parent>
-  <modelVersion>4.0.0</modelVersion>
-  <artifactId>example-jetty-embedded</artifactId>
-  <name>Example :: Jetty Embedded</name>
-  <description>Jetty Embedded Examples</description>
-  <url>http://www.eclipse.org/jetty</url>
-  <dependencies>
-    <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-webapp</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-    <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-security</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-    <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-servlets</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-    <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-rewrite</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-    <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-deploy</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-    <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-jmx</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-    <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-ajp</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-     <dependency>
-      <groupId>org.eclipse.jetty.toolchain</groupId>
-      <artifactId>jetty-test-helper</artifactId>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-websocket</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-    <dependency>
-      <groupId>junit</groupId>
-      <artifactId>junit</artifactId>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>test-jetty-servlet</artifactId>
-      <version>${project.version}</version>
-      <scope>test</scope>
-    </dependency>
-  </dependencies>
-</project>
diff --git a/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/DumpServlet.java b/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/DumpServlet.java
deleted file mode 100644
index d636321..0000000
--- a/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/DumpServlet.java
+++ /dev/null
@@ -1,53 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.embedded;
-
-import java.io.IOException;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
- at SuppressWarnings("serial")
-public class DumpServlet extends HttpServlet
-{
-    public DumpServlet()
-    {
-    }
-
-    @Override
-    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
-    {
-        response.setContentType("text/html");
-        response.setStatus(HttpServletResponse.SC_OK);
-        response.getWriter().println("<h1>DumpServlet</h1><pre>");
-        response.getWriter().println("requestURI=" + request.getRequestURI());
-        response.getWriter().println("contextPath=" + request.getContextPath());
-        response.getWriter().println("servletPath=" + request.getServletPath());
-        response.getWriter().println("pathInfo=" + request.getPathInfo());
-        response.getWriter().println("session=" + request.getSession(true).getId());
-        
-        String r=request.getParameter("resource");
-        if (r!=null)
-            response.getWriter().println("resource("+r+")=" + getServletContext().getResource(r));
-            
-        response.getWriter().println("</pre>");
-    }
-}
diff --git a/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/FileServer.java b/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/FileServer.java
deleted file mode 100644
index eaa071c..0000000
--- a/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/FileServer.java
+++ /dev/null
@@ -1,63 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.embedded;
-
-import org.eclipse.jetty.server.Handler;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.handler.DefaultHandler;
-import org.eclipse.jetty.server.handler.HandlerList;
-import org.eclipse.jetty.server.handler.ResourceHandler;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-
-/* ------------------------------------------------------------ */
-/** Simple Jetty FileServer.
- * This is a simple example of Jetty configured as a FileServer.
- * 
- * File server Usage - java org.eclipse.jetty.server.example.FileServer [ port [
- * docroot ]]
- * 
- * @see FileServerXml for the equivalent example done in XML configuration.
- * @author gregw
- * 
- */
-public class FileServer
-{
-    private static final Logger LOG = Log.getLogger(FileServer.class);
-
-    public static void main(String[] args) throws Exception
-    {
-        Server server = new Server(args.length == 0?8080:Integer.parseInt(args[0]));
-
-        ResourceHandler resource_handler = new ResourceHandler();
-        resource_handler.setDirectoriesListed(true);
-        resource_handler.setWelcomeFiles(new String[]{ "index.html" });
-
-        resource_handler.setResourceBase(args.length == 2?args[1]:".");
-        LOG.info("serving " + resource_handler.getBaseResource());
-        
-        HandlerList handlers = new HandlerList();
-        handlers.setHandlers(new Handler[] { resource_handler, new DefaultHandler() });
-        server.setHandler(handlers);
-
-        server.start();
-        server.join();
-    }
-
-}
diff --git a/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/FileServerXml.java b/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/FileServerXml.java
deleted file mode 100644
index 5af665d..0000000
--- a/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/FileServerXml.java
+++ /dev/null
@@ -1,44 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.embedded;
-
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.util.resource.Resource;
-import org.eclipse.jetty.xml.XmlConfiguration;
-
-
-/* ------------------------------------------------------------ */
-/** A Jetty FileServer.
- * This server is identical to {@link FileServer}, except that it
- * is configured via an {@link XmlConfiguration} config file that
- * does the identical work.
- * <p>
- * See <a href="http://git.eclipse.org/c/jetty/org.eclipse.jetty.project.git/tree/example-jetty-embedded/src/main/resources/fileserver.xml">fileserver.xml</a>
- */
-public class FileServerXml
-{
-    public static void main(String[] args) throws Exception
-    {
-        Resource fileserver_xml = Resource.newSystemResource("fileserver.xml");
-        XmlConfiguration configuration = new XmlConfiguration(fileserver_xml.getInputStream());
-        Server server = (Server)configuration.configure();
-        server.start();
-        server.join();
-    }
-}
diff --git a/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/HelloHandler.java b/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/HelloHandler.java
deleted file mode 100644
index bfa14f7..0000000
--- a/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/HelloHandler.java
+++ /dev/null
@@ -1,63 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.embedded;
-
-import java.io.IOException;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jetty.server.Request;
-import org.eclipse.jetty.server.handler.AbstractHandler;
-
-public class HelloHandler extends AbstractHandler
-{
-    final String _greeting;
-    final String _body;
-    
-    public HelloHandler()
-    {
-        _greeting="Hello World";
-        _body=null;
-    }
-    
-    public HelloHandler(String greeting)
-    {
-        _greeting=greeting;
-        _body=null;
-    }
-    
-    public HelloHandler(String greeting,String body)
-    {
-        _greeting=greeting;
-        _body=body;
-    }
-    
-    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
-    {
-        response.setContentType("text/html;charset=utf-8");
-        response.setStatus(HttpServletResponse.SC_OK);
-        baseRequest.setHandled(true);
-        
-        response.getWriter().println("<h1>"+_greeting+"</h1>");
-        if (_body!=null)
-            response.getWriter().println(_body);
-    }
-}
diff --git a/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/HelloServlet.java b/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/HelloServlet.java
deleted file mode 100644
index 9cb10c9..0000000
--- a/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/HelloServlet.java
+++ /dev/null
@@ -1,49 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.embedded;
-
-import java.io.IOException;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
- at SuppressWarnings("serial")
-public class HelloServlet extends HttpServlet
-{
-    String greeting = "Hello";
-
-    public HelloServlet()
-    {
-    }
-
-    public HelloServlet(String hi)
-    {
-        greeting = hi;
-    }
-
-    @Override
-    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
-    {
-        response.setContentType("text/html");
-        response.setStatus(HttpServletResponse.SC_OK);
-        response.getWriter().println("<h1>" + greeting + " from HelloServlet</h1>");
-    }
-}
diff --git a/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/LikeJettyXml.java b/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/LikeJettyXml.java
deleted file mode 100644
index 9340bda..0000000
--- a/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/LikeJettyXml.java
+++ /dev/null
@@ -1,170 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.embedded;
-
-import java.lang.management.ManagementFactory;
-
-import org.eclipse.jetty.deploy.DeploymentManager;
-import org.eclipse.jetty.deploy.providers.ContextProvider;
-import org.eclipse.jetty.deploy.providers.WebAppProvider;
-import org.eclipse.jetty.jmx.MBeanContainer;
-import org.eclipse.jetty.security.HashLoginService;
-import org.eclipse.jetty.server.AsyncNCSARequestLog;
-import org.eclipse.jetty.server.Connector;
-import org.eclipse.jetty.server.Handler;
-import org.eclipse.jetty.server.NCSARequestLog;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.handler.ContextHandlerCollection;
-import org.eclipse.jetty.server.handler.DefaultHandler;
-import org.eclipse.jetty.server.handler.HandlerCollection;
-import org.eclipse.jetty.server.handler.RequestLogHandler;
-import org.eclipse.jetty.server.handler.StatisticsHandler;
-import org.eclipse.jetty.server.nio.BlockingChannelConnector;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
-import org.eclipse.jetty.server.ssl.SslSelectChannelConnector;
-import org.eclipse.jetty.server.ssl.SslSocketConnector;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.ssl.SslContextFactory;
-import org.eclipse.jetty.util.thread.QueuedThreadPool;
-
-public class LikeJettyXml
-{
-    public static void main(String[] args) throws Exception
-    {
-        String jetty_home = System.getProperty("jetty.home","../jetty-distribution/target/distribution");
-        System.setProperty("jetty.home",jetty_home);
-
-        Server server = new Server();
-        server.setDumpAfterStart(true);
-        server.setDumpBeforeStop(true);
-        
-        // Setup JMX
-        MBeanContainer mbContainer=new MBeanContainer(ManagementFactory.getPlatformMBeanServer());
-        mbContainer.start();
-        server.getContainer().addEventListener(mbContainer);
-        server.addBean(mbContainer,true);
-        mbContainer.addBean(new Log());
-        
-        // Setup Threadpool
-        QueuedThreadPool threadPool = new QueuedThreadPool();
-        threadPool.setMaxThreads(500);
-        server.setThreadPool(threadPool);
-
-        // Setup Connectors
-        SelectChannelConnector connector = new SelectChannelConnector();
-        connector.setPort(8080);
-        connector.setMaxIdleTime(30000);
-        connector.setConfidentialPort(8443);
-        connector.setStatsOn(false);
-        
-        server.setConnectors(new Connector[]
-        { connector });
-        
-        BlockingChannelConnector bConnector = new BlockingChannelConnector();
-        bConnector.setPort(8888);
-        bConnector.setMaxIdleTime(30000);
-        bConnector.setConfidentialPort(8443);
-        bConnector.setAcceptors(1);
-        server.addConnector(bConnector);
-
-        SslSelectChannelConnector ssl_connector = new SslSelectChannelConnector();
-        ssl_connector.setPort(8443);
-        SslContextFactory cf = ssl_connector.getSslContextFactory();
-        cf.setKeyStorePath(jetty_home + "/etc/keystore");
-        cf.setKeyStorePassword("OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4");
-        cf.setKeyManagerPassword("OBF:1u2u1wml1z7s1z7a1wnl1u2g");
-        cf.setTrustStore(jetty_home + "/etc/keystore");
-        cf.setTrustStorePassword("OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4");
-        cf.setExcludeCipherSuites(
-                new String[] {
-                    "SSL_RSA_WITH_DES_CBC_SHA",
-                    "SSL_DHE_RSA_WITH_DES_CBC_SHA",
-                    "SSL_DHE_DSS_WITH_DES_CBC_SHA",
-                    "SSL_RSA_EXPORT_WITH_RC4_40_MD5",
-                    "SSL_RSA_EXPORT_WITH_DES40_CBC_SHA",
-                    "SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA",
-                    "SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA"
-                });
-        ssl_connector.setStatsOn(false);
-        server.addConnector(ssl_connector);
-        ssl_connector.open();
-        
-        SslSocketConnector ssl2_connector = new SslSocketConnector(cf);
-        ssl2_connector.setPort(8444);
-        ssl2_connector.setStatsOn(false);
-        server.addConnector(ssl2_connector);
-        ssl2_connector.open();
-
-       
-        /*
-        
-        Ajp13SocketConnector ajp = new Ajp13SocketConnector();
-        ajp.setPort(8009);
-        server.addConnector(ajp);
-        
-        */
-        
-        HandlerCollection handlers = new HandlerCollection();
-        ContextHandlerCollection contexts = new ContextHandlerCollection();
-        RequestLogHandler requestLogHandler = new RequestLogHandler();
-        
-        handlers.setHandlers(new Handler[] { contexts, new DefaultHandler(), requestLogHandler });
-        
-        StatisticsHandler stats = new StatisticsHandler();
-        stats.setHandler(handlers);
-        
-        server.setHandler(stats);
-
-        // Setup deployers
-        DeploymentManager deployer = new DeploymentManager();
-        deployer.setContexts(contexts);
-        server.addBean(deployer);   
-        
-        ContextProvider context_provider = new ContextProvider();
-        context_provider.setMonitoredDirName(jetty_home + "/contexts");
-        context_provider.setScanInterval(2);
-        deployer.addAppProvider(context_provider);
-
-        WebAppProvider webapp_provider = new WebAppProvider();
-        webapp_provider.setMonitoredDirName(jetty_home + "/webapps");
-        webapp_provider.setParentLoaderPriority(false);
-        webapp_provider.setExtractWars(true);
-        webapp_provider.setScanInterval(2);
-        webapp_provider.setDefaultsDescriptor(jetty_home + "/etc/webdefault.xml");
-        webapp_provider.setContextXmlDir(jetty_home + "/contexts");
-        deployer.addAppProvider(webapp_provider);
-
-        HashLoginService login = new HashLoginService();
-        login.setName("Test Realm");
-        login.setConfig(jetty_home + "/etc/realm.properties");
-        server.addBean(login);
-
-        NCSARequestLog requestLog = new AsyncNCSARequestLog();
-        requestLog.setFilename(jetty_home + "/logs/jetty-yyyy_mm_dd.log");
-        requestLog.setExtended(false);
-        requestLogHandler.setRequestLog(requestLog);
-
-        server.setStopAtShutdown(true);
-        server.setSendServerVersion(true);
-  
-        server.start();
-        
-        server.join();
-    }
-}
diff --git a/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/ManyConnectors.java b/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/ManyConnectors.java
deleted file mode 100644
index 8c30d4a..0000000
--- a/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/ManyConnectors.java
+++ /dev/null
@@ -1,67 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.embedded;
-
-import org.eclipse.jetty.server.Connector;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
-import org.eclipse.jetty.server.ssl.SslSelectChannelConnector;
-import org.eclipse.jetty.util.ssl.SslContextFactory;
-import org.eclipse.jetty.util.thread.QueuedThreadPool;
-
-/* ------------------------------------------------------------ */
-/**
- * A Jetty server with multiple connectors.
- * 
- */
-public class ManyConnectors
-{
-    public static void main(String[] args) throws Exception
-    {
-        Server server = new Server();
-
-        SelectChannelConnector connector0 = new SelectChannelConnector();
-        connector0.setPort(8080);
-        connector0.setMaxIdleTime(30000);
-        connector0.setRequestHeaderSize(8192);
-
-        SelectChannelConnector connector1 = new SelectChannelConnector();
-        connector1.setHost("127.0.0.1");
-        connector1.setPort(8888);
-        connector1.setThreadPool(new QueuedThreadPool(20));
-        connector1.setName("admin");
-
-        SslSelectChannelConnector ssl_connector = new SslSelectChannelConnector();
-        String jetty_home = System.getProperty("jetty.home","../jetty-distribution/target/distribution");
-        System.setProperty("jetty.home",jetty_home);
-        ssl_connector.setPort(8443);
-        SslContextFactory cf = ssl_connector.getSslContextFactory();
-        cf.setKeyStorePath(jetty_home + "/etc/keystore");
-        cf.setKeyStorePassword("OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4");
-        cf.setKeyManagerPassword("OBF:1u2u1wml1z7s1z7a1wnl1u2g");
-
-        server.setConnectors(new Connector[]
-        { connector0, connector1, ssl_connector });
-
-        server.setHandler(new HelloHandler());
-
-        server.start();
-        server.join();
-    }
-}
diff --git a/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/ManyContexts.java b/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/ManyContexts.java
deleted file mode 100644
index 3895a9a..0000000
--- a/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/ManyContexts.java
+++ /dev/null
@@ -1,77 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.embedded;
-
-import org.eclipse.jetty.server.Connector;
-import org.eclipse.jetty.server.Handler;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.handler.ContextHandler;
-import org.eclipse.jetty.server.handler.ContextHandlerCollection;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
-
-/* ------------------------------------------------------------ */
-/**
- * A {@link ContextHandlerCollection} handler may be used to direct a request to
- * a specific Context. The URI path prefix and optional virtual host is used to
- * select the context.
- * 
- */
-public class ManyContexts
-{
-    public final static String BODY=
-        "<a href='/'>root context</a><br/>"+
-        "<a href='http://127.0.0.1:8080/context'>normal context</a><br/>"+
-        "<a href='http://127.0.0.2:8080/context'>virtual context</a><br/>";
-        
-    public static void main(String[] args) throws Exception
-    {
-        Server server = new Server();
-        Connector connector = new SelectChannelConnector();
-        connector.setPort(8080);
-        server.setConnectors(new Connector[]
-        { connector });
-
-        ContextHandler context0 = new ContextHandler();
-        context0.setContextPath("/");
-        Handler handler0 = new HelloHandler("Root Context",BODY);
-        context0.setHandler(handler0);
-
-        ContextHandler context1 = new ContextHandler();
-        context1.setContextPath("/context");
-        Handler handler1 = new HelloHandler("A Context",BODY);
-        context1.setHandler(handler1);
-
-        ContextHandler context2 = new ContextHandler();
-        context2.setContextPath("/context");
-        context2.setVirtualHosts(new String[]
-        { "127.0.0.2" });
-        Handler handler2 = new HelloHandler("A Virtual Context",BODY);
-        context2.setHandler(handler2);
-
-        ContextHandlerCollection contexts = new ContextHandlerCollection();
-        contexts.setHandlers(new Handler[]
-        { context0, context1, context2 });
-
-        server.setHandler(contexts);
-
-        server.start();
-        System.err.println(server.dump());
-        server.join();
-    }
-}
diff --git a/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/ManyHandlers.java b/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/ManyHandlers.java
deleted file mode 100644
index 4789533..0000000
--- a/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/ManyHandlers.java
+++ /dev/null
@@ -1,124 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.embedded;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.Map;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jetty.server.Handler;
-import org.eclipse.jetty.server.NCSARequestLog;
-import org.eclipse.jetty.server.Request;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.handler.AbstractHandler;
-import org.eclipse.jetty.server.handler.DefaultHandler;
-import org.eclipse.jetty.server.handler.HandlerCollection;
-import org.eclipse.jetty.server.handler.HandlerList;
-import org.eclipse.jetty.server.handler.HandlerWrapper;
-import org.eclipse.jetty.server.handler.RequestLogHandler;
-import org.eclipse.jetty.util.ajax.JSON;
-
-/* ------------------------------------------------------------ */
-/**
- * Frequently many handlers are combined together to handle different aspects of
- * a request. A handler may:
- * <ul>
- * <li>handle the request and completely generate the response
- * <li>partially handle the request, but defer response generation to another
- * handler.
- * <li>select another handler to pass the request to.
- * <li>use business logic to decide to do one of the above.
- * </ul>
- * 
- * Multiple handlers may be combined with:
- * <ul>
- * <li>{@link HandlerWrapper} which will nest one handler inside another. In
- * this example, the HelloHandler is nested inside a HandlerWrapper that sets
- * the greeting as a request attribute.
- * <li>{@link HandlerList} which will call a collection of handlers until the
- * request is marked as handled. In this example, a list is used to combine the
- * param handler (which only handles the request if there are parameters) and
- * the wrapper handler. Frequently handler lists are terminated with the
- * {@link DefaultHandler}, which will generate a suitable 404 response if the
- * request has not been handled.
- * <li>{@link HandlerCollection} which will call each handler regardless if the
- * request has been handled or not. Typically this is used to always pass a
- * request to the logging handler.
- * </ul>
- */
-public class ManyHandlers
-{
-    public static void main(String[] args) throws Exception
-    {
-        Server server = new Server(8080);
-
-        // create the handlers
-        Handler param = new ParamHandler();
-        HandlerWrapper wrapper = new HandlerWrapper()
-        {
-            @Override
-            public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException,
-                    ServletException
-            {
-                request.setAttribute("welcome","Hello");
-                super.handle(target,baseRequest,request,response);
-            }
-        };
-        Handler hello = new HelloHandler();
-        Handler dft = new DefaultHandler();
-        RequestLogHandler log = new RequestLogHandler();
-
-        // configure logs
-        log.setRequestLog(new NCSARequestLog(File.createTempFile("demo","log").getAbsolutePath()));
-
-        // create the handler collections
-        HandlerCollection handlers = new HandlerCollection();
-        HandlerList list = new HandlerList();
-
-        // link them all together
-        wrapper.setHandler(hello);
-        list.setHandlers(new Handler[]
-        { param, wrapper, dft });
-        handlers.setHandlers(new Handler[]
-        { list, log });
-
-        server.setHandler(handlers);
-
-        server.start();
-        server.join();
-    }
-
-    public static class ParamHandler extends AbstractHandler
-    {
-        public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
-        {
-            Map params = request.getParameterMap();
-            if (params.size() > 0)
-            {
-                response.setContentType("text/plain");
-                response.getWriter().println(JSON.toString(params));
-                ((Request)request).setHandled(true);
-            }
-        }
-    }
-}
diff --git a/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/ManyServletContexts.java b/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/ManyServletContexts.java
deleted file mode 100644
index 7b623b2..0000000
--- a/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/ManyServletContexts.java
+++ /dev/null
@@ -1,61 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.embedded;
-
-
-
-import java.lang.management.ManagementFactory;
-
-import org.eclipse.jetty.jmx.MBeanContainer;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.handler.ContextHandlerCollection;
-import org.eclipse.jetty.servlet.DefaultServlet;
-import org.eclipse.jetty.servlet.ServletContextHandler;
-import org.eclipse.jetty.servlet.ServletHolder;
-import org.eclipse.jetty.util.log.Log;
-
-public class ManyServletContexts
-{
-    public static void main(String[] args) throws Exception
-    {
-        Server server = new Server(8080);
-
-        // Setup JMX
-        MBeanContainer mbContainer=new MBeanContainer(ManagementFactory.getPlatformMBeanServer());
-        mbContainer.start();
-        server.getContainer().addEventListener(mbContainer);
-        server.addBean(mbContainer,true);
-        
-        ContextHandlerCollection contexts = new ContextHandlerCollection();
-        server.setHandler(contexts);
-
-        ServletContextHandler root = new ServletContextHandler(contexts,"/",ServletContextHandler.SESSIONS);
-        root.addServlet(new ServletHolder(new HelloServlet("Hello")),"/");
-        root.addServlet(new ServletHolder(new HelloServlet("Ciao")),"/it/*");
-        root.addServlet(new ServletHolder(new HelloServlet("Bonjoir")),"/fr/*");
-
-        ServletContextHandler other = new ServletContextHandler(contexts,"/other",ServletContextHandler.SESSIONS);
-        other.addServlet(DefaultServlet.class.getCanonicalName(),"/");
-        other.addServlet(new ServletHolder(new HelloServlet("YO!")),"*.yo");
-
-        server.start();
-        System.err.println(server.dump());
-        server.join();
-    }
-}
diff --git a/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/MinimalServlets.java b/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/MinimalServlets.java
deleted file mode 100644
index dfc20a1..0000000
--- a/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/MinimalServlets.java
+++ /dev/null
@@ -1,62 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.embedded;
-
-import java.io.IOException;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jetty.server.Connector;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.bio.SocketConnector;
-import org.eclipse.jetty.servlet.ServletHandler;
-
-public class MinimalServlets
-{
-    public static void main(String[] args) throws Exception
-    {
-        Server server = new Server();
-        Connector connector = new SocketConnector();
-        connector.setPort(8080);
-        server.setConnectors(new Connector[]
-        { connector });
-
-        ServletHandler handler = new ServletHandler();
-        server.setHandler(handler);
-
-        handler.addServletWithMapping("org.eclipse.jetty.embedded.MinimalServlets$HelloServlet","/");
-
-        server.start();
-        server.join();
-    }
-
-    public static class HelloServlet extends HttpServlet
-    {
-        @Override
-        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
-        {
-            response.setContentType("text/html");
-            response.setStatus(HttpServletResponse.SC_OK);
-            response.getWriter().println("<h1>Hello SimpleServlet</h1>");
-        }
-    }
-}
diff --git a/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/OneContext.java b/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/OneContext.java
deleted file mode 100644
index 13ea8d7..0000000
--- a/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/OneContext.java
+++ /dev/null
@@ -1,48 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.embedded;
-
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.handler.ContextHandler;
-
-/* ------------------------------------------------------------ */
-/**
- * A {@link ContextHandler} provides a common environment for multiple Handlers,
- * such as: URI context path, class loader, static resource base.
- * 
- * Typically a ContextHandler is used only when multiple contexts are likely.
- */
-public class OneContext
-{
-    public static void main(String[] args) throws Exception
-    {
-        Server server = new Server(8080);
-
-        ContextHandler context = new ContextHandler();
-        context.setContextPath("/");
-        context.setResourceBase(".");
-        context.setClassLoader(Thread.currentThread().getContextClassLoader());
-        server.setHandler(context);
-
-        context.setHandler(new HelloHandler());
-
-        server.start();
-        server.join();
-    }
-}
diff --git a/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/OneHandler.java b/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/OneHandler.java
deleted file mode 100644
index dfda1bc..0000000
--- a/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/OneHandler.java
+++ /dev/null
@@ -1,33 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.embedded;
-
-import org.eclipse.jetty.server.Server;
-
-public class OneHandler
-{
-    public static void main(String[] args) throws Exception
-    {
-        Server server = new Server(8080);
-        server.setHandler(new HelloHandler());
-
-        server.start();
-        server.join();
-    }
-}
diff --git a/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/OneServletContext.java b/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/OneServletContext.java
deleted file mode 100644
index cefb97d..0000000
--- a/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/OneServletContext.java
+++ /dev/null
@@ -1,48 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.embedded;
-
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.servlet.ServletContextHandler;
-import org.eclipse.jetty.servlet.ServletHolder;
-
-public class OneServletContext
-{
-    public static void main(String[] args) throws Exception
-    {
-        Server server = new Server(8080);
-
-        ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
-        context.setContextPath("/");
-        server.setHandler(context);
-
-        // Server content from tmp
-        ServletHolder holder = context.addServlet(org.eclipse.jetty.servlet.DefaultServlet.class,"/tmp/*");
-        holder.setInitParameter("resourceBase","/tmp");
-        holder.setInitParameter("pathInfoOnly","true");
-        
-        // Serve some hello world servlets
-        context.addServlet(new ServletHolder(new HelloServlet()),"/*");
-        context.addServlet(new ServletHolder(new HelloServlet("Buongiorno Mondo")),"/it/*");
-        context.addServlet(new ServletHolder(new HelloServlet("Bonjour le Monde")),"/fr/*");
-
-        server.start();
-        server.join();
-    }
-}
diff --git a/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/OneWebApp.java b/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/OneWebApp.java
deleted file mode 100644
index 82c0dc6..0000000
--- a/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/OneWebApp.java
+++ /dev/null
@@ -1,64 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.embedded;
-
-import org.eclipse.jetty.server.Connector;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
-import org.eclipse.jetty.webapp.WebAppContext;
-
-public class OneWebApp
-{
-    public static void main(String[] args) throws Exception
-    {
-        Server server = new Server();
-
-        Connector connector = new SelectChannelConnector();
-        connector.setPort(Integer.getInteger("jetty.port",8080).intValue());
-        server.setConnectors(new Connector[]
-        { connector });
-
-
-        //If you're running this from inside Eclipse, then Server.getVersion will not provide
-        //the correct number as there is no manifest. Use the command line instead to provide the path to the
-        //test webapp
-        String war = args.length > 0?args[0]: "../test-jetty-webapp/target/test-jetty-webapp-"+Server.getVersion();
-        String path = args.length > 1?args[1]:"/";
-
-        System.err.println(war + " " + path);
-
-        WebAppContext webapp = new WebAppContext();
-        webapp.setContextPath(path);
-        webapp.setWar(war);
-        
-        //If the webapp contains security constraints, you will need to configure a LoginService
-        if (war.contains("test-jetty-webapp"))
-        {
-            org.eclipse.jetty.security.HashLoginService loginService = new org.eclipse.jetty.security.HashLoginService();
-            loginService.setName("Test Realm");
-            loginService.setConfig("src/test/resources/realm.properties");
-            webapp.getSecurityHandler().setLoginService(loginService);
-        }
-
-        server.setHandler(webapp);
-
-        server.start();
-        server.join();
-    }
-}
diff --git a/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/ProxyServer.java b/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/ProxyServer.java
deleted file mode 100644
index 7e93dd4..0000000
--- a/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/ProxyServer.java
+++ /dev/null
@@ -1,58 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.embedded;
-
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.handler.HandlerCollection;
-import org.eclipse.jetty.server.handler.ConnectHandler;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
-import org.eclipse.jetty.servlet.ServletContextHandler;
-import org.eclipse.jetty.servlet.ServletHolder;
-import org.eclipse.jetty.servlets.ProxyServlet;
-
-public class ProxyServer
-{
-    public static void main(String[] args) throws Exception
-    {
-        Server server = new Server();
-        SelectChannelConnector connector = new SelectChannelConnector();
-        connector.setPort(8888);
-        server.addConnector(connector);
-
-        HandlerCollection handlers = new HandlerCollection();
-        server.setHandler(handlers);
-
-        // Setup proxy servlet
-        ServletContextHandler context = new ServletContextHandler(handlers, "/", ServletContextHandler.SESSIONS);
-        ServletHolder proxyServlet = new ServletHolder(ProxyServlet.class);
-        proxyServlet.setInitParameter("whiteList", "google.com, www.eclipse.org, localhost");
-        proxyServlet.setInitParameter("blackList", "google.com/calendar/*, www.eclipse.org/committers/");
-        context.addServlet(proxyServlet, "/*");
-        
-        
-        // Setup proxy handler to handle CONNECT methods
-        ConnectHandler proxy = new ConnectHandler();
-        proxy.setWhite(new String[]{"mail.google.com"});
-        proxy.addWhite("www.google.com");
-        handlers.addHandler(proxy);
-
-        server.start();
-    }
-
-}
diff --git a/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/SecuredHelloHandler.java b/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/SecuredHelloHandler.java
deleted file mode 100644
index d28b3ed..0000000
--- a/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/SecuredHelloHandler.java
+++ /dev/null
@@ -1,70 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.embedded;
-
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
-
-import org.eclipse.jetty.security.ConstraintMapping;
-import org.eclipse.jetty.security.ConstraintSecurityHandler;
-import org.eclipse.jetty.security.HashLoginService;
-import org.eclipse.jetty.security.LoginService;
-import org.eclipse.jetty.security.authentication.BasicAuthenticator;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.util.security.Constraint;
-
-public class SecuredHelloHandler
-{
-    public static void main(String[] args) throws Exception
-    {
-        Server server = new Server(8080);
-        
-        LoginService loginService = new HashLoginService("MyRealm","src/test/resources/realm.properties");
-        server.addBean(loginService); 
-
-        ConstraintSecurityHandler security = new ConstraintSecurityHandler();
-        server.setHandler(security);
-
-        Constraint constraint = new Constraint();
-        constraint.setName("auth");
-        constraint.setAuthenticate( true );
-        constraint.setRoles(new String[]{"user", "admin"});
-
-        ConstraintMapping mapping = new ConstraintMapping();
-        mapping.setPathSpec( "/*" );
-        mapping.setConstraint( constraint );
-
-        Set<String> knownRoles = new HashSet<String>();
-        knownRoles.add("user");
-        knownRoles.add("admin");
-        
-        security.setConstraintMappings(Collections.singletonList(mapping), knownRoles);
-        security.setAuthenticator(new BasicAuthenticator());
-        security.setLoginService(loginService);
-        security.setStrict(false);
-        
-        HelloHandler hh = new HelloHandler();
-        
-        security.setHandler(hh);
-        
-        server.start();
-        server.join();
-    }
-}
diff --git a/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/SimplestServer.java b/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/SimplestServer.java
deleted file mode 100644
index a199b30..0000000
--- a/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/SimplestServer.java
+++ /dev/null
@@ -1,34 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.embedded;
-
-import org.eclipse.jetty.server.Server;
-
-/* ------------------------------------------------------------ */
-/** The simplest possible Jetty server.
- */
-public class SimplestServer
-{
-    public static void main(String[] args) throws Exception
-    {
-        Server server = new Server(8080);
-        server.start();
-        server.join();
-    }
-}
diff --git a/example-jetty-embedded/src/main/resources/fileserver.xml b/example-jetty-embedded/src/main/resources/fileserver.xml
deleted file mode 100644
index 9354f79..0000000
--- a/example-jetty-embedded/src/main/resources/fileserver.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0"?>
-<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
- 
-<Configure id="FileServer" class="org.eclipse.jetty.server.Server">
- 
-    <Call name="addConnector">
-      <Arg>
-          <New class="org.eclipse.jetty.server.nio.SelectChannelConnector">
-            <Set name="port">8080</Set>
-          </New>
-      </Arg>
-    </Call>
- 
-    <Set name="handler">
-      <New class="org.eclipse.jetty.server.handler.HandlerList">
-        <Set name="handlers">
-          <Array type="org.eclipse.jetty.server.Handler">
-            <Item>
-              <New class="org.eclipse.jetty.server.handler.ResourceHandler">
-                <Set name="directoriesListed">true</Set>
-                <Set name="welcomeFiles">
-                  <Array type="String"><Item>index.html</Item></Array>
-                </Set>
-                <Set name="resourceBase">.</Set>
-              </New>
-            </Item>
-            <Item>
-              <New class="org.eclipse.jetty.server.handler.DefaultHandler">
-              </New>
-            </Item>
-          </Array>
-        </Set>
-      </New>
-    </Set>
-</Configure>
\ No newline at end of file
diff --git a/example-jetty-embedded/src/test/java/org/eclipse/jetty/embedded/GzipHandlerTest.java b/example-jetty-embedded/src/test/java/org/eclipse/jetty/embedded/GzipHandlerTest.java
deleted file mode 100644
index 436644b..0000000
--- a/example-jetty-embedded/src/test/java/org/eclipse/jetty/embedded/GzipHandlerTest.java
+++ /dev/null
@@ -1,131 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.embedded;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.PrintWriter;
-import java.util.zip.GZIPInputStream;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jetty.io.ByteArrayBuffer;
-import org.eclipse.jetty.server.Handler;
-import org.eclipse.jetty.server.LocalConnector;
-import org.eclipse.jetty.server.Request;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.handler.AbstractHandler;
-import org.eclipse.jetty.server.handler.GzipHandler;
-import org.eclipse.jetty.testing.HttpTester;
-import org.eclipse.jetty.util.IO;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-public class GzipHandlerTest
-{
-    private static String __content =
-        "Lorem ipsum dolor sit amet, consectetur adipiscing elit. In quis felis nunc. "+
-        "Quisque suscipit mauris et ante auctor ornare rhoncus lacus aliquet. Pellentesque "+
-        "habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. "+
-        "Vestibulum sit amet felis augue, vel convallis dolor. Cras accumsan vehicula diam "+
-        "at faucibus. Etiam in urna turpis, sed congue mi. Morbi et lorem eros. Donec vulputate "+
-        "velit in risus suscipit lobortis. Aliquam id urna orci, nec sollicitudin ipsum. "+
-        "Cras a orci turpis. Donec suscipit vulputate cursus. Mauris nunc tellus, fermentum "+
-        "eu auctor ut, mollis at diam. Quisque porttitor ultrices metus, vitae tincidunt massa "+
-        "sollicitudin a. Vivamus porttitor libero eget purus hendrerit cursus. Integer aliquam "+
-        "consequat mauris quis luctus. Cras enim nibh, dignissim eu faucibus ac, mollis nec neque. "+
-        "Aliquam purus mauris, consectetur nec convallis lacinia, porta sed ante. Suspendisse "+
-        "et cursus magna. Donec orci enim, molestie a lobortis eu, imperdiet vitae neque.";
-
-    private Server _server;
-    private LocalConnector _connector;
-
-    @Before
-    public void init() throws Exception
-    {
-        _server = new Server();
-
-        _connector = new LocalConnector();
-        _server.addConnector(_connector);
-
-        Handler testHandler = new AbstractHandler()
-        {
-            public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException,
-                    ServletException
-            {
-                PrintWriter writer = response.getWriter();
-                writer.write(__content);
-                writer.close();
-
-                baseRequest.setHandled(true);
-            }
-        };
-        
-        GzipHandler gzipHandler = new GzipHandler();
-        gzipHandler.setHandler(testHandler);
-
-        _server.setHandler(gzipHandler);
-        _server.start();
-    }
-
-    @After
-    public void destroy() throws Exception
-    {
-        _server.stop();
-        _server.join();
-    }
-
-    @Test
-    public void testGzipHandler() throws Exception
-    {
-
-        // generated and parsed test
-        HttpTester request = new HttpTester();
-        HttpTester response = new HttpTester();
-
-        request.setMethod("GET");
-        request.setVersion("HTTP/1.0");
-        request.setHeader("Host","tester");
-        request.setHeader("accept-encoding","gzip");
-        request.setURI("/");
-        
-        ByteArrayBuffer reqsBuff = new ByteArrayBuffer(request.generate().getBytes());
-        ByteArrayBuffer respBuff = _connector.getResponses(reqsBuff, false);
-        response.parse(respBuff.asArray());
-                
-        assertTrue(response.getMethod()==null);
-        assertTrue(response.getHeader("Content-Encoding").equalsIgnoreCase("gzip"));
-        assertEquals(HttpServletResponse.SC_OK,response.getStatus());
-        
-        InputStream testIn = new GZIPInputStream(new ByteArrayInputStream(response.getContentBytes()));
-        ByteArrayOutputStream testOut = new ByteArrayOutputStream();
-        IO.copy(testIn,testOut);
-        
-        assertEquals(__content, testOut.toString("UTF8"));
-
-    }
-}
diff --git a/example-jetty-embedded/src/test/resources/realm.properties b/example-jetty-embedded/src/test/resources/realm.properties
deleted file mode 100644
index 6cd8ffa..0000000
--- a/example-jetty-embedded/src/test/resources/realm.properties
+++ /dev/null
@@ -1,22 +0,0 @@
-#
-# This file defines users passwords and roles for a HashUserRealm
-#
-# The format is
-#  <username>: <password>[,<rolename> ...]
-#
-# Passwords may be clear text, obfuscated or checksummed.  The class 
-# org.eclipse.util.Password should be used to generate obfuscated
-# passwords or password checksums
-#
-# If DIGEST Authentication is used, the password must be in a recoverable
-# format, either plain text or OBF:.
-#
-# if using digest authentication, do not MD5-hash the password
-jetty: jetty,user
-admin: CRYPT:ad1ks..kc.1Ug,server-administrator,content-administrator,admin,user
-other: OBF:1xmk1w261u9r1w1c1xmq,user
-plain: plain,user
-user: password,user
-
-# This entry is for digest auth.  The credential is a MD5 hash of username:realmname:password
-digest: MD5:6e120743ad67abfbc385bc2bb754e297,user
diff --git a/examples/async-rest/async-rest-jar/pom.xml b/examples/async-rest/async-rest-jar/pom.xml
new file mode 100644
index 0000000..6fe814a
--- /dev/null
+++ b/examples/async-rest/async-rest-jar/pom.xml
@@ -0,0 +1,30 @@
+<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">
+  <parent>
+    <groupId>org.eclipse.jetty</groupId>
+    <artifactId>example-async-rest</artifactId>
+    <version>9.2.13.v20150730</version>
+  </parent>  
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>org.eclipse.jetty.example-async-rest</groupId>
+  <artifactId>example-async-rest-jar</artifactId>
+  <packaging>jar</packaging>
+  <name>Example Async Rest :: Jar</name>
+  <url>http://www.eclipse.org/jetty</url>
+  <dependencies>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-client</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-util-ajax</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>javax.servlet</groupId>
+      <artifactId>javax.servlet-api</artifactId>
+      <scope>provided</scope>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/examples/async-rest/async-rest-jar/src/main/java/org/eclipse/jetty/example/asyncrest/AbstractRestServlet.java b/examples/async-rest/async-rest-jar/src/main/java/org/eclipse/jetty/example/asyncrest/AbstractRestServlet.java
new file mode 100644
index 0000000..8c70fc7
--- /dev/null
+++ b/examples/async-rest/async-rest-jar/src/main/java/org/eclipse/jetty/example/asyncrest/AbstractRestServlet.java
@@ -0,0 +1,129 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.example.asyncrest;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.net.URLEncoder;
+import java.util.Map;
+import java.util.Queue;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Abstract Servlet implementation class AsyncRESTServlet.
+ * Enquires ebay REST service for auctions by key word.
+ * May be configured with init parameters: <dl>
+ * <dt>appid</dt><dd>The eBay application ID to use</dd>
+ * </dl>
+ * Each request examines the following request parameters:<dl>
+ * <dt>items</dt><dd>The keyword to search for</dd>
+ * </dl>
+ */
+public class AbstractRestServlet extends HttpServlet
+{
+    protected final static String __DEFAULT_APPID = "Webtide81-adf4-4f0a-ad58-d91e41bbe85";
+    protected final static String STYLE = 
+        "<style type='text/css'>"+
+        "  img.thumb:hover {height:50px}"+
+        "  img.thumb {vertical-align:text-top}"+
+        "  span.red {color: #ff0000}"+
+        "  span.green {color: #00ff00}"+
+        "  iframe {border: 0px}"+
+        "</style>";
+
+    protected final static String ITEMS_PARAM = "items";
+    protected final static String APPID_PARAM = "appid";
+
+    protected String _appid;
+
+    @Override
+    public void init(ServletConfig servletConfig) throws ServletException
+    {
+        if (servletConfig.getInitParameter(APPID_PARAM) == null)
+            _appid = __DEFAULT_APPID;
+        else
+            _appid = servletConfig.getInitParameter(APPID_PARAM);
+    }
+
+
+    public static String sanitize(String s)
+    {
+        if (s==null)
+            return null;
+        return s.replace("<","?").replace("&","?").replace("\n","?");
+    }
+    
+    protected String restURL(String item) 
+    {
+        try
+        {
+            return ("http://open.api.ebay.com/shopping?MaxEntries=3&appid=" + _appid + 
+                    "&version=573&siteid=0&callname=FindItems&responseencoding=JSON&QueryKeywords=" + 
+                    URLEncoder.encode(item,"UTF-8"));
+        }
+        catch(Exception e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+    
+    protected String generateThumbs(Queue<Map<String,String>> results)
+    {
+        StringBuilder thumbs = new StringBuilder();
+        for (Map<String, String> m : results)
+        {
+            if (!m.containsKey("GalleryURL"))
+                continue;
+                
+            thumbs.append("<a href=\""+m.get("ViewItemURLForNaturalSearch")+"\">");
+            thumbs.append("<img class='thumb' border='1px' height='25px'"+
+                        " src='"+m.get("GalleryURL")+"'"+
+                        " title='"+m.get("Title")+"'"+
+                        "/>");
+            thumbs.append("</a> ");
+        }
+        return thumbs.toString();
+    }
+
+    protected String ms(long nano)
+    {
+        BigDecimal dec = new BigDecimal(nano);
+        return dec.divide(new BigDecimal(1000000L)).setScale(1,RoundingMode.UP).toString();
+    }
+    
+    protected int width(long nano)
+    {
+        int w=(int)((nano+999999L)/5000000L);
+        if (w==0)
+            w=2;
+        return w;
+    }
+    
+    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+    {
+        doGet(request, response);
+    }
+
+}
diff --git a/examples/async-rest/async-rest-jar/src/main/java/org/eclipse/jetty/example/asyncrest/AsyncRestServlet.java b/examples/async-rest/async-rest-jar/src/main/java/org/eclipse/jetty/example/asyncrest/AsyncRestServlet.java
new file mode 100644
index 0000000..39e1c6e
--- /dev/null
+++ b/examples/async-rest/async-rest-jar/src/main/java/org/eclipse/jetty/example/asyncrest/AsyncRestServlet.java
@@ -0,0 +1,206 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.example.asyncrest;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.nio.ByteBuffer;
+import java.util.Map;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.client.api.Result;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Utf8StringBuilder;
+import org.eclipse.jetty.util.ajax.JSON;
+
+/**
+ * Servlet implementation class AsyncRESTServlet.
+ * Enquires ebay REST service for auctions by key word.
+ * May be configured with init parameters: <dl>
+ * <dt>appid</dt><dd>The eBay application ID to use</dd>
+ * </dl>
+ * Each request examines the following request parameters:<dl>
+ * <dt>items</dt><dd>The keyword to search for</dd>
+ * </dl>
+ */
+public class AsyncRestServlet extends AbstractRestServlet
+{
+    final static String RESULTS_ATTR = "org.eclipse.jetty.demo.client";
+    final static String DURATION_ATTR = "org.eclipse.jetty.demo.duration";
+    final static String START_ATTR = "org.eclispe.jetty.demo.start";
+
+    HttpClient _client;
+
+    @Override
+    public void init(ServletConfig servletConfig) throws ServletException
+    {
+        super.init(servletConfig);
+
+        _client = new HttpClient();
+
+        try
+        {
+            _client.start();
+        }
+        catch (Exception e)
+        {
+            throw new ServletException(e);
+        }
+    }
+
+    @Override
+    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+    {
+        Long start=System.nanoTime();
+
+        // Do we have results yet?
+        Queue<Map<String, String>> results = (Queue<Map<String, String>>) request.getAttribute(RESULTS_ATTR);
+
+        // If no results, this must be the first dispatch, so send the REST request(s)
+        if (results==null)
+        {
+            // define results data structures
+            final Queue<Map<String, String>> resultsQueue = new ConcurrentLinkedQueue<>();
+            request.setAttribute(RESULTS_ATTR, results=resultsQueue);
+
+            // suspend the request
+            // This is done before scheduling async handling to avoid race of
+            // dispatch before startAsync!
+            final AsyncContext async = request.startAsync();
+            async.setTimeout(30000);
+
+            // extract keywords to search for
+            String[] keywords=sanitize(request.getParameter(ITEMS_PARAM)).split(",");
+            final AtomicInteger outstanding=new AtomicInteger(keywords.length);
+
+            // Send request each keyword
+            for (final String item:keywords)
+            {
+                _client.newRequest(restURL(item)).method(HttpMethod.GET).send(
+                    new AsyncRestRequest()
+                    {
+                        @Override
+                        void onAuctionFound(Map<String,String> auction)
+                        {
+                            resultsQueue.add(auction);
+                        }
+                        @Override
+                        void onComplete()
+                        {
+                            if (outstanding.decrementAndGet()<=0)
+                                async.dispatch();
+                        }
+                    });
+            }
+
+            // save timing info and return
+            request.setAttribute(START_ATTR, start);
+            request.setAttribute(DURATION_ATTR, System.nanoTime() - start);
+
+            return;
+        }
+
+        // We have results!
+
+        // Generate the response
+        String thumbs = generateThumbs(results);
+
+        response.setContentType("text/html");
+        PrintWriter out = response.getWriter();
+        out.println("<html><head>");
+        out.println(STYLE);
+        out.println("</head><body><small>");
+
+        long initial = (Long) request.getAttribute(DURATION_ATTR);
+        long start0 = (Long) request.getAttribute(START_ATTR);
+
+        long now = System.nanoTime();
+        long total=now-start0;
+        long generate=now-start;
+        long thread=initial+generate;
+
+        out.print("<b>Asynchronous: "+sanitize(request.getParameter(ITEMS_PARAM))+"</b><br/>");
+        out.print("Total Time: "+ms(total)+"ms<br/>");
+
+        out.print("Thread held (<span class='red'>red</span>): "+ms(thread)+"ms (" + ms(initial) + " initial + " + ms(generate) + " generate )<br/>");
+        out.print("Async wait (<span class='green'>green</span>): "+ms(total-thread)+"ms<br/>");
+
+        out.println("<img border='0px' src='asyncrest/red.png'   height='20px' width='"+width(initial)+"px'>"+
+                    "<img border='0px' src='asyncrest/green.png' height='20px' width='"+width(total-thread)+"px'>"+
+                    "<img border='0px' src='asyncrest/red.png'   height='20px' width='"+width(generate)+"px'>");
+
+        out.println("<hr />");
+        out.println(thumbs);
+        out.println("</small>");
+        out.println("</body></html>");
+        out.close();
+    }
+    
+    private abstract class AsyncRestRequest extends Response.Listener.Adapter
+    {
+        final Utf8StringBuilder _content = new Utf8StringBuilder();
+
+        AsyncRestRequest()
+        {
+        }
+
+        @Override
+        public void onContent(Response response, ByteBuffer content)
+        {
+            byte[] bytes = BufferUtil.toArray(content);
+            _content.append(bytes,0,bytes.length);
+        }
+
+        @Override
+        public void onComplete(Result result)
+        {
+            // extract auctions from the results
+            Map<String,?> query = (Map<String,?>) JSON.parse(_content.toString());
+            Object[] auctions = (Object[]) query.get("Item");
+            if (auctions != null)
+            {
+                for (Object o : auctions)
+                    onAuctionFound((Map<String,String>)o);
+            }
+            onComplete();
+
+        }
+
+        abstract void onAuctionFound(Map<String,String> details);
+        abstract void onComplete();
+
+    }
+
+    @Override
+    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+    {
+        doGet(request, response);
+    }
+}
diff --git a/examples/async-rest/async-rest-jar/src/main/java/org/eclipse/jetty/example/asyncrest/SerialRestServlet.java b/examples/async-rest/async-rest-jar/src/main/java/org/eclipse/jetty/example/asyncrest/SerialRestServlet.java
new file mode 100644
index 0000000..5fc5e42
--- /dev/null
+++ b/examples/async-rest/async-rest-jar/src/main/java/org/eclipse/jetty/example/asyncrest/SerialRestServlet.java
@@ -0,0 +1,106 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.example.asyncrest;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Queue;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.util.ajax.JSON;
+
+/**
+ * Servlet implementation class SerialRestServlet
+ */
+public class SerialRestServlet extends AbstractRestServlet
+{   
+    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+    {
+        long start = System.nanoTime();
+        
+
+        String[] keywords=sanitize(request.getParameter(ITEMS_PARAM)).split(",");
+        Queue<Map<String,String>> results = new LinkedList<Map<String,String>>();
+        
+        // make all requests serially
+        for (String itemName : keywords)
+        {
+            URL url = new URL(restURL(itemName));
+            
+            HttpURLConnection connection = (HttpURLConnection)url.openConnection();
+            connection.setRequestMethod("GET");
+            
+            Map query = (Map)JSON.parse(new BufferedReader(new InputStreamReader(connection.getInputStream())));
+            Object[] auctions = (Object[]) query.get("Item");
+            if (auctions != null)
+            {
+                for (Object o : auctions)
+                    results.add((Map) o);
+            }
+        }
+        
+
+        // Generate the response
+        String thumbs=generateThumbs(results);
+        
+        response.setContentType("text/html");
+        PrintWriter out = response.getWriter();
+        out.println("<html><head>");
+        out.println(STYLE);
+        out.println("</head><body><small>");
+
+        long now = System.nanoTime();
+        long total=now-start;
+
+        out.print("<b>Blocking: "+sanitize(request.getParameter(ITEMS_PARAM))+"</b><br/>");
+        out.print("Total Time: "+ms(total)+"ms<br/>");
+        out.print("Thread held (<span class='red'>red</span>): "+ms(total)+"ms<br/>");
+        
+        out.println("<img border='0px' src='asyncrest/red.png'   height='20px' width='"+width(total)+"px'>");
+        
+        out.println("<hr />");
+        out.println(thumbs);
+        out.println("</small>");
+        out.println("</body></html>");
+        out.close();
+        
+        
+
+    }
+
+    /**
+     * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse
+     *      response)
+     */
+    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+    {
+        doGet(request, response);
+    }
+
+}
diff --git a/example-async-rest/async-rest-jar/src/main/resources/META-INF/resources/asyncrest.html b/examples/async-rest/async-rest-jar/src/main/resources/META-INF/resources/asyncrest.html
similarity index 100%
rename from example-async-rest/async-rest-jar/src/main/resources/META-INF/resources/asyncrest.html
rename to examples/async-rest/async-rest-jar/src/main/resources/META-INF/resources/asyncrest.html
diff --git a/example-async-rest/async-rest-jar/src/main/resources/META-INF/resources/asyncrest/green.png b/examples/async-rest/async-rest-jar/src/main/resources/META-INF/resources/asyncrest/green.png
similarity index 100%
rename from example-async-rest/async-rest-jar/src/main/resources/META-INF/resources/asyncrest/green.png
rename to examples/async-rest/async-rest-jar/src/main/resources/META-INF/resources/asyncrest/green.png
diff --git a/example-async-rest/async-rest-jar/src/main/resources/META-INF/resources/asyncrest/red.png b/examples/async-rest/async-rest-jar/src/main/resources/META-INF/resources/asyncrest/red.png
similarity index 100%
rename from example-async-rest/async-rest-jar/src/main/resources/META-INF/resources/asyncrest/red.png
rename to examples/async-rest/async-rest-jar/src/main/resources/META-INF/resources/asyncrest/red.png
diff --git a/examples/async-rest/async-rest-jar/src/main/resources/META-INF/web-fragment.xml b/examples/async-rest/async-rest-jar/src/main/resources/META-INF/web-fragment.xml
new file mode 100644
index 0000000..db2b21b
--- /dev/null
+++ b/examples/async-rest/async-rest-jar/src/main/resources/META-INF/web-fragment.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<web-fragment 
+   xmlns="http://xmlns.jcp.org/xml/ns/javaee"
+   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+   xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-fragment_3_1.xsd"
+   version="3.1">
+	<servlet>
+		<display-name>SerialRestServlet</display-name>
+		<servlet-name>SerialRestServlet</servlet-name>
+		<servlet-class>org.eclipse.jetty.example.asyncrest.SerialRestServlet</servlet-class>
+	</servlet>
+	<servlet-mapping>
+		<servlet-name>SerialRestServlet</servlet-name>
+		<url-pattern>/testSerial</url-pattern>
+	</servlet-mapping>
+	
+	<servlet>
+		<display-name>AsyncRestServlet</display-name>
+		<servlet-name>AsyncRestServlet</servlet-name>
+		<servlet-class>org.eclipse.jetty.example.asyncrest.AsyncRestServlet</servlet-class>
+		<async-supported>true</async-supported>
+	</servlet>
+	<servlet-mapping>
+		<servlet-name>AsyncRestServlet</servlet-name>
+		<url-pattern>/testAsync</url-pattern>
+	</servlet-mapping>	
+</web-fragment>
diff --git a/examples/async-rest/async-rest-webapp/pom.xml b/examples/async-rest/async-rest-webapp/pom.xml
new file mode 100644
index 0000000..74cacbd
--- /dev/null
+++ b/examples/async-rest/async-rest-webapp/pom.xml
@@ -0,0 +1,33 @@
+<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">
+  <parent>
+    <groupId>org.eclipse.jetty</groupId>
+    <artifactId>example-async-rest</artifactId>
+    <version>9.2.13.v20150730</version>
+  </parent>  
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>org.eclipse.jetty.example-async-rest</groupId>
+  <artifactId>example-async-rest-webapp</artifactId>
+  <packaging>war</packaging>
+  <name>Example Async Rest :: Webapp</name>
+  <build>
+    <finalName>async-rest</finalName>
+  </build>
+  <dependencies>
+    <dependency>
+      <groupId>org.eclipse.jetty.example-async-rest</groupId>
+      <artifactId>example-async-rest-jar</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-webapp</artifactId>
+      <version>${project.version}</version>
+      <scope>test</scope>
+    </dependency>
+      <dependency>
+         <groupId>javax.servlet</groupId>
+         <artifactId>javax.servlet-api</artifactId>
+         <scope>provided</scope>
+       </dependency>
+  </dependencies>
+</project>
diff --git a/example-async-rest/async-rest-webapp/src/main/webapp/META-INF/MANIFEST.MF b/examples/async-rest/async-rest-webapp/src/main/webapp/META-INF/MANIFEST.MF
similarity index 100%
rename from example-async-rest/async-rest-webapp/src/main/webapp/META-INF/MANIFEST.MF
rename to examples/async-rest/async-rest-webapp/src/main/webapp/META-INF/MANIFEST.MF
diff --git a/examples/async-rest/async-rest-webapp/src/main/webapp/WEB-INF/jetty-web.xml b/examples/async-rest/async-rest-webapp/src/main/webapp/WEB-INF/jetty-web.xml
new file mode 100644
index 0000000..9376677
--- /dev/null
+++ b/examples/async-rest/async-rest-webapp/src/main/webapp/WEB-INF/jetty-web.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
+
+<!--
+This is the jetty specific web application configuration file.  When starting
+a Web Application, the WEB-INF/jetty-web.xml file is looked for and if found, treated
+as a org.eclipse.jetty.server.server.xml.XmlConfiguration file and is applied to the
+org.eclipse.jetty.servlet.WebApplicationContext object
+-->
+
+<Configure class="org.eclipse.jetty.webapp.WebAppContext">
+  <Get class="org.eclipse.jetty.util.log.Log" name="rootLogger">
+    <Call name="warn"><Arg>async-rest webapp is deployed. DO NOT USE IN PRODUCTION!</Arg></Call>
+  </Get>
+</Configure>
diff --git a/examples/async-rest/async-rest-webapp/src/main/webapp/WEB-INF/web.xml b/examples/async-rest/async-rest-webapp/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000..c674332
--- /dev/null
+++ b/examples/async-rest/async-rest-webapp/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0"?>
+<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" 
+   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+   xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" 
+   version="3.1">
+
+	<display-name>Async REST Webservice Example</display-name>
+
+</web-app>
diff --git a/examples/async-rest/async-rest-webapp/src/main/webapp/index.html b/examples/async-rest/async-rest-webapp/src/main/webapp/index.html
new file mode 100644
index 0000000..e7f5d0a
--- /dev/null
+++ b/examples/async-rest/async-rest-webapp/src/main/webapp/index.html
@@ -0,0 +1,44 @@
+<html>
+ <head>
+  <style type='text/css'>
+    iframe {border: 0px}
+    table, tr, td {border: 0px}
+  </style>
+</head>
+<body>
+<h1>Blocking vs Asynchronous REST</h1>
+<p>
+This demo calls the EBay WS API both synchronously and asynchronously,
+to obtain items matching each of the keywords passed on the query
+string.  The time the request thread is held by the servlet is displayed in red for both.
+</p>
+
+<table width='100%'>
+	
+<tr>
+<td>
+  <iframe id="f1" width='100%' height='175px' src="testSerial?items=kayak"></iframe>
+</td>
+<td>
+  <iframe id="f3" width='100%' height='175px' src="testSerial?items=mouse,beer,gnome"></iframe>
+</td>
+</tr>
+ 
+<tr>
+<td>
+  <iframe id="f2" width='100%' height='175px' src="testAsync?items=kayak"/></iframe>
+</td>
+<td>
+  <iframe id="f4" width='100%' height='175px' src="testAsync?items=mouse,beer,gnome"/></iframe>
+</td>
+</tr>
+
+</table>
+By the use of Asynchronous Servlets and the Jetty Asychronous client, the server is able to release the thread (green) while
+waiting for the response from Ebay.  This thread goes back into the thread pool and can service many other requests during the wait.
+This greatly reduces the number of threads needed, which in turn greatly reduces the memory requirements of the server.
+<p>
+Press reload to see even better results after JIT and TCP/IP warmup!
+
+</body>
+</html>
diff --git a/examples/async-rest/async-rest-webapp/src/test/java/org/eclipse/jetty/example/asyncrest/DemoServer.java b/examples/async-rest/async-rest-webapp/src/test/java/org/eclipse/jetty/example/asyncrest/DemoServer.java
new file mode 100644
index 0000000..c6172da
--- /dev/null
+++ b/examples/async-rest/async-rest-webapp/src/test/java/org/eclipse/jetty/example/asyncrest/DemoServer.java
@@ -0,0 +1,43 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.example.asyncrest;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.webapp.WebAppContext;
+
+public class DemoServer
+{
+    public static void main(String[] args)
+        throws Exception
+    {
+        String jetty_home = System.getProperty("jetty.home",".");
+
+        Server server = new Server(Integer.getInteger("jetty.port",8080).intValue());
+                
+        WebAppContext webapp = new WebAppContext();
+        webapp.setContextPath("/");
+        webapp.setWar(jetty_home+"/target/async-rest/");
+        webapp.setParentLoaderPriority(true);
+        webapp.setServerClasses(new String[]{});
+        server.setHandler(webapp);
+        
+        server.start();
+        server.join();
+    }
+}
diff --git a/examples/async-rest/pom.xml b/examples/async-rest/pom.xml
new file mode 100644
index 0000000..c61d0d7
--- /dev/null
+++ b/examples/async-rest/pom.xml
@@ -0,0 +1,17 @@
+<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">
+  <parent>
+    <groupId>org.eclipse.jetty.examples</groupId>
+    <artifactId>examples-parent</artifactId>
+    <version>9.2.13.v20150730</version>
+    <relativePath>../pom.xml</relativePath>
+  </parent>  
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>org.eclipse.jetty</groupId>
+  <artifactId>example-async-rest</artifactId>
+  <packaging>pom</packaging>
+  <name>Example Async Rest</name>
+  <modules>
+    <module>async-rest-jar</module>
+    <module>async-rest-webapp</module>
+  </modules>
+</project>
diff --git a/examples/embedded/pom.xml b/examples/embedded/pom.xml
new file mode 100644
index 0000000..dd0458b
--- /dev/null
+++ b/examples/embedded/pom.xml
@@ -0,0 +1,109 @@
+<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">
+  <parent>
+    <groupId>org.eclipse.jetty.examples</groupId>
+    <artifactId>examples-parent</artifactId>
+    <version>9.2.13.v20150730</version>
+    <relativePath>../pom.xml</relativePath>
+  </parent>
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>org.eclipse.jetty</groupId>
+  <artifactId>example-jetty-embedded</artifactId>
+  <name>Example :: Jetty Embedded</name>
+  <description>Jetty Embedded Examples</description>
+  <url>http://www.eclipse.org/jetty</url>
+  <dependencies>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-util-ajax</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-webapp</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-security</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-servlets</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-deploy</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-jmx</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.websocket</groupId>
+      <artifactId>javax-websocket-server-impl</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.websocket</groupId>
+      <artifactId>websocket-server</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.spdy</groupId>
+      <artifactId>spdy-http-server</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-annotations</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.tests</groupId>
+      <artifactId>test-mock-resources</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>javax.transaction</groupId>
+      <artifactId>javax.transaction-api</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-proxy</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-jaas</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-plus</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>apache-jsp</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>apache-jstl</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.orbit</groupId>
+      <artifactId>javax.mail.glassfish</artifactId>
+    </dependency>
+     <dependency>
+      <groupId>org.eclipse.jetty.toolchain</groupId>
+      <artifactId>jetty-test-helper</artifactId>
+      <!-- scope>test</scope-->
+    </dependency>
+  </dependencies>
+</project>
diff --git a/example-jetty-embedded/prodDb.properties b/examples/embedded/prodDb.properties
similarity index 100%
rename from example-jetty-embedded/prodDb.properties
rename to examples/embedded/prodDb.properties
diff --git a/example-jetty-embedded/prodDb.script b/examples/embedded/prodDb.script
similarity index 100%
rename from example-jetty-embedded/prodDb.script
rename to examples/embedded/prodDb.script
diff --git a/examples/embedded/src/main/java/HelloWorld.java b/examples/embedded/src/main/java/HelloWorld.java
new file mode 100644
index 0000000..8b1c231
--- /dev/null
+++ b/examples/embedded/src/main/java/HelloWorld.java
@@ -0,0 +1,59 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+
+public class HelloWorld extends AbstractHandler
+{
+    @Override
+    public void handle( String target,
+                        Request baseRequest,
+                        HttpServletRequest request,
+                        HttpServletResponse response ) throws IOException,
+                                                      ServletException
+    {
+        // Declare response encoding and types
+        response.setContentType("text/html; charset=utf-8");
+
+        // Declare response status code
+        response.setStatus(HttpServletResponse.SC_OK);
+
+        // Write back response
+        response.getWriter().println("<h1>Hello World</h1>");
+
+        // Inform jetty that this request has now been handled
+        baseRequest.setHandled(true);
+    }
+
+    public static void main( String[] args ) throws Exception
+    {
+        Server server = new Server(8080);
+        server.setHandler(new HelloWorld());
+
+        server.start();
+        server.join();
+    }
+}
diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/AsyncEchoServlet.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/AsyncEchoServlet.java
new file mode 100644
index 0000000..7423176
--- /dev/null
+++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/AsyncEchoServlet.java
@@ -0,0 +1,107 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.embedded;
+
+import java.io.IOException;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.ReadListener;
+import javax.servlet.ServletException;
+import javax.servlet.ServletInputStream;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.WriteListener;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+
+public class AsyncEchoServlet extends HttpServlet
+{
+    @Override
+    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+    {
+        AsyncContext asyncContext = request.startAsync(request, response);
+        asyncContext.setTimeout(0);
+        Echoer echoer = new Echoer(asyncContext);
+        request.getInputStream().setReadListener(echoer);
+        response.getOutputStream().setWriteListener(echoer);
+    }
+
+    private class Echoer implements ReadListener, WriteListener
+    {
+        private final byte[] buffer = new byte[4096];
+        private final AsyncContext asyncContext;
+        private final ServletInputStream input;
+        private final ServletOutputStream output;
+        private final AtomicBoolean complete = new AtomicBoolean(false);
+
+        private Echoer(AsyncContext asyncContext) throws IOException
+        {
+            this.asyncContext = asyncContext;
+            this.input = asyncContext.getRequest().getInputStream();
+            this.output = asyncContext.getResponse().getOutputStream();
+        }
+        
+        @Override
+        public void onDataAvailable() throws IOException
+        {
+            onWritePossible();
+        }
+
+        @Override
+        public void onAllDataRead() throws IOException
+        {
+            onWritePossible();
+        }
+
+        @Override
+        public void onWritePossible() throws IOException
+        {
+            // This method is called:
+            //   1) after first registering a WriteListener (ready for first write)
+            //   2) after first registering a ReadListener iff write is ready
+            //   3) when a previous write completes after an output.isReady() returns false
+            //   4) from an input callback 
+           
+            // We should try to read, only if we are able to write!
+            while (output.isReady() && input.isReady())
+            {
+                int read = input.read(buffer);
+                if (read<0)
+                {
+                    if (complete.compareAndSet(false,true))
+                        asyncContext.complete();
+                    break;
+                }
+                else if (read>0)
+                {
+                    output.write(buffer, 0, read);
+                }
+            }
+        }
+
+        @Override
+        public void onError(Throwable failure)
+        {
+            failure.printStackTrace();
+            asyncContext.complete();
+        }
+    }
+}
diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/DumpServlet.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/DumpServlet.java
new file mode 100644
index 0000000..7a03856
--- /dev/null
+++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/DumpServlet.java
@@ -0,0 +1,59 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.embedded;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+ at SuppressWarnings("serial")
+public class DumpServlet extends HttpServlet
+{
+    @Override
+    protected void doGet( HttpServletRequest request,
+                          HttpServletResponse response ) throws ServletException,
+                                                        IOException
+    {
+        response.setContentType("text/html");
+        response.setStatus(HttpServletResponse.SC_OK);
+
+        PrintWriter out = response.getWriter();
+
+        out.println("<h1>DumpServlet</h1>");
+        out.println("<pre>");
+        out.println("requestURI=" + request.getRequestURI());
+        out.println("contextPath=" + request.getContextPath());
+        out.println("servletPath=" + request.getServletPath());
+        out.println("pathInfo=" + request.getPathInfo());
+        out.println("session=" + request.getSession(true).getId());
+
+        String r = request.getParameter("resource");
+        if (r != null)
+        {
+            out.println("resource(" + r + ")="
+                    + getServletContext().getResource(r));
+        }
+
+        out.println("</pre>");
+    }
+}
diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ExampleServer.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ExampleServer.java
new file mode 100644
index 0000000..54e1261
--- /dev/null
+++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ExampleServer.java
@@ -0,0 +1,51 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.embedded;
+
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.handler.DefaultHandler;
+import org.eclipse.jetty.server.handler.HandlerCollection;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+
+public class ExampleServer
+{
+    public static void main( String[] args ) throws Exception
+    {
+        Server server = new Server();
+
+        ServerConnector connector = new ServerConnector(server);
+        connector.setPort(8080);
+        server.setConnectors(new Connector[] { connector });
+
+        ServletContextHandler context = new ServletContextHandler();
+        context.setContextPath("/");
+        context.addServlet(HelloServlet.class, "/hello");
+        context.addServlet(AsyncEchoServlet.class, "/echo/*");
+
+        HandlerCollection handlers = new HandlerCollection();
+        handlers.setHandlers(new Handler[] { context, new DefaultHandler() });
+        server.setHandler(handlers);
+
+        server.start();
+        server.join();
+    }
+}
diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ExampleServerXml.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ExampleServerXml.java
new file mode 100644
index 0000000..fa8f773
--- /dev/null
+++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ExampleServerXml.java
@@ -0,0 +1,40 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.embedded;
+
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.xml.XmlConfiguration;
+
+/**
+ * Configures and Starts a Jetty server from an XML declaration.
+ * <p>
+ * See <a href=
+ * "http://git.eclipse.org/c/jetty/org.eclipse.jetty.project.git/tree/examples/embedded/src/main/resources/exampleserver.xml"
+ * >exampleserver.xml</a>
+ * </p>
+ */
+public class ExampleServerXml
+{
+    public static void main( String[] args ) throws Exception
+    {
+        // Find Jetty XML (in classpath) that configures and starts Server.
+        Resource serverXml = Resource.newSystemResource("exampleserver.xml");
+        XmlConfiguration.main(serverXml.getFile().getAbsolutePath());
+    }
+}
diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/FastFileServer.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/FastFileServer.java
new file mode 100644
index 0000000..998ca40
--- /dev/null
+++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/FastFileServer.java
@@ -0,0 +1,194 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.embedded;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileChannel.MapMode;
+import java.nio.file.StandardOpenOption;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.HttpOutput;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.server.handler.DefaultHandler;
+import org.eclipse.jetty.server.handler.HandlerList;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.resource.Resource;
+
+/**
+ * Fast FileServer.
+ * <p>
+ * This example shows how to use the Jetty APIs for sending static as fast as
+ * possible using various strategies for small, medium and large content.
+ * </p>
+ * <p>
+ * The Jetty {@link DefaultServlet} does all this and more, and to a lesser
+ * extent so does the {@link ResourceHandler}, so unless you have exceptional
+ * circumstances it is best to use those classes for static content
+ * </p>
+ */
+public class FastFileServer
+{
+    public static void main( String[] args ) throws Exception
+    {
+        Server server = new Server(8080);
+
+        HandlerList handlers = new HandlerList();
+        handlers.setHandlers(new Handler[] {
+                new FastFileHandler(new File(System.getProperty("user.dir"))),
+                new DefaultHandler() });
+        server.setHandler(handlers);
+
+        server.start();
+        server.join();
+    }
+
+    static class FastFileHandler extends AbstractHandler
+    {
+        private final MimeTypes mimeTypes = new MimeTypes();
+        private final File dir;
+
+        private FastFileHandler( File dir )
+        {
+            this.dir = dir;
+        }
+
+        @Override
+        public void handle( String target,
+                            Request baseRequest,
+                            HttpServletRequest request,
+                            HttpServletResponse response ) throws IOException,
+                                                          ServletException
+        {
+            // define small medium and large.
+            // This should be turned for your content, JVM and OS, but we will
+            // huge HTTP response buffer size as a measure
+            final int SMALL = response.getBufferSize();
+            final int MEDIUM = 8 * SMALL;
+
+            // What file to serve?
+            final File file = new File(this.dir, request.getPathInfo());
+
+            // Only handle existing files
+            if (!file.exists())
+                return;
+
+            // we will handle this request
+            baseRequest.setHandled(true);
+
+            // Handle directories
+            if (file.isDirectory())
+            {
+                if (!request.getPathInfo().endsWith(URIUtil.SLASH))
+                {
+                    response.sendRedirect(response.encodeRedirectURL(URIUtil
+                            .addPaths(request.getRequestURI(), URIUtil.SLASH)));
+                    return;
+                }
+                String listing = Resource.newResource(file).getListHTML(
+                        request.getRequestURI(),
+                        request.getPathInfo().lastIndexOf("/") > 0);
+                response.setContentType("text/html; charset=utf-8");
+                response.getWriter().println(listing);
+                return;
+            }
+
+            // Set some content headers.
+            
+            // Jetty DefaultServlet will cache formatted date strings, but we
+            // will reformat for each request here
+            response.setDateHeader("Last-Modified", file.lastModified());
+            response.setDateHeader("Content-Length", file.length());
+            response.setContentType(mimeTypes.getMimeByExtension(file.getName()));
+
+            // send "small" files blocking directly from an input stream
+            if (file.length() < SMALL)
+            {
+                // need to caste to Jetty output stream for best API
+                ((HttpOutput) response.getOutputStream())
+                        .sendContent(FileChannel.open(file.toPath(),
+                                StandardOpenOption.READ));
+                return;
+            }
+
+            // send not "small" files asynchronously so we don't hold threads if
+            // the client is slow
+            final AsyncContext async = request.startAsync();
+            Callback completionCB = new Callback()
+            {
+                @Override
+                public void succeeded()
+                {
+                    // Async content write succeeded, so complete async response
+                    async.complete();
+                }
+
+                @Override
+                public void failed( Throwable x )
+                {
+                    // log error and complete async response;
+                    x.printStackTrace();
+                    async.complete();
+                }
+            };
+
+            // send "medium" files from an input stream
+            if (file.length() < MEDIUM)
+            {
+                // the file channel is closed by the async send
+                ((HttpOutput) response.getOutputStream())
+                        .sendContent(FileChannel.open(file.toPath(),
+                                StandardOpenOption.READ), completionCB);
+                return;
+            }
+
+            // for "large" files get the file mapped buffer to send Typically
+            // the resulting buffer should be cached as allocating kernel memory
+            // can be hard to GC on some JVMs. But for this example we will
+            // create a new buffer per file
+            ByteBuffer buffer;
+            try ( RandomAccessFile raf = new RandomAccessFile(file, "r"); )
+            {
+                buffer = raf.getChannel().map(MapMode.READ_ONLY, 0,
+                        raf.length());
+            }
+
+            // Assuming the file buffer might be shared cached version, so lets
+            // take our own view of it
+            buffer = buffer.asReadOnlyBuffer();
+
+            // send the content as a buffer with a callback to complete the
+            // async request need to caste to Jetty output stream for best API
+            ((HttpOutput) response.getOutputStream()).sendContent(buffer,
+                    completionCB);
+        }
+    }
+}
diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/FileServer.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/FileServer.java
new file mode 100644
index 0000000..18a90bf
--- /dev/null
+++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/FileServer.java
@@ -0,0 +1,59 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.embedded;
+
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.handler.DefaultHandler;
+import org.eclipse.jetty.server.handler.HandlerList;
+import org.eclipse.jetty.server.handler.ResourceHandler;
+
+/** 
+ * Simple Jetty FileServer.
+ * This is a simple example of Jetty configured as a FileServer.
+ */
+public class FileServer
+{
+    public static void main(String[] args) throws Exception
+    {
+        // Create a basic Jetty server object that will listen on port 8080.  Note that if you set this to port 0
+        // then a randomly available port will be assigned that you can either look in the logs for the port,
+        // or programmatically obtain it for use in test cases.
+        Server server = new Server(8080);
+
+        // Create the ResourceHandler. It is the object that will actually handle the request for a given file. It is
+        // a Jetty Handler object so it is suitable for chaining with other handlers as you will see in other examples.
+        ResourceHandler resource_handler = new ResourceHandler();
+        // Configure the ResourceHandler. Setting the resource base indicates where the files should be served out of.
+        // In this example it is the current directory but it can be configured to anything that the jvm has access to.
+        resource_handler.setDirectoriesListed(true);
+        resource_handler.setWelcomeFiles(new String[]{ "index.html" });
+        resource_handler.setResourceBase(".");
+
+        // Add the ResourceHandler to the server.
+        HandlerList handlers = new HandlerList();
+        handlers.setHandlers(new Handler[] { resource_handler, new DefaultHandler() });
+        server.setHandler(handlers);
+
+        // Start things up! By using the server.join() the server thread will join with the current thread.
+        // See "http://docs.oracle.com/javase/1.5.0/docs/api/java/lang/Thread.html#join()" for more details.
+        server.start();
+        server.join();
+    }
+}
diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/FileServerXml.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/FileServerXml.java
new file mode 100644
index 0000000..a9081d4
--- /dev/null
+++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/FileServerXml.java
@@ -0,0 +1,48 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.embedded;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.xml.XmlConfiguration;
+
+/**
+ * A Jetty FileServer.
+ * <p>
+ * This server is identical to {@link FileServer}, except that it is configured
+ * via an {@link XmlConfiguration} config file that does the identical work.
+ * </p>
+ * <p>
+ * See <a href=
+ * "http://git.eclipse.org/c/jetty/org.eclipse.jetty.project.git/tree/examples/embedded/src/main/resources/fileserver.xml"
+ * >fileserver.xml</a>
+ * </p>
+ */
+public class FileServerXml
+{
+    public static void main( String[] args ) throws Exception
+    {
+        Resource fileserverXml = Resource.newSystemResource("fileserver.xml");
+        XmlConfiguration configuration = new XmlConfiguration(
+                fileserverXml.getInputStream());
+        Server server = (Server) configuration.configure();
+        server.start();
+        server.join();
+    }
+}
diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/HelloHandler.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/HelloHandler.java
new file mode 100644
index 0000000..554767e
--- /dev/null
+++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/HelloHandler.java
@@ -0,0 +1,71 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.embedded;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+
+public class HelloHandler extends AbstractHandler
+{
+    final String greeting;
+    final String body;
+
+    public HelloHandler()
+    {
+        this("Hello World");
+    }
+
+    public HelloHandler( String greeting )
+    {
+        this(greeting, null);
+    }
+
+    public HelloHandler( String greeting, String body )
+    {
+        this.greeting = greeting;
+        this.body = body;
+    }
+
+    public void handle( String target,
+                        Request baseRequest,
+                        HttpServletRequest request,
+                        HttpServletResponse response ) throws IOException,
+                                                      ServletException
+    {
+        response.setContentType("text/html; charset=utf-8");
+        response.setStatus(HttpServletResponse.SC_OK);
+
+        PrintWriter out = response.getWriter();
+
+        out.println("<h1>" + greeting + "</h1>");
+        if (body != null)
+        {
+            out.println(body);
+        }
+
+        baseRequest.setHandled(true);
+    }
+}
diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/HelloServlet.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/HelloServlet.java
new file mode 100644
index 0000000..4d5d744
--- /dev/null
+++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/HelloServlet.java
@@ -0,0 +1,53 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.embedded;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+ at SuppressWarnings("serial")
+public class HelloServlet extends HttpServlet
+{
+    final String greeting;
+
+    public HelloServlet()
+    {
+        this("Hello");
+    }
+
+    public HelloServlet( String greeting )
+    {
+        this.greeting = greeting;
+    }
+
+    @Override
+    protected void doGet( HttpServletRequest request,
+                          HttpServletResponse response ) throws ServletException,
+                                                        IOException
+    {
+        response.setContentType("text/html");
+        response.setStatus(HttpServletResponse.SC_OK);
+        response.getWriter().println(
+                "<h1>" + greeting + " from HelloServlet</h1>");
+    }
+}
diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/JarServer.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/JarServer.java
new file mode 100644
index 0000000..e6da54a
--- /dev/null
+++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/JarServer.java
@@ -0,0 +1,51 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.embedded;
+
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.handler.DefaultHandler;
+import org.eclipse.jetty.server.handler.HandlerList;
+import org.eclipse.jetty.servlet.DefaultServlet;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jetty.util.resource.Resource;
+
+/** 
+ */
+public class JarServer
+{
+    public static void main(String[] args) throws Exception
+    {
+        Server server = new Server(8080);
+
+        ServletContextHandler context = new ServletContextHandler();
+        Resource.setDefaultUseCaches(true);
+        Resource base = Resource.newResource("jar:file:src/main/resources/content.jar!/");
+        context.setBaseResource(base);
+        context.addServlet(new ServletHolder(new DefaultServlet()), "/");
+        
+        HandlerList handlers = new HandlerList();
+        handlers.setHandlers(new Handler[] { context, new DefaultHandler() });
+        server.setHandler(handlers);
+
+        server.start();
+        server.join();
+    }
+}
diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/LikeJettyXml.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/LikeJettyXml.java
new file mode 100644
index 0000000..efaef9d
--- /dev/null
+++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/LikeJettyXml.java
@@ -0,0 +1,221 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.embedded;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.lang.management.ManagementFactory;
+
+import org.eclipse.jetty.deploy.DeploymentManager;
+import org.eclipse.jetty.deploy.PropertiesConfigurationManager;
+import org.eclipse.jetty.deploy.providers.WebAppProvider;
+import org.eclipse.jetty.jmx.MBeanContainer;
+import org.eclipse.jetty.security.HashLoginService;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.HttpConnectionFactory;
+import org.eclipse.jetty.server.LowResourceMonitor;
+import org.eclipse.jetty.server.NCSARequestLog;
+import org.eclipse.jetty.server.SecureRequestCustomizer;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.SslConnectionFactory;
+import org.eclipse.jetty.server.handler.ContextHandlerCollection;
+import org.eclipse.jetty.server.handler.DefaultHandler;
+import org.eclipse.jetty.server.handler.HandlerCollection;
+import org.eclipse.jetty.server.handler.RequestLogHandler;
+import org.eclipse.jetty.server.handler.StatisticsHandler;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
+import org.eclipse.jetty.webapp.Configuration;
+
+/**
+ * Starts the Jetty Distribution's demo-base directory using entirely
+ * embedded jetty techniques.
+ */
+public class LikeJettyXml
+{
+    public static void main( String[] args ) throws Exception
+    {
+        // Path to as-built jetty-distribution directory
+        String jettyHomeBuild = "../../jetty-distribution/target/distribution";
+        
+        // Find jetty home and base directories
+        String homePath = System.getProperty("jetty.home", jettyHomeBuild);
+        File homeDir = new File(homePath);
+        if (!homeDir.exists())
+        {
+            throw new FileNotFoundException(homeDir.getAbsolutePath());
+        }
+        String basePath = System.getProperty("jetty.base", homeDir + "/demo-base");
+        File baseDir = new File(basePath);
+        if(!baseDir.exists())
+        {
+            throw new FileNotFoundException(baseDir.getAbsolutePath());
+        }
+        
+        // Configure jetty.home and jetty.base system properties
+        String jetty_home = homeDir.getAbsolutePath();
+        String jetty_base = baseDir.getAbsolutePath();
+        System.setProperty("jetty.home", jetty_home);
+        System.setProperty("jetty.base", jetty_base);
+
+
+        // === jetty.xml ===
+        // Setup Threadpool
+        QueuedThreadPool threadPool = new QueuedThreadPool();
+        threadPool.setMaxThreads(500);
+
+        // Server
+        Server server = new Server(threadPool);
+
+        // Scheduler
+        server.addBean(new ScheduledExecutorScheduler());
+
+        // HTTP Configuration
+        HttpConfiguration http_config = new HttpConfiguration();
+        http_config.setSecureScheme("https");
+        http_config.setSecurePort(8443);
+        http_config.setOutputBufferSize(32768);
+        http_config.setRequestHeaderSize(8192);
+        http_config.setResponseHeaderSize(8192);
+        http_config.setSendServerVersion(true);
+        http_config.setSendDateHeader(false);
+        // httpConfig.addCustomizer(new ForwardedRequestCustomizer());
+
+        // Handler Structure
+        HandlerCollection handlers = new HandlerCollection();
+        ContextHandlerCollection contexts = new ContextHandlerCollection();
+        handlers.setHandlers(new Handler[] { contexts, new DefaultHandler() });
+        server.setHandler(handlers);
+
+        // Extra options
+        server.setDumpAfterStart(false);
+        server.setDumpBeforeStop(false);
+        server.setStopAtShutdown(true);
+
+        // === jetty-jmx.xml ===
+        MBeanContainer mbContainer = new MBeanContainer(
+                ManagementFactory.getPlatformMBeanServer());
+        server.addBean(mbContainer);
+
+
+        // === jetty-http.xml ===
+        ServerConnector http = new ServerConnector(server,
+                new HttpConnectionFactory(http_config));
+        http.setPort(8080);
+        http.setIdleTimeout(30000);
+        server.addConnector(http);
+
+
+        // === jetty-https.xml ===
+        // SSL Context Factory
+        SslContextFactory sslContextFactory = new SslContextFactory();
+        sslContextFactory.setKeyStorePath(jetty_home + "/etc/keystore");
+        sslContextFactory.setKeyStorePassword("OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4");
+        sslContextFactory.setKeyManagerPassword("OBF:1u2u1wml1z7s1z7a1wnl1u2g");
+        sslContextFactory.setTrustStorePath(jetty_home + "/etc/keystore");
+        sslContextFactory.setTrustStorePassword("OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4");
+        sslContextFactory.setExcludeCipherSuites("SSL_RSA_WITH_DES_CBC_SHA",
+                "SSL_DHE_RSA_WITH_DES_CBC_SHA", "SSL_DHE_DSS_WITH_DES_CBC_SHA",
+                "SSL_RSA_EXPORT_WITH_RC4_40_MD5",
+                "SSL_RSA_EXPORT_WITH_DES40_CBC_SHA",
+                "SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA",
+                "SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA");
+
+        // SSL HTTP Configuration
+        HttpConfiguration https_config = new HttpConfiguration(http_config);
+        https_config.addCustomizer(new SecureRequestCustomizer());
+
+        // SSL Connector
+        ServerConnector sslConnector = new ServerConnector(server,
+            new SslConnectionFactory(sslContextFactory,"http/1.1"),
+            new HttpConnectionFactory(https_config));
+        sslConnector.setPort(8443);
+        server.addConnector(sslConnector);
+
+
+        // === jetty-deploy.xml ===
+        DeploymentManager deployer = new DeploymentManager();
+        deployer.setContexts(contexts);
+        deployer.setContextAttribute(
+                "org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern",
+                ".*/servlet-api-[^/]*\\.jar$");
+
+        WebAppProvider webapp_provider = new WebAppProvider();
+        webapp_provider.setMonitoredDirName(jetty_base + "/webapps");
+        webapp_provider.setDefaultsDescriptor(jetty_home + "/etc/webdefault.xml");
+        webapp_provider.setScanInterval(1);
+        webapp_provider.setExtractWars(true);
+        webapp_provider.setConfigurationManager(new PropertiesConfigurationManager());
+
+        deployer.addAppProvider(webapp_provider);
+        server.addBean(deployer);
+        
+        // === setup jetty plus ==
+        Configuration.ClassList.setServerDefault(server).addAfter(
+                "org.eclipse.jetty.webapp.FragmentConfiguration",
+                "org.eclipse.jetty.plus.webapp.EnvConfiguration",
+                "org.eclipse.jetty.plus.webapp.PlusConfiguration");
+
+        // === jetty-stats.xml ===
+        StatisticsHandler stats = new StatisticsHandler();
+        stats.setHandler(server.getHandler());
+        server.setHandler(stats);
+
+
+        // === jetty-requestlog.xml ===
+        NCSARequestLog requestLog = new NCSARequestLog();
+        requestLog.setFilename(jetty_home + "/logs/yyyy_mm_dd.request.log");
+        requestLog.setFilenameDateFormat("yyyy_MM_dd");
+        requestLog.setRetainDays(90);
+        requestLog.setAppend(true);
+        requestLog.setExtended(true);
+        requestLog.setLogCookies(false);
+        requestLog.setLogTimeZone("GMT");
+        RequestLogHandler requestLogHandler = new RequestLogHandler();
+        requestLogHandler.setRequestLog(requestLog);
+        handlers.addHandler(requestLogHandler);
+
+
+        // === jetty-lowresources.xml ===
+        LowResourceMonitor lowResourcesMonitor=new LowResourceMonitor(server);
+        lowResourcesMonitor.setPeriod(1000);
+        lowResourcesMonitor.setLowResourcesIdleTimeout(200);
+        lowResourcesMonitor.setMonitorThreads(true);
+        lowResourcesMonitor.setMaxConnections(0);
+        lowResourcesMonitor.setMaxMemory(0);
+        lowResourcesMonitor.setMaxLowResourcesTime(5000);
+        server.addBean(lowResourcesMonitor);
+
+
+        // === test-realm.xml ===
+        HashLoginService login = new HashLoginService();
+        login.setName("Test Realm");
+        login.setConfig(jetty_base + "/etc/realm.properties");
+        login.setRefreshInterval(0);
+        server.addBean(login);
+
+        
+        // Start the server
+        server.start();
+        server.join();
+    }
+}
diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ManyConnectors.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ManyConnectors.java
new file mode 100644
index 0000000..e4bbf5e
--- /dev/null
+++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ManyConnectors.java
@@ -0,0 +1,127 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.embedded;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.HttpConnectionFactory;
+import org.eclipse.jetty.server.SecureRequestCustomizer;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.SslConnectionFactory;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+
+/**
+ * A Jetty server with multiple connectors.
+ */
+public class ManyConnectors
+{
+    public static void main( String[] args ) throws Exception
+    {
+        // Since this example shows off SSL configuration, we need a keystore
+        // with the appropriate key. These lookup of jetty.home is purely a hack
+        // to get access to a keystore that we use in many unit tests and should
+        // probably be a direct path to your own keystore.
+
+        String jettyDistKeystore = "../../jetty-distribution/target/distribution/etc/keystore";
+        String keystorePath = System.getProperty(
+                "example.keystore", jettyDistKeystore);
+        File keystoreFile = new File(keystorePath);
+        if (!keystoreFile.exists())
+        {
+            throw new FileNotFoundException(keystoreFile.getAbsolutePath());
+        }
+
+        // Create a basic jetty server object without declaring the port. Since
+        // we are configuring connectors directly we'll be setting ports on
+        // those connectors.
+        Server server = new Server();
+
+        // HTTP Configuration
+        // HttpConfiguration is a collection of configuration information
+        // appropriate for http and https. The default scheme for http is
+        // <code>http</code> of course, as the default for secured http is
+        // <code>https</code> but we show setting the scheme to show it can be
+        // done. The port for secured communication is also set here.
+        HttpConfiguration http_config = new HttpConfiguration();
+        http_config.setSecureScheme("https");
+        http_config.setSecurePort(8443);
+        http_config.setOutputBufferSize(32768);
+
+        // HTTP connector
+        // The first server connector we create is the one for http, passing in
+        // the http configuration we configured above so it can get things like
+        // the output buffer size, etc. We also set the port (8080) and
+        // configure an idle timeout.
+        ServerConnector http = new ServerConnector(server,
+                new HttpConnectionFactory(http_config));
+        http.setPort(8080);
+        http.setIdleTimeout(30000);
+
+        // SSL Context Factory for HTTPS and SPDY
+        // SSL requires a certificate so we configure a factory for ssl contents
+        // with information pointing to what keystore the ssl connection needs
+        // to know about. Much more configuration is available the ssl context,
+        // including things like choosing the particular certificate out of a
+        // keystore to be used.
+        SslContextFactory sslContextFactory = new SslContextFactory();
+        sslContextFactory.setKeyStorePath(keystoreFile.getAbsolutePath());
+        sslContextFactory.setKeyStorePassword("OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4");
+        sslContextFactory.setKeyManagerPassword("OBF:1u2u1wml1z7s1z7a1wnl1u2g");
+
+        // HTTPS Configuration
+        // A new HttpConfiguration object is needed for the next connector and
+        // you can pass the old one as an argument to effectively clone the
+        // contents. On this HttpConfiguration object we add a
+        // SecureRequestCustomizer which is how a new connector is able to
+        // resolve the https connection before handing control over to the Jetty
+        // Server.
+        HttpConfiguration https_config = new HttpConfiguration(http_config);
+        https_config.addCustomizer(new SecureRequestCustomizer());
+
+        // HTTPS connector
+        // We create a second ServerConnector, passing in the http configuration
+        // we just made along with the previously created ssl context factory.
+        // Next we set the port and a longer idle timeout.
+        ServerConnector https = new ServerConnector(server,
+                new SslConnectionFactory(sslContextFactory, "http/1.1"),
+                new HttpConnectionFactory(https_config));
+        https.setPort(8443);
+        https.setIdleTimeout(500000);
+
+        // Here you see the server having multiple connectors registered with
+        // it, now requests can flow into the server from both http and https
+        // urls to their respective ports and be processed accordingly by jetty.
+        // A simple handler is also registered with the server so the example
+        // has something to pass requests off to.
+
+        // Set the connectors
+        server.setConnectors(new Connector[] { http, https });
+
+        // Set a handler
+        server.setHandler(new HelloHandler());
+
+        // Start the server
+        server.start();
+        server.join();
+    }
+}
diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ManyContexts.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ManyContexts.java
new file mode 100644
index 0000000..4258326
--- /dev/null
+++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ManyContexts.java
@@ -0,0 +1,55 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.embedded;
+
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.ContextHandlerCollection;
+
+public class ManyContexts
+{
+    public static void main( String[] args ) throws Exception
+    {
+        Server server = new Server(8080);
+
+        ContextHandler context = new ContextHandler("/");
+        context.setContextPath("/");
+        context.setHandler(new HelloHandler("Root Hello"));
+
+        ContextHandler contextFR = new ContextHandler("/fr");
+        contextFR.setHandler(new HelloHandler("Bonjoir"));
+
+        ContextHandler contextIT = new ContextHandler("/it");
+        contextIT.setHandler(new HelloHandler("Bongiorno"));
+
+        ContextHandler contextV = new ContextHandler("/");
+        contextV.setVirtualHosts(new String[] { "127.0.0.2" });
+        contextV.setHandler(new HelloHandler("Virtual Hello"));
+
+        ContextHandlerCollection contexts = new ContextHandlerCollection();
+        contexts.setHandlers(new Handler[] { context, contextFR, contextIT,
+                contextV });
+
+        server.setHandler(contexts);
+
+        server.start();
+        server.join();
+    }
+}
diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ManyHandlers.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ManyHandlers.java
new file mode 100644
index 0000000..2bc37cb
--- /dev/null
+++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ManyHandlers.java
@@ -0,0 +1,150 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.embedded;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Map;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.NCSARequestLog;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.server.handler.DefaultHandler;
+import org.eclipse.jetty.server.handler.HandlerCollection;
+import org.eclipse.jetty.server.handler.HandlerList;
+import org.eclipse.jetty.server.handler.HandlerWrapper;
+import org.eclipse.jetty.server.handler.RequestLogHandler;
+import org.eclipse.jetty.util.ajax.JSON;
+
+/**
+ * Frequently many handlers are combined together to handle different aspects of
+ * a request. A handler may:
+ * <ul>
+ * <li>handle the request and completely generate the response
+ * <li>partially handle the request, but defer response generation to another
+ * handler.
+ * <li>select another handler to pass the request to.
+ * <li>use business logic to decide to do one of the above.
+ * </ul>
+ * Multiple handlers may be combined with:
+ * <ul>
+ * <li>{@link HandlerWrapper} which will nest one handler inside another. In
+ * this example, the HelloHandler is nested inside a HandlerWrapper that sets
+ * the greeting as a request attribute.
+ * <li>{@link HandlerList} which will call a collection of handlers until the
+ * request is marked as handled. In this example, a list is used to combine the
+ * param handler (which only handles the request if there are parameters) and
+ * the wrapper handler. Frequently handler lists are terminated with the
+ * {@link DefaultHandler}, which will generate a suitable 404 response if the
+ * request has not been handled.
+ * <li>{@link HandlerCollection} which will call each handler regardless if the
+ * request has been handled or not. Typically this is used to always pass a
+ * request to the logging handler.
+ * </ul>
+ */
+public class ManyHandlers
+{
+    /**
+     * Produce output that lists all of the request parameters
+     */
+    public static class ParamHandler extends AbstractHandler
+    {
+        public void handle( String target,
+                            Request baseRequest,
+                            HttpServletRequest request,
+                            HttpServletResponse response ) throws IOException,
+                                                          ServletException
+        {
+            Map<String, String[]> params = request.getParameterMap();
+            if (params.size() > 0)
+            {
+                response.setContentType("text/plain");
+                response.getWriter().println(JSON.toString(params));
+                baseRequest.setHandled(true);
+            }
+        }
+    }
+
+    /**
+     * Add a request attribute, but produce no output.
+     */
+    public static class WelcomeWrapHandler extends HandlerWrapper
+    {
+        @Override
+        public void handle( String target,
+                            Request baseRequest,
+                            HttpServletRequest request,
+                            HttpServletResponse response ) throws IOException,
+                                                          ServletException
+        {
+            request.setAttribute("welcome", "Hello");
+            super.handle(target, baseRequest, request, response);
+        }
+    }
+
+    public static void main( String[] args ) throws Exception
+    {
+        Server server = new Server(8080);
+
+        // create the handlers
+        Handler param = new ParamHandler();
+        HandlerWrapper wrapper = new WelcomeWrapHandler();
+        Handler hello = new HelloHandler();
+        Handler dft = new DefaultHandler();
+        RequestLogHandler requestLog = new RequestLogHandler();
+
+        // configure request logging
+        File requestLogFile = File.createTempFile("demo", "log");
+        NCSARequestLog ncsaLog = new NCSARequestLog(
+                requestLogFile.getAbsolutePath());
+        requestLog.setRequestLog(ncsaLog);
+
+        // create the handler collections
+        HandlerCollection handlers = new HandlerCollection();
+        HandlerList list = new HandlerList();
+
+        // link them all together
+        wrapper.setHandler(hello);
+        list.setHandlers(new Handler[] { param, wrapper, dft });
+        handlers.setHandlers(new Handler[] { list, requestLog });
+
+        // Handler tree looks like the following
+        // <pre>
+        // Server
+        // + HandlerCollection
+        // . + HandlerList
+        // . | + param (ParamHandler)
+        // . | + wrapper (WelcomeWrapHandler)
+        // . | | \ hello (HelloHandler)
+        // . | \ dft (DefaultHandler)
+        // . \ requestLog (RequestLogHandler)
+        // </pre>
+
+        server.setHandler(handlers);
+
+        server.start();
+        server.join();
+    }
+}
diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ManyServletContexts.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ManyServletContexts.java
new file mode 100644
index 0000000..19aade1
--- /dev/null
+++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ManyServletContexts.java
@@ -0,0 +1,63 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.embedded;
+
+import java.lang.management.ManagementFactory;
+
+import org.eclipse.jetty.jmx.MBeanContainer;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.handler.ContextHandlerCollection;
+import org.eclipse.jetty.servlet.DefaultServlet;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+
+public class ManyServletContexts
+{
+    public static void main( String[] args ) throws Exception
+    {
+        Server server = new Server(8080);
+
+        // Setup JMX
+        MBeanContainer mbContainer = new MBeanContainer(
+                ManagementFactory.getPlatformMBeanServer());
+        server.addBean(mbContainer, true);
+
+        // Declare server handler collection
+        ContextHandlerCollection contexts = new ContextHandlerCollection();
+        server.setHandler(contexts);
+
+        // Configure context "/" (root) for servlets
+        ServletContextHandler root = new ServletContextHandler(contexts, "/",
+                ServletContextHandler.SESSIONS);
+        // Add servlets to root context
+        root.addServlet(new ServletHolder(new HelloServlet("Hello")), "/");
+        root.addServlet(new ServletHolder(new HelloServlet("Ciao")), "/it/*");
+        root.addServlet(new ServletHolder(new HelloServlet("Bonjoir")), "/fr/*");
+
+        // Configure context "/other" for servlets
+        ServletContextHandler other = new ServletContextHandler(contexts,
+                "/other", ServletContextHandler.SESSIONS);
+        // Add servlets to /other context
+        other.addServlet(DefaultServlet.class.getCanonicalName(), "/");
+        other.addServlet(new ServletHolder(new HelloServlet("YO!")), "*.yo");
+
+        server.start();
+        server.join();
+    }
+}
diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/MinimalServlets.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/MinimalServlets.java
new file mode 100644
index 0000000..c385a44
--- /dev/null
+++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/MinimalServlets.java
@@ -0,0 +1,78 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.embedded;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.servlet.ServletHandler;
+
+public class MinimalServlets
+{
+    public static void main( String[] args ) throws Exception
+    {
+        // Create a basic jetty server object that will listen on port 8080.
+        // Note that if you set this to port 0 then a randomly available port
+        // will be assigned that you can either look in the logs for the port,
+        // or programmatically obtain it for use in test cases.
+        Server server = new Server(8080);
+
+        // The ServletHandler is a dead simple way to create a context handler
+        // that is backed by an instance of a Servlet.
+        // This handler then needs to be registered with the Server object.
+        ServletHandler handler = new ServletHandler();
+        server.setHandler(handler);
+
+        // Passing in the class for the Servlet allows jetty to instantiate an
+        // instance of that Servlet and mount it on a given context path.
+
+        // IMPORTANT:
+        // This is a raw Servlet, not a Servlet that has been configured
+        // through a web.xml @WebServlet annotation, or anything similar.
+        handler.addServletWithMapping(HelloServlet.class, "/*");
+
+        // Start things up!
+        server.start();
+
+        // The use of server.join() the will make the current thread join and
+        // wait until the server is done executing.
+        // See
+        // http://docs.oracle.com/javase/7/docs/api/java/lang/Thread.html#join()
+        server.join();
+    }
+
+    @SuppressWarnings("serial")
+    public static class HelloServlet extends HttpServlet
+    {
+        @Override
+        protected void doGet( HttpServletRequest request,
+                              HttpServletResponse response ) throws ServletException,
+                                                            IOException
+        {
+            response.setContentType("text/html");
+            response.setStatus(HttpServletResponse.SC_OK);
+            response.getWriter().println("<h1>Hello from HelloServlet</h1>");
+        }
+    }
+}
diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneConnector.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneConnector.java
new file mode 100644
index 0000000..7effc34
--- /dev/null
+++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneConnector.java
@@ -0,0 +1,50 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.embedded;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+
+/**
+ * A Jetty server with one connectors.
+ */
+public class OneConnector
+{
+    public static void main( String[] args ) throws Exception
+    {
+        // The Server
+        Server server = new Server();
+
+        // HTTP connector
+        ServerConnector http = new ServerConnector(server);
+        http.setHost("localhost");
+        http.setPort(8080);
+        http.setIdleTimeout(30000);
+
+        // Set the connector
+        server.addConnector(http);
+
+        // Set a handler
+        server.setHandler(new HelloHandler());
+
+        // Start the server
+        server.start();
+        server.join();
+    }
+}
diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneContext.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneContext.java
new file mode 100644
index 0000000..a6ace8b
--- /dev/null
+++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneContext.java
@@ -0,0 +1,43 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.embedded;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.handler.ContextHandler;
+
+public class OneContext
+{
+    public static void main( String[] args ) throws Exception
+    {
+        Server server = new Server( 8080 );
+
+        // Add a single handler on context "/hello"
+        ContextHandler context = new ContextHandler();
+        context.setContextPath( "/hello" );
+        context.setHandler( new HelloHandler() );
+
+        // Can be accessed using http://localhost:8080/hello
+
+        server.setHandler( context );
+
+        // Start the server
+        server.start();
+        server.join();
+    }
+}
diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneHandler.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneHandler.java
new file mode 100644
index 0000000..06a4294
--- /dev/null
+++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneHandler.java
@@ -0,0 +1,33 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.embedded;
+
+import org.eclipse.jetty.server.Server;
+
+public class OneHandler
+{
+    public static void main( String[] args ) throws Exception
+    {
+        Server server = new Server(8080);
+        server.setHandler(new HelloHandler());
+
+        server.start();
+        server.join();
+    }
+}
diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneServletContext.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneServletContext.java
new file mode 100644
index 0000000..ec9f461
--- /dev/null
+++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneServletContext.java
@@ -0,0 +1,45 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.embedded;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.servlet.DefaultServlet;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+
+public class OneServletContext
+{
+    public static void main( String[] args ) throws Exception
+    {
+        Server server = new Server(8080);
+
+        ServletContextHandler context = new ServletContextHandler(
+                ServletContextHandler.SESSIONS);
+        context.setContextPath("/");
+        context.setResourceBase(System.getProperty("java.io.tmpdir"));
+        server.setHandler(context);
+
+        // Add dump servlet
+        context.addServlet(DumpServlet.class, "/dump/*");
+        // Add default servlet
+        context.addServlet(DefaultServlet.class, "/");
+
+        server.start();
+        server.join();
+    }
+}
diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneServletContextJmxStats.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneServletContextJmxStats.java
new file mode 100644
index 0000000..252f4a1
--- /dev/null
+++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneServletContextJmxStats.java
@@ -0,0 +1,52 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.embedded;
+
+import java.lang.management.ManagementFactory;
+
+import org.eclipse.jetty.jmx.MBeanContainer;
+import org.eclipse.jetty.server.ConnectorStatistics;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.servlet.DefaultServlet;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+
+public class OneServletContextJmxStats
+{
+    public static void main( String[] args ) throws Exception
+    {
+        Server server = new Server(8080);
+        // Add JMX tracking to Server
+        server.addBean(new MBeanContainer(ManagementFactory
+                .getPlatformMBeanServer()));
+
+        ServletContextHandler context = new ServletContextHandler(
+                ServletContextHandler.SESSIONS);
+        context.setContextPath("/");
+        server.setHandler(context);
+
+        context.addServlet(DumpServlet.class, "/dump/*");
+        context.addServlet(DefaultServlet.class, "/");
+
+        // Add Connector Statistics tracking to all connectors
+        ConnectorStatistics.addToAllConnectors(server);
+
+        server.start();
+        server.join();
+    }
+}
diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneWebApp.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneWebApp.java
new file mode 100644
index 0000000..7c9a17b
--- /dev/null
+++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneWebApp.java
@@ -0,0 +1,82 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.embedded;
+
+import java.io.File;
+import java.lang.management.ManagementFactory;
+
+import org.eclipse.jetty.jmx.MBeanContainer;
+import org.eclipse.jetty.security.HashLoginService;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.webapp.WebAppContext;
+
+public class OneWebApp
+{
+    public static void main( String[] args ) throws Exception
+    {
+        // Create a basic jetty server object that will listen on port 8080.
+        // Note that if you set this to port 0 then a randomly available port
+        // will be assigned that you can either look in the logs for the port,
+        // or programmatically obtain it for use in test cases.
+        Server server = new Server(8080);
+
+        // Setup JMX
+        MBeanContainer mbContainer = new MBeanContainer(
+                ManagementFactory.getPlatformMBeanServer());
+        server.addBean(mbContainer);
+
+        // The WebAppContext is the entity that controls the environment in
+        // which a web application lives and breathes. In this example the
+        // context path is being set to "/" so it is suitable for serving root
+        // context requests and then we see it setting the location of the war.
+        // A whole host of other configurations are available, ranging from
+        // configuring to support annotation scanning in the webapp (through
+        // PlusConfiguration) to choosing where the webapp will unpack itself.
+        WebAppContext webapp = new WebAppContext();
+        webapp.setContextPath("/");
+        File warFile = new File(
+                "../../jetty-distribution/target/distribution/demo-base/webapps/test.war");
+        webapp.setWar(warFile.getAbsolutePath());
+
+        // A WebAppContext is a ContextHandler as well so it needs to be set to
+        // the server so it is aware of where to send the appropriate requests.
+        server.setHandler(webapp);
+
+        // Configure a LoginService
+        // Since this example is for our test webapp, we need to setup a
+        // LoginService so this shows how to create a very simple hashmap based
+        // one. The name of the LoginService needs to correspond to what is
+        // configured in the webapp's web.xml and since it has a lifecycle of
+        // its own we register it as a bean with the Jetty server object so it
+        // can be started and stopped according to the lifecycle of the server
+        // itself.
+        HashLoginService loginService = new HashLoginService();
+        loginService.setName("Test Realm");
+        loginService.setConfig("src/test/resources/realm.properties");
+        server.addBean(loginService);
+
+        // Start things up! 
+        server.start();
+
+        // The use of server.join() the will make the current thread join and
+        // wait until the server is done executing.
+        // See http://docs.oracle.com/javase/7/docs/api/java/lang/Thread.html#join()
+        server.join();
+    }
+}
diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneWebAppWithJsp.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneWebAppWithJsp.java
new file mode 100644
index 0000000..f391be7
--- /dev/null
+++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneWebAppWithJsp.java
@@ -0,0 +1,109 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.embedded;
+
+import java.io.File;
+import java.lang.management.ManagementFactory;
+
+import org.eclipse.jetty.jmx.MBeanContainer;
+import org.eclipse.jetty.security.HashLoginService;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.webapp.Configuration;
+import org.eclipse.jetty.webapp.WebAppContext;
+
+public class OneWebAppWithJsp
+{
+    public static void main( String[] args ) throws Exception
+    {
+        // Create a basic jetty server object that will listen on port 8080.
+        // Note that if you set this to port 0 then
+        // a randomly available port will be assigned that you can either look
+        // in the logs for the port,
+        // or programmatically obtain it for use in test cases.
+        Server server = new Server( 8080 );
+
+        // Setup JMX
+        MBeanContainer mbContainer = new MBeanContainer(
+                ManagementFactory.getPlatformMBeanServer() );
+        server.addBean( mbContainer );
+
+        // The WebAppContext is the entity that controls the environment in
+        // which a web application lives and
+        // breathes. In this example the context path is being set to "/" so it
+        // is suitable for serving root context
+        // requests and then we see it setting the location of the war. A whole
+        // host of other configurations are
+        // available, ranging from configuring to support annotation scanning in
+        // the webapp (through
+        // PlusConfiguration) to choosing where the webapp will unpack itself.
+        WebAppContext webapp = new WebAppContext();
+        webapp.setContextPath( "/" );
+        File warFile = new File(
+                "../../jetty-distribution/target/distribution/demo-base/webapps/test.war" );
+        if (!warFile.exists())
+        {
+            throw new RuntimeException( "Unable to find WAR File: "
+                    + warFile.getAbsolutePath() );
+        }
+        webapp.setWar( warFile.getAbsolutePath() );
+
+        // This webapp will use jsps and jstl. We need to enable the
+        // AnnotationConfiguration in order to correctly
+        // set up the jsp container
+        Configuration.ClassList classlist = Configuration.ClassList
+                .setServerDefault( server );
+        classlist.addBefore(
+                "org.eclipse.jetty.webapp.JettyWebXmlConfiguration",
+                "org.eclipse.jetty.annotations.AnnotationConfiguration" );
+
+        // Set the ContainerIncludeJarPattern so that jetty examines these
+        // container-path jars for tlds, web-fragments etc.
+        // If you omit the jar that contains the jstl .tlds, the jsp engine will
+        // scan for them instead.
+        webapp.setAttribute(
+                "org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern",
+                ".*/[^/]*servlet-api-[^/]*\\.jar$|.*/javax.servlet.jsp.jstl-.*\\.jar$|.*/[^/]*taglibs.*\\.jar$" );
+
+        // A WebAppContext is a ContextHandler as well so it needs to be set to
+        // the server so it is aware of where to
+        // send the appropriate requests.
+        server.setHandler( webapp );
+
+        // Configure a LoginService.
+        // Since this example is for our test webapp, we need to setup a
+        // LoginService so this shows how to create a very simple hashmap based
+        // one. The name of the LoginService needs to correspond to what is
+        // configured in the webapp's web.xml and since it has a lifecycle of
+        // its own we register it as a bean with the Jetty server object so it
+        // can be started and stopped according to the lifecycle of the server
+        // itself.
+        HashLoginService loginService = new HashLoginService();
+        loginService.setName( "Test Realm" );
+        loginService.setConfig( "src/test/resources/realm.properties" );
+        server.addBean( loginService );
+
+        // Start things up! 
+        server.start();
+
+        // The use of server.join() the will make the current thread join and
+        // wait until the server is done executing.
+        // See http://docs.oracle.com/javase/7/docs/api/java/lang/Thread.html#join()
+        server.join();
+    }
+}
diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ProxyServer.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ProxyServer.java
new file mode 100644
index 0000000..8236264
--- /dev/null
+++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ProxyServer.java
@@ -0,0 +1,51 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.embedded;
+
+import org.eclipse.jetty.proxy.ConnectHandler;
+import org.eclipse.jetty.proxy.ProxyServlet;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+
+public class ProxyServer
+{
+    public static void main( String[] args ) throws Exception
+    {
+        Server server = new Server();
+        ServerConnector connector = new ServerConnector(server);
+        connector.setPort(8888);
+        server.addConnector(connector);
+
+        // Setup proxy handler to handle CONNECT methods
+        ConnectHandler proxy = new ConnectHandler();
+        server.setHandler(proxy);
+
+        // Setup proxy servlet
+        ServletContextHandler context = new ServletContextHandler(proxy, "/",
+                ServletContextHandler.SESSIONS);
+        ServletHolder proxyServlet = new ServletHolder(ProxyServlet.class);
+        proxyServlet.setInitParameter("blackList", "www.eclipse.org");
+        context.addServlet(proxyServlet, "/*");
+
+        server.start();
+    }
+
+}
diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/SecuredHelloHandler.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/SecuredHelloHandler.java
new file mode 100644
index 0000000..5604dba
--- /dev/null
+++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/SecuredHelloHandler.java
@@ -0,0 +1,104 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.embedded;
+
+import java.util.Collections;
+
+import org.eclipse.jetty.security.ConstraintMapping;
+import org.eclipse.jetty.security.ConstraintSecurityHandler;
+import org.eclipse.jetty.security.HashLoginService;
+import org.eclipse.jetty.security.LoginService;
+import org.eclipse.jetty.security.authentication.BasicAuthenticator;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.util.security.Constraint;
+
+public class SecuredHelloHandler
+{
+    public static void main( String[] args ) throws Exception
+    {
+        // Create a basic jetty server object that will listen on port 8080.
+        // Note that if you set this to port 0 then a randomly available port
+        // will be assigned that you can either look in the logs for the port,
+        // or programmatically obtain it for use in test cases.
+        Server server = new Server(8080);
+
+        // Since this example is for our test webapp, we need to setup a
+        // LoginService so this shows how to create a very simple hashmap based
+        // one. The name of the LoginService needs to correspond to what is
+        // configured a webapp's web.xml and since it has a lifecycle of its own
+        // we register it as a bean with the Jetty server object so it can be
+        // started and stopped according to the lifecycle of the server itself.
+        // In this example the name can be whatever you like since we are not
+        // dealing with webapp realms.
+        LoginService loginService = new HashLoginService("MyRealm",
+                "src/test/resources/realm.properties");
+        server.addBean(loginService);
+
+        // A security handler is a jetty handler that secures content behind a
+        // particular portion of a url space. The ConstraintSecurityHandler is a
+        // more specialized handler that allows matching of urls to different
+        // constraints. The server sets this as the first handler in the chain,
+        // effectively applying these constraints to all subsequent handlers in
+        // the chain.
+        ConstraintSecurityHandler security = new ConstraintSecurityHandler();
+        server.setHandler(security);
+
+        // This constraint requires authentication and in addition that an
+        // authenticated user be a member of a given set of roles for
+        // authorization purposes.
+        Constraint constraint = new Constraint();
+        constraint.setName("auth");
+        constraint.setAuthenticate(true);
+        constraint.setRoles(new String[] { "user", "admin" });
+
+        // Binds a url pattern with the previously created constraint. The roles
+        // for this constraing mapping are mined from the Constraint itself
+        // although methods exist to declare and bind roles separately as well.
+        ConstraintMapping mapping = new ConstraintMapping();
+        mapping.setPathSpec("/*");
+        mapping.setConstraint(constraint);
+
+        // First you see the constraint mapping being applied to the handler as
+        // a singleton list, however you can passing in as many security
+        // constraint mappings as you like so long as they follow the mapping
+        // requirements of the servlet api. Next we set a BasicAuthenticator
+        // instance which is the object that actually checks the credentials
+        // followed by the LoginService which is the store of known users, etc.
+        security.setConstraintMappings(Collections.singletonList(mapping));
+        security.setAuthenticator(new BasicAuthenticator());
+        security.setLoginService(loginService);
+
+        // The Hello Handler is the handler we are securing so we create one,
+        // and then set it as the handler on the
+        // security handler to complain the simple handler chain.
+        HelloHandler hh = new HelloHandler();
+
+        // chain the hello handler into the security handler
+        security.setHandler(hh);
+
+        // Start things up!
+        server.start();
+
+        // The use of server.join() the will make the current thread join and
+        // wait until the server is done executing.
+        // See
+        // http://docs.oracle.com/javase/7/docs/api/java/lang/Thread.html#join()
+        server.join();
+    }
+}
diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ServerWithAnnotations.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ServerWithAnnotations.java
new file mode 100644
index 0000000..0537de6
--- /dev/null
+++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ServerWithAnnotations.java
@@ -0,0 +1,82 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.embedded;
+
+import java.io.File;
+
+import org.eclipse.jetty.plus.jndi.EnvEntry;
+import org.eclipse.jetty.plus.jndi.Resource;
+import org.eclipse.jetty.plus.jndi.Transaction;
+import org.eclipse.jetty.security.HashLoginService;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.webapp.Configuration;
+import org.eclipse.jetty.webapp.WebAppContext;
+
+/**
+ * ServerWithAnnotations
+ */
+public class ServerWithAnnotations
+{
+    public static final void main( String args[] ) throws Exception
+    {
+        // Create the server
+        Server server = new Server(8080);
+
+        // Enable parsing of jndi-related parts of web.xml and jetty-env.xml
+        Configuration.ClassList classlist = Configuration.ClassList
+                .setServerDefault(server);
+        classlist.addAfter("org.eclipse.jetty.webapp.FragmentConfiguration",
+                "org.eclipse.jetty.plus.webapp.EnvConfiguration",
+                "org.eclipse.jetty.plus.webapp.PlusConfiguration");
+        classlist.addBefore(
+                "org.eclipse.jetty.webapp.JettyWebXmlConfiguration",
+                "org.eclipse.jetty.annotations.AnnotationConfiguration");
+
+        // Create a WebApp
+        WebAppContext webapp = new WebAppContext();
+        webapp.setContextPath("/");
+        File warFile = new File(
+                "../../jetty-distribution/target/distribution/demo-base/webapps/test.war");
+        webapp.setWar(warFile.getAbsolutePath());
+        webapp.setAttribute(
+                "org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern",
+                ".*/javax.servlet-[^/]*\\.jar$|.*/servlet-api-[^/]*\\.jar$");
+        server.setHandler(webapp);
+
+        // Register new transaction manager in JNDI
+        // At runtime, the webapp accesses this as java:comp/UserTransaction
+        new Transaction(new com.acme.MockUserTransaction());
+
+        // Define an env entry with webapp scope.
+        new EnvEntry(webapp, "maxAmount", new Double(100), true);
+
+        // Register a mock DataSource scoped to the webapp
+        new Resource(webapp, "jdbc/mydatasource", new com.acme.MockDataSource());
+
+        // Configure a LoginService
+        HashLoginService loginService = new HashLoginService();
+        loginService.setName("Test Realm");
+        loginService.setConfig("src/test/resources/realm.properties");
+        server.addBean(loginService);
+
+        server.start();
+        server.join();
+    }
+
+}
diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ServerWithJMX.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ServerWithJMX.java
new file mode 100644
index 0000000..79d2a5f
--- /dev/null
+++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ServerWithJMX.java
@@ -0,0 +1,56 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.embedded;
+
+import java.lang.management.ManagementFactory;
+
+import javax.management.remote.JMXServiceURL;
+
+import org.eclipse.jetty.jmx.ConnectorServer;
+import org.eclipse.jetty.jmx.MBeanContainer;
+import org.eclipse.jetty.server.Server;
+
+/**
+ * The simplest possible Jetty server.
+ */
+public class ServerWithJMX
+{
+    public static void main( String[] args ) throws Exception
+    {
+        // === jetty-jmx.xml ===
+        MBeanContainer mbContainer = new MBeanContainer(
+                ManagementFactory.getPlatformMBeanServer());
+        
+        Server server = new Server(8080);
+        server.addBean(mbContainer);
+        
+        ConnectorServer jmx = new ConnectorServer(
+                new JMXServiceURL(
+                        "rmi",
+                        null,
+                        1999,
+                        "/jndi/rmi://localhost:1999/jmxrmi"),
+                        "org.eclipse.jetty.jmx:name=rmiconnectorserver");
+        server.addBean(jmx);
+        
+        server.start();
+        server.dumpStdErr();
+        server.join();
+    }
+}
diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ServerWithJNDI.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ServerWithJNDI.java
new file mode 100644
index 0000000..cf02dfe
--- /dev/null
+++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ServerWithJNDI.java
@@ -0,0 +1,116 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.embedded;
+
+import java.io.File;
+import java.util.Properties;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.webapp.Configuration;
+import org.eclipse.jetty.webapp.WebAppContext;
+
+/**
+ * ServerWithJNDI
+ */
+public class ServerWithJNDI
+{
+    public static void main( String[] args ) throws Exception
+    {
+
+        // Create the server
+        Server server = new Server(8080);
+
+        // Enable parsing of jndi-related parts of web.xml and jetty-env.xml
+        Configuration.ClassList classlist = Configuration.ClassList
+                .setServerDefault(server);
+        classlist.addAfter("org.eclipse.jetty.webapp.FragmentConfiguration",
+                "org.eclipse.jetty.plus.webapp.EnvConfiguration",
+                "org.eclipse.jetty.plus.webapp.PlusConfiguration");
+
+        // Create a WebApp
+        WebAppContext webapp = new WebAppContext();
+        webapp.setContextPath("/");
+        File warFile = new File(
+                "../../jetty-distribution/target/distribution/demo-base/webapps/test.war");
+        webapp.setWar(warFile.getAbsolutePath());
+        server.setHandler(webapp);
+
+        // Register new transaction manager in JNDI
+        // At runtime, the webapp accesses this as java:comp/UserTransaction
+        new org.eclipse.jetty.plus.jndi.Transaction(
+                new com.acme.MockUserTransaction());
+
+        // Define an env entry with Server scope.
+        // At runtime, the webapp accesses this as java:comp/env/woggle
+        // This is equivalent to putting an env-entry in web.xml:
+        // <env-entry>
+        // <env-entry-name>woggle</env-entry-name>
+        // <env-entry-type>java.lang.Integer</env-entry-type>
+        // <env-entry-value>4000</env-entry-value>
+        // </env-entry>
+        new org.eclipse.jetty.plus.jndi.EnvEntry(server, "woggle", new Integer(4000), false);
+
+        // Define an env entry with webapp scope.
+        // At runtime, the webapp accesses this as java:comp/env/wiggle
+        // This is equivalent to putting a web.xml entry in web.xml:
+        // <env-entry>
+        // <env-entry-name>wiggle</env-entry-name>
+        // <env-entry-value>100</env-entry-value>
+        // <env-entry-type>java.lang.Double</env-entry-type>
+        // </env-entry>
+        // Note that the last arg of "true" means that this definition for
+        // "wiggle" would override an entry of the
+        // same name in web.xml
+        new org.eclipse.jetty.plus.jndi.EnvEntry(webapp, "wiggle", new Double(100), true);
+
+        // Register a reference to a mail service scoped to the webapp.
+        // This must be linked to the webapp by an entry in web.xml:
+        // <resource-ref>
+        // <res-ref-name>mail/Session</res-ref-name>
+        // <res-type>javax.mail.Session</res-type>
+        // <res-auth>Container</res-auth>
+        // </resource-ref>
+        // At runtime the webapp accesses this as java:comp/env/mail/Session
+        org.eclipse.jetty.jndi.factories.MailSessionReference mailref = new org.eclipse.jetty.jndi.factories.MailSessionReference();
+        mailref.setUser("CHANGE-ME");
+        mailref.setPassword("CHANGE-ME");
+        Properties props = new Properties();
+        props.put("mail.smtp.auth", "false");
+        props.put("mail.smtp.host", "CHANGE-ME");
+        props.put("mail.from", "CHANGE-ME");
+        props.put("mail.debug", "false");
+        mailref.setProperties(props);
+        new org.eclipse.jetty.plus.jndi.Resource(webapp, "mail/Session", mailref);
+
+        // Register a mock DataSource scoped to the webapp
+        // This must be linked to the webapp via an entry in web.xml:
+        // <resource-ref>
+        // <res-ref-name>jdbc/mydatasource</res-ref-name>
+        // <res-type>javax.sql.DataSource</res-type>
+        // <res-auth>Container</res-auth>
+        // </resource-ref>
+        // At runtime the webapp accesses this as
+        // java:comp/env/jdbc/mydatasource
+        new org.eclipse.jetty.plus.jndi.Resource(
+                webapp, "jdbc/mydatasource", new com.acme.MockDataSource());
+
+        server.start();
+        server.join();
+    }
+}
diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/SimplestServer.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/SimplestServer.java
new file mode 100644
index 0000000..0591419
--- /dev/null
+++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/SimplestServer.java
@@ -0,0 +1,35 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.embedded;
+
+import org.eclipse.jetty.server.Server;
+
+/**
+ * The simplest possible Jetty server.
+ */
+public class SimplestServer
+{
+    public static void main( String[] args ) throws Exception
+    {
+        Server server = new Server(8080);
+        server.start();
+        server.dumpStdErr();
+        server.join();
+    }
+}
diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/SpdyConnector.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/SpdyConnector.java
new file mode 100644
index 0000000..eed17c2
--- /dev/null
+++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/SpdyConnector.java
@@ -0,0 +1,114 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.embedded;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.HttpConnectionFactory;
+import org.eclipse.jetty.server.SecureRequestCustomizer;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.SslConnectionFactory;
+import org.eclipse.jetty.spdy.server.NPNServerConnectionFactory;
+import org.eclipse.jetty.spdy.server.SPDYServerConnectionFactory;
+import org.eclipse.jetty.spdy.server.http.HTTPSPDYServerConnectionFactory;
+import org.eclipse.jetty.spdy.server.http.ReferrerPushStrategy;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+
+/**
+ * A Jetty server with HTTP and SPDY connectors.
+ */
+public class SpdyConnector
+{
+    public static void main(String[] args) throws Exception
+    {
+        // Path to as-built jetty-distribution directory
+        String jettyHomeBuild = "../../jetty-distribution/target/distribution";
+        
+        // Find jetty home directories
+        String homePath = System.getProperty("jetty.home", jettyHomeBuild);
+        File homeDir = new File(homePath);
+        if (!homeDir.exists())
+        {
+            throw new FileNotFoundException(homeDir.getAbsolutePath());
+        }
+        String jetty_home = homeDir.getAbsolutePath();
+        System.setProperty("jetty.home", jetty_home);
+
+        // The Server
+        Server server = new Server();
+
+        // HTTP Configuration
+        HttpConfiguration http_config = new HttpConfiguration();
+        http_config.setSecureScheme("https");
+        http_config.setSecurePort(8443);
+
+        // HTTP connector
+        ServerConnector http = new ServerConnector(server,
+                new HttpConnectionFactory(http_config));        
+        http.setPort(8080);
+        server.addConnector(http);
+ 
+        // SSL Context Factory for HTTPS and SPDY
+        SslContextFactory sslContextFactory = new SslContextFactory();
+        sslContextFactory.setKeyStorePath(jetty_home + "/etc/keystore");
+        sslContextFactory.setKeyStorePassword("OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4");
+        sslContextFactory.setKeyManagerPassword("OBF:1u2u1wml1z7s1z7a1wnl1u2g");
+
+        // HTTPS Configuration
+        HttpConfiguration https_config = new HttpConfiguration(http_config);
+        https_config.addCustomizer(new SecureRequestCustomizer());
+        
+        // SPDY versions
+        HTTPSPDYServerConnectionFactory spdy2 = 
+                new HTTPSPDYServerConnectionFactory(2, https_config);
+
+        HTTPSPDYServerConnectionFactory spdy3 = 
+                new HTTPSPDYServerConnectionFactory(3, https_config, 
+                        new ReferrerPushStrategy());
+
+        // NPN Factory
+        SPDYServerConnectionFactory.checkProtocolNegotiationAvailable();
+        NPNServerConnectionFactory npn = new NPNServerConnectionFactory(
+                spdy3.getProtocol(), 
+                spdy2.getProtocol(),
+                http.getDefaultProtocol());
+        npn.setDefaultProtocol(http.getDefaultProtocol());
+
+        // SSL Factory
+        SslConnectionFactory ssl = new SslConnectionFactory(
+                sslContextFactory, npn.getProtocol());
+
+        // SPDY Connector
+        ServerConnector spdyConnector = new ServerConnector(server, ssl, 
+                npn, spdy3, spdy2, 
+                new HttpConnectionFactory(https_config));
+        spdyConnector.setPort(8443);
+        server.addConnector(spdyConnector);
+        
+        // Set a handler
+        server.setHandler(new HelloHandler());
+
+        // Start the server
+        server.start();
+        server.join();
+    }
+}
diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/SpdyServer.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/SpdyServer.java
new file mode 100644
index 0000000..5e8c92a
--- /dev/null
+++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/SpdyServer.java
@@ -0,0 +1,210 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.embedded;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.lang.management.ManagementFactory;
+
+import org.eclipse.jetty.deploy.DeploymentManager;
+import org.eclipse.jetty.deploy.providers.WebAppProvider;
+import org.eclipse.jetty.jmx.MBeanContainer;
+import org.eclipse.jetty.security.HashLoginService;
+import org.eclipse.jetty.server.AsyncNCSARequestLog;
+import org.eclipse.jetty.server.ForwardedRequestCustomizer;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.HttpConnectionFactory;
+import org.eclipse.jetty.server.NCSARequestLog;
+import org.eclipse.jetty.server.SecureRequestCustomizer;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.SslConnectionFactory;
+import org.eclipse.jetty.server.handler.ContextHandlerCollection;
+import org.eclipse.jetty.server.handler.DefaultHandler;
+import org.eclipse.jetty.server.handler.HandlerCollection;
+import org.eclipse.jetty.server.handler.RequestLogHandler;
+import org.eclipse.jetty.server.handler.StatisticsHandler;
+import org.eclipse.jetty.spdy.server.NPNServerConnectionFactory;
+import org.eclipse.jetty.spdy.server.SPDYServerConnectionFactory;
+import org.eclipse.jetty.spdy.server.http.HTTPSPDYServerConnectionFactory;
+import org.eclipse.jetty.spdy.server.http.PushStrategy;
+import org.eclipse.jetty.spdy.server.http.ReferrerPushStrategy;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+
+public class SpdyServer
+{
+    public static void main( String[] args ) throws Exception
+    {
+        // Path to as-built jetty-distribution directory
+        String jettyHomeBuild = "../../jetty-distribution/target/distribution";
+
+        // Find jetty home directories
+        String homePath = System.getProperty("jetty.home", jettyHomeBuild);
+        File homeDir = new File(homePath);
+        if (!homeDir.exists())
+        {
+            throw new FileNotFoundException(homeDir.getAbsolutePath());
+        }
+        String jetty_home = homeDir.getAbsolutePath();
+        System.setProperty("jetty.home", jetty_home);
+
+        // Setup Threadpool
+        QueuedThreadPool threadPool = new QueuedThreadPool(512);
+
+        // Setup Jetty Server instance
+        Server server = new Server(threadPool);
+        server.manage(threadPool);
+        server.setDumpAfterStart(false);
+        server.setDumpBeforeStop(false);
+
+        // Setup JMX
+        MBeanContainer mbContainer = new MBeanContainer(
+                ManagementFactory.getPlatformMBeanServer());
+        server.addBean(mbContainer);
+
+        // Common HTTP configuration
+        HttpConfiguration config = new HttpConfiguration();
+        config.setSecurePort(8443);
+        config.addCustomizer(new ForwardedRequestCustomizer());
+        config.addCustomizer(new SecureRequestCustomizer());
+        config.setSendServerVersion(true);
+
+        // Http Connector Setup
+
+        // A plain HTTP connector listening on port 8080. Note that it's also
+        // possible to have port 8080 configured as a non SSL SPDY connector.
+        // But the specification and most browsers do not allow to use SPDY
+        // without SSL encryption. However some browsers allow it to be
+        // configured.
+        HttpConnectionFactory http = new HttpConnectionFactory(config);
+        ServerConnector httpConnector = new ServerConnector(server, http);
+        httpConnector.setPort(8080);
+        httpConnector.setIdleTimeout(10000);
+        server.addConnector(httpConnector);
+
+        // SSL configurations
+
+        // We need a SSLContextFactory for the SSL encryption. That
+        // SSLContextFactory will be used by the SPDY
+        // connector.
+        SslContextFactory sslContextFactory = new SslContextFactory();
+        sslContextFactory.setKeyStorePath(jetty_home + "/etc/keystore");
+        sslContextFactory.setKeyStorePassword("OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4");
+        sslContextFactory.setKeyManagerPassword("OBF:1u2u1wml1z7s1z7a1wnl1u2g");
+        sslContextFactory.setTrustStorePath(jetty_home + "/etc/keystore");
+        sslContextFactory.setTrustStorePassword("OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4");
+        sslContextFactory.setExcludeCipherSuites(
+                "SSL_RSA_WITH_DES_CBC_SHA",
+                "SSL_DHE_RSA_WITH_DES_CBC_SHA", 
+                "SSL_DHE_DSS_WITH_DES_CBC_SHA",
+                "SSL_RSA_EXPORT_WITH_RC4_40_MD5",
+                "SSL_RSA_EXPORT_WITH_DES40_CBC_SHA",
+                "SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA",
+                "SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA");
+
+        // Spdy Connector
+
+        // Make sure that the required NPN implementations are available.
+        SPDYServerConnectionFactory.checkProtocolNegotiationAvailable();
+
+        // A ReferrerPushStrategy is being initialized.
+        // See:
+        // http://www.eclipse.org/jetty/documentation/current/spdy-configuring-push.html
+        // for more details.
+        PushStrategy push = new ReferrerPushStrategy();
+        HTTPSPDYServerConnectionFactory spdy2 = 
+                new HTTPSPDYServerConnectionFactory(2, config, push);
+        spdy2.setInputBufferSize(8192);
+        spdy2.setInitialWindowSize(32768);
+
+        // We need a connection factory per protocol that our server is supposed
+        // to support on the NPN port. We then
+        // create a ServerConnector and pass in the supported factories. NPN
+        // will then be used to negotiate the
+        // protocol with the client.
+        HTTPSPDYServerConnectionFactory spdy3 = 
+                new HTTPSPDYServerConnectionFactory(3, config, push);
+        spdy3.setInputBufferSize(8192);
+
+        NPNServerConnectionFactory npn = new NPNServerConnectionFactory(
+                spdy3.getProtocol(), spdy2.getProtocol(), http.getProtocol());
+        npn.setDefaultProtocol(http.getProtocol());
+        npn.setInputBufferSize(1024);
+
+        SslConnectionFactory ssl = new SslConnectionFactory(sslContextFactory,
+                npn.getProtocol());
+
+        // Setup the npn connector on port 8443
+        ServerConnector spdyConnector = new ServerConnector(server, ssl, 
+                npn, spdy3, spdy2, http);
+        spdyConnector.setPort(8443);
+
+        server.addConnector(spdyConnector);
+
+        // The following section adds some handlers, deployers and webapp
+        // providers. See
+        // http://www.eclipse.org/jetty/documentation/current/advanced-embedding.html
+        // for details.
+
+        // Setup handlers
+        HandlerCollection handlers = new HandlerCollection();
+        ContextHandlerCollection contexts = new ContextHandlerCollection();
+        RequestLogHandler requestLogHandler = new RequestLogHandler();
+
+        handlers.setHandlers(new Handler[] { contexts, new DefaultHandler(),
+                requestLogHandler });
+
+        StatisticsHandler stats = new StatisticsHandler();
+        stats.setHandler(handlers);
+
+        server.setHandler(stats);
+
+        // Setup deployers
+        DeploymentManager deployer = new DeploymentManager();
+        deployer.setContexts(contexts);
+        server.addBean(deployer);
+
+        WebAppProvider webapp_provider = new WebAppProvider();
+        webapp_provider.setMonitoredDirName(jetty_home + "/webapps");
+        webapp_provider.setParentLoaderPriority(false);
+        webapp_provider.setExtractWars(true);
+        webapp_provider.setScanInterval(2);
+        webapp_provider.setDefaultsDescriptor(jetty_home
+                + "/etc/webdefault.xml");
+        deployer.addAppProvider(webapp_provider);
+
+        HashLoginService login = new HashLoginService();
+        login.setName("Test Realm");
+        login.setConfig(jetty_home + "/etc/realm.properties");
+        server.addBean(login);
+
+        NCSARequestLog requestLog = new AsyncNCSARequestLog();
+        requestLog.setFilename(jetty_home + "/logs/jetty-yyyy_mm_dd.log");
+        requestLog.setExtended(false);
+        requestLogHandler.setRequestLog(requestLog);
+
+        server.setStopAtShutdown(true);
+
+        server.start();
+        server.dumpStdErr();
+        server.join();
+    }
+}
diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/SplitFileServer.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/SplitFileServer.java
new file mode 100644
index 0000000..92c767c
--- /dev/null
+++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/SplitFileServer.java
@@ -0,0 +1,93 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.embedded;
+
+import java.io.File;
+
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.ContextHandlerCollection;
+import org.eclipse.jetty.server.handler.ResourceHandler;
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.eclipse.jetty.util.resource.Resource;
+
+/**
+ * A {@link ContextHandlerCollection} handler may be used to direct a request to
+ * a specific Context. The URI path prefix and optional virtual host is used to
+ * select the context.
+ */
+public class SplitFileServer
+{
+    public static void main( String[] args ) throws Exception
+    {
+        // Create the Server object and a corresponding ServerConnector and then
+        // set the port for the connector. In this example the server will
+        // listen on port 8090. If you set this to port 0 then when the server
+        // has been started you can called connector.getLocalPort() to
+        // programmatically get the port the server started on.
+        Server server = new Server();
+        ServerConnector connector = new ServerConnector(server);
+        connector.setPort(8090);
+        server.setConnectors(new Connector[] { connector });
+
+        // Create a Context Handler and ResourceHandler. The ContextHandler is
+        // getting set to "/" path but this could be anything you like for
+        // builing out your url. Note how we are setting the ResourceBase using
+        // our jetty maven testing utilities to get the proper resource
+        // directory, you needn't use these, you simply need to supply the paths
+        // you are looking to serve content from.
+        ContextHandler context0 = new ContextHandler();
+        context0.setContextPath("/");
+        ResourceHandler rh0 = new ResourceHandler();
+        File dir0 = MavenTestingUtils.getTestResourceDir("dir0");
+        rh0.setBaseResource(Resource.newResource(dir0));
+        context0.setHandler(rh0);
+
+        // Rinse and repeat the previous item, only specifying a different
+        // resource base.
+        ContextHandler context1 = new ContextHandler();
+        context1.setContextPath("/");
+        ResourceHandler rh1 = new ResourceHandler();
+        File dir1 = MavenTestingUtils.getTestResourceDir("dir1");
+        rh1.setBaseResource(Resource.newResource(dir1));
+        context1.setHandler(rh1);
+
+        // Create a ContextHandlerCollection and set the context handlers to it.
+        // This will let jetty process urls against the declared contexts in
+        // order to match up content.
+        ContextHandlerCollection contexts = new ContextHandlerCollection();
+        contexts.setHandlers(new Handler[] { context0, context1 });
+
+        server.setHandler(contexts);
+
+        // Start things up! 
+        server.start();
+        
+        // Dump the server state
+        System.out.println(server.dump());
+
+        // The use of server.join() the will make the current thread join and
+        // wait until the server is done executing.
+        // See http://docs.oracle.com/javase/7/docs/api/java/lang/Thread.html#join()
+        server.join();
+    }
+}
diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/WebSocketJsrServer.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/WebSocketJsrServer.java
new file mode 100644
index 0000000..7774a60
--- /dev/null
+++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/WebSocketJsrServer.java
@@ -0,0 +1,68 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.embedded;
+
+import javax.websocket.OnMessage;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.websocket.jsr356.server.ServerContainer;
+import org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer;
+
+/**
+ * Example of setting up a javax.websocket server with Jetty embedded
+ */
+public class WebSocketJsrServer
+{
+    /**
+     * A server socket endpoint
+     */
+    @ServerEndpoint(value = "/echo")
+    public static class EchoJsrSocket
+    {
+        @OnMessage
+        public void onMessage( Session session, String message )
+        {
+            session.getAsyncRemote().sendText(message);
+        }
+    }
+
+    public static void main( String[] args ) throws Exception
+    {
+        Server server = new Server(8080);
+
+        ServletContextHandler context = new ServletContextHandler(
+                ServletContextHandler.SESSIONS);
+        context.setContextPath("/");
+        server.setHandler(context);
+
+        // Enable javax.websocket configuration for the context
+        ServerContainer wsContainer = WebSocketServerContainerInitializer
+                .configureContext(context);
+
+        // Add your websockets to the container
+        wsContainer.addEndpoint(EchoJsrSocket.class);
+
+        server.start();
+        context.dumpStdErr();
+        server.join();
+    }
+}
diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/WebSocketServer.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/WebSocketServer.java
new file mode 100644
index 0000000..85136ed
--- /dev/null
+++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/WebSocketServer.java
@@ -0,0 +1,80 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.embedded;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
+import org.eclipse.jetty.websocket.api.annotations.WebSocket;
+import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
+import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
+
+/**
+ * Example of setting up a Jetty WebSocket server
+ * <p>
+ * Note: this uses the Jetty WebSocket API, not the javax.websocket API.
+ */
+public class WebSocketServer
+{
+    /**
+     * Example of a Jetty API WebSocket Echo Socket
+     */
+    @WebSocket
+    public static class EchoSocket
+    {
+        @OnWebSocketMessage
+        public void onMessage( Session session, String message )
+        {
+            session.getRemote().sendStringByFuture(message);
+        }
+    }
+
+    /**
+     * Servlet layer
+     */
+    @SuppressWarnings("serial")
+    public static class EchoServlet extends WebSocketServlet
+    {
+        @Override
+        public void configure( WebSocketServletFactory factory )
+        {
+            // Register the echo websocket with the basic WebSocketCreator
+            factory.register(EchoSocket.class);
+        }
+    }
+
+    public static void main( String[] args ) throws Exception
+    {
+        Server server = new Server(8080);
+
+        ServletContextHandler context = new ServletContextHandler(
+                ServletContextHandler.SESSIONS);
+        context.setContextPath("/");
+        server.setHandler(context);
+
+        // Add the echo socket servlet to the /echo path map
+        context.addServlet(new ServletHolder(EchoServlet.class), "/echo");
+
+        server.start();
+        context.dumpStdErr();
+        server.join();
+    }
+}
diff --git a/examples/embedded/src/main/resources/content.jar b/examples/embedded/src/main/resources/content.jar
new file mode 100644
index 0000000..846f71f
Binary files /dev/null and b/examples/embedded/src/main/resources/content.jar differ
diff --git a/examples/embedded/src/main/resources/exampleserver.xml b/examples/embedded/src/main/resources/exampleserver.xml
new file mode 100644
index 0000000..a0fc811
--- /dev/null
+++ b/examples/embedded/src/main/resources/exampleserver.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0"?>
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
+
+<Configure id="ExampleServer" class="org.eclipse.jetty.server.Server">
+
+  <Set name="connectors">
+    <Array type="org.eclipse.jetty.server.Connector">
+      <Item>
+        <New class="org.eclipse.jetty.server.ServerConnector">
+          <Arg><Ref refid="ExampleServer"/></Arg>
+          <Set name="port">8080</Set>
+        </New>
+      </Item>
+    </Array>
+  </Set>
+
+  <New id="context" class="org.eclipse.jetty.servlet.ServletContextHandler">
+    <Set name="contextPath">/hello</Set>
+    <Call name="addServlet">
+      <Arg>org.eclipse.jetty.embedded.HelloServlet</Arg>
+      <Arg>/</Arg>
+    </Call>
+  </New>
+
+  <Set name="handler">
+    <New class="org.eclipse.jetty.server.handler.HandlerCollection">
+      <Set name="handlers">
+        <Array type="org.eclipse.jetty.server.Handler">
+          <Item>
+            <Ref refid="context" />
+          </Item>
+          <Item>
+            <New class="org.eclipse.jetty.server.handler.DefaultHandler" />
+          </Item>
+        </Array>
+      </Set>
+    </New>
+  </Set>
+</Configure>
diff --git a/examples/embedded/src/main/resources/fileserver.xml b/examples/embedded/src/main/resources/fileserver.xml
new file mode 100644
index 0000000..0b4daa3
--- /dev/null
+++ b/examples/embedded/src/main/resources/fileserver.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0"?>
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
+
+<Configure id="FileServer" class="org.eclipse.jetty.server.Server">
+
+    <Call name="addConnector">
+      <Arg>
+          <New class="org.eclipse.jetty.server.ServerConnector">
+            <Arg><Ref refid="FileServer"/></Arg>
+            <Set name="port">8080</Set>
+          </New>
+      </Arg>
+    </Call>
+
+    <Set name="handler">
+      <New class="org.eclipse.jetty.server.handler.HandlerList">
+        <Set name="handlers">
+          <Array type="org.eclipse.jetty.server.Handler">
+            <Item>
+              <New class="org.eclipse.jetty.server.handler.ResourceHandler">
+                <Set name="directoriesListed">true</Set>
+                <Set name="welcomeFiles">
+                  <Array type="String"><Item>index.html</Item></Array>
+                </Set>
+                <Set name="resourceBase">.</Set>
+              </New>
+            </Item>
+            <Item>
+              <New class="org.eclipse.jetty.server.handler.DefaultHandler">
+              </New>
+            </Item>
+          </Array>
+        </Set>
+      </New>
+    </Set>
+</Configure>
diff --git a/examples/embedded/src/main/resources/jetty-logging.properties b/examples/embedded/src/main/resources/jetty-logging.properties
new file mode 100644
index 0000000..d353093
--- /dev/null
+++ b/examples/embedded/src/main/resources/jetty-logging.properties
@@ -0,0 +1,12 @@
+org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
+org.eclipse.jetty.LEVEL=INFO
+org.eclipse.jetty.STACKS=true
+org.eclipse.jetty.SOURCE=false
+#org.eclipse.jetty.STACKS=false
+#org.eclipse.jetty.spdy.LEVEL=DEBUG
+#org.eclipse.jetty.server.LEVEL=DEBUG
+#org.eclipse.jetty.io.LEVEL=DEBUG
+#org.eclipse.jetty.io.ssl.LEVEL=DEBUG
+#org.eclipse.jetty.spdy.server.LEVEL=DEBUG
+#org.eclipse.jetty.server.LEVEL=DEBUG
+#org.eclipse.jetty.servlets.LEVEL=DEBUG
diff --git a/examples/embedded/src/main/resources/jetty-otherserver.xml b/examples/embedded/src/main/resources/jetty-otherserver.xml
new file mode 100644
index 0000000..4c8a5cd
--- /dev/null
+++ b/examples/embedded/src/main/resources/jetty-otherserver.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0"?>
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
+
+<Configure id="OtherServer" class="org.eclipse.jetty.server.Server">
+    <Set name="handler">
+      <New id="Handlers" class="org.eclipse.jetty.server.handler.HandlerCollection">
+        <Set name="handlers">
+         <Array type="org.eclipse.jetty.server.Handler">
+           <Item>
+             <New id="OtherContexts" class="org.eclipse.jetty.server.handler.ContextHandlerCollection"/>
+           </Item>
+           <Item>
+             <New class="org.eclipse.jetty.server.handler.DefaultHandler"/>
+           </Item>
+         </Array>
+        </Set>
+      </New>
+    </Set>
+
+  <Call name="addConnector">
+    <Arg>
+      <New class="org.eclipse.jetty.server.ServerConnector">
+        <Arg name="server"><Ref refid="OtherServer" /></Arg>
+        <Set name="port">8888</Set>
+      </New>
+    </Arg>
+  </Call>
+
+  <Call name="addBean">
+    <Arg>
+      <New id="DeploymentManager" class="org.eclipse.jetty.deploy.DeploymentManager">
+        <Set name="contexts">
+          <Ref refid="OtherContexts" />
+        </Set>
+
+        <Call id="webappprovider" name="addAppProvider">
+          <Arg>
+            <New class="org.eclipse.jetty.deploy.providers.WebAppProvider">
+              <Set name="monitoredDirName"><Property name="jetty.base" default="." />/other-webapps</Set>
+              <Set name="defaultsDescriptor"><Property name="jetty.home" default="." />/etc/webdefault.xml</Set>
+              <Set name="extractWars">true</Set>
+              <Set name="configurationManager">
+                <New class="org.eclipse.jetty.deploy.PropertiesConfigurationManager"/>
+              </Set>
+            </New>
+          </Arg>
+        </Call>
+      </New>
+    </Arg>
+  </Call>
+</Configure>
diff --git a/examples/embedded/src/test/java/org/eclipse/jetty/embedded/GzipHandlerTest.java b/examples/embedded/src/test/java/org/eclipse/jetty/embedded/GzipHandlerTest.java
new file mode 100644
index 0000000..65a68df
--- /dev/null
+++ b/examples/embedded/src/test/java/org/eclipse/jetty/embedded/GzipHandlerTest.java
@@ -0,0 +1,125 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.embedded;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.util.zip.GZIPInputStream;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpTester;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.LocalConnector;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.servlets.gzip.GzipHandler;
+import org.eclipse.jetty.util.IO;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class GzipHandlerTest
+{
+    private static String __content =
+        "Lorem ipsum dolor sit amet, consectetur adipiscing elit. In quis felis nunc. "+
+        "Quisque suscipit mauris et ante auctor ornare rhoncus lacus aliquet. Pellentesque "+
+        "habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. "+
+        "Vestibulum sit amet felis augue, vel convallis dolor. Cras accumsan vehicula diam "+
+        "at faucibus. Etiam in urna turpis, sed congue mi. Morbi et lorem eros. Donec vulputate "+
+        "velit in risus suscipit lobortis. Aliquam id urna orci, nec sollicitudin ipsum. "+
+        "Cras a orci turpis. Donec suscipit vulputate cursus. Mauris nunc tellus, fermentum "+
+        "eu auctor ut, mollis at diam. Quisque porttitor ultrices metus, vitae tincidunt massa "+
+        "sollicitudin a. Vivamus porttitor libero eget purus hendrerit cursus. Integer aliquam "+
+        "consequat mauris quis luctus. Cras enim nibh, dignissim eu faucibus ac, mollis nec neque. "+
+        "Aliquam purus mauris, consectetur nec convallis lacinia, porta sed ante. Suspendisse "+
+        "et cursus magna. Donec orci enim, molestie a lobortis eu, imperdiet vitae neque.";
+
+    private Server _server;
+    private LocalConnector _connector;
+
+    @Before
+    public void init() throws Exception
+    {
+        _server = new Server();
+        _connector = new LocalConnector(_server);
+        _server.addConnector(_connector);
+
+        Handler testHandler = new AbstractHandler()
+        {
+            public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException,
+                    ServletException
+            {
+                PrintWriter writer = response.getWriter();
+                writer.write(__content);
+                writer.close();
+
+                baseRequest.setHandled(true);
+            }
+        };
+
+        GzipHandler gzipHandler = new GzipHandler();
+        gzipHandler.setHandler(testHandler);
+
+        _server.setHandler(gzipHandler);
+        _server.start();
+    }
+
+    @After
+    public void destroy() throws Exception
+    {
+        _server.stop();
+        _server.join();
+    }
+
+    @Test
+    public void testGzipHandler() throws Exception
+    {
+        // generated and parsed test
+        HttpTester.Request request = HttpTester.newRequest();
+        HttpTester.Response response;
+
+        request.setMethod("GET");
+        request.setVersion("HTTP/1.0");
+        request.setHeader("Host","tester");
+        request.setHeader("accept-encoding","gzip");
+        request.setURI("/");
+
+        response = HttpTester.parseResponse(_connector.getResponses(request.generate()));
+
+        assertTrue(response.get("Content-Encoding").equalsIgnoreCase("gzip"));
+        assertEquals(HttpServletResponse.SC_OK,response.getStatus());
+
+        InputStream testIn = new GZIPInputStream(new ByteArrayInputStream(response.getContentBytes()));
+        ByteArrayOutputStream testOut = new ByteArrayOutputStream();
+        IO.copy(testIn,testOut);
+
+        assertEquals(__content, testOut.toString("UTF8"));
+
+    }
+}
diff --git a/examples/embedded/src/test/java/org/eclipse/jetty/embedded/TestXml.java b/examples/embedded/src/test/java/org/eclipse/jetty/embedded/TestXml.java
new file mode 100644
index 0000000..7bfe1b8
--- /dev/null
+++ b/examples/embedded/src/test/java/org/eclipse/jetty/embedded/TestXml.java
@@ -0,0 +1,36 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.embedded;
+
+import org.eclipse.jetty.xml.XmlConfiguration;
+
+public class TestXml
+{
+    public static void main(String[] args) throws Exception
+    {
+        System.setProperty("jetty.home","../jetty-distribution/target/distribution");
+        XmlConfiguration.main(new String[]
+            {
+            "../jetty-jmx/src/main/config/etc/jetty-jmx.xml",
+            "../jetty-server/src/main/config/etc/jetty.xml",
+            "../jetty-spdy/spdy-jetty-http-webapp/src/main/config/etc/jetty-spdy.xml"
+            }
+        );
+    }
+}
diff --git a/examples/embedded/src/test/resources/dir0/test0.txt b/examples/embedded/src/test/resources/dir0/test0.txt
new file mode 100644
index 0000000..a39c44c
--- /dev/null
+++ b/examples/embedded/src/test/resources/dir0/test0.txt
@@ -0,0 +1 @@
+test0
\ No newline at end of file
diff --git a/examples/embedded/src/test/resources/dir1/test1.txt b/examples/embedded/src/test/resources/dir1/test1.txt
new file mode 100644
index 0000000..f079749
--- /dev/null
+++ b/examples/embedded/src/test/resources/dir1/test1.txt
@@ -0,0 +1 @@
+test1
\ No newline at end of file
diff --git a/examples/embedded/src/test/resources/realm.properties b/examples/embedded/src/test/resources/realm.properties
new file mode 100644
index 0000000..9d88b85
--- /dev/null
+++ b/examples/embedded/src/test/resources/realm.properties
@@ -0,0 +1,21 @@
+#
+# This file defines users passwords and roles for a HashUserRealm
+#
+# The format is
+#  <username>: <password>[,<rolename> ...]
+#
+# Passwords may be clear text, obfuscated or checksummed.  The class 
+# org.eclipse.util.Password should be used to generate obfuscated
+# passwords or password checksums
+#
+# If DIGEST Authentication is used, the password must be in a recoverable
+# format, either plain text or OBF:.
+#
+jetty: MD5:164c88b302622e17050af52c89945d44,user
+admin: CRYPT:adpexzg3FUZAk,server-administrator,content-administrator,admin,user
+other: OBF:1xmk1w261u9r1w1c1xmq,user
+plain: plain,user
+user: password,user
+
+# This entry is for digest auth.  The credential is a MD5 hash of username:realmname:password
+digest: MD5:6e120743ad67abfbc385bc2bb754e297,user
diff --git a/examples/pom.xml b/examples/pom.xml
new file mode 100644
index 0000000..9a4c8fd
--- /dev/null
+++ b/examples/pom.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+// ========================================================================
+// Copyright (c) Webtide LLC
+// 
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at 
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.apache.org/licenses/LICENSE-2.0.txt
+//
+// You may elect to redistribute this code under either of these licenses. 
+// ========================================================================
+ -->
+<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>
+  <parent>
+    <groupId>org.eclipse.jetty</groupId>
+    <artifactId>jetty-project</artifactId>
+    <version>9.2.13.v20150730</version>
+    <relativePath>../pom.xml</relativePath>
+  </parent>
+  <groupId>org.eclipse.jetty.examples</groupId>
+  <artifactId>examples-parent</artifactId>
+  <name>Jetty Examples :: Parent</name>
+  <packaging>pom</packaging>
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>findbugs-maven-plugin</artifactId>
+        <configuration>
+          <!-- No Point running Findbugs on example projects -->
+          <skip>true</skip>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+  <modules>
+    <!--
+    - The async-rest and embedded are examples that have historical locations, 
+    - new ones should appear nested under o.e.jetty.examples groupId
+    -->
+    <module>async-rest</module>
+    <module>embedded</module>
+  </modules>
+</project>
diff --git a/jetty-aggregate/jetty-all-server/pom.xml b/jetty-aggregate/jetty-all-server/pom.xml
deleted file mode 100644
index 7c8cde4..0000000
--- a/jetty-aggregate/jetty-all-server/pom.xml
+++ /dev/null
@@ -1,213 +0,0 @@
-<?xml version="1.0"?>
-<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">
-    <parent>
-        <groupId>org.eclipse.jetty.aggregate</groupId>
-        <artifactId>jetty-aggregate-project</artifactId>
-        <version>8.1.17.v20150415</version>
-    </parent>
-    <modelVersion>4.0.0</modelVersion>
-    <artifactId>jetty-all-server</artifactId>
-    <name>Jetty :: Aggregate :: All Server</name>
-    <url>http://www.eclipse.org/jetty</url>
-    <properties>
-      <bundle-symbolic-name>${project.groupId}.${project.artifactId}</bundle-symbolic-name>
-    </properties>
-
-    <build>
-        <sourceDirectory>${project.build.directory}/sources</sourceDirectory>
-        <plugins>
-            <plugin>
-                <groupId>org.apache.maven.plugins</groupId>
-                <artifactId>maven-dependency-plugin</artifactId>
-                <executions>
-                    <execution>
-                        <id>unpack-dependencies</id>
-                        <goals>
-                            <goal>unpack-dependencies</goal>
-                        </goals>
-                        <configuration>
-                            <excludes>**/MANIFEST.MF,javax/**</excludes>
-                            <excludeArtifactIds>javax</excludeArtifactIds>
-                            <excludeGroupIds>javax,org.eclipse.jetty.orbit</excludeGroupIds>
-                            <outputDirectory>${project.build.directory}/classes</outputDirectory>
-                            <overWriteReleases>false</overWriteReleases>
-                            <overWriteSnapshots>true</overWriteSnapshots>
-                        </configuration>
-                    </execution>
-                    <execution>
-                        <id>unpack-source</id>
-                        <phase>generate-sources</phase>
-                        <goals>
-                            <goal>unpack-dependencies</goal>
-                        </goals>
-                        <configuration>
-                            <classifier>sources</classifier>
-                            <includes>**/*</includes>
-                            <excludes>META-INF/**</excludes>
-                            <includeGroupIds>org.eclipse.jetty</includeGroupIds>
-                            <excludeArtifactIds>javax</excludeArtifactIds>
-                            <excludeGroupIds>javax,org.eclipse.jetty.orbit</excludeGroupIds>
-                            <outputDirectory>${project.build.directory}/sources</outputDirectory>
-                            <overWriteReleases>true</overWriteReleases>
-                            <overWriteSnapshots>true</overWriteSnapshots>
-                        </configuration>
-                    </execution>
-                </executions>
-            </plugin>
-            <plugin>
-                <artifactId>maven-jar-plugin</artifactId>
-                <configuration>
-                    <archive>
-                        <manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
-                    </archive>
-                </configuration>
-            </plugin>
-            <plugin>
-                <groupId>org.apache.felix</groupId>
-                <artifactId>maven-bundle-plugin</artifactId>
-                <extensions>true</extensions>
-                <executions>
-                    <execution>
-                        <id>bundle-manifest</id>
-                        <phase>process-classes</phase>
-                        <goals>
-                            <goal>manifest</goal>
-                        </goals>
-                    </execution>
-                </executions>
-                <configuration>
-                    <instructions>
-                        <Import-Package>
-                            !org.eclipse.jetty*,
-                            javax.annotation;version="1.0.0";resolution:=optional,
-                            javax.servlet;version="2.6.0",
-                            javax.servlet.annotation;version="2.6.0",
-                            javax.servlet.descriptor;version="2.6.0",
-                            javax.servlet.http;version="2.6.0",
-                            javax.mail;version="1.4.0";resolution:=optional,
-                            javax.mail.event;version="1.4.0";resolution:=optional,
-                            javax.mail.internet;version="1.4.0";resolution:=optional,
-                            javax.mail.search;version="1.4.0";resolution:=optional,
-                            javax.mail.util;version="1.4.0";resolution:=optional,
-                            javax.transaction;version="1.1.0";resolution:=optional,
-                            javax.transaction.xa;version="1.1.0";resolution:=optional,
-                            org.slf4j;resolution:=optional,
-                            org.slf4j.spi;resolution:=optional,
-                            org.slf4j.helpers;resolution:=optional,
-                            org.xml.sax,
-                            org.xml.sax.helpers,
-                            javax.security.cert,
-                            javax.xml.parsers,
-                            javax.net.ssl,
-                            !org.mortbay.*,
-                            org.objectweb.asm;version="3.1.0";resolution:=optional,
-                            org.objectweb.asm.commons;version="3.1.0";resolution:=optional,
-                            javax.security.auth.message*;resolution:=optional,
-                            *
-                        </Import-Package>
-                        <Export-Package>org.eclipse.jetty*;version="${parsedVersion.osgiVersion}"</Export-Package>
-                        <!-- disable the uses directive: jetty will accomodate pretty much any versions
-                        of the packages it uses; no need to reflect some tight dependency determined at
-                        compilation time. -->
-                        <_nouses>true</_nouses>
-                        <Bundle-RequiredExecutionEnvironment>J2SE-1.5</Bundle-RequiredExecutionEnvironment>
-                    </instructions>
-                </configuration>
-            </plugin>
-        </plugins>
-    </build>
-
-    <dependencies>
-        <dependency>
-            <groupId>org.eclipse.jetty</groupId>
-            <artifactId>jetty-deploy</artifactId>
-            <version>${project.version}</version>
-            <scope>provided</scope>
-        </dependency>
-        <dependency>
-          <groupId>org.eclipse.jetty</groupId>
-          <artifactId>jetty-websocket</artifactId>
-          <version>${project.version}</version>
-          <scope>provided</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.eclipse.jetty.orbit</groupId>
-            <artifactId>javax.servlet</artifactId>
-            <scope>compile</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.eclipse.jetty</groupId>
-            <artifactId>jetty-jmx</artifactId>
-            <version>${project.version}</version>
-            <scope>provided</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.eclipse.jetty</groupId>
-            <artifactId>jetty-plus</artifactId>
-            <version>${project.version}</version>
-            <scope>provided</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.eclipse.jetty</groupId>
-            <artifactId>jetty-ajp</artifactId>
-            <version>${project.version}</version>
-            <scope>provided</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.eclipse.jetty</groupId>
-            <artifactId>jetty-annotations</artifactId>
-            <version>${project.version}</version>
-            <scope>provided</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.eclipse.jetty</groupId>
-            <artifactId>jetty-jaspi</artifactId>
-            <version>${project.version}</version>
-            <scope>provided</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.eclipse.jetty</groupId>
-            <artifactId>jetty-jndi</artifactId>
-            <version>${project.version}</version>
-            <scope>provided</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.eclipse.jetty</groupId>
-            <artifactId>jetty-rewrite</artifactId>
-            <version>${project.version}</version>
-            <scope>provided</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.eclipse.jetty</groupId>
-            <artifactId>jetty-servlets</artifactId>
-            <version>${project.version}</version>
-            <scope>provided</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.eclipse.jetty</groupId>
-            <artifactId>jetty-nested</artifactId>
-            <version>${project.version}</version>
-            <scope>provided</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.eclipse.jetty.orbit</groupId>
-            <artifactId>javax.security.auth.message</artifactId>
-            <scope>compile</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.eclipse.jetty.orbit</groupId>
-            <artifactId>javax.mail.glassfish</artifactId>
-            <scope>compile</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.eclipse.jetty.orbit</groupId>
-            <artifactId>javax.activation</artifactId>
-            <scope>compile</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.eclipse.jetty.orbit</groupId>
-            <artifactId>javax.annotation</artifactId>
-            <scope>compile</scope>
-        </dependency>
-    </dependencies>
-</project>
diff --git a/jetty-aggregate/jetty-all/pom.xml b/jetty-aggregate/jetty-all/pom.xml
deleted file mode 100644
index 552589a..0000000
--- a/jetty-aggregate/jetty-all/pom.xml
+++ /dev/null
@@ -1,176 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<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">
-  <parent>
-    <groupId>org.eclipse.jetty.aggregate</groupId>
-    <artifactId>jetty-aggregate-project</artifactId>
-    <version>8.1.17.v20150415</version>
-  </parent>
-  <modelVersion>4.0.0</modelVersion>
-  <artifactId>jetty-all</artifactId>
-  <name>Jetty :: Aggregate :: All core Jetty</name>
-  <url>http://www.eclipse.org/jetty</url>
-  <build>
-    <sourceDirectory>${project.build.directory}/sources</sourceDirectory>
-    <plugins>
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-dependency-plugin</artifactId>
-        <executions>
-          <execution>
-            <id>unpack-dependencies</id>
-            <goals>
-              <goal>unpack-dependencies</goal>
-            </goals>
-            <configuration>
-              <excludes>**/MANIFEST.MF,javax/**</excludes>
-              <excludeArtifactIds>javax</excludeArtifactIds>
-              <excludeGroupIds>javax,org.eclipse.jetty.orbit</excludeGroupIds>
-              <outputDirectory>${project.build.directory}/classes</outputDirectory>
-              <overWriteReleases>false</overWriteReleases>
-              <overWriteSnapshots>true</overWriteSnapshots>
-            </configuration>
-          </execution>
-          <execution>
-            <id>unpack-source</id>
-            <phase>generate-sources</phase>
-            <goals>
-              <goal>unpack-dependencies</goal>
-            </goals>
-            <configuration>
-              <classifier>sources</classifier>
-              <includes>**/*</includes>
-              <excludes>META-INF/**,**/Servlet3Continuation*,**/Jetty6Continuation*</excludes>
-              <includeGroupIds>org.eclipse.jetty</includeGroupIds>
-              <excludeArtifactIds>javax</excludeArtifactIds>
-              <excludeGroupIds>javax,org.eclipse.jetty.orbit</excludeGroupIds>
-              <outputDirectory>${project.build.directory}/sources</outputDirectory>
-              <overWriteReleases>true</overWriteReleases>
-              <overWriteSnapshots>true</overWriteSnapshots>
-            </configuration>
-          </execution>
-        </executions>
-      </plugin>
-      <plugin>
-        <groupId>org.apache.maven.plugins
-        </groupId>
-        <artifactId>maven-jar-plugin</artifactId>
-        <executions>
-          <execution>
-            <id>package</id>
-            <phase>package</phase>
-            <goals>
-              <goal>jar</goal>
-            </goals>
-            <configuration>
-              <archive>
-                <manifest>
-                </manifest>
-                <manifestEntries>
-                  <mode>development</mode>
-                  <url>http://eclipse.org/jetty</url>
-                  <Built-By>${user.name}</Built-By>
-                  <package>org.eclipse.jetty</package>
-                  <Bundle-License>http://git.eclipse.org/c/jetty/org.eclipse.jetty.project.git/tree/NOTICE.txt</Bundle-License>
-                  <Bundle-Name>Jetty</Bundle-Name>
-                </manifestEntries>
-              </archive>
-            </configuration>
-          </execution>
-        </executions>
-      </plugin>
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-javadoc-plugin</artifactId>
-        <executions>
-          <execution>
-            <id>javadoc-jar</id>
-            <phase>compile</phase>
-            <goals>
-              <goal>jar</goal>
-            </goals>
-          </execution>
-        </executions>
-      </plugin>
-    </plugins>
-  </build>
-
-  <dependencies>
-    <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-client</artifactId>
-      <version>${project.version}</version>
-      <scope>provided</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-deploy</artifactId>
-      <version>${project.version}</version>
-      <scope>provided</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-websocket</artifactId>
-      <version>${project.version}</version>
-      <scope>provided</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.eclipse.jetty.orbit</groupId>
-      <artifactId>javax.servlet</artifactId>
-      <scope>compile</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-jmx</artifactId>
-      <version>${project.version}</version>
-      <scope>provided</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-plus</artifactId>
-      <version>${project.version}</version>
-      <scope>provided</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-ajp</artifactId>
-      <version>${project.version}</version>
-      <scope>provided</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-annotations</artifactId>
-      <version>${project.version}</version>
-      <scope>provided</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-jaspi</artifactId>
-      <version>${project.version}</version>
-      <scope>provided</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-jndi</artifactId>
-      <version>${project.version}</version>
-      <scope>provided</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-rewrite</artifactId>
-      <version>${project.version}</version>
-      <scope>provided</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-servlets</artifactId>
-      <version>${project.version}</version>
-      <scope>provided</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-nested</artifactId>
-      <version>${project.version}</version>
-      <scope>provided</scope>
-    </dependency>
-  </dependencies>
-</project>
diff --git a/jetty-aggregate/jetty-client/pom.xml b/jetty-aggregate/jetty-client/pom.xml
deleted file mode 100644
index fe60954..0000000
--- a/jetty-aggregate/jetty-client/pom.xml
+++ /dev/null
@@ -1,87 +0,0 @@
-<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">
-  <parent>
-    <groupId>org.eclipse.jetty.aggregate</groupId>
-    <artifactId>jetty-aggregate-project</artifactId>
-    <version>8.1.17.v20150415</version>
-  </parent>
-  <modelVersion>4.0.0</modelVersion>
-  <artifactId>jetty-client</artifactId>
-  <name>Jetty :: Aggregate :: HTTP Client</name>
-  <url>http://www.eclipse.org/jetty</url>
-  <build>
-    <sourceDirectory>${project.build.directory}/sources</sourceDirectory>
-    <plugins>
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-dependency-plugin</artifactId>
-        <executions>
-          <execution>
-            <id>unpack-dependencies</id>
-            <goals>
-              <goal>unpack-dependencies</goal>
-            </goals>
-            <configuration>
-              <includes>META-INF/**,org/eclipse/**</includes>
-              <excludes>**/MANIFEST.MF</excludes>
-              <outputDirectory>${project.build.directory}/classes</outputDirectory>
-              <overWriteReleases>false</overWriteReleases>
-              <overWriteSnapshots>true</overWriteSnapshots>
-            </configuration>
-          </execution>
-          <execution>
-            <id>unpack-source</id>
-            <phase>generate-sources</phase>
-            <goals>
-              <goal>unpack-dependencies</goal>
-            </goals>
-            <configuration>
-              <classifier>sources</classifier>
-              <includes>**/*</includes>
-              <excludes>META-INF/**</excludes>
-              <includeGroupIds>org.eclipse.jetty</includeGroupIds>
-              <outputDirectory>${project.build.directory}/sources</outputDirectory>
-              <overWriteReleases>true</overWriteReleases>
-              <overWriteSnapshots>true</overWriteSnapshots>
-            </configuration>
-          </execution>
-        </executions>
-      </plugin>
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-jar-plugin</artifactId>
-        <executions>
-          <execution>
-            <id>package</id>
-            <phase>package</phase>
-            <goals>
-              <goal>jar</goal>
-            </goals>
-            <configuration>
-              <archive>
-                <manifest>
-                </manifest>
-                <manifestEntries>
-                  <mode>development</mode>
-                  <url>http://eclipse.org/jetty</url>
-                  <Built-By>${user.name}</Built-By>
-                  <package>org.eclipse.jetty</package>
-                  <Bundle-License>http://git.eclipse.org/c/jetty/org.eclipse.jetty.project.git/tree/NOTICE.txt</Bundle-License>
-                  <Bundle-Name>Jetty HTTP Client</Bundle-Name>
-                </manifestEntries>
-              </archive>
-            </configuration>
-          </execution>
-        </executions>
-      </plugin>
-    </plugins>
-  </build>
-
-  <dependencies>
-    <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-client</artifactId>
-      <version>${project.version}</version>
-      <scope>provided</scope>
-    </dependency>
-  </dependencies>
-</project>
diff --git a/jetty-aggregate/jetty-plus/pom.xml b/jetty-aggregate/jetty-plus/pom.xml
deleted file mode 100644
index 28a8cab..0000000
--- a/jetty-aggregate/jetty-plus/pom.xml
+++ /dev/null
@@ -1,91 +0,0 @@
-<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">
-  <parent>
-    <groupId>org.eclipse.jetty.aggregate</groupId>
-    <artifactId>jetty-aggregate-project</artifactId>
-    <version>8.1.17.v20150415</version>
-  </parent>
-  <modelVersion>4.0.0</modelVersion>
-  <artifactId>jetty-plus</artifactId>
-  <name>Jetty :: Aggregate :: Plus Server</name>
-  <url>http://www.eclipse.org/jetty</url>
-  <build>
-    <sourceDirectory>${project.build.directory}/sources</sourceDirectory>
-    <plugins>
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-dependency-plugin</artifactId>
-        <executions>
-          <execution>
-            <id>unpack-dependencies</id>
-            <goals>
-              <goal>unpack-dependencies</goal>
-            </goals>
-            <configuration>
-              <excludes>**/MANIFEST.MF,javax/**</excludes>
-              <excludeArtifactIds>javax</excludeArtifactIds>
-              <excludeGroupIds>javax,org.eclipse.jetty.orbit</excludeGroupIds>
-              <outputDirectory>${project.build.directory}/classes</outputDirectory>
-              <overWriteReleases>false</overWriteReleases>
-              <overWriteSnapshots>true</overWriteSnapshots>
-            </configuration>
-          </execution>
-          <execution>
-            <id>unpack-source</id>
-            <phase>generate-sources</phase>
-            <goals>
-              <goal>unpack-dependencies</goal>
-            </goals>
-            <configuration>
-              <classifier>sources</classifier>
-              <includes>**/*</includes>
-              <excludes>META-INF/**</excludes>
-              <includeGroupIds>org.eclipse.jetty</includeGroupIds>
-              <excludeArtifactIds>javax</excludeArtifactIds>
-              <excludeGroupIds>javax,org.eclipse.jetty.orbit</excludeGroupIds>
-              <outputDirectory>${project.build.directory}/sources</outputDirectory>
-              <overWriteReleases>true</overWriteReleases>
-              <overWriteSnapshots>true</overWriteSnapshots>
-            </configuration>
-          </execution>
-        </executions>
-      </plugin>
-      <plugin>
-        <groupId>org.apache.maven.plugins
-        </groupId>
-        <artifactId>maven-jar-plugin</artifactId>
-        <executions>
-          <execution>
-            <id>package</id>
-            <phase>package</phase>
-            <goals>
-              <goal>jar</goal>
-            </goals>
-            <configuration>
-              <archive>
-                <manifest>
-                </manifest>
-                <manifestEntries>
-                  <mode>development</mode>
-                  <url>http://eclipse.org/jetty</url>
-                  <Built-By>${user.name}</Built-By>
-                  <package>org.eclipse.jetty</package>
-                  <Bundle-License>http://git.eclipse.org/c/jetty/org.eclipse.jetty.project.git/tree/NOTICE.txt</Bundle-License>
-                  <Bundle-Name>Jetty HTTP Server</Bundle-Name>
-                </manifestEntries>
-              </archive>
-            </configuration>
-          </execution>
-        </executions>
-      </plugin>
-    </plugins>
-  </build>
-
-  <dependencies>
-    <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-plus</artifactId>
-      <version>${project.version}</version>
-      <scope>provided</scope>
-    </dependency>
-  </dependencies>
-</project>
diff --git a/jetty-aggregate/jetty-server/pom.xml b/jetty-aggregate/jetty-server/pom.xml
deleted file mode 100644
index c2213fc..0000000
--- a/jetty-aggregate/jetty-server/pom.xml
+++ /dev/null
@@ -1,97 +0,0 @@
-<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">
-  <parent>
-    <groupId>org.eclipse.jetty.aggregate</groupId>
-    <artifactId>jetty-aggregate-project</artifactId>
-    <version>8.1.17.v20150415</version>
-  </parent>
-  <modelVersion>4.0.0</modelVersion>
-  <artifactId>jetty-server</artifactId>
-  <name>Jetty :: Aggregate :: HTTP Server</name>
-  <url>http://www.eclipse.org/jetty</url>
-  <build>
-    <sourceDirectory>${project.build.directory}/sources</sourceDirectory>
-    <plugins>
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-dependency-plugin</artifactId>
-        <executions>
-          <execution>
-            <id>unpack-dependencies</id>
-            <goals>
-              <goal>unpack-dependencies</goal>
-            </goals>
-            <configuration>
-              <excludes>**/MANIFEST.MF,javax/**</excludes>
-              <excludeArtifactIds>javax</excludeArtifactIds>
-              <excludeGroupIds>javax,org.eclipse.jetty.orbit</excludeGroupIds>
-              <outputDirectory>${project.build.directory}/classes</outputDirectory>
-              <overWriteReleases>false</overWriteReleases>
-              <overWriteSnapshots>true</overWriteSnapshots>
-            </configuration>
-          </execution>
-          <execution>
-            <id>unpack-source</id>
-            <phase>generate-sources</phase>
-            <goals>
-              <goal>unpack-dependencies</goal>
-            </goals>
-            <configuration>
-              <classifier>sources</classifier>
-              <includes>**/*</includes>
-              <excludes>META-INF/**</excludes>
-              <includeGroupIds>org.eclipse.jetty</includeGroupIds>
-              <excludeArtifactIds>javax</excludeArtifactIds>
-              <excludeGroupIds>javax,org.eclipse.jetty.orbit</excludeGroupIds>
-              <outputDirectory>${project.build.directory}/sources</outputDirectory>
-              <overWriteReleases>true</overWriteReleases>
-              <overWriteSnapshots>true</overWriteSnapshots>
-            </configuration>
-          </execution>
-        </executions>
-      </plugin>
-      <plugin>
-        <groupId>org.apache.maven.plugins
-        </groupId>
-        <artifactId>maven-jar-plugin</artifactId>
-        <executions>
-          <execution>
-            <id>package</id>
-            <phase>package</phase>
-            <goals>
-              <goal>jar</goal>
-            </goals>
-            <configuration>
-              <archive>
-                <manifest>
-                </manifest>
-                <manifestEntries>
-                  <mode>development</mode>
-                  <url>http://eclipse.org/jetty</url>
-                  <Built-By>${user.name}</Built-By>
-                  <package>org.eclipse.jetty</package>
-                  <Bundle-License>http://git.eclipse.org/c/jetty/org.eclipse.jetty.project.git/tree/NOTICE.txt</Bundle-License>
-                  <Bundle-Name>Jetty HTTP Server</Bundle-Name>
-                </manifestEntries>
-              </archive>
-            </configuration>
-          </execution>
-        </executions>
-      </plugin>
-    </plugins>
-  </build>
-
-  <dependencies>
-    <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-server</artifactId>
-      <version>${project.version}</version>
-      <scope>provided</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-websocket</artifactId>
-      <version>${project.version}</version>
-      <scope>provided</scope>
-    </dependency>
-  </dependencies>
-</project>
diff --git a/jetty-aggregate/jetty-servlet/pom.xml b/jetty-aggregate/jetty-servlet/pom.xml
deleted file mode 100644
index 7c1ed25..0000000
--- a/jetty-aggregate/jetty-servlet/pom.xml
+++ /dev/null
@@ -1,96 +0,0 @@
-<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">
-  <parent>
-    <groupId>org.eclipse.jetty.aggregate</groupId>
-    <artifactId>jetty-aggregate-project</artifactId>
-    <version>8.1.17.v20150415</version>
-  </parent>
-  <modelVersion>4.0.0</modelVersion>
-  <artifactId>jetty-servlet</artifactId>
-  <name>Jetty :: Aggregate :: Servlet Server</name>
-  <url>http://www.eclipse.org/jetty</url>
-  <build>
-    <sourceDirectory>${project.build.directory}/sources</sourceDirectory>
-    <plugins>
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-dependency-plugin</artifactId>
-        <executions>
-          <execution>
-            <id>unpack-dependencies</id>
-            <goals>
-              <goal>unpack-dependencies</goal>
-            </goals>
-            <configuration>
-              <excludes>**/MANIFEST.MF,javax/**</excludes>
-              <excludeArtifactIds>javax</excludeArtifactIds>
-              <excludeGroupIds>javax,org.eclipse.jetty.orbit</excludeGroupIds>
-              <outputDirectory>${project.build.directory}/classes</outputDirectory>
-              <overWriteReleases>false</overWriteReleases>
-              <overWriteSnapshots>true</overWriteSnapshots>
-            </configuration>
-          </execution>
-          <execution>
-            <id>unpack-source</id>
-            <phase>generate-sources</phase>
-            <goals>
-              <goal>unpack-dependencies</goal>
-            </goals>
-            <configuration>
-              <classifier>sources</classifier>
-              <includes>**/*</includes>
-              <excludes>META-INF/**</excludes>
-              <includeGroupIds>org.eclipse.jetty</includeGroupIds>
-              <excludeArtifactIds>javax</excludeArtifactIds>
-              <excludeGroupIds>javax,org.eclipse.jetty.orbit</excludeGroupIds>
-              <outputDirectory>${project.build.directory}/sources</outputDirectory>
-              <overWriteReleases>true</overWriteReleases>
-              <overWriteSnapshots>true</overWriteSnapshots>
-            </configuration>
-          </execution>
-        </executions>
-      </plugin>
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-jar-plugin</artifactId>
-        <executions>
-          <execution>
-            <id>package</id>
-            <phase>package</phase>
-            <goals>
-              <goal>jar</goal>
-            </goals>
-            <configuration>
-              <archive>
-                <manifest>
-                </manifest>
-                <manifestEntries>
-                  <mode>development</mode>
-                  <url>http://eclipse.org/jetty</url>
-                  <Built-By>${user.name}</Built-By>
-                  <package>org.eclipse.jetty</package>
-                  <Bundle-License>http://git.eclipse.org/c/jetty/org.eclipse.jetty.project.git/tree/NOTICE.txt</Bundle-License>
-                  <Bundle-Name>Jetty HTTP Server</Bundle-Name>
-                </manifestEntries>
-              </archive>
-            </configuration>
-          </execution>
-        </executions>
-      </plugin>
-    </plugins>
-  </build>
-
-  <dependencies>
-    <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-servlet</artifactId>
-      <version>${project.version}</version>
-      <scope>provided</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-websocket</artifactId>
-      <version>${project.version}</version>
-      <scope>provided</scope>
-    </dependency>
-  </dependencies>
-</project>
diff --git a/jetty-aggregate/jetty-webapp/pom.xml b/jetty-aggregate/jetty-webapp/pom.xml
deleted file mode 100644
index d839e46..0000000
--- a/jetty-aggregate/jetty-webapp/pom.xml
+++ /dev/null
@@ -1,103 +0,0 @@
-<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">
-  <parent>
-    <groupId>org.eclipse.jetty.aggregate</groupId>
-    <artifactId>jetty-aggregate-project</artifactId>
-    <version>8.1.17.v20150415</version>
-  </parent>
-  <modelVersion>4.0.0</modelVersion>
-  <artifactId>jetty-webapp</artifactId>
-  <name>Jetty :: Aggregate :: WebApp Server</name>
-  <url>http://www.eclipse.org/jetty</url>
-  <build>
-    <sourceDirectory>${project.build.directory}/sources</sourceDirectory>
-    <plugins>
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-dependency-plugin</artifactId>
-        <executions>
-          <execution>
-            <id>unpack-dependencies</id>
-            <goals>
-              <goal>unpack-dependencies</goal>
-            </goals>
-            <configuration>
-              <includes>META-INF/**,org/eclipse/**,org/apache/jasper/compiler/**</includes>
-              <excludes>**/MANIFEST.MF,javax/**</excludes>
-              <excludeArtifactIds>javax</excludeArtifactIds>
-              <excludeGroupIds>javax,org.eclipse.jetty.orbit</excludeGroupIds>
-              <outputDirectory>${project.build.directory}/classes</outputDirectory>
-              <overWriteReleases>false</overWriteReleases>
-              <overWriteSnapshots>true</overWriteSnapshots>
-            </configuration>
-          </execution>
-          <execution>
-            <id>unpack-source</id>
-            <phase>generate-sources</phase>
-            <goals>
-              <goal>unpack-dependencies</goal>
-            </goals>
-            <configuration>
-              <classifier>sources</classifier>
-              <includes>**/*</includes>
-              <excludes>META-INF/**</excludes>
-              <includeGroupIds>org.eclipse.jetty</includeGroupIds>
-              <excludeArtifactIds>javax</excludeArtifactIds>
-              <excludeGroupIds>javax,org.eclipse.jetty.orbit</excludeGroupIds>
-              <outputDirectory>${project.build.directory}/sources</outputDirectory>
-              <overWriteReleases>true</overWriteReleases>
-              <overWriteSnapshots>true</overWriteSnapshots>
-            </configuration>
-          </execution>
-        </executions>
-      </plugin>
-      <plugin>
-        <groupId>org.apache.maven.plugins
-        </groupId>
-        <artifactId>maven-jar-plugin</artifactId>
-        <executions>
-          <execution>
-            <id>package</id>
-            <phase>package</phase>
-            <goals>
-              <goal>jar</goal>
-            </goals>
-            <configuration>
-              <archive>
-                <manifest>
-                </manifest>
-                <manifestEntries>
-                  <mode>development</mode>
-                  <url>http://eclipse.org/jetty</url>
-                  <Built-By>${user.name}</Built-By>
-                  <package>org.eclipse.jetty</package>
-                  <Bundle-License>http://git.eclipse.org/c/jetty/org.eclipse.jetty.project.git/tree/NOTICE.txt</Bundle-License>
-                  <Bundle-Name>Jetty HTTP Server</Bundle-Name>
-                </manifestEntries>
-              </archive>
-            </configuration>
-          </execution>
-        </executions>
-      </plugin>
-    </plugins>
-  </build>
-
-  <dependencies>
-    <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-webapp</artifactId>
-      <version>${project.version}</version>
-      <scope>provided</scope>
-      <exclusions>
-        <exclusion>
-          <groupId>org.eclipse.jetty.orbit</groupId>
-          <artifactId>javax.servlet</artifactId>
-        </exclusion>
-      </exclusions>
-    </dependency>
-    <dependency>
-      <groupId>org.eclipse.jetty.orbit</groupId>
-      <artifactId>javax.servlet</artifactId>
-      <scope>compile</scope>
-    </dependency>
-  </dependencies>
-</project>
diff --git a/jetty-aggregate/jetty-websocket/pom.xml b/jetty-aggregate/jetty-websocket/pom.xml
deleted file mode 100644
index 4d184ea..0000000
--- a/jetty-aggregate/jetty-websocket/pom.xml
+++ /dev/null
@@ -1,91 +0,0 @@
-<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">
-  <parent>
-    <groupId>org.eclipse.jetty.aggregate</groupId>
-    <artifactId>jetty-aggregate-project</artifactId>
-    <version>8.1.17.v20150415</version>
-  </parent>
-  <modelVersion>4.0.0</modelVersion>
-  <artifactId>jetty-websocket</artifactId>
-  <name>Jetty :: Aggregate :: Websocket</name>
-  <url>http://www.eclipse.org/jetty</url>
-  <build>
-    <sourceDirectory>${project.build.directory}/sources</sourceDirectory>
-    <plugins>
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-dependency-plugin</artifactId>
-        <executions>
-          <execution>
-            <id>unpack-dependencies</id>
-            <goals>
-              <goal>unpack-dependencies</goal>
-            </goals>
-            <configuration>
-              <excludes>**/MANIFEST.MF,javax/**,about.html</excludes>
-              <excludeArtifactIds>javax</excludeArtifactIds>
-              <excludeGroupIds>javax,org.eclipse.jetty.orbit</excludeGroupIds>
-              <outputDirectory>${project.build.directory}/classes</outputDirectory>
-              <overWriteReleases>false</overWriteReleases>
-              <overWriteSnapshots>true</overWriteSnapshots>
-            </configuration>
-          </execution>
-          <execution>
-            <id>unpack-source</id>
-            <phase>generate-sources</phase>
-            <goals>
-              <goal>unpack-dependencies</goal>
-            </goals>
-            <configuration>
-              <classifier>sources</classifier>
-              <includes>**/*</includes>
-              <excludes>META-INF/**</excludes>
-              <includeGroupIds>org.eclipse.jetty</includeGroupIds>
-              <excludeArtifactIds>javax</excludeArtifactIds>
-              <excludeGroupIds>javax,org.eclipse.jetty.orbit</excludeGroupIds>
-              <outputDirectory>${project.build.directory}/sources</outputDirectory>
-              <overWriteReleases>true</overWriteReleases>
-              <overWriteSnapshots>true</overWriteSnapshots>
-            </configuration>
-          </execution>
-        </executions>
-      </plugin>
-      <plugin>
-        <groupId>org.apache.maven.plugins
-        </groupId>
-        <artifactId>maven-jar-plugin</artifactId>
-        <executions>
-          <execution>
-            <id>package</id>
-            <phase>package</phase>
-            <goals>
-              <goal>jar</goal>
-            </goals>
-            <configuration>
-              <archive>
-                <manifest>
-                </manifest>
-                <manifestEntries>
-                  <mode>development</mode>
-                  <url>http://eclipse.org/jetty</url>
-                  <Built-By>${user.name}</Built-By>
-                  <package>org.eclipse.jetty</package>
-                  <Bundle-License>http://git.eclipse.org/c/jetty/org.eclipse.jetty.project.git/tree/NOTICE.txt</Bundle-License>
-                  <Bundle-Name>Jetty HTTP Server</Bundle-Name>
-                </manifestEntries>
-              </archive>
-            </configuration>
-          </execution>
-        </executions>
-      </plugin>
-    </plugins>
-  </build>
-
-  <dependencies>
-    <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-websocket</artifactId>
-      <version>${project.version}</version>
-      <scope>provided</scope>
-    </dependency>
-  </dependencies>
-</project>
diff --git a/jetty-aggregate/pom.xml b/jetty-aggregate/pom.xml
deleted file mode 100644
index 0f75aa9..0000000
--- a/jetty-aggregate/pom.xml
+++ /dev/null
@@ -1,44 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<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>
-  <parent>
-    <groupId>org.eclipse.jetty</groupId>
-    <artifactId>jetty-project</artifactId>
-    <version>8.1.17.v20150415</version>
-  </parent>
-  <groupId>org.eclipse.jetty.aggregate</groupId>
-  <artifactId>jetty-aggregate-project</artifactId>
-  <name>Jetty :: Aggregate Project</name>
-  <url>http://www.eclipse.org/jetty</url>
-  <packaging>pom</packaging>
-  <build>
-    <plugins>
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-pmd-plugin</artifactId>
-        <configuration>
-          <!-- No Point running PMD on aggregate projects -->
-          <skip>true</skip>
-        </configuration>
-      </plugin>
-      <plugin>
-        <groupId>org.codehaus.mojo</groupId>
-        <artifactId>findbugs-maven-plugin</artifactId>
-        <configuration>
-          <!-- No Point running Findbugs on aggregate projects -->
-          <skip>true</skip>
-        </configuration>
-      </plugin>
-    </plugins>
-  </build>
-  <modules>
-    <module>jetty-server</module>
-    <module>jetty-client</module>
-    <module>jetty-servlet</module>
-    <module>jetty-webapp</module>
-    <module>jetty-websocket</module>
-    <module>jetty-plus</module>
-    <module>jetty-all-server</module>
-    <module>jetty-all</module>
-  </modules>
-</project>
diff --git a/jetty-ajp/pom.xml b/jetty-ajp/pom.xml
deleted file mode 100644
index 0726d58..0000000
--- a/jetty-ajp/pom.xml
+++ /dev/null
@@ -1,83 +0,0 @@
-<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">
-  <parent>
-    <groupId>org.eclipse.jetty</groupId>
-    <artifactId>jetty-project</artifactId>
-    <version>8.1.17.v20150415</version>
-  </parent>
-  <modelVersion>4.0.0</modelVersion>
-  <artifactId>jetty-ajp</artifactId>
-  <name>Jetty :: AJP</name>
-  <url>http://www.eclipse.org/jetty</url>
-  <properties>
-    <bundle-symbolic-name>${project.groupId}.ajp</bundle-symbolic-name>
-  </properties>
-  <build>
-    <plugins>
-      <plugin>
-        <groupId>org.apache.felix</groupId>
-        <artifactId>maven-bundle-plugin</artifactId>
-        <extensions>true</extensions>
-        <executions>
-          <execution>
-            <goals>
-              <goal>manifest</goal>
-            </goals>
-            <configuration>
-              <instructions>
-                <Import-Package>javax.servlet.*;version="2.6.0",*</Import-Package>
-              </instructions>
-            </configuration>
-           </execution>
-        </executions>
-      </plugin>
-    <!--
-        Required for OSGI
-        -->
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-jar-plugin</artifactId>
-        <configuration>
-          <archive>
-            <manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
-          </archive>
-        </configuration>
-      </plugin>
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-assembly-plugin</artifactId>
-        <executions>
-          <execution>
-            <phase>package</phase>
-            <goals>
-              <goal>single</goal>
-            </goals>
-            <configuration>
-              <descriptorRefs>
-                <descriptorRef>config</descriptorRef>
-              </descriptorRefs>
-            </configuration>
-          </execution>
-        </executions>
-      </plugin>
-      <plugin>
-        <groupId>org.codehaus.mojo</groupId>
-        <artifactId>findbugs-maven-plugin</artifactId>
-        <configuration>
-          <onlyAnalyze>org.eclipse.jetty.ajp.*</onlyAnalyze>
-        </configuration>
-      </plugin>
-    </plugins>
-  </build>
-  <dependencies>
-    <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-server</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-    <dependency>
-      <groupId>junit</groupId>
-      <artifactId>junit</artifactId>
-      <scope>test</scope>
-    </dependency>
-  </dependencies>
-</project>
diff --git a/jetty-ajp/src/main/config/etc/jetty-ajp.xml b/jetty-ajp/src/main/config/etc/jetty-ajp.xml
deleted file mode 100644
index 04a775e..0000000
--- a/jetty-ajp/src/main/config/etc/jetty-ajp.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0"?>
-<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
-
-<Configure id="Server" class="org.eclipse.jetty.server.Server">
-
-  <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
-  <!-- Add a AJP listener on port 8009                           -->
-  <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
-  <Call name="addConnector">
-    <Arg>
-       <New class="org.eclipse.jetty.ajp.Ajp13SocketConnector">
-         <Set name="port">8009</Set>
-       </New>
-    </Arg>
-  </Call>
-
-</Configure>
-
diff --git a/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13Connection.java b/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13Connection.java
deleted file mode 100644
index 39a1a04..0000000
--- a/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13Connection.java
+++ /dev/null
@@ -1,250 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.ajp;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.security.cert.CertificateFactory;
-import java.security.cert.X509Certificate;
-import java.util.Collection;
-
-import javax.servlet.ServletInputStream;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jetty.http.HttpException;
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.server.BlockingHttpConnection;
-import org.eclipse.jetty.server.Connector;
-import org.eclipse.jetty.server.Request;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-
-/**
- * Connection implementation of the Ajp13 protocol. <p/> XXX Refactor to remove
- * duplication of HttpConnection
- * 
- */
-public class Ajp13Connection extends BlockingHttpConnection
-{
-    private static final Logger LOG = Log.getLogger(Ajp13Connection.class);
-
-    public Ajp13Connection(Connector connector, EndPoint endPoint, Server server)
-    {
-        super(connector, endPoint, server,
-                new Ajp13Parser(connector.getRequestBuffers(), endPoint),
-                new Ajp13Generator(connector.getResponseBuffers(), endPoint),
-                new Ajp13Request()
-                );
-        
-        ((Ajp13Parser)_parser).setEventHandler(new RequestHandler());
-        ((Ajp13Parser)_parser).setGenerator((Ajp13Generator)_generator);
-        ((Ajp13Request)_request).setConnection(this);
-    }
-
-    @Override
-    public boolean isConfidential(Request request)
-    {
-        return ((Ajp13Request) request).isSslSecure();
-    }
-
-    @Override
-    public boolean isIntegral(Request request)
-    {
-        return ((Ajp13Request) request).isSslSecure();
-    }
-
-    @Override
-    public ServletInputStream getInputStream()
-    {
-        if (_in == null)
-            _in = new Ajp13Parser.Input((Ajp13Parser) _parser, _connector.getMaxIdleTime());
-        return _in;
-    }
-
-    private class RequestHandler implements Ajp13Parser.EventHandler
-    {
-        public void startForwardRequest() throws IOException
-        {
-            _uri.clear();
-	    
-            ((Ajp13Request) _request).setSslSecure(false);
-            _request.setTimeStamp(System.currentTimeMillis());
-            _request.setUri(_uri);
-            
-        }
-
-        public void parsedAuthorizationType(Buffer authType) throws IOException
-        {
-            //TODO JASPI this doesn't appear to make sense yet... how does ajp auth fit into jetty auth?
-//            _request.setAuthType(authType.toString());
-        }
-
-        public void parsedRemoteUser(Buffer remoteUser) throws IOException
-        {
-            ((Ajp13Request)_request).setRemoteUser(remoteUser.toString());
-        }
-
-        public void parsedServletPath(Buffer servletPath) throws IOException
-        {
-            _request.setServletPath(servletPath.toString());
-        }
-
-        public void parsedContextPath(Buffer context) throws IOException
-        {
-            _request.setContextPath(context.toString());
-        }
-
-        public void parsedSslCert(Buffer sslCert) throws IOException
-        {
-            try 
-            {
-                CertificateFactory cf = CertificateFactory.getInstance("X.509");
-                ByteArrayInputStream bis = new ByteArrayInputStream(sslCert.toString().getBytes());
-
-                Collection<? extends java.security.cert.Certificate> certCollection = cf.generateCertificates(bis);
-                X509Certificate[] certificates = new X509Certificate[certCollection.size()];
-
-                int i=0;
-                for (Object aCertCollection : certCollection)
-                {
-                    certificates[i++] = (X509Certificate) aCertCollection;
-                }
-
-                _request.setAttribute("javax.servlet.request.X509Certificate", certificates);
-            } 
-            catch (Exception e) 
-            {
-                LOG.warn(e.toString());
-                LOG.ignore(e);
-                if (sslCert!=null)
-                    _request.setAttribute("javax.servlet.request.X509Certificate", sslCert.toString());
-            }
-        }
-
-        public void parsedSslCipher(Buffer sslCipher) throws IOException
-        {
-            _request.setAttribute("javax.servlet.request.cipher_suite", sslCipher.toString());
-        }
-
-        public void parsedSslSession(Buffer sslSession) throws IOException
-        {
-            _request.setAttribute("javax.servlet.request.ssl_session", sslSession.toString());
-        }
-
-        public void parsedSslKeySize(int keySize) throws IOException
-        {
-           _request.setAttribute("javax.servlet.request.key_size", new Integer(keySize));
-        }
-
-        public void parsedMethod(Buffer method) throws IOException
-        {
-            if (method == null)
-                throw new HttpException(HttpServletResponse.SC_BAD_REQUEST);
-            _request.setMethod(method.toString());
-        }
-
-        public void parsedUri(Buffer uri) throws IOException
-        {
-            _uri.parse(uri.toString());
-        }
-
-        public void parsedProtocol(Buffer protocol) throws IOException
-        {
-            if (protocol != null && protocol.length()>0)
-            {
-                _request.setProtocol(protocol.toString());
-            }
-        }
-
-        public void parsedRemoteAddr(Buffer addr) throws IOException
-        {
-            if (addr != null && addr.length()>0)
-            {
-                _request.setRemoteAddr(addr.toString());
-            }
-        }
-
-        public void parsedRemoteHost(Buffer name) throws IOException
-        {
-            if (name != null && name.length()>0)
-            {
-                _request.setRemoteHost(name.toString());
-            }
-        }
-
-        public void parsedServerName(Buffer name) throws IOException
-        {
-            if (name != null && name.length()>0)
-            {
-                _request.setServerName(name.toString());
-            }
-        }
-
-        public void parsedServerPort(int port) throws IOException
-        {
-            _request.setServerPort(port);
-        }
-
-        public void parsedSslSecure(boolean secure) throws IOException
-        {
-            ((Ajp13Request) _request).setSslSecure(secure);
-        }
-
-        public void parsedQueryString(Buffer value) throws IOException
-        {
-            String u = _uri + "?" + value;
-            _uri.parse(u);
-        }
-
-        public void parsedHeader(Buffer name, Buffer value) throws IOException
-        {
-            _requestFields.add(name, value);
-        }
-
-        public void parsedRequestAttribute(String key, Buffer value) throws IOException
-        {
-            if (value==null)
-                _request.removeAttribute(key);
-            else
-                _request.setAttribute(key,value.toString());
-        }
-        
-        public void parsedRequestAttribute(String key, int value) throws IOException
-        {
-            _request.setAttribute(key, Integer.toString(value));
-        }
-
-        public void headerComplete() throws IOException
-        {
-            handleRequest();
-        }
-
-        public void messageComplete(long contextLength) throws IOException
-        {
-        }
-
-        public void content(Buffer ref) throws IOException
-        {
-        }
-
-    }
-
-}
diff --git a/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13Generator.java b/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13Generator.java
deleted file mode 100644
index 439c3dd..0000000
--- a/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13Generator.java
+++ /dev/null
@@ -1,782 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.ajp;
-
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.util.HashMap;
-
-import org.eclipse.jetty.http.AbstractGenerator;
-import org.eclipse.jetty.http.HttpFields;
-import org.eclipse.jetty.http.HttpGenerator;
-import org.eclipse.jetty.http.HttpTokens;
-import org.eclipse.jetty.http.HttpVersions;
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.Buffers;
-import org.eclipse.jetty.io.ByteArrayBuffer;
-import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.io.EofException;
-import org.eclipse.jetty.util.StringUtil;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-
-/**
- *
- *
- */
-public class Ajp13Generator extends AbstractGenerator
-{
-    private static final Logger LOG = Log.getLogger(Ajp13Generator.class);
-
-    private static HashMap __headerHash = new HashMap();
-
-    static
-    {
-        byte[] xA001 =
-        { (byte)0xA0, (byte)0x01 };
-        byte[] xA002 =
-        { (byte)0xA0, (byte)0x02 };
-        byte[] xA003 =
-        { (byte)0xA0, (byte)0x03 };
-        byte[] xA004 =
-        { (byte)0xA0, (byte)0x04 };
-        byte[] xA005 =
-        { (byte)0xA0, (byte)0x05 };
-        byte[] xA006 =
-        { (byte)0xA0, (byte)0x06 };
-        byte[] xA007 =
-        { (byte)0xA0, (byte)0x07 };
-        byte[] xA008 =
-        { (byte)0xA0, (byte)0x08 };
-        byte[] xA009 =
-        { (byte)0xA0, (byte)0x09 };
-        byte[] xA00A =
-        { (byte)0xA0, (byte)0x0A };
-        byte[] xA00B =
-        { (byte)0xA0, (byte)0x0B };
-        __headerHash.put("Content-Type",xA001);
-        __headerHash.put("Content-Language",xA002);
-        __headerHash.put("Content-Length",xA003);
-        __headerHash.put("Date",xA004);
-        __headerHash.put("Last-Modified",xA005);
-        __headerHash.put("Location",xA006);
-        __headerHash.put("Set-Cookie",xA007);
-        __headerHash.put("Set-Cookie2",xA008);
-        __headerHash.put("Servlet-Engine",xA009);
-        __headerHash.put("Status",xA00A);
-        __headerHash.put("WWW-Authenticate",xA00B);
-
-    }
-
-    // A, B ajp response header
-    // 0, 1 ajp int 1 packet length
-    // 9 CPONG response Code
-    private static final byte[] AJP13_CPONG_RESPONSE =
-    { 'A', 'B', 0, 1, 9 };
-
-    private static final byte[] AJP13_END_RESPONSE =
-    { 'A', 'B', 0, 2, 5, 1 };
-
-    // AB ajp respose
-    // 0, 3 int = 3 packets in length
-    // 6, send signal to get more data
-    // 31, -7 byte values for int 8185 = (8 * 1024) - 7 MAX_DATA
-    private static final byte[] AJP13_MORE_CONTENT =
-    { 'A', 'B', 0, 3, 6, 31, -7 };
-
-    private static String SERVER = "Server: Jetty(7.x.x)";
-
-    public static void setServerVersion(String version)
-    {
-        SERVER = "Jetty(" + version + ")";
-    }
-
-    /* ------------------------------------------------------------ */
-    private boolean _expectMore = false;
-
-    private boolean _needMore = false;
-
-    private boolean _needEOC = false;
-
-    private boolean _bufferPrepared = false;
-
-    /* ------------------------------------------------------------ */
-    public Ajp13Generator(Buffers buffers, EndPoint io)
-    {
-        super(buffers,io);
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public boolean isRequest()
-    {
-        return false;
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public boolean isResponse()
-    {
-        return true;
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public void reset()
-    {
-        super.reset();
-
-        _needEOC = false;
-        _needMore = false;
-        _expectMore = false;
-        _bufferPrepared = false;
-        _last = false;
-
-        _state = STATE_HEADER;
-
-        _status = 0;
-        _version = HttpVersions.HTTP_1_1_ORDINAL;
-        _reason = null;
-        _method = null;
-        _uri = null;
-
-        _contentWritten = 0;
-        _contentLength = HttpTokens.UNKNOWN_CONTENT;
-        _last = false;
-        _head = false;
-        _noContent = false;
-        _persistent = true;
-
-        _header = null; // Buffer for HTTP header (and maybe small _content)
-        _buffer = null; // Buffer for copy of passed _content
-        _content = null; // Buffer passed to addContent
-
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public int getContentBufferSize()
-    {
-        try
-        {
-            initContent();
-        }
-        catch (IOException e)
-        {
-            throw new RuntimeException(e);
-        }
-        return super.getContentBufferSize() - 7;
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public void increaseContentBufferSize(int contentBufferSize)
-    {
-        // Not supported with AJP
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Add content.
-     *
-     * @param content
-     * @param last
-     * @throws IllegalArgumentException
-     *             if <code>content</code> is {@link Buffer#isImmutable immutable}.
-     * @throws IllegalStateException
-     *             If the request is not expecting any more content, or if the buffers are full and cannot be flushed.
-     * @throws IOException
-     *             if there is a problem flushing the buffers.
-     */
-    public void addContent(Buffer content, boolean last) throws IOException
-    {
-        if (_noContent)
-        {
-            content.clear();
-            return;
-        }
-
-        if (content.isImmutable())
-            throw new IllegalArgumentException("immutable");
-
-        if (_last || _state == STATE_END)
-        {
-            LOG.debug("Ignoring extra content {}",content);
-            content.clear();
-            return;
-        }
-        _last = last;
-
-        if (!_endp.isOpen())
-        {
-            _state = STATE_END;
-            return;
-        }
-
-        // Handle any unfinished business?
-        if (_content != null && _content.length() > 0)
-        {
-
-            flushBuffer();
-            if (_content != null && _content.length() > 0)
-                throw new IllegalStateException("FULL");
-        }
-
-        _content = content;
-
-        _contentWritten += content.length();
-
-        // Handle the _content
-        if (_head)
-        {
-            content.clear();
-            _content = null;
-        }
-        else
-        {
-            // Yes - so we better check we have a buffer
-            initContent();
-            // Copy _content to buffer;
-            int len = 0;
-            len = _buffer.put(_content);
-
-            // make sure there is space for a trailing null
-            if (len > 0 && _buffer.space() == 0)
-            {
-                len--;
-                _buffer.setPutIndex(_buffer.putIndex() - 1);
-            }
-
-            _content.skip(len);
-
-            if (_content.length() == 0)
-                _content = null;
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Prepare buffer for unchecked writes. Prepare the generator buffer to receive unchecked writes
-     *
-     * @return the available space in the buffer.
-     * @throws IOException
-     */
-    @Override
-    public int prepareUncheckedAddContent() throws IOException
-    {
-        if (_noContent)
-            return -1;
-
-        if (_last || _state == STATE_END)
-            throw new IllegalStateException("Closed");
-
-        if (!_endp.isOpen())
-        {
-            _state = STATE_END;
-            return -1;
-        }
-
-        // Handle any unfinished business?
-        Buffer content = _content;
-        if (content != null && content.length() > 0)
-        {
-            flushBuffer();
-            if (content != null && content.length() > 0)
-                throw new IllegalStateException("FULL");
-        }
-
-        // we better check we have a buffer
-        initContent();
-
-        _contentWritten -= _buffer.length();
-
-        // Handle the _content
-        if (_head)
-            return Integer.MAX_VALUE;
-
-        return _buffer.space() - 1;
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public void completeHeader(HttpFields fields, boolean allContentAdded) throws IOException
-    {
-        if (_state != STATE_HEADER)
-            return;
-
-        if (_last && !allContentAdded)
-            throw new IllegalStateException("last?");
-        _last = _last | allContentAdded;
-
-        boolean has_server = false;
-        if (_persistent == null)
-            _persistent = (_version > HttpVersions.HTTP_1_0_ORDINAL);
-
-        // get a header buffer
-        if (_header == null)
-            _header = _buffers.getHeader();
-
-        Buffer tmpbuf = _buffer;
-        _buffer = _header;
-
-        try
-        {
-            // start the header
-            _buffer.put((byte)'A');
-            _buffer.put((byte)'B');
-            addInt(0);
-            _buffer.put((byte)0x4);
-            addInt(_status);
-            if (_reason == null)
-                _reason = HttpGenerator.getReasonBuffer(_status);
-            if (_reason == null)
-                _reason = new ByteArrayBuffer(Integer.toString(_status));
-            addBuffer(_reason);
-
-            if (_status == 100 || _status == 204 || _status == 304)
-            {
-                _noContent = true;
-                _content = null;
-            }
-
-            // allocate 2 bytes for number of headers
-            int field_index = _buffer.putIndex();
-            addInt(0);
-
-            int num_fields = 0;
-
-            if (fields != null)
-            {
-                // Add headers
-                int s = fields.size();
-                for (int f = 0; f < s; f++)
-                {
-                    HttpFields.Field field = fields.getField(f);
-                    if (field == null)
-                        continue;
-                    num_fields++;
-
-                    byte[] codes = (byte[])__headerHash.get(field.getName());
-                    if (codes != null)
-                    {
-                        _buffer.put(codes);
-                    }
-                    else
-                    {
-                        addString(field.getName());
-                    }
-                    addString(field.getValue());
-                }
-            }
-
-            if (!has_server && _status > 100 && getSendServerVersion())
-            {
-                num_fields++;
-                addString("Server");
-                addString(SERVER);
-            }
-
-            // TODO Add content length if last content known.
-
-            // insert the number of headers
-            int tmp = _buffer.putIndex();
-            _buffer.setPutIndex(field_index);
-            addInt(num_fields);
-            _buffer.setPutIndex(tmp);
-
-            // get the payload size ( - 4 bytes for the ajp header)
-            // excluding the
-            // ajp header
-            int payloadSize = _buffer.length() - 4;
-            // insert the total packet size on 2nd and 3rd byte that
-            // was previously
-            // allocated
-            addInt(2,payloadSize);
-        }
-        finally
-        {
-            _buffer = tmpbuf;
-        }
-
-        _state = STATE_CONTENT;
-
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Complete the message.
-     *
-     * @throws IOException
-     */
-    @Override
-    public void complete() throws IOException
-    {
-        if (_state == STATE_END)
-            return;
-
-        super.complete();
-
-        if (_state < STATE_FLUSHING)
-        {
-            _state = STATE_FLUSHING;
-            _needEOC = true;
-        }
-
-        flushBuffer();
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public int flushBuffer() throws IOException
-    {
-        try
-        {
-            if (_state == STATE_HEADER && !_expectMore)
-                throw new IllegalStateException("State==HEADER");
-            prepareBuffers();
-
-            if (_endp == null)
-            {
-                // TODO - probably still needed!
-                // if (_rneedMore && _buffe != null)
-                // {
-                // if(!_hasSentEOC)
-                // _buffer.put(AJP13_MORE_CONTENT);
-                // }
-                if (!_expectMore && _needEOC && _buffer != null)
-                {
-                    _buffer.put(AJP13_END_RESPONSE);
-                }
-                _needEOC = false;
-                return 0;
-            }
-
-            // Keep flushing while there is something to flush
-            // (except break below)
-            int total = 0;
-            long last_len = -1;
-            Flushing: while (true)
-            {
-                int len = -1;
-                int to_flush = ((_header != null && _header.length() > 0)?4:0) | ((_buffer != null && _buffer.length() > 0)?2:0);
-
-                switch (to_flush)
-                {
-                    case 7:
-                        throw new IllegalStateException(); // should
-                        // never
-                        // happen!
-                    case 6:
-                        len = _endp.flush(_header,_buffer,null);
-
-                        break;
-                    case 5:
-                        throw new IllegalStateException(); // should
-                        // never
-                        // happen!
-                    case 4:
-                        len = _endp.flush(_header);
-                        break;
-                    case 3:
-                        throw new IllegalStateException(); // should
-                        // never
-                        // happen!
-                    case 2:
-                        len = _endp.flush(_buffer);
-
-                        break;
-                    case 1:
-                        throw new IllegalStateException(); // should
-                        // never
-                        // happen!
-                    case 0:
-                    {
-                        // Nothing more we can write now.
-                        if (_header != null)
-                            _header.clear();
-
-                        _bufferPrepared = false;
-
-                        if (_buffer != null)
-                        {
-                            _buffer.clear();
-
-                            // reserve some space for the
-                            // header
-                            _buffer.setPutIndex(7);
-                            _buffer.setGetIndex(7);
-
-                            // Special case handling for
-                            // small left over buffer from
-                            // an addContent that caused a
-                            // buffer flush.
-                            if (_content != null && _content.length() < _buffer.space() && _state != STATE_FLUSHING)
-                            {
-
-                                _buffer.put(_content);
-                                _content.clear();
-                                _content = null;
-                                break Flushing;
-                            }
-
-                        }
-
-                        // Are we completely finished for now?
-                        if (!_expectMore && !_needEOC && (_content == null || _content.length() == 0))
-                        {
-                            if (_state == STATE_FLUSHING)
-                                _state = STATE_END;
-
-                            // if (_state == STATE_END)
-                            // {
-                            // _endp.close();
-                            // }
-                            //
-
-                            break Flushing;
-                        }
-
-                        // Try to prepare more to write.
-                        prepareBuffers();
-                    }
-                }
-
-                // If we failed to flush anything twice in a row
-                // break
-                if (len <= 0)
-                {
-                    if (last_len <= 0)
-                        break Flushing;
-                    break;
-                }
-                last_len = len;
-                total += len;
-            }
-
-            return total;
-        }
-        catch (IOException e)
-        {
-            LOG.ignore(e);
-            throw (e instanceof EofException)?e:new EofException(e);
-        }
-
-    }
-
-    /* ------------------------------------------------------------ */
-    private void prepareBuffers()
-    {
-        if (!_bufferPrepared)
-        {
-
-            // Refill buffer if possible
-            if (_content != null && _content.length() > 0 && _buffer != null && _buffer.space() > 0)
-            {
-
-                int len = _buffer.put(_content);
-
-                // Make sure there is space for a trailing null
-                if (len > 0 && _buffer.space() == 0)
-                {
-                    len--;
-                    _buffer.setPutIndex(_buffer.putIndex() - 1);
-                }
-                _content.skip(len);
-
-                if (_content.length() == 0)
-                    _content = null;
-
-                if (_buffer.length() == 0)
-                {
-                    _content = null;
-                }
-            }
-
-            // add header if needed
-            if (_buffer != null)
-            {
-
-                int payloadSize = _buffer.length();
-
-                // 4 bytes for the ajp header
-                // 1 byte for response type
-                // 2 bytes for the response size
-                // 1 byte because we count from zero??
-
-                if (payloadSize > 0)
-                {
-                    _bufferPrepared = true;
-
-                    _buffer.put((byte)0);
-                    int put = _buffer.putIndex();
-                    _buffer.setGetIndex(0);
-                    _buffer.setPutIndex(0);
-                    _buffer.put((byte)'A');
-                    _buffer.put((byte)'B');
-                    addInt(payloadSize + 4);
-                    _buffer.put((byte)3);
-                    addInt(payloadSize);
-                    _buffer.setPutIndex(put);
-                }
-            }
-
-            if (_needMore)
-            {
-
-                if (_header == null)
-                {
-                    _header = _buffers.getHeader();
-                }
-
-                if (_buffer == null && _header != null && _header.space() >= AJP13_MORE_CONTENT.length)
-                {
-                    _header.put(AJP13_MORE_CONTENT);
-                    _needMore = false;
-                }
-                else if (_buffer != null && _buffer.space() >= AJP13_MORE_CONTENT.length)
-                {
-                    // send closing packet if all contents
-                    // are added
-                    _buffer.put(AJP13_MORE_CONTENT);
-                    _needMore = false;
-                    _bufferPrepared = true;
-                }
-
-            }
-
-            if (!_expectMore && _needEOC)
-            {
-                if (_buffer == null && _header.space() >= AJP13_END_RESPONSE.length)
-                {
-
-                    _header.put(AJP13_END_RESPONSE);
-                    _needEOC = false;
-                }
-                else if (_buffer != null && _buffer.space() >= AJP13_END_RESPONSE.length)
-                {
-                    // send closing packet if all contents
-                    // are added
-
-                    _buffer.put(AJP13_END_RESPONSE);
-                    _needEOC = false;
-                    _bufferPrepared = true;
-                }
-            }
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public boolean isComplete()
-    {
-        return !_expectMore && _state == STATE_END;
-    }
-
-    /* ------------------------------------------------------------ */
-    private void initContent() throws IOException
-    {
-        if (_buffer == null)
-        {
-            _buffer = _buffers.getBuffer();
-            _buffer.setPutIndex(7);
-            _buffer.setGetIndex(7);
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    private void addInt(int i)
-    {
-        _buffer.put((byte)((i >> 8) & 0xFF));
-        _buffer.put((byte)(i & 0xFF));
-    }
-
-    /* ------------------------------------------------------------ */
-    private void addInt(int startIndex, int i)
-    {
-        _buffer.poke(startIndex,(byte)((i >> 8) & 0xFF));
-        _buffer.poke((startIndex + 1),(byte)(i & 0xFF));
-    }
-
-    /* ------------------------------------------------------------ */
-    private void addString(String str) throws UnsupportedEncodingException
-    {
-        if (str == null)
-        {
-            addInt(0xFFFF);
-            return;
-        }
-
-        // TODO - need to use a writer to convert, to avoid this hacky
-        // conversion and temp buffer
-        byte[] b = str.getBytes(StringUtil.__ISO_8859_1);
-
-        addInt(b.length);
-
-        _buffer.put(b);
-        _buffer.put((byte)0);
-    }
-
-    /* ------------------------------------------------------------ */
-    private void addBuffer(Buffer b)
-    {
-        if (b == null)
-        {
-            addInt(0xFFFF);
-            return;
-        }
-
-        addInt(b.length());
-        _buffer.put(b);
-        _buffer.put((byte)0);
-    }
-
-    /* ------------------------------------------------------------ */
-    public void getBodyChunk() throws IOException
-    {
-        ByteArrayBuffer bf = new ByteArrayBuffer(AJP13_MORE_CONTENT);
-        _endp.flush(bf);
-    }
-
-    /* ------------------------------------------------------------ */
-    public void gotBody()
-    {
-        _needMore = false;
-        _expectMore = false;
-    }
-
-    /* ------------------------------------------------------------ */
-    public void sendCPong() throws IOException
-    {
-
-        Buffer buff = _buffers.getBuffer();
-        buff.put(AJP13_CPONG_RESPONSE);
-
-        // flushing cpong response
-        do
-        {
-            _endp.flush(buff);
-        }
-        while (buff.length() > 0);
-        _buffers.returnBuffer(buff);
-
-        reset();
-
-    }
-
-}
diff --git a/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13Packet.java b/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13Packet.java
deleted file mode 100644
index ab1fa05..0000000
--- a/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13Packet.java
+++ /dev/null
@@ -1,68 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.ajp;
-
-import org.eclipse.jetty.io.BufferCache;
-
-/**
- * 
- */
-public class Ajp13Packet
-{
-
-    public final static int MAX_PACKET_SIZE=(8*1024);
-    public final static int HDR_SIZE=4;
-
-    // Used in writing response...
-    public final static int DATA_HDR_SIZE=7;
-    public final static int MAX_DATA_SIZE=MAX_PACKET_SIZE-DATA_HDR_SIZE;
-
-    public final static String
-    // Server -> Container
-            FORWARD_REQUEST="FORWARD REQUEST",
-            SHUTDOWN="SHUTDOWN",
-            PING_REQUEST="PING REQUEST", // Obsolete
-            CPING_REQUEST="CPING REQUEST",
-
-            // Server <- Container
-            SEND_BODY_CHUNK="SEND BODY CHUNK", SEND_HEADERS="SEND HEADERS", END_RESPONSE="END RESPONSE",
-            GET_BODY_CHUNK="GET BODY CHUNK",
-            CPONG_REPLY="CPONG REPLY";
-
-    public final static int FORWARD_REQUEST_ORDINAL=2, SHUTDOWN_ORDINAL=7,
-            PING_REQUEST_ORDINAL=8, // Obsolete
-            CPING_REQUEST_ORDINAL=10, SEND_BODY_CHUNK_ORDINAL=3, SEND_HEADERS_ORDINAL=4, END_RESPONSE_ORDINAL=5, GET_BODY_CHUNK_ORDINAL=6,
-            CPONG_REPLY_ORDINAL=9;
-
-    public final static BufferCache CACHE=new BufferCache();
-
-    static
-    {
-        CACHE.add(FORWARD_REQUEST,FORWARD_REQUEST_ORDINAL);
-        CACHE.add(SHUTDOWN,SHUTDOWN_ORDINAL);
-        CACHE.add(PING_REQUEST,PING_REQUEST_ORDINAL); // Obsolete
-        CACHE.add(CPING_REQUEST,CPING_REQUEST_ORDINAL);
-        CACHE.add(SEND_BODY_CHUNK,SEND_BODY_CHUNK_ORDINAL);
-        CACHE.add(SEND_HEADERS,SEND_HEADERS_ORDINAL);
-        CACHE.add(END_RESPONSE,END_RESPONSE_ORDINAL);
-        CACHE.add(GET_BODY_CHUNK,GET_BODY_CHUNK_ORDINAL);
-        CACHE.add(CPONG_REPLY,CPONG_REPLY_ORDINAL);
-    }
-
-}
diff --git a/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13PacketMethods.java b/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13PacketMethods.java
deleted file mode 100644
index 53b687a..0000000
--- a/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13PacketMethods.java
+++ /dev/null
@@ -1,74 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.ajp;
-
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.BufferCache;
-
-/**
- * 
- */
-public class Ajp13PacketMethods
-{
-
-    // TODO - this can probably be replaced by HttpMethods or at least an
-    // extension of it.
-    // It is probably most efficient if "GET" ends up as the same instance
-
-    public final static String OPTIONS="OPTIONS", GET="GET", HEAD="HEAD", POST="POST", PUT="PUT", DELETE="DELETE", TRACE="TRACE", PROPFIND="PROPFIND",
-            PROPPATCH="PROPPATCH", MKCOL="MKCOL", COPY="COPY", MOVE="MOVE", LOCK="LOCK", UNLOCK="UNLOCK", ACL="ACL", REPORT="REPORT",
-            VERSION_CONTROL="VERSION-CONTROL", CHECKIN="CHECKIN", CHECKOUT="CHECKOUT", UNCHCKOUT="UNCHECKOUT", SEARCH="SEARCH", MKWORKSPACE="MKWORKSPACE",
-            UPDATE="UPDATE", LABEL="LABEL", MERGE="MERGE", BASELINE_CONTROL="BASELINE-CONTROL", MKACTIVITY="MKACTIVITY";
-
-    public final static int OPTIONS_ORDINAL=1, GET_ORDINAL=2, HEAD_ORDINAL=3, POST__ORDINAL=4, PUT_ORDINAL=5, DELETE_ORDINAL=6, TRACE_ORDINAL=7,
-            PROPFIND_ORDINAL=8, PROPPATCH_ORDINAL=9, MKCOL_ORDINAL=10, COPY_ORDINAL=11, MOVE_ORDINAL=12, LOCK_ORDINAL=13, UNLOCK_ORDINAL=14, ACL_ORDINAL=15,
-            REPORT_ORDINAL=16, VERSION_CONTROL_ORDINAL=17, CHECKIN_ORDINAL=18, CHECKOUT_ORDINAL=19, UNCHCKOUT_ORDINAL=20, SEARCH_ORDINAL=21,
-            MKWORKSPACE_ORDINAL=22, UPDATE_ORDINAL=23, LABEL_ORDINAL=24, MERGE_ORDINAL=25, BASELINE_CONTROL_ORDINAL=26, MKACTIVITY_ORDINAL=27;
-
-    public final static BufferCache CACHE=new BufferCache();
-
-    public final static Buffer 
-        OPTIONS_BUFFER=CACHE.add(OPTIONS,OPTIONS_ORDINAL), 
-        GET_BUFFER=CACHE.add(GET,GET_ORDINAL), 
-        HEAD_BUFFER=CACHE.add(HEAD, HEAD_ORDINAL), 
-        POST__BUFFER=CACHE.add(POST,POST__ORDINAL), 
-        PUT_BUFFER=CACHE.add(PUT,PUT_ORDINAL), 
-        DELETE_BUFFER=CACHE.add(DELETE,DELETE_ORDINAL),
-        TRACE_BUFFER=CACHE.add(TRACE,TRACE_ORDINAL), 
-        PROPFIND_BUFFER=CACHE.add(PROPFIND,PROPFIND_ORDINAL), 
-        PROPPATCH_BUFFER=CACHE.add(PROPPATCH, PROPPATCH_ORDINAL), 
-        MKCOL_BUFFER=CACHE.add(MKCOL,MKCOL_ORDINAL), 
-        COPY_BUFFER=CACHE.add(COPY,COPY_ORDINAL), 
-        MOVE_BUFFER=CACHE.add(MOVE,MOVE_ORDINAL), 
-        LOCK_BUFFER=CACHE.add(LOCK,LOCK_ORDINAL), 
-        UNLOCK_BUFFER=CACHE.add(UNLOCK,UNLOCK_ORDINAL), 
-        ACL_BUFFER=CACHE.add(ACL,ACL_ORDINAL), 
-        REPORT_BUFFER=CACHE.add(REPORT,REPORT_ORDINAL), 
-        VERSION_CONTROL_BUFFER=CACHE.add(VERSION_CONTROL,VERSION_CONTROL_ORDINAL),
-        CHECKIN_BUFFER=CACHE.add(CHECKIN,CHECKIN_ORDINAL), 
-        CHECKOUT_BUFFER=CACHE.add(CHECKOUT,CHECKOUT_ORDINAL), 
-        UNCHCKOUT_BUFFER=CACHE.add(UNCHCKOUT,UNCHCKOUT_ORDINAL), 
-        SEARCH_BUFFER=CACHE.add(SEARCH,SEARCH_ORDINAL), 
-        MKWORKSPACE_BUFFER=CACHE.add(MKWORKSPACE,MKWORKSPACE_ORDINAL),
-        UPDATE_BUFFER=CACHE.add(UPDATE,UPDATE_ORDINAL), 
-        LABEL_BUFFER=CACHE.add(LABEL,LABEL_ORDINAL), 
-        MERGE_BUFFER=CACHE.add(MERGE,MERGE_ORDINAL),
-        BASELINE_CONTROL_BUFFER=CACHE.add(BASELINE_CONTROL,BASELINE_CONTROL_ORDINAL), 
-        MKACTIVITY_BUFFER=CACHE.add(MKACTIVITY,MKACTIVITY_ORDINAL);
-}
diff --git a/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13Parser.java b/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13Parser.java
deleted file mode 100644
index 76a6e4b..0000000
--- a/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13Parser.java
+++ /dev/null
@@ -1,890 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.ajp;
-
-import java.io.IOException;
-import java.io.InterruptedIOException;
-
-import javax.servlet.ServletInputStream;
-
-import org.eclipse.jetty.http.HttpTokens;
-import org.eclipse.jetty.http.Parser;
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.BufferUtil;
-import org.eclipse.jetty.io.Buffers;
-import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.io.EofException;
-import org.eclipse.jetty.io.View;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-
-/**
- * 
- */
-public class Ajp13Parser implements Parser
-{
-    private static final Logger LOG = Log.getLogger(Ajp13Parser.class);
-
-    private final static int STATE_START = -1;
-    private final static int STATE_END = 0;
-    private final static int STATE_AJP13CHUNK_START = 1;
-    private final static int STATE_AJP13CHUNK = 2;
-
-    private int _state = STATE_START;
-    private long _contentLength;
-    private long _contentPosition;
-    private int _chunkLength;
-    private int _chunkPosition;
-    private int _headers;
-    private Buffers _buffers;
-    private EndPoint _endp;
-    private Buffer _buffer;
-    private Buffer _header; // Buffer for header data (and small _content)
-    private Buffer _body; // Buffer for large content
-    private View _contentView = new View();
-    private EventHandler _handler;
-    private Ajp13Generator _generator;
-    private View _tok0; // Saved token: header name, request method or response version
-    private View _tok1; // Saved token: header value, request URI orresponse code
-    protected int _length;
-    protected int _packetLength;
-    
-
-    /* ------------------------------------------------------------------------------- */
-    public Ajp13Parser(Buffers buffers, EndPoint endPoint)
-    {
-        _buffers = buffers;
-        _endp = endPoint;
-    }
-    
-    /* ------------------------------------------------------------------------------- */
-    public void setEventHandler(EventHandler handler)
-    {
-        _handler=handler;
-    }
-    
-    /* ------------------------------------------------------------------------------- */
-    public void setGenerator(Ajp13Generator generator)
-    {
-        _generator=generator;
-    }
-
-    /* ------------------------------------------------------------------------------- */
-    public long getContentLength()
-    {
-        return _contentLength;
-    }
-
-    /* ------------------------------------------------------------------------------- */
-    public int getState()
-    {
-        return _state;
-    }
-
-    /* ------------------------------------------------------------------------------- */
-    public boolean inContentState()
-    {
-        return _state > 0;
-    }
-
-    /* ------------------------------------------------------------------------------- */
-    public boolean inHeaderState()
-    {
-        return _state < 0;
-    }
-
-    /* ------------------------------------------------------------------------------- */
-    public boolean isIdle()
-    {
-        return _state == STATE_START;
-    }
-
-    /* ------------------------------------------------------------------------------- */
-    public boolean isComplete()
-    {
-        return _state == STATE_END;
-    }
-
-    /* ------------------------------------------------------------------------------- */
-    public boolean isMoreInBuffer()
-    {
-
-        if (_header != null && _header.hasContent() || _body != null && _body.hasContent())
-            return true;
-
-        return false;
-    }
-
-    /* ------------------------------------------------------------------------------- */
-    public boolean isState(int state)
-    {
-        return _state == state;
-    }
-
-    /* ------------------------------------------------------------------------------- */
-    public void parse() throws IOException
-    {
-        if (_state == STATE_END)
-            reset();
-        if (_state != STATE_START)
-            throw new IllegalStateException("!START");
-
-        // continue parsing
-        while (!isComplete())
-        {
-            parseNext();
-        }
-    }
-
-    /* ------------------------------------------------------------------------------- */
-    public boolean parseAvailable() throws IOException
-    {
-        boolean progress=parseNext()>0;
-        
-        // continue parsing
-        while (!isComplete() && _buffer!=null && _buffer.length()>0)
-        {
-            progress |= parseNext()>0;
-        }
-        return progress;
-    }
-
-    /* ------------------------------------------------------------------------------- */
-    private int fill() throws IOException
-    {
-        int filled = -1;
-        if (_body != null && _buffer != _body)
-        {
-            // mod_jk implementations may have some partial data from header
-            // check if there are partial contents in the header
-            // copy it to the body if there are any
-            if(_header.length() > 0)
-            {
-                // copy the patial data from the header to the body
-                _body.put(_header);
-            }
-
-            _buffer = _body;
-            
-            if (_buffer.length()>0)
-            {            
-                filled = _buffer.length();
-                return filled;
-            }
-        }
-
-        if (_buffer.markIndex() == 0 && _buffer.putIndex() == _buffer.capacity())
-            throw new IOException("FULL");
-        if (_endp != null && filled <= 0)
-        {
-            // Compress buffer if handling _content buffer
-            // TODO check this is not moving data too much
-            if (_buffer == _body)
-                _buffer.compact();
-
-            if (_buffer.space() == 0)
-                throw new IOException("FULL");
-
-            try
-            {
-                filled = _endp.fill(_buffer);
-            }
-            catch (IOException e)
-            {
-                // This is normal in AJP since the socket closes on timeout only
-                LOG.debug(e);
-                reset();
-                throw (e instanceof EofException) ? e : new EofException(e);
-            }
-        }
-        
-        if (filled < 0)
-        {
-            if (_state > STATE_END)
-            {
-                _state = STATE_END;
-                _handler.messageComplete(_contentPosition);
-                return filled;
-            }
-            reset();
-            throw new EofException();
-        }
-    
-        return filled;
-    }
-    
-    volatile int _seq=0;
-    /* ------------------------------------------------------------------------------- */
-    public int parseNext() throws IOException
-    {
-        int total_filled = 0;
-
-        if (_buffer == null)
-        {
-            if (_header == null)
-                _header = _buffers.getHeader();
-           
-            _buffer = _header;
-            _tok0 = new View(_header);
-            _tok1 = new View(_header);
-            _tok0.setPutIndex(_tok0.getIndex());
-            _tok1.setPutIndex(_tok1.getIndex());
-        }
-
-        if (_state == STATE_END)
-            throw new IllegalStateException("STATE_END");
-        if (_state > STATE_END && _contentPosition == _contentLength)
-        {
-            _state = STATE_END;
-            _handler.messageComplete(_contentPosition);
-            return 1;
-        }
-        
-        if (_state < 0)
-        {
-            // have we seen a packet?
-            if (_packetLength<=0)
-            {
-                if (_buffer.length()<4)
-                {
-                    if (total_filled<0) 
-                        total_filled=0;
-                    total_filled+=fill();
-                    if (_buffer.length()<4)
-                        return total_filled;
-                }
-                
-                _contentLength = HttpTokens.UNKNOWN_CONTENT;
-                int _magic = Ajp13RequestPacket.getInt(_buffer);
-                if (_magic != Ajp13RequestHeaders.MAGIC)
-                    throw new IOException("Bad AJP13 rcv packet: " + "0x" + Integer.toHexString(_magic) + " expected " + "0x" + Integer.toHexString(Ajp13RequestHeaders.MAGIC) + " " + this);
-
-
-                _packetLength = Ajp13RequestPacket.getInt(_buffer);
-                if (_packetLength > Ajp13Packet.MAX_PACKET_SIZE)
-                    throw new IOException("AJP13 packet (" + _packetLength + "bytes) too large for buffer");
-                
-            }
-            
-            if (_buffer.length() < _packetLength)
-            {
-                if (total_filled<0) 
-                    total_filled=0;
-                total_filled+=fill();
-                if (_buffer.length() < _packetLength)
-                    return total_filled;
-            }
-
-            // Parse Header
-            Buffer bufHeaderName = null;
-            Buffer bufHeaderValue = null;
-            int attr_type = 0;
-
-            byte packetType = Ajp13RequestPacket.getByte(_buffer);
-            
-            switch (packetType)
-            {
-                case Ajp13Packet.FORWARD_REQUEST_ORDINAL:
-                    _handler.startForwardRequest();
-                    break;
-                case Ajp13Packet.CPING_REQUEST_ORDINAL:
-                    (_generator).sendCPong();
-                    
-                    if(_header != null)
-                    {
-                        _buffers.returnBuffer(_header);
-                        _header = null;
-                    }
-
-                    if(_body != null)
-                    {
-                        _buffers.returnBuffer(_body);
-                        _body = null;
-                    }
-
-                    _buffer= null;
-
-                    reset();
-
-                    return -1;
-                case Ajp13Packet.SHUTDOWN_ORDINAL:
-                    shutdownRequest();
-
-                    return -1;
-
-                default:
-                    // XXX Throw an Exception here?? Close
-                    // connection!
-                    LOG.warn("AJP13 message type ({PING}: "+packetType+" ) not supported/recognized as an AJP request");
-                throw new IllegalStateException("PING is not implemented");
-            }
-
-
-            _handler.parsedMethod(Ajp13RequestPacket.getMethod(_buffer));
-            _handler.parsedProtocol(Ajp13RequestPacket.getString(_buffer, _tok0));
-            _handler.parsedUri(Ajp13RequestPacket.getString(_buffer, _tok1));
-            _handler.parsedRemoteAddr(Ajp13RequestPacket.getString(_buffer, _tok1));
-            _handler.parsedRemoteHost(Ajp13RequestPacket.getString(_buffer, _tok1));
-            _handler.parsedServerName(Ajp13RequestPacket.getString(_buffer, _tok1));
-            _handler.parsedServerPort(Ajp13RequestPacket.getInt(_buffer));
-            _handler.parsedSslSecure(Ajp13RequestPacket.getBool(_buffer));
-
-
-            _headers = Ajp13RequestPacket.getInt(_buffer);
-
-            for (int h=0;h<_headers;h++)
-            {
-                bufHeaderName = Ajp13RequestPacket.getHeaderName(_buffer, _tok0);
-                bufHeaderValue = Ajp13RequestPacket.getString(_buffer, _tok1);
-
-                if (bufHeaderName != null && bufHeaderName.toString().equals(Ajp13RequestHeaders.CONTENT_LENGTH))
-                {
-                    _contentLength = BufferUtil.toLong(bufHeaderValue);
-                    if (_contentLength == 0)
-                        _contentLength = HttpTokens.NO_CONTENT;
-                }
-
-                _handler.parsedHeader(bufHeaderName, bufHeaderValue);
-            }
-
-
-
-            attr_type = Ajp13RequestPacket.getByte(_buffer) & 0xff;
-            while (attr_type != 0xFF)
-            {
-                switch (attr_type)
-                {
-                    // XXX How does this plug into the web
-                    // containers
-                    // authentication?
-
-                    case Ajp13RequestHeaders.REMOTE_USER_ATTR:
-                        _handler.parsedRemoteUser(Ajp13RequestPacket.getString(_buffer, _tok1));
-                        break;
-                    case Ajp13RequestHeaders.AUTH_TYPE_ATTR:
-                        //XXX JASPI how does this make sense?
-                        _handler.parsedAuthorizationType(Ajp13RequestPacket.getString(_buffer, _tok1));
-                        break;
-
-                    case Ajp13RequestHeaders.QUERY_STRING_ATTR:
-                        _handler.parsedQueryString(Ajp13RequestPacket.getString(_buffer, _tok1));
-                        break;
-
-                    case Ajp13RequestHeaders.JVM_ROUTE_ATTR:
-                        // moved to Eclipse naming usage
-                        // used in org.eclipse.jetty.servlet.HashSessionIdManager
-                        _handler.parsedRequestAttribute("org.eclipse.jetty.ajp.JVMRoute", Ajp13RequestPacket.getString(_buffer, _tok1));
-                        break;
-
-                    case Ajp13RequestHeaders.SSL_CERT_ATTR:
-                        _handler.parsedSslCert(Ajp13RequestPacket.getString(_buffer, _tok1));
-                        break;
-
-                    case Ajp13RequestHeaders.SSL_CIPHER_ATTR:
-                        _handler.parsedSslCipher(Ajp13RequestPacket.getString(_buffer, _tok1));
-                        // SslSocketConnector.customize()
-                        break;
-
-                    case Ajp13RequestHeaders.SSL_SESSION_ATTR:
-                        _handler.parsedSslSession(Ajp13RequestPacket.getString(_buffer, _tok1));
-                        break;
-
-                    case Ajp13RequestHeaders.REQUEST_ATTR:
-                        _handler.parsedRequestAttribute(Ajp13RequestPacket.getString(_buffer, _tok0).toString(), Ajp13RequestPacket.getString(_buffer, _tok1));
-                        break;
-
-                        // New Jk API?
-                        // Check if experimental or can they
-                        // assumed to be
-                        // supported
-                        
-                    case Ajp13RequestHeaders.SSL_KEYSIZE_ATTR:
-                        
-                        // This has been implemented in AJP13 as either a string or a integer.
-                        // Servlet specs say javax.servlet.request.key_size must be an Integer
-                        
-                        // Does it look like a string containing digits?
-                        int length = Ajp13RequestPacket.getInt(_buffer);
-                        
-                        if (length>0 && length<16)
-                        {
-                            // this must be a string length rather than a key length
-                            _buffer.skip(-2);
-                            _handler.parsedSslKeySize(Integer.parseInt(Ajp13RequestPacket.getString(_buffer, _tok1).toString()));
-                        }
-                        else
-                            _handler.parsedSslKeySize(length);
-                        
-                        break;
-
-                        
-                        // Used to lock down jk requests with a
-                        // secreate
-                        // key.
-                        
-                    case Ajp13RequestHeaders.SECRET_ATTR:
-                        // XXX Investigate safest way to
-                        // deal with
-                        // this...
-                        // should this tie into shutdown
-                        // packet?
-                        break;
-
-                    case Ajp13RequestHeaders.STORED_METHOD_ATTR:
-                        // XXX Confirm this should
-                        // really overide
-                        // previously parsed method?
-                        // _handler.parsedMethod(Ajp13PacketMethods.CACHE.get(Ajp13RequestPacket.getString()));
-                        break;
-
-
-                    case Ajp13RequestHeaders.CONTEXT_ATTR:
-                        _handler.parsedContextPath(Ajp13RequestPacket.getString(_buffer, _tok1));
-                        break;
-                    case Ajp13RequestHeaders.SERVLET_PATH_ATTR:
-                        _handler.parsedServletPath(Ajp13RequestPacket.getString(_buffer, _tok1));
-
-                        break;
-                    default:
-                        LOG.warn("Unsupported Ajp13 Request Attribute {}", new Integer(attr_type));
-                    break;
-                }
-
-                attr_type = Ajp13RequestPacket.getByte(_buffer) & 0xff;
-            }
-
-            _contentPosition = 0;
-            switch ((int) _contentLength)
-            {
-
-                case HttpTokens.NO_CONTENT:
-                    _state = STATE_END;
-                    _handler.headerComplete();
-                    _handler.messageComplete(_contentPosition);
-
-                    break;
-
-                case HttpTokens.UNKNOWN_CONTENT:
-
-                    _generator.getBodyChunk();
-                    if (_buffers != null && _body == null && _buffer == _header && _header.length() <= 0)
-                    {
-                        _body = _buffers.getBuffer();
-                        _body.clear();
-                    }
-                    _state = STATE_AJP13CHUNK_START;
-                    _handler.headerComplete(); // May recurse here!
-
-                    return total_filled;
-
-                default:
-
-                    if (_buffers != null && _body == null && _buffer == _header && _contentLength > (_header.capacity() - _header.getIndex()))
-                    {
-                        _body = _buffers.getBuffer();
-                        _body.clear();
-
-                    }
-                _state = STATE_AJP13CHUNK_START;
-                _handler.headerComplete(); // May recurse here!
-                return total_filled;
-            }
-        }
-
-        Buffer chunk;
-
-        while (_state>STATE_END)
-        {
-            switch (_state)
-            {
-                case STATE_AJP13CHUNK_START:
-                    if (_buffer.length()<6)
-                    {
-                        if (total_filled<0) 
-                            total_filled=0;
-                        total_filled+=fill();
-                        if (_buffer.length()<6)
-                            return total_filled;
-                    }
-                    int _magic=Ajp13RequestPacket.getInt(_buffer);
-                    if (_magic!=Ajp13RequestHeaders.MAGIC)
-                    {
-                        throw new IOException("Bad AJP13 rcv packet: "+"0x"+Integer.toHexString(_magic)+" expected "+"0x"
-                                +Integer.toHexString(Ajp13RequestHeaders.MAGIC)+" "+this);
-                    }
-                    _chunkPosition=0;
-                    _chunkLength=Ajp13RequestPacket.getInt(_buffer)-2;
-                    Ajp13RequestPacket.getInt(_buffer);
-                    if (_chunkLength==0)
-                    {
-                        _state=STATE_END;
-                         _generator.gotBody();
-                        _handler.messageComplete(_contentPosition);
-                        return total_filled;
-                    }
-                    _state=STATE_AJP13CHUNK;
-                    break;
-
-                case STATE_AJP13CHUNK:
-                    if (_buffer.length()<_chunkLength)
-                    {
-                        if (total_filled<0) 
-                            total_filled=0;
-                        total_filled+=fill();
-                        if (_buffer.length()<_chunkLength)
-                            return total_filled;
-                    }
-
-                    int remaining=_chunkLength-_chunkPosition;
-                    
-                    if (remaining==0)
-                    {
-                        _state=STATE_AJP13CHUNK_START;
-                        if (_contentPosition<_contentLength)
-                        {
-                            _generator.getBodyChunk();
-                        }
-                        else
-                        {
-                            _generator.gotBody();
-                        }
-
-                        return total_filled;
-                    }
-
-                    if (_buffer.length()<remaining)
-                    {
-                        remaining=_buffer.length();
-                    }
-
-                    chunk=Ajp13RequestPacket.get(_buffer,remaining);
-                    
-                    _contentPosition+=chunk.length();
-                    _chunkPosition+=chunk.length();
-                    _contentView.update(chunk);
-
-                    remaining=_chunkLength-_chunkPosition;
-
-                    if (remaining==0)
-                    {
-                        _state=STATE_AJP13CHUNK_START;
-                        if (_contentPosition<_contentLength || _contentLength == HttpTokens.UNKNOWN_CONTENT)
-                        {
-                            _generator.getBodyChunk();
-                        }
-                        else
-                        {
-                            _generator.gotBody();
-                        }
-                    }
-
-                    _handler.content(chunk);
-
-                return total_filled;
-
-            default:
-                throw new IllegalStateException("Invalid Content State");
-
-            }
-
-        }
-        
-        return total_filled;
-    }
-
-    /* ------------------------------------------------------------------------------- */
-    public void reset()
-    {
-        _state = STATE_START;
-        _contentLength = HttpTokens.UNKNOWN_CONTENT;
-        _contentPosition = 0;
-        _length = 0;
-        _packetLength = 0;
-
-        if (_body!=null && _body.hasContent())
-        {
-            // There is content in the body after the end of the request.
-            // This is probably a pipelined header of the next request, so we need to
-            // copy it to the header buffer.
-            if (_header==null)
-            {
-                _header=_buffers.getHeader();
-                _tok0.update(_header);
-                _tok0.update(0,0);
-                _tok1.update(_header);
-                _tok1.update(0,0);
-            }
-            else
-            {
-                _header.setMarkIndex(-1);
-                _header.compact();
-            }
-            int take=_header.space();
-            if (take>_body.length())
-                take=_body.length();
-            _body.peek(_body.getIndex(),take);
-            _body.skip(_header.put(_body.peek(_body.getIndex(),take)));
-        }
-
-        if (_header!=null)
-            _header.setMarkIndex(-1);
-        if (_body!=null)
-            _body.setMarkIndex(-1);
-
-        _buffer=_header;
-    }
-
-
-    /* ------------------------------------------------------------------------------- */
-    public void returnBuffers()
-    {
-        if (_body!=null && !_body.hasContent() && _body.markIndex()==-1)
-        {   
-            if (_buffer==_body)
-                _buffer=_header;
-            if (_buffers!=null)
-                _buffers.returnBuffer(_body);
-            _body=null; 
-        }
-
-        if (_header!=null && !_header.hasContent() && _header.markIndex()==-1)
-        {
-            if (_buffer==_header)
-                _buffer=null;
-            _buffers.returnBuffer(_header);
-            _header=null;
-        }
-    }
-
-    /* ------------------------------------------------------------------------------- */
-    Buffer getHeaderBuffer()
-    {
-        return _buffer;
-    }
-
-    private void shutdownRequest()
-    {
-        _state = STATE_END;
-
-        if(!Ajp13SocketConnector.__allowShutdown)
-        {
-            LOG.warn("AJP13: Shutdown Request is Denied, allowShutdown is set to false!!!");
-            return;
-        }
-
-        if(Ajp13SocketConnector.__secretWord != null)
-        {
-            LOG.warn("AJP13: Validating Secret Word");
-            try
-            {
-                String secretWord = Ajp13RequestPacket.getString(_buffer, _tok1).toString();
-
-                if(!Ajp13SocketConnector.__secretWord.equals(secretWord))
-                {
-                    LOG.warn("AJP13: Shutdown Request Denied, Invalid Sercret word!!!");
-                    throw new IllegalStateException("AJP13: Secret Word is Invalid: Peer has requested shutdown but, Secret Word did not match");
-                }
-            }
-            catch (Exception e)
-            {
-                LOG.warn("AJP13: Secret Word is Required!!!");
-                LOG.debug(e);
-                throw new IllegalStateException("AJP13: Secret Word is Required: Peer has requested shutdown but, has not provided a Secret Word");
-            }
-
-
-            LOG.warn("AJP13: Shutdown Request is Denied, allowShutdown is set to false!!!");
-            return;
-        }
-
-        LOG.warn("AJP13: Peer Has Requested for Shutdown!!!");
-        LOG.warn("AJP13: Jetty 6 is shutting down !!!");
-        System.exit(0);
-    }
-
-    /* ------------------------------------------------------------------------------- */
-    public interface EventHandler
-    {
-
-        // public void shutdownRequest() throws IOException;
-        // public void cpingRequest() throws IOException;
-
-        public void content(Buffer ref) throws IOException;
-
-        public void headerComplete() throws IOException;
-
-        public void messageComplete(long contextLength) throws IOException;
-
-        public void parsedHeader(Buffer name, Buffer value) throws IOException;
-
-        public void parsedMethod(Buffer method) throws IOException;
-
-        public void parsedProtocol(Buffer protocol) throws IOException;
-
-        public void parsedQueryString(Buffer value) throws IOException;
-
-        public void parsedRemoteAddr(Buffer addr) throws IOException;
-
-        public void parsedRemoteHost(Buffer host) throws IOException;
-
-        public void parsedRequestAttribute(String key, Buffer value) throws IOException;
-        
-        public void parsedRequestAttribute(String key, int value) throws IOException;
-
-        public void parsedServerName(Buffer name) throws IOException;
-
-        public void parsedServerPort(int port) throws IOException;
-
-        public void parsedSslSecure(boolean secure) throws IOException;
-
-        public void parsedUri(Buffer uri) throws IOException;
-
-        public void startForwardRequest() throws IOException;
-
-        public void parsedAuthorizationType(Buffer authType) throws IOException;
-        
-        public void parsedRemoteUser(Buffer remoteUser) throws IOException;
-
-        public void parsedServletPath(Buffer servletPath) throws IOException;
-        
-        public void parsedContextPath(Buffer context) throws IOException;
-
-        public void parsedSslCert(Buffer sslCert) throws IOException;
-
-        public void parsedSslCipher(Buffer sslCipher) throws IOException;
-
-        public void parsedSslSession(Buffer sslSession) throws IOException;
-
-        public void parsedSslKeySize(int keySize) throws IOException;
-
-
-
-
-
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * TODO Make this common with HttpParser
-     * 
-     */
-    public static class Input extends ServletInputStream
-    {
-        private Ajp13Parser _parser;
-        private EndPoint _endp;
-        private long _maxIdleTime;
-        private View _content;
-
-        /* ------------------------------------------------------------ */
-        public Input(Ajp13Parser parser, long maxIdleTime)
-        {
-            _parser = parser;
-            _endp = parser._endp;
-            _maxIdleTime = maxIdleTime;
-            _content = _parser._contentView;
-        }
-
-        /* ------------------------------------------------------------ */
-        @Override
-        public int read() throws IOException
-        {
-            int c = -1;
-            if (blockForContent())
-                c = 0xff & _content.get();
-            return c;
-        }
-
-        /* ------------------------------------------------------------ */
-        /*
-         * @see java.io.InputStream#read(byte[], int, int)
-         */
-        @Override
-        public int read(byte[] b, int off, int len) throws IOException
-        {
-            int l = -1;
-            if (blockForContent())
-                l = _content.get(b, off, len);
-            return l;
-        }
-
-        /* ------------------------------------------------------------ */
-        private boolean blockForContent() throws IOException
-        {
-            if (_content.length() > 0)
-                return true;
-            if (_parser.isState(Ajp13Parser.STATE_END) || _parser.isState(Ajp13Parser.STATE_START))
-                return false;
-
-            // Handle simple end points.
-            if (_endp == null)
-                _parser.parseNext();
-
-            // Handle blocking end points
-            else if (_endp.isBlocking())
-            {
-                _parser.parseNext();
-                
-                // parse until some progress is made (or IOException thrown for timeout)
-                while (_content.length() == 0 && !_parser.isState(Ajp13Parser.STATE_END))
-                {
-                    // Try to get more _parser._content
-                    _parser.parseNext();
-                }
-            }
-            else // Handle non-blocking end point
-            {
-                long filled = _parser.parseNext();
-                boolean blocked = false;
-
-                // parse until some progress is made (or
-                // IOException thrown for timeout)
-                while (_content.length() == 0 && !_parser.isState(Ajp13Parser.STATE_END))
-                {
-                    // if fill called, but no bytes read,
-                    // then block
-                    if (filled > 0)
-                        blocked = false;
-                    else if (filled == 0)
-                    {
-                        if (blocked)
-                            throw new InterruptedIOException("timeout");
-
-                        blocked = true;
-                        _endp.blockReadable(_maxIdleTime);
-                    }
-
-                    // Try to get more _parser._content
-                    filled = _parser.parseNext();
-                }
-            }
-
-            return _content.length() > 0;
-        }
-    }
-
-    public boolean isPersistent()
-    {
-        return true;
-    }
-
-    public void setPersistent(boolean persistent)
-    {
-        LOG.warn("AJP13.setPersistent is not IMPLEMENTED!");
-    }
-}
diff --git a/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13Request.java b/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13Request.java
deleted file mode 100644
index 0045c20..0000000
--- a/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13Request.java
+++ /dev/null
@@ -1,124 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.ajp;
-
-import org.eclipse.jetty.server.AbstractHttpConnection;
-import org.eclipse.jetty.server.Request;
-
-public class Ajp13Request extends Request
-{
-    protected String _remoteAddr;
-    protected String _remoteHost;
-    protected String _remoteUser;
-    protected boolean _sslSecure;
-
-    /* ------------------------------------------------------------ */
-    public Ajp13Request(AbstractHttpConnection connection)
-    {
-        super(connection);     
-    }
-    
-    /* ------------------------------------------------------------ */
-    public Ajp13Request()
-    {     
-    }
-
-    /* ------------------------------------------------------------ */
-    void setConnection(Ajp13Connection connection)
-    {
-        super.setConnection(connection);
-    }
-
-    /* ------------------------------------------------------------ */
-    public void setRemoteUser(String remoteUser)
-    {
-        _remoteUser = remoteUser;
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public String getRemoteUser()
-    {
-        if(_remoteUser != null)
-            return _remoteUser;
-        return super.getRemoteUser();
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public String getRemoteAddr()
-    {
-        if (_remoteAddr != null)
-            return _remoteAddr;
-        if (_remoteHost != null)
-            return _remoteHost;
-        return super.getRemoteAddr();
-    }
-
-
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public void setRemoteAddr(String remoteAddr)
-    {
-        _remoteAddr = remoteAddr;
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public String getRemoteHost()
-    {
-        if (_remoteHost != null)
-            return _remoteHost;
-        if (_remoteAddr != null)
-            return _remoteAddr;
-        return super.getRemoteHost();
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public void setRemoteHost(String remoteHost)
-    {
-        _remoteHost = remoteHost;
-    }
-    
-    /* ------------------------------------------------------------ */
-    public boolean isSslSecure()
-    {
-        return _sslSecure;
-    }
-
-    /* ------------------------------------------------------------ */
-    public void setSslSecure(boolean sslSecure)
-    {
-        _sslSecure = sslSecure;
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    protected void recycle()
-    {
-        super.recycle();
-        _remoteAddr = null;
-        _remoteHost = null;
-	_remoteUser = null;
-	_sslSecure = false;
-    }
-
-}
diff --git a/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13RequestHeaders.java b/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13RequestHeaders.java
deleted file mode 100644
index 1bccba9..0000000
--- a/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13RequestHeaders.java
+++ /dev/null
@@ -1,67 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.ajp;
-
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.BufferCache;
-
-/**
- * XXX Should this implement the Buffer interface?
- * 
- * 
- */
-public class Ajp13RequestHeaders extends BufferCache
-{
-
-    public final static int MAGIC=0x1234;
-
-    public final static String ACCEPT="accept", ACCEPT_CHARSET="accept-charset", ACCEPT_ENCODING="accept-encoding", ACCEPT_LANGUAGE="accept-language",
-            AUTHORIZATION="authorization", CONNECTION="connection", CONTENT_TYPE="content-type", CONTENT_LENGTH="content-length", COOKIE="cookie",
-            COOKIE2="cookie2", HOST="host", PRAGMA="pragma", REFERER="referer", USER_AGENT="user-agent";
-
-    public final static int ACCEPT_ORDINAL=1, ACCEPT_CHARSET_ORDINAL=2, ACCEPT_ENCODING_ORDINAL=3, ACCEPT_LANGUAGE_ORDINAL=4, AUTHORIZATION_ORDINAL=5,
-            CONNECTION_ORDINAL=6, CONTENT_TYPE_ORDINAL=7, CONTENT_LENGTH_ORDINAL=8, COOKIE_ORDINAL=9, COOKIE2_ORDINAL=10, HOST_ORDINAL=11, PRAGMA_ORDINAL=12,
-            REFERER_ORDINAL=13, USER_AGENT_ORDINAL=14;
-
-    public final static BufferCache CACHE=new BufferCache();
-
-    public final static Buffer ACCEPT_BUFFER=CACHE.add(ACCEPT,ACCEPT_ORDINAL), ACCEPT_CHARSET_BUFFER=CACHE.add(ACCEPT_CHARSET,ACCEPT_CHARSET_ORDINAL),
-            ACCEPT_ENCODING_BUFFER=CACHE.add(ACCEPT_ENCODING,ACCEPT_ENCODING_ORDINAL), ACCEPT_LANGUAGE_BUFFER=CACHE
-                    .add(ACCEPT_LANGUAGE,ACCEPT_LANGUAGE_ORDINAL), AUTHORIZATION_BUFFER=CACHE.add(AUTHORIZATION,AUTHORIZATION_ORDINAL), CONNECTION_BUFFER=CACHE
-                    .add(CONNECTION,CONNECTION_ORDINAL), CONTENT_TYPE_BUFFER=CACHE.add(CONTENT_TYPE,CONTENT_TYPE_ORDINAL), CONTENT_LENGTH_BUFFER=CACHE.add(
-                    CONTENT_LENGTH,CONTENT_LENGTH_ORDINAL), COOKIE_BUFFER=CACHE.add(COOKIE,COOKIE_ORDINAL), COOKIE2_BUFFER=CACHE.add(COOKIE2,COOKIE2_ORDINAL),
-            HOST_BUFFER=CACHE.add(HOST,HOST_ORDINAL), PRAGMA_BUFFER=CACHE.add(PRAGMA,PRAGMA_ORDINAL), REFERER_BUFFER=CACHE.add(REFERER,REFERER_ORDINAL),
-            USER_AGENT_BUFFER=CACHE.add(USER_AGENT,USER_AGENT_ORDINAL);
-
-    public final static byte 
-            CONTEXT_ATTR=1, // Legacy
-            SERVLET_PATH_ATTR=2, // Legacy
-            REMOTE_USER_ATTR=3, 
-            AUTH_TYPE_ATTR=4, 
-            QUERY_STRING_ATTR=5, 
-            JVM_ROUTE_ATTR=6, 
-            SSL_CERT_ATTR=7, 
-            SSL_CIPHER_ATTR=8,
-            SSL_SESSION_ATTR=9,
-            REQUEST_ATTR=10, 
-            SSL_KEYSIZE_ATTR=11, 
-            SECRET_ATTR=12, 
-            STORED_METHOD_ATTR=13;
-
-}
diff --git a/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13RequestPacket.java b/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13RequestPacket.java
deleted file mode 100644
index 1f0267f..0000000
--- a/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13RequestPacket.java
+++ /dev/null
@@ -1,90 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.ajp;
-
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.View;
-
-/**
- * 
- * 
- * 
- */
-public class Ajp13RequestPacket
-{
-    public static boolean isEmpty(Buffer _buffer)
-    {
-        return _buffer.length()==0;
-    }
-
-    public static int getInt(Buffer _buffer)
-    {
-        return ((_buffer.get()&0xFF)<<8)|(_buffer.get()&0xFF);
-    }
-
-    public static Buffer getString(Buffer _buffer, View tok)
-    {
-        int len=((_buffer.peek()&0xFF)<<8)|(_buffer.peek(_buffer.getIndex()+1)&0xFF);
-        if (len==0xffff)
-        {
-            _buffer.skip(2);
-            return null;
-        }
-        int start=_buffer.getIndex();
-        tok.update(start+2,start+len+2);
-        _buffer.skip(len+3);
-        return tok;
-    }
-
-    public static byte getByte(Buffer _buffer)
-    {
-        return _buffer.get();
-    }
-
-    public static boolean getBool(Buffer _buffer)
-    {
-        return _buffer.get()>0;
-    }
-
-    public static Buffer getMethod(Buffer _buffer)
-    {
-        return Ajp13PacketMethods.CACHE.get(_buffer.get());
-    }
-
-    public static Buffer getHeaderName(Buffer _buffer, View tok)
-    {
-        int len=((_buffer.peek()&0xFF)<<8)|(_buffer.peek(_buffer.getIndex()+1)&0xFF);
-        if ((0xFF00&len)==0xA000)
-        {
-            _buffer.skip(1);
-            return Ajp13RequestHeaders.CACHE.get(_buffer.get());
-        }
-        int start=_buffer.getIndex();
-        tok.update(start+2,start+len+2);
-        _buffer.skip(len+3);
-        return tok;
-
-    }
-
-    public static Buffer get(Buffer buffer, int length)
-    {
-        return buffer.get(length);
-    }
-
-}
diff --git a/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13ResponseHeaders.java b/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13ResponseHeaders.java
deleted file mode 100644
index 135ce3f..0000000
--- a/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13ResponseHeaders.java
+++ /dev/null
@@ -1,48 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.ajp;
-
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.BufferCache;
-
-/**
- * 
- */
-public class Ajp13ResponseHeaders extends BufferCache
-{
-
-    public final static int MAGIC=0xab00;
-
-    public final static String CONTENT_TYPE="Content-Type", CONTENT_LANGUAGE="Content-Language", CONTENT_LENGTH="Content-Length", DATE="Date",
-            LAST_MODIFIED="Last-Modified", LOCATION="Location", SET_COOKIE="Set-Cookie", SET_COOKIE2="Set-Cookie2", SERVLET_ENGINE="Servlet-Engine",
-            STATUS="Status", WWW_AUTHENTICATE="WWW-Authenticate";
-
-    public final static int CONTENT_TYPE_ORDINAL=1, CONTENT_LANGUAGE_ORDINAL=2, CONTENT_LENGTH_ORDINAL=3, DATE_ORDINAL=4, LAST_MODIFIED_ORDINAL=5,
-            LOCATION_ORDINAL=6, SET_COOKIE_ORDINAL=7, SET_COOKIE2_ORDINAL=8, SERVLET_ENGINE_ORDINAL=9, STATUS_ORDINAL=10, WWW_AUTHENTICATE_ORDINAL=11;
-
-    public final static BufferCache CACHE=new BufferCache();
-
-    public final static Buffer CONTENT_TYPE_BUFFER=CACHE.add(CONTENT_TYPE,CONTENT_TYPE_ORDINAL), CONTENT_LANGUAGE_BUFFER=CACHE.add(CONTENT_LANGUAGE,
-            CONTENT_LANGUAGE_ORDINAL), CONTENT_LENGTH_BUFFER=CACHE.add(CONTENT_LENGTH,CONTENT_LENGTH_ORDINAL), DATE_BUFFER=CACHE.add(DATE,DATE_ORDINAL),
-            LAST_MODIFIED_BUFFER=CACHE.add(LAST_MODIFIED,LAST_MODIFIED_ORDINAL), LOCATION_BUFFER=CACHE.add(LOCATION,LOCATION_ORDINAL), SET_COOKIE_BUFFER=CACHE
-                    .add(SET_COOKIE,SET_COOKIE_ORDINAL), SET_COOKIE2_BUFFER=CACHE.add(SET_COOKIE2,SET_COOKIE2_ORDINAL), SERVLET_ENGINE_BUFFER=CACHE.add(
-                    SERVLET_ENGINE,SERVLET_ENGINE_ORDINAL), STATUS_BUFFER=CACHE.add(STATUS,STATUS_ORDINAL), WWW_AUTHENTICATE_BUFFER=CACHE.add(WWW_AUTHENTICATE,
-                    WWW_AUTHENTICATE_ORDINAL);
-
-}
diff --git a/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13SocketConnector.java b/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13SocketConnector.java
deleted file mode 100644
index 2351c38..0000000
--- a/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13SocketConnector.java
+++ /dev/null
@@ -1,132 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.ajp;
-
-import java.io.IOException;
-
-import org.eclipse.jetty.http.HttpSchemes;
-import org.eclipse.jetty.io.Connection;
-import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.server.Request;
-import org.eclipse.jetty.server.bio.SocketConnector;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-
-/**
- *
- *
- *
- */
-public class Ajp13SocketConnector extends SocketConnector
-{
-    private static final Logger LOG = Log.getLogger(Ajp13SocketConnector.class);
-
-    static String __secretWord = null;
-    static boolean __allowShutdown = false;
-    public Ajp13SocketConnector()
-    {
-        super.setRequestHeaderSize(Ajp13Packet.MAX_PACKET_SIZE);
-        super.setResponseHeaderSize(Ajp13Packet.MAX_PACKET_SIZE);
-        super.setRequestBufferSize(Ajp13Packet.MAX_PACKET_SIZE);
-        super.setResponseBufferSize(Ajp13Packet.MAX_PACKET_SIZE);
-        // IN AJP protocol the socket stay open, so
-        // by default the time out is set to 0 seconds
-        super.setMaxIdleTime(0);
-    }
-
-    @Override
-    protected void doStart() throws Exception
-    {
-        super.doStart();
-        LOG.info("AJP13 is not a secure protocol. Please protect port {}",Integer.toString(getLocalPort()));
-    }
-
-
-
-    /* ------------------------------------------------------------ */
-    /* (non-Javadoc)
-     * @see org.eclipse.jetty.server.bio.SocketConnector#customize(org.eclipse.io.EndPoint, org.eclipse.jetty.server.Request)
-     */
-    @Override
-    public void customize(EndPoint endpoint, Request request) throws IOException
-    {
-        super.customize(endpoint,request);
-        if (request.isSecure())
-            request.setScheme(HttpSchemes.HTTPS);
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    protected Connection newConnection(EndPoint endpoint)
-    {
-        return new Ajp13Connection(this,endpoint,getServer());
-    }
-
-    /* ------------------------------------------------------------ */
-    // Secured on a packet by packet bases not by connection
-    @Override
-    public boolean isConfidential(Request request)
-    {
-        return ((Ajp13Request) request).isSslSecure();
-    }
-
-    /* ------------------------------------------------------------ */
-    // Secured on a packet by packet bases not by connection
-    @Override
-    public boolean isIntegral(Request request)
-    {
-        return ((Ajp13Request) request).isSslSecure();
-    }
-
-    /* ------------------------------------------------------------ */
-    @Deprecated
-    public void setHeaderBufferSize(int headerBufferSize)
-    {
-        LOG.debug(Log.IGNORED);
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public void setRequestBufferSize(int requestBufferSize)
-    {
-        LOG.debug(Log.IGNORED);
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public void setResponseBufferSize(int responseBufferSize)
-    {
-        LOG.debug(Log.IGNORED);
-    }
-
-    /* ------------------------------------------------------------ */
-    public void setAllowShutdown(boolean allowShutdown)
-    {
-        LOG.warn("AJP13: Shutdown Request is: " + allowShutdown);
-        __allowShutdown = allowShutdown;
-    }
-
-    /* ------------------------------------------------------------ */
-    public void setSecretWord(String secretWord)
-    {
-        LOG.warn("AJP13: Shutdown Request secret word is : " + secretWord);
-        __secretWord = secretWord;
-    }
-
-}
diff --git a/jetty-ajp/src/test/java/org/eclipse/jetty/ajp/Ajp13ConnectionTest.java b/jetty-ajp/src/test/java/org/eclipse/jetty/ajp/Ajp13ConnectionTest.java
deleted file mode 100644
index 1cce029..0000000
--- a/jetty-ajp/src/test/java/org/eclipse/jetty/ajp/Ajp13ConnectionTest.java
+++ /dev/null
@@ -1,331 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.ajp;
-
-import static org.junit.Assert.assertTrue;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.net.Socket;
-import java.net.SocketTimeoutException;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jetty.server.Connector;
-import org.eclipse.jetty.server.Request;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.handler.AbstractHandler;
-import org.eclipse.jetty.util.IO;
-import org.eclipse.jetty.util.TypeUtil;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-import org.junit.After;
-import org.junit.AfterClass;
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.Test;
-
-public class Ajp13ConnectionTest
-{
-    private static final Logger LOG = Log.getLogger(Ajp13ConnectionTest.class);
-
-    private static Server _server;
-    private static Ajp13SocketConnector _connector;
-    private Socket _client;
-
-    @BeforeClass
-    public static void startServer() throws Exception
-    {
-        _server=new Server();
-        _connector=new Ajp13SocketConnector();
-
-        _connector.setPort(0);
-        _server.setConnectors(new Connector[] { _connector });
-        _server.setHandler(new Handler());
-        _server.start();
-    }
-
-    @AfterClass
-    public static void stopServer() throws Exception
-    {
-        _connector.close();
-        _server.stop();
-    }
-
-    @Before
-    public void openSocket() throws Exception
-    {
-        _client=new Socket("localhost",_connector.getLocalPort());
-        _client.setSoTimeout(500);
-    }
-
-    @After
-    public void closeSocket() throws Exception
-    {
-        _client.close();
-    }
-
-    @Test
-    public void testPacket1() throws Exception
-    {
-        OutputStream os=_client.getOutputStream();
-
-        String packet="123401070202000f77696474683d20485454502f312e300000122f636f6e74726f6c2f70726f647563742f2200000e3230382e32372e3230332e31323800ffff000c7777772e756c74612e636f6d000050000005a006000a6b6565702d616c69766500a00b000c7777772e756c74612e636f6d00a00e002b4d6f7a696c6c612f342e302028636f6d70617469626c653b20426f726465724d616e6167657220332e302900a0010043696d6167652f6769662c20696d6167652f782d786269746d61702c20696d6167652f6a7065672c20696d6167652f706a7065672c20696d6167652f706d672c202a2f2 [...]
-        os.write(TypeUtil.fromHexString(packet));
-        os.flush();
-
-        readResponse(_client);
-        assertTrue(true);
-    }
-
-    @Test
-    public void testPacket2() throws Exception
-    {
-        OutputStream os=_client.getOutputStream();
-
-        String packet="1234020102020008485454502f312e3100000f2f6363632d7777777777772f61616100000c38382e3838382e38382e383830ffff00116363632e6363636363636363632e636f6d0001bb010009a00b00116363632e6363636363636363632e636f6d00a00e005a4d6f7a696c6c612f352e30202857696e646f77733b20553b2057696e646f7773204e5420352e313b20656e2d55533b2072763a312e382e312e3129204765636b6f2f32303036313230342046697265666f782f322e302e302e3100a0010063746578742f786d6c2c6170706c69636174696f6e2f786d6c2c6170706c69636174696f6e2 [...]
-        os.write(TypeUtil.fromHexString(packet));
-        os.flush();
-
-        readResponse(_client);
-        assertTrue(true);
-    }
-
-    @Test
-    public void testPacket3() throws Exception
-    {
-        OutputStream os=_client.getOutputStream();
-
-        String packet="1234028f02020008485454502f312e3100000d2f666f726d746573742e6a737000000d3139322e3136382e342e31383000ffff00107777772e777265636b6167652e6f726700005000000aa0010063746578742f786d6c2c6170706c69636174696f6e2f786d6c2c6170706c69636174696f6e2f7868746d6c2b786d6c2c746578742f68746d6c3b713d302e392c746578742f706c61696e3b713d302e382c696d6167652f706e672c2a2f2a3b713d302e3500a00200075554462d382c2a00a003000c677a69702c6465666c61746500a004000e656e2d67622c656e3b713d302e3500a006000a6b65657 [...]
-        os.write(TypeUtil.fromHexString(packet));
-        os.flush();
-        readResponse(_client);
-
-        assertTrue(true);
-    }
-
-    @Test
-    public void testSSLPacketWithIntegerKeySize() throws Exception
-    {
-        OutputStream os=_client.getOutputStream();
-
-        String packet="1234025002020008485454502f312e3100000f2f746573742f64756d702f696e666f00000e3139322e3136382e3130302e343000ffff000c776562746964652d746573740001bb01000ca00b000c776562746964652d7465737400a00e005a4d6f7a696c6c612f352e30202857696e646f77733b20553b2057696e646f7773204e5420352e313b20656e2d55533b2072763a312e382e312e3129204765636b6f2f32303036313230342046697265666f782f322e302e302e3100a0010063746578742f786d6c2c6170706c69636174696f6e2f786d6c2c6170706c69636174696f6e2f7868746d6c2b786 [...]
-        os.write(TypeUtil.fromHexString(packet));
-        os.flush();
-        readResponse(_client);
-
-        assertTrue(true);
-    }
-
-    @Test
-    public void testSSLPacketWithStringKeySize() throws Exception
-    {
-        OutputStream os=_client.getOutputStream();
-
-        String packet="1234025002020008485454502f312e3100000f2f746573742f64756d702f696e666f00000e3139322e3136382e3130302e343000ffff000c776562746964652d746573740001bb01000ca00b000c776562746964652d7465737400a00e005a4d6f7a696c6c612f352e30202857696e646f77733b20553b2057696e646f7773204e5420352e313b20656e2d55533b2072763a312e382e312e3129204765636b6f2f32303036313230342046697265666f782f322e302e302e3100a0010063746578742f786d6c2c6170706c69636174696f6e2f786d6c2c6170706c69636174696f6e2f7868746d6c2b786 [...]
-        os.write(TypeUtil.fromHexString(packet));
-        os.flush();
-        readResponse(_client);
-
-        assertTrue(true);
-    }
-
-    @Test
-    public void testPacketWithBody() throws Exception
-    {
-        OutputStream os=_client.getOutputStream();
-
-        os.write(TypeUtil.fromHexString(getTestHeader()));
-        os.write(TypeUtil.fromHexString(getTestShortBody()));
-        os.write(TypeUtil.fromHexString(getTestTinyBody()));
-
-        readResponse(_client);
-
-        assertTrue(true);
-    }
-
-    @Test
-    public void testPacketWithChunkedBody() throws Exception
-    {
-        OutputStream os=_client.getOutputStream();
-
-        String packet="123400ff02040008485454502f312e3100000f2f746573742f64756d702f696e666f0000093132372e302e302e3100ffff00096c6f63616c686f7374000050000007a00e000d4a6176612f312e352e305f313100a00b00096c6f63616c686f737400a0010034746578742f68746d6c2c20696d6167652f6769662c20696d6167652f6a7065672c202a3b20713d2e322c202a2f2a3b20713d2e3200a006000a6b6565702d616c69766500a00700216170706c69636174696f6e2f782d7777772d666f726d2d75726c656e636f6465640000115472616e736665722d456e636f64696e670000076368756e6 [...]
-
-        os.write(TypeUtil.fromHexString(packet));
-        os.flush();
-
-        os.write(TypeUtil.fromHexString("1234007e007c7468656e616d653d746865253230717569636b25323062726f776e253230666f782532306a756d70732532306f766572253230746f2532307468652532306c617a79253230646f67253230544845253230515549434b25323042524f574e253230464f582532304a554d50532532304f564552253230544f25323054"));
-        os.flush();
-
-        os.write(TypeUtil.fromHexString("12340042004048452532304c415a59253230444f472532302676616c75656f66323d6162636465666768696a6b6c6d6e6f707172737475767778797a31323334353637383930"));
-        os.flush();
-
-        os.write(TypeUtil.fromHexString("123400020000"));
-        os.flush();
-
-        readResponse(_client);
-        assertTrue(true);
-    }
-
-    private String getTestHeader()
-    {
-        StringBuffer header=new StringBuffer("");
-        header.append("1234026902040008485454502f31");
-        header.append("2e310000162f61646d696e2f496d6167");
-        header.append("6555706c6f61642e68746d00000a3130");
-        header.append("2e34382e31302e3100ffff000a31302e");
-        header.append("34382e31302e3200005000000da00b00");
-        header.append("0a31302e34382e31302e3200a00e005a");
-        header.append("4d6f7a696c6c612f352e30202857696e");
-        header.append("646f77733b20553b2057696e646f7773");
-        header.append("204e5420352e313b20656e2d55533b20");
-        header.append("72763a312e382e312e3129204765636b");
-        header.append("6f2f3230303631323034204669726566");
-        header.append("6f782f322e302e302e3100a001006374");
-        header.append("6578742f786d6c2c6170706c69636174");
-        header.append("696f6e2f786d6c2c6170706c69636174");
-        header.append("696f6e2f7868746d6c2b786d6c2c7465");
-        header.append("78742f68746d6c3b713d302e392c7465");
-        header.append("78742f706c61696e3b713d302e382c69");
-        header.append("6d6167652f706e672c2a2f2a3b713d30");
-        header.append("2e3500a004000e656e2d75732c656e3b");
-        header.append("713d302e3500a003000c677a69702c64");
-        header.append("65666c61746500a002001e49534f2d38");
-        header.append("3835392d312c7574662d383b713d302e");
-        header.append("372c2a3b713d302e3700000a4b656570");
-        header.append("2d416c69766500000333303000a00600");
-        header.append("0a6b6565702d616c69766500a00d003f");
-        header.append("687474703a2f2f31302e34382e31302e");
-        header.append("322f61646d696e2f496d61676555706c");
-        header.append("6f61642e68746d3f6964303d4974656d");
-        header.append("266964313d32266964323d696d673200");
-        header.append("a00900174a53455353494f4e49443d75");
-        header.append("383977733070696168746d00a0070046");
-        header.append("6d756c7469706172742f666f726d2d64");
-        header.append("6174613b20626f756e646172793d2d2d");
-        header.append("2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d");
-        header.append("2d2d2d2d2d2d2d2d2d39343338333235");
-        header.append("34323630383700a00800033735390000");
-        header.append("0c4d61782d466f727761726473000002");
-        header.append("3130000500176964303d4974656d2669");
-        header.append("64313d32266964323d696d673200ff");
-
-        return header.toString();
-
-    }
-
-    private String getTestShortBody()
-    {
-        StringBuffer body=new StringBuffer("");
-
-        body.append("123402f702f52d2d2d2d2d2d2d2d");
-        body.append("2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d");
-        body.append("2d2d2d2d2d3934333833323534323630");
-        body.append("38370d0a436f6e74656e742d44697370");
-        body.append("6f736974696f6e3a20666f726d2d6461");
-        body.append("74613b206e616d653d227265636f7264");
-        body.append("4964220d0a0d0a320d0a2d2d2d2d2d2d");
-        body.append("2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d");
-        body.append("2d2d2d2d2d2d2d393433383332353432");
-        body.append("363038370d0a436f6e74656e742d4469");
-        body.append("73706f736974696f6e3a20666f726d2d");
-        body.append("646174613b206e616d653d226e616d65");
-        body.append("220d0a0d0a4974656d0d0a2d2d2d2d2d");
-        body.append("2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d");
-        body.append("2d2d2d2d2d2d2d2d3934333833323534");
-        body.append("32363038370d0a436f6e74656e742d44");
-        body.append("6973706f736974696f6e3a20666f726d");
-        body.append("2d646174613b206e616d653d22746e49");
-        body.append("6d674964220d0a0d0a696d67320d0a2d");
-        body.append("2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d");
-        body.append("2d2d2d2d2d2d2d2d2d2d2d2d39343338");
-        body.append("3332353432363038370d0a436f6e7465");
-        body.append("6e742d446973706f736974696f6e3a20");
-        body.append("666f726d2d646174613b206e616d653d");
-        body.append("227468756d624e61696c496d61676546");
-        body.append("696c65223b2066696c656e616d653d22");
-        body.append("6161612e747874220d0a436f6e74656e");
-        body.append("742d547970653a20746578742f706c61");
-        body.append("696e0d0a0d0a61616161616161616161");
-        body.append("61616161616161616161616161616161");
-        body.append("61616161616161616161616161616161");
-        body.append("61616161616161616161616161616161");
-        body.append("0d0a2d2d2d2d2d2d2d2d2d2d2d2d2d2d");
-        body.append("2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d39");
-        body.append("3433383332353432363038370d0a436f");
-        body.append("6e74656e742d446973706f736974696f");
-        body.append("6e3a20666f726d2d646174613b206e61");
-        body.append("6d653d226c61726765496d6167654669");
-        body.append("6c65223b2066696c656e616d653d2261");
-        body.append("61612e747874220d0a436f6e74656e74");
-        body.append("2d547970653a20746578742f706c6169");
-        body.append("6e0d0a0d0a6161616161616161616161");
-        body.append("61616161616161616161616161616161");
-        body.append("61616161616161616161616161616161");
-        body.append("6161616161616161616161616161610d");
-        body.append("0a2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d");
-        body.append("2d2d2d2d2d2d2d2d2d2d2d2d2d2d3934");
-        body.append("33383332353432363038372d2d");
-
-        return body.toString();
-
-    }
-
-    private String getTestTinyBody()
-    {
-        StringBuffer body = new StringBuffer("");
-
-        body.append("123400042d2d0d0a");
-
-        return  body.toString();
-
-    }
-
-    // TODO: char array instead of string?
-    private String readResponse(Socket _client) throws IOException
-    {
-        ByteArrayOutputStream bout = new ByteArrayOutputStream();
-
-        try
-        {
-            IO.copy(_client.getInputStream(),bout);
-        }
-        catch(SocketTimeoutException e)
-        {
-            LOG.ignore(e);
-        }
-        return bout.toString("utf-8");
-    }
-
-    public static class Handler extends AbstractHandler
-    {
-        public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
-        {
-            baseRequest.setHandled(true);
-            response.setStatus(HttpServletResponse.SC_OK);
-            response.setContentType("text/plain");
-            response.getWriter().println("success");
-        }
-
-    }
-
-}
diff --git a/jetty-ajp/src/test/java/org/eclipse/jetty/ajp/TestAjpParser.java b/jetty-ajp/src/test/java/org/eclipse/jetty/ajp/TestAjpParser.java
deleted file mode 100644
index 15ada85..0000000
--- a/jetty-ajp/src/test/java/org/eclipse/jetty/ajp/TestAjpParser.java
+++ /dev/null
@@ -1,608 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.ajp;
-
-import static junit.framework.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import java.io.IOException;
-
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.ByteArrayBuffer;
-import org.eclipse.jetty.io.ByteArrayEndPoint;
-import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.io.SimpleBuffers;
-import org.eclipse.jetty.util.TypeUtil;
-import org.junit.Test;
-
-public class TestAjpParser
-{
-    @Test
-    public void testPacket1() throws Exception
-    {
-        String packet = "123401070202000f77696474683d20485454502f312e300000122f636f6e74726f6c2f70726f647563742f2200000e3230382e32372e3230332e31323800ffff000c7777772e756c74612e636f6d000050000005a006000a6b6565702d616c69766500a00b000c7777772e756c74612e636f6d00a00e002b4d6f7a696c6c612f342e302028636f6d70617469626c653b20426f726465724d616e6167657220332e302900a0010043696d6167652f6769662c20696d6167652f782d786269746d61702c20696d6167652f6a7065672c20696d6167652f706a7065672c20696d6167652f706d672c202a2 [...]
-        byte[] src = TypeUtil.fromHexString(packet);
-
-        ByteArrayBuffer buffer= new ByteArrayBuffer(Ajp13Packet.MAX_PACKET_SIZE);
-        SimpleBuffers buffers=new SimpleBuffers(buffer,null);
-
-        EndPoint endp = new ByteArrayEndPoint(src,Ajp13Packet.MAX_PACKET_SIZE);
-
-        Ajp13Parser parser = new Ajp13Parser(buffers,endp);
-        parser.setEventHandler(new EH());
-        parser.setGenerator(new Ajp13Generator(buffers,endp));
-
-        parser.parseAvailable();
-
-        assertTrue(true);
-    }
-
-    @Test
-    public void testPacket2() throws Exception
-    {
-        String packet="1234020102020008485454502f312e3100000f2f6363632d7777777777772f61616100000c38382e3838382e38382e383830ffff00116363632e6363636363636363632e636f6d0001bb010009a00b00116363632e6363636363636363632e636f6d00a00e005a4d6f7a696c6c612f352e30202857696e646f77733b20553b2057696e646f7773204e5420352e313b20656e2d55533b2072763a312e382e312e3129204765636b6f2f32303036313230342046697265666f782f322e302e302e3100a0010063746578742f786d6c2c6170706c69636174696f6e2f786d6c2c6170706c69636174696f6e2 [...]
-        byte[] src=TypeUtil.fromHexString(packet);
-        ByteArrayBuffer buffer=new ByteArrayBuffer(Ajp13Packet.MAX_PACKET_SIZE);
-        SimpleBuffers buffers=new SimpleBuffers(buffer,null);
-        EndPoint endp=new ByteArrayEndPoint(src,Ajp13Packet.MAX_PACKET_SIZE);
-        Ajp13Parser parser = new Ajp13Parser(buffers,endp);
-        parser.setEventHandler(new EH());
-        parser.setGenerator(new Ajp13Generator(buffers,endp));
-        parser.parse();
-        assertTrue(true);
-    }
-
-    @Test
-    public void testPacket3() throws Exception
-    {
-        String packet="1234028f02020008485454502f312e3100000d2f666f726d746573742e6a737000000d3139322e3136382e342e31383000ffff00107777772e777265636b6167652e6f726700005000000aa0010063746578742f786d6c2c6170706c69636174696f6e2f786d6c2c6170706c69636174696f6e2f7868746d6c2b786d6c2c746578742f68746d6c3b713d302e392c746578742f706c61696e3b713d302e382c696d6167652f706e672c2a2f2a3b713d302e3500a00200075554462d382c2a00a003000c677a69702c6465666c61746500a004000e656e2d67622c656e3b713d302e3500a006000a6b65657 [...]
-        byte[] src=TypeUtil.fromHexString(packet);
-        ByteArrayBuffer buffer=new ByteArrayBuffer(Ajp13Packet.MAX_PACKET_SIZE);
-        SimpleBuffers buffers=new SimpleBuffers(buffer,null);
-        EndPoint endp=new ByteArrayEndPoint(src,Ajp13Packet.MAX_PACKET_SIZE);
-        Ajp13Parser parser = new Ajp13Parser(buffers,endp);
-        parser.setEventHandler(new EH());
-        parser.setGenerator(new Ajp13Generator(buffers,endp));
-        parser.parse();
-        assertTrue(true);
-    }
-
-    @Test
-    public void testSSLPacketWithIntegerKeySize() throws Exception
-    {
-        String packet = "1234025002020008485454502f312e3100000f2f746573742f64756d702f696e666f00000e3139322e3136382e3130302e343000ffff000c776562746964652d746573740001bb01000ca00b000c776562746964652d7465737400a00e005a4d6f7a696c6c612f352e30202857696e646f77733b20553b2057696e646f7773204e5420352e313b20656e2d55533b2072763a312e382e312e3129204765636b6f2f32303036313230342046697265666f782f322e302e302e3100a0010063746578742f786d6c2c6170706c69636174696f6e2f786d6c2c6170706c69636174696f6e2f7868746d6c2b7 [...]
-        byte[] src = TypeUtil.fromHexString(packet);
-
-        ByteArrayBuffer buffer= new ByteArrayBuffer(Ajp13Packet.MAX_PACKET_SIZE);
-        SimpleBuffers buffers=new SimpleBuffers(buffer,null);
-
-        EndPoint endp = new ByteArrayEndPoint(src,Ajp13Packet.MAX_PACKET_SIZE);
-
-        Ajp13Parser parser = new Ajp13Parser(buffers,endp);
-        parser.setEventHandler(new EH());
-        parser.setGenerator(new Ajp13Generator(buffers,endp));
-
-        parser.parseAvailable();
-
-        assertTrue(true);
-    }
-
-    @Test
-    public void testSSLPacketWithStringKeySize() throws Exception
-    {
-        String packet = "1234025002020008485454502f312e3100000f2f746573742f64756d702f696e666f00000e3139322e3136382e3130302e343000ffff000c776562746964652d746573740001bb01000ca00b000c776562746964652d7465737400a00e005a4d6f7a696c6c612f352e30202857696e646f77733b20553b2057696e646f7773204e5420352e313b20656e2d55533b2072763a312e382e312e3129204765636b6f2f32303036313230342046697265666f782f322e302e302e3100a0010063746578742f786d6c2c6170706c69636174696f6e2f786d6c2c6170706c69636174696f6e2f7868746d6c2b7 [...]
-        byte[] src = TypeUtil.fromHexString(packet);
-
-        ByteArrayBuffer buffer= new ByteArrayBuffer(Ajp13Packet.MAX_PACKET_SIZE);
-        SimpleBuffers buffers=new SimpleBuffers(buffer,null);
-
-        EndPoint endp = new ByteArrayEndPoint(src,Ajp13Packet.MAX_PACKET_SIZE);
-
-        Ajp13Parser parser = new Ajp13Parser(buffers,endp);
-        parser.setEventHandler(new EH());
-        parser.setGenerator(new Ajp13Generator(buffers,endp));
-
-        parser.parseAvailable();
-
-        assertTrue(true);
-    }
-
-    @Test
-    public void testSSLPacketFragment() throws Exception
-    {
-        String packet = "1234025002020008485454502f312e3100000f2f746573742f64756d702f696e666f00000e3139322e3136382e3130302e343000ffff000c776562746964652d746573740001bb01000ca00b000c776562746964652d7465737400a00e005a4d6f7a696c6c612f352e30202857696e646f77733b20553b2057696e646f7773204e5420352e313b20656e2d55533b2072763a312e382e312e3129204765636b6f2f32303036313230342046697265666f782f322e302e302e3100a0010063746578742f786d6c2c6170706c69636174696f6e2f786d6c2c6170706c69636174696f6e2f7868746d6c2b7 [...]
-        byte[] src = TypeUtil.fromHexString(packet);
-
-        for (int f=1;f<src.length;f++)
-        {
-            byte[] frag0=new byte[src.length-f];
-            byte[] frag1=new byte[f];
-
-            System.arraycopy(src,0,frag0,0,src.length-f);
-            System.arraycopy(src,src.length-f,frag1,0,f);
-
-            ByteArrayBuffer buffer= new ByteArrayBuffer(Ajp13Packet.MAX_PACKET_SIZE);
-            SimpleBuffers buffers=new SimpleBuffers(buffer,null);
-
-            ByteArrayEndPoint endp = new ByteArrayEndPoint(frag0,Ajp13Packet.MAX_PACKET_SIZE);
-            endp.setNonBlocking(true);
-
-            Ajp13Parser parser = new Ajp13Parser(buffers,endp);
-            parser.setEventHandler(new EH());
-            parser.setGenerator(new Ajp13Generator(buffers,endp));
-            parser.parseNext();
-
-            endp.setIn(new ByteArrayBuffer(frag1));
-            parser.parseAvailable();
-        }
-
-        assertTrue(true);
-    }
-
-    @Test
-    public void testPacketWithBody() throws Exception
-    {
-        String packet=getTestHeader();
-        byte[] src=TypeUtil.fromHexString(packet);
-        ByteArrayBuffer buffer=new ByteArrayBuffer(Ajp13Packet.MAX_PACKET_SIZE);
-        SimpleBuffers buffers=new SimpleBuffers(buffer,null);
-        ByteArrayEndPoint endp=new ByteArrayEndPoint(src,Ajp13Packet.MAX_PACKET_SIZE);
-        endp.setNonBlocking(true);
-
-        final int count[]={0};
-        Ajp13Generator gen = new Ajp13Generator(buffers,endp)
-        {
-            public void getBodyChunk() throws IOException
-            {
-                count[0]++;
-                super.getBodyChunk();
-            }
-        };
-        Ajp13Parser parser = new Ajp13Parser(buffers,endp);
-        parser.setEventHandler(new EH());
-        parser.setGenerator(gen);
-
-        parser.parseNext();
-        assertEquals(1,parser.getState());
-        assertEquals(0,count[0]);
-
-        endp.setIn(new ByteArrayBuffer(TypeUtil.fromHexString(getTestShortBody())));
-
-        parser.parseNext();
-        assertEquals(1,parser.getState());
-        assertEquals(1,count[0]);
-
-        endp.setIn(new ByteArrayBuffer(TypeUtil.fromHexString(getTestTinyBody())));
-
-        parser.parseNext();
-        parser.parseNext();
-        assertEquals(0,parser.getState());
-        assertEquals(1,count[0]);
-
-        assertTrue(true);
-    }
-
-    @Test
-    public void testPacketWithChunkedBody() throws Exception
-    {
-        String packet="123400ff02040008485454502f312e3100000f2f746573742f64756d702f696e666f0000093132372e302e302e3100ffff00096c6f63616c686f7374000050000007a00e000d4a6176612f312e352e305f313100a00b00096c6f63616c686f737400a0010034746578742f68746d6c2c20696d6167652f6769662c20696d6167652f6a7065672c202a3b20713d2e322c202a2f2a3b20713d2e3200a006000a6b6565702d616c69766500a00700216170706c69636174696f6e2f782d7777772d666f726d2d75726c656e636f6465640000115472616e736665722d456e636f64696e670000076368756e6 [...]
-        byte[] src=TypeUtil.fromHexString(packet);
-        ByteArrayBuffer buffer=new ByteArrayBuffer(Ajp13Packet.MAX_PACKET_SIZE);
-        SimpleBuffers buffers=new SimpleBuffers(buffer,null);
-        ByteArrayEndPoint endp=new ByteArrayEndPoint(src,Ajp13Packet.MAX_PACKET_SIZE);
-        endp.setNonBlocking(true);
-
-        final int count[]={0};
-        Ajp13Generator gen = new Ajp13Generator(buffers,endp)
-        {
-            public void getBodyChunk() throws IOException
-            {
-                count[0]++;
-                super.getBodyChunk();
-            }
-        };
-        Ajp13Parser parser = new Ajp13Parser(buffers,endp);
-        parser.setEventHandler(new EH());
-        parser.setGenerator(gen);
-
-        parser.parseNext();
-        assertEquals(1,parser.getState());
-        assertEquals(1,count[0]);
-
-        endp.setIn(new ByteArrayBuffer(TypeUtil.fromHexString("1234007e007c7468656e616d653d746865253230717569636b25323062726f776e253230666f782532306a756d70732532306f766572253230746f2532307468652532306c617a79253230646f67253230544845253230515549434b25323042524f574e253230464f582532304a554d50532532304f564552253230544f25323054")));
-
-        while (parser.parseNext()>0);
-        assertEquals(1,parser.getState());
-        assertEquals(2,count[0]);
-
-        endp.setIn(new ByteArrayBuffer(TypeUtil.fromHexString("12340042004048452532304c415a59253230444f472532302676616c75656f66323d6162636465666768696a6b6c6d6e6f707172737475767778797a31323334353637383930")));
-
-        while (parser.parseNext()>0);
-        assertEquals(1,parser.getState());
-        assertEquals(3,count[0]);
-
-        endp.setIn(new ByteArrayBuffer(TypeUtil.fromHexString("123400020000")));
-
-        while (parser.getState()!=0 && parser.parseNext()>0);
-        assertEquals(0,parser.getState());
-        assertEquals(3,count[0]);
-
-        assertTrue(true);
-    }
-
-    @Test
-    public void testPacketFragment() throws Exception
-    {
-        String packet = "123401070202000f77696474683d20485454502f312e300000122f636f6e74726f6c2f70726f647563742f2200000e3230382e32372e3230332e31323800ffff000c7777772e756c74612e636f6d000050000005a006000a6b6565702d616c69766500a00b000c7777772e756c74612e636f6d00a00e002b4d6f7a696c6c612f342e302028636f6d70617469626c653b20426f726465724d616e6167657220332e302900a0010043696d6167652f6769662c20696d6167652f782d786269746d61702c20696d6167652f6a7065672c20696d6167652f706a7065672c20696d6167652f706d672c202a2 [...]
-        byte[] src = TypeUtil.fromHexString(packet);
-
-        for (int f=1;f<src.length;f++)
-        {
-            byte[] frag0=new byte[src.length-f];
-            byte[] frag1=new byte[f];
-
-            System.arraycopy(src,0,frag0,0,src.length-f);
-            System.arraycopy(src,src.length-f,frag1,0,f);
-
-            ByteArrayBuffer buffer= new ByteArrayBuffer(Ajp13Packet.MAX_PACKET_SIZE);
-            SimpleBuffers buffers=new SimpleBuffers(buffer,null);
-
-            ByteArrayEndPoint endp = new ByteArrayEndPoint(frag0,Ajp13Packet.MAX_PACKET_SIZE);
-            endp.setNonBlocking(true);
-
-            Ajp13Parser parser = new Ajp13Parser(buffers,endp);
-            parser.setEventHandler(new EH());
-            parser.setGenerator(new Ajp13Generator(buffers,endp));
-            parser.parseNext();
-
-            endp.setIn(new ByteArrayBuffer(frag1));
-            parser.parseAvailable();
-        }
-
-        assertTrue(true);
-    }
-
-    @Test
-    public void testPacketFragmentWithBody() throws Exception
-    {
-        String packet = getTestHeader()+getTestBody();
-        byte[] src = TypeUtil.fromHexString(packet);
-
-        for (int f=1;f<src.length;f++)
-        {
-            byte[] frag0=new byte[src.length-f];
-            byte[] frag1=new byte[f];
-
-            System.arraycopy(src,0,frag0,0,src.length-f);
-            System.arraycopy(src,src.length-f,frag1,0,f);
-
-            ByteArrayBuffer buffer= new ByteArrayBuffer(Ajp13Packet.MAX_PACKET_SIZE);
-            SimpleBuffers buffers=new SimpleBuffers(buffer,null);
-
-            ByteArrayEndPoint endp = new ByteArrayEndPoint(frag0,Ajp13Packet.MAX_PACKET_SIZE);
-            endp.setNonBlocking(true);
-
-            Ajp13Parser parser = new Ajp13Parser(buffers,endp);
-            parser.setEventHandler(new EH());
-            parser.setGenerator(new Ajp13Generator(buffers,endp));
-            parser.parseNext();
-
-            endp.setIn(new ByteArrayBuffer(frag1));
-            parser.parseAvailable();
-        }
-
-        assertTrue(true);
-    }
-
-    private String getTestHeader()
-    {
-        StringBuffer header = new StringBuffer("");
-
-        header.append("1234026902040008485454502f31");
-        header.append("2e310000162f61646d696e2f496d6167");
-        header.append("6555706c6f61642e68746d00000a3130");
-        header.append("2e34382e31302e3100ffff000a31302e");
-        header.append("34382e31302e3200005000000da00b00");
-        header.append("0a31302e34382e31302e3200a00e005a");
-        header.append("4d6f7a696c6c612f352e30202857696e");
-        header.append("646f77733b20553b2057696e646f7773");
-        header.append("204e5420352e313b20656e2d55533b20");
-        header.append("72763a312e382e312e3129204765636b");
-        header.append("6f2f3230303631323034204669726566");
-        header.append("6f782f322e302e302e3100a001006374");
-        header.append("6578742f786d6c2c6170706c69636174");
-        header.append("696f6e2f786d6c2c6170706c69636174");
-        header.append("696f6e2f7868746d6c2b786d6c2c7465");
-        header.append("78742f68746d6c3b713d302e392c7465");
-        header.append("78742f706c61696e3b713d302e382c69");
-        header.append("6d6167652f706e672c2a2f2a3b713d30");
-        header.append("2e3500a004000e656e2d75732c656e3b");
-        header.append("713d302e3500a003000c677a69702c64");
-        header.append("65666c61746500a002001e49534f2d38");
-        header.append("3835392d312c7574662d383b713d302e");
-        header.append("372c2a3b713d302e3700000a4b656570");
-        header.append("2d416c69766500000333303000a00600");
-        header.append("0a6b6565702d616c69766500a00d003f");
-        header.append("687474703a2f2f31302e34382e31302e");
-        header.append("322f61646d696e2f496d61676555706c");
-        header.append("6f61642e68746d3f6964303d4974656d");
-        header.append("266964313d32266964323d696d673200");
-        header.append("a00900174a53455353494f4e49443d75");
-        header.append("383977733070696168746d00a0070046");
-        header.append("6d756c7469706172742f666f726d2d64");
-        header.append("6174613b20626f756e646172793d2d2d");
-        header.append("2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d");
-        header.append("2d2d2d2d2d2d2d2d2d39343338333235");
-        header.append("34323630383700a00800033735390000");
-        header.append("0c4d61782d466f727761726473000002");
-        header.append("3130000500176964303d4974656d2669");
-        header.append("64313d32266964323d696d673200ff");
-
-        return header.toString();
-    }
-
-    private String getTestBody()
-    {
-        StringBuffer body = new StringBuffer("");
-
-        body.append("123402f902f72d2d2d2d2d2d2d2d");
-        body.append("2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d");
-        body.append("2d2d2d2d2d3934333833323534323630");
-        body.append("38370d0a436f6e74656e742d44697370");
-        body.append("6f736974696f6e3a20666f726d2d6461");
-        body.append("74613b206e616d653d227265636f7264");
-        body.append("4964220d0a0d0a320d0a2d2d2d2d2d2d");
-        body.append("2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d");
-        body.append("2d2d2d2d2d2d2d393433383332353432");
-        body.append("363038370d0a436f6e74656e742d4469");
-        body.append("73706f736974696f6e3a20666f726d2d");
-        body.append("646174613b206e616d653d226e616d65");
-        body.append("220d0a0d0a4974656d0d0a2d2d2d2d2d");
-        body.append("2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d");
-        body.append("2d2d2d2d2d2d2d2d3934333833323534");
-        body.append("32363038370d0a436f6e74656e742d44");
-        body.append("6973706f736974696f6e3a20666f726d");
-        body.append("2d646174613b206e616d653d22746e49");
-        body.append("6d674964220d0a0d0a696d67320d0a2d");
-        body.append("2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d");
-        body.append("2d2d2d2d2d2d2d2d2d2d2d2d39343338");
-        body.append("3332353432363038370d0a436f6e7465");
-        body.append("6e742d446973706f736974696f6e3a20");
-        body.append("666f726d2d646174613b206e616d653d");
-        body.append("227468756d624e61696c496d61676546");
-        body.append("696c65223b2066696c656e616d653d22");
-        body.append("6161612e747874220d0a436f6e74656e");
-        body.append("742d547970653a20746578742f706c61");
-        body.append("696e0d0a0d0a61616161616161616161");
-        body.append("61616161616161616161616161616161");
-        body.append("61616161616161616161616161616161");
-        body.append("61616161616161616161616161616161");
-        body.append("0d0a2d2d2d2d2d2d2d2d2d2d2d2d2d2d");
-        body.append("2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d39");
-        body.append("3433383332353432363038370d0a436f");
-        body.append("6e74656e742d446973706f736974696f");
-        body.append("6e3a20666f726d2d646174613b206e61");
-        body.append("6d653d226c61726765496d6167654669");
-        body.append("6c65223b2066696c656e616d653d2261");
-        body.append("61612e747874220d0a436f6e74656e74");
-        body.append("2d547970653a20746578742f706c6169");
-        body.append("6e0d0a0d0a6161616161616161616161");
-        body.append("61616161616161616161616161616161");
-        body.append("61616161616161616161616161616161");
-        body.append("6161616161616161616161616161610d");
-        body.append("0a2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d");
-        body.append("2d2d2d2d2d2d2d2d2d2d2d2d2d2d3934");
-        body.append("33383332353432363038372d2d0d0a");
-
-        return  body.toString();
-    }
-
-
-    private String getTestShortBody()
-    {
-        StringBuffer body = new StringBuffer("");
-
-        body.append("123402f702f52d2d2d2d2d2d2d2d");
-        body.append("2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d");
-        body.append("2d2d2d2d2d3934333833323534323630");
-        body.append("38370d0a436f6e74656e742d44697370");
-        body.append("6f736974696f6e3a20666f726d2d6461");
-        body.append("74613b206e616d653d227265636f7264");
-        body.append("4964220d0a0d0a320d0a2d2d2d2d2d2d");
-        body.append("2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d");
-        body.append("2d2d2d2d2d2d2d393433383332353432");
-        body.append("363038370d0a436f6e74656e742d4469");
-        body.append("73706f736974696f6e3a20666f726d2d");
-        body.append("646174613b206e616d653d226e616d65");
-        body.append("220d0a0d0a4974656d0d0a2d2d2d2d2d");
-        body.append("2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d");
-        body.append("2d2d2d2d2d2d2d2d3934333833323534");
-        body.append("32363038370d0a436f6e74656e742d44");
-        body.append("6973706f736974696f6e3a20666f726d");
-        body.append("2d646174613b206e616d653d22746e49");
-        body.append("6d674964220d0a0d0a696d67320d0a2d");
-        body.append("2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d");
-        body.append("2d2d2d2d2d2d2d2d2d2d2d2d39343338");
-        body.append("3332353432363038370d0a436f6e7465");
-        body.append("6e742d446973706f736974696f6e3a20");
-        body.append("666f726d2d646174613b206e616d653d");
-        body.append("227468756d624e61696c496d61676546");
-        body.append("696c65223b2066696c656e616d653d22");
-        body.append("6161612e747874220d0a436f6e74656e");
-        body.append("742d547970653a20746578742f706c61");
-        body.append("696e0d0a0d0a61616161616161616161");
-        body.append("61616161616161616161616161616161");
-        body.append("61616161616161616161616161616161");
-        body.append("61616161616161616161616161616161");
-        body.append("0d0a2d2d2d2d2d2d2d2d2d2d2d2d2d2d");
-        body.append("2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d39");
-        body.append("3433383332353432363038370d0a436f");
-        body.append("6e74656e742d446973706f736974696f");
-        body.append("6e3a20666f726d2d646174613b206e61");
-        body.append("6d653d226c61726765496d6167654669");
-        body.append("6c65223b2066696c656e616d653d2261");
-        body.append("61612e747874220d0a436f6e74656e74");
-        body.append("2d547970653a20746578742f706c6169");
-        body.append("6e0d0a0d0a6161616161616161616161");
-        body.append("61616161616161616161616161616161");
-        body.append("61616161616161616161616161616161");
-        body.append("6161616161616161616161616161610d");
-        body.append("0a2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d");
-        body.append("2d2d2d2d2d2d2d2d2d2d2d2d2d2d3934");
-        body.append("33383332353432363038372d2d");
-
-        return  body.toString();
-    }
-    private String getTestTinyBody()
-    {
-        StringBuffer body = new StringBuffer("");
-
-        body.append("123400042d2d0d0a");
-
-        return  body.toString();
-    }
-
-    private static class EH implements Ajp13Parser.EventHandler
-    {
-        final boolean debug=false;
-        
-        public void content(Buffer ref) throws IOException
-        {
-            if (debug) System.err.println(ref);
-        }
-
-        public void headerComplete() throws IOException
-        {
-            if (debug) System.err.println("--");
-        }
-
-        public void messageComplete(long contextLength) throws IOException
-        {
-            if (debug) System.err.println("==");
-        }
-
-        public void parsedHeader(Buffer name, Buffer value) throws IOException
-        {
-            if (debug) System.err.println(name+": "+value);
-        }
-
-        public void parsedMethod(Buffer method) throws IOException
-        {
-            if (debug) System.err.println(method);
-        }
-
-        public void parsedProtocol(Buffer protocol) throws IOException
-        {
-            if (debug) System.err.println(protocol);
-
-        }
-
-        public void parsedQueryString(Buffer value) throws IOException
-        {
-            if (debug) System.err.println("?"+value);
-        }
-
-        public void parsedRemoteAddr(Buffer addr) throws IOException
-        {
-            if (debug) System.err.println("addr="+addr);
-
-        }
-
-        public void parsedRemoteHost(Buffer host) throws IOException
-        {
-            if (debug) System.err.println("host="+host);
-
-        }
-
-        public void parsedRequestAttribute(String key, Buffer value) throws IOException
-        {
-            if (debug) System.err.println(key+":: "+value);
-        }
-
-        public void parsedServerName(Buffer name) throws IOException
-        {
-            if (debug) System.err.println("Server:: "+name);
-        }
-
-        public void parsedServerPort(int port) throws IOException
-        {
-            if (debug) System.err.println("Port:: "+port);
-        }
-
-        public void parsedSslSecure(boolean secure) throws IOException
-        {
-            if (debug) System.err.println("Secure:: "+secure);
-        }
-
-        public void parsedUri(Buffer uri) throws IOException
-        {
-            if (debug) System.err.println("URI:: "+uri);
-        }
-
-        public void startForwardRequest() throws IOException
-        {
-            if (debug) System.err.println("..");
-        }
-
-        public void parsedAuthorizationType(Buffer authType) throws IOException
-        {
-            if (debug) System.err.println("auth:: "+authType);
-        }
-
-        public void parsedRemoteUser(Buffer remoteUser) throws IOException
-        {
-            if (debug) System.err.println("user:: "+remoteUser);
-        }
-
-        public void parsedServletPath(Buffer servletPath) throws IOException
-        {
-            if (debug) System.err.println("servletPath:: "+servletPath);
-        }
-
-        public void parsedContextPath(Buffer context) throws IOException
-        {
-            if (debug) System.err.println("Context:: "+context);
-        }
-
-        public void parsedSslCert(Buffer sslCert) throws IOException
-        {
-            if (debug) System.err.println("sslCert:: "+sslCert);
-        }
-
-        public void parsedSslCipher(Buffer sslCipher) throws IOException
-        {
-            if (debug) System.err.println("sslCipher:: "+sslCipher);
-        }
-
-        public void parsedSslSession(Buffer sslSession) throws IOException
-        {
-            if (debug) System.err.println("sslSession:: "+sslSession);
-        }
-
-        public void parsedSslKeySize(int keySize) throws IOException
-        {
-            if (debug) System.err.println("sslkeysize:: "+keySize);
-        }
-
-        public void parsedRequestAttribute(String key, int value) throws IOException
-        {
-            if (debug) System.err.println(key+":: "+value);
-        }
-    }
-}
diff --git a/jetty-alpn/jetty-alpn-client/pom.xml b/jetty-alpn/jetty-alpn-client/pom.xml
new file mode 100644
index 0000000..cae3b13
--- /dev/null
+++ b/jetty-alpn/jetty-alpn-client/pom.xml
@@ -0,0 +1,76 @@
+<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">
+  <parent>
+    <groupId>org.eclipse.jetty</groupId>
+    <artifactId>jetty-alpn-parent</artifactId>
+    <version>9.2.13.v20150730</version>
+  </parent>
+  <modelVersion>4.0.0</modelVersion>
+  <artifactId>jetty-alpn-client</artifactId>
+  <name>Jetty :: ALPN Client</name>
+  <description>Jetty ALPN client services</description>
+  <url>http://www.eclipse.org/jetty</url>
+  <properties>
+    <bundle-symbolic-name>${project.groupId}.alpn.client</bundle-symbolic-name>
+  </properties>
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>maven-bundle-plugin</artifactId>
+        <extensions>true</extensions>
+        <executions>
+          <execution>
+            <goals>
+              <goal>manifest</goal>
+            </goals>
+            <configuration>
+              <instructions>
+               <Import-Package>org.eclipse.jetty.alpn;resolution:=optional</Import-Package>
+              </instructions>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-jar-plugin</artifactId>
+        <configuration>
+          <archive>
+            <manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
+          </archive>
+        </configuration>
+      </plugin>
+      <!-- always include the sources to be able to prepare the eclipse-jetty-SDK feature
+      with a snapshot. -->
+      <plugin>
+         <groupId>org.apache.maven.plugins</groupId>
+         <artifactId>maven-source-plugin</artifactId>
+      </plugin>
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>findbugs-maven-plugin</artifactId>
+        <configuration>
+          <onlyAnalyze>org.eclipse.jetty.alpn.*</onlyAnalyze>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+  <dependencies>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-io</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.alpn</groupId>
+      <artifactId>alpn-api</artifactId>
+      <version>${alpn.api.version}</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.toolchain</groupId>
+      <artifactId>jetty-test-helper</artifactId>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/jetty-alpn/jetty-alpn-client/src/main/java/org/eclipse/jetty/alpn/client/ALPNClientConnection.java b/jetty-alpn/jetty-alpn-client/src/main/java/org/eclipse/jetty/alpn/client/ALPNClientConnection.java
new file mode 100644
index 0000000..7f82a28
--- /dev/null
+++ b/jetty-alpn/jetty-alpn-client/src/main/java/org/eclipse/jetty/alpn/client/ALPNClientConnection.java
@@ -0,0 +1,81 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.alpn.client;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Executor;
+import javax.net.ssl.SSLEngine;
+
+import org.eclipse.jetty.alpn.ALPN;
+import org.eclipse.jetty.io.ClientConnectionFactory;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.NegotiatingClientConnection;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public class ALPNClientConnection extends NegotiatingClientConnection implements ALPN.ClientProvider
+{
+    private static final Logger LOG = Log.getLogger(ALPNClientConnection.class);
+
+    private final String protocol;
+
+    public ALPNClientConnection(EndPoint endPoint, Executor executor, ClientConnectionFactory connectionFactory, SSLEngine sslEngine, Map<String, Object> context, String protocol)
+    {
+        super(endPoint, executor, sslEngine, connectionFactory, context);
+        this.protocol = protocol;
+        ALPN.put(sslEngine, this);
+    }
+
+    @Override
+    public void unsupported()
+    {
+        ALPN.remove(getSSLEngine());
+        completed();
+    }
+
+    @Override
+    public List<String> protocols()
+    {
+        return Arrays.asList(protocol);
+    }
+
+    @Override
+    public void selected(String protocol)
+    {
+        if (this.protocol.equals(protocol))
+        {
+            ALPN.remove(getSSLEngine());
+            completed();
+        }
+        else
+        {
+            LOG.info("Could not negotiate protocol: server {} - client {}", protocol, this.protocol);
+            close();
+        }
+    }
+
+    @Override
+    public void close()
+    {
+        ALPN.remove(getSSLEngine());
+        super.close();
+    }
+}
diff --git a/jetty-alpn/jetty-alpn-client/src/main/java/org/eclipse/jetty/alpn/client/ALPNClientConnectionFactory.java b/jetty-alpn/jetty-alpn-client/src/main/java/org/eclipse/jetty/alpn/client/ALPNClientConnectionFactory.java
new file mode 100644
index 0000000..26bf4ba
--- /dev/null
+++ b/jetty-alpn/jetty-alpn-client/src/main/java/org/eclipse/jetty/alpn/client/ALPNClientConnectionFactory.java
@@ -0,0 +1,51 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.alpn.client;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.concurrent.Executor;
+
+import javax.net.ssl.SSLEngine;
+
+import org.eclipse.jetty.io.ClientConnectionFactory;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.NegotiatingClientConnectionFactory;
+import org.eclipse.jetty.io.ssl.SslClientConnectionFactory;
+
+public class ALPNClientConnectionFactory extends NegotiatingClientConnectionFactory
+{
+    private final Executor executor;
+    private final String protocol;
+
+    public ALPNClientConnectionFactory(Executor executor, ClientConnectionFactory connectionFactory, String protocol)
+    {
+        super(connectionFactory);
+        this.executor = executor;
+        this.protocol = protocol;
+    }
+
+    @Override
+    public Connection newConnection(EndPoint endPoint, Map<String, Object> context) throws IOException
+    {
+        return new ALPNClientConnection(endPoint, executor, getClientConnectionFactory(),
+                (SSLEngine)context.get(SslClientConnectionFactory.SSL_ENGINE_CONTEXT_KEY), context, protocol);
+    }
+}
diff --git a/jetty-alpn/jetty-alpn-server/pom.xml b/jetty-alpn/jetty-alpn-server/pom.xml
new file mode 100644
index 0000000..38c233b
--- /dev/null
+++ b/jetty-alpn/jetty-alpn-server/pom.xml
@@ -0,0 +1,94 @@
+<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">
+  <parent>
+    <groupId>org.eclipse.jetty</groupId>
+    <artifactId>jetty-alpn-parent</artifactId>
+    <version>9.2.13.v20150730</version>
+  </parent>
+  <modelVersion>4.0.0</modelVersion>
+  <artifactId>jetty-alpn-server</artifactId>
+  <name>Jetty :: ALPN Server</name>
+  <description>Jetty ALPN server services</description>
+  <url>http://www.eclipse.org/jetty</url>
+  <properties>
+    <bundle-symbolic-name>${project.groupId}.alpn.server</bundle-symbolic-name>
+  </properties>
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>maven-bundle-plugin</artifactId>
+        <extensions>true</extensions>
+        <executions>
+          <execution>
+            <goals>
+              <goal>manifest</goal>
+            </goals>
+            <configuration>
+              <instructions>
+               <Import-Package>org.eclipse.jetty.alpn,*</Import-Package>
+               <_nouses>true</_nouses>
+              </instructions>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-jar-plugin</artifactId>
+        <configuration>
+          <archive>
+            <manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
+          </archive>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-assembly-plugin</artifactId>
+        <executions>
+          <execution>
+            <phase>package</phase>
+            <goals>
+              <goal>single</goal>
+            </goals>
+            <configuration>
+              <descriptorRefs>
+                <descriptorRef>config</descriptorRef>
+              </descriptorRefs>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+      <!-- always include the sources to be able to prepare the eclipse-jetty-SDK feature
+      with a snapshot. -->
+      <plugin>
+         <groupId>org.apache.maven.plugins</groupId>
+         <artifactId>maven-source-plugin</artifactId>
+      </plugin>
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>findbugs-maven-plugin</artifactId>
+        <configuration>
+          <onlyAnalyze>org.eclipse.jetty.alpn.*</onlyAnalyze>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+  <dependencies>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-server</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.alpn</groupId>
+      <artifactId>alpn-api</artifactId>
+      <version>${alpn.api.version}</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.toolchain</groupId>
+      <artifactId>jetty-test-helper</artifactId>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/jetty-alpn/jetty-alpn-server/src/main/config/etc/protonego-alpn.xml b/jetty-alpn/jetty-alpn-server/src/main/config/etc/protonego-alpn.xml
new file mode 100644
index 0000000..5d2ac7a
--- /dev/null
+++ b/jetty-alpn/jetty-alpn-server/src/main/config/etc/protonego-alpn.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
+
+<Configure id="protonego" class="org.eclipse.jetty.alpn.server.ALPNServerConnectionFactory">
+    <Arg name="protocols">
+        <Array type="String">
+            <Item>spdy/3</Item>
+            <Item>spdy/2</Item>
+            <Item>http/1.1</Item>
+        </Array>
+    </Arg>
+
+    <Set name="defaultProtocol">http/1.1</Set>
+
+    <!-- Enables NPN debugging on System.err -->
+    <!--<Set class="org.eclipse.jetty.alpn.ALPN" name="debug" type="boolean">true</Set>-->
+
+</Configure>
+
diff --git a/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.7.0_40.mod b/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.7.0_40.mod
new file mode 100644
index 0000000..54d3731
--- /dev/null
+++ b/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.7.0_40.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/7.1.0.v20141016/alpn-boot-7.1.0.v20141016.jar|lib/alpn/alpn-boot-7.1.0.v20141016.jar
+
+[exec]
+-Xbootclasspath/p:lib/alpn/alpn-boot-7.1.0.v20141016.jar
diff --git a/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.7.0_45.mod b/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.7.0_45.mod
new file mode 100644
index 0000000..54d3731
--- /dev/null
+++ b/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.7.0_45.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/7.1.0.v20141016/alpn-boot-7.1.0.v20141016.jar|lib/alpn/alpn-boot-7.1.0.v20141016.jar
+
+[exec]
+-Xbootclasspath/p:lib/alpn/alpn-boot-7.1.0.v20141016.jar
diff --git a/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.7.0_51.mod b/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.7.0_51.mod
new file mode 100644
index 0000000..54d3731
--- /dev/null
+++ b/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.7.0_51.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/7.1.0.v20141016/alpn-boot-7.1.0.v20141016.jar|lib/alpn/alpn-boot-7.1.0.v20141016.jar
+
+[exec]
+-Xbootclasspath/p:lib/alpn/alpn-boot-7.1.0.v20141016.jar
diff --git a/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.7.0_55.mod b/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.7.0_55.mod
new file mode 100644
index 0000000..54d3731
--- /dev/null
+++ b/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.7.0_55.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/7.1.0.v20141016/alpn-boot-7.1.0.v20141016.jar|lib/alpn/alpn-boot-7.1.0.v20141016.jar
+
+[exec]
+-Xbootclasspath/p:lib/alpn/alpn-boot-7.1.0.v20141016.jar
diff --git a/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.7.0_60.mod b/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.7.0_60.mod
new file mode 100644
index 0000000..54d3731
--- /dev/null
+++ b/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.7.0_60.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/7.1.0.v20141016/alpn-boot-7.1.0.v20141016.jar|lib/alpn/alpn-boot-7.1.0.v20141016.jar
+
+[exec]
+-Xbootclasspath/p:lib/alpn/alpn-boot-7.1.0.v20141016.jar
diff --git a/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.7.0_65.mod b/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.7.0_65.mod
new file mode 100644
index 0000000..54d3731
--- /dev/null
+++ b/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.7.0_65.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/7.1.0.v20141016/alpn-boot-7.1.0.v20141016.jar|lib/alpn/alpn-boot-7.1.0.v20141016.jar
+
+[exec]
+-Xbootclasspath/p:lib/alpn/alpn-boot-7.1.0.v20141016.jar
diff --git a/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.7.0_67.mod b/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.7.0_67.mod
new file mode 100644
index 0000000..54d3731
--- /dev/null
+++ b/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.7.0_67.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/7.1.0.v20141016/alpn-boot-7.1.0.v20141016.jar|lib/alpn/alpn-boot-7.1.0.v20141016.jar
+
+[exec]
+-Xbootclasspath/p:lib/alpn/alpn-boot-7.1.0.v20141016.jar
diff --git a/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.7.0_71.mod b/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.7.0_71.mod
new file mode 100644
index 0000000..e9b4e2a
--- /dev/null
+++ b/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.7.0_71.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/7.1.2.v20141202/alpn-boot-7.1.2.v20141202.jar|lib/alpn/alpn-boot-7.1.2.v20141202.jar
+
+[exec]
+-Xbootclasspath/p:lib/alpn/alpn-boot-7.1.2.v20141202.jar
diff --git a/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.7.0_72.mod b/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.7.0_72.mod
new file mode 100644
index 0000000..e9b4e2a
--- /dev/null
+++ b/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.7.0_72.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/7.1.2.v20141202/alpn-boot-7.1.2.v20141202.jar|lib/alpn/alpn-boot-7.1.2.v20141202.jar
+
+[exec]
+-Xbootclasspath/p:lib/alpn/alpn-boot-7.1.2.v20141202.jar
diff --git a/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.7.0_75.mod b/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.7.0_75.mod
new file mode 100644
index 0000000..ac315d6
--- /dev/null
+++ b/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.7.0_75.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/7.1.3.v20150130/alpn-boot-7.1.3.v20150130.jar|lib/alpn/alpn-boot-7.1.3.v20150130.jar
+
+[exec]
+-Xbootclasspath/p:lib/alpn/alpn-boot-7.1.3.v20150130.jar
diff --git a/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.7.0_76.mod b/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.7.0_76.mod
new file mode 100644
index 0000000..ac315d6
--- /dev/null
+++ b/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.7.0_76.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/7.1.3.v20150130/alpn-boot-7.1.3.v20150130.jar|lib/alpn/alpn-boot-7.1.3.v20150130.jar
+
+[exec]
+-Xbootclasspath/p:lib/alpn/alpn-boot-7.1.3.v20150130.jar
diff --git a/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.7.0_79.mod b/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.7.0_79.mod
new file mode 100644
index 0000000..ac315d6
--- /dev/null
+++ b/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.7.0_79.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/7.1.3.v20150130/alpn-boot-7.1.3.v20150130.jar|lib/alpn/alpn-boot-7.1.3.v20150130.jar
+
+[exec]
+-Xbootclasspath/p:lib/alpn/alpn-boot-7.1.3.v20150130.jar
diff --git a/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.7.0_80.mod b/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.7.0_80.mod
new file mode 100644
index 0000000..ac315d6
--- /dev/null
+++ b/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.7.0_80.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/7.1.3.v20150130/alpn-boot-7.1.3.v20150130.jar|lib/alpn/alpn-boot-7.1.3.v20150130.jar
+
+[exec]
+-Xbootclasspath/p:lib/alpn/alpn-boot-7.1.3.v20150130.jar
diff --git a/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.8.0.mod b/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.8.0.mod
new file mode 100644
index 0000000..a81732c
--- /dev/null
+++ b/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.8.0.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/8.1.0.v20141016/alpn-boot-8.1.0.v20141016.jar|lib/alpn/alpn-boot-8.1.0.v20141016.jar
+
+[exec]
+-Xbootclasspath/p:lib/alpn/alpn-boot-8.1.0.v20141016.jar
diff --git a/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.8.0_05.mod b/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.8.0_05.mod
new file mode 100644
index 0000000..a81732c
--- /dev/null
+++ b/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.8.0_05.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/8.1.0.v20141016/alpn-boot-8.1.0.v20141016.jar|lib/alpn/alpn-boot-8.1.0.v20141016.jar
+
+[exec]
+-Xbootclasspath/p:lib/alpn/alpn-boot-8.1.0.v20141016.jar
diff --git a/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.8.0_11.mod b/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.8.0_11.mod
new file mode 100644
index 0000000..a81732c
--- /dev/null
+++ b/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.8.0_11.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/8.1.0.v20141016/alpn-boot-8.1.0.v20141016.jar|lib/alpn/alpn-boot-8.1.0.v20141016.jar
+
+[exec]
+-Xbootclasspath/p:lib/alpn/alpn-boot-8.1.0.v20141016.jar
diff --git a/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.8.0_20.mod b/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.8.0_20.mod
new file mode 100644
index 0000000..a81732c
--- /dev/null
+++ b/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.8.0_20.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/8.1.0.v20141016/alpn-boot-8.1.0.v20141016.jar|lib/alpn/alpn-boot-8.1.0.v20141016.jar
+
+[exec]
+-Xbootclasspath/p:lib/alpn/alpn-boot-8.1.0.v20141016.jar
diff --git a/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.8.0_25.mod b/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.8.0_25.mod
new file mode 100644
index 0000000..8d13261
--- /dev/null
+++ b/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.8.0_25.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/8.1.2.v20141202/alpn-boot-8.1.2.v20141202.jar|lib/alpn/alpn-boot-8.1.2.v20141202.jar
+
+[exec]
+-Xbootclasspath/p:lib/alpn/alpn-boot-8.1.2.v20141202.jar
diff --git a/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.8.0_31.mod b/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.8.0_31.mod
new file mode 100644
index 0000000..4114979
--- /dev/null
+++ b/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.8.0_31.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/8.1.3.v20150130/alpn-boot-8.1.3.v20150130.jar|lib/alpn/alpn-boot-8.1.3.v20150130.jar
+
+[exec]
+-Xbootclasspath/p:lib/alpn/alpn-boot-8.1.3.v20150130.jar
diff --git a/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.8.0_40.mod b/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.8.0_40.mod
new file mode 100644
index 0000000..4114979
--- /dev/null
+++ b/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.8.0_40.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/8.1.3.v20150130/alpn-boot-8.1.3.v20150130.jar|lib/alpn/alpn-boot-8.1.3.v20150130.jar
+
+[exec]
+-Xbootclasspath/p:lib/alpn/alpn-boot-8.1.3.v20150130.jar
diff --git a/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.8.0_45.mod b/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.8.0_45.mod
new file mode 100644
index 0000000..4114979
--- /dev/null
+++ b/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.8.0_45.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/8.1.3.v20150130/alpn-boot-8.1.3.v20150130.jar|lib/alpn/alpn-boot-8.1.3.v20150130.jar
+
+[exec]
+-Xbootclasspath/p:lib/alpn/alpn-boot-8.1.3.v20150130.jar
diff --git a/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.8.0_51.mod b/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.8.0_51.mod
new file mode 100644
index 0000000..ddc18d7
--- /dev/null
+++ b/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.8.0_51.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/8.1.4.v20150727/alpn-boot-8.1.4.v20150727.jar|lib/alpn/alpn-boot-8.1.4.v20150727.jar
+
+[exec]
+-Xbootclasspath/p:lib/alpn/alpn-boot-8.1.4.v20150727.jar
diff --git a/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn.mod b/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn.mod
new file mode 100644
index 0000000..d3e4118
--- /dev/null
+++ b/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn.mod
@@ -0,0 +1,42 @@
+# ALPN is provided via a -Xbootclasspath that modifies the secure connections
+# in java to support the ALPN layer needed for SPDY (and eventually HTTP/2)
+#
+# This modification has a tight dependency on specific recent updates of
+# Java 1.7 and Java 1.8
+# (Java versions prior to 1.7u40 are not supported)
+#
+# The alpn protonego module will use an appropriate alpn-boot jar for your
+# specific version of Java.
+#
+# IMPORTANT: Versions of Java that exist after this module was created are
+#            not guaranteed to work with existing alpn-boot jars, and might
+#            need a new alpn-boot to be created / tested / deployed by the
+#            Jetty project in order to provide support for these future
+#            Java versions.
+#
+# All versions of alpn-boot can be found at
+# http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/
+
+[name]
+protonego-impl
+
+[depend]
+protonego-impl/alpn-${java.version}
+
+[lib]
+lib/jetty-alpn-client-${jetty.version}.jar
+lib/jetty-alpn-server-${jetty.version}.jar
+
+[xml]
+etc/protonego-alpn.xml
+
+[files]
+lib/
+lib/alpn/
+
+[license]
+ALPN is a hosted at github under the GPL v2 with ClassPath Exception.
+ALPN replaces/modifies OpenJDK classes in the java.sun.security.ssl package.
+http://github.com/jetty-project/jetty-alpn
+http://openjdk.java.net/legal/gplv2+ce.html
+
diff --git a/jetty-alpn/jetty-alpn-server/src/main/java/org/eclipse/jetty/alpn/server/ALPNServerConnection.java b/jetty-alpn/jetty-alpn-server/src/main/java/org/eclipse/jetty/alpn/server/ALPNServerConnection.java
new file mode 100644
index 0000000..88abfd3
--- /dev/null
+++ b/jetty-alpn/jetty-alpn-server/src/main/java/org/eclipse/jetty/alpn/server/ALPNServerConnection.java
@@ -0,0 +1,82 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.alpn.server;
+
+import java.util.Collections;
+import java.util.List;
+import javax.net.ssl.SSLEngine;
+
+import org.eclipse.jetty.alpn.ALPN;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.NegotiatingServerConnection;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public class ALPNServerConnection extends NegotiatingServerConnection implements ALPN.ServerProvider
+{
+    private static final Logger LOG = Log.getLogger(ALPNServerConnection.class);
+
+    public ALPNServerConnection(Connector connector, EndPoint endPoint, SSLEngine engine, List<String> protocols, String defaultProtocol)
+    {
+        super(connector, endPoint, engine, protocols, defaultProtocol);
+        ALPN.put(engine, this);
+    }
+
+    @Override
+    public void unsupported()
+    {
+        select(Collections.<String>emptyList());
+    }
+
+    @Override
+    public String select(List<String> clientProtocols)
+    {
+        List<String> serverProtocols = getProtocols();
+        String negotiated = null;
+
+        // RFC 7301 states that the server picks the protocol
+        // that it prefers that is also supported by the client.
+        for (String serverProtocol : serverProtocols)
+        {
+            if (clientProtocols.contains(serverProtocol))
+            {
+                negotiated = serverProtocol;
+                break;
+            }
+        }
+
+        if (negotiated == null)
+        {
+            negotiated = getDefaultProtocol();
+        }
+        if (LOG.isDebugEnabled())
+            LOG.debug("{} protocol selected {}", this, negotiated);
+        setProtocol(negotiated);
+        ALPN.remove(getSSLEngine());
+        return negotiated;
+    }
+
+    @Override
+    public void close()
+    {
+        ALPN.remove(getSSLEngine());
+        super.close();
+    }
+}
diff --git a/jetty-alpn/jetty-alpn-server/src/main/java/org/eclipse/jetty/alpn/server/ALPNServerConnectionFactory.java b/jetty-alpn/jetty-alpn-server/src/main/java/org/eclipse/jetty/alpn/server/ALPNServerConnectionFactory.java
new file mode 100644
index 0000000..6fdda2b
--- /dev/null
+++ b/jetty-alpn/jetty-alpn-server/src/main/java/org/eclipse/jetty/alpn/server/ALPNServerConnectionFactory.java
@@ -0,0 +1,61 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.alpn.server;
+
+import java.util.List;
+import javax.net.ssl.SSLEngine;
+
+import org.eclipse.jetty.alpn.ALPN;
+import org.eclipse.jetty.io.AbstractConnection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.NegotiatingServerConnectionFactory;
+import org.eclipse.jetty.util.annotation.Name;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public class ALPNServerConnectionFactory extends NegotiatingServerConnectionFactory
+{
+    private static final Logger LOG = Log.getLogger(ALPNServerConnectionFactory.class);
+
+    public ALPNServerConnectionFactory(@Name("protocols") String... protocols)
+    {
+        super("alpn", protocols);
+        try
+        {
+            ClassLoader alpnClassLoader = ALPN.class.getClassLoader();
+            if (alpnClassLoader != null)
+            {
+                LOG.warn("ALPN must be in the boot classloader, not in: " + alpnClassLoader);
+                throw new IllegalStateException("ALPN must be in the boot classloader");
+            }
+        }
+        catch (Throwable x)
+        {
+            LOG.warn("ALPN not available", x);
+            throw new IllegalStateException("ALPN not available", x);
+        }
+    }
+
+    @Override
+    protected AbstractConnection newServerConnection(Connector connector, EndPoint endPoint, SSLEngine engine, List<String> protocols, String defaultProtocol)
+    {
+        return new ALPNServerConnection(connector, endPoint, engine, protocols, defaultProtocol);
+    }
+}
diff --git a/jetty-alpn/pom.xml b/jetty-alpn/pom.xml
new file mode 100644
index 0000000..e462949
--- /dev/null
+++ b/jetty-alpn/pom.xml
@@ -0,0 +1,17 @@
+<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">
+  <parent>
+    <groupId>org.eclipse.jetty</groupId>
+    <artifactId>jetty-project</artifactId>
+    <version>9.2.13.v20150730</version>
+  </parent>
+  <modelVersion>4.0.0</modelVersion>
+  <artifactId>jetty-alpn-parent</artifactId>
+  <packaging>pom</packaging>
+  <name>Jetty :: ALPN :: Parent</name>
+  <description>Jetty ALPN services parent</description>
+  <url>http://www.eclipse.org/jetty</url>
+  <modules>
+    <module>jetty-alpn-server</module>
+    <module>jetty-alpn-client</module>
+  </modules>
+</project>
diff --git a/jetty-annotations/pom.xml b/jetty-annotations/pom.xml
index 9394c2b..8a57842 100644
--- a/jetty-annotations/pom.xml
+++ b/jetty-annotations/pom.xml
@@ -2,7 +2,7 @@
   <parent>
     <groupId>org.eclipse.jetty</groupId>
     <artifactId>jetty-project</artifactId>
-    <version>8.1.17.v20150415</version>
+    <version>9.2.13.v20150730</version>
   </parent>
   <modelVersion>4.0.0</modelVersion>
   <artifactId>jetty-annotations</artifactId>
@@ -43,7 +43,8 @@
             </goals>
             <configuration>
               <instructions>
-                <Import-Package>javax.servlet.*;version="2.6.0",*</Import-Package>
+                <Import-Package>javax.servlet.*;version="[2.6.0,3.2)",org.objectweb.asm.*;version=5,*</Import-Package>
+                <Require-Capability>osgi.serviceloader; filter:="(osgi.serviceloader=javax.servlet.ServletContainerInitializer)";resolution:=optional;cardinality:=multiple, osgi.extender; filter:="(osgi.extender=osgi.serviceloader.processor)"</Require-Capability>
               </instructions>
             </configuration>
           </execution>
@@ -72,11 +73,6 @@
   </build>
   <dependencies>
     <dependency>
-      <groupId>junit</groupId>
-      <artifactId>junit</artifactId>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
       <groupId>org.eclipse.jetty.toolchain</groupId>
       <artifactId>jetty-test-helper</artifactId>
       <scope>test</scope>
@@ -98,12 +94,16 @@
       <version>${project.version}</version>
     </dependency>
     <dependency>
-      <groupId>org.eclipse.jetty.orbit</groupId>
-      <artifactId>javax.annotation</artifactId>
+      <groupId>javax.annotation</groupId>
+      <artifactId>javax.annotation-api</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.ow2.asm</groupId>
+      <artifactId>asm</artifactId>
     </dependency>
     <dependency>
-      <groupId>org.eclipse.jetty.orbit</groupId>
-      <artifactId>org.objectweb.asm</artifactId>
+      <groupId>org.ow2.asm</groupId>
+      <artifactId>asm-commons</artifactId>
     </dependency>
   </dependencies>
 </project>
diff --git a/jetty-annotations/src/main/config/etc/jetty-annotations.xml b/jetty-annotations/src/main/config/etc/jetty-annotations.xml
index 7aa719d..637b511 100644
--- a/jetty-annotations/src/main/config/etc/jetty-annotations.xml
+++ b/jetty-annotations/src/main/config/etc/jetty-annotations.xml
@@ -1,23 +1,20 @@
 <?xml version="1.0"?>
-<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
 
 <Configure id="Server" class="org.eclipse.jetty.server.Server">
-
-    <!-- ================================================================= -->
-    <!-- Enable annotations - configure deployment steps for every web app -->
-    <!-- ================================================================= -->
-    <Call name="setAttribute">
-      <Arg>org.eclipse.jetty.webapp.configuration</Arg>
+  <!-- =========================================================== -->
+  <!-- Add annotation Configuring classes to all webapps for this Server -->
+  <!-- =========================================================== -->
+  <Call class="org.eclipse.jetty.webapp.Configuration$ClassList" name="setServerDefault">
+    <Arg><Ref refid="Server" /></Arg>
+    <Call name="addBefore">
+      <Arg name="beforeClass">org.eclipse.jetty.webapp.JettyWebXmlConfiguration</Arg>
       <Arg>
-          <Array type="java.lang.String">
-               <Item>org.eclipse.jetty.webapp.WebInfConfiguration</Item>
-               <Item>org.eclipse.jetty.webapp.WebXmlConfiguration</Item>
-               <Item>org.eclipse.jetty.webapp.MetaInfConfiguration</Item>
-               <Item>org.eclipse.jetty.webapp.FragmentConfiguration</Item>
-               <Item>org.eclipse.jetty.annotations.AnnotationConfiguration</Item>
-               <Item>org.eclipse.jetty.webapp.JettyWebXmlConfiguration</Item>
-          </Array>
+        <Array type="String">
+          <Item>org.eclipse.jetty.annotations.AnnotationConfiguration</Item>
+        </Array>
       </Arg>
     </Call>
+  </Call>
 
 </Configure>
diff --git a/jetty-annotations/src/main/config/modules/annotations.mod b/jetty-annotations/src/main/config/modules/annotations.mod
new file mode 100644
index 0000000..65e4654
--- /dev/null
+++ b/jetty-annotations/src/main/config/modules/annotations.mod
@@ -0,0 +1,17 @@
+#
+# Jetty Annotation Scanning Module
+#
+
+[depend]
+# Annotations needs plus, and jndi features
+plus
+
+[lib]
+# Annotations needs jetty annotation jars
+lib/jetty-annotations-${jetty.version}.jar
+# Need annotation processing jars too
+lib/annotations/*.jar
+
+[xml]
+# Enable annotation scanning webapp configurations
+etc/jetty-annotations.xml
diff --git a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AbstractDiscoverableAnnotationHandler.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AbstractDiscoverableAnnotationHandler.java
index 4781b97..b307c4d 100644
--- a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AbstractDiscoverableAnnotationHandler.java
+++ b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AbstractDiscoverableAnnotationHandler.java
@@ -18,63 +18,30 @@
 
 package org.eclipse.jetty.annotations;
 
-import java.util.ArrayList;
-import java.util.List;
-
-import org.eclipse.jetty.annotations.AnnotationParser.DiscoverableAnnotationHandler;
-import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.annotations.AnnotationParser.AbstractHandler;
 import org.eclipse.jetty.webapp.DiscoveredAnnotation;
 import org.eclipse.jetty.webapp.WebAppContext;
 
 /**
  * DiscoverableAnnotationHandler
  *
- *
+ * Base class for handling the discovery of an annotation.
+ * 
  */
-public abstract class AbstractDiscoverableAnnotationHandler implements DiscoverableAnnotationHandler
+public abstract class AbstractDiscoverableAnnotationHandler extends AbstractHandler
 {
     protected WebAppContext _context;
-    protected List<DiscoveredAnnotation> _annotations; 
-    protected Resource _resource;
-    
+  
+
     public AbstractDiscoverableAnnotationHandler(WebAppContext context)
     {
-        this(context, null);
-    }
-    
-    public AbstractDiscoverableAnnotationHandler(WebAppContext  context, List<DiscoveredAnnotation> list)
-    {
         _context = context;
-        if (list == null)
-            _annotations = new ArrayList<DiscoveredAnnotation>();
-        else
-            _annotations = list;
-    }
-
-    public Resource getResource()
-    {
-        return _resource;
     }
     
-    public void setResource(Resource resource)
-    {
-        _resource = resource;
-    }
-    
-    public List<DiscoveredAnnotation> getAnnotationList ()
-    {
-        return _annotations;
-    }
-    
-    public void resetList()
-    {
-        _annotations.clear();
-    }
-    
-    
+
     public void addAnnotation (DiscoveredAnnotation a)
     {
-        _annotations.add(a);
+        _context.getMetaData().addDiscoveredAnnotation(a);
     }
 
 }
diff --git a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationConfiguration.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationConfiguration.java
index 9e614b4..4f1daa6 100644
--- a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationConfiguration.java
+++ b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationConfiguration.java
@@ -18,21 +18,38 @@
 
 package org.eclipse.jetty.annotations;
 
+import java.io.IOException;
+import java.net.MalformedURLException;
 import java.net.URI;
+import java.net.URL;
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 import java.util.ServiceLoader;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Pattern;
 
 import javax.servlet.ServletContainerInitializer;
 import javax.servlet.annotation.HandlesTypes;
 
-import org.eclipse.jetty.annotations.AnnotationParser.DiscoverableAnnotationHandler;
+import org.eclipse.jetty.annotations.AnnotationParser.Handler;
 import org.eclipse.jetty.plus.annotation.ContainerInitializer;
-import org.eclipse.jetty.util.MultiMap;
+import org.eclipse.jetty.util.ConcurrentHashSet;
+import org.eclipse.jetty.util.MultiException;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
 import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.util.statistic.CounterStatistic;
 import org.eclipse.jetty.webapp.AbstractConfiguration;
 import org.eclipse.jetty.webapp.FragmentDescriptor;
 import org.eclipse.jetty.webapp.MetaDataComplete;
@@ -47,31 +64,357 @@ import org.eclipse.jetty.webapp.WebDescriptor;
 public class AnnotationConfiguration extends AbstractConfiguration
 {
     private static final Logger LOG = Log.getLogger(AnnotationConfiguration.class);
-    public static final String CLASS_INHERITANCE_MAP  = "org.eclipse.jetty.classInheritanceMap";    
+    
+    public static final String SERVLET_CONTAINER_INITIALIZER_EXCLUSION_PATTERN = "org.eclipse.jetty.containerInitializerExclusionPattern";
+    public static final String SERVLET_CONTAINER_INITIALIZER_ORDER = "org.eclipse.jetty.containerInitializerOrder";
+    public static final String CLASS_INHERITANCE_MAP  = "org.eclipse.jetty.classInheritanceMap";
     public static final String CONTAINER_INITIALIZERS = "org.eclipse.jetty.containerInitializers";
-    public static final String CONTAINER_INITIALIZER_LISTENER = "org.eclipse.jetty.containerInitializerListener";
-  
+    public static final String CONTAINER_INITIALIZER_STARTER = "org.eclipse.jetty.containerInitializerStarter";
+    public static final String MULTI_THREADED = "org.eclipse.jetty.annotations.multiThreaded";
+    public static final String MAX_SCAN_WAIT = "org.eclipse.jetty.annotations.maxWait";
     
-    protected List<DiscoverableAnnotationHandler> _discoverableAnnotationHandlers = new ArrayList<DiscoverableAnnotationHandler>();
+    public static final int DEFAULT_MAX_SCAN_WAIT = 60; /* time in sec */  
+    public static final boolean DEFAULT_MULTI_THREADED = true;
+    
+    protected List<AbstractDiscoverableAnnotationHandler> _discoverableAnnotationHandlers = new ArrayList<AbstractDiscoverableAnnotationHandler>();
     protected ClassInheritanceHandler _classInheritanceHandler;
     protected List<ContainerInitializerAnnotationHandler> _containerInitializerAnnotationHandlers = new ArrayList<ContainerInitializerAnnotationHandler>();
    
+    protected List<ParserTask> _parserTasks;
+    protected WebAppClassNameResolver _webAppClassNameResolver;
+    protected ContainerClassNameResolver _containerClassNameResolver;
+
+    protected CounterStatistic _containerPathStats;
+    protected CounterStatistic _webInfLibStats;
+    protected CounterStatistic _webInfClassesStats;
+    protected Pattern _sciExcludePattern;
+  
+    /**
+     * TimeStatistic
+     *
+     * Simple class to capture elapsed time of an operation.
+     * 
+     */
+    public class TimeStatistic 
+    {
+        public long _start = 0;
+        public long _end = 0;
+        
+        public void start ()
+        {
+            _start = System.nanoTime();
+        }
+        
+        public void end ()
+        {
+            _end = System.nanoTime();
+        }
+        
+        public long getStart()
+        {
+            return _start;
+        }
+        
+        public long getEnd ()
+        {
+            return _end;
+        }
+        
+        public long getElapsed ()
+        {
+            return (_end > _start?(_end-_start):0);
+        }
+    }
+    
+    
+    /**
+     * ParserTask
+     *
+     * Task to executing scanning of a resource for annotations.
+     * 
+     */
+    public class ParserTask implements Callable<Void>
+    {
+        protected Exception _exception;
+        protected final AnnotationParser _parser;
+        protected final Set<? extends Handler> _handlers;
+        protected final ClassNameResolver _resolver;
+        protected final Resource _resource;
+        protected TimeStatistic _stat;
+        
+        public ParserTask (AnnotationParser parser, Set<? extends Handler>handlers, Resource resource, ClassNameResolver resolver)
+        {
+            _parser = parser;
+            _handlers = handlers;
+            _resolver = resolver;
+            _resource = resource;
+        }
+        
+        public void setStatistic(TimeStatistic stat)
+        {
+           _stat = stat; 
+        }
+
+        public Void call() throws Exception
+        {            
+            if (_stat != null)
+                _stat.start();
+            if (_parser != null)
+                _parser.parse(_handlers, _resource, _resolver); 
+            if (_stat != null)
+                _stat.end();
+            return null;
+        }
+        
+        public TimeStatistic getStatistic()
+        {
+            return _stat;
+        }
+        
+        public Resource getResource()
+        {
+            return _resource;
+        }
+        
+    }
+
+    /**
+     * WebAppClassNameResolver
+     *
+     * Checks to see if a classname belongs to hidden or visible packages when scanning,
+     * and whether a classname that is a duplicate should override a previously
+     * scanned classname. 
+     * 
+     * This is analogous to the management of classes that the WebAppClassLoader is doing,
+     * however we don't want to load the classes at this point so we are doing it on
+     * the name only.
+     * 
+     */
+    public class WebAppClassNameResolver implements ClassNameResolver
+    {
+        private WebAppContext _context;
+
+        public WebAppClassNameResolver (WebAppContext context)
+        {
+            _context = context;
+        }
+
+        public boolean isExcluded (String name)
+        {
+            if (_context.isSystemClass(name)) return true;
+            if (_context.isServerClass(name)) return false;
+            return false;
+        }
+
+        public boolean shouldOverride (String name)
+        {
+            //looking at webapp classpath, found already-parsed class 
+            //of same name - did it come from system or duplicate in webapp?
+            if (_context.isParentLoaderPriority())
+                return false;
+            return true;
+        }
+    }
+
+    
+    /**
+     * ContainerClassNameResolver
+     *
+     * Checks to see if a classname belongs to a hidden or visible package
+     * when scanning for annotations and thus whether it should be excluded from
+     * consideration or not.
+     * 
+     * This is analogous to the management of classes that the WebAppClassLoader is doing,
+     * however we don't want to load the classes at this point so we are doing it on
+     * the name only.
+     * 
+     */
+    public class ContainerClassNameResolver implements ClassNameResolver
+    { 
+        private WebAppContext _context;
+        
+        public ContainerClassNameResolver (WebAppContext context)
+        {
+            _context = context;
+        }
+        public boolean isExcluded (String name)
+        {
+            if (_context.isSystemClass(name)) return false;
+            if (_context.isServerClass(name)) return true;
+            return false;
+        }
+
+        public boolean shouldOverride (String name)
+        {
+            //visiting the container classpath, 
+            if (_context.isParentLoaderPriority())
+                return true;
+            return false;
+        }
+    }
+    
+    
+    /**
+     * ServletContainerInitializerOrdering
+     *
+     * A list of classnames of ServletContainerInitializers in the order in which
+     * they are to be called back. One name only in the list can be "*", which is a
+     * wildcard which matches any other ServletContainerInitializer name not already
+     * matched.
+     */
+    public class ServletContainerInitializerOrdering 
+    {
+        private Map<String, Integer> _indexMap = new HashMap<String, Integer>();
+        private Integer _star = null;
+        private String _ordering = null;
+        
+        public ServletContainerInitializerOrdering (String ordering)
+        {
+            if (ordering != null)
+            {
+                _ordering = ordering;
+                
+                String[] tmp = ordering.split(",");
+                
+                for (int i=0; i<tmp.length; i++)
+                {
+                    String s = tmp[i].trim();
+                    _indexMap.put(s, Integer.valueOf(i));
+                    if ("*".equals(s))
+                    {
+                        if (_star != null)
+                            throw new IllegalArgumentException("Duplicate wildcards in ServletContainerInitializer ordering "+ordering);
+                        _star = Integer.valueOf(i);
+                    }
+                    
+                }
+            }
+        }
+        
+        /**
+         * True if "*" is one of the values.
+         * @return
+         */
+        public boolean hasWildcard()
+        {
+            return _star != null;
+        }
+        
+        /**
+         * Get the index of the "*" element, if it is specified. -1 otherwise.
+         * @return
+         */
+        public int getWildcardIndex()
+        {
+            if (!hasWildcard())
+                return -1;
+            return _star.intValue();
+        }
+        
+        /**
+         * True if the ordering contains a single value of "*"
+         * @return
+         */
+        public boolean isDefaultOrder ()
+        {
+            return (getSize() == 1 && hasWildcard());
+        }
+        
+        /**
+         * Get the order index of the given classname
+         * @param name
+         * @return
+         */
+        public int getIndexOf (String name)
+        {
+            Integer i = _indexMap.get(name);
+            if (i == null)
+                return -1;
+            return i.intValue();
+        }
+        
+        /**
+         * Get the number of elements of the ordering
+         * @return
+         */
+        public int getSize()
+        {
+            return _indexMap.size();
+        }
+        
+        public String toString()
+        {
+            if (_ordering == null)
+                return "";
+            return _ordering;
+        }
+    }
+    
+    
+    
+    /**
+     * ServletContainerInitializerComparator
+     *
+     * Comparator impl that orders a set of ServletContainerInitializers according to the
+     * list of classnames (optionally containing a "*" wildcard character) established in a
+     * ServletContainerInitializerOrdering.
+     * @see ServletContainerInitializerOrdering
+     */
+    public class ServletContainerInitializerComparator implements Comparator<ServletContainerInitializer>
+    {
+        private ServletContainerInitializerOrdering _ordering;
+        
+        
+        public ServletContainerInitializerComparator (ServletContainerInitializerOrdering ordering)
+        {
+            _ordering = ordering;
+        }
+
+        @Override
+        public int compare(ServletContainerInitializer sci1, ServletContainerInitializer sci2)
+        {
+            String c1 = (sci1 != null? sci1.getClass().getName() : null);
+            String c2 = (sci2 != null? sci2.getClass().getName() : null);
+
+            if (c1 == null && c2 == null)
+                return 0;
+            
+            int i1 = _ordering.getIndexOf(c1);
+            if (i1 < 0 && _ordering.hasWildcard())
+                i1 = _ordering.getWildcardIndex();
+            int i2 = _ordering.getIndexOf(c2);
+            if (i2 < 0 && _ordering.hasWildcard())
+                i2 = _ordering.getWildcardIndex();
+           
+            return Integer.compare(i1, i2);
+        }
+    }
     
     @Override
     public void preConfigure(final WebAppContext context) throws Exception
     {
+        _webAppClassNameResolver = new WebAppClassNameResolver(context);
+        _containerClassNameResolver = new ContainerClassNameResolver(context);
+        String tmp = (String)context.getAttribute(SERVLET_CONTAINER_INITIALIZER_EXCLUSION_PATTERN);
+        _sciExcludePattern = (tmp==null?null:Pattern.compile(tmp));
     }
+
    
+    public void addDiscoverableAnnotationHandler(AbstractDiscoverableAnnotationHandler handler)
+    {
+        _discoverableAnnotationHandlers.add(handler);
+    }
+
     @Override
     public void deconfigure(WebAppContext context) throws Exception
     {
         context.removeAttribute(CLASS_INHERITANCE_MAP);
         context.removeAttribute(CONTAINER_INITIALIZERS);
-        ServletContainerInitializerListener listener = (ServletContainerInitializerListener)context.getAttribute(CONTAINER_INITIALIZER_LISTENER);
-        if (listener != null)
+        ServletContainerInitializersStarter starter = (ServletContainerInitializersStarter)context.getAttribute(CONTAINER_INITIALIZER_STARTER);
+        if (starter != null)
         {
-            context.removeBean(listener);
-            context.removeAttribute(CONTAINER_INITIALIZER_LISTENER);
+            context.removeBean(starter);
+            context.removeAttribute(CONTAINER_INITIALIZER_STARTER);
         }
     }
     
@@ -81,13 +424,11 @@ public class AnnotationConfiguration extends AbstractConfiguration
     @Override
     public void configure(WebAppContext context) throws Exception
     {
-       boolean metadataComplete = context.getMetaData().isMetaDataComplete();
-       context.addDecorator(new AnnotationDecorator(context));   
-      
-       
+       context.addDecorator(new AnnotationDecorator(context));
+
        //Even if metadata is complete, we still need to scan for ServletContainerInitializers - if there are any
-       AnnotationParser parser = null;
-       if (!metadataComplete)
+      
+       if (!context.getMetaData().isMetaDataComplete())
        {
            //If metadata isn't complete, if this is a servlet 3 webapp or isConfigDiscovered is true, we need to search for annotations
            if (context.getServletContext().getEffectiveMajorVersion() >= 3 || context.isConfigurationDiscovered())
@@ -97,62 +438,150 @@ public class AnnotationConfiguration extends AbstractConfiguration
                _discoverableAnnotationHandlers.add(new WebListenerAnnotationHandler(context));
            }
        }
-       else
-           if (LOG.isDebugEnabled()) LOG.debug("Metadata-complete==true,  not processing discoverable servlet annotations for context "+context);
-       
-       
-       
+
        //Regardless of metadata, if there are any ServletContainerInitializers with @HandlesTypes, then we need to scan all the
        //classes so we can call their onStartup() methods correctly
        createServletContainerInitializerAnnotationHandlers(context, getNonExcludedInitializers(context));
-       
+
        if (!_discoverableAnnotationHandlers.isEmpty() || _classInheritanceHandler != null || !_containerInitializerAnnotationHandlers.isEmpty())
-       {           
-           parser = createAnnotationParser();
-           if (LOG.isDebugEnabled()) LOG.debug("Scanning all classses for annotations: webxmlVersion="+context.getServletContext().getEffectiveMajorVersion()+" configurationDiscovered="+context.isConfigurationDiscovered());
-           parseContainerPath(context, parser);
-           //email from Rajiv Mordani jsrs 315 7 April 2010
-           //    If there is a <others/> then the ordering should be 
-           //          WEB-INF/classes the order of the declared elements + others.
-           //    In case there is no others then it is 
-           //          WEB-INF/classes + order of the elements.
-           parseWebInfClasses(context, parser);
-           parseWebInfLib (context, parser);
-           
-           for (DiscoverableAnnotationHandler h:_discoverableAnnotationHandlers)
-               context.getMetaData().addDiscoveredAnnotations(((AbstractDiscoverableAnnotationHandler)h).getAnnotationList());      
+           scanForAnnotations(context);   
+       
+       // Resolve container initializers
+       List<ContainerInitializer> initializers = 
+                   (List<ContainerInitializer>)context.getAttribute(AnnotationConfiguration.CONTAINER_INITIALIZERS);
+       if (initializers != null && initializers.size()>0)
+       {
+           Map<String, Set<String>> map = ( Map<String, Set<String>>) context.getAttribute(AnnotationConfiguration.CLASS_INHERITANCE_MAP);
+           if (map == null)
+               LOG.warn ("ServletContainerInitializers: detected. Class hierarchy: empty");
+           for (ContainerInitializer i : initializers)
+                   i.resolveClasses(context,map);
        }
     }
-    
-    
-    
+
+
     /** 
      * @see org.eclipse.jetty.webapp.AbstractConfiguration#postConfigure(org.eclipse.jetty.webapp.WebAppContext)
      */
     @Override
     public void postConfigure(WebAppContext context) throws Exception
     {
-        MultiMap map = (MultiMap)context.getAttribute(CLASS_INHERITANCE_MAP);
-        if (map != null)
-            map.clear();
+        ConcurrentHashMap<String, ConcurrentHashSet<String>> classMap = (ClassInheritanceMap)context.getAttribute(CLASS_INHERITANCE_MAP);
+        List<ContainerInitializer> initializers = (List<ContainerInitializer>)context.getAttribute(CONTAINER_INITIALIZERS);
         
         context.removeAttribute(CLASS_INHERITANCE_MAP);
+        if (classMap != null)
+            classMap.clear();
         
-        List<ContainerInitializer> initializers = (List<ContainerInitializer>)context.getAttribute(CONTAINER_INITIALIZERS);
+        context.removeAttribute(CONTAINER_INITIALIZERS);
         if (initializers != null)
             initializers.clear();
+        
         if (_discoverableAnnotationHandlers != null)
             _discoverableAnnotationHandlers.clear();
-      
+
         _classInheritanceHandler = null;
         if (_containerInitializerAnnotationHandlers != null)
             _containerInitializerAnnotationHandlers.clear();
-  
+
+        if (_parserTasks != null)
+        {
+            _parserTasks.clear();
+            _parserTasks = null;
+        }
+        
         super.postConfigure(context);
     }
+    
+    
+    
+    /**
+     * Perform scanning of classes for annotations
+     * 
+     * @param context
+     * @throws Exception
+     */
+    protected void scanForAnnotations (WebAppContext context)
+    throws Exception
+    {
+        AnnotationParser parser = createAnnotationParser();
+        _parserTasks = new ArrayList<ParserTask>();
+
+        long start = 0; 
+
+
+        if (LOG.isDebugEnabled())
+            LOG.debug("Annotation scanning commencing: webxml={}, metadatacomplete={}, configurationDiscovered={}, multiThreaded={}, maxScanWait={}", 
+                      context.getServletContext().getEffectiveMajorVersion(), 
+                      context.getMetaData().isMetaDataComplete(),
+                      context.isConfigurationDiscovered(),
+                      isUseMultiThreading(context),
+                      getMaxScanWait(context));
+
+             
+        parseContainerPath(context, parser);
+        //email from Rajiv Mordani jsrs 315 7 April 2010
+        //    If there is a <others/> then the ordering should be
+        //          WEB-INF/classes the order of the declared elements + others.
+        //    In case there is no others then it is
+        //          WEB-INF/classes + order of the elements.
+        parseWebInfClasses(context, parser);
+        parseWebInfLib (context, parser); 
+        
+        start = System.nanoTime();
+        
+        //execute scan, either effectively synchronously (1 thread only), or asynchronously (limited by number of processors available) 
+        final Semaphore task_limit = (isUseMultiThreading(context)? new Semaphore(Runtime.getRuntime().availableProcessors()):new Semaphore(1));     
+        final CountDownLatch latch = new CountDownLatch(_parserTasks.size());
+        final MultiException me = new MultiException();
+    
+        for (final ParserTask p:_parserTasks)
+        {
+            task_limit.acquire();
+            context.getServer().getThreadPool().execute(new Runnable()
+            {
+                @Override
+                public void run()
+                {
+                   try
+                   {
+                       p.call();
+                   }
+                   catch (Exception e)
+                   {
+                       me.add(e);
+                   }
+                   finally
+                   {
+                       task_limit.release();
+                       latch.countDown();
+                   }
+                }         
+            });
+        }
+       
+        boolean timeout = !latch.await(getMaxScanWait(context), TimeUnit.SECONDS);
+          
+        if (LOG.isDebugEnabled())
+        {
+            for (ParserTask p:_parserTasks)
+                LOG.debug("Scanned {} in {}ms", p.getResource(), TimeUnit.MILLISECONDS.convert(p.getStatistic().getElapsed(), TimeUnit.NANOSECONDS));
+
+            LOG.debug("Scanned {} container path jars, {} WEB-INF/lib jars, {} WEB-INF/classes dirs in {}ms for context {}",
+                    _containerPathStats.getTotal(), _webInfLibStats.getTotal(), _webInfClassesStats.getTotal(),
+                    (TimeUnit.MILLISECONDS.convert(System.nanoTime()-start, TimeUnit.NANOSECONDS)),
+                    context);
+        }
+
+        if (timeout)
+            me.add(new Exception("Timeout scanning annotations"));
+        me.ifExceptionThrow();   
+    }
 
+    
+    
     /**
-     * @return a new AnnotationParser. This method can be overridden to use a different impleemntation of
+     * @return a new AnnotationParser. This method can be overridden to use a different implementation of
      * the AnnotationParser. Note that this is considered internal API.
      */
     protected AnnotationParser createAnnotationParser()
@@ -160,16 +589,67 @@ public class AnnotationConfiguration extends AbstractConfiguration
         return new AnnotationParser();
     }
     
+    /**
+     * Check if we should use multiple threads to scan for annotations or not
+     * @param context
+     * @return
+     */
+    protected boolean isUseMultiThreading(WebAppContext context)
+    {
+        //try context attribute to see if we should use multithreading
+        Object o = context.getAttribute(MULTI_THREADED);
+        if (o instanceof Boolean)
+        {
+            return ((Boolean)o).booleanValue();
+        }
+        //try server attribute to see if we should use multithreading
+        o = context.getServer().getAttribute(MULTI_THREADED);
+        if (o instanceof Boolean)
+        {
+            return ((Boolean)o).booleanValue();
+        }
+        //try system property to see if we should use multithreading
+        return Boolean.valueOf(System.getProperty(MULTI_THREADED, Boolean.toString(DEFAULT_MULTI_THREADED)));
+    }
+
+   
+    
+    /**
+     * Work out how long we should wait for the async scanning to occur.
+     * 
+     * @param context
+     * @return
+     */
+    protected int getMaxScanWait (WebAppContext context)
+    {
+        //try context attribute to get max time in sec to wait for scan completion
+        Object o = context.getAttribute(MAX_SCAN_WAIT);
+        if (o != null && o instanceof Number)
+        {
+            return ((Number)o).intValue();
+        }
+        //try server attribute to get max time in sec to wait for scan completion
+        o = context.getServer().getAttribute(MAX_SCAN_WAIT);
+        if (o != null && o instanceof Number)
+        {
+            return ((Number)o).intValue();
+        }
+        //try system property to get max time in sec to wait for scan completion
+        return Integer.getInteger(MAX_SCAN_WAIT, DEFAULT_MAX_SCAN_WAIT).intValue();
+    }
+    
+    
+    
     /** 
      * @see org.eclipse.jetty.webapp.AbstractConfiguration#cloneConfigure(org.eclipse.jetty.webapp.WebAppContext, org.eclipse.jetty.webapp.WebAppContext)
      */
     @Override
     public void cloneConfigure(WebAppContext template, WebAppContext context) throws Exception
     {
-        context.addDecorator(new AnnotationDecorator(context));   
+        context.addDecorator(new AnnotationDecorator(context));
     }
-    
-    
+
+
     
     /**
      * @param context
@@ -179,10 +659,8 @@ public class AnnotationConfiguration extends AbstractConfiguration
     public void createServletContainerInitializerAnnotationHandlers (WebAppContext context, List<ServletContainerInitializer> scis)
     throws Exception
     {
-        
         if (scis == null || scis.isEmpty())
             return; // nothing to do
-        
 
         List<ContainerInitializer> initializers = new ArrayList<ContainerInitializer>();
         context.setAttribute(CONTAINER_INITIALIZERS, initializers);
@@ -190,182 +668,338 @@ public class AnnotationConfiguration extends AbstractConfiguration
         for (ServletContainerInitializer service : scis)
         {
             HandlesTypes annotation = service.getClass().getAnnotation(HandlesTypes.class);
-            ContainerInitializer initializer = new ContainerInitializer();
-            initializer.setTarget(service);
-            initializers.add(initializer);
+            ContainerInitializer initializer = null;
             if (annotation != null)
-            {
+            {    
                 //There is a HandlesTypes annotation on the on the ServletContainerInitializer
-                Class[] classes = annotation.value();
+                Class<?>[] classes = annotation.value();
                 if (classes != null)
                 {
-                    initializer.setInterestedTypes(classes);
+                    initializer = new ContainerInitializer(service, classes);
 
                     //If we haven't already done so, we need to register a handler that will
                     //process the whole class hierarchy to satisfy the ServletContainerInitializer
                     if (context.getAttribute(CLASS_INHERITANCE_MAP) == null)
                     {
-                        MultiMap map = new MultiMap();
+                        //MultiMap<String> map = new MultiMap<>();
+                        ConcurrentHashMap<String, ConcurrentHashSet<String>> map = new ClassInheritanceMap();
                         context.setAttribute(CLASS_INHERITANCE_MAP, map);
                         _classInheritanceHandler = new ClassInheritanceHandler(map);
                     }
-                                     
-                    for (Class c: classes)
+
+                    for (Class<?> c: classes)
                     {
                         //The value of one of the HandlesTypes classes is actually an Annotation itself so
                         //register a handler for it
                         if (c.isAnnotation())
                         {
                             if (LOG.isDebugEnabled()) LOG.debug("Registering annotation handler for "+c.getName());
-                           
                            _containerInitializerAnnotationHandlers.add(new ContainerInitializerAnnotationHandler(initializer, c));
                         }
                     }
                 }
                 else
+                {
+                    initializer = new ContainerInitializer(service, null);
                     if (LOG.isDebugEnabled()) LOG.debug("No classes in HandlesTypes on initializer "+service.getClass());
+                }
             }
             else
+            {
+                initializer = new ContainerInitializer(service, null);
                 if (LOG.isDebugEnabled()) LOG.debug("No annotation on initializer "+service.getClass());
+            }
+            
+            initializers.add(initializer);
         }
         
-
-        //add a bean which will call the servletcontainerinitializers when appropriate
-        ServletContainerInitializerListener listener = (ServletContainerInitializerListener)context.getAttribute(CONTAINER_INITIALIZER_LISTENER);
-        if (listener != null)
-            throw new IllegalStateException("ServletContainerInitializerListener already exists");
-        listener = new ServletContainerInitializerListener();
-        listener.setWebAppContext(context);
-        context.setAttribute(CONTAINER_INITIALIZER_LISTENER, listener);
-        context.addBean(listener, true);
+        
+        //add a bean to the context which will call the servletcontainerinitializers when appropriate
+        ServletContainerInitializersStarter starter = (ServletContainerInitializersStarter)context.getAttribute(CONTAINER_INITIALIZER_STARTER);
+        if (starter != null)
+            throw new IllegalStateException("ServletContainerInitializersStarter already exists");
+        starter = new ServletContainerInitializersStarter(context);
+        context.setAttribute(CONTAINER_INITIALIZER_STARTER, starter);
+        context.addBean(starter, true);
     }
 
     
+    public Resource getJarFor (ServletContainerInitializer service) 
+    throws MalformedURLException, IOException
+    {
+        //try the thread context classloader to get the jar that loaded the class
+        URL jarURL = Thread.currentThread().getContextClassLoader().getResource(service.getClass().getName().replace('.','/')+".class");
+        
+        //if for some reason that failed (eg we're in osgi and the TCCL does not know about the service) try the classloader that
+        //loaded the class
+        if (jarURL == null)
+            jarURL = service.getClass().getClassLoader().getResource(service.getClass().getName().replace('.','/')+".class");
+  
+        String loadingJarName = jarURL.toString();
+
+        int i = loadingJarName.indexOf(".jar");
+        if (i < 0)
+            return null; //not from a jar
+        
+        loadingJarName = loadingJarName.substring(0,i+4);
+        loadingJarName = (loadingJarName.startsWith("jar:")?loadingJarName.substring(4):loadingJarName);
+        return Resource.newResource(loadingJarName);
+    }
     
+
     /**
      * Check to see if the ServletContainerIntializer loaded via the ServiceLoader came
      * from a jar that is excluded by the fragment ordering. See ServletSpec 3.0 p.85.
-     * @param orderedJars
-     * @param service
-     * @return
+     * @param context
+     * @param sci
+     * @return true if excluded
      */
-    public boolean isFromExcludedJar (WebAppContext context, ServletContainerInitializer service)
+    public boolean isFromExcludedJar (WebAppContext context, ServletContainerInitializer sci, Resource sciResource)
     throws Exception
     {
-        List<Resource> orderedJars = context.getMetaData().getOrderedWebInfJars();
-
+        if (sci == null)
+            throw new IllegalArgumentException("ServletContainerInitializer null");
+        if (context == null)
+            throw new IllegalArgumentException("WebAppContext null");
+        
+        if (LOG.isDebugEnabled()) LOG.debug("Checking {} for jar exclusion", sci);
+        
+        //A ServletContainerInitializer that came from the container's classpath cannot be excluded by an ordering
+        //of WEB-INF/lib jars
+        if (isFromContainerClassPath(context, sci))
+            return false;
+        
         //If no ordering, nothing is excluded
         if (context.getMetaData().getOrdering() == null)
             return false;
+        
+        
+        List<Resource> orderedJars = context.getMetaData().getOrderedWebInfJars();
 
         //there is an ordering, but there are no jars resulting from the ordering, everything excluded
         if (orderedJars.isEmpty())
-            return true; 
-
-        String loadingJarName = Thread.currentThread().getContextClassLoader().getResource(service.getClass().getName().replace('.','/')+".class").toString();
+            return true;
 
-        int i = loadingJarName.indexOf(".jar");  
-        if (i < 0)
+        if (sciResource == null)
             return false; //not from a jar therefore not from WEB-INF so not excludable
-
-        loadingJarName = loadingJarName.substring(0,i+4);
-        loadingJarName = (loadingJarName.startsWith("jar:")?loadingJarName.substring(4):loadingJarName);
-        URI loadingJarURI = Resource.newResource(loadingJarName).getURI();
+        
+        URI loadingJarURI = sciResource.getURI();
         boolean found = false;
         Iterator<Resource> itor = orderedJars.iterator();
         while (!found && itor.hasNext())
         {
-            Resource r = itor.next();         
+            Resource r = itor.next();
             found = r.getURI().equals(loadingJarURI);
         }
 
         return !found;
     }
-   
-    
-    
+
+
     /**
+     * Test if the ServletContainerIntializer is excluded by the 
+     * o.e.j.containerInitializerExclusionPattern
      * @param context
+     * @param sci
      * @return
-     * @throws Exception
      */
-    public List<ServletContainerInitializer> getNonExcludedInitializers (WebAppContext context)
-    throws Exception
+    public boolean matchesExclusionPattern(ServletContainerInitializer sci)
     {
-        List<ServletContainerInitializer> nonExcludedInitializers = new ArrayList<ServletContainerInitializer>();
+        //no exclusion pattern, no SCI is excluded by it
+        if (_sciExcludePattern == null)
+            return false;
         
-        //We use the ServiceLoader mechanism to find the ServletContainerInitializer classes to inspect
-        ServiceLoader<ServletContainerInitializer> loadedInitializers = ServiceLoader.load(ServletContainerInitializer.class, context.getClassLoader());
-       
-        if (loadedInitializers != null)
-        {  
-            for (ServletContainerInitializer service : loadedInitializers)
-            {
-                if (!isFromExcludedJar(context, service))
-                    nonExcludedInitializers.add(service);
-            }
-        }
-        return nonExcludedInitializers;
+        //test if name of class matches the regex
+        if (LOG.isDebugEnabled()) LOG.debug("Checking {} against containerInitializerExclusionPattern",sci.getClass().getName());
+        return _sciExcludePattern.matcher(sci.getClass().getName()).matches();
     }
     
     
-    
-    
     /**
-     * Scan jars on container path.
+     * Test if the ServletContainerInitializer is from the container classpath
      * 
      * @param context
-     * @param parser
+     * @param sci
+     * @return
+     */
+    public boolean isFromContainerClassPath (WebAppContext context, ServletContainerInitializer sci)
+    {
+        if (sci == null)
+            return false;
+        return sci.getClass().getClassLoader()==context.getClassLoader().getParent();
+    }
+
+    /**
+     * @param context
+     * @return list of non-excluded {@link ServletContainerInitializer}s
      * @throws Exception
      */
-    public void parseContainerPath (final WebAppContext context, final AnnotationParser parser)
+    public List<ServletContainerInitializer> getNonExcludedInitializers (WebAppContext context)
     throws Exception
     {
-        //if no pattern for the container path is defined, then by default scan NOTHING
-        LOG.debug("Scanning container jars");
-        
-        //always parse for discoverable annotations as well as class hierarchy and servletcontainerinitializer related annotations
-        parser.clearHandlers();
-        for (DiscoverableAnnotationHandler h:_discoverableAnnotationHandlers)
+        ArrayList<ServletContainerInitializer> nonExcludedInitializers = new ArrayList<ServletContainerInitializer>();
+
+        //We use the ServiceLoader mechanism to find the ServletContainerInitializer classes to inspect
+        long start = 0;
+
+        ClassLoader old = Thread.currentThread().getContextClassLoader();
+        ServiceLoader<ServletContainerInitializer> loadedInitializers = null;
+        try
+        {        
+            if (LOG.isDebugEnabled())
+                start = System.nanoTime();
+            Thread.currentThread().setContextClassLoader(context.getClassLoader());
+            loadedInitializers = ServiceLoader.load(ServletContainerInitializer.class);
+        }
+        finally
         {
-            if (h instanceof AbstractDiscoverableAnnotationHandler)
-                ((AbstractDiscoverableAnnotationHandler)h).setResource(null); //
+            Thread.currentThread().setContextClassLoader(old);
         }
-        parser.registerHandlers(_discoverableAnnotationHandlers);
-        parser.registerHandler(_classInheritanceHandler);
-        parser.registerHandlers(_containerInitializerAnnotationHandlers);
         
-        //Convert from Resource to URI
-        ArrayList<URI> containerUris = new ArrayList<URI>();
-        for (Resource r : context.getMetaData().getOrderedContainerJars())
+        if (LOG.isDebugEnabled())
+            LOG.debug("Service loaders found in {}ms", (TimeUnit.MILLISECONDS.convert((System.nanoTime()-start), TimeUnit.NANOSECONDS)));
+        
+        
+        Map<ServletContainerInitializer,Resource> sciResourceMap = new HashMap<ServletContainerInitializer,Resource>();
+        ServletContainerInitializerOrdering initializerOrdering = getInitializerOrdering(context);
+
+        //Get initial set of SCIs that aren't from excluded jars or excluded by the containerExclusionPattern, or excluded
+        //because containerInitializerOrdering omits it
+        for (ServletContainerInitializer sci:loadedInitializers)
+        {       
+            if (matchesExclusionPattern(sci)) 
+            {
+                if (LOG.isDebugEnabled()) LOG.debug("{} excluded by pattern", sci);
+                continue;
+            }
+            
+            Resource sciResource = getJarFor(sci);
+            if (isFromExcludedJar(context, sci, sciResource)) 
+            { 
+                if (LOG.isDebugEnabled()) LOG.debug("{} is from excluded jar", sci);
+                continue;
+            }
+            
+            //check containerInitializerOrdering doesn't exclude it
+            String name = sci.getClass().getName();
+            if (initializerOrdering != null
+                && (!initializerOrdering.hasWildcard() && initializerOrdering.getIndexOf(name) < 0))
+            {
+                if (LOG.isDebugEnabled()) LOG.debug("{} is excluded by ordering", sci);
+                continue;
+            }
+            
+            sciResourceMap.put(sci, sciResource);
+        }
+
+        //Order the SCIs that are included
+        if (initializerOrdering != null && !initializerOrdering.isDefaultOrder())
         {
-            URI uri = r.getURI();
-                containerUris.add(uri);          
+            if (LOG.isDebugEnabled())
+                LOG.debug("Ordering ServletContainerInitializers with "+initializerOrdering);
+
+            //There is an ordering that is not just "*".
+            //Arrange ServletContainerInitializers according to the ordering of classnames given, irrespective of coming from container or webapp classpaths
+            nonExcludedInitializers.addAll(sciResourceMap.keySet());
+            Collections.sort(nonExcludedInitializers, new ServletContainerInitializerComparator(initializerOrdering));
         }
-        
-        parser.parse (containerUris.toArray(new URI[containerUris.size()]),
-                new ClassNameResolver ()
+        else
+        {
+            //No jetty-specific ordering specified, or just the wildcard value "*" specified.
+            //Fallback to ordering the ServletContainerInitializers according to:
+            //container classpath first, WEB-INF/classes then WEB-INF/lib (obeying any web.xml jar ordering)
+             
+            //no web.xml ordering defined, add SCIs in any order
+            if (context.getMetaData().getOrdering() == null)
+            {
+                if (LOG.isDebugEnabled())  LOG.debug("No web.xml ordering, ServletContainerInitializers in random order");
+                nonExcludedInitializers.addAll(sciResourceMap.keySet());
+            }
+            else
+            {
+                if (LOG.isDebugEnabled())  LOG.debug("Ordering ServletContainerInitializers with ordering {}",context.getMetaData().getOrdering());
+                for (Map.Entry<ServletContainerInitializer, Resource> entry:sciResourceMap.entrySet())
                 {
-                    public boolean isExcluded (String name)
+                    //add in SCIs from the container classpath
+                    if (entry.getKey().getClass().getClassLoader()==context.getClassLoader().getParent())
+                        nonExcludedInitializers.add(entry.getKey());
+                    else if (entry.getValue() == null) //add in SCIs not in a jar, as they must be from WEB-INF/classes and can't be ordered
+                        nonExcludedInitializers.add(entry.getKey());
+                }
+              
+                //add SCIs according to the ordering of its containing jar
+                for (Resource webInfJar:context.getMetaData().getOrderedWebInfJars())
+                {
+                    for (Map.Entry<ServletContainerInitializer, Resource> entry:sciResourceMap.entrySet())
                     {
-                        if (context.isSystemClass(name)) return false;
-                        if (context.isServerClass(name)) return true;
-                        return false;
+                        if (webInfJar.equals(entry.getValue()))
+                            nonExcludedInitializers.add(entry.getKey());
                     }
+                }
+            }
+        }   
 
-                    public boolean shouldOverride (String name)
-                    { 
-                        //looking at system classpath
-                        if (context.isParentLoaderPriority())
-                            return true;
-                        return false;
-                    }
-                });   
+        if (LOG.isDebugEnabled()) 
+        {
+            int i=0;
+            for (ServletContainerInitializer sci:nonExcludedInitializers)
+                LOG.debug("ServletContainerInitializer: {} {}",(++i), sci.getClass().getName());
+        }
+        return nonExcludedInitializers;
+    }
+
+
+    /**
+     * Jetty-specific extension that allows an ordering to be applied across ALL ServletContainerInitializers.
+     * 
+     * @return
+     */
+    public ServletContainerInitializerOrdering getInitializerOrdering (WebAppContext context)
+    {
+        if (context == null)
+            return null;
         
-         
+        String tmp = (String)context.getAttribute(SERVLET_CONTAINER_INITIALIZER_ORDER);
+        if (tmp == null || "".equals(tmp.trim()))
+            return null;
+        
+        return new ServletContainerInitializerOrdering(tmp);
     }
-    
-    
+
+
+    /**
+     * Scan jars on container path.
+     * 
+     * @param context
+     * @param parser
+     * @throws Exception
+     */
+    public void parseContainerPath (final WebAppContext context, final AnnotationParser parser) throws Exception
+    {
+        //always parse for discoverable annotations as well as class hierarchy and servletcontainerinitializer related annotations
+        final Set<Handler> handlers = new HashSet<Handler>();
+        handlers.addAll(_discoverableAnnotationHandlers);
+        handlers.addAll(_containerInitializerAnnotationHandlers);
+        if (_classInheritanceHandler != null)
+            handlers.add(_classInheritanceHandler);
+
+        _containerPathStats = new CounterStatistic();
+
+        for (Resource r : context.getMetaData().getContainerResources())
+        {
+            //queue it up for scanning if using multithreaded mode
+            if (_parserTasks != null)
+            {
+                ParserTask task = new ParserTask(parser, handlers, r, _containerClassNameResolver);
+                _parserTasks.add(task);  
+                _containerPathStats.increment();
+                if (LOG.isDebugEnabled())
+                    task.setStatistic(new TimeStatistic());
+            }
+        } 
+    }
+
+
     /**
      * Scan jars in WEB-INF/lib
      * 
@@ -373,29 +1007,32 @@ public class AnnotationConfiguration extends AbstractConfiguration
      * @param parser
      * @throws Exception
      */
-    public void parseWebInfLib (final WebAppContext context, final AnnotationParser parser)
-    throws Exception
-    {  
+    public void parseWebInfLib (final WebAppContext context, final AnnotationParser parser) throws Exception
+    {   
         List<FragmentDescriptor> frags = context.getMetaData().getFragments();
-        
+
         //email from Rajiv Mordani jsrs 315 7 April 2010
         //jars that do not have a web-fragment.xml are still considered fragments
         //they have to participate in the ordering
         ArrayList<URI> webInfUris = new ArrayList<URI>();
+
+        List<Resource> jars = null;
         
-        List<Resource> jars = context.getMetaData().getOrderedWebInfJars();
-        
-        //No ordering just use the jars in any order
-        if (jars == null || jars.isEmpty())
+        if (context.getMetaData().getOrdering() != null)
+            jars = context.getMetaData().getOrderedWebInfJars();
+        else
+            //No ordering just use the jars in any order
             jars = context.getMetaData().getWebInfJars();
-   
+
+        _webInfLibStats = new CounterStatistic();
+
         for (Resource r : jars)
-        {          
+        {
             //for each jar, we decide which set of annotations we need to parse for
-            parser.clearHandlers();
-            URI uri  = r.getURI();
+            final Set<Handler> handlers = new HashSet<Handler>();
+
             FragmentDescriptor f = getFragmentFromJar(r, frags);
-           
+
             //if its from a fragment jar that is metadata complete, we should skip scanning for @webservlet etc
             // but yet we still need to do the scanning for the classes on behalf of  the servletcontainerinitializers
             //if a jar has no web-fragment.xml we scan it (because it is not excluded by the ordering)
@@ -403,44 +1040,29 @@ public class AnnotationConfiguration extends AbstractConfiguration
             if (f == null || !isMetaDataComplete(f) || _classInheritanceHandler != null ||  !_containerInitializerAnnotationHandlers.isEmpty())
             {
                 //register the classinheritance handler if there is one
-                parser.registerHandler(_classInheritanceHandler);
-                
+                if (_classInheritanceHandler != null)
+                    handlers.add(_classInheritanceHandler);
+
                 //register the handlers for the @HandlesTypes values that are themselves annotations if there are any
-                parser.registerHandlers(_containerInitializerAnnotationHandlers);
-                
+                handlers.addAll(_containerInitializerAnnotationHandlers);
+
                 //only register the discoverable annotation handlers if this fragment is not metadata complete, or has no fragment descriptor
                 if (f == null || !isMetaDataComplete(f))
+                    handlers.addAll(_discoverableAnnotationHandlers);
+
+                if (_parserTasks != null)
                 {
-                    for (DiscoverableAnnotationHandler h:_discoverableAnnotationHandlers)
-                    {
-                        if (h instanceof AbstractDiscoverableAnnotationHandler)
-                            ((AbstractDiscoverableAnnotationHandler)h).setResource(r);
-                    }
-                    parser.registerHandlers(_discoverableAnnotationHandlers);
+                    ParserTask task = new ParserTask(parser, handlers,r, _webAppClassNameResolver);
+                    _parserTasks.add (task);
+                    _webInfLibStats.increment();
+                    if (LOG.isDebugEnabled())
+                        task.setStatistic(new TimeStatistic());
                 }
-
-                parser.parse(uri, 
-                             new ClassNameResolver()
-                             {
-                                 public boolean isExcluded (String name)
-                                 {    
-                                     if (context.isSystemClass(name)) return true;
-                                     if (context.isServerClass(name)) return false;
-                                     return false;
-                                 }
-
-                                 public boolean shouldOverride (String name)
-                                 {
-                                    //looking at webapp classpath, found already-parsed class of same name - did it come from system or duplicate in webapp?
-                                    if (context.isParentLoaderPriority())
-                                        return false;
-                                    return true;
-                                 }
-                             });   
             }
         }
     }
-     
+
+
     /**
      * Scan classes in WEB-INF/classes
      * 
@@ -451,52 +1073,35 @@ public class AnnotationConfiguration extends AbstractConfiguration
     public void parseWebInfClasses (final WebAppContext context, final AnnotationParser parser)
     throws Exception
     {
-        LOG.debug("Scanning classes in WEB-INF/classes");
-        if (context.getWebInf() != null)
+        Set<Handler> handlers = new HashSet<Handler>();
+        handlers.addAll(_discoverableAnnotationHandlers);
+        if (_classInheritanceHandler != null)
+            handlers.add(_classInheritanceHandler);
+        handlers.addAll(_containerInitializerAnnotationHandlers);
+
+        _webInfClassesStats = new CounterStatistic();
+
+        for (Resource dir : context.getMetaData().getWebInfClassesDirs())
         {
-            Resource classesDir = context.getWebInf().addPath("classes/");
-            if (classesDir.exists())
+            if (_parserTasks != null)
             {
-                parser.clearHandlers();
-                for (DiscoverableAnnotationHandler h:_discoverableAnnotationHandlers)
-                {
-                    if (h instanceof AbstractDiscoverableAnnotationHandler)
-                        ((AbstractDiscoverableAnnotationHandler)h).setResource(null); //
-                }
-                parser.registerHandlers(_discoverableAnnotationHandlers);
-                parser.registerHandler(_classInheritanceHandler);
-                parser.registerHandlers(_containerInitializerAnnotationHandlers);
-                
-                parser.parse(classesDir, 
-                             new ClassNameResolver()
-                {
-                    public boolean isExcluded (String name)
-                    {
-                        if (context.isSystemClass(name)) return true;
-                        if (context.isServerClass(name)) return false;
-                        return false;
-                    }
-
-                    public boolean shouldOverride (String name)
-                    {
-                        //looking at webapp classpath, found already-parsed class of same name - did it come from system or duplicate in webapp?
-                        if (context.isParentLoaderPriority())
-                            return false;
-                        return true;
-                    }
-                });
+                ParserTask task = new ParserTask(parser, handlers, dir, _webAppClassNameResolver);
+                _parserTasks.add(task);
+                _webInfClassesStats.increment();
+                if (LOG.isDebugEnabled())
+                    task.setStatistic(new TimeStatistic());
             }
         }
     }
-    
- 
-    
+
+
+
     /**
      * Get the web-fragment.xml from a jar
      * 
      * @param jar
      * @param frags
-     * @return
+     * @return the fragment if found, or null of not found
      * @throws Exception
      */
     public FragmentDescriptor getFragmentFromJar (Resource jar,  List<FragmentDescriptor> frags)
@@ -515,9 +1120,21 @@ public class AnnotationConfiguration extends AbstractConfiguration
         }
         return d;
     }
-    
+
     public boolean isMetaDataComplete (WebDescriptor d)
     {
         return (d!=null && d.getMetaDataComplete() == MetaDataComplete.True);
     }
+
+    public static class ClassInheritanceMap extends ConcurrentHashMap<String, ConcurrentHashSet<String>>
+    {
+        
+        @Override
+        public String toString()
+        {
+            return String.format("ClassInheritanceMap@%x{size=%d}",hashCode(),size());
+        }
+    }
 }
+
+
diff --git a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationDecorator.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationDecorator.java
index 4e0fe12..3f07ebc 100644
--- a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationDecorator.java
+++ b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationDecorator.java
@@ -18,32 +18,28 @@
 
 package org.eclipse.jetty.annotations;
 
-import java.util.EventListener;
-
-import javax.servlet.Filter;
-import javax.servlet.Servlet;
-import javax.servlet.ServletException;
-
-import org.eclipse.jetty.server.handler.ContextHandler;
-import org.eclipse.jetty.servlet.FilterHolder;
-import org.eclipse.jetty.servlet.ServletHolder;
 import org.eclipse.jetty.servlet.ServletContextHandler.Decorator;
 import org.eclipse.jetty.webapp.WebAppContext;
 
 /**
- * WebAppDecoratorWrapper
+ * AnnotationDecorator
  *
  *
  */
 public class AnnotationDecorator implements Decorator
 {
-    AnnotationIntrospector _introspector = new AnnotationIntrospector();
-    
+    protected AnnotationIntrospector _introspector = new AnnotationIntrospector();
+
     /**
      * @param context
      */
     public AnnotationDecorator(WebAppContext context)
     {
+       registerHandlers(context);
+    }
+    
+    public void registerHandlers (WebAppContext context)
+    {
         _introspector.registerHandler(new ResourceAnnotationHandler(context));
         _introspector.registerHandler(new ResourcesAnnotationHandler(context));
         _introspector.registerHandler(new RunAsAnnotationHandler(context));
@@ -54,99 +50,6 @@ public class AnnotationDecorator implements Decorator
         _introspector.registerHandler(new ServletSecurityAnnotationHandler(context));
     }
 
-    /* ------------------------------------------------------------ */
-    /**
-     * @param filter
-     * @throws ServletException
-     * @see org.eclipse.jetty.servlet.ServletContextHandler.Decorator#decorateFilterHolder(org.eclipse.jetty.servlet.FilterHolder)
-     */
-    public void decorateFilterHolder(FilterHolder filter) throws ServletException
-    {
-    }
-    
-    /* ------------------------------------------------------------ */
-    /**
-     * @param <T>
-     * @param filter
-     * @return the decorated filter
-     * @throws ServletException
-     * @see org.eclipse.jetty.servlet.ServletContextHandler.Decorator#decorateFilterInstance(javax.servlet.Filter)
-     */
-    public <T extends Filter> T decorateFilterInstance(T filter) throws ServletException
-    {
-        introspect(filter);
-        return filter;
-    }
-    
-    /* ------------------------------------------------------------ */
-    /**
-     * @param <T>
-     * @param listener
-     * @return the decorated event listener instance
-     * @throws ServletException
-     * @see org.eclipse.jetty.servlet.ServletContextHandler.Decorator#decorateListenerInstance(java.util.EventListener)
-     */
-    public <T extends EventListener> T decorateListenerInstance(T listener) throws ServletException
-    {
-        introspect(listener);
-        return listener;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param servlet
-     * @throws ServletException
-     * @see org.eclipse.jetty.servlet.ServletContextHandler.Decorator#decorateServletHolder(org.eclipse.jetty.servlet.ServletHolder)
-     */
-    public void decorateServletHolder(ServletHolder servlet) throws ServletException
-    {
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param <T>
-     * @param servlet
-     * @return the decorated servlet instance
-     * @throws ServletException
-     * @see org.eclipse.jetty.servlet.ServletContextHandler.Decorator#decorateServletInstance(javax.servlet.Servlet)
-     */
-    public <T extends Servlet> T decorateServletInstance(T servlet) throws ServletException
-    {
-        introspect(servlet);
-        return servlet;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param f
-     * @see org.eclipse.jetty.servlet.ServletContextHandler.Decorator#destroyFilterInstance(javax.servlet.Filter)
-     */
-    public void destroyFilterInstance(Filter f)
-    {
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param s
-     * @see org.eclipse.jetty.servlet.ServletContextHandler.Decorator#destroyServletInstance(javax.servlet.Servlet)
-     */
-    public void destroyServletInstance(Servlet s)
-    {
-    }
-
-    
-    
-
-  
-    /* ------------------------------------------------------------ */
-    /**
-     * @param f
-     * @see org.eclipse.jetty.servlet.ServletContextHandler.Decorator#destroyListenerInstance(java.util.EventListener)
-     */
-    public void destroyListenerInstance(EventListener f)
-    {
-    }
-
     /**
      * Look for annotations that can be discovered with introspection:
      * <ul>
@@ -162,4 +65,17 @@ public class AnnotationDecorator implements Decorator
     {
         _introspector.introspect(o.getClass());
     }
+
+    @Override
+    public Object decorate(Object o)
+    {
+       introspect(o);
+       return o;
+    }
+
+    @Override
+    public void destroy(Object o)
+    {
+        
+    }
 }
diff --git a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationParser.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationParser.java
index cab5ada..0c55a52 100644
--- a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationParser.java
+++ b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationParser.java
@@ -18,597 +18,528 @@
 
 package org.eclipse.jetty.annotations;
 
+import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.URI;
 import java.net.URL;
 import java.net.URLClassLoader;
-import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Locale;
 import java.util.Set;
 import java.util.jar.JarEntry;
+import java.util.jar.JarInputStream;
 
+import org.eclipse.jetty.util.ConcurrentHashSet;
 import org.eclipse.jetty.util.Loader;
+import org.eclipse.jetty.util.MultiException;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
 import org.eclipse.jetty.util.resource.Resource;
 import org.eclipse.jetty.webapp.JarScanner;
 import org.objectweb.asm.AnnotationVisitor;
 import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
 import org.objectweb.asm.FieldVisitor;
 import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.commons.EmptyVisitor;
+import org.objectweb.asm.Opcodes;
+
 
 /**
  * AnnotationParser
+ *
+ * Use asm to scan classes for annotations. A SAX-style parsing is done.
+ * Handlers are registered which will be called back when various types of
+ * entity are encountered, eg a class, a method, a field. 
+ * 
+ * Handlers are not called back in any particular order and are assumed
+ * to be order-independent.
  * 
- * Use asm to scan classes for annotations. A SAX-style parsing is done, with
- * a handler being able to be registered to handle each annotation type.
+ * As a registered Handler will be called back for each annotation discovered
+ * on a class, a method, a field, the Handler should test to see if the annotation
+ * is one that it is interested in.
+ * 
+ * For the servlet spec, we are only interested in annotations on classes, methods and fields,
+ * so the callbacks for handling finding a class, a method a field are themselves
+ * not fully implemented.
  */
 public class AnnotationParser
 {
     private static final Logger LOG = Log.getLogger(AnnotationParser.class);
- 
-    protected Set<String> _parsedClassNames = new HashSet<String>();
-    protected List<Handler> _handlers = new ArrayList<Handler>();
+
+    protected Set<String> _parsedClassNames = new ConcurrentHashSet<String>();
     
+    protected static int ASM_OPCODE_VERSION = Opcodes.ASM5; //compatibility of api
+   
+
+    /**
+     * Convert internal name to simple name
+     * 
+     * @param name
+     * @return
+     */
     public static String normalize (String name)
     {
         if (name==null)
             return null;
-        
+
         if (name.startsWith("L") && name.endsWith(";"))
             name = name.substring(1, name.length()-1);
-        
+
         if (name.endsWith(".class"))
             name = name.substring(0, name.length()-".class".length());
-        
+
         return name.replace('/', '.');
     }
     
+    /**
+     * Convert internal names to simple names.
+     * 
+     * @param list
+     * @return
+     */
+    public static String[] normalize (String[] list)
+    {
+        if (list == null)
+            return null;       
+        String[] normalList = new String[list.length];
+        int i=0;
+        for (String s : list)
+            normalList[i++] = normalize(s);
+        return normalList;
+    }
 
     
-    public abstract class Value
+    /**
+     * ClassInfo
+     * 
+     * Immutable information gathered by parsing class header.
+     * 
+     */
+    public class ClassInfo 
     {
-        String _name;
+        final Resource _containingResource;
+        final String _className;
+        final int _version;
+        final int _access;
+        final String _signature;
+        final String _superName; 
+        final String[] _interfaces;
         
-        public Value (String name)
+        public ClassInfo(Resource resource, String className, int version, int access, String signature, String superName, String[] interfaces)
         {
-            _name = name;
+            super();
+            _containingResource = resource;
+            _className = className;
+            _version = version;
+            _access = access;
+            _signature = signature;
+            _superName = superName;
+            _interfaces = interfaces;
         }
-        
-        public String getName()
+
+        public String getClassName()
         {
-            return _name;
+            return _className;
         }
-        
-        public abstract Object getValue();
-           
-    }
-    
-   
- 
-    
-    public class SimpleValue extends Value
-    {
-        Object _val;
-        
-        public SimpleValue(String name)
+
+        public int getVersion()
         {
-            super(name);
+            return _version;
         }
-        
-        public void setValue(Object val)
+
+        public int getAccess()
         {
-            _val=val;
-        } 
-        public Object getValue()
+            return _access;
+        }
+
+        public String getSignature()
         {
-            return _val;
-        } 
-        
-        public String toString()
+            return _signature;
+        }
+
+        public String getSuperName()
+        {
+            return _superName;
+        }
+
+        public String[] getInterfaces()
         {
-            return "("+getName()+":"+_val+")";
+            return _interfaces;
+        }
+
+        public Resource getContainingResource()
+        {
+            return _containingResource;
         }
     }
+
     
-    public class ListValue extends Value
+    /**
+     * MethodInfo
+     * 
+     * Immutable information gathered by parsing a method on a class.
+     */
+    public class MethodInfo
     {
-        List<Value> _val;
+        final ClassInfo _classInfo;
+        final String _methodName; 
+        final int _access;
+        final String _desc; 
+        final String _signature;
+        final String[] _exceptions;
         
-        public ListValue (String name)
+        public MethodInfo(ClassInfo classInfo, String methodName, int access, String desc, String signature, String[] exceptions)
         {
-            super(name);
-            _val = new ArrayList<Value>();
+            super();
+            _classInfo = classInfo;
+            _methodName = methodName;
+            _access = access;
+            _desc = desc;
+            _signature = signature;
+            _exceptions = exceptions;
         }
-      
-        public Object getValue()
+
+        public ClassInfo getClassInfo()
         {
-            return _val;
+            return _classInfo;
         }
-        
-        public List<Value> getList()
+
+        public String getMethodName()
         {
-            return _val;
+            return _methodName;
         }
-        
-        public void addValue (Value v)
+
+        public int getAccess()
         {
-            _val.add(v);
+            return _access;
         }
-        
-        public int size ()
+
+        public String getDesc()
         {
-            return _val.size();
+            return _desc;
         }
-        
-        public String toString()
+
+        public String getSignature()
         {
-            StringBuffer buff = new StringBuffer();
-            buff.append("(");
-            buff.append(getName());
-            buff.append(":");
-            for (Value n: _val)
-            {
-                buff.append(" "+n.toString());
-            }
-            buff.append(")");
-            
-            return buff.toString();
+            return _signature;
+        }
+
+        public String[] getExceptions()
+        {
+            return _exceptions;
         }
     }
     
     
     
     /**
-     * Handler
-     *
-     * Signature for all handlers that respond to parsing class files.
-     */
-    public interface Handler
-    {
-       
-    }
-    
-    
-    
-    /**
-     * DiscoverableAnnotationHandler
+     * FieldInfo
      *
-     * Processes an annotation when it is discovered on a class.
+     * Immutable information gathered by parsing a field on a class.
+     * 
      */
-    public interface DiscoverableAnnotationHandler extends Handler
+    public class FieldInfo
     {
-        /**
-         * Process an annotation that was discovered on a class
-         * @param className
-         * @param version
-         * @param access
-         * @param signature
-         * @param superName
-         * @param interfaces
-         * @param annotation
-         * @param values
-         */
-        public void handleClass (String className, int version, int access, 
-                                 String signature, String superName, String[] interfaces, 
-                                 String annotation, List<Value>values);
-        
-        /**
-         * Process an annotation that was discovered on a method
-         * @param className
-         * @param methodName
-         * @param access
-         * @param desc
-         * @param signature
-         * @param exceptions
-         * @param annotation
-         * @param values
-         */
-        public void handleMethod (String className, String methodName, int access,  
-                                  String desc, String signature,String[] exceptions, 
-                                  String annotation, List<Value>values);
-        
+        final ClassInfo _classInfo;
+        final String _fieldName;
+        final int _access;
+        final String _fieldType;
+        final String _signature;
+        final Object _value;
         
-        /**
-         * Process an annotation that was discovered on a field
-         * @param className
-         * @param fieldName
-         * @param access
-         * @param fieldType
-         * @param signature
-         * @param value
-         * @param annotation
-         * @param values
-         */
-        public void handleField (String className, String fieldName,  int access, 
-                                 String fieldType, String signature, Object value, 
-                                 String annotation, List<Value>values);
-        
-        
-        /**
-         * Get the name of the annotation processed by this handler. Can be null
-         * 
-         * @return
-         */
-        public String getAnnotationName();
+        public FieldInfo(ClassInfo classInfo, String fieldName, int access, String fieldType, String signature, Object value)
+        {
+            super();
+            _classInfo = classInfo;
+            _fieldName = fieldName;
+            _access = access;
+            _fieldType = fieldType;
+            _signature = signature;
+            _value = value;
+        }
+
+        public ClassInfo getClassInfo()
+        {
+            return _classInfo;
+        }
+
+        public String getFieldName()
+        {
+            return _fieldName;
+        }
+
+        public int getAccess()
+        {
+            return _access;
+        }
+
+        public String getFieldType()
+        {
+            return _fieldType;
+        }
+
+        public String getSignature()
+        {
+            return _signature;
+        }
+
+        public Object getValue()
+        {
+            return _value;
+        }
     }
     
     
-    
     /**
-     * ClassHandler
+     * Handler
      *
-     * Responds to finding a Class
+     * Signature for all handlers that respond to parsing class files.
      */
-    public interface ClassHandler extends Handler
+    public static interface Handler
     {
-        public void handle (String className, int version, int access, String signature, String superName, String[] interfaces);
+        public void handle(ClassInfo classInfo);
+        public void handle(MethodInfo methodInfo);
+        public void handle (FieldInfo fieldInfo);
+        public void handle (ClassInfo info, String annotationName);
+        public void handle (MethodInfo info, String annotationName);
+        public void handle (FieldInfo info, String annotationName);
     }
     
     
     
     /**
-     * MethodHandler
-     *
-     * Responds to finding a Method
-     */
-    public interface MethodHandler extends Handler
-    {
-        public void handle (String className, String methodName, int access,  String desc, String signature,String[] exceptions);
-    }
-    
-    
-    /**
-     * FieldHandler
+     * AbstractHandler
      *
-     * Responds to finding a Field
+     * Convenience base class to provide no-ops for all Handler methods.
+     * 
      */
-    public interface FieldHandler extends Handler
+    public static abstract class AbstractHandler implements Handler
     {
-        public void handle (String className, String fieldName, int access, String fieldType, String signature, Object value);
+
+        @Override
+        public void handle(ClassInfo classInfo)
+        {
+           //no-op
+        }
+
+        @Override
+        public void handle(MethodInfo methodInfo)
+        {
+            // no-op           
+        }
+
+        @Override
+        public void handle(FieldInfo fieldInfo)
+        {
+            // no-op 
+        }
+
+        @Override
+        public void handle(ClassInfo info, String annotationName)
+        {
+            // no-op 
+        }
+
+        @Override
+        public void handle(MethodInfo info, String annotationName)
+        {
+            // no-op            
+        }
+
+        @Override
+        public void handle(FieldInfo info, String annotationName)
+        {
+           // no-op
+        }        
     }
-    
+
     
     
     /**
-     * MyAnnotationVisitor
-     *
-     * ASM Visitor for Annotations
+     * MyMethodVisitor
+     * 
+     * ASM Visitor for parsing a method. We are only interested in the annotations on methods.
      */
-    public class MyAnnotationVisitor implements AnnotationVisitor
+    public class MyMethodVisitor extends MethodVisitor
     {
-        List<Value> _annotationValues;
-        String _annotationName;
+        final MethodInfo _mi;
+        final Set<? extends Handler> _handlers;
         
-        public MyAnnotationVisitor (String annotationName, List<Value> values)
+        public MyMethodVisitor(final Set<? extends Handler> handlers,
+                               final ClassInfo classInfo,
+                               final int access,
+                               final String name,
+                               final String methodDesc,
+                               final String signature,
+                               final String[] exceptions)
         {
-            _annotationValues = values;
-            _annotationName = annotationName;
-        }
-        
-        public List<Value> getAnnotationValues()
-        {
-            return _annotationValues;
+            super(ASM_OPCODE_VERSION);
+            _handlers = handlers;
+            _mi = new MethodInfo(classInfo, name, access, methodDesc,signature, exceptions);
         }
 
-        /** 
-         * Visit a single-valued (name,value) pair for this annotation
-         * @see org.objectweb.asm.AnnotationVisitor#visit(java.lang.String, java.lang.Object)
+        
+        /**
+         * We are only interested in finding the annotations on methods.
+         * 
+         * @see org.objectweb.asm.MethodVisitor#visitAnnotation(java.lang.String, boolean)
          */
-        public void visit(String aname, Object avalue)
+        @Override
+        public AnnotationVisitor visitAnnotation(String desc, boolean visible)
         {
-           SimpleValue v = new SimpleValue(aname);
-           v.setValue(avalue);
-           _annotationValues.add(v);
+            String annotationName = normalize(desc);
+            for (Handler h:_handlers)
+                h.handle(_mi, annotationName);
+            return null;
         }
+    }
 
-        /** 
-         * Visit a (name,value) pair whose value is another Annotation
-         * @see org.objectweb.asm.AnnotationVisitor#visitAnnotation(java.lang.String, java.lang.String)
-         */
-        public AnnotationVisitor visitAnnotation(String name, String desc)
-        {
-            String s = normalize(desc);
-            ListValue v = new ListValue(s);
-            _annotationValues.add(v);
-            MyAnnotationVisitor visitor = new MyAnnotationVisitor(s, v.getList());
-            return visitor; 
-        }
 
-        /** 
-         * Visit an array valued (name, value) pair for this annotation
-         * @see org.objectweb.asm.AnnotationVisitor#visitArray(java.lang.String)
-         */
-        public AnnotationVisitor visitArray(String name)
+    
+    /**
+     * MyFieldVisitor
+     * 
+     * An ASM visitor for parsing Fields. 
+     * We are only interested in visiting annotations on Fields.
+     *
+     */
+    public class MyFieldVisitor extends FieldVisitor
+    {   
+        final FieldInfo _fieldInfo;
+        final Set<? extends Handler> _handlers;
+        
+    
+        public MyFieldVisitor(final Set<? extends Handler> handlers,
+                              final ClassInfo classInfo,
+                              final int access,
+                              final String fieldName,
+                              final String fieldType,
+                              final String signature,
+                              final Object value)
         {
-            ListValue v = new ListValue(name);
-            _annotationValues.add(v);
-            MyAnnotationVisitor visitor = new MyAnnotationVisitor(null, v.getList());
-            return visitor; 
+            super(ASM_OPCODE_VERSION);
+            _handlers = handlers;
+            _fieldInfo = new FieldInfo(classInfo, fieldName, access, fieldType, signature, value);
         }
 
-        /** 
-         * Visit a enum-valued (name,value) pair for this annotation
-         * @see org.objectweb.asm.AnnotationVisitor#visitEnum(java.lang.String, java.lang.String, java.lang.String)
+
+        /**
+         * Parse an annotation found on a Field.
+         * 
+         * @see org.objectweb.asm.FieldVisitor#visitAnnotation(java.lang.String, boolean)
          */
-        public void visitEnum(String name, String desc, String value)
+        @Override
+        public AnnotationVisitor visitAnnotation(String desc, boolean visible)
         {
-            //TODO
-        }
-        
-        public void visitEnd()
-        {   
+            String annotationName = normalize(desc);
+            for (Handler h : _handlers)
+               h.handle(_fieldInfo, annotationName);
+
+            return null;
         }
     }
-    
-    
 
-    
+  
+
+
     /**
      * MyClassVisitor
      *
      * ASM visitor for a class.
      */
-    public class MyClassVisitor extends EmptyVisitor
+    public class MyClassVisitor extends ClassVisitor
     {
-        String _className;
-        int _access;
-        String _signature;
-        String _superName;
-        String[] _interfaces;
-        int _version;
+
+        final Resource _containingResource;
+        final Set<? extends Handler> _handlers;
+        ClassInfo _ci;
+        
+        public MyClassVisitor(Set<? extends Handler> handlers, Resource containingResource)
+        {
+            super(ASM_OPCODE_VERSION);
+            _handlers = handlers;
+            _containingResource = containingResource;
+        }
 
 
-        public void visit (int version,
+        @Override
+        public void visit (final int version,
                            final int access,
                            final String name,
                            final String signature,
                            final String superName,
                            final String[] interfaces)
-        {     
-            _className = normalize(name);
-            _access = access;
-            _signature = signature;
-            _superName = superName;
-            _interfaces = interfaces;
-            _version = version;
+        {           
+            _ci = new ClassInfo(_containingResource, normalize(name), version, access, signature, normalize(superName), normalize(interfaces));
             
-            _parsedClassNames.add(_className);
-            //call all registered ClassHandlers
-            String[] normalizedInterfaces = null;
-            if (interfaces!= null)
-            {
-                normalizedInterfaces = new String[interfaces.length];
-                int i=0;
-                for (String s : interfaces)
-                    normalizedInterfaces[i++] = normalize(s);
-            }
+            _parsedClassNames.add(_ci.getClassName());                 
 
-            for (Handler h : AnnotationParser.this._handlers)
-            {
-                if (h instanceof ClassHandler)
-                {
-                    ((ClassHandler)h).handle(_className, _version, _access, _signature, normalize(_superName), normalizedInterfaces);
-                }
-            }
+            for (Handler h:_handlers)
+               h.handle(_ci);
         }
+        
 
+        /**
+         * Visit an annotation on a Class
+         * 
+         * @see org.objectweb.asm.ClassVisitor#visitAnnotation(java.lang.String, boolean)
+         */
+        @Override
         public AnnotationVisitor visitAnnotation (String desc, boolean visible)
-        {                
-            MyAnnotationVisitor visitor = new MyAnnotationVisitor(normalize(desc), new ArrayList<Value>())
-            {
-                public void visitEnd()
-                {   
-                    super.visitEnd();
+        {
+            String annotationName = normalize(desc);
+            for (Handler h : _handlers)
+                h.handle(_ci, annotationName);
 
-                    //call all AnnotationHandlers with classname, annotation name + values
-                    for (Handler h : AnnotationParser.this._handlers)
-                    {
-                        if (h instanceof DiscoverableAnnotationHandler)
-                        {
-                            DiscoverableAnnotationHandler dah = (DiscoverableAnnotationHandler)h;
-                            if (_annotationName.equalsIgnoreCase(dah.getAnnotationName()))
-                                dah.handleClass(_className, _version, _access, _signature, _superName, _interfaces, _annotationName, _annotationValues);
-                        }
-                    }
-                }
-            };
-            
-            return visitor;
+            return null;
         }
 
+
+        /**
+         * Visit a method to extract its annotations
+         * 
+         * @see org.objectweb.asm.ClassVisitor#visitMethod(int, java.lang.String, java.lang.String, java.lang.String, java.lang.String[])
+         */
+        @Override
         public MethodVisitor visitMethod (final int access,
                                           final String name,
                                           final String methodDesc,
                                           final String signature,
                                           final String[] exceptions)
-        {   
+        {
 
-            return new EmptyVisitor ()
-            {
-                public AnnotationVisitor visitAnnotation(String desc, boolean visible)
-                {
-                    MyAnnotationVisitor visitor = new MyAnnotationVisitor (normalize(desc), new ArrayList<Value>())
-                    {
-                        public void visitEnd()
-                        {   
-                            super.visitEnd();
-                            //call all AnnotationHandlers with classname, method, annotation name + values
-                            for (Handler h : AnnotationParser.this._handlers)
-                            {
-                                if (h instanceof DiscoverableAnnotationHandler)
-                                {
-                                    DiscoverableAnnotationHandler dah = (DiscoverableAnnotationHandler)h;
-                                    if (_annotationName.equalsIgnoreCase(dah.getAnnotationName()))
-                                        dah.handleMethod(_className, name, access, methodDesc, signature, exceptions, _annotationName, _annotationValues);
-                                }
-                            }
-                        }
-                    };
-                   
-                    return visitor;
-                }
-            };
+            return new MyMethodVisitor(_handlers, _ci, access, name, methodDesc, signature, exceptions);
         }
 
+        /**
+         * Visit a field to extract its annotations
+         * 
+         * @see org.objectweb.asm.ClassVisitor#visitField(int, java.lang.String, java.lang.String, java.lang.String, java.lang.Object)
+         */
+        @Override
         public FieldVisitor visitField (final int access,
                                         final String fieldName,
                                         final String fieldType,
                                         final String signature,
                                         final Object value)
         {
-
-            return new EmptyVisitor ()
-            {
-                public AnnotationVisitor visitAnnotation(String desc, boolean visible)
-                {
-                    MyAnnotationVisitor visitor = new MyAnnotationVisitor(normalize(desc), new ArrayList<Value>())
-                    {
-                        public void visitEnd()
-                        {
-                            super.visitEnd();
-                            for (Handler h : AnnotationParser.this._handlers)
-                            {
-                                if (h instanceof DiscoverableAnnotationHandler)
-                                {
-                                    DiscoverableAnnotationHandler dah = (DiscoverableAnnotationHandler)h;
-                                    if (_annotationName.equalsIgnoreCase(dah.getAnnotationName()))
-                                        dah.handleField(_className, fieldName, access, fieldType, signature, value, _annotationName, _annotationValues);
-                                }
-                            }
-                        }
-                    };
-                    return visitor;
-                }
-            };
-        }
-    }
-    
-    
-    /**
-     * Register a handler that will be called back when the named annotation is
-     * encountered on a class.
-     * 
-     * @deprecated see registerHandler(Handler)
-     * @param annotationName
-     * @param handler
-     */
-    public void registerAnnotationHandler (String annotationName, DiscoverableAnnotationHandler handler)
-    {
-        _handlers.add(handler);
-    }
-    
-    
-    /**
-     * @deprecated
-     * @param annotationName
-     * @return
-     */
-    public List<DiscoverableAnnotationHandler> getAnnotationHandlers(String annotationName)
-    {
-        List<DiscoverableAnnotationHandler> handlers = new ArrayList<DiscoverableAnnotationHandler>();
-        for (Handler h:_handlers)
-        {
-            if (h instanceof DiscoverableAnnotationHandler)
-            {
-                DiscoverableAnnotationHandler dah = (DiscoverableAnnotationHandler)h;
-                if (annotationName.equals(dah.getAnnotationName()))
-                    handlers.add(dah);
-            }
+            return new MyFieldVisitor(_handlers, _ci, access, fieldName, fieldType, signature, value);
         }
- 
-        return handlers;
     }
 
-    /**
-     * @deprecated
-     * @return
-     */
-    public List<DiscoverableAnnotationHandler> getAnnotationHandlers()
-    {
-        List<DiscoverableAnnotationHandler> allAnnotationHandlers = new ArrayList<DiscoverableAnnotationHandler>();
-        for (Handler h:_handlers)
-        {
-            if (h instanceof DiscoverableAnnotationHandler)
-            allAnnotationHandlers.add((DiscoverableAnnotationHandler)h);
-        }
-        return allAnnotationHandlers;
-    }
-
-    /**
-     * @deprecated see registerHandler(Handler)
-     * @param handler
-     */
-    public void registerClassHandler (ClassHandler handler)
-    {
-        _handlers.add(handler);
-    }
-    
-    
-    
-    /**
-     * Add a particular handler
-     * 
-     * @param h
-     */
-    public void registerHandler(Handler h)
-    {
-        if (h == null)
-            return;
-        
-        _handlers.add(h);
-    }
-    
-    
-    /**
-     * Add a list of handlers
-     * 
-     * @param handlers
-     */
-    public void registerHandlers(List<? extends Handler> handlers)
-    {
-        if (handlers == null)
-            return;
-        _handlers.addAll(handlers);
-    }
-    
-    
-    /**
-     * Remove a particular handler
-     * 
-     * @param h
-     * @return
-     */
-    public boolean deregisterHandler(Handler h)
-    {
-        return _handlers.remove(h);
-    }
-    
-    
-    /**
-     * Remove all registered handlers
-     */
-    public void clearHandlers()
-    {
-        _handlers.clear();
-    }
-    
+ 
 
     /**
      * True if the class has already been processed, false otherwise
      * @param className
-     * @return
      */
     public boolean isParsed (String className)
     {
         return _parsedClassNames.contains(className);
     }
-    
+
     
     
     /**
@@ -618,27 +549,27 @@ public class AnnotationParser
      * @param resolver
      * @throws Exception
      */
-    public void parse (String className, ClassNameResolver resolver) 
+    public void parse (Set<? extends Handler> handlers, String className, ClassNameResolver resolver)
     throws Exception
     {
         if (className == null)
             return;
-        
+
         if (!resolver.isExcluded(className))
         {
             if (!isParsed(className) || resolver.shouldOverride(className))
             {
                 className = className.replace('.', '/')+".class";
-                URL resource = Loader.getResource(this.getClass(), className, false);
+                URL resource = Loader.getResource(this.getClass(), className);
                 if (resource!= null)
                 {
                     Resource r = Resource.newResource(resource);
-                    scanClass(r.getInputStream());
+                    scanClass(handlers, null, r.getInputStream());
                 }
             }
         }
     }
-    
+
     
     
     /**
@@ -649,10 +580,10 @@ public class AnnotationParser
      * @param visitSuperClasses
      * @throws Exception
      */
-    public void parse (Class clazz, ClassNameResolver resolver, boolean visitSuperClasses)
+    public void parse (Set<? extends Handler> handlers, Class<?> clazz, ClassNameResolver resolver, boolean visitSuperClasses)
     throws Exception
     {
-        Class cz = clazz;
+        Class<?> cz = clazz;
         while (cz != null)
         {
             if (!resolver.isExcluded(cz.getName()))
@@ -660,21 +591,22 @@ public class AnnotationParser
                 if (!isParsed(cz.getName()) || resolver.shouldOverride(cz.getName()))
                 {
                     String nameAsResource = cz.getName().replace('.', '/')+".class";
-                    URL resource = Loader.getResource(this.getClass(), nameAsResource, false);
+                    URL resource = Loader.getResource(this.getClass(), nameAsResource);
                     if (resource!= null)
                     {
                         Resource r = Resource.newResource(resource);
-                        scanClass(r.getInputStream());
+                        scanClass(handlers, null, r.getInputStream());
                     }
                 }
             }
+
             if (visitSuperClasses)
                 cz = cz.getSuperclass();
             else
                 cz = null;
         }
     }
-    
+
     
     
     /**
@@ -684,15 +616,15 @@ public class AnnotationParser
      * @param resolver
      * @throws Exception
      */
-    public void parse (String[] classNames, ClassNameResolver resolver)
+    public void parse (Set<? extends Handler> handlers, String[] classNames, ClassNameResolver resolver)
     throws Exception
     {
         if (classNames == null)
             return;
-       
-        parse(Arrays.asList(classNames), resolver); 
+
+        parse(handlers, Arrays.asList(classNames), resolver);
     }
-    
+
     
     /**
      * Parse the given classes
@@ -701,24 +633,34 @@ public class AnnotationParser
      * @param resolver
      * @throws Exception
      */
-    public void parse (List<String> classNames, ClassNameResolver resolver)
+    public void parse (Set<? extends Handler> handlers, List<String> classNames, ClassNameResolver resolver)
     throws Exception
     {
+        MultiException me = new MultiException();
+        
         for (String s:classNames)
         {
-            if ((resolver == null) || (!resolver.isExcluded(s) &&  (!isParsed(s) || resolver.shouldOverride(s))))
-            {            
-                s = s.replace('.', '/')+".class"; 
-                URL resource = Loader.getResource(this.getClass(), s, false);
-                if (resource!= null)
+            try
+            {
+                if ((resolver == null) || (!resolver.isExcluded(s) &&  (!isParsed(s) || resolver.shouldOverride(s))))
                 {
-                    Resource r = Resource.newResource(resource);
-                    scanClass(r.getInputStream());
+                    s = s.replace('.', '/')+".class";
+                    URL resource = Loader.getResource(this.getClass(), s);
+                    if (resource!= null)
+                    {
+                        Resource r = Resource.newResource(resource);
+                        scanClass(handlers, null, r.getInputStream());
+                    }
                 }
             }
+            catch (Exception e)
+            {
+                me.add(new RuntimeException("Error scanning class "+s, e));
+            }
         }
+        me.ifExceptionThrow();
     }
-    
+
     
     /**
      * Parse all classes in a directory
@@ -727,45 +669,56 @@ public class AnnotationParser
      * @param resolver
      * @throws Exception
      */
-    public void parse (Resource dir, ClassNameResolver resolver)
+    protected void parseDir (Set<? extends Handler> handlers, Resource dir, ClassNameResolver resolver)
     throws Exception
     {
+        //skip dirs whose name start with . (ie hidden)
         if (!dir.isDirectory() || !dir.exists() || dir.getName().startsWith("."))
             return;
-        
 
+        if (LOG.isDebugEnabled()) {LOG.debug("Scanning dir {}", dir);};
+
+        MultiException me = new MultiException();
+        
         String[] files=dir.list();
         for (int f=0;files!=null && f<files.length;f++)
         {
-            try 
+            Resource res = dir.addPath(files[f]);
+            if (res.isDirectory())
+                parseDir(handlers, res, resolver);
+            else
             {
-                Resource res = dir.addPath(files[f]);
-                if (res.isDirectory())
-                    parse(res, resolver);
-                else
+                //we've already verified the directories, so just verify the class file name
+                File file = res.getFile();
+                if (isValidClassFileName((file==null?null:file.getName())))
                 {
-                    String fullname = res.getName();
-                    String filename = res.getFile().getName();
-                   
-                    if (isValidClassFileName(filename))
+                    try
                     {
-                        if ((resolver == null)|| (!resolver.isExcluded(fullname) && (!isParsed(fullname) || resolver.shouldOverride(fullname))))
+                        String name = res.getName();
+                        if ((resolver == null)|| (!resolver.isExcluded(name) && (!isParsed(name) || resolver.shouldOverride(name))))
                         {
                             Resource r = Resource.newResource(res.getURL());
-                            scanClass(r.getInputStream());
+                            if (LOG.isDebugEnabled()) {LOG.debug("Scanning class {}", r);};
+                            scanClass(handlers, dir, r.getInputStream());
                         }
-
+                    }                  
+                    catch (Exception ex)
+                    {
+                        if (LOG.isDebugEnabled()) LOG.debug("Error scanning file "+files[f], ex);
+                        me.add(new RuntimeException("Error scanning file "+files[f],ex));
                     }
                 }
-            }
-            catch (Exception ex)
-            {
-                LOG.warn(Log.EXCEPTION,ex);
+                else
+                {
+                   if (LOG.isDebugEnabled()) LOG.debug("Skipping scan on invalid file {}", res);
+                }
             }
         }
+
+        me.ifExceptionThrow();
     }
-    
-    
+
+
     /**
      * Parse classes in the supplied classloader. 
      * Only class files in jar files will be scanned.
@@ -776,127 +729,249 @@ public class AnnotationParser
      * @param resolver
      * @throws Exception
      */
-    public void parse (ClassLoader loader, boolean visitParents, boolean nullInclusive, final ClassNameResolver resolver)
+    public void parse (final Set<? extends Handler> handlers, ClassLoader loader, boolean visitParents, boolean nullInclusive, final ClassNameResolver resolver)
     throws Exception
     {
         if (loader==null)
             return;
-        
+
         if (!(loader instanceof URLClassLoader))
             return; //can't extract classes?
-       
+
+        final MultiException me = new MultiException();
+        
         JarScanner scanner = new JarScanner()
         {
+            @Override
             public void processEntry(URI jarUri, JarEntry entry)
-            {   
+            {
                 try
                 {
-                    //skip directories
-                    if (entry.isDirectory())
-                        return;
-                    
-                    String name = entry.getName();                    
-                    if (isValidClassFilePath(name) && isValidClassFileName(name))
-                    {
-                        String shortName =  name.replace('/', '.').substring(0,name.length()-6);
-                        if ((resolver == null)
-                             ||
-                            (!resolver.isExcluded(shortName) && (!isParsed(shortName) || resolver.shouldOverride(shortName))))
-                        {
-
-                            Resource clazz = Resource.newResource("jar:"+jarUri+"!/"+name);                     
-                            scanClass(clazz.getInputStream());
-                        }
-                    }
+                    parseJarEntry(handlers, Resource.newResource(jarUri), entry, resolver);
                 }
                 catch (Exception e)
                 {
-                    LOG.warn("Problem processing jar entry "+entry, e);
+                    me.add(new RuntimeException("Error parsing entry "+entry.getName()+" from jar "+ jarUri, e));
                 }
             }
-            
+
         };
 
         scanner.scan(null, loader, nullInclusive, visitParents);
+        me.ifExceptionThrow();
     }
-    
-    
+
+
     /**
-     * Parse classes in the supplied url of jar files.
+     * Parse classes in the supplied uris.
      * 
      * @param uris
      * @param resolver
      * @throws Exception
      */
-    public void parse (URI[] uris, final ClassNameResolver resolver)
+    public void parse (final Set<? extends Handler> handlers, final URI[] uris, final ClassNameResolver resolver)
     throws Exception
     {
         if (uris==null)
             return;
+
+        MultiException me = new MultiException();
         
-        JarScanner scanner = new JarScanner()
+        for (URI uri:uris)
         {
-            public void processEntry(URI jarUri, JarEntry entry)
-            {   
-                try
+            try
+            {
+                parse(handlers, uri, resolver);
+            }
+            catch (Exception e)
+            {
+                me.add(new RuntimeException("Problem parsing classes from "+ uri, e));
+            }
+        }
+        me.ifExceptionThrow();
+    }
+
+    /**
+     * Parse a particular uri
+     * @param uri
+     * @param resolver
+     * @throws Exception
+     */
+    public void parse (final Set<? extends Handler> handlers, URI uri, final ClassNameResolver resolver)
+    throws Exception
+    {
+        if (uri == null)
+            return;
+
+        parse (handlers, Resource.newResource(uri), resolver);
+    }
+
+    
+    /**
+     * Parse a resource
+     * @param r
+     * @param resolver
+     * @throws Exception
+     */
+    public void parse (final Set<? extends Handler> handlers, Resource r, final ClassNameResolver resolver)
+    throws Exception
+    {
+        if (r == null)
+            return;
+        
+        if (r.exists() && r.isDirectory())
+        {
+            parseDir(handlers, r, resolver);
+            return;
+        }
+
+        String fullname = r.toString();
+        if (fullname.endsWith(".jar"))
+        {
+            parseJar(handlers, r, resolver);
+            return;
+        }
+
+        if (fullname.endsWith(".class"))
+        {
+            scanClass(handlers, null, r.getInputStream());
+            return;
+        }
+        
+        if (LOG.isDebugEnabled()) LOG.warn("Resource not scannable for classes: {}", r);
+    }
+
+    
+  
+
+    /**
+     * Parse a resource that is a jar file.
+     * 
+     * @param jarResource
+     * @param resolver
+     * @throws Exception
+     */
+    protected void parseJar (Set<? extends Handler> handlers, Resource jarResource,  final ClassNameResolver resolver)
+    throws Exception
+    {
+        if (jarResource == null)
+            return;
+       
+        if (jarResource.toString().endsWith(".jar"))
+        {
+            if (LOG.isDebugEnabled()) {LOG.debug("Scanning jar {}", jarResource);};
+
+            //treat it as a jar that we need to open and scan all entries from  
+            //TODO alternative impl
+            /*
+            long start = System.nanoTime();
+            Collection<Resource> resources = Resource.newResource("jar:"+jarResource+"!/").getAllResources();
+            System.err.println(jarResource+String.valueOf(resources.size())+" resources listed in "+ ((TimeUnit.MILLISECONDS.convert(System.nanoTime()-start, TimeUnit.NANOSECONDS))));
+            for (Resource r:resources)
+            {
+                //skip directories
+                if (r.isDirectory())
+                    continue;
+
+                String name = r.getName();
+                name = name.substring(name.indexOf("!/")+2);
+
+                //check file is a valid class file name
+                if (isValidClassFileName(name) && isValidClassFilePath(name))
                 {
-                    //skip directories
-                    if (entry.isDirectory())
-                        return;
-                    
-                    String name = entry.getName();
-                    if (isValidClassFilePath(name) && isValidClassFileName(name))
+                    String shortName =  name.replace('/', '.').substring(0,name.length()-6);
+
+                    if ((resolver == null)
+                            ||
+                        (!resolver.isExcluded(shortName) && (!isParsed(shortName) || resolver.shouldOverride(shortName))))
                     {
-                        String shortName =  name.replace('/', '.').substring(0,name.length()-6);
+                        if (LOG.isDebugEnabled()) {LOG.debug("Scanning class from jar {}", r);};
+                        scanClass(handlers, jarResource, r.getInputStream());
+                    }
+                }
+            }
+            */
 
-                        if ((resolver == null)
-                             ||
-                            (!resolver.isExcluded(shortName) && (!isParsed(shortName) || resolver.shouldOverride(shortName))))
-                        {
-                            Resource clazz = Resource.newResource("jar:"+jarUri+"!/"+name);                     
-                            scanClass(clazz.getInputStream());
+           InputStream in = jarResource.getInputStream();
+            if (in==null)
+                return;
 
-                        }
+            MultiException me = new MultiException();
+            
+            JarInputStream jar_in = new JarInputStream(in);
+            try
+            { 
+                JarEntry entry = jar_in.getNextJarEntry();
+                while (entry!=null)
+                {      
+                    try
+                    {
+                        parseJarEntry(handlers, jarResource, entry, resolver);                        
                     }
-                }
-                catch (Exception e)
-                {
-                    LOG.warn("Problem processing jar entry "+entry, e);
+                    catch (Exception e)
+                    {
+                        me.add(new RuntimeException("Error scanning entry "+entry.getName()+" from jar "+jarResource, e));
+                    }
+                    entry = jar_in.getNextJarEntry();
                 }
             }
-            
-        };        
-        scanner.scan(null, uris, true);
+            finally
+            {
+                jar_in.close();
+            }
+            me.ifExceptionThrow();
+        }        
     }
-    
+
     /**
-     * Parse a particular resource
-     * @param uri
+     * Parse a single entry in a jar file
+     * @param jar
+     * @param entry
      * @param resolver
      * @throws Exception
      */
-    public void parse (URI uri, final ClassNameResolver resolver)
+    protected void parseJarEntry (Set<? extends Handler> handlers, Resource jar, JarEntry entry, final ClassNameResolver resolver)
     throws Exception
     {
-        if (uri == null)
+        if (jar == null || entry == null)
+            return;
+
+        //skip directories
+        if (entry.isDirectory())
             return;
-        URI[] uris = {uri};
-        parse(uris, resolver);
+
+        String name = entry.getName();
+
+        //check file is a valid class file name
+        if (isValidClassFileName(name) && isValidClassFilePath(name))
+        {
+            String shortName =  name.replace('/', '.').substring(0,name.length()-6);
+
+            if ((resolver == null)
+                    ||
+                (!resolver.isExcluded(shortName) && (!isParsed(shortName) || resolver.shouldOverride(shortName))))
+            {
+                Resource clazz = Resource.newResource("jar:"+jar.getURI()+"!/"+name);
+                if (LOG.isDebugEnabled()) {LOG.debug("Scanning class from jar {}", clazz);};
+                scanClass(handlers, jar, clazz.getInputStream());
+            }
+        }
     }
     
     
-    
+
     /**
      * Use ASM on a class
      * 
+     * @param containingResource the dir or jar that the class is contained within, can be null if not known
      * @param is
      * @throws IOException
      */
-    protected void scanClass (InputStream is)
+    protected void scanClass (Set<? extends Handler> handlers, Resource containingResource, InputStream is)
     throws IOException
     {
         ClassReader reader = new ClassReader(is);
-        reader.accept(new MyClassVisitor(), ClassReader.SKIP_CODE|ClassReader.SKIP_DEBUG|ClassReader.SKIP_FRAMES);
+        reader.accept(new MyClassVisitor(handlers, containingResource), ClassReader.SKIP_CODE|ClassReader.SKIP_DEBUG|ClassReader.SKIP_FRAMES);
     }
     
     /**
@@ -907,7 +982,7 @@ public class AnnotationParser
      * <li> it isn't a dot file or in a hidden directory </li>
      * <li> the name of the class at least begins with a valid identifier for a class name </li>
      * </ul>
-     * @param path
+     * @param name
      * @return
      */
     private boolean isValidClassFileName (String name)
@@ -924,7 +999,7 @@ public class AnnotationParser
         }
 
         //skip any classfiles that are not a valid java identifier
-        int c0 = 0;
+        int c0 = 0;      
         int ldir = name.lastIndexOf('/', name.length()-6);
         c0 = (ldir > -1 ? ldir+1 : c0);
         if (!Character.isJavaIdentifierStart(name.charAt(c0)))
@@ -932,15 +1007,14 @@ public class AnnotationParser
             if (LOG.isDebugEnabled()) LOG.debug("Not a java identifier: {}"+name);
             return false;
         }
-        
+   
         return true;
     }
-
-
-
+    
+    
     /**
      * Check that the given path does not contain hidden directories
-     * 
+     *
      * @param path
      * @return
      */
@@ -949,15 +1023,14 @@ public class AnnotationParser
         //no path is not valid
         if (path == null || path.length()==0)
             return false;
-        
-        
+
         //skip any classfiles that are in a hidden directory
         if (path.startsWith(".") || path.contains("/."))
-        { 
+        {
             if (LOG.isDebugEnabled()) LOG.debug("Contains hidden dirs: {}"+path);
             return false;
         }
-        
+
         return true;
     }
 }
diff --git a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/ClassInheritanceHandler.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/ClassInheritanceHandler.java
index 14748d1..ad84056 100644
--- a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/ClassInheritanceHandler.java
+++ b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/ClassInheritanceHandler.java
@@ -18,10 +18,11 @@
 
 package org.eclipse.jetty.annotations;
 
-import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
 
-import org.eclipse.jetty.annotations.AnnotationParser.ClassHandler;
-import org.eclipse.jetty.util.MultiMap;
+import org.eclipse.jetty.annotations.AnnotationParser.AbstractHandler;
+import org.eclipse.jetty.annotations.AnnotationParser.ClassInfo;
+import org.eclipse.jetty.util.ConcurrentHashSet;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
 
@@ -30,34 +31,33 @@ import org.eclipse.jetty.util.log.Logger;
  *
  * As asm scans for classes, remember the type hierarchy.
  */
-public class ClassInheritanceHandler implements ClassHandler
+public class ClassInheritanceHandler extends AbstractHandler
 {
     private static final Logger LOG = Log.getLogger(ClassInheritanceHandler.class);
-
-    
-    MultiMap _inheritanceMap;
     
-    public ClassInheritanceHandler()
-    {
-       _inheritanceMap = new MultiMap();
-    }
+    ConcurrentHashMap<String, ConcurrentHashSet<String>> _inheritanceMap;
+ 
     
-    public ClassInheritanceHandler(MultiMap map)
+    public ClassInheritanceHandler(ConcurrentHashMap<String, ConcurrentHashSet<String>> map)
     {
         _inheritanceMap = map;
     }
 
-    public void handle(String className, int version, int access, String signature, String superName, String[] interfaces)
+    public void handle(ClassInfo classInfo)
     {
         try
         {
-            for (int i=0; interfaces != null && i<interfaces.length;i++)
+            for (int i=0; classInfo.getInterfaces() != null && i < classInfo.getInterfaces().length;i++)
             {
-                _inheritanceMap.add (interfaces[i], className);
+                addToInheritanceMap(classInfo.getInterfaces()[i], classInfo.getClassName());
+                //_inheritanceMap.add (classInfo.getInterfaces()[i], classInfo.getClassName());
             }
             //To save memory, we don't record classes that only extend Object, as that can be assumed
-            if (!"java.lang.Object".equals(superName))
-                _inheritanceMap.add(superName, className);
+            if (!"java.lang.Object".equals(classInfo.getSuperName()))
+            {
+                addToInheritanceMap(classInfo.getSuperName(), classInfo.getClassName());
+                //_inheritanceMap.add(classInfo.getSuperName(), classInfo.getClassName());
+            }
         }
         catch (Exception e)
         {
@@ -65,13 +65,21 @@ public class ClassInheritanceHandler implements ClassHandler
         }  
     }
     
-    public List getClassNamesExtendingOrImplementing (String className)
+    private void addToInheritanceMap (String interfaceOrSuperClassName, String implementingOrExtendingClassName)
     {
-        return _inheritanceMap.getValues(className);
-    }
-    
-    public MultiMap getMap ()
-    {
-        return _inheritanceMap;
+      
+        //As it is likely that the interfaceOrSuperClassName is already in the map, try getting it first
+        ConcurrentHashSet<String> implementingClasses = _inheritanceMap.get(interfaceOrSuperClassName);
+        //If it isn't in the map, then add it in, but test to make sure that someone else didn't get in 
+        //first and add it
+        if (implementingClasses == null)
+        {
+            implementingClasses = new ConcurrentHashSet<String>();
+            ConcurrentHashSet<String> tmp = _inheritanceMap.putIfAbsent(interfaceOrSuperClassName, implementingClasses);
+            if (tmp != null)
+                implementingClasses = tmp;
+        }
+        
+        implementingClasses.add(implementingOrExtendingClassName);
     }
 }
diff --git a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/ContainerInitializerAnnotationHandler.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/ContainerInitializerAnnotationHandler.java
index 04c4d19..c710960 100644
--- a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/ContainerInitializerAnnotationHandler.java
+++ b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/ContainerInitializerAnnotationHandler.java
@@ -19,65 +19,75 @@
 
 package org.eclipse.jetty.annotations;
 
-import java.util.List;
 
-import javax.servlet.annotation.HandlesTypes;
-
-import org.eclipse.jetty.annotations.AnnotationParser.DiscoverableAnnotationHandler;
-import org.eclipse.jetty.annotations.AnnotationParser.Value;
+import org.eclipse.jetty.annotations.AnnotationParser.AbstractHandler;
+import org.eclipse.jetty.annotations.AnnotationParser.ClassInfo;
+import org.eclipse.jetty.annotations.AnnotationParser.FieldInfo;
+import org.eclipse.jetty.annotations.AnnotationParser.MethodInfo;
 import org.eclipse.jetty.plus.annotation.ContainerInitializer;
-import org.eclipse.jetty.util.Loader;
-import org.eclipse.jetty.util.log.Log;
 
 /**
  * ContainerInitializerAnnotationHandler
  *
  *  Discovers classes that contain the specified annotation, either at class or
  *  method level. The specified annotation is derived from an @HandlesTypes on
- *  a ServletContainerInitializer class. 
+ *  a ServletContainerInitializer class.
+ */
+/**
+ * @author janb
+ *
  */
-public class ContainerInitializerAnnotationHandler implements DiscoverableAnnotationHandler
+public class ContainerInitializerAnnotationHandler extends AbstractHandler
 {
-    ContainerInitializer _initializer;
-    Class _annotation;
+    final ContainerInitializer _initializer;
+    final Class _annotation;
 
     public ContainerInitializerAnnotationHandler (ContainerInitializer initializer, Class annotation)
     {
         _initializer = initializer;
         _annotation = annotation;
     }
-    
-    /** 
+
+    /**
      * Handle finding a class that is annotated with the annotation we were constructed with.
-     * @see org.eclipse.jetty.annotations.AnnotationParser.DiscoverableAnnotationHandler#handleClass(java.lang.String, int, int, java.lang.String, java.lang.String, java.lang.String[], java.lang.String, java.util.List)
+     * 
+     * @see org.eclipse.jetty.annotations.AnnotationParser.Handler#handle(ClassInfo, String)
      */
-    public void handleClass(String className, int version, int access, String signature, String superName, String[] interfaces, String annotationName,
-                            List<Value> values)
-    { 
-         _initializer.addAnnotatedTypeName(className);
-    }
-
-    public void handleField(String className, String fieldName, int access, String fieldType, String signature, Object value, String annotation,
-                            List<Value> values)
+    public void handle(ClassInfo info, String annotationName)
     {
-       _initializer.addAnnotatedTypeName(className);
+        if (annotationName == null || !_annotation.getName().equals(annotationName))
+                return;
+        
+         _initializer.addAnnotatedTypeName(info.getClassName());
     }
 
-    public void handleMethod(String className, String methodName, int access, String params, String signature, String[] exceptions, String annotation,
-                             List<Value> values)
-    {
-       _initializer.addAnnotatedTypeName(className);
+    /**
+     * Handle finding a field that is annotated with the annotation we were constructed with.
+     * 
+     * @see org.eclipse.jetty.annotations.AnnotationParser.Handler#handle(FieldInfo, String)
+     */
+    public void handle(FieldInfo info, String annotationName)
+    {        
+        if (annotationName == null || !_annotation.getName().equals(annotationName))
+            return;
+        _initializer.addAnnotatedTypeName(info.getClassInfo().getClassName());
     }
 
-    @Override
-    public String getAnnotationName()
+    /**
+     * Handle finding a method that is annotated with the annotation we were constructed with. 
+     * 
+     * @see org.eclipse.jetty.annotations.AnnotationParser.Handler#handle(MethodInfo, String)
+     */
+    public void handle(MethodInfo info, String annotationName)
     {
-       return _annotation.getName();
+        if (annotationName == null || !_annotation.getName().equals(annotationName))
+            return;
+       _initializer.addAnnotatedTypeName(info.getClassInfo().getClassName());
     }
+
     
     public ContainerInitializer getContainerInitializer()
     {
         return _initializer;
     }
-
 }
diff --git a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/DeclareRolesAnnotationHandler.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/DeclareRolesAnnotationHandler.java
index 0beee04..6231ef1 100644
--- a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/DeclareRolesAnnotationHandler.java
+++ b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/DeclareRolesAnnotationHandler.java
@@ -18,9 +18,9 @@
 
 package org.eclipse.jetty.annotations;
 
+import javax.annotation.security.DeclareRoles;
 import javax.servlet.Servlet;
 
-import javax.annotation.security.DeclareRoles;
 import org.eclipse.jetty.annotations.AnnotationIntrospector.AbstractIntrospectableAnnotationHandler;
 import org.eclipse.jetty.security.ConstraintSecurityHandler;
 import org.eclipse.jetty.webapp.WebAppContext;
@@ -34,7 +34,7 @@ public class DeclareRolesAnnotationHandler extends AbstractIntrospectableAnnotat
 {
 
     protected WebAppContext _context;
-    
+
     /**
      * @param context
      */
@@ -43,20 +43,20 @@ public class DeclareRolesAnnotationHandler extends AbstractIntrospectableAnnotat
         super(false);
         _context = context;
     }
- 
 
-    /** 
+
+    /**
      * @see org.eclipse.jetty.annotations.AnnotationIntrospector.AbstractIntrospectableAnnotationHandler#doHandle(java.lang.Class)
      */
     public void doHandle(Class clazz)
     {
         if (!Servlet.class.isAssignableFrom(clazz))
             return; //only applicable on javax.servlet.Servlet derivatives
-        
+
         DeclareRoles declareRoles = (DeclareRoles) clazz.getAnnotation(DeclareRoles.class);
         if (declareRoles == null)
             return;
-        
+
         String[] roles = declareRoles.value();
 
         if (roles != null && roles.length > 0)
diff --git a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/MultiPartConfigAnnotationHandler.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/MultiPartConfigAnnotationHandler.java
index 43169aa..b32a43b 100644
--- a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/MultiPartConfigAnnotationHandler.java
+++ b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/MultiPartConfigAnnotationHandler.java
@@ -70,7 +70,7 @@ public class MultiPartConfigAnnotationHandler extends AbstractIntrospectableAnno
             //let the annotation override it
             if (d == null)
             {
-                metaData.setOrigin(holder.getName()+".servlet.multipart-config");
+                metaData.setOrigin(holder.getName()+".servlet.multipart-config",multi,clazz);
                 holder.getRegistration().setMultipartConfig(new MultipartConfigElement(multi));
             }
         }
diff --git a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/PostConstructAnnotationHandler.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/PostConstructAnnotationHandler.java
index aa0907e..196a8c6 100644
--- a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/PostConstructAnnotationHandler.java
+++ b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/PostConstructAnnotationHandler.java
@@ -42,9 +42,9 @@ public class PostConstructAnnotationHandler extends AbstractIntrospectableAnnota
 
 
     public void doHandle(Class clazz)
-    {  
+    {
         //Check that the PostConstruct is on a class that we're interested in
-        if (Util.isServletType(clazz))
+        if (Util.supportsPostConstructPreDestroy(clazz))
         {
             Method[] methods = clazz.getDeclaredMethods();
             for (int i=0; i<methods.length; i++)
@@ -60,17 +60,17 @@ public class PostConstructAnnotationHandler extends AbstractIntrospectableAnnota
                         throw new IllegalStateException(m+" throws checked exceptions");
                     if (Modifier.isStatic(m.getModifiers()))
                         throw new IllegalStateException(m+" is static");
-                   
+
                     //ServletSpec 3.0 p80 If web.xml declares even one post-construct then all post-constructs
                     //in fragments must be ignored. Otherwise, they are additive.
                     MetaData metaData = _context.getMetaData();
                     Origin origin = metaData.getOrigin("post-construct");
-                    if (origin != null && 
+                    if (origin != null &&
                         (origin == Origin.WebXml ||
-                         origin == Origin.WebDefaults || 
+                         origin == Origin.WebDefaults ||
                          origin == Origin.WebOverride))
                         return;
-                                        
+
                     PostConstructCallback callback = new PostConstructCallback();
                     callback.setTarget(clazz.getName(), m.getName());
                     LifeCycleCallbackCollection lifecycles = (LifeCycleCallbackCollection)_context.getAttribute(LifeCycleCallbackCollection.LIFECYCLE_CALLBACK_COLLECTION);
diff --git a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/PreDestroyAnnotationHandler.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/PreDestroyAnnotationHandler.java
index 64cd831..92aae9b 100644
--- a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/PreDestroyAnnotationHandler.java
+++ b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/PreDestroyAnnotationHandler.java
@@ -33,7 +33,7 @@ import org.eclipse.jetty.webapp.WebAppContext;
 public class PreDestroyAnnotationHandler extends AbstractIntrospectableAnnotationHandler
 {
     WebAppContext _context;
-    
+
     public PreDestroyAnnotationHandler (WebAppContext wac)
     {
         super(true);
@@ -41,9 +41,9 @@ public class PreDestroyAnnotationHandler extends AbstractIntrospectableAnnotatio
     }
 
     public void doHandle(Class clazz)
-    { 
+    {
         //Check that the PreDestroy is on a class that we're interested in
-        if (Util.isServletType(clazz))
+        if (Util.supportsPostConstructPreDestroy(clazz))
         {
             Method[] methods = clazz.getDeclaredMethods();
             for (int i=0; i<methods.length; i++)
@@ -59,17 +59,17 @@ public class PreDestroyAnnotationHandler extends AbstractIntrospectableAnnotatio
                         throw new IllegalStateException(m+" throws checked exceptions");
                     if (Modifier.isStatic(m.getModifiers()))
                         throw new IllegalStateException(m+" is static");
-                    
+
                     //ServletSpec 3.0 p80 If web.xml declares even one predestroy then all predestroys
-                    //in fragments must be ignored. Otherwise, they are additive.                    
+                    //in fragments must be ignored. Otherwise, they are additive.
                     MetaData metaData = _context.getMetaData();
                     Origin origin = metaData.getOrigin("pre-destroy");
-                    if (origin != null && 
+                    if (origin != null &&
                             (origin == Origin.WebXml ||
-                             origin == Origin.WebDefaults || 
+                             origin == Origin.WebDefaults ||
                              origin == Origin.WebOverride))
                             return;
-                    
+
                     PreDestroyCallback callback = new PreDestroyCallback();
                     callback.setTarget(clazz.getName(), m.getName());
 
diff --git a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/ResourceAnnotationHandler.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/ResourceAnnotationHandler.java
index c76c7d7..3b46e8d 100644
--- a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/ResourceAnnotationHandler.java
+++ b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/ResourceAnnotationHandler.java
@@ -41,7 +41,7 @@ public class ResourceAnnotationHandler extends AbstractIntrospectableAnnotationH
     private static final Logger LOG = Log.getLogger(ResourceAnnotationHandler.class);
 
     protected WebAppContext _context;
-   
+
 
     public ResourceAnnotationHandler (WebAppContext wac)
     {
@@ -57,10 +57,10 @@ public class ResourceAnnotationHandler extends AbstractIntrospectableAnnotationH
      */
     public void doHandle(Class<?> clazz)
     {
-        if (Util.isServletType(clazz))
+        if (Util.supportsResourceInjection(clazz))
         {
             handleClass(clazz);
-            
+
             Method[] methods = clazz.getDeclaredMethods();
             for (int i=0; i<methods.length; i++)
                 handleMethod(clazz, methods[i]);
@@ -70,7 +70,7 @@ public class ResourceAnnotationHandler extends AbstractIntrospectableAnnotationH
                 handleField(clazz, fields[i]);
         }
     }
-        
+
      public void handleClass (Class<?> clazz)
      {
          Resource resource = (Resource)clazz.getAnnotation(Resource.class);
@@ -78,10 +78,10 @@ public class ResourceAnnotationHandler extends AbstractIntrospectableAnnotationH
          {
              String name = resource.name();
              String mappedName = resource.mappedName();
-             
+
              if (name==null || name.trim().equals(""))
                  throw new IllegalStateException ("Class level Resource annotations must contain a name (Common Annotations Spec Section 2.3)");
-             
+
              try
              {
                  if (!org.eclipse.jetty.plus.jndi.NamingEntryUtil.bindToENC(_context, name,mappedName))
@@ -115,14 +115,14 @@ public class ResourceAnnotationHandler extends AbstractIntrospectableAnnotationH
             }
 
             //work out default name
-            String name = clazz.getCanonicalName()+"/"+field.getName();     
-           
+            String name = clazz.getCanonicalName()+"/"+field.getName();
+
             //allow @Resource name= to override the field name
             name = (resource.name()!=null && !resource.name().trim().equals("")? resource.name(): name);
             String mappedName = (resource.mappedName()!=null && !resource.mappedName().trim().equals("")?resource.mappedName():null);
             //get the type of the Field
             Class<?> type = field.getType();
-            
+
             //Servlet Spec 3.0 p. 76
             //If a descriptor has specified at least 1 injection target for this
             //resource, then it overrides this annotation
@@ -152,7 +152,7 @@ public class ResourceAnnotationHandler extends AbstractIntrospectableAnnotationH
                     if (!bound)
                         bound = org.eclipse.jetty.plus.jndi.NamingEntryUtil.bindToENC(_context.getServer(), name, mappedName);
                     if (!bound)
-                        bound =  org.eclipse.jetty.plus.jndi.NamingEntryUtil.bindToENC(null, name, mappedName); 
+                        bound =  org.eclipse.jetty.plus.jndi.NamingEntryUtil.bindToENC(null, name, mappedName);
                     if (!bound)
                     {
                         //see if there is an env-entry value been bound
@@ -160,7 +160,7 @@ public class ResourceAnnotationHandler extends AbstractIntrospectableAnnotationH
                         {
                             InitialContext ic = new InitialContext();
                             String nameInEnvironment = (mappedName!=null?mappedName:name);
-                            ic.lookup("java:comp/env/"+nameInEnvironment);                               
+                            ic.lookup("java:comp/env/"+nameInEnvironment);
                             bound = true;
                         }
                         catch (NameNotFoundException e)
@@ -168,22 +168,22 @@ public class ResourceAnnotationHandler extends AbstractIntrospectableAnnotationH
                             bound = false;
                         }
                     }
-                    //Check there is a JNDI entry for this annotation 
+                    //Check there is a JNDI entry for this annotation
                     if (bound)
-                    { 
+                    {
                         LOG.debug("Bound "+(mappedName==null?name:mappedName) + " as "+ name);
                         //   Make the Injection for it if the binding succeeded
                         injection = new Injection();
                         injection.setTarget(clazz, field, type);
                         injection.setJndiName(name);
                         injection.setMappingName(mappedName);
-                        injections.add(injection); 
-                        
-                        //TODO - an @Resource is equivalent to a resource-ref, resource-env-ref, message-destination 
-                        metaData.setOrigin("resource-ref."+name+".injection");
-                    }  
+                        injections.add(injection);
+
+                        //TODO - an @Resource is equivalent to a resource-ref, resource-env-ref, message-destination
+                        metaData.setOrigin("resource-ref."+name+".injection",resource,clazz);
+                    }
                     else if (!Util.isEnvEntryType(type))
-                    {   
+                    {
                         //if this is an env-entry type resource and there is no value bound for it, it isn't
                         //an error, it just means that perhaps the code will use a default value instead
                         // JavaEE Spec. sec 5.4.1.3
@@ -203,10 +203,10 @@ public class ResourceAnnotationHandler extends AbstractIntrospectableAnnotationH
         }
     }
 
-    
+
     /**
      * Process a Resource annotation on a Method.
-     * 
+     *
      * This will generate a JNDI entry, and an Injection to be
      * processed when an instance of the class is created.
      */
@@ -219,17 +219,17 @@ public class ResourceAnnotationHandler extends AbstractIntrospectableAnnotationH
             /*
              * Commons Annotations Spec 2.3
              * " The Resource annotation is used to declare a reference to a resource.
-             *   It can be specified on a class, methods or on fields. When the 
-             *   annotation is applied on a field or method, the container will 
-             *   inject an instance of the requested resource into the application 
-             *   when the application is initialized... Even though this annotation 
-             *   is not marked Inherited, if used all superclasses MUST be examined 
-             *   to discover all uses of this annotation. All such annotation instances 
-             *   specify resources that are needed by the application. Note that this 
-             *   annotation may appear on private fields and methods of the superclasses. 
-             *   Injection of the declared resources needs to happen in these cases as 
+             *   It can be specified on a class, methods or on fields. When the
+             *   annotation is applied on a field or method, the container will
+             *   inject an instance of the requested resource into the application
+             *   when the application is initialized... Even though this annotation
+             *   is not marked Inherited, if used all superclasses MUST be examined
+             *   to discover all uses of this annotation. All such annotation instances
+             *   specify resources that are needed by the application. Note that this
+             *   annotation may appear on private fields and methods of the superclasses.
+             *   Injection of the declared resources needs to happen in these cases as
              *   well, even if a method with such an annotation is overridden by a subclass."
-             *  
+             *
              *  Which IMHO, put more succinctly means "If you find a @Resource on any method
              *  or field, inject it!".
              */
@@ -251,13 +251,13 @@ public class ResourceAnnotationHandler extends AbstractIntrospectableAnnotationH
             if (method.getParameterTypes().length != 1)
             {
                 LOG.warn("Skipping Resource annotation on "+clazz.getName()+"."+method.getName()+": invalid java bean, not single argument to method");
-                return; 
+                return;
             }
 
             if (Void.TYPE != method.getReturnType())
             {
                 LOG.warn("Skipping Resource annotation on "+clazz.getName()+"."+method.getName()+": invalid java bean, not void");
-                return; 
+                return;
             }
 
 
@@ -271,7 +271,7 @@ public class ResourceAnnotationHandler extends AbstractIntrospectableAnnotationH
             Class<?> paramType = method.getParameterTypes()[0];
 
             Class<?> resourceType = resource.type();
-            
+
             //Servlet Spec 3.0 p. 76
             //If a descriptor has specified at least 1 injection target for this
             //resource, then it overrides this annotation
@@ -282,7 +282,7 @@ public class ResourceAnnotationHandler extends AbstractIntrospectableAnnotationH
                 //it overrides this annotation
                 return;
             }
-            
+
             //check if an injection has already been setup for this target by web.xml
             InjectionCollection injections = (InjectionCollection)_context.getAttribute(InjectionCollection.INJECTION_COLLECTION);
             if (injections == null)
@@ -315,7 +315,7 @@ public class ResourceAnnotationHandler extends AbstractIntrospectableAnnotationH
                         {
                             InitialContext ic = new InitialContext();
                             String nameInEnvironment = (mappedName!=null?mappedName:name);
-                            ic.lookup("java:comp/env/"+nameInEnvironment);                               
+                            ic.lookup("java:comp/env/"+nameInEnvironment);
                             bound = true;
                         }
                         catch (NameNotFoundException e)
@@ -333,20 +333,20 @@ public class ResourceAnnotationHandler extends AbstractIntrospectableAnnotationH
                         injection.setJndiName(name);
                         injection.setMappingName(mappedName);
                         injections.add(injection);
-                        //TODO - an @Resource is equivalent to a resource-ref, resource-env-ref, message-destination 
-                        metaData.setOrigin("resource-ref."+name+".injection");
-                    } 
+                        //TODO - an @Resource is equivalent to a resource-ref, resource-env-ref, message-destination
+                        metaData.setOrigin("resource-ref."+name+".injection",resource,clazz);
+                    }
                     else if (!Util.isEnvEntryType(paramType))
                     {
 
                         //if this is an env-entry type resource and there is no value bound for it, it isn't
                         //an error, it just means that perhaps the code will use a default value instead
-                        // JavaEE Spec. sec 5.4.1.3   
+                        // JavaEE Spec. sec 5.4.1.3
                         throw new IllegalStateException("No resource at "+(mappedName==null?name:mappedName));
                     }
                 }
                 catch (NamingException e)
-                {  
+                {
                     //if this is an env-entry type resource and there is no value bound for it, it isn't
                     //an error, it just means that perhaps the code will use a default value instead
                     // JavaEE Spec. sec 5.4.1.3
diff --git a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/RunAsAnnotationHandler.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/RunAsAnnotationHandler.java
index a687c2a..af3a0df 100644
--- a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/RunAsAnnotationHandler.java
+++ b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/RunAsAnnotationHandler.java
@@ -18,12 +18,9 @@
 
 package org.eclipse.jetty.annotations;
 
-import java.util.List;
-
 import javax.servlet.Servlet;
 
 import org.eclipse.jetty.annotations.AnnotationIntrospector.AbstractIntrospectableAnnotationHandler;
-import org.eclipse.jetty.annotations.AnnotationParser.Value;
 import org.eclipse.jetty.plus.annotation.RunAsCollection;
 import org.eclipse.jetty.servlet.ServletHolder;
 import org.eclipse.jetty.util.log.Log;
@@ -46,12 +43,12 @@ public class RunAsAnnotationHandler extends AbstractIntrospectableAnnotationHand
         super(false);
         _context = wac;
     }
-    
+
     public void doHandle (Class clazz)
     {
         if (!Servlet.class.isAssignableFrom(clazz))
             return;
-        
+
         javax.annotation.security.RunAs runAs = (javax.annotation.security.RunAs)clazz.getAnnotation(javax.annotation.security.RunAs.class);
         if (runAs != null)
         {
@@ -63,11 +60,11 @@ public class RunAsAnnotationHandler extends AbstractIntrospectableAnnotationHand
                 {
                     MetaData metaData = _context.getMetaData();
                     Descriptor d = metaData.getOriginDescriptor(holder.getName()+".servlet.run-as");
-                    //if a descriptor has already set the value for run-as, do not 
+                    //if a descriptor has already set the value for run-as, do not
                     //let the annotation override it
                     if (d == null)
                     {
-                        metaData.setOrigin(holder.getName()+".servlet.run-as");
+                        metaData.setOrigin(holder.getName()+".servlet.run-as",runAs,clazz);
                         org.eclipse.jetty.plus.annotation.RunAs ra = new org.eclipse.jetty.plus.annotation.RunAs();
                         ra.setTargetClassName(clazz.getCanonicalName());
                         ra.setRoleName(role);
@@ -87,14 +84,12 @@ public class RunAsAnnotationHandler extends AbstractIntrospectableAnnotationHand
 
     }
 
-    public void handleField(String className, String fieldName, int access, String fieldType, String signature, Object value, String annotation,
-                            List<Value> values)
+    public void handleField(String className, String fieldName, int access, String fieldType, String signature, Object value, String annotation)
     {
        LOG.warn ("@RunAs annotation not applicable for fields: "+className+"."+fieldName);
     }
 
-    public void handleMethod(String className, String methodName, int access, String params, String signature, String[] exceptions, String annotation,
-                             List<Value> values)
+    public void handleMethod(String className, String methodName, int access, String params, String signature, String[] exceptions, String annotation)
     {
         LOG.warn("@RunAs annotation ignored on method: "+className+"."+methodName+" "+signature);
     }
diff --git a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/ServletContainerInitializerListener.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/ServletContainerInitializerListener.java
deleted file mode 100644
index 9fa8c85..0000000
--- a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/ServletContainerInitializerListener.java
+++ /dev/null
@@ -1,144 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.annotations;
-
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-import org.eclipse.jetty.annotations.AnnotationConfiguration;
-import org.eclipse.jetty.plus.annotation.ContainerInitializer;
-import org.eclipse.jetty.util.MultiMap;
-import org.eclipse.jetty.util.component.AbstractLifeCycle;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.webapp.WebAppContext;
-
-/**
- * ServletContainerInitializerListener
- *
- *
- */
-public class ServletContainerInitializerListener extends AbstractLifeCycle
-{
-    private static final Logger LOG = Log.getLogger(ServletContainerInitializerListener.class);
-    protected WebAppContext _context = null;
-    
-    
-    public void setWebAppContext (WebAppContext context)
-    {
-        _context = context;
-    }
-
-    
-    /** 
-     * Call the doStart method of the ServletContainerInitializers
-     * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart()
-     */
-    public void doStart()
-    {
-        List<ContainerInitializer> initializers = (List<ContainerInitializer>)_context.getAttribute(AnnotationConfiguration.CONTAINER_INITIALIZERS);
-        MultiMap classMap = (MultiMap)_context.getAttribute(AnnotationConfiguration.CLASS_INHERITANCE_MAP);
-        
-        if (initializers != null)
-        {
-            for (ContainerInitializer i : initializers)
-            {
-                //We have already found the classes that directly have an annotation that was in the HandlesTypes
-                //annotation of the ServletContainerInitializer. For each of those classes, walk the inheritance
-                //hierarchy to find classes that extend or implement them.
-                if (i.getAnnotatedTypeNames() != null)
-                {
-                    Set<String> annotatedClassNames = new HashSet<String>(i.getAnnotatedTypeNames());
-                    for (String name : annotatedClassNames)
-                    {
-                        //add the class with the annotation
-                        i.addApplicableTypeName(name);
-                        //add the classes that inherit the annotation
-                        if (classMap != null)
-                        {
-                            List<String> implementsOrExtends = (List<String>)classMap.getValues(name);
-                            if (implementsOrExtends != null && !implementsOrExtends.isEmpty())
-                                addInheritedTypes(classMap, i, implementsOrExtends);
-                        }
-                    }
-                }
-
-
-                //Now we need to look at the HandlesTypes classes that were not annotations. We need to
-                //find all classes that extend or implement them.
-                if (i.getInterestedTypes() != null)
-                {
-                    for (Class c : i.getInterestedTypes())
-                    {
-                        if (!c.isAnnotation())
-                        {
-                            //add the classes that implement or extend the class.
-                            //TODO but not including the class itself?
-                            if (classMap != null)
-                            {
-                                List<String> implementsOrExtends = (List<String>)classMap.getValues(c.getName());
-                                if (implementsOrExtends != null && !implementsOrExtends.isEmpty())
-                                    addInheritedTypes(classMap, i, implementsOrExtends);
-                            }
-                        }
-                    }
-                }
-
-                //instantiate ServletContainerInitializers, call doStart
-                try
-                {
-                    i.callStartup(_context);
-                }
-                catch (Exception e)
-                {
-                    LOG.warn(e);
-                    throw new RuntimeException(e);
-                }
-            }
-        }       
-    }
-
-    
-    void addInheritedTypes (MultiMap classMap, ContainerInitializer initializer, List<String> applicableTypes)
-    {
-        for (String s : applicableTypes)
-        {
-            //add the name of the class that extends or implements
-            initializer.addApplicableTypeName(s);
-            
-            //walk the hierarchy and find all types that extend or implement it
-            List<String> implementsOrExtends = (List<String>)classMap.getValues(s);
-            if (implementsOrExtends != null && !implementsOrExtends.isEmpty())
-                addInheritedTypes (classMap, initializer, implementsOrExtends);
-        }
-    }
-    
-    
-   
-    /** 
-     * Nothing to do for ServletContainerInitializers on stop
-     * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStop()
-     */
-    public void doStop()
-    {
-       
-    }
-
-}
diff --git a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/ServletContainerInitializersStarter.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/ServletContainerInitializersStarter.java
new file mode 100644
index 0000000..2cf40fd
--- /dev/null
+++ b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/ServletContainerInitializersStarter.java
@@ -0,0 +1,76 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.annotations;
+
+import java.util.List;
+import org.eclipse.jetty.plus.annotation.ContainerInitializer;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.webapp.WebAppContext;
+
+
+/**
+ * ServletContainerInitializersStarter
+ *
+ * Call the onStartup() method on all ServletContainerInitializers, after having 
+ * found all applicable classes (if any) to pass in as args.
+ */
+public class ServletContainerInitializersStarter extends AbstractLifeCycle implements ServletContextHandler.ServletContainerInitializerCaller
+{
+    private static final Logger LOG = Log.getLogger(ServletContainerInitializersStarter.class);
+    WebAppContext _context;
+    
+    /**
+     * @param context
+     */
+    public ServletContainerInitializersStarter(WebAppContext context)
+    {
+        _context = context;
+    }
+ 
+   /** 
+    * Call the doStart method of the ServletContainerInitializers
+    * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart()
+    */
+    public void doStart()
+    {
+        List<ContainerInitializer> initializers = (List<ContainerInitializer>)_context.getAttribute(AnnotationConfiguration.CONTAINER_INITIALIZERS);
+        if (initializers == null)
+            return;
+        
+        for (ContainerInitializer i : initializers)
+        {
+            try
+            {
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Calling ServletContainerInitializer "+i.getTarget().getClass().getName());
+                i.callStartup(_context);
+            }
+            catch (Exception e)
+            {
+                LOG.warn(e);
+                throw new RuntimeException(e);
+            }
+        }
+    }
+    
+    
+}
diff --git a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/ServletSecurityAnnotationHandler.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/ServletSecurityAnnotationHandler.java
index 2f08f2b..c5b700e 100644
--- a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/ServletSecurityAnnotationHandler.java
+++ b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/ServletSecurityAnnotationHandler.java
@@ -22,8 +22,6 @@ import java.util.ArrayList;
 import java.util.List;
 
 import javax.servlet.ServletSecurityElement;
-import javax.servlet.annotation.HttpConstraint;
-import javax.servlet.annotation.HttpMethodConstraint;
 import javax.servlet.annotation.ServletSecurity;
 import javax.servlet.annotation.ServletSecurity.EmptyRoleSemantic;
 import javax.servlet.annotation.ServletSecurity.TransportGuarantee;
@@ -37,7 +35,6 @@ import org.eclipse.jetty.servlet.ServletMapping;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
 import org.eclipse.jetty.util.security.Constraint;
-import org.eclipse.jetty.webapp.Origin;
 import org.eclipse.jetty.webapp.WebAppContext;
 
 /**
@@ -45,7 +42,7 @@ import org.eclipse.jetty.webapp.WebAppContext;
  *
  * Inspect a class to see if it has an @ServletSecurity annotation on it,
  * setting up the <security-constraint>s.
- * 
+ *
  * A servlet can be defined in:
  * <ul>
  * <li>web.xml
@@ -53,7 +50,7 @@ import org.eclipse.jetty.webapp.WebAppContext;
  * <li>@WebServlet annotation discovered
  * <li>ServletContext.createServlet
  * </ul>
- * 
+ *
  * The ServletSecurity annotation for a servlet should only be processed
  * iff metadata-complete == false.
  */
@@ -62,14 +59,14 @@ public class ServletSecurityAnnotationHandler extends AbstractIntrospectableAnno
     private static final Logger LOG = Log.getLogger(ServletSecurityAnnotationHandler.class);
 
     private WebAppContext _context;
-    
+
     public ServletSecurityAnnotationHandler(WebAppContext wac)
     {
         super(false);
         _context = wac;
     }
-    
-    /** 
+
+    /**
      * @see org.eclipse.jetty.annotations.AnnotationIntrospector.IntrospectableAnnotationHandler#handle(java.lang.Class)
      */
     public void doHandle(Class clazz)
@@ -79,17 +76,17 @@ public class ServletSecurityAnnotationHandler extends AbstractIntrospectableAnno
             LOG.warn("SecurityHandler not ConstraintAware, skipping security annotation processing");
             return;
         }
-        
+
        ServletSecurity servletSecurity = (ServletSecurity)clazz.getAnnotation(ServletSecurity.class);
        if (servletSecurity == null)
            return;
-       
+
        //If there are already constraints defined (ie from web.xml) that match any 
        //of the url patterns defined for this servlet, then skip the security annotation.
-      
+
        List<ServletMapping> servletMappings = getServletMappings(clazz.getCanonicalName());
        List<ConstraintMapping> constraintMappings =  ((ConstraintAware)_context.getSecurityHandler()).getConstraintMappings();
-     
+
        if (constraintsExist(servletMappings, constraintMappings))
        {
            LOG.warn("Constraints already defined for "+clazz.getName()+", skipping ServletSecurity annotation");
@@ -98,13 +95,13 @@ public class ServletSecurityAnnotationHandler extends AbstractIntrospectableAnno
 
        //Make a fresh list
        constraintMappings = new ArrayList<ConstraintMapping>();
-       
+
        ServletSecurityElement securityElement = new ServletSecurityElement(servletSecurity);
        for (ServletMapping sm : servletMappings)
        {
            for (String url : sm.getPathSpecs())
            {
-               _context.getMetaData().setOrigin("constraint.url."+url, Origin.Annotation);
+               _context.getMetaData().setOrigin("constraint.url."+url,servletSecurity,clazz);
                constraintMappings.addAll(ConstraintSecurityHandler.createConstraintsWithMappingsForPath(clazz.getName(), url, securityElement));
            }
        }
@@ -113,11 +110,14 @@ public class ServletSecurityAnnotationHandler extends AbstractIntrospectableAnno
        ConstraintAware securityHandler = (ConstraintAware)_context.getSecurityHandler();
 
        for (ConstraintMapping m:constraintMappings)
-           securityHandler.addConstraintMapping(m); 
+           securityHandler.addConstraintMapping(m);
+       
+       //Servlet Spec 3.1 requires paths with uncovered http methods to be reported
+       securityHandler.checkPathsWithUncoveredHttpMethods();
     }
-    
- 
-    
+
+
+
     /**
      * Make a jetty Constraint object, which represents the <auth-constraint> and
      * <user-data-constraint> elements, based on the security annotation.
@@ -125,19 +125,17 @@ public class ServletSecurityAnnotationHandler extends AbstractIntrospectableAnno
      * @param rolesAllowed
      * @param permitOrDeny
      * @param transport
-     * @return
      */
     protected Constraint makeConstraint (Class servlet, String[] rolesAllowed, EmptyRoleSemantic permitOrDeny, TransportGuarantee transport)
-    {  
+    {
         return ConstraintSecurityHandler.createConstraint(servlet.getName(), rolesAllowed, permitOrDeny, transport);
     }
-    
-    
-    
+
+
+
     /**
      * Get the ServletMappings for the servlet's class.
      * @param className
-     * @return
      */
     protected List<ServletMapping> getServletMappings(String className)
     {
@@ -152,14 +150,13 @@ public class ServletSecurityAnnotationHandler extends AbstractIntrospectableAnno
         }
         return results;
     }
-    
-    
-    
+
+
+
     /**
      * Check if there are already <security-constraint> elements defined that match the url-patterns for
      * the servlet.
      * @param servletMappings
-     * @return
      */
     protected boolean constraintsExist (List<ServletMapping> servletMappings, List<ConstraintMapping> constraintMappings)
     {
@@ -168,7 +165,7 @@ public class ServletSecurityAnnotationHandler extends AbstractIntrospectableAnno
         //Check to see if the path spec on each constraint mapping matches a pathSpec in the servlet mappings.
         //If it does, then we should ignore the security annotations.
         for (ServletMapping mapping : servletMappings)
-        {  
+        {
             //Get its url mappings
             String[] pathSpecs = mapping.getPathSpecs();
             if (pathSpecs == null)
@@ -189,7 +186,7 @@ public class ServletSecurityAnnotationHandler extends AbstractIntrospectableAnno
                    }
                }
            }
-        }      
+        }
         return exists;
     }
 
diff --git a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/Util.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/Util.java
index e3b6231..a2ed1c5 100644
--- a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/Util.java
+++ b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/Util.java
@@ -68,6 +68,44 @@ public class Util
         
         return isServlet;  
     }
+    
+    
+    public static boolean supportsResourceInjection (Class c)
+    {
+        if (javax.servlet.Servlet.class.isAssignableFrom(c) ||
+                javax.servlet.Filter.class.isAssignableFrom(c) || 
+                javax.servlet.ServletContextListener.class.isAssignableFrom(c) ||
+                javax.servlet.ServletContextAttributeListener.class.isAssignableFrom(c) ||
+                javax.servlet.ServletRequestListener.class.isAssignableFrom(c) ||
+                javax.servlet.ServletRequestAttributeListener.class.isAssignableFrom(c) ||
+                javax.servlet.http.HttpSessionListener.class.isAssignableFrom(c) ||
+                javax.servlet.http.HttpSessionAttributeListener.class.isAssignableFrom(c) ||
+                javax.servlet.http.HttpSessionIdListener.class.isAssignableFrom(c) ||
+                javax.servlet.AsyncListener.class.isAssignableFrom(c) ||
+                javax.servlet.http.HttpUpgradeHandler.class.isAssignableFrom(c))
+            return true;
+        
+        return false;
+    }
+    
+    
+    public static boolean supportsPostConstructPreDestroy (Class c)
+    {
+        if (javax.servlet.Servlet.class.isAssignableFrom(c) ||
+                javax.servlet.Filter.class.isAssignableFrom(c) || 
+                javax.servlet.ServletContextListener.class.isAssignableFrom(c) ||
+                javax.servlet.ServletContextAttributeListener.class.isAssignableFrom(c) ||
+                javax.servlet.ServletRequestListener.class.isAssignableFrom(c) ||
+                javax.servlet.ServletRequestAttributeListener.class.isAssignableFrom(c) ||
+                javax.servlet.http.HttpSessionListener.class.isAssignableFrom(c) ||
+                javax.servlet.http.HttpSessionAttributeListener.class.isAssignableFrom(c) ||
+                javax.servlet.http.HttpSessionIdListener.class.isAssignableFrom(c) ||
+                javax.servlet.AsyncListener.class.isAssignableFrom(c) ||
+                javax.servlet.http.HttpUpgradeHandler.class.isAssignableFrom(c))
+            return true;
+        
+        return false;
+    }
 
     public static boolean isEnvEntryType (Class type)
     {
diff --git a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/WebFilterAnnotation.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/WebFilterAnnotation.java
index 15ca3d6..8226758 100644
--- a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/WebFilterAnnotation.java
+++ b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/WebFilterAnnotation.java
@@ -34,8 +34,8 @@ import org.eclipse.jetty.util.log.Logger;
 import org.eclipse.jetty.util.resource.Resource;
 import org.eclipse.jetty.webapp.DiscoveredAnnotation;
 import org.eclipse.jetty.webapp.MetaData;
-import org.eclipse.jetty.webapp.WebAppContext;
 import org.eclipse.jetty.webapp.Origin;
+import org.eclipse.jetty.webapp.WebAppContext;
 
 /**
  * WebFilterAnnotation
@@ -60,21 +60,21 @@ public class WebFilterAnnotation extends DiscoveredAnnotation
         super(context, className, resource);
     }
 
-    /** 
-     * @see org.eclipse.jetty.annotations.ClassAnnotation#apply()
+    /**
+     * @see DiscoveredAnnotation#apply()
      */
     public void apply()
     {
         // TODO verify against rules for annotation v descriptor
-        
+
         Class clazz = getTargetClass();
         if (clazz == null)
         {
             LOG.warn(_className+" cannot be loaded");
             return;
         }
-        
-        
+
+
         //Servlet Spec 8.1.2
         if (!Filter.class.isAssignableFrom(clazz))
         {
@@ -82,7 +82,7 @@ public class WebFilterAnnotation extends DiscoveredAnnotation
             return;
         }
         MetaData metaData = _context.getMetaData();
-        
+
         WebFilter filterAnnotation = (WebFilter)clazz.getAnnotation(WebFilter.class);
 
         if (filterAnnotation.value().length > 0 && filterAnnotation.urlPatterns().length > 0)
@@ -95,24 +95,24 @@ public class WebFilterAnnotation extends DiscoveredAnnotation
         String[] urlPatterns = filterAnnotation.value();
         if (urlPatterns.length == 0)
             urlPatterns = filterAnnotation.urlPatterns();
-        
+
         FilterHolder holder = _context.getServletHandler().getFilter(name);
         if (holder == null)
         {
             //Filter with this name does not already exist, so add it
             holder = _context.getServletHandler().newFilterHolder(Holder.Source.ANNOTATION);
             holder.setName(name);
-            
+
             holder.setHeldClass(clazz);
-            metaData.setOrigin(name+".filter.filter-class");
- 
-            holder.setDisplayName(filterAnnotation.displayName()); 
-            metaData.setOrigin(name+".filter.display-name");
+            metaData.setOrigin(name+".filter.filter-class",filterAnnotation,clazz);
+
+            holder.setDisplayName(filterAnnotation.displayName());
+            metaData.setOrigin(name+".filter.display-name",filterAnnotation,clazz);
 
             for (WebInitParam ip:  filterAnnotation.initParams())
             {
                 holder.setInitParameter(ip.name(), ip.value());
-                metaData.setOrigin(name+".filter.init-param."+ip.name());
+                metaData.setOrigin(name+".filter.init-param."+ip.name(),ip,clazz);
             }
 
             FilterMapping mapping = new FilterMapping();
@@ -120,12 +120,12 @@ public class WebFilterAnnotation extends DiscoveredAnnotation
 
             if (urlPatterns.length > 0)
             {
-                ArrayList paths = new ArrayList();
+                ArrayList<String> paths = new ArrayList<String>();
                 for (String s:urlPatterns)
                 {
                     paths.add(Util.normalizePattern(s));
                 }
-                mapping.setPathSpecs((String[])paths.toArray(new String[paths.size()]));
+                mapping.setPathSpecs(paths.toArray(new String[paths.size()]));
             }
 
             if (filterAnnotation.servletNames().length > 0)
@@ -135,19 +135,19 @@ public class WebFilterAnnotation extends DiscoveredAnnotation
                 {
                     names.add(s);
                 }
-                mapping.setServletNames((String[])names.toArray(new String[names.size()]));
+                mapping.setServletNames(names.toArray(new String[names.size()]));
             }
 
-            EnumSet<DispatcherType> dispatcherSet = EnumSet.noneOf(DispatcherType.class);           
+            EnumSet<DispatcherType> dispatcherSet = EnumSet.noneOf(DispatcherType.class);
             for (DispatcherType d : filterAnnotation.dispatcherTypes())
             {
                 dispatcherSet.add(d);
             }
             mapping.setDispatcherTypes(dispatcherSet);
-            metaData.setOrigin(name+".filter.mappings");
+            metaData.setOrigin(name+".filter.mappings",filterAnnotation,clazz);
 
             holder.setAsyncSupported(filterAnnotation.asyncSupported());
-            metaData.setOrigin(name+".filter.async-supported");
+            metaData.setOrigin(name+".filter.async-supported",filterAnnotation,clazz);
 
             _context.getServletHandler().addFilter(holder);
             _context.getServletHandler().addFilterMapping(mapping);
@@ -165,10 +165,10 @@ public class WebFilterAnnotation extends DiscoveredAnnotation
                 if (metaData.getOrigin(name+".filter.init-param."+ip.name())==Origin.NotSet)
                 {
                     holder.setInitParameter(ip.name(), ip.value());
-                    metaData.setOrigin(name+".filter.init-param."+ip.name());
+                    metaData.setOrigin(name+".filter.init-param."+ip.name(),ip,clazz);
                 }
             }
-            
+
             FilterMapping[] mappings = _context.getServletHandler().getFilterMappings();
             boolean mappingExists = false;
             if (mappings != null)
@@ -191,12 +191,12 @@ public class WebFilterAnnotation extends DiscoveredAnnotation
 
                 if (urlPatterns.length > 0)
                 {
-                    ArrayList paths = new ArrayList();
+                    ArrayList<String> paths = new ArrayList<String>();
                     for (String s:urlPatterns)
                     {
                         paths.add(Util.normalizePattern(s));
                     }
-                    mapping.setPathSpecs((String[])paths.toArray(new String[paths.size()]));
+                    mapping.setPathSpecs(paths.toArray(new String[paths.size()]));
                 }
                 if (filterAnnotation.servletNames().length > 0)
                 {
@@ -205,17 +205,17 @@ public class WebFilterAnnotation extends DiscoveredAnnotation
                     {
                         names.add(s);
                     }
-                    mapping.setServletNames((String[])names.toArray(new String[names.size()]));
+                    mapping.setServletNames(names.toArray(new String[names.size()]));
                 }
-                
-                EnumSet<DispatcherType> dispatcherSet = EnumSet.noneOf(DispatcherType.class);           
+
+                EnumSet<DispatcherType> dispatcherSet = EnumSet.noneOf(DispatcherType.class);
                 for (DispatcherType d : filterAnnotation.dispatcherTypes())
                 {
                     dispatcherSet.add(d);
                 }
                 mapping.setDispatcherTypes(dispatcherSet);
-                _context.getServletHandler().addFilterMapping(mapping);   
-                metaData.setOrigin(name+".filter.mappings");
+                _context.getServletHandler().addFilterMapping(mapping);
+                metaData.setOrigin(name+".filter.mappings",filterAnnotation,clazz);
             }
         }
     }
diff --git a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/WebFilterAnnotationHandler.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/WebFilterAnnotationHandler.java
index 1223b8a..5a8a8db 100644
--- a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/WebFilterAnnotationHandler.java
+++ b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/WebFilterAnnotationHandler.java
@@ -18,13 +18,11 @@
 
 package org.eclipse.jetty.annotations;
 
-import java.util.List;
-
-import org.eclipse.jetty.annotations.AnnotationParser.DiscoverableAnnotationHandler;
-import org.eclipse.jetty.annotations.AnnotationParser.Value;
+import org.eclipse.jetty.annotations.AnnotationParser.ClassInfo;
+import org.eclipse.jetty.annotations.AnnotationParser.FieldInfo;
+import org.eclipse.jetty.annotations.AnnotationParser.MethodInfo;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.webapp.DiscoveredAnnotation;
 import org.eclipse.jetty.webapp.WebAppContext;
 
 /**
@@ -40,35 +38,31 @@ public class WebFilterAnnotationHandler extends AbstractDiscoverableAnnotationHa
     {
         super(context);
     }
+   
     
-    public WebFilterAnnotationHandler (WebAppContext context, List<DiscoveredAnnotation> list)
-    {
-        super(context, list);
-    }
-    
-    public void handleClass(String className, int version, int access, String signature, String superName, String[] interfaces, String annotation,
-                            List<Value> values)
+    @Override
+    public void handle(ClassInfo info, String annotationName)
     {
-        WebFilterAnnotation wfAnnotation = new WebFilterAnnotation(_context, className, _resource);
+        if (annotationName == null || !"javax.servlet.annotation.WebFilter".equals(annotationName))
+            return;
+        
+        WebFilterAnnotation wfAnnotation = new WebFilterAnnotation(_context, info.getClassName(), info.getContainingResource());
         addAnnotation(wfAnnotation);
     }
 
-    public void handleField(String className, String fieldName, int access, String fieldType, String signature, Object value, String annotation,
-                            List<Value> values)
-    {
-        LOG.warn ("@WebFilter not applicable for fields: "+className+"."+fieldName);
-    }
-
-    public void handleMethod(String className, String methodName, int access, String params, String signature, String[] exceptions, String annotation,
-                             List<Value> values)
-    {
-        LOG.warn ("@WebFilter not applicable for methods: "+className+"."+methodName+" "+signature);
+    @Override
+    public void handle(FieldInfo info, String annotationName)
+    {  
+        if (annotationName == null || !"javax.servlet.annotation.WebFilter".equals(annotationName))
+            return;
+        LOG.warn ("@WebFilter not applicable for fields: "+info.getClassInfo().getClassName()+"."+info.getFieldName());
     }
 
     @Override
-    public String getAnnotationName()
-    {
-        return "javax.servlet.annotation.WebFilter";
+    public void handle(MethodInfo info, String annotationName)
+    {  
+        if (annotationName == null || !"javax.servlet.annotation.WebFilter".equals(annotationName))
+            return;
+        LOG.warn ("@WebFilter not applicable for methods: "+info.getClassInfo().getClassName()+"."+info.getMethodName()+" "+info.getSignature());
     }
-
 }
diff --git a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/WebListenerAnnotation.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/WebListenerAnnotation.java
index dffd97d..963b14b 100644
--- a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/WebListenerAnnotation.java
+++ b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/WebListenerAnnotation.java
@@ -18,20 +18,25 @@
 
 package org.eclipse.jetty.annotations;
 
+import java.util.EventListener;
+
 import javax.servlet.ServletContextAttributeListener;
 import javax.servlet.ServletContextListener;
 import javax.servlet.ServletRequestAttributeListener;
 import javax.servlet.ServletRequestListener;
 import javax.servlet.http.HttpSessionAttributeListener;
+import javax.servlet.http.HttpSessionIdListener;
 import javax.servlet.http.HttpSessionListener;
 
+import org.eclipse.jetty.servlet.BaseHolder.Source;
+import org.eclipse.jetty.servlet.ListenerHolder;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
 import org.eclipse.jetty.util.resource.Resource;
 import org.eclipse.jetty.webapp.DiscoveredAnnotation;
 import org.eclipse.jetty.webapp.MetaData;
-import org.eclipse.jetty.webapp.WebAppContext;
 import org.eclipse.jetty.webapp.Origin;
+import org.eclipse.jetty.webapp.WebAppContext;
 
 /**
  * WebListenerAnnotation
@@ -56,15 +61,13 @@ public class WebListenerAnnotation extends DiscoveredAnnotation
         super(context, className, resource);
     }
 
-    /** 
-     * @see org.eclipse.jetty.annotations.ClassAnnotation#apply()
+    /**
+     * @see DiscoveredAnnotation#apply()
      */
     public void apply()
     {
-        // TODO check algorithm against ordering rules for descriptors v annotations
-        
-        Class clazz = getTargetClass();
-        
+        Class<? extends java.util.EventListener> clazz = (Class<? extends EventListener>)getTargetClass();
+
         if (clazz == null)
         {
             LOG.warn(_className+" cannot be loaded");
@@ -73,17 +76,23 @@ public class WebListenerAnnotation extends DiscoveredAnnotation
 
         try
         {
-            if (ServletContextListener.class.isAssignableFrom(clazz) || 
+            if (ServletContextListener.class.isAssignableFrom(clazz) ||
                     ServletContextAttributeListener.class.isAssignableFrom(clazz) ||
                     ServletRequestListener.class.isAssignableFrom(clazz) ||
                     ServletRequestAttributeListener.class.isAssignableFrom(clazz) ||
                     HttpSessionListener.class.isAssignableFrom(clazz) ||
-                    HttpSessionAttributeListener.class.isAssignableFrom(clazz))
+                    HttpSessionAttributeListener.class.isAssignableFrom(clazz) ||
+                    HttpSessionIdListener.class.isAssignableFrom(clazz))
             {
-                java.util.EventListener listener = (java.util.EventListener)clazz.newInstance();
+                java.util.EventListener listener = (java.util.EventListener)_context.getServletContext().createInstance(clazz);      
                 MetaData metaData = _context.getMetaData();
                 if (metaData.getOrigin(clazz.getName()+".listener") == Origin.NotSet)
+                {
+                    ListenerHolder h = _context.getServletHandler().newListenerHolder(Source.ANNOTATION);
+                    h.setListener(listener);
+                    _context.getServletHandler().addListener(h);
                     _context.addEventListener(listener);
+                }
             }
             else
                 LOG.warn(clazz.getName()+" does not implement one of the servlet listener interfaces");
diff --git a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/WebListenerAnnotationHandler.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/WebListenerAnnotationHandler.java
index 1699321..739285a 100644
--- a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/WebListenerAnnotationHandler.java
+++ b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/WebListenerAnnotationHandler.java
@@ -18,12 +18,11 @@
 
 package org.eclipse.jetty.annotations;
 
-import java.util.List;
-
-import org.eclipse.jetty.annotations.AnnotationParser.Value;
+import org.eclipse.jetty.annotations.AnnotationParser.ClassInfo;
+import org.eclipse.jetty.annotations.AnnotationParser.FieldInfo;
+import org.eclipse.jetty.annotations.AnnotationParser.MethodInfo;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.webapp.DiscoveredAnnotation;
 import org.eclipse.jetty.webapp.WebAppContext;
 
 public class WebListenerAnnotationHandler extends AbstractDiscoverableAnnotationHandler
@@ -35,37 +34,30 @@ public class WebListenerAnnotationHandler extends AbstractDiscoverableAnnotation
        super(context);
     }
     
-    public WebListenerAnnotationHandler (WebAppContext context, List<DiscoveredAnnotation> list)
-    {
-       super(context, list);
-    }
-    
+  
     /** 
-     * @see org.eclipse.jetty.annotations.AnnotationParser.DiscoverableAnnotationHandler#handleClass(java.lang.String, int, int, java.lang.String, java.lang.String, java.lang.String[], java.lang.String, java.util.List)
+     * @see org.eclipse.jetty.annotations.AnnotationParser.Handler#handle(ClassInfo, String)
      */
-    public void handleClass(String className, int version, int access, String signature, String superName, String[] interfaces, String annotation,
-                            List<Value> values)
+    public void handle(ClassInfo info, String annotationName)
     {
-        WebListenerAnnotation wlAnnotation = new WebListenerAnnotation(_context, className, _resource);
+        if (annotationName == null || !"javax.servlet.annotation.WebListener".equals(annotationName))
+            return;
+        
+        WebListenerAnnotation wlAnnotation = new WebListenerAnnotation(_context, info.getClassName(), info.getContainingResource());
         addAnnotation(wlAnnotation);
     }
 
-    public void handleField(String className, String fieldName, int access, String fieldType, String signature, Object value, String annotation,
-                            List<Value> values)
+    public void handle(FieldInfo info, String annotationName)
     {
-        LOG.warn ("@WebListener is not applicable to fields: "+className+"."+fieldName);
+        if (annotationName == null || !"javax.servlet.annotation.WebListener".equals(annotationName))
+            return;
+        LOG.warn ("@WebListener is not applicable to fields: "+info.getClassInfo().getClassName()+"."+info.getFieldName());
     }
 
-    public void handleMethod(String className, String methodName, int access, String params, String signature, String[] exceptions, String annotation,
-                             List<Value> values)
+    public void handle(MethodInfo info, String annotationName)
     {
-        LOG.warn ("@WebListener is not applicable to methods: "+className+"."+methodName+" "+signature);
+        if (annotationName == null || !"javax.servlet.annotation.WebListener".equals(annotationName))
+            return;
+        LOG.warn ("@WebListener is not applicable to methods: "+info.getClassInfo().getClassName()+"."+info.getMethodName()+" "+info.getSignature());
     }
-
-    @Override
-    public String getAnnotationName()
-    {
-        return "javax.servlet.annotation.WebListener";
-    }
-
 }
diff --git a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/WebServletAnnotation.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/WebServletAnnotation.java
index e2cd8a8..9ee4352 100644
--- a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/WebServletAnnotation.java
+++ b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/WebServletAnnotation.java
@@ -20,6 +20,7 @@ package org.eclipse.jetty.annotations;
 
 import java.util.ArrayList;
 
+import javax.servlet.Servlet;
 import javax.servlet.annotation.WebInitParam;
 import javax.servlet.annotation.WebServlet;
 import javax.servlet.http.HttpServlet;
@@ -33,8 +34,8 @@ import org.eclipse.jetty.util.log.Logger;
 import org.eclipse.jetty.util.resource.Resource;
 import org.eclipse.jetty.webapp.DiscoveredAnnotation;
 import org.eclipse.jetty.webapp.MetaData;
-import org.eclipse.jetty.webapp.WebAppContext;
 import org.eclipse.jetty.webapp.Origin;
+import org.eclipse.jetty.webapp.WebAppContext;
 
 /**
  * WebServletAnnotation
@@ -44,7 +45,7 @@ import org.eclipse.jetty.webapp.Origin;
 public class WebServletAnnotation extends DiscoveredAnnotation
 {
     private static final Logger LOG = Log.getLogger(WebServletAnnotation.class);
-    
+
     public WebServletAnnotation (WebAppContext context, String className)
     {
         super(context, className);
@@ -56,52 +57,52 @@ public class WebServletAnnotation extends DiscoveredAnnotation
         super(context, className, resource);
     }
     
-    /** 
-     * @see org.eclipse.jetty.annotations.ClassAnnotation#apply()
+    /**
+     * @see DiscoveredAnnotation#apply()
      */
     public void apply()
     {
         //TODO check this algorithm with new rules for applying descriptors and annotations in order
-        Class clazz = getTargetClass();
-        
+        Class<? extends Servlet> clazz = (Class<? extends Servlet>)getTargetClass();
+
         if (clazz == null)
         {
             LOG.warn(_className+" cannot be loaded");
             return;
         }
-        
+
         //Servlet Spec 8.1.1
         if (!HttpServlet.class.isAssignableFrom(clazz))
         {
             LOG.warn(clazz.getName()+" is not assignable from javax.servlet.http.HttpServlet");
             return;
         }
-        
+
         WebServlet annotation = (WebServlet)clazz.getAnnotation(WebServlet.class);
-        
+
         if (annotation.urlPatterns().length > 0 && annotation.value().length > 0)
         {
             LOG.warn(clazz.getName()+ " defines both @WebServlet.value and @WebServlet.urlPatterns");
             return;
         }
-        
+
         String[] urlPatterns = annotation.value();
         if (urlPatterns.length == 0)
             urlPatterns = annotation.urlPatterns();
-        
+
         if (urlPatterns.length == 0)
         {
             LOG.warn(clazz.getName()+ " defines neither @WebServlet.value nor @WebServlet.urlPatterns");
             return;
         }
-        
+
         //canonicalize the patterns
         ArrayList<String> urlPatternList = new ArrayList<String>();
         for (String p : urlPatterns)
             urlPatternList.add(Util.normalizePattern(p));
-        
+
         String servletName = (annotation.name().equals("")?clazz.getName():annotation.name());
-        
+
         MetaData metaData = _context.getMetaData();
 
         //Find out if a <servlet> already exists with this name
@@ -126,31 +127,31 @@ public class WebServletAnnotation extends DiscoveredAnnotation
             //No servlet of this name has already been defined, either by a descriptor
             //or another annotation (which would be impossible).
             holder = _context.getServletHandler().newServletHolder(Holder.Source.ANNOTATION);
-            holder.setHeldClass(clazz);   
-            metaData.setOrigin(servletName+".servlet.servlet-class");
-            
+            holder.setHeldClass(clazz);
+            metaData.setOrigin(servletName+".servlet.servlet-class",annotation,clazz);
+
             holder.setName(servletName);
             holder.setDisplayName(annotation.displayName());
-            metaData.setOrigin(servletName+".servlet.display-name");
-            
+            metaData.setOrigin(servletName+".servlet.display-name",annotation,clazz);
+
             holder.setInitOrder(annotation.loadOnStartup());
-            metaData.setOrigin(servletName+".servlet.load-on-startup");
-            
+            metaData.setOrigin(servletName+".servlet.load-on-startup",annotation,clazz);
+
             holder.setAsyncSupported(annotation.asyncSupported());
-            metaData.setOrigin(servletName+".servlet.async-supported");
-            
+            metaData.setOrigin(servletName+".servlet.async-supported",annotation,clazz);
+
             for (WebInitParam ip:annotation.initParams())
             {
                 holder.setInitParameter(ip.name(), ip.value());
-                metaData.setOrigin(servletName+".servlet.init-param."+ip.name());
+                metaData.setOrigin(servletName+".servlet.init-param."+ip.name(),ip,clazz);
             }
-          
+
             _context.getServletHandler().addServlet(holder);
-            ServletMapping mapping = new ServletMapping();  
+            ServletMapping mapping = new ServletMapping();
             mapping.setServletName(holder.getName());
             mapping.setPathSpecs( LazyList.toStringArray(urlPatternList));
             _context.getServletHandler().addServletMapping(mapping);
-            metaData.setOrigin(servletName+".servlet.mappings");
+            metaData.setOrigin(servletName+".servlet.mappings",annotation,clazz);
         }
         else
         {
@@ -158,26 +159,26 @@ public class WebServletAnnotation extends DiscoveredAnnotation
             //NOTE: this may be considered as "completing" an incomplete servlet registration, and it is
             //not clear from servlet 3.0 spec whether this is intended, or if only a ServletContext.addServlet() call
             //can complete it, see http://java.net/jira/browse/SERVLET_SPEC-42
-            if (holder.getClassName() == null) 
+            if (holder.getClassName() == null)
                 holder.setClassName(clazz.getName());
             if (holder.getHeldClass() == null)
                 holder.setHeldClass(clazz);
-            
+
             //check if the existing servlet has each init-param from the annotation
             //if not, add it
             for (WebInitParam ip:annotation.initParams())
             {
-                if (metaData.getOrigin(servletName+".servlet.init-param"+ip.name())==Origin.NotSet)
+                if (metaData.getOrigin(servletName+".servlet.init-param."+ip.name())==Origin.NotSet)
                 {
                     holder.setInitParameter(ip.name(), ip.value());
-                    metaData.setOrigin(servletName+".servlet.init-param."+ip.name());
-                }  
+                    metaData.setOrigin(servletName+".servlet.init-param."+ip.name(),ip,clazz);
+                }
             }
-            
-            //check the url-patterns   
-            //ServletSpec 3.0 p81 If a servlet already has url mappings from a 
+
+            //check the url-patterns
+            //ServletSpec 3.0 p81 If a servlet already has url mappings from a
             //webxml or fragment descriptor the annotation is ignored. However, we want to be able to
-            //replace mappings that were given in webdefault.xml     
+            //replace mappings that were given in webdefault.xml
             boolean mappingsExist = false;
             boolean anyNonDefaults = false;
             ServletMapping[] allMappings = _context.getServletHandler().getServletMappings();
@@ -186,7 +187,7 @@ public class WebServletAnnotation extends DiscoveredAnnotation
                 for (ServletMapping m:allMappings)
                 {
                     if (m.getServletName() != null && servletName.equals(m.getServletName()))
-                    {    
+                    {
                         mappingsExist = true;
                         if (!m.isDefault())
                         {
@@ -196,10 +197,10 @@ public class WebServletAnnotation extends DiscoveredAnnotation
                     }
                 }
             }
-            
+
             if (anyNonDefaults)
                 return;  //if any mappings already set by a descriptor that is not webdefault.xml, we're done
-       
+
             boolean clash = false;
             if (mappingsExist)
             {
@@ -214,14 +215,14 @@ public class WebServletAnnotation extends DiscoveredAnnotation
                     }
                 }
             }
-       
+
             if (!mappingsExist || !clash)
             {
                 ServletMapping m = new ServletMapping();
                 m.setServletName(servletName);
                 m.setPathSpecs(LazyList.toStringArray(urlPatternList));
-                _context.getServletHandler().addServletMapping(m); 
+                _context.getServletHandler().addServletMapping(m);
             }
         }
-    }   
+    }
 }
diff --git a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/WebServletAnnotationHandler.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/WebServletAnnotationHandler.java
index 0210b05..35510d2 100644
--- a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/WebServletAnnotationHandler.java
+++ b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/WebServletAnnotationHandler.java
@@ -18,68 +18,59 @@
 
 package org.eclipse.jetty.annotations;
 
-import java.util.List;
-
-import org.eclipse.jetty.annotations.AnnotationParser.DiscoverableAnnotationHandler;
-import org.eclipse.jetty.annotations.AnnotationParser.Value;
+import org.eclipse.jetty.annotations.AnnotationParser.ClassInfo;
+import org.eclipse.jetty.annotations.AnnotationParser.FieldInfo;
+import org.eclipse.jetty.annotations.AnnotationParser.MethodInfo;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.webapp.DiscoveredAnnotation;
 import org.eclipse.jetty.webapp.WebAppContext;
 
 /**
  * WebServletAnnotationHandler
  *
  * Process a WebServlet annotation on a class.
- * 
+ *
  */
 public class WebServletAnnotationHandler extends AbstractDiscoverableAnnotationHandler
 {
     private static final Logger LOG = Log.getLogger(WebServletAnnotationHandler.class);
-    
+
     public WebServletAnnotationHandler (WebAppContext context)
     {
         super(context);
     }
-    
-    public WebServletAnnotationHandler (WebAppContext context, List<DiscoveredAnnotation> list)
-    {
-        super(context, list);
-    }
-    
-    
-    /** 
+
+
+    /**
      * Handle discovering a WebServlet annotation.
-     * 
-     *  
-     * @see org.eclipse.jetty.annotations.AnnotationParser.DiscoverableAnnotationHandler#handleClass(java.lang.String, int, int, java.lang.String, java.lang.String, java.lang.String[], java.lang.String, java.util.List)
+     *
+     * @see org.eclipse.jetty.annotations.AnnotationParser.Handler#handle(ClassInfo, String)
      */
-    public void handleClass(String className, int version, int access, String signature, String superName, String[] interfaces, String annotationName,
-                            List<Value> values)
+    @Override
+    public void handle(ClassInfo info, String annotationName)
     {
-        if (!"javax.servlet.annotation.WebServlet".equals(annotationName))
-            return;    
-       
-        WebServletAnnotation annotation = new WebServletAnnotation (_context, className, _resource);
+        if (annotationName == null || !"javax.servlet.annotation.WebServlet".equals(annotationName))
+            return;
+        
+        WebServletAnnotation annotation = new WebServletAnnotation (_context, info.getClassName(), info.getContainingResource());
         addAnnotation(annotation);
     }
 
-    public void handleField(String className, String fieldName, int access, String fieldType, String signature, Object value, String annotation,
-                            List<Value> values)
+    @Override
+    public void handle(FieldInfo info, String annotationName)
     {
+        if (annotationName == null || !"javax.servlet.annotation.WebServlet".equals(annotationName))
+            return;
+        
         LOG.warn ("@WebServlet annotation not supported for fields");
     }
 
-    public void handleMethod(String className, String methodName, int access, String params, String signature, String[] exceptions, String annotation,
-                             List<Value> values)
+    @Override
+    public void handle(MethodInfo info, String annotationName)
     {
+        if (annotationName == null || !"javax.servlet.annotation.WebServlet".equals(annotationName))
+            return;
+        
         LOG.warn ("@WebServlet annotation not supported for methods");
     }
-
-
-    @Override
-    public String getAnnotationName()
-    {
-        return "javax.servlet.annotation.WebServlet";
-    }    
 }
diff --git a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/package-info.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/package-info.java
new file mode 100644
index 0000000..9dc6188
--- /dev/null
+++ b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Annotations : Support for Servlet Annotations
+ */
+package org.eclipse.jetty.annotations;
+
diff --git a/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/FilterC.java b/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/FilterC.java
index 7d2f142..955f09f 100644
--- a/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/FilterC.java
+++ b/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/FilterC.java
@@ -43,17 +43,17 @@ public class FilterC implements Filter
 {
     @Resource (mappedName="foo")
     private Double foo;
-    
+
     @PreDestroy
     public void pre ()
     {
-        
+
     }
-    
+
     @PostConstruct
     public void post()
     {
-        
+
     }
 
 
@@ -70,10 +70,10 @@ public class FilterC implements Filter
     }
 
     public void destroy()
-    { 
+    {
     }
 
     public void init(FilterConfig arg0) throws ServletException
-    {  
+    {
     }
 }
diff --git a/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/ServletC.java b/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/ServletC.java
index d047a4b..7632bc9 100644
--- a/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/ServletC.java
+++ b/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/ServletC.java
@@ -45,19 +45,19 @@ public class ServletC extends HttpServlet
 {
     @Resource (mappedName="foo", type=Double.class)
     private Double foo;
-    
+
     @PreDestroy
     public void pre ()
     {
-        
+
     }
-    
+
     @PostConstruct
     public void post()
     {
-        
+
     }
-    
+
     protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
     {
         response.setContentType("text/html");
diff --git a/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestAnnotationConfiguration.java b/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestAnnotationConfiguration.java
index 150cbdb..ec09ab6 100644
--- a/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestAnnotationConfiguration.java
+++ b/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestAnnotationConfiguration.java
@@ -18,28 +18,29 @@
 
 package org.eclipse.jetty.annotations;
 
+import static org.junit.Assert.assertNotNull;
+
 import java.io.File;
 import java.net.URL;
 import java.util.ArrayList;
 import java.util.List;
 
-import junit.framework.TestCase;
-
 import org.eclipse.jetty.util.resource.Resource;
 import org.eclipse.jetty.webapp.FragmentDescriptor;
 import org.eclipse.jetty.webapp.WebAppContext;
+import org.junit.Test;
 
 /**
  * TestAnnotationConfiguration
  *
  *
  */
-public class TestAnnotationConfiguration extends TestCase
+public class TestAnnotationConfiguration
 {
-    public void testGetFragmentFromJar ()
-    throws Exception
+    @Test
+    public void testGetFragmentFromJar() throws Exception
     {
-        String dir = System.getProperty("basedir", ".");   
+        String dir = System.getProperty("basedir", ".");
         File file = new File(dir);
         file=new File(file.getCanonicalPath());
         URL url=file.toURL();
diff --git a/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestAnnotationInheritance.java b/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestAnnotationInheritance.java
index 28f98ad..a628b57 100644
--- a/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestAnnotationInheritance.java
+++ b/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestAnnotationInheritance.java
@@ -18,23 +18,27 @@
 
 package org.eclipse.jetty.annotations;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
-import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
 import javax.naming.Context;
 import javax.naming.InitialContext;
 
-import org.eclipse.jetty.annotations.AnnotationParser.DiscoverableAnnotationHandler;
-import org.eclipse.jetty.annotations.AnnotationParser.Value;
-import org.eclipse.jetty.util.MultiMap;
+import org.eclipse.jetty.annotations.AnnotationParser.AbstractHandler;
+import org.eclipse.jetty.annotations.AnnotationParser.ClassInfo;
+import org.eclipse.jetty.annotations.AnnotationParser.FieldInfo;
+import org.eclipse.jetty.annotations.AnnotationParser.MethodInfo;
+import org.eclipse.jetty.util.ConcurrentHashSet;
 import org.junit.After;
 import org.junit.Test;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-
 /**
  *
  */
@@ -43,34 +47,32 @@ public class TestAnnotationInheritance
     List<String> classNames = new ArrayList<String>();
   
     
-    class SampleHandler implements DiscoverableAnnotationHandler
+    class SampleHandler extends AbstractHandler
     {
         public final List<String> annotatedClassNames = new ArrayList<String>();
         public final List<String> annotatedMethods = new ArrayList<String>();
         public final List<String> annotatedFields = new ArrayList<String>();
 
-        public void handleClass(String className, int version, int access, String signature, String superName, String[] interfaces, String annotation,
-                                List<Value> values)
+        public void handle(ClassInfo info, String annotation)
         {
-            annotatedClassNames.add(className);
+            if (annotation == null || !"org.eclipse.jetty.annotations.Sample".equals(annotation))
+                return;
+            
+            annotatedClassNames.add(info.getClassName());
         }
 
-        public void handleField(String className, String fieldName, int access, String fieldType, String signature, Object value, String annotation,
-                                List<Value> values)
-        {
-            annotatedFields.add(className+"."+fieldName);
+        public void handle(FieldInfo info, String annotation)
+        {   
+            if (annotation == null || !"org.eclipse.jetty.annotations.Sample".equals(annotation))
+                return;
+            annotatedFields.add(info.getClassInfo().getClassName()+"."+info.getFieldName());
         }
 
-        public void handleMethod(String className, String methodName, int access, String params, String signature, String[] exceptions, String annotation,
-                                 List<Value> values)
-        {
-            annotatedMethods.add(className+"."+methodName);
-        }
-        
-        @Override
-        public String getAnnotationName()
+        public void handle(MethodInfo info, String annotation)
         {
-            return "org.eclipse.jetty.annotations.Sample";
+            if (annotation == null || !"org.eclipse.jetty.annotations.Sample".equals(annotation))
+                return;
+            annotatedMethods.add(info.getClassInfo().getClassName()+"."+info.getMethodName());
         }
     }
 
@@ -91,8 +93,7 @@ public class TestAnnotationInheritance
 
         SampleHandler handler = new SampleHandler();
         AnnotationParser parser = new AnnotationParser();
-        parser.registerHandler(handler);
-        parser.parse(classNames, new ClassNameResolver ()
+        parser.parse(Collections.singleton(handler), classNames, new ClassNameResolver ()
         {
             public boolean isExcluded(String name)
             {
@@ -128,8 +129,7 @@ public class TestAnnotationInheritance
     {
         SampleHandler handler = new SampleHandler();
         AnnotationParser parser = new AnnotationParser();
-        parser.registerAnnotationHandler("org.eclipse.jetty.annotations.Sample", handler);
-        parser.parse(ClassB.class, new ClassNameResolver ()
+        parser.parse(Collections.singleton(handler), ClassB.class, new ClassNameResolver ()
         {
             public boolean isExcluded(String name)
             {
@@ -165,8 +165,7 @@ public class TestAnnotationInheritance
     {
         AnnotationParser parser = new AnnotationParser();
         SampleHandler handler = new SampleHandler();
-        parser.registerAnnotationHandler("org.eclipse.jetty.annotations.Sample", handler);
-        parser.parse(ClassA.class.getName(), new ClassNameResolver()
+        parser.parse(Collections.singleton(handler), ClassA.class.getName(), new ClassNameResolver()
         {
             public boolean isExcluded(String name)
             {
@@ -186,7 +185,7 @@ public class TestAnnotationInheritance
         handler.annotatedFields.clear();
         handler.annotatedMethods.clear();
 
-        parser.parse (ClassA.class.getName(), new ClassNameResolver()
+        parser.parse (Collections.singleton(handler), ClassA.class.getName(), new ClassNameResolver()
         {
             public boolean isExcluded(String name)
             {
@@ -204,9 +203,10 @@ public class TestAnnotationInheritance
     @Test
     public void testTypeInheritanceHandling() throws Exception
     {
+       ConcurrentHashMap<String, ConcurrentHashSet<String>> map = new ConcurrentHashMap<String, ConcurrentHashSet<String>>();
+        
         AnnotationParser parser = new AnnotationParser();
-        ClassInheritanceHandler handler = new ClassInheritanceHandler();
-        parser.registerClassHandler(handler);
+        ClassInheritanceHandler handler = new ClassInheritanceHandler(map);
 
         class Foo implements InterfaceD
         {
@@ -218,22 +218,22 @@ public class TestAnnotationInheritance
         classNames.add(InterfaceD.class.getName());
         classNames.add(Foo.class.getName());
 
-        parser.parse(classNames, null);
+        parser.parse(Collections.singleton(handler), classNames, null);
 
-        MultiMap map = handler.getMap();
         assertNotNull(map);
         assertFalse(map.isEmpty());
         assertEquals(2, map.size());
-        Map stringArrayMap = map.toStringArrayMap();
-        assertTrue (stringArrayMap.keySet().contains("org.eclipse.jetty.annotations.ClassA"));
-        assertTrue (stringArrayMap.keySet().contains("org.eclipse.jetty.annotations.InterfaceD"));
-        String[] classes = (String[])stringArrayMap.get("org.eclipse.jetty.annotations.ClassA");
-        assertEquals(1, classes.length);
-        assertEquals ("org.eclipse.jetty.annotations.ClassB", classes[0]);
-
-        classes = (String[])stringArrayMap.get("org.eclipse.jetty.annotations.InterfaceD");
-        assertEquals(2, classes.length);
-        assertEquals ("org.eclipse.jetty.annotations.ClassB", classes[0]);
-        assertEquals(Foo.class.getName(), classes[1]);
+      
+        
+        assertTrue (map.keySet().contains("org.eclipse.jetty.annotations.ClassA"));
+        assertTrue (map.keySet().contains("org.eclipse.jetty.annotations.InterfaceD"));
+        ConcurrentHashSet<String> classes = map.get("org.eclipse.jetty.annotations.ClassA");
+        assertEquals(1, classes.size());
+        assertEquals ("org.eclipse.jetty.annotations.ClassB", classes.iterator().next());
+
+        classes = map.get("org.eclipse.jetty.annotations.InterfaceD");
+        assertEquals(2, classes.size());
+        assertTrue(classes.contains("org.eclipse.jetty.annotations.ClassB"));
+        assertTrue(classes.contains(Foo.class.getName()));
     }
 }
diff --git a/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestAnnotationParser.java b/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestAnnotationParser.java
index 9fd791c..b8edaee 100644
--- a/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestAnnotationParser.java
+++ b/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestAnnotationParser.java
@@ -18,6 +18,12 @@
 
 package org.eclipse.jetty.annotations;
 
+import static org.hamcrest.Matchers.contains;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
@@ -25,126 +31,87 @@ import java.io.InputStream;
 import java.io.OutputStream;
 import java.net.URL;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 
-import org.eclipse.jetty.annotations.AnnotationParser.DiscoverableAnnotationHandler;
-import org.eclipse.jetty.annotations.AnnotationParser.Value;
+import org.eclipse.jetty.annotations.AnnotationParser.ClassInfo;
+import org.eclipse.jetty.annotations.AnnotationParser.FieldInfo;
+import org.eclipse.jetty.annotations.AnnotationParser.Handler;
+import org.eclipse.jetty.annotations.AnnotationParser.MethodInfo;
 import org.eclipse.jetty.toolchain.test.FS;
+import org.eclipse.jetty.toolchain.test.IO;
 import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
 import org.eclipse.jetty.toolchain.test.TestingDir;
-import org.eclipse.jetty.util.IO;
-import org.eclipse.jetty.util.resource.Resource;
+import org.junit.Assert;
 import org.junit.Rule;
 import org.junit.Test;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
 public class TestAnnotationParser
 {
-   
-    @Rule
-    public TestingDir testdir = new TestingDir();
-    
-    
-    
-    public static class TrackingAnnotationHandler implements DiscoverableAnnotationHandler
+    public static class TrackingAnnotationHandler extends AnnotationParser.AbstractHandler
     {
-
         private final String annotationName;
         public final Set<String> foundClasses;
 
         public TrackingAnnotationHandler(String annotationName)
         {
             this.annotationName = annotationName;
-            this.foundClasses = new HashSet<String>();
+            this.foundClasses = new HashSet<>();
         }
 
-       
-        public void handleClass(String className, int version, int access, String signature, String superName, String[] interfaces, String annotation,
-                List<Value> values)
-        {
-            foundClasses.add(className);
-        }
-
-      
-        public void handleMethod(String className, String methodName, int access, String desc, String signature, String[] exceptions, String annotation,
-                List<Value> values)
-        {
-            /* ignore */
-        }
-
-       
-        public void handleField(String className, String fieldName, int access, String fieldType, String signature, Object value, String annotation,
-                List<Value> values)
-        {
-            /* ignore */
-        }
-
-
         @Override
-        public String getAnnotationName()
+        public void handle(ClassInfo info, String annotation)
         {
-           return this.annotationName;
+            if (annotation == null || !annotationName.equals(annotation))
+                return;
+            foundClasses.add(info.getClassName());
         }
     }
 
+    @Rule
+    public TestingDir testdir = new TestingDir();
 
-
-    
     @Test
     public void testSampleAnnotation() throws Exception
     {
-        String[] classNames = new String[]{"org.eclipse.jetty.annotations.ClassA"};
+        String[] classNames = new String[]
+        { "org.eclipse.jetty.annotations.ClassA" };
         AnnotationParser parser = new AnnotationParser();
 
-        class SampleAnnotationHandler implements DiscoverableAnnotationHandler
+        class SampleAnnotationHandler extends AnnotationParser.AbstractHandler
         {
-            private List<String> methods = Arrays.asList("a", "b", "c", "d", "l");
-
-           
-            
-            
-            public void handleClass(String className, int version, int access, String signature, String superName, String[] interfaces, String annotation,
-                                    List<Value> values)
-            {
-                assertEquals ("org.eclipse.jetty.annotations.ClassA", className);
-            }
+            private List<String> methods = Arrays.asList("a","b","c","d","l");
 
-            public void handleField(String className, String fieldName, int access, String fieldType, String signature, Object value, String annotation,
-                                    List<Value> values)
+            public void handle(ClassInfo info, String annotation)
             {
-                assertEquals ("m", fieldName);
-                assertEquals (org.objectweb.asm.Type.OBJECT, org.objectweb.asm.Type.getType(fieldType).getSort());
-                assertEquals (1, values.size());
-                Value anv1 = values.get(0);
-                assertEquals ("value", anv1.getName());
-                assertEquals (7, anv1.getValue());
+                if (annotation == null || !"org.eclipse.jetty.annotations.Sample".equals(annotation))
+                    return;
 
+                assertEquals("org.eclipse.jetty.annotations.ClassA",info.getClassName());
             }
 
-            public void handleMethod(String className, String methodName, int access, String desc, String signature, String[] exceptions, String annotation,
-                                     List<Value> values)
-            {
-                assertEquals("org.eclipse.jetty.annotations.ClassA", className);
-                assertTrue(methods.contains(methodName));
-                assertEquals("org.eclipse.jetty.annotations.Sample", annotation);
+            public void handle(FieldInfo info, String annotation)
+            {                
+                if (annotation == null || !"org.eclipse.jetty.annotations.Sample".equals(annotation))
+                    return;
+                assertEquals("m",info.getFieldName());
+                assertEquals(org.objectweb.asm.Type.OBJECT,org.objectweb.asm.Type.getType(info.getFieldType()).getSort());
             }
 
-            @Override
-            public String getAnnotationName()
-            {
-                return "org.eclipse.jetty.annotations.Sample";
+            public void handle(MethodInfo info, String annotation)
+            {                
+                if (annotation == null || !"org.eclipse.jetty.annotations.Sample".equals(annotation))
+                    return;
+                assertEquals("org.eclipse.jetty.annotations.ClassA",info.getClassInfo().getClassName());
+                assertTrue(methods.contains(info.getMethodName()));
+                assertEquals("org.eclipse.jetty.annotations.Sample",annotation);
             }
         }
 
-        parser.registerHandler(new SampleAnnotationHandler());
-
-        long start = System.currentTimeMillis();
-        parser.parse(classNames, new ClassNameResolver ()
+        //long start = System.currentTimeMillis();
+        parser.parse(Collections.singleton(new SampleAnnotationHandler()), classNames,new ClassNameResolver()
         {
             public boolean isExcluded(String name)
             {
@@ -157,68 +124,64 @@ public class TestAnnotationParser
             }
 
         });
-        long end = System.currentTimeMillis();
-        //System.err.println("Time to parse class: "+((end-start)));
+        //long end = System.currentTimeMillis();
+
+        //System.err.println("Time to parse class: " + ((end - start)));
     }
 
     @Test
     public void testMultiAnnotation() throws Exception
     {
-        String[] classNames = new String[]{"org.eclipse.jetty.annotations.ClassB"};
+        String[] classNames = new String[]
+        { "org.eclipse.jetty.annotations.ClassB" };
         AnnotationParser parser = new AnnotationParser();
 
-        class MultiAnnotationHandler implements DiscoverableAnnotationHandler
+        class MultiAnnotationHandler extends AnnotationParser.AbstractHandler
         {
-            public void handleClass(String className, int version, int access, String signature, String superName, String[] interfaces, String annotation,
-                                    List<Value> values)
+            public void handle(ClassInfo info, String annotation)
             {
-                assertTrue("org.eclipse.jetty.annotations.ClassB".equals(className));
+                if (annotation == null || ! "org.eclipse.jetty.annotations.Multi".equals(annotation))
+                    return;
+                assertTrue("org.eclipse.jetty.annotations.ClassB".equals(info.getClassName()));
             }
 
-            public void handleField(String className, String fieldName, int access, String fieldType, String signature, Object value, String annotation,
-                                    List<Value> values)
-            {
-                //there should not be any
+            public void handle(FieldInfo info, String annotation)
+            {                
+                if (annotation == null || ! "org.eclipse.jetty.annotations.Multi".equals(annotation))
+                    return;
+                // there should not be any
                 fail();
             }
 
-            public void handleMethod(String className, String methodName, int access, String params, String signature, String[] exceptions, String annotation,
-                                     List<Value> values)
-            {
-                assertTrue("org.eclipse.jetty.annotations.ClassB".equals(className));
-                assertTrue("a".equals(methodName));
-            }
-
-            @Override
-            public String getAnnotationName()
-            {
-                return "org.eclipse.jetty.annotations.Multi";
+            public void handle(MethodInfo info, String annotation)
+            {  
+                if (annotation == null || ! "org.eclipse.jetty.annotations.Multi".equals(annotation))
+                    return;
+                assertTrue("org.eclipse.jetty.annotations.ClassB".equals(info.getClassInfo().getClassName()));
+                assertTrue("a".equals(info.getMethodName()));
             }
-            
-            
         }
 
-        parser.registerHandler(new MultiAnnotationHandler());
-        parser.parse(classNames, null);
+        parser.parse(Collections.singleton(new MultiAnnotationHandler()), classNames,null);
     }
 
-
     @Test
-    public void testHiddenFilesInJar () throws Exception
+    public void testHiddenFilesInJar() throws Exception
     {
         File badClassesJar = MavenTestingUtils.getTestResourceFile("bad-classes.jar");
         AnnotationParser parser = new AnnotationParser();
-        parser.parse(badClassesJar.toURI(), null);
-        //only the valid classes inside bad-classes.jar should be parsed. If any invalid classes are parsed and exception would be thrown here
+        Set<Handler> emptySet = Collections.emptySet();
+        parser.parse(emptySet, badClassesJar.toURI(),null);
+        // only the valid classes inside bad-classes.jar should be parsed. If any invalid classes are parsed and exception would be thrown here
     }
-    
+
     @Test
     public void testBasedirExclusion() throws Exception
     {
         // Build up basedir, which itself has a path segment that violates java package and classnaming.
         // The basedir should have no effect on annotation scanning.
         // Intentionally using a base director name that starts with a "."
-        // This mimics what you see in jenkins, hudson, hadoop, solr, camel, and selenium for their
+        // This mimics what you see in jenkins, hudson, hadoop, solr, camel, and selenium for their 
         // installed and/or managed webapps
         File basedir = testdir.getFile(".base/workspace/classes");
         FS.ensureEmpty(basedir);
@@ -231,20 +194,19 @@ public class TestAnnotationParser
 
         // Setup annotation scanning
         AnnotationParser parser = new AnnotationParser();
-        parser.registerHandler(tracker);
-
+        
         // Parse
-        parser.parse(Resource.newResource(basedir),null);
-
+        parser.parse(Collections.singleton(tracker), basedir.toURI(),null);
+        
         // Validate
-        assertTrue(tracker.foundClasses.contains(ClassA.class.getName()));
+        Assert.assertThat("Found Class", tracker.foundClasses, contains(ClassA.class.getName()));
     }
 
     private void copyClass(Class<?> clazz, File basedir) throws IOException
     {
         String classname = clazz.getName().replace('.',File.separatorChar) + ".class";
         URL url = this.getClass().getResource('/'+classname);
-        assertTrue(url != null);
+        Assert.assertThat("URL for: " + classname,url,notNullValue());
 
         String classpath = classname.substring(0,classname.lastIndexOf(File.separatorChar));
         FS.ensureDirExists(new File(basedir,classpath));
@@ -263,5 +225,4 @@ public class TestAnnotationParser
             IO.close(in);
         }
     }
-
 }
diff --git a/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestSecurityAnnotationConversions.java b/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestSecurityAnnotationConversions.java
index 46c77d0..0a08075 100644
--- a/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestSecurityAnnotationConversions.java
+++ b/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestSecurityAnnotationConversions.java
@@ -18,178 +18,167 @@
 
 package org.eclipse.jetty.annotations;
 
-import java.util.ArrayList;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
 import java.util.Arrays;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 
-import javax.servlet.annotation.ServletSecurity;
 import javax.servlet.annotation.HttpConstraint;
 import javax.servlet.annotation.HttpMethodConstraint;
-import javax.servlet.annotation.ServletSecurity.TransportGuarantee;
+import javax.servlet.annotation.ServletSecurity;
 import javax.servlet.annotation.ServletSecurity.EmptyRoleSemantic;
+import javax.servlet.annotation.ServletSecurity.TransportGuarantee;
 import javax.servlet.http.HttpServlet;
 
 import org.eclipse.jetty.security.ConstraintAware;
 import org.eclipse.jetty.security.ConstraintMapping;
-import org.eclipse.jetty.servlet.Holder;
 import org.eclipse.jetty.security.ConstraintSecurityHandler;
 import org.eclipse.jetty.servlet.ServletHolder;
 import org.eclipse.jetty.servlet.ServletMapping;
-import org.eclipse.jetty.util.LazyList;
 import org.eclipse.jetty.util.security.Constraint;
 import org.eclipse.jetty.webapp.WebAppContext;
+import org.junit.Test;
 
-import junit.framework.TestCase;
-
-public class TestSecurityAnnotationConversions extends TestCase
+public class TestSecurityAnnotationConversions
 {
-    @ServletSecurity(value=@HttpConstraint(value=EmptyRoleSemantic.DENY)) 
+    @ServletSecurity(value=@HttpConstraint(value=EmptyRoleSemantic.DENY))
     public static class DenyServlet extends HttpServlet
     {}
-    
+
     @ServletSecurity
     public static class PermitServlet extends HttpServlet
     {}
-    
-    @ServletSecurity(value=@HttpConstraint(value=EmptyRoleSemantic.PERMIT, transportGuarantee=TransportGuarantee.CONFIDENTIAL, rolesAllowed={"tom", "dick", "harry"})) 
+
+    @ServletSecurity(value=@HttpConstraint(value=EmptyRoleSemantic.PERMIT, transportGuarantee=TransportGuarantee.CONFIDENTIAL, rolesAllowed={"tom", "dick", "harry"}))
     public static class RolesServlet extends HttpServlet
     {}
-    
+
     @ServletSecurity(value=@HttpConstraint(value=EmptyRoleSemantic.PERMIT, transportGuarantee=TransportGuarantee.CONFIDENTIAL, rolesAllowed={"tom", "dick", "harry"}),
-                     httpMethodConstraints={@HttpMethodConstraint(value="GET")}) 
+                     httpMethodConstraints={@HttpMethodConstraint(value="GET")})
     public static class Method1Servlet extends HttpServlet
     {}
-    
+
     @ServletSecurity(value=@HttpConstraint(value=EmptyRoleSemantic.PERMIT, transportGuarantee=TransportGuarantee.CONFIDENTIAL, rolesAllowed={"tom", "dick", "harry"}),
-                     httpMethodConstraints={@HttpMethodConstraint(value="GET", transportGuarantee=TransportGuarantee.CONFIDENTIAL)}) 
+                     httpMethodConstraints={@HttpMethodConstraint(value="GET", transportGuarantee=TransportGuarantee.CONFIDENTIAL)})
     public static class Method2Servlet extends HttpServlet
     {}
-    
-    
+
+
     public void setUp()
     {
     }
-    
-    public void testDenyAllOnClass ()
-    throws Exception
+
+    @Test
+    public void testDenyAllOnClass() throws Exception
     {
 
         WebAppContext wac = makeWebAppContext(DenyServlet.class.getCanonicalName(), "denyServlet", new String[]{"/foo/*", "*.foo"});
-        
+
         //Assume we found 1 servlet with a @HttpConstraint with value=EmptyRoleSemantic.DENY security annotation
         ServletSecurityAnnotationHandler annotationHandler = new ServletSecurityAnnotationHandler(wac);
         AnnotationIntrospector introspector = new AnnotationIntrospector();
         introspector.registerHandler(annotationHandler);
-        
+
         //set up the expected outcomes:
         //1 ConstraintMapping per ServletMapping pathSpec
         Constraint expectedConstraint = new Constraint();
         expectedConstraint.setAuthenticate(true);
         expectedConstraint.setDataConstraint(Constraint.DC_NONE);
-        
+
         ConstraintMapping[] expectedMappings = new ConstraintMapping[2];
-        
+
         expectedMappings[0] = new ConstraintMapping();
         expectedMappings[0].setConstraint(expectedConstraint);
         expectedMappings[0].setPathSpec("/foo/*");
-        
+
         expectedMappings[1] = new ConstraintMapping();
         expectedMappings[1].setConstraint(expectedConstraint);
         expectedMappings[1].setPathSpec("*.foo");
-        
+
         introspector.introspect(DenyServlet.class);
-       
-        compareResults(expectedMappings, ((ConstraintAware)wac.getSecurityHandler()).getConstraintMappings());   
+
+        compareResults(expectedMappings, ((ConstraintAware)wac.getSecurityHandler()).getConstraintMappings());
     }
-    
-   
-    public void testPermitAll()
-    throws Exception
+
+    @Test
+    public void testPermitAll() throws Exception
     {
         //Assume we found 1 servlet with a @ServletSecurity security annotation
         WebAppContext wac = makeWebAppContext(PermitServlet.class.getCanonicalName(), "permitServlet", new String[]{"/foo/*", "*.foo"});
-        
+
         ServletSecurityAnnotationHandler annotationHandler = new ServletSecurityAnnotationHandler(wac);
         AnnotationIntrospector introspector = new AnnotationIntrospector();
         introspector.registerHandler(annotationHandler);
-        
-      
-        //set up the expected outcomes:
+
+
+        //set up the expected outcomes - no constraints at all as per Servlet Spec 3.1 pg 129
         //1 ConstraintMapping per ServletMapping pathSpec
-        Constraint expectedConstraint = new Constraint();
-        expectedConstraint.setAuthenticate(false);
-        expectedConstraint.setDataConstraint(Constraint.DC_NONE);
-                     
-        ConstraintMapping[] expectedMappings = new ConstraintMapping[2];
-        expectedMappings[0] = new ConstraintMapping();
-        expectedMappings[0].setConstraint(expectedConstraint);
-        expectedMappings[0].setPathSpec("/foo/*");
-        
-        expectedMappings[1] = new ConstraintMapping();
-        expectedMappings[1].setConstraint(expectedConstraint);
-        expectedMappings[1].setPathSpec("*.foo");
+       
 
-        introspector.introspect(PermitServlet.class);
+        ConstraintMapping[] expectedMappings = new ConstraintMapping[]{};
        
+        introspector.introspect(PermitServlet.class);
+
         compareResults (expectedMappings, ((ConstraintAware)wac.getSecurityHandler()).getConstraintMappings());
     }
-    
-    public void testRolesAllowedWithTransportGuarantee ()
-    throws Exception
+
+    @Test
+    public void testRolesAllowedWithTransportGuarantee() throws Exception
     {
-        //Assume we found 1 servlet with annotation with roles defined and 
+        //Assume we found 1 servlet with annotation with roles defined and
         //and a TransportGuarantee
-        
+
         WebAppContext wac = makeWebAppContext(RolesServlet.class.getCanonicalName(), "rolesServlet", new String[]{"/foo/*", "*.foo"});
-        
+
         ServletSecurityAnnotationHandler annotationHandler = new ServletSecurityAnnotationHandler(wac);
         AnnotationIntrospector introspector = new AnnotationIntrospector();
         introspector.registerHandler(annotationHandler);
-        
+
         //set up the expected outcomes:compareResults
-        //1 ConstraintMapping per ServletMapping 
+        //1 ConstraintMapping per ServletMapping
         Constraint expectedConstraint = new Constraint();
         expectedConstraint.setAuthenticate(true);
         expectedConstraint.setRoles(new String[]{"tom", "dick", "harry"});
         expectedConstraint.setDataConstraint(Constraint.DC_CONFIDENTIAL);
-        
+
         ConstraintMapping[] expectedMappings = new ConstraintMapping[2];
         expectedMappings[0] = new ConstraintMapping();
         expectedMappings[0].setConstraint(expectedConstraint);
         expectedMappings[0].setPathSpec("/foo/*");
-        
+
         expectedMappings[1] = new ConstraintMapping();
         expectedMappings[1].setConstraint(expectedConstraint);
         expectedMappings[1].setPathSpec("*.foo");
-        
+
         introspector.introspect(RolesServlet.class);
         compareResults (expectedMappings, ((ConstraintAware)wac.getSecurityHandler()).getConstraintMappings());
     }
-  
-  
-    public void testMethodAnnotation ()
-    throws Exception
+
+    @Test
+    public void testMethodAnnotation() throws Exception
     {
         //ServletSecurity annotation with HttpConstraint of TransportGuarantee.CONFIDENTIAL, and a list of rolesAllowed, and
         //a HttpMethodConstraint for GET method that permits all and has TransportGuarantee.NONE (ie is default)
-        
+
         WebAppContext wac = makeWebAppContext(Method1Servlet.class.getCanonicalName(), "method1Servlet",  new String[]{"/foo/*", "*.foo"});
-       
+
         //set up the expected outcomes: - a Constraint for the RolesAllowed on the class
-        //with userdata constraint of DC_CONFIDENTIAL 
+        //with userdata constraint of DC_CONFIDENTIAL
         //and mappings for each of the pathSpecs
         Constraint expectedConstraint1 = new Constraint();
         expectedConstraint1.setAuthenticate(true);
         expectedConstraint1.setRoles(new String[]{"tom", "dick", "harry"});
-        expectedConstraint1.setDataConstraint(Constraint.DC_CONFIDENTIAL);       
-        
+        expectedConstraint1.setDataConstraint(Constraint.DC_CONFIDENTIAL);
+
         //a Constraint for the PermitAll on the doGet method with a userdata
         //constraint of DC_CONFIDENTIAL inherited from the class
-        Constraint expectedConstraint2 = new Constraint();  
+        Constraint expectedConstraint2 = new Constraint();
         expectedConstraint2.setDataConstraint(Constraint.DC_NONE);
-        
+
         ConstraintMapping[] expectedMappings = new ConstraintMapping[4];
         expectedMappings[0] = new ConstraintMapping();
         expectedMappings[0].setConstraint(expectedConstraint1);
@@ -197,9 +186,9 @@ public class TestSecurityAnnotationConversions extends TestCase
         expectedMappings[0].setMethodOmissions(new String[]{"GET"});
         expectedMappings[1] = new ConstraintMapping();
         expectedMappings[1].setConstraint(expectedConstraint1);
-        expectedMappings[1].setPathSpec("*.foo"); 
+        expectedMappings[1].setPathSpec("*.foo");
         expectedMappings[1].setMethodOmissions(new String[]{"GET"});
-        
+
         expectedMappings[2] = new ConstraintMapping();
         expectedMappings[2].setConstraint(expectedConstraint2);
         expectedMappings[2].setPathSpec("/foo/*");
@@ -208,7 +197,7 @@ public class TestSecurityAnnotationConversions extends TestCase
         expectedMappings[3].setConstraint(expectedConstraint2);
         expectedMappings[3].setPathSpec("*.foo");
         expectedMappings[3].setMethod("GET");
-        
+
         AnnotationIntrospector introspector = new AnnotationIntrospector();
         ServletSecurityAnnotationHandler annotationHandler = new ServletSecurityAnnotationHandler(wac);
         introspector.registerHandler(annotationHandler);
@@ -216,8 +205,8 @@ public class TestSecurityAnnotationConversions extends TestCase
         compareResults (expectedMappings, ((ConstraintAware)wac.getSecurityHandler()).getConstraintMappings());
     }
 
-    public void testMethodAnnotation2 ()
-    throws Exception
+    @Test
+    public void testMethodAnnotation2() throws Exception
     {
         //A ServletSecurity annotation that has HttpConstraint of CONFIDENTIAL with defined roles, but a
         //HttpMethodConstraint for GET that permits all, but also requires CONFIDENTIAL
@@ -234,12 +223,12 @@ public class TestSecurityAnnotationConversions extends TestCase
         expectedConstraint1.setAuthenticate(true);
         expectedConstraint1.setRoles(new String[]{"tom", "dick", "harry"});
         expectedConstraint1.setDataConstraint(Constraint.DC_CONFIDENTIAL);
-     
+
         //a Constraint for the Permit on the GET method with a userdata
-        //constraint of DC_CONFIDENTIAL      
-        Constraint expectedConstraint2 = new Constraint();  
+        //constraint of DC_CONFIDENTIAL
+        Constraint expectedConstraint2 = new Constraint();
         expectedConstraint2.setDataConstraint(Constraint.DC_CONFIDENTIAL);
-        
+
         ConstraintMapping[] expectedMappings = new ConstraintMapping[4];
         expectedMappings[0] = new ConstraintMapping();
         expectedMappings[0].setConstraint(expectedConstraint1);
@@ -247,9 +236,9 @@ public class TestSecurityAnnotationConversions extends TestCase
         expectedMappings[0].setMethodOmissions(new String[]{"GET"});
         expectedMappings[1] = new ConstraintMapping();
         expectedMappings[1].setConstraint(expectedConstraint1);
-        expectedMappings[1].setPathSpec("*.foo"); 
+        expectedMappings[1].setPathSpec("*.foo");
         expectedMappings[1].setMethodOmissions(new String[]{"GET"});
-        
+
         expectedMappings[2] = new ConstraintMapping();
         expectedMappings[2].setConstraint(expectedConstraint2);
         expectedMappings[2].setPathSpec("/foo/*");
@@ -258,7 +247,7 @@ public class TestSecurityAnnotationConversions extends TestCase
         expectedMappings[3].setConstraint(expectedConstraint2);
         expectedMappings[3].setPathSpec("*.foo");
         expectedMappings[3].setMethod("GET");
-        
+
         introspector.introspect(Method2Servlet.class);
         compareResults (expectedMappings, ((ConstraintAware)wac.getSecurityHandler()).getConstraintMappings());
     }
@@ -269,10 +258,10 @@ public class TestSecurityAnnotationConversions extends TestCase
         assertEquals(expectedMappings.length, actualMappings.size());
 
         for (int k=0; k < actualMappings.size(); k++)
-        {   
+        {
             ConstraintMapping am = actualMappings.get(k);
             boolean matched  = false;
-          
+
             for (int i=0; i< expectedMappings.length && !matched; i++)
             {
                 ConstraintMapping em = expectedMappings[i];
@@ -280,8 +269,8 @@ public class TestSecurityAnnotationConversions extends TestCase
                 {
                     if ((em.getMethod()==null && am.getMethod() == null) || em.getMethod() != null && em.getMethod().equals(am.getMethod()))
                     {
-                        matched = true; 
-                      
+                        matched = true;
+
                         assertEquals(em.getConstraint().getAuthenticate(), am.getConstraint().getAuthenticate());
                         assertEquals(em.getConstraint().getDataConstraint(), am.getConstraint().getDataConstraint());
                         if (em.getMethodOmissions() == null)
@@ -292,7 +281,7 @@ public class TestSecurityAnnotationConversions extends TestCase
                         {
                             assertTrue(Arrays.equals(am.getMethodOmissions(), em.getMethodOmissions()));
                         }
-                        
+
                         if (em.getConstraint().getRoles() == null)
                         {
                             assertNull(am.getConstraint().getRoles());
@@ -300,21 +289,21 @@ public class TestSecurityAnnotationConversions extends TestCase
                         else
                         {
                             assertTrue(Arrays.equals(em.getConstraint().getRoles(), am.getConstraint().getRoles()));
-                        }  
+                        }
                     }
                 }
             }
-           
+
             if (!matched)
                 fail("No expected ConstraintMapping matching method:"+am.getMethod()+" pathSpec: "+am.getPathSpec());
         }
     }
-    
-    
+
+
     private WebAppContext makeWebAppContext (String className, String servletName, String[] paths)
     {
-        WebAppContext wac = new WebAppContext(); 
-   
+        WebAppContext wac = new WebAppContext();
+
         ServletHolder[] holders = new ServletHolder[1];
         holders[0] = new ServletHolder();
         holders[0].setClassName(className);
@@ -322,10 +311,10 @@ public class TestSecurityAnnotationConversions extends TestCase
         holders[0].setServletHandler(wac.getServletHandler());
         wac.getServletHandler().setServlets(holders);
         wac.setSecurityHandler(new ConstraintSecurityHandler());
-        
+
         ServletMapping[] servletMappings = new ServletMapping[1];
         servletMappings[0] = new ServletMapping();
-      
+
         servletMappings[0].setPathSpecs(paths);
         servletMappings[0].setServletName(servletName);
         wac.getServletHandler().setServletMappings(servletMappings);
diff --git a/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestServletAnnotations.java b/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestServletAnnotations.java
index b82c957..5885419 100644
--- a/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestServletAnnotations.java
+++ b/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestServletAnnotations.java
@@ -18,29 +18,45 @@
 
 package org.eclipse.jetty.annotations;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 
 import org.eclipse.jetty.security.ConstraintSecurityHandler;
 import org.eclipse.jetty.servlet.ServletHolder;
 import org.eclipse.jetty.servlet.ServletMapping;
+import org.eclipse.jetty.webapp.DiscoveredAnnotation;
 import org.eclipse.jetty.webapp.WebAppContext;
 import org.junit.Test;
 
 /**
  * TestServletAnnotations
- *
- *
  */
 public class TestServletAnnotations
 {
+    public class TestWebServletAnnotationHandler extends WebServletAnnotationHandler
+    {
+        List<DiscoveredAnnotation> _list = null;
+
+        public TestWebServletAnnotationHandler(WebAppContext context, List<DiscoveredAnnotation> list)
+        {
+            super(context);
+            _list = list;
+        }
+
+        @Override
+        public void addAnnotation(DiscoveredAnnotation a)
+        {
+            super.addAnnotation(a);
+            _list.add(a);
+        }
+    }
+    
     @Test
     public void testServletAnnotation() throws Exception
     {
@@ -48,11 +64,12 @@ public class TestServletAnnotations
         classes.add("org.eclipse.jetty.annotations.ServletC");
         AnnotationParser parser = new AnnotationParser();
 
-        WebAppContext wac = new WebAppContext();       
-        WebServletAnnotationHandler handler = new WebServletAnnotationHandler(wac);
-        parser.registerAnnotationHandler("javax.servlet.annotation.WebServlet", handler);
+        WebAppContext wac = new WebAppContext();
+        List<DiscoveredAnnotation> results = new ArrayList<DiscoveredAnnotation>();
+        
+        TestWebServletAnnotationHandler handler = new TestWebServletAnnotationHandler(wac, results);
        
-        parser.parse(classes, new ClassNameResolver ()
+        parser.parse(Collections.singleton(handler), classes, new ClassNameResolver ()
         {
             public boolean isExcluded(String name)
             {
@@ -64,30 +81,36 @@ public class TestServletAnnotations
                 return false;
             }
         });
-        
-        assertEquals(1, handler.getAnnotationList().size());
-        assertTrue(handler.getAnnotationList().get(0) instanceof WebServletAnnotation);
-        
-        handler.getAnnotationList().get(0).apply();
-       
+
+  
+        assertEquals(1, results.size());
+        assertTrue(results.get(0) instanceof WebServletAnnotation);
+
+        results.get(0).apply();
+
         ServletHolder[] holders = wac.getServletHandler().getServlets();
         assertNotNull(holders);
         assertEquals(1, holders.length);
-        assertEquals("CServlet", holders[0].getName());
+        
+        // Verify servlet annotations
+        ServletHolder cholder = holders[0];
+        assertThat("Servlet Name", cholder.getName(), is("CServlet"));
+        assertThat("InitParameter[x]", cholder.getInitParameter("x"), is("y"));
+        assertThat("Init Order", cholder.getInitOrder(), is(2));
+        assertThat("Async Supported", cholder.isAsyncSupported(), is(false));
+        
+        // Verify mappings
         ServletMapping[] mappings = wac.getServletHandler().getServletMappings();
         assertNotNull(mappings);
         assertEquals(1, mappings.length);
         String[] paths = mappings[0].getPathSpecs();
         assertNotNull(paths);
         assertEquals(2, paths.length);
-        assertEquals("y", holders[0].getInitParameter("x"));
-        assertEquals(2,holders[0].getInitOrder());
-        assertFalse(holders[0].isAsyncSupported());
     }
-    
+
     public void testDeclareRoles ()
     throws Exception
-    { 
+    {
         WebAppContext wac = new WebAppContext();
         ConstraintSecurityHandler sh = new ConstraintSecurityHandler();
         wac.setSecurityHandler(sh);
diff --git a/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/resources/ResourceA.java b/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/resources/ResourceA.java
index fa424a7..ed1d767 100644
--- a/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/resources/ResourceA.java
+++ b/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/resources/ResourceA.java
@@ -37,20 +37,20 @@ public class ResourceA implements javax.servlet.Servlet
     private Integer h;
     private Integer k;
 
-    
+
     @Resource(name="myf", mappedName="resB") //test giving both a name and mapped name from the environment
     private Integer f;//test a non inherited field that needs injection
-    
+
     @Resource(mappedName="resA") //test the default naming scheme but using a mapped name from the environment
     private Integer g;
-    
+
     @Resource(name="resA") //test using the given name as the name from the environment
     private Integer j;
-    
+
     @Resource(mappedName="resB") //test using the default name on an inherited field
     protected Integer n; //TODO - if it's inherited, is it supposed to use the classname of the class it is inherited by?
-    
-    
+
+
     @Resource(name="mye", mappedName="resA", type=Integer.class)
     public void setE(Integer e)
     {
@@ -60,28 +60,28 @@ public class ResourceA implements javax.servlet.Servlet
     {
         return this.e;
     }
-    
+
     public Integer getF()
     {
         return this.f;
     }
-    
+
     public Integer getG()
     {
         return this.g;
     }
-    
+
     public Integer getJ()
     {
         return this.j;
     }
-    
+
     @Resource(mappedName="resA")
     public void setH(Integer h)
     {
         this.h=h;
     }
-    
+
     @Resource(name="resA")
     public void setK(Integer k)
     {
@@ -91,30 +91,27 @@ public class ResourceA implements javax.servlet.Servlet
     {
         System.err.println("ResourceA.x");
     }
+    @Override
     public void destroy()
     {
-        // TODO Auto-generated method stub
-        
     }
+    @Override
     public ServletConfig getServletConfig()
     {
-        // TODO Auto-generated method stub
         return null;
     }
+    @Override
     public String getServletInfo()
     {
-        // TODO Auto-generated method stub
         return null;
     }
+    @Override
     public void init(ServletConfig arg0) throws ServletException
     {
-        // TODO Auto-generated method stub
-        
     }
+    @Override
     public void service(ServletRequest arg0, ServletResponse arg1)
             throws ServletException, IOException
     {
-        // TODO Auto-generated method stub
-        
     }
 }
diff --git a/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/resources/TestResourceAnnotations.java b/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/resources/TestResourceAnnotations.java
index a035848..199604d 100644
--- a/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/resources/TestResourceAnnotations.java
+++ b/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/resources/TestResourceAnnotations.java
@@ -18,8 +18,12 @@
 
 package org.eclipse.jetty.annotations.resources;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
 import java.lang.reflect.Field;
 import java.util.List;
+
 import javax.naming.Context;
 import javax.naming.InitialContext;
 
@@ -34,9 +38,6 @@ import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-
 public class TestResourceAnnotations
 {
     private Server server;
diff --git a/jetty-ant/pom.xml b/jetty-ant/pom.xml
new file mode 100644
index 0000000..f81e811
--- /dev/null
+++ b/jetty-ant/pom.xml
@@ -0,0 +1,72 @@
+<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">
+  <parent>
+    <groupId>org.eclipse.jetty</groupId>
+    <artifactId>jetty-project</artifactId>
+    <version>9.2.13.v20150730</version>
+  </parent>
+  <modelVersion>4.0.0</modelVersion>
+  <artifactId>jetty-ant</artifactId>
+  <packaging>jar</packaging>
+  <name>Jetty :: Ant Plugin</name>
+ 
+  <build>
+    <plugins>
+      <plugin>
+        <artifactId>maven-dependency-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>copy-lib-deps</id>
+            <phase>generate-resources</phase>
+            <goals>
+              <goal>copy-dependencies</goal>
+            </goals>
+            <configuration>
+              <includeGroupIds>org.eclipse.jetty</includeGroupIds>
+              <excludeGroupIds>org.eclipse.jetty.orbit,org.eclipse.jetty.spdy,org.eclipse.jetty.websocket,org.eclipse.jetty.drafts</excludeGroupIds>
+              <excludeArtifactIds>jetty-all,jetty-start,jetty-monitor,jetty-jsp</excludeArtifactIds>
+              <includeTypes>jar</includeTypes>
+              <outputDirectory>${project.build.directory}/test-lib</outputDirectory>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+  <dependencies>
+    <dependency>
+      <groupId>org.eclipse.jetty.toolchain</groupId>
+      <artifactId>jetty-test-helper</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>ant</groupId>
+      <artifactId>ant</artifactId>
+      <version>1.6.5</version>
+    </dependency>
+    <dependency>
+      <groupId>ant</groupId>
+      <artifactId>ant-launcher</artifactId>
+      <version>1.6.5</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-security</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-plus</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-webapp</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-annotations</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/jetty-ant/src/main/java/org/eclipse/jetty/ant/AntWebAppContext.java b/jetty-ant/src/main/java/org/eclipse/jetty/ant/AntWebAppContext.java
new file mode 100644
index 0000000..a8752b3
--- /dev/null
+++ b/jetty-ant/src/main/java/org/eclipse/jetty/ant/AntWebAppContext.java
@@ -0,0 +1,766 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2012 Sabre Holdings.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.ant;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.security.CodeSource;
+import java.security.PermissionCollection;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.EventListener;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.jar.Manifest;
+
+import javax.servlet.Servlet;
+
+import org.apache.tools.ant.AntClassLoader;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.types.FileSet;
+import org.eclipse.jetty.annotations.AnnotationConfiguration;
+import org.eclipse.jetty.ant.types.Attribute;
+import org.eclipse.jetty.ant.types.Attributes;
+import org.eclipse.jetty.ant.types.FileMatchingConfiguration;
+import org.eclipse.jetty.ant.utils.TaskLog;
+import org.eclipse.jetty.plus.webapp.EnvConfiguration;
+import org.eclipse.jetty.plus.webapp.PlusConfiguration;
+import org.eclipse.jetty.servlet.FilterHolder;
+import org.eclipse.jetty.servlet.FilterMapping;
+import org.eclipse.jetty.servlet.Holder;
+import org.eclipse.jetty.servlet.ServletHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jetty.servlet.ServletMapping;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.webapp.Configuration;
+import org.eclipse.jetty.webapp.FragmentConfiguration;
+import org.eclipse.jetty.webapp.JettyWebXmlConfiguration;
+import org.eclipse.jetty.webapp.MetaInfConfiguration;
+import org.eclipse.jetty.webapp.WebAppClassLoader;
+import org.eclipse.jetty.webapp.WebAppContext;
+import org.eclipse.jetty.webapp.WebInfConfiguration;
+import org.eclipse.jetty.webapp.WebXmlConfiguration;
+import org.eclipse.jetty.xml.XmlConfiguration;
+
+
+
+/**
+ * AntWebAppContext
+ * 
+ * Extension of WebAppContext to allow configuration via Ant environment.
+ *
+ */
+public class AntWebAppContext extends WebAppContext
+{
+    private static final Logger LOG = Log.getLogger(WebAppContext.class);
+    
+    public final AntWebInfConfiguration antWebInfConfiguration = new AntWebInfConfiguration();
+    public final WebXmlConfiguration webXmlConfiguration = new WebXmlConfiguration();
+    public final MetaInfConfiguration metaInfConfiguration = new MetaInfConfiguration();
+    public final FragmentConfiguration fragmentConfiguration = new FragmentConfiguration();
+    public final EnvConfiguration envConfiguration = new EnvConfiguration();
+    public final PlusConfiguration plusConfiguration = new PlusConfiguration();
+    public final AnnotationConfiguration annotationConfiguration = new AnnotationConfiguration();
+    public final JettyWebXmlConfiguration jettyWebXmlConfiguration = new JettyWebXmlConfiguration();
+
+
+    public final Configuration[] DEFAULT_CONFIGURATIONS = 
+        { 
+         antWebInfConfiguration,
+         webXmlConfiguration,
+         metaInfConfiguration,
+         fragmentConfiguration,
+         envConfiguration,
+         plusConfiguration,
+         annotationConfiguration,
+         jettyWebXmlConfiguration
+        };
+    
+
+    public final static String DEFAULT_CONTAINER_INCLUDE_JAR_PATTERN =
+    ".*/.*jsp-api-[^/]*\\.jar$|.*/.*jsp-[^/]*\\.jar$|.*/.*taglibs[^/]*\\.jar$|.*/.*jstl[^/]*\\.jar$|.*/.*jsf-impl-[^/]*\\.jar$|.*/.*javax.faces-[^/]*\\.jar$|.*/.*myfaces-impl-[^/]*\\.jar$";
+
+
+    /** Location of jetty-env.xml file. */
+    private File jettyEnvXml;
+    
+    /** List of web application libraries. */
+    private List libraries = new ArrayList();
+
+    /** List of web application class directories. */
+    private List classes = new ArrayList();
+    
+    /** context xml file to apply to the webapp */
+    private File contextXml;
+    
+    /** List of extra scan targets for this web application. */
+    private FileSet scanTargets;
+    
+    /** context attributes to set **/
+    private Attributes attributes;
+    
+    private Project project;
+    
+    private List<File> scanFiles;
+    
+
+
+    /** Extra scan targets. */
+    private FileMatchingConfiguration extraScanTargetsConfiguration;
+
+
+    private FileMatchingConfiguration librariesConfiguration;
+    
+
+    public static void dump(ClassLoader loader)
+    {
+        while (loader != null)
+        {
+            System.err.println(loader);
+            if (loader instanceof URLClassLoader)
+            {
+                URL[] urls = ((URLClassLoader)loader).getURLs();
+                if (urls != null)
+                {
+                    for (URL u:urls)
+                        System.err.println("\t"+u+"\n");
+                }
+            }
+            loader = loader.getParent();
+        }
+    }
+
+    
+    /**
+     * AntURLClassLoader
+     *
+     * Adapt the AntClassLoader which is not a URLClassLoader - this is needed for
+     * jsp to be able to search the classpath.
+     */
+    public static class AntURLClassLoader extends URLClassLoader
+    {
+        private AntClassLoader antLoader;
+        
+        public AntURLClassLoader(AntClassLoader antLoader)
+        {
+            super(new URL[] {}, antLoader);
+            this.antLoader = antLoader;      
+        }
+
+        @Override
+        public InputStream getResourceAsStream(String name)
+        {
+            return super.getResourceAsStream(name);
+        }
+
+        @Override
+        public void close() throws IOException
+        {
+            super.close();
+        }
+
+        @Override
+        protected void addURL(URL url)
+        {
+            super.addURL(url);
+        }
+
+        @Override
+        public URL[] getURLs()
+        {
+            Set<URL> urls = new HashSet<URL>();
+            
+            //convert urls from antLoader
+            String[] paths = antLoader.getClasspath().split(new String(new char[]{File.pathSeparatorChar}));
+            if (paths != null)
+            {
+                for (String p:paths)
+                {
+                    File f = new File(p);
+                    try
+                    {
+                        urls.add(f.toURI().toURL());   
+                    }
+                    catch (Exception e)
+                    {
+                        LOG.ignore(e);
+                    }
+                }
+            }
+            
+            //add in any that may have been added to us as a URL directly
+            URL[] ourURLS = super.getURLs();
+            if (ourURLS != null)
+            {
+                for (URL u:ourURLS)
+                    urls.add(u);
+            }
+            
+            return urls.toArray(new URL[urls.size()]);
+        }
+
+        @Override
+        protected Class<?> findClass(String name) throws ClassNotFoundException
+        {
+            return super.findClass(name);
+        }
+
+        @Override
+        protected Package definePackage(String name, Manifest man, URL url) throws IllegalArgumentException
+        {
+            return super.definePackage(name, man, url);
+        }
+
+        @Override
+        public URL findResource(String name)
+        {
+            return super.findResource(name);
+        }
+
+        @Override
+        public Enumeration<URL> findResources(String name) throws IOException
+        {
+            return super.findResources(name);
+        }
+
+        @Override
+        protected PermissionCollection getPermissions(CodeSource codesource)
+        {
+            return super.getPermissions(codesource);
+        }
+
+        @Override
+        public Class<?> loadClass(String name) throws ClassNotFoundException
+        {
+            return super.loadClass(name);
+        }
+
+        @Override
+        protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
+        {
+            return super.loadClass(name, resolve);
+        }
+
+        @Override
+        protected Object getClassLoadingLock(String className)
+        {
+            return super.getClassLoadingLock(className);
+        }
+
+        @Override
+        public URL getResource(String name)
+        {
+            return super.getResource(name);
+        }
+
+        @Override
+        public Enumeration<URL> getResources(String name) throws IOException
+        {
+            return super.getResources(name);
+        }
+
+        @Override
+        protected Package definePackage(String name, String specTitle, String specVersion, String specVendor, String implTitle, String implVersion,
+                                        String implVendor, URL sealBase) throws IllegalArgumentException
+        {
+            return super.definePackage(name, specTitle, specVersion, specVendor, implTitle, implVersion, implVendor, sealBase);
+        }
+
+        @Override
+        protected Package getPackage(String name)
+        {
+            return super.getPackage(name);
+        }
+
+        @Override
+        protected Package[] getPackages()
+        {
+            return super.getPackages();
+        }
+
+        @Override
+        protected String findLibrary(String libname)
+        {
+            return super.findLibrary(libname);
+        }
+
+        @Override
+        public void setDefaultAssertionStatus(boolean enabled)
+        {
+            super.setDefaultAssertionStatus(enabled);
+        }
+
+        @Override
+        public void setPackageAssertionStatus(String packageName, boolean enabled)
+        {
+            super.setPackageAssertionStatus(packageName, enabled);
+        }
+
+        @Override
+        public void setClassAssertionStatus(String className, boolean enabled)
+        {
+            super.setClassAssertionStatus(className, enabled);
+        }
+
+        @Override
+        public void clearAssertionStatus()
+        {
+            super.clearAssertionStatus();
+        }
+    }
+    
+    
+    /**
+     * AntServletHolder
+     *
+     *
+     */
+    public static class AntServletHolder extends ServletHolder
+    {
+
+        public AntServletHolder()
+        {
+            super();
+        }
+
+
+        public AntServletHolder(Class<? extends Servlet> servlet)
+        {
+            super(servlet);
+        }
+
+
+        public AntServletHolder(Servlet servlet)
+        {
+            super(servlet);
+        }
+
+
+        public AntServletHolder(String name, Class<? extends Servlet> servlet)
+        {
+            super(name, servlet);
+        }
+
+
+        public AntServletHolder(String name, Servlet servlet)
+        {
+            super(name, servlet);
+        }
+
+        protected String getSystemClassPath (ClassLoader loader) throws Exception
+        {
+            StringBuilder classpath=new StringBuilder();
+            while (loader != null)
+            {
+                if (loader instanceof URLClassLoader)
+                {
+                    URL[] urls = ((URLClassLoader)loader).getURLs();
+                    if (urls != null)
+                    {
+                        for (int i=0;i<urls.length;i++)
+                        {
+                            Resource resource = Resource.newResource(urls[i]);
+                            File file=resource.getFile();
+                            if (file!=null && file.exists())
+                            {
+                                if (classpath.length()>0)
+                                    classpath.append(File.pathSeparatorChar);
+                                classpath.append(file.getAbsolutePath());
+                            }
+                        }
+                    }
+                }
+                else if (loader instanceof AntClassLoader)
+                {
+                    classpath.append(((AntClassLoader)loader).getClasspath());
+                }
+
+                loader = loader.getParent();
+            }
+
+            return classpath.toString();
+        }
+
+    }
+
+    
+    
+    /**
+     * AntServletHandler
+     *
+     *
+     */
+    public static class AntServletHandler extends ServletHandler
+    {
+
+        @Override
+        public ServletHolder newServletHolder(Holder.Source source)
+        {
+            return new AntServletHolder();
+        }
+
+    }
+
+
+
+    /**
+     * Default constructor. Takes project as an argument
+     *
+     * @param project the project.
+     */
+    public AntWebAppContext(Project project) throws Exception
+    {
+        super();
+        this.project = project;
+        setConfigurations(DEFAULT_CONFIGURATIONS);
+        setAttribute(WebInfConfiguration.CONTAINER_JAR_PATTERN, DEFAULT_CONTAINER_INCLUDE_JAR_PATTERN);
+        setParentLoaderPriority(true);
+    }
+    
+
+    /**
+     * Adds a new Ant's attributes tag object if it have not been created yet.
+     */
+    public void addAttributes(Attributes atts)
+    {
+        if (this.attributes != null)
+        {
+            throw new BuildException("Only one <attributes> tag is allowed!");
+        }
+
+        this.attributes = atts;
+    }
+
+    
+    public void addLib(FileSet lib)
+    {
+        libraries.add(lib);
+    }
+
+
+    public void addClasses(FileSet classes)
+    {
+        this.classes.add(classes);
+    }
+    
+    
+
+    @Override
+    protected ServletHandler newServletHandler()
+    {
+        return new AntServletHandler();
+    }
+
+
+    public void setJettyEnvXml(File jettyEnvXml)
+    {
+        this.jettyEnvXml = jettyEnvXml;
+        TaskLog.log("jetty-env.xml file: = " + (jettyEnvXml == null ? null : jettyEnvXml.getAbsolutePath()));
+    }
+
+    public File getJettyEnvXml ()
+    {
+        return this.jettyEnvXml;
+    }
+
+    
+
+
+    public List getLibraries()
+    {
+        return librariesConfiguration.getBaseDirectories();
+    }
+
+ 
+    public void addScanTargets(FileSet scanTargets)
+    {
+        if (this.scanTargets != null)
+        {
+            throw new BuildException("Only one <scanTargets> tag is allowed!");
+        }
+
+        this.scanTargets = scanTargets;
+    }
+    
+    public List getScanTargetFiles () 
+    {
+        if (this.scanTargets == null)
+            return null;
+        
+      
+        FileMatchingConfiguration configuration = new FileMatchingConfiguration();
+        configuration.addDirectoryScanner(scanTargets.getDirectoryScanner(project));
+        return configuration.getBaseDirectories();
+    }
+    
+    public List<File> getScanFiles()
+    {
+        if (scanFiles == null)
+            scanFiles = initScanFiles();
+        return scanFiles;
+    }
+    
+    
+    public boolean isScanned (File file)
+    {
+       List<File> files = getScanFiles();
+       if (files == null || files.isEmpty())
+           return false;
+       return files.contains(file);
+    }
+    
+    
+    public List<File> initScanFiles ()
+    {
+        List<File> scanList = new ArrayList<File>();
+        
+        if (getDescriptor() != null)
+        {
+            try (Resource r = Resource.newResource(getDescriptor());)
+            {
+                scanList.add(r.getFile());
+            }
+            catch (IOException e)
+            {
+                throw new BuildException(e);
+            }
+        }
+
+        if (getJettyEnvXml() != null)
+        {
+            try (Resource r = Resource.newResource(getJettyEnvXml());)
+            {
+                scanList.add(r.getFile());
+            }
+            catch (IOException e)
+            {
+                throw new BuildException("Problem configuring scanner for jetty-env.xml", e);
+            }
+        }
+
+        if (getDefaultsDescriptor() != null)
+        {
+            try (Resource r = Resource.newResource(getDefaultsDescriptor());)
+            {
+                if (!WebAppContext.WEB_DEFAULTS_XML.equals(getDefaultsDescriptor()))
+                {   
+                    scanList.add(r.getFile());
+                }
+            }
+            catch (IOException e)
+            {
+                throw new BuildException("Problem configuring scanner for webdefaults.xml", e);
+            }
+        }
+
+        if (getOverrideDescriptor() != null)
+        {
+            try
+            {
+                Resource r = Resource.newResource(getOverrideDescriptor());
+                scanList.add(r.getFile());
+            }
+            catch (IOException e)
+            {
+                throw new BuildException("Problem configuring scanner for webdefaults.xml", e);
+            }
+        }
+
+        //add any extra classpath and libs 
+        List<File> cpFiles = getClassPathFiles();
+        if (cpFiles != null)
+            scanList.addAll(cpFiles);
+        
+        //any extra scan targets
+        @SuppressWarnings("unchecked")
+        List<File> scanFiles = (List<File>)getScanTargetFiles();
+        if (scanFiles != null)
+            scanList.addAll(scanFiles);
+        
+        return scanList;
+    }
+    
+    
+    
+    @Override
+    public void setWar(String path)
+    {
+        super.setWar(path);
+
+        try
+        {
+            Resource war = Resource.newResource(path);
+            if (war.exists() && war.isDirectory() && getDescriptor() == null)
+            {
+                Resource webXml = war.addPath("WEB-INF/web.xml");
+                setDescriptor(webXml.toString());
+            }
+        }
+        catch (IOException e)
+        {
+            throw new BuildException(e);
+        }
+    }
+
+
+    /**
+     * 
+     */
+    public void doStart()
+    {
+        try
+        {
+            TaskLog.logWithTimestamp("Starting web application "+this.getDescriptor());
+            if (jettyEnvXml != null && jettyEnvXml.exists())
+                envConfiguration.setJettyEnvXml(Resource.toURL(jettyEnvXml));
+            
+            ClassLoader parentLoader = this.getClass().getClassLoader();
+            if (parentLoader instanceof AntClassLoader)
+                parentLoader = new AntURLClassLoader((AntClassLoader)parentLoader);
+
+            setClassLoader(new WebAppClassLoader(parentLoader, this));
+            if (attributes != null && attributes.getAttributes() != null)
+            {
+                for (Attribute a:attributes.getAttributes())
+                    setAttribute(a.getName(), a.getValue());
+            }
+            
+            //apply a context xml file if one was supplied
+            if (contextXml != null)
+            {
+                XmlConfiguration xmlConfiguration = new XmlConfiguration(Resource.toURL(contextXml));
+                TaskLog.log("Applying context xml file "+contextXml);
+                xmlConfiguration.configure(this);   
+            }
+            
+            super.doStart();
+        }
+        catch (Exception e)
+        {
+            TaskLog.log(e.toString());
+        }
+    }
+
+    public void doStop()
+    {
+        try
+        {
+            scanFiles = null;
+            TaskLog.logWithTimestamp("Stopping web application "+this);
+            Thread.currentThread().sleep(500L);
+            super.doStop();
+            //remove all filters, servlets and listeners. They will be recreated
+            //either via application of a context xml file or web.xml or annotation or servlet api
+            setEventListeners(new EventListener[0]);
+            getServletHandler().setFilters(new FilterHolder[0]);
+            getServletHandler().setFilterMappings(new FilterMapping[0]);
+            getServletHandler().setServlets(new ServletHolder[0]);
+            getServletHandler().setServletMappings(new ServletMapping[0]);
+        }
+        catch (InterruptedException e)
+        {
+            TaskLog.log(e.toString());
+        }
+        catch (Exception e)
+        {
+            TaskLog.log(e.toString());
+        }
+    }
+
+
+    
+    /**
+     * @return a list of classpath files (libraries and class directories).
+     */
+    public List<File> getClassPathFiles()
+    {
+        List<File> classPathFiles = new ArrayList<File>();
+        Iterator classesIterator = classes.iterator();
+        while (classesIterator.hasNext())
+        {
+            FileSet clazz = (FileSet) classesIterator.next();
+            classPathFiles.add(clazz.getDirectoryScanner(project).getBasedir());
+        }
+
+        Iterator iterator = libraries.iterator();
+        while (iterator.hasNext())
+        {
+            FileSet library = (FileSet) iterator.next();
+            String[] includedFiles = library.getDirectoryScanner(project).getIncludedFiles();
+            File baseDir = library.getDirectoryScanner(project).getBasedir();
+
+            for (int i = 0; i < includedFiles.length; i++)
+            {
+                classPathFiles.add(new File(baseDir, includedFiles[i]));
+            }
+        }
+
+
+        return classPathFiles;
+    }
+
+    
+    /**
+     * @return a <code>FileMatchingConfiguration</code> object describing the
+     *         configuration of all libraries added to this particular web app
+     *         (both classes and libraries).
+     */
+    public FileMatchingConfiguration getLibrariesConfiguration()
+    {
+        FileMatchingConfiguration config = new FileMatchingConfiguration();
+
+        Iterator classesIterator = classes.iterator();
+        while (classesIterator.hasNext())
+        {
+            FileSet clazz = (FileSet) classesIterator.next();
+            config.addDirectoryScanner(clazz.getDirectoryScanner(project));
+        }
+
+        Iterator librariesIterator = libraries.iterator();
+        while (librariesIterator.hasNext())
+        {
+            FileSet library = (FileSet) librariesIterator.next();
+            config.addDirectoryScanner(library.getDirectoryScanner(project));
+        }
+
+        return config;
+    }
+
+
+    public File getContextXml()
+    {
+        return contextXml;
+    }
+
+
+    public void setContextXml(File contextXml)
+    {
+        this.contextXml = contextXml;
+    }
+    
+}
diff --git a/jetty-ant/src/main/java/org/eclipse/jetty/ant/AntWebInfConfiguration.java b/jetty-ant/src/main/java/org/eclipse/jetty/ant/AntWebInfConfiguration.java
new file mode 100644
index 0000000..09b2e66
--- /dev/null
+++ b/jetty-ant/src/main/java/org/eclipse/jetty/ant/AntWebInfConfiguration.java
@@ -0,0 +1,170 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.ant;
+
+import java.io.File;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import org.apache.tools.ant.AntClassLoader;
+import org.eclipse.jetty.util.PatternMatcher;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.webapp.WebAppClassLoader;
+import org.eclipse.jetty.webapp.WebAppContext;
+import org.eclipse.jetty.webapp.WebInfConfiguration;
+import org.eclipse.jetty.webapp.WebXmlConfiguration;
+
+public class AntWebInfConfiguration extends WebInfConfiguration
+{
+
+    
+    @Override
+    public void preConfigure(final WebAppContext context) throws Exception
+    {        
+        //Make a temp directory for the webapp if one is not already set
+        resolveTempDirectory(context);
+        
+        //Extract webapp if necessary
+        unpack (context);
+
+        
+        //Apply an initial ordering to the jars which governs which will be scanned for META-INF
+        //info and annotations. The ordering is based on inclusion patterns.       
+        String tmp = (String)context.getAttribute(WEBINF_JAR_PATTERN);
+        Pattern webInfPattern = (tmp==null?null:Pattern.compile(tmp));
+        tmp = (String)context.getAttribute(CONTAINER_JAR_PATTERN);
+        Pattern containerPattern = (tmp==null?null:Pattern.compile(tmp));
+
+        //Apply ordering to container jars - if no pattern is specified, we won't
+        //match any of the container jars
+        PatternMatcher containerJarNameMatcher = new PatternMatcher ()
+        {
+            public void matched(URI uri) throws Exception
+            {
+                context.getMetaData().addContainerResource(Resource.newResource(uri));
+            }      
+        };
+        ClassLoader loader = context.getClassLoader();
+        if (loader != null)
+        {
+            loader = loader.getParent();
+            if (loader != null)
+            {
+                URI[] containerUris = null; 
+           
+                if (loader instanceof URLClassLoader)
+                {
+                    URL[] urls = ((URLClassLoader)loader).getURLs();
+                    if (urls != null)
+                    {
+                        containerUris = new URI[urls.length];
+                        int i=0;
+                        for (URL u : urls)
+                        {
+                            try 
+                            {
+                                containerUris[i] = u.toURI();
+                            }
+                            catch (URISyntaxException e)
+                            {
+                                containerUris[i] = new URI(u.toString().replaceAll(" ", "%20"));
+                            }  
+                            i++;
+                        }
+                    }
+                }
+                else if (loader instanceof AntClassLoader)
+                {
+                    AntClassLoader antLoader = (AntClassLoader)loader;     
+                    String[] paths = antLoader.getClasspath().split(new String(new char[]{File.pathSeparatorChar}));
+                    if (paths != null)
+                    {
+                        containerUris = new URI[paths.length];
+                        int i=0;
+                        for (String p:paths)
+                        {
+                            File f = new File(p);
+                            containerUris[i] = f.toURI();
+                            i++;
+                        }
+                    }
+                }
+
+                containerJarNameMatcher.match(containerPattern, containerUris, false);
+            }
+        }
+        
+        //Apply ordering to WEB-INF/lib jars
+        PatternMatcher webInfJarNameMatcher = new PatternMatcher ()
+        {
+            @Override
+            public void matched(URI uri) throws Exception
+            {
+                context.getMetaData().addWebInfJar(Resource.newResource(uri));
+            }      
+        };
+        List<Resource> jars = findJars(context);
+       
+        //Convert to uris for matching
+        URI[] uris = null;
+        if (jars != null)
+        {
+            uris = new URI[jars.size()];
+            int i=0;
+            for (Resource r: jars)
+            {
+                uris[i++] = r.getURI();
+            }
+        }
+        webInfJarNameMatcher.match(webInfPattern, uris, true); //null is inclusive, no pattern == all jars match 
+        
+        //No pattern to appy to classes, just add to metadata
+        context.getMetaData().setWebInfClassesDirs(findClassDirs(context));
+    }
+    
+
+    /**
+     * Adds classpath files into web application classloader, and
+     * sets web.xml and base directory for the configured web application.
+     *
+     * @see WebXmlConfiguration#configure(WebAppContext)
+     */
+    public void configure(WebAppContext context) throws Exception
+    {
+        if (context instanceof AntWebAppContext)
+        {
+            List<File> classPathFiles = ((AntWebAppContext)context).getClassPathFiles();
+            if (classPathFiles != null)
+            {
+                for (File cpFile:classPathFiles)
+                {
+                    if (cpFile.exists())
+                    {
+                        ((WebAppClassLoader) context.getClassLoader()).addClassPath(cpFile.getCanonicalPath());
+                    }
+                }
+            }
+        }
+        super.configure(context);
+    }
+}
diff --git a/jetty-ant/src/main/java/org/eclipse/jetty/ant/AntWebXmlConfiguration.java b/jetty-ant/src/main/java/org/eclipse/jetty/ant/AntWebXmlConfiguration.java
new file mode 100644
index 0000000..2022b1c
--- /dev/null
+++ b/jetty-ant/src/main/java/org/eclipse/jetty/ant/AntWebXmlConfiguration.java
@@ -0,0 +1,65 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2012 Sabre Holdings.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.ant;
+
+import java.io.File;
+import java.util.List;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.webapp.WebXmlConfiguration;
+
+
+/**
+ * This configuration object provides additional way to inject application
+ * properties into the configured web application. The list of classpath files,
+ * the application base directory and web.xml file could be specified in this
+ * way.
+ */
+public class AntWebXmlConfiguration extends WebXmlConfiguration
+{
+    private static final Logger LOG = Log.getLogger(WebXmlConfiguration.class);
+
+
+
+    /** List of classpath files. */
+    private List classPathFiles;
+
+    /** Web application root directory. */
+    private File webAppBaseDir;
+
+
+    public AntWebXmlConfiguration()
+    {
+        super();
+    }
+
+    public void setClassPathFiles(List classPathFiles)
+    {
+        this.classPathFiles = classPathFiles;
+    }
+
+    public void setWebAppBaseDir(File webAppBaseDir)
+    {
+        this.webAppBaseDir = webAppBaseDir;
+    }
+
+
+
+}
diff --git a/jetty-ant/src/main/java/org/eclipse/jetty/ant/JettyRunTask.java b/jetty-ant/src/main/java/org/eclipse/jetty/ant/JettyRunTask.java
new file mode 100644
index 0000000..626ea4d
--- /dev/null
+++ b/jetty-ant/src/main/java/org/eclipse/jetty/ant/JettyRunTask.java
@@ -0,0 +1,327 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2012 Sabre Holdings.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.ant;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Task;
+import org.apache.tools.ant.taskdefs.Property;
+import org.eclipse.jetty.ant.types.Connector;
+import org.eclipse.jetty.ant.types.Connectors;
+import org.eclipse.jetty.ant.types.ContextHandlers;
+import org.eclipse.jetty.ant.types.LoginServices;
+import org.eclipse.jetty.ant.types.SystemProperties;
+import org.eclipse.jetty.ant.utils.TaskLog;
+import org.eclipse.jetty.security.LoginService;
+import org.eclipse.jetty.server.RequestLog;
+import org.eclipse.jetty.webapp.WebAppContext;
+
+/**
+ * Ant task for running a Jetty server.
+ */
+public class JettyRunTask extends Task
+{
+
+    private int scanIntervalSeconds; 
+    
+    
+    /** Temporary files directory. */
+    private File tempDirectory;
+
+    /** List of web applications to be deployed. */
+    private List<AntWebAppContext> webapps = new ArrayList<AntWebAppContext>();
+
+    /** Location of jetty.xml file. */
+    private File jettyXml;
+
+    /** List of server connectors. */
+    private Connectors connectors = null;
+
+    /** Server request logger object. */
+    private RequestLog requestLog;
+
+    /** List of login services. */
+    private LoginServices loginServices;
+
+    /** List of system properties to be set. */
+    private SystemProperties systemProperties;
+    
+    /** List of other contexts to deploy */
+    private ContextHandlers contextHandlers;
+
+ 
+    /** Port Jetty will use for the default connector */
+    private int jettyPort = 8080;
+    
+    private int stopPort;
+    
+    private String stopKey;
+
+    private boolean daemon;
+    
+  
+
+
+    public JettyRunTask()
+    {
+        TaskLog.setTask(this);
+    }
+
+    /**
+     * Creates a new <code>WebApp</code> Ant object.
+     *
+     */
+    public void addWebApp(AntWebAppContext webapp)
+    {
+       webapps.add(webapp);
+    }
+    
+    
+
+    /**
+     * Adds a new Ant's connector tag object if it have not been created yet.
+     */
+    public void addConnectors(Connectors connectors)
+    {
+        if (this.connectors != null)
+            throw new BuildException("Only one <connectors> tag is allowed!");
+        this.connectors = connectors;
+    }
+
+
+    /**
+     * @param services
+     */
+    public void addLoginServices(LoginServices services)
+    {        
+        if (this.loginServices != null )
+            throw new BuildException("Only one <loginServices> tag is allowed!");       
+        this.loginServices = services;  
+    }
+
+    public void addSystemProperties(SystemProperties systemProperties)
+    {
+        if (this.systemProperties != null)
+            throw new BuildException("Only one <systemProperties> tag is allowed!");
+        this.systemProperties = systemProperties;
+    }
+    
+    /**
+     * @param handlers
+     */
+    public void addContextHandlers (ContextHandlers handlers)
+    {
+        if (this.contextHandlers != null)
+            throw new BuildException("Only one <contextHandlers> tag is allowed!");
+        this.contextHandlers = handlers;
+    }
+
+    public File getTempDirectory()
+    {
+        return tempDirectory;
+    }
+
+    /**
+     * @param tempDirectory
+     */
+    public void setTempDirectory(File tempDirectory)
+    {
+        this.tempDirectory = tempDirectory;
+    }
+
+    public File getJettyXml()
+    {
+        return jettyXml;
+    }
+
+    /**
+     * @param jettyXml
+     */
+    public void setJettyXml(File jettyXml)
+    {
+        this.jettyXml = jettyXml;
+    }
+
+    /**
+     * @param className
+     */
+    public void setRequestLog(String className)
+    {
+        try
+        {
+            this.requestLog = (RequestLog) Class.forName(className).newInstance();
+        }
+        catch (InstantiationException e)
+        {
+            throw new BuildException("Request logger instantiation exception: " + e);
+        }
+        catch (IllegalAccessException e)
+        {
+            throw new BuildException("Request logger instantiation exception: " + e);
+        }
+        catch (ClassNotFoundException e)
+        {
+            throw new BuildException("Unknown request logger class: " + className);
+        }
+    }
+
+    public String getRequestLog()
+    {
+        if (requestLog != null)
+        {
+            return requestLog.getClass().getName();
+        }
+
+        return "";
+    }
+
+    /**
+     * Sets the port Jetty uses for the default connector.
+     * 
+     * @param jettyPort The port Jetty will use for the default connector
+     */
+    public void setJettyPort(final int jettyPort)
+    {
+        this.jettyPort = jettyPort;
+    }
+
+    /**
+     * Executes this Ant task. The build flow is being stopped until Jetty
+     * server stops.
+     *
+     * @throws BuildException
+     */
+    public void execute() throws BuildException
+    {
+
+        TaskLog.log("Configuring Jetty for project: " + getProject().getName());
+        
+        setSystemProperties();
+
+        List<Connector> connectorsList = null;
+
+        if (connectors != null)
+            connectorsList = connectors.getConnectors();
+        else
+            connectorsList = new Connectors(jettyPort,30000).getDefaultConnectors();
+
+        List<LoginService> loginServicesList = (loginServices != null?loginServices.getLoginServices():new ArrayList<LoginService>());
+        ServerProxyImpl server = new ServerProxyImpl();
+        server.setConnectors(connectorsList);
+        server.setLoginServices(loginServicesList);
+        server.setRequestLog(requestLog);
+        server.setJettyXml(jettyXml);
+        server.setDaemon(daemon);
+        server.setStopPort(stopPort);
+        server.setStopKey(stopKey);
+        server.setContextHandlers(contextHandlers);
+        server.setTempDirectory(tempDirectory);
+        server.setScanIntervalSecs(scanIntervalSeconds);
+
+        try
+        {
+            for (WebAppContext webapp: webapps)
+            {
+                server.addWebApplication((AntWebAppContext)webapp);
+            }
+        }
+        catch (Exception e)
+        {
+            throw new BuildException(e);
+        }
+
+        server.start();
+    }
+
+    public int getStopPort()
+    {
+        return stopPort;
+    }
+
+    public void setStopPort(int stopPort)
+    {
+        this.stopPort = stopPort;
+        TaskLog.log("stopPort="+stopPort);
+    }
+
+    public String getStopKey()
+    {
+        return stopKey;
+    }
+
+    public void setStopKey(String stopKey)
+    {
+        this.stopKey = stopKey;
+        TaskLog.log("stopKey="+stopKey);
+    }
+
+    /**
+     * @return the daemon
+     */
+    public boolean isDaemon()
+    {
+        return daemon;
+    }
+
+    /**
+     * @param daemon the daemon to set
+     */
+    public void setDaemon(boolean daemon)
+    {
+        this.daemon = daemon;
+        TaskLog.log("Daemon="+daemon);
+    }
+
+    public int getScanIntervalSeconds()
+    {
+        return scanIntervalSeconds;
+    }
+
+    /**
+     * @param secs
+     */
+    public void setScanIntervalSeconds(int secs)
+    {
+        scanIntervalSeconds = secs;
+        TaskLog.log("scanIntervalSecs="+secs);
+    }
+    
+
+    
+    /**
+     * Sets the system properties.
+     */
+    private void setSystemProperties()
+    {
+        if (systemProperties != null)
+        {
+            Iterator propertiesIterator = systemProperties.getSystemProperties().iterator();
+            while (propertiesIterator.hasNext())
+            {
+                Property property = ((Property) propertiesIterator.next());
+                SystemProperties.setIfNotSetAlready(property);
+            }
+        }
+    }
+
+}
diff --git a/jetty-ant/src/main/java/org/eclipse/jetty/ant/JettyStopTask.java b/jetty-ant/src/main/java/org/eclipse/jetty/ant/JettyStopTask.java
new file mode 100644
index 0000000..36026a3
--- /dev/null
+++ b/jetty-ant/src/main/java/org/eclipse/jetty/ant/JettyStopTask.java
@@ -0,0 +1,127 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.ant;
+
+import java.io.InputStreamReader;
+import java.io.LineNumberReader;
+import java.io.OutputStream;
+import java.net.ConnectException;
+import java.net.InetAddress;
+import java.net.Socket;
+
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Task;
+import org.eclipse.jetty.ant.utils.TaskLog;
+
+/**
+ * JettyStopTask
+ *
+ *
+ */
+public class JettyStopTask extends Task
+{
+
+    private int stopPort;
+    
+    private String stopKey;
+    
+    private int stopWait;
+    
+    
+    
+    /**
+     * 
+     */
+    public JettyStopTask()
+    {
+        TaskLog.setTask(this);
+    }
+
+    /** 
+     * @see org.apache.tools.ant.Task#execute()
+     */
+    public void execute() throws BuildException
+    {
+        try
+        {        
+            Socket s = new Socket(InetAddress.getByName("127.0.0.1"),stopPort);
+            if (stopWait > 0)
+                s.setSoTimeout(stopWait*1000);
+            try
+            {
+                OutputStream out = s.getOutputStream();
+                out.write((stopKey + "\r\nstop\r\n").getBytes());
+                out.flush();
+
+                if (stopWait > 0)
+                {
+                    TaskLog.log("Waiting"+(stopWait > 0 ? (" "+stopWait+"sec") : "")+" for jetty to stop");
+                    LineNumberReader lin = new LineNumberReader(new InputStreamReader(s.getInputStream()));
+                    String response=lin.readLine();
+                    if ("Stopped".equals(response))
+                        System.err.println("Stopped");
+                }
+            }
+            finally
+            {
+                s.close();
+            }  
+        }
+        catch (ConnectException e)
+        {
+            TaskLog.log("Jetty not running!");
+        }
+        catch (Exception e)
+        {
+            TaskLog.log(e.getMessage());
+        }
+    }
+
+    public int getStopPort() 
+    {
+        return stopPort;
+    }
+
+    public void setStopPort(int stopPort) 
+    {
+        this.stopPort = stopPort;
+    }
+
+    public String getStopKey() 
+    {
+        return stopKey;
+    }
+
+    public void setStopKey(String stopKey) 
+    {
+        this.stopKey = stopKey;
+    }
+
+    public int getStopWait()
+    {
+        return stopWait;
+    }
+
+    public void setStopWait(int stopWait)
+    {
+        this.stopWait = stopWait;
+    }
+    
+    
+}
diff --git a/jetty-ant/src/main/java/org/eclipse/jetty/ant/ServerProxyImpl.java b/jetty-ant/src/main/java/org/eclipse/jetty/ant/ServerProxyImpl.java
new file mode 100644
index 0000000..0e03150
--- /dev/null
+++ b/jetty-ant/src/main/java/org/eclipse/jetty/ant/ServerProxyImpl.java
@@ -0,0 +1,514 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2012 Sabre Holdings.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.ant;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.eclipse.jetty.ant.types.Connector;
+import org.eclipse.jetty.ant.types.ContextHandlers;
+import org.eclipse.jetty.ant.utils.ServerProxy;
+import org.eclipse.jetty.ant.utils.TaskLog;
+import org.eclipse.jetty.security.LoginService;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.RequestLog;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.ShutdownMonitor;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.ContextHandlerCollection;
+import org.eclipse.jetty.server.handler.DefaultHandler;
+import org.eclipse.jetty.server.handler.HandlerCollection;
+import org.eclipse.jetty.server.handler.RequestLogHandler;
+import org.eclipse.jetty.util.Scanner;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.xml.XmlConfiguration;
+import org.xml.sax.SAXException;
+
+
+
+/**
+ * A proxy class for interaction with Jetty server object. Used to have some
+ * level of abstraction over standard Jetty classes.
+ */
+public class ServerProxyImpl implements ServerProxy
+{
+
+    /** Proxied Jetty server object. */
+    private Server server;
+    
+    /** Temporary files directory. */
+    private File tempDirectory;
+    
+    /** Collection of context handlers (web application contexts). */
+    private ContextHandlerCollection contexts;
+
+    /** Location of jetty.xml file. */
+    private File jettyXml;
+
+    /** List of connectors. */
+    private List<Connector> connectors;
+
+    /** Request logger. */
+    private RequestLog requestLog;
+
+    /** User realms. */
+    private List<LoginService> loginServices;
+
+    /** List of added web applications. */
+    private List<AntWebAppContext> webApplications = new ArrayList<AntWebAppContext>();
+
+    /** other contexts to deploy */
+    private ContextHandlers contextHandlers;
+
+    /** scan interval for changed files */
+    private int scanIntervalSecs;
+
+    /** port to listen for stop command */
+    private int stopPort;
+
+    /** security key for stop command */
+    private String stopKey;
+
+    /** wait for all jetty threads to exit or continue */
+    private boolean daemon;
+
+
+    private boolean configured = false;
+
+
+    
+    /**
+     * WebAppScannerListener
+     *
+     * Handle notifications that files we are interested in have changed
+     * during execution.
+     * 
+     */
+    public static class WebAppScannerListener implements Scanner.BulkListener
+    {     
+        AntWebAppContext awc;
+
+        public WebAppScannerListener (AntWebAppContext awc)
+        {
+            this.awc = awc;
+        }
+
+        public void filesChanged(List<String> changedFileNames)
+        {
+            boolean isScanned = false;
+            try
+            {
+                Iterator<String> itor = changedFileNames.iterator();
+                while (!isScanned && itor.hasNext())
+                {
+                    isScanned = awc.isScanned(Resource.newResource(itor.next()).getFile());
+                }
+                if (isScanned)
+                {
+                    awc.stop();
+                    awc.start();
+                }
+            }
+            catch (Exception e)
+            {
+                TaskLog.log(e.getMessage());
+            }
+        }
+
+    }
+
+
+    /**
+     * Default constructor. Creates a new Jetty server with a standard connector
+     * listening on a given port.
+     */
+    public ServerProxyImpl ()
+    {
+        server = new Server();
+        server.setStopAtShutdown(true);
+    }
+
+   
+    public void addWebApplication(AntWebAppContext webApp)
+    {
+       webApplications.add(webApp);
+    }
+
+    public int getStopPort()
+    {
+        return stopPort;
+    }
+
+    public void setStopPort(int stopPort)
+    {
+        this.stopPort = stopPort;
+    }
+
+    public String getStopKey()
+    {
+        return stopKey;
+    }
+
+    public void setStopKey(String stopKey)
+    {
+        this.stopKey = stopKey;
+    }
+
+    public File getJettyXml()
+    {
+        return jettyXml;
+    }
+
+    public void setJettyXml(File jettyXml)
+    {
+        this.jettyXml = jettyXml;
+    }
+
+    public List<Connector> getConnectors()
+    {
+        return connectors;
+    }
+
+    public void setConnectors(List<Connector> connectors)
+    {
+        this.connectors = connectors;
+    }
+
+    public RequestLog getRequestLog()
+    {
+        return requestLog;
+    }
+
+    public void setRequestLog(RequestLog requestLog)
+    {
+        this.requestLog = requestLog;
+    }
+
+    public List<LoginService> getLoginServices()
+    {
+        return loginServices;
+    }
+
+    public void setLoginServices(List<LoginService> loginServices)
+    {
+        this.loginServices = loginServices;
+    }
+
+    public List<AntWebAppContext> getWebApplications()
+    {
+        return webApplications;
+    }
+
+    public void setWebApplications(List<AntWebAppContext> webApplications)
+    {
+        this.webApplications = webApplications;
+    }
+
+    
+    public File getTempDirectory()
+    {
+        return tempDirectory;
+    }
+
+
+    public void setTempDirectory(File tempDirectory)
+    {
+        this.tempDirectory = tempDirectory;
+    }
+
+
+    /**
+     * @see org.eclipse.jetty.ant.utils.ServerProxy#start()
+     */
+    public void start()
+    {
+        try
+        {
+            configure();
+            
+            configureWebApps();
+            
+            server.start();
+         
+            System.setProperty("jetty.ant.server.port","" + ((ServerConnector)server.getConnectors()[0]).getLocalPort());
+            
+            String host = ((ServerConnector)server.getConnectors()[0]).getHost();
+            
+            if (host == null)
+            {
+                System.setProperty("jetty.ant.server.host", "localhost");
+            }
+            else
+            {
+                System.setProperty("jetty.ant.server.host", host);
+            }
+            
+            startScanners();
+            
+            TaskLog.log("Jetty AntTask Started");
+
+            if (!daemon)
+                server.join();
+        }
+        catch (InterruptedException e)
+        {
+            new RuntimeException(e);
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+            new RuntimeException(e);
+        }
+    }
+
+
+
+  
+    /**
+     * @see org.eclipse.jetty.ant.utils.ServerProxy#getProxiedObject()
+     */
+    public Object getProxiedObject()
+    {
+        return server;
+    }
+
+
+    /**
+     * @return the daemon
+     */
+    public boolean isDaemon()
+    {
+        return daemon;
+    }
+
+
+    /**
+     * @param daemon the daemon to set
+     */
+    public void setDaemon(boolean daemon)
+    {       
+        this.daemon = daemon;
+    }
+
+
+    /**
+     * @return the contextHandlers
+     */
+    public ContextHandlers getContextHandlers()
+    {
+        return contextHandlers;
+    }
+
+
+    /**
+     * @param contextHandlers the contextHandlers to set
+     */
+    public void setContextHandlers (ContextHandlers contextHandlers)
+    {
+        this.contextHandlers = contextHandlers;
+    }
+
+
+    public int getScanIntervalSecs()
+    {
+        return scanIntervalSecs;
+    }
+
+
+    public void setScanIntervalSecs(int scanIntervalSecs)
+    {
+        this.scanIntervalSecs = scanIntervalSecs;
+    }
+    
+
+    /**
+     * Configures Jetty server before adding any web applications to it.
+     */
+    private void configure()
+    {
+        if (configured)
+            return;
+        
+        configured = true;
+
+        if(stopPort>0 && stopKey!=null)
+        {
+            ShutdownMonitor monitor = ShutdownMonitor.getInstance();
+            monitor.setPort(stopPort);
+            monitor.setKey(stopKey);
+            monitor.setExitVm(false);
+        }
+        
+        if (tempDirectory != null && !tempDirectory.exists())
+            tempDirectory.mkdirs();
+        
+        // Applies external configuration via jetty.xml
+        applyJettyXml();
+
+        // Configures connectors for this server instance.
+        if (connectors != null)
+        {
+            for (Connector c:connectors)
+            {
+                ServerConnector jc = new ServerConnector(server);
+
+                jc.setPort(c.getPort());
+                jc.setIdleTimeout(c.getMaxIdleTime());
+
+                server.addConnector(jc);
+            }
+        }
+
+        // Configures login services
+        if (loginServices != null)
+        {
+            for (LoginService ls:loginServices)
+            {
+                server.addBean(ls);
+            }
+        }
+
+        // Does not cache resources, to prevent Windows from locking files
+        Resource.setDefaultUseCaches(false);
+
+        // Set default server handlers
+        configureHandlers();
+    }
+    
+    
+    /**
+     * 
+     */
+    private void configureHandlers()
+    {
+        RequestLogHandler requestLogHandler = new RequestLogHandler();
+        if (requestLog != null)
+            requestLogHandler.setRequestLog(requestLog);
+
+        contexts = (ContextHandlerCollection) server
+                .getChildHandlerByClass(ContextHandlerCollection.class);
+        if (contexts == null)
+        {
+            contexts = new ContextHandlerCollection();
+            HandlerCollection handlers = (HandlerCollection) server
+                    .getChildHandlerByClass(HandlerCollection.class);
+            if (handlers == null)
+            {
+                handlers = new HandlerCollection();
+                server.setHandler(handlers);
+                handlers.setHandlers(new Handler[] { contexts, new DefaultHandler(),
+                        requestLogHandler });
+            }
+            else
+            {
+                handlers.addHandler(contexts);
+            }
+        }
+        
+        //if there are any extra contexts to deploy
+        if (contextHandlers != null && contextHandlers.getContextHandlers() != null)
+        {
+            for (ContextHandler c:contextHandlers.getContextHandlers())
+                contexts.addHandler(c);
+        }
+    }
+
+
+
+    
+    /**
+     * Applies jetty.xml configuration to the Jetty server instance.
+     */
+    private void applyJettyXml()
+    {
+        if (jettyXml != null && jettyXml.exists())
+        {
+            TaskLog.log("Configuring jetty from xml configuration file = "
+                    + jettyXml.getAbsolutePath());
+            XmlConfiguration configuration;
+            try
+            {
+                configuration = new XmlConfiguration(Resource.toURL(jettyXml));
+                configuration.configure(server);
+            }
+            catch (MalformedURLException e)
+            {
+                throw new RuntimeException(e);
+            }
+            catch (SAXException e)
+            {
+                throw new RuntimeException(e);
+            }
+            catch (IOException e)
+            {
+                throw new RuntimeException(e);
+            }
+            catch (Exception e)
+            {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
+    
+    /**
+     * Starts web applications' scanners.
+     */
+    private void startScanners() throws Exception
+    {
+        for (AntWebAppContext awc:webApplications)
+        {
+            if (scanIntervalSecs <= 0)
+                return;
+
+            List<File> scanList = awc.getScanFiles();
+ 
+            TaskLog.log("Web application '" + awc + "': starting scanner at interval of "
+                    + scanIntervalSecs + " seconds.");
+            Scanner.Listener changeListener = new WebAppScannerListener(awc);
+            Scanner scanner = new Scanner();
+            scanner.setScanInterval(scanIntervalSecs);
+            scanner.addListener(changeListener);
+            scanner.setScanDirs(scanList);
+            scanner.setReportExistingFilesOnStartup(false);
+            scanner.start();
+        }  
+    }
+    
+    
+    /**
+     * 
+     */
+    private void configureWebApps()
+    {
+        for (AntWebAppContext awc:webApplications)
+        {
+            awc.setAttribute(AntWebAppContext.BASETEMPDIR, tempDirectory);
+            if (contexts != null)
+                contexts.addHandler(awc);
+        }
+    }
+    
+}
diff --git a/jetty-ant/src/main/java/org/eclipse/jetty/ant/package-info.java b/jetty-ant/src/main/java/org/eclipse/jetty/ant/package-info.java
new file mode 100644
index 0000000..8400308
--- /dev/null
+++ b/jetty-ant/src/main/java/org/eclipse/jetty/ant/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Ant : Ant Tasks and Configuration
+ */
+package org.eclipse.jetty.ant;
+
diff --git a/jetty-ant/src/main/java/org/eclipse/jetty/ant/types/Attribute.java b/jetty-ant/src/main/java/org/eclipse/jetty/ant/types/Attribute.java
new file mode 100644
index 0000000..1471e10
--- /dev/null
+++ b/jetty-ant/src/main/java/org/eclipse/jetty/ant/types/Attribute.java
@@ -0,0 +1,47 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.ant.types;
+
+public class Attribute
+{
+
+    String name;
+    
+    String value;
+    
+    public void setName(String name)
+    {
+        this.name = name;
+    }
+    
+    public void setValue(String value)
+    {
+        this.value = value;
+    }
+    
+    public String getName()
+    {
+        return name;
+    }
+    
+    public String getValue()
+    {
+        return value;
+    }
+}
diff --git a/jetty-ant/src/main/java/org/eclipse/jetty/ant/types/Attributes.java b/jetty-ant/src/main/java/org/eclipse/jetty/ant/types/Attributes.java
new file mode 100644
index 0000000..6e9a2fb
--- /dev/null
+++ b/jetty-ant/src/main/java/org/eclipse/jetty/ant/types/Attributes.java
@@ -0,0 +1,38 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.ant.types;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class Attributes
+{
+
+    List<Attribute> _attributes = new ArrayList<Attribute>();
+    
+    public void addAttribute(Attribute attr )
+    {
+        _attributes.add(attr);
+    }
+    
+    public List<Attribute> getAttributes()
+    {
+        return _attributes;
+    }
+}
diff --git a/jetty-ant/src/main/java/org/eclipse/jetty/ant/types/Connector.java b/jetty-ant/src/main/java/org/eclipse/jetty/ant/types/Connector.java
new file mode 100644
index 0000000..81d69c4
--- /dev/null
+++ b/jetty-ant/src/main/java/org/eclipse/jetty/ant/types/Connector.java
@@ -0,0 +1,62 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.ant.types;
+
+/**
+ * Connector
+ *
+ *
+ */
+public class Connector
+{
+    private int port;
+    private int maxIdleTime;
+
+    public Connector()
+    {
+
+    }
+
+    public Connector(int port, int maxIdleTime)
+    {
+        this.port = port;
+        this.maxIdleTime = maxIdleTime;
+    }
+
+    public int getPort()
+    {
+        return port;
+    }
+
+    public void setPort(int port)
+    {
+        this.port = port;
+    }
+
+    public int getMaxIdleTime()
+    {
+        return maxIdleTime;
+    }
+
+    public void setMaxIdleTime(int maxIdleTime)
+    {
+        this.maxIdleTime = maxIdleTime;
+    }
+
+}
diff --git a/jetty-ant/src/main/java/org/eclipse/jetty/ant/types/Connectors.java b/jetty-ant/src/main/java/org/eclipse/jetty/ant/types/Connectors.java
new file mode 100644
index 0000000..9c425e1
--- /dev/null
+++ b/jetty-ant/src/main/java/org/eclipse/jetty/ant/types/Connectors.java
@@ -0,0 +1,86 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2012 Sabre Holdings.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.ant.types;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 
+ * Connectors
+ * 
+ * Specifies a jetty configuration <connectors/> element for Ant build file.
+ *
+ */
+public class Connectors
+{
+    private List<Connector> connectors = new ArrayList<Connector>();
+    private List<Connector> defaultConnectors = new ArrayList<Connector>();
+
+    /**
+     * Default constructor.
+     */
+    public Connectors() {
+        this(8080, 30000);
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param port The port that the default connector will listen on
+     * @param maxIdleTime The maximum idle time for the default connector
+     */
+    public Connectors(int port, int maxIdleTime)
+    {
+        defaultConnectors.add(new Connector(port, maxIdleTime));
+    }
+
+    /**
+     * Adds a connector to the list of connectors to deploy.
+     *
+     * @param connector A connector to add to the list
+     */
+    public void add(Connector connector)
+    {
+        connectors.add(connector);
+    }
+
+    /**
+     * Returns the list of known connectors to deploy.
+     *
+     * @return The list of known connectors
+     */
+    public List<Connector> getConnectors()
+    {
+        return connectors;
+    }
+
+    /**
+     * Gets the default list of connectors to deploy when no connectors
+     * were explicitly added to the list.
+     *
+     * @return The list of default connectors
+     */
+    public List<Connector> getDefaultConnectors()
+    {
+        return defaultConnectors;
+    }
+
+}
diff --git a/jetty-ant/src/main/java/org/eclipse/jetty/ant/types/ContextHandlers.java b/jetty-ant/src/main/java/org/eclipse/jetty/ant/types/ContextHandlers.java
new file mode 100644
index 0000000..bfbf49d
--- /dev/null
+++ b/jetty-ant/src/main/java/org/eclipse/jetty/ant/types/ContextHandlers.java
@@ -0,0 +1,45 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2012 Sabre Holdings.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.ant.types;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jetty.server.handler.ContextHandler;
+
+/**
+ * Specifies <contextHandlers/> element in web app configuration.
+ * 
+ */
+public class ContextHandlers
+{
+
+    private List<ContextHandler> contextHandlers = new ArrayList<ContextHandler>();
+
+    public void add(ContextHandler handler)
+    {
+        contextHandlers.add(handler);
+    }
+
+    public List<ContextHandler> getContextHandlers()
+    {
+        return contextHandlers;
+    }
+}
diff --git a/jetty-ant/src/main/java/org/eclipse/jetty/ant/types/FileMatchingConfiguration.java b/jetty-ant/src/main/java/org/eclipse/jetty/ant/types/FileMatchingConfiguration.java
new file mode 100644
index 0000000..5576780
--- /dev/null
+++ b/jetty-ant/src/main/java/org/eclipse/jetty/ant/types/FileMatchingConfiguration.java
@@ -0,0 +1,98 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2012 Sabre Holdings.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.ant.types;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.tools.ant.DirectoryScanner;
+
+/**
+ * Describes set of files matched by <fileset/> elements in ant configuration
+ * file. It is used to group application classes, libraries, and scannedTargets
+ * elements.
+ * 
+ */
+public class FileMatchingConfiguration
+{
+
+    private List directoryScanners;
+
+    public FileMatchingConfiguration()
+    {
+        this.directoryScanners = new ArrayList();
+    }
+
+    /**
+     * @param directoryScanner new directory scanner retrieved from the
+     *            <fileset/> element.
+     */
+    public void addDirectoryScanner(DirectoryScanner directoryScanner)
+    {
+        this.directoryScanners.add(directoryScanner);
+    }
+
+    /**
+     * @return a list of base directories denoted by a list of directory
+     *         scanners.
+     */
+    public List getBaseDirectories()
+    {
+        List baseDirs = new ArrayList();
+        Iterator scanners = directoryScanners.iterator();
+        while (scanners.hasNext())
+        {
+            DirectoryScanner scanner = (DirectoryScanner) scanners.next();
+            baseDirs.add(scanner.getBasedir());
+        }
+
+        return baseDirs;
+    }
+
+    /**
+     * Checks if passed file is scanned by any of the directory scanners.
+     * 
+     * @param pathToFile a fully qualified path to tested file.
+     * @return true if so, false otherwise.
+     */
+    public boolean isIncluded(String pathToFile)
+    {
+        Iterator scanners = directoryScanners.iterator();
+        while (scanners.hasNext())
+        {
+            DirectoryScanner scanner = (DirectoryScanner) scanners.next();
+            scanner.scan();
+            String[] includedFiles = scanner.getIncludedFiles();
+
+            for (int i = 0; i < includedFiles.length; i++)
+            {
+                File includedFile = new File(scanner.getBasedir(), includedFiles[i]);
+                if (pathToFile.equalsIgnoreCase(includedFile.getAbsolutePath()))
+                {
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+}
diff --git a/jetty-ant/src/main/java/org/eclipse/jetty/ant/types/LoginServices.java b/jetty-ant/src/main/java/org/eclipse/jetty/ant/types/LoginServices.java
new file mode 100644
index 0000000..f83a1d7
--- /dev/null
+++ b/jetty-ant/src/main/java/org/eclipse/jetty/ant/types/LoginServices.java
@@ -0,0 +1,47 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2012 Sabre Holdings.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.ant.types;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jetty.security.LoginService;
+
+/**
+ * LoginServices
+ * 
+ * Specifies a jetty configuration <loginServices/> element for Ant build file.
+ *
+ */
+public class LoginServices
+{
+
+    private List<LoginService> loginServices = new ArrayList<LoginService>();
+
+    public void add(LoginService service)
+    {
+        loginServices.add(service);
+    }
+
+    public List<LoginService> getLoginServices()
+    {
+        return loginServices;
+    }
+}
diff --git a/jetty-ant/src/main/java/org/eclipse/jetty/ant/types/SystemProperties.java b/jetty-ant/src/main/java/org/eclipse/jetty/ant/types/SystemProperties.java
new file mode 100644
index 0000000..72bed64
--- /dev/null
+++ b/jetty-ant/src/main/java/org/eclipse/jetty/ant/types/SystemProperties.java
@@ -0,0 +1,67 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2012 Sabre Holdings.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.ant.types;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.tools.ant.taskdefs.Property;
+import org.eclipse.jetty.ant.utils.TaskLog;
+
+/**
+ * SystemProperties
+ * 
+ * Ant <systemProperties/> tag definition.
+ * 
+ */
+public class SystemProperties
+{
+
+    private List systemProperties = new ArrayList();
+
+    public List getSystemProperties()
+    {
+        return systemProperties;
+    }
+
+    public void addSystemProperty(Property property)
+    {
+        systemProperties.add(property);
+    }
+
+    /**
+     * Set a System.property with this value if it is not already set.
+     * 
+     * @returns true if property has been set
+     */
+    public static boolean setIfNotSetAlready(Property property)
+    {
+        if (System.getProperty(property.getName()) == null)
+        {
+            System.setProperty(property.getName(), (property.getValue() == null ? "" : property
+                    .getValue()));
+            TaskLog.log("Setting property '" + property.getName() + "' to value '"
+                    + property.getValue() + "'");
+            return true;
+        }
+
+        return false;
+    }
+}
diff --git a/jetty-ant/src/main/java/org/eclipse/jetty/ant/types/package-info.java b/jetty-ant/src/main/java/org/eclipse/jetty/ant/types/package-info.java
new file mode 100644
index 0000000..6074266
--- /dev/null
+++ b/jetty-ant/src/main/java/org/eclipse/jetty/ant/types/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Ant : Ant Wrappers of Jetty Internals
+ */
+package org.eclipse.jetty.ant.types;
+
diff --git a/jetty-ant/src/main/java/org/eclipse/jetty/ant/utils/ServerProxy.java b/jetty-ant/src/main/java/org/eclipse/jetty/ant/utils/ServerProxy.java
new file mode 100644
index 0000000..432778e
--- /dev/null
+++ b/jetty-ant/src/main/java/org/eclipse/jetty/ant/utils/ServerProxy.java
@@ -0,0 +1,42 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2012 Sabre Holdings.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.ant.utils;
+
+import org.eclipse.jetty.ant.AntWebAppContext;
+
+public interface ServerProxy
+{
+
+    /**
+     * Adds a new web application to this server.
+     * 
+     * @param awc a AntWebAppContext object.
+     */
+    public void addWebApplication(AntWebAppContext awc);
+
+    /**
+     * Starts this server.
+     */
+    public void start();
+    
+    
+    public Object getProxiedObject();
+
+}
diff --git a/jetty-ant/src/main/java/org/eclipse/jetty/ant/utils/TaskLog.java b/jetty-ant/src/main/java/org/eclipse/jetty/ant/utils/TaskLog.java
new file mode 100644
index 0000000..e9aad1d
--- /dev/null
+++ b/jetty-ant/src/main/java/org/eclipse/jetty/ant/utils/TaskLog.java
@@ -0,0 +1,58 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2012 Sabre Holdings.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.ant.utils;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import org.apache.tools.ant.Task;
+
+/**
+ * Provides logging functionality for classes without access to the Ant project
+ * variable.
+ * 
+ */
+public class TaskLog
+{
+
+    private static Task task;
+
+    private static final SimpleDateFormat format = new SimpleDateFormat(
+            "yyyy-MM-dd HH:mm:ss.SSS");
+
+    public static void setTask(Task task)
+    {
+        TaskLog.task = task;
+    }
+
+    public static void log(String message)
+    {
+        task.log(message);
+    }
+
+    public static void logWithTimestamp(String message)
+    {
+        String date;
+        synchronized (format)
+        {
+            date = format.format(new Date());
+        }
+        task.log(date + ": " + message);
+    }
+}
diff --git a/jetty-ant/src/main/java/org/eclipse/jetty/ant/utils/package-info.java b/jetty-ant/src/main/java/org/eclipse/jetty/ant/utils/package-info.java
new file mode 100644
index 0000000..b9cc7a3
--- /dev/null
+++ b/jetty-ant/src/main/java/org/eclipse/jetty/ant/utils/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Ant : Utility Classes
+ */
+package org.eclipse.jetty.ant.utils;
+
diff --git a/jetty-ant/src/main/resources/tasks.properties b/jetty-ant/src/main/resources/tasks.properties
new file mode 100644
index 0000000..0bfeba2
--- /dev/null
+++ b/jetty-ant/src/main/resources/tasks.properties
@@ -0,0 +1,2 @@
+jetty.run=org.eclipse.jetty.ant.JettyRunTask
+jetty.stop=org.eclipse.jetty.ant.JettyStopTask
\ No newline at end of file
diff --git a/jetty-ant/src/test/config/build.xml b/jetty-ant/src/test/config/build.xml
new file mode 100644
index 0000000..9ca5fed
--- /dev/null
+++ b/jetty-ant/src/test/config/build.xml
@@ -0,0 +1,42 @@
+<!-- =======================================================================================-->
+<!-- Build file for running the test-jetty-webapp from the jetty distro.                    -->
+<!--                                                                                        -->
+<!-- You will need to have a full jetty-distribution available unpacked on your local file  -->
+<!-- system. We will refer to the top level directory of this distribution as $JETTY_HOME.  -->
+<!--                                                                                        -->
+<!-- To use:                                                                                -->
+<!-- * mkdir test-jetty-ant                                                                 -->
+<!-- * cp this file to test-jetty-ant                                                       -->
+<!-- * cd test-jetty-ant; mkdir jetty-lib;  mkdir jetty-temp                                -->
+<!-- * copy all jars from $JETTY_HOME/lib and all subdirs flatly into ./jetty-lib           -->
+<!-- * copy the jetty-ant jar into ./jetty-lib                                              -->
+<!-- * copy the test.war from $JETTY_HOME/webapps.demo dir                                  -->
+<!--                                                                                        -->
+<!-- To run:                                                                                -->
+<!--     ant jetty.run                                                                      -->
+<!-- =======================================================================================-->
+<project name="Jetty-Ant integration test" basedir=".">
+  <path id="jetty.plugin.classpath">
+    <fileset dir="jetty-lib" includes="*.jar"/>
+  </path>
+  <taskdef classpathref="jetty.plugin.classpath" resource="tasks.properties" loaderref="jetty.loader" />
+
+  <typedef name="hashLoginService" classname="org.eclipse.jetty.security.HashLoginService"
+           classpathref="jetty.plugin.classpath" loaderref="jetty.loader" />
+ 
+  <target name="jetty.run">
+    <jetty.run tempDirectory="jetty-temp">
+      <loginServices>
+        <hashLoginService name="Test Realm" config="realm.properties"/>
+      </loginServices>
+      <webApp 
+              war="test.war" 
+              contextpath="/test" >
+        <attributes>
+          <attribute name="org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern" value=".*/servlet-api-[^/]*\.jar$"/>
+
+        </attributes>
+      </webApp>
+    </jetty.run>
+  </target>
+</project>
diff --git a/jetty-ant/src/test/java/org/eclipse/jetty/ant/AntBuild.java b/jetty-ant/src/test/java/org/eclipse/jetty/ant/AntBuild.java
new file mode 100644
index 0000000..792f639
--- /dev/null
+++ b/jetty-ant/src/test/java/org/eclipse/jetty/ant/AntBuild.java
@@ -0,0 +1,294 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.ant;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.tools.ant.DefaultLogger;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.ProjectHelper;
+import org.eclipse.jetty.toolchain.test.IO;
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+
+public class AntBuild
+{
+    private Thread _process;
+    private String _ant;
+
+    private int _port;
+    private String _host;
+    
+    public AntBuild(String ant)
+    {
+        _ant = ant;
+    }
+
+    private class AntBuildProcess implements Runnable
+    {
+        List<String[]> connList;
+        
+        @Override
+        public void run()
+        {
+            File buildFile = new File(_ant);
+            
+            Project antProject = new Project();
+            try
+            {
+                antProject.setBaseDir(MavenTestingUtils.getBasedir());
+                antProject.setUserProperty("ant.file",buildFile.getAbsolutePath());
+                DefaultLogger logger = new DefaultLogger();
+
+                ConsoleParser parser = new ConsoleParser();
+                //connList = parser.newPattern(".*([0-9]+\\.[0-9]*\\.[0-9]*\\.[0-9]*):([0-9]*)",1);
+                connList = parser.newPattern("Jetty AntTask Started",1);
+
+                PipedOutputStream pos = new PipedOutputStream();
+                PipedInputStream pis = new PipedInputStream(pos);
+
+                PipedOutputStream pose = new PipedOutputStream();
+                PipedInputStream pise = new PipedInputStream(pose);
+
+                startPump("STDOUT",parser,pis);
+                startPump("STDERR",parser,pise);
+                
+                logger.setErrorPrintStream(new PrintStream(pos));
+                logger.setOutputPrintStream(new PrintStream(pose));
+                logger.setMessageOutputLevel(Project.MSG_VERBOSE);
+                antProject.addBuildListener(logger);
+
+                antProject.fireBuildStarted();
+                antProject.init();
+
+                ProjectHelper helper = ProjectHelper.getProjectHelper();
+
+                antProject.addReference("ant.projectHelper",helper);
+
+                helper.parse(antProject,buildFile);
+
+                antProject.executeTarget("jetty.run");
+            
+                parser.waitForDone(10000,TimeUnit.MILLISECONDS);
+            }
+            catch (Exception e)
+            {
+                antProject.fireBuildFinished(e);
+            }
+        }
+        
+        
+        public void waitForStarted() throws Exception
+        {
+            while (connList == null || connList.isEmpty())
+            {
+                Thread.sleep(10);
+            }
+        }
+    }
+
+    public void start() throws Exception
+    {
+        System.out.println("Starting Ant Build ...");
+        AntBuildProcess abp = new AntBuildProcess();
+        _process = new Thread(abp);
+
+        _process.start();
+        
+        abp.waitForStarted();
+        
+        // once this has returned we should have the connection info we need
+        //_host = abp.getConnectionList().get(0)[0];
+        //_port = Integer.parseInt(abp.getConnectionList().get(0)[1]);
+        
+    }
+    
+    public int getJettyPort()
+    {
+        return Integer.parseInt(System.getProperty("jetty.ant.server.port"));
+    }
+    
+    public String getJettyHost()
+    {
+        return System.getProperty("jetty.ant.server.host");
+    }
+    
+    
+    /**
+     * Stop the jetty server
+     */
+    public void stop()
+    {
+        System.out.println("Stopping Ant Build ...");
+        _process.interrupt();
+    }
+
+    private static class ConsoleParser
+    {
+        private List<ConsolePattern> patterns = new ArrayList<ConsolePattern>();
+        private CountDownLatch latch;
+        private int count;
+
+        public List<String[]> newPattern(String exp, int cnt)
+        {
+            ConsolePattern pat = new ConsolePattern(exp,cnt);
+            patterns.add(pat);
+            count += cnt;
+
+            return pat.getMatches();
+        }
+
+        public void parse(String line)
+        {
+            for (ConsolePattern pat : patterns)
+            {
+                Matcher mat = pat.getMatcher(line);
+                if (mat.find())
+                {
+                    int num = 0, count = mat.groupCount();
+                    String[] match = new String[count];
+                    while (num++ < count)
+                    {
+                        match[num - 1] = mat.group(num);
+                    }
+                    pat.getMatches().add(match);
+
+                    if (pat.getCount() > 0)
+                    {
+                        getLatch().countDown();
+                    }
+                }
+            }
+        }
+
+        public void waitForDone(long timeout, TimeUnit unit) throws InterruptedException
+        {
+            getLatch().await(timeout,unit);
+        }
+
+        private CountDownLatch getLatch()
+        {
+            synchronized (this)
+            {
+                if (latch == null)
+                {
+                    latch = new CountDownLatch(count);
+                }
+            }
+
+            return latch;
+        }
+    }
+
+    private static class ConsolePattern
+    {
+        private Pattern pattern;
+        private List<String[]> matches;
+        private int count;
+
+        ConsolePattern(String exp, int cnt)
+        {
+            pattern = Pattern.compile(exp);
+            matches = new ArrayList<String[]>();
+            count = cnt;
+        }
+
+        public Matcher getMatcher(String line)
+        {
+            return pattern.matcher(line);
+        }
+
+        public List<String[]> getMatches()
+        {
+            return matches;
+        }
+
+        public int getCount()
+        {
+            return count;
+        }
+    }
+
+    private void startPump(String mode, ConsoleParser parser, InputStream inputStream)
+    {
+        ConsoleStreamer pump = new ConsoleStreamer(mode,inputStream);
+        pump.setParser(parser);
+        Thread thread = new Thread(pump,"ConsoleStreamer/" + mode);
+        thread.start();
+    }
+
+    /**
+     * Simple streamer for the console output from a Process
+     */
+    private static class ConsoleStreamer implements Runnable
+    {
+        private String mode;
+        private BufferedReader reader;
+        private ConsoleParser parser;
+
+        public ConsoleStreamer(String mode, InputStream is)
+        {
+            this.mode = mode;
+            this.reader = new BufferedReader(new InputStreamReader(is));
+        }
+
+        public void setParser(ConsoleParser connector)
+        {
+            this.parser = connector;
+        }
+
+        public void run()
+        {
+            String line;
+            //System.out.printf("ConsoleStreamer/%s initiated%n",mode);
+            try
+            {
+                while ((line = reader.readLine()) != (null))
+                {
+                    if (parser != null)
+                    {
+                        parser.parse(line);
+                    }
+                    System.out.println("[" + mode + "] " + line);
+                }
+            }
+            catch (IOException ignore)
+            {
+                /* ignore */
+            }
+            finally
+            {
+                IO.close(reader);
+            }
+            //System.out.printf("ConsoleStreamer/%s finished%n",mode);
+        }
+    }
+}
diff --git a/jetty-ant/src/test/java/org/eclipse/jetty/ant/JettyAntTaskTest.java b/jetty-ant/src/test/java/org/eclipse/jetty/ant/JettyAntTaskTest.java
new file mode 100644
index 0000000..7794dc5
--- /dev/null
+++ b/jetty-ant/src/test/java/org/eclipse/jetty/ant/JettyAntTaskTest.java
@@ -0,0 +1,72 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.ant;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
+import java.net.HttpURLConnection;
+import java.net.URI;
+
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.junit.Test;
+
+public class JettyAntTaskTest
+{
+    
+    @Test
+    public void testConnectorTask() throws Exception
+    {
+        AntBuild build = new AntBuild(MavenTestingUtils.getTestResourceFile("connector-test.xml").getAbsolutePath());
+      
+        build.start();
+        
+        URI uri = new URI("http://" + build.getJettyHost() + ":" + build.getJettyPort());
+        
+        HttpURLConnection connection = (HttpURLConnection)uri.toURL().openConnection();
+        
+        connection.connect();
+
+        assertThat("response code is 404", connection.getResponseCode(), is(404));
+
+        build.stop();
+    }
+
+
+    @Test
+    public void testWebApp () throws Exception
+    {
+        AntBuild build = new AntBuild(MavenTestingUtils.getTestResourceFile("webapp-test.xml").getAbsolutePath());
+
+        build.start();
+
+        URI uri = new URI("http://" + build.getJettyHost() + ":" + build.getJettyPort() + "/");
+
+        HttpURLConnection connection = (HttpURLConnection)uri.toURL().openConnection();
+
+        connection.connect();
+
+        assertThat("response code is 200", connection.getResponseCode(), is(200));
+
+        System.err.println("Stop build!");
+        build.stop();
+    }
+
+   
+}
diff --git a/jetty-ant/src/test/resources/connector-test.xml b/jetty-ant/src/test/resources/connector-test.xml
new file mode 100644
index 0000000..fdc69d7
--- /dev/null
+++ b/jetty-ant/src/test/resources/connector-test.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<project name="Jetty-Ant integration test" basedir=".">
+  <path id="jetty.plugin.classpath">
+    <fileset dir="target/test-lib" includes="*.jar"/>
+  </path>
+	
+  <taskdef classpathref="jetty.plugin.classpath" resource="tasks.properties" loaderref="jetty.loader" />
+ 
+	<typedef name="connector" classname="org.eclipse.jetty.ant.types.Connector" 
+		classpathref="jetty.plugin.classpath" loaderref="jetty.loader" />
+
+  <target name="jetty.run">
+    <jetty.run>
+    	<connectors>
+        <connector port="0"/>	
+    	</connectors>
+    </jetty.run>
+  </target>
+</project>
diff --git a/jetty-ant/src/test/resources/foo/WEB-INF/acme-taglib.tld b/jetty-ant/src/test/resources/foo/WEB-INF/acme-taglib.tld
new file mode 100644
index 0000000..09b70ae
--- /dev/null
+++ b/jetty-ant/src/test/resources/foo/WEB-INF/acme-taglib.tld
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE taglib
+        PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN"
+	"http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd">
+
+<taglib>
+
+  <tlib-version>1.0</tlib-version>
+  <jsp-version>1.2</jsp-version>
+  <short-name>acme</short-name>
+  <uri>http://www.acme.com/taglib</uri>
+  <description>taglib example</description>
+  <listener>
+    <listener-class>com.acme.TagListener</listener-class>
+  </listener>
+
+  <tag>
+    <name>date</name>
+    <tag-class>com.acme.DateTag</tag-class>
+    <body-content>TAGDEPENDENT</body-content>
+    <description>Display Date</description>
+    <attribute>
+       <name>tz</name>
+       <required>false</required>
+    </attribute>
+  </tag>       
+
+</taglib>
diff --git a/test-jetty-webapp/src/main/webapp/WEB-INF/acme-taglib2.tld b/jetty-ant/src/test/resources/foo/WEB-INF/acme-taglib2.tld
similarity index 100%
copy from test-jetty-webapp/src/main/webapp/WEB-INF/acme-taglib2.tld
copy to jetty-ant/src/test/resources/foo/WEB-INF/acme-taglib2.tld
diff --git a/test-jetty-webapp/src/main/webapp/WEB-INF/tags/panel.tag b/jetty-ant/src/test/resources/foo/WEB-INF/tags/panel.tag
similarity index 100%
copy from test-jetty-webapp/src/main/webapp/WEB-INF/tags/panel.tag
copy to jetty-ant/src/test/resources/foo/WEB-INF/tags/panel.tag
diff --git a/jetty-ant/src/test/resources/foo/WEB-INF/web.xml b/jetty-ant/src/test/resources/foo/WEB-INF/web.xml
new file mode 100644
index 0000000..b3b1176
--- /dev/null
+++ b/jetty-ant/src/test/resources/foo/WEB-INF/web.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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>Test WebApp</display-name>
+  
+  <context-param>
+    <param-name>org.eclipse.jetty.server.context.ManagedAttributes</param-name>
+    <param-value>QoSFilter,TransparentProxy.ThreadPool,TransparentProxy.HttpClient</param-value>
+  </context-param>
+  
+
+  <servlet>
+     <servlet-name>foo.jsp</servlet-name>
+     <jsp-file>/jsp/foo/foo.jsp</jsp-file>
+  </servlet>
+  <servlet-mapping>
+    <servlet-name>foo.jsp</servlet-name>
+    <url-pattern>/jsp/foo/</url-pattern>
+  </servlet-mapping>
+
+
+</web-app>
+
+
diff --git a/jetty-ant/src/test/resources/foo/index.html b/jetty-ant/src/test/resources/foo/index.html
new file mode 100644
index 0000000..bab0d7c
--- /dev/null
+++ b/jetty-ant/src/test/resources/foo/index.html
@@ -0,0 +1,5 @@
+<html>
+  <body>
+   <h1>INDEX!</h1>
+  </body>
+</html>
diff --git a/test-jetty-webapp/src/main/webapp/jsp/bean1.jsp b/jetty-ant/src/test/resources/foo/jsp/bean1.jsp
similarity index 100%
copy from test-jetty-webapp/src/main/webapp/jsp/bean1.jsp
copy to jetty-ant/src/test/resources/foo/jsp/bean1.jsp
diff --git a/test-jetty-webapp/src/main/webapp/jsp/bean2.jsp b/jetty-ant/src/test/resources/foo/jsp/bean2.jsp
similarity index 100%
copy from test-jetty-webapp/src/main/webapp/jsp/bean2.jsp
copy to jetty-ant/src/test/resources/foo/jsp/bean2.jsp
diff --git a/test-jetty-nested/src/main/webapp/nested/dump.jsp b/jetty-ant/src/test/resources/foo/jsp/dump.jsp
similarity index 100%
rename from test-jetty-nested/src/main/webapp/nested/dump.jsp
rename to jetty-ant/src/test/resources/foo/jsp/dump.jsp
diff --git a/test-jetty-webapp/src/main/webapp/jsp/expr.jsp b/jetty-ant/src/test/resources/foo/jsp/expr.jsp
similarity index 100%
copy from test-jetty-webapp/src/main/webapp/jsp/expr.jsp
copy to jetty-ant/src/test/resources/foo/jsp/expr.jsp
diff --git a/test-jetty-webapp/src/main/webapp/jsp/foo/foo.jsp b/jetty-ant/src/test/resources/foo/jsp/foo/foo.jsp
similarity index 100%
copy from test-jetty-webapp/src/main/webapp/jsp/foo/foo.jsp
copy to jetty-ant/src/test/resources/foo/jsp/foo/foo.jsp
diff --git a/test-jetty-webapp/src/main/webapp/jsp/index.html b/jetty-ant/src/test/resources/foo/jsp/index.html
similarity index 100%
copy from test-jetty-webapp/src/main/webapp/jsp/index.html
copy to jetty-ant/src/test/resources/foo/jsp/index.html
diff --git a/test-jetty-webapp/src/main/webapp/jsp/jstl.jsp b/jetty-ant/src/test/resources/foo/jsp/jstl.jsp
similarity index 100%
copy from test-jetty-webapp/src/main/webapp/jsp/jstl.jsp
copy to jetty-ant/src/test/resources/foo/jsp/jstl.jsp
diff --git a/test-jetty-webapp/src/main/webapp/jsp/tag.jsp b/jetty-ant/src/test/resources/foo/jsp/tag.jsp
similarity index 100%
copy from test-jetty-webapp/src/main/webapp/jsp/tag.jsp
copy to jetty-ant/src/test/resources/foo/jsp/tag.jsp
diff --git a/test-jetty-webapp/src/main/webapp/jsp/tag2.jsp b/jetty-ant/src/test/resources/foo/jsp/tag2.jsp
similarity index 100%
copy from test-jetty-webapp/src/main/webapp/jsp/tag2.jsp
copy to jetty-ant/src/test/resources/foo/jsp/tag2.jsp
diff --git a/test-jetty-webapp/src/main/webapp/jsp/tagfile.jsp b/jetty-ant/src/test/resources/foo/jsp/tagfile.jsp
similarity index 100%
copy from test-jetty-webapp/src/main/webapp/jsp/tagfile.jsp
copy to jetty-ant/src/test/resources/foo/jsp/tagfile.jsp
diff --git a/jetty-ant/src/test/resources/webapp-test.xml b/jetty-ant/src/test/resources/webapp-test.xml
new file mode 100644
index 0000000..2593759
--- /dev/null
+++ b/jetty-ant/src/test/resources/webapp-test.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<project name="Jetty-Ant integration test" basedir="." >
+  <path id="jetty.plugin.classpath">
+    <fileset dir="target/test-lib" includes="*.jar"/>
+  </path>
+	
+  <taskdef classpathref="jetty.plugin.classpath" resource="tasks.properties" loaderref="jetty.loader" />
+ 
+  <typedef name="webapp" classname="org.eclipse.jetty.ant.AntWebAppContext" 
+           classpathref="jetty.plugin.classpath" loaderref="jetty.loader" />
+
+  <target name="jetty.run">
+    <jetty.run daemon="true" scanIntervalSeconds="10" jettyPort="0">
+       <webapp  war="${basedir}/src/test/resources/foo" contextpath="/" />
+    </jetty.run>
+  </target>
+</project>
diff --git a/jetty-cdi/pom.xml b/jetty-cdi/pom.xml
new file mode 100644
index 0000000..f349271
--- /dev/null
+++ b/jetty-cdi/pom.xml
@@ -0,0 +1,49 @@
+<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">
+  <parent>
+    <groupId>org.eclipse.jetty</groupId>
+    <artifactId>jetty-project</artifactId>
+    <version>9.2.13.v20150730</version>
+  </parent>
+  <modelVersion>4.0.0</modelVersion>
+  <artifactId>jetty-cdi</artifactId>
+  <name>Jetty :: CDI Configurations</name>
+  <url>http://www.eclipse.org/jetty</url>
+  <packaging>jar</packaging>
+  <properties>
+    <bundle-symbolic-name>${project.groupId}.${project.artifactId}</bundle-symbolic-name>
+  </properties>
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-assembly-plugin</artifactId>
+        <executions>
+          <execution>
+            <phase>package</phase>
+            <goals>
+              <goal>single</goal>
+            </goals>
+            <configuration>
+              <descriptorRefs>
+                <descriptorRef>config</descriptorRef>
+              </descriptorRefs>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-plus</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-deploy</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/jetty-cdi/src/main/config/etc/jetty-cdi.xml b/jetty-cdi/src/main/config/etc/jetty-cdi.xml
new file mode 100644
index 0000000..d364f4c
--- /dev/null
+++ b/jetty-cdi/src/main/config/etc/jetty-cdi.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0"?>
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
+
+<!-- =============================================================== -->
+<!-- Mixin the Weld / CDI classes to the class loader                -->
+<!-- =============================================================== -->
+
+<Configure id="Server" class="org.eclipse.jetty.server.Server">
+  <Ref refid="DeploymentManager">
+    <Call name="addLifeCycleBinding">
+      <Arg>
+        <New
+          class="org.eclipse.jetty.cdi.WeldDeploymentBinding">
+        </New>
+      </Arg>
+    </Call>
+  </Ref>
+</Configure>
+
diff --git a/jetty-cdi/src/main/config/modules/cdi.mod b/jetty-cdi/src/main/config/modules/cdi.mod
new file mode 100644
index 0000000..68dea58
--- /dev/null
+++ b/jetty-cdi/src/main/config/modules/cdi.mod
@@ -0,0 +1,26 @@
+#
+# CDI / Weld Jetty module
+#
+
+[depend]
+deploy
+annotations
+plus
+# JSP (and EL) are requirements for CDI and Weld
+jsp
+
+[files]
+lib/weld/
+http://central.maven.org/maven2/org/jboss/weld/servlet/weld-servlet/2.2.5.Final/weld-servlet-2.2.5.Final.jar|lib/weld/weld-servlet-2.2.5.Final.jar
+
+[lib]
+lib/weld/weld-servlet-2.2.5.Final.jar
+lib/jetty-cdi-${jetty.version}.jar
+
+[xml]
+etc/jetty-cdi.xml
+
+[license]
+Weld is an open source project hosted on Github and released under the Apache 2.0 license.
+http://weld.cdi-spec.org/
+http://www.apache.org/licenses/LICENSE-2.0.html
diff --git a/jetty-cdi/src/main/java/org/eclipse/jetty/cdi/WeldDeploymentBinding.java b/jetty-cdi/src/main/java/org/eclipse/jetty/cdi/WeldDeploymentBinding.java
new file mode 100644
index 0000000..0ce37ec
--- /dev/null
+++ b/jetty-cdi/src/main/java/org/eclipse/jetty/cdi/WeldDeploymentBinding.java
@@ -0,0 +1,77 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.cdi;
+
+import javax.naming.Reference;
+
+import org.eclipse.jetty.deploy.App;
+import org.eclipse.jetty.deploy.AppLifeCycle;
+import org.eclipse.jetty.deploy.graph.Node;
+import org.eclipse.jetty.plus.jndi.Resource;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.webapp.WebAppContext;
+
+/**
+ * Perform some basic weld configuration of WebAppContext
+ */
+public class WeldDeploymentBinding implements AppLifeCycle.Binding
+{
+    public String[] getBindingTargets()
+    {
+        return new String[]
+        { "deploying" };
+    }
+
+    public void processBinding(Node node, App app) throws Exception
+    {
+        ContextHandler handler = app.getContextHandler();
+        if (handler == null)
+        {
+            throw new NullPointerException("No Handler created for App: " + app);
+        }
+
+        if (handler instanceof WebAppContext)
+        {
+            WebAppContext webapp = (WebAppContext)handler;
+
+            // Add context specific weld container reference.
+            // See https://issues.jboss.org/browse/WELD-1710
+            // and https://github.com/weld/core/blob/2.2.5.Final/environments/servlet/core/src/main/java/org/jboss/weld/environment/servlet/WeldServletLifecycle.java#L244-L253
+            webapp.setInitParameter("org.jboss.weld.environment.container.class",
+                    "org.jboss.weld.environment.jetty.JettyContainer");
+            
+            // Setup Weld BeanManager reference
+            Reference ref = new Reference("javax.enterprise.inject.spi.BeanManager",
+                    "org.jboss.weld.resources.ManagerObjectFactory", null);
+            new Resource(webapp,"BeanManager",ref);
+            
+            // webapp cannot change / replace weld classes
+            webapp.addSystemClass("org.jboss.weld.");
+            webapp.addSystemClass("org.jboss.classfilewriter.");
+            webapp.addSystemClass("org.jboss.logging.");
+            webapp.addSystemClass("com.google.common.");
+            
+            // don't hide weld classes from webapps (allow webapp to use ones from system classloader)
+            webapp.addServerClass("-org.jboss.weld.");
+            webapp.addServerClass("-org.jboss.classfilewriter.");
+            webapp.addServerClass("-org.jboss.logging.");
+            webapp.addServerClass("-com.google.common.");
+        }
+    }
+}
diff --git a/jetty-client/pom.xml b/jetty-client/pom.xml
index 05f9e31..22bcb06 100644
--- a/jetty-client/pom.xml
+++ b/jetty-client/pom.xml
@@ -1,119 +1,128 @@
 <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">
-    <parent>
-        <groupId>org.eclipse.jetty</groupId>
-        <artifactId>jetty-project</artifactId>
-        <version>8.1.17.v20150415</version>
-    </parent>
+  <parent>
+    <groupId>org.eclipse.jetty</groupId>
+    <artifactId>jetty-project</artifactId>
+    <version>9.2.13.v20150730</version>
+  </parent>
 
-    <modelVersion>4.0.0</modelVersion>
-    <artifactId>jetty-client</artifactId>
-    <name>Jetty :: Asynchronous HTTP Client</name>
-    <url>http://www.eclipse.org/jetty</url>
-    <properties>
-        <bundle-symbolic-name>${project.groupId}.client</bundle-symbolic-name>
-        <jetty.test.policy.loc>target/test-policy</jetty.test.policy.loc>
-    </properties>
-    <build>
-        <plugins>
-            <plugin>
-                <groupId>org.apache.felix</groupId>
-                <artifactId>maven-bundle-plugin</artifactId>
-                <extensions>true</extensions>
-                <executions>
-                    <execution>
-                        <goals>
-                            <goal>manifest</goal>
-                        </goals>
-                        <configuration>
-                            <instructions>
-                                <Import-Package>javax.net.*,*</Import-Package>
-                            </instructions>
-                        </configuration>
-                    </execution>
-                </executions>
-            </plugin>
-            <plugin>
-                <!--
-                Required for OSGI
-                -->
-                <groupId>org.apache.maven.plugins</groupId>
-                <artifactId>maven-jar-plugin</artifactId>
-                <configuration>
-                    <archive>
-                        <manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
-                    </archive>
-                </configuration>
-            </plugin>
-            <plugin>
-                <groupId>org.codehaus.mojo</groupId>
-                <artifactId>findbugs-maven-plugin</artifactId>
-                <configuration>
-                    <onlyAnalyze>org.eclipse.jetty.client.*</onlyAnalyze>
-                </configuration>
-            </plugin>
-            <plugin>
-              <groupId>org.apache.maven.plugins</groupId>
-              <artifactId>maven-dependency-plugin</artifactId>
-              <executions>
-                <execution>
-                  <id>unpack</id>
-                  <phase>generate-test-resources</phase>
-                  <goals>
-                    <goal>unpack</goal>
-                  </goals>
-                  <configuration>
-                    <artifactItems>
-                      <artifactItem>
-                        <groupId>org.eclipse.jetty.toolchain</groupId>
-                        <artifactId>jetty-test-policy</artifactId>
-                        <version>${jetty-test-policy-version}</version>
-                        <type>jar</type>
-                        <overWrite>true</overWrite>
-                        <includes>**/*.keystore,**/*.pem</includes>
-                        <outputDirectory>${jetty.test.policy.loc}</outputDirectory>
-                      </artifactItem>
-                    </artifactItems>
-                  </configuration>
-                </execution>
-              </executions>
-            </plugin>   
-        </plugins>
-    </build>
+  <modelVersion>4.0.0</modelVersion>
+  <artifactId>jetty-client</artifactId>
+  <name>Jetty :: Asynchronous HTTP Client</name>
+  <url>http://www.eclipse.org/jetty</url>
+  <properties>
+    <bundle-symbolic-name>${project.groupId}.client</bundle-symbolic-name>
+    <jetty.test.policy.loc>target/test-policy</jetty.test.policy.loc>
+  </properties>
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-assembly-plugin</artifactId>
+        <executions>
+          <execution>
+            <phase>package</phase>
+            <goals>
+              <goal>single</goal>
+            </goals>
+            <configuration>
+              <descriptorRefs>
+                <descriptorRef>config</descriptorRef>
+              </descriptorRefs>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>maven-bundle-plugin</artifactId>
+        <extensions>true</extensions>
+        <executions>
+          <execution>
+            <goals>
+              <goal>manifest</goal>
+            </goals>
+            <configuration>
+              <instructions>
+                <Import-Package>javax.net.*,*</Import-Package>
+              </instructions>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+      <!-- Required for OSGI -->
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-jar-plugin</artifactId>
+        <configuration>
+          <archive>
+            <manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
+          </archive>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>findbugs-maven-plugin</artifactId>
+        <configuration>
+          <onlyAnalyze>org.eclipse.jetty.client.*</onlyAnalyze>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-dependency-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>unpack</id>
+            <phase>generate-test-resources</phase>
+            <goals>
+              <goal>unpack</goal>
+            </goals>
+            <configuration>
+              <artifactItems>
+                <artifactItem>
+                  <groupId>org.eclipse.jetty.toolchain</groupId>
+                  <artifactId>jetty-test-policy</artifactId>
+                  <version>${jetty-test-policy-version}</version>
+                  <type>jar</type>
+                  <overWrite>true</overWrite>
+                  <includes>**/*.keystore,**/*.pem</includes>
+                  <outputDirectory>${jetty.test.policy.loc}</outputDirectory>
+                </artifactItem>
+              </artifactItems>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
 
-    <dependencies>
-        <dependency>
-            <groupId>org.eclipse.jetty</groupId>
-            <artifactId>jetty-http</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-        <dependency>
-            <groupId>org.eclipse.jetty</groupId>
-            <artifactId>jetty-server</artifactId>
-            <version>${project.version}</version>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.eclipse.jetty</groupId>
-            <artifactId>jetty-security</artifactId>
-            <version>${project.version}</version>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.eclipse.jetty</groupId>
-            <artifactId>jetty-servlet</artifactId>
-            <version>${project.version}</version>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.eclipse.jetty</groupId>
-            <artifactId>jetty-websocket</artifactId>
-            <version>${project.version}</version>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.eclipse.jetty.toolchain</groupId>
-            <artifactId>jetty-test-helper</artifactId>
-            <scope>test</scope>
-        </dependency>
-    </dependencies>
+  <dependencies>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-http</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-io</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-server</artifactId>
+      <version>${project.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-security</artifactId>
+      <version>${project.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.toolchain</groupId>
+      <artifactId>jetty-test-helper</artifactId>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
 </project>
diff --git a/jetty-client/src/main/config/modules/client.mod b/jetty-client/src/main/config/modules/client.mod
new file mode 100644
index 0000000..39b58d4
--- /dev/null
+++ b/jetty-client/src/main/config/modules/client.mod
@@ -0,0 +1,6 @@
+#
+# Client Feature
+#
+
+[lib]
+lib/jetty-client-${jetty.version}.jar
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractHttpClientTransport.java b/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractHttpClientTransport.java
new file mode 100644
index 0000000..5d22fa7
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractHttpClientTransport.java
@@ -0,0 +1,172 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.io.IOException;
+import java.net.SocketAddress;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.SocketChannel;
+import java.util.Map;
+
+import org.eclipse.jetty.client.api.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.SelectChannelEndPoint;
+import org.eclipse.jetty.io.SelectorManager;
+import org.eclipse.jetty.io.ssl.SslClientConnectionFactory;
+import org.eclipse.jetty.util.Promise;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public abstract class AbstractHttpClientTransport extends ContainerLifeCycle implements HttpClientTransport
+{
+    protected static final Logger LOG = Log.getLogger(HttpClientTransport.class);
+
+    private final int selectors;
+    private volatile HttpClient client;
+    private volatile SelectorManager selectorManager;
+
+    protected AbstractHttpClientTransport(int selectors)
+    {
+        this.selectors = selectors;
+    }
+
+    protected HttpClient getHttpClient()
+    {
+        return client;
+    }
+
+    @Override
+    public void setHttpClient(HttpClient client)
+    {
+        this.client = client;
+    }
+
+    @Override
+    protected void doStart() throws Exception
+    {
+        selectorManager = newSelectorManager(client);
+        selectorManager.setConnectTimeout(client.getConnectTimeout());
+        addBean(selectorManager);
+        super.doStart();
+    }
+
+    @Override
+    protected void doStop() throws Exception
+    {
+        super.doStop();
+        removeBean(selectorManager);
+    }
+
+    @Override
+    public void connect(SocketAddress address, Map<String, Object> context)
+    {
+        SocketChannel channel = null;
+        try
+        {
+            channel = SocketChannel.open();
+            HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY);
+            HttpClient client = destination.getHttpClient();
+            SocketAddress bindAddress = client.getBindAddress();
+            if (bindAddress != null)
+                channel.bind(bindAddress);
+            configure(client, channel);
+            channel.configureBlocking(false);
+
+            context.put(SslClientConnectionFactory.SSL_PEER_HOST_CONTEXT_KEY, destination.getHost());
+            context.put(SslClientConnectionFactory.SSL_PEER_PORT_CONTEXT_KEY, destination.getPort());
+
+            if (channel.connect(address))
+                selectorManager.accept(channel, context);
+            else
+                selectorManager.connect(channel, context);
+        }
+        // Must catch all exceptions, since some like
+        // UnresolvedAddressException are not IOExceptions.
+        catch (Throwable x)
+        {
+            try
+            {
+                if (channel != null)
+                    channel.close();
+            }
+            catch (IOException xx)
+            {
+                LOG.ignore(xx);
+            }
+            finally
+            {
+                connectFailed(context, x);
+            }
+        }
+    }
+
+    protected void connectFailed(Map<String, Object> context, Throwable x)
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("Could not connect to {}", context.get(HTTP_DESTINATION_CONTEXT_KEY));
+        @SuppressWarnings("unchecked")
+        Promise<Connection> promise = (Promise<Connection>)context.get(HTTP_CONNECTION_PROMISE_CONTEXT_KEY);
+        promise.failed(x);
+    }
+
+    protected void configure(HttpClient client, SocketChannel channel) throws IOException
+    {
+        channel.socket().setTcpNoDelay(client.isTCPNoDelay());
+    }
+
+    protected SelectorManager newSelectorManager(HttpClient client)
+    {
+        return new ClientSelectorManager(client, selectors);
+    }
+
+    protected class ClientSelectorManager extends SelectorManager
+    {
+        private final HttpClient client;
+
+        protected ClientSelectorManager(HttpClient client, int selectors)
+        {
+            super(client.getExecutor(), client.getScheduler(), selectors);
+            this.client = client;
+        }
+
+        @Override
+        protected EndPoint newEndPoint(SocketChannel channel, ManagedSelector selector, SelectionKey key)
+        {
+            return new SelectChannelEndPoint(channel, selector, key, getScheduler(), client.getIdleTimeout());
+        }
+
+        @Override
+        public org.eclipse.jetty.io.Connection newConnection(SocketChannel channel, EndPoint endPoint, Object attachment) throws IOException
+        {
+            @SuppressWarnings("unchecked")
+            Map<String, Object> context = (Map<String, Object>)attachment;
+            HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY);
+            return destination.getClientConnectionFactory().newConnection(endPoint, context);
+        }
+
+        @Override
+        protected void connectionFailed(SocketChannel channel, Throwable x, Object attachment)
+        {
+            @SuppressWarnings("unchecked")
+            Map<String, Object> context = (Map<String, Object>)attachment;
+            connectFailed(context, x);
+        }
+    }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractHttpConnection.java b/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractHttpConnection.java
deleted file mode 100644
index 5ffa8cb..0000000
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractHttpConnection.java
+++ /dev/null
@@ -1,571 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Collections;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import org.eclipse.jetty.client.security.Authentication;
-import org.eclipse.jetty.http.HttpFields;
-import org.eclipse.jetty.http.HttpGenerator;
-import org.eclipse.jetty.http.HttpHeaderValues;
-import org.eclipse.jetty.http.HttpHeaders;
-import org.eclipse.jetty.http.HttpMethods;
-import org.eclipse.jetty.http.HttpParser;
-import org.eclipse.jetty.http.HttpSchemes;
-import org.eclipse.jetty.http.HttpStatus;
-import org.eclipse.jetty.http.HttpVersions;
-import org.eclipse.jetty.io.AbstractConnection;
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.Buffers;
-import org.eclipse.jetty.io.Connection;
-import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.io.EofException;
-import org.eclipse.jetty.io.View;
-import org.eclipse.jetty.util.component.AggregateLifeCycle;
-import org.eclipse.jetty.util.component.Dumpable;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.util.thread.Timeout;
-
-/**
- *
- * @version $Revision: 879 $ $Date: 2009-09-11 16:13:28 +0200 (Fri, 11 Sep 2009) $
- */
-public abstract class AbstractHttpConnection extends AbstractConnection implements Dumpable
-{
-    private static final Logger LOG = Log.getLogger(AbstractHttpConnection.class);
-
-    protected HttpDestination _destination;
-    protected HttpGenerator _generator;
-    protected HttpParser _parser;
-    protected boolean _http11 = true;
-    protected int _status;
-    protected Buffer _connectionHeader;
-    protected boolean _reserved;
-
-    // The current exchange waiting for a response
-    protected volatile HttpExchange _exchange;
-    protected HttpExchange _pipeline;
-    private final Timeout.Task _idleTimeout = new ConnectionIdleTask();
-    private AtomicBoolean _idle = new AtomicBoolean(false);
-
-
-    AbstractHttpConnection(Buffers requestBuffers, Buffers responseBuffers, EndPoint endp)
-    {
-        super(endp);
-
-        _generator = new HttpGenerator(requestBuffers,endp);
-        _parser = new HttpParser(responseBuffers,endp,new Handler());
-    }
-
-    public void setReserved (boolean reserved)
-    {
-        _reserved = reserved;
-    }
-
-    public boolean isReserved()
-    {
-        return _reserved;
-    }
-
-    public HttpDestination getDestination()
-    {
-        return _destination;
-    }
-
-    public void setDestination(HttpDestination destination)
-    {
-        _destination = destination;
-    }
-
-    public boolean send(HttpExchange ex) throws IOException
-    {
-        LOG.debug("Send {} on {}",ex,this);
-        synchronized (this)
-        {
-            if (_exchange != null)
-            {
-                if (_pipeline != null)
-                    throw new IllegalStateException(this + " PIPELINED!!!  _exchange=" + _exchange);
-                _pipeline = ex;
-                return true;
-            }
-
-            _exchange = ex;
-            _exchange.associate(this);
-
-            // The call to associate() may have closed the connection, check if it's the case
-            if (!_endp.isOpen())
-            {
-                _exchange.disassociate();
-                _exchange = null;
-                return false;
-            }
-
-            _exchange.setStatus(HttpExchange.STATUS_WAITING_FOR_COMMIT);
-
-            adjustIdleTimeout();
-
-            return true;
-        }
-    }
-
-    private void adjustIdleTimeout() throws IOException
-    {
-        // Adjusts the idle timeout in case the default or exchange timeout
-        // are greater. This is needed for long polls, where one wants an
-        // aggressive releasing of idle connections (so idle timeout is small)
-        // but still allow long polls to complete normally
-
-        long timeout = _exchange.getTimeout();
-        if (timeout <= 0)
-            timeout = _destination.getHttpClient().getTimeout();
-
-        long endPointTimeout = _endp.getMaxIdleTime();
-
-        if (timeout > 0 && timeout > endPointTimeout)
-        {
-            // Make it larger than the exchange timeout so that there are
-            // no races between the idle timeout and the exchange timeout
-            // when trying to close the endpoint
-            _endp.setMaxIdleTime(2 * (int)timeout);
-        }
-    }
-
-    public abstract Connection handle() throws IOException;
-
-
-    public boolean isIdle()
-    {
-        synchronized (this)
-        {
-            return _exchange == null;
-        }
-    }
-
-    public boolean isSuspended()
-    {
-        return false;
-    }
-
-    public void onClose()
-    {
-    }
-
-    /**
-     * @throws IOException
-     */
-    protected void commitRequest() throws IOException
-    {
-        synchronized (this)
-        {
-            _status=0;
-            if (_exchange.getStatus() != HttpExchange.STATUS_WAITING_FOR_COMMIT)
-                throw new IllegalStateException();
-
-            _exchange.setStatus(HttpExchange.STATUS_SENDING_REQUEST);
-            _generator.setVersion(_exchange.getVersion());
-
-            String method=_exchange.getMethod();
-            String uri = _exchange.getRequestURI();
-            if (_destination.isProxied())
-            {
-                if (!HttpMethods.CONNECT.equals(method) && uri.startsWith("/"))
-                {
-                    boolean secure = _destination.isSecure();
-                    String host = _destination.getAddress().getHost();
-                    int port = _destination.getAddress().getPort();
-                    StringBuilder absoluteURI = new StringBuilder();
-                    absoluteURI.append(secure ? HttpSchemes.HTTPS : HttpSchemes.HTTP);
-                    absoluteURI.append("://");
-                    absoluteURI.append(host);
-                    // Avoid adding default ports
-                    if (!(secure && port == 443 || !secure && port == 80))
-                        absoluteURI.append(":").append(port);
-                    absoluteURI.append(uri);
-                    uri = absoluteURI.toString();
-                }
-                Authentication auth = _destination.getProxyAuthentication();
-                if (auth != null)
-                    auth.setCredentials(_exchange);
-            }
-
-            _generator.setRequest(method, uri);
-            _parser.setHeadResponse(HttpMethods.HEAD.equalsIgnoreCase(method));
-
-            HttpFields requestHeaders = _exchange.getRequestFields();
-            if (_exchange.getVersion() >= HttpVersions.HTTP_1_1_ORDINAL)
-            {
-                if (!requestHeaders.containsKey(HttpHeaders.HOST_BUFFER))
-                    requestHeaders.add(HttpHeaders.HOST_BUFFER,_destination.getHostHeader());
-            }
-
-            Buffer requestContent = _exchange.getRequestContent();
-            if (requestContent != null)
-            {
-                requestHeaders.putLongField(HttpHeaders.CONTENT_LENGTH, requestContent.length());
-                _generator.completeHeader(requestHeaders,false);
-                _generator.addContent(new View(requestContent),true);
-                _exchange.setStatus(HttpExchange.STATUS_WAITING_FOR_RESPONSE);
-            }
-            else
-            {
-                InputStream requestContentStream = _exchange.getRequestContentSource();
-                if (requestContentStream != null)
-                {
-                    _generator.completeHeader(requestHeaders, false);
-                }
-                else
-                {
-                    requestHeaders.remove(HttpHeaders.CONTENT_LENGTH);
-                    _generator.completeHeader(requestHeaders, true);
-                    _exchange.setStatus(HttpExchange.STATUS_WAITING_FOR_RESPONSE);
-                }
-            }
-        }
-    }
-
-    protected void reset() throws IOException
-    {
-        _connectionHeader = null;
-        _parser.reset();
-        _generator.reset();
-        _http11 = true;
-    }
-
-
-    private class Handler extends HttpParser.EventHandler
-    {
-        @Override
-        public void startRequest(Buffer method, Buffer url, Buffer version) throws IOException
-        {
-            // System.out.println( method.toString() + "///" + url.toString() +
-            // "///" + version.toString() );
-            // TODO validate this is acceptable, the <!DOCTYPE goop was coming
-            // out here
-            // throw new IllegalStateException();
-        }
-
-        @Override
-        public void startResponse(Buffer version, int status, Buffer reason) throws IOException
-        {
-            HttpExchange exchange = _exchange;
-            if (exchange==null)
-            {
-                LOG.warn("No exchange for response");
-                _endp.close();
-                return;
-            }
-
-            switch(status)
-            {
-                case HttpStatus.CONTINUE_100:
-                case HttpStatus.PROCESSING_102:
-                    // TODO check if appropriate expect was sent in the request.
-                    exchange.setEventListener(new NonFinalResponseListener(exchange));
-                    break;
-
-                case HttpStatus.OK_200:
-                    // handle special case for CONNECT 200 responses
-                    if (HttpMethods.CONNECT.equalsIgnoreCase(exchange.getMethod()))
-                        _parser.setHeadResponse(true);
-                    break;
-            }
-
-            _http11 = HttpVersions.HTTP_1_1_BUFFER.equals(version);
-            _status=status;
-            exchange.getEventListener().onResponseStatus(version,status,reason);
-            exchange.setStatus(HttpExchange.STATUS_PARSING_HEADERS);
-
-        }
-
-        @Override
-        public void parsedHeader(Buffer name, Buffer value) throws IOException
-        {
-            HttpExchange exchange = _exchange;
-            if (exchange!=null)
-            {
-                if (HttpHeaders.CACHE.getOrdinal(name) == HttpHeaders.CONNECTION_ORDINAL)
-                {
-                    _connectionHeader = HttpHeaderValues.CACHE.lookup(value);
-                }
-                exchange.getEventListener().onResponseHeader(name,value);
-            }
-        }
-
-        @Override
-        public void headerComplete() throws IOException
-        {
-            HttpExchange exchange = _exchange;
-            if (exchange!=null)
-            {
-                exchange.setStatus(HttpExchange.STATUS_PARSING_CONTENT);
-                if (HttpMethods.CONNECT.equalsIgnoreCase(exchange.getMethod()))
-                    _parser.setPersistent(true);
-            }
-        }
-
-        @Override
-        public void content(Buffer ref) throws IOException
-        {
-            HttpExchange exchange = _exchange;
-            if (exchange!=null)
-                exchange.getEventListener().onResponseContent(ref);
-        }
-
-        @Override
-        public void messageComplete(long contextLength) throws IOException
-        {
-            HttpExchange exchange = _exchange;
-            if (exchange!=null)
-                exchange.setStatus(HttpExchange.STATUS_COMPLETED);
-        }
-
-        @Override
-        public void earlyEOF()
-        {
-            HttpExchange exchange = _exchange;
-            if (exchange!=null)
-            {
-                if (!exchange.isDone())
-                {
-                    if (exchange.setStatus(HttpExchange.STATUS_EXCEPTED))
-                        exchange.getEventListener().onException(new EofException("early EOF"));
-                }
-            }
-        }
-    }
-
-    @Override
-    public String toString()
-    {
-        return String.format("%s %s g=%s p=%s",
-                super.toString(),
-                _destination == null ? "?.?.?.?:??" : _destination.getAddress(),
-                _generator,
-                _parser);
-    }
-
-    public String toDetailString()
-    {
-        return toString() + " ex=" + _exchange + " idle for " + _idleTimeout.getAge();
-    }
-
-    public void close() throws IOException
-    {
-        //if there is a live, unfinished exchange, set its status to be
-        //excepted and wake up anyone waiting on waitForDone()
-
-        HttpExchange exchange = _exchange;
-        if (exchange != null && !exchange.isDone())
-        {
-            switch (exchange.getStatus())
-            {
-                case HttpExchange.STATUS_CANCELLED:
-                case HttpExchange.STATUS_CANCELLING:
-                case HttpExchange.STATUS_COMPLETED:
-                case HttpExchange.STATUS_EXCEPTED:
-                case HttpExchange.STATUS_EXPIRED:
-                    break;
-                case HttpExchange.STATUS_PARSING_CONTENT:
-                    if (_endp.isInputShutdown() && _parser.isState(HttpParser.STATE_EOF_CONTENT))
-                        break;
-                default:
-                    String exch= exchange.toString();
-                    String reason = _endp.isOpen()?(_endp.isInputShutdown()?"half closed: ":"local close: "):"closed: ";
-                    if (exchange.setStatus(HttpExchange.STATUS_EXCEPTED))
-                        exchange.getEventListener().onException(new EofException(reason+exch));
-            }
-        }
-
-        if (_endp.isOpen())
-        {
-            _endp.close();
-            _destination.returnConnection(this, true);
-        }
-    }
-
-    public void setIdleTimeout()
-    {
-        synchronized (this)
-        {
-            if (_idle.compareAndSet(false, true))
-                _destination.getHttpClient().scheduleIdle(_idleTimeout);
-            else
-                throw new IllegalStateException();
-        }
-    }
-
-    public boolean cancelIdleTimeout()
-    {
-        synchronized (this)
-        {
-            if (_idle.compareAndSet(true, false))
-            {
-                _destination.getHttpClient().cancel(_idleTimeout);
-                return true;
-            }
-        }
-
-        return false;
-    }
-
-    protected void exchangeExpired(HttpExchange exchange)
-    {
-        synchronized (this)
-        {
-            // We are expiring an exchange, but the exchange is pending
-            // Cannot reuse the connection because the reply may arrive, so close it
-            if (_exchange == exchange)
-            {
-                try
-                {
-                    _destination.returnConnection(this, true);
-                }
-                catch (IOException x)
-                {
-                    LOG.ignore(x);
-                }
-            }
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.util.component.Dumpable#dump()
-     */
-    public String dump()
-    {
-        return AggregateLifeCycle.dump(this);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.util.component.Dumpable#dump(java.lang.Appendable, java.lang.String)
-     */
-    public void dump(Appendable out, String indent) throws IOException
-    {
-        synchronized (this)
-        {
-            out.append(String.valueOf(this)).append("\n");
-            AggregateLifeCycle.dump(out,indent,Collections.singletonList(_endp));
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    private class ConnectionIdleTask extends Timeout.Task
-    {
-        /* ------------------------------------------------------------ */
-        @Override
-        public void expired()
-        {
-            // Connection idle, close it
-            if (_idle.compareAndSet(true, false))
-            {
-                _destination.returnIdleConnection(AbstractHttpConnection.this);
-            }
-        }
-    }
-
-
-    /* ------------------------------------------------------------ */
-    private class NonFinalResponseListener implements HttpEventListener
-    {
-        final HttpExchange _exchange;
-        final HttpEventListener _next;
-
-        /* ------------------------------------------------------------ */
-        public NonFinalResponseListener(HttpExchange exchange)
-        {
-            _exchange=exchange;
-            _next=exchange.getEventListener();
-        }
-
-        /* ------------------------------------------------------------ */
-        public void onRequestCommitted() throws IOException
-        {
-        }
-
-        /* ------------------------------------------------------------ */
-        public void onRequestComplete() throws IOException
-        {
-        }
-
-        /* ------------------------------------------------------------ */
-        public void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException
-        {
-        }
-
-        /* ------------------------------------------------------------ */
-        public void onResponseHeader(Buffer name, Buffer value) throws IOException
-        {
-            _next.onResponseHeader(name,value);
-        }
-
-        /* ------------------------------------------------------------ */
-        public void onResponseHeaderComplete() throws IOException
-        {
-            _next.onResponseHeaderComplete();
-        }
-
-        /* ------------------------------------------------------------ */
-        public void onResponseContent(Buffer content) throws IOException
-        {
-        }
-
-        /* ------------------------------------------------------------ */
-        public void onResponseComplete() throws IOException
-        {
-            _exchange.setEventListener(_next);
-            _exchange.setStatus(HttpExchange.STATUS_WAITING_FOR_RESPONSE);
-            _parser.reset();
-        }
-
-        /* ------------------------------------------------------------ */
-        public void onConnectionFailed(Throwable ex)
-        {
-            _exchange.setEventListener(_next);
-            _next.onConnectionFailed(ex);
-        }
-
-        /* ------------------------------------------------------------ */
-        public void onException(Throwable ex)
-        {
-            _exchange.setEventListener(_next);
-            _next.onException(ex);
-        }
-
-        /* ------------------------------------------------------------ */
-        public void onExpire()
-        {
-            _exchange.setEventListener(_next);
-            _next.onExpire();
-        }
-
-        /* ------------------------------------------------------------ */
-        public void onRetry()
-        {
-            _exchange.setEventListener(_next);
-            _next.onRetry();
-        }
-    }
-}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/Address.java b/jetty-client/src/main/java/org/eclipse/jetty/client/Address.java
deleted file mode 100644
index febeeca..0000000
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/Address.java
+++ /dev/null
@@ -1,96 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client;
-
-import java.net.InetSocketAddress;
-
-/**
- * @version $Revision: 4135 $ $Date: 2008-12-02 11:57:07 +0100 (Tue, 02 Dec 2008) $
- */
-public class Address
-{
-    private final String host;
-    private final int port;
-
-    public static Address from(String hostAndPort)
-    {
-        String host;
-        int port;
-        int colon = hostAndPort.indexOf(':');
-        if (colon >= 0)
-        {
-            host = hostAndPort.substring(0, colon);
-            port = Integer.parseInt(hostAndPort.substring(colon + 1));
-        }
-        else
-        {
-            host = hostAndPort;
-            port = 0;
-        }
-        return new Address(host, port);
-    }
-
-    public Address(String host, int port)
-    {
-        if (host == null)
-            throw new IllegalArgumentException("Host is null");
-        
-        this.host = host.trim();
-        this.port = port;
-    }
-
-    @Override
-    public boolean equals(Object obj)
-    {
-        if (this == obj) return true;
-        if (obj == null || getClass() != obj.getClass()) return false;
-        Address that = (Address)obj;
-        if (!host.equals(that.host)) return false;
-        return port == that.port;
-    }
-
-    @Override
-    public int hashCode()
-    {
-        int result = host.hashCode();
-        result = 31 * result + port;
-        return result;
-    }
-
-    public String getHost()
-    {
-        return host;
-    }
-
-    public int getPort()
-    {
-        return port;
-    }
-
-    public InetSocketAddress toSocketAddress()
-    {
-        return new InetSocketAddress(getHost(), getPort());
-    }
-
-    @Override
-    public String toString()
-    {
-        return host + ":" + port;
-    }
-}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/AsyncContentProvider.java b/jetty-client/src/main/java/org/eclipse/jetty/client/AsyncContentProvider.java
new file mode 100644
index 0000000..9ea69ac
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/AsyncContentProvider.java
@@ -0,0 +1,45 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.util.EventListener;
+
+import org.eclipse.jetty.client.api.ContentProvider;
+
+/**
+ * A {@link ContentProvider} that notifies listeners that content is available.
+ */
+public interface AsyncContentProvider extends ContentProvider
+{
+    /**
+     * @param listener the listener to be notified of content availability
+     */
+    public void setListener(Listener listener);
+
+    /**
+     * A listener that is notified of content availability
+     */
+    public interface Listener extends EventListener
+    {
+        /**
+         * Callback method invoked when content is available
+         */
+        public void onContent();
+    }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/AsyncHttpConnection.java b/jetty-client/src/main/java/org/eclipse/jetty/client/AsyncHttpConnection.java
deleted file mode 100644
index 32cba4a..0000000
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/AsyncHttpConnection.java
+++ /dev/null
@@ -1,269 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client;
-
-import java.io.IOException;
-
-import org.eclipse.jetty.http.AbstractGenerator;
-import org.eclipse.jetty.http.HttpStatus;
-import org.eclipse.jetty.io.AsyncEndPoint;
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.Buffers;
-import org.eclipse.jetty.io.Connection;
-import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.io.nio.AsyncConnection;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-
-
-
-/* ------------------------------------------------------------ */
-/** Asynchronous Client HTTP Connection
- */
-public class AsyncHttpConnection extends AbstractHttpConnection implements AsyncConnection
-{
-    private static final Logger LOG = Log.getLogger(AsyncHttpConnection.class);
-
-    private boolean _requestComplete;
-    private Buffer _requestContentChunk;
-    private final AsyncEndPoint _asyncEndp;
-
-    AsyncHttpConnection(Buffers requestBuffers, Buffers responseBuffers, EndPoint endp)
-    {
-        super(requestBuffers,responseBuffers,endp);
-        _asyncEndp=(AsyncEndPoint)endp;
-    }
-
-    protected void reset() throws IOException
-    {
-        _requestComplete = false;
-        super.reset();
-    }
-
-    public Connection handle() throws IOException
-    {
-        Connection connection = this;
-        boolean progress=true;
-
-        try
-        {
-            boolean failed = false;
-
-            // While we are making progress and have not changed connection
-            while (progress && connection==this)
-            {
-                LOG.debug("while open={} more={} progress={}",_endp.isOpen(),_parser.isMoreInBuffer(),progress);
-
-                progress=false;
-                HttpExchange exchange=_exchange;
-
-                LOG.debug("exchange {} on {}",exchange,this);
-
-                try
-                {
-                    // Should we commit the request?
-                    if (!_generator.isCommitted() && exchange!=null && exchange.getStatus() == HttpExchange.STATUS_WAITING_FOR_COMMIT)
-                    {
-                        LOG.debug("commit {}",exchange);
-                        progress=true;
-                        commitRequest();
-                    }
-
-                    // Generate output
-                    if (_generator.isCommitted() && !_generator.isComplete())
-                    {
-                        if (_generator.flushBuffer()>0)
-                        {
-                            LOG.debug("flushed");
-                            progress=true;
-                        }
-
-                        // Is there more content to send or should we complete the generator
-                        if (_generator.isState(AbstractGenerator.STATE_CONTENT))
-                        {
-                            // Look for more content to send.
-                            if (_requestContentChunk==null)
-                                _requestContentChunk = exchange.getRequestContentChunk(null);
-
-                            if (_requestContentChunk==null)
-                            {
-                                LOG.debug("complete {}",exchange);
-                                progress=true;
-                                _generator.complete();
-                                if (exchange.getStatus() < HttpExchange.STATUS_WAITING_FOR_RESPONSE)
-                                    exchange.setStatus(HttpExchange.STATUS_WAITING_FOR_RESPONSE);
-                            }
-                            else if (_generator.isEmpty())
-                            {
-                                LOG.debug("addChunk");
-                                progress=true;
-                                Buffer chunk=_requestContentChunk;
-                                _requestContentChunk=exchange.getRequestContentChunk(null);
-                                _generator.addContent(chunk,_requestContentChunk==null);
-                                if (_requestContentChunk==null)
-                                    exchange.setStatus(HttpExchange.STATUS_WAITING_FOR_RESPONSE);
-                            }
-                        }
-                    }
-
-                    // Signal request completion
-                    if (_generator.isComplete() && !_requestComplete)
-                    {
-                        LOG.debug("requestComplete {}",exchange);
-                        progress=true;
-                        _requestComplete = true;
-                        exchange.getEventListener().onRequestComplete();
-                    }
-
-                    // Read any input that is available
-                    if (!_parser.isComplete() && _parser.parseAvailable())
-                    {
-                        LOG.debug("parsed {}",exchange);
-                        progress=true;
-                    }
-
-                    // Flush output
-                    _endp.flush();
-
-                    // Has any IO been done by the endpoint itself since last loop
-                    if (_asyncEndp.hasProgressed())
-                    {
-                        LOG.debug("hasProgressed {}",exchange);
-                        progress=true;
-                    }
-                }
-                catch (Throwable e)
-                {
-                    LOG.debug("Failure on " + _exchange, e);
-
-                    failed = true;
-
-                    synchronized (this)
-                    {
-                        if (exchange != null)
-                        {
-                            // Cancelling the exchange causes an exception as we close the connection,
-                            // but we don't report it as it is normal cancelling operation
-                            if (exchange.getStatus() != HttpExchange.STATUS_CANCELLING &&
-                                    exchange.getStatus() != HttpExchange.STATUS_CANCELLED &&
-                                    !exchange.isDone())
-                            {
-                                if (exchange.setStatus(HttpExchange.STATUS_EXCEPTED))
-                                    exchange.getEventListener().onException(e);
-                            }
-                        }
-                        else
-                        {
-                            if (e instanceof IOException)
-                                throw (IOException)e;
-                            if (e instanceof Error)
-                                throw (Error)e;
-                            if (e instanceof RuntimeException)
-                                throw (RuntimeException)e;
-                            throw new RuntimeException(e);
-                        }
-                    }
-                }
-                finally
-                {
-                    LOG.debug("finally {} on {} progress={} {}",exchange,this,progress,_endp);
-
-                    boolean complete = failed || _generator.isComplete() && _parser.isComplete();
-
-                    if (complete)
-                    {
-                        boolean persistent = !failed && _parser.isPersistent() && _generator.isPersistent();
-                        _generator.setPersistent(persistent);
-                        reset();
-                        if (persistent)
-                            _endp.setMaxIdleTime((int)_destination.getHttpClient().getIdleTimeout());
-
-                        synchronized (this)
-                        {
-                            exchange=_exchange;
-                            _exchange = null;
-
-                            // Cancel the exchange
-                            if (exchange!=null)
-                            {
-                                exchange.cancelTimeout(_destination.getHttpClient());
-
-                                // TODO should we check the exchange is done?
-                            }
-
-                            // handle switched protocols
-                            if (_status==HttpStatus.SWITCHING_PROTOCOLS_101)
-                            {
-                                Connection switched=exchange.onSwitchProtocol(_endp);
-                                if (switched!=null)
-                                {
-                                    // switched protocol!
-                                    if (_pipeline!=null)
-                                    {
-                                        _destination.send(_pipeline);
-                                    }
-                                    _pipeline = null;
-
-                                    connection=switched;
-                                }
-                            }
-
-                            // handle pipelined requests
-                            if (_pipeline!=null)
-                            {
-                                if (!persistent || connection!=this)
-                                    _destination.send(_pipeline);
-                                else
-                                    _exchange=_pipeline;
-                                _pipeline=null;
-                            }
-
-                            if (_exchange==null && !isReserved())  // TODO how do we return switched connections?
-                                _destination.returnConnection(this, !persistent);
-                        }
-
-                    }
-                }
-            }
-        }
-        finally
-        {
-            _parser.returnBuffers();
-            _generator.returnBuffers();
-            LOG.debug("unhandle {} on {}",_exchange,_endp);
-        }
-
-        return connection;
-    }
-
-    public void onInputShutdown() throws IOException
-    {
-        if (_generator.isIdle())
-            _endp.shutdownOutput();
-    }
-
-    @Override
-    public boolean send(HttpExchange ex) throws IOException
-    {
-        boolean sent=super.send(ex);
-        if (sent)
-            _asyncEndp.asyncDispatch();
-        return sent;
-    }
-}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/AuthenticationProtocolHandler.java b/jetty-client/src/main/java/org/eclipse/jetty/client/AuthenticationProtocolHandler.java
new file mode 100644
index 0000000..9e7f684
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/AuthenticationProtocolHandler.java
@@ -0,0 +1,195 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.eclipse.jetty.client.api.Authentication;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.client.api.Result;
+import org.eclipse.jetty.client.util.BufferingResponseListener;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public abstract class AuthenticationProtocolHandler implements ProtocolHandler
+{
+    public static final int DEFAULT_MAX_CONTENT_LENGTH = 4096;
+    public static final Logger LOG = Log.getLogger(AuthenticationProtocolHandler.class);
+    private static final Pattern AUTHENTICATE_PATTERN = Pattern.compile("([^\\s]+)\\s+realm=\"([^\"]+)\"(.*)", Pattern.CASE_INSENSITIVE);
+    private static final String AUTHENTICATION_ATTRIBUTE = AuthenticationProtocolHandler.class.getName() + ".authentication";
+
+    private final HttpClient client;
+    private final int maxContentLength;
+    private final ResponseNotifier notifier;
+
+    protected AuthenticationProtocolHandler(HttpClient client, int maxContentLength)
+    {
+        this.client = client;
+        this.maxContentLength = maxContentLength;
+        this.notifier = new ResponseNotifier();
+    }
+
+    protected HttpClient getHttpClient()
+    {
+        return client;
+    }
+
+    protected abstract HttpHeader getAuthenticateHeader();
+
+    protected abstract HttpHeader getAuthorizationHeader();
+
+    protected abstract URI getAuthenticationURI(Request request);
+
+    @Override
+    public Response.Listener getResponseListener()
+    {
+        // Return new instances every time to keep track of the response content
+        return new AuthenticationListener();
+    }
+
+    private class AuthenticationListener extends BufferingResponseListener
+    {
+        private AuthenticationListener()
+        {
+            super(maxContentLength);
+        }
+
+        @Override
+        public void onComplete(Result result)
+        {
+            HttpRequest request = (HttpRequest)result.getRequest();
+            ContentResponse response = new HttpContentResponse(result.getResponse(), getContent(), getMediaType(), getEncoding());
+            if (result.isFailed())
+            {
+                Throwable failure = result.getFailure();
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Authentication challenge failed {}", failure);
+                forwardFailureComplete(request, result.getRequestFailure(), response, result.getResponseFailure());
+                return;
+            }
+
+            HttpConversation conversation = request.getConversation();
+            if (conversation.getAttribute(AUTHENTICATION_ATTRIBUTE) != null)
+            {
+                // We have already tried to authenticate, but we failed again
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Bad credentials for {}", request);
+                forwardSuccessComplete(request, response);
+                return;
+            }
+
+            HttpHeader header = getAuthenticateHeader();
+            List<Authentication.HeaderInfo> headerInfos = parseAuthenticateHeader(response, header);
+            if (headerInfos.isEmpty())
+            {
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Authentication challenge without {} header", header);
+                forwardFailureComplete(request, null, response, new HttpResponseException("HTTP protocol violation: Authentication challenge without " + header + " header", response));
+                return;
+            }
+
+            Authentication authentication = null;
+            Authentication.HeaderInfo headerInfo = null;
+            URI uri = getAuthenticationURI(request);
+            if (uri != null)
+            {
+                for (Authentication.HeaderInfo element : headerInfos)
+                {
+                    authentication = client.getAuthenticationStore().findAuthentication(element.getType(), uri, element.getRealm());
+                    if (authentication != null)
+                    {
+                        headerInfo = element;
+                        break;
+                    }
+                }
+            }
+            if (authentication == null)
+            {
+                if (LOG.isDebugEnabled())
+                    LOG.debug("No authentication available for {}", request);
+                forwardSuccessComplete(request, response);
+                return;
+            }
+
+            final Authentication.Result authnResult = authentication.authenticate(request, response, headerInfo, conversation);
+            if (LOG.isDebugEnabled())
+                LOG.debug("Authentication result {}", authnResult);
+            if (authnResult == null)
+            {
+                forwardSuccessComplete(request, response);
+                return;
+            }
+
+            conversation.setAttribute(AUTHENTICATION_ATTRIBUTE, true);
+
+            Request newRequest = client.copyRequest(request, request.getURI());
+            authnResult.apply(newRequest);
+            newRequest.onResponseSuccess(new Response.SuccessListener()
+            {
+                @Override
+                public void onSuccess(Response response)
+                {
+                    client.getAuthenticationStore().addAuthenticationResult(authnResult);
+                }
+            }).send(null);
+        }
+
+        private void forwardSuccessComplete(HttpRequest request, Response response)
+        {
+            HttpConversation conversation = request.getConversation();
+            conversation.updateResponseListeners(null);
+            notifier.forwardSuccessComplete(conversation.getResponseListeners(), request, response);
+        }
+
+        private void forwardFailureComplete(HttpRequest request, Throwable requestFailure, Response response, Throwable responseFailure)
+        {
+            HttpConversation conversation = request.getConversation();
+            conversation.updateResponseListeners(null);
+            notifier.forwardFailureComplete(conversation.getResponseListeners(), request, requestFailure, response, responseFailure);
+        }
+
+        private List<Authentication.HeaderInfo> parseAuthenticateHeader(Response response, HttpHeader header)
+        {
+            // TODO: these should be ordered by strength
+            List<Authentication.HeaderInfo> result = new ArrayList<>();
+            List<String> values = Collections.list(response.getHeaders().getValues(header.asString()));
+            for (String value : values)
+            {
+                Matcher matcher = AUTHENTICATE_PATTERN.matcher(value);
+                if (matcher.matches())
+                {
+                    String type = matcher.group(1);
+                    String realm = matcher.group(2);
+                    String params = matcher.group(3);
+                    Authentication.HeaderInfo headerInfo = new Authentication.HeaderInfo(type, realm, params, getAuthorizationHeader());
+                    result.add(headerInfo);
+                }
+            }
+            return result;
+        }
+    }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/BlockingHttpConnection.java b/jetty-client/src/main/java/org/eclipse/jetty/client/BlockingHttpConnection.java
deleted file mode 100644
index 3b91267..0000000
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/BlockingHttpConnection.java
+++ /dev/null
@@ -1,314 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client;
-
-import java.io.IOException;
-import java.io.InterruptedIOException;
-
-import org.eclipse.jetty.http.AbstractGenerator;
-import org.eclipse.jetty.http.HttpStatus;
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.Buffers;
-import org.eclipse.jetty.io.Connection;
-import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-
-
-/* ------------------------------------------------------------ */
-/** Blocking HTTP Connection
- */
-public class BlockingHttpConnection extends AbstractHttpConnection
-{
-    private static final Logger LOG = Log.getLogger(BlockingHttpConnection.class);
-
-    private boolean _requestComplete;
-    private Buffer _requestContentChunk;
-    private boolean _expired=false;
-
-    BlockingHttpConnection(Buffers requestBuffers, Buffers responseBuffers, EndPoint endPoint)
-    {
-        super(requestBuffers, responseBuffers, endPoint);
-    }
-
-    protected void reset() throws IOException
-    {
-        _requestComplete = false;
-        _expired = false;
-        super.reset();
-    }
-    
-    
-    @Override
-    protected void exchangeExpired(HttpExchange exchange)
-    {
-        synchronized (this)
-        {
-           super.exchangeExpired(exchange);
-           _expired = true;
-           this.notifyAll();
-        }
-    }
-    
-    
-
-    @Override
-    public void onIdleExpired(long idleForMs)
-    {
-        try
-        {
-            LOG.debug("onIdleExpired {}ms {} {}",idleForMs,this,_endp);
-            _expired = true;
-            _endp.close();
-        }
-        catch(IOException e)
-        {
-            LOG.ignore(e);
-
-            try
-            {
-                _endp.close();
-            }
-            catch(IOException e2)
-            {
-                LOG.ignore(e2);
-            }
-        }
-
-        synchronized(this)
-        {
-            this.notifyAll();
-        }
-    }
-
-    @Override
-    public Connection handle() throws IOException
-    {
-        Connection connection = this;
-
-        try
-        {
-            boolean failed = false;
-
-
-            // While we are making progress and have not changed connection
-            while (_endp.isOpen() && connection==this)
-            {
-                LOG.debug("open={} more={}",_endp.isOpen(),_parser.isMoreInBuffer());
-
-                HttpExchange exchange;
-                synchronized (this)
-                {
-                    exchange=_exchange;
-                    while (exchange == null)
-                    {
-                        try
-                        {
-                            this.wait();
-                            exchange=_exchange;
-                            if (_expired)
-                            {
-                                failed = true;
-                                throw new InterruptedException();
-                            }
-
-                        }
-                        catch (InterruptedException e)
-                        {
-                            throw new InterruptedIOException();
-                        }
-                    }
-                }
-                LOG.debug("exchange {}",exchange);
-
-                try
-                {
-                    // Should we commit the request?
-                    if (!_generator.isCommitted() && exchange!=null && exchange.getStatus() == HttpExchange.STATUS_WAITING_FOR_COMMIT)
-                    {
-                        LOG.debug("commit");
-                        commitRequest();
-                    }
-
-                    // Generate output
-                    while (_generator.isCommitted() && !_generator.isComplete())
-                    {
-                        if (_generator.flushBuffer()>0)
-                        {
-                            LOG.debug("flushed");
-                        }
-
-                        // Is there more content to send or should we complete the generator
-                        if (_generator.isState(AbstractGenerator.STATE_CONTENT))
-                        {
-                            // Look for more content to send.
-                            if (_requestContentChunk==null)
-                                _requestContentChunk = exchange.getRequestContentChunk(null);
-
-                            if (_requestContentChunk==null)
-                            {
-                                LOG.debug("complete");
-                                _generator.complete();
-                            }
-                            else if (_generator.isEmpty())
-                            {
-                                LOG.debug("addChunk");
-                                Buffer chunk=_requestContentChunk;
-                                _requestContentChunk=exchange.getRequestContentChunk(null);
-                                _generator.addContent(chunk,_requestContentChunk==null);
-                                if (_requestContentChunk==null)
-                                    exchange.setStatus(HttpExchange.STATUS_WAITING_FOR_RESPONSE);
-                            }
-                        }
-                    }
-
-                    // Signal request completion
-                    if (_generator.isComplete() && !_requestComplete)
-                    {
-                        LOG.debug("requestComplete");
-                        _requestComplete = true;
-                        exchange.getEventListener().onRequestComplete();
-                    }
-
-                    // Read any input that is available
-                    if (!_parser.isComplete() && _parser.parseAvailable())
-                    {
-                        LOG.debug("parsed");
-                    }
-
-                    // Flush output
-                    _endp.flush();
-                }
-                catch (Throwable e)
-                {
-                    LOG.debug("Failure on " + _exchange, e);
-
-                    failed = true;
-
-                    synchronized (this)
-                    {
-                        if (exchange != null)
-                        {
-                            // Cancelling the exchange causes an exception as we close the connection,
-                            // but we don't report it as it is normal cancelling operation
-                            if (exchange.getStatus() != HttpExchange.STATUS_CANCELLING &&
-                                    exchange.getStatus() != HttpExchange.STATUS_CANCELLED &&
-                                    !exchange.isDone())
-                            {
-                                if(exchange.setStatus(HttpExchange.STATUS_EXCEPTED))
-                                    exchange.getEventListener().onException(e);
-                            }
-                        }
-                        else
-                        {
-                            if (e instanceof IOException)
-                                throw (IOException)e;
-                            if (e instanceof Error)
-                                throw (Error)e;
-                            if (e instanceof RuntimeException)
-                                throw (RuntimeException)e;
-                            throw new RuntimeException(e);
-                        }
-                    }
-                }
-                finally
-                {
-                    LOG.debug("{} {}",_generator, _parser);
-                    LOG.debug("{}",_endp);
-
-                    boolean complete = failed || _generator.isComplete() && _parser.isComplete();
-
-                    if (complete)
-                    {
-                        boolean persistent = !failed && _parser.isPersistent() && _generator.isPersistent();
-                        _generator.setPersistent(persistent);
-                        reset();
-                        if (persistent)
-                            _endp.setMaxIdleTime((int)_destination.getHttpClient().getIdleTimeout());
-
-                        synchronized (this)
-                        {
-                            exchange=_exchange;
-                            _exchange = null;
-
-                            // Cancel the exchange
-                            if (exchange!=null)
-                            {
-                                exchange.cancelTimeout(_destination.getHttpClient());
-
-                                // TODO should we check the exchange is done?
-                            }
-
-                            // handle switched protocols
-                            if (_status==HttpStatus.SWITCHING_PROTOCOLS_101)
-                            {
-                                Connection switched=exchange.onSwitchProtocol(_endp);
-                                if (switched!=null)
-                                    connection=switched;
-                                {
-                                    // switched protocol!
-                                    _pipeline = null;
-                                    if (_pipeline!=null)
-                                        _destination.send(_pipeline);
-                                    _pipeline = null;
-
-                                    connection=switched;
-                                }
-                            }
-
-                            // handle pipelined requests
-                            if (_pipeline!=null)
-                            {
-                                if (!persistent || connection!=this)
-                                    _destination.send(_pipeline);
-                                else
-                                    _exchange=_pipeline;
-                                _pipeline=null;
-                            }
-
-                            if (_exchange==null && !isReserved())  // TODO how do we return switched connections?
-                                _destination.returnConnection(this, !persistent);
-                        }
-                    }
-                }
-            }
-        }
-        finally
-        {
-            _parser.returnBuffers();
-            _generator.returnBuffers();
-        }
-
-        return connection;
-    }
-
-    @Override
-    public boolean send(HttpExchange ex) throws IOException
-    {
-        boolean sent=super.send(ex);
-        if (sent)
-        {
-            synchronized (this)
-            {
-                notifyAll();
-            }
-        }
-        return sent;
-    }
-}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/CachedExchange.java b/jetty-client/src/main/java/org/eclipse/jetty/client/CachedExchange.java
deleted file mode 100644
index cb0c798..0000000
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/CachedExchange.java
+++ /dev/null
@@ -1,75 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client;
-
-import java.io.IOException;
-
-import org.eclipse.jetty.http.HttpFields;
-import org.eclipse.jetty.io.Buffer;
-
-/**
- * An exchange that retains response status and response headers for later use.
- */
-public class CachedExchange extends HttpExchange
-{
-    private final HttpFields _responseFields;
-    private volatile int _responseStatus;
-
-    /**
-     * Creates a new CachedExchange.
-     *
-     * @param cacheHeaders true to cache response headers, false to not cache them
-     */
-    public CachedExchange(boolean cacheHeaders)
-    {
-        _responseFields = cacheHeaders ? new HttpFields() : null;
-    }
-
-    public synchronized int getResponseStatus()
-    {
-        if (getStatus() < HttpExchange.STATUS_PARSING_HEADERS)
-            throw new IllegalStateException("Response not received yet");
-        return _responseStatus;
-    }
-
-    public synchronized HttpFields getResponseFields()
-    {
-        if (getStatus() < HttpExchange.STATUS_PARSING_CONTENT)
-            throw new IllegalStateException("Headers not completely received yet");
-        return _responseFields;
-    }
-
-    @Override
-    protected synchronized void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException
-    {
-        _responseStatus = status;
-        super.onResponseStatus(version, status, reason);
-    }
-
-    @Override
-    protected synchronized void onResponseHeader(Buffer name, Buffer value) throws IOException
-    {
-        if (_responseFields != null)
-        {
-            _responseFields.add(name, value.asImmutableBuffer());
-        }
-        
-        super.onResponseHeader(name, value);
-    }
-}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/ConnectionPool.java b/jetty-client/src/main/java/org/eclipse/jetty/client/ConnectionPool.java
new file mode 100644
index 0000000..456dc5e
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/ConnectionPool.java
@@ -0,0 +1,410 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.BlockingDeque;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingDeque;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.locks.ReentrantLock;
+
+import org.eclipse.jetty.client.api.Connection;
+import org.eclipse.jetty.client.api.Destination;
+import org.eclipse.jetty.util.BlockingArrayQueue;
+import org.eclipse.jetty.util.Promise;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.component.Dumpable;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.Sweeper;
+
+public class ConnectionPool implements Closeable, Dumpable, Sweeper.Sweepable
+{
+    protected static final Logger LOG = Log.getLogger(ConnectionPool.class);
+
+    private final AtomicInteger connectionCount = new AtomicInteger();
+    private final ReentrantLock lock = new ReentrantLock();
+    private final Destination destination;
+    private final int maxConnections;
+    private final Promise<Connection> requester;
+    private final BlockingDeque<Connection> idleConnections;
+    private final BlockingQueue<Connection> activeConnections;
+
+    public ConnectionPool(Destination destination, int maxConnections, Promise<Connection> requester)
+    {
+        this.destination = destination;
+        this.maxConnections = maxConnections;
+        this.requester = requester;
+        this.idleConnections = new LinkedBlockingDeque<>(maxConnections);
+        this.activeConnections = new BlockingArrayQueue<>(maxConnections);
+    }
+
+    public int getConnectionCount()
+    {
+        return connectionCount.get();
+    }
+
+    public BlockingQueue<Connection> getIdleConnections()
+    {
+        return idleConnections;
+    }
+
+    public BlockingQueue<Connection> getActiveConnections()
+    {
+        return activeConnections;
+    }
+
+    public Connection acquire()
+    {
+        Connection connection = activateIdle();
+        if (connection == null)
+            connection = tryCreate();
+        return connection;
+    }
+
+    private Connection tryCreate()
+    {
+        while (true)
+        {
+            int current = getConnectionCount();
+            final int next = current + 1;
+
+            if (next > maxConnections)
+            {
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Max connections {}/{} reached", current, maxConnections);
+                // Try again the idle connections
+                return activateIdle();
+            }
+
+            if (connectionCount.compareAndSet(current, next))
+            {
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Connection {}/{} creation", next, maxConnections);
+
+                destination.newConnection(new Promise<Connection>()
+                {
+                    @Override
+                    public void succeeded(Connection connection)
+                    {
+                        if (LOG.isDebugEnabled())
+                            LOG.debug("Connection {}/{} creation succeeded {}", next, maxConnections, connection);
+
+                        idleCreated(connection);
+
+                        requester.succeeded(connection);
+                    }
+
+                    @Override
+                    public void failed(Throwable x)
+                    {
+                        if (LOG.isDebugEnabled())
+                            LOG.debug("Connection " + next + "/" + maxConnections + " creation failed", x);
+
+                        connectionCount.decrementAndGet();
+
+                        requester.failed(x);
+                    }
+                });
+
+                // Try again the idle connections
+                return activateIdle();
+            }
+        }
+    }
+
+    protected void idleCreated(Connection connection)
+    {
+        boolean idle;
+        final ReentrantLock lock = this.lock;
+        lock.lock();
+        try
+        {
+            // Use "cold" new connections as last.
+            idle = idleConnections.offerLast(connection);
+        }
+        finally
+        {
+            lock.unlock();
+        }
+
+        idle(connection, idle);
+    }
+
+    private Connection activateIdle()
+    {
+        boolean acquired;
+        Connection connection;
+        final ReentrantLock lock = this.lock;
+        lock.lock();
+        try
+        {
+            connection = idleConnections.pollFirst();
+            if (connection == null)
+                return null;
+            acquired = activeConnections.offer(connection);
+        }
+        finally
+        {
+            lock.unlock();
+        }
+
+        if (acquired)
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("Connection active {}", connection);
+            acquired(connection);
+            return connection;
+        }
+        else
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("Connection active overflow {}", connection);
+            connection.close();
+            return null;
+        }
+    }
+
+    protected void acquired(Connection connection)
+    {
+    }
+
+    public boolean release(Connection connection)
+    {
+        boolean idle;
+        final ReentrantLock lock = this.lock;
+        lock.lock();
+        try
+        {
+            if (!activeConnections.remove(connection))
+                return false;
+            // Make sure we use "hot" connections first.
+            idle = idleConnections.offerFirst(connection);
+        }
+        finally
+        {
+            lock.unlock();
+        }
+
+        released(connection);
+        return idle(connection, idle);
+    }
+
+    protected boolean idle(Connection connection, boolean idle)
+    {
+        if (idle)
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("Connection idle {}", connection);
+            return true;
+        }
+        else
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("Connection idle overflow {}", connection);
+            connection.close();
+            return false;
+        }
+    }
+
+    protected void released(Connection connection)
+    {
+    }
+
+    public boolean remove(Connection connection)
+    {
+        boolean activeRemoved;
+        boolean idleRemoved;
+        final ReentrantLock lock = this.lock;
+        lock.lock();
+        try
+        {
+            activeRemoved = activeConnections.remove(connection);
+            idleRemoved = idleConnections.remove(connection);
+        }
+        finally
+        {
+            lock.unlock();
+        }
+
+        if (activeRemoved)
+            released(connection);
+        boolean removed = activeRemoved || idleRemoved;
+        if (removed)
+        {
+            int pooled = connectionCount.decrementAndGet();
+            if (LOG.isDebugEnabled())
+                LOG.debug("Connection removed {} - pooled: {}", connection, pooled);
+        }
+        return removed;
+    }
+
+    public boolean isActive(Connection connection)
+    {
+        final ReentrantLock lock = this.lock;
+        lock.lock();
+        try
+        {
+            return activeConnections.contains(connection);
+        }
+        finally
+        {
+            lock.unlock();
+        }
+    }
+
+    public boolean isIdle(Connection connection)
+    {
+        final ReentrantLock lock = this.lock;
+        lock.lock();
+        try
+        {
+            return idleConnections.contains(connection);
+        }
+        finally
+        {
+            lock.unlock();
+        }
+    }
+
+    public boolean isEmpty()
+    {
+        return connectionCount.get() == 0;
+    }
+
+    public void close()
+    {
+        List<Connection> idles = new ArrayList<>();
+        List<Connection> actives = new ArrayList<>();
+        final ReentrantLock lock = this.lock;
+        lock.lock();
+        try
+        {
+            idles.addAll(idleConnections);
+            idleConnections.clear();
+            actives.addAll(activeConnections);
+            activeConnections.clear();
+        }
+        finally
+        {
+            lock.unlock();
+        }
+
+        connectionCount.set(0);
+
+        for (Connection connection : idles)
+            connection.close();
+
+        // A bit drastic, but we cannot wait for all requests to complete
+        for (Connection connection : actives)
+            connection.close();
+    }
+
+    @Override
+    public String dump()
+    {
+        return ContainerLifeCycle.dump(this);
+    }
+
+    @Override
+    public void dump(Appendable out, String indent) throws IOException
+    {
+        List<Connection> actives = new ArrayList<>();
+        List<Connection> idles = new ArrayList<>();
+        final ReentrantLock lock = this.lock;
+        lock.lock();
+        try
+        {
+            actives.addAll(activeConnections);
+            idles.addAll(idleConnections);
+        }
+        finally
+        {
+            lock.unlock();
+        }
+
+        ContainerLifeCycle.dumpObject(out, this);
+        ContainerLifeCycle.dump(out, indent, actives, idles);
+    }
+
+    @Override
+    public boolean sweep()
+    {
+        List<Sweeper.Sweepable> toSweep = new ArrayList<>();
+        final ReentrantLock lock = this.lock;
+        lock.lock();
+        try
+        {
+            for (Connection connection : getActiveConnections())
+            {
+                if (connection instanceof Sweeper.Sweepable)
+                    toSweep.add(((Sweeper.Sweepable)connection));
+            }
+        }
+        finally
+        {
+            lock.unlock();
+        }
+
+        for (Sweeper.Sweepable candidate : toSweep)
+        {
+            if (candidate.sweep())
+            {
+                boolean removed = getActiveConnections().remove(candidate);
+                LOG.warn("Connection swept: {}{}{} from active connections{}{}",
+                        candidate,
+                        System.lineSeparator(),
+                        removed ? "Removed" : "Not removed",
+                        System.lineSeparator(),
+                        dump());
+            }
+        }
+
+        return false;
+    }
+
+    @Override
+    public String toString()
+    {
+        int activeSize;
+        int idleSize;
+        final ReentrantLock lock = this.lock;
+        lock.lock();
+        try
+        {
+            activeSize = activeConnections.size();
+            idleSize = idleConnections.size();
+        }
+        finally
+        {
+            lock.unlock();
+        }
+
+        return String.format("%s[c=%d/%d,a=%d,i=%d]",
+                getClass().getSimpleName(),
+                connectionCount.get(),
+                maxConnections,
+                activeSize,
+                idleSize);
+    }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/ContentDecoder.java b/jetty-client/src/main/java/org/eclipse/jetty/client/ContentDecoder.java
new file mode 100644
index 0000000..7b230f6
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/ContentDecoder.java
@@ -0,0 +1,86 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.nio.ByteBuffer;
+
+/**
+ * {@link ContentDecoder} decodes content bytes of a response.
+ *
+ * @see Factory
+ */
+public interface ContentDecoder
+{
+    /**
+     * <p>Decodes the bytes in the given {@code buffer} and returns decoded bytes, if any.</p>
+     *
+     * @param buffer the buffer containing encoded bytes
+     * @return a buffer containing decoded bytes, if any
+     */
+    public abstract ByteBuffer decode(ByteBuffer buffer);
+
+    /**
+     * Factory for {@link ContentDecoder}s; subclasses must implement {@link #newContentDecoder()}.
+     * <p />
+     * {@link Factory} have an {@link #getEncoding() encoding}, which is the string used in
+     * {@code Accept-Encoding} request header and in {@code Content-Encoding} response headers.
+     * <p />
+     * {@link Factory} instances are configured in {@link HttpClient} via
+     * {@link HttpClient#getContentDecoderFactories()}.
+     */
+    public static abstract class Factory
+    {
+        private final String encoding;
+
+        protected Factory(String encoding)
+        {
+            this.encoding = encoding;
+        }
+
+        /**
+         * @return the type of the decoders created by this factory
+         */
+        public String getEncoding()
+        {
+            return encoding;
+        }
+
+        @Override
+        public boolean equals(Object obj)
+        {
+            if (this == obj) return true;
+            if (!(obj instanceof Factory)) return false;
+            Factory that = (Factory)obj;
+            return encoding.equals(that.encoding);
+        }
+
+        @Override
+        public int hashCode()
+        {
+            return encoding.hashCode();
+        }
+
+        /**
+         * Factory method for {@link ContentDecoder}s
+         *
+         * @return a new instance of a {@link ContentDecoder}
+         */
+        public abstract ContentDecoder newContentDecoder();
+    }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/ContentExchange.java b/jetty-client/src/main/java/org/eclipse/jetty/client/ContentExchange.java
deleted file mode 100644
index a86c688..0000000
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/ContentExchange.java
+++ /dev/null
@@ -1,135 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client;
-
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.UnsupportedEncodingException;
-
-import org.eclipse.jetty.http.HttpHeaders;
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.BufferUtil;
-import org.eclipse.jetty.util.StringUtil;
-
-/**
- * A exchange that retains response content for later use.
- */
-public class ContentExchange extends CachedExchange
-{
-    private int _bufferSize = 4096;
-    private String _encoding = "utf-8";
-    private ByteArrayOutputStream _responseContent;
-    private File _fileForUpload;
-
-    public ContentExchange()
-    {
-        super(false);
-    }
-
-    public ContentExchange(boolean cacheFields)
-    {
-        super(cacheFields);
-    }
-
-    public synchronized String getResponseContent() throws UnsupportedEncodingException
-    {
-        if (_responseContent != null)
-            return _responseContent.toString(_encoding);
-        return null;
-    }
-
-    public synchronized byte[] getResponseContentBytes()
-    {
-        if (_responseContent != null)
-            return _responseContent.toByteArray();
-        return null;
-    }
-
-    @Override
-    protected synchronized void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException
-    {
-        if (_responseContent!=null)
-            _responseContent.reset();
-        super.onResponseStatus(version,status,reason);
-    }
-
-    @Override
-    protected synchronized void onResponseHeader(Buffer name, Buffer value) throws IOException
-    {
-        super.onResponseHeader(name, value);
-        int header = HttpHeaders.CACHE.getOrdinal(name);
-        switch (header)
-        {
-            case HttpHeaders.CONTENT_LENGTH_ORDINAL:
-                _bufferSize = BufferUtil.toInt(value);
-                break;
-            case HttpHeaders.CONTENT_TYPE_ORDINAL:
-                String mime = StringUtil.asciiToLowerCase(value.toString());
-                int i = mime.indexOf("charset=");
-                if (i > 0)
-                {
-                    _encoding = mime.substring(i + 8);
-                    i = _encoding.indexOf(';');
-                    if (i > 0)
-                        _encoding = _encoding.substring(0, i);
-                }
-                break;
-        }
-    }
-
-    @Override
-    protected synchronized void onResponseContent(Buffer content) throws IOException
-    {
-        super.onResponseContent(content);
-        if (_responseContent == null)
-            _responseContent = new ByteArrayOutputStream(_bufferSize);
-        content.writeTo(_responseContent);
-    }
-
-    @Override
-    protected synchronized void onRetry() throws IOException
-    {
-        if (_fileForUpload != null)
-        {
-            setRequestContent(null);
-            setRequestContentSource(getInputStream());
-        }
-        else
-            super.onRetry();
-    }
-
-    private synchronized InputStream getInputStream() throws IOException
-    {
-        return new FileInputStream(_fileForUpload);
-    }
-
-    public synchronized File getFileForUpload()
-    {
-        return _fileForUpload;
-    }
-
-    public synchronized void setFileForUpload(File fileForUpload) throws IOException
-    {
-        this._fileForUpload = fileForUpload;
-        setRequestContentSource(getInputStream());
-    }
-}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/ContinueProtocolHandler.java b/jetty-client/src/main/java/org/eclipse/jetty/client/ContinueProtocolHandler.java
new file mode 100644
index 0000000..2120ac2
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/ContinueProtocolHandler.java
@@ -0,0 +1,120 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.util.List;
+
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.client.api.Result;
+import org.eclipse.jetty.client.util.BufferingResponseListener;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpHeaderValue;
+
+public class ContinueProtocolHandler implements ProtocolHandler
+{
+    private static final String ATTRIBUTE = ContinueProtocolHandler.class.getName() + ".100continue";
+
+    private final HttpClient client;
+    private final ResponseNotifier notifier;
+
+    public ContinueProtocolHandler(HttpClient client)
+    {
+        this.client = client;
+        this.notifier = new ResponseNotifier();
+    }
+
+    @Override
+    public boolean accept(Request request, Response response)
+    {
+        boolean expect100 = request.getHeaders().contains(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString());
+        HttpConversation conversation = ((HttpRequest)request).getConversation();
+        boolean handled100 = conversation.getAttribute(ATTRIBUTE) != null;
+        return expect100 && !handled100;
+    }
+
+    @Override
+    public Response.Listener getResponseListener()
+    {
+        // Return new instances every time to keep track of the response content
+        return new ContinueListener();
+    }
+
+    protected class ContinueListener extends BufferingResponseListener
+    {
+        @Override
+        public void onSuccess(Response response)
+        {
+            // Handling of success must be done here and not from onComplete(),
+            // since the onComplete() is not invoked because the request is not completed yet.
+
+            HttpConversation conversation = ((HttpRequest)response.getRequest()).getConversation();
+            // Mark the 100 Continue response as handled
+            conversation.setAttribute(ATTRIBUTE, Boolean.TRUE);
+
+            // Reset the conversation listeners, since we are going to receive another response code
+            conversation.updateResponseListeners(null);
+
+            HttpExchange exchange = conversation.getExchanges().peekLast();
+            assert exchange.getResponse() == response;
+            switch (response.getStatus())
+            {
+                case 100:
+                {
+                    // All good, continue
+                    exchange.resetResponse();
+                    exchange.proceed(null);
+                    break;
+                }
+                default:
+                {
+                    // Server either does not support 100 Continue,
+                    // or it does and wants to refuse the request content,
+                    // or we got some other HTTP status code like a redirect.
+                    List<Response.ResponseListener> listeners = exchange.getResponseListeners();
+                    HttpContentResponse contentResponse = new HttpContentResponse(response, getContent(), getMediaType(), getEncoding());
+                    notifier.forwardSuccess(listeners, contentResponse);
+                    exchange.proceed(new HttpRequestException("Expectation failed", exchange.getRequest()));
+                    break;
+                }
+            }
+        }
+
+        @Override
+        public void onFailure(Response response, Throwable failure)
+        {
+            HttpConversation conversation = ((HttpRequest)response.getRequest()).getConversation();
+            // Mark the 100 Continue response as handled
+            conversation.setAttribute(ATTRIBUTE, Boolean.TRUE);
+            // Reset the conversation listeners to allow the conversation to be completed
+            conversation.updateResponseListeners(null);
+
+            HttpExchange exchange = conversation.getExchanges().peekLast();
+            assert exchange.getResponse() == response;
+            List<Response.ResponseListener> listeners = exchange.getResponseListeners();
+            HttpContentResponse contentResponse = new HttpContentResponse(response, getContent(), getMediaType(), getEncoding());
+            notifier.forwardFailureComplete(listeners, exchange.getRequest(), exchange.getRequestFailure(), contentResponse, failure);
+        }
+
+        @Override
+        public void onComplete(Result result)
+        {
+        }
+    }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/GZIPContentDecoder.java b/jetty-client/src/main/java/org/eclipse/jetty/client/GZIPContentDecoder.java
new file mode 100644
index 0000000..dedd0cb
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/GZIPContentDecoder.java
@@ -0,0 +1,359 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.zip.DataFormatException;
+import java.util.zip.Inflater;
+import java.util.zip.ZipException;
+
+import org.eclipse.jetty.util.BufferUtil;
+
+/**
+ * {@link ContentDecoder} for the "gzip" encoding.
+ */
+public class GZIPContentDecoder implements ContentDecoder
+{
+    private final Inflater inflater = new Inflater(true);
+    private final byte[] bytes;
+    private byte[] output;
+    private State state;
+    private int size;
+    private int value;
+    private byte flags;
+
+    public GZIPContentDecoder()
+    {
+        this(2048);
+    }
+
+    public GZIPContentDecoder(int bufferSize)
+    {
+        this.bytes = new byte[bufferSize];
+        reset();
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p>If the decoding did not produce any output, for example because it consumed gzip header
+     * or trailer bytes, it returns a buffer with zero capacity.</p>
+     * <p>This method never returns null.</p>
+     * <p>The given {@code buffer}'s position will be modified to reflect the bytes consumed during
+     * the decoding.</p>
+     * <p>The decoding may be finished without consuming the buffer completely if the buffer contains
+     * gzip bytes plus other bytes (either plain or gzipped).</p>
+     */
+    @Override
+    public ByteBuffer decode(ByteBuffer buffer)
+    {
+        try
+        {
+            while (buffer.hasRemaining())
+            {
+                byte currByte = buffer.get();
+                switch (state)
+                {
+                    case INITIAL:
+                    {
+                        buffer.position(buffer.position() - 1);
+                        state = State.ID;
+                        break;
+                    }
+                    case ID:
+                    {
+                        value += (currByte & 0xFF) << 8 * size;
+                        ++size;
+                        if (size == 2)
+                        {
+                            if (value != 0x8B1F)
+                                throw new ZipException("Invalid gzip bytes");
+                            state = State.CM;
+                        }
+                        break;
+                    }
+                    case CM:
+                    {
+                        if ((currByte & 0xFF) != 0x08)
+                            throw new ZipException("Invalid gzip compression method");
+                        state = State.FLG;
+                        break;
+                    }
+                    case FLG:
+                    {
+                        flags = currByte;
+                        state = State.MTIME;
+                        size = 0;
+                        value = 0;
+                        break;
+                    }
+                    case MTIME:
+                    {
+                        // Skip the 4 MTIME bytes
+                        ++size;
+                        if (size == 4)
+                            state = State.XFL;
+                        break;
+                    }
+                    case XFL:
+                    {
+                        // Skip XFL
+                        state = State.OS;
+                        break;
+                    }
+                    case OS:
+                    {
+                        // Skip OS
+                        state = State.FLAGS;
+                        break;
+                    }
+                    case FLAGS:
+                    {
+                        buffer.position(buffer.position() - 1);
+                        if ((flags & 0x04) == 0x04)
+                        {
+                            state = State.EXTRA_LENGTH;
+                            size = 0;
+                            value = 0;
+                        }
+                        else if ((flags & 0x08) == 0x08)
+                            state = State.NAME;
+                        else if ((flags & 0x10) == 0x10)
+                            state = State.COMMENT;
+                        else if ((flags & 0x2) == 0x2)
+                        {
+                            state = State.HCRC;
+                            size = 0;
+                            value = 0;
+                        }
+                        else
+                            state = State.DATA;
+                        break;
+                    }
+                    case EXTRA_LENGTH:
+                    {
+                        value += (currByte & 0xFF) << 8 * size;
+                        ++size;
+                        if (size == 2)
+                            state = State.EXTRA;
+                        break;
+                    }
+                    case EXTRA:
+                    {
+                        // Skip EXTRA bytes
+                        --value;
+                        if (value == 0)
+                        {
+                            // Clear the EXTRA flag and loop on the flags
+                            flags &= ~0x04;
+                            state = State.FLAGS;
+                        }
+                        break;
+                    }
+                    case NAME:
+                    {
+                        // Skip NAME bytes
+                        if (currByte == 0)
+                        {
+                            // Clear the NAME flag and loop on the flags
+                            flags &= ~0x08;
+                            state = State.FLAGS;
+                        }
+                        break;
+                    }
+                    case COMMENT:
+                    {
+                        // Skip COMMENT bytes
+                        if (currByte == 0)
+                        {
+                            // Clear the COMMENT flag and loop on the flags
+                            flags &= ~0x10;
+                            state = State.FLAGS;
+                        }
+                        break;
+                    }
+                    case HCRC:
+                    {
+                        // Skip HCRC
+                        ++size;
+                        if (size == 2)
+                        {
+                            // Clear the HCRC flag and loop on the flags
+                            flags &= ~0x02;
+                            state = State.FLAGS;
+                        }
+                        break;
+                    }
+                    case DATA:
+                    {
+                        buffer.position(buffer.position() - 1);
+                        while (true)
+                        {
+                            int decoded = inflate(bytes);
+                            if (decoded == 0)
+                            {
+                                if (inflater.needsInput())
+                                {
+                                    if (buffer.hasRemaining())
+                                    {
+                                        byte[] input = new byte[buffer.remaining()];
+                                        buffer.get(input);
+                                        inflater.setInput(input);
+                                    }
+                                    else
+                                    {
+                                        if (output != null)
+                                        {
+                                            ByteBuffer result = ByteBuffer.wrap(output);
+                                            output = null;
+                                            return result;
+                                        }
+                                        break;
+                                    }
+                                }
+                                else if (inflater.finished())
+                                {
+                                    int remaining = inflater.getRemaining();
+                                    buffer.position(buffer.limit() - remaining);
+                                    state = State.CRC;
+                                    size = 0;
+                                    value = 0;
+                                    break;
+                                }
+                                else
+                                {
+                                    throw new ZipException("Invalid inflater state");
+                                }
+                            }
+                            else
+                            {
+                                if (output == null)
+                                {
+                                    // Save the inflated bytes and loop to see if we have finished
+                                    output = Arrays.copyOf(bytes, decoded);
+                                }
+                                else
+                                {
+                                    // Accumulate inflated bytes and loop to see if we have finished
+                                    byte[] newOutput = Arrays.copyOf(output, output.length + decoded);
+                                    System.arraycopy(bytes, 0, newOutput, output.length, decoded);
+                                    output = newOutput;
+                                }
+                            }
+                        }
+                        break;
+                    }
+                    case CRC:
+                    {
+                        value += (currByte & 0xFF) << 8 * size;
+                        ++size;
+                        if (size == 4)
+                        {
+                            // From RFC 1952, compliant decoders need not to verify the CRC
+                            state = State.ISIZE;
+                            size = 0;
+                            value = 0;
+                        }
+                        break;
+                    }
+                    case ISIZE:
+                    {
+                        value += (currByte & 0xFF) << 8 * size;
+                        ++size;
+                        if (size == 4)
+                        {
+                            if (value != inflater.getBytesWritten())
+                                throw new ZipException("Invalid input size");
+
+                            ByteBuffer result = output == null ? BufferUtil.EMPTY_BUFFER : ByteBuffer.wrap(output);
+                            reset();
+                            return result;
+                        }
+                        break;
+                    }
+                    default:
+                        throw new ZipException();
+                }
+            }
+            return BufferUtil.EMPTY_BUFFER;
+        }
+        catch (ZipException x)
+        {
+            throw new RuntimeException(x);
+        }
+    }
+
+    private int inflate(byte[] bytes) throws ZipException
+    {
+        try
+        {
+            return inflater.inflate(bytes);
+        }
+        catch (DataFormatException x)
+        {
+            throw new ZipException(x.getMessage());
+        }
+    }
+
+    private void reset()
+    {
+        inflater.reset();
+        Arrays.fill(bytes, (byte)0);
+        output = null;
+        state = State.INITIAL;
+        size = 0;
+        value = 0;
+        flags = 0;
+    }
+
+    protected boolean isFinished()
+    {
+        return state == State.INITIAL;
+    }
+
+    /**
+     * Specialized {@link ContentDecoder.Factory} for the "gzip" encoding.
+     */
+    public static class Factory extends ContentDecoder.Factory
+    {
+        private final int bufferSize;
+
+        public Factory()
+        {
+            this(2048);
+        }
+
+        public Factory(int bufferSize)
+        {
+            super("gzip");
+            this.bufferSize = bufferSize;
+        }
+
+        @Override
+        public ContentDecoder newContentDecoder()
+        {
+            return new GZIPContentDecoder(bufferSize);
+        }
+    }
+
+    private enum State
+    {
+        INITIAL, ID, CM, FLG, MTIME, XFL, OS, FLAGS, EXTRA_LENGTH, EXTRA, NAME, COMMENT, HCRC, DATA, CRC, ISIZE
+    }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpAuthenticationStore.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpAuthenticationStore.java
new file mode 100644
index 0000000..bd4eb16
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpAuthenticationStore.java
@@ -0,0 +1,93 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.net.URI;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.eclipse.jetty.client.api.Authentication;
+import org.eclipse.jetty.client.api.AuthenticationStore;
+
+public class HttpAuthenticationStore implements AuthenticationStore
+{
+    private final List<Authentication> authentications = new CopyOnWriteArrayList<>();
+    private final Map<URI, Authentication.Result> results = new ConcurrentHashMap<>();
+
+    @Override
+    public void addAuthentication(Authentication authentication)
+    {
+        authentications.add(authentication);
+    }
+
+    @Override
+    public void removeAuthentication(Authentication authentication)
+    {
+        authentications.remove(authentication);
+    }
+
+    @Override
+    public void clearAuthentications()
+    {
+        authentications.clear();
+    }
+
+    @Override
+    public Authentication findAuthentication(String type, URI uri, String realm)
+    {
+        for (Authentication authentication : authentications)
+        {
+            if (authentication.matches(type, uri, realm))
+                return authentication;
+        }
+        return null;
+    }
+
+    @Override
+    public void addAuthenticationResult(Authentication.Result result)
+    {
+        results.put(result.getURI(), result);
+    }
+
+    @Override
+    public void removeAuthenticationResult(Authentication.Result result)
+    {
+        results.remove(result.getURI());
+    }
+
+    @Override
+    public void clearAuthenticationResults()
+    {
+        results.clear();
+    }
+
+    @Override
+    public Authentication.Result findAuthenticationResult(URI uri)
+    {
+        // TODO: I should match the longest URI
+        for (Map.Entry<URI, Authentication.Result> entry : results.entrySet())
+        {
+            if (uri.toString().startsWith(entry.getKey().toString()))
+                return entry.getValue();
+        }
+        return null;
+    }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpChannel.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpChannel.java
new file mode 100644
index 0000000..d493a9e
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpChannel.java
@@ -0,0 +1,142 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import org.eclipse.jetty.client.api.Result;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public abstract class HttpChannel
+{
+    protected static final Logger LOG = Log.getLogger(HttpChannel.class);
+
+    private final HttpDestination _destination;
+    private HttpExchange _exchange;
+
+    protected HttpChannel(HttpDestination destination)
+    {
+        this._destination = destination;
+    }
+
+    public HttpDestination getHttpDestination()
+    {
+        return _destination;
+    }
+
+    /**
+     * <p>Associates the given {@code exchange} to this channel in order to be sent over the network.</p>
+     * <p>If the association is successful, the exchange can be sent. Otherwise, the channel must be
+     * disposed because whoever terminated the exchange did not do it - it did not have the channel yet.</p>
+     *
+     * @param exchange the exchange to associate
+     * @return true if the association was successful, false otherwise
+     */
+    public boolean associate(HttpExchange exchange)
+    {
+        boolean result = false;
+        boolean abort = true;
+        synchronized (this)
+        {
+            if (_exchange == null)
+            {
+                abort = false;
+                result = exchange.associate(this);
+                if (result)
+                    _exchange = exchange;
+            }
+        }
+
+        if (abort)
+            exchange.getRequest().abort(new UnsupportedOperationException("Pipelined requests not supported"));
+
+        if (LOG.isDebugEnabled())
+            LOG.debug("{} associated {} to {}", exchange, result, this);
+
+        return result;
+    }
+
+    public boolean disassociate(HttpExchange exchange)
+    {
+        boolean result = false;
+        synchronized (this)
+        {
+            HttpExchange existing = _exchange;
+            _exchange = null;
+            if (existing == exchange)
+            {
+                existing.disassociate(this);
+                result = true;
+            }
+        }
+
+        if (LOG.isDebugEnabled())
+            LOG.debug("{} disassociated {} from {}", exchange, result, this);
+        return result;
+    }
+
+    public HttpExchange getHttpExchange()
+    {
+        synchronized (this)
+        {
+            return _exchange;
+        }
+    }
+
+    protected abstract HttpSender getHttpSender();
+
+    protected abstract HttpReceiver getHttpReceiver();
+
+    public abstract void send();
+
+    public abstract void release();
+
+    public void proceed(HttpExchange exchange, Throwable failure)
+    {
+        getHttpSender().proceed(exchange, failure);
+    }
+
+    public boolean abort(HttpExchange exchange, Throwable requestFailure, Throwable responseFailure)
+    {
+        boolean requestAborted = false;
+        if (requestFailure != null)
+            requestAborted = getHttpSender().abort(exchange, requestFailure);
+
+        boolean responseAborted = false;
+        if (responseFailure != null)
+            responseAborted = abortResponse(exchange, responseFailure);
+
+        return requestAborted || responseAborted;
+    }
+
+    public boolean abortResponse(HttpExchange exchange, Throwable failure)
+    {
+        return getHttpReceiver().abort(exchange, failure);
+    }
+
+    public void exchangeTerminated(HttpExchange exchange, Result result)
+    {
+        disassociate(exchange);
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s@%x(exchange=%s)", getClass().getSimpleName(), hashCode(), getHttpExchange());
+    }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java
index d850c1e..3ec0e02 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java
@@ -19,922 +19,1085 @@
 package org.eclipse.jetty.client;
 
 import java.io.IOException;
-import java.io.InputStream;
-import java.net.UnknownHostException;
+import java.net.CookieManager;
+import java.net.CookiePolicy;
+import java.net.CookieStore;
+import java.net.SocketAddress;
+import java.net.URI;
+import java.nio.channels.SocketChannel;
+import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Collections;
-import java.util.Enumeration;
-import java.util.LinkedList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
-
-import javax.net.ssl.SSLContext;
-
-import org.eclipse.jetty.client.security.Authentication;
-import org.eclipse.jetty.client.security.RealmResolver;
-import org.eclipse.jetty.client.security.SecurityListener;
-import org.eclipse.jetty.http.HttpBuffers;
-import org.eclipse.jetty.http.HttpBuffersImpl;
-import org.eclipse.jetty.http.HttpSchemes;
-import org.eclipse.jetty.io.Buffers;
-import org.eclipse.jetty.io.Buffers.Type;
-import org.eclipse.jetty.util.Attributes;
-import org.eclipse.jetty.util.AttributesMap;
-import org.eclipse.jetty.util.component.AggregateLifeCycle;
-import org.eclipse.jetty.util.component.Dumpable;
-import org.eclipse.jetty.util.component.LifeCycle;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.eclipse.jetty.client.api.AuthenticationStore;
+import org.eclipse.jetty.client.api.Connection;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Destination;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP;
+import org.eclipse.jetty.client.util.FormContentProvider;
+import org.eclipse.jetty.http.HttpField;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpScheme;
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.MappedByteBufferPool;
+import org.eclipse.jetty.util.Fields;
+import org.eclipse.jetty.util.Jetty;
+import org.eclipse.jetty.util.Promise;
+import org.eclipse.jetty.util.SocketAddressResolver;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
 import org.eclipse.jetty.util.ssl.SslContextFactory;
 import org.eclipse.jetty.util.thread.QueuedThreadPool;
-import org.eclipse.jetty.util.thread.ThreadPool;
-import org.eclipse.jetty.util.thread.Timeout;
+import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
+import org.eclipse.jetty.util.thread.Scheduler;
 
 /**
- * Http Client.
- * <p/>
- * HttpClient is the main active component of the client API implementation.
- * It is the opposite of the Connectors in standard Jetty, in that it listens
- * for responses rather than requests.   Just like the connectors, there is a
- * blocking socket version and a non-blocking NIO version (implemented as nested classes
- * selected by {@link #setConnectorType(int)}).
- * <p/>
- * The an instance of {@link HttpExchange} is passed to the {@link #send(HttpExchange)} method
- * to send a request.  The exchange contains both the headers and content (source) of the request
- * plus the callbacks to handle responses.   A HttpClient can have many exchanges outstanding
- * and they may be queued on the {@link HttpDestination} waiting for a {@link AbstractHttpConnection},
- * queued in the {@link AbstractHttpConnection} waiting to be transmitted or pipelined on the actual
- * TCP/IP connection waiting for a response.
- * <p/>
- * The {@link HttpDestination} class is an aggregation of {@link AbstractHttpConnection}s for the
- * same host, port and protocol.   A destination may limit the number of connections
- * open and they provide a pool of open connections that may be reused.   Connections may also
- * be allocated from a destination, so that multiple request sources are not multiplexed
- * over the same connection.
+ * <p>{@link HttpClient} provides an efficient, asynchronous, non-blocking implementation
+ * to perform HTTP requests to a server through a simple API that offers also blocking semantic.</p>
+ * <p>{@link HttpClient} provides easy-to-use methods such as {@link #GET(String)} that allow to perform HTTP
+ * requests in a one-liner, but also gives the ability to fine tune the configuration of requests via
+ * {@link HttpClient#newRequest(URI)}.</p>
+ * <p>{@link HttpClient} acts as a central configuration point for network parameters (such as idle timeouts)
+ * and HTTP parameters (such as whether to follow redirects).</p>
+ * <p>{@link HttpClient} transparently pools connections to servers, but allows direct control of connections
+ * for cases where this is needed.</p>
+ * <p>{@link HttpClient} also acts as a central configuration point for cookies, via {@link #getCookieStore()}.</p>
+ * <p>Typical usage:</p>
+ * <pre>
+ * HttpClient httpClient = new HttpClient();
+ * httpClient.start();
+ *
+ * // One liner:
+ * httpClient.GET("http://localhost:8080/").getStatus();
+ *
+ * // Building a request with a timeout
+ * ContentResponse response = httpClient.newRequest("http://localhost:8080")
+ *         .timeout(5, TimeUnit.SECONDS)
+ *         .send();
+ * int status = response.status();
  *
- * @see HttpExchange
- * @see HttpDestination
+ * // Asynchronously
+ * httpClient.newRequest("http://localhost:8080").send(new Response.CompleteListener()
+ * {
+ *     @Override
+ *     public void onComplete(Result result)
+ *     {
+ *         ...
+ *     }
+ * });
+ * </pre>
  */
-public class HttpClient extends AggregateLifeCycle implements HttpBuffers, Attributes, Dumpable
+public class HttpClient extends ContainerLifeCycle
 {
-    public static final int CONNECTOR_SOCKET = 0;
-    public static final int CONNECTOR_SELECT_CHANNEL = 2;
-
-    private int _connectorType = CONNECTOR_SELECT_CHANNEL;
-    private boolean _useDirectBuffers = true;
-    private boolean _connectBlocking = true;
-    private boolean _removeIdleDestinations=false;
-    private int _maxConnectionsPerAddress = Integer.MAX_VALUE;
-    private int _maxQueueSizePerAddress = Integer.MAX_VALUE;
-    private ConcurrentMap<Address, HttpDestination> _destinations = new ConcurrentHashMap<Address, HttpDestination>();
-    ThreadPool _threadPool;
-    Connector _connector;
-    private long _idleTimeout = 20000;
-    private long _timeout = 320000;
-    private int _connectTimeout = 75000;
-    private Timeout _timeoutQ = new Timeout();
-    private Timeout _idleTimeoutQ = new Timeout();
-    private Address _proxy;
-    private Authentication _proxyAuthentication;
-    private Set<String> _noProxy;
-    private int _maxRetries = 3;
-    private int _maxRedirects = 20;
-    private LinkedList<String> _registeredListeners;
-
-    private final SslContextFactory _sslContextFactory;
-
-    private RealmResolver _realmResolver;
-
-    private AttributesMap _attributes=new AttributesMap();
-
-    private final HttpBuffersImpl _buffers= new HttpBuffersImpl();
-
-    /* ------------------------------------------------------------------------------- */
-    private void setBufferTypes()
-    {
-        if (_connectorType==CONNECTOR_SOCKET)
-        {
-            _buffers.setRequestBufferType(Type.BYTE_ARRAY);
-            _buffers.setRequestHeaderType(Type.BYTE_ARRAY);
-            _buffers.setResponseBufferType(Type.BYTE_ARRAY);
-            _buffers.setResponseHeaderType(Type.BYTE_ARRAY);
-        }
-        else
-        {
-            _buffers.setRequestBufferType(Type.DIRECT);
-            _buffers.setRequestHeaderType(_useDirectBuffers?Type.DIRECT:Type.INDIRECT);
-            _buffers.setResponseBufferType(Type.DIRECT);
-            _buffers.setResponseHeaderType(_useDirectBuffers?Type.DIRECT:Type.INDIRECT);
-        }
+    private static final Logger LOG = Log.getLogger(HttpClient.class);
+
+    private final ConcurrentMap<Origin, HttpDestination> destinations = new ConcurrentHashMap<>();
+    private final List<ProtocolHandler> handlers = new ArrayList<>();
+    private final List<Request.Listener> requestListeners = new ArrayList<>();
+    private final AuthenticationStore authenticationStore = new HttpAuthenticationStore();
+    private final Set<ContentDecoder.Factory> decoderFactories = new ContentDecoderFactorySet();
+    private final ProxyConfiguration proxyConfig = new ProxyConfiguration();
+    private final HttpClientTransport transport;
+    private final SslContextFactory sslContextFactory;
+    private volatile CookieManager cookieManager;
+    private volatile CookieStore cookieStore;
+    private volatile Executor executor;
+    private volatile ByteBufferPool byteBufferPool;
+    private volatile Scheduler scheduler;
+    private volatile SocketAddressResolver resolver;
+    private volatile HttpField agentField = new HttpField(HttpHeader.USER_AGENT, "Jetty/" + Jetty.VERSION);
+    private volatile boolean followRedirects = true;
+    private volatile int maxConnectionsPerDestination = 64;
+    private volatile int maxRequestsQueuedPerDestination = 1024;
+    private volatile int requestBufferSize = 4096;
+    private volatile int responseBufferSize = 16384;
+    private volatile int maxRedirects = 8;
+    private volatile SocketAddress bindAddress;
+    private volatile long connectTimeout = 15000;
+    private volatile long addressResolutionTimeout = 15000;
+    private volatile long idleTimeout;
+    private volatile boolean tcpNoDelay = true;
+    private volatile boolean dispatchIO = true;
+    private volatile boolean strictEventOrdering = false;
+    private volatile HttpField encodingField;
+    private volatile boolean removeIdleDestinations = false;
 
-    }
-
-    /* ------------------------------------------------------------------------------- */
+    /**
+     * Creates a {@link HttpClient} instance that can perform requests to non-TLS destinations only
+     * (that is, requests with the "http" scheme only, and not "https").
+     *
+     * @see #HttpClient(SslContextFactory) to perform requests to TLS destinations.
+     */
     public HttpClient()
     {
-        this(new SslContextFactory());
+        this(null);
     }
 
-    /* ------------------------------------------------------------------------------- */
+    /**
+     * Creates a {@link HttpClient} instance that can perform requests to non-TLS and TLS destinations
+     * (that is, both requests with the "http" scheme and with the "https" scheme).
+     *
+     * @param sslContextFactory the {@link SslContextFactory} that manages TLS encryption
+     * @see #getSslContextFactory()
+     */
     public HttpClient(SslContextFactory sslContextFactory)
     {
-        _sslContextFactory = sslContextFactory;
-        addBean(_sslContextFactory);
-        addBean(_buffers);
+        this(new HttpClientTransportOverHTTP(), sslContextFactory);
     }
 
-    /* ------------------------------------------------------------------------------- */
-    /**
-     * @return True if connects will be in blocking mode.
-     */
-    public boolean isConnectBlocking()
+    public HttpClient(HttpClientTransport transport, SslContextFactory sslContextFactory)
     {
-        return _connectBlocking;
+        this.transport = transport;
+        this.sslContextFactory = sslContextFactory;
+    }
+
+    public HttpClientTransport getTransport()
+    {
+        return transport;
     }
 
-    /* ------------------------------------------------------------------------------- */
     /**
-     * @param connectBlocking True if connects will be in blocking mode.
+     * @return the {@link SslContextFactory} that manages TLS encryption
+     * @see #HttpClient(SslContextFactory)
      */
-    public void setConnectBlocking(boolean connectBlocking)
+    public SslContextFactory getSslContextFactory()
     {
-        _connectBlocking = connectBlocking;
+        return sslContextFactory;
     }
 
-    /* ------------------------------------------------------------------------------- */
-    public boolean isRemoveIdleDestinations()
+    @Override
+    protected void doStart() throws Exception
     {
-        return _removeIdleDestinations;
+        if (sslContextFactory != null)
+            addBean(sslContextFactory);
+
+        String name = HttpClient.class.getSimpleName() + "@" + hashCode();
+
+        if (executor == null)
+        {
+            QueuedThreadPool threadPool = new QueuedThreadPool();
+            threadPool.setName(name);
+            executor = threadPool;
+        }
+        addBean(executor);
+
+        if (byteBufferPool == null)
+            byteBufferPool = new MappedByteBufferPool();
+        addBean(byteBufferPool);
+
+        if (scheduler == null)
+            scheduler = new ScheduledExecutorScheduler(name + "-scheduler", false);
+        addBean(scheduler);
+
+        transport.setHttpClient(this);
+        addBean(transport);
+
+        resolver = new SocketAddressResolver(executor, scheduler, getAddressResolutionTimeout());
+
+        handlers.add(new ContinueProtocolHandler(this));
+        handlers.add(new RedirectProtocolHandler(this));
+        handlers.add(new WWWAuthenticationProtocolHandler(this));
+        handlers.add(new ProxyAuthenticationProtocolHandler(this));
+
+        decoderFactories.add(new GZIPContentDecoder.Factory());
+
+        cookieManager = newCookieManager();
+        cookieStore = cookieManager.getCookieStore();
+
+        super.doStart();
     }
 
-    /* ------------------------------------------------------------------------------- */
-    public void setRemoveIdleDestinations(boolean removeIdleDestinations)
+    private CookieManager newCookieManager()
     {
-        _removeIdleDestinations = removeIdleDestinations;
+        return new CookieManager(getCookieStore(), CookiePolicy.ACCEPT_ALL);
     }
 
-    /* ------------------------------------------------------------------------------- */
-    public void send(HttpExchange exchange) throws IOException
+    @Override
+    protected void doStop() throws Exception
     {
-        boolean ssl = HttpSchemes.HTTPS_BUFFER.equalsIgnoreCase(exchange.getScheme());
-        HttpDestination destination = getDestination(exchange.getAddress(), ssl);
-        destination.send(exchange);
+        cookieStore.removeAll();
+        decoderFactories.clear();
+        handlers.clear();
+
+        for (HttpDestination destination : destinations.values())
+            destination.close();
+        destinations.clear();
+
+        requestListeners.clear();
+        authenticationStore.clearAuthentications();
+        authenticationStore.clearAuthenticationResults();
+
+        super.doStop();
     }
 
-    /* ------------------------------------------------------------ */
     /**
-     * @return the threadpool
+     * Returns a <em>non</em> thread-safe list of {@link org.eclipse.jetty.client.api.Request.Listener}s that can be modified before
+     * performing requests.
+     *
+     * @return a list of {@link org.eclipse.jetty.client.api.Request.Listener} that can be used to add and remove listeners
      */
-    public ThreadPool getThreadPool()
+    public List<Request.Listener> getRequestListeners()
     {
-        return _threadPool;
+        return requestListeners;
     }
 
-    /* ------------------------------------------------------------ */
-    /** Set the ThreadPool.
-     * The threadpool passed is added via {@link #addBean(Object)} so that
-     * it's lifecycle may be managed as a {@link AggregateLifeCycle}.
-     * @param threadPool the threadPool to set
+    /**
+     * @return the cookie store associated with this instance
      */
-    public void setThreadPool(ThreadPool threadPool)
+    public CookieStore getCookieStore()
     {
-        removeBean(_threadPool);
-        _threadPool = threadPool;
-        addBean(_threadPool);
+        return cookieStore;
     }
 
-
-    /* ------------------------------------------------------------ */
     /**
-     * @param name
-     * @return Attribute associated with client
+     * @param cookieStore the cookie store associated with this instance
      */
-    public Object getAttribute(String name)
+    public void setCookieStore(CookieStore cookieStore)
     {
-        return _attributes.getAttribute(name);
+        this.cookieStore = Objects.requireNonNull(cookieStore);
+        this.cookieManager = newCookieManager();
     }
 
-    /* ------------------------------------------------------------ */
     /**
-     * @return names of attributes associated with client
+     * Keep this method package-private because its interface is so ugly
+     * that we really don't want to expose it more than strictly needed.
+     *
+     * @return the cookie manager
      */
-    public Enumeration getAttributeNames()
+    CookieManager getCookieManager()
     {
-        return _attributes.getAttributeNames();
+        return cookieManager;
     }
 
-    /* ------------------------------------------------------------ */
     /**
-     * @param name
+     * @return the authentication store associated with this instance
      */
-    public void removeAttribute(String name)
+    public AuthenticationStore getAuthenticationStore()
     {
-        _attributes.removeAttribute(name);
+        return authenticationStore;
     }
 
-    /* ------------------------------------------------------------ */
     /**
-     * Set an attribute on the HttpClient.
-     * Attributes are not used by the client, but are provided for
-     * so that users of a shared HttpClient may share other structures.
-     * @param name
-     * @param attribute
+     * Returns a <em>non</em> thread-safe set of {@link ContentDecoder.Factory}s that can be modified before
+     * performing requests.
+     *
+     * @return a set of {@link ContentDecoder.Factory} that can be used to add and remove content decoder factories
      */
-    public void setAttribute(String name, Object attribute)
-    {
-        _attributes.setAttribute(name,attribute);
-    }
-
-    /* ------------------------------------------------------------ */
-    public void clearAttributes()
-    {
-        _attributes.clearAttributes();
-    }
-
-    /* ------------------------------------------------------------------------------- */
-    public HttpDestination getDestination(Address remote, boolean ssl) throws IOException
+    public Set<ContentDecoder.Factory> getContentDecoderFactories()
     {
-        return getDestination(remote, ssl, getSslContextFactory());
+        return decoderFactories;
     }
 
-    /* ------------------------------------------------------------------------------- */
-    public HttpDestination getDestination(Address remote, boolean ssl, SslContextFactory sslContextFactory) throws IOException
-    {
-        if (remote == null)
-            throw new UnknownHostException("Remote socket address cannot be null.");
-
-        HttpDestination destination = _destinations.get(remote);
-        if (destination == null)
-        {
-            destination = new HttpDestination(this, remote, ssl, sslContextFactory);
-            if (_proxy != null && (_noProxy == null || !_noProxy.contains(remote.getHost())))
-            {
-                destination.setProxy(_proxy);
-                if (_proxyAuthentication != null)
-                    destination.setProxyAuthentication(_proxyAuthentication);
-            }
-            HttpDestination other =_destinations.putIfAbsent(remote, destination);
-            if (other!=null)
-                destination=other;
-        }
-        return destination;
-    }
-    
-    /* ------------------------------------------------------------------------------- */
-    public Collection<Address> getDestinations()
-    {
-        return Collections.unmodifiableCollection(_destinations.keySet());
-    }
-    
-    /* ------------------------------------------------------------------------------- */
-    public void removeDestination(HttpDestination destination)
-    {
-        _destinations.remove(destination.getAddress(),destination);
-    }
-    
-    /* ------------------------------------------------------------ */
-    public void schedule(Timeout.Task task)
+    /**
+     * Performs a GET request to the specified URI.
+     *
+     * @param uri the URI to GET
+     * @return the {@link ContentResponse} for the request
+     * @see #GET(URI)
+     */
+    public ContentResponse GET(String uri) throws InterruptedException, ExecutionException, TimeoutException
     {
-        _timeoutQ.schedule(task);
+        return GET(URI.create(uri));
     }
 
-    /* ------------------------------------------------------------ */
-    public void schedule(Timeout.Task task, long timeout)
+    /**
+     * Performs a GET request to the specified URI.
+     *
+     * @param uri the URI to GET
+     * @return the {@link ContentResponse} for the request
+     * @see #newRequest(URI)
+     */
+    public ContentResponse GET(URI uri) throws InterruptedException, ExecutionException, TimeoutException
     {
-        _timeoutQ.schedule(task, timeout - _timeoutQ.getDuration());
+        return newRequest(uri).send();
     }
 
-    /* ------------------------------------------------------------ */
-    public void scheduleIdle(Timeout.Task task)
+    /**
+     * Performs a POST request to the specified URI with the given form parameters.
+     *
+     * @param uri the URI to POST
+     * @param fields the fields composing the form name/value pairs
+     * @return the {@link ContentResponse} for the request
+     */
+    public ContentResponse FORM(String uri, Fields fields) throws InterruptedException, ExecutionException, TimeoutException
     {
-        _idleTimeoutQ.schedule(task);
+        return FORM(URI.create(uri), fields);
     }
 
-    /* ------------------------------------------------------------ */
-    public void cancel(Timeout.Task task)
+    /**
+     * Performs a POST request to the specified URI with the given form parameters.
+     *
+     * @param uri the URI to POST
+     * @param fields the fields composing the form name/value pairs
+     * @return the {@link ContentResponse} for the request
+     */
+    public ContentResponse FORM(URI uri, Fields fields) throws InterruptedException, ExecutionException, TimeoutException
     {
-        task.cancel();
+        return POST(uri).content(new FormContentProvider(fields)).send();
     }
 
-    /* ------------------------------------------------------------ */
     /**
-     * Get whether the connector can use direct NIO buffers.
+     * Creates a POST request to the specified URI.
+     *
+     * @param uri the URI to POST to
+     * @return the POST request
+     * @see #POST(URI)
      */
-    public boolean getUseDirectBuffers()
+    public Request POST(String uri)
     {
-        return _useDirectBuffers;
+        return POST(URI.create(uri));
     }
 
-    /* ------------------------------------------------------------ */
-    /** Set a RealmResolver for client Authentication.
-     * If a realmResolver is set, then the HttpDestinations created by
-     * this client will instantiate a {@link SecurityListener} so that
-     * BASIC and DIGEST authentication can be performed.
-     * @param resolver
+    /**
+     * Creates a POST request to the specified URI.
+     *
+     * @param uri the URI to POST to
+     * @return the POST request
      */
-    public void setRealmResolver(RealmResolver resolver)
+    public Request POST(URI uri)
     {
-        _realmResolver = resolver;
+        return newRequest(uri).method(HttpMethod.POST);
     }
 
-    /* ------------------------------------------------------------ */
     /**
-     * returns the SecurityRealmResolver reg_realmResolveristered with the HttpClient or null
+     * Creates a new request with the "http" scheme and the specified host and port
      *
-     * @return the SecurityRealmResolver reg_realmResolveristered with the HttpClient or null
+     * @param host the request host
+     * @param port the request port
+     * @return the request just created
      */
-    public RealmResolver getRealmResolver()
+    public Request newRequest(String host, int port)
     {
-        return _realmResolver;
+        return newRequest(new Origin("http", host, port).asString());
     }
 
-    /* ------------------------------------------------------------ */
-    public boolean hasRealms()
+    /**
+     * Creates a new request with the specified URI.
+     *
+     * @param uri the URI to request
+     * @return the request just created
+     */
+    public Request newRequest(String uri)
     {
-        return _realmResolver == null ? false : true;
+        return newRequest(URI.create(uri));
     }
 
-
-    /* ------------------------------------------------------------ */
     /**
-     * Registers a listener that can listen to the stream of execution between the client and the
-     * server and influence events.  Sequential calls to the method wrapper sequentially wrap the preceding
-     * listener in a delegation model.
-     * <p/>
-     * NOTE: the SecurityListener is a special listener which doesn't need to be added via this
-     * mechanic, if you register security realms then it will automatically be added as the top listener of the
-     * delegation stack.
+     * Creates a new request with the specified URI.
      *
-     * @param listenerClass
+     * @param uri the URI to request
+     * @return the request just created
      */
-    public void registerListener(String listenerClass)
+    public Request newRequest(URI uri)
     {
-        if (_registeredListeners == null)
+        return newHttpRequest(newConversation(), uri);
+    }
+
+    protected Request copyRequest(HttpRequest oldRequest, URI newURI)
+    {
+        Request newRequest = newHttpRequest(oldRequest.getConversation(), newURI);
+        newRequest.method(oldRequest.getMethod())
+                .version(oldRequest.getVersion())
+                .content(oldRequest.getContent())
+                .idleTimeout(oldRequest.getIdleTimeout(), TimeUnit.MILLISECONDS)
+                .timeout(oldRequest.getTimeout(), TimeUnit.MILLISECONDS)
+                .followRedirects(oldRequest.isFollowRedirects());
+        for (HttpField field : oldRequest.getHeaders())
         {
-            _registeredListeners = new LinkedList<String>();
+            HttpHeader header = field.getHeader();
+            // We have a new URI, so skip the host header if present.
+            if (HttpHeader.HOST == header)
+                continue;
+
+            // Remove expectation headers.
+            if (HttpHeader.EXPECT == header)
+                continue;
+
+            // Remove cookies.
+            if (HttpHeader.COOKIE == header)
+                continue;
+
+            // Remove authorization headers.
+            if (HttpHeader.AUTHORIZATION == header ||
+                    HttpHeader.PROXY_AUTHORIZATION == header)
+                continue;
+
+            String value = field.getValue();
+            if (!newRequest.getHeaders().contains(header, value))
+                newRequest.header(field.getName(), value);
         }
-        _registeredListeners.add(listenerClass);
+        return newRequest;
     }
 
-    /* ------------------------------------------------------------ */
-    public LinkedList<String> getRegisteredListeners()
+    protected HttpRequest newHttpRequest(HttpConversation conversation, URI uri)
     {
-        return _registeredListeners;
+        return new HttpRequest(this, conversation, uri);
     }
 
-
-    /* ------------------------------------------------------------ */
     /**
-     * Set to use NIO direct buffers.
+     * Returns a {@link Destination} for the given scheme, host and port.
+     * Applications may use {@link Destination}s to create {@link Connection}s
+     * that will be outside {@link HttpClient}'s pooling mechanism, to explicitly
+     * control the connection lifecycle (in particular their termination with
+     * {@link Connection#close()}).
      *
-     * @param direct If True (the default), the connector can use NIO direct
-     *               buffers. Some JVMs have memory management issues (bugs) with
-     *               direct buffers.
+     * @param scheme the destination scheme
+     * @param host the destination host
+     * @param port the destination port
+     * @return the destination
+     * @see #getDestinations()
      */
-    public void setUseDirectBuffers(boolean direct)
+    public Destination getDestination(String scheme, String host, int port)
     {
-        _useDirectBuffers = direct;
-        setBufferTypes();
+        return destinationFor(scheme, host, port);
     }
 
-    /* ------------------------------------------------------------ */
-    /**
-     * Get the type of connector (socket, blocking or select) in use.
-     */
-    public int getConnectorType()
+    protected HttpDestination destinationFor(String scheme, String host, int port)
     {
-        return _connectorType;
-    }
+        port = normalizePort(scheme, port);
 
-    /* ------------------------------------------------------------ */
-    public void setConnectorType(int connectorType)
-    {
-        this._connectorType = connectorType;
-        setBufferTypes();
-    }
+        Origin origin = new Origin(scheme, host, port);
+        HttpDestination destination = destinations.get(origin);
+        if (destination == null)
+        {
+            destination = transport.newHttpDestination(origin);
+            if (isRunning())
+            {
+                HttpDestination existing = destinations.putIfAbsent(origin, destination);
+                if (existing != null)
+                {
+                    destination = existing;
+                }
+                else
+                {
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("Created {}", destination);
+                }
+                if (!isRunning())
+                    destinations.remove(origin);
+            }
 
-    /* ------------------------------------------------------------ */
-    public int getMaxConnectionsPerAddress()
-    {
-        return _maxConnectionsPerAddress;
+        }
+        return destination;
     }
 
-    /* ------------------------------------------------------------ */
-    public void setMaxConnectionsPerAddress(int maxConnectionsPerAddress)
+    protected boolean removeDestination(HttpDestination destination)
     {
-        _maxConnectionsPerAddress = maxConnectionsPerAddress;
+        return destinations.remove(destination.getOrigin()) != null;
     }
 
-    public int getMaxQueueSizePerAddress()
+    /**
+     * @return the list of destinations known to this {@link HttpClient}.
+     */
+    public List<Destination> getDestinations()
     {
-        return _maxQueueSizePerAddress;
+        return new ArrayList<Destination>(destinations.values());
     }
 
-    public void setMaxQueueSizePerAddress(int maxQueueSizePerAddress)
+    protected void send(final HttpRequest request, List<Response.ResponseListener> listeners)
     {
-        this._maxQueueSizePerAddress = maxQueueSizePerAddress;
+        String scheme = request.getScheme().toLowerCase(Locale.ENGLISH);
+        if (!HttpScheme.HTTP.is(scheme) && !HttpScheme.HTTPS.is(scheme))
+            throw new IllegalArgumentException("Invalid protocol " + scheme);
+
+        String host = request.getHost().toLowerCase(Locale.ENGLISH);
+        HttpDestination destination = destinationFor(scheme, host, request.getPort());
+        destination.send(request, listeners);
     }
 
-    /* ------------------------------------------------------------ */
-    @Override
-    protected void doStart() throws Exception
+    protected void newConnection(final HttpDestination destination, final Promise<Connection> promise)
     {
-        setBufferTypes();
-
-        _timeoutQ.setDuration(_timeout);
-        _timeoutQ.setNow();
-        _idleTimeoutQ.setDuration(_idleTimeout);
-        _idleTimeoutQ.setNow();
-
-        if (_threadPool==null)
+        Origin.Address address = destination.getConnectAddress();
+        resolver.resolve(address.getHost(), address.getPort(), new Promise<SocketAddress>()
         {
-            QueuedThreadPool pool = new LocalQueuedThreadPool();
-            pool.setMaxThreads(16);
-            pool.setDaemon(true);
-            pool.setName("HttpClient");
-            _threadPool = pool;
-            addBean(_threadPool,true);
-        }
-
-        _connector=(_connectorType == CONNECTOR_SELECT_CHANNEL)?new SelectConnector(this):new SocketConnector(this);
-        addBean(_connector,true);
-
-        super.doStart();
+            @Override
+            public void succeeded(SocketAddress socketAddress)
+            {
+                Map<String, Object> context = new HashMap<>();
+                context.put(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY, destination);
+                context.put(HttpClientTransport.HTTP_CONNECTION_PROMISE_CONTEXT_KEY, promise);
+                transport.connect(socketAddress, context);
+            }
 
-        _threadPool.dispatch(new Runnable()
-        {
-            public void run()
+            @Override
+            public void failed(Throwable x)
             {
-                while (isRunning())
-                {
-                    _timeoutQ.tick(System.currentTimeMillis());
-                    _idleTimeoutQ.tick(_timeoutQ.getNow());
-                    try
-                    {
-                        Thread.sleep(200);
-                    }
-                    catch (InterruptedException ignored)
-                    {
-                    }
-                }
+                promise.failed(x);
             }
         });
     }
 
-    /* ------------------------------------------------------------ */
-    @Override
-    protected void doStop() throws Exception
+    private HttpConversation newConversation()
     {
-        for (HttpDestination destination : _destinations.values())
-            destination.close();
-
-        _timeoutQ.cancelAll();
-        _idleTimeoutQ.cancelAll();
+        return new HttpConversation();
+    }
 
-        super.doStop();
+    public List<ProtocolHandler> getProtocolHandlers()
+    {
+        return handlers;
+    }
 
-        if (_threadPool instanceof LocalQueuedThreadPool)
+    protected ProtocolHandler findProtocolHandler(Request request, Response response)
+    {
+        // Optimized to avoid allocations of iterator instances
+        List<ProtocolHandler> protocolHandlers = getProtocolHandlers();
+        for (int i = 0; i < protocolHandlers.size(); ++i)
         {
-            removeBean(_threadPool);
-            _threadPool = null;
+            ProtocolHandler handler = protocolHandlers.get(i);
+            if (handler.accept(request, response))
+                return handler;
         }
-
-        removeBean(_connector);
+        return null;
     }
 
-    /* ------------------------------------------------------------ */
-    interface Connector extends LifeCycle
+    /**
+     * @return the {@link ByteBufferPool} of this {@link HttpClient}
+     */
+    public ByteBufferPool getByteBufferPool()
     {
-        public void startConnection(HttpDestination destination) throws IOException;
+        return byteBufferPool;
     }
 
-    /* ------------------------------------------------------------ */
     /**
-     * if a keystore location has been provided then client will attempt to use it as the keystore,
-     * otherwise we simply ignore certificates and run with a loose ssl context.
-     *
-     * @return the SSL context
+     * @param byteBufferPool the {@link ByteBufferPool} of this {@link HttpClient}
      */
-    protected SSLContext getSSLContext()
+    public void setByteBufferPool(ByteBufferPool byteBufferPool)
     {
-        return _sslContextFactory.getSslContext();
+        this.byteBufferPool = byteBufferPool;
     }
 
-    /* ------------------------------------------------------------ */
     /**
-     * @return the instance of SslContextFactory associated with the client
+     * @return the max time, in milliseconds, a connection can take to connect to destinations
      */
-    public SslContextFactory getSslContextFactory()
+    public long getConnectTimeout()
     {
-        return _sslContextFactory;
+        return connectTimeout;
     }
 
-    /* ------------------------------------------------------------ */
     /**
-     * @return the period in milliseconds a {@link AbstractHttpConnection} can be idle for before it is closed.
+     * @param connectTimeout the max time, in milliseconds, a connection can take to connect to destinations
+     * @see java.net.Socket#connect(SocketAddress, int)
      */
-    public long getIdleTimeout()
+    public void setConnectTimeout(long connectTimeout)
     {
-        return _idleTimeout;
+        this.connectTimeout = connectTimeout;
     }
 
-    /* ------------------------------------------------------------ */
     /**
-     * @param ms the period in milliseconds a {@link AbstractHttpConnection} can be idle for before it is closed.
+     * @return the timeout, in milliseconds, for the DNS resolution of host addresses
      */
-    public void setIdleTimeout(long ms)
+    public long getAddressResolutionTimeout()
     {
-        _idleTimeout = ms;
+        return addressResolutionTimeout;
     }
 
-    /* ------------------------------------------------------------ */
     /**
-     * @return the period in ms that an exchange will wait for a response from the server.
-     * @deprecated use {@link #getTimeout()} instead.
+     * @param addressResolutionTimeout the timeout, in milliseconds, for the DNS resolution of host addresses
      */
-    @Deprecated
-    public int getSoTimeout()
+    public void setAddressResolutionTimeout(long addressResolutionTimeout)
     {
-        return Long.valueOf(getTimeout()).intValue();
+        this.addressResolutionTimeout = addressResolutionTimeout;
     }
 
-    /* ------------------------------------------------------------ */
     /**
-     * @deprecated use {@link #setTimeout(long)} instead.
-     * @param timeout the period in ms that an exchange will wait for a response from the server.
+     * @return the max time, in milliseconds, a connection can be idle (that is, without traffic of bytes in either direction)
      */
-    @Deprecated
-    public void setSoTimeout(int timeout)
+    public long getIdleTimeout()
     {
-        setTimeout(timeout);
+        return idleTimeout;
     }
 
-    /* ------------------------------------------------------------ */
     /**
-     * @return the period in ms that an exchange will wait for a response from the server.
+     * @param idleTimeout the max time, in milliseconds, a connection can be idle (that is, without traffic of bytes in either direction)
      */
-    public long getTimeout()
+    public void setIdleTimeout(long idleTimeout)
     {
-        return _timeout;
+        this.idleTimeout = idleTimeout;
     }
 
-    /* ------------------------------------------------------------ */
     /**
-     * @param timeout the period in ms that an exchange will wait for a response from the server.
+     * @return the address to bind socket channels to
+     * @see #setBindAddress(SocketAddress)
      */
-    public void setTimeout(long timeout)
+    public SocketAddress getBindAddress()
     {
-        _timeout = timeout;
+        return bindAddress;
     }
 
-    /* ------------------------------------------------------------ */
     /**
-     * @return the period in ms before timing out an attempt to connect
+     * @param bindAddress the address to bind socket channels to
+     * @see #getBindAddress()
+     * @see SocketChannel#bind(SocketAddress)
      */
-    public int getConnectTimeout()
+    public void setBindAddress(SocketAddress bindAddress)
     {
-        return _connectTimeout;
+        this.bindAddress = bindAddress;
     }
 
-    /* ------------------------------------------------------------ */
     /**
-     * @param connectTimeout the period in ms before timing out an attempt to connect
+     * @return the "User-Agent" HTTP field of this {@link HttpClient}
      */
-    public void setConnectTimeout(int connectTimeout)
+    public HttpField getUserAgentField()
     {
-        this._connectTimeout = connectTimeout;
+        return agentField;
     }
 
-    /* ------------------------------------------------------------ */
-    public Address getProxy()
+    /**
+     * @param agent the "User-Agent" HTTP header string of this {@link HttpClient}
+     */
+    public void setUserAgentField(HttpField agent)
     {
-        return _proxy;
+        if (agent.getHeader() != HttpHeader.USER_AGENT)
+            throw new IllegalArgumentException();
+        this.agentField = agent;
     }
 
-    /* ------------------------------------------------------------ */
-    public void setProxy(Address proxy)
+    /**
+     * @return whether this {@link HttpClient} follows HTTP redirects
+     * @see Request#isFollowRedirects()
+     */
+    public boolean isFollowRedirects()
     {
-        this._proxy = proxy;
+        return followRedirects;
     }
 
-    /* ------------------------------------------------------------ */
-    public Authentication getProxyAuthentication()
+    /**
+     * @param follow whether this {@link HttpClient} follows HTTP redirects
+     * @see #setMaxRedirects(int)
+     */
+    public void setFollowRedirects(boolean follow)
     {
-        return _proxyAuthentication;
+        this.followRedirects = follow;
     }
 
-    /* ------------------------------------------------------------ */
-    public void setProxyAuthentication(Authentication authentication)
+    /**
+     * @return the {@link Executor} of this {@link HttpClient}
+     */
+    public Executor getExecutor()
     {
-        _proxyAuthentication = authentication;
+        return executor;
     }
 
-    /* ------------------------------------------------------------ */
-    public boolean isProxied()
+    /**
+     * @param executor the {@link Executor} of this {@link HttpClient}
+     */
+    public void setExecutor(Executor executor)
     {
-        return this._proxy != null;
+        this.executor = executor;
     }
 
-    /* ------------------------------------------------------------ */
-    public Set<String> getNoProxy()
+    /**
+     * @return the {@link Scheduler} of this {@link HttpClient}
+     */
+    public Scheduler getScheduler()
     {
-        return _noProxy;
+        return scheduler;
     }
 
-    /* ------------------------------------------------------------ */
-    public void setNoProxy(Set<String> noProxyAddresses)
+    /**
+     * @param scheduler the {@link Scheduler} of this {@link HttpClient}
+     */
+    public void setScheduler(Scheduler scheduler)
     {
-        _noProxy = noProxyAddresses;
+        this.scheduler = scheduler;
     }
 
-    /* ------------------------------------------------------------ */
-    public int maxRetries()
+    /**
+     * @return the max number of connections that this {@link HttpClient} opens to {@link Destination}s
+     */
+    public int getMaxConnectionsPerDestination()
     {
-        return _maxRetries;
+        return maxConnectionsPerDestination;
     }
 
-    /* ------------------------------------------------------------ */
-    public void setMaxRetries(int retries)
+    /**
+     * Sets the max number of connections to open to each destinations.
+     * <p />
+     * RFC 2616 suggests that 2 connections should be opened per each destination,
+     * but browsers commonly open 6.
+     * If this {@link HttpClient} is used for load testing, it is common to have only one destination
+     * (the server to load test), and it is recommended to set this value to a high value (at least as
+     * much as the threads present in the {@link #getExecutor() executor}).
+     *
+     * @param maxConnectionsPerDestination the max number of connections that this {@link HttpClient} opens to {@link Destination}s
+     */
+    public void setMaxConnectionsPerDestination(int maxConnectionsPerDestination)
     {
-        _maxRetries = retries;
+        this.maxConnectionsPerDestination = maxConnectionsPerDestination;
     }
 
-    /* ------------------------------------------------------------ */
-    public int maxRedirects()
+    /**
+     * @return the max number of requests that may be queued to a {@link Destination}.
+     */
+    public int getMaxRequestsQueuedPerDestination()
     {
-        return _maxRedirects;
+        return maxRequestsQueuedPerDestination;
     }
 
-    /* ------------------------------------------------------------ */
-    public void setMaxRedirects(int redirects)
+    /**
+     * Sets the max number of requests that may be queued to a destination.
+     * <p />
+     * If this {@link HttpClient} performs a high rate of requests to a destination,
+     * and all the connections managed by that destination are busy with other requests,
+     * then new requests will be queued up in the destination.
+     * This parameter controls how many requests can be queued before starting to reject them.
+     * If this {@link HttpClient} is used for load testing, it is common to have this parameter
+     * set to a high value, although this may impact latency (requests sit in the queue for a long
+     * time before being sent).
+     *
+     * @param maxRequestsQueuedPerDestination the max number of requests that may be queued to a {@link Destination}.
+     */
+    public void setMaxRequestsQueuedPerDestination(int maxRequestsQueuedPerDestination)
     {
-        _maxRedirects = redirects;
+        this.maxRequestsQueuedPerDestination = maxRequestsQueuedPerDestination;
     }
 
+    /**
+     * @return the size of the buffer used to write requests
+     */
     public int getRequestBufferSize()
     {
-        return _buffers.getRequestBufferSize();
+        return requestBufferSize;
     }
 
+    /**
+     * @param requestBufferSize the size of the buffer used to write requests
+     */
     public void setRequestBufferSize(int requestBufferSize)
     {
-        _buffers.setRequestBufferSize(requestBufferSize);
-    }
-
-    public int getRequestHeaderSize()
-    {
-        return _buffers.getRequestHeaderSize();
-    }
-
-    public void setRequestHeaderSize(int requestHeaderSize)
-    {
-        _buffers.setRequestHeaderSize(requestHeaderSize);
+        this.requestBufferSize = requestBufferSize;
     }
 
+    /**
+     * @return the size of the buffer used to read responses
+     */
     public int getResponseBufferSize()
     {
-        return _buffers.getResponseBufferSize();
+        return responseBufferSize;
     }
 
+    /**
+     * @param responseBufferSize the size of the buffer used to read responses
+     */
     public void setResponseBufferSize(int responseBufferSize)
     {
-        _buffers.setResponseBufferSize(responseBufferSize);
-    }
-
-    public int getResponseHeaderSize()
-    {
-        return _buffers.getResponseHeaderSize();
-    }
-
-    public void setResponseHeaderSize(int responseHeaderSize)
-    {
-        _buffers.setResponseHeaderSize(responseHeaderSize);
-    }
-
-    public Type getRequestBufferType()
-    {
-        return _buffers.getRequestBufferType();
-    }
-
-    public Type getRequestHeaderType()
-    {
-        return _buffers.getRequestHeaderType();
+        this.responseBufferSize = responseBufferSize;
     }
 
-    public Type getResponseBufferType()
-    {
-        return _buffers.getResponseBufferType();
-    }
-
-    public Type getResponseHeaderType()
+    /**
+     * @return the max number of HTTP redirects that are followed
+     * @see #setMaxRedirects(int)
+     */
+    public int getMaxRedirects()
     {
-        return _buffers.getResponseHeaderType();
+        return maxRedirects;
     }
 
-    public void setRequestBuffers(Buffers requestBuffers)
+    /**
+     * @param maxRedirects the max number of HTTP redirects that are followed
+     * @see #setFollowRedirects(boolean)
+     */
+    public void setMaxRedirects(int maxRedirects)
     {
-        _buffers.setRequestBuffers(requestBuffers);
+        this.maxRedirects = maxRedirects;
     }
 
-    public void setResponseBuffers(Buffers responseBuffers)
+    /**
+     * @return whether TCP_NODELAY is enabled
+     */
+    public boolean isTCPNoDelay()
     {
-        _buffers.setResponseBuffers(responseBuffers);
+        return tcpNoDelay;
     }
 
-    public Buffers getRequestBuffers()
+    /**
+     * @param tcpNoDelay whether TCP_NODELAY is enabled
+     * @see java.net.Socket#setTcpNoDelay(boolean)
+     */
+    public void setTCPNoDelay(boolean tcpNoDelay)
     {
-        return _buffers.getRequestBuffers();
+        this.tcpNoDelay = tcpNoDelay;
     }
 
-    public Buffers getResponseBuffers()
+    /**
+     * @return true to dispatch I/O operations in a different thread, false to execute them in the selector thread
+     * @see #setDispatchIO(boolean)
+     */
+    public boolean isDispatchIO()
     {
-        return _buffers.getResponseBuffers();
+        return dispatchIO;
     }
 
-    public void setMaxBuffers(int maxBuffers)
+    /**
+     * Whether to dispatch I/O operations from the selector thread to a different thread.
+     * <p />
+     * This implementation never blocks on I/O operation, but invokes application callbacks that may
+     * take time to execute or block on other I/O.
+     * If application callbacks are known to take time or block on I/O, then parameter {@code dispatchIO}
+     * should be set to true.
+     * If application callbacks are known to be quick and never block on I/O, then parameter {@code dispatchIO}
+     * may be set to false.
+     *
+     * @param dispatchIO true to dispatch I/O operations in a different thread,
+     *                   false to execute them in the selector thread
+     */
+    public void setDispatchIO(boolean dispatchIO)
     {
-        _buffers.setMaxBuffers(maxBuffers);
+        this.dispatchIO = dispatchIO;
     }
 
-    public int getMaxBuffers()
+    /**
+     * @return whether request events must be strictly ordered
+     * @see #setStrictEventOrdering(boolean)
+     */
+    public boolean isStrictEventOrdering()
     {
-        return _buffers.getMaxBuffers();
+        return strictEventOrdering;
     }
 
-    /* ------------------------------------------------------------ */
-    @Deprecated
-    public String getTrustStoreLocation()
+    /**
+     * Whether request/response events must be strictly ordered with respect to connection usage.
+     * <p />
+     * From the point of view of connection usage, the connection can be reused just before the
+     * "complete" event notified to {@link org.eclipse.jetty.client.api.Response.CompleteListener}s
+     * (but after the "success" event).
+     * <p />
+     * When a request/response exchange is completing, the destination may have another request
+     * queued to be sent to the server.
+     * If the connection for that destination is reused for the second request before the "complete"
+     * event of the first exchange, it may happen that the "begin" event of the second request
+     * happens before the "complete" event of the first exchange.
+     * <p />
+     * Enforcing strict ordering of events so that a "begin" event of a request can never happen
+     * before the "complete" event of the previous exchange comes with the cost of increased
+     * connection usage.
+     * In case of HTTP redirects and strict event ordering, for example, the redirect request will
+     * be forced to open a new connection because it is typically sent from the complete listener
+     * when the connection cannot yet be reused.
+     * When strict event ordering is not enforced, the redirect request will reuse the already
+     * open connection making the system more efficient.
+     * <p />
+     * The default value for this property is {@code false}.
+     *
+     * @param strictEventOrdering whether request/response events must be strictly ordered
+     */
+    public void setStrictEventOrdering(boolean strictEventOrdering)
     {
-        return _sslContextFactory.getTrustStore();
+        this.strictEventOrdering = strictEventOrdering;
     }
 
-    /* ------------------------------------------------------------ */
-    @Deprecated
-    public void setTrustStoreLocation(String trustStoreLocation)
+    /**
+     * @return whether destinations that have no connections should be removed
+     * @see #setRemoveIdleDestinations(boolean)
+     */
+    public boolean isRemoveIdleDestinations()
     {
-        _sslContextFactory.setTrustStore(trustStoreLocation);
+        return removeIdleDestinations;
     }
 
-    /* ------------------------------------------------------------ */
-    @Deprecated
-    public InputStream getTrustStoreInputStream()
+    /**
+     * Whether destinations that have no connections (nor active nor idle) should be removed.
+     * <p />
+     * Applications typically make request to a limited number of destinations so keeping
+     * destinations around is not a problem for the memory or the GC.
+     * However, for applications that hit millions of different destinations (e.g. a spider
+     * bot) it would be useful to be able to remove the old destinations that won't be visited
+     * anymore and leave space for new destinations.
+     *
+     * @param removeIdleDestinations whether destinations that have no connections should be removed
+     * @see org.eclipse.jetty.client.ConnectionPool
+     */
+    public void setRemoveIdleDestinations(boolean removeIdleDestinations)
     {
-        return _sslContextFactory.getTrustStoreInputStream();
+        this.removeIdleDestinations = removeIdleDestinations;
     }
 
-    /* ------------------------------------------------------------ */
-    @Deprecated
-    public void setTrustStoreInputStream(InputStream trustStoreInputStream)
+    /**
+     * @return the forward proxy configuration
+     */
+    public ProxyConfiguration getProxyConfiguration()
     {
-        _sslContextFactory.setTrustStoreInputStream(trustStoreInputStream);
+        return proxyConfig;
     }
 
-    /* ------------------------------------------------------------ */
-    @Deprecated
-    public String getKeyStoreLocation()
+    protected HttpField getAcceptEncodingField()
     {
-        return _sslContextFactory.getKeyStorePath();
+        return encodingField;
     }
 
-    /* ------------------------------------------------------------ */
-    @Deprecated
-    public void setKeyStoreLocation(String keyStoreLocation)
+    protected String normalizeHost(String host)
     {
-        _sslContextFactory.setKeyStorePath(keyStoreLocation);
+        if (host != null && host.matches("\\[.*\\]"))
+            return host.substring(1, host.length() - 1);
+        return host;
     }
 
-    @Deprecated
-    public InputStream getKeyStoreInputStream()
+    protected int normalizePort(String scheme, int port)
     {
-        return _sslContextFactory.getKeyStoreInputStream();
+        return port > 0 ? port : HttpScheme.HTTPS.is(scheme) ? 443 : 80;
     }
 
-    @Deprecated
-    public void setKeyStoreInputStream(InputStream keyStoreInputStream)
+    protected boolean isDefaultPort(String scheme, int port)
     {
-        _sslContextFactory.setKeyStoreInputStream(keyStoreInputStream);
+        return HttpScheme.HTTPS.is(scheme) ? port == 443 : port == 80;
     }
 
-    /* ------------------------------------------------------------ */
-    @Deprecated
-    public void setKeyStorePassword(String keyStorePassword)
+    @Override
+    public void dump(Appendable out, String indent) throws IOException
     {
-        _sslContextFactory.setKeyStorePassword(keyStorePassword);
+        dumpThis(out);
+        dump(out, indent, getBeans(), destinations.values());
     }
 
-    /* ------------------------------------------------------------ */
-    @Deprecated
-    public void setKeyManagerPassword(String keyManagerPassword)
+    private class ContentDecoderFactorySet implements Set<ContentDecoder.Factory>
     {
-        _sslContextFactory.setKeyManagerPassword(keyManagerPassword);
-    }
+        private final Set<ContentDecoder.Factory> set = new HashSet<>();
 
-    /* ------------------------------------------------------------ */
-    @Deprecated
-    public void setTrustStorePassword(String trustStorePassword)
-    {
-        _sslContextFactory.setTrustStorePassword(trustStorePassword);
-    }
+        @Override
+        public boolean add(ContentDecoder.Factory e)
+        {
+            boolean result = set.add(e);
+            invalidate();
+            return result;
+        }
 
-    /* ------------------------------------------------------------ */
-    @Deprecated
-    public String getKeyStoreType()
-    {
-        return _sslContextFactory.getKeyStoreType();
-    }
+        @Override
+        public boolean addAll(Collection<? extends ContentDecoder.Factory> c)
+        {
+            boolean result = set.addAll(c);
+            invalidate();
+            return result;
+        }
 
-    /* ------------------------------------------------------------ */
-    @Deprecated
-    public void setKeyStoreType(String keyStoreType)
-    {
-        _sslContextFactory.setKeyStoreType(keyStoreType);
-    }
+        @Override
+        public boolean remove(Object o)
+        {
+            boolean result = set.remove(o);
+            invalidate();
+            return result;
+        }
 
-    /* ------------------------------------------------------------ */
-    @Deprecated
-    public String getTrustStoreType()
-    {
-        return _sslContextFactory.getTrustStoreType();
-    }
+        @Override
+        public boolean removeAll(Collection<?> c)
+        {
+            boolean result = set.removeAll(c);
+            invalidate();
+            return result;
+        }
 
-    /* ------------------------------------------------------------ */
-    @Deprecated
-    public void setTrustStoreType(String trustStoreType)
-    {
-        _sslContextFactory.setTrustStoreType(trustStoreType);
-    }
+        @Override
+        public boolean retainAll(Collection<?> c)
+        {
+            boolean result = set.retainAll(c);
+            invalidate();
+            return result;
+        }
 
-    /* ------------------------------------------------------------ */
-    @Deprecated
-    public String getKeyManagerAlgorithm()
-    {
-        return _sslContextFactory.getSslKeyManagerFactoryAlgorithm();
-    }
+        @Override
+        public void clear()
+        {
+            set.clear();
+            invalidate();
+        }
 
-    /* ------------------------------------------------------------ */
-    @Deprecated
-    public void setKeyManagerAlgorithm(String keyManagerAlgorithm)
-    {
-        _sslContextFactory.setSslKeyManagerFactoryAlgorithm(keyManagerAlgorithm);
-    }
+        @Override
+        public int size()
+        {
+            return set.size();
+        }
 
-    /* ------------------------------------------------------------ */
-    @Deprecated
-    public String getTrustManagerAlgorithm()
-    {
-        return _sslContextFactory.getTrustManagerFactoryAlgorithm();
-    }
+        @Override
+        public boolean isEmpty()
+        {
+            return set.isEmpty();
+        }
 
-    /* ------------------------------------------------------------ */
-    @Deprecated
-    public void setTrustManagerAlgorithm(String trustManagerAlgorithm)
-    {
-        _sslContextFactory.setTrustManagerFactoryAlgorithm(trustManagerAlgorithm);
-    }
+        @Override
+        public boolean contains(Object o)
+        {
+            return set.contains(o);
+        }
 
-    /* ------------------------------------------------------------ */
-    @Deprecated
-    public String getProtocol()
-    {
-        return _sslContextFactory.getProtocol();
-    }
+        @Override
+        public boolean containsAll(Collection<?> c)
+        {
+            return set.containsAll(c);
+        }
 
-    /* ------------------------------------------------------------ */
-    @Deprecated
-    public void setProtocol(String protocol)
-    {
-        _sslContextFactory.setProtocol(protocol);
-    }
+        @Override
+        public Iterator<ContentDecoder.Factory> iterator()
+        {
+            final Iterator<ContentDecoder.Factory> iterator = set.iterator();
+            return new Iterator<ContentDecoder.Factory>()
+            {
+                @Override
+                public boolean hasNext()
+                {
+                    return iterator.hasNext();
+                }
 
-    /* ------------------------------------------------------------ */
-    @Deprecated
-    public String getProvider()
-    {
-        return _sslContextFactory.getProvider();
-    }
+                @Override
+                public ContentDecoder.Factory next()
+                {
+                    return iterator.next();
+                }
 
-    /* ------------------------------------------------------------ */
-    @Deprecated
-    public void setProvider(String provider)
-    {
-        _sslContextFactory.setProvider(provider);
-    }
+                @Override
+                public void remove()
+                {
+                    iterator.remove();
+                    invalidate();
+                }
+            };
+        }
 
-    /* ------------------------------------------------------------ */
-    @Deprecated
-    public String getSecureRandomAlgorithm()
-    {
-        return _sslContextFactory.getSecureRandomAlgorithm();
-    }
+        @Override
+        public Object[] toArray()
+        {
+            return set.toArray();
+        }
 
-    /* ------------------------------------------------------------ */
-    @Deprecated
-    public void setSecureRandomAlgorithm(String secureRandomAlgorithm)
-    {
-        _sslContextFactory.setSecureRandomAlgorithm(secureRandomAlgorithm);
-    }
+        @Override
+        public <T> T[] toArray(T[] a)
+        {
+            return set.toArray(a);
+        }
 
-    private static class LocalQueuedThreadPool extends QueuedThreadPool
-    {
+        private void invalidate()
+        {
+            if (set.isEmpty())
+            {
+                encodingField = null;
+            }
+            else
+            {
+                StringBuilder value = new StringBuilder();
+                for (Iterator<ContentDecoder.Factory> iterator = set.iterator(); iterator.hasNext();)
+                {
+                    ContentDecoder.Factory decoderFactory = iterator.next();
+                    value.append(decoderFactory.getEncoding());
+                    if (iterator.hasNext())
+                        value.append(",");
+                }
+                encodingField = new HttpField(HttpHeader.ACCEPT_ENCODING, value.toString());
+            }
+        }
     }
-
 }
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClientTransport.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClientTransport.java
new file mode 100644
index 0000000..4d16d0f
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClientTransport.java
@@ -0,0 +1,71 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.net.SocketAddress;
+import java.util.Map;
+
+import org.eclipse.jetty.io.ClientConnectionFactory;
+
+/**
+ * {@link HttpClientTransport} represents what transport implementations should provide
+ * in order to plug-in a different transport for {@link HttpClient}.
+ * <p/>
+ * While the {@link HttpClient} APIs define the HTTP semantic (request, response, headers, etc.)
+ * <em>how</em> a HTTP exchange is carried over the network depends on implementations of this class.
+ * <p/>
+ * The default implementation uses the HTTP protocol to carry over the network the HTTP exchange,
+ * but the HTTP exchange may also be carried using the SPDY protocol or the FCGI protocol or, in future,
+ * other protocols.
+ */
+public interface HttpClientTransport extends ClientConnectionFactory
+{
+    public static final String HTTP_DESTINATION_CONTEXT_KEY = "http.destination";
+    public static final String HTTP_CONNECTION_PROMISE_CONTEXT_KEY = "http.connection.promise";
+
+    /**
+     * Sets the {@link HttpClient} instance on this transport.
+     * <p />
+     * This is needed because of a chicken-egg problem: in order to create the {@link HttpClient}
+     * a {@link HttpClientTransport} is needed, that therefore cannot have a reference yet to the
+     * {@link HttpClient}.
+     *
+     * @param client the {@link HttpClient} that uses this transport.
+     */
+    public void setHttpClient(HttpClient client);
+
+    /**
+     * Creates a new, transport-specific, {@link HttpDestination} object.
+     * <p />
+     * {@link HttpDestination} controls the destination-connection cardinality: protocols like
+     * HTTP have 1-N cardinality, while multiplexed protocols like SPDY have a 1-1 cardinality.
+     *
+     * @param origin the destination origin
+     * @return a new, transport-specific, {@link HttpDestination} object
+     */
+    public HttpDestination newHttpDestination(Origin origin);
+
+    /**
+     * Establishes a physical connection to the given {@code address}.
+     *
+     * @param address the address to connect to
+     * @param context the context information to establish the connection
+     */
+    public void connect(SocketAddress address, Map<String, Object> context);
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java
new file mode 100644
index 0000000..e859d72
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java
@@ -0,0 +1,175 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.net.CookieStore;
+import java.net.HttpCookie;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jetty.client.api.Authentication;
+import org.eclipse.jetty.client.api.Connection;
+import org.eclipse.jetty.client.api.ContentProvider;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.http.HttpField;
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpHeaderValue;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpVersion;
+
+public abstract class HttpConnection implements Connection
+{
+    private static final HttpField CHUNKED_FIELD = new HttpField(HttpHeader.TRANSFER_ENCODING, HttpHeaderValue.CHUNKED);
+
+    private final HttpDestination destination;
+
+    protected HttpConnection(HttpDestination destination)
+    {
+        this.destination = destination;
+    }
+
+    public HttpClient getHttpClient()
+    {
+        return destination.getHttpClient();
+    }
+
+    public HttpDestination getHttpDestination()
+    {
+        return destination;
+    }
+
+    @Override
+    public void send(Request request, Response.CompleteListener listener)
+    {
+        ArrayList<Response.ResponseListener> listeners = new ArrayList<>(2);
+        if (request.getTimeout() > 0)
+        {
+            TimeoutCompleteListener timeoutListener = new TimeoutCompleteListener(request);
+            timeoutListener.schedule(getHttpClient().getScheduler());
+            listeners.add(timeoutListener);
+        }
+        if (listener != null)
+            listeners.add(listener);
+
+        HttpExchange exchange = new HttpExchange(getHttpDestination(), (HttpRequest)request, listeners);
+
+        send(exchange);
+    }
+
+    protected abstract void send(HttpExchange exchange);
+
+    protected void normalizeRequest(Request request)
+    {
+        String method = request.getMethod();
+        HttpVersion version = request.getVersion();
+        HttpFields headers = request.getHeaders();
+        ContentProvider content = request.getContent();
+        ProxyConfiguration.Proxy proxy = destination.getProxy();
+
+        // Make sure the path is there
+        String path = request.getPath();
+        if (path.trim().length() == 0)
+        {
+            path = "/";
+            request.path(path);
+        }
+        if (proxy != null && !HttpMethod.CONNECT.is(method))
+        {
+            path = request.getURI().toString();
+            request.path(path);
+        }
+
+        // If we are HTTP 1.1, add the Host header
+        if (version.getVersion() > 10)
+        {
+            if (!headers.containsKey(HttpHeader.HOST.asString()))
+                headers.put(getHttpDestination().getHostField());
+        }
+
+        // Add content headers
+        if (content != null)
+        {
+            if (content instanceof ContentProvider.Typed)
+            {
+                if (!headers.containsKey(HttpHeader.CONTENT_TYPE.asString()))
+                {
+                    String contentType = ((ContentProvider.Typed)content).getContentType();
+                    if (contentType != null)
+                        headers.put(HttpHeader.CONTENT_TYPE, contentType);
+                }
+            }
+            long contentLength = content.getLength();
+            if (contentLength >= 0)
+            {
+                if (!headers.containsKey(HttpHeader.CONTENT_LENGTH.asString()))
+                    headers.put(HttpHeader.CONTENT_LENGTH, String.valueOf(contentLength));
+            }
+            else
+            {
+                if (!headers.containsKey(HttpHeader.TRANSFER_ENCODING.asString()))
+                    headers.put(CHUNKED_FIELD);
+            }
+        }
+
+        // Cookies
+        CookieStore cookieStore = getHttpClient().getCookieStore();
+        if (cookieStore != null)
+        {
+            URI uri = request.getURI();
+            StringBuilder cookies = null;
+            if (uri != null)
+                cookies = convertCookies(cookieStore.get(uri), null);
+            cookies = convertCookies(request.getCookies(), cookies);
+            if (cookies != null)
+                request.header(HttpHeader.COOKIE.asString(), cookies.toString());
+        }
+
+        // Authorization
+        URI authenticationURI = proxy != null ? proxy.getURI() : request.getURI();
+        if (authenticationURI != null)
+        {
+            Authentication.Result authnResult = getHttpClient().getAuthenticationStore().findAuthenticationResult(authenticationURI);
+            if (authnResult != null)
+                authnResult.apply(request);
+        }
+    }
+
+    private StringBuilder convertCookies(List<HttpCookie> cookies, StringBuilder builder)
+    {
+        for (int i = 0; i < cookies.size(); ++i)
+        {
+            if (builder == null)
+                builder = new StringBuilder();
+            if (builder.length() > 0)
+                builder.append("; ");
+            HttpCookie cookie = cookies.get(i);
+            builder.append(cookie.getName()).append("=").append(cookie.getValue());
+        }
+        return builder;
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s@%h", getClass().getSimpleName(), this);
+    }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpContent.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpContent.java
new file mode 100644
index 0000000..237b835
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpContent.java
@@ -0,0 +1,217 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.io.Closeable;
+import java.nio.ByteBuffer;
+import java.util.Collections;
+import java.util.Iterator;
+
+import org.eclipse.jetty.client.api.ContentProvider;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * {@link HttpContent} is a stateful, linear representation of the request content provided
+ * by a {@link ContentProvider} that can be traversed one-way to obtain content buffers to
+ * send to a HTTP server.
+ * <p />
+ * {@link HttpContent} offers the notion of a one-way cursor to traverse the content.
+ * The cursor starts in a virtual "before" position and can be advanced using {@link #advance()}
+ * until it reaches a virtual "after" position where the content is fully consumed.
+ * <pre>
+ *      +---+  +---+  +---+  +---+  +---+
+ *      |   |  |   |  |   |  |   |  |   |
+ *      +---+  +---+  +---+  +---+  +---+
+ *   ^           ^                    ^    ^
+ *   |           | --> advance()      |    |
+ *   |           |                  last   |
+ *   |           |                         |
+ * before        |                        after
+ *               |
+ *            current
+ * </pre>
+ * At each valid (non-before and non-after) cursor position, {@link HttpContent} provides the following state:
+ * <ul>
+ * <li>the buffer containing the content to send, via {@link #getByteBuffer()}</li>
+ * <li>a copy of the content buffer that can be used for notifications, via {@link #getContent()}</li>
+ * <li>whether the buffer to write is the last one, via {@link #isLast()}</li>
+ * </ul>
+ * {@link HttpContent} may not have content, if the related {@link ContentProvider} is {@code null}, and this
+ * is reflected by {@link #hasContent()}.
+ * <p />
+ * {@link HttpContent} may have {@link AsyncContentProvider deferred content}, in which case {@link #advance()}
+ * moves the cursor to a position that provides {@code null} {@link #getByteBuffer() buffer} and
+ * {@link #getContent() content}. When the deferred content is available, a further call to {@link #advance()}
+ * will move the cursor to a position that provides non {@code null} buffer and content.
+ */
+public class HttpContent implements Callback, Closeable
+{
+    private static final Logger LOG = Log.getLogger(HttpContent.class);
+    private static final ByteBuffer AFTER = ByteBuffer.allocate(0);
+
+    private final ContentProvider provider;
+    private final Iterator<ByteBuffer> iterator;
+    private ByteBuffer buffer;
+    private volatile ByteBuffer content;
+
+    public HttpContent(ContentProvider provider)
+    {
+        this.provider = provider;
+        this.iterator = provider == null ? Collections.<ByteBuffer>emptyIterator() : provider.iterator();
+    }
+
+    /**
+     * @return whether there is any content at all
+     */
+    public boolean hasContent()
+    {
+        return provider != null;
+    }
+
+    /**
+     * @return whether the cursor points to the last content
+     */
+    public boolean isLast()
+    {
+        return !iterator.hasNext();
+    }
+
+    /**
+     * @return the {@link ByteBuffer} containing the content at the cursor's position
+     */
+    public ByteBuffer getByteBuffer()
+    {
+        return buffer;
+    }
+
+    /**
+     * @return a {@link ByteBuffer#slice()} of {@link #getByteBuffer()} at the cursor's position
+     */
+    public ByteBuffer getContent()
+    {
+        return content;
+    }
+
+    /**
+     * Advances the cursor to the next block of content.
+     * <p />
+     * The next block of content may be valid (which yields a non-null buffer
+     * returned by {@link #getByteBuffer()}), but may also be deferred
+     * (which yields a null buffer returned by {@link #getByteBuffer()}).
+     * <p />
+     * If the block of content pointed by the new cursor position is valid, this method returns true.
+     *
+     * @return true if there is content at the new cursor's position, false otherwise.
+     */
+    public boolean advance()
+    {
+        boolean advanced;
+        boolean hasNext;
+        ByteBuffer bytes;
+        if (iterator instanceof Synchronizable)
+        {
+            synchronized (((Synchronizable)iterator).getLock())
+            {
+                advanced = iterator.hasNext();
+                bytes = advanced ? iterator.next() : null;
+                hasNext = advanced && iterator.hasNext();
+            }
+        }
+        else
+        {
+            advanced = iterator.hasNext();
+            bytes = advanced ? iterator.next() : null;
+            hasNext = advanced && iterator.hasNext();
+        }
+
+        if (advanced)
+        {
+            buffer = bytes;
+            content = bytes == null ? null : bytes.slice();
+            if (LOG.isDebugEnabled())
+                LOG.debug("Advanced content to {} chunk {}", hasNext ? "next" : "last", bytes);
+            return bytes != null;
+        }
+        else
+        {
+            if (content != AFTER)
+            {
+                content = buffer = AFTER;
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Advanced content past last chunk");
+            }
+            return false;
+        }
+    }
+
+    /**
+     * @return whether the cursor has been advanced past the {@link #isLast() last} position.
+     */
+    public boolean isConsumed()
+    {
+        return content == AFTER;
+    }
+
+    @Override
+    public void succeeded()
+    {
+        if (isConsumed())
+            return;
+        if (iterator instanceof Callback)
+            ((Callback)iterator).succeeded();
+    }
+
+    @Override
+    public void failed(Throwable x)
+    {
+        if (isConsumed())
+            return;
+        if (iterator instanceof Callback)
+            ((Callback)iterator).failed(x);
+    }
+
+    @Override
+    public void close()
+    {
+        try
+        {
+            if (iterator instanceof Closeable)
+                ((Closeable)iterator).close();
+        }
+        catch (Exception x)
+        {
+            LOG.ignore(x);
+        }
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s@%x - has=%b,last=%b,consumed=%b,buffer=%s",
+                getClass().getSimpleName(),
+                hashCode(),
+                hasContent(),
+                isLast(),
+                isConsumed(),
+                BufferUtil.toDetailString(getContent()));
+    }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpContentResponse.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpContentResponse.java
new file mode 100644
index 0000000..ddff5e7
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpContentResponse.java
@@ -0,0 +1,138 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.StandardCharsets;
+import java.nio.charset.UnsupportedCharsetException;
+import java.util.List;
+
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpVersion;
+
+public class HttpContentResponse implements ContentResponse
+{
+    private final Response response;
+    private final byte[] content;
+    private final String mediaType;
+    private final String encoding;
+
+    public HttpContentResponse(Response response, byte[] content, String mediaType, String encoding)
+    {
+        this.response = response;
+        this.content = content;
+        this.mediaType = mediaType;
+        this.encoding = encoding;
+    }
+
+    @Override
+    public Request getRequest()
+    {
+        return response.getRequest();
+    }
+
+    @Override
+    public <T extends ResponseListener> List<T> getListeners(Class<T> listenerClass)
+    {
+        return response.getListeners(listenerClass);
+    }
+
+    @Override
+    public HttpVersion getVersion()
+    {
+        return response.getVersion();
+    }
+
+    @Override
+    public int getStatus()
+    {
+        return response.getStatus();
+    }
+
+    @Override
+    public String getReason()
+    {
+        return response.getReason();
+    }
+
+    @Override
+    public HttpFields getHeaders()
+    {
+        return response.getHeaders();
+    }
+
+    @Override
+    public boolean abort(Throwable cause)
+    {
+        return response.abort(cause);
+    }
+
+    @Override
+    public String getMediaType()
+    {
+        return mediaType;
+    }
+
+    @Override
+    public String getEncoding()
+    {
+        return encoding;
+    }
+
+    @Override
+    public byte[] getContent()
+    {
+        return content;
+    }
+
+    @Override
+    public String getContentAsString()
+    {
+        String encoding = this.encoding;
+        if (encoding == null)
+        {
+            return new String(getContent(), StandardCharsets.UTF_8);
+        }
+        else
+        {
+            try
+            {
+                return new String(getContent(), encoding);
+            }
+            catch (UnsupportedEncodingException e)
+            {
+                throw new UnsupportedCharsetException(encoding);
+            }
+        }
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s[%s %d %s - %d bytes]",
+                HttpContentResponse.class.getSimpleName(),
+                getVersion(),
+                getStatus(),
+                getReason(),
+                getContent().length);
+    }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConversation.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConversation.java
new file mode 100644
index 0000000..2a5d0ae
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConversation.java
@@ -0,0 +1,149 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.util.ArrayList;
+import java.util.Deque;
+import java.util.List;
+import java.util.concurrent.ConcurrentLinkedDeque;
+
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.util.AttributesMap;
+
+public class HttpConversation extends AttributesMap
+{
+    private final Deque<HttpExchange> exchanges = new ConcurrentLinkedDeque<>();
+    private volatile List<Response.ResponseListener> listeners;
+
+    public Deque<HttpExchange> getExchanges()
+    {
+        return exchanges;
+    }
+
+    /**
+     * Returns the list of response listeners that needs to be notified of response events.
+     * This list changes as the conversation proceeds, as follows:
+     * <ol>
+     * <li>
+     *     request R1 send => conversation.updateResponseListeners(null)
+     *     <ul>
+     *         <li>exchanges in conversation: E1</li>
+     *         <li>listeners to be notified: E1.listeners</li>
+     *     </ul>
+     * </li>
+     * <li>
+     *     response R1 arrived, 401 => conversation.updateResponseListeners(AuthenticationProtocolHandler.listener)
+     *     <ul>
+     *         <li>exchanges in conversation: E1</li>
+     *         <li>listeners to be notified: AuthenticationProtocolHandler.listener</li>
+     *     </ul>
+     * </li>
+     * <li>
+     *     request R2 send => conversation.updateResponseListeners(null)
+     *     <ul>
+     *         <li>exchanges in conversation: E1 + E2</li>
+     *         <li>listeners to be notified: E2.listeners + E1.listeners</li>
+     *     </ul>
+     * </li>
+     * <li>
+     *     response R2 arrived, 302 => conversation.updateResponseListeners(RedirectProtocolHandler.listener)
+     *     <ul>
+     *         <li>exchanges in conversation: E1 + E2</li>
+     *         <li>listeners to be notified: E2.listeners + RedirectProtocolHandler.listener</li>
+     *     </ul>
+     * </li>
+     * <li>
+     *     request R3 send => conversation.updateResponseListeners(null)
+     *     <ul>
+     *         <li>exchanges in conversation: E1 + E2 + E3</li>
+     *         <li>listeners to be notified: E3.listeners + E1.listeners</li>
+     *     </ul>
+     * </li>
+     * <li>
+     *     response R3 arrived, 200 => conversation.updateResponseListeners(null)
+     *     <ul>
+     *         <li>exchanges in conversation: E1 + E2 + E3</li>
+     *         <li>listeners to be notified: E3.listeners + E1.listeners</li>
+     *     </ul>
+     * </li>
+     * </ol>
+     * Basically the override conversation listener replaces the first exchange response listener,
+     * and we also notify the last exchange response listeners (if it's not also the first).
+     *
+     * This scheme allows for protocol handlers to not worry about other protocol handlers, or to worry
+     * too much about notifying the first exchange response listeners, but still allowing a protocol
+     * handler to perform completion activities while another protocol handler performs new ones (as an
+     * example, the {@link AuthenticationProtocolHandler} stores the successful authentication credentials
+     * while the {@link RedirectProtocolHandler} performs a redirect).
+     *
+     * @return the list of response listeners that needs to be notified of response events
+     */
+    public List<Response.ResponseListener> getResponseListeners()
+    {
+        return listeners;
+    }
+
+    /**
+     * Requests to update the response listener, eventually using the given override response listener,
+     * that must be notified instead of the first exchange response listeners.
+     * This works in conjunction with {@link #getResponseListeners()}, returning the appropriate response
+     * listeners that needs to be notified of response events.
+     *
+     * @param overrideListener the override response listener
+     */
+    public void updateResponseListeners(Response.ResponseListener overrideListener)
+    {
+        // Create a new instance to avoid that iterating over the listeners
+        // will notify a listener that may send a new request and trigger
+        // another call to this method which will build different listeners
+        // which may be iterated over when the iteration continues.
+        List<Response.ResponseListener> listeners = new ArrayList<>();
+        HttpExchange firstExchange = exchanges.peekFirst();
+        HttpExchange lastExchange = exchanges.peekLast();
+        if (firstExchange == lastExchange)
+        {
+            if (overrideListener != null)
+                listeners.add(overrideListener);
+            else
+                listeners.addAll(firstExchange.getResponseListeners());
+        }
+        else
+        {
+            // Order is important, we want to notify the last exchange first
+            listeners.addAll(lastExchange.getResponseListeners());
+            if (overrideListener != null)
+                listeners.add(overrideListener);
+            else
+                listeners.addAll(firstExchange.getResponseListeners());
+        }
+        this.listeners = listeners;
+    }
+
+    public boolean abort(Throwable cause)
+    {
+        HttpExchange exchange = exchanges.peekLast();
+        return exchange != null && exchange.abort(cause);
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s[%x]", HttpConversation.class.getSimpleName(), hashCode());
+    }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpDestination.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpDestination.java
index 6811836..7538921 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpDestination.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpDestination.java
@@ -18,756 +18,265 @@
 
 package org.eclipse.jetty.client;
 
+import java.io.Closeable;
 import java.io.IOException;
-import java.lang.reflect.Constructor;
-import java.net.ProtocolException;
+import java.nio.channels.AsynchronousCloseException;
 import java.util.ArrayList;
-import java.util.LinkedList;
 import java.util.List;
-import java.util.concurrent.ArrayBlockingQueue;
-import java.util.concurrent.BlockingQueue;
+import java.util.Queue;
 import java.util.concurrent.RejectedExecutionException;
 
-import org.eclipse.jetty.client.HttpClient.Connector;
-import org.eclipse.jetty.client.security.Authentication;
-import org.eclipse.jetty.client.security.SecurityListener;
-import org.eclipse.jetty.http.HttpCookie;
-import org.eclipse.jetty.http.HttpHeaders;
-import org.eclipse.jetty.http.HttpMethods;
-import org.eclipse.jetty.http.HttpStatus;
-import org.eclipse.jetty.http.PathMap;
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.ByteArrayBuffer;
-import org.eclipse.jetty.io.Connection;
-import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.util.component.AggregateLifeCycle;
+import org.eclipse.jetty.client.api.Connection;
+import org.eclipse.jetty.client.api.Destination;
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.http.HttpField;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpScheme;
+import org.eclipse.jetty.io.ClientConnectionFactory;
+import org.eclipse.jetty.io.ssl.SslClientConnectionFactory;
+import org.eclipse.jetty.util.BlockingArrayQueue;
+import org.eclipse.jetty.util.Promise;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
 import org.eclipse.jetty.util.component.Dumpable;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.util.ssl.SslContextFactory;
 
-/**
- * @version $Revision: 879 $ $Date: 2009-09-11 16:13:28 +0200 (Fri, 11 Sep 2009) $
- */
-public class HttpDestination implements Dumpable
+public abstract class HttpDestination implements Destination, Closeable, Dumpable
 {
-    private static final Logger LOG = Log.getLogger(HttpDestination.class);
-
-    private final List<HttpExchange> _exchanges = new LinkedList<HttpExchange>();
-    private final List<AbstractHttpConnection> _connections = new LinkedList<AbstractHttpConnection>();
-    private final BlockingQueue<Object> _reservedConnections = new ArrayBlockingQueue<Object>(10, true);
-    private final List<AbstractHttpConnection> _idleConnections = new ArrayList<AbstractHttpConnection>();
-    private final HttpClient _client;
-    private final Address _address;
-    private final boolean _ssl;
-    private final SslContextFactory _sslContextFactory;
-    private final ByteArrayBuffer _hostHeader;
-    private volatile int _maxConnections;
-    private volatile int _maxQueueSize;
-    private int _pendingConnections = 0;
-    private int _pendingReservedConnections = 0;
-    private volatile Address _proxy;
-    private Authentication _proxyAuthentication;
-    private PathMap _authorizations;
-    private List<HttpCookie> _cookies;
-
-    HttpDestination(HttpClient client, Address address, boolean ssl, SslContextFactory sslContextFactory)
-    {
-        _client = client;
-        _address = address;
-        _ssl = ssl;
-        _sslContextFactory = sslContextFactory;
-        _maxConnections = _client.getMaxConnectionsPerAddress();
-        _maxQueueSize = _client.getMaxQueueSizePerAddress();
-        String addressString = address.getHost();
-        if (address.getPort() != (_ssl ? 443 : 80))
-            addressString += ":" + address.getPort();
-        _hostHeader = new ByteArrayBuffer(addressString);
-    }
+    protected static final Logger LOG = Log.getLogger(HttpDestination.class);
 
-    public HttpClient getHttpClient()
-    {
-        return _client;
-    }
+    private final HttpClient client;
+    private final Origin origin;
+    private final Queue<HttpExchange> exchanges;
+    private final RequestNotifier requestNotifier;
+    private final ResponseNotifier responseNotifier;
+    private final ProxyConfiguration.Proxy proxy;
+    private final ClientConnectionFactory connectionFactory;
+    private final HttpField hostField;
 
-    public Address getAddress()
+    public HttpDestination(HttpClient client, Origin origin)
     {
-        return _address;
-    }
+        this.client = client;
+        this.origin = origin;
 
-    public boolean isSecure()
-    {
-        return _ssl;
-    }
+        this.exchanges = newExchangeQueue(client);
 
-    public SslContextFactory getSslContextFactory()
-    {
-        return _sslContextFactory;
-    }
+        this.requestNotifier = new RequestNotifier(client);
+        this.responseNotifier = new ResponseNotifier();
 
-    public Buffer getHostHeader()
-    {
-        return _hostHeader;
-    }
+        ProxyConfiguration proxyConfig = client.getProxyConfiguration();
+        proxy = proxyConfig.match(origin);
+        ClientConnectionFactory connectionFactory = client.getTransport();
+        if (proxy != null)
+        {
+            connectionFactory = proxy.newClientConnectionFactory(connectionFactory);
+        }
+        else
+        {
+            if (HttpScheme.HTTPS.is(getScheme()))
+                connectionFactory = newSslClientConnectionFactory(connectionFactory);
+        }
+        this.connectionFactory = connectionFactory;
 
-    public int getMaxConnections()
-    {
-        return _maxConnections;
+        String host = getHost();
+        if (!client.isDefaultPort(getScheme(), getPort()))
+            host += ":" + getPort();
+        hostField = new HttpField(HttpHeader.HOST, host);
     }
 
-    public void setMaxConnections(int maxConnections)
+    protected Queue<HttpExchange> newExchangeQueue(HttpClient client)
     {
-        this._maxConnections = maxConnections;
+        return new BlockingArrayQueue<>(client.getMaxRequestsQueuedPerDestination());
     }
 
-    public int getMaxQueueSize()
+    protected ClientConnectionFactory newSslClientConnectionFactory(ClientConnectionFactory connectionFactory)
     {
-        return _maxQueueSize;
+        return new SslClientConnectionFactory(client.getSslContextFactory(), client.getByteBufferPool(), client.getExecutor(), connectionFactory);
     }
 
-    public void setMaxQueueSize(int maxQueueSize)
+    public HttpClient getHttpClient()
     {
-        this._maxQueueSize = maxQueueSize;
+        return client;
     }
 
-    public int getConnections()
+    public Origin getOrigin()
     {
-        synchronized (this)
-        {
-            return _connections.size();
-        }
+        return origin;
     }
 
-    public int getIdleConnections()
+    public Queue<HttpExchange> getHttpExchanges()
     {
-        synchronized (this)
-        {
-            return _idleConnections.size();
-        }
+        return exchanges;
     }
 
-    public void addAuthorization(String pathSpec, Authentication authorization)
+    public RequestNotifier getRequestNotifier()
     {
-        synchronized (this)
-        {
-            if (_authorizations == null)
-                _authorizations = new PathMap();
-            _authorizations.put(pathSpec, authorization);
-        }
-
-        // TODO query and remove methods
+        return requestNotifier;
     }
 
-    public void addCookie(HttpCookie cookie)
-    {
-        synchronized (this)
-        {
-            if (_cookies == null)
-                _cookies = new ArrayList<HttpCookie>();
-            _cookies.add(cookie);
-        }
-
-        // TODO query, remove and age methods
-    }
-    
-    public void clearCookies()
+    public ResponseNotifier getResponseNotifier()
     {
-        synchronized (this)
-        {
-            _cookies.clear();
-        }
-
+        return responseNotifier;
     }
 
-    /**
-     * Get a connection. We either get an idle connection if one is available, or
-     * we make a new connection, if we have not yet reached maxConnections. If we
-     * have reached maxConnections, we wait until the number reduces.
-     *
-     * @param timeout max time prepared to block waiting to be able to get a connection
-     * @return a HttpConnection for this destination
-     * @throws IOException if an I/O error occurs
-     */
-    private AbstractHttpConnection getConnection(long timeout) throws IOException
+    public ProxyConfiguration.Proxy getProxy()
     {
-        AbstractHttpConnection connection = null;
-
-        while ((connection == null) && (connection = getIdleConnection()) == null && timeout > 0)
-        {
-            boolean startConnection = false;
-            synchronized (this)
-            {
-                int totalConnections = _connections.size() + _pendingConnections;
-                if (totalConnections < _maxConnections)
-                {
-                    _pendingReservedConnections++;
-                    startConnection = true;
-                }
-            }
-
-            if (startConnection)
-            {
-                startNewConnection();
-                try
-                {
-                    Object o = _reservedConnections.take();
-                    if (o instanceof AbstractHttpConnection)
-                    {
-                        connection = (AbstractHttpConnection)o;
-                    }
-                    else
-                        throw (IOException)o;
-                }
-                catch (InterruptedException e)
-                {
-                    LOG.ignore(e);
-                }
-            }
-            else
-            {
-                try
-                {
-                    Thread.currentThread();
-                    Thread.sleep(200);
-                    timeout -= 200;
-                }
-                catch (InterruptedException e)
-                {
-                    LOG.ignore(e);
-                }
-            }
-        }
-        return connection;
+        return proxy;
     }
 
-    public AbstractHttpConnection reserveConnection(long timeout) throws IOException
+    public ClientConnectionFactory getClientConnectionFactory()
     {
-        AbstractHttpConnection connection = getConnection(timeout);
-        if (connection != null)
-            connection.setReserved(true);
-        return connection;
+        return connectionFactory;
     }
 
-    public AbstractHttpConnection getIdleConnection() throws IOException
+    @Override
+    public String getScheme()
     {
-        AbstractHttpConnection connection = null;
-        while (true)
-        {
-            synchronized (this)
-            {
-                if (connection != null)
-                {
-                    _connections.remove(connection);
-                    connection.close();
-                    connection = null;
-                }
-                if (_idleConnections.size() > 0)
-                    connection = _idleConnections.remove(_idleConnections.size() - 1);
-            }
-
-            if (connection == null)
-            {
-                return null;
-            }
-
-            // Check if the connection was idle,
-            // but it expired just a moment ago
-            if (connection.cancelIdleTimeout())
-            {
-                return connection;
-            }
-        }
+        return origin.getScheme();
     }
 
-    protected void startNewConnection()
+    @Override
+    public String getHost()
     {
-        try
-        {
-            synchronized (this)
-            {
-                _pendingConnections++;
-            }
-            final Connector connector = _client._connector;
-            if (connector != null)
-                connector.startConnection(this);
-        }
-        catch (Exception e)
-        {
-            LOG.debug(e);
-            onConnectionFailed(e);
-        }
+        // InetSocketAddress.getHostString() transforms the host string
+        // in case of IPv6 addresses, so we return the original host string
+        return origin.getAddress().getHost();
     }
 
-    public void onConnectionFailed(Throwable throwable)
+    @Override
+    public int getPort()
     {
-        Throwable connect_failure = null;
-
-        boolean startConnection = false;
-        synchronized (this)
-        {
-            _pendingConnections--;
-            if (_pendingReservedConnections > 0)
-            {
-                connect_failure = throwable;
-                _pendingReservedConnections--;
-            }
-            else if (_exchanges.size() > 0)
-            {
-                HttpExchange ex = _exchanges.remove(0);
-                if (ex.setStatus(HttpExchange.STATUS_EXCEPTED))
-                    ex.getEventListener().onConnectionFailed(throwable);
-
-                // Since an existing connection had failed, we need to create a
-                // connection if the  queue is not empty and client is running.
-                if (!_exchanges.isEmpty() && _client.isStarted())
-                    startConnection = true;
-            }
-        }
-
-        if (startConnection)
-            startNewConnection();
-
-        if (connect_failure != null)
-        {
-            try
-            {
-                _reservedConnections.put(connect_failure);
-            }
-            catch (InterruptedException e)
-            {
-                LOG.ignore(e);
-            }
-        }
+        return origin.getAddress().getPort();
     }
 
-    public void onException(Throwable throwable)
+    public Origin.Address getConnectAddress()
     {
-        synchronized (this)
-        {
-            _pendingConnections--;
-            if (_exchanges.size() > 0)
-            {
-                HttpExchange ex = _exchanges.remove(0);
-                if (ex.setStatus(HttpExchange.STATUS_EXCEPTED))
-                    ex.getEventListener().onException(throwable);
-            }
-        }
+        return proxy == null ? origin.getAddress() : proxy.getAddress();
     }
 
-    public void onNewConnection(final AbstractHttpConnection connection) throws IOException
+    public HttpField getHostField()
     {
-        Connection reservedConnection = null;
-
-        synchronized (this)
-        {
-            _pendingConnections--;
-            _connections.add(connection);
-
-            if (_pendingReservedConnections > 0)
-            {
-                reservedConnection = connection;
-                _pendingReservedConnections--;
-            }
-            else
-            {
-                // Establish the tunnel if needed
-                EndPoint endPoint = connection.getEndPoint();
-                if (isProxied() && endPoint instanceof SelectConnector.UpgradableEndPoint)
-                {
-                    SelectConnector.UpgradableEndPoint proxyEndPoint = (SelectConnector.UpgradableEndPoint)endPoint;
-                    ConnectExchange connect = new ConnectExchange(getAddress(), proxyEndPoint);
-                    connect.setAddress(getProxy());
-                    LOG.debug("Establishing tunnel to {} via {}", getAddress(), getProxy());
-                    send(connection, connect);
-                }
-                else
-                {
-                    // Another connection stole the exchange that caused the creation of this connection ?
-                    if (_exchanges.size() == 0)
-                    {
-                        LOG.debug("No exchanges for new connection {}", connection);
-                        connection.setIdleTimeout();
-                        _idleConnections.add(connection);
-                    }
-                    else
-                    {
-                        HttpExchange exchange = _exchanges.remove(0);
-                        send(connection, exchange);
-                    }
-                }
-            }
-        }
-
-        if (reservedConnection != null)
-        {
-            try
-            {
-                _reservedConnections.put(reservedConnection);
-            }
-            catch (InterruptedException e)
-            {
-                LOG.ignore(e);
-            }
-        }
+        return hostField;
     }
 
-    public void returnConnection(AbstractHttpConnection connection, boolean close) throws IOException
+    protected void send(HttpRequest request, List<Response.ResponseListener> listeners)
     {
-        if (connection.isReserved())
-            connection.setReserved(false);
+        if (!getScheme().equalsIgnoreCase(request.getScheme()))
+            throw new IllegalArgumentException("Invalid request scheme " + request.getScheme() + " for destination " + this);
+        if (!getHost().equalsIgnoreCase(request.getHost()))
+            throw new IllegalArgumentException("Invalid request host " + request.getHost() + " for destination " + this);
+        int port = request.getPort();
+        if (port >= 0 && getPort() != port)
+            throw new IllegalArgumentException("Invalid request port " + port + " for destination " + this);
 
-        if (close)
-        {
-            try
-            {
-                connection.close();
-            }
-            catch (IOException e)
-            {
-                LOG.ignore(e);
-            }
-        }
-
-        if (!_client.isStarted())
-            return;
+        HttpExchange exchange = new HttpExchange(this, request, listeners);
 
-        if (!close && connection.getEndPoint().isOpen())
+        if (client.isRunning())
         {
-            synchronized (this)
+            if (enqueue(exchanges, exchange))
             {
-                if (_exchanges.size() == 0)
+                if (!client.isRunning() && exchanges.remove(exchange))
                 {
-                    connection.setIdleTimeout();
-                    _idleConnections.add(connection);
+                    request.abort(new RejectedExecutionException(client + " is stopping"));
                 }
                 else
                 {
-                    HttpExchange ex = _exchanges.remove(0);
-                    send(connection, ex);
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("Queued {} for {}", request, this);
+                    requestNotifier.notifyQueued(request);
+                    send();
                 }
-                this.notifyAll();
-            }
-        }
-        else
-        {
-            boolean startConnection = false;
-            boolean remove = false;
-            synchronized (this)
-            {
-                _connections.remove(connection);
-                if (_exchanges.isEmpty())
-                    remove=_client.isRemoveIdleDestinations() && ( _cookies==null || _cookies.isEmpty() ) &&_connections.isEmpty() && _idleConnections.isEmpty();
-                else if (_client.isStarted())
-                    startConnection = true;
             }
-
-            if (startConnection)
-                startNewConnection();
-            if (remove)
-                _client.removeDestination(this);
-        }
-    }
-
-    public void returnIdleConnection(AbstractHttpConnection connection)
-    {
-        // TODO work out the real idle time;
-        long idleForMs = connection.getEndPoint() != null ? connection.getEndPoint().getMaxIdleTime() : -1;
-        connection.onIdleExpired(idleForMs);
-
-        boolean startConnection = false;
-        boolean remove = false;
-        synchronized (this)
-        {
-            _idleConnections.remove(connection);
-            _connections.remove(connection);
-
-            if (_exchanges.isEmpty())
-                remove=_client.isRemoveIdleDestinations() && ( _cookies==null || _cookies.isEmpty() ) &&_connections.isEmpty() && _idleConnections.isEmpty();
-            else if (_client.isStarted())
-                startConnection = true;
-        }
-
-        if (startConnection)
-            startNewConnection();
-        if (remove)
-            _client.removeDestination(this);
-    }
-
-    public void send(HttpExchange ex) throws IOException
-    {
-        ex.setStatus(HttpExchange.STATUS_WAITING_FOR_CONNECTION);
-
-        LinkedList<String> listeners = _client.getRegisteredListeners();
-        if (listeners != null)
-        {
-            // Add registered listeners, fail if we can't load them
-            for (int i = listeners.size(); i > 0; --i)
+            else
             {
-                String listenerClass = listeners.get(i - 1);
-                try
-                {
-                    Class<?> listener = Class.forName(listenerClass);
-                    Constructor constructor = listener.getDeclaredConstructor(HttpDestination.class, HttpExchange.class);
-                    HttpEventListener elistener = (HttpEventListener)constructor.newInstance(this, ex);
-                    ex.setEventListener(elistener);
-                }
-                catch (final Exception e)
-                {
-                    throw new IOException("Unable to instantiate registered listener for destination: " + listenerClass)
-                    {
-                        {
-                            initCause(e);
-                        }
-                    };
-                }
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Max queue size {} exceeded by {} for {}", client.getMaxRequestsQueuedPerDestination(), request, this);
+                request.abort(new RejectedExecutionException("Max requests per destination " + client.getMaxRequestsQueuedPerDestination() + " exceeded for " + this));
             }
         }
-
-        // Security is supported by default and should be the first consulted
-        if (_client.hasRealms())
-        {
-            ex.setEventListener(new SecurityListener(this, ex));
-        }
-
-        doSend(ex);
-    }
-
-    public void resend(HttpExchange ex) throws IOException
-    {
-        ex.getEventListener().onRetry();
-        ex.reset();
-        doSend(ex);
-    }
-
-    protected void doSend(HttpExchange ex) throws IOException
-    {
-        // add cookies
-        // TODO handle max-age etc.
-        synchronized (this)
-        {
-            if (_cookies != null)
-            {
-                StringBuilder buf = null;
-                for (HttpCookie cookie : _cookies)
-                {
-                    if (buf == null)
-                        buf = new StringBuilder();
-                    else
-                        buf.append("; ");
-                    buf.append(cookie.getName()); // TODO quotes
-                    buf.append("=");
-                    buf.append(cookie.getValue()); // TODO quotes
-                }
-                if (buf != null)
-                    ex.addRequestHeader(HttpHeaders.COOKIE, buf.toString());
-            }  
-        }
-
-        // Add any known authorizations
-        if (_authorizations != null)
-        {
-            Authentication auth = (Authentication)_authorizations.match(ex.getRequestURI());
-            if (auth != null)
-                (auth).setCredentials(ex);
-        }
-
-        // Schedule the timeout here, before we queue the exchange
-        // so that we count also the queue time in the timeout
-        ex.scheduleTimeout(this);
-
-        AbstractHttpConnection connection = getIdleConnection();
-        if (connection != null)
-        {
-            send(connection, ex);
-        }
         else
         {
-            boolean startConnection = false;
-            synchronized (this)
-            {
-                if (_exchanges.size() == _maxQueueSize)
-                    throw new RejectedExecutionException("Queue full for address " + _address);
-
-                _exchanges.add(ex);
-                if (_connections.size() + _pendingConnections < _maxConnections)
-                    startConnection = true;
-            }
-
-            if (startConnection)
-                startNewConnection();
-        }
-    }
-
-    protected void exchangeExpired(HttpExchange exchange)
-    {
-        // The exchange may expire while waiting in the
-        // destination queue, make sure it is removed
-        synchronized (this)
-        {
-            _exchanges.remove(exchange);
+            request.abort(new RejectedExecutionException(client + " is stopped"));
         }
     }
 
-    protected void send(AbstractHttpConnection connection, HttpExchange exchange) throws IOException
+    protected boolean enqueue(Queue<HttpExchange> queue, HttpExchange exchange)
     {
-        synchronized (this)
-        {
-            // If server closes the connection, put the exchange back
-            // to the exchange queue and recycle the connection
-            if (!connection.send(exchange))
-            {
-                if (exchange.getStatus() <= HttpExchange.STATUS_WAITING_FOR_CONNECTION)
-                    _exchanges.add(0, exchange);
-                returnIdleConnection(connection);
-            }
-        }
+        return queue.offer(exchange);
     }
 
-    @Override
-    public synchronized String toString()
-    {
-        return String.format("HttpDestination@%x//%s:%d(%d/%d,%d,%d/%d)%n", hashCode(), _address.getHost(), _address.getPort(), _connections.size(), _maxConnections, _idleConnections.size(), _exchanges.size(), _maxQueueSize);
-    }
+    protected abstract void send();
 
-    public synchronized String toDetailString()
+    public void newConnection(Promise<Connection> promise)
     {
-        StringBuilder b = new StringBuilder();
-        b.append(toString());
-        b.append('\n');
-        synchronized (this)
-        {
-            for (AbstractHttpConnection connection : _connections)
-            {
-                b.append(connection.toDetailString());
-                if (_idleConnections.contains(connection))
-                    b.append(" IDLE");
-                b.append('\n');
-            }
-        }
-        b.append("--");
-        b.append('\n');
-
-        return b.toString();
+        createConnection(promise);
     }
 
-    public void setProxy(Address proxy)
+    protected void createConnection(Promise<Connection> promise)
     {
-        _proxy = proxy;
+        client.newConnection(this, promise);
     }
 
-    public Address getProxy()
+    public boolean remove(HttpExchange exchange)
     {
-        return _proxy;
+        return exchanges.remove(exchange);
     }
 
-    public Authentication getProxyAuthentication()
+    public void close()
     {
-        return _proxyAuthentication;
+        abort(new AsynchronousCloseException());
+        if (LOG.isDebugEnabled())
+            LOG.debug("Closed {}", this);
     }
 
-    public void setProxyAuthentication(Authentication authentication)
+    public void release(Connection connection)
     {
-        _proxyAuthentication = authentication;
     }
 
-    public boolean isProxied()
+    public void close(Connection connection)
     {
-        return _proxy != null;
     }
 
-    public void close() throws IOException
+    /**
+     * Aborts all the {@link HttpExchange}s queued in this destination.
+     *
+     * @param cause the abort cause
+     */
+    public void abort(Throwable cause)
     {
-        synchronized (this)
-        {
-            for (AbstractHttpConnection connection : _connections)
-            {
-                connection.close();
-            }
-        }
+        // Copy the queue of exchanges and fail only those that are queued at this moment.
+        // The application may queue another request from the failure/complete listener
+        // and we don't want to fail it immediately as if it was queued before the failure.
+        // The call to Request.abort() will remove the exchange from the exchanges queue.
+        for (HttpExchange exchange : new ArrayList<>(exchanges))
+            exchange.getRequest().abort(cause);
     }
 
+    @Override
     public String dump()
     {
-        return AggregateLifeCycle.dump(this);
+        return ContainerLifeCycle.dump(this);
     }
 
+    @Override
     public void dump(Appendable out, String indent) throws IOException
     {
-        synchronized (this)
-        {
-            out.append(String.valueOf(this));
-            out.append("idle=");
-            out.append(String.valueOf(_idleConnections.size()));
-            out.append(" pending=");
-            out.append(String.valueOf(_pendingConnections));
-            out.append("\n");
-            AggregateLifeCycle.dump(out, indent, _connections);
-        }
+        ContainerLifeCycle.dumpObject(out, toString());
     }
 
-    private class ConnectExchange extends ContentExchange
+    public String asString()
     {
-        private final SelectConnector.UpgradableEndPoint proxyEndPoint;
-
-        public ConnectExchange(Address serverAddress, SelectConnector.UpgradableEndPoint proxyEndPoint)
-        {
-            this.proxyEndPoint = proxyEndPoint;
-            setMethod(HttpMethods.CONNECT);
-            String serverHostAndPort = serverAddress.toString();
-            setRequestURI(serverHostAndPort);
-            addRequestHeader(HttpHeaders.HOST, serverHostAndPort);
-            addRequestHeader(HttpHeaders.PROXY_CONNECTION, "keep-alive");
-            addRequestHeader(HttpHeaders.USER_AGENT, "Jetty-Client");
-        }
-
-        @Override
-        protected void onResponseComplete() throws IOException
-        {
-            int responseStatus = getResponseStatus();
-            if (responseStatus == HttpStatus.OK_200)
-            {
-                proxyEndPoint.upgrade();
-            }
-            else if (responseStatus == HttpStatus.GATEWAY_TIMEOUT_504)
-            {
-                onExpire();
-            }
-            else
-            {
-                onException(new ProtocolException("Proxy: " + proxyEndPoint.getRemoteAddr() + ":" + proxyEndPoint.getRemotePort() + " didn't return http return code 200, but " + responseStatus));
-            }
-        }
-
-        @Override
-        protected void onConnectionFailed(Throwable x)
-        {
-            HttpDestination.this.onConnectionFailed(x);
-        }
-
-        @Override
-        protected void onException(Throwable x)
-        {
-            HttpExchange exchange = null;
-            synchronized (HttpDestination.this)
-            {
-                if (!_exchanges.isEmpty())
-                    exchange = _exchanges.remove(0);
-            }
-            if (exchange != null && exchange.setStatus(STATUS_EXCEPTED))
-                exchange.getEventListener().onException(x);
-        }
+        return origin.asString();
+    }
 
-        @Override
-        protected void onExpire()
-        {
-            HttpExchange exchange = null;
-            synchronized (HttpDestination.this)
-            {
-                if (!_exchanges.isEmpty())
-                    exchange = _exchanges.remove(0);
-            }
-            if (exchange != null && exchange.setStatus(STATUS_EXPIRED))
-                exchange.getEventListener().onExpire();
-        }
+    @Override
+    public String toString()
+    {
+        return String.format("%s[%s]%x%s,queue=%d",
+                HttpDestination.class.getSimpleName(),
+                asString(),
+                hashCode(),
+                proxy == null ? "" : "(via " + proxy + ")",
+                exchanges.size());
     }
 }
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpEventListener.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpEventListener.java
deleted file mode 100644
index 22f7cd0..0000000
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpEventListener.java
+++ /dev/null
@@ -1,68 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client;
-
-
-import java.io.IOException;
-
-import org.eclipse.jetty.io.Buffer;
-
-/**
- * 
- * 
- * 
- */
-public interface HttpEventListener
-{
-
-    // TODO review the methods here, we can probably trim these down on what to expose
-    
-    public void onRequestCommitted() throws IOException;
-
-
-    public void onRequestComplete() throws IOException;
-
-
-    public void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException;
-
-
-    public void onResponseHeader(Buffer name, Buffer value) throws IOException;
-
-    
-    public void onResponseHeaderComplete() throws IOException;
-
-    
-    public void onResponseContent(Buffer content) throws IOException;
-
-
-    public void onResponseComplete() throws IOException;
-
-
-    public void onConnectionFailed(Throwable ex);
-
-
-    public void onException(Throwable ex);
-
-
-    public void onExpire();
-    
-    public void onRetry();
-    
-
-}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpEventListenerWrapper.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpEventListenerWrapper.java
deleted file mode 100644
index 365b175..0000000
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpEventListenerWrapper.java
+++ /dev/null
@@ -1,167 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-
-package org.eclipse.jetty.client;
-
-import java.io.IOException;
-
-import org.eclipse.jetty.io.Buffer;
-
-public class HttpEventListenerWrapper implements HttpEventListener
-{
-    HttpEventListener _listener;
-    boolean _delegatingRequests;
-    boolean _delegatingResponses;
-    boolean _delegationResult = true;
-    private Buffer _version;
-    private int _status;
-    private Buffer _reason;
-
-    public HttpEventListenerWrapper()
-    {
-        _listener=null;
-        _delegatingRequests=false;
-        _delegatingResponses=false;
-    }
-    
-    public HttpEventListenerWrapper(HttpEventListener eventListener,boolean delegating)
-    {
-        _listener=eventListener;
-        _delegatingRequests=delegating;
-        _delegatingResponses=delegating;
-    }
-    
-    public HttpEventListener getEventListener()
-    {
-        return _listener;
-    }
-
-    public void setEventListener(HttpEventListener listener)
-    {
-        _listener = listener;
-    }
-
-    public boolean isDelegatingRequests()
-    {
-        return _delegatingRequests;
-    }
-    
-    public boolean isDelegatingResponses()
-    {
-        return _delegatingResponses;
-    }
-
-    public void setDelegatingRequests(boolean delegating)
-    {
-        _delegatingRequests = delegating;
-    }
-    
-    public void setDelegatingResponses(boolean delegating)
-    {
-        _delegatingResponses = delegating;
-    }
-    
-    public void setDelegationResult( boolean result )
-    {
-        _delegationResult = result;
-    }
-    
-    public void onConnectionFailed(Throwable ex)
-    {
-        if (_delegatingRequests)
-            _listener.onConnectionFailed(ex);
-    }
-
-    public void onException(Throwable ex)
-    {
-        if (_delegatingRequests||_delegatingResponses)
-            _listener.onException(ex);
-    }
-
-    public void onExpire()
-    {
-        if (_delegatingRequests||_delegatingResponses)
-            _listener.onExpire();
-    }
-
-    public void onRequestCommitted() throws IOException
-    {
-        if (_delegatingRequests)
-            _listener.onRequestCommitted();
-    }
-
-    public void onRequestComplete() throws IOException
-    {
-        if (_delegatingRequests)
-            _listener.onRequestComplete();
-    }
-
-    public void onResponseComplete() throws IOException
-    {
-        if (_delegatingResponses)
-        {
-            if (_delegationResult == false)
-            {
-                _listener.onResponseStatus(_version,_status,_reason);
-            }
-            _listener.onResponseComplete();
-        }
-    }
-
-    public void onResponseContent(Buffer content) throws IOException
-    {
-        if (_delegatingResponses)
-            _listener.onResponseContent(content);
-    }
-
-    public void onResponseHeader(Buffer name, Buffer value) throws IOException
-    {
-        if (_delegatingResponses)
-            _listener.onResponseHeader(name,value);
-    }
-
-    public void onResponseHeaderComplete() throws IOException
-    {
-        if (_delegatingResponses)
-            _listener.onResponseHeaderComplete();
-    }
-
-    public void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException
-    {
-        if (_delegatingResponses)
-        {
-            _listener.onResponseStatus(version,status,reason);
-        }
-        else
-        {
-            _version = version;
-            _status = status;
-            _reason = reason;
-        }
-    }
-
-    public void onRetry()
-    {
-        if (_delegatingRequests)
-            _listener.onRetry();
-    }
-    
-    
-    
-}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpExchange.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpExchange.java
index 18b05c5..34ff1a8 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpExchange.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpExchange.java
@@ -18,1310 +18,288 @@
 
 package org.eclipse.jetty.client;
 
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URI;
-import java.util.concurrent.atomic.AtomicInteger;
+import java.util.List;
 
-import org.eclipse.jetty.client.security.SecurityListener;
-import org.eclipse.jetty.http.HttpFields;
-import org.eclipse.jetty.http.HttpHeaders;
-import org.eclipse.jetty.http.HttpMethods;
-import org.eclipse.jetty.http.HttpSchemes;
-import org.eclipse.jetty.http.HttpURI;
-import org.eclipse.jetty.http.HttpVersions;
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.BufferCache.CachedBuffer;
-import org.eclipse.jetty.io.ByteArrayBuffer;
-import org.eclipse.jetty.io.Connection;
-import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.client.api.Result;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.util.thread.Timeout;
 
-/**
- * <p>
- * An HTTP client API that encapsulates an exchange (a request and its response) with a HTTP server.
- * </p>
- *
- * This object encapsulates:
- * <ul>
- * <li>The HTTP server address, see {@link #setAddress(Address)}, or {@link #setURI(URI)}, or {@link #setURL(String)})
- * <li>The HTTP request method, URI and HTTP version (see {@link #setMethod(String)}, {@link #setRequestURI(String)}, and {@link #setVersion(int)})
- * <li>The request headers (see {@link #addRequestHeader(String, String)} or {@link #setRequestHeader(String, String)})
- * <li>The request content (see {@link #setRequestContent(Buffer)} or {@link #setRequestContentSource(InputStream)})
- * <li>The status of the exchange (see {@link #getStatus()})
- * <li>Callbacks to handle state changes (see the onXxx methods such as {@link #onRequestComplete()} or {@link #onResponseComplete()})
- * <li>The ability to intercept callbacks (see {@link #setEventListener(HttpEventListener)}
- * </ul>
- *
- * <p>
- * The HttpExchange class is intended to be used by a developer wishing to have close asynchronous interaction with the the exchange.<br />
- * Typically a developer will extend the HttpExchange class with a derived class that overrides some or all of the onXxx callbacks. <br />
- * There are also some predefined HttpExchange subtypes that can be used as a basis, see {@link org.eclipse.jetty.client.ContentExchange} and
- * {@link org.eclipse.jetty.client.CachedExchange}.
- * </p>
- *
- * <p>
- * Typically the HttpExchange is passed to the {@link HttpClient#send(HttpExchange)} method, which in turn selects a {@link HttpDestination} and calls its
- * {@link HttpDestination#send(HttpExchange)}, which then creates or selects a {@link AbstractHttpConnection} and calls its {@link AbstractHttpConnection#send(HttpExchange)}. A
- * developer may wish to directly call send on the destination or connection if they wish to bypass some handling provided (eg Cookie handling in the
- * HttpDestination).
- * </p>
- *
- * <p>
- * In some circumstances, the HttpClient or HttpDestination may wish to retry a HttpExchange (eg. failed pipeline request, authentication retry or redirection).
- * In such cases, the HttpClient and/or HttpDestination may insert their own HttpExchangeListener to intercept and filter the call backs intended for the
- * HttpExchange.
- * </p>
- */
 public class HttpExchange
 {
-    static final Logger LOG = Log.getLogger(HttpExchange.class);
+    private static final Logger LOG = Log.getLogger(HttpExchange.class);
 
-    public static final int STATUS_START = 0;
-    public static final int STATUS_WAITING_FOR_CONNECTION = 1;
-    public static final int STATUS_WAITING_FOR_COMMIT = 2;
-    public static final int STATUS_SENDING_REQUEST = 3;
-    public static final int STATUS_WAITING_FOR_RESPONSE = 4;
-    public static final int STATUS_PARSING_HEADERS = 5;
-    public static final int STATUS_PARSING_CONTENT = 6;
-    public static final int STATUS_COMPLETED = 7;
-    public static final int STATUS_EXPIRED = 8;
-    public static final int STATUS_EXCEPTED = 9;
-    public static final int STATUS_CANCELLING = 10;
-    public static final int STATUS_CANCELLED = 11;
-    public static final int STATUS_SENDING_PARSING_HEADERS = 12;
-    public static final int STATUS_SENDING_PARSING_CONTENT = 13;
-    public static final int STATUS_SENDING_COMPLETED = 14;
+    private final HttpDestination destination;
+    private final HttpRequest request;
+    private final List<Response.ResponseListener> listeners;
+    private final HttpResponse response;
+    private State requestState = State.PENDING;
+    private State responseState = State.PENDING;
+    private HttpChannel _channel;
+    private Throwable requestFailure;
+    private Throwable responseFailure;
 
-    // HTTP protocol fields
-    private String _method = HttpMethods.GET;
-    private Buffer _scheme = HttpSchemes.HTTP_BUFFER;
-    private String _uri;
-    private int _version = HttpVersions.HTTP_1_1_ORDINAL;
-    private Address _address;
-    private final HttpFields _requestFields = new HttpFields();
-    private Buffer _requestContent;
-    private InputStream _requestContentSource;
-
-    private AtomicInteger _status = new AtomicInteger(STATUS_START);
-    private boolean _retryStatus = false;
-    // controls if the exchange will have listeners autoconfigured by the destination
-    private boolean _configureListeners = true;
-    private HttpEventListener _listener = new Listener();
-    private volatile AbstractHttpConnection _connection;
-
-    private Address _localAddress = null;
-
-    // a timeout for this exchange
-    private long _timeout = -1;
-    private volatile Timeout.Task _timeoutTask;
-    private long _lastStateChange=System.currentTimeMillis();
-    private long _sent=-1;
-    private int _lastState=-1;
-    private int _lastStatePeriod=-1;
-
-    boolean _onRequestCompleteDone;
-    boolean _onResponseCompleteDone;
-    boolean _onDone; // == onConnectionFail || onException || onExpired || onCancelled || onResponseCompleted && onRequestCompleted
-
-    protected void expire(HttpDestination destination)
+    public HttpExchange(HttpDestination destination, HttpRequest request, List<Response.ResponseListener> listeners)
     {
-        AbstractHttpConnection connection = _connection;
-        int status = getStatus();
-        if (status < STATUS_COMPLETED ||
-                status == STATUS_SENDING_PARSING_HEADERS ||
-                status == STATUS_SENDING_PARSING_CONTENT ||
-                status == STATUS_SENDING_COMPLETED)
-            setStatus(STATUS_EXPIRED);
-        destination.exchangeExpired(this);
-        if (connection != null)
-            connection.exchangeExpired(this);
+        this.destination = destination;
+        this.request = request;
+        this.listeners = listeners;
+        this.response = new HttpResponse(request, listeners);
+        HttpConversation conversation = request.getConversation();
+        conversation.getExchanges().offer(this);
+        conversation.updateResponseListeners(null);
     }
 
-    public int getStatus()
+    public HttpConversation getConversation()
     {
-        return _status.get();
+        return request.getConversation();
     }
 
-    /**
-     * @param status
-     *            the status to wait for
-     * @throws InterruptedException
-     *             if the waiting thread is interrupted
-     * @deprecated Use {@link #waitForDone()} instead
-     */
-    @Deprecated
-    public void waitForStatus(int status) throws InterruptedException
+    public HttpRequest getRequest()
     {
-        throw new UnsupportedOperationException();
+        return request;
     }
 
-    /**
-     * Wait until the exchange is "done". Done is defined as when a final state has been passed to the HttpExchange via the associated onXxx call. Note that an
-     * exchange can transit a final state when being used as part of a dialog (eg {@link SecurityListener}. Done status is thus defined as:
-     *
-     * <pre>
-     * done == onConnectionFailed || onException || onExpire || onRequestComplete && onResponseComplete
-     * </pre>
-     *
-     * @return the done status
-     * @throws InterruptedException
-     */
-    public int waitForDone() throws InterruptedException
+    public Throwable getRequestFailure()
     {
         synchronized (this)
         {
-            while (!isDone())
-                this.wait();
-            return _status.get();
+            return requestFailure;
         }
     }
 
-    public void reset()
+    public List<Response.ResponseListener> getResponseListeners()
     {
-        // TODO - this should do a cancel and wakeup everybody that was waiting.
-        // might need a version number concept
-        synchronized (this)
-        {
-            _timeoutTask = null;
-            _onRequestCompleteDone = false;
-            _onResponseCompleteDone = false;
-            _onDone = false;
-            setStatus(STATUS_START);
-        }
+        return listeners;
     }
 
-    /* ------------------------------------------------------------ */
-    /**
-     * @param newStatus
-     * @return True if the status was actually set.
-     */
-    boolean setStatus(int newStatus)
+    public HttpResponse getResponse()
     {
-        boolean set = false;
-        try
-        {
-            int oldStatus = _status.get();
-            boolean ignored = false;
-            if (oldStatus != newStatus)
-            {
-                long now = System.currentTimeMillis();
-                _lastStatePeriod=(int)(now-_lastStateChange);
-                _lastState=oldStatus;
-                _lastStateChange=now;
-                if (newStatus==STATUS_SENDING_REQUEST)
-                    _sent=_lastStateChange;
-            }
-            
-            // State machine: from which old status you can go into which new status
-            switch (oldStatus)
-            {
-                case STATUS_START:
-                    switch (newStatus)
-                    {
-                        case STATUS_START:
-                        case STATUS_WAITING_FOR_CONNECTION:
-                        case STATUS_WAITING_FOR_COMMIT:
-                        case STATUS_CANCELLING:
-                        case STATUS_EXCEPTED:
-                            set = _status.compareAndSet(oldStatus,newStatus);
-                            break;
-                        case STATUS_EXPIRED:
-                            set = setStatusExpired(newStatus,oldStatus);
-                            break;
-                    }
-                    break;
-                case STATUS_WAITING_FOR_CONNECTION:
-                    switch (newStatus)
-                    {
-                        case STATUS_WAITING_FOR_COMMIT:
-                        case STATUS_CANCELLING:
-                        case STATUS_EXCEPTED:
-                            set = _status.compareAndSet(oldStatus,newStatus);
-                            break;
-                        case STATUS_EXPIRED:
-                            set = setStatusExpired(newStatus,oldStatus);
-                            break;
-                    }
-                    break;
-                case STATUS_WAITING_FOR_COMMIT:
-                    switch (newStatus)
-                    {
-                        case STATUS_SENDING_REQUEST:
-                        case STATUS_CANCELLING:
-                        case STATUS_EXCEPTED:
-                            set = _status.compareAndSet(oldStatus,newStatus);
-                            break;
-                        case STATUS_EXPIRED:
-                            set = setStatusExpired(newStatus,oldStatus);
-                            break;
-                    }
-                    break;
-                case STATUS_SENDING_REQUEST:
-                    switch (newStatus)
-                    {
-                        case STATUS_PARSING_HEADERS:
-                            set = _status.compareAndSet(oldStatus,STATUS_SENDING_PARSING_HEADERS);
-                            break;
-                        case STATUS_WAITING_FOR_RESPONSE:
-                            if (set = _status.compareAndSet(oldStatus,newStatus))
-                                getEventListener().onRequestCommitted();
-                            break;
-                        case STATUS_CANCELLING:
-                        case STATUS_EXCEPTED:
-                            set = _status.compareAndSet(oldStatus,newStatus);
-                            break;
-                        case STATUS_EXPIRED:
-                            set = setStatusExpired(newStatus,oldStatus);
-                            break;
-                    }
-                    break;
-                case STATUS_WAITING_FOR_RESPONSE:
-                    switch (newStatus)
-                    {
-                        case STATUS_PARSING_HEADERS:
-                        case STATUS_CANCELLING:
-                        case STATUS_EXCEPTED:
-                            set = _status.compareAndSet(oldStatus,newStatus);
-                            break;
-                        case STATUS_EXPIRED:
-                            set = setStatusExpired(newStatus,oldStatus);
-                            break;
-                    }
-                    break;
-                case STATUS_PARSING_HEADERS:
-                    switch (newStatus)
-                    {
-                        case STATUS_PARSING_CONTENT:
-                            if (set = _status.compareAndSet(oldStatus,newStatus))
-                                getEventListener().onResponseHeaderComplete();
-                            break;
-                        case STATUS_CANCELLING:
-                        case STATUS_EXCEPTED:
-                            set = _status.compareAndSet(oldStatus,newStatus);
-                            break;
-                        case STATUS_EXPIRED:
-                            set = setStatusExpired(newStatus,oldStatus);
-                            break;
-                    }
-                    break;
-                case STATUS_PARSING_CONTENT:
-                    switch (newStatus)
-                    {
-                        case STATUS_COMPLETED:
-                            if (set = _status.compareAndSet(oldStatus,newStatus))
-                                getEventListener().onResponseComplete();
-                            break;
-                        case STATUS_CANCELLING:
-                        case STATUS_EXCEPTED:
-                            set = _status.compareAndSet(oldStatus,newStatus);
-                            break;
-                        case STATUS_EXPIRED:
-                            set = setStatusExpired(newStatus,oldStatus);
-                            break;
-                    }
-                    break;
-                case STATUS_COMPLETED:
-                    switch (newStatus)
-                    {
-                        case STATUS_START:
-                            set = _status.compareAndSet(oldStatus,newStatus);
-                            break;
-                        case STATUS_WAITING_FOR_RESPONSE:
-                            if (isResponseCompleted())
-                            {
-                                // Don't change the status, it's too late.
-                                ignored = true;
-                                getEventListener().onRequestCommitted();
-                            }
-                            else
-                            {
-                                // The 1xx cases go from COMPLETED => WAITING again.
-                                set = _status.compareAndSet(oldStatus,newStatus);
-                            }
-                            break;
-                        case STATUS_EXCEPTED:
-                        case STATUS_CANCELLING:
-                        case STATUS_EXPIRED:
-                            // Don't change the status, it's too late
-                            ignored = true;
-                            break;
-                    }
-                    break;
-                case STATUS_CANCELLING:
-                    switch (newStatus)
-                    {
-                        case STATUS_EXCEPTED:
-                        case STATUS_CANCELLED:
-                            if (set = _status.compareAndSet(oldStatus,newStatus))
-                                done();
-                            break;
-                        default:
-                            // Ignore other statuses, we're cancelling
-                            ignored = true;
-                            break;
-                    }
-                    break;
-                case STATUS_EXCEPTED:
-                case STATUS_EXPIRED:
-                case STATUS_CANCELLED:
-                    switch (newStatus)
-                    {
-                        case STATUS_START:
-                            set = _status.compareAndSet(oldStatus,newStatus);
-                            break;
-                        case STATUS_COMPLETED:
-                            ignored = true;
-                            done();
-                            break; 
-                        default:
-                            ignored = true;
-                            break;
-                    }
-                    break;
-                case STATUS_SENDING_PARSING_HEADERS:
-                    switch (newStatus)
-                    {
-                        case STATUS_WAITING_FOR_RESPONSE:
-                            if (set = _status.compareAndSet(oldStatus,STATUS_PARSING_HEADERS))
-                                getEventListener().onRequestCommitted();
-                            break;
-                        case STATUS_PARSING_CONTENT:
-                            if (set = _status.compareAndSet(oldStatus,STATUS_SENDING_PARSING_CONTENT))
-                                getEventListener().onResponseHeaderComplete();
-                            break;
-                        case STATUS_CANCELLING:
-                        case STATUS_EXCEPTED:
-                            set = _status.compareAndSet(oldStatus,newStatus);
-                            break;
-                        case STATUS_EXPIRED:
-                            set = setStatusExpired(newStatus,oldStatus);
-                            break;
-                        default:
-                            break;
-                    }
-                    break;
-                case STATUS_SENDING_PARSING_CONTENT:
-                    switch (newStatus)
-                    {
-                        case STATUS_WAITING_FOR_RESPONSE:
-                            if (set = _status.compareAndSet(oldStatus,STATUS_PARSING_CONTENT))
-                                getEventListener().onRequestCommitted();
-                            break;
-                        case STATUS_COMPLETED:
-                            if (set = _status.compareAndSet(oldStatus,STATUS_SENDING_COMPLETED))
-                                getEventListener().onResponseComplete();
-                            break;
-                        case STATUS_CANCELLING:
-                        case STATUS_EXCEPTED:
-                            set = _status.compareAndSet(oldStatus,newStatus);
-                            break;
-                        case STATUS_EXPIRED:
-                            set = setStatusExpired(newStatus,oldStatus);
-                            break;
-                        default:
-                            break;
-                    }
-                    break;
-                case STATUS_SENDING_COMPLETED:
-                    switch (newStatus)
-                    {
-                        case STATUS_WAITING_FOR_RESPONSE:
-                            if (set = _status.compareAndSet(oldStatus,STATUS_COMPLETED))
-                                getEventListener().onRequestCommitted();
-                            break;
-                        case STATUS_CANCELLING:
-                        case STATUS_EXCEPTED:
-                            set = _status.compareAndSet(oldStatus,newStatus);
-                            break;
-                        case STATUS_EXPIRED:
-                            set = setStatusExpired(newStatus,oldStatus);
-                            break;
-                        default:
-                            break;
-                    }
-                    break;
-                default:
-                    // Here means I allowed to set a state that I don't recognize
-                    throw new AssertionError(oldStatus + " => " + newStatus);
-            }
-
-            if (!set && !ignored)
-                throw new IllegalStateException(toState(oldStatus) + " => " + toState(newStatus));
-            LOG.debug("setStatus {} {}",newStatus,this);
-        }
-        catch (IOException x)
-        {
-            LOG.warn(x);
-        }
-        return set;
+        return response;
     }
 
-    private boolean isResponseCompleted()
+    public Throwable getResponseFailure()
     {
         synchronized (this)
         {
-            return _onResponseCompleteDone;
+            return responseFailure;
         }
     }
 
-    private boolean setStatusExpired(int newStatus, int oldStatus)
-    {
-        boolean set;
-        if (set = _status.compareAndSet(oldStatus,newStatus))
-            getEventListener().onExpire();
-        return set;
-    }
-
-    public boolean isDone()
-    {
-        synchronized (this)
-        {
-            return _onDone;
-        }
-    }
-
-    /**
-     * @deprecated
-     */
-    @Deprecated
-    public boolean isDone(int status)
-    {
-        return isDone();
-    }
-
-    public HttpEventListener getEventListener()
-    {
-        return _listener;
-    }
-
-    public void setEventListener(HttpEventListener listener)
-    {
-        _listener = listener;
-    }
-
-    public void setTimeout(long timeout)
-    {
-        _timeout = timeout;
-    }
-
-    public long getTimeout()
-    {
-        return _timeout;
-    }
-
-    /**
-     * @param url
-     *            an absolute URL (for example 'http://localhost/foo/bar?a=1')
-     */
-    public void setURL(String url)
-    {
-        setURI(URI.create(url));
-    }
-
-    /**
-     * @param address
-     *            the address of the server
-     */
-    public void setAddress(Address address)
-    {
-        _address = address;
-    }
-
-    /**
-     * @return the address of the server
-     */
-    public Address getAddress()
-    {
-        return _address;
-    }
-
-    /**
-     * the local address used by the connection
-     *
-     * Note: this method will not be populated unless the exchange has been executed by the HttpClient
-     *
-     * @return the local address used for the running of the exchange if available, null otherwise.
-     */
-    public Address getLocalAddress()
-    {
-        return _localAddress;
-    }
-
-    /**
-     * @param scheme
-     *            the scheme of the URL (for example 'http')
-     */
-    public void setScheme(Buffer scheme)
-    {
-        _scheme = scheme;
-    }
-
-    /**
-     * @param scheme
-     *            the scheme of the URL (for example 'http')
-     */
-    public void setScheme(String scheme)
-    {
-        if (scheme != null)
-        {
-            if (HttpSchemes.HTTP.equalsIgnoreCase(scheme))
-                setScheme(HttpSchemes.HTTP_BUFFER);
-            else if (HttpSchemes.HTTPS.equalsIgnoreCase(scheme))
-                setScheme(HttpSchemes.HTTPS_BUFFER);
-            else
-                setScheme(new ByteArrayBuffer(scheme));
-        }
-    }
-
-    /**
-     * @return the scheme of the URL
-     */
-    public Buffer getScheme()
-    {
-        return _scheme;
-    }
-
-    /**
-     * @param version
-     *            the HTTP protocol version as integer, 9, 10 or 11 for 0.9, 1.0 or 1.1
-     */
-    public void setVersion(int version)
-    {
-        _version = version;
-    }
-
-    /**
-     * @param version
-     *            the HTTP protocol version as string
-     */
-    public void setVersion(String version)
-    {
-        CachedBuffer v = HttpVersions.CACHE.get(version);
-        if (v == null)
-            _version = 10;
-        else
-            _version = v.getOrdinal();
-    }
-
-    /**
-     * @return the HTTP protocol version as integer
-     * @see #setVersion(int)
-     */
-    public int getVersion()
-    {
-        return _version;
-    }
-
-    /**
-     * @param method
-     *            the HTTP method (for example 'GET')
-     */
-    public void setMethod(String method)
-    {
-        _method = method;
-    }
-
     /**
-     * @return the HTTP method
-     */
-    public String getMethod()
-    {
-        return _method;
-    }
-
-    /**
-     * @return request URI
-     * @see #getRequestURI()
-     * @deprecated
-     */
-    @Deprecated
-    public String getURI()
-    {
-        return getRequestURI();
-    }
-
-    /**
-     * @return request URI
-     */
-    public String getRequestURI()
-    {
-        return _uri;
-    }
-
-    /**
-     * Set the request URI
-     *
-     * @param uri
-     *            new request URI
-     * @see #setRequestURI(String)
-     * @deprecated
-     */
-    @Deprecated
-    public void setURI(String uri)
-    {
-        setRequestURI(uri);
-    }
-
-    /**
-     * Set the request URI
+     * <p>Associates the given {@code channel} to this exchange.</p>
+     * <p>Works in strict collaboration with {@link HttpChannel#associate(HttpExchange)}.</p>
      *
-     * Per RFC 2616 sec5, Request-URI = "*" | absoluteURI | abs_path | authority<br/>
-     * where:<br/>
-     * <br/>
-     * "*" - request applies to server itself<br/>
-     * absoluteURI - required for proxy requests, e.g. http://localhost:8080/context<br/>
-     * (this form is generated automatically by HttpClient)<br/>
-     * abs_path - used for most methods, e.g. /context<br/>
-     * authority - used for CONNECT method only, e.g. localhost:8080<br/>
-     * <br/>
-     * For complete definition of URI components, see RFC 2396 sec3.<br/>
-     *
-     * @param uri
-     *            new request URI
-     */
-    public void setRequestURI(String uri)
-    {
-        _uri = uri;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param uri
-     *            an absolute URI (for example 'http://localhost/foo/bar?a=1')
+     * @param channel the channel to associate to this exchange
+     * @return true if the channel could be associated, false otherwise
      */
-    public void setURI(URI uri)
-    {
-        if (!uri.isAbsolute())
-            throw new IllegalArgumentException("!Absolute URI: " + uri);
-
-        if (uri.isOpaque())
-            throw new IllegalArgumentException("Opaque URI: " + uri);
-
-        if (LOG.isDebugEnabled())
-            LOG.debug("URI = {}",uri.toASCIIString());
-
-        String scheme = uri.getScheme();
-        int port = uri.getPort();
-        if (port <= 0)
-            port = "https".equalsIgnoreCase(scheme)?443:80;
-
-        setScheme(scheme);
-        setAddress(new Address(uri.getHost(),port));
-
-        HttpURI httpUri = new HttpURI(uri);
-        String completePath = httpUri.getCompletePath();
-        setRequestURI(completePath == null?"/":completePath);
-    }
-
-    /**
-     * Adds the specified request header
-     *
-     * @param name
-     *            the header name
-     * @param value
-     *            the header value
-     */
-    public void addRequestHeader(String name, String value)
-    {
-        getRequestFields().add(name,value);
-    }
-
-    /**
-     * Adds the specified request header
-     *
-     * @param name
-     *            the header name
-     * @param value
-     *            the header value
-     */
-    public void addRequestHeader(Buffer name, Buffer value)
-    {
-        getRequestFields().add(name,value);
-    }
-
-    /**
-     * Sets the specified request header
-     *
-     * @param name
-     *            the header name
-     * @param value
-     *            the header value
-     */
-    public void setRequestHeader(String name, String value)
-    {
-        getRequestFields().put(name,value);
-    }
-
-    /**
-     * Sets the specified request header
-     *
-     * @param name
-     *            the header name
-     * @param value
-     *            the header value
-     */
-    public void setRequestHeader(Buffer name, Buffer value)
-    {
-        getRequestFields().put(name,value);
-    }
-
-    /**
-     * @param value
-     *            the content type of the request
-     */
-    public void setRequestContentType(String value)
-    {
-        getRequestFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,value);
-    }
-
-    /**
-     * @return the request headers
-     */
-    public HttpFields getRequestFields()
-    {
-        return _requestFields;
-    }
-
-    /**
-     * @param requestContent
-     *            the request content
-     */
-    public void setRequestContent(Buffer requestContent)
-    {
-        _requestContent = requestContent;
-    }
-
-    /**
-     * @param stream
-     *            the request content as a stream
-     */
-    public void setRequestContentSource(InputStream stream)
-    {
-        _requestContentSource = stream;
-        if (_requestContentSource != null && _requestContentSource.markSupported())
-            _requestContentSource.mark(Integer.MAX_VALUE);
-    }
-
-    /**
-     * @return the request content as a stream
-     */
-    public InputStream getRequestContentSource()
-    {
-        return _requestContentSource;
-    }
-
-    public Buffer getRequestContentChunk(Buffer buffer) throws IOException
+    boolean associate(HttpChannel channel)
     {
+        boolean result = false;
+        boolean abort = false;
         synchronized (this)
         {
-            if (_requestContentSource!=null)
+            // Only associate if the exchange state is initial,
+            // as the exchange could be already failed.
+            if (requestState == State.PENDING && responseState == State.PENDING)
             {
-                if (buffer == null)
-                    buffer = new ByteArrayBuffer(8192); // TODO configure
-
-                int space = buffer.space();
-                int length = _requestContentSource.read(buffer.array(),buffer.putIndex(),space);
-                if (length >= 0)
+                abort = _channel != null;
+                if (!abort)
                 {
-                    buffer.setPutIndex(buffer.putIndex()+length);
-                    return buffer;
+                    _channel = channel;
+                    result = true;
                 }
             }
-            return null;
         }
-    }
 
-    /**
-     * @return the request content
-     */
-    public Buffer getRequestContent()
-    {
-        return _requestContent;
-    }
+        if (abort)
+            request.abort(new IllegalStateException(toString()));
 
-    /**
-     * @return whether a retry will be attempted or not
-     */
-    public boolean getRetryStatus()
-    {
-        return _retryStatus;
+        return result;
     }
 
-    /**
-     * @param retryStatus
-     *            whether a retry will be attempted or not
-     */
-    public void setRetryStatus(boolean retryStatus)
+    void disassociate(HttpChannel channel)
     {
-        _retryStatus = retryStatus;
-    }
+        boolean abort = false;
+        synchronized (this)
+        {
+            if (_channel != channel || requestState != State.TERMINATED || responseState != State.TERMINATED)
+                abort = true;
+            _channel = null;
+        }
 
-    /**
-     * Initiates the cancelling of this exchange. The status of the exchange is set to {@link #STATUS_CANCELLING}. Cancelling the exchange is an asynchronous
-     * operation with respect to the request/response, and as such checking the request/response status of a cancelled exchange may return undefined results
-     * (for example it may have only some of the response headers being sent by the server). The cancelling of the exchange is completed when the exchange
-     * status (see {@link #getStatus()}) is {@link #STATUS_CANCELLED}, and this can be waited using {@link #waitForDone()}.
-     */
-    public void cancel()
-    {
-        setStatus(STATUS_CANCELLING);
-        abort();
+        if (abort)
+            request.abort(new IllegalStateException(toString()));
     }
 
-    private void done()
+    private HttpChannel getHttpChannel()
     {
         synchronized (this)
         {
-            disassociate();
-            _onDone = true;
-            notifyAll();
+            return _channel;
         }
     }
 
-    private void abort()
+    public boolean requestComplete(Throwable failure)
     {
-        AbstractHttpConnection httpConnection = _connection;
-        if (httpConnection != null)
+        synchronized (this)
         {
-            try
-            {
-                // Closing the connection here will cause the connection
-                // to be returned in HttpConnection.handle()
-                httpConnection.close();
-            }
-            catch (IOException x)
-            {
-                LOG.debug(x);
-            }
-            finally
-            {
-                disassociate();
-            }
+            return completeRequest(failure);
         }
     }
 
-    void associate(AbstractHttpConnection connection)
-    {
-        if (connection.getEndPoint().getLocalAddr() != null)
-            _localAddress = new Address(connection.getEndPoint().getLocalAddr(),connection.getEndPoint().getLocalPort());
-
-        _connection = connection;
-        if (getStatus() == STATUS_CANCELLING)
-            abort();
-    }
-
-    boolean isAssociated()
-    {
-        return this._connection != null;
-    }
-
-    AbstractHttpConnection disassociate()
-    {
-        AbstractHttpConnection result = _connection;
-        this._connection = null;
-        if (getStatus() == STATUS_CANCELLING)
-            setStatus(STATUS_CANCELLED);
-        return result;
-    }
-
-    public static String toState(int s)
+    private boolean completeRequest(Throwable failure)
     {
-        String state;
-        switch (s)
+        if (requestState == State.PENDING)
         {
-            case STATUS_START:
-                state = "START";
-                break;
-            case STATUS_WAITING_FOR_CONNECTION:
-                state = "CONNECTING";
-                break;
-            case STATUS_WAITING_FOR_COMMIT:
-                state = "CONNECTED";
-                break;
-            case STATUS_SENDING_REQUEST:
-                state = "SENDING";
-                break;
-            case STATUS_WAITING_FOR_RESPONSE:
-                state = "WAITING";
-                break;
-            case STATUS_PARSING_HEADERS:
-                state = "HEADERS";
-                break;
-            case STATUS_PARSING_CONTENT:
-                state = "CONTENT";
-                break;
-            case STATUS_COMPLETED:
-                state = "COMPLETED";
-                break;
-            case STATUS_EXPIRED:
-                state = "EXPIRED";
-                break;
-            case STATUS_EXCEPTED:
-                state = "EXCEPTED";
-                break;
-            case STATUS_CANCELLING:
-                state = "CANCELLING";
-                break;
-            case STATUS_CANCELLED:
-                state = "CANCELLED";
-                break;
-            case STATUS_SENDING_PARSING_HEADERS:
-                state = "SENDING+HEADERS";
-                break;
-            case STATUS_SENDING_PARSING_CONTENT:
-                state = "SENDING+CONTENT";
-                break;
-            case STATUS_SENDING_COMPLETED:
-                state = "SENDING+COMPLETED";
-                break;
-            default:
-                state = "UNKNOWN";
+            requestState = State.COMPLETED;
+            requestFailure = failure;
+            return true;
         }
-        return state;
-    }
-
-    @Override
-    public String toString()
-    {
-        String state=toState(getStatus());
-        long now=System.currentTimeMillis();
-        long forMs = now -_lastStateChange;
-        String s= _lastState>=0
-            ?String.format("%s@%x=%s//%s%s#%s(%dms)->%s(%dms)",getClass().getSimpleName(),hashCode(),_method,_address,_uri,toState(_lastState),_lastStatePeriod,state,forMs)
-            :String.format("%s@%x=%s//%s%s#%s(%dms)",getClass().getSimpleName(),hashCode(),_method,_address,_uri,state,forMs);
-        if (getStatus()>=STATUS_SENDING_REQUEST && _sent>0)
-            s+="sent="+(now-_sent)+"ms";
-        return s;
+        return false;
     }
 
-    /**
-     */
-    protected Connection onSwitchProtocol(EndPoint endp) throws IOException
+    public boolean responseComplete(Throwable failure)
     {
-        return null;
-    }
-
-    /**
-     * Callback called when the request headers have been sent to the server. This implementation does nothing.
-     *
-     * @throws IOException
-     *             allowed to be thrown by overriding code
-     */
-    protected void onRequestCommitted() throws IOException
-    {
-    }
-
-    /**
-     * Callback called when the request and its body have been sent to the server. This implementation does nothing.
-     *
-     * @throws IOException
-     *             allowed to be thrown by overriding code
-     */
-    protected void onRequestComplete() throws IOException
-    {
-    }
-
-    /**
-     * Callback called when a response status line has been received from the server. This implementation does nothing.
-     *
-     * @param version
-     *            the HTTP version
-     * @param status
-     *            the HTTP status code
-     * @param reason
-     *            the HTTP status reason string
-     * @throws IOException
-     *             allowed to be thrown by overriding code
-     */
-    protected void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException
-    {
-    }
-
-    /**
-     * Callback called for each response header received from the server. This implementation does nothing.
-     *
-     * @param name
-     *            the header name
-     * @param value
-     *            the header value
-     * @throws IOException
-     *             allowed to be thrown by overriding code
-     */
-    protected void onResponseHeader(Buffer name, Buffer value) throws IOException
-    {
-    }
-
-    /**
-     * Callback called when the response headers have been completely received from the server. This implementation does nothing.
-     *
-     * @throws IOException
-     *             allowed to be thrown by overriding code
-     */
-    protected void onResponseHeaderComplete() throws IOException
-    {
-    }
-
-    /**
-     * Callback called for each chunk of the response content received from the server. This implementation does nothing.
-     *
-     * @param content
-     *            the buffer holding the content chunk
-     * @throws IOException
-     *             allowed to be thrown by overriding code
-     */
-    protected void onResponseContent(Buffer content) throws IOException
-    {
-    }
-
-    /**
-     * Callback called when the entire response has been received from the server This implementation does nothing.
-     *
-     * @throws IOException
-     *             allowed to be thrown by overriding code
-     */
-    protected void onResponseComplete() throws IOException
-    {
-    }
-
-    /**
-     * Callback called when an exception was thrown during an attempt to establish the connection with the server (for example the server is not listening).
-     * This implementation logs a warning.
-     *
-     * @param x
-     *            the exception thrown attempting to establish the connection with the server
-     */
-    protected void onConnectionFailed(Throwable x)
-    {
-        LOG.warn("CONNECTION FAILED " + this,x);
-    }
-
-    /**
-     * Callback called when any other exception occurs during the handling of this exchange. This implementation logs a warning.
-     *
-     * @param x
-     *            the exception thrown during the handling of this exchange
-     */
-    protected void onException(Throwable x)
-    {
-        LOG.warn("EXCEPTION " + this,x);
+        synchronized (this)
+        {
+            return completeResponse(failure);
+        }
     }
 
-    /**
-     * Callback called when no response has been received within the timeout. This implementation logs a warning.
-     */
-    protected void onExpire()
+    private boolean completeResponse(Throwable failure)
     {
-        LOG.warn("EXPIRED " + this);
+        if (responseState == State.PENDING)
+        {
+            responseState = State.COMPLETED;
+            responseFailure = failure;
+            return true;
+        }
+        return false;
     }
 
-    /**
-     * Callback called when the request is retried (due to failures or authentication). Implementations must reset any consumable content that needs to be sent.
-     *
-     * @throws IOException
-     *             allowed to be thrown by overriding code
-     */
-    protected void onRetry() throws IOException
+    public Result terminateRequest()
     {
-        if (_requestContentSource != null)
+        Result result = null;
+        synchronized (this)
         {
-            if (_requestContentSource.markSupported())
-            {
-                _requestContent = null;
-                _requestContentSource.reset();
-            }
-            else
-            {
-                throw new IOException("Unsupported retry attempt");
-            }
+            if (requestState == State.COMPLETED)
+                requestState = State.TERMINATED;
+            if (requestState == State.TERMINATED && responseState == State.TERMINATED)
+                result = new Result(getRequest(), requestFailure, getResponse(), responseFailure);
         }
-    }
 
-    /**
-     * @return true if the exchange should have listeners configured for it by the destination, false if this is being managed elsewhere
-     * @see #setConfigureListeners(boolean)
-     */
-    public boolean configureListeners()
-    {
-        return _configureListeners;
-    }
+        if (LOG.isDebugEnabled())
+            LOG.debug("Terminated request for {}, result: {}", this, result);
 
-    /**
-     * @param autoConfigure
-     *            whether the listeners are configured by the destination or elsewhere
-     */
-    public void setConfigureListeners(boolean autoConfigure)
-    {
-        this._configureListeners = autoConfigure;
+        return result;
     }
 
-    protected void scheduleTimeout(final HttpDestination destination)
+    public Result terminateResponse()
     {
-        assert _timeoutTask == null;
-
-        _timeoutTask = new Timeout.Task()
+        Result result = null;
+        synchronized (this)
         {
-            @Override
-            public void expired()
-            {
-                HttpExchange.this.expire(destination);
-            }
-        };
+            if (responseState == State.COMPLETED)
+                responseState = State.TERMINATED;
+            if (requestState == State.TERMINATED && responseState == State.TERMINATED)
+                result = new Result(getRequest(), requestFailure, getResponse(), responseFailure);
+        }
 
-        HttpClient httpClient = destination.getHttpClient();
-        long timeout = getTimeout();
-        if (timeout > 0)
-            httpClient.schedule(_timeoutTask,timeout);
-        else
-            httpClient.schedule(_timeoutTask);
-    }
+        if (LOG.isDebugEnabled())
+            LOG.debug("Terminated response for {}, result: {}", this, result);
 
-    protected void cancelTimeout(HttpClient httpClient)
-    {
-        Timeout.Task task = _timeoutTask;
-        if (task != null)
-            httpClient.cancel(task);
-        _timeoutTask = null;
+        return result;
     }
 
-    private class Listener implements HttpEventListener
+    public boolean abort(Throwable failure)
     {
-        public void onConnectionFailed(Throwable ex)
-        {
-            try
-            {
-                HttpExchange.this.onConnectionFailed(ex);
-            }
-            finally
-            {
-                done();
-            }
-        }
-
-        public void onException(Throwable ex)
+        // Atomically change the state of this exchange to be completed.
+        // This will avoid that this exchange can be associated to a channel.
+        boolean abortRequest;
+        boolean abortResponse;
+        synchronized (this)
         {
-            try
-            {
-                HttpExchange.this.onException(ex);
-            }
-            finally
-            {
-                done();
-            }
+            abortRequest = completeRequest(failure);
+            abortResponse = completeResponse(failure);
         }
 
-        public void onExpire()
-        {
-            try
-            {
-                HttpExchange.this.onExpire();
-            }
-            finally
-            {
-                done();
-            }
-        }
+        if (LOG.isDebugEnabled())
+            LOG.debug("Failed {}: req={}/rsp={} {}", this, abortRequest, abortResponse, failure);
 
-        public void onRequestCommitted() throws IOException
-        {
-            HttpExchange.this.onRequestCommitted();
-        }
+        if (!abortRequest && !abortResponse)
+            return false;
 
-        public void onRequestComplete() throws IOException
-        {
-            try
-            {
-                HttpExchange.this.onRequestComplete();
-            }
-            finally
-            {
-                synchronized (HttpExchange.this)
-                {
-                    _onRequestCompleteDone = true;
-                    // Member _onDone may already be true, for example
-                    // because the exchange expired or has been canceled
-                    _onDone |= _onResponseCompleteDone;
-                    if (_onDone)
-                        disassociate();
-                    HttpExchange.this.notifyAll();
-                }
-            }
-        }
+        // We failed this exchange, deal with it.
 
-        public void onResponseComplete() throws IOException
+        // Case #1: exchange was in the destination queue.
+        if (destination.remove(this))
         {
-            try
-            {
-                HttpExchange.this.onResponseComplete();
-            }
-            finally
-            {
-                synchronized (HttpExchange.this)
-                {
-                    _onResponseCompleteDone = true;
-                    // Member _onDone may already be true, for example
-                    // because the exchange expired or has been canceled
-                    _onDone |= _onRequestCompleteDone;
-                    if (_onDone)
-                        disassociate();
-                    HttpExchange.this.notifyAll();
-                }
-            }
+            if (LOG.isDebugEnabled())
+                LOG.debug("Aborting while queued {}: {}", this, failure);
+            notifyFailureComplete(failure);
+            return true;
         }
 
-        public void onResponseContent(Buffer content) throws IOException
+        HttpChannel channel = getHttpChannel();
+        if (channel == null)
         {
-            HttpExchange.this.onResponseContent(content);
+            // Case #2: exchange was not yet associated.
+            // Because this exchange is failed, when associate() is called
+            // it will return false, and the caller will dispose the channel.
+            if (LOG.isDebugEnabled())
+                LOG.debug("Aborted before association {}: {}", this, failure);
+            notifyFailureComplete(failure);
+            return true;
         }
 
-        public void onResponseHeader(Buffer name, Buffer value) throws IOException
-        {
-            HttpExchange.this.onResponseHeader(name,value);
-        }
+        // Case #3: exchange was already associated.
+        boolean aborted = channel.abort(this, abortRequest ? failure : null, abortResponse ? failure : null);
+        if (LOG.isDebugEnabled())
+            LOG.debug("Aborted ({}) while active {}: {}", aborted, this, failure);
+        return aborted;
+    }
 
-        public void onResponseHeaderComplete() throws IOException
-        {
-            HttpExchange.this.onResponseHeaderComplete();
-        }
+    private void notifyFailureComplete(Throwable failure)
+    {
+        destination.getRequestNotifier().notifyFailure(request, failure);
+        List<Response.ResponseListener> listeners = getConversation().getResponseListeners();
+        ResponseNotifier responseNotifier = destination.getResponseNotifier();
+        responseNotifier.notifyFailure(listeners, response, failure);
+        responseNotifier.notifyComplete(listeners, new Result(request, failure, response, failure));
+    }
 
-        public void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException
+    public void resetResponse()
+    {
+        synchronized (this)
         {
-            HttpExchange.this.onResponseStatus(version,status,reason);
+            responseState = State.PENDING;
+            responseFailure = null;
         }
+    }
 
-        public void onRetry()
-        {
-            HttpExchange.this.setRetryStatus(true);
-            try
-            {
-                HttpExchange.this.onRetry();
-            }
-            catch (IOException e)
-            {
-                LOG.debug(e);
-            }
-        }
+    public void proceed(Throwable failure)
+    {
+        HttpChannel channel = getHttpChannel();
+        if (channel != null)
+            channel.proceed(this, failure);
     }
 
-    /**
-     * @deprecated use {@link org.eclipse.jetty.client.CachedExchange} instead
-     */
-    @Deprecated
-    public static class CachedExchange extends org.eclipse.jetty.client.CachedExchange
+    @Override
+    public String toString()
     {
-        public CachedExchange(boolean cacheFields)
+        synchronized (this)
         {
-            super(cacheFields);
+            return String.format("%s@%x req=%s/%s@%h res=%s/%s@%h",
+                    HttpExchange.class.getSimpleName(),
+                    hashCode(),
+                    requestState, requestFailure, requestFailure,
+                    responseState, responseFailure, responseFailure);
         }
     }
 
-    /**
-     * @deprecated use {@link org.eclipse.jetty.client.ContentExchange} instead
-     */
-    @Deprecated
-    public static class ContentExchange extends org.eclipse.jetty.client.ContentExchange
+    private enum State
     {
+        PENDING, COMPLETED, TERMINATED
     }
 }
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpProxy.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpProxy.java
new file mode 100644
index 0000000..c46bc6c
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpProxy.java
@@ -0,0 +1,205 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.client.api.Connection;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.client.api.Result;
+import org.eclipse.jetty.client.http.HttpConnectionOverHTTP;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpScheme;
+import org.eclipse.jetty.io.ClientConnectionFactory;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.ssl.SslClientConnectionFactory;
+import org.eclipse.jetty.util.Promise;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+
+public class HttpProxy extends ProxyConfiguration.Proxy
+{
+    public HttpProxy(String host, int port)
+    {
+        this(new Origin.Address(host, port), false);
+    }
+
+    public HttpProxy(Origin.Address address, boolean secure)
+    {
+        super(address, secure);
+    }
+
+    @Override
+    public ClientConnectionFactory newClientConnectionFactory(ClientConnectionFactory connectionFactory)
+    {
+        return new HttpProxyClientConnectionFactory(connectionFactory);
+    }
+
+    @Override
+    public URI getURI()
+    {
+        String scheme = isSecure() ? HttpScheme.HTTPS.asString() : HttpScheme.HTTP.asString();
+        return URI.create(new Origin(scheme, getAddress()).asString());
+    }
+
+    public static class HttpProxyClientConnectionFactory implements ClientConnectionFactory
+    {
+        private static final Logger LOG = Log.getLogger(HttpProxyClientConnectionFactory.class);
+        private final ClientConnectionFactory connectionFactory;
+
+        public HttpProxyClientConnectionFactory(ClientConnectionFactory connectionFactory)
+        {
+            this.connectionFactory = connectionFactory;
+        }
+
+        @Override
+        public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map<String, Object> context) throws IOException
+        {
+            @SuppressWarnings("unchecked")
+            Promise<Connection> promise = (Promise<Connection>)context.get(HttpClientTransport.HTTP_CONNECTION_PROMISE_CONTEXT_KEY);
+            final ProxyPromise proxyPromise = new ProxyPromise(endPoint, promise, context);
+            // Replace the promise with the proxy one
+            context.put(HttpClientTransport.HTTP_CONNECTION_PROMISE_CONTEXT_KEY, proxyPromise);
+            return connectionFactory.newConnection(endPoint, context);
+        }
+
+        /**
+         * Decides whether to establish a proxy tunnel using HTTP CONNECT.
+         * It is implemented as a promise because it needs to establish the
+         * tunnel after the TCP connection is succeeded, and needs to notify
+         * the nested promise when the tunnel is established (or failed).
+         */
+        private class ProxyPromise implements Promise<Connection>
+        {
+            private final EndPoint endPoint;
+            private final Promise<Connection> promise;
+            private final Map<String, Object> context;
+
+            private ProxyPromise(EndPoint endPoint, Promise<Connection> promise, Map<String, Object> context)
+            {
+                this.endPoint = endPoint;
+                this.promise = promise;
+                this.context = context;
+            }
+
+            @Override
+            public void succeeded(Connection connection)
+            {
+                HttpDestination destination = (HttpDestination)context.get(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY);
+                if (HttpScheme.HTTPS.is(destination.getScheme()))
+                {
+                    SslContextFactory sslContextFactory = destination.getHttpClient().getSslContextFactory();
+                    if (sslContextFactory != null)
+                    {
+                        tunnel(destination, connection);
+                    }
+                    else
+                    {
+                        String message = String.format("Cannot perform requests over SSL, no %s in %s",
+                                SslContextFactory.class.getSimpleName(), HttpClient.class.getSimpleName());
+                        promise.failed(new IllegalStateException(message));
+                    }
+                }
+                else
+                {
+                    promise.succeeded(connection);
+                }
+            }
+
+            @Override
+            public void failed(Throwable x)
+            {
+                promise.failed(x);
+            }
+
+            private void tunnel(HttpDestination destination, final Connection connection)
+            {
+                String target = destination.getOrigin().getAddress().asString();
+                Origin.Address proxyAddress = destination.getConnectAddress();
+                HttpClient httpClient = destination.getHttpClient();
+                Request connect = httpClient.newRequest(proxyAddress.getHost(), proxyAddress.getPort())
+                        .scheme(HttpScheme.HTTP.asString())
+                        .method(HttpMethod.CONNECT)
+                        .path(target)
+                        .header(HttpHeader.HOST, target)
+                        .timeout(httpClient.getConnectTimeout(), TimeUnit.MILLISECONDS);
+
+                connection.send(connect, new Response.CompleteListener()
+                {
+                    @Override
+                    public void onComplete(Result result)
+                    {
+                        if (result.isFailed())
+                        {
+                            tunnelFailed(result.getFailure());
+                        }
+                        else
+                        {
+                            Response response = result.getResponse();
+                            if (response.getStatus() == 200)
+                            {
+                                tunnelSucceeded();
+                            }
+                            else
+                            {
+                                tunnelFailed(new HttpResponseException("Received " + response + " for " + result.getRequest(), response));
+                            }
+                        }
+                    }
+                });
+            }
+
+            private void tunnelSucceeded()
+            {
+                try
+                {
+                    // Replace the promise back with the original
+                    context.put(HttpClientTransport.HTTP_CONNECTION_PROMISE_CONTEXT_KEY, promise);
+                    HttpDestination destination = (HttpDestination)context.get(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY);
+                    HttpClient client = destination.getHttpClient();
+                    ClientConnectionFactory sslConnectionFactory = new SslClientConnectionFactory(client.getSslContextFactory(), client.getByteBufferPool(), client.getExecutor(), connectionFactory);
+                    HttpConnectionOverHTTP oldConnection = (HttpConnectionOverHTTP)endPoint.getConnection();
+                    org.eclipse.jetty.io.Connection newConnection = sslConnectionFactory.newConnection(endPoint, context);
+                    Helper.replaceConnection(oldConnection, newConnection);
+                    // Avoid setting fill interest in the old Connection,
+                    // without closing the underlying EndPoint.
+                    oldConnection.softClose();
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("HTTP tunnel established: {} over {}", oldConnection, newConnection);
+                }
+                catch (Throwable x)
+                {
+                    tunnelFailed(x);
+                }
+            }
+
+            private void tunnelFailed(Throwable failure)
+            {
+                endPoint.close();
+                failed(failure);
+            }
+        }
+    }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpReceiver.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpReceiver.java
new file mode 100644
index 0000000..deb6069
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpReceiver.java
@@ -0,0 +1,583 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.io.IOException;
+import java.net.URI;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.client.api.Result;
+import org.eclipse.jetty.http.HttpField;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.CountingCallback;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * {@link HttpReceiver} provides the abstract code to implement the various steps of the receive of HTTP responses.
+ * <p />
+ * {@link HttpReceiver} maintains a state machine that is updated when the steps of receiving a response are executed.
+ * <p />
+ * Subclasses must handle the transport-specific details, for example how to read from the raw socket and how to parse
+ * the bytes read from the socket. Then they have to call the methods defined in this class in the following order:
+ * <ol>
+ * <li>{@link #responseBegin(HttpExchange)}, when the HTTP response data containing the HTTP status code
+ * is available</li>
+ * <li>{@link #responseHeader(HttpExchange, HttpField)}, when a HTTP field is available</li>
+ * <li>{@link #responseHeaders(HttpExchange)}, when all HTTP headers are available</li>
+ * <li>{@link #responseContent(HttpExchange, ByteBuffer, Callback)}, when HTTP content is available</li>
+ * <li>{@link #responseSuccess(HttpExchange)}, when the response is successful</li>
+ * </ol>
+ * At any time, subclasses may invoke {@link #responseFailure(Throwable)} to indicate that the response has failed
+ * (for example, because of I/O exceptions).
+ * At any time, user threads may abort the response which will cause {@link #responseFailure(Throwable)} to be
+ * invoked.
+ * <p />
+ * The state machine maintained by this class ensures that the response steps are not executed by an I/O thread
+ * if the response has already been failed.
+ *
+ * @see HttpSender
+ */
+public abstract class HttpReceiver
+{
+    protected static final Logger LOG = Log.getLogger(HttpReceiver.class);
+
+    private final AtomicReference<ResponseState> responseState = new AtomicReference<>(ResponseState.IDLE);
+    private final HttpChannel channel;
+    private ContentDecoder decoder;
+    private Throwable failure;
+
+    protected HttpReceiver(HttpChannel channel)
+    {
+        this.channel = channel;
+    }
+
+    protected HttpChannel getHttpChannel()
+    {
+        return channel;
+    }
+
+    protected HttpExchange getHttpExchange()
+    {
+        return channel.getHttpExchange();
+    }
+
+    protected HttpDestination getHttpDestination()
+    {
+        return channel.getHttpDestination();
+    }
+
+    /**
+     * Method to be invoked when the response status code is available.
+     * <p />
+     * Subclasses must have set the response status code on the {@link Response} object of the {@link HttpExchange}
+     * prior invoking this method.
+     * <p />
+     * This method takes case of notifying {@link org.eclipse.jetty.client.api.Response.BeginListener}s.
+     *
+     * @param exchange the HTTP exchange
+     * @return whether the processing should continue
+     */
+    protected boolean responseBegin(HttpExchange exchange)
+    {
+        if (!updateResponseState(ResponseState.IDLE, ResponseState.TRANSIENT))
+            return false;
+
+        HttpConversation conversation = exchange.getConversation();
+        HttpResponse response = exchange.getResponse();
+        // Probe the protocol handlers
+        HttpDestination destination = getHttpDestination();
+        HttpClient client = destination.getHttpClient();
+        ProtocolHandler protocolHandler = client.findProtocolHandler(exchange.getRequest(), response);
+        Response.Listener handlerListener = null;
+        if (protocolHandler != null)
+        {
+            handlerListener = protocolHandler.getResponseListener();
+            if (LOG.isDebugEnabled())
+                LOG.debug("Found protocol handler {}", protocolHandler);
+        }
+        exchange.getConversation().updateResponseListeners(handlerListener);
+
+        if (LOG.isDebugEnabled())
+            LOG.debug("Response begin {}", response);
+        ResponseNotifier notifier = destination.getResponseNotifier();
+        notifier.notifyBegin(conversation.getResponseListeners(), response);
+
+        if (updateResponseState(ResponseState.TRANSIENT, ResponseState.BEGIN))
+            return true;
+
+        terminateResponse(exchange);
+        return false;
+    }
+
+    /**
+     * Method to be invoked when a response HTTP header is available.
+     * <p />
+     * Subclasses must not have added the header to the {@link Response} object of the {@link HttpExchange}
+     * prior invoking this method.
+     * <p />
+     * This method takes case of notifying {@link org.eclipse.jetty.client.api.Response.HeaderListener}s and storing cookies.
+     *
+     * @param exchange the HTTP exchange
+     * @param field the response HTTP field
+     * @return whether the processing should continue
+     */
+    protected boolean responseHeader(HttpExchange exchange, HttpField field)
+    {
+        out: while (true)
+        {
+            ResponseState current = responseState.get();
+            switch (current)
+            {
+                case BEGIN:
+                case HEADER:
+                {
+                    if (updateResponseState(current, ResponseState.TRANSIENT))
+                        break out;
+                    break;
+                }
+                default:
+                {
+                    return false;
+                }
+            }
+        }
+
+        HttpResponse response = exchange.getResponse();
+        ResponseNotifier notifier = getHttpDestination().getResponseNotifier();
+        boolean process = notifier.notifyHeader(exchange.getConversation().getResponseListeners(), response, field);
+        if (process)
+        {
+            response.getHeaders().add(field);
+            HttpHeader fieldHeader = field.getHeader();
+            if (fieldHeader != null)
+            {
+                switch (fieldHeader)
+                {
+                    case SET_COOKIE:
+                    case SET_COOKIE2:
+                    {
+                        storeCookie(exchange.getRequest().getURI(), field);
+                        break;
+                    }
+                    default:
+                    {
+                        break;
+                    }
+                }
+            }
+        }
+
+        if (updateResponseState(ResponseState.TRANSIENT, ResponseState.HEADER))
+            return true;
+
+        terminateResponse(exchange);
+        return false;
+    }
+
+    protected void storeCookie(URI uri, HttpField field)
+    {
+        try
+        {
+            String value = field.getValue();
+            if (value != null)
+            {
+                Map<String, List<String>> header = new HashMap<>(1);
+                header.put(field.getHeader().asString(), Collections.singletonList(value));
+                getHttpDestination().getHttpClient().getCookieManager().put(uri, header);
+            }
+        }
+        catch (IOException x)
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug(x);
+        }
+    }
+
+    /**
+     * Method to be invoked after all response HTTP headers are available.
+     * <p />
+     * This method takes case of notifying {@link org.eclipse.jetty.client.api.Response.HeadersListener}s.
+     *
+     * @param exchange the HTTP exchange
+     * @return whether the processing should continue
+     */
+    protected boolean responseHeaders(HttpExchange exchange)
+    {
+        out: while (true)
+        {
+            ResponseState current = responseState.get();
+            switch (current)
+            {
+                case BEGIN:
+                case HEADER:
+                {
+                    if (updateResponseState(current, ResponseState.TRANSIENT))
+                        break out;
+                    break;
+                }
+                default:
+                {
+                    return false;
+                }
+            }
+        }
+
+        HttpResponse response = exchange.getResponse();
+        if (LOG.isDebugEnabled())
+            LOG.debug("Response headers {}{}{}", response, System.lineSeparator(), response.getHeaders().toString().trim());
+        ResponseNotifier notifier = getHttpDestination().getResponseNotifier();
+        notifier.notifyHeaders(exchange.getConversation().getResponseListeners(), response);
+
+        Enumeration<String> contentEncodings = response.getHeaders().getValues(HttpHeader.CONTENT_ENCODING.asString(), ",");
+        if (contentEncodings != null)
+        {
+            for (ContentDecoder.Factory factory : getHttpDestination().getHttpClient().getContentDecoderFactories())
+            {
+                while (contentEncodings.hasMoreElements())
+                {
+                    if (factory.getEncoding().equalsIgnoreCase(contentEncodings.nextElement()))
+                    {
+                        this.decoder = factory.newContentDecoder();
+                        break;
+                    }
+                }
+            }
+        }
+
+        if (updateResponseState(ResponseState.TRANSIENT, ResponseState.HEADERS))
+            return true;
+
+        terminateResponse(exchange);
+        return false;
+    }
+
+    /**
+     * Method to be invoked when response HTTP content is available.
+     * <p />
+     * This method takes case of decoding the content, if necessary, and notifying {@link org.eclipse.jetty.client.api.Response.ContentListener}s.
+     *
+     * @param exchange the HTTP exchange
+     * @param buffer the response HTTP content buffer
+     * @return whether the processing should continue
+     */
+    protected boolean responseContent(HttpExchange exchange, ByteBuffer buffer, final Callback callback)
+    {
+        out: while (true)
+        {
+            ResponseState current = responseState.get();
+            switch (current)
+            {
+                case HEADERS:
+                case CONTENT:
+                {
+                    if (updateResponseState(current, ResponseState.TRANSIENT))
+                        break out;
+                    break;
+                }
+                default:
+                {
+                    return false;
+                }
+            }
+        }
+
+        HttpResponse response = exchange.getResponse();
+        if (LOG.isDebugEnabled())
+            LOG.debug("Response content {}{}{}", response, System.lineSeparator(), BufferUtil.toDetailString(buffer));
+
+        ResponseNotifier notifier = getHttpDestination().getResponseNotifier();
+        List<Response.ResponseListener> listeners = exchange.getConversation().getResponseListeners();
+
+        ContentDecoder decoder = this.decoder;
+        if (decoder == null)
+        {
+            notifier.notifyContent(listeners, response, buffer, callback);
+        }
+        else
+        {
+            List<ByteBuffer> decodeds = new ArrayList<>(2);
+            while (buffer.hasRemaining())
+            {
+                ByteBuffer decoded = decoder.decode(buffer);
+                if (!decoded.hasRemaining())
+                    continue;
+                decodeds.add(decoded);
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Response content decoded ({}) {}{}{}", decoder, response, System.lineSeparator(), BufferUtil.toDetailString(decoded));
+            }
+
+            if (decodeds.isEmpty())
+            {
+                callback.succeeded();
+            }
+            else
+            {
+                int size = decodeds.size();
+                CountingCallback counter = new CountingCallback(callback, size);
+                for (int i = 0; i < size; ++i)
+                    notifier.notifyContent(listeners, response, decodeds.get(i), counter);
+            }
+        }
+
+        if (updateResponseState(ResponseState.TRANSIENT, ResponseState.CONTENT))
+            return true;
+
+        terminateResponse(exchange);
+        return false;
+    }
+
+    /**
+     * Method to be invoked when the response is successful.
+     * <p />
+     * This method takes case of notifying {@link org.eclipse.jetty.client.api.Response.SuccessListener}s and possibly
+     * {@link org.eclipse.jetty.client.api.Response.CompleteListener}s (if the exchange is completed).
+     *
+     * @param exchange the HTTP exchange
+     * @return whether the response was processed as successful
+     */
+    protected boolean responseSuccess(HttpExchange exchange)
+    {
+        // Mark atomically the response as completed, with respect
+        // to concurrency between response success and response failure.
+        if (!exchange.responseComplete(null))
+            return false;
+
+        responseState.set(ResponseState.IDLE);
+
+        // Reset to be ready for another response.
+        reset();
+
+        HttpResponse response = exchange.getResponse();
+        if (LOG.isDebugEnabled())
+            LOG.debug("Response success {}", response);
+        List<Response.ResponseListener> listeners = exchange.getConversation().getResponseListeners();
+        ResponseNotifier notifier = getHttpDestination().getResponseNotifier();
+        notifier.notifySuccess(listeners, response);
+
+        // Special case for 100 Continue that cannot
+        // be handled by the ContinueProtocolHandler.
+        if (exchange.getResponse().getStatus() == HttpStatus.CONTINUE_100)
+            return true;
+
+        // Mark atomically the response as terminated, with
+        // respect to concurrency between request and response.
+        Result result = exchange.terminateResponse();
+        terminateResponse(exchange, result);
+
+        return true;
+    }
+
+    /**
+     * Method to be invoked when the response is failed.
+     * <p />
+     * This method takes care of notifying {@link org.eclipse.jetty.client.api.Response.FailureListener}s.
+     *
+     * @param failure the response failure
+     * @return whether the response was processed as failed
+     */
+    protected boolean responseFailure(Throwable failure)
+    {
+        HttpExchange exchange = getHttpExchange();
+        // In case of a response error, the failure has already been notified
+        // and it is possible that a further attempt to read in the receive
+        // loop throws an exception that reenters here but without exchange;
+        // or, the server could just have timed out the connection.
+        if (exchange == null)
+            return false;
+
+        // Mark atomically the response as completed, with respect
+        // to concurrency between response success and response failure.
+        if (exchange.responseComplete(failure))
+            return abort(exchange, failure);
+
+        return false;
+    }
+
+    private void terminateResponse(HttpExchange exchange)
+    {
+        Result result = exchange.terminateResponse();
+        terminateResponse(exchange, result);
+    }
+
+    private void terminateResponse(HttpExchange exchange, Result result)
+    {
+        HttpResponse response = exchange.getResponse();
+
+        if (LOG.isDebugEnabled())
+            LOG.debug("Response complete {}", response);
+
+        if (result != null)
+        {
+            boolean ordered = getHttpDestination().getHttpClient().isStrictEventOrdering();
+            if (!ordered)
+                channel.exchangeTerminated(exchange, result);
+            if (LOG.isDebugEnabled())
+                LOG.debug("Request/Response {}: {}", failure == null ? "succeeded" : "failed", result);
+            List<Response.ResponseListener> listeners = exchange.getConversation().getResponseListeners();
+            ResponseNotifier notifier = getHttpDestination().getResponseNotifier();
+            notifier.notifyComplete(listeners, result);
+            if (ordered)
+                channel.exchangeTerminated(exchange, result);
+        }
+    }
+
+    /**
+     * Resets this {@link HttpReceiver} state.
+     * <p />
+     * Subclasses should override (but remember to call {@code super}) to reset their own state.
+     * <p />
+     * Either this method or {@link #dispose()} is called.
+     */
+    protected void reset()
+    {
+        decoder = null;
+    }
+
+    /**
+     * Disposes this {@link HttpReceiver} state.
+     * <p />
+     * Subclasses should override (but remember to call {@code super}) to dispose their own state.
+     * <p />
+     * Either this method or {@link #reset()} is called.
+     */
+    protected void dispose()
+    {
+        decoder = null;
+    }
+
+    public boolean abort(HttpExchange exchange, Throwable failure)
+    {
+        // Update the state to avoid more response processing.
+        boolean terminate;
+        out: while (true)
+        {
+            ResponseState current = responseState.get();
+            switch (current)
+            {
+                case FAILURE:
+                {
+                    return false;
+                }
+                default:
+                {
+                    if (updateResponseState(current, ResponseState.FAILURE))
+                    {
+                        terminate = current != ResponseState.TRANSIENT;
+                        break out;
+                    }
+                    break;
+                }
+            }
+        }
+
+        this.failure = failure;
+
+        dispose();
+
+        HttpResponse response = exchange.getResponse();
+        if (LOG.isDebugEnabled())
+            LOG.debug("Response failure {} {} on {}: {}", response, exchange, getHttpChannel(), failure);
+        List<Response.ResponseListener> listeners = exchange.getConversation().getResponseListeners();
+        ResponseNotifier notifier = getHttpDestination().getResponseNotifier();
+        notifier.notifyFailure(listeners, response, failure);
+
+        if (terminate)
+        {
+            // Mark atomically the response as terminated, with
+            // respect to concurrency between request and response.
+            Result result = exchange.terminateResponse();
+            terminateResponse(exchange, result);
+        }
+        else
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("Concurrent failure: response termination skipped, performed by helpers");
+        }
+
+        return true;
+    }
+
+    private boolean updateResponseState(ResponseState from, ResponseState to)
+    {
+        boolean updated = responseState.compareAndSet(from, to);
+        if (!updated)
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("State update failed: {} -> {}: {}", from, to, responseState.get());
+        }
+        return updated;
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s@%x(rsp=%s,failure=%s)",
+                getClass().getSimpleName(),
+                hashCode(),
+                responseState,
+                failure);
+    }
+
+    /**
+     * The request states {@link HttpReceiver} goes through when receiving a response.
+     */
+    private enum ResponseState
+    {
+        /**
+         * One of the response*() methods is being executed.
+         */
+        TRANSIENT,
+        /**
+         * The response is not yet received, the initial state
+         */
+        IDLE,
+        /**
+         * The response status code has been received
+         */
+        BEGIN,
+        /**
+         * The response headers are being received
+         */
+        HEADER,
+        /**
+         * All the response headers have been received
+         */
+        HEADERS,
+        /**
+         * The response content is being received
+         */
+        CONTENT,
+        /**
+         * The response is failed
+         */
+        FAILURE
+    }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRedirector.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRedirector.java
new file mode 100644
index 0000000..a0cac1b
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRedirector.java
@@ -0,0 +1,335 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.client.api.Result;
+import org.eclipse.jetty.client.util.BufferingResponseListener;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * Utility class that handles HTTP redirects.
+ * <p />
+ * Applications can disable redirection via {@link Request#followRedirects(boolean)}
+ * and then rely on this class to perform the redirect in a simpler way, for example:
+ * <pre>
+ * HttpRedirector redirector = new HttpRedirector(httpClient);
+ *
+ * Request request = httpClient.newRequest("http://host/path").followRedirects(false);
+ * ContentResponse response = request.send();
+ * while (redirector.isRedirect(response))
+ * {
+ *     // Validate the redirect URI
+ *     if (!validate(redirector.extractRedirectURI(response)))
+ *         break;
+ *
+ *     Result result = redirector.redirect(request, response);
+ *     request = result.getRequest();
+ *     response = result.getResponse();
+ * }
+ * </pre>
+ */
+public class HttpRedirector
+{
+    private static final Logger LOG = Log.getLogger(HttpRedirector.class);
+    private static final String SCHEME_REGEXP = "(^https?)";
+    private static final String AUTHORITY_REGEXP = "([^/\\?#]+)";
+    // The location may be relative so the scheme://authority part may be missing
+    private static final String DESTINATION_REGEXP = "(" + SCHEME_REGEXP + "://" + AUTHORITY_REGEXP + ")?";
+    private static final String PATH_REGEXP = "([^\\?#]*)";
+    private static final String QUERY_REGEXP = "([^#]*)";
+    private static final String FRAGMENT_REGEXP = "(.*)";
+    private static final Pattern URI_PATTERN = Pattern.compile(DESTINATION_REGEXP + PATH_REGEXP + QUERY_REGEXP + FRAGMENT_REGEXP);
+    private static final String ATTRIBUTE = HttpRedirector.class.getName() + ".redirects";
+
+    private final HttpClient client;
+    private final ResponseNotifier notifier;
+
+    public HttpRedirector(HttpClient client)
+    {
+        this.client = client;
+        this.notifier = new ResponseNotifier();
+    }
+
+    /**
+     * @param response the response to check for redirects
+     * @return whether the response code is a HTTP redirect code
+     */
+    public boolean isRedirect(Response response)
+    {
+        switch (response.getStatus())
+        {
+            case 301:
+            case 302:
+            case 303:
+            case 307:
+            case 308:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    /**
+     * Redirects the given {@code response}, blocking until the redirect is complete.
+     *
+     * @param request the original request that triggered the redirect
+     * @param response the response to the original request
+     * @return a {@link Result} object containing the request to the redirected location and its response
+     * @throws InterruptedException if the thread is interrupted while waiting for the redirect to complete
+     * @throws ExecutionException if the redirect failed
+     * @see #redirect(Request, Response, Response.CompleteListener)
+     */
+    public Result redirect(Request request, Response response) throws InterruptedException, ExecutionException
+    {
+        final AtomicReference<Result> resultRef = new AtomicReference<>();
+        final CountDownLatch latch = new CountDownLatch(1);
+        Request redirect = redirect(request, response, new BufferingResponseListener()
+        {
+            @Override
+            public void onComplete(Result result)
+            {
+                resultRef.set(new Result(result.getRequest(),
+                        result.getRequestFailure(),
+                        new HttpContentResponse(result.getResponse(), getContent(), getMediaType(), getEncoding()),
+                        result.getResponseFailure()));
+                latch.countDown();
+            }
+        });
+
+        try
+        {
+            latch.await();
+            Result result = resultRef.get();
+            if (result.isFailed())
+                throw new ExecutionException(result.getFailure());
+            return result;
+        }
+        catch (InterruptedException x)
+        {
+            // If the application interrupts, we need to abort the redirect
+            redirect.abort(x);
+            throw x;
+        }
+    }
+
+    /**
+     * Redirects the given {@code response} asynchronously.
+     *
+     * @param request the original request that triggered the redirect
+     * @param response the response to the original request
+     * @param listener the listener that receives response events
+     * @return the request to the redirected location
+     */
+    public Request redirect(Request request, Response response, Response.CompleteListener listener)
+    {
+        if (isRedirect(response))
+        {
+            String location = response.getHeaders().get("Location");
+            URI newURI = extractRedirectURI(response);
+            if (newURI != null)
+            {
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Redirecting to {} (Location: {})", newURI, location);
+                return redirect(request, response, listener, newURI);
+            }
+            else
+            {
+                fail(request, response, new HttpResponseException("Invalid 'Location' header: " + location, response));
+                return null;
+            }
+        }
+        else
+        {
+            fail(request, response, new HttpResponseException("Cannot redirect: " + response, response));
+            return null;
+        }
+    }
+
+    /**
+     * Extracts and sanitizes (by making it absolute and escaping paths and query parameters)
+     * the redirect URI of the given {@code response}.
+     *
+     * @param response the response to extract the redirect URI from
+     * @return the absolute redirect URI, or null if the response does not contain a valid redirect location
+     */
+    public URI extractRedirectURI(Response response)
+    {
+        String location = response.getHeaders().get("location");
+        if (location != null)
+            return sanitize(location);
+        return null;
+    }
+
+    private URI sanitize(String location)
+    {
+        // Redirects should be valid, absolute, URIs, with properly escaped paths and encoded
+        // query parameters. However, shit happens, and here we try our best to recover.
+
+        try
+        {
+            // Direct hit first: if passes, we're good
+            return new URI(location);
+        }
+        catch (URISyntaxException x)
+        {
+            Matcher matcher = URI_PATTERN.matcher(location);
+            if (matcher.matches())
+            {
+                String scheme = matcher.group(2);
+                String authority = matcher.group(3);
+                String path = matcher.group(4);
+                String query = matcher.group(5);
+                if (query.length() == 0)
+                    query = null;
+                String fragment = matcher.group(6);
+                if (fragment.length() == 0)
+                    fragment = null;
+                try
+                {
+                    return new URI(scheme, authority, path, query, fragment);
+                }
+                catch (URISyntaxException xx)
+                {
+                    // Give up
+                }
+            }
+            return null;
+        }
+    }
+
+    private Request redirect(Request request, Response response, Response.CompleteListener listener, URI newURI)
+    {
+        if (!newURI.isAbsolute())
+            newURI = request.getURI().resolve(newURI);
+
+        int status = response.getStatus();
+        switch (status)
+        {
+            case 301:
+            {
+                String method = request.getMethod();
+                if (HttpMethod.GET.is(method) || HttpMethod.HEAD.is(method) || HttpMethod.PUT.is(method))
+                    return redirect(request, response, listener, newURI, method);
+                else if (HttpMethod.POST.is(method))
+                    return redirect(request, response, listener, newURI, HttpMethod.GET.asString());
+                fail(request, response, new HttpResponseException("HTTP protocol violation: received 301 for non GET/HEAD/POST/PUT request", response));
+                return null;
+            }
+            case 302:
+            {
+                String method = request.getMethod();
+                if (HttpMethod.HEAD.is(method) || HttpMethod.PUT.is(method))
+                    return redirect(request, response, listener, newURI, method);
+                else
+                    return redirect(request, response, listener, newURI, HttpMethod.GET.asString());
+            }
+            case 303:
+            {
+                String method = request.getMethod();
+                if (HttpMethod.HEAD.is(method))
+                    return redirect(request, response, listener, newURI, method);
+                else
+                    return redirect(request, response, listener, newURI, HttpMethod.GET.asString());
+            }
+            case 307:
+            case 308:
+            {
+                // Keep same method
+                return redirect(request, response, listener, newURI, request.getMethod());
+            }
+            default:
+            {
+                fail(request, response, new HttpResponseException("Unhandled HTTP status code " + status, response));
+                return null;
+            }
+        }
+    }
+
+    private Request redirect(Request request, Response response, Response.CompleteListener listener, URI location, String method)
+    {
+        HttpRequest httpRequest = (HttpRequest)request;
+        HttpConversation conversation = httpRequest.getConversation();
+        Integer redirects = (Integer)conversation.getAttribute(ATTRIBUTE);
+        if (redirects == null)
+            redirects = 0;
+        if (redirects < client.getMaxRedirects())
+        {
+            ++redirects;
+            conversation.setAttribute(ATTRIBUTE, redirects);
+            return sendRedirect(httpRequest, response, listener, location, method);
+        }
+        else
+        {
+            fail(request, response, new HttpResponseException("Max redirects exceeded " + redirects, response));
+            return null;
+        }
+    }
+
+    private Request sendRedirect(final HttpRequest httpRequest, Response response, Response.CompleteListener listener, URI location, String method)
+    {
+        try
+        {
+            Request redirect = client.copyRequest(httpRequest, location);
+
+            // Use given method
+            redirect.method(method);
+
+            redirect.onRequestBegin(new Request.BeginListener()
+            {
+                @Override
+                public void onBegin(Request redirect)
+                {
+                    Throwable cause = httpRequest.getAbortCause();
+                    if (cause != null)
+                        redirect.abort(cause);
+                }
+            });
+
+            redirect.send(listener);
+            return redirect;
+        }
+        catch (Throwable x)
+        {
+            fail(httpRequest, response, x);
+            return null;
+        }
+    }
+
+    protected void fail(Request request, Response response, Throwable failure)
+    {
+        HttpConversation conversation = ((HttpRequest)request).getConversation();
+        conversation.updateResponseListeners(null);
+        List<Response.ResponseListener> listeners = conversation.getResponseListeners();
+        notifier.notifyFailure(listeners, response, failure);
+        notifier.notifyComplete(listeners, new Result(request, response, failure));
+    }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequest.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequest.java
new file mode 100644
index 0000000..8fe757c
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequest.java
@@ -0,0 +1,815 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.HttpCookie;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+import java.nio.ByteBuffer;
+import java.nio.charset.UnsupportedCharsetException;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.jetty.client.api.ContentProvider;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.client.api.Result;
+import org.eclipse.jetty.client.util.FutureResponseListener;
+import org.eclipse.jetty.client.util.PathContentProvider;
+import org.eclipse.jetty.http.HttpField;
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.Fields;
+
+public class HttpRequest implements Request
+{
+    private static final URI NULL_URI = URI.create("null:0");
+
+    private final HttpFields headers = new HttpFields();
+    private final Fields params = new Fields(true);
+    private final List<Response.ResponseListener> responseListeners = new ArrayList<>();
+    private final AtomicReference<Throwable> aborted = new AtomicReference<>();
+    private final HttpClient client;
+    private final HttpConversation conversation;
+    private final String host;
+    private final int port;
+    private URI uri;
+    private String scheme;
+    private String path;
+    private String query;
+    private String method = HttpMethod.GET.asString();
+    private HttpVersion version = HttpVersion.HTTP_1_1;
+    private long idleTimeout;
+    private long timeout;
+    private ContentProvider content;
+    private boolean followRedirects;
+    private List<HttpCookie> cookies;
+    private Map<String, Object> attributes;
+    private List<RequestListener> requestListeners;
+
+    protected HttpRequest(HttpClient client, HttpConversation conversation, URI uri)
+    {
+        this.client = client;
+        this.conversation = conversation;
+        scheme = uri.getScheme();
+        host = client.normalizeHost(uri.getHost());
+        port = client.normalizePort(scheme, uri.getPort());
+        path = uri.getRawPath();
+        query = uri.getRawQuery();
+        extractParams(query);
+        followRedirects(client.isFollowRedirects());
+        idleTimeout = client.getIdleTimeout();
+        HttpField acceptEncodingField = client.getAcceptEncodingField();
+        if (acceptEncodingField != null)
+            headers.put(acceptEncodingField);
+        HttpField userAgentField = client.getUserAgentField();
+        if (userAgentField != null)
+            headers.put(userAgentField);
+    }
+
+    protected HttpConversation getConversation()
+    {
+        return conversation;
+    }
+
+    @Override
+    public String getScheme()
+    {
+        return scheme;
+    }
+
+    @Override
+    public Request scheme(String scheme)
+    {
+        this.scheme = scheme;
+        this.uri = null;
+        return this;
+    }
+
+    @Override
+    public String getHost()
+    {
+        return host;
+    }
+
+    @Override
+    public int getPort()
+    {
+        return port;
+    }
+
+    @Override
+    public String getMethod()
+    {
+        return method;
+    }
+
+    @Override
+    public Request method(HttpMethod method)
+    {
+        return method(method.asString());
+    }
+
+    @Override
+    public Request method(String method)
+    {
+        this.method = Objects.requireNonNull(method).toUpperCase(Locale.ENGLISH);
+        return this;
+    }
+
+    @Override
+    public String getPath()
+    {
+        return path;
+    }
+
+    @Override
+    public Request path(String path)
+    {
+        URI uri = newURI(path);
+        if (uri == null)
+        {
+            this.path = path;
+            this.query = null;
+        }
+        else
+        {
+            String rawPath = uri.getRawPath();
+            if (uri.isOpaque())
+                rawPath = path;
+            if (rawPath == null)
+                rawPath = "";
+            this.path = rawPath;
+            String query = uri.getRawQuery();
+            if (query != null)
+            {
+                this.query = query;
+                params.clear();
+                extractParams(query);
+            }
+            if (uri.isAbsolute())
+                this.path = buildURI(false).toString();
+        }
+        this.uri = null;
+        return this;
+    }
+
+    @Override
+    public String getQuery()
+    {
+        return query;
+    }
+
+    @Override
+    public URI getURI()
+    {
+        if (uri == null)
+            uri = buildURI(true);
+        return uri == NULL_URI ? null : uri;
+    }
+
+    @Override
+    public HttpVersion getVersion()
+    {
+        return version;
+    }
+
+    @Override
+    public Request version(HttpVersion version)
+    {
+        this.version = Objects.requireNonNull(version);
+        return this;
+    }
+
+    @Override
+    public Request param(String name, String value)
+    {
+        return param(name, value, false);
+    }
+
+    private Request param(String name, String value, boolean fromQuery)
+    {
+        params.add(name, value);
+        if (!fromQuery)
+        {
+            // If we have an existing query string, preserve it and append the new parameter.
+            if (query != null)
+                query += "&" + urlEncode(name) + "=" + urlEncode(value);
+            else
+                query = buildQuery();
+            uri = null;
+        }
+        return this;
+    }
+
+    @Override
+    public Fields getParams()
+    {
+        return new Fields(params, true);
+    }
+
+    @Override
+    public String getAgent()
+    {
+        return headers.get(HttpHeader.USER_AGENT);
+    }
+
+    @Override
+    public Request agent(String agent)
+    {
+        headers.put(HttpHeader.USER_AGENT, agent);
+        return this;
+    }
+
+    @Override
+    public Request accept(String... accepts)
+    {
+        StringBuilder result = new StringBuilder();
+        for (String accept : accepts)
+        {
+            if (result.length() > 0)
+                result.append(", ");
+            result.append(accept);
+        }
+        if (result.length() > 0)
+            headers.put(HttpHeader.ACCEPT, result.toString());
+        return this;
+    }
+
+    @Override
+    public Request header(String name, String value)
+    {
+        if (value == null)
+            headers.remove(name);
+        else
+            headers.add(name, value);
+        return this;
+    }
+
+    @Override
+    public Request header(HttpHeader header, String value)
+    {
+        if (value == null)
+            headers.remove(header);
+        else
+            headers.add(header, value);
+        return this;
+    }
+
+    @Override
+    public List<HttpCookie> getCookies()
+    {
+        return cookies != null ? cookies : Collections.<HttpCookie>emptyList();
+    }
+
+    @Override
+    public Request cookie(HttpCookie cookie)
+    {
+        if (cookies == null)
+            cookies = new ArrayList<>();
+        cookies.add(cookie);
+        return this;
+    }
+
+    @Override
+    public Request attribute(String name, Object value)
+    {
+        if (attributes == null)
+            attributes = new HashMap<>(4);
+        attributes.put(name, value);
+        return this;
+    }
+
+    @Override
+    public Map<String, Object> getAttributes()
+    {
+        return attributes != null ? attributes : Collections.<String, Object>emptyMap();
+    }
+
+    @Override
+    public HttpFields getHeaders()
+    {
+        return headers;
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public <T extends RequestListener> List<T> getRequestListeners(Class<T> type)
+    {
+        // This method is invoked often in a request/response conversation,
+        // so we avoid allocation if there is no need to filter.
+        if (type == null || requestListeners == null)
+            return requestListeners != null ? (List<T>)requestListeners : Collections.<T>emptyList();
+
+        ArrayList<T> result = new ArrayList<>();
+        for (RequestListener listener : requestListeners)
+            if (type.isInstance(listener))
+                result.add((T)listener);
+        return result;
+    }
+
+    @Override
+    public Request listener(Request.Listener listener)
+    {
+        return requestListener(listener);
+    }
+
+    @Override
+    public Request onRequestQueued(final QueuedListener listener)
+    {
+        return requestListener(new QueuedListener()
+        {
+            @Override
+            public void onQueued(Request request)
+            {
+                listener.onQueued(request);
+            }
+        });
+    }
+
+    @Override
+    public Request onRequestBegin(final BeginListener listener)
+    {
+        return requestListener(new BeginListener()
+        {
+            @Override
+            public void onBegin(Request request)
+            {
+                listener.onBegin(request);
+            }
+        });
+    }
+
+    @Override
+    public Request onRequestHeaders(final HeadersListener listener)
+    {
+        return requestListener(new HeadersListener()
+        {
+            @Override
+            public void onHeaders(Request request)
+            {
+                listener.onHeaders(request);
+            }
+        });
+    }
+
+    @Override
+    public Request onRequestCommit(final CommitListener listener)
+    {
+        return requestListener(new CommitListener()
+        {
+            @Override
+            public void onCommit(Request request)
+            {
+                listener.onCommit(request);
+            }
+        });
+    }
+
+    @Override
+    public Request onRequestContent(final ContentListener listener)
+    {
+        return requestListener(new ContentListener()
+        {
+            @Override
+            public void onContent(Request request, ByteBuffer content)
+            {
+                listener.onContent(request, content);
+            }
+        });
+    }
+
+    @Override
+    public Request onRequestSuccess(final SuccessListener listener)
+    {
+        return requestListener(new SuccessListener()
+        {
+            @Override
+            public void onSuccess(Request request)
+            {
+                listener.onSuccess(request);
+            }
+        });
+    }
+
+    @Override
+    public Request onRequestFailure(final FailureListener listener)
+    {
+        return requestListener(new FailureListener()
+        {
+            @Override
+            public void onFailure(Request request, Throwable failure)
+            {
+                listener.onFailure(request, failure);
+            }
+        });
+    }
+
+    private Request requestListener(RequestListener listener)
+    {
+        if (requestListeners == null)
+            requestListeners = new ArrayList<>();
+        requestListeners.add(listener);
+        return this;
+    }
+
+    @Override
+    public Request onResponseBegin(final Response.BeginListener listener)
+    {
+        this.responseListeners.add(new Response.BeginListener()
+        {
+            @Override
+            public void onBegin(Response response)
+            {
+                listener.onBegin(response);
+            }
+        });
+        return this;
+    }
+
+    @Override
+    public Request onResponseHeader(final Response.HeaderListener listener)
+    {
+        this.responseListeners.add(new Response.HeaderListener()
+        {
+            @Override
+            public boolean onHeader(Response response, HttpField field)
+            {
+                return listener.onHeader(response, field);
+            }
+        });
+        return this;
+    }
+
+    @Override
+    public Request onResponseHeaders(final Response.HeadersListener listener)
+    {
+        this.responseListeners.add(new Response.HeadersListener()
+        {
+            @Override
+            public void onHeaders(Response response)
+            {
+                listener.onHeaders(response);
+            }
+        });
+        return this;
+    }
+
+    @Override
+    public Request onResponseContent(final Response.ContentListener listener)
+    {
+        this.responseListeners.add(new Response.AsyncContentListener()
+        {
+            @Override
+            public void onContent(Response response, ByteBuffer content, Callback callback)
+            {
+                try
+                {
+                    listener.onContent(response, content);
+                    callback.succeeded();
+                }
+                catch (Exception x)
+                {
+                    callback.failed(x);
+                }
+            }
+        });
+        return this;
+    }
+
+    @Override
+    public Request onResponseContentAsync(final Response.AsyncContentListener listener)
+    {
+        this.responseListeners.add(new Response.AsyncContentListener()
+        {
+            @Override
+            public void onContent(Response response, ByteBuffer content, Callback callback)
+            {
+                listener.onContent(response, content, callback);
+            }
+        });
+        return this;
+    }
+
+    @Override
+    public Request onResponseSuccess(final Response.SuccessListener listener)
+    {
+        this.responseListeners.add(new Response.SuccessListener()
+        {
+            @Override
+            public void onSuccess(Response response)
+            {
+                listener.onSuccess(response);
+            }
+        });
+        return this;
+    }
+
+    @Override
+    public Request onResponseFailure(final Response.FailureListener listener)
+    {
+        this.responseListeners.add(new Response.FailureListener()
+        {
+            @Override
+            public void onFailure(Response response, Throwable failure)
+            {
+                listener.onFailure(response, failure);
+            }
+        });
+        return this;
+    }
+
+    @Override
+    public Request onComplete(final Response.CompleteListener listener)
+    {
+        this.responseListeners.add(new Response.CompleteListener()
+        {
+            @Override
+            public void onComplete(Result result)
+            {
+                listener.onComplete(result);
+            }
+        });
+        return this;
+    }
+
+    @Override
+    public ContentProvider getContent()
+    {
+        return content;
+    }
+
+    @Override
+    public Request content(ContentProvider content)
+    {
+        return content(content, null);
+    }
+
+    @Override
+    public Request content(ContentProvider content, String contentType)
+    {
+        if (contentType != null)
+            header(HttpHeader.CONTENT_TYPE, contentType);
+        this.content = content;
+        return this;
+    }
+
+    @Override
+    public Request file(Path file) throws IOException
+    {
+        return file(file, "application/octet-stream");
+    }
+
+    @Override
+    public Request file(Path file, String contentType) throws IOException
+    {
+        return content(new PathContentProvider(contentType, file));
+    }
+
+    @Override
+    public boolean isFollowRedirects()
+    {
+        return followRedirects;
+    }
+
+    @Override
+    public Request followRedirects(boolean follow)
+    {
+        this.followRedirects = follow;
+        return this;
+    }
+
+    @Override
+    public long getIdleTimeout()
+    {
+        return idleTimeout;
+    }
+
+    @Override
+    public Request idleTimeout(long timeout, TimeUnit unit)
+    {
+        this.idleTimeout = unit.toMillis(timeout);
+        return this;
+    }
+
+    @Override
+    public long getTimeout()
+    {
+        return timeout;
+    }
+
+    @Override
+    public Request timeout(long timeout, TimeUnit unit)
+    {
+        this.timeout = unit.toMillis(timeout);
+        return this;
+    }
+
+    @Override
+    public ContentResponse send() throws InterruptedException, TimeoutException, ExecutionException
+    {
+        FutureResponseListener listener = new FutureResponseListener(this);
+        send(this, listener);
+
+        try
+        {
+            long timeout = getTimeout();
+            if (timeout <= 0)
+                return listener.get();
+
+            return listener.get(timeout, TimeUnit.MILLISECONDS);
+        }
+        catch (Throwable x)
+        {
+            // Differently from the Future, the semantic of this method is that if
+            // the send() is interrupted or times out, we abort the request.
+            abort(x);
+            throw x;
+        }
+    }
+
+    @Override
+    public void send(Response.CompleteListener listener)
+    {
+        TimeoutCompleteListener timeoutListener = null;
+        try
+        {
+            if (getTimeout() > 0)
+            {
+                timeoutListener = new TimeoutCompleteListener(this);
+                timeoutListener.schedule(client.getScheduler());
+                responseListeners.add(timeoutListener);
+            }
+            send(this, listener);
+        }
+        catch (Throwable x)
+        {
+            // Do not leak the scheduler task if we
+            // can't even start sending the request.
+            if (timeoutListener != null)
+                timeoutListener.cancel();
+            throw x;
+        }
+    }
+
+    private void send(HttpRequest request, Response.CompleteListener listener)
+    {
+        if (listener != null)
+            responseListeners.add(listener);
+        client.send(request, responseListeners);
+    }
+
+    @Override
+    public boolean abort(Throwable cause)
+    {
+        if (aborted.compareAndSet(null, Objects.requireNonNull(cause)))
+        {
+            if (content instanceof Callback)
+                ((Callback)content).failed(cause);
+            return conversation.abort(cause);
+        }
+        return false;
+    }
+
+    @Override
+    public Throwable getAbortCause()
+    {
+        return aborted.get();
+    }
+
+    private String buildQuery()
+    {
+        StringBuilder result = new StringBuilder();
+        for (Iterator<Fields.Field> iterator = params.iterator(); iterator.hasNext(); )
+        {
+            Fields.Field field = iterator.next();
+            List<String> values = field.getValues();
+            for (int i = 0; i < values.size(); ++i)
+            {
+                if (i > 0)
+                    result.append("&");
+                result.append(field.getName()).append("=");
+                result.append(urlEncode(values.get(i)));
+            }
+            if (iterator.hasNext())
+                result.append("&");
+        }
+        return result.toString();
+    }
+
+    private String urlEncode(String value)
+    {
+        if (value == null)
+            return "";
+
+        String encoding = "UTF-8";
+        try
+        {
+            return URLEncoder.encode(value, encoding);
+        }
+        catch (UnsupportedEncodingException e)
+        {
+            throw new UnsupportedCharsetException(encoding);
+        }
+    }
+
+    private void extractParams(String query)
+    {
+        if (query != null)
+        {
+            for (String nameValue : query.split("&"))
+            {
+                String[] parts = nameValue.split("=");
+                if (parts.length > 0)
+                {
+                    String name = urlDecode(parts[0]);
+                    if (name.trim().length() == 0)
+                        continue;
+                    param(name, parts.length < 2 ? "" : urlDecode(parts[1]), true);
+                }
+            }
+        }
+    }
+
+    private String urlDecode(String value)
+    {
+        String charset = "UTF-8";
+        try
+        {
+            return URLDecoder.decode(value, charset);
+        }
+        catch (UnsupportedEncodingException x)
+        {
+            throw new UnsupportedCharsetException(charset);
+        }
+    }
+
+    private URI buildURI(boolean withQuery)
+    {
+        String path = getPath();
+        String query = getQuery();
+        if (query != null && withQuery)
+            path += "?" + query;
+        URI result = newURI(path);
+        if (result == null)
+            return NULL_URI;
+        if (!result.isAbsolute() && !result.isOpaque())
+            result = URI.create(new Origin(getScheme(), getHost(), getPort()).asString() + path);
+        return result;
+    }
+
+    private URI newURI(String uri)
+    {
+        try
+        {
+            return new URI(uri);
+        }
+        catch (URISyntaxException x)
+        {
+            // The "path" of a HTTP request may not be a URI,
+            // for example for CONNECT 127.0.0.1:8080 or OPTIONS *.
+            return null;
+        }
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s[%s %s %s]@%x", HttpRequest.class.getSimpleName(), getMethod(), getPath(), getVersion(), hashCode());
+    }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequestException.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequestException.java
new file mode 100644
index 0000000..37fcb97
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequestException.java
@@ -0,0 +1,37 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import org.eclipse.jetty.client.api.Request;
+
+public class HttpRequestException extends Throwable
+{
+    private final Request request;
+
+    public HttpRequestException(String message, Request request)
+    {
+        super(message);
+        this.request = request;
+    }
+
+    public Request getRequest()
+    {
+        return request;
+    }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpResponse.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpResponse.java
new file mode 100644
index 0000000..56a9b3d
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpResponse.java
@@ -0,0 +1,111 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpVersion;
+
+public class HttpResponse implements Response
+{
+    private final HttpFields headers = new HttpFields();
+    private final Request request;
+    private final List<ResponseListener> listeners;
+    private HttpVersion version;
+    private int status;
+    private String reason;
+
+    public HttpResponse(Request request, List<ResponseListener> listeners)
+    {
+        this.request = request;
+        this.listeners = listeners;
+    }
+
+    @Override
+    public Request getRequest()
+    {
+        return request;
+    }
+
+    public HttpVersion getVersion()
+    {
+        return version;
+    }
+
+    public HttpResponse version(HttpVersion version)
+    {
+        this.version = version;
+        return this;
+    }
+
+    @Override
+    public int getStatus()
+    {
+        return status;
+    }
+
+    public HttpResponse status(int status)
+    {
+        this.status = status;
+        return this;
+    }
+
+    public String getReason()
+    {
+        return reason;
+    }
+
+    public HttpResponse reason(String reason)
+    {
+        this.reason = reason;
+        return this;
+    }
+
+    @Override
+    public HttpFields getHeaders()
+    {
+        return headers;
+    }
+
+    @Override
+    public <T extends ResponseListener> List<T> getListeners(Class<T> type)
+    {
+        ArrayList<T> result = new ArrayList<>();
+        for (ResponseListener listener : listeners)
+            if (type == null || type.isInstance(listener))
+                result.add((T)listener);
+        return result;
+    }
+
+    @Override
+    public boolean abort(Throwable cause)
+    {
+        return request.abort(cause);
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s[%s %d %s]@%x", HttpResponse.class.getSimpleName(), getVersion(), getStatus(), getReason(), hashCode());
+    }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpResponseException.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpResponseException.java
new file mode 100644
index 0000000..f32aa60
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpResponseException.java
@@ -0,0 +1,37 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import org.eclipse.jetty.client.api.Response;
+
+public class HttpResponseException extends RuntimeException
+{
+    private final Response response;
+
+    public HttpResponseException(String message, Response response)
+    {
+        super(message);
+        this.response = response;
+    }
+
+    public Response getResponse()
+    {
+        return response;
+    }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpSender.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpSender.java
new file mode 100644
index 0000000..be6b761
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpSender.java
@@ -0,0 +1,908 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.nio.ByteBuffer;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.jetty.client.api.ContentProvider;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.api.Result;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpHeaderValue;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.IteratingCallback;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * {@link HttpSender} abstracts the algorithm to send HTTP requests, so that subclasses only implement
+ * the transport-specific code to send requests over the wire, implementing
+ * {@link #sendHeaders(HttpExchange, HttpContent, Callback)} and
+ * {@link #sendContent(HttpExchange, HttpContent, Callback)}.
+ * <p />
+ * {@link HttpSender} governs two state machines.
+ * <p />
+ * The request state machine is updated by {@link HttpSender} as the various steps of sending a request
+ * are executed, see {@link RequestState}.
+ * At any point in time, a user thread may abort the request, which may (if the request has not been
+ * completely sent yet) move the request state machine to {@link RequestState#FAILURE}.
+ * The request state machine guarantees that the request steps are executed (by I/O threads) only if
+ * the request has not been failed already.
+ * <p />
+ * The sender state machine is updated by {@link HttpSender} from three sources: deferred content notifications
+ * (via {@link #onContent()}), 100-continue notifications (via {@link #proceed(HttpExchange, Throwable)})
+ * and normal request send (via {@link #sendContent(HttpExchange, HttpContent, Callback)}).
+ * This state machine must guarantee that the request sending is never executed concurrently: only one of
+ * those sources may trigger the call to {@link #sendContent(HttpExchange, HttpContent, Callback)}.
+ *
+ * @see HttpReceiver
+ */
+public abstract class HttpSender implements AsyncContentProvider.Listener
+{
+    protected static final Logger LOG = Log.getLogger(HttpSender.class);
+
+    private final AtomicReference<RequestState> requestState = new AtomicReference<>(RequestState.QUEUED);
+    private final AtomicReference<SenderState> senderState = new AtomicReference<>(SenderState.IDLE);
+    private final Callback commitCallback = new CommitCallback();
+    private final IteratingCallback contentCallback = new ContentCallback();
+    private final Callback lastCallback = new LastContentCallback();
+    private final HttpChannel channel;
+    private HttpContent content;
+    private Throwable failure;
+
+    protected HttpSender(HttpChannel channel)
+    {
+        this.channel = channel;
+    }
+
+    protected HttpChannel getHttpChannel()
+    {
+        return channel;
+    }
+
+    protected HttpExchange getHttpExchange()
+    {
+        return channel.getHttpExchange();
+    }
+
+    @Override
+    public void onContent()
+    {
+        HttpExchange exchange = getHttpExchange();
+        if (exchange == null)
+            return;
+
+        while (true)
+        {
+            SenderState current = senderState.get();
+            switch (current)
+            {
+                case IDLE:
+                {
+                    SenderState newSenderState = SenderState.SENDING;
+                    if (updateSenderState(current, newSenderState))
+                    {
+                        if (LOG.isDebugEnabled())
+                            LOG.debug("Deferred content available, {} -> {}", current, newSenderState);
+                        contentCallback.iterate();
+                        return;
+                    }
+                    break;
+                }
+                case SENDING:
+                {
+                    SenderState newSenderState = SenderState.SENDING_WITH_CONTENT;
+                    if (updateSenderState(current, newSenderState))
+                    {
+                        if (LOG.isDebugEnabled())
+                            LOG.debug("Deferred content available, {} -> {}", current, newSenderState);
+                        return;
+                    }
+                    break;
+                }
+                case EXPECTING:
+                {
+                    SenderState newSenderState = SenderState.EXPECTING_WITH_CONTENT;
+                    if (updateSenderState(current, newSenderState))
+                    {
+                        if (LOG.isDebugEnabled())
+                            LOG.debug("Deferred content available, {} -> {}", current, newSenderState);
+                        return;
+                    }
+                    break;
+                }
+                case PROCEEDING:
+                {
+                    SenderState newSenderState = SenderState.PROCEEDING_WITH_CONTENT;
+                    if (updateSenderState(current, newSenderState))
+                    {
+                        if (LOG.isDebugEnabled())
+                            LOG.debug("Deferred content available, {} -> {}", current, newSenderState);
+                        return;
+                    }
+                    break;
+                }
+                case SENDING_WITH_CONTENT:
+                case EXPECTING_WITH_CONTENT:
+                case PROCEEDING_WITH_CONTENT:
+                case WAITING:
+                case COMPLETED:
+                case FAILED:
+                {
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("Deferred content available, {}", current);
+                    return;
+                }
+                default:
+                {
+                    illegalSenderState(current);
+                    return;
+                }
+            }
+        }
+    }
+
+    public void send(HttpExchange exchange)
+    {
+        if (!queuedToBegin(exchange))
+            return;
+
+        Request request = exchange.getRequest();
+        ContentProvider contentProvider = request.getContent();
+        HttpContent content = this.content = new HttpContent(contentProvider);
+
+        SenderState newSenderState = SenderState.SENDING;
+        if (expects100Continue(request))
+            newSenderState = content.hasContent() ? SenderState.EXPECTING_WITH_CONTENT : SenderState.EXPECTING;
+
+        out: while (true)
+        {
+            SenderState current = senderState.get();
+            switch (current)
+            {
+                case IDLE:
+                case COMPLETED:
+                {
+                    if (updateSenderState(current, newSenderState))
+                        break out;
+                    break;
+                }
+                default:
+                {
+                    illegalSenderState(current);
+                    return;
+                }
+            }
+        }
+
+        // Setting the listener may trigger calls to onContent() by other
+        // threads so we must set it only after the sender state has been updated
+        if (contentProvider instanceof AsyncContentProvider)
+            ((AsyncContentProvider)contentProvider).setListener(this);
+
+        if (!beginToHeaders(exchange))
+            return;
+
+        sendHeaders(exchange, content, commitCallback);
+    }
+
+    protected boolean expects100Continue(Request request)
+    {
+        return request.getHeaders().contains(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString());
+    }
+
+    protected boolean queuedToBegin(HttpExchange exchange)
+    {
+        if (!updateRequestState(RequestState.QUEUED, RequestState.TRANSIENT))
+            return false;
+
+        Request request = exchange.getRequest();
+        if (LOG.isDebugEnabled())
+            LOG.debug("Request begin {}", request);
+        RequestNotifier notifier = getHttpChannel().getHttpDestination().getRequestNotifier();
+        notifier.notifyBegin(request);
+
+        if (updateRequestState(RequestState.TRANSIENT, RequestState.BEGIN))
+            return true;
+
+        terminateRequest(exchange);
+        return false;
+    }
+
+    protected boolean beginToHeaders(HttpExchange exchange)
+    {
+        if (!updateRequestState(RequestState.BEGIN, RequestState.TRANSIENT))
+            return false;
+
+        Request request = exchange.getRequest();
+        if (LOG.isDebugEnabled())
+            LOG.debug("Request headers {}{}{}", request, System.lineSeparator(), request.getHeaders().toString().trim());
+        RequestNotifier notifier = getHttpChannel().getHttpDestination().getRequestNotifier();
+        notifier.notifyHeaders(request);
+
+        if (updateRequestState(RequestState.TRANSIENT, RequestState.HEADERS))
+            return true;
+
+        terminateRequest(exchange);
+        return false;
+    }
+
+    protected boolean headersToCommit(HttpExchange exchange)
+    {
+        if (!updateRequestState(RequestState.HEADERS, RequestState.TRANSIENT))
+            return false;
+
+        Request request = exchange.getRequest();
+        if (LOG.isDebugEnabled())
+            LOG.debug("Request committed {}", request);
+        RequestNotifier notifier = getHttpChannel().getHttpDestination().getRequestNotifier();
+        notifier.notifyCommit(request);
+
+        if (updateRequestState(RequestState.TRANSIENT, RequestState.COMMIT))
+            return true;
+
+        terminateRequest(exchange);
+        return false;
+    }
+
+    protected boolean someToContent(HttpExchange exchange, ByteBuffer content)
+    {
+        RequestState current = requestState.get();
+        switch (current)
+        {
+            case COMMIT:
+            case CONTENT:
+            {
+                if (!updateRequestState(current, RequestState.TRANSIENT))
+                    return false;
+
+                Request request = exchange.getRequest();
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Request content {}{}{}", request, System.lineSeparator(), BufferUtil.toDetailString(content));
+                RequestNotifier notifier = getHttpChannel().getHttpDestination().getRequestNotifier();
+                notifier.notifyContent(request, content);
+
+                if (updateRequestState(RequestState.TRANSIENT, RequestState.CONTENT))
+                    return true;
+
+                terminateRequest(exchange);
+                return false;
+            }
+            default:
+            {
+                return false;
+            }
+        }
+    }
+
+    protected boolean someToSuccess(HttpExchange exchange)
+    {
+        RequestState current = requestState.get();
+        switch (current)
+        {
+            case COMMIT:
+            case CONTENT:
+            {
+                // Mark atomically the request as completed, with respect
+                // to concurrency between request success and request failure.
+                if (!exchange.requestComplete(null))
+                    return false;
+
+                requestState.set(RequestState.QUEUED);
+
+                // Reset to be ready for another request.
+                reset();
+
+                Request request = exchange.getRequest();
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Request success {}", request);
+                HttpDestination destination = getHttpChannel().getHttpDestination();
+                destination.getRequestNotifier().notifySuccess(exchange.getRequest());
+
+                // Mark atomically the request as terminated, with
+                // respect to concurrency between request and response.
+                Result result = exchange.terminateRequest();
+                terminateRequest(exchange, null, result);
+                return true;
+            }
+            default:
+            {
+                return false;
+            }
+        }
+    }
+
+    protected boolean anyToFailure(Throwable failure)
+    {
+        HttpExchange exchange = getHttpExchange();
+        if (exchange == null)
+            return false;
+
+        // Mark atomically the request as completed, with respect
+        // to concurrency between request success and request failure.
+        if (exchange.requestComplete(failure))
+            return abort(exchange, failure);
+
+        return false;
+    }
+
+    private void terminateRequest(HttpExchange exchange)
+    {
+        // In abort(), the state is updated before the failure is recorded
+        // to avoid to overwrite it, so here we may read a null failure.
+        Throwable failure = this.failure;
+        if (failure == null)
+            failure = new HttpRequestException("Concurrent failure", exchange.getRequest());
+        Result result = exchange.terminateRequest();
+        terminateRequest(exchange, failure, result);
+    }
+
+    private void terminateRequest(HttpExchange exchange, Throwable failure, Result result)
+    {
+        Request request = exchange.getRequest();
+
+        if (LOG.isDebugEnabled())
+            LOG.debug("Terminating request {}", request);
+
+        if (result == null)
+        {
+            if (failure != null)
+            {
+                if (exchange.responseComplete(failure))
+                {
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("Response failure from request {} {}", request, exchange);
+                    getHttpChannel().abortResponse(exchange, failure);
+                }
+            }
+        }
+        else
+        {
+            HttpDestination destination = getHttpChannel().getHttpDestination();
+            boolean ordered = destination.getHttpClient().isStrictEventOrdering();
+            if (!ordered)
+                channel.exchangeTerminated(exchange, result);
+            if (LOG.isDebugEnabled())
+                LOG.debug("Request/Response {}: {}", failure == null ? "succeeded" : "failed", result);
+            HttpConversation conversation = exchange.getConversation();
+            destination.getResponseNotifier().notifyComplete(conversation.getResponseListeners(), result);
+            if (ordered)
+                channel.exchangeTerminated(exchange, result);
+        }
+    }
+
+    /**
+     * Implementations should send the HTTP headers over the wire, possibly with some content,
+     * in a single write, and notify the given {@code callback} of the result of this operation.
+     * <p />
+     * If there is more content to send, then {@link #sendContent(HttpExchange, HttpContent, Callback)}
+     * will be invoked.
+     *
+     * @param exchange the exchange to send
+     * @param content the content to send
+     * @param callback the callback to notify
+     */
+    protected abstract void sendHeaders(HttpExchange exchange, HttpContent content, Callback callback);
+
+    /**
+     * Implementations should send the content at the {@link HttpContent} cursor position over the wire.
+     * <p />
+     * The {@link HttpContent} cursor is advanced by {@link HttpSender} at the right time, and if more
+     * content needs to be sent, this method is invoked again; subclasses need only to send the content
+     * at the {@link HttpContent} cursor position.
+     * <p />
+     * This method is invoked one last time when {@link HttpContent#isConsumed()} is true and therefore
+     * there is no actual content to send.
+     * This is done to allow subclasses to write "terminal" bytes (such as the terminal chunk when the
+     * transfer encoding is chunked) if their protocol needs to.
+     *
+     * @param exchange the exchange to send
+     * @param content the content to send
+     * @param callback the callback to notify
+     */
+    protected abstract void sendContent(HttpExchange exchange, HttpContent content, Callback callback);
+
+    protected void reset()
+    {
+        HttpContent content = this.content;
+        this.content = null;
+        content.close();
+        senderState.set(SenderState.COMPLETED);
+    }
+
+    protected void dispose()
+    {
+        HttpContent content = this.content;
+        this.content = null;
+        if (content != null)
+            content.close();
+        senderState.set(SenderState.FAILED);
+    }
+
+    public void proceed(HttpExchange exchange, Throwable failure)
+    {
+        if (!expects100Continue(exchange.getRequest()))
+            return;
+
+        if (failure != null)
+        {
+            anyToFailure(failure);
+            return;
+        }
+
+        while (true)
+        {
+            SenderState current = senderState.get();
+            switch (current)
+            {
+                case EXPECTING:
+                {
+                    // We are still sending the headers, but we already got the 100 Continue.
+                    if (updateSenderState(current, SenderState.PROCEEDING))
+                    {
+                        if (LOG.isDebugEnabled())
+                            LOG.debug("Proceeding while expecting");
+                        return;
+                    }
+                    break;
+                }
+                case EXPECTING_WITH_CONTENT:
+                {
+                    // More deferred content was submitted to onContent(), we already
+                    // got the 100 Continue, but we may be still sending the headers
+                    // (for example, with SSL we may have sent the encrypted data,
+                    // received the 100 Continue but not yet updated the decrypted
+                    // WriteFlusher so sending more content now may result in a
+                    // WritePendingException).
+                    if (updateSenderState(current, SenderState.PROCEEDING_WITH_CONTENT))
+                    {
+                        if (LOG.isDebugEnabled())
+                            LOG.debug("Proceeding while scheduled");
+                        return;
+                    }
+                    break;
+                }
+                case WAITING:
+                {
+                    // We received the 100 Continue, now send the content if any.
+                    if (updateSenderState(current, SenderState.SENDING))
+                    {
+                        if (LOG.isDebugEnabled())
+                            LOG.debug("Proceeding while waiting");
+                        contentCallback.iterate();
+                        return;
+                    }
+                    break;
+                }
+                case FAILED:
+                {
+                    return;
+                }
+                default:
+                {
+                    illegalSenderState(current);
+                    return;
+                }
+            }
+        }
+    }
+
+    public boolean abort(HttpExchange exchange, Throwable failure)
+    {
+        // Update the state to avoid more request processing.
+        boolean terminate;
+        out: while (true)
+        {
+            RequestState current = requestState.get();
+            switch (current)
+            {
+                case FAILURE:
+                {
+                    return false;
+                }
+                default:
+                {
+                    if (updateRequestState(current, RequestState.FAILURE))
+                    {
+                        terminate = current != RequestState.TRANSIENT;
+                        break out;
+                    }
+                    break;
+                }
+            }
+        }
+
+        this.failure = failure;
+
+        dispose();
+
+        Request request = exchange.getRequest();
+        if (LOG.isDebugEnabled())
+            LOG.debug("Request failure {} {} on {}: {}", request, exchange, getHttpChannel(), failure);
+        HttpDestination destination = getHttpChannel().getHttpDestination();
+        destination.getRequestNotifier().notifyFailure(request, failure);
+
+        if (terminate)
+        {
+            // Mark atomically the request as terminated, with
+            // respect to concurrency between request and response.
+            Result result = exchange.terminateRequest();
+            terminateRequest(exchange, failure, result);
+        }
+        else
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("Concurrent failure: request termination skipped, performed by helpers");
+        }
+
+        return true;
+    }
+
+    private boolean updateRequestState(RequestState from, RequestState to)
+    {
+        boolean updated = requestState.compareAndSet(from, to);
+        if (!updated && LOG.isDebugEnabled())
+            LOG.debug("RequestState update failed: {} -> {}: {}", from, to, requestState.get());
+        return updated;
+    }
+
+    private boolean updateSenderState(SenderState from, SenderState to)
+    {
+        boolean updated = senderState.compareAndSet(from, to);
+        if (!updated && LOG.isDebugEnabled())
+            LOG.debug("SenderState update failed: {} -> {}: {}", from, to, senderState.get());
+        return updated;
+    }
+
+    private void illegalSenderState(SenderState current)
+    {
+        anyToFailure(new IllegalStateException("Expected " + current + " found " + senderState.get() + " instead"));
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s@%x(req=%s,snd=%s,failure=%s)",
+                getClass().getSimpleName(),
+                hashCode(),
+                requestState,
+                senderState,
+                failure);
+    }
+
+    /**
+     * The request states {@link HttpSender} goes through when sending a request.
+     */
+    private enum RequestState
+    {
+        /**
+         * One of the state transition methods is being executed.
+         */
+        TRANSIENT,
+        /**
+         * The request is queued, the initial state
+         */
+        QUEUED,
+        /**
+         * The request has been dequeued
+         */
+        BEGIN,
+        /**
+         * The request headers (and possibly some content) is about to be sent
+         */
+        HEADERS,
+        /**
+         * The request headers (and possibly some content) have been sent
+         */
+        COMMIT,
+        /**
+         * The request content is being sent
+         */
+        CONTENT,
+        /**
+         * The request is failed
+         */
+        FAILURE
+    }
+
+    /**
+     * The sender states {@link HttpSender} goes through when sending a request.
+     */
+    private enum SenderState
+    {
+        /**
+         * {@link HttpSender} is not sending request headers nor request content
+         */
+        IDLE,
+        /**
+         * {@link HttpSender} is sending the request header or request content
+         */
+        SENDING,
+        /**
+         * {@link HttpSender} is currently sending the request, and deferred content is available to be sent
+         */
+        SENDING_WITH_CONTENT,
+        /**
+         * {@link HttpSender} is sending the headers but will wait for 100 Continue before sending the content
+         */
+        EXPECTING,
+        /**
+         * {@link HttpSender} is currently sending the headers, will wait for 100 Continue, and deferred content is available to be sent
+         */
+        EXPECTING_WITH_CONTENT,
+        /**
+         * {@link HttpSender} has sent the headers and is waiting for 100 Continue
+         */
+        WAITING,
+        /**
+         * {@link HttpSender} is sending the headers, while 100 Continue has arrived
+         */
+        PROCEEDING,
+        /**
+         * {@link HttpSender} is sending the headers, while 100 Continue has arrived, and deferred content is available to be sent
+         */
+        PROCEEDING_WITH_CONTENT,
+        /**
+         * {@link HttpSender} has finished to send the request
+         */
+        COMPLETED,
+        /**
+         * {@link HttpSender} has failed to send the request
+         */
+        FAILED
+    }
+
+    private class CommitCallback implements Callback
+    {
+        @Override
+        public void succeeded()
+        {
+            try
+            {
+                HttpContent content = HttpSender.this.content;
+                if (content == null)
+                    return;
+                content.succeeded();
+                process();
+            }
+            catch (Throwable x)
+            {
+                anyToFailure(x);
+            }
+        }
+
+        @Override
+        public void failed(Throwable failure)
+        {
+            HttpContent content = HttpSender.this.content;
+            if (content == null)
+                return;
+            content.failed(failure);
+            anyToFailure(failure);
+        }
+
+        private void process() throws Exception
+        {
+            HttpExchange exchange = getHttpExchange();
+            if (exchange == null)
+                return;
+
+            if (!headersToCommit(exchange))
+                return;
+
+             HttpContent content = HttpSender.this.content;
+            if (content == null)
+                return;
+
+            if (!content.hasContent())
+            {
+                // No content to send, we are done.
+                someToSuccess(exchange);
+            }
+            else
+            {
+                // Was any content sent while committing ?
+                ByteBuffer contentBuffer = content.getContent();
+                if (contentBuffer != null)
+                {
+                    if (!someToContent(exchange, contentBuffer))
+                        return;
+                }
+
+                while (true)
+                {
+                    SenderState current = senderState.get();
+                    switch (current)
+                    {
+                        case SENDING:
+                        {
+                            contentCallback.iterate();
+                            return;
+                        }
+                        case SENDING_WITH_CONTENT:
+                        {
+                            // We have deferred content to send.
+                            updateSenderState(current, SenderState.SENDING);
+                            break;
+                        }
+                        case EXPECTING:
+                        {
+                            // We sent the headers, wait for the 100 Continue response.
+                            if (updateSenderState(current, SenderState.WAITING))
+                                return;
+                            break;
+                        }
+                        case EXPECTING_WITH_CONTENT:
+                        {
+                            // We sent the headers, we have deferred content to send,
+                            // wait for the 100 Continue response.
+                            if (updateSenderState(current, SenderState.WAITING))
+                                return;
+                            break;
+                        }
+                        case PROCEEDING:
+                        {
+                            // We sent the headers, we have the 100 Continue response,
+                            // we have no content to send.
+                            if (updateSenderState(current, SenderState.IDLE))
+                                return;
+                            break;
+                        }
+                        case PROCEEDING_WITH_CONTENT:
+                        {
+                            // We sent the headers, we have the 100 Continue response,
+                            // we have deferred content to send.
+                            updateSenderState(current, SenderState.SENDING);
+                            break;
+                        }
+                        case FAILED:
+                        {
+                            return;
+                        }
+                        default:
+                        {
+                            illegalSenderState(current);
+                            return;
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    private class ContentCallback extends IteratingCallback
+    {
+        @Override
+        protected Action process() throws Exception
+        {
+            HttpExchange exchange = getHttpExchange();
+            if (exchange == null)
+                return Action.IDLE;
+
+            HttpContent content = HttpSender.this.content;
+            if (content == null)
+                return Action.IDLE;
+
+            while (true)
+            {
+                boolean advanced = content.advance();
+                boolean consumed = content.isConsumed();
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Content {} consumed {} for {}", advanced, consumed, exchange.getRequest());
+
+                if (advanced)
+                {
+                    sendContent(exchange, content, this);
+                    return Action.SCHEDULED;
+                }
+
+                if (consumed)
+                {
+                    sendContent(exchange, content, lastCallback);
+                    return Action.IDLE;
+                }
+
+                SenderState current = senderState.get();
+                switch (current)
+                {
+                    case SENDING:
+                    {
+                        if (updateSenderState(current, SenderState.IDLE))
+                        {
+                            if (LOG.isDebugEnabled())
+                                LOG.debug("Content is deferred for {}", exchange.getRequest());
+                            return Action.IDLE;
+                        }
+                        break;
+                    }
+                    case SENDING_WITH_CONTENT:
+                    {
+                        updateSenderState(current, SenderState.SENDING);
+                        break;
+                    }
+                    default:
+                    {
+                        illegalSenderState(current);
+                        return Action.IDLE;
+                    }
+                }
+            }
+        }
+
+        @Override
+        public void succeeded()
+        {
+            HttpExchange exchange = getHttpExchange();
+            if (exchange == null)
+                return;
+            HttpContent content = HttpSender.this.content;
+            if (content == null)
+                return;
+            content.succeeded();
+            ByteBuffer buffer = content.getContent();
+            someToContent(exchange, buffer);
+            super.succeeded();
+        }
+
+        @Override
+        public void onCompleteFailure(Throwable failure)
+        {
+            HttpContent content = HttpSender.this.content;
+            if (content == null)
+                return;
+            content.failed(failure);
+            anyToFailure(failure);
+        }
+
+        @Override
+        protected void onCompleteSuccess()
+        {
+            // Nothing to do, since we always return false from process().
+            // Termination is obtained via LastContentCallback.
+        }
+    }
+
+    private class LastContentCallback implements Callback
+    {
+        @Override
+        public void succeeded()
+        {
+            HttpExchange exchange = getHttpExchange();
+            if (exchange == null)
+                return;
+            HttpContent content = HttpSender.this.content;
+            if (content == null)
+                return;
+            content.succeeded();
+            someToSuccess(exchange);
+        }
+
+        @Override
+        public void failed(Throwable failure)
+        {
+            HttpContent content = HttpSender.this.content;
+            if (content == null)
+                return;
+            content.failed(failure);
+            anyToFailure(failure);
+        }
+    }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/LeakTrackingConnectionPool.java b/jetty-client/src/main/java/org/eclipse/jetty/client/LeakTrackingConnectionPool.java
new file mode 100644
index 0000000..da1040e
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/LeakTrackingConnectionPool.java
@@ -0,0 +1,92 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import org.eclipse.jetty.client.api.Connection;
+import org.eclipse.jetty.client.api.Destination;
+import org.eclipse.jetty.util.LeakDetector;
+import org.eclipse.jetty.util.Promise;
+
+public class LeakTrackingConnectionPool extends ConnectionPool
+{
+    private final LeakDetector<Connection> leakDetector = new LeakDetector<Connection>()
+    {
+        @Override
+        protected void leaked(LeakInfo leakInfo)
+        {
+            LeakTrackingConnectionPool.this.leaked(leakInfo);
+        }
+    };
+
+    public LeakTrackingConnectionPool(Destination destination, int maxConnections, Promise<Connection> connectionPromise)
+    {
+        super(destination, maxConnections, connectionPromise);
+        start();
+    }
+
+    private void start()
+    {
+        try
+        {
+            leakDetector.start();
+        }
+        catch (Exception x)
+        {
+            throw new RuntimeException(x);
+        }
+    }
+
+    @Override
+    public void close()
+    {
+        stop();
+        super.close();
+    }
+
+    private void stop()
+    {
+        try
+        {
+            leakDetector.stop();
+        }
+        catch (Exception x)
+        {
+            throw new RuntimeException(x);
+        }
+    }
+
+    @Override
+    protected void acquired(Connection connection)
+    {
+        if (!leakDetector.acquired(connection))
+            LOG.info("Connection {}@{} not tracked", connection, leakDetector.id(connection));
+    }
+
+    @Override
+    protected void released(Connection connection)
+    {
+        if (!leakDetector.released(connection))
+            LOG.info("Connection {}@{} released but not acquired", connection, leakDetector.id(connection));
+    }
+
+    protected void leaked(LeakDetector.LeakInfo leakInfo)
+    {
+        LOG.info("Connection " + leakInfo.getResourceDescription() + " leaked at:", leakInfo.getStackFrames());
+    }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/MultiplexHttpDestination.java b/jetty-client/src/main/java/org/eclipse/jetty/client/MultiplexHttpDestination.java
new file mode 100644
index 0000000..df3f860
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/MultiplexHttpDestination.java
@@ -0,0 +1,166 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.jetty.client.api.Connection;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.util.Promise;
+
+public abstract class MultiplexHttpDestination<C extends Connection> extends HttpDestination implements Promise<Connection>
+{
+    private final AtomicReference<ConnectState> connect = new AtomicReference<>(ConnectState.DISCONNECTED);
+    private C connection;
+
+    protected MultiplexHttpDestination(HttpClient client, Origin origin)
+    {
+        super(client, origin);
+    }
+
+    @Override
+    protected void send()
+    {
+        while (true)
+        {
+            ConnectState current = connect.get();
+            switch (current)
+            {
+                case DISCONNECTED:
+                {
+                    if (!connect.compareAndSet(current, ConnectState.CONNECTING))
+                        break;
+                    newConnection(this);
+                    return;
+                }
+                case CONNECTING:
+                {
+                    // Waiting to connect, just return
+                    return;
+                }
+                case CONNECTED:
+                {
+                    if (process(connection, false))
+                        break;
+                    return;
+                }
+                default:
+                {
+                    abort(new IllegalStateException("Invalid connection state " + current));
+                    return;
+                }
+            }
+        }
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public void succeeded(Connection result)
+    {
+        C connection = this.connection = (C)result;
+        if (connect.compareAndSet(ConnectState.CONNECTING, ConnectState.CONNECTED))
+        {
+            process(connection, true);
+        }
+        else
+        {
+            connection.close();
+            failed(new IllegalStateException());
+        }
+    }
+
+    @Override
+    public void failed(Throwable x)
+    {
+        connect.set(ConnectState.DISCONNECTED);
+    }
+
+    protected boolean process(final C connection, boolean dispatch)
+    {
+        HttpClient client = getHttpClient();
+        final HttpExchange exchange = getHttpExchanges().poll();
+        if (LOG.isDebugEnabled())
+            LOG.debug("Processing {} on {}", exchange, connection);
+        if (exchange == null)
+            return false;
+
+        final Request request = exchange.getRequest();
+        Throwable cause = request.getAbortCause();
+        if (cause != null)
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("Aborted before processing {}: {}", exchange, cause);
+            // It may happen that the request is aborted before the exchange
+            // is created. Aborting the exchange a second time will result in
+            // a no-operation, so we just abort here to cover that edge case.
+            exchange.abort(cause);
+        }
+        else
+        {
+            if (dispatch)
+            {
+                client.getExecutor().execute(new Runnable()
+                {
+                    @Override
+                    public void run()
+                    {
+                        send(connection, exchange);
+                    }
+                });
+            }
+            else
+            {
+                send(connection, exchange);
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public void close()
+    {
+        super.close();
+        C connection = this.connection;
+        if (connection != null)
+            connection.close();
+    }
+
+    @Override
+    public void close(Connection connection)
+    {
+        super.close(connection);
+        while (true)
+        {
+            ConnectState current = connect.get();
+            if (connect.compareAndSet(current, ConnectState.DISCONNECTED))
+            {
+                if (getHttpClient().isRemoveIdleDestinations())
+                    getHttpClient().removeDestination(this);
+                break;
+            }
+        }
+    }
+
+    protected abstract void send(C connection, HttpExchange exchange);
+
+    private enum ConnectState
+    {
+        DISCONNECTED, CONNECTING, CONNECTED
+    }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/Origin.java b/jetty-client/src/main/java/org/eclipse/jetty/client/Origin.java
new file mode 100644
index 0000000..6d2e7a5
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/Origin.java
@@ -0,0 +1,124 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.util.Objects;
+
+import org.eclipse.jetty.util.URIUtil;
+
+public class Origin
+{
+    private final String scheme;
+    private final Address address;
+
+    public Origin(String scheme, String host, int port)
+    {
+        this(scheme, new Address(host, port));
+    }
+
+    public Origin(String scheme, Address address)
+    {
+        this.scheme = Objects.requireNonNull(scheme);
+        this.address = address;
+    }
+
+    public String getScheme()
+    {
+        return scheme;
+    }
+
+    public Address getAddress()
+    {
+        return address;
+    }
+
+    public String asString()
+    {
+        StringBuilder result = new StringBuilder();
+        URIUtil.appendSchemeHostPort(result, scheme, address.host, address.port);
+        return result.toString();
+    }
+
+    @Override
+    public boolean equals(Object obj)
+    {
+        if (this == obj) return true;
+        if (obj == null || getClass() != obj.getClass()) return false;
+        Origin that = (Origin)obj;
+        return scheme.equals(that.scheme) && address.equals(that.address);
+    }
+
+    @Override
+    public int hashCode()
+    {
+        int result = scheme.hashCode();
+        result = 31 * result + address.hashCode();
+        return result;
+    }
+
+    public static class Address
+    {
+        private final String host;
+        private final int port;
+
+        public Address(String host, int port)
+        {
+            this.host = Objects.requireNonNull(host);
+            this.port = port;
+        }
+
+        public String getHost()
+        {
+            return host;
+        }
+
+        public int getPort()
+        {
+            return port;
+        }
+
+        @Override
+        public boolean equals(Object obj)
+        {
+            if (this == obj) return true;
+            if (obj == null || getClass() != obj.getClass()) return false;
+            Address that = (Address)obj;
+            return host.equals(that.host) && port == that.port;
+        }
+
+        @Override
+        public int hashCode()
+        {
+            int result = host.hashCode();
+            result = 31 * result + port;
+            return result;
+        }
+
+        public String asString()
+        {
+            return String.format("%s:%d", host, port);
+        }
+
+        @Override
+        public String toString()
+        {
+            return asString();
+        }
+    }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/PoolingHttpDestination.java b/jetty-client/src/main/java/org/eclipse/jetty/client/PoolingHttpDestination.java
new file mode 100644
index 0000000..6bbca21
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/PoolingHttpDestination.java
@@ -0,0 +1,235 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+import org.eclipse.jetty.client.api.Connection;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.util.Promise;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.thread.Sweeper;
+
+public abstract class PoolingHttpDestination<C extends Connection> extends HttpDestination implements Promise<Connection>
+{
+    private final ConnectionPool connectionPool;
+
+    public PoolingHttpDestination(HttpClient client, Origin origin)
+    {
+        super(client, origin);
+        this.connectionPool = newConnectionPool(client);
+        Sweeper sweeper = client.getBean(Sweeper.class);
+        if (sweeper != null)
+            sweeper.offer(connectionPool);
+    }
+
+    protected ConnectionPool newConnectionPool(HttpClient client)
+    {
+        return new ConnectionPool(this, client.getMaxConnectionsPerDestination(), this);
+    }
+
+    public ConnectionPool getConnectionPool()
+    {
+        return connectionPool;
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public void succeeded(Connection connection)
+    {
+        send(true);
+    }
+
+    @Override
+    public void failed(final Throwable x)
+    {
+        getHttpClient().getExecutor().execute(new Runnable()
+        {
+            @Override
+            public void run()
+            {
+                abort(x);
+            }
+        });
+    }
+
+    @Override
+    protected void send()
+    {
+        send(false);
+    }
+
+    private void send(boolean dispatch)
+    {
+        if (getHttpExchanges().isEmpty())
+            return;
+        C connection = acquire();
+        if (connection != null)
+            process(connection, dispatch);
+    }
+
+    @SuppressWarnings("unchecked")
+    public C acquire()
+    {
+        return (C)connectionPool.acquire();
+    }
+
+    /**
+     * <p>Processes a new connection making it idle or active depending on whether requests are waiting to be sent.</p>
+     * <p>A new connection is created when a request needs to be executed; it is possible that the request that
+     * triggered the request creation is executed by another connection that was just released, so the new connection
+     * may become idle.</p>
+     * <p>If a request is waiting to be executed, it will be dequeued and executed by the new connection.</p>
+     *
+     * @param connection the new connection
+     * @param dispatch whether to dispatch the processing to another thread
+     */
+    public void process(final C connection, boolean dispatch)
+    {
+        HttpClient client = getHttpClient();
+        final HttpExchange exchange = getHttpExchanges().poll();
+        if (LOG.isDebugEnabled())
+            LOG.debug("Processing exchange {} on {} of {}", exchange, connection, this);
+        if (exchange == null)
+        {
+            if (!connectionPool.release(connection))
+                connection.close();
+
+            if (!client.isRunning())
+            {
+                if (LOG.isDebugEnabled())
+                    LOG.debug("{} is stopping", client);
+                connection.close();
+            }
+        }
+        else
+        {
+            final Request request = exchange.getRequest();
+            Throwable cause = request.getAbortCause();
+            if (cause != null)
+            {
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Aborted before processing {}: {}", exchange, cause);
+                // It may happen that the request is aborted before the exchange
+                // is created. Aborting the exchange a second time will result in
+                // a no-operation, so we just abort here to cover that edge case.
+                exchange.abort(cause);
+            }
+            else
+            {
+                if (dispatch)
+                {
+                    client.getExecutor().execute(new Runnable()
+                    {
+                        @Override
+                        public void run()
+                        {
+                            send(connection, exchange);
+                        }
+                    });
+                }
+                else
+                {
+                    send(connection, exchange);
+                }
+            }
+        }
+    }
+
+    protected abstract void send(C connection, HttpExchange exchange);
+
+    @Override
+    public void release(Connection c)
+    {
+        @SuppressWarnings("unchecked")
+        C connection = (C)c;
+        if (LOG.isDebugEnabled())
+            LOG.debug("{} released", connection);
+        HttpClient client = getHttpClient();
+        if (client.isRunning())
+        {
+            if (connectionPool.isActive(connection))
+            {
+                process(connection, false);
+            }
+            else
+            {
+                if (LOG.isDebugEnabled())
+                    LOG.debug("{} explicit", connection);
+            }
+        }
+        else
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("{} is stopped", client);
+            connection.close();
+        }
+    }
+
+    @Override
+    public void close(Connection oldConnection)
+    {
+        super.close(oldConnection);
+
+        connectionPool.remove(oldConnection);
+
+        if (getHttpExchanges().isEmpty())
+        {
+            if (getHttpClient().isRemoveIdleDestinations() && connectionPool.isEmpty())
+            {
+                // There is a race condition between this thread removing the destination
+                // and another thread queueing a request to this same destination.
+                // If this destination is removed, but the request queued, a new connection
+                // will be opened, the exchange will be executed and eventually the connection
+                // will idle timeout and be closed. Meanwhile a new destination will be created
+                // in HttpClient and will be used for other requests.
+                getHttpClient().removeDestination(this);
+            }
+        }
+        else
+        {
+            // We need to execute queued requests even if this connection failed.
+            // We may create a connection that is not needed, but it will eventually
+            // idle timeout, so no worries.
+            C newConnection = acquire();
+            if (newConnection != null)
+                process(newConnection, false);
+        }
+    }
+
+    public void close()
+    {
+        super.close();
+        connectionPool.close();
+    }
+
+    @Override
+    public void dump(Appendable out, String indent) throws IOException
+    {
+        super.dump(out, indent);
+        ContainerLifeCycle.dump(out, indent, Arrays.asList(connectionPool));
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s,pool=%s", super.toString(), connectionPool);
+    }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/ProtocolHandler.java b/jetty-client/src/main/java/org/eclipse/jetty/client/ProtocolHandler.java
new file mode 100644
index 0000000..a74e657
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/ProtocolHandler.java
@@ -0,0 +1,29 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.api.Response;
+
+public interface ProtocolHandler
+{
+    public boolean accept(Request request, Response response);
+
+    public Response.Listener getResponseListener();
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/ProxyAuthenticationProtocolHandler.java b/jetty-client/src/main/java/org/eclipse/jetty/client/ProxyAuthenticationProtocolHandler.java
new file mode 100644
index 0000000..64491b2
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/ProxyAuthenticationProtocolHandler.java
@@ -0,0 +1,65 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.net.URI;
+
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpStatus;
+
+public class ProxyAuthenticationProtocolHandler extends AuthenticationProtocolHandler
+{
+    public ProxyAuthenticationProtocolHandler(HttpClient client)
+    {
+        this(client, DEFAULT_MAX_CONTENT_LENGTH);
+    }
+
+    public ProxyAuthenticationProtocolHandler(HttpClient client, int maxContentLength)
+    {
+        super(client, maxContentLength);
+    }
+
+    @Override
+    public boolean accept(Request request, Response response)
+    {
+        return response.getStatus() == HttpStatus.PROXY_AUTHENTICATION_REQUIRED_407;
+    }
+
+    @Override
+    protected HttpHeader getAuthenticateHeader()
+    {
+        return HttpHeader.PROXY_AUTHENTICATE;
+    }
+
+    @Override
+    protected HttpHeader getAuthorizationHeader()
+    {
+        return HttpHeader.PROXY_AUTHORIZATION;
+    }
+
+    @Override
+    protected URI getAuthenticationURI(Request request)
+    {
+        HttpDestination destination = getHttpClient().destinationFor(request.getScheme(), request.getHost(), request.getPort());
+        ProxyConfiguration.Proxy proxy = destination.getProxy();
+        return proxy != null ? proxy.getURI() : request.getURI();
+    }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/ProxyConfiguration.java b/jetty-client/src/main/java/org/eclipse/jetty/client/ProxyConfiguration.java
new file mode 100644
index 0000000..d1b6267
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/ProxyConfiguration.java
@@ -0,0 +1,170 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.eclipse.jetty.io.ClientConnectionFactory;
+
+/**
+ * The configuration of the forward proxy to use with {@link org.eclipse.jetty.client.HttpClient}.
+ * <p />
+ * Applications add subclasses of {@link Proxy} to this configuration via:
+ * <pre>
+ * ProxyConfiguration proxyConfig = httpClient.getProxyConfiguration();
+ * proxyConfig.getProxies().add(new HttpProxy(proxyHost, 8080));
+ * </pre>
+ *
+ * @see HttpClient#getProxyConfiguration()
+ */
+public class ProxyConfiguration
+{
+    private final List<Proxy> proxies = new ArrayList<>();
+
+    public List<Proxy> getProxies()
+    {
+        return proxies;
+    }
+
+    public Proxy match(Origin origin)
+    {
+        for (Proxy proxy : getProxies())
+        {
+            if (proxy.matches(origin))
+                return proxy;
+        }
+        return null;
+    }
+
+    public static abstract class Proxy
+    {
+        private final Set<String> included = new HashSet<>();
+        private final Set<String> excluded = new HashSet<>();
+        private final Origin.Address address;
+        private final boolean secure;
+
+        protected Proxy(Origin.Address address, boolean secure)
+        {
+            this.address = address;
+            this.secure = secure;
+        }
+
+        /**
+         * @return the address of this proxy
+         */
+        public Origin.Address getAddress()
+        {
+            return address;
+        }
+
+        /**
+         * @return whether the connection to the proxy must be secured via TLS
+         */
+        public boolean isSecure()
+        {
+            return secure;
+        }
+
+        /**
+         * @return the list of origins that must be proxied
+         * @see #matches(Origin)
+         * @see #getExcludedAddresses()
+         */
+        public Set<String> getIncludedAddresses()
+        {
+            return included;
+        }
+
+        /**
+         * @return the list of origins that must not be proxied.
+         * @see #matches(Origin)
+         * @see #getIncludedAddresses()
+         */
+        public Set<String> getExcludedAddresses()
+        {
+            return excluded;
+        }
+
+        /**
+         * @return an URI representing this proxy, or null if no URI can represent this proxy
+         */
+        public URI getURI()
+        {
+            return null;
+        }
+
+        /**
+         * Matches the given {@code origin} with the included and excluded addresses,
+         * returning true if the given {@code origin} is to be proxied.
+         *
+         * @param origin the origin to test for proxying
+         * @return true if the origin must be proxied, false otherwise
+         */
+        public boolean matches(Origin origin)
+        {
+            boolean result = included.isEmpty();
+            Origin.Address address = origin.getAddress();
+            for (String included : this.included)
+            {
+                if (matches(address, included))
+                {
+                    result = true;
+                    break;
+                }
+            }
+            for (String excluded : this.excluded)
+            {
+                if (matches(address, excluded))
+                {
+                    result = false;
+                    break;
+                }
+            }
+            return result;
+        }
+
+        private boolean matches(Origin.Address address, String pattern)
+        {
+            // TODO: add support for CIDR notation like 192.168.0.0/24, see DoSFilter
+            int colon = pattern.indexOf(':');
+            if (colon < 0)
+                return pattern.equals(address.getHost());
+            String host = pattern.substring(0, colon);
+            String port = pattern.substring(colon + 1);
+            return host.equals(address.getHost()) && port.equals(String.valueOf(address.getPort()));
+        }
+
+        /**
+         * @param connectionFactory the nested {@link ClientConnectionFactory}
+         * @return a new {@link ClientConnectionFactory} for this {@link Proxy}
+         */
+        public abstract ClientConnectionFactory newClientConnectionFactory(ClientConnectionFactory connectionFactory);
+
+        @Override
+        public String toString()
+        {
+            return address.toString();
+        }
+    }
+
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/RedirectListener.java b/jetty-client/src/main/java/org/eclipse/jetty/client/RedirectListener.java
deleted file mode 100644
index 5833ac2..0000000
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/RedirectListener.java
+++ /dev/null
@@ -1,212 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client;
-
-import java.io.IOException;
-
-import org.eclipse.jetty.http.HttpHeaders;
-import org.eclipse.jetty.http.HttpSchemes;
-import org.eclipse.jetty.http.HttpStatus;
-import org.eclipse.jetty.io.Buffer;
-
-/**
- * RedirectListener
- *
- * Detect and handle the redirect responses
- */
-public class RedirectListener extends HttpEventListenerWrapper
-{
-    private final HttpExchange _exchange;
-    private HttpDestination _destination;
-    private String _location;
-    private int _attempts;
-    private boolean _requestComplete;
-    private boolean _responseComplete;
-    private boolean _redirected;
-
-    public RedirectListener(HttpDestination destination, HttpExchange ex)
-    {
-        // Start of sending events through to the wrapped listener
-        // Next decision point is the onResponseStatus
-        super(ex.getEventListener(),true);
-
-        _destination = destination;
-        _exchange = ex;
-    }
-
-    @Override
-    public void onResponseStatus( Buffer version, int status, Buffer reason )
-        throws IOException
-    {
-        _redirected = ((status == HttpStatus.MOVED_PERMANENTLY_301 ||
-                        status == HttpStatus.MOVED_TEMPORARILY_302) &&
-                       _attempts < _destination.getHttpClient().maxRedirects());
-
-        if (_redirected)
-        {
-            setDelegatingRequests(false);
-            setDelegatingResponses(false);
-        }
-
-        super.onResponseStatus(version,status,reason);
-    }
-
-
-    @Override
-    public void onResponseHeader( Buffer name, Buffer value )
-        throws IOException
-    {
-        if (_redirected)
-        {
-            int header = HttpHeaders.CACHE.getOrdinal(name);
-            switch (header)
-            {
-                case HttpHeaders.LOCATION_ORDINAL:
-                    _location = value.toString();
-                    break;
-            }
-        }
-        super.onResponseHeader(name,value);
-    }
-
-    @Override
-    public void onRequestComplete() throws IOException
-    {
-        _requestComplete = true;
-
-        if (checkExchangeComplete())
-        {
-            super.onRequestComplete();
-        }
-    }
-
-    @Override
-    public void onResponseComplete() throws IOException
-    {
-        _responseComplete = true;
-
-        if (checkExchangeComplete())
-        {
-            super.onResponseComplete();
-        }
-    }
-
-    public boolean checkExchangeComplete()
-        throws IOException
-    {
-        if (_redirected && _requestComplete && _responseComplete)
-        {
-            if (_location != null)
-            {
-                if (_location.indexOf("://")>0)
-                {
-                    _exchange.setURL(_location);
-                }
-                else
-                {
-                    _exchange.setRequestURI(_location);
-                }
-
-                // destination may have changed
-                boolean isHttps = HttpSchemes.HTTPS.equals(String.valueOf(_exchange.getScheme()));
-                HttpDestination destination=_destination.getHttpClient().getDestination(_exchange.getAddress(),isHttps);
-
-                if (_destination==destination)
-                {
-                    _destination.resend(_exchange);
-                }
-                else
-                {
-                    // unwrap to find ultimate listener.
-                    HttpEventListener listener=this;
-                    while(listener instanceof HttpEventListenerWrapper)
-                    {
-                        listener=((HttpEventListenerWrapper)listener).getEventListener();
-                    }
-                    
-                    //reset the listener
-                    _exchange.getEventListener().onRetry();
-                    _exchange.reset();
-                    _exchange.setEventListener(listener);
-
-                    // Set the new Host header
-                    Address address = _exchange.getAddress();
-                    int port = address.getPort();
-                    StringBuilder hostHeader = new StringBuilder( 64 );
-                    hostHeader.append( address.getHost() );
-                    if( !( ( port == 80 && !isHttps ) || ( port == 443 && isHttps ) ) ) 
-                    {
-                        hostHeader.append( ':' );
-                        hostHeader.append( port );
-                    }
-                    
-                    _exchange.setRequestHeader( HttpHeaders.HOST, hostHeader.toString() );
-
-                    destination.send(_exchange);
-                }
-
-                return false;
-            }
-            else
-            {
-                setDelegationResult(false);
-            }
-        }
-
-        return true;
-    }
-
-    public void onRetry()
-    {
-        _redirected=false;
-        _attempts++;
-
-        setDelegatingRequests(true);
-        setDelegatingResponses(true);
-
-        _requestComplete=false;
-        _responseComplete=false;
-
-        super.onRetry();
-    }
-
-    /**
-     * Delegate failed connection
-     */
-    @Override
-    public void onConnectionFailed( Throwable ex )
-    {
-        setDelegatingRequests(true);
-        setDelegatingResponses(true);
-
-        super.onConnectionFailed( ex );
-    }
-
-    /**
-     * Delegate onException
-     */
-    @Override
-    public void onException( Throwable ex )
-    {
-        setDelegatingRequests(true);
-        setDelegatingResponses(true);
-
-        super.onException( ex );
-    }
-}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/RedirectProtocolHandler.java b/jetty-client/src/main/java/org/eclipse/jetty/client/RedirectProtocolHandler.java
new file mode 100644
index 0000000..8ed88bf
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/RedirectProtocolHandler.java
@@ -0,0 +1,56 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.client.api.Result;
+
+public class RedirectProtocolHandler extends Response.Listener.Adapter implements ProtocolHandler
+{
+    private final HttpRedirector redirector;
+
+    public RedirectProtocolHandler(HttpClient client)
+    {
+        redirector = new HttpRedirector(client);
+    }
+
+    @Override
+    public boolean accept(Request request, Response response)
+    {
+        return redirector.isRedirect(response) && request.isFollowRedirects();
+    }
+
+    @Override
+    public Response.Listener getResponseListener()
+    {
+        return this;
+    }
+
+    @Override
+    public void onComplete(Result result)
+    {
+        Request request = result.getRequest();
+        Response response = result.getResponse();
+        if (result.isSucceeded())
+            redirector.redirect(request, response, null);
+        else
+            redirector.fail(request, response, result.getFailure());
+    }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/RequestNotifier.java b/jetty-client/src/main/java/org/eclipse/jetty/client/RequestNotifier.java
new file mode 100644
index 0000000..e468e26
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/RequestNotifier.java
@@ -0,0 +1,260 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.nio.ByteBuffer;
+import java.util.List;
+
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public class RequestNotifier
+{
+    private static final Logger LOG = Log.getLogger(ResponseNotifier.class);
+
+    private final HttpClient client;
+
+    public RequestNotifier(HttpClient client)
+    {
+        this.client = client;
+    }
+
+    public void notifyQueued(Request request)
+    {
+        // Optimized to avoid allocations of iterator instances
+        List<Request.RequestListener> requestListeners = request.getRequestListeners(null);
+        for (int i = 0; i < requestListeners.size(); ++i)
+        {
+            Request.RequestListener listener = requestListeners.get(i);
+            if (listener instanceof Request.QueuedListener)
+                notifyQueued((Request.QueuedListener)listener, request);
+        }
+        List<Request.Listener> listeners = client.getRequestListeners();
+        for (int i = 0; i < listeners.size(); ++i)
+        {
+            Request.Listener listener = listeners.get(i);
+            notifyQueued(listener, request);
+        }
+    }
+
+    private void notifyQueued(Request.QueuedListener listener, Request request)
+    {
+        try
+        {
+            listener.onQueued(request);
+        }
+        catch (Throwable x)
+        {
+            LOG.info("Exception while notifying listener " + listener, x);
+        }
+    }
+
+    public void notifyBegin(Request request)
+    {
+        // Optimized to avoid allocations of iterator instances
+        List<Request.RequestListener> requestListeners = request.getRequestListeners(null);
+        for (int i = 0; i < requestListeners.size(); ++i)
+        {
+            Request.RequestListener listener = requestListeners.get(i);
+            if (listener instanceof Request.BeginListener)
+                notifyBegin((Request.BeginListener)listener, request);
+        }
+        List<Request.Listener> listeners = client.getRequestListeners();
+        for (int i = 0; i < listeners.size(); ++i)
+        {
+            Request.Listener listener = listeners.get(i);
+            notifyBegin(listener, request);
+        }
+    }
+
+    private void notifyBegin(Request.BeginListener listener, Request request)
+    {
+        try
+        {
+            listener.onBegin(request);
+        }
+        catch (Throwable x)
+        {
+            LOG.info("Exception while notifying listener " + listener, x);
+        }
+    }
+
+    public void notifyHeaders(Request request)
+    {
+        // Optimized to avoid allocations of iterator instances
+        List<Request.RequestListener> requestListeners = request.getRequestListeners(null);
+        for (int i = 0; i < requestListeners.size(); ++i)
+        {
+            Request.RequestListener listener = requestListeners.get(i);
+            if (listener instanceof Request.HeadersListener)
+                notifyHeaders((Request.HeadersListener)listener, request);
+        }
+        List<Request.Listener> listeners = client.getRequestListeners();
+        for (int i = 0; i < listeners.size(); ++i)
+        {
+            Request.Listener listener = listeners.get(i);
+            notifyHeaders(listener, request);
+        }
+    }
+
+    private void notifyHeaders(Request.HeadersListener listener, Request request)
+    {
+        try
+        {
+            listener.onHeaders(request);
+        }
+        catch (Throwable x)
+        {
+            LOG.info("Exception while notifying listener " + listener, x);
+        }
+    }
+
+    public void notifyCommit(Request request)
+    {
+        // Optimized to avoid allocations of iterator instances
+        List<Request.RequestListener> requestListeners = request.getRequestListeners(null);
+        for (int i = 0; i < requestListeners.size(); ++i)
+        {
+            Request.RequestListener listener = requestListeners.get(i);
+            if (listener instanceof Request.CommitListener)
+                notifyCommit((Request.CommitListener)listener, request);
+        }
+        List<Request.Listener> listeners = client.getRequestListeners();
+        for (int i = 0; i < listeners.size(); ++i)
+        {
+            Request.Listener listener = listeners.get(i);
+            notifyCommit(listener, request);
+        }
+    }
+
+    private void notifyCommit(Request.CommitListener listener, Request request)
+    {
+        try
+        {
+            listener.onCommit(request);
+        }
+        catch (Throwable x)
+        {
+            LOG.info("Exception while notifying listener " + listener, x);
+        }
+    }
+
+    public void notifyContent(Request request, ByteBuffer content)
+    {
+        // Slice the buffer to avoid that listeners peek into data they should not look at.
+        content = content.slice();
+        if (!content.hasRemaining())
+            return;
+        // Optimized to avoid allocations of iterator instances.
+        List<Request.RequestListener> requestListeners = request.getRequestListeners(null);
+        for (int i = 0; i < requestListeners.size(); ++i)
+        {
+            Request.RequestListener listener = requestListeners.get(i);
+            if (listener instanceof Request.ContentListener)
+            {
+                // The buffer was sliced, so we always clear it (position=0, limit=capacity)
+                // before passing it to the listener that may consume it.
+                content.clear();
+                notifyContent((Request.ContentListener)listener, request, content);
+            }
+        }
+        List<Request.Listener> listeners = client.getRequestListeners();
+        for (int i = 0; i < listeners.size(); ++i)
+        {
+            Request.Listener listener = listeners.get(i);
+            // The buffer was sliced, so we always clear it (position=0, limit=capacity)
+            // before passing it to the listener that may consume it.
+            content.clear();
+            notifyContent(listener, request, content);
+        }
+    }
+
+    private void notifyContent(Request.ContentListener listener, Request request, ByteBuffer content)
+    {
+        try
+        {
+            listener.onContent(request, content);
+        }
+        catch (Throwable x)
+        {
+            LOG.info("Exception while notifying listener " + listener, x);
+        }
+    }
+
+    public void notifySuccess(Request request)
+    {
+        // Optimized to avoid allocations of iterator instances
+        List<Request.RequestListener> requestListeners = request.getRequestListeners(null);
+        for (int i = 0; i < requestListeners.size(); ++i)
+        {
+            Request.RequestListener listener = requestListeners.get(i);
+            if (listener instanceof Request.SuccessListener)
+                notifySuccess((Request.SuccessListener)listener, request);
+        }
+        List<Request.Listener> listeners = client.getRequestListeners();
+        for (int i = 0; i < listeners.size(); ++i)
+        {
+            Request.Listener listener = listeners.get(i);
+            notifySuccess(listener, request);
+        }
+    }
+
+    private void notifySuccess(Request.SuccessListener listener, Request request)
+    {
+        try
+        {
+            listener.onSuccess(request);
+        }
+        catch (Throwable x)
+        {
+            LOG.info("Exception while notifying listener " + listener, x);
+        }
+    }
+
+    public void notifyFailure(Request request, Throwable failure)
+    {
+        // Optimized to avoid allocations of iterator instances
+        List<Request.RequestListener> requestListeners = request.getRequestListeners(null);
+        for (int i = 0; i < requestListeners.size(); ++i)
+        {
+            Request.RequestListener listener = requestListeners.get(i);
+            if (listener instanceof Request.FailureListener)
+                notifyFailure((Request.FailureListener)listener, request, failure);
+        }
+        List<Request.Listener> listeners = client.getRequestListeners();
+        for (int i = 0; i < listeners.size(); ++i)
+        {
+            Request.Listener listener = listeners.get(i);
+            notifyFailure(listener, request, failure);
+        }
+    }
+
+    private void notifyFailure(Request.FailureListener listener, Request request, Throwable failure)
+    {
+        try
+        {
+            listener.onFailure(request, failure);
+        }
+        catch (Throwable x)
+        {
+            LOG.info("Exception while notifying listener " + listener, x);
+        }
+    }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/ResponseNotifier.java b/jetty-client/src/main/java/org/eclipse/jetty/client/ResponseNotifier.java
new file mode 100644
index 0000000..7d6d48a
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/ResponseNotifier.java
@@ -0,0 +1,291 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.nio.ByteBuffer;
+import java.util.Iterator;
+import java.util.List;
+
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.client.api.Result;
+import org.eclipse.jetty.http.HttpField;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.IteratingNestedCallback;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public class ResponseNotifier
+{
+    private static final Logger LOG = Log.getLogger(ResponseNotifier.class);
+
+    public void notifyBegin(List<Response.ResponseListener> listeners, Response response)
+    {
+        // Optimized to avoid allocations of iterator instances
+        for (int i = 0; i < listeners.size(); ++i)
+        {
+            Response.ResponseListener listener = listeners.get(i);
+            if (listener instanceof Response.BeginListener)
+                notifyBegin((Response.BeginListener)listener, response);
+        }
+    }
+
+    private void notifyBegin(Response.BeginListener listener, Response response)
+    {
+        try
+        {
+            listener.onBegin(response);
+        }
+        catch (Throwable x)
+        {
+            LOG.info("Exception while notifying listener " + listener, x);
+        }
+    }
+
+    public boolean notifyHeader(List<Response.ResponseListener> listeners, Response response, HttpField field)
+    {
+        boolean result = true;
+        // Optimized to avoid allocations of iterator instances
+        for (int i = 0; i < listeners.size(); ++i)
+        {
+            Response.ResponseListener listener = listeners.get(i);
+            if (listener instanceof Response.HeaderListener)
+                result &= notifyHeader((Response.HeaderListener)listener, response, field);
+        }
+        return result;
+    }
+
+    private boolean notifyHeader(Response.HeaderListener listener, Response response, HttpField field)
+    {
+        try
+        {
+            return listener.onHeader(response, field);
+        }
+        catch (Throwable x)
+        {
+            LOG.info("Exception while notifying listener " + listener, x);
+            return false;
+        }
+    }
+
+    public void notifyHeaders(List<Response.ResponseListener> listeners, Response response)
+    {
+        // Optimized to avoid allocations of iterator instances
+        for (int i = 0; i < listeners.size(); ++i)
+        {
+            Response.ResponseListener listener = listeners.get(i);
+            if (listener instanceof Response.HeadersListener)
+                notifyHeaders((Response.HeadersListener)listener, response);
+        }
+    }
+
+    private void notifyHeaders(Response.HeadersListener listener, Response response)
+    {
+        try
+        {
+            listener.onHeaders(response);
+        }
+        catch (Throwable x)
+        {
+            LOG.info("Exception while notifying listener " + listener, x);
+        }
+    }
+
+    public void notifyContent(List<Response.ResponseListener> listeners, Response response, ByteBuffer buffer, Callback callback)
+    {
+        // Here we use an IteratingNestedCallback not to avoid the stack overflow, but to
+        // invoke the listeners one after the other. When all of them have invoked the
+        // callback they got passed, the callback passed to this method is finally invoked.
+        ContentCallback contentCallback = new ContentCallback(listeners, response, buffer, callback);
+        contentCallback.iterate();
+    }
+
+    private void notifyContent(Response.AsyncContentListener listener, Response response, ByteBuffer buffer, Callback callback)
+    {
+        try
+        {
+            listener.onContent(response, buffer, callback);
+        }
+        catch (Throwable x)
+        {
+            LOG.info("Exception while notifying listener " + listener, x);
+        }
+    }
+
+    public void notifySuccess(List<Response.ResponseListener> listeners, Response response)
+    {
+        // Optimized to avoid allocations of iterator instances
+        for (int i = 0; i < listeners.size(); ++i)
+        {
+            Response.ResponseListener listener = listeners.get(i);
+            if (listener instanceof Response.SuccessListener)
+                notifySuccess((Response.SuccessListener)listener, response);
+        }
+    }
+
+    private void notifySuccess(Response.SuccessListener listener, Response response)
+    {
+        try
+        {
+            listener.onSuccess(response);
+        }
+        catch (Throwable x)
+        {
+            LOG.info("Exception while notifying listener " + listener, x);
+        }
+    }
+
+    public void notifyFailure(List<Response.ResponseListener> listeners, Response response, Throwable failure)
+    {
+        // Optimized to avoid allocations of iterator instances
+        for (int i = 0; i < listeners.size(); ++i)
+        {
+            Response.ResponseListener listener = listeners.get(i);
+            if (listener instanceof Response.FailureListener)
+                notifyFailure((Response.FailureListener)listener, response, failure);
+        }
+    }
+
+    private void notifyFailure(Response.FailureListener listener, Response response, Throwable failure)
+    {
+        try
+        {
+            listener.onFailure(response, failure);
+        }
+        catch (Throwable x)
+        {
+            LOG.info("Exception while notifying listener " + listener, x);
+        }
+    }
+
+    public void notifyComplete(List<Response.ResponseListener> listeners, Result result)
+    {
+        // Optimized to avoid allocations of iterator instances
+        for (int i = 0; i < listeners.size(); ++i)
+        {
+            Response.ResponseListener listener = listeners.get(i);
+            if (listener instanceof Response.CompleteListener)
+                notifyComplete((Response.CompleteListener)listener, result);
+        }
+    }
+
+    private void notifyComplete(Response.CompleteListener listener, Result result)
+    {
+        try
+        {
+            listener.onComplete(result);
+        }
+        catch (Throwable x)
+        {
+            LOG.info("Exception while notifying listener " + listener, x);
+        }
+    }
+
+    public void forwardSuccess(List<Response.ResponseListener> listeners, Response response)
+    {
+        notifyBegin(listeners, response);
+        for (Iterator<HttpField> iterator = response.getHeaders().iterator(); iterator.hasNext();)
+        {
+            HttpField field = iterator.next();
+            if (!notifyHeader(listeners, response, field))
+                iterator.remove();
+        }
+        notifyHeaders(listeners, response);
+        if (response instanceof ContentResponse)
+            // TODO: handle callback
+            notifyContent(listeners, response, ByteBuffer.wrap(((ContentResponse)response).getContent()), new Callback.Adapter());
+        notifySuccess(listeners, response);
+    }
+
+    public void forwardSuccessComplete(List<Response.ResponseListener> listeners, Request request, Response response)
+    {
+        forwardSuccess(listeners, response);
+        notifyComplete(listeners, new Result(request, response));
+    }
+
+    public void forwardFailure(List<Response.ResponseListener> listeners, Response response, Throwable failure)
+    {
+        notifyBegin(listeners, response);
+        for (Iterator<HttpField> iterator = response.getHeaders().iterator(); iterator.hasNext();)
+        {
+            HttpField field = iterator.next();
+            if (!notifyHeader(listeners, response, field))
+                iterator.remove();
+        }
+        notifyHeaders(listeners, response);
+        if (response instanceof ContentResponse)
+            // TODO: handle callback
+            notifyContent(listeners, response, ByteBuffer.wrap(((ContentResponse)response).getContent()), new Callback.Adapter());
+        notifyFailure(listeners, response, failure);
+    }
+
+    public void forwardFailureComplete(List<Response.ResponseListener> listeners, Request request, Throwable requestFailure, Response response, Throwable responseFailure)
+    {
+        forwardFailure(listeners, response, responseFailure);
+        notifyComplete(listeners, new Result(request, requestFailure, response, responseFailure));
+    }
+
+    private class ContentCallback extends IteratingNestedCallback
+    {
+        private final List<Response.ResponseListener> listeners;
+        private final Response response;
+        private final ByteBuffer buffer;
+        private int index;
+
+        private ContentCallback(List<Response.ResponseListener> listeners, Response response, ByteBuffer buffer, Callback callback)
+        {
+            super(callback);
+            this.listeners = listeners;
+            this.response = response;
+            // Slice the buffer to avoid that listeners peek into data they should not look at.
+            this.buffer = buffer.slice();
+        }
+
+        @Override
+        protected Action process() throws Exception
+        {
+            if (index == listeners.size())
+                return Action.SUCCEEDED;
+
+            Response.ResponseListener listener = listeners.get(index);
+            if (listener instanceof Response.AsyncContentListener)
+            {
+                // The buffer was sliced, so we always clear it
+                // (clear => position=0, limit=capacity) before
+                // passing it to the listener that may consume it.
+                buffer.clear();
+                ResponseNotifier.this.notifyContent((Response.AsyncContentListener)listener, response, buffer, this);
+                return Action.SCHEDULED;
+            }
+            else
+            {
+                succeeded();
+                return Action.SCHEDULED;
+            }
+        }
+
+        @Override
+        public void succeeded()
+        {
+            ++index;
+            super.succeeded();
+        }
+    }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/SelectConnector.java b/jetty-client/src/main/java/org/eclipse/jetty/client/SelectConnector.java
deleted file mode 100644
index 6aa9f44..0000000
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/SelectConnector.java
+++ /dev/null
@@ -1,449 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client;
-
-import java.io.IOException;
-import java.net.SocketTimeoutException;
-import java.nio.channels.SelectionKey;
-import java.nio.channels.SocketChannel;
-import java.nio.channels.UnresolvedAddressException;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-import javax.net.ssl.SSLEngine;
-
-import org.eclipse.jetty.io.AsyncEndPoint;
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.ConnectedEndPoint;
-import org.eclipse.jetty.io.Connection;
-import org.eclipse.jetty.io.nio.AsyncConnection;
-import org.eclipse.jetty.io.nio.SelectChannelEndPoint;
-import org.eclipse.jetty.io.nio.SelectorManager;
-import org.eclipse.jetty.io.nio.SslConnection;
-import org.eclipse.jetty.util.component.AggregateLifeCycle;
-import org.eclipse.jetty.util.component.Dumpable;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.util.ssl.SslContextFactory;
-import org.eclipse.jetty.util.thread.Timeout;
-import org.eclipse.jetty.util.thread.Timeout.Task;
-
-class SelectConnector extends AggregateLifeCycle implements HttpClient.Connector, Dumpable
-{
-    private static final Logger LOG = Log.getLogger(SelectConnector.class);
-
-    private final HttpClient _httpClient;
-    private final Manager _selectorManager=new Manager();
-    private final Map<SocketChannel, Timeout.Task> _connectingChannels = new ConcurrentHashMap<SocketChannel, Timeout.Task>();
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param httpClient the HttpClient this connector is associated to. It is 
-     * added via the {@link #addBean(Object, boolean)} as an unmanaged bean.
-     */
-    SelectConnector(HttpClient httpClient)
-    {
-        _httpClient = httpClient;
-        addBean(_httpClient,false);
-        addBean(_selectorManager,true);
-    }
-
-    /* ------------------------------------------------------------ */
-    public void startConnection( HttpDestination destination )
-        throws IOException
-    {
-        SocketChannel channel = null;
-        try
-        {
-            channel = SocketChannel.open();
-            Address address = destination.isProxied() ? destination.getProxy() : destination.getAddress();
-            channel.socket().setTcpNoDelay(true);
-
-            if (_httpClient.isConnectBlocking())
-            {
-                channel.socket().connect(address.toSocketAddress(), _httpClient.getConnectTimeout());
-                channel.configureBlocking(false);
-                _selectorManager.register( channel, destination );
-            }
-            else
-            {
-                channel.configureBlocking(false);
-                channel.connect(address.toSocketAddress());
-                _selectorManager.register(channel,destination);
-                ConnectTimeout connectTimeout = new ConnectTimeout(channel,destination);
-                _httpClient.schedule(connectTimeout,_httpClient.getConnectTimeout());
-                _connectingChannels.put(channel,connectTimeout);
-            }
-        }
-        catch (UnresolvedAddressException ex)
-        {
-            if (channel != null)
-                channel.close();
-            destination.onConnectionFailed(ex);
-        }
-        catch(IOException ex)
-        {
-            if (channel != null)
-                channel.close();
-            destination.onConnectionFailed(ex);
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    class Manager extends SelectorManager
-    {
-        Logger LOG = SelectConnector.LOG;
-
-        @Override
-        public boolean dispatch(Runnable task)
-        {
-            return _httpClient._threadPool.dispatch(task);
-        }
-
-        @Override
-        protected void endPointOpened(SelectChannelEndPoint endpoint)
-        {
-        }
-
-        @Override
-        protected void endPointClosed(SelectChannelEndPoint endpoint)
-        {
-        }
-
-        @Override
-        protected void endPointUpgraded(ConnectedEndPoint endpoint, Connection oldConnection)
-        {
-        }
-
-        @Override
-        public AsyncConnection newConnection(SocketChannel channel, AsyncEndPoint endpoint, Object attachment)
-        {
-            return new AsyncHttpConnection(_httpClient.getRequestBuffers(),_httpClient.getResponseBuffers(),endpoint);
-        }
-
-        @Override
-        protected SelectChannelEndPoint newEndPoint(SocketChannel channel, SelectSet selectSet, SelectionKey key) throws IOException
-        {
-            // We're connected, cancel the connect timeout
-            Timeout.Task connectTimeout = _connectingChannels.remove(channel);
-            if (connectTimeout != null)
-                connectTimeout.cancel();
-            if (LOG.isDebugEnabled())
-                LOG.debug("Channels with connection pending: {}", _connectingChannels.size());
-
-            // key should have destination at this point (will be replaced by endpoint after this call)
-            HttpDestination dest=(HttpDestination)key.attachment();
-
-            SelectChannelEndPoint scep = new SelectChannelEndPoint(channel, selectSet, key, (int)_httpClient.getIdleTimeout());
-            AsyncEndPoint ep = scep;
-
-            if (dest.isSecure())
-            {
-                LOG.debug("secure to {}, proxied={}",channel,dest.isProxied());
-                ep = new UpgradableEndPoint(ep,newSslEngine(dest.getSslContextFactory(), channel));
-            }
-
-            AsyncConnection connection = selectSet.getManager().newConnection(channel,ep, key.attachment());
-            ep.setConnection(connection);
-
-            AbstractHttpConnection httpConnection=(AbstractHttpConnection)connection;
-            httpConnection.setDestination(dest);
-
-            if (dest.isSecure() && !dest.isProxied())
-                ((UpgradableEndPoint)ep).upgrade();
-
-            dest.onNewConnection(httpConnection);
-
-            return scep;
-        }
-
-        private synchronized SSLEngine newSslEngine(SslContextFactory sslContextFactory, SocketChannel channel) throws IOException
-        {
-            SSLEngine sslEngine;
-            if (channel != null)
-            {
-                String peerHost = channel.socket().getInetAddress().getHostAddress();
-                int peerPort = channel.socket().getPort();
-                sslEngine = sslContextFactory.newSslEngine(peerHost, peerPort);
-            }
-            else
-            {
-                sslEngine = sslContextFactory.newSslEngine();
-            }
-            sslEngine.setUseClientMode(true);
-            sslEngine.beginHandshake();
-
-            return sslEngine;
-        }
-
-        /* ------------------------------------------------------------ */
-        /* (non-Javadoc)
-         * @see org.eclipse.io.nio.SelectorManager#connectionFailed(java.nio.channels.SocketChannel, java.lang.Throwable, java.lang.Object)
-         */
-        @Override
-        protected void connectionFailed(SocketChannel channel, Throwable ex, Object attachment)
-        {
-            Timeout.Task connectTimeout = _connectingChannels.remove(channel);
-            if (connectTimeout != null)
-                connectTimeout.cancel();
-
-            if (attachment instanceof HttpDestination)
-                ((HttpDestination)attachment).onConnectionFailed(ex);
-            else
-                super.connectionFailed(channel,ex,attachment);
-        }
-    }
-
-    private class ConnectTimeout extends Timeout.Task
-    {
-        private final SocketChannel channel;
-        private final HttpDestination destination;
-
-        public ConnectTimeout(SocketChannel channel, HttpDestination destination)
-        {
-            this.channel = channel;
-            this.destination = destination;
-        }
-
-        @Override
-        public void expired()
-        {
-            if (channel.isConnectionPending())
-            {
-                LOG.debug("Channel {} timed out while connecting, closing it", channel);
-                close();
-                _connectingChannels.remove(channel);
-                destination.onConnectionFailed(new SocketTimeoutException());
-            }
-        }
-
-        private void close()
-        {
-            try
-            {
-                // This will unregister the channel from the selector
-                channel.close();
-            }
-            catch (IOException x)
-            {
-                LOG.ignore(x);
-            }
-        }
-    }
-
-    public static class UpgradableEndPoint implements AsyncEndPoint
-    {
-        AsyncEndPoint _endp;
-        SSLEngine _engine;
-
-        public UpgradableEndPoint(AsyncEndPoint endp, SSLEngine engine) throws IOException
-        {
-            _engine=engine;
-            _endp=endp;
-        }
-
-        public void upgrade()
-        {
-            AsyncHttpConnection connection = (AsyncHttpConnection)_endp.getConnection();
-
-            SslConnection sslConnection = new SslConnection(_engine,_endp);
-            _endp.setConnection(sslConnection);
-
-            _endp=sslConnection.getSslEndPoint();
-            sslConnection.getSslEndPoint().setConnection(connection);
-
-            LOG.debug("upgrade {} to {} for {}",this,sslConnection,connection);
-        }
-
-
-        public Connection getConnection()
-        {
-            return _endp.getConnection();
-        }
-
-        public void setConnection(Connection connection)
-        {
-            _endp.setConnection(connection);
-        }
-
-        public void shutdownOutput() throws IOException
-        {
-            _endp.shutdownOutput();
-        }
-
-        public void dispatch()
-        {
-            _endp.asyncDispatch();
-        }
-
-        public void asyncDispatch()
-        {
-            _endp.asyncDispatch();
-        }
-
-        public boolean isOutputShutdown()
-        {
-            return _endp.isOutputShutdown();
-        }
-
-        public void shutdownInput() throws IOException
-        {
-            _endp.shutdownInput();
-        }
-
-        public void scheduleWrite()
-        {
-            _endp.scheduleWrite();
-        }
-
-        public boolean isInputShutdown()
-        {
-            return _endp.isInputShutdown();
-        }
-
-        public void close() throws IOException
-        {
-            _endp.close();
-        }
-
-        public int fill(Buffer buffer) throws IOException
-        {
-            return _endp.fill(buffer);
-        }
-
-        public boolean isWritable()
-        {
-            return _endp.isWritable();
-        }
-
-        public boolean hasProgressed()
-        {
-            return _endp.hasProgressed();
-        }
-
-        public int flush(Buffer buffer) throws IOException
-        {
-            return _endp.flush(buffer);
-        }
-
-        public void scheduleTimeout(Task task, long timeoutMs)
-        {
-            _endp.scheduleTimeout(task,timeoutMs);
-        }
-
-        public void cancelTimeout(Task task)
-        {
-            _endp.cancelTimeout(task);
-        }
-
-        public int flush(Buffer header, Buffer buffer, Buffer trailer) throws IOException
-        {
-            return _endp.flush(header,buffer,trailer);
-        }
-
-        public String getLocalAddr()
-        {
-            return _endp.getLocalAddr();
-        }
-
-        public String getLocalHost()
-        {
-            return _endp.getLocalHost();
-        }
-
-        public int getLocalPort()
-        {
-            return _endp.getLocalPort();
-        }
-
-        public String getRemoteAddr()
-        {
-            return _endp.getRemoteAddr();
-        }
-
-        public String getRemoteHost()
-        {
-            return _endp.getRemoteHost();
-        }
-
-        public int getRemotePort()
-        {
-            return _endp.getRemotePort();
-        }
-
-        public boolean isBlocking()
-        {
-            return _endp.isBlocking();
-        }
-
-        public boolean blockReadable(long millisecs) throws IOException
-        {
-            return _endp.blockReadable(millisecs);
-        }
-
-        public boolean blockWritable(long millisecs) throws IOException
-        {
-            return _endp.blockWritable(millisecs);
-        }
-
-        public boolean isOpen()
-        {
-            return _endp.isOpen();
-        }
-
-        public Object getTransport()
-        {
-            return _endp.getTransport();
-        }
-
-        public void flush() throws IOException
-        {
-            _endp.flush();
-        }
-
-        public int getMaxIdleTime()
-        {
-            return _endp.getMaxIdleTime();
-        }
-
-        public void setMaxIdleTime(int timeMs) throws IOException
-        {
-            _endp.setMaxIdleTime(timeMs);
-        }
-
-        public void onIdleExpired(long idleForMs)
-        {
-            _endp.onIdleExpired(idleForMs);
-        }
-
-        public void setCheckForIdle(boolean check)
-        {
-            _endp.setCheckForIdle(check);
-        }
-
-        public boolean isCheckForIdle()
-        {
-            return _endp.isCheckForIdle();
-        }
-
-        public String toString()
-        {
-            return "Upgradable:"+_endp.toString();
-        }
-    }
-}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/SocketConnector.java b/jetty-client/src/main/java/org/eclipse/jetty/client/SocketConnector.java
deleted file mode 100644
index 919540f..0000000
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/SocketConnector.java
+++ /dev/null
@@ -1,110 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client;
-
-import java.io.IOException;
-import java.io.InterruptedIOException;
-import java.net.Socket;
-import javax.net.SocketFactory;
-
-import org.eclipse.jetty.io.Connection;
-import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.io.bio.SocketEndPoint;
-import org.eclipse.jetty.util.component.AbstractLifeCycle;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-
-class SocketConnector extends AbstractLifeCycle implements HttpClient.Connector
-{
-    private static final Logger LOG = Log.getLogger(SocketConnector.class);
-
-    /**
-     *
-     */
-    private final HttpClient _httpClient;
-
-    /**
-     * @param httpClient
-     */
-    SocketConnector(HttpClient httpClient)
-    {
-        _httpClient = httpClient;
-    }
-
-    public void startConnection(final HttpDestination destination) throws IOException
-    {
-        Socket socket= destination.isSecure()
-            ? destination.getSslContextFactory().newSslSocket()
-            : SocketFactory.getDefault().createSocket();
-
-        socket.setSoTimeout(0);
-        socket.setTcpNoDelay(true);
-
-        Address address = destination.isProxied() ? destination.getProxy() : destination.getAddress();
-        socket.connect(address.toSocketAddress(), _httpClient.getConnectTimeout());
-
-        final EndPoint endpoint=new SocketEndPoint(socket);
-
-        final AbstractHttpConnection connection=new BlockingHttpConnection(_httpClient.getRequestBuffers(),_httpClient.getResponseBuffers(),endpoint);
-        connection.setDestination(destination);
-        destination.onNewConnection(connection);
-        _httpClient.getThreadPool().dispatch(new Runnable()
-        {
-            public void run()
-            {
-                try
-                {
-                    Connection con = connection;
-                    while(true)
-                    {
-                        final Connection next = con.handle();
-                        if (next!=con)
-                        {
-                            con=next;
-                            continue;
-                        }
-                        break;
-                    }
-                }
-                catch (IOException e)
-                {
-                    if (e instanceof InterruptedIOException)
-                        LOG.ignore(e);
-                    else
-                    {
-                        LOG.debug(e);
-                        destination.onException(e);
-                    }
-                }
-                finally
-                {
-                    try
-                    {
-                        destination.returnConnection(connection,true);
-                    }
-                    catch (IOException e)
-                    {
-                        LOG.debug(e);
-                    }
-                }
-            }
-        });
-
-    }
-}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/Socks4Proxy.java b/jetty-client/src/main/java/org/eclipse/jetty/client/Socks4Proxy.java
new file mode 100644
index 0000000..456e444
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/Socks4Proxy.java
@@ -0,0 +1,241 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
+import java.util.concurrent.Executor;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.eclipse.jetty.client.api.Connection;
+import org.eclipse.jetty.http.HttpScheme;
+import org.eclipse.jetty.io.AbstractConnection;
+import org.eclipse.jetty.io.ClientConnectionFactory;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.ssl.SslClientConnectionFactory;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.Promise;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public class Socks4Proxy extends ProxyConfiguration.Proxy
+{
+    public Socks4Proxy(String host, int port)
+    {
+        this(new Origin.Address(host, port), false);
+    }
+
+    public Socks4Proxy(Origin.Address address, boolean secure)
+    {
+        super(address, secure);
+    }
+
+    @Override
+    public ClientConnectionFactory newClientConnectionFactory(ClientConnectionFactory connectionFactory)
+    {
+        return new Socks4ProxyClientConnectionFactory(connectionFactory);
+    }
+
+    public static class Socks4ProxyClientConnectionFactory implements ClientConnectionFactory
+    {
+        private final ClientConnectionFactory connectionFactory;
+
+        public Socks4ProxyClientConnectionFactory(ClientConnectionFactory connectionFactory)
+        {
+            this.connectionFactory = connectionFactory;
+        }
+
+        @Override
+        public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map<String, Object> context) throws IOException
+        {
+            HttpDestination destination = (HttpDestination)context.get(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY);
+            Executor executor = destination.getHttpClient().getExecutor();
+            return new Socks4ProxyConnection(endPoint, executor, connectionFactory, context);
+        }
+    }
+
+    private static class Socks4ProxyConnection extends AbstractConnection implements Callback
+    {
+        private static final Pattern IPv4_PATTERN = Pattern.compile("(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})");
+        private static final Logger LOG = Log.getLogger(Socks4ProxyConnection.class);
+
+        private final Socks4Parser parser = new Socks4Parser();
+        private final ClientConnectionFactory connectionFactory;
+        private final Map<String, Object> context;
+
+        public Socks4ProxyConnection(EndPoint endPoint, Executor executor, ClientConnectionFactory connectionFactory, Map<String, Object> context)
+        {
+            super(endPoint, executor);
+            this.connectionFactory = connectionFactory;
+            this.context = context;
+        }
+
+        @Override
+        public void onOpen()
+        {
+            super.onOpen();
+            writeSocks4Connect();
+        }
+
+        /**
+         * Writes the SOCKS "connect" bytes, differentiating between SOCKS 4 and 4A;
+         * the former sends an IPv4 address, the latter the full domain name.
+         */
+        private void writeSocks4Connect()
+        {
+            HttpDestination destination = (HttpDestination)context.get(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY);
+            String host = destination.getHost();
+            short port = (short)destination.getPort();
+            Matcher matcher = IPv4_PATTERN.matcher(host);
+            if (matcher.matches())
+            {
+                // SOCKS 4
+                ByteBuffer buffer = ByteBuffer.allocate(9);
+                buffer.put((byte)4).put((byte)1).putShort(port);
+                for (int i = 1; i <= 4; ++i)
+                    buffer.put((byte)Integer.parseInt(matcher.group(i)));
+                buffer.put((byte)0);
+                buffer.flip();
+                getEndPoint().write(this, buffer);
+            }
+            else
+            {
+                // SOCKS 4A
+                byte[] hostBytes = host.getBytes(StandardCharsets.UTF_8);
+                ByteBuffer buffer = ByteBuffer.allocate(9 + hostBytes.length + 1);
+                buffer.put((byte)4).put((byte)1).putShort(port);
+                buffer.put((byte)0).put((byte)0).put((byte)0).put((byte)1).put((byte)0);
+                buffer.put(hostBytes).put((byte)0);
+                buffer.flip();
+                getEndPoint().write(this, buffer);
+            }
+        }
+
+        @Override
+        public void succeeded()
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("Written SOCKS4 connect request");
+            fillInterested();
+        }
+
+        @Override
+        public void failed(Throwable x)
+        {
+            close();
+            @SuppressWarnings("unchecked")
+            Promise<Connection> promise = (Promise<Connection>)context.get(HttpClientTransport.HTTP_CONNECTION_PROMISE_CONTEXT_KEY);
+            promise.failed(x);
+        }
+
+        @Override
+        public void onFillable()
+        {
+            try
+            {
+                while (true)
+                {
+                    // Avoid to read too much from the socket: ask
+                    // the parser how much left there is to read.
+                    ByteBuffer buffer = BufferUtil.allocate(parser.expected());
+                    int filled = getEndPoint().fill(buffer);
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("Read SOCKS4 connect response, {} bytes", filled);
+
+                    if (filled < 0)
+                        throw new IOException("SOCKS4 tunnel failed, connection closed");
+
+                    if (filled == 0)
+                    {
+                        fillInterested();
+                        return;
+                    }
+
+                    if (parser.parse(buffer))
+                        return;
+                }
+            }
+            catch (Throwable x)
+            {
+                failed(x);
+            }
+        }
+
+        private void onSocks4Response(int responseCode) throws IOException
+        {
+            if (responseCode == 0x5A)
+                tunnel();
+            else
+                throw new IOException("SOCKS4 tunnel failed with code " + responseCode);
+        }
+
+        private void tunnel()
+        {
+            try
+            {
+                HttpDestination destination = (HttpDestination)context.get(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY);
+                HttpClient client = destination.getHttpClient();
+                ClientConnectionFactory connectionFactory = this.connectionFactory;
+                if (HttpScheme.HTTPS.is(destination.getScheme()))
+                    connectionFactory = new SslClientConnectionFactory(client.getSslContextFactory(), client.getByteBufferPool(), client.getExecutor(), connectionFactory);
+                org.eclipse.jetty.io.Connection connection = connectionFactory.newConnection(getEndPoint(), context);
+                ClientConnectionFactory.Helper.replaceConnection(this, connection);
+                if (LOG.isDebugEnabled())
+                    LOG.debug("SOCKS4 tunnel established: {} over {}", this, connection);
+            }
+            catch (Throwable x)
+            {
+                failed(x);
+            }
+        }
+
+        private class Socks4Parser
+        {
+            private static final int EXPECTED_LENGTH = 8;
+            private int cursor;
+            private int response;
+
+            private boolean parse(ByteBuffer buffer) throws IOException
+            {
+                while (buffer.hasRemaining())
+                {
+                    byte current = buffer.get();
+                    if (cursor == 1)
+                        response = current & 0xFF;
+                    ++cursor;
+                    if (cursor == EXPECTED_LENGTH)
+                    {
+                        onSocks4Response(response);
+                        return true;
+                    }
+                }
+                return false;
+            }
+
+            private int expected()
+            {
+                return EXPECTED_LENGTH - cursor;
+            }
+        }
+    }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/Synchronizable.java b/jetty-client/src/main/java/org/eclipse/jetty/client/Synchronizable.java
new file mode 100644
index 0000000..068d6bb
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/Synchronizable.java
@@ -0,0 +1,45 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+/**
+ * <p>Implementations of this interface expose a lock object
+ * via {@link #getLock()} so that callers can synchronize
+ * externally on that lock:</p>
+ * <pre>
+ * if (iterator instanceof Synchronizable)
+ * {
+ *     Object element = null;
+ *     synchronized (((Synchronizable)iterator).getLock())
+ *     {
+ *         if (iterator.hasNext())
+ *             element = iterator.next();
+ *     }
+ * }
+ * </pre>
+ * <p>In the example above, the calls to {@code hasNext()} and
+ * {@code next()} are performed "atomically".</p>
+ */
+public interface Synchronizable
+{
+    /**
+     * @return the lock object to synchronize on
+     */
+    public Object getLock();
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/TimeoutCompleteListener.java b/jetty-client/src/main/java/org/eclipse/jetty/client/TimeoutCompleteListener.java
new file mode 100644
index 0000000..45b9a81
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/TimeoutCompleteListener.java
@@ -0,0 +1,84 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.client.api.Result;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+public class TimeoutCompleteListener implements Response.CompleteListener, Runnable
+{
+    private static final Logger LOG = Log.getLogger(TimeoutCompleteListener.class);
+
+    private final AtomicReference<Scheduler.Task> task = new AtomicReference<>();
+    private final Request request;
+
+    public TimeoutCompleteListener(Request request)
+    {
+        this.request = request;
+    }
+
+    @Override
+    public void onComplete(Result result)
+    {
+        cancel();
+    }
+
+    public boolean schedule(Scheduler scheduler)
+    {
+        long timeout = request.getTimeout();
+        Scheduler.Task task = scheduler.schedule(this, timeout, TimeUnit.MILLISECONDS);
+        Scheduler.Task existing = this.task.getAndSet(task);
+        if (existing != null)
+        {
+            existing.cancel();
+            cancel();
+            throw new IllegalStateException();
+        }
+        if (LOG.isDebugEnabled())
+            LOG.debug("Scheduled timeout task {} in {} ms for {}", task, timeout, request);
+        return true;
+    }
+
+    @Override
+    public void run()
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("Executing timeout task {} for {}", task, request);
+        request.abort(new TimeoutException("Total timeout elapsed"));
+    }
+
+    public void cancel()
+    {
+        Scheduler.Task task = this.task.getAndSet(null);
+        if (task != null)
+        {
+            boolean cancelled = task.cancel();
+            if (LOG.isDebugEnabled())
+                LOG.debug("Cancelled (successfully: {}) timeout task {}", cancelled, task);
+        }
+    }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/WWWAuthenticationProtocolHandler.java b/jetty-client/src/main/java/org/eclipse/jetty/client/WWWAuthenticationProtocolHandler.java
new file mode 100644
index 0000000..4bc6258
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/WWWAuthenticationProtocolHandler.java
@@ -0,0 +1,63 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.net.URI;
+
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpStatus;
+
+public class WWWAuthenticationProtocolHandler extends AuthenticationProtocolHandler
+{
+    public WWWAuthenticationProtocolHandler(HttpClient client)
+    {
+        this(client, DEFAULT_MAX_CONTENT_LENGTH);
+    }
+
+    public WWWAuthenticationProtocolHandler(HttpClient client, int maxContentLength)
+    {
+        super(client, maxContentLength);
+    }
+
+    @Override
+    public boolean accept(Request request, Response response)
+    {
+        return response.getStatus() == HttpStatus.UNAUTHORIZED_401;
+    }
+
+    @Override
+    protected HttpHeader getAuthenticateHeader()
+    {
+        return HttpHeader.WWW_AUTHENTICATE;
+    }
+
+    @Override
+    protected HttpHeader getAuthorizationHeader()
+    {
+        return HttpHeader.AUTHORIZATION;
+    }
+
+    @Override
+    protected URI getAuthenticationURI(Request request)
+    {
+        return request.getURI();
+    }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/api/Authentication.java b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Authentication.java
new file mode 100644
index 0000000..48323a2
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Authentication.java
@@ -0,0 +1,138 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client.api;
+
+import java.net.URI;
+
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.util.Attributes;
+
+/**
+ * {@link Authentication} represents a mechanism to authenticate requests for protected resources.
+ * <p />
+ * {@link Authentication}s are added to an {@link AuthenticationStore}, which is then
+ * {@link #matches(String, URI, String) queried} to find the right
+ * {@link Authentication} mechanism to use based on its type, URI and realm, as returned by
+ * {@code WWW-Authenticate} response headers.
+ * <p />
+ * If an {@link Authentication} mechanism is found, it is then
+ * {@link #authenticate(Request, ContentResponse, HeaderInfo, Attributes) executed} for the given request,
+ * returning an {@link Authentication.Result}, which is then stored in the {@link AuthenticationStore}
+ * so that subsequent requests can be preemptively authenticated.
+ */
+public interface Authentication
+{
+    /**
+     * Matches {@link Authentication}s based on the given parameters
+     * @param type the {@link Authentication} type such as "Basic" or "Digest"
+     * @param uri the request URI
+     * @param realm the authentication realm as provided in the {@code WWW-Authenticate} response header
+     * @return true if this authentication matches, false otherwise
+     */
+    boolean matches(String type, URI uri, String realm);
+
+    /**
+     * Executes the authentication mechanism for the given request, returning a {@link Result} that can be
+     * used to actually authenticate the request via {@link Result#apply(Request)}.
+     * <p />
+     * If a request for {@code "/secure"} returns a {@link Result}, then the result may be used for other
+     * requests such as {@code "/secure/foo"} or {@code "/secure/bar"}, unless those resources are protected
+     * by other realms.
+     *
+     * @param request the request to execute the authentication mechanism for
+     * @param response the 401 response obtained in the previous attempt to request the protected resource
+     * @param headerInfo the {@code WWW-Authenticate} (or {@code Proxy-Authenticate}) header chosen for this
+     *                     authentication (among the many that the response may contain)
+     * @param context the conversation context in case the authentication needs multiple exchanges
+     *                to be completed and information needs to be stored across exchanges
+     * @return the authentication result, or null if the authentication could not be performed
+     */
+    Result authenticate(Request request, ContentResponse response, HeaderInfo headerInfo, Attributes context);
+
+    /**
+     * Structure holding information about the {@code WWW-Authenticate} (or {@code Proxy-Authenticate}) header.
+     */
+    public static class HeaderInfo
+    {
+        private final String type;
+        private final String realm;
+        private final String params;
+        private final HttpHeader header;
+
+        public HeaderInfo(String type, String realm, String params, HttpHeader header)
+        {
+            this.type = type;
+            this.realm = realm;
+            this.params = params;
+            this.header = header;
+        }
+
+        /**
+         * @return the authentication type (for example "Basic" or "Digest")
+         */
+        public String getType()
+        {
+            return type;
+        }
+
+        /**
+         * @return the realm name
+         */
+        public String getRealm()
+        {
+            return realm;
+        }
+
+        /**
+         * @return additional authentication parameters
+         */
+        public String getParameters()
+        {
+            return params;
+        }
+
+        /**
+         * @return the {@code Authorization} (or {@code Proxy-Authorization}) header
+         */
+        public HttpHeader getHeader()
+        {
+            return header;
+        }
+    }
+
+    /**
+     * {@link Result} holds the information needed to authenticate a {@link Request} via {@link #apply(Request)}.
+     */
+    public static interface Result
+    {
+        /**
+         * @return the URI of the request that has been used to generate this {@link Result}
+         */
+        URI getURI();
+
+        /**
+         * Applies the authentication result to the given request.
+         * Typically, a {@code Authorization} header is added to the request, with the right information to
+         * successfully authenticate at the server.
+         *
+         * @param request the request to authenticate
+         */
+        void apply(Request request);
+    }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/api/AuthenticationStore.java b/jetty-client/src/main/java/org/eclipse/jetty/client/api/AuthenticationStore.java
new file mode 100644
index 0000000..91bdbd8
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/api/AuthenticationStore.java
@@ -0,0 +1,78 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client.api;
+
+import java.net.URI;
+
+/**
+ * A store for {@link Authentication}s and {@link Authentication.Result}s.
+ */
+public interface AuthenticationStore
+{
+    /**
+     * @param authentication the {@link Authentication} to add
+     */
+    public void addAuthentication(Authentication authentication);
+
+    /**
+     * @param authentication the {@link Authentication} to remove
+     */
+    public void removeAuthentication(Authentication authentication);
+
+    /**
+     * Removes all {@link Authentication}s stored
+     */
+    public void clearAuthentications();
+
+    /**
+     * Returns the authentication that matches the given type (for example, "Basic" or "Digest"),
+     * the given request URI and the given realm.
+     * If no such authentication can be found, returns null.
+     *
+     * @param type the {@link Authentication} type such as "Basic" or "Digest"
+     * @param uri the request URI
+     * @param realm the authentication realm
+     * @return the authentication that matches the given parameters, or null
+     */
+    public Authentication findAuthentication(String type, URI uri, String realm);
+
+    /**
+     * @param result the {@link Authentication.Result} to add
+     */
+    public void addAuthenticationResult(Authentication.Result result);
+
+    /**
+     * @param result the {@link Authentication.Result} to remove
+     */
+    public void removeAuthenticationResult(Authentication.Result result);
+
+    /**
+     * Removes all authentication results stored
+     */
+    public void clearAuthenticationResults();
+
+    /**
+     * Returns an {@link Authentication.Result} that matches the given URI, or null if no
+     * {@link Authentication.Result}s match the given URI.
+     *
+     * @param uri the request URI
+     * @return the {@link Authentication.Result} that matches the given URI, or null
+     */
+    public Authentication.Result findAuthenticationResult(URI uri);
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/api/Connection.java b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Connection.java
new file mode 100644
index 0000000..0242de5
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Connection.java
@@ -0,0 +1,46 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client.api;
+
+import java.io.Closeable;
+
+/**
+ * {@link Connection} represent a connection to a {@link Destination} and allow applications to send
+ * requests via {@link #send(Request, Response.CompleteListener)}.
+ * <p />
+ * {@link Connection}s are normally pooled by {@link Destination}s, but unpooled {@link Connection}s
+ * may be created by applications that want to do their own connection management via
+ * {@link Destination#newConnection(Promise)} and {@link Connection#close()}.
+ */
+public interface Connection extends Closeable
+{
+    /**
+     * Sends a request with an associated response listener.
+     * <p />
+     * {@link Request#send(Response.CompleteListener)} will eventually call this method to send the request.
+     * It is exposed to allow applications to send requests via unpooled connections.
+     *
+     * @param request the request to send
+     * @param listener the response listener
+     */
+    void send(Request request, Response.CompleteListener listener);
+
+    @Override
+    void close();
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/api/ContentProvider.java b/jetty-client/src/main/java/org/eclipse/jetty/client/api/ContentProvider.java
new file mode 100644
index 0000000..6658cb3
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/api/ContentProvider.java
@@ -0,0 +1,54 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client.api;
+
+import java.nio.ByteBuffer;
+
+/**
+ * {@link ContentProvider} provides a source of request content.
+ * <p/>
+ * Implementations should return an {@link Iterator} over the request content.
+ * If the request content comes from a source that needs to be closed (for
+ * example, an {@link InputStream}), then the iterator implementation class
+ * must implement {@link Closeable} and will be closed when the request is
+ * completed (either successfully or failed).
+ * <p/>
+ * Applications should rely on utility classes such as {@link ByteBufferContentProvider}
+ * or {@link PathContentProvider}.
+ */
+public interface ContentProvider extends Iterable<ByteBuffer>
+{
+    /**
+     * @return the content length, if known, or -1 if the content length is unknown
+     */
+    long getLength();
+
+    /**
+     * An extension of {@link ContentProvider} that provides a content type string
+     * to be used as a {@code Content-Type} HTTP header in requests.
+     */
+    public interface Typed extends ContentProvider
+    {
+        /**
+         * @return the content type string such as "application/octet-stream" or
+         * "application/json;charset=UTF8", or null if no content type must be set
+         */
+        public String getContentType();
+    }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/api/ContentResponse.java b/jetty-client/src/main/java/org/eclipse/jetty/client/api/ContentResponse.java
new file mode 100644
index 0000000..7d18b27
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/api/ContentResponse.java
@@ -0,0 +1,46 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client.api;
+
+/**
+ * A specialized {@link Response} that can hold a limited content in memory.
+ */
+public interface ContentResponse extends Response
+{
+    /**
+     * @return the media type of the content, such as "text/html" or "application/octet-stream"
+     */
+    String getMediaType();
+
+    /**
+     * @return the encoding of the content, such as "UTF-8"
+     */
+    String getEncoding();
+
+    /**
+     * @return the response content
+     */
+    byte[] getContent();
+
+    /**
+     * @return the response content as a string, decoding the bytes using the charset
+     * provided by the {@code Content-Type} header, if any, or UTF-8.
+     */
+    String getContentAsString();
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/api/Destination.java b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Destination.java
new file mode 100644
index 0000000..a68b124
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Destination.java
@@ -0,0 +1,64 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client.api;
+
+import org.eclipse.jetty.util.Promise;
+
+/**
+ * {@link Destination} represents the triple made of the {@link #getScheme}, the {@link #getHost}
+ * and the {@link #getPort}.
+ * <p />
+ * {@link Destination} holds a pool of {@link Connection}s, but allows to create unpooled
+ * connections if the application wants full control over connection management via {@link #newConnection(Promise)}.
+ * <p />
+ * {@link Destination}s may be obtained via {@link HttpClient#getDestination(String, String, int)}
+ */
+public interface Destination
+{
+    /**
+     * @return the scheme of this destination, such as "http" or "https"
+     */
+    String getScheme();
+
+    /**
+     * @return the host of this destination, such as "127.0.0.1" or "google.com"
+     */
+    String getHost();
+
+    /**
+     * @return the port of this destination such as 80 or 443
+     */
+    int getPort();
+
+    /**
+     * Creates asynchronously a new, unpooled, {@link Connection} that will be returned
+     * at a later time through the given {@link Promise}.
+     * <p />
+     * Use {@link FuturePromise} to wait for the connection:
+     * <pre>
+     * Destination destination = ...;
+     * FuturePromise<Connection> futureConnection = new FuturePromise<>();
+     * destination.newConnection(futureConnection);
+     * Connection connection = futureConnection.get(5, TimeUnit.SECONDS);
+     * </pre>
+     *
+     * @param promise the promise of a new, unpooled, {@link Connection}
+     */
+    void newConnection(Promise<Connection> promise);
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/api/Request.java b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Request.java
new file mode 100644
index 0000000..9721712
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Request.java
@@ -0,0 +1,574 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client.api;
+
+import java.io.IOException;
+import java.net.HttpCookie;
+import java.net.URI;
+import java.nio.ByteBuffer;
+import java.nio.file.Path;
+import java.util.EventListener;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.util.Fields;
+
+/**
+ * <p>{@link Request} represents a HTTP request, and offers a fluent interface to customize
+ * various attributes such as the path, the headers, the content, etc.</p>
+ * <p>You can create {@link Request} objects via {@link HttpClient#newRequest(String)} and
+ * you can send them using either {@link #send()} for a blocking semantic, or
+ * {@link #send(Response.CompleteListener)} for an asynchronous semantic.</p>
+ *
+ * @see Response
+ */
+public interface Request
+{
+    /**
+     * @return the scheme of this request, such as "http" or "https"
+     */
+    String getScheme();
+
+    /**
+     * @param scheme the scheme of this request, such as "http" or "https"
+     * @return this request object
+     */
+    Request scheme(String scheme);
+
+    /**
+     * @return the host of this request, such as "127.0.0.1" or "google.com"
+     */
+    String getHost();
+
+    /**
+     * @return the port of this request such as 80 or 443
+     */
+    int getPort();
+
+    /**
+     * @return the method of this request, such as GET or POST, as a String
+     */
+    String getMethod();
+
+    /**
+     * @param method the method of this request, such as GET or POST
+     * @return this request object
+     */
+    Request method(HttpMethod method);
+
+    /**
+     * @param method the method of this request, such as GET or POST
+     * @return this request object
+     */
+    Request method(String method);
+
+    /**
+     * @return the path of this request, such as "/" or "/path" - without the query
+     * @see #getQuery()
+     */
+    String getPath();
+
+    /**
+     * Specifies the path - and possibly the query - of this request.
+     * If the query part is specified, parameter values must be properly
+     * {@link URLEncoder#encode(String, String) UTF-8 URL encoded}.
+     * For example, if the value for parameter "currency" is the euro symbol € then the
+     * query string for this parameter must be "currency=%E2%82%AC".
+     * For transparent encoding of parameter values, use {@link #param(String, String)}.
+     *
+     * @param path the path of this request, such as "/" or "/path?param=1"
+     * @return this request object
+     */
+    Request path(String path);
+
+    /**
+     * @return the query string of this request such as "param=1"
+     * @see #getPath()
+     * @see #getParams()
+     */
+    String getQuery();
+
+    /**
+     * @return the full URI of this request such as "http://host:port/path?param=1"
+     */
+    URI getURI();
+
+    /**
+     * @return the HTTP version of this request, such as "HTTP/1.1"
+     */
+    HttpVersion getVersion();
+
+    /**
+     * @param version the HTTP version of this request, such as "HTTP/1.1"
+     * @return this request object
+     */
+    Request version(HttpVersion version);
+
+    /**
+     * @return the query parameters of this request
+     */
+    Fields getParams();
+
+    /**
+     * Adds a query parameter with the given name and value.
+     * The value is {@link URLEncoder#encode(String, String) UTF-8 URL encoded}.
+     *
+     * @param name the name of the query parameter
+     * @param value the value of the query parameter
+     * @return this request object
+     */
+    Request param(String name, String value);
+
+    /**
+     * @return the headers of this request
+     */
+    HttpFields getHeaders();
+
+    /**
+     * @param name the name of the header
+     * @param value the value of the header
+     * @return this request object
+     * @see #header(HttpHeader, String)
+     */
+    Request header(String name, String value);
+
+    /**
+     * <p>Adds the given {@code value} to the specified {@code header}.</p>
+     * <p>Multiple calls with the same parameters will add multiple values;
+     * use the value {@code null} to remove the header completely.</p>
+     *
+     * @param header the header name
+     * @param value the value of the header
+     * @return this request object
+     */
+    Request header(HttpHeader header, String value);
+
+    /**
+     * @return the cookies associated with this request
+     */
+    List<HttpCookie> getCookies();
+
+    /**
+     * @param cookie a cookie for this request
+     * @return this request object
+     */
+    Request cookie(HttpCookie cookie);
+
+    /**
+     * @param name the name of the attribute
+     * @param value the value of the attribute
+     * @return this request object
+     */
+    Request attribute(String name, Object value);
+
+    /**
+     * @return the attributes of this request
+     */
+    Map<String, Object> getAttributes();
+
+    /**
+     * @return the content provider of this request
+     */
+    ContentProvider getContent();
+
+    /**
+     * @param content the content provider of this request
+     * @return this request object
+     */
+    Request content(ContentProvider content);
+
+    /**
+     * @param content the content provider of this request
+     * @return this request object
+     */
+    Request content(ContentProvider content, String contentType);
+
+    /**
+     * Shortcut method to specify a file as a content for this request, with the default content type of
+     * "application/octect-stream".
+     *
+     * @param file the file to upload
+     * @return this request object
+     * @throws IOException if the file does not exist or cannot be read
+     */
+    Request file(Path file) throws IOException;
+
+    /**
+     * Shortcut method to specify a file as a content for this request, with the given content type.
+     *
+     * @param file the file to upload
+     * @param contentType the content type of the file
+     * @return this request object
+     * @throws IOException if the file does not exist or cannot be read
+     */
+    Request file(Path file, String contentType) throws IOException;
+
+    /**
+     * @return the user agent for this request
+     */
+    String getAgent();
+
+    /**
+     * @param agent the user agent for this request (corresponds to the {@code User-Agent} header)
+     * @return this request object
+     */
+    Request agent(String agent);
+
+    /**
+     * @param accepts the media types that are acceptable in the response, such as
+     *                "text/plain;q=0.5" or "text/html" (corresponds to the {@code Accept} header)
+     * @return this request object
+     */
+    Request accept(String... accepts);
+
+    /**
+     * @return the idle timeout for this request, in milliseconds
+     */
+    long getIdleTimeout();
+
+    /**
+     * @param timeout the idle timeout for this request
+     * @param unit the idle timeout unit
+     * @return this request object
+     */
+    Request idleTimeout(long timeout, TimeUnit unit);
+
+    /**
+     * @return the total timeout for this request, in milliseconds
+     */
+    long getTimeout();
+
+    /**
+     * @param timeout the total timeout for the request/response conversation
+     * @param unit the timeout unit
+     * @return this request object
+     */
+    Request timeout(long timeout, TimeUnit unit);
+
+    /**
+     * @return whether this request follows redirects
+     */
+    boolean isFollowRedirects();
+
+    /**
+     * @param follow whether this request follows redirects
+     * @return this request object
+     */
+    Request followRedirects(boolean follow);
+
+    /**
+     * @param listenerClass the class of the listener, or null for all listeners classes
+     * @return the listeners for request events of the given class
+     */
+    <T extends RequestListener> List<T> getRequestListeners(Class<T> listenerClass);
+
+    /**
+     * @param listener a listener for request events
+     * @return this request object
+     */
+    Request listener(Listener listener);
+
+    /**
+     * @param listener a listener for request queued event
+     * @return this request object
+     */
+    Request onRequestQueued(QueuedListener listener);
+
+    /**
+     * @param listener a listener for request begin event
+     * @return this request object
+     */
+    Request onRequestBegin(BeginListener listener);
+
+    /**
+     * @param listener a listener for request headers event
+     * @return this request object
+     */
+    Request onRequestHeaders(HeadersListener listener);
+
+    /**
+     * @param listener a listener for request commit event
+     * @return this request object
+     */
+    Request onRequestCommit(CommitListener listener);
+
+    /**
+     * @param listener a listener for request content events
+     * @return this request object
+     */
+    Request onRequestContent(ContentListener listener);
+
+    /**
+     * @param listener a listener for request success event
+     * @return this request object
+     */
+    Request onRequestSuccess(SuccessListener listener);
+
+    /**
+     * @param listener a listener for request failure event
+     * @return this request object
+     */
+    Request onRequestFailure(FailureListener listener);
+
+    /**
+     * @param listener a listener for response begin event
+     * @return this request object
+     */
+    Request onResponseBegin(Response.BeginListener listener);
+
+    /**
+     * @param listener a listener for response header event
+     * @return this request object
+     */
+    Request onResponseHeader(Response.HeaderListener listener);
+
+    /**
+     * @param listener a listener for response headers event
+     * @return this request object
+     */
+    Request onResponseHeaders(Response.HeadersListener listener);
+
+    /**
+     * @param listener a consuming listener for response content events
+     * @return this request object
+     */
+    Request onResponseContent(Response.ContentListener listener);
+
+    /**
+     * @param listener an asynchronous listener for response content events
+     * @return this request object
+     */
+    Request onResponseContentAsync(Response.AsyncContentListener listener);
+
+    /**
+     * @param listener a listener for response success event
+     * @return this request object
+     */
+    Request onResponseSuccess(Response.SuccessListener listener);
+
+    /**
+     * @param listener a listener for response failure event
+     * @return this request object
+     */
+    Request onResponseFailure(Response.FailureListener listener);
+
+    /**
+     * @param listener a listener for complete event
+     * @return this request object
+     */
+    Request onComplete(Response.CompleteListener listener);
+
+    /**
+     * Sends this request and returns the response.
+     * <p />
+     * This method should be used when a simple blocking semantic is needed, and when it is known
+     * that the response content can be buffered without exceeding memory constraints.
+     * <p />
+     * For example, this method is not appropriate to download big files from a server; consider using
+     * {@link #send(Response.CompleteListener)} instead, passing your own {@link Response.Listener} or a utility
+     * listener such as {@link InputStreamResponseListener}.
+     * <p />
+     * The method returns when the {@link Response.CompleteListener complete event} is fired.
+     *
+     * @return a {@link ContentResponse} for this request
+     * @see Response.CompleteListener#onComplete(Result)
+     */
+    ContentResponse send() throws InterruptedException, TimeoutException, ExecutionException;
+
+    /**
+     * <p>Sends this request and asynchronously notifies the given listener for response events.</p>
+     * <p>This method should be used when the application needs to be notified of the various response events
+     * as they happen, or when the application needs to efficiently manage the response content.</p>
+     * <p>The listener passed to this method may implement not only {@link Response.CompleteListener}
+     * but also other response listener interfaces, and all the events implemented will be notified.
+     * This allows application code to write a single listener class to handle all relevant events.</p>
+     *
+     * @param listener the listener that receives response events
+     */
+    void send(Response.CompleteListener listener);
+
+    /**
+     * Attempts to abort the send of this request.
+     *
+     * @param cause the abort cause, must not be null
+     * @return whether the abort succeeded
+     */
+    boolean abort(Throwable cause);
+
+    /**
+     * @return the abort cause passed to {@link #abort(Throwable)},
+     * or null if this request has not been aborted
+     */
+    Throwable getAbortCause();
+
+    /**
+     * Common, empty, super-interface for request listeners.
+     */
+    public interface RequestListener extends EventListener
+    {
+    }
+
+    /**
+     * Listener for the request queued event.
+     */
+    public interface QueuedListener extends RequestListener
+    {
+        /**
+         * Callback method invoked when the request is queued, waiting to be sent
+         *
+         * @param request the request being queued
+         */
+        public void onQueued(Request request);
+    }
+
+    /**
+     * Listener for the request begin event.
+     */
+    public interface BeginListener extends RequestListener
+    {
+        /**
+         * Callback method invoked when the request begins being processed in order to be sent.
+         * This is the last opportunity to modify the request.
+         *
+         * @param request the request that begins being processed
+         */
+        public void onBegin(Request request);
+    }
+
+    /**
+     * Listener for the request headers event.
+     */
+    public interface HeadersListener extends RequestListener
+    {
+        /**
+         * Callback method invoked when the request headers (and perhaps small content) are ready to be sent.
+         * The request has been converted into bytes, but not yet sent to the server, and further modifications
+         * to the request may have no effect.
+         * @param request the request that is about to be committed
+         */
+        public void onHeaders(Request request);
+    }
+
+    /**
+     * Listener for the request committed event.
+     */
+    public interface CommitListener extends RequestListener
+    {
+        /**
+         * Callback method invoked when the request headers (and perhaps small content) have been sent.
+         * The request is now committed, and in transit to the server, and further modifications to the
+         * request may have no effect.
+         * @param request the request that has been committed
+         */
+        public void onCommit(Request request);
+    }
+
+    /**
+     * Listener for the request content event.
+     */
+    public interface ContentListener extends RequestListener
+    {
+        /**
+         * Callback method invoked when a chunk of request content has been sent successfully.
+         * Changes to bytes in the given buffer have no effect, as the content has already been sent.
+         * @param request the request that has been committed
+         */
+        public void onContent(Request request, ByteBuffer content);
+    }
+
+    /**
+     * Listener for the request succeeded event.
+     */
+    public interface SuccessListener extends RequestListener
+    {
+        /**
+         * Callback method invoked when the request has been successfully sent.
+         *
+         * @param request the request sent
+         */
+        public void onSuccess(Request request);
+    }
+
+    /**
+     * Listener for the request failed event.
+     */
+    public interface FailureListener extends RequestListener
+    {
+        /**
+         * Callback method invoked when the request has failed to be sent
+         * @param request the request that failed
+         * @param failure the failure
+         */
+        public void onFailure(Request request, Throwable failure);
+    }
+
+    /**
+     * Listener for all request events.
+     */
+    public interface Listener extends QueuedListener, BeginListener, HeadersListener, CommitListener, ContentListener, SuccessListener, FailureListener
+    {
+        /**
+         * An empty implementation of {@link Listener}
+         */
+        public static class Adapter implements Listener
+        {
+            @Override
+            public void onQueued(Request request)
+            {
+            }
+
+            @Override
+            public void onBegin(Request request)
+            {
+            }
+
+            @Override
+            public void onHeaders(Request request)
+            {
+            }
+
+            @Override
+            public void onCommit(Request request)
+            {
+            }
+
+            @Override
+            public void onContent(Request request, ByteBuffer content)
+            {
+            }
+
+            @Override
+            public void onSuccess(Request request)
+            {
+            }
+
+            @Override
+            public void onFailure(Request request, Throwable failure)
+            {
+            }
+        }
+    }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/api/Response.java b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Response.java
new file mode 100644
index 0000000..f1d933c
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Response.java
@@ -0,0 +1,270 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client.api;
+
+import java.nio.ByteBuffer;
+import java.util.EventListener;
+import java.util.List;
+
+import org.eclipse.jetty.http.HttpField;
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.util.Callback;
+
+/**
+ * <p>{@link Response} represents a HTTP response and offers methods to retrieve status code, HTTP version
+ * and headers.</p>
+ * <p>{@link Response} objects are passed as parameters to {@link Response.Listener} callbacks, or as
+ * future result of {@link Request#send()}.</p>
+ * <p>{@link Response} objects do not contain getters for the response content, because it may be too large
+ * to fit into memory.
+ * The response content should be retrieved via {@link Response.Listener#onContent(Response, ByteBuffer) content
+ * events}, or via utility classes such as {@link BufferingResponseListener}.</p>
+ */
+public interface Response
+{
+    /**
+     * @return the request associated with this response
+     */
+    Request getRequest();
+
+    /**
+     * @return the response listener passed to {@link Request#send(CompleteListener)}
+     */
+    <T extends ResponseListener> List<T> getListeners(Class<T> listenerClass);
+
+    /**
+     * @return the HTTP version of this response, such as "HTTP/1.1"
+     */
+    HttpVersion getVersion();
+
+    /**
+     * @return the HTTP status code of this response, such as 200 or 404
+     */
+    int getStatus();
+
+    /**
+     * @return the HTTP reason associated to the {@link #getStatus}
+     */
+    String getReason();
+
+    /**
+     * @return the headers of this response
+     */
+    HttpFields getHeaders();
+
+    /**
+     * Attempts to abort the receive of this response.
+     *
+     * @param cause the abort cause, must not be null
+     * @return whether the abort succeeded
+     */
+    boolean abort(Throwable cause);
+
+    /**
+     * Common, empty, super-interface for response listeners
+     */
+    public interface ResponseListener extends EventListener
+    {
+    }
+
+    /**
+     * Listener for the response begin event.
+     */
+    public interface BeginListener extends ResponseListener
+    {
+        /**
+         * Callback method invoked when the response line containing HTTP version,
+         * HTTP status code and reason has been received and parsed.
+         * <p />
+         * This method is the best approximation to detect when the first bytes of the response arrived to the client.
+         *
+         * @param response the response containing the response line data
+         */
+        public void onBegin(Response response);
+    }
+
+    /**
+     * Listener for a response header event.
+     */
+    public interface HeaderListener extends ResponseListener
+    {
+        /**
+         * Callback method invoked when a response header has been received,
+         * returning whether the header should be processed or not.
+         *
+         * @param response the response containing the response line data and the headers so far
+         * @param field the header received
+         * @return true to process the header, false to skip processing of the header
+         */
+        public boolean onHeader(Response response, HttpField field);
+    }
+
+    /**
+     * Listener for the response headers event.
+     */
+    public interface HeadersListener extends ResponseListener
+    {
+        /**
+         * Callback method invoked when the response headers have been received and parsed.
+         *
+         * @param response the response containing the response line data and the headers
+         */
+        public void onHeaders(Response response);
+    }
+
+    /**
+     * Listener for the response content events.
+     */
+    public interface ContentListener extends ResponseListener
+    {
+        /**
+         * Callback method invoked when the response content has been received.
+         * This method may be invoked multiple times, and the {@code content} buffer must be consumed
+         * before returning from this method.
+         *
+         * @param response the response containing the response line data and the headers
+         * @param content the content bytes received
+         */
+        public void onContent(Response response, ByteBuffer content);
+    }
+
+    public interface AsyncContentListener extends ResponseListener
+    {
+        /**
+         * Callback method invoked asynchronously when the response content has been received.
+         *
+         * @param response the response containing the response line data and the headers
+         * @param content the content bytes received
+         * @param callback the callback to call when the content is consumed.
+         */
+        public void onContent(Response response, ByteBuffer content, Callback callback);
+    }
+
+    /**
+     * Listener for the response succeeded event.
+     */
+    public interface SuccessListener extends ResponseListener
+    {
+        /**
+         * Callback method invoked when the whole response has been successfully received.
+         *
+         * @param response the response containing the response line data and the headers
+         */
+        public void onSuccess(Response response);
+    }
+
+    /**
+     * Listener for the response failure event.
+     */
+    public interface FailureListener extends ResponseListener
+    {
+        /**
+         * Callback method invoked when the response has failed in the process of being received
+         *
+         * @param response the response containing data up to the point the failure happened
+         * @param failure the failure happened
+         */
+        public void onFailure(Response response, Throwable failure);
+    }
+
+    /**
+     * Listener for the request and response completed event.
+     */
+    public interface CompleteListener extends ResponseListener
+    {
+        /**
+         * Callback method invoked when the request <em><b>and</b></em> the response have been processed,
+         * either successfully or not.
+         * <p/>
+         * The {@code result} parameter contains the request, the response, and eventual failures.
+         * <p/>
+         * Requests may complete <em>after</em> response, for example in case of big uploads that are
+         * discarded or read asynchronously by the server.
+         * This method is always invoked <em>after</em> {@link SuccessListener#onSuccess(Response)} or
+         * {@link FailureListener#onFailure(Response, Throwable)}, and only when request indicates that
+         * it is completed.
+         *
+         * @param result the result of the request / response exchange
+         */
+        public void onComplete(Result result);
+    }
+
+    /**
+     * Listener for all response events.
+     */
+    public interface Listener extends BeginListener, HeaderListener, HeadersListener, ContentListener, AsyncContentListener, SuccessListener, FailureListener, CompleteListener
+    {
+        /**
+         * An empty implementation of {@link Listener}
+         */
+        public static class Adapter implements Listener
+        {
+            @Override
+            public void onBegin(Response response)
+            {
+            }
+
+            @Override
+            public boolean onHeader(Response response, HttpField field)
+            {
+                return true;
+            }
+
+            @Override
+            public void onHeaders(Response response)
+            {
+            }
+
+            @Override
+            public void onContent(Response response, ByteBuffer content)
+            {
+            }
+
+            @Override
+            public void onContent(Response response, ByteBuffer content, Callback callback)
+            {
+                try
+                {
+                    onContent(response, content);
+                    callback.succeeded();
+                }
+                catch (Exception x)
+                {
+                    callback.failed(x);
+                }
+            }
+
+            @Override
+            public void onSuccess(Response response)
+            {
+            }
+
+            @Override
+            public void onFailure(Response response, Throwable failure)
+            {
+            }
+
+            @Override
+            public void onComplete(Result result)
+            {
+            }
+        }
+    }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/api/Result.java b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Result.java
new file mode 100644
index 0000000..a782bd8
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Result.java
@@ -0,0 +1,120 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client.api;
+
+/**
+ * The result of a request / response exchange, containing the {@link Request}, the {@link Response}
+ * and eventual failures of either.
+ */
+public class Result
+{
+    private final Request request;
+    private final Throwable requestFailure;
+    private final Response response;
+    private final Throwable responseFailure;
+
+    public Result(Request request, Response response)
+    {
+        this(request, null, response, null);
+    }
+
+    public Result(Request request, Response response, Throwable responseFailure)
+    {
+        this(request, null, response, responseFailure);
+    }
+
+    public Result(Request request, Throwable requestFailure, Response response)
+    {
+        this(request, requestFailure, response, null);
+    }
+
+    public Result(Request request, Throwable requestFailure, Response response, Throwable responseFailure)
+    {
+        this.request = request;
+        this.requestFailure = requestFailure;
+        this.response = response;
+        this.responseFailure = responseFailure;
+    }
+
+    /**
+     * @return the request object
+     */
+    public Request getRequest()
+    {
+        return request;
+    }
+
+    /**
+     * @return the request failure, if any
+     */
+    public Throwable getRequestFailure()
+    {
+        return requestFailure;
+    }
+
+    /**
+     * @return the response object
+     */
+    public Response getResponse()
+    {
+        return response;
+    }
+
+    /**
+     * @return the response failure, if any
+     */
+    public Throwable getResponseFailure()
+    {
+        return responseFailure;
+    }
+
+    /**
+     * @return whether both the request and the response succeeded
+     */
+    public boolean isSucceeded()
+    {
+        return getFailure() == null;
+    }
+
+    /**
+     * @return whether either the response or the request failed
+     */
+    public boolean isFailed()
+    {
+        return !isSucceeded();
+    }
+
+    /**
+     * @return the response failure, if any, otherwise the request failure, if any
+     */
+    public Throwable getFailure()
+    {
+        return responseFailure != null ? responseFailure : requestFailure;
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s[%s > %s] %s",
+                Result.class.getSimpleName(),
+                request,
+                response,
+                getFailure());
+    }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/api/package-info.java b/jetty-client/src/main/java/org/eclipse/jetty/client/api/package-info.java
new file mode 100644
index 0000000..cd53d0e
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/api/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Client : API Classes
+ */
+package org.eclipse.jetty.client.api;
+
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpChannelOverHTTP.java b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpChannelOverHTTP.java
new file mode 100644
index 0000000..6578c44
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpChannelOverHTTP.java
@@ -0,0 +1,129 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client.http;
+
+import org.eclipse.jetty.client.HttpChannel;
+import org.eclipse.jetty.client.HttpExchange;
+import org.eclipse.jetty.client.HttpReceiver;
+import org.eclipse.jetty.client.HttpSender;
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.client.api.Result;
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpHeaderValue;
+import org.eclipse.jetty.http.HttpVersion;
+
+public class HttpChannelOverHTTP extends HttpChannel
+{
+    private final HttpConnectionOverHTTP connection;
+    private final HttpSenderOverHTTP sender;
+    private final HttpReceiverOverHTTP receiver;
+
+    public HttpChannelOverHTTP(HttpConnectionOverHTTP connection)
+    {
+        super(connection.getHttpDestination());
+        this.connection = connection;
+        this.sender = newHttpSender();
+        this.receiver = newHttpReceiver();
+    }
+
+    protected HttpSenderOverHTTP newHttpSender()
+    {
+        return new HttpSenderOverHTTP(this);
+    }
+
+    protected HttpReceiverOverHTTP newHttpReceiver()
+    {
+        return new HttpReceiverOverHTTP(this);
+    }
+
+    @Override
+    protected HttpSender getHttpSender()
+    {
+        return sender;
+    }
+
+    @Override
+    protected HttpReceiver getHttpReceiver()
+    {
+        return receiver;
+    }
+
+    public HttpConnectionOverHTTP getHttpConnection()
+    {
+        return connection;
+    }
+
+    @Override
+    public void send()
+    {
+        HttpExchange exchange = getHttpExchange();
+        if (exchange != null)
+            sender.send(exchange);
+    }
+
+    @Override
+    public void release()
+    {
+        connection.release();
+    }
+
+    public void receive()
+    {
+        receiver.receive();
+    }
+
+    @Override
+    public void exchangeTerminated(HttpExchange exchange, Result result)
+    {
+        super.exchangeTerminated(exchange, result);
+
+        Response response = result.getResponse();
+        HttpFields responseHeaders = response.getHeaders();
+        boolean close = result.isFailed() || receiver.isShutdown();
+
+        if (!close)
+        {
+            if (response.getVersion().compareTo(HttpVersion.HTTP_1_1) < 0)
+            {
+                // HTTP 1.0 must close the connection unless it has an explicit keep alive.
+                close = !responseHeaders.contains(HttpHeader.CONNECTION, HttpHeaderValue.KEEP_ALIVE.asString());
+            }
+            else
+            {
+                // HTTP 1.1 or greater closes only if it has an explicit close.
+                close = responseHeaders.contains(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString());
+            }
+        }
+
+        if (close)
+            connection.close();
+        else
+            release();
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s[send=%s,recv=%s]",
+                super.toString(),
+                sender,
+                receiver);
+    }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpClientTransportOverHTTP.java b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpClientTransportOverHTTP.java
new file mode 100644
index 0000000..fe6617f
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpClientTransportOverHTTP.java
@@ -0,0 +1,65 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client.http;
+
+import java.io.IOException;
+import java.util.Map;
+
+import org.eclipse.jetty.client.AbstractHttpClientTransport;
+import org.eclipse.jetty.client.HttpDestination;
+import org.eclipse.jetty.client.Origin;
+import org.eclipse.jetty.client.api.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.util.Promise;
+
+public class HttpClientTransportOverHTTP extends AbstractHttpClientTransport
+{
+    public HttpClientTransportOverHTTP()
+    {
+        this(Math.max(1, Runtime.getRuntime().availableProcessors() / 2));
+    }
+
+    public HttpClientTransportOverHTTP(int selectors)
+    {
+        super(selectors);
+    }
+
+    @Override
+    public HttpDestination newHttpDestination(Origin origin)
+    {
+        return new HttpDestinationOverHTTP(getHttpClient(), origin);
+    }
+
+    @Override
+    public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map<String, Object> context) throws IOException
+    {
+
+        HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY);
+        HttpConnectionOverHTTP connection = newHttpConnection(endPoint, destination);
+        @SuppressWarnings("unchecked")
+        Promise<Connection> promise = (Promise<Connection>)context.get(HTTP_CONNECTION_PROMISE_CONTEXT_KEY);
+        promise.succeeded(connection);
+        return connection;
+    }
+
+    protected HttpConnectionOverHTTP newHttpConnection(EndPoint endPoint, HttpDestination destination)
+    {
+        return new HttpConnectionOverHTTP(endPoint, destination);
+    }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpConnectionOverHTTP.java b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpConnectionOverHTTP.java
new file mode 100644
index 0000000..28c4c2e
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpConnectionOverHTTP.java
@@ -0,0 +1,221 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client.http;
+
+import java.nio.channels.AsynchronousCloseException;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.eclipse.jetty.client.HttpConnection;
+import org.eclipse.jetty.client.HttpDestination;
+import org.eclipse.jetty.client.HttpExchange;
+import org.eclipse.jetty.client.api.Connection;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.io.AbstractConnection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.Sweeper;
+
+public class HttpConnectionOverHTTP extends AbstractConnection implements Connection, Sweeper.Sweepable
+{
+    private static final Logger LOG = Log.getLogger(HttpConnectionOverHTTP.class);
+
+    private final AtomicBoolean closed = new AtomicBoolean();
+    private final AtomicInteger sweeps = new AtomicInteger();
+    private final Delegate delegate;
+    private final HttpChannelOverHTTP channel;
+    private long idleTimeout;
+
+    public HttpConnectionOverHTTP(EndPoint endPoint, HttpDestination destination)
+    {
+        super(endPoint, destination.getHttpClient().getExecutor(), destination.getHttpClient().isDispatchIO());
+        this.delegate = new Delegate(destination);
+        this.channel = newHttpChannel();
+    }
+
+    protected HttpChannelOverHTTP newHttpChannel()
+    {
+        return new HttpChannelOverHTTP(this);
+    }
+
+    public HttpChannelOverHTTP getHttpChannel()
+    {
+        return channel;
+    }
+
+    public HttpDestinationOverHTTP getHttpDestination()
+    {
+        return (HttpDestinationOverHTTP)delegate.getHttpDestination();
+    }
+
+    @Override
+    public void send(Request request, Response.CompleteListener listener)
+    {
+        delegate.send(request, listener);
+    }
+
+    protected void send(HttpExchange exchange)
+    {
+        delegate.send(exchange);
+    }
+
+    @Override
+    public void onOpen()
+    {
+        super.onOpen();
+        fillInterested();
+    }
+
+    public boolean isClosed()
+    {
+        return closed.get();
+    }
+
+    @Override
+    protected boolean onReadTimeout()
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("{} idle timeout", this);
+        close(new TimeoutException());
+        return false;
+    }
+
+    @Override
+    public void onFillable()
+    {
+        HttpExchange exchange = channel.getHttpExchange();
+        if (exchange != null)
+        {
+            channel.receive();
+        }
+        else
+        {
+            // If there is no exchange, then could be either a remote close,
+            // or garbage bytes; in both cases we close the connection
+            close();
+        }
+    }
+
+    public void release()
+    {
+        // Restore idle timeout
+        getEndPoint().setIdleTimeout(idleTimeout);
+        getHttpDestination().release(this);
+    }
+
+    @Override
+    public void close()
+    {
+        close(new AsynchronousCloseException());
+    }
+
+    protected void close(Throwable failure)
+    {
+        if (softClose())
+        {
+            // First close then abort, to be sure that the connection cannot be reused
+            // from an onFailure() handler or by blocking code waiting for completion.
+            getHttpDestination().close(this);
+            getEndPoint().shutdownOutput();
+            if (LOG.isDebugEnabled())
+                LOG.debug("{} oshut", this);
+            getEndPoint().close();
+            if (LOG.isDebugEnabled())
+                LOG.debug("{} closed", this);
+
+            abort(failure);
+        }
+    }
+
+    public boolean softClose()
+    {
+        return closed.compareAndSet(false, true);
+    }
+
+    protected boolean abort(Throwable failure)
+    {
+        HttpExchange exchange = channel.getHttpExchange();
+        return exchange != null && exchange.getRequest().abort(failure);
+    }
+
+    @Override
+    public boolean sweep()
+    {
+        if (!closed.get())
+            return false;
+
+        if (sweeps.incrementAndGet() < 4)
+            return false;
+
+        return true;
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s@%h(l:%s <-> r:%s,closed=%b)[%s]",
+                getClass().getSimpleName(),
+                this,
+                getEndPoint().getLocalAddress(),
+                getEndPoint().getRemoteAddress(),
+                closed.get(),
+                channel);
+    }
+
+    private class Delegate extends HttpConnection
+    {
+        private Delegate(HttpDestination destination)
+        {
+            super(destination);
+        }
+
+        @Override
+        protected void send(HttpExchange exchange)
+        {
+            Request request = exchange.getRequest();
+            normalizeRequest(request);
+
+            // Save the old idle timeout to restore it
+            EndPoint endPoint = getEndPoint();
+            idleTimeout = endPoint.getIdleTimeout();
+            endPoint.setIdleTimeout(request.getIdleTimeout());
+
+            // One channel per connection, just delegate the send
+            if (channel.associate(exchange))
+                channel.send();
+            else
+                channel.release();
+        }
+
+        @Override
+        public void close()
+        {
+            HttpConnectionOverHTTP.this.close();
+        }
+
+        @Override
+        public String toString()
+        {
+            return HttpConnectionOverHTTP.this.toString();
+        }
+    }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpDestinationOverHTTP.java b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpDestinationOverHTTP.java
new file mode 100644
index 0000000..304ba96
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpDestinationOverHTTP.java
@@ -0,0 +1,38 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client.http;
+
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.HttpExchange;
+import org.eclipse.jetty.client.Origin;
+import org.eclipse.jetty.client.PoolingHttpDestination;
+
+public class HttpDestinationOverHTTP extends PoolingHttpDestination<HttpConnectionOverHTTP>
+{
+    public HttpDestinationOverHTTP(HttpClient client, Origin origin)
+    {
+        super(client, origin);
+    }
+
+    @Override
+    protected void send(HttpConnectionOverHTTP connection, HttpExchange exchange)
+    {
+        connection.send(exchange);
+    }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpReceiverOverHTTP.java b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpReceiverOverHTTP.java
new file mode 100644
index 0000000..d4d0378
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpReceiverOverHTTP.java
@@ -0,0 +1,317 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client.http;
+
+import java.io.EOFException;
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.HttpExchange;
+import org.eclipse.jetty.client.HttpReceiver;
+import org.eclipse.jetty.client.HttpResponse;
+import org.eclipse.jetty.client.HttpResponseException;
+import org.eclipse.jetty.http.HttpField;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpParser;
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.CompletableCallback;
+
+public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.ResponseHandler<ByteBuffer>
+{
+    private final HttpParser parser = new HttpParser(this);
+    private ByteBuffer buffer;
+    private boolean shutdown;
+
+    public HttpReceiverOverHTTP(HttpChannelOverHTTP channel)
+    {
+        super(channel);
+    }
+
+    @Override
+    public HttpChannelOverHTTP getHttpChannel()
+    {
+        return (HttpChannelOverHTTP)super.getHttpChannel();
+    }
+
+    private HttpConnectionOverHTTP getHttpConnection()
+    {
+        return getHttpChannel().getHttpConnection();
+    }
+
+    protected ByteBuffer getResponseBuffer()
+    {
+        return buffer;
+    }
+
+    public void receive()
+    {
+        if (buffer == null)
+            acquireBuffer();
+        process();
+    }
+
+    private void acquireBuffer()
+    {
+        HttpClient client = getHttpDestination().getHttpClient();
+        ByteBufferPool bufferPool = client.getByteBufferPool();
+        buffer = bufferPool.acquire(client.getResponseBufferSize(), true);
+    }
+
+    private void releaseBuffer()
+    {
+        if (buffer == null)
+            throw new IllegalStateException();
+        if (BufferUtil.hasContent(buffer))
+            throw new IllegalStateException();
+        HttpClient client = getHttpDestination().getHttpClient();
+        ByteBufferPool bufferPool = client.getByteBufferPool();
+        bufferPool.release(buffer);
+        buffer = null;
+    }
+
+    private void process()
+    {
+        try
+        {
+            HttpConnectionOverHTTP connection = getHttpConnection();
+            EndPoint endPoint = connection.getEndPoint();
+            while (true)
+            {
+                // Connection may be closed in a parser callback.
+                if (connection.isClosed())
+                {
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("{} closed", connection);
+                    releaseBuffer();
+                    return;
+                }
+
+                if (parse())
+                    return;
+
+                int read = endPoint.fill(buffer);
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Read {} bytes {} from {}", read, BufferUtil.toDetailString(buffer), endPoint);
+
+                if (read > 0)
+                {
+                    if (parse())
+                        return;
+                }
+                else if (read == 0)
+                {
+                    releaseBuffer();
+                    fillInterested();
+                    return;
+                }
+                else
+                {
+                    releaseBuffer();
+                    shutdown();
+                    return;
+                }
+            }
+        }
+        catch (Throwable x)
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug(x);
+            BufferUtil.clear(buffer);
+            if (buffer != null)
+                releaseBuffer();
+            failAndClose(x);
+        }
+    }
+
+    /**
+     * Parses a HTTP response in the receivers buffer.
+     *
+     * @return true to indicate that parsing should be interrupted (and will be resumed by another thread).
+     */
+    private boolean parse()
+    {
+        while (true)
+        {
+            // Must parse even if the buffer is fully consumed, to allow the
+            // parser to advance from asynchronous content to response complete.
+            boolean handle = parser.parseNext(buffer);
+            if (LOG.isDebugEnabled())
+                LOG.debug("Parsed {}, remaining {} {}", handle, buffer.remaining(), parser);
+            if (handle || !buffer.hasRemaining())
+                return handle;
+        }
+    }
+
+    protected void fillInterested()
+    {
+        getHttpChannel().getHttpConnection().fillInterested();
+    }
+
+    private void shutdown()
+    {
+        // Mark this receiver as shutdown, so that we can
+        // close the connection when the exchange terminates.
+        // We cannot close the connection from here because
+        // the request may still be in process.
+        shutdown = true;
+
+        // Shutting down the parser may invoke messageComplete() or earlyEOF().
+        // In case of content delimited by EOF, without a Connection: close
+        // header, the connection will be closed at exchange termination
+        // thanks to the flag we have set above.
+        parser.atEOF();
+        parser.parseNext(BufferUtil.EMPTY_BUFFER);
+    }
+
+    protected boolean isShutdown()
+    {
+        return shutdown;
+    }
+
+    @Override
+    public int getHeaderCacheSize()
+    {
+        // TODO get from configuration
+        return 256;
+    }
+
+    @Override
+    public boolean startResponse(HttpVersion version, int status, String reason)
+    {
+        HttpExchange exchange = getHttpExchange();
+        if (exchange == null)
+            return false;
+
+        String method = exchange.getRequest().getMethod();
+        parser.setHeadResponse(HttpMethod.HEAD.is(method) || HttpMethod.CONNECT.is(method));
+        exchange.getResponse().version(version).status(status).reason(reason);
+
+        responseBegin(exchange);
+        return false;
+    }
+
+    @Override
+    public boolean parsedHeader(HttpField field)
+    {
+        HttpExchange exchange = getHttpExchange();
+        if (exchange == null)
+            return false;
+
+        responseHeader(exchange, field);
+        return false;
+    }
+
+    @Override
+    public boolean headerComplete()
+    {
+        HttpExchange exchange = getHttpExchange();
+        if (exchange == null)
+            return false;
+
+        responseHeaders(exchange);
+        return false;
+    }
+
+    @Override
+    public boolean content(ByteBuffer buffer)
+    {
+        HttpExchange exchange = getHttpExchange();
+        if (exchange == null)
+            return false;
+
+        CompletableCallback callback = new CompletableCallback()
+        {
+            @Override
+            public void resume()
+            {
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Content consumed asynchronously, resuming processing");
+                process();
+            }
+
+            public void abort(Throwable x)
+            {
+                failAndClose(x);
+            }
+        };
+        responseContent(exchange, buffer, callback);
+        return callback.tryComplete();
+    }
+
+    @Override
+    public boolean messageComplete()
+    {
+        HttpExchange exchange = getHttpExchange();
+        if (exchange != null)
+            responseSuccess(exchange);
+        return false;
+    }
+
+    @Override
+    public void earlyEOF()
+    {
+        HttpExchange exchange = getHttpExchange();
+        HttpConnectionOverHTTP connection = getHttpConnection();
+        if (exchange == null)
+            connection.close();
+        else
+            failAndClose(new EOFException(String.valueOf(connection)));
+    }
+
+    @Override
+    public void badMessage(int status, String reason)
+    {
+        HttpExchange exchange = getHttpExchange();
+        if (exchange != null)
+        {
+            HttpResponse response = exchange.getResponse();
+            response.status(status).reason(reason);
+            failAndClose(new HttpResponseException("HTTP protocol violation: bad response on " + getHttpConnection(), response));
+        }
+    }
+
+    @Override
+    protected void reset()
+    {
+        super.reset();
+        parser.reset();
+    }
+
+    @Override
+    protected void dispose()
+    {
+        super.dispose();
+        parser.close();
+    }
+
+    private void failAndClose(Throwable failure)
+    {
+        if (responseFailure(failure))
+            getHttpConnection().close(failure);
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s[%s]", super.toString(), parser);
+    }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpSenderOverHTTP.java b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpSenderOverHTTP.java
new file mode 100644
index 0000000..dac4962
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpSenderOverHTTP.java
@@ -0,0 +1,250 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client.http;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.HttpContent;
+import org.eclipse.jetty.client.HttpExchange;
+import org.eclipse.jetty.client.HttpRequestException;
+import org.eclipse.jetty.client.HttpSender;
+import org.eclipse.jetty.client.api.ContentProvider;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.http.HttpGenerator;
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.util.Callback;
+
+public class HttpSenderOverHTTP extends HttpSender
+{
+    private final HttpGenerator generator = new HttpGenerator();
+
+    public HttpSenderOverHTTP(HttpChannelOverHTTP channel)
+    {
+        super(channel);
+    }
+
+    @Override
+    public HttpChannelOverHTTP getHttpChannel()
+    {
+        return (HttpChannelOverHTTP)super.getHttpChannel();
+    }
+
+    @Override
+    protected void sendHeaders(HttpExchange exchange, HttpContent content, Callback callback)
+    {
+        Request request = exchange.getRequest();
+        ContentProvider requestContent = request.getContent();
+        long contentLength = requestContent == null ? -1 : requestContent.getLength();
+        String path = request.getPath();
+        String query = request.getQuery();
+        if (query != null)
+            path += "?" + query;
+        HttpGenerator.RequestInfo requestInfo = new HttpGenerator.RequestInfo(request.getVersion(), request.getHeaders(), contentLength, request.getMethod(), path);
+
+        try
+        {
+            HttpClient client = getHttpChannel().getHttpDestination().getHttpClient();
+            ByteBufferPool bufferPool = client.getByteBufferPool();
+            ByteBuffer header = bufferPool.acquire(client.getRequestBufferSize(), false);
+            ByteBuffer chunk = null;
+
+            ByteBuffer contentBuffer = null;
+            boolean lastContent = false;
+            if (!expects100Continue(request))
+            {
+                content.advance();
+                contentBuffer = content.getByteBuffer();
+                lastContent = content.isLast();
+            }
+            while (true)
+            {
+                HttpGenerator.Result result = generator.generateRequest(requestInfo, header, chunk, contentBuffer, lastContent);
+                switch (result)
+                {
+                    case NEED_CHUNK:
+                    {
+                        chunk = bufferPool.acquire(HttpGenerator.CHUNK_SIZE, false);
+                        break;
+                    }
+                    case FLUSH:
+                    {
+                        int size = 1;
+                        boolean hasChunk = chunk != null;
+                        if (hasChunk)
+                            ++size;
+                        boolean hasContent = contentBuffer != null;
+                        if (hasContent)
+                            ++size;
+                        ByteBuffer[] toWrite = new ByteBuffer[size];
+                        ByteBuffer[] toRecycle = new ByteBuffer[hasChunk ? 2 : 1];
+                        toWrite[0] = header;
+                        toRecycle[0] = header;
+                        if (hasChunk)
+                        {
+                            toWrite[1] = chunk;
+                            toRecycle[1] = chunk;
+                        }
+                        if (hasContent)
+                            toWrite[toWrite.length - 1] = contentBuffer;
+                        EndPoint endPoint = getHttpChannel().getHttpConnection().getEndPoint();
+                        endPoint.write(new ByteBufferRecyclerCallback(callback, bufferPool, toRecycle), toWrite);
+                        return;
+                    }
+                    case DONE:
+                    {
+                        // The headers have already been generated, perhaps by a concurrent abort.
+                        callback.failed(new HttpRequestException("Could not generate headers", request));
+                        return;
+                    }
+                    default:
+                    {
+                        callback.failed(new IllegalStateException(result.toString()));
+                        return;
+                    }
+                }
+            }
+        }
+        catch (Throwable x)
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug(x);
+            callback.failed(x);
+        }
+    }
+
+    @Override
+    protected void sendContent(HttpExchange exchange, HttpContent content, Callback callback)
+    {
+        try
+        {
+            HttpClient client = getHttpChannel().getHttpDestination().getHttpClient();
+            ByteBufferPool bufferPool = client.getByteBufferPool();
+            ByteBuffer chunk = null;
+            while (true)
+            {
+                ByteBuffer contentBuffer = content.getByteBuffer();
+                boolean lastContent = content.isLast();
+                HttpGenerator.Result result = generator.generateRequest(null, null, chunk, contentBuffer, lastContent);
+                switch (result)
+                {
+                    case NEED_CHUNK:
+                    {
+                        chunk = bufferPool.acquire(HttpGenerator.CHUNK_SIZE, false);
+                        break;
+                    }
+                    case FLUSH:
+                    {
+                        EndPoint endPoint = getHttpChannel().getHttpConnection().getEndPoint();
+                        if (chunk != null)
+                            endPoint.write(new ByteBufferRecyclerCallback(callback, bufferPool, chunk), chunk, contentBuffer);
+                        else
+                            endPoint.write(callback, contentBuffer);
+                        return;
+                    }
+                    case SHUTDOWN_OUT:
+                    {
+                        shutdownOutput();
+                        break;
+                    }
+                    case CONTINUE:
+                    {
+                        break;
+                    }
+                    case DONE:
+                    {
+                        assert generator.isEnd();
+                        callback.succeeded();
+                        return;
+                    }
+                    default:
+                    {
+                        throw new IllegalStateException();
+                    }
+                }
+            }
+        }
+        catch (Exception x)
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug(x);
+            callback.failed(x);
+        }
+    }
+
+    @Override
+    protected void reset()
+    {
+        generator.reset();
+        super.reset();
+    }
+
+    @Override
+    protected void dispose()
+    {
+        generator.abort();
+        super.dispose();
+        shutdownOutput();
+    }
+
+    private void shutdownOutput()
+    {
+        getHttpChannel().getHttpConnection().getEndPoint().shutdownOutput();
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s[%s]", super.toString(), generator);
+    }
+
+    private class ByteBufferRecyclerCallback implements Callback
+    {
+        private final Callback callback;
+        private final ByteBufferPool pool;
+        private final ByteBuffer[] buffers;
+
+        private ByteBufferRecyclerCallback(Callback callback, ByteBufferPool pool, ByteBuffer... buffers)
+        {
+            this.callback = callback;
+            this.pool = pool;
+            this.buffers = buffers;
+        }
+
+        @Override
+        public void succeeded()
+        {
+            for (ByteBuffer buffer : buffers)
+            {
+                assert !buffer.hasRemaining();
+                pool.release(buffer);
+            }
+            callback.succeeded();
+        }
+
+        @Override
+        public void failed(Throwable x)
+        {
+            for (ByteBuffer buffer : buffers)
+                pool.release(buffer);
+            callback.failed(x);
+        }
+    }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/package-info.java b/jetty-client/src/main/java/org/eclipse/jetty/client/package-info.java
new file mode 100644
index 0000000..7b5ebf7
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/package-info.java
@@ -0,0 +1,51 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Client : Implementation and Core Classes
+ * 
+ * This package provides APIs, utility classes and an implementation of an asynchronous HTTP client.
+ * <p />
+ * The core class is {@link org.eclipse.jetty.client.api.HttpClient}, which acts as a central configuration object (for example
+ * for {@link org.eclipse.jetty.client.api.HttpClient#setIdleTimeout(long) idle timeouts}, {@link org.eclipse.jetty.client.api.HttpClient#setMaxConnectionsPerDestination(int)
+ * max connections per destination}, etc.) and as a factory for {@link Request} objects.
+ * <p />
+ * The HTTP protocol is based on the request/response paradigm, a unit that in this implementation is called
+ * <em>exchange</em> and is represented by {@link org.eclipse.jetty.client.api.HttpExchange}.
+ * An initial request may trigger a sequence of exchanges with one or more servers, called a <em>conversation</em>
+ * and represented by {@link org.eclipse.jetty.client.api.HttpConversation}. A typical example of a conversation is a redirect, where
+ * upon a request for a resource URI, the server replies with a redirect (for example with the 303 status code)
+ * to another URI. This conversation is made of a first exchange made of the original request and its 303 response,
+ * and of a second exchange made of the request for the new URI and its 200 response.
+ * <p />
+ * {@link org.eclipse.jetty.client.api.HttpClient} holds a number of {@link org.eclipse.jetty.client.api.HttpDestination destinations}, which in turn hold a number of
+ * pooled {@link org.eclipse.jetty.client.api.HttpConnection connections}.
+ * <p />
+ * When a request is sent, its exchange is associated to a connection, either taken from an idle queue or created
+ * anew, and when both the request and response are completed, the exchange is disassociated from the connection.
+ * Conversations may span multiple connections on different destinations, and therefore are maintained at the
+ * {@link org.eclipse.jetty.client.api.HttpClient} level.
+ * <p />
+ * Applications may decide to send the request and wait for the response in a blocking way, using
+ * {@link org.eclipse.jetty.client.api.Request#send()}.
+ * Alternatively, application may ask to be notified of response events asynchronously, using
+ * {@link org.eclipse.jetty.client.api.Request#send(Response.Listener)}.
+ */
+package org.eclipse.jetty.client;
+
+
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/security/Authentication.java b/jetty-client/src/main/java/org/eclipse/jetty/client/security/Authentication.java
deleted file mode 100644
index fa8742d..0000000
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/security/Authentication.java
+++ /dev/null
@@ -1,33 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client.security;
-
-
-
-import java.io.IOException;
-
-import org.eclipse.jetty.client.HttpExchange;
-
-/**
- * Simple authentication interface that sets required fields on the exchange.
- */
-public interface Authentication
-{
-    public void setCredentials( HttpExchange exchange) throws IOException;
-}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/security/BasicAuthentication.java b/jetty-client/src/main/java/org/eclipse/jetty/client/security/BasicAuthentication.java
deleted file mode 100644
index c81195b..0000000
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/security/BasicAuthentication.java
+++ /dev/null
@@ -1,57 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client.security;
-
-
-import java.io.IOException;
-
-import org.eclipse.jetty.client.HttpExchange;
-import org.eclipse.jetty.http.HttpHeaders;
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.ByteArrayBuffer;
-import org.eclipse.jetty.util.B64Code;
-import org.eclipse.jetty.util.StringUtil;
-
-/**
- * Sets authentication headers for BASIC authentication challenges
- * 
- * 
- */
-public class BasicAuthentication implements Authentication
-{
-    private Buffer _authorization;
-    
-    public BasicAuthentication(Realm realm) throws IOException
-    {
-        String authenticationString = "Basic " + B64Code.encode( realm.getPrincipal() + ":" + realm.getCredentials(), StringUtil.__ISO_8859_1);
-        _authorization= new ByteArrayBuffer(authenticationString);
-    }
-    
-    /**
-     * BASIC authentication is of the form
-     * 
-     * encoded credentials are of the form: username:password
-     * 
-     * 
-     */
-    public void setCredentials( HttpExchange exchange ) throws IOException
-    {
-        exchange.setRequestHeader( HttpHeaders.AUTHORIZATION_BUFFER, _authorization);
-    }
-}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/security/DigestAuthentication.java b/jetty-client/src/main/java/org/eclipse/jetty/client/security/DigestAuthentication.java
deleted file mode 100644
index 22092c2..0000000
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/security/DigestAuthentication.java
+++ /dev/null
@@ -1,141 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client.security;
-
-
-import java.io.IOException;
-import java.security.MessageDigest;
-import java.util.Map;
-
-import org.eclipse.jetty.client.HttpExchange;
-import org.eclipse.jetty.http.HttpHeaders;
-import org.eclipse.jetty.util.StringUtil;
-import org.eclipse.jetty.util.TypeUtil;
-
-public class DigestAuthentication implements Authentication
-{
-    private static final String NC = "00000001";
-    Realm securityRealm;
-    Map details;
-    
-    public DigestAuthentication(Realm realm, Map details)
-    {
-        this.securityRealm=realm;
-        this.details=details;
-    }
-    
-
-    public void setCredentials( HttpExchange exchange ) 
-    throws IOException
-    {        
-        StringBuilder buffer = new StringBuilder().append("Digest");
-        
-        buffer.append(" ").append("username").append('=').append('"').append(securityRealm.getPrincipal()).append('"');
-        
-        buffer.append(", ").append("realm").append('=').append('"').append(String.valueOf(details.get("realm"))).append('"');
-        
-        buffer.append(", ").append("nonce").append('=').append('"').append(String.valueOf(details.get("nonce"))).append('"');
-        
-        buffer.append(", ").append("uri").append('=').append('"').append(exchange.getURI()).append('"');
-        
-        buffer.append(", ").append("algorithm").append('=').append(String.valueOf(details.get("algorithm")));
-        
-        String cnonce = newCnonce(exchange, securityRealm, details);
-        
-        buffer.append(", ").append("response").append('=').append('"').append(newResponse(cnonce, 
-                exchange, securityRealm, details)).append('"');
-        
-        buffer.append(", ").append("qop").append('=').append(String.valueOf(details.get("qop")));
-        
-
-        buffer.append(", ").append("nc").append('=').append(NC);
-        
-        buffer.append(", ").append("cnonce").append('=').append('"').append(cnonce).append('"');
-        
-        exchange.setRequestHeader( HttpHeaders.AUTHORIZATION, 
-                new String(buffer.toString().getBytes(StringUtil.__ISO_8859_1)));
-    }
-    
-    protected String newResponse(String cnonce, HttpExchange exchange, Realm securityRealm, Map details)
-    {        
-        try{
-            MessageDigest md = MessageDigest.getInstance("MD5");
-            
-            // calc A1 digest
-            md.update(securityRealm.getPrincipal().getBytes(StringUtil.__ISO_8859_1));
-            md.update((byte)':');
-            md.update(String.valueOf(details.get("realm")).getBytes(StringUtil.__ISO_8859_1));
-            md.update((byte)':');
-            md.update(securityRealm.getCredentials().getBytes(StringUtil.__ISO_8859_1));
-            byte[] ha1 = md.digest();
-            // calc A2 digest
-            md.reset();
-            md.update(exchange.getMethod().getBytes(StringUtil.__ISO_8859_1));
-            md.update((byte)':');
-            md.update(exchange.getURI().getBytes(StringUtil.__ISO_8859_1));
-            byte[] ha2=md.digest();
-            
-            md.update(TypeUtil.toString(ha1,16).getBytes(StringUtil.__ISO_8859_1));
-            md.update((byte)':');
-            md.update(String.valueOf(details.get("nonce")).getBytes(StringUtil.__ISO_8859_1));
-            md.update((byte)':');
-            md.update(NC.getBytes(StringUtil.__ISO_8859_1));
-            md.update((byte)':');
-            md.update(cnonce.getBytes(StringUtil.__ISO_8859_1));
-            md.update((byte)':');
-            md.update(String.valueOf(details.get("qop")).getBytes(StringUtil.__ISO_8859_1));
-            md.update((byte)':');
-            md.update(TypeUtil.toString(ha2,16).getBytes(StringUtil.__ISO_8859_1));
-            byte[] digest=md.digest();
-            
-            // check digest
-            return encode(digest);
-        }
-        catch(Exception e)
-        {
-            throw new RuntimeException(e);
-        }        
-    }
-    
-    protected String newCnonce(HttpExchange exchange, Realm securityRealm, Map details)
-    {
-        try
-        {
-            MessageDigest md = MessageDigest.getInstance("MD5");
-            byte[] b= md.digest(String.valueOf(System.currentTimeMillis()).getBytes(StringUtil.__ISO_8859_1));            
-            return encode(b);
-        }
-        catch(Exception e)
-        {
-            throw new RuntimeException(e);
-        }
-    }
-    
-    private static String encode(byte[] data)
-    {
-        StringBuilder buffer = new StringBuilder();
-        for (int i=0; i<data.length; i++) 
-        {
-            buffer.append(Integer.toHexString((data[i] & 0xf0) >>> 4));
-            buffer.append(Integer.toHexString(data[i] & 0x0f));
-        }
-        return buffer.toString();
-    }
-
-}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/security/HashRealmResolver.java b/jetty-client/src/main/java/org/eclipse/jetty/client/security/HashRealmResolver.java
deleted file mode 100644
index 07d0573..0000000
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/security/HashRealmResolver.java
+++ /dev/null
@@ -1,45 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client.security;
-
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.Map;
-
-import org.eclipse.jetty.client.HttpDestination;
-
-public class HashRealmResolver implements RealmResolver
-{
-    private Map<String, Realm>_realmMap;  
-    
-    public void addSecurityRealm( Realm realm )
-    {
-        if (_realmMap == null)
-        {
-            _realmMap = new HashMap<String, Realm>();
-        }
-        _realmMap.put( realm.getId(), realm );
-    }
-    
-    public Realm getRealm( String realmName, HttpDestination destination, String path ) throws IOException
-    {
-        return _realmMap.get( realmName );
-    }
-
-}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/security/ProxyAuthorization.java b/jetty-client/src/main/java/org/eclipse/jetty/client/security/ProxyAuthorization.java
deleted file mode 100644
index 18a5b99..0000000
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/security/ProxyAuthorization.java
+++ /dev/null
@@ -1,57 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client.security;
-
-
-import java.io.IOException;
-
-import org.eclipse.jetty.client.HttpExchange;
-import org.eclipse.jetty.http.HttpHeaders;
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.ByteArrayBuffer;
-import org.eclipse.jetty.util.B64Code;
-import org.eclipse.jetty.util.StringUtil;
-
-/**
- * Sets proxy authentication headers for BASIC authentication challenges
- * 
- * 
- */
-public class ProxyAuthorization implements Authentication
-{
-    private Buffer _authorization;
-    
-    public ProxyAuthorization(String username,String password) throws IOException
-    {
-        String authenticationString = "Basic " + B64Code.encode( username + ":" + password, StringUtil.__ISO_8859_1);
-        _authorization= new ByteArrayBuffer(authenticationString);
-    }
-    
-    /**
-     * BASIC proxy authentication is of the form
-     * 
-     * encoded credentials are of the form: username:password
-     * 
-     * 
-     */
-    public void setCredentials( HttpExchange exchange ) throws IOException
-    {
-        exchange.setRequestHeader( HttpHeaders.PROXY_AUTHORIZATION_BUFFER, _authorization);
-    }
-}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/security/Realm.java b/jetty-client/src/main/java/org/eclipse/jetty/client/security/Realm.java
deleted file mode 100644
index 59a90aa..0000000
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/security/Realm.java
+++ /dev/null
@@ -1,32 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client.security;
-
-/**
- * Simple security realm interface.
- */
-public interface Realm
-{
-    public String getId();
-
-    public String getPrincipal();
-
-    public String getCredentials();
-
-}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/security/RealmResolver.java b/jetty-client/src/main/java/org/eclipse/jetty/client/security/RealmResolver.java
deleted file mode 100644
index 97a007f..0000000
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/security/RealmResolver.java
+++ /dev/null
@@ -1,28 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client.security;
-
-import java.io.IOException;
-
-import org.eclipse.jetty.client.HttpDestination;
-
-public interface RealmResolver
-{
-    public Realm getRealm( String realmName, HttpDestination destination, String path ) throws IOException;   
-}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/security/SecurityListener.java b/jetty-client/src/main/java/org/eclipse/jetty/client/security/SecurityListener.java
deleted file mode 100644
index 809b3f3..0000000
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/security/SecurityListener.java
+++ /dev/null
@@ -1,276 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client.security;
-
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.StringTokenizer;
-
-import org.eclipse.jetty.client.HttpDestination;
-import org.eclipse.jetty.client.HttpEventListenerWrapper;
-import org.eclipse.jetty.client.HttpExchange;
-import org.eclipse.jetty.http.HttpHeaders;
-import org.eclipse.jetty.http.HttpStatus;
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.util.StringUtil;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-
-
-/**
- * SecurityListener
- * 
- * Allow for insertion of security dialog when performing an
- * HttpExchange.
- */
-public class SecurityListener extends HttpEventListenerWrapper
-{
-    private static final Logger LOG = Log.getLogger(SecurityListener.class);
-	
-    private HttpDestination _destination;
-    private HttpExchange _exchange;
-    private boolean _requestComplete;
-    private boolean _responseComplete;  
-    private boolean _needIntercept;
-    
-    private int _attempts = 0; // TODO remember to settle on winning solution
-
-    public SecurityListener(HttpDestination destination, HttpExchange ex)
-    {
-        // Start of sending events through to the wrapped listener
-        // Next decision point is the onResponseStatus
-        super(ex.getEventListener(),true);
-        _destination=destination;
-        _exchange=ex;
-    }
-    
-    
-    /**
-     * scrapes an authentication type from the authString
-     * 
-     * @param authString
-     * @return the authentication type
-     */
-    protected String scrapeAuthenticationType( String authString )
-    {
-        String authType;
-
-        if ( authString.indexOf( " " ) == -1 )
-        {
-            authType = authString.toString().trim();
-        }
-        else
-        {
-            String authResponse = authString.toString();
-            authType = authResponse.substring( 0, authResponse.indexOf( " " ) ).trim();
-        }
-        return authType;
-    }
-    
-    /**
-     * scrapes a set of authentication details from the authString
-     * 
-     * @param authString
-     * @return the authentication details
-     */
-    protected Map<String, String> scrapeAuthenticationDetails( String authString )
-    {
-        Map<String, String> authenticationDetails = new HashMap<String, String>();
-        authString = authString.substring( authString.indexOf( " " ) + 1, authString.length() );
-        StringTokenizer strtok = new StringTokenizer( authString, ",");
-        
-        while ( strtok.hasMoreTokens() )
-        {
-            String token = strtok.nextToken();
-            String[] pair = token.split( "=" );
-            
-            // authentication details ought to come in two parts, if not then just skip
-            if ( pair.length == 2 )
-            {
-                String itemName = pair[0].trim();
-                String itemValue = pair[1].trim();
-                
-                itemValue = StringUtil.unquote( itemValue );
-                
-                authenticationDetails.put( itemName, itemValue );
-            }    
-            else
-            {
-                LOG.debug("SecurityListener: missed scraping authentication details - " + token );
-            }
-        }
-        return authenticationDetails;
-    }
-
-  
-    @Override
-    public void onResponseStatus( Buffer version, int status, Buffer reason )
-        throws IOException
-    {
-        if (LOG.isDebugEnabled())
-            LOG.debug("SecurityListener:Response Status: " + status );
-
-        if ( status == HttpStatus.UNAUTHORIZED_401 && _attempts<_destination.getHttpClient().maxRetries()) 
-        {
-            // Let's absorb events until we have done some retries
-            setDelegatingResponses(false);
-            _needIntercept = true;
-        }
-        else 
-        {
-            setDelegatingResponses(true);
-            setDelegatingRequests(true);
-            _needIntercept = false;
-        }
-        super.onResponseStatus(version,status,reason);
-    }
-
-
-    @Override
-    public void onResponseHeader( Buffer name, Buffer value )
-        throws IOException
-    {
-        if (LOG.isDebugEnabled())
-            LOG.debug( "SecurityListener:Header: " + name.toString() + " / " + value.toString() );
-        
-        
-        if (!isDelegatingResponses())
-        {
-            int header = HttpHeaders.CACHE.getOrdinal(name);
-            switch (header)
-            {
-                case HttpHeaders.WWW_AUTHENTICATE_ORDINAL:
-
-                    // TODO don't hard code this bit.
-                    String authString = value.toString();
-                    String type = scrapeAuthenticationType( authString );                  
-
-                    // TODO maybe avoid this map creation
-                    Map<String,String> details = scrapeAuthenticationDetails( authString );
-                    String pathSpec="/"; // TODO work out the real path spec
-                    RealmResolver realmResolver = _destination.getHttpClient().getRealmResolver();
-                    
-                    if ( realmResolver == null )
-                    {
-                        break;
-                    }
-                    
-                    Realm realm = realmResolver.getRealm( details.get("realm"), _destination, pathSpec ); // TODO work our realm correctly 
-                    
-                    if ( realm == null )
-                    {
-                        LOG.warn( "Unknown Security Realm: " + details.get("realm") );
-                    }
-                    else if ("digest".equalsIgnoreCase(type))
-                    {
-                        _destination.addAuthorization("/",new DigestAuthentication(realm,details));
-                        
-                    }
-                    else if ("basic".equalsIgnoreCase(type))
-                    {
-                        _destination.addAuthorization(pathSpec,new BasicAuthentication(realm));
-                    }
-                    
-                    break;
-            }
-        }
-        super.onResponseHeader(name,value);
-    }
-    
-
-    @Override
-    public void onRequestComplete() throws IOException
-    {
-        _requestComplete = true;
-
-        if (_needIntercept)
-        {
-            if (_requestComplete && _responseComplete)
-            {
-               if (LOG.isDebugEnabled())
-                   LOG.debug("onRequestComplete, Both complete: Resending from onResponseComplete "+_exchange); 
-                _responseComplete = false;
-                _requestComplete = false;
-                setDelegatingRequests(true);
-                setDelegatingResponses(true);
-                _destination.resend(_exchange);  
-            } 
-            else
-            {
-                if (LOG.isDebugEnabled())
-                    LOG.debug("onRequestComplete, Response not yet complete onRequestComplete, calling super for "+_exchange);
-                super.onRequestComplete(); 
-            }
-        }
-        else
-        {
-            if (LOG.isDebugEnabled())
-                LOG.debug("onRequestComplete, delegating to super with Request complete="+_requestComplete+", response complete="+_responseComplete+" "+_exchange);
-            super.onRequestComplete();
-        }
-    }
-
-
-    @Override
-    public void onResponseComplete() throws IOException
-    {   
-        _responseComplete = true;
-        if (_needIntercept)
-        {  
-            if (_requestComplete && _responseComplete)
-            {              
-                if (LOG.isDebugEnabled())
-                    LOG.debug("onResponseComplete, Both complete: Resending from onResponseComplete"+_exchange);
-                _responseComplete = false;
-                _requestComplete = false;
-                setDelegatingResponses(true);
-                setDelegatingRequests(true);
-                _destination.resend(_exchange); 
-
-            }
-            else
-            {
-               if (LOG.isDebugEnabled())
-                   LOG.debug("onResponseComplete, Request not yet complete from onResponseComplete,  calling super "+_exchange);
-                super.onResponseComplete(); 
-            }
-        }
-        else
-        {
-            if (LOG.isDebugEnabled())
-                LOG.debug("OnResponseComplete, delegating to super with Request complete="+_requestComplete+", response complete="+_responseComplete+" "+_exchange);
-            super.onResponseComplete();  
-        }
-    }
-
-    @Override
-    public void onRetry()
-    {
-        _attempts++;
-        setDelegatingRequests(true);
-        setDelegatingResponses(true);
-        _requestComplete=false;
-        _responseComplete=false;
-        _needIntercept=false;
-        super.onRetry();
-    }  
-    
-    
-}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/security/SimpleRealmResolver.java b/jetty-client/src/main/java/org/eclipse/jetty/client/security/SimpleRealmResolver.java
deleted file mode 100644
index f1e3f50..0000000
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/security/SimpleRealmResolver.java
+++ /dev/null
@@ -1,44 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client.security;
-
-import java.io.IOException;
-
-import org.eclipse.jetty.client.HttpDestination;
-
-/**
- * Simple Realm Resolver.
- * <p> A Realm Resolver that wraps a single realm.
- * 
- *
- */
-public class SimpleRealmResolver implements RealmResolver
-{
-    private Realm _realm;
-    
-    public SimpleRealmResolver( Realm realm )
-    {
-        _realm=realm;
-    }
-    
-    public Realm getRealm( String realmName, HttpDestination destination, String path ) throws IOException
-    {
-        return _realm;
-    }
-}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/AbstractTypedContentProvider.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/AbstractTypedContentProvider.java
new file mode 100644
index 0000000..6c03aca
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/AbstractTypedContentProvider.java
@@ -0,0 +1,37 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client.util;
+
+import org.eclipse.jetty.client.api.ContentProvider;
+
+public abstract class AbstractTypedContentProvider implements ContentProvider.Typed
+{
+    private final String contentType;
+
+    protected AbstractTypedContentProvider(String contentType)
+    {
+        this.contentType = contentType;
+    }
+
+    @Override
+    public String getContentType()
+    {
+        return contentType;
+    }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/BasicAuthentication.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/BasicAuthentication.java
new file mode 100644
index 0000000..7c6a7a0
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/BasicAuthentication.java
@@ -0,0 +1,109 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client.util;
+
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+
+import org.eclipse.jetty.client.api.Authentication;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.util.Attributes;
+import org.eclipse.jetty.util.B64Code;
+
+/**
+ * Implementation of the HTTP "Basic" authentication defined in RFC 2617.
+ * <p />
+ * Applications should create objects of this class and add them to the
+ * {@link AuthenticationStore} retrieved from the {@link HttpClient}
+ * via {@link HttpClient#getAuthenticationStore()}.
+ */
+public class BasicAuthentication implements Authentication
+{
+    private final URI uri;
+    private final String realm;
+    private final String user;
+    private final String password;
+
+    /**
+     * @param uri the URI to match for the authentication
+     * @param realm the realm to match for the authentication
+     * @param user the user that wants to authenticate
+     * @param password the password of the user
+     */
+    public BasicAuthentication(URI uri, String realm, String user, String password)
+    {
+        this.uri = uri;
+        this.realm = realm;
+        this.user = user;
+        this.password = password;
+    }
+
+    @Override
+    public boolean matches(String type, URI uri, String realm)
+    {
+        if (!"basic".equalsIgnoreCase(type))
+            return false;
+
+        if (!uri.toString().startsWith(this.uri.toString()))
+            return false;
+
+        return this.realm.equals(realm);
+    }
+
+    @Override
+    public Result authenticate(Request request, ContentResponse response, HeaderInfo headerInfo, Attributes context)
+    {
+        String value = "Basic " + B64Code.encode(user + ":" + password, StandardCharsets.ISO_8859_1);
+        return new BasicResult(headerInfo.getHeader(), uri, value);
+    }
+
+    private static class BasicResult implements Result
+    {
+        private final HttpHeader header;
+        private final URI uri;
+        private final String value;
+
+        public BasicResult(HttpHeader header, URI uri, String value)
+        {
+            this.header = header;
+            this.uri = uri;
+            this.value = value;
+        }
+
+        @Override
+        public URI getURI()
+        {
+            return uri;
+        }
+
+        @Override
+        public void apply(Request request)
+        {
+            request.header(header, value);
+        }
+
+        @Override
+        public String toString()
+        {
+            return String.format("Basic authentication result for %s", uri);
+        }
+    }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/BufferingResponseListener.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/BufferingResponseListener.java
new file mode 100644
index 0000000..ee071f3
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/BufferingResponseListener.java
@@ -0,0 +1,192 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client.util;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.Locale;
+
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.client.api.Response.Listener;
+import org.eclipse.jetty.client.api.Result;
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.util.BufferUtil;
+
+/**
+ * <p>Implementation of {@link Listener} that buffers the content up to a maximum length
+ * specified to the constructors.</p>
+ * <p>The content may be retrieved from {@link #onSuccess(Response)} or {@link #onComplete(Result)}
+ * via {@link #getContent()} or {@link #getContentAsString()}.</p>
+ */
+public abstract class BufferingResponseListener extends Listener.Adapter
+{
+    private final int maxLength;
+    private volatile ByteBuffer buffer;
+    private volatile String mediaType;
+    private volatile String encoding;
+
+    /**
+     * Creates an instance with a default maximum length of 2 MiB.
+     */
+    public BufferingResponseListener()
+    {
+        this(2 * 1024 * 1024);
+    }
+
+    /**
+     * Creates an instance with the given maximum length
+     *
+     * @param maxLength the maximum length of the content
+     */
+    public BufferingResponseListener(int maxLength)
+    {
+        this.maxLength = maxLength;
+    }
+
+    @Override
+    public void onHeaders(Response response)
+    {
+        super.onHeaders(response);
+
+        HttpFields headers = response.getHeaders();
+        long length = headers.getLongField(HttpHeader.CONTENT_LENGTH.asString());
+        if (length > maxLength)
+        {
+            response.abort(new IllegalArgumentException("Buffering capacity exceeded"));
+            return;
+        }
+
+        buffer = BufferUtil.allocate(length > 0 ? (int)length : 1024);
+
+        String contentType = headers.get(HttpHeader.CONTENT_TYPE);
+        if (contentType != null)
+        {
+            String media = contentType;
+
+            String charset = "charset=";
+            int index = contentType.toLowerCase(Locale.ENGLISH).indexOf(charset);
+            if (index > 0)
+            {
+                media = contentType.substring(0, index);
+                String encoding = contentType.substring(index + charset.length());
+                // Sometimes charsets arrive with an ending semicolon
+                int semicolon = encoding.indexOf(';');
+                if (semicolon > 0)
+                    encoding = encoding.substring(0, semicolon).trim();
+                this.encoding = encoding;
+            }
+
+            int semicolon = media.indexOf(';');
+            if (semicolon > 0)
+                media = media.substring(0, semicolon).trim();
+            this.mediaType = media;
+        }
+    }
+
+    @Override
+    public void onContent(Response response, ByteBuffer content)
+    {
+        int length = content.remaining();
+        if (length > BufferUtil.space(buffer))
+        {
+            int requiredCapacity = buffer == null ? 0 : buffer.capacity() + length;
+            if (requiredCapacity > maxLength)
+                response.abort(new IllegalArgumentException("Buffering capacity exceeded"));
+
+            int newCapacity = Math.min(Integer.highestOneBit(requiredCapacity) << 1, maxLength);
+            buffer = BufferUtil.ensureCapacity(buffer, newCapacity);
+        }
+        BufferUtil.append(buffer, content);
+    }
+
+    @Override
+    public abstract void onComplete(Result result);
+
+    public String getMediaType()
+    {
+        return mediaType;
+    }
+
+    public String getEncoding()
+    {
+        return encoding;
+    }
+
+    /**
+     * @return the content as bytes
+     * @see #getContentAsString()
+     */
+    public byte[] getContent()
+    {
+        if (buffer == null)
+            return new byte[0];
+        return BufferUtil.toArray(buffer);
+    }
+
+    /**
+     * @return the content as a string, using the "Content-Type" header to detect the encoding
+     * or defaulting to UTF-8 if the encoding could not be detected.
+     * @see #getContentAsString(String)
+     */
+    public String getContentAsString()
+    {
+        String encoding = this.encoding;
+        if (encoding == null)
+            return getContentAsString(StandardCharsets.UTF_8);
+        return getContentAsString(encoding);
+    }
+
+    /**
+     * @param encoding the encoding of the content bytes
+     * @return the content as a string, with the specified encoding
+     * @see #getContentAsString()
+     */
+    public String getContentAsString(String encoding)
+    {
+        if (buffer == null)
+            return null;
+        return BufferUtil.toString(buffer, Charset.forName(encoding));
+    }
+
+    /**
+     * @param encoding the encoding of the content bytes
+     * @return the content as a string, with the specified encoding
+     * @see #getContentAsString()
+     */
+    public String getContentAsString(Charset encoding)
+    {
+        if (buffer == null)
+            return null;
+        return BufferUtil.toString(buffer, encoding);
+    }
+    
+    /**
+     * @return Content as InputStream
+     */
+    public InputStream getContentAsInputStream()
+    {
+        if (buffer == null)
+            return new ByteArrayInputStream(new byte[]{});
+        return new ByteArrayInputStream(buffer.array(), buffer.arrayOffset(), buffer.remaining());
+    }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/ByteBufferContentProvider.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/ByteBufferContentProvider.java
new file mode 100644
index 0000000..e8e2d2e
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/ByteBufferContentProvider.java
@@ -0,0 +1,94 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client.util;
+
+import java.nio.ByteBuffer;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * A {@link ContentProvider} for {@link ByteBuffer}s.
+ * <p />
+ * The position and limit of the {@link ByteBuffer}s passed to the constructor are not modified,
+ * and each invocation of the {@link #iterator()} method returns a {@link ByteBuffer#slice() slice}
+ * of the original {@link ByteBuffer}.
+ */
+public class ByteBufferContentProvider extends AbstractTypedContentProvider
+{
+    private final ByteBuffer[] buffers;
+    private final int length;
+
+    public ByteBufferContentProvider(ByteBuffer... buffers)
+    {
+        this("application/octet-stream", buffers);
+    }
+
+    public ByteBufferContentProvider(String contentType, ByteBuffer... buffers)
+    {
+        super(contentType);
+        this.buffers = buffers;
+        int length = 0;
+        for (ByteBuffer buffer : buffers)
+            length += buffer.remaining();
+        this.length = length;
+    }
+
+    @Override
+    public long getLength()
+    {
+        return length;
+    }
+
+    @Override
+    public Iterator<ByteBuffer> iterator()
+    {
+        return new Iterator<ByteBuffer>()
+        {
+            private int index;
+
+            @Override
+            public boolean hasNext()
+            {
+                return index < buffers.length;
+            }
+
+            @Override
+            public ByteBuffer next()
+            {
+                try
+                {
+                    ByteBuffer buffer = buffers[index];
+                    buffers[index] = buffer.slice();
+                    ++index;
+                    return buffer;
+                }
+                catch (ArrayIndexOutOfBoundsException x)
+                {
+                    throw new NoSuchElementException();
+                }
+            }
+
+            @Override
+            public void remove()
+            {
+                throw new UnsupportedOperationException();
+            }
+        };
+    }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/BytesContentProvider.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/BytesContentProvider.java
new file mode 100644
index 0000000..56dd205
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/BytesContentProvider.java
@@ -0,0 +1,87 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client.util;
+
+import java.nio.ByteBuffer;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * A {@link ContentProvider} for byte arrays.
+ */
+public class BytesContentProvider extends AbstractTypedContentProvider
+{
+    private final byte[][] bytes;
+    private final long length;
+
+    public BytesContentProvider(byte[]... bytes)
+    {
+        this("application/octet-stream", bytes);
+    }
+
+    public BytesContentProvider(String contentType, byte[]... bytes)
+    {
+        super(contentType);
+        this.bytes = bytes;
+        long length = 0;
+        for (byte[] buffer : bytes)
+            length += buffer.length;
+        this.length = length;
+    }
+
+    @Override
+    public long getLength()
+    {
+        return length;
+    }
+
+    @Override
+    public Iterator<ByteBuffer> iterator()
+    {
+        return new Iterator<ByteBuffer>()
+        {
+            private int index;
+
+            @Override
+            public boolean hasNext()
+            {
+                return index < bytes.length;
+            }
+
+            @Override
+            public ByteBuffer next()
+            {
+                try
+                {
+                    return ByteBuffer.wrap(bytes[index++]);
+                }
+                catch (ArrayIndexOutOfBoundsException x)
+                {
+                    throw new NoSuchElementException();
+                }
+            }
+
+            @Override
+            public void remove()
+            {
+                throw new UnsupportedOperationException();
+            }
+        };
+    }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/DeferredContentProvider.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/DeferredContentProvider.java
new file mode 100644
index 0000000..f9aa729
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/DeferredContentProvider.java
@@ -0,0 +1,341 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client.util;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.jetty.client.AsyncContentProvider;
+import org.eclipse.jetty.client.Synchronizable;
+import org.eclipse.jetty.client.api.ContentProvider;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.util.ArrayQueue;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+
+/**
+ * A {@link ContentProvider} that allows to add content after {@link Request#send(Response.CompleteListener)}
+ * has been called, therefore providing the request content at a later time.
+ * <p />
+ * {@link DeferredContentProvider} can only be used in conjunction with
+ * {@link Request#send(Response.CompleteListener)} (and not with its blocking counterpart {@link Request#send()})
+ * because it provides content asynchronously.
+ * <p />
+ * The deferred content is provided once and then fully consumed.
+ * Invocations to the {@link #iterator()} method after the first will return an "empty" iterator
+ * because the stream has been consumed on the first invocation.
+ * However, it is possible for subclasses to override {@link #offer(ByteBuffer)} and {@link #close()} to copy
+ * the content to another location (for example a file) and be able to support multiple invocations
+ * of of {@link #iterator()} returning the iterator provided by this
+  * class on the first invocation, and an iterator on the bytes copied to the other location
+  * for subsequent invocations.
+ * <p />
+ * Typical usage of {@link DeferredContentProvider} is in asynchronous proxies, where HTTP headers arrive
+ * separately from HTTP content chunks.
+ * <p />
+ * The deferred content must be provided through {@link #offer(ByteBuffer)}, which can be invoked multiple
+ * times, and when all content has been provided it must be signaled with a call to {@link #close()}.
+ * <p />
+ * Example usage:
+ * <pre>
+ * HttpClient httpClient = ...;
+ *
+ * // Use try-with-resources to autoclose DeferredContentProvider
+ * try (DeferredContentProvider content = new DeferredContentProvider())
+ * {
+ *     httpClient.newRequest("localhost", 8080)
+ *             .content(content)
+ *             .send(new Response.CompleteListener()
+ *             {
+ *                 &#64Override
+ *                 public void onComplete(Result result)
+ *                 {
+ *                     // Your logic here
+ *                 }
+ *             });
+ *
+ *     // At a later time...
+ *     content.offer(ByteBuffer.wrap("some content".getBytes()));
+ * }
+ * </pre>
+ */
+public class DeferredContentProvider implements AsyncContentProvider, Callback, Closeable
+{
+    private static final Chunk CLOSE = new Chunk(BufferUtil.EMPTY_BUFFER, Callback.Adapter.INSTANCE);
+
+    private final Object lock = this;
+    private final ArrayQueue<Chunk> chunks = new ArrayQueue<>(4, 64, lock);
+    private final AtomicReference<Listener> listener = new AtomicReference<>();
+    private final DeferredContentProviderIterator iterator = new DeferredContentProviderIterator();
+    private final AtomicBoolean closed = new AtomicBoolean();
+    private long length = -1;
+    private int size;
+    private Throwable failure;
+
+    /**
+     * Creates a new {@link DeferredContentProvider} with the given initial content
+     *
+     * @param buffers the initial content
+     */
+    public DeferredContentProvider(ByteBuffer... buffers)
+    {
+        for (ByteBuffer buffer : buffers)
+            offer(buffer);
+    }
+
+    @Override
+    public void setListener(Listener listener)
+    {
+        if (!this.listener.compareAndSet(null, listener))
+            throw new IllegalStateException(String.format("The same %s instance cannot be used in multiple requests",
+                    AsyncContentProvider.class.getName()));
+
+        if (isClosed())
+        {
+            synchronized (lock)
+            {
+                long total = 0;
+                for (Chunk chunk : chunks)
+                    total += chunk.buffer.remaining();
+                length = total;
+            }
+        }
+    }
+
+    @Override
+    public long getLength()
+    {
+        return length;
+    }
+
+    /**
+     * Adds the given content buffer to this content provider
+     * and notifies the listener that content is available.
+     *
+     * @param buffer the content to add
+     * @return true if the content was added, false otherwise
+     */
+    public boolean offer(ByteBuffer buffer)
+    {
+        return offer(buffer, Callback.Adapter.INSTANCE);
+    }
+
+    public boolean offer(ByteBuffer buffer, Callback callback)
+    {
+        return offer(new Chunk(buffer, callback));
+    }
+
+    private boolean offer(Chunk chunk)
+    {
+        Throwable failure;
+        boolean result = false;
+        synchronized (lock)
+        {
+            failure = this.failure;
+            if (failure == null)
+            {
+                result = chunks.offer(chunk);
+                if (result && chunk != CLOSE)
+                    ++size;
+            }
+        }
+        if (failure != null)
+            chunk.callback.failed(failure);
+        else if (result)
+            notifyListener();
+        return result;
+    }
+
+    private void clear()
+    {
+        synchronized (lock)
+        {
+            chunks.clear();
+        }
+    }
+
+    public void flush() throws IOException
+    {
+        synchronized (lock)
+        {
+            try
+            {
+                while (true)
+                {
+                    if (failure != null)
+                        throw new IOException(failure);
+                    if (size == 0)
+                        break;
+                    lock.wait();
+                }
+            }
+            catch (InterruptedException x)
+            {
+                throw new InterruptedIOException();
+            }
+        }
+    }
+
+    /**
+     * No more content will be added to this content provider
+     * and notifies the listener that no more content is available.
+     */
+    public void close()
+    {
+        if (closed.compareAndSet(false, true))
+            offer(CLOSE);
+    }
+
+    public boolean isClosed()
+    {
+        return closed.get();
+    }
+
+    @Override
+    public void succeeded()
+    {
+    }
+
+    @Override
+    public void failed(Throwable failure)
+    {
+        iterator.failed(failure);
+    }
+
+    private void notifyListener()
+    {
+        Listener listener = this.listener.get();
+        if (listener != null)
+            listener.onContent();
+    }
+
+    @Override
+    public Iterator<ByteBuffer> iterator()
+    {
+        return iterator;
+    }
+
+    private class DeferredContentProviderIterator implements Iterator<ByteBuffer>, Callback, Synchronizable
+    {
+        private Chunk current;
+
+        @Override
+        public boolean hasNext()
+        {
+            synchronized (lock)
+            {
+                return chunks.peek() != CLOSE;
+            }
+        }
+
+        @Override
+        public ByteBuffer next()
+        {
+            synchronized (lock)
+            {
+                Chunk chunk = current = chunks.poll();
+                if (chunk == CLOSE)
+                {
+                    // Slow path: reinsert the CLOSE chunk
+                    // so that hasNext() works correctly.
+                    chunks.add(0, CLOSE);
+                    throw new NoSuchElementException();
+                }
+                return chunk == null ? null : chunk.buffer;
+            }
+        }
+
+        @Override
+        public void remove()
+        {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void succeeded()
+        {
+            Chunk chunk;
+            synchronized (lock)
+            {
+                chunk = current;
+                if (chunk != null)
+                {
+                    --size;
+                    lock.notify();
+                }
+            }
+            if (chunk != null)
+                chunk.callback.succeeded();
+        }
+
+        @Override
+        public void failed(Throwable x)
+        {
+            List<Chunk> chunks = new ArrayList<>();
+            synchronized (lock)
+            {
+                failure = x;
+                // Transfer all chunks to fail them all.
+                Chunk chunk = current;
+                current = null;
+                if (chunk != null)
+                    chunks.add(chunk);
+                chunks.addAll(DeferredContentProvider.this.chunks);
+                clear();
+                lock.notify();
+            }
+            for (Chunk chunk : chunks)
+                chunk.callback.failed(x);
+        }
+
+        @Override
+        public Object getLock()
+        {
+            return lock;
+        }
+    }
+
+    public static class Chunk
+    {
+        public final ByteBuffer buffer;
+        public final Callback callback;
+
+        public Chunk(ByteBuffer buffer, Callback callback)
+        {
+            this.buffer = Objects.requireNonNull(buffer);
+            this.callback = Objects.requireNonNull(callback);
+        }
+
+        @Override
+        public String toString()
+        {
+            return String.format("%s@%x", getClass().getSimpleName(), hashCode());
+        }
+    }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/DigestAuthentication.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/DigestAuthentication.java
new file mode 100644
index 0000000..e853395
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/DigestAuthentication.java
@@ -0,0 +1,282 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client.util;
+
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Random;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.eclipse.jetty.client.api.Authentication;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.util.Attributes;
+import org.eclipse.jetty.util.TypeUtil;
+
+/**
+ * Implementation of the HTTP "Digest" authentication defined in RFC 2617.
+ * <p />
+ * Applications should create objects of this class and add them to the
+ * {@link AuthenticationStore} retrieved from the {@link HttpClient}
+ * via {@link HttpClient#getAuthenticationStore()}.
+ */
+public class DigestAuthentication implements Authentication
+{
+    private static final Pattern PARAM_PATTERN = Pattern.compile("([^=]+)=(.*)");
+
+    private final URI uri;
+    private final String realm;
+    private final String user;
+    private final String password;
+
+    /**
+     * @param uri the URI to match for the authentication
+     * @param realm the realm to match for the authentication
+     * @param user the user that wants to authenticate
+     * @param password the password of the user
+     */
+    public DigestAuthentication(URI uri, String realm, String user, String password)
+    {
+        this.uri = uri;
+        this.realm = realm;
+        this.user = user;
+        this.password = password;
+    }
+
+    @Override
+    public boolean matches(String type, URI uri, String realm)
+    {
+        if (!"digest".equalsIgnoreCase(type))
+            return false;
+
+        if (!uri.toString().startsWith(this.uri.toString()))
+            return false;
+
+        return this.realm.equals(realm);
+    }
+
+    @Override
+    public Result authenticate(Request request, ContentResponse response, HeaderInfo headerInfo, Attributes context)
+    {
+        Map<String, String> params = parseParameters(headerInfo.getParameters());
+        String nonce = params.get("nonce");
+        if (nonce == null || nonce.length() == 0)
+            return null;
+        String opaque = params.get("opaque");
+        String algorithm = params.get("algorithm");
+        if (algorithm == null)
+            algorithm = "MD5";
+        MessageDigest digester = getMessageDigest(algorithm);
+        if (digester == null)
+            return null;
+        String serverQOP = params.get("qop");
+        String clientQOP = null;
+        if (serverQOP != null)
+        {
+            List<String> serverQOPValues = Arrays.asList(serverQOP.split(","));
+            if (serverQOPValues.contains("auth"))
+                clientQOP = "auth";
+            else if (serverQOPValues.contains("auth-int"))
+                clientQOP = "auth-int";
+        }
+
+        return new DigestResult(headerInfo.getHeader(), uri, response.getContent(), realm, user, password, algorithm, nonce, clientQOP, opaque);
+    }
+
+    private Map<String, String> parseParameters(String wwwAuthenticate)
+    {
+        Map<String, String> result = new HashMap<>();
+        List<String> parts = splitParams(wwwAuthenticate);
+        for (String part : parts)
+        {
+            Matcher matcher = PARAM_PATTERN.matcher(part);
+            if (matcher.matches())
+            {
+                String name = matcher.group(1).trim().toLowerCase(Locale.ENGLISH);
+                String value = matcher.group(2).trim();
+                if (value.startsWith("\"") && value.endsWith("\""))
+                    value = value.substring(1, value.length() - 1);
+                result.put(name, value);
+            }
+        }
+        return result;
+    }
+
+    private List<String> splitParams(String paramString)
+    {
+        List<String> result = new ArrayList<>();
+        int start = 0;
+        for (int i = 0; i < paramString.length(); ++i)
+        {
+            int quotes = 0;
+            char ch = paramString.charAt(i);
+            switch (ch)
+            {
+                case '\\':
+                    ++i;
+                    break;
+                case '"':
+                    ++quotes;
+                    break;
+                case ',':
+                    if (quotes % 2 == 0)
+                    {
+                        String element = paramString.substring(start, i).trim();
+                        if (element.length() > 0)
+                            result.add(element);
+                        start = i + 1;
+                    }
+                    break;
+                default:
+                    break;
+            }
+        }
+        result.add(paramString.substring(start, paramString.length()).trim());
+        return result;
+    }
+
+    private MessageDigest getMessageDigest(String algorithm)
+    {
+        try
+        {
+            return MessageDigest.getInstance(algorithm);
+        }
+        catch (NoSuchAlgorithmException x)
+        {
+            return null;
+        }
+    }
+
+    private class DigestResult implements Result
+    {
+        private final AtomicInteger nonceCount = new AtomicInteger();
+        private final HttpHeader header;
+        private final URI uri;
+        private final byte[] content;
+        private final String realm;
+        private final String user;
+        private final String password;
+        private final String algorithm;
+        private final String nonce;
+        private final String qop;
+        private final String opaque;
+
+        public DigestResult(HttpHeader header, URI uri, byte[] content, String realm, String user, String password, String algorithm, String nonce, String qop, String opaque)
+        {
+            this.header = header;
+            this.uri = uri;
+            this.content = content;
+            this.realm = realm;
+            this.user = user;
+            this.password = password;
+            this.algorithm = algorithm;
+            this.nonce = nonce;
+            this.qop = qop;
+            this.opaque = opaque;
+        }
+
+        @Override
+        public URI getURI()
+        {
+            return uri;
+        }
+
+        @Override
+        public void apply(Request request)
+        {
+            MessageDigest digester = getMessageDigest(algorithm);
+            if (digester == null)
+                return;
+
+            String A1 = user + ":" + realm + ":" + password;
+            String hashA1 = toHexString(digester.digest(A1.getBytes(StandardCharsets.ISO_8859_1)));
+
+            String A2 = request.getMethod() + ":" + request.getURI();
+            if ("auth-int".equals(qop))
+                A2 += ":" + toHexString(digester.digest(content));
+            String hashA2 = toHexString(digester.digest(A2.getBytes(StandardCharsets.ISO_8859_1)));
+
+            String nonceCount;
+            String clientNonce;
+            String A3;
+            if (qop != null)
+            {
+                nonceCount = nextNonceCount();
+                clientNonce = newClientNonce();
+                A3 = hashA1 + ":" + nonce + ":" +  nonceCount + ":" + clientNonce + ":" + qop + ":" + hashA2;
+            }
+            else
+            {
+                nonceCount = null;
+                clientNonce = null;
+                A3 = hashA1 + ":" + nonce + ":" + hashA2;
+            }
+            String hashA3 = toHexString(digester.digest(A3.getBytes(StandardCharsets.ISO_8859_1)));
+
+            StringBuilder value = new StringBuilder("Digest");
+            value.append(" username=\"").append(user).append("\"");
+            value.append(", realm=\"").append(realm).append("\"");
+            value.append(", nonce=\"").append(nonce).append("\"");
+            if (opaque != null)
+                value.append(", opaque=\"").append(opaque).append("\"");
+            value.append(", algorithm=\"").append(algorithm).append("\"");
+            value.append(", uri=\"").append(request.getURI()).append("\"");
+            if (qop != null)
+            {
+                value.append(", qop=\"").append(qop).append("\"");
+                value.append(", nc=\"").append(nonceCount).append("\"");
+                value.append(", cnonce=\"").append(clientNonce).append("\"");
+            }
+            value.append(", response=\"").append(hashA3).append("\"");
+
+            request.header(header, value.toString());
+        }
+
+        private String nextNonceCount()
+        {
+            String padding = "00000000";
+            String next = Integer.toHexString(nonceCount.incrementAndGet()).toLowerCase(Locale.ENGLISH);
+            return padding.substring(0, padding.length() - next.length()) + next;
+        }
+
+        private String newClientNonce()
+        {
+            Random random = new Random();
+            byte[] bytes = new byte[8];
+            random.nextBytes(bytes);
+            return toHexString(bytes);
+        }
+
+        private String toHexString(byte[] bytes)
+        {
+            return TypeUtil.toHexString(bytes).toLowerCase(Locale.ENGLISH);
+        }
+    }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/FormContentProvider.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/FormContentProvider.java
new file mode 100644
index 0000000..49f0de0
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/FormContentProvider.java
@@ -0,0 +1,77 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client.util;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.nio.charset.UnsupportedCharsetException;
+
+import org.eclipse.jetty.util.Fields;
+
+/**
+ * A {@link ContentProvider} for form uploads with the
+ * "application/x-www-form-urlencoded" content type.
+ */
+public class FormContentProvider extends StringContentProvider
+{
+    public FormContentProvider(Fields fields)
+    {
+        this(fields, StandardCharsets.UTF_8);
+    }
+
+    public FormContentProvider(Fields fields, Charset charset)
+    {
+        super("application/x-www-form-urlencoded", convert(fields, charset), charset);
+    }
+
+    public static String convert(Fields fields)
+    {
+        return convert(fields, StandardCharsets.UTF_8);
+    }
+
+    public static String convert(Fields fields, Charset charset)
+    {
+        // Assume 32 chars between name and value.
+        StringBuilder builder = new StringBuilder(fields.getSize() * 32);
+        for (Fields.Field field : fields)
+        {
+            for (String value : field.getValues())
+            {
+                if (builder.length() > 0)
+                    builder.append("&");
+                builder.append(encode(field.getName(), charset)).append("=").append(encode(value, charset));
+            }
+        }
+        return builder.toString();
+    }
+
+    private static String encode(String value, Charset charset)
+    {
+        try
+        {
+            return URLEncoder.encode(value, charset.name());
+        }
+        catch (UnsupportedEncodingException x)
+        {
+            throw new UnsupportedCharsetException(charset.name());
+        }
+    }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/FutureResponseListener.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/FutureResponseListener.java
new file mode 100644
index 0000000..0fc9511
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/FutureResponseListener.java
@@ -0,0 +1,121 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client.util;
+
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.eclipse.jetty.client.HttpContentResponse;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.api.Result;
+
+/**
+ * A {@link BufferingResponseListener} that is also a {@link Future}, to allow applications
+ * to block (indefinitely or for a timeout) until {@link #onComplete(Result)} is called,
+ * or to {@link #cancel(boolean) abort} the request/response conversation.
+ * <p />
+ * Typical usage is:
+ * <pre>
+ * Request request = httpClient.newRequest(...)...;
+ * FutureResponseListener listener = new FutureResponseListener(request);
+ * request.send(listener); // Asynchronous send
+ * ContentResponse response = listener.get(5, TimeUnit.SECONDS); // Timed block
+ * </pre>
+ */
+public class FutureResponseListener extends BufferingResponseListener implements Future<ContentResponse>
+{
+    private final CountDownLatch latch = new CountDownLatch(1);
+    private final Request request;
+    private ContentResponse response;
+    private Throwable failure;
+    private volatile boolean cancelled;
+
+    public FutureResponseListener(Request request)
+    {
+        this(request, 2 * 1024 * 1024);
+    }
+
+    public FutureResponseListener(Request request, int maxLength)
+    {
+        super(maxLength);
+        this.request = request;
+    }
+
+    public Request getRequest()
+    {
+        return request;
+    }
+
+    @Override
+    public void onComplete(Result result)
+    {
+        response = new HttpContentResponse(result.getResponse(), getContent(), getMediaType(), getEncoding());
+        failure = result.getFailure();
+        latch.countDown();
+    }
+
+    @Override
+    public boolean cancel(boolean mayInterruptIfRunning)
+    {
+        cancelled = true;
+        return request.abort(new CancellationException());
+    }
+
+    @Override
+    public boolean isCancelled()
+    {
+        return cancelled;
+    }
+
+    @Override
+    public boolean isDone()
+    {
+        return latch.getCount() == 0 || isCancelled();
+    }
+
+    @Override
+    public ContentResponse get() throws InterruptedException, ExecutionException
+    {
+        latch.await();
+        return getResult();
+    }
+
+    @Override
+    public ContentResponse get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException
+    {
+        boolean expired = !latch.await(timeout, unit);
+        if (expired)
+            throw new TimeoutException();
+        return getResult();
+    }
+
+    private ContentResponse getResult() throws ExecutionException
+    {
+        if (isCancelled())
+            throw (CancellationException)new CancellationException().initCause(failure);
+        if (failure != null)
+            throw new ExecutionException(failure);
+        return response;
+    }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/InputStreamContentProvider.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/InputStreamContentProvider.java
new file mode 100644
index 0000000..edd88db
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/InputStreamContentProvider.java
@@ -0,0 +1,263 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client.util;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+import org.eclipse.jetty.client.api.ContentProvider;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * A {@link ContentProvider} for an {@link InputStream}.
+ * <p />
+ * The input stream is read once and therefore fully consumed.
+ * Invocations to the {@link #iterator()} method after the first will return an "empty" iterator
+ * because the stream has been consumed on the first invocation.
+ * <p />
+ * However, it is possible for subclasses to override {@link #onRead(byte[], int, int)} to copy
+ * the content read from the stream to another location (for example a file), and be able to
+ * support multiple invocations of {@link #iterator()}, returning the iterator provided by this
+ * class on the first invocation, and an iterator on the bytes copied to the other location
+ * for subsequent invocations.
+ * <p />
+ * It is possible to specify, at the constructor, a buffer size used to read content from the
+ * stream, by default 4096 bytes.
+ * <p />
+ * The {@link InputStream} passed to the constructor is by default closed when is it fully
+ * consumed (or when an exception is thrown while reading it), unless otherwise specified
+ * to the {@link #InputStreamContentProvider(java.io.InputStream, int, boolean) constructor}.
+ */
+public class InputStreamContentProvider implements ContentProvider, Callback, Closeable
+{
+    private static final Logger LOG = Log.getLogger(InputStreamContentProvider.class);
+
+    private final InputStreamContentProviderIterator iterator = new InputStreamContentProviderIterator();
+    private final InputStream stream;
+    private final int bufferSize;
+    private final boolean autoClose;
+
+    public InputStreamContentProvider(InputStream stream)
+    {
+        this(stream, 4096);
+    }
+
+    public InputStreamContentProvider(InputStream stream, int bufferSize)
+    {
+        this(stream, bufferSize, true);
+    }
+
+    public InputStreamContentProvider(InputStream stream, int bufferSize, boolean autoClose)
+    {
+        this.stream = stream;
+        this.bufferSize = bufferSize;
+        this.autoClose = autoClose;
+    }
+
+    @Override
+    public long getLength()
+    {
+        return -1;
+    }
+
+    /**
+     * Callback method invoked just after having read from the stream,
+     * but before returning the iteration element (a {@link ByteBuffer}
+     * to the caller.
+     * <p />
+     * Subclasses may override this method to copy the content read from
+     * the stream to another location (a file, or in memory if the content
+     * is known to fit).
+     *
+     * @param buffer the byte array containing the bytes read
+     * @param offset the offset from where bytes should be read
+     * @param length the length of the bytes read
+     * @return a {@link ByteBuffer} wrapping the byte array
+     */
+    protected ByteBuffer onRead(byte[] buffer, int offset, int length)
+    {
+        if (length <= 0)
+            return BufferUtil.EMPTY_BUFFER;
+        return ByteBuffer.wrap(buffer, offset, length);
+    }
+
+    /**
+     * Callback method invoked when an exception is thrown while reading
+     * from the stream.
+     *
+     * @param failure the exception thrown while reading from the stream.
+     */
+    protected void onReadFailure(Throwable failure)
+    {
+    }
+
+    @Override
+    public Iterator<ByteBuffer> iterator()
+    {
+        return iterator;
+    }
+
+    @Override
+    public void close()
+    {
+        if (autoClose)
+        {
+            try
+            {
+                stream.close();
+            }
+            catch (IOException x)
+            {
+                LOG.ignore(x);
+            }
+        }
+    }
+
+    @Override
+    public void succeeded()
+    {
+    }
+
+    @Override
+    public void failed(Throwable failure)
+    {
+        // TODO: forward the failure to the iterator.
+        close();
+    }
+
+    /**
+     * Iterating over an {@link InputStream} is tricky, because {@link #hasNext()} must return false
+     * if the stream reads -1. However, we don't know what to return until we read the stream, which
+     * means that stream reading must be performed by {@link #hasNext()}, which introduces a side-effect
+     * on what is supposed to be a simple query method (with respect to the Query Command Separation
+     * Principle).
+     * <p />
+     * Alternatively, we could return {@code true} from {@link #hasNext()} even if we don't know that
+     * we will read -1, but then when {@link #next()} reads -1 it must return an empty buffer.
+     * However this is problematic, since GETs with no content indication would become GET with chunked
+     * content, and not understood by servers.
+     * <p />
+     * Therefore we need to make sure that {@link #hasNext()} does not perform any side effect (so that
+     * it can be called multiple times) until {@link #next()} is called.
+     */
+    private class InputStreamContentProviderIterator implements Iterator<ByteBuffer>, Closeable
+    {
+        private Throwable failure;
+        private ByteBuffer buffer;
+        private Boolean hasNext;
+
+        @Override
+        public boolean hasNext()
+        {
+            try
+            {
+                if (hasNext != null)
+                    return hasNext;
+
+                byte[] bytes = new byte[bufferSize];
+                int read = stream.read(bytes);
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Read {} bytes from {}", read, stream);
+                if (read > 0)
+                {
+                    hasNext = Boolean.TRUE;
+                    buffer = onRead(bytes, 0, read);
+                    return true;
+                }
+                else if (read < 0)
+                {
+                    hasNext = Boolean.FALSE;
+                    buffer = null;
+                    close();
+                    return false;
+                }
+                else
+                {
+                    hasNext = Boolean.TRUE;
+                    buffer = BufferUtil.EMPTY_BUFFER;
+                    return true;
+                }
+            }
+            catch (Throwable x)
+            {
+                if (LOG.isDebugEnabled())
+                    LOG.debug(x);
+                if (failure == null)
+                {
+                    failure = x;
+                    onReadFailure(x);
+                    // Signal we have more content to cause a call to
+                    // next() which will throw NoSuchElementException.
+                    hasNext = Boolean.TRUE;
+                    buffer = null;
+                    close();
+                    return true;
+                }
+                throw new IllegalStateException();
+            }
+        }
+
+        @Override
+        public ByteBuffer next()
+        {
+            if (failure != null)
+            {
+                // Consume the failure so that calls to hasNext() will return false.
+                hasNext = Boolean.FALSE;
+                buffer = null;
+                throw (NoSuchElementException)new NoSuchElementException().initCause(failure);
+            }
+            if (!hasNext())
+                throw new NoSuchElementException();
+
+            ByteBuffer result = buffer;
+            if (result == null)
+            {
+                hasNext = Boolean.FALSE;
+                buffer = null;
+                throw new NoSuchElementException();
+            }
+            else
+            {
+                hasNext = null;
+                buffer = null;
+                return result;
+            }
+        }
+
+        @Override
+        public void remove()
+        {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void close()
+        {
+            InputStreamContentProvider.this.close();
+        }
+    }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/InputStreamResponseListener.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/InputStreamResponseListener.java
new file mode 100644
index 0000000..984e7d8
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/InputStreamResponseListener.java
@@ -0,0 +1,349 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InterruptedIOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.AsynchronousCloseException;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.client.api.Response.Listener;
+import org.eclipse.jetty.client.api.Result;
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * Implementation of {@link Listener} that produces an {@link InputStream}
+ * that allows applications to read the response content.
+ * <p />
+ * Typical usage is:
+ * <pre>
+ * InputStreamResponseListener listener = new InputStreamResponseListener();
+ * client.newRequest(...).send(listener);
+ *
+ * // Wait for the response headers to arrive
+ * Response response = listener.get(5, TimeUnit.SECONDS);
+ * if (response.getStatus() == 200)
+ * {
+ *     // Obtain the input stream on the response content
+ *     try (InputStream input = listener.getInputStream())
+ *     {
+ *         // Read the response content
+ *     }
+ * }
+ * </pre>
+ * <p />
+ * The {@link HttpClient} implementation (the producer) will feed the input stream
+ * asynchronously while the application (the consumer) is reading from it.
+ * Chunks of content are maintained in a queue, and it is possible to specify a
+ * maximum buffer size for the bytes held in the queue, by default 16384 bytes.
+ * <p />
+ * If the consumer is faster than the producer, then the consumer will block
+ * with the typical {@link InputStream#read()} semantic.
+ * If the consumer is slower than the producer, then the producer will block
+ * until the client consumes.
+ */
+public class InputStreamResponseListener extends Listener.Adapter
+{
+    private static final Logger LOG = Log.getLogger(InputStreamResponseListener.class);
+    private static final byte[] EOF = new byte[0];
+    private static final byte[] CLOSED = new byte[0];
+    private static final byte[] FAILURE = new byte[0];
+    private final BlockingQueue<byte[]> queue = new LinkedBlockingQueue<>();
+    private final AtomicLong length = new AtomicLong();
+    private final CountDownLatch responseLatch = new CountDownLatch(1);
+    private final CountDownLatch resultLatch = new CountDownLatch(1);
+    private final AtomicReference<InputStream> stream = new AtomicReference<>();
+    private final long maxBufferSize;
+    private Response response;
+    private Result result;
+    private volatile Throwable failure;
+    private volatile boolean closed;
+
+    public InputStreamResponseListener()
+    {
+        this(16 * 1024L);
+    }
+
+    public InputStreamResponseListener(long maxBufferSize)
+    {
+        this.maxBufferSize = maxBufferSize;
+    }
+
+    @Override
+    public void onHeaders(Response response)
+    {
+        this.response = response;
+        responseLatch.countDown();
+    }
+
+    @Override
+    public void onContent(Response response, ByteBuffer content)
+    {
+        if (!closed)
+        {
+            int remaining = content.remaining();
+            if (remaining > 0)
+            {
+
+                byte[] bytes = new byte[remaining];
+                content.get(bytes);
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Queuing {}/{} bytes", bytes, remaining);
+                queue.offer(bytes);
+
+                long newLength = length.addAndGet(remaining);
+                while (newLength >= maxBufferSize)
+                {
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("Queued bytes limit {}/{} exceeded, waiting", newLength, maxBufferSize);
+                    // Block to avoid infinite buffering
+                    if (!await())
+                        break;
+                    newLength = length.get();
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("Queued bytes limit {}/{} exceeded, woken up", newLength, maxBufferSize);
+                }
+            }
+            else
+            {
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Queuing skipped, empty content {}", content);
+            }
+        }
+        else
+        {
+            LOG.debug("Queuing skipped, stream already closed");
+        }
+    }
+
+    @Override
+    public void onSuccess(Response response)
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("Queuing end of content {}{}", EOF, "");
+        queue.offer(EOF);
+        signal();
+    }
+
+    @Override
+    public void onFailure(Response response, Throwable failure)
+    {
+        fail(failure);
+        signal();
+    }
+
+    @Override
+    public void onComplete(Result result)
+    {
+        if (result.isFailed() && failure == null)
+            fail(result.getFailure());
+        this.result = result;
+        resultLatch.countDown();
+        signal();
+    }
+
+    private void fail(Throwable failure)
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("Queuing failure {} {}", FAILURE, failure);
+        queue.offer(FAILURE);
+        this.failure = failure;
+        responseLatch.countDown();
+    }
+
+    protected boolean await()
+    {
+        try
+        {
+            synchronized (this)
+            {
+                while (length.get() >= maxBufferSize && failure == null && !closed)
+                    wait();
+                // Re-read the values as they may have changed while waiting.
+                return failure == null && !closed;
+            }
+        }
+        catch (InterruptedException x)
+        {
+            Thread.currentThread().interrupt();
+            return false;
+        }
+    }
+
+    protected void signal()
+    {
+        synchronized (this)
+        {
+            notifyAll();
+        }
+    }
+
+    /**
+     * Waits for the given timeout for the response to be available, then returns it.
+     * <p />
+     * The wait ends as soon as all the HTTP headers have been received, without waiting for the content.
+     * To wait for the whole content, see {@link #await(long, TimeUnit)}.
+     *
+     * @param timeout the time to wait
+     * @param unit the timeout unit
+     * @return the response
+     * @throws InterruptedException if the thread is interrupted
+     * @throws TimeoutException if the timeout expires
+     * @throws ExecutionException if a failure happened
+     */
+    public Response get(long timeout, TimeUnit unit) throws InterruptedException, TimeoutException, ExecutionException
+    {
+        boolean expired = !responseLatch.await(timeout, unit);
+        if (expired)
+            throw new TimeoutException();
+        if (failure != null)
+            throw new ExecutionException(failure);
+        return response;
+    }
+
+    /**
+     * Waits for the given timeout for the whole request/response cycle to be finished,
+     * then returns the corresponding result.
+     * <p />
+     *
+     * @param timeout the time to wait
+     * @param unit the timeout unit
+     * @return the result
+     * @throws InterruptedException if the thread is interrupted
+     * @throws TimeoutException if the timeout expires
+     * @see #get(long, TimeUnit)
+     */
+    public Result await(long timeout, TimeUnit unit) throws InterruptedException, TimeoutException
+    {
+        boolean expired = !resultLatch.await(timeout, unit);
+        if (expired)
+            throw new TimeoutException();
+        return result;
+    }
+
+    /**
+     * Returns an {@link InputStream} providing the response content bytes.
+     * <p />
+     * The method may be invoked only once; subsequent invocations will return a closed {@link InputStream}.
+     *
+     * @return an input stream providing the response content
+     */
+    public InputStream getInputStream()
+    {
+        InputStream result = new Input();
+        if (stream.compareAndSet(null, result))
+            return result;
+        return IO.getClosedStream();
+    }
+
+    private class Input extends InputStream
+    {
+        private byte[] bytes;
+        private int index;
+
+        @Override
+        public int read() throws IOException
+        {
+            while (true)
+            {
+                if (bytes == EOF)
+                {
+                    // Mark the fact that we saw -1,
+                    // so that in the close case we don't throw
+                    index = -1;
+                    return -1;
+                }
+                else if (bytes == FAILURE)
+                {
+                    throw failure();
+                }
+                else if (bytes == CLOSED)
+                {
+                    if (index < 0)
+                        return -1;
+                    throw new AsynchronousCloseException();
+                }
+                else if (bytes != null)
+                {
+                    int result = bytes[index] & 0xFF;
+                    if (++index == bytes.length)
+                    {
+                        length.addAndGet(-index);
+                        bytes = null;
+                        index = 0;
+                        signal();
+                    }
+                    return result;
+                }
+                else
+                {
+                    bytes = take();
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("Dequeued {}/{} bytes", bytes, bytes.length);
+                }
+            }
+        }
+
+        private IOException failure()
+        {
+            if (failure instanceof IOException)
+                return (IOException)failure;
+            else
+                return new IOException(failure);
+        }
+
+        private byte[] take() throws IOException
+        {
+            try
+            {
+                return queue.take();
+            }
+            catch (InterruptedException x)
+            {
+                throw new InterruptedIOException();
+            }
+        }
+
+        @Override
+        public void close() throws IOException
+        {
+            if (!closed)
+            {
+                super.close();
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Queuing close {}{}", CLOSED, "");
+                queue.offer(CLOSED);
+                closed = true;
+                signal();
+            }
+        }
+    }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/OutputStreamContentProvider.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/OutputStreamContentProvider.java
new file mode 100644
index 0000000..55131c2
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/OutputStreamContentProvider.java
@@ -0,0 +1,154 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client.util;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.util.Iterator;
+
+import org.eclipse.jetty.client.AsyncContentProvider;
+import org.eclipse.jetty.client.api.ContentProvider;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.util.Callback;
+
+/**
+ * A {@link ContentProvider} that provides content asynchronously through an {@link OutputStream}
+ * similar to {@link DeferredContentProvider}.
+ * <p />
+ * {@link OutputStreamContentProvider} can only be used in conjunction with
+ * {@link Request#send(Response.CompleteListener)} (and not with its blocking counterpart {@link Request#send()})
+ * because it provides content asynchronously.
+ * <p />
+ * The deferred content is provided once by writing to the {@link #getOutputStream() output stream}
+ * and then fully consumed.
+ * Invocations to the {@link #iterator()} method after the first will return an "empty" iterator
+ * because the stream has been consumed on the first invocation.
+ * However, it is possible for subclasses to support multiple invocations of {@link #iterator()}
+ * by overriding {@link #write(ByteBuffer)} and {@link #close()}, copying the bytes and making them
+ * available for subsequent invocations.
+ * <p />
+ * Content must be provided by writing to the {@link #getOutputStream() output stream}, that must be
+ * {@link OutputStream#close() closed} when all content has been provided.
+ * <p />
+ * Example usage:
+ * <pre>
+ * HttpClient httpClient = ...;
+ *
+ * // Use try-with-resources to autoclose the output stream
+ * OutputStreamContentProvider content = new OutputStreamContentProvider();
+ * try (OutputStream output = content.getOutputStream())
+ * {
+ *     httpClient.newRequest("localhost", 8080)
+ *             .content(content)
+ *             .send(new Response.CompleteListener()
+ *             {
+ *                 &#64Override
+ *                 public void onComplete(Result result)
+ *                 {
+ *                     // Your logic here
+ *                 }
+ *             });
+ *
+ *     // At a later time...
+ *     output.write("some content".getBytes());
+ * }
+ * </pre>
+ */
+public class OutputStreamContentProvider implements AsyncContentProvider, Callback, Closeable
+{
+    private final DeferredContentProvider deferred = new DeferredContentProvider();
+    private final OutputStream output = new DeferredOutputStream();
+
+    @Override
+    public long getLength()
+    {
+        return deferred.getLength();
+    }
+
+    @Override
+    public Iterator<ByteBuffer> iterator()
+    {
+        return deferred.iterator();
+    }
+
+    @Override
+    public void setListener(Listener listener)
+    {
+        deferred.setListener(listener);
+    }
+
+    public OutputStream getOutputStream()
+    {
+        return output;
+    }
+
+    protected void write(ByteBuffer buffer)
+    {
+        deferred.offer(buffer);
+    }
+
+    @Override
+    public void close()
+    {
+        deferred.close();
+    }
+
+    @Override
+    public void succeeded()
+    {
+        deferred.succeeded();
+    }
+
+    @Override
+    public void failed(Throwable failure)
+    {
+        deferred.failed(failure);
+    }
+
+    private class DeferredOutputStream extends OutputStream
+    {
+        @Override
+        public void write(int b) throws IOException
+        {
+            write(new byte[]{(byte)b}, 0, 1);
+        }
+
+        @Override
+        public void write(byte[] b, int off, int len) throws IOException
+        {
+            OutputStreamContentProvider.this.write(ByteBuffer.wrap(b, off, len));
+            flush();
+        }
+
+        @Override
+        public void flush() throws IOException
+        {
+            deferred.flush();
+        }
+
+        @Override
+        public void close() throws IOException
+        {
+            OutputStreamContentProvider.this.close();
+        }
+    }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/PathContentProvider.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/PathContentProvider.java
new file mode 100644
index 0000000..f7685b9
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/PathContentProvider.java
@@ -0,0 +1,161 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client.util;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.SeekableByteChannel;
+import java.nio.file.AccessDeniedException;
+import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * A {@link ContentProvider} for files using JDK 7's {@code java.nio.file} APIs.
+ * <p />
+ * It is possible to specify, at the constructor, a buffer size used to read content from the
+ * stream, by default 4096 bytes.
+ */
+public class PathContentProvider extends AbstractTypedContentProvider
+{
+    private static final Logger LOG = Log.getLogger(PathContentProvider.class);
+
+    private final Path filePath;
+    private final long fileSize;
+    private final int bufferSize;
+
+    public PathContentProvider(Path filePath) throws IOException
+    {
+        this(filePath, 4096);
+    }
+
+    public PathContentProvider(Path filePath, int bufferSize) throws IOException
+    {
+        this("application/octet-stream", filePath, bufferSize);
+    }
+
+    public PathContentProvider(String contentType, Path filePath) throws IOException
+    {
+        this(contentType, filePath, 4096);
+    }
+
+    public PathContentProvider(String contentType, Path filePath, int bufferSize) throws IOException
+    {
+        super(contentType);
+        if (!Files.isRegularFile(filePath))
+            throw new NoSuchFileException(filePath.toString());
+        if (!Files.isReadable(filePath))
+            throw new AccessDeniedException(filePath.toString());
+        this.filePath = filePath;
+        this.fileSize = Files.size(filePath);
+        this.bufferSize = bufferSize;
+    }
+
+    @Override
+    public long getLength()
+    {
+        return fileSize;
+    }
+
+    @Override
+    public Iterator<ByteBuffer> iterator()
+    {
+        return new PathIterator();
+    }
+
+    private class PathIterator implements Iterator<ByteBuffer>, Closeable
+    {
+        private final ByteBuffer buffer = ByteBuffer.allocateDirect(bufferSize);
+        private SeekableByteChannel channel;
+        private long position;
+
+        @Override
+        public boolean hasNext()
+        {
+            return position < getLength();
+        }
+
+        @Override
+        public ByteBuffer next()
+        {
+            try
+            {
+                if (channel == null)
+                {
+                    channel = Files.newByteChannel(filePath, StandardOpenOption.READ);
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("Opened file {}", filePath);
+                }
+
+                buffer.clear();
+                int read = channel.read(buffer);
+                if (read < 0)
+                    throw new NoSuchElementException();
+
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Read {} bytes from {}", read, filePath);
+
+                position += read;
+
+                if (!hasNext())
+                    close();
+
+                buffer.flip();
+                return buffer;
+            }
+            catch (NoSuchElementException x)
+            {
+                close();
+                throw x;
+            }
+            catch (Exception x)
+            {
+                close();
+                throw (NoSuchElementException)new NoSuchElementException().initCause(x);
+            }
+        }
+
+        @Override
+        public void remove()
+        {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void close()
+        {
+            try
+            {
+                if (channel != null)
+                    channel.close();
+            }
+            catch (Exception x)
+            {
+                LOG.ignore(x);
+            }
+        }
+    }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/StringContentProvider.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/StringContentProvider.java
new file mode 100644
index 0000000..a1f0109
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/StringContentProvider.java
@@ -0,0 +1,51 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client.util;
+
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * A {@link ContentProvider} for strings.
+ * <p />
+ * It is possible to specify, at the constructor, an encoding used to convert
+ * the string into bytes, by default UTF-8.
+ */
+public class StringContentProvider extends BytesContentProvider
+{
+    public StringContentProvider(String content)
+    {
+        this(content, StandardCharsets.UTF_8);
+    }
+
+    public StringContentProvider(String content, String encoding)
+    {
+        this(content, Charset.forName(encoding));
+    }
+
+    public StringContentProvider(String content, Charset charset)
+    {
+        this("text/plain;charset=" + charset.name(), content, charset);
+    }
+
+    public StringContentProvider(String contentType, String content, Charset charset)
+    {
+        super(contentType, content.getBytes(charset));
+    }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/package-info.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/package-info.java
new file mode 100644
index 0000000..90e0918
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Client : Utility Classes
+ */
+package org.eclipse.jetty.client.util;
+
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/webdav/MkcolExchange.java b/jetty-client/src/main/java/org/eclipse/jetty/client/webdav/MkcolExchange.java
deleted file mode 100644
index 9f8695f..0000000
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/webdav/MkcolExchange.java
+++ /dev/null
@@ -1,64 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client.webdav;
-
-import java.io.IOException;
-
-import org.eclipse.jetty.client.CachedExchange;
-import org.eclipse.jetty.http.HttpStatus;
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-
-
-public class MkcolExchange extends CachedExchange
-{
-    private static final Logger LOG = Log.getLogger(MkcolExchange.class);
-
-    boolean exists = false;
-
-    public MkcolExchange()
-    {
-        super(true);
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    protected void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException
-    {
-        if ( status == HttpStatus.CREATED_201 )
-        {
-            LOG.debug( "MkcolExchange:Status: Successfully created resource" );
-            exists = true;
-        }
-
-        if ( status == HttpStatus.METHOD_NOT_ALLOWED_405 ) // returned when resource exists
-        {
-            LOG.debug( "MkcolExchange:Status: Resource must exist" );
-            exists = true;
-        }
-
-        super.onResponseStatus(version, status, reason);
-    }
-
-    public boolean exists()
-    {
-        return exists;
-    }
-}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/webdav/PropfindExchange.java b/jetty-client/src/main/java/org/eclipse/jetty/client/webdav/PropfindExchange.java
deleted file mode 100644
index c1a7ea5..0000000
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/webdav/PropfindExchange.java
+++ /dev/null
@@ -1,57 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client.webdav;
-
-import java.io.IOException;
-
-import org.eclipse.jetty.client.HttpExchange;
-import org.eclipse.jetty.http.HttpStatus;
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-
-
-public class PropfindExchange extends HttpExchange
-{
-    private static final Logger LOG = Log.getLogger(PropfindExchange.class);
-
-    boolean _propertyExists = false;
-
-    /* ------------------------------------------------------------ */
-    @Override
-    protected void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException
-    {
-        if ( status == HttpStatus.OK_200 )
-        {
-            LOG.debug( "PropfindExchange:Status: Exists" );
-            _propertyExists = true;
-        }
-        else
-        {
-            LOG.debug( "PropfindExchange:Status: Not Exists" );
-        }
-
-        super.onResponseStatus(version, status, reason);
-    }
-
-    public boolean exists()
-    {
-        return _propertyExists;
-    }
-}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/webdav/WebdavListener.java b/jetty-client/src/main/java/org/eclipse/jetty/client/webdav/WebdavListener.java
deleted file mode 100644
index 81c388e..0000000
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/webdav/WebdavListener.java
+++ /dev/null
@@ -1,332 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-
-package org.eclipse.jetty.client.webdav;
-
-import java.io.IOException;
-
-import org.eclipse.jetty.client.HttpDestination;
-import org.eclipse.jetty.client.HttpEventListenerWrapper;
-import org.eclipse.jetty.client.HttpExchange;
-import org.eclipse.jetty.client.security.SecurityListener;
-import org.eclipse.jetty.http.HttpMethods;
-import org.eclipse.jetty.http.HttpStatus;
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.util.URIUtil;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-
-/**
- * WebdavListener
- * 
- * 
- * 
- * 
- */
-public class WebdavListener extends HttpEventListenerWrapper
-{
-    private static final Logger LOG = Log.getLogger(WebdavListener.class);
-
-    private HttpDestination _destination;
-    private HttpExchange _exchange;
-    private boolean _requestComplete;
-    private boolean _responseComplete; 
-    private boolean _webdavEnabled;
-    private boolean _needIntercept;
-
-    public WebdavListener(HttpDestination destination, HttpExchange ex)
-    {
-        // Start of sending events through to the wrapped listener
-        // Next decision point is the onResponseStatus
-        super(ex.getEventListener(),true);
-        _destination=destination;
-        _exchange=ex;
-
-        // We'll only enable webdav if this is a PUT request
-        if ( HttpMethods.PUT.equalsIgnoreCase( _exchange.getMethod() ) )
-        {
-            _webdavEnabled = true;
-        }
-    }
-
-    @Override
-    public void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException
-    {
-        if ( !_webdavEnabled )
-        {
-            _needIntercept = false;
-            super.onResponseStatus(version, status, reason);
-            return;
-        }
-        
-        if (LOG.isDebugEnabled())
-            LOG.debug("WebdavListener:Response Status: " + status );
-
-        // The dav spec says that CONFLICT should be returned when the parent collection doesn't exist but I am seeing
-        // FORBIDDEN returned instead so running with that.
-        if ( status == HttpStatus.FORBIDDEN_403 || status == HttpStatus.CONFLICT_409 )
-        {
-            if ( _webdavEnabled )
-            {
-                if (LOG.isDebugEnabled())
-                    LOG.debug("WebdavListener:Response Status: dav enabled, taking a stab at resolving put issue" );
-                setDelegatingResponses( false ); // stop delegating, we can try and fix this request
-                _needIntercept = true;
-            }
-            else
-            {
-                if (LOG.isDebugEnabled())
-                    LOG.debug("WebdavListener:Response Status: Webdav Disabled" );
-                setDelegatingResponses( true ); // just make sure we delegate
-                setDelegatingRequests( true );
-                _needIntercept = false;
-            }
-        }
-        else
-        {
-            _needIntercept = false;
-            setDelegatingResponses( true );
-            setDelegatingRequests( true );
-        }
-
-        super.onResponseStatus(version, status, reason);
-    }
-
-    @Override
-    public void onResponseComplete() throws IOException
-    {
-        _responseComplete = true;
-        if (_needIntercept)
-        {
-            if ( _requestComplete && _responseComplete)
-            {
-                try
-                {
-                    // we have some work to do before retrying this
-                    if ( resolveCollectionIssues() )
-                    {
-                        setDelegatingRequests( true );
-                        setDelegatingResponses(true);
-                        _requestComplete = false;
-                        _responseComplete = false;
-                        _destination.resend(_exchange);
-                    }
-                    else
-                    {
-                        // admit defeat but retry because someone else might have 
-                        setDelegationResult(false);
-                        setDelegatingRequests( true );
-                        setDelegatingResponses(true);
-                        super.onResponseComplete();
-                    }
-                }
-                catch ( IOException ioe )
-                {
-                    LOG.debug("WebdavListener:Complete:IOException: might not be dealing with dav server, delegate");
-                    super.onResponseComplete();
-                }
-            }
-            else
-            {
-                if (LOG.isDebugEnabled())
-                    LOG.debug("WebdavListener:Not ready, calling super");
-                super.onResponseComplete();
-            }
-        }
-        else
-        {
-            super.onResponseComplete();
-        }
-    }
-
-    
-    
-    @Override
-    public void onRequestComplete () throws IOException
-    {
-        _requestComplete = true;
-        if (_needIntercept)
-        {
-            if ( _requestComplete && _responseComplete)
-            {
-                try
-                {
-                    // we have some work to do before retrying this
-                    if ( resolveCollectionIssues() )
-                    {
-                        setDelegatingRequests( true );
-                        setDelegatingResponses(true);
-                        _requestComplete = false;
-                        _responseComplete = false;
-                        _destination.resend(_exchange);
-                    }
-                    else
-                    {
-                        // admit defeat but retry because someone else might have 
-                        setDelegatingRequests( true );
-                        setDelegatingResponses(true);
-                        super.onRequestComplete();
-                    }
-                }
-                catch ( IOException ioe )
-                {
-                    LOG.debug("WebdavListener:Complete:IOException: might not be dealing with dav server, delegate");
-                    super.onRequestComplete();
-                }
-            }
-            else
-            {
-                if (LOG.isDebugEnabled())
-                    LOG.debug("WebdavListener:Not ready, calling super");
-                super.onRequestComplete();
-            }
-        }
-        else
-        {
-            super.onRequestComplete();
-        } 
-    }
-
-   
-    
-    
-    /**
-     * walk through the steps to try and resolve missing parent collection issues via webdav
-     *
-     * TODO this really ought to use URI itself for this resolution
-     *
-     * @return
-     * @throws IOException
-     */
-    private boolean resolveCollectionIssues() throws IOException
-    {
-
-        String uri = _exchange.getURI();
-        String[] uriCollection = _exchange.getURI().split("/");
-        int checkNum = uriCollection.length;
-        int rewind = 0;
-
-        String parentUri = URIUtil.parentPath( uri );
-        while ( parentUri != null && !checkExists(parentUri) )
-        {
-            ++rewind;
-            parentUri = URIUtil.parentPath( parentUri );
-        }
-
-        // confirm webdav is supported for this collection
-        if ( checkWebdavSupported() )
-        {
-            for (int i = 0; i < rewind;)
-            {
-                makeCollection(parentUri + "/" + uriCollection[checkNum - rewind - 1]);
-                parentUri = parentUri + "/" + uriCollection[checkNum - rewind - 1];
-                --rewind;
-            }
-        }
-        else
-        {
-            return false;
-        }
-
-        return true;
-    }
-
-    private boolean checkExists( String uri ) throws IOException
-    {
-        if (uri == null)
-        {
-            System.out.println("have failed miserably");
-            return false;
-        }
-        
-        PropfindExchange propfindExchange = new PropfindExchange();
-        propfindExchange.setAddress( _exchange.getAddress() );
-        propfindExchange.setMethod( HttpMethods.GET ); // PROPFIND acts wonky, just use get
-        propfindExchange.setScheme( _exchange.getScheme() );
-        propfindExchange.setEventListener( new SecurityListener( _destination, propfindExchange ) );
-        propfindExchange.setConfigureListeners( false );
-        propfindExchange.setRequestURI( uri );
-
-        _destination.send( propfindExchange );
-
-        try
-        {
-            propfindExchange.waitForDone();
-
-            return propfindExchange.exists();
-        }
-        catch ( InterruptedException ie )
-        {
-            LOG.ignore( ie );                  
-            return false;
-        }
-    }
-
-    private boolean makeCollection( String uri ) throws IOException
-    {
-        MkcolExchange mkcolExchange = new MkcolExchange();
-        mkcolExchange.setAddress( _exchange.getAddress() );
-        mkcolExchange.setMethod( "MKCOL " + uri + " HTTP/1.1" );
-        mkcolExchange.setScheme( _exchange.getScheme() );
-        mkcolExchange.setEventListener( new SecurityListener( _destination, mkcolExchange ) );
-        mkcolExchange.setConfigureListeners( false );
-        mkcolExchange.setRequestURI( uri );
-
-        _destination.send( mkcolExchange );
-
-        try
-        {
-            mkcolExchange.waitForDone();
-
-            return mkcolExchange.exists();
-        }
-        catch ( InterruptedException ie )
-        {
-            LOG.ignore( ie );
-            return false;
-        }
-    }
-
-    
-    private boolean checkWebdavSupported() throws IOException
-    {
-        WebdavSupportedExchange supportedExchange = new WebdavSupportedExchange();
-        supportedExchange.setAddress( _exchange.getAddress() );
-        supportedExchange.setMethod( HttpMethods.OPTIONS );
-        supportedExchange.setScheme( _exchange.getScheme() );
-        supportedExchange.setEventListener( new SecurityListener( _destination, supportedExchange ) );
-        supportedExchange.setConfigureListeners( false );
-        supportedExchange.setRequestURI( _exchange.getURI() );
-
-        _destination.send( supportedExchange );
-
-        try
-        {
-            supportedExchange.waitTilCompletion();
-            return supportedExchange.isWebdavSupported();
-        }
-        catch (InterruptedException ie )
-        {            
-            LOG.ignore( ie );
-            return false;
-        }
-
-    }
-
-}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/webdav/WebdavSupportedExchange.java b/jetty-client/src/main/java/org/eclipse/jetty/client/webdav/WebdavSupportedExchange.java
deleted file mode 100644
index 858d8c8..0000000
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/webdav/WebdavSupportedExchange.java
+++ /dev/null
@@ -1,75 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client.webdav;
-
-import java.io.IOException;
-
-import org.eclipse.jetty.client.HttpExchange;
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-
-
-public class WebdavSupportedExchange extends HttpExchange
-{
-    private static final Logger LOG = Log.getLogger(WebdavSupportedExchange.class);
-
-    private boolean _webdavSupported = false;
-    private boolean _isComplete = false;
-
-    @Override
-    protected void onResponseHeader(Buffer name, Buffer value) throws IOException
-    {
-        if (LOG.isDebugEnabled())
-            LOG.debug("WebdavSupportedExchange:Header:" + name.toString() + " / " + value.toString() );
-        if ( "DAV".equals( name.toString() ) )
-        {
-            if ( value.toString().indexOf( "1" ) >= 0 || value.toString().indexOf( "2" ) >= 0 )
-            {
-                _webdavSupported = true;
-            }
-        }
-
-        super.onResponseHeader(name, value);
-    }
-
-    public void waitTilCompletion() throws InterruptedException
-    {
-        synchronized (this)
-        {
-            while ( !_isComplete)
-            {
-                this.wait();
-            }
-        }
-    }
-
-    @Override
-    protected void onResponseComplete() throws IOException
-    {
-        _isComplete = true;
-
-        super.onResponseComplete();
-    }
-
-    public boolean isWebdavSupported()
-    {
-        return _webdavSupported;
-    }
-}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/AbstractConnectionTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/AbstractConnectionTest.java
deleted file mode 100644
index bed2da0..0000000
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/AbstractConnectionTest.java
+++ /dev/null
@@ -1,443 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.net.ServerSocket;
-import java.net.Socket;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-import org.junit.Test;
-
-/**
- * @version $Revision$ $Date$
- */
-public abstract class AbstractConnectionTest
-{
-    protected HttpClient newHttpClient()
-    {
-        HttpClient httpClient = new HttpClient();
-
-        // httpClient.setConnectorType(HttpClient.CONNECTOR_SOCKET);
-        return httpClient;
-    }
-
-
-    protected ServerSocket newServerSocket() throws IOException
-    {
-        ServerSocket serverSocket=new ServerSocket();
-        serverSocket.bind(null);
-        return serverSocket;
-    }
-    
-    @Test
-    public void testServerClosedConnection() throws Exception
-    {
-        ServerSocket serverSocket = new ServerSocket();
-        serverSocket.bind(null);
-        int port=serverSocket.getLocalPort();
-
-        HttpClient httpClient = newHttpClient();
-        httpClient.setMaxConnectionsPerAddress(1);
-        httpClient.start();
-        try
-        {
-            CountDownLatch latch = new CountDownLatch(1);
-            HttpExchange exchange = new ConnectionExchange(latch);
-            exchange.setAddress(new Address("localhost", port));
-            exchange.setRequestURI("/");
-            httpClient.send(exchange);
-
-            Socket remote = serverSocket.accept();
-
-            // HttpClient.send() above is async, so if we write the response immediately
-            // there is a chance that it arrives before the request is being sent, so we
-            // read the request before sending the response to avoid the race
-            InputStream input = remote.getInputStream();
-            BufferedReader reader = new BufferedReader(new InputStreamReader(input, "UTF-8"));
-            String line;
-            while ((line = reader.readLine()) != null)
-            {
-                if (line.length() == 0)
-                    break;
-            }
-
-            OutputStream output = remote.getOutputStream();
-            output.write("HTTP/1.1 200 OK\r\n".getBytes("UTF-8"));
-            output.write("Content-Length: 0\r\n".getBytes("UTF-8"));
-            output.write("\r\n".getBytes("UTF-8"));
-            output.flush();
-
-            assertEquals(HttpExchange.STATUS_COMPLETED, exchange.waitForDone());
-
-            remote.close();
-
-            // Need to wait a bit to allow the client to detect
-            // that the server has closed the connection
-            Thread.sleep(500);
-
-            // The server has closed the connection and another attempt to send
-            // with the same connection would fail because the connection has been
-            // closed by the client as well.
-            // The client must open a new connection in this case, and we check
-            // that the new request completes correctly
-            exchange.reset();
-            httpClient.send(exchange);
-
-            remote = serverSocket.accept();
-
-            input = remote.getInputStream();
-            reader = new BufferedReader(new InputStreamReader(input, "UTF-8"));
-            while ((line = reader.readLine()) != null)
-            {
-                if (line.length() == 0)
-                    break;
-            }
-
-            output = remote.getOutputStream();
-            output.write("HTTP/1.1 200 OK\r\n".getBytes("UTF-8"));
-            output.write("Content-Length: 0\r\n".getBytes("UTF-8"));
-            output.write("\r\n".getBytes("UTF-8"));
-            output.flush();
-
-            assertEquals(HttpExchange.STATUS_COMPLETED, exchange.waitForDone());
-        }
-        finally
-        {
-            httpClient.stop();
-        }
-    }
-
-    protected String getScheme()
-    {
-        return "http";
-    }
-    
-    @Test
-    public void testServerClosedIncomplete() throws Exception
-    {
-        ServerSocket serverSocket = newServerSocket();
-        int port=serverSocket.getLocalPort();
-
-        HttpClient httpClient = newHttpClient();
-        httpClient.setMaxConnectionsPerAddress(1);
-        httpClient.start();
-        try
-        {
-            CountDownLatch latch = new CountDownLatch(1);
-            HttpExchange exchange = new ConnectionExchange(latch);
-            exchange.setScheme(getScheme());
-            exchange.setAddress(new Address("localhost", port));
-            exchange.setRequestURI("/");
-            httpClient.send(exchange);
-
-            Socket remote = serverSocket.accept();
-
-            // HttpClient.send() above is async, so if we write the response immediately
-            // there is a chance that it arrives before the request is being sent, so we
-            // read the request before sending the response to avoid the race
-            InputStream input = remote.getInputStream();
-            BufferedReader reader = new BufferedReader(new InputStreamReader(input, "UTF-8"));
-            String line;
-            while ((line = reader.readLine()) != null)
-            {
-                if (line.length() == 0)
-                    break;
-            }
-
-            OutputStream output = remote.getOutputStream();
-            output.write("HTTP/1.1 200 OK\r\n".getBytes("UTF-8"));
-            output.write("Content-Length: 10\r\n".getBytes("UTF-8"));
-            output.write("\r\n".getBytes("UTF-8"));
-            output.flush();
-
-            remote.close();
-
-            int status = exchange.waitForDone();
-            
-            assertEquals(HttpExchange.STATUS_EXCEPTED, status);
-
-        }
-        finally
-        {
-            httpClient.stop();
-        }
-    }
-
-    @Test
-    public void testServerHalfClosedIncomplete() throws Exception
-    {
-        ServerSocket serverSocket = new ServerSocket();
-        serverSocket.bind(null);
-        int port=serverSocket.getLocalPort();
-
-        HttpClient httpClient = newHttpClient();
-        httpClient.setIdleTimeout(10000);
-        httpClient.setMaxConnectionsPerAddress(1);
-        httpClient.start();
-        try
-        {
-            CountDownLatch latch = new CountDownLatch(1);
-            HttpExchange exchange = new ConnectionExchange(latch);
-            exchange.setAddress(new Address("localhost", port));
-            exchange.setRequestURI("/");
-            httpClient.send(exchange);
-
-            Socket remote = serverSocket.accept();
-
-            // HttpClient.send() above is async, so if we write the response immediately
-            // there is a chance that it arrives before the request is being sent, so we
-            // read the request before sending the response to avoid the race
-            InputStream input = remote.getInputStream();
-            BufferedReader reader = new BufferedReader(new InputStreamReader(input, "UTF-8"));
-            String line;
-            while ((line = reader.readLine()) != null)
-            {
-                if (line.length() == 0)
-                    break;
-            }
-
-            OutputStream output = remote.getOutputStream();
-            output.write("HTTP/1.1 200 OK\r\n".getBytes("UTF-8"));
-            output.write("Content-Length: 10\r\n".getBytes("UTF-8"));
-            output.write("\r\n".getBytes("UTF-8"));
-            output.flush();
-
-            remote.shutdownOutput();
-
-            assertEquals(HttpExchange.STATUS_EXCEPTED, exchange.waitForDone());
-
-        }
-        finally
-        {
-            httpClient.stop();
-        }
-    }
-
-    @Test
-    public void testConnectionFailed() throws Exception
-    {
-        ServerSocket serverSocket = new ServerSocket();
-        serverSocket.bind(null);
-        int port=serverSocket.getLocalPort();
-        serverSocket.close();
-
-        HttpClient httpClient = newHttpClient();
-        httpClient.start();
-        try
-        {
-            CountDownLatch latch = new CountDownLatch(1);
-            HttpExchange exchange = new ConnectionExchange(latch);
-            exchange.setAddress(new Address("localhost", port));
-            exchange.setRequestURI("/");
-            httpClient.send(exchange);
-
-            boolean passed = latch.await(4000, TimeUnit.MILLISECONDS);
-            assertTrue(passed);
-
-            long wait = 100;
-            long maxWait = 10 * wait;
-            long curWait = wait;
-            while (curWait < maxWait && !exchange.isDone())
-            {
-                Thread.sleep(wait);
-                curWait += wait;
-            }
-
-            assertEquals(HttpExchange.STATUS_EXCEPTED, exchange.getStatus());
-        }
-        finally
-        {
-            httpClient.stop();
-        }
-    }
-
-    @Test
-    public void testMultipleConnectionsFailed() throws Exception
-    {
-        ServerSocket serverSocket = new ServerSocket();
-        serverSocket.bind(null);
-        int port=serverSocket.getLocalPort();
-        serverSocket.close();
-
-        HttpClient httpClient = newHttpClient();
-        httpClient.setMaxConnectionsPerAddress(1);
-        httpClient.start();
-        try
-        {
-            HttpExchange[] exchanges = new HttpExchange[20];
-            final CountDownLatch latch = new CountDownLatch(exchanges.length);
-            for (int i = 0; i < exchanges.length; ++i)
-            {
-                HttpExchange exchange = new HttpExchange()
-                {
-                    @Override
-                    protected void onConnectionFailed(Throwable x)
-                    {
-                        latch.countDown();
-                    }
-                };
-                exchange.setAddress(new Address("localhost", port));
-                exchange.setRequestURI("/");
-                exchanges[i] = exchange;
-            }
-
-            for (HttpExchange exchange : exchanges)
-                httpClient.send(exchange);
-
-            for (HttpExchange exchange : exchanges)
-                assertEquals(HttpExchange.STATUS_EXCEPTED, exchange.waitForDone());
-
-            assertTrue(latch.await(1000, TimeUnit.MILLISECONDS));
-        }
-        finally
-        {
-            httpClient.stop();
-        }
-    }
-
-    @Test
-    public void testConnectionTimeout() throws Exception
-    {
-        HttpClient httpClient = newHttpClient();
-        int connectTimeout = 5000;
-        httpClient.setConnectTimeout(connectTimeout);
-        httpClient.start();
-        try
-        {
-            CountDownLatch latch = new CountDownLatch(1);
-            HttpExchange exchange = new ConnectionExchange(latch);
-            // Using a IP address has a different behavior than using a host name
-            exchange.setAddress(new Address("127.0.0.1", 1));
-            exchange.setRequestURI("/");
-            httpClient.send(exchange);
-
-            boolean passed = latch.await(connectTimeout * 2L, TimeUnit.MILLISECONDS);
-            assertTrue(passed);
-
-            int status = exchange.waitForDone();
-            assertEquals(HttpExchange.STATUS_EXCEPTED, status);
-        }
-        finally
-        {
-            httpClient.stop();
-        }
-    }
-
-    @Test
-    public void testIdleConnection() throws Exception
-    {
-        ServerSocket serverSocket = new ServerSocket();
-        serverSocket.bind(null);
-        int port=serverSocket.getLocalPort();
-
-        HttpClient httpClient = newHttpClient();
-        httpClient.setIdleTimeout(700);
-        httpClient.start();
-        try
-        {
-            HttpExchange exchange = new ConnectionExchange();
-            exchange.setAddress(new Address("localhost", port));
-            exchange.setRequestURI("/");
-            HttpDestination dest = httpClient.getDestination(new Address("localhost", port),false);
-
-            httpClient.send(exchange);
-            Socket server = serverSocket.accept();
-            server.setSoTimeout(5000);
-            byte[] buf = new byte[4096];
-            
-            int len=server.getInputStream().read(buf);
-            assertEquals(1,dest.getConnections());
-            assertEquals(0,dest.getIdleConnections());
-
-            server.getOutputStream().write("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n".getBytes());
-            
-            assertEquals(HttpExchange.STATUS_COMPLETED,exchange.waitForDone());
-            Thread.sleep(200); // TODO get rid of this
-            assertEquals(1,dest.getConnections());
-            assertEquals(1,dest.getIdleConnections());
-
-            exchange = new ConnectionExchange();
-            exchange.setAddress(new Address("localhost", port));
-            exchange.setRequestURI("/");
-            httpClient.send(exchange);
-            assertEquals(1,dest.getConnections());
-            assertEquals(0,dest.getIdleConnections());
-            
-            
-            len=server.getInputStream().read(buf);
-            assertEquals(1,dest.getConnections());
-            assertEquals(0,dest.getIdleConnections());
-            server.getOutputStream().write("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n".getBytes());
-
-            Thread.sleep(500);
-
-            assertEquals(1,dest.getConnections());
-            assertEquals(1,dest.getIdleConnections());
-
-            Thread.sleep(500);
-
-            assertEquals(0,dest.getConnections());
-            assertEquals(0,dest.getIdleConnections());
-
-            serverSocket.close();
-        }
-        finally
-        {
-            httpClient.stop();
-        }
-    }
-
-    protected class ConnectionExchange extends HttpExchange
-    {
-        private final CountDownLatch latch;
-
-        protected ConnectionExchange()
-        {
-            this.latch = null;
-        }
-
-        protected ConnectionExchange(CountDownLatch latch)
-        {
-            this.latch = latch;
-        }
-
-        @Override
-        protected void onConnectionFailed(Throwable ex)
-        {
-            if (latch!=null)
-                latch.countDown();
-        }
-
-        @Override
-        protected void onException(Throwable x)
-        {
-            if (latch!=null)
-                latch.countDown();
-        }
-    }
-}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/AbstractHttpClientServerTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/AbstractHttpClientServerTest.java
new file mode 100644
index 0000000..1ca753c
--- /dev/null
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/AbstractHttpClientServerTest.java
@@ -0,0 +1,104 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import org.eclipse.jetty.http.HttpScheme;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.NetworkConnector;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.toolchain.test.TestTracker;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.junit.After;
+import org.junit.Rule;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+ at RunWith(Parameterized.class)
+public abstract class AbstractHttpClientServerTest
+{
+    @Parameterized.Parameters
+    public static Collection<SslContextFactory[]> parameters()
+    {
+        return Arrays.asList(new SslContextFactory[]{null}, new SslContextFactory[]{new SslContextFactory()});
+    }
+
+    @Rule
+    public final TestTracker tracker = new TestTracker();
+
+    protected SslContextFactory sslContextFactory;
+    protected String scheme;
+    protected Server server;
+    protected HttpClient client;
+    protected NetworkConnector connector;
+
+    public AbstractHttpClientServerTest(SslContextFactory sslContextFactory)
+    {
+        this.sslContextFactory = sslContextFactory;
+        this.scheme = (sslContextFactory == null ? HttpScheme.HTTP : HttpScheme.HTTPS).asString();
+    }
+
+    public void start(Handler handler) throws Exception
+    {
+        if (sslContextFactory != null)
+        {
+            sslContextFactory.setEndpointIdentificationAlgorithm("");
+            sslContextFactory.setKeyStorePath("src/test/resources/keystore.jks");
+            sslContextFactory.setKeyStorePassword("storepwd");
+            sslContextFactory.setTrustStorePath("src/test/resources/truststore.jks");
+            sslContextFactory.setTrustStorePassword("storepwd");
+        }
+
+        if (server == null)
+        {
+            QueuedThreadPool serverThreads = new QueuedThreadPool();
+            serverThreads.setName("server");
+            server = new Server(serverThreads);
+        }
+        connector = new ServerConnector(server, sslContextFactory);
+        server.addConnector(connector);
+        server.setHandler(handler);
+        server.start();
+
+        startClient();
+    }
+
+    protected void startClient() throws Exception
+    {
+        QueuedThreadPool clientThreads = new QueuedThreadPool();
+        clientThreads.setName("client");
+        client = new HttpClient(sslContextFactory);
+        client.setExecutor(clientThreads);
+        client.start();
+    }
+
+    @After
+    public void dispose() throws Exception
+    {
+        if (client != null)
+            client.stop();
+        if (server != null)
+            server.stop();
+        server = null;
+    }
+}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/AbstractHttpExchangeCancelTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/AbstractHttpExchangeCancelTest.java
deleted file mode 100644
index f661971..0000000
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/AbstractHttpExchangeCancelTest.java
+++ /dev/null
@@ -1,500 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client;
-
-import java.io.IOException;
-import java.net.SocketTimeoutException;
-import javax.servlet.ServletException;
-import javax.servlet.ServletOutputStream;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.server.AbstractHttpConnection;
-import org.eclipse.jetty.server.Connector;
-import org.eclipse.jetty.server.Request;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.handler.AbstractHandler;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.StdErrLog;
-import org.junit.After;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-
-import static org.hamcrest.Matchers.is;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.assertTrue;
-
-/**
- */
-public abstract class AbstractHttpExchangeCancelTest
-{
-    private Server server;
-    private Connector connector;
-
-    @Before
-    public void setUp() throws Exception
-    {
-        server = new Server();
-        connector = new SelectChannelConnector();
-        server.addConnector(connector);
-        server.setHandler(new EmptyHandler());
-        server.start();
-    }
-
-    @After
-    public void tearDown() throws Exception
-    {
-        server.stop();
-        server.join();
-    }
-
-    /* ------------------------------------------------------------ */
-    @Test
-    public void testHttpExchangeCancelOnSend1() throws Exception
-    {
-        // One of the first things that HttpClient.send() does
-        // is to change the status of the exchange
-        // We exploit that to be sure the exchange is canceled
-        // without race conditions
-        TestHttpExchange exchange = new TestHttpExchange()
-        {
-            @Override
-            boolean setStatus(int status)
-            {
-                // Cancel before setting the new status
-                if (getStatus() == HttpExchange.STATUS_START &&
-                    status == STATUS_WAITING_FOR_CONNECTION)
-                    cancel();
-                return super.setStatus(status);
-            }
-        };
-        exchange.setAddress(newAddress());
-        exchange.setRequestURI("/");
-
-        getHttpClient().send(exchange);
-        // Cancelling here is wrong and makes the test fail spuriously
-        // due to a race condition with send(): the send() can complete
-        // before the exchange is canceled so it will be in STATUS_COMPLETE
-        // which will fail the test.
-//        exchange.cancel();
-
-        int status = exchange.waitForDone();
-        assertEquals(HttpExchange.STATUS_CANCELLED, status);
-        assertFalse(exchange.isResponseCompleted());
-        assertFalse(exchange.isFailed());
-        assertFalse(exchange.isAssociated());
-    }
-
-    /* ------------------------------------------------------------ */
-    @Test
-    public void testHttpExchangeCancelOnSend2() throws Exception
-    {
-        // One of the first things that HttpClient.send() does
-        // is to change the status of the exchange
-        // We exploit that to be sure the exchange is canceled
-        // without race conditions
-        TestHttpExchange exchange = new TestHttpExchange()
-        {
-            @Override
-            boolean setStatus(int status)
-            {
-                // Cancel after setting the new status
-                int oldStatus = getStatus();
-                boolean set = super.setStatus(status);
-                if (oldStatus == STATUS_START &&
-                    getStatus() == HttpExchange.STATUS_WAITING_FOR_CONNECTION)
-                    cancel();
-                return set;
-            }
-        };
-        exchange.setAddress(newAddress());
-        exchange.setRequestURI("/");
-
-        getHttpClient().send(exchange);
-        // Cancelling here is wrong and makes the test fail spuriously
-        // due to a race condition with send(): the send() can complete
-        // before the exchange is canceled so it will be in STATUS_COMPLETE
-        // which will fail the test.
-//        exchange.cancel();
-
-        int status = exchange.waitForDone();
-        assertEquals(HttpExchange.STATUS_CANCELLED, status);
-        assertFalse(exchange.isResponseCompleted());
-        assertFalse(exchange.isFailed());
-        assertFalse(exchange.isAssociated());
-    }
-
-    /* ------------------------------------------------------------ */
-    @Test
-    public void testHttpExchangeCancelOnRequestCommitted() throws Exception
-    {
-        TestHttpExchange exchange = new TestHttpExchange()
-        {
-            @Override
-            protected void onRequestCommitted() throws IOException
-            {
-                super.onRequestCommitted();
-                cancel();
-            }
-        };
-        exchange.setAddress(newAddress());
-        exchange.setRequestURI("/");
-
-        getHttpClient().send(exchange);
-
-        int status = exchange.waitForDone();
-        assertEquals(HttpExchange.STATUS_CANCELLED, status);
-        assertFalse(exchange.isResponseCompleted());
-        assertFalse(exchange.isFailed());
-        assertFalse(exchange.isAssociated());
-    }
-
-    /* ------------------------------------------------------------ */
-    @Test
-    public void testHttpExchangeCancelOnRequestComplete() throws Exception
-    {
-        TestHttpExchange exchange = new TestHttpExchange()
-        {
-            @Override
-            protected void onRequestComplete() throws IOException
-            {
-                super.onRequestComplete();
-                cancel();
-            }
-        };
-        exchange.setAddress(newAddress());
-        exchange.setRequestURI("/");
-
-        getHttpClient().send(exchange);
-
-        int status = exchange.waitForDone();
-        assertThat("Exchange Status", status, is(HttpExchange.STATUS_CANCELLED));
-        assertThat("Exchange.isResponseCompleted", exchange.isResponseCompleted(), is(false));
-        assertThat("Exchange.isFailed", exchange.isFailed(), is(false));
-        assertThat("Exchange.isAssociated", exchange.isAssociated(), is(false));
-    }
-
-    /* ------------------------------------------------------------ */
-    @Test
-    public void testHttpExchangeCancelOnResponseStatus() throws Exception
-    {
-        TestHttpExchange exchange = new TestHttpExchange()
-        {
-            @Override
-            protected void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException
-            {
-                super.onResponseStatus(version, status, reason);
-                cancel();
-            }
-        };
-        exchange.setAddress(newAddress());
-        exchange.setRequestURI("/");
-
-        getHttpClient().send(exchange);
-
-        int status = exchange.waitForDone();
-        assertEquals(HttpExchange.STATUS_CANCELLED, status);
-        assertFalse(exchange.isResponseCompleted());
-        assertFalse(exchange.isFailed());
-        assertFalse(exchange.isAssociated());
-    }
-
-    /* ------------------------------------------------------------ */
-    @Test
-    public void testHttpExchangeCancelOnResponseHeader() throws Exception
-    {
-        TestHttpExchange exchange = new TestHttpExchange()
-        {
-            @Override
-            protected void onResponseHeader(Buffer name, Buffer value) throws IOException
-            {
-                super.onResponseHeader(name, value);
-                cancel();
-            }
-        };
-        exchange.setAddress(newAddress());
-        exchange.setRequestURI("/");
-
-        getHttpClient().send(exchange);
-
-        int status = exchange.waitForDone();
-        assertEquals(HttpExchange.STATUS_CANCELLED, status);
-        assertFalse(exchange.isResponseCompleted());
-        assertFalse(exchange.isFailed());
-        assertFalse(exchange.isAssociated());
-    }
-
-    /* ------------------------------------------------------------ */
-    @Test
-    public void testHttpExchangeCancelOnResponseHeadersComplete() throws Exception
-    {
-        TestHttpExchange exchange = new TestHttpExchange()
-        {
-            @Override
-            protected void onResponseHeaderComplete() throws IOException
-            {
-                super.onResponseHeaderComplete();
-                cancel();
-            }
-        };
-        exchange.setAddress(newAddress());
-        exchange.setRequestURI("/");
-
-        getHttpClient().send(exchange);
-
-        int status = exchange.waitForDone();
-        assertEquals(HttpExchange.STATUS_CANCELLED, status);
-        assertFalse(exchange.isResponseCompleted());
-        assertFalse(exchange.isFailed());
-        assertFalse(exchange.isAssociated());
-    }
-
-    /* ------------------------------------------------------------ */
-    @Test
-    public void testHttpExchangeCancelOnResponseContent() throws Exception
-    {
-        TestHttpExchange exchange = new TestHttpExchange()
-        {
-            @Override
-            protected void onResponseContent(Buffer content) throws IOException
-            {
-                super.onResponseContent(content);
-                cancel();
-            }
-        };
-        exchange.setAddress(newAddress());
-        exchange.setRequestURI("/?action=body");
-
-        getHttpClient().send(exchange);
-
-        int status = exchange.waitForDone();
-        assertEquals(HttpExchange.STATUS_CANCELLED, status);
-        assertFalse(exchange.isResponseCompleted());
-        assertFalse(exchange.isFailed());
-        assertFalse(exchange.isAssociated());
-    }
-
-    /* ------------------------------------------------------------ */
-    @Test
-    public void testHttpExchangeCancelOnResponseComplete() throws Exception
-    {
-        TestHttpExchange exchange = new TestHttpExchange()
-        {
-            @Override
-            protected void onResponseComplete() throws IOException
-            {
-                super.onResponseComplete();
-                cancel();
-            }
-        };
-        exchange.setAddress(newAddress());
-        exchange.setRequestURI("/");
-
-        getHttpClient().send(exchange);
-
-        int status = exchange.waitForDone();
-        assertTrue(exchange.isResponseCompleted());
-        assertFalse(exchange.isFailed());
-        assertFalse(exchange.isAssociated());
-        assertEquals(HttpExchange.STATUS_COMPLETED, status);
-    }
-
-    /* ------------------------------------------------------------ */
-    @Test
-    public void testHttpExchangeOnServerException() throws Exception
-    {
-        try
-        {
-            ((StdErrLog)Log.getLogger(AbstractHttpConnection.class)).setHideStacks(true);
-            TestHttpExchange exchange = new TestHttpExchange();
-            exchange.setAddress(newAddress());
-            exchange.setRequestURI("/?action=throw");
-
-            getHttpClient().send(exchange);
-
-            int status = exchange.waitForDone();
-            assertEquals(HttpExchange.STATUS_COMPLETED, status);
-            assertTrue(exchange.isResponseCompleted());
-            assertFalse(exchange.isFailed());
-            assertFalse(exchange.isAssociated());
-        }
-        finally
-        {
-            ((StdErrLog)Log.getLogger(AbstractHttpConnection.class)).setHideStacks(false);
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    @Test
-    public void testHttpExchangeOnExpire() throws Exception
-    {
-        HttpClient httpClient = getHttpClient();
-        httpClient.stop();
-        httpClient.setTimeout(1000);
-        httpClient.start();
-
-        TestHttpExchange exchange = new TestHttpExchange();
-        exchange.setAddress(newAddress());
-        exchange.setRequestURI("/?action=wait5000");
-
-        long start = System.currentTimeMillis();
-        httpClient.send(exchange);
-
-        int status = exchange.waitForDone();
-        long end = System.currentTimeMillis();
-
-        assertTrue(HttpExchange.STATUS_EXPIRED==status||HttpExchange.STATUS_EXCEPTED==status);
-        assertFalse(exchange.isResponseCompleted());
-        assertTrue(end-start<4000);
-        assertTrue(exchange.isExpired());
-        assertFalse(exchange.isFailed());
-        assertFalse(exchange.isAssociated());
-    }
-
-    @Test
-    public void testHttpExchangeCancelReturnsConnection() throws Exception
-    {
-        TestHttpExchange exchange = new TestHttpExchange();
-        Address address = newAddress();
-        exchange.setAddress(address);
-        long delay = 5000;
-        exchange.setRequestURI("/?action=wait" + delay);
-
-        HttpClient httpClient = getHttpClient();
-        HttpDestination destination = httpClient.getDestination(address, false);
-        int connections = destination.getConnections();
-        httpClient.send(exchange);
-        Thread.sleep(delay / 2);
-        Assert.assertEquals(connections + 1, destination.getConnections());
-
-        exchange.cancel();
-        Assert.assertEquals(connections, destination.getConnections());
-    }
-
-    /* ------------------------------------------------------------ */
-    protected abstract HttpClient getHttpClient();
-
-    /* ------------------------------------------------------------ */
-    protected Address newAddress()
-    {
-        return new Address("localhost", connector.getLocalPort());
-    }
-
-    /* ------------------------------------------------------------ */
-    private static class EmptyHandler extends AbstractHandler
-    {
-        /* ------------------------------------------------------------ */
-        public void handle(String path, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException
-        {
-            request.setHandled(true);
-            String action = httpRequest.getParameter("action");
-            if (action != null)
-            {
-                if ("body".equals(action))
-                {
-                    ServletOutputStream output = httpResponse.getOutputStream();
-                    output.write("body".getBytes("UTF-8"));
-//                    output.flush();
-                }
-                else if ("throw".equals(action))
-                {
-                    throw new ServletException();
-                }
-                else if (action.startsWith("wait"))
-                {
-                    long sleep = Long.valueOf(action.substring("wait".length()));
-                    long start=System.currentTimeMillis();
-                    try
-                    {
-                        Thread.sleep(sleep);
-                        long end=System.currentTimeMillis();
-                        assertTrue("Duration "+(end-start)+" >~ "+sleep,(end-start)>sleep-100);
-                    }
-                    catch (InterruptedException x)
-                    {
-                        Thread.currentThread().interrupt();
-                    }
-                }
-            }
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    protected static class TestHttpExchange extends ContentExchange
-    {
-        private boolean responseCompleted;
-        private boolean failed = false;
-        private boolean expired = false;
-
-        /* ------------------------------------------------------------ */
-        protected TestHttpExchange()
-        {
-            super(true);
-        }
-
-        /* ------------------------------------------------------------ */
-        @Override
-        protected synchronized void onResponseComplete() throws IOException
-        {
-            this.responseCompleted = true;
-        }
-
-        /* ------------------------------------------------------------ */
-        public synchronized boolean isResponseCompleted()
-        {
-            return responseCompleted;
-        }
-
-        /* ------------------------------------------------------------ */
-        @Override
-        protected synchronized void onException(Throwable ex)
-        {
-            LOG.debug(ex);
-            if (ex instanceof SocketTimeoutException ||
-                ex.getCause() instanceof SocketTimeoutException)
-                expired=true;
-            else
-                failed = true;
-        }
-
-        /* ------------------------------------------------------------ */
-        public synchronized boolean isFailed()
-        {
-            return failed;
-        }
-
-        /* ------------------------------------------------------------ */
-        @Override
-        protected synchronized void onExpire()
-        {
-            this.expired = true;
-        }
-
-        /* ------------------------------------------------------------ */
-        public synchronized boolean isExpired()
-        {
-            return expired;
-        }
-    }
-}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/AsyncCallbackHttpExchangeTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/AsyncCallbackHttpExchangeTest.java
deleted file mode 100644
index fba5241..0000000
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/AsyncCallbackHttpExchangeTest.java
+++ /dev/null
@@ -1,110 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client;
-
-import static org.junit.Assert.assertNull;
-
-import java.io.IOException;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicReference;
-
-import org.junit.Test;
-
-/* ------------------------------------------------------------ */
-/**
- * @version $Revision$ $Date$
- */
-public class AsyncCallbackHttpExchangeTest
-{
-    /* ------------------------------------------------------------ */
-    /**
-     * If the HttpExchange callbacks are called holding the lock on HttpExchange,
-     * it will be impossible for the callback to perform some work asynchronously
-     * and contemporarly accessing the HttpExchange instance synchronized state.
-     * This test verifies that this situation does not happen.
-     *
-     * @throws Exception if the test fails
-     */
-    @Test
-    public void testAsyncCallback() throws Exception
-    {
-        ExecutorService executor = Executors.newCachedThreadPool();
-        try
-        {
-            AtomicReference<Exception> failure = new AtomicReference<Exception>();
-            TestHttpExchange exchange = new TestHttpExchange(executor, failure);
-            exchange.setStatus(HttpExchange.STATUS_WAITING_FOR_COMMIT);
-            exchange.setStatus(HttpExchange.STATUS_SENDING_REQUEST);
-            // This status change triggers onRequestCommitted()
-            exchange.setStatus(HttpExchange.STATUS_WAITING_FOR_RESPONSE);
-            assertNull(failure.get());
-        }
-        finally
-        {
-            executor.shutdown();
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    private class TestHttpExchange extends HttpExchange
-    {
-        private final ExecutorService executor;
-        private final AtomicReference<Exception> failure;
-
-        /* ------------------------------------------------------------ */
-        private TestHttpExchange(ExecutorService executor, AtomicReference<Exception> failure)
-        {
-            this.executor = executor;
-            this.failure = failure;
-        }
-
-        /* ------------------------------------------------------------ */
-        @Override
-        protected void onRequestCommitted() throws IOException
-        {
-            Future<Integer> future = executor.submit(new Callable<Integer>()
-            {
-                /* ------------------------------------------------------------ */
-                public Integer call() throws Exception
-                {
-                    // Method getStatus() reads synchronized state
-                    return TestHttpExchange.this.getStatus();
-                }
-            });
-
-            // We're waiting for the future to complete, thus never exiting
-            // this method; if this method is called with the lock held,
-            // this method never completes
-            try
-            {
-                future.get(1000, TimeUnit.MILLISECONDS);
-                // Test green here
-            }
-            catch (Exception x)
-            {
-                // Timed out, the test did not pass
-                failure.set(x);
-            }
-        }
-    }
-}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/AsyncSelectConnectionTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/AsyncSelectConnectionTest.java
deleted file mode 100644
index 92f2816..0000000
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/AsyncSelectConnectionTest.java
+++ /dev/null
@@ -1,73 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client;
-
-import java.io.IOException;
-import java.net.ServerSocket;
-
-import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
-import org.eclipse.jetty.util.ssl.SslContextFactory;
-import org.junit.BeforeClass;
-
-public class AsyncSelectConnectionTest extends AbstractConnectionTest
-{
-    protected HttpClient newHttpClient()
-    {
-        HttpClient httpClient = new HttpClient();
-        httpClient.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);
-        httpClient.setConnectBlocking(false);
-        return httpClient;
-    }
-
-    static SslContextFactory ctx = new SslContextFactory(MavenTestingUtils.getTestResourceFile("keystore").getAbsolutePath());
-
-    @BeforeClass
-    public static void initKS() throws Exception
-    {
-        ctx.setKeyStorePassword("storepwd");
-        ctx.setKeyManagerPassword("keypwd");
-        ctx.start();
-    }
-
-    @Override
-    protected String getScheme()
-    {
-        return "https";
-    }
-    
-    @Override
-    protected ServerSocket newServerSocket() throws IOException
-    {
-        return ctx.newSslServerSocket(null,0,100);
-    }
-
-    @Override
-    public void testServerHalfClosedIncomplete() throws Exception
-    {
-        // SSL doesn't do half closes
-    }
-    
-    @Override
-    public void testServerClosedIncomplete() throws Exception
-    {
-        super.testServerClosedIncomplete();
-    }
-
-    
-}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/AsyncSslHttpExchangeTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/AsyncSslHttpExchangeTest.java
deleted file mode 100644
index 8ab2cd4..0000000
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/AsyncSslHttpExchangeTest.java
+++ /dev/null
@@ -1,53 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client;
-
-import org.eclipse.jetty.client.helperClasses.AsyncSslServerAndClientCreator;
-import org.eclipse.jetty.client.helperClasses.ServerAndClientCreator;
-import org.junit.Before;
-import org.junit.Test;
-
-public class AsyncSslHttpExchangeTest extends SslHttpExchangeTest
-{
-    private static ServerAndClientCreator serverAndClientCreator = new AsyncSslServerAndClientCreator();
-    
-    @Before
-    public void setUpOnce() throws Exception
-    {
-        _scheme="https";
-        _server = serverAndClientCreator.createServer();
-        _httpClient = serverAndClientCreator.createClient(3000L,3500L,2000);
-        _port = _server.getConnectors()[0].getLocalPort();
-    }
-
-
-    @Test
-    public void testPerf1() throws Exception
-    {
-        sender(1,true);
-    }
-
-
-    @Override
-    public void testBigPostWithContentExchange() throws Exception
-    {
-        super.testBigPostWithContentExchange();
-    }
-    
-}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/AsyncSslSecurityListenerTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/AsyncSslSecurityListenerTest.java
deleted file mode 100644
index 0609286..0000000
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/AsyncSslSecurityListenerTest.java
+++ /dev/null
@@ -1,36 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client;
-
-import org.junit.Before;
-
-/* ------------------------------------------------------------ */
-public class AsyncSslSecurityListenerTest extends SslSecurityListenerTest
-{
-
-    /* ------------------------------------------------------------ */
-    @Before
-    @Override
-    public void setUp() throws Exception
-    {
-        _type = HttpClient.CONNECTOR_SELECT_CHANNEL;
-        super.setUp();
-    }
-
-}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/BlockingHttpExchangeCancelTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/BlockingHttpExchangeCancelTest.java
deleted file mode 100644
index 55b883d..0000000
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/BlockingHttpExchangeCancelTest.java
+++ /dev/null
@@ -1,60 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client;
-
-import org.junit.After;
-import org.junit.Before;
-
-
-/* ------------------------------------------------------------ */
-/**
- * @version $Revision$ $Date$
- */
-public class BlockingHttpExchangeCancelTest extends AbstractHttpExchangeCancelTest
-{
-    private HttpClient httpClient;
-
-    /* ------------------------------------------------------------ */
-    @Before
-    @Override
-    public void setUp() throws Exception
-    {
-        super.setUp();
-        httpClient = new HttpClient();
-        httpClient.setConnectorType(HttpClient.CONNECTOR_SOCKET);
-        httpClient.start();
-    }
-
-    /* ------------------------------------------------------------ */
-    @After
-    @Override
-    public void tearDown() throws Exception
-    {
-        httpClient.stop();
-        super.tearDown();
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    protected HttpClient getHttpClient()
-    {
-        return httpClient;
-    }
-   
-}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/CachedHeadersIsolationTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/CachedHeadersIsolationTest.java
deleted file mode 100644
index 2215462..0000000
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/CachedHeadersIsolationTest.java
+++ /dev/null
@@ -1,140 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client;
-
-import static org.junit.Assert.assertEquals;
-
-import java.io.IOException;
-import java.util.Enumeration;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jetty.http.HttpStatus;
-import org.eclipse.jetty.server.Connector;
-import org.eclipse.jetty.server.Request;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.handler.AbstractHandler;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-/* ------------------------------------------------------------ */
-public class CachedHeadersIsolationTest
-{
-
-    Server server;
-    HttpClient client;
-    int port;
-
-    @Before
-    public void setUp() throws Exception
-    {
-        server = new Server();
-
-        Connector connector = new SelectChannelConnector();
-
-        server.addConnector(connector);
-
-        server.setHandler(new AbstractHandler()
-        {
-            /* ------------------------------------------------------------ */
-            public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException,
-                    ServletException
-            {
-                response.setStatus(HttpStatus.OK_200);
-                response.addHeader("For",request.getQueryString());
-                response.addHeader("Name","Value");
-                response.getOutputStream().print("blah");
-                response.flushBuffer();
-            }
-        });
-
-        server.start();
-
-        port = server.getConnectors()[0].getLocalPort();
-
-        client = new HttpClient();
-        client.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);
-        client.setConnectTimeout(5);
-        client.start();
-
-    }
-
-    /* ------------------------------------------------------------ */
-    @After
-    public void tearDown() throws Exception
-    {
-        server.stop();
-        client.stop();
-    }
-
-    /* ------------------------------------------------------------ */
-    @Test
-    public void testHeaderWhenReadEarly() throws Exception
-    {
-
-        CachedExchange e1 = new CachedExchange(true);
-        CachedExchange e2 = new CachedExchange(true);
-
-        e1.setURL("http://localhost:" + port + "/?a=short");
-        e2.setURL("http://localhost:" + port + "/?a=something_longer");
-
-        client.send(e1);
-        while (!e1.isDone())
-            Thread.sleep(100);
-
-        assertEquals("Read buffer","Value",e1.getResponseFields().getStringField("Name"));
-
-        client.send(e2);
-        while (!e2.isDone())
-            Thread.sleep(100);
-
-        assertEquals("Overwritten buffer","Value",e1.getResponseFields().getStringField("Name"));
-    }
-
-    /* ------------------------------------------------------------ */
-    @Test
-    public void testHeaderWhenReadLate() throws Exception
-    {
-
-        CachedExchange e1 = new CachedExchange(true);
-        CachedExchange e2 = new CachedExchange(true);
-
-        e1.setURL("http://localhost:" + port + "/?a=short");
-        e2.setURL("http://localhost:" + port + "/?a=something_longer");
-
-        client.send(e1);
-        while (!e1.isDone())
-            Thread.sleep(100);
-
-        client.send(e2);
-        while (!e2.isDone())
-            Thread.sleep(100);
-
-        for ( Enumeration<String> e = e1.getResponseFields().getValues("Name"); e.hasMoreElements();)
-        {
-            System.out.println(e.nextElement());
-        }
-        
-        assertEquals("Overwritten buffer","Value",e1.getResponseFields().getStringField("Name"));
-    }
-}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/ContentExchangeTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/ContentExchangeTest.java
deleted file mode 100644
index b0f2100..0000000
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/ContentExchangeTest.java
+++ /dev/null
@@ -1,389 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.URLDecoder;
-
-import javax.servlet.ServletException;
-import javax.servlet.ServletInputStream;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jetty.client.security.Realm;
-import org.eclipse.jetty.client.security.SimpleRealmResolver;
-import org.eclipse.jetty.http.HttpMethods;
-import org.eclipse.jetty.http.HttpStatus;
-import org.eclipse.jetty.io.ByteArrayBuffer;
-import org.eclipse.jetty.io.EofException;
-import org.eclipse.jetty.server.Handler;
-import org.eclipse.jetty.server.Request;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.handler.AbstractHandler;
-import org.eclipse.jetty.server.handler.HandlerCollection;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
-import org.eclipse.jetty.servlet.DefaultServlet;
-import org.eclipse.jetty.servlet.ServletContextHandler;
-import org.eclipse.jetty.servlet.ServletHolder;
-import org.eclipse.jetty.util.IO;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-public class ContentExchangeTest
-{
-    private static String _content =
-        "Lorem ipsum dolor sit amet, consectetur adipiscing elit. In quis felis nunc. "+
-        "Quisque suscipit mauris et ante auctor ornare rhoncus lacus aliquet. Pellentesque "+
-        "habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. "+
-        "Vestibulum sit amet felis augue, vel convallis dolor. Cras accumsan vehicula diam "+
-        "at faucibus. Etiam in urna turpis, sed congue mi. Morbi et lorem eros. Donec vulputate "+
-        "velit in risus suscipit lobortis. Aliquam id urna orci, nec sollicitudin ipsum. "+
-        "Cras a orci turpis. Donec suscipit vulputate cursus. Mauris nunc tellus, fermentum "+
-        "eu auctor ut, mollis at diam. Quisque porttitor ultrices metus, vitae tincidunt massa "+
-        "sollicitudin a. Vivamus porttitor libero eget purus hendrerit cursus. Integer aliquam "+
-        "consequat mauris quis luctus. Cras enim nibh, dignissim eu faucibus ac, mollis nec neque. "+
-        "Aliquam purus mauris, consectetur nec convallis lacinia, porta sed ante. Suspendisse "+
-        "et cursus magna. Donec orci enim, molestie a lobortis eu, imperdiet vitae neque.";
-
-    private File _docRoot;
-    private Server _server;
-    private HttpClient _client;
-    private Realm _realm;
-    private String _protocol;
-    private String _baseUrl;
-    private String _requestContent;
-
-    /* ------------------------------------------------------------ */
-    @Before
-    public void setUp()
-        throws Exception
-    {
-        _docRoot = new File("target/test-output/docroot/");
-        _docRoot.mkdirs();
-        _docRoot.deleteOnExit();
-        
-        File content = new File(_docRoot,"input.txt");
-        FileOutputStream out = new FileOutputStream(content);
-        out.write(_content.getBytes("utf-8"));
-        out.close();
-        
-        _server = new Server();
-        configureServer(_server);
-        _server.start();
-
-        int port = _server.getConnectors()[0].getLocalPort();
-        _baseUrl = _protocol+"://localhost:"+port+ "/";
-    }
-    
-    /* ------------------------------------------------------------ */
-    @After
-    public void tearDown()
-        throws Exception
-    {
-        if (_server != null)
-        {
-            _server.stop();
-            _server = null;
-        }
-    }
-    
-    /* ------------------------------------------------------------ */
-    @Test
-    public void testPut() throws Exception
-    {
-        startClient(_realm);
-    
-        ContentExchange putExchange = new ContentExchange();
-        putExchange.setURL(getBaseUrl() + "output.txt");
-        putExchange.setMethod(HttpMethods.PUT);
-        putExchange.setRequestContent(new ByteArrayBuffer(_content.getBytes()));
-    
-        _client.send(putExchange);
-        int state = putExchange.waitForDone();
-    
-        int responseStatus = putExchange.getResponseStatus();
-    
-        stopClient();
-    
-        boolean statusOk = (responseStatus == 200 || responseStatus == 201);
-        assertTrue(statusOk);
-        
-        String content = IO.toString(new FileInputStream(new File(_docRoot,"output.txt")));
-        assertEquals(_content,content);
-    }
-    
-    /* ------------------------------------------------------------ */
-    @Test
-    public void testGet() throws Exception
-    {
-        startClient(_realm);
-    
-        ContentExchange getExchange = new ContentExchange();
-        getExchange.setURL(getBaseUrl() + "input.txt");
-        getExchange.setMethod(HttpMethods.GET);
-    
-        _client.send(getExchange);
-        int state = getExchange.waitForDone();
-    
-        String content = "";
-        int responseStatus = getExchange.getResponseStatus();
-        if (responseStatus == HttpStatus.OK_200)
-        {
-            content = getExchange.getResponseContent();
-        }
-    
-        stopClient();
-    
-        assertEquals(HttpStatus.OK_200,responseStatus);
-        assertEquals(_content,content);
-    }
-    
-    /* ------------------------------------------------------------ */
-    @Test
-    public void testHead() throws Exception
-    {
-        startClient(_realm);
-    
-        ContentExchange getExchange = new ContentExchange();
-        getExchange.setURL(getBaseUrl() + "input.txt");
-        getExchange.setMethod(HttpMethods.HEAD);
-    
-        _client.send(getExchange);
-        getExchange.waitForDone();
-    
-        int responseStatus = getExchange.getResponseStatus();
-
-        stopClient();
-    
-        assertEquals(HttpStatus.OK_200,responseStatus);
-    }
-    
-    /* ------------------------------------------------------------ */
-    @Test
-    public void testPost() throws Exception
-    {
-        startClient(_realm);
-    
-        ContentExchange postExchange = new ContentExchange();
-        postExchange.setURL(getBaseUrl() + "test");
-        postExchange.setMethod(HttpMethods.POST);
-        postExchange.setRequestContent(new ByteArrayBuffer(_content.getBytes()));
-   
-        _client.send(postExchange);
-        int state = postExchange.waitForDone();
-    
-        int responseStatus = postExchange.getResponseStatus();
- 
-        stopClient();
-    
-        assertEquals(HttpStatus.OK_200,responseStatus);
-        assertEquals(_content,_requestContent);
-    }
-    
-    /* ------------------------------------------------------------ */
-    protected void configureServer(Server server)
-        throws Exception
-    {
-        setProtocol("http");
-        
-        SelectChannelConnector connector = new SelectChannelConnector();
-        server.addConnector(connector);
-
-        Handler handler = new TestHandler(getBasePath());
-        
-        ServletContextHandler root = new ServletContextHandler();
-        root.setContextPath("/");
-        root.setResourceBase(_docRoot.getAbsolutePath());
-        ServletHolder servletHolder = new ServletHolder( new DefaultServlet() );
-        servletHolder.setInitParameter( "gzip", "true" );
-        root.addServlet( servletHolder, "/*" );    
-
-        HandlerCollection handlers = new HandlerCollection();
-        handlers.setHandlers(new Handler[]{handler, root});
-        server.setHandler( handlers ); 
-
-    }
-    
-    /* ------------------------------------------------------------ */
-    protected void startClient(Realm realm)
-        throws Exception
-    {
-        _client = new HttpClient();
-        configureClient(_client);
-        
-        if (realm != null)
-            _client.setRealmResolver(new SimpleRealmResolver(realm));
-        
-        _client.start();
-    }
-    
-    /* ------------------------------------------------------------ */
-    protected void configureClient(HttpClient client)
-        throws Exception
-    {
-        client.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);
-    }
-
-    /* ------------------------------------------------------------ */
-    protected void stopClient()
-        throws Exception
-    {
-        if (_client != null)
-        {
-            _client.stop();
-            _client = null;
-        }
-    }
-    
-    /* ------------------------------------------------------------ */
-    protected String getBasePath()
-    {
-        return _docRoot.getAbsolutePath();
-    }
-    
-    /* ------------------------------------------------------------ */
-    protected String getBaseUrl()
-    {
-        return _baseUrl;
-    }
-    
-    /* ------------------------------------------------------------ */
-    protected HttpClient getClient()
-    {
-        return _client;
-    }
-    
-    /* ------------------------------------------------------------ */
-    protected Realm getRealm()
-    {
-        return _realm;
-    }
-    
-    /* ------------------------------------------------------------ */
-    protected String getContent()
-    {
-        return _content;
-    }
-    
-    /* ------------------------------------------------------------ */
-    protected void setProtocol(String protocol)
-    {
-        _protocol = protocol;
-    }
-    
-    /* ------------------------------------------------------------ */
-    protected void setRealm(Realm realm)
-    {
-        _realm = realm;
-    }
-    
-    /* ------------------------------------------------------------ */
-    public static void copyStream(InputStream in, OutputStream out)
-    {
-        try
-        {
-            byte[] buffer=new byte[1024];
-            int len;
-            while ((len=in.read(buffer))>=0)
-            {
-                out.write(buffer,0,len);
-            }
-        }
-        catch (EofException e)
-        {
-            System.err.println(e);
-        }
-        catch (IOException e)
-        {
-            e.printStackTrace();
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    protected class TestHandler extends AbstractHandler {
-        private final String resourcePath;
-
-        /* ------------------------------------------------------------ */
-        public TestHandler(String repositoryPath) {
-            this.resourcePath = repositoryPath;
-        }
-
-        /* ------------------------------------------------------------ */
-        public void handle(String target, Request baseRequest,
-                HttpServletRequest request, HttpServletResponse response)
-            throws IOException, ServletException
-        {
-            if (baseRequest.isHandled())
-            {
-                return;
-            }
-
-            OutputStream out = null;
-            
-            if (baseRequest.getMethod().equals("PUT"))
-            {
-            	baseRequest.setHandled(true);
-
-            	File file = new File(resourcePath, URLDecoder.decode(request.getPathInfo()));
-            	file.getParentFile().mkdirs();
-            	file.deleteOnExit();
-            
-            	out = new FileOutputStream(file);
-
-	            response.setStatus(HttpServletResponse.SC_CREATED);
-            }
-            
-            if (baseRequest.getMethod().equals("POST"))
-            {
-            	baseRequest.setHandled(true);
-            	out = new ByteArrayOutputStream();
-
-	        response.setStatus(HttpServletResponse.SC_OK);
-            }
-            
-            if (out != null)
-            {
-                ServletInputStream in = request.getInputStream();
-	            try
-	            {
-	                copyStream( in, out );
-	            }
-	            finally
-	            {
-	                in.close();
-	                out.close();
-	            }
-	            
-	            if (!(out instanceof FileOutputStream))
-	            	_requestContent = out.toString();
-            }
-            
-        }
-    }
-}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/ContentResponseTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/ContentResponseTest.java
new file mode 100644
index 0000000..e9094af
--- /dev/null
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/ContentResponseTest.java
@@ -0,0 +1,124 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.io.IOException;
+import java.util.Random;
+import java.util.concurrent.TimeUnit;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class ContentResponseTest extends AbstractHttpClientServerTest
+{
+    public ContentResponseTest(SslContextFactory sslContextFactory)
+    {
+        super(sslContextFactory);
+    }
+
+    @Test
+    public void testResponseWithoutContentType() throws Exception
+    {
+        final byte[] content = new byte[1024];
+        new Random().nextBytes(content);
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                response.getOutputStream().write(content);
+            }
+        });
+
+        ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        Assert.assertEquals(200, response.getStatus());
+        Assert.assertArrayEquals(content, response.getContent());
+        Assert.assertNull(response.getMediaType());
+        Assert.assertNull(response.getEncoding());
+    }
+
+    @Test
+    public void testResponseWithMediaType() throws Exception
+    {
+        final String content = "The quick brown fox jumped over the lazy dog";
+        final String mediaType = "text/plain";
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                response.setHeader(HttpHeader.CONTENT_TYPE.asString(), mediaType);
+                response.getOutputStream().write(content.getBytes("UTF-8"));
+            }
+        });
+
+        ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        Assert.assertEquals(200, response.getStatus());
+        Assert.assertEquals(content, response.getContentAsString());
+        Assert.assertEquals(mediaType, response.getMediaType());
+        Assert.assertNull(response.getEncoding());
+    }
+
+    @Test
+    public void testResponseWithContentType() throws Exception
+    {
+        final String content = "The quick brown fox jumped over the lazy dog";
+        final String mediaType = "text/plain";
+        final String encoding = "UTF-8";
+        final String contentType = mediaType + "; charset=" + encoding;
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                response.setHeader(HttpHeader.CONTENT_TYPE.asString(), contentType);
+                response.getOutputStream().write(content.getBytes("UTF-8"));
+            }
+        });
+
+        ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        Assert.assertEquals(200, response.getStatus());
+        Assert.assertEquals(content, response.getContentAsString());
+        Assert.assertEquals(mediaType, response.getMediaType());
+        Assert.assertEquals(encoding, response.getEncoding());
+    }
+}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/Curl.java b/jetty-client/src/test/java/org/eclipse/jetty/client/Curl.java
deleted file mode 100644
index 26ed9de..0000000
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/Curl.java
+++ /dev/null
@@ -1,200 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client;
-
-import java.io.IOException;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import org.eclipse.jetty.http.HttpMethods;
-import org.eclipse.jetty.io.Buffer;
-
-
-/* ------------------------------------------------------------ */
-/** A simple test http client like curl.
- * <p>
- * Usage is java -cp $CLASSPATH org.eclipse.jetty.client.Curl [ option | URL ] ...
- * Options supported are: <ul>
- * <li>--async   : The following URLs are fetched in parallel (default)
- * <li>--sync    : The following URLs are fetched in sequence
- * <li>--dump    : The content is dumped to stdout
- * <li>--nodump  : The content is suppressed (default)
- * </ul>
- */
-public class Curl
-{
-    public static void main(String[] args)
-        throws Exception
-    {
-        if (args.length==0)
-            args=new String[] 
-                 { "--sync", "http://www.sun.com/robots.txt", "http://www.sun.com/favicon.ico" , "--dump", "http://www.sun.com/robots.txt"};
-        
-        HttpClient client = new HttpClient();
-        client.setIdleTimeout(2000);
-        client.start();
-        boolean async=true;
-        boolean dump= false;
-        boolean verbose= false;
-        
-        
-        int urls=0;
-        for (String arg : args)
-        {
-            if (!arg.startsWith("-"))
-                urls++;
-        }
-
-        final CountDownLatch latch = new CountDownLatch(urls);        
-        
-        for (String arg : args)
-        {
-            if ("--verbose".equals(arg))
-            {
-                verbose=true;
-                continue;
-            }
-            
-            if ("--sync".equals(arg))
-            {
-                async=false;
-                continue;
-            }
-            
-            if ("--async".equals(arg))
-            {
-                async=true;
-                continue;
-            }
-
-            if ("--dump".equals(arg))
-            {
-                dump=true;
-                continue;
-            }
-            
-            if ("--nodump".equals(arg))
-            {
-                dump=false;
-                continue;
-            }
-
-            final boolean d = dump;
-            final boolean v = verbose;
-            HttpExchange ex = new HttpExchange()
-            {
-                AtomicBoolean counted=new AtomicBoolean(false);
-
-                @Override
-                protected void onConnectionFailed(Throwable ex)
-                {
-                    if (!counted.getAndSet(true))
-                        latch.countDown();
-                    super.onConnectionFailed(ex);
-                }
-
-                @Override
-                protected void onException(Throwable ex)
-                {
-                    if (!counted.getAndSet(true))
-                        latch.countDown();
-                    super.onException(ex);
-                }
-
-                @Override
-                protected void onExpire()
-                {
-                    if (!counted.getAndSet(true))
-                        latch.countDown();
-                    super.onExpire();
-                }
-
-                @Override
-                protected void onResponseComplete() throws IOException
-                {
-                    if (!counted.getAndSet(true))
-                        latch.countDown();
-                    super.onResponseComplete();
-                }
-
-                @Override
-                protected void onResponseContent(Buffer content) throws IOException
-                {
-                    super.onResponseContent(content);
-                    if (d)
-                        System.out.print(content.toString());
-                    if (v)
-                        System.err.println("got "+content.length());
-                }
-
-                /* ------------------------------------------------------------ */
-                /**
-                 * @see org.eclipse.jetty.client.HttpExchange#onResponseHeader(org.eclipse.jetty.io.Buffer, org.eclipse.jetty.io.Buffer)
-                 */
-                @Override
-                protected void onResponseHeader(Buffer name, Buffer value) throws IOException
-                {
-                    super.onResponseHeader(name,value);
-                    if (v)
-                        System.err.println(name+": "+value);
-                }
-
-                /* ------------------------------------------------------------ */
-                /**
-                 * @see org.eclipse.jetty.client.HttpExchange#onResponseHeaderComplete()
-                 */
-                @Override
-                protected void onResponseHeaderComplete() throws IOException
-                {
-                    super.onResponseHeaderComplete();
-                    if (v)
-                        System.err.println();
-                }
-
-                /* ------------------------------------------------------------ */
-                /**
-                 * @see org.eclipse.jetty.client.HttpExchange#onResponseStatus(org.eclipse.jetty.io.Buffer, int, org.eclipse.jetty.io.Buffer)
-                 */
-                @Override
-                protected void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException
-                {
-                    super.onResponseStatus(version,status,reason);
-                    if (v)
-                        System.err.println(version+" "+status+" "+reason);
-                }
-            };
-            
-            ex.setMethod(HttpMethods.GET);
-            ex.setURL(arg);
-
-            System.err.println("\nSending "+ex);
-            client.send(ex);
-            
-            if (!async)
-            {
-                System.err.println("waiting...");
-                ex.waitForDone();
-                System.err.println("Done");
-            }
-            
-        }
-        
-        latch.await();
-    }
-}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/EmptyServerHandler.java b/jetty-client/src/test/java/org/eclipse/jetty/client/EmptyServerHandler.java
new file mode 100644
index 0000000..b22854e
--- /dev/null
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/EmptyServerHandler.java
@@ -0,0 +1,37 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+
+public class EmptyServerHandler extends AbstractHandler
+{
+    @Override
+    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+    {
+        baseRequest.setHandled(true);
+    }
+}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/ErrorStatusTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/ErrorStatusTest.java
deleted file mode 100644
index eca8015..0000000
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/ErrorStatusTest.java
+++ /dev/null
@@ -1,276 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client;
-
-import static org.junit.Assert.assertEquals;
-
-import java.io.IOException;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jetty.http.HttpMethods;
-import org.eclipse.jetty.http.HttpStatus;
-import org.eclipse.jetty.io.ByteArrayBuffer;
-import org.eclipse.jetty.server.Handler;
-import org.eclipse.jetty.server.Request;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.handler.AbstractHandler;
-import org.eclipse.jetty.server.handler.HandlerCollection;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
-import org.eclipse.jetty.servlet.DefaultServlet;
-import org.eclipse.jetty.servlet.ServletContextHandler;
-import org.eclipse.jetty.servlet.ServletHolder;
-import org.junit.Test;
-
-/* ------------------------------------------------------------ */
-public class ErrorStatusTest
-    extends ContentExchangeTest
-{
-
-    /* ------------------------------------------------------------ */
-    @Test
-    public void testPutBadRequest()
-        throws Exception
-    {
-        doPutFail(HttpStatus.BAD_REQUEST_400);
-    }
-    
-    /* ------------------------------------------------------------ */
-    @Test
-    public void testPutUnauthorized()
-        throws Exception
-    {
-        doPutFail(HttpStatus.UNAUTHORIZED_401);
-    }
-    
-    /* ------------------------------------------------------------ */
-    @Test
-    public void testPutForbidden()
-        throws Exception
-    {
-        doPutFail(HttpStatus.FORBIDDEN_403);
-    }
-    
-    /* ------------------------------------------------------------ */
-    @Test
-    public void testPutNotFound()
-        throws Exception
-    {
-        doPutFail(HttpStatus.NOT_FOUND_404);    
-    }
-    
-    /* ------------------------------------------------------------ */
-    @Test
-    public void testPutServerError()
-        throws Exception
-    {
-        doPutFail(HttpStatus.INTERNAL_SERVER_ERROR_500);    
-    }
-    
-    /* ------------------------------------------------------------ */
-    @Test
-    public void testGetBadRequest()
-        throws Exception
-    {
-        doGetFail(HttpStatus.BAD_REQUEST_400);
-    }
-    
-    /* ------------------------------------------------------------ */
-    @Test
-    public void testGetUnauthorized()
-        throws Exception
-    {
-        doGetFail(HttpStatus.UNAUTHORIZED_401);
-    }
-    
-    /* ------------------------------------------------------------ */
-    @Test
-    public void testGetNotFound()
-        throws Exception
-    {
-        doGetFail(HttpStatus.NOT_FOUND_404);    
-    }
-    
-    /* ------------------------------------------------------------ */
-    @Test
-    public void testGetServerError()
-        throws Exception
-    {
-        doGetFail(HttpStatus.INTERNAL_SERVER_ERROR_500);    
-    }
-    
-    /* ------------------------------------------------------------ */
-    @Test
-    public void testPostBadRequest() 
-        throws Exception
-    {
-        doPostFail(HttpStatus.BAD_REQUEST_400);
-    }
-
-    /* ------------------------------------------------------------ */
-    @Test
-    public void testPostUnauthorized()
-        throws Exception
-    {
-        doPostFail(HttpStatus.UNAUTHORIZED_401);
-    }
-
-    /* ------------------------------------------------------------ */
-    @Test
-    public void testPostForbidden()
-        throws Exception
-    {
-        doPostFail(HttpStatus.FORBIDDEN_403);
-    }
-
-    /* ------------------------------------------------------------ */
-    @Test
-    public void testPostNotFound()
-        throws Exception
-    {
-        doPostFail(HttpStatus.NOT_FOUND_404);
-    }
-
-    /* ------------------------------------------------------------ */
-    @Test
-    public void testPostServerError()
-        throws Exception
-    {
-        doPostFail(HttpStatus.INTERNAL_SERVER_ERROR_500);
-    }
-
-    /* ------------------------------------------------------------ */
-    protected void doPutFail(int status)
-        throws Exception
-    {
-        startClient(getRealm());
-
-        ContentExchange putExchange = new ContentExchange();
-        putExchange.setURL(getBaseUrl() + "output.txt");
-        putExchange.setMethod(HttpMethods.PUT);
-        putExchange.setRequestHeader("X-Response-Status",Integer.toString(status));
-        putExchange.setRequestContent(new ByteArrayBuffer(getContent().getBytes()));
-        
-        getClient().send(putExchange);
-        int state = putExchange.waitForDone();
-                
-        int responseStatus = putExchange.getResponseStatus();
-        
-        stopClient();
-
-        assertEquals(status, responseStatus);            
-    }
-    
-    /* ------------------------------------------------------------ */
-    protected void doGetFail(int status)
-        throws Exception
-    {
-        startClient(getRealm());
-
-        ContentExchange getExchange = new ContentExchange();
-        getExchange.setURL(getBaseUrl() + "input.txt");
-        getExchange.setMethod(HttpMethods.GET);
-        getExchange.setRequestHeader("X-Response-Status",Integer.toString(status));
-       
-        getClient().send(getExchange);
-        int state = getExchange.waitForDone();
-        
-        String content;
-        int responseStatus = getExchange.getResponseStatus();
-        if (responseStatus == HttpStatus.OK_200) {
-            content = getExchange.getResponseContent();
-        }
-        
-        stopClient();
-
-        assertEquals(status, responseStatus);
-    }
-    
-    /* ------------------------------------------------------------ */
-    protected void doPostFail(int status)
-        throws Exception
-    {
-        startClient(getRealm());
-    
-        ContentExchange postExchange = new ContentExchange();
-        postExchange.setURL(getBaseUrl() + "test");
-        postExchange.setMethod(HttpMethods.POST);
-        postExchange.setRequestHeader("X-Response-Status",Integer.toString(status));
-        postExchange.setRequestContent(new ByteArrayBuffer(getContent().getBytes()));
-        
-        getClient().send(postExchange);
-        int state = postExchange.waitForDone();
-                
-        int responseStatus = postExchange.getResponseStatus();
-        
-        stopClient();
-    
-        assertEquals(status, responseStatus);            
-    }
-
-    /* ------------------------------------------------------------ */
-    protected void configureServer(Server server)
-        throws Exception
-    {
-        setProtocol("http");
-    
-        SelectChannelConnector connector = new SelectChannelConnector();
-        server.addConnector(connector);
-        
-        ServletContextHandler root = new ServletContextHandler();
-        root.setContextPath("/");
-        root.setResourceBase(getBasePath());
-        ServletHolder servletHolder = new ServletHolder( new DefaultServlet() );
-        servletHolder.setInitParameter( "gzip", "true" );
-        root.addServlet( servletHolder, "/*" );    
-
-        Handler status = new StatusHandler();
-        Handler test = new TestHandler(getBasePath());
-        
-        HandlerCollection handlers = new HandlerCollection();
-        handlers.setHandlers(new Handler[]{status, test, root});
-        server.setHandler( handlers ); 
-    }
-    
-    /* ------------------------------------------------------------ */
-    protected static class StatusHandler extends AbstractHandler {
-        /* ------------------------------------------------------------ */
-        public void handle(String target, Request baseRequest,
-                HttpServletRequest request, HttpServletResponse response)
-            throws IOException, ServletException
-        {
-            if (baseRequest.isHandled())
-                return;
-
-            int statusValue = 0;
-            String statusHeader = request.getHeader("X-Response-Status");
-            if (statusHeader != null)
-            {
-                statusValue = Integer.parseInt(statusHeader);
-            }
-            if (statusValue != 0)
-            {
-                response.setStatus(statusValue);
-                baseRequest.setHandled(true);
-            }
-        }
-    }
-}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/ExpirationWithLimitedConnectionsTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/ExpirationWithLimitedConnectionsTest.java
deleted file mode 100644
index 92de422..0000000
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/ExpirationWithLimitedConnectionsTest.java
+++ /dev/null
@@ -1,197 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.net.ServerSocket;
-import java.net.Socket;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicLong;
-
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-import org.junit.Assert;
-import org.junit.Test;
-import org.junit.Ignore;
-
-public class ExpirationWithLimitedConnectionsTest
-{
-    @Ignore
-    public void testExpirationWithMaxConnectionPerAddressReached() throws Exception
-    {
-        final Logger logger = Log.getLogger("org.eclipse.jetty.client");
-        logger.setDebugEnabled(true);
-
-        HttpClient client = new HttpClient();
-        int maxConnectionsPerAddress = 10;
-        client.setMaxConnectionsPerAddress(maxConnectionsPerAddress);
-        long timeout = 1000;
-        client.setTimeout(timeout);
-        client.start();
-
-        final List<Socket> sockets = new CopyOnWriteArrayList<Socket>();
-        final List<Exception> failures = new CopyOnWriteArrayList<Exception>();
-        final AtomicLong processingDelay = new AtomicLong(200);
-
-        final ExecutorService threadPool = Executors.newCachedThreadPool();
-        final ServerSocket server = new ServerSocket(0);
-        threadPool.submit(new Runnable()
-        {
-            public void run()
-            {
-                while (true)
-                {
-                    try
-                    {
-                        final Socket socket = server.accept();
-                        sockets.add(socket);
-                        logger.debug("CONNECTION {}", socket.getRemoteSocketAddress());
-                        threadPool.submit(new Runnable()
-                        {
-                            public void run()
-                            {
-                                while (true)
-                                {
-                                    try
-                                    {
-                                        BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));
-                                        String firstLine = reader.readLine();
-                                        String line = firstLine;
-                                        while (line != null)
-                                        {
-                                            if (line.length() == 0)
-                                                break;
-                                            line = reader.readLine();
-                                        }
-
-                                        if (line == null)
-                                            break;
-
-                                        long sleep = processingDelay.get();
-                                        logger.debug("{} {} {} ms", firstLine, socket.getRemoteSocketAddress(), sleep);
-                                        TimeUnit.MILLISECONDS.sleep(sleep);
-
-                                        String response = "" +
-                                                "HTTP/1.1 200 OK\r\n" +
-                                                "Content-Length: 0\r\n" +
-                                                "\r\n";
-                                        OutputStream output = socket.getOutputStream();
-                                        output.write(response.getBytes("UTF-8"));
-                                        output.flush();
-                                    }
-                                    catch (Exception x)
-                                    {
-                                        failures.add(x);
-                                        break;
-                                    }
-                                }
-                            }
-                        });
-                    }
-                    catch (Exception x)
-                    {
-                        failures.add(x);
-                        break;
-                    }
-                }
-            }
-        });
-
-        List<ContentExchange> exchanges = new ArrayList<ContentExchange>();
-
-        final AtomicBoolean firstExpired = new AtomicBoolean();
-        int count = 0;
-        int maxAdditionalRequest = 100;
-        int additionalRequests = 0;
-        while (true)
-        {
-            TimeUnit.MILLISECONDS.sleep(1); // Just avoid being too fast
-            ContentExchange exchange = new ContentExchange(true)
-            {
-                @Override
-                protected void onResponseComplete() throws IOException
-                {
-                    logger.debug("{} {} OK", getMethod(), getRequestURI());
-                }
-
-                @Override
-                protected void onExpire()
-                {
-                    logger.debug("{} {} EXPIRED {}", getMethod(), getRequestURI(), this);
-                    firstExpired.compareAndSet(false, true);
-                }
-            };
-            exchanges.add(exchange);
-            Address address = new Address("localhost", server.getLocalPort());
-            exchange.setAddress(address);
-            exchange.setMethod("GET");
-            exchange.setRequestURI("/" + count);
-            exchange.setVersion("HTTP/1.1");
-            exchange.setRequestHeader("Host", address.toString());
-            logger.debug("{} {} SENT", exchange.getMethod(), exchange.getRequestURI());
-            client.send(exchange);
-            ++count;
-
-            if (processingDelay.get() > 0)
-            {
-                if (client.getDestination(address, false).getConnections() == maxConnectionsPerAddress)
-                {
-                    if (firstExpired.get())
-                    {
-                        ++additionalRequests;
-                        if (additionalRequests == maxAdditionalRequest)
-                            processingDelay.set(0);
-                    }
-                }
-            }
-            else
-            {
-                ++additionalRequests;
-                if (additionalRequests == 2 * maxAdditionalRequest)
-                    break;
-            }
-        }
-
-        for (ContentExchange exchange : exchanges)
-        {
-            int status = exchange.waitForDone();
-            Assert.assertTrue(status == HttpExchange.STATUS_COMPLETED || status == HttpExchange.STATUS_EXPIRED);
-        }
-
-        client.stop();
-
-        Assert.assertTrue(failures.isEmpty());
-
-        for (Socket socket : sockets)
-            socket.close();
-        server.close();
-
-        threadPool.shutdown();
-        threadPool.awaitTermination(5, TimeUnit.SECONDS);
-    }
-}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/ExpireTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/ExpireTest.java
deleted file mode 100644
index e339aea..0000000
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/ExpireTest.java
+++ /dev/null
@@ -1,159 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client;
-
-import static org.junit.Assert.assertTrue;
-
-import java.io.IOException;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jetty.server.Request;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.handler.AbstractHandler;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
-import org.junit.After;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-
-/**
- * Test contributed by: Michiel Thuys for JETTY-806
- */
-public class ExpireTest
-{
-    private Server server;
-    private HttpClient client;
-    private int port;
-
-    @Before
-    public void init() throws Exception
-    {
-        server = new Server();
-        SelectChannelConnector connector = new SelectChannelConnector();
-        connector.setHost("localhost");
-        connector.setPort(0);
-        server.addConnector(connector);
-        server.setHandler(new AbstractHandler()
-        {
-            public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
-                    throws IOException, ServletException
-            {
-                httpResponse.setStatus(200);
-                request.setHandled(true);
-                try
-                {
-                    if (request.getRequestURI().contains("/sleep"))
-                        Thread.sleep(2000);
-                }
-                catch (InterruptedException x)
-                {
-                }
-            }
-        });
-        server.start();
-        port = connector.getLocalPort();
-
-        client = new HttpClient();
-        client.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);
-        client.setTimeout(200);
-        client.setMaxRetries(0);
-        client.setMaxConnectionsPerAddress(100);
-    }
-
-    @After
-    public void destroy() throws Exception
-    {
-        client.stop();
-        server.stop();
-        server.join();
-    }
-
-    @Test
-    public void testExpire() throws Exception
-    {
-        client.setIdleTimeout(5000);
-        client.start();
-        
-        String baseUrl = "http://" + "localhost" + ":" + port + "/sleep";
-
-        int count = 200;
-        final CountDownLatch expires = new CountDownLatch(count);
-
-        for (int i=0;i<count;i++)
-        {
-            final ContentExchange exchange = new ContentExchange()
-            {
-                @Override
-                protected void onExpire()
-                {
-                    expires.countDown();
-                }
-            };
-            exchange.setMethod("GET");
-            exchange.setURL(baseUrl);
-
-            client.send(exchange);
-        }
-
-        // Wait to be sure that all exchanges have expired
-        assertTrue(expires.await(5, TimeUnit.SECONDS));
-    }
-    
-    @Test
-    public void testRemoveIdleDestination() throws Exception
-    {
-        client.setIdleTimeout(200);
-        client.setRemoveIdleDestinations(true);
-        client.start();
-        
-        String baseUrl = "http://" + "localhost" + ":" + port + "/other";
-
-        int count = 5;
-        final CountDownLatch latch = new CountDownLatch(count);
-
-        for (int i=0;i<count;i++)
-        {
-            final ContentExchange exchange = new ContentExchange()
-            {
-                @Override
-                protected void onResponseComplete()
-                {
-                    latch.countDown();
-                }
-            };
-            exchange.setMethod("GET");
-            exchange.setURL(baseUrl);
-
-            client.send(exchange);
-        }
-
-        // Wait to be sure that all exchanges have expired
-        assertTrue(latch.await(1, TimeUnit.SECONDS));
-        
-        Assert.assertEquals(1,client.getDestinations().size());
-        Thread.sleep(500);
-        Assert.assertEquals(0,client.getDestinations().size());
-                
-    }
-}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/ExternalKeyStoreAsyncSslHttpExchangeTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/ExternalKeyStoreAsyncSslHttpExchangeTest.java
deleted file mode 100644
index 35d219c..0000000
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/ExternalKeyStoreAsyncSslHttpExchangeTest.java
+++ /dev/null
@@ -1,37 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client;
-
-import org.eclipse.jetty.client.helperClasses.ExternalKeyStoreAsyncSslServerAndClientCreator;
-import org.eclipse.jetty.client.helperClasses.ServerAndClientCreator;
-import org.junit.Before;
-
-public class ExternalKeyStoreAsyncSslHttpExchangeTest extends SslHttpExchangeTest
-{
-    private static ServerAndClientCreator serverAndClientCreator = new ExternalKeyStoreAsyncSslServerAndClientCreator();
-    
-    @Before
-    public void setUpOnce() throws Exception
-    {
-        _scheme="https";
-        _server = serverAndClientCreator.createServer();
-        _httpClient = serverAndClientCreator.createClient(3000L,3500L,2000);
-        _port = _server.getConnectors()[0].getLocalPort();
-    }
-}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/ExternalSiteTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/ExternalSiteTest.java
index fdcc504..51abf8f 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/ExternalSiteTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/ExternalSiteTest.java
@@ -18,47 +18,170 @@
 
 package org.eclipse.jetty.client;
 
-import java.io.IOException;
 import java.net.Socket;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
-import org.eclipse.jetty.http.HttpSchemes;
-import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.client.api.Result;
+import org.eclipse.jetty.http.HttpScheme;
+import org.eclipse.jetty.toolchain.test.TestTracker;
 import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.junit.After;
 import org.junit.Assert;
 import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 
 public class ExternalSiteTest
 {
+    @Rule
+    public final TestTracker tracker = new TestTracker();
+
+    private HttpClient client;
+
+    @Before
+    public void prepare() throws Exception
+    {
+        client = new HttpClient(new SslContextFactory());
+        client.start();
+    }
+
+    @After
+    public void dispose() throws Exception
+    {
+        client.stop();
+    }
+
+    @Test
+    public void testExternalSite() throws Exception
+    {
+        String host = "wikipedia.org";
+        int port = 80;
+
+        // Verify that we have connectivity
+        assumeCanConnectTo(host, port);
+
+        final CountDownLatch latch1 = new CountDownLatch(1);
+        client.newRequest(host, port).send(new Response.CompleteListener()
+        {
+            @Override
+            public void onComplete(Result result)
+            {
+                if (!result.isFailed() && result.getResponse().getStatus() == 200)
+                    latch1.countDown();
+            }
+        });
+        Assert.assertTrue(latch1.await(10, TimeUnit.SECONDS));
+
+        // Try again the same URI, but without specifying the port
+        final CountDownLatch latch2 = new CountDownLatch(1);
+        client.newRequest("http://" + host).send(new Response.CompleteListener()
+        {
+            @Override
+            public void onComplete(Result result)
+            {
+                Assert.assertTrue(result.isSucceeded());
+                Assert.assertEquals(200, result.getResponse().getStatus());
+                latch2.countDown();
+            }
+        });
+        Assert.assertTrue(latch2.await(10, TimeUnit.SECONDS));
+    }
+
     @Test
     public void testExternalSSLSite() throws Exception
     {
-        HttpClient client = new HttpClient(new SslContextFactory());
+        client.stop();
+        client = new HttpClient(new SslContextFactory());
         client.start();
 
         String host = "api-3t.paypal.com";
         int port = 443;
 
         // Verify that we have connectivity
+        assumeCanConnectTo(host, port);
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        client.newRequest(host, port).scheme("https").path("/nvp").send(new Response.CompleteListener()
+        {
+            @Override
+            public void onComplete(Result result)
+            {
+                if (result.isSucceeded() && result.getResponse().getStatus() == 200)
+                    latch.countDown();
+            }
+        });
+        Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testExternalSiteWrongProtocol() throws Exception
+    {
+        String host = "github.com";
+        int port = 22; // SSH port
+
+        // Verify that we have connectivity
+        assumeCanConnectTo(host, port);
+
+        for (int i = 0; i < 2; ++i)
+        {
+            final CountDownLatch latch = new CountDownLatch(3);
+            client.newRequest(host, port)
+                    .onResponseFailure(new Response.FailureListener()
+                    {
+                        @Override
+                        public void onFailure(Response response, Throwable failure)
+                        {
+                            latch.countDown();
+                        }
+                    })
+                    .send(new Response.Listener.Adapter()
+                    {
+                        @Override
+                        public void onFailure(Response response, Throwable failure)
+                        {
+                            latch.countDown();
+                        }
+
+                        @Override
+                        public void onComplete(Result result)
+                        {
+                            Assert.assertTrue(result.isFailed());
+                            latch.countDown();
+                        }
+                    });
+            Assert.assertTrue(latch.await(10, TimeUnit.SECONDS));
+        }
+    }
+
+    @Test
+    public void testExternalSiteRedirect() throws Exception
+    {
+        String host = "twitter.com";
+        int port = 443;
+
+        // Verify that we have connectivity
+        assumeCanConnectTo(host, port);
+
+        ContentResponse response = client.newRequest(host, port)
+                .scheme(HttpScheme.HTTPS.asString())
+                .path("/twitter")
+                .send();
+        Assert.assertEquals(200, response.getStatus());
+    }
+
+    protected void assumeCanConnectTo(String host, int port)
+    {
         try
         {
             new Socket(host, port).close();
         }
-        catch (IOException x)
+        catch (Throwable x)
         {
             Assume.assumeNoException(x);
         }
-
-        ContentExchange exchange = new ContentExchange(true);
-        exchange.setScheme(HttpSchemes.HTTPS_BUFFER);
-        exchange.setAddress(new Address(host, port));
-        exchange.setRequestURI("/nvp");
-        client.send(exchange);
-        int done = exchange.waitForDone();
-        Assert.assertEquals(HttpExchange.STATUS_COMPLETED, done);
-        Assert.assertEquals(HttpStatus.OK_200, exchange.getResponseStatus());
-        Assert.assertNotNull(exchange.getResponseContentBytes());
-
-        client.stop();
     }
 }
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/GZIPContentDecoderTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/GZIPContentDecoderTest.java
new file mode 100644
index 0000000..a849183
--- /dev/null
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/GZIPContentDecoderTest.java
@@ -0,0 +1,292 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.GZIPOutputStream;
+
+import org.eclipse.jetty.toolchain.test.TestTracker;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class GZIPContentDecoderTest
+{
+    @Rule
+    public final TestTracker tracker = new TestTracker();
+
+    @Test
+    public void testStreamNoBlocks() throws Exception
+    {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        GZIPOutputStream output = new GZIPOutputStream(baos);
+        output.close();
+        byte[] bytes = baos.toByteArray();
+
+        GZIPInputStream input = new GZIPInputStream(new ByteArrayInputStream(bytes), 1);
+        int read = input.read();
+        assertEquals(-1, read);
+    }
+
+    @Test
+    public void testStreamBigBlockOneByteAtATime() throws Exception
+    {
+        String data = "0123456789ABCDEF";
+        for (int i = 0; i < 10; ++i)
+            data += data;
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        GZIPOutputStream output = new GZIPOutputStream(baos);
+        output.write(data.getBytes(StandardCharsets.UTF_8));
+        output.close();
+        byte[] bytes = baos.toByteArray();
+
+        baos = new ByteArrayOutputStream();
+        GZIPInputStream input = new GZIPInputStream(new ByteArrayInputStream(bytes), 1);
+        int read;
+        while ((read = input.read()) >= 0)
+            baos.write(read);
+        assertEquals(data, new String(baos.toByteArray(), StandardCharsets.UTF_8));
+    }
+
+    @Test
+    public void testNoBlocks() throws Exception
+    {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        GZIPOutputStream output = new GZIPOutputStream(baos);
+        output.close();
+        byte[] bytes = baos.toByteArray();
+
+        GZIPContentDecoder decoder = new GZIPContentDecoder();
+        ByteBuffer decoded = decoder.decode(ByteBuffer.wrap(bytes));
+        assertEquals(0, decoded.remaining());
+    }
+
+    @Test
+    public void testSmallBlock() throws Exception
+    {
+        String data = "0";
+
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        GZIPOutputStream output = new GZIPOutputStream(baos);
+        output.write(data.getBytes(StandardCharsets.UTF_8));
+        output.close();
+        byte[] bytes = baos.toByteArray();
+
+        GZIPContentDecoder decoder = new GZIPContentDecoder();
+        ByteBuffer decoded = decoder.decode(ByteBuffer.wrap(bytes));
+        assertEquals(data, StandardCharsets.UTF_8.decode(decoded).toString());
+    }
+
+    @Test
+    public void testSmallBlockWithGZIPChunkedAtBegin() throws Exception
+    {
+        String data = "0";
+
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        GZIPOutputStream output = new GZIPOutputStream(baos);
+        output.write(data.getBytes(StandardCharsets.UTF_8));
+        output.close();
+        byte[] bytes = baos.toByteArray();
+
+        // The header is 10 bytes, chunk at 11 bytes
+        byte[] bytes1 = new byte[11];
+        System.arraycopy(bytes, 0, bytes1, 0, bytes1.length);
+        byte[] bytes2 = new byte[bytes.length - bytes1.length];
+        System.arraycopy(bytes, bytes1.length, bytes2, 0, bytes2.length);
+
+        GZIPContentDecoder decoder = new GZIPContentDecoder();
+        ByteBuffer decoded = decoder.decode(ByteBuffer.wrap(bytes1));
+        assertEquals(0, decoded.capacity());
+        decoded = decoder.decode(ByteBuffer.wrap(bytes2));
+        assertEquals(data, StandardCharsets.UTF_8.decode(decoded).toString());
+    }
+
+    @Test
+    public void testSmallBlockWithGZIPChunkedAtEnd() throws Exception
+    {
+        String data = "0";
+
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        GZIPOutputStream output = new GZIPOutputStream(baos);
+        output.write(data.getBytes(StandardCharsets.UTF_8));
+        output.close();
+        byte[] bytes = baos.toByteArray();
+
+        // The trailer is 8 bytes, chunk the last 9 bytes
+        byte[] bytes1 = new byte[bytes.length - 9];
+        System.arraycopy(bytes, 0, bytes1, 0, bytes1.length);
+        byte[] bytes2 = new byte[bytes.length - bytes1.length];
+        System.arraycopy(bytes, bytes1.length, bytes2, 0, bytes2.length);
+
+        GZIPContentDecoder decoder = new GZIPContentDecoder();
+        ByteBuffer decoded = decoder.decode(ByteBuffer.wrap(bytes1));
+        assertEquals(data, StandardCharsets.UTF_8.decode(decoded).toString());
+        assertFalse(decoder.isFinished());
+        decoded = decoder.decode(ByteBuffer.wrap(bytes2));
+        assertEquals(0, decoded.remaining());
+        assertTrue(decoder.isFinished());
+    }
+
+    @Test
+    public void testSmallBlockWithGZIPTrailerChunked() throws Exception
+    {
+        String data = "0";
+
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        GZIPOutputStream output = new GZIPOutputStream(baos);
+        output.write(data.getBytes(StandardCharsets.UTF_8));
+        output.close();
+        byte[] bytes = baos.toByteArray();
+
+        // The trailer is 4+4 bytes, chunk the last 3 bytes
+        byte[] bytes1 = new byte[bytes.length - 3];
+        System.arraycopy(bytes, 0, bytes1, 0, bytes1.length);
+        byte[] bytes2 = new byte[bytes.length - bytes1.length];
+        System.arraycopy(bytes, bytes1.length, bytes2, 0, bytes2.length);
+
+        GZIPContentDecoder decoder = new GZIPContentDecoder();
+        ByteBuffer decoded = decoder.decode(ByteBuffer.wrap(bytes1));
+        assertEquals(0, decoded.capacity());
+        decoded = decoder.decode(ByteBuffer.wrap(bytes2));
+        assertEquals(data, StandardCharsets.UTF_8.decode(decoded).toString());
+    }
+
+    @Test
+    public void testTwoSmallBlocks() throws Exception
+    {
+        String data1 = "0";
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        GZIPOutputStream output = new GZIPOutputStream(baos);
+        output.write(data1.getBytes(StandardCharsets.UTF_8));
+        output.close();
+        byte[] bytes1 = baos.toByteArray();
+
+        String data2 = "1";
+        baos = new ByteArrayOutputStream();
+        output = new GZIPOutputStream(baos);
+        output.write(data2.getBytes(StandardCharsets.UTF_8));
+        output.close();
+        byte[] bytes2 = baos.toByteArray();
+
+        byte[] bytes = new byte[bytes1.length + bytes2.length];
+        System.arraycopy(bytes1, 0, bytes, 0, bytes1.length);
+        System.arraycopy(bytes2, 0, bytes, bytes1.length, bytes2.length);
+
+        GZIPContentDecoder decoder = new GZIPContentDecoder();
+        ByteBuffer buffer = ByteBuffer.wrap(bytes);
+        ByteBuffer decoded = decoder.decode(buffer);
+        assertEquals(data1, StandardCharsets.UTF_8.decode(decoded).toString());
+        assertTrue(decoder.isFinished());
+        assertTrue(buffer.hasRemaining());
+        decoded = decoder.decode(buffer);
+        assertEquals(data2, StandardCharsets.UTF_8.decode(decoded).toString());
+        assertTrue(decoder.isFinished());
+        assertFalse(buffer.hasRemaining());
+    }
+
+    @Test
+    public void testBigBlock() throws Exception
+    {
+        String data = "0123456789ABCDEF";
+        for (int i = 0; i < 10; ++i)
+            data += data;
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        GZIPOutputStream output = new GZIPOutputStream(baos);
+        output.write(data.getBytes(StandardCharsets.UTF_8));
+        output.close();
+        byte[] bytes = baos.toByteArray();
+
+        String result = "";
+        GZIPContentDecoder decoder = new GZIPContentDecoder();
+        ByteBuffer buffer = ByteBuffer.wrap(bytes);
+        while (buffer.hasRemaining())
+        {
+            ByteBuffer decoded = decoder.decode(buffer);
+            result += StandardCharsets.UTF_8.decode(decoded).toString();
+        }
+        assertEquals(data, result);
+    }
+
+    @Test
+    public void testBigBlockOneByteAtATime() throws Exception
+    {
+        String data = "0123456789ABCDEF";
+        for (int i = 0; i < 10; ++i)
+            data += data;
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        GZIPOutputStream output = new GZIPOutputStream(baos);
+        output.write(data.getBytes(StandardCharsets.UTF_8));
+        output.close();
+        byte[] bytes = baos.toByteArray();
+
+        String result = "";
+        GZIPContentDecoder decoder = new GZIPContentDecoder(64);
+        ByteBuffer buffer = ByteBuffer.wrap(bytes);
+        while (buffer.hasRemaining())
+        {
+            ByteBuffer decoded = decoder.decode(ByteBuffer.wrap(new byte[]{buffer.get()}));
+            if (decoded.hasRemaining())
+                result += StandardCharsets.UTF_8.decode(decoded).toString();
+        }
+        assertEquals(data, result);
+        assertTrue(decoder.isFinished());
+    }
+
+    @Test
+    public void testBigBlockWithExtraBytes() throws Exception
+    {
+        String data1 = "0123456789ABCDEF";
+        for (int i = 0; i < 10; ++i)
+            data1 += data1;
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        GZIPOutputStream output = new GZIPOutputStream(baos);
+        output.write(data1.getBytes(StandardCharsets.UTF_8));
+        output.close();
+        byte[] bytes1 = baos.toByteArray();
+
+        String data2 = "HELLO";
+        byte[] bytes2 = data2.getBytes(StandardCharsets.UTF_8);
+
+        byte[] bytes = new byte[bytes1.length + bytes2.length];
+        System.arraycopy(bytes1, 0, bytes, 0, bytes1.length);
+        System.arraycopy(bytes2, 0, bytes, bytes1.length, bytes2.length);
+
+        String result = "";
+        GZIPContentDecoder decoder = new GZIPContentDecoder(64);
+        ByteBuffer buffer = ByteBuffer.wrap(bytes);
+        while (buffer.hasRemaining())
+        {
+            ByteBuffer decoded = decoder.decode(buffer);
+            if (decoded.hasRemaining())
+                result += StandardCharsets.UTF_8.decode(decoded).toString();
+            if (decoder.isFinished())
+                break;
+        }
+        assertEquals(data1, result);
+        assertTrue(buffer.hasRemaining());
+        assertEquals(data2, StandardCharsets.UTF_8.decode(buffer).toString());
+    }
+}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HostnameVerificationTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HostnameVerificationTest.java
new file mode 100644
index 0000000..4d2add5
--- /dev/null
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HostnameVerificationTest.java
@@ -0,0 +1,172 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.io.IOException;
+import java.nio.channels.ClosedChannelException;
+import java.security.cert.CertificateException;
+import java.util.concurrent.ExecutionException;
+import javax.net.ssl.SSLHandshakeException;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.NetworkConnector;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.handler.DefaultHandler;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.hamcrest.Matchers;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * This test class runs tests to make sure that hostname verification (http://www.ietf.org/rfc/rfc2818.txt
+ * section 3.1) is configurable in SslContextFactory and works as expected.
+ */
+public class HostnameVerificationTest
+{
+    private SslContextFactory clientSslContextFactory = new SslContextFactory();
+    private Server server = new Server();
+    private HttpClient client;
+    private NetworkConnector connector;
+
+    @Before
+    public void setUp() throws Exception
+    {
+        SslContextFactory serverSslContextFactory = new SslContextFactory();
+        serverSslContextFactory.setKeyStorePath("src/test/resources/keystore.jks");
+        serverSslContextFactory.setKeyStorePassword("storepwd");
+        connector = new ServerConnector(server, serverSslContextFactory);
+        server.addConnector(connector);
+        server.setHandler(new DefaultHandler()
+        {
+            @Override
+            public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                response.getWriter().write("foobar");
+            }
+        });
+        server.start();
+
+        // keystore contains a hostname which doesn't match localhost
+        clientSslContextFactory.setKeyStorePath("src/test/resources/keystore.jks");
+        clientSslContextFactory.setKeyStorePassword("storepwd");
+
+        QueuedThreadPool executor = new QueuedThreadPool();
+        executor.setName(executor.getName() + "-client");
+        client = new HttpClient(clientSslContextFactory);
+        client.setExecutor(executor);
+        client.start();
+    }
+
+    @After
+    public void tearDown() throws Exception
+    {
+        client.stop();
+        server.stop();
+        server.join();
+    }
+
+    /**
+     * This test is supposed to verify that hostname verification works as described in:
+     * http://www.ietf.org/rfc/rfc2818.txt section 3.1. It uses a certificate with a common name different to localhost
+     * and sends a request to localhost. This should fail with a SSLHandshakeException.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void simpleGetWithHostnameVerificationEnabledTest() throws Exception
+    {
+        clientSslContextFactory.setEndpointIdentificationAlgorithm("HTTPS");
+        String uri = "https://localhost:" + connector.getLocalPort() + "/";
+        try
+        {
+            client.GET(uri);
+            Assert.fail("sending request to client should have failed with an Exception!");
+        }
+        catch (ExecutionException x)
+        {
+            // The test may fail in 2 ways, since the CertificateException thrown because of the hostname
+            // verification failure is not rethrown immediately by the JDK SSL implementation, but only
+            // rethrown on the next read or write.
+            // Therefore this test may catch a SSLHandshakeException, or a ClosedChannelException.
+            // If it is the former, we verify that its cause is a CertificateException.
+
+            // ExecutionException wraps an SSLHandshakeException
+            Throwable cause = x.getCause();
+            if (cause==null)
+            {
+                x.printStackTrace();
+                Assert.fail("No cause?");
+            }
+            if (cause instanceof SSLHandshakeException)
+                Assert.assertThat(cause.getCause().getCause(), Matchers.instanceOf(CertificateException.class));
+            else
+                Assert.assertThat(cause.getCause(), Matchers.instanceOf(ClosedChannelException.class));
+        }
+    }
+
+    /**
+     * This test has hostname verification disabled and connecting, ssl handshake and sending the request should just
+     * work fine.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void simpleGetWithHostnameVerificationDisabledTest() throws Exception
+    {
+        clientSslContextFactory.setEndpointIdentificationAlgorithm(null);
+        String uri = "https://localhost:" + connector.getLocalPort() + "/";
+        try
+        {
+            client.GET(uri);
+        }
+        catch (ExecutionException e)
+        {
+            Assert.fail("SSLHandshake should work just fine as hostname verification is disabled! " + e.getMessage());
+        }
+    }
+
+    /**
+     * This test has hostname verification disabled by setting trustAll to true and connecting,
+     * ssl handshake and sending the request should just work fine.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void trustAllDisablesHostnameVerificationTest() throws Exception
+    {
+        clientSslContextFactory.setTrustAll(true);
+        String uri = "https://localhost:" + connector.getLocalPort() + "/";
+        try
+        {
+            client.GET(uri);
+        }
+        catch (ExecutionException e)
+        {
+            Assert.fail("SSLHandshake should work just fine as hostname verification is disabled! " + e.getMessage());
+        }
+    }
+}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/Http100ContinueTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/Http100ContinueTest.java
deleted file mode 100644
index c3febeb..0000000
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/Http100ContinueTest.java
+++ /dev/null
@@ -1,245 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-
-package org.eclipse.jetty.client;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jetty.http.HttpMethods;
-import org.eclipse.jetty.http.HttpStatus;
-import org.eclipse.jetty.server.Connector;
-import org.eclipse.jetty.server.Request;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.handler.AbstractHandler;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
-import org.eclipse.jetty.util.log.Log;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.Test;
-
-
-/* ------------------------------------------------------------ */
-public class Http100ContinueTest
-{
-    private static final int TIMEOUT = 500;
-    
-    private static final String CONTENT = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. In quis felis nunc. "
-            + "Quisque suscipit mauris et ante auctor ornare rhoncus lacus aliquet. Pellentesque "
-            + "habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. "
-            + "Vestibulum sit amet felis augue, vel convallis dolor. Cras accumsan vehicula diam "
-            + "at faucibus. Etiam in urna turpis, sed congue mi. Morbi et lorem eros. Donec vulputate "
-            + "velit in risus suscipit lobortis. Aliquam id urna orci, nec sollicitudin ipsum. "
-            + "Cras a orci turpis. Donec suscipit vulputate cursus. Mauris nunc tellus, fermentum "
-            + "eu auctor ut, mollis at diam. Quisque porttitor ultrices metus, vitae tincidunt massa "
-            + "sollicitudin a. Vivamus porttitor libero eget purus hendrerit cursus. Integer aliquam "
-            + "consequat mauris quis luctus. Cras enim nibh, dignissim eu faucibus ac, mollis nec neque. "
-            + "Aliquam purus mauris, consectetur nec convallis lacinia, porta sed ante. Suspendisse "
-            + "et cursus magna. Donec orci enim, molestie a lobortis eu, imperdiet vitae neque.";
-    
-    private static TestFeature _feature;
-
-    private static Server _server;
-    private static TestHandler _handler;
-    private static HttpClient _client;
-    private static String _requestUrl;
-
-    @BeforeClass
-    public static void init() throws Exception
-    {
-        File docRoot = new File("target/test-output/docroot/");
-        if (!docRoot.exists())
-            assertTrue(docRoot.mkdirs());
-        docRoot.deleteOnExit();
-    
-        _server = new Server();
-        Connector connector = new SelectChannelConnector();
-        _server.addConnector(connector);
-    
-        _handler = new TestHandler();
-        _server.setHandler(_handler);
-    
-        _server.start();
-    
-        _client = new HttpClient();
-        _client.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);
-        _client.setTimeout(TIMEOUT);
-        _client.setMaxRetries(0);
-        _client.start();
-
-        _requestUrl = "http://localhost:" + connector.getLocalPort() + "/";
-
-    }
-    
-    @AfterClass
-    public static void destroy() throws Exception
-    {
-        _client.stop();
-        
-        _server.stop();
-        _server.join();
-    }
-    
-    @Test
-    public void testSuccess() throws Exception
-    {
-        // Handler to send CONTINUE 100
-        _feature = TestFeature.CONTINUE;
-        
-        ContentExchange exchange = sendExchange();
-
-        int state = exchange.waitForDone();
-        assertEquals(HttpExchange.STATUS_COMPLETED, state);
-        
-        int responseStatus = exchange.getResponseStatus();
-        assertEquals(HttpStatus.OK_200,responseStatus);
-
-        String content = exchange.getResponseContent();
-        assertEquals(Http100ContinueTest.CONTENT,content);
-    }
-
-    @Test
-    public void testMissingContinue() throws Exception
-    {
-        // Handler does not send CONTINUE 100
-        _feature = TestFeature.NORMAL;
-        
-        ContentExchange exchange = sendExchange();
-
-        int state = exchange.waitForDone();
-        assertEquals(HttpExchange.STATUS_COMPLETED, state);
-        
-        int responseStatus = exchange.getResponseStatus();
-        assertEquals(HttpStatus.OK_200,responseStatus);
-
-        String content = exchange.getResponseContent();
-        assertEquals(Http100ContinueTest.CONTENT,content);
-    }
-
-    @Test
-    public void testError() throws Exception
-    {
-        // Handler sends NOT FOUND 404 response
-        _feature = TestFeature.NOTFOUND;
-        
-        ContentExchange exchange = sendExchange();
-
-        int state = exchange.waitForDone();
-        assertEquals(HttpExchange.STATUS_COMPLETED, state);
-        
-        int responseStatus = exchange.getResponseStatus();
-        assertEquals(HttpStatus.NOT_FOUND_404,responseStatus);
-    }
-
-    @Test
-    public void testTimeout() throws Exception
-    {
-        // Handler delays response till client times out
-        _feature = TestFeature.TIMEOUT;
-        
-        final CountDownLatch expires = new CountDownLatch(1);
-        ContentExchange exchange = new ContentExchange()
-        {
-            @Override
-            protected void onExpire()
-            {
-                expires.countDown();
-            }
-        };
-
-        configureExchange(exchange);
-        _client.send(exchange);
-
-        assertTrue(expires.await(TIMEOUT*10,TimeUnit.MILLISECONDS));
-    }
-
-    public ContentExchange sendExchange() throws Exception
-    {
-        ContentExchange exchange = new ContentExchange();
-
-        configureExchange(exchange);
-        _client.send(exchange);
-
-        return exchange;
-    }
-    
-    public void configureExchange(ContentExchange exchange)
-    {
-        exchange.setURL(_requestUrl);
-        exchange.setMethod(HttpMethods.GET);
-        exchange.addRequestHeader("User-Agent","Jetty-Client/7.0");
-        exchange.addRequestHeader("Expect","100-continue"); //server to send CONTINUE 100
-    }
-
-    private static class TestHandler extends AbstractHandler
-    {
-        public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
-        {
-            if (baseRequest.isHandled())
-                return;
-            
-            baseRequest.setHandled(true);
-
-            switch (_feature)
-            {
-                case CONTINUE:
-                    // force 100 Continue response to be sent
-                    request.getInputStream();
-                    // next send data
-
-                case NORMAL:
-                    response.setContentType("text/plain");
-                    response.setStatus(HttpServletResponse.SC_OK);
-                    response.getWriter().print(CONTENT);
-                    break;
-                    
-                case NOTFOUND:
-                    response.setStatus(HttpServletResponse.SC_NOT_FOUND);
-                    break;
-
-                case TIMEOUT:
-                    try
-                    {
-                        Thread.sleep(TIMEOUT*4);
-                    }
-                    catch (InterruptedException ex)
-                    {
-                        Log.ignore(ex);
-                    }
-                    break;
-            }
-        }
-    }
-    
-    enum TestFeature {
-        CONTINUE,
-        NORMAL,
-        NOTFOUND,
-        TIMEOUT
-    }
-}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpAsserts.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpAsserts.java
deleted file mode 100644
index 89438fb..0000000
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpAsserts.java
+++ /dev/null
@@ -1,47 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client;
-
-import java.util.Collections;
-import java.util.List;
-
-import org.eclipse.jetty.http.HttpFields;
-import org.junit.Assert;
-
-public final class HttpAsserts
-{
-    public static void assertContainsHeaderKey(String expectedKey, HttpFields headers)
-    {
-        if (headers.containsKey(expectedKey))
-        {
-            return;
-        }
-        List<String> names = Collections.list(headers.getFieldNames());
-        StringBuilder err = new StringBuilder();
-        err.append("Missing expected header key [").append(expectedKey);
-        err.append("] (of ").append(names.size()).append(" header fields)");
-        for (int i = 0; i < names.size(); i++)
-        {
-            String value = headers.getStringField(names.get(i));
-            err.append("\n").append(i).append("] ").append(names.get(i));
-            err.append(": ").append(value);
-        }
-        Assert.fail(err.toString());
-    }
-}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAsyncContentTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAsyncContentTest.java
new file mode 100644
index 0000000..cf4f93f
--- /dev/null
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAsyncContentTest.java
@@ -0,0 +1,118 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.client.api.Result;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class HttpClientAsyncContentTest extends AbstractHttpClientServerTest
+{
+    public HttpClientAsyncContentTest(SslContextFactory sslContextFactory)
+    {
+        super(sslContextFactory);
+    }
+
+    @Test
+    public void testSmallAsyncContent() throws Exception
+    {
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                ServletOutputStream output = response.getOutputStream();
+                output.write(65);
+                output.flush();
+                output.write(66);
+            }
+        });
+
+        final AtomicInteger contentCount = new AtomicInteger();
+        final AtomicReference<Callback> callbackRef = new AtomicReference<>();
+        final AtomicReference<CountDownLatch> contentLatch = new AtomicReference<>(new CountDownLatch(1));
+        final CountDownLatch completeLatch = new CountDownLatch(1);
+        client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .onResponseContentAsync(new Response.AsyncContentListener()
+                {
+                    @Override
+                    public void onContent(Response response, ByteBuffer content, Callback callback)
+                    {
+                        contentCount.incrementAndGet();
+                        callbackRef.set(callback);
+                        contentLatch.get().countDown();
+                    }
+                })
+                .send(new Response.CompleteListener()
+                {
+                    @Override
+                    public void onComplete(Result result)
+                    {
+                        completeLatch.countDown();
+                    }
+                });
+
+        Assert.assertTrue(contentLatch.get().await(5, TimeUnit.SECONDS));
+        Callback callback = callbackRef.get();
+
+        // Wait a while to be sure that the parsing does not proceed.
+        TimeUnit.MILLISECONDS.sleep(1000);
+
+        Assert.assertEquals(1, contentCount.get());
+
+        // Succeed the content callback to proceed with parsing.
+        callbackRef.set(null);
+        contentLatch.set(new CountDownLatch(1));
+        callback.succeeded();
+
+        Assert.assertTrue(contentLatch.get().await(5, TimeUnit.SECONDS));
+        callback = callbackRef.get();
+
+        // Wait a while to be sure that the parsing does not proceed.
+        TimeUnit.MILLISECONDS.sleep(1000);
+
+        Assert.assertEquals(2, contentCount.get());
+        Assert.assertEquals(1, completeLatch.getCount());
+
+        // Succeed the content callback to proceed with parsing.
+        callbackRef.set(null);
+        contentLatch.set(new CountDownLatch(1));
+        callback.succeeded();
+
+        Assert.assertTrue(completeLatch.await(5, TimeUnit.SECONDS));
+        Assert.assertEquals(2, contentCount.get());
+    }
+}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAuthenticationTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAuthenticationTest.java
new file mode 100644
index 0000000..24e5720
--- /dev/null
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAuthenticationTest.java
@@ -0,0 +1,323 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.client.api.Authentication;
+import org.eclipse.jetty.client.api.AuthenticationStore;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.util.BasicAuthentication;
+import org.eclipse.jetty.client.util.DigestAuthentication;
+import org.eclipse.jetty.security.Authenticator;
+import org.eclipse.jetty.security.ConstraintMapping;
+import org.eclipse.jetty.security.ConstraintSecurityHandler;
+import org.eclipse.jetty.security.HashLoginService;
+import org.eclipse.jetty.security.LoginService;
+import org.eclipse.jetty.security.authentication.BasicAuthenticator;
+import org.eclipse.jetty.security.authentication.DigestAuthenticator;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.security.Constraint;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest
+{
+    private String realm = "TestRealm";
+
+    public HttpClientAuthenticationTest(SslContextFactory sslContextFactory)
+    {
+        super(sslContextFactory);
+    }
+
+    public void startBasic(Handler handler) throws Exception
+    {
+        start(new BasicAuthenticator(), handler);
+    }
+
+    public void startDigest(Handler handler) throws Exception
+    {
+        start(new DigestAuthenticator(), handler);
+    }
+
+    private void start(Authenticator authenticator, Handler handler) throws Exception
+    {
+        server = new Server();
+        File realmFile = MavenTestingUtils.getTestResourceFile("realm.properties");
+        LoginService loginService = new HashLoginService(realm, realmFile.getAbsolutePath());
+        server.addBean(loginService);
+
+        ConstraintSecurityHandler securityHandler = new ConstraintSecurityHandler();
+
+        Constraint constraint = new Constraint();
+        constraint.setAuthenticate(true);
+        constraint.setRoles(new String[]{"**"}); //allow any authenticated user
+        ConstraintMapping mapping = new ConstraintMapping();
+        mapping.setPathSpec("/secure");
+        mapping.setConstraint(constraint);
+
+        securityHandler.addConstraintMapping(mapping);
+        securityHandler.setAuthenticator(authenticator);
+        securityHandler.setLoginService(loginService);
+
+        securityHandler.setHandler(handler);
+        start(securityHandler);
+    }
+
+    @Test
+    public void test_BasicAuthentication() throws Exception
+    {
+        startBasic(new EmptyServerHandler());
+        URI uri = URI.create(scheme + "://localhost:" + connector.getLocalPort());
+        test_Authentication(new BasicAuthentication(uri, realm, "basic", "basic"));
+    }
+
+    @Test
+    public void test_DigestAuthentication() throws Exception
+    {
+        startDigest(new EmptyServerHandler());
+        URI uri = URI.create(scheme + "://localhost:" + connector.getLocalPort());
+        test_Authentication(new DigestAuthentication(uri, realm, "digest", "digest"));
+    }
+
+    private void test_Authentication(Authentication authentication) throws Exception
+    {
+        AuthenticationStore authenticationStore = client.getAuthenticationStore();
+
+        final AtomicReference<CountDownLatch> requests = new AtomicReference<>(new CountDownLatch(1));
+        Request.Listener.Adapter requestListener = new Request.Listener.Adapter()
+        {
+            @Override
+            public void onSuccess(Request request)
+            {
+                requests.get().countDown();
+            }
+        };
+        client.getRequestListeners().add(requestListener);
+
+        // Request without Authentication causes a 401
+        Request request = client.newRequest("localhost", connector.getLocalPort()).scheme(scheme).path("/secure");
+        ContentResponse response = request.timeout(5, TimeUnit.SECONDS).send();
+        Assert.assertNotNull(response);
+        Assert.assertEquals(401, response.getStatus());
+        Assert.assertTrue(requests.get().await(5, TimeUnit.SECONDS));
+        client.getRequestListeners().remove(requestListener);
+
+        authenticationStore.addAuthentication(authentication);
+
+        requests.set(new CountDownLatch(2));
+        requestListener = new Request.Listener.Adapter()
+        {
+            @Override
+            public void onSuccess(Request request)
+            {
+                requests.get().countDown();
+            }
+        };
+        client.getRequestListeners().add(requestListener);
+
+        // Request with authentication causes a 401 (no previous successful authentication) + 200
+        request = client.newRequest("localhost", connector.getLocalPort()).scheme(scheme).path("/secure");
+        response = request.timeout(5, TimeUnit.SECONDS).send();
+        Assert.assertNotNull(response);
+        Assert.assertEquals(200, response.getStatus());
+        Assert.assertTrue(requests.get().await(5, TimeUnit.SECONDS));
+        client.getRequestListeners().remove(requestListener);
+
+        requests.set(new CountDownLatch(1));
+        requestListener = new Request.Listener.Adapter()
+        {
+            @Override
+            public void onSuccess(Request request)
+            {
+                requests.get().countDown();
+            }
+        };
+        client.getRequestListeners().add(requestListener);
+
+        // Further requests do not trigger 401 because there is a previous successful authentication
+        // Remove existing header to be sure it's added by the implementation
+        request = client.newRequest("localhost", connector.getLocalPort()).scheme(scheme).path("/secure");
+        response = request.timeout(5, TimeUnit.SECONDS).send();
+        Assert.assertNotNull(response);
+        Assert.assertEquals(200, response.getStatus());
+        Assert.assertTrue(requests.get().await(5, TimeUnit.SECONDS));
+        client.getRequestListeners().remove(requestListener);
+    }
+
+    @Test
+    public void test_BasicAuthentication_ThenRedirect() throws Exception
+    {
+        startBasic(new AbstractHandler()
+        {
+            private final AtomicInteger requests = new AtomicInteger();
+
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                if (requests.incrementAndGet() == 1)
+                    response.sendRedirect(URIUtil.newURI(scheme,request.getServerName(),request.getServerPort(),request.getRequestURI(),null));
+            }
+        });
+
+        URI uri = URI.create(scheme + "://localhost:" + connector.getLocalPort());
+        client.getAuthenticationStore().addAuthentication(new BasicAuthentication(uri, realm, "basic", "basic"));
+
+        final CountDownLatch requests = new CountDownLatch(3);
+        Request.Listener.Adapter requestListener = new Request.Listener.Adapter()
+        {
+            @Override
+            public void onSuccess(Request request)
+            {
+                requests.countDown();
+            }
+        };
+        client.getRequestListeners().add(requestListener);
+
+        ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .path("/secure")
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+        Assert.assertNotNull(response);
+        Assert.assertEquals(200, response.getStatus());
+        Assert.assertTrue(requests.await(5, TimeUnit.SECONDS));
+        client.getRequestListeners().remove(requestListener);
+    }
+
+    @Test
+    public void test_Redirect_ThenBasicAuthentication() throws Exception
+    {
+        startBasic(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                if (request.getRequestURI().endsWith("/redirect"))
+                    response.sendRedirect(URIUtil.newURI(scheme,request.getServerName(),request.getServerPort(),"/secure",null));
+            }
+        });
+
+        URI uri = URI.create(scheme + "://localhost:" + connector.getLocalPort());
+        client.getAuthenticationStore().addAuthentication(new BasicAuthentication(uri, realm, "basic", "basic"));
+
+        final CountDownLatch requests = new CountDownLatch(3);
+        Request.Listener.Adapter requestListener = new Request.Listener.Adapter()
+        {
+            @Override
+            public void onSuccess(Request request)
+            {
+                requests.countDown();
+            }
+        };
+        client.getRequestListeners().add(requestListener);
+
+        ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .path("/redirect")
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+        Assert.assertNotNull(response);
+        Assert.assertEquals(200, response.getStatus());
+        Assert.assertTrue(requests.await(5, TimeUnit.SECONDS));
+        client.getRequestListeners().remove(requestListener);
+    }
+
+    @Test
+    public void test_BasicAuthentication_WithAuthenticationRemoved() throws Exception
+    {
+        startBasic(new EmptyServerHandler());
+
+        final AtomicReference<CountDownLatch> requests = new AtomicReference<>(new CountDownLatch(2));
+        Request.Listener.Adapter requestListener = new Request.Listener.Adapter()
+        {
+            @Override
+            public void onSuccess(Request request)
+            {
+                requests.get().countDown();
+            }
+        };
+        client.getRequestListeners().add(requestListener);
+
+        AuthenticationStore authenticationStore = client.getAuthenticationStore();
+        URI uri = URI.create(scheme + "://localhost:" + connector.getLocalPort());
+        BasicAuthentication authentication = new BasicAuthentication(uri, realm, "basic", "basic");
+        authenticationStore.addAuthentication(authentication);
+
+        Request request = client.newRequest("localhost", connector.getLocalPort()).scheme(scheme).path("/secure");
+        ContentResponse response = request.timeout(5, TimeUnit.SECONDS).send();
+        Assert.assertNotNull(response);
+        Assert.assertEquals(200, response.getStatus());
+        Assert.assertTrue(requests.get().await(5, TimeUnit.SECONDS));
+
+        authenticationStore.removeAuthentication(authentication);
+
+        requests.set(new CountDownLatch(1));
+        request = client.newRequest("localhost", connector.getLocalPort()).scheme(scheme).path("/secure");
+        response = request.timeout(5, TimeUnit.SECONDS).send();
+        Assert.assertNotNull(response);
+        Assert.assertEquals(200, response.getStatus());
+        Assert.assertTrue(requests.get().await(5, TimeUnit.SECONDS));
+
+        Authentication.Result result = authenticationStore.findAuthenticationResult(request.getURI());
+        Assert.assertNotNull(result);
+        authenticationStore.removeAuthenticationResult(result);
+
+        requests.set(new CountDownLatch(1));
+        request = client.newRequest("localhost", connector.getLocalPort()).scheme(scheme).path("/secure");
+        response = request.timeout(5, TimeUnit.SECONDS).send();
+        Assert.assertNotNull(response);
+        Assert.assertEquals(401, response.getStatus());
+        Assert.assertTrue(requests.get().await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void test_BasicAuthentication_WithWrongPassword() throws Exception
+    {
+        startBasic(new EmptyServerHandler());
+
+        AuthenticationStore authenticationStore = client.getAuthenticationStore();
+        URI uri = URI.create(scheme + "://localhost:" + connector.getLocalPort());
+        BasicAuthentication authentication = new BasicAuthentication(uri, realm, "basic", "wrong");
+        authenticationStore.addAuthentication(authentication);
+
+        Request request = client.newRequest("localhost", connector.getLocalPort()).scheme(scheme).path("/secure");
+        ContentResponse response = request.timeout(5, TimeUnit.SECONDS).send();
+        Assert.assertNotNull(response);
+        Assert.assertEquals(401, response.getStatus());
+    }
+}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientChunkedContentTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientChunkedContentTest.java
new file mode 100644
index 0000000..4ef82f1
--- /dev/null
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientChunkedContentTest.java
@@ -0,0 +1,216 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.client.api.Result;
+import org.eclipse.jetty.client.util.FutureResponseListener;
+import org.eclipse.jetty.toolchain.test.TestTracker;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+
+import static org.junit.Assert.assertTrue;
+
+public class HttpClientChunkedContentTest
+{
+    @Rule
+    public final TestTracker tracker = new TestTracker();
+    private HttpClient client;
+
+    private void startClient() throws Exception
+    {
+        QueuedThreadPool clientThreads = new QueuedThreadPool();
+        clientThreads.setName("client");
+        client = new HttpClient();
+        client.setExecutor(clientThreads);
+        client.start();
+    }
+
+    @After
+    public void dispose() throws Exception
+    {
+        if (client != null)
+            client.stop();
+    }
+
+    @Test
+    public void test_Server_HeadersPauseTerminal_Client_Response() throws Exception
+    {
+        startClient();
+
+        try (ServerSocket server = new ServerSocket())
+        {
+            server.bind(new InetSocketAddress("localhost", 0));
+
+            final AtomicReference<Result> resultRef = new AtomicReference<>();
+            final CountDownLatch completeLatch = new CountDownLatch(1);
+            client.newRequest("localhost", server.getLocalPort())
+                    .timeout(5, TimeUnit.SECONDS)
+                    .send(new Response.CompleteListener()
+                    {
+                        @Override
+                        public void onComplete(Result result)
+                        {
+                            resultRef.set(result);
+                            completeLatch.countDown();
+                        }
+                    });
+
+            try (Socket socket = server.accept())
+            {
+                consumeRequestHeaders(socket);
+
+                OutputStream output = socket.getOutputStream();
+                String headers = "" +
+                        "HTTP/1.1 200 OK\r\n" +
+                        "Transfer-Encoding: chunked\r\n" +
+                        "\r\n";
+                output.write(headers.getBytes(StandardCharsets.UTF_8));
+                output.flush();
+
+                Thread.sleep(1000);
+
+                String terminal = "" +
+                        "0\r\n" +
+                        "\r\n";
+                output.write(terminal.getBytes(StandardCharsets.UTF_8));
+                output.flush();
+
+                assertTrue(completeLatch.await(5, TimeUnit.SECONDS));
+                Result result = resultRef.get();
+                assertTrue(result.isSucceeded());
+                Response response = result.getResponse();
+                Assert.assertEquals(200, response.getStatus());
+            }
+        }
+    }
+
+    @Test
+    public void test_Server_ContentTerminal_Client_ContentDelay() throws Exception
+    {
+        startClient();
+
+        try (ServerSocket server = new ServerSocket())
+        {
+            server.bind(new InetSocketAddress("localhost", 0));
+
+            final AtomicReference<Callback> callbackRef = new AtomicReference<>();
+            final CountDownLatch firstContentLatch = new CountDownLatch(1);
+            final AtomicReference<Result> resultRef = new AtomicReference<>();
+            final CountDownLatch completeLatch = new CountDownLatch(1);
+            client.newRequest("localhost", server.getLocalPort())
+                    .onResponseContentAsync(new Response.AsyncContentListener()
+                    {
+                        @Override
+                        public void onContent(Response response, ByteBuffer content, Callback callback)
+                        {
+                            if (callbackRef.compareAndSet(null, callback))
+                                firstContentLatch.countDown();
+                            else
+                                callback.succeeded();
+                        }
+                    })
+                    .timeout(5, TimeUnit.SECONDS)
+                    .send(new Response.CompleteListener()
+                    {
+                        @Override
+                        public void onComplete(Result result)
+                        {
+                            resultRef.set(result);
+                            completeLatch.countDown();
+                        }
+                    });
+
+            try (Socket socket = server.accept())
+            {
+                consumeRequestHeaders(socket);
+
+                OutputStream output = socket.getOutputStream();
+                String response = "" +
+                        "HTTP/1.1 200 OK\r\n" +
+                        "Transfer-Encoding: chunked\r\n" +
+                        "\r\n" +
+                        "8\r\n" +
+                        "01234567\r\n" +
+                        "0\r\n" +
+                        "\r\n";
+                output.write(response.getBytes(StandardCharsets.UTF_8));
+                output.flush();
+
+                // Simulate a delay in consuming the content.
+                assertTrue(firstContentLatch.await(5, TimeUnit.SECONDS));
+                Thread.sleep(1000);
+                callbackRef.get().succeeded();
+
+                // Wait for the client to read 0 and become idle.
+                Thread.sleep(1000);
+
+                assertTrue(completeLatch.await(5, TimeUnit.SECONDS));
+                Result result = resultRef.get();
+                assertTrue(result.isSucceeded());
+                Assert.assertEquals(200, result.getResponse().getStatus());
+
+                // Issue another request to be sure the connection is sane.
+                Request request = client.newRequest("localhost", server.getLocalPort())
+                        .timeout(5, TimeUnit.SECONDS);
+                FutureResponseListener listener = new FutureResponseListener(request);
+                request.send(listener);
+
+                consumeRequestHeaders(socket);
+                output.write(response.getBytes(StandardCharsets.UTF_8));
+                output.flush();
+
+                Assert.assertEquals(200, listener.get(5, TimeUnit.SECONDS).getStatus());
+            }
+        }
+    }
+
+    private void consumeRequestHeaders(Socket socket) throws IOException
+    {
+        InputStream input = socket.getInputStream();
+        int crlfs = 0;
+        while (true)
+        {
+            int read = input.read();
+            if (read == '\r' || read == '\n')
+                ++crlfs;
+            else
+                crlfs = 0;
+            if (crlfs == 4)
+                break;
+        }
+    }
+}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientContinueTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientContinueTest.java
new file mode 100644
index 0000000..a7ed98c
--- /dev/null
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientContinueTest.java
@@ -0,0 +1,724 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import javax.servlet.ServletException;
+import javax.servlet.ServletInputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.client.api.Result;
+import org.eclipse.jetty.client.util.BufferingResponseListener;
+import org.eclipse.jetty.client.util.BytesContentProvider;
+import org.eclipse.jetty.client.util.DeferredContentProvider;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpHeaderValue;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.toolchain.test.annotation.Slow;
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class HttpClientContinueTest extends AbstractHttpClientServerTest
+{
+    public HttpClientContinueTest(SslContextFactory sslContextFactory)
+    {
+        super(sslContextFactory);
+    }
+
+    @Test
+    public void test_Expect100Continue_WithOneContent_Respond100Continue() throws Exception
+    {
+        test_Expect100Continue_Respond100Continue("data1".getBytes(StandardCharsets.UTF_8));
+    }
+
+    @Test
+    public void test_Expect100Continue_WithMultipleContents_Respond100Continue() throws Exception
+    {
+        test_Expect100Continue_Respond100Continue("data1".getBytes(StandardCharsets.UTF_8), "data2".getBytes(StandardCharsets.UTF_8), "data3".getBytes(StandardCharsets.UTF_8));
+    }
+
+    private void test_Expect100Continue_Respond100Continue(byte[]... contents) throws Exception
+    {
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                // Send 100-Continue and copy the content back
+                IO.copy(request.getInputStream(), response.getOutputStream());
+            }
+        });
+
+        ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString())
+                .content(new BytesContentProvider(contents))
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        Assert.assertNotNull(response);
+        Assert.assertEquals(200, response.getStatus());
+
+        int index = 0;
+        byte[] responseContent = response.getContent();
+        for (byte[] content : contents)
+        {
+            for (byte b : content)
+            {
+                Assert.assertEquals(b, responseContent[index++]);
+            }
+        }
+    }
+
+    @Test
+    public void test_Expect100Continue_WithChunkedContent_Respond100Continue() throws Exception
+    {
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                // Send 100-Continue and copy the content back
+                ServletInputStream input = request.getInputStream();
+                // Make sure we chunk the response too
+                response.flushBuffer();
+                IO.copy(input, response.getOutputStream());
+            }
+        });
+
+        byte[] content1 = new byte[10240];
+        byte[] content2 = new byte[16384];
+        ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString())
+                .content(new BytesContentProvider(content1, content2)
+                {
+                    @Override
+                    public long getLength()
+                    {
+                        return -1;
+                    }
+                })
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        Assert.assertNotNull(response);
+        Assert.assertEquals(200, response.getStatus());
+
+        int index = 0;
+        byte[] responseContent = response.getContent();
+        for (byte b : content1)
+            Assert.assertEquals(b, responseContent[index++]);
+        for (byte b : content2)
+            Assert.assertEquals(b, responseContent[index++]);
+    }
+
+    @Test
+    public void test_Expect100Continue_WithContent_Respond417ExpectationFailed() throws Exception
+    {
+        test_Expect100Continue_WithContent_RespondError(417);
+    }
+
+    @Test
+    public void test_Expect100Continue_WithContent_Respond413RequestEntityTooLarge() throws Exception
+    {
+        test_Expect100Continue_WithContent_RespondError(413);
+    }
+
+    private void test_Expect100Continue_WithContent_RespondError(final int error) throws Exception
+    {
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                response.sendError(error);
+            }
+        });
+
+        byte[] content1 = new byte[10240];
+        byte[] content2 = new byte[16384];
+        final CountDownLatch latch = new CountDownLatch(1);
+        client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString())
+                .content(new BytesContentProvider(content1, content2))
+                .send(new BufferingResponseListener()
+                {
+                    @Override
+                    public void onComplete(Result result)
+                    {
+                        Assert.assertTrue(result.isFailed());
+                        Assert.assertNotNull(result.getRequestFailure());
+                        Assert.assertNull(result.getResponseFailure());
+                        byte[] content = getContent();
+                        Assert.assertNotNull(content);
+                        Assert.assertTrue(content.length > 0);
+                        Assert.assertEquals(error, result.getResponse().getStatus());
+                        latch.countDown();
+                    }
+                });
+
+        Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void test_Expect100Continue_WithContent_WithRedirect() throws Exception
+    {
+        final String data = "success";
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                if (request.getRequestURI().endsWith("/done"))
+                {
+                    response.getOutputStream().print(data);
+                }
+                else
+                {
+                    // Send 100-Continue and consume the content
+                    IO.copy(request.getInputStream(), new ByteArrayOutputStream());
+                    // Send a redirect
+                    response.sendRedirect("/done");
+                }
+            }
+        });
+
+        byte[] content = new byte[10240];
+        final CountDownLatch latch = new CountDownLatch(1);
+        client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .method(HttpMethod.POST)
+                .path("/continue")
+                .header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString())
+                .content(new BytesContentProvider(content))
+                .send(new BufferingResponseListener()
+                {
+                    @Override
+                    public void onComplete(Result result)
+                    {
+                        Assert.assertFalse(result.isFailed());
+                        Assert.assertEquals(200, result.getResponse().getStatus());
+                        Assert.assertEquals(data, getContentAsString());
+                        latch.countDown();
+                    }
+                });
+
+        Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void test_Redirect_WithExpect100Continue_WithContent() throws Exception
+    {
+        // A request with Expect: 100-Continue cannot receive non-final responses like 3xx
+
+        final String data = "success";
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                if (request.getRequestURI().endsWith("/done"))
+                {
+                    // Send 100-Continue and consume the content
+                    IO.copy(request.getInputStream(), new ByteArrayOutputStream());
+                    response.getOutputStream().print(data);
+                }
+                else
+                {
+                    // Send a redirect
+                    response.sendRedirect("/done");
+                }
+            }
+        });
+
+        byte[] content = new byte[10240];
+        final CountDownLatch latch = new CountDownLatch(1);
+        client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .method(HttpMethod.POST)
+                .path("/redirect")
+                .header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString())
+                .content(new BytesContentProvider(content))
+                .send(new BufferingResponseListener()
+                {
+                    @Override
+                    public void onComplete(Result result)
+                    {
+                        Assert.assertTrue(result.isFailed());
+                        Assert.assertNotNull(result.getRequestFailure());
+                        Assert.assertNull(result.getResponseFailure());
+                        Assert.assertEquals(302, result.getResponse().getStatus());
+                        latch.countDown();
+                    }
+                });
+
+        Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Slow
+    @Test
+    public void test_Expect100Continue_WithContent_WithResponseFailure_Before100Continue() throws Exception
+    {
+        final long idleTimeout = 1000;
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                try
+                {
+                    TimeUnit.MILLISECONDS.sleep(2 * idleTimeout);
+                }
+                catch (InterruptedException x)
+                {
+                    throw new ServletException(x);
+                }
+            }
+        });
+
+        client.setIdleTimeout(idleTimeout);
+
+        byte[] content = new byte[1024];
+        final CountDownLatch latch = new CountDownLatch(1);
+        client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString())
+                .content(new BytesContentProvider(content))
+                .send(new BufferingResponseListener()
+                {
+                    @Override
+                    public void onComplete(Result result)
+                    {
+                        Assert.assertTrue(result.isFailed());
+                        Assert.assertNotNull(result.getRequestFailure());
+                        Assert.assertNotNull(result.getResponseFailure());
+                        latch.countDown();
+                    }
+                });
+
+        Assert.assertTrue(latch.await(3 * idleTimeout, TimeUnit.MILLISECONDS));
+    }
+
+    @Slow
+    @Test
+    public void test_Expect100Continue_WithContent_WithResponseFailure_After100Continue() throws Exception
+    {
+        final long idleTimeout = 1000;
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                // Send 100-Continue and consume the content
+                IO.copy(request.getInputStream(), new ByteArrayOutputStream());
+                try
+                {
+                    TimeUnit.MILLISECONDS.sleep(2 * idleTimeout);
+                }
+                catch (InterruptedException x)
+                {
+                    throw new ServletException(x);
+                }
+            }
+        });
+
+        client.setIdleTimeout(idleTimeout);
+
+        byte[] content = new byte[1024];
+        final CountDownLatch latch = new CountDownLatch(1);
+        client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString())
+                .content(new BytesContentProvider(content))
+                .send(new BufferingResponseListener()
+                {
+                    @Override
+                    public void onComplete(Result result)
+                    {
+                        Assert.assertTrue(result.isFailed());
+                        Assert.assertNull(result.getRequestFailure());
+                        Assert.assertNotNull(result.getResponseFailure());
+                        latch.countDown();
+                    }
+                });
+
+        Assert.assertTrue(latch.await(3 * idleTimeout, TimeUnit.MILLISECONDS));
+    }
+
+    @Test
+    public void test_Expect100Continue_WithContent_WithResponseFailure_During100Continue() throws Exception
+    {
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                // Send 100-Continue and consume the content
+                IO.copy(request.getInputStream(), new ByteArrayOutputStream());
+            }
+        });
+
+        client.getProtocolHandlers().clear();
+        client.getProtocolHandlers().add(new ContinueProtocolHandler(client)
+        {
+            @Override
+            public Response.Listener getResponseListener()
+            {
+                final Response.Listener listener = super.getResponseListener();
+                return new Response.Listener.Adapter()
+                {
+                    @Override
+                    public void onBegin(Response response)
+                    {
+                        response.abort(new Exception());
+                    }
+
+                    @Override
+                    public void onFailure(Response response, Throwable failure)
+                    {
+                        listener.onFailure(response, failure);
+                    }
+                };
+            }
+        });
+
+        byte[] content = new byte[1024];
+        final CountDownLatch latch = new CountDownLatch(1);
+        client.newRequest("localhost", connector.getLocalPort())
+        .scheme(scheme)
+        .header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString())
+        .content(new BytesContentProvider(content))
+        .send(new BufferingResponseListener()
+        {
+            @Override
+            public void onComplete(Result result)
+            {
+                Assert.assertTrue(result.isFailed());
+                Assert.assertNotNull(result.getRequestFailure());
+                Assert.assertNotNull(result.getResponseFailure());
+                latch.countDown();
+            }
+        });
+
+        Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Slow
+    @Test
+    public void test_Expect100Continue_WithDeferredContent_Respond100Continue() throws Exception
+    {
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                // Send 100-Continue and echo the content
+                IO.copy(request.getInputStream(), response.getOutputStream());
+            }
+        });
+
+        final byte[] chunk1 = new byte[]{0, 1, 2, 3};
+        final byte[] chunk2 = new byte[]{4, 5, 6, 7};
+        final byte[] data = new byte[chunk1.length + chunk2.length];
+        System.arraycopy(chunk1, 0, data, 0, chunk1.length);
+        System.arraycopy(chunk2, 0, data, chunk1.length, chunk2.length);
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        DeferredContentProvider content = new DeferredContentProvider();
+        client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString())
+                .content(content)
+                .send(new BufferingResponseListener()
+                {
+                    @Override
+                    public void onComplete(Result result)
+                    {
+                        Assert.assertArrayEquals(data, getContent());
+                        latch.countDown();
+                    }
+                });
+
+        Thread.sleep(1000);
+
+        content.offer(ByteBuffer.wrap(chunk1));
+
+        Thread.sleep(1000);
+
+        content.offer(ByteBuffer.wrap(chunk2));
+        content.close();
+
+        Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Slow
+    @Test
+    public void test_Expect100Continue_WithInitialAndDeferredContent_Respond100Continue() throws Exception
+    {
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                // Send 100-Continue and echo the content
+                IO.copy(request.getInputStream(), response.getOutputStream());
+            }
+        });
+
+        final byte[] chunk1 = new byte[]{0, 1, 2, 3};
+        final byte[] chunk2 = new byte[]{4, 5, 6, 7};
+        final byte[] data = new byte[chunk1.length + chunk2.length];
+        System.arraycopy(chunk1, 0, data, 0, chunk1.length);
+        System.arraycopy(chunk2, 0, data, chunk1.length, chunk2.length);
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        DeferredContentProvider content = new DeferredContentProvider(ByteBuffer.wrap(chunk1));
+        client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString())
+                .content(content)
+                .send(new BufferingResponseListener()
+                {
+                    @Override
+                    public void onComplete(Result result)
+                    {
+                        Assert.assertArrayEquals(data, getContent());
+                        latch.countDown();
+                    }
+                });
+
+        Thread.sleep(1000);
+
+        content.offer(ByteBuffer.wrap(chunk2));
+        content.close();
+
+        Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void test_Expect100Continue_WithConcurrentDeferredContent_Respond100Continue() throws Exception
+    {
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                // Send 100-Continue and echo the content
+                IO.copy(request.getInputStream(), response.getOutputStream());
+            }
+        });
+
+        final byte[] data = new byte[]{0, 1, 2, 3, 4, 5, 6, 7};
+        final DeferredContentProvider content = new DeferredContentProvider();
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString())
+                .onRequestHeaders(new org.eclipse.jetty.client.api.Request.HeadersListener()
+                {
+                    @Override
+                    public void onHeaders(org.eclipse.jetty.client.api.Request request)
+                    {
+                        content.offer(ByteBuffer.wrap(data));
+                        content.close();
+                    }
+                })
+                .content(content)
+                .send(new BufferingResponseListener()
+                {
+                    @Override
+                    public void onComplete(Result result)
+                    {
+                        Assert.assertArrayEquals(data, getContent());
+                        latch.countDown();
+                    }
+                });
+
+        Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void test_Expect100Continue_WithInitialAndConcurrentDeferredContent_Respond100Continue() throws Exception
+    {
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                // Send 100-Continue and echo the content
+                IO.copy(request.getInputStream(), response.getOutputStream());
+            }
+        });
+
+        final byte[] chunk1 = new byte[]{0, 1, 2, 3};
+        final byte[] chunk2 = new byte[]{4, 5, 6};
+        final byte[] data = new byte[chunk1.length + chunk2.length];
+        System.arraycopy(chunk1, 0, data, 0, chunk1.length);
+        System.arraycopy(chunk2, 0, data, chunk1.length, chunk2.length);
+
+        final DeferredContentProvider content = new DeferredContentProvider(ByteBuffer.wrap(chunk1));
+
+        List<ProtocolHandler> protocolHandlers = client.getProtocolHandlers();
+        for (Iterator<ProtocolHandler> iterator = protocolHandlers.iterator(); iterator.hasNext();)
+        {
+            ProtocolHandler protocolHandler = iterator.next();
+            if (protocolHandler instanceof ContinueProtocolHandler)
+                iterator.remove();
+        }
+        protocolHandlers.add(new ContinueProtocolHandler(client)
+        {
+            @Override
+            public Response.Listener getResponseListener()
+            {
+                return new ContinueListener()
+                {
+                    @Override
+                    public void onHeaders(Response response)
+                    {
+                        super.onHeaders(response);
+                        content.offer(ByteBuffer.wrap(chunk2));
+                        content.close();
+                    }
+                };
+            }
+        });
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString())
+                .content(content)
+                .send(new BufferingResponseListener()
+                {
+                    @Override
+                    public void onComplete(Result result)
+                    {
+                        Assert.assertArrayEquals(data, getContent());
+                        latch.countDown();
+                    }
+                });
+
+        Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void test_Expect100Continue_WithTwoResponsesInOneRead() throws Exception
+    {
+        // There is a chance that the server replies with the 100 Continue response
+        // and immediately after with the "normal" response, say a 200 OK.
+        // These may be read by the client in a single read, and must be handled correctly.
+
+        startClient();
+
+        try (ServerSocket server = new ServerSocket())
+        {
+            server.bind(new InetSocketAddress("localhost", 0));
+
+            final CountDownLatch latch = new CountDownLatch(1);
+            client.newRequest("localhost", server.getLocalPort())
+                    .header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString())
+                    .content(new BytesContentProvider(new byte[]{0}))
+                    .send(new Response.CompleteListener()
+                    {
+                        @Override
+                        public void onComplete(Result result)
+                        {
+                            Assert.assertTrue(result.toString(), result.isSucceeded());
+                            Assert.assertEquals(200, result.getResponse().getStatus());
+                            latch.countDown();
+                        }
+                    });
+
+            try (Socket socket = server.accept())
+            {
+                // Read the request headers.
+                InputStream input = socket.getInputStream();
+                int crlfs = 0;
+                while (true)
+                {
+                    int read = input.read();
+                    if (read == '\r' || read == '\n')
+                        ++crlfs;
+                    else
+                        crlfs = 0;
+                    if (crlfs == 4)
+                        break;
+                }
+
+                OutputStream output = socket.getOutputStream();
+                String responses = "" +
+                        "HTTP/1.1 100 Continue\r\n" +
+                        "\r\n" +
+                        "HTTP/1.1 200 OK\r\n" +
+                        "Transfer-Encoding: chunked\r\n" +
+                        "\r\n" +
+                        "10\r\n" +
+                        "0123456789ABCDEF\r\n";
+                output.write(responses.getBytes(StandardCharsets.UTF_8));
+                output.flush();
+
+                Thread.sleep(1000);
+
+                String content = "" +
+                        "10\r\n" +
+                        "0123456789ABCDEF\r\n" +
+                        "0\r\n" +
+                        "\r\n";
+                output.write(content.getBytes(StandardCharsets.UTF_8));
+                output.flush();
+
+                Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+            }
+        }
+    }
+}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientCustomProxyTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientCustomProxyTest.java
new file mode 100644
index 0000000..08a45c9
--- /dev/null
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientCustomProxyTest.java
@@ -0,0 +1,246 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.io.IOException;
+import java.net.URI;
+import java.nio.ByteBuffer;
+import java.util.Map;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.client.api.Connection;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.io.AbstractConnection;
+import org.eclipse.jetty.io.ClientConnectionFactory;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.server.AbstractConnectionFactory;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.HttpConnectionFactory;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.Promise;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class HttpClientCustomProxyTest
+{
+    public static final byte[] CAFE_BABE = new byte[]{(byte)0xCA, (byte)0xFE, (byte)0xBA, (byte)0xBE};
+
+    private Server server;
+    private ServerConnector connector;
+    private HttpClient client;
+
+    public void prepare(Handler handler) throws Exception
+    {
+        server = new Server();
+        connector = new ServerConnector(server, new CAFEBABEServerConnectionFactory(new HttpConnectionFactory()));
+        server.addConnector(connector);
+        server.setHandler(handler);
+        server.start();
+
+        QueuedThreadPool executor = new QueuedThreadPool();
+        executor.setName(executor.getName() + "-client");
+        client = new HttpClient();
+        client.setExecutor(executor);
+        client.start();
+    }
+
+    @After
+    public void dispose() throws Exception
+    {
+        if (client != null)
+            client.stop();
+        if (server != null)
+            server.stop();
+    }
+
+    @Test
+    public void testCustomProxy() throws Exception
+    {
+        final String serverHost = "server";
+        final int status = HttpStatus.NO_CONTENT_204;
+        prepare(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                if (!URI.create(baseRequest.getUri().toString()).isAbsolute())
+                    response.setStatus(HttpServletResponse.SC_USE_PROXY);
+                else if (serverHost.equals(request.getServerName()))
+                    response.setStatus(status);
+                else
+                    response.setStatus(HttpServletResponse.SC_NOT_ACCEPTABLE);
+            }
+        });
+
+        // Setup the custom proxy
+        int proxyPort = connector.getLocalPort();
+        int serverPort = proxyPort + 1; // Any port will do for these tests - just not the same as the proxy
+        client.getProxyConfiguration().getProxies().add(new CAFEBABEProxy(new Origin.Address("localhost", proxyPort), false));
+
+        ContentResponse response = client.newRequest(serverHost, serverPort)
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        Assert.assertEquals(status, response.getStatus());
+    }
+
+    private class CAFEBABEProxy extends ProxyConfiguration.Proxy
+    {
+        private CAFEBABEProxy(Origin.Address address, boolean secure)
+        {
+            super(address, secure);
+        }
+
+        @Override
+        public ClientConnectionFactory newClientConnectionFactory(ClientConnectionFactory connectionFactory)
+        {
+            return new CAFEBABEClientConnectionFactory(connectionFactory);
+        }
+    }
+
+    private class CAFEBABEClientConnectionFactory implements ClientConnectionFactory
+    {
+        private final ClientConnectionFactory connectionFactory;
+
+        private CAFEBABEClientConnectionFactory(ClientConnectionFactory connectionFactory)
+        {
+            this.connectionFactory = connectionFactory;
+        }
+
+        @Override
+        public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map<String, Object> context) throws IOException
+        {
+            HttpDestination destination = (HttpDestination)context.get(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY);
+            Executor executor = destination.getHttpClient().getExecutor();
+            return new CAFEBABEConnection(endPoint, executor, connectionFactory, context);
+        }
+    }
+
+    private class CAFEBABEConnection extends AbstractConnection
+    {
+        private final ClientConnectionFactory connectionFactory;
+        private final Map<String, Object> context;
+
+        public CAFEBABEConnection(EndPoint endPoint, Executor executor, ClientConnectionFactory connectionFactory, Map<String, Object> context)
+        {
+            super(endPoint, executor);
+            this.connectionFactory = connectionFactory;
+            this.context = context;
+        }
+
+        @Override
+        public void onOpen()
+        {
+            super.onOpen();
+            fillInterested();
+            getEndPoint().write(new Callback.Adapter(), ByteBuffer.wrap(CAFE_BABE));
+        }
+
+        @Override
+        public void onFillable()
+        {
+            try
+            {
+                ByteBuffer buffer = BufferUtil.allocate(4);
+                int filled = getEndPoint().fill(buffer);
+                Assert.assertEquals(4, filled);
+                Assert.assertArrayEquals(CAFE_BABE, buffer.array());
+
+                // We are good, upgrade the connection
+                ClientConnectionFactory.Helper.replaceConnection(this, connectionFactory.newConnection(getEndPoint(), context));
+            }
+            catch (Throwable x)
+            {
+                close();
+                @SuppressWarnings("unchecked")
+                Promise<Connection> promise = (Promise<Connection>)context.get(HttpClientTransport.HTTP_CONNECTION_PROMISE_CONTEXT_KEY);
+                promise.failed(x);
+            }
+        }
+    }
+
+    private class CAFEBABEServerConnectionFactory extends AbstractConnectionFactory
+    {
+        private final org.eclipse.jetty.server.ConnectionFactory connectionFactory;
+
+        private CAFEBABEServerConnectionFactory(org.eclipse.jetty.server.ConnectionFactory connectionFactory)
+        {
+            super("cafebabe");
+            this.connectionFactory = connectionFactory;
+        }
+
+        @Override
+        public org.eclipse.jetty.io.Connection newConnection(Connector connector, EndPoint endPoint)
+        {
+            return new CAFEBABEServerConnection(connector, endPoint, connectionFactory);
+        }
+    }
+
+    private class CAFEBABEServerConnection extends AbstractConnection
+    {
+        private final org.eclipse.jetty.server.ConnectionFactory connectionFactory;
+
+        public CAFEBABEServerConnection(Connector connector, EndPoint endPoint, org.eclipse.jetty.server.ConnectionFactory connectionFactory)
+        {
+            super(endPoint, connector.getExecutor());
+            this.connectionFactory = connectionFactory;
+        }
+
+        @Override
+        public void onOpen()
+        {
+            super.onOpen();
+            fillInterested();
+        }
+
+        @Override
+        public void onFillable()
+        {
+            try
+            {
+                ByteBuffer buffer = BufferUtil.allocate(4);
+                int filled = getEndPoint().fill(buffer);
+                Assert.assertEquals(4, filled);
+                Assert.assertArrayEquals(CAFE_BABE, buffer.array());
+                getEndPoint().write(new Callback.Adapter(), buffer);
+
+                // We are good, upgrade the connection
+                ClientConnectionFactory.Helper.replaceConnection(this, connectionFactory.newConnection(connector, getEndPoint()));
+            }
+            catch (Throwable x)
+            {
+                close();
+            }
+        }
+    }
+}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientDuplexTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientDuplexTest.java
deleted file mode 100644
index d189646..0000000
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientDuplexTest.java
+++ /dev/null
@@ -1,385 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.InterruptedIOException;
-import java.io.OutputStream;
-import java.net.ServerSocket;
-import java.net.Socket;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.ByteArrayBuffer;
-import org.junit.After;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-
-public class HttpClientDuplexTest
-{
-    private ServerSocket server;
-    private HttpClient client;
-
-    @Before
-    public void prepare() throws Exception
-    {
-        server = new ServerSocket(0);
-
-        client = new HttpClient();
-        client.start();
-    }
-
-    @After
-    public void dispose() throws Exception
-    {
-        client.stop();
-        server.close();
-    }
-
-    @Test
-    public void testResponseHeadersBeforeRequestContent() throws Exception
-    {
-        final byte[] chunk1 = new byte[]{'A'};
-        final byte[] chunk2 = new byte[]{'B'};
-        final CountDownLatch requestContentLatch = new CountDownLatch(1);
-        ContentExchange exchange = new ContentExchange(true)
-        {
-            private int chunks;
-
-            @Override
-            public Buffer getRequestContentChunk(Buffer buffer) throws IOException
-            {
-                ++chunks;
-                if (chunks == 1)
-                {
-                    if (!await(requestContentLatch, 5000))
-                        throw new IOException();
-                    return new ByteArrayBuffer(chunk1);
-                }
-                else if (chunks == 2)
-                {
-                    // The test needs a second chunk to stay in "sending"
-                    // state and trigger the condition we want to test.
-                    return new ByteArrayBuffer(chunk2);
-                }
-                else
-                {
-                    return null;
-                }
-            }
-        };
-        exchange.setURL("http://localhost:" + server.getLocalPort());
-        // The test needs a fake content source to invoke
-        // getRequestContentChunk() which will provide the content.
-        exchange.setRequestContentSource(new ClosedInputStream());
-        exchange.setRequestHeader("Content-Length", String.valueOf(chunk1.length + chunk2.length));
-        client.send(exchange);
-
-        Socket socket = server.accept();
-        BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));
-        OutputStream output = socket.getOutputStream();
-
-        // Read headers.
-        while (true)
-        {
-            String line = input.readLine();
-            Assert.assertNotNull(line);
-            if (line.length() == 0)
-                break;
-        }
-
-        byte[] responseContent = new byte[64];
-        String responseHeaders = "" +
-                "HTTP/1.1 200 OK\r\n" +
-                "Content-Length: " + responseContent.length + "\r\n" +
-                "\r\n";
-        output.write(responseHeaders.getBytes("UTF-8"));
-        output.flush();
-
-        // Now send more request content.
-        requestContentLatch.countDown();
-
-        // Read request content on server.
-        for (int i = 0; i < chunk1.length; ++i)
-            Assert.assertNotEquals(-1, input.read());
-        for (int i = 0; i < chunk2.length; ++i)
-            Assert.assertNotEquals(-1, input.read());
-
-        // Send response content to client.
-        output.write(responseContent);
-        output.flush();
-
-        Assert.assertEquals(HttpExchange.STATUS_COMPLETED, exchange.waitForDone());
-        Assert.assertEquals(200, exchange.getResponseStatus());
-
-        socket.close();
-    }
-
-    @Test
-    public void testResponseHeadersBeforeRequestContentThenExpire() throws Exception
-    {
-        final byte[] chunk1 = new byte[]{'A'};
-        final byte[] chunk2 = new byte[]{'B'};
-        final byte[] chunk3 = new byte[]{'C'};
-        final CountDownLatch requestContentLatch = new CountDownLatch(1);
-        final long idleTimeout = 1000;
-        ContentExchange exchange = new ContentExchange(true)
-        {
-            private int chunks;
-
-            @Override
-            public Buffer getRequestContentChunk(Buffer buffer) throws IOException
-            {
-                ++chunks;
-                if (chunks == 1)
-                {
-                    if (!await(requestContentLatch, 5000))
-                        throw new IOException();
-                    return new ByteArrayBuffer(chunk1);
-                }
-                else if (chunks == 2)
-                {
-                    // The test needs a second chunk to stay in "sending"
-                    // state and trigger the condition we want to test.
-                    return new ByteArrayBuffer(chunk2);
-                }
-                else if (chunks == 3)
-                {
-                    // Idle timeout.
-                    await(new CountDownLatch(1), idleTimeout * 2);
-                }
-                return null;
-            }
-        };
-        exchange.setURL("http://localhost:" + server.getLocalPort());
-        // The test needs a fake content source to invoke
-        // getRequestContentChunk() which will provide the content.
-        exchange.setRequestContentSource(new ClosedInputStream());
-        exchange.setRequestHeader("Content-Length", String.valueOf(chunk1.length + chunk2.length + chunk3.length));
-        exchange.setTimeout(idleTimeout);
-        client.send(exchange);
-
-        Socket socket = server.accept();
-        BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));
-        OutputStream output = socket.getOutputStream();
-
-        // Read headers.
-        while (true)
-        {
-            String line = input.readLine();
-            Assert.assertNotNull(line);
-            if (line.length() == 0)
-                break;
-        }
-
-        byte[] responseContent = new byte[64];
-        String responseHeaders = "" +
-                "HTTP/1.1 200 OK\r\n" +
-                "Content-Length: " + responseContent.length + "\r\n" +
-                "\r\n";
-        output.write(responseHeaders.getBytes("UTF-8"));
-        output.flush();
-
-        // Now try to send more request content, but it will timeout.
-        requestContentLatch.countDown();
-
-        // Read request content on server.
-        for (int i = 0; i < chunk1.length; ++i)
-            Assert.assertNotEquals(-1, input.read());
-        // Server could possibly read -1.
-        for (int i = 0; i < chunk2.length; ++i)
-            input.read();
-        for (int i = 0; i < chunk3.length; ++i)
-            input.read();
-
-        // Send response content to client.
-        output.write(responseContent);
-        output.flush();
-
-        Assert.assertEquals(HttpExchange.STATUS_EXPIRED, exchange.waitForDone());
-
-        socket.close();
-    }
-
-    @Test
-    public void testResponseHeadersBeforeRequestContentThenThrow() throws Exception
-    {
-        final byte[] chunk1 = new byte[]{'A'};
-        final byte[] chunk2 = new byte[]{'B'};
-        final CountDownLatch requestContentLatch = new CountDownLatch(1);
-        ContentExchange exchange = new ContentExchange(true)
-        {
-            private int chunks;
-
-            @Override
-            public Buffer getRequestContentChunk(Buffer buffer) throws IOException
-            {
-                ++chunks;
-                if (chunks == 1)
-                {
-                    if (!await(requestContentLatch, 5000))
-                        throw new IOException();
-                    return new ByteArrayBuffer(chunk1);
-                }
-                else if (chunks == 2)
-                {
-                    // The test needs a second chunk to stay in "sending"
-                    // state and trigger the condition we want to test.
-                    return new ByteArrayBuffer(chunk2);
-                }
-                else
-                {
-                    throw new IOException();
-                }
-            }
-        };
-        exchange.setURL("http://localhost:" + server.getLocalPort());
-        // The test needs a fake content source to invoke
-        // getRequestContentChunk() which will provide the content.
-        exchange.setRequestContentSource(new ClosedInputStream());
-        exchange.setRequestHeader("Content-Length", String.valueOf(chunk1.length + chunk2.length));
-        client.send(exchange);
-
-        Socket socket = server.accept();
-        BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));
-        OutputStream output = socket.getOutputStream();
-
-        // Read headers.
-        while (true)
-        {
-            String line = input.readLine();
-            Assert.assertNotNull(line);
-            if (line.length() == 0)
-                break;
-        }
-
-        byte[] responseContent = new byte[64];
-        String responseHeaders = "" +
-                "HTTP/1.1 200 OK\r\n" +
-                "Content-Length: " + responseContent.length + "\r\n" +
-                "\r\n";
-        output.write(responseHeaders.getBytes("UTF-8"));
-        output.flush();
-
-        // Now send more request content.
-        requestContentLatch.countDown();
-
-        // Read request content on server.
-        for (int i = 0; i < chunk1.length; ++i)
-            Assert.assertNotEquals(-1, input.read());
-        // Server could possibly read -1.
-        for (int i = 0; i < chunk2.length; ++i)
-            input.read();
-
-        // Send response content to client.
-        output.write(responseContent);
-        output.flush();
-
-        Assert.assertEquals(HttpExchange.STATUS_EXCEPTED, exchange.waitForDone());
-
-        socket.close();
-    }
-
-
-    @Test
-    public void testResponseCompleteBeforeRequestContent() throws Exception
-    {
-        // Must be greater than 2 to stay in "sending" state while
-        // receiving the response and trigger the condition of this test.
-        int contentLength = 4;
-        final byte[] chunk = new byte[]{'A'};
-        final AtomicInteger requestContent = new AtomicInteger(contentLength);
-        ContentExchange exchange = new ContentExchange(true)
-        {
-            @Override
-            public Buffer getRequestContentChunk(Buffer buffer) throws IOException
-            {
-                if (requestContent.decrementAndGet() > 0)
-                    return new ByteArrayBuffer(chunk);
-                return null;
-            }
-        };
-        exchange.setURL("http://localhost:" + server.getLocalPort());
-        // The test needs a fake content source to invoke
-        // getRequestContentChunk() which will provide the content.
-        exchange.setRequestContentSource(new ClosedInputStream());
-        exchange.setRequestHeader("Content-Length", String.valueOf(contentLength));
-        client.send(exchange);
-
-        Socket socket = server.accept();
-        BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));
-        OutputStream output = socket.getOutputStream();
-
-        // Read headers.
-        while (true)
-        {
-            String line = input.readLine();
-            Assert.assertNotNull(line);
-            if (line.length() == 0)
-                break;
-        }
-
-        // Send the whole response.
-        String responseHeaders = "" +
-                "HTTP/1.1 200 OK\r\n" +
-                "Content-Length: 0\r\n" +
-                "\r\n";
-        output.write(responseHeaders.getBytes("UTF-8"));
-        output.flush();
-
-        // Read request content on server.
-        while (true)
-        {
-            if (input.read() < 0)
-                break;
-        }
-
-        Assert.assertEquals(HttpExchange.STATUS_COMPLETED, exchange.waitForDone());
-        Assert.assertEquals(200, exchange.getResponseStatus());
-
-        socket.close();
-    }
-
-    private boolean await(CountDownLatch latch, long millis) throws InterruptedIOException
-    {
-        try
-        {
-            return latch.await(millis, TimeUnit.MILLISECONDS);
-        }
-        catch (InterruptedException x)
-        {
-            throw new InterruptedIOException();
-        }
-    }
-
-    private class ClosedInputStream extends InputStream
-    {
-        @Override
-        public int read() throws IOException
-        {
-            return -1;
-        }
-    }
-}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientExplicitConnectionTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientExplicitConnectionTest.java
new file mode 100644
index 0000000..34c0b0f
--- /dev/null
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientExplicitConnectionTest.java
@@ -0,0 +1,101 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.client.api.Connection;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Destination;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.http.HttpConnectionOverHTTP;
+import org.eclipse.jetty.client.http.HttpDestinationOverHTTP;
+import org.eclipse.jetty.client.util.FutureResponseListener;
+import org.eclipse.jetty.toolchain.test.annotation.Slow;
+import org.eclipse.jetty.util.FuturePromise;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class HttpClientExplicitConnectionTest extends AbstractHttpClientServerTest
+{
+    public HttpClientExplicitConnectionTest(SslContextFactory sslContextFactory)
+    {
+        super(sslContextFactory);
+    }
+
+    @Test
+    public void testExplicitConnection() throws Exception
+    {
+        start(new EmptyServerHandler());
+
+        Destination destination = client.getDestination(scheme, "localhost", connector.getLocalPort());
+        FuturePromise<Connection> futureConnection = new FuturePromise<>();
+        destination.newConnection(futureConnection);
+        try (Connection connection = futureConnection.get(5, TimeUnit.SECONDS))
+        {
+            Request request = client.newRequest(destination.getHost(), destination.getPort()).scheme(scheme);
+            FutureResponseListener listener = new FutureResponseListener(request);
+            connection.send(request, listener);
+            ContentResponse response = listener.get(5, TimeUnit.SECONDS);
+
+            Assert.assertNotNull(response);
+            Assert.assertEquals(200, response.getStatus());
+
+            HttpDestinationOverHTTP httpDestination = (HttpDestinationOverHTTP)destination;
+            ConnectionPool connectionPool = httpDestination.getConnectionPool();
+            Assert.assertTrue(connectionPool.getActiveConnections().isEmpty());
+            Assert.assertTrue(connectionPool.getIdleConnections().isEmpty());
+        }
+    }
+
+    @Slow
+    @Test
+    public void testExplicitConnectionIsClosedOnRemoteClose() throws Exception
+    {
+        start(new EmptyServerHandler());
+
+        Destination destination = client.getDestination(scheme, "localhost", connector.getLocalPort());
+        FuturePromise<Connection> futureConnection = new FuturePromise<>();
+        destination.newConnection(futureConnection);
+        Connection connection = futureConnection.get(5, TimeUnit.SECONDS);
+        Request request = client.newRequest(destination.getHost(), destination.getPort()).scheme(scheme);
+        FutureResponseListener listener = new FutureResponseListener(request);
+        connection.send(request, listener);
+        ContentResponse response = listener.get(5, TimeUnit.SECONDS);
+
+        Assert.assertEquals(200, response.getStatus());
+
+        // Wait some time to have the client is an idle state.
+        TimeUnit.SECONDS.sleep(1);
+
+        connector.stop();
+
+        // Give the connection some time to process the remote close.
+        TimeUnit.SECONDS.sleep(1);
+
+        HttpConnectionOverHTTP httpConnection = (HttpConnectionOverHTTP)connection;
+        Assert.assertFalse(httpConnection.getEndPoint().isOpen());
+
+        HttpDestinationOverHTTP httpDestination = (HttpDestinationOverHTTP)destination;
+        ConnectionPool connectionPool = httpDestination.getConnectionPool();
+        Assert.assertTrue(connectionPool.getActiveConnections().isEmpty());
+        Assert.assertTrue(connectionPool.getIdleConnections().isEmpty());
+    }
+}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientFailureTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientFailureTest.java
new file mode 100644
index 0000000..585ff24
--- /dev/null
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientFailureTest.java
@@ -0,0 +1,270 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.nio.ByteBuffer;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.client.api.Result;
+import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP;
+import org.eclipse.jetty.client.http.HttpConnectionOverHTTP;
+import org.eclipse.jetty.client.util.DeferredContentProvider;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class HttpClientFailureTest
+{
+    private Server server;
+    private ServerConnector connector;
+    private HttpClient client;
+
+    private void startServer(Handler handler) throws Exception
+    {
+        QueuedThreadPool serverThreads = new QueuedThreadPool();
+        serverThreads.setName("server");
+        server = new Server(serverThreads);
+        connector = new ServerConnector(server);
+        server.addConnector(connector);
+        server.setHandler(handler);
+        server.start();
+    }
+
+    @After
+    public void dispose() throws Exception
+    {
+        if (server != null)
+            server.stop();
+        if (client != null)
+            client.stop();
+    }
+
+    @Test
+    public void testFailureBeforeRequestCommit() throws Exception
+    {
+        startServer(new EmptyServerHandler());
+
+        final AtomicReference<HttpConnectionOverHTTP> connectionRef = new AtomicReference<>();
+        client = new HttpClient(new HttpClientTransportOverHTTP()
+        {
+            @Override
+            protected HttpConnectionOverHTTP newHttpConnection(EndPoint endPoint, HttpDestination destination)
+            {
+                HttpConnectionOverHTTP connection = super.newHttpConnection(endPoint, destination);
+                connectionRef.set(connection);
+                return connection;
+            }
+        }, null);
+        client.start();
+
+        try
+        {
+            client.newRequest("localhost", connector.getLocalPort())
+                    .onRequestHeaders(new Request.HeadersListener()
+                    {
+                        @Override
+                        public void onHeaders(Request request)
+                        {
+                            connectionRef.get().getEndPoint().close();
+                        }
+                    })
+                    .timeout(5, TimeUnit.SECONDS)
+                    .send();
+            Assert.fail();
+        }
+        catch (ExecutionException x)
+        {
+            // Expected.
+        }
+
+        ConnectionPool connectionPool = connectionRef.get().getHttpDestination().getConnectionPool();
+        Assert.assertEquals(0, connectionPool.getConnectionCount());
+        Assert.assertEquals(0, connectionPool.getActiveConnections().size());
+        Assert.assertEquals(0, connectionPool.getIdleConnections().size());
+    }
+
+    @Test
+    public void testFailureAfterRequestCommit() throws Exception
+    {
+        startServer(new EmptyServerHandler());
+
+        final AtomicReference<HttpConnectionOverHTTP> connectionRef = new AtomicReference<>();
+        client = new HttpClient(new HttpClientTransportOverHTTP()
+        {
+            @Override
+            protected HttpConnectionOverHTTP newHttpConnection(EndPoint endPoint, HttpDestination destination)
+            {
+                HttpConnectionOverHTTP connection = super.newHttpConnection(endPoint, destination);
+                connectionRef.set(connection);
+                return connection;
+            }
+        }, null);
+        client.start();
+
+        final CountDownLatch commitLatch = new CountDownLatch(1);
+        final CountDownLatch completeLatch = new CountDownLatch(1);
+        DeferredContentProvider content = new DeferredContentProvider();
+        client.newRequest("localhost", connector.getLocalPort())
+                .onRequestCommit(new Request.CommitListener()
+                {
+                    @Override
+                    public void onCommit(Request request)
+                    {
+                        connectionRef.get().getEndPoint().close();
+                        commitLatch.countDown();
+                    }
+                })
+                .content(content)
+                .idleTimeout(2, TimeUnit.SECONDS)
+                .send(new Response.CompleteListener()
+                {
+                    @Override
+                    public void onComplete(Result result)
+                    {
+                        if (result.isFailed())
+                            completeLatch.countDown();
+                    }
+                });
+
+        Assert.assertTrue(commitLatch.await(5, TimeUnit.SECONDS));
+        final CountDownLatch contentLatch = new CountDownLatch(1);
+        content.offer(ByteBuffer.allocate(1024), new Callback.Adapter()
+        {
+            @Override
+            public void failed(Throwable x)
+            {
+                contentLatch.countDown();
+            }
+        });
+
+        Assert.assertTrue(commitLatch.await(5, TimeUnit.SECONDS));
+        Assert.assertTrue(contentLatch.await(5, TimeUnit.SECONDS));
+        Assert.assertTrue(completeLatch.await(5, TimeUnit.SECONDS));
+
+        ConnectionPool connectionPool = connectionRef.get().getHttpDestination().getConnectionPool();
+        Assert.assertEquals(0, connectionPool.getConnectionCount());
+        Assert.assertEquals(0, connectionPool.getActiveConnections().size());
+        Assert.assertEquals(0, connectionPool.getIdleConnections().size());
+    }
+/*
+    @Test
+    public void test_ExchangeIsComplete_WhenRequestFailsMidway_WithResponse() throws Exception
+    {
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                // Echo back
+                IO.copy(request.getInputStream(), response.getOutputStream());
+            }
+        });
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                        // The second ByteBuffer set to null will throw an exception
+                .content(new ContentProvider()
+                {
+                    @Override
+                    public long getLength()
+                    {
+                        return -1;
+                    }
+
+                    @Override
+                    public Iterator<ByteBuffer> iterator()
+                    {
+                        return new Iterator<ByteBuffer>()
+                        {
+                            @Override
+                            public boolean hasNext()
+                            {
+                                return true;
+                            }
+
+                            @Override
+                            public ByteBuffer next()
+                            {
+                                throw new NoSuchElementException("explicitly_thrown_by_test");
+                            }
+
+                            @Override
+                            public void remove()
+                            {
+                                throw new UnsupportedOperationException();
+                            }
+                        };
+                    }
+                })
+                .send(new Response.Listener.Adapter()
+                {
+                    @Override
+                    public void onComplete(Result result)
+                    {
+                        latch.countDown();
+                    }
+                });
+
+        Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void test_ExchangeIsComplete_WhenRequestFails_WithNoResponse() throws Exception
+    {
+        start(new EmptyServerHandler());
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        final String host = "localhost";
+        final int port = connector.getLocalPort();
+        client.newRequest(host, port)
+                .scheme(scheme)
+                .onRequestBegin(new Request.BeginListener()
+                {
+                    @Override
+                    public void onBegin(Request request)
+                    {
+                        HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
+                        destination.getConnectionPool().getActiveConnections().peek().close();
+                    }
+                })
+                .send(new Response.Listener.Adapter()
+                {
+                    @Override
+                    public void onComplete(Result result)
+                    {
+                        latch.countDown();
+                    }
+                });
+
+        Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+    }
+*/
+}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientGZIPTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientGZIPTest.java
new file mode 100644
index 0000000..bd45bb8
--- /dev/null
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientGZIPTest.java
@@ -0,0 +1,209 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.util.Arrays;
+import java.util.concurrent.TimeUnit;
+import java.util.zip.GZIPOutputStream;
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class HttpClientGZIPTest extends AbstractHttpClientServerTest
+{
+    public HttpClientGZIPTest(SslContextFactory sslContextFactory)
+    {
+        super(sslContextFactory);
+    }
+
+    @Test
+    public void testGZIPContentEncoding() throws Exception
+    {
+        final byte[] data = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                response.setHeader("Content-Encoding", "gzip");
+                GZIPOutputStream gzipOutput = new GZIPOutputStream(response.getOutputStream());
+                gzipOutput.write(data);
+                gzipOutput.finish();
+            }
+        });
+
+        ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        Assert.assertEquals(200, response.getStatus());
+        Assert.assertArrayEquals(data, response.getContent());
+    }
+
+    @Test
+    public void testGZIPContentOneByteAtATime() throws Exception
+    {
+        final byte[] data = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                response.setHeader("Content-Encoding", "gzip");
+
+                ByteArrayOutputStream gzipData = new ByteArrayOutputStream();
+                GZIPOutputStream gzipOutput = new GZIPOutputStream(gzipData);
+                gzipOutput.write(data);
+                gzipOutput.finish();
+
+                ServletOutputStream output = response.getOutputStream();
+                byte[] gzipBytes = gzipData.toByteArray();
+                for (byte gzipByte : gzipBytes)
+                {
+                    output.write(gzipByte);
+                    output.flush();
+                    sleep(100);
+                }
+            }
+        });
+
+        ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .send();
+
+        Assert.assertEquals(200, response.getStatus());
+        Assert.assertArrayEquals(data, response.getContent());
+    }
+
+    @Test
+    public void testGZIPContentSentTwiceInOneWrite() throws Exception
+    {
+        final byte[] data = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                response.setHeader("Content-Encoding", "gzip");
+
+                ByteArrayOutputStream gzipData = new ByteArrayOutputStream();
+                GZIPOutputStream gzipOutput = new GZIPOutputStream(gzipData);
+                gzipOutput.write(data);
+                gzipOutput.finish();
+
+                byte[] gzipBytes = gzipData.toByteArray();
+                byte[] content = Arrays.copyOf(gzipBytes, 2 * gzipBytes.length);
+                System.arraycopy(gzipBytes, 0, content, gzipBytes.length, gzipBytes.length);
+
+                ServletOutputStream output = response.getOutputStream();
+                output.write(content);
+            }
+        });
+
+        ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .send();
+
+        Assert.assertEquals(200, response.getStatus());
+
+        byte[] expected = Arrays.copyOf(data, 2 * data.length);
+        System.arraycopy(data, 0, expected, data.length, data.length);
+        Assert.assertArrayEquals(expected, response.getContent());
+    }
+
+    @Test
+    public void testGZIPContentFragmentedBeforeTrailer() throws Exception
+    {
+        // There are 8 trailer bytes to gzip encoding.
+        testGZIPContentFragmented(9);
+    }
+
+    @Test
+    public void testGZIPContentFragmentedAtTrailer() throws Exception
+    {
+        // There are 8 trailer bytes to gzip encoding.
+        testGZIPContentFragmented(1);
+    }
+
+    private void testGZIPContentFragmented(final int fragment) throws Exception
+    {
+        final byte[] data = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                response.setHeader("Content-Encoding", "gzip");
+
+                ByteArrayOutputStream gzipData = new ByteArrayOutputStream();
+                GZIPOutputStream gzipOutput = new GZIPOutputStream(gzipData);
+                gzipOutput.write(data);
+                gzipOutput.finish();
+
+                byte[] gzipBytes = gzipData.toByteArray();
+                byte[] chunk1 = Arrays.copyOfRange(gzipBytes, 0, gzipBytes.length - fragment);
+                byte[] chunk2 = Arrays.copyOfRange(gzipBytes, gzipBytes.length - fragment, gzipBytes.length);
+
+                ServletOutputStream output = response.getOutputStream();
+                output.write(chunk1);
+                output.flush();
+
+                sleep(500);
+
+                output.write(chunk2);
+                output.flush();
+            }
+        });
+
+        ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .send();
+
+        Assert.assertEquals(200, response.getStatus());
+        Assert.assertArrayEquals(data, response.getContent());
+    }
+
+    private static void sleep(long ms) throws IOException
+    {
+        try
+        {
+            TimeUnit.MILLISECONDS.sleep(ms);
+        }
+        catch (InterruptedException x)
+        {
+            throw new InterruptedIOException();
+        }
+    }
+}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientLoadTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientLoadTest.java
new file mode 100644
index 0000000..d5dbda2
--- /dev/null
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientLoadTest.java
@@ -0,0 +1,317 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+import java.util.Random;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.client.api.Connection;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.client.api.Result;
+import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP;
+import org.eclipse.jetty.client.http.HttpConnectionOverHTTP;
+import org.eclipse.jetty.client.http.HttpDestinationOverHTTP;
+import org.eclipse.jetty.client.util.BytesContentProvider;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpScheme;
+import org.eclipse.jetty.io.LeakTrackingByteBufferPool;
+import org.eclipse.jetty.io.MappedByteBufferPool;
+import org.eclipse.jetty.server.AbstractConnectionFactory;
+import org.eclipse.jetty.server.HttpConnectionFactory;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.LeakDetector;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.eclipse.jetty.util.thread.Scheduler;
+import org.junit.Assert;
+import org.junit.Test;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
+public class HttpClientLoadTest extends AbstractHttpClientServerTest
+{
+    private final Logger logger = Log.getLogger(HttpClientLoadTest.class);
+
+    public HttpClientLoadTest(SslContextFactory sslContextFactory)
+    {
+        super(sslContextFactory);
+    }
+
+    @Test
+    public void testIterative() throws Exception
+    {
+        int cores = Runtime.getRuntime().availableProcessors();
+
+        final AtomicLong connectionLeaks = new AtomicLong();
+
+        start(new LoadHandler());
+        server.stop();
+        server.removeConnector(connector);
+        LeakTrackingByteBufferPool serverBufferPool = new LeakTrackingByteBufferPool(new MappedByteBufferPool.Tagged());
+        connector = new ServerConnector(server, connector.getExecutor(), connector.getScheduler(),
+                serverBufferPool , 1, Math.min(1, cores / 2), 
+                AbstractConnectionFactory.getFactories(sslContextFactory, new HttpConnectionFactory()));
+        server.addConnector(connector);
+        server.start();
+
+        client.stop();
+        
+        HttpClient newClient = new HttpClient(new HttpClientTransportOverHTTP()
+        {
+            @Override
+            public HttpDestination newHttpDestination(Origin origin)
+            {
+                return new HttpDestinationOverHTTP(getHttpClient(), origin)
+                {
+                    @Override
+                    protected ConnectionPool newConnectionPool(HttpClient client)
+                    {
+                        return new LeakTrackingConnectionPool(this, client.getMaxConnectionsPerDestination(), this)
+                        {
+                            @Override
+                            protected void leaked(LeakDetector.LeakInfo resource)
+                            {
+                                connectionLeaks.incrementAndGet();
+                            }
+                        };
+                    }
+                };
+            }
+        }, sslContextFactory);
+        newClient.setExecutor(client.getExecutor());
+        client = newClient;
+        LeakTrackingByteBufferPool clientBufferPool = new LeakTrackingByteBufferPool(new MappedByteBufferPool.Tagged());
+        client.setByteBufferPool(clientBufferPool);
+        client.setMaxConnectionsPerDestination(32768);
+        client.setMaxRequestsQueuedPerDestination(1024 * 1024);
+        client.setDispatchIO(false);
+        client.setStrictEventOrdering(false);
+        client.start();
+
+        Random random = new Random();
+        // At least 25k requests to warmup properly (use -XX:+PrintCompilation to verify JIT activity)
+        int runs = 1;
+        int iterations = 500;
+        for (int i = 0; i < runs; ++i)
+        {
+            run(random, iterations);
+        }
+
+        // Re-run after warmup
+        iterations = 5_000;
+        for (int i = 0; i < runs; ++i)
+        {
+            run(random, iterations);
+        }
+
+        System.gc();
+
+        assertThat("Server BufferPool - leaked acquires", serverBufferPool.getLeakedAcquires(), is(0L));
+        assertThat("Server BufferPool - leaked releases", serverBufferPool.getLeakedReleases(), is(0L));
+        assertThat("Server BufferPool - unreleased", serverBufferPool.getLeakedResources(), is(0L));
+        
+        assertThat("Client BufferPool - leaked acquires", clientBufferPool.getLeakedAcquires(), is(0L));
+        assertThat("Client BufferPool - leaked releases", clientBufferPool.getLeakedReleases(), is(0L));
+        assertThat("Client BufferPool - unreleased", clientBufferPool.getLeakedResources(), is(0L));
+        
+        assertThat("Connection Leaks", connectionLeaks.get(), is(0L));
+    }
+
+    private void run(Random random, int iterations) throws InterruptedException
+    {
+        CountDownLatch latch = new CountDownLatch(iterations);
+        List<String> failures = new ArrayList<>();
+
+        int factor = logger.isDebugEnabled() ? 25 : 1;
+        factor *= "http".equalsIgnoreCase(scheme) ? 10 : 1000;
+
+        // Dumps the state of the client if the test takes too long
+        final Thread testThread = Thread.currentThread();
+        Scheduler.Task task = client.getScheduler().schedule(new Runnable()
+        {
+            @Override
+            public void run()
+            {
+                logger.warn("Interrupting test, it is taking too long");
+                for (String host : Arrays.asList("localhost", "127.0.0.1"))
+                {
+                    HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, connector.getLocalPort());
+                    ConnectionPool connectionPool = destination.getConnectionPool();
+                    for (Connection connection : new ArrayList<>(connectionPool.getActiveConnections()))
+                    {
+                        HttpConnectionOverHTTP active = (HttpConnectionOverHTTP)connection;
+                        logger.warn(active.getEndPoint() + " exchange " + active.getHttpChannel().getHttpExchange());
+                    }
+                }
+                testThread.interrupt();
+            }
+        }, iterations * factor, TimeUnit.MILLISECONDS);
+
+        long begin = System.nanoTime();
+        for (int i = 0; i < iterations; ++i)
+        {
+            test(random, latch, failures);
+//            test("http", "localhost", "GET", false, false, 64 * 1024, false, latch, failures);
+        }
+        Assert.assertTrue(latch.await(iterations, TimeUnit.SECONDS));
+        long end = System.nanoTime();
+        task.cancel();
+        long elapsed = TimeUnit.NANOSECONDS.toMillis(end - begin);
+        logger.info("{} requests in {} ms, {} req/s", iterations, elapsed, elapsed > 0 ? iterations * 1000 / elapsed : -1);
+
+        for (String failure : failures)
+            System.err.println("FAILED: "+failure);
+
+        Assert.assertTrue(failures.toString(), failures.isEmpty());
+    }
+
+    private void test(Random random, final CountDownLatch latch, final List<String> failures) throws InterruptedException
+    {
+        // Choose a random destination
+        String host = random.nextBoolean() ? "localhost" : "127.0.0.1";
+        // Choose a random method
+        HttpMethod method = random.nextBoolean() ? HttpMethod.GET : HttpMethod.POST;
+
+        boolean ssl = HttpScheme.HTTPS.is(scheme);
+
+        // Choose randomly whether to close the connection on the client or on the server
+        boolean clientClose = false;
+        if (!ssl && random.nextBoolean())
+            clientClose = true;
+        boolean serverClose = false;
+        if (!ssl && random.nextBoolean())
+            serverClose = true;
+
+        int maxContentLength = 64 * 1024;
+        int contentLength = random.nextInt(maxContentLength) + 1;
+
+        test(scheme, host, method.asString(), clientClose, serverClose, contentLength, true, latch, failures);
+    }
+
+    private void test(String scheme, String host, String method, boolean clientClose, boolean serverClose, int contentLength, final boolean checkContentLength, final CountDownLatch latch, final List<String> failures) throws InterruptedException
+    {
+        Request request = client.newRequest(host, connector.getLocalPort())
+                .scheme(scheme)
+                .method(method);
+
+        if (clientClose)
+            request.header(HttpHeader.CONNECTION, "close");
+        else if (serverClose)
+            request.header("X-Close", "true");
+
+        switch (method)
+        {
+            case "GET":
+                request.header("X-Download", String.valueOf(contentLength));
+                break;
+            case "POST":
+                request.header("X-Upload", String.valueOf(contentLength));
+                request.content(new BytesContentProvider(new byte[contentLength]));
+                break;
+        }
+
+        final CountDownLatch requestLatch = new CountDownLatch(1);
+        request.send(new Response.Listener.Adapter()
+        {
+            private final AtomicInteger contentLength = new AtomicInteger();
+
+            @Override
+            public void onHeaders(Response response)
+            {
+                if (checkContentLength)
+                {
+                    String content = response.getHeaders().get("X-Content");
+                    if (content != null)
+                        contentLength.set(Integer.parseInt(content));
+                }
+            }
+
+            @Override
+            public void onContent(Response response, ByteBuffer content)
+            {
+                if (checkContentLength)
+                    contentLength.addAndGet(-content.remaining());
+            }
+
+            @Override
+            public void onComplete(Result result)
+            {
+                if (result.isFailed())
+                {
+                    result.getFailure().printStackTrace();
+                    failures.add("Result failed " + result);
+                }
+
+                if (checkContentLength && contentLength.get() != 0)
+                    failures.add("Content length mismatch " + contentLength);
+
+                requestLatch.countDown();
+                latch.countDown();
+            }
+        });
+        requestLatch.await(5, TimeUnit.SECONDS);
+    }
+
+    private class LoadHandler extends AbstractHandler
+    {
+        @Override
+        public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+        {
+            String method = request.getMethod().toUpperCase(Locale.ENGLISH);
+            switch (method)
+            {
+                case "GET":
+                    int contentLength = request.getIntHeader("X-Download");
+                    if (contentLength > 0)
+                    {
+                        response.setHeader("X-Content", String.valueOf(contentLength));
+                        response.getOutputStream().write(new byte[contentLength]);
+                    }
+                    break;
+                case "POST":
+                    response.setHeader("X-Content", request.getHeader("X-Upload"));
+                    IO.copy(request.getInputStream(), response.getOutputStream());
+                    break;
+            }
+
+            if (Boolean.parseBoolean(request.getHeader("X-Close")))
+                response.setHeader("Connection", "close");
+
+            baseRequest.setHandled(true);
+        }
+    }
+}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientProxyTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientProxyTest.java
new file mode 100644
index 0000000..1156804
--- /dev/null
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientProxyTest.java
@@ -0,0 +1,160 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.io.IOException;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.util.BasicAuthentication;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.util.B64Code;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class HttpClientProxyTest extends AbstractHttpClientServerTest
+{
+    public HttpClientProxyTest(SslContextFactory sslContextFactory)
+    {
+        // Avoid TLS otherwise CONNECT requests are sent instead of proxied requests
+        super(null);
+    }
+
+    @Test
+    public void testProxiedRequest() throws Exception
+    {
+        final String serverHost = "server";
+        final int status = HttpStatus.NO_CONTENT_204;
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                if (!URI.create(baseRequest.getUri().toString()).isAbsolute())
+                    response.setStatus(HttpServletResponse.SC_USE_PROXY);
+                else if (serverHost.equals(request.getServerName()))
+                    response.setStatus(status);
+                else
+                    response.setStatus(HttpServletResponse.SC_NOT_ACCEPTABLE);
+            }
+        });
+
+        int proxyPort = connector.getLocalPort();
+        int serverPort = proxyPort + 1; // Any port will do for these tests - just not the same as the proxy
+        client.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", proxyPort));
+
+        ContentResponse response = client.newRequest(serverHost, serverPort)
+                .scheme(scheme)
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        Assert.assertEquals(status, response.getStatus());
+    }
+
+    @Test
+    public void testAuthenticatedProxiedRequest() throws Exception
+    {
+        final String user = "foo";
+        final String password = "bar";
+        final String credentials = B64Code.encode(user + ":" + password, StandardCharsets.ISO_8859_1);
+        final String serverHost = "server";
+        final String realm = "test_realm";
+        final int status = HttpStatus.NO_CONTENT_204;
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                String authorization = request.getHeader(HttpHeader.PROXY_AUTHORIZATION.asString());
+                if (authorization == null)
+                {
+                    response.setStatus(HttpStatus.PROXY_AUTHENTICATION_REQUIRED_407);
+                    response.setHeader(HttpHeader.PROXY_AUTHENTICATE.asString(), "Basic realm=\"" + realm + "\"");
+                }
+                else
+                {
+                    String prefix = "Basic ";
+                    if (authorization.startsWith(prefix))
+                    {
+                        String attempt = authorization.substring(prefix.length());
+                        if (credentials.equals(attempt))
+                            response.setStatus(status);
+                    }
+                }
+            }
+        });
+
+        String proxyHost = "localhost";
+        int proxyPort = connector.getLocalPort();
+        int serverPort = proxyPort + 1; // Any port will do for these tests - just not the same as the proxy
+        client.getProxyConfiguration().getProxies().add(new HttpProxy(proxyHost, proxyPort));
+
+        ContentResponse response1 = client.newRequest(serverHost, serverPort)
+                .scheme(scheme)
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        // No Authentication available => 407
+        Assert.assertEquals(HttpStatus.PROXY_AUTHENTICATION_REQUIRED_407, response1.getStatus());
+
+        // Add authentication...
+        URI uri = URI.create(scheme + "://" + proxyHost + ":" + proxyPort);
+        client.getAuthenticationStore().addAuthentication(new BasicAuthentication(uri, realm, user, password));
+        final AtomicInteger requests = new AtomicInteger();
+        client.getRequestListeners().add(new Request.Listener.Adapter()
+        {
+            @Override
+            public void onSuccess(Request request)
+            {
+                requests.incrementAndGet();
+            }
+        });
+        // ...and perform the request again => 407 + 204
+        ContentResponse response2 = client.newRequest(serverHost, serverPort)
+                .scheme(scheme)
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        Assert.assertEquals(status, response2.getStatus());
+        Assert.assertEquals(2, requests.get());
+
+        // Now the authentication result is cached => 204
+        requests.set(0);
+        ContentResponse response3 = client.newRequest(serverHost, serverPort)
+                .scheme(scheme)
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        Assert.assertEquals(status, response3.getStatus());
+        Assert.assertEquals(1, requests.get());
+    }
+}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientRedirectTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientRedirectTest.java
new file mode 100644
index 0000000..53cfa5a
--- /dev/null
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientRedirectTest.java
@@ -0,0 +1,490 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.io.IOException;
+import java.net.URLDecoder;
+import java.nio.ByteBuffer;
+import java.nio.channels.UnresolvedAddressException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.client.api.Result;
+import org.eclipse.jetty.client.util.ByteBufferContentProvider;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.toolchain.test.IO;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.hamcrest.Matchers;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class HttpClientRedirectTest extends AbstractHttpClientServerTest
+{
+    public HttpClientRedirectTest(SslContextFactory sslContextFactory)
+    {
+        super(sslContextFactory);
+    }
+
+    @Before
+    public void prepare() throws Exception
+    {
+        start(new RedirectHandler());
+    }
+
+    @Test
+    public void test_303() throws Exception
+    {
+        Response response = client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .path("/303/localhost/done")
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+        Assert.assertNotNull(response);
+        Assert.assertEquals(200, response.getStatus());
+        Assert.assertFalse(response.getHeaders().containsKey(HttpHeader.LOCATION.asString()));
+    }
+
+    @Test
+    public void test_303_302() throws Exception
+    {
+        Response response = client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .path("/303/localhost/302/localhost/done")
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+        Assert.assertNotNull(response);
+        Assert.assertEquals(200, response.getStatus());
+        Assert.assertFalse(response.getHeaders().containsKey(HttpHeader.LOCATION.asString()));
+    }
+
+    @Test
+    public void test_303_302_OnDifferentDestinations() throws Exception
+    {
+        Response response = client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .path("/303/127.0.0.1/302/localhost/done")
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+        Assert.assertNotNull(response);
+        Assert.assertEquals(200, response.getStatus());
+        Assert.assertFalse(response.getHeaders().containsKey(HttpHeader.LOCATION.asString()));
+    }
+
+    @Test
+    public void test_301() throws Exception
+    {
+        Response response = client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .method(HttpMethod.HEAD)
+                .path("/301/localhost/done")
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+        Assert.assertNotNull(response);
+        Assert.assertEquals(200, response.getStatus());
+        Assert.assertFalse(response.getHeaders().containsKey(HttpHeader.LOCATION.asString()));
+    }
+
+    @Test
+    public void test_301_WithWrongMethod() throws Exception
+    {
+        try
+        {
+            client.newRequest("localhost", connector.getLocalPort())
+                    .scheme(scheme)
+                    .method(HttpMethod.DELETE)
+                    .path("/301/localhost/done")
+                    .timeout(5, TimeUnit.SECONDS)
+                    .send();
+            Assert.fail();
+        }
+        catch (ExecutionException x)
+        {
+            HttpResponseException xx = (HttpResponseException)x.getCause();
+            Response response = xx.getResponse();
+            Assert.assertNotNull(response);
+            Assert.assertEquals(301, response.getStatus());
+            Assert.assertTrue(response.getHeaders().containsKey(HttpHeader.LOCATION.asString()));
+        }
+    }
+
+    @Test
+    public void test_307_WithRequestContent() throws Exception
+    {
+        byte[] data = new byte[]{0, 1, 2, 3, 4, 5, 6, 7};
+        ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .method(HttpMethod.POST)
+                .path("/307/localhost/done")
+                .content(new ByteBufferContentProvider(ByteBuffer.wrap(data)))
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+        Assert.assertNotNull(response);
+        Assert.assertEquals(200, response.getStatus());
+        Assert.assertFalse(response.getHeaders().containsKey(HttpHeader.LOCATION.asString()));
+        Assert.assertArrayEquals(data, response.getContent());
+    }
+
+    @Test
+    public void testMaxRedirections() throws Exception
+    {
+        client.setMaxRedirects(1);
+
+        try
+        {
+            client.newRequest("localhost", connector.getLocalPort())
+                    .scheme(scheme)
+                    .path("/303/localhost/302/localhost/done")
+                    .timeout(5, TimeUnit.SECONDS)
+                    .send();
+            Assert.fail();
+        }
+        catch (ExecutionException x)
+        {
+            HttpResponseException xx = (HttpResponseException)x.getCause();
+            Response response = xx.getResponse();
+            Assert.assertNotNull(response);
+            Assert.assertEquals(302, response.getStatus());
+            Assert.assertTrue(response.getHeaders().containsKey(HttpHeader.LOCATION.asString()));
+        }
+    }
+
+    @Test
+    public void test_303_WithConnectionClose_WithBigRequest() throws Exception
+    {
+        Response response = client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .path("/303/localhost/done?close=true")
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+        Assert.assertNotNull(response);
+        Assert.assertEquals(200, response.getStatus());
+        Assert.assertFalse(response.getHeaders().containsKey(HttpHeader.LOCATION.asString()));
+    }
+
+    @Test
+    public void testDontFollowRedirects() throws Exception
+    {
+        Response response = client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .followRedirects(false)
+                .path("/303/localhost/done?close=true")
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+        Assert.assertNotNull(response);
+        Assert.assertEquals(303, response.getStatus());
+        Assert.assertTrue(response.getHeaders().containsKey(HttpHeader.LOCATION.asString()));
+    }
+
+    @Test
+    public void testRelativeLocation() throws Exception
+    {
+        Response response = client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .path("/303/localhost/done?relative=true")
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+        Assert.assertNotNull(response);
+        Assert.assertEquals(200, response.getStatus());
+        Assert.assertFalse(response.getHeaders().containsKey(HttpHeader.LOCATION.asString()));
+    }
+
+    @Test
+    public void testAbsoluteURIPathWithSpaces() throws Exception
+    {
+        Response response = client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .path("/303/localhost/a+space?decode=true")
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+        Assert.assertNotNull(response);
+        Assert.assertEquals(200, response.getStatus());
+        Assert.assertFalse(response.getHeaders().containsKey(HttpHeader.LOCATION.asString()));
+    }
+
+    @Test
+    public void testRelativeURIPathWithSpaces() throws Exception
+    {
+        Response response = client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .path("/303/localhost/a+space?relative=true&decode=true")
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+        Assert.assertNotNull(response);
+        Assert.assertEquals(200, response.getStatus());
+        Assert.assertFalse(response.getHeaders().containsKey(HttpHeader.LOCATION.asString()));
+    }
+
+    @Test
+    public void testRedirectWithWrongScheme() throws Exception
+    {
+        dispose();
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                response.setStatus(303);
+                response.setHeader("Location", "ssh://localhost/path");
+            }
+        });
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .path("/path")
+                .timeout(5, TimeUnit.SECONDS)
+                .send(new Response.CompleteListener()
+                {
+                    @Override
+                    public void onComplete(Result result)
+                    {
+                        Assert.assertTrue(result.isFailed());
+                        latch.countDown();
+                    }
+                });
+        Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    @Ignore
+    public void testRedirectFailed() throws Exception
+    {
+        // TODO this test is failing with timout after an ISP upgrade??  DNS dependent?
+        try
+        {
+            client.newRequest("localhost", connector.getLocalPort())
+                    .scheme(scheme)
+                    .path("/303/doesNotExist/done")
+                    .timeout(5, TimeUnit.SECONDS)
+                    .send();
+        }
+        catch (ExecutionException x)
+        {
+            Assert.assertThat(x.getCause(), Matchers.instanceOf(UnresolvedAddressException.class));
+        }
+    }
+
+    @Test
+    public void test_HEAD_301() throws Exception
+    {
+        testSameMethodRedirect(HttpMethod.HEAD, HttpStatus.MOVED_PERMANENTLY_301);
+    }
+
+    @Test
+    public void test_POST_301() throws Exception
+    {
+        testGETRedirect(HttpMethod.POST, HttpStatus.MOVED_PERMANENTLY_301);
+    }
+
+    @Test
+    public void test_PUT_301() throws Exception
+    {
+        testSameMethodRedirect(HttpMethod.PUT, HttpStatus.MOVED_PERMANENTLY_301);
+    }
+
+    @Test
+    public void test_HEAD_302() throws Exception
+    {
+        testSameMethodRedirect(HttpMethod.HEAD, HttpStatus.FOUND_302);
+    }
+
+    @Test
+    public void test_POST_302() throws Exception
+    {
+        testGETRedirect(HttpMethod.POST, HttpStatus.FOUND_302);
+    }
+
+    @Test
+    public void test_PUT_302() throws Exception
+    {
+        testSameMethodRedirect(HttpMethod.PUT, HttpStatus.FOUND_302);
+    }
+
+    @Test
+    public void test_HEAD_303() throws Exception
+    {
+        testSameMethodRedirect(HttpMethod.HEAD, HttpStatus.SEE_OTHER_303);
+    }
+
+    @Test
+    public void test_POST_303() throws Exception
+    {
+        testGETRedirect(HttpMethod.POST, HttpStatus.SEE_OTHER_303);
+    }
+
+    @Test
+    public void test_PUT_303() throws Exception
+    {
+        testGETRedirect(HttpMethod.PUT, HttpStatus.SEE_OTHER_303);
+    }
+
+    @Test
+    public void test_HEAD_307() throws Exception
+    {
+        testSameMethodRedirect(HttpMethod.HEAD, HttpStatus.TEMPORARY_REDIRECT_307);
+    }
+
+    @Test
+    public void test_POST_307() throws Exception
+    {
+        testSameMethodRedirect(HttpMethod.POST, HttpStatus.TEMPORARY_REDIRECT_307);
+    }
+
+    @Test
+    public void test_PUT_307() throws Exception
+    {
+        testSameMethodRedirect(HttpMethod.PUT, HttpStatus.TEMPORARY_REDIRECT_307);
+    }
+
+    @Test
+    public void testHttpRedirector() throws Exception
+    {
+        final HttpRedirector redirector = new HttpRedirector(client);
+
+        org.eclipse.jetty.client.api.Request request1 = client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .path("/303/localhost/302/localhost/done")
+                .timeout(5, TimeUnit.SECONDS)
+                .followRedirects(false);
+        ContentResponse response1 = request1.send();
+
+        Assert.assertEquals(303, response1.getStatus());
+        Assert.assertTrue(redirector.isRedirect(response1));
+
+        Result result = redirector.redirect(request1, response1);
+        org.eclipse.jetty.client.api.Request request2 = result.getRequest();
+        Response response2 = result.getResponse();
+
+        Assert.assertEquals(302, response2.getStatus());
+        Assert.assertTrue(redirector.isRedirect(response2));
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        redirector.redirect(request2, response2, new Response.CompleteListener()
+        {
+            @Override
+            public void onComplete(Result result)
+            {
+                Response response3 = result.getResponse();
+                Assert.assertEquals(200, response3.getStatus());
+                Assert.assertFalse(redirector.isRedirect(response3));
+                latch.countDown();
+            }
+        });
+        Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+    }
+
+    private void testSameMethodRedirect(final HttpMethod method, int redirectCode) throws Exception
+    {
+        testMethodRedirect(method, method, redirectCode);
+    }
+
+    private void testGETRedirect(final HttpMethod method, int redirectCode) throws Exception
+    {
+        testMethodRedirect(method, HttpMethod.GET, redirectCode);
+    }
+
+    private void testMethodRedirect(final HttpMethod requestMethod, final HttpMethod redirectMethod, int redirectCode) throws Exception
+    {
+        final AtomicInteger passes = new AtomicInteger();
+        client.getRequestListeners().add(new org.eclipse.jetty.client.api.Request.Listener.Adapter()
+        {
+            @Override
+            public void onBegin(org.eclipse.jetty.client.api.Request request)
+            {
+                int pass = passes.incrementAndGet();
+                if (pass == 1)
+                {
+                    if (!requestMethod.is(request.getMethod()))
+                        request.abort(new Exception());
+                }
+                else if (pass == 2)
+                {
+                    if (!redirectMethod.is(request.getMethod()))
+                        request.abort(new Exception());
+                }
+                else
+                {
+                    request.abort(new Exception());
+                }
+            }
+        });
+
+        ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .method(requestMethod)
+                .path("/" + redirectCode + "/localhost/done")
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        Assert.assertEquals(200, response.getStatus());
+    }
+
+    private class RedirectHandler extends AbstractHandler
+    {
+        @Override
+        public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+        {
+            try
+            {
+                String[] paths = target.split("/", 4);
+
+                int status = Integer.parseInt(paths[1]);
+                response.setStatus(status);
+
+                String host = paths[2];
+                String path = paths[3];
+                boolean relative = Boolean.parseBoolean(request.getParameter("relative"));
+                String location = relative ? "" : request.getScheme() + "://" + host + ":" + request.getServerPort();
+                location += "/" + path;
+
+                if (Boolean.parseBoolean(request.getParameter("decode")))
+                    location = URLDecoder.decode(location, "UTF-8");
+
+                response.setHeader("Location", location);
+
+                if (Boolean.parseBoolean(request.getParameter("close")))
+                    response.setHeader("Connection", "close");
+            }
+            catch (NumberFormatException x)
+            {
+                response.setStatus(200);
+                // Echo content back
+                IO.copy(request.getInputStream(), response.getOutputStream());
+            }
+            finally
+            {
+                baseRequest.setHandled(true);
+            }
+        }
+    }
+}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientStreamTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientStreamTest.java
new file mode 100644
index 0000000..2b7c7e0
--- /dev/null
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientStreamTest.java
@@ -0,0 +1,1127 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InterruptedIOException;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.channels.AsynchronousCloseException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.Random;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicReference;
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.client.api.Result;
+import org.eclipse.jetty.client.util.BufferingResponseListener;
+import org.eclipse.jetty.client.util.BytesContentProvider;
+import org.eclipse.jetty.client.util.DeferredContentProvider;
+import org.eclipse.jetty.client.util.InputStreamContentProvider;
+import org.eclipse.jetty.client.util.InputStreamResponseListener;
+import org.eclipse.jetty.client.util.OutputStreamContentProvider;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.eclipse.jetty.toolchain.test.annotation.Slow;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.junit.Assert;
+import org.junit.Test;
+
+import static java.nio.file.StandardOpenOption.CREATE;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+public class HttpClientStreamTest extends AbstractHttpClientServerTest
+{
+    public HttpClientStreamTest(SslContextFactory sslContextFactory)
+    {
+        super(sslContextFactory);
+    }
+
+    @Test
+    public void testFileUpload() throws Exception
+    {
+        // Prepare a big file to upload
+        Path targetTestsDir = MavenTestingUtils.getTargetTestingDir().toPath();
+        Files.createDirectories(targetTestsDir);
+        Path upload = Paths.get(targetTestsDir.toString(), "http_client_upload.big");
+        try (OutputStream output = Files.newOutputStream(upload, CREATE))
+        {
+            byte[] kb = new byte[1024];
+            for (int i = 0; i < 10 * 1024; ++i)
+                output.write(kb);
+        }
+
+        start(new EmptyServerHandler());
+
+        final AtomicLong requestTime = new AtomicLong();
+        ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .file(upload)
+                .onRequestSuccess(new Request.SuccessListener()
+                {
+                    @Override
+                    public void onSuccess(Request request)
+                    {
+                        requestTime.set(System.nanoTime());
+                    }
+                })
+                .timeout(10, TimeUnit.SECONDS)
+                .send();
+        long responseTime = System.nanoTime();
+
+        Assert.assertEquals(200, response.getStatus());
+        Assert.assertTrue(requestTime.get() <= responseTime);
+
+        // Give some time to the server to consume the request content
+        // This is just to avoid exception traces in the test output
+        Thread.sleep(1000);
+    }
+
+    @Test
+    public void testDownload() throws Exception
+    {
+        final byte[] data = new byte[128 * 1024];
+        byte value = 1;
+        Arrays.fill(data, value);
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                response.getOutputStream().write(data);
+            }
+        });
+
+        InputStreamResponseListener listener = new InputStreamResponseListener();
+        client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .send(listener);
+        Response response = listener.get(5, TimeUnit.SECONDS);
+        Assert.assertNotNull(response);
+        Assert.assertEquals(200, response.getStatus());
+
+        InputStream input = listener.getInputStream();
+        Assert.assertNotNull(input);
+
+        int length = 0;
+        while (input.read() == value)
+        {
+            if (length % 100 == 0)
+                Thread.sleep(1);
+            ++length;
+        }
+
+        Assert.assertEquals(data.length, length);
+
+        Result result = listener.await(5, TimeUnit.SECONDS);
+        Assert.assertNotNull(result);
+        Assert.assertFalse(result.isFailed());
+        Assert.assertSame(response, result.getResponse());
+    }
+
+    @Test
+    public void testDownloadOfUTF8Content() throws Exception
+    {
+        final byte[] data = new byte[]{(byte)0xC3, (byte)0xA8}; // UTF-8 representation of è
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                response.getOutputStream().write(data);
+            }
+        });
+
+        InputStreamResponseListener listener = new InputStreamResponseListener();
+        client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .send(listener);
+        Response response = listener.get(5, TimeUnit.SECONDS);
+        Assert.assertNotNull(response);
+        Assert.assertEquals(200, response.getStatus());
+
+        InputStream input = listener.getInputStream();
+        Assert.assertNotNull(input);
+
+        for (byte b : data)
+        {
+            int read = input.read();
+            assertTrue(read >= 0);
+            Assert.assertEquals(b & 0xFF, read);
+        }
+
+        Assert.assertEquals(-1, input.read());
+
+        Result result = listener.await(5, TimeUnit.SECONDS);
+        Assert.assertNotNull(result);
+        Assert.assertFalse(result.isFailed());
+        Assert.assertSame(response, result.getResponse());
+    }
+
+    @Test
+    public void testDownloadWithFailure() throws Exception
+    {
+        final byte[] data = new byte[64 * 1024];
+        byte value = 1;
+        Arrays.fill(data, value);
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                // Say we want to send this much...
+                response.setContentLength(2 * data.length);
+                // ...but write only half...
+                response.getOutputStream().write(data);
+                // ...then shutdown output
+                baseRequest.getHttpChannel().getEndPoint().shutdownOutput();
+            }
+        });
+
+        InputStreamResponseListener listener = new InputStreamResponseListener();
+        client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .send(listener);
+        Response response = listener.get(5, TimeUnit.SECONDS);
+        Assert.assertNotNull(response);
+        Assert.assertEquals(200, response.getStatus());
+
+        InputStream input = listener.getInputStream();
+        Assert.assertNotNull(input);
+
+        int length = 0;
+        try
+        {
+            length = 0;
+            while (input.read() == value)
+            {
+                if (length % 100 == 0)
+                    Thread.sleep(1);
+                ++length;
+            }
+            fail();
+        }
+        catch (IOException expected)
+        {
+        }
+
+        Assert.assertEquals(data.length, length);
+
+        Result result = listener.await(5, TimeUnit.SECONDS);
+        Assert.assertNotNull(result);
+        Assert.assertTrue(result.isFailed());
+    }
+
+    @Test(expected = AsynchronousCloseException.class)
+    public void testInputStreamResponseListenerClosedBeforeReading() throws Exception
+    {
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                IO.copy(request.getInputStream(), response.getOutputStream());
+            }
+        });
+
+        InputStreamResponseListener listener = new InputStreamResponseListener();
+        InputStream stream = listener.getInputStream();
+        // Close the stream immediately
+        stream.close();
+
+        client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .content(new BytesContentProvider(new byte[]{0, 1, 2, 3}))
+                .send(listener);
+        Response response = listener.get(5, TimeUnit.SECONDS);
+        Assert.assertEquals(200, response.getStatus());
+
+        stream.read(); // Throws
+    }
+
+    @Test
+    public void testInputStreamResponseListenerClosedWhileWaiting() throws Exception
+    {
+        final byte[] chunk1 = new byte[]{0, 1};
+        final byte[] chunk2 = new byte[]{2, 3};
+        final CountDownLatch closeLatch = new CountDownLatch(1);
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                response.setContentLength(chunk1.length + chunk2.length);
+                ServletOutputStream output = response.getOutputStream();
+                output.write(chunk1);
+                output.flush();
+                try
+                {
+                    closeLatch.await(5, TimeUnit.SECONDS);
+                    output.write(chunk2);
+                    output.flush();
+                }
+                catch (InterruptedException x)
+                {
+                    throw new InterruptedIOException();
+                }
+            }
+        });
+
+        final CountDownLatch waitLatch = new CountDownLatch(1);
+        final CountDownLatch waitedLatch = new CountDownLatch(1);
+        InputStreamResponseListener listener = new InputStreamResponseListener(1)
+        {
+            @Override
+            protected boolean await()
+            {
+                waitLatch.countDown();
+                boolean result = super.await();
+                waitedLatch.countDown();
+                return result;
+            }
+        };
+        client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .send(listener);
+        Response response = listener.get(5, TimeUnit.SECONDS);
+        Assert.assertEquals(200, response.getStatus());
+
+        InputStream stream = listener.getInputStream();
+        // Wait until we block
+        Assert.assertTrue(waitLatch.await(5, TimeUnit.SECONDS));
+        // Close the stream
+        stream.close();
+        closeLatch.countDown();
+
+        // Be sure we're not stuck waiting
+        Assert.assertTrue(waitedLatch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testInputStreamResponseListenerFailedWhileWaiting() throws Exception
+    {
+        final byte[] chunk1 = new byte[]{0, 1};
+        final byte[] chunk2 = new byte[]{2, 3};
+        final CountDownLatch closeLatch = new CountDownLatch(1);
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                response.setContentLength(chunk1.length + chunk2.length);
+                ServletOutputStream output = response.getOutputStream();
+                output.write(chunk1);
+                output.flush();
+                try
+                {
+                    closeLatch.await(5, TimeUnit.SECONDS);
+                    output.write(chunk2);
+                    output.flush();
+                }
+                catch (InterruptedException x)
+                {
+                    throw new InterruptedIOException();
+                }
+            }
+        });
+
+        final CountDownLatch waitLatch = new CountDownLatch(1);
+        final CountDownLatch waitedLatch = new CountDownLatch(1);
+        InputStreamResponseListener listener = new InputStreamResponseListener(1)
+        {
+            @Override
+            protected boolean await()
+            {
+                waitLatch.countDown();
+                boolean result = super.await();
+                waitedLatch.countDown();
+                return result;
+            }
+        };
+        client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .send(listener);
+        Response response = listener.get(5, TimeUnit.SECONDS);
+        Assert.assertEquals(200, response.getStatus());
+
+        // Wait until we block
+        Assert.assertTrue(waitLatch.await(5, TimeUnit.SECONDS));
+        // Fail the response
+        response.abort(new Exception());
+        closeLatch.countDown();
+
+        // Be sure we're not stuck waiting
+        Assert.assertTrue(waitedLatch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testInputStreamResponseListenerConsumingBeforeWaiting() throws Exception
+    {
+        final byte[] data = new byte[]{0, 1};
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                response.setContentLength(data.length);
+                ServletOutputStream output = response.getOutputStream();
+                output.write(data);
+                output.flush();
+            }
+        });
+
+        final AtomicReference<Throwable> failure = new AtomicReference<>();
+        InputStreamResponseListener listener = new InputStreamResponseListener(1)
+        {
+            @Override
+            protected boolean await()
+            {
+                // Consume everything just before waiting
+                InputStream stream = getInputStream();
+                consume(stream, data);
+                return super.await();
+            }
+
+            private void consume(InputStream stream, byte[] data)
+            {
+                try
+                {
+                    for (byte datum : data)
+                        Assert.assertEquals(datum, stream.read());
+                }
+                catch (IOException x)
+                {
+                    failure.compareAndSet(null, x);
+                }
+            }
+        };
+        client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .send(listener);
+        Result result = listener.await(5, TimeUnit.SECONDS);
+        Assert.assertEquals(200, result.getResponse().getStatus());
+        Assert.assertNull(failure.get());
+    }
+
+    @Test
+    public void testInputStreamResponseListenerFailedBeforeResponse() throws Exception
+    {
+        start(new EmptyServerHandler());
+        int port = connector.getLocalPort();
+        server.stop();
+
+        InputStreamResponseListener listener = new InputStreamResponseListener();
+        // Connect to the wrong port
+        client.newRequest("localhost", port)
+                .scheme(scheme)
+                .send(listener);
+        Result result = listener.await(5, TimeUnit.SECONDS);
+        Assert.assertNotNull(result);
+    }
+
+    @Test(expected = ExecutionException.class)
+    public void testInputStreamContentProviderThrowingWhileReading() throws Exception
+    {
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                IO.copy(request.getInputStream(), response.getOutputStream());
+            }
+        });
+
+        final byte[] data = new byte[]{0, 1, 2, 3};
+        client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .content(new InputStreamContentProvider(new InputStream()
+                {
+                    private int index = 0;
+
+                    @Override
+                    public int read() throws IOException
+                    {
+                        // Will eventually throw ArrayIndexOutOfBounds
+                        return data[index++];
+                    }
+                }, data.length / 2))
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+    }
+
+    @Test(expected = AsynchronousCloseException.class)
+    public void testDownloadWithCloseBeforeContent() throws Exception
+    {
+        final byte[] data = new byte[128 * 1024];
+        byte value = 3;
+        Arrays.fill(data, value);
+        final CountDownLatch latch = new CountDownLatch(1);
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                response.flushBuffer();
+
+                try
+                {
+                    Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+                }
+                catch (InterruptedException e)
+                {
+                    throw new InterruptedIOException();
+                }
+
+                response.getOutputStream().write(data);
+            }
+        });
+
+        InputStreamResponseListener listener = new InputStreamResponseListener();
+        client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .send(listener);
+        Response response = listener.get(5, TimeUnit.SECONDS);
+        Assert.assertNotNull(response);
+        Assert.assertEquals(200, response.getStatus());
+
+        InputStream input = listener.getInputStream();
+        Assert.assertNotNull(input);
+        input.close();
+
+        latch.countDown();
+
+        input.read(); // Throws
+    }
+
+    @Test(expected = AsynchronousCloseException.class)
+    public void testDownloadWithCloseMiddleOfContent() throws Exception
+    {
+        final byte[] data1 = new byte[1024];
+        final byte[] data2 = new byte[1024];
+        final CountDownLatch latch = new CountDownLatch(1);
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                response.getOutputStream().write(data1);
+                response.flushBuffer();
+
+                try
+                {
+                    Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+                }
+                catch (InterruptedException e)
+                {
+                    throw new InterruptedIOException();
+                }
+
+                response.getOutputStream().write(data2);
+            }
+        });
+
+        InputStreamResponseListener listener = new InputStreamResponseListener();
+        client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .send(listener);
+        Response response = listener.get(5, TimeUnit.SECONDS);
+        Assert.assertNotNull(response);
+        Assert.assertEquals(200, response.getStatus());
+
+        InputStream input = listener.getInputStream();
+        Assert.assertNotNull(input);
+
+        for (byte datum1 : data1)
+            Assert.assertEquals(datum1, input.read());
+
+        input.close();
+
+        latch.countDown();
+
+        input.read(); // Throws
+    }
+
+    @Test
+    public void testDownloadWithCloseEndOfContent() throws Exception
+    {
+        final byte[] data = new byte[1024];
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                response.getOutputStream().write(data);
+                response.flushBuffer();
+            }
+        });
+
+        InputStreamResponseListener listener = new InputStreamResponseListener();
+        client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .send(listener);
+        Response response = listener.get(5, TimeUnit.SECONDS);
+        Assert.assertNotNull(response);
+        Assert.assertEquals(200, response.getStatus());
+
+        InputStream input = listener.getInputStream();
+        Assert.assertNotNull(input);
+
+        for (byte datum : data)
+            Assert.assertEquals(datum, input.read());
+
+        // Read EOF
+        Assert.assertEquals(-1, input.read());
+
+        input.close();
+
+        // Must not throw
+        Assert.assertEquals(-1, input.read());
+    }
+
+    @Slow
+    @Test
+    public void testUploadWithDeferredContentProviderFromInputStream() throws Exception
+    {
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                IO.copy(request.getInputStream(), new ByteArrayOutputStream());
+            }
+        });
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        try (DeferredContentProvider content = new DeferredContentProvider())
+        {
+            client.newRequest("localhost", connector.getLocalPort())
+                    .scheme(scheme)
+                    .content(content)
+                    .send(new Response.CompleteListener()
+                    {
+                        @Override
+                        public void onComplete(Result result)
+                        {
+                            if (result.isSucceeded() && result.getResponse().getStatus() == 200)
+                                latch.countDown();
+                        }
+                    });
+
+            // Make sure we provide the content *after* the request has been "sent".
+            Thread.sleep(1000);
+
+            try (ByteArrayInputStream input = new ByteArrayInputStream(new byte[1024]))
+            {
+                byte[] buffer = new byte[200];
+                int read;
+                while ((read = input.read(buffer)) >= 0)
+                    content.offer(ByteBuffer.wrap(buffer, 0, read));
+            }
+        }
+        Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testUploadWithDeferredContentAvailableCallbacksNotifiedOnce() throws Exception
+    {
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                IO.copy(request.getInputStream(), new ByteArrayOutputStream());
+            }
+        });
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        final AtomicInteger succeeds = new AtomicInteger();
+        try (DeferredContentProvider content = new DeferredContentProvider())
+        {
+            // Make the content immediately available.
+            content.offer(ByteBuffer.allocate(1024), new Callback.Adapter()
+            {
+                @Override
+                public void succeeded()
+                {
+                    succeeds.incrementAndGet();
+                }
+            });
+
+            client.newRequest("localhost", connector.getLocalPort())
+                    .scheme(scheme)
+                    .content(content)
+                    .send(new Response.CompleteListener()
+                    {
+                        @Override
+                        public void onComplete(Result result)
+                        {
+                            if (result.isSucceeded() && result.getResponse().getStatus() == 200)
+                                latch.countDown();
+                        }
+                    });
+        }
+        Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+        Assert.assertEquals(1, succeeds.get());
+    }
+
+    @Test
+    public void testUploadWithDeferredContentProviderRacingWithSend() throws Exception
+    {
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                IO.copy(request.getInputStream(), response.getOutputStream());
+            }
+        });
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        final byte[] data = new byte[512];
+        final DeferredContentProvider content = new DeferredContentProvider()
+        {
+            @Override
+            public void setListener(Listener listener)
+            {
+                super.setListener(listener);
+                // Simulate a concurrent call
+                offer(ByteBuffer.wrap(data));
+                close();
+            }
+        };
+
+        client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .content(content)
+                .send(new BufferingResponseListener()
+                {
+                    @Override
+                    public void onComplete(Result result)
+                    {
+                        if (result.isSucceeded() &&
+                                result.getResponse().getStatus() == 200 &&
+                                Arrays.equals(data, getContent()))
+                            latch.countDown();
+                    }
+                });
+
+        Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testUploadWithDeferredContentProviderRacingWithIterator() throws Exception
+    {
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                IO.copy(request.getInputStream(), response.getOutputStream());
+            }
+        });
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        final byte[] data = new byte[512];
+        final AtomicReference<DeferredContentProvider> contentRef = new AtomicReference<>();
+        final DeferredContentProvider content = new DeferredContentProvider()
+        {
+            @Override
+            public Iterator<ByteBuffer> iterator()
+            {
+                return new Iterator<ByteBuffer>()
+                {
+                    // Data for the deferred content iterator:
+                    // [0] => deferred
+                    // [1] => deferred
+                    // [2] => data
+                    private final byte[][] iteratorData = new byte[3][];
+                    private final AtomicInteger index = new AtomicInteger();
+
+                    {
+                        iteratorData[0] = null;
+                        iteratorData[1] = null;
+                        iteratorData[2] = data;
+                    }
+
+                    @Override
+                    public boolean hasNext()
+                    {
+                        return index.get() < iteratorData.length;
+                    }
+
+                    @Override
+                    public ByteBuffer next()
+                    {
+                        byte[] chunk = iteratorData[index.getAndIncrement()];
+                        ByteBuffer result = chunk == null ? null : ByteBuffer.wrap(chunk);
+                        if (index.get() == 2)
+                        {
+                            contentRef.get().offer(result == null ? BufferUtil.EMPTY_BUFFER : result);
+                            contentRef.get().close();
+                        }
+                        return result;
+                    }
+
+                    @Override
+                    public void remove()
+                    {
+                    }
+                };
+            }
+        };
+        contentRef.set(content);
+
+        client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .content(content)
+                .send(new BufferingResponseListener()
+                {
+                    @Override
+                    public void onComplete(Result result)
+                    {
+                        if (result.isSucceeded() &&
+                                result.getResponse().getStatus() == 200 &&
+                                Arrays.equals(data, getContent()))
+                            latch.countDown();
+                    }
+                });
+
+        Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testUploadWithOutputStream() throws Exception
+    {
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                IO.copy(request.getInputStream(), response.getOutputStream());
+            }
+        });
+
+        final byte[] data = new byte[512];
+        final CountDownLatch latch = new CountDownLatch(1);
+        OutputStreamContentProvider content = new OutputStreamContentProvider();
+        client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .content(content)
+                .send(new BufferingResponseListener()
+                {
+                    @Override
+                    public void onComplete(Result result)
+                    {
+                        if (result.isSucceeded() &&
+                                result.getResponse().getStatus() == 200 &&
+                                Arrays.equals(data, getContent()))
+                            latch.countDown();
+                    }
+                });
+
+        // Make sure we provide the content *after* the request has been "sent".
+        Thread.sleep(1000);
+
+        try (OutputStream output = content.getOutputStream())
+        {
+            output.write(data);
+        }
+
+        Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testBigUploadWithOutputStreamFromInputStream() throws Exception
+    {
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                IO.copy(request.getInputStream(), response.getOutputStream());
+            }
+        });
+
+        final byte[] data = new byte[16 * 1024 * 1024];
+        new Random().nextBytes(data);
+        final CountDownLatch latch = new CountDownLatch(1);
+        OutputStreamContentProvider content = new OutputStreamContentProvider();
+        client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .content(content)
+                .send(new BufferingResponseListener(data.length)
+                {
+                    @Override
+                    public void onComplete(Result result)
+                    {
+                        Assert.assertTrue(result.isSucceeded());
+                        Assert.assertEquals(200, result.getResponse().getStatus());
+                        Assert.assertArrayEquals(data, getContent());
+                        latch.countDown();
+                    }
+                });
+
+        // Make sure we provide the content *after* the request has been "sent".
+        Thread.sleep(1000);
+
+        try (InputStream input = new ByteArrayInputStream(data); OutputStream output = content.getOutputStream())
+        {
+            byte[] buffer = new byte[1024];
+            while (true)
+            {
+                int read = input.read(buffer);
+                if (read < 0)
+                    break;
+                output.write(buffer, 0, read);
+            }
+        }
+
+        Assert.assertTrue(latch.await(30, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testUploadWithOutputStreamFailureToConnect() throws Exception
+    {
+        start(new EmptyServerHandler());
+
+        final byte[] data = new byte[512];
+        final CountDownLatch latch = new CountDownLatch(1);
+        OutputStreamContentProvider content = new OutputStreamContentProvider();
+        client.newRequest("0.0.0.1", connector.getLocalPort())
+                .scheme(scheme)
+                .content(content)
+                .send(new Response.CompleteListener()
+                {
+                    @Override
+                    public void onComplete(Result result)
+                    {
+                        if (result.isFailed())
+                            latch.countDown();
+                    }
+                });
+
+        try (OutputStream output = content.getOutputStream())
+        {
+            output.write(data);
+            Assert.fail();
+        }
+        catch (IOException x)
+        {
+            // Expected
+        }
+
+        Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testUploadWithDeferredContentProviderFailsMultipleOffers() throws Exception
+    {
+        start(new EmptyServerHandler());
+
+        final CountDownLatch failLatch = new CountDownLatch(2);
+        final Callback.Adapter callback = new Callback.Adapter()
+        {
+            @Override
+            public void failed(Throwable x)
+            {
+                failLatch.countDown();
+            }
+        };
+
+        final CountDownLatch completeLatch = new CountDownLatch(1);
+        final DeferredContentProvider content = new DeferredContentProvider();
+        client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .content(content)
+                .onRequestBegin(new Request.BeginListener()
+                {
+                    @Override
+                    public void onBegin(Request request)
+                    {
+                        content.offer(ByteBuffer.wrap(new byte[256]), callback);
+                        content.offer(ByteBuffer.wrap(new byte[256]), callback);
+                        request.abort(new Exception("explicitly_thrown_by_test"));
+                    }
+                })
+                .send(new Response.CompleteListener()
+                {
+                    @Override
+                    public void onComplete(Result result)
+                    {
+                        if (result.isFailed())
+                            completeLatch.countDown();
+                    }
+                });
+        Assert.assertTrue(completeLatch.await(5, TimeUnit.SECONDS));
+        Assert.assertTrue(failLatch.await(5, TimeUnit.SECONDS));
+
+        // Make sure that adding more content results in the callback to be failed.
+        final CountDownLatch latch = new CountDownLatch(1);
+        content.offer(ByteBuffer.wrap(new byte[128]), new Callback.Adapter()
+        {
+            @Override
+            public void failed(Throwable x)
+            {
+                latch.countDown();
+            }
+        });
+        Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testUploadWithConnectFailureClosesStream() throws Exception
+    {
+        start(new EmptyServerHandler());
+
+        final CountDownLatch closeLatch = new CountDownLatch(1);
+        InputStream stream = new ByteArrayInputStream("test".getBytes(StandardCharsets.UTF_8))
+        {
+            @Override
+            public void close() throws IOException
+            {
+                super.close();
+                closeLatch.countDown();
+            }
+        };
+        InputStreamContentProvider content = new InputStreamContentProvider(stream);
+
+        final CountDownLatch completeLatch = new CountDownLatch(1);
+        client.newRequest("0.0.0.1", connector.getLocalPort())
+                .scheme(scheme)
+                .content(content)
+                .send(new Response.CompleteListener()
+                {
+                    @Override
+                    public void onComplete(Result result)
+                    {
+                        Assert.assertTrue(result.isFailed());
+                        completeLatch.countDown();
+                    }
+                });
+
+        Assert.assertTrue(completeLatch.await(5, TimeUnit.SECONDS));
+        Assert.assertTrue(closeLatch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testUploadWithWriteFailureClosesStream() throws Exception
+    {
+        start(new EmptyServerHandler());
+
+        final AtomicInteger bytes = new AtomicInteger();
+        final CountDownLatch closeLatch = new CountDownLatch(1);
+        InputStream stream = new InputStream()
+        {
+            @Override
+            public int read() throws IOException
+            {
+                int result = bytes.incrementAndGet();
+                switch (result)
+                {
+                    case 1:
+                    {
+                        break;
+                    }
+                    case 2:
+                    {
+                        try
+                        {
+                            connector.stop();
+                        }
+                        catch (Exception x)
+                        {
+                            throw new IOException(x);
+                        }
+                        break;
+                    }
+                    default:
+                    {
+                        result = -1;
+                        break;
+                    }
+                }
+                return result;
+            }
+
+            @Override
+            public void close() throws IOException
+            {
+                super.close();
+                closeLatch.countDown();
+            }
+        };
+        InputStreamContentProvider provider = new InputStreamContentProvider(stream, 1);
+
+        final CountDownLatch completeLatch = new CountDownLatch(1);
+        client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .content(provider)
+                .send(new Response.CompleteListener()
+                {
+                    @Override
+                    public void onComplete(Result result)
+                    {
+                        Assert.assertTrue(result.isFailed());
+                        completeLatch.countDown();
+                    }
+                });
+
+        Assert.assertTrue(completeLatch.await(5, TimeUnit.SECONDS));
+        Assert.assertTrue(closeLatch.await(5, TimeUnit.SECONDS));
+    }
+}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientSynchronizationTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientSynchronizationTest.java
new file mode 100644
index 0000000..e7e1661
--- /dev/null
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientSynchronizationTest.java
@@ -0,0 +1,108 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.net.ConnectException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.client.api.Result;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.hamcrest.Matchers;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Verifies that synchronization performed from outside HttpClient does not cause deadlocks
+ */
+public class HttpClientSynchronizationTest extends AbstractHttpClientServerTest
+{
+    public HttpClientSynchronizationTest(SslContextFactory sslContextFactory)
+    {
+        super(sslContextFactory);
+    }
+
+    @Test
+    public void testSynchronizationOnException() throws Exception
+    {
+        start(new EmptyServerHandler());
+        int port = connector.getLocalPort();
+        server.stop();
+
+        int count = 10;
+        final CountDownLatch latch = new CountDownLatch(count);
+        for (int i = 0; i < count; ++i)
+        {
+            Request request = client.newRequest("localhost", port)
+                    .scheme(scheme);
+
+            synchronized (this)
+            {
+                request.send(new Response.Listener.Adapter()
+                {
+                    @Override
+                    public void onFailure(Response response, Throwable failure)
+                    {
+                        synchronized (HttpClientSynchronizationTest.this)
+                        {
+                            Assert.assertThat(failure, Matchers.instanceOf(ConnectException.class));
+                            latch.countDown();
+                        }
+                    }
+                });
+            }
+        }
+
+        Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testSynchronizationOnComplete() throws Exception
+    {
+        start(new EmptyServerHandler());
+
+        int count = 10;
+        final CountDownLatch latch = new CountDownLatch(count);
+        for (int i = 0; i < count; ++i)
+        {
+            Request request = client.newRequest("localhost", connector.getLocalPort())
+                    .scheme(scheme);
+
+            synchronized (this)
+            {
+                request.send(new Response.Listener.Adapter()
+                {
+                    @Override
+                    public void onComplete(Result result)
+                    {
+                        synchronized (HttpClientSynchronizationTest.this)
+                        {
+                            Assert.assertFalse(result.isFailed());
+                            latch.countDown();
+                        }
+                    }
+                });
+            }
+        }
+
+        Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+    }
+}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java
new file mode 100644
index 0000000..800c737
--- /dev/null
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java
@@ -0,0 +1,1487 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.HttpCookie;
+import java.net.URI;
+import java.net.URLEncoder;
+import java.nio.ByteBuffer;
+import java.nio.channels.UnresolvedAddressException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Random;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Exchanger;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicReference;
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.client.api.Connection;
+import org.eclipse.jetty.client.api.ContentProvider;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Destination;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.client.api.Result;
+import org.eclipse.jetty.client.http.HttpConnectionOverHTTP;
+import org.eclipse.jetty.client.http.HttpDestinationOverHTTP;
+import org.eclipse.jetty.client.util.BufferingResponseListener;
+import org.eclipse.jetty.client.util.BytesContentProvider;
+import org.eclipse.jetty.client.util.DeferredContentProvider;
+import org.eclipse.jetty.client.util.FutureResponseListener;
+import org.eclipse.jetty.client.util.StringContentProvider;
+import org.eclipse.jetty.http.HttpField;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpHeaderValue;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.toolchain.test.TestingDir;
+import org.eclipse.jetty.toolchain.test.annotation.Slow;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.FuturePromise;
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.junit.Assert;
+import org.junit.Assume;
+import org.junit.Rule;
+import org.junit.Test;
+
+import static java.nio.file.StandardOpenOption.CREATE;
+import static org.junit.Assert.assertTrue;
+
+public class HttpClientTest extends AbstractHttpClientServerTest
+{
+    @Rule
+    public TestingDir testdir = new TestingDir();
+
+    public HttpClientTest(SslContextFactory sslContextFactory)
+    {
+        super(sslContextFactory);
+    }
+
+    @Test
+    public void testStoppingClosesConnections() throws Exception
+    {
+        start(new EmptyServerHandler());
+
+        String host = "localhost";
+        int port = connector.getLocalPort();
+        String path = "/";
+        Response response = client.GET(scheme + "://" + host + ":" + port + path);
+        Assert.assertEquals(200, response.getStatus());
+
+        HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
+        ConnectionPool connectionPool = destination.getConnectionPool();
+
+        long start = System.nanoTime();
+        HttpConnectionOverHTTP connection = null;
+        while (connection == null && TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - start) < 5)
+        {
+            connection = (HttpConnectionOverHTTP)connectionPool.getIdleConnections().peek();
+            TimeUnit.MILLISECONDS.sleep(10);
+        }
+        Assert.assertNotNull(connection);
+
+        String uri = destination.getScheme() + "://" + destination.getHost() + ":" + destination.getPort();
+        client.getCookieStore().add(URI.create(uri), new HttpCookie("foo", "bar"));
+
+        client.stop();
+
+        Assert.assertEquals(0, client.getDestinations().size());
+        Assert.assertEquals(0, connectionPool.getIdleConnections().size());
+        Assert.assertEquals(0, connectionPool.getActiveConnections().size());
+        Assert.assertFalse(connection.getEndPoint().isOpen());
+    }
+
+    @Test
+    public void test_DestinationCount() throws Exception
+    {
+        start(new EmptyServerHandler());
+
+        String host = "localhost";
+        int port = connector.getLocalPort();
+        client.GET(scheme + "://" + host + ":" + port);
+
+        List<Destination> destinations = client.getDestinations();
+        Assert.assertNotNull(destinations);
+        Assert.assertEquals(1, destinations.size());
+        Destination destination = destinations.get(0);
+        Assert.assertNotNull(destination);
+        Assert.assertEquals(scheme, destination.getScheme());
+        Assert.assertEquals(host, destination.getHost());
+        Assert.assertEquals(port, destination.getPort());
+    }
+
+    @Test
+    public void test_GET_ResponseWithoutContent() throws Exception
+    {
+        start(new EmptyServerHandler());
+
+        Response response = client.GET(scheme + "://localhost:" + connector.getLocalPort());
+
+        Assert.assertNotNull(response);
+        Assert.assertEquals(200, response.getStatus());
+    }
+
+    @Test
+    public void test_GET_ResponseWithContent() throws Exception
+    {
+        final byte[] data = new byte[]{0, 1, 2, 3, 4, 5, 6, 7};
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                response.getOutputStream().write(data);
+                baseRequest.setHandled(true);
+            }
+        });
+
+        ContentResponse response = client.GET(scheme + "://localhost:" + connector.getLocalPort());
+
+        Assert.assertNotNull(response);
+        Assert.assertEquals(200, response.getStatus());
+        byte[] content = response.getContent();
+        Assert.assertArrayEquals(data, content);
+    }
+
+    @Test
+    public void test_GET_WithParameters_ResponseWithContent() throws Exception
+    {
+        final String paramName1 = "a";
+        final String paramName2 = "b";
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                response.setCharacterEncoding("UTF-8");
+                ServletOutputStream output = response.getOutputStream();
+                String paramValue1 = request.getParameter(paramName1);
+                output.write(paramValue1.getBytes(StandardCharsets.UTF_8));
+                String paramValue2 = request.getParameter(paramName2);
+                Assert.assertEquals("", paramValue2);
+                output.write("empty".getBytes(StandardCharsets.UTF_8));
+                baseRequest.setHandled(true);
+            }
+        });
+
+        String value1 = "\u20AC";
+        String paramValue1 = URLEncoder.encode(value1, "UTF-8");
+        String query = paramName1 + "=" + paramValue1 + "&" + paramName2;
+        ContentResponse response = client.GET(scheme + "://localhost:" + connector.getLocalPort() + "/?" + query);
+
+        Assert.assertNotNull(response);
+        Assert.assertEquals(200, response.getStatus());
+        String content = new String(response.getContent(), StandardCharsets.UTF_8);
+        Assert.assertEquals(value1 + "empty", content);
+    }
+
+    @Test
+    public void test_GET_WithParametersMultiValued_ResponseWithContent() throws Exception
+    {
+        final String paramName1 = "a";
+        final String paramName2 = "b";
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                response.setCharacterEncoding("UTF-8");
+                ServletOutputStream output = response.getOutputStream();
+                String[] paramValues1 = request.getParameterValues(paramName1);
+                for (String paramValue : paramValues1)
+                    output.write(paramValue.getBytes(StandardCharsets.UTF_8));
+                String paramValue2 = request.getParameter(paramName2);
+                output.write(paramValue2.getBytes(StandardCharsets.UTF_8));
+                baseRequest.setHandled(true);
+            }
+        });
+
+        String value11 = "\u20AC";
+        String value12 = "\u20AA";
+        String value2 = "&";
+        String paramValue11 = URLEncoder.encode(value11, "UTF-8");
+        String paramValue12 = URLEncoder.encode(value12, "UTF-8");
+        String paramValue2 = URLEncoder.encode(value2, "UTF-8");
+        String query = paramName1 + "=" + paramValue11 + "&" + paramName1 + "=" + paramValue12 + "&" + paramName2 + "=" + paramValue2;
+        ContentResponse response = client.GET(scheme + "://localhost:" + connector.getLocalPort() + "/?" + query);
+
+        Assert.assertNotNull(response);
+        Assert.assertEquals(200, response.getStatus());
+        String content = new String(response.getContent(), StandardCharsets.UTF_8);
+        Assert.assertEquals(value11 + value12 + value2, content);
+    }
+
+    @Test
+    public void test_POST_WithParameters() throws Exception
+    {
+        final String paramName = "a";
+        final String paramValue = "\u20AC";
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                String value = request.getParameter(paramName);
+                if (paramValue.equals(value))
+                {
+                    response.setCharacterEncoding("UTF-8");
+                    response.setContentType("text/plain");
+                    response.getOutputStream().print(value);
+                }
+            }
+        });
+
+        ContentResponse response = client.POST(scheme + "://localhost:" + connector.getLocalPort())
+                .param(paramName, paramValue)
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        Assert.assertNotNull(response);
+        Assert.assertEquals(200, response.getStatus());
+        Assert.assertEquals(paramValue, new String(response.getContent(), StandardCharsets.UTF_8));
+    }
+
+    @Test
+    public void test_PUT_WithParameters() throws Exception
+    {
+        final String paramName = "a";
+        final String paramValue = "\u20AC";
+        final String encodedParamValue = URLEncoder.encode(paramValue, "UTF-8");
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                String value = request.getParameter(paramName);
+                if (paramValue.equals(value))
+                {
+                    response.setCharacterEncoding("UTF-8");
+                    response.setContentType("text/plain");
+                    response.getOutputStream().print(value);
+                }
+            }
+        });
+
+        URI uri = URI.create(scheme + "://localhost:" + connector.getLocalPort() + "/path?" + paramName + "=" + encodedParamValue);
+        ContentResponse response = client.newRequest(uri)
+                .method(HttpMethod.PUT)
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        Assert.assertNotNull(response);
+        Assert.assertEquals(200, response.getStatus());
+        Assert.assertEquals(paramValue, new String(response.getContent(), StandardCharsets.UTF_8));
+    }
+
+    @Test
+    public void test_POST_WithParameters_WithContent() throws Exception
+    {
+        final byte[] content = {0, 1, 2, 3};
+        final String paramName = "a";
+        final String paramValue = "\u20AC";
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                consume(request.getInputStream());
+                String value = request.getParameter(paramName);
+                if (paramValue.equals(value))
+                {
+                    response.setCharacterEncoding("UTF-8");
+                    response.setContentType("text/plain");
+                    response.getOutputStream().write(content);
+                }
+            }
+        });
+
+        ContentResponse response = client.POST(scheme + "://localhost:" + connector.getLocalPort() + "/?b=1")
+                .param(paramName, paramValue)
+                .content(new BytesContentProvider(content))
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        Assert.assertNotNull(response);
+        Assert.assertEquals(200, response.getStatus());
+        Assert.assertArrayEquals(content, response.getContent());
+    }
+
+    @Test
+    public void test_POST_WithContent_NotifiesRequestContentListener() throws Exception
+    {
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                consume(request.getInputStream());
+            }
+        });
+
+        final byte[] content = {0, 1, 2, 3};
+        ContentResponse response = client.POST(scheme + "://localhost:" + connector.getLocalPort())
+                .onRequestContent(new Request.ContentListener()
+                {
+                    @Override
+                    public void onContent(Request request, ByteBuffer buffer)
+                    {
+                        byte[] bytes = new byte[buffer.remaining()];
+                        buffer.get(bytes);
+                        if (!Arrays.equals(content, bytes))
+                            request.abort(new Exception());
+                    }
+                })
+                .content(new BytesContentProvider(content))
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        Assert.assertNotNull(response);
+        Assert.assertEquals(200, response.getStatus());
+    }
+
+    @Test
+    public void test_POST_WithContent_TracksProgress() throws Exception
+    {
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                consume(request.getInputStream());
+            }
+        });
+
+        final AtomicInteger progress = new AtomicInteger();
+        ContentResponse response = client.POST(scheme + "://localhost:" + connector.getLocalPort())
+                .onRequestContent(new Request.ContentListener()
+                {
+                    @Override
+                    public void onContent(Request request, ByteBuffer buffer)
+                    {
+                        byte[] bytes = new byte[buffer.remaining()];
+                        Assert.assertEquals(1, bytes.length);
+                        buffer.get(bytes);
+                        Assert.assertEquals(bytes[0], progress.getAndIncrement());
+                    }
+                })
+                .content(new BytesContentProvider(new byte[]{0}, new byte[]{1}, new byte[]{2}, new byte[]{3}, new byte[]{4}))
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        Assert.assertNotNull(response);
+        Assert.assertEquals(200, response.getStatus());
+        Assert.assertEquals(5, progress.get());
+    }
+
+    @Test
+    public void test_QueuedRequest_IsSent_WhenPreviousRequestSucceeded() throws Exception
+    {
+        start(new EmptyServerHandler());
+
+        client.setMaxConnectionsPerDestination(1);
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        final CountDownLatch successLatch = new CountDownLatch(2);
+        client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .onRequestBegin(new Request.BeginListener()
+                {
+                    @Override
+                    public void onBegin(Request request)
+                    {
+                        try
+                        {
+                            latch.await();
+                        }
+                        catch (InterruptedException x)
+                        {
+                            x.printStackTrace();
+                        }
+                    }
+                })
+                .send(new Response.Listener.Adapter()
+                {
+                    @Override
+                    public void onSuccess(Response response)
+                    {
+                        Assert.assertEquals(200, response.getStatus());
+                        successLatch.countDown();
+                    }
+                });
+
+        client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .onRequestQueued(new Request.QueuedListener()
+                {
+                    @Override
+                    public void onQueued(Request request)
+                    {
+                        latch.countDown();
+                    }
+                })
+                .send(new Response.Listener.Adapter()
+                {
+                    @Override
+                    public void onSuccess(Response response)
+                    {
+                        Assert.assertEquals(200, response.getStatus());
+                        successLatch.countDown();
+                    }
+                });
+
+        Assert.assertTrue(successLatch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void test_QueuedRequest_IsSent_WhenPreviousRequestClosedConnection() throws Exception
+    {
+        start(new EmptyServerHandler());
+
+        client.setMaxConnectionsPerDestination(1);
+        final long idleTimeout = 1000;
+        client.setIdleTimeout(idleTimeout);
+
+        final CountDownLatch latch = new CountDownLatch(3);
+        client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .path("/one")
+                .listener(new Request.Listener.Adapter()
+                {
+                    @Override
+                    public void onBegin(Request request)
+                    {
+                        try
+                        {
+                            TimeUnit.MILLISECONDS.sleep(2 * idleTimeout);
+                        }
+                        catch (InterruptedException x)
+                        {
+                            x.printStackTrace();
+                        }
+                    }
+
+                    @Override
+                    public void onFailure(Request request, Throwable failure)
+                    {
+                        latch.countDown();
+                    }
+                })
+                .onResponseFailure(new Response.FailureListener()
+                {
+                    @Override
+                    public void onFailure(Response response, Throwable failure)
+                    {
+                        latch.countDown();
+                    }
+                })
+                .send(null);
+
+        client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .path("/two")
+                .onResponseSuccess(new Response.SuccessListener()
+                {
+                    @Override
+                    public void onSuccess(Response response)
+                    {
+                        Assert.assertEquals(200, response.getStatus());
+                        latch.countDown();
+                    }
+                })
+                .send(null);
+
+        Assert.assertTrue(latch.await(5 * idleTimeout, TimeUnit.MILLISECONDS));
+    }
+
+    @Test
+    public void test_ExchangeIsComplete_OnlyWhenBothRequestAndResponseAreComplete() throws Exception
+    {
+        start(new EmptyServerHandler());
+
+        // Prepare a big file to upload
+        Path targetTestsDir = testdir.getEmptyDir().toPath();
+        Files.createDirectories(targetTestsDir);
+        Path file = Paths.get(targetTestsDir.toString(), "http_client_conversation.big");
+        try (OutputStream output = Files.newOutputStream(file, CREATE))
+        {
+            byte[] kb = new byte[1024];
+            for (int i = 0; i < 10 * 1024; ++i)
+                output.write(kb);
+        }
+
+        final CountDownLatch latch = new CountDownLatch(3);
+        final AtomicLong exchangeTime = new AtomicLong();
+        final AtomicLong requestTime = new AtomicLong();
+        final AtomicLong responseTime = new AtomicLong();
+        client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .file(file)
+                .onRequestSuccess(new Request.SuccessListener()
+                {
+                    @Override
+                    public void onSuccess(Request request)
+                    {
+                        requestTime.set(System.nanoTime());
+                        latch.countDown();
+                    }
+                })
+                .send(new Response.Listener.Adapter()
+                {
+                    @Override
+                    public void onSuccess(Response response)
+                    {
+                        responseTime.set(System.nanoTime());
+                        latch.countDown();
+                    }
+
+                    @Override
+                    public void onComplete(Result result)
+                    {
+                        exchangeTime.set(System.nanoTime());
+                        latch.countDown();
+                    }
+                });
+
+        Assert.assertTrue(latch.await(10, TimeUnit.SECONDS));
+
+        Assert.assertTrue(requestTime.get() <= exchangeTime.get());
+        Assert.assertTrue(responseTime.get() <= exchangeTime.get());
+
+        // Give some time to the server to consume the request content
+        // This is just to avoid exception traces in the test output
+        Thread.sleep(1000);
+
+        Files.delete(file);
+    }
+
+    @Test
+    public void test_ExchangeIsComplete_WhenRequestFailsMidway_WithResponse() throws Exception
+    {
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                // Echo back
+                IO.copy(request.getInputStream(), response.getOutputStream());
+            }
+        });
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                // The second ByteBuffer set to null will throw an exception
+                .content(new ContentProvider()
+                {
+                    @Override
+                    public long getLength()
+                    {
+                        return -1;
+                    }
+
+                    @Override
+                    public Iterator<ByteBuffer> iterator()
+                    {
+                        return new Iterator<ByteBuffer>()
+                        {
+                            @Override
+                            public boolean hasNext()
+                            {
+                                return true;
+                            }
+
+                            @Override
+                            public ByteBuffer next()
+                            {
+                                throw new NoSuchElementException("explicitly_thrown_by_test");
+                            }
+
+                            @Override
+                            public void remove()
+                            {
+                                throw new UnsupportedOperationException();
+                            }
+                        };
+                    }
+                })
+                .send(new Response.Listener.Adapter()
+                {
+                    @Override
+                    public void onComplete(Result result)
+                    {
+                        latch.countDown();
+                    }
+                });
+
+        Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void test_ExchangeIsComplete_WhenRequestFails_WithNoResponse() throws Exception
+    {
+        start(new EmptyServerHandler());
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        final String host = "localhost";
+        final int port = connector.getLocalPort();
+        client.newRequest(host, port)
+                .scheme(scheme)
+                .onRequestBegin(new Request.BeginListener()
+                {
+                    @Override
+                    public void onBegin(Request request)
+                    {
+                        HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
+                        destination.getConnectionPool().getActiveConnections().peek().close();
+                    }
+                })
+                .send(new Response.Listener.Adapter()
+                {
+                    @Override
+                    public void onComplete(Result result)
+                    {
+                        latch.countDown();
+                    }
+                });
+
+        Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Slow
+    @Test
+    public void test_Request_IdleTimeout() throws Exception
+    {
+        final long idleTimeout = 1000;
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                try
+                {
+                    baseRequest.setHandled(true);
+                    TimeUnit.MILLISECONDS.sleep(2 * idleTimeout);
+                }
+                catch (InterruptedException x)
+                {
+                    throw new ServletException(x);
+                }
+            }
+        });
+
+        final String host = "localhost";
+        final int port = connector.getLocalPort();
+        try
+        {
+            client.newRequest(host, port)
+                    .scheme(scheme)
+                    .idleTimeout(idleTimeout, TimeUnit.MILLISECONDS)
+                    .timeout(3 * idleTimeout, TimeUnit.MILLISECONDS)
+                    .send();
+            Assert.fail();
+        }
+        catch (ExecutionException expected)
+        {
+            Assert.assertTrue(expected.getCause() instanceof TimeoutException);
+        }
+
+        // Make another request without specifying the idle timeout, should not fail
+        ContentResponse response = client.newRequest(host, port)
+                .scheme(scheme)
+                .timeout(3 * idleTimeout, TimeUnit.MILLISECONDS)
+                .send();
+
+        Assert.assertNotNull(response);
+        Assert.assertEquals(200, response.getStatus());
+    }
+
+    @Test
+    public void testSendToIPv6Address() throws Exception
+    {
+        start(new EmptyServerHandler());
+
+        ContentResponse response = client.newRequest("[::1]", connector.getLocalPort())
+                .scheme(scheme)
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        Assert.assertNotNull(response);
+        Assert.assertEquals(200, response.getStatus());
+    }
+
+    @Test
+    public void testHeaderProcessing() throws Exception
+    {
+        final String headerName = "X-Header-Test";
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                response.setHeader(headerName, "X");
+            }
+        });
+
+        ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .onResponseHeader(new Response.HeaderListener()
+                {
+                    @Override
+                    public boolean onHeader(Response response, HttpField field)
+                    {
+                        return !field.getName().equals(headerName);
+                    }
+                })
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        Assert.assertNotNull(response);
+        Assert.assertEquals(200, response.getStatus());
+        Assert.assertFalse(response.getHeaders().containsKey(headerName));
+    }
+
+    @Test
+    public void testAllHeadersDiscarded() throws Exception
+    {
+        start(new EmptyServerHandler());
+
+        int count = 10;
+        final CountDownLatch latch = new CountDownLatch(count);
+        for (int i = 0; i < count; ++i)
+        {
+            client.newRequest("localhost", connector.getLocalPort())
+                    .scheme(scheme)
+                    .send(new Response.Listener.Adapter()
+                    {
+                        @Override
+                        public boolean onHeader(Response response, HttpField field)
+                        {
+                            return false;
+                        }
+
+                        @Override
+                        public void onComplete(Result result)
+                        {
+                            if (result.isSucceeded())
+                                latch.countDown();
+                        }
+                    });
+        }
+
+        assertTrue(latch.await(10, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void test_HEAD_With_ResponseContentLength() throws Exception
+    {
+        final int length = 1024;
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                response.getOutputStream().write(new byte[length]);
+            }
+        });
+
+        // HEAD requests receive a Content-Length header, but do not
+        // receive the content so they must handle this case properly
+        ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .method(HttpMethod.HEAD)
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        Assert.assertNotNull(response);
+        Assert.assertEquals(200, response.getStatus());
+        Assert.assertEquals(0, response.getContent().length);
+
+        // Perform a normal GET request to be sure the content is now read
+        response = client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        Assert.assertNotNull(response);
+        Assert.assertEquals(200, response.getStatus());
+        Assert.assertEquals(length, response.getContent().length);
+    }
+
+    @Test
+    public void testConnectThrowsUnresolvedAddressException() throws Exception
+    {
+        start(new EmptyServerHandler());
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        client.newRequest("idontexist", 80)
+                .send(new Response.CompleteListener()
+                {
+                    @Override
+                    public void onComplete(Result result)
+                    {
+                        Assert.assertTrue(result.isFailed());
+                        Assert.assertTrue(result.getFailure() instanceof UnresolvedAddressException);
+                        latch.countDown();
+                    }
+                });
+        Assert.assertTrue(latch.await(10, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testCustomUserAgent() throws Exception
+    {
+        final String userAgent = "Test/1.0";
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                ArrayList<String> userAgents = Collections.list(request.getHeaders("User-Agent"));
+                Assert.assertEquals(1, userAgents.size());
+                Assert.assertEquals(userAgent, userAgents.get(0));
+            }
+        });
+
+        ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .agent(userAgent)
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        Assert.assertEquals(200, response.getStatus());
+
+        response = client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .header(HttpHeader.USER_AGENT, null)
+                .header(HttpHeader.USER_AGENT, userAgent)
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        Assert.assertEquals(200, response.getStatus());
+    }
+
+    @Test
+    public void testUserAgentCanBeRemoved() throws Exception
+    {
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                ArrayList<String> userAgents = Collections.list(request.getHeaders("User-Agent"));
+                if ("/ua".equals(target))
+                    Assert.assertEquals(1, userAgents.size());
+                else
+                    Assert.assertEquals(0, userAgents.size());
+            }
+        });
+
+        // User agent not specified, use default.
+        ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .path("/ua")
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        Assert.assertEquals(200, response.getStatus());
+
+        // User agent explicitly removed.
+        response = client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .agent(null)
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        Assert.assertEquals(200, response.getStatus());
+
+        // User agent explicitly removed.
+        response = client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .header(HttpHeader.USER_AGENT, null)
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        Assert.assertEquals(200, response.getStatus());
+    }
+
+    @Test
+    public void testRequestListenerForMultipleEventsIsInvokedOncePerEvent() throws Exception
+    {
+        start(new EmptyServerHandler());
+
+        final AtomicInteger counter = new AtomicInteger();
+        Request.Listener listener = new Request.Listener()
+        {
+            @Override
+            public void onQueued(Request request)
+            {
+                counter.incrementAndGet();
+            }
+
+            @Override
+            public void onBegin(Request request)
+            {
+                counter.incrementAndGet();
+            }
+
+            @Override
+            public void onHeaders(Request request)
+            {
+                counter.incrementAndGet();
+            }
+
+            @Override
+            public void onCommit(Request request)
+            {
+                counter.incrementAndGet();
+            }
+
+            @Override
+            public void onContent(Request request, ByteBuffer content)
+            {
+                // Should not be invoked
+                counter.incrementAndGet();
+            }
+
+            @Override
+            public void onFailure(Request request, Throwable failure)
+            {
+                // Should not be invoked
+                counter.incrementAndGet();
+            }
+
+            @Override
+            public void onSuccess(Request request)
+            {
+                counter.incrementAndGet();
+            }
+        };
+        ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .onRequestQueued(listener)
+                .onRequestBegin(listener)
+                .onRequestHeaders(listener)
+                .onRequestCommit(listener)
+                .onRequestContent(listener)
+                .onRequestSuccess(listener)
+                .onRequestFailure(listener)
+                .listener(listener)
+                .send();
+
+        Assert.assertEquals(200, response.getStatus());
+        int expectedEventsTriggeredByOnRequestXXXListeners = 5;
+        int expectedEventsTriggeredByListener = 5;
+        int expected = expectedEventsTriggeredByOnRequestXXXListeners + expectedEventsTriggeredByListener;
+        Assert.assertEquals(expected, counter.get());
+    }
+
+    @Test
+    public void testResponseListenerForMultipleEventsIsInvokedOncePerEvent() throws Exception
+    {
+        start(new EmptyServerHandler());
+
+        final AtomicInteger counter = new AtomicInteger();
+        final CountDownLatch latch = new CountDownLatch(1);
+        Response.Listener listener = new Response.Listener()
+        {
+            @Override
+            public void onBegin(Response response)
+            {
+                counter.incrementAndGet();
+            }
+
+            @Override
+            public boolean onHeader(Response response, HttpField field)
+            {
+                // Number of header may vary, so don't count
+                return true;
+            }
+
+            @Override
+            public void onHeaders(Response response)
+            {
+                counter.incrementAndGet();
+            }
+
+            @Override
+            public void onContent(Response response, ByteBuffer content)
+            {
+                // Should not be invoked
+                counter.incrementAndGet();
+            }
+
+            @Override
+            public void onContent(Response response, ByteBuffer content, Callback callback)
+            {
+                // Should not be invoked
+                counter.incrementAndGet();
+            }
+
+            @Override
+            public void onSuccess(Response response)
+            {
+                counter.incrementAndGet();
+            }
+
+            @Override
+            public void onFailure(Response response, Throwable failure)
+            {
+                // Should not be invoked
+                counter.incrementAndGet();
+            }
+
+            @Override
+            public void onComplete(Result result)
+            {
+                Assert.assertEquals(200, result.getResponse().getStatus());
+                counter.incrementAndGet();
+                latch.countDown();
+            }
+        };
+        client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .onResponseBegin(listener)
+                .onResponseHeader(listener)
+                .onResponseHeaders(listener)
+                .onResponseContent(listener)
+                .onResponseContentAsync(listener)
+                .onResponseSuccess(listener)
+                .onResponseFailure(listener)
+                .send(listener);
+
+        Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+        int expectedEventsTriggeredByOnResponseXXXListeners = 3;
+        int expectedEventsTriggeredByCompletionListener = 4;
+        int expected = expectedEventsTriggeredByOnResponseXXXListeners + expectedEventsTriggeredByCompletionListener;
+        Assert.assertEquals(expected, counter.get());
+    }
+
+    @Test
+    public void setOnCompleteCallbackWithBlockingSend() throws Exception
+    {
+        final byte[] content = new byte[512];
+        new Random().nextBytes(content);
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                response.getOutputStream().write(content);
+            }
+        });
+
+        final Exchanger<Response> ex = new Exchanger<Response>();
+        BufferingResponseListener listener = new BufferingResponseListener()
+        {
+            @Override
+            public void onComplete(Result result)
+            {
+                try
+                {
+                    ex.exchange(result.getResponse());
+                }
+                catch (InterruptedException e)
+                {
+                    e.printStackTrace();
+                }
+            }
+        };
+
+        
+        client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .send(listener);
+        
+        Response response = ex.exchange(null);
+
+        Assert.assertEquals(200, response.getStatus());
+        Assert.assertArrayEquals(content, listener.getContent());
+        
+    }
+
+    @Test
+    public void testCustomHostHeader() throws Exception
+    {
+        final String host = "localhost";
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                Assert.assertEquals(host, request.getServerName());
+            }
+        });
+
+        ContentResponse response = client.newRequest("http://127.0.0.1:" + connector.getLocalPort() + "/path")
+                .scheme(scheme)
+                .header(HttpHeader.HOST, host)
+                .send();
+
+        Assert.assertEquals(200, response.getStatus());
+    }
+
+    @Test
+    public void testHTTP10WithKeepAliveAndContentLength() throws Exception
+    {
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                // Send the headers at this point, then write the content
+                byte[] content = "TEST".getBytes("UTF-8");
+                response.setContentLength(content.length);
+                response.flushBuffer();
+                response.getOutputStream().write(content);
+            }
+        });
+
+        ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .version(HttpVersion.HTTP_1_0)
+                .header(HttpHeader.CONNECTION, HttpHeaderValue.KEEP_ALIVE.asString())
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        Assert.assertEquals(200, response.getStatus());
+        Assert.assertTrue(response.getHeaders().contains(HttpHeader.CONNECTION, HttpHeaderValue.KEEP_ALIVE.asString()));
+    }
+
+    @Test
+    public void testHTTP10WithKeepAliveAndNoContentLength() throws Exception
+    {
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                // Send the headers at this point, then write the content
+                response.flushBuffer();
+                response.getOutputStream().print("TEST");
+            }
+        });
+
+        FuturePromise<Connection> promise = new FuturePromise<>();
+        Destination destination = client.getDestination(scheme, "localhost", connector.getLocalPort());
+        destination.newConnection(promise);
+        try (Connection connection = promise.get(5, TimeUnit.SECONDS))
+        {
+            long timeout = 5000;
+            Request request = client.newRequest(destination.getHost(), destination.getPort())
+                    .scheme(destination.getScheme())
+                    .version(HttpVersion.HTTP_1_0)
+                    .header(HttpHeader.CONNECTION, HttpHeaderValue.KEEP_ALIVE.asString())
+                    .timeout(timeout, TimeUnit.MILLISECONDS);
+
+            FutureResponseListener listener = new FutureResponseListener(request);
+            connection.send(request, listener);
+            ContentResponse response = listener.get(2 * timeout, TimeUnit.MILLISECONDS);
+
+            Assert.assertEquals(200, response.getStatus());
+            // The parser notifies end-of-content and therefore the CompleteListener
+            // before closing the connection, so we need to wait before checking
+            // that the connection is closed to avoid races.
+            Thread.sleep(1000);
+            Assert.assertTrue(((HttpConnectionOverHTTP)connection).isClosed());
+        }
+    }
+
+    @Test
+    public void testHTTP10WithKeepAliveAndNoContent() throws Exception
+    {
+        start(new EmptyServerHandler());
+
+        ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .version(HttpVersion.HTTP_1_0)
+                .header(HttpHeader.CONNECTION, HttpHeaderValue.KEEP_ALIVE.asString())
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        Assert.assertEquals(200, response.getStatus());
+        Assert.assertTrue(response.getHeaders().contains(HttpHeader.CONNECTION, HttpHeaderValue.KEEP_ALIVE.asString()));
+    }
+
+    @Test
+    public void testLongPollIsAbortedWhenClientIsStopped() throws Exception
+    {
+        final CountDownLatch latch = new CountDownLatch(1);
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                request.startAsync();
+                latch.countDown();
+            }
+        });
+
+        final CountDownLatch completeLatch = new CountDownLatch(1);
+        client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .send(new Response.CompleteListener()
+                {
+                    @Override
+                    public void onComplete(Result result)
+                    {
+                        if (result.isFailed())
+                            completeLatch.countDown();
+                    }
+                });
+
+        Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+
+        // Stop the client, the complete listener must be invoked.
+        client.stop();
+
+        Assert.assertTrue(completeLatch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testSmallContentDelimitedByEOFWithSlowRequestHTTP10() throws Exception
+    {
+        testContentDelimitedByEOFWithSlowRequest(HttpVersion.HTTP_1_0, 1024);
+    }
+
+    @Test
+    public void testBigContentDelimitedByEOFWithSlowRequestHTTP10() throws Exception
+    {
+        testContentDelimitedByEOFWithSlowRequest(HttpVersion.HTTP_1_0, 128 * 1024);
+    }
+
+    @Test
+    public void testSmallContentDelimitedByEOFWithSlowRequestHTTP11() throws Exception
+    {
+        testContentDelimitedByEOFWithSlowRequest(HttpVersion.HTTP_1_1, 1024);
+    }
+
+    @Test
+    public void testBigContentDelimitedByEOFWithSlowRequestHTTP11() throws Exception
+    {
+        testContentDelimitedByEOFWithSlowRequest(HttpVersion.HTTP_1_1, 128 * 1024);
+    }
+
+    private void testContentDelimitedByEOFWithSlowRequest(final HttpVersion version, int length) throws Exception
+    {
+        // This test is crafted in a way that the response completes before the request is fully written.
+        // With SSL, the response coming down will close the SSLEngine so it would not be possible to
+        // write the last chunk of the request content, and the request will be failed, failing also the
+        // test, which is not what we want.
+        // This is a limit of Java's SSL implementation that does not allow half closes.
+        Assume.assumeTrue(sslContextFactory == null);
+
+        final byte[] data = new byte[length];
+        new Random().nextBytes(data);
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                // Send Connection: close to avoid that the server chunks the content with HTTP 1.1.
+                if (version.compareTo(HttpVersion.HTTP_1_0) > 0)
+                    response.setHeader("Connection", "close");
+                response.getOutputStream().write(data);
+            }
+        });
+
+        DeferredContentProvider content = new DeferredContentProvider(ByteBuffer.wrap(new byte[]{0}));
+        Request request = client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .version(version)
+                .content(content);
+        FutureResponseListener listener = new FutureResponseListener(request);
+        request.send(listener);
+        // Wait some time to simulate a slow request.
+        Thread.sleep(1000);
+        content.close();
+
+        ContentResponse response = listener.get(5, TimeUnit.SECONDS);
+
+        Assert.assertEquals(200, response.getStatus());
+        Assert.assertArrayEquals(data, response.getContent());
+    }
+
+    @Test
+    public void testRequestRetries() throws Exception
+    {
+        final int maxRetries = 3;
+        final AtomicInteger requests = new AtomicInteger();
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                int count = requests.incrementAndGet();
+                if (count == maxRetries)
+                    baseRequest.setHandled(true);
+                consume(request.getInputStream());
+            }
+        });
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        new RetryListener(client, scheme, "localhost", connector.getLocalPort(), maxRetries)
+        {
+            @Override
+            protected void completed(Result result)
+            {
+                latch.countDown();
+            }
+        }.perform();
+
+        Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+    }
+    
+    @Test
+    public void testCompleteNotInvokedUntilContentConsumed() throws Exception
+    {
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                ServletOutputStream output = response.getOutputStream();
+                output.write(new byte[1024]);
+            }
+        });
+
+        final AtomicReference<Callback> callbackRef = new AtomicReference<>();
+        final CountDownLatch contentLatch = new CountDownLatch(1);
+        final CountDownLatch completeLatch = new CountDownLatch(1);
+        client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .send(new Response.Listener.Adapter()
+                {
+                    @Override
+                    public void onContent(Response response, ByteBuffer content, Callback callback)
+                    {
+                        // Do not notify the callback yet.
+                        callbackRef.set(callback);
+                        contentLatch.countDown();
+                    }
+
+                    @Override
+                    public void onComplete(Result result)
+                    {
+                        if (result.isSucceeded())
+                            completeLatch.countDown();
+                    }
+                });
+
+        Assert.assertTrue(contentLatch.await(5, TimeUnit.SECONDS));
+
+        // Make sure the complete event is not emitted.
+        Assert.assertFalse(completeLatch.await(1, TimeUnit.SECONDS));
+
+        // Consume the content.
+        callbackRef.get().succeeded();
+
+        // Now the complete event is emitted.
+        Assert.assertTrue(completeLatch.await(5, TimeUnit.SECONDS));
+    }
+
+    private void consume(InputStream input) throws IOException
+    {
+        while (true)
+        {
+            if (input.read() < 0)
+                break;
+        }
+    }
+
+    public static abstract class RetryListener implements Response.CompleteListener
+    {
+        private final HttpClient client;
+        private final String scheme;
+        private final String host;
+        private final int port;
+        private final int maxRetries;
+        private int retries;
+
+        public RetryListener(HttpClient client, String scheme, String host, int port, int maxRetries)
+        {
+            this.client = client;
+            this.scheme = scheme;
+            this.host = host;
+            this.port = port;
+            this.maxRetries = maxRetries;
+        }
+
+        protected abstract void completed(Result result);
+
+        @Override
+        public void onComplete(Result result)
+        {
+            if (retries > maxRetries || result.isSucceeded() && result.getResponse().getStatus() == 200)
+                completed(result);
+            else
+                retry();
+        }
+
+        private void retry()
+        {
+            ++retries;
+            perform();
+        }
+
+        public void perform()
+        {
+            client.newRequest(host, port)
+                    .scheme(scheme)
+                    .method("POST")
+                    .param("attempt", String.valueOf(retries))
+                    .content(new StringContentProvider("0123456789ABCDEF"))
+                    .send(this);
+        }
+    }
+}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTimeoutTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTimeoutTest.java
new file mode 100644
index 0000000..a715177
--- /dev/null
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTimeoutTest.java
@@ -0,0 +1,509 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.SocketTimeoutException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import javax.net.ssl.SSLEngine;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.client.api.Connection;
+import org.eclipse.jetty.client.api.Destination;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.client.api.Result;
+import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP;
+import org.eclipse.jetty.client.http.HttpDestinationOverHTTP;
+import org.eclipse.jetty.client.util.BufferingResponseListener;
+import org.eclipse.jetty.client.util.InputStreamContentProvider;
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.ClientConnectionFactory;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.ssl.SslClientConnectionFactory;
+import org.eclipse.jetty.io.ssl.SslConnection;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.toolchain.test.annotation.Slow;
+import org.eclipse.jetty.util.FuturePromise;
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.hamcrest.Matchers;
+import org.junit.Assert;
+import org.junit.Assume;
+import org.junit.Test;
+
+public class HttpClientTimeoutTest extends AbstractHttpClientServerTest
+{
+    public HttpClientTimeoutTest(SslContextFactory sslContextFactory)
+    {
+        super(sslContextFactory);
+    }
+
+    @Slow
+    @Test(expected = TimeoutException.class)
+    public void testTimeoutOnFuture() throws Exception
+    {
+        long timeout = 1000;
+        start(new TimeoutHandler(2 * timeout));
+
+        client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .timeout(timeout, TimeUnit.MILLISECONDS)
+                .send();
+    }
+
+    @Slow
+    @Test
+    public void testTimeoutOnListener() throws Exception
+    {
+        long timeout = 1000;
+        start(new TimeoutHandler(2 * timeout));
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        Request request = client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .timeout(timeout, TimeUnit.MILLISECONDS);
+        request.send(new Response.CompleteListener()
+        {
+            @Override
+            public void onComplete(Result result)
+            {
+                Assert.assertTrue(result.isFailed());
+                latch.countDown();
+            }
+        });
+        Assert.assertTrue(latch.await(3 * timeout, TimeUnit.MILLISECONDS));
+    }
+
+    @Slow
+    @Test
+    public void testTimeoutOnQueuedRequest() throws Exception
+    {
+        long timeout = 1000;
+        start(new TimeoutHandler(3 * timeout));
+
+        // Only one connection so requests get queued
+        client.setMaxConnectionsPerDestination(1);
+
+        // The first request has a long timeout
+        final CountDownLatch firstLatch = new CountDownLatch(1);
+        Request request = client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .timeout(4 * timeout, TimeUnit.MILLISECONDS);
+        request.send(new Response.CompleteListener()
+        {
+            @Override
+            public void onComplete(Result result)
+            {
+                Assert.assertFalse(result.isFailed());
+                firstLatch.countDown();
+            }
+        });
+
+        // Second request has a short timeout and should fail in the queue
+        final CountDownLatch secondLatch = new CountDownLatch(1);
+        request = client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .timeout(timeout, TimeUnit.MILLISECONDS);
+        request.send(new Response.CompleteListener()
+        {
+            @Override
+            public void onComplete(Result result)
+            {
+                Assert.assertTrue(result.isFailed());
+                secondLatch.countDown();
+            }
+        });
+
+        Assert.assertTrue(secondLatch.await(2 * timeout, TimeUnit.MILLISECONDS));
+        // The second request must fail before the first request has completed
+        Assert.assertTrue(firstLatch.getCount() > 0);
+        Assert.assertTrue(firstLatch.await(5 * timeout, TimeUnit.MILLISECONDS));
+    }
+
+    @Slow
+    @Test
+    public void testTimeoutIsCancelledOnSuccess() throws Exception
+    {
+        long timeout = 1000;
+        start(new TimeoutHandler(timeout));
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        final byte[] content = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+        Request request = client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .content(new InputStreamContentProvider(new ByteArrayInputStream(content)))
+                .timeout(2 * timeout, TimeUnit.MILLISECONDS);
+        request.send(new BufferingResponseListener()
+        {
+            @Override
+            public void onComplete(Result result)
+            {
+                Assert.assertFalse(result.isFailed());
+                Assert.assertArrayEquals(content, getContent());
+                latch.countDown();
+            }
+        });
+
+        Assert.assertTrue(latch.await(3 * timeout, TimeUnit.MILLISECONDS));
+
+        TimeUnit.MILLISECONDS.sleep(2 * timeout);
+
+        Assert.assertNull(request.getAbortCause());
+    }
+
+    @Slow
+    @Test
+    public void testTimeoutOnListenerWithExplicitConnection() throws Exception
+    {
+        long timeout = 1000;
+        start(new TimeoutHandler(2 * timeout));
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        Destination destination = client.getDestination(scheme, "localhost", connector.getLocalPort());
+        FuturePromise<Connection> futureConnection = new FuturePromise<>();
+        destination.newConnection(futureConnection);
+        try (Connection connection = futureConnection.get(5, TimeUnit.SECONDS))
+        {
+            Request request = client.newRequest("localhost", connector.getLocalPort())
+                    .scheme(scheme)
+                    .timeout(timeout, TimeUnit.MILLISECONDS);
+            connection.send(request, new Response.CompleteListener()
+            {
+                @Override
+                public void onComplete(Result result)
+                {
+                    Assert.assertTrue(result.isFailed());
+                    latch.countDown();
+                }
+            });
+
+            Assert.assertTrue(latch.await(3 * timeout, TimeUnit.MILLISECONDS));
+        }
+    }
+
+    @Slow
+    @Test
+    public void testTimeoutIsCancelledOnSuccessWithExplicitConnection() throws Exception
+    {
+        long timeout = 1000;
+        start(new TimeoutHandler(timeout));
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        Destination destination = client.getDestination(scheme, "localhost", connector.getLocalPort());
+        FuturePromise<Connection> futureConnection = new FuturePromise<>();
+        destination.newConnection(futureConnection);
+        try (Connection connection = futureConnection.get(5, TimeUnit.SECONDS))
+        {
+            Request request = client.newRequest(destination.getHost(), destination.getPort())
+                    .scheme(scheme)
+                    .timeout(2 * timeout, TimeUnit.MILLISECONDS);
+            connection.send(request, new Response.CompleteListener()
+            {
+                @Override
+                public void onComplete(Result result)
+                {
+                    Response response = result.getResponse();
+                    Assert.assertEquals(200, response.getStatus());
+                    Assert.assertFalse(result.isFailed());
+                    latch.countDown();
+                }
+            });
+
+            Assert.assertTrue(latch.await(3 * timeout, TimeUnit.MILLISECONDS));
+
+            TimeUnit.MILLISECONDS.sleep(2 * timeout);
+
+            Assert.assertNull(request.getAbortCause());
+        }
+    }
+
+    @Test
+    public void testIdleTimeout() throws Throwable
+    {
+        long timeout = 1000;
+        start(new TimeoutHandler(2 * timeout));
+        client.stop();
+        final AtomicBoolean sslIdle = new AtomicBoolean();
+        client = new HttpClient(new HttpClientTransportOverHTTP()
+        {
+            @Override
+            public HttpDestination newHttpDestination(Origin origin)
+            {
+                return new HttpDestinationOverHTTP(getHttpClient(), origin)
+                {
+                    @Override
+                    protected ClientConnectionFactory newSslClientConnectionFactory(ClientConnectionFactory connectionFactory)
+                    {
+                        HttpClient client = getHttpClient();
+                        return new SslClientConnectionFactory(client.getSslContextFactory(), client.getByteBufferPool(), client.getExecutor(), connectionFactory)
+                        {
+                            @Override
+                            protected SslConnection newSslConnection(ByteBufferPool byteBufferPool, Executor executor, EndPoint endPoint, SSLEngine engine)
+                            {
+                                return new SslConnection(byteBufferPool, executor, endPoint, engine)
+                                {
+                                    @Override
+                                    protected boolean onReadTimeout()
+                                    {
+                                        sslIdle.set(true);
+                                        return super.onReadTimeout();
+                                    }
+                                };
+                            }
+                        };
+                    }
+                };
+            }
+        }, sslContextFactory);
+        client.setIdleTimeout(timeout);
+        client.start();
+
+        try
+        {
+            client.newRequest("localhost", connector.getLocalPort())
+                    .scheme(scheme)
+                    .send();
+            Assert.fail();
+        }
+        catch (Exception x)
+        {
+            Assert.assertFalse(sslIdle.get());
+            Assert.assertThat(x.getCause(), Matchers.instanceOf(TimeoutException.class));
+        }
+    }
+
+    @Slow
+    @Test
+    public void testConnectTimeoutFailsRequest() throws Exception
+    {
+        String host = "10.255.255.1";
+        int port = 80;
+        int connectTimeout = 1000;
+        assumeConnectTimeout(host, port, connectTimeout);
+
+        start(new EmptyServerHandler());
+        client.stop();
+        client.setConnectTimeout(connectTimeout);
+        client.start();
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        Request request = client.newRequest(host, port);
+        request.scheme(scheme)
+                .send(new Response.CompleteListener()
+                {
+                    @Override
+                    public void onComplete(Result result)
+                    {
+                        if (result.isFailed())
+                            latch.countDown();
+                    }
+                });
+
+        Assert.assertTrue(latch.await(2 * connectTimeout, TimeUnit.MILLISECONDS));
+        Assert.assertNotNull(request.getAbortCause());
+    }
+
+    @Slow
+    @Test
+    public void testConnectTimeoutIsCancelledByShorterRequestTimeout() throws Exception
+    {
+        String host = "10.255.255.1";
+        int port = 80;
+        int connectTimeout = 2000;
+        assumeConnectTimeout(host, port, connectTimeout);
+
+        start(new EmptyServerHandler());
+        client.stop();
+        client.setConnectTimeout(connectTimeout);
+        client.start();
+
+        final AtomicInteger completes = new AtomicInteger();
+        final CountDownLatch latch = new CountDownLatch(2);
+        Request request = client.newRequest(host, port);
+        request.scheme(scheme)
+                .timeout(connectTimeout / 2, TimeUnit.MILLISECONDS)
+                .send(new Response.CompleteListener()
+                {
+                    @Override
+                    public void onComplete(Result result)
+                    {
+                        completes.incrementAndGet();
+                        latch.countDown();
+                    }
+                });
+
+        Assert.assertFalse(latch.await(2 * connectTimeout, TimeUnit.MILLISECONDS));
+        Assert.assertEquals(1, completes.get());
+        Assert.assertNotNull(request.getAbortCause());
+    }
+
+    @Test
+    public void retryAfterConnectTimeout() throws Exception
+    {
+        final String host = "10.255.255.1";
+        final int port = 80;
+        int connectTimeout = 1000;
+        assumeConnectTimeout(host, port, connectTimeout);
+
+        start(new EmptyServerHandler());
+        client.stop();
+        client.setConnectTimeout(connectTimeout);
+        client.start();
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        Request request = client.newRequest(host, port);
+        request.scheme(scheme)
+                .send(new Response.CompleteListener()
+                {
+                    @Override
+                    public void onComplete(Result result)
+                    {
+                        if (result.isFailed())
+                        {
+                            // Retry
+                            client.newRequest(host, port)
+                                    .scheme(scheme)
+                                    .send(new Response.CompleteListener()
+                                    {
+                                        @Override
+                                        public void onComplete(Result result)
+                                        {
+                                            if (result.isFailed())
+                                                latch.countDown();
+                                        }
+                                    });
+                        }
+                    }
+                });
+
+        Assert.assertTrue(latch.await(333 * connectTimeout, TimeUnit.MILLISECONDS));
+        Assert.assertNotNull(request.getAbortCause());
+    }
+
+    @Test
+    public void testVeryShortTimeout() throws Exception
+    {
+        start(new EmptyServerHandler());
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .timeout(1, TimeUnit.MILLISECONDS) // Very short timeout
+                .send(new Response.CompleteListener()
+                {
+                    @Override
+                    public void onComplete(Result result)
+                    {
+                        latch.countDown();
+                    }
+                });
+
+        Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testTimeoutCancelledWhenSendingThrowsException() throws Exception
+    {
+        start(new EmptyServerHandler());
+
+        long timeout = 1000;
+        Request request = client.newRequest("badscheme://localhost:" + connector.getLocalPort());
+
+        try
+        {
+            request.timeout(timeout, TimeUnit.MILLISECONDS)
+                    .send(new Response.CompleteListener()
+                    {
+                        @Override
+                        public void onComplete(Result result)
+                        {
+                        }
+                    });
+            Assert.fail();
+        }
+        catch (Exception expected)
+        {
+        }
+
+        Thread.sleep(2 * timeout);
+
+        // If the task was not cancelled, it aborted the request.
+        Assert.assertNull(request.getAbortCause());
+    }
+
+    private void assumeConnectTimeout(String host, int port, int connectTimeout) throws IOException
+    {
+        try (Socket socket = new Socket())
+        {
+            // Try to connect to a private address in the 10.x.y.z range.
+            // These addresses are usually not routed, so an attempt to
+            // connect to them will hang the connection attempt, which is
+            // what we want to simulate in this test.
+            socket.connect(new InetSocketAddress(host, port), connectTimeout);
+            // Abort the test if we can connect.
+            Assume.assumeTrue(false);
+        }
+        catch (SocketTimeoutException x)
+        {
+            // Expected timeout during connect, continue the test.
+            Assume.assumeTrue(true);
+        }
+        catch (Throwable x)
+        {
+            // Abort if any other exception happens.
+            Assume.assumeTrue(false);
+        }
+    }
+
+    private class TimeoutHandler extends AbstractHandler
+    {
+        private final long timeout;
+
+        public TimeoutHandler(long timeout)
+        {
+            this.timeout = timeout;
+        }
+
+        @Override
+        public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+        {
+            baseRequest.setHandled(true);
+            try
+            {
+                TimeUnit.MILLISECONDS.sleep(timeout);
+                IO.copy(request.getInputStream(), response.getOutputStream());
+            }
+            catch (InterruptedException x)
+            {
+                throw new ServletException(x);
+            }
+        }
+    }
+}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientURITest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientURITest.java
new file mode 100644
index 0000000..d6eb4e9
--- /dev/null
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientURITest.java
@@ -0,0 +1,474 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.io.IOException;
+import java.net.URLEncoder;
+import java.util.concurrent.TimeUnit;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.util.Fields;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class HttpClientURITest extends AbstractHttpClientServerTest
+{
+    public HttpClientURITest(SslContextFactory sslContextFactory)
+    {
+        super(sslContextFactory);
+    }
+
+    @Test
+    public void testIPv6Host() throws Exception
+    {
+        start(new EmptyServerHandler());
+
+        String host = "::1";
+        Request request = client.newRequest(host, connector.getLocalPort())
+                .scheme(scheme)
+                .timeout(5, TimeUnit.SECONDS);
+
+        Assert.assertEquals(host, request.getHost());
+        StringBuilder uri = new StringBuilder();
+        URIUtil.appendSchemeHostPort(uri, scheme, host, connector.getLocalPort());
+        Assert.assertEquals(uri.toString(), request.getURI().toString());
+
+        Assert.assertEquals(HttpStatus.OK_200, request.send().getStatus());
+    }
+
+    @Test
+    public void testPath() throws Exception
+    {
+        final String path = "/path";
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                Assert.assertEquals(path, request.getRequestURI());
+            }
+        });
+
+        Request request = client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .timeout(5, TimeUnit.SECONDS)
+                .path(path);
+
+        Assert.assertEquals(path, request.getPath());
+        Assert.assertNull(request.getQuery());
+        Fields params = request.getParams();
+        Assert.assertEquals(0, params.getSize());
+        Assert.assertTrue(request.getURI().toString().endsWith(path));
+
+        ContentResponse response = request.send();
+
+        Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
+    }
+
+    @Test
+    public void testPathWithQuery() throws Exception
+    {
+        String name = "a";
+        String value = "1";
+        final String query = name + "=" + value;
+        final String path = "/path";
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                Assert.assertEquals(path, request.getRequestURI());
+                Assert.assertEquals(query, request.getQueryString());
+            }
+        });
+
+        String pathQuery = path + "?" + query;
+        Request request = client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .timeout(5, TimeUnit.SECONDS)
+                .path(pathQuery);
+
+        Assert.assertEquals(path, request.getPath());
+        Assert.assertEquals(query, request.getQuery());
+        Assert.assertTrue(request.getURI().toString().endsWith(pathQuery));
+        Fields params = request.getParams();
+        Assert.assertEquals(1, params.getSize());
+        Assert.assertEquals(value, params.get(name).getValue());
+
+        ContentResponse response = request.send();
+
+        Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
+    }
+
+    @Test
+    public void testPathWithParam() throws Exception
+    {
+        String name = "a";
+        String value = "1";
+        final String query = name + "=" + value;
+        final String path = "/path";
+        String pathQuery = path + "?" + query;
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                Assert.assertEquals(path, request.getRequestURI());
+                Assert.assertEquals(query, request.getQueryString());
+            }
+        });
+
+        Request request = client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .timeout(5, TimeUnit.SECONDS)
+                .path(path)
+                .param(name, value);
+
+        Assert.assertEquals(path, request.getPath());
+        Assert.assertEquals(query, request.getQuery());
+        Assert.assertTrue(request.getURI().toString().endsWith(pathQuery));
+        Fields params = request.getParams();
+        Assert.assertEquals(1, params.getSize());
+        Assert.assertEquals(value, params.get(name).getValue());
+
+        ContentResponse response = request.send();
+
+        Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
+    }
+
+    @Test
+    public void testPathWithQueryAndParam() throws Exception
+    {
+        String name1 = "a";
+        String value1 = "1";
+        String name2 = "b";
+        String value2 = "2";
+        final String query = name1 + "=" + value1 + "&" + name2 + "=" + value2;
+        final String path = "/path";
+        String pathQuery = path + "?" + query;
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                Assert.assertEquals(path, request.getRequestURI());
+                Assert.assertEquals(query, request.getQueryString());
+            }
+        });
+
+        Request request = client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .timeout(5, TimeUnit.SECONDS)
+                .path(path + "?" + name1 + "=" + value1)
+                .param(name2, value2);
+
+        Assert.assertEquals(path, request.getPath());
+        Assert.assertEquals(query, request.getQuery());
+        Assert.assertTrue(request.getURI().toString().endsWith(pathQuery));
+        Fields params = request.getParams();
+        Assert.assertEquals(2, params.getSize());
+        Assert.assertEquals(value1, params.get(name1).getValue());
+        Assert.assertEquals(value2, params.get(name2).getValue());
+
+        ContentResponse response = request.send();
+
+        Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
+    }
+
+    @Test
+    public void testPathWithQueryAndParamValueEncoded() throws Exception
+    {
+        final String name1 = "a";
+        final String value1 = "\u20AC";
+        final String encodedValue1 = URLEncoder.encode(value1, "UTF-8");
+        final String name2 = "b";
+        final String value2 = "\u00A5";
+        String encodedValue2 = URLEncoder.encode(value2, "UTF-8");
+        final String query = name1 + "=" + encodedValue1 + "&" + name2 + "=" + encodedValue2;
+        final String path = "/path";
+        String pathQuery = path + "?" + query;
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                Assert.assertEquals(path, request.getRequestURI());
+                Assert.assertEquals(query, request.getQueryString());
+                Assert.assertEquals(value1, request.getParameter(name1));
+                Assert.assertEquals(value2, request.getParameter(name2));
+            }
+        });
+
+        Request request = client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .timeout(5, TimeUnit.SECONDS)
+                .path(path + "?" + name1 + "=" + encodedValue1)
+                .param(name2, value2);
+
+        Assert.assertEquals(path, request.getPath());
+        Assert.assertEquals(query, request.getQuery());
+        Assert.assertTrue(request.getURI().toString().endsWith(pathQuery));
+        Fields params = request.getParams();
+        Assert.assertEquals(2, params.getSize());
+        Assert.assertEquals(value1, params.get(name1).getValue());
+        Assert.assertEquals(value2, params.get(name2).getValue());
+
+        ContentResponse response = request.send();
+
+        Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
+    }
+
+    @Test
+    public void testNoParameterNameNoParameterValue() throws Exception
+    {
+        final String path = "/path";
+        final String query = "="; // Bogus query
+        String pathQuery = path + "?" + query;
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                Assert.assertEquals(path, request.getRequestURI());
+                Assert.assertEquals(query, request.getQueryString());
+            }
+        });
+
+        Request request = client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .timeout(5, TimeUnit.SECONDS)
+                .path(pathQuery);
+
+        Assert.assertEquals(path, request.getPath());
+        Assert.assertEquals(query, request.getQuery());
+        Assert.assertTrue(request.getURI().toString().endsWith(pathQuery));
+        Fields params = request.getParams();
+        Assert.assertEquals(0, params.getSize());
+
+        ContentResponse response = request.send();
+
+        Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
+    }
+
+    @Test
+    public void testNoParameterNameWithParameterValue() throws Exception
+    {
+        final String path = "/path";
+        final String query = "=1"; // Bogus query
+        String pathQuery = path + "?" + query;
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                Assert.assertEquals(path, request.getRequestURI());
+                Assert.assertEquals(query, request.getQueryString());
+            }
+        });
+
+        Request request = client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .timeout(5, TimeUnit.SECONDS)
+                .path(pathQuery);
+
+        Assert.assertEquals(path, request.getPath());
+        Assert.assertEquals(query, request.getQuery());
+        Assert.assertTrue(request.getURI().toString().endsWith(pathQuery));
+        Fields params = request.getParams();
+        Assert.assertEquals(0, params.getSize());
+
+        ContentResponse response = request.send();
+
+        Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
+    }
+
+    @Test
+    public void testCaseSensitiveParameterName() throws Exception
+    {
+        final String name1 = "a";
+        final String name2 = "A";
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                Assert.assertEquals(name1, request.getParameter(name1));
+                Assert.assertEquals(name2, request.getParameter(name2));
+            }
+        });
+
+        ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .path("/path?" + name1 + "=" + name1)
+                .param(name2, name2)
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
+    }
+
+    @Test
+    public void testRawQueryIsPreservedInURI() throws Exception
+    {
+        final String name = "a";
+        final String rawValue = "Hello%20World";
+        final String rawQuery = name + "=" + rawValue;
+        final String value = "Hello World";
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                Assert.assertEquals(rawQuery, request.getQueryString());
+                Assert.assertEquals(value, request.getParameter(name));
+            }
+        });
+
+        String uri = scheme + "://localhost:" + connector.getLocalPort() + "/path?" + rawQuery;
+        Request request = client.newRequest(uri)
+                .timeout(5, TimeUnit.SECONDS);
+        Assert.assertEquals(rawQuery, request.getQuery());
+
+        ContentResponse response = request.send();
+
+        Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
+    }
+
+    @Test
+    public void testRawQueryIsPreservedInPath() throws Exception
+    {
+        final String name = "a";
+        final String rawValue = "Hello%20World";
+        final String rawQuery = name + "=" + rawValue;
+        final String value = "Hello World";
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                Assert.assertEquals(rawQuery, request.getQueryString());
+                Assert.assertEquals(value, request.getParameter(name));
+            }
+        });
+
+        Request request = client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .path("/path?" + rawQuery)
+                .timeout(5, TimeUnit.SECONDS);
+        Assert.assertEquals(rawQuery, request.getQuery());
+
+        ContentResponse response = request.send();
+
+        Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
+    }
+
+    @Test
+    public void testRawQueryIsPreservedWithParam() throws Exception
+    {
+        final String name1 = "a";
+        final String name2 = "b";
+        final String rawValue1 = "Hello%20World";
+        final String rawQuery1 = name1 + "=" + rawValue1;
+        final String value1 = "Hello World";
+        final String value2 = "alfa omega";
+        final String encodedQuery2 = name2 + "=" + URLEncoder.encode(value2, "UTF-8");
+        final String query = rawQuery1 + "&" + encodedQuery2;
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                Assert.assertEquals(query, request.getQueryString());
+                Assert.assertEquals(value1, request.getParameter(name1));
+                Assert.assertEquals(value2, request.getParameter(name2));
+            }
+        });
+
+        Request request = client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .path("/path?" + rawQuery1)
+                .param(name2, value2)
+                .timeout(5, TimeUnit.SECONDS);
+        Assert.assertEquals(query, request.getQuery());
+
+        ContentResponse response = request.send();
+
+        Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
+    }
+
+    @Test
+    public void testSchemeIsCaseInsensitive() throws Exception
+    {
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+            }
+        });
+
+        ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme.toUpperCase())
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
+    }
+
+    @Test
+    public void testHostIsCaseInsensitive() throws Exception
+    {
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+            }
+        });
+
+        ContentResponse response = client.newRequest("LOCALHOST", connector.getLocalPort())
+                .scheme(scheme)
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
+    }
+}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientUploadDuringServerShutdown.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientUploadDuringServerShutdown.java
new file mode 100644
index 0000000..16bfd44
--- /dev/null
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientUploadDuringServerShutdown.java
@@ -0,0 +1,277 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Random;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.client.api.Result;
+import org.eclipse.jetty.client.http.HttpChannelOverHTTP;
+import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP;
+import org.eclipse.jetty.client.http.HttpConnectionOverHTTP;
+import org.eclipse.jetty.client.http.HttpDestinationOverHTTP;
+import org.eclipse.jetty.client.util.BytesContentProvider;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class HttpClientUploadDuringServerShutdown
+{
+    /**
+     * A server used in conjunction with {@link ClientSide}.
+     */
+    public static class ServerSide
+    {
+        public static void main(String[] args) throws Exception
+        {
+            QueuedThreadPool serverThreads = new QueuedThreadPool();
+            serverThreads.setName("server");
+            Server server = new Server(serverThreads);
+            ServerConnector connector = new ServerConnector(server);
+            connector.setPort(8888);
+            server.addConnector(connector);
+            server.setHandler(new AbstractHandler()
+            {
+                @Override
+                public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+                {
+                    baseRequest.setHandled(true);
+                    byte[] buffer = new byte[1024];
+                    InputStream input = request.getInputStream();
+                    while (true)
+                    {
+                        int read = input.read(buffer);
+                        if (read < 0)
+                            break;
+                        long now = System.nanoTime();
+                        long sleep = TimeUnit.MICROSECONDS.toNanos(1);
+                        while (System.nanoTime() < now + sleep)
+                        {
+                            // Wait.
+                        }
+                    }
+                }
+            });
+            server.start();
+        }
+    }
+
+    /**
+     * An infinite loop of a client uploading content to the server.
+     * The server may be killed while this client is running, and the
+     * behavior should be that this client continues running, failing
+     * exchanges while the server is down, but succeeding them when
+     * the server is up and running.
+     *
+     * @see ServerSide
+     */
+    public static class ClientSide
+    {
+        public static void main(String[] args) throws Exception
+        {
+            QueuedThreadPool clientThreads = new QueuedThreadPool();
+            clientThreads.setName("client");
+            HttpClient client = new HttpClient(new HttpClientTransportOverHTTP(2), null);
+            client.setMaxConnectionsPerDestination(2);
+            client.setIdleTimeout(10000);
+            client.setExecutor(clientThreads);
+            client.start();
+
+            Random random = new Random();
+
+            while (true)
+            {
+                int count = 1;
+                final CountDownLatch latch = new CountDownLatch(count);
+                for (int i = 0; i < count; ++i)
+                {
+                    int length = 16 * 1024 * 1024 + random.nextInt(16 * 1024 * 1024);
+                    client.newRequest("localhost", 8888)
+                            .content(new BytesContentProvider(new byte[length]))
+                            .send(new Response.CompleteListener()
+                            {
+                                @Override
+                                public void onComplete(Result result)
+                                {
+                                    latch.countDown();
+                                }
+                            });
+                    long sleep = 1 + random.nextInt(10);
+                    TimeUnit.MILLISECONDS.sleep(sleep);
+                }
+                latch.await();
+            }
+        }
+    }
+
+    @Test
+    public void testUploadDuringServerShutdown() throws Exception
+    {
+        final AtomicReference<EndPoint> endPointRef = new AtomicReference<>();
+        final CountDownLatch serverLatch = new CountDownLatch(1);
+        QueuedThreadPool serverThreads = new QueuedThreadPool();
+        serverThreads.setName("server");
+        Server server = new Server(serverThreads);
+        ServerConnector connector = new ServerConnector(server);
+        server.addConnector(connector);
+        server.setHandler(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                endPointRef.set(baseRequest.getHttpChannel().getEndPoint());
+                serverLatch.countDown();
+            }
+        });
+        server.start();
+
+        final AtomicBoolean afterSetup = new AtomicBoolean();
+        final CountDownLatch sendLatch = new CountDownLatch(1);
+        final CountDownLatch beginLatch = new CountDownLatch(1);
+        final CountDownLatch associateLatch = new CountDownLatch(1);
+        QueuedThreadPool clientThreads = new QueuedThreadPool();
+        clientThreads.setName("client");
+        HttpClient client = new HttpClient(new HttpClientTransportOverHTTP(1)
+        {
+            @Override
+            protected HttpConnectionOverHTTP newHttpConnection(EndPoint endPoint, HttpDestination destination)
+            {
+                return new HttpConnectionOverHTTP(endPoint, destination)
+                {
+                    @Override
+                    protected HttpChannelOverHTTP newHttpChannel()
+                    {
+                        return new HttpChannelOverHTTP(this)
+                        {
+                            @Override
+                            public void send()
+                            {
+                                if (afterSetup.get())
+                                {
+                                    associateLatch.countDown();
+                                }
+                                super.send();
+                            }
+                        };
+                    }
+
+                    @Override
+                    protected void close(Throwable failure)
+                    {
+                        try
+                        {
+                            sendLatch.countDown();
+                            beginLatch.await(5, TimeUnit.SECONDS);
+                            super.close(failure);
+                        }
+                        catch (InterruptedException x)
+                        {
+                            x.printStackTrace();
+                        }
+                    }
+
+                    @Override
+                    protected boolean abort(Throwable failure)
+                    {
+                        try
+                        {
+                            associateLatch.await(5, TimeUnit.SECONDS);
+                            return super.abort(failure);
+                        }
+                        catch (InterruptedException x)
+                        {
+                            x.printStackTrace();
+                            return false;
+                        }
+                    }
+                };
+            }
+        }, null);
+        client.setIdleTimeout(10000);
+        client.setExecutor(clientThreads);
+        client.start();
+
+        // Create one connection.
+        client.newRequest("localhost", connector.getLocalPort()).send();
+        Assert.assertTrue(serverLatch.await(5, TimeUnit.SECONDS));
+
+        afterSetup.set(true);
+        Thread.sleep(1000);
+
+        // Close the connection, so that the receiver is woken
+        // up and will call HttpConnectionOverHTTP.close().
+        EndPoint endPoint = endPointRef.get();
+        endPoint.close();
+
+        // Wait for close() so that the connection that
+        // is being closed is used to send the request.
+        Assert.assertTrue(sendLatch.await(5, TimeUnit.SECONDS));
+
+        final CountDownLatch completeLatch = new CountDownLatch(1);
+        client.newRequest("localhost", connector.getLocalPort())
+                .timeout(10, TimeUnit.SECONDS)
+                .onRequestBegin(new org.eclipse.jetty.client.api.Request.BeginListener()
+                {
+                    @Override
+                    public void onBegin(org.eclipse.jetty.client.api.Request request)
+                    {
+                        try
+                        {
+                            beginLatch.countDown();
+                            completeLatch.await(5, TimeUnit.SECONDS);
+                        }
+                        catch (InterruptedException x)
+                        {
+                            x.printStackTrace();
+                        }
+                    }
+                })
+                .send(new Response.CompleteListener()
+                {
+                    @Override
+                    public void onComplete(Result result)
+                    {
+                        completeLatch.countDown();
+                    }
+                });
+
+        Assert.assertTrue(completeLatch.await(5, TimeUnit.SECONDS));
+
+        HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination("http", "localhost", connector.getLocalPort());
+        ConnectionPool pool = destination.getConnectionPool();
+        Assert.assertEquals(0, pool.getConnectionCount());
+        Assert.assertEquals(0, pool.getIdleConnections().size());
+        Assert.assertEquals(0, pool.getActiveConnections().size());
+    }
+}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpConnectionLifecycleTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpConnectionLifecycleTest.java
new file mode 100644
index 0000000..368c5ab
--- /dev/null
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpConnectionLifecycleTest.java
@@ -0,0 +1,528 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.client.api.Connection;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.client.api.Result;
+import org.eclipse.jetty.client.http.HttpDestinationOverHTTP;
+import org.eclipse.jetty.client.util.ByteBufferContentProvider;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.toolchain.test.annotation.Slow;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.StdErrLog;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest
+{
+    public HttpConnectionLifecycleTest(SslContextFactory sslContextFactory)
+    {
+        super(sslContextFactory);
+    }
+
+    @Override
+    public void start(Handler handler) throws Exception
+    {
+        super.start(handler);
+        client.setStrictEventOrdering(false);
+    }
+
+    @Test
+    public void test_SuccessfulRequest_ReturnsConnection() throws Exception
+    {
+        start(new EmptyServerHandler());
+
+        String host = "localhost";
+        int port = connector.getLocalPort();
+        HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
+        ConnectionPool connectionPool = destination.getConnectionPool();
+
+        final BlockingQueue<Connection> idleConnections = connectionPool.getIdleConnections();
+        Assert.assertEquals(0, idleConnections.size());
+
+        final BlockingQueue<Connection> activeConnections = connectionPool.getActiveConnections();
+        Assert.assertEquals(0, activeConnections.size());
+
+        final CountDownLatch headersLatch = new CountDownLatch(1);
+        final CountDownLatch successLatch = new CountDownLatch(3);
+        client.newRequest(host, port)
+                .scheme(scheme)
+                .onRequestSuccess(new Request.SuccessListener()
+                {
+                    @Override
+                    public void onSuccess(Request request)
+                    {
+                        successLatch.countDown();
+                    }
+                })
+                .onResponseHeaders(new Response.HeadersListener()
+                {
+                    @Override
+                    public void onHeaders(Response response)
+                    {
+                        Assert.assertEquals(0, idleConnections.size());
+                        Assert.assertEquals(1, activeConnections.size());
+                        headersLatch.countDown();
+                    }
+                })
+                .send(new Response.Listener.Adapter()
+                {
+                    @Override
+                    public void onSuccess(Response response)
+                    {
+                        successLatch.countDown();
+                    }
+
+                    @Override
+                    public void onComplete(Result result)
+                    {
+                        Assert.assertFalse(result.isFailed());
+                        successLatch.countDown();
+                    }
+                });
+
+        Assert.assertTrue(headersLatch.await(5, TimeUnit.SECONDS));
+        Assert.assertTrue(successLatch.await(5, TimeUnit.SECONDS));
+
+        Assert.assertEquals(1, idleConnections.size());
+        Assert.assertEquals(0, activeConnections.size());
+    }
+
+    @Test
+    public void test_FailedRequest_RemovesConnection() throws Exception
+    {
+        start(new EmptyServerHandler());
+
+        String host = "localhost";
+        int port = connector.getLocalPort();
+        HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
+        ConnectionPool connectionPool = destination.getConnectionPool();
+
+        final BlockingQueue<Connection> idleConnections = connectionPool.getIdleConnections();
+        Assert.assertEquals(0, idleConnections.size());
+
+        final BlockingQueue<Connection> activeConnections = connectionPool.getActiveConnections();
+        Assert.assertEquals(0, activeConnections.size());
+
+        final CountDownLatch beginLatch = new CountDownLatch(1);
+        final CountDownLatch failureLatch = new CountDownLatch(2);
+        client.newRequest(host, port).scheme(scheme).listener(new Request.Listener.Adapter()
+        {
+            @Override
+            public void onBegin(Request request)
+            {
+                activeConnections.peek().close();
+                beginLatch.countDown();
+            }
+
+            @Override
+            public void onFailure(Request request, Throwable failure)
+            {
+                failureLatch.countDown();
+            }
+        }).send(new Response.Listener.Adapter()
+        {
+            @Override
+            public void onComplete(Result result)
+            {
+                Assert.assertTrue(result.isFailed());
+                Assert.assertEquals(0, idleConnections.size());
+                Assert.assertEquals(0, activeConnections.size());
+                failureLatch.countDown();
+            }
+        });
+
+        Assert.assertTrue(beginLatch.await(5, TimeUnit.SECONDS));
+        Assert.assertTrue(failureLatch.await(5, TimeUnit.SECONDS));
+
+        Assert.assertEquals(0, idleConnections.size());
+        Assert.assertEquals(0, activeConnections.size());
+    }
+
+    @Test
+    public void test_BadRequest_RemovesConnection() throws Exception
+    {
+        start(new EmptyServerHandler());
+
+        String host = "localhost";
+        int port = connector.getLocalPort();
+        HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
+        ConnectionPool connectionPool = destination.getConnectionPool();
+
+        final BlockingQueue<Connection> idleConnections = connectionPool.getIdleConnections();
+        Assert.assertEquals(0, idleConnections.size());
+
+        final BlockingQueue<Connection> activeConnections = connectionPool.getActiveConnections();
+        Assert.assertEquals(0, activeConnections.size());
+
+        final CountDownLatch successLatch = new CountDownLatch(3);
+        client.newRequest(host, port)
+                .scheme(scheme)
+                .listener(new Request.Listener.Adapter()
+                {
+                    @Override
+                    public void onBegin(Request request)
+                    {
+                        // Remove the host header, this will make the request invalid
+                        request.header(HttpHeader.HOST, null);
+                    }
+
+                    @Override
+                    public void onSuccess(Request request)
+                    {
+                        successLatch.countDown();
+                    }
+                })
+                .send(new Response.Listener.Adapter()
+                {
+                    @Override
+                    public void onSuccess(Response response)
+                    {
+                        Assert.assertEquals(400, response.getStatus());
+                        // 400 response also come with a Connection: close,
+                        // so the connection is closed and removed
+                        successLatch.countDown();
+                    }
+
+                    @Override
+                    public void onComplete(Result result)
+                    {
+                        Assert.assertFalse(result.isFailed());
+                        successLatch.countDown();
+                    }
+                });
+
+        Assert.assertTrue(successLatch.await(5, TimeUnit.SECONDS));
+
+        Assert.assertEquals(0, idleConnections.size());
+        Assert.assertEquals(0, activeConnections.size());
+    }
+
+    @Slow
+    @Test
+    public void test_BadRequest_WithSlowRequest_RemovesConnection() throws Exception
+    {
+        start(new EmptyServerHandler());
+
+        String host = "localhost";
+        int port = connector.getLocalPort();
+        HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
+        ConnectionPool connectionPool = destination.getConnectionPool();
+
+        final BlockingQueue<Connection> idleConnections = connectionPool.getIdleConnections();
+        Assert.assertEquals(0, idleConnections.size());
+
+        final BlockingQueue<Connection> activeConnections = connectionPool.getActiveConnections();
+        Assert.assertEquals(0, activeConnections.size());
+
+        final long delay = 1000;
+        final CountDownLatch successLatch = new CountDownLatch(3);
+        client.newRequest(host, port)
+                .scheme(scheme)
+                .listener(new Request.Listener.Adapter()
+                {
+                    @Override
+                    public void onBegin(Request request)
+                    {
+                        // Remove the host header, this will make the request invalid
+                        request.header(HttpHeader.HOST, null);
+                    }
+
+                    @Override
+                    public void onHeaders(Request request)
+                    {
+                        try
+                        {
+                            TimeUnit.MILLISECONDS.sleep(delay);
+                        }
+                        catch (InterruptedException e)
+                        {
+                            e.printStackTrace();
+                        }
+                    }
+
+                    @Override
+                    public void onSuccess(Request request)
+                    {
+                        successLatch.countDown();
+                    }
+                })
+                .send(new Response.Listener.Adapter()
+                {
+                    @Override
+                    public void onSuccess(Response response)
+                    {
+                        Assert.assertEquals(400, response.getStatus());
+                        // 400 response also come with a Connection: close,
+                        // so the connection is closed and removed
+                        successLatch.countDown();
+                    }
+
+                    @Override
+                    public void onComplete(Result result)
+                    {
+                        Assert.assertFalse(result.isFailed());
+                        successLatch.countDown();
+                    }
+                });
+
+        Assert.assertTrue(successLatch.await(delay * 5, TimeUnit.MILLISECONDS));
+
+        Assert.assertEquals(0, idleConnections.size());
+        Assert.assertEquals(0, activeConnections.size());
+    }
+
+    @Test
+    public void test_ConnectionFailure_RemovesConnection() throws Exception
+    {
+        start(new EmptyServerHandler());
+
+        String host = "localhost";
+        int port = connector.getLocalPort();
+        HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
+        ConnectionPool connectionPool = destination.getConnectionPool();
+
+        final BlockingQueue<Connection> idleConnections = connectionPool.getIdleConnections();
+        Assert.assertEquals(0, idleConnections.size());
+
+        final BlockingQueue<Connection> activeConnections = connectionPool.getActiveConnections();
+        Assert.assertEquals(0, activeConnections.size());
+
+        server.stop();
+
+        final CountDownLatch failureLatch = new CountDownLatch(2);
+        client.newRequest(host, port)
+                .scheme(scheme)
+                .onRequestFailure(new Request.FailureListener()
+                {
+                    @Override
+                    public void onFailure(Request request, Throwable failure)
+                    {
+                        failureLatch.countDown();
+                    }
+                })
+                .send(new Response.Listener.Adapter()
+                {
+                    @Override
+                    public void onComplete(Result result)
+                    {
+                        Assert.assertTrue(result.isFailed());
+                        failureLatch.countDown();
+                    }
+                });
+
+        Assert.assertTrue(failureLatch.await(5, TimeUnit.SECONDS));
+
+        Assert.assertEquals(0, idleConnections.size());
+        Assert.assertEquals(0, activeConnections.size());
+    }
+
+    @Test
+    public void test_ResponseWithConnectionCloseHeader_RemovesConnection() throws Exception
+    {
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                response.setHeader("Connection", "close");
+                baseRequest.setHandled(true);
+            }
+        });
+
+        String host = "localhost";
+        int port = connector.getLocalPort();
+        HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
+        ConnectionPool connectionPool = destination.getConnectionPool();
+
+        final BlockingQueue<Connection> idleConnections = connectionPool.getIdleConnections();
+        Assert.assertEquals(0, idleConnections.size());
+
+        final BlockingQueue<Connection> activeConnections = connectionPool.getActiveConnections();
+        Assert.assertEquals(0, activeConnections.size());
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        client.newRequest(host, port)
+                .scheme(scheme)
+                .send(new Response.Listener.Adapter()
+                {
+                    @Override
+                    public void onComplete(Result result)
+                    {
+                        Assert.assertFalse(result.isFailed());
+                        Assert.assertEquals(0, idleConnections.size());
+                        Assert.assertEquals(0, activeConnections.size());
+                        latch.countDown();
+                    }
+                });
+
+        Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+
+        Assert.assertEquals(0, idleConnections.size());
+        Assert.assertEquals(0, activeConnections.size());
+    }
+
+    @Test
+    public void test_BigRequestContent_ResponseWithConnectionCloseHeader_RemovesConnection() throws Exception
+    {
+        StdErrLog logger = StdErrLog.getLogger(org.eclipse.jetty.server.HttpConnection.class);
+        logger.setHideStacks(true);
+        try
+        {
+            start(new AbstractHandler()
+            {
+                @Override
+                public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+                {
+                    response.setHeader("Connection", "close");
+                    baseRequest.setHandled(true);
+                    // Don't read request content; this causes the server parser to be closed
+                }
+            });
+
+            String host = "localhost";
+            int port = connector.getLocalPort();
+            HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
+            ConnectionPool connectionPool = destination.getConnectionPool();
+
+            final BlockingQueue<Connection> idleConnections = connectionPool.getIdleConnections();
+            Assert.assertEquals(0, idleConnections.size());
+
+            final BlockingQueue<Connection> activeConnections = connectionPool.getActiveConnections();
+            Assert.assertEquals(0, activeConnections.size());
+
+            Log.getLogger(HttpConnection.class).info("Expecting java.lang.IllegalStateException: HttpParser{s=CLOSED,...");
+
+            final CountDownLatch latch = new CountDownLatch(1);
+            ByteBuffer buffer = ByteBuffer.allocate(16 * 1024 * 1024);
+            Arrays.fill(buffer.array(),(byte)'x');
+            client.newRequest(host, port)
+                    .scheme(scheme)
+                    .content(new ByteBufferContentProvider(buffer))
+                    .send(new Response.Listener.Adapter()
+                    {
+                        @Override
+                        public void onComplete(Result result)
+                        {
+                            Assert.assertEquals(1, latch.getCount());
+                            Assert.assertEquals(0, idleConnections.size());
+                            Assert.assertEquals(0, activeConnections.size());
+                            latch.countDown();
+                        }
+                    });
+
+            Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+
+            Assert.assertEquals(0, idleConnections.size());
+            Assert.assertEquals(0, activeConnections.size());
+            
+            server.stop();
+        }
+        finally
+        {
+            logger.setHideStacks(false);
+        }
+    }
+
+    @Slow
+    @Test
+    public void test_IdleConnection_IsClosed_OnRemoteClose() throws Exception
+    {
+        start(new EmptyServerHandler());
+
+        String host = "localhost";
+        int port = connector.getLocalPort();
+        HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
+        ConnectionPool connectionPool = destination.getConnectionPool();
+
+        final BlockingQueue<Connection> idleConnections = connectionPool.getIdleConnections();
+        Assert.assertEquals(0, idleConnections.size());
+
+        final BlockingQueue<Connection> activeConnections = connectionPool.getActiveConnections();
+        Assert.assertEquals(0, activeConnections.size());
+
+        ContentResponse response = client.newRequest(host, port)
+                .scheme(scheme)
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        Assert.assertEquals(200, response.getStatus());
+
+        connector.stop();
+
+        // Give the connection some time to process the remote close
+        TimeUnit.SECONDS.sleep(1);
+
+        Assert.assertEquals(0, idleConnections.size());
+        Assert.assertEquals(0, activeConnections.size());
+    }
+
+    @Test
+    public void testConnectionForHTTP10ResponseIsRemoved() throws Exception
+    {
+        start(new EmptyServerHandler());
+
+        String host = "localhost";
+        int port = connector.getLocalPort();
+        HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
+        ConnectionPool connectionPool = destination.getConnectionPool();
+
+        final BlockingQueue<Connection> idleConnections = connectionPool.getIdleConnections();
+        Assert.assertEquals(0, idleConnections.size());
+
+        final BlockingQueue<Connection> activeConnections = connectionPool.getActiveConnections();
+        Assert.assertEquals(0, activeConnections.size());
+
+        client.setStrictEventOrdering(false);
+        ContentResponse response = client.newRequest(host, port)
+                .scheme(scheme)
+                .onResponseBegin(new Response.BeginListener()
+                {
+                    @Override
+                    public void onBegin(Response response)
+                    {
+                        // Simulate a HTTP 1.0 response has been received.
+                        ((HttpResponse)response).version(HttpVersion.HTTP_1_0);
+                    }
+                })
+                .send();
+
+        Assert.assertEquals(200, response.getStatus());
+
+        Assert.assertEquals(0, idleConnections.size());
+        Assert.assertEquals(0, activeConnections.size());
+    }
+}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpCookieTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpCookieTest.java
new file mode 100644
index 0000000..5fa3806
--- /dev/null
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpCookieTest.java
@@ -0,0 +1,154 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.io.IOException;
+import java.net.HttpCookie;
+import java.net.URI;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import javax.servlet.ServletException;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class HttpCookieTest extends AbstractHttpClientServerTest
+{
+    public HttpCookieTest(SslContextFactory sslContextFactory)
+    {
+        super(sslContextFactory);
+    }
+
+    @Test
+    public void test_CookieIsStored() throws Exception
+    {
+        final String name = "foo";
+        final String value = "bar";
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                response.addCookie(new Cookie(name, value));
+                baseRequest.setHandled(true);
+            }
+        });
+
+        String host = "localhost";
+        int port = connector.getLocalPort();
+        String path = "/path";
+        String uri = scheme + "://" + host + ":" + port + path;
+        Response response = client.GET(uri);
+        Assert.assertEquals(200, response.getStatus());
+
+        List<HttpCookie> cookies = client.getCookieStore().get(URI.create(uri));
+        Assert.assertNotNull(cookies);
+        Assert.assertEquals(1, cookies.size());
+        HttpCookie cookie = cookies.get(0);
+        Assert.assertEquals(name, cookie.getName());
+        Assert.assertEquals(value, cookie.getValue());
+    }
+
+    @Test
+    public void test_CookieIsSent() throws Exception
+    {
+        final String name = "foo";
+        final String value = "bar";
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                Cookie[] cookies = request.getCookies();
+                Assert.assertNotNull(cookies);
+                Assert.assertEquals(1, cookies.length);
+                Cookie cookie = cookies[0];
+                Assert.assertEquals(name, cookie.getName());
+                Assert.assertEquals(value, cookie.getValue());
+            }
+        });
+
+        String host = "localhost";
+        int port = connector.getLocalPort();
+        String path = "/path";
+        String uri = scheme + "://" + host + ":" + port;
+        HttpCookie cookie = new HttpCookie(name, value);
+        client.getCookieStore().add(URI.create(uri), cookie);
+
+        Response response = client.GET(scheme + "://" + host + ":" + port + path);
+        Assert.assertEquals(200, response.getStatus());
+    }
+
+    @Test
+    public void test_CookieWithoutValue() throws Exception
+    {
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                response.addHeader("Set-Cookie", "");
+            }
+        });
+
+        ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .send();
+        Assert.assertEquals(200, response.getStatus());
+        Assert.assertTrue(client.getCookieStore().getCookies().isEmpty());
+    }
+
+    @Test
+    public void test_PerRequestCookieIsSent() throws Exception
+    {
+        final String name = "foo";
+        final String value = "bar";
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                Cookie[] cookies = request.getCookies();
+                Assert.assertNotNull(cookies);
+                Assert.assertEquals(1, cookies.length);
+                Cookie cookie = cookies[0];
+                Assert.assertEquals(name, cookie.getName());
+                Assert.assertEquals(value, cookie.getValue());
+            }
+        });
+
+        ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .cookie(new HttpCookie(name, value))
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+        Assert.assertEquals(200, response.getStatus());
+    }
+}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpDestinationQueueTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpDestinationQueueTest.java
deleted file mode 100644
index 9620a75..0000000
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpDestinationQueueTest.java
+++ /dev/null
@@ -1,225 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client;
-
-import java.net.ServerSocket;
-import java.net.Socket;
-import java.util.concurrent.RejectedExecutionException;
-
-import org.junit.Assert;
-import org.junit.BeforeClass;
-import org.junit.Test;
-
-public class HttpDestinationQueueTest
-{
-    private static HttpClient _httpClient;
-    private static long _timeout = 200;
-
-    @BeforeClass
-    public static void beforeOnce() throws Exception
-    {
-        _httpClient = new HttpClient();
-        _httpClient.setMaxConnectionsPerAddress(1);
-        _httpClient.setMaxQueueSizePerAddress(1);
-        _httpClient.setTimeout(_timeout);
-        _httpClient.start();
-    }
-
-    @Test
-    public void testDestinationMaxQueueSize() throws Exception
-    {
-        ServerSocket server = new ServerSocket(0);
-
-        // This will keep the connection busy
-        HttpExchange exchange1 = new HttpExchange();
-        exchange1.setMethod("GET");
-        exchange1.setURL("http://localhost:" + server.getLocalPort() + "/exchange1");
-        _httpClient.send(exchange1);
-
-        // Read request so we are sure that this exchange is out of the queue
-        Socket socket = server.accept();
-        byte[] buffer = new byte[1024];
-        StringBuilder request = new StringBuilder();
-        while (true)
-        {
-            int read = socket.getInputStream().read(buffer);
-            request.append(new String(buffer,0,read,"UTF-8"));
-            if (request.toString().endsWith("\r\n\r\n"))
-                break;
-        }
-        Assert.assertTrue(request.toString().contains("exchange1"));
-
-        // This will be queued
-        HttpExchange exchange2 = new HttpExchange();
-        exchange2.setMethod("GET");
-        exchange2.setURL("http://localhost:" + server.getLocalPort() + "/exchange2");
-        _httpClient.send(exchange2);
-
-        // This will be rejected, since the connection is busy and the queue is full
-        HttpExchange exchange3 = new HttpExchange();
-        exchange3.setMethod("GET");
-        exchange3.setURL("http://localhost:" + server.getLocalPort() + "/exchange3");
-        try
-        {
-            _httpClient.send(exchange3);
-            Assert.fail();
-        }
-        catch (RejectedExecutionException x)
-        {
-            // Expected
-        }
-
-        // Send the response to avoid exceptions in the console
-        socket.getOutputStream().write("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n".getBytes("UTF-8"));
-        Assert.assertEquals(HttpExchange.STATUS_COMPLETED,exchange1.waitForDone());
-
-        // Be sure that the second exchange can be sent
-        request.setLength(0);
-        while (true)
-        {
-            int read = socket.getInputStream().read(buffer);
-            request.append(new String(buffer,0,read,"UTF-8"));
-            if (request.toString().endsWith("\r\n\r\n"))
-                break;
-        }
-        Assert.assertTrue(request.toString().contains("exchange2"));
-
-        socket.getOutputStream().write("HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n".getBytes("UTF-8"));
-        socket.close();
-        Assert.assertEquals(HttpExchange.STATUS_COMPLETED,exchange2.waitForDone());
-
-        server.close();
-    }
-
-    @Test
-    public void testDefaultTimeoutIncludesQueuingExchangeExpiresInQueue() throws Exception
-    {
-
-        ServerSocket server = new ServerSocket(0);
-
-        // This will keep the connection busy
-        HttpExchange exchange1 = new HttpExchange();
-        exchange1.setTimeout(_timeout * 3); // Be sure it does not expire
-        exchange1.setMethod("GET");
-        exchange1.setURL("http://localhost:" + server.getLocalPort() + "/exchange1");
-        _httpClient.send(exchange1);
-
-        // Read request so we are sure that this exchange is out of the queue
-        Socket socket = server.accept();
-        byte[] buffer = new byte[1024];
-        StringBuilder request = new StringBuilder();
-        while (true)
-        {
-            int read = socket.getInputStream().read(buffer);
-            request.append(new String(buffer,0,read,"UTF-8"));
-            if (request.toString().endsWith("\r\n\r\n"))
-                break;
-        }
-        Assert.assertTrue(request.toString().contains("exchange1"));
-
-        // This will be queued
-        HttpExchange exchange2 = new HttpExchange();
-        exchange2.setMethod("GET");
-        exchange2.setURL("http://localhost:" + server.getLocalPort() + "/exchange2");
-        _httpClient.send(exchange2);
-
-        // Wait until the queued exchange times out in the queue
-        Thread.sleep(_timeout * 2);
-
-        Assert.assertEquals(HttpExchange.STATUS_EXPIRED,exchange2.getStatus());
-
-        // Send the response to the first exchange to avoid exceptions in the console
-        socket.getOutputStream().write("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n".getBytes("UTF-8"));
-        Assert.assertEquals(HttpExchange.STATUS_COMPLETED,exchange1.waitForDone());
-        socket.close();
-
-        server.close();
-    }
-
-    @Test
-    public void testDefaultTimeoutIncludesQueuingExchangeExpiresDuringRequest() throws Exception
-    {
-        ServerSocket server = new ServerSocket(0);
-
-        HttpExchange exchange1 = new HttpExchange();
-        exchange1.setMethod("GET");
-        exchange1.setURL("http://localhost:" + server.getLocalPort() + "/exchange1");
-        _httpClient.send(exchange1);
-
-        // Read request so we are sure that this exchange is out of the queue
-        Socket socket = server.accept();
-        byte[] buffer = new byte[1024];
-        StringBuilder request = new StringBuilder();
-        while (true)
-        {
-            int read = socket.getInputStream().read(buffer);
-            request.append(new String(buffer,0,read,"UTF-8"));
-            if (request.toString().endsWith("\r\n\r\n"))
-                break;
-        }
-        Assert.assertTrue(request.toString().contains("exchange1"));
-
-        // Wait until the exchange times out during the request
-        Thread.sleep(_timeout * 2);
-
-        Assert.assertEquals(HttpExchange.STATUS_EXPIRED,exchange1.getStatus());
-
-        socket.close();
-
-        server.close();
-    }
-
-    @Test
-    public void testExchangeTimeoutIncludesQueuingExchangeExpiresDuringResponse() throws Exception
-    {
-        ServerSocket server = new ServerSocket(0);
-
-        long timeout = 1000;
-        HttpExchange exchange1 = new HttpExchange();
-        exchange1.setTimeout(timeout);
-        exchange1.setMethod("GET");
-        exchange1.setURL("http://localhost:" + server.getLocalPort() + "/exchange1");
-        _httpClient.send(exchange1);
-
-        // Read request so we are sure that this exchange is out of the queue
-        Socket socket = server.accept();
-        byte[] buffer = new byte[1024];
-        StringBuilder request = new StringBuilder();
-        while (true)
-        {
-            int read = socket.getInputStream().read(buffer);
-            request.append(new String(buffer,0,read,"UTF-8"));
-            if (request.toString().endsWith("\r\n\r\n"))
-                break;
-        }
-        Assert.assertTrue(request.toString().contains("exchange1"));
-
-        // Write part of the response
-        socket.getOutputStream().write("HTTP/1.1 200 OK\r\nContent-Type: application/octet-stream\r\nContent-Length: 1\r\n\r\n".getBytes("UTF-8"));
-
-        // Wait until the exchange times out during the response
-        Thread.sleep(timeout * 2);
-
-        Assert.assertEquals(HttpExchange.STATUS_EXPIRED,exchange1.getStatus());
-
-        socket.close();
-
-        server.close();
-    }
-}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpExchangeTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpExchangeTest.java
deleted file mode 100644
index 918c113..0000000
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpExchangeTest.java
+++ /dev/null
@@ -1,695 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client;
-
-import static org.junit.Assert.*;
-import static org.junit.matchers.JUnitMatchers.*;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.URI;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicReference;
-
-import org.eclipse.jetty.client.helperClasses.HttpServerAndClientCreator;
-import org.eclipse.jetty.client.helperClasses.ServerAndClientCreator;
-import org.eclipse.jetty.client.security.ProxyAuthorization;
-import org.eclipse.jetty.http.HttpFields;
-import org.eclipse.jetty.http.HttpMethods;
-import org.eclipse.jetty.http.HttpStatus;
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.ByteArrayBuffer;
-import org.eclipse.jetty.io.Connection;
-import org.eclipse.jetty.io.EofException;
-import org.eclipse.jetty.io.nio.DirectNIOBuffer;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.toolchain.test.PropertyFlag;
-import org.eclipse.jetty.util.component.AbstractLifeCycle;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-/* ------------------------------------------------------------ */
-/**
- * Functional testing for HttpExchange.
- */
-public class HttpExchangeTest
-{
-    final static boolean verbose=HttpExchange.LOG.isDebugEnabled();
-    protected static int _maxConnectionsPerAddress = 2;
-    protected static String _scheme = "http";
-    protected static Server _server;
-    protected static int _port;
-    protected static HttpClient _httpClient;
-    protected static AtomicInteger _count = new AtomicInteger();
-    protected static ServerAndClientCreator serverAndClientCreator = new HttpServerAndClientCreator();
-
-    protected static URI getBaseURI()
-    {
-        return URI.create(_scheme + "://localhost:" + _port + "/");
-    }
-
-    /* ------------------------------------------------------------ */
-    // TODO work out why BeforeClass does not work here?
-    @Before
-    public void setUpOnce() throws Exception
-    {
-        _scheme = "http";
-        _server = serverAndClientCreator.createServer();
-        _httpClient = serverAndClientCreator.createClient(3000L,3500L,2000);
-        _port = _server.getConnectors()[0].getLocalPort();
-    }
-
-    /* ------------------------------------------------------------ */
-    @After
-    public void tearDownOnce() throws Exception
-    {
-        _httpClient.stop();
-        long startTime = System.currentTimeMillis();
-        while (!_httpClient.getState().equals(AbstractLifeCycle.STOPPED))
-        {
-            if (System.currentTimeMillis() - startTime > 1000)
-                break;
-            Thread.sleep(5);
-        }
-        _server.stop();
-    }
-
-    /* ------------------------------------------------------------ */
-    @Test
-    public void testResetNewExchange() throws Exception
-    {
-        HttpExchange exchange = new HttpExchange();
-        exchange.reset();
-    }
-
-    /* ------------------------------------------------------------ */
-    @Test
-    public void testPerf() throws Exception
-    {
-        sender(1,false);
-        sender(1,true);
-        sender(10,false);
-        sender(10,true);
-
-        if (PropertyFlag.isEnabled("test.stress"))
-        {
-            sender(100,false);
-            sender(100,true);
-            sender(10000,false);
-            sender(10000,true);
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Test sending data through the exchange.
-     *
-     * @throws IOException
-     */
-    public void sender(final int nb, final boolean close) throws Exception
-    {
-        // System.err.printf("%nSENDER %d %s%n",nb,close);
-        _count.set(0);
-        final CountDownLatch complete = new CountDownLatch(nb);
-        final AtomicInteger allcontent = new AtomicInteger(nb);
-        HttpExchange[] httpExchange = new HttpExchange[nb];
-        long start = System.currentTimeMillis();
-        for (int i = 0; i < nb; i++)
-        {
-            final int n = i;
-
-            httpExchange[n] = new HttpExchange()
-            {
-                String result = "pending";
-                int len = 0;
-
-                /* ------------------------------------------------------------ */
-                @Override
-                protected void onRequestCommitted()
-                {
-                    if (verbose)
-                        System.err.println(n+" [ "+this);
-                    result = "committed";
-                }
-
-                /* ------------------------------------------------------------ */
-                @Override
-                protected void onRequestComplete() throws IOException
-                {
-                    if (verbose)
-                        System.err.println(n+" [ ==");
-                    result = "sent";
-                }
-
-                @Override
-                /* ------------------------------------------------------------ */
-                protected void onResponseStatus(Buffer version, int status, Buffer reason)
-                {
-                    if (verbose)
-                        System.err.println(n+" ] "+version+" "+status+" "+reason);
-                    result = "status";
-                }
-
-                /* ------------------------------------------------------------ */
-                @Override
-                protected void onResponseHeader(Buffer name, Buffer value)
-                {
-                    if (verbose)
-                        System.err.println(n+" ] "+name+": "+value);
-                }
-
-                /* ------------------------------------------------------------ */
-                @Override
-                protected void onResponseHeaderComplete() throws IOException
-                {
-                    if (verbose)
-                        System.err.println(n+" ] -");
-                    result = "content";
-                    super.onResponseHeaderComplete();
-                }
-
-                /* ------------------------------------------------------------ */
-                @Override
-                protected void onResponseContent(Buffer content)
-                {
-                    len += content.length();
-                    if (verbose)
-                        System.err.println(n+" ] "+content.length()+" -> "+len);
-                }
-
-                /* ------------------------------------------------------------ */
-                @Override
-                protected void onResponseComplete()
-                {
-                    if (verbose)
-                        System.err.println(n+" ] == "+len+" "+complete.getCount()+"/"+nb);
-                    result = "complete";
-                    if (len == 2009)
-                        allcontent.decrementAndGet();
-                    else
-                        System.err.println(n+ " ONLY " + len+ "/2009");
-                    complete.countDown();
-                }
-
-                /* ------------------------------------------------------------ */
-                @Override
-                protected void onConnectionFailed(Throwable ex)
-                {
-                    if (verbose)
-                        System.err.println(n+" ] "+ex);
-                    complete.countDown();
-                    result = "failed";
-                    System.err.println(n+ " FAILED " + ex);
-                    super.onConnectionFailed(ex);
-                }
-
-                /* ------------------------------------------------------------ */
-                @Override
-                protected void onException(Throwable ex)
-                {
-                    if (verbose)
-                        System.err.println(n+" ] "+ex);
-                    complete.countDown();
-                    result = "excepted";
-                    System.err.println(n+ " EXCEPTED " + ex);
-                    super.onException(ex);
-                }
-
-                /* ------------------------------------------------------------ */
-                @Override
-                protected void onExpire()
-                {
-                    if (verbose)
-                        System.err.println(n+" ] expired");
-                    complete.countDown();
-                    result = "expired";
-                    System.err.println(n + " EXPIRED " + len);
-                    super.onExpire();
-                }
-
-                /* ------------------------------------------------------------ */
-                @Override
-                public String toString()
-                {
-                    return n+"/"+result+"/"+len+"/"+super.toString();
-                }
-            };
-
-            httpExchange[n].setURI(getBaseURI().resolve("/" + n));
-            httpExchange[n].addRequestHeader("arbitrary","value");
-            if (close)
-                httpExchange[n].setRequestHeader("Connection","close");
-
-            _httpClient.send(httpExchange[n]);
-        }
-
-        if (!complete.await(2,TimeUnit.SECONDS))
-            System.err.println(_httpClient.dump());
-
-        assertTrue(complete.await(20,TimeUnit.SECONDS));
-
-        assertEquals("nb="+nb+" close="+close,0,allcontent.get());
-    }
-
-    /* ------------------------------------------------------------ */
-    @Test
-    public void testPostWithContentExchange() throws Exception
-    {
-        for (int i=0;i<20;i++)
-        {
-            ContentExchange httpExchange=new ContentExchange();
-            httpExchange.setURI(getBaseURI());
-            httpExchange.setMethod(HttpMethods.POST);
-            httpExchange.setRequestContent(new ByteArrayBuffer("<hello />"));
-            _httpClient.send(httpExchange);
-            int status = httpExchange.waitForDone();
-            //httpExchange.waitForStatus(HttpExchange.STATUS_COMPLETED);
-            String result=httpExchange.getResponseContent();
-            assertEquals(HttpExchange.STATUS_COMPLETED, status);
-            assertEquals("i="+i,"<hello />",result);
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    @Test
-    public void testGetWithContentExchange() throws Exception
-    {
-        for (int i=0;i<10;i++)
-        {
-            ContentExchange httpExchange=new ContentExchange();
-            URI uri = getBaseURI().resolve("?i=" + i);
-            httpExchange.setURI(uri);
-            httpExchange.setMethod(HttpMethods.GET);
-            _httpClient.send(httpExchange);
-            int status = httpExchange.waitForDone();
-            //httpExchange.waitForStatus(HttpExchange.STATUS_COMPLETED);
-            String result=httpExchange.getResponseContent();
-            assertNotNull("Should have received response content", result);
-            assertEquals("i="+i,0,result.indexOf("<hello>"));
-            assertEquals("i="+i,result.length()-10,result.indexOf("</hello>"));
-            assertEquals(HttpExchange.STATUS_COMPLETED, status);
-            Thread.sleep(5);
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    @Test
-    public void testLocalAddressAvailabilityWithContentExchange() throws Exception
-    {
-        for (int i=0;i<10;i++)
-        {
-            ContentExchange httpExchange=new ContentExchange();
-            URI uri = getBaseURI().resolve("?i=" + i);
-            httpExchange.setURI(uri);
-            httpExchange.setMethod(HttpMethods.GET);
-            _httpClient.send(httpExchange);
-            int status = httpExchange.waitForDone();
-
-            assertNotNull(httpExchange.getLocalAddress());
-
-            String result=httpExchange.getResponseContent();
-            assertNotNull("Should have received response content", result);
-            assertEquals("i="+i,0,result.indexOf("<hello>"));
-            assertEquals("i="+i,result.length()-10,result.indexOf("</hello>"));
-            assertEquals(HttpExchange.STATUS_COMPLETED, status);
-            Thread.sleep(5);
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    @Test
-    public void testShutdownWithExchange() throws Exception
-    {
-        final AtomicReference<Throwable> throwable=new AtomicReference<Throwable>();
-
-        HttpExchange httpExchange=new HttpExchange()
-        {
-
-            /* ------------------------------------------------------------ */
-            /**
-             * @see org.eclipse.jetty.client.HttpExchange#onException(java.lang.Throwable)
-             */
-            @Override
-            protected void onException(Throwable x)
-            {
-                throwable.set(x);
-            }
-
-            /* ------------------------------------------------------------ */
-            /**
-             * @see org.eclipse.jetty.client.HttpExchange#onConnectionFailed(java.lang.Throwable)
-             */
-            @Override
-            protected void onConnectionFailed(Throwable x)
-            {
-                throwable.set(x);
-            }
-        };
-        httpExchange.setURI(getBaseURI());
-        httpExchange.setMethod("SLEEP");
-        _httpClient.send(httpExchange);
-        new Thread()
-        {
-            @Override
-            public void run()
-            {
-                try {
-                    Thread.sleep(500);
-                    _httpClient.stop();
-                } catch(Exception e) {e.printStackTrace();}
-            }
-        }.start();
-        int status = httpExchange.waitForDone();
-
-        System.err.println(throwable.get());
-        assertTrue(throwable.get().toString().indexOf("close")>=0);
-        assertEquals(HttpExchange.STATUS_EXCEPTED, status);
-        _httpClient.start();
-    }
-
-    /* ------------------------------------------------------------ */
-    @Test
-    public void testBigPostWithContentExchange() throws Exception
-    {
-        int size =32;
-        ContentExchange httpExchange=new ContentExchange()
-        {
-            int total;
-
-            @Override
-            protected synchronized void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException
-            {
-                if (verbose)
-                    System.err.println("] "+version+" "+status+" "+reason);
-                super.onResponseStatus(version,status,reason);
-            }
-
-            @Override
-            protected synchronized void onResponseHeader(Buffer name, Buffer value) throws IOException
-            {
-                if (verbose)
-                    System.err.println("] "+name+": "+value);
-                super.onResponseHeader(name,value);
-            }
-
-            @Override
-            protected synchronized void onResponseContent(Buffer content) throws IOException
-            {
-                if (verbose)
-                {
-                    total+=content.length();
-                    System.err.println("] "+content.length()+" -> "+total);
-                }
-                super.onResponseContent(content);
-            }
-
-            @Override
-            protected void onRequestComplete() throws IOException
-            {
-                if (verbose)
-                    System.err.println("] ==");
-                super.onRequestComplete();
-            }
-
-            @Override
-            protected void onResponseHeaderComplete() throws IOException
-            {
-                if (verbose)
-                    System.err.println("] --");
-                super.onResponseHeaderComplete();
-            }
-
-        };
-
-        Buffer babuf = new ByteArrayBuffer(size*36*1024);
-        Buffer niobuf = new DirectNIOBuffer(size*36*1024);
-
-        byte[] bytes="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ".getBytes();
-
-        for (int i=0;i<size*1024;i++)
-        {
-            babuf.put(bytes);
-            niobuf.put(bytes);
-        }
-
-        httpExchange.setURI(getBaseURI());
-        httpExchange.setMethod(HttpMethods.POST);
-        httpExchange.setRequestContentType("application/data");
-        httpExchange.setRequestContent(babuf);
-        _httpClient.send(httpExchange);
-
-        long start=System.currentTimeMillis();
-        while(!httpExchange.isDone())
-        {
-            long now=System.currentTimeMillis();
-            if ((now-start)>=10000)
-            {
-                System.err.println("TEST IS TAKING TOOOOO LONG!!!!!!!!!!!!!!!!!!!!");
-                System.err.println("CLIENT:");
-                System.err.println(_httpClient.dump());
-                System.err.println("SERVER:");
-                _server.dumpStdErr();
-                break;
-            }
-            Thread.sleep(100);
-        }
-        int status = httpExchange.waitForDone();
-        assertEquals(HttpExchange.STATUS_COMPLETED,status);
-        String result=httpExchange.getResponseContent();
-        assertEquals(babuf.length(),result.length());
-
-        httpExchange.reset();
-        httpExchange.setURI(getBaseURI());
-        httpExchange.setMethod(HttpMethods.POST);
-        httpExchange.setRequestContentType("application/data");
-        httpExchange.setRequestContent(niobuf);
-        _httpClient.send(httpExchange);
-
-        start=System.currentTimeMillis();
-        while(!httpExchange.isDone())
-        {
-            long now=System.currentTimeMillis();
-            if ((now-start)>=10000)
-            {
-                System.err.println("TEST IS TAKING TOOOOO LONG!!!!!!!!!!!!!!!!!!!!");
-                System.err.println("CLIENT:");
-                System.err.println(_httpClient.dump());
-                System.err.println("SERVER:");
-                _server.dumpStdErr();
-                break;
-            }
-            Thread.sleep(100);
-        }
-        status = httpExchange.waitForDone();
-        assertEquals(HttpExchange.STATUS_COMPLETED, status);
-        result=httpExchange.getResponseContent();
-        assertEquals(niobuf.length(),result.length());
-    }
-
-    /* ------------------------------------------------------------ */
-    @Test
-    public void testSlowPost() throws Exception
-    {
-        ContentExchange httpExchange=new ContentExchange();
-        httpExchange.setURI(getBaseURI());
-        httpExchange.setMethod(HttpMethods.POST);
-
-        final String data="012345678901234567890123456789012345678901234567890123456789";
-
-        InputStream content = new InputStream()
-        {
-            int _index=0;
-
-            @Override
-            public int read() throws IOException
-            {
-                // System.err.printf("reading 1 of %d/%d%n",_index,data.length());
-                if (_index>=data.length())
-                    return -1;
-
-                try
-                {
-                    Thread.sleep(5);
-                }
-                catch (InterruptedException e)
-                {
-                    e.printStackTrace();
-                }
-
-                // System.err.printf("read 1%n");
-                return data.charAt(_index++);
-            }
-
-            @Override
-            public int read(byte[] b, int off, int len) throws IOException
-            {
-                // System.err.printf("reading %d of %d/%d%n",len,_index,data.length());
-                if (_index >= data.length())
-                    return -1;
-
-                try
-                {
-                    Thread.sleep(25);
-                }
-                catch (InterruptedException e)
-                {
-                    e.printStackTrace();
-                }
-
-                int l = 0;
-
-                while (l < 5 && _index < data.length() && l < len)
-                    b[off + l++] = (byte)data.charAt(_index++);
-                // System.err.printf("read %d%n",l);
-                return l;
-            }
-
-        };
-
-        httpExchange.setRequestContentSource(content);
-
-        _httpClient.send(httpExchange);
-
-        int status = httpExchange.waitForDone();
-        String result = httpExchange.getResponseContent();
-        assertEquals(HttpExchange.STATUS_COMPLETED,status);
-        assertEquals(data,result);
-    }
-
-    /* ------------------------------------------------------------ */
-    @Test
-    public void testProxy() throws Exception
-    {
-        if (_scheme.equals("https"))
-            return;
-        try
-        {
-            _httpClient.setProxy(new Address("127.0.0.1",_port));
-            _httpClient.setProxyAuthentication(new ProxyAuthorization("user","password"));
-
-            ContentExchange httpExchange=new ContentExchange();
-            httpExchange.setAddress(new Address("jetty.eclipse.org",8080));
-            httpExchange.setMethod(HttpMethods.GET);
-            httpExchange.setRequestURI("/jetty-6");
-            _httpClient.send(httpExchange);
-            int status = httpExchange.waitForDone();
-            //httpExchange.waitForStatus(HttpExchange.STATUS_COMPLETED);
-            String result=httpExchange.getResponseContent();
-            assertNotNull("Should have received response content", result);
-            result=result.trim();
-            assertEquals(HttpExchange.STATUS_COMPLETED, status);
-            assertTrue(result.startsWith("Proxy request: http://jetty.eclipse.org:8080/jetty-6"));
-            assertTrue(result.endsWith("Basic dXNlcjpwYXNzd29yZA=="));
-        }
-        finally
-        {
-            _httpClient.setProxy(null);
-        }
-    }
-
-
-    /* ------------------------------------------------------------ */
-    @Test
-    public void testReserveConnections () throws Exception
-    {
-        _httpClient = serverAndClientCreator.createClient(3000L,3500L,2000);
-        final HttpDestination destination = _httpClient.getDestination(new Address("localhost",_port),_scheme.equalsIgnoreCase("https"));
-        final org.eclipse.jetty.client.AbstractHttpConnection[] connections = new org.eclipse.jetty.client.AbstractHttpConnection[_maxConnectionsPerAddress];
-        for (int i = 0; i < _maxConnectionsPerAddress; i++)
-        {
-            connections[i] = destination.reserveConnection(200);
-            assertNotNull(connections[i]);
-            HttpExchange ex = new ContentExchange();
-            ex.setURI(getBaseURI().resolve("?i=" + i));
-            ex.setMethod(HttpMethods.GET);
-            connections[i].send(ex);
-        }
-
-        // try to get a connection, and only wait 500ms, as we have
-        // already reserved the max, should return null
-        Connection c = destination.reserveConnection(500);
-        assertNull(c);
-
-        // unreserve first connection
-        destination.returnConnection(connections[0],false);
-
-        // reserving one should now work
-        c = destination.reserveConnection(500);
-        assertNotNull(c);
-
-        // release connections
-        for (AbstractHttpConnection httpConnection : connections){
-            destination.returnConnection(httpConnection,false);
-        }
-    }
-
-    @Test
-    public void testOptionsWithExchange() throws Exception
-    {
-        ContentExchange httpExchange = new ContentExchange(true);
-        httpExchange.setURL(getBaseURI().toASCIIString());
-        httpExchange.setRequestURI("*");
-        httpExchange.setMethod(HttpMethods.OPTIONS);
-    //    httpExchange.setRequestHeader("Connection","close");
-        _httpClient.send(httpExchange);
-
-        int state = httpExchange.waitForDone();
-        assertEquals(HttpExchange.STATUS_COMPLETED, state);
-        assertEquals(HttpStatus.OK_200,httpExchange.getResponseStatus());
-
-        HttpFields headers = httpExchange.getResponseFields();
-        HttpAsserts.assertContainsHeaderKey("Content-Length", headers);
-        assertEquals("Content-Length header value", 0, headers.getLongField("Content-Length"));
-
-        HttpAsserts.assertContainsHeaderKey("Allow",headers);
-        String allow = headers.getStringField("Allow");
-        String expectedMethods[] =
-        { "GET", "HEAD", "POST", "PUT", "DELETE", "MOVE", "OPTIONS", "TRACE" };
-        for (String expectedMethod : expectedMethods)
-        {
-            assertThat(allow,containsString(expectedMethod));
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    public static void copyStream(InputStream in, OutputStream out)
-    {
-        try
-        {
-            byte[] buffer = new byte[1024];
-            int len;
-            while ((len = in.read(buffer)) >= 0)
-            {
-                out.write(buffer,0,len);
-            }
-        }
-        catch (EofException e)
-        {
-            System.err.println("HttpExchangeTest#copyStream: " + e);
-        }
-        catch (IOException e)
-        {
-            e.printStackTrace();
-        }
-    }
-}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpGetRedirectTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpGetRedirectTest.java
deleted file mode 100644
index ff3f882..0000000
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpGetRedirectTest.java
+++ /dev/null
@@ -1,237 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client;
-
-import static org.junit.Assert.assertEquals;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.PrintWriter;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jetty.client.security.Realm;
-import org.eclipse.jetty.client.security.SimpleRealmResolver;
-import org.eclipse.jetty.http.HttpMethods;
-import org.eclipse.jetty.http.HttpStatus;
-import org.eclipse.jetty.server.Request;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.handler.AbstractHandler;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-
-/* ------------------------------------------------------------ */
-public class HttpGetRedirectTest
-{
-    private static String _content =
-        "Lorem ipsum dolor sit amet, consectetur adipiscing elit. In quis felis nunc. "+
-        "Quisque suscipit mauris et ante auctor ornare rhoncus lacus aliquet. Pellentesque "+
-        "habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. "+
-        "Vestibulum sit amet felis augue, vel convallis dolor. Cras accumsan vehicula diam "+
-        "at faucibus. Etiam in urna turpis, sed congue mi. Morbi et lorem eros. Donec vulputate "+
-        "velit in risus suscipit lobortis. Aliquam id urna orci, nec sollicitudin ipsum. "+
-        "Cras a orci turpis. Donec suscipit vulputate cursus. Mauris nunc tellus, fermentum "+
-        "eu auctor ut, mollis at diam. Quisque porttitor ultrices metus, vitae tincidunt massa "+
-        "sollicitudin a. Vivamus porttitor libero eget purus hendrerit cursus. Integer aliquam "+
-        "consequat mauris quis luctus. Cras enim nibh, dignissim eu faucibus ac, mollis nec neque. "+
-        "Aliquam purus mauris, consectetur nec convallis lacinia, porta sed ante. Suspendisse "+
-        "et cursus magna. Donec orci enim, molestie a lobortis eu, imperdiet vitae neque.";
-
-    private File _docRoot;
-    private Server _server;
-    private HttpClient _client;
-    private Realm _realm;
-    private String _protocol;
-    private String _requestUrl;
-    private String _requestUrl2;
-    private RedirectHandler _handler;
-
-    /* ------------------------------------------------------------ */
-    @Before
-    public void setUp()
-        throws Exception
-    {
-        _docRoot = new File("target/test-output/docroot/");
-        _docRoot.mkdirs();
-        _docRoot.deleteOnExit();
-
-        _server = new Server();
-        configureServer(_server);
-        org.eclipse.jetty.server.bio.SocketConnector connector = new org.eclipse.jetty.server.bio.SocketConnector();
-        _server.addConnector(connector);
-        _server.start();
-
-        int port = _server.getConnectors()[0].getLocalPort();
-        _requestUrl = _protocol+"://localhost:"+port+ "/content.txt";
-        
-        _handler._toURL=_protocol+"://localhost:"+connector.getLocalPort()+ "/moved.txt";
-    }
-
-    /* ------------------------------------------------------------ */
-    @After
-    public void tearDown()
-        throws Exception
-    {
-        if (_server != null)
-        {
-            _server.stop();
-            _server = null;
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    @Test
-    public void testGet() throws Exception
-    {
-        startClient(_realm);
-        
-        ContentExchange getExchange = new ContentExchange();
-        getExchange.setURL(_requestUrl);
-        getExchange.setMethod(HttpMethods.GET);
-
-        _client.send(getExchange);
-        int state = getExchange.waitForDone();
-
-        String content = "";
-        int responseStatus = getExchange.getResponseStatus();
-        if (responseStatus == HttpStatus.OK_200)
-        {
-            content = getExchange.getResponseContent();
-        }
-
-        assertEquals(HttpStatus.OK_200,responseStatus);
-        assertEquals(_content,content);
-        
-        stopClient();
-    }
-
-    /* ------------------------------------------------------------ */
-    protected void configureServer(Server server)
-        throws Exception
-    {
-        setProtocol("http");
-
-        SelectChannelConnector connector = new SelectChannelConnector();
-        server.addConnector(connector);
-
-        _handler = new RedirectHandler(HttpStatus.MOVED_PERMANENTLY_301, "/content.txt", "WAIT FOR IT", 2);
-        server.setHandler( _handler );
-
-    }
-
-    /* ------------------------------------------------------------ */
-    protected void startClient(Realm realm)
-        throws Exception
-    {
-        _client = new HttpClient();
-        _client.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);
-        _client.registerListener("org.eclipse.jetty.client.RedirectListener");
-        if (realm != null)
-            _client.setRealmResolver(new SimpleRealmResolver(realm));
-        _client.start();
-    }
-
-    /* ------------------------------------------------------------ */
-    protected void stopClient()
-        throws Exception
-    {
-        if (_client != null)
-        {
-            _client.stop();
-            _client = null;
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    protected String getBasePath()
-    {
-        return _docRoot.getAbsolutePath();
-    }
-
-    /* ------------------------------------------------------------ */
-    protected void setProtocol(String protocol)
-    {
-        _protocol = protocol;
-    }
-
-    /* ------------------------------------------------------------ */
-    protected void setRealm(Realm realm)
-    {
-        _realm = realm;
-    }
-
-
-    /* ------------------------------------------------------------ */
-    private static class RedirectHandler
-        extends AbstractHandler
-    {
-        private final String _fromURI;
-        private final int _code;
-        private final int _maxRedirects;
-        private int _redirectCount = 0;
-        private String _toURL;
-
-        /* ------------------------------------------------------------ */
-        public RedirectHandler( final int code, final String fromURI, final String toURL, final int maxRedirects )
-        {
-            this._code = code;
-            this._fromURI = fromURI;
-            this._toURL = toURL;
-            this._maxRedirects = maxRedirects;
-            
-            if (_fromURI==null || _toURL==null)
-                throw new IllegalArgumentException();
-            
-        }
-
-        /* ------------------------------------------------------------ */
-        public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
-            throws IOException, ServletException
-        {
-            if ( baseRequest.isHandled() )
-            {
-                return;
-            }
-
-            if (request.getRequestURI().equals(_fromURI))
-            {
-                _redirectCount++;
-
-                String location = ( _redirectCount <= _maxRedirects )?_fromURI:_toURL;
-
-                response.setStatus( _code );
-                response.setHeader( "Location", location );
-                
-                ( (Request) request ).setHandled( true );
-            }
-            else
-            {
-                PrintWriter out = response.getWriter();
-                out.write(_content);
-
-                baseRequest.setHandled( true );
-            }
-        }
-    }
-}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpHeadersTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpHeadersTest.java
deleted file mode 100644
index 2b47f72..0000000
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpHeadersTest.java
+++ /dev/null
@@ -1,193 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.Map;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jetty.http.HttpMethods;
-import org.eclipse.jetty.http.HttpStatus;
-import org.eclipse.jetty.server.Connector;
-import org.eclipse.jetty.server.Request;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.handler.AbstractHandler;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-public class HttpHeadersTest
-{
-    private static final Logger LOG = Log.getLogger(HttpHeadersTest.class);
-
-    private static final String CONTENT = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. In quis felis nunc. "
-            + "Quisque suscipit mauris et ante auctor ornare rhoncus lacus aliquet. Pellentesque "
-            + "habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. "
-            + "Vestibulum sit amet felis augue, vel convallis dolor. Cras accumsan vehicula diam "
-            + "at faucibus. Etiam in urna turpis, sed congue mi. Morbi et lorem eros. Donec vulputate "
-            + "velit in risus suscipit lobortis. Aliquam id urna orci, nec sollicitudin ipsum. "
-            + "Cras a orci turpis. Donec suscipit vulputate cursus. Mauris nunc tellus, fermentum "
-            + "eu auctor ut, mollis at diam. Quisque porttitor ultrices metus, vitae tincidunt massa "
-            + "sollicitudin a. Vivamus porttitor libero eget purus hendrerit cursus. Integer aliquam "
-            + "consequat mauris quis luctus. Cras enim nibh, dignissim eu faucibus ac, mollis nec neque. "
-            + "Aliquam purus mauris, consectetur nec convallis lacinia, porta sed ante. Suspendisse "
-            + "et cursus magna. Donec orci enim, molestie a lobortis eu, imperdiet vitae neque.";
-
-    private Server _server;
-    private TestHeaderHandler _handler;
-    private int _port;
-
-    @Before
-    public void init() throws Exception
-    {
-        File docRoot = new File("target/test-output/docroot/");
-        if (!docRoot.exists())
-            assertTrue(docRoot.mkdirs());
-        docRoot.deleteOnExit();
-
-        _server = new Server();
-        Connector connector = new SelectChannelConnector();
-        _server.addConnector(connector);
-
-        _handler = new TestHeaderHandler();
-        _server.setHandler(_handler);
-
-        _server.start();
-
-        _port = connector.getLocalPort();
-    }
-
-    @After
-    public void destroy() throws Exception
-    {
-        _server.stop();
-        _server.join();
-    }
-
-    @Test
-    public void testHttpHeaders() throws Exception
-    {
-        HttpClient httpClient = new HttpClient();
-        httpClient.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);
-        httpClient.start();
-        try
-        {
-            String requestUrl = "http://localhost:" + _port + "/header";
-
-            ContentExchange exchange = new ContentExchange();
-            exchange.setURL(requestUrl);
-            exchange.setMethod(HttpMethods.GET);
-            exchange.addRequestHeader("User-Agent","Jetty-Client/7.0");
-
-            httpClient.send(exchange);
-
-            int state = exchange.waitForDone();
-            assertEquals(HttpExchange.STATUS_COMPLETED, state);
-            int responseStatus = exchange.getResponseStatus();
-            assertEquals(HttpStatus.OK_200,responseStatus);
-
-            String content = exchange.getResponseContent();
-
-            assertEquals(HttpHeadersTest.CONTENT,content);
-            assertEquals("Jetty-Client/7.0",_handler.headers.get("User-Agent"));
-        }
-        finally
-        {
-            httpClient.stop();
-        }
-    }
-    
-    @Test
-    public void testHttpHeadersSize() throws Exception
-    {
-        HttpClient httpClient = new HttpClient();
-        httpClient.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);
-        httpClient.start();
-        try
-        {
-            String requestUrl = "http://localhost:" + _port + "/header";
-
-            ContentExchange exchange = new ContentExchange()
-            {
-                @Override
-                protected void onException(Throwable x)
-                {
-                    // suppress exception
-                    LOG.ignore(x);
-                }
-            };
-            exchange.setURL(requestUrl);
-            exchange.setMethod(HttpMethods.GET);
-
-            for (int i = 0; i < 4; i++)
-            {
-                for (int j = 0; j < 1024; j++)
-                {
-                    exchange.addRequestHeader("header" + i + "-" + j,"v");
-                }
-            }
-
-            httpClient.send(exchange);
-
-            int state = exchange.waitForDone();
-            assertEquals(HttpExchange.STATUS_EXCEPTED, state);
-        }
-        finally
-        {
-            httpClient.stop();
-        }
-    }
-
-    private static class TestHeaderHandler extends AbstractHandler
-    {
-        protected Map<String, String> headers;
-
-        public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
-        {
-            if (baseRequest.isHandled())
-                return;
-
-            headers = new HashMap<String, String>();
-            for (Enumeration e = request.getHeaderNames(); e.hasMoreElements();)
-            {
-                String name = (String)e.nextElement();
-                headers.put(name,request.getHeader(name));
-            }
-
-            response.setContentType("text/plain");
-            response.setStatus(HttpServletResponse.SC_OK);
-            response.getWriter().print(CONTENT);
-
-            baseRequest.setHandled(true);
-        }
-
-    }
-}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpRequestAbortTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpRequestAbortTest.java
new file mode 100644
index 0000000..d40178d
--- /dev/null
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpRequestAbortTest.java
@@ -0,0 +1,561 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.client.api.Result;
+import org.eclipse.jetty.client.http.HttpDestinationOverHTTP;
+import org.eclipse.jetty.client.util.ByteBufferContentProvider;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class HttpRequestAbortTest extends AbstractHttpClientServerTest
+{
+    public HttpRequestAbortTest(SslContextFactory sslContextFactory)
+    {
+        super(sslContextFactory);
+    }
+
+    @Test
+    public void testAbortOnQueued() throws Exception
+    {
+        start(new EmptyServerHandler());
+
+        final Throwable cause = new Exception();
+        final AtomicBoolean aborted = new AtomicBoolean();
+        final CountDownLatch latch = new CountDownLatch(1);
+        final AtomicBoolean begin = new AtomicBoolean();
+        try
+        {
+            client.newRequest("localhost", connector.getLocalPort())
+                    .scheme(scheme)
+                    .listener(new Request.Listener.Adapter()
+                    {
+                        @Override
+                        public void onQueued(Request request)
+                        {
+                            aborted.set(request.abort(cause));
+                            latch.countDown();
+                        }
+
+                        @Override
+                        public void onBegin(Request request)
+                        {
+                            begin.set(true);
+                        }
+                    })
+                    .timeout(5, TimeUnit.SECONDS)
+                    .send();
+            Assert.fail();
+        }
+        catch (ExecutionException x)
+        {
+            Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+            if (aborted.get())
+                Assert.assertSame(cause, x.getCause());
+            Assert.assertFalse(begin.get());
+        }
+
+        HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, "localhost", connector.getLocalPort());
+        ConnectionPool connectionPool = destination.getConnectionPool();
+        Assert.assertEquals(0, connectionPool.getConnectionCount());
+        Assert.assertEquals(0, connectionPool.getActiveConnections().size());
+        Assert.assertEquals(0, connectionPool.getIdleConnections().size());
+    }
+
+    @Test
+    public void testAbortOnBegin() throws Exception
+    {
+        start(new EmptyServerHandler());
+
+        final Throwable cause = new Exception();
+        final AtomicBoolean aborted = new AtomicBoolean();
+        final CountDownLatch latch = new CountDownLatch(1);
+        final CountDownLatch committed = new CountDownLatch(1);
+        try
+        {
+            client.newRequest("localhost", connector.getLocalPort())
+                    .scheme(scheme)
+                    .listener(new Request.Listener.Adapter()
+                    {
+                        @Override
+                        public void onBegin(Request request)
+                        {
+                            aborted.set(request.abort(cause));
+                            latch.countDown();
+                        }
+
+                        @Override
+                        public void onCommit(Request request)
+                        {
+                            committed.countDown();
+                        }
+                    })
+                    .timeout(5, TimeUnit.SECONDS)
+                    .send();
+            Assert.fail();
+        }
+        catch (ExecutionException x)
+        {
+            Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+            if (aborted.get())
+                Assert.assertSame(cause, x.getCause());
+            Assert.assertFalse(committed.await(1, TimeUnit.SECONDS));
+        }
+
+        HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, "localhost", connector.getLocalPort());
+        ConnectionPool connectionPool = destination.getConnectionPool();
+        Assert.assertEquals(0, connectionPool.getConnectionCount());
+        Assert.assertEquals(0, connectionPool.getActiveConnections().size());
+        Assert.assertEquals(0, connectionPool.getIdleConnections().size());
+    }
+
+    @Test
+    public void testAbortOnHeaders() throws Exception
+    {
+        start(new EmptyServerHandler());
+
+        final Throwable cause = new Exception();
+        final AtomicBoolean aborted = new AtomicBoolean();
+        final CountDownLatch latch = new CountDownLatch(1);
+        final CountDownLatch committed = new CountDownLatch(1);
+        try
+        {
+            client.newRequest("localhost", connector.getLocalPort())
+                    .scheme(scheme)
+                    .listener(new Request.Listener.Adapter()
+                    {
+                        @Override
+                        public void onHeaders(Request request)
+                        {
+                            aborted.set(request.abort(cause));
+                            latch.countDown();
+                        }
+
+                        @Override
+                        public void onCommit(Request request)
+                        {
+                            committed.countDown();
+                        }
+                    })
+                    .timeout(5, TimeUnit.SECONDS)
+                    .send();
+            Assert.fail();
+        }
+        catch (ExecutionException x)
+        {
+            Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+            if (aborted.get())
+                Assert.assertSame(cause, x.getCause());
+            Assert.assertFalse(committed.await(1, TimeUnit.SECONDS));
+        }
+
+        HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, "localhost", connector.getLocalPort());
+        ConnectionPool connectionPool = destination.getConnectionPool();
+        Assert.assertEquals(0, connectionPool.getConnectionCount());
+        Assert.assertEquals(0, connectionPool.getActiveConnections().size());
+        Assert.assertEquals(0, connectionPool.getIdleConnections().size());
+    }
+
+    @Test
+    public void testAbortOnCommit() throws Exception
+    {
+        start(new EmptyServerHandler());
+
+        // Test can behave in 2 ways:
+        // A) the request is failed before the response arrived
+        // B) the request is failed after the response arrived
+
+        final Throwable cause = new Exception();
+        final AtomicBoolean aborted = new AtomicBoolean();
+        final CountDownLatch latch = new CountDownLatch(1);
+        try
+        {
+            client.newRequest("localhost", connector.getLocalPort())
+                    .scheme(scheme)
+                    .onRequestCommit(new Request.CommitListener()
+                    {
+                        @Override
+                        public void onCommit(Request request)
+                        {
+                            aborted.set(request.abort(cause));
+                            latch.countDown();
+                        }
+                    })
+                    .timeout(5, TimeUnit.SECONDS)
+                    .send();
+            Assert.fail();
+        }
+        catch (ExecutionException x)
+        {
+            Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+            if (aborted.get())
+                Assert.assertSame(cause, x.getCause());
+        }
+
+        HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, "localhost", connector.getLocalPort());
+        ConnectionPool connectionPool = destination.getConnectionPool();
+        Assert.assertEquals(0, connectionPool.getConnectionCount());
+        Assert.assertEquals(0, connectionPool.getActiveConnections().size());
+        Assert.assertEquals(0, connectionPool.getIdleConnections().size());
+    }
+
+    @Test
+    public void testAbortOnCommitWithContent() throws Exception
+    {
+        final AtomicReference<IOException> failure = new AtomicReference<>();
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                try
+                {
+                    baseRequest.setHandled(true);
+                    IO.copy(request.getInputStream(), response.getOutputStream());
+                }
+                catch (IOException x)
+                {
+                    failure.set(x);
+                    throw x;
+                }
+            }
+        });
+
+        final Throwable cause = new Exception();
+        final AtomicBoolean aborted = new AtomicBoolean();
+        final CountDownLatch latch = new CountDownLatch(1);
+        try
+        {
+            client.newRequest("localhost", connector.getLocalPort())
+                    .scheme(scheme)
+                    .onRequestCommit(new Request.CommitListener()
+                    {
+                        @Override
+                        public void onCommit(Request request)
+                        {
+                            aborted.set(request.abort(cause));
+                            latch.countDown();
+                        }
+                    })
+                    .content(new ByteBufferContentProvider(ByteBuffer.wrap(new byte[]{0}), ByteBuffer.wrap(new byte[]{1}))
+                    {
+                        @Override
+                        public long getLength()
+                        {
+                            return -1;
+                        }
+                    })
+                    .timeout(5, TimeUnit.SECONDS)
+                    .send();
+            Assert.fail();
+        }
+        catch (ExecutionException x)
+        {
+            Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+            if (aborted.get())
+                Assert.assertSame(cause, x.getCause());
+        }
+
+        HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, "localhost", connector.getLocalPort());
+        ConnectionPool connectionPool = destination.getConnectionPool();
+        Assert.assertEquals(0, connectionPool.getConnectionCount());
+        Assert.assertEquals(0, connectionPool.getActiveConnections().size());
+        Assert.assertEquals(0, connectionPool.getIdleConnections().size());
+    }
+
+    @Test
+    public void testAbortOnContent() throws Exception
+    {
+        start(new EmptyServerHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                super.handle(target, baseRequest, request, response);
+                IO.copy(request.getInputStream(), response.getOutputStream());
+            }
+        });
+
+        final Throwable cause = new Exception();
+        final AtomicBoolean aborted = new AtomicBoolean();
+        final CountDownLatch latch = new CountDownLatch(1);
+        try
+        {
+            client.newRequest("localhost", connector.getLocalPort())
+                    .scheme(scheme)
+                    .onRequestContent(new Request.ContentListener()
+                    {
+                        @Override
+                        public void onContent(Request request, ByteBuffer content)
+                        {
+                            aborted.set(request.abort(cause));
+                            latch.countDown();
+                        }
+                    })
+                    .content(new ByteBufferContentProvider(ByteBuffer.wrap(new byte[]{0}), ByteBuffer.wrap(new byte[]{1}))
+                    {
+                        @Override
+                        public long getLength()
+                        {
+                            return -1;
+                        }
+                    })
+                    .timeout(5, TimeUnit.SECONDS)
+                    .send();
+            Assert.fail();
+        }
+        catch (ExecutionException x)
+        {
+            Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+            if (aborted.get())
+                Assert.assertSame(cause, x.getCause());
+        }
+
+        HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, "localhost", connector.getLocalPort());
+        ConnectionPool connectionPool = destination.getConnectionPool();
+        Assert.assertEquals(0, connectionPool.getConnectionCount());
+        Assert.assertEquals(0, connectionPool.getActiveConnections().size());
+        Assert.assertEquals(0, connectionPool.getIdleConnections().size());
+    }
+
+    @Test(expected = InterruptedException.class)
+    public void testInterrupt() throws Exception
+    {
+        final long delay = 1000;
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                try
+                {
+                    baseRequest.setHandled(true);
+                    TimeUnit.MILLISECONDS.sleep(2 * delay);
+                }
+                catch (InterruptedException x)
+                {
+                    throw new ServletException(x);
+                }
+            }
+        });
+
+        Request request = client.newRequest("localhost", connector.getLocalPort())
+                .timeout(3 * delay, TimeUnit.MILLISECONDS)
+                .scheme(scheme);
+
+        final Thread thread = Thread.currentThread();
+        new Thread()
+        {
+            @Override
+            public void run()
+            {
+                try
+                {
+                    TimeUnit.MILLISECONDS.sleep(delay);
+                    thread.interrupt();
+                }
+                catch (InterruptedException x)
+                {
+                    throw new RuntimeException(x);
+                }
+            }
+        }.start();
+
+        request.send();
+    }
+
+    @Test
+    public void testAbortLongPoll() throws Exception
+    {
+        final long delay = 1000;
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                try
+                {
+                    baseRequest.setHandled(true);
+                    TimeUnit.MILLISECONDS.sleep(2 * delay);
+                }
+                catch (InterruptedException x)
+                {
+                    throw new ServletException(x);
+                }
+            }
+        });
+
+        final Request request = client.newRequest("localhost", connector.getLocalPort())
+                .timeout(3 * delay, TimeUnit.MILLISECONDS)
+                .scheme(scheme);
+
+        final Throwable cause = new Exception();
+        final AtomicBoolean aborted = new AtomicBoolean();
+        final CountDownLatch latch = new CountDownLatch(1);
+        new Thread()
+        {
+            @Override
+            public void run()
+            {
+                try
+                {
+                    TimeUnit.MILLISECONDS.sleep(delay);
+                    aborted.set(request.abort(cause));
+                    latch.countDown();
+                }
+                catch (InterruptedException x)
+                {
+                    throw new RuntimeException(x);
+                }
+            }
+        }.start();
+
+        try
+        {
+            request.send();
+        }
+        catch (ExecutionException x)
+        {
+            Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+            if (aborted.get())
+                Assert.assertSame(cause, x.getCause());
+        }
+
+        HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, "localhost", connector.getLocalPort());
+        ConnectionPool connectionPool = destination.getConnectionPool();
+        Assert.assertEquals(0, connectionPool.getConnectionCount());
+        Assert.assertEquals(0, connectionPool.getActiveConnections().size());
+        Assert.assertEquals(0, connectionPool.getIdleConnections().size());
+    }
+
+    @Test
+    public void testAbortLongPollAsync() throws Exception
+    {
+        final long delay = 1000;
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                try
+                {
+                    baseRequest.setHandled(true);
+                    TimeUnit.MILLISECONDS.sleep(2 * delay);
+                }
+                catch (InterruptedException x)
+                {
+                    throw new ServletException(x);
+                }
+            }
+        });
+
+        final Throwable cause = new Exception();
+        final CountDownLatch latch = new CountDownLatch(1);
+        Request request = client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .timeout(3 * delay, TimeUnit.MILLISECONDS);
+        request.send(new Response.CompleteListener()
+        {
+            @Override
+            public void onComplete(Result result)
+            {
+                Assert.assertTrue(result.isFailed());
+                Assert.assertSame(cause, result.getFailure());
+                latch.countDown();
+            }
+        });
+
+        TimeUnit.MILLISECONDS.sleep(delay);
+
+        request.abort(cause);
+
+        Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testAbortConversation() throws Exception
+    {
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                if (!"/done".equals(request.getRequestURI()))
+                    response.sendRedirect("/done");
+            }
+        });
+
+        // The test may fail to abort the request in this way:
+        // T1 aborts the request, which aborts the sender, which shuts down the output;
+        // server reads -1 and closes; T2 reads -1 and the receiver fails the response with an EOFException;
+        // T1 tries to abort the receiver, but it's already failed.
+
+        final Throwable cause = new Exception();
+        final AtomicBoolean aborted = new AtomicBoolean();
+        final CountDownLatch latch = new CountDownLatch(1);
+        client.getProtocolHandlers().clear();
+        client.getProtocolHandlers().add(new RedirectProtocolHandler(client)
+        {
+            @Override
+            public void onComplete(Result result)
+            {
+                // Abort the request after the 3xx response but before issuing the next request
+                if (!result.isFailed())
+                {
+                    aborted.set(result.getRequest().abort(cause));
+                    latch.countDown();
+                }
+                super.onComplete(result);
+            }
+        });
+
+        try
+        {
+            client.newRequest("localhost", connector.getLocalPort())
+                    .scheme(scheme)
+                    .path("/redirect")
+                    .timeout(5, TimeUnit.SECONDS)
+                    .send();
+            Assert.fail();
+        }
+        catch (ExecutionException x)
+        {
+            Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+            if (aborted.get())
+                Assert.assertSame(cause, x.getCause());
+        }
+    }
+}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpResponseAbortTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpResponseAbortTest.java
new file mode 100644
index 0000000..7a22d12
--- /dev/null
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpResponseAbortTest.java
@@ -0,0 +1,259 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.client.api.Result;
+import org.eclipse.jetty.http.HttpField;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class HttpResponseAbortTest extends AbstractHttpClientServerTest
+{
+    public HttpResponseAbortTest(SslContextFactory sslContextFactory)
+    {
+        super(sslContextFactory);
+    }
+
+    @Test
+    public void testAbortOnBegin() throws Exception
+    {
+        start(new EmptyServerHandler());
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .onResponseBegin(new Response.BeginListener()
+                {
+                    @Override
+                    public void onBegin(Response response)
+                    {
+                        response.abort(new Exception());
+                    }
+                })
+                .send(new Response.CompleteListener()
+                {
+                    @Override
+                    public void onComplete(Result result)
+                    {
+                        Assert.assertTrue(result.isFailed());
+                        latch.countDown();
+                    }
+                });
+        Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testAbortOnHeader() throws Exception
+    {
+        start(new EmptyServerHandler());
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .onResponseHeader(new Response.HeaderListener()
+                {
+                    @Override
+                    public boolean onHeader(Response response, HttpField field)
+                    {
+                        response.abort(new Exception());
+                        return true;
+                    }
+                })
+                .send(new Response.CompleteListener()
+                {
+                    @Override
+                    public void onComplete(Result result)
+                    {
+                        Assert.assertTrue(result.isFailed());
+                        latch.countDown();
+                    }
+                });
+        Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testAbortOnHeaders() throws Exception
+    {
+        start(new EmptyServerHandler());
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .onResponseHeaders(new Response.HeadersListener()
+                {
+                    @Override
+                    public void onHeaders(Response response)
+                    {
+                        response.abort(new Exception());
+                    }
+                })
+                .send(new Response.CompleteListener()
+                {
+
+                    @Override
+                    public void onComplete(Result result)
+                    {
+                        Assert.assertTrue(result.isFailed());
+                        latch.countDown();
+                    }
+                });
+        Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testAbortOnContent() throws Exception
+    {
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                try
+                {
+                    baseRequest.setHandled(true);
+                    OutputStream output = response.getOutputStream();
+                    output.write(1);
+                    output.flush();
+                    output.write(2);
+                    output.flush();
+                }
+                catch (IOException ignored)
+                {
+                    // The client may have already closed, and we'll get an exception here, but it's expected
+                }
+            }
+        });
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .onResponseContent(new Response.ContentListener()
+                {
+                    @Override
+                    public void onContent(Response response, ByteBuffer content)
+                    {
+                        response.abort(new Exception());
+                    }
+                })
+                .send(new Response.CompleteListener()
+                {
+                    @Override
+                    public void onComplete(Result result)
+                    {
+                        Assert.assertTrue(result.isFailed());
+                        latch.countDown();
+                    }
+                });
+        Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testAbortOnContentBeforeRequestTermination() throws Exception
+    {
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                try
+                {
+                    baseRequest.setHandled(true);
+                    OutputStream output = response.getOutputStream();
+                    output.write(1);
+                    output.flush();
+                    output.write(2);
+                    output.flush();
+                }
+                catch (IOException ignored)
+                {
+                    // The client may have already closed, and we'll get an exception here, but it's expected
+                }
+            }
+        });
+
+        final CountDownLatch abortLatch = new CountDownLatch(1);
+        final AtomicInteger completes = new AtomicInteger();
+        final CountDownLatch completeLatch = new CountDownLatch(1);
+        client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .onRequestSuccess(new org.eclipse.jetty.client.api.Request.SuccessListener()
+                {
+                    @Override
+                    public void onSuccess(org.eclipse.jetty.client.api.Request request)
+                    {
+                        try
+                        {
+                            abortLatch.await(5, TimeUnit.SECONDS);
+                        }
+                        catch (InterruptedException x)
+                        {
+                            x.printStackTrace();
+                        }
+                    }
+                })
+                .onResponseContent(new Response.ContentListener()
+                {
+                    @Override
+                    public void onContent(Response response, ByteBuffer content)
+                    {
+                        try
+                        {
+                            response.abort(new Exception());
+                            abortLatch.countDown();
+                            // Delay to let the request side to finish its processing.
+                            Thread.sleep(1000);
+                        }
+                        catch (InterruptedException x)
+                        {
+                            x.printStackTrace();
+                        }
+                    }
+                })
+                .send(new Response.CompleteListener()
+                {
+                    @Override
+                    public void onComplete(Result result)
+                    {
+                        completes.incrementAndGet();
+                        Assert.assertTrue(result.isFailed());
+                        completeLatch.countDown();
+                    }
+                });
+        Assert.assertTrue(completeLatch.await(5, TimeUnit.SECONDS));
+
+        // Wait to be sure that the complete event is only notified once.
+        Thread.sleep(1000);
+
+        Assert.assertEquals(1, completes.get());
+    }
+}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpResponseConcurrentAbortTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpResponseConcurrentAbortTest.java
new file mode 100644
index 0000000..bc9fecb
--- /dev/null
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpResponseConcurrentAbortTest.java
@@ -0,0 +1,192 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.client.api.Result;
+import org.eclipse.jetty.http.HttpField;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class HttpResponseConcurrentAbortTest extends AbstractHttpClientServerTest
+{
+    private final CountDownLatch callbackLatch = new CountDownLatch(1);
+    private final CountDownLatch failureLatch = new CountDownLatch(1);
+    private final CountDownLatch completeLatch = new CountDownLatch(1);
+    private final AtomicBoolean success = new AtomicBoolean();
+
+    public HttpResponseConcurrentAbortTest(SslContextFactory sslContextFactory)
+    {
+        super(sslContextFactory);
+    }
+
+    @Test
+    public void testAbortOnBegin() throws Exception
+    {
+        start(new EmptyServerHandler());
+
+        client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .onResponseBegin(new Response.BeginListener()
+                {
+                    @Override
+                    public void onBegin(Response response)
+                    {
+                        abort(response);
+                    }
+                })
+                .send(new TestResponseListener());
+        Assert.assertTrue(callbackLatch.await(5, TimeUnit.SECONDS));
+        Assert.assertTrue(completeLatch.await(6, TimeUnit.SECONDS));
+        Assert.assertTrue(success.get());
+    }
+
+    @Test
+    public void testAbortOnHeader() throws Exception
+    {
+        start(new EmptyServerHandler());
+
+        client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .onResponseHeader(new Response.HeaderListener()
+                {
+                    @Override
+                    public boolean onHeader(Response response, HttpField field)
+                    {
+                        abort(response);
+                        return true;
+                    }
+                })
+                .send(new TestResponseListener());
+        Assert.assertTrue(callbackLatch.await(5, TimeUnit.SECONDS));
+        Assert.assertTrue(completeLatch.await(5, TimeUnit.SECONDS));
+        Assert.assertTrue(success.get());
+    }
+
+    @Test
+    public void testAbortOnHeaders() throws Exception
+    {
+        start(new EmptyServerHandler());
+
+        client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .onResponseHeaders(new Response.HeadersListener()
+                {
+                    @Override
+                    public void onHeaders(Response response)
+                    {
+                        abort(response);
+                    }
+                })
+                .send(new TestResponseListener());
+        Assert.assertTrue(callbackLatch.await(5, TimeUnit.SECONDS));
+        Assert.assertTrue(completeLatch.await(5, TimeUnit.SECONDS));
+        Assert.assertTrue(success.get());
+    }
+
+    @Test
+    public void testAbortOnContent() throws Exception
+    {
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                OutputStream output = response.getOutputStream();
+                output.write(1);
+                output.flush();
+            }
+        });
+
+        client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .onResponseContent(new Response.ContentListener()
+                {
+                    @Override
+                    public void onContent(Response response, ByteBuffer content)
+                    {
+                        abort(response);
+                    }
+                })
+                .send(new TestResponseListener());
+        Assert.assertTrue(callbackLatch.await(5, TimeUnit.SECONDS));
+        Assert.assertTrue(completeLatch.await(5, TimeUnit.SECONDS));
+        Assert.assertTrue(success.get());
+    }
+
+    private void abort(final Response response)
+    {
+        new Thread("abort")
+        {
+            @Override
+            public void run()
+            {
+                response.abort(new Exception());
+            }
+        }.start();
+
+        try
+        {
+            // The failure callback must be executed asynchronously.
+            boolean latched = failureLatch.await(4, TimeUnit.SECONDS);
+            success.set(latched);
+
+            // The complete callback must not be executed
+            // until we return from this callback.
+            latched = completeLatch.await(1, TimeUnit.SECONDS);
+            success.set(!latched);
+
+            callbackLatch.countDown();
+        }
+        catch (InterruptedException x)
+        {
+            throw new RuntimeException(x);
+        }
+    }
+
+    private class TestResponseListener extends Response.Listener.Adapter
+    {
+        @Override
+        public void onFailure(Response response, Throwable failure)
+        {
+            failureLatch.countDown();
+        }
+
+        @Override
+        public void onComplete(Result result)
+        {
+            Assert.assertTrue(result.isFailed());
+            completeLatch.countDown();
+        }
+    }
+}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpsProxyAuthenticationTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpsProxyAuthenticationTest.java
deleted file mode 100644
index d8c95f2..0000000
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpsProxyAuthenticationTest.java
+++ /dev/null
@@ -1,123 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client;
-
-import java.io.IOException;
-import java.net.InetSocketAddress;
-import java.net.Socket;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jetty.client.security.Authentication;
-import org.eclipse.jetty.client.security.BasicAuthentication;
-import org.eclipse.jetty.client.security.Realm;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.handler.ConnectHandler;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
-import org.junit.After;
-import org.junit.Assert;
-import org.junit.Assume;
-import org.junit.Before;
-import org.junit.Test;
-
-public class HttpsProxyAuthenticationTest
-{
-    private Server _proxy = new Server();
-    private HttpClient _client = new HttpClient();
-    private boolean authHandlerSend;
-
-    @Before
-    public void init() throws Exception
-    {
-        SelectChannelConnector connector = new SelectChannelConnector();
-        _proxy.addConnector(connector);
-        _proxy.setHandler(new ConnectHandler()
-        {
-            @Override
-            protected boolean handleAuthentication(HttpServletRequest request, HttpServletResponse response, String address) throws ServletException, IOException
-            {
-                String authHeader = request.getHeader("Authorization");
-                if (authHeader != null && authHeader.length() > 0)
-                    authHandlerSend = true;
-                return super.handleAuthentication(request,response,address);
-            }
-        });
-        _proxy.start();
-        int proxyPort = connector.getLocalPort();
-
-        Authentication authentication = new BasicAuthentication(new Realm()
-        {
-            public String getId()
-            {
-                return "MyRealm";
-            }
-
-            public String getPrincipal()
-            {
-                return "jetty";
-            }
-
-            public String getCredentials()
-            {
-                return "jetty";
-            }
-        });
-
-        _client.setProxy(new Address("localhost", proxyPort));
-        _client.setProxyAuthentication(authentication);
-        _client.start();
-    }
-
-    @After
-    public void destroy() throws Exception
-    {
-        _client.stop();
-        _proxy.stop();
-        _proxy.join();
-    }
-
-    @Test
-    public void httpsViaProxyThatReturns504ErrorTest() throws Exception
-    {
-        // Assume that we can connect to google
-        String host = "google.com";
-        int port = 443;
-        Socket socket = new Socket();
-        try
-        {
-            socket.connect(new InetSocketAddress(host, port), 1000);
-        }
-        catch (IOException x)
-        {
-            Assume.assumeNoException(x);
-        }
-        finally
-        {
-            socket.close();
-        }
-
-        HttpExchange exchange = new ContentExchange();
-        exchange.setURL("https://" + host + ":" + port);
-        exchange.addRequestHeader("behaviour", "google");
-        _client.send(exchange);
-        Assert.assertEquals(HttpExchange.STATUS_COMPLETED, exchange.waitForDone());
-        Assert.assertTrue("Authorization header not set!", authHandlerSend);
-    }
-}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpsViaBrokenHttpProxyTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpsViaBrokenHttpProxyTest.java
deleted file mode 100644
index f8c736d..0000000
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpsViaBrokenHttpProxyTest.java
+++ /dev/null
@@ -1,141 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client;
-
-import static org.junit.Assert.assertEquals;
-
-import java.io.IOException;
-import java.net.ProtocolException;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jetty.http.HttpStatus;
-import org.eclipse.jetty.server.AbstractHttpConnection;
-import org.eclipse.jetty.server.Request;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.handler.ConnectHandler;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-/* ------------------------------------------------------------ */
-/**
- * This UnitTest class executes two tests. Both will send a http request to https://google.com through a misbehaving proxy server.
- * <p/>
- * The first test runs against a proxy which simply closes the connection (as nginx does) for a connect request. The second proxy server always responds with a
- * 500 error.
- * <p/>
- * The expected result for both tests is an exception and the HttpExchange should have status HttpExchange.STATUS_EXCEPTED.
- */
-public class HttpsViaBrokenHttpProxyTest
-{
-    private Server _proxy = new Server();
-    private HttpClient _client = new HttpClient();
-
-    @Before
-    public void init() throws Exception
-    {
-        // setup proxies with different behaviour
-        _proxy.addConnector(new SelectChannelConnector());
-        _proxy.setHandler(new BadBehavingConnectHandler());
-        _proxy.start();
-        int proxyClosingConnectionPort = _proxy.getConnectors()[0].getLocalPort();
-
-        _client.setProxy(new Address("localhost", proxyClosingConnectionPort));
-        _client.start();
-    }
-
-    @After
-    public void destroy() throws Exception
-    {
-        _client.stop();
-        _proxy.stop();
-    }
-
-    @Test
-    public void httpsViaProxyThatClosesConnectionOnConnectRequestTest() throws Exception
-    {
-        sendRequestThroughProxy(new ContentExchange()
-        {
-
-            @Override
-            protected void onException(Throwable x)
-            {
-                
-            }
-            
-        }, "close", 9);
-    }
-
-    @Test
-    public void httpsViaProxyThatReturns500ErrorTest() throws Exception
-    {
-        HttpExchange exchange = new ContentExchange()
-        {
-            @Override
-            protected void onException(Throwable x)
-            {
-                // Suppress logging for expected exception
-                if (!(x instanceof ProtocolException))
-                    super.onException(x);
-            }
-        };
-        sendRequestThroughProxy(exchange, "error500", 9);
-    }
-
-    @Test
-    public void httpsViaProxyThatReturns504ErrorTest() throws Exception
-    {
-        sendRequestThroughProxy(new ContentExchange(), "error504", 8);
-    }
-
-    private void sendRequestThroughProxy(HttpExchange exchange, String desiredBehaviour, int exptectedStatus) throws Exception
-    {
-        String url = "https://" + desiredBehaviour + ".com/";
-        exchange.setURL(url);
-        exchange.addRequestHeader("behaviour", desiredBehaviour);
-        _client.send(exchange);
-        assertEquals(HttpExchange.toState(exptectedStatus) + " status awaited", exptectedStatus, exchange.waitForDone());
-    }
-
-    private class BadBehavingConnectHandler extends ConnectHandler
-    {
-        @Override
-        protected void handleConnect(Request baseRequest, HttpServletRequest request, HttpServletResponse response, String serverAddress)
-                throws ServletException, IOException
-        {
-            if (serverAddress.contains("close"))
-            {
-                AbstractHttpConnection.getCurrentConnection().getEndPoint().close();
-            }
-            else if (serverAddress.contains("error500"))
-            {
-                response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR_500);
-            }
-            else if (serverAddress.contains("error504"))
-            {
-                response.setStatus(HttpStatus.GATEWAY_TIMEOUT_504);
-            }
-            baseRequest.setHandled(true);
-        }
-    }
-}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/IdleTimeoutTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/IdleTimeoutTest.java
deleted file mode 100644
index 14ecd9f..0000000
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/IdleTimeoutTest.java
+++ /dev/null
@@ -1,147 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client;
-
-import java.io.IOException;
-import java.net.URI;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jetty.http.HttpMethods;
-import org.eclipse.jetty.server.Connector;
-import org.eclipse.jetty.server.Request;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.handler.AbstractHandler;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
-import org.junit.Assert;
-import org.junit.Ignore;
-import org.junit.Test;
-
-import static org.hamcrest.CoreMatchers.is;
-import static org.junit.Assert.assertThat;
-
-/**
- * IdleTimeoutTest
- *
- * Warning - this is a slow test. Uncomment the ignore to run it.
- *
- */
-public class IdleTimeoutTest
-{
-    public int _repetitions = 30;
-    
-    @Ignore
-    //@Test
-    public void testIdleTimeoutOnBlockingConnector() throws Exception
-    {
-        final HttpClient client = new HttpClient();
-        client.setMaxConnectionsPerAddress(4);
-        client.setConnectorType(HttpClient.CONNECTOR_SOCKET);
-        client.setTimeout(TimeUnit.SECONDS.toMillis(86400)); // very long timeout on data
-        client.setIdleTimeout(500); // very short idle timeout
-        client.start();
-
-        final CountDownLatch counter = new CountDownLatch(_repetitions);
-        
-        Thread runner = new Thread()
-        {
-            public void run()
-            {
-                try
-                {
-                    for (int i=0; i<_repetitions; i++) 
-                    {
-                        ContentExchange exchange = new ContentExchange();
-                        exchange.setURL("http://www.google.com/?i="+i);
-                        client.send(exchange);
-                        exchange.waitForDone();
-                        counter.countDown();
-                        System.err.println(counter.getCount());
-                        Thread.sleep(1000); //wait long enough for idle timeout to expire   
-                    }
-                }
-                catch (Exception e)
-                {
-                    Assert.fail(e.getMessage());
-                }
-            }
-        };
-       
-        runner.start();
-        if (!counter.await(80, TimeUnit.SECONDS))
-            Assert.fail("Test did not complete in time");
-        
-    }
-
-    @Test
-    public void testConnectionsAreReleasedWhenExpired() throws Exception
-    {
-        // we need a server that times out and a client with shorter timeout settings, so we need to create new ones
-        Server server = new Server();
-        Connector connector = new SelectChannelConnector();
-        server.addConnector(connector);
-        server.setHandler(new AbstractHandler()
-        {
-            public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
-            {
-                if (request.getParameter("timeout") != null)
-                {
-                    try
-                    {
-                        Thread.sleep(1000);
-                    }
-                    catch (InterruptedException e)
-                    {
-                        e.printStackTrace();
-                    }
-                }
-                baseRequest.setHandled(true);
-                response.getWriter().write("Hello world");
-            }
-        });
-        server.start();
-
-        HttpClient httpClient = new HttpClient();
-        httpClient.setMaxConnectionsPerAddress(1);
-        httpClient.setConnectTimeout(200);
-        httpClient.setTimeout(200);
-        httpClient.setIdleTimeout(200);
-        httpClient.start();
-
-        String uriString =  "http://localhost:" + connector.getLocalPort() + "/";
-
-        HttpExchange httpExchange = new HttpExchange();
-        httpExchange.setURI(URI.create(uriString).resolve("?timeout=true"));
-        httpExchange.setMethod(HttpMethods.GET);
-        httpClient.send(httpExchange);
-        int status = httpExchange.waitForDone();
-        assertThat("First request expired", status, is(8));
-
-        httpExchange = new HttpExchange();
-        httpExchange.setURI(URI.create(uriString));
-        httpExchange.setMethod(HttpMethods.GET);
-        httpClient.send(httpExchange);
-        status = httpExchange.waitForDone();
-        assertThat("Second request was successful as timeout is not set", status, is(7));
-    }
-}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/NonBlockingHttpExchangeCancelTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/NonBlockingHttpExchangeCancelTest.java
deleted file mode 100644
index a7dc3b8..0000000
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/NonBlockingHttpExchangeCancelTest.java
+++ /dev/null
@@ -1,64 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client;
-
-import org.junit.After;
-import org.junit.Before;
-
-/* ------------------------------------------------------------ */
-/**
- * @version $Revision$ $Date$
- */
-public class NonBlockingHttpExchangeCancelTest extends AbstractHttpExchangeCancelTest
-{
-    private HttpClient httpClient;
-
-    /* ------------------------------------------------------------ */
-    @Before
-    @Override
-    public void setUp() throws Exception
-    {
-        super.setUp();
-        httpClient = new HttpClient();
-        httpClient.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);
-        httpClient.start();
-    }
-
-    /* ------------------------------------------------------------ */
-    @After
-    @Override
-    public void tearDown() throws Exception
-    {
-        httpClient.stop();
-        super.tearDown();
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    protected HttpClient getHttpClient()
-    {
-        return httpClient;
-    }
-    
-    /* ------------------------------------------------------------ */
-    public void testHttpExchangeCancelOnRequestComplete() throws Exception
-    {
-        super.testHttpExchangeCancelOnRequestComplete();
-    }
-}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/ProxyConfigurationTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/ProxyConfigurationTest.java
new file mode 100644
index 0000000..47bf5bd
--- /dev/null
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/ProxyConfigurationTest.java
@@ -0,0 +1,66 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class ProxyConfigurationTest
+{
+    @Test
+    public void testProxyMatchesWithoutIncludesWithoutExcludes() throws Exception
+    {
+        HttpProxy proxy = new HttpProxy("host", 0);
+        Assert.assertTrue(proxy.matches(new Origin("http", "any", 0)));
+    }
+
+    @Test
+    public void testProxyMatchesWithOnlyExcludes() throws Exception
+    {
+        HttpProxy proxy = new HttpProxy("host", 0);
+        proxy.getExcludedAddresses().add("1.2.3.4:5");
+
+        Assert.assertTrue(proxy.matches(new Origin("http", "any", 0)));
+        Assert.assertTrue(proxy.matches(new Origin("http", "1.2.3.4", 0)));
+        Assert.assertFalse(proxy.matches(new Origin("http", "1.2.3.4", 5)));
+    }
+
+    @Test
+    public void testProxyMatchesWithOnlyIncludes() throws Exception
+    {
+        HttpProxy proxy = new HttpProxy("host", 0);
+        proxy.getIncludedAddresses().add("1.2.3.4:5");
+
+        Assert.assertFalse(proxy.matches(new Origin("http", "any", 0)));
+        Assert.assertFalse(proxy.matches(new Origin("http", "1.2.3.4", 0)));
+        Assert.assertTrue(proxy.matches(new Origin("http", "1.2.3.4", 5)));
+    }
+
+    @Test
+    public void testProxyMatchesWithIncludesAndExcludes() throws Exception
+    {
+        HttpProxy proxy = new HttpProxy("host", 0);
+        proxy.getIncludedAddresses().add("1.2.3.4");
+        proxy.getExcludedAddresses().add("1.2.3.4:5");
+
+        Assert.assertFalse(proxy.matches(new Origin("http", "any", 0)));
+        Assert.assertTrue(proxy.matches(new Origin("http", "1.2.3.4", 0)));
+        Assert.assertFalse(proxy.matches(new Origin("http", "1.2.3.4", 5)));
+    }
+}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/ProxyFakeTunnelTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/ProxyFakeTunnelTest.java
deleted file mode 100644
index 88c4266..0000000
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/ProxyFakeTunnelTest.java
+++ /dev/null
@@ -1,237 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.ServerSocket;
-import java.net.Socket;
-
-import org.eclipse.jetty.toolchain.test.IO;
-
-
-public class ProxyFakeTunnelTest extends ProxyTunnellingTest
-{
-    ServerSocket _proxySocket;
-    Thread _proxyThread;
-
-    protected int proxyPort()
-    {
-        return _proxySocket.getLocalPort();
-    }
-
-
-    protected void startProxy() throws Exception
-    {
-        _proxySocket = new ServerSocket(0);
-
-        _proxyThread = new Thread()
-        {
-            @Override
-            public void run()
-            {
-                while (!_proxySocket.isClosed())
-                {
-                    try
-                    {
-                        Socket socket=_proxySocket.accept();
-                        System.err.println("accepted "+socket);
-                        new FakeProxy(socket).start();
-                    }
-                    catch (IOException e)
-                    {
-                    }
-                }
-            }
-        };
-        _proxyThread.setDaemon(true);
-        _proxyThread.start();
-
-    }
-
-    protected void stopProxy() throws Exception
-    {
-        if (_proxySocket != null)
-        {
-            _proxySocket.close();
-            _proxyThread.interrupt();
-        }
-    }
-
-    @Override
-    public void testExternalProxy() throws Exception
-    {
-        // Do not execute this test, since it won't hit the fake proxy
-    }
-
-    static class FakeProxy extends Thread
-    {
-        Socket _socket;
-
-        public FakeProxy(Socket socket)
-        {
-            _socket=socket;
-        }
-
-        public void run()
-        {
-
-            Socket toserver=null;
-            final InputStream in;
-            final OutputStream out;
-            try
-            {
-                 in = _socket.getInputStream();
-                 out = _socket.getOutputStream();
-
-                String address="";
-                int state=0;
-
-                for (int b=in.read();b>=0;b=in.read())
-                {
-                    switch(state)
-                    {
-                        case 0:
-                            if (' '==b)
-                                state=1;
-                            break;
-
-                        case 1:
-                            if (' '==b)
-                                state=2;
-                            else
-                                address+=(char)b;
-                            break;
-
-                        case 2:
-                            if ('\r'==b)
-                                state=3;
-                            break;
-
-                        case 3:
-                            if ('\n'==b)
-                                state=4;
-                            else
-                                state=2;
-                            break;
-
-                        case 4:
-                            if ('\r'==b)
-                                state=5;
-                            else
-                                state=2;
-                            break;
-
-                        case 5:
-                            if ('\n'==b)
-                            {
-                                state=6;
-                                System.err.println("address="+address);
-                                String[] parts=address.split(":");
-                                try
-                                {
-                                    toserver = new Socket(parts[0],Integer.parseInt(parts[1]));
-                                    out.write((
-                                            "HTTP/1.1 200 OK\r\n"+
-                                            "Server: fake\r\n"+
-                                            // "Content-Length: 0\r\n"+
-                                            "\r\n"
-                                            ).getBytes());
-                                }
-                                catch(IOException e)
-                                {
-                                    out.write((
-                                            "HTTP/1.1 503 Unavailable\r\n"+
-                                            "Server: fake\r\n"+
-                                            "Content-Length: 0\r\n"+
-                                            "\r\n"
-                                            ).getBytes());
-                                }
-                                out.flush();
-
-                                if (toserver!=null)
-                                {
-                                    final InputStream from = toserver.getInputStream();
-
-                                    Thread copy = new Thread()
-                                    {
-                                        public void run()
-                                        {
-                                            try
-                                            {
-                                                IO.copy(from,out);
-                                                out.close();
-                                            }
-                                            catch (IOException e)
-                                            {
-                                            }
-                                            finally
-                                            {
-                                                try
-                                                {
-                                                    out.close();
-                                                }
-                                                catch (IOException e)
-                                                {
-                                                }
-                                            }
-                                        }
-                                    };
-                                    copy.setDaemon(true);
-                                    copy.start();
-                                }
-
-                            }
-                            else
-                                state=2;
-                            break;
-
-                        case 6:
-                            toserver.getOutputStream().write((byte)b);
-
-                    }
-                }
-
-
-            }
-            catch (IOException e)
-            {
-                e.printStackTrace();
-            }
-            finally
-            {
-                if (toserver!=null)
-                {
-                    try
-                    {
-                        toserver.close();
-                    }
-                    catch (IOException e)
-                    {
-                        e.printStackTrace();
-                    }
-                }
-            }
-        }
-
-    }
-
-
-}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/ProxyTunnellingTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/ProxyTunnellingTest.java
deleted file mode 100644
index 9b7f6be..0000000
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/ProxyTunnellingTest.java
+++ /dev/null
@@ -1,414 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client;
-
-import java.io.IOException;
-import java.io.InterruptedIOException;
-import java.net.Socket;
-import java.net.URLEncoder;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicReference;
-import javax.servlet.ServletException;
-import javax.servlet.ServletOutputStream;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jetty.http.HttpHeaders;
-import org.eclipse.jetty.http.HttpMethods;
-import org.eclipse.jetty.http.HttpStatus;
-import org.eclipse.jetty.http.MimeTypes;
-import org.eclipse.jetty.io.ByteArrayBuffer;
-import org.eclipse.jetty.server.Connector;
-import org.eclipse.jetty.server.Handler;
-import org.eclipse.jetty.server.Request;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.handler.AbstractHandler;
-import org.eclipse.jetty.server.handler.ConnectHandler;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
-import org.eclipse.jetty.server.ssl.SslSelectChannelConnector;
-import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
-import org.eclipse.jetty.util.ssl.SslContextFactory;
-import org.junit.After;
-import org.junit.Assert;
-import org.junit.Assume;
-import org.junit.Test;
-import org.junit.Ignore;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-public class ProxyTunnellingTest
-{
-    private Server server;
-    private Connector serverConnector;
-    private Server proxy;
-    private Connector proxyConnector;
-    private int serverConnectTimeout = 1000;
-
-    protected int proxyPort()
-    {
-        return proxyConnector.getLocalPort();
-    }
-
-    protected void startSSLServer(Handler handler) throws Exception
-    {
-        SslSelectChannelConnector connector = new SslSelectChannelConnector();
-        String keyStorePath = MavenTestingUtils.getTestResourceFile("keystore").getAbsolutePath();
-        SslContextFactory cf = connector.getSslContextFactory();
-        cf.setKeyStorePath(keyStorePath);
-        cf.setKeyStorePassword("storepwd");
-        cf.setKeyManagerPassword("keypwd");
-        startServer(connector, handler);
-    }
-
-    protected void startServer(Connector connector, Handler handler) throws Exception
-    {
-        server = new Server();
-        serverConnector = connector;
-        server.addConnector(serverConnector);
-        server.setHandler(handler);
-        server.start();
-    }
-
-    protected void startProxy() throws Exception
-    {
-        proxy = new Server();
-        proxyConnector = new SelectChannelConnector();
-        proxy.addConnector(proxyConnector);
-        ConnectHandler connectHandler = new ConnectHandler();
-        // Under Windows, it takes a while to detect that a connection
-        // attempt fails, so use an explicit timeout
-        connectHandler.setConnectTimeout(serverConnectTimeout);
-        proxy.setHandler(connectHandler);
-        proxy.start();
-    }
-
-    @After
-    public void stop() throws Exception
-    {
-        stopProxy();
-        stopServer();
-    }
-
-    protected void stopServer() throws Exception
-    {
-        if (server != null)
-        {
-            server.stop();
-            server.join();
-        }
-    }
-
-    protected void stopProxy() throws Exception
-    {
-        if (proxy != null)
-        {
-            proxy.stop();
-            proxy.join();
-        }
-    }
-
-    @Test
-    public void testOneExchangeViaSSL() throws Exception
-    {
-        startSSLServer(new ServerHandler());
-        startProxy();
-
-        HttpClient httpClient = new HttpClient();
-        httpClient.setProxy(new Address("localhost", proxyPort()));
-        httpClient.start();
-
-        try
-        {
-            ContentExchange exchange = new ContentExchange(true);
-            exchange.setMethod(HttpMethods.GET);
-            String body = "BODY";
-            exchange.setURL("https://localhost:" + serverConnector.getLocalPort() + "/echo?body=" + URLEncoder.encode(body, "UTF-8"));
-
-            httpClient.send(exchange);
-            assertEquals(HttpExchange.STATUS_COMPLETED, exchange.waitForDone());
-            String content = exchange.getResponseContent();
-            assertEquals(body, content);
-        }
-        finally
-        {
-            httpClient.stop();
-        }
-    }
-
-    @Test
-    public void testTwoExchangesViaSSL() throws Exception
-    {
-        startSSLServer(new ServerHandler());
-        startProxy();
-
-        HttpClient httpClient = new HttpClient();
-        httpClient.setProxy(new Address("localhost", proxyPort()));
-        httpClient.start();
-
-        try
-        {
-            ContentExchange exchange = new ContentExchange(true);
-            exchange.setMethod(HttpMethods.GET);
-            String body = "BODY";
-            exchange.setURL("https://localhost:" + serverConnector.getLocalPort() + "/echo?body=" + URLEncoder.encode(body, "UTF-8"));
-
-            httpClient.send(exchange);
-            assertEquals(HttpExchange.STATUS_COMPLETED, exchange.waitForDone());
-            String content = exchange.getResponseContent();
-            assertEquals(body, content);
-
-            exchange = new ContentExchange(true);
-            exchange.setMethod(HttpMethods.POST);
-            exchange.setURL("https://localhost:" + serverConnector.getLocalPort() + "/echo");
-            exchange.setRequestHeader(HttpHeaders.CONTENT_TYPE, MimeTypes.FORM_ENCODED);
-            content = "body=" + body;
-            exchange.setRequestHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(content.length()));
-            exchange.setRequestContent(new ByteArrayBuffer(content, "UTF-8"));
-
-            httpClient.send(exchange);
-            assertEquals(HttpExchange.STATUS_COMPLETED, exchange.waitForDone());
-            content = exchange.getResponseContent();
-            assertEquals(body, content);
-        }
-        finally
-        {
-            httpClient.stop();
-        }
-    }
-
-    @Test
-    public void testTwoConcurrentExchangesViaSSL() throws Exception
-    {
-        startSSLServer(new ServerHandler());
-        startProxy();
-
-        final HttpClient httpClient = new HttpClient();
-        httpClient.setProxy(new Address("localhost", proxyPort()));
-        httpClient.start();
-
-        try
-        {
-            final AtomicReference<AbstractHttpConnection> connection = new AtomicReference<AbstractHttpConnection>();
-            final CountDownLatch connectionLatch = new CountDownLatch(1);
-            ContentExchange exchange1 = new ContentExchange(true)
-            {
-                @Override
-                protected void onRequestCommitted() throws IOException
-                {
-                    // Simulate the concurrent send of a second exchange which
-                    // triggers the opening of a second connection but then
-                    // it's "stolen" by the first connection, so that the
-                    // second connection is put into the idle connections.
-
-                    HttpDestination destination = httpClient.getDestination(new Address("localhost", serverConnector.getLocalPort()), true);
-                    destination.startNewConnection();
-
-                    // Wait until we have the new connection
-                    AbstractHttpConnection httpConnection = null;
-                    while (httpConnection == null)
-                    {
-                        try
-                        {
-                            Thread.sleep(10);
-                            httpConnection = destination.getIdleConnection();
-                        }
-                        catch (InterruptedException x)
-                        {
-                            throw new InterruptedIOException();
-                        }
-                    }
-
-                    connection.set(httpConnection);
-                    connectionLatch.countDown();
-                }
-            };
-            exchange1.setMethod(HttpMethods.GET);
-            String body1 = "BODY";
-            exchange1.setURL("https://localhost:" + serverConnector.getLocalPort() + "/echo?body=" + URLEncoder.encode(body1, "UTF-8"));
-
-            httpClient.send(exchange1);
-            assertEquals(HttpExchange.STATUS_COMPLETED, exchange1.waitForDone());
-            assertEquals(HttpStatus.OK_200, exchange1.getResponseStatus());
-            String content1 = exchange1.getResponseContent();
-            assertEquals(body1, content1);
-
-            Assert.assertTrue(connectionLatch.await(5, TimeUnit.SECONDS));
-
-            ContentExchange exchange2 = new ContentExchange(true);
-            exchange2.setMethod(HttpMethods.POST);
-            exchange2.setURL("https://localhost:" + serverConnector.getLocalPort() + "/echo");
-            exchange2.setRequestHeader(HttpHeaders.CONTENT_TYPE, MimeTypes.FORM_ENCODED);
-            String body2 = "body=" + body1;
-            exchange2.setRequestHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(body2.length()));
-            exchange2.setRequestContent(new ByteArrayBuffer(body2, "UTF-8"));
-
-            // Make sure the second connection can send the exchange via the tunnel
-            connection.get().send(exchange2);
-            assertEquals(HttpExchange.STATUS_COMPLETED, exchange2.waitForDone());
-            assertEquals(HttpStatus.OK_200, exchange2.getResponseStatus());
-            String content2 = exchange2.getResponseContent();
-            assertEquals(body1, content2);
-        }
-        finally
-        {
-            httpClient.stop();
-        }
-    }
-
-    @Test
-    public void testProxyDown() throws Exception
-    {
-        startSSLServer(new ServerHandler());
-        startProxy();
-        int proxyPort = proxyPort();
-        stopProxy();
-
-        HttpClient httpClient = new HttpClient();
-        httpClient.setProxy(new Address("localhost", proxyPort));
-        httpClient.start();
-
-        try
-        {
-            final CountDownLatch latch = new CountDownLatch(1);
-            ContentExchange exchange = new ContentExchange(true)
-            {
-                @Override
-                protected void onConnectionFailed(Throwable x)
-                {
-                    latch.countDown();
-                }
-            };
-            exchange.setMethod(HttpMethods.GET);
-            String body = "BODY";
-            exchange.setURL("https://localhost:" + serverConnector.getLocalPort() + "/echo?body=" + URLEncoder.encode(body, "UTF-8"));
-
-            httpClient.send(exchange);
-            assertTrue(latch.await(1000, TimeUnit.MILLISECONDS));
-        }
-        finally
-        {
-            httpClient.stop();
-        }
-    }
-
-    @Test
-    public void testServerDown() throws Exception
-    {
-        startSSLServer(new ServerHandler());
-        int serverPort = serverConnector.getLocalPort();
-        stopServer();
-        startProxy();
-
-        HttpClient httpClient = new HttpClient();
-        httpClient.setProxy(new Address("localhost", proxyPort()));
-        httpClient.start();
-
-        try
-        {
-            final CountDownLatch latch = new CountDownLatch(1);
-            ContentExchange exchange = new ContentExchange(true)
-            {
-                @Override
-                protected void onException(Throwable x)
-                {
-                    latch.countDown();
-                }
-
-            };
-            exchange.setMethod(HttpMethods.GET);
-            String body = "BODY";
-            exchange.setURL("https://localhost:" + serverPort + "/echo?body=" + URLEncoder.encode(body, "UTF-8"));
-
-            httpClient.send(exchange);
-            assertTrue("Server connect exception should have occurred", latch.await(serverConnectTimeout * 2, TimeUnit.MILLISECONDS));
-        }
-        finally
-        {
-            httpClient.stop();
-        }
-    }
-
-    @Test
-    @Ignore
-    public void testExternalProxy() throws Exception
-    {
-        // Free proxy server obtained from http://hidemyass.com/proxy-list/
-        String proxyHost = "81.208.25.53";
-        int proxyPort = 3128;
-        try
-        {
-            new Socket(proxyHost, proxyPort).close();
-        }
-        catch (IOException x)
-        {
-            Assume.assumeNoException(x);
-        }
-
-        // Start the server to start the SslContextFactory
-        startSSLServer(new AbstractHandler()
-        {
-            public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
-            {
-                throw new ServletException();
-            }
-        });
-
-        HttpClient httpClient = new HttpClient();
-        httpClient.setProxy(new Address(proxyHost, proxyPort));
-        httpClient.registerListener(RedirectListener.class.getName());
-        httpClient.start();
-
-        try
-        {
-            ContentExchange exchange = new ContentExchange(true);
-            // Use a longer timeout, sometimes the proxy takes a while to answer
-            exchange.setTimeout(20000);
-            exchange.setURL("https://www.google.com");
-            httpClient.send(exchange);
-            assertEquals(HttpExchange.STATUS_COMPLETED, exchange.waitForDone());
-            assertEquals(200, exchange.getResponseStatus());
-        }
-        finally
-        {
-            httpClient.stop();
-        }
-    }
-
-    private static class ServerHandler extends AbstractHandler
-    {
-        public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException
-        {
-            request.setHandled(true);
-
-            String uri = httpRequest.getRequestURI();
-            if ("/echo".equals(uri))
-            {
-                String body = httpRequest.getParameter("body");
-                ServletOutputStream output = httpResponse.getOutputStream();
-                output.print(body);
-            }
-            else
-            {
-                throw new ServletException();
-            }
-        }
-    }
-}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/SecuredContentExchangeTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/SecuredContentExchangeTest.java
deleted file mode 100644
index a392602..0000000
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/SecuredContentExchangeTest.java
+++ /dev/null
@@ -1,108 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client;
-
-import java.io.File;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
-
-import org.eclipse.jetty.client.security.Realm;
-import org.eclipse.jetty.security.ConstraintMapping;
-import org.eclipse.jetty.security.ConstraintSecurityHandler;
-import org.eclipse.jetty.security.HashLoginService;
-import org.eclipse.jetty.security.LoginService;
-import org.eclipse.jetty.security.authentication.BasicAuthenticator;
-import org.eclipse.jetty.server.Handler;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.handler.HandlerCollection;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
-import org.eclipse.jetty.servlet.DefaultServlet;
-import org.eclipse.jetty.servlet.ServletContextHandler;
-import org.eclipse.jetty.servlet.ServletHolder;
-import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
-import org.eclipse.jetty.util.security.Constraint;
-
-public class SecuredContentExchangeTest
-    extends ContentExchangeTest
-{ 
-    protected void configureServer(Server server)
-        throws Exception
-    {
-        setProtocol("http");
-        setRealm(new Realm()
-                 {
-                     public String getId()
-                     {
-                         return "MyRealm";
-                     }
-                
-                     public String getPrincipal()
-                     {
-                         return "jetty";
-                     }
-                
-                     public String getCredentials()
-                     {
-                         return "jetty";
-                     }
-                 });
-                        
-        SelectChannelConnector connector = new SelectChannelConnector();
-        server.addConnector(connector);
-        
-        File realmPropFile = MavenTestingUtils.getTestResourceFile("realm.properties");
-        LoginService loginService = new HashLoginService("MyRealm",realmPropFile.getAbsolutePath());
-        server.addBean(loginService); 
-
-        ConstraintSecurityHandler security = new ConstraintSecurityHandler();
-        server.setHandler(security);
-
-        Constraint constraint = new Constraint();
-        constraint.setName("auth");
-        constraint.setAuthenticate( true );
-        constraint.setRoles(new String[]{"user", "admin"});
-
-        ConstraintMapping mapping = new ConstraintMapping();
-        mapping.setPathSpec( "/*" );
-        mapping.setConstraint( constraint );
-
-        Set<String> knownRoles = new HashSet<String>();
-        knownRoles.add("user");
-        knownRoles.add("admin");
-        
-        security.setConstraintMappings(Collections.singletonList(mapping), knownRoles);
-        security.setAuthenticator(new BasicAuthenticator());
-        security.setLoginService(loginService);
-        security.setStrict(false);
-        
-        ServletContextHandler root = new ServletContextHandler();
-        root.setContextPath("/");
-        root.setResourceBase(getBasePath());
-        ServletHolder servletHolder = new ServletHolder( new DefaultServlet() );
-        servletHolder.setInitParameter( "gzip", "true" );
-        root.addServlet( servletHolder, "/*" );    
-
-        Handler handler = new TestHandler(getBasePath());       
-        
-        HandlerCollection handlers = new HandlerCollection();
-        handlers.setHandlers(new Handler[]{handler, root});
-        security.setHandler(handlers);
-    }
-}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/SecuredErrorStatusTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/SecuredErrorStatusTest.java
deleted file mode 100644
index 011a22c..0000000
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/SecuredErrorStatusTest.java
+++ /dev/null
@@ -1,193 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client;
-
-import java.io.File;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
-
-import org.eclipse.jetty.client.security.Realm;
-import org.eclipse.jetty.http.HttpStatus;
-import org.eclipse.jetty.security.ConstraintMapping;
-import org.eclipse.jetty.security.ConstraintSecurityHandler;
-import org.eclipse.jetty.security.HashLoginService;
-import org.eclipse.jetty.security.LoginService;
-import org.eclipse.jetty.security.authentication.BasicAuthenticator;
-import org.eclipse.jetty.server.Handler;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.handler.HandlerCollection;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
-import org.eclipse.jetty.servlet.DefaultServlet;
-import org.eclipse.jetty.servlet.ServletContextHandler;
-import org.eclipse.jetty.servlet.ServletHolder;
-import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
-import org.eclipse.jetty.util.security.Constraint;
-import org.junit.Test;
-
-public class SecuredErrorStatusTest
-    extends ErrorStatusTest
-{  
-    private Realm _testRealm;
-    private Realm _dummyRealm;
-    
-    /* ------------------------------------------------------------ */
-    @Test
-    @Override
-    public void testPutUnauthorized()
-        throws Exception
-    {
-        setRealm(null);
-        
-        doPutFail(HttpStatus.UNAUTHORIZED_401);
-        
-        setRealm(_testRealm);
-    }
-    
-    /* ------------------------------------------------------------ */
-    @Test
-    public void testPutWrongPassword()
-        throws Exception
-    {
-        setRealm(_dummyRealm);
-        
-        doPutFail(HttpStatus.UNAUTHORIZED_401);
-        
-        setRealm(_testRealm);
-    }
-    
-    /* ------------------------------------------------------------ */
-    @Test
-    @Override
-    public void testGetUnauthorized()
-        throws Exception
-    {
-        setRealm(null);
-        
-        doGetFail(HttpStatus.UNAUTHORIZED_401);
-        
-        setRealm(_testRealm);
-    }
-    
-    /* ------------------------------------------------------------ */
-    @Test
-    public void testGetWrongPassword()
-        throws Exception
-    {
-        setRealm(_dummyRealm);
-        
-        doGetFail(HttpStatus.UNAUTHORIZED_401); 
-        
-        setRealm(_testRealm);
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    protected void configureServer(Server server)
-        throws Exception
-    {
-        setProtocol("http");
-        
-        _testRealm = new Realm()
-                         {
-                             /* ------------------------------------------------------------ */
-                             public String getId()
-                             {
-                                 return "MyRealm";
-                             }
-                               
-                             /* ------------------------------------------------------------ */
-                             public String getPrincipal()
-                             {
-                                 return "jetty";
-                             }
-                          
-                             /* ------------------------------------------------------------ */
-                             public String getCredentials()
-                             {
-                                 return "jetty";
-                             }
-                         };
-                         
-        _dummyRealm = new Realm()
-                          {
-                              /* ------------------------------------------------------------ */
-                              public String getId()
-                              {
-                                  return "MyRealm";
-                              }
-                    
-                              /* ------------------------------------------------------------ */
-                              public String getPrincipal()
-                              {
-                                  return "jetty";
-                              }
-                                                   
-                              /* ------------------------------------------------------------ */
-                              public String getCredentials()
-                              {
-                                  return "dummy";
-                              }
-                          };
-                          
-        setRealm(_testRealm);
-        
-        SelectChannelConnector connector = new SelectChannelConnector();
-        server.addConnector(connector);
-        
-        File realmPropFile = MavenTestingUtils.getTestResourceFile("realm.properties");
-        LoginService loginService = new HashLoginService("MyRealm",realmPropFile.getAbsolutePath());
-        server.addBean(loginService); 
-
-        ConstraintSecurityHandler security = new ConstraintSecurityHandler();
-        server.setHandler(security);
-
-        Constraint constraint = new Constraint();
-        constraint.setName("auth");
-        constraint.setAuthenticate( true );
-        constraint.setRoles(new String[]{"user", "admin"});
-
-        ConstraintMapping mapping = new ConstraintMapping();
-        mapping.setPathSpec( "/*" );
-        mapping.setConstraint( constraint );
-
-        Set<String> knownRoles = new HashSet<String>();
-        knownRoles.add("user");
-        knownRoles.add("admin");
-        
-        security.setConstraintMappings(Collections.singletonList(mapping), knownRoles);
-        security.setAuthenticator(new BasicAuthenticator());
-        security.setLoginService(loginService);
-        security.setStrict(false);
-                
-        ServletContextHandler root = new ServletContextHandler();
-        root.setContextPath("/");
-        root.setResourceBase(getBasePath());
-        ServletHolder servletHolder = new ServletHolder( new DefaultServlet() );
-        servletHolder.setInitParameter( "gzip", "true" );
-        root.addServlet( servletHolder, "/*" );    
-
-        Handler status = new StatusHandler();
-        Handler test = new TestHandler(getBasePath());
-        
-        HandlerCollection handlers = new HandlerCollection();
-        handlers.setHandlers(new Handler[]{status, test, root});
-        security.setHandler(handlers); 
-    }
-}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/SecurityListenerTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/SecurityListenerTest.java
deleted file mode 100644
index cb54f01..0000000
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/SecurityListenerTest.java
+++ /dev/null
@@ -1,355 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.CyclicBarrier;
-import java.util.concurrent.TimeUnit;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jetty.client.security.Realm;
-import org.eclipse.jetty.client.security.SimpleRealmResolver;
-import org.eclipse.jetty.http.HttpMethods;
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.EofException;
-import org.eclipse.jetty.security.ConstraintMapping;
-import org.eclipse.jetty.security.ConstraintSecurityHandler;
-import org.eclipse.jetty.security.HashLoginService;
-import org.eclipse.jetty.security.LoginService;
-import org.eclipse.jetty.security.authentication.BasicAuthenticator;
-import org.eclipse.jetty.server.Connector;
-import org.eclipse.jetty.server.Handler;
-import org.eclipse.jetty.server.Request;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.handler.AbstractHandler;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
-import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
-import org.eclipse.jetty.util.security.Constraint;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-/* ------------------------------------------------------------ */
-/**
- * Functional testing for HttpExchange.
- */
-public class SecurityListenerTest
-{
-    private Server _server;
-    private int _port;
-    private HttpClient _httpClient;
-
-    private Realm _jettyRealm;
-    private static final String APP_CONTEXT = "localhost /";
-
-    /* ------------------------------------------------------------ */
-    @Before
-    public void setUp() throws Exception
-    {
-        startServer();
-        _httpClient=new HttpClient();
-        _httpClient.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);
-        _httpClient.setMaxConnectionsPerAddress(2);
-        _httpClient.start();
-        
-        _jettyRealm = new Realm()
-        {
-            public String getId()
-            {
-                return "MyRealm";
-            }
-
-            public String getPrincipal()
-            {
-                return "jetty";
-            }
-
-            public String getCredentials()
-            {
-                return "jetty";
-            }
-        };
-
-        _httpClient.setRealmResolver( new SimpleRealmResolver(_jettyRealm) );
-    }
-
-    /* ------------------------------------------------------------ */
-    @After
-    public void tearDown() throws Exception
-    {
-        stopServer();
-        _httpClient.stop();
-    }
-
-    /* ------------------------------------------------------------ */
-//    @Test
-    public void xtestPerf() throws Exception
-    {
-        sender(1);
-        Thread.sleep(200);
-        sender(10);
-        Thread.sleep(200);
-        sender(100);
-        Thread.sleep(200);
-        sender(1000);
-        Thread.sleep(200);
-        sender(10000);
-    }
-
-    /* ------------------------------------------------------------ */
-    public void sender(final int nb) throws Exception
-    {
-        final CountDownLatch latch=new CountDownLatch(nb);
-        long l0=System.currentTimeMillis();
-        for (int i=0; i<nb; i++)
-        {
-            final int n=i;
-            if (n%1000==0)
-            {
-                Thread.sleep(200);
-            }
-
-            HttpExchange httpExchange=new HttpExchange()
-            {
-                @Override
-                protected void onRequestCommitted()
-                {
-                    // System.err.println("Request committed");
-                }
-
-                @Override
-                protected void onResponseStatus(Buffer version, int status, Buffer reason)
-                {
-                    // System.err.println("Response Status: " + version+" "+status+" "+reason);
-                }
-
-                @Override
-                protected void onResponseHeader(Buffer name, Buffer value)
-                {
-                    // System.err.println("Response header: " + name + " = " + value);
-                }
-
-                @Override
-                protected void onResponseContent(Buffer content)
-                {
-                    // System.err.println("Response content:" + content);
-                }
-
-                @Override
-                protected void onResponseComplete()
-                {
-                    // System.err.println("Response completed "+n);
-                    latch.countDown();
-                }
-
-            };
-
-            httpExchange.setURL("http://localhost:"+_port+"/");
-            httpExchange.addRequestHeader("arbitrary","value");
-
-            _httpClient.send(httpExchange);
-        }
-
-        long last=latch.getCount();
-        while(last>0)
-        {
-            // System.err.println("waiting for "+last+" sent "+(System.currentTimeMillis()-l0)/1000 + "s ago ...");
-            latch.await(5,TimeUnit.SECONDS);
-            long next=latch.getCount();
-            if (last==next)
-                break;
-            last=next;
-        }
-        // System.err.println("missed "+latch.getCount()+" sent "+(System.currentTimeMillis()-l0)/1000 + "s ago.");
-        assertEquals(0,latch.getCount());
-        long l1=System.currentTimeMillis();
-    }
-
-    //TODO jaspi hangs ???
-//    public void testGetWithContentExchange() throws Exception
-//    {
-//        int i = 1;
-//
-//        final CyclicBarrier barrier = new CyclicBarrier(2);
-//        ContentExchange httpExchange = new ContentExchange()
-//        {
-//            protected void onResponseComplete() throws IOException
-//            {
-//                super.onResponseComplete();
-//                try{barrier.await();}catch(Exception e){}
-//            }
-//        };
-//        httpExchange.setURL("http://localhost:" + _port + "/?i=" + i);
-//        httpExchange.setMethod(HttpMethods.GET);
-//
-//        _httpClient.send(httpExchange);
-//
-//        try{barrier.await();}catch(Exception e){}
-//
-//    }
-    
-    
-    /* ------------------------------------------------------------ */
-    @Test
-    public void testDestinationSecurityCaching() throws Exception
-    {
-        final CyclicBarrier barrier = new CyclicBarrier(2);
-        
-        ContentExchange httpExchange = new ContentExchange()
-        {
-            @Override
-            protected void onResponseComplete() throws IOException
-            {
-                super.onResponseComplete();
-                try{barrier.await();}catch(Exception e){}
-            }
-        };
-        
-        httpExchange.setURL("http://localhost:" + _port + "/?i=1");
-        httpExchange.setMethod(HttpMethods.GET);
-        
-        _httpClient.send(httpExchange);
-
-        try{barrier.await();}catch(Exception e){}
-        
-        
-        barrier.reset();
-        ContentExchange httpExchange2 = new ContentExchange()
-        {
-            @Override
-            protected void onResponseComplete() throws IOException
-            {
-                super.onResponseComplete();
-                try{barrier.await();}catch(Exception e){}
-            }
-        };
-        
-        httpExchange2.setURL("http://localhost:" + _port + "/?i=2");
-        httpExchange2.setMethod(HttpMethods.GET);
-        
-        _httpClient.send(httpExchange2);
-
-        try{barrier.await();}catch(Exception e){}
-        
-        assertFalse( "exchange was retried", httpExchange2.getRetryStatus() );
-        
-    }   
-
-    /* ------------------------------------------------------------ */
-    public static void copyStream(InputStream in, OutputStream out)
-    {
-        try
-        {
-            byte[] buffer=new byte[1024];
-            int len;
-            while ((len=in.read(buffer))>=0)
-            {
-                out.write(buffer,0,len);
-            }
-        }
-        catch (EofException e)
-        {
-            System.err.println(e);
-        }
-        catch (IOException e)
-        {
-            e.printStackTrace();
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    private void startServer() throws Exception
-     {
-         _server = new Server();
-         _server.setGracefulShutdown(500);
-         Connector connector = new SelectChannelConnector();
-
-         connector.setPort(0);
-         _server.setConnectors(new Connector[]{connector});
-
-         Constraint constraint = new Constraint();
-         constraint.setName("Need User or Admin");
-         constraint.setRoles(new String[]{"user", "admin"});
-         constraint.setAuthenticate(true);
-
-         ConstraintMapping cm = new ConstraintMapping();
-         cm.setConstraint(constraint);
-         cm.setPathSpec("/*");
-
-         File realmPropFile = MavenTestingUtils.getTestResourceFile("realm.properties");
-         LoginService loginService = new HashLoginService("MyRealm",realmPropFile.getAbsolutePath());
-         ConstraintSecurityHandler sh = new ConstraintSecurityHandler();
-         sh.setLoginService(loginService);
-         sh.setAuthenticator(new BasicAuthenticator());
-         
-         //ServerAuthentication serverAuthentication = new BasicServerAuthentication(loginService, "MyRealm");
-         //sh.setServerAuthentication(serverAuthentication);
-         _server.setHandler(sh);
-
-         Handler testHandler = new AbstractHandler()
-         {
-             public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
-             {
-                 // System.out.println("passed authentication!");
-                 baseRequest.setHandled(true);
-                 response.setStatus(200);
-                 if (request.getServerName().equals("jetty.eclipse.org"))
-                 {
-                     response.getOutputStream().println("Proxy request: "+request.getRequestURL());
-                 }
-                 else if (request.getMethod().equalsIgnoreCase("GET"))
-                 {
-                     response.getOutputStream().println("<hello>");
-                     for (int i=0; i<100; i++)
-                     {
-                         response.getOutputStream().println("  <world>"+i+"</world>");
-                         if (i%20==0)
-                             response.getOutputStream().flush();
-                     }
-                     response.getOutputStream().println("</hello>");
-                 }
-                 else
-                 {
-                     copyStream(request.getInputStream(),response.getOutputStream());
-                 }
-             }
-         };
-
-         sh.setHandler(testHandler);
-
-         _server.start();
-         _port = connector.getLocalPort();
-     }
-
-    /* ------------------------------------------------------------ */
-    private void stopServer() throws Exception
-    {
-        _server.stop();
-    }
-}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/SelectConnectionTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/SelectConnectionTest.java
deleted file mode 100644
index b3b7d4b..0000000
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/SelectConnectionTest.java
+++ /dev/null
@@ -1,30 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client;
-
-public class SelectConnectionTest extends AbstractConnectionTest
-{
-    protected HttpClient newHttpClient()
-    {
-        HttpClient httpClient = new HttpClient();
-        httpClient.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);
-        httpClient.setConnectBlocking(true);
-        return httpClient;
-    }
-}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/Siege.java b/jetty-client/src/test/java/org/eclipse/jetty/client/Siege.java
deleted file mode 100644
index ff8025b..0000000
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/Siege.java
+++ /dev/null
@@ -1,229 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import org.eclipse.jetty.http.HttpMethods;
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.util.thread.QueuedThreadPool;
-
-
-/* ------------------------------------------------------------ */
-/** 
- */
-public class Siege
-{
-    private static final class ConcurrentExchange extends HttpExchange
-    {
-        private final long _start=System.currentTimeMillis();
-        private final HttpClient _client;
-        private final CountDownLatch _latch;
-        volatile int _status;
-        volatile int _count;
-        volatile long _bytes;
-        final List<String> _uris;
-        final int _repeats;
-        int _u;
-        int _r;
-        
-        AtomicBoolean counted=new AtomicBoolean(false);
-
-        public ConcurrentExchange(HttpClient client,CountDownLatch latch, List<String> uris, int repeats)
-        {
-            _client = client;
-            _latch = latch;
-            _uris = uris;
-            _repeats = repeats;
-        }
-
-        @Override
-        protected void onConnectionFailed(Throwable ex)
-        {
-            if (!counted.getAndSet(true))
-                _latch.countDown();
-            super.onConnectionFailed(ex);
-        }
-
-        @Override
-        protected void onException(Throwable ex)
-        {
-            if (!counted.getAndSet(true))
-                _latch.countDown();
-            super.onException(ex);
-        }
-
-        @Override
-        protected void onExpire()
-        {
-            if (!counted.getAndSet(true))
-                _latch.countDown();
-            super.onExpire();
-        }
-
-        @Override
-        protected void onResponseComplete() throws IOException
-        {
-            if (_status==200)
-                _count++;
-            if (!next() && !counted.getAndSet(true))
-            {
-                _latch.countDown();
-                long duration=System.currentTimeMillis()-_start;
-                System.err.printf("Got %d/%d with %dB in %dms %d%n",_count,_uris.size()*_repeats,_bytes,duration,_latch.getCount());
-            }
-        }
-        
-
-        /* ------------------------------------------------------------ */
-        @Override
-        protected void onResponseContent(Buffer content) throws IOException
-        {
-            _bytes+=content.length();
-            super.onResponseContent(content);
-        }
-
-        /* ------------------------------------------------------------ */
-        /**
-         * @see org.eclipse.jetty.client.HttpExchange#onResponseHeader(org.eclipse.jetty.io.Buffer, org.eclipse.jetty.io.Buffer)
-         */
-        @Override
-        protected void onResponseHeader(Buffer name, Buffer value) throws IOException
-        {
-            super.onResponseHeader(name,value);                
-            if ("Set-Cookie".equalsIgnoreCase(name.toString()))
-            {
-                String v=value.toString();
-                int c = v.indexOf(';');
-                if (c>=0)
-                    v=v.substring(0,c);
-                addRequestHeader("Cookie",v);
-            }
-        }
-
-        /* ------------------------------------------------------------ */
-        /**
-         * @see org.eclipse.jetty.client.HttpExchange#onResponseHeaderComplete()
-         */
-        @Override
-        protected void onResponseHeaderComplete() throws IOException
-        {
-            super.onResponseHeaderComplete();
-        }
-
-        /* ------------------------------------------------------------ */
-        /**
-         * @see org.eclipse.jetty.client.HttpExchange#onResponseStatus(org.eclipse.jetty.io.Buffer, int, org.eclipse.jetty.io.Buffer)
-         */
-        @Override
-        protected void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException
-        {
-            _status=status;
-            super.onResponseStatus(version,status,reason);
-        }
-
-        public boolean next()
-        {
-            if (_u>=_uris.size())
-            {
-                _u=0;
-                _r++;
-                if (_r>=_repeats)
-                    return false;
-            }
-            
-            String uri=_uris.get(_u++);
-            
-            reset();
-            setMethod(HttpMethods.GET);
-            setURL(uri);
-
-            try
-            {
-                _client.send(this);
-            }
-            catch(IOException e)
-            {
-                e.printStackTrace();
-                return false;
-            }
-            return true;
-        }
-    }
-
-    public static void main(String[] args)
-        throws Exception
-    {
-        if (args.length==0)
-            args=new String[] 
-                 { "-c", "2", "-r", "2", "http://localhost:8080/dump", "http://localhost:8080/d.txt"};
-        
-        int concurrent=1;
-        int repeats=1;
-        final List<String> uris = new ArrayList<String>();
-
-        for (int i=0; i<args.length; i++)
-        {
-            String arg=args[i];
-            if ("-c".equals(arg))
-            {
-                concurrent=Integer.parseInt(args[++i]);
-                continue;
-            }
-            
-            if ("-r".equals(arg))
-            {
-                repeats=Integer.parseInt(args[++i]);
-                continue;
-            }
-            
-            uris.add(arg);
-        }
-        
-        QueuedThreadPool pool = new QueuedThreadPool();
-        pool.setMaxThreads(500);
-        pool.setDaemon(true);
-        
-        HttpClient client = new HttpClient();
-        client.setThreadPool(pool);
-        client.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);
-        client.setIdleTimeout(30000);
-        client.setConnectTimeout(30000);
-        client.setMaxConnectionsPerAddress(concurrent*2);
-        client.start();
-        
-        final CountDownLatch latch = new CountDownLatch(concurrent);   
-        
-        
-        for (int i=0;i<concurrent;i++)
-        {
-            ConcurrentExchange ex = new ConcurrentExchange(client,latch,uris,repeats);
-            if (!ex.next())
-                latch.countDown();
-        }
-        
-        latch.await();
-        client.stop();
-        pool.stop();
-    }
-}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/SluggishServerTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/SluggishServerTest.java
deleted file mode 100644
index 1091c42..0000000
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/SluggishServerTest.java
+++ /dev/null
@@ -1,264 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-
-import javax.servlet.ServletException;
-import javax.servlet.ServletInputStream;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import junit.framework.Assert;
-
-import org.eclipse.jetty.http.HttpHeaderValues;
-import org.eclipse.jetty.http.HttpHeaders;
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.server.Request;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.bio.SocketConnector;
-import org.eclipse.jetty.server.handler.AbstractHandler;
-import org.junit.Test;
-
-/**
- * A class that attempts to simulate a client communicating with a slow server. It imposes a delay between each handle() call made to the server's handler.
- * 
- * The client sends a binary blob of data to the server, and this blob is then inspected to verify correct transfer.
- */
-public class SluggishServerTest
-{
-
-    /** msec to wait between reads in the handler -- may need to adjust based on OS/HW/etc. to reproduce bug */
-    private final static int READ_DELAY = 5;
-
-    private final static String URL = "http://localhost:";
-
-    /** Stream providing a binary message to send */
-    private static class SluggishStream extends InputStream
-    {
-        private final byte[] request;
-        private int pos;
-
-        public SluggishStream(byte[] request)
-        {
-            this.request = request;
-            this.pos = 0;
-        }
-
-        @Override
-        public int read() throws IOException
-        {
-            if (pos < request.length)
-            {
-                int byteVal = request[pos++] & 0xFF;
-                return byteVal;
-            }
-            else
-            {
-                return -1;
-            }
-        }
-
-    }
-
-    /** Sends a message containing random binary content to a SluggishHandler */
-    private static class SluggishExchange extends HttpExchange
-    {
-        private byte[] request;
-
-        public SluggishExchange(int port, int count)
-        {
-            request = new byte[count];
-            for (int i=0;i<count;i++)
-                request[i]=(byte)('A'+(i%26));
-            setURL(URL+port);
-            setRequestContentSource(new SluggishStream(request));
-            setRequestContentType("application/octet-stream");
-            setRequestHeader(HttpHeaders.TRANSFER_ENCODING,HttpHeaderValues.CHUNKED);
-        }
-
-        public byte[] getRequestBody()
-        {
-            return request;
-        }
-
-        @Override
-        protected void onRequestComplete()
-        {
-            //System.err.println("REQUEST COMPLETE " + this);
-        }
-
-        @Override
-        protected void onResponseComplete()
-        {
-            //System.err.println("RESPONSE COMPLETE " + this);
-        }
-
-        @Override
-        protected void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException
-        {
-            //System.err.printf("<<< %s %d %s%n",version,status,reason);
-            super.onResponseStatus(version,status,reason);
-        }
-
-        @Override
-        protected void onResponseHeader(Buffer name, Buffer value) throws IOException
-        {
-            //System.err.printf("<<< %s: %s%n",name,value);
-            super.onResponseHeader(name,value);
-        }
-
-        @Override
-        protected void onResponseHeaderComplete() throws IOException
-        {
-            //System.err.printf("<<< --%n");
-            super.onResponseHeaderComplete();
-        }
-        
-    }
-
-    /** Receives binary message from a SluggishExchange & stores it for validation */
-    private static class SluggishHandler extends AbstractHandler
-    {
-
-        private final ArrayList<Byte> accumulatedRequest;
-
-        public SluggishHandler(int requestSize)
-        {
-            accumulatedRequest = new ArrayList<Byte>(requestSize);
-        }
-
-        public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
-        {
-            accumulatedRequest.clear();
-            ServletInputStream input = request.getInputStream();
-            byte[] buffer = new byte[16384];
-            int bytesAvailable;
-            while ((bytesAvailable = input.read(buffer,0,buffer.length)) > 0)
-            {
-                //System.err.println("AVAILABLE FOR READ = " + bytesAvailable);
-                for (int n = 0; n < bytesAvailable; ++n)
-                {
-                    accumulatedRequest.add(buffer[n]);
-                }
-                try
-                {
-                    Thread.sleep(READ_DELAY);
-                }
-                catch (InterruptedException e)
-                {
-                    e.printStackTrace();
-                }
-            }
-            response.setStatus(HttpServletResponse.SC_OK);
-            baseRequest.setHandled(true);
-            //System.err.println("HANDLED");
-        }
-
-        public byte[] getAccumulatedRequest()
-        {
-            byte[] buffer = new byte[accumulatedRequest.size()];
-            int pos = 0;
-            for (Byte b : accumulatedRequest)
-            {
-                buffer[pos++] = b;
-            }
-            return buffer;
-        }
-    }
-
-    private static boolean compareBuffers(byte[] sent, byte[] received)
-    {
-        if (sent.length != received.length)
-        {
-            System.err.format("Mismatch in sent/received lengths: sent=%d received=%d\n",sent.length,received.length);
-            return false;
-        }
-        else
-        {
-            for (int n = 0; n < sent.length; ++n)
-            {
-                if (sent[n] != received[n])
-                {
-                    System.err.format("Mismatch at offset %d: request=%d response=%d\n",n,sent[n],received[n]);
-                    return false;
-                }
-            }
-        }
-        return true;
-    }
-
-    @Test
-    public void test0() throws Exception
-    {
-        goSlow(20000,10);    
-    }
-    
-    @Test
-    public void test1() throws Exception
-    {
-        goSlow(200000,5);    
-    }
-    
-    @Test
-    public void test2() throws Exception
-    {
-        goSlow(2000000,2);    
-    }
-    
-    void goSlow(int requestSize,int iterations) throws Exception
-    {
-        Server server = new Server();
-        SocketConnector connector = new SocketConnector();
-        server.addConnector(connector);
-        SluggishHandler handler = new SluggishHandler(requestSize);
-        server.setHandler(handler);
-        server.start();
-        int port = connector.getLocalPort();
-
-        HttpClient client = new HttpClient();
-        client.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);
-        client.setConnectTimeout(5000);
-        client.setIdleTimeout(60000);
-        client.start();
-
-        try
-        {
-            for (int i = 0; i < iterations; ++i)
-            {
-                //System.err.format("-------------- ITERATION %d ------------------\n",i);
-                SluggishExchange exchange = new SluggishExchange(port,requestSize);
-                long startTime = System.currentTimeMillis();
-                client.send(exchange);
-                exchange.waitForDone();
-                long endTime = System.currentTimeMillis();
-                //System.err.println("EXCHANGE STATUS = " + exchange);
-                //System.err.println("ELAPSED MSEC = " + (endTime - startTime));
-                Assert.assertTrue(compareBuffers(exchange.getRequestBody(),handler.getAccumulatedRequest()));
-            }
-        }
-        finally
-        {
-            server.stop();
-            server.join();
-        }
-    }
-}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/SocketConnectionTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/SocketConnectionTest.java
deleted file mode 100644
index 5408831..0000000
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/SocketConnectionTest.java
+++ /dev/null
@@ -1,101 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client;
-
-import static org.junit.Assert.assertEquals;
-
-import java.io.BufferedReader;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.net.ServerSocket;
-import java.net.Socket;
-import java.util.concurrent.CountDownLatch;
-
-public class SocketConnectionTest extends AbstractConnectionTest
-{
-    protected HttpClient newHttpClient()
-    {
-        HttpClient httpClient = new HttpClient();
-        httpClient.setConnectorType(HttpClient.CONNECTOR_SOCKET);
-        return httpClient;
-    }
-
-    @Override
-    public void testServerClosedConnection() throws Exception
-    {
-        // Differently from the SelectConnector, the SocketConnector cannot detect server closes.
-        // Therefore, upon a second send, the exchange will fail.
-        // Applications needs to retry it explicitly.
-
-        ServerSocket serverSocket = new ServerSocket();
-        serverSocket.bind(null);
-        int port=serverSocket.getLocalPort();
-
-        HttpClient httpClient = this.newHttpClient();
-        httpClient.setMaxConnectionsPerAddress(1);
-        httpClient.start();
-        try
-        {
-            CountDownLatch latch = new CountDownLatch(1);
-            HttpExchange exchange = new ConnectionExchange(latch);
-            exchange.setAddress(new Address("localhost", port));
-            exchange.setRequestURI("/");
-            httpClient.send(exchange);
-
-            Socket remote = serverSocket.accept();
-
-            // HttpClient.send() above is async, so if we write the response immediately
-            // there is a chance that it arrives before the request is being sent, so we
-            // read the request before sending the response to avoid the race
-            InputStream input = remote.getInputStream();
-            BufferedReader reader = new BufferedReader(new InputStreamReader(input, "UTF-8"));
-            String line;
-            while ((line = reader.readLine()) != null)
-            {
-                if (line.length() == 0)
-                    break;
-            }
-
-            OutputStream output = remote.getOutputStream();
-            output.write("HTTP/1.1 200 OK\r\n".getBytes("UTF-8"));
-            output.write("Content-Length: 0\r\n".getBytes("UTF-8"));
-            output.write("\r\n".getBytes("UTF-8"));
-            output.flush();
-
-            assertEquals(HttpExchange.STATUS_COMPLETED, exchange.waitForDone());
-
-            remote.close();
-
-            exchange.reset();
-            httpClient.send(exchange);
-
-            assertEquals(HttpExchange.STATUS_EXCEPTED, exchange.waitForDone());
-        }
-        finally
-        {
-            httpClient.stop();
-        }
-    }
-    
-    public void testIdleConnection() throws Exception
-    {
-        super.testIdleConnection();
-    }
-}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/Socks4ProxyTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/Socks4ProxyTest.java
new file mode 100644
index 0000000..bffca10
--- /dev/null
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/Socks4ProxyTest.java
@@ -0,0 +1,182 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.channels.ServerSocketChannel;
+import java.nio.channels.SocketChannel;
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.client.api.Result;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class Socks4ProxyTest
+{
+    private ServerSocketChannel server;
+    private HttpClient client;
+
+    @Before
+    public void prepare() throws Exception
+    {
+        server = ServerSocketChannel.open();
+        server.bind(new InetSocketAddress("localhost", 0));
+
+        client = new HttpClient();
+        client.start();
+    }
+
+    @After
+    public void dispose() throws Exception
+    {
+        client.stop();
+        server.close();
+    }
+
+    @Test
+    public void testSocks4Proxy() throws Exception
+    {
+        int proxyPort = server.socket().getLocalPort();
+        client.getProxyConfiguration().getProxies().add(new Socks4Proxy("localhost", proxyPort));
+
+        final CountDownLatch latch = new CountDownLatch(1);
+
+        byte ip1 = 127;
+        byte ip2 = 0;
+        byte ip3 = 0;
+        byte ip4 = 13;
+        String serverHost = ip1 + "." + ip2 + "." + ip3 + "." + ip4;
+        int serverPort = proxyPort + 1; // Any port will do
+        client.newRequest(serverHost, serverPort)
+                .path("/path")
+                .timeout(5, TimeUnit.SECONDS)
+                .send(new Response.CompleteListener()
+                {
+                    @Override
+                    public void onComplete(Result result)
+                    {
+                        if (result.isSucceeded())
+                            latch.countDown();
+                    }
+                });
+
+        SocketChannel channel = server.accept();
+
+        int socks4MessageLength = 9;
+        ByteBuffer buffer = ByteBuffer.allocate(socks4MessageLength);
+        int read = channel.read(buffer);
+        Assert.assertEquals(socks4MessageLength, read);
+        Assert.assertEquals(4, buffer.get(0) & 0xFF);
+        Assert.assertEquals(1, buffer.get(1) & 0xFF);
+        Assert.assertEquals(serverPort, buffer.getShort(2) & 0xFFFF);
+        Assert.assertEquals(ip1, buffer.get(4) & 0xFF);
+        Assert.assertEquals(ip2, buffer.get(5) & 0xFF);
+        Assert.assertEquals(ip3, buffer.get(6) & 0xFF);
+        Assert.assertEquals(ip4, buffer.get(7) & 0xFF);
+        Assert.assertEquals(0, buffer.get(8) & 0xFF);
+
+        // Socks4 response.
+        channel.write(ByteBuffer.wrap(new byte[]{0, 0x5A, 0, 0, 0, 0, 0, 0}));
+
+        buffer = ByteBuffer.allocate(3);
+        read = channel.read(buffer);
+        Assert.assertEquals(3, read);
+        buffer.flip();
+        Assert.assertEquals("GET", StandardCharsets.UTF_8.decode(buffer).toString());
+
+        // Response
+        String response = "" +
+                "HTTP/1.1 200 OK\r\n" +
+                "Content-Length: 0\r\n" +
+                "Connection: close\r\n" +
+                "\r\n";
+        channel.write(ByteBuffer.wrap(response.getBytes("UTF-8")));
+
+        Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+
+        channel.close();
+    }
+
+    @Test
+    public void testSocks4ProxyWithSplitResponse() throws Exception
+    {
+        int proxyPort = server.socket().getLocalPort();
+        client.getProxyConfiguration().getProxies().add(new Socks4Proxy("localhost", proxyPort));
+
+        final CountDownLatch latch = new CountDownLatch(1);
+
+        String serverHost = "127.0.0.13"; // Test expects an IP address.
+        int serverPort = proxyPort + 1; // Any port will do
+        client.newRequest(serverHost, serverPort)
+                .path("/path")
+                .timeout(5, TimeUnit.SECONDS)
+                .send(new Response.CompleteListener()
+                {
+                    @Override
+                    public void onComplete(Result result)
+                    {
+                        if (result.isSucceeded())
+                            latch.countDown();
+                        else
+                            result.getFailure().printStackTrace();
+                    }
+                });
+
+        SocketChannel channel = server.accept();
+
+        int socks4MessageLength = 9;
+        ByteBuffer buffer = ByteBuffer.allocate(socks4MessageLength);
+        int read = channel.read(buffer);
+        Assert.assertEquals(socks4MessageLength, read);
+
+        // Socks4 response, with split bytes.
+        byte[] chunk1 = new byte[]{0, 0x5A, 0};
+        byte[] chunk2 = new byte[]{0, 0, 0, 0, 0};
+        channel.write(ByteBuffer.wrap(chunk1));
+
+        // Wait before sending the second chunk.
+        Thread.sleep(1000);
+
+        channel.write(ByteBuffer.wrap(chunk2));
+
+        buffer = ByteBuffer.allocate(3);
+        read = channel.read(buffer);
+        Assert.assertEquals(3, read);
+        buffer.flip();
+        Assert.assertEquals("GET", StandardCharsets.UTF_8.decode(buffer).toString());
+
+        // Response
+        String response = "" +
+                "HTTP/1.1 200 OK\r\n" +
+                "Content-Length: 0\r\n" +
+                "Connection: close\r\n" +
+                "\r\n";
+        channel.write(ByteBuffer.wrap(response.getBytes("UTF-8")));
+
+        Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+
+        channel.close();
+    }
+}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/SslBytesClientTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/SslBytesClientTest.java
deleted file mode 100644
index 55e97eb..0000000
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/SslBytesClientTest.java
+++ /dev/null
@@ -1,166 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
-
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLServerSocket;
-import javax.net.ssl.SSLSocket;
-
-import org.eclipse.jetty.http.HttpMethods;
-import org.eclipse.jetty.http.HttpStatus;
-import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
-import org.eclipse.jetty.util.ssl.SslContextFactory;
-import org.junit.After;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-
-public class SslBytesClientTest extends SslBytesTest
-{
-    private ExecutorService threadPool;
-    private HttpClient client;
-    private SimpleProxy proxy;
-    private SSLServerSocket acceptor;
-
-    @Before
-    public void init() throws Exception
-    {
-        threadPool = Executors.newCachedThreadPool();
-
-        client = new HttpClient();
-        client.setMaxConnectionsPerAddress(1);
-        client.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);
-        File keyStore = MavenTestingUtils.getTestResourceFile("keystore");
-        SslContextFactory cf = client.getSslContextFactory();
-        cf.setKeyStorePath(keyStore.getAbsolutePath());
-        cf.setKeyStorePassword("storepwd");
-        cf.setKeyManagerPassword("keypwd");
-        client.start();
-
-        SSLContext sslContext = cf.getSslContext();
-        acceptor = (SSLServerSocket)sslContext.getServerSocketFactory().createServerSocket(0);
-
-        int serverPort = acceptor.getLocalPort();
-
-        proxy = new SimpleProxy(threadPool, "localhost", serverPort);
-        proxy.start();
-        logger.debug(":{} <==> :{}", proxy.getPort(), serverPort);
-    }
-
-    @After
-    public void destroy() throws Exception
-    {
-        if (acceptor != null)
-            acceptor.close();
-        if (proxy != null)
-            proxy.stop();
-        if (client != null)
-            client.stop();
-        if (threadPool != null)
-            threadPool.shutdownNow();
-    }
-
-    @Test
-    public void testHandshake() throws Exception
-    {
-        ContentExchange exchange = new ContentExchange(true);
-        exchange.setURL("https://localhost:" + proxy.getPort());
-        String method = HttpMethods.GET;
-        exchange.setMethod(method);
-        client.send(exchange);
-        Assert.assertTrue(proxy.awaitClient(5, TimeUnit.SECONDS));
-
-        final SSLSocket server = (SSLSocket)acceptor.accept();
-        server.setUseClientMode(false);
-
-        Future<Object> handshake = threadPool.submit(new Callable<Object>()
-        {
-            public Object call() throws Exception
-            {
-                server.startHandshake();
-                return null;
-            }
-        });
-
-        // Client Hello
-        TLSRecord record = proxy.readFromClient();
-        Assert.assertEquals(TLSRecord.Type.HANDSHAKE, record.getType());
-        proxy.flushToServer(record);
-
-        // Server Hello + Certificate + Server Done
-        record = proxy.readFromServer();
-        Assert.assertEquals(TLSRecord.Type.HANDSHAKE, record.getType());
-        proxy.flushToClient(record);
-
-        // Client Key Exchange
-        record = proxy.readFromClient();
-        Assert.assertEquals(TLSRecord.Type.HANDSHAKE, record.getType());
-        proxy.flushToServer(record);
-
-        // Change Cipher Spec
-        record = proxy.readFromClient();
-        Assert.assertEquals(TLSRecord.Type.CHANGE_CIPHER_SPEC, record.getType());
-        proxy.flushToServer(record);
-
-        // Client Done
-        record = proxy.readFromClient();
-        Assert.assertEquals(TLSRecord.Type.HANDSHAKE, record.getType());
-        proxy.flushToServer(record);
-
-        // Change Cipher Spec
-        record = proxy.readFromServer();
-        Assert.assertEquals(TLSRecord.Type.CHANGE_CIPHER_SPEC, record.getType());
-        proxy.flushToClient(record);
-
-        // Server Done
-        record = proxy.readFromServer();
-        Assert.assertEquals(TLSRecord.Type.HANDSHAKE, record.getType());
-        proxy.flushToClient(record);
-
-        Assert.assertNull(handshake.get(5, TimeUnit.SECONDS));
-
-        SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow();
-        // Read request
-        BufferedReader reader = new BufferedReader(new InputStreamReader(server.getInputStream(), "UTF-8"));
-        String line = reader.readLine();
-        Assert.assertTrue(line.startsWith(method));
-        while (line.length() > 0)
-            line = reader.readLine();
-        // Write response
-        OutputStream output = server.getOutputStream();
-        output.write(("HTTP/1.1 200 OK\r\n" +
-                "Content-Length: 0\r\n" +
-                "\r\n").getBytes("UTF-8"));
-        output.flush();
-        Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS));
-
-        Assert.assertEquals(HttpExchange.STATUS_COMPLETED, exchange.waitForDone());
-        Assert.assertEquals(HttpStatus.OK_200, exchange.getResponseStatus());
-    }
-}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/SslBytesServerTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/SslBytesServerTest.java
deleted file mode 100644
index 0ed5947..0000000
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/SslBytesServerTest.java
+++ /dev/null
@@ -1,1783 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client;
-
-import java.io.BufferedReader;
-import java.io.EOFException;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.net.SocketTimeoutException;
-import java.nio.channels.SocketChannel;
-import java.util.Arrays;
-import java.util.concurrent.Callable;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLEngine;
-import javax.net.ssl.SSLSocket;
-import javax.servlet.ServletException;
-import javax.servlet.ServletInputStream;
-import javax.servlet.ServletOutputStream;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jetty.http.HttpParser;
-import org.eclipse.jetty.io.AsyncEndPoint;
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.Buffers;
-import org.eclipse.jetty.io.Connection;
-import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.io.nio.AsyncConnection;
-import org.eclipse.jetty.io.nio.SslConnection;
-import org.eclipse.jetty.server.AsyncHttpConnection;
-import org.eclipse.jetty.server.Request;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.handler.AbstractHandler;
-import org.eclipse.jetty.server.ssl.SslSelectChannelConnector;
-import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
-import org.eclipse.jetty.toolchain.test.OS;
-import org.eclipse.jetty.util.component.Dumpable;
-import org.eclipse.jetty.util.ssl.SslContextFactory;
-import org.junit.After;
-import org.junit.Assert;
-import org.junit.Assume;
-import org.junit.Before;
-import org.junit.Test;
-
-import static org.hamcrest.Matchers.containsString;
-import static org.hamcrest.Matchers.greaterThan;
-import static org.hamcrest.Matchers.lessThan;
-import static org.hamcrest.Matchers.not;
-
-public class SslBytesServerTest extends SslBytesTest
-{
-    private final AtomicInteger sslHandles = new AtomicInteger();
-    private final AtomicInteger sslFlushes = new AtomicInteger();
-    private final AtomicInteger httpParses = new AtomicInteger();
-    private final AtomicReference<EndPoint> serverEndPoint = new AtomicReference<EndPoint>();
-    private final int idleTimeout = 2000;
-    private ExecutorService threadPool;
-    private Server server;
-    private int serverPort;
-    private SSLContext sslContext;
-    private SimpleProxy proxy;
-    private Runnable idleHook;
-
-    @Before
-    public void init() throws Exception
-    {
-        threadPool = Executors.newCachedThreadPool();
-        server = new Server();
-
-        SslSelectChannelConnector connector = new SslSelectChannelConnector()
-        {
-            @Override
-            protected SslConnection newSslConnection(AsyncEndPoint endPoint, SSLEngine engine)
-            {
-                serverEndPoint.set(endPoint);
-                return new SslConnection(engine, endPoint)
-                {
-                    @Override
-                    public Connection handle() throws IOException
-                    {
-                        sslHandles.incrementAndGet();
-                        return super.handle();
-                    }
-
-                    @Override
-                    protected SslEndPoint newSslEndPoint()
-                    {
-                        return new SslEndPoint()
-                        {
-                            @Override
-                            public int flush(Buffer buffer) throws IOException
-                            {
-                                sslFlushes.incrementAndGet();
-                                return super.flush(buffer);
-                            }
-                        };
-                    }
-
-                    @Override
-                    public void onIdleExpired(long idleForMs)
-                    {
-                        final Runnable idleHook = SslBytesServerTest.this.idleHook;
-                        if (idleHook != null)
-                            idleHook.run();
-                        super.onIdleExpired(idleForMs);
-                    }
-                };
-            }
-
-            @Override
-            protected AsyncConnection newPlainConnection(SocketChannel channel, AsyncEndPoint endPoint)
-            {
-                return new AsyncHttpConnection(this, endPoint, getServer())
-                {
-                    @Override
-                    protected HttpParser newHttpParser(Buffers requestBuffers, EndPoint endPoint, HttpParser.EventHandler requestHandler)
-                    {
-                        return new HttpParser(requestBuffers, endPoint, requestHandler)
-                        {
-                            @Override
-                            public int parseNext() throws IOException
-                            {
-                                httpParses.incrementAndGet();
-                                return super.parseNext();
-                            }
-                        };
-                    }
-                };
-            }
-        };
-        connector.setMaxIdleTime(idleTimeout);
-
-//        connector.setPort(5870);
-        connector.setPort(0);
-
-        File keyStore = MavenTestingUtils.getTestResourceFile("keystore");
-        SslContextFactory cf = connector.getSslContextFactory();
-        cf.setKeyStorePath(keyStore.getAbsolutePath());
-        cf.setKeyStorePassword("storepwd");
-        cf.setKeyManagerPassword("keypwd");
-        server.addConnector(connector);
-        server.setHandler(new AbstractHandler()
-        {
-            public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException
-            {
-                try
-                {
-                    request.setHandled(true);
-                    String contentLength = request.getHeader("Content-Length");
-                    if (contentLength != null)
-                    {
-                        int length = Integer.parseInt(contentLength);
-                        ServletInputStream input = httpRequest.getInputStream();
-                        ServletOutputStream output = httpResponse.getOutputStream();
-                        byte[] buffer = new byte[32 * 1024];
-                        while (length > 0)
-                        {
-                            int read = input.read(buffer);
-                            if (read < 0)
-                                throw new EOFException();
-                            length -= read;
-                            if (target.startsWith("/echo"))
-                                output.write(buffer, 0, read);
-                        }
-                    }
-                }
-                catch (IOException x)
-                {
-                    if (!(target.endsWith("suppress_exception")))
-                        throw x;
-                }
-            }
-        });
-        server.start();
-        serverPort = connector.getLocalPort();
-
-        sslContext = cf.getSslContext();
-
-        proxy = new SimpleProxy(threadPool, "localhost", serverPort);
-        proxy.start();
-        logger.debug(":{} <==> :{}", proxy.getPort(), serverPort);
-    }
-
-    @After
-    public void destroy() throws Exception
-    {
-        if (proxy != null)
-            proxy.stop();
-        if (server != null)
-            server.stop();
-        if (threadPool != null)
-            threadPool.shutdownNow();
-    }
-
-    @Test
-    public void testHandshake() throws Exception
-    {
-        final SSLSocket client = newClient();
-
-        Future<Object> handshake = threadPool.submit(new Callable<Object>()
-        {
-            public Object call() throws Exception
-            {
-                client.startHandshake();
-                return null;
-            }
-        });
-
-        // Client Hello
-        TLSRecord record = proxy.readFromClient();
-        Assert.assertNotNull(record);
-        proxy.flushToServer(record);
-
-        // Server Hello + Certificate + Server Done
-        record = proxy.readFromServer();
-        Assert.assertNotNull(record);
-        proxy.flushToClient(record);
-
-        // Client Key Exchange
-        record = proxy.readFromClient();
-        Assert.assertNotNull(record);
-        proxy.flushToServer(record);
-
-        // Change Cipher Spec
-        record = proxy.readFromClient();
-        Assert.assertNotNull(record);
-        proxy.flushToServer(record);
-
-        // Client Done
-        record = proxy.readFromClient();
-        Assert.assertNotNull(record);
-        proxy.flushToServer(record);
-
-        // Change Cipher Spec
-        record = proxy.readFromServer();
-        Assert.assertNotNull(record);
-        proxy.flushToClient(record);
-
-        // Server Done
-        record = proxy.readFromServer();
-        Assert.assertNotNull(record);
-        proxy.flushToClient(record);
-
-        Assert.assertNull(handshake.get(5, TimeUnit.SECONDS));
-
-        // Check that we did not spin
-        TimeUnit.MILLISECONDS.sleep(500);
-        Assert.assertThat(sslHandles.get(), lessThan(20));
-        Assert.assertThat(sslFlushes.get(), lessThan(20));
-        Assert.assertThat(httpParses.get(), lessThan(50));
-
-        closeClient(client);
-    }
-
-    @Test
-    public void testHandshakeWithSplitBoundary() throws Exception
-    {
-        final SSLSocket client = newClient();
-
-        Future<Object> handshake = threadPool.submit(new Callable<Object>()
-        {
-            public Object call() throws Exception
-            {
-                client.startHandshake();
-                return null;
-            }
-        });
-
-        // Client Hello
-        TLSRecord record = proxy.readFromClient();
-        byte[] bytes = record.getBytes();
-        byte[] chunk1 = new byte[2 * bytes.length / 3];
-        System.arraycopy(bytes, 0, chunk1, 0, chunk1.length);
-        byte[] chunk2 = new byte[bytes.length - chunk1.length];
-        System.arraycopy(bytes, chunk1.length, chunk2, 0, chunk2.length);
-        proxy.flushToServer(100, chunk1);
-        proxy.flushToServer(100, chunk2);
-
-        // Server Hello + Certificate + Server Done
-        record = proxy.readFromServer();
-        proxy.flushToClient(record);
-
-        // Client Key Exchange
-        record = proxy.readFromClient();
-        bytes = record.getBytes();
-        chunk1 = new byte[2 * bytes.length / 3];
-        System.arraycopy(bytes, 0, chunk1, 0, chunk1.length);
-        chunk2 = new byte[bytes.length - chunk1.length];
-        System.arraycopy(bytes, chunk1.length, chunk2, 0, chunk2.length);
-        proxy.flushToServer(100, chunk1);
-        proxy.flushToServer(100, chunk2);
-
-        // Change Cipher Spec
-        record = proxy.readFromClient();
-        bytes = record.getBytes();
-        chunk1 = new byte[2 * bytes.length / 3];
-        System.arraycopy(bytes, 0, chunk1, 0, chunk1.length);
-        chunk2 = new byte[bytes.length - chunk1.length];
-        System.arraycopy(bytes, chunk1.length, chunk2, 0, chunk2.length);
-        proxy.flushToServer(100, chunk1);
-        proxy.flushToServer(100, chunk2);
-
-        // Client Done
-        record = proxy.readFromClient();
-        bytes = record.getBytes();
-        chunk1 = new byte[2 * bytes.length / 3];
-        System.arraycopy(bytes, 0, chunk1, 0, chunk1.length);
-        chunk2 = new byte[bytes.length - chunk1.length];
-        System.arraycopy(bytes, chunk1.length, chunk2, 0, chunk2.length);
-        proxy.flushToServer(100, chunk1);
-        proxy.flushToServer(100, chunk2);
-
-        // Change Cipher Spec
-        record = proxy.readFromServer();
-        Assert.assertNotNull(record);
-        proxy.flushToClient(record);
-
-        // Server Done
-        record = proxy.readFromServer();
-        Assert.assertNotNull(record);
-        proxy.flushToClient(record);
-
-        Assert.assertNull(handshake.get(5, TimeUnit.SECONDS));
-
-        // Check that we did not spin
-        TimeUnit.MILLISECONDS.sleep(500);
-        Assert.assertThat(sslHandles.get(), lessThan(20));
-        Assert.assertThat(sslFlushes.get(), lessThan(20));
-        Assert.assertThat(httpParses.get(), lessThan(50));
-
-        client.close();
-
-        // Close Alert
-        record = proxy.readFromClient();
-        bytes = record.getBytes();
-        chunk1 = new byte[2 * bytes.length / 3];
-        System.arraycopy(bytes, 0, chunk1, 0, chunk1.length);
-        chunk2 = new byte[bytes.length - chunk1.length];
-        System.arraycopy(bytes, chunk1.length, chunk2, 0, chunk2.length);
-        proxy.flushToServer(100, chunk1);
-        proxy.flushToServer(100, chunk2);
-        // Socket close
-        record = proxy.readFromClient();
-        Assert.assertNull(String.valueOf(record), record);
-        proxy.flushToServer(record);
-
-        // Close Alert
-        record = proxy.readFromServer();
-        proxy.flushToClient(record);
-        // Socket close
-        record = proxy.readFromServer();
-        Assert.assertNull(String.valueOf(record), record);
-        proxy.flushToClient(record);
-    }
-
-    @Test
-    public void testClientHelloIncompleteThenReset() throws Exception
-    {
-        final SSLSocket client = newClient();
-
-        threadPool.submit(new Callable<Object>()
-        {
-            public Object call() throws Exception
-            {
-                client.startHandshake();
-                return null;
-            }
-        });
-
-        // Client Hello
-        TLSRecord record = proxy.readFromClient();
-        byte[] bytes = record.getBytes();
-        byte[] chunk1 = new byte[2 * bytes.length / 3];
-        System.arraycopy(bytes, 0, chunk1, 0, chunk1.length);
-        proxy.flushToServer(100, chunk1);
-
-        proxy.sendRSTToServer();
-
-        // Wait a while to detect spinning
-        TimeUnit.MILLISECONDS.sleep(500);
-        Assert.assertThat(sslHandles.get(), lessThan(20));
-        Assert.assertThat(sslFlushes.get(), lessThan(20));
-        Assert.assertThat(httpParses.get(), lessThan(50));
-
-        client.close();
-    }
-
-    @Test
-    public void testClientHelloThenReset() throws Exception
-    {
-        final SSLSocket client = newClient();
-
-        threadPool.submit(new Callable<Object>()
-        {
-            public Object call() throws Exception
-            {
-                client.startHandshake();
-                return null;
-            }
-        });
-
-        // Client Hello
-        TLSRecord record = proxy.readFromClient();
-        Assert.assertNotNull(record);
-        proxy.flushToServer(record);
-
-        proxy.sendRSTToServer();
-
-        // Wait a while to detect spinning
-        TimeUnit.MILLISECONDS.sleep(500);
-        Assert.assertThat(sslHandles.get(), lessThan(20));
-        Assert.assertThat(sslFlushes.get(), lessThan(20));
-        Assert.assertThat(httpParses.get(), lessThan(50));
-
-        client.close();
-    }
-
-    @Test
-    public void testHandshakeThenReset() throws Exception
-    {
-        final SSLSocket client = newClient();
-
-        SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow();
-        client.startHandshake();
-        Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS));
-
-        proxy.sendRSTToServer();
-
-        // Wait a while to detect spinning
-        TimeUnit.MILLISECONDS.sleep(500);
-        Assert.assertThat(sslHandles.get(), lessThan(20));
-        Assert.assertThat(sslFlushes.get(), lessThan(20));
-        Assert.assertThat(httpParses.get(), lessThan(50));
-
-        client.close();
-    }
-
-    @Test
-    public void testRequestIncompleteThenReset() throws Exception
-    {
-        final SSLSocket client = newClient();
-
-        SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow();
-        client.startHandshake();
-        Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS));
-
-        threadPool.submit(new Callable<Object>()
-        {
-            public Object call() throws Exception
-            {
-                OutputStream clientOutput = client.getOutputStream();
-                clientOutput.write(("" +
-                        "GET / HTTP/1.1\r\n" +
-                        "Host: localhost\r\n" +
-                        "\r\n").getBytes("UTF-8"));
-                clientOutput.flush();
-                return null;
-            }
-        });
-
-        // Application data
-        TLSRecord record = proxy.readFromClient();
-        byte[] bytes = record.getBytes();
-        byte[] chunk1 = new byte[2 * bytes.length / 3];
-        System.arraycopy(bytes, 0, chunk1, 0, chunk1.length);
-        proxy.flushToServer(100, chunk1);
-
-        proxy.sendRSTToServer();
-
-        // Wait a while to detect spinning
-        TimeUnit.MILLISECONDS.sleep(500);
-        Assert.assertThat(sslHandles.get(), lessThan(20));
-        Assert.assertThat(sslFlushes.get(), lessThan(20));
-        Assert.assertThat(httpParses.get(), lessThan(50));
-
-        client.close();
-    }
-
-    @Test
-    public void testRequestResponse() throws Exception
-    {
-        final SSLSocket client = newClient();
-
-        SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow();
-        client.startHandshake();
-        Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS));
-
-        Future<Object> request = threadPool.submit(new Callable<Object>()
-        {
-            public Object call() throws Exception
-            {
-                OutputStream clientOutput = client.getOutputStream();
-                clientOutput.write(("" +
-                        "GET / HTTP/1.1\r\n" +
-                        "Host: localhost\r\n" +
-                        "\r\n").getBytes("UTF-8"));
-                clientOutput.flush();
-                return null;
-            }
-        });
-
-        // Application data
-        TLSRecord record = proxy.readFromClient();
-        proxy.flushToServer(record);
-        Assert.assertNull(request.get(5, TimeUnit.SECONDS));
-
-        // Application data
-        record = proxy.readFromServer();
-        Assert.assertEquals(TLSRecord.Type.APPLICATION, record.getType());
-        proxy.flushToClient(record);
-
-        BufferedReader reader = new BufferedReader(new InputStreamReader(client.getInputStream(), "UTF-8"));
-        String line = reader.readLine();
-        Assert.assertNotNull(line);
-        Assert.assertTrue(line.startsWith("HTTP/1.1 200 "));
-        while ((line = reader.readLine()) != null)
-        {
-            if (line.trim().length() == 0)
-                break;
-        }
-
-        // Check that we did not spin
-        TimeUnit.MILLISECONDS.sleep(500);
-        Assert.assertThat(sslHandles.get(), lessThan(20));
-        Assert.assertThat(sslFlushes.get(), lessThan(20));
-        Assert.assertThat(httpParses.get(), lessThan(50));
-
-        closeClient(client);
-    }
-
-    @Test
-    public void testHandshakeAndRequestOneByteAtATime() throws Exception
-    {
-        final SSLSocket client = newClient();
-
-        Future<Object> handshake = threadPool.submit(new Callable<Object>()
-        {
-            public Object call() throws Exception
-            {
-                client.startHandshake();
-                return null;
-            }
-        });
-
-        // Client Hello
-        TLSRecord record = proxy.readFromClient();
-        for (byte b : record.getBytes())
-            proxy.flushToServer(50, b);
-
-        // Server Hello + Certificate + Server Done
-        record = proxy.readFromServer();
-        proxy.flushToClient(record);
-
-        // Client Key Exchange
-        record = proxy.readFromClient();
-        for (byte b : record.getBytes())
-            proxy.flushToServer(50, b);
-
-        // Change Cipher Spec
-        record = proxy.readFromClient();
-        for (byte b : record.getBytes())
-            proxy.flushToServer(50, b);
-
-        // Client Done
-        record = proxy.readFromClient();
-        for (byte b : record.getBytes())
-            proxy.flushToServer(50, b);
-
-        // Change Cipher Spec
-        record = proxy.readFromServer();
-        proxy.flushToClient(record);
-
-        // Server Done
-        record = proxy.readFromServer();
-        proxy.flushToClient(record);
-
-        Assert.assertNull(handshake.get(5, TimeUnit.SECONDS));
-
-        Future<Object> request = threadPool.submit(new Callable<Object>()
-        {
-            public Object call() throws Exception
-            {
-                OutputStream clientOutput = client.getOutputStream();
-                clientOutput.write(("" +
-                        "GET / HTTP/1.1\r\n" +
-                        "Host: localhost\r\n" +
-                        "\r\n").getBytes("UTF-8"));
-                clientOutput.flush();
-                return null;
-            }
-        });
-
-        // Application data
-        record = proxy.readFromClient();
-        for (byte b : record.getBytes())
-            proxy.flushToServer(50, b);
-        Assert.assertNull(request.get(5, TimeUnit.SECONDS));
-
-        // Application data
-        record = proxy.readFromServer();
-        Assert.assertEquals(TLSRecord.Type.APPLICATION, record.getType());
-        proxy.flushToClient(record);
-
-        BufferedReader reader = new BufferedReader(new InputStreamReader(client.getInputStream(), "UTF-8"));
-        String line = reader.readLine();
-        Assert.assertNotNull(line);
-        Assert.assertTrue(line.startsWith("HTTP/1.1 200 "));
-        while ((line = reader.readLine()) != null)
-        {
-            if (line.trim().length() == 0)
-                break;
-        }
-
-        // Check that we did not spin
-        TimeUnit.MILLISECONDS.sleep(1000);
-        Assert.assertThat(sslHandles.get(), lessThan(750));
-        Assert.assertThat(sslFlushes.get(), lessThan(750));
-        // An average of 958 httpParses is seen in standard Oracle JDK's
-        // An average of 1183 httpParses is seen in OpenJDK JVMs.
-        Assert.assertThat(httpParses.get(), lessThan(1500));
-
-        client.close();
-
-        // Close Alert
-        record = proxy.readFromClient();
-        for (byte b : record.getBytes())
-            proxy.flushToServer(50, b);
-        // Socket close
-        record = proxy.readFromClient();
-        Assert.assertNull(String.valueOf(record), record);
-        proxy.flushToServer(record);
-
-        // Close Alert
-        record = proxy.readFromServer();
-        proxy.flushToClient(record);
-        // Socket close
-        record = proxy.readFromServer();
-        Assert.assertNull(String.valueOf(record), record);
-        proxy.flushToClient(record);
-    }
-
-    @Test
-    public void testRequestWithCloseAlertAndShutdown() throws Exception
-    {
-        // See next test on why we only run in Linux
-        Assume.assumeTrue(OS.IS_LINUX);
-
-        final SSLSocket client = newClient();
-
-        SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow();
-        client.startHandshake();
-        Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS));
-
-        Future<Object> request = threadPool.submit(new Callable<Object>()
-        {
-            public Object call() throws Exception
-            {
-                OutputStream clientOutput = client.getOutputStream();
-                clientOutput.write(("" +
-                        "GET / HTTP/1.1\r\n" +
-                        "Host: localhost\r\n" +
-                        "\r\n").getBytes("UTF-8"));
-                clientOutput.flush();
-                return null;
-            }
-        });
-
-        // Application data
-        TLSRecord record = proxy.readFromClient();
-        proxy.flushToServer(record);
-        Assert.assertNull(request.get(5, TimeUnit.SECONDS));
-
-        client.close();
-
-        // Close Alert
-        record = proxy.readFromClient();
-        proxy.flushToServer(record);
-        // Socket close
-        record = proxy.readFromClient();
-        Assert.assertNull(String.valueOf(record), record);
-        proxy.flushToServer(record);
-
-        // Expect response from server
-        // SSLSocket is limited and we cannot read the response, but we make sure
-        // it is application data and not a close alert
-        record = proxy.readFromServer();
-        Assert.assertNotNull(record);
-        Assert.assertEquals(TLSRecord.Type.APPLICATION, record.getType());
-        proxy.flushToClient(record);
-
-        // Close Alert
-        record = proxy.readFromServer();
-        Assert.assertNotNull(record);
-        Assert.assertEquals(TLSRecord.Type.ALERT, record.getType());
-        // We can't forward to the client, its socket is already closed
-
-        // Check that we did not spin
-        TimeUnit.MILLISECONDS.sleep(500);
-        Assert.assertThat(sslHandles.get(), lessThan(20));
-        Assert.assertThat(sslFlushes.get(), lessThan(20));
-        Assert.assertThat(httpParses.get(), lessThan(50));
-
-        // Socket close
-        record = proxy.readFromServer();
-        Assert.assertNull(String.valueOf(record), record);
-        proxy.flushToClient(record);
-    }
-
-    @Test
-    public void testRequestWithCloseAlert() throws Exception
-    {
-        // Currently we are ignoring this test on anything other then linux
-        // http://tools.ietf.org/html/rfc2246#section-7.2.1
-
-        // TODO (react to this portion which seems to allow win/mac behavior)
-        // It is required that the other party respond with a close_notify alert of its own
-        // and close down the connection immediately, discarding any pending writes. It is not
-        // required for the initiator of the close to wait for the responding
-        // close_notify alert before closing the read side of the connection.
-        Assume.assumeTrue(OS.IS_LINUX);
-
-        final SSLSocket client = newClient();
-
-        SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow();
-        client.startHandshake();
-        Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS));
-
-        Future<Object> request = threadPool.submit(new Callable<Object>()
-        {
-            public Object call() throws Exception
-            {
-                OutputStream clientOutput = client.getOutputStream();
-                clientOutput.write(("" +
-                        "GET / HTTP/1.1\r\n" +
-                        "Host: localhost\r\n" +
-                        "\r\n").getBytes("UTF-8"));
-                clientOutput.flush();
-                return null;
-            }
-        });
-
-        // Application data
-        TLSRecord record = proxy.readFromClient();
-        Assert.assertEquals(TLSRecord.Type.APPLICATION, record.getType());
-        proxy.flushToServer(record);
-        Assert.assertNull(request.get(5, TimeUnit.SECONDS));
-
-        client.close();
-
-        // Close Alert
-        record = proxy.readFromClient();
-        Assert.assertEquals(TLSRecord.Type.ALERT, record.getType());
-        proxy.flushToServer(record);
-
-        // Do not close the raw socket yet
-
-        // Expect response from server
-        // SSLSocket is limited and we cannot read the response, but we make sure
-        // it is application data and not a close alert
-        record = proxy.readFromServer();
-        Assert.assertNotNull(record);
-        Assert.assertEquals(TLSRecord.Type.APPLICATION, record.getType());
-        proxy.flushToClient(record);
-
-        // Close Alert
-        record = proxy.readFromServer();
-        Assert.assertNotNull(record);
-        Assert.assertEquals(TLSRecord.Type.ALERT, record.getType());
-        // We can't forward to the client, its socket is already closed
-
-        // Check that we did not spin
-        TimeUnit.MILLISECONDS.sleep(500);
-        Assert.assertThat(sslHandles.get(), lessThan(20));
-        Assert.assertThat(sslFlushes.get(), lessThan(20));
-        Assert.assertThat(httpParses.get(), lessThan(50));
-
-        // Socket close
-        record = proxy.readFromClient();
-        Assert.assertNull(String.valueOf(record), record);
-        proxy.flushToServer(record);
-
-        // Socket close
-        record = proxy.readFromServer();
-        Assert.assertNull(String.valueOf(record), record);
-        proxy.flushToClient(record);
-    }
-
-    @Test
-    public void testRequestWithRawClose() throws Exception
-    {
-        final SSLSocket client = newClient();
-
-        SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow();
-        client.startHandshake();
-        Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS));
-
-        Future<Object> request = threadPool.submit(new Callable<Object>()
-        {
-            public Object call() throws Exception
-            {
-                OutputStream clientOutput = client.getOutputStream();
-                clientOutput.write(("" +
-                        "GET / HTTP/1.1\r\n" +
-                        "Host: localhost\r\n" +
-                        "\r\n").getBytes("UTF-8"));
-                clientOutput.flush();
-                return null;
-            }
-        });
-
-        // Application data
-        TLSRecord record = proxy.readFromClient();
-        Assert.assertEquals(TLSRecord.Type.APPLICATION, record.getType());
-        proxy.flushToServer(record);
-        Assert.assertNull(request.get(5, TimeUnit.SECONDS));
-
-        // Application data
-        record = proxy.readFromServer();
-        Assert.assertEquals(TLSRecord.Type.APPLICATION, record.getType());
-        proxy.flushToClient(record);
-
-        // Close the raw socket, this generates a truncation attack
-        proxy.flushToServer((TLSRecord)null);
-
-        // Expect alert + raw close from server
-        record = proxy.readFromServer();
-        Assert.assertEquals(TLSRecord.Type.ALERT, record.getType());
-        record = proxy.readFromServer();
-        Assert.assertNull(String.valueOf(record), record);
-        proxy.flushToClient(record);
-
-        // Check that we did not spin
-        TimeUnit.MILLISECONDS.sleep(500);
-        Assert.assertThat(sslHandles.get(), lessThan(20));
-        Assert.assertThat(sslFlushes.get(), lessThan(20));
-        Assert.assertThat(httpParses.get(), lessThan(50));
-
-        client.close();
-    }
-
-    @Test
-    public void testRequestWithBigContentWriteBlockedThenReset() throws Exception
-    {
-        final SSLSocket client = newClient();
-
-        SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow();
-        client.startHandshake();
-        Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS));
-
-        byte[] data = new byte[128 * 1024];
-        Arrays.fill(data, (byte)'X');
-        final String content = new String(data, "UTF-8");
-        Future<Object> request = threadPool.submit(new Callable<Object>()
-        {
-            public Object call() throws Exception
-            {
-                OutputStream clientOutput = client.getOutputStream();
-                clientOutput.write(("" +
-                        "GET /echo HTTP/1.1\r\n" +
-                        "Host: localhost\r\n" +
-                        "Content-Length: " + content.length() + "\r\n" +
-                        "\r\n" +
-                        content).getBytes("UTF-8"));
-                clientOutput.flush();
-                return null;
-            }
-        });
-
-        // Nine TLSRecords will be generated for the request
-        for (int i = 0; i < 9; ++i)
-        {
-            // Application data
-            TLSRecord record = proxy.readFromClient();
-            Assert.assertEquals(TLSRecord.Type.APPLICATION, record.getType());
-            proxy.flushToServer(record, 0);
-        }
-        Assert.assertNull(request.get(5, TimeUnit.SECONDS));
-
-        // We asked the server to echo back the data we sent
-        // but we do not read it, thus causing a write interest
-        // on the server.
-        // However, we then simulate that the client resets the
-        // connection, and this will cause an exception in the
-        // server that is trying to write the data
-
-        TimeUnit.MILLISECONDS.sleep(500);
-        proxy.sendRSTToServer();
-
-        // Wait a while to detect spinning
-        TimeUnit.MILLISECONDS.sleep(500);
-        Assert.assertThat(sslHandles.get(), lessThan(20));
-        Assert.assertThat(sslFlushes.get(), lessThan(20));
-        Assert.assertThat(httpParses.get(), lessThan(50));
-
-        client.close();
-    }
-
-    @Test
-    public void testRequestWithBigContentReadBlockedThenReset() throws Exception
-    {
-        final SSLSocket client = newClient();
-
-        SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow();
-        client.startHandshake();
-        Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS));
-
-        byte[] data = new byte[128 * 1024];
-        Arrays.fill(data, (byte)'X');
-        final String content = new String(data, "UTF-8");
-        Future<Object> request = threadPool.submit(new Callable<Object>()
-        {
-            public Object call() throws Exception
-            {
-                OutputStream clientOutput = client.getOutputStream();
-                clientOutput.write(("" +
-                        "GET /echo_suppress_exception HTTP/1.1\r\n" +
-                        "Host: localhost\r\n" +
-                        "Content-Length: " + content.length() + "\r\n" +
-                        "\r\n" +
-                        content).getBytes("UTF-8"));
-                clientOutput.flush();
-                return null;
-            }
-        });
-
-        // Nine TLSRecords will be generated for the request,
-        // but we write only 5 of them, so the server goes in read blocked state
-        for (int i = 0; i < 5; ++i)
-        {
-            // Application data
-            TLSRecord record = proxy.readFromClient();
-            Assert.assertEquals(TLSRecord.Type.APPLICATION, record.getType());
-            proxy.flushToServer(record, 0);
-        }
-        Assert.assertNull(request.get(5, TimeUnit.SECONDS));
-
-        // The server should be read blocked, and we send a RST
-        TimeUnit.MILLISECONDS.sleep(500);
-        proxy.sendRSTToServer();
-
-        // Wait a while to detect spinning
-        TimeUnit.MILLISECONDS.sleep(500);
-        Assert.assertThat(sslHandles.get(), lessThan(20));
-        Assert.assertThat(sslFlushes.get(), lessThan(20));
-        Assert.assertThat(httpParses.get(), lessThan(50));
-
-        client.close();
-    }
-
-    @Test
-    public void testRequestWithCloseAlertWithSplitBoundary() throws Exception
-    {
-        if ( !OS.IS_LINUX )
-        {
-            // currently we are ignoring this test on anything other then linux
-
-            //http://tools.ietf.org/html/rfc2246#section-7.2.1
-
-            // TODO (react to this portion which seems to allow win/mac behavior)
-            //It is required that the other party respond with a close_notify alert of its own
-            //and close down the connection immediately, discarding any pending writes. It is not
-            //required for the initiator of the close to wait for the responding
-            //close_notify alert before closing the read side of the connection.
-            return;
-        }
-
-        final SSLSocket client = newClient();
-
-        SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow();
-        client.startHandshake();
-        Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS));
-
-        Future<Object> request = threadPool.submit(new Callable<Object>()
-        {
-            public Object call() throws Exception
-            {
-                OutputStream clientOutput = client.getOutputStream();
-                clientOutput.write(("" +
-                        "GET / HTTP/1.1\r\n" +
-                        "Host: localhost\r\n" +
-                        "\r\n").getBytes("UTF-8"));
-                clientOutput.flush();
-                return null;
-            }
-        });
-
-        // Application data
-        TLSRecord dataRecord = proxy.readFromClient();
-        Assert.assertNull(request.get(5, TimeUnit.SECONDS));
-
-        client.close();
-
-        // Close Alert
-        TLSRecord closeRecord = proxy.readFromClient();
-
-        // Send request and half of the close alert bytes
-        byte[] dataBytes = dataRecord.getBytes();
-        byte[] closeBytes = closeRecord.getBytes();
-        byte[] bytes = new byte[dataBytes.length + closeBytes.length / 2];
-        System.arraycopy(dataBytes, 0, bytes, 0, dataBytes.length);
-        System.arraycopy(closeBytes, 0, bytes, dataBytes.length, closeBytes.length / 2);
-        proxy.flushToServer(100, bytes);
-
-        bytes = new byte[closeBytes.length - closeBytes.length / 2];
-        System.arraycopy(closeBytes, closeBytes.length / 2, bytes, 0, bytes.length);
-        proxy.flushToServer(100, bytes);
-
-        // Do not close the raw socket yet
-
-        // Expect response from server
-        // SSLSocket is limited and we cannot read the response, but we make sure
-        // it is application data and not a close alert
-        TLSRecord record = proxy.readFromServer();
-        Assert.assertNotNull(record);
-        Assert.assertEquals(TLSRecord.Type.APPLICATION, record.getType());
-        proxy.flushToClient(record);
-
-        // Close Alert
-        record = proxy.readFromServer();
-        Assert.assertNotNull(record);
-        Assert.assertEquals(TLSRecord.Type.ALERT, record.getType());
-        // We can't forward to the client, its socket is already closed
-
-        // Check that we did not spin
-        TimeUnit.MILLISECONDS.sleep(500);
-        Assert.assertThat(sslHandles.get(), lessThan(20));
-        Assert.assertThat(sslFlushes.get(), lessThan(20));
-        Assert.assertThat(httpParses.get(), lessThan(50));
-
-        // Socket close
-        record = proxy.readFromClient();
-        Assert.assertNull(String.valueOf(record), record);
-        proxy.flushToServer(record);
-
-        // Socket close
-        record = proxy.readFromServer();
-        Assert.assertNull(String.valueOf(record), record);
-        proxy.flushToClient(record);
-    }
-
-    @Test
-    public void testRequestWithContentWithSplitBoundary() throws Exception
-    {
-        final SSLSocket client = newClient();
-
-        SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow();
-        client.startHandshake();
-        Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS));
-
-        final String content = "0123456789ABCDEF";
-
-        Future<Object> request = threadPool.submit(new Callable<Object>()
-        {
-            public Object call() throws Exception
-            {
-                OutputStream clientOutput = client.getOutputStream();
-                clientOutput.write(("" +
-                        "POST / HTTP/1.1\r\n" +
-                        "Host: localhost\r\n" +
-                        "Content-Type: text/plain\r\n" +
-                        "Content-Length: " + content.length() + "\r\n" +
-                        "\r\n" +
-                        content).getBytes("UTF-8"));
-                clientOutput.flush();
-                return null;
-            }
-        });
-
-        // Application data
-        TLSRecord record = proxy.readFromClient();
-        Assert.assertNull(request.get(5, TimeUnit.SECONDS));
-        byte[] chunk1 = new byte[2 * record.getBytes().length / 3];
-        System.arraycopy(record.getBytes(), 0, chunk1, 0, chunk1.length);
-        proxy.flushToServer(100, chunk1);
-
-        byte[] chunk2 = new byte[record.getBytes().length - chunk1.length];
-        System.arraycopy(record.getBytes(), chunk1.length, chunk2, 0, chunk2.length);
-        proxy.flushToServer(100, chunk2);
-
-        record = proxy.readFromServer();
-        Assert.assertEquals(TLSRecord.Type.APPLICATION, record.getType());
-        proxy.flushToClient(record);
-
-        BufferedReader reader = new BufferedReader(new InputStreamReader(client.getInputStream(), "UTF-8"));
-        String line = reader.readLine();
-        Assert.assertNotNull(line);
-        Assert.assertTrue(line.startsWith("HTTP/1.1 200 "));
-        while ((line = reader.readLine()) != null)
-        {
-            if (line.trim().length() == 0)
-                break;
-        }
-
-        // Check that we did not spin
-        TimeUnit.MILLISECONDS.sleep(500);
-        Assert.assertThat(sslHandles.get(), lessThan(20));
-        Assert.assertThat(sslFlushes.get(), lessThan(20));
-        Assert.assertThat(httpParses.get(), lessThan(50));
-
-        closeClient(client);
-    }
-
-    @Test
-    public void testRequestWithBigContentWithSplitBoundary() throws Exception
-    {
-        final SSLSocket client = newClient();
-
-        SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow();
-        client.startHandshake();
-        Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS));
-
-        // Use a content that is larger than the TLS record which is 2^14 (around 16k)
-        byte[] data = new byte[128 * 1024];
-        Arrays.fill(data, (byte)'X');
-        final String content = new String(data, "UTF-8");
-
-        Future<Object> request = threadPool.submit(new Callable<Object>()
-        {
-            public Object call() throws Exception
-            {
-                OutputStream clientOutput = client.getOutputStream();
-                clientOutput.write(("" +
-                        "POST / HTTP/1.1\r\n" +
-                        "Host: localhost\r\n" +
-                        "Content-Type: text/plain\r\n" +
-                        "Content-Length: " + content.length() + "\r\n" +
-                        "\r\n" +
-                        content).getBytes("UTF-8"));
-                clientOutput.flush();
-                return null;
-            }
-        });
-
-        // Nine TLSRecords will be generated for the request
-        for (int i = 0; i < 9; ++i)
-        {
-            // Application data
-            TLSRecord record = proxy.readFromClient();
-            byte[] bytes = record.getBytes();
-            byte[] chunk1 = new byte[2 * bytes.length / 3];
-            System.arraycopy(bytes, 0, chunk1, 0, chunk1.length);
-            byte[] chunk2 = new byte[bytes.length - chunk1.length];
-            System.arraycopy(bytes, chunk1.length, chunk2, 0, chunk2.length);
-            proxy.flushToServer(100, chunk1);
-            proxy.flushToServer(100, chunk2);
-        }
-
-        // Check that we did not spin
-        TimeUnit.MILLISECONDS.sleep(500);
-        Assert.assertThat(sslHandles.get(), lessThan(20));
-        Assert.assertThat(sslFlushes.get(), lessThan(20));
-        Assert.assertThat(httpParses.get(), lessThan(150));
-
-        Assert.assertNull(request.get(5, TimeUnit.SECONDS));
-
-        TLSRecord record = proxy.readFromServer();
-        Assert.assertEquals(TLSRecord.Type.APPLICATION, record.getType());
-        proxy.flushToClient(record);
-
-        BufferedReader reader = new BufferedReader(new InputStreamReader(client.getInputStream(), "UTF-8"));
-        String line = reader.readLine();
-        Assert.assertNotNull(line);
-        Assert.assertTrue(line.startsWith("HTTP/1.1 200 "));
-        while ((line = reader.readLine()) != null)
-        {
-            if (line.trim().length() == 0)
-                break;
-        }
-
-        // Check that we did not spin
-        TimeUnit.MILLISECONDS.sleep(500);
-        Assert.assertThat(sslHandles.get(), lessThan(20));
-        Assert.assertThat(sslFlushes.get(), lessThan(20));
-        Assert.assertThat(httpParses.get(), lessThan(150));
-
-        closeClient(client);
-    }
-
-    @Test
-    public void testRequestWithBigContentWithRenegotiationInMiddleOfContent() throws Exception
-    {
-        assumeJavaVersionSupportsTLSRenegotiations();
-
-        final SSLSocket client = newClient();
-        final OutputStream clientOutput = client.getOutputStream();
-
-        SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow();
-        client.startHandshake();
-        Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS));
-
-        // Use a content that is larger than the TLS record which is 2^14 (around 16k)
-        byte[] data1 = new byte[80 * 1024];
-        Arrays.fill(data1, (byte)'X');
-        String content1 = new String(data1, "UTF-8");
-        byte[] data2 = new byte[48 * 1024];
-        Arrays.fill(data2, (byte)'Y');
-        final String content2 = new String(data2, "UTF-8");
-
-        // Write only part of the body
-        automaticProxyFlow = proxy.startAutomaticFlow();
-        clientOutput.write(("" +
-                "POST / HTTP/1.1\r\n" +
-                "Host: localhost\r\n" +
-                "Content-Type: text/plain\r\n" +
-                "Content-Length: " + (content1.length() + content2.length()) + "\r\n" +
-                "\r\n" +
-                content1).getBytes("UTF-8"));
-        clientOutput.flush();
-        Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS));
-
-        // Renegotiate
-        Future<Object> renegotiation = threadPool.submit(new Callable<Object>()
-        {
-            public Object call() throws Exception
-            {
-                client.startHandshake();
-                return null;
-            }
-        });
-
-        // Renegotiation Handshake
-        TLSRecord record = proxy.readFromClient();
-        Assert.assertEquals(TLSRecord.Type.HANDSHAKE, record.getType());
-        proxy.flushToServer(record);
-
-        // Renegotiation Handshake
-        record = proxy.readFromServer();
-        Assert.assertEquals(TLSRecord.Type.HANDSHAKE, record.getType());
-        proxy.flushToClient(record);
-
-        // Renegotiation Change Cipher
-        record = proxy.readFromServer();
-        Assert.assertEquals(TLSRecord.Type.CHANGE_CIPHER_SPEC, record.getType());
-        proxy.flushToClient(record);
-
-        // Renegotiation Handshake
-        record = proxy.readFromServer();
-        Assert.assertEquals(TLSRecord.Type.HANDSHAKE, record.getType());
-        proxy.flushToClient(record);
-
-        // Trigger a read to have the client write the final renegotiation steps
-        client.setSoTimeout(100);
-        try
-        {
-            client.getInputStream().read();
-            Assert.fail();
-        }
-        catch (SocketTimeoutException x)
-        {
-            // Expected
-        }
-
-        // Renegotiation Change Cipher
-        record = proxy.readFromClient();
-        Assert.assertEquals(TLSRecord.Type.CHANGE_CIPHER_SPEC, record.getType());
-        proxy.flushToServer(record);
-
-        // Renegotiation Handshake
-        record = proxy.readFromClient();
-        Assert.assertEquals(TLSRecord.Type.HANDSHAKE, record.getType());
-        proxy.flushToServer(record);
-
-        Assert.assertNull(renegotiation.get(5, TimeUnit.SECONDS));
-
-        // Write the rest of the request
-        Future<Object> request = threadPool.submit(new Callable<Object>()
-        {
-            public Object call() throws Exception
-            {
-                clientOutput.write(content2.getBytes("UTF-8"));
-                clientOutput.flush();
-                return null;
-            }
-        });
-
-        // Three TLSRecords will be generated for the remainder of the content
-        for (int i = 0; i < 3; ++i)
-        {
-            // Application data
-            record = proxy.readFromClient();
-            proxy.flushToServer(record);
-        }
-
-        Assert.assertNull(request.get(5, TimeUnit.SECONDS));
-
-        // Read response
-        // Application Data
-        record = proxy.readFromServer();
-        Assert.assertEquals(TLSRecord.Type.APPLICATION, record.getType());
-        proxy.flushToClient(record);
-
-        BufferedReader reader = new BufferedReader(new InputStreamReader(client.getInputStream(), "UTF-8"));
-        String line = reader.readLine();
-        Assert.assertNotNull(line);
-        Assert.assertTrue(line.startsWith("HTTP/1.1 200 "));
-        while ((line = reader.readLine()) != null)
-        {
-            if (line.trim().length() == 0)
-                break;
-        }
-
-        // Check that we did not spin
-        TimeUnit.MILLISECONDS.sleep(500);
-        Assert.assertThat(sslHandles.get(), lessThan(20));
-        Assert.assertThat(sslFlushes.get(), lessThan(20));
-        Assert.assertThat(httpParses.get(), lessThan(50));
-
-        closeClient(client);
-    }
-
-    @Test
-    public void testRequestWithBigContentWithRenegotiationInMiddleOfContentWithSplitBoundary() throws Exception
-    {
-        assumeJavaVersionSupportsTLSRenegotiations();
-
-        final SSLSocket client = newClient();
-        final OutputStream clientOutput = client.getOutputStream();
-
-        SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow();
-        client.startHandshake();
-        Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS));
-
-        // Use a content that is larger than the TLS record which is 2^14 (around 16k)
-        byte[] data1 = new byte[80 * 1024];
-        Arrays.fill(data1, (byte)'X');
-        String content1 = new String(data1, "UTF-8");
-        byte[] data2 = new byte[48 * 1024];
-        Arrays.fill(data2, (byte)'Y');
-        final String content2 = new String(data2, "UTF-8");
-
-        // Write only part of the body
-        automaticProxyFlow = proxy.startAutomaticFlow();
-        clientOutput.write(("" +
-                "POST / HTTP/1.1\r\n" +
-                "Host: localhost\r\n" +
-                "Content-Type: text/plain\r\n" +
-                "Content-Length: " + (content1.length() + content2.length()) + "\r\n" +
-                "\r\n" +
-                content1).getBytes("UTF-8"));
-        clientOutput.flush();
-        Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS));
-
-        // Renegotiate
-        Future<Object> renegotiation = threadPool.submit(new Callable<Object>()
-        {
-            public Object call() throws Exception
-            {
-                client.startHandshake();
-                return null;
-            }
-        });
-
-        // Renegotiation Handshake
-        TLSRecord record = proxy.readFromClient();
-        Assert.assertEquals(TLSRecord.Type.HANDSHAKE, record.getType());
-        byte[] bytes = record.getBytes();
-        byte[] chunk1 = new byte[2 * bytes.length / 3];
-        System.arraycopy(bytes, 0, chunk1, 0, chunk1.length);
-        byte[] chunk2 = new byte[bytes.length - chunk1.length];
-        System.arraycopy(bytes, chunk1.length, chunk2, 0, chunk2.length);
-        proxy.flushToServer(100, chunk1);
-        proxy.flushToServer(100, chunk2);
-
-        // Renegotiation Handshake
-        record = proxy.readFromServer();
-        Assert.assertEquals(TLSRecord.Type.HANDSHAKE, record.getType());
-        proxy.flushToClient(record);
-
-        // Renegotiation Change Cipher
-        record = proxy.readFromServer();
-        Assert.assertEquals(TLSRecord.Type.CHANGE_CIPHER_SPEC, record.getType());
-        proxy.flushToClient(record);
-
-        // Renegotiation Handshake
-        record = proxy.readFromServer();
-        Assert.assertEquals(TLSRecord.Type.HANDSHAKE, record.getType());
-        proxy.flushToClient(record);
-
-        // Trigger a read to have the client write the final renegotiation steps
-        client.setSoTimeout(100);
-        try
-        {
-            client.getInputStream().read();
-            Assert.fail();
-        }
-        catch (SocketTimeoutException x)
-        {
-            // Expected
-        }
-
-        // Renegotiation Change Cipher
-        record = proxy.readFromClient();
-        Assert.assertEquals(TLSRecord.Type.CHANGE_CIPHER_SPEC, record.getType());
-        bytes = record.getBytes();
-        chunk1 = new byte[2 * bytes.length / 3];
-        System.arraycopy(bytes, 0, chunk1, 0, chunk1.length);
-        chunk2 = new byte[bytes.length - chunk1.length];
-        System.arraycopy(bytes, chunk1.length, chunk2, 0, chunk2.length);
-        proxy.flushToServer(100, chunk1);
-        proxy.flushToServer(100, chunk2);
-
-        // Renegotiation Handshake
-        record = proxy.readFromClient();
-        Assert.assertEquals(TLSRecord.Type.HANDSHAKE, record.getType());
-        bytes = record.getBytes();
-        chunk1 = new byte[2 * bytes.length / 3];
-        System.arraycopy(bytes, 0, chunk1, 0, chunk1.length);
-        chunk2 = new byte[bytes.length - chunk1.length];
-        System.arraycopy(bytes, chunk1.length, chunk2, 0, chunk2.length);
-        proxy.flushToServer(100, chunk1);
-        // Do not write the second chunk now, but merge it with content, see below
-
-        Assert.assertNull(renegotiation.get(5, TimeUnit.SECONDS));
-
-        // Write the rest of the request
-        Future<Object> request = threadPool.submit(new Callable<Object>()
-        {
-            public Object call() throws Exception
-            {
-                clientOutput.write(content2.getBytes("UTF-8"));
-                clientOutput.flush();
-                return null;
-            }
-        });
-
-        // Three TLSRecords will be generated for the remainder of the content
-        // Merge the last chunk of the renegotiation with the first data record
-        record = proxy.readFromClient();
-        Assert.assertEquals(TLSRecord.Type.APPLICATION, record.getType());
-        byte[] dataBytes = record.getBytes();
-        byte[] mergedBytes = new byte[chunk2.length + dataBytes.length];
-        System.arraycopy(chunk2, 0, mergedBytes, 0, chunk2.length);
-        System.arraycopy(dataBytes, 0, mergedBytes, chunk2.length, dataBytes.length);
-        proxy.flushToServer(100, mergedBytes);
-        // Write the remaining 2 TLS records
-        for (int i = 0; i < 2; ++i)
-        {
-            // Application data
-            record = proxy.readFromClient();
-            Assert.assertEquals(TLSRecord.Type.APPLICATION, record.getType());
-            proxy.flushToServer(record);
-        }
-
-        Assert.assertNull(request.get(5, TimeUnit.SECONDS));
-
-        // Read response
-        // Application Data
-        record = proxy.readFromServer();
-        Assert.assertEquals(TLSRecord.Type.APPLICATION, record.getType());
-        proxy.flushToClient(record);
-
-        BufferedReader reader = new BufferedReader(new InputStreamReader(client.getInputStream(), "UTF-8"));
-        String line = reader.readLine();
-        Assert.assertNotNull(line);
-        Assert.assertTrue(line.startsWith("HTTP/1.1 200 "));
-        while ((line = reader.readLine()) != null)
-        {
-            if (line.trim().length() == 0)
-                break;
-        }
-
-        // Check that we did not spin
-        TimeUnit.MILLISECONDS.sleep(500);
-        Assert.assertThat(sslHandles.get(), lessThan(20));
-        Assert.assertThat(sslFlushes.get(), lessThan(20));
-        Assert.assertThat(httpParses.get(), lessThan(100));
-
-        closeClient(client);
-    }
-
-    @Test
-    public void testServerShutdownOutputClientDoesNotCloseServerCloses() throws Exception
-    {
-        final SSLSocket client = newClient();
-        final OutputStream clientOutput = client.getOutputStream();
-
-        SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow();
-        client.startHandshake();
-        Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS));
-
-        byte[] data = new byte[3 * 1024];
-        Arrays.fill(data, (byte)'Y');
-        String content = new String(data, "UTF-8");
-        automaticProxyFlow = proxy.startAutomaticFlow();
-        clientOutput.write(("" +
-                "POST / HTTP/1.1\r\n" +
-                "Host: localhost\r\n" +
-                "Content-Type: text/plain\r\n" +
-                "Content-Length: " + content.length() + "\r\n" +
-                "Connection: close\r\n" +
-                "\r\n" +
-                content).getBytes("UTF-8"));
-        clientOutput.flush();
-
-        BufferedReader reader = new BufferedReader(new InputStreamReader(client.getInputStream(), "UTF-8"));
-        String line = reader.readLine();
-        Assert.assertNotNull(line);
-        Assert.assertTrue(line.startsWith("HTTP/1.1 200 "));
-        while ((line = reader.readLine()) != null)
-        {
-            if (line.trim().length() == 0)
-                break;
-        }
-        Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS));
-
-        // Check client is at EOF
-        Assert.assertEquals(-1,client.getInputStream().read());
-
-        // Client should close the socket, but let's hold it open.
-
-        // Check that we did not spin
-        TimeUnit.MILLISECONDS.sleep(500);
-        Assert.assertThat(sslHandles.get(), lessThan(20));
-        Assert.assertThat(sslFlushes.get(), lessThan(20));
-        Assert.assertThat(httpParses.get(), lessThan(50));
-
-        // The server has shutdown the output since the client sent a Connection: close
-        // but the client does not close, so the server must idle timeout the endPoint.
-
-        TimeUnit.MILLISECONDS.sleep(idleTimeout + idleTimeout/2);
-
-        Assert.assertFalse(serverEndPoint.get().isOpen());
-    }
-
-    @Test
-    public void testPlainText() throws Exception
-    {
-        final SSLSocket client = newClient();
-
-        threadPool.submit(new Callable<Object>()
-        {
-            public Object call() throws Exception
-            {
-                client.startHandshake();
-                return null;
-            }
-        });
-
-        // Instead of passing the Client Hello, we simulate plain text was passed in
-        proxy.flushToServer(0, "GET / HTTP/1.1\r\n".getBytes("UTF-8"));
-
-        // We expect that the server closes the connection immediately
-        TLSRecord record = proxy.readFromServer();
-        Assert.assertNull(String.valueOf(record), record);
-
-        // Check that we did not spin
-        TimeUnit.MILLISECONDS.sleep(500);
-        Assert.assertThat(sslHandles.get(), lessThan(20));
-        Assert.assertThat(sslFlushes.get(), lessThan(20));
-        Assert.assertThat(httpParses.get(), lessThan(50));
-
-        client.close();
-    }
-
-    @Test
-    public void testRequestConcurrentWithIdleExpiration() throws Exception
-    {
-        final SSLSocket client = newClient();
-        final OutputStream clientOutput = client.getOutputStream();
-        final CountDownLatch latch = new CountDownLatch(1);
-
-        idleHook = new Runnable()
-        {
-            public void run()
-            {
-                if (latch.getCount()==0)
-                    return;
-                try
-                {
-                    // Send request
-                    clientOutput.write(("" +
-                            "GET / HTTP/1.1\r\n" +
-                            "Host: localhost\r\n" +
-                            "\r\n").getBytes("UTF-8"));
-                    clientOutput.flush();
-                    latch.countDown();
-                }
-                catch (Exception x)
-                {
-                    // Latch won't trigger and test will fail
-                    x.printStackTrace();
-                }
-            }
-        };
-
-        SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow();
-        client.startHandshake();
-        Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS));
-
-        Assert.assertTrue(latch.await(idleTimeout * 2, TimeUnit.MILLISECONDS));
-
-        // Be sure that the server sent a SSL close alert
-        TLSRecord record = proxy.readFromServer();
-        Assert.assertNotNull(record);
-        Assert.assertEquals(TLSRecord.Type.ALERT, record.getType());
-
-        // Write the request to the server, to simulate a request
-        // concurrent with the SSL close alert
-        record = proxy.readFromClient();
-        Assert.assertEquals(TLSRecord.Type.APPLICATION, record.getType());
-        proxy.flushToServer(record, 0);
-
-        // Check that we did not spin
-        TimeUnit.MILLISECONDS.sleep(500);
-        Assert.assertThat(sslHandles.get(), lessThan(20));
-        Assert.assertThat(sslFlushes.get(), lessThan(20));
-        Assert.assertThat(httpParses.get(), lessThan(50));
-
-        completeClose(client);
-
-        TimeUnit.MILLISECONDS.sleep(200);
-        //System.err.println(((Dumpable)server.getConnectors()[0]).dump());
-        Assert.assertThat(((Dumpable)server.getConnectors()[0]).dump(),not(containsString("SCEP@")));
-
-    }
-/*
-    @Test
-    public void testRequestWriteBlockedWithPipelinedRequest() throws Exception
-    {
-        final SSLSocket client = newClient();
-        final OutputStream clientOutput = client.getOutputStream();
-
-        SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow();
-        client.startHandshake();
-        Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS));
-
-        byte[] data = new byte[128 * 1024];
-        Arrays.fill(data, (byte)'X');
-        final String content = new String(data, "UTF-8");
-        Future<Object> request = threadPool.submit(new Callable<Object>()
-        {
-            public Object call() throws Exception
-            {
-                clientOutput.write(("" +
-                        "POST /echo HTTP/1.1\r\n" +
-                        "Host: localhost\r\n" +
-                        "Content-Length: " + content.length() + "\r\n" +
-                        "\r\n" +
-                        content).getBytes("UTF-8"));
-                clientOutput.flush();
-                return null;
-            }
-        });
-
-        // Nine TLSRecords will be generated for the request
-        for (int i = 0; i < 9; ++i)
-        {
-            // Application data
-            TLSRecord record = proxy.readFromClient();
-            Assert.assertEquals(TLSRecord.Type.APPLICATION, record.getType());
-            proxy.flushToServer(record, 0);
-        }
-        Assert.assertNull(request.get(5, TimeUnit.SECONDS));
-
-        // We do not read the big request to cause a write blocked on the server
-        TimeUnit.MILLISECONDS.sleep(500);
-
-        // Now send the pipelined request
-        Future<Object> pipelined = threadPool.submit(new Callable<Object>()
-        {
-            public Object call() throws Exception
-            {
-                clientOutput.write(("" +
-                        "GET /pipelined HTTP/1.1\r\n" +
-                        "Host: localhost\r\n" +
-                        "\r\n").getBytes("UTF-8"));
-                clientOutput.flush();
-                return null;
-            }
-        });
-
-        TLSRecord record = proxy.readFromClient();
-        Assert.assertEquals(TLSRecord.Type.APPLICATION, record.getType());
-        proxy.flushToServer(record, 0);
-        Assert.assertNull(pipelined.get(5, TimeUnit.SECONDS));
-
-        // Check that we did not spin
-        TimeUnit.MILLISECONDS.sleep(500);
-        Assert.assertThat(sslHandles.get(), lessThan(20));
-        Assert.assertThat(sslFlushes.get(), lessThan(20));
-        Assert.assertThat(httpParses.get(), lessThan(50));
-
-        Thread.sleep(5000);
-
-//        closeClient(client);
-    }
-*/
-    private void assumeJavaVersionSupportsTLSRenegotiations()
-    {
-        // Due to a security bug, TLS renegotiations were disabled in JDK 1.6.0_19-21
-        // so we check the java version in order to avoid to fail the test.
-        String javaVersion = System.getProperty("java.version");
-        Pattern regexp = Pattern.compile("1\\.6\\.0_(\\d{2})");
-        Matcher matcher = regexp.matcher(javaVersion);
-        if (matcher.matches())
-        {
-            String nano = matcher.group(1);
-            Assume.assumeThat(Integer.parseInt(nano), greaterThan(21));
-        }
-    }
-
-    private SSLSocket newClient() throws IOException, InterruptedException
-    {
-        SSLSocket client = (SSLSocket)sslContext.getSocketFactory().createSocket("localhost", proxy.getPort());
-        client.setUseClientMode(true);
-        Assert.assertTrue(proxy.awaitClient(5, TimeUnit.SECONDS));
-        return client;
-    }
-
-    private void closeClient(SSLSocket client) throws Exception
-    {
-        client.close();
-
-        // Close Alert
-        TLSRecord record = proxy.readFromClient();
-        proxy.flushToServer(record);
-        // Socket close
-        record = proxy.readFromClient();
-        Assert.assertNull(String.valueOf(record), record);
-        proxy.flushToServer(record);
-
-        // Close Alert
-        record = proxy.readFromServer();
-        proxy.flushToClient(record);
-
-        // Socket close
-        record = proxy.readFromServer();
-        Assert.assertNull(String.valueOf(record), record);
-        proxy.flushToClient(record);
-    }
-
-    private void completeClose(SSLSocket client) throws Exception
-    {
-        client.close();
-
-        // Close Alert
-        TLSRecord record = proxy.readFromClient();
-        proxy.flushToServer(record);
-        // Socket close
-        record = proxy.readFromClient();
-        Assert.assertNull(String.valueOf(record), record);
-        proxy.flushToServer(record);
-
-        // Close Alert
-        try
-        {
-            record = proxy.readFromServer();
-        }
-        catch (IOException e)
-        {
-            e.printStackTrace();
-        }
-        proxy.flushToClient(record);
-
-    }
-}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/SslBytesTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/SslBytesTest.java
deleted file mode 100644
index 3013daf..0000000
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/SslBytesTest.java
+++ /dev/null
@@ -1,371 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client;
-
-import java.io.EOFException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InterruptedIOException;
-import java.io.OutputStream;
-import java.net.ServerSocket;
-import java.net.Socket;
-import java.net.SocketTimeoutException;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.Callable;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
-
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-import org.junit.Assert;
-
-public abstract class SslBytesTest
-{
-    protected final Logger logger = Log.getLogger(getClass());
-
-    public static class TLSRecord
-    {
-        private final SslBytesServerTest.TLSRecord.Type type;
-        private final byte[] bytes;
-
-        public TLSRecord(SslBytesServerTest.TLSRecord.Type type, byte[] bytes)
-        {
-            this.type = type;
-            this.bytes = bytes;
-        }
-
-        public SslBytesServerTest.TLSRecord.Type getType()
-        {
-            return type;
-        }
-
-        public byte[] getBytes()
-        {
-            return bytes;
-        }
-
-        @Override
-        public String toString()
-        {
-            return "TLSRecord [" + type + "] " + bytes.length + " bytes";
-        }
-
-        public enum Type
-        {
-            CHANGE_CIPHER_SPEC(20), ALERT(21), HANDSHAKE(22), APPLICATION(23);
-
-            private int code;
-
-            private Type(int code)
-            {
-                this.code = code;
-                SslBytesServerTest.TLSRecord.Type.Mapper.codes.put(this.code, this);
-            }
-
-            public static SslBytesServerTest.TLSRecord.Type from(int code)
-            {
-                SslBytesServerTest.TLSRecord.Type result = SslBytesServerTest.TLSRecord.Type.Mapper.codes.get(code);
-                if (result == null)
-                    throw new IllegalArgumentException("Invalid TLSRecord.Type " + code);
-                return result;
-            }
-
-            private static class Mapper
-            {
-                private static final Map<Integer, SslBytesServerTest.TLSRecord.Type> codes = new HashMap<Integer, SslBytesServerTest.TLSRecord.Type>();
-            }
-        }
-    }
-
-    public class SimpleProxy implements Runnable
-    {
-        private final CountDownLatch latch = new CountDownLatch(1);
-        private final ExecutorService threadPool;
-        private final String serverHost;
-        private final int serverPort;
-        private volatile ServerSocket serverSocket;
-        private volatile Socket server;
-        private volatile Socket client;
-
-        public SimpleProxy(ExecutorService threadPool, String serverHost, int serverPort)
-        {
-            this.threadPool = threadPool;
-            this.serverHost = serverHost;
-            this.serverPort = serverPort;
-        }
-
-        public void start() throws Exception
-        {
-//            serverSocket = new ServerSocket(5871);
-            serverSocket = new ServerSocket(0);
-            Thread acceptor = new Thread(this);
-            acceptor.start();
-            server = new Socket(serverHost, serverPort);
-        }
-
-        public void stop() throws Exception
-        {
-            serverSocket.close();
-        }
-
-        public void run()
-        {
-            try
-            {
-                client = serverSocket.accept();
-                latch.countDown();
-            }
-            catch (IOException x)
-            {
-                x.printStackTrace();
-            }
-        }
-
-        public int getPort()
-        {
-            return serverSocket.getLocalPort();
-        }
-
-        public TLSRecord readFromClient() throws IOException
-        {
-            TLSRecord record = read(client);
-            logger.debug("C --> P {}", record);
-            return record;
-        }
-
-        private TLSRecord read(Socket socket) throws IOException
-        {
-            InputStream input = socket.getInputStream();
-            int first = -2;
-            while (true)
-            {
-                try
-                {
-                    socket.setSoTimeout(500);
-                    first = input.read();
-                    break;
-                }
-                catch (SocketTimeoutException x)
-                {
-                    if (Thread.currentThread().isInterrupted())
-                        break;
-                }
-            }
-            if (first == -2)
-                throw new InterruptedIOException();
-            else if (first == -1)
-                return null;
-
-            if (first >= 0x80)
-            {
-                // SSLv2 Record
-                int hiLength = first & 0x3F;
-                int loLength = input.read();
-                int length = (hiLength << 8) + loLength;
-                byte[] bytes = new byte[2 + length];
-                bytes[0] = (byte)first;
-                bytes[1] = (byte)loLength;
-                return read(TLSRecord.Type.HANDSHAKE, input, bytes, 2, length);
-            }
-            else
-            {
-                // TLS Record
-                int major = input.read();
-                int minor = input.read();
-                int hiLength = input.read();
-                int loLength = input.read();
-                int length = (hiLength << 8) + loLength;
-                byte[] bytes = new byte[1 + 2 + 2 + length];
-                bytes[0] = (byte)first;
-                bytes[1] = (byte)major;
-                bytes[2] = (byte)minor;
-                bytes[3] = (byte)hiLength;
-                bytes[4] = (byte)loLength;
-                return read(TLSRecord.Type.from(first), input, bytes, 5, length);
-            }
-        }
-
-        private TLSRecord read(SslBytesServerTest.TLSRecord.Type type, InputStream input, byte[] bytes, int offset, int length) throws IOException
-        {
-            while (length > 0)
-            {
-                int read = input.read(bytes, offset, length);
-                if (read < 0)
-                    throw new EOFException();
-                offset += read;
-                length -= read;
-            }
-            return new TLSRecord(type, bytes);
-        }
-
-        public void flushToServer(TLSRecord record) throws Exception
-        {
-            flushToServer(record, 100);
-        }
-
-        public void flushToServer(TLSRecord record, long sleep) throws Exception
-        {
-            if (record == null)
-            {
-                server.shutdownOutput();
-                if (client.isOutputShutdown())
-                {
-                    client.close();
-                    server.close();
-                }
-            }
-            else
-            {
-                flush(sleep, server, record.getBytes());
-            }
-        }
-
-        public void flushToServer(long sleep, byte... bytes) throws Exception
-        {
-            flush(sleep, server, bytes);
-        }
-
-        private void flush(long sleep, Socket socket, byte... bytes) throws Exception
-        {
-            OutputStream output = socket.getOutputStream();
-            output.write(bytes);
-            output.flush();
-            if (sleep > 0)
-                TimeUnit.MILLISECONDS.sleep(sleep);
-        }
-
-        public TLSRecord readFromServer() throws IOException
-        {
-            TLSRecord record = read(server);
-            logger.debug("P <-- S {}", record);
-            return record;
-        }
-
-        public void flushToClient(TLSRecord record) throws Exception
-        {
-            if (record == null)
-            {
-                client.shutdownOutput();
-                if (server.isOutputShutdown())
-                {
-                    server.close();
-                    client.close();
-                }
-            }
-            else
-            {
-                flush(0, client, record.getBytes());
-            }
-        }
-
-        public SslBytesServerTest.SimpleProxy.AutomaticFlow startAutomaticFlow() throws InterruptedException
-        {
-            final CountDownLatch startLatch = new CountDownLatch(2);
-            final CountDownLatch stopLatch = new CountDownLatch(2);
-            Future<Object> clientToServer = threadPool.submit(new Callable<Object>()
-            {
-                public Object call() throws Exception
-                {
-                    startLatch.countDown();
-                    logger.debug("Automatic flow C --> S started");
-                    try
-                    {
-                        while (true)
-                        {
-                            flushToServer(readFromClient(), 0);
-                        }
-                    }
-                    catch (InterruptedIOException x)
-                    {
-                        return null;
-                    }
-                    finally
-                    {
-                        stopLatch.countDown();
-                        logger.debug("Automatic flow C --> S finished");
-                    }
-                }
-            });
-            Future<Object> serverToClient = threadPool.submit(new Callable<Object>()
-            {
-                public Object call() throws Exception
-                {
-                    startLatch.countDown();
-                    logger.debug("Automatic flow C <-- S started");
-                    try
-                    {
-                        while (true)
-                        {
-                            flushToClient(readFromServer());
-                        }
-                    }
-                    catch (InterruptedIOException x)
-                    {
-                        return null;
-                    }
-                    finally
-                    {
-                        stopLatch.countDown();
-                        logger.debug("Automatic flow C <-- S finished");
-                    }
-                }
-            });
-            Assert.assertTrue(startLatch.await(5, TimeUnit.SECONDS));
-            return new SslBytesServerTest.SimpleProxy.AutomaticFlow(stopLatch, clientToServer, serverToClient);
-        }
-
-        public boolean awaitClient(int time, TimeUnit unit) throws InterruptedException
-        {
-            return latch.await(time, unit);
-        }
-
-        public void sendRSTToServer() throws IOException
-        {
-            // Calling setSoLinger(true, 0) causes close()
-            // to send a RST instead of a FIN, causing an
-            // exception to be thrown on the other end
-            server.setSoLinger(true, 0);
-            server.close();
-        }
-
-        public class AutomaticFlow
-        {
-            private final CountDownLatch stopLatch;
-            private final Future<Object> clientToServer;
-            private final Future<Object> serverToClient;
-
-            public AutomaticFlow(CountDownLatch stopLatch, Future<Object> clientToServer, Future<Object> serverToClient)
-            {
-                this.stopLatch = stopLatch;
-                this.clientToServer = clientToServer;
-                this.serverToClient = serverToClient;
-            }
-
-            public boolean stop(long time, TimeUnit unit) throws InterruptedException
-            {
-                clientToServer.cancel(true);
-                serverToClient.cancel(true);
-                return stopLatch.await(time, unit);
-            }
-        }
-    }
-}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/SslCertSecuredExchangeTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/SslCertSecuredExchangeTest.java
deleted file mode 100644
index 36c76cb..0000000
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/SslCertSecuredExchangeTest.java
+++ /dev/null
@@ -1,174 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client;
-
-import java.security.Principal;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
-
-import javax.security.auth.Subject;
-
-import org.eclipse.jetty.security.ConstraintMapping;
-import org.eclipse.jetty.security.ConstraintSecurityHandler;
-import org.eclipse.jetty.security.IdentityService;
-import org.eclipse.jetty.security.LoginService;
-import org.eclipse.jetty.security.MappedLoginService.KnownUser;
-import org.eclipse.jetty.security.authentication.ClientCertAuthenticator;
-import org.eclipse.jetty.server.Handler;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.UserIdentity;
-import org.eclipse.jetty.server.handler.HandlerCollection;
-import org.eclipse.jetty.server.ssl.SslSelectChannelConnector;
-import org.eclipse.jetty.servlet.DefaultServlet;
-import org.eclipse.jetty.servlet.ServletContextHandler;
-import org.eclipse.jetty.servlet.ServletHolder;
-import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
-import org.eclipse.jetty.util.security.Constraint;
-import org.eclipse.jetty.util.security.Credential;
-import org.eclipse.jetty.util.ssl.SslContextFactory;
-
-public class SslCertSecuredExchangeTest// extends ContentExchangeTest
-{ 
-    // certificate is valid until Jan 1, 2050
-    private String _keypath = MavenTestingUtils.getTargetFile("test-policy/validation/jetty-valid.keystore").getAbsolutePath();
-    private String _trustpath = MavenTestingUtils.getTargetFile("test-policy/validation/jetty-trust.keystore").getAbsolutePath();
-    private String _clientpath = MavenTestingUtils.getTargetFile("test-policy/validation/jetty-client.keystore").getAbsolutePath();
-    private String _crlpath = MavenTestingUtils.getTargetFile("test-policy/validation/crlfile.pem").getAbsolutePath();
-    private String _password = "OBF:1wnl1sw01ta01z0f1tae1svy1wml";
-
-    protected void configureServer(Server server)
-        throws Exception
-    {
-        //setProtocol("https");
-                        
-        SslSelectChannelConnector connector = new SslSelectChannelConnector();
-        SslContextFactory cf = connector.getSslContextFactory();
-        cf.setValidateCerts(true);
-        cf.setCrlPath(_crlpath);
-        cf.setNeedClientAuth(true);
-        cf.setKeyStorePath(_keypath);
-        cf.setKeyStorePassword(_password);
-        cf.setKeyManagerPassword(_password);
-        cf.setTrustStore(_trustpath);
-        cf.setTrustStorePassword(_password);
-        server.addConnector(connector);
-
-        LoginService loginService = new LoginService() {
-            public String getName()
-            {
-                return "MyLoginService";
-            }
-
-            public UserIdentity login(String username, Object credentials)
-            {
-                return new UserIdentity() {
-                    public Subject getSubject()
-                    {
-                        Subject subject = new Subject();
-                        subject.getPrincipals().add(getUserPrincipal());
-                        subject.setReadOnly();
-                        return subject;
-                    }
-
-                    public Principal getUserPrincipal()
-                    {
-                        return new KnownUser("client", new Credential() {
-                            @Override
-                            public boolean check(Object credentials)
-                            {
-                                return true;
-                            }
-                        });
-                    }
-
-                    public boolean isUserInRole(String role, Scope scope) { return true; }
-                };
-            }
-
-            public boolean validate(UserIdentity user) { return true; }
-
-            public IdentityService getIdentityService() { return null; }
-
-            public void setIdentityService(IdentityService service) {}
-
-            public void logout(UserIdentity user) {}
-            
-        };
-        server.addBean(loginService); 
-
-        ConstraintSecurityHandler security = new ConstraintSecurityHandler();
-        server.setHandler(security);
-
-        Constraint constraint = new Constraint();
-        constraint.setName("auth");
-        constraint.setAuthenticate( true );
-        constraint.setRoles(new String[]{"user", "admin"});
-
-        ConstraintMapping mapping = new ConstraintMapping();
-        mapping.setPathSpec( "/*" );
-        mapping.setConstraint( constraint );
-
-        Set<String> knownRoles = new HashSet<String>();
-        knownRoles.add("user");
-        knownRoles.add("admin");
-        
-        security.setConstraintMappings(Collections.singletonList(mapping), knownRoles);
-        security.setLoginService(loginService);
-
-        ClientCertAuthenticator auth = new ClientCertAuthenticator();
-        auth.setValidateCerts(true);
-        auth.setCrlPath(_crlpath);
-        auth.setTrustStore(_trustpath);
-        auth.setTrustStorePassword(_password);
-        security.setAuthenticator(auth);
-        security.setAuthMethod(auth.getAuthMethod());
-        security.setRealmName("MyRealm");
-        security.setStrict(true);
-        
-        ServletContextHandler root = new ServletContextHandler();
-        root.setContextPath("/");
-       // root.setResourceBase(getBasePath());
-        ServletHolder servletHolder = new ServletHolder( new DefaultServlet() );
-        servletHolder.setInitParameter( "gzip", "true" );
-        root.addServlet( servletHolder, "/*" );    
-
-      //  Handler handler = new TestHandler(getBasePath());
-        
-        HandlerCollection handlers = new HandlerCollection();
-      //  handlers.setHandlers(new Handler[]{handler, root});
-        security.setHandler(handlers);
-    }
-    
-//    @Override
-//    protected void configureClient(HttpClient client) throws Exception
-//    {
-//        SslContextFactory cf = client.getSslContextFactory();
-//        cf.setValidateCerts(true);
-//        cf.setCrlPath(_crlpath);
-//        
-//        cf.setCertAlias("client");
-//        cf.setKeyStorePath(_clientpath);
-//        cf.setKeyStorePassword(_password);
-//        cf.setKeyManagerPassword(_password);
-//        
-//        cf.setTrustStore(_trustpath);
-//        cf.setTrustStorePassword(_password);
-//    }
-}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/SslContentExchangeTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/SslContentExchangeTest.java
deleted file mode 100644
index c1bd6f1..0000000
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/SslContentExchangeTest.java
+++ /dev/null
@@ -1,74 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client;
-
-import java.io.File;
-
-import org.eclipse.jetty.server.Handler;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.handler.HandlerCollection;
-import org.eclipse.jetty.server.ssl.SslSelectChannelConnector;
-import org.eclipse.jetty.servlet.DefaultServlet;
-import org.eclipse.jetty.servlet.ServletContextHandler;
-import org.eclipse.jetty.servlet.ServletHolder;
-import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
-import org.eclipse.jetty.util.ssl.SslContextFactory;
-
-public class SslContentExchangeTest
-	extends ContentExchangeTest
-{
-
-    protected void configureServer(Server server)
-        throws Exception
-    {
-        setProtocol("https");
-
-        SslSelectChannelConnector connector = new SslSelectChannelConnector();
-        File keystore = MavenTestingUtils.getTestResourceFile("keystore");
-        SslContextFactory cf = connector.getSslContextFactory();
-        cf.setKeyStorePath(keystore.getAbsolutePath());
-        cf.setKeyStorePassword("storepwd");
-        cf.setKeyManagerPassword("keypwd");
-        cf.setSessionCachingEnabled(true);
-        server.addConnector(connector);
-
-        Handler handler = new TestHandler(getBasePath());
-
-        ServletContextHandler root = new ServletContextHandler();
-        root.setContextPath("/");
-        root.setResourceBase(getBasePath());
-        ServletHolder servletHolder = new ServletHolder( new DefaultServlet() );
-        servletHolder.setInitParameter( "gzip", "true" );
-        root.addServlet( servletHolder, "/*" );
-
-        HandlerCollection handlers = new HandlerCollection();
-        handlers.setHandlers(new Handler[]{handler, root});
-        server.setHandler( handlers );
-    }
-
-    @Override
-    protected void configureClient(HttpClient client)
-        throws Exception
-    {
-        client.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);
-
-        SslContextFactory cf = client.getSslContextFactory();
-        cf.setSessionCachingEnabled(true);
-    }
-}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/SslHttpExchangeTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/SslHttpExchangeTest.java
deleted file mode 100644
index 27dd5a6..0000000
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/SslHttpExchangeTest.java
+++ /dev/null
@@ -1,110 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client;
-
-import static org.hamcrest.Matchers.containsString;
-import static org.hamcrest.Matchers.not;
-
-import java.util.Locale;
-
-import org.eclipse.jetty.client.helperClasses.ServerAndClientCreator;
-import org.eclipse.jetty.client.helperClasses.SslServerAndClientCreator;
-import org.eclipse.jetty.server.Connector;
-import org.junit.Assume;
-import org.junit.Before;
-import org.junit.Test;
-
-/**
- * Functional testing for HttpExchange.
- */
-public class SslHttpExchangeTest extends HttpExchangeTest
-{
-    protected static ServerAndClientCreator serverAndClientCreator = new SslServerAndClientCreator();
-    
-    /* ------------------------------------------------------------ */
-    @Before
-    public void setUpOnce() throws Exception
-    {
-        _scheme="https";
-        _server = serverAndClientCreator.createServer();
-        _httpClient = serverAndClientCreator.createClient(3000L,3500L,2000);
-        Connector[] connectors = _server.getConnectors();
-        _port = connectors[0].getLocalPort();
-    }
-
-    /* ------------------------------------------------------------ */
-    private void IgnoreTestOnBuggyIBM()
-    {
-        // Use Junit 4.x to flag test as ignored if encountering IBM JVM
-        // Will show up in various junit reports as an ignored test as well.
-        Assume.assumeThat(System.getProperty("java.vendor").toLowerCase(Locale.ENGLISH),not(containsString("ibm")));
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.client.HttpExchangeTest#testGetWithContentExchange()
-     */
-    @Test
-    @Override
-    public void testGetWithContentExchange() throws Exception
-    {
-        // TODO Resolve problems on IBM JVM https://bugs.eclipse.org/bugs/show_bug.cgi?id=304532
-        IgnoreTestOnBuggyIBM();
-        super.testGetWithContentExchange();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.client.HttpExchangeTest#testPerf()
-     */
-    @Test
-    @Override
-    public void testPerf() throws Exception
-    {
-        // TODO Resolve problems on IBM JVM https://bugs.eclipse.org/bugs/show_bug.cgi?id=304532
-        IgnoreTestOnBuggyIBM();
-        super.testPerf();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.client.HttpExchangeTest#testPostWithContentExchange()
-     */
-    @Test
-    @Override
-    public void testPostWithContentExchange() throws Exception
-    {
-        // TODO Resolve problems on IBM JVM https://bugs.eclipse.org/bugs/show_bug.cgi?id=304532
-        IgnoreTestOnBuggyIBM();
-        super.testPostWithContentExchange();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.client.HttpExchangeTest#testReserveConnections()
-     */
-    @Test
-    @Override
-    public void testReserveConnections() throws Exception
-    {
-        // TODO Resolve problems on IBM JVM https://bugs.eclipse.org/bugs/show_bug.cgi?id=304532
-        IgnoreTestOnBuggyIBM();
-        super.testReserveConnections();
-    }
-}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/SslSecuredContentExchangeTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/SslSecuredContentExchangeTest.java
deleted file mode 100644
index 0587661..0000000
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/SslSecuredContentExchangeTest.java
+++ /dev/null
@@ -1,114 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client;
-
-import java.io.File;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
-
-import org.eclipse.jetty.client.security.Realm;
-import org.eclipse.jetty.security.ConstraintMapping;
-import org.eclipse.jetty.security.ConstraintSecurityHandler;
-import org.eclipse.jetty.security.HashLoginService;
-import org.eclipse.jetty.security.LoginService;
-import org.eclipse.jetty.security.authentication.BasicAuthenticator;
-import org.eclipse.jetty.server.Handler;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.handler.HandlerCollection;
-import org.eclipse.jetty.server.ssl.SslSelectChannelConnector;
-import org.eclipse.jetty.servlet.DefaultServlet;
-import org.eclipse.jetty.servlet.ServletContextHandler;
-import org.eclipse.jetty.servlet.ServletHolder;
-import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
-import org.eclipse.jetty.util.security.Constraint;
-import org.eclipse.jetty.util.ssl.SslContextFactory;
-
-public class SslSecuredContentExchangeTest
-extends ContentExchangeTest
-{ 
-    protected void configureServer(Server server)
-        throws Exception
-    {
-        setProtocol("https");
-        setRealm(new Realm()
-                 {
-                     public String getId()
-                     {
-                         return "MyRealm";
-                     }
-                
-                     public String getPrincipal()
-                     {
-                         return "jetty";
-                     }
-                
-                     public String getCredentials()
-                     {
-                         return "jetty";
-                     }
-                 });
-                        
-        SslSelectChannelConnector connector = new SslSelectChannelConnector();
-        File keystore = MavenTestingUtils.getTestResourceFile("keystore");
-        SslContextFactory cf = connector.getSslContextFactory();
-        cf.setKeyStorePath(keystore.getAbsolutePath());
-        cf.setKeyStorePassword("storepwd");
-        cf.setKeyManagerPassword("keypwd");
-        server.addConnector(connector);
-
-        File realmPropFile = MavenTestingUtils.getTestResourceFile("realm.properties");
-        LoginService loginService = new HashLoginService("MyRealm",realmPropFile.getAbsolutePath());
-        server.addBean(loginService); 
-
-        ConstraintSecurityHandler security = new ConstraintSecurityHandler();
-        server.setHandler(security);
-
-        Constraint constraint = new Constraint();
-        constraint.setName("auth");
-        constraint.setAuthenticate( true );
-        constraint.setRoles(new String[]{"user", "admin"});
-
-        ConstraintMapping mapping = new ConstraintMapping();
-        mapping.setPathSpec( "/*" );
-        mapping.setConstraint( constraint );
-
-        Set<String> knownRoles = new HashSet<String>();
-        knownRoles.add("user");
-        knownRoles.add("admin");
-        
-        security.setConstraintMappings(Collections.singletonList(mapping), knownRoles);
-        security.setAuthenticator(new BasicAuthenticator());
-        security.setLoginService(loginService);
-        security.setStrict(false);
-        
-        ServletContextHandler root = new ServletContextHandler();
-        root.setContextPath("/");
-        root.setResourceBase(getBasePath());
-        ServletHolder servletHolder = new ServletHolder( new DefaultServlet() );
-        servletHolder.setInitParameter( "gzip", "true" );
-        root.addServlet( servletHolder, "/*" );    
-
-        Handler handler = new TestHandler(getBasePath());       
-        
-        HandlerCollection handlers = new HandlerCollection();
-        handlers.setHandlers(new Handler[]{handler, root});
-        security.setHandler(handlers);
-    }
-}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/SslSecuredErrorStatusTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/SslSecuredErrorStatusTest.java
deleted file mode 100644
index 7c1202b..0000000
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/SslSecuredErrorStatusTest.java
+++ /dev/null
@@ -1,191 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client;
-
-import java.io.File;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
-
-import org.eclipse.jetty.client.security.Realm;
-import org.eclipse.jetty.http.HttpStatus;
-import org.eclipse.jetty.security.ConstraintMapping;
-import org.eclipse.jetty.security.ConstraintSecurityHandler;
-import org.eclipse.jetty.security.HashLoginService;
-import org.eclipse.jetty.security.LoginService;
-import org.eclipse.jetty.security.authentication.BasicAuthenticator;
-import org.eclipse.jetty.server.Handler;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.handler.HandlerCollection;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
-import org.eclipse.jetty.servlet.DefaultServlet;
-import org.eclipse.jetty.servlet.ServletContextHandler;
-import org.eclipse.jetty.servlet.ServletHolder;
-import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
-import org.eclipse.jetty.util.security.Constraint;
-import org.junit.Test;
-
-/* ------------------------------------------------------------ */
-public class SslSecuredErrorStatusTest
-    extends ErrorStatusTest
-{  
-    private Realm _testRealm;
-    private Realm _dummyRealm;
-    
-    /* ------------------------------------------------------------ */
-    @Test
-    public void testPutUnauthorized()
-        throws Exception
-    {
-        setRealm(null);
-        
-        doPutFail(HttpStatus.UNAUTHORIZED_401);
-        
-        setRealm(_testRealm);
-    }
-
-    /* ------------------------------------------------------------ */
-    @Test
-    public void testPutWrongPassword()
-        throws Exception
-    {
-        setRealm(_dummyRealm);
-        
-        doPutFail(HttpStatus.UNAUTHORIZED_401);
-        
-        setRealm(_testRealm);
-    }
-    
-    /* ------------------------------------------------------------ */
-    @Test
-    public void testGetUnauthorized()
-        throws Exception
-    {
-        setRealm(null);
-        
-        doGetFail(HttpStatus.UNAUTHORIZED_401);
-        
-        setRealm(_testRealm);
-    }
-    
-    /* ------------------------------------------------------------ */
-    @Test
-    public void testGetWrongPassword()
-        throws Exception
-    {
-        setRealm(_dummyRealm);
-        
-        doGetFail(HttpStatus.UNAUTHORIZED_401); 
-        
-        setRealm(_testRealm);
-    }
-
-    /* ------------------------------------------------------------ */
-    protected void configureServer(Server server)
-        throws Exception
-    {
-        setProtocol("http");
-        
-        _testRealm = new Realm()
-                         {
-                             /* ------------------------------------------------------------ */
-                             public String getId()
-                             {
-                                 return "MyRealm";
-                             }
-                               
-                             /* ------------------------------------------------------------ */
-                             public String getPrincipal()
-                             {
-                                 return "jetty";
-                             }
-                          
-                             /* ------------------------------------------------------------ */
-                             public String getCredentials()
-                             {
-                                 return "jetty";
-                             }
-                         };
-                         
-        _dummyRealm = new Realm()
-                          {
-                             /* ------------------------------------------------------------ */
-                              public String getId()
-                              {
-                                  return "MyRealm";
-                              }
-                    
-                              /* ------------------------------------------------------------ */
-                              public String getPrincipal()
-                              {
-                                  return "jetty";
-                              }
-                                                   
-                              /* ------------------------------------------------------------ */
-                              public String getCredentials()
-                              {
-                                  return "dummy";
-                              }
-                          };
-                          
-        setRealm(_testRealm);
-        
-        SelectChannelConnector connector = new SelectChannelConnector();
-        server.addConnector(connector);
-        
-        File realmPropFile = MavenTestingUtils.getTestResourceFile("realm.properties");
-        LoginService loginService = new HashLoginService("MyRealm",realmPropFile.getAbsolutePath());
-        server.addBean(loginService); 
-
-        ConstraintSecurityHandler security = new ConstraintSecurityHandler();
-        server.setHandler(security);
-
-        Constraint constraint = new Constraint();
-        constraint.setName("auth");
-        constraint.setAuthenticate( true );
-        constraint.setRoles(new String[]{"user", "admin"});
-
-        ConstraintMapping mapping = new ConstraintMapping();
-        mapping.setPathSpec( "/*" );
-        mapping.setConstraint( constraint );
-
-        Set<String> knownRoles = new HashSet<String>();
-        knownRoles.add("user");
-        knownRoles.add("admin");
-        
-        security.setConstraintMappings(Collections.singletonList(mapping), knownRoles);
-        security.setAuthenticator(new BasicAuthenticator());
-        security.setLoginService(loginService);
-        security.setStrict(false);
-                
-        ServletContextHandler root = new ServletContextHandler();
-        root.setContextPath("/");
-        root.setResourceBase(getBasePath());
-        ServletHolder servletHolder = new ServletHolder( new DefaultServlet() );
-        servletHolder.setInitParameter( "gzip", "true" );
-        root.addServlet( servletHolder, "/*" );    
-
-        Handler status = new StatusHandler();
-        Handler test = new TestHandler(getBasePath());
-        
-        HandlerCollection handlers = new HandlerCollection();
-        handlers.setHandlers(new Handler[]{status, test, root});
-        security.setHandler(handlers); 
-    }
-}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/SslSecurityListenerTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/SslSecurityListenerTest.java
deleted file mode 100644
index 4e02cf7..0000000
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/SslSecurityListenerTest.java
+++ /dev/null
@@ -1,269 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Locale;
-import java.util.Set;
-import java.util.concurrent.CyclicBarrier;
-import java.util.concurrent.TimeUnit;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jetty.client.security.HashRealmResolver;
-import org.eclipse.jetty.client.security.Realm;
-import org.eclipse.jetty.http.HttpMethods;
-import org.eclipse.jetty.io.EofException;
-import org.eclipse.jetty.security.ConstraintMapping;
-import org.eclipse.jetty.security.ConstraintSecurityHandler;
-import org.eclipse.jetty.security.HashLoginService;
-import org.eclipse.jetty.security.LoginService;
-import org.eclipse.jetty.security.authentication.BasicAuthenticator;
-import org.eclipse.jetty.server.Connector;
-import org.eclipse.jetty.server.Handler;
-import org.eclipse.jetty.server.Request;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.handler.AbstractHandler;
-import org.eclipse.jetty.server.ssl.SslSocketConnector;
-import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.util.security.Constraint;
-import org.eclipse.jetty.util.ssl.SslContextFactory;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-/* ------------------------------------------------------------ */
-/**
- * Functional testing.
- */
-public class SslSecurityListenerTest
-{
-    private static final Logger LOG = Log.getLogger(SslSecurityListenerTest.class);
-
-    protected  Server _server;
-    protected int _port;
-    protected HttpClient _httpClient;
-    protected Realm _jettyRealm;
-    protected int _type = HttpClient.CONNECTOR_SOCKET;
-    private static final String APP_CONTEXT = "localhost /";
-
-    /* ------------------------------------------------------------ */
-    @Before
-    public void setUp() throws Exception
-    {
-        startServer();
-        _httpClient = new HttpClient();
-        _httpClient.setConnectorType(_type);
-        _httpClient.setMaxConnectionsPerAddress(2);
-        _httpClient.start();
-
-        _jettyRealm = new Realm()
-        {
-            /* ------------------------------------------------------------ */
-            public String getId()
-            {
-                return "MyRealm";
-            }
-
-            /* ------------------------------------------------------------ */
-            public String getPrincipal()
-            {
-                return "jetty";
-            }
-
-            /* ------------------------------------------------------------ */
-            public String getCredentials()
-            {
-                return "jetty";
-            }
-        };
-
-        HashRealmResolver resolver = new HashRealmResolver();
-        resolver.addSecurityRealm(_jettyRealm);
-        _httpClient.setRealmResolver(resolver);
-    }
-
-    /* ------------------------------------------------------------ */
-    @After
-    public void tearDown() throws Exception
-    {
-        Thread.sleep(1000);
-        _httpClient.stop();
-        Thread.sleep(1000);
-        stopServer();
-    }
-
-    /* ------------------------------------------------------------ */
-    @Test
-    public void testSslGet() throws Exception
-    {
-        // TODO Resolve problems on IBM JVM https://bugs.eclipse.org/bugs/show_bug.cgi?id=304532
-        if (System.getProperty("java.vendor").toLowerCase(Locale.ENGLISH).indexOf("ibm")>=0)
-        {
-            LOG.warn("Skipped SSL testSslGet on IBM JVM");
-            return;
-        }
-        
-        
-        final CyclicBarrier barrier = new CyclicBarrier(2);
-        
-        ContentExchange httpExchange = new ContentExchange(true)
-        {
-            /* ------------------------------------------------------------ */
-            @Override
-            protected void onResponseComplete() throws IOException
-            {
-                super.onResponseComplete();
-                try{barrier.await();}catch(Exception e){}
-            }
-        };
-        
-        httpExchange.setURL("https://127.0.0.1:" + _port + "/");
-        httpExchange.setMethod(HttpMethods.GET);
-
-        _httpClient.send(httpExchange);
-        
-        barrier.await(10000,TimeUnit.SECONDS);
-        
-        assertEquals(HttpServletResponse.SC_OK,httpExchange.getResponseStatus());
-
-        assertTrue(httpExchange.getResponseContent().length()>400);
-        
-    }
-
-    /* ------------------------------------------------------------ */
-    protected void startServer() throws Exception
-    {
-        _server = new Server();
-        //SslSelectChannelConnector connector = new SslSelectChannelConnector();
-        SslSocketConnector connector = new SslSocketConnector();
-
-        String keystore = MavenTestingUtils.getTestResourceFile("keystore").getAbsolutePath();
-
-        connector.setPort(0);
-        SslContextFactory cf = connector.getSslContextFactory();
-        cf.setKeyStorePath(keystore);
-        cf.setKeyStorePassword("storepwd");
-        cf.setKeyManagerPassword("keypwd");
-
-        _server.setConnectors(new Connector[]
-        { connector });
-
-        Constraint constraint = new Constraint();
-        constraint.setName("Need User or Admin");
-        constraint.setRoles(new String[]
-        { "user", "admin" });
-        constraint.setAuthenticate(true);
-
-        ConstraintMapping cm = new ConstraintMapping();
-        cm.setConstraint(constraint);
-        cm.setPathSpec("/*");
-
-        File realmPropFile = MavenTestingUtils.getTestResourceFile("realm.properties");
-        LoginService loginService = new HashLoginService("MyRealm",realmPropFile.getAbsolutePath());
-        _server.addBean(loginService);
-        
-        BasicAuthenticator authenticator = new BasicAuthenticator();
-        
-        ConstraintSecurityHandler sh = new ConstraintSecurityHandler();
-        sh.setAuthenticator(authenticator);
-        
-        Set<String> roles = new HashSet<String>(Arrays.asList(new String[]{"user", "admin"}));
-        sh.setConstraintMappings(Collections.singletonList(cm), roles);
-        _server.setHandler(sh);
-
-        Handler testHandler = new AbstractHandler()
-        {
-            /* ------------------------------------------------------------ */
-            public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
-            {
-                // System.err.println("passed authentication!\n"+((Request)request).getConnection().getRequestFields());
-                
-                baseRequest.setHandled(true);
-                response.setStatus(200);
-                response.setContentType("text/plain");
-                if (request.getServerName().equals("jetty.eclipse.org"))
-                {
-                    response.getOutputStream().println("Proxy request: " + request.getRequestURL());
-                }
-                else if (request.getMethod().equalsIgnoreCase("GET"))
-                {
-                    response.getOutputStream().println("<hello>");
-                    for (int i = 0; i < 100; i++)
-                    {
-                        response.getOutputStream().println("  <world>" + i + "</world>");
-                        if (i % 20 == 0)
-                            response.getOutputStream().flush();
-                    }
-                    response.getOutputStream().println("</hello>");
-                }
-                else
-                {
-                    copyStream(request.getInputStream(),response.getOutputStream());
-                }
-            }
-        };
-
-        sh.setHandler(testHandler);
-
-        _server.start();
-        _port = connector.getLocalPort();
-    }
-
-    /* ------------------------------------------------------------ */
-    public static void copyStream(InputStream in, OutputStream out)
-    {
-        try
-        {
-            byte[] buffer = new byte[1024];
-            int len;
-            while ((len = in.read(buffer)) >= 0)
-            {
-                out.write(buffer,0,len);
-            }
-        }
-        catch (EofException e)
-        {
-            System.err.println(e);
-        }
-        catch (IOException e)
-        {
-            e.printStackTrace();
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    private void stopServer() throws Exception
-    {
-        _server.stop();
-    }
-}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/SslSelectChannelValidationTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/SslSelectChannelValidationTest.java
deleted file mode 100644
index 13cebf5..0000000
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/SslSelectChannelValidationTest.java
+++ /dev/null
@@ -1,30 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client;
-
-import org.eclipse.jetty.server.ssl.SslSelectChannelConnector;
-
-public class SslSelectChannelValidationTest extends SslValidationTestBase
-{
-    static
-    {
-        __klass = SslSelectChannelConnector.class;
-        __konnector = HttpClient.CONNECTOR_SELECT_CHANNEL;
-    }
-}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/SslSocketValidationTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/SslSocketValidationTest.java
deleted file mode 100644
index 1e38ae0..0000000
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/SslSocketValidationTest.java
+++ /dev/null
@@ -1,30 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client;
-
-import org.eclipse.jetty.server.ssl.SslSocketConnector;
-
-public class SslSocketValidationTest extends SslValidationTestBase
-{
-    static
-    {
-        __klass = SslSocketConnector.class;
-        __konnector = HttpClient.CONNECTOR_SOCKET;
-    }
-}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/SslValidationTestBase.java b/jetty-client/src/test/java/org/eclipse/jetty/client/SslValidationTestBase.java
deleted file mode 100644
index dfa1179..0000000
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/SslValidationTestBase.java
+++ /dev/null
@@ -1,116 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client;
-
-import java.io.InputStream;
-import java.lang.reflect.Constructor;
-import java.security.KeyStore;
-import java.security.cert.CRL;
-import java.util.Collection;
-
-import org.eclipse.jetty.server.Handler;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.handler.HandlerCollection;
-import org.eclipse.jetty.server.ssl.SslConnector;
-import org.eclipse.jetty.servlet.DefaultServlet;
-import org.eclipse.jetty.servlet.ServletContextHandler;
-import org.eclipse.jetty.servlet.ServletHolder;
-import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
-import org.eclipse.jetty.util.security.CertificateUtils;
-import org.eclipse.jetty.util.ssl.SslContextFactory;
-
-public abstract class SslValidationTestBase //extends ContentExchangeTest
-{
-    protected static Class<? extends SslConnector> __klass;
-    protected static int __konnector;
-
-    // certificate is valid until Jan 1, 2050
-    private String _keypath = MavenTestingUtils.getTargetFile("test-policy/validation/jetty-valid.keystore").getAbsolutePath();
-    private String _trustpath = MavenTestingUtils.getTargetFile("test-policy/validation/jetty-trust.keystore").getAbsolutePath();
-    private String _clientpath = MavenTestingUtils.getTargetFile("test-policy/validation/jetty-client.keystore").getAbsolutePath();
-    private String _crlpath = MavenTestingUtils.getTargetFile("test-policy/validation/crlfile.pem").getAbsolutePath();
-    private String _password = "OBF:1wnl1sw01ta01z0f1tae1svy1wml";
-    
-    
-    protected void configureServer(Server server)
-        throws Exception
-    {
-//        setProtocol("https");
-//
-//        SslContextFactory srvFactory = new SslContextFactory() {
-//            @Override
-//            protected KeyStore getKeyStore(InputStream storeStream, String storePath, String storeType, String storeProvider, String storePassword) throws Exception
-//            {
-//                return CertificateUtils.getKeyStore(storeStream, storePath, storeType, storeProvider, storePassword);
-//            }
-//
-//            @Override
-//            protected Collection<? extends CRL> loadCRL(String crlPath) throws Exception
-//            {
-//                return CertificateUtils.loadCRL(crlPath);
-//            }
-//        };
-//        srvFactory.setValidateCerts(true);
-//        srvFactory.setCrlPath(_crlpath);
-//        srvFactory.setNeedClientAuth(true);
-//
-//        srvFactory.setKeyStorePath(_keypath);
-//        srvFactory.setKeyStorePassword(_password);
-//        srvFactory.setKeyManagerPassword(_password);
-//        
-//        srvFactory.setTrustStore(_trustpath);
-//        srvFactory.setTrustStorePassword(_password);
-//
-//        Constructor<? extends SslConnector> constructor = __klass.getConstructor(SslContextFactory.class);
-//        SslConnector connector = constructor.newInstance(srvFactory);
-//        connector.setMaxIdleTime(5000);
-//        server.addConnector(connector);
-//
-//        Handler handler = new TestHandler(getBasePath());
-//
-//        ServletContextHandler root = new ServletContextHandler();
-//        root.setContextPath("/");
-//        root.setResourceBase(getBasePath());
-//        ServletHolder servletHolder = new ServletHolder( new DefaultServlet() );
-//        servletHolder.setInitParameter( "gzip", "true" );
-//        root.addServlet( servletHolder, "/*" );
-//
-//        HandlerCollection handlers = new HandlerCollection();
-//        handlers.setHandlers(new Handler[]{handler, root});
-//        server.setHandler( handlers );
-//    }
-//
-//    @Override
-//    protected void configureClient(HttpClient client)
-//        throws Exception
-//    {
-//        client.setConnectorType(__konnector);
-//
-//        SslContextFactory cf = client.getSslContextFactory();
-//        cf.setValidateCerts(true);
-//        cf.setCrlPath(_crlpath);
-//        
-//        cf.setKeyStorePath(_clientpath);
-//        cf.setKeyStorePassword(_password);
-//        cf.setKeyManagerPassword(_password);
-//        
-//        cf.setTrustStore(_trustpath);
-//        cf.setTrustStorePassword(_password);
-    }
-}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/TimeoutExchangeTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/TimeoutExchangeTest.java
deleted file mode 100644
index cb14563..0000000
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/TimeoutExchangeTest.java
+++ /dev/null
@@ -1,379 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client;
-
-import java.io.IOException;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import junit.framework.Assert;
-
-import org.eclipse.jetty.http.HttpMethods;
-import org.eclipse.jetty.http.HttpStatus;
-import org.eclipse.jetty.io.ByteArrayBuffer;
-import org.eclipse.jetty.server.Connector;
-import org.eclipse.jetty.server.Handler;
-import org.eclipse.jetty.server.Request;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.handler.AbstractHandler;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
-import org.junit.After;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.Test;
-
-public class TimeoutExchangeTest
-{
-    private static HttpClient _httpClient;
-    private static Server _server;
-    private static int _port;
-
-    @BeforeClass
-    public static void startServer() throws Exception
-    {
-        _server = new Server();
-        _server.setGracefulShutdown(500);
-        Connector _connector = new SelectChannelConnector();
-        _server.addConnector(_connector);
-        Handler handler = new AbstractHandler()
-        {
-            public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException,
-                    ServletException
-            {
-                try
-                {
-                    Long sleep = Long.parseLong(request.getParameter("sleep"));
-                    Thread.sleep(sleep);
-                    response.setContentType("text/html");
-                    response.setStatus(HttpServletResponse.SC_OK);
-                    response.getWriter().println("<h1>Hello</h1>");
-                    baseRequest.setHandled(true);
-                }
-                catch (InterruptedException x)
-                {
-                    Thread.currentThread().interrupt();
-                    throw new ServletException(x);
-                }
-            }
-        };
-        _server.setHandler(handler);
-        _server.start();
-        _port = _connector.getLocalPort();
-    }
-
-    @AfterClass
-    public static void stopServer() throws Exception
-    {
-        _server.stop();
-        _server.join();
-        _server = null;
-    }
-
-    @After
-    public void stopClient() throws Exception
-    {
-        if (_httpClient != null)
-        {
-            _httpClient.stop();
-            _httpClient = null;
-        }
-    }
-
-    private void startClient(long clientTimeout) throws Exception
-    {
-        startClient(clientTimeout, 20000);
-    }
-
-    private void startClient(long clientTimeout, long maxIdleTimeout) throws Exception
-    {
-        _httpClient = new HttpClient();
-        _httpClient.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);
-        _httpClient.setMaxConnectionsPerAddress(2);
-        _httpClient.setTimeout(clientTimeout);
-        _httpClient.setIdleTimeout(maxIdleTimeout);
-        _httpClient.start();
-    }
-
-    @Test
-    public void testDefaultTimeoutNotExpiring() throws Exception
-    {
-        startClient(300);
-        long serverSleep = 100;
-
-        CustomContentExchange httpExchange = new CustomContentExchange();
-        httpExchange.setURL("http://localhost:" + _port + "/?sleep=" + serverSleep);
-        httpExchange.setMethod(HttpMethods.POST);
-        httpExchange.setRequestContent(new ByteArrayBuffer("<h1>??</h1>"));
-        _httpClient.send(httpExchange);
-
-        Assert.assertTrue(httpExchange.getDoneLatch().await(4 * serverSleep, TimeUnit.MILLISECONDS));
-        Assert.assertFalse(httpExchange.isTimeoutOccurred());
-        Assert.assertTrue(httpExchange.isResponseReceived());
-        Assert.assertFalse(httpExchange.isErrorOccurred());
-    }
-
-    @Test
-    public void testDefaultTimeoutExpiring() throws Exception
-    {
-        startClient(100);
-        long serverSleep = 200;
-
-        CustomContentExchange httpExchange = new CustomContentExchange();
-        httpExchange.setURL("http://localhost:" + _port + "/?sleep=" + serverSleep);
-        httpExchange.setMethod(HttpMethods.POST);
-        httpExchange.setRequestContent(new ByteArrayBuffer("<h1>??</h1>"));
-        _httpClient.send(httpExchange);
-
-        Assert.assertTrue(httpExchange.getDoneLatch().await(2 * serverSleep, TimeUnit.MILLISECONDS));
-        Assert.assertTrue(httpExchange.isTimeoutOccurred());
-        Assert.assertFalse(httpExchange.isResponseReceived());
-        Assert.assertFalse(httpExchange.isErrorOccurred());
-    }
-
-    @Test
-    public void testExchangeTimeoutNotExpiring() throws Exception
-    {
-        startClient(100);
-        long serverSleep = 200;
-        long exchangeTimeout = 300;
-
-        CustomContentExchange httpExchange = new CustomContentExchange();
-        httpExchange.setURL("http://localhost:" + _port + "/?sleep=" + serverSleep);
-        httpExchange.setMethod(HttpMethods.POST);
-        httpExchange.setRequestContent(new ByteArrayBuffer("<h1>??</h1>"));
-        httpExchange.setTimeout(exchangeTimeout);
-        _httpClient.send(httpExchange);
-
-        Assert.assertTrue(httpExchange.getDoneLatch().await(2 * exchangeTimeout, TimeUnit.MILLISECONDS));
-        Assert.assertFalse(httpExchange.isTimeoutOccurred());
-        Assert.assertTrue(httpExchange.isResponseReceived());
-        Assert.assertFalse(httpExchange.isErrorOccurred());
-    }
-
-    @Test
-    public void testExchangeTimeoutExpiring() throws Exception
-    {
-        startClient(500);
-
-        long serverSleep = 300;
-        long exchangeTimeout = 100;
-
-        CustomContentExchange httpExchange = new CustomContentExchange();
-        httpExchange.setURL("http://localhost:" + _port + "/?sleep=" + serverSleep);
-        httpExchange.setMethod(HttpMethods.POST);
-        httpExchange.setRequestContent(new ByteArrayBuffer("<h1>??</h1>"));
-        httpExchange.setTimeout(exchangeTimeout);
-        _httpClient.send(httpExchange);
-
-        Assert.assertTrue(httpExchange.getDoneLatch().await(2 * serverSleep, TimeUnit.MILLISECONDS));
-        Assert.assertTrue(httpExchange.isTimeoutOccurred());
-        Assert.assertFalse(httpExchange.isResponseReceived());
-        Assert.assertFalse(httpExchange.isErrorOccurred());
-    }
-
-    @Test
-    public void testDefaultTimeoutWithSmallerIdleTimeoutNotExpiring() throws Exception
-    {
-        startClient(500,150);
-        long serverSleep = 300;
-
-        // The idle timeout is shorter than the default timeout, but will be
-        // temporarily increased on the endpoint in order for the exchange to complete.
-
-        CustomContentExchange httpExchange = new CustomContentExchange();
-        httpExchange.setURL("http://localhost:" + _port + "/?sleep=" + serverSleep);
-        httpExchange.setMethod(HttpMethods.POST);
-        httpExchange.setRequestContent(new ByteArrayBuffer("<h1>??</h1>"));
-        _httpClient.send(httpExchange);
-
-        Assert.assertTrue(httpExchange.getDoneLatch().await(2 * serverSleep, TimeUnit.MILLISECONDS));
-        Assert.assertFalse(httpExchange.isTimeoutOccurred());
-        Assert.assertTrue(httpExchange.isResponseReceived());
-        Assert.assertFalse(httpExchange.isErrorOccurred());
-    }
-
-    @Test
-    public void testExchangeTimeoutWithSmallerIdleTimeoutNotExpiring() throws Exception
-    {
-        startClient(500,150);
-        long serverSleep = 150;
-        long exchangeTimeout = 300;
-
-        // The idle timeout is shorter than the default timeout, but will be
-        // temporarily increased on the endpoint in order for the exchange to complete.
-
-        CustomContentExchange httpExchange = new CustomContentExchange();
-        httpExchange.setURL("http://localhost:" + _port + "/?sleep=" + serverSleep);
-        httpExchange.setMethod(HttpMethods.POST);
-        httpExchange.setRequestContent(new ByteArrayBuffer("<h1>??</h1>"));
-        httpExchange.setTimeout(exchangeTimeout);
-        _httpClient.send(httpExchange);
-
-        Assert.assertTrue(httpExchange.getDoneLatch().await(2 * serverSleep, TimeUnit.MILLISECONDS));
-        Assert.assertFalse(httpExchange.isTimeoutOccurred());
-        Assert.assertTrue(httpExchange.isResponseReceived());
-        Assert.assertFalse(httpExchange.isErrorOccurred());
-    }
-
-    private class CustomContentExchange extends ContentExchange
-    {
-        private final CountDownLatch _doneLatch = new CountDownLatch(1);
-        private boolean _errorOccurred = false;
-        private boolean _timeoutOccurred = false;
-        private boolean _responseReceived = false;
-
-        public boolean isErrorOccurred()
-        {
-            return _errorOccurred;
-        }
-
-        public boolean isTimeoutOccurred()
-        {
-            return _timeoutOccurred;
-        }
-
-        public boolean isResponseReceived()
-        {
-            return _responseReceived;
-        }
-
-        public CustomContentExchange()
-        {
-            super(true);
-        }
-
-        @Override
-        protected void onResponseComplete() throws IOException
-        {
-            try
-            {
-                super.onResponseComplete();
-            }
-            finally
-            {
-                doTaskCompleted();
-            }
-        }
-
-        @Override
-        protected void onExpire()
-        {
-            try
-            {
-                super.onExpire();
-            }
-            finally
-            {
-                doTaskCompleted();
-            }
-        }
-
-        @Override
-        protected void onException(Throwable ex)
-        {
-            try
-            {
-                super.onException(ex);
-            }
-            finally
-            {
-                doTaskCompleted(ex);
-            }
-        }
-
-        @Override
-        protected void onConnectionFailed(Throwable ex)
-        {
-            try
-            {
-                super.onConnectionFailed(ex);
-            }
-            finally
-            {
-                doTaskCompleted(ex);
-            }
-        }
-
-        protected void doTaskCompleted()
-        {
-            int exchangeState = getStatus();
-
-            try
-            {
-                if (exchangeState == HttpExchange.STATUS_COMPLETED)
-                {
-                    // process the response as the state is ok
-                    try
-                    {
-                        int responseCode = getResponseStatus();
-
-                        if (responseCode >= HttpStatus.CONTINUE_100 && responseCode < HttpStatus.MULTIPLE_CHOICES_300)
-                        {
-                            _responseReceived = true;
-                        }
-                        else
-                        {
-                            _errorOccurred = true;
-                        }
-                    }
-                    catch (Exception e)
-                    {
-                        _errorOccurred = true;
-                        e.printStackTrace();
-                    }
-                }
-                else if (exchangeState == HttpExchange.STATUS_EXPIRED)
-                {
-                    _timeoutOccurred = true;
-                }
-                else
-                {
-                    _errorOccurred = true;
-                }
-            }
-            finally
-            {
-                // make sure to lower the latch
-                getDoneLatch().countDown();
-            }
-        }
-
-        protected void doTaskCompleted(Throwable ex)
-        {
-            try
-            {
-                _errorOccurred = true;
-            }
-            finally
-            {
-                // make sure to lower the latch
-                getDoneLatch().countDown();
-            }
-        }
-
-        public CountDownLatch getDoneLatch()
-        {
-            return _doneLatch;
-        }
-    }
-}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/TimeoutTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/TimeoutTest.java
deleted file mode 100644
index 8ae2dae..0000000
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/TimeoutTest.java
+++ /dev/null
@@ -1,438 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client;
-
-import static org.hamcrest.Matchers.*;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.net.Socket;
-import java.net.SocketException;
-import java.nio.channels.SocketChannel;
-import java.util.Arrays;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicReference;
-
-import javax.servlet.ServletException;
-import javax.servlet.ServletInputStream;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jetty.http.HttpParser;
-import org.eclipse.jetty.io.AsyncEndPoint;
-import org.eclipse.jetty.io.Buffers;
-import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.io.nio.AsyncConnection;
-import org.eclipse.jetty.server.Request;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.handler.AbstractHandler;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
-import org.eclipse.jetty.toolchain.test.IO;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-import org.junit.After;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
-
-public class TimeoutTest
-{
-    private static final Logger logger = Log.getLogger(TimeoutTest.class);
-    private final AtomicInteger httpParses = new AtomicInteger();
-    private final AtomicInteger httpRequests = new AtomicInteger();
-    private ExecutorService threadPool;
-    private Server server;
-    private int serverPort;
-    private final AtomicReference<EndPoint> serverEndPoint = new AtomicReference<EndPoint>();
-
-    @Before
-    public void init() throws Exception
-    {
-        threadPool = Executors.newCachedThreadPool();
-        server = new Server();
-
-        SelectChannelConnector connector = new SelectChannelConnector()
-        {
-            @Override
-            protected AsyncConnection newConnection(SocketChannel channel, final AsyncEndPoint endPoint)
-            {
-                serverEndPoint.set(endPoint);
-                return new org.eclipse.jetty.server.AsyncHttpConnection(this,endPoint,getServer())
-                {
-                    @Override
-                    protected HttpParser newHttpParser(Buffers requestBuffers, EndPoint endPoint, HttpParser.EventHandler requestHandler)
-                    {
-                        return new HttpParser(requestBuffers,endPoint,requestHandler)
-                        {
-                            @Override
-                            public int parseNext() throws IOException
-                            {
-                                httpParses.incrementAndGet();
-                                return super.parseNext();
-                            }
-                        };
-                    }
-                };
-            }
-        };
-        connector.setMaxIdleTime(2000);
-
-        //        connector.setPort(5870);
-        connector.setPort(0);
-
-        server.addConnector(connector);
-        server.setHandler(new AbstractHandler()
-        {
-            public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException,
-                    ServletException
-            {
-                httpRequests.incrementAndGet();
-                request.setHandled(true);
-                String contentLength = request.getHeader("Content-Length");
-                if (contentLength != null)
-                {
-                    int length = Integer.parseInt(contentLength);
-                    ServletInputStream input = request.getInputStream();
-                    for (int i = 0; i < length; ++i)
-                        input.read();
-                }
-            }
-        });
-        server.start();
-        serverPort = connector.getLocalPort();
-
-        httpRequests.set(0);
-        logger.debug(" => :{}",serverPort);
-    }
-
-    @After
-    public void destroy() throws Exception
-    {
-        if (server != null)
-            server.stop();
-        if (threadPool != null)
-            threadPool.shutdownNow();
-    }
-
-    private Socket newClient() throws IOException, InterruptedException
-    {
-        Socket client = new Socket("localhost",serverPort);
-        return client;
-    }
-
-    /**
-     * Test that performs a normal http POST request, with connection:close.
-     * Check that shutdownOutput is sufficient to close the server connection.
-     */
-    @Test
-    public void testServerCloseClientDoesClose() throws Exception
-    {
-        // Log.getLogger("").setDebugEnabled(true);
-        final Socket client = newClient();
-        final OutputStream clientOutput = client.getOutputStream();
-
-        byte[] data = new byte[3 * 1024];
-        Arrays.fill(data,(byte)'Y');
-        String content = new String(data,"UTF-8");
-        
-        // The request section
-        StringBuilder req = new StringBuilder();
-        req.append("POST / HTTP/1.1\r\n");
-        req.append("Host: localhost\r\n");
-        req.append("Content-Type: text/plain\r\n");
-        req.append("Content-Length: ").append(content.length()).append("\r\n");
-        req.append("Connection: close\r\n");
-        req.append("\r\n");
-        // and now, the POST content section.
-        req.append(content);
-
-        // Send request to server
-        clientOutput.write(req.toString().getBytes("UTF-8"));
-        clientOutput.flush();
-
-        InputStream in = null;
-        InputStreamReader isr = null;
-        BufferedReader reader = null;
-        try
-        {
-            in = client.getInputStream();
-            isr = new InputStreamReader(in);
-            reader = new BufferedReader(isr);
-
-            // Read the response header
-            String line = reader.readLine();
-            Assert.assertNotNull(line);
-            Assert.assertThat(line,startsWith("HTTP/1.1 200 "));
-            while ((line = reader.readLine()) != null)
-            {
-                if (line.trim().length() == 0)
-                {
-                    break;
-                }
-            }
-            Assert.assertEquals("one request handled",1,httpRequests.get());
-
-            Assert.assertEquals("EOF received",-1,client.getInputStream().read());
-            
-            // shutdown the output
-            client.shutdownOutput();
-
-            // Check that we did not spin
-            int httpParseCount = httpParses.get();
-            Assert.assertThat(httpParseCount,lessThan(50));
-            
-            // Try to write another request (to prove that stream is closed)
-            try
-            {
-                clientOutput.write(req.toString().getBytes("UTF-8"));
-                clientOutput.flush();
-
-                Assert.fail("Should not have been able to send a second POST request (connection: close)");
-            }
-            catch(SocketException e)
-            {
-            }
-            
-            Assert.assertEquals("one request handled",1,httpRequests.get());
-        }
-        finally
-        {
-            IO.close(reader);
-            IO.close(isr);
-            IO.close(in);
-            closeClient(client);
-        }
-    }
-    
-    /**
-     * Test that performs a seemingly normal http POST request, but with
-     * a client that issues "connection: close", and then attempts to
-     * write a second POST request.
-     * <p>
-     * The connection should be closed by the server
-     */
-    @Test
-    public void testServerCloseClientMoreDataSent() throws Exception
-    {
-        // Log.getLogger("").setDebugEnabled(true);
-        final Socket client = newClient();
-        final OutputStream clientOutput = client.getOutputStream();
-
-        byte[] data = new byte[3 * 1024];
-        Arrays.fill(data,(byte)'Y');
-        String content = new String(data,"UTF-8");
-        
-        // The request section
-        StringBuilder req = new StringBuilder();
-        req.append("POST / HTTP/1.1\r\n");
-        req.append("Host: localhost\r\n");
-        req.append("Content-Type: text/plain\r\n");
-        req.append("Content-Length: ").append(content.length()).append("\r\n");
-        req.append("Connection: close\r\n");
-        req.append("\r\n");
-        // and now, the POST content section.
-        req.append(content);
-
-        // Send request to server
-        clientOutput.write(req.toString().getBytes("UTF-8"));
-        clientOutput.flush();
-
-        InputStream in = null;
-        InputStreamReader isr = null;
-        BufferedReader reader = null;
-        try
-        {
-            in = client.getInputStream();
-            isr = new InputStreamReader(in);
-            reader = new BufferedReader(isr);
-
-            // Read the response header
-            String line = reader.readLine();
-            Assert.assertNotNull(line);
-            Assert.assertThat(line,startsWith("HTTP/1.1 200 "));
-            while ((line = reader.readLine()) != null)
-            {
-                if (line.trim().length() == 0)
-                {
-                    break;
-                }
-            }
-
-            Assert.assertEquals("EOF received",-1,client.getInputStream().read());
-            Assert.assertEquals("one request handled",1,httpRequests.get());
-            
-            // Don't shutdown the output
-            // client.shutdownOutput();
-            
-            // server side seeking EOF
-            Assert.assertTrue("is open",serverEndPoint.get().isOpen());
-            Assert.assertTrue("close sent",serverEndPoint.get().isOutputShutdown());
-            Assert.assertFalse("close not received",serverEndPoint.get().isInputShutdown());
-            
-
-            // Check that we did not spin
-            TimeUnit.SECONDS.sleep(1);
-            int httpParseCount = httpParses.get();
-            Assert.assertThat(httpParseCount,lessThan(50));
-            
-
-            // Write another request (which is ignored as the stream is closing), which causes real close.
-            clientOutput.write(req.toString().getBytes("UTF-8"));
-            clientOutput.flush();
-
-            // Check that we did not spin
-            TimeUnit.SECONDS.sleep(1);
-            httpParseCount = httpParses.get();
-            Assert.assertThat(httpParseCount,lessThan(50));
-            
-
-            // server side is closed
-            Assert.assertFalse("is open",serverEndPoint.get().isOpen());
-            Assert.assertTrue("close sent",serverEndPoint.get().isOutputShutdown());
-            Assert.assertTrue("close not received",serverEndPoint.get().isInputShutdown());
-            
-            Assert.assertEquals("one request handled",1,httpRequests.get());
-
-        }
-        finally
-        {
-            IO.close(reader);
-            IO.close(isr);
-            IO.close(in);
-            closeClient(client);
-        }
-    }
-    
-    
-    /**
-     * Test that performs a seemingly normal http POST request, but with
-     * a client that issues "connection: close", and then does not close 
-     * the connection after reading the response.
-     * <p>
-     * The connection should be closed by the server after a timeout.
-     */
-    @Test
-    public void testServerCloseClientDoesNotClose() throws Exception
-    {
-        // Log.getLogger("").setDebugEnabled(true);
-        final Socket client = newClient();
-        final OutputStream clientOutput = client.getOutputStream();
-
-        byte[] data = new byte[3 * 1024];
-        Arrays.fill(data,(byte)'Y');
-        String content = new String(data,"UTF-8");
-        
-        // The request section
-        StringBuilder req = new StringBuilder();
-        req.append("POST / HTTP/1.1\r\n");
-        req.append("Host: localhost\r\n");
-        req.append("Content-Type: text/plain\r\n");
-        req.append("Content-Length: ").append(content.length()).append("\r\n");
-        req.append("Connection: close\r\n");
-        req.append("\r\n");
-        // and now, the POST content section.
-        req.append(content);
-
-        // Send request to server
-        clientOutput.write(req.toString().getBytes("UTF-8"));
-        clientOutput.flush();
-
-        InputStream in = null;
-        InputStreamReader isr = null;
-        BufferedReader reader = null;
-        try
-        {
-            in = client.getInputStream();
-            isr = new InputStreamReader(in);
-            reader = new BufferedReader(isr);
-
-            // Read the response header
-            String line = reader.readLine();
-            Assert.assertNotNull(line);
-            Assert.assertThat(line,startsWith("HTTP/1.1 200 "));
-            while ((line = reader.readLine()) != null)
-            {
-                if (line.trim().length() == 0)
-                {
-                    break;
-                }
-            }
-
-            Assert.assertEquals("EOF received",-1,client.getInputStream().read());
-            Assert.assertEquals("one request handled",1,httpRequests.get());
-            
-            // Don't shutdown the output
-            // client.shutdownOutput();
-            
-            // server side seeking EOF
-            Assert.assertTrue("is open",serverEndPoint.get().isOpen());
-            Assert.assertTrue("close sent",serverEndPoint.get().isOutputShutdown());
-            Assert.assertFalse("close not received",serverEndPoint.get().isInputShutdown());
-            
-
-            // Wait for the server idle timeout
-            TimeUnit.SECONDS.sleep(3);
-            int httpParseCount = httpParses.get();
-            Assert.assertThat(httpParseCount,lessThan(50));
-
-            // server side is closed
-            Assert.assertFalse("is open",serverEndPoint.get().isOpen());
-            Assert.assertTrue("close sent",serverEndPoint.get().isOutputShutdown());
-            Assert.assertTrue("close not received",serverEndPoint.get().isInputShutdown());
-            
-            Assert.assertEquals("one request handled",1,httpRequests.get());
-            
-            
-            // client will eventually get broken pipe if it keeps writing
-            try
-            {
-                for (int i=0;i<1000;i++)
-                {
-                    clientOutput.write(req.toString().getBytes("UTF-8"));
-                    clientOutput.flush(); 
-                }
-                Assert.fail("Client should have seen a broken pipe");
-            }
-            catch(IOException e)
-            {
-                // expected broken pipe
-            }
-
-        }
-        finally
-        {
-            IO.close(reader);
-            IO.close(isr);
-            IO.close(in);
-            closeClient(client);
-        }
-    }
-
-    private void closeClient(Socket client) throws IOException
-    {
-        client.close();
-    }
-}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/UnexpectedDataTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/UnexpectedDataTest.java
deleted file mode 100644
index 4ddff9b..0000000
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/UnexpectedDataTest.java
+++ /dev/null
@@ -1,181 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jetty.http.HttpMethods;
-import org.eclipse.jetty.server.Connector;
-import org.eclipse.jetty.server.Request;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.handler.AbstractHandler;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
-import org.eclipse.jetty.util.IO;
-import org.junit.After;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-
-/**
- * Functional testing for HttpExchange.
- *
- * @author Matthew Purland
- * @author Greg Wilkins
- */
-public class UnexpectedDataTest
-{
-    private Server _server;
-    private int _port;
-    private HttpClient _httpClient;
-    private Connector _connector;
-    private AtomicInteger _count = new AtomicInteger();
-
-    @Before
-    public void setUp() throws Exception
-    {
-        startServer();
-        _httpClient = new HttpClient();
-        _httpClient.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);
-        _httpClient.setMaxConnectionsPerAddress(1);
-        _httpClient.start();
-    }
-
-    @After
-    public void tearDown() throws Exception
-    {
-        _httpClient.stop();
-        Thread.sleep(500);
-        stopServer();
-    }
-
-    @Test
-    public void testUnexpectedData() throws Exception
-    {
-        for (int i = 0; i < 4; ++i)
-        {
-            final CountDownLatch done = new CountDownLatch(1);
-            ContentExchange httpExchange = new ContentExchange()
-            {
-                protected void onResponseComplete() throws IOException
-                {
-                    super.onResponseComplete();
-                    done.countDown();
-                }
-            };
-            httpExchange.setURL("http://localhost:" + _port + "/?i=" + i);
-            httpExchange.setMethod(HttpMethods.GET);
-            _httpClient.send(httpExchange);
-
-            Assert.assertTrue(done.await(1000, TimeUnit.SECONDS));
-
-            int status = httpExchange.getStatus();
-            String result = httpExchange.getResponseContent();
-            Assert.assertEquals("i=" + i, 0, result.indexOf("<hello>"));
-            Assert.assertEquals("i=" + i, result.length() - 10, result.indexOf("</hello>"));
-            Assert.assertEquals(HttpExchange.STATUS_COMPLETED, status);
-
-            // Give the client the time to read -1 from server before issuing the next request
-            // There is currently no simple way to be notified of connection closed.
-            Thread.sleep(500);
-        }
-    }
-
-    protected void newServer() throws Exception
-    {
-        _server = new Server();
-        _server.setGracefulShutdown(500);
-        _connector = new SelectChannelConnector();
-        _connector.setPort(0);
-        _server.setConnectors(new Connector[]{_connector});
-    }
-
-    protected void startServer() throws Exception
-    {
-        newServer();
-        _server.setHandler(new AbstractHandler()
-        {
-            public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
-                    throws IOException, ServletException
-            {
-                int i = 0;
-                try
-                {
-                    baseRequest.setHandled(true);
-                    response.setStatus(200);
-                    _count.incrementAndGet();
-
-                    if (request.getMethod().equalsIgnoreCase("GET"))
-                    {
-                        StringBuilder buffer = new StringBuilder();
-                        buffer.append("<hello>\r\n");
-                        for (; i < 100; i++)
-                        {
-                            buffer.append("  <world>").append(i).append("</world>\r\n");
-                        }
-                        buffer.append("</hello>\r\n");
-
-                        byte[] buff = buffer.toString().getBytes();
-                        response.setContentLength(buff.length);
-
-                        buffer.append("extra data");
-                        buff = buffer.toString().getBytes();
-
-                        OutputStream out = response.getOutputStream();
-                        out.write(buff, 0, buff.length);
-                        out.flush();
-                    }
-                    else
-                    {
-                        response.setContentType(request.getContentType());
-                        int size = request.getContentLength();
-                        ByteArrayOutputStream bout = new ByteArrayOutputStream(size > 0 ? size : 32768);
-                        IO.copy(request.getInputStream(), bout);
-                        response.getOutputStream().write(bout.toByteArray());
-                    }
-                }
-                catch (IOException e)
-                {
-                    e.printStackTrace();
-                    throw e;
-                }
-                catch (Throwable e)
-                {
-                    e.printStackTrace();
-                    throw new ServletException(e);
-                }
-            }
-        });
-        _server.start();
-        _port = _connector.getLocalPort();
-    }
-
-    private void stopServer() throws Exception
-    {
-        _server.stop();
-    }
-}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/WebSocketUpgradeTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/WebSocketUpgradeTest.java
deleted file mode 100644
index 561fb21..0000000
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/WebSocketUpgradeTest.java
+++ /dev/null
@@ -1,265 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client;
-
-import static org.junit.Assert.assertEquals;
-
-import java.io.IOException;
-import java.util.concurrent.ArrayBlockingQueue;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.concurrent.TimeUnit;
-
-import javax.servlet.http.HttpServletRequest;
-
-import org.eclipse.jetty.http.HttpMethods;
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.Connection;
-import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.server.Connector;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
-import org.eclipse.jetty.websocket.WebSocket;
-import org.eclipse.jetty.websocket.WebSocketBuffers;
-import org.eclipse.jetty.websocket.WebSocketConnectionD00;
-import org.eclipse.jetty.websocket.WebSocketHandler;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-/* ------------------------------------------------------------ */
-/**
- * Functional testing for HttpExchange.
- */
-public class WebSocketUpgradeTest
-{
-    protected Server _server;
-    protected int _port;
-    protected HttpClient _httpClient;
-    protected Connector _connector;
-    protected ConcurrentLinkedQueue<TestWebSocket> _webSockets= new ConcurrentLinkedQueue<TestWebSocket>();
-    protected WebSocketHandler _handler;
-    protected TestWebSocket _websocket;
-    final BlockingQueue<Object> _results = new ArrayBlockingQueue<Object>(100);
-
-    /* ------------------------------------------------------------ */
-    @Before
-    public void setUp() throws Exception
-    {
-        startServer();
-        _httpClient=new HttpClient();
-        _httpClient.setIdleTimeout(2000);
-        _httpClient.setTimeout(2500);
-        _httpClient.setConnectTimeout(1000);
-        _httpClient.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);
-        _httpClient.setMaxConnectionsPerAddress(10);
-        _httpClient.start();
-    }
-
-    /* ------------------------------------------------------------ */
-    @After
-    public void tearDown() throws Exception
-    {
-        _httpClient.stop();
-        Thread.sleep(500);
-        stopServer();
-    }
-
-    /* ------------------------------------------------------------ */
-    @Test
-    public void testGetWithContentExchange() throws Exception
-    {
-        final WebSocket clientWS = new WebSocket.OnTextMessage()
-        {
-            Connection _connection;
-            
-            /* ------------------------------------------------------------ */
-            public void onClose(int closeCode, String message)
-            {
-            }
-            
-            /* ------------------------------------------------------------ */
-            public void onOpen(Connection connection)
-            {
-                _connection=connection;
-                _results.add("clientWS.onConnect");
-                _results.add(_connection);
-            }
-            
-            /* ------------------------------------------------------------ */
-            public void onMessage(String data)
-            {
-                _results.add("clientWS.onMessage");
-                _results.add(data);
-            }
-        };
-
-
-        HttpExchange httpExchange=new HttpExchange()
-        {
-            /* ------------------------------------------------------------ */
-            /**
-             * @see org.eclipse.jetty.client.HttpExchange#onResponseStatus(org.eclipse.jetty.io.Buffer, int, org.eclipse.jetty.io.Buffer)
-             */
-            @Override
-            protected void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException
-            {
-                waitFor(2);
-                _results.add(new Integer(status));
-                super.onResponseStatus(version,status,reason);
-            }
-
-            /* ------------------------------------------------------------ */
-            /**
-             * @see org.eclipse.jetty.client.HttpExchange#onSwitchProtocol(org.eclipse.jetty.io.EndPoint)
-             */
-            @Override
-            protected Connection onSwitchProtocol(EndPoint endp) throws IOException
-            {
-                waitFor(3);
-                WebSocketConnectionD00 connection = new WebSocketConnectionD00(clientWS,endp,new WebSocketBuffers(4096),System.currentTimeMillis(),1000,"");
-
-                _results.add("onSwitchProtocol");
-                _results.add(connection);
-                clientWS.onOpen(connection);
-                return connection;
-            }
-
-            /* ------------------------------------------------------------ */
-            private void waitFor(int results)
-            {
-                try
-                {
-                    int c=10;
-                    while(_results.size()<results && c-->0)
-                        Thread.sleep(10);
-                }
-                catch(InterruptedException e)
-                {
-                    e.printStackTrace();
-                }
-            }
-        };
-
-        httpExchange.setURL("http://localhost:"+_port+"/");
-        httpExchange.setMethod(HttpMethods.GET);
-
-        httpExchange.addRequestHeader("Upgrade","WebSocket");
-        httpExchange.addRequestHeader("Connection","Upgrade");
-
-        _httpClient.send(httpExchange);
-        int status = httpExchange.waitForDone();
-        assertEquals(HttpExchange.STATUS_COMPLETED, status);
-
-        assertEquals("serverWS.onConnect", _results.poll(1,TimeUnit.SECONDS));
-        TestWebSocket serverWS = (TestWebSocket)_results.poll(1,TimeUnit.SECONDS);
-
-        assertEquals(new Integer(101), _results.poll(1,TimeUnit.SECONDS));
-
-        assertEquals("onSwitchProtocol", _results.poll(1,TimeUnit.SECONDS));
-        WebSocketConnectionD00 client_conn=(WebSocketConnectionD00)_results.poll(1,TimeUnit.SECONDS);
-
-        assertEquals("clientWS.onConnect", _results.poll(1,TimeUnit.SECONDS));
-        assertEquals(client_conn, _results.poll(1,TimeUnit.SECONDS));
-
-        client_conn.sendMessage("hello world");
-
-        assertEquals("serverWS.onMessage", _results.poll(1,TimeUnit.SECONDS));
-        assertEquals("hello world", _results.poll(1,TimeUnit.SECONDS));
-
-        serverWS.sendMessage("buongiorno");
-
-        assertEquals("clientWS.onMessage", _results.poll(1,TimeUnit.SECONDS));
-        assertEquals("buongiorno", _results.poll(1,TimeUnit.SECONDS));
-
-    }
-
-    /* ------------------------------------------------------------ */
-    protected void newServer() throws Exception
-    {
-        _server=new Server();
-        _server.setGracefulShutdown(500);
-        _connector=new SelectChannelConnector();
-
-        _connector.setPort(0);
-        _server.setConnectors(new Connector[] { _connector });
-    }
-
-    /* ------------------------------------------------------------ */
-    protected void startServer() throws Exception
-    {
-        newServer();
-        _handler= new WebSocketHandler()
-        {
-            /* ------------------------------------------------------------ */
-            public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol)
-            {
-                _websocket = new TestWebSocket();
-                return _websocket;
-            }
-        };
-        _handler.getWebSocketFactory().setMinVersion(-1);
-
-        _server.setHandler(_handler);
-        _server.start();
-        _port=_connector.getLocalPort();
-    }
-
-    /* ------------------------------------------------------------ */
-    private void stopServer() throws Exception
-    {
-        _server.stop();
-        _server.join();
-    }
-
-    /* ------------------------------------------------------------ */
-    class TestWebSocket implements WebSocket.OnTextMessage
-    {
-        Connection _connection;
-
-        /* ------------------------------------------------------------ */
-        public void onOpen(Connection connection)
-        {
-            _connection=connection;
-            _webSockets.add(this);
-            _results.add("serverWS.onConnect");
-            _results.add(this);
-        }
-
-        /* ------------------------------------------------------------ */
-        public void onMessage(final String data)
-        {
-            _results.add("serverWS.onMessage");
-            _results.add(data);
-        }
-        
-        /* ------------------------------------------------------------ */
-        public void onClose(int code, String message)
-        {
-            _results.add("onClose");
-            _webSockets.remove(this);
-        }
-
-        /* ------------------------------------------------------------ */
-        public void sendMessage(String msg) throws IOException
-        {
-            _connection.sendMessage(msg);
-        }
-    }
-}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/WebdavListenerTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/WebdavListenerTest.java
deleted file mode 100644
index 7da64a5..0000000
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/WebdavListenerTest.java
+++ /dev/null
@@ -1,144 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client;
-
-import java.io.File;
-
-import org.eclipse.jetty.client.security.Realm;
-import org.eclipse.jetty.client.security.SimpleRealmResolver;
-import org.eclipse.jetty.http.HttpMethods;
-import org.eclipse.jetty.server.Connector;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
-import org.eclipse.jetty.toolchain.test.PathAssert;
-import org.junit.After;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
-
-/**
- * Functional testing for HttpExchange.
- */
-public class WebdavListenerTest
-{
-    protected String _scheme = "http://";
-    protected Server _server;
-    protected int _port;
-    protected HttpClient _httpClient;
-    protected Connector _connector;
-
-    private String _username = "janb";
-    private String _password = "xxxxx";
-
-    private String _singleFileURL;
-    private String _dirFileURL;
-    private String _dirURL;
-    
-    @Before
-    public void setUp() throws Exception
-    {
-        _singleFileURL = "https://dav.codehaus.org/user/" + _username + "/foo.txt";
-        _dirURL = "https://dav.codehaus.org/user/" + _username + "/ttt/";
-        _dirFileURL = _dirURL+"foo.txt";
-        _scheme="https://";
- 
-        _httpClient=new HttpClient();
-        _httpClient.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);
-        //_httpClient.setMaxConnectionsPerAddress(4);
-
-        _httpClient.setRealmResolver( new SimpleRealmResolver (
-                new Realm(){
-                    public String getId()
-                    {
-                        return _username + "'s webspace";  //To change body of implemented methods use File | Settings | File Templates.
-                    }
-
-                    public String getPrincipal()
-                    {
-                        return _username;  //To change body of implemented methods use File | Settings | File Templates.
-                    }
-
-                    public String getCredentials()
-                    {
-                        return _password;  //To change body of implemented methods use File | Settings | File Templates.
-                    }
-                }
-        ));
-
-        _httpClient.registerListener( "org.eclipse.jetty.client.webdav.WebdavListener");
-        _httpClient.start();
-    }
-    
-    
-    @After
-    public void tearDown () throws Exception
-    {
-        _httpClient.stop();
-    }
-   
-
-    @Test
-    @Ignore("Only works with real WebDAV server")
-    public void testPUTandDELETEwithSSL() throws Exception
-    {
-    	File file = MavenTestingUtils.getTestResourceFile("foo.txt");
-    	PathAssert.assertFileExists("WebDAV test file", file);
-    	
-        //PUT a FILE
-        ContentExchange singleFileExchange = new ContentExchange();
-        singleFileExchange.setURL(_singleFileURL);
-        singleFileExchange.setMethod( HttpMethods.PUT );
-        singleFileExchange.setFileForUpload(file);
-        singleFileExchange.setRequestHeader( "Content-Type", "application/octet-stream");
-        singleFileExchange.setRequestHeader("Content-Length", String.valueOf( file.length() ));
-        _httpClient.send(singleFileExchange);
-        singleFileExchange.waitForDone();
-        
-        String result = singleFileExchange.getResponseContent();
-        Assert.assertEquals(201, singleFileExchange.getResponseStatus());    
-       
-        //PUT a FILE in a directory hierarchy
-        ContentExchange dirFileExchange = new ContentExchange();
-        dirFileExchange.setURL(_dirFileURL);
-        dirFileExchange.setMethod( HttpMethods.PUT );
-        dirFileExchange.setFileForUpload(file);
-        dirFileExchange.setRequestHeader( "Content-Type", "application/octet-stream");
-        dirFileExchange.setRequestHeader("Content-Length", String.valueOf( file.length() ));
-        _httpClient.send(dirFileExchange);
-        dirFileExchange.waitForDone();
-        result = dirFileExchange.getResponseContent();        
-        Assert.assertEquals(201, singleFileExchange.getResponseStatus());
-       
-     
-        //DELETE the single file
-        HttpExchange del = new HttpExchange();
-        del.setURL(_singleFileURL);
-        del.setMethod(HttpMethods.DELETE);
-        _httpClient.send(del);
-        del.waitForDone();
-          
-        //DELETE the whole dir
-        del.setURL(_dirURL);
-        del.setMethod(HttpMethods.DELETE);  
-        del.setRequestHeader("Depth", "infinity");
-        _httpClient.send(del);
-        del.waitForDone();
-    }
-}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/api/Usage.java b/jetty-client/src/test/java/org/eclipse/jetty/client/api/Usage.java
new file mode 100644
index 0000000..b83d321
--- /dev/null
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/api/Usage.java
@@ -0,0 +1,347 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client.api;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.HttpCookie;
+import java.net.URI;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Paths;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.util.BasicAuthentication;
+import org.eclipse.jetty.client.util.DeferredContentProvider;
+import org.eclipse.jetty.client.util.FutureResponseListener;
+import org.eclipse.jetty.client.util.InputStreamContentProvider;
+import org.eclipse.jetty.client.util.InputStreamResponseListener;
+import org.eclipse.jetty.client.util.OutputStreamContentProvider;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.util.FuturePromise;
+import org.junit.Assert;
+import org.junit.Ignore;
+import org.junit.Test;
+
+ at Ignore
+public class Usage
+{
+    @Test
+    public void testGETBlocking_ShortAPI() throws Exception
+    {
+        HttpClient client = new HttpClient();
+        client.start();
+
+        // Block to get the response
+        ContentResponse response = client.GET("http://localhost:8080/foo");
+
+        // Verify response status code
+        Assert.assertEquals(200, response.getStatus());
+
+        // Access headers
+        response.getHeaders().get("Content-Length");
+    }
+
+    @Test
+    public void testGETBlocking() throws Exception
+    {
+        HttpClient client = new HttpClient();
+        client.start();
+
+        // Address must be provided, it's the only thing non defaultable
+        Request request = client.newRequest("localhost", 8080)
+                .scheme("https")
+                .method(HttpMethod.GET)
+                .path("/uri")
+                .version(HttpVersion.HTTP_1_1)
+                .param("a", "b")
+                .header("X-Header", "Y-value")
+                .agent("Jetty HTTP Client")
+                .idleTimeout(5000, TimeUnit.MILLISECONDS)
+                .timeout(20, TimeUnit.SECONDS);
+
+        ContentResponse response = request.send();
+        Assert.assertEquals(200, response.getStatus());
+    }
+
+    @Test
+    public void testGETAsync() throws Exception
+    {
+        HttpClient client = new HttpClient();
+        client.start();
+
+        final AtomicReference<Response> responseRef = new AtomicReference<>();
+        final CountDownLatch latch = new CountDownLatch(1);
+
+        client.newRequest("localhost", 8080)
+                // Send asynchronously
+                .send(new Response.CompleteListener()
+        {
+            @Override
+            public void onComplete(Result result)
+            {
+                if (result.isSucceeded())
+                {
+                    responseRef.set(result.getResponse());
+                    latch.countDown();
+                }
+            }
+        });
+
+        Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+        Response response = responseRef.get();
+        Assert.assertNotNull(response);
+        Assert.assertEquals(200, response.getStatus());
+    }
+
+    @Test
+    public void testPOSTWithParams_ShortAPI() throws Exception
+    {
+        HttpClient client = new HttpClient();
+        client.start();
+
+        // One liner to POST
+        client.POST("http://localhost:8080").param("a", "\u20AC").send();
+    }
+
+    @Test
+    public void testRequestListener() throws Exception
+    {
+        HttpClient client = new HttpClient();
+        client.start();
+
+        Response response = client.newRequest("localhost", 8080)
+                // Add a request listener
+                .listener(new Request.Listener.Adapter()
+                {
+                    @Override
+                    public void onSuccess(Request request)
+                    {
+                    }
+                }).send();
+        Assert.assertEquals(200, response.getStatus());
+    }
+
+    @Test
+    public void testRequestWithExplicitConnectionControl() throws Exception
+    {
+        HttpClient client = new HttpClient();
+        client.start();
+
+        // Create an explicit connection, and use try-with-resources to manage it
+        FuturePromise<Connection> futureConnection = new FuturePromise<>();
+        client.getDestination("http", "localhost", 8080).newConnection(futureConnection);
+        try (Connection connection = futureConnection.get(5, TimeUnit.SECONDS))
+        {
+            Request request = client.newRequest("localhost", 8080);
+
+            // Asynchronous send but using FutureResponseListener
+            FutureResponseListener listener = new FutureResponseListener(request);
+            connection.send(request, listener);
+            // Wait for the response on the listener
+            Response response = listener.get(5, TimeUnit.SECONDS);
+
+            Assert.assertNotNull(response);
+            Assert.assertEquals(200, response.getStatus());
+        }
+    }
+
+    @Test
+    public void testFileUpload() throws Exception
+    {
+        HttpClient client = new HttpClient();
+        client.start();
+
+        // One liner to upload files
+        Response response = client.newRequest("localhost", 8080).file(Paths.get("file_to_upload.txt")).send();
+
+        Assert.assertEquals(200, response.getStatus());
+    }
+
+    @Test
+    public void testCookie() throws Exception
+    {
+        HttpClient client = new HttpClient();
+        client.start();
+
+        // Set a cookie to be sent in requests that match the cookie's domain
+        client.getCookieStore().add(URI.create("http://host:8080/path"), new HttpCookie("name", "value"));
+
+        // Send a request for the cookie's domain
+        Response response = client.newRequest("host", 8080).send();
+
+        Assert.assertEquals(200, response.getStatus());
+    }
+
+    @Test
+    public void testBasicAuthentication() throws Exception
+    {
+        HttpClient client = new HttpClient();
+        client.start();
+
+        URI uri = URI.create("http://localhost:8080/secure");
+
+        // Setup Basic authentication credentials for TestRealm
+        client.getAuthenticationStore().addAuthentication(new BasicAuthentication(uri, "TestRealm", "username", "password"));
+
+        // One liner to send the request
+        ContentResponse response = client.newRequest(uri).timeout(5, TimeUnit.SECONDS).send();
+
+        Assert.assertEquals(200, response.getStatus());
+    }
+
+    @Test
+    public void testFollowRedirects() throws Exception
+    {
+        HttpClient client = new HttpClient();
+        client.start();
+
+        // Do not follow redirects by default
+        client.setFollowRedirects(false);
+
+        ContentResponse response = client.newRequest("localhost", 8080)
+                // Follow redirects for this request only
+                .followRedirects(true)
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        Assert.assertEquals(200, response.getStatus());
+    }
+
+    @Test
+    public void testResponseInputStream() throws Exception
+    {
+        HttpClient client = new HttpClient();
+        client.start();
+
+        InputStreamResponseListener listener = new InputStreamResponseListener();
+        // Send asynchronously with the InputStreamResponseListener
+        client.newRequest("localhost", 8080).send(listener);
+
+        // Call to the listener's get() blocks until the headers arrived
+        Response response = listener.get(5, TimeUnit.SECONDS);
+
+        // Now check the response information that arrived to decide whether to read the content
+        if (response.getStatus() == 200)
+        {
+            byte[] buffer = new byte[256];
+            try (InputStream input = listener.getInputStream())
+            {
+                while (true)
+                {
+                    int read = input.read(buffer);
+                    if (read < 0)
+                        break;
+                    // Do something with the bytes just read
+                }
+            }
+        }
+        else
+        {
+            response.abort(new Exception());
+        }
+    }
+
+    @Test
+    public void testRequestInputStream() throws Exception
+    {
+        HttpClient client = new HttpClient();
+        client.start();
+
+        InputStream input = new ByteArrayInputStream("content".getBytes(StandardCharsets.UTF_8));
+
+        ContentResponse response = client.newRequest("localhost", 8080)
+                // Provide the content as InputStream
+                .content(new InputStreamContentProvider(input))
+                .send();
+
+        Assert.assertEquals(200, response.getStatus());
+    }
+
+    @Test
+    public void testRequestOutputStream() throws Exception
+    {
+        HttpClient client = new HttpClient();
+        client.start();
+
+        OutputStreamContentProvider content = new OutputStreamContentProvider();
+        try (OutputStream output = content.getOutputStream())
+        {
+            client.newRequest("localhost", 8080)
+                    .content(content)
+                    .send(new Response.CompleteListener()
+                    {
+                        @Override
+                        public void onComplete(Result result)
+                        {
+                            Assert.assertEquals(200, result.getResponse().getStatus());
+                        }
+                    });
+
+            output.write(new byte[1024]);
+            output.write(new byte[512]);
+            output.write(new byte[256]);
+            output.write(new byte[128]);
+        }
+    }
+
+    @Test
+    public void testProxyUsage() throws Exception
+    {
+        // In proxies, we receive the headers but not the content, so we must be able to send the request with
+        // a lazy content provider that does not block request.send(...)
+
+        HttpClient client = new HttpClient();
+        client.start();
+
+        final AtomicBoolean sendContent = new AtomicBoolean(true);
+        DeferredContentProvider async = new DeferredContentProvider(ByteBuffer.wrap(new byte[]{0, 1, 2}));
+        client.newRequest("localhost", 8080)
+                .content(async)
+                .send(new Response.Listener.Adapter()
+                {
+                    @Override
+                    public void onBegin(Response response)
+                    {
+                        if (response.getStatus() == 404)
+                            sendContent.set(false);
+                    }
+                });
+
+        Thread.sleep(100);
+
+        if (sendContent.get())
+            async.offer(ByteBuffer.wrap(new byte[]{0}));
+
+        Thread.sleep(100);
+
+        if (sendContent.get())
+            async.offer(ByteBuffer.wrap(new byte[]{0}));
+
+        Thread.sleep(100);
+
+        async.close();
+    }
+}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/helperClasses/AbstractSslServerAndClientCreator.java b/jetty-client/src/test/java/org/eclipse/jetty/client/helperClasses/AbstractSslServerAndClientCreator.java
deleted file mode 100644
index 9171319..0000000
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/helperClasses/AbstractSslServerAndClientCreator.java
+++ /dev/null
@@ -1,60 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-
-package org.eclipse.jetty.client.helperClasses;
-
-import org.eclipse.jetty.server.Connector;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.ssl.SslSocketConnector;
-import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.util.ssl.SslContextFactory;
-
-
-/* ------------------------------------------------------------ */
-/**
- */
-public abstract class AbstractSslServerAndClientCreator implements ServerAndClientCreator
-{
-    private static final Logger LOG = Log.getLogger(AbstractSslServerAndClientCreator.class);
-
-    /* ------------------------------------------------------------ */
-    public Server createServer() throws Exception
-    {
-        Server server = new Server();
-        // SslSelectChannelConnector connector = new SslSelectChannelConnector();
-        SslSocketConnector connector = new SslSocketConnector();
-
-        String keystore = MavenTestingUtils.getTestResourceFile("keystore").getAbsolutePath();
-
-        connector.setPort(0);
-        SslContextFactory cf = connector.getSslContextFactory();
-        cf.setKeyStorePath(keystore);
-        cf.setKeyStorePassword("storepwd");
-        cf.setKeyManagerPassword("keypwd");
-
-        server.setConnectors(new Connector[]{ connector });
-        server.setHandler(new GenericServerHandler());
-        server.start();
-        return server;
-    }
-
-
-}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/helperClasses/AsyncSslServerAndClientCreator.java b/jetty-client/src/test/java/org/eclipse/jetty/client/helperClasses/AsyncSslServerAndClientCreator.java
deleted file mode 100644
index 29c55ca..0000000
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/helperClasses/AsyncSslServerAndClientCreator.java
+++ /dev/null
@@ -1,42 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client.helperClasses;
-
-import org.eclipse.jetty.client.HttpClient;
-import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
-
-public class AsyncSslServerAndClientCreator extends AbstractSslServerAndClientCreator implements ServerAndClientCreator
-{
-
-    /* ------------------------------------------------------------ */
-    public HttpClient createClient(long idleTimeout, long timeout, int connectTimeout) throws Exception
-    {
-        HttpClient httpClient = new HttpClient();
-        httpClient.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);
-        httpClient.setMaxConnectionsPerAddress(2);
-
-        String keystore = MavenTestingUtils.getTestResourceFile("keystore").getAbsolutePath();
-        httpClient.getSslContextFactory().setKeyStorePath(keystore);
-        httpClient.getSslContextFactory().setKeyStorePassword("storepwd");
-        httpClient.getSslContextFactory().setKeyManagerPassword("keypwd");
-        httpClient.start();
-        return httpClient;
-    }
-
-}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/helperClasses/ExternalKeyStoreAsyncSslServerAndClientCreator.java b/jetty-client/src/test/java/org/eclipse/jetty/client/helperClasses/ExternalKeyStoreAsyncSslServerAndClientCreator.java
deleted file mode 100644
index 2dfe35d..0000000
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/helperClasses/ExternalKeyStoreAsyncSslServerAndClientCreator.java
+++ /dev/null
@@ -1,45 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client.helperClasses;
-
-import java.io.FileInputStream;
-
-import org.eclipse.jetty.client.HttpClient;
-import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
-
-public class ExternalKeyStoreAsyncSslServerAndClientCreator extends AbstractSslServerAndClientCreator implements ServerAndClientCreator
-{
-
-    public HttpClient createClient(long idleTimeout, long timeout, int connectTimeout) throws Exception
-    {
-        HttpClient httpClient = new HttpClient();
-        httpClient.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);
-        httpClient.setMaxConnectionsPerAddress(2);
-
-        String keystore = MavenTestingUtils.getTestResourceFile("keystore").getAbsolutePath();
-
-        httpClient.setKeyStoreInputStream(new FileInputStream(keystore));
-        httpClient.setKeyStorePassword("storepwd");
-        httpClient.setKeyManagerPassword("keypwd");
-        httpClient.start();
-        return httpClient;
-    }
-
-
-}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/helperClasses/GenericServerHandler.java b/jetty-client/src/test/java/org/eclipse/jetty/client/helperClasses/GenericServerHandler.java
deleted file mode 100644
index c2e0d6d..0000000
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/helperClasses/GenericServerHandler.java
+++ /dev/null
@@ -1,109 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client.helperClasses;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jetty.http.HttpHeaders;
-import org.eclipse.jetty.io.EofException;
-import org.eclipse.jetty.server.Request;
-import org.eclipse.jetty.server.handler.AbstractHandler;
-import org.eclipse.jetty.util.IO;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-
-/**
- * Generic Server Handler used for various client tests.
- */
-public class GenericServerHandler extends AbstractHandler
-{
-    private static final Logger LOG = Log.getLogger(GenericServerHandler.class);
-
-    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
-    {
-        int i = 0;
-        try
-        {
-            baseRequest.setHandled(true);
-            response.setStatus(200);
-
-            if (request.getServerName().equals("jetty.eclipse.org"))
-            {
-                response.getOutputStream().println("Proxy request: " + request.getRequestURL());
-                response.getOutputStream().println(request.getHeader(HttpHeaders.PROXY_AUTHORIZATION));
-            }
-            else if (request.getMethod().equalsIgnoreCase("GET"))
-            {
-                response.getOutputStream().println("<hello>");
-                for (; i < 100; i++)
-                {
-                    response.getOutputStream().println("  <world>" + i + "</world");
-                    if (i % 20 == 0)
-                        response.getOutputStream().flush();
-                }
-                response.getOutputStream().println("</hello>");
-            }
-            else if (request.getMethod().equalsIgnoreCase("OPTIONS"))
-            {
-                if ("*".equals(target))
-                {
-                    response.setContentLength(0);
-                    response.setHeader("Allow","GET,HEAD,POST,PUT,DELETE,MOVE,OPTIONS,TRACE");
-                }
-            }
-            else if (request.getMethod().equalsIgnoreCase("SLEEP"))
-            {
-                Thread.sleep(10000);
-            }
-            else
-            {
-                response.setContentType(request.getContentType());
-                int size = request.getContentLength();
-                ByteArrayOutputStream bout = new ByteArrayOutputStream(size > 0?size:32768);
-                IO.copy(request.getInputStream(),bout);
-                response.getOutputStream().write(bout.toByteArray());
-            }
-        }
-        catch (InterruptedException e)
-        {
-            LOG.debug(e);
-        }
-        catch (EofException e)
-        {
-            LOG.info(e.toString());
-            LOG.debug(e);
-            throw e;
-        }
-        catch (IOException e)
-        {
-            LOG.warn(e);
-            throw e;
-        }
-        catch (Throwable e)
-        {
-            LOG.warn(e);
-            throw new ServletException(e);
-        }
-    }
-}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/helperClasses/HttpServerAndClientCreator.java b/jetty-client/src/test/java/org/eclipse/jetty/client/helperClasses/HttpServerAndClientCreator.java
deleted file mode 100644
index 6536665..0000000
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/helperClasses/HttpServerAndClientCreator.java
+++ /dev/null
@@ -1,54 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client.helperClasses;
-
-import org.eclipse.jetty.client.HttpClient;
-import org.eclipse.jetty.server.Connector;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
-
-public class HttpServerAndClientCreator implements ServerAndClientCreator
-{
-    public HttpClient createClient(long idleTimeout, long timeout, int connectTimeout) throws Exception
-    {
-        HttpClient httpClient = new HttpClient();
-        httpClient.setIdleTimeout(idleTimeout);
-        httpClient.setTimeout(timeout);
-        httpClient.setConnectTimeout(connectTimeout);
-        httpClient.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);
-        httpClient.setMaxConnectionsPerAddress(2);
-        httpClient.start();
-        return httpClient;
-    }
-    
-    public Server createServer() throws Exception
-    {
-        Server _server = new Server();
-        _server.setGracefulShutdown(500);
-        Connector _connector = new SelectChannelConnector();
-
-        _connector.setMaxIdleTime(3000000);
-
-        _connector.setPort(0);
-        _server.setConnectors(new Connector[]{ _connector });
-        _server.setHandler(new GenericServerHandler());
-        _server.start();
-        return _server;
-    }
-}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/helperClasses/ServerAndClientCreator.java b/jetty-client/src/test/java/org/eclipse/jetty/client/helperClasses/ServerAndClientCreator.java
deleted file mode 100644
index ee06128..0000000
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/helperClasses/ServerAndClientCreator.java
+++ /dev/null
@@ -1,29 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client.helperClasses;
-
-import org.eclipse.jetty.client.HttpClient;
-import org.eclipse.jetty.server.Server;
-
-public interface ServerAndClientCreator
-{
-    Server createServer() throws Exception;
-    
-    HttpClient createClient(long idleTimeout, long timeout, int connectTimeout) throws Exception;
-}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/helperClasses/SslServerAndClientCreator.java b/jetty-client/src/test/java/org/eclipse/jetty/client/helperClasses/SslServerAndClientCreator.java
deleted file mode 100644
index b271a29..0000000
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/helperClasses/SslServerAndClientCreator.java
+++ /dev/null
@@ -1,37 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client.helperClasses;
-
-import org.eclipse.jetty.client.HttpClient;
-
-public class SslServerAndClientCreator extends AbstractSslServerAndClientCreator implements ServerAndClientCreator
-{
-
-    public HttpClient createClient(long idleTimeout, long timeout, int connectTimeout) throws Exception
-    {
-        HttpClient httpClient = new HttpClient();
-        httpClient.setIdleTimeout(idleTimeout);
-        httpClient.setTimeout(timeout);
-        httpClient.setConnectTimeout(connectTimeout);
-        httpClient.setConnectorType(HttpClient.CONNECTOR_SOCKET);
-        httpClient.setMaxConnectionsPerAddress(2);
-        httpClient.start();
-        return httpClient;
-    }
-}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpDestinationOverHTTPTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpDestinationOverHTTPTest.java
new file mode 100644
index 0000000..9b292e9
--- /dev/null
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpDestinationOverHTTPTest.java
@@ -0,0 +1,275 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client.http;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.client.AbstractHttpClientServerTest;
+import org.eclipse.jetty.client.ConnectionPool;
+import org.eclipse.jetty.client.EmptyServerHandler;
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.Origin;
+import org.eclipse.jetty.client.api.Connection;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Destination;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.client.api.Result;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpHeaderValue;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.hamcrest.Matchers;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class HttpDestinationOverHTTPTest extends AbstractHttpClientServerTest
+{
+    public HttpDestinationOverHTTPTest(SslContextFactory sslContextFactory)
+    {
+        super(sslContextFactory);
+    }
+
+    @Before
+    public void init() throws Exception
+    {
+        start(new EmptyServerHandler());
+    }
+
+    @Test
+    public void test_FirstAcquire_WithEmptyQueue() throws Exception
+    {
+        HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", connector.getLocalPort()));
+        Connection connection = destination.acquire();
+        if (connection == null)
+        {
+            // There are no queued requests, so the newly created connection will be idle
+            connection = destination.getConnectionPool().getIdleConnections().poll(5, TimeUnit.SECONDS);
+        }
+        Assert.assertNotNull(connection);
+    }
+
+    @Test
+    public void test_SecondAcquire_AfterFirstAcquire_WithEmptyQueue_ReturnsSameConnection() throws Exception
+    {
+        HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", connector.getLocalPort()));
+        Connection connection1 = destination.acquire();
+        if (connection1 == null)
+        {
+            // There are no queued requests, so the newly created connection will be idle
+            long start = System.nanoTime();
+            while (connection1 == null && TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - start) < 5)
+            {
+                TimeUnit.MILLISECONDS.sleep(50);
+                connection1 = destination.getConnectionPool().getIdleConnections().peek();
+            }
+            Assert.assertNotNull(connection1);
+
+            Connection connection2 = destination.acquire();
+            Assert.assertSame(connection1, connection2);
+        }
+    }
+
+    @Test
+    public void test_SecondAcquire_ConcurrentWithFirstAcquire_WithEmptyQueue_CreatesTwoConnections() throws Exception
+    {
+        final CountDownLatch idleLatch = new CountDownLatch(1);
+        final CountDownLatch latch = new CountDownLatch(1);
+        HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", connector.getLocalPort()))
+        {
+            @Override
+            protected ConnectionPool newConnectionPool(HttpClient client)
+            {
+                return new ConnectionPool(this, client.getMaxConnectionsPerDestination(), this)
+                {
+                    @Override
+                    protected void idleCreated(Connection connection)
+                    {
+                        try
+                        {
+                            idleLatch.countDown();
+                            latch.await(5, TimeUnit.SECONDS);
+                            super.idleCreated(connection);
+                        }
+                        catch (InterruptedException x)
+                        {
+                            x.printStackTrace();
+                        }
+                    }
+                };
+            }
+        };
+        Connection connection1 = destination.acquire();
+
+        // Make sure we entered idleCreated().
+        Assert.assertTrue(idleLatch.await(5, TimeUnit.SECONDS));
+
+        // There are no available existing connections, so acquire()
+        // returns null because we delayed idleCreated() above
+        Assert.assertNull(connection1);
+
+        // Second attempt also returns null because we delayed idleCreated() above.
+        Connection connection2 = destination.acquire();
+        Assert.assertNull(connection2);
+
+        latch.countDown();
+
+        // There must be 2 idle connections
+        Connection connection = destination.getConnectionPool().getIdleConnections().poll(5, TimeUnit.SECONDS);
+        Assert.assertNotNull(connection);
+        connection = destination.getConnectionPool().getIdleConnections().poll(5, TimeUnit.SECONDS);
+        Assert.assertNotNull(connection);
+    }
+
+    @Test
+    public void test_Acquire_Process_Release_Acquire_ReturnsSameConnection() throws Exception
+    {
+        HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", connector.getLocalPort()));
+        HttpConnectionOverHTTP connection1 = destination.acquire();
+
+        long start = System.nanoTime();
+        while (connection1 == null && TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - start) < 5)
+        {
+            TimeUnit.MILLISECONDS.sleep(50);
+            connection1 = (HttpConnectionOverHTTP)destination.getConnectionPool().getIdleConnections().peek();
+        }
+        Assert.assertNotNull(connection1);
+
+        // Acquire the connection to make it active
+        Assert.assertSame(connection1, destination.acquire());
+
+        destination.process(connection1, false);
+        destination.release(connection1);
+
+        Connection connection2 = destination.acquire();
+        Assert.assertSame(connection1, connection2);
+    }
+
+    @Test
+    public void test_IdleConnection_IdleTimeout() throws Exception
+    {
+        long idleTimeout = 1000;
+        client.setIdleTimeout(idleTimeout);
+
+        HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", connector.getLocalPort()));
+        Connection connection1 = destination.acquire();
+        if (connection1 == null)
+        {
+            // There are no queued requests, so the newly created connection will be idle
+            long start = System.nanoTime();
+            while (connection1 == null && TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - start) < 5)
+            {
+                TimeUnit.MILLISECONDS.sleep(50);
+                connection1 = destination.getConnectionPool().getIdleConnections().peek();
+            }
+            Assert.assertNotNull(connection1);
+
+            TimeUnit.MILLISECONDS.sleep(2 * idleTimeout);
+
+            connection1 = destination.getConnectionPool().getIdleConnections().poll();
+            Assert.assertNull(connection1);
+        }
+    }
+
+    @Test
+    public void test_Request_Failed_If_MaxRequestsQueuedPerDestination_Exceeded() throws Exception
+    {
+        int maxQueued = 1;
+        client.setMaxRequestsQueuedPerDestination(maxQueued);
+        client.setMaxConnectionsPerDestination(1);
+
+        // Make one request to open the connection and be sure everything is setup properly
+        ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .send();
+        Assert.assertEquals(200, response.getStatus());
+
+        // Send another request that is sent immediately
+        final CountDownLatch successLatch = new CountDownLatch(1);
+        final CountDownLatch failureLatch = new CountDownLatch(1);
+        client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .path("/one")
+                .onRequestQueued(new Request.QueuedListener()
+                {
+                    @Override
+                    public void onQueued(Request request)
+                    {
+                        // This request exceeds the maximum queued, should fail
+                        client.newRequest("localhost", connector.getLocalPort())
+                                .scheme(scheme)
+                                .path("/two")
+                                .send(new Response.CompleteListener()
+                                {
+                                    @Override
+                                    public void onComplete(Result result)
+                                    {
+                                        Assert.assertTrue(result.isFailed());
+                                        Assert.assertThat(result.getRequestFailure(), Matchers.instanceOf(RejectedExecutionException.class));
+                                        failureLatch.countDown();
+                                    }
+                                });
+                    }
+                })
+                .send(new Response.CompleteListener()
+                {
+                    @Override
+                    public void onComplete(Result result)
+                    {
+                        if (result.isSucceeded())
+                            successLatch.countDown();
+                    }
+                });
+
+        Assert.assertTrue(failureLatch.await(5, TimeUnit.SECONDS));
+        Assert.assertTrue(successLatch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testDestinationIsRemoved() throws Exception
+    {
+        String host = "localhost";
+        int port = connector.getLocalPort();
+        Destination destinationBefore = client.getDestination(scheme, host, port);
+
+        ContentResponse response = client.newRequest(host, port)
+                .scheme(scheme)
+                .header(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString())
+                .send();
+
+        Assert.assertEquals(200, response.getStatus());
+
+        Destination destinationAfter = client.getDestination(scheme, host, port);
+        Assert.assertSame(destinationBefore, destinationAfter);
+
+        client.setRemoveIdleDestinations(true);
+
+        response = client.newRequest(host, port)
+                .scheme(scheme)
+                .header(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString())
+                .send();
+
+        Assert.assertEquals(200, response.getStatus());
+
+        destinationAfter = client.getDestination(scheme, host, port);
+        Assert.assertNotSame(destinationBefore, destinationAfter);
+    }
+}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpReceiverOverHTTPTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpReceiverOverHTTPTest.java
new file mode 100644
index 0000000..6894841
--- /dev/null
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpReceiverOverHTTPTest.java
@@ -0,0 +1,250 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client.http;
+
+import java.io.EOFException;
+import java.nio.charset.StandardCharsets;
+import java.util.Collections;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.HttpExchange;
+import org.eclipse.jetty.client.HttpRequest;
+import org.eclipse.jetty.client.HttpResponseException;
+import org.eclipse.jetty.client.Origin;
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.client.util.FutureResponseListener;
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.io.ByteArrayEndPoint;
+import org.eclipse.jetty.toolchain.test.TestTracker;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class HttpReceiverOverHTTPTest
+{
+    @Rule
+    public final TestTracker tracker = new TestTracker();
+
+    private HttpClient client;
+    private HttpDestinationOverHTTP destination;
+    private ByteArrayEndPoint endPoint;
+    private HttpConnectionOverHTTP connection;
+
+    @Before
+    public void init() throws Exception
+    {
+        client = new HttpClient();
+        client.start();
+        destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", 8080));
+        endPoint = new ByteArrayEndPoint();
+        connection = new HttpConnectionOverHTTP(endPoint, destination);
+    }
+
+    @After
+    public void destroy() throws Exception
+    {
+        client.stop();
+    }
+
+    protected HttpExchange newExchange()
+    {
+        HttpRequest request = (HttpRequest)client.newRequest("http://localhost");
+        FutureResponseListener listener = new FutureResponseListener(request);
+        HttpExchange exchange = new HttpExchange(destination, request, Collections.<Response.ResponseListener>singletonList(listener));
+        boolean associated = connection.getHttpChannel().associate(exchange);
+        Assert.assertTrue(associated);
+        exchange.requestComplete(null);
+        exchange.terminateRequest();
+        return exchange;
+    }
+
+    @Test
+    public void test_Receive_NoResponseContent() throws Exception
+    {
+        endPoint.setInput("" +
+                "HTTP/1.1 200 OK\r\n" +
+                "Content-length: 0\r\n" +
+                "\r\n");
+        HttpExchange exchange = newExchange();
+        FutureResponseListener listener = (FutureResponseListener)exchange.getResponseListeners().get(0);
+        connection.getHttpChannel().receive();
+
+        Response response = listener.get(5, TimeUnit.SECONDS);
+        Assert.assertNotNull(response);
+        Assert.assertEquals(200, response.getStatus());
+        Assert.assertEquals("OK", response.getReason());
+        Assert.assertSame(HttpVersion.HTTP_1_1, response.getVersion());
+        HttpFields headers = response.getHeaders();
+        Assert.assertNotNull(headers);
+        Assert.assertEquals(1, headers.size());
+        Assert.assertEquals("0", headers.get(HttpHeader.CONTENT_LENGTH));
+    }
+
+    @Test
+    public void test_Receive_ResponseContent() throws Exception
+    {
+        String content = "0123456789ABCDEF";
+        endPoint.setInput("" +
+                "HTTP/1.1 200 OK\r\n" +
+                "Content-length: " + content.length() + "\r\n" +
+                "\r\n" +
+                content);
+        HttpExchange exchange = newExchange();
+        FutureResponseListener listener = (FutureResponseListener)exchange.getResponseListeners().get(0);
+        connection.getHttpChannel().receive();
+
+        Response response = listener.get(5, TimeUnit.SECONDS);
+        Assert.assertNotNull(response);
+        Assert.assertEquals(200, response.getStatus());
+        Assert.assertEquals("OK", response.getReason());
+        Assert.assertSame(HttpVersion.HTTP_1_1, response.getVersion());
+        HttpFields headers = response.getHeaders();
+        Assert.assertNotNull(headers);
+        Assert.assertEquals(1, headers.size());
+        Assert.assertEquals(String.valueOf(content.length()), headers.get(HttpHeader.CONTENT_LENGTH));
+        String received = listener.getContentAsString(StandardCharsets.UTF_8);
+        Assert.assertEquals(content, received);
+    }
+
+    @Test
+    public void test_Receive_ResponseContent_EarlyEOF() throws Exception
+    {
+        String content1 = "0123456789";
+        String content2 = "ABCDEF";
+        endPoint.setInput("" +
+                "HTTP/1.1 200 OK\r\n" +
+                "Content-length: " + (content1.length() + content2.length()) + "\r\n" +
+                "\r\n" +
+                content1);
+        HttpExchange exchange = newExchange();
+        FutureResponseListener listener = (FutureResponseListener)exchange.getResponseListeners().get(0);
+        connection.getHttpChannel().receive();
+        endPoint.setInputEOF();
+        connection.getHttpChannel().receive();
+
+        try
+        {
+            listener.get(5, TimeUnit.SECONDS);
+            Assert.fail();
+        }
+        catch (ExecutionException e)
+        {
+            Assert.assertTrue(e.getCause() instanceof EOFException);
+        }
+    }
+
+    @Test
+    public void test_Receive_ResponseContent_IdleTimeout() throws Exception
+    {
+        endPoint.setInput("" +
+                "HTTP/1.1 200 OK\r\n" +
+                "Content-length: 1\r\n" +
+                "\r\n");
+        HttpExchange exchange = newExchange();
+        FutureResponseListener listener = (FutureResponseListener)exchange.getResponseListeners().get(0);
+        connection.getHttpChannel().receive();
+        // Simulate an idle timeout
+        connection.onReadTimeout();
+
+        try
+        {
+            listener.get(5, TimeUnit.SECONDS);
+            Assert.fail();
+        }
+        catch (ExecutionException e)
+        {
+            Assert.assertTrue(e.getCause() instanceof TimeoutException);
+        }
+    }
+
+    @Test
+    public void test_Receive_BadResponse() throws Exception
+    {
+        endPoint.setInput("" +
+                "HTTP/1.1 200 OK\r\n" +
+                "Content-length: A\r\n" +
+                "\r\n");
+        HttpExchange exchange = newExchange();
+        FutureResponseListener listener = (FutureResponseListener)exchange.getResponseListeners().get(0);
+        connection.getHttpChannel().receive();
+
+        try
+        {
+            listener.get(5, TimeUnit.SECONDS);
+            Assert.fail();
+        }
+        catch (ExecutionException e)
+        {
+            Assert.assertTrue(e.getCause() instanceof HttpResponseException);
+        }
+    }
+
+    @Test
+    public void test_FillInterested_RacingWith_BufferRelease() throws Exception
+    {
+        connection = new HttpConnectionOverHTTP(endPoint, destination)
+        {
+            @Override
+            protected HttpChannelOverHTTP newHttpChannel()
+            {
+                return new HttpChannelOverHTTP(this)
+                {
+                    @Override
+                    protected HttpReceiverOverHTTP newHttpReceiver()
+                    {
+                        return new HttpReceiverOverHTTP(this)
+                        {
+                            @Override
+                            protected void fillInterested()
+                            {
+                                // Verify that the buffer has been released
+                                // before fillInterested() is called.
+                                Assert.assertNull(getResponseBuffer());
+                                // Fill the endpoint so receive is called again.
+                                endPoint.setInput("X");
+                                super.fillInterested();
+                            }
+                        };
+                    }
+                };
+            }
+        };
+        
+        // Partial response to trigger the call to fillInterested().
+        endPoint.setInput("" +
+                "HTTP/1.1 200 OK\r\n" +
+                "Content-Length: 1\r\n" +
+                "\r\n");
+
+        HttpExchange exchange = newExchange();
+        FutureResponseListener listener = (FutureResponseListener)exchange.getResponseListeners().get(0);
+        connection.getHttpChannel().receive();
+
+        Response response = listener.get(5, TimeUnit.SECONDS);
+        Assert.assertNotNull(response);
+        Assert.assertEquals(200, response.getStatus());
+    }
+}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpSenderOverHTTPTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpSenderOverHTTPTest.java
new file mode 100644
index 0000000..fdbcc43
--- /dev/null
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpSenderOverHTTPTest.java
@@ -0,0 +1,302 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client.http;
+
+import java.net.URI;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.Locale;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.Origin;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.client.api.Result;
+import org.eclipse.jetty.client.util.ByteBufferContentProvider;
+import org.eclipse.jetty.io.ByteArrayEndPoint;
+import org.eclipse.jetty.toolchain.test.TestTracker;
+import org.eclipse.jetty.toolchain.test.annotation.Slow;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class HttpSenderOverHTTPTest
+{
+    @Rule
+    public final TestTracker tracker = new TestTracker();
+
+    private HttpClient client;
+
+    @Before
+    public void init() throws Exception
+    {
+        client = new HttpClient();
+        client.start();
+    }
+
+    @After
+    public void destroy() throws Exception
+    {
+        client.stop();
+    }
+
+    @Test
+    public void test_Send_NoRequestContent() throws Exception
+    {
+        ByteArrayEndPoint endPoint = new ByteArrayEndPoint();
+        HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", 8080));
+        HttpConnectionOverHTTP connection = new HttpConnectionOverHTTP(endPoint, destination);
+        Request request = client.newRequest(URI.create("http://localhost/"));
+        final CountDownLatch headersLatch = new CountDownLatch(1);
+        final CountDownLatch successLatch = new CountDownLatch(1);
+        request.listener(new Request.Listener.Adapter()
+        {
+            @Override
+            public void onHeaders(Request request)
+            {
+                headersLatch.countDown();
+            }
+
+            @Override
+            public void onSuccess(Request request)
+            {
+                successLatch.countDown();
+            }
+        });
+        connection.send(request, null);
+
+        String requestString = endPoint.takeOutputString();
+        Assert.assertTrue(requestString.startsWith("GET "));
+        Assert.assertTrue(requestString.endsWith("\r\n\r\n"));
+        Assert.assertTrue(headersLatch.await(5, TimeUnit.SECONDS));
+        Assert.assertTrue(successLatch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Slow
+    @Test
+    public void test_Send_NoRequestContent_IncompleteFlush() throws Exception
+    {
+        ByteArrayEndPoint endPoint = new ByteArrayEndPoint("", 16);
+        HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", 8080));
+        HttpConnectionOverHTTP connection = new HttpConnectionOverHTTP(endPoint, destination);
+        Request request = client.newRequest(URI.create("http://localhost/"));
+        connection.send(request, null);
+
+        // This take will free space in the buffer and allow for the write to complete
+        StringBuilder builder = new StringBuilder(endPoint.takeOutputString());
+
+        // Wait for the write to complete
+        TimeUnit.SECONDS.sleep(1);
+
+        String chunk = endPoint.takeOutputString();
+        while (chunk.length() > 0)
+        {
+            builder.append(chunk);
+            chunk = endPoint.takeOutputString();
+        }
+
+        String requestString = builder.toString();
+        Assert.assertTrue(requestString.startsWith("GET "));
+        Assert.assertTrue(requestString.endsWith("\r\n\r\n"));
+    }
+
+    @Test
+    public void test_Send_NoRequestContent_Exception() throws Exception
+    {
+        ByteArrayEndPoint endPoint = new ByteArrayEndPoint();
+        // Shutdown output to trigger the exception on write
+        endPoint.shutdownOutput();
+        HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", 8080));
+        HttpConnectionOverHTTP connection = new HttpConnectionOverHTTP(endPoint, destination);
+        Request request = client.newRequest(URI.create("http://localhost/"));
+        final CountDownLatch failureLatch = new CountDownLatch(2);
+        request.listener(new Request.Listener.Adapter()
+        {
+            @Override
+            public void onFailure(Request request, Throwable x)
+            {
+                failureLatch.countDown();
+            }
+        });
+        connection.send(request, new Response.Listener.Adapter()
+        {
+            @Override
+            public void onComplete(Result result)
+            {
+                Assert.assertTrue(result.isFailed());
+                failureLatch.countDown();
+            }
+        });
+
+        Assert.assertTrue(failureLatch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void test_Send_NoRequestContent_IncompleteFlush_Exception() throws Exception
+    {
+        ByteArrayEndPoint endPoint = new ByteArrayEndPoint("", 16);
+        HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", 8080));
+        HttpConnectionOverHTTP connection = new HttpConnectionOverHTTP(endPoint, destination);
+        Request request = client.newRequest(URI.create("http://localhost/"));
+        final CountDownLatch failureLatch = new CountDownLatch(2);
+        request.listener(new Request.Listener.Adapter()
+        {
+            @Override
+            public void onFailure(Request request, Throwable x)
+            {
+                failureLatch.countDown();
+            }
+        });
+        connection.send(request, new Response.Listener.Adapter()
+        {
+            @Override
+            public void onComplete(Result result)
+            {
+                Assert.assertTrue(result.isFailed());
+                failureLatch.countDown();
+            }
+        });
+
+        // Shutdown output to trigger the exception on write
+        endPoint.shutdownOutput();
+        // This take will free space in the buffer and allow for the write to complete
+        // although it will fail because we shut down the output
+        endPoint.takeOutputString();
+
+        Assert.assertTrue(failureLatch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void test_Send_SmallRequestContent_InOneBuffer() throws Exception
+    {
+        ByteArrayEndPoint endPoint = new ByteArrayEndPoint();
+        HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", 8080));
+        HttpConnectionOverHTTP connection = new HttpConnectionOverHTTP(endPoint, destination);
+        Request request = client.newRequest(URI.create("http://localhost/"));
+        String content = "abcdef";
+        request.content(new ByteBufferContentProvider(ByteBuffer.wrap(content.getBytes(StandardCharsets.UTF_8))));
+        final CountDownLatch headersLatch = new CountDownLatch(1);
+        final CountDownLatch successLatch = new CountDownLatch(1);
+        request.listener(new Request.Listener.Adapter()
+        {
+            @Override
+            public void onHeaders(Request request)
+            {
+                headersLatch.countDown();
+            }
+
+            @Override
+            public void onSuccess(Request request)
+            {
+                successLatch.countDown();
+            }
+        });
+        connection.send(request, null);
+
+        String requestString = endPoint.takeOutputString();
+        Assert.assertTrue(requestString.startsWith("GET "));
+        Assert.assertTrue(requestString.endsWith("\r\n\r\n" + content));
+        Assert.assertTrue(headersLatch.await(5, TimeUnit.SECONDS));
+        Assert.assertTrue(successLatch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void test_Send_SmallRequestContent_InTwoBuffers() throws Exception
+    {
+        ByteArrayEndPoint endPoint = new ByteArrayEndPoint();
+        HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", 8080));
+        HttpConnectionOverHTTP connection = new HttpConnectionOverHTTP(endPoint, destination);
+        Request request = client.newRequest(URI.create("http://localhost/"));
+        String content1 = "0123456789";
+        String content2 = "abcdef";
+        request.content(new ByteBufferContentProvider(ByteBuffer.wrap(content1.getBytes(StandardCharsets.UTF_8)), ByteBuffer.wrap(content2.getBytes(StandardCharsets.UTF_8))));
+        final CountDownLatch headersLatch = new CountDownLatch(1);
+        final CountDownLatch successLatch = new CountDownLatch(1);
+        request.listener(new Request.Listener.Adapter()
+        {
+            @Override
+            public void onHeaders(Request request)
+            {
+                headersLatch.countDown();
+            }
+
+            @Override
+            public void onSuccess(Request request)
+            {
+                successLatch.countDown();
+            }
+        });
+        connection.send(request, null);
+
+        String requestString = endPoint.takeOutputString();
+        Assert.assertTrue(requestString.startsWith("GET "));
+        Assert.assertTrue(requestString.endsWith("\r\n\r\n" + content1 + content2));
+        Assert.assertTrue(headersLatch.await(5, TimeUnit.SECONDS));
+        Assert.assertTrue(successLatch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void test_Send_SmallRequestContent_Chunked_InTwoChunks() throws Exception
+    {
+        ByteArrayEndPoint endPoint = new ByteArrayEndPoint();
+        HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", 8080));
+        HttpConnectionOverHTTP connection = new HttpConnectionOverHTTP(endPoint, destination);
+        Request request = client.newRequest(URI.create("http://localhost/"));
+        String content1 = "0123456789";
+        String content2 = "ABCDEF";
+        request.content(new ByteBufferContentProvider(ByteBuffer.wrap(content1.getBytes(StandardCharsets.UTF_8)), ByteBuffer.wrap(content2.getBytes(StandardCharsets.UTF_8)))
+        {
+            @Override
+            public long getLength()
+            {
+                return -1;
+            }
+        });
+        final CountDownLatch headersLatch = new CountDownLatch(1);
+        final CountDownLatch successLatch = new CountDownLatch(1);
+        request.listener(new Request.Listener.Adapter()
+        {
+            @Override
+            public void onHeaders(Request request)
+            {
+                headersLatch.countDown();
+            }
+
+            @Override
+            public void onSuccess(Request request)
+            {
+                successLatch.countDown();
+            }
+        });
+        connection.send(request, null);
+
+        String requestString = endPoint.takeOutputString();
+        Assert.assertTrue(requestString.startsWith("GET "));
+        String content = Integer.toHexString(content1.length()).toUpperCase(Locale.ENGLISH) + "\r\n" + content1 + "\r\n";
+        content += Integer.toHexString(content2.length()).toUpperCase(Locale.ENGLISH) + "\r\n" + content2 + "\r\n";
+        content += "0\r\n\r\n";
+        Assert.assertTrue(requestString.endsWith("\r\n\r\n" + content));
+        Assert.assertTrue(headersLatch.await(5, TimeUnit.SECONDS));
+        Assert.assertTrue(successLatch.await(5, TimeUnit.SECONDS));
+    }
+}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/security/SecurityResolverTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/security/SecurityResolverTest.java
deleted file mode 100644
index 2144898..0000000
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/security/SecurityResolverTest.java
+++ /dev/null
@@ -1,51 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.client.security;
-
-import org.junit.Test;
-
-public class SecurityResolverTest
-{
-    @Test
-    public void testNothing()
-    {
-    }
-
-    /* TODO
-
-    public void testCredentialParsing() throws Exception
-    {
-        SecurityListener resolver = new SecurityListener();
-        Buffer value = new ByteArrayBuffer("basic a=b".getBytes());
-
-        assertEquals( "basic", resolver.scrapeAuthenticationType( value.toString() ) );
-        assertEquals( 1, resolver.scrapeAuthenticationDetails( value.toString() ).size() );
-
-        value = new ByteArrayBuffer("digest a=boo, c=\"doo\" , egg=foo".getBytes());
-
-        assertEquals( "digest", resolver.scrapeAuthenticationType( value.toString() ) );
-        Map<String,String> testMap = resolver.scrapeAuthenticationDetails( value.toString() );
-        assertEquals( 3, testMap.size() );
-        assertEquals( "boo", testMap.get("a") );
-        assertEquals( "doo", testMap.get("c") );
-        assertEquals( "foo", testMap.get("egg") );
-    }
-
-    */
-}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesClientTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesClientTest.java
new file mode 100644
index 0000000..23db943
--- /dev/null
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesClientTest.java
@@ -0,0 +1,366 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client.ssl;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.SocketTimeoutException;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLServerSocket;
+import javax.net.ssl.SSLSocket;
+
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.util.FutureResponseListener;
+import org.eclipse.jetty.http.HttpScheme;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class SslBytesClientTest extends SslBytesTest
+{
+    private ExecutorService threadPool;
+    private HttpClient client;
+    private SslContextFactory sslContextFactory;
+    private SSLServerSocket acceptor;
+    private SimpleProxy proxy;
+
+    @Before
+    public void init() throws Exception
+    {
+        threadPool = Executors.newCachedThreadPool();
+
+        client = new HttpClient(new SslContextFactory(true));
+        client.setMaxConnectionsPerDestination(1);
+        File keyStore = MavenTestingUtils.getTestResourceFile("keystore.jks");
+        sslContextFactory = client.getSslContextFactory();
+        sslContextFactory.setKeyStorePath(keyStore.getAbsolutePath());
+        sslContextFactory.setKeyStorePassword("storepwd");
+        client.start();
+
+        SSLContext sslContext = sslContextFactory.getSslContext();
+        acceptor = (SSLServerSocket)sslContext.getServerSocketFactory().createServerSocket(0);
+
+        int serverPort = acceptor.getLocalPort();
+
+        proxy = new SimpleProxy(threadPool, "localhost", serverPort);
+        proxy.start();
+        logger.info(":{} <==> :{}", proxy.getPort(), serverPort);
+    }
+
+    @After
+    public void destroy() throws Exception
+    {
+        if (acceptor != null)
+            acceptor.close();
+        if (proxy != null)
+            proxy.stop();
+        if (client != null)
+            client.stop();
+        if (threadPool != null)
+            threadPool.shutdownNow();
+    }
+
+    @Test
+    public void testHandshake() throws Exception
+    {
+        Request request = client.newRequest("localhost", proxy.getPort());
+        FutureResponseListener listener = new FutureResponseListener(request);
+        request.scheme(HttpScheme.HTTPS.asString()).send(listener);
+
+        Assert.assertTrue(proxy.awaitClient(5, TimeUnit.SECONDS));
+
+        final SSLSocket server = (SSLSocket)acceptor.accept();
+        server.setUseClientMode(false);
+
+        Future<Object> handshake = threadPool.submit(new Callable<Object>()
+        {
+            public Object call() throws Exception
+            {
+                server.startHandshake();
+                return null;
+            }
+        });
+
+        // Client Hello
+        TLSRecord record = proxy.readFromClient();
+        Assert.assertEquals(TLSRecord.Type.HANDSHAKE, record.getType());
+        proxy.flushToServer(record);
+
+        // Server Hello + Certificate + Server Done
+        record = proxy.readFromServer();
+        Assert.assertEquals(TLSRecord.Type.HANDSHAKE, record.getType());
+        proxy.flushToClient(record);
+
+        // Client Key Exchange
+        record = proxy.readFromClient();
+        Assert.assertEquals(TLSRecord.Type.HANDSHAKE, record.getType());
+        proxy.flushToServer(record);
+
+        // Change Cipher Spec
+        record = proxy.readFromClient();
+        Assert.assertEquals(TLSRecord.Type.CHANGE_CIPHER_SPEC, record.getType());
+        proxy.flushToServer(record);
+
+        // Client Done
+        record = proxy.readFromClient();
+        Assert.assertEquals(TLSRecord.Type.HANDSHAKE, record.getType());
+        proxy.flushToServer(record);
+
+        // Change Cipher Spec
+        record = proxy.readFromServer();
+        Assert.assertEquals(TLSRecord.Type.CHANGE_CIPHER_SPEC, record.getType());
+        proxy.flushToClient(record);
+
+        // Server Done
+        record = proxy.readFromServer();
+        Assert.assertEquals(TLSRecord.Type.HANDSHAKE, record.getType());
+        proxy.flushToClient(record);
+
+        Assert.assertNull(handshake.get(5, TimeUnit.SECONDS));
+
+        SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow();
+        // Read request
+        BufferedReader reader = new BufferedReader(new InputStreamReader(server.getInputStream(), StandardCharsets.UTF_8));
+        String line = reader.readLine();
+        Assert.assertTrue(line.startsWith("GET"));
+        while (line.length() > 0)
+            line = reader.readLine();
+
+        // Write response
+        OutputStream output = server.getOutputStream();
+        output.write(("HTTP/1.1 200 OK\r\n" +
+                "Content-Length: 0\r\n" +
+                "\r\n").getBytes(StandardCharsets.UTF_8));
+        output.flush();
+        Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS));
+
+        ContentResponse response = listener.get(5, TimeUnit.SECONDS);
+        Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
+
+        server.close();
+    }
+
+    @Test
+    public void testServerRenegotiation() throws Exception
+    {
+        Request request = client.newRequest("localhost", proxy.getPort());
+        FutureResponseListener listener = new FutureResponseListener(request);
+        request.scheme(HttpScheme.HTTPS.asString()).send(listener);
+
+        Assert.assertTrue(proxy.awaitClient(5, TimeUnit.SECONDS));
+
+        final SSLSocket server = (SSLSocket)acceptor.accept();
+        server.setUseClientMode(false);
+
+        Future<Object> handshake = threadPool.submit(new Callable<Object>()
+        {
+            public Object call() throws Exception
+            {
+                server.startHandshake();
+                return null;
+            }
+        });
+
+        SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow();
+        Assert.assertNull(handshake.get(5, TimeUnit.SECONDS));
+
+        // Read request
+        InputStream serverInput = server.getInputStream();
+        BufferedReader reader = new BufferedReader(new InputStreamReader(serverInput, StandardCharsets.UTF_8));
+        String line = reader.readLine();
+        Assert.assertTrue(line.startsWith("GET"));
+        while (line.length() > 0)
+            line = reader.readLine();
+
+        OutputStream serverOutput = server.getOutputStream();
+        byte[] data1 = new byte[1024];
+        Arrays.fill(data1, (byte)'X');
+        String content1 = new String(data1, StandardCharsets.UTF_8);
+        byte[] data2 = new byte[1024];
+        Arrays.fill(data2, (byte)'Y');
+        final String content2 = new String(data2, StandardCharsets.UTF_8);
+        // Write first part of the response
+        serverOutput.write(("HTTP/1.1 200 OK\r\n" +
+                "Content-Type: text/plain\r\n" +
+                "Content-Length: " + (content1.length() + content2.length()) + "\r\n" +
+                "\r\n" +
+                content1).getBytes(StandardCharsets.UTF_8));
+        serverOutput.flush();
+        Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS));
+
+        // Renegotiate
+        Future<Object> renegotiation = threadPool.submit(new Callable<Object>()
+        {
+            public Object call() throws Exception
+            {
+                server.startHandshake();
+                return null;
+            }
+        });
+
+        // Renegotiation Handshake
+        TLSRecord record = proxy.readFromServer();
+        Assert.assertEquals(TLSRecord.Type.HANDSHAKE, record.getType());
+        proxy.flushToClient(record);
+
+        // Renegotiation Handshake
+        record = proxy.readFromClient();
+        Assert.assertEquals(TLSRecord.Type.HANDSHAKE, record.getType());
+        proxy.flushToServer(record);
+
+        // Trigger a read to have the server write the final renegotiation steps
+        server.setSoTimeout(100);
+        try
+        {
+            serverInput.read();
+            Assert.fail();
+        }
+        catch (SocketTimeoutException x)
+        {
+            // Expected
+        }
+
+        // Renegotiation Handshake
+        record = proxy.readFromServer();
+        Assert.assertEquals(TLSRecord.Type.HANDSHAKE, record.getType());
+        proxy.flushToClient(record);
+
+        // Renegotiation Change Cipher
+        record = proxy.readFromServer();
+        Assert.assertEquals(TLSRecord.Type.CHANGE_CIPHER_SPEC, record.getType());
+        proxy.flushToClient(record);
+
+        // Renegotiation Handshake
+        record = proxy.readFromServer();
+        Assert.assertEquals(TLSRecord.Type.HANDSHAKE, record.getType());
+        proxy.flushToClient(record);
+
+        // Renegotiation Change Cipher
+        record = proxy.readFromClient();
+        Assert.assertEquals(TLSRecord.Type.CHANGE_CIPHER_SPEC, record.getType());
+        proxy.flushToServer(record);
+
+        // Renegotiation Handshake
+        record = proxy.readFromClient();
+        Assert.assertEquals(TLSRecord.Type.HANDSHAKE, record.getType());
+        proxy.flushToServer(record);
+
+        Assert.assertNull(renegotiation.get(5, TimeUnit.SECONDS));
+
+        // Complete the response
+        automaticProxyFlow = proxy.startAutomaticFlow();
+        serverOutput.write(data2);
+        serverOutput.flush();
+        Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS));
+
+        ContentResponse response = listener.get(5, TimeUnit.SECONDS);
+        Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
+        Assert.assertEquals(data1.length + data2.length, response.getContent().length);
+
+        server.close();
+    }
+
+    @Test
+    public void testServerRenegotiationWhenRenegotiationIsForbidden() throws Exception
+    {
+        sslContextFactory.setRenegotiationAllowed(false);
+
+        Request request = client.newRequest("localhost", proxy.getPort());
+        FutureResponseListener listener = new FutureResponseListener(request);
+        request.scheme(HttpScheme.HTTPS.asString()).send(listener);
+
+        Assert.assertTrue(proxy.awaitClient(5, TimeUnit.SECONDS));
+
+        final SSLSocket server = (SSLSocket)acceptor.accept();
+        server.setUseClientMode(false);
+
+        Future<Object> handshake = threadPool.submit(new Callable<Object>()
+        {
+            public Object call() throws Exception
+            {
+                server.startHandshake();
+                return null;
+            }
+        });
+
+        SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow();
+        Assert.assertNull(handshake.get(5, TimeUnit.SECONDS));
+
+        // Read request
+        InputStream serverInput = server.getInputStream();
+        BufferedReader reader = new BufferedReader(new InputStreamReader(serverInput, StandardCharsets.UTF_8));
+        String line = reader.readLine();
+        Assert.assertTrue(line.startsWith("GET"));
+        while (line.length() > 0)
+            line = reader.readLine();
+
+        OutputStream serverOutput = server.getOutputStream();
+        byte[] data1 = new byte[1024];
+        Arrays.fill(data1, (byte)'X');
+        String content1 = new String(data1, StandardCharsets.UTF_8);
+        byte[] data2 = new byte[1024];
+        Arrays.fill(data2, (byte)'Y');
+        final String content2 = new String(data2, StandardCharsets.UTF_8);
+        // Write first part of the response
+        serverOutput.write(("HTTP/1.1 200 OK\r\n" +
+                "Content-Type: text/plain\r\n" +
+                "Content-Length: " + (content1.length() + content2.length()) + "\r\n" +
+                "\r\n" +
+                content1).getBytes(StandardCharsets.UTF_8));
+        serverOutput.flush();
+        Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS));
+
+        // Renegotiate
+        threadPool.submit(new Callable<Object>()
+        {
+            public Object call() throws Exception
+            {
+                server.startHandshake();
+                return null;
+            }
+        });
+
+        // Renegotiation Handshake
+        TLSRecord record = proxy.readFromServer();
+        Assert.assertEquals(TLSRecord.Type.HANDSHAKE, record.getType());
+        proxy.flushToClient(record);
+
+        record = proxy.readFromClient();
+        Assert.assertNull(record);
+
+        server.close();
+    }
+}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesServerTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesServerTest.java
new file mode 100644
index 0000000..ca00519
--- /dev/null
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesServerTest.java
@@ -0,0 +1,1978 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client.ssl;
+
+import static org.hamcrest.Matchers.nullValue;
+
+import java.io.BufferedReader;
+import java.io.EOFException;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.SocketTimeoutException;
+import java.nio.ByteBuffer;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.SocketChannel;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLSocket;
+import javax.servlet.ServletException;
+import javax.servlet.ServletInputStream;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.client.ssl.SslBytesTest.TLSRecord.Type;
+import org.eclipse.jetty.http.HttpParser;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.SelectChannelEndPoint;
+import org.eclipse.jetty.io.SelectorManager.ManagedSelector;
+import org.eclipse.jetty.io.ssl.SslConnection;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.HttpConnection;
+import org.eclipse.jetty.server.HttpConnectionFactory;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.SecureRequestCustomizer;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.SslConnectionFactory;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.eclipse.jetty.toolchain.test.OS;
+import org.eclipse.jetty.util.component.Dumpable;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.hamcrest.Matchers;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class SslBytesServerTest extends SslBytesTest
+{
+    private final AtomicInteger sslFills = new AtomicInteger();
+    private final AtomicInteger sslFlushes = new AtomicInteger();
+    private final AtomicInteger httpParses = new AtomicInteger();
+    private final AtomicReference<EndPoint> serverEndPoint = new AtomicReference<>();
+    private final int idleTimeout = 2000;
+    private ExecutorService threadPool;
+    private Server server;
+    private SslContextFactory sslContextFactory;
+    private int serverPort;
+    private SSLContext sslContext;
+    private SimpleProxy proxy;
+    private Runnable idleHook;
+
+    @Before
+    public void init() throws Exception
+    {
+        threadPool = Executors.newCachedThreadPool();
+        server = new Server();
+
+        File keyStore = MavenTestingUtils.getTestResourceFile("keystore.jks");
+        sslContextFactory = new SslContextFactory();
+        sslContextFactory.setKeyStorePath(keyStore.getAbsolutePath());
+        sslContextFactory.setKeyStorePassword("storepwd");
+
+        HttpConnectionFactory httpFactory = new HttpConnectionFactory()
+        {
+            @Override
+            public Connection newConnection(Connector connector, EndPoint endPoint)
+            {
+                return configure(new HttpConnection(getHttpConfiguration(), connector, endPoint)
+                {
+                    @Override
+                    protected HttpParser newHttpParser()
+                    {
+                        return new HttpParser(newRequestHandler(), getHttpConfiguration().getRequestHeaderSize())
+                        {
+                            @Override
+                            public boolean parseNext(ByteBuffer buffer)
+                            {
+                                httpParses.incrementAndGet();
+                                return super.parseNext(buffer);
+                            }
+                        };
+                    }
+
+                    @Override
+                    protected boolean onReadTimeout()
+                    {
+                        final Runnable idleHook = SslBytesServerTest.this.idleHook;
+                        if (idleHook != null)
+                            idleHook.run();
+                        return super.onReadTimeout();
+                    }
+                }, connector, endPoint);
+            }
+        };
+        httpFactory.getHttpConfiguration().addCustomizer(new SecureRequestCustomizer());
+        SslConnectionFactory sslFactory = new SslConnectionFactory(sslContextFactory, httpFactory.getProtocol())
+        {
+            @Override
+            protected SslConnection newSslConnection(Connector connector, EndPoint endPoint, SSLEngine engine)
+            {
+                return new SslConnection(connector.getByteBufferPool(), connector.getExecutor(), endPoint, engine)
+                {
+                    @Override
+                    protected DecryptedEndPoint newDecryptedEndPoint()
+                    {
+                        return new DecryptedEndPoint()
+                        {
+                            @Override
+                            public int fill(ByteBuffer buffer) throws IOException
+                            {
+                                sslFills.incrementAndGet();
+                                return super.fill(buffer);
+                            }
+
+                            @Override
+                            public boolean flush(ByteBuffer... appOuts) throws IOException
+                            {
+                                sslFlushes.incrementAndGet();
+                                return super.flush(appOuts);
+                            }
+                        };
+                    }
+                };
+            }
+        };
+
+        ServerConnector connector = new ServerConnector(server, null,null,null,1,1,sslFactory, httpFactory)
+        {
+            @Override
+            protected SelectChannelEndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey key) throws IOException
+            {
+                SelectChannelEndPoint endp = super.newEndPoint(channel,selectSet,key);
+                serverEndPoint.set(endp);
+                return endp;
+            }
+        };
+        connector.setIdleTimeout(idleTimeout);
+        connector.setPort(0);
+
+        server.addConnector(connector);
+        server.setHandler(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException
+            {
+                try
+                {
+                    request.setHandled(true);
+                    String contentLength = request.getHeader("Content-Length");
+                    if (contentLength != null)
+                    {
+                        int length = Integer.parseInt(contentLength);
+                        ServletInputStream input = httpRequest.getInputStream();
+                        ServletOutputStream output = httpResponse.getOutputStream();
+                        byte[] buffer = new byte[32 * 1024];
+                        while (length > 0)
+                        {
+                            int read = input.read(buffer);
+                            if (read < 0)
+                                throw new EOFException();
+                            length -= read;
+                            if (target.startsWith("/echo"))
+                                output.write(buffer, 0, read);
+                        }
+                    }
+                }
+                catch (IOException x)
+                {
+                    if (!(target.endsWith("suppress_exception")))
+                        throw x;
+                }
+            }
+        });
+        server.start();
+        serverPort = connector.getLocalPort();
+
+        sslContext = sslContextFactory.getSslContext();
+
+        proxy = new SimpleProxy(threadPool, "localhost", serverPort);
+        proxy.start();
+        logger.info("proxy:{} <==> server:{}", proxy.getPort(), serverPort);
+    }
+
+    @After
+    public void destroy() throws Exception
+    {
+        if (proxy != null)
+            proxy.stop();
+        if (server != null)
+            server.stop();
+        if (threadPool != null)
+            threadPool.shutdownNow();
+    }
+
+    @Test(timeout=10000)
+    public void testHandshake() throws Exception
+    {
+        final SSLSocket client = newClient();
+
+        Future<Object> handshake = threadPool.submit(new Callable<Object>()
+        {
+            @Override
+            public Object call() throws Exception
+            {
+                client.startHandshake();
+                return null;
+            }
+        });
+
+        // Client Hello
+        TLSRecord record = proxy.readFromClient();
+        Assert.assertNotNull(record);
+        proxy.flushToServer(record);
+
+        // Server Hello + Certificate + Server Done
+        record = proxy.readFromServer();
+        Assert.assertNotNull(record);
+        proxy.flushToClient(record);
+
+        // Client Key Exchange
+        record = proxy.readFromClient();
+        Assert.assertNotNull(record);
+        proxy.flushToServer(record);
+
+        // Change Cipher Spec
+        record = proxy.readFromClient();
+        Assert.assertNotNull(record);
+        proxy.flushToServer(record);
+
+        // Client Done
+        record = proxy.readFromClient();
+        Assert.assertNotNull(record);
+        proxy.flushToServer(record);
+
+        // Change Cipher Spec
+        record = proxy.readFromServer();
+        Assert.assertNotNull(record);
+        proxy.flushToClient(record);
+
+        // Server Done
+        record = proxy.readFromServer();
+        Assert.assertNotNull(record);
+        proxy.flushToClient(record);
+
+        Assert.assertNull(handshake.get(5, TimeUnit.SECONDS));
+
+        // Check that we did not spin
+        TimeUnit.MILLISECONDS.sleep(500);
+        Assert.assertThat(sslFills.get(), Matchers.lessThan(20));
+        Assert.assertThat(sslFlushes.get(), Matchers.lessThan(20));
+        Assert.assertThat(httpParses.get(), Matchers.lessThan(20));
+
+        closeClient(client);
+    }
+
+    @Test
+    public void testHandshakeWithResumedSessionThenClose() throws Exception
+    {
+        // First socket will establish the SSL session
+        SSLSocket client1 = newClient();
+        SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow();
+        client1.startHandshake();
+        client1.close();
+        Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS));
+        int proxyPort = proxy.getPort();
+        proxy.stop();
+
+        proxy = new SimpleProxy(threadPool, proxyPort, "localhost", serverPort);
+        proxy.start();
+        logger.info("proxy:{} <==> server:{}", proxy.getPort(), serverPort);
+
+        final SSLSocket client2 = newClient(proxy);
+
+        Future<Object> handshake = threadPool.submit(new Callable<Object>()
+        {
+            @Override
+            public Object call() throws Exception
+            {
+                client2.startHandshake();
+                return null;
+            }
+        });
+
+        // Client Hello with SessionID
+        TLSRecord record = proxy.readFromClient();
+        Assert.assertNotNull(record);
+        proxy.flushToServer(record);
+
+        // Server Hello
+        record = proxy.readFromServer();
+        Assert.assertNotNull(record);
+        proxy.flushToClient(record);
+
+        // Change Cipher Spec
+        record = proxy.readFromServer();
+        Assert.assertNotNull(record);
+        proxy.flushToClient(record);
+
+        // Server Done
+        record = proxy.readFromServer();
+        Assert.assertNotNull(record);
+        proxy.flushToClient(record);
+
+        // Client Key Exchange
+        record = proxy.readFromClient();
+        Assert.assertNotNull(record);
+        // Client Done
+        TLSRecord doneRecord = proxy.readFromClient();
+        Assert.assertNotNull(doneRecord);
+        // Close
+        client2.close();
+        TLSRecord closeRecord = proxy.readFromClient();
+        Assert.assertNotNull(closeRecord);
+        Assert.assertEquals(TLSRecord.Type.ALERT, closeRecord.getType());
+        // Flush to server Client Key Exchange + Client Done + Close in one chunk
+        byte[] recordBytes = record.getBytes();
+        byte[] doneBytes = doneRecord.getBytes();
+        byte[] closeRecordBytes = closeRecord.getBytes();
+        byte[] chunk = new byte[recordBytes.length + doneBytes.length + closeRecordBytes.length];
+        System.arraycopy(recordBytes, 0, chunk, 0, recordBytes.length);
+        System.arraycopy(doneBytes, 0, chunk, recordBytes.length, doneBytes.length);
+        System.arraycopy(closeRecordBytes, 0, chunk, recordBytes.length + doneBytes.length, closeRecordBytes.length);
+        proxy.flushToServer(0, chunk);
+        // Close the raw socket
+        proxy.flushToServer(null);
+
+        // Expect the server to send a FIN as well
+        record = proxy.readFromServer();
+        Assert.assertNull(record);
+
+        // Check that we did not spin
+        TimeUnit.MILLISECONDS.sleep(500);
+        Assert.assertThat(sslFills.get(), Matchers.lessThan(20));
+        Assert.assertThat(sslFlushes.get(), Matchers.lessThan(20));
+        Assert.assertThat(httpParses.get(), Matchers.lessThan(20));
+    }
+
+    @Test
+    public void testHandshakeWithSplitBoundary() throws Exception
+    {
+        final SSLSocket client = newClient();
+
+        Future<Object> handshake = threadPool.submit(new Callable<Object>()
+        {
+            @Override
+            public Object call() throws Exception
+            {
+                client.startHandshake();
+                return null;
+            }
+        });
+
+        // Client Hello
+        TLSRecord record = proxy.readFromClient();
+        byte[] bytes = record.getBytes();
+        byte[] chunk1 = new byte[2 * bytes.length / 3];
+        System.arraycopy(bytes, 0, chunk1, 0, chunk1.length);
+        byte[] chunk2 = new byte[bytes.length - chunk1.length];
+        System.arraycopy(bytes, chunk1.length, chunk2, 0, chunk2.length);
+        proxy.flushToServer(100, chunk1);
+        proxy.flushToServer(100, chunk2);
+
+        // Server Hello + Certificate + Server Done
+        record = proxy.readFromServer();
+        proxy.flushToClient(record);
+
+        // Client Key Exchange
+        record = proxy.readFromClient();
+        bytes = record.getBytes();
+        chunk1 = new byte[2 * bytes.length / 3];
+        System.arraycopy(bytes, 0, chunk1, 0, chunk1.length);
+        chunk2 = new byte[bytes.length - chunk1.length];
+        System.arraycopy(bytes, chunk1.length, chunk2, 0, chunk2.length);
+        proxy.flushToServer(100, chunk1);
+        proxy.flushToServer(100, chunk2);
+
+        // Change Cipher Spec
+        record = proxy.readFromClient();
+        bytes = record.getBytes();
+        chunk1 = new byte[2 * bytes.length / 3];
+        System.arraycopy(bytes, 0, chunk1, 0, chunk1.length);
+        chunk2 = new byte[bytes.length - chunk1.length];
+        System.arraycopy(bytes, chunk1.length, chunk2, 0, chunk2.length);
+        proxy.flushToServer(100, chunk1);
+        proxy.flushToServer(100, chunk2);
+
+        // Client Done
+        record = proxy.readFromClient();
+        bytes = record.getBytes();
+        chunk1 = new byte[2 * bytes.length / 3];
+        System.arraycopy(bytes, 0, chunk1, 0, chunk1.length);
+        chunk2 = new byte[bytes.length - chunk1.length];
+        System.arraycopy(bytes, chunk1.length, chunk2, 0, chunk2.length);
+        proxy.flushToServer(100, chunk1);
+        proxy.flushToServer(100, chunk2);
+
+        // Change Cipher Spec
+        record = proxy.readFromServer();
+        Assert.assertNotNull(record);
+        proxy.flushToClient(record);
+
+        // Server Done
+        record = proxy.readFromServer();
+        Assert.assertNotNull(record);
+        proxy.flushToClient(record);
+
+        Assert.assertNull(handshake.get(5, TimeUnit.SECONDS));
+
+        // Check that we did not spin
+        TimeUnit.MILLISECONDS.sleep(500);
+        Assert.assertThat(sslFills.get(), Matchers.lessThan(40));
+        Assert.assertThat(sslFlushes.get(), Matchers.lessThan(20));
+        Assert.assertThat(httpParses.get(), Matchers.lessThan(20));
+
+        client.close();
+
+        // Close Alert
+        record = proxy.readFromClient();
+        bytes = record.getBytes();
+        chunk1 = new byte[2 * bytes.length / 3];
+        System.arraycopy(bytes, 0, chunk1, 0, chunk1.length);
+        chunk2 = new byte[bytes.length - chunk1.length];
+        System.arraycopy(bytes, chunk1.length, chunk2, 0, chunk2.length);
+        proxy.flushToServer(100, chunk1);
+        proxy.flushToServer(100, chunk2);
+        // Socket close
+        record = proxy.readFromClient();
+        Assert.assertNull(String.valueOf(record), record);
+        proxy.flushToServer(record);
+
+        // Socket close
+        record = proxy.readFromServer();
+        if (record!=null)
+        {
+            Assert.assertEquals(record.getType(),Type.ALERT);
+            
+            // Now should be a raw close
+            record = proxy.readFromServer();
+            Assert.assertNull(String.valueOf(record), record);
+        }
+    }
+
+    @Test
+    public void testClientHelloIncompleteThenReset() throws Exception
+    {
+        final SSLSocket client = newClient();
+
+        threadPool.submit(new Callable<Object>()
+        {
+            @Override
+            public Object call() throws Exception
+            {
+                client.startHandshake();
+                return null;
+            }
+        });
+
+        // Client Hello
+        TLSRecord record = proxy.readFromClient();
+        byte[] bytes = record.getBytes();
+        byte[] chunk1 = new byte[2 * bytes.length / 3];
+        System.arraycopy(bytes, 0, chunk1, 0, chunk1.length);
+        proxy.flushToServer(100, chunk1);
+
+        proxy.sendRSTToServer();
+
+        // Wait a while to detect spinning
+        TimeUnit.MILLISECONDS.sleep(500);
+        Assert.assertThat(sslFills.get(), Matchers.lessThan(20));
+        Assert.assertThat(sslFlushes.get(), Matchers.lessThan(20));
+        Assert.assertThat(httpParses.get(), Matchers.lessThan(20));
+
+        client.close();
+    }
+
+    @Test
+    public void testClientHelloThenReset() throws Exception
+    {
+        final SSLSocket client = newClient();
+
+        threadPool.submit(new Callable<Object>()
+        {
+            @Override
+            public Object call() throws Exception
+            {
+                client.startHandshake();
+                return null;
+            }
+        });
+
+        // Client Hello
+        TLSRecord record = proxy.readFromClient();
+        Assert.assertNotNull(record);
+        proxy.flushToServer(record);
+
+        proxy.sendRSTToServer();
+
+        // Wait a while to detect spinning
+        TimeUnit.MILLISECONDS.sleep(500);
+        Assert.assertThat(sslFills.get(), Matchers.lessThan(20));
+        Assert.assertThat(sslFlushes.get(), Matchers.lessThan(20));
+        Assert.assertThat(httpParses.get(), Matchers.lessThan(20));
+
+        client.close();
+    }
+
+    @Test
+    public void testHandshakeThenReset() throws Exception
+    {
+        final SSLSocket client = newClient();
+
+        SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow();
+        client.startHandshake();
+        Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS));
+
+        proxy.sendRSTToServer();
+
+        // Wait a while to detect spinning
+        TimeUnit.MILLISECONDS.sleep(500);
+        Assert.assertThat(sslFills.get(), Matchers.lessThan(20));
+        Assert.assertThat(sslFlushes.get(), Matchers.lessThan(20));
+        Assert.assertThat(httpParses.get(), Matchers.lessThan(20));
+
+        client.close();
+    }
+
+    @Test
+    public void testRequestIncompleteThenReset() throws Exception
+    {
+        final SSLSocket client = newClient();
+
+        SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow();
+        client.startHandshake();
+        Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS));
+
+        threadPool.submit(new Callable<Object>()
+        {
+            @Override
+            public Object call() throws Exception
+            {
+                OutputStream clientOutput = client.getOutputStream();
+                clientOutput.write(("" +
+                        "GET / HTTP/1.1\r\n" +
+                        "Host: localhost\r\n" +
+                        "\r\n").getBytes(StandardCharsets.UTF_8));
+                clientOutput.flush();
+                return null;
+            }
+        });
+
+        // Application data
+        TLSRecord record = proxy.readFromClient();
+        byte[] bytes = record.getBytes();
+        byte[] chunk1 = new byte[2 * bytes.length / 3];
+        System.arraycopy(bytes, 0, chunk1, 0, chunk1.length);
+        proxy.flushToServer(100, chunk1);
+
+        proxy.sendRSTToServer();
+
+        // Wait a while to detect spinning
+        TimeUnit.MILLISECONDS.sleep(500);
+        Assert.assertThat(sslFills.get(), Matchers.lessThan(20));
+        Assert.assertThat(sslFlushes.get(), Matchers.lessThan(20));
+        Assert.assertThat(httpParses.get(), Matchers.lessThan(20));
+
+        client.close();
+    }
+
+    @Test
+    public void testRequestResponse() throws Exception
+    {
+        final SSLSocket client = newClient();
+
+        SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow();
+        client.startHandshake();
+        Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS));
+
+        Future<Object> request = threadPool.submit(new Callable<Object>()
+        {
+            @Override
+            public Object call() throws Exception
+            {
+                OutputStream clientOutput = client.getOutputStream();
+                clientOutput.write(("" +
+                        "GET / HTTP/1.1\r\n" +
+                        "Host: localhost\r\n" +
+                        "\r\n").getBytes(StandardCharsets.UTF_8));
+                clientOutput.flush();
+                return null;
+            }
+        });
+
+        // Application data
+        TLSRecord record = proxy.readFromClient();
+        proxy.flushToServer(record);
+        Assert.assertNull(request.get(5, TimeUnit.SECONDS));
+
+        // Application data
+        record = proxy.readFromServer();
+        Assert.assertEquals(TLSRecord.Type.APPLICATION, record.getType());
+        proxy.flushToClient(record);
+
+        BufferedReader reader = new BufferedReader(new InputStreamReader(client.getInputStream(), StandardCharsets.UTF_8));
+        String line = reader.readLine();
+        Assert.assertNotNull(line);
+        Assert.assertTrue(line.startsWith("HTTP/1.1 200 "));
+        while ((line = reader.readLine()) != null)
+        {
+            if (line.trim().length() == 0)
+                break;
+        }
+
+        // Check that we did not spin
+        TimeUnit.MILLISECONDS.sleep(500);
+        Assert.assertThat(sslFills.get(), Matchers.lessThan(20));
+        Assert.assertThat(sslFlushes.get(), Matchers.lessThan(20));
+        Assert.assertThat(httpParses.get(), Matchers.lessThan(20));
+
+        closeClient(client);
+    }
+
+    @Test
+    public void testHandshakeAndRequestOneByteAtATime() throws Exception
+    {
+        final SSLSocket client = newClient();
+
+        Future<Object> handshake = threadPool.submit(new Callable<Object>()
+        {
+            @Override
+            public Object call() throws Exception
+            {
+                client.startHandshake();
+                return null;
+            }
+        });
+
+        // Client Hello
+        TLSRecord record = proxy.readFromClient();
+        for (byte b : record.getBytes())
+            proxy.flushToServer(5, b);
+
+        // Server Hello + Certificate + Server Done
+        record = proxy.readFromServer();
+        proxy.flushToClient(record);
+
+        // Client Key Exchange
+        record = proxy.readFromClient();
+        for (byte b : record.getBytes())
+            proxy.flushToServer(5,b);
+
+        // Change Cipher Spec
+        record = proxy.readFromClient();
+        for (byte b : record.getBytes())
+            proxy.flushToServer(5, b);
+
+        // Client Done
+        record = proxy.readFromClient();
+        for (byte b : record.getBytes())
+            proxy.flushToServer(5, b);
+
+        // Change Cipher Spec
+        record = proxy.readFromServer();
+        proxy.flushToClient(record);
+
+        // Server Done
+        record = proxy.readFromServer();
+        proxy.flushToClient(record);
+
+        Assert.assertNull(handshake.get(1, TimeUnit.SECONDS));
+
+        Future<Object> request = threadPool.submit(new Callable<Object>()
+        {
+            @Override
+            public Object call() throws Exception
+            {
+                OutputStream clientOutput = client.getOutputStream();
+                clientOutput.write(("" +
+                        "GET / HTTP/1.1\r\n" +
+                        "Host: localhost\r\n" +
+                        "\r\n").getBytes(StandardCharsets.UTF_8));
+                clientOutput.flush();
+                return null;
+            }
+        });
+
+        // Application data
+        record = proxy.readFromClient();
+        for (byte b : record.getBytes())
+            proxy.flushToServer(5, b);
+        Assert.assertNull(request.get(1, TimeUnit.SECONDS));
+
+        // Application data
+        record = proxy.readFromServer();
+        Assert.assertEquals(TLSRecord.Type.APPLICATION, record.getType());
+        proxy.flushToClient(record);
+
+        BufferedReader reader = new BufferedReader(new InputStreamReader(client.getInputStream(), StandardCharsets.UTF_8));
+        String line = reader.readLine();
+        Assert.assertNotNull(line);
+        Assert.assertTrue(line.startsWith("HTTP/1.1 200 "));
+        while ((line = reader.readLine()) != null)
+        {
+            if (line.trim().length() == 0)
+                break;
+        }
+
+        // Check that we did not spin
+        TimeUnit.MILLISECONDS.sleep(1000);
+        Assert.assertThat(sslFills.get(), Matchers.lessThan(1000));
+        Assert.assertThat(sslFlushes.get(), Matchers.lessThan(20));
+        // An average of 958 httpParses is seen in standard Oracle JDK's
+        // An average of 1183 httpParses is seen in OpenJDK JVMs.
+        Assert.assertThat(httpParses.get(), Matchers.lessThan(500));
+
+        client.close();
+
+        // Close Alert
+        record = proxy.readFromClient();
+        for (byte b : record.getBytes())
+            proxy.flushToServer(5, b);
+        // Socket close
+        record = proxy.readFromClient();
+        Assert.assertNull(String.valueOf(record), record);
+        proxy.flushToServer(record);
+
+        // Socket close
+        record = proxy.readFromServer();
+        // Raw close or alert
+        if (record!=null)
+        {
+            Assert.assertEquals(record.getType(),Type.ALERT);
+            
+            // Now should be a raw close
+            record = proxy.readFromServer();
+            Assert.assertNull(String.valueOf(record), record);
+        }
+    }
+
+    @Test
+    public void testRequestWithCloseAlertAndShutdown() throws Exception
+    {
+        // See next test on why we only run in Linux
+        Assume.assumeTrue(OS.IS_LINUX);
+
+        final SSLSocket client = newClient();
+
+        SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow();
+        client.startHandshake();
+        Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS));
+
+        Future<Object> request = threadPool.submit(new Callable<Object>()
+        {
+            @Override
+            public Object call() throws Exception
+            {
+                OutputStream clientOutput = client.getOutputStream();
+                clientOutput.write(("" +
+                        "GET / HTTP/1.1\r\n" +
+                        "Host: localhost\r\n" +
+                        "\r\n").getBytes(StandardCharsets.UTF_8));
+                clientOutput.flush();
+                return null;
+            }
+        });
+
+        // Application data
+        TLSRecord record = proxy.readFromClient();
+        proxy.flushToServer(record);
+        Assert.assertNull(request.get(5, TimeUnit.SECONDS));
+
+        client.close();
+
+        // Close Alert
+        record = proxy.readFromClient();
+        proxy.flushToServer(record);
+        // Socket close
+        record = proxy.readFromClient();
+        Assert.assertNull(String.valueOf(record), record);
+        proxy.flushToServer(record);
+
+        // Expect response from server
+        // SSLSocket is limited and we cannot read the response, but we make sure
+        // it is application data and not a close alert
+        record = proxy.readFromServer();
+        Assert.assertNotNull(record);
+        Assert.assertEquals(TLSRecord.Type.APPLICATION, record.getType());
+        proxy.flushToClient(record);
+
+        // Socket close
+        record = proxy.readFromServer();
+        if (record!=null)
+        {
+            Assert.assertEquals(record.getType(),Type.ALERT);
+            
+            // Now should be a raw close
+            record = proxy.readFromServer();
+            Assert.assertNull(String.valueOf(record), record);
+        }
+
+        // Check that we did not spin
+        TimeUnit.MILLISECONDS.sleep(500);
+        Assert.assertThat(sslFills.get(), Matchers.lessThan(20));
+        Assert.assertThat(sslFlushes.get(), Matchers.lessThan(20));
+        Assert.assertThat(httpParses.get(), Matchers.lessThan(20));
+    }
+
+    @Test
+    public void testRequestWithCloseAlert() throws Exception
+    {
+        // Currently we are ignoring this test on anything other then linux
+        // http://tools.ietf.org/html/rfc2246#section-7.2.1
+
+        // TODO (react to this portion which seems to allow win/mac behavior)
+        // It is required that the other party respond with a close_notify alert of its own
+        // and close down the connection immediately, discarding any pending writes. It is not
+        // required for the initiator of the close to wait for the responding
+        // close_notify alert before closing the read side of the connection.
+        Assume.assumeTrue(OS.IS_LINUX);
+
+        final SSLSocket client = newClient();
+
+        SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow();
+        client.startHandshake();
+        Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS));
+
+        Future<Object> request = threadPool.submit(new Callable<Object>()
+        {
+            @Override
+            public Object call() throws Exception
+            {
+                OutputStream clientOutput = client.getOutputStream();
+                clientOutput.write(("" +
+                        "GET / HTTP/1.1\r\n" +
+                        "Host: localhost\r\n" +
+                        "\r\n").getBytes(StandardCharsets.UTF_8));
+                clientOutput.flush();
+                return null;
+            }
+        });
+
+        // Application data
+        TLSRecord record = proxy.readFromClient();
+        Assert.assertEquals(TLSRecord.Type.APPLICATION, record.getType());
+        proxy.flushToServer(record);
+        Assert.assertNull(request.get(5, TimeUnit.SECONDS));
+
+        client.close();
+
+        // Close Alert
+        record = proxy.readFromClient();
+        Assert.assertEquals(TLSRecord.Type.ALERT, record.getType());
+        proxy.flushToServer(record);
+
+        // Do not close the raw socket yet
+
+        // Expect response from server
+        // SSLSocket is limited and we cannot read the response, but we make sure
+        // it is application data and not a close alert
+        record = proxy.readFromServer();
+        Assert.assertNotNull(record);
+        Assert.assertEquals(TLSRecord.Type.APPLICATION, record.getType());
+        proxy.flushToClient(record);
+
+        // Socket close
+        record = proxy.readFromServer();
+        if (record!=null)
+        {
+            Assert.assertEquals(record.getType(),Type.ALERT);
+            
+            // Now should be a raw close
+            record = proxy.readFromServer();
+            Assert.assertNull(String.valueOf(record), record);
+        }
+
+        // Check that we did not spin
+        TimeUnit.MILLISECONDS.sleep(500);
+        Assert.assertThat(sslFills.get(), Matchers.lessThan(20));
+        Assert.assertThat(sslFlushes.get(), Matchers.lessThan(20));
+        Assert.assertThat(httpParses.get(), Matchers.lessThan(20));
+
+        // Socket close
+        record = proxy.readFromClient();
+        Assert.assertNull(String.valueOf(record), record);
+        proxy.flushToServer(record);
+    }
+
+    @Test
+    public void testRequestWithRawClose() throws Exception
+    {
+        final SSLSocket client = newClient();
+
+        SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow();
+        client.startHandshake();
+        Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS));
+
+        Future<Object> request = threadPool.submit(new Callable<Object>()
+        {
+            @Override
+            public Object call() throws Exception
+            {
+                OutputStream clientOutput = client.getOutputStream();
+                clientOutput.write(("" +
+                        "GET / HTTP/1.1\r\n" +
+                        "Host: localhost\r\n" +
+                        "\r\n").getBytes(StandardCharsets.UTF_8));
+                clientOutput.flush();
+                return null;
+            }
+        });
+
+        // Application data
+        TLSRecord record = proxy.readFromClient();
+        Assert.assertEquals(TLSRecord.Type.APPLICATION, record.getType());
+        proxy.flushToServer(record);
+        Assert.assertNull(request.get(5, TimeUnit.SECONDS));
+
+        // Application data
+        record = proxy.readFromServer();
+        Assert.assertEquals(TLSRecord.Type.APPLICATION, record.getType());
+        proxy.flushToClient(record);
+
+        // Close the raw socket, this generates a truncation attack
+        proxy.flushToServer(null);
+
+        // Expect raw close from server OR ALERT
+        record = proxy.readFromServer();
+        // TODO check that this is OK?
+        if (record!=null)
+        {
+            Assert.assertEquals(record.getType(),Type.ALERT);
+            
+            // Now should be a raw close
+            record = proxy.readFromServer();
+            Assert.assertNull(String.valueOf(record), record);
+        }
+
+        // Check that we did not spin
+        TimeUnit.MILLISECONDS.sleep(500);
+        Assert.assertThat(sslFills.get(), Matchers.lessThan(20));
+        Assert.assertThat(sslFlushes.get(), Matchers.lessThan(20));
+        Assert.assertThat(httpParses.get(), Matchers.lessThan(20));
+
+        client.close();
+    }
+
+    @Test
+    public void testRequestWithImmediateRawClose() throws Exception
+    {
+        final SSLSocket client = newClient();
+
+        SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow();
+        client.startHandshake();
+        Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS));
+
+        Future<Object> request = threadPool.submit(new Callable<Object>()
+        {
+            @Override
+            public Object call() throws Exception
+            {
+                OutputStream clientOutput = client.getOutputStream();
+                clientOutput.write(("" +
+                        "GET / HTTP/1.1\r\n" +
+                        "Host: localhost\r\n" +
+                        "\r\n").getBytes(StandardCharsets.UTF_8));
+                clientOutput.flush();
+                return null;
+            }
+        });
+
+        // Application data
+        TLSRecord record = proxy.readFromClient();
+        Assert.assertEquals(TLSRecord.Type.APPLICATION, record.getType());
+        proxy.flushToServer(record, 0);
+        // Close the raw socket, this generates a truncation attack
+        proxy.flushToServer(null);
+        Assert.assertNull(request.get(5, TimeUnit.SECONDS));
+
+        // Application data
+        record = proxy.readFromServer();
+        Assert.assertEquals(TLSRecord.Type.APPLICATION, record.getType());
+        proxy.flushToClient(record);
+
+        // Expect raw close from server
+        record = proxy.readFromServer();
+        if (record!=null)
+        {
+            Assert.assertEquals(record.getType(),Type.ALERT);
+            
+            // Now should be a raw close
+            record = proxy.readFromServer();
+            Assert.assertNull(String.valueOf(record), record);
+        }
+
+        // Check that we did not spin
+        TimeUnit.MILLISECONDS.sleep(500);
+        Assert.assertThat(sslFills.get(), Matchers.lessThan(20));
+        Assert.assertThat(sslFlushes.get(), Matchers.lessThan(20));
+        Assert.assertThat(httpParses.get(), Matchers.lessThan(20));
+
+        client.close();
+    }
+
+    @Test
+    public void testRequestWithBigContentWriteBlockedThenReset() throws Exception
+    {
+        // Don't run on Windows (buggy JVM)
+        Assume.assumeTrue(!OS.IS_WINDOWS);
+        
+        final SSLSocket client = newClient();
+
+        SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow();
+        client.startHandshake();
+        Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS));
+
+        byte[] data = new byte[128 * 1024];
+        Arrays.fill(data, (byte)'X');
+        final String content = new String(data, StandardCharsets.UTF_8);
+        Future<Object> request = threadPool.submit(new Callable<Object>()
+        {
+            @Override
+            public Object call() throws Exception
+            {
+                OutputStream clientOutput = client.getOutputStream();
+                clientOutput.write(("" +
+                        "GET /echo HTTP/1.1\r\n" +
+                        "Host: localhost\r\n" +
+                        "Content-Length: " + content.length() + "\r\n" +
+                        "\r\n" +
+                        content).getBytes(StandardCharsets.UTF_8));
+                clientOutput.flush();
+                return null;
+            }
+        });
+
+        // Nine TLSRecords will be generated for the request
+        for (int i = 0; i < 9; ++i)
+        {
+            // Application data
+            TLSRecord record = proxy.readFromClient();
+            Assert.assertEquals(TLSRecord.Type.APPLICATION, record.getType());
+            proxy.flushToServer(record, 0);
+        }
+        Assert.assertNull(request.get(5, TimeUnit.SECONDS));
+
+        // We asked the server to echo back the data we sent
+        // but we do not read it, thus causing a write interest
+        // on the server.
+        // However, we then simulate that the client resets the
+        // connection, and this will cause an exception in the
+        // server that is trying to write the data
+
+        TimeUnit.MILLISECONDS.sleep(500);
+        proxy.sendRSTToServer();
+
+        // Wait a while to detect spinning
+        TimeUnit.MILLISECONDS.sleep(500);
+        Assert.assertThat(sslFills.get(), Matchers.lessThan(40));
+        Assert.assertThat(sslFlushes.get(), Matchers.lessThan(40));
+        Assert.assertThat(httpParses.get(), Matchers.lessThan(50));
+
+        client.close();
+    }
+
+    @Test
+    public void testRequestWithBigContentReadBlockedThenReset() throws Exception
+    {
+        // Don't run on Windows (buggy JVM)
+        Assume.assumeTrue(!OS.IS_WINDOWS);
+        
+        final SSLSocket client = newClient();
+
+        SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow();
+        client.startHandshake();
+        Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS));
+
+        byte[] data = new byte[128 * 1024];
+        Arrays.fill(data, (byte)'X');
+        final String content = new String(data, StandardCharsets.UTF_8);
+        Future<Object> request = threadPool.submit(new Callable<Object>()
+        {
+            @Override
+            public Object call() throws Exception
+            {
+                OutputStream clientOutput = client.getOutputStream();
+                clientOutput.write(("" +
+                        "GET /echo_suppress_exception HTTP/1.1\r\n" +
+                        "Host: localhost\r\n" +
+                        "Content-Length: " + content.length() + "\r\n" +
+                        "\r\n" +
+                        content).getBytes(StandardCharsets.UTF_8));
+                clientOutput.flush();
+                return null;
+            }
+        });
+
+        // Nine TLSRecords will be generated for the request,
+        // but we write only 5 of them, so the server goes in read blocked state
+        for (int i = 0; i < 5; ++i)
+        {
+            // Application data
+            TLSRecord record = proxy.readFromClient();
+            Assert.assertEquals(TLSRecord.Type.APPLICATION, record.getType());
+            proxy.flushToServer(record, 0);
+        }
+        Assert.assertNull(request.get(5, TimeUnit.SECONDS));
+
+        // The server should be read blocked, and we send a RST
+        TimeUnit.MILLISECONDS.sleep(500);
+        proxy.sendRSTToServer();
+
+        // Wait a while to detect spinning
+        TimeUnit.MILLISECONDS.sleep(500);
+        Assert.assertThat(sslFills.get(), Matchers.lessThan(40));
+        Assert.assertThat(sslFlushes.get(), Matchers.lessThan(40));
+        Assert.assertThat(httpParses.get(), Matchers.lessThan(50));
+
+        client.close();
+    }
+
+    @Test
+    public void testRequestWithCloseAlertWithSplitBoundary() throws Exception
+    {
+        if (!OS.IS_LINUX)
+        {
+            // currently we are ignoring this test on anything other then linux
+
+            //http://tools.ietf.org/html/rfc2246#section-7.2.1
+
+            // TODO (react to this portion which seems to allow win/mac behavior)
+            //It is required that the other party respond with a close_notify alert of its own
+            //and close down the connection immediately, discarding any pending writes. It is not
+            //required for the initiator of the close to wait for the responding
+            //close_notify alert before closing the read side of the connection.
+            return;
+        }
+
+        final SSLSocket client = newClient();
+
+        SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow();
+        client.startHandshake();
+        Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS));
+
+        Future<Object> request = threadPool.submit(new Callable<Object>()
+        {
+            @Override
+            public Object call() throws Exception
+            {
+                OutputStream clientOutput = client.getOutputStream();
+                clientOutput.write(("" +
+                        "GET / HTTP/1.1\r\n" +
+                        "Host: localhost\r\n" +
+                        "\r\n").getBytes(StandardCharsets.UTF_8));
+                clientOutput.flush();
+                return null;
+            }
+        });
+
+        // Application data
+        TLSRecord dataRecord = proxy.readFromClient();
+        Assert.assertNull(request.get(5, TimeUnit.SECONDS));
+
+        client.close();
+
+        // Close Alert
+        TLSRecord closeRecord = proxy.readFromClient();
+
+        // Send request and half of the close alert bytes
+        byte[] dataBytes = dataRecord.getBytes();
+        byte[] closeBytes = closeRecord.getBytes();
+        byte[] bytes = new byte[dataBytes.length + closeBytes.length / 2];
+        System.arraycopy(dataBytes, 0, bytes, 0, dataBytes.length);
+        System.arraycopy(closeBytes, 0, bytes, dataBytes.length, closeBytes.length / 2);
+        proxy.flushToServer(100, bytes);
+
+        // Send the other half of the close alert bytes
+        bytes = new byte[closeBytes.length - closeBytes.length / 2];
+        System.arraycopy(closeBytes, closeBytes.length / 2, bytes, 0, bytes.length);
+        proxy.flushToServer(100, bytes);
+
+        // Do not close the raw socket yet
+
+        // Expect response from server
+        // SSLSocket is limited and we cannot read the response, but we make sure
+        // it is application data and not a close alert
+        TLSRecord record = proxy.readFromServer();
+        Assert.assertNotNull(record);
+        Assert.assertEquals(TLSRecord.Type.APPLICATION, record.getType());
+        proxy.flushToClient(record);
+
+        // Socket close
+        record = proxy.readFromServer();
+        if (record!=null)
+        {
+            Assert.assertEquals(record.getType(),Type.ALERT);
+            
+            // Now should be a raw close
+            record = proxy.readFromServer();
+            Assert.assertNull(String.valueOf(record), record);
+        }
+
+        // Check that we did not spin
+        TimeUnit.MILLISECONDS.sleep(500);
+        Assert.assertThat(sslFills.get(), Matchers.lessThan(20));
+        Assert.assertThat(sslFlushes.get(), Matchers.lessThan(20));
+        Assert.assertThat(httpParses.get(), Matchers.lessThan(20));
+    }
+
+    @Test
+    public void testRequestWithContentWithSplitBoundary() throws Exception
+    {
+        final SSLSocket client = newClient();
+
+        SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow();
+        client.startHandshake();
+        Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS));
+
+        final String content = "0123456789ABCDEF";
+
+        Future<Object> request = threadPool.submit(new Callable<Object>()
+        {
+            @Override
+            public Object call() throws Exception
+            {
+                OutputStream clientOutput = client.getOutputStream();
+                clientOutput.write(("" +
+                        "POST / HTTP/1.1\r\n" +
+                        "Host: localhost\r\n" +
+                        "Content-Type: text/plain\r\n" +
+                        "Content-Length: " + content.length() + "\r\n" +
+                        "\r\n" +
+                        content).getBytes(StandardCharsets.UTF_8));
+                clientOutput.flush();
+                return null;
+            }
+        });
+
+        // Application data
+        TLSRecord record = proxy.readFromClient();
+        Assert.assertNull(request.get(5, TimeUnit.SECONDS));
+        byte[] chunk1 = new byte[2 * record.getBytes().length / 3];
+        System.arraycopy(record.getBytes(), 0, chunk1, 0, chunk1.length);
+        proxy.flushToServer(100, chunk1);
+
+        byte[] chunk2 = new byte[record.getBytes().length - chunk1.length];
+        System.arraycopy(record.getBytes(), chunk1.length, chunk2, 0, chunk2.length);
+        proxy.flushToServer(100, chunk2);
+
+        record = proxy.readFromServer();
+        Assert.assertEquals(TLSRecord.Type.APPLICATION, record.getType());
+        proxy.flushToClient(record);
+
+        BufferedReader reader = new BufferedReader(new InputStreamReader(client.getInputStream(), StandardCharsets.UTF_8));
+        String line = reader.readLine();
+        Assert.assertNotNull(line);
+        Assert.assertTrue(line.startsWith("HTTP/1.1 200 "));
+        while ((line = reader.readLine()) != null)
+        {
+            if (line.trim().length() == 0)
+                break;
+        }
+
+        // Check that we did not spin
+        TimeUnit.MILLISECONDS.sleep(500);
+        Assert.assertThat(sslFills.get(), Matchers.lessThan(20));
+        Assert.assertThat(sslFlushes.get(), Matchers.lessThan(20));
+        Assert.assertThat(httpParses.get(), Matchers.lessThan(20));
+
+        closeClient(client);
+    }
+
+    @Test
+    public void testRequestWithBigContentWithSplitBoundary() throws Exception
+    {
+        final SSLSocket client = newClient();
+
+        SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow();
+        client.startHandshake();
+        Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS));
+
+        // Use a content that is larger than the TLS record which is 2^14 (around 16k)
+        byte[] data = new byte[128 * 1024];
+        Arrays.fill(data, (byte)'X');
+        final String content = new String(data, StandardCharsets.UTF_8);
+
+        Future<Object> request = threadPool.submit(new Callable<Object>()
+        {
+            @Override
+            public Object call() throws Exception
+            {
+                OutputStream clientOutput = client.getOutputStream();
+                clientOutput.write(("" +
+                        "POST / HTTP/1.1\r\n" +
+                        "Host: localhost\r\n" +
+                        "Content-Type: text/plain\r\n" +
+                        "Content-Length: " + content.length() + "\r\n" +
+                        "\r\n" +
+                        content).getBytes(StandardCharsets.UTF_8));
+                clientOutput.flush();
+                return null;
+            }
+        });
+
+        // Nine TLSRecords will be generated for the request
+        for (int i = 0; i < 9; ++i)
+        {
+            // Application data
+            TLSRecord record = proxy.readFromClient();
+            byte[] bytes = record.getBytes();
+            byte[] chunk1 = new byte[2 * bytes.length / 3];
+            System.arraycopy(bytes, 0, chunk1, 0, chunk1.length);
+            byte[] chunk2 = new byte[bytes.length - chunk1.length];
+            System.arraycopy(bytes, chunk1.length, chunk2, 0, chunk2.length);
+            proxy.flushToServer(100, chunk1);
+            proxy.flushToServer(100, chunk2);
+        }
+
+        // Check that we did not spin
+        TimeUnit.MILLISECONDS.sleep(500);
+        Assert.assertThat(sslFills.get(), Matchers.lessThan(50));
+        Assert.assertThat(sslFlushes.get(), Matchers.lessThan(20));
+        Assert.assertThat(httpParses.get(), Matchers.lessThan(100));
+
+        Assert.assertNull(request.get(5, TimeUnit.SECONDS));
+
+        TLSRecord record = proxy.readFromServer();
+        Assert.assertEquals(TLSRecord.Type.APPLICATION, record.getType());
+        proxy.flushToClient(record);
+
+        BufferedReader reader = new BufferedReader(new InputStreamReader(client.getInputStream(), StandardCharsets.UTF_8));
+        String line = reader.readLine();
+        Assert.assertNotNull(line);
+        Assert.assertTrue(line.startsWith("HTTP/1.1 200 "));
+        while ((line = reader.readLine()) != null)
+        {
+            if (line.trim().length() == 0)
+                break;
+        }
+
+        // Check that we did not spin
+        TimeUnit.MILLISECONDS.sleep(500);
+        Assert.assertThat(sslFills.get(), Matchers.lessThan(50));
+        Assert.assertThat(sslFlushes.get(), Matchers.lessThan(20));
+        Assert.assertThat(httpParses.get(), Matchers.lessThan(100));
+
+        closeClient(client);
+    }
+
+    // TODO work out why this test frequently fails
+    @Ignore
+    @Test(timeout=10000)
+    public void testRequestWithContentWithRenegotiationInMiddleOfContentWhenRenegotiationIsForbidden() throws Exception
+    {
+        assumeJavaVersionSupportsTLSRenegotiations();
+
+        sslContextFactory.setRenegotiationAllowed(false);
+
+        final SSLSocket client = newClient();
+        final OutputStream clientOutput = client.getOutputStream();
+
+        SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow();
+        client.startHandshake();
+        Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS));
+
+        byte[] data1 = new byte[1024];
+        Arrays.fill(data1, (byte)'X');
+        String content1 = new String(data1, StandardCharsets.UTF_8);
+        byte[] data2 = new byte[1024];
+        Arrays.fill(data2, (byte)'Y');
+        final String content2 = new String(data2, StandardCharsets.UTF_8);
+
+        // Write only part of the body
+        automaticProxyFlow = proxy.startAutomaticFlow();
+        clientOutput.write(("" +
+                "POST / HTTP/1.1\r\n" +
+                "Host: localhost\r\n" +
+                "Content-Type: text/plain\r\n" +
+                "Content-Length: " + (content1.length() + content2.length()) + "\r\n" +
+                "\r\n" +
+                content1).getBytes(StandardCharsets.UTF_8));
+        clientOutput.flush();
+        Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS));
+
+        // Renegotiate
+        threadPool.submit(new Callable<Object>()
+        {
+            @Override
+            public Object call() throws Exception
+            {
+                client.startHandshake();
+                return null;
+            }
+        });
+
+        // Renegotiation Handshake
+        TLSRecord record = proxy.readFromClient();
+        Assert.assertEquals(TLSRecord.Type.HANDSHAKE, record.getType());
+        proxy.flushToServer(record);
+
+        // Renegotiation now allowed, server has closed
+        record = proxy.readFromServer();
+        Assert.assertEquals(TLSRecord.Type.ALERT, record.getType());
+        proxy.flushToClient(record);
+
+        record = proxy.readFromServer();
+        Assert.assertNull(record);
+
+        // Write the rest of the request
+        threadPool.submit(new Callable<Object>()
+        {
+            @Override
+            public Object call() throws Exception
+            {
+                clientOutput.write(content2.getBytes(StandardCharsets.UTF_8));
+                clientOutput.flush();
+                return null;
+            }
+        });
+
+        // Trying to write more application data results in an exception since the server closed
+        record = proxy.readFromClient();
+        proxy.flushToServer(record);
+        try
+        {
+            record = proxy.readFromClient();
+            Assert.assertNotNull(record);
+            proxy.flushToServer(record);
+            Assert.fail();
+        }
+        catch (IOException expected)
+        {
+        }
+
+        // Check that we did not spin
+        TimeUnit.MILLISECONDS.sleep(500);
+        Assert.assertThat(sslFills.get(), Matchers.lessThan(50));
+        Assert.assertThat(sslFlushes.get(), Matchers.lessThan(20));
+        Assert.assertThat(httpParses.get(), Matchers.lessThan(50));
+
+        client.close();
+    }
+
+    @Test
+    public void testRequestWithBigContentWithRenegotiationInMiddleOfContent() throws Exception
+    {
+        assumeJavaVersionSupportsTLSRenegotiations();
+
+        final SSLSocket client = newClient();
+        final OutputStream clientOutput = client.getOutputStream();
+
+        SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow();
+        client.startHandshake();
+        Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS));
+
+        // Use a content that is larger than the TLS record which is 2^14 (around 16k)
+        byte[] data1 = new byte[80 * 1024];
+        Arrays.fill(data1, (byte)'X');
+        String content1 = new String(data1, StandardCharsets.UTF_8);
+        byte[] data2 = new byte[48 * 1024];
+        Arrays.fill(data2, (byte)'Y');
+        final String content2 = new String(data2, StandardCharsets.UTF_8);
+
+        // Write only part of the body
+        automaticProxyFlow = proxy.startAutomaticFlow();
+        clientOutput.write(("" +
+                "POST / HTTP/1.1\r\n" +
+                "Host: localhost\r\n" +
+                "Content-Type: text/plain\r\n" +
+                "Content-Length: " + (content1.length() + content2.length()) + "\r\n" +
+                "\r\n" +
+                content1).getBytes(StandardCharsets.UTF_8));
+        clientOutput.flush();
+        Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS));
+
+        // Renegotiate
+        Future<Object> renegotiation = threadPool.submit(new Callable<Object>()
+        {
+            @Override
+            public Object call() throws Exception
+            {
+                client.startHandshake();
+                return null;
+            }
+        });
+
+        // Renegotiation Handshake
+        TLSRecord record = proxy.readFromClient();
+        Assert.assertEquals(TLSRecord.Type.HANDSHAKE, record.getType());
+        proxy.flushToServer(record);
+
+        // Renegotiation Handshake
+        record = proxy.readFromServer();
+        Assert.assertEquals(TLSRecord.Type.HANDSHAKE, record.getType());
+        proxy.flushToClient(record);
+
+        // Renegotiation Change Cipher
+        record = proxy.readFromServer();
+        Assert.assertEquals(TLSRecord.Type.CHANGE_CIPHER_SPEC, record.getType());
+        proxy.flushToClient(record);
+
+        // Renegotiation Handshake
+        record = proxy.readFromServer();
+        Assert.assertEquals(TLSRecord.Type.HANDSHAKE, record.getType());
+        proxy.flushToClient(record);
+
+        // Trigger a read to have the client write the final renegotiation steps
+        client.setSoTimeout(100);
+        try
+        {
+            client.getInputStream().read();
+            Assert.fail();
+        }
+        catch (SocketTimeoutException x)
+        {
+            // Expected
+        }
+
+        // Renegotiation Change Cipher
+        record = proxy.readFromClient();
+        Assert.assertEquals(TLSRecord.Type.CHANGE_CIPHER_SPEC, record.getType());
+        proxy.flushToServer(record);
+
+        // Renegotiation Handshake
+        record = proxy.readFromClient();
+        Assert.assertEquals(TLSRecord.Type.HANDSHAKE, record.getType());
+        proxy.flushToServer(record);
+
+        Assert.assertNull(renegotiation.get(5, TimeUnit.SECONDS));
+
+        // Write the rest of the request
+        Future<Object> request = threadPool.submit(new Callable<Object>()
+        {
+            @Override
+            public Object call() throws Exception
+            {
+                clientOutput.write(content2.getBytes(StandardCharsets.UTF_8));
+                clientOutput.flush();
+                return null;
+            }
+        });
+
+        // Three TLSRecords will be generated for the remainder of the content
+        for (int i = 0; i < 3; ++i)
+        {
+            // Application data
+            record = proxy.readFromClient();
+            proxy.flushToServer(record);
+        }
+
+        Assert.assertNull(request.get(5, TimeUnit.SECONDS));
+
+        // Read response
+        // Application Data
+        record = proxy.readFromServer();
+        Assert.assertEquals(TLSRecord.Type.APPLICATION, record.getType());
+        proxy.flushToClient(record);
+
+        BufferedReader reader = new BufferedReader(new InputStreamReader(client.getInputStream(), StandardCharsets.UTF_8));
+        String line = reader.readLine();
+        Assert.assertNotNull(line);
+        Assert.assertTrue(line.startsWith("HTTP/1.1 200 "));
+        while ((line = reader.readLine()) != null)
+        {
+            if (line.trim().length() == 0)
+                break;
+        }
+
+        // Check that we did not spin
+        TimeUnit.MILLISECONDS.sleep(500);
+        Assert.assertThat(sslFills.get(), Matchers.lessThan(50));
+        Assert.assertThat(sslFlushes.get(), Matchers.lessThan(20));
+        Assert.assertThat(httpParses.get(), Matchers.lessThan(50));
+
+        closeClient(client);
+    }
+
+    @Test(timeout=10000)
+    public void testRequestWithBigContentWithRenegotiationInMiddleOfContentWithSplitBoundary() throws Exception
+    {
+        assumeJavaVersionSupportsTLSRenegotiations();
+
+        final SSLSocket client = newClient();
+        final OutputStream clientOutput = client.getOutputStream();
+
+        SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow();
+        client.startHandshake();
+        Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS));
+
+        // Use a content that is larger than the TLS record which is 2^14 (around 16k)
+        byte[] data1 = new byte[80 * 1024];
+        Arrays.fill(data1, (byte)'X');
+        String content1 = new String(data1, StandardCharsets.UTF_8);
+        byte[] data2 = new byte[48 * 1024];
+        Arrays.fill(data2, (byte)'Y');
+        final String content2 = new String(data2, StandardCharsets.UTF_8);
+
+        // Write only part of the body
+        automaticProxyFlow = proxy.startAutomaticFlow();
+        clientOutput.write(("" +
+                "POST / HTTP/1.1\r\n" +
+                "Host: localhost\r\n" +
+                "Content-Type: text/plain\r\n" +
+                "Content-Length: " + (content1.length() + content2.length()) + "\r\n" +
+                "\r\n" +
+                content1).getBytes(StandardCharsets.UTF_8));
+        clientOutput.flush();
+        Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS));
+
+        // Renegotiate
+        Future<Object> renegotiation = threadPool.submit(new Callable<Object>()
+        {
+            @Override
+            public Object call() throws Exception
+            {
+                client.startHandshake();
+                return null;
+            }
+        });
+
+        // Renegotiation Handshake
+        TLSRecord record = proxy.readFromClient();
+        Assert.assertEquals(TLSRecord.Type.HANDSHAKE, record.getType());
+        byte[] bytes = record.getBytes();
+        byte[] chunk1 = new byte[2 * bytes.length / 3];
+        System.arraycopy(bytes, 0, chunk1, 0, chunk1.length);
+        byte[] chunk2 = new byte[bytes.length - chunk1.length];
+        System.arraycopy(bytes, chunk1.length, chunk2, 0, chunk2.length);
+        proxy.flushToServer(100, chunk1);
+        proxy.flushToServer(100, chunk2);
+
+        // Renegotiation Handshake
+        record = proxy.readFromServer();
+        Assert.assertEquals(TLSRecord.Type.HANDSHAKE, record.getType());
+        proxy.flushToClient(record);
+
+        // Renegotiation Change Cipher
+        record = proxy.readFromServer();
+        Assert.assertEquals(TLSRecord.Type.CHANGE_CIPHER_SPEC, record.getType());
+        proxy.flushToClient(record);
+
+        // Renegotiation Handshake
+        record = proxy.readFromServer();
+        Assert.assertEquals(TLSRecord.Type.HANDSHAKE, record.getType());
+        proxy.flushToClient(record);
+
+        // Trigger a read to have the client write the final renegotiation steps
+        client.setSoTimeout(100);
+        try
+        {
+            client.getInputStream().read();
+            Assert.fail();
+        }
+        catch (SocketTimeoutException x)
+        {
+            // Expected
+        }
+
+        // Renegotiation Change Cipher
+        record = proxy.readFromClient();
+        Assert.assertEquals(TLSRecord.Type.CHANGE_CIPHER_SPEC, record.getType());
+        bytes = record.getBytes();
+        chunk1 = new byte[2 * bytes.length / 3];
+        System.arraycopy(bytes, 0, chunk1, 0, chunk1.length);
+        chunk2 = new byte[bytes.length - chunk1.length];
+        System.arraycopy(bytes, chunk1.length, chunk2, 0, chunk2.length);
+        proxy.flushToServer(100, chunk1);
+        proxy.flushToServer(100, chunk2);
+
+        // Renegotiation Handshake
+        record = proxy.readFromClient();
+        Assert.assertEquals(TLSRecord.Type.HANDSHAKE, record.getType());
+        bytes = record.getBytes();
+        chunk1 = new byte[2 * bytes.length / 3];
+        System.arraycopy(bytes, 0, chunk1, 0, chunk1.length);
+        chunk2 = new byte[bytes.length - chunk1.length];
+        System.arraycopy(bytes, chunk1.length, chunk2, 0, chunk2.length);
+        proxy.flushToServer(100, chunk1);
+        // Do not write the second chunk now, but merge it with content, see below
+
+        Assert.assertNull(renegotiation.get(5, TimeUnit.SECONDS));
+
+        // Write the rest of the request
+        Future<Object> request = threadPool.submit(new Callable<Object>()
+        {
+            @Override
+            public Object call() throws Exception
+            {
+                clientOutput.write(content2.getBytes(StandardCharsets.UTF_8));
+                clientOutput.flush();
+                return null;
+            }
+        });
+
+        // Three TLSRecords will be generated for the remainder of the content
+        // Merge the last chunk of the renegotiation with the first data record
+        record = proxy.readFromClient();
+        Assert.assertEquals(TLSRecord.Type.APPLICATION, record.getType());
+        byte[] dataBytes = record.getBytes();
+        byte[] mergedBytes = new byte[chunk2.length + dataBytes.length];
+        System.arraycopy(chunk2, 0, mergedBytes, 0, chunk2.length);
+        System.arraycopy(dataBytes, 0, mergedBytes, chunk2.length, dataBytes.length);
+        proxy.flushToServer(100, mergedBytes);
+        // Write the remaining 2 TLS records
+        for (int i = 0; i < 2; ++i)
+        {
+            // Application data
+            record = proxy.readFromClient();
+            Assert.assertEquals(TLSRecord.Type.APPLICATION, record.getType());
+            proxy.flushToServer(record);
+        }
+
+        Assert.assertNull(request.get(5, TimeUnit.SECONDS));
+
+        // Read response
+        // Application Data
+        record = proxy.readFromServer();
+        Assert.assertEquals(TLSRecord.Type.APPLICATION, record.getType());
+        proxy.flushToClient(record);
+
+        BufferedReader reader = new BufferedReader(new InputStreamReader(client.getInputStream(), StandardCharsets.UTF_8));
+        String line = reader.readLine();
+        Assert.assertNotNull(line);
+        Assert.assertTrue(line.startsWith("HTTP/1.1 200 "));
+        while ((line = reader.readLine()) != null)
+        {
+            if (line.trim().length() == 0)
+                break;
+        }
+
+        // Check that we did not spin
+        TimeUnit.MILLISECONDS.sleep(500);
+        Assert.assertThat(sslFills.get(), Matchers.lessThan(50));
+        Assert.assertThat(sslFlushes.get(), Matchers.lessThan(20));
+        Assert.assertThat(httpParses.get(), Matchers.lessThan(100));
+
+        closeClient(client);
+    }
+
+    @Test
+    public void testServerShutdownOutputClientDoesNotCloseServerCloses() throws Exception
+    {
+        final SSLSocket client = newClient();
+        final OutputStream clientOutput = client.getOutputStream();
+
+        SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow();
+        client.startHandshake();
+        Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS));
+
+        byte[] data = new byte[3 * 1024];
+        Arrays.fill(data, (byte)'Y');
+        String content = new String(data, StandardCharsets.UTF_8);
+        automaticProxyFlow = proxy.startAutomaticFlow();
+        clientOutput.write(("" +
+                "POST / HTTP/1.1\r\n" +
+                "Host: localhost\r\n" +
+                "Content-Type: text/plain\r\n" +
+                "Content-Length: " + content.length() + "\r\n" +
+                "Connection: close\r\n" +
+                "\r\n" +
+                content).getBytes(StandardCharsets.UTF_8));
+        clientOutput.flush();
+
+        BufferedReader reader = new BufferedReader(new InputStreamReader(client.getInputStream(), StandardCharsets.UTF_8));
+        String line = reader.readLine();
+        Assert.assertNotNull(line);
+        Assert.assertTrue(line.startsWith("HTTP/1.1 200 "));
+        while ((line = reader.readLine()) != null)
+        {
+            if (line.trim().length() == 0)
+                break;
+        }
+        Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS));
+
+        // Check client is at EOF
+        Assert.assertEquals(-1, client.getInputStream().read());
+
+        // Client should close the socket, but let's hold it open.
+
+        // Check that we did not spin
+        TimeUnit.MILLISECONDS.sleep(500);
+        Assert.assertThat(sslFills.get(), Matchers.lessThan(20));
+        Assert.assertThat(sslFlushes.get(), Matchers.lessThan(20));
+        Assert.assertThat(httpParses.get(), Matchers.lessThan(20));
+
+        // The server has shutdown the output since the client sent a Connection: close
+        // but the client does not close, so the server must idle timeout the endPoint.
+
+        TimeUnit.MILLISECONDS.sleep(idleTimeout + idleTimeout / 2);
+
+        Assert.assertFalse(serverEndPoint.get().isOpen());
+    }
+
+    @Test
+    public void testPlainText() throws Exception
+    {
+        final SSLSocket client = newClient();
+
+        threadPool.submit(new Callable<Object>()
+        {
+            @Override
+            public Object call() throws Exception
+            {
+                client.startHandshake();
+                return null;
+            }
+        });
+
+        // Instead of passing the Client Hello, we simulate plain text was passed in
+        proxy.flushToServer(0, "GET / HTTP/1.1\r\n".getBytes(StandardCharsets.UTF_8));
+
+        // We expect that the server closes the connection immediately
+        TLSRecord record = proxy.readFromServer();
+        Assert.assertNull(String.valueOf(record), record);
+
+        // Check that we did not spin
+        TimeUnit.MILLISECONDS.sleep(500);
+        Assert.assertThat(sslFills.get(), Matchers.lessThan(20));
+        Assert.assertThat(sslFlushes.get(), Matchers.lessThan(20));
+        Assert.assertThat(httpParses.get(), Matchers.lessThan(20));
+
+        client.close();
+    }
+
+    @Test
+    public void testRequestConcurrentWithIdleExpiration() throws Exception
+    {
+        final SSLSocket client = newClient();
+        final OutputStream clientOutput = client.getOutputStream();
+        final CountDownLatch latch = new CountDownLatch(1);
+
+        idleHook = new Runnable()
+        {
+            public void run()
+            {
+                if (latch.getCount()==0)
+                    return;
+                try
+                {
+                    // Send request
+                    clientOutput.write(("" +
+                            "GET / HTTP/1.1\r\n" +
+                            "Host: localhost\r\n" +
+                            "\r\n").getBytes(StandardCharsets.UTF_8));
+                    clientOutput.flush();
+                    latch.countDown();
+                }
+                catch (Exception x)
+                {
+                    // Latch won't trigger and test will fail
+                    x.printStackTrace();
+                }
+            }
+        };
+
+        SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow();
+        client.startHandshake();
+        Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS));
+
+        Assert.assertTrue(latch.await(idleTimeout * 2, TimeUnit.MILLISECONDS));
+
+        // Be sure that the server sent a SSL close alert
+        TLSRecord record = proxy.readFromServer();
+        Assert.assertNotNull(record);
+        Assert.assertEquals(TLSRecord.Type.ALERT, record.getType());
+
+        // Write the request to the server, to simulate a request
+        // concurrent with the SSL close alert
+        record = proxy.readFromClient();
+        Assert.assertEquals(TLSRecord.Type.APPLICATION, record.getType());
+        proxy.flushToServer(record, 0);
+
+        // Check that we did not spin
+        TimeUnit.MILLISECONDS.sleep(500);
+        Assert.assertThat(sslFills.get(), Matchers.lessThan(20));
+        Assert.assertThat(sslFlushes.get(), Matchers.lessThan(20));
+        Assert.assertThat(httpParses.get(), Matchers.lessThan(50));
+
+        record = proxy.readFromServer();
+        Assert.assertNull(record);
+
+        TimeUnit.MILLISECONDS.sleep(200);
+        Assert.assertThat(((Dumpable)server.getConnectors()[0]).dump(), Matchers.not(Matchers.containsString("SCEP@")));
+    }
+
+    private void assumeJavaVersionSupportsTLSRenegotiations()
+    {
+        // Due to a security bug, TLS renegotiations were disabled in JDK 1.6.0_19-21
+        // so we check the java version in order to avoid to fail the test.
+        String javaVersion = System.getProperty("java.version");
+        Pattern regexp = Pattern.compile("1\\.6\\.0_(\\d{2})");
+        Matcher matcher = regexp.matcher(javaVersion);
+        if (matcher.matches())
+        {
+            String nano = matcher.group(1);
+            Assume.assumeThat(Integer.parseInt(nano), Matchers.greaterThan(21));
+        }
+    }
+
+    private SSLSocket newClient() throws IOException, InterruptedException
+    {
+        return newClient(proxy);
+    }
+
+    private SSLSocket newClient(SimpleProxy proxy) throws IOException, InterruptedException
+    {
+        SSLSocket client = (SSLSocket)sslContext.getSocketFactory().createSocket("localhost", proxy.getPort());
+        client.setUseClientMode(true);
+        Assert.assertTrue(proxy.awaitClient(5, TimeUnit.SECONDS));
+        return client;
+    }
+
+    private void closeClient(SSLSocket client) throws Exception
+    {
+        client.close();
+
+        // Close Alert
+        TLSRecord record = proxy.readFromClient();
+        proxy.flushToServer(record);
+        // Socket close
+        record = proxy.readFromClient();
+        Assert.assertNull(String.valueOf(record), record);
+        proxy.flushToServer(record);
+
+        // Socket close
+        record = proxy.readFromServer();
+        if (record!=null)
+        {
+            Assert.assertEquals(record.getType(),Type.ALERT);
+            record = proxy.readFromServer();
+        }
+        Assert.assertThat(record,nullValue());
+    }
+}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesTest.java
new file mode 100644
index 0000000..9d530bb
--- /dev/null
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesTest.java
@@ -0,0 +1,385 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client.ssl;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InterruptedIOException;
+import java.io.OutputStream;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.SocketTimeoutException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.toolchain.test.TestTracker;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.junit.Assert;
+import org.junit.Rule;
+
+public abstract class SslBytesTest
+{
+    @Rule
+    public TestTracker tracker = new TestTracker();
+
+    protected final Logger logger = Log.getLogger(getClass());
+
+    public static class TLSRecord
+    {
+        private final SslBytesServerTest.TLSRecord.Type type;
+        private final byte[] bytes;
+
+        public TLSRecord(SslBytesServerTest.TLSRecord.Type type, byte[] bytes)
+        {
+            this.type = type;
+            this.bytes = bytes;
+        }
+
+        public SslBytesServerTest.TLSRecord.Type getType()
+        {
+            return type;
+        }
+
+        public byte[] getBytes()
+        {
+            return bytes;
+        }
+
+        @Override
+        public String toString()
+        {
+            return "TLSRecord [" + type + "] " + bytes.length + " bytes";
+        }
+
+        public enum Type
+        {
+            CHANGE_CIPHER_SPEC(20), ALERT(21), HANDSHAKE(22), APPLICATION(23);
+
+            private int code;
+
+            private Type(int code)
+            {
+                this.code = code;
+                SslBytesServerTest.TLSRecord.Type.Mapper.codes.put(this.code, this);
+            }
+
+            public static SslBytesServerTest.TLSRecord.Type from(int code)
+            {
+                SslBytesServerTest.TLSRecord.Type result = SslBytesServerTest.TLSRecord.Type.Mapper.codes.get(code);
+                if (result == null)
+                    throw new IllegalArgumentException("Invalid TLSRecord.Type " + code);
+                return result;
+            }
+
+            private static class Mapper
+            {
+                private static final Map<Integer, SslBytesServerTest.TLSRecord.Type> codes = new HashMap<>();
+            }
+        }
+    }
+
+    public class SimpleProxy implements Runnable
+    {
+        private final CountDownLatch latch = new CountDownLatch(1);
+        private final ExecutorService threadPool;
+        private final int proxyPort;
+        private final String serverHost;
+        private final int serverPort;
+        private volatile ServerSocket serverSocket;
+        private volatile Socket server;
+        private volatile Socket client;
+
+        public SimpleProxy(ExecutorService threadPool, String serverHost, int serverPort)
+        {
+            this(threadPool, 0, serverHost, serverPort);
+        }
+
+        public SimpleProxy(ExecutorService threadPool, int proxyPort, String serverHost, int serverPort)
+        {
+            this.threadPool = threadPool;
+            this.proxyPort = proxyPort;
+            this.serverHost = serverHost;
+            this.serverPort = serverPort;
+        }
+
+        public void start() throws Exception
+        {
+            serverSocket = new ServerSocket(proxyPort);
+            Thread acceptor = new Thread(this);
+            acceptor.start();
+            server = new Socket(serverHost, serverPort);
+        }
+
+        public void stop() throws Exception
+        {
+            server.close();
+            if (client != null) // some tests only run on linux, those won't create a client on other OS
+                client.close();
+            serverSocket.close();
+        }
+
+        public void run()
+        {
+            try
+            {
+                client = serverSocket.accept();
+                latch.countDown();
+            }
+            catch (IOException x)
+            {
+                x.printStackTrace();
+            }
+        }
+
+        public int getPort()
+        {
+            return serverSocket.getLocalPort();
+        }
+
+        public TLSRecord readFromClient() throws IOException
+        {
+            TLSRecord record = read(client);
+            logger.debug("C --> P {}", record);
+            return record;
+        }
+
+        private TLSRecord read(Socket socket) throws IOException
+        {
+            InputStream input = socket.getInputStream();
+            int first = -2;
+            while (true)
+            {
+                try
+                {
+                    socket.setSoTimeout(500);
+                    first = input.read();
+                    break;
+                }
+                catch (SocketTimeoutException x)
+                {
+                    if (Thread.currentThread().isInterrupted())
+                        break;
+                }
+            }
+            if (first == -2)
+                throw new InterruptedIOException();
+            else if (first == -1)
+                return null;
+
+            if (first >= 0x80)
+            {
+                // SSLv2 Record
+                int hiLength = first & 0x3F;
+                int loLength = input.read();
+                int length = (hiLength << 8) + loLength;
+                byte[] bytes = new byte[2 + length];
+                bytes[0] = (byte)first;
+                bytes[1] = (byte)loLength;
+                return read(TLSRecord.Type.HANDSHAKE, input, bytes, 2, length);
+            }
+            else
+            {
+                // TLS Record
+                int major = input.read();
+                int minor = input.read();
+                int hiLength = input.read();
+                int loLength = input.read();
+                int length = (hiLength << 8) + loLength;
+                byte[] bytes = new byte[1 + 2 + 2 + length];
+                bytes[0] = (byte)first;
+                bytes[1] = (byte)major;
+                bytes[2] = (byte)minor;
+                bytes[3] = (byte)hiLength;
+                bytes[4] = (byte)loLength;
+                return read(TLSRecord.Type.from(first), input, bytes, 5, length);
+            }
+        }
+
+        private TLSRecord read(SslBytesServerTest.TLSRecord.Type type, InputStream input, byte[] bytes, int offset, int length) throws IOException
+        {
+            while (length > 0)
+            {
+                int read = input.read(bytes, offset, length);
+                if (read < 0)
+                    throw new EOFException();
+                offset += read;
+                length -= read;
+            }
+            return new TLSRecord(type, bytes);
+        }
+
+        public void flushToServer(TLSRecord record) throws Exception
+        {
+            flushToServer(record, 100);
+        }
+
+        public void flushToServer(TLSRecord record, long sleep) throws Exception
+        {
+            if (record == null)
+            {
+                server.shutdownOutput();
+                if (client.isOutputShutdown())
+                {
+                    client.close();
+                    server.close();
+                }
+            }
+            else
+            {
+                flush(sleep, server, record.getBytes());
+            }
+        }
+
+        public void flushToServer(long sleep, byte... bytes) throws Exception
+        {
+            flush(sleep, server, bytes);
+        }
+
+        private void flush(long sleep, Socket socket, byte... bytes) throws Exception
+        {
+            OutputStream output = socket.getOutputStream();
+            output.write(bytes);
+            output.flush();
+            if (sleep > 0)
+                TimeUnit.MILLISECONDS.sleep(sleep);
+        }
+
+        public TLSRecord readFromServer() throws IOException
+        {
+            TLSRecord record = read(server);
+            logger.debug("P <-- S {}", record);
+            return record;
+        }
+
+        public void flushToClient(TLSRecord record) throws Exception
+        {
+            if (record == null)
+            {
+                client.shutdownOutput();
+                if (server.isOutputShutdown())
+                {
+                    server.close();
+                    client.close();
+                }
+            }
+            else
+            {
+                flush(0, client, record.getBytes());
+            }
+        }
+
+        public SslBytesServerTest.SimpleProxy.AutomaticFlow startAutomaticFlow() throws InterruptedException
+        {
+            final CountDownLatch startLatch = new CountDownLatch(2);
+            final CountDownLatch stopLatch = new CountDownLatch(2);
+            Future<Object> clientToServer = threadPool.submit(new Callable<Object>()
+            {
+                public Object call() throws Exception
+                {
+                    startLatch.countDown();
+                    logger.debug("Automatic flow C --> S started");
+                    try
+                    {
+                        while (true)
+                        {
+                            flushToServer(readFromClient(), 0);
+                        }
+                    }
+                    catch (InterruptedIOException x)
+                    {
+                        return null;
+                    }
+                    finally
+                    {
+                        stopLatch.countDown();
+                        logger.debug("Automatic flow C --> S finished");
+                    }
+                }
+            });
+            Future<Object> serverToClient = threadPool.submit(new Callable<Object>()
+            {
+                public Object call() throws Exception
+                {
+                    startLatch.countDown();
+                    logger.debug("Automatic flow C <-- S started");
+                    try
+                    {
+                        while (true)
+                        {
+                            flushToClient(readFromServer());
+                        }
+                    }
+                    catch (InterruptedIOException x)
+                    {
+                        return null;
+                    }
+                    finally
+                    {
+                        stopLatch.countDown();
+                        logger.debug("Automatic flow C <-- S finished");
+                    }
+                }
+            });
+            Assert.assertTrue(startLatch.await(5, TimeUnit.SECONDS));
+            return new SslBytesServerTest.SimpleProxy.AutomaticFlow(stopLatch, clientToServer, serverToClient);
+        }
+
+        public boolean awaitClient(int time, TimeUnit unit) throws InterruptedException
+        {
+            return latch.await(time, unit);
+        }
+
+        public void sendRSTToServer() throws IOException
+        {
+            // Calling setSoLinger(true, 0) causes close()
+            // to send a RST instead of a FIN, causing an
+            // exception to be thrown on the other end
+            server.setSoLinger(true, 0);
+            server.close();
+        }
+
+        public class AutomaticFlow
+        {
+            private final CountDownLatch stopLatch;
+            private final Future<Object> clientToServer;
+            private final Future<Object> serverToClient;
+
+            public AutomaticFlow(CountDownLatch stopLatch, Future<Object> clientToServer, Future<Object> serverToClient)
+            {
+                this.stopLatch = stopLatch;
+                this.clientToServer = clientToServer;
+                this.serverToClient = serverToClient;
+            }
+
+            public boolean stop(long time, TimeUnit unit) throws InterruptedException
+            {
+                clientToServer.cancel(true);
+                serverToClient.cancel(true);
+                return stopLatch.await(time, unit);
+            }
+        }
+    }
+}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/util/DeferredContentProviderTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/util/DeferredContentProviderTest.java
new file mode 100644
index 0000000..b78fb93
--- /dev/null
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/util/DeferredContentProviderTest.java
@@ -0,0 +1,156 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client.util;
+
+import java.nio.ByteBuffer;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.eclipse.jetty.util.Callback;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class DeferredContentProviderTest
+{
+    private ExecutorService executor;
+
+    @Before
+    public void prepare() throws Exception
+    {
+        executor = Executors.newCachedThreadPool();
+    }
+
+    @After
+    public void dispose() throws Exception
+    {
+        executor.shutdownNow();
+    }
+
+    @Test
+    public void testWhenEmptyFlushDoesNotBlock() throws Exception
+    {
+        final DeferredContentProvider provider = new DeferredContentProvider();
+
+        Future<?> task = executor.submit(new Callable<Object>()
+        {
+            @Override
+            public Object call() throws Exception
+            {
+                provider.flush();
+                return null;
+            }
+        });
+
+        Assert.assertTrue(await(task, 5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testOfferFlushBlocksUntilSucceeded() throws Exception
+    {
+        final DeferredContentProvider provider = new DeferredContentProvider();
+        Iterator<ByteBuffer> iterator = provider.iterator();
+
+        provider.offer(ByteBuffer.allocate(0));
+
+        Future<?> task = executor.submit(new Callable<Object>()
+        {
+            @Override
+            public Object call() throws Exception
+            {
+                provider.flush();
+                return null;
+            }
+        });
+
+        // Wait until flush() blocks.
+        Assert.assertFalse(await(task, 1, TimeUnit.SECONDS));
+
+        // Consume the content and succeed the callback.
+        iterator.next();
+        ((Callback)iterator).succeeded();
+
+        // Flush should return.
+        Assert.assertTrue(await(task, 5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testCloseFlushDoesNotBlock() throws Exception
+    {
+        final DeferredContentProvider provider = new DeferredContentProvider();
+
+        provider.close();
+
+        Future<?> task = executor.submit(new Callable<Object>()
+        {
+            @Override
+            public Object call() throws Exception
+            {
+                provider.flush();
+                return null;
+            }
+        });
+
+        // Wait until flush() blocks.
+        Assert.assertTrue(await(task, 5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testCloseNextHasNextReturnsFalse() throws Exception
+    {
+        DeferredContentProvider provider = new DeferredContentProvider();
+        Iterator<ByteBuffer> iterator = provider.iterator();
+
+        provider.close();
+
+        Assert.assertFalse(iterator.hasNext());
+
+        try
+        {
+            iterator.next();
+            Assert.fail();
+        }
+        catch (NoSuchElementException x)
+        {
+            // Expected
+        }
+
+        Assert.assertFalse(iterator.hasNext());
+    }
+
+    private boolean await(Future<?> task, long time, TimeUnit unit) throws Exception
+    {
+        try
+        {
+            task.get(time, unit);
+            return true;
+        }
+        catch (TimeoutException x)
+        {
+            return false;
+        }
+    }
+}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/util/InputStreamContentProviderTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/util/InputStreamContentProviderTest.java
new file mode 100644
index 0000000..2cca59d
--- /dev/null
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/util/InputStreamContentProviderTest.java
@@ -0,0 +1,185 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client.util;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class InputStreamContentProviderTest
+{
+    @Test
+    public void testHasNextFalseThenNext()
+    {
+        final AtomicBoolean closed = new AtomicBoolean();
+        InputStream stream = new InputStream()
+        {
+            @Override
+            public int read() throws IOException
+            {
+                return -1;
+            }
+
+            @Override
+            public void close() throws IOException
+            {
+                super.close();
+                closed.compareAndSet(false, true);
+            }
+        };
+
+        InputStreamContentProvider provider = new InputStreamContentProvider(stream);
+        Iterator<ByteBuffer> iterator = provider.iterator();
+
+        Assert.assertNotNull(iterator);
+        Assert.assertFalse(iterator.hasNext());
+
+        try
+        {
+            iterator.next();
+            Assert.fail();
+        }
+        catch (NoSuchElementException expected)
+        {
+        }
+
+        Assert.assertFalse(iterator.hasNext());
+        Assert.assertTrue(closed.get());
+    }
+
+    @Test
+    public void testStreamWithContentThenNextThenNext()
+    {
+        final AtomicBoolean closed = new AtomicBoolean();
+        ByteArrayInputStream stream = new ByteArrayInputStream(new byte[]{1})
+        {
+            @Override
+            public void close() throws IOException
+            {
+                super.close();
+                closed.compareAndSet(false, true);
+            }
+        };
+
+        InputStreamContentProvider provider = new InputStreamContentProvider(stream);
+        Iterator<ByteBuffer> iterator = provider.iterator();
+
+        Assert.assertNotNull(iterator);
+
+        ByteBuffer buffer = iterator.next();
+
+        Assert.assertNotNull(buffer);
+
+        try
+        {
+            iterator.next();
+            Assert.fail();
+        }
+        catch (NoSuchElementException expected)
+        {
+        }
+
+        Assert.assertFalse(iterator.hasNext());
+        Assert.assertTrue(closed.get());
+    }
+
+    @Test
+    public void testStreamWithExceptionThenNext()
+    {
+        final AtomicBoolean closed = new AtomicBoolean();
+        InputStream stream = new InputStream()
+        {
+            @Override
+            public int read() throws IOException
+            {
+                throw new IOException();
+            }
+
+            @Override
+            public void close() throws IOException
+            {
+                super.close();
+                closed.compareAndSet(false, true);
+            }
+        };
+
+        InputStreamContentProvider provider = new InputStreamContentProvider(stream);
+        Iterator<ByteBuffer> iterator = provider.iterator();
+
+        Assert.assertNotNull(iterator);
+
+        try
+        {
+            iterator.next();
+            Assert.fail();
+        }
+        catch (NoSuchElementException expected)
+        {
+        }
+
+        Assert.assertFalse(iterator.hasNext());
+        Assert.assertTrue(closed.get());
+    }
+
+    @Test
+    public void testHasNextWithExceptionThenNext()
+    {
+        final AtomicBoolean closed = new AtomicBoolean();
+        InputStream stream = new InputStream()
+        {
+            @Override
+            public int read() throws IOException
+            {
+                throw new IOException();
+            }
+
+            @Override
+            public void close() throws IOException
+            {
+                super.close();
+                closed.compareAndSet(false, true);
+            }
+        };
+
+        InputStreamContentProvider provider = new InputStreamContentProvider(stream);
+        Iterator<ByteBuffer> iterator = provider.iterator();
+
+        Assert.assertNotNull(iterator);
+        Assert.assertTrue(iterator.hasNext());
+
+        try
+        {
+            iterator.next();
+            Assert.fail();
+        }
+        catch (NoSuchElementException expected)
+        {
+        }
+
+        Assert.assertFalse(iterator.hasNext());
+        Assert.assertTrue(closed.get());
+    }
+}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/util/TypedContentProviderTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/util/TypedContentProviderTest.java
new file mode 100644
index 0000000..3cdc6fc
--- /dev/null
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/util/TypedContentProviderTest.java
@@ -0,0 +1,141 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client.util;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.client.AbstractHttpClientServerTest;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.util.Fields;
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.hamcrest.Matchers;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class TypedContentProviderTest extends AbstractHttpClientServerTest
+{
+    public TypedContentProviderTest(SslContextFactory sslContextFactory)
+    {
+        super(sslContextFactory);
+    }
+
+    @Test
+    public void testFormContentProvider() throws Exception
+    {
+        final String name1 = "a";
+        final String value1 = "1";
+        final String name2 = "b";
+        final String value2 = "2";
+        final String value3 = "\u20AC";
+
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                Assert.assertEquals("POST", request.getMethod());
+                Assert.assertEquals(MimeTypes.Type.FORM_ENCODED.asString(), request.getContentType());
+                Assert.assertEquals(value1, request.getParameter(name1));
+                String[] values = request.getParameterValues(name2);
+                Assert.assertNotNull(values);
+                Assert.assertEquals(2, values.length);
+                Assert.assertThat(values, Matchers.arrayContainingInAnyOrder(value2, value3));
+            }
+        });
+
+        Fields fields = new Fields();
+        fields.put(name1, value1);
+        fields.add(name2, value2);
+        fields.add(name2, value3);
+        ContentResponse response = client.FORM(scheme + "://localhost:" + connector.getLocalPort(), fields);
+
+        Assert.assertEquals(200, response.getStatus());
+    }
+
+    @Test
+    public void testFormContentProviderWithDifferentContentType() throws Exception
+    {
+        final String name1 = "a";
+        final String value1 = "1";
+        final String name2 = "b";
+        final String value2 = "2";
+        Fields fields = new Fields();
+        fields.put(name1, value1);
+        fields.add(name2, value2);
+        final String content = FormContentProvider.convert(fields);
+        final String contentType = "text/plain;charset=UTF-8";
+
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                Assert.assertEquals("POST", request.getMethod());
+                Assert.assertEquals(contentType, request.getContentType());
+                Assert.assertEquals(content, IO.toString(request.getInputStream()));
+            }
+        });
+
+        ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .method(HttpMethod.POST)
+                .content(new FormContentProvider(fields))
+                .header(HttpHeader.CONTENT_TYPE, contentType)
+                .send();
+
+        Assert.assertEquals(200, response.getStatus());
+    }
+
+    @Test
+    public void testTypedContentProviderWithNoContentType() throws Exception
+    {
+        final String content = "data";
+
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                Assert.assertEquals("GET", request.getMethod());
+                Assert.assertNull(request.getContentType());
+                Assert.assertEquals(content, IO.toString(request.getInputStream()));
+            }
+        });
+
+        ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .content(new StringContentProvider(null, content, StandardCharsets.UTF_8))
+                .send();
+
+        Assert.assertEquals(200, response.getStatus());
+    }
+}
diff --git a/jetty-client/src/test/resources/foo.txt b/jetty-client/src/test/resources/foo.txt
deleted file mode 100644
index 58b7882..0000000
--- a/jetty-client/src/test/resources/foo.txt
+++ /dev/null
@@ -1 +0,0 @@
-<html/>
\ No newline at end of file
diff --git a/jetty-client/src/test/resources/jetty-logging.properties b/jetty-client/src/test/resources/jetty-logging.properties
index ac8bcd8..1c19e53 100644
--- a/jetty-client/src/test/resources/jetty-logging.properties
+++ b/jetty-client/src/test/resources/jetty-logging.properties
@@ -1,3 +1,3 @@
-# Setup default logging implementation for during testing
 org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
-org.eclipse.jetty.client.LEVEL=INFO
+#org.eclipse.jetty.LEVEL=DEBUG
+#org.eclipse.jetty.client.LEVEL=DEBUG
diff --git a/jetty-spdy/spdy-jetty-http-webapp/src/main/resources/keystore.jks b/jetty-client/src/test/resources/keystore.jks
similarity index 100%
copy from jetty-spdy/spdy-jetty-http-webapp/src/main/resources/keystore.jks
copy to jetty-client/src/test/resources/keystore.jks
diff --git a/jetty-client/src/test/resources/realm.properties b/jetty-client/src/test/resources/realm.properties
index 6cd8ffa..54ace47 100644
--- a/jetty-client/src/test/resources/realm.properties
+++ b/jetty-client/src/test/resources/realm.properties
@@ -1,22 +1,3 @@
-#
-# This file defines users passwords and roles for a HashUserRealm
-#
-# The format is
-#  <username>: <password>[,<rolename> ...]
-#
-# Passwords may be clear text, obfuscated or checksummed.  The class 
-# org.eclipse.util.Password should be used to generate obfuscated
-# passwords or password checksums
-#
-# If DIGEST Authentication is used, the password must be in a recoverable
-# format, either plain text or OBF:.
-#
-# if using digest authentication, do not MD5-hash the password
-jetty: jetty,user
-admin: CRYPT:ad1ks..kc.1Ug,server-administrator,content-administrator,admin,user
-other: OBF:1xmk1w261u9r1w1c1xmq,user
-plain: plain,user
-user: password,user
-
-# This entry is for digest auth.  The credential is a MD5 hash of username:realmname:password
-digest: MD5:6e120743ad67abfbc385bc2bb754e297,user
+# Format is <user>:<password>,<roles>
+basic:basic
+digest:digest
diff --git a/jetty-spdy/spdy-jetty-http-webapp/src/main/resources/truststore.jks b/jetty-client/src/test/resources/truststore.jks
similarity index 100%
copy from jetty-spdy/spdy-jetty-http-webapp/src/main/resources/truststore.jks
copy to jetty-client/src/test/resources/truststore.jks
diff --git a/jetty-continuation/pom.xml b/jetty-continuation/pom.xml
index 1a6d5a4..d3e1257 100644
--- a/jetty-continuation/pom.xml
+++ b/jetty-continuation/pom.xml
@@ -2,7 +2,7 @@
   <parent>
     <groupId>org.eclipse.jetty</groupId>
     <artifactId>jetty-project</artifactId>
-    <version>8.1.17.v20150415</version>
+    <version>9.2.13.v20150730</version>
   </parent>
   <modelVersion>4.0.0</modelVersion>
   <artifactId>jetty-continuation</artifactId>
@@ -23,11 +23,6 @@
             <goals>
               <goal>manifest</goal>
             </goals>
-            <configuration>
-              <instructions>
-                <Import-Package>javax.servlet.*;version="2.6.0",org.mortbay.log.*;version="[6.1,7)";resolution:=optional,org.mortbay.util.ajax.*;version="[6.1,7)";resolution:=optional,*</Import-Package>
-              </instructions>
-            </configuration>
            </execution>
         </executions>
       </plugin>
@@ -41,15 +36,9 @@
               <goal>jar</goal>
             </goals>
           </execution>
-          <execution>
-            <id>test-jar</id>
-            <goals>
-              <goal>test-jar</goal>
-            </goals>
-          </execution>
         </executions>
         <configuration>
-          <archive>               
+          <archive>
             <manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
           </archive>
         </configuration>
@@ -65,15 +54,8 @@
   </build>
   <dependencies>
     <dependency>
-      <groupId>org.eclipse.jetty.orbit</groupId> 
-      <artifactId>javax.servlet</artifactId>
-      <version>3.0.0.v201112011016</version>
-      <scope>provided</scope>
-    </dependency> 
-    <dependency>
-      <groupId>org.mortbay.jetty</groupId> 
-      <artifactId>jetty-util</artifactId>
-      <version>6.1.26</version>
+      <groupId>javax.servlet</groupId> 
+      <artifactId>javax.servlet-api</artifactId>
       <scope>provided</scope>
     </dependency> 
   </dependencies>
diff --git a/jetty-continuation/src/main/java/org/eclipse/jetty/continuation/Continuation.java b/jetty-continuation/src/main/java/org/eclipse/jetty/continuation/Continuation.java
index fc47d20..0d71b81 100644
--- a/jetty-continuation/src/main/java/org/eclipse/jetty/continuation/Continuation.java
+++ b/jetty-continuation/src/main/java/org/eclipse/jetty/continuation/Continuation.java
@@ -18,12 +18,7 @@
 
 package org.eclipse.jetty.continuation;
 
-import javax.servlet.Filter;
-import javax.servlet.FilterChain;
-import javax.servlet.Servlet;
-import javax.servlet.ServletRequest;
 import javax.servlet.ServletResponse;
-import javax.servlet.ServletResponseWrapper;
 
 /* ------------------------------------------------------------ */
 /**
diff --git a/jetty-continuation/src/main/java/org/eclipse/jetty/continuation/ContinuationFilter.java b/jetty-continuation/src/main/java/org/eclipse/jetty/continuation/ContinuationFilter.java
index 531f6e9..60029b1 100644
--- a/jetty-continuation/src/main/java/org/eclipse/jetty/continuation/ContinuationFilter.java
+++ b/jetty-continuation/src/main/java/org/eclipse/jetty/continuation/ContinuationFilter.java
@@ -34,15 +34,14 @@ import javax.servlet.ServletResponse;
 /**
  * <p>ContinuationFilter must be applied to servlet paths that make use of
  * the asynchronous features provided by {@link Continuation} APIs, but that
- * are deployed in servlet containers that are neither Jetty (>= 7) nor a
+ * are deployed in servlet containers that are a
  * compliant Servlet 3.0 container.</p>
  * <p>The following init parameters may be used to configure the filter (these are mostly for testing):</p>
  * <dl>
  * <dt>debug</dt><dd>Boolean controlling debug output</dd>
- * <dt>jetty6</dt><dd>Boolean to force use of Jetty 6 continuations</dd>
  * <dt>faux</dt><dd>Boolean to force use of faux continuations</dd>
  * </dl>
- * <p>If the servlet container is not Jetty (either 6 or 7) nor a Servlet 3
+ * <p>If the servlet container is not Jetty 7+ nor a Servlet 3
  * container, then "faux" continuations will be used.</p>
  * <p>Faux continuations will just put the thread that called {@link Continuation#suspend()}
  * in wait, and will notify that thread when {@link Continuation#resume()} or
@@ -55,7 +54,6 @@ public class ContinuationFilter implements Filter
     static boolean _initialized;
     static boolean __debug; // shared debug status
     private boolean _faux;
-    private boolean _jetty6;
     private boolean _filtered;
     ServletContext _context;
     private boolean _debug;
@@ -70,25 +68,17 @@ public class ContinuationFilter implements Filter
         if (_debug)
             __debug=true;
 
-        param=filterConfig.getInitParameter("jetty6");
-        if (param==null)
-            param=filterConfig.getInitParameter("partial");
-        if (param!=null)
-            _jetty6=Boolean.parseBoolean(param);
-        else
-            _jetty6=ContinuationSupport.__jetty6 && !jetty_7_or_greater;
-
+        param=filterConfig.getInitParameter("partial");
         param=filterConfig.getInitParameter("faux");
         if (param!=null)
             _faux=Boolean.parseBoolean(param);
         else
-            _faux=!(jetty_7_or_greater || _jetty6 || _context.getMajorVersion()>=3);
+            _faux=!(jetty_7_or_greater || _context.getMajorVersion()>=3);
 
-        _filtered=_faux||_jetty6;
+        _filtered=_faux;
         if (_debug)
             _context.log("ContinuationFilter "+
                     " jetty="+jetty_7_or_greater+
-                    " jetty6="+_jetty6+
                     " faux="+_faux+
                     " filtered="+_filtered+
                     " servlet3="+ContinuationSupport.__servlet3);
diff --git a/jetty-continuation/src/main/java/org/eclipse/jetty/continuation/ContinuationListener.java b/jetty-continuation/src/main/java/org/eclipse/jetty/continuation/ContinuationListener.java
index ae5da35..5d81cf4 100644
--- a/jetty-continuation/src/main/java/org/eclipse/jetty/continuation/ContinuationListener.java
+++ b/jetty-continuation/src/main/java/org/eclipse/jetty/continuation/ContinuationListener.java
@@ -20,33 +20,31 @@ package org.eclipse.jetty.continuation;
 
 import java.util.EventListener;
 
-import javax.servlet.ServletRequestListener;
-
 
 /* ------------------------------------------------------------ */
 /** A Continuation Listener
  * <p>
  * A ContinuationListener may be registered with a call to
  * {@link Continuation#addContinuationListener(ContinuationListener)}.
- * 
+ *
  */
-public interface ContinuationListener extends EventListener 
-{    
+public interface ContinuationListener extends EventListener
+{
     /* ------------------------------------------------------------ */
     /**
      * Called when a continuation life cycle is complete and after
      * any calls to {@link ServletRequestListener#requestDestroyed(javax.servlet.ServletRequestEvent)}
      * The response may still be written to during the call.
-     * 
+     *
      * @param continuation
      */
     public void onComplete(Continuation continuation);
-    
+
     /* ------------------------------------------------------------ */
     /**
      * Called when a suspended continuation has timed out.
-     * The response may be written to and the methods 
-     * {@link Continuation#resume()} or {@link Continuation#complete()} 
+     * The response may be written to and the methods
+     * {@link Continuation#resume()} or {@link Continuation#complete()}
      * may be called by a onTimeout implementation,
      * @param continuation
      */
diff --git a/jetty-continuation/src/main/java/org/eclipse/jetty/continuation/ContinuationSupport.java b/jetty-continuation/src/main/java/org/eclipse/jetty/continuation/ContinuationSupport.java
index 0caefd3..8fa1104 100644
--- a/jetty-continuation/src/main/java/org/eclipse/jetty/continuation/ContinuationSupport.java
+++ b/jetty-continuation/src/main/java/org/eclipse/jetty/continuation/ContinuationSupport.java
@@ -19,24 +19,22 @@
 package org.eclipse.jetty.continuation;
 
 import java.lang.reflect.Constructor;
+
 import javax.servlet.ServletRequest;
 import javax.servlet.ServletRequestWrapper;
 import javax.servlet.ServletResponse;
 
-/* ------------------------------------------------------------ */
-/** ContinuationSupport.
+/** 
+ * ContinuationSupport.
  *
  * Factory class for accessing Continuation instances, which with either be
- * native to the container (jetty >= 6), a servlet 3.0 or a faux continuation.
- *
+ * a servlet 3.0 or a faux continuation.
  */
 public class ContinuationSupport
 {
-    static final boolean __jetty6;
     static final boolean __servlet3;
     static final Class<?> __waitingContinuation;
     static final Constructor<? extends Continuation> __newServlet3Continuation;
-    static final Constructor<? extends Continuation> __newJetty6Continuation;
     static
     {
         boolean servlet3Support=false;
@@ -59,27 +57,6 @@ public class ContinuationSupport
             __newServlet3Continuation=s3cc;
         }
 
-        boolean jetty6Support=false;
-        Constructor<? extends Continuation>j6cc=null;
-        try
-        {
-            Class<?> jetty6ContinuationClass = ContinuationSupport.class.getClassLoader().loadClass("org.mortbay.util.ajax.Continuation");
-            boolean jetty6=jetty6ContinuationClass!=null;
-            if (jetty6)
-            {
-                Class<? extends Continuation> j6c = ContinuationSupport.class.getClassLoader().loadClass("org.eclipse.jetty.continuation.Jetty6Continuation").asSubclass(Continuation.class);
-                j6cc=j6c.getConstructor(ServletRequest.class, jetty6ContinuationClass);
-                jetty6Support=true;
-            }
-        }
-        catch (Exception e)
-        {}
-        finally
-        {
-            __jetty6=jetty6Support;
-            __newJetty6Continuation=j6cc;
-        }
-
         Class<?> waiting=null;
         try
         {
@@ -128,24 +105,6 @@ public class ContinuationSupport
             }
         }
 
-        if (__jetty6)
-        {
-            Object c=request.getAttribute("org.mortbay.jetty.ajax.Continuation");
-            try
-            {
-                if (c==null || __waitingContinuation==null || __waitingContinuation.isInstance(c))
-                    continuation=new FauxContinuation(request);
-                else
-                    continuation= __newJetty6Continuation.newInstance(request,c);
-                request.setAttribute(Continuation.ATTRIBUTE,continuation);
-                return continuation;
-            }
-            catch(Exception e)
-            {
-                throw new RuntimeException(e);
-            }
-        }
-
         throw new IllegalStateException("!(Jetty || Servlet 3.0 || ContinuationFilter)");
     }
 
diff --git a/jetty-continuation/src/main/java/org/eclipse/jetty/continuation/FauxContinuation.java b/jetty-continuation/src/main/java/org/eclipse/jetty/continuation/FauxContinuation.java
index 00f5e08..7ba51e7 100644
--- a/jetty-continuation/src/main/java/org/eclipse/jetty/continuation/FauxContinuation.java
+++ b/jetty-continuation/src/main/java/org/eclipse/jetty/continuation/FauxContinuation.java
@@ -32,14 +32,14 @@ import org.eclipse.jetty.continuation.ContinuationFilter.FilteredContinuation;
 /**
  * A blocking implementation of Continuation.
  * This implementation of Continuation is used by the {@link ContinuationFilter}
- * when there are is no native or asynchronous continuation type available. 
+ * when there are is no native or asynchronous continuation type available.
  */
 class FauxContinuation implements FilteredContinuation
 {
-    // common exception used for all continuations.  
+    // common exception used for all continuations.
     // Turn on debug in ContinuationFilter to see real stack trace.
     private final static ContinuationThrowable __exception = new ContinuationThrowable();
-    
+
     private static final int __HANDLING=1;   // Request dispatched to filter/servlet
     private static final int __SUSPENDING=2;   // Suspend called, but not yet returned to container
     private static final int __RESUMING=3;     // resumed while suspending
@@ -50,15 +50,15 @@ class FauxContinuation implements FilteredContinuation
 
     private final ServletRequest _request;
     private ServletResponse _response;
-    
+
     private int _state=__HANDLING;
     private boolean _initial=true;
     private boolean _resumed=false;
     private boolean _timeout=false;
     private boolean _responseWrapped=false;
-    private  long _timeoutMs=30000; // TODO configure
-    
-    private ArrayList<ContinuationListener> _listeners; 
+    private long _timeoutMs=30000;
+
+    private ArrayList<ContinuationListener> _listeners;
 
     FauxContinuation(final ServletRequest request)
     {
@@ -72,7 +72,7 @@ class FauxContinuation implements FilteredContinuation
             for (ContinuationListener l:_listeners)
                 l.onComplete(this);
     }
-    
+
     /* ------------------------------------------------------------ */
     public void onTimeout()
     {
@@ -85,12 +85,14 @@ class FauxContinuation implements FilteredContinuation
     /**
      * @see org.eclipse.jetty.continuation.Continuation#isResponseWrapped()
      */
+    @Override
     public boolean isResponseWrapped()
     {
         return _responseWrapped;
     }
 
     /* ------------------------------------------------------------ */
+    @Override
     public boolean isInitial()
     {
         synchronized(this)
@@ -100,6 +102,7 @@ class FauxContinuation implements FilteredContinuation
     }
 
     /* ------------------------------------------------------------ */
+    @Override
     public boolean isResumed()
     {
         synchronized(this)
@@ -109,6 +112,7 @@ class FauxContinuation implements FilteredContinuation
     }
 
     /* ------------------------------------------------------------ */
+    @Override
     public boolean isSuspended()
     {
         synchronized(this)
@@ -124,12 +128,13 @@ class FauxContinuation implements FilteredContinuation
                     return true;
                 case __UNSUSPENDING:
                 default:
-                    return false;   
+                    return false;
             }
         }
     }
 
     /* ------------------------------------------------------------ */
+    @Override
     public boolean isExpired()
     {
         synchronized(this)
@@ -139,20 +144,23 @@ class FauxContinuation implements FilteredContinuation
     }
 
     /* ------------------------------------------------------------ */
+    @Override
     public void setTimeout(long timeoutMs)
     {
         _timeoutMs = timeoutMs;
     }
 
     /* ------------------------------------------------------------ */
+    @Override
     public void suspend(ServletResponse response)
     {
         _response=response;
         _responseWrapped=response instanceof ServletResponseWrapper;
         suspend();
     }
-    
+
     /* ------------------------------------------------------------ */
+    @Override
     public void suspend()
     {
         synchronized (this)
@@ -186,6 +194,7 @@ class FauxContinuation implements FilteredContinuation
     /* (non-Javadoc)
      * @see org.mortbay.jetty.Suspendor#resume()
      */
+    @Override
     public void resume()
     {
         synchronized (this)
@@ -195,7 +204,7 @@ class FauxContinuation implements FilteredContinuation
                 case __HANDLING:
                     _resumed=true;
                     return;
-                    
+
                 case __SUSPENDING:
                     _resumed=true;
                     _state=__RESUMING;
@@ -204,26 +213,27 @@ class FauxContinuation implements FilteredContinuation
                 case __RESUMING:
                 case __COMPLETING:
                     return;
-                    
+
                 case __SUSPENDED:
                     fauxResume();
                     _resumed=true;
                     _state=__UNSUSPENDING;
                     break;
-                    
+
                 case __UNSUSPENDING:
                     _resumed=true;
                     return;
-                    
+
                 default:
                     throw new IllegalStateException(this.getStatusString());
             }
         }
-        
+
     }
-    
+
 
     /* ------------------------------------------------------------ */
+    @Override
     public void complete()
     {
         // just like resume, except don't set _resumed=true;
@@ -233,25 +243,25 @@ class FauxContinuation implements FilteredContinuation
             {
                 case __HANDLING:
                     throw new IllegalStateException(this.getStatusString());
-                    
+
                 case __SUSPENDING:
                     _state=__COMPLETING;
                     break;
-                    
+
                 case __RESUMING:
                     break;
 
                 case __COMPLETING:
                     return;
-                    
+
                 case __SUSPENDED:
                     _state=__COMPLETING;
                     fauxResume();
                     break;
-                    
+
                 case __UNSUSPENDING:
                     return;
-                    
+
                 default:
                     throw new IllegalStateException(this.getStatusString());
             }
@@ -262,6 +272,7 @@ class FauxContinuation implements FilteredContinuation
     /**
      * @see org.eclipse.jetty.continuation.Continuation#getServletResponse()
      */
+    @Override
     public boolean enter(ServletResponse response)
     {
         _response=response;
@@ -272,11 +283,12 @@ class FauxContinuation implements FilteredContinuation
     /**
      * @see org.eclipse.jetty.continuation.Continuation#getServletResponse()
      */
+    @Override
     public ServletResponse getServletResponse()
     {
         return _response;
     }
-    
+
 
     /* ------------------------------------------------------------ */
     void handling()
@@ -298,6 +310,9 @@ class FauxContinuation implements FilteredContinuation
 
                 case __SUSPENDED:
                     fauxResume();
+                    _state=__HANDLING;
+                    return;
+                    
                 case __UNSUSPENDING:
                     _state=__HANDLING;
                     return;
@@ -313,6 +328,7 @@ class FauxContinuation implements FilteredContinuation
     /**
      * @return true if handling is complete
      */
+    @Override
     public boolean exit()
     {
         synchronized (this)
@@ -333,15 +349,15 @@ class FauxContinuation implements FilteredContinuation
                         onComplete();
                         return true;
                     }
-                    
+
                     _initial=false;
                     _state=__HANDLING;
-                    return false; 
+                    return false;
 
                 case __RESUMING:
                     _initial=false;
                     _state=__HANDLING;
-                    return false; 
+                    return false;
 
                 case __COMPLETING:
                     _initial=false;
@@ -366,37 +382,37 @@ class FauxContinuation implements FilteredContinuation
         {
             _timeout=true;
         }
-        
+
         onTimeout();
-        
+
         synchronized (this)
         {
             switch(_state)
             {
                 case __HANDLING:
                     return;
-                    
+
                 case __SUSPENDING:
                     _timeout=true;
                     _state=__RESUMING;
                     fauxResume();
                     return;
-                    
+
                 case __RESUMING:
                     return;
-                    
+
                 case __COMPLETING:
                     return;
-                    
+
                 case __SUSPENDED:
                     _timeout=true;
                     _state=__UNSUSPENDING;
                     break;
-                    
+
                 case __UNSUSPENDING:
                     _timeout=true;
                     return;
-                    
+
                 default:
                     throw new IllegalStateException(this.getStatusString());
             }
@@ -423,19 +439,19 @@ class FauxContinuation implements FilteredContinuation
         if (_timeoutMs>0 && wait<=0)
             expire();
     }
-    
+
     private void fauxResume()
     {
         _timeoutMs=0;
         this.notifyAll();
     }
-    
+
     @Override
     public String toString()
     {
         return getStatusString();
     }
-    
+
     String getStatusString()
     {
         synchronized (this)
@@ -454,19 +470,21 @@ class FauxContinuation implements FilteredContinuation
         }
     }
 
-    
+
+    @Override
     public void addContinuationListener(ContinuationListener listener)
     {
         if (_listeners==null)
             _listeners=new ArrayList<ContinuationListener>();
         _listeners.add(listener);
-        
+
     }
 
     /* ------------------------------------------------------------ */
     /**
      * @see org.eclipse.jetty.continuation.Continuation#getAttribute(java.lang.String)
      */
+    @Override
     public Object getAttribute(String name)
     {
         return _request.getAttribute(name);
@@ -476,6 +494,7 @@ class FauxContinuation implements FilteredContinuation
     /**
      * @see org.eclipse.jetty.continuation.Continuation#removeAttribute(java.lang.String)
      */
+    @Override
     public void removeAttribute(String name)
     {
         _request.removeAttribute(name);
@@ -485,6 +504,7 @@ class FauxContinuation implements FilteredContinuation
     /**
      * @see org.eclipse.jetty.continuation.Continuation#setAttribute(java.lang.String, java.lang.Object)
      */
+    @Override
     public void setAttribute(String name, Object attribute)
     {
         _request.setAttribute(name,attribute);
@@ -494,6 +514,7 @@ class FauxContinuation implements FilteredContinuation
     /**
      * @see org.eclipse.jetty.continuation.Continuation#undispatch()
      */
+    @Override
     public void undispatch()
     {
         if (isSuspended())
@@ -503,6 +524,6 @@ class FauxContinuation implements FilteredContinuation
             throw __exception;
         }
         throw new IllegalStateException("!suspended");
-        
+
     }
 }
diff --git a/jetty-continuation/src/main/java/org/eclipse/jetty/continuation/Jetty6Continuation.java b/jetty-continuation/src/main/java/org/eclipse/jetty/continuation/Jetty6Continuation.java
deleted file mode 100644
index 9bba385..0000000
--- a/jetty-continuation/src/main/java/org/eclipse/jetty/continuation/Jetty6Continuation.java
+++ /dev/null
@@ -1,267 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.continuation;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
-import javax.servlet.ServletResponseWrapper;
-
-import org.mortbay.log.Log;
-import org.mortbay.log.Logger;
-
-/* ------------------------------------------------------------ */
-/**
- * This implementation of Continuation is used by {@link ContinuationSupport}
- * when it detects that the application is deployed in a jetty-6 server.
- * This continuation requires the {@link ContinuationFilter} to be deployed.
- */
-public class Jetty6Continuation implements ContinuationFilter.FilteredContinuation
-{
-    private static final Logger LOG = Log.getLogger(Jetty6Continuation.class.getName());
-
-    // Exception reused for all continuations
-    // Turn on debug in ContinuationFilter to see real stack trace.
-    private final static ContinuationThrowable __exception = new ContinuationThrowable();
-
-    private final ServletRequest _request;
-    private ServletResponse _response;
-    private final org.mortbay.util.ajax.Continuation _j6Continuation;
-
-    private Throwable _retry;
-    private int _timeout;
-    private boolean _initial=true;
-    private volatile boolean _completed=false;
-    private volatile boolean _resumed=false;
-    private volatile boolean _expired=false;
-    private boolean _responseWrapped=false;
-    private List<ContinuationListener> _listeners;
-
-    public Jetty6Continuation(ServletRequest request, org.mortbay.util.ajax.Continuation continuation)
-    {
-        if (!ContinuationFilter._initialized)
-        {
-            LOG.warn("!ContinuationFilter installed",null,null);
-            throw new IllegalStateException("!ContinuationFilter installed");
-        }
-        _request=request;
-        _j6Continuation=continuation;
-    }
-
-    public void addContinuationListener(final ContinuationListener listener)
-    {
-        if (_listeners==null)
-            _listeners=new ArrayList<ContinuationListener>();
-        _listeners.add(listener);
-    }
-
-    public void complete()
-    {
-        synchronized(this)
-        {
-            if (_resumed)
-                throw new IllegalStateException();
-            _completed=true;
-            if (_j6Continuation.isPending())
-                _j6Continuation.resume();
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.continuation.Continuation#getAttribute(java.lang.String)
-     */
-    public Object getAttribute(String name)
-    {
-        return _request.getAttribute(name);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.continuation.Continuation#removeAttribute(java.lang.String)
-     */
-    public void removeAttribute(String name)
-    {
-        _request.removeAttribute(name);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.continuation.Continuation#setAttribute(java.lang.String, java.lang.Object)
-     */
-    public void setAttribute(String name, Object attribute)
-    {
-        _request.setAttribute(name,attribute);
-    }
-
-    /* ------------------------------------------------------------ */
-    public ServletResponse getServletResponse()
-    {
-        return _response;
-    }
-
-    /* ------------------------------------------------------------ */
-    public boolean isExpired()
-    {
-        return _expired;
-    }
-
-    /* ------------------------------------------------------------ */
-    public boolean isInitial()
-    {
-        return _initial;
-    }
-
-    /* ------------------------------------------------------------ */
-    public boolean isResumed()
-    {
-        return _resumed;
-    }
-
-    /* ------------------------------------------------------------ */
-    public boolean isSuspended()
-    {
-        return _retry!=null;
-    }
-
-    /* ------------------------------------------------------------ */
-    public void resume()
-    {
-        synchronized(this)
-        {
-            if (_completed)
-                throw new IllegalStateException();
-            _resumed=true;
-            if (_j6Continuation.isPending())
-                _j6Continuation.resume();
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    public void setTimeout(long timeoutMs)
-    {
-        _timeout=(timeoutMs>Integer.MAX_VALUE)?Integer.MAX_VALUE:(int)timeoutMs;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.continuation.Continuation#suspend(javax.servlet.ServletResponse)
-     */
-    public void suspend(ServletResponse response)
-    {
-        try
-        {
-            _response=response;
-            _responseWrapped=_response instanceof ServletResponseWrapper;
-            _resumed=false;
-            _expired=false;
-            _completed=false;
-            _j6Continuation.suspend(_timeout);
-        }
-        catch(Throwable retry)
-        {
-            _retry=retry;
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    public void suspend()
-    {
-        try
-        {
-            _response=null;
-            _responseWrapped=false;
-            _resumed=false;
-            _expired=false;
-            _completed=false;
-            _j6Continuation.suspend(_timeout);
-        }
-        catch(Throwable retry)
-        {
-            _retry=retry;
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    public boolean isResponseWrapped()
-    {
-        return _responseWrapped;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.continuation.Continuation#undispatch()
-     */
-    public void undispatch()
-    {
-        if (isSuspended())
-        {
-            if (ContinuationFilter.__debug)
-                throw new ContinuationThrowable();
-            throw __exception;
-        }
-        throw new IllegalStateException("!suspended");
-    }
-
-    /* ------------------------------------------------------------ */
-    public boolean enter(ServletResponse response)
-    {
-        _response=response;
-        _expired=!_j6Continuation.isResumed();
-
-        if (_initial)
-            return true;
-
-        _j6Continuation.reset();
-
-        if (_expired)
-        {
-            if (_listeners!=null)
-            {
-                for (ContinuationListener l: _listeners)
-                    l.onTimeout(this);
-            }
-        }
-
-        return !_completed;
-    }
-
-    /* ------------------------------------------------------------ */
-    public boolean exit()
-    {
-        _initial=false;
-
-        Throwable th=_retry;
-        _retry=null;
-        if (th instanceof Error)
-            throw (Error)th;
-        if (th instanceof RuntimeException)
-            throw (RuntimeException)th;
-
-        if (_listeners!=null)
-        {
-            for (ContinuationListener l: _listeners)
-                l.onComplete(this);
-        }
-
-        return true;
-    }
-}
diff --git a/jetty-continuation/src/main/java/org/eclipse/jetty/continuation/Servlet3Continuation.java b/jetty-continuation/src/main/java/org/eclipse/jetty/continuation/Servlet3Continuation.java
index 6fdb3a7..fd62bd4 100644
--- a/jetty-continuation/src/main/java/org/eclipse/jetty/continuation/Servlet3Continuation.java
+++ b/jetty-continuation/src/main/java/org/eclipse/jetty/continuation/Servlet3Continuation.java
@@ -21,6 +21,7 @@ package org.eclipse.jetty.continuation;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
+
 import javax.servlet.AsyncContext;
 import javax.servlet.AsyncEvent;
 import javax.servlet.AsyncListener;
@@ -33,10 +34,10 @@ import javax.servlet.ServletResponseWrapper;
 /* ------------------------------------------------------------ */
 /**
  * This implementation of Continuation is used by {@link ContinuationSupport}
- * when it detects that the application has been deployed in a non-jetty Servlet 3
+ * when it detects that the application has been deployed in a Servlet 3
  * server.
  */
-public class Servlet3Continuation implements Continuation
+public class Servlet3Continuation implements Continuation, AsyncListener
 {
     // Exception reused for all continuations
     // Turn on debug in ContinuationFilter to see real stack trace.
@@ -45,7 +46,7 @@ public class Servlet3Continuation implements Continuation
     private final ServletRequest _request;
     private ServletResponse _response;
     private AsyncContext _context;
-    private List<AsyncListener> _listeners=new ArrayList<AsyncListener>();
+    private final List<ContinuationListener> _listeners=new ArrayList<ContinuationListener>();
     private volatile boolean _initial=true;
     private volatile boolean _resumed=false;
     private volatile boolean _expired=false;
@@ -57,64 +58,17 @@ public class Servlet3Continuation implements Continuation
     public Servlet3Continuation(ServletRequest request)
     {
         _request=request;
-
-        _listeners.add(new AsyncListener()
-        {
-            public void onComplete(AsyncEvent event) throws IOException
-            {
-            }
-
-            public void onError(AsyncEvent event) throws IOException
-            {
-            }
-
-            public void onStartAsync(AsyncEvent event) throws IOException
-            {
-                event.getAsyncContext().addListener(this);
-            }
-
-            public void onTimeout(AsyncEvent event) throws IOException
-            {
-                _initial=false;
-                event.getAsyncContext().dispatch();
-            }
-        });
     }
 
     /* ------------------------------------------------------------ */
+    @Override
     public void addContinuationListener(final ContinuationListener listener)
     {
-        AsyncListener wrapped = new AsyncListener()
-        {
-            public void onComplete(final AsyncEvent event) throws IOException
-            {
-                listener.onComplete(Servlet3Continuation.this);
-            }
-
-            public void onError(AsyncEvent event) throws IOException
-            {
-                listener.onComplete(Servlet3Continuation.this);
-            }
-
-            public void onStartAsync(AsyncEvent event) throws IOException
-            {
-                event.getAsyncContext().addListener(this);
-            }
-
-            public void onTimeout(AsyncEvent event) throws IOException
-            {
-                _expired=true;
-                listener.onTimeout(Servlet3Continuation.this);
-            }
-        };
-
-        if (_context!=null)
-            _context.addListener(wrapped);
-        else
-            _listeners.add(wrapped);
+        _listeners.add(listener);
     }
 
     /* ------------------------------------------------------------ */
+    @Override
     public void complete()
     {
         AsyncContext context=_context;
@@ -124,34 +78,48 @@ public class Servlet3Continuation implements Continuation
     }
 
     /* ------------------------------------------------------------ */
+    @Override
     public ServletResponse getServletResponse()
     {
         return _response;
     }
 
     /* ------------------------------------------------------------ */
+    @Override
     public boolean isExpired()
     {
         return _expired;
     }
 
     /* ------------------------------------------------------------ */
+    @Override
     public boolean isInitial()
     {
-        // TODO - this is not perfect if non continuation API is used directly
         return _initial&&_request.getDispatcherType()!=DispatcherType.ASYNC;
     }
 
     /* ------------------------------------------------------------ */
+    @Override
     public boolean isResumed()
     {
         return _resumed;
     }
 
     /* ------------------------------------------------------------ */
+    @Override
     public boolean isSuspended()
     {
-        return _request.isAsyncStarted();
+        if (_request.isAsyncStarted())
+            return true;
+        try
+        {
+            return _request.getAsyncContext()!=null;
+        }
+        catch(IllegalStateException e)
+        {
+            // ignored
+        }
+        return false;
     }
 
     /* ------------------------------------------------------------ */
@@ -161,6 +129,7 @@ public class Servlet3Continuation implements Continuation
     }
 
     /* ------------------------------------------------------------ */
+    @Override
     public void resume()
     {
         AsyncContext context=_context;
@@ -171,6 +140,7 @@ public class Servlet3Continuation implements Continuation
     }
 
     /* ------------------------------------------------------------ */
+    @Override
     public void setTimeout(long timeoutMs)
     {
         _timeoutMs=timeoutMs;
@@ -179,6 +149,7 @@ public class Servlet3Continuation implements Continuation
     }
 
     /* ------------------------------------------------------------ */
+    @Override
     public void suspend(ServletResponse response)
     {
         _response=response;
@@ -187,26 +158,22 @@ public class Servlet3Continuation implements Continuation
         _expired=false;
         _context=_request.startAsync();
         _context.setTimeout(_timeoutMs);
-
-        for (AsyncListener listener:_listeners)
-            _context.addListener(listener);
-        _listeners.clear();
+        _context.addListener(this);
     }
 
     /* ------------------------------------------------------------ */
+    @Override
     public void suspend()
     {
         _resumed=false;
         _expired=false;
         _context=_request.startAsync();
         _context.setTimeout(_timeoutMs);
-
-        for (AsyncListener listener:_listeners)
-            _context.addListener(listener);
-        _listeners.clear();
+        _context.addListener(this);
     }
 
     /* ------------------------------------------------------------ */
+    @Override
     public boolean isResponseWrapped()
     {
         return _responseWrapped;
@@ -216,6 +183,7 @@ public class Servlet3Continuation implements Continuation
     /**
      * @see org.eclipse.jetty.continuation.Continuation#getAttribute(java.lang.String)
      */
+    @Override
     public Object getAttribute(String name)
     {
         return _request.getAttribute(name);
@@ -225,6 +193,7 @@ public class Servlet3Continuation implements Continuation
     /**
      * @see org.eclipse.jetty.continuation.Continuation#removeAttribute(java.lang.String)
      */
+    @Override
     public void removeAttribute(String name)
     {
         _request.removeAttribute(name);
@@ -234,6 +203,7 @@ public class Servlet3Continuation implements Continuation
     /**
      * @see org.eclipse.jetty.continuation.Continuation#setAttribute(java.lang.String, java.lang.Object)
      */
+    @Override
     public void setAttribute(String name, Object attribute)
     {
         _request.setAttribute(name,attribute);
@@ -243,6 +213,7 @@ public class Servlet3Continuation implements Continuation
     /**
      * @see org.eclipse.jetty.continuation.Continuation#undispatch()
      */
+    @Override
     public void undispatch()
     {
         if (isSuspended())
@@ -253,4 +224,38 @@ public class Servlet3Continuation implements Continuation
         }
         throw new IllegalStateException("!suspended");
     }
+    
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void onComplete(AsyncEvent event) throws IOException
+    {
+        for (ContinuationListener listener:_listeners)
+            listener.onComplete(this);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void onError(AsyncEvent event) throws IOException
+    {
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void onStartAsync(AsyncEvent event) throws IOException
+    {
+        _initial=false;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void onTimeout(AsyncEvent event) throws IOException
+    {
+        _initial=false;
+        _expired=true;
+        for (ContinuationListener listener:_listeners)
+            listener.onTimeout(this);
+        if (event.getSuppliedRequest().isAsyncStarted())
+            event.getAsyncContext().dispatch();
+    }
 }
diff --git a/jetty-continuation/src/main/java/org/eclipse/jetty/continuation/package-info.java b/jetty-continuation/src/main/java/org/eclipse/jetty/continuation/package-info.java
new file mode 100644
index 0000000..3514552
--- /dev/null
+++ b/jetty-continuation/src/main/java/org/eclipse/jetty/continuation/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Continuation : Generic Async Servlet Method
+ */
+package org.eclipse.jetty.continuation;
+
diff --git a/jetty-deploy/pom.xml b/jetty-deploy/pom.xml
index 4a1ab7e..5d90d59 100644
--- a/jetty-deploy/pom.xml
+++ b/jetty-deploy/pom.xml
@@ -2,7 +2,7 @@
   <parent>
     <groupId>org.eclipse.jetty</groupId>
     <artifactId>jetty-project</artifactId>
-    <version>8.1.17.v20150415</version>
+    <version>9.2.13.v20150730</version>
   </parent>
   <modelVersion>4.0.0</modelVersion>
   <artifactId>jetty-deploy</artifactId>
@@ -25,7 +25,8 @@
             </goals>
             <configuration>
               <instructions>
-                <Import-Package>org.eclipse.jetty.jmx.*;version="8.0";resolution:=optional,*</Import-Package>
+                <Import-Package>org.eclipse.jetty.jmx.*;version="9.1";resolution:=optional,*</Import-Package>
+                <_nouses>true</_nouses>
               </instructions>
             </configuration>
            </execution>
@@ -91,11 +92,5 @@
       <version>${project.version}</version>
       <optional>true</optional>
     </dependency>
-    <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-websocket</artifactId>
-      <version>${project.version}</version>
-      <scope>test</scope>
-    </dependency>
   </dependencies>
 </project>
diff --git a/jetty-deploy/src/main/config/etc/jetty-contexts.xml b/jetty-deploy/src/main/config/etc/jetty-contexts.xml
deleted file mode 100644
index caa7270..0000000
--- a/jetty-deploy/src/main/config/etc/jetty-contexts.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0"?>
-<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
-
-<!-- =============================================================== -->
-<!-- Add a ContextProvider to the deployment manager                 -->
-<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
-<!-- This scans the webapps directory for war files and directories  -->
-<!-- to deploy.                                                      -->
-<!-- This configuration must be used with jetty-deploy.xml, which    -->
-<!-- creates the deployment manager instance                         -->
-<!-- =============================================================== -->
-<Configure id="Server" class="org.eclipse.jetty.server.Server">
-        <Ref id="DeploymentManager">
-          <Call name="addAppProvider">
-            <Arg>
-              <New class="org.eclipse.jetty.deploy.providers.ContextProvider">
-                <Set name="monitoredDirName"><Property name="jetty.home" default="." />/contexts</Set>
-                <Set name="scanInterval">1</Set>
-              </New>
-            </Arg>
-          </Call>
-        </Ref>
-</Configure>
diff --git a/jetty-deploy/src/main/config/etc/jetty-deploy.xml b/jetty-deploy/src/main/config/etc/jetty-deploy.xml
index 1b3fd66..6dfc7e5 100644
--- a/jetty-deploy/src/main/config/etc/jetty-deploy.xml
+++ b/jetty-deploy/src/main/config/etc/jetty-deploy.xml
@@ -1,48 +1,62 @@
 <?xml version="1.0"?>
-<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
 
 <!-- =============================================================== -->
 <!-- Create the deployment manager                                   -->
 <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
 <!-- The deplyment manager handles the lifecycle of deploying web    -->
 <!-- applications. Apps are provided by instances of the             -->
-<!-- AppProvider interface.  Typically these are provided by         -->
-<!-- one or more of:                                                 -->
-<!--   jetty-webapps.xml       - monitors webapps for wars and dirs  -->
-<!--   jetty-contexts.xml      - monitors contexts for context xml   -->
-<!--   jetty-templates.xml     - monitors contexts and templates     -->
+<!-- AppProvider interface.                                          -->
 <!-- =============================================================== -->
 <Configure id="Server" class="org.eclipse.jetty.server.Server">
 
-    <Call name="addBean">
-      <Arg>
-        <New id="DeploymentManager" class="org.eclipse.jetty.deploy.DeploymentManager">
-          <Set name="contexts">
-            <Ref id="Contexts" />
-          </Set>
-          <Call name="setContextAttribute">
-            <Arg>org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern</Arg>
-            <Arg>.*/servlet-api-[^/]*\.jar$</Arg>
-          </Call>
-          
-          
-          <!-- Add a customize step to the deployment lifecycle -->
-          <!-- uncomment and replace DebugBinding with your extended AppLifeCycle.Binding class 
-          <Call name="insertLifeCycleNode">
-            <Arg>deployed</Arg>
-            <Arg>starting</Arg>
-            <Arg>customise</Arg>
-          </Call>
-          <Call name="addLifeCycleBinding">
-            <Arg>
-              <New class="org.eclipse.jetty.deploy.bindings.DebugBinding">
-                <Arg>customise</Arg>
-              </New>
-            </Arg>
-          </Call>
-          -->
-          
-        </New>
-      </Arg>
-    </Call>
+  <Call name="addBean">
+    <Arg>
+      <New id="DeploymentManager" class="org.eclipse.jetty.deploy.DeploymentManager">
+        <Set name="contexts">
+          <Ref refid="Contexts" />
+        </Set>
+        <Call name="setContextAttribute">
+          <Arg>org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern</Arg>
+          <Arg>.*/[^/]*servlet-api-[^/]*\.jar$|.*/javax.servlet.jsp.jstl-.*\.jar$|.*/org.apache.taglibs.taglibs-standard-impl-.*\.jar$</Arg>
+        </Call>
+
+        <!-- Add a customize step to the deployment lifecycle -->
+        <!-- uncomment and replace DebugBinding with your extended AppLifeCycle.Binding class
+        <Call name="insertLifeCycleNode">
+          <Arg>deployed</Arg>
+          <Arg>starting</Arg>
+          <Arg>customise</Arg>
+        </Call>
+        <Call name="addLifeCycleBinding">
+          <Arg>
+            <New class="org.eclipse.jetty.deploy.bindings.DebugBinding">
+              <Arg>customise</Arg>
+            </New>
+          </Arg>
+        </Call> -->
+
+        <Call id="webappprovider" name="addAppProvider">
+          <Arg>
+            <New class="org.eclipse.jetty.deploy.providers.WebAppProvider">
+              <Set name="monitoredDirName"><Property name="jetty.base" default="." />/<Property name="jetty.deploy.monitoredDirName" default="webapps"/></Set>
+              <Set name="defaultsDescriptor"><Property name="jetty.home" default="." />/etc/webdefault.xml</Set>
+              <Set name="scanInterval"><Property name="jetty.deploy.scanInterval" default="1"/></Set>
+              <Set name="extractWars"><Property name="jetty.deploy.extractWars" default="true"/></Set>
+              <Set name="configurationManager">
+                <New class="org.eclipse.jetty.deploy.PropertiesConfigurationManager">
+                  <!-- file of context configuration properties
+                  <Set name="file"><SystemProperty name="jetty.base"/>/etc/some.properties</Set>
+                  -->
+                  <!-- set a context configuration property
+                  <Call name="put"><Arg>name</Arg><Arg>value</Arg></Call>
+                  -->
+                </New>
+              </Set>
+            </New>
+          </Arg>
+        </Call>
+      </New>
+    </Arg>
+  </Call>
 </Configure>
diff --git a/jetty-deploy/src/main/config/etc/jetty-webapps.xml b/jetty-deploy/src/main/config/etc/jetty-webapps.xml
deleted file mode 100644
index 1071ae5..0000000
--- a/jetty-deploy/src/main/config/etc/jetty-webapps.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0"?>
-<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
-
-<!-- =============================================================== -->
-<!-- Add a WebAppProvider to the deployment manager                  -->
-<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
-<!-- This scans the webapps directory for war files and directories  -->
-<!-- to deploy.                                                      -->
-<!-- This configuration must be used with jetty-deploy.xml, which    -->
-<!-- creates the deployment manager instance                         -->
-<!-- =============================================================== -->
-<Configure id="Server" class="org.eclipse.jetty.server.Server">
-    <Ref id="DeploymentManager">
-          <Call id="webappprovider" name="addAppProvider">
-            <Arg>
-              <New class="org.eclipse.jetty.deploy.providers.WebAppProvider">
-                <Set name="monitoredDirName"><Property name="jetty.home" default="." />/webapps</Set>
-                <Set name="defaultsDescriptor"><Property name="jetty.home" default="."/>/etc/webdefault.xml</Set>
-                <Set name="scanInterval">1</Set>
-                <Set name="contextXmlDir"><Property name="jetty.home" default="." />/contexts</Set>
-		<Set name="extractWars">true</Set>
-              </New>
-            </Arg>
-          </Call>
-    </Ref>
-</Configure>
diff --git a/jetty-deploy/src/main/config/modules/deploy.mod b/jetty-deploy/src/main/config/modules/deploy.mod
new file mode 100644
index 0000000..f16b3f2
--- /dev/null
+++ b/jetty-deploy/src/main/config/modules/deploy.mod
@@ -0,0 +1,21 @@
+#
+# Deploy Feature
+#
+
+[depend]
+webapp
+
+[lib]
+lib/jetty-deploy-${jetty.version}.jar
+
+[files]
+webapps/
+
+[xml]
+etc/jetty-deploy.xml
+
+[ini-template]
+## DeployManager configuration
+# Monitored Directory name (relative to jetty.base)
+# jetty.deploy.monitoredDirName=webapps
+
diff --git a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/App.java b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/App.java
index b076f89..64395e1 100644
--- a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/App.java
+++ b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/App.java
@@ -83,6 +83,7 @@ public class App
         return _provider;
     }
 
+    /* ------------------------------------------------------------ */
     /**
      * Get ContextHandler for the App.
      * 
diff --git a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/AppProvider.java b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/AppProvider.java
index b4097b2..a60b32e 100644
--- a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/AppProvider.java
+++ b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/AppProvider.java
@@ -18,8 +18,6 @@
 
 package org.eclipse.jetty.deploy;
 
-import java.io.IOException;
-
 import org.eclipse.jetty.server.handler.ContextHandler;
 import org.eclipse.jetty.util.component.LifeCycle;
 
diff --git a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/ContextDeployer.java b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/ContextDeployer.java
deleted file mode 100644
index 5390ed5..0000000
--- a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/ContextDeployer.java
+++ /dev/null
@@ -1,473 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.deploy;
-
-import java.io.File;
-import java.io.FilenameFilter;
-import java.util.HashMap;
-import java.util.Map;
-
-import org.eclipse.jetty.deploy.providers.ContextProvider;
-import org.eclipse.jetty.deploy.providers.ScanningAppProvider;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.handler.ContextHandler;
-import org.eclipse.jetty.server.handler.ContextHandlerCollection;
-import org.eclipse.jetty.util.AttributesMap;
-import org.eclipse.jetty.util.Scanner;
-import org.eclipse.jetty.util.component.AbstractLifeCycle;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.util.resource.Resource;
-import org.eclipse.jetty.xml.XmlConfiguration;
-
-/**
- * Legacy Context Deployer.
- * 
- * <p>
- * Note: The WebAppDeployer is being phased out of Jetty in favor of the {@link DeploymentManager} and
- * {@link org.eclipse.jetty.deploy.providers.ContextProvider} implementation.
- * 
- * <p>
- * This deployer scans a designated directory by {@link #setConfigurationDir(String)} for the appearance/disappearance
- * or changes to xml configuration files. The scan is performed at startup and at an optional hot deployment frequency
- * specified by {@link #setScanInterval(int)}. By default, the scanning is NOT recursive, but can be made so by
- * {@link #setRecursive(boolean)}.
- * 
- * <p>
- * Each configuration file is in {@link XmlConfiguration} format and represents the configuration of a instance of
- * {@link ContextHandler} (or a subclass specified by the XML <code>Configure</code> element).
- * 
- * <p>
- * The xml should configure the context and the instance is deployed to the {@link ContextHandlerCollection} specified
- * by {@link Server#setHandler(org.eclipse.jetty.server.Handler)}.
- * 
- * <p>
- * Similarly, when one of these existing files is removed, the corresponding context is undeployed; when one of these
- * files is changed, the corresponding context is undeployed, the (changed) xml config file reapplied to it, and then
- * (re)deployed.
- * 
- * <p>
- * Note that the context itself is NOT copied into the hot deploy directory. The webapp directory or war file can exist
- * anywhere. It is the xml config file that points to it's location and deploys it from there.
- * 
- * <p>
- * It means, for example, that you can keep a "read-only" copy of your webapp somewhere, and apply different
- * configurations to it simply by dropping different xml configuration files into the configuration directory.
- * 
- * @see DeploymentManager
- * @see ScanningAppProvider
- * 
- * @org.apache.xbean.XBean element="hotDeployer" description="Creates a hot deployer to watch a directory for changes at
- *                         a configurable interval."
- * @deprecated replaced with {@link ContextProvider} from the {@link DeploymentManager}
- */
- at SuppressWarnings("unchecked")
- at Deprecated
-public class ContextDeployer extends AbstractLifeCycle
-{
-    private static final Logger LOG = Log.getLogger(ContextDeployer.class);
-
-    private int _scanInterval=10;
-    private Scanner _scanner;
-    private ScannerListener _scannerListener;
-    private Resource _contextsDir;
-    private Map _currentDeployments = new HashMap();
-    private ContextHandlerCollection _contexts;
-    private ConfigurationManager _configMgr;
-    private boolean _recursive = false;
-    private AttributesMap _contextAttributes = new AttributesMap();
-    
-    
-    /* ------------------------------------------------------------ */
-    protected class ScannerListener implements Scanner.DiscreteListener
-    {
-        /**
-         * Handle a new deployment
-         * 
-         * @see org.eclipse.jetty.util.Scanner.DiscreteListener#fileAdded(java.lang.String)
-         */
-        public void fileAdded(String filename) throws Exception
-        {
-            deploy(filename);
-        }
-
-        /**
-         * Handle a change to an existing deployment. Undeploy then redeploy.
-         * 
-         * @see org.eclipse.jetty.util.Scanner.DiscreteListener#fileChanged(java.lang.String)
-         */
-        public void fileChanged(String filename) throws Exception
-        {
-            redeploy(filename);
-        }
-
-        /**
-         * Handle an undeploy.
-         * 
-         * @see org.eclipse.jetty.util.Scanner.DiscreteListener#fileRemoved(java.lang.String)
-         */
-        public void fileRemoved(String filename) throws Exception
-        {
-            undeploy(filename);
-        }
-        @Override
-        public String toString()
-        {
-            return "ContextDeployer$Scanner";
-        }
-    }
-
-    /**
-     * Constructor
-     */
-    public ContextDeployer() 
-    {
-        LOG.warn("ContextDeployer is deprecated. Use ContextProvider");
-        _scanner=new Scanner();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return the ContextHandlerColletion to which to deploy the contexts
-     */
-    public ContextHandlerCollection getContexts()
-    {
-        return _contexts;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Associate with a {@link ContextHandlerCollection}.
-     * 
-     * @param contexts
-     *            the ContextHandlerColletion to which to deploy the contexts
-     */
-    public void setContexts(ContextHandlerCollection contexts)
-    {
-        if (isStarted()||isStarting())
-            throw new IllegalStateException("Cannot set Contexts after deployer start");
-        _contexts=contexts;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param seconds
-     *            The period in second between scans for changed configuration
-     *            files. A zero or negative interval disables hot deployment
-     */
-    public void setScanInterval(int seconds)
-    {
-        if (isStarted()||isStarting())
-            throw new IllegalStateException("Cannot change scan interval after deployer start");
-        _scanInterval=seconds;
-    }
-
-    /* ------------------------------------------------------------ */
-    public int getScanInterval()
-    {
-        return _scanInterval;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param dir Directory to scan for context descriptors
-     */
-    public void setContextsDir(String dir)
-    {
-        try
-        {
-            _contextsDir=Resource.newResource(dir);
-        }
-        catch(Exception e)
-        {
-            throw new IllegalArgumentException(e);
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    public String getContextsDir()
-    {
-        return _contextsDir==null?null:_contextsDir.toString();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param dir
-     * @throws Exception
-     * @deprecated use {@link #setContextsDir(String)}
-     */
-    @Deprecated
-    public void setConfigurationDir(String dir) throws Exception
-    {
-        setConfigurationDir(Resource.newResource(dir));
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param file
-     * @throws Exception
-     * @deprecated use {@link #setContextsDir(String)}
-     */
-    @Deprecated
-    public void setConfigurationDir(File file) throws Exception
-    {
-        setConfigurationDir(Resource.newResource(Resource.toURL(file)));
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param resource
-     * @deprecated use {@link #setContextsDir(String)}
-     */
-    @Deprecated
-    public void setConfigurationDir(Resource resource)
-    {
-        if (isStarted()||isStarting())
-            throw new IllegalStateException("Cannot change hot deploy dir after deployer start");
-        _contextsDir=resource;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param directory
-     * @deprecated use {@link #setContextsDir(String)}
-     */
-    @Deprecated
-    public void setDirectory(String directory) throws Exception
-    {
-        setConfigurationDir(directory);
-    }
-    
-    /* ------------------------------------------------------------ */
-    /**
-     * @return the directory
-     * @deprecated use {@link #setContextsDir(String)}
-     */
-    @Deprecated
-    public String getDirectory()
-    {
-        return getConfigurationDir().getName();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return the configuration directory
-     * @deprecated use {@link #setContextsDir(String)}
-     */
-    @Deprecated
-    public Resource getConfigurationDir()
-    {
-        return _contextsDir;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param configMgr
-     */
-    public void setConfigurationManager(ConfigurationManager configMgr)
-    {
-        _configMgr=configMgr;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return the configuration manager
-     */
-    public ConfigurationManager getConfigurationManager()
-    {
-        return _configMgr;
-    }
-
-
-    /* ------------------------------------------------------------ */
-    public void setRecursive (boolean recursive)
-    {
-        _recursive=recursive;
-    }
-
-    /* ------------------------------------------------------------ */
-    public boolean getRecursive ()
-    {
-        return _recursive;
-    }
-
-    /* ------------------------------------------------------------ */
-    public boolean isRecursive()
-    {
-        return _recursive;
-    }
-    
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Set a contextAttribute that will be set for every Context deployed by this deployer.
-     * @param name
-     * @param value
-     */
-    public void setAttribute (String name, Object value)
-    {
-        _contextAttributes.setAttribute(name,value);
-    }
-    
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Get a contextAttribute that will be set for every Context deployed by this deployer.
-     * @param name
-     * @return the attribute value
-     */
-    public Object getAttribute (String name)
-    {
-        return _contextAttributes.getAttribute(name);
-    }
-    
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Remove a contextAttribute that will be set for every Context deployed by this deployer.
-     * @param name
-     */
-    public void removeAttribute(String name)
-    {
-        _contextAttributes.removeAttribute(name);
-    }
-
-    /* ------------------------------------------------------------ */
-    private void deploy(String filename) throws Exception
-    {
-        ContextHandler context=createContext(filename);
-        LOG.info("Deploy "+filename+" -> "+ context);
-        _contexts.addHandler(context);
-        _currentDeployments.put(filename,context);
-        if (_contexts.isStarted())
-            context.start();
-    }
-
-    /* ------------------------------------------------------------ */
-    private void undeploy(String filename) throws Exception
-    {
-        ContextHandler context=(ContextHandler)_currentDeployments.get(filename);
-        LOG.info("Undeploy "+filename+" -> "+context);
-        if (context==null)
-            return;
-        context.stop();
-        _contexts.removeHandler(context);
-        _currentDeployments.remove(filename);
-    }
-
-    /* ------------------------------------------------------------ */
-    private void redeploy(String filename) throws Exception
-    {
-        undeploy(filename);
-        deploy(filename);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Start the hot deployer looking for webapps to deploy/undeploy
-     * 
-     * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart()
-     */
-    @SuppressWarnings("deprecation")
-    @Override
-    protected void doStart() throws Exception
-    {
-        if (_contextsDir==null)
-            throw new IllegalStateException("No configuration dir specified");
-
-        if (_contexts==null)
-            throw new IllegalStateException("No context handler collection specified for deployer");
-
-        _scanner.setScanDir(_contextsDir.getFile());
-        _scanner.setScanInterval(getScanInterval());
-        _scanner.setRecursive(_recursive); //only look in the top level for deployment files?
-        // Accept changes only in files that could be a deployment descriptor
-        _scanner.setFilenameFilter(new FilenameFilter()
-        {
-            public boolean accept(File dir, String name)
-            {
-                try
-                {
-                    if (name.endsWith(".xml"))
-                        return true;
-                    return false;
-                }
-                catch (Exception e)
-                {
-                    LOG.warn(e);
-                    return false;
-                }
-            }
-        });
-        _scannerListener=new ScannerListener();
-        _scanner.addListener(_scannerListener);
-        _scanner.scan();
-        _scanner.start();
-        _contexts.getServer().getContainer().addBean(_scanner);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Stop the hot deployer.
-     * 
-     * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStop()
-     */
-    @Override
-    protected void doStop() throws Exception
-    {
-        _scanner.removeListener(_scannerListener);
-        _scanner.stop();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Create a WebAppContext for the webapp being hot deployed, then apply the
-     * xml config file to it to configure it.
-     * 
-     * @param filename
-     *            the config file found in the hot deploy directory
-     * @return
-     * @throws Exception
-     */
-    private ContextHandler createContext(String filename) throws Exception
-    {
-        // The config file can call any method on WebAppContext to configure
-        // the webapp being deployed.
-        Resource resource = Resource.newResource(filename);
-        if (!resource.exists())
-            return null;
-
-        XmlConfiguration xmlConfiguration=new XmlConfiguration(resource.getURL());
-        xmlConfiguration.getIdMap().put("Server", _contexts.getServer());
-        if (_configMgr!=null)
-            xmlConfiguration.getProperties().putAll(_configMgr.getProperties());
-           
-        ContextHandler context=(ContextHandler)xmlConfiguration.configure();
-        
-        // merge attributes
-        if (_contextAttributes!=null && _contextAttributes.size()>0)
-        {
-            AttributesMap attributes = new AttributesMap(_contextAttributes);
-            attributes.addAll(context.getAttributes());
-            context.setAttributes(attributes);
-        }
-        return context;
-    }
-
-}
diff --git a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/DeploymentManager.java b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/DeploymentManager.java
index 7de97ea..c9e1151 100644
--- a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/DeploymentManager.java
+++ b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/DeploymentManager.java
@@ -38,7 +38,11 @@ import org.eclipse.jetty.deploy.graph.Path;
 import org.eclipse.jetty.server.Server;
 import org.eclipse.jetty.server.handler.ContextHandlerCollection;
 import org.eclipse.jetty.util.AttributesMap;
-import org.eclipse.jetty.util.component.AggregateLifeCycle;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.annotation.ManagedOperation;
+import org.eclipse.jetty.util.annotation.Name;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
 
@@ -56,7 +60,8 @@ import org.eclipse.jetty.util.log.Logger;
  * <p>
  * <img src="doc-files/DeploymentManager.png">
  */
-public class DeploymentManager extends AggregateLifeCycle
+ at ManagedObject("Deployment Manager")
+public class DeploymentManager extends ContainerLifeCycle
 {
     private static final Logger LOG = Log.getLogger(DeploymentManager.class);
 
@@ -129,7 +134,7 @@ public class DeploymentManager extends AggregateLifeCycle
      */
     public void addApp(App app)
     {
-        LOG.info("Deployable added: " + app.getOriginId());
+        LOG.debug("Deployable added: {}",app.getOriginId());
         AppEntry entry = new AppEntry();
         entry.app = app;
         entry.setLifeCycleNode(_lifecycle.getNodeByName("undeployed"));
@@ -145,7 +150,7 @@ public class DeploymentManager extends AggregateLifeCycle
     /* ------------------------------------------------------------ */
     /** Set the AppProviders.
      * The providers passed are added via {@link #addBean(Object)} so that 
-     * their lifecycles may be managed as a {@link AggregateLifeCycle}.
+     * their lifecycles may be managed as a {@link ContainerLifeCycle}.
      * @param providers
      */
     public void setAppProviders(Collection<AppProvider> providers)
@@ -160,6 +165,7 @@ public class DeploymentManager extends AggregateLifeCycle
                 addBean(provider);
     }
 
+    @ManagedAttribute("Application Providers")
     public Collection<AppProvider> getAppProviders()
     {
         return Collections.unmodifiableList(_providers);
@@ -169,11 +175,7 @@ public class DeploymentManager extends AggregateLifeCycle
     {
         if (isRunning())
             throw new IllegalStateException();
-        
-        List<AppProvider> old = new ArrayList<AppProvider>(_providers);
-        if (_providers.add(provider) && getServer()!=null)
-            getServer().getContainer().update(this, null, provider, "provider");
-            
+        _providers.add(provider);
         addBean(provider);        
     }
 
@@ -215,6 +217,9 @@ public class DeploymentManager extends AggregateLifeCycle
     @Override
     protected void doStart() throws Exception
     {
+        if (getContexts()==null)
+            throw new IllegalStateException("No Contexts");
+        
         if (_useStandardBindings)
         {
             LOG.debug("DeploymentManager using standard bindings");
@@ -282,6 +287,7 @@ public class DeploymentManager extends AggregateLifeCycle
         return _apps;
     }
 
+    @ManagedAttribute("Deployed Apps")
     public Collection<App> getApps()
     {
         List<App> ret = new ArrayList<App>();
@@ -360,6 +366,7 @@ public class DeploymentManager extends AggregateLifeCycle
         return _contextAttributes;
     }
 
+    @ManagedAttribute("Deployed Contexts")
     public ContextHandlerCollection getContexts()
     {
         return _contexts;
@@ -401,7 +408,7 @@ public class DeploymentManager extends AggregateLifeCycle
                 if (! AppLifeCycle.UNDEPLOYED.equals(entry.lifecyleNode.getName()))
                     requestAppGoal(entry.app,AppLifeCycle.UNDEPLOYED);
                 it.remove();
-                LOG.info("Deployable removed: " + entry.app);
+                LOG.debug("Deployable removed: {}",entry.app);
             }
         }
     }
@@ -409,11 +416,8 @@ public class DeploymentManager extends AggregateLifeCycle
     public void removeAppProvider(AppProvider provider)
     {
         if(_providers.remove(provider))
-        {
             removeBean(provider);
-            if (getServer()!=null)
-                getServer().getContainer().update(this, provider,null, "provider");
-        }
+        
         try
         {
             provider.stop();
@@ -511,7 +515,8 @@ public class DeploymentManager extends AggregateLifeCycle
      * @param nodeName
      *            the name of the node to attain
      */
-    public void requestAppGoal(String appId, String nodeName)
+    @ManagedOperation(value="request the app to be moved to the specified lifecycle node", impact="ACTION")
+    public void requestAppGoal(@Name("appId") String appId, @Name("nodeName") String nodeName)
     {
         AppEntry appentry = findAppByOriginId(appId);
         if (appentry == null)
@@ -562,7 +567,7 @@ public class DeploymentManager extends AggregateLifeCycle
 
     public void undeployAll()
     {
-        LOG.info("Undeploy All");
+        LOG.debug("Undeploy All");
         for (AppEntry appentry : _apps)
         {
             requestAppGoal(appentry,"undeployed");
@@ -584,7 +589,8 @@ public class DeploymentManager extends AggregateLifeCycle
         return _lifecycle.getNodes();
     }
     
-    public Collection<App> getApps(String nodeName)
+    @ManagedOperation(value="list apps that are located at specified App LifeCycle nodes", impact="ACTION")
+    public Collection<App> getApps(@Name("nodeName") String nodeName)
     {
         return getApps(_lifecycle.getNodeByName(nodeName));
     }
diff --git a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/FileConfigurationManager.java b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/FileConfigurationManager.java
deleted file mode 100644
index 8793f1a..0000000
--- a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/FileConfigurationManager.java
+++ /dev/null
@@ -1,75 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.deploy;
-
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.net.MalformedURLException;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Properties;
-
-import org.eclipse.jetty.util.resource.Resource;
-
-/**
- * FileConfigurationManager
- * 
- * Supplies properties defined in a file.
- */
-public class FileConfigurationManager implements ConfigurationManager
-{
-    private Resource _file;
-    private Map<String,String> _map = new HashMap<String,String>();
-
-    public FileConfigurationManager()
-    {
-    }
-
-    public void setFile(String filename) throws MalformedURLException, IOException
-    {
-        _file = Resource.newResource(filename);
-    }
-
-    /**
-     * @see org.eclipse.jetty.deploy.ConfigurationManager#getProperties()
-     */
-    public Map<String, String> getProperties()
-    {
-        try
-        {
-            loadProperties();
-            return _map;
-        }
-        catch (Exception e)
-        {
-            throw new RuntimeException(e);
-        }
-    }
-
-    private void loadProperties() throws FileNotFoundException, IOException
-    {
-        if (_map.isEmpty() && _file!=null)
-        {
-            Properties properties = new Properties();
-            properties.load(_file.getInputStream());
-            for (Map.Entry<Object, Object> entry : properties.entrySet())
-                _map.put(entry.getKey().toString(),String.valueOf(entry.getValue()));
-        }
-    }
-}
diff --git a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/PropertiesConfigurationManager.java b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/PropertiesConfigurationManager.java
new file mode 100644
index 0000000..5d82173
--- /dev/null
+++ b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/PropertiesConfigurationManager.java
@@ -0,0 +1,92 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.deploy;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.annotation.ManagedOperation;
+import org.eclipse.jetty.util.annotation.Name;
+import org.eclipse.jetty.util.resource.Resource;
+
+/**
+ * FileConfigurationManager
+ * 
+ * Supplies properties defined in a file.
+ */
+ at ManagedObject("Configure deployed webapps via properties")
+public class PropertiesConfigurationManager implements ConfigurationManager
+{
+    private String _properties;
+    private final Map<String,String> _map = new HashMap<String,String>();
+
+    public PropertiesConfigurationManager(String properties)
+    {
+    }
+    
+    public PropertiesConfigurationManager()
+    {
+    }
+
+    @ManagedAttribute("A file or URL of properties")
+    public void setFile(String resource) throws MalformedURLException, IOException
+    {
+        _properties=resource;
+        _map.clear();
+        loadProperties(_properties);
+    }
+
+    public String getFile()
+    {
+        return _properties;
+    }
+    
+    @ManagedOperation("Set a property")
+    public void put(@Name("name")String name, @Name("value")String value)
+    {
+        _map.put(name,value);
+    }
+    
+    /**
+     * @see org.eclipse.jetty.deploy.ConfigurationManager#getProperties()
+     */
+    @Override
+    public Map<String, String> getProperties()
+    {
+        return new HashMap<>(_map);
+    }
+
+    private void loadProperties(String resource) throws FileNotFoundException, IOException
+    {   
+        Resource file=Resource.newResource(resource);
+        if (file!=null && file.exists())
+        {
+            Properties properties = new Properties();
+            properties.load(file.getInputStream());
+            for (Map.Entry<Object, Object> entry : properties.entrySet())
+                _map.put(entry.getKey().toString(),String.valueOf(entry.getValue()));
+        }
+    }
+}
diff --git a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/WebAppDeployer.java b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/WebAppDeployer.java
deleted file mode 100644
index 7b41ed8..0000000
--- a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/WebAppDeployer.java
+++ /dev/null
@@ -1,328 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.deploy;
-
-import java.util.ArrayList;
-import java.util.Locale;
-
-import org.eclipse.jetty.deploy.providers.ScanningAppProvider;
-import org.eclipse.jetty.server.Handler;
-import org.eclipse.jetty.server.handler.ContextHandler;
-import org.eclipse.jetty.server.handler.ContextHandlerCollection;
-import org.eclipse.jetty.server.handler.HandlerCollection;
-import org.eclipse.jetty.util.AttributesMap;
-import org.eclipse.jetty.util.URIUtil;
-import org.eclipse.jetty.util.component.AbstractLifeCycle;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.util.resource.Resource;
-import org.eclipse.jetty.webapp.WebAppContext;
-
-/**
- * Legacy Web Application Deployer.
- * 
- * <p>
- * Note: The WebAppDeployer is being phased out of Jetty in favor of the {@link DeploymentManager} and
- * {@link org.eclipse.jetty.deploy.providers.WebAppProvider} implementation.
- * 
- * <p>
- * The class searches a directory for and deploys standard web application. At startup, the directory specified by
- * {@link #setWebAppDir(String)} is searched for subdirectories (excluding hidden and CVS) or files ending with ".zip"
- * or "*.war". For each webapp discovered is passed to a new instance of {@link WebAppContext} (or a subclass specified
- * by {@link #getContexts()}. {@link ContextHandlerCollection#getContextClass()}
- * 
- * <p>
- * This deployer does not do hot deployment or undeployment. Nor does it support per web application configuration. For
- * these features see {@link ContextDeployer}.
- * 
- * @deprecated
- * @see DeploymentManager
- * @see ScanningAppProvider
- * @see ContextDeployer
- */
- at SuppressWarnings("unchecked")
-public class WebAppDeployer extends AbstractLifeCycle
-{
-    private static final Logger LOG = Log.getLogger(WebAppDeployer.class);
-
-    private HandlerCollection _contexts;
-    private String _webAppDir;
-    private String _defaultsDescriptor;
-    private String[] _configurationClasses;
-    private boolean _extract;
-    private boolean _parentLoaderPriority;
-    private boolean _allowDuplicates;
-    private ArrayList _deployed;
-    private AttributesMap _contextAttributes = new AttributesMap();
-    
-    
-    public WebAppDeployer()
-    {
-        LOG.warn("WebAppDeployer is deprecated. Use WebAppProvider");
-    }
-    
-    public String[] getConfigurationClasses()
-    {
-        return _configurationClasses;
-    }
-
-    public void setConfigurationClasses(String[] configurationClasses)
-    {
-        _configurationClasses=configurationClasses;
-    }
-
-    public HandlerCollection getContexts()
-    {
-        return _contexts;
-    }
-
-    public void setContexts(HandlerCollection contexts)
-    {
-        _contexts=contexts;
-    }
-
-    public String getDefaultsDescriptor()
-    {
-        return _defaultsDescriptor;
-    }
-
-    public void setDefaultsDescriptor(String defaultsDescriptor)
-    {
-        _defaultsDescriptor=defaultsDescriptor;
-    }
-
-    public boolean isExtract()
-    {
-        return _extract;
-    }
-
-    public void setExtract(boolean extract)
-    {
-        _extract=extract;
-    }
-
-    public boolean isParentLoaderPriority()
-    {
-        return _parentLoaderPriority;
-    }
-
-    public void setParentLoaderPriority(boolean parentPriorityClassLoading)
-    {
-        _parentLoaderPriority=parentPriorityClassLoading;
-    }
-
-    public String getWebAppDir()
-    {
-        return _webAppDir;
-    }
-
-    public void setWebAppDir(String dir)
-    {
-        _webAppDir=dir;
-    }
-
-    public boolean getAllowDuplicates()
-    {
-        return _allowDuplicates;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param allowDuplicates If false, do not deploy webapps that have already been deployed or duplicate context path
-     */
-    public void setAllowDuplicates(boolean allowDuplicates)
-    {
-        _allowDuplicates=allowDuplicates;
-    }
-
-    
-    /**
-     * Set a contextAttribute that will be set for every Context deployed by this deployer.
-     * @param name
-     * @param value
-     */
-    public void setAttribute (String name, Object value)
-    {
-        _contextAttributes.setAttribute(name,value);
-    }
-    
-    
-    /**
-     * Get a contextAttribute that will be set for every Context deployed by this deployer.
-     * @param name
-     * @return the attribute value
-     */
-    public Object getAttribute (String name)
-    {
-        return _contextAttributes.getAttribute(name);
-    }
-    
-    
-    /**
-     * Remove a contextAttribute that will be set for every Context deployed by this deployer.
-     * @param name
-     */
-    public void removeAttribute(String name)
-    {
-        _contextAttributes.removeAttribute(name);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @throws Exception 
-     */
-    @Override
-    public void doStart() throws Exception
-    {
-        _deployed=new ArrayList();
-        
-        scan();
-        
-    }
-    
-    /* ------------------------------------------------------------ */
-    /** Scan for webapplications.
-     * 
-     * @throws Exception
-     */
-    public void scan() throws Exception
-    {
-        if (_contexts==null)
-            throw new IllegalArgumentException("No HandlerContainer");
-
-        Resource r=Resource.newResource(_webAppDir);
-        if (!r.exists())
-            throw new IllegalArgumentException("No such webapps resource "+r);
-
-        if (!r.isDirectory())
-            throw new IllegalArgumentException("Not directory webapps resource "+r);
-
-        String[] files=r.list();
-
-        files: for (int f=0; files!=null&&f<files.length; f++)
-        {
-            String context=files[f];
-
-            if (context.equalsIgnoreCase("CVS/")||context.equalsIgnoreCase("CVS")||context.startsWith("."))
-                continue;
-
-            Resource app=r.addPath(r.encode(context));
-
-            if (context.toLowerCase(Locale.ENGLISH).endsWith(".war")||context.toLowerCase(Locale.ENGLISH).endsWith(".jar"))
-            {
-                context=context.substring(0,context.length()-4);
-                Resource unpacked=r.addPath(context);
-                if (unpacked!=null&&unpacked.exists()&&unpacked.isDirectory())
-                    continue;
-            }
-            else if (!app.isDirectory())
-                continue;
-
-            if (context.equalsIgnoreCase("root")||context.equalsIgnoreCase("root/"))
-                context=URIUtil.SLASH;
-            else
-                context="/"+context;
-            if (context.endsWith("/")&&context.length()>0)
-                context=context.substring(0,context.length()-1);
-
-            // Check the context path has not already been added or the webapp itself is not already deployed
-            if (!_allowDuplicates)
-            {
-                Handler[] installed=_contexts.getChildHandlersByClass(ContextHandler.class);
-                for (int i=0; i<installed.length; i++)
-                {
-                    ContextHandler c = (ContextHandler)installed[i];
-
-                    if (context.equals(c.getContextPath()))
-                        continue files;
-
-                    
-                    try
-                    {
-                        String path = null;
-                        if (c instanceof WebAppContext)
-                            path = Resource.newResource(((WebAppContext)c).getWar()).getFile().getCanonicalPath();
-                        else if (c.getBaseResource() != null)
-                            path = c.getBaseResource().getFile().getCanonicalPath();
-
-                        if (path != null && path.equals(app.getFile().getCanonicalPath()))
-                        {
-                            LOG.debug("Already deployed: {}",path);
-                            continue files;
-                        }
-                    }
-                    catch (Exception e)
-                    {
-                        LOG.ignore(e);
-                    }
-
-                }
-            }
-
-            // create a webapp
-            WebAppContext wah=null;
-            if (_contexts instanceof ContextHandlerCollection && 
-                WebAppContext.class.isAssignableFrom(((ContextHandlerCollection)_contexts).getContextClass()))
-            {
-                try
-                {
-                    wah=(WebAppContext)((ContextHandlerCollection)_contexts).getContextClass().newInstance();
-                }
-                catch (Exception e)
-                {
-                    throw new Error(e);
-                }
-            }
-            else
-            {
-                wah=new WebAppContext();
-            }
-            
-            // configure it
-            wah.setContextPath(context);
-            if (_configurationClasses!=null)
-                wah.setConfigurationClasses(_configurationClasses);
-            if (_defaultsDescriptor!=null)
-                wah.setDefaultsDescriptor(_defaultsDescriptor);
-            wah.setExtractWAR(_extract);
-            wah.setWar(app.toString());
-            wah.setParentLoaderPriority(_parentLoaderPriority);
-            
-            //set up any contextAttributes
-            wah.setAttributes(new AttributesMap(_contextAttributes));
-            
-            // add it
-            _contexts.addHandler(wah);
-            _deployed.add(wah);
-            
-            if (_contexts.isStarted())
-                wah.start();  // TODO Multi exception
-        }
-    }
-    
-    @Override
-    public void doStop() throws Exception
-    {
-        for (int i=_deployed.size();i-->0;)
-        {
-            ContextHandler wac = (ContextHandler)_deployed.get(i);
-            wac.stop();// TODO Multi exception
-        }
-    }
-}
diff --git a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/bindings/GlobalWebappConfigBinding.java b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/bindings/GlobalWebappConfigBinding.java
index 7d3cb3d..0c41218 100644
--- a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/bindings/GlobalWebappConfigBinding.java
+++ b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/bindings/GlobalWebappConfigBinding.java
@@ -18,7 +18,7 @@
 
 package org.eclipse.jetty.deploy.bindings;
 
-import java.net.URL;
+import java.io.File;
 
 import org.eclipse.jetty.deploy.App;
 import org.eclipse.jetty.deploy.AppLifeCycle;
@@ -26,22 +26,21 @@ import org.eclipse.jetty.deploy.graph.Node;
 import org.eclipse.jetty.server.handler.ContextHandler;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.util.resource.FileResource;
 import org.eclipse.jetty.util.resource.Resource;
 import org.eclipse.jetty.webapp.WebAppContext;
 import org.eclipse.jetty.xml.XmlConfiguration;
 
 /**
  * Provides a way of globally setting various aspects of webapp contexts.
- * 
- * Adding this binding will allow the user to arbitrarily apply a file of 
+ *
+ * Adding this binding will allow the user to arbitrarily apply a file of
  * jetty-web.xml like settings to a webapp context.
- * 
+ *
  * Example usage would be:
  * - adding a server or system class setting to all webapp contexts
- * - adding an override descriptor 
- * 
- * Note: Currently properties from startup will not be available for 
+ * - adding an override descriptor
+ *
+ * Note: Currently properties from startup will not be available for
  * reference.
  *
  */
@@ -88,13 +87,19 @@ public class GlobalWebappConfigBinding implements AppLifeCycle.Binding
             {
                 LOG.warn("Binding: global context binding is enabled but no jetty-web.xml file has been registered");
             }
-            
+
             Resource globalContextSettings = Resource.newResource(_jettyXml);
 
             if (globalContextSettings.exists())
             {
                 XmlConfiguration jettyXmlConfig = new XmlConfiguration(globalContextSettings.getInputStream());
 
+                Resource resource = Resource.newResource(app.getOriginId());
+                File file = resource.getFile();
+                jettyXmlConfig.getIdMap().put("Server",app.getDeploymentManager().getServer());
+                jettyXmlConfig.getProperties().put("jetty.webapp",file.getCanonicalPath());
+                jettyXmlConfig.getProperties().put("jetty.webapps",file.getParentFile().getCanonicalPath());
+                
                 jettyXmlConfig.configure(context);
             }
             else
diff --git a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/bindings/StandardStarter.java b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/bindings/StandardStarter.java
index 70076e8..4806c88 100644
--- a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/bindings/StandardStarter.java
+++ b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/bindings/StandardStarter.java
@@ -25,18 +25,22 @@ import org.eclipse.jetty.server.handler.ContextHandler;
 
 public class StandardStarter implements AppLifeCycle.Binding
 {
+    @Override
     public String[] getBindingTargets()
     {
         return new String[]
         { "starting" };
     }
 
+    @Override
     public void processBinding(Node node, App app) throws Exception
     {
         ContextHandler handler = app.getContextHandler();
-        if (!handler.isStarted())
-        {
-            handler.start();
-        }
+        
+        // start the handler
+        handler.start();
+        
+        // After starting let the context manage state
+        app.getDeploymentManager().getContexts().manage(handler);
     }
 }
diff --git a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/bindings/StandardStopper.java b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/bindings/StandardStopper.java
index ee3a7c4..2d2c33f 100644
--- a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/bindings/StandardStopper.java
+++ b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/bindings/StandardStopper.java
@@ -25,18 +25,22 @@ import org.eclipse.jetty.server.handler.ContextHandler;
 
 public class StandardStopper implements AppLifeCycle.Binding
 {
+    @Override
     public String[] getBindingTargets()
     {
         return new String[]
         { "stopping" };
     }
 
+    @Override
     public void processBinding(Node node, App app) throws Exception
     {
         ContextHandler handler = app.getContextHandler();
-        if (!handler.isStopped())
-        {
-            handler.stop();
-        }
+        
+        // Before stopping, take back management from the context
+        app.getDeploymentManager().getContexts().unmanage(handler);
+        
+        // Stop it
+        handler.stop();
     }
 }
diff --git a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/bindings/StandardUndeployer.java b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/bindings/StandardUndeployer.java
index 5865ac4..1611f01 100644
--- a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/bindings/StandardUndeployer.java
+++ b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/bindings/StandardUndeployer.java
@@ -32,12 +32,14 @@ public class StandardUndeployer implements AppLifeCycle.Binding
 {
     private static final Logger LOG = Log.getLogger(StandardUndeployer.class);
 
+    @Override
     public String[] getBindingTargets()
     {
         return new String[]
         { "undeploying" };
     }
 
+    @Override
     public void processBinding(Node node, App app) throws Exception
     {
         ContextHandler handler = app.getContextHandler();
diff --git a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/bindings/package-info.java b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/bindings/package-info.java
new file mode 100644
index 0000000..3be43e4
--- /dev/null
+++ b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/bindings/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Deploy : Standard Deployment Bindings
+ */
+package org.eclipse.jetty.deploy.bindings;
+
diff --git a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/graph/package-info.java b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/graph/package-info.java
new file mode 100644
index 0000000..b39fd80
--- /dev/null
+++ b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/graph/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Deploy : Deployment Graph
+ */
+package org.eclipse.jetty.deploy.graph;
+
diff --git a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/jmx/package-info.java b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/jmx/package-info.java
new file mode 100644
index 0000000..cfac2b2
--- /dev/null
+++ b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/jmx/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Deploy : JMX Integration
+ */
+package org.eclipse.jetty.deploy.jmx;
+
diff --git a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/package-info.java b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/package-info.java
new file mode 100644
index 0000000..c231c76
--- /dev/null
+++ b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Deploy : Webapp Deploy Management
+ */
+package org.eclipse.jetty.deploy;
+
diff --git a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ContextProvider.java b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ContextProvider.java
deleted file mode 100644
index c36e86a..0000000
--- a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ContextProvider.java
+++ /dev/null
@@ -1,150 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.deploy.providers;
-
-import java.io.File;
-import java.io.FilenameFilter;
-import java.util.Locale;
-
-import org.eclipse.jetty.deploy.App;
-import org.eclipse.jetty.deploy.ConfigurationManager;
-import org.eclipse.jetty.deploy.util.FileID;
-import org.eclipse.jetty.server.handler.ContextHandler;
-import org.eclipse.jetty.util.resource.Resource;
-import org.eclipse.jetty.webapp.WebAppContext;
-import org.eclipse.jetty.xml.XmlConfiguration;
-
-/** Context directory App Provider.
- * <p>This specialization of {@link ScanningAppProvider} is the
- * replacement for the old (and deprecated) <code>org.eclipse.jetty.deploy.ContextDeployer</code> and it will scan a directory
- * only for context.xml files.
- */
-public class ContextProvider extends ScanningAppProvider
-{
-    private ConfigurationManager _configurationManager;
-    private boolean _parentLoaderPriority = false;
-    private String _defaultsDescriptor;
-
-    public ContextProvider()
-    {
-        super(  new FilenameFilter()
-        {
-            public boolean accept(File dir, String name)
-            {
-                if (!dir.exists())
-                    return false;
-                String lowername = name.toLowerCase(Locale.ENGLISH);
-                if (lowername.startsWith("."))
-                    return false;
-                
-                return  (lowername.endsWith(".xml") && !new File(dir,name).isDirectory());
-            }
-        });
-    }
-
-
-    /* ------------------------------------------------------------ */
-    public ConfigurationManager getConfigurationManager()
-    {
-        return _configurationManager;
-    }
-    
-    /* ------------------------------------------------------------ */
-    /** Set the configurationManager.
-     * @param configurationManager the configurationManager to set
-     */
-    public void setConfigurationManager(ConfigurationManager configurationManager)
-    {
-        _configurationManager = configurationManager;
-    }
-
-    /* ------------------------------------------------------------ */
-    public ContextHandler createContextHandler(App app) throws Exception
-    {
-        Resource resource = Resource.newResource(app.getOriginId());
-        File file = resource.getFile();
-        
-        if (resource.exists() && FileID.isXmlFile(file))
-        {
-            XmlConfiguration xmlc = new XmlConfiguration(resource.getURL())
-            {
-                @Override
-                public void initializeDefaults(Object context)
-                {
-                    super.initializeDefaults(context);
-                    
-                    if (context instanceof WebAppContext)
-                    {
-                        WebAppContext webapp = (WebAppContext)context;
-                        webapp.setParentLoaderPriority(_parentLoaderPriority);
-                        if (_defaultsDescriptor!=null)
-                            webapp.setDefaultsDescriptor(_defaultsDescriptor);
-                    }
-                }
-            };
-            
-            xmlc.getIdMap().put("Server",getDeploymentManager().getServer());
-            if (getConfigurationManager() != null)
-                xmlc.getProperties().putAll(getConfigurationManager().getProperties());
-            return (ContextHandler)xmlc.configure();
-        }
-        
-        throw new IllegalStateException("App resouce does not exist "+resource);
-    }
-
-    /* ------------------------------------------------------------ */
-    /** Get the parentLoaderPriority.
-     * @return the parentLoaderPriority
-     */
-    public boolean isParentLoaderPriority()
-    {
-        return _parentLoaderPriority;
-    }
-
-    /* ------------------------------------------------------------ */
-    /** Set the parentLoaderPriority.
-     * <p>If the context created is a WebAppContext, then set the 
-     * default value for {@link WebAppContext#setParentLoaderPriority(boolean)}.
-     * @param parentLoaderPriority the parentLoaderPriority to set
-     */
-    public void setParentLoaderPriority(boolean parentLoaderPriority)
-    {
-        _parentLoaderPriority = parentLoaderPriority;
-    }
-    
-    /* ------------------------------------------------------------ */
-    /** Get the defaultsDescriptor.
-     * @return the defaultsDescriptor
-     */
-    public String getDefaultsDescriptor()
-    {
-        return _defaultsDescriptor;
-    }
-
-    /* ------------------------------------------------------------ */
-    /** Set the defaultsDescriptor.
-     * <p>If the context created is a WebAppContext, then set the 
-     * default value for {@link WebAppContext#setDefaultsDescriptor(String)}
-     * @param defaultsDescriptor the defaultsDescriptor to set
-     */
-    public void setDefaultsDescriptor(String defaultsDescriptor)
-    {
-        _defaultsDescriptor = defaultsDescriptor;
-    }
-}
diff --git a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ScanningAppProvider.java b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ScanningAppProvider.java
index e109716..fdc6760 100644
--- a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ScanningAppProvider.java
+++ b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ScanningAppProvider.java
@@ -20,14 +20,20 @@ package org.eclipse.jetty.deploy.providers;
 
 import java.io.File;
 import java.io.FilenameFilter;
+import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
+import java.util.concurrent.CopyOnWriteArrayList;
 
 import org.eclipse.jetty.deploy.App;
 import org.eclipse.jetty.deploy.AppProvider;
 import org.eclipse.jetty.deploy.DeploymentManager;
 import org.eclipse.jetty.util.Scanner;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
 import org.eclipse.jetty.util.component.AbstractLifeCycle;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
@@ -35,6 +41,7 @@ import org.eclipse.jetty.util.resource.Resource;
 
 /**
  */
+ at ManagedObject("Abstract Provider for loading webapps")
 public abstract class ScanningAppProvider extends AbstractLifeCycle implements AppProvider
 {
     private static final Logger LOG = Log.getLogger(ScanningAppProvider.class);
@@ -42,8 +49,8 @@ public abstract class ScanningAppProvider extends AbstractLifeCycle implements A
     private Map<String, App> _appMap = new HashMap<String, App>();
 
     private DeploymentManager _deploymentManager;
-    protected final FilenameFilter _filenameFilter;
-    private Resource _monitoredDir;
+    protected FilenameFilter _filenameFilter;
+    private final List<Resource> _monitored= new CopyOnWriteArrayList<>();
     private boolean _recursive = false;
     private int _scanInterval = 10;
     private Scanner _scanner;
@@ -51,16 +58,19 @@ public abstract class ScanningAppProvider extends AbstractLifeCycle implements A
     /* ------------------------------------------------------------ */
     private final Scanner.DiscreteListener _scannerListener = new Scanner.DiscreteListener()
     {
+        @Override
         public void fileAdded(String filename) throws Exception
         {
             ScanningAppProvider.this.fileAdded(filename);
         }
 
+        @Override
         public void fileChanged(String filename) throws Exception
         {
             ScanningAppProvider.this.fileChanged(filename);
         }
 
+        @Override
         public void fileRemoved(String filename) throws Exception
         {
             ScanningAppProvider.this.fileRemoved(filename);
@@ -68,12 +78,25 @@ public abstract class ScanningAppProvider extends AbstractLifeCycle implements A
     };
 
     /* ------------------------------------------------------------ */
+    protected ScanningAppProvider()
+    {
+    }
+    
+    /* ------------------------------------------------------------ */
     protected ScanningAppProvider(FilenameFilter filter)
     {
         _filenameFilter = filter;
     }
 
     /* ------------------------------------------------------------ */
+    protected void setFilenameFilter(FilenameFilter filter)
+    {
+        if (isRunning())
+            throw new IllegalStateException();
+        _filenameFilter = filter;
+    }
+    
+    /* ------------------------------------------------------------ */
     /**
      * @return The index of currently deployed applications.
      */
@@ -104,15 +127,21 @@ public abstract class ScanningAppProvider extends AbstractLifeCycle implements A
     {
         if (LOG.isDebugEnabled()) 
             LOG.debug(this.getClass().getSimpleName() + ".doStart()");
-        if (_monitoredDir == null)
-        {
+        if (_monitored.size()==0)
             throw new IllegalStateException("No configuration dir specified");
-        }
 
-        File scandir = _monitoredDir.getFile();
-        LOG.info("Deployment monitor " + scandir + " at interval " + _scanInterval);
+        LOG.info("Deployment monitor " + _monitored + " at interval " + _scanInterval);
+        List<File> files = new ArrayList<>();
+        for (Resource resource:_monitored)
+        {
+            if (resource.exists() && resource.getFile().canRead())
+                files.add(resource.getFile());
+            else
+                LOG.warn("Does not exist: "+resource);
+        }
+        
         _scanner = new Scanner();
-        _scanner.setScanDirs(Collections.singletonList(scandir));
+        _scanner.setScanDirs(files);
         _scanner.setScanInterval(_scanInterval);
         _scanner.setRecursive(_recursive);
         _scanner.setFilenameFilter(_filenameFilter);
@@ -132,6 +161,12 @@ public abstract class ScanningAppProvider extends AbstractLifeCycle implements A
             _scanner = null;
         }
     }
+    
+    /* ------------------------------------------------------------ */
+    protected boolean exists(String path)
+    {
+        return _scanner.exists(path);
+    }
 
     /* ------------------------------------------------------------ */
     protected void fileAdded(String filename) throws Exception
@@ -189,52 +224,64 @@ public abstract class ScanningAppProvider extends AbstractLifeCycle implements A
     /* ------------------------------------------------------------ */
     public Resource getMonitoredDirResource()
     {
-        return _monitoredDir;
+        if (_monitored.size()==0)
+            return null;
+        if (_monitored.size()>1)
+            throw new IllegalStateException();
+        return _monitored.get(0);
     }
 
     /* ------------------------------------------------------------ */
     public String getMonitoredDirName()
     {
-        return _monitoredDir.toString();
+        Resource resource=getMonitoredDirResource();
+        return resource==null?null:resource.toString();
     }
 
     /* ------------------------------------------------------------ */
+    @ManagedAttribute("scanning interval to detect changes which need reloaded")
     public int getScanInterval()
     {
         return _scanInterval;
     }
 
     /* ------------------------------------------------------------ */
+    @ManagedAttribute("recursive scanning supported")
     public boolean isRecursive()
     {
         return _recursive;
     }
 
     /* ------------------------------------------------------------ */
+    @Override
     public void setDeploymentManager(DeploymentManager deploymentManager)
     {
         _deploymentManager = deploymentManager;
     }
     
     /* ------------------------------------------------------------ */
-    public void setMonitoredDirResource(Resource contextsDir)
+    public void setMonitoredResources(List<Resource> resources)
     {
-        _monitoredDir = contextsDir;
+        _monitored.clear();
+        _monitored.addAll(resources);
     }
-
+    
     /* ------------------------------------------------------------ */
-    public void addScannerListener(Scanner.Listener listener)
+    public List<Resource> getMonitoredResources()
     {
-        _scanner.addListener(listener);
+        return Collections.unmodifiableList(_monitored);
+    }
+    
+    /* ------------------------------------------------------------ */
+    public void setMonitoredDirResource(Resource resource)
+    {
+        setMonitoredResources(Collections.singletonList(resource));
     }
 
     /* ------------------------------------------------------------ */
-    /**
-     * @deprecated Use {@link #setMonitoredDirName(String)}
-     */
-    public void setMonitoredDir(String dir)
+    public void addScannerListener(Scanner.Listener listener)
     {
-        setMonitoredDirName(dir);
+        _scanner.addListener(listener);
     }
     
     /* ------------------------------------------------------------ */
@@ -244,16 +291,25 @@ public abstract class ScanningAppProvider extends AbstractLifeCycle implements A
      */
     public void setMonitoredDirName(String dir)
     {
+        setMonitoredDirectories(Collections.singletonList(dir));
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setMonitoredDirectories(Collection<String> directories)
+    {
         try
         {
-            setMonitoredDirResource(Resource.newResource(dir));
+            List<Resource> resources = new ArrayList<>();
+            for (String dir:directories)
+                resources.add(Resource.newResource(dir));
+            setMonitoredResources(resources);
         }
         catch (Exception e)
         {
             throw new IllegalArgumentException(e);
         }
     }
-
+    
     /* ------------------------------------------------------------ */
     protected void setRecursive(boolean recursive)
     {
diff --git a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/WebAppProvider.java b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/WebAppProvider.java
index 291d2fa..20af493 100644
--- a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/WebAppProvider.java
+++ b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/WebAppProvider.java
@@ -20,40 +20,57 @@ package org.eclipse.jetty.deploy.providers;
 
 import java.io.File;
 import java.io.FilenameFilter;
-import java.io.IOException;
-import java.net.MalformedURLException;
 import java.util.Locale;
 
 import org.eclipse.jetty.deploy.App;
+import org.eclipse.jetty.deploy.ConfigurationManager;
 import org.eclipse.jetty.deploy.util.FileID;
 import org.eclipse.jetty.server.handler.ContextHandler;
 import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
 import org.eclipse.jetty.util.resource.Resource;
 import org.eclipse.jetty.webapp.WebAppContext;
+import org.eclipse.jetty.xml.XmlConfiguration;
 
 /* ------------------------------------------------------------ */
-/** Context directory App Provider.
- * <p>This specialization of {@link ScanningAppProvider} is the
- * replacement for old (and deprecated) <code>org.eclipse.jetty.deploy.WebAppDeployer</code> and it will scan a directory
- * only for war files or directories files.</p>
+/** The webapps directory scanning provider.
  * <p>
- * Webapps with names root or starting with root- are deployed at /.
- * If the name is in the format root-hostname, then the webapp is deployed
- * at / in the virtual host hostname.
+ * This provider scans one or more directories (typically "webapps") for contexts to
+ * deploy, which may be:<ul>
+ * <li>A standard WAR file (must end in ".war")</li>
+ * <li>A directory containing an expanded WAR file</li>
+ * <li>A directory containing static content</li>
+ * <li>An XML descriptor in {@link XmlConfiguration} format that configures a {@link ContextHandler} instance</li>
+ * </ul>
+ * <p>
+ * To avoid double deployments and allow flexibility of the content of the scanned directories, the provider
+ * implements some heuristics to ignore some files found in the scans: <ul>
+ * <li>Hidden files (starting with ".") are ignored</li>
+ * <li>Directories with names ending in ".d" are ignored</li>
+ * <li>If a directory and a WAR file exist ( eg foo/ and foo.war) then the directory is assumed to be
+ * the unpacked WAR and only the WAR is deployed (which may reused the unpacked directory)</li>
+ * <li>If a directory and a matching XML file exist ( eg foo/ and foo.xml) then the directory is assumed to be
+ * an unpacked WAR and only the XML is deployed (which may used the directory in it's configuration)</li>
+ * <li>If a WAR file and a matching XML exist (eg foo.war and foo.xml) then the WAR is assumed to
+ * be configured by the XML and only the XML is deployed.
+ * </ul>
+ * <p>For XML configured contexts, the ID map will contain a reference to the {@link Server} instance called "Server" and
+ * properties for the webapp file as "jetty.webapp" and directory as "jetty.webapps".
  */
+ at ManagedObject("Provider for start-up deployement of webapps based on presence in directory")
 public class WebAppProvider extends ScanningAppProvider
 {
     private boolean _extractWars = false;
     private boolean _parentLoaderPriority = false;
+    private ConfigurationManager _configurationManager;
     private String _defaultsDescriptor;
-    private Filter _filter;
     private File _tempDirectory;
     private String[] _configurationClasses;
 
-    public static class Filter implements FilenameFilter
+    public class Filter implements FilenameFilter
     {
-        private File _contexts;
-        
+        @Override
         public boolean accept(File dir, String name)
         {
             if (!dir.exists())
@@ -61,55 +78,56 @@ public class WebAppProvider extends ScanningAppProvider
                 return false;
             }
             String lowername = name.toLowerCase(Locale.ENGLISH);
-            
+
             File file = new File(dir,name);
-            // is it not a directory and not a war ?
-            if (!file.isDirectory() && !lowername.endsWith(".war"))
-            {
-                return false;
-            }
-            
-            //ignore hidden files
+
+            // ignore hidden files
             if (lowername.startsWith("."))
                 return false;
-                   
+
+            // Ignore some directories
             if (file.isDirectory())
             {
-                // is it a directory for an existing war file?
-                if (new File(dir,name+".war").exists() ||
-                    new File(dir,name+".WAR").exists())
+                // is it a nominated config directory
+                if (lowername.endsWith(".d"))
+                    return false;
 
+                // is it an unpacked directory for an existing war file?
+                if (exists(name+".war")||exists(name+".WAR"))
                     return false;
- 
+
+                // is it a directory for an existing xml file?
+                if (exists(name+".xml")||exists(name+".XML"))
+                    return false;
+
                 //is it a sccs dir?
                 if ("cvs".equals(lowername) || "cvsroot".equals(lowername))
                     return false;
+
+                // OK to deploy it then
+                return true;
             }
-            
-            // is there a contexts config file
-            if (_contexts!=null)
+
+            // else is it a war file
+            if (lowername.endsWith(".war"))
             {
-                String context=name;
-                if (!file.isDirectory())
-                {
-                    context=context.substring(0,context.length()-4);
-                }
-                if (new File(_contexts,context+".xml").exists() ||
-                    new File(_contexts,context+".XML").exists() )
-                {
-                    return false;
-                }
+                //defer deployment decision to fileChanged()
+                return true;
             }
-               
-            return true;
+
+            // else is it a context XML file 
+            if (lowername.endsWith(".xml"))
+                return true;
+
+            return false;
         }
     }
-    
+
     /* ------------------------------------------------------------ */
     public WebAppProvider()
     {
-        super(new Filter());
-        _filter=(Filter)_filenameFilter;
+        super();
+        setFilenameFilter(new Filter());
         setScanInterval(0);
     }
 
@@ -117,6 +135,7 @@ public class WebAppProvider extends ScanningAppProvider
     /** Get the extractWars.
      * @return the extractWars
      */
+    @ManagedAttribute("extract war files")
     public boolean isExtractWars()
     {
         return _extractWars;
@@ -135,6 +154,7 @@ public class WebAppProvider extends ScanningAppProvider
     /** Get the parentLoaderPriority.
      * @return the parentLoaderPriority
      */
+    @ManagedAttribute("parent classloader has priority")
     public boolean isParentLoaderPriority()
     {
         return _parentLoaderPriority;
@@ -153,6 +173,7 @@ public class WebAppProvider extends ScanningAppProvider
     /** Get the defaultsDescriptor.
      * @return the defaultsDescriptor
      */
+    @ManagedAttribute("default descriptor for webapps")
     public String getDefaultsDescriptor()
     {
         return _defaultsDescriptor;
@@ -168,39 +189,20 @@ public class WebAppProvider extends ScanningAppProvider
     }
 
     /* ------------------------------------------------------------ */
-    public String getContextXmlDir()
+    public ConfigurationManager getConfigurationManager()
     {
-        return _filter._contexts==null?null:_filter._contexts.toString();
+        return _configurationManager;
     }
-
+    
     /* ------------------------------------------------------------ */
-    /**
-     * Set the directory in which to look for context XML files.
-     * <p>
-     * If a webapp call "foo/" or "foo.war" is discovered in the monitored
-     * directory, then the ContextXmlDir is examined to see if a foo.xml
-     * file exists.  If it does, then this deployer will not deploy the webapp
-     * and the ContextProvider should be used to act on the foo.xml file.
-     * @see ContextProvider
-     * @param contextsDir
+    /** Set the configurationManager.
+     * @param configurationManager the configurationManager to set
      */
-    public void setContextXmlDir(String contextsDir)
+    public void setConfigurationManager(ConfigurationManager configurationManager)
     {
-        try
-        {
-            _filter._contexts=Resource.newResource(contextsDir).getFile();
-        }
-        catch (MalformedURLException e)
-        {
-            throw new RuntimeException(e);
-        }
-        catch (IOException e)
-        {
-            throw new RuntimeException(e);
-        }
+        _configurationManager = configurationManager;
     }
     
-    
     /* ------------------------------------------------------------ */
     /**
      * @param configurations The configuration class names.
@@ -212,18 +214,19 @@ public class WebAppProvider extends ScanningAppProvider
     
     /* ------------------------------------------------------------ */
     /**
-     * 
+     *
      */
+    @ManagedAttribute("configuration classes for webapps to be processed through")
     public String[] getConfigurationClasses()
     {
         return _configurationClasses;
     }
-    
+
     /**
      * Set the Work directory where unpacked WAR files are managed from.
      * <p>
      * Default is the same as the <code>java.io.tmpdir</code> System Property.
-     * 
+     *
      * @param directory the new work directory
      */
     public void setTempDir(File directory)
@@ -231,17 +234,20 @@ public class WebAppProvider extends ScanningAppProvider
         _tempDirectory = directory;
     }
     
+    /* ------------------------------------------------------------ */
     /**
      * Get the user supplied Work Directory.
-     * 
+     *
      * @return the user supplied work directory (null if user has not set Temp Directory yet)
      */
+    @ManagedAttribute("temp directory for use, null if no user set temp directory")
     public File getTempDir()
     {
         return _tempDirectory;
     }
-    
+
     /* ------------------------------------------------------------ */
+    @Override
     public ContextHandler createContextHandler(final App app) throws Exception
     {
         Resource resource = Resource.newResource(app.getOriginId());
@@ -250,8 +256,37 @@ public class WebAppProvider extends ScanningAppProvider
             throw new IllegalStateException("App resouce does not exist "+resource);
 
         String context = file.getName();
-        
-        if (file.isDirectory())
+
+        if (resource.exists() && FileID.isXmlFile(file))
+        {
+            XmlConfiguration xmlc = new XmlConfiguration(resource.getURL())
+            {
+                @Override
+                public void initializeDefaults(Object context)
+                {
+                    super.initializeDefaults(context);
+
+                    if (context instanceof WebAppContext)
+                    {
+                        WebAppContext webapp = (WebAppContext)context;
+                        webapp.setParentLoaderPriority(_parentLoaderPriority);
+                        if (_defaultsDescriptor != null)
+                            webapp.setDefaultsDescriptor(_defaultsDescriptor);
+                    }
+                }
+            };
+            
+            xmlc.getIdMap().put("Server", getDeploymentManager().getServer());
+            xmlc.getProperties().put("jetty.home",System.getProperty("jetty.home","."));
+            xmlc.getProperties().put("jetty.base",System.getProperty("jetty.base","."));
+            xmlc.getProperties().put("jetty.webapp",file.getCanonicalPath());
+            xmlc.getProperties().put("jetty.webapps",file.getParentFile().getCanonicalPath());
+
+            if (getConfigurationManager() != null)
+                xmlc.getProperties().putAll(getConfigurationManager().getProperties());
+            return (ContextHandler)xmlc.configure();
+        }
+        else if (file.isDirectory())
         {
             // must be a directory
         }
@@ -260,21 +295,21 @@ public class WebAppProvider extends ScanningAppProvider
             // Context Path is the same as the archive.
             context = context.substring(0,context.length() - 4);
         }
-        else 
+        else
         {
             throw new IllegalStateException("unable to create ContextHandler for "+app);
         }
 
         // Ensure "/" is Not Trailing in context paths.
-        if (context.endsWith("/") && context.length() > 0) 
+        if (context.endsWith("/") && context.length() > 0)
         {
             context = context.substring(0,context.length() - 1);
         }
-        
+
         // Start building the webapplication
-        WebAppContext wah = new WebAppContext();
-        wah.setDisplayName(context);
-        
+        WebAppContext webAppContext = new WebAppContext();
+        webAppContext.setDisplayName(context);
+
         // special case of archive (or dir) named "root" is / context
         if (context.equalsIgnoreCase("root"))
         {
@@ -284,28 +319,28 @@ public class WebAppProvider extends ScanningAppProvider
         {
             int dash=context.toLowerCase(Locale.ENGLISH).indexOf('-');
             String virtual = context.substring(dash+1);
-            wah.setVirtualHosts(new String[]{virtual});
+            webAppContext.setVirtualHosts(new String[]{virtual});
             context = URIUtil.SLASH;
         }
 
         // Ensure "/" is Prepended to all context paths.
-        if (context.charAt(0) != '/') 
+        if (context.charAt(0) != '/')
         {
             context = "/" + context;
         }
 
 
-        wah.setContextPath(context);
-        wah.setWar(file.getAbsolutePath());
-        if (_defaultsDescriptor != null) 
+        webAppContext.setContextPath(context);
+        webAppContext.setWar(file.getAbsolutePath());
+        if (_defaultsDescriptor != null)
         {
-            wah.setDefaultsDescriptor(_defaultsDescriptor);
+            webAppContext.setDefaultsDescriptor(_defaultsDescriptor);
         }
-        wah.setExtractWAR(_extractWars);
-        wah.setParentLoaderPriority(_parentLoaderPriority);
-        if (_configurationClasses != null) 
+        webAppContext.setExtractWAR(_extractWars);
+        webAppContext.setParentLoaderPriority(_parentLoaderPriority);
+        if (_configurationClasses != null)
         {
-            wah.setConfigurationClasses(_configurationClasses);
+            webAppContext.setConfigurationClasses(_configurationClasses);
         }
 
         if (_tempDirectory != null)
@@ -317,9 +352,154 @@ public class WebAppProvider extends ScanningAppProvider
              * If we used .setTempDirectory(File) all webapps will wind up in the
              * same temp / work directory, overwriting each others work.
              */
-            wah.setAttribute(WebAppContext.BASETEMPDIR,_tempDirectory);
+            webAppContext.setAttribute(WebAppContext.BASETEMPDIR, _tempDirectory);
+        }
+        return webAppContext;
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void fileChanged(String filename) throws Exception
+    {        
+        File file = new File(filename);
+        if (!file.exists())
+            return;
+        
+        File parent = file.getParentFile();
+        
+        //is the file that changed a directory? 
+        if (file.isDirectory())
+        {
+            //is there a .xml file of the same name?
+            if (exists(file.getName()+".xml")||exists(file.getName()+".XML"))
+                return; //ignore it
+
+            //is there .war file of the same name?
+            if (exists(file.getName()+".war")||exists(file.getName()+".WAR"))
+                return; //ignore it
+
+             super.fileChanged(filename);
+             return;
+        }
+        
+      
+        String lowname = file.getName().toLowerCase(Locale.ENGLISH);
+        //is the file that changed a .war file?
+        if (lowname.endsWith(".war"))
+        {
+            String name = file.getName();
+            String base=name.substring(0,name.length()-4);
+            String xmlname = base+".xml";
+            if (exists(xmlname))
+            {
+                //if a .xml file exists for it, then redeploy that instead
+                File xml = new File (parent, xmlname);
+                super.fileChanged(xml.getCanonicalPath());
+                return;
+            }
+            
+            xmlname = base+".XML";
+            if (exists(xmlname))
+            {
+                //if a .XML file exists for it, then redeploy that instead
+                File xml = new File(parent, xmlname);
+                super.fileChanged(xml.getCanonicalPath());
+                return;
+            }
+            
+            //redeploy the changed war
+            super.fileChanged(filename);
+            return;
+        }
+
+        //is the file that changed a .xml file?
+        if (lowname.endsWith(".xml"))
+            super.fileChanged(filename);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void fileAdded(String filename) throws Exception
+    {
+        File file = new File(filename);
+        if (!file.exists())
+            return;
+
+        //is the file that was added a directory? 
+        if (file.isDirectory())
+        {
+            //is there a .xml file of the same name?
+            if (exists(file.getName()+".xml")||exists(file.getName()+".XML"))
+                return; //assume we will get added events for the xml file
+
+            //is there .war file of the same name?
+            if (exists(file.getName()+".war")||exists(file.getName()+".WAR"))
+                return; //assume we will get added events for the war file
+
+            super.fileAdded(filename);
+            return;
         }
-        return wah; 
+
+
+        //is the file that was added a .war file?
+        String lowname = file.getName().toLowerCase(Locale.ENGLISH);
+        if (lowname.endsWith(".war"))
+        {
+            String name = file.getName();
+            String base=name.substring(0,name.length()-4);
+            //is there a .xml file of the same name?
+            if (exists(base+".xml")||exists(base+".XML")) 
+                return; //ignore it as we should get addition of the xml file
+
+            super.fileAdded(filename);
+            return;
+        }
+
+        //is the file that was added an .xml file?
+        if (lowname.endsWith(".xml"))
+            super.fileAdded(filename);
     }
+
     
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void fileRemoved(String filename) throws Exception
+    { 
+        File file = new File(filename);
+
+        //is the file that was removed a directory? 
+        if (file.isDirectory())
+        {
+            //is there a .xml file of the same name?
+            if (exists(file.getName()+".xml")||exists(file.getName()+".XML"))
+                return; //assume we will get removed events for the xml file
+
+            //is there .war file of the same name?
+            if (exists(file.getName()+".war")||exists(file.getName()+".WAR"))
+                return; //assume we will get removed events for the war file
+
+            super.fileRemoved(filename);
+            return;
+        }
+  
+        //is the file that was removed a .war file?
+        String lowname = file.getName().toLowerCase(Locale.ENGLISH);
+        if (lowname.endsWith(".war"))
+        {
+            //is there a .xml file of the same name?
+            String name = file.getName();
+            String base=name.substring(0,name.length()-4);
+            if (exists(base+".xml")||exists(base+".XML"))
+                return; //ignore it as we should get removal of the xml file
+
+            super.fileRemoved(filename);
+            return;
+        }
+
+        //is the file that was removed an .xml file?
+        if (lowname.endsWith(".xml"))
+            super.fileRemoved(filename);
+    }
+
 }
diff --git a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/package-info.java b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/package-info.java
new file mode 100644
index 0000000..6b29474
--- /dev/null
+++ b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Deploy : Webapp Deployment Providers
+ */
+package org.eclipse.jetty.deploy.providers;
+
diff --git a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/util/package-info.java b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/util/package-info.java
new file mode 100644
index 0000000..7c30d6d
--- /dev/null
+++ b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/util/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Deploy : Utilities
+ */
+package org.eclipse.jetty.deploy.util;
+
diff --git a/jetty-deploy/src/main/resources/org/eclipse/jetty/deploy/jmx/ContextDeployer-mbean.properties b/jetty-deploy/src/main/resources/org/eclipse/jetty/deploy/jmx/ContextDeployer-mbean.properties
deleted file mode 100644
index 335a5ee..0000000
--- a/jetty-deploy/src/main/resources/org/eclipse/jetty/deploy/jmx/ContextDeployer-mbean.properties
+++ /dev/null
@@ -1,9 +0,0 @@
-ContextDeployer: Deployer for runtime deploy/undeploy of webapps
-contexts: MObject: The ContextHandlerCollection to which the deployer deploys
-scanInterval: Object: The interval in seconds between scans of the deploy directory
-configurationDir: Object:RO: The deploy directory
-setConfigurationDir(java.lang.String):ACTION:Set the deploy directory
-setConfigurationDir(java.lang.String)[0]:dir:The directory
-setConfigurationDir(java.io.File):ACTION:Set the deploy directory
-setConfigurationDir(java.io.File)[0]:file:The directory
-configurationManager: MObject:Source of property values for property name resolution in deployed config file
diff --git a/jetty-deploy/src/main/resources/org/eclipse/jetty/deploy/jmx/DeploymentManager-mbean.properties b/jetty-deploy/src/main/resources/org/eclipse/jetty/deploy/jmx/DeploymentManager-mbean.properties
deleted file mode 100644
index f640c90..0000000
--- a/jetty-deploy/src/main/resources/org/eclipse/jetty/deploy/jmx/DeploymentManager-mbean.properties
+++ /dev/null
@@ -1,10 +0,0 @@
-DeploymentManager: Deployment Manager
-nodes:MBean: App LifeCycle Nodes
-apps:MBean: Deployed Apps
-contexts:MMBean: Deployed Contexts
-appProviders:MMBean: Application Providers
-getApps(java.lang.String):MBean:ACTION: List apps that are located at specified App LifeCycle node
-getApps(java.lang.String)[0]:nodeName: Name of the App LifeCycle node
-requestAppGoal(java.lang.String,java.lang.String):ACTION: Request the app to be moved to the specified App LifeCycle node
-requestAppGoal(java.lang.String,java.lang.String)[0]:appId:App identifier
-requestAppGoal(java.lang.String,java.lang.String)[1]:nodeName:Name of the App LifeCycle node
diff --git a/jetty-deploy/src/main/resources/org/eclipse/jetty/deploy/jmx/WebAppDeployer-mbean.properties b/jetty-deploy/src/main/resources/org/eclipse/jetty/deploy/jmx/WebAppDeployer-mbean.properties
deleted file mode 100644
index 2815ae6..0000000
--- a/jetty-deploy/src/main/resources/org/eclipse/jetty/deploy/jmx/WebAppDeployer-mbean.properties
+++ /dev/null
@@ -1,14 +0,0 @@
-WebAppDeployer: Deployer for startup deployment of webapps
-contexts: MObject: The ContextHandlerCollection to which the deployer deploys
-allowDuplicates: Object:RO: Whether or not duplicate deployments are allowed
-setAllowDuplicates(boolean):ACTION:  Whether or not duplicate deployments are allowed
-setAllowDuplicates(boolean)[0]:allowDuplicates: True allows duplicate webapps to be deployed while false does not
-defaultsDescriptor: Object: The webdefault.xml descriptor to use for all webapps deployed by the deployer
-configurationClasses: Object: The set of configuration classes to apply to the deployment of each webapp
-webAppDir: Object: The directory where the webapps are to be found to deploy
-extract: Object:RO: Whether or not to extract war files on deployment
-setExtract(boolean):ACTION:Set whether or not to extract war files
-setExtract(boolean)[0]:extract: True will extract war files while false will not
-parentLoaderPriority: Object:RO: Whether to use j2se classloading order or servlet spec classloading order
-setParentLoaderPriority(boolean):ACTION: Set which classloading paradigm to use
-setParentLoaderPriority(boolean)[0]:parentPriorityClassLoading: True uses j2se classloading order while false uses servlet spec order
diff --git a/jetty-deploy/src/main/resources/org/eclipse/jetty/deploy/providers/jmx/ContextProvider-mbean.properties b/jetty-deploy/src/main/resources/org/eclipse/jetty/deploy/providers/jmx/ContextProvider-mbean.properties
deleted file mode 100644
index 3b3cc05..0000000
--- a/jetty-deploy/src/main/resources/org/eclipse/jetty/deploy/providers/jmx/ContextProvider-mbean.properties
+++ /dev/null
@@ -1 +0,0 @@
-ContextProvider: Context AppProvider for Deployment manager
\ No newline at end of file
diff --git a/jetty-deploy/src/main/resources/org/eclipse/jetty/deploy/providers/jmx/ScanningAppProvider-mbean.properties b/jetty-deploy/src/main/resources/org/eclipse/jetty/deploy/providers/jmx/ScanningAppProvider-mbean.properties
deleted file mode 100644
index ecd15b3..0000000
--- a/jetty-deploy/src/main/resources/org/eclipse/jetty/deploy/providers/jmx/ScanningAppProvider-mbean.properties
+++ /dev/null
@@ -1,4 +0,0 @@
-ScanningAppProvider: Scanning AppProvider for Deployment manager
-monitoredDirName: The directory to monitor for new Apps
-scanInterval: The scan interval in seconds
-recursive: Look in subdirectories
diff --git a/jetty-deploy/src/main/resources/org/eclipse/jetty/deploy/providers/jmx/WebAppProvider-mbean.properties b/jetty-deploy/src/main/resources/org/eclipse/jetty/deploy/providers/jmx/WebAppProvider-mbean.properties
deleted file mode 100644
index f47065e..0000000
--- a/jetty-deploy/src/main/resources/org/eclipse/jetty/deploy/providers/jmx/WebAppProvider-mbean.properties
+++ /dev/null
@@ -1,7 +0,0 @@
-WebAppProvider: Web application AppProvider for Deployment manager
-extractWars: Extract WAR files to temporary directory
-parentLoaderPriority: ClassLoaders give priority to classes from parent loader
-defaultsDescriptor: The default descriptor applied before any web.xml
-contextXmlDir: The directory of context.xml files
-configurationClasses: The configuration classes to apply
-tempDir: The temporary directory to use 
\ No newline at end of file
diff --git a/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/AppLifeCycleTest.java b/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/AppLifeCycleTest.java
index 0c68024..867f97f 100644
--- a/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/AppLifeCycleTest.java
+++ b/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/AppLifeCycleTest.java
@@ -36,10 +36,10 @@ import org.junit.Test;
  */
 public class AppLifeCycleTest
 {
-	@Rule
-	public TestingDir testdir = new TestingDir();
+        @Rule
+        public TestingDir testdir = new TestingDir();
 
-	private void assertNoPath(String from, String to)
+        private void assertNoPath(String from, String to)
     {
         assertPath(from,to,new ArrayList<String>());
     }
diff --git a/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/DeploymentManagerLifeCyclePathTest.java b/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/DeploymentManagerLifeCyclePathTest.java
index 1706a67..49dd8d0 100644
--- a/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/DeploymentManagerLifeCyclePathTest.java
+++ b/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/DeploymentManagerLifeCyclePathTest.java
@@ -27,7 +27,6 @@ import javax.management.ObjectName;
 
 import org.eclipse.jetty.jmx.MBeanContainer;
 import org.eclipse.jetty.server.handler.ContextHandlerCollection;
-import org.eclipse.jetty.toolchain.jmx.JmxServiceConnection;
 import org.junit.Test;
 
 public class DeploymentManagerLifeCyclePathTest
@@ -36,6 +35,7 @@ public class DeploymentManagerLifeCyclePathTest
     public void testStateTransition_NewToDeployed() throws Exception
     {
         DeploymentManager depman = new DeploymentManager();
+        depman.setContexts(new ContextHandlerCollection());
         depman.setDefaultLifeCycleGoal(null); // no default
         AppLifeCyclePathCollector pathtracker = new AppLifeCyclePathCollector();
         MockAppProvider mockProvider = new MockAppProvider();
@@ -68,6 +68,7 @@ public class DeploymentManagerLifeCyclePathTest
     public void testStateTransition_Receive() throws Exception
     {
         DeploymentManager depman = new DeploymentManager();
+        depman.setContexts(new ContextHandlerCollection());
         depman.setDefaultLifeCycleGoal(null); // no default
         AppLifeCyclePathCollector pathtracker = new AppLifeCyclePathCollector();
         MockAppProvider mockProvider = new MockAppProvider();
@@ -88,7 +89,7 @@ public class DeploymentManagerLifeCyclePathTest
 
         pathtracker.assertExpected("Test StateTransition / New only",expected);
     }
-    
+
     @Test
     public void testStateTransition_DeployedToUndeployed() throws Exception
     {
@@ -99,8 +100,7 @@ public class DeploymentManagerLifeCyclePathTest
 
         // Setup JMX
         MBeanContainer mbContainer=new MBeanContainer(ManagementFactory.getPlatformMBeanServer());
-        mbContainer.start();
-        mbContainer.addBean(depman);
+        depman.addBean(mbContainer);
 
         depman.addLifeCycleBinding(pathtracker);
         depman.addAppProvider(mockProvider);
@@ -116,10 +116,10 @@ public class DeploymentManagerLifeCyclePathTest
 
         // Request Deploy of App
         depman.requestAppGoal(app,"deployed");
-        
+
         JmxServiceConnection jmxConnection = new JmxServiceConnection();
         jmxConnection.connect();
-        
+
         MBeanServerConnection mbsConnection = jmxConnection.getConnection();
         ObjectName dmObjName = new ObjectName("org.eclipse.jetty.deploy:type=deploymentmanager,id=0");
         String[] params = new String[] {"mock-foo-webapp-1.war", "undeployed"};
@@ -135,5 +135,5 @@ public class DeploymentManagerLifeCyclePathTest
         expected.add("undeployed");
 
         pathtracker.assertExpected("Test JMX StateTransition / Deployed -> Undeployed",expected);
-    }    
+    }
 }
diff --git a/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/DeploymentManagerTest.java b/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/DeploymentManagerTest.java
index 847a2f2..5333fe8 100644
--- a/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/DeploymentManagerTest.java
+++ b/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/DeploymentManagerTest.java
@@ -22,6 +22,7 @@ import java.util.Collection;
 import java.util.Set;
 
 import org.eclipse.jetty.deploy.test.XmlConfiguredJetty;
+import org.eclipse.jetty.server.handler.ContextHandlerCollection;
 import org.eclipse.jetty.toolchain.test.TestingDir;
 import org.junit.Assert;
 import org.junit.Rule;
@@ -29,13 +30,14 @@ import org.junit.Test;
 
 public class DeploymentManagerTest
 {
-	@Rule
-	public TestingDir testdir = new TestingDir();
+    @Rule
+    public TestingDir testdir = new TestingDir();
 
-	@Test
+    @Test
     public void testReceiveApp() throws Exception
     {
         DeploymentManager depman = new DeploymentManager();
+        depman.setContexts(new ContextHandlerCollection());
         depman.setDefaultLifeCycleGoal(null); // no default
         AppLifeCyclePathCollector pathtracker = new AppLifeCyclePathCollector();
         MockAppProvider mockProvider = new MockAppProvider();
@@ -84,6 +86,7 @@ public class DeploymentManagerTest
         {
             jetty = new XmlConfiguredJetty(testdir);
             jetty.addConfiguration("jetty.xml");
+            jetty.addConfiguration("jetty-http.xml");
             jetty.addConfiguration("jetty-deploymgr-contexts.xml");
 
             // Should not throw an Exception
diff --git a/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/JmxServiceConnection.java b/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/JmxServiceConnection.java
new file mode 100644
index 0000000..57b47ef
--- /dev/null
+++ b/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/JmxServiceConnection.java
@@ -0,0 +1,154 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.deploy;
+
+import java.io.IOException;
+import java.lang.management.ManagementFactory;
+
+import javax.management.MBeanServer;
+import javax.management.MBeanServerConnection;
+import javax.management.remote.JMXConnector;
+import javax.management.remote.JMXConnectorFactory;
+import javax.management.remote.JMXConnectorServer;
+import javax.management.remote.JMXConnectorServerFactory;
+import javax.management.remote.JMXServiceURL;
+
+import org.eclipse.jetty.toolchain.test.IO;
+
+/**
+ * JmxServiceConnection
+ * 
+ * Provides ability to create a connection to either an external
+ * JMX server, or a loopback connection to the internal one.
+ */
+public class JmxServiceConnection
+{
+    private String serviceUrl;
+    private MBeanServer server;
+    private JMXConnectorServer connectorServer;
+    private JMXConnector serverConnector;
+    private MBeanServerConnection serviceConnection;
+
+    /**
+     * Construct a loopback connection to an internal server
+     */
+    public JmxServiceConnection()
+    {
+        this(null);
+    }
+
+    /**
+     * Construct a connection to specified server
+     * 
+     * @param url
+     *            URL of JMX server
+     */
+    public JmxServiceConnection(String url)
+    {
+        serviceUrl = url;
+    }
+
+    /**
+     * Retrieve an external URL for the JMX server
+     * 
+     * @return service URL
+     */
+    public String getServiceUrl()
+    {
+        return serviceUrl;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Retrieve a connection to MBean server
+     * 
+     * @return connection to MBean server
+     */
+    public MBeanServerConnection getConnection()
+    {
+        return serviceConnection;
+    }
+
+    public void connect() throws IOException
+    {
+        if (serviceConnection == null)
+        {
+            if (serviceUrl == null)
+            {
+                openLoopbackConnection();
+            }
+            else
+            {
+                openServerConnection(serviceUrl);
+            }
+        }
+    }
+
+    /**
+     * Open a loopback connection to local JMX server
+     * 
+     * @throws IOException
+     */
+    private void openLoopbackConnection() throws IOException
+    {
+        server = ManagementFactory.getPlatformMBeanServer();
+
+        JMXServiceURL serviceUrl = new JMXServiceURL("service:jmx:rmi://");
+        connectorServer = JMXConnectorServerFactory.newJMXConnectorServer(serviceUrl,null,server);
+        connectorServer.start();
+
+        this.serviceUrl = connectorServer.getAddress().toString();
+
+        serverConnector = JMXConnectorFactory.connect(connectorServer.getAddress());
+        serviceConnection = serverConnector.getMBeanServerConnection();
+    }
+
+    /**
+     * Open a connection to remote JMX server
+     * 
+     * @param url
+     * @throws IOException
+     */
+    private void openServerConnection(String url) throws IOException
+    {
+        serviceUrl = url;
+        serverConnector = JMXConnectorFactory.connect(new JMXServiceURL(serviceUrl));
+        serviceConnection = serverConnector.getMBeanServerConnection();
+    }
+
+    /**
+     * Close the connections
+     */
+    public void disconnect()
+    {
+        IO.close(serverConnector);
+
+        if (connectorServer != null)
+        {
+            try
+            {
+                connectorServer.stop();
+            }
+            catch (Exception ignore)
+            {
+                /* ignore */
+            }
+        }
+    }
+}
diff --git a/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/bindings/GlobalWebappConfigBindingTest.java b/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/bindings/GlobalWebappConfigBindingTest.java
index dacfab8..95a168d 100644
--- a/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/bindings/GlobalWebappConfigBindingTest.java
+++ b/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/bindings/GlobalWebappConfigBindingTest.java
@@ -18,12 +18,14 @@
 
 package org.eclipse.jetty.deploy.bindings;
 
-import static org.hamcrest.Matchers.*;
+import static org.hamcrest.Matchers.greaterThan;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.isIn;
+import static org.hamcrest.Matchers.not;
 
 import java.io.File;
 import java.util.List;
 
-import org.eclipse.jetty.deploy.providers.ScanningAppProvider;
 import org.eclipse.jetty.deploy.test.XmlConfiguredJetty;
 import org.eclipse.jetty.toolchain.test.IO;
 import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
@@ -50,9 +52,10 @@ public class GlobalWebappConfigBindingTest
     {
         jetty = new XmlConfiguredJetty(testdir);
         jetty.addConfiguration("jetty.xml");
+        jetty.addConfiguration("jetty-http.xml");
 
         // Setup initial context
-        jetty.copyContext("foo.xml","foo.xml");
+        jetty.copyWebapp("foo.xml","foo.xml");
         jetty.copyWebapp("foo-webapp-1.war","foo.war");
 
     }
@@ -68,9 +71,9 @@ public class GlobalWebappConfigBindingTest
     public void testServerAndSystemClassesOverride() throws Exception
     {
         File srcXml = MavenTestingUtils.getTestResourceFile("context-binding-test-1.xml");
-        File destXml = new File(jetty.getJettyHome(),"context-binding-test-1.xml"); 
+        File destXml = new File(jetty.getJettyHome(),"context-binding-test-1.xml");
         IO.copy(srcXml,destXml);
-        
+
         PathAssert.assertFileExists("Context Binding XML",destXml);
 
         jetty.addConfiguration("binding-test-contexts-1.xml");
@@ -79,7 +82,7 @@ public class GlobalWebappConfigBindingTest
 
         List<WebAppContext> contexts = jetty.getWebAppContexts();
         Assert.assertThat("List of Contexts", contexts, hasSize(greaterThan(0)));
-        
+
         WebAppContext context = contexts.get(0);
 
         Assert.assertNotNull("Context should not be null",context);
@@ -101,7 +104,7 @@ public class GlobalWebappConfigBindingTest
         //                jndiPackage = true;
         //            }
         //        }
-        //        
+        //
         //        Assert.assertFalse(jndiPackage);
     }
 }
diff --git a/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/graph/GraphTest.java b/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/graph/GraphTest.java
index 8199029..df46048 100644
--- a/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/graph/GraphTest.java
+++ b/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/graph/GraphTest.java
@@ -18,11 +18,9 @@
 
 package org.eclipse.jetty.deploy.graph;
 
-import junit.framework.Assert;
-
+import org.junit.Assert;
 import org.junit.Test;
 
-
 public class GraphTest
 {
     final Node nodeA = new Node("A");
@@ -35,22 +33,22 @@ public class GraphTest
     @Test
     public void testPath()
     {
-        
+
         Path path = new Path();
-        
-        Assert.assertEquals(0,path.nodes());
+
+        Assert.assertEquals(0, path.nodes());
         Assert.assertEquals(null,path.firstNode());
         Assert.assertEquals(null,path.lastNode());
-        
+
         path.add(new Edge(nodeA ,nodeB));
         Assert.assertEquals(2,path.nodes());
         Assert.assertEquals(nodeA,path.firstNode());
         Assert.assertEquals(nodeB,path.lastNode());
-        
+
         path.add(new Edge(nodeB ,nodeC));
         Assert.assertEquals(3,path.nodes());
         Assert.assertEquals(nodeA,path.firstNode());
-        Assert.assertEquals(nodeC,path.lastNode());       
+        Assert.assertEquals(nodeC,path.lastNode());
     }
 
     @Test
@@ -90,7 +88,7 @@ public class GraphTest
         Assert.assertEquals(2,path.nodes());
         path = graph.getPath(nodeB,nodeC);
         Assert.assertEquals(2,path.nodes());
-    
+
     }
 
     @Test
@@ -105,12 +103,12 @@ public class GraphTest
         Assert.assertEquals(4,graph.getEdges().size());
         Path path = graph.getPath(nodeA,nodeC);
         Assert.assertEquals(3,path.nodes());
-        
+
         path = graph.getPath(nodeC,nodeA);
         Assert.assertEquals(null,path);
-        
+
     }
-    
+
     @Test
     public void testSquareCyclic()
     {
@@ -123,16 +121,16 @@ public class GraphTest
         Assert.assertEquals(4,graph.getEdges().size());
         Path path = graph.getPath(nodeA,nodeB);
         Assert.assertEquals(2,path.nodes());
-        
+
         path = graph.getPath(nodeA,nodeC);
         Assert.assertEquals(3,path.nodes());
         path = graph.getPath(nodeA,nodeD);
         Assert.assertEquals(4,path.nodes());
-        
+
         graph.addNode(nodeE);
         path = graph.getPath(nodeA,nodeE);
         Assert.assertEquals(null,path);
     }
-    
-    
+
+
 }
diff --git a/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/ScanningAppProviderRuntimeUpdatesTest.java b/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/ScanningAppProviderRuntimeUpdatesTest.java
index 0658abc..158d62b 100644
--- a/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/ScanningAppProviderRuntimeUpdatesTest.java
+++ b/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/ScanningAppProviderRuntimeUpdatesTest.java
@@ -62,6 +62,7 @@ public class ScanningAppProviderRuntimeUpdatesTest
         
         jetty = new XmlConfiguredJetty(testdir);
         jetty.addConfiguration("jetty.xml");
+        jetty.addConfiguration("jetty-http.xml");
         jetty.addConfiguration("jetty-deploymgr-contexts.xml");
 
         // Should not throw an Exception
@@ -71,7 +72,7 @@ public class ScanningAppProviderRuntimeUpdatesTest
         jetty.start();
 
         // monitor tick
-        DeploymentManager dm = jetty.getServer().getBeans(DeploymentManager.class).get(0);
+        DeploymentManager dm = jetty.getServer().getBean(DeploymentManager.class);
         for (AppProvider provider : dm.getAppProviders())
         {
             if (provider instanceof ScanningAppProvider)
@@ -112,7 +113,7 @@ public class ScanningAppProviderRuntimeUpdatesTest
         }
         while(_scans.get()<scan);
     }
-    
+
     /**
      * Simple webapp deployment after startup of server.
      */
@@ -120,7 +121,7 @@ public class ScanningAppProviderRuntimeUpdatesTest
     public void testAfterStartupContext() throws IOException
     {
         jetty.copyWebapp("foo-webapp-1.war","foo.war");
-        jetty.copyContext("foo.xml","foo.xml");
+        jetty.copyWebapp("foo.xml","foo.xml");
 
         waitForDirectoryScan();
         waitForDirectoryScan();
@@ -135,19 +136,20 @@ public class ScanningAppProviderRuntimeUpdatesTest
     public void testAfterStartupThenRemoveContext() throws IOException
     {
         jetty.copyWebapp("foo-webapp-1.war","foo.war");
-        jetty.copyContext("foo.xml","foo.xml");
+        jetty.copyWebapp("foo.xml","foo.xml");
 
         waitForDirectoryScan();
         waitForDirectoryScan();
 
         jetty.assertWebAppContextsExists("/foo");
 
-        jetty.removeContext("foo.xml");
+        jetty.removeWebapp("foo.war");
+        jetty.removeWebapp("foo.xml");
 
         waitForDirectoryScan();
         waitForDirectoryScan();
 
-        // FIXME: hot undeploy with removal not working! - jetty.assertNoWebAppContexts();
+        jetty.assertNoWebAppContexts();
     }
 
     /**
@@ -161,9 +163,9 @@ public class ScanningAppProviderRuntimeUpdatesTest
         Assume.assumeTrue(!OS.IS_WINDOWS);
         Assume.assumeTrue(!OS.IS_OSX); // build server has issues with finding itself apparently
 
-        
+
         jetty.copyWebapp("foo-webapp-1.war","foo.war");
-        jetty.copyContext("foo.xml","foo.xml");
+        jetty.copyWebapp("foo.xml","foo.xml");
 
         waitForDirectoryScan();
         waitForDirectoryScan();
@@ -174,9 +176,9 @@ public class ScanningAppProviderRuntimeUpdatesTest
         jetty.assertResponseContains("/foo/info","FooServlet-1");
 
         waitForDirectoryScan();
-        System.out.println("Updating war files");
+        //System.err.println("Updating war files");
+        jetty.copyWebapp("foo.xml","foo.xml"); // essentially "touch" the context xml
         jetty.copyWebapp("foo-webapp-2.war","foo.war");
-        jetty.copyContext("foo.xml","foo.xml"); // essentially "touch" the context xml
 
         // This should result in the existing foo.war being replaced with the new foo.war
         waitForDirectoryScan();
diff --git a/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/ScanningAppProviderStartupTest.java b/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/ScanningAppProviderStartupTest.java
index 489cf0c..2968787 100644
--- a/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/ScanningAppProviderStartupTest.java
+++ b/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/ScanningAppProviderStartupTest.java
@@ -30,8 +30,8 @@ import org.junit.Test;
  */
 public class ScanningAppProviderStartupTest
 {
-	@Rule
-	public TestingDir testdir = new TestingDir();
+        @Rule
+        public TestingDir testdir = new TestingDir();
     private static XmlConfiguredJetty jetty;
 
     @Before
@@ -39,10 +39,11 @@ public class ScanningAppProviderStartupTest
     {
         jetty = new XmlConfiguredJetty(testdir);
         jetty.addConfiguration("jetty.xml");
+        jetty.addConfiguration("jetty-http.xml");
         jetty.addConfiguration("jetty-deploymgr-contexts.xml");
 
         // Setup initial context
-        jetty.copyContext("foo.xml","foo.xml");
+        jetty.copyWebapp("foo.xml","foo.xml");
         jetty.copyWebapp("foo-webapp-1.war","foo.war");
 
         // Should not throw an Exception
diff --git a/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/WebAppProviderTest.java b/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/WebAppProviderTest.java
index dfde4e2..b5bcb17 100644
--- a/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/WebAppProviderTest.java
+++ b/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/WebAppProviderTest.java
@@ -40,10 +40,10 @@ public class WebAppProviderTest
     {
         jetty = new XmlConfiguredJetty(testdir);
         jetty.addConfiguration("jetty.xml");
+        jetty.addConfiguration("jetty-http.xml");
         jetty.addConfiguration("jetty-deploy-wars.xml");
 
         // Setup initial context
-        jetty.copyContext("foo.xml","foo.xml");
         jetty.copyWebapp("foo-webapp-1.war","foo.war");
 
         // Should not throw an Exception
@@ -69,7 +69,7 @@ public class WebAppProviderTest
         File workDir = jetty.getJettyDir("workish");
 
         System.err.println("workDir="+workDir);
-        
+
         // Test for regressions
         assertDirNotExists("root of work directory",workDir,"webinf");
         assertDirNotExists("root of work directory",workDir,"jsp");
@@ -80,16 +80,19 @@ public class WebAppProviderTest
 
     private static boolean hasJettyGeneratedPath(File basedir, String expectedWarFilename)
     {
-        for (File path : basedir.listFiles())
+        File[] paths = basedir.listFiles();
+        if (paths != null)
         {
-            if (path.exists() && path.isDirectory() && path.getName().startsWith("jetty-") && path.getName().contains(expectedWarFilename))
+            for (File path : paths)
             {
-                System.out.println("Found expected generated directory: " + path);
-                return true;
+                if (path.exists() && path.isDirectory() && path.getName().startsWith("jetty-") && path.getName().contains(expectedWarFilename))
+                {
+                    System.err.println("Found expected generated directory: " + path);
+                    return true;
+                }
             }
+            System.err.println("did not find "+expectedWarFilename+" in "+Arrays.asList(paths));
         }
-
-        System.err.println("did not find "+expectedWarFilename+" in "+Arrays.asList(basedir.listFiles()));
         return false;
     }
 
diff --git a/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/test/XmlConfiguredJetty.java b/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/test/XmlConfiguredJetty.java
index a2e8b98..de8270c 100644
--- a/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/test/XmlConfiguredJetty.java
+++ b/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/test/XmlConfiguredJetty.java
@@ -18,12 +18,13 @@
 
 package org.eclipse.jetty.deploy.test;
 
-import static org.hamcrest.Matchers.*;
+import static org.hamcrest.Matchers.is;
 
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.OutputStream;
 import java.net.InetAddress;
 import java.net.MalformedURLException;
 import java.net.URI;
@@ -36,16 +37,17 @@ import java.util.List;
 import java.util.Map;
 import java.util.Properties;
 
-import org.eclipse.jetty.deploy.DeploymentManager;
-import org.eclipse.jetty.http.HttpSchemes;
+import org.eclipse.jetty.http.HttpScheme;
 import org.eclipse.jetty.server.Connector;
 import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.NetworkConnector;
 import org.eclipse.jetty.server.Server;
 import org.eclipse.jetty.server.handler.HandlerCollection;
 import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
 import org.eclipse.jetty.toolchain.test.PathAssert;
 import org.eclipse.jetty.toolchain.test.TestingDir;
 import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.URIUtil;
 import org.eclipse.jetty.util.resource.Resource;
 import org.eclipse.jetty.webapp.WebAppContext;
 import org.eclipse.jetty.xml.XmlConfiguration;
@@ -57,19 +59,19 @@ import org.junit.Assert;
 public class XmlConfiguredJetty
 {
     private List<URL> _xmlConfigurations;
-    private Map<String,String> _properties = new HashMap<String,String>();
+    private Map<String,String> _properties = new HashMap<>();
     private Server _server;
     private int _serverPort;
-    private String _scheme = HttpSchemes.HTTP;
+    private String _scheme = HttpScheme.HTTP.asString();
     private File _jettyHome;
 
     public XmlConfiguredJetty(TestingDir testdir) throws IOException
     {
-        _xmlConfigurations = new ArrayList<URL>();
+        _xmlConfigurations = new ArrayList<>();
         Properties properties = new Properties();
 
         String jettyHomeBase = testdir.getDir().getAbsolutePath();
-        // Ensure we have a new (pristene) directory to work with. 
+        // Ensure we have a new (pristene) directory to work with.
         int idx = 0;
         _jettyHome = new File(jettyHomeBase + "#" + idx);
         while (_jettyHome.exists())
@@ -89,13 +91,6 @@ public class XmlConfiguredJetty
         IO.copyFile(MavenTestingUtils.getTestResourceFile("etc/realm.properties"),new File(etcDir,"realm.properties"));
         IO.copyFile(MavenTestingUtils.getTestResourceFile("etc/webdefault.xml"),new File(etcDir,"webdefault.xml"));
 
-        File contextsDir = new File(_jettyHome,"contexts");
-        if (contextsDir.exists())
-        {
-            deleteContents(contextsDir);
-        }
-        contextsDir.mkdirs();
-
         File webappsDir = new File(_jettyHome,"webapps");
         if (webappsDir.exists())
         {
@@ -129,15 +124,17 @@ public class XmlConfiguredJetty
 
         // Write out configuration for use by ConfigurationManager.
         File testConfig = new File(_jettyHome, "xml-configured-jetty.properties");
-        FileOutputStream out = new FileOutputStream(testConfig);
-        properties.store(out,"Generated by " + XmlConfiguredJetty.class.getName());
+        try (OutputStream out = new FileOutputStream(testConfig))
+        {
+            properties.store(out,"Generated by " + XmlConfiguredJetty.class.getName());
+        }
         for (Object key:properties.keySet())
-            _properties.put(String.valueOf(key),String.valueOf(properties.get(key)));
+            setProperty(String.valueOf(key),String.valueOf(properties.get(key)));
     }
 
     public void addConfiguration(File xmlConfigFile) throws MalformedURLException
     {
-        _xmlConfigurations.add(Resource.toURL(xmlConfigFile));
+        addConfiguration(Resource.toURL(xmlConfigFile));
     }
 
     public void addConfiguration(String testConfigName) throws MalformedURLException
@@ -157,7 +154,7 @@ public class XmlConfiguredJetty
         {
             for (WebAppContext context : contexts)
             {
-                System.out.println("WebAppContext should not exist:\n" + context);
+                System.err.println("WebAppContext should not exist:\n" + context);
             }
             Assert.assertEquals("Contexts.size",0,contexts.size());
         }
@@ -184,7 +181,7 @@ public class XmlConfiguredJetty
 
     public void assertResponseContains(String path, String needle) throws IOException
     {
-        System.out.println("Issuing request to " + path);
+        // System.err.println("Issuing request to " + path);
         String content = getResponse(path);
         Assert.assertTrue("Content should contain <" + needle + ">, instead got <" + content + ">",content.contains(needle));
     }
@@ -194,15 +191,15 @@ public class XmlConfiguredJetty
         List<WebAppContext> contexts = getWebAppContexts();
         if (expectedContextPaths.length != contexts.size())
         {
-            System.out.println("## Expected Contexts");
+            System.err.println("## Expected Contexts");
             for (String expected : expectedContextPaths)
             {
-                System.out.println(expected);
+                System.err.println(expected);
             }
-            System.out.println("## Actual Contexts");
+            System.err.println("## Actual Contexts");
             for (WebAppContext context : contexts)
             {
-                System.out.printf("%s ## %s%n",context.getContextPath(),context);
+                System.err.printf("%s ## %s%n",context.getContextPath(),context);
             }
             Assert.assertEquals("Contexts.size",expectedContextPaths.length,contexts.size());
         }
@@ -223,30 +220,18 @@ public class XmlConfiguredJetty
         }
     }
 
-    public void copyContext(String srcName, String destName) throws IOException
-    {
-        System.out.printf("Copying Context: %s -> %s%n",srcName,destName);
-        File srcDir = MavenTestingUtils.getTestResourceDir("contexts");
-        File destDir = new File(_jettyHome,"contexts");
-
-        File srcFile = new File(srcDir,srcName);
-        File destFile = new File(destDir,destName);
-
-        copyFile("Context",srcFile,destFile);
-    }
-
     private void copyFile(String type, File srcFile, File destFile) throws IOException
     {
         PathAssert.assertFileExists(type + " File",srcFile);
         IO.copyFile(srcFile,destFile);
         PathAssert.assertFileExists(type + " File",destFile);
-        System.out.printf("Copy %s: %s%n  To %s: %s%n",type,srcFile,type,destFile);
-        System.out.printf("Destination Exists: %s - %s%n",destFile.exists(),destFile);
+        System.err.printf("Copy %s: %s%n  To %s: %s%n",type,srcFile,type,destFile);
+        System.err.printf("Destination Exists: %s - %s%n",destFile.exists(),destFile);
     }
 
     public void copyWebapp(String srcName, String destName) throws IOException
     {
-        System.out.printf("Copying Webapp: %s -> %s%n",srcName,destName);
+        System.err.printf("Copying Webapp: %s -> %s%n",srcName,destName);
         File srcDir = MavenTestingUtils.getTestResourceDir("webapps");
         File destDir = new File(_jettyHome,"webapps");
 
@@ -258,33 +243,32 @@ public class XmlConfiguredJetty
 
     private void deleteContents(File dir)
     {
-        System.out.printf("Delete  (dir) %s/%n",dir);
+        // System.err.printf("Delete  (dir) %s/%n",dir);
         if (!dir.exists())
         {
             return;
         }
 
-        for (File file : dir.listFiles())
+        File[] files = dir.listFiles();
+        if (files != null)
         {
-            // Safety measure. only recursively delete within target directory.
-            if (file.isDirectory() && file.getAbsolutePath().contains("target" + File.separator))
-            {
-                deleteContents(file);
-                Assert.assertTrue("Delete failed: " + file.getAbsolutePath(),file.delete());
-            }
-            else
+            for (File file : files)
             {
-                System.out.printf("Delete (file) %s%n",file);
-                Assert.assertTrue("Delete failed: " + file.getAbsolutePath(),file.delete());
+                // Safety measure. only recursively delete within target directory.
+                if (file.isDirectory() && file.getAbsolutePath().contains("target" + File.separator))
+                {
+                    deleteContents(file);
+                    Assert.assertTrue("Delete failed: " + file.getAbsolutePath(),file.delete());
+                }
+                else
+                {
+                    System.err.printf("Delete (file) %s%n",file);
+                    Assert.assertTrue("Delete failed: " + file.getAbsolutePath(),file.delete());
+                }
             }
         }
     }
 
-    public DeploymentManager getActiveDeploymentManager()
-    {
-        return _server.getBean(DeploymentManager.class);
-    }
-
     public File getJettyDir(String name)
     {
         return new File(_jettyHome,name);
@@ -312,18 +296,15 @@ public class XmlConfiguredJetty
 
     public URI getServerURI() throws UnknownHostException
     {
-        StringBuffer uri = new StringBuffer();
-        uri.append(this._scheme).append("://");
-        uri.append(InetAddress.getLocalHost().getHostAddress());
-        uri.append(":").append(this._serverPort);
+        StringBuilder uri = new StringBuilder();
+        URIUtil.appendSchemeHostPort(uri, getScheme(), InetAddress.getLocalHost().getHostAddress(), getServerPort());
         return URI.create(uri.toString());
     }
 
     public List<WebAppContext> getWebAppContexts()
     {
-        List<WebAppContext> contexts = new ArrayList<WebAppContext>();
+        List<WebAppContext> contexts = new ArrayList<>();
         HandlerCollection handlers = (HandlerCollection)_server.getHandler();
-        System.out.println(_server.dump());
         Handler children[] = handlers.getChildHandlers();
 
         for (Handler handler : children)
@@ -349,9 +330,7 @@ public class XmlConfiguredJetty
             URL configURL = this._xmlConfigurations.get(i);
             XmlConfiguration configuration = new XmlConfiguration(configURL);
             if (last != null)
-            {
                 configuration.getIdMap().putAll(last.getIdMap());
-            }
             configuration.getProperties().putAll(_properties);
             obj[i] = configuration.configure();
             last = configuration;
@@ -382,17 +361,16 @@ public class XmlConfiguredJetty
         Assert.assertEquals("Server load count",1,serverCount);
 
         this._server = foundServer;
-        this._server.setGracefulShutdown(10);
-
+        this._server.setStopTimeout(10);
     }
 
-    public void removeContext(String name)
+    public void removeWebapp(String name)
     {
-        File destDir = new File(_jettyHome,"contexts");
+        File destDir = new File(_jettyHome,"webapps");
         File contextFile = new File(destDir,name);
         if (contextFile.exists())
         {
-            Assert.assertTrue("Delete of Context file: " + contextFile.getAbsolutePath(),contextFile.delete());
+            Assert.assertTrue("Delete of Webapp file: " + contextFile.getAbsolutePath(),contextFile.delete());
         }
     }
 
@@ -413,22 +391,22 @@ public class XmlConfiguredJetty
         _server.start();
 
         // Find the active server port.
-        this._serverPort = (-1);
+        _serverPort = -1;
         Connector connectors[] = _server.getConnectors();
-        for (int i = 0; i < connectors.length; i++)
+        for (int i = 0; _serverPort<0 && i < connectors.length; i++)
         {
-            Connector connector = connectors[i];
-            if (connector.getLocalPort() > 0)
+            if (connectors[i] instanceof NetworkConnector)
             {
-                this._serverPort = connector.getLocalPort();
-                break;
+                int port = ((NetworkConnector)connectors[i]).getLocalPort();
+                if (port>0)
+                    _serverPort=port;
             }
         }
 
         Assert.assertTrue("Server Port is between 1 and 65535. Actually <" + _serverPort + ">",(1 <= this._serverPort) && (this._serverPort <= 65535));
 
         // Uncomment to have server start and continue to run (without exiting)
-        // System.out.printf("Listening to port %d%n",this.serverPort);
+        // System.err.printf("Listening to port %d%n",this.serverPort);
         // server.join();
     }
 
@@ -436,5 +414,4 @@ public class XmlConfiguredJetty
     {
         _server.stop();
     }
-
 }
diff --git a/jetty-deploy/src/test/resources/binding-test-contexts-1.xml b/jetty-deploy/src/test/resources/binding-test-contexts-1.xml
index 65e13ab..3c6a950 100644
--- a/jetty-deploy/src/test/resources/binding-test-contexts-1.xml
+++ b/jetty-deploy/src/test/resources/binding-test-contexts-1.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0"?>
-<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
 
 <Configure id="Server" class="org.eclipse.jetty.server.Server">
 
@@ -25,14 +25,14 @@
     <Item>org.eclipse.jetty.servlet.DefaultServlet</Item>
   </Array>
 
-  <Call name="addLifeCycle">
+  <Call name="addBean">
     <Arg>
       <New id="DeploymentManager" class="org.eclipse.jetty.deploy.DeploymentManager">
         <Set name="contexts">
-          <Ref id="Contexts" />
-        </Set>      
-        
-        <Ref id="DeploymentManager">
+          <Ref refid="Contexts" />
+        </Set>
+
+        <Ref refid="DeploymentManager">
           <Call name="addLifeCycleBinding">
             <Arg>
               <New class="org.eclipse.jetty.deploy.bindings.GlobalWebappConfigBinding">
@@ -41,16 +41,16 @@
             </Arg>
           </Call>
        </Ref>
-        
+
         <!-- Providers of Apps -->
         <Set name="appProviders">
           <Array type="org.eclipse.jetty.deploy.AppProvider">
             <Item>
-             <New class="org.eclipse.jetty.deploy.providers.ContextProvider">
-              <Set name="monitoredDirName"><SystemProperty name="jetty.home" />/contexts</Set>
+             <New class="org.eclipse.jetty.deploy.providers.WebAppProvider">
+              <Set name="monitoredDirName"><SystemProperty name="jetty.home" />/webapps</Set>
               <Set name="scanInterval">1</Set>
               <Set name="configurationManager">
-                <New class="org.eclipse.jetty.deploy.FileConfigurationManager">
+                <New class="org.eclipse.jetty.deploy.PropertiesConfigurationManager">
                   <Set name="file">
                     <SystemProperty name="jetty.home"/>/xml-configured-jetty.properties
                   </Set>
@@ -58,13 +58,6 @@
               </Set>
              </New>
             </Item>
-            <Item>
-             <New class="org.eclipse.jetty.deploy.providers.WebAppProvider">
-              <Set name="monitoredDirName"><SystemProperty name="jetty.home" />/webapps</Set>
-              <Set name="scanInterval">1</Set>
-              <Set name="contextXmlDir"><SystemProperty name="jetty.home" />/contexts</Set>
-             </New>
-            </Item>
           </Array>
         </Set>
       </New>
diff --git a/jetty-deploy/src/test/resources/context-binding-test-1.xml b/jetty-deploy/src/test/resources/context-binding-test-1.xml
index 925f257..ae997ac 100644
--- a/jetty-deploy/src/test/resources/context-binding-test-1.xml
+++ b/jetty-deploy/src/test/resources/context-binding-test-1.xml
@@ -26,6 +26,6 @@
     <Item>org.eclipse.jetty.servlet.DefaultServlet</Item>
   </Array>
 
-  <Set name="serverClasses"><Ref id="serverClasses"/></Set>
-  <Set name="systemClasses"><Ref id="systemClasses"/></Set-->
+  <Set name="serverClasses"><Ref refid="serverClasses"/></Set>
+  <Set name="systemClasses"><Ref refid="systemClasses"/></Set-->
 </Configure>
diff --git a/jetty-deploy/src/test/resources/contexts/foo.xml b/jetty-deploy/src/test/resources/contexts/foo.xml
deleted file mode 100644
index 4c730f0..0000000
--- a/jetty-deploy/src/test/resources/contexts/foo.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0"  encoding="ISO-8859-1"?>
-<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
-<Configure class="org.eclipse.jetty.webapp.WebAppContext">
-    <Set name="contextPath">/foo</Set>
-    <Set name="war">
-        <Property name="test.webapps" default="." />/foo.war
-    </Set>
-</Configure>
diff --git a/jetty-deploy/src/test/resources/etc/webdefault.xml b/jetty-deploy/src/test/resources/etc/webdefault.xml
index 35a5a9b..13a96e9 100644
--- a/jetty-deploy/src/test/resources/etc/webdefault.xml
+++ b/jetty-deploy/src/test/resources/etc/webdefault.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="ISO-8859-1"?>
+<?xml version="1.0" encoding="UTF-8"?>
 
 <!-- ===================================================================== -->
 <!-- This file contains the default descriptor for web applications.       -->
@@ -331,7 +331,7 @@
   <!-- ==================================================================== -->
   <!-- Default MIME mappings                                                -->
   <!-- The default MIME mappings are provided by the mime.properties        -->
-  <!-- resource in the org.eclipse.jetty.server.jar file.  Additional or modified  -->
+  <!-- resource in the jetty-http.jar file.  Additional or modified         -->
   <!-- mappings may be specified here                                       -->
   <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -->
   <!-- UNCOMMENT TO ACTIVATE
diff --git a/jetty-deploy/src/test/resources/jetty-deploy-wars.xml b/jetty-deploy/src/test/resources/jetty-deploy-wars.xml
index 6ab95b6..9933cb6 100644
--- a/jetty-deploy/src/test/resources/jetty-deploy-wars.xml
+++ b/jetty-deploy/src/test/resources/jetty-deploy-wars.xml
@@ -1,15 +1,15 @@
 <?xml version="1.0"?>
-<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
 
 <Configure id="Server" class="org.eclipse.jetty.server.Server">
 
-  <Call name="addLifeCycle">
+  <Call name="addBean">
     <Arg>
       <New id="DeploymentManager" class="org.eclipse.jetty.deploy.DeploymentManager">
         <Set name="contexts">
-          <Ref id="Contexts" />
+          <Ref refid="Contexts" />
         </Set>
-        
+
         <!-- Providers of Apps -->
         <Set name="appProviders">
           <Array type="org.eclipse.jetty.deploy.AppProvider">
diff --git a/jetty-deploy/src/test/resources/jetty-deploymgr-contexts.xml b/jetty-deploy/src/test/resources/jetty-deploymgr-contexts.xml
index c542672..8d55d22 100644
--- a/jetty-deploy/src/test/resources/jetty-deploymgr-contexts.xml
+++ b/jetty-deploy/src/test/resources/jetty-deploymgr-contexts.xml
@@ -1,40 +1,37 @@
 <?xml version="1.0"?>
-<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
 
 <Configure id="Server" class="org.eclipse.jetty.server.Server">
-
-  <Call name="addLifeCycle">
+  <Call name="addBean">
     <Arg>
       <New id="DeploymentManager" class="org.eclipse.jetty.deploy.DeploymentManager">
         <Set name="contexts">
-          <Ref id="Contexts" />
+          <Ref refid="Contexts" />
         </Set>
         
-        <!-- Providers of Apps -->
-        <Set name="appProviders">
-          <Array type="org.eclipse.jetty.deploy.AppProvider">
-            <Item>
-             <New class="org.eclipse.jetty.deploy.providers.ContextProvider">
-              <Set name="monitoredDirName"><SystemProperty name="jetty.home" />/contexts</Set>
+        <Call name="setContextAttribute">
+          <Arg>org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern</Arg>
+          <Arg>.*/servlet-api-[^/]*\.jar$</Arg>
+        </Call>
+        
+        <Call id="webappprovider" name="addAppProvider">
+          <Arg>
+            <New class="org.eclipse.jetty.deploy.providers.WebAppProvider">
+              <Set name="monitoredDirName"><Property name="jetty.home" default="." />/webapps</Set>
+              <Set name="defaultsDescriptor"><Property name="jetty.home" default="." />/etc/webdefault.xml</Set>
               <Set name="scanInterval">1</Set>
+              <Set name="extractWars">true</Set>
               <Set name="configurationManager">
-                <New class="org.eclipse.jetty.deploy.FileConfigurationManager">
+                <New class="org.eclipse.jetty.deploy.PropertiesConfigurationManager">
                   <Set name="file">
                     <SystemProperty name="jetty.home"/>/xml-configured-jetty.properties
                   </Set>
                 </New>
               </Set>
-             </New>
-            </Item>
-            <Item>
-             <New class="org.eclipse.jetty.deploy.providers.WebAppProvider">
-              <Set name="monitoredDirName"><SystemProperty name="jetty.home" />/webapps</Set>
-              <Set name="scanInterval">1</Set>
-              <Set name="contextXmlDir"><SystemProperty name="jetty.home" />/contexts</Set>
-             </New>
-            </Item>
-          </Array>
-        </Set>
+            </New>
+          </Arg>
+        </Call>
+
       </New>
     </Arg>
   </Call>
diff --git a/jetty-deploy/src/test/resources/jetty-http.xml b/jetty-deploy/src/test/resources/jetty-http.xml
new file mode 100644
index 0000000..42b889b
--- /dev/null
+++ b/jetty-deploy/src/test/resources/jetty-http.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0"?>
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
+
+<!-- ============================================================= -->
+<!-- Configure the Jetty Server instance with an ID "Server"       -->
+<!-- by adding a HTTP connector.                                   -->
+<!-- This configuration must be used in conjunction with jetty.xml -->
+<!-- ============================================================= -->
+<Configure id="Server" class="org.eclipse.jetty.server.Server">
+
+  <!-- =========================================================== -->
+  <!-- Add a HTTP Connector.                                       -->
+  <!-- Configure an o.e.j.server.ServerConnector with a single     -->
+  <!-- HttpConnectionFactory instance using the common httpConfig  -->
+  <!-- instance defined in jetty.xml                               -->
+  <!--                                                             -->
+  <!-- Consult the javadoc of o.e.j.server.ServerConnector and     -->
+  <!-- o.e.j.server.HttpConnectionFactory for all configuration    -->
+  <!-- that may be set here.                                       -->
+  <!-- =========================================================== -->
+  <Call name="addConnector">
+    <Arg>
+      <New class="org.eclipse.jetty.server.ServerConnector">
+        <Arg name="server"><Ref refid="Server" /></Arg>
+        <Arg name="factories">
+          <Array type="org.eclipse.jetty.server.ConnectionFactory">
+            <Item>
+              <New class="org.eclipse.jetty.server.HttpConnectionFactory">
+                <Arg name="config"><Ref refid="httpConfig" /></Arg>
+              </New>
+            </Item>
+          </Array>
+        </Arg>
+        <Set name="host"></Set>
+        <Set name="port">0</Set>
+        <Set name="idleTimeout">300000</Set>
+      </New>
+    </Arg>
+  </Call>
+
+</Configure>
diff --git a/jetty-deploy/src/test/resources/jetty-logging.properties b/jetty-deploy/src/test/resources/jetty-logging.properties
new file mode 100644
index 0000000..9d7bbe4
--- /dev/null
+++ b/jetty-deploy/src/test/resources/jetty-logging.properties
@@ -0,0 +1,3 @@
+org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
+org.eclipse.jetty.deploy.LEVEL=WARN
+org.eclipse.jetty.util.Scanner=WARN
diff --git a/jetty-deploy/src/test/resources/jetty.xml b/jetty-deploy/src/test/resources/jetty.xml
index 4340e7a..25332f6 100644
--- a/jetty-deploy/src/test/resources/jetty.xml
+++ b/jetty-deploy/src/test/resources/jetty.xml
@@ -1,76 +1,109 @@
 <?xml version="1.0"?>
-<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
 
 <!-- =============================================================== -->
-<!-- Configure the Jetty Server                                      -->
-<!--                                                                 -->
 <!-- Documentation of this file format can be found at:              -->
-<!-- http://docs.codehaus.org/display/JETTY/jetty.xml                -->
+<!-- http://wiki.eclipse.org/Jetty/Reference/jetty.xml_syntax        -->
+<!--                                                                 -->
+<!-- Additional configuration files are available in $JETTY_HOME/etc -->
+<!-- and can be mixed in. See start.ini file for the default         -->
+<!-- configuration files.                                            -->
 <!--                                                                 -->
+<!-- For a description of the configuration mechanism, see the       -->
+<!-- output of:                                                      -->
+<!--   java -jar start.jar -?                                        -->
 <!-- =============================================================== -->
 
-
+<!-- =============================================================== -->
+<!-- Configure a Jetty Server instance with an ID "Server"           -->
+<!-- Other configuration files may also configure the "Server"       -->
+<!-- ID, in which case they are adding configuration to the same     -->
+<!-- instance.  If other configuration have a different ID, they     -->
+<!-- will create and configure another instance of Jetty.            -->
+<!-- Consult the javadoc of o.e.j.server.Server for all              -->
+<!-- configuration that may be set here.                             -->
+<!-- =============================================================== -->
 <Configure id="Server" class="org.eclipse.jetty.server.Server">
+
     <!-- =========================================================== -->
-    <!-- Server Thread Pool                                          -->
+    <!-- Configure the Server Thread Pool.                           -->
+    <!-- The server holds a common thread pool which is used by      -->
+    <!-- default as the executor used by all connectors and servlet  -->
+    <!-- dispatches.                                                 -->
+    <!--                                                             -->
+    <!-- Configuring a fixed thread pool is vital to controlling the -->
+    <!-- maximal memory footprint of the server and is a key tuning  -->
+    <!-- parameter for tuning.  In an application that rarely blocks -->
+    <!-- then maximal threads may be close to the number of 5*CPUs.  -->
+    <!-- In an application that frequently blocks, then maximal      -->
+    <!-- threads should be set as high as possible given the memory  -->
+    <!-- available.                                                  -->
+    <!--                                                             -->
+    <!-- Consult the javadoc of o.e.j.util.thread.QueuedThreadPool   -->
+    <!-- for all configuration that may be set here.                 -->
     <!-- =========================================================== -->
-    <Set name="ThreadPool">
-      <!-- Default queued blocking threadpool 
-      -->
-      <New class="org.eclipse.jetty.util.thread.QueuedThreadPool">
-        <Set name="minThreads">10</Set>
-        <Set name="maxThreads">200</Set>
-      </New>
-
-      <!-- Optional Java 5 bounded threadpool with job queue 
-      <New class="org.eclipse.thread.concurrent.ThreadPool">
-        <Set name="corePoolSize">50</Set>
-        <Set name="maximumPoolSize">50</Set>
-      </New>
-      -->
-    </Set>
+    <!-- uncomment to change type of threadpool
+    <Arg name="threadpool"><New id="threadpool" class="org.eclipse.jetty.util.thread.QueuedThreadPool"/></Arg>
+    -->
+    <Get name="ThreadPool">
+      <Set name="minThreads" type="int"><Property name="threads.min" default="10"/></Set>
+      <Set name="maxThreads" type="int"><Property name="threads.max" default="200"/></Set>
+      <Set name="idleTimeout" type="int"><Property name="threads.timeout" default="60000"/></Set>
+      <Set name="detailedDump">false</Set>
+    </Get>
 
     <!-- =========================================================== -->
-    <!-- Set connectors                                              -->
+    <!-- Add shared Scheduler instance                               -->
     <!-- =========================================================== -->
-
-    <Call name="addConnector">
+    <Call name="addBean">
       <Arg>
-          <New class="org.eclipse.jetty.server.nio.SelectChannelConnector">
-            <Set name="host"></Set>
-            <Set name="port">0</Set>
-            <Set name="maxIdleTime">300000</Set>
-            <Set name="Acceptors">2</Set>
-            <Set name="statsOn">false</Set>
-            <Set name="confidentialPort">8443</Set>
-        <Set name="lowResourcesConnections">20000</Set>
-        <Set name="lowResourcesMaxIdleTime">5000</Set>
-          </New>
+        <New class="org.eclipse.jetty.util.thread.ScheduledExecutorScheduler"/>
       </Arg>
     </Call>
 
-    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
-    <!-- To add a HTTPS SSL connector                                    -->
-    <!-- mixin jetty-ssl.xml:                                            -->
-    <!--   java -jar start.jar etc/jetty-ssl.xml                         -->
-    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
-    
-    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
-    <!-- To add a HTTP blocking connector                                -->
-    <!-- mixin jetty-bio.xml:                                            -->
-    <!--   java -jar start.jar etc/jetty-bio.xml                         -->
-    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
-    
-    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
-    <!-- To allow Jetty to be started from xinetd                        -->
-    <!-- mixin jetty-xinetd.xml:                                         -->
-    <!--   java -jar start.jar etc/jetty-xinetd.xml                      -->
-    <!--                                                                 -->
-    <!-- See jetty-xinetd.xml for further instructions.                  -->
-    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+    <!-- =========================================================== -->
+    <!-- Http Configuration.                                         -->
+    <!-- This is a common configuration instance used by all         -->
+    <!-- connectors that can carry HTTP semantics (HTTP, HTTPS, SPDY)-->
+    <!-- It configures the non wire protocol aspects of the HTTP     -->
+    <!-- semantic.                                                   -->
+    <!--                                                             -->
+    <!-- This configuration is only defined here and is used by      -->
+    <!-- reference from the jetty-http.xml, jetty-https.xml and      -->
+    <!-- jetty-spdy.xml configuration files which instantiate the    -->
+    <!-- connectors.                                                 -->
+    <!--                                                             -->
+    <!-- Consult the javadoc of o.e.j.server.HttpConfiguration       -->
+    <!-- for all configuration that may be set here.                 -->
+    <!-- =========================================================== -->
+    <New id="httpConfig" class="org.eclipse.jetty.server.HttpConfiguration">
+      <Set name="secureScheme">https</Set>
+      <Set name="securePort" type="java.lang.Integer"><Property name="jetty.secure.port" default="8443" /></Set>
+      <Set name="outputBufferSize">32768</Set>
+      <Set name="requestHeaderSize">8192</Set>
+      <Set name="responseHeaderSize">8192</Set>
+      <Set name="sendServerVersion">true</Set>
+      <Set name="sendDateHeader">false</Set>
+      <Set name="headerCacheSize">512</Set>
+
+      <!-- Uncomment to enable handling of X-Forwarded- style headers
+      <Call name="addCustomizer">
+        <Arg><New class="org.eclipse.jetty.server.ForwardedRequestCustomizer"/></Arg>
+      </Call>
+      -->
+    </New>
+
 
     <!-- =========================================================== -->
-    <!-- Set handler Collection Structure                            --> 
+    <!-- Set the default handler structure for the Server            -->
+    <!-- A handler collection is used to pass received requests to   -->
+    <!-- both the ContextHandlerCollection, which selects the next   -->
+    <!-- handler by context path and virtual host, and the           -->
+    <!-- DefaultHandler, which handles any requests not handled by   -->
+    <!-- the context handlers.                                       -->
+    <!-- Other handlers may be added to the "Handlers" collection,   -->
+    <!-- for example the jetty-requestlog.xml file adds the          -->
+    <!-- RequestLogHandler after the default handler                 -->
     <!-- =========================================================== -->
     <Set name="handler">
       <New id="Handlers" class="org.eclipse.jetty.server.handler.HandlerCollection">
@@ -82,58 +115,17 @@
            <Item>
              <New id="DefaultHandler" class="org.eclipse.jetty.server.handler.DefaultHandler"/>
            </Item>
-           <Item>
-             <New id="RequestLog" class="org.eclipse.jetty.server.handler.RequestLogHandler"/>
-           </Item>
          </Array>
         </Set>
       </New>
     </Set>
-    
-    <!-- =========================================================== -->
-    <!-- Configure Authentication Login Service                      -->
-    <!-- Realms may be configured for the entire server here, or     -->
-    <!-- they can be configured for a specific web app in a context  -->
-    <!-- configuration (see $(jetty.home)/contexts/test.xml for an   -->
-    <!-- example).                                                   -->
-    <!-- =========================================================== -->
-    <Call name="addBean">
-      <Arg>
-        <New class="org.eclipse.jetty.security.HashLoginService">
-          <Set name="name">Test Realm</Set>
-          <Set name="config"><SystemProperty name="jetty.home" default="."/>/etc/realm.properties</Set>
-          <Set name="refreshInterval">0</Set>
-        </New>
-      </Arg>
-    </Call>
-
-    <!-- =========================================================== -->
-    <!-- Configure Request Log                                       -->
-    <!-- Request logs  may be configured for the entire server here, -->
-    <!-- or they can be configured for a specific web app in a       -->
-    <!-- contexts configuration (see $(jetty.home)/contexts/test.xml -->
-    <!-- for an example).                                            -->
-    <!-- =========================================================== -->
-    <Ref id="RequestLog">
-      <Set name="requestLog">
-        <New id="RequestLogImpl" class="org.eclipse.jetty.server.NCSARequestLog">
-          <Set name="filename"><SystemProperty name="jetty.home" default="."/>/logs/yyyy_mm_dd.request.log</Set>
-          <Set name="filenameDateFormat">yyyy_MM_dd</Set>
-          <Set name="retainDays">90</Set>
-          <Set name="append">true</Set>
-          <Set name="extended">false</Set>
-          <Set name="logCookies">false</Set>
-          <Set name="LogTimeZone">GMT</Set>
-        </New>
-      </Set>
-    </Ref>
 
     <!-- =========================================================== -->
-    <!-- extra options                                               -->
+    <!-- extra server options                                        -->
     <!-- =========================================================== -->
     <Set name="stopAtShutdown">true</Set>
-    <Set name="sendServerVersion">true</Set>
-    <Set name="sendDateHeader">true</Set>
-    <Set name="gracefulShutdown">1000</Set>
+    <Set name="stopTimeout">5000</Set>
+    <Set name="dumpAfterStart"><Property name="jetty.dump.start" default="false"/></Set>
+    <Set name="dumpBeforeStop"><Property name="jetty.dump.stop" default="false"/></Set>
 
 </Configure>
diff --git a/jetty-deploy/src/test/resources/webapps/foo.xml b/jetty-deploy/src/test/resources/webapps/foo.xml
new file mode 100644
index 0000000..e7c6e9d
--- /dev/null
+++ b/jetty-deploy/src/test/resources/webapps/foo.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
+<Configure class="org.eclipse.jetty.webapp.WebAppContext">
+    <Set name="contextPath">/foo</Set>
+    <Set name="war">
+        <Property name="test.webapps" default="." />/foo.war
+    </Set>
+</Configure>
diff --git a/jetty-distribution/pom.xml b/jetty-distribution/pom.xml
index fa1cccb..d3e40a7 100644
--- a/jetty-distribution/pom.xml
+++ b/jetty-distribution/pom.xml
@@ -3,14 +3,15 @@
   <parent>
     <groupId>org.eclipse.jetty</groupId>
     <artifactId>jetty-project</artifactId>
-    <version>8.1.17.v20150415</version>
+    <version>9.2.13.v20150730</version>
   </parent>
   <artifactId>jetty-distribution</artifactId>
   <name>Jetty :: Distribution Assemblies</name>
   <url>http://www.eclipse.org/jetty</url>
   <packaging>pom</packaging>
   <properties>
-    <assembly-directory>target/distribution</assembly-directory>
+    <assembly-directory>${basedir}/target/distribution</assembly-directory>
+    <jetty-setuid-version>1.0.1</jetty-setuid-version>
   </properties>
   <build>
     <plugins>
@@ -65,8 +66,7 @@
             </goals>
             <configuration>
               <resourceBundles>
-                <resourceBundle>org.eclipse.jetty.toolchain:jetty-artifact-remote-resources:1.0</resourceBundle>
-                <resourceBundle>org.eclipse.jetty.toolchain:jetty-distribution-remote-resources:1.1</resourceBundle>
+                <resourceBundle>org.eclipse.jetty.toolchain:jetty-distribution-remote-resources:1.2</resourceBundle>
               </resourceBundles>
               <outputDirectory>${assembly-directory}</outputDirectory>
             </configuration>
@@ -101,10 +101,60 @@
                   <type>war</type>
                   <overWrite>true</overWrite>
                   <includes>**</includes>
-                  <outputDirectory>${assembly-directory}/webapps</outputDirectory>
+                  <outputDirectory>${assembly-directory}/demo-base/webapps</outputDirectory>
                   <destFileName>test.war</destFileName>
                 </artifactItem>
                 <artifactItem>
+                  <groupId>org.eclipse.jetty.tests</groupId>
+                  <artifactId>test-jaas-webapp</artifactId>
+                  <version>${project.version}</version>
+                  <type>war</type>
+                  <overWrite>true</overWrite>
+                  <includes>**</includes>
+                  <outputDirectory>${assembly-directory}/demo-base/webapps</outputDirectory>
+                  <destFileName>test-jaas.war</destFileName>
+                </artifactItem>
+                <artifactItem>
+                  <groupId>org.eclipse.jetty.tests</groupId>
+                  <artifactId>test-jndi-webapp</artifactId>
+                  <version>${project.version}</version>
+                  <type>war</type>
+                  <overWrite>true</overWrite>
+                  <includes>**</includes>
+                  <outputDirectory>${assembly-directory}/demo-base/webapps</outputDirectory>
+                  <destFileName>test-jndi.war</destFileName>
+                </artifactItem>
+                <artifactItem>
+                  <groupId>org.eclipse.jetty.tests</groupId>
+                  <artifactId>test-spec-webapp</artifactId>
+                  <version>${project.version}</version>
+                  <type>war</type>
+                  <overWrite>true</overWrite>
+                  <includes>**</includes>
+                  <outputDirectory>${assembly-directory}/demo-base/webapps</outputDirectory>
+                  <destFileName>test-spec.war</destFileName>
+                </artifactItem>
+                <artifactItem>
+                  <groupId>org.eclipse.jetty</groupId>
+                  <artifactId>test-proxy-webapp</artifactId>
+                  <version>${project.version}</version>
+                  <type>war</type>
+                  <overWrite>true</overWrite>
+                  <includes>**</includes>
+                  <outputDirectory>${assembly-directory}/demo-base/webapps</outputDirectory>
+                  <destFileName>xref-proxy.war</destFileName>
+                </artifactItem>
+                <artifactItem>
+                  <groupId>org.eclipse.jetty.example-async-rest</groupId>
+                  <artifactId>example-async-rest-webapp</artifactId>
+                  <version>${project.version}</version>
+                  <type>war</type>
+                  <overWrite>true</overWrite>
+                  <includes>**</includes>
+                  <outputDirectory>${assembly-directory}/demo-base/webapps</outputDirectory>
+                  <destFileName>async-rest.war</destFileName>
+                </artifactItem>
+                <artifactItem>
                   <groupId>org.eclipse.jetty</groupId>
                   <artifactId>jetty-start</artifactId>
                   <version>${project.version}</version>
@@ -118,6 +168,132 @@
             </configuration>
           </execution>
           <execution>
+            <id>copy-setuid-deps</id>
+            <phase>generate-resources</phase>
+            <goals>
+              <goal>copy</goal>
+            </goals>
+            <configuration>
+              <artifactItems>
+                <artifactItem>
+                  <groupId>org.eclipse.jetty.toolchain.setuid</groupId>
+                  <artifactId>jetty-setuid-java</artifactId>
+                  <version>${jetty-setuid-version}</version>
+                  <type>jar</type>
+                  <overWrite>true</overWrite>
+                  <outputDirectory>${assembly-directory}/lib/setuid</outputDirectory>
+                </artifactItem>
+                <artifactItem>
+                  <groupId>org.eclipse.jetty.toolchain.setuid</groupId>
+                  <artifactId>libsetuid-linux</artifactId>
+                  <version>${jetty-setuid-version}</version>
+                  <type>so</type>
+                  <overWrite>true</overWrite>
+                  <outputDirectory>${assembly-directory}/lib/setuid</outputDirectory>
+                   <destFileName>libsetuid-linux.so</destFileName>
+                </artifactItem>
+                <artifactItem>
+                  <groupId>org.eclipse.jetty.toolchain.setuid</groupId>
+                  <artifactId>libsetuid-osx</artifactId>
+                  <version>${jetty-setuid-version}</version>
+                  <type>so</type>
+                  <overWrite>true</overWrite>
+                  <outputDirectory>${assembly-directory}/lib/setuid</outputDirectory>
+                  <destFileName>libsetuid-osx.so</destFileName>
+                </artifactItem>
+              </artifactItems>
+            </configuration>
+          </execution>
+
+          <execution>
+            <id>unpack-setuid-config</id>
+            <phase>process-resources</phase>
+            <goals>
+              <goal>unpack</goal>
+            </goals>
+            <configuration>
+              <artifactItems>
+                <artifactItem>
+                  <groupId>org.eclipse.jetty.toolchain.setuid</groupId>
+                  <artifactId>jetty-setuid-java</artifactId>
+                  <version>${jetty-setuid-version}</version>
+                  <classifier>config</classifier>
+                  <type>jar</type>
+                  <overWrite>true</overWrite>
+                  <outputDirectory>${assembly-directory}</outputDirectory>
+                </artifactItem>
+              </artifactItems>
+              <excludes>META-INF/**</excludes>
+            </configuration>
+          </execution>
+
+          <execution>
+            <id>unpack-test-jaas-config</id>
+            <phase>process-resources</phase>
+            <goals>
+              <goal>unpack</goal>
+            </goals>
+            <configuration>
+              <artifactItems>
+                <artifactItem>
+                  <groupId>org.eclipse.jetty.tests</groupId>
+                  <artifactId>test-jaas-webapp</artifactId>
+                  <version>${project.version}</version>
+                  <classifier>config</classifier>
+                  <type>jar</type>
+                  <overWrite>true</overWrite>
+                  <outputDirectory>${assembly-directory}</outputDirectory>
+                </artifactItem>
+              </artifactItems>
+              <excludes>META-INF/**</excludes>
+            </configuration>
+          </execution>
+
+          <execution>
+            <id>unpack-test-jndi-config</id>
+            <phase>process-resources</phase>
+            <goals>
+              <goal>unpack</goal>
+            </goals>
+            <configuration>
+              <artifactItems>
+                <artifactItem>
+                  <groupId>org.eclipse.jetty.tests</groupId>
+                  <artifactId>test-jndi-webapp</artifactId>
+                  <version>${project.version}</version>
+                  <classifier>config</classifier>
+                  <type>jar</type>
+                  <overWrite>true</overWrite>
+                  <outputDirectory>${assembly-directory}</outputDirectory>
+                </artifactItem>
+              </artifactItems>
+              <excludes>META-INF/**</excludes>
+            </configuration>
+          </execution>
+
+          <execution>
+            <id>unpack-test-spec-config</id>
+            <phase>process-resources</phase>
+            <goals>
+              <goal>unpack</goal>
+            </goals>
+            <configuration>
+              <artifactItems>
+                <artifactItem>
+                  <groupId>org.eclipse.jetty.tests</groupId>
+                  <artifactId>test-spec-webapp</artifactId>
+                  <version>${project.version}</version>
+                  <classifier>config</classifier>
+                  <type>jar</type>
+                  <overWrite>true</overWrite>
+                  <outputDirectory>${assembly-directory}</outputDirectory>
+                </artifactItem>
+              </artifactItems>
+              <excludes>META-INF/**</excludes>
+            </configuration>
+          </execution>
+
+          <execution>
             <id>copy-lib-deps</id>
             <phase>generate-resources</phase>
             <goals>
@@ -125,14 +301,71 @@
             </goals>
             <configuration>
               <includeGroupIds>org.eclipse.jetty</includeGroupIds>
-              <excludeGroupIds>org.eclipse.jetty.orbit,org.eclipse.jetty.spdy</excludeGroupIds>
-              <excludeArtifactIds>jetty-all,jetty-start,jetty-monitor,jetty-jsp</excludeArtifactIds>
+              <excludeGroupIds>org.eclipse.jetty.orbit,org.eclipse.jetty.spdy,org.eclipse.jetty.websocket,org.eclipse.jetty.fcgi,org.eclipse.jetty.toolchain,org.apache.taglibs</excludeGroupIds>
+              <excludeArtifactIds>jetty-all,jetty-jsp,apache-jsp,apache-jstl,jetty-start,jetty-monitor,jetty-spring</excludeArtifactIds>
               <includeTypes>jar</includeTypes>
               <outputDirectory>${assembly-directory}/lib</outputDirectory>
             </configuration>
           </execution>
           <execution>
-            <id>copy-orbit-servlet-api-deps</id>
+            <id>copy-lib-websocket-deps</id>
+            <phase>generate-resources</phase>
+            <goals>
+              <goal>copy-dependencies</goal>
+            </goals>
+            <configuration>
+              <includeGroupIds>javax.websocket,org.eclipse.jetty.websocket</includeGroupIds>
+              <excludeArtifactIds>javax.websocket-client-api</excludeArtifactIds>
+              <includeTypes>jar</includeTypes>
+              <outputDirectory>${assembly-directory}/lib/websocket</outputDirectory>
+            </configuration>
+          </execution>
+          <execution>
+            <id>copy-lib-fcgi-deps</id>
+            <phase>generate-resources</phase>
+            <goals>
+              <goal>copy-dependencies</goal>
+            </goals>
+            <configuration>
+              <includeGroupIds>org.eclipse.jetty.fcgi</includeGroupIds>
+              <includeTypes>jar</includeTypes>
+              <outputDirectory>${assembly-directory}/lib/fcgi</outputDirectory>
+            </configuration>
+          </execution>
+          <execution>
+            <id>copy-lib-spring-deps</id>
+            <phase>generate-resources</phase>
+            <goals>
+              <goal>copy-dependencies</goal>
+            </goals>
+            <configuration>
+              <includeGroupIds>org.eclipse.jetty</includeGroupIds>
+              <includeArtifactIds>jetty-spring</includeArtifactIds>
+              <includeTypes>jar</includeTypes>
+              <outputDirectory>${assembly-directory}/lib/spring</outputDirectory>
+            </configuration>
+          </execution>
+          <execution>
+            <id>copy-lib-monitor-deps</id>
+            <phase>generate-resources</phase>
+            <goals>
+              <goal>copy</goal>
+            </goals>
+            <configuration>
+              <artifactItems>
+                <artifactItem>
+                  <groupId>org.eclipse.jetty</groupId>
+                  <artifactId>jetty-monitor</artifactId>
+                  <version>${project.version}</version>
+                  <type>jar</type>
+                  <overWrite>true</overWrite>
+                  <outputDirectory>${assembly-directory}/lib/monitor</outputDirectory>
+                </artifactItem>
+              </artifactItems>
+            </configuration>
+          </execution>
+          <execution>
+            <id>copy-servlet-api-deps</id>
             <phase>generate-resources</phase>
             <goals>
               <goal>copy</goal>
@@ -140,114 +373,225 @@
             <configuration>
               <artifactItems>
                 <artifactItem>
-                  <groupId>org.eclipse.jetty.orbit</groupId>
-                  <artifactId>javax.servlet</artifactId>
-                  <version>${orbit-servlet-api-version}</version>
+                 <groupId>javax.servlet</groupId>
+                 <artifactId>javax.servlet-api</artifactId>
+                 <version>3.1.0</version>
                   <overWrite>true</overWrite>
                   <outputDirectory>${assembly-directory}/lib</outputDirectory>
-                  <destFileName>servlet-api-3.0.jar</destFileName>
+                  <destFileName>servlet-api-3.1.jar</destFileName>
+                </artifactItem>
+                <artifactItem>
+                  <groupId>org.eclipse.jetty.toolchain</groupId>
+                  <artifactId>jetty-schemas</artifactId>
+                  <version>3.1.M0</version>
+                  <overWrite>true</overWrite>
+                  <outputDirectory>${assembly-directory}/lib</outputDirectory>
+                  <destFileName>jetty-schemas-3.1.jar</destFileName>
                 </artifactItem>
               </artifactItems>
             </configuration>
           </execution>
           <execution>
-            <id>copy-orbit-lib-annotations-deps</id>
+            <id>unpack-spdy</id>
+            <phase>process-resources</phase>
+            <goals>
+              <goal>unpack-dependencies</goal>
+            </goals>
+            <configuration>
+              <includeGroupIds>org.eclipse.jetty.spdy</includeGroupIds>
+              <classifier>config</classifier>
+              <failOnMissingClassifierArtifact>false</failOnMissingClassifierArtifact>
+              <excludes>META-INF/**</excludes>
+              <outputDirectory>${assembly-directory}</outputDirectory>
+            </configuration>
+          </execution>
+          <execution>
+            <id>copy-lib-spdy-deps</id>
+            <phase>process-resources</phase>
+            <goals>
+              <goal>copy-dependencies</goal>
+            </goals>
+            <configuration>
+              <includeGroupIds>org.eclipse.jetty.spdy</includeGroupIds>
+              <includeTypes>jar</includeTypes>
+              <outputDirectory>${assembly-directory}/lib/spdy</outputDirectory>
+            </configuration>
+          </execution>
+          <execution>
+            <id>copy-annotations-deps</id>
             <phase>generate-resources</phase>
             <goals>
               <goal>copy-dependencies</goal>
             </goals>
             <configuration>
-              <includeGroupIds>org.eclipse.jetty.orbit</includeGroupIds>
-              <includeArtifactIds>javax.annotation,org.objectweb.asm</includeArtifactIds>
+              <includeGroupIds>javax.annotation,org.eclipse.jetty.orbit,org.ow2.asm</includeGroupIds>
+              <includeArtifactIds>javax.annotation-api,asm,asm-commons</includeArtifactIds>
               <includeTypes>jar</includeTypes>
               <outputDirectory>${assembly-directory}/lib/annotations</outputDirectory>
             </configuration>
           </execution>
+
           <execution>
-            <id>copy-orbit-lib-jta-deps</id>
+            <id>copy-jta-deps</id>
             <phase>generate-resources</phase>
             <goals>
               <goal>copy-dependencies</goal>
             </goals>
             <configuration>
-              <includeGroupIds>org.eclipse.jetty.orbit</includeGroupIds>
-              <includeArtifactIds>javax.transaction</includeArtifactIds>
+              <includeGroupIds>javax.transaction</includeGroupIds>
+              <includeArtifactIds>javax.transaction-api</includeArtifactIds>
               <includeTypes>jar</includeTypes>
-              <outputDirectory>${assembly-directory}/lib/jta</outputDirectory>
+              <outputDirectory>${assembly-directory}/lib/jndi</outputDirectory>
             </configuration>
           </execution>
           <execution>
-            <id>copy-orbit-lib-jndi-deps</id>
+            <id>copy-jndi-deps</id>
             <phase>generate-resources</phase>
             <goals>
               <goal>copy-dependencies</goal>
             </goals>
             <configuration>
               <includeGroupIds>org.eclipse.jetty.orbit</includeGroupIds>
-              <includeArtifactIds>javax.mail.glassfish,javax.activation</includeArtifactIds>
+              <includeArtifactIds>javax.mail.glassfish</includeArtifactIds>
               <includeTypes>jar</includeTypes>
               <outputDirectory>${assembly-directory}/lib/jndi</outputDirectory>
             </configuration>
           </execution>
           <execution>
-            <id>copy-orbit-lib-jsp-deps</id>
+            <id>copy-glassfish-jsp-deps</id>
             <phase>generate-resources</phase>
             <goals>
               <goal>copy-dependencies</goal>
             </goals>
             <configuration>
-              <includeGroupIds>org.eclipse.jetty.orbit</includeGroupIds>
-              <includeArtifactIds>com.sun.el,javax.el,javax.servlet.jsp,javax.servlet.jsp.jstl,org.apache.jasper.glassfish,org.apache.taglibs.standard.glassfish,org.eclipse.jdt.core</includeArtifactIds>
+              <includeGroupIds>org.eclipse.jetty.orbit,org.glassfish.web, org.glassfish, javax.el, javax.servlet.jsp, org.eclipse.jetty.toolchain, org.eclipse.jetty</includeGroupIds>
+              <includeArtifactIds>org.eclipse.jdt.core, javax.servlet.jsp-api, javax.servlet.jsp, jetty-jsp-jdt, javax.el-api, javax.el, jetty-jsp</includeArtifactIds>
               <includeTypes>jar</includeTypes>
               <outputDirectory>${assembly-directory}/lib/jsp</outputDirectory>
             </configuration>
           </execution>
           <execution>
-            <id>unpack-config-deps</id>
+            <id>copy-apache-jsp-deps</id>
             <phase>generate-resources</phase>
             <goals>
-              <goal>unpack-dependencies</goal>
+              <goal>copy-dependencies</goal>
             </goals>
             <configuration>
-              <includeGroupIds>org.eclipse.jetty</includeGroupIds>
-              <classifier>config</classifier>
-              <failOnMissingClassifierArtifact>false</failOnMissingClassifierArtifact>
-              <excludes>META-INF/**</excludes>
-              <outputDirectory>${assembly-directory}</outputDirectory>
+              <includeGroupIds>org.eclipse.jetty,org.eclipse.jetty.toolchain,org.mortbay.jasper,org.eclipse.jetty.orbit</includeGroupIds>
+              <includeArtifactIds>apache-jsp,apache-el,org.eclipse.jdt.core</includeArtifactIds>
+              <includeTypes>jar</includeTypes>
+              <prependGroupId>true</prependGroupId>
+              <outputDirectory>${assembly-directory}/lib/apache-jsp</outputDirectory>
             </configuration>
           </execution>
           <execution>
-            <id>copy-lib-monitor-deps</id>
+           <id>copy-jstl-api</id>
+           <phase>generate-resources</phase>
+           <goals>
+             <goal>copy-dependencies</goal>
+           </goals>
+           <configuration>
+              <includeGroupIds>org.eclipse.jetty.orbit</includeGroupIds>
+              <includeArtifactIds>javax.servlet.jsp.jstl</includeArtifactIds>
+              <prependGroupId>true</prependGroupId>
+              <includeTypes>jar</includeTypes>
+              <outputDirectory>${assembly-directory}/lib/jsp</outputDirectory>
+           </configuration>
+          </execution>
+          <execution>
+           <id>copy-jstl-impl</id>
+           <phase>generate-resources</phase>
+           <goals>
+             <goal>copy-dependencies</goal>
+           </goals>
+           <configuration>
+              <includeGroupIds>org.glassfish.web</includeGroupIds>
+              <includeArtifactIds>javax.servlet.jsp.jstl</includeArtifactIds>
+              <includeTypes>jar</includeTypes>
+              <outputDirectory>${assembly-directory}/lib/jsp</outputDirectory>
+           </configuration>
+          </execution>
+          <execution>
+           <id>copy-apache-jstl-deps</id>
+           <phase>generate-resources</phase>
+           <goals>
+             <goal>copy-dependencies</goal>
+           </goals>
+           <configuration>
+          <excludeGroupIds>org.glassfish.web</excludeGroupIds>
+          <includeArtifactIds>taglibs-standard-spec,taglibs-standard-impl</includeArtifactIds>
+              <prependGroupId>true</prependGroupId>
+              <includeTypes>jar</includeTypes>
+              <outputDirectory>${assembly-directory}/lib/apache-jstl</outputDirectory>
+           </configuration>
+          </execution>
+          <execution>
+            <id>copy-jaspi-deps</id>
             <phase>generate-resources</phase>
             <goals>
               <goal>copy-dependencies</goal>
             </goals>
             <configuration>
-              <includeGroupIds>org.eclipse.jetty</includeGroupIds>
-              <includeArtifactIds>jetty-monitor</includeArtifactIds>
+              <includeGroupIds>org.eclipse.jetty.orbit</includeGroupIds>
+              <includeArtifactIds>javax.security.auth.message</includeArtifactIds>
               <includeTypes>jar</includeTypes>
-              <excludeTransitive>true</excludeTransitive>
-              <outputDirectory>${assembly-directory}/lib/monitor</outputDirectory>
+              <outputDirectory>${assembly-directory}/lib/jaspi</outputDirectory>
             </configuration>
           </execution>
           <execution>
-            <id>unpack-javadoc</id>
+            <id>unpack-config-deps</id>
             <phase>generate-resources</phase>
             <goals>
               <goal>unpack-dependencies</goal>
             </goals>
             <configuration>
-              <!-- Use already generated javadoc, don't bother regenerating again -->
-              <includeGroupIds>org.eclipse.jetty.aggregate</includeGroupIds>
-              <includeArtifactIds>jetty-all</includeArtifactIds>
-              <includeClassifier>javadoc</includeClassifier>
-              <excludeTransitive>true</excludeTransitive>
-              <outputDirectory>${assembly-directory}/javadoc</outputDirectory>
+              <includeGroupIds>org.eclipse.jetty,org.eclipse.jetty.websocket</includeGroupIds>
+              <classifier>config</classifier>
+              <failOnMissingClassifierArtifact>false</failOnMissingClassifierArtifact>
+              <excludes>META-INF/**</excludes>
+              <outputDirectory>${assembly-directory}</outputDirectory>
             </configuration>
           </execution>
         </executions>
       </plugin>
       <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>exec-maven-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>setup home</id>
+            <phase>process-classes</phase>
+            <configuration>
+              <mainClass>org.eclipse.jetty.start.Main</mainClass>
+              <arguments>
+                <argument>jetty.home=${assembly-directory}</argument>
+                <argument>jetty.base=${assembly-directory}</argument>
+                <argument>--add-to-start=server,deploy,websocket,ext,resources,jsp,jstl,http</argument>
+              </arguments>
+            </configuration>
+            <goals>
+              <goal>java</goal>
+            </goals>
+          </execution>
+          <execution>
+            <id>setup demo-base</id>
+            <phase>process-classes</phase>
+            <configuration>
+              <mainClass>org.eclipse.jetty.start.Main</mainClass>
+              <arguments>
+                <argument>jetty.home=${assembly-directory}</argument>
+                <argument>jetty.base=${assembly-directory}/demo-base</argument>
+                <argument>--add-to-start=server,continuation,deploy,websocket,ext,resources,client,annotations,jndi,servlets</argument>
+                <argument>--add-to-startd=jsp,jstl,http,https</argument>
+              </arguments>
+            </configuration>
+            <goals>
+              <goal>java</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-assembly-plugin</artifactId>
         <configuration>
@@ -288,36 +632,54 @@
     <!-- Orbit Deps -->
     <dependency>
       <groupId>org.eclipse.jetty.orbit</groupId>
-      <artifactId>javax.annotation</artifactId>
+      <artifactId>javax.mail.glassfish</artifactId>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jetty.orbit</groupId>
-      <artifactId>org.objectweb.asm</artifactId>
+      <artifactId>javax.security.auth.message</artifactId>
     </dependency>
+
     <dependency>
-      <groupId>org.eclipse.jetty.orbit</groupId>
-      <artifactId>javax.activation</artifactId>
+      <groupId>javax.annotation</groupId>
+      <artifactId>javax.annotation-api</artifactId>
     </dependency>
     <dependency>
-      <groupId>org.eclipse.jetty.orbit</groupId>
-      <artifactId>javax.mail.glassfish</artifactId>
+      <groupId>javax.transaction</groupId>
+      <artifactId>javax.transaction-api</artifactId>
     </dependency>
+
     <dependency>
-      <groupId>org.eclipse.jetty.orbit</groupId>
-      <artifactId>javax.transaction</artifactId>
+      <groupId>org.glassfish.web</groupId>
+      <artifactId>javax.servlet.jsp.jstl</artifactId>
     </dependency>
+
     <dependency>
-      <groupId>org.eclipse.jetty.orbit</groupId>
-      <artifactId>javax.security.auth.message</artifactId>
+      <groupId>org.glassfish.web</groupId>
+      <artifactId>javax.servlet.jsp</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.toolchain</groupId>
+      <artifactId>jetty-jsp-jdt</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>javax.servlet.jsp</groupId>
+      <artifactId>javax.servlet.jsp-api</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.glassfish</groupId>
+      <artifactId>javax.el</artifactId>
     </dependency>
 
     <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-jsp</artifactId>
-      <version>${project.version}</version>
+      <groupId>org.ow2.asm</groupId>
+      <artifactId>asm</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.ow2.asm</groupId>
+      <artifactId>asm-commons</artifactId>
     </dependency>
 
-    <!-- Standard Jetty Deps -->
+    <!-- jetty deps -->
     <dependency>
       <groupId>org.eclipse.jetty</groupId>
       <artifactId>jetty-deploy</artifactId>
@@ -325,33 +687,69 @@
     </dependency>
     <dependency>
       <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-rewrite</artifactId>
+      <artifactId>test-jetty-webapp</artifactId>
+      <type>war</type>
       <version>${project.version}</version>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-ajp</artifactId>
+      <artifactId>test-proxy-webapp</artifactId>
+      <type>war</type>
       <version>${project.version}</version>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-annotations</artifactId>
+      <artifactId>jetty-jmx</artifactId>
       <version>${project.version}</version>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jetty</groupId>
-      <artifactId>test-jetty-webapp</artifactId>
-      <type>war</type>
+      <artifactId>jetty-monitor</artifactId>
       <version>${project.version}</version>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-jmx</artifactId>
+      <artifactId>jetty-quickstart</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-start</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-servlets</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.websocket</groupId>
+      <artifactId>websocket-servlet</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.websocket</groupId>
+      <artifactId>websocket-server</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.websocket</groupId>
+      <artifactId>javax-websocket-server-impl</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-jsp</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>apache-jsp</artifactId>
       <version>${project.version}</version>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-jndi</artifactId>
+      <artifactId>apache-jstl</artifactId>
       <version>${project.version}</version>
     </dependency>
     <dependency>
@@ -366,128 +764,87 @@
     </dependency>
     <dependency>
       <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-start</artifactId>
+      <artifactId>jetty-continuation</artifactId>
       <version>${project.version}</version>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-policy</artifactId>
+      <artifactId>jetty-proxy</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.fcgi</groupId>
+      <artifactId>fcgi-server</artifactId>
       <version>${project.version}</version>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-servlets</artifactId>
+      <artifactId>jetty-spring</artifactId>
       <version>${project.version}</version>
     </dependency>
+<!--
     <dependency>
       <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-monitor</artifactId>
+      <artifactId>jetty-overlay-deployer</artifactId>
       <version>${project.version}</version>
     </dependency>
+-->
     <dependency>
       <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-websocket</artifactId>
+      <artifactId>jetty-cdi</artifactId>
       <version>${project.version}</version>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-overlay-deployer</artifactId>
+      <artifactId>jetty-jaas</artifactId>
       <version>${project.version}</version>
     </dependency>
     <dependency>
-      <groupId>org.eclipse.jetty.aggregate</groupId>
-      <artifactId>jetty-all</artifactId>
-      <classifier>javadoc</classifier>
-      <type>jar</type>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-annotations</artifactId>
       <version>${project.version}</version>
     </dependency>
-  </dependencies>
-  <profiles>
-    <profile>
-      <!-- Modules that are only for JDK7+ builds -->
-      <id>JDK7-plus-modules</id>
-      <activation>
-        <jdk>[1.7,)</jdk>
-      </activation>
-      <build>
-      <plugins>
-       <plugin>
-        <artifactId>maven-dependency-plugin</artifactId>
-        <executions>
-         <execution>
-            <id>copy-lib-spdy-deps</id>
-            <phase>process-resources</phase>
-            <goals>
-              <goal>copy-dependencies</goal>
-            </goals>
-            <configuration>
-              <includeGroupIds>org.eclipse.jetty.spdy</includeGroupIds>
-              <includeTypes>jar</includeTypes>
-              <outputDirectory>${assembly-directory}/lib/spdy</outputDirectory>
-            </configuration>
-          </execution>
-          <execution>
-            <id>copy-spdy</id>
-            <phase>process-resources</phase>
-            <goals>
-              <goal>copy</goal>
-            </goals>
-            <configuration>
-              <artifactItems>
-                <artifactItem>
-                  <groupId>org.eclipse.jetty.spdy</groupId>
-                  <artifactId>spdy-jetty-http-webapp</artifactId>
-                  <version>${project.version}</version>
-                  <type>war</type>
-                  <overWrite>true</overWrite>
-                  <includes>**</includes>
-                  <outputDirectory>${assembly-directory}/webapps</outputDirectory>
-                  <destFileName>spdy.war</destFileName>
-                </artifactItem>
-              </artifactItems>
-            </configuration>
-          </execution>
-          <execution>
-            <id>unpack-spdy</id>
-            <phase>process-resources</phase>
-            <goals>
-              <goal>unpack-dependencies</goal>
-            </goals>
-            <configuration>
-              <includeGroupIds>org.eclipse.jetty.spdy</includeGroupIds>
-              <classifier>config</classifier>
-              <failOnMissingClassifierArtifact>false</failOnMissingClassifierArtifact>
-              <excludes>META-INF/**</excludes>
-              <outputDirectory>${assembly-directory}</outputDirectory>
-            </configuration>
-          </execution>
-        </executions>
-       </plugin>
-      </plugins>
-      </build>
-      <dependencies>
-          <dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-rewrite</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
       <groupId>org.eclipse.jetty.spdy</groupId>
       <artifactId>spdy-core</artifactId>
       <version>${project.version}</version>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jetty.spdy</groupId>
-      <artifactId>spdy-jetty</artifactId>
+      <artifactId>spdy-server</artifactId>
       <version>${project.version}</version>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jetty.spdy</groupId>
-      <artifactId>spdy-jetty-http</artifactId>
+      <artifactId>spdy-http-server</artifactId>
       <version>${project.version}</version>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jetty.spdy</groupId>
-      <artifactId>spdy-jetty-http-webapp</artifactId>
+      <artifactId>spdy-example-webapp</artifactId>
       <version>${project.version}</version>
       <type>war</type>
-      </dependency>
-      </dependencies>
-    </profile>
-  </profiles>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-alpn-server</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.example-async-rest</groupId>
+      <artifactId>example-async-rest-webapp</artifactId>
+      <version>${project.version}</version>
+      <type>war</type>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-jaspi</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+  </dependencies>
 </project>
diff --git a/jetty-distribution/src/main/resources/README.TXT b/jetty-distribution/src/main/resources/README.TXT
new file mode 100644
index 0000000..4f9d64b
--- /dev/null
+++ b/jetty-distribution/src/main/resources/README.TXT
@@ -0,0 +1,68 @@
+
+JETTY
+=====
+The Jetty project is a 100% Java HTTP Server, HTTP Client
+and Servlet Container from the eclipse foundation
+
+  http://www.eclipse.org/jetty/
+
+Jetty is open source and is dual licensed using the Apache 2.0 and
+Eclipse Public License 1.0.   You may choose either license when
+distributing Jetty.
+
+
+RUNNING JETTY
+=============
+The run directory is either the top-level of a binary release
+or jetty-distribution/target/distribution directory when built from
+source.
+
+To run with the default options:
+
+  $ cd demo-base
+  $ java -jar ../start.jar
+
+To see the available options and the default arguments
+provided by the start.ini file:
+
+  $ java -jar /path/to/start.jar --help
+
+
+Many Jetty features can be enabled by using the --module command
+For example:
+
+  $ cd mybase
+  $ java -jar /path/to/start.jar --module=https,deploy
+
+Will enable the https and deploy modules (and their transitive
+dependencies) temporarily for this specific run of Jetty.
+
+To see what modules are available
+
+  $ java -jar /path/to/start.jar --list-modules
+
+
+
+JETTY BASE
+==========
+
+The jetty.base property is a property that can be defined on the
+command line (defaults to what your java 'user.dir' property points to)
+Jetty's start.jar mechanism will configure your jetty instance from
+the configuration present in this jetty.base directory.
+
+Example setup:
+
+# Create the base directory
+  
+  $ mkdir mybase
+  $ cd mybase
+  
+# Initialize the base directory's start.ini and needed directories
+
+  $ java -jar /path/to/jetty-dist/start.jar --add-to-start=http,deploy
+  
+# Run this base directory configuration
+ 
+  $ java -jar /path/to/jetty-dist/start.jar
+
diff --git a/jetty-distribution/src/main/resources/README.txt b/jetty-distribution/src/main/resources/README.txt
deleted file mode 100644
index 8bafb49..0000000
--- a/jetty-distribution/src/main/resources/README.txt
+++ /dev/null
@@ -1,70 +0,0 @@
-
-JETTY
-=====
-
-The Jetty project is a 100% Java HTTP Server, HTTP Client
-and Servlet Container.
-
-
-The Jetty @ eclipse project is based on the Jetty project at codehaus
-
-  http://jetty.codehaus.org
-
-Ongoing development is now at the eclipse foundation
-
-  http://www.eclipse.org/jetty/
-
-
-Jetty @ eclipse is open source and is dual licensed using the apache 2.0 and
-eclipse public license 1.0.   You may choose either license when distributing
-jetty.
-
-
-
-BUILDING JETTY
-==============
-
-Jetty uses maven 2 as its build system.  Maven will fetch
-the dependancies, build the server and assemble a runnable
-version:
-
-  mvn install
-
-
-
-RUNNING JETTY
-=============
-
-The run directory is either the top-level of a binary release
-or jetty-distribution/target/assembly-prep directory when built from
-source.
-
-To run with the default options:
-
-  java -jar start.jar
-
-To see the available options and the default arguments
-provided by the start.ini file:
-
-  java -jar start.jar --help
-
-To run with extra configuration file(s) appended, eg SSL
-
-  java -jar start.jar etc/jetty-ssl.xml
-
-To run with properties 
-
-  java -jar start.jar jetty.port=8081
-
-To run with extra configuration file(s) prepended, eg logging & jmx
-
-  java -jar start.jar --pre=etc/jetty-logging.xml --pre=etc/jetty-jmx.xml 
-
-To run without the args from start.ini 
-
-  java -jar start.jar --ini OPTIONS=Server,websocket etc/jetty.xml etc/jetty-deploy.xml etc/jetty-ssl.xml
-
-to list the know OPTIONS:
-
-  java -jar start.jar --list-options
-
diff --git a/jetty-distribution/src/main/resources/bin/README.jetty-cygwin.txt.txt b/jetty-distribution/src/main/resources/bin/README.jetty-cygwin.txt.txt
deleted file mode 100644
index 6ac050d..0000000
--- a/jetty-distribution/src/main/resources/bin/README.jetty-cygwin.txt.txt
+++ /dev/null
@@ -1,11 +0,0 @@
-Before running jetty-cygwin.sh on cygwin, define the JAVA_HOME and JETTY_HOME first.
-
-$ export JAVA_HOME=/path/to/jvm
-$ export JETTY_HOME=/path/to/jetty
-
-Examples:
-$ export JAVA_HOME=/usr/bin/jvm
-So assuming you installed cygwin on C:\cygwin, the jvm needs to be in C:\cygwin\usr\bin\jvm
-
-$ export JETTY_HOME=/usr/share/jetty6
-So assuming you installed cygwin on C:\cygwin, jetty needs to be in C:\cygwin\usr\share\jetty6
\ No newline at end of file
diff --git a/jetty-distribution/src/main/resources/bin/jetty-cygwin.sh b/jetty-distribution/src/main/resources/bin/jetty-cygwin.sh
deleted file mode 100755
index 16371be..0000000
--- a/jetty-distribution/src/main/resources/bin/jetty-cygwin.sh
+++ /dev/null
@@ -1,677 +0,0 @@
-#!/bin/bash  
-#
-# Startup script for jetty under *nix systems (it works under NT/cygwin too).
-
-# To get the service to restart correctly on reboot, uncomment below (3 lines):
-# ========================
-# chkconfig: 3 99 99
-# description: Jetty 8 webserver
-# processname: jetty
-# ========================
-
-# Configuration files
-#
-# /etc/default/jetty
-#   If it exists, this is read at the start of script. It may perform any 
-#   sequence of shell commands, like setting relevant environment variables.
-#
-# $HOME/.jettyrc
-#   If it exists, this is read at the start of script. It may perform any 
-#   sequence of shell commands, like setting relevant environment variables.
-#
-# /etc/jetty.conf
-#   If found, and no configurations were given on the command line,
-#   the file will be used as this script's configuration. 
-#   Each line in the file may contain:
-#     - A comment denoted by the pound (#) sign as first non-blank character.
-#     - The path to a regular file, which will be passed to jetty as a 
-#       config.xml file.
-#     - The path to a directory. Each *.xml file in the directory will be
-#       passed to jetty as a config.xml file.
-#
-#   The files will be checked for existence before being passed to jetty.
-#
-# $JETTY_HOME/etc/jetty.xml
-#   If found, used as this script's configuration file, but only if
-#   /etc/jetty.conf was not present. See above.
-#   
-# Configuration variables
-#
-# JAVA_HOME  
-#   Home of Java installation. 
-#
-# JAVA
-#   Command to invoke Java. If not set, $JAVA_HOME/bin/java will be
-#   used.
-#
-# JAVA_OPTIONS
-#   Extra options to pass to the JVM
-#
-# JETTY_HOME
-#   Where Jetty is installed. If not set, the script will try go
-#   guess it by first looking at the invocation path for the script,
-#   and then by looking in standard locations as $HOME/opt/jetty
-#   and /opt/jetty. The java system property "jetty.home" will be
-#   set to this value for use by configure.xml files, f.e.:
-#
-#    <Arg><SystemProperty name="jetty.home" default="."/>/webapps/jetty.war</Arg>
-#
-# JETTY_PORT
-#   Override the default port for Jetty servers. If not set then the
-#   default value in the xml configuration file will be used. The java
-#   system property "jetty.port" will be set to this value for use in
-#   configure.xml files. For example, the following idiom is widely
-#   used in the demo config files to respect this property in Listener
-#   configuration elements:
-#
-#    <Set name="Port"><SystemProperty name="jetty.port" default="8080"/></Set>
-#
-#   Note: that the config file could ignore this property simply by saying:
-#
-#    <Set name="Port">8080</Set>
-#
-# JETTY_RUN
-#   Where the jetty.pid file should be stored. It defaults to the
-#   first available of /var/run, /usr/var/run, and /tmp if not set.
-#  
-# JETTY_PID
-#   The Jetty PID file, defaults to $JETTY_RUN/jetty.pid
-#   
-# JETTY_ARGS
-#   The default arguments to pass to jetty.
-#
-# JETTY_USER
-#   if set, then used as a username to run the server as
-#
-
-usage()
-{
-    echo "Usage: $0 {start|stop|run|restart|check|supervise} [ CONFIGS ... ] "
-    exit 1
-}
-
-[ $# -gt 0 ] || usage
-
-
-##################################################
-# Some utility functions
-##################################################
-findDirectory()
-{
-    OP=$1
-    shift
-    for L in $* ; do
-        [ $OP $L ] || continue 
-        echo $L
-        break
-    done 
-}
-
-running()
-{
-    [ -f $1 ] || return 1
-    PID=$(cat $1)
-    ps -p $PID >/dev/null 2>/dev/null || return 1
-    return 0
-}
-
-
-
-
-
-
-
-##################################################
-# Get the action & configs
-##################################################
-
-ACTION=$1
-shift
-ARGS="$*"
-CONFIGS=""
-NO_START=0
-
-##################################################
-# See if there's a default configuration file
-##################################################
-if [ -f /etc/default/jetty8 ] ; then 
-  . /etc/default/jetty8
-elif [ -f /etc/default/jetty ] ; then 
-  . /etc/default/jetty
-fi
-
-
-##################################################
-# See if there's a user-specific configuration file
-##################################################
-if [ -f $HOME/.jettyrc ] ; then 
-  . $HOME/.jettyrc
-fi
-
-##################################################
-# Set tmp if not already set.
-##################################################
-
-if [ -z "$TMP" ] 
-then
-  TMP=/tmp
-fi
-
-##################################################
-# Jetty's hallmark
-##################################################
-JETTY_INSTALL_TRACE_FILE="etc/jetty.xml"
-TMPJ=$TMP/j$$
-
-
-##################################################
-# Try to determine JETTY_HOME if not set
-##################################################
-if [ -z "$JETTY_HOME" ] 
-then
-  JETTY_HOME_1=`dirname "$0"`
-  JETTY_HOME_1=`dirname "$JETTY_HOME_1"`
-  if [ -f "${JETTY_HOME_1}/${JETTY_INSTALL_TRACE_FILE}" ] ; 
-  then 
-     JETTY_HOME=${JETTY_HOME_1} 
-  fi
-fi
-
-
-##################################################
-# if no JETTY_HOME, search likely locations.
-##################################################
-if [ "$JETTY_HOME" = "" ] ; then
-  STANDARD_LOCATIONS="           \
-        /usr/share               \
-        /usr/share/java          \
-        $HOME                    \
-        $HOME/src                \
-        ${HOME}/opt/             \
-        /opt                     \
-        /java                    \
-        /usr/local               \
-        /usr/local/share         \
-        /usr/local/share/java    \
-        /home                    \
-        "
-  JETTY_DIR_NAMES="              \
-        jetty-8                  \
-        jetty8                   \
-        jetty-8.*                \
-        jetty                    \
-        Jetty-8                  \
-        Jetty8                   \
-        Jetty-8.*                \
-        Jetty                    \
-        "
-        
-  JETTY_HOME=
-  for L in $STANDARD_LOCATIONS 
-  do
-     for N in $JETTY_DIR_NAMES 
-     do
-         if [ -d $L/$N ] && [ -f "$L/${N}/${JETTY_INSTALL_TRACE_FILE}" ] ; 
-         then 
-            JETTY_HOME="$L/$N"
-         fi
-     done
-     [ ! -z "$JETTY_HOME" ] && break
-  done
-fi
-
-
-##################################################
-# No JETTY_HOME yet? We're out of luck!
-##################################################
-if [ -z "$JETTY_HOME" ] ; then
-    echo "** ERROR: JETTY_HOME not set, you need to set it or install in a standard location" 
-    exit 1
-fi
-
-cd $JETTY_HOME
-JETTY_HOME=`pwd`
-
-
-#####################################################
-# Check that jetty is where we think it is
-#####################################################
-if [ ! -r $JETTY_HOME/$JETTY_INSTALL_TRACE_FILE ] 
-then
-   echo "** ERROR: Oops! Jetty doesn't appear to be installed in $JETTY_HOME"
-   echo "** ERROR:  $JETTY_HOME/$JETTY_INSTALL_TRACE_FILE is not readable!"
-   exit 1
-fi
-
-
-###########################################################
-# Get the list of config.xml files from the command line.
-###########################################################
-if [ ! -z "$ARGS" ] 
-then
-  for A in $ARGS 
-  do
-    if [ -f $A ] 
-    then
-       CONF="$A" 
-    elif [ -f $JETTY_HOME/etc/$A ] 
-    then
-       CONF="$JETTY_HOME/etc/$A" 
-    elif [ -f ${A}.xml ] 
-    then
-       CONF="${A}.xml" 
-    elif [ -f $JETTY_HOME/etc/${A}.xml ] 
-    then
-       CONF="$JETTY_HOME/etc/${A}.xml" 
-    else
-       echo "** ERROR: Cannot find configuration '$A' specified in the command line." 
-       exit 1
-    fi
-    if [ ! -r $CONF ] 
-    then
-       echo "** ERROR: Cannot read configuration '$A' specified in the command line." 
-       exit 1
-    fi
-    CONFIGS="$CONFIGS $CONF"
-  done
-fi
-
-
-##################################################
-# Try to find this script's configuration file,
-# but only if no configurations were given on the
-# command line.
-##################################################
-if [ -z "$JETTY_CONF" ] 
-then
-  if [ -f /etc/jetty.conf ]
-  then
-     JETTY_CONF=/etc/jetty.conf
-  elif [ -f "${JETTY_HOME}/etc/jetty.conf" ]
-  then
-     JETTY_CONF="${JETTY_HOME}/etc/jetty.conf"
-  fi
-fi
-
-##################################################
-# Read the configuration file if one exists
-##################################################
-CONFIG_LINES=
-if [ -z "$CONFIGS" ] && [ -f "$JETTY_CONF" ] && [ -r "$JETTY_CONF" ] 
-then
-  CONFIG_LINES=`cat $JETTY_CONF | grep -v "^[:space:]*#" | tr "\n" " "` 
-fi
-
-##################################################
-# Get the list of config.xml files from jetty.conf
-##################################################
-if [ ! -z "${CONFIG_LINES}" ] 
-then
-  for CONF in ${CONFIG_LINES} 
-  do
-    if [ ! -r "$CONF" ] 
-    then
-      echo "** WARNING: Cannot read '$CONF' specified in '$JETTY_CONF'" 
-    elif [ -f "$CONF" ] 
-    then
-      # assume it's a configure.xml file
-      CONFIGS="$CONFIGS $CONF" 
-    elif [ -d "$CONF" ] 
-    then
-      # assume it's a directory with configure.xml files
-      # for example: /etc/jetty.d/
-      # sort the files before adding them to the list of CONFIGS
-      XML_FILES=`ls ${CONF}/*.xml | sort | tr "\n" " "` 
-      for FILE in ${XML_FILES} 
-      do
-         if [ -r "$FILE" ] && [ -f "$FILE" ] 
-         then
-            CONFIGS="$CONFIGS $FILE" 
-         else
-           echo "** WARNING: Cannot read '$FILE' specified in '$JETTY_CONF'" 
-         fi
-      done
-    else
-      echo "** WARNING: Don''t know what to do with '$CONF' specified in '$JETTY_CONF'" 
-    fi
-  done
-fi
-
-#####################################################
-# Run the standard server if there's nothing else to run
-#####################################################
-if [ -z "$CONFIGS" ] 
-then
-    CONFIGS="${JETTY_HOME}/etc/jetty-logging.xml ${JETTY_HOME}/etc/jetty.xml"
-fi
-
-
-#####################################################
-# Find a location for the pid file
-#####################################################
-if [  -z "$JETTY_RUN" ] 
-then
-  JETTY_RUN=`findDirectory -w /var/run /usr/var/run /tmp`
-fi
-
-#####################################################
-# Find a PID for the pid file
-#####################################################
-if [  -z "$JETTY_PID" ] 
-then
-  JETTY_PID="$JETTY_RUN/jetty.pid"
-fi
-
-
-##################################################
-# Check for JAVA_HOME
-##################################################
-if [ -z "$JAVA_HOME" ]
-then
-    # If a java runtime is not defined, search the following
-    # directories for a JVM and sort by version. Use the highest
-    # version number.
-
-    # Java search path
-    JAVA_LOCATIONS="\
-        /usr/java \
-        /usr/bin \
-        /usr/local/bin \
-        /usr/local/java \
-        /usr/local/jdk \
-        /usr/local/jre \
-	/usr/lib/jvm \
-        /opt/java \
-        /opt/jdk \
-        /opt/jre \
-    " 
-    JAVA_NAMES="java jdk jre"
-    for N in $JAVA_NAMES ; do
-        for L in $JAVA_LOCATIONS ; do
-            [ -d $L ] || continue 
-            find $L -name "$N" ! -type d | grep -v threads | while read J ; do
-                [ -x $J ] || continue
-                VERSION=`eval $J -version 2>&1`       
-                [ $? = 0 ] || continue
-                VERSION=`expr "$VERSION" : '.*"\(1.[0-9\.]*\)["_]'`
-                [ "$VERSION" = "" ] && continue
-                expr $VERSION \< 1.2 >/dev/null && continue
-                echo $VERSION:$J
-            done
-        done
-    done | sort | tail -1 > $TMPJ
-    JAVA=`cat $TMPJ | cut -d: -f2`
-    JVERSION=`cat $TMPJ | cut -d: -f1`
-
-    JAVA_HOME=`dirname $JAVA`
-    while [ ! -z "$JAVA_HOME" -a "$JAVA_HOME" != "/" -a ! -f "$JAVA_HOME/lib/tools.jar" ] ; do
-        JAVA_HOME=`dirname $JAVA_HOME`
-    done
-    [ "$JAVA_HOME" = "" ] && JAVA_HOME=
-
-    echo "Found JAVA=$JAVA in JAVA_HOME=$JAVA_HOME"
-fi
-
-
-##################################################
-# Determine which JVM of version >1.2
-# Try to use JAVA_HOME
-##################################################
-if [ "$JAVA" = "" -a "$JAVA_HOME" != "" ]
-then
-  if [ ! -z "$JAVACMD" ] 
-  then
-     JAVA="$JAVACMD" 
-  else
-    [ -x $JAVA_HOME/bin/jre -a ! -d $JAVA_HOME/bin/jre ] && JAVA=$JAVA_HOME/bin/jre
-    [ -x $JAVA_HOME/bin/java -a ! -d $JAVA_HOME/bin/java ] && JAVA=$JAVA_HOME/bin/java
-  fi
-fi
-
-if [ "$JAVA" = "" ]
-then
-    echo "Cannot find a JRE or JDK. Please set JAVA_HOME to a >=1.2 JRE" 2>&2
-    exit 1
-fi
-
-JAVA_VERSION=`expr "$($JAVA -version 2>&1 | head -1)" : '.*1\.\([0-9]\)'`
-
-#####################################################
-# See if JETTY_PORT is defined
-#####################################################
-if [ "$JETTY_PORT" != "" ] 
-then
-  JAVA_OPTIONS="$JAVA_OPTIONS -Djetty.port=$JETTY_PORT"
-fi
-
-#####################################################
-# See if JETTY_LOGS is defined
-#####################################################
-if [ "$JETTY_LOGS" != "" ]
-then
-  JAVA_OPTIONS="$JAVA_OPTIONS -Djetty.logs=$JETTY_LOGS"
-fi
-
-#####################################################
-# Are we running on Windows? Could be, with Cygwin/NT.
-#####################################################
-case "`uname`" in
-CYGWIN*) PATH_SEPARATOR=";";;
-*) PATH_SEPARATOR=":";;
-esac
-
-
-#####################################################
-# Add jetty properties to Java VM options.
-#####################################################
-JAVA_OPTIONS="$JAVA_OPTIONS -Djetty.home=$JETTY_HOME -Djava.io.tmpdir=$TMP"
-
-[ -f $JETTY_HOME/etc/start.config ] && JAVA_OPTIONS="-DSTART=$JETTY_HOME/etc/start.config $JAVA_OPTIONS"
-
-#####################################################
-# This is how the Jetty server will be started
-#####################################################
-
-JETTY_START=$JETTY_HOME/start.jar
-[ ! -f $JETTY_START ] && JETTY_START=$JETTY_HOME/lib/start.jar
-
-case "`uname`" in
-CYGWIN*)
-JETTY_START="`cygpath -w $JETTY_START`"
-echo $JETTY_START
-
-CONFIGS="`cygpath -w $CONFIGS`"
-echo $CONFIGS
-;;
-esac
-
-
-RUN_ARGS="$JAVA_OPTIONS -jar $JETTY_START $JETTY_ARGS $CONFIGS"
-RUN_CMD="$JAVA $RUN_ARGS"
-
-#####################################################
-# Comment these out after you're happy with what 
-# the script is doing.
-#####################################################
-#echo "JETTY_HOME     =  $JETTY_HOME"
-#echo "JETTY_CONF     =  $JETTY_CONF"
-#echo "JETTY_RUN      =  $JETTY_RUN"
-#echo "JETTY_PID      =  $JETTY_PID"
-#echo "JETTY_ARGS     =  $JETTY_ARGS"
-#echo "CONFIGS        =  $CONFIGS"
-#echo "JAVA_OPTIONS   =  $JAVA_OPTIONS"
-#echo "JAVA           =  $JAVA"
-
-
-##################################################
-# Do the action
-##################################################
-case "$ACTION" in
-  start)
-        echo -n "Starting Jetty: "
-
-        if [ "$NO_START" = "1" ]; then 
-	  echo "Not starting jetty - NO_START=1 in /etc/default/jetty8";
-          exit 0;
-	fi
-
-
-	if [ "$START_STOP_DAEMON" = "1" ] && type start-stop-daemon > /dev/null 2>&1
-	then
-          [ x$JETTY_USER = x ] && JETTY_USER=$(whoami)
-	  [ $UID = 0 ] && CH_USER="-c $JETTY_USER"
-	  if start-stop-daemon -S -p$JETTY_PID $CH_USER -d $JETTY_HOME -b -m -a $JAVA -- $RUN_ARGS 
-	  then
-	      sleep 1
-	      if running $JETTY_PID
-	      then
-                  echo OK
-              else
-                  echo FAILED
-              fi
-	  fi
-
-	else
-
-          if [ -f $JETTY_PID ]
-          then            
-            if running $JETTY_PID
-            then
-              echo "Already Running!!"
-              exit 1
-            else
-              # dead pid file - remove
-              rm -f $JETTY_PID
-            fi
-          fi
-
-          if [ x$JETTY_USER != x ] 
-          then
-              touch $JETTY_PID
-              chown $JETTY_USER $JETTY_PID
-              su - $JETTY_USER -c "
-                $RUN_CMD &
-                PID=\$!
-                disown \$PID
-                echo \$PID > $JETTY_PID"
-          else
-              $RUN_CMD &
-              PID=$!
-              disown $PID
-              echo $PID > $JETTY_PID
-          fi
-
-          echo "STARTED Jetty `date`" 
-        fi
-
-        ;;
-
-  stop)
-        echo -n "Stopping Jetty: "
-	if [ "$START_STOP_DAEMON" = "1" ] && type start-stop-daemon > /dev/null 2>&1; then
-	  start-stop-daemon -K -p $JETTY_PID -d $JETTY_HOME -a $JAVA -s HUP 
-	  sleep 1
-	  if running $JETTY_PID
-	  then
-	      sleep 3
-	      if running $JETTY_PID
-	      then
-		  sleep 30
-	          if running $JETTY_PID
-	          then
-	             start-stop-daemon -K -p $JETTY_PID -d $JETTY_HOME -a $JAVA -s KILL
-		  fi
-              fi
-	  fi
-
-	  rm -f $JETTY_PID
-          echo OK
-	else
-	  PID=`cat $JETTY_PID 2>/dev/null`
-          TIMEOUT=30
-          while running $JETTY_PID && [ $TIMEOUT -gt 0 ]
-          do
-            kill $PID 2>/dev/null
-            sleep 1
-            let TIMEOUT=$TIMEOUT-1
-          done
-          
-          [ $TIMEOUT -gt 0 ] || kill -9 $PID 2>/dev/null
-
-	  rm -f $JETTY_PID
-          echo OK
-	fi
-        ;;
-
-  restart)
-        JETTY_SH=$0
-        if [ ! -f $JETTY_SH ]; then
-          if [ ! -f $JETTY_HOME/bin/jetty.sh ]; then
-            echo "$JETTY_HOME/bin/jetty.sh does not exist."
-            exit 1
-          fi
-          JETTY_SH=$JETTY_HOME/bin/jetty.sh
-        fi
-        $JETTY_SH stop $*
-        sleep 5
-        $JETTY_SH start $*
-        ;;
-
-  supervise)
-       #
-       # Under control of daemontools supervise monitor which
-       # handles restarts and shutdowns via the svc program.
-       #
-         exec $RUN_CMD
-         ;;
-
-  run|demo)
-        echo "Running Jetty: "
-
-        if [ -f $JETTY_PID ]
-        then
-            if running $JETTY_PID
-            then
-              echo "Already Running!!"
-              exit 1
-            else
-              # dead pid file - remove
-              rm -f $JETTY_PID
-            fi
-        fi
-
-        exec $RUN_CMD
-        ;;
-
-  check)
-        echo "Checking arguments to Jetty: "
-        echo "JETTY_HOME     =  $JETTY_HOME"
-        echo "JETTY_CONF     =  $JETTY_CONF"
-        echo "JETTY_RUN      =  $JETTY_RUN"
-        echo "JETTY_PID      =  $JETTY_PID"
-        echo "JETTY_PORT     =  $JETTY_PORT"
-        echo "JETTY_LOGS     =  $JETTY_LOGS"
-        echo "CONFIGS        =  $CONFIGS"
-        echo "JAVA_OPTIONS   =  $JAVA_OPTIONS"
-        echo "JAVA           =  $JAVA"
-        echo "CLASSPATH      =  $CLASSPATH"
-        echo "RUN_CMD        =  $RUN_CMD"
-        echo
-        
-        if [ -f $JETTY_RUN/jetty.pid ]
-        then
-            echo "Jetty running pid="`cat $JETTY_RUN/jetty.pid`
-            exit 0
-        fi
-        exit 1
-        ;;
-
-*)
-        usage
-	;;
-esac
-
-exit 0
-
-
-
diff --git a/jetty-distribution/src/main/resources/bin/jetty-xinetd.sh b/jetty-distribution/src/main/resources/bin/jetty-xinetd.sh
deleted file mode 100755
index f74b932..0000000
--- a/jetty-distribution/src/main/resources/bin/jetty-xinetd.sh
+++ /dev/null
@@ -1,14 +0,0 @@
-#!/bin/bash
-
-
-# look for JETTY_HOME
-if [ -z "$JETTY_HOME" ] 
-then
-  JETTY_HOME_1=`dirname "$0"`
-  JETTY_HOME_1=`dirname "$JETTY_HOME_1"`
-  JETTY_HOME=${JETTY_HOME_1} 
-fi
-
-cd $JETTY_HOME
-exec /usr/bin/java -Djetty.port=8088 -jar start.jar etc/jetty.xml etc/jetty-xinetd.xml
-
diff --git a/jetty-distribution/src/main/resources/bin/jetty.sh b/jetty-distribution/src/main/resources/bin/jetty.sh
index baafc4d..da6f571 100755
--- a/jetty-distribution/src/main/resources/bin/jetty.sh
+++ b/jetty-distribution/src/main/resources/bin/jetty.sh
@@ -1,25 +1,31 @@
-#!/usr/bin/env bash  
+#!/usr/bin/env bash
 #
 # Startup script for jetty under *nix systems (it works under NT/cygwin too).
 
+##################################################
+# Set the name which is used by other variables.
+# Defaults to the file name without extension.
+##################################################
+NAME=$(echo $(basename $0) | sed -e 's/^[SK][0-9]*//' -e 's/\.sh$//')
+
 # To get the service to restart correctly on reboot, uncomment below (3 lines):
 # ========================
 # chkconfig: 3 99 99
-# description: Jetty 8 webserver
+# description: Jetty 9 webserver
 # processname: jetty
 # ========================
 
 # Configuration files
 #
-# /etc/default/jetty
+# /etc/default/$NAME
 #   If it exists, this is read at the start of script. It may perform any 
 #   sequence of shell commands, like setting relevant environment variables.
 #
-# $HOME/.jettyrc
+# $HOME/.$NAMErc (e.g. $HOME/.jettyrc)
 #   If it exists, this is read at the start of script. It may perform any 
 #   sequence of shell commands, like setting relevant environment variables.
 #
-# /etc/jetty.conf
+# /etc/$NAME.conf
 #   If found, and no configurations were given on the command line,
 #   the file will be used as this script's configuration. 
 #   Each line in the file may contain:
@@ -28,13 +34,10 @@
 #       config.xml file.
 #     - The path to a directory. Each *.xml file in the directory will be
 #       passed to jetty as a config.xml file.
+#     - All other lines will be passed, as-is to the start.jar
 #
 #   The files will be checked for existence before being passed to jetty.
 #
-# $JETTY_HOME/etc/jetty.xml
-#   If found, used as this script's configuration file, but only if
-#   /etc/jetty.conf was not present. See above.
-#   
 # Configuration variables
 #
 # JAVA
@@ -45,41 +48,37 @@
 #
 # JETTY_HOME
 #   Where Jetty is installed. If not set, the script will try go
-#   guess it by first looking at the invocation path for the script,
-#   and then by looking in standard locations as $HOME/opt/jetty
-#   and /opt/jetty. The java system property "jetty.home" will be
+#   guess it by looking at the invocation path for the script
+#   The java system property "jetty.home" will be
 #   set to this value for use by configure.xml files, f.e.:
 #
 #    <Arg><Property name="jetty.home" default="."/>/webapps/jetty.war</Arg>
 #
-# JETTY_PORT
-#   Override the default port for Jetty servers. If not set then the
-#   default value in the xml configuration file will be used. The java
-#   system property "jetty.port" will be set to this value for use in
-#   configure.xml files. For example, the following idiom is widely
-#   used in the demo config files to respect this property in Listener
-#   configuration elements:
-#
-#    <Set name="Port"><Property name="jetty.port" default="8080"/></Set>
-#
-#   Note: that the config file could ignore this property simply by saying:
-#
-#    <Set name="Port">8080</Set>
+# JETTY_BASE
+#   Where your Jetty base directory is.  If not set, the value from
+#   $JETTY_HOME will be used.
 #
 # JETTY_RUN
-#   Where the jetty.pid file should be stored. It defaults to the
-#   first available of /var/run, /usr/var/run, JETTY_HOME and /tmp 
+#   Where the $NAME.pid file should be stored. It defaults to the
+#   first available of /var/run, /usr/var/run, JETTY_BASE and /tmp
 #   if not set.
 #  
 # JETTY_PID
-#   The Jetty PID file, defaults to $JETTY_RUN/jetty.pid
+#   The Jetty PID file, defaults to $JETTY_RUN/$NAME.pid
 #   
 # JETTY_ARGS
 #   The default arguments to pass to jetty.
+#   For example
+#      JETTY_ARGS=jetty.port=8080 jetty.spdy.port=8443 jetty.secure.port=443
 #
 # JETTY_USER
 #   if set, then used as a username to run the server as
 #
+# JETTY_SHELL
+#   If set, then used as the shell by su when starting the server.  Will have
+#   no effect if start-stop-daemon exists.  Useful when JETTY_USER does not
+#   have shell access, e.g. /bin/false
+#
 
 usage()
 {
@@ -106,8 +105,14 @@ findDirectory()
 
 running()
 {
-  local PID=$(cat "$1" 2>/dev/null) || return 1
-  kill -0 "$PID" 2>/dev/null
+  if [ -f "$1" ]
+  then
+    local PID=$(cat "$1" 2>/dev/null) || return 1
+    kill -0 "$PID" 2>/dev/null
+    return
+  fi
+  rm -f "$1"
+  return 1
 }
 
 started()
@@ -161,7 +166,7 @@ then
   ETC=$HOME/etc
 fi
 
-for CONFIG in $ETC/default/jetty{,8} $HOME/.jettyrc; do
+for CONFIG in $ETC/default/${NAME}{,9} $HOME/.${NAME}rc; do
   if [ -f "$CONFIG" ] ; then 
     readConfig "$CONFIG"
   fi
@@ -176,7 +181,7 @@ TMPDIR=${TMPDIR:-/tmp}
 ##################################################
 # Jetty's hallmark
 ##################################################
-JETTY_INSTALL_TRACE_FILE="etc/jetty.xml"
+JETTY_INSTALL_TRACE_FILE="start.jar"
 
 
 ##################################################
@@ -186,13 +191,15 @@ if [ -z "$JETTY_HOME" ]
 then
   JETTY_SH=$0
   case "$JETTY_SH" in
-    /*)   ;;
-    ./*)  ;;
-    *)    JETTY_SH=./$JETTY_SH ;;
+    /*)     JETTY_HOME=${JETTY_SH%/*/*} ;;
+    ./*/*)  JETTY_HOME=${JETTY_SH%/*/*} ;;
+    ./*)    JETTY_HOME=.. ;;
+    */*/*)  JETTY_HOME=./${JETTY_SH%/*/*} ;;
+    */*)    JETTY_HOME=. ;;
+    *)      JETTY_HOME=.. ;;
   esac
-  JETTY_HOME=${JETTY_SH%/*/*}
 
-  if [ ! -f "${JETTY_SH%/*/*}/$JETTY_INSTALL_TRACE_FILE" ]
+  if [ ! -f "$JETTY_HOME/$JETTY_INSTALL_TRACE_FILE" ]
   then 
     JETTY_HOME=
   fi
@@ -200,64 +207,6 @@ fi
 
 
 ##################################################
-# if no JETTY_HOME, search likely locations.
-##################################################
-if [ -z "$JETTY_HOME" ] ; then
-  STANDARD_LOCATIONS=(
-        "/usr/share"
-        "/usr/share/java"
-        "${HOME}"
-        "${HOME}/src"
-        "${HOME}/opt"
-        "/opt"
-        "/java"
-        "/usr/local"
-        "/usr/local/share"
-        "/usr/local/share/java"
-        "/home"
-        )
-  JETTY_DIR_NAMES=(
-        "jetty-8"
-        "jetty8"
-        "jetty-8.*"
-        "jetty"
-        "Jetty-8"
-        "Jetty8"
-        "Jetty-8.*"
-        "Jetty"
-        )
-        
-  for L in "${STANDARD_LOCATIONS[@]}"
-  do
-    for N in "${JETTY_DIR_NAMES[@]}"
-    do
-      POSSIBLE_JETTY_HOME=("$L/"$N)
-      if [ ! -d "$POSSIBLE_JETTY_HOME" ]
-      then
-        # Not a directory. skip.
-        unset POSSIBLE_JETTY_HOME
-      elif [ ! -f "$POSSIBLE_JETTY_HOME/$JETTY_INSTALL_TRACE_FILE" ]
-      then
-        # Trace file not found. skip.
-        unset POSSIBLE_JETTY_HOME
-      else
-        # Good hit, Use it
-        JETTY_HOME=$POSSIBLE_JETTY_HOME
-        # Break out of JETTY_DIR_NAMES loop
-        break
-      fi
-    done
-    if [ -n "$POSSIBLE_JETTY_HOME" ]
-    then
-      # We have found our JETTY_HOME
-      # Break out of STANDARD_LOCATIONS loop
-      break
-    fi
-  done
-fi
-
-
-##################################################
 # No JETTY_HOME yet? We're out of luck!
 ##################################################
 if [ -z "$JETTY_HOME" ]; then
@@ -269,6 +218,17 @@ cd "$JETTY_HOME"
 JETTY_HOME=$PWD
 
 
+##################################################
+# Set JETTY_BASE 
+##################################################
+if [ -z "$JETTY_BASE" ]; then
+  JETTY_BASE=$JETTY_HOME
+fi
+
+cd "$JETTY_BASE"
+JETTY_BASE=$PWD
+
+
 #####################################################
 # Check that jetty is where we think it is
 #####################################################
@@ -286,19 +246,50 @@ fi
 ##################################################
 if [ -z "$JETTY_CONF" ] 
 then
-  if [ -f $ETC/jetty.conf ]
+  if [ -f $ETC/${NAME}.conf ]
   then
-    JETTY_CONF=$ETC/jetty.conf
+    JETTY_CONF=$ETC/${NAME}.conf
+  elif [ -f "$JETTY_BASE/etc/jetty.conf" ]
+  then
+    JETTY_CONF=$JETTY_BASE/etc/jetty.conf
   elif [ -f "$JETTY_HOME/etc/jetty.conf" ]
   then
     JETTY_CONF=$JETTY_HOME/etc/jetty.conf
   fi
 fi
 
+#####################################################
+# Find a location for the pid file
+#####################################################
+if [ -z "$JETTY_RUN" ] 
+then
+  JETTY_RUN=$(findDirectory -w /var/run /usr/var/run $JETTY_BASE /tmp)
+fi
+
+#####################################################
+# Find a pid and state file
+#####################################################
+if [ -z "$JETTY_PID" ] 
+then
+  JETTY_PID="$JETTY_RUN/${NAME}.pid"
+fi
+
+if [ -z "$JETTY_STATE" ] 
+then
+  JETTY_STATE=$JETTY_BASE/${NAME}.state
+fi
+
+case "`uname`" in
+CYGWIN*) JETTY_STATE="`cygpath -w $JETTY_STATE`";;
+esac
+
+
+JETTY_ARGS=(${JETTY_ARGS[*]} "jetty.state=$JETTY_STATE")
+
 ##################################################
 # Get the list of config.xml files from jetty.conf
 ##################################################
-if [ -z "$CONFIGS" ] && [ -f "$JETTY_CONF" ] && [ -r "$JETTY_CONF" ] 
+if [ -f "$JETTY_CONF" ] && [ -r "$JETTY_CONF" ] 
 then
   while read -r CONF
   do
@@ -310,46 +301,23 @@ then
     then
       # assume it's a directory with configure.xml files
       # for example: /etc/jetty.d/
-      # sort the files before adding them to the list of CONFIGS
+      # sort the files before adding them to the list of JETTY_ARGS
       for XMLFILE in "$CONF/"*.xml
       do
         if [ -r "$XMLFILE" ] && [ -f "$XMLFILE" ] 
         then
-          CONFIGS+=("$XMLFILE")
+          JETTY_ARGS=(${JETTY_ARGS[*]} "$XMLFILE")
         else
           echo "** WARNING: Cannot read '$XMLFILE' specified in '$JETTY_CONF'" 
         fi
       done
     else
       # assume it's a command line parameter (let start.jar deal with its validity)
-      CONFIGS+=("$CONF")
+      JETTY_ARGS=(${JETTY_ARGS[*]} "$CONF")
     fi
   done < "$JETTY_CONF"
 fi
 
-#####################################################
-# Find a location for the pid file
-#####################################################
-if [ -z "$JETTY_RUN" ] 
-then
-  JETTY_RUN=$(findDirectory -w /var/run /usr/var/run $JETTY_HOME /tmp)
-fi
-
-#####################################################
-# Find a pid and state file
-#####################################################
-if [ -z "$JETTY_PID" ] 
-then
-  JETTY_PID="$JETTY_RUN/jetty.pid"
-fi
-
-if [ -z "$JETTY_STATE" ] 
-then
-  JETTY_STATE=$JETTY_HOME/jetty.state
-fi
-JAVA_OPTIONS+=("-Djetty.state=$JETTY_STATE")
-rm -f $JETTY_STATE
-
 ##################################################
 # Setup JAVA if unset
 ##################################################
@@ -360,24 +328,29 @@ fi
 
 if [ -z "$JAVA" ]
 then
-  echo "Cannot find a Java JDK. Please set either set JAVA or put java (>=1.5) in your PATH." 2>&2
+  echo "Cannot find a Java JDK. Please set either set JAVA or put java (>=1.5) in your PATH." >&2
   exit 1
 fi
 
 #####################################################
-# See if JETTY_PORT is defined
+# See if JETTY_LOGS is defined
 #####################################################
-if [ "$JETTY_PORT" ] 
+if [ -z "$JETTY_LOGS" ] && [ -d $JETTY_BASE/logs ] 
 then
-  JAVA_OPTIONS+=("-Djetty.port=$JETTY_PORT")
+  JETTY_LOGS=$JETTY_BASE/logs
+fi
+if [ -z "$JETTY_LOGS" ] && [ -d $JETTY_HOME/logs ] 
+then
+  JETTY_LOGS=$JETTY_HOME/logs
 fi
-
-#####################################################
-# See if JETTY_LOGS is defined
-#####################################################
 if [ "$JETTY_LOGS" ]
 then
-  JAVA_OPTIONS+=("-Djetty.logs=$JETTY_LOGS")
+
+  case "`uname`" in
+  CYGWIN*) JETTY_LOGS="`cygpath -w $JETTY_LOGS`";;
+  esac
+
+  JAVA_OPTIONS=(${JAVA_OPTIONS[*]} "-Djetty.logs=$JETTY_LOGS")
 fi
 
 #####################################################
@@ -392,21 +365,35 @@ esac
 #####################################################
 # Add jetty properties to Java VM options.
 #####################################################
-JAVA_OPTIONS+=("-Djetty.home=$JETTY_HOME" "-Djava.io.tmpdir=$TMPDIR")
 
-[ -f "$JETTY_HOME/etc/start.config" ] && JAVA_OPTIONS=("-DSTART=$JETTY_HOME/etc/start.config" "${JAVA_OPTIONS[@]}")
+case "`uname`" in
+CYGWIN*) 
+JETTY_HOME="`cygpath -w $JETTY_HOME`"
+JETTY_BASE="`cygpath -w $JETTY_BASE`"
+TMPDIR="`cygpath -w $TMPDIR`"
+;;
+esac
+
+JAVA_OPTIONS=(${JAVA_OPTIONS[*]} "-Djetty.home=$JETTY_HOME" "-Djetty.base=$JETTY_BASE" "-Djava.io.tmpdir=$TMPDIR")
 
 #####################################################
 # This is how the Jetty server will be started
 #####################################################
 
 JETTY_START=$JETTY_HOME/start.jar
-[ ! -f "$JETTY_START" ] && JETTY_START=$JETTY_HOME/lib/start.jar
+START_INI=$JETTY_BASE/start.ini
+START_D=$JETTY_BASE/start.d
+if [ ! -f "$START_INI" -a ! -d "$START_D" ]
+then
+  echo "Cannot find a start.ini file or a start.d directory in your JETTY_BASE directory: $JETTY_BASE" >&2
+  exit 1
+fi
 
-START_INI=$(dirname $JETTY_START)/start.ini
-[ -r "$START_INI" ] || START_INI=""
+case "`uname`" in
+CYGWIN*) JETTY_START="`cygpath -w $JETTY_START`";;
+esac
 
-RUN_ARGS=(${JAVA_OPTIONS[@]} -jar "$JETTY_START" $JETTY_ARGS "${CONFIGS[@]}")
+RUN_ARGS=(${JAVA_OPTIONS[@]} -jar "$JETTY_START" ${JETTY_ARGS[*]})
 RUN_CMD=("$JAVA" ${RUN_ARGS[@]})
 
 #####################################################
@@ -415,15 +402,17 @@ RUN_CMD=("$JAVA" ${RUN_ARGS[@]})
 #####################################################
 if (( DEBUG ))
 then
+  echo "START_INI      =  $START_INI"
+  echo "START_D        =  $START_D"
   echo "JETTY_HOME     =  $JETTY_HOME"
+  echo "JETTY_BASE     =  $JETTY_BASE"
   echo "JETTY_CONF     =  $JETTY_CONF"
-  echo "JETTY_RUN      =  $JETTY_RUN"
   echo "JETTY_PID      =  $JETTY_PID"
-  echo "JETTY_ARGS     =  $JETTY_ARGS"
-  echo "CONFIGS        =  ${CONFIGS[*]}"
+  echo "JETTY_START    =  $JETTY_START"
+  echo "JETTY_ARGS     =  ${JETTY_ARGS[*]}"
   echo "JAVA_OPTIONS   =  ${JAVA_OPTIONS[*]}"
   echo "JAVA           =  $JAVA"
-  echo "RUN_CMD        =  ${RUN_CMD}"
+  echo "RUN_CMD        =  ${RUN_CMD[*]}"
 fi
 
 ##################################################
@@ -434,7 +423,7 @@ case "$ACTION" in
     echo -n "Starting Jetty: "
 
     if (( NO_START )); then 
-      echo "Not starting jetty - NO_START=1";
+      echo "Not starting ${NAME} - NO_START=1";
       exit
     fi
 
@@ -446,29 +435,29 @@ case "$ACTION" in
         CH_USER="-c$JETTY_USER"
       fi
 
-      start-stop-daemon -S -p"$JETTY_PID" $CH_USER -d"$JETTY_HOME" -b -m -a "$JAVA" -- "${RUN_ARGS[@]}" --daemon
+      start-stop-daemon -S -p"$JETTY_PID" $CH_USER -d"$JETTY_BASE" -b -m -a "$JAVA" -- "${RUN_ARGS[@]}" start-log-file="$JETTY_LOGS/start.log"
 
     else
 
-      if [ -f "$JETTY_PID" ]
+      if running $JETTY_PID
       then
-        if running $JETTY_PID
-        then
-          echo "Already Running!"
-          exit 1
-        else
-          # dead pid file - remove
-          rm -f "$JETTY_PID"
-        fi
+        echo "Already Running $(cat $JETTY_PID)!"
+        exit 1
       fi
 
-      if [ "$JETTY_USER" ] 
+      if [ -n "$JETTY_USER" ] 
       then
+        unset SU_SHELL
+        if [ "$JETTY_SHELL" ]
+        then
+          SU_SHELL="-s $JETTY_SHELL"
+        fi
+
         touch "$JETTY_PID"
         chown "$JETTY_USER" "$JETTY_PID"
         # FIXME: Broken solution: wordsplitting, pathname expansion, arbitrary command execution, etc.
-        su - "$JETTY_USER" -c "
-          exec ${RUN_CMD[*]} --daemon &
+        su - "$JETTY_USER" $SU_SHELL -c "
+          exec ${RUN_CMD[*]} start-log-file="$JETTY_LOGS/start.log" &
           disown \$!
           echo \$! > '$JETTY_PID'"
       else
@@ -479,13 +468,14 @@ case "$ACTION" in
 
     fi
 
-    if expr "${CONFIGS[*]}" : '.*etc/jetty-started.xml.*' >/dev/null
+    if expr "${JETTY_ARGS[*]}" : '.*jetty-started.xml.*' >/dev/null
     then
       if started "$JETTY_STATE" "$JETTY_PID"
       then
         echo "OK `date`"
       else
         echo "FAILED `date`"
+        exit 1
       fi
     else
       echo "ok `date`"
@@ -506,11 +496,17 @@ case "$ACTION" in
 
         sleep 1
       done
-
-      rm -f "$JETTY_PID"
-      echo OK
     else
+      if [ ! -f "$JETTY_PID" ] ; then
+        echo "ERROR: no pid found at $JETTY_PID"
+        exit 1
+      fi
+
       PID=$(cat "$JETTY_PID" 2>/dev/null)
+      if [ -z "$PID" ] ; then
+        echo "ERROR: no pid id found in $JETTY_PID"
+        exit 1
+      fi
       kill "$PID" 2>/dev/null
       
       TIMEOUT=30
@@ -521,11 +517,12 @@ case "$ACTION" in
 
         sleep 1
       done
-
-      rm -f "$JETTY_PID"
-      echo OK
     fi
 
+    rm -f "$JETTY_PID"
+    rm -f "$JETTY_STATE"
+    echo OK
+
     ;;
 
   restart)
@@ -555,39 +552,34 @@ case "$ACTION" in
   run|demo)
     echo "Running Jetty: "
 
-    if [ -f "$JETTY_PID" ]
+    if running "$JETTY_PID"
     then
-      if running "$JETTY_PID"
-      then
-        echo "Already Running!"
-        exit 1
-      else
-        # dead pid file - remove
-        rm -f "$JETTY_PID"
-      fi
+      echo Already Running $(cat "$JETTY_PID")!
+      exit 1
     fi
 
     exec "${RUN_CMD[@]}"
-
     ;;
 
   check|status)
     echo "Checking arguments to Jetty: "
+    echo "START_INI      =  $START_INI"
+    echo "START_D        =  $START_D"
     echo "JETTY_HOME     =  $JETTY_HOME"
+    echo "JETTY_BASE     =  $JETTY_BASE"
     echo "JETTY_CONF     =  $JETTY_CONF"
-    echo "JETTY_RUN      =  $JETTY_RUN"
     echo "JETTY_PID      =  $JETTY_PID"
-    echo "JETTY_PORT     =  $JETTY_PORT"
+    echo "JETTY_START    =  $JETTY_START"
     echo "JETTY_LOGS     =  $JETTY_LOGS"
-    echo "START_INI      =  $START_INI"
-    echo "CONFIGS        =  ${CONFIGS[*]}"
-    echo "JAVA_OPTIONS   =  ${JAVA_OPTIONS[*]}"
-    echo "JAVA           =  $JAVA"
+    echo "JETTY_STATE    =  $JETTY_STATE"
     echo "CLASSPATH      =  $CLASSPATH"
+    echo "JAVA           =  $JAVA"
+    echo "JAVA_OPTIONS   =  ${JAVA_OPTIONS[*]}"
+    echo "JETTY_ARGS     =  ${JETTY_ARGS[*]}"
     echo "RUN_CMD        =  ${RUN_CMD[*]}"
     echo
     
-    if [ -f "$JETTY_PID" ]
+    if running "$JETTY_PID"
     then
       echo "Jetty running pid=$(< "$JETTY_PID")"
       exit 0
diff --git a/jetty-distribution/src/main/resources/contexts-available/README.TXT b/jetty-distribution/src/main/resources/contexts-available/README.TXT
deleted file mode 100644
index 7fa2ac5..0000000
--- a/jetty-distribution/src/main/resources/contexts-available/README.TXT
+++ /dev/null
@@ -1,3 +0,0 @@
-
-This directory contains example contexts that may be deployed by
-moving/copying/linking them to the ../contexts directory.
diff --git a/jetty-distribution/src/main/resources/contexts-available/resources.xml b/jetty-distribution/src/main/resources/contexts-available/resources.xml
deleted file mode 100644
index 87a8d32..0000000
--- a/jetty-distribution/src/main/resources/contexts-available/resources.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0"  encoding="ISO-8859-1"?>
-<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://jetty.eclipse.org/configure.dtd">
-
-<!--
-Configure a custom context for serving static resources 
-
-This context contains only a ResourceHandler
-to serve static html files and images.
--->
-
-<Configure class="org.eclipse.jetty.server.handler.ContextHandler">
-  <Call class="org.eclipse.jetty.util.log.Log" name="debug"><Arg>Configure javadoc.xml</Arg></Call>
-  <Set name="contextPath">/resources</Set>
-  <Set name="resourceBase"><SystemProperty name="jetty.home" default="."/>/resources/</Set>
-  <Set name="handler">
-    <New class="org.eclipse.jetty.server.handler.ResourceHandler">
-      <Set name="welcomeFiles">
-        <Array type="String">
-          <Item>index.html</Item>
-        </Array>
-      </Set>
-      <Set name="cacheControl">max-age=3600,public</Set>
-    </New>
-  </Set>
-
-</Configure>
-
diff --git a/jetty-distribution/src/main/resources/contexts/README.TXT b/jetty-distribution/src/main/resources/contexts/README.TXT
deleted file mode 100644
index 36f67d7..0000000
--- a/jetty-distribution/src/main/resources/contexts/README.TXT
+++ /dev/null
@@ -1,15 +0,0 @@
-
-This directory is scanned by the ContextDeployer instance
-configured by the standard $JETTY_HOME/etc/jetty.xml configuration. 
-
-It should contain XmlConfiguration files that describe individual
-contexts to be deployed to the server.  This directory is scanned
-for additions, removals and updates for hot deployment.
-
-Frequenty the context configuration files here will reference
-war files or directories from $JETTY_HOME/webapps.  Care must be
-taken to avoid a WebAppDeployer deploying duplicates of such
-webapplications.
-
-The directory ../contexts-available contains more example contexts
-that may be deployed by being copied here.
diff --git a/jetty-distribution/src/main/resources/contexts/javadoc.xml b/jetty-distribution/src/main/resources/contexts/javadoc.xml
deleted file mode 100644
index d2a1509..0000000
--- a/jetty-distribution/src/main/resources/contexts/javadoc.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0"  encoding="ISO-8859-1"?>
-<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
-
-<!--
-Configure a custom context for the javadoc.
-
-This context contains only a ServletHandler with a default servlet
-to serve static html files and images.
--->
-
-<Configure class="org.eclipse.jetty.server.handler.ContextHandler">
-  <Call class="org.eclipse.jetty.util.log.Log" name="debug"><Arg>Configure javadoc.xml</Arg></Call>
-  <Set name="contextPath">/javadoc</Set>
-  <Set name="resourceBase"><SystemProperty name="jetty.home" default="."/>/javadoc/</Set>
-  <Set name="handler">
-    <New class="org.eclipse.jetty.server.handler.ResourceHandler">
-      <Set name="welcomeFiles">
-        <Array type="String">
-          <Item>index.html</Item>
-        </Array>
-      </Set>
-      <Set name="cacheControl">max-age=3600,public</Set>
-    </New>
-  </Set>
-
-</Configure>
-
diff --git a/jetty-distribution/src/main/resources/demo-base/etc/keystore b/jetty-distribution/src/main/resources/demo-base/etc/keystore
new file mode 100644
index 0000000..08f6cda
Binary files /dev/null and b/jetty-distribution/src/main/resources/demo-base/etc/keystore differ
diff --git a/jetty-distribution/src/main/resources/demo-base/webapps/README.TXT b/jetty-distribution/src/main/resources/demo-base/webapps/README.TXT
new file mode 100644
index 0000000..d6fb93b
--- /dev/null
+++ b/jetty-distribution/src/main/resources/demo-base/webapps/README.TXT
@@ -0,0 +1,12 @@
+
+This directory is scanned by the demo WebAppDeployer provider 
+created in the etc/jetty-demo.xml file and enabled by the 
+start.d/900-demo.ini file.
+
+To disable the demo, either remove the start.d/900-demo.ini or issue the following command:
+
+  java -jar start.jar --disable=demo
+
+
+For normal webapp deployment, use the webapps directory.
+
diff --git a/jetty-distribution/src/main/resources/demo-base/webapps/ROOT/images/jetty-header.jpg b/jetty-distribution/src/main/resources/demo-base/webapps/ROOT/images/jetty-header.jpg
new file mode 100644
index 0000000..f40c364
Binary files /dev/null and b/jetty-distribution/src/main/resources/demo-base/webapps/ROOT/images/jetty-header.jpg differ
diff --git a/jetty-distribution/src/main/resources/demo-base/webapps/ROOT/images/webtide_logo.jpg b/jetty-distribution/src/main/resources/demo-base/webapps/ROOT/images/webtide_logo.jpg
new file mode 100644
index 0000000..b949919
Binary files /dev/null and b/jetty-distribution/src/main/resources/demo-base/webapps/ROOT/images/webtide_logo.jpg differ
diff --git a/jetty-distribution/src/main/resources/demo-base/webapps/ROOT/index.html b/jetty-distribution/src/main/resources/demo-base/webapps/ROOT/index.html
new file mode 100644
index 0000000..ce03053
--- /dev/null
+++ b/jetty-distribution/src/main/resources/demo-base/webapps/ROOT/index.html
@@ -0,0 +1,72 @@
+<html xmlns=\ "http://www.w3.org/1999/xhtml\" xml:lang=\"en\">
+<head>
+<META http-equiv="Pragma" content="no-cache">
+<META http-equiv="Cache-Control" content="no-cache,no-store">
+<META HTTP-EQUIV="Content-Script-Type" CONTENT="text/javascript">
+<title>Welcome to Jetty-9</title>
+<style type="text/css" title="jetty">
+ at import url(jetty.css);
+</style>
+</head>
+<body>
+
+  <div id="header"></div>
+
+  <div id="content">
+    <h1>Welcome to Jetty 9</h1>
+
+    <p>
+      The Jetty project is a 100% Java <a
+        href="http://en.wikipedia.org/wiki/Java_Servlet">Servlet</a>
+      Container which supports asynchronous server and client
+      implementations of the <a href="http://en.wikipedia.org/wiki/HTTP">HTTP</a>,
+      <a href="http://en.wikipedia.org/wiki/WebSocket">Websocket</a> and <a
+        href="http://en.wikipedia.org/wiki/SPDY">SPDY</a> protocols. The
+      project is 100% <a href="http://en.wikipedia.org/wiki/Open_source">Open Source</a> and hosted by the <a href="http://www.eclipse.org">Eclipse Foundation</a> at <a href="http://www.eclipse.org/jetty/">http://www.eclipse.org/jetty</a>.
+    </p>
+  </div>
+
+  <div id="links">
+    <table>
+      <tr>
+        <td>
+          <h2>examples ...</h2>
+          <ul>
+            <li><a href="/test/">Test Jetty Webapp</a></li>
+            <li><a href="/async-rest/">Async Rest</a></li>
+            <li><a href="/test-jaas/">JAAS Test</a></li>
+            <li><a href="/test-jndi/">JNDI Test</a></li>
+            <li><a href="/test-spec/">Servlet 3.1 Test</a></li>
+            <li><a href="/oldContextPath/">Redirected Context</a></li>
+          </ul>
+        </td>
+        <td>
+          <h2>information ...</h2>
+          <ul>
+            <li><a href="http://www.eclipse.org/jetty/">Jetty Homepage</a></li>
+            <li><a href="http://www.eclipse.org/jetty/documentation/current">Jetty Documentation</a></li>
+            <li><a href="/proxy/apidocs/">Javadoc</a> (via transparent proxy)</li>
+            <li><a href="/proxy/xref/">Xref</a> (via transparent proxy)</li>
+            <li><a
+              href="http://www.eclipse.org/jetty/powered">Jetty Powered</a></li>
+          </ul>
+        </td>
+        <td>
+          <h2>getting help ...</h2>
+          <ul>
+            <li><a href="http://www.eclipse.org/jetty/mailinglists.php">Mailing lists @ eclipse</a></li>
+            <li><a href="http://www.webtide.com/advice/">Developer Advice</a></li>
+            <li><a href="http://www.webtide.com/development">Custom Development</a></li>
+            <li><a href="http://www.webtide.com/support">Production support</a></li>
+          </ul>
+        </td>
+      </tr>
+    </table>
+  </div>
+
+  <div id='blog'>
+    <h1>Jetty Blog</h1>
+    <iframe src="http://www.webtide.com/blog.jsp" />
+  </div>
+</body>
+</html>
diff --git a/jetty-distribution/src/main/resources/demo-base/webapps/ROOT/jetty.css b/jetty-distribution/src/main/resources/demo-base/webapps/ROOT/jetty.css
new file mode 100644
index 0000000..90d281a
--- /dev/null
+++ b/jetty-distribution/src/main/resources/demo-base/webapps/ROOT/jetty.css
@@ -0,0 +1,351 @@
+BODY
+{
+  font-family: Arial, Helvetica, sans-serif;
+  background-color: #FFFFFF;
+  font-size: 10pt;
+  color: #666666;
+}
+
+img
+{
+  border: 0px;
+}
+
+
+div#header
+{
+  clear: both;
+  background-image: url('images/jetty-header.jpg');
+  background-repeat: no-repeat;
+  background-position: center;
+  height:200px;
+  border-bottom: 3px ridge #cccccc;
+  border-right: 3px ridge #cccccc;
+  border-top: 3px groove #cccccc;
+  border-left: 3px groove #cccccc;
+  width: 850px;
+  margin-left: auto;
+  margin-right: auto;
+}
+
+div#content
+{
+  clear: both;
+  font-size: 10pt;
+  font-weight: normal;
+  color: #666666;
+  margin-top: 30px;
+  width: 850px;
+  margin-left: auto;
+  margin-right: auto;
+}
+ 
+div#links table
+{
+  width: 850px;
+}
+ 
+div#links td
+{
+  vertical-align: top;
+  text-align: left;
+  border: 2px dotted #cccccc; 
+  padding: 8px;
+  background-color: #efefef;
+  width: 280px;
+}
+
+
+div#links
+{
+  margin-top:  20px;
+  width: 850px;
+  color: #999999;
+  margin-left: auto;
+  margin-right: auto;
+}
+
+
+div#links h1,h2,p 
+{
+  font-size: 10pt;
+  font-family: sans-serif;
+  text-align:left;
+  margin: 5px;
+}
+
+div#links h1
+{
+  font-size: 22pt;
+  font-weight: normal;
+  font-family: sans-serif;
+  letter-spacing: 6pt;
+  color: #666666;
+  text-align: center;
+  margin-top:20px;
+}
+
+
+div#links h2
+{
+  font-size: 12pt;
+  font-weight: normal;
+  letter-spacing: 2pt;
+  color: #666666;
+}
+
+
+
+div#blog
+{
+  margin-top:  20px;
+  width: 850px;
+  margin-left: auto;
+  margin-right: auto;
+}
+
+div#blog iframe
+{
+  width: 100%;
+  height:350px;
+  border: 2px dotted #cccccc; 
+}
+
+
+
+div#footer
+{
+  clear: both;
+  border-top: 4px groove #cccccc;
+  margin-top:20px;
+  padding-top:10px;
+  width: 850px;
+}
+
+
+
+h1
+{
+  font-size: 14pt;
+  text-align:center;
+}
+
+A:link 
+{ 
+  color: #0099cc; 
+  text-decoration: none;
+  font-weight: normal;
+  font-size: 10pt;
+  font-family:sans-serif;
+}
+
+A:visited 
+{ 
+  color: #0099cc; 
+  text-decoration: none;
+  font-weight: normal;
+  font-size: 10pt;
+  font-family:sans-serif;
+}
+
+A:hover 
+{ 
+  color: #ff6600; 
+  text-decoration: none;
+  font-weight: normal;
+  font-size: 10pt;
+  font-family:sans-serif;
+} 
+
+A:active 
+{ 
+  color: #0099cc; 
+  text-decoration: none;
+  font-weight: normal;
+  font-size: 10pt;
+  font-family:sans-serif;
+} 
+
+A.disabled
+{
+	color: #666666;
+}
+
+
+form
+{
+  display: inline;
+}
+
+.SEVERE
+{
+  background-color: #ff0000;
+}
+
+.WARNING
+{
+  background-color: #ff6633;
+}
+
+.INFO
+{
+  background-color: #00cc33;
+}
+
+.CONFIG
+{
+  background-color: #999999;
+}
+
+.FINE
+{
+  background-color: #3333ff;
+}
+
+.FINER
+{
+  background-color: #3333cc;
+}
+
+.FINEST
+{
+  background-color: #333399;
+}
+
+
+table
+{
+  width: 100%;
+  border-spacing: 0px;
+  border-collapse: separate;
+}
+
+
+td
+{
+  font-family: Arial,sans-serif;
+  font-size: 10pt;
+  padding: 2px;
+  margin: 0px;
+  border: #eeeeee groove 2px;
+}
+
+
+#content td
+{
+  vertical-align: top;
+}
+
+#key
+{
+  border-spacing: 0px;
+  border: 0px;
+}
+
+#key td
+{
+  border: #eeeeee groove 2px;
+  font-size: 8pt;
+  font-family: Helvetica;
+  font-weight: bold;
+  color: white;
+}
+
+thead th
+{
+  border: #eeeeee groove 2px;
+}
+
+.seq
+{
+    width: 7em;
+    min-width: 7em;
+    max-width: 7em;
+    color: white;
+    font-family: Helvetica;
+    font-weight: bold;
+}
+
+.date
+{
+    width: 19em;
+    min-width: 19em;
+    max-width: 19em;
+    color: black;
+    background-color: #eeeeee;
+}
+
+.thread
+{
+    width: 3em;
+    min-width: 3em;
+    max-width: 3em;
+    color: black;
+    background-color: #eeeeee;
+}
+
+.logger
+{
+    width: 30em;
+    min-width: 30em;
+    max-width: 30em;
+    background-color: #eeeeee;
+}
+
+.logclass
+{
+    width: 30em;
+    min-width: 30em;
+    max-width: 30em;
+    background-color: #eeeeee;
+}
+
+.method
+{
+    width: 20em;
+    min-width: 20em;
+    max-width: 20em;
+    background-color: #eeeeee;
+}
+
+.message
+{
+    width: 250em;
+    min-width: 250em;
+    max-width: 250em;
+    text-align: left;
+    background-color: #eeeeee;
+    font-family: monospace;
+}
+
+
+.message table
+{
+    border: 0px;
+}
+
+.message td,tr
+{
+    text-align: left;
+    vertical-align: top;
+    padding-top: 0px;
+    border: 0px;
+}
+
+.except
+{  
+    background: #eeeeee;
+    padding-top: 2px;
+}
+
+.excepticon
+{
+    width: 40px;
+    max-width: 40px;
+    min-width: 40px;
+}
+
+button
+{
+  background: white;
+}
diff --git a/jetty-distribution/src/main/resources/demo-base/webapps/example-moved.xml b/jetty-distribution/src/main/resources/demo-base/webapps/example-moved.xml
new file mode 100644
index 0000000..53d6bfd
--- /dev/null
+++ b/jetty-distribution/src/main/resources/demo-base/webapps/example-moved.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
+
+<!-- Simple handler to redirect from old path to new -->
+<Configure class="org.eclipse.jetty.server.handler.MovedContextHandler">
+  <Set name="contextPath">/oldContextPath</Set>
+  <Set name="newContextURL">/test/dump/moved</Set>
+  <Set name="permanent">false</Set>
+  <Set name="discardPathInfo">false</Set>
+  <Set name="discardQuery">false</Set>
+  <Set name="expires">-1</Set>
+</Configure>
diff --git a/jetty-distribution/src/main/resources/etc/hawtio.xml b/jetty-distribution/src/main/resources/etc/hawtio.xml
new file mode 100644
index 0000000..228adc1
--- /dev/null
+++ b/jetty-distribution/src/main/resources/etc/hawtio.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0"?>
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
+
+<Configure id="Contexts" class="org.eclipse.jetty.server.handler.ContextHandlerCollection">
+  <Call name="addHandler">
+    <Arg>
+      <New class="org.eclipse.jetty.webapp.WebAppContext">
+	<Set name="contextPath">/hawtio</Set>
+	<Set name="war"><Property name="jetty.base" default="."/>/lib/hawtio/hawtio.war</Set>
+	<Set name="extractWAR">true</Set>
+	<Set name="copyWebDir">false</Set>
+	<Set name="defaultsDescriptor"><Property name="jetty.home" default="."/>/etc/webdefault.xml</Set>
+      </New>
+    </Arg>
+  </Call>
+</Configure>
diff --git a/jetty-distribution/src/main/resources/etc/jamon.xml b/jetty-distribution/src/main/resources/etc/jamon.xml
new file mode 100644
index 0000000..d00d9c7
--- /dev/null
+++ b/jetty-distribution/src/main/resources/etc/jamon.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0"?>
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
+
+<!-- =============================================================== -->
+<!-- Mixin the Jamon Handler                                         -->
+<!-- =============================================================== -->
+
+<Configure id="Server" class="org.eclipse.jetty.server.Server">
+
+  <Get id="oldhandler" name="handler" />
+  <Set name="handler">
+    <New id="JamonHandler" class="com.jamonapi.http.JAMonJettyHandlerNew">
+      <Set name="handler"><Ref refid="oldhandler" /></Set>
+      <Set name="summaryLabels"><Property name="jamon.summaryLabels" /></Set>
+    </New>
+  </Set>
+
+  <Ref refid="Contexts">
+    <Call name="addHandler">
+      <Arg>
+	<New class="org.eclipse.jetty.webapp.WebAppContext">
+	  <Set name="contextPath">/jamon</Set>
+	  <Set name="war"><Property name="jetty.base" default="."/>/lib/jamon/jamon.war</Set>
+	  <Set name="extractWAR">true</Set>
+	  <Set name="copyWebDir">false</Set>
+	  <Set name="defaultsDescriptor"><Property name="jetty.home" default="."/>/etc/webdefault.xml</Set>
+	</New>
+      </Arg>
+    </Call>
+  </Ref>
+
+</Configure>
diff --git a/jetty-distribution/src/main/resources/etc/jetty-started.xml b/jetty-distribution/src/main/resources/etc/jetty-started.xml
index 9207b1c..b958074 100644
--- a/jetty-distribution/src/main/resources/etc/jetty-started.xml
+++ b/jetty-distribution/src/main/resources/etc/jetty-started.xml
@@ -8,7 +8,7 @@
   <Call name="addLifeCycleListener">
     <Arg>
       <New class="org.eclipse.jetty.util.component.FileNoticeLifeCycleListener">
-	<Arg><SystemProperty name="jetty.state" default="./jetty.state"/></Arg>
+        <Arg><Property name="jetty.state" default="./jetty.state"/></Arg>
       </New>
     </Arg>
   </Call>
diff --git a/jetty-distribution/src/main/resources/etc/jetty.conf b/jetty-distribution/src/main/resources/etc/jetty.conf
index 2e2e1aa..2061402 100644
--- a/jetty-distribution/src/main/resources/etc/jetty.conf
+++ b/jetty-distribution/src/main/resources/etc/jetty.conf
@@ -8,5 +8,5 @@
 # Each line in this file becomes an arguement to start.jar
 # in addition to those found in the start.ini file
 # =======================================================
-etc/jetty-logging.xml
-etc/jetty-started.xml
+jetty-logging.xml
+jetty-started.xml
diff --git a/jetty-distribution/src/main/resources/etc/jminix.xml b/jetty-distribution/src/main/resources/etc/jminix.xml
new file mode 100644
index 0000000..1be4757
--- /dev/null
+++ b/jetty-distribution/src/main/resources/etc/jminix.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0"?>
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
+ 
+<Configure id="Server" class="org.eclipse.jetty.server.Server">
+  <Call name="addBean">
+    <Arg>
+      <New class="org.jminix.console.tool.StandaloneMiniConsole">
+        <Arg type="int"><Property name="jminix.port" default="8088" /></Arg>
+      </New>
+    </Arg>
+  </Call>
+</Configure>
diff --git a/jetty-distribution/src/main/resources/etc/jolokia.xml b/jetty-distribution/src/main/resources/etc/jolokia.xml
new file mode 100644
index 0000000..575912f
--- /dev/null
+++ b/jetty-distribution/src/main/resources/etc/jolokia.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0"?>
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
+
+<Configure id="Contexts" class="org.eclipse.jetty.server.handler.ContextHandlerCollection">
+  <Call name="addHandler">
+    <Arg>
+      <New class="org.eclipse.jetty.webapp.WebAppContext">
+	<Set name="contextPath">/jolokia</Set>
+	<Set name="war"><Property name="jetty.base" default="."/>/lib/jolokia/jolokia.war</Set>
+	<Set name="extractWAR">true</Set>
+	<Set name="copyWebDir">false</Set>
+	<Set name="defaultsDescriptor"><Property name="jetty.home" default="."/>/etc/webdefault.xml</Set>
+      </New>
+    </Arg>
+  </Call>
+</Configure>
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-distribution/src/main/resources/modules/.donotdelete
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-distribution/src/main/resources/modules/.donotdelete
diff --git a/jetty-distribution/src/main/resources/modules/hawtio.mod b/jetty-distribution/src/main/resources/modules/hawtio.mod
new file mode 100644
index 0000000..2dfb31b
--- /dev/null
+++ b/jetty-distribution/src/main/resources/modules/hawtio.mod
@@ -0,0 +1,28 @@
+#
+# Hawtio x module
+#
+
+[depend]
+stats
+deploy
+jmx
+
+[xml]
+etc/hawtio.xml
+
+[files]
+etc/hawtio/
+lib/hawtio/
+https://oss.sonatype.org/content/repositories/public/io/hawt/hawtio-default/1.4.16/hawtio-default-1.4.16.war|lib/hawtio/hawtio.war
+
+[license]
+Hawtio is a redhat JBoss project released under the Apache License, v2.0
+http://hawt.io/
+http://github.com/hawtio/hawtio
+http://www.apache.org/licenses/LICENSE-2.0.html
+
+[ini-template]
+
+-Dhawtio.authenticationEnabled=false
+-Dhawtio.dirname=/dirname
+-Dhawtio.config.dir=${jetty.base}/etc/hawtio
diff --git a/jetty-distribution/src/main/resources/modules/jamon.mod b/jetty-distribution/src/main/resources/modules/jamon.mod
new file mode 100644
index 0000000..2aeb2ad
--- /dev/null
+++ b/jetty-distribution/src/main/resources/modules/jamon.mod
@@ -0,0 +1,30 @@
+#
+# JAMon Jetty module
+#
+
+[depend]
+stats
+deploy
+jmx
+jsp
+
+[xml]
+etc/jamon.xml
+
+[files]
+lib/jamon/
+http://central.maven.org/maven2/com/jamonapi/jamon/2.79/jamon-2.79.jar|lib/jamon/jamon-2.79.jar
+http://central.maven.org/maven2/com/jamonapi/jamon_war/2.79/jamon_war-2.79.war|lib/jamon/jamon.war
+
+[lib]
+lib/jamon/**.jar
+
+[license]
+JAMon is a source forge hosted project released under a BSD derived license.
+http://jamonapi.sourceforge.net
+http://jamonapi.sourceforge.net/JAMonLicense.html
+
+[ini-template]
+jamon.summaryLabels=default, request.getStatus().contextpath.value.ms
+#jamon.summaryLabels=demo
+
diff --git a/jetty-distribution/src/main/resources/modules/jminix.mod b/jetty-distribution/src/main/resources/modules/jminix.mod
new file mode 100644
index 0000000..b35d702
--- /dev/null
+++ b/jetty-distribution/src/main/resources/modules/jminix.mod
@@ -0,0 +1,41 @@
+#
+# JaMON Jetty module
+#
+
+[depend]
+stats
+jmx
+
+[xml]
+etc/jminix.xml
+
+[files]
+lib/jminix/
+http://central.maven.org/maven2/org/jminix/jminix/1.1.0/jminix-1.1.0.jar|lib/jminix/jminix-1.1.0.jar
+http://maven.restlet.com/org/restlet/org.restlet/1.1.5/org.restlet-1.1.5.jar|lib/jminix/org.restlet-1.1.5.jar
+http://maven.restlet.com/org/restlet/org.restlet.ext.velocity/1.1.5/org.restlet.ext.velocity-1.1.5.jar|lib/jminix/org.restlet.ext.velocity-1.1.5.jar
+http://central.maven.org/maven2/org/apache/velocity/velocity/1.5/velocity-1.5.jar|lib/jminix/velocity-1.5.jar
+http://central.maven.org/maven2/oro/oro/2.0.8/oro-2.0.8.jar|lib/jminix/oro-2.0.8.jar
+http://maven.restlet.com/com/noelios/restlet/com.noelios.restlet/1.1.5/com.noelios.restlet-1.1.5.jar|lib/jminix/com.noelios.restlet-1.1.5.jar
+http://maven.restlet.com/com/noelios/restlet/com.noelios.restlet.ext.servlet/1.1.5/com.noelios.restlet.ext.servlet-1.1.5.jar|lib/jminix/com.noelios.restlet.ext.servlet-1.1.5.jar
+http://central.maven.org/maven2/commons-logging/commons-logging/1.1.1/commons-logging-1.1.1.jar|lib/jminix/commons-logging-1.1.1.jar
+http://repo2.maven.org/maven2/net/sf/json-lib/json-lib/2.2.3/json-lib-2.2.3-jdk15.jar|lib/jminix/json-lib-2.2.3-jdk15.jar
+http://central.maven.org/maven2/commons-lang/commons-lang/2.4/commons-lang-2.4.jar|lib/jminix/commons-lang-2.4.jar
+http://central.maven.org/maven2/commons-beanutils/commons-beanutils/1.7.0/commons-beanutils-1.7.0.jar|lib/jminix/commons-beanutils-1.7.0.jar
+http://central.maven.org/maven2/commons-collections/commons-collections/3.2/commons-collections-3.2.jar|lib/jminix/commons-collections-3.2.jar
+http://central.maven.org/maven2/net/sf/ezmorph/ezmorph/1.0.6/ezmorph-1.0.6.jar|lib/jminix/ezmorph-1.0.6.jar
+http://central.maven.org/maven2/org/jgroups/jgroups/2.12.1.3.Final/jgroups-2.12.1.3.Final.jar|lib/jminix/jgroups-2.12.1.3.Final.jar
+http://central.maven.org/maven2/org/jasypt/jasypt/1.8/jasypt-1.8.jar|lib/jminix/jasypt-1.8.jar
+
+[lib]
+lib/jminix/**.jar
+
+[license]
+JMiniX is a hosted at google code and released under the Apache License 2.0
+https://code.google.com/p/jminix/
+http://www.apache.org/licenses/LICENSE-2.0
+
+[ini-template]
+# Jminix Configuration
+jminix.port=8088
+
diff --git a/jetty-distribution/src/main/resources/modules/jolokia.mod b/jetty-distribution/src/main/resources/modules/jolokia.mod
new file mode 100644
index 0000000..876c2fc
--- /dev/null
+++ b/jetty-distribution/src/main/resources/modules/jolokia.mod
@@ -0,0 +1,19 @@
+#
+# Jolokia Jetty module
+#
+
+[depend]
+stats
+deploy
+jmx
+
+[xml]
+etc/jolokia.xml
+
+[files]
+http://repo1.maven.org/maven2/org/jolokia/jolokia-war/1.2.2/jolokia-war-1.2.2.war|lib/jolokia/jolokia.war
+
+[license]
+Jolokia is released under the Apache License 2.0
+http://www.jolokia.org
+http://www.apache.org/licenses/LICENSE-2.0
diff --git a/jetty-distribution/src/main/resources/modules/jsp.mod b/jetty-distribution/src/main/resources/modules/jsp.mod
new file mode 100644
index 0000000..bb31ca7
--- /dev/null
+++ b/jetty-distribution/src/main/resources/modules/jsp.mod
@@ -0,0 +1,21 @@
+#
+# Jetty JSP Module
+#
+
+[depend]
+servlet
+annotations
+jsp-impl/${jsp-impl}-jsp
+
+[ini-template]
+# JSP Configuration
+
+# Select JSP implementation, choices are
+#   glassfish : The reference implementation 
+#               default in jetty <= 9.1
+#   apache    : The apache version 
+#               default jetty >= 9.2
+jsp-impl=apache
+
+# To use a non-jdk compiler for JSP compilation when using glassfish uncomment next line
+# -Dorg.apache.jasper.compiler.disablejsr199=true
diff --git a/jetty-distribution/src/main/resources/modules/jstl.mod b/jetty-distribution/src/main/resources/modules/jstl.mod
new file mode 100644
index 0000000..cb06244
--- /dev/null
+++ b/jetty-distribution/src/main/resources/modules/jstl.mod
@@ -0,0 +1,14 @@
+#
+# Jetty JSP Module
+#
+
+[depend]
+jsp
+jsp-impl/${jsp-impl}-jstl
+
+[ini-template]
+# JSTL Configuration
+# The glassfish jsp-impl includes JSTL by default and this module
+# is not required to activate it.
+# The apache jsp-impl does not include JSTL by default and this module
+# is required to put JSTL on the container classpath
diff --git a/jetty-distribution/src/main/resources/modules/protonego.mod b/jetty-distribution/src/main/resources/modules/protonego.mod
new file mode 100644
index 0000000..fbf4d08
--- /dev/null
+++ b/jetty-distribution/src/main/resources/modules/protonego.mod
@@ -0,0 +1,24 @@
+#
+# Protocol Negotiatin Selection Module
+#
+
+[depend]
+protonego-impl/${protonego}
+
+[ini-template]
+# Protocol Negotiation Implementation Selection
+#  choices are:
+#    'npn'  : original implementation for SPDY (now deprecated)
+#    'alpn' : replacement for NPN, in use by current SPDY implementations
+#             and the future HTTP/2 spec
+#  Note: java 1.8+ are ALPN only.
+protonego=alpn
+
+# Configuration for NPN
+# npn.protocols=spdy/3,http/1.1
+# npn.defaultProtocol=http/1.1
+
+# Configuration for ALPN
+# alpn.protocols=h2-14,http/1.1
+# alpn.defaultProtocol=http/1.1
+
diff --git a/jetty-distribution/src/main/resources/modules/setuid.mod b/jetty-distribution/src/main/resources/modules/setuid.mod
new file mode 100644
index 0000000..64c9e23
--- /dev/null
+++ b/jetty-distribution/src/main/resources/modules/setuid.mod
@@ -0,0 +1,19 @@
+#
+# Set UID Feature
+#
+
+[depend]
+server
+
+[lib]
+lib/setuid/jetty-setuid-java-1.0.1.jar
+
+[xml]
+etc/jetty-setuid.xml
+
+[ini-template]
+## SetUID Configuration
+# jetty.startServerAsPrivileged=false
+# jetty.username=jetty
+# jetty.groupname=jetty
+# jetty.umask=002
diff --git a/jetty-distribution/src/main/resources/start.ini b/jetty-distribution/src/main/resources/start.ini
index dfc2783..bd7e62c 100644
--- a/jetty-distribution/src/main/resources/start.ini
+++ b/jetty-distribution/src/main/resources/start.ini
@@ -1,68 +1,22 @@
 #===========================================================
-# Jetty start.jar arguments
-# Each line of this file is prepended to the command line 
-# arguments # of a call to:
-#    java -jar start.jar [arg...]
-#===========================================================
-
-
-
-#===========================================================
-# If the arguements in this file include JVM arguments 
-# (eg -Xmx512m) or JVM System properties (eg com.sun.???),
-# then these will not take affect unless the --exec 
-# parameter is included or if the output from --dry-run
-# is executed like:
-#   eval $(java -jar start.jar --dry-run)
+# Jetty Startup 
+#
+# Starting Jetty from this {jetty.home} is not recommended.
+#
+# A proper {jetty.base} directory should be configured, instead
+# of making changes to this {jetty.home} directory.
+#
+# See documentation about {jetty.base} at
+# http://www.eclipse.org/jetty/documentation/current/startup.html
+#
+# A demo-base directory has been provided as an example of
+# this sort of setup.
+#
+# $ cd demo-base
+# $ java -jar ../start.jar
 #
-# Below are some recommended options for Sun's JRE
-#-----------------------------------------------------------
-# --exec
-# -Dorg.apache.jasper.compiler.disablejsr199=true
-# -Dcom.sun.management.jmxremote
-# -Dorg.eclipse.jetty.util.log.IGNORED=true
-# -Dorg.eclipse.jetty.LEVEL=DEBUG
-# -Dorg.eclipse.jetty.util.log.stderr.SOURCE=true
-# -Xmx2000m
-# -Xmn512m
-# -verbose:gc
-# -XX:+PrintGCDateStamps
-# -XX:+PrintGCTimeStamps
-# -XX:+PrintGCDetails
-# -XX:+PrintTenuringDistribution
-# -XX:+PrintCommandLineFlags
-# -XX:+DisableExplicitGC
-# -XX:+UseConcMarkSweepGC
-# -XX:ParallelCMSThreads=2
-# -XX:+CMSClassUnloadingEnabled  
-# -XX:+UseCMSCompactAtFullCollection
-# -XX:CMSInitiatingOccupancyFraction=80
-#-----------------------------------------------------------
-
-
 #===========================================================
-# Start classpath OPTIONS.
-# These control what classes are on the classpath
-# for a full listing do
-#   java -jar start.jar --list-options
-#-----------------------------------------------------------
-OPTIONS=Server,jsp,jmx,resources,websocket,ext,plus,annotations
-#-----------------------------------------------------------
 
+# To disable the warning message, comment the following line
+--module=home-base-warning
 
-#===========================================================
-# Configuration files.
-# For a full list of available configuration files do
-#   java -jar start.jar --help
-#-----------------------------------------------------------
-#etc/jetty-jmx.xml
-etc/jetty.xml
-etc/jetty-annotations.xml
-# etc/jetty-ssl.xml
-# etc/jetty-requestlog.xml
-etc/jetty-deploy.xml
-#etc/jetty-overlay.xml
-etc/jetty-webapps.xml
-etc/jetty-contexts.xml
-etc/jetty-testrealm.xml
-#===========================================================
diff --git a/jetty-distribution/src/main/resources/webapps/README.TXT b/jetty-distribution/src/main/resources/webapps/README.TXT
new file mode 100644
index 0000000..170137a
--- /dev/null
+++ b/jetty-distribution/src/main/resources/webapps/README.TXT
@@ -0,0 +1,33 @@
+
+This directory is scanned by the WebAppDeployer provider for web
+applications to deploy using the following conventions:
+
++ A directory called example/ will be deployed as a standard web
+application if it contains a WEB-INF/ subdirectory, otherwise it will be
+deployed as context of static content. The context path will be /example
+(eg http://localhost:8080/example/) unless the base name is root, in
+which case the context path is /. If the directory name ends with ".d"
+it is ignored (by may be used by explicit configuration).
+
++ A file called example.war will be deployed as a standard web application
+with the context path /example  (eg http://localhost:8080/example/). If he
+base name is root, then the context path is /. If example.war and example/
+exist, then only the WAR is deployed (which may use the directory as an
+unpack location).
+
++ An XML file like example.xml will be deployed as a context whose
+configuration is defined by the XML. The context path must be set by
+the configuration itself. If example.xml and example.war exist, then
+only the XML is deployed (which may use the war in its configuration).
+
+This directory is scanned for additions, removals and updates 
+for hot deployment.
+
+To configure the deployment mechanism, edit the files:
+   start.d/500-deploy.ini
+   etc/jetty-deploy.ini
+
+To disable the auto deploy mechanism use the command:
+
+   java -jar start.jar --disable=deploy
+
diff --git a/jetty-fcgi/fcgi-client/pom.xml b/jetty-fcgi/fcgi-client/pom.xml
new file mode 100644
index 0000000..fac7cff
--- /dev/null
+++ b/jetty-fcgi/fcgi-client/pom.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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">
+    <parent>
+        <groupId>org.eclipse.jetty.fcgi</groupId>
+        <artifactId>fcgi-parent</artifactId>
+        <version>9.2.13.v20150730</version>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>fcgi-client</artifactId>
+    <name>Jetty :: FastCGI :: Client</name>
+
+    <properties>
+        <bundle-symbolic-name>${project.groupId}.client</bundle-symbolic-name>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-util</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-io</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-http</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-client</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+    </dependencies>
+
+</project>
diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/FCGI.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/FCGI.java
new file mode 100644
index 0000000..9dd977c
--- /dev/null
+++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/FCGI.java
@@ -0,0 +1,136 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.fcgi;
+
+public class FCGI
+{
+    private FCGI()
+    {
+    }
+
+    public enum Role
+    {
+        RESPONDER(1), AUTHORIZER(2), FILTER(3);
+
+        public static Role from(int code)
+        {
+            switch (code)
+            {
+                case 1:
+                    return RESPONDER;
+                case 2:
+                    return AUTHORIZER;
+                case 3:
+                    return FILTER;
+                default:
+                    throw new IllegalArgumentException();
+            }
+        }
+
+        public final int code;
+
+        private Role(int code)
+        {
+            this.code = code;
+        }
+    }
+
+    public enum FrameType
+    {
+        BEGIN_REQUEST(1),
+        ABORT_REQUEST(2),
+        END_REQUEST(3),
+        PARAMS(4),
+        STDIN(5),
+        STDOUT(6),
+        STDERR(7),
+        DATA(8),
+        GET_VALUES(9),
+        GET_VALUES_RESULT(10);
+
+        public static FrameType from(int code)
+        {
+            switch (code)
+            {
+                case 1:
+                    return BEGIN_REQUEST;
+                case 2:
+                    return ABORT_REQUEST;
+                case 3:
+                    return END_REQUEST;
+                case 4:
+                    return PARAMS;
+                case 5:
+                    return STDIN;
+                case 6:
+                    return STDOUT;
+                case 7:
+                    return STDERR;
+                case 8:
+                    return DATA;
+                case 9:
+                    return GET_VALUES;
+                case 10:
+                    return GET_VALUES_RESULT;
+                default:
+                    throw new IllegalArgumentException();
+            }
+        }
+
+        public final int code;
+
+        private FrameType(int code)
+        {
+            this.code = code;
+        }
+    }
+
+    public enum StreamType
+    {
+        STD_IN, STD_OUT, STD_ERR
+    }
+
+    public static class Headers
+    {
+        public static final String AUTH_TYPE = "AUTH_TYPE";
+        public static final String CONTENT_LENGTH = "CONTENT_LENGTH";
+        public static final String CONTENT_TYPE = "CONTENT_TYPE";
+        public static final String DOCUMENT_ROOT = "DOCUMENT_ROOT";
+        public static final String DOCUMENT_URI = "DOCUMENT_URI";
+        public static final String GATEWAY_INTERFACE = "GATEWAY_INTERFACE";
+        public static final String HTTPS = "HTTPS";
+        public static final String PATH_INFO = "PATH_INFO";
+        public static final String QUERY_STRING = "QUERY_STRING";
+        public static final String REMOTE_ADDR = "REMOTE_ADDR";
+        public static final String REMOTE_PORT = "REMOTE_PORT";
+        public static final String REQUEST_METHOD = "REQUEST_METHOD";
+        public static final String REQUEST_URI = "REQUEST_URI";
+        public static final String SCRIPT_FILENAME = "SCRIPT_FILENAME";
+        public static final String SCRIPT_NAME = "SCRIPT_NAME";
+        public static final String SERVER_ADDR = "SERVER_ADDR";
+        public static final String SERVER_NAME = "SERVER_NAME";
+        public static final String SERVER_PORT = "SERVER_PORT";
+        public static final String SERVER_PROTOCOL = "SERVER_PROTOCOL";
+        public static final String SERVER_SOFTWARE = "SERVER_SOFTWARE";
+
+        private Headers()
+        {
+        }
+    }
+}
diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java
new file mode 100644
index 0000000..fb47ec7
--- /dev/null
+++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java
@@ -0,0 +1,176 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.fcgi.client.http;
+
+import java.nio.ByteBuffer;
+import java.util.concurrent.TimeoutException;
+
+import org.eclipse.jetty.client.HttpChannel;
+import org.eclipse.jetty.client.HttpExchange;
+import org.eclipse.jetty.client.HttpReceiver;
+import org.eclipse.jetty.client.HttpSender;
+import org.eclipse.jetty.client.api.Result;
+import org.eclipse.jetty.fcgi.generator.Flusher;
+import org.eclipse.jetty.fcgi.generator.Generator;
+import org.eclipse.jetty.http.HttpField;
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.io.IdleTimeout;
+import org.eclipse.jetty.util.Callback;
+
+public class HttpChannelOverFCGI extends HttpChannel
+{
+    private final HttpConnectionOverFCGI connection;
+    private final Flusher flusher;
+    private final int request;
+    private final HttpSenderOverFCGI sender;
+    private final HttpReceiverOverFCGI receiver;
+    private final FCGIIdleTimeout idle;
+    private HttpVersion version;
+
+    public HttpChannelOverFCGI(final HttpConnectionOverFCGI connection, Flusher flusher, int request, long idleTimeout)
+    {
+        super(connection.getHttpDestination());
+        this.connection = connection;
+        this.flusher = flusher;
+        this.request = request;
+        this.sender = new HttpSenderOverFCGI(this);
+        this.receiver = new HttpReceiverOverFCGI(this);
+        this.idle = new FCGIIdleTimeout(connection, idleTimeout);
+    }
+
+    protected int getRequest()
+    {
+        return request;
+    }
+
+    @Override
+    protected HttpSender getHttpSender()
+    {
+        return sender;
+    }
+
+    @Override
+    protected HttpReceiver getHttpReceiver()
+    {
+        return receiver;
+    }
+
+    @Override
+    public void send()
+    {
+        HttpExchange exchange = getHttpExchange();
+        if (exchange != null)
+        {
+            version = exchange.getRequest().getVersion();
+            sender.send(exchange);
+        }
+    }
+
+    @Override
+    public void release()
+    {
+        connection.release(this);
+    }
+
+    protected boolean responseBegin(int code, String reason)
+    {
+        HttpExchange exchange = getHttpExchange();
+        if (exchange == null)
+            return false;
+        exchange.getResponse().version(version).status(code).reason(reason);
+        return receiver.responseBegin(exchange);
+    }
+
+    protected boolean responseHeader(HttpField field)
+    {
+        HttpExchange exchange = getHttpExchange();
+        return exchange != null && receiver.responseHeader(exchange, field);
+    }
+
+    protected boolean responseHeaders()
+    {
+        HttpExchange exchange = getHttpExchange();
+        return exchange != null && receiver.responseHeaders(exchange);
+    }
+
+    protected boolean content(ByteBuffer buffer, Callback callback)
+    {
+        HttpExchange exchange = getHttpExchange();
+        if (exchange != null)
+            return receiver.responseContent(exchange, buffer, callback);
+        callback.succeeded();
+        return false;
+    }
+
+    protected boolean responseSuccess()
+    {
+        HttpExchange exchange = getHttpExchange();
+        return exchange != null && receiver.responseSuccess(exchange);
+    }
+
+    protected boolean responseFailure(Throwable failure)
+    {
+        HttpExchange exchange = getHttpExchange();
+        return exchange != null && receiver.responseFailure(failure);
+    }
+
+    @Override
+    public void exchangeTerminated(HttpExchange exchange, Result result)
+    {
+        super.exchangeTerminated(exchange, result);
+        idle.onClose();
+        HttpFields responseHeaders = result.getResponse().getHeaders();
+        if (result.isFailed())
+            connection.close(result.getFailure());
+        else if (!connection.closeByHTTP(responseHeaders))
+            release();
+    }
+
+    protected void flush(Generator.Result... results)
+    {
+        flusher.flush(results);
+    }
+
+    private class FCGIIdleTimeout extends IdleTimeout
+    {
+        private final HttpConnectionOverFCGI connection;
+
+        public FCGIIdleTimeout(HttpConnectionOverFCGI connection, long idleTimeout)
+        {
+            super(connection.getHttpDestination().getHttpClient().getScheduler());
+            this.connection = connection;
+            setIdleTimeout(idleTimeout);
+        }
+
+        @Override
+        protected void onIdleExpired(TimeoutException timeout)
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("Idle timeout for request {}", request);
+            connection.abort(timeout);
+        }
+
+        @Override
+        public boolean isOpen()
+        {
+            return connection.getEndPoint().isOpen();
+        }
+    }
+}
diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpClientTransportOverFCGI.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpClientTransportOverFCGI.java
new file mode 100644
index 0000000..1491a24
--- /dev/null
+++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpClientTransportOverFCGI.java
@@ -0,0 +1,85 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.fcgi.client.http;
+
+import java.io.IOException;
+import java.util.Map;
+
+import org.eclipse.jetty.client.AbstractHttpClientTransport;
+import org.eclipse.jetty.client.HttpDestination;
+import org.eclipse.jetty.client.Origin;
+import org.eclipse.jetty.client.api.Connection;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.fcgi.FCGI;
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.util.Promise;
+
+public class HttpClientTransportOverFCGI extends AbstractHttpClientTransport
+{
+    private final boolean multiplexed;
+    private final String scriptRoot;
+
+    public HttpClientTransportOverFCGI(String scriptRoot)
+    {
+        this(Math.max(1, Runtime.getRuntime().availableProcessors() / 2), false, scriptRoot);
+    }
+
+    public HttpClientTransportOverFCGI(int selectors, boolean multiplexed, String scriptRoot)
+    {
+        super(selectors);
+        this.multiplexed = multiplexed;
+        this.scriptRoot = scriptRoot;
+    }
+
+    public boolean isMultiplexed()
+    {
+        return multiplexed;
+    }
+
+    public String getScriptRoot()
+    {
+        return scriptRoot;
+    }
+
+    @Override
+    public HttpDestination newHttpDestination(Origin origin)
+    {
+        return isMultiplexed() ? new MultiplexHttpDestinationOverFCGI(getHttpClient(), origin)
+                : new HttpDestinationOverFCGI(getHttpClient(), origin);
+    }
+
+    @Override
+    public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map<String, Object> context) throws IOException
+    {
+        HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY);
+        HttpConnectionOverFCGI connection = new HttpConnectionOverFCGI(endPoint, destination, isMultiplexed());
+        if (LOG.isDebugEnabled())
+            LOG.debug("Created {}", connection);
+        @SuppressWarnings("unchecked")
+        Promise<Connection> promise = (Promise<Connection>)context.get(HTTP_CONNECTION_PROMISE_CONTEXT_KEY);
+        promise.succeeded(connection);
+        return connection;
+    }
+
+    protected void customize(Request request, HttpFields fastCGIHeaders)
+    {
+        fastCGIHeaders.put(FCGI.Headers.DOCUMENT_ROOT, getScriptRoot());
+    }
+}
diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpConnectionOverFCGI.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpConnectionOverFCGI.java
new file mode 100644
index 0000000..0560980
--- /dev/null
+++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpConnectionOverFCGI.java
@@ -0,0 +1,425 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.fcgi.client.http;
+
+import java.io.EOFException;
+import java.nio.ByteBuffer;
+import java.nio.channels.AsynchronousCloseException;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.HttpConnection;
+import org.eclipse.jetty.client.HttpDestination;
+import org.eclipse.jetty.client.HttpExchange;
+import org.eclipse.jetty.client.api.Connection;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.fcgi.FCGI;
+import org.eclipse.jetty.fcgi.generator.Flusher;
+import org.eclipse.jetty.fcgi.parser.ClientParser;
+import org.eclipse.jetty.http.HttpField;
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpHeaderValue;
+import org.eclipse.jetty.io.AbstractConnection;
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.CompletableCallback;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public class HttpConnectionOverFCGI extends AbstractConnection implements Connection
+{
+    private static final Logger LOG = Log.getLogger(HttpConnectionOverFCGI.class);
+
+    private final LinkedList<Integer> requests = new LinkedList<>();
+    private final Map<Integer, HttpChannelOverFCGI> channels = new ConcurrentHashMap<>();
+    private final AtomicBoolean closed = new AtomicBoolean();
+    private final Flusher flusher;
+    private final HttpDestination destination;
+    private final boolean multiplexed;
+    private final Delegate delegate;
+    private final ClientParser parser;
+    private ByteBuffer buffer;
+
+    public HttpConnectionOverFCGI(EndPoint endPoint, HttpDestination destination, boolean multiplexed)
+    {
+        super(endPoint, destination.getHttpClient().getExecutor(), destination.getHttpClient().isDispatchIO());
+        this.destination = destination;
+        this.multiplexed = multiplexed;
+        this.flusher = new Flusher(endPoint);
+        this.delegate = new Delegate(destination);
+        this.parser = new ClientParser(new ResponseListener());
+        requests.addLast(0);
+    }
+
+    public HttpDestination getHttpDestination()
+    {
+        return destination;
+    }
+
+    @Override
+    public void send(Request request, Response.CompleteListener listener)
+    {
+        delegate.send(request, listener);
+    }
+
+    protected void send(HttpExchange exchange)
+    {
+        delegate.send(exchange);
+    }
+
+    @Override
+    public void onOpen()
+    {
+        super.onOpen();
+        fillInterested();
+    }
+
+    @Override
+    public void onFillable()
+    {
+        HttpClient client = destination.getHttpClient();
+        ByteBufferPool bufferPool = client.getByteBufferPool();
+        buffer = bufferPool.acquire(client.getResponseBufferSize(), true);
+        process();
+    }
+
+    private void process()
+    {
+        if (readAndParse())
+        {
+            HttpClient client = destination.getHttpClient();
+            ByteBufferPool bufferPool = client.getByteBufferPool();
+            bufferPool.release(buffer);
+            // Don't linger the buffer around if we are idle.
+            buffer = null;
+        }
+    }
+
+    private boolean readAndParse()
+    {
+        EndPoint endPoint = getEndPoint();
+        ByteBuffer buffer = this.buffer;
+        while (true)
+        {
+            try
+            {
+                if (parse(buffer))
+                    return false;
+
+                int read = endPoint.fill(buffer);
+                if (LOG.isDebugEnabled()) // Avoid boxing of variable 'read'.
+                    LOG.debug("Read {} bytes from {}", read, endPoint);
+                if (read > 0)
+                {
+                    if (parse(buffer))
+                        return false;
+                }
+                else if (read == 0)
+                {
+                    fillInterested();
+                    return true;
+                }
+                else
+                {
+                    shutdown();
+                    return true;
+                }
+            }
+            catch (Exception x)
+            {
+                if (LOG.isDebugEnabled())
+                    LOG.debug(x);
+                close(x);
+                return false;
+            }
+        }
+    }
+
+    private boolean parse(ByteBuffer buffer)
+    {
+        return parser.parse(buffer);
+    }
+
+    private void shutdown()
+    {
+        // Close explicitly only if we are idle, since the request may still
+        // be in progress, otherwise close only if we can fail the responses.
+        if (channels.isEmpty())
+            close();
+        else
+            failAndClose(new EOFException());
+    }
+
+    @Override
+    protected boolean onReadTimeout()
+    {
+        close(new TimeoutException());
+        return false;
+    }
+
+    protected void release(HttpChannelOverFCGI channel)
+    {
+        channels.remove(channel.getRequest());
+        destination.release(this);
+    }
+
+    @Override
+    public void close()
+    {
+        close(new AsynchronousCloseException());
+    }
+
+    protected void close(Throwable failure)
+    {
+        if (closed.compareAndSet(false, true))
+        {
+            // First close then abort, to be sure that the connection cannot be reused
+            // from an onFailure() handler or by blocking code waiting for completion.
+            getHttpDestination().close(this);
+            getEndPoint().shutdownOutput();
+            if (LOG.isDebugEnabled())
+                LOG.debug("{} oshut", this);
+            getEndPoint().close();
+            if (LOG.isDebugEnabled())
+                LOG.debug("{} closed", this);
+
+            abort(failure);
+        }
+    }
+
+    protected boolean closeByHTTP(HttpFields fields)
+    {
+        if (multiplexed)
+            return false;
+        if (!fields.contains(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString()))
+            return false;
+        close();
+        return true;
+    }
+
+    protected void abort(Throwable failure)
+    {
+        for (HttpChannelOverFCGI channel : channels.values())
+        {
+            HttpExchange exchange = channel.getHttpExchange();
+            if (exchange != null)
+                exchange.getRequest().abort(failure);
+        }
+        channels.clear();
+    }
+
+    private void failAndClose(Throwable failure)
+    {
+        boolean result = false;
+        for (HttpChannelOverFCGI channel : channels.values())
+            result |= channel.responseFailure(failure);
+        if (result)
+            close(failure);
+    }
+
+    private int acquireRequest()
+    {
+        synchronized (requests)
+        {
+            int last = requests.getLast();
+            int request = last + 1;
+            requests.addLast(request);
+            return request;
+        }
+    }
+
+    private void releaseRequest(int request)
+    {
+        synchronized (requests)
+        {
+            requests.removeFirstOccurrence(request);
+        }
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s@%h(l:%s <-> r:%s)",
+                getClass().getSimpleName(),
+                this,
+                getEndPoint().getLocalAddress(),
+                getEndPoint().getRemoteAddress());
+    }
+
+    private class Delegate extends HttpConnection
+    {
+        private Delegate(HttpDestination destination)
+        {
+            super(destination);
+        }
+
+        @Override
+        protected void send(HttpExchange exchange)
+        {
+            Request request = exchange.getRequest();
+            normalizeRequest(request);
+
+            // FCGI may be multiplexed, so create one channel for each request.
+            int id = acquireRequest();
+            HttpChannelOverFCGI channel = new HttpChannelOverFCGI(HttpConnectionOverFCGI.this, flusher, id, request.getIdleTimeout());
+            channels.put(id, channel);
+            if (channel.associate(exchange))
+                channel.send();
+            else
+                channel.release();
+        }
+
+        @Override
+        public void close()
+        {
+            HttpConnectionOverFCGI.this.close();
+        }
+
+        @Override
+        public String toString()
+        {
+            return HttpConnectionOverFCGI.this.toString();
+        }
+    }
+
+    private class ResponseListener implements ClientParser.Listener
+    {
+        @Override
+        public void onBegin(int request, int code, String reason)
+        {
+            HttpChannelOverFCGI channel = channels.get(request);
+            if (channel != null)
+                channel.responseBegin(code, reason);
+            else
+                noChannel(request);
+        }
+
+        @Override
+        public void onHeader(int request, HttpField field)
+        {
+            HttpChannelOverFCGI channel = channels.get(request);
+            if (channel != null)
+                channel.responseHeader(field);
+            else
+                noChannel(request);
+        }
+
+        @Override
+        public void onHeaders(int request)
+        {
+            HttpChannelOverFCGI channel = channels.get(request);
+            if (channel != null)
+                channel.responseHeaders();
+            else
+                noChannel(request);
+        }
+
+        @Override
+        public boolean onContent(int request, FCGI.StreamType stream, ByteBuffer buffer)
+        {
+            switch (stream)
+            {
+                case STD_OUT:
+                {
+                    HttpChannelOverFCGI channel = channels.get(request);
+                    if (channel != null)
+                    {
+                        CompletableCallback callback = new CompletableCallback()
+                        {
+                            @Override
+                            public void resume()
+                            {
+                                if (LOG.isDebugEnabled())
+                                    LOG.debug("Content consumed asynchronously, resuming processing");
+                                process();
+                            }
+
+                            @Override
+                            public void abort(Throwable x)
+                            {
+                                close(x);
+                            }
+                        };
+                        if (!channel.content(buffer, callback))
+                            return true;
+                        return callback.tryComplete();
+                    }
+                    else
+                    {
+                        noChannel(request);
+                    }
+                    break;
+                }
+                case STD_ERR:
+                {
+                    LOG.info(BufferUtil.toUTF8String(buffer));
+                    break;
+                }
+                default:
+                {
+                    throw new IllegalArgumentException();
+                }
+            }
+            return false;
+        }
+
+        @Override
+        public void onEnd(int request)
+        {
+            HttpChannelOverFCGI channel = channels.get(request);
+            if (channel != null)
+            {
+                if (channel.responseSuccess())
+                    releaseRequest(request);
+            }
+            else
+            {
+                noChannel(request);
+            }
+        }
+
+        @Override
+        public void onFailure(int request, Throwable failure)
+        {
+            HttpChannelOverFCGI channel = channels.get(request);
+            if (channel != null)
+            {
+                if (channel.responseFailure(failure))
+                    releaseRequest(request);
+            }
+            else
+            {
+                noChannel(request);
+            }
+        }
+
+        private void noChannel(int request)
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("Channel not found for request {}", request);
+        }
+    }
+}
diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpDestinationOverFCGI.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpDestinationOverFCGI.java
new file mode 100644
index 0000000..f6adf48
--- /dev/null
+++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpDestinationOverFCGI.java
@@ -0,0 +1,38 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.fcgi.client.http;
+
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.HttpExchange;
+import org.eclipse.jetty.client.Origin;
+import org.eclipse.jetty.client.PoolingHttpDestination;
+
+public class HttpDestinationOverFCGI extends PoolingHttpDestination<HttpConnectionOverFCGI>
+{
+    public HttpDestinationOverFCGI(HttpClient client, Origin origin)
+    {
+        super(client, origin);
+    }
+
+    @Override
+    protected void send(HttpConnectionOverFCGI connection, HttpExchange exchange)
+    {
+        connection.send(exchange);
+    }
+}
diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpReceiverOverFCGI.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpReceiverOverFCGI.java
new file mode 100644
index 0000000..86a018c
--- /dev/null
+++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpReceiverOverFCGI.java
@@ -0,0 +1,71 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.fcgi.client.http;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.client.HttpChannel;
+import org.eclipse.jetty.client.HttpExchange;
+import org.eclipse.jetty.client.HttpReceiver;
+import org.eclipse.jetty.http.HttpField;
+import org.eclipse.jetty.util.Callback;
+
+public class HttpReceiverOverFCGI extends HttpReceiver
+{
+    public HttpReceiverOverFCGI(HttpChannel channel)
+    {
+        super(channel);
+    }
+
+    @Override
+    protected boolean responseBegin(HttpExchange exchange)
+    {
+        return super.responseBegin(exchange);
+    }
+
+    @Override
+    protected boolean responseHeader(HttpExchange exchange, HttpField field)
+    {
+        return super.responseHeader(exchange, field);
+    }
+
+    @Override
+    protected boolean responseHeaders(HttpExchange exchange)
+    {
+        return super.responseHeaders(exchange);
+    }
+
+    @Override
+    protected boolean responseContent(HttpExchange exchange, ByteBuffer buffer, Callback callback)
+    {
+        return super.responseContent(exchange, buffer, callback);
+    }
+
+    @Override
+    protected boolean responseSuccess(HttpExchange exchange)
+    {
+        return super.responseSuccess(exchange);
+    }
+
+    @Override
+    protected boolean responseFailure(Throwable failure)
+    {
+        return super.responseFailure(failure);
+    }
+}
diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpSenderOverFCGI.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpSenderOverFCGI.java
new file mode 100644
index 0000000..a257185
--- /dev/null
+++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpSenderOverFCGI.java
@@ -0,0 +1,128 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.fcgi.client.http;
+
+import java.net.URI;
+import java.util.Locale;
+
+import org.eclipse.jetty.client.HttpChannel;
+import org.eclipse.jetty.client.HttpContent;
+import org.eclipse.jetty.client.HttpExchange;
+import org.eclipse.jetty.client.HttpSender;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.fcgi.FCGI;
+import org.eclipse.jetty.fcgi.generator.ClientGenerator;
+import org.eclipse.jetty.fcgi.generator.Generator;
+import org.eclipse.jetty.http.HttpField;
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.Jetty;
+
+public class HttpSenderOverFCGI extends HttpSender
+{
+    private final ClientGenerator generator;
+
+    public HttpSenderOverFCGI(HttpChannel channel)
+    {
+        super(channel);
+        this.generator = new ClientGenerator(channel.getHttpDestination().getHttpClient().getByteBufferPool());
+    }
+
+    @Override
+    protected HttpChannelOverFCGI getHttpChannel()
+    {
+        return (HttpChannelOverFCGI)super.getHttpChannel();
+    }
+
+    @Override
+    protected void sendHeaders(HttpExchange exchange, HttpContent content, Callback callback)
+    {
+        Request request = exchange.getRequest();
+        // Copy the request headers to be able to convert them properly
+        HttpFields headers = new HttpFields();
+        for (HttpField field : request.getHeaders())
+            headers.put(field);
+        HttpFields fcgiHeaders = new HttpFields();
+
+        // FastCGI headers based on the URI
+        URI uri = request.getURI();
+        String path = uri.getRawPath();
+        fcgiHeaders.put(FCGI.Headers.DOCUMENT_URI, path);
+        String query = uri.getRawQuery();
+        fcgiHeaders.put(FCGI.Headers.QUERY_STRING, query == null ? "" : query);
+
+        // FastCGI headers based on HTTP headers
+        HttpField httpField = headers.remove(HttpHeader.AUTHORIZATION);
+        if (httpField != null)
+            fcgiHeaders.put(FCGI.Headers.AUTH_TYPE, httpField.getValue());
+        httpField = headers.remove(HttpHeader.CONTENT_LENGTH);
+        fcgiHeaders.put(FCGI.Headers.CONTENT_LENGTH, httpField == null ? "" : httpField.getValue());
+        httpField = headers.remove(HttpHeader.CONTENT_TYPE);
+        fcgiHeaders.put(FCGI.Headers.CONTENT_TYPE, httpField == null ? "" : httpField.getValue());
+
+        // FastCGI headers that are not based on HTTP headers nor URI
+        fcgiHeaders.put(FCGI.Headers.REQUEST_METHOD, request.getMethod());
+        fcgiHeaders.put(FCGI.Headers.SERVER_PROTOCOL, request.getVersion().asString());
+        fcgiHeaders.put(FCGI.Headers.GATEWAY_INTERFACE, "CGI/1.1");
+        fcgiHeaders.put(FCGI.Headers.SERVER_SOFTWARE, "Jetty/" + Jetty.VERSION);
+
+        // Translate remaining HTTP header into the HTTP_* format
+        for (HttpField field : headers)
+        {
+            String name = field.getName();
+            String fcgiName = "HTTP_" + name.replaceAll("-", "_").toUpperCase(Locale.ENGLISH);
+            fcgiHeaders.add(fcgiName, field.getValue());
+        }
+
+        // Give a chance to the transport implementation to customize the FastCGI headers
+        HttpClientTransportOverFCGI transport = (HttpClientTransportOverFCGI)getHttpChannel().getHttpDestination().getHttpClient().getTransport();
+        transport.customize(request, fcgiHeaders);
+
+        int id = getHttpChannel().getRequest();
+        boolean hasContent = content.hasContent();
+        Generator.Result headersResult = generator.generateRequestHeaders(id, fcgiHeaders,
+                hasContent ? callback : Callback.Adapter.INSTANCE);
+        if (hasContent)
+        {
+            getHttpChannel().flush(headersResult);
+        }
+        else
+        {
+            Generator.Result noContentResult = generator.generateRequestContent(id, BufferUtil.EMPTY_BUFFER, true, callback);
+            getHttpChannel().flush(headersResult, noContentResult);
+        }
+    }
+
+    @Override
+    protected void sendContent(HttpExchange exchange, HttpContent content, Callback callback)
+    {
+        if (content.isConsumed())
+        {
+            callback.succeeded();
+        }
+        else
+        {
+            int request = getHttpChannel().getRequest();
+            Generator.Result result = generator.generateRequestContent(request, content.getByteBuffer(), content.isLast(), callback);
+            getHttpChannel().flush(result);
+        }
+    }
+}
diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/MultiplexHttpDestinationOverFCGI.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/MultiplexHttpDestinationOverFCGI.java
new file mode 100644
index 0000000..77f2259
--- /dev/null
+++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/MultiplexHttpDestinationOverFCGI.java
@@ -0,0 +1,38 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.fcgi.client.http;
+
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.HttpExchange;
+import org.eclipse.jetty.client.MultiplexHttpDestination;
+import org.eclipse.jetty.client.Origin;
+
+public class MultiplexHttpDestinationOverFCGI extends MultiplexHttpDestination<HttpConnectionOverFCGI>
+{
+    public MultiplexHttpDestinationOverFCGI(HttpClient client, Origin origin)
+    {
+        super(client, origin);
+    }
+
+    @Override
+    protected void send(HttpConnectionOverFCGI connection, HttpExchange exchange)
+    {
+        connection.send(exchange);
+    }
+}
diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/generator/ClientGenerator.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/generator/ClientGenerator.java
new file mode 100644
index 0000000..aa3a4ff
--- /dev/null
+++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/generator/ClientGenerator.java
@@ -0,0 +1,166 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.fcgi.generator;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jetty.fcgi.FCGI;
+import org.eclipse.jetty.http.HttpField;
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+
+public class ClientGenerator extends Generator
+{
+    // To keep the algorithm simple, and given that the max length of a
+    // frame is 0xFF_FF we allow the max length of a name (or value) to be
+    // 0x7F_FF - 4 (the 4 is to make room for the name (or value) length).
+    public static final int MAX_PARAM_LENGTH = 0x7F_FF - 4;
+
+    public ClientGenerator(ByteBufferPool byteBufferPool)
+    {
+        super(byteBufferPool);
+    }
+
+    public Result generateRequestHeaders(int request, HttpFields fields, Callback callback)
+    {
+        request &= 0xFF_FF;
+
+        Charset utf8 = Charset.forName("UTF-8");
+        List<byte[]> bytes = new ArrayList<>(fields.size() * 2);
+        int fieldsLength = 0;
+        for (HttpField field : fields)
+        {
+            String name = field.getName();
+            byte[] nameBytes = name.getBytes(utf8);
+            if (nameBytes.length > MAX_PARAM_LENGTH)
+                throw new IllegalArgumentException("Field name " + name + " exceeds max length " + MAX_PARAM_LENGTH);
+            bytes.add(nameBytes);
+
+            String value = field.getValue();
+            byte[] valueBytes = value.getBytes(utf8);
+            if (valueBytes.length > MAX_PARAM_LENGTH)
+                throw new IllegalArgumentException("Field value " + value + " exceeds max length " + MAX_PARAM_LENGTH);
+            bytes.add(valueBytes);
+
+            int nameLength = nameBytes.length;
+            fieldsLength += bytesForLength(nameLength);
+
+            int valueLength = valueBytes.length;
+            fieldsLength += bytesForLength(valueLength);
+
+            fieldsLength += nameLength;
+            fieldsLength += valueLength;
+        }
+
+        // Worst case FCGI_PARAMS frame: long name + long value - both of MAX_PARAM_LENGTH
+        int maxCapacity = 4 + 4 + 2 * MAX_PARAM_LENGTH;
+
+        // One FCGI_BEGIN_REQUEST + N FCGI_PARAMS + one last FCGI_PARAMS
+
+        ByteBuffer beginRequestBuffer = byteBufferPool.acquire(16, false);
+        BufferUtil.clearToFill(beginRequestBuffer);
+        Result result = new Result(byteBufferPool, callback);
+        result = result.append(beginRequestBuffer, true);
+
+        // Generate the FCGI_BEGIN_REQUEST frame
+        beginRequestBuffer.putInt(0x01_01_00_00 + request);
+        beginRequestBuffer.putInt(0x00_08_00_00);
+        // Hardcode RESPONDER role and KEEP_ALIVE flag
+        beginRequestBuffer.putLong(0x00_01_01_00_00_00_00_00L);
+        beginRequestBuffer.flip();
+
+        int index = 0;
+        while (fieldsLength > 0)
+        {
+            int capacity = 8 + Math.min(maxCapacity, fieldsLength);
+            ByteBuffer buffer = byteBufferPool.acquire(capacity, true);
+            BufferUtil.clearToFill(buffer);
+            result = result.append(buffer, true);
+
+            // Generate the FCGI_PARAMS frame
+            buffer.putInt(0x01_04_00_00 + request);
+            buffer.putShort((short)0);
+            buffer.putShort((short)0);
+            capacity -= 8;
+
+            int length = 0;
+            while (index < bytes.size())
+            {
+                byte[] nameBytes = bytes.get(index);
+                int nameLength = nameBytes.length;
+                byte[] valueBytes = bytes.get(index + 1);
+                int valueLength = valueBytes.length;
+
+                int required = bytesForLength(nameLength) + bytesForLength(valueLength) + nameLength + valueLength;
+                if (required > capacity)
+                    break;
+
+                putParamLength(buffer, nameLength);
+                putParamLength(buffer, valueLength);
+                buffer.put(nameBytes);
+                buffer.put(valueBytes);
+
+                length += required;
+                fieldsLength -= required;
+                capacity -= required;
+                index += 2;
+            }
+
+            buffer.putShort(4, (short)length);
+            buffer.flip();
+        }
+
+
+        ByteBuffer lastParamsBuffer = byteBufferPool.acquire(8, false);
+        BufferUtil.clearToFill(lastParamsBuffer);
+        result = result.append(lastParamsBuffer, true);
+
+        // Generate the last FCGI_PARAMS frame
+        lastParamsBuffer.putInt(0x01_04_00_00 + request);
+        lastParamsBuffer.putInt(0x00_00_00_00);
+        lastParamsBuffer.flip();
+
+        return result;
+    }
+
+    private int putParamLength(ByteBuffer buffer, int length)
+    {
+        int result = bytesForLength(length);
+        if (result == 4)
+            buffer.putInt(length | 0x80_00_00_00);
+        else
+            buffer.put((byte)length);
+        return result;
+    }
+
+    private int bytesForLength(int length)
+    {
+        return length > 127 ? 4 : 1;
+    }
+
+    public Result generateRequestContent(int request, ByteBuffer content, boolean lastContent, Callback callback)
+    {
+        return generateContent(request, content, false, lastContent, callback, FCGI.FrameType.STDIN);
+    }
+}
diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/generator/Flusher.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/generator/Flusher.java
new file mode 100644
index 0000000..ceb16c1
--- /dev/null
+++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/generator/Flusher.java
@@ -0,0 +1,143 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.fcgi.generator;
+
+import java.nio.ByteBuffer;
+import java.util.Queue;
+
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.util.ConcurrentArrayQueue;
+import org.eclipse.jetty.util.IteratingCallback;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public class Flusher
+{
+    private static final Logger LOG = Log.getLogger(Flusher.class);
+
+    private final Queue<Generator.Result> queue = new ConcurrentArrayQueue<>();
+    private final IteratingCallback flushCallback = new FlushCallback();
+    private final EndPoint endPoint;
+
+    public Flusher(EndPoint endPoint)
+    {
+        this.endPoint = endPoint;
+    }
+
+    public void flush(Generator.Result... results)
+    {
+        for (Generator.Result result : results)
+            queue.offer(result);
+        flushCallback.iterate();
+    }
+
+    public void shutdown()
+    {
+        flush(new ShutdownResult());
+    }
+
+    private class FlushCallback extends IteratingCallback
+    {
+        private Generator.Result active;
+
+        @Override
+        protected Action process() throws Exception
+        {
+            // Look if other writes are needed.
+            Generator.Result result = queue.poll();
+            if (result == null)
+            {
+                // No more writes to do, return.
+                return Action.IDLE;
+            }
+
+            // Attempt to gather another result.
+            // Most often there is another result in the
+            // queue so this is a real optimization because
+            // it sends both results in just one TCP packet.
+            Generator.Result other = queue.poll();
+            if (other != null)
+                result = result.join(other);
+
+            active = result;
+            ByteBuffer[] buffers = result.getByteBuffers();
+            endPoint.write(this, buffers);
+            return Action.SCHEDULED;
+        }
+
+        @Override
+        protected void onCompleteSuccess()
+        {
+            // We never return Action.SUCCEEDED, so this method is never called.
+            throw new IllegalStateException();
+        }
+
+        @Override
+        public void succeeded()
+        {
+            if (active != null)
+                active.succeeded();
+            active = null;
+            super.succeeded();
+        }
+
+        @Override
+        public void onCompleteFailure(Throwable x)
+        {
+            if (active != null)
+                active.failed(x);
+            active = null;
+
+            while (true)
+            {
+                Generator.Result result = queue.poll();
+                if (result == null)
+                    break;
+                result.failed(x);
+            }
+        }
+    }
+
+    private class ShutdownResult extends Generator.Result
+    {
+        private ShutdownResult()
+        {
+            super(null, null);
+        }
+
+        @Override
+        public void succeeded()
+        {
+            shutdown();
+        }
+
+        @Override
+        public void failed(Throwable x)
+        {
+            shutdown();
+        }
+
+        private void shutdown()
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("Shutting down {}", endPoint);
+            endPoint.shutdownOutput();
+        }
+    }
+}
diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/generator/Generator.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/generator/Generator.java
new file mode 100644
index 0000000..5a47411
--- /dev/null
+++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/generator/Generator.java
@@ -0,0 +1,155 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.fcgi.generator;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jetty.fcgi.FCGI;
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+
+public class Generator
+{
+    public static final int MAX_CONTENT_LENGTH = 0xFF_FF;
+
+    protected final ByteBufferPool byteBufferPool;
+
+    public Generator(ByteBufferPool byteBufferPool)
+    {
+        this.byteBufferPool = byteBufferPool;
+    }
+
+    protected Result generateContent(int id, ByteBuffer content, boolean recycle, boolean lastContent, Callback callback, FCGI.FrameType frameType)
+    {
+        id &= 0xFF_FF;
+
+        int contentLength = content == null ? 0 : content.remaining();
+        Result result = new Result(byteBufferPool, callback);
+
+        while (contentLength > 0 || lastContent)
+        {
+            ByteBuffer buffer = byteBufferPool.acquire(8, false);
+            BufferUtil.clearToFill(buffer);
+            result = result.append(buffer, true);
+
+            // Generate the frame header
+            buffer.put((byte)0x01);
+            buffer.put((byte)frameType.code);
+            buffer.putShort((short)id);
+            int length = Math.min(MAX_CONTENT_LENGTH, contentLength);
+            buffer.putShort((short)length);
+            buffer.putShort((short)0);
+            buffer.flip();
+
+            if (contentLength == 0)
+                break;
+
+            // Slice the content to avoid copying
+            int limit = content.limit();
+            content.limit(content.position() + length);
+            ByteBuffer slice = content.slice();
+            // Don't recycle the slice
+            result = result.append(slice, false);
+            content.position(content.limit());
+            content.limit(limit);
+            contentLength -= length;
+            // Recycle the content buffer if needed
+            if (recycle && contentLength == 0)
+                result = result.append(content, true);
+        }
+
+        return result;
+    }
+
+    public static class Result implements Callback
+    {
+        private final List<Callback> callbacks = new ArrayList<>(2);
+        private final List<ByteBuffer> buffers = new ArrayList<>(8);
+        private final List<Boolean> recycles = new ArrayList<>(8);
+        private final ByteBufferPool byteBufferPool;
+
+        public Result(ByteBufferPool byteBufferPool, Callback callback)
+        {
+            this.byteBufferPool = byteBufferPool;
+            this.callbacks.add(callback);
+        }
+
+        public Result append(ByteBuffer buffer, boolean recycle)
+        {
+            if (buffer != null)
+            {
+                buffers.add(buffer);
+                recycles.add(recycle);
+            }
+            return this;
+        }
+
+        public Result join(Result that)
+        {
+            callbacks.addAll(that.callbacks);
+            buffers.addAll(that.buffers);
+            recycles.addAll(that.recycles);
+            return this;
+        }
+
+        public ByteBuffer[] getByteBuffers()
+        {
+            return buffers.toArray(new ByteBuffer[buffers.size()]);
+        }
+
+        @Override
+        @SuppressWarnings("ForLoopReplaceableByForEach")
+        public void succeeded()
+        {
+            recycle();
+            for (int i = 0; i < callbacks.size(); ++i)
+            {
+                Callback callback = callbacks.get(i);
+                if (callback != null)
+                    callback.succeeded();
+            }
+        }
+
+        @Override
+        @SuppressWarnings("ForLoopReplaceableByForEach")
+        public void failed(Throwable x)
+        {
+            recycle();
+            for (int i = 0; i < callbacks.size(); ++i)
+            {
+                Callback callback = callbacks.get(i);
+                if (callback != null)
+                    callback.failed(x);
+            }
+        }
+
+        protected void recycle()
+        {
+            for (int i = 0; i < buffers.size(); ++i)
+            {
+                ByteBuffer buffer = buffers.get(i);
+                if (recycles.get(i))
+                    byteBufferPool.release(buffer);
+            }
+        }
+    }
+}
diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/generator/ServerGenerator.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/generator/ServerGenerator.java
new file mode 100644
index 0000000..091e3eb
--- /dev/null
+++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/generator/ServerGenerator.java
@@ -0,0 +1,134 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.fcgi.generator;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jetty.fcgi.FCGI;
+import org.eclipse.jetty.http.HttpField;
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+
+public class ServerGenerator extends Generator
+{
+    private static final byte[] STATUS = new byte[]{'S', 't', 'a', 't', 'u', 's'};
+    private static final byte[] COLON = new byte[]{':', ' '};
+    private static final byte[] EOL = new byte[]{'\r', '\n'};
+
+    private final boolean sendStatus200;
+
+    public ServerGenerator(ByteBufferPool byteBufferPool)
+    {
+        this(byteBufferPool, true);
+    }
+
+    public ServerGenerator(ByteBufferPool byteBufferPool, boolean sendStatus200)
+    {
+        super(byteBufferPool);
+        this.sendStatus200 = sendStatus200;
+    }
+
+    public Result generateResponseHeaders(int request, int code, String reason, HttpFields fields, Callback callback)
+    {
+        request &= 0xFF_FF;
+
+        Charset utf8 = Charset.forName("UTF-8");
+        List<byte[]> bytes = new ArrayList<>(fields.size() * 2);
+        int length = 0;
+
+        if (code != 200 || sendStatus200)
+        {
+            // Special 'Status' header
+            bytes.add(STATUS);
+            length += STATUS.length + COLON.length;
+            if (reason == null)
+                reason = HttpStatus.getMessage(code);
+            byte[] responseBytes = (code + " " + reason).getBytes(utf8);
+            bytes.add(responseBytes);
+            length += responseBytes.length + EOL.length;
+        }
+
+        // Other headers
+        for (HttpField field : fields)
+        {
+            String name = field.getName();
+            byte[] nameBytes = name.getBytes(utf8);
+            bytes.add(nameBytes);
+
+            String value = field.getValue();
+            byte[] valueBytes = value.getBytes(utf8);
+            bytes.add(valueBytes);
+
+            length += nameBytes.length + COLON.length;
+            length += valueBytes.length + EOL.length;
+        }
+        // End of headers
+        length += EOL.length;
+
+        final ByteBuffer buffer = byteBufferPool.acquire(length, true);
+        BufferUtil.clearToFill(buffer);
+
+        for (int i = 0; i < bytes.size(); i += 2)
+            buffer.put(bytes.get(i)).put(COLON).put(bytes.get(i + 1)).put(EOL);
+        buffer.put(EOL);
+
+        buffer.flip();
+
+        return generateContent(request, buffer, true, false, callback, FCGI.FrameType.STDOUT);
+    }
+
+    public Result generateResponseContent(int request, ByteBuffer content, boolean lastContent, boolean aborted, Callback callback)
+    {
+        if (aborted)
+        {
+            Result result = new Result(byteBufferPool, callback);
+            if (lastContent)
+                result.append(generateEndRequest(request, true), true);
+            else
+                result.append(BufferUtil.EMPTY_BUFFER, false);
+            return result;
+        }
+        else
+        {
+            Result result = generateContent(request, content, false, lastContent, callback, FCGI.FrameType.STDOUT);
+            if (lastContent)
+                result.append(generateEndRequest(request, false), true);
+            return result;
+        }
+    }
+
+    private ByteBuffer generateEndRequest(int request, boolean aborted)
+    {
+        request &= 0xFF_FF;
+        ByteBuffer endRequestBuffer = byteBufferPool.acquire(8, false);
+        BufferUtil.clearToFill(endRequestBuffer);
+        endRequestBuffer.putInt(0x01_03_00_00 + request);
+        endRequestBuffer.putInt(0x00_08_00_00);
+        endRequestBuffer.putInt(aborted ? 1 : 0);
+        endRequestBuffer.putInt(0);
+        endRequestBuffer.flip();
+        return endRequestBuffer;
+    }
+}
diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/BeginRequestContentParser.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/BeginRequestContentParser.java
new file mode 100644
index 0000000..2367fb6
--- /dev/null
+++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/BeginRequestContentParser.java
@@ -0,0 +1,127 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.fcgi.parser;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.fcgi.FCGI;
+
+public class BeginRequestContentParser extends ContentParser
+{
+    private final ServerParser.Listener listener;
+    private State state = State.ROLE;
+    private int cursor;
+    private int role;
+    private int flags;
+
+    public BeginRequestContentParser(HeaderParser headerParser, ServerParser.Listener listener)
+    {
+        super(headerParser);
+        this.listener = listener;
+    }
+
+    @Override
+    public Result parse(ByteBuffer buffer)
+    {
+        while (buffer.hasRemaining())
+        {
+            switch (state)
+            {
+                case ROLE:
+                {
+                    if (buffer.remaining() >= 2)
+                    {
+                        role = buffer.getShort();
+                        state = State.FLAGS;
+                    }
+                    else
+                    {
+                        state = State.ROLE_BYTES;
+                        cursor = 0;
+                    }
+                    break;
+                }
+                case ROLE_BYTES:
+                {
+                    int halfShort = buffer.get() & 0xFF;
+                    role = (role << 8) + halfShort;
+                    if (++cursor == 2)
+                        state = State.FLAGS;
+                    break;
+                }
+                case FLAGS:
+                {
+                    flags = buffer.get() & 0xFF;
+                    state = State.RESERVED;
+                    break;
+                }
+                case RESERVED:
+                {
+                    if (buffer.remaining() >= 5)
+                    {
+                        buffer.position(buffer.position() + 5);
+                        onStart();
+                        reset();
+                        return Result.COMPLETE;
+                    }
+                    else
+                    {
+                        state = State.RESERVED_BYTES;
+                        cursor = 0;
+                        break;
+                    }
+                }
+                case RESERVED_BYTES:
+                {
+                    buffer.get();
+                    if (++cursor == 5)
+                    {
+                        onStart();
+                        reset();
+                        return Result.COMPLETE;
+                    }
+                    break;
+                }
+                default:
+                {
+                    throw new IllegalStateException();
+                }
+            }
+        }
+        return Result.PENDING;
+    }
+
+    private void onStart()
+    {
+        listener.onStart(getRequest(), FCGI.Role.from(role), flags);
+    }
+
+    private void reset()
+    {
+        state = State.ROLE;
+        cursor = 0;
+        role = 0;
+        flags = 0;
+    }
+
+    private enum State
+    {
+        ROLE, ROLE_BYTES, FLAGS, RESERVED, RESERVED_BYTES
+    }
+}
diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ClientParser.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ClientParser.java
new file mode 100644
index 0000000..f75a9af
--- /dev/null
+++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ClientParser.java
@@ -0,0 +1,110 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.fcgi.parser;
+
+import java.nio.ByteBuffer;
+import java.util.EnumMap;
+
+import org.eclipse.jetty.fcgi.FCGI;
+import org.eclipse.jetty.http.HttpField;
+
+public class ClientParser extends Parser
+{
+    private final EnumMap<FCGI.FrameType, ContentParser> contentParsers = new EnumMap<>(FCGI.FrameType.class);
+
+    public ClientParser(Listener listener)
+    {
+        ResponseContentParser stdOutParser = new ResponseContentParser(headerParser, listener);
+        contentParsers.put(FCGI.FrameType.STDOUT, stdOutParser);
+        StreamContentParser stdErrParser = new StreamContentParser(headerParser, FCGI.StreamType.STD_ERR, listener);
+        contentParsers.put(FCGI.FrameType.STDERR, stdErrParser);
+        contentParsers.put(FCGI.FrameType.END_REQUEST, new EndRequestContentParser(headerParser, new EndRequestListener(listener, stdOutParser, stdErrParser)));
+    }
+
+    @Override
+    protected ContentParser findContentParser(FCGI.FrameType frameType)
+    {
+        return contentParsers.get(frameType);
+    }
+
+    public interface Listener extends Parser.Listener
+    {
+        public void onBegin(int request, int code, String reason);
+
+        public static class Adapter extends Parser.Listener.Adapter implements Listener
+        {
+            @Override
+            public void onBegin(int request, int code, String reason)
+            {
+            }
+        }
+    }
+
+    private class EndRequestListener implements Listener
+    {
+        private final Listener listener;
+        private final StreamContentParser[] streamParsers;
+
+        private EndRequestListener(Listener listener, StreamContentParser... streamParsers)
+        {
+            this.listener = listener;
+            this.streamParsers = streamParsers;
+        }
+
+        @Override
+        public void onBegin(int request, int code, String reason)
+        {
+            listener.onBegin(request, code, reason);
+        }
+
+        @Override
+        public void onHeader(int request, HttpField field)
+        {
+            listener.onHeader(request, field);
+        }
+
+        @Override
+        public void onHeaders(int request)
+        {
+            listener.onHeaders(request);
+        }
+
+        @Override
+        public boolean onContent(int request, FCGI.StreamType stream, ByteBuffer buffer)
+        {
+            return listener.onContent(request, stream, buffer);
+        }
+
+        @Override
+        public void onEnd(int request)
+        {
+            listener.onEnd(request);
+            for (StreamContentParser streamParser : streamParsers)
+                streamParser.end(request);
+        }
+
+        @Override
+        public void onFailure(int request, Throwable failure)
+        {
+            listener.onFailure(request, failure);
+            for (StreamContentParser streamParser : streamParsers)
+                streamParser.end(request);
+        }
+    }
+}
diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ContentParser.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ContentParser.java
new file mode 100644
index 0000000..7478fb1
--- /dev/null
+++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ContentParser.java
@@ -0,0 +1,53 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.fcgi.parser;
+
+import java.nio.ByteBuffer;
+
+public abstract class ContentParser
+{
+    private final HeaderParser headerParser;
+
+    protected ContentParser(HeaderParser headerParser)
+    {
+        this.headerParser = headerParser;
+    }
+
+    public abstract Result parse(ByteBuffer buffer);
+
+    public void noContent()
+    {
+        throw new IllegalStateException();
+    }
+
+    protected int getRequest()
+    {
+        return headerParser.getRequest();
+    }
+
+    protected int getContentLength()
+    {
+        return headerParser.getContentLength();
+    }
+
+    public enum Result
+    {
+        PENDING, ASYNC, COMPLETE
+    }
+}
diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/EndRequestContentParser.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/EndRequestContentParser.java
new file mode 100644
index 0000000..b8173bf
--- /dev/null
+++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/EndRequestContentParser.java
@@ -0,0 +1,130 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.fcgi.parser;
+
+import java.nio.ByteBuffer;
+
+public class EndRequestContentParser extends ContentParser
+{
+    private final Parser.Listener listener;
+    private State state = State.APPLICATION;
+    private int cursor;
+    private int application;
+    private int protocol;
+
+    public EndRequestContentParser(HeaderParser headerParser, Parser.Listener listener)
+    {
+        super(headerParser);
+        this.listener = listener;
+    }
+
+    @Override
+    public Result parse(ByteBuffer buffer)
+    {
+        while (buffer.hasRemaining())
+        {
+            switch (state)
+            {
+                case APPLICATION:
+                {
+                    if (buffer.remaining() >= 4)
+                    {
+                        application = buffer.getInt();
+                        state = State.PROTOCOL;
+                    }
+                    else
+                    {
+                        state = State.APPLICATION_BYTES;
+                        cursor = 0;
+                    }
+                    break;
+                }
+                case APPLICATION_BYTES:
+                {
+                    int quarterInt = buffer.get() & 0xFF;
+                    application = (application << 8) + quarterInt;
+                    if (++cursor == 4)
+                        state = State.PROTOCOL;
+                    break;
+                }
+                case PROTOCOL:
+                {
+                    protocol = buffer.get() & 0xFF;
+                    state = State.RESERVED;
+                    break;
+                }
+                case RESERVED:
+                {
+                    if (buffer.remaining() >= 3)
+                    {
+                        buffer.position(buffer.position() + 3);
+                        onEnd();
+                        reset();
+                        return Result.COMPLETE;
+                    }
+                    else
+                    {
+                        state = State.APPLICATION_BYTES;
+                        cursor = 0;
+                        break;
+                    }
+                }
+                case RESERVED_BYTES:
+                {
+                    buffer.get();
+                    if (++cursor == 0)
+                    {
+                        onEnd();
+                        reset();
+                        return Result.COMPLETE;
+                    }
+                    break;
+                }
+                default:
+                {
+                    throw new IllegalStateException();
+                }
+            }
+        }
+        return Result.PENDING;
+    }
+
+    private void onEnd()
+    {
+        if (application != 0)
+            listener.onFailure(getRequest(), new Exception("FastCGI application returned code " + application));
+        else if (protocol != 0)
+            listener.onFailure(getRequest(), new Exception("FastCGI server returned code " + protocol));
+        else
+            listener.onEnd(getRequest());
+    }
+
+    private void reset()
+    {
+        state = State.APPLICATION;
+        cursor = 0;
+        application = 0;
+        protocol = 0;
+    }
+
+    private enum State
+    {
+        APPLICATION, APPLICATION_BYTES, PROTOCOL, RESERVED, RESERVED_BYTES
+    }
+}
diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/HeaderParser.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/HeaderParser.java
new file mode 100644
index 0000000..078105a
--- /dev/null
+++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/HeaderParser.java
@@ -0,0 +1,158 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.fcgi.parser;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.fcgi.FCGI;
+
+public class HeaderParser
+{
+    private State state = State.VERSION;
+    private int cursor;
+    private int version;
+    private int type;
+    private int request;
+    private int length;
+    private int padding;
+
+    /**
+     * Parses the bytes in the given {@code buffer} as FastCGI header bytes
+     *
+     * @param buffer the bytes to parse
+     * @return whether there were enough bytes for a FastCGI header
+     */
+    public boolean parse(ByteBuffer buffer)
+    {
+        while (buffer.hasRemaining())
+        {
+            switch (state)
+            {
+                case VERSION:
+                {
+                    version = buffer.get() & 0xFF;
+                    state = State.TYPE;
+                    break;
+                }
+                case TYPE:
+                {
+                    type = buffer.get() & 0xFF;
+                    state = State.REQUEST;
+                    break;
+                }
+                case REQUEST:
+                {
+                    if (buffer.remaining() >= 2)
+                    {
+                        request = buffer.getShort() & 0xFF_FF;
+                        state = State.LENGTH;
+                    }
+                    else
+                    {
+                        state = State.REQUEST_BYTES;
+                        cursor = 0;
+                    }
+                    break;
+                }
+                case REQUEST_BYTES:
+                {
+                    int halfShort = buffer.get() & 0xFF;
+                    request = (request << 8) + halfShort;
+                    if (++cursor == 2)
+                        state = State.LENGTH;
+                    break;
+                }
+                case LENGTH:
+                {
+                    if (buffer.remaining() >= 2)
+                    {
+                        length = buffer.getShort() & 0xFF_FF;
+                        state = State.PADDING;
+                    }
+                    else
+                    {
+                        state = State.LENGTH_BYTES;
+                        cursor = 0;
+                    }
+                    break;
+                }
+                case LENGTH_BYTES:
+                {
+                    int halfShort = buffer.get() & 0xFF;
+                    length = (length << 8) + halfShort;
+                    if (++cursor == 2)
+                        state = State.PADDING;
+                    break;
+                }
+                case PADDING:
+                {
+                    padding = buffer.get() & 0xFF;
+                    state = State.RESERVED;
+                    break;
+                }
+                case RESERVED:
+                {
+                    buffer.get();
+                    return true;
+                }
+                default:
+                {
+                    throw new IllegalStateException();
+                }
+            }
+        }
+        return false;
+    }
+
+    public FCGI.FrameType getFrameType()
+    {
+        return FCGI.FrameType.from(type);
+    }
+
+    public int getRequest()
+    {
+        return request;
+    }
+
+    public int getContentLength()
+    {
+        return length;
+    }
+
+    public int getPaddingLength()
+    {
+        return padding;
+    }
+
+    protected void reset()
+    {
+        state = State.VERSION;
+        cursor = 0;
+        version = 0;
+        type = 0;
+        request = 0;
+        length = 0;
+        padding = 0;
+    }
+
+    private enum State
+    {
+        VERSION, TYPE, REQUEST, REQUEST_BYTES, LENGTH, LENGTH_BYTES, PADDING, RESERVED
+    }
+}
diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ParamsContentParser.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ParamsContentParser.java
new file mode 100644
index 0000000..4678ad5
--- /dev/null
+++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ParamsContentParser.java
@@ -0,0 +1,259 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.fcgi.parser;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+
+import org.eclipse.jetty.http.HttpField;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public class ParamsContentParser extends ContentParser
+{
+    private static final Logger LOG = Log.getLogger(ParamsContentParser.class);
+
+    private final ServerParser.Listener listener;
+    private State state = State.LENGTH;
+    private int cursor;
+    private int length;
+    private int nameLength;
+    private int valueLength;
+    private byte[] nameBytes;
+    private byte[] valueBytes;
+
+    public ParamsContentParser(HeaderParser headerParser, ServerParser.Listener listener)
+    {
+        super(headerParser);
+        this.listener = listener;
+    }
+
+    @Override
+    public Result parse(ByteBuffer buffer)
+    {
+        while (buffer.hasRemaining() || state == State.PARAM)
+        {
+            switch (state)
+            {
+                case LENGTH:
+                {
+                    length = getContentLength();
+                    state = State.NAME_LENGTH;
+                    break;
+                }
+                case NAME_LENGTH:
+                {
+                    if (isLargeLength(buffer))
+                    {
+                        if (buffer.remaining() >= 4)
+                        {
+                            nameLength = buffer.getInt() & 0x7F_FF;
+                            state = State.VALUE_LENGTH;
+                            length -= 4;
+                        }
+                        else
+                        {
+                            state = State.NAME_LENGTH_BYTES;
+                            cursor = 0;
+                        }
+                    }
+                    else
+                    {
+                        nameLength = buffer.get() & 0xFF;
+                        state = State.VALUE_LENGTH;
+                        --length;
+                    }
+                    break;
+                }
+                case NAME_LENGTH_BYTES:
+                {
+                    int quarterInt = buffer.get() & 0xFF;
+                    nameLength = (nameLength << 8) + quarterInt;
+                    --length;
+                    if (++cursor == 4)
+                    {
+                        nameLength &= 0x7F_FF;
+                        state = State.VALUE_LENGTH;
+                    }
+                    break;
+                }
+                case VALUE_LENGTH:
+                {
+                    if (isLargeLength(buffer))
+                    {
+                        if (buffer.remaining() >= 4)
+                        {
+                            valueLength = buffer.getInt() & 0x7F_FF;
+                            state = State.NAME;
+                            length -= 4;
+                        }
+                        else
+                        {
+                            state = State.VALUE_LENGTH_BYTES;
+                            cursor = 0;
+                        }
+                    }
+                    else
+                    {
+                        valueLength = buffer.get() & 0xFF;
+                        state = State.NAME;
+                        --length;
+                    }
+                    break;
+                }
+                case VALUE_LENGTH_BYTES:
+                {
+                    int quarterInt = buffer.get() & 0xFF;
+                    valueLength = (valueLength << 8) + quarterInt;
+                    --length;
+                    if (++cursor == 4)
+                    {
+                        valueLength &= 0x7F_FF;
+                        state = State.NAME;
+                    }
+                    break;
+                }
+                case NAME:
+                {
+                    nameBytes = new byte[nameLength];
+                    if (buffer.remaining() >= nameLength)
+                    {
+                        buffer.get(nameBytes);
+                        state = State.VALUE;
+                        length -= nameLength;
+                    }
+                    else
+                    {
+                        state = State.NAME_BYTES;
+                        cursor = 0;
+                    }
+                    break;
+                }
+                case NAME_BYTES:
+                {
+                    nameBytes[cursor] = buffer.get();
+                    --length;
+                    if (++cursor == nameLength)
+                        state = State.VALUE;
+                    break;
+                }
+                case VALUE:
+                {
+                    valueBytes = new byte[valueLength];
+                    if (buffer.remaining() >= valueLength)
+                    {
+                        buffer.get(valueBytes);
+                        state = State.PARAM;
+                        length -= valueLength;
+                    }
+                    else
+                    {
+                        state = State.VALUE_BYTES;
+                        cursor = 0;
+                    }
+                    break;
+                }
+                case VALUE_BYTES:
+                {
+                    valueBytes[cursor] = buffer.get();
+                    --length;
+                    if (++cursor == valueLength)
+                        state = State.PARAM;
+                    break;
+                }
+                case PARAM:
+                {
+                    Charset utf8 = Charset.forName("UTF-8");
+                    onParam(new String(nameBytes, utf8), new String(valueBytes, utf8));
+                    partialReset();
+                    if (length == 0)
+                    {
+                        reset();
+                        return Result.COMPLETE;
+                    }
+                    break;
+                }
+                default:
+                {
+                    throw new IllegalStateException();
+                }
+            }
+        }
+        return Result.PENDING;
+    }
+
+    @Override
+    public void noContent()
+    {
+        onParams();
+    }
+
+    protected void onParam(String name, String value)
+    {
+        try
+        {
+            listener.onHeader(getRequest(), new HttpField(name, value));
+        }
+        catch (Throwable x)
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("Exception while invoking listener " + listener, x);
+        }
+    }
+
+    protected void onParams()
+    {
+        try
+        {
+            listener.onHeaders(getRequest());
+        }
+        catch (Throwable x)
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("Exception while invoking listener " + listener, x);
+        }
+    }
+
+    private boolean isLargeLength(ByteBuffer buffer)
+    {
+        return (buffer.get(buffer.position()) & 0x80) == 0x80;
+    }
+
+    private void partialReset()
+    {
+        state = State.NAME_LENGTH;
+        cursor = 0;
+        nameLength = 0;
+        valueLength = 0;
+        nameBytes = null;
+        valueBytes = null;
+    }
+
+    private void reset()
+    {
+        partialReset();
+        state = State.LENGTH;
+        length = 0;
+    }
+
+    private enum State
+    {
+        LENGTH, NAME_LENGTH, NAME_LENGTH_BYTES, VALUE_LENGTH, VALUE_LENGTH_BYTES, NAME, NAME_BYTES, VALUE, VALUE_BYTES, PARAM
+    }
+}
diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/Parser.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/Parser.java
new file mode 100644
index 0000000..cb86d66
--- /dev/null
+++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/Parser.java
@@ -0,0 +1,161 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.fcgi.parser;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.fcgi.FCGI;
+import org.eclipse.jetty.http.HttpField;
+
+public abstract class Parser
+{
+    protected final HeaderParser headerParser = new HeaderParser();
+    private State state = State.HEADER;
+    private int padding;
+
+    /**
+     * @param buffer the bytes to parse
+     * @return true if the caller should stop parsing, false if the caller should continue parsing
+     */
+    public boolean parse(ByteBuffer buffer)
+    {
+        while (true)
+        {
+            switch (state)
+            {
+                case HEADER:
+                {
+                    if (!headerParser.parse(buffer))
+                        return false;
+                    state = State.CONTENT;
+                    break;
+                }
+                case CONTENT:
+                {
+                    ContentParser contentParser = findContentParser(headerParser.getFrameType());
+                    if (headerParser.getContentLength() == 0)
+                    {
+                        contentParser.noContent();
+                    }
+                    else
+                    {
+                        ContentParser.Result result = contentParser.parse(buffer);
+                        if (result == ContentParser.Result.PENDING)
+                        {
+                            // Not enough data, signal to read/parse more.
+                            return false;
+                        }
+                        if (result == ContentParser.Result.ASYNC)
+                        {
+                            // The content will be processed asynchronously, signal to stop
+                            // parsing; the async operation will eventually resume parsing.
+                            return true;
+                        }
+                    }
+                    padding = headerParser.getPaddingLength();
+                    state = State.PADDING;
+                    break;
+                }
+                case PADDING:
+                {
+                    if (buffer.remaining() >= padding)
+                    {
+                        buffer.position(buffer.position() + padding);
+                        reset();
+                        break;
+                    }
+                    else
+                    {
+                        padding -= buffer.remaining();
+                        buffer.position(buffer.limit());
+                        return false;
+                    }
+                }
+                default:
+                {
+                    throw new IllegalStateException();
+                }
+            }
+        }
+    }
+
+    protected abstract ContentParser findContentParser(FCGI.FrameType frameType);
+
+    private void reset()
+    {
+        headerParser.reset();
+        state = State.HEADER;
+        padding = 0;
+    }
+
+    public interface Listener
+    {
+        public void onHeader(int request, HttpField field);
+
+        public void onHeaders(int request);
+
+        /**
+         * @param request the request id
+         * @param stream the stream type
+         * @param buffer the content bytes
+         * @return true to signal to the parser to stop parsing, false to continue parsing
+         * @see Parser#parse(java.nio.ByteBuffer)
+         */
+        public boolean onContent(int request, FCGI.StreamType stream, ByteBuffer buffer);
+
+        public void onEnd(int request);
+
+        public void onFailure(int request, Throwable failure);
+
+        public static class Adapter implements Listener
+        {
+            @Override
+            public void onHeader(int request, HttpField field)
+            {
+            }
+
+            @Override
+            public void onHeaders(int request)
+            {
+            }
+
+            @Override
+            public boolean onContent(int request, FCGI.StreamType stream, ByteBuffer buffer)
+            {
+                return false;
+            }
+
+            @Override
+            public void onEnd(int request)
+            {
+            }
+
+            @Override
+            public void onFailure(int request, Throwable failure)
+            {
+
+            }
+        }
+    }
+
+    private enum State
+    {
+        HEADER, CONTENT, PADDING
+    }
+}
diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ResponseContentParser.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ResponseContentParser.java
new file mode 100644
index 0000000..18900fb
--- /dev/null
+++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ResponseContentParser.java
@@ -0,0 +1,327 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.fcgi.parser;
+
+import java.nio.ByteBuffer;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.eclipse.jetty.fcgi.FCGI;
+import org.eclipse.jetty.http.HttpField;
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpParser;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public class ResponseContentParser extends StreamContentParser
+{
+    private static final Logger LOG = Log.getLogger(ResponseContentParser.class);
+
+    private final Map<Integer, ResponseParser> parsers = new ConcurrentHashMap<>();
+    private final ClientParser.Listener listener;
+
+    public ResponseContentParser(HeaderParser headerParser, ClientParser.Listener listener)
+    {
+        super(headerParser, FCGI.StreamType.STD_OUT, listener);
+        this.listener = listener;
+    }
+
+    @Override
+    public void noContent()
+    {
+        // Does nothing, since for responses the end of content is signaled via a FCGI_END_REQUEST frame
+    }
+
+    @Override
+    protected boolean onContent(ByteBuffer buffer)
+    {
+        int request = getRequest();
+        ResponseParser parser = parsers.get(request);
+        if (parser == null)
+        {
+            parser = new ResponseParser(listener, request);
+            parsers.put(request, parser);
+        }
+        return parser.parse(buffer);
+    }
+
+    @Override
+    protected void end(int request)
+    {
+        super.end(request);
+        parsers.remove(request);
+    }
+
+    private class ResponseParser implements HttpParser.ResponseHandler<ByteBuffer>
+    {
+        private final HttpFields fields = new HttpFields();
+        private ClientParser.Listener listener;
+        private final int request;
+        private final FCGIHttpParser httpParser;
+        private State state = State.HEADERS;
+        private boolean seenResponseCode;
+
+        private ResponseParser(ClientParser.Listener listener, int request)
+        {
+            this.listener = listener;
+            this.request = request;
+            this.httpParser = new FCGIHttpParser(this);
+        }
+
+        public boolean parse(ByteBuffer buffer)
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("Response {} {} content {} {}", request, FCGI.StreamType.STD_OUT, state, buffer);
+
+            int remaining = buffer.remaining();
+            while (remaining > 0)
+            {
+                switch (state)
+                {
+                    case HEADERS:
+                    {
+                        if (httpParser.parseNext(buffer))
+                            state = State.CONTENT_MODE;
+                        remaining = buffer.remaining();
+                        break;
+                    }
+                    case CONTENT_MODE:
+                    {
+                        // If we have no indication of the content, then
+                        // the HTTP parser will assume there is no content
+                        // and will not parse it even if it is provided,
+                        // so we have to parse it raw ourselves here.
+                        boolean rawContent = fields.size() == 0 ||
+                                (fields.get(HttpHeader.CONTENT_LENGTH) == null &&
+                                        fields.get(HttpHeader.TRANSFER_ENCODING) == null);
+                        state = rawContent ? State.RAW_CONTENT : State.HTTP_CONTENT;
+                        break;
+                    }
+                    case RAW_CONTENT:
+                    {
+                        if (notifyContent(buffer))
+                            return true;
+                        remaining = 0;
+                        break;
+                    }
+                    case HTTP_CONTENT:
+                    {
+                        if (httpParser.parseNext(buffer))
+                            return true;
+                        remaining = buffer.remaining();
+                        break;
+                    }
+                    default:
+                    {
+                        throw new IllegalStateException();
+                    }
+                }
+            }
+            return false;
+        }
+
+        @Override
+        public int getHeaderCacheSize()
+        {
+            // TODO: configure this
+            return 0;
+        }
+
+        @Override
+        public boolean startResponse(HttpVersion version, int status, String reason)
+        {
+            // The HTTP request line does not exist in FCGI responses
+            throw new IllegalStateException();
+        }
+
+        @Override
+        public boolean parsedHeader(HttpField httpField)
+        {
+            try
+            {
+                String name = httpField.getName();
+                if ("Status".equalsIgnoreCase(name))
+                {
+                    if (!seenResponseCode)
+                    {
+                        seenResponseCode = true;
+
+                        // Need to set the response status so the
+                        // HttpParser can handle the content properly.
+                        String value = httpField.getValue();
+                        String[] parts = value.split(" ");
+                        String status = parts[0];
+                        int code = Integer.parseInt(status);
+                        httpParser.setResponseStatus(code);
+                        String reason = parts.length > 1 ? value.substring(status.length()) : HttpStatus.getMessage(code);
+
+                        notifyBegin(code, reason.trim());
+                        notifyHeaders(fields);
+                    }
+                }
+                else
+                {
+                    fields.add(httpField);
+                    if (seenResponseCode)
+                        notifyHeader(httpField);
+                }
+            }
+            catch (Throwable x)
+            {
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Exception while invoking listener " + listener, x);
+            }
+            return false;
+        }
+
+        private void notifyBegin(int code, String reason)
+        {
+            try
+            {
+                listener.onBegin(request, code, reason);
+            }
+            catch (Throwable x)
+            {
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Exception while invoking listener " + listener, x);
+            }
+        }
+
+        private void notifyHeader(HttpField httpField)
+        {
+            try
+            {
+                listener.onHeader(request, httpField);
+            }
+            catch (Throwable x)
+            {
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Exception while invoking listener " + listener, x);
+            }
+        }
+
+        private void notifyHeaders(HttpFields fields)
+        {
+            if (fields != null)
+            {
+                for (HttpField field : fields)
+                    notifyHeader(field);
+            }
+        }
+
+        private void notifyHeaders()
+        {
+            try
+            {
+                listener.onHeaders(request);
+            }
+            catch (Throwable x)
+            {
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Exception while invoking listener " + listener, x);
+            }
+        }
+
+        @Override
+        public boolean headerComplete()
+        {
+            if (!seenResponseCode)
+            {
+                // No Status header but we have other headers, assume 200 OK
+                notifyBegin(200, "OK");
+                notifyHeaders(fields);
+            }
+            notifyHeaders();
+            // Return from parsing so that we can parse the content
+            return true;
+        }
+
+        @Override
+        public boolean content(ByteBuffer buffer)
+        {
+            return notifyContent(buffer);
+        }
+
+        private boolean notifyContent(ByteBuffer buffer)
+        {
+            try
+            {
+                return listener.onContent(request, FCGI.StreamType.STD_OUT, buffer);
+            }
+            catch (Throwable x)
+            {
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Exception while invoking listener " + listener, x);
+                return false;
+            }
+        }
+
+        @Override
+        public boolean messageComplete()
+        {
+            // Return from parsing so that we can parse the next headers or the raw content.
+            // No need to notify the listener because it will be done by FCGI_END_REQUEST.
+            return true;
+        }
+
+        @Override
+        public void earlyEOF()
+        {
+            // TODO
+        }
+
+        @Override
+        public void badMessage(int status, String reason)
+        {
+            // TODO
+        }
+    }
+
+    // Methods overridden to make them visible here
+    private static class FCGIHttpParser extends HttpParser
+    {
+        private FCGIHttpParser(ResponseHandler<ByteBuffer> handler)
+        {
+            super(handler, 65 * 1024, true);
+            reset();
+        }
+
+        @Override
+        public void reset()
+        {
+            super.reset();
+            setResponseStatus(200);
+            setState(State.HEADER);
+        }
+
+        @Override
+        protected void setResponseStatus(int status)
+        {
+            super.setResponseStatus(status);
+        }
+    }
+
+    private enum State
+    {
+        HEADERS, CONTENT_MODE, RAW_CONTENT, HTTP_CONTENT
+    }
+}
diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ServerParser.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ServerParser.java
new file mode 100644
index 0000000..f999cf6
--- /dev/null
+++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ServerParser.java
@@ -0,0 +1,54 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.fcgi.parser;
+
+import java.util.EnumMap;
+
+import org.eclipse.jetty.fcgi.FCGI;
+
+public class ServerParser extends Parser
+{
+    private final EnumMap<FCGI.FrameType, ContentParser> contentParsers = new EnumMap<>(FCGI.FrameType.class);
+
+    public ServerParser(Listener listener)
+    {
+        contentParsers.put(FCGI.FrameType.BEGIN_REQUEST, new BeginRequestContentParser(headerParser, listener));
+        contentParsers.put(FCGI.FrameType.PARAMS, new ParamsContentParser(headerParser, listener));
+        contentParsers.put(FCGI.FrameType.STDIN, new StreamContentParser(headerParser, FCGI.StreamType.STD_IN, listener));
+    }
+
+    @Override
+    protected ContentParser findContentParser(FCGI.FrameType frameType)
+    {
+        return contentParsers.get(frameType);
+    }
+
+    public interface Listener extends Parser.Listener
+    {
+        public void onStart(int request, FCGI.Role role, int flags);
+
+        public static class Adapter extends Parser.Listener.Adapter implements Listener
+        {
+            @Override
+            public void onStart(int request, FCGI.Role role, int flags)
+            {
+            }
+        }
+    }
+}
diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/StreamContentParser.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/StreamContentParser.java
new file mode 100644
index 0000000..a341013
--- /dev/null
+++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/StreamContentParser.java
@@ -0,0 +1,117 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.fcgi.parser;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.fcgi.FCGI;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public class StreamContentParser extends ContentParser
+{
+    private static final Logger LOG = Log.getLogger(StreamContentParser.class);
+
+    private final FCGI.StreamType streamType;
+    private final Parser.Listener listener;
+    private State state = State.LENGTH;
+    private int contentLength;
+
+    public StreamContentParser(HeaderParser headerParser, FCGI.StreamType streamType, Parser.Listener listener)
+    {
+        super(headerParser);
+        this.streamType = streamType;
+        this.listener = listener;
+    }
+
+    @Override
+    public Result parse(ByteBuffer buffer)
+    {
+        while (buffer.hasRemaining())
+        {
+            switch (state)
+            {
+                case LENGTH:
+                {
+                    contentLength = getContentLength();
+                    state = State.CONTENT;
+                    break;
+                }
+                case CONTENT:
+                {
+                    int length = Math.min(contentLength, buffer.remaining());
+                    int limit = buffer.limit();
+                    buffer.limit(buffer.position() + length);
+                    ByteBuffer slice = buffer.slice();
+                    buffer.position(buffer.limit());
+                    buffer.limit(limit);
+                    contentLength -= length;
+                    if (onContent(slice))
+                        return Result.ASYNC;
+                    if (contentLength > 0)
+                        break;
+                    state = State.LENGTH;
+                    return Result.COMPLETE;
+                }
+                default:
+                {
+                    throw new IllegalStateException();
+                }
+            }
+        }
+        return Result.PENDING;
+    }
+
+    @Override
+    public void noContent()
+    {
+        try
+        {
+            listener.onEnd(getRequest());
+        }
+        catch (Throwable x)
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("Exception while invoking listener " + listener, x);
+        }
+    }
+
+    protected boolean onContent(ByteBuffer buffer)
+    {
+        try
+        {
+            return listener.onContent(getRequest(), streamType, buffer);
+        }
+        catch (Throwable x)
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("Exception while invoking listener " + listener, x);
+            return false;
+        }
+    }
+
+    protected void end(int request)
+    {
+    }
+
+    private enum State
+    {
+        LENGTH, CONTENT
+    }
+}
diff --git a/jetty-fcgi/fcgi-client/src/test/java/org/eclipse/jetty/fcgi/generator/ClientGeneratorTest.java b/jetty-fcgi/fcgi-client/src/test/java/org/eclipse/jetty/fcgi/generator/ClientGeneratorTest.java
new file mode 100644
index 0000000..3e21bc2
--- /dev/null
+++ b/jetty-fcgi/fcgi-client/src/test/java/org/eclipse/jetty/fcgi/generator/ClientGeneratorTest.java
@@ -0,0 +1,191 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.fcgi.generator;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.eclipse.jetty.fcgi.FCGI;
+import org.eclipse.jetty.fcgi.parser.ServerParser;
+import org.eclipse.jetty.http.HttpField;
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.MappedByteBufferPool;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class ClientGeneratorTest
+{
+    @Test
+    public void testGenerateRequestHeaders() throws Exception
+    {
+        HttpFields fields = new HttpFields();
+
+        // Short name, short value
+        final String shortShortName = "REQUEST_METHOD";
+        final String shortShortValue = "GET";
+        fields.put(new HttpField(shortShortName, shortShortValue));
+
+        // Short name, long value
+        final String shortLongName = "REQUEST_URI";
+        // Be sure it's longer than 127 chars to test the large value
+        final String shortLongValue = "/api/0.6/map?bbox=-64.217736,-31.456810,-64.187736,-31.432322,filler=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
+        fields.put(new HttpField(shortLongName, shortLongValue));
+
+        // Long name, short value
+        // Be sure it's longer than 127 chars to test the large name
+        final String longShortName = "FEDCBA9876543210FEDCBA9876543210FEDCBA9876543210FEDCBA9876543210FEDCBA9876543210FEDCBA9876543210FEDCBA9876543210FEDCBA9876543210";
+        final String longShortValue = "api.openstreetmap.org";
+        fields.put(new HttpField(longShortName, longShortValue));
+
+        // Long name, long value
+        char[] chars = new char[ClientGenerator.MAX_PARAM_LENGTH];
+        Arrays.fill(chars, 'z');
+        final String longLongName = new String(chars);
+        final String longLongValue = new String(chars);
+        fields.put(new HttpField(longLongName, longLongValue));
+
+        ByteBufferPool byteBufferPool = new MappedByteBufferPool();
+        ClientGenerator generator = new ClientGenerator(byteBufferPool);
+        final int id = 13;
+        Generator.Result result = generator.generateRequestHeaders(id, fields, null);
+
+        // Use the fundamental theorem of arithmetic to test the results.
+        // This way we know onHeader() has been called the right number of
+        // times with the right arguments, and so onHeaders().
+        final int[] primes = new int[]{2, 3, 5, 7, 11};
+        int value = 1;
+        for (int prime : primes)
+            value *= prime;
+
+        final AtomicInteger params = new AtomicInteger(1);
+        ServerParser parser = new ServerParser(new ServerParser.Listener.Adapter()
+        {
+            @Override
+            public void onHeader(int request, HttpField field)
+            {
+                Assert.assertEquals(id, request);
+                switch (field.getName())
+                {
+                    case shortShortName:
+                        Assert.assertEquals(shortShortValue, field.getValue());
+                        params.set(params.get() * primes[0]);
+                        break;
+                    case shortLongName:
+                        Assert.assertEquals(shortLongValue, field.getValue());
+                        params.set(params.get() * primes[1]);
+                        break;
+                    case longShortName:
+                        Assert.assertEquals(longShortValue, field.getValue());
+                        params.set(params.get() * primes[2]);
+                        break;
+                    default:
+                        Assert.assertEquals(longLongName, field.getName());
+                        Assert.assertEquals(longLongValue, field.getValue());
+                        params.set(params.get() * primes[3]);
+                        break;
+                }
+            }
+
+            @Override
+            public void onHeaders(int request)
+            {
+                Assert.assertEquals(id, request);
+                params.set(params.get() * primes[4]);
+            }
+        });
+
+        for (ByteBuffer buffer : result.getByteBuffers())
+        {
+            parser.parse(buffer);
+            Assert.assertFalse(buffer.hasRemaining());
+        }
+
+        Assert.assertEquals(value, params.get());
+
+        // Parse again byte by byte
+        params.set(1);
+        for (ByteBuffer buffer : result.getByteBuffers())
+        {
+            buffer.flip();
+            while (buffer.hasRemaining())
+                parser.parse(ByteBuffer.wrap(new byte[]{buffer.get()}));
+            Assert.assertFalse(buffer.hasRemaining());
+        }
+
+        Assert.assertEquals(value, params.get());
+    }
+
+    @Test
+    public void testGenerateSmallRequestContent() throws Exception
+    {
+        testGenerateRequestContent(1024);
+    }
+
+    @Test
+    public void testGenerateLargeRequestContent() throws Exception
+    {
+        testGenerateRequestContent(128 * 1024);
+    }
+
+    private void testGenerateRequestContent(final int contentLength) throws Exception
+    {
+        ByteBuffer content = ByteBuffer.allocate(contentLength);
+
+        ByteBufferPool byteBufferPool = new MappedByteBufferPool();
+        ClientGenerator generator = new ClientGenerator(byteBufferPool);
+        final int id = 13;
+        Generator.Result result = generator.generateRequestContent(id, content, true, null);
+
+        final AtomicInteger totalLength = new AtomicInteger();
+        ServerParser parser = new ServerParser(new ServerParser.Listener.Adapter()
+        {
+            @Override
+            public boolean onContent(int request, FCGI.StreamType stream, ByteBuffer buffer)
+            {
+                Assert.assertEquals(id, request);
+                totalLength.addAndGet(buffer.remaining());
+                return false;
+            }
+
+            @Override
+            public void onEnd(int request)
+            {
+                Assert.assertEquals(id, request);
+                Assert.assertEquals(contentLength, totalLength.get());
+            }
+        });
+
+        for (ByteBuffer buffer : result.getByteBuffers())
+        {
+            parser.parse(buffer);
+            Assert.assertFalse(buffer.hasRemaining());
+        }
+
+        // Parse again one byte at a time
+        for (ByteBuffer buffer : result.getByteBuffers())
+        {
+            buffer.flip();
+            while (buffer.hasRemaining())
+                parser.parse(ByteBuffer.wrap(new byte[]{buffer.get()}));
+            Assert.assertFalse(buffer.hasRemaining());
+        }
+    }
+}
diff --git a/jetty-fcgi/fcgi-client/src/test/java/org/eclipse/jetty/fcgi/parser/ClientParserTest.java b/jetty-fcgi/fcgi-client/src/test/java/org/eclipse/jetty/fcgi/parser/ClientParserTest.java
new file mode 100644
index 0000000..702150d
--- /dev/null
+++ b/jetty-fcgi/fcgi-client/src/test/java/org/eclipse/jetty/fcgi/parser/ClientParserTest.java
@@ -0,0 +1,255 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.fcgi.parser;
+
+import java.nio.ByteBuffer;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.eclipse.jetty.fcgi.FCGI;
+import org.eclipse.jetty.fcgi.generator.Generator;
+import org.eclipse.jetty.fcgi.generator.ServerGenerator;
+import org.eclipse.jetty.http.HttpField;
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.MappedByteBufferPool;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class ClientParserTest
+{
+    @Test
+    public void testParseResponseHeaders() throws Exception
+    {
+        final int id = 13;
+        HttpFields fields = new HttpFields();
+
+        final int statusCode = 200;
+        final String statusMessage = "OK";
+        final String contentTypeName = "Content-Type";
+        final String contentTypeValue = "text/html;charset=utf-8";
+        fields.put(contentTypeName, contentTypeValue);
+
+        ByteBufferPool byteBufferPool = new MappedByteBufferPool();
+        ServerGenerator generator = new ServerGenerator(byteBufferPool);
+        Generator.Result result = generator.generateResponseHeaders(id, statusCode, statusMessage, fields, null);
+
+        // Use the fundamental theorem of arithmetic to test the results.
+        // This way we know onHeader() has been called the right number of
+        // times with the right arguments, and so onHeaders().
+        final int[] primes = new int[]{2, 3, 5};
+        int value = 1;
+        for (int prime : primes)
+            value *= prime;
+
+        final AtomicInteger params = new AtomicInteger(1);
+        ClientParser parser = new ClientParser(new ClientParser.Listener.Adapter()
+        {
+            @Override
+            public void onBegin(int request, int code, String reason)
+            {
+                Assert.assertEquals(statusCode, code);
+                Assert.assertEquals(statusMessage, reason);
+                params.set(params.get() * primes[0]);
+            }
+
+            @Override
+            public void onHeader(int request, HttpField field)
+            {
+                Assert.assertEquals(id, request);
+                switch (field.getName())
+                {
+                    case contentTypeName:
+                        Assert.assertEquals(contentTypeValue, field.getValue());
+                        params.set(params.get() * primes[1]);
+                        break;
+                    default:
+                        break;
+                }
+            }
+
+            @Override
+            public void onHeaders(int request)
+            {
+                Assert.assertEquals(id, request);
+                params.set(params.get() * primes[2]);
+            }
+        });
+
+        for (ByteBuffer buffer : result.getByteBuffers())
+        {
+            parser.parse(buffer);
+            Assert.assertFalse(buffer.hasRemaining());
+        }
+
+        Assert.assertEquals(value, params.get());
+    }
+
+    @Test
+    public void testParseNoResponseContent() throws Exception
+    {
+        final int id = 13;
+        HttpFields fields = new HttpFields();
+        fields.put("Content-Length", "0");
+
+        ByteBufferPool byteBufferPool = new MappedByteBufferPool();
+        ServerGenerator generator = new ServerGenerator(byteBufferPool);
+        Generator.Result result1 = generator.generateResponseHeaders(id, 200, "OK", fields, null);
+        Generator.Result result2 = generator.generateResponseContent(id, null, true, false, null);
+
+        final AtomicInteger verifier = new AtomicInteger();
+        ClientParser parser = new ClientParser(new ClientParser.Listener.Adapter()
+        {
+            @Override
+            public boolean onContent(int request, FCGI.StreamType stream, ByteBuffer buffer)
+            {
+                Assert.assertEquals(id, request);
+                verifier.addAndGet(2);
+                return false;
+            }
+
+            @Override
+            public void onEnd(int request)
+            {
+                Assert.assertEquals(id, request);
+                verifier.addAndGet(3);
+            }
+        });
+
+        for (ByteBuffer buffer : result1.getByteBuffers())
+        {
+            parser.parse(buffer);
+            Assert.assertFalse(buffer.hasRemaining());
+        }
+        for (ByteBuffer buffer : result2.getByteBuffers())
+        {
+            parser.parse(buffer);
+            Assert.assertFalse(buffer.hasRemaining());
+        }
+
+        Assert.assertEquals(3, verifier.get());
+    }
+
+    @Test
+    public void testParseSmallResponseContent() throws Exception
+    {
+        final int id = 13;
+        HttpFields fields = new HttpFields();
+
+        ByteBuffer content = ByteBuffer.wrap(new byte[1024]);
+        final int contentLength = content.remaining();
+
+        final int code = 200;
+        final String contentTypeName = "Content-Length";
+        final String contentTypeValue = String.valueOf(contentLength);
+        fields.put(contentTypeName, contentTypeValue);
+
+        ByteBufferPool byteBufferPool = new MappedByteBufferPool();
+        ServerGenerator generator = new ServerGenerator(byteBufferPool);
+        Generator.Result result1 = generator.generateResponseHeaders(id, code, "OK", fields, null);
+        Generator.Result result2 = generator.generateResponseContent(id, content, true, false, null);
+
+        final AtomicInteger verifier = new AtomicInteger();
+        ClientParser parser = new ClientParser(new ClientParser.Listener.Adapter()
+        {
+            @Override
+            public boolean onContent(int request, FCGI.StreamType stream, ByteBuffer buffer)
+            {
+                Assert.assertEquals(id, request);
+                Assert.assertEquals(contentLength, buffer.remaining());
+                verifier.addAndGet(2);
+                return false;
+            }
+
+            @Override
+            public void onEnd(int request)
+            {
+                Assert.assertEquals(id, request);
+                verifier.addAndGet(3);
+            }
+        });
+
+        for (ByteBuffer buffer : result1.getByteBuffers())
+        {
+            parser.parse(buffer);
+            Assert.assertFalse(buffer.hasRemaining());
+        }
+        for (ByteBuffer buffer : result2.getByteBuffers())
+        {
+            parser.parse(buffer);
+            Assert.assertFalse(buffer.hasRemaining());
+        }
+
+        Assert.assertEquals(5, verifier.get());
+    }
+
+    @Test
+    public void testParseLargeResponseContent() throws Exception
+    {
+        final int id = 13;
+        HttpFields fields = new HttpFields();
+
+        ByteBuffer content = ByteBuffer.wrap(new byte[128 * 1024]);
+        final int contentLength = content.remaining();
+
+        final int code = 200;
+        final String contentTypeName = "Content-Length";
+        final String contentTypeValue = String.valueOf(contentLength);
+        fields.put(contentTypeName, contentTypeValue);
+
+        ByteBufferPool byteBufferPool = new MappedByteBufferPool();
+        ServerGenerator generator = new ServerGenerator(byteBufferPool);
+        Generator.Result result1 = generator.generateResponseHeaders(id, code, "OK", fields, null);
+        Generator.Result result2 = generator.generateResponseContent(id, content, true, false, null);
+
+        final AtomicInteger totalLength = new AtomicInteger();
+        final AtomicBoolean verifier = new AtomicBoolean();
+        ClientParser parser = new ClientParser(new ClientParser.Listener.Adapter()
+        {
+            @Override
+            public boolean onContent(int request, FCGI.StreamType stream, ByteBuffer buffer)
+            {
+                Assert.assertEquals(id, request);
+                totalLength.addAndGet(buffer.remaining());
+                return false;
+            }
+
+            @Override
+            public void onEnd(int request)
+            {
+                Assert.assertEquals(id, request);
+                Assert.assertEquals(contentLength, totalLength.get());
+                verifier.set(true);
+            }
+        });
+
+        for (ByteBuffer buffer : result1.getByteBuffers())
+        {
+            parser.parse(buffer);
+            Assert.assertFalse(buffer.hasRemaining());
+        }
+        for (ByteBuffer buffer : result2.getByteBuffers())
+        {
+            parser.parse(buffer);
+            Assert.assertFalse(buffer.hasRemaining());
+        }
+
+        Assert.assertTrue(verifier.get());
+    }
+}
diff --git a/jetty-fcgi/fcgi-client/src/test/resources/jetty-logging.properties b/jetty-fcgi/fcgi-client/src/test/resources/jetty-logging.properties
new file mode 100644
index 0000000..b8df62d
--- /dev/null
+++ b/jetty-fcgi/fcgi-client/src/test/resources/jetty-logging.properties
@@ -0,0 +1,3 @@
+org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
+#org.eclipse.jetty.client.LEVEL=DEBUG
+#org.eclipse.jetty.fcgi.LEVEL=DEBUG
diff --git a/jetty-fcgi/fcgi-server/pom.xml b/jetty-fcgi/fcgi-server/pom.xml
new file mode 100644
index 0000000..134f2c7
--- /dev/null
+++ b/jetty-fcgi/fcgi-server/pom.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>org.eclipse.jetty.fcgi</groupId>
+        <artifactId>fcgi-parent</artifactId>
+        <version>9.2.13.v20150730</version>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>fcgi-server</artifactId>
+    <name>Jetty :: FastCGI :: Server</name>
+
+    <properties>
+        <bundle-symbolic-name>${project.groupId}.server</bundle-symbolic-name>
+    </properties>
+
+    <build>
+        <plugins>
+            <plugin>
+                <artifactId>maven-assembly-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>single</goal>
+                        </goals>
+                        <configuration>
+                            <descriptorRefs>
+                                <descriptorRef>config</descriptorRef>
+                            </descriptorRefs>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>javax.servlet-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty.fcgi</groupId>
+            <artifactId>fcgi-client</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-proxy</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-server</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-servlet</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty.spdy</groupId>
+            <artifactId>spdy-http-server</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+</project>
diff --git a/jetty-fcgi/fcgi-server/src/main/config/modules/fcgi.mod b/jetty-fcgi/fcgi-server/src/main/config/modules/fcgi.mod
new file mode 100644
index 0000000..e837b00
--- /dev/null
+++ b/jetty-fcgi/fcgi-server/src/main/config/modules/fcgi.mod
@@ -0,0 +1,15 @@
+#
+# FastCGI Module
+#
+
+[depend]
+servlet
+client
+
+[lib]
+lib/jetty-proxy-${jetty.version}.jar
+lib/fcgi/*.jar
+
+[ini-template]
+## For configuration of FastCGI contexts, see
+## TODO: documentation url here
diff --git a/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpChannelOverFCGI.java b/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpChannelOverFCGI.java
new file mode 100644
index 0000000..62e3825
--- /dev/null
+++ b/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpChannelOverFCGI.java
@@ -0,0 +1,206 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.fcgi.server;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.jetty.fcgi.FCGI;
+import org.eclipse.jetty.http.HttpField;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.HttpChannel;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.HttpInput;
+import org.eclipse.jetty.server.HttpTransport;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public class HttpChannelOverFCGI extends HttpChannel<ByteBuffer>
+{
+    private static final Logger LOG = Log.getLogger(HttpChannelOverFCGI.class);
+
+    private final List<HttpField> fields = new ArrayList<>();
+    private final Dispatcher dispatcher;
+    private String method;
+    private String path;
+    private String query;
+    private String version;
+
+    public HttpChannelOverFCGI(Connector connector, HttpConfiguration configuration, EndPoint endPoint, HttpTransport transport, HttpInput<ByteBuffer> input)
+    {
+        super(connector, configuration, endPoint, transport, input);
+        this.dispatcher = new Dispatcher(connector.getExecutor(), this);
+    }
+
+    protected void header(HttpField field)
+    {
+        if (FCGI.Headers.REQUEST_METHOD.equalsIgnoreCase(field.getName()))
+            method = field.getValue();
+        else if (FCGI.Headers.DOCUMENT_URI.equalsIgnoreCase(field.getName()))
+            path = field.getValue();
+        else if (FCGI.Headers.QUERY_STRING.equalsIgnoreCase(field.getName()))
+            query = field.getValue();
+        else if (FCGI.Headers.SERVER_PROTOCOL.equalsIgnoreCase(field.getName()))
+            version = field.getValue();
+        else
+            fields.add(field);
+    }
+
+    @Override
+    public boolean headerComplete()
+    {
+        String uri = path;
+        if (query != null && query.length() > 0)
+            uri += "?" + query;
+        startRequest(HttpMethod.fromString(method), method, ByteBuffer.wrap(uri.getBytes(StandardCharsets.UTF_8)),
+                HttpVersion.fromString(version));
+
+        for (HttpField fcgiField : fields)
+        {
+            HttpField httpField = convertHeader(fcgiField);
+            if (httpField != null)
+                parsedHeader(httpField);
+        }
+
+        return super.headerComplete();
+    }
+
+    private HttpField convertHeader(HttpField field)
+    {
+        String name = field.getName();
+        if (name.startsWith("HTTP_"))
+        {
+            // Converts e.g. "HTTP_ACCEPT_ENCODING" to "Accept-Encoding"
+            String[] parts = name.split("_");
+            StringBuilder httpName = new StringBuilder();
+            for (int i = 1; i < parts.length; ++i)
+            {
+                if (i > 1)
+                    httpName.append("-");
+                String part = parts[i];
+                httpName.append(Character.toUpperCase(part.charAt(0)));
+                httpName.append(part.substring(1).toLowerCase(Locale.ENGLISH));
+            }
+            return new HttpField(httpName.toString(), field.getValue());
+        }
+        return null;
+    }
+
+    protected void dispatch()
+    {
+        dispatcher.dispatch();
+    }
+
+    private static class Dispatcher implements Runnable
+    {
+        private final AtomicReference<State> state = new AtomicReference<>(State.IDLE);
+        private final Executor executor;
+        private final Runnable runnable;
+
+        private Dispatcher(Executor executor, Runnable runnable)
+        {
+            this.executor = executor;
+            this.runnable = runnable;
+        }
+
+        public void dispatch()
+        {
+            while (true)
+            {
+                State current = state.get();
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Dispatching, state={}", current);
+                switch (current)
+                {
+                    case IDLE:
+                    {
+                        if (!state.compareAndSet(current, State.DISPATCH))
+                            continue;
+                        executor.execute(this);
+                        return;
+                    }
+                    case DISPATCH:
+                    case EXECUTE:
+                    {
+                        if (state.compareAndSet(current, State.SCHEDULE))
+                            return;
+                        continue;
+                    }
+                    case SCHEDULE:
+                    {
+                        return;
+                    }
+                    default:
+                    {
+                        throw new IllegalStateException();
+                    }
+                }
+            }
+        }
+
+        @Override
+        public void run()
+        {
+            while (true)
+            {
+                State current = state.get();
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Running, state={}", current);
+                switch (current)
+                {
+                    case DISPATCH:
+                    {
+                        if (state.compareAndSet(current, State.EXECUTE))
+                            runnable.run();
+                        continue;
+                    }
+                    case EXECUTE:
+                    {
+                        if (state.compareAndSet(current, State.IDLE))
+                            return;
+                        continue;
+                    }
+                    case SCHEDULE:
+                    {
+                        if (state.compareAndSet(current, State.DISPATCH))
+                            continue;
+                        throw new IllegalStateException();
+                    }
+                    default:
+                    {
+                        throw new IllegalStateException();
+                    }
+                }
+            }
+        }
+
+        private enum State
+        {
+            IDLE, DISPATCH, EXECUTE, SCHEDULE
+        }
+    }
+}
diff --git a/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpTransportOverFCGI.java b/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpTransportOverFCGI.java
new file mode 100644
index 0000000..24210d0
--- /dev/null
+++ b/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpTransportOverFCGI.java
@@ -0,0 +1,127 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.fcgi.server;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.fcgi.generator.Flusher;
+import org.eclipse.jetty.fcgi.generator.Generator;
+import org.eclipse.jetty.fcgi.generator.ServerGenerator;
+import org.eclipse.jetty.http.HttpGenerator;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpHeaderValue;
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.server.HttpTransport;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+
+public class HttpTransportOverFCGI implements HttpTransport
+{
+    private final ServerGenerator generator;
+    private final Flusher flusher;
+    private final int request;
+    private volatile boolean head;
+    private volatile boolean shutdown;
+    private volatile boolean aborted;
+
+    public HttpTransportOverFCGI(ByteBufferPool byteBufferPool, Flusher flusher, int request, boolean sendStatus200)
+    {
+        this.generator = new ServerGenerator(byteBufferPool, sendStatus200);
+        this.flusher = flusher;
+        this.request = request;
+    }
+
+    @Override
+    public void send(HttpGenerator.ResponseInfo info, ByteBuffer content, boolean lastContent, Callback callback)
+    {
+        boolean head = this.head = info.isHead();
+        boolean shutdown = this.shutdown = info.getHttpFields().contains(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString());
+
+        if (head)
+        {
+            if (lastContent)
+            {
+                Generator.Result headersResult = generateResponseHeaders(info, Callback.Adapter.INSTANCE);
+                Generator.Result contentResult = generateResponseContent(BufferUtil.EMPTY_BUFFER, true, callback);
+                flusher.flush(headersResult, contentResult);
+            }
+            else
+            {
+                Generator.Result headersResult = generateResponseHeaders(info, callback);
+                flusher.flush(headersResult);
+            }
+        }
+        else
+        {
+            Generator.Result headersResult = generateResponseHeaders(info, Callback.Adapter.INSTANCE);
+            Generator.Result contentResult = generateResponseContent(content, lastContent, callback);
+            flusher.flush(headersResult, contentResult);
+        }
+
+        if (lastContent && shutdown)
+            flusher.shutdown();
+    }
+
+    @Override
+    public void send(ByteBuffer content, boolean lastContent, Callback callback)
+    {
+        if (head)
+        {
+            if (lastContent)
+            {
+                Generator.Result result = generateResponseContent(BufferUtil.EMPTY_BUFFER, true, callback);
+                flusher.flush(result);
+            }
+            else
+            {
+                // Skip content generation
+                callback.succeeded();
+            }
+        }
+        else
+        {
+            Generator.Result result = generateResponseContent(content, lastContent, callback);
+            flusher.flush(result);
+        }
+
+        if (lastContent && shutdown)
+            flusher.shutdown();
+    }
+
+    protected Generator.Result generateResponseHeaders(HttpGenerator.ResponseInfo info, Callback callback)
+    {
+        return generator.generateResponseHeaders(request, info.getStatus(), info.getReason(), info.getHttpFields(), callback);
+    }
+
+    protected Generator.Result generateResponseContent(ByteBuffer buffer, boolean lastContent, Callback callback)
+    {
+        return generator.generateResponseContent(request, buffer, lastContent, aborted, callback);
+    }
+
+    @Override
+    public void abort()
+    {
+        aborted = true;
+    }
+
+    @Override
+    public void completed()
+    {
+    }
+}
diff --git a/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/ServerFCGIConnection.java b/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/ServerFCGIConnection.java
new file mode 100644
index 0000000..9809dd9
--- /dev/null
+++ b/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/ServerFCGIConnection.java
@@ -0,0 +1,196 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.fcgi.server;
+
+import java.nio.ByteBuffer;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import org.eclipse.jetty.fcgi.FCGI;
+import org.eclipse.jetty.fcgi.generator.Flusher;
+import org.eclipse.jetty.fcgi.parser.ServerParser;
+import org.eclipse.jetty.http.HttpField;
+import org.eclipse.jetty.io.AbstractConnection;
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.server.ByteBufferQueuedHttpInput;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public class ServerFCGIConnection extends AbstractConnection
+{
+    private static final Logger LOG = Log.getLogger(ServerFCGIConnection.class);
+
+    private final ConcurrentMap<Integer, HttpChannelOverFCGI> channels = new ConcurrentHashMap<>();
+    private final Connector connector;
+    private final boolean sendStatus200;
+    private final Flusher flusher;
+    private final HttpConfiguration configuration;
+    private final ServerParser parser;
+
+    public ServerFCGIConnection(Connector connector, EndPoint endPoint, HttpConfiguration configuration, boolean sendStatus200)
+    {
+        super(endPoint, connector.getExecutor());
+        this.connector = connector;
+        this.flusher = new Flusher(endPoint);
+        this.configuration = configuration;
+        this.sendStatus200 = sendStatus200;
+        this.parser = new ServerParser(new ServerListener());
+    }
+
+    @Override
+    public void onOpen()
+    {
+        super.onOpen();
+        fillInterested();
+    }
+
+    @Override
+    public void onFillable()
+    {
+        EndPoint endPoint = getEndPoint();
+        ByteBufferPool bufferPool = connector.getByteBufferPool();
+        ByteBuffer buffer = bufferPool.acquire(configuration.getResponseHeaderSize(), true);
+        try
+        {
+            while (true)
+            {
+                int read = endPoint.fill(buffer);
+                if (LOG.isDebugEnabled()) // Avoid boxing of variable 'read'
+                    LOG.debug("Read {} bytes from {}", read, endPoint);
+                if (read > 0)
+                {
+                    parse(buffer);
+                }
+                else if (read == 0)
+                {
+                    fillInterested();
+                    break;
+                }
+                else
+                {
+                    shutdown();
+                    break;
+                }
+            }
+        }
+        catch (Exception x)
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug(x);
+            // TODO: fail and close ?
+        }
+        finally
+        {
+            bufferPool.release(buffer);
+        }
+    }
+
+    private void parse(ByteBuffer buffer)
+    {
+        while (buffer.hasRemaining())
+            parser.parse(buffer);
+    }
+
+    private void shutdown()
+    {
+        flusher.shutdown();
+    }
+
+    private class ServerListener implements ServerParser.Listener
+    {
+        @Override
+        public void onStart(int request, FCGI.Role role, int flags)
+        {
+            // TODO: handle flags
+            HttpChannelOverFCGI channel = new HttpChannelOverFCGI(connector, configuration, getEndPoint(),
+                    new HttpTransportOverFCGI(connector.getByteBufferPool(), flusher, request, sendStatus200),
+                    new ByteBufferQueuedHttpInput());
+            HttpChannelOverFCGI existing = channels.putIfAbsent(request, channel);
+            if (existing != null)
+                throw new IllegalStateException();
+            if (LOG.isDebugEnabled())
+                LOG.debug("Request {} start on {}", request, channel);
+        }
+
+        @Override
+        public void onHeader(int request, HttpField field)
+        {
+            HttpChannelOverFCGI channel = channels.get(request);
+            if (LOG.isDebugEnabled())
+                LOG.debug("Request {} header {} on {}", request, field, channel);
+            if (channel != null)
+                channel.header(field);
+        }
+
+        @Override
+        public void onHeaders(int request)
+        {
+            HttpChannelOverFCGI channel = channels.get(request);
+            if (LOG.isDebugEnabled())
+                LOG.debug("Request {} headers on {}", request, channel);
+            if (channel != null)
+            {
+                if (channel.headerComplete())
+                    channel.dispatch();
+            }
+        }
+
+        @Override
+        public boolean onContent(int request, FCGI.StreamType stream, ByteBuffer buffer)
+        {
+            HttpChannelOverFCGI channel = channels.get(request);
+            if (LOG.isDebugEnabled())
+                LOG.debug("Request {} {} content {} on {}", request, stream, buffer, channel);
+            if (channel != null)
+            {
+                if (channel.content(buffer))
+                    channel.dispatch();
+            }
+            return false;
+        }
+
+        @Override
+        public void onEnd(int request)
+        {
+            HttpChannelOverFCGI channel = channels.remove(request);
+            if (LOG.isDebugEnabled())
+                LOG.debug("Request {} end on {}", request, channel);
+            if (channel != null)
+            {
+                if (channel.messageComplete())
+                    channel.dispatch();
+            }
+        }
+
+        @Override
+        public void onFailure(int request, Throwable failure)
+        {
+            HttpChannelOverFCGI channel = channels.remove(request);
+            if (LOG.isDebugEnabled())
+                LOG.debug("Request {} failure on {}: {}", request, channel, failure);
+            if (channel != null)
+            {
+                channel.badMessage(400, failure.toString());
+            }
+        }
+    }
+}
diff --git a/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/ServerFCGIConnectionFactory.java b/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/ServerFCGIConnectionFactory.java
new file mode 100644
index 0000000..5e49e4a
--- /dev/null
+++ b/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/ServerFCGIConnectionFactory.java
@@ -0,0 +1,49 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.fcgi.server;
+
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.server.AbstractConnectionFactory;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.HttpConfiguration;
+
+public class ServerFCGIConnectionFactory extends AbstractConnectionFactory
+{
+    private final HttpConfiguration configuration;
+    private final boolean sendStatus200;
+
+    public ServerFCGIConnectionFactory(HttpConfiguration configuration)
+    {
+        this(configuration, true);
+    }
+
+    public ServerFCGIConnectionFactory(HttpConfiguration configuration, boolean sendStatus200)
+    {
+        super("fcgi/1.0");
+        this.configuration = configuration;
+        this.sendStatus200 = sendStatus200;
+    }
+
+    @Override
+    public Connection newConnection(Connector connector, EndPoint endPoint)
+    {
+        return new ServerFCGIConnection(connector, endPoint, configuration, sendStatus200);
+    }
+}
diff --git a/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/proxy/FastCGIProxyServlet.java b/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/proxy/FastCGIProxyServlet.java
new file mode 100644
index 0000000..b725ccf
--- /dev/null
+++ b/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/proxy/FastCGIProxyServlet.java
@@ -0,0 +1,188 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.fcgi.server.proxy;
+
+import java.net.URI;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.fcgi.FCGI;
+import org.eclipse.jetty.fcgi.client.http.HttpClientTransportOverFCGI;
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpScheme;
+import org.eclipse.jetty.proxy.AsyncProxyServlet;
+import org.eclipse.jetty.proxy.ProxyServlet;
+
+/**
+ * Specific implementation of {@link ProxyServlet.Transparent} for FastCGI.
+ * <p />
+ * This servlet accepts a HTTP request and transforms it into a FastCGI request
+ * that is sent to the FastCGI server specified in the <code>proxyTo</code>
+ * init-param.
+ * <p />
+ * This servlet accepts two additional init-params:
+ * <ul>
+ *     <li><code>scriptRoot</code>, mandatory, that must be set to the directory where
+ *     the application that must be served via FastCGI is installed and corresponds to
+ *     the FastCGI DOCUMENT_ROOT parameter</li>
+ *     <li><code>scriptPattern</code>, optional, defaults to <code>(.+?\.php)</code>,
+ *     that specifies a regular expression with at least 1 and at most 2 groups that specify
+ *     respectively:
+ *     <ul>
+ *         <li>the FastCGI SCRIPT_NAME parameter</li>
+ *         <li>the FastCGI PATH_INFO parameter</li>
+ *     </ul></li>
+ *     <li><code>fastCGI.HTTPS</code>, optional, defaults to false, that specifies whether
+ *     to force the FastCGI <code>HTTPS</code> parameter to the value <code>on</code></li>
+ * </ul>
+ *
+ * @see TryFilesFilter
+ */
+public class FastCGIProxyServlet extends AsyncProxyServlet.Transparent
+{
+    public static final String SCRIPT_ROOT_INIT_PARAM = "scriptRoot";
+    public static final String SCRIPT_PATTERN_INIT_PARAM = "scriptPattern";
+    public static final String FASTCGI_HTTPS_INIT_PARAM = "fastCGI.HTTPS";
+
+    private static final String REMOTE_ADDR_ATTRIBUTE = FastCGIProxyServlet.class.getName() + ".remoteAddr";
+    private static final String REMOTE_PORT_ATTRIBUTE = FastCGIProxyServlet.class.getName() + ".remotePort";
+    private static final String SERVER_NAME_ATTRIBUTE = FastCGIProxyServlet.class.getName() + ".serverName";
+    private static final String SERVER_ADDR_ATTRIBUTE = FastCGIProxyServlet.class.getName() + ".serverAddr";
+    private static final String SERVER_PORT_ATTRIBUTE = FastCGIProxyServlet.class.getName() + ".serverPort";
+    private static final String SCHEME_ATTRIBUTE = FastCGIProxyServlet.class.getName() + ".scheme";
+    private static final String REQUEST_URI_ATTRIBUTE = FastCGIProxyServlet.class.getName() + ".requestURI";
+
+    private Pattern scriptPattern;
+    private boolean fcgiHTTPS;
+
+    @Override
+    public void init() throws ServletException
+    {
+        super.init();
+
+        String value = getInitParameter(SCRIPT_PATTERN_INIT_PARAM);
+        if (value == null)
+            value = "(.+?\\.php)";
+        scriptPattern = Pattern.compile(value);
+
+        fcgiHTTPS = Boolean.parseBoolean(getInitParameter(FASTCGI_HTTPS_INIT_PARAM));
+    }
+
+    @Override
+    protected HttpClient newHttpClient()
+    {
+        ServletConfig config = getServletConfig();
+        String scriptRoot = config.getInitParameter(SCRIPT_ROOT_INIT_PARAM);
+        if (scriptRoot == null)
+            throw new IllegalArgumentException("Mandatory parameter '" + SCRIPT_ROOT_INIT_PARAM + "' not configured");
+        return new HttpClient(new ProxyHttpClientTransportOverFCGI(scriptRoot), null);
+    }
+
+    @Override
+    protected void customizeProxyRequest(Request proxyRequest, HttpServletRequest request)
+    {
+        proxyRequest.attribute(REMOTE_ADDR_ATTRIBUTE, request.getRemoteAddr());
+        proxyRequest.attribute(REMOTE_PORT_ATTRIBUTE, String.valueOf(request.getRemotePort()));
+        proxyRequest.attribute(SERVER_NAME_ATTRIBUTE, request.getServerName());
+        proxyRequest.attribute(SERVER_ADDR_ATTRIBUTE, request.getLocalAddr());
+        proxyRequest.attribute(SERVER_PORT_ATTRIBUTE, String.valueOf(request.getLocalPort()));
+
+        proxyRequest.attribute(SCHEME_ATTRIBUTE, request.getScheme());
+
+        // If we are forwarded or included, retain the original request URI.
+        String originalPath = (String)request.getAttribute(RequestDispatcher.FORWARD_REQUEST_URI);
+        String originalQuery = (String)request.getAttribute(RequestDispatcher.FORWARD_QUERY_STRING);
+        if (originalPath == null)
+        {
+            originalPath = (String)request.getAttribute(RequestDispatcher.INCLUDE_REQUEST_URI);
+            originalQuery = (String)request.getAttribute(RequestDispatcher.INCLUDE_QUERY_STRING);
+        }
+        if (originalPath != null)
+        {
+            String originalURI = originalPath;
+            if (originalQuery != null)
+                originalURI += "?" + originalQuery;
+            proxyRequest.attribute(REQUEST_URI_ATTRIBUTE, originalURI);
+        }
+
+        super.customizeProxyRequest(proxyRequest, request);
+    }
+
+    protected void customizeFastCGIHeaders(Request proxyRequest, HttpFields fastCGIHeaders)
+    {
+        fastCGIHeaders.put(FCGI.Headers.REMOTE_ADDR, (String)proxyRequest.getAttributes().get(REMOTE_ADDR_ATTRIBUTE));
+        fastCGIHeaders.put(FCGI.Headers.REMOTE_PORT, (String)proxyRequest.getAttributes().get(REMOTE_PORT_ATTRIBUTE));
+        fastCGIHeaders.put(FCGI.Headers.SERVER_NAME, (String)proxyRequest.getAttributes().get(SERVER_NAME_ATTRIBUTE));
+        fastCGIHeaders.put(FCGI.Headers.SERVER_ADDR, (String)proxyRequest.getAttributes().get(SERVER_ADDR_ATTRIBUTE));
+        fastCGIHeaders.put(FCGI.Headers.SERVER_PORT, (String)proxyRequest.getAttributes().get(SERVER_PORT_ATTRIBUTE));
+
+        if (fcgiHTTPS || HttpScheme.HTTPS.is((String)proxyRequest.getAttributes().get(SCHEME_ATTRIBUTE)))
+            fastCGIHeaders.put(FCGI.Headers.HTTPS, "on");
+
+        URI proxyRequestURI = proxyRequest.getURI();
+        String rawPath = proxyRequestURI.getRawPath();
+        String rawQuery = proxyRequestURI.getRawQuery();
+
+        String requestURI = (String)proxyRequest.getAttributes().get(REQUEST_URI_ATTRIBUTE);
+        if (requestURI == null)
+        {
+            requestURI = rawPath;
+            if (rawQuery != null)
+                requestURI += "?" + rawQuery;
+        }
+        fastCGIHeaders.put(FCGI.Headers.REQUEST_URI, requestURI);
+
+        String scriptName = rawPath;
+        Matcher matcher = scriptPattern.matcher(rawPath);
+        if (matcher.matches())
+        {
+            // Expect at least one group in the regular expression.
+            scriptName = matcher.group(1);
+
+            // If there is a second group, map it to PATH_INFO.
+            if (matcher.groupCount() > 1)
+                fastCGIHeaders.put(FCGI.Headers.PATH_INFO, matcher.group(2));
+        }
+        fastCGIHeaders.put(FCGI.Headers.SCRIPT_NAME, scriptName);
+
+        String root = fastCGIHeaders.get(FCGI.Headers.DOCUMENT_ROOT);
+        fastCGIHeaders.put(FCGI.Headers.SCRIPT_FILENAME, root + scriptName);
+    }
+
+    private class ProxyHttpClientTransportOverFCGI extends HttpClientTransportOverFCGI
+    {
+        public ProxyHttpClientTransportOverFCGI(String scriptRoot)
+        {
+            super(scriptRoot);
+        }
+
+        @Override
+        protected void customize(Request request, HttpFields fastCGIHeaders)
+        {
+            super.customize(request, fastCGIHeaders);
+            customizeFastCGIHeaders(request, fastCGIHeaders);
+        }
+    }
+}
diff --git a/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/proxy/TryFilesFilter.java b/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/proxy/TryFilesFilter.java
new file mode 100644
index 0000000..5cdfa1d
--- /dev/null
+++ b/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/proxy/TryFilesFilter.java
@@ -0,0 +1,141 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.fcgi.server.proxy;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Inspired by nginx's try_files functionality.
+ * <p />
+ * This filter accepts the <code>files</code> init-param as a list of space-separated
+ * file URIs. The special token <code>$path</code> represents the current request URL's
+ * path (the portion after the context path).
+ * <p />
+ * Typical example of how this filter can be configured is the following:
+ * <pre>
+ * <filter>
+ *     <filter-name>try_files</filter-name>
+ *     <filter-class>org.eclipse.jetty.fcgi.server.proxy.TryFilesFilter</filter-class>
+ *     <init-param>
+ *         <param-name>files</param-name>
+ *         <param-value>maintenance.html $path index.php?p=$path</param-value>
+ *     </init-param>
+ * </filter>
+ * </pre>
+ * For a request such as <code>/context/path/to/resource.ext</code>, this filter will
+ * try to serve the <code>/maintenance.html</code> file if it finds it; failing that,
+ * it will try to serve the <code>/path/to/resource.ext</code> file if it finds it;
+ * failing that it will forward the request to <code>index.php?p=/path/to/resource.ext</code>.
+ * The last file URI specified in the list is therefore the "fallback" to which the request
+ * is forwarded to in case no previous files can be found.
+ * <p />
+ * The files are resolved using {@link ServletContext#getResource(String)} to make sure
+ * that only files visible to the application are served.
+ *
+ * @see FastCGIProxyServlet
+ */
+public class TryFilesFilter implements Filter
+{
+    public static final String FILES_INIT_PARAM = "files";
+
+    private String[] files;
+
+    @Override
+    public void init(FilterConfig config) throws ServletException
+    {
+        String param = config.getInitParameter(FILES_INIT_PARAM);
+        if (param == null)
+            throw new ServletException(String.format("Missing mandatory parameter '%s'", FILES_INIT_PARAM));
+        files = param.split(" ");
+    }
+
+    @Override
+    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
+    {
+        HttpServletRequest httpRequest = (HttpServletRequest)request;
+        HttpServletResponse httpResponse = (HttpServletResponse)response;
+
+        for (int i = 0; i < files.length - 1; ++i)
+        {
+            String file = files[i];
+            String resolved = resolve(httpRequest, file);
+
+            URL url = request.getServletContext().getResource(resolved);
+            if (url == null)
+                continue;
+
+            if (Files.isReadable(toPath(url)))
+            {
+                chain.doFilter(httpRequest, httpResponse);
+                return;
+            }
+        }
+
+        // The last one is the fallback
+        fallback(httpRequest, httpResponse, chain, files[files.length - 1]);
+    }
+
+    private Path toPath(URL url) throws IOException
+    {
+        try
+        {
+            return Paths.get(url.toURI());
+        }
+        catch (URISyntaxException x)
+        {
+            throw new IOException(x);
+        }
+    }
+
+    protected void fallback(HttpServletRequest request, HttpServletResponse response, FilterChain chain, String fallback) throws IOException, ServletException
+    {
+        String resolved = resolve(request, fallback);
+        request.getServletContext().getRequestDispatcher(resolved).forward(request, response);
+    }
+
+    private String resolve(HttpServletRequest request, String value)
+    {
+        String path = request.getServletPath();
+        String info = request.getPathInfo();
+        if (info != null)
+            path += info;
+        if (!path.startsWith("/"))
+            path = "/" + path;
+        return value.replaceAll("\\$path", path);
+    }
+
+    @Override
+    public void destroy()
+    {
+    }
+}
diff --git a/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/AbstractHttpClientServerTest.java b/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/AbstractHttpClientServerTest.java
new file mode 100644
index 0000000..843dbd8
--- /dev/null
+++ b/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/AbstractHttpClientServerTest.java
@@ -0,0 +1,123 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.fcgi.server;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.eclipse.jetty.client.ConnectionPool;
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.HttpDestination;
+import org.eclipse.jetty.client.LeakTrackingConnectionPool;
+import org.eclipse.jetty.client.Origin;
+import org.eclipse.jetty.fcgi.client.http.HttpClientTransportOverFCGI;
+import org.eclipse.jetty.fcgi.client.http.HttpDestinationOverFCGI;
+import org.eclipse.jetty.http.HttpScheme;
+import org.eclipse.jetty.io.LeakTrackingByteBufferPool;
+import org.eclipse.jetty.io.MappedByteBufferPool;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.toolchain.test.TestTracker;
+import org.eclipse.jetty.util.LeakDetector;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.junit.After;
+import org.junit.Rule;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
+public abstract class AbstractHttpClientServerTest
+{
+    @Rule
+    public final TestTracker tracker = new TestTracker();
+    private LeakTrackingByteBufferPool serverBufferPool;
+    private LeakTrackingByteBufferPool clientBufferPool;
+    private final AtomicLong connectionLeaks = new AtomicLong();
+    protected Server server;
+    protected ServerConnector connector;
+    protected HttpClient client;
+    protected String scheme = HttpScheme.HTTP.asString();
+
+    public void start(Handler handler) throws Exception
+    {
+        server = new Server();
+
+        ServerFCGIConnectionFactory fcgiConnectionFactory = new ServerFCGIConnectionFactory(new HttpConfiguration());
+        serverBufferPool = new LeakTrackingByteBufferPool(new MappedByteBufferPool.Tagged());
+        connector = new ServerConnector(server, null, null, serverBufferPool,
+                1, Math.max(1, Runtime.getRuntime().availableProcessors() / 2), fcgiConnectionFactory);
+//        connector.setPort(9000);
+
+        server.addConnector(connector);
+        server.setHandler(handler);
+        server.start();
+
+        QueuedThreadPool executor = new QueuedThreadPool();
+        executor.setName(executor.getName() + "-client");
+        
+        client = new HttpClient(new HttpClientTransportOverFCGI(1, false, "")
+        {
+            @Override
+            public HttpDestination newHttpDestination(Origin origin)
+            {
+                return new HttpDestinationOverFCGI(client, origin)
+                {
+                    @Override
+                    protected ConnectionPool newConnectionPool(HttpClient client)
+                    {
+                        return new LeakTrackingConnectionPool(this, client.getMaxConnectionsPerDestination(), this)
+                        {
+                            @Override
+                            protected void leaked(LeakDetector.LeakInfo leakInfo)
+                            {
+                                connectionLeaks.incrementAndGet();
+                            }
+                        };
+                    }
+                };
+            }
+        }, null);
+        client.setExecutor(executor);
+        clientBufferPool = new LeakTrackingByteBufferPool(new MappedByteBufferPool.Tagged());
+        client.setByteBufferPool(clientBufferPool);
+        client.start();
+    }
+
+    @After
+    public void dispose() throws Exception
+    {
+        System.gc();
+
+        assertThat("Server BufferPool - leaked acquires", serverBufferPool.getLeakedAcquires(), is(0L));
+        assertThat("Server BufferPool - leaked releases", serverBufferPool.getLeakedReleases(), is(0L));
+        assertThat("Server BufferPool - unreleased", serverBufferPool.getLeakedResources(), is(0L));
+        
+        assertThat("Client BufferPool - leaked acquires", clientBufferPool.getLeakedAcquires(), is(0L));
+        assertThat("Client BufferPool - leaked releases", clientBufferPool.getLeakedReleases(), is(0L));
+        assertThat("Client BufferPool - unreleased", clientBufferPool.getLeakedResources(), is(0L));
+        
+        assertThat("Connection Leaks", connectionLeaks.get(), is(0L));
+
+        if (client != null)
+            client.stop();
+        if (server != null)
+            server.stop();
+    }
+}
diff --git a/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/EmptyServerHandler.java b/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/EmptyServerHandler.java
new file mode 100644
index 0000000..37307ab
--- /dev/null
+++ b/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/EmptyServerHandler.java
@@ -0,0 +1,36 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.fcgi.server;
+
+import java.io.IOException;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+
+public class EmptyServerHandler extends AbstractHandler
+{
+    @Override
+    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+    {
+        baseRequest.setHandled(true);
+    }
+}
diff --git a/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/ExternalFastCGIServerTest.java b/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/ExternalFastCGIServerTest.java
new file mode 100644
index 0000000..97d0ca1
--- /dev/null
+++ b/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/ExternalFastCGIServerTest.java
@@ -0,0 +1,55 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.fcgi.server;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.fcgi.client.http.HttpClientTransportOverFCGI;
+import org.junit.Assert;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class ExternalFastCGIServerTest
+{
+    @Test
+    @Ignore("Relies on an external server")
+    public void testExternalFastCGIServer() throws Exception
+    {
+        // Assume a FastCGI server is listening on localhost:9000
+
+        HttpClient client = new HttpClient(new HttpClientTransportOverFCGI("/var/www/php-fcgi"), null);
+        client.start();
+
+        ContentResponse response = client.newRequest("localhost", 9000)
+                .path("/index.php")
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        Assert.assertEquals(200, response.getStatus());
+
+        Path responseFile = Paths.get(System.getProperty("java.io.tmpdir"), "fcgi_response.html");
+        Files.write(responseFile, response.getContent(), StandardOpenOption.CREATE, StandardOpenOption.WRITE);
+    }
+}
diff --git a/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/HttpClientTest.java b/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/HttpClientTest.java
new file mode 100644
index 0000000..885f774
--- /dev/null
+++ b/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/HttpClientTest.java
@@ -0,0 +1,703 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.fcgi.server;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URLEncoder;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.Random;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.zip.GZIPOutputStream;
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.client.api.Result;
+import org.eclipse.jetty.client.util.BytesContentProvider;
+import org.eclipse.jetty.client.util.DeferredContentProvider;
+import org.eclipse.jetty.client.util.FutureResponseListener;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.toolchain.test.IO;
+import org.eclipse.jetty.toolchain.test.annotation.Slow;
+import org.eclipse.jetty.util.Callback;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class HttpClientTest extends AbstractHttpClientServerTest
+{
+    @Test
+    public void testGETResponseWithoutContent() throws Exception
+    {
+        start(new EmptyServerHandler());
+
+        for (int i = 0; i < 2; ++i)
+        {
+            Response response = client.GET(scheme + "://localhost:" + connector.getLocalPort());
+            Assert.assertNotNull(response);
+            Assert.assertEquals(200, response.getStatus());
+        }
+    }
+
+    @Test
+    public void testGETResponseWithContent() throws Exception
+    {
+        final byte[] data = new byte[]{0, 1, 2, 3, 4, 5, 6, 7};
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                response.getOutputStream().write(data);
+                baseRequest.setHandled(true);
+            }
+        });
+
+        int maxConnections = 256;
+        client.setMaxConnectionsPerDestination(maxConnections);
+
+        for (int i = 0; i < maxConnections + 1; ++i)
+        {
+            ContentResponse response = client.GET(scheme + "://localhost:" + connector.getLocalPort());
+            Assert.assertNotNull(response);
+            Assert.assertEquals(200, response.getStatus());
+            byte[] content = response.getContent();
+            Assert.assertArrayEquals(data, content);
+        }
+    }
+
+    @Test
+    public void testGETWithParametersResponseWithContent() throws Exception
+    {
+        final String paramName1 = "a";
+        final String paramName2 = "b";
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                response.setCharacterEncoding("UTF-8");
+                ServletOutputStream output = response.getOutputStream();
+                String paramValue1 = request.getParameter(paramName1);
+                output.write(paramValue1.getBytes("UTF-8"));
+                String paramValue2 = request.getParameter(paramName2);
+                Assert.assertEquals("", paramValue2);
+                output.write("empty".getBytes("UTF-8"));
+                baseRequest.setHandled(true);
+            }
+        });
+
+        String value1 = "\u20AC";
+        String paramValue1 = URLEncoder.encode(value1, "UTF-8");
+        String query = paramName1 + "=" + paramValue1 + "&" + paramName2;
+        ContentResponse response = client.GET(scheme + "://localhost:" + connector.getLocalPort() + "/?" + query);
+
+        Assert.assertNotNull(response);
+        Assert.assertEquals(200, response.getStatus());
+        String content = new String(response.getContent(), "UTF-8");
+        Assert.assertEquals(value1 + "empty", content);
+    }
+
+    @Test
+    public void testGETWithParametersMultiValuedResponseWithContent() throws Exception
+    {
+        final String paramName1 = "a";
+        final String paramName2 = "b";
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                response.setCharacterEncoding("UTF-8");
+                ServletOutputStream output = response.getOutputStream();
+                String[] paramValues1 = request.getParameterValues(paramName1);
+                for (String paramValue : paramValues1)
+                    output.write(paramValue.getBytes("UTF-8"));
+                String paramValue2 = request.getParameter(paramName2);
+                output.write(paramValue2.getBytes("UTF-8"));
+                baseRequest.setHandled(true);
+            }
+        });
+
+        String value11 = "\u20AC";
+        String value12 = "\u20AA";
+        String value2 = "&";
+        String paramValue11 = URLEncoder.encode(value11, "UTF-8");
+        String paramValue12 = URLEncoder.encode(value12, "UTF-8");
+        String paramValue2 = URLEncoder.encode(value2, "UTF-8");
+        String query = paramName1 + "=" + paramValue11 + "&" + paramName1 + "=" + paramValue12 + "&" + paramName2 + "=" + paramValue2;
+        ContentResponse response = client.GET(scheme + "://localhost:" + connector.getLocalPort() + "/?" + query);
+
+        Assert.assertNotNull(response);
+        Assert.assertEquals(200, response.getStatus());
+        String content = new String(response.getContent(), "UTF-8");
+        Assert.assertEquals(value11 + value12 + value2, content);
+    }
+
+    @Test
+    public void testPOSTWithParameters() throws Exception
+    {
+        final String paramName = "a";
+        final String paramValue = "\u20AC";
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                String value = request.getParameter(paramName);
+                if (paramValue.equals(value))
+                {
+                    response.setCharacterEncoding("UTF-8");
+                    response.setContentType("text/plain");
+                    response.getOutputStream().print(value);
+                }
+            }
+        });
+
+        ContentResponse response = client.POST(scheme + "://localhost:" + connector.getLocalPort())
+                .param(paramName, paramValue)
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        Assert.assertNotNull(response);
+        Assert.assertEquals(200, response.getStatus());
+        Assert.assertEquals(paramValue, new String(response.getContent(), "UTF-8"));
+    }
+
+    @Test
+    public void testPOSTWithQueryString() throws Exception
+    {
+        final String paramName = "a";
+        final String paramValue = "\u20AC";
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                String value = request.getParameter(paramName);
+                if (paramValue.equals(value))
+                {
+                    response.setCharacterEncoding("UTF-8");
+                    response.setContentType("text/plain");
+                    response.getOutputStream().print(value);
+                }
+            }
+        });
+
+        String uri = scheme + "://localhost:" + connector.getLocalPort() +
+                "/?" + paramName + "=" + URLEncoder.encode(paramValue, "UTF-8");
+        ContentResponse response = client.POST(uri)
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        Assert.assertNotNull(response);
+        Assert.assertEquals(200, response.getStatus());
+        Assert.assertEquals(paramValue, new String(response.getContent(), "UTF-8"));
+    }
+
+    @Test
+    public void testPUTWithParameters() throws Exception
+    {
+        final String paramName = "a";
+        final String paramValue = "\u20AC";
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                String value = request.getParameter(paramName);
+                if (paramValue.equals(value))
+                {
+                    response.setCharacterEncoding("UTF-8");
+                    response.setContentType("text/plain");
+                    response.getOutputStream().print(value);
+                }
+            }
+        });
+
+        URI uri = URI.create(scheme + "://localhost:" + connector.getLocalPort() + "/path?" + paramName + "=" + paramValue);
+        ContentResponse response = client.newRequest(uri)
+                .method(HttpMethod.PUT)
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        Assert.assertNotNull(response);
+        Assert.assertEquals(200, response.getStatus());
+        Assert.assertEquals(paramValue, new String(response.getContent(), "UTF-8"));
+    }
+
+    @Test
+    public void testPOSTWithParametersWithContent() throws Exception
+    {
+        final byte[] content = {0, 1, 2, 3};
+        final String paramName = "a";
+        final String paramValue = "\u20AC";
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                String value = request.getParameter(paramName);
+                if (paramValue.equals(value))
+                {
+                    response.setCharacterEncoding("UTF-8");
+                    response.setContentType("application/octet-stream");
+                    IO.copy(request.getInputStream(), response.getOutputStream());
+                }
+            }
+        });
+
+        for (int i = 0; i < 256; ++i)
+        {
+            ContentResponse response = client.POST(scheme + "://localhost:" + connector.getLocalPort() + "/?b=1")
+                    .param(paramName, paramValue)
+                    .content(new BytesContentProvider(content))
+                    .timeout(5, TimeUnit.SECONDS)
+                    .send();
+
+            Assert.assertNotNull(response);
+            Assert.assertEquals(200, response.getStatus());
+            Assert.assertArrayEquals(content, response.getContent());
+        }
+    }
+
+    @Test
+    public void testPOSTWithContentNotifiesRequestContentListener() throws Exception
+    {
+        final byte[] content = {0, 1, 2, 3};
+        start(new EmptyServerHandler());
+
+        ContentResponse response = client.POST(scheme + "://localhost:" + connector.getLocalPort())
+                .onRequestContent(new Request.ContentListener()
+                {
+                    @Override
+                    public void onContent(Request request, ByteBuffer buffer)
+                    {
+                        byte[] bytes = new byte[buffer.remaining()];
+                        buffer.get(bytes);
+                        if (!Arrays.equals(content, bytes))
+                            request.abort(new Exception());
+                    }
+                })
+                .content(new BytesContentProvider(content))
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        Assert.assertNotNull(response);
+        Assert.assertEquals(200, response.getStatus());
+    }
+
+    @Test
+    public void testPOSTWithContentTracksProgress() throws Exception
+    {
+        start(new EmptyServerHandler());
+
+        final AtomicInteger progress = new AtomicInteger();
+        ContentResponse response = client.POST(scheme + "://localhost:" + connector.getLocalPort())
+                .onRequestContent(new Request.ContentListener()
+                {
+                    @Override
+                    public void onContent(Request request, ByteBuffer buffer)
+                    {
+                        byte[] bytes = new byte[buffer.remaining()];
+                        Assert.assertEquals(1, bytes.length);
+                        buffer.get(bytes);
+                        Assert.assertEquals(bytes[0], progress.getAndIncrement());
+                    }
+                })
+                .content(new BytesContentProvider(new byte[]{0}, new byte[]{1}, new byte[]{2}, new byte[]{3}, new byte[]{4}))
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        Assert.assertNotNull(response);
+        Assert.assertEquals(200, response.getStatus());
+        Assert.assertEquals(5, progress.get());
+    }
+
+    @Test
+    public void testGZIPContentEncoding() throws Exception
+    {
+        final byte[] data = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                response.setHeader("Content-Encoding", "gzip");
+                GZIPOutputStream gzipOutput = new GZIPOutputStream(response.getOutputStream());
+                gzipOutput.write(data);
+                gzipOutput.finish();
+            }
+        });
+
+        ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        Assert.assertEquals(200, response.getStatus());
+        Assert.assertArrayEquals(data, response.getContent());
+    }
+
+    @Slow
+    @Test
+    public void testRequestIdleTimeout() throws Exception
+    {
+        final long idleTimeout = 1000;
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                try
+                {
+                    baseRequest.setHandled(true);
+                    TimeUnit.MILLISECONDS.sleep(2 * idleTimeout);
+                }
+                catch (InterruptedException x)
+                {
+                    throw new ServletException(x);
+                }
+            }
+        });
+
+        final String host = "localhost";
+        final int port = connector.getLocalPort();
+        try
+        {
+            client.newRequest(host, port)
+                    .scheme(scheme)
+                    .idleTimeout(idleTimeout, TimeUnit.MILLISECONDS)
+                    .timeout(3 * idleTimeout, TimeUnit.MILLISECONDS)
+                    .send();
+            Assert.fail();
+        }
+        catch (ExecutionException expected)
+        {
+            Assert.assertTrue(expected.getCause() instanceof TimeoutException);
+        }
+
+        // Make another request without specifying the idle timeout, should not fail
+        ContentResponse response = client.newRequest(host, port)
+                .scheme(scheme)
+                .timeout(3 * idleTimeout, TimeUnit.MILLISECONDS)
+                .send();
+
+        Assert.assertNotNull(response);
+        Assert.assertEquals(200, response.getStatus());
+    }
+    
+    @Test
+    public void testConnectionIdleTimeout() throws Exception
+    {
+        final long idleTimeout = 1000;
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                try
+                {
+                    baseRequest.setHandled(true);
+                    TimeUnit.MILLISECONDS.sleep(2 * idleTimeout);
+                }
+                catch (InterruptedException x)
+                {
+                    throw new ServletException(x);
+                }
+            }
+        });
+
+        connector.setIdleTimeout(idleTimeout);
+
+        try
+        {
+            client.newRequest("localhost", connector.getLocalPort())
+                    .scheme(scheme)
+                    .idleTimeout(4 * idleTimeout, TimeUnit.MILLISECONDS)
+                    .timeout(3 * idleTimeout, TimeUnit.MILLISECONDS)
+                    .send();
+            Assert.fail();
+        }
+        catch (ExecutionException x)
+        {
+            Assert.assertTrue(x.getCause() instanceof EOFException);
+        }
+
+        connector.setIdleTimeout(5 * idleTimeout);
+
+        // Make another request to be sure the connection is recreated
+        ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .idleTimeout(4 * idleTimeout, TimeUnit.MILLISECONDS)
+                .timeout(3 * idleTimeout, TimeUnit.MILLISECONDS)
+                .send();
+
+        Assert.assertNotNull(response);
+        Assert.assertEquals(200, response.getStatus());
+    }
+
+    @Test
+    public void testSendToIPv6Address() throws Exception
+    {
+        start(new EmptyServerHandler());
+
+        ContentResponse response = client.newRequest("[::1]", connector.getLocalPort())
+                .scheme(scheme)
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        Assert.assertNotNull(response);
+        Assert.assertEquals(200, response.getStatus());
+    }
+
+    @Test
+    public void testHEADWithResponseContentLength() throws Exception
+    {
+        final int length = 1024;
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                response.getOutputStream().write(new byte[length]);
+            }
+        });
+
+        // HEAD requests receive a Content-Length header, but do not
+        // receive the content so they must handle this case properly
+        ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .method(HttpMethod.HEAD)
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        Assert.assertNotNull(response);
+        Assert.assertEquals(200, response.getStatus());
+        Assert.assertEquals(0, response.getContent().length);
+
+        // Perform a normal GET request to be sure the content is now read
+        response = client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        Assert.assertNotNull(response);
+        Assert.assertEquals(200, response.getStatus());
+        Assert.assertEquals(length, response.getContent().length);
+    }
+
+    @Test
+    public void testLongPollIsAbortedWhenClientIsStopped() throws Exception
+    {
+        final CountDownLatch latch = new CountDownLatch(1);
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                request.startAsync();
+                latch.countDown();
+            }
+        });
+
+        final CountDownLatch completeLatch = new CountDownLatch(1);
+        client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .send(new Response.CompleteListener()
+                {
+                    @Override
+                    public void onComplete(Result result)
+                    {
+                        if (result.isFailed())
+                            completeLatch.countDown();
+                    }
+                });
+
+        Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+
+        // Stop the client, the complete listener must be invoked.
+        client.stop();
+
+        Assert.assertTrue(completeLatch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testEarlyEOF() throws Exception
+    {
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                // Promise some content, then flush the headers, then fail to send the content.
+                response.setContentLength(16);
+                response.flushBuffer();
+                throw new NullPointerException("Explicitly thrown by test");
+            }
+        });
+
+        try
+        {
+            client.newRequest("localhost", connector.getLocalPort())
+                    .scheme(scheme)
+                    .timeout(5, TimeUnit.SECONDS)
+                    .send();
+            Assert.fail();
+        }
+        catch (ExecutionException x)
+        {
+            // Expected.
+        }
+    }
+
+    @Test
+    public void testSmallContentDelimitedByEOFWithSlowRequest() throws Exception
+    {
+        testContentDelimitedByEOFWithSlowRequest(1024);
+    }
+
+    @Test
+    public void testBigContentDelimitedByEOFWithSlowRequest() throws Exception
+    {
+        testContentDelimitedByEOFWithSlowRequest(128 * 1024);
+    }
+
+    private void testContentDelimitedByEOFWithSlowRequest(int length) throws Exception
+    {
+        final byte[] data = new byte[length];
+        new Random().nextBytes(data);
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                response.setHeader("Connection", "close");
+                response.getOutputStream().write(data);
+            }
+        });
+
+        DeferredContentProvider content = new DeferredContentProvider(ByteBuffer.wrap(new byte[]{0}));
+        Request request = client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .content(content);
+        FutureResponseListener listener = new FutureResponseListener(request);
+        request.send(listener);
+        // Wait some time to simulate a slow request.
+        Thread.sleep(1000);
+        content.close();
+
+        ContentResponse response = listener.get(5, TimeUnit.SECONDS);
+
+        Assert.assertEquals(200, response.getStatus());
+        Assert.assertArrayEquals(data, response.getContent());
+    }
+
+    @Test
+    public void testSmallAsyncContent() throws Exception
+    {
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                ServletOutputStream output = response.getOutputStream();
+                output.write(65);
+                output.flush();
+                output.write(66);
+            }
+        });
+
+        final AtomicInteger contentCount = new AtomicInteger();
+        final AtomicReference<Callback> callbackRef = new AtomicReference<>();
+        final AtomicReference<CountDownLatch> contentLatch = new AtomicReference<>(new CountDownLatch(1));
+        final CountDownLatch completeLatch = new CountDownLatch(1);
+        client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .onResponseContentAsync(new Response.AsyncContentListener()
+                {
+                    @Override
+                    public void onContent(Response response, ByteBuffer content, Callback callback)
+                    {
+                        contentCount.incrementAndGet();
+                        callbackRef.set(callback);
+                        contentLatch.get().countDown();
+                    }
+                })
+                .send(new Response.CompleteListener()
+                {
+                    @Override
+                    public void onComplete(Result result)
+                    {
+                        completeLatch.countDown();
+                    }
+                });
+
+        Assert.assertTrue(contentLatch.get().await(5, TimeUnit.SECONDS));
+        Callback callback = callbackRef.get();
+
+        // Wait a while to be sure that the parsing does not proceed.
+        TimeUnit.MILLISECONDS.sleep(1000);
+
+        Assert.assertEquals(1, contentCount.get());
+
+        // Succeed the content callback to proceed with parsing.
+        callbackRef.set(null);
+        contentLatch.set(new CountDownLatch(1));
+        callback.succeeded();
+
+        Assert.assertTrue(contentLatch.get().await(5, TimeUnit.SECONDS));
+        callback = callbackRef.get();
+
+        // Wait a while to be sure that the parsing does not proceed.
+        TimeUnit.MILLISECONDS.sleep(1000);
+
+        Assert.assertEquals(2, contentCount.get());
+        Assert.assertEquals(1, completeLatch.getCount());
+
+        // Succeed the content callback to proceed with parsing.
+        callbackRef.set(null);
+        contentLatch.set(new CountDownLatch(1));
+        callback.succeeded();
+
+        Assert.assertTrue(completeLatch.await(5, TimeUnit.SECONDS));
+        Assert.assertEquals(2, contentCount.get());
+    }
+}
diff --git a/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/proxy/DrupalSPDYFastCGIProxyServer.java b/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/proxy/DrupalSPDYFastCGIProxyServer.java
new file mode 100644
index 0000000..948997d
--- /dev/null
+++ b/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/proxy/DrupalSPDYFastCGIProxyServer.java
@@ -0,0 +1,77 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.fcgi.server.proxy;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.servlet.DefaultServlet;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jetty.spdy.api.SPDY;
+import org.eclipse.jetty.spdy.server.http.HTTPSPDYServerConnector;
+import org.eclipse.jetty.spdy.server.http.PushStrategy;
+import org.eclipse.jetty.spdy.server.http.ReferrerPushStrategy;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+
+public class DrupalSPDYFastCGIProxyServer
+{
+    public static void main(String[] args) throws Exception
+    {
+        SslContextFactory sslContextFactory = new SslContextFactory();
+        sslContextFactory.setEndpointIdentificationAlgorithm("");
+        sslContextFactory.setKeyStorePath("src/test/resources/keystore.jks");
+        sslContextFactory.setKeyStorePassword("storepwd");
+        sslContextFactory.setTrustStorePath("src/test/resources/truststore.jks");
+        sslContextFactory.setTrustStorePassword("storepwd");
+
+        Server server = new Server();
+
+        Map<Short, PushStrategy> pushStrategies = new HashMap<>();
+        pushStrategies.put(SPDY.V3, new ReferrerPushStrategy());
+        HTTPSPDYServerConnector connector = new HTTPSPDYServerConnector(server, sslContextFactory, pushStrategies);
+        connector.setPort(8443);
+        server.addConnector(connector);
+
+        // Drupal seems to only work on the root context,
+        // at least out of the box without additional plugins
+
+        String root = "/home/simon/programs/drupal-7.23";
+
+        ServletContextHandler context = new ServletContextHandler(server, "/");
+        context.setResourceBase(root);
+        context.setWelcomeFiles(new String[]{"index.php"});
+
+        // Serve static resources
+        ServletHolder defaultServlet = new ServletHolder(DefaultServlet.class);
+        defaultServlet.setName("default");
+        context.addServlet(defaultServlet, "/");
+
+        // FastCGI
+        ServletHolder fcgiServlet = new ServletHolder(FastCGIProxyServlet.class);
+        fcgiServlet.setInitParameter(FastCGIProxyServlet.SCRIPT_ROOT_INIT_PARAM, root);
+        fcgiServlet.setInitParameter("proxyTo", "http://localhost:9000");
+        fcgiServlet.setInitParameter("prefix", "/");
+        fcgiServlet.setInitParameter(FastCGIProxyServlet.SCRIPT_PATTERN_INIT_PARAM, "(.+\\.php)");
+        context.addServlet(fcgiServlet, "*.php");
+
+        server.start();
+    }
+}
diff --git a/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/proxy/FastCGIProxyServletTest.java b/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/proxy/FastCGIProxyServletTest.java
new file mode 100644
index 0000000..e326d42
--- /dev/null
+++ b/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/proxy/FastCGIProxyServletTest.java
@@ -0,0 +1,174 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.fcgi.server.proxy;
+
+import java.io.IOException;
+import java.net.URI;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Random;
+import java.util.concurrent.TimeUnit;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.client.util.FutureResponseListener;
+import org.eclipse.jetty.fcgi.server.ServerFCGIConnectionFactory;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jetty.util.Callback;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+ at RunWith(Parameterized.class)
+public class FastCGIProxyServletTest
+{
+    @Parameterized.Parameters
+    public static Collection<Object[]> parameters()
+    {
+        return Arrays.asList(new Object[]{true}, new Object[]{false});
+    }
+
+    private final boolean sendStatus200;
+    private Server server;
+    private ServerConnector httpConnector;
+    private ServerConnector fcgiConnector;
+    private HttpClient client;
+
+    public FastCGIProxyServletTest(boolean sendStatus200)
+    {
+        this.sendStatus200 = sendStatus200;
+    }
+
+    public void prepare(HttpServlet servlet) throws Exception
+    {
+        server = new Server();
+        httpConnector = new ServerConnector(server);
+        server.addConnector(httpConnector);
+
+        fcgiConnector = new ServerConnector(server, new ServerFCGIConnectionFactory(new HttpConfiguration(), sendStatus200));
+        server.addConnector(fcgiConnector);
+
+        final String contextPath = "/";
+        ServletContextHandler context = new ServletContextHandler(server, contextPath);
+
+        final String servletPath = "/script";
+        FastCGIProxyServlet fcgiServlet = new FastCGIProxyServlet()
+        {
+            @Override
+            protected URI rewriteURI(HttpServletRequest request)
+            {
+                return URI.create("http://localhost:" + fcgiConnector.getLocalPort() + servletPath + request.getServletPath());
+            }
+        };
+        ServletHolder fcgiServletHolder = new ServletHolder(fcgiServlet);
+        context.addServlet(fcgiServletHolder, "*.php");
+        fcgiServletHolder.setInitParameter(FastCGIProxyServlet.SCRIPT_ROOT_INIT_PARAM, "/scriptRoot");
+        fcgiServletHolder.setInitParameter("proxyTo", "http://localhost");
+        fcgiServletHolder.setInitParameter(FastCGIProxyServlet.SCRIPT_PATTERN_INIT_PARAM, "(.+?\\.php)");
+
+        context.addServlet(new ServletHolder(servlet), servletPath + "/*");
+
+        client = new HttpClient();
+        server.addBean(client);
+
+        server.start();
+    }
+
+    @After
+    public void dispose() throws Exception
+    {
+        server.stop();
+    }
+
+    @Test
+    public void testGETWithSmallResponseContent() throws Exception
+    {
+        testGETWithResponseContent(1024, 0);
+    }
+
+    @Test
+    public void testGETWithLargeResponseContent() throws Exception
+    {
+        testGETWithResponseContent(16 * 1024 * 1024, 0);
+    }
+
+    @Test
+    public void testGETWithLargeResponseContentWithSlowClient() throws Exception
+    {
+        testGETWithResponseContent(16 * 1024 * 1024, 1);
+    }
+
+    private void testGETWithResponseContent(int length, final long delay) throws Exception
+    {
+        final byte[] data = new byte[length];
+        new Random().nextBytes(data);
+
+        final String path = "/foo/index.php";
+        prepare(new HttpServlet()
+        {
+            @Override
+            protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+            {
+                Assert.assertTrue(req.getRequestURI().endsWith(path));
+                resp.setContentLength(data.length);
+                resp.getOutputStream().write(data);
+            }
+        });
+
+        Request request = client.newRequest("localhost", httpConnector.getLocalPort())
+                .onResponseContentAsync(new Response.AsyncContentListener()
+                {
+                    @Override
+                    public void onContent(Response response, ByteBuffer content, Callback callback)
+                    {
+                        try
+                        {
+                            if (delay > 0)
+                                TimeUnit.MILLISECONDS.sleep(delay);
+                            callback.succeeded();
+                        }
+                        catch (InterruptedException x)
+                        {
+                            callback.failed(x);
+                        }
+                    }
+                })
+                .path(path);
+        FutureResponseListener listener = new FutureResponseListener(request, length);
+        request.send(listener);
+
+        ContentResponse response = listener.get(30, TimeUnit.SECONDS);
+
+        Assert.assertEquals(200, response.getStatus());
+        Assert.assertArrayEquals(data, response.getContent());
+    }
+}
diff --git a/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/proxy/TryFilesFilterTest.java b/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/proxy/TryFilesFilterTest.java
new file mode 100644
index 0000000..c1b9f2e
--- /dev/null
+++ b/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/proxy/TryFilesFilterTest.java
@@ -0,0 +1,107 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.fcgi.server.proxy;
+
+import java.io.IOException;
+import java.util.EnumSet;
+import javax.servlet.DispatcherType;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.servlet.FilterHolder;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class TryFilesFilterTest
+{
+    private Server server;
+    private ServerConnector connector;
+    private ServerConnector sslConnector;
+    private HttpClient client;
+    private String forwardPath;
+
+    public void prepare(HttpServlet servlet) throws Exception
+    {
+        server = new Server();
+        connector = new ServerConnector(server);
+        server.addConnector(connector);
+
+        SslContextFactory sslContextFactory = new SslContextFactory();
+        sslContextFactory.setEndpointIdentificationAlgorithm("");
+        sslContextFactory.setKeyStorePath("src/test/resources/keystore.jks");
+        sslContextFactory.setKeyStorePassword("storepwd");
+        sslContextFactory.setTrustStorePath("src/test/resources/truststore.jks");
+        sslContextFactory.setTrustStorePassword("storepwd");
+        sslConnector = new ServerConnector(server, sslContextFactory);
+        server.addConnector(sslConnector);
+
+        ServletContextHandler context = new ServletContextHandler(server, "/");
+
+        FilterHolder filterHolder = context.addFilter(TryFilesFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST));
+        forwardPath = "/index.php";
+        filterHolder.setInitParameter(TryFilesFilter.FILES_INIT_PARAM, "$path " + forwardPath + "?p=$path");
+
+        context.addServlet(new ServletHolder(servlet), "/*");
+
+        client = new HttpClient(sslContextFactory);
+        server.addBean(client);
+
+        server.start();
+    }
+
+    @After
+    public void dispose() throws Exception
+    {
+        server.stop();
+    }
+
+    @Test
+    public void testHTTPSRequestIsForwarded() throws Exception
+    {
+        final String path = "/one/";
+        prepare(new HttpServlet()
+        {
+            @Override
+            protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+            {
+                Assert.assertTrue("https".equalsIgnoreCase(req.getScheme()));
+                Assert.assertTrue(req.isSecure());
+                Assert.assertEquals(forwardPath, req.getRequestURI());
+                Assert.assertTrue(req.getQueryString().endsWith(path));
+            }
+        });
+
+        ContentResponse response = client.newRequest("localhost", sslConnector.getLocalPort())
+                .scheme("https")
+                .path(path)
+                .send();
+
+        Assert.assertEquals(200, response.getStatus());
+    }
+}
diff --git a/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/proxy/WordPressSPDYFastCGIProxyServer.java b/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/proxy/WordPressSPDYFastCGIProxyServer.java
new file mode 100644
index 0000000..716227a
--- /dev/null
+++ b/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/proxy/WordPressSPDYFastCGIProxyServer.java
@@ -0,0 +1,85 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.fcgi.server.proxy;
+
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.Map;
+import javax.servlet.DispatcherType;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.servlet.DefaultServlet;
+import org.eclipse.jetty.servlet.FilterHolder;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jetty.spdy.api.SPDY;
+import org.eclipse.jetty.spdy.server.http.HTTPSPDYServerConnector;
+import org.eclipse.jetty.spdy.server.http.PushStrategy;
+import org.eclipse.jetty.spdy.server.http.ReferrerPushStrategy;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+
+public class WordPressSPDYFastCGIProxyServer
+{
+    public static void main(String[] args) throws Exception
+    {
+        int port = 8080;
+        int tlsPort = 8443;
+
+        SslContextFactory sslContextFactory = new SslContextFactory();
+        sslContextFactory.setEndpointIdentificationAlgorithm("");
+        sslContextFactory.setKeyStorePath("src/test/resources/keystore.jks");
+        sslContextFactory.setKeyStorePassword("storepwd");
+        sslContextFactory.setTrustStorePath("src/test/resources/truststore.jks");
+        sslContextFactory.setTrustStorePassword("storepwd");
+
+        Server server = new Server();
+
+        Map<Short, PushStrategy> pushStrategies = new HashMap<>();
+        pushStrategies.put(SPDY.V3, new ReferrerPushStrategy());
+        HTTPSPDYServerConnector tlsConnector = new HTTPSPDYServerConnector(server, sslContextFactory, pushStrategies);
+        tlsConnector.setPort(tlsPort);
+        server.addConnector(tlsConnector);
+        HTTPSPDYServerConnector connector = new HTTPSPDYServerConnector(server, null, pushStrategies);
+        connector.setPort(port);
+        server.addConnector(connector);
+
+        String root = "/home/simon/programs/wordpress-3.7.1";
+
+        ServletContextHandler context = new ServletContextHandler(server, "/wp");
+        context.setResourceBase(root);
+        context.setWelcomeFiles(new String[]{"index.php"});
+
+        // Serve static resources
+        ServletHolder defaultServlet = new ServletHolder("default", DefaultServlet.class);
+        context.addServlet(defaultServlet, "/");
+
+        FilterHolder tryFilesFilter = context.addFilter(TryFilesFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST));
+//        tryFilesFilter.setInitParameter(TryFilesFilter.FILES_INIT_PARAM, "$path $path/index.php"); // Permalink /?p=123
+        tryFilesFilter.setInitParameter(TryFilesFilter.FILES_INIT_PARAM, "$path /index.php?p=$path"); // Permalink /%year%/%monthnum%/%postname%
+
+        // FastCGI
+        ServletHolder fcgiServlet = context.addServlet(FastCGIProxyServlet.class, "*.php");
+        fcgiServlet.setInitParameter(FastCGIProxyServlet.SCRIPT_ROOT_INIT_PARAM, root);
+        fcgiServlet.setInitParameter("proxyTo", "http://localhost:9000");
+        fcgiServlet.setInitParameter("prefix", "/");
+        fcgiServlet.setInitParameter(FastCGIProxyServlet.SCRIPT_PATTERN_INIT_PARAM, "(.+?\\.php)");
+
+        server.start();
+    }
+}
diff --git a/jetty-fcgi/fcgi-server/src/test/resources/jetty-logging.properties b/jetty-fcgi/fcgi-server/src/test/resources/jetty-logging.properties
new file mode 100644
index 0000000..b8df62d
--- /dev/null
+++ b/jetty-fcgi/fcgi-server/src/test/resources/jetty-logging.properties
@@ -0,0 +1,3 @@
+org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
+#org.eclipse.jetty.client.LEVEL=DEBUG
+#org.eclipse.jetty.fcgi.LEVEL=DEBUG
diff --git a/jetty-spdy/spdy-jetty-http/src/test/resources/keystore.jks b/jetty-fcgi/fcgi-server/src/test/resources/keystore.jks
similarity index 100%
rename from jetty-spdy/spdy-jetty-http/src/test/resources/keystore.jks
rename to jetty-fcgi/fcgi-server/src/test/resources/keystore.jks
diff --git a/jetty-spdy/spdy-jetty-http/src/test/resources/truststore.jks b/jetty-fcgi/fcgi-server/src/test/resources/truststore.jks
similarity index 100%
rename from jetty-spdy/spdy-jetty-http/src/test/resources/truststore.jks
rename to jetty-fcgi/fcgi-server/src/test/resources/truststore.jks
diff --git a/jetty-fcgi/pom.xml b/jetty-fcgi/pom.xml
new file mode 100644
index 0000000..c11bf0f
--- /dev/null
+++ b/jetty-fcgi/pom.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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">
+    <parent>
+        <groupId>org.eclipse.jetty</groupId>
+        <artifactId>jetty-project</artifactId>
+        <version>9.2.13.v20150730</version>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>org.eclipse.jetty.fcgi</groupId>
+    <artifactId>fcgi-parent</artifactId>
+    <packaging>pom</packaging>
+    <name>Jetty :: FastCGI :: Parent</name>
+
+    <modules>
+        <module>fcgi-client</module>
+        <module>fcgi-server</module>
+    </modules>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.eclipse.jetty.toolchain</groupId>
+            <artifactId>jetty-test-helper</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+</project>
diff --git a/jetty-http-spi/pom.xml b/jetty-http-spi/pom.xml
index ac36c19..cea791f 100644
--- a/jetty-http-spi/pom.xml
+++ b/jetty-http-spi/pom.xml
@@ -2,7 +2,7 @@
   <parent>
     <groupId>org.eclipse.jetty</groupId>
     <artifactId>jetty-project</artifactId>
-    <version>8.1.17.v20150415</version>
+    <version>9.2.13.v20150730</version>
   </parent>
   <modelVersion>4.0.0</modelVersion>
   <artifactId>jetty-http-spi</artifactId>
@@ -13,8 +13,8 @@
   </properties>
   <dependencies>
     <dependency>
-      <groupId>junit</groupId>
-      <artifactId>junit</artifactId>
+      <groupId>org.eclipse.jetty.toolchain</groupId>
+      <artifactId>jetty-test-helper</artifactId>
       <scope>test</scope>
     </dependency>
     <dependency>
diff --git a/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/DelegatingThreadPool.java b/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/DelegatingThreadPool.java
new file mode 100644
index 0000000..429c48f
--- /dev/null
+++ b/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/DelegatingThreadPool.java
@@ -0,0 +1,139 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http.spi;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.component.LifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.ThreadPool;
+
+public class DelegatingThreadPool extends ContainerLifeCycle implements ThreadPool
+{
+    private static final Logger LOG = Log.getLogger(DelegatingThreadPool.class);
+    
+    private Executor _executor; // memory barrier provided by start/stop semantics
+
+    public DelegatingThreadPool(Executor executor)
+    {
+        _executor=executor;
+        addBean(_executor);
+    }
+
+    /* ------------------------------------------------------------ */
+    public Executor getExecutor()
+    {
+        return _executor;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public void setExecutor(Executor executor)
+    {
+        if (isRunning())
+            throw new IllegalStateException(getState());
+        updateBean(_executor,executor);
+        _executor=executor;
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public void execute(Runnable job)
+    {
+        _executor.execute(job);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public int getIdleThreads()
+    {
+        final Executor executor=_executor;
+        if (executor instanceof ThreadPool)
+            return ((ThreadPool)executor).getIdleThreads();
+        
+        if (executor instanceof ThreadPoolExecutor)
+        {
+            final ThreadPoolExecutor tpe = (ThreadPoolExecutor)executor;
+            return tpe.getPoolSize() - tpe.getActiveCount();
+        }
+        return -1;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public int getThreads()
+    {
+        final Executor executor=_executor;
+        if (executor instanceof ThreadPool)
+            return ((ThreadPool)executor).getThreads();
+        
+        if (executor instanceof ThreadPoolExecutor)
+        {
+            final ThreadPoolExecutor tpe = (ThreadPoolExecutor)executor;
+            return tpe.getPoolSize();
+        }
+        return -1;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean isLowOnThreads()
+    {
+        final Executor executor=_executor;
+        if (executor instanceof ThreadPool)
+            return ((ThreadPool)executor).isLowOnThreads();
+        
+        if (executor instanceof ThreadPoolExecutor)
+        {
+            final ThreadPoolExecutor tpe = (ThreadPoolExecutor)executor;
+            // getActiveCount() locks the thread pool, so execute it last
+            return tpe.getPoolSize() == tpe.getMaximumPoolSize() &&
+                    tpe.getQueue().size() >= tpe.getPoolSize() - tpe.getActiveCount();
+        }
+        return false;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void join() throws InterruptedException
+    {
+        final Executor executor=_executor;
+        if (executor instanceof ThreadPool)
+            ((ThreadPool)executor).join();
+        else if (executor instanceof ExecutorService)
+            ((ExecutorService)executor).awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
+        else
+            throw new IllegalStateException();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void doStop() throws Exception
+    {
+        super.doStop();
+        if (!(_executor instanceof LifeCycle) && (_executor instanceof ExecutorService))
+            ((ExecutorService)_executor).shutdownNow();
+    }
+
+}
diff --git a/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/HttpSpiContextHandler.java b/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/HttpSpiContextHandler.java
index f09f2fb..ea32d9e 100644
--- a/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/HttpSpiContextHandler.java
+++ b/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/HttpSpiContextHandler.java
@@ -29,10 +29,12 @@ import javax.servlet.http.HttpServletResponse;
 
 import org.eclipse.jetty.server.Request;
 import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
 
 import com.sun.net.httpserver.Authenticator;
 import com.sun.net.httpserver.Authenticator.Result;
-import com.sun.net.httpserver.BasicAuthenticator;
 import com.sun.net.httpserver.HttpContext;
 import com.sun.net.httpserver.HttpExchange;
 import com.sun.net.httpserver.HttpHandler;
@@ -43,7 +45,8 @@ import com.sun.net.httpserver.HttpPrincipal;
  */
 public class HttpSpiContextHandler extends ContextHandler
 {
-
+    public static final Logger LOG = Log.getLogger(HttpSpiContextHandler.class);
+    
     private HttpContext _httpContext;
 
     private HttpHandler _httpHandler;
@@ -88,18 +91,22 @@ public class HttpSpiContextHandler extends ContextHandler
         }
         catch (Exception ex)
         {
+            LOG.debug(ex);
             PrintWriter writer = new PrintWriter(jettyHttpExchange.getResponseBody());
 
             resp.setStatus(500);
             writer.println("<h2>HTTP ERROR: 500</h2>");
             writer.println("<pre>INTERNAL_SERVER_ERROR</pre>");
-            writer.println("<p>RequestURI=" + req.getRequestURI() + "</p>");
-
-            writer.println("<pre>");
-            ex.printStackTrace(writer);
-            writer.println("</pre>");
+            writer.println("<p>RequestURI=" + StringUtil.sanitizeXmlString(req.getRequestURI()) + "</p>");
 
-            writer.println("<p><i><small><a href=\"http://jetty.mortbay.org\">Powered by jetty://</a></small></i></p>");
+            if (LOG.isDebugEnabled())
+            {
+                writer.println("<pre>");
+                ex.printStackTrace(writer);
+                writer.println("</pre>");
+            }
+            
+            writer.println("<p><i><small><a href=\"http://eclipse.org/jetty\">Powered by jetty://</a></small></i></p>");
 
             writer.close();
         }
diff --git a/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpContext.java b/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpContext.java
index aaff2ad..6b8a798 100644
--- a/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpContext.java
+++ b/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpContext.java
@@ -97,8 +97,8 @@ public class JettyHttpContext extends com.sun.net.httpserver.HttpContext
     @Override
     public Authenticator setAuthenticator(Authenticator auth)
     {
-    	Authenticator previous = _authenticator;
-    	_authenticator = auth;
+        Authenticator previous = _authenticator;
+        _authenticator = auth;
         return previous;
     }
 
diff --git a/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpExchange.java b/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpExchange.java
index e831497..4b9510e 100644
--- a/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpExchange.java
+++ b/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpExchange.java
@@ -57,7 +57,7 @@ public class JettyHttpExchange extends HttpExchange implements JettyExchange
 
     /* ------------------------------------------------------------ */
     /**
-     * @see org.eclipse.jetty.http.spi.JettyExchange#getRequestHeaders()
+     * @see JettyHttpExchangeDelegate#getRequestHeaders()
      */
     @Override
     public Headers getRequestHeaders()
@@ -67,7 +67,7 @@ public class JettyHttpExchange extends HttpExchange implements JettyExchange
 
     /* ------------------------------------------------------------ */
     /**
-     * @see org.eclipse.jetty.http.spi.JettyExchange#getResponseHeaders()
+     * @see JettyHttpExchangeDelegate#getResponseHeaders()
      */
     @Override
     public Headers getResponseHeaders()
@@ -77,7 +77,7 @@ public class JettyHttpExchange extends HttpExchange implements JettyExchange
 
     /* ------------------------------------------------------------ */
     /**
-     * @see org.eclipse.jetty.http.spi.JettyExchange#getRequestURI()
+     * @see JettyHttpExchangeDelegate#getRequestURI()
      */
     @Override
     public URI getRequestURI()
@@ -87,7 +87,7 @@ public class JettyHttpExchange extends HttpExchange implements JettyExchange
 
     /* ------------------------------------------------------------ */
     /**
-     * @see org.eclipse.jetty.http.spi.JettyExchange#getRequestMethod()
+     * @see JettyHttpExchangeDelegate#getRequestMethod()
      */
     @Override
     public String getRequestMethod()
@@ -97,7 +97,7 @@ public class JettyHttpExchange extends HttpExchange implements JettyExchange
 
     /* ------------------------------------------------------------ */
     /**
-     * @see org.eclipse.jetty.http.spi.JettyExchange#getHttpContext()
+     * @see JettyHttpExchangeDelegate#getHttpContext()
      */
     @Override
     public HttpContext getHttpContext()
@@ -107,7 +107,7 @@ public class JettyHttpExchange extends HttpExchange implements JettyExchange
 
     /* ------------------------------------------------------------ */
     /**
-     * @see org.eclipse.jetty.http.spi.JettyExchange#close()
+     * @see JettyHttpExchangeDelegate#close()
      */
     @Override
     public void close()
@@ -127,7 +127,7 @@ public class JettyHttpExchange extends HttpExchange implements JettyExchange
 
     /* ------------------------------------------------------------ */
     /**
-     * @see org.eclipse.jetty.http.spi.JettyExchange#getRequestBody()
+     * @see JettyHttpExchangeDelegate#getRequestBody()
      */
     @Override
     public InputStream getRequestBody()
@@ -137,7 +137,7 @@ public class JettyHttpExchange extends HttpExchange implements JettyExchange
 
     /* ------------------------------------------------------------ */
     /**
-     * @see org.eclipse.jetty.http.spi.JettyExchange#getResponseBody()
+     * @see JettyHttpExchangeDelegate#getResponseBody()
      */
     @Override
     public OutputStream getResponseBody()
@@ -147,7 +147,7 @@ public class JettyHttpExchange extends HttpExchange implements JettyExchange
 
     /* ------------------------------------------------------------ */
     /**
-     * @see org.eclipse.jetty.http.spi.JettyExchange#sendResponseHeaders(int, long)
+     * @see JettyHttpExchangeDelegate#sendResponseHeaders(int, long)
      */
     @Override
     public void sendResponseHeaders(int rCode, long responseLength) throws IOException
@@ -157,7 +157,7 @@ public class JettyHttpExchange extends HttpExchange implements JettyExchange
 
     /* ------------------------------------------------------------ */
     /**
-     * @see org.eclipse.jetty.http.spi.JettyExchange#getRemoteAddress()
+     * @see JettyHttpExchangeDelegate#getRemoteAddress()
      */
     @Override
     public InetSocketAddress getRemoteAddress()
@@ -167,7 +167,7 @@ public class JettyHttpExchange extends HttpExchange implements JettyExchange
 
     /* ------------------------------------------------------------ */
     /**
-     * @see org.eclipse.jetty.http.spi.JettyExchange#getResponseCode()
+     * @see JettyHttpExchangeDelegate#getResponseCode()
      */
     @Override
     public int getResponseCode()
@@ -177,7 +177,7 @@ public class JettyHttpExchange extends HttpExchange implements JettyExchange
 
     /* ------------------------------------------------------------ */
     /**
-     * @see org.eclipse.jetty.http.spi.JettyExchange#getLocalAddress()
+     * @see JettyHttpExchangeDelegate#getLocalAddress()
      */
     @Override
     public InetSocketAddress getLocalAddress()
@@ -187,7 +187,7 @@ public class JettyHttpExchange extends HttpExchange implements JettyExchange
 
     /* ------------------------------------------------------------ */
     /**
-     * @see org.eclipse.jetty.http.spi.JettyExchange#getProtocol()
+     * @see JettyHttpExchangeDelegate#getProtocol()
      */
     @Override
     public String getProtocol()
@@ -197,7 +197,7 @@ public class JettyHttpExchange extends HttpExchange implements JettyExchange
 
     /* ------------------------------------------------------------ */
     /**
-     * @see org.eclipse.jetty.http.spi.JettyExchange#getAttribute(java.lang.String)
+     * @see JettyHttpExchangeDelegate#getAttribute(java.lang.String)
      */
     @Override
     public Object getAttribute(String name)
@@ -207,7 +207,7 @@ public class JettyHttpExchange extends HttpExchange implements JettyExchange
 
     /* ------------------------------------------------------------ */
     /**
-     * @see org.eclipse.jetty.http.spi.JettyExchange#setAttribute(java.lang.String, java.lang.Object)
+     * @see JettyHttpExchangeDelegate#setAttribute(java.lang.String, java.lang.Object)
      */
     @Override
     public void setAttribute(String name, Object value)
@@ -217,7 +217,7 @@ public class JettyHttpExchange extends HttpExchange implements JettyExchange
 
     /* ------------------------------------------------------------ */
     /**
-     * @see org.eclipse.jetty.http.spi.JettyExchange#setStreams(java.io.InputStream, java.io.OutputStream)
+     * @see JettyHttpExchangeDelegate#setStreams(java.io.InputStream, java.io.OutputStream)
      */
     @Override
     public void setStreams(InputStream i, OutputStream o)
diff --git a/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpServer.java b/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpServer.java
index 24606d4..73a12ce 100644
--- a/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpServer.java
+++ b/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpServer.java
@@ -1,267 +1,271 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.http.spi;
-
-import com.sun.net.httpserver.HttpContext;
-import com.sun.net.httpserver.HttpHandler;
-import org.eclipse.jetty.server.Connector;
-import org.eclipse.jetty.server.Handler;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.handler.ContextHandler;
-import org.eclipse.jetty.server.handler.HandlerCollection;
-import org.eclipse.jetty.server.handler.ContextHandlerCollection;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
-import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
-
-
-import java.io.IOException;
-import java.net.InetSocketAddress;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.Executor;
-import java.util.concurrent.ThreadPoolExecutor;
-
-/**
- * Jetty implementation of {@link com.sun.net.httpserver.HttpServer}.
- */
-public class JettyHttpServer extends com.sun.net.httpserver.HttpServer
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http.spi;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ThreadPoolExecutor;
+
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.NetworkConnector;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.ContextHandlerCollection;
+import org.eclipse.jetty.server.handler.HandlerCollection;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.ThreadPool;
+
+import com.sun.net.httpserver.HttpContext;
+import com.sun.net.httpserver.HttpHandler;
+
+/**
+ * Jetty implementation of {@link com.sun.net.httpserver.HttpServer}.
+ */
+public class JettyHttpServer extends com.sun.net.httpserver.HttpServer
 {
     private static final Logger LOG = Log.getLogger(JettyHttpServer.class);
-
-
-    private Server _server;
-    
-    private boolean _serverShared;
-
-    private InetSocketAddress _addr;
-
-    private ThreadPoolExecutor _executor;
-
-    private Map<String, JettyHttpContext> _contexts = new HashMap<String, JettyHttpContext>();
-    
-    private Map<String, Connector> _connectors = new HashMap<String, Connector>();
-
-    
-    public JettyHttpServer(Server server, boolean shared)
-    {
-        this._server = server;
-        this._serverShared = shared;
-    }
-
-    @Override
-    public void bind(InetSocketAddress addr, int backlog) throws IOException
-    {
-    	// check if there is already a connector listening
-        Connector[] connectors = _server.getConnectors();
-        if (connectors != null)
-        {
-            for (Connector connector : connectors)
-            {
-                if (connector.getPort() == addr.getPort()) {
-                    if (LOG.isDebugEnabled()) LOG.debug("server already bound to port " + addr.getPort() + ", no need to rebind");
-                    return;
-                }
-            }
-        }
-        
-        if (_serverShared)
-        	throw new IOException("jetty server is not bound to port " + addr.getPort());
-        
-        this._addr = addr;
-
-        if (LOG.isDebugEnabled()) LOG.debug("binding server to port " + addr.getPort());
-        SelectChannelConnector connector = new SelectChannelConnector();
-        connector.setAcceptors(1);
-        connector.setPort(addr.getPort());
-        connector.setHost(addr.getHostName());
-        _server.addConnector(connector);
-        
-        _connectors.put(addr.getHostName() + addr.getPort(), connector);
-    }
-
-    @Override
-    public InetSocketAddress getAddress()
-    {
-        return _addr;
-    }
-
-    @Override
-    public void start()
-    {
-    	if (_serverShared) return;
-    	
-        try
-        {
-            _server.start();
-        }
-        catch (Exception ex)
-        {
-            throw new RuntimeException(ex);
-        }
-    }
-
-    @Override
-    public void setExecutor(Executor executor)
-    {
-        if (executor == null)
-            throw new IllegalArgumentException("missing required 'executor' argument");
-
-    	if (!(executor instanceof ThreadPoolExecutor))
-    		throw new IllegalArgumentException("only java.util.concurrent.ThreadPoolExecutor instances are allowed, got: " + executor.getClass().getName());
-    	
-    	if (LOG.isDebugEnabled()) LOG.debug("using ThreadPoolExecutor for server thread pool");
-    	this._executor = (ThreadPoolExecutor) executor;
-    	_server.setThreadPool(new ThreadPoolExecutorAdapter(_executor));
-    }
-
-    @Override
-    public Executor getExecutor()
-    {
-        return _executor;
-    }
-
-    @Override
-    public void stop(int delay)
-    {
-    	cleanUpContexts();
-    	cleanUpConnectors();
-    	
-    	if (_serverShared) return;
-
-    	try
-        {
-            _server.stop();
-        }
-        catch (Exception ex)
-        {
-            throw new RuntimeException(ex);
-        }
-    }
-
-	private void cleanUpContexts()
-	{
-        for (Map.Entry<String, JettyHttpContext> stringJettyHttpContextEntry : _contexts.entrySet())
-        {
-            JettyHttpContext context = stringJettyHttpContextEntry.getValue();
-            _server.removeBean(context.getJettyContextHandler());
-        }
-		_contexts.clear();
-	}
-
-    private void cleanUpConnectors()
-    {
-        for (Map.Entry<String, Connector> stringConnectorEntry : _connectors.entrySet())
-        {
-            Connector connector = stringConnectorEntry.getValue();
-            try
-            {
-                connector.stop();
-            } catch (Exception ex) {
-                LOG.warn(ex);
-            }
-            _server.removeConnector(connector);
-        }
-		_connectors.clear();
-	}
-
-	@Override
-    public HttpContext createContext(String path, HttpHandler httpHandler)
-    {
-    	checkIfContextIsFree(path);
-
-        JettyHttpContext context = new JettyHttpContext(this, path, httpHandler);
-        HttpSpiContextHandler jettyContextHandler = context.getJettyContextHandler();
-
-        ContextHandlerCollection chc = findContextHandlerCollection(_server.getHandlers());
-        if (chc == null)
-        	throw new RuntimeException("could not find ContextHandlerCollection, you must configure one");
-
-        chc.addHandler(jettyContextHandler);
-        _contexts.put(path, context);
-
-        return context;
-    }
-
-    private ContextHandlerCollection findContextHandlerCollection(Handler[] handlers)
-    {
-        if (handlers == null)
-            return null;
-
-        for (Handler handler : handlers)
-        {
-            if (handler instanceof ContextHandlerCollection)
-            {
-                return (ContextHandlerCollection) handler;
-            }
-
-            if (handler instanceof HandlerCollection)
-            {
-                HandlerCollection hc = (HandlerCollection) handler;
-                ContextHandlerCollection chc = findContextHandlerCollection(hc.getHandlers());
-                if (chc != null)
-                    return chc;
-            }
-        }
-        return null;
-    }
-
-    private void checkIfContextIsFree(String path)
-    {
-    	Handler serverHandler = _server.getHandler();
-		if (serverHandler instanceof ContextHandler)
-		{
-			ContextHandler ctx = (ContextHandler) serverHandler;
-			if (ctx.getContextPath().equals(path))
-	        	throw new RuntimeException("another context already bound to path " + path);
-		}
-    	
-    	Handler[] handlers = _server.getHandlers();
-    	if (handlers == null) return;
-
-        for (Handler handler : handlers)
-        {
-            if (handler instanceof ContextHandler) {
-                ContextHandler ctx = (ContextHandler) handler;
-                if (ctx.getContextPath().equals(path))
-                    throw new RuntimeException("another context already bound to path " + path);
-            }
-        }
-	}
-
-	@Override
-    public HttpContext createContext(String path)
-    {
-        return createContext(path, null);
-    }
-
-    @Override
-    public void removeContext(String path) throws IllegalArgumentException
-    {
-        JettyHttpContext context = _contexts.remove(path);
-        if (context == null) return;
-        _server.removeBean(context.getJettyContextHandler());
-    }
-
-    @Override
-    public void removeContext(HttpContext context)
-    {
-        removeContext(context.getPath());
-    }
-
-}
+
+
+    private Server _server;
+    
+    private boolean _serverShared;
+
+    private InetSocketAddress _addr;
+
+    private ThreadPoolExecutor _executor;
+
+    private Map<String, JettyHttpContext> _contexts = new HashMap<String, JettyHttpContext>();
+    
+    private Map<String, Connector> _connectors = new HashMap<String, Connector>();
+
+    
+    public JettyHttpServer(Server server, boolean shared)
+    {
+        this._server = server;
+        this._serverShared = shared;
+    }
+
+    @Override
+    public void bind(InetSocketAddress addr, int backlog) throws IOException
+    {
+        // check if there is already a connector listening
+        Collection<NetworkConnector> connectors = _server.getBeans(NetworkConnector.class);
+        if (connectors != null)
+        {
+            for (NetworkConnector connector : connectors)
+            {
+                if (connector.getPort() == addr.getPort()) {
+                    if (LOG.isDebugEnabled()) LOG.debug("server already bound to port " + addr.getPort() + ", no need to rebind");
+                    return;
+                }
+            }
+        }
+        
+        if (_serverShared)
+                throw new IOException("jetty server is not bound to port " + addr.getPort());
+        
+        this._addr = addr;
+
+        if (LOG.isDebugEnabled()) LOG.debug("binding server to port " + addr.getPort());
+        ServerConnector connector = new ServerConnector(_server);
+        connector.setPort(addr.getPort());
+        connector.setHost(addr.getHostName());
+        _server.addConnector(connector);
+        
+        _connectors.put(addr.getHostName() + addr.getPort(), connector);
+    }
+
+    @Override
+    public InetSocketAddress getAddress()
+    {
+        return _addr;
+    }
+
+    @Override
+    public void start()
+    {
+        if (_serverShared) return;
+        
+        try
+        {
+            _server.start();
+        }
+        catch (Exception ex)
+        {
+            throw new RuntimeException(ex);
+        }
+    }
+
+    @Override
+    public void setExecutor(Executor executor)
+    {
+        if (executor == null)
+            throw new IllegalArgumentException("missing required 'executor' argument");
+        ThreadPool threadPool = _server.getThreadPool();
+        if (threadPool instanceof DelegatingThreadPool)
+            ((DelegatingThreadPool)_server.getThreadPool()).setExecutor(executor);
+        else
+            throw new UnsupportedOperationException("!DelegatingThreadPool");
+    }
+
+    @Override
+    public Executor getExecutor()
+    {
+        ThreadPool threadPool = _server.getThreadPool();
+        if (threadPool instanceof DelegatingThreadPool)
+            return ((DelegatingThreadPool)_server.getThreadPool()).getExecutor();
+        return threadPool;
+    }
+
+    @Override
+    public void stop(int delay)
+    {
+        cleanUpContexts();
+        cleanUpConnectors();
+        
+        if (_serverShared) return;
+
+        try
+        {
+            _server.stop();
+        }
+        catch (Exception ex)
+        {
+            throw new RuntimeException(ex);
+        }
+    }
+
+        private void cleanUpContexts()
+        {
+        for (Map.Entry<String, JettyHttpContext> stringJettyHttpContextEntry : _contexts.entrySet())
+        {
+            JettyHttpContext context = stringJettyHttpContextEntry.getValue();
+            _server.removeBean(context.getJettyContextHandler());
+        }
+                _contexts.clear();
+        }
+
+    private void cleanUpConnectors()
+    {
+        for (Map.Entry<String, Connector> stringConnectorEntry : _connectors.entrySet())
+        {
+            Connector connector = stringConnectorEntry.getValue();
+            try
+            {
+                connector.stop();
+            } catch (Exception ex) {
+                LOG.warn(ex);
+            }
+            _server.removeConnector(connector);
+        }
+                _connectors.clear();
+        }
+
+        @Override
+    public HttpContext createContext(String path, HttpHandler httpHandler)
+    {
+        checkIfContextIsFree(path);
+
+        JettyHttpContext context = new JettyHttpContext(this, path, httpHandler);
+        HttpSpiContextHandler jettyContextHandler = context.getJettyContextHandler();
+
+        ContextHandlerCollection chc = findContextHandlerCollection(_server.getHandlers());
+        if (chc == null)
+                throw new RuntimeException("could not find ContextHandlerCollection, you must configure one");
+
+        chc.addHandler(jettyContextHandler);
+        _contexts.put(path, context);
+
+        return context;
+    }
+
+    private ContextHandlerCollection findContextHandlerCollection(Handler[] handlers)
+    {
+        if (handlers == null)
+            return null;
+
+        for (Handler handler : handlers)
+        {
+            if (handler instanceof ContextHandlerCollection)
+            {
+                return (ContextHandlerCollection) handler;
+            }
+
+            if (handler instanceof HandlerCollection)
+            {
+                HandlerCollection hc = (HandlerCollection) handler;
+                ContextHandlerCollection chc = findContextHandlerCollection(hc.getHandlers());
+                if (chc != null)
+                    return chc;
+            }
+        }
+        return null;
+    }
+
+    private void checkIfContextIsFree(String path)
+    {
+        Handler serverHandler = _server.getHandler();
+                if (serverHandler instanceof ContextHandler)
+                {
+                        ContextHandler ctx = (ContextHandler) serverHandler;
+                        if (ctx.getContextPath().equals(path))
+                        throw new RuntimeException("another context already bound to path " + path);
+                }
+        
+        Handler[] handlers = _server.getHandlers();
+        if (handlers == null) return;
+
+        for (Handler handler : handlers)
+        {
+            if (handler instanceof ContextHandler) {
+                ContextHandler ctx = (ContextHandler) handler;
+                if (ctx.getContextPath().equals(path))
+                    throw new RuntimeException("another context already bound to path " + path);
+            }
+        }
+        }
+
+        @Override
+    public HttpContext createContext(String path)
+    {
+        return createContext(path, null);
+    }
+
+    @Override
+    public void removeContext(String path) throws IllegalArgumentException
+    {
+        JettyHttpContext context = _contexts.remove(path);
+        if (context == null) return;
+        _server.removeBean(context.getJettyContextHandler());
+    }
+
+    @Override
+    public void removeContext(HttpContext context)
+    {
+        removeContext(context.getPath());
+    }
+
+}
diff --git a/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpServerProvider.java b/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpServerProvider.java
index 5b6717e..9fdcff8 100644
--- a/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpServerProvider.java
+++ b/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpServerProvider.java
@@ -23,9 +23,11 @@ import java.net.InetSocketAddress;
 
 import org.eclipse.jetty.server.Handler;
 import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.handler.ContextHandlerCollection;
 import org.eclipse.jetty.server.handler.DefaultHandler;
 import org.eclipse.jetty.server.handler.HandlerCollection;
-import org.eclipse.jetty.server.handler.ContextHandlerCollection;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.eclipse.jetty.util.thread.ThreadPool;
 
 import com.sun.net.httpserver.HttpServer;
 import com.sun.net.httpserver.HttpsServer;
@@ -41,7 +43,7 @@ public class JettyHttpServerProvider extends HttpServerProvider
 
     public static void setServer(Server server)
     {
-    	_server = server;
+        _server = server;
     }
 
     @Override
@@ -49,15 +51,16 @@ public class JettyHttpServerProvider extends HttpServerProvider
             throws IOException
     {
         Server server = _server;
-    	boolean shared = true;
+        boolean shared = true;
 
         if (server == null)
         {
-        	server = new Server();
-        	
-        	HandlerCollection handlerCollection = new HandlerCollection();
-        	handlerCollection.setHandlers(new Handler[] {new ContextHandlerCollection(), new DefaultHandler()});
-			server.setHandler(handlerCollection);
+            ThreadPool threadPool = new DelegatingThreadPool(new QueuedThreadPool());
+            server = new Server(threadPool);
+
+            HandlerCollection handlerCollection = new HandlerCollection();
+            handlerCollection.setHandlers(new Handler[] {new ContextHandlerCollection(), new DefaultHandler()});
+            server.setHandler(handlerCollection);
 
             shared = false;
         }
diff --git a/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/ThreadPoolExecutorAdapter.java b/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/ThreadPoolExecutorAdapter.java
deleted file mode 100644
index ef2302f..0000000
--- a/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/ThreadPoolExecutorAdapter.java
+++ /dev/null
@@ -1,124 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.http.spi;
-
-import java.util.concurrent.RejectedExecutionException;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
-
-import org.eclipse.jetty.util.component.AbstractLifeCycle;
-import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
-
-import org.eclipse.jetty.util.thread.ThreadPool;
-
-/**
- * Jetty {@link ThreadPool} that bridges requests to a {@link ThreadPoolExecutor}.
- */
-public class ThreadPoolExecutorAdapter extends AbstractLifeCycle implements ThreadPool
-{
-    private static final Logger LOG = Log.getLogger(ThreadPoolExecutorAdapter.class);
-
-	
-	private ThreadPoolExecutor executor;
-	
-	public ThreadPoolExecutorAdapter(ThreadPoolExecutor executor)
-	{
-		this.executor = executor;
-	}
-
-	public boolean dispatch(Runnable job)
-	{
-		try
-        {       
-			executor.execute(job);
-            return true;
-        }
-        catch(RejectedExecutionException e)
-        {
-            LOG.warn(e);
-            return false;
-        }
-	}
-
-	public int getIdleThreads()
-	{
-		return executor.getPoolSize()-executor.getActiveCount();
-	}
-
-	public int getThreads()
-	{
-		return executor.getPoolSize();
-	}
-
-	public boolean isLowOnThreads()
-	{
-		return executor.getActiveCount()>=executor.getMaximumPoolSize();
-	}
-
-	public void join() throws InterruptedException
-	{
-		executor.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
-	}
-
-	
-	
-	public boolean isFailed()
-	{
-		return false;
-	}
-
-	public boolean isRunning()
-	{
-		return !executor.isTerminated() && !executor.isTerminating();
-	}
-
-	public boolean isStarted()
-	{
-		return !executor.isTerminated() && !executor.isTerminating();
-	}
-
-	public boolean isStarting()
-	{
-		return false;
-	}
-
-	public boolean isStopped()
-	{
-		return executor.isTerminated();
-	}
-
-	public boolean isStopping()
-	{
-		return executor.isTerminating();
-	}
-
-	protected void doStart() throws Exception
-	{
-		if (executor.isTerminated() || executor.isTerminating() || executor.isShutdown())
-            throw new IllegalStateException("Cannot restart");
-	}
-
-	protected void doStop() throws Exception
-	{
-		executor.shutdown();
-        if (!executor.awaitTermination(60, TimeUnit.SECONDS))
-        	executor.shutdownNow();
-	}
-
-}
diff --git a/jetty-http/pom.xml b/jetty-http/pom.xml
index f78f901..c1c0f55 100644
--- a/jetty-http/pom.xml
+++ b/jetty-http/pom.xml
@@ -3,7 +3,7 @@
   <parent>
     <artifactId>jetty-project</artifactId>
     <groupId>org.eclipse.jetty</groupId>
-    <version>8.1.17.v20150415</version>
+    <version>9.2.13.v20150730</version>
   </parent>
   <modelVersion>4.0.0</modelVersion>
   <artifactId>jetty-http</artifactId>
@@ -15,17 +15,12 @@
   <dependencies>
     <dependency>
       <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-io</artifactId>
+      <artifactId>jetty-util</artifactId>
       <version>${project.version}</version>
     </dependency>
     <dependency>
-      <groupId>org.eclipse.jetty.orbit</groupId>
-      <artifactId>javax.servlet</artifactId>
-      <scope>provided</scope>
-    </dependency>
-    <dependency>
-      <groupId>junit</groupId>
-      <artifactId>junit</artifactId>
+      <groupId>org.eclipse.jetty.toolchain</groupId>
+      <artifactId>jetty-test-helper</artifactId>
       <scope>test</scope>
     </dependency>
   </dependencies>
@@ -42,7 +37,7 @@
             </goals>
             <configuration>
               <instructions>
-                <Import-Package>javax.servlet.*;version="2.6.0",javax.net.*,*</Import-Package>
+                <Import-Package>javax.servlet.*;version="[2.6.0,3.2)",javax.net.*,*</Import-Package>
               </instructions>
             </configuration>
            </execution>
diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/AbstractGenerator.java b/jetty-http/src/main/java/org/eclipse/jetty/http/AbstractGenerator.java
deleted file mode 100644
index 1aed78b..0000000
--- a/jetty-http/src/main/java/org/eclipse/jetty/http/AbstractGenerator.java
+++ /dev/null
@@ -1,534 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.http;
-
-import java.io.IOException;
-
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.Buffers;
-import org.eclipse.jetty.io.ByteArrayBuffer;
-import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.io.EofException;
-import org.eclipse.jetty.io.View;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-
-/* ------------------------------------------------------------ */
-/**
- * Abstract Generator. Builds HTTP Messages.
- *
- * Currently this class uses a system parameter "jetty.direct.writers" to control
- * two optional writer to byte conversions. buffer.writers=true will probably be
- * faster, but will consume more memory.   This option is just for testing and tuning.
- *
- */
-public abstract class AbstractGenerator implements Generator
-{
-    private static final Logger LOG = Log.getLogger(AbstractGenerator.class);
-
-    // states
-    public final static int STATE_HEADER = 0;
-    public final static int STATE_CONTENT = 2;
-    public final static int STATE_FLUSHING = 3;
-    public final static int STATE_END = 4;
-
-    public static final byte[] NO_BYTES = {};
-
-    // data
-
-    protected final Buffers _buffers; // source of buffers
-    protected final EndPoint _endp;
-
-    protected int _state = STATE_HEADER;
-
-    protected int _status = 0;
-    protected int _version = HttpVersions.HTTP_1_1_ORDINAL;
-    protected  Buffer _reason;
-    protected  Buffer _method;
-    protected  String _uri;
-
-    protected long _contentWritten = 0;
-    protected long _contentLength = HttpTokens.UNKNOWN_CONTENT;
-    protected boolean _last = false;
-    protected boolean _head = false;
-    protected boolean _noContent = false;
-    protected Boolean _persistent = null;
-
-    protected Buffer _header; // Buffer for HTTP header (and maybe small _content)
-    protected Buffer _buffer; // Buffer for copy of passed _content
-    protected Buffer _content; // Buffer passed to addContent
-
-    protected Buffer _date;
-
-    private boolean _sendServerVersion;
-
-
-    /* ------------------------------------------------------------------------------- */
-    /**
-     * Constructor.
-     *
-     * @param buffers buffer pool
-     * @param io the end point
-     */
-    public AbstractGenerator(Buffers buffers, EndPoint io)
-    {
-        this._buffers = buffers;
-        this._endp = io;
-    }
-
-    /* ------------------------------------------------------------------------------- */
-    public abstract boolean isRequest();
-
-    /* ------------------------------------------------------------------------------- */
-    public abstract boolean isResponse();
-
-    /* ------------------------------------------------------------------------------- */
-    public boolean isOpen()
-    {
-        return _endp.isOpen();
-    }
-
-    /* ------------------------------------------------------------------------------- */
-    public void reset()
-    {
-        _state = STATE_HEADER;
-        _status = 0;
-        _version = HttpVersions.HTTP_1_1_ORDINAL;
-        _reason = null;
-        _last = false;
-        _head = false;
-        _noContent=false;
-        _persistent = null;
-        _contentWritten = 0;
-        _contentLength = HttpTokens.UNKNOWN_CONTENT;
-        _date = null;
-
-        _content = null;
-        _method=null;
-    }
-
-    /* ------------------------------------------------------------------------------- */
-    public void returnBuffers()
-    {
-        if (_buffer!=null && _buffer.length()==0)
-        {
-            _buffers.returnBuffer(_buffer);
-            _buffer=null;
-        }
-
-        if (_header!=null && _header.length()==0)
-        {
-            _buffers.returnBuffer(_header);
-            _header=null;
-        }
-    }
-
-    /* ------------------------------------------------------------------------------- */
-    public void resetBuffer()
-    {
-        if(_state>=STATE_FLUSHING)
-            throw new IllegalStateException("Flushed");
-
-        _last = false;
-        _persistent=null;
-        _contentWritten = 0;
-        _contentLength = HttpTokens.UNKNOWN_CONTENT;
-        _content=null;
-        if (_buffer!=null)
-            _buffer.clear();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return Returns the contentBufferSize.
-     */
-    public int getContentBufferSize()
-    {
-        if (_buffer==null)
-            _buffer=_buffers.getBuffer();
-        return _buffer.capacity();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param contentBufferSize The contentBufferSize to set.
-     */
-    public void increaseContentBufferSize(int contentBufferSize)
-    {
-        if (_buffer==null)
-            _buffer=_buffers.getBuffer();
-        if (contentBufferSize > _buffer.capacity())
-        {
-            Buffer nb = _buffers.getBuffer(contentBufferSize);
-            nb.put(_buffer);
-            _buffers.returnBuffer(_buffer);
-            _buffer = nb;
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    public Buffer getUncheckedBuffer()
-    {
-        return _buffer;
-    }
-
-    /* ------------------------------------------------------------ */
-    public boolean getSendServerVersion ()
-    {
-        return _sendServerVersion;
-    }
-
-    /* ------------------------------------------------------------ */
-    public void setSendServerVersion (boolean sendServerVersion)
-    {
-        _sendServerVersion = sendServerVersion;
-    }
-
-    /* ------------------------------------------------------------ */
-    public int getState()
-    {
-        return _state;
-    }
-
-    /* ------------------------------------------------------------ */
-    public boolean isState(int state)
-    {
-        return _state == state;
-    }
-
-    /* ------------------------------------------------------------ */
-    public boolean isComplete()
-    {
-        return _state == STATE_END;
-    }
-
-    /* ------------------------------------------------------------ */
-    public boolean isIdle()
-    {
-        return _state == STATE_HEADER && _method==null && _status==0;
-    }
-
-    /* ------------------------------------------------------------ */
-    public boolean isCommitted()
-    {
-        return _state != STATE_HEADER;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return Returns the head.
-     */
-    public boolean isHead()
-    {
-        return _head;
-    }
-
-    /* ------------------------------------------------------------ */
-    public void setContentLength(long value)
-    {
-        if (value<0)
-            _contentLength=HttpTokens.UNKNOWN_CONTENT;
-        else
-            _contentLength=value;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param head The head to set.
-     */
-    public void setHead(boolean head)
-    {
-        _head = head;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return <code>false</code> if the connection should be closed after a request has been read,
-     * <code>true</code> if it should be used for additional requests.
-     */
-    public boolean isPersistent()
-    {
-        return _persistent!=null
-        ?_persistent.booleanValue()
-        :(isRequest()?true:_version>HttpVersions.HTTP_1_0_ORDINAL);
-    }
-
-    /* ------------------------------------------------------------ */
-    public void setPersistent(boolean persistent)
-    {
-        _persistent=persistent;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param version The version of the client the response is being sent to (NB. Not the version
-     *            in the response, which is the version of the server).
-     */
-    public void setVersion(int version)
-    {
-        if (_state != STATE_HEADER)
-            throw new IllegalStateException("STATE!=START "+_state);
-        _version = version;
-        if (_version==HttpVersions.HTTP_0_9_ORDINAL && _method!=null)
-            _noContent=true;
-    }
-
-    /* ------------------------------------------------------------ */
-    public int getVersion()
-    {
-        return _version;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.http.Generator#setDate(org.eclipse.jetty.io.Buffer)
-     */
-    public void setDate(Buffer timeStampBuffer)
-    {
-        _date=timeStampBuffer;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     */
-    public void setRequest(String method, String uri)
-    {
-        if (method==null || HttpMethods.GET.equals(method) )
-            _method=HttpMethods.GET_BUFFER;
-        else
-            _method=HttpMethods.CACHE.lookup(method);
-        _uri=uri;
-        if (_version==HttpVersions.HTTP_0_9_ORDINAL)
-            _noContent=true;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param status The status code to send.
-     * @param reason the status message to send.
-     */
-    public void setResponse(int status, String reason)
-    {
-        if (_state != STATE_HEADER) throw new IllegalStateException("STATE!=START");
-        _method=null;
-        _status = status;
-        if (reason!=null)
-        {
-            int len=reason.length();
-
-            // TODO don't hard code
-            if (len>1024)
-                len=1024;
-            _reason=new ByteArrayBuffer(len);
-            for (int i=0;i<len;i++)
-            {
-                char ch = reason.charAt(i);
-                if (ch!='\r'&&ch!='\n')
-                    _reason.put((byte)ch);
-                else
-                    _reason.put((byte)' ');
-            }
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    /** Prepare buffer for unchecked writes.
-     * Prepare the generator buffer to receive unchecked writes
-     * @return the available space in the buffer.
-     * @throws IOException
-     */
-    public abstract int prepareUncheckedAddContent() throws IOException;
-
-    /* ------------------------------------------------------------ */
-    void uncheckedAddContent(int b)
-    {
-        _buffer.put((byte)b);
-    }
-
-    /* ------------------------------------------------------------ */
-    public void completeUncheckedAddContent()
-    {
-        if (_noContent)
-        {
-            if(_buffer!=null)
-                _buffer.clear();
-        }
-        else
-        {
-            _contentWritten+=_buffer.length();
-            if (_head)
-                _buffer.clear();
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    public boolean isBufferFull()
-    {
-        if (_buffer != null && _buffer.space()==0)
-        {
-            if (_buffer.length()==0 && !_buffer.isImmutable())
-                _buffer.compact();
-            return _buffer.space()==0;
-        }
-
-        return _content!=null && _content.length()>0;
-    }
-
-    /* ------------------------------------------------------------ */
-    public boolean isWritten()
-    {
-        return _contentWritten>0;
-    }
-
-    /* ------------------------------------------------------------ */
-    public boolean isAllContentWritten()
-    {
-        return _contentLength>=0 && _contentWritten>=_contentLength;
-    }
-
-    /* ------------------------------------------------------------ */
-    public abstract void completeHeader(HttpFields fields, boolean allContentAdded) throws IOException;
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Complete the message.
-     *
-     * @throws IOException
-     */
-    public void complete() throws IOException
-    {
-        if (_state == STATE_HEADER)
-        {
-            throw new IllegalStateException("State==HEADER");
-        }
-
-        if (_contentLength >= 0 && _contentLength != _contentWritten && !_head)
-        {
-            if (LOG.isDebugEnabled())
-                LOG.debug("ContentLength written=="+_contentWritten+" != contentLength=="+_contentLength);
-            _persistent = false;
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    public abstract int flushBuffer() throws IOException;
-
-
-    /* ------------------------------------------------------------ */
-    public void flush(long maxIdleTime) throws IOException
-    {
-        // block until everything is flushed
-        long now=System.currentTimeMillis();
-        long end=now+maxIdleTime;
-        Buffer content = _content;
-        Buffer buffer = _buffer;
-        if (content!=null && content.length()>0 || buffer!=null && buffer.length()>0 || isBufferFull())
-        {
-            flushBuffer();
-
-            while (now<end && (content!=null && content.length()>0 ||buffer!=null && buffer.length()>0))
-            {
-            	if (!_endp.isOpen() || _endp.isOutputShutdown())
-            		throw new EofException();
-                blockForOutput(end-now);
-                now=System.currentTimeMillis();
-            }
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Utility method to send an error response. If the builder is not committed, this call is
-     * equivalent to a setResponse, addContent and complete call.
-     *
-     * @param code The error code
-     * @param reason The error reason
-     * @param content Contents of the error page
-     * @param close True if the connection should be closed
-     * @throws IOException if there is a problem flushing the response
-     */
-    public void sendError(int code, String reason, String content, boolean close) throws IOException
-    {
-        if (close)
-            _persistent=false;
-        if (isCommitted())
-        {
-            LOG.debug("sendError on committed: {} {}",code,reason);
-        }
-        else
-        {
-            LOG.debug("sendError: {} {}",code,reason);
-            setResponse(code, reason);
-            if (content != null)
-            {
-                completeHeader(null, false);
-                addContent(new View(new ByteArrayBuffer(content)), Generator.LAST);
-            }
-            else if (code>=400)
-            {
-                completeHeader(null, false);
-                addContent(new View(new ByteArrayBuffer("Error: "+(reason==null?(""+code):reason))), Generator.LAST);
-            }
-            else
-            {
-                completeHeader(null, true);
-            }
-            complete();
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return Returns the contentWritten.
-     */
-    public long getContentWritten()
-    {
-        return _contentWritten;
-    }
-
-
-
-    /* ------------------------------------------------------------ */
-    public void  blockForOutput(long maxIdleTime) throws IOException
-    {
-        if (_endp.isBlocking())
-        {
-            try
-            {
-                flushBuffer();
-            }
-            catch(IOException e)
-            {
-                _endp.close();
-                throw e;
-            }
-        }
-        else
-        {
-            if (!_endp.blockWritable(maxIdleTime))
-            {
-                _endp.close();
-                throw new EofException("timeout");
-            }
-
-            flushBuffer();
-        }
-    }
-
-}
diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/DateGenerator.java b/jetty-http/src/main/java/org/eclipse/jetty/http/DateGenerator.java
new file mode 100644
index 0000000..c5e02f2
--- /dev/null
+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/DateGenerator.java
@@ -0,0 +1,164 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.TimeZone;
+
+import org.eclipse.jetty.util.StringUtil;
+
+/**
+ * ThreadLocal Date formatters for HTTP style dates.
+ *
+ */
+public class DateGenerator
+{
+    private static final TimeZone __GMT = TimeZone.getTimeZone("GMT");
+    static
+    {
+        __GMT.setID("GMT");
+    }
+    
+    static final String[] DAYS =
+        { "Sat", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
+    static final String[] MONTHS =
+        { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "Jan"};
+
+
+    private static final ThreadLocal<DateGenerator> __dateGenerator =new ThreadLocal<DateGenerator>()
+    {
+        @Override
+        protected DateGenerator initialValue()
+        {
+            return new DateGenerator();
+        }
+    };
+
+
+    public final static String __01Jan1970=DateGenerator.formatDate(0);
+    
+    /**
+     * Format HTTP date "EEE, dd MMM yyyy HH:mm:ss 'GMT'"
+     */
+    public static String formatDate(long date)
+    {
+        return __dateGenerator.get().doFormatDate(date);
+    }
+
+    /**
+     * Format "EEE, dd-MMM-yyyy HH:mm:ss 'GMT'" for cookies
+     */
+    public static void formatCookieDate(StringBuilder buf, long date)
+    {
+        __dateGenerator.get().doFormatCookieDate(buf,date);
+    }
+
+    /**
+     * Format "EEE, dd-MMM-yyyy HH:mm:ss 'GMT'" for cookies
+     */
+    public static String formatCookieDate(long date)
+    {
+        StringBuilder buf = new StringBuilder(28);
+        formatCookieDate(buf, date);
+        return buf.toString();
+    }
+    
+    private final StringBuilder buf = new StringBuilder(32);
+    private final GregorianCalendar gc = new GregorianCalendar(__GMT);
+
+    /**
+     * Format HTTP date "EEE, dd MMM yyyy HH:mm:ss 'GMT'"
+     */
+    public String doFormatDate(long date)
+    {
+        buf.setLength(0);
+        gc.setTimeInMillis(date);
+
+        int day_of_week = gc.get(Calendar.DAY_OF_WEEK);
+        int day_of_month = gc.get(Calendar.DAY_OF_MONTH);
+        int month = gc.get(Calendar.MONTH);
+        int year = gc.get(Calendar.YEAR);
+        int century = year / 100;
+        year = year % 100;
+
+        int hours = gc.get(Calendar.HOUR_OF_DAY);
+        int minutes = gc.get(Calendar.MINUTE);
+        int seconds = gc.get(Calendar.SECOND);
+
+        buf.append(DAYS[day_of_week]);
+        buf.append(',');
+        buf.append(' ');
+        StringUtil.append2digits(buf, day_of_month);
+
+        buf.append(' ');
+        buf.append(MONTHS[month]);
+        buf.append(' ');
+        StringUtil.append2digits(buf, century);
+        StringUtil.append2digits(buf, year);
+
+        buf.append(' ');
+        StringUtil.append2digits(buf, hours);
+        buf.append(':');
+        StringUtil.append2digits(buf, minutes);
+        buf.append(':');
+        StringUtil.append2digits(buf, seconds);
+        buf.append(" GMT");
+        return buf.toString();
+    }
+
+    /**
+     * Format "EEE, dd-MMM-yy HH:mm:ss 'GMT'" for cookies
+     */
+    public void doFormatCookieDate(StringBuilder buf, long date)
+    {
+        gc.setTimeInMillis(date);
+
+        int day_of_week = gc.get(Calendar.DAY_OF_WEEK);
+        int day_of_month = gc.get(Calendar.DAY_OF_MONTH);
+        int month = gc.get(Calendar.MONTH);
+        int year = gc.get(Calendar.YEAR);
+        year = year % 10000;
+
+        int epoch = (int) ((date / 1000) % (60 * 60 * 24));
+        int seconds = epoch % 60;
+        epoch = epoch / 60;
+        int minutes = epoch % 60;
+        int hours = epoch / 60;
+
+        buf.append(DAYS[day_of_week]);
+        buf.append(',');
+        buf.append(' ');
+        StringUtil.append2digits(buf, day_of_month);
+
+        buf.append('-');
+        buf.append(MONTHS[month]);
+        buf.append('-');
+        StringUtil.append2digits(buf, year/100);
+        StringUtil.append2digits(buf, year%100);
+
+        buf.append(' ');
+        StringUtil.append2digits(buf, hours);
+        buf.append(':');
+        StringUtil.append2digits(buf, minutes);
+        buf.append(':');
+        StringUtil.append2digits(buf, seconds);
+        buf.append(" GMT");
+    }
+}
diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/DateParser.java b/jetty-http/src/main/java/org/eclipse/jetty/http/DateParser.java
new file mode 100644
index 0000000..ea44889
--- /dev/null
+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/DateParser.java
@@ -0,0 +1,109 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+/**
+ * ThreadLocal data parsers for HTTP style dates
+ *
+ */
+class DateParser
+{
+    private static final TimeZone __GMT = TimeZone.getTimeZone("GMT");
+    static
+    {
+        __GMT.setID("GMT");
+    }
+    
+    final static String __dateReceiveFmt[] =
+    {
+        "EEE, dd MMM yyyy HH:mm:ss zzz",
+        "EEE, dd-MMM-yy HH:mm:ss",
+        "EEE MMM dd HH:mm:ss yyyy",
+
+        "EEE, dd MMM yyyy HH:mm:ss", "EEE dd MMM yyyy HH:mm:ss zzz",
+        "EEE dd MMM yyyy HH:mm:ss", "EEE MMM dd yyyy HH:mm:ss zzz", "EEE MMM dd yyyy HH:mm:ss",
+        "EEE MMM-dd-yyyy HH:mm:ss zzz", "EEE MMM-dd-yyyy HH:mm:ss", "dd MMM yyyy HH:mm:ss zzz",
+        "dd MMM yyyy HH:mm:ss", "dd-MMM-yy HH:mm:ss zzz", "dd-MMM-yy HH:mm:ss", "MMM dd HH:mm:ss yyyy zzz",
+        "MMM dd HH:mm:ss yyyy", "EEE MMM dd HH:mm:ss yyyy zzz",
+        "EEE, MMM dd HH:mm:ss yyyy zzz", "EEE, MMM dd HH:mm:ss yyyy", "EEE, dd-MMM-yy HH:mm:ss zzz",
+        "EEE dd-MMM-yy HH:mm:ss zzz", "EEE dd-MMM-yy HH:mm:ss",
+    };
+
+    public static long parseDate(String date)
+    {
+        return __dateParser.get().parse(date);
+    }
+
+    private static final ThreadLocal<DateParser> __dateParser =new ThreadLocal<DateParser>()
+    {
+        @Override
+        protected DateParser initialValue()
+        {
+            return new DateParser();
+        }
+    };
+    
+    final SimpleDateFormat _dateReceive[]= new SimpleDateFormat[__dateReceiveFmt.length];
+
+    private long parse(final String dateVal)
+    {
+        for (int i = 0; i < _dateReceive.length; i++)
+        {
+            if (_dateReceive[i] == null)
+            {
+                _dateReceive[i] = new SimpleDateFormat(__dateReceiveFmt[i], Locale.US);
+                _dateReceive[i].setTimeZone(__GMT);
+            }
+
+            try
+            {
+                Date date = (Date) _dateReceive[i].parseObject(dateVal);
+                return date.getTime();
+            }
+            catch (java.lang.Exception e)
+            {
+                // LOG.ignore(e);
+            }
+        }
+
+        if (dateVal.endsWith(" GMT"))
+        {
+            final String val = dateVal.substring(0, dateVal.length() - 4);
+
+            for (SimpleDateFormat element : _dateReceive)
+            {
+                try
+                {
+                    Date date = (Date) element.parseObject(val);
+                    return date.getTime();
+                }
+                catch (java.lang.Exception e)
+                {
+                    // LOG.ignore(e);
+                }
+            }
+        }
+        return -1;
+    }
+}
diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/EncodedHttpURI.java b/jetty-http/src/main/java/org/eclipse/jetty/http/EncodedHttpURI.java
deleted file mode 100644
index 065aa4d..0000000
--- a/jetty-http/src/main/java/org/eclipse/jetty/http/EncodedHttpURI.java
+++ /dev/null
@@ -1,183 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.http;
-
-import java.io.UnsupportedEncodingException;
-
-import org.eclipse.jetty.util.MultiMap;
-import org.eclipse.jetty.util.StringUtil;
-import org.eclipse.jetty.util.TypeUtil;
-import org.eclipse.jetty.util.URIUtil;
-import org.eclipse.jetty.util.UrlEncoded;
-import org.eclipse.jetty.util.Utf8StringBuffer;
-
-public class EncodedHttpURI extends HttpURI
-{
-    private final String _encoding;
-    
-    public EncodedHttpURI(String encoding)
-    {
-        super();
-        _encoding = encoding;
-    }
-    
-    
-    @Override
-    public String getScheme()
-    {
-        if (_scheme==_authority)
-            return null;
-        int l=_authority-_scheme;
-        if (l==5 && 
-            _raw[_scheme]=='h' && 
-            _raw[_scheme+1]=='t' && 
-            _raw[_scheme+2]=='t' && 
-            _raw[_scheme+3]=='p' )
-            return HttpSchemes.HTTP;
-        if (l==6 && 
-            _raw[_scheme]=='h' && 
-            _raw[_scheme+1]=='t' && 
-            _raw[_scheme+2]=='t' && 
-            _raw[_scheme+3]=='p' && 
-            _raw[_scheme+4]=='s' )
-            return HttpSchemes.HTTPS;
-        
-        return StringUtil.toString(_raw,_scheme,_authority-_scheme-1,_encoding);
-    }
-    
-    @Override
-    public String getAuthority()
-    {
-        if (_authority==_path)
-            return null;
-        return StringUtil.toString(_raw,_authority,_path-_authority,_encoding);
-    }
-    
-    @Override
-    public String getHost()
-    {
-        if (_host==_port)
-            return null;
-        return StringUtil.toString(_raw,_host,_port-_host,_encoding);
-    }
-    
-    @Override
-    public int getPort()
-    {
-        if (_port==_path)
-            return -1;
-        return TypeUtil.parseInt(_raw, _port+1, _path-_port-1,10);
-    }
-    
-    @Override
-    public String getPath()
-    {
-        if (_path==_param)
-            return null;
-        return StringUtil.toString(_raw,_path,_param-_path,_encoding);
-    }
-    
-    @Override
-    public String getDecodedPath()
-    {
-        if (_path==_param)
-            return null;
-        return URIUtil.decodePath(_raw,_path,_param-_path);
-    }
-    
-    @Override
-    public String getPathAndParam()
-    {
-        if (_path==_query)
-            return null;
-        return StringUtil.toString(_raw,_path,_query-_path,_encoding);
-    }
-    
-    @Override
-    public String getCompletePath()
-    {
-        if (_path==_end)
-            return null;
-        return StringUtil.toString(_raw,_path,_end-_path,_encoding);
-    }
-    
-    @Override
-    public String getParam()
-    {
-        if (_param==_query)
-            return null;
-        return StringUtil.toString(_raw,_param+1,_query-_param-1,_encoding);
-    }
-    
-    @Override
-    public String getQuery()
-    {
-        if (_query==_fragment)
-            return null;
-        return StringUtil.toString(_raw,_query+1,_fragment-_query-1,_encoding);
-    }
-    
-    @Override
-    public boolean hasQuery()
-    {
-        return (_fragment>_query);
-    }
-    
-    @Override
-    public String getFragment()
-    {
-        if (_fragment==_end)
-            return null;
-        return StringUtil.toString(_raw,_fragment+1,_end-_fragment-1,_encoding);
-    }
-
-    @Override
-    public void decodeQueryTo(MultiMap parameters) 
-    {
-        if (_query==_fragment)
-            return;
-        UrlEncoded.decodeTo(StringUtil.toString(_raw,_query+1,_fragment-_query-1,_encoding),parameters,_encoding);
-    }
-
-    @Override
-    public void decodeQueryTo(MultiMap parameters, String encoding) 
-        throws UnsupportedEncodingException
-    {
-        if (_query==_fragment)
-            return;
-       
-        if (encoding==null)
-            encoding=_encoding;
-        UrlEncoded.decodeTo(StringUtil.toString(_raw,_query+1,_fragment-_query-1,encoding),parameters,encoding);
-    }
-    
-    @Override
-    public String toString()
-    {
-        if (_rawString==null)
-            _rawString= StringUtil.toString(_raw,_scheme,_end-_scheme,_encoding);
-        return _rawString;
-    }
-    
-    public void writeTo(Utf8StringBuffer buf)
-    {
-        buf.getStringBuffer().append(toString());
-    }
-    
-}
diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/Generator.java b/jetty-http/src/main/java/org/eclipse/jetty/http/Generator.java
deleted file mode 100644
index 0564aeb..0000000
--- a/jetty-http/src/main/java/org/eclipse/jetty/http/Generator.java
+++ /dev/null
@@ -1,96 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-
-package org.eclipse.jetty.http;
-
-import java.io.IOException;
-
-import org.eclipse.jetty.io.Buffer;
-
-public interface Generator
-{
-    public static final boolean LAST=true;
-    public static final boolean MORE=false;
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Add content.
-     * 
-     * @param content
-     * @param last
-     * @throws IllegalArgumentException if <code>content</code> is {@link Buffer#isImmutable immutable}.
-     * @throws IllegalStateException If the request is not expecting any more content,
-     *   or if the buffers are full and cannot be flushed.
-     * @throws IOException if there is a problem flushing the buffers.
-     */
-    void addContent(Buffer content, boolean last) throws IOException;
-
-    void complete() throws IOException;
-
-    void completeHeader(HttpFields responseFields, boolean last) throws IOException;
-
-    int flushBuffer() throws IOException;
-
-    int getContentBufferSize();
-
-    long getContentWritten();
-
-    boolean isWritten();
-    
-    boolean isAllContentWritten();
-
-    void increaseContentBufferSize(int size);
-    
-    boolean isBufferFull();
-
-    boolean isCommitted();
-
-    boolean isComplete();
-
-    boolean isPersistent();
-
-    void reset();
-
-    void resetBuffer();
-    
-    void returnBuffers();
-
-    void sendError(int code, String reason, String content, boolean close) throws IOException;
-    
-    void setHead(boolean head);
-
-    void setRequest(String method, String uri);
-
-    void setResponse(int status, String reason);
-
-
-    void setSendServerVersion(boolean sendServerVersion);
- 
-    void setVersion(int version);
-
-    boolean isIdle();
-
-    void setContentLength(long length);
-    
-    void setPersistent(boolean persistent);
-
-    void setDate(Buffer timeStampBuffer);
-    
-
-}
diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpBuffers.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpBuffers.java
deleted file mode 100644
index ff49e73..0000000
--- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpBuffers.java
+++ /dev/null
@@ -1,108 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.http;
-
-import org.eclipse.jetty.io.Buffers;
-import org.eclipse.jetty.io.BuffersFactory;
-import org.eclipse.jetty.util.component.AbstractLifeCycle;
-
-/* ------------------------------------------------------------ */
-/** Abstract Buffer pool.
- */
-public interface HttpBuffers
-{
-    /**
-     * @return the requestBufferSize
-     */
-    public int getRequestBufferSize();
-    
-    /**
-     * @param requestBufferSize the requestBufferSize to set
-     */
-    public void setRequestBufferSize(int requestBufferSize);
-
-    /**
-     * @return the requestHeaderSize
-     */
-    public int getRequestHeaderSize();
-
-    /**
-     * @param requestHeaderSize the requestHeaderSize to set
-     */
-    public void setRequestHeaderSize(int requestHeaderSize);
-
-    /**
-     * @return the responseBufferSize
-     */
-    public int getResponseBufferSize();
-
-    /**
-     * @param responseBufferSize the responseBufferSize to set
-     */
-    public void setResponseBufferSize(int responseBufferSize);
-
-    /**
-     * @return the responseHeaderSize
-     */
-    public int getResponseHeaderSize();
-
-    /**
-     * @param responseHeaderSize the responseHeaderSize to set
-     */
-    public void setResponseHeaderSize(int responseHeaderSize);
-
-    /**
-     * @return the requestBufferType
-     */
-    public Buffers.Type getRequestBufferType();
-
-    /**
-     * @return the requestHeaderType
-     */
-    public Buffers.Type getRequestHeaderType();
-
-    /**
-     * @return the responseBufferType
-     */
-    public Buffers.Type getResponseBufferType();
-
-    /**
-     * @return the responseHeaderType
-     */
-    public Buffers.Type getResponseHeaderType();
-
-    /**
-     * @param requestBuffers the requestBuffers to set
-     */
-    public void setRequestBuffers(Buffers requestBuffers);
-
-    /**
-     * @param responseBuffers the responseBuffers to set
-     */
-    public void setResponseBuffers(Buffers responseBuffers);
-
-    public Buffers getRequestBuffers();
-
-    public Buffers getResponseBuffers();
-
-    public void setMaxBuffers(int maxBuffers);
-
-    public int getMaxBuffers();
-    
-}
diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpBuffersImpl.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpBuffersImpl.java
deleted file mode 100644
index 50edb92..0000000
--- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpBuffersImpl.java
+++ /dev/null
@@ -1,238 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.http;
-
-import org.eclipse.jetty.io.Buffers;
-import org.eclipse.jetty.io.BuffersFactory;
-import org.eclipse.jetty.util.component.AbstractLifeCycle;
-
-/* ------------------------------------------------------------ */
-/** Abstract Buffer pool.
- * simple unbounded pool of buffers for header, request and response sizes.
- *
- */
-public class HttpBuffersImpl extends AbstractLifeCycle implements HttpBuffers
-{
-    private int _requestBufferSize=16*1024;
-    private int _requestHeaderSize=6*1024;
-    private int _responseBufferSize=32*1024;
-    private int _responseHeaderSize=6*1024;
-    private int _maxBuffers=1024;
-    
-    private Buffers.Type _requestBufferType=Buffers.Type.BYTE_ARRAY;
-    private Buffers.Type _requestHeaderType=Buffers.Type.BYTE_ARRAY;
-    private Buffers.Type _responseBufferType=Buffers.Type.BYTE_ARRAY;
-    private Buffers.Type _responseHeaderType=Buffers.Type.BYTE_ARRAY;
-    
-    private Buffers _requestBuffers;
-    private Buffers _responseBuffers;
-    
-    
-    public HttpBuffersImpl()
-    {
-        super();
-    }
-    
-    /**
-     * @return the requestBufferSize
-     */
-    public int getRequestBufferSize()
-    {
-        return _requestBufferSize;
-    }
-    
-    /**
-     * @param requestBufferSize the requestBufferSize to set
-     */
-    public void setRequestBufferSize(int requestBufferSize)
-    {
-        _requestBufferSize = requestBufferSize;
-    }
-
-    /**
-     * @return the requestHeaderSize
-     */
-    public int getRequestHeaderSize()
-    {
-        return _requestHeaderSize;
-    }
-
-    /**
-     * @param requestHeaderSize the requestHeaderSize to set
-     */
-    public void setRequestHeaderSize(int requestHeaderSize)
-    {
-        _requestHeaderSize = requestHeaderSize;
-    }
-
-    /**
-     * @return the responseBufferSize
-     */
-    public int getResponseBufferSize()
-    {
-        return _responseBufferSize;
-    }
-
-    /**
-     * @param responseBufferSize the responseBufferSize to set
-     */
-    public void setResponseBufferSize(int responseBufferSize)
-    {
-        _responseBufferSize = responseBufferSize;
-    }
-
-    /**
-     * @return the responseHeaderSize
-     */
-    public int getResponseHeaderSize()
-    {
-        return _responseHeaderSize;
-    }
-
-    /**
-     * @param responseHeaderSize the responseHeaderSize to set
-     */
-    public void setResponseHeaderSize(int responseHeaderSize)
-    {
-        _responseHeaderSize = responseHeaderSize;
-    }
-
-    /**
-     * @return the requestBufferType
-     */
-    public Buffers.Type getRequestBufferType()
-    {
-        return _requestBufferType;
-    }
-
-    /**
-     * @param requestBufferType the requestBufferType to set
-     */
-    public void setRequestBufferType(Buffers.Type requestBufferType)
-    {
-        _requestBufferType = requestBufferType;
-    }
-
-    /**
-     * @return the requestHeaderType
-     */
-    public Buffers.Type getRequestHeaderType()
-    {
-        return _requestHeaderType;
-    }
-
-    /**
-     * @param requestHeaderType the requestHeaderType to set
-     */
-    public void setRequestHeaderType(Buffers.Type requestHeaderType)
-    {
-        _requestHeaderType = requestHeaderType;
-    }
-
-    /**
-     * @return the responseBufferType
-     */
-    public Buffers.Type getResponseBufferType()
-    {
-        return _responseBufferType;
-    }
-
-    /**
-     * @param responseBufferType the responseBufferType to set
-     */
-    public void setResponseBufferType(Buffers.Type responseBufferType)
-    {
-        _responseBufferType = responseBufferType;
-    }
-
-    /**
-     * @return the responseHeaderType
-     */
-    public Buffers.Type getResponseHeaderType()
-    {
-        return _responseHeaderType;
-    }
-
-    /**
-     * @param responseHeaderType the responseHeaderType to set
-     */
-    public void setResponseHeaderType(Buffers.Type responseHeaderType)
-    {
-        _responseHeaderType = responseHeaderType;
-    }
-
-    /**
-     * @param requestBuffers the requestBuffers to set
-     */
-    public void setRequestBuffers(Buffers requestBuffers)
-    {
-        _requestBuffers = requestBuffers;
-    }
-
-    /**
-     * @param responseBuffers the responseBuffers to set
-     */
-    public void setResponseBuffers(Buffers responseBuffers)
-    {
-        _responseBuffers = responseBuffers;
-    }
-
-    @Override
-    protected void doStart()
-        throws Exception
-    {
-        _requestBuffers=BuffersFactory.newBuffers(_requestHeaderType,_requestHeaderSize,_requestBufferType,_requestBufferSize,_requestBufferType,getMaxBuffers());
-        _responseBuffers=BuffersFactory.newBuffers(_responseHeaderType,_responseHeaderSize,_responseBufferType,_responseBufferSize,_responseBufferType,getMaxBuffers());
-        super.doStart();
-    }
-    
-    @Override
-    protected void doStop()
-        throws Exception
-    {
-        _requestBuffers=null;
-        _responseBuffers=null;
-    }
-
-    public Buffers getRequestBuffers()
-    {
-        return _requestBuffers;
-    }
-    
-
-    public Buffers getResponseBuffers()
-    {
-        return _responseBuffers;
-    }
-
-    public void setMaxBuffers(int maxBuffers)
-    {
-        _maxBuffers = maxBuffers;
-    }
-
-    public int getMaxBuffers()
-    {
-        return _maxBuffers;
-    }
-    
-    public String toString()
-    {
-        return _requestBuffers+"/"+_responseBuffers;
-    }
-}
diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpContent.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpContent.java
index 2edd1f5..4bff0c9 100644
--- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpContent.java
+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpContent.java
@@ -20,11 +20,10 @@ package org.eclipse.jetty.http;
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.channels.ReadableByteChannel;
 
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.ByteArrayBuffer;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.BufferUtil;
 import org.eclipse.jetty.util.resource.Resource;
 
 /* ------------------------------------------------------------ */
@@ -34,134 +33,146 @@ import org.eclipse.jetty.util.resource.Resource;
  */
 public interface HttpContent
 {
-    Buffer getContentType();
-    Buffer getLastModified();
-    Buffer getIndirectBuffer();
-    Buffer getDirectBuffer();
-    Buffer getETag();
+    String getContentType();
+    String getLastModified();
+    ByteBuffer getIndirectBuffer();
+    ByteBuffer getDirectBuffer();
+    String getETag();
     Resource getResource();
     long getContentLength();
     InputStream getInputStream() throws IOException;
+    ReadableByteChannel getReadableByteChannel() throws IOException;
     void release();
-    
+
     /* ------------------------------------------------------------ */
     /* ------------------------------------------------------------ */
     /* ------------------------------------------------------------ */
     public class ResourceAsHttpContent implements HttpContent
     {
-        private static final Logger LOG = Log.getLogger(ResourceAsHttpContent.class);
-        
         final Resource _resource;
-        final Buffer _mimeType;
+        final String _mimeType;
         final int _maxBuffer;
-        final Buffer _etag;
+        final String _etag;
 
         /* ------------------------------------------------------------ */
-        public ResourceAsHttpContent(final Resource resource, final Buffer mimeType)
+        public ResourceAsHttpContent(final Resource resource, final String mimeType)
         {
             this(resource,mimeType,-1,false);
         }
 
         /* ------------------------------------------------------------ */
-        public ResourceAsHttpContent(final Resource resource, final Buffer mimeType, int maxBuffer)
+        public ResourceAsHttpContent(final Resource resource, final String mimeType, int maxBuffer)
         {
             this(resource,mimeType,maxBuffer,false);
         }
 
         /* ------------------------------------------------------------ */
-        public ResourceAsHttpContent(final Resource resource, final Buffer mimeType, boolean etag)
+        public ResourceAsHttpContent(final Resource resource, final String mimeType, boolean etag)
         {
             this(resource,mimeType,-1,etag);
         }
 
         /* ------------------------------------------------------------ */
-        public ResourceAsHttpContent(final Resource resource, final Buffer mimeType, int maxBuffer, boolean etag)
+        public ResourceAsHttpContent(final Resource resource, final String mimeType, int maxBuffer, boolean etag)
         {
             _resource=resource;
             _mimeType=mimeType;
             _maxBuffer=maxBuffer;
-            _etag=etag?new ByteArrayBuffer(resource.getWeakETag()):null;
+            _etag=etag?resource.getWeakETag():null;
         }
 
         /* ------------------------------------------------------------ */
-        public Buffer getContentType()
+        @Override
+        public String getContentType()
         {
             return _mimeType;
         }
 
         /* ------------------------------------------------------------ */
-        public Buffer getLastModified()
+        @Override
+        public String getLastModified()
         {
             return null;
         }
 
         /* ------------------------------------------------------------ */
-        public Buffer getDirectBuffer()
+        @Override
+        public ByteBuffer getDirectBuffer()
         {
-            return null;
+            if (_resource.length()<=0 || _maxBuffer<_resource.length())
+                return null;
+            try
+            {
+                return BufferUtil.toBuffer(_resource,true);
+            }
+            catch(IOException e)
+            {
+                throw new RuntimeException(e);
+            }
         }
         
         /* ------------------------------------------------------------ */
-        public Buffer getETag()
+        @Override
+        public String getETag()
         {
             return _etag;
         }
 
         /* ------------------------------------------------------------ */
-        public Buffer getIndirectBuffer()
+        @Override
+        public ByteBuffer getIndirectBuffer()
         {
-            InputStream inputStream = null;
+            if (_resource.length()<=0 || _maxBuffer<_resource.length())
+                return null;
             try
             {
-                if (_resource.length() <= 0 || _maxBuffer < _resource.length())
-                    return null;
-                ByteArrayBuffer buffer = new ByteArrayBuffer((int)_resource.length());
-                inputStream = _resource.getInputStream();
-                buffer.readFrom(inputStream,(int)_resource.length());
-                return buffer;
+                return BufferUtil.toBuffer(_resource,false);
             }
-            catch (IOException e)
+            catch(IOException e)
             {
                 throw new RuntimeException(e);
             }
-            finally
-            {
-                if (inputStream != null)
-                {
-                    try
-                    {
-                        inputStream.close();
-                    }
-                    catch (IOException e)
-                    {
-                        LOG.warn("Couldn't close inputStream. Possible file handle leak",e);
-                    }
-                }
-            }
         }
 
         /* ------------------------------------------------------------ */
+        @Override
         public long getContentLength()
         {
             return _resource.length();
         }
 
         /* ------------------------------------------------------------ */
+        @Override
         public InputStream getInputStream() throws IOException
         {
             return _resource.getInputStream();
         }
+        
+        /* ------------------------------------------------------------ */
+        @Override
+        public ReadableByteChannel getReadableByteChannel() throws IOException
+        {
+            return _resource.getReadableByteChannel();
+        }
 
         /* ------------------------------------------------------------ */
+        @Override
         public Resource getResource()
         {
             return _resource;
         }
 
         /* ------------------------------------------------------------ */
+        @Override
         public void release()
         {
-            _resource.release();
+            _resource.close();
+        }
+        
+        @Override
+        public String toString()
+        {
+            return String.format("%s@%x{r=%s}",this.getClass().getSimpleName(),hashCode(),_resource);
         }
     }
 }
diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpCookie.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpCookie.java
index 8323e61..b8d3936 100644
--- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpCookie.java
+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpCookie.java
@@ -18,174 +18,147 @@
 
 package org.eclipse.jetty.http;
 
+import java.util.concurrent.TimeUnit;
+
 public class HttpCookie
 {
-    private final String _name;        
-    private final String _value;     
-    private final String _comment;                               
-    private final String _domain;    
-    private final int _maxAge;  
-    private final String _path;       
-    private final boolean _secure;   
-    private final int _version;   
+    private final String _name;
+    private final String _value;
+    private final String _comment;
+    private final String _domain;
+    private final long _maxAge;
+    private final String _path;
+    private final boolean _secure;
+    private final int _version;
     private final boolean _httpOnly;
+    private final long _expiration;
 
-    /* ------------------------------------------------------------ */
     public HttpCookie(String name, String value)
     {
-        super();
-        _name = name;
-        _value = value;
-        _comment = null;
-        _domain = null;
-        _httpOnly = false;
-        _maxAge = -1;
-        _path = null;
-        _secure = false;
-        _version = 0;
-    }
-    
-    /* ------------------------------------------------------------ */
+        this(name, value, -1);
+    }
+
     public HttpCookie(String name, String value, String domain, String path)
     {
-        super();
-        _name = name;
-        _value = value;
-        _comment = null;
-        _domain = domain;
-        _httpOnly = false;
-        _maxAge = -1;
-        _path = path;
-        _secure = false;
-        _version = 0;
-        
+        this(name, value, domain, path, -1, false, false);
     }
-    
-    /* ------------------------------------------------------------ */
-    public HttpCookie(String name, String value, int maxAge)
+
+    public HttpCookie(String name, String value, long maxAge)
     {
-        super();
-        _name = name;
-        _value = value;
-        _comment = null;
-        _domain = null;
-        _httpOnly = false;
-        _maxAge = maxAge;
-        _path = null;
-        _secure = false;
-        _version = 0;
+        this(name, value, null, null, maxAge, false, false);
     }
-    
-    /* ------------------------------------------------------------ */
-    public HttpCookie(String name, String value, String domain, String path, int maxAge, boolean httpOnly, boolean secure)
+
+    public HttpCookie(String name, String value, String domain, String path, long maxAge, boolean httpOnly, boolean secure)
     {
-        super();
-        _comment = null;
-        _domain = domain;
-        _httpOnly = httpOnly;
-        _maxAge = maxAge;
-        _name = name;
-        _path = path;
-        _secure = secure;
-        _value = value;
-        _version = 0;
+        this(name, value, domain, path, maxAge, httpOnly, secure, null, 0);
     }
-    
-    /* ------------------------------------------------------------ */
-    public HttpCookie(String name, String value, String domain, String path, int maxAge, boolean httpOnly, boolean secure, String comment, int version)
+
+    public HttpCookie(String name, String value, String domain, String path, long maxAge, boolean httpOnly, boolean secure, String comment, int version)
     {
-        super();
-        _comment = comment;
-        _domain = domain;
-        _httpOnly = httpOnly;
-        _maxAge = maxAge;
         _name = name;
+        _value = value;
+        _domain = domain;
         _path = path;
+        _maxAge = maxAge;
+        _httpOnly = httpOnly;
         _secure = secure;
-        _value = value;
+        _comment = comment;
         _version = version;
+        _expiration = maxAge < 0 ? -1 : System.nanoTime() + TimeUnit.SECONDS.toNanos(maxAge);
     }
-    
-    /* ------------------------------------------------------------ */
-    /** Get the name.
-     * @return the name
+
+    /**
+     * @return the cookie name
      */
     public String getName()
     {
         return _name;
     }
-    
-    /* ------------------------------------------------------------ */
-    /** Get the value.
-     * @return the value
+
+    /**
+     * @return the cookie value
      */
     public String getValue()
     {
         return _value;
     }
-    
-    /* ------------------------------------------------------------ */
-    /** Get the comment.
-     * @return the comment
+
+    /**
+     * @return the cookie comment
      */
     public String getComment()
     {
         return _comment;
     }
-    
-    /* ------------------------------------------------------------ */
-    /** Get the domain.
-     * @return the domain
+
+    /**
+     * @return the cookie domain
      */
     public String getDomain()
     {
         return _domain;
     }
-    
-    /* ------------------------------------------------------------ */
-    /** Get the maxAge.
-     * @return the maxAge
+
+    /**
+     * @return the cookie max age in seconds
      */
-    public int getMaxAge()
+    public long getMaxAge()
     {
         return _maxAge;
     }
-    
-    /* ------------------------------------------------------------ */
-    /** Get the path.
-     * @return the path
+
+    /**
+     * @return the cookie path
      */
     public String getPath()
     {
         return _path;
     }
-    
-    /* ------------------------------------------------------------ */
-    /** Get the secure.
-     * @return the secure
+
+    /**
+     * @return whether the cookie is valid for secure domains
      */
     public boolean isSecure()
     {
         return _secure;
     }
-    
-    /* ------------------------------------------------------------ */
-    /** Get the version.
-     * @return the version
+
+    /**
+     * @return the cookie version
      */
     public int getVersion()
     {
         return _version;
     }
-    
-    /* ------------------------------------------------------------ */
-    /** Get the isHttpOnly.
-     * @return the isHttpOnly
+
+    /**
+     * @return whether the cookie is valid for the http protocol only
      */
     public boolean isHttpOnly()
     {
         return _httpOnly;
     }
-    
-    
+
+    /**
+     * @param timeNanos the time to check for cookie expiration, in nanoseconds
+     * @return whether the cookie is expired by the given time
+     */
+    public boolean isExpired(long timeNanos)
+    {
+        return _expiration >= 0 && timeNanos >= _expiration;
+    }
+
+    /**
+     * @return a string representation of this cookie
+     */
+    public String asString()
+    {
+        StringBuilder builder = new StringBuilder();
+        builder.append(getName()).append("=").append(getValue());
+        if (getDomain() != null)
+            builder.append(";$Domain=").append(getDomain());
+        if (getPath() != null)
+            builder.append(";$Path=").append(getPath());
+        return builder.toString();
+    }
 }
diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpException.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpException.java
deleted file mode 100644
index 8342f1a..0000000
--- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpException.java
+++ /dev/null
@@ -1,94 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.http;
-
-import java.io.IOException;
-
-public class HttpException extends IOException
-{
-    int _status;
-    String _reason;
-
-    /* ------------------------------------------------------------ */
-    public HttpException(int status)
-    {
-        _status=status;
-        _reason=null;
-    }
-
-    /* ------------------------------------------------------------ */
-    public HttpException(int status,String reason)
-    {
-        _status=status;
-        _reason=reason;
-    }
-
-    /* ------------------------------------------------------------ */
-    public HttpException(int status,String reason, Throwable rootCause)
-    {
-        _status=status;
-        _reason=reason;
-        initCause(rootCause);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return Returns the reason.
-     */
-    public String getReason()
-    {
-        return _reason;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param reason The reason to set.
-     */
-    public void setReason(String reason)
-    {
-        _reason = reason;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return Returns the status.
-     */
-    public int getStatus()
-    {
-        return _status;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param status The status to set.
-     */
-    public void setStatus(int status)
-    {
-        _status = status;
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public String toString()
-    {
-        return ("HttpException("+_status+","+_reason+","+super.getCause()+")");
-    }
-    
-    
-}
diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpField.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpField.java
new file mode 100644
index 0000000..9355d8b
--- /dev/null
+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpField.java
@@ -0,0 +1,89 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+
+/* ------------------------------------------------------------ */
+/** A HTTP Field
+ */
+public class HttpField
+{
+    private final HttpHeader _header;
+    private final String _name;
+    private final String _value;
+        
+    public HttpField(HttpHeader header, String name, String value)
+    {
+        _header = header;
+        _name = name;
+        _value = value;
+    }  
+    
+    public HttpField(HttpHeader header, String value)
+    {
+        this(header,header.asString(),value);
+    }
+    
+    public HttpField(HttpHeader header, HttpHeaderValue value)
+    {
+        this(header,header.asString(),value.asString());
+    }
+    
+    public HttpField(String name, String value)
+    {
+        this(HttpHeader.CACHE.get(name),name,value);
+    }
+
+    public HttpHeader getHeader()
+    {
+        return _header;
+    }
+
+    public String getName()
+    {
+        return _name;
+    }
+
+    public String getValue()
+    {
+        return _value;
+    }
+    
+    @Override
+    public String toString()
+    {
+        String v=getValue();
+        return getName() + ": " + (v==null?"":v);
+    }
+
+    public boolean isSame(HttpField field)
+    {
+        if (field==null)
+            return false;
+        if (field==this)
+            return true;
+        if (_header!=null && _header==field.getHeader())
+            return true;
+        if (_name.equalsIgnoreCase(field.getName()))
+            return true;
+        return false;
+    }
+    
+    
+}
diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java
index eac095a..698392e 100644
--- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java
+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java
@@ -18,409 +18,198 @@
 
 package org.eclipse.jetty.http;
 
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.text.SimpleDateFormat;
 import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.Collections;
 import java.util.Collection;
-import java.util.Date;
+import java.util.Collections;
 import java.util.Enumeration;
-import java.util.GregorianCalendar;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
-import java.util.Locale;
 import java.util.Map;
 import java.util.NoSuchElementException;
+import java.util.Set;
 import java.util.StringTokenizer;
-import java.util.TimeZone;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.BufferCache;
-import org.eclipse.jetty.io.BufferCache.CachedBuffer;
-import org.eclipse.jetty.io.BufferDateCache;
-import org.eclipse.jetty.io.BufferUtil;
-import org.eclipse.jetty.io.ByteArrayBuffer;
+import java.util.regex.Pattern;
+
+import org.eclipse.jetty.util.ArrayTernaryTrie;
 import org.eclipse.jetty.util.LazyList;
 import org.eclipse.jetty.util.QuotedStringTokenizer;
-import org.eclipse.jetty.util.StringMap;
 import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.Trie;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
 
-/* ------------------------------------------------------------ */
+
 /**
- * HTTP Fields. A collection of HTTP header and or Trailer fields. 
- * 
+ * HTTP Fields. A collection of HTTP header and or Trailer fields.
+ *
  * <p>This class is not synchronized as it is expected that modifications will only be performed by a
  * single thread.
  * 
- * 
+ * <p>The cookie handling provided by this class is guided by the Servlet specification and RFC6265.
+ *
  */
-public class HttpFields
+public class HttpFields implements Iterable<HttpField>
 {
     private static final Logger LOG = Log.getLogger(HttpFields.class);
-    
-    /* ------------------------------------------------------------ */
-    public static final String __COOKIE_DELIM="\"\\\n\r\t\f\b%+ ;=";
-    public static final TimeZone __GMT = TimeZone.getTimeZone("GMT");
-    public static final BufferDateCache __dateCache = new BufferDateCache("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US);
-
-    /* -------------------------------------------------------------- */
-    static
-    {
-        __GMT.setID("GMT");
-        __dateCache.setTimeZone(__GMT);
-    }
-    
-    /* ------------------------------------------------------------ */
+    private final static Pattern __splitter = Pattern.compile("\\s*,\\s*");     
     public final static String __separators = ", \t";
 
-    /* ------------------------------------------------------------ */
-    private static final String[] DAYS =
-    { "Sat", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
-    private static final String[] MONTHS =
-    { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "Jan"};
+    private final ArrayList<HttpField> _fields = new ArrayList<>(20);
 
-    
-    /* ------------------------------------------------------------ */
-    private static class DateGenerator
+    /**
+     * Constructor.
+     */
+    public HttpFields()
     {
-        private final StringBuilder buf = new StringBuilder(32);
-        private final GregorianCalendar gc = new GregorianCalendar(__GMT);
-        
-        /**
-         * Format HTTP date "EEE, dd MMM yyyy HH:mm:ss 'GMT'" 
-         */
-        public String formatDate(long date)
-        {
-            buf.setLength(0);
-            gc.setTimeInMillis(date);
-            
-            int day_of_week = gc.get(Calendar.DAY_OF_WEEK);
-            int day_of_month = gc.get(Calendar.DAY_OF_MONTH);
-            int month = gc.get(Calendar.MONTH);
-            int year = gc.get(Calendar.YEAR);
-            int century = year / 100;
-            year = year % 100;
-            
-            int hours = gc.get(Calendar.HOUR_OF_DAY);
-            int minutes = gc.get(Calendar.MINUTE);
-            int seconds = gc.get(Calendar.SECOND);
-
-            buf.append(DAYS[day_of_week]);
-            buf.append(',');
-            buf.append(' ');
-            StringUtil.append2digits(buf, day_of_month);
-
-            buf.append(' ');
-            buf.append(MONTHS[month]);
-            buf.append(' ');
-            StringUtil.append2digits(buf, century);
-            StringUtil.append2digits(buf, year);
-            
-            buf.append(' ');
-            StringUtil.append2digits(buf, hours);
-            buf.append(':');
-            StringUtil.append2digits(buf, minutes);
-            buf.append(':');
-            StringUtil.append2digits(buf, seconds);
-            buf.append(" GMT");
-            return buf.toString();
-        }
-
-        /* ------------------------------------------------------------ */
-        /**
-         * Format "EEE, dd-MMM-yy HH:mm:ss 'GMT'" for cookies
-         */
-        public void formatCookieDate(StringBuilder buf, long date)
-        {
-            gc.setTimeInMillis(date);
-            
-            int day_of_week = gc.get(Calendar.DAY_OF_WEEK);
-            int day_of_month = gc.get(Calendar.DAY_OF_MONTH);
-            int month = gc.get(Calendar.MONTH);
-            int year = gc.get(Calendar.YEAR);
-            year = year % 10000;
-
-            int epoch = (int) ((date / 1000) % (60 * 60 * 24));
-            int seconds = epoch % 60;
-            epoch = epoch / 60;
-            int minutes = epoch % 60;
-            int hours = epoch / 60;
-
-            buf.append(DAYS[day_of_week]);
-            buf.append(',');
-            buf.append(' ');
-            StringUtil.append2digits(buf, day_of_month);
-
-            buf.append('-');
-            buf.append(MONTHS[month]);
-            buf.append('-');
-            StringUtil.append2digits(buf, year/100);
-            StringUtil.append2digits(buf, year%100);
-            
-            buf.append(' ');
-            StringUtil.append2digits(buf, hours);
-            buf.append(':');
-            StringUtil.append2digits(buf, minutes);
-            buf.append(':');
-            StringUtil.append2digits(buf, seconds);
-            buf.append(" GMT");
-        }
     }
 
-    /* ------------------------------------------------------------ */
-    private static final ThreadLocal<DateGenerator> __dateGenerator =new ThreadLocal<DateGenerator>()
+    /**
+     * Get Collection of header names.
+     */
+    public Collection<String> getFieldNamesCollection()
     {
-        @Override
-        protected DateGenerator initialValue()
+        final Set<String> list = new HashSet<>(_fields.size());
+        for (HttpField f : _fields)
         {
-            return new DateGenerator();
+            if (f!=null)
+                list.add(f.getName());
         }
-    };
-    
-    /* ------------------------------------------------------------ */
+        return list;
+    }
+
     /**
-     * Format HTTP date "EEE, dd MMM yyyy HH:mm:ss 'GMT'" 
+     * Get enumeration of header _names. Returns an enumeration of strings representing the header
+     * _names for this request.
      */
-    public static String formatDate(long date)
+    public Enumeration<String> getFieldNames()
     {
-        return __dateGenerator.get().formatDate(date);
+        return Collections.enumeration(getFieldNamesCollection());
     }
 
-    /* ------------------------------------------------------------ */
-    /**
-     * Format "EEE, dd-MMM-yyyy HH:mm:ss 'GMT'" for cookies
-     */
-    public static void formatCookieDate(StringBuilder buf, long date)
+    public int size()
     {
-        __dateGenerator.get().formatCookieDate(buf,date);
+        return _fields.size();
     }
-    
-    /* ------------------------------------------------------------ */
+
     /**
-     * Format "EEE, dd-MMM-yyyy HH:mm:ss 'GMT'" for cookies
+     * Get a Field by index.
+     * @return A Field value or null if the Field value has not been set
+     *
      */
-    public static String formatCookieDate(long date)
+    public HttpField getField(int i)
     {
-        StringBuilder buf = new StringBuilder(28);
-        formatCookieDate(buf, date);
-        return buf.toString();
+        return _fields.get(i);
     }
 
-    /* ------------------------------------------------------------ */
-    private final static String __dateReceiveFmt[] =
-    {   
-        "EEE, dd MMM yyyy HH:mm:ss zzz", 
-        "EEE, dd-MMM-yy HH:mm:ss",
-        "EEE MMM dd HH:mm:ss yyyy",
-
-        "EEE, dd MMM yyyy HH:mm:ss", "EEE dd MMM yyyy HH:mm:ss zzz", 
-        "EEE dd MMM yyyy HH:mm:ss", "EEE MMM dd yyyy HH:mm:ss zzz", "EEE MMM dd yyyy HH:mm:ss", 
-        "EEE MMM-dd-yyyy HH:mm:ss zzz", "EEE MMM-dd-yyyy HH:mm:ss", "dd MMM yyyy HH:mm:ss zzz", 
-        "dd MMM yyyy HH:mm:ss", "dd-MMM-yy HH:mm:ss zzz", "dd-MMM-yy HH:mm:ss", "MMM dd HH:mm:ss yyyy zzz", 
-        "MMM dd HH:mm:ss yyyy", "EEE MMM dd HH:mm:ss yyyy zzz",  
-        "EEE, MMM dd HH:mm:ss yyyy zzz", "EEE, MMM dd HH:mm:ss yyyy", "EEE, dd-MMM-yy HH:mm:ss zzz", 
-        "EEE dd-MMM-yy HH:mm:ss zzz", "EEE dd-MMM-yy HH:mm:ss",
-    };
-
-    /* ------------------------------------------------------------ */
-    private static class DateParser
+    @Override
+    public Iterator<HttpField> iterator()
     {
-        final SimpleDateFormat _dateReceive[]= new SimpleDateFormat[__dateReceiveFmt.length];
- 
-        long parse(final String dateVal)
-        {
-            for (int i = 0; i < _dateReceive.length; i++)
-            {
-                if (_dateReceive[i] == null)
-                {
-                    _dateReceive[i] = new SimpleDateFormat(__dateReceiveFmt[i], Locale.US);
-                    _dateReceive[i].setTimeZone(__GMT);
-                }
-
-                try
-                {
-                    Date date = (Date) _dateReceive[i].parseObject(dateVal);
-                    return date.getTime();
-                }
-                catch (java.lang.Exception e)
-                {
-                    // LOG.ignore(e);
-                }
-            }
-            
-            if (dateVal.endsWith(" GMT"))
-            {
-                final String val = dateVal.substring(0, dateVal.length() - 4);
-
-                for (int i = 0; i < _dateReceive.length; i++)
-                {
-                    try
-                    {
-                        Date date = (Date) _dateReceive[i].parseObject(val);
-                        return date.getTime();
-                    }
-                    catch (java.lang.Exception e)
-                    {
-                        // LOG.ignore(e);
-                    }
-                }
-            }    
-            return -1;
-        }
+        return _fields.iterator();
     }
 
-    /* ------------------------------------------------------------ */
-    public static long parseDate(String date)
+    public HttpField getField(HttpHeader header)
     {
-        return __dateParser.get().parse(date);
+        for (int i=0;i<_fields.size();i++)
+        {
+            HttpField f=_fields.get(i);
+            if (f.getHeader()==header)
+                return f;
+        }
+        return null;
     }
 
-    /* ------------------------------------------------------------ */
-    private static final ThreadLocal<DateParser> __dateParser =new ThreadLocal<DateParser>()
+    public HttpField getField(String name)
     {
-        @Override
-        protected DateParser initialValue()
+        for (int i=0;i<_fields.size();i++)
         {
-            return new DateParser();
+            HttpField f=_fields.get(i);
+            if (f.getName().equalsIgnoreCase(name))
+                return f;
         }
-    };
-
-    /* -------------------------------------------------------------- */
-    public final static String __01Jan1970=formatDate(0);
-    public final static Buffer __01Jan1970_BUFFER=new ByteArrayBuffer(__01Jan1970);
-    public final static String __01Jan1970_COOKIE = formatCookieDate(0).trim();
-
-    /* -------------------------------------------------------------- */
-    private final ArrayList<Field> _fields = new ArrayList<Field>(20);
-    private final HashMap<Buffer,Field> _names = new HashMap<Buffer,Field>(32);
-    
-    /* ------------------------------------------------------------ */
-    /**
-     * Constructor.
-     */
-    public HttpFields()
-    {
+        return null;
     }
-
-    // TODO externalize this cache so it can be configurable
-    private static ConcurrentMap<String, Buffer> __cache = new ConcurrentHashMap<String, Buffer>();
-    private static int __cacheSize = Integer.getInteger("org.eclipse.jetty.http.HttpFields.CACHE",2000);
     
-    /* -------------------------------------------------------------- */
-    private Buffer convertValue(String value)
+    public boolean contains(HttpHeader header, String value)
     {
-        Buffer buffer = __cache.get(value);
-        if (buffer!=null)
-            return buffer;
-        
-        try
-        {   
-            buffer = new ByteArrayBuffer(value,StringUtil.__ISO_8859_1);
-            
-            if (__cacheSize>0)
-            {
-                if (__cache.size()>__cacheSize)
-                    __cache.clear();
-                Buffer b=__cache.putIfAbsent(value,buffer);
-                if (b!=null)
-                    buffer=b;
-            }
-            
-            return buffer;
-        }
-        catch (UnsupportedEncodingException e)
+        for (int i=0;i<_fields.size();i++)
         {
-            throw new RuntimeException(e);
+            HttpField f=_fields.get(i);
+            if (f.getHeader()==header && contains(f,value))
+                return true;
         }
+        return false;
     }
     
-    /* -------------------------------------------------------------- */
-    /**
-     * Get Collection of header names. 
-     */
-    public Collection<String> getFieldNamesCollection()
+    public boolean contains(String name, String value)
     {
-        final List<String> list = new ArrayList<String>(_fields.size());
-
-	for (Field f : _fields)
-	{
-	    if (f!=null)
-	        list.add(BufferUtil.to8859_1_String(f._name));
-	}
-	return list;
+        for (int i=0;i<_fields.size();i++)
+        {
+            HttpField f=_fields.get(i);
+            if (f.getName().equalsIgnoreCase(name) && contains(f,value))
+                return true;
+        }
+        return false;
     }
     
-    /* -------------------------------------------------------------- */
-    /**
-     * Get enumeration of header _names. Returns an enumeration of strings representing the header
-     * _names for this request.
-     */
-    public Enumeration<String> getFieldNames()
+    private boolean contains(HttpField field,String value)
     {
-        final Enumeration<?> buffers = Collections.enumeration(_names.keySet());
-        return new Enumeration<String>()
+        String v = field.getValue();
+        if (v==null)
+            return false;
+
+        if (value.equalsIgnoreCase(v))
+            return true;
+
+        String[] split = __splitter.split(v);
+        for (int i = 0; split!=null && i < split.length; i++) 
         {
-            public String nextElement()
-            {
-                return buffers.nextElement().toString();
-            }
-            
-            public boolean hasMoreElements()
-            {
-                return buffers.hasMoreElements();
-            }
-        }; 
+            if (value.equals(split[i]))
+                return true;
+        }
+
+        return false;
     }
-    
-    /* ------------------------------------------------------------ */
-    public int size()
+
+    public boolean contains(HttpHeader header)
     {
-        return _fields.size();
+        for (int i=0;i<_fields.size();i++)
+        {
+            HttpField f=_fields.get(i);
+            if (f.getHeader()==header)
+                return true;
+        }
+        return false;
     }
     
-    /* ------------------------------------------------------------ */
-    /**
-     * Get a Field by index.
-     * @return A Field value or null if the Field value has not been set
-     * 
-     */
-    public Field getField(int i)
-    {
-        return _fields.get(i);
-    }
-
-    /* ------------------------------------------------------------ */
-    private Field getField(String name)
+    public boolean containsKey(String name)
     {
-        return _names.get(HttpHeaders.CACHE.lookup(name));
+        for (int i=0;i<_fields.size();i++)
+        {
+            HttpField f=_fields.get(i);
+            if (f.getName().equalsIgnoreCase(name))
+                return true;
+        }
+        return false;
     }
-
-    /* ------------------------------------------------------------ */
-    private Field getField(Buffer name)
+    
+    
+    public String getStringField(HttpHeader header)
     {
-        return _names.get(HttpHeaders.CACHE.lookup(name));
+        return getStringField(header.asString());
     }
 
-    /* ------------------------------------------------------------ */
-    public boolean containsKey(Buffer name)
+    public String get(HttpHeader header)
     {
-        return _names.containsKey(HttpHeaders.CACHE.lookup(name));
+        return getStringField(header.asString());
     }
 
-    /* ------------------------------------------------------------ */
-    public boolean containsKey(String name)
+    public String get(String header)
     {
-        return _names.containsKey(HttpHeaders.CACHE.lookup(name));
+        return getStringField(header);
     }
 
-    /* -------------------------------------------------------------- */
     /**
      * @return the value of a field, or null if not found. For multiple fields of the same name,
      *         only the first is returned.
@@ -428,134 +217,87 @@ public class HttpFields
      */
     public String getStringField(String name)
     {
-        Field field = getField(name);
+        HttpField field = getField(name);
         return field==null?null:field.getValue();
     }
 
-    /* -------------------------------------------------------------- */
-    /**
-     * @return the value of a field, or null if not found. For multiple fields of the same name,
-     *         only the first is returned.
-     * @param name the case-insensitive field name
-     */
-    public String getStringField(Buffer name)
-    {
-        Field field = getField(name);
-        return field==null?null:field.getValue();
-    }
-
-    /* -------------------------------------------------------------- */
-    /**
-     * @return the value of a field, or null if not found. For multiple fields of the same name,
-     *         only the first is returned.
-     * @param name the case-insensitive field name
-     */
-    public Buffer get(Buffer name)
-    {
-        Field field = getField(name);
-        return field==null?null:field._value;
-    }
-
-
-    /* -------------------------------------------------------------- */
     /**
      * Get multi headers
-     * 
-     * @return Enumeration of the values, or null if no such header.
+     *
+     * @return List the values
      * @param name the case-insensitive field name
      */
-    public Collection<String> getValuesCollection(String name)
+    public List<String> getValuesList(String name)
     {
-        Field field = getField(name);
-	if (field==null)
-	    return null;
-
-        final List<String> list = new ArrayList<String>();
-
-	while(field!=null)
-	{
-	    list.add(field.getValue());
-	    field=field._next;
-	}
-	return list;
+        final List<String> list = new ArrayList<>();
+        for (HttpField f : _fields)
+            if (f.getName().equalsIgnoreCase(name))
+                list.add(f.getValue());
+        return list;
     }
 
-    /* -------------------------------------------------------------- */
     /**
      * Get multi headers
-     * 
+     *
      * @return Enumeration of the values
      * @param name the case-insensitive field name
      */
-    public Enumeration<String> getValues(String name)
+    public Enumeration<String> getValues(final String name)
     {
-        final Field field = getField(name);
-        if (field == null) 
-        {
-            List<String> empty=Collections.emptyList();
-            return Collections.enumeration(empty);
-        }
-
-        return new Enumeration<String>()
+        for (int i=0;i<_fields.size();i++)
         {
-            Field f = field;
-
-            public boolean hasMoreElements()
-            {
-                return f != null;
-            }
-
-            public String nextElement() throws NoSuchElementException
+            final HttpField f = _fields.get(i);
+            
+            if (f.getName().equalsIgnoreCase(name) && f.getValue()!=null)
             {
-                if (f == null) throw new NoSuchElementException();
-                Field n = f;
-                f = f._next;
-                return n.getValue();
-            }
-        };
-    }
+                final int first=i;
+                return new Enumeration<String>()
+                {
+                    HttpField field=f;
+                    int i = first+1;
 
-    /* -------------------------------------------------------------- */
-    /**
-     * Get multi headers
-     * 
-     * @return Enumeration of the value Strings
-     * @param name the case-insensitive field name
-     */
-    public Enumeration<String> getValues(Buffer name)
-    {
-        final Field field = getField(name);
-        if (field == null) 
-        {
-            List<String> empty=Collections.emptyList();
-            return Collections.enumeration(empty);
-        }
+                    @Override
+                    public boolean hasMoreElements()
+                    {
+                        if (field==null)
+                        {
+                            while (i<_fields.size()) 
+                            {
+                                field=_fields.get(i++);
+                                if (field.getName().equalsIgnoreCase(name) && field.getValue()!=null)
+                                    return true;
+                            }
+                            field=null;
+                            return false;
+                        }
+                        return true;
+                    }
 
-        return new Enumeration<String>()
-        {
-            Field f = field;
+                    @Override
+                    public String nextElement() throws NoSuchElementException
+                    {
+                        if (hasMoreElements())
+                        {
+                            String value=field.getValue();
+                            field=null;
+                            return value;
+                        }
+                        throw new NoSuchElementException();
+                    }
 
-            public boolean hasMoreElements()
-            {
-                return f != null;
+                };
             }
+        }
 
-            public String nextElement() throws NoSuchElementException
-            {
-                if (f == null) throw new NoSuchElementException();
-                Field n = f;
-                f = f._next;
-                return n.getValue();
-            }
-        };
+        List<String> empty=Collections.emptyList();
+        return Collections.enumeration(empty);
     }
 
-    /* -------------------------------------------------------------- */
     /**
      * Get multi field values with separator. The multiple values can be represented as separate
      * headers of the same name, or by a single header using the separator(s), or a combination of
      * both. Separators may be quoted.
-     * 
+     *
      * @param name the case-insensitive field name
      * @param separators String of separators.
      * @return Enumeration of the values, or null if no such header.
@@ -563,25 +305,30 @@ public class HttpFields
     public Enumeration<String> getValues(String name, final String separators)
     {
         final Enumeration<String> e = getValues(name);
-        if (e == null) 
+        if (e == null)
             return null;
         return new Enumeration<String>()
         {
             QuotedStringTokenizer tok = null;
 
+            @Override
             public boolean hasMoreElements()
             {
                 if (tok != null && tok.hasMoreElements()) return true;
                 while (e.hasMoreElements())
                 {
                     String value = e.nextElement();
-                    tok = new QuotedStringTokenizer(value, separators, false, false);
-                    if (tok.hasMoreElements()) return true;
+                    if (value!=null)
+                    {
+                        tok = new QuotedStringTokenizer(value, separators, false, false);
+                        if (tok.hasMoreElements()) return true;
+                    }
                 }
                 tok = null;
                 return false;
             }
 
+            @Override
             public String nextElement() throws NoSuchElementException
             {
                 if (!hasMoreElements()) throw new NoSuchElementException();
@@ -592,103 +339,78 @@ public class HttpFields
         };
     }
 
+    public void put(HttpField field)
+    {
+        boolean put=false;
+        for (int i=_fields.size();i-->0;)
+        {
+            HttpField f=_fields.get(i);
+            if (f.isSame(field))
+            {
+                if (put)
+                    _fields.remove(i);
+                else
+                {
+                    _fields.set(i,field);
+                    put=true;
+                }
+            }
+        }
+        if (!put)
+            _fields.add(field);
+    }
     
-    /* -------------------------------------------------------------- */
     /**
      * Set a field.
-     * 
+     *
      * @param name the name of the field
      * @param value the value of the field. If null the field is cleared.
      */
     public void put(String name, String value)
     {
-        if (value==null)
+        if (value == null)
             remove(name);
         else
-        {
-            Buffer n = HttpHeaders.CACHE.lookup(name);
-            Buffer v = convertValue(value);
-            put(n, v);
-        }
+            put(new HttpField(name, value));
     }
 
-    /* -------------------------------------------------------------- */
-    /**
-     * Set a field.
-     * 
-     * @param name the name of the field
-     * @param value the value of the field. If null the field is cleared.
-     */
-    public void put(Buffer name, String value)
+    public void put(HttpHeader header, HttpHeaderValue value)
     {
-        Buffer n = HttpHeaders.CACHE.lookup(name);
-        Buffer v = convertValue(value);
-        put(n, v);
+        put(header,value.toString());
     }
 
-    /* -------------------------------------------------------------- */
     /**
      * Set a field.
-     * 
-     * @param name the name of the field
+     *
+     * @param header the header name of the field
      * @param value the value of the field. If null the field is cleared.
      */
-    public void put(Buffer name, Buffer value)
+    public void put(HttpHeader header, String value)
     {
-        remove(name);
         if (value == null)
-            return;
-
-        if (!(name instanceof BufferCache.CachedBuffer)) 
-            name = HttpHeaders.CACHE.lookup(name);
-        if (!(value instanceof CachedBuffer))
-            value= HttpHeaderValues.CACHE.lookup(value).asImmutableBuffer();
-        
-        // new value;
-        Field field = new Field(name, value);
-        _fields.add(field);
-        _names.put(name, field);
+            remove(header);
+        else
+            put(new HttpField(header, value));
     }
 
-    /* -------------------------------------------------------------- */
     /**
      * Set a field.
-     * 
+     *
      * @param name the name of the field
      * @param list the List value of the field. If null the field is cleared.
      */
-    public void put(String name, List<?> list)
+    public void put(String name, List<String> list)
     {
-        if (list == null || list.size() == 0)
-        {
-            remove(name);
-            return;
-        }
-        Buffer n = HttpHeaders.CACHE.lookup(name);
-
-        Object v = list.get(0);
-        if (v != null)
-            put(n, HttpHeaderValues.CACHE.lookup(v.toString()));
-        else
-            remove(n);
-
-        if (list.size() > 1)
-        {
-            java.util.Iterator<?> iter = list.iterator();
-            iter.next();
-            while (iter.hasNext())
-            {
-                v = iter.next();
-                if (v != null) put(n, HttpHeaderValues.CACHE.lookup(v.toString()));
-            }
-        }
+        remove(name);
+        for (String v : list)
+            if (v!=null)
+                add(name,v);
     }
 
-    /* -------------------------------------------------------------- */
     /**
      * Add to or set a field. If the field is allowed to have multiple values, add will add multiple
      * headers of the same name.
-     * 
+     *
      * @param name the name of the field
      * @param value the value of the field.
      * @exception IllegalArgumentException If the name is a single valued field and already has a
@@ -696,388 +418,172 @@ public class HttpFields
      */
     public void add(String name, String value) throws IllegalArgumentException
     {
-        if (value==null)
+        if (value == null)
             return;
-        Buffer n = HttpHeaders.CACHE.lookup(name);
-        Buffer v = convertValue(value);
-        add(n, v);
+
+        HttpField field = new HttpField(name, value);
+        _fields.add(field);
+    }
+
+    public void add(HttpHeader header, HttpHeaderValue value) throws IllegalArgumentException
+    {
+        add(header,value.toString());
     }
 
-    /* -------------------------------------------------------------- */
     /**
      * Add to or set a field. If the field is allowed to have multiple values, add will add multiple
      * headers of the same name.
-     * 
-     * @param name the name of the field
+     *
+     * @param header the header
      * @param value the value of the field.
-     * @exception IllegalArgumentException If the name is a single valued field and already has a
-     *                value.
+     * @exception IllegalArgumentException 
      */
-    public void add(Buffer name, Buffer value) throws IllegalArgumentException
-    {   
+    public void add(HttpHeader header, String value) throws IllegalArgumentException
+    {
         if (value == null) throw new IllegalArgumentException("null value");
 
-        if (!(name instanceof CachedBuffer))
-            name = HttpHeaders.CACHE.lookup(name);
-        name=name.asImmutableBuffer();
-        
-        if (!(value instanceof CachedBuffer) && HttpHeaderValues.hasKnownValues(HttpHeaders.CACHE.getOrdinal(name)))
-            value= HttpHeaderValues.CACHE.lookup(value);
-        value=value.asImmutableBuffer();
-        
-        Field field = _names.get(name);
-        Field last = null;
-        while (field != null)
-        {
-            last = field;
-            field = field._next;
-        }
-
-        // create the field
-        field = new Field(name, value);
+        HttpField field = new HttpField(header, value);
         _fields.add(field);
-
-        // look for chain to add too
-        if (last != null)
-            last._next = field;
-        else
-            _names.put(name, field);
     }
 
-    /* ------------------------------------------------------------ */
     /**
      * Remove a field.
-     * 
-     * @param name
+     *
+     * @param name the field to remove
      */
-    public void remove(String name)
+    public HttpField remove(HttpHeader name)
     {
-        remove(HttpHeaders.CACHE.lookup(name));
+        for (int i=_fields.size();i-->0;)
+        {
+            HttpField f=_fields.get(i);
+            if (f.getHeader()==name)
+                return _fields.remove(i);
+        }
+        return null;
     }
 
-    /* ------------------------------------------------------------ */
     /**
      * Remove a field.
-     * 
-     * @param name
+     *
+     * @param name the field to remove
      */
-    public void remove(Buffer name)
+    public HttpField remove(String name)
     {
-        if (!(name instanceof BufferCache.CachedBuffer)) 
-            name = HttpHeaders.CACHE.lookup(name);
-        Field field = _names.remove(name);
-        while (field != null)
+        for (int i=_fields.size();i-->0;)
         {
-            _fields.remove(field);
-            field = field._next;
+            HttpField f=_fields.get(i);
+            if (f.getName().equalsIgnoreCase(name))
+                return _fields.remove(i);
         }
+        return null;
     }
 
-    /* -------------------------------------------------------------- */
     /**
      * Get a header as an long value. Returns the value of an integer field or -1 if not found. The
      * case of the field name is ignored.
-     * 
+     *
      * @param name the case-insensitive field name
      * @exception NumberFormatException If bad long found
      */
     public long getLongField(String name) throws NumberFormatException
     {
-        Field field = getField(name);
-        return field==null?-1L:field.getLongValue();
+        HttpField field = getField(name);
+        return field==null?-1L:StringUtil.toLong(field.getValue());
     }
 
-    /* -------------------------------------------------------------- */
-    /**
-     * Get a header as an long value. Returns the value of an integer field or -1 if not found. The
-     * case of the field name is ignored.
-     * 
-     * @param name the case-insensitive field name
-     * @exception NumberFormatException If bad long found
-     */
-    public long getLongField(Buffer name) throws NumberFormatException
-    {
-        Field field = getField(name);
-        return field==null?-1L:field.getLongValue();
-    }
-
-    /* -------------------------------------------------------------- */
     /**
      * Get a header as a date value. Returns the value of a date field, or -1 if not found. The case
      * of the field name is ignored.
-     * 
+     *
      * @param name the case-insensitive field name
      */
     public long getDateField(String name)
     {
-        Field field = getField(name);
-        if (field == null) 
+        HttpField field = getField(name);
+        if (field == null)
             return -1;
 
-        String val = valueParameters(BufferUtil.to8859_1_String(field._value), null);
-        if (val == null) 
+        String val = valueParameters(field.getValue(), null);
+        if (val == null)
             return -1;
 
-        final long date = __dateParser.get().parse(val);
+        final long date = DateParser.parseDate(val);
         if (date==-1)
             throw new IllegalArgumentException("Cannot convert date: " + val);
         return date;
     }
 
-    /* -------------------------------------------------------------- */
+
     /**
      * Sets the value of an long field.
-     * 
+     *
      * @param name the field name
      * @param value the field long value
      */
-    public void putLongField(Buffer name, long value)
+    public void putLongField(HttpHeader name, long value)
     {
-        Buffer v = BufferUtil.toBuffer(value);
+        String v = Long.toString(value);
         put(name, v);
     }
 
-    /* -------------------------------------------------------------- */
     /**
      * Sets the value of an long field.
-     * 
+     *
      * @param name the field name
      * @param value the field long value
      */
     public void putLongField(String name, long value)
     {
-        Buffer n = HttpHeaders.CACHE.lookup(name);
-        Buffer v = BufferUtil.toBuffer(value);
-        put(n, v);
+        String v = Long.toString(value);
+        put(name, v);
     }
 
-    /* -------------------------------------------------------------- */
-    /**
-     * Sets the value of an long field.
-     * 
-     * @param name the field name
-     * @param value the field long value
-     */
-    public void addLongField(String name, long value)
-    {
-        Buffer n = HttpHeaders.CACHE.lookup(name);
-        Buffer v = BufferUtil.toBuffer(value);
-        add(n, v);
-    }
 
-    /* -------------------------------------------------------------- */
-    /**
-     * Sets the value of an long field.
-     * 
-     * @param name the field name
-     * @param value the field long value
-     */
-    public void addLongField(Buffer name, long value)
-    {
-        Buffer v = BufferUtil.toBuffer(value);
-        add(name, v);
-    }
-
-    /* -------------------------------------------------------------- */
     /**
      * Sets the value of a date field.
-     * 
+     *
      * @param name the field name
      * @param date the field date value
      */
-    public void putDateField(Buffer name, long date)
+    public void putDateField(HttpHeader name, long date)
     {
-        String d=formatDate(date);
-        Buffer v = new ByteArrayBuffer(d);
-        put(name, v);
+        String d=DateGenerator.formatDate(date);
+        put(name, d);
     }
 
-    /* -------------------------------------------------------------- */
     /**
      * Sets the value of a date field.
-     * 
+     *
      * @param name the field name
      * @param date the field date value
      */
     public void putDateField(String name, long date)
     {
-        Buffer n = HttpHeaders.CACHE.lookup(name);
-        putDateField(n,date);
+        String d=DateGenerator.formatDate(date);
+        put(name, d);
     }
 
-    /* -------------------------------------------------------------- */
     /**
      * Sets the value of a date field.
-     * 
+     *
      * @param name the field name
      * @param date the field date value
      */
     public void addDateField(String name, long date)
     {
-        String d=formatDate(date);
-        Buffer n = HttpHeaders.CACHE.lookup(name);
-        Buffer v = new ByteArrayBuffer(d);
-        add(n, v);
+        String d=DateGenerator.formatDate(date);
+        add(name,d);
     }
 
-    /* ------------------------------------------------------------ */
-    /**
-     * Format a set cookie value
-     * 
-     * @param cookie The cookie.
-     */
-    public void addSetCookie(HttpCookie cookie)
-    {
-        addSetCookie(
-                cookie.getName(),
-                cookie.getValue(),
-                cookie.getDomain(),
-                cookie.getPath(),
-                cookie.getMaxAge(),
-                cookie.getComment(),
-                cookie.isSecure(),
-                cookie.isHttpOnly(),
-                cookie.getVersion());
-    }
-
-    /**
-     * Format a set cookie value
-     * 
-     * @param name the name
-     * @param value the value
-     * @param domain the domain
-     * @param path the path
-     * @param maxAge the maximum age
-     * @param comment the comment (only present on versions > 0)
-     * @param isSecure true if secure cookie
-     * @param isHttpOnly true if for http only
-     * @param version version of cookie logic to use (0 == default behavior)
-     */
-    public void addSetCookie(
-            final String name, 
-            final String value, 
-            final String domain,
-            final String path, 
-            final long maxAge,
-            final String comment, 
-            final boolean isSecure,
-            final boolean isHttpOnly, 
-            int version)
-    {
-    	String delim=__COOKIE_DELIM;
-    	
-        // Check arguments
-        if (name == null || name.length() == 0) 
-            throw new IllegalArgumentException("Bad cookie name");
-
-        // Format value and params
-        StringBuilder buf = new StringBuilder(128);
-        String name_value_params;
-        QuotedStringTokenizer.quoteIfNeeded(buf, name, delim);
-        buf.append('=');
-        String start=buf.toString();
-        boolean hasDomain = false;
-        boolean hasPath = false;
-        
-        if (value != null && value.length() > 0)
-            QuotedStringTokenizer.quoteIfNeeded(buf, value, delim);        
-
-        if (comment != null && comment.length() > 0)
-        {
-            buf.append(";Comment=");
-            QuotedStringTokenizer.quoteIfNeeded(buf, comment, delim);
-        }
-
-        if (path != null && path.length() > 0)
-        {
-            hasPath = true;
-            buf.append(";Path=");
-            if (path.trim().startsWith("\""))
-                buf.append(path);
-            else
-                QuotedStringTokenizer.quoteIfNeeded(buf,path,delim);
-        }
-        if (domain != null && domain.length() > 0)
-        {
-            hasDomain = true;
-            buf.append(";Domain=");
-            QuotedStringTokenizer.quoteIfNeeded(buf,domain.toLowerCase(Locale.ENGLISH),delim);
-        }
-
-        if (maxAge >= 0)
-        {
-            // Always add the expires param as some browsers still don't handle max-age
-            buf.append(";Expires=");
-            if (maxAge == 0)
-                buf.append(__01Jan1970_COOKIE);
-            else
-                formatCookieDate(buf, System.currentTimeMillis() + 1000L * maxAge);
-
-            if (version >0)
-            {
-                buf.append(";Max-Age=");
-                buf.append(maxAge);
-            }
-        }
-
-        if (isSecure)
-            buf.append(";Secure");
-        if (isHttpOnly) 
-            buf.append(";HttpOnly");
-
-        name_value_params = buf.toString();
-        
-        // remove existing set-cookie of same name
-        Field field = getField(HttpHeaders.SET_COOKIE);
-        Field last=null;
-        while (field!=null)
-        {
-            String val = (field._value == null ? null : field._value.toString());
-            if (val!=null && val.startsWith(start))
-            {
-                //existing cookie has same name, does it also match domain and path?
-                if (((!hasDomain && !val.contains("Domain")) || (hasDomain && val.contains("Domain="+domain))) &&
-                    ((!hasPath && !val.contains("Path")) || (hasPath && val.contains("Path="+path))))
-                {
-                    _fields.remove(field);
-                    if (last==null)
-                        _names.put(HttpHeaders.SET_COOKIE_BUFFER,field._next);
-                    else
-                        last._next=field._next;
-                    break;
-                }
-            }
-            last=field;
-            field=field._next;
-        }
-
-        add(HttpHeaders.SET_COOKIE_BUFFER, new ByteArrayBuffer(name_value_params));
-        
-        // Expire responses with set-cookie headers so they do not get cached.
-        put(HttpHeaders.EXPIRES_BUFFER, __01Jan1970_BUFFER);
-    }
-
-    /* -------------------------------------------------------------- */
-    public void putTo(Buffer buffer) throws IOException
-    {
-        for (int i = 0; i < _fields.size(); i++)
-        {
-            Field field = _fields.get(i);
-            if (field != null) 
-                field.putTo(buffer);
-        }
-        BufferUtil.putCRLF(buffer);
-    }
-
-    /* -------------------------------------------------------------- */
-    public String toString()
+    @Override
+    public String
+    toString()
     {
         try
         {
-            StringBuffer buffer = new StringBuffer();
-            for (int i = 0; i < _fields.size(); i++)
+            StringBuilder buffer = new StringBuilder();
+            for (HttpField field : _fields)
             {
-                Field field = (Field) _fields.get(i);
                 if (field != null)
                 {
                     String tmp = field.getName();
@@ -1098,48 +604,51 @@ public class HttpFields
         }
     }
 
-    /* ------------------------------------------------------------ */
     /**
      * Clear the header.
      */
     public void clear()
     {
         _fields.clear();
-        _names.clear();
     }
 
-    /* ------------------------------------------------------------ */
+    public void add(HttpField field)
+    {
+        _fields.add(field);
+    }
+
+    
+    
     /**
      * Add fields from another HttpFields instance. Single valued fields are replaced, while all
      * others are added.
-     * 
-     * @param fields
+     *
+     * @param fields the fields to add
      */
     public void add(HttpFields fields)
     {
         if (fields == null) return;
 
-        Enumeration e = fields.getFieldNames();
+        Enumeration<String> e = fields.getFieldNames();
         while (e.hasMoreElements())
         {
-            String name = (String) e.nextElement();
-            Enumeration values = fields.getValues(name);
+            String name = e.nextElement();
+            Enumeration<String> values = fields.getValues(name);
             while (values.hasMoreElements())
-                add(name, (String) values.nextElement());
+                add(name, values.nextElement());
         }
     }
 
-    /* ------------------------------------------------------------ */
     /**
      * Get field value parameters. Some field values can have parameters. This method separates the
      * value from the parameters and optionally populates a map with the parameters. For example:
-     * 
+     *
      * <PRE>
-     * 
+     *
      * FieldName : Value ; param1=val1 ; param2=val2
-     * 
+     *
      * </PRE>
-     * 
+     *
      * @param value The Field value, possibly with parameteres.
      * @param parameters A map to populate with the parameters, or null
      * @return The value.
@@ -1169,13 +678,12 @@ public class HttpFields
         return value.substring(0, i).trim();
     }
 
-    /* ------------------------------------------------------------ */
     private static final Float __one = new Float("1.0");
     private static final Float __zero = new Float("0.0");
-    private static final StringMap __qualities = new StringMap();
+    private static final Trie<Float> __qualities = new ArrayTernaryTrie<>();
     static
     {
-        __qualities.put(null, __one);
+        __qualities.put("*", __one);
         __qualities.put("1.0", __one);
         __qualities.put("1", __one);
         __qualities.put("0.9", new Float("0.9"));
@@ -1193,7 +701,6 @@ public class HttpFields
         __qualities.put("0.0", __zero);
     }
 
-    /* ------------------------------------------------------------ */
     public static Float getQuality(String value)
     {
         if (value == null) return __zero;
@@ -1204,14 +711,17 @@ public class HttpFields
         if (value.charAt(qe++) == 'q')
         {
             qe++;
-            Map.Entry entry = __qualities.getEntry(value, qe, value.length() - qe);
-            if (entry != null) return (Float) entry.getValue();
+            Float q = __qualities.get(value, qe, value.length() - qe);
+            if (q != null)
+                return q;
         }
 
-        HashMap params = new HashMap(3);
+        Map<String,String> params = new HashMap<>(4);
         valueParameters(value, params);
-        String qs = (String) params.get("q");
-        Float q = (Float) __qualities.get(qs);
+        String qs = params.get("q");
+        if (qs==null)
+            qs="*";
+        Float q = __qualities.get(qs);
         if (q == null)
         {
             try
@@ -1226,16 +736,16 @@ public class HttpFields
         return q;
     }
 
-    /* ------------------------------------------------------------ */
     /**
      * List values in quality order.
-     * 
+     *
      * @param e Enumeration of values with quality parameters
      * @return values in quality order.
      */
-    public static List qualityList(Enumeration e)
+    public static List<String> qualityList(Enumeration<String> e)
     {
-        if (e == null || !e.hasMoreElements()) return Collections.EMPTY_LIST;
+        if (e == null || !e.hasMoreElements())
+            return Collections.emptyList();
 
         Object list = null;
         Object qual = null;
@@ -1243,29 +753,30 @@ public class HttpFields
         // Assume list will be well ordered and just add nonzero
         while (e.hasMoreElements())
         {
-            String v = e.nextElement().toString();
+            String v = e.nextElement();
             Float q = getQuality(v);
 
-            if (q.floatValue() >= 0.001)
+            if (q >= 0.001)
             {
                 list = LazyList.add(list, v);
                 qual = LazyList.add(qual, q);
             }
         }
 
-        List vl = LazyList.getList(list, false);
-        if (vl.size() < 2) return vl;
+        List<String> vl = LazyList.getList(list, false);
+        if (vl.size() < 2) 
+            return vl;
 
-        List ql = LazyList.getList(qual, false);
+        List<Float> ql = LazyList.getList(qual, false);
 
         // sort list with swaps
         Float last = __zero;
         for (int i = vl.size(); i-- > 0;)
         {
-            Float q = (Float) ql.get(i);
+            Float q = ql.get(i);
             if (last.compareTo(q) > 0)
             {
-                Object tmp = vl.get(i);
+                String tmp = vl.get(i);
                 vl.set(i, vl.get(i + 1));
                 vl.set(i + 1, tmp);
                 ql.set(i, ql.get(i + 1));
@@ -1280,127 +791,6 @@ public class HttpFields
         return vl;
     }
 
-    /* ------------------------------------------------------------ */
-    /* ------------------------------------------------------------ */
-    /* ------------------------------------------------------------ */
-    public static final class Field
-    {
-        private Buffer _name;
-        private Buffer _value;
-        private Field _next;
-
-        /* ------------------------------------------------------------ */
-        private Field(Buffer name, Buffer value)
-        {
-            _name = name;
-            _value = value;
-            _next = null;
-        }
-        
-        /* ------------------------------------------------------------ */
-        public void putTo(Buffer buffer) throws IOException
-        {
-            int o=(_name instanceof CachedBuffer)?((CachedBuffer)_name).getOrdinal():-1;
-            if (o>=0)
-                buffer.put(_name);
-            else
-            {
-                int s=_name.getIndex();
-                int e=_name.putIndex();
-                while (s<e)
-                {
-                    byte b=_name.peek(s++);
-                    switch(b)
-                    {
-                        case '\r':
-                        case '\n':
-                        case ':' :
-                            continue;
-                        default:
-                            buffer.put(b);
-                    }
-                }
-            }
-            
-            buffer.put((byte) ':');
-            buffer.put((byte) ' ');
-            
-            o=(_value instanceof CachedBuffer)?((CachedBuffer)_value).getOrdinal():-1;
-            if (o>=0)
-                buffer.put(_value);
-            else
-            {
-                int s=_value.getIndex();
-                int e=_value.putIndex();
-                while (s<e)
-                {
-                    byte b=_value.peek(s++);
-                    switch(b)
-                    {
-                        case '\r':
-                        case '\n':
-                            continue;
-                        default:
-                            buffer.put(b);
-                    }
-                }
-            }
-
-            BufferUtil.putCRLF(buffer);
-        }
 
-        /* ------------------------------------------------------------ */
-        public String getName()
-        {
-            return BufferUtil.to8859_1_String(_name);
-        }
-
-        /* ------------------------------------------------------------ */
-        Buffer getNameBuffer()
-        {
-            return _name;
-        }
 
-        /* ------------------------------------------------------------ */
-        public int getNameOrdinal()
-        {
-            return HttpHeaders.CACHE.getOrdinal(_name);
-        }
-
-        /* ------------------------------------------------------------ */
-        public String getValue()
-        {
-            return BufferUtil.to8859_1_String(_value);
-        }
-
-        /* ------------------------------------------------------------ */
-        public Buffer getValueBuffer()
-        {
-            return _value;
-        }
-
-        /* ------------------------------------------------------------ */
-        public int getValueOrdinal()
-        {
-            return HttpHeaderValues.CACHE.getOrdinal(_value);
-        }
-
-        /* ------------------------------------------------------------ */
-        public int getIntValue()
-        {
-            return (int) getLongValue();
-        }
-
-        /* ------------------------------------------------------------ */
-        public long getLongValue()
-        {
-            return BufferUtil.toLong(_value);
-        }
-
-        /* ------------------------------------------------------------ */
-        public String toString()
-        {
-            return ("[" + getName() + "=" + _value + (_next == null ? "" : "->") + "]");
-        }
-    }
 }
diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpGenerator.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpGenerator.java
index f34544c..a4e3037 100644
--- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpGenerator.java
+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpGenerator.java
@@ -19,15 +19,13 @@
 package org.eclipse.jetty.http;
 
 import java.io.IOException;
-import java.io.InterruptedIOException;
-
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.BufferCache.CachedBuffer;
-import org.eclipse.jetty.io.BufferUtil;
-import org.eclipse.jetty.io.Buffers;
-import org.eclipse.jetty.io.ByteArrayBuffer;
-import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.io.EofException;
+import java.nio.BufferOverflowException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+
+import org.eclipse.jetty.http.HttpTokens.EndOfContent;
+import org.eclipse.jetty.util.BufferUtil;
 import org.eclipse.jetty.util.StringUtil;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
@@ -36,1057 +34,1081 @@ import org.eclipse.jetty.util.log.Logger;
 /**
  * HttpGenerator. Builds HTTP Messages.
  *
- *
- *
+ * If the system property "org.eclipse.jetty.http.HttpGenerator.STRICT" is set to true,
+ * then the generator will strictly pass on the exact strings received from methods and header
+ * fields.  Otherwise a fast case insensitive string lookup is used that may alter the
+ * case and white space of some methods/headers
+ * </p>
  */
-public class HttpGenerator extends AbstractGenerator
+public class HttpGenerator
 {
-    private static final Logger LOG = Log.getLogger(HttpGenerator.class);
+    private final static Logger LOG = Log.getLogger(HttpGenerator.class);
 
-    // Build cache of response lines for status
-    private static class Status
-    {
-        Buffer _reason;
-        Buffer _schemeCode;
-        Buffer _responseLine;
-    }
-    private static final Status[] __status = new Status[HttpStatus.MAX_CODE+1];
-    static
-    {
-        int versionLength=HttpVersions.HTTP_1_1_BUFFER.length();
+    public final static boolean __STRICT=Boolean.getBoolean("org.eclipse.jetty.http.HttpGenerator.STRICT"); 
 
-        for (int i=0;i<__status.length;i++)
-        {
-            HttpStatus.Code code = HttpStatus.getCode(i);
-            if (code==null)
-                continue;
-            String reason=code.getMessage();
-            byte[] bytes=new byte[versionLength+5+reason.length()+2];
-            HttpVersions.HTTP_1_1_BUFFER.peek(0,bytes, 0, versionLength);
-            bytes[versionLength+0]=' ';
-            bytes[versionLength+1]=(byte)('0'+i/100);
-            bytes[versionLength+2]=(byte)('0'+(i%100)/10);
-            bytes[versionLength+3]=(byte)('0'+(i%10));
-            bytes[versionLength+4]=' ';
-            for (int j=0;j<reason.length();j++)
-                bytes[versionLength+5+j]=(byte)reason.charAt(j);
-            bytes[versionLength+5+reason.length()]=HttpTokens.CARRIAGE_RETURN;
-            bytes[versionLength+6+reason.length()]=HttpTokens.LINE_FEED;
-
-            __status[i] = new Status();
-            __status[i]._reason=new ByteArrayBuffer(bytes,versionLength+5,bytes.length-versionLength-7,Buffer.IMMUTABLE);
-            __status[i]._schemeCode=new ByteArrayBuffer(bytes,0,versionLength+5,Buffer.IMMUTABLE);
-            __status[i]._responseLine=new ByteArrayBuffer(bytes,0,bytes.length,Buffer.IMMUTABLE);
-        }
-    }
+    private final static byte[] __colon_space = new byte[] {':',' '};
+    private final static HttpHeaderValue[] CLOSE = {HttpHeaderValue.CLOSE};
+    public static final ResponseInfo CONTINUE_100_INFO = new ResponseInfo(HttpVersion.HTTP_1_1,null,-1,100,null,false);
+    public static final ResponseInfo PROGRESS_102_INFO = new ResponseInfo(HttpVersion.HTTP_1_1,null,-1,102,null,false);
+    public final static ResponseInfo RESPONSE_500_INFO =
+        new ResponseInfo(HttpVersion.HTTP_1_1,new HttpFields(){{put(HttpHeader.CONNECTION,HttpHeaderValue.CLOSE);}},0,HttpStatus.INTERNAL_SERVER_ERROR_500,null,false);
 
-    /* ------------------------------------------------------------------------------- */
-    public static Buffer getReasonBuffer(int code)
-    {
-        Status status = code<__status.length?__status[code]:null;
-        if (status!=null)
-            return status._reason;
-        return null;
-    }
+    // states
+    public enum State { START, COMMITTED, COMPLETING, COMPLETING_1XX, END }
+    public enum Result { NEED_CHUNK,NEED_INFO,NEED_HEADER,FLUSH,CONTINUE,SHUTDOWN_OUT,DONE}
 
+    // other statics
+    public static final int CHUNK_SIZE = 12;
 
-    // common _content
-    private static final byte[] LAST_CHUNK =
-    { (byte) '0', (byte) '\015', (byte) '\012', (byte) '\015', (byte) '\012'};
-    private static final byte[] CONTENT_LENGTH_0 = StringUtil.getBytes("Content-Length: 0\015\012");
-    private static final byte[] CONNECTION_KEEP_ALIVE = StringUtil.getBytes("Connection: keep-alive\015\012");
-    private static final byte[] CONNECTION_CLOSE = StringUtil.getBytes("Connection: close\015\012");
-    private static final byte[] CONNECTION_ = StringUtil.getBytes("Connection: ");
-    private static final byte[] CRLF = StringUtil.getBytes("\015\012");
-    private static final byte[] TRANSFER_ENCODING_CHUNKED = StringUtil.getBytes("Transfer-Encoding: chunked\015\012");
-    private static byte[] SERVER = StringUtil.getBytes("Server: Jetty(7.0.x)\015\012");
+    private State _state = State.START;
+    private EndOfContent _endOfContent = EndOfContent.UNKNOWN_CONTENT;
+
+    private long _contentPrepared = 0;
+    private boolean _noContent = false;
+    private Boolean _persistent = null;
+
+    private final int _send;
+    private final static int SEND_SERVER = 0x01;
+    private final static int SEND_XPOWEREDBY = 0x02;
 
-    // other statics
-    private static final int CHUNK_SPACE = 12;
 
-    public static void setServerVersion(String version)
+    /* ------------------------------------------------------------------------------- */
+    public static void setJettyVersion(String serverVersion)
     {
-        SERVER=StringUtil.getBytes("Server: Jetty("+version+")\015\012");
+        SEND[SEND_SERVER] = StringUtil.getBytes("Server: " + serverVersion + "\015\012");
+        SEND[SEND_XPOWEREDBY] = StringUtil.getBytes("X-Powered-By: " + serverVersion + "\015\012");
+        SEND[SEND_SERVER | SEND_XPOWEREDBY] = StringUtil.getBytes("Server: " + serverVersion + "\015\012X-Powered-By: " +
+                serverVersion + "\015\012");
     }
 
+    /* ------------------------------------------------------------------------------- */
     // data
-    protected boolean _bypass = false; // True if _content buffer can be written directly to endp and bypass the content buffer
     private boolean _needCRLF = false;
-    private boolean _needEOC = false;
-    private boolean _bufferChunked = false;
-
 
     /* ------------------------------------------------------------------------------- */
-    /**
-     * Constructor.
-     *
-     * @param buffers buffer pool
-     * @param io the end point to use
-     */
-    public HttpGenerator(Buffers buffers, EndPoint io)
+    public HttpGenerator()
+    {
+        this(false,false);
+    }
+    
+    /* ------------------------------------------------------------------------------- */
+    public HttpGenerator(boolean sendServerVersion,boolean sendXPoweredBy)
     {
-        super(buffers,io);
+        _send=(sendServerVersion?SEND_SERVER:0) | (sendXPoweredBy?SEND_XPOWEREDBY:0);
     }
 
     /* ------------------------------------------------------------------------------- */
-    @Override
     public void reset()
     {
-        if (_persistent!=null && !_persistent && _endp!=null && !_endp.isOutputShutdown())
-        {
-            try
-            {
-                _endp.shutdownOutput();
-            }
-            catch(IOException e)
-            {
-                LOG.ignore(e);
-            }
-        }
-        super.reset();
-        if (_buffer!=null)
-            _buffer.clear();
-        if (_header!=null)
-            _header.clear();
-        if (_content!=null)
-            _content=null;
-        _bypass = false;
-        _needCRLF = false;
-        _needEOC = false;
-        _bufferChunked=false;
-        _method=null;
-        _uri=null;
+        _state = State.START;
+        _endOfContent = EndOfContent.UNKNOWN_CONTENT;
         _noContent=false;
+        _persistent = null;
+        _contentPrepared = 0;
+        _needCRLF = false;
     }
 
     /* ------------------------------------------------------------ */
-    /**
-     * Add content.
-     *
-     * @param content
-     * @param last
-     * @throws IllegalArgumentException if <code>content</code> is {@link Buffer#isImmutable immutable}.
-     * @throws IllegalStateException If the request is not expecting any more content,
-     *   or if the buffers are full and cannot be flushed.
-     * @throws IOException if there is a problem flushing the buffers.
-     */
-    public void addContent(Buffer content, boolean last) throws IOException
+    @Deprecated
+    public boolean getSendServerVersion ()
     {
-        if (_noContent)
-            throw new IllegalStateException("NO CONTENT");
-
-        if (_last || _state==STATE_END)
-        {
-            LOG.warn("Ignoring extra content {}",content);
-            content.clear();
-            return;
-        }
-        _last = last;
-
-        // Handle any unfinished business?
-        if (_content!=null && _content.length()>0 || _bufferChunked)
-        {
-            if (_endp.isOutputShutdown())
-                throw new EofException();
-            flushBuffer();
-            if (_content != null && _content.length()>0)
-            {
-                if (_bufferChunked)
-                {
-                    Buffer nc=_buffers.getBuffer(_content.length()+CHUNK_SPACE+content.length());
-                    nc.put(_content);
-                    nc.put(HttpTokens.CRLF);
-                    BufferUtil.putHexInt(nc, content.length());
-                    nc.put(HttpTokens.CRLF);
-                    nc.put(content);
-                    content=nc;
-                }
-                else
-                {
-                    Buffer nc=_buffers.getBuffer(_content.length()+content.length());
-                    nc.put(_content);
-                    nc.put(content);
-                    content=nc;
-                }
-            }
-        }
-
-        _content = content;
-        _contentWritten += content.length();
-
-        // Handle the _content
-        if (_head)
-        {
-            content.clear();
-            _content=null;
-        }
-        else if (_endp != null && (_buffer==null || _buffer.length()==0) && _content.length() > 0 && (_last || isCommitted() && _content.length()>1024))
-        {
-            _bypass = true;
-        }
-        else if (!_bufferChunked)
-        {
-            // Yes - so we better check we have a buffer
-            if (_buffer == null)
-                _buffer = _buffers.getBuffer();
-
-            // Copy _content to buffer;
-            int len=_buffer.put(_content);
-            _content.skip(len);
-            if (_content.length() == 0)
-                _content = null;
-        }
+        return (_send&SEND_SERVER)!=0;
     }
 
     /* ------------------------------------------------------------ */
-    /**
-     * send complete response.
-     *
-     * @param response
-     */
-    public void sendResponse(Buffer response) throws IOException
+    @Deprecated
+    public void setSendServerVersion (boolean sendServerVersion)
     {
-        if (_noContent || _state!=STATE_HEADER || _content!=null && _content.length()>0 || _bufferChunked || _head )
-            throw new IllegalStateException();
-
-        _last = true;
-
-        _content = response;
-        _bypass = true;
-        _state = STATE_FLUSHING;
-
-        // TODO this is not exactly right, but should do.
-        _contentLength =_contentWritten = response.length();
-
+        throw new UnsupportedOperationException();
     }
 
     /* ------------------------------------------------------------ */
-    /** Prepare buffer for unchecked writes.
-     * Prepare the generator buffer to receive unchecked writes
-     * @return the available space in the buffer.
-     * @throws IOException
-     */
-    @Override
-    public int prepareUncheckedAddContent() throws IOException
+    public State getState()
     {
-        if (_noContent)
-            return -1;
-
-        if (_last || _state==STATE_END)
-            return -1;
-
-        // Handle any unfinished business?
-        Buffer content = _content;
-        if (content != null && content.length()>0 || _bufferChunked)
-        {
-            flushBuffer();
-            if (content != null && content.length()>0 || _bufferChunked)
-                throw new IllegalStateException("FULL");
-        }
-
-        // we better check we have a buffer
-        if (_buffer == null)
-            _buffer = _buffers.getBuffer();
-
-        _contentWritten-=_buffer.length();
+        return _state;
+    }
 
-        // Handle the _content
-        if (_head)
-            return Integer.MAX_VALUE;
+    /* ------------------------------------------------------------ */
+    public boolean isState(State state)
+    {
+        return _state == state;
+    }
 
-        return _buffer.space()-(_contentLength == HttpTokens.CHUNKED_CONTENT?CHUNK_SPACE:0);
+    /* ------------------------------------------------------------ */
+    public boolean isIdle()
+    {
+        return _state == State.START;
     }
 
     /* ------------------------------------------------------------ */
-    @Override
-    public boolean isBufferFull()
+    public boolean isEnd()
     {
-        // Should we flush the buffers?
-        return super.isBufferFull() || _bufferChunked || _bypass  || (_contentLength == HttpTokens.CHUNKED_CONTENT && _buffer != null && _buffer.space() < CHUNK_SPACE);
+        return _state == State.END;
     }
 
     /* ------------------------------------------------------------ */
-    public void send1xx(int code) throws IOException
+    public boolean isCommitted()
     {
-        if (_state != STATE_HEADER)
-            return;
+        return _state.ordinal() >= State.COMMITTED.ordinal();
+    }
 
-        if (code<100||code>199)
-            throw new IllegalArgumentException("!1xx");
-        Status status=__status[code];
-        if (status==null)
-            throw new IllegalArgumentException(code+"?");
+    /* ------------------------------------------------------------ */
+    public boolean isChunking()
+    {
+        return _endOfContent==EndOfContent.CHUNKED_CONTENT;
+    }
 
-        // get a header buffer
-        if (_header == null)
-            _header = _buffers.getHeader();
+    /* ------------------------------------------------------------ */
+    public boolean isNoContent()
+    {
+        return _noContent;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public void setPersistent(boolean persistent)
+    {
+        _persistent=persistent;
+    }
 
-        _header.put(status._responseLine);
-        _header.put(HttpTokens.CRLF);
+    /* ------------------------------------------------------------ */
+    /**
+     * @return true if known to be persistent
+     */
+    public boolean isPersistent()
+    {
+        return Boolean.TRUE.equals(_persistent);
+    }
 
-        try
-        {
-            // nasty semi busy flush!
-            while(_header.length()>0)
-            {
-                int len = _endp.flush(_header);
-                if (len<0 || !_endp.isOpen())
-                    throw new EofException();
-                if (len==0)
-                    Thread.sleep(100);
-            }
-        }
-        catch(InterruptedException e)
-        {
-            LOG.debug(e);
-            throw new InterruptedIOException(e.toString());
-        }
+    /* ------------------------------------------------------------ */
+    public boolean isWritten()
+    {
+        return _contentPrepared>0;
     }
 
     /* ------------------------------------------------------------ */
-    @Override
-    public boolean isRequest()
+    public long getContentPrepared()
     {
-        return _method!=null;
+        return _contentPrepared;
     }
 
     /* ------------------------------------------------------------ */
-    @Override
-    public boolean isResponse()
+    public void abort()
     {
-        return _method==null;
+        _persistent=false;
+        _state=State.END;
+        _endOfContent=null;
     }
 
     /* ------------------------------------------------------------ */
-    @Override
-    public void completeHeader(HttpFields fields, boolean allContentAdded) throws IOException
+    public Result generateRequest(RequestInfo info, ByteBuffer header, ByteBuffer chunk, ByteBuffer content, boolean last) throws IOException
     {
-        if (_state != STATE_HEADER)
-            return;
+        switch(_state)
+        {
+            case START:
+            {
+                if (info==null)
+                    return Result.NEED_INFO;
 
-        // handle a reset
-        if (isResponse() && _status==0)
-            throw new EofException();
+                // Do we need a request header
+                if (header==null)
+                    return Result.NEED_HEADER;
 
-        if (_last && !allContentAdded)
-            throw new IllegalStateException("last?");
-        _last = _last | allContentAdded;
+                // If we have not been told our persistence, set the default
+                if (_persistent==null)
+                    _persistent=(info.getHttpVersion().ordinal() > HttpVersion.HTTP_1_0.ordinal());
 
-        // get a header buffer
-        if (_header == null)
-            _header = _buffers.getHeader();
+                // prepare the header
+                int pos=BufferUtil.flipToFill(header);
+                try
+                {
+                    // generate ResponseLine
+                    generateRequestLine(info,header);
 
-        boolean has_server = false;
+                    if (info.getHttpVersion()==HttpVersion.HTTP_0_9)
+                        _noContent=true;
+                    else
+                        generateHeaders(info,header,content,last);
 
-        try
-        {
-            if (isRequest())
+                    boolean expect100 = info.getHttpFields().contains(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString());
+
+                    if (expect100)
+                    {
+                        _state = State.COMMITTED;
+                    }
+                    else
+                    {
+                        // handle the content.
+                        int len = BufferUtil.length(content);
+                        if (len>0)
+                        {
+                            _contentPrepared+=len;
+                            if (isChunking())
+                                prepareChunk(header,len);
+                        }
+                        _state = last?State.COMPLETING:State.COMMITTED;
+                    }
+
+                    return Result.FLUSH;
+                }
+                catch(Exception e)
+                {
+                    String message= (e instanceof BufferOverflowException)?"Response header too large":e.getMessage();
+                    throw new IOException(message,e);
+                }
+                finally
+                {
+                    BufferUtil.flipToFlush(header,pos);
+                }
+            }
+
+            case COMMITTED:
             {
-                _persistent=true;
+                int len = BufferUtil.length(content);
 
-                if (_version == HttpVersions.HTTP_0_9_ORDINAL)
+                if (len>0)
                 {
-                    _contentLength = HttpTokens.NO_CONTENT;
-                    _header.put(_method);
-                    _header.put((byte)' ');
-                    _header.put(_uri.getBytes("UTF-8")); // TODO check
-                    _header.put(HttpTokens.CRLF);
-                    _state = STATE_FLUSHING;
-                    _noContent=true;
-                    return;
+                    // Do we need a chunk buffer?
+                    if (isChunking())
+                    {
+                        // Do we need a chunk buffer?
+                        if (chunk==null)
+                            return Result.NEED_CHUNK;
+                        BufferUtil.clearToFill(chunk);
+                        prepareChunk(chunk,len);
+                        BufferUtil.flipToFlush(chunk,0);
+                    }
+                    _contentPrepared+=len;
                 }
-                else
+
+                if (last)
                 {
-                    _header.put(_method);
-                    _header.put((byte)' ');
-                    _header.put(_uri.getBytes("UTF-8")); // TODO check
-                    _header.put((byte)' ');
-                    _header.put(_version==HttpVersions.HTTP_1_0_ORDINAL?HttpVersions.HTTP_1_0_BUFFER:HttpVersions.HTTP_1_1_BUFFER);
-                    _header.put(HttpTokens.CRLF);
+                    _state=State.COMPLETING;
+                    return len>0?Result.FLUSH:Result.CONTINUE;
                 }
+
+                return Result.FLUSH;
             }
-            else
+
+            case COMPLETING:
             {
-                // Responses
-                if (_version == HttpVersions.HTTP_0_9_ORDINAL)
+                if (BufferUtil.hasContent(content))
                 {
-                    _persistent = false;
-                    _contentLength = HttpTokens.EOF_CONTENT;
-                    _state = STATE_CONTENT;
-                    return;
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("discarding content in COMPLETING");
+                    BufferUtil.clear(content);
                 }
-                else
+
+                if (isChunking())
                 {
-                    if (_persistent==null)
-                        _persistent= (_version > HttpVersions.HTTP_1_0_ORDINAL);
+                    // Do we need a chunk buffer?
+                    if (chunk==null)
+                        return Result.NEED_CHUNK;
+                    BufferUtil.clearToFill(chunk);
+                    prepareChunk(chunk,0);
+                    BufferUtil.flipToFlush(chunk,0);
+                    _endOfContent=EndOfContent.UNKNOWN_CONTENT;
+                    return Result.FLUSH;
+                }
 
-                    // add response line
-                    Status status = _status<__status.length?__status[_status]:null;
+                _state=State.END;
+               return Boolean.TRUE.equals(_persistent)?Result.DONE:Result.SHUTDOWN_OUT;
+            }
 
-                    if (status==null)
-                    {
-                        _header.put(HttpVersions.HTTP_1_1_BUFFER);
-                        _header.put((byte) ' ');
-                        _header.put((byte) ('0' + _status / 100));
-                        _header.put((byte) ('0' + (_status % 100) / 10));
-                        _header.put((byte) ('0' + (_status % 10)));
-                        _header.put((byte) ' ');
-                        if (_reason==null)
-                        {
-                            _header.put((byte) ('0' + _status / 100));
-                            _header.put((byte) ('0' + (_status % 100) / 10));
-                            _header.put((byte) ('0' + (_status % 10)));
-                        }
-                        else
-                            _header.put(_reason);
-                        _header.put(HttpTokens.CRLF);
-                    }
-                    else
-                    {
-                        if (_reason==null)
-                            _header.put(status._responseLine);
-                        else
-                        {
-                            _header.put(status._schemeCode);
-                            _header.put(_reason);
-                            _header.put(HttpTokens.CRLF);
-                        }
-                    }
+            case END:
+                if (BufferUtil.hasContent(content))
+                {
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("discarding content in COMPLETING");
+                    BufferUtil.clear(content);
+                }
+                return Result.DONE;
+
+            default:
+                throw new IllegalStateException();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public Result generateResponse(ResponseInfo info, ByteBuffer header, ByteBuffer chunk, ByteBuffer content, boolean last) throws IOException
+    {
+        switch(_state)
+        {
+            case START:
+            {
+                if (info==null)
+                    return Result.NEED_INFO;
 
-                    if (_status<200 && _status>=100 )
+                // Handle 0.9
+                if (info.getHttpVersion() == HttpVersion.HTTP_0_9)
+                {
+                    _persistent = false;
+                    _endOfContent=EndOfContent.EOF_CONTENT;
+                    if (BufferUtil.hasContent(content))
+                        _contentPrepared+=content.remaining();
+                    _state = last?State.COMPLETING:State.COMMITTED;
+                    return Result.FLUSH;
+                }
+
+                // Do we need a response header
+                if (header==null)
+                    return Result.NEED_HEADER;
+
+                // If we have not been told our persistence, set the default
+                if (_persistent==null)
+                    _persistent=(info.getHttpVersion().ordinal() > HttpVersion.HTTP_1_0.ordinal());
+
+                // prepare the header
+                int pos=BufferUtil.flipToFill(header);
+                try
+                {
+                    // generate ResponseLine
+                    generateResponseLine(info,header);
+
+                    // Handle 1xx and no content responses
+                    int status=info.getStatus();
+                    if (status>=100 && status<200 )
                     {
                         _noContent=true;
-                        _content=null;
-                        if (_buffer!=null)
-                            _buffer.clear();
-                        // end the header.
 
-                        if (_status!=101 )
+                        if (status!=HttpStatus.SWITCHING_PROTOCOLS_101 )
                         {
-                            _header.put(HttpTokens.CRLF);
-                            _state = STATE_CONTENT;
-                            return;
+                            header.put(HttpTokens.CRLF);
+                            _state=State.COMPLETING_1XX;
+                            return Result.FLUSH;
                         }
                     }
-                    else if (_status==204 || _status==304)
+                    else if (status==HttpStatus.NO_CONTENT_204 || status==HttpStatus.NOT_MODIFIED_304)
                     {
                         _noContent=true;
-                        _content=null;
-                        if (_buffer!=null)
-                            _buffer.clear();
                     }
+
+                    generateHeaders(info,header,content,last);
+
+                    // handle the content.
+                    int len = BufferUtil.length(content);
+                    if (len>0)
+                    {
+                        _contentPrepared+=len;
+                        if (isChunking() && !info.isHead())
+                            prepareChunk(header,len);
+                    }
+                    _state = last?State.COMPLETING:State.COMMITTED;
+                }
+                catch(Exception e)
+                {
+                    String message= (e instanceof BufferOverflowException)?"Response header too large":e.getMessage();
+                    throw new IOException(message,e);
                 }
+                finally
+                {
+                    BufferUtil.flipToFlush(header,pos);
+                }
+
+                return Result.FLUSH;
             }
 
-            // Add headers
-            if (_status>=200 && _date!=null)
+            case COMMITTED:
             {
-                _header.put(HttpHeaders.DATE_BUFFER);
-                _header.put((byte)':');
-                _header.put((byte)' ');
-                _header.put(_date);
-                _header.put(CRLF);
+                int len = BufferUtil.length(content);
+
+                // handle the content.
+                if (len>0)
+                {
+                    if (isChunking())
+                    {
+                        if (chunk==null)
+                            return Result.NEED_CHUNK;
+                        BufferUtil.clearToFill(chunk);
+                        prepareChunk(chunk,len);
+                        BufferUtil.flipToFlush(chunk,0);
+                    }
+                    _contentPrepared+=len;
+                }
+
+                if (last)
+                {
+                    _state=State.COMPLETING;
+                    return len>0?Result.FLUSH:Result.CONTINUE;
+                }
+                return len>0?Result.FLUSH:Result.DONE;
+
             }
 
-            // key field values
-            HttpFields.Field content_length = null;
-            HttpFields.Field transfer_encoding = null;
-            boolean keep_alive = false;
-            boolean close=false;
-            boolean content_type=false;
-            StringBuilder connection = null;
+            case COMPLETING_1XX:
+            {
+                reset();
+                return Result.DONE;
+            }
 
-            if (fields != null)
+            case COMPLETING:
             {
-                int s=fields.size();
-                for (int f=0;f<s;f++)
+                if (BufferUtil.hasContent(content))
+                {
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("discarding content in COMPLETING");
+                    BufferUtil.clear(content);
+                }
+
+                if (isChunking())
                 {
-                    HttpFields.Field field = fields.getField(f);
-                    if (field==null)
-                        continue;
+                    // Do we need a chunk buffer?
+                    if (chunk==null)
+                        return Result.NEED_CHUNK;
+
+                    // Write the last chunk
+                    BufferUtil.clearToFill(chunk);
+                    prepareChunk(chunk,0);
+                    BufferUtil.flipToFlush(chunk,0);
+                    _endOfContent=EndOfContent.UNKNOWN_CONTENT;
+                    return Result.FLUSH;
+                }
 
-                    switch (field.getNameOrdinal())
-                    {
-                        case HttpHeaders.CONTENT_LENGTH_ORDINAL:
-                            content_length = field;
-                            _contentLength = field.getLongValue();
+                _state=State.END;
+
+               return Boolean.TRUE.equals(_persistent)?Result.DONE:Result.SHUTDOWN_OUT;
+            }
+
+            case END:
+                if (BufferUtil.hasContent(content))
+                {
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("discarding content in COMPLETING");
+                    BufferUtil.clear(content);
+                }
+                return Result.DONE;
+
+            default:
+                throw new IllegalStateException();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    private void prepareChunk(ByteBuffer chunk, int remaining)
+    {
+        // if we need CRLF add this to header
+        if (_needCRLF)
+            BufferUtil.putCRLF(chunk);
+
+        // Add the chunk size to the header
+        if (remaining>0)
+        {
+            BufferUtil.putHexInt(chunk, remaining);
+            BufferUtil.putCRLF(chunk);
+            _needCRLF=true;
+        }
+        else
+        {
+            chunk.put(LAST_CHUNK);
+            _needCRLF=false;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    private void generateRequestLine(RequestInfo request,ByteBuffer header)
+    {
+        header.put(StringUtil.getBytes(request.getMethod()));
+        header.put((byte)' ');
+        header.put(StringUtil.getBytes(request.getUri()));
+        switch(request.getHttpVersion())
+        {
+            case HTTP_1_0:
+            case HTTP_1_1:
+                header.put((byte)' ');
+                header.put(request.getHttpVersion().toBytes());
+                break;
+            default:
+                throw new IllegalStateException();
+        }
+        header.put(HttpTokens.CRLF);
+    }
+
+    /* ------------------------------------------------------------ */
+    private void generateResponseLine(ResponseInfo response, ByteBuffer header)
+    {
+        // Look for prepared response line
+        int status=response.getStatus();
+        PreparedResponse preprepared = status<__preprepared.length?__preprepared[status]:null;
+        String reason=response.getReason();
+        if (preprepared!=null)
+        {
+            if (reason==null)
+                header.put(preprepared._responseLine);
+            else
+            {
+                header.put(preprepared._schemeCode);
+                header.put(getReasonBytes(reason));
+                header.put(HttpTokens.CRLF);
+            }
+        }
+        else // generate response line
+        {
+            header.put(HTTP_1_1_SPACE);
+            header.put((byte) ('0' + status / 100));
+            header.put((byte) ('0' + (status % 100) / 10));
+            header.put((byte) ('0' + (status % 10)));
+            header.put((byte) ' ');
+            if (reason==null)
+            {
+                header.put((byte) ('0' + status / 100));
+                header.put((byte) ('0' + (status % 100) / 10));
+                header.put((byte) ('0' + (status % 10)));
+            }
+            else
+                header.put(getReasonBytes(reason));
+            header.put(HttpTokens.CRLF);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    private byte[] getReasonBytes(String reason)
+    {
+        if (reason.length()>1024)
+            reason=reason.substring(0,1024);
+        byte[] _bytes = StringUtil.getBytes(reason);
+
+        for (int i=_bytes.length;i-->0;)
+            if (_bytes[i]=='\r' || _bytes[i]=='\n')
+                _bytes[i]='?';
+        return _bytes;
+    }
+
+    /* ------------------------------------------------------------ */
+    private void generateHeaders(Info _info,ByteBuffer header,ByteBuffer content,boolean last)
+    {
+        final RequestInfo request=(_info instanceof RequestInfo)?(RequestInfo)_info:null;
+        final ResponseInfo response=(_info instanceof ResponseInfo)?(ResponseInfo)_info:null;
+
+        // default field values
+        int send=_send;
+        HttpField transfer_encoding=null;
+        boolean keep_alive=false;
+        boolean close=false;
+        boolean content_type=false;
+        StringBuilder connection = null;
+
+        // Generate fields
+        if (_info.getHttpFields() != null)
+        {
+            for (HttpField field : _info.getHttpFields())
+            {
+                HttpHeader h = field.getHeader();
 
-                            if (_contentLength < _contentWritten || _last && _contentLength != _contentWritten)
-                                content_length = null;
+                switch (h==null?HttpHeader.UNKNOWN:h)
+                {
+                    case CONTENT_LENGTH:
+                        // handle specially below
+                        if (_info.getContentLength()>=0)
+                            _endOfContent=EndOfContent.CONTENT_LENGTH;
+                        break;
 
-                            // write the field to the header buffer
-                            field.putTo(_header);
-                            break;
+                    case CONTENT_TYPE:
+                    {
+                        if (field.getValue().startsWith(MimeTypes.Type.MULTIPART_BYTERANGES.toString()))
+                            _endOfContent=EndOfContent.SELF_DEFINING_CONTENT;
 
-                        case HttpHeaders.CONTENT_TYPE_ORDINAL:
-                            if (BufferUtil.isPrefix(MimeTypes.MULTIPART_BYTERANGES_BUFFER, field.getValueBuffer())) _contentLength = HttpTokens.SELF_DEFINING_CONTENT;
+                        // write the field to the header
+                        content_type=true;
+                        putTo(field,header);
+                        break;
+                    }
 
-                            // write the field to the header buffer
-                            content_type=true;
-                            field.putTo(_header);
-                            break;
+                    case TRANSFER_ENCODING:
+                    {
+                        if (_info.getHttpVersion() == HttpVersion.HTTP_1_1)
+                            transfer_encoding = field;
+                        // Do NOT add yet!
+                        break;
+                    }
 
-                        case HttpHeaders.TRANSFER_ENCODING_ORDINAL:
-                            if (_version == HttpVersions.HTTP_1_1_ORDINAL)
-                                transfer_encoding = field;
-                            // Do NOT add yet!
-                            break;
+                    case CONNECTION:
+                    {
+                        if (request!=null)
+                            putTo(field,header);
 
-                        case HttpHeaders.CONNECTION_ORDINAL:
-                            if (isRequest())
-                                field.putTo(_header);
+                        // Lookup and/or split connection value field
+                        HttpHeaderValue[] values = HttpHeaderValue.CLOSE.is(field.getValue())?CLOSE:new HttpHeaderValue[]{HttpHeaderValue.CACHE.get(field.getValue())};
+                        String[] split = null;
 
-                            int connection_value = field.getValueOrdinal();
-                            switch (connection_value)
+                        if (values[0]==null)
+                        {
+                            split = field.getValue().split("\\s*,\\s*");
+                            if (split.length>0)
                             {
-                                case -1:
-                                {
-                                    String[] values = field.getValue().split(",");
-                                    for  (int i=0;values!=null && i<values.length;i++)
-                                    {
-                                        CachedBuffer cb = HttpHeaderValues.CACHE.get(values[i].trim());
-
-                                        if (cb!=null)
-                                        {
-                                            switch(cb.getOrdinal())
-                                            {
-                                                case HttpHeaderValues.CLOSE_ORDINAL:
-                                                    close=true;
-                                                    if (isResponse())
-                                                        _persistent=false;
-                                                    keep_alive=false;
-                                                    if (!_persistent && isResponse() && _contentLength == HttpTokens.UNKNOWN_CONTENT)
-                                                        _contentLength = HttpTokens.EOF_CONTENT;
-                                                    break;
-
-                                                case HttpHeaderValues.KEEP_ALIVE_ORDINAL:
-                                                    if (_version == HttpVersions.HTTP_1_0_ORDINAL)
-                                                    {
-                                                        keep_alive = true;
-                                                        if (isResponse())
-                                                            _persistent = true;
-                                                    }
-                                                    break;
-
-                                                default:
-                                                    if (connection==null)
-                                                        connection=new StringBuilder();
-                                                    else
-                                                        connection.append(',');
-                                                    connection.append(values[i]);
-                                            }
-                                        }
-                                        else
-                                        {
-                                            if (connection==null)
-                                                connection=new StringBuilder();
-                                            else
-                                                connection.append(',');
-                                            connection.append(values[i]);
-                                        }
-                                    }
+                                values=new HttpHeaderValue[split.length];
+                                for (int i=0;i<split.length;i++)
+                                    values[i]=HttpHeaderValue.CACHE.get(split[i]);
+                            }
+                        }
 
-                                    break;
-                                }
-                                case HttpHeaderValues.UPGRADE_ORDINAL:
+                        // Handle connection values
+                        for (int i=0;i<values.length;i++)
+                        {
+                            HttpHeaderValue value=values[i];
+                            switch (value==null?HttpHeaderValue.UNKNOWN:value)
+                            {
+                                case UPGRADE:
                                 {
                                     // special case for websocket connection ordering
-                                    if (isResponse())
-                                    {
-                                        field.putTo(_header);
-                                        continue;
-                                    }
+                                    header.put(HttpHeader.CONNECTION.getBytesColonSpace()).put(HttpHeader.UPGRADE.getBytes());
+                                    header.put(CRLF);
+                                    break;
                                 }
-                                case HttpHeaderValues.CLOSE_ORDINAL:
+
+                                case CLOSE:
                                 {
                                     close=true;
-                                    if (isResponse())
+                                    if (response!=null)
+                                    {
                                         _persistent=false;
-                                    if (!_persistent && isResponse() && _contentLength == HttpTokens.UNKNOWN_CONTENT)
-                                        _contentLength = HttpTokens.EOF_CONTENT;
+                                        if (_endOfContent == EndOfContent.UNKNOWN_CONTENT)
+                                            _endOfContent=EndOfContent.EOF_CONTENT;
+                                    }
                                     break;
                                 }
-                                case HttpHeaderValues.KEEP_ALIVE_ORDINAL:
+
+                                case KEEP_ALIVE:
                                 {
-                                    if (_version == HttpVersions.HTTP_1_0_ORDINAL)
+                                    if (_info.getHttpVersion() == HttpVersion.HTTP_1_0)
                                     {
                                         keep_alive = true;
-                                        if (isResponse())
+                                        if (response!=null)
                                             _persistent=true;
                                     }
                                     break;
                                 }
+
                                 default:
                                 {
                                     if (connection==null)
                                         connection=new StringBuilder();
                                     else
                                         connection.append(',');
-                                    connection.append(field.getValue());
+                                    connection.append(split==null?field.getValue():split[i]);
                                 }
                             }
+                        }
 
-                            // Do NOT add yet!
-                            break;
-
-                        case HttpHeaders.SERVER_ORDINAL:
-                            if (getSendServerVersion())
-                            {
-                                has_server=true;
-                                field.putTo(_header);
-                            }
-                            break;
+                        // Do NOT add yet!
+                        break;
+                    }
 
-                        default:
-                            // write the field to the header buffer
-                            field.putTo(_header);
+                    case SERVER:
+                    {
+                        send=send&~SEND_SERVER;
+                        putTo(field,header);
+                        break;
                     }
+
+                    default:
+                        putTo(field,header);
                 }
             }
+        }
 
-            // Calculate how to end _content and connection, _content length and transfer encoding
-            // settings.
-            // From RFC 2616 4.4:
-            // 1. No body for 1xx, 204, 304 & HEAD response
-            // 2. Force _content-length?
-            // 3. If Transfer-Encoding!=identity && HTTP/1.1 && !HttpConnection==close then chunk
-            // 4. Content-Length
-            // 5. multipart/byteranges
-            // 6. close
-            switch ((int) _contentLength)
-            {
-                case HttpTokens.UNKNOWN_CONTENT:
-                    // It may be that we have no _content, or perhaps _content just has not been
-                    // written yet?
-
-                    // Response known not to have a body
-                    if (_contentWritten == 0 && isResponse() && (_status < 200 || _status == 204 || _status == 304))
-                        _contentLength = HttpTokens.NO_CONTENT;
-                    else if (_last)
+
+        // Calculate how to end _content and connection, _content length and transfer encoding
+        // settings.
+        // From RFC 2616 4.4:
+        // 1. No body for 1xx, 204, 304 & HEAD response
+        // 2. Force _content-length?
+        // 3. If Transfer-Encoding!=identity && HTTP/1.1 && !HttpConnection==close then chunk
+        // 4. Content-Length
+        // 5. multipart/byteranges
+        // 6. close
+        int status=response!=null?response.getStatus():-1;
+        switch (_endOfContent)
+        {
+            case UNKNOWN_CONTENT:
+                // It may be that we have no _content, or perhaps _content just has not been
+                // written yet?
+
+                // Response known not to have a body
+                if (_contentPrepared == 0 && response!=null && (status < 200 || status == 204 || status == 304))
+                    _endOfContent=EndOfContent.NO_CONTENT;
+                else if (_info.getContentLength()>0)
+                {
+                    // we have been given a content length
+                    _endOfContent=EndOfContent.CONTENT_LENGTH;
+                    long content_length = _info.getContentLength();
+                    if ((response!=null || content_length>0 || content_type ) && !_noContent)
                     {
-                        // we have seen all the _content there is
-                        _contentLength = _contentWritten;
-                        if (content_length == null && (isResponse() || _contentLength>0 || content_type ) && !_noContent)
-                        {
-                            // known length but not actually set.
-                            _header.put(HttpHeaders.CONTENT_LENGTH_BUFFER);
-                            _header.put(HttpTokens.COLON);
-                            _header.put((byte) ' ');
-                            BufferUtil.putDecLong(_header, _contentLength);
-                            _header.put(HttpTokens.CRLF);
-                        }
+                        // known length but not actually set.
+                        header.put(HttpHeader.CONTENT_LENGTH.getBytesColonSpace());
+                        BufferUtil.putDecLong(header, content_length);
+                        header.put(HttpTokens.CRLF);
                     }
-                    else
+                }
+                else if (last)
+                {
+                    // we have seen all the _content there is, so we can be content-length limited.
+                    _endOfContent=EndOfContent.CONTENT_LENGTH;
+                    long content_length = _contentPrepared+BufferUtil.length(content);
+
+                    // Do we need to tell the headers about it
+                    if ((response!=null || content_length>0 || content_type ) && !_noContent)
                     {
-                        // No idea, so we must assume that a body is coming
-                        _contentLength = (!_persistent || _version < HttpVersions.HTTP_1_1_ORDINAL ) ? HttpTokens.EOF_CONTENT : HttpTokens.CHUNKED_CONTENT;
-                        if (isRequest() && _contentLength==HttpTokens.EOF_CONTENT)
-                        {
-                            _contentLength=HttpTokens.NO_CONTENT;
-                            _noContent=true;
-                        }
+                        header.put(HttpHeader.CONTENT_LENGTH.getBytesColonSpace());
+                        BufferUtil.putDecLong(header, content_length);
+                        header.put(HttpTokens.CRLF);
                     }
-                    break;
+                }
+                else
+                {
+                    // No idea, so we must assume that a body is coming.
+                    _endOfContent = EndOfContent.CHUNKED_CONTENT;
+                    // HTTP 1.0 does not understand chunked content, so we must use EOF content.
+                    // For a request with HTTP 1.0 & Connection: keep-alive
+                    // we *must* close the connection, otherwise the client
+                    // has no way to detect the end of the content.
+                    if (!isPersistent() || _info.getHttpVersion().ordinal() < HttpVersion.HTTP_1_1.ordinal())
+                        _endOfContent = EndOfContent.EOF_CONTENT;
+                }
+                break;
 
-                case HttpTokens.NO_CONTENT:
-                    if (content_length == null && isResponse() && _status >= 200 && _status != 204 && _status != 304)
-                        _header.put(CONTENT_LENGTH_0);
-                    break;
+            case CONTENT_LENGTH:
+                long content_length = _info.getContentLength();
+                if ((response!=null || content_length>0 || content_type ) && !_noContent)
+                {
+                    // known length but not actually set.
+                    header.put(HttpHeader.CONTENT_LENGTH.getBytesColonSpace());
+                    BufferUtil.putDecLong(header, content_length);
+                    header.put(HttpTokens.CRLF);
+                }
+                break;
 
-                case HttpTokens.EOF_CONTENT:
-                    _persistent = isRequest();
-                    break;
+            case NO_CONTENT:
+                if (response!=null && status >= 200 && status != 204 && status != 304)
+                    header.put(CONTENT_LENGTH_0);
+                break;
 
-                case HttpTokens.CHUNKED_CONTENT:
-                    break;
+            case EOF_CONTENT:
+                _persistent = request!=null;
+                break;
 
-                default:
-                    // TODO - maybe allow forced chunking by setting te ???
-                    break;
-            }
+            case CHUNKED_CONTENT:
+                break;
 
-            // Add transfer_encoding if needed
-            if (_contentLength == HttpTokens.CHUNKED_CONTENT)
+            default:
+                break;
+        }
+
+        // Add transfer_encoding if needed
+        if (isChunking())
+        {
+            // try to use user supplied encoding as it may have other values.
+            if (transfer_encoding != null && !HttpHeaderValue.CHUNKED.toString().equalsIgnoreCase(transfer_encoding.getValue()))
             {
-                // try to use user supplied encoding as it may have other values.
-                if (transfer_encoding != null && HttpHeaderValues.CHUNKED_ORDINAL != transfer_encoding.getValueOrdinal())
-                {
-                    String c = transfer_encoding.getValue();
-                    if (c.endsWith(HttpHeaderValues.CHUNKED))
-                        transfer_encoding.putTo(_header);
-                    else
-                        throw new IllegalArgumentException("BAD TE");
-                }
+                String c = transfer_encoding.getValue();
+                if (c.endsWith(HttpHeaderValue.CHUNKED.toString()))
+                    putTo(transfer_encoding,header);
                 else
-                    _header.put(TRANSFER_ENCODING_CHUNKED);
+                    throw new IllegalArgumentException("BAD TE");
             }
+            else
+                header.put(TRANSFER_ENCODING_CHUNKED);
+        }
 
-            // Handle connection if need be
-            if (_contentLength==HttpTokens.EOF_CONTENT)
-            {
-                keep_alive=false;
-                _persistent=false;
-            }
+        // Handle connection if need be
+        if (_endOfContent==EndOfContent.EOF_CONTENT)
+        {
+            keep_alive=false;
+            _persistent=false;
+        }
 
-            if (isResponse())
+        // If this is a response, work out persistence
+        if (response!=null)
+        {
+            if (!isPersistent() && (close || _info.getHttpVersion().ordinal() > HttpVersion.HTTP_1_0.ordinal()))
             {
-                if (!_persistent && (close || _version > HttpVersions.HTTP_1_0_ORDINAL))
-                {
-                    _header.put(CONNECTION_CLOSE);
-                    if (connection!=null)
-                    {
-                        _header.setPutIndex(_header.putIndex()-2);
-                        _header.put((byte)',');
-                        _header.put(connection.toString().getBytes());
-                        _header.put(CRLF);
-                    }
-                }
-                else if (keep_alive)
+                if (connection==null)
+                    header.put(CONNECTION_CLOSE);
+                else
                 {
-                    _header.put(CONNECTION_KEEP_ALIVE);
-                    if (connection!=null)
-                    {
-                        _header.setPutIndex(_header.putIndex()-2);
-                        _header.put((byte)',');
-                        _header.put(connection.toString().getBytes());
-                        _header.put(CRLF);
-                    }
+                    header.put(CONNECTION_CLOSE,0,CONNECTION_CLOSE.length-2);
+                    header.put((byte)',');
+                    header.put(StringUtil.getBytes(connection.toString()));
+                    header.put(CRLF);
                 }
-                else if (connection!=null)
+            }
+            else if (keep_alive)
+            {
+                if (connection==null)
+                    header.put(CONNECTION_KEEP_ALIVE);
+                else
                 {
-                    _header.put(CONNECTION_);
-                    _header.put(connection.toString().getBytes());
-                    _header.put(CRLF);
+                    header.put(CONNECTION_KEEP_ALIVE,0,CONNECTION_KEEP_ALIVE.length-2);
+                    header.put((byte)',');
+                    header.put(StringUtil.getBytes(connection.toString()));
+                    header.put(CRLF);
                 }
             }
+            else if (connection!=null)
+            {
+                header.put(HttpHeader.CONNECTION.getBytesColonSpace());
+                header.put(StringUtil.getBytes(connection.toString()));
+                header.put(CRLF);
+            }
+        }
 
-            if (!has_server && _status>199 && getSendServerVersion())
-                _header.put(SERVER);
+        if (status>199)
+            header.put(SEND[send]);
 
-            // end the header.
-            _header.put(HttpTokens.CRLF);
-            _state = STATE_CONTENT;
+        // end the header.
+        header.put(HttpTokens.CRLF);
+    }
 
-        }
-        catch(ArrayIndexOutOfBoundsException e)
-        {
-            throw new RuntimeException("Header>"+_header.capacity(),e);
-        }
+    /* ------------------------------------------------------------------------------- */
+    public static byte[] getReasonBuffer(int code)
+    {
+        PreparedResponse status = code<__preprepared.length?__preprepared[code]:null;
+        if (status!=null)
+            return status._reason;
+        return null;
     }
 
-    /* ------------------------------------------------------------ */
-    /**
-     * Complete the message.
-     *
-     * @throws IOException
-     */
+    /* ------------------------------------------------------------------------------- */
     @Override
-    public void complete() throws IOException
+    public String toString()
     {
-        if (_state == STATE_END)
-            return;
+        return String.format("%s{s=%s}",
+                getClass().getSimpleName(),
+                _state);
+    }
 
-        super.complete();
+    /* ------------------------------------------------------------------------------- */
+    /* ------------------------------------------------------------------------------- */
+    /* ------------------------------------------------------------------------------- */
+    // common _content
+    private static final byte[] LAST_CHUNK =    { (byte) '0', (byte) '\015', (byte) '\012', (byte) '\015', (byte) '\012'};
+    private static final byte[] CONTENT_LENGTH_0 = StringUtil.getBytes("Content-Length: 0\015\012");
+    private static final byte[] CONNECTION_KEEP_ALIVE = StringUtil.getBytes("Connection: keep-alive\015\012");
+    private static final byte[] CONNECTION_CLOSE = StringUtil.getBytes("Connection: close\015\012");
+    private static final byte[] HTTP_1_1_SPACE = StringUtil.getBytes(HttpVersion.HTTP_1_1+" ");
+    private static final byte[] CRLF = StringUtil.getBytes("\015\012");
+    private static final byte[] TRANSFER_ENCODING_CHUNKED = StringUtil.getBytes("Transfer-Encoding: chunked\015\012");
+    private static final byte[][] SEND = new byte[][]{
+            new byte[0],
+            StringUtil.getBytes("Server: Jetty(9.x.x)\015\012"),
+        StringUtil.getBytes("X-Powered-By: Jetty(9.x.x)\015\012"),
+        StringUtil.getBytes("Server: Jetty(9.x.x)\015\012X-Powered-By: Jetty(9.x.x)\015\012")
+    };
 
-        if (_state < STATE_FLUSHING)
+    /* ------------------------------------------------------------------------------- */
+    /* ------------------------------------------------------------------------------- */
+    /* ------------------------------------------------------------------------------- */
+    // Build cache of response lines for status
+    private static class PreparedResponse
+    {
+        byte[] _reason;
+        byte[] _schemeCode;
+        byte[] _responseLine;
+    }
+    private static final PreparedResponse[] __preprepared = new PreparedResponse[HttpStatus.MAX_CODE+1];
+    static
+    {
+        int versionLength=HttpVersion.HTTP_1_1.toString().length();
+
+        for (int i=0;i<__preprepared.length;i++)
         {
-            _state = STATE_FLUSHING;
-            if (_contentLength == HttpTokens.CHUNKED_CONTENT)
-                _needEOC = true;
+            HttpStatus.Code code = HttpStatus.getCode(i);
+            if (code==null)
+                continue;
+            String reason=code.getMessage();
+            byte[] line=new byte[versionLength+5+reason.length()+2];
+            HttpVersion.HTTP_1_1.toBuffer().get(line,0,versionLength);
+            line[versionLength+0]=' ';
+            line[versionLength+1]=(byte)('0'+i/100);
+            line[versionLength+2]=(byte)('0'+(i%100)/10);
+            line[versionLength+3]=(byte)('0'+(i%10));
+            line[versionLength+4]=' ';
+            for (int j=0;j<reason.length();j++)
+                line[versionLength+5+j]=(byte)reason.charAt(j);
+            line[versionLength+5+reason.length()]=HttpTokens.CARRIAGE_RETURN;
+            line[versionLength+6+reason.length()]=HttpTokens.LINE_FEED;
+
+            __preprepared[i] = new PreparedResponse();
+            __preprepared[i]._schemeCode = Arrays.copyOfRange(line, 0,versionLength+5);
+            __preprepared[i]._reason = Arrays.copyOfRange(line, versionLength+5, line.length-2);
+            __preprepared[i]._responseLine=line;
         }
-
-        flushBuffer();
     }
 
-    /* ------------------------------------------------------------ */
-    @Override
-    public int flushBuffer() throws IOException
+    public static class Info
     {
-        try
-        {
-
-            if (_state == STATE_HEADER)
-                throw new IllegalStateException("State==HEADER");
+        final HttpVersion _httpVersion;
+        final HttpFields _httpFields;
+        final long _contentLength;
 
-            prepareBuffers();
+        private Info(HttpVersion httpVersion, HttpFields httpFields, long contentLength)
+        {
+            _httpVersion = httpVersion;
+            _httpFields = httpFields;
+            _contentLength = contentLength;
+        }
 
-            if (_endp == null)
-            {
-                if (_needCRLF && _buffer!=null)
-                    _buffer.put(HttpTokens.CRLF);
-                if (_needEOC && _buffer!=null && !_head)
-                    _buffer.put(LAST_CHUNK);
-                _needCRLF=false;
-                _needEOC=false;
-                return 0;
-            }
+        public HttpVersion getHttpVersion()
+        {
+            return _httpVersion;
+        }
+        public HttpFields getHttpFields()
+        {
+            return _httpFields;
+        }
+        public long getContentLength()
+        {
+            return _contentLength;
+        }
+    }
 
-            int total= 0;
+    public static class RequestInfo extends Info
+    {
+        private final String _method;
+        private final String _uri;
 
-            int len = -1;
-            int to_flush = flushMask();
-            int last_flush;
+        public RequestInfo(HttpVersion httpVersion, HttpFields httpFields, long contentLength, String method, String uri)
+        {
+            super(httpVersion,httpFields,contentLength);
+            _method = method;
+            _uri = uri;
+        }
 
-            do
-            {
-                last_flush=to_flush;
-                switch (to_flush)
-                {
-                    case 7:
-                        throw new IllegalStateException(); // should never happen!
-                    case 6:
-                        len = _endp.flush(_header, _buffer, null);
-                        break;
-                    case 5:
-                        len = _endp.flush(_header, _content, null);
-                        break;
-                    case 4:
-                        len = _endp.flush(_header);
-                        break;
-                    case 3:
-                        len = _endp.flush(_buffer, _content, null);
-                        break;
-                    case 2:
-                        len = _endp.flush(_buffer);
-                        break;
-                    case 1:
-                        len = _endp.flush(_content);
-                        break;
-                    case 0:
-                    {
-                        len=0;
-                        // Nothing more we can write now.
-                        if (_header != null)
-                            _header.clear();
+        public String getMethod()
+        {
+            return _method;
+        }
 
-                        _bypass = false;
-                        _bufferChunked = false;
+        public String getUri()
+        {
+            return _uri;
+        }
 
-                        if (_buffer != null)
-                        {
-                            _buffer.clear();
-                            if (_contentLength == HttpTokens.CHUNKED_CONTENT)
-                            {
-                                // reserve some space for the chunk header
-                                _buffer.setPutIndex(CHUNK_SPACE);
-                                _buffer.setGetIndex(CHUNK_SPACE);
+        @Override
+        public String toString()
+        {
+            return String.format("RequestInfo{%s %s %s,%d}",_method,_uri,_httpVersion,_contentLength);
+        }
+    }
 
-                                // Special case handling for small left over buffer from
-                                // an addContent that caused a buffer flush.
-                                if (_content != null && _content.length() < _buffer.space() && _state != STATE_FLUSHING)
-                                {
-                                    _buffer.put(_content);
-                                    _content.clear();
-                                    _content=null;
-                                }
-                            }
-                        }
+    public static class ResponseInfo extends Info
+    {
+        private final int _status;
+        private final String _reason;
+        private final boolean _head;
 
-                        // Are we completely finished for now?
-                        if (!_needCRLF && !_needEOC && (_content==null || _content.length()==0))
-                        {
-                            if (_state == STATE_FLUSHING)
-                                _state = STATE_END;
+        public ResponseInfo(HttpVersion httpVersion, HttpFields httpFields, long contentLength, int status, String reason, boolean head)
+        {
+            super(httpVersion,httpFields,contentLength);
+            _status = status;
+            _reason = reason;
+            _head = head;
+        }
 
-                            if (_state==STATE_END && _persistent != null && !_persistent && _status!=100 && _method==null)
-                                _endp.shutdownOutput();
-                        }
-                        else
-                            // Try to prepare more to write.
-                            prepareBuffers();
-                    }
+        public boolean isInformational()
+        {
+            return _status>=100 && _status<200;
+        }
 
-                }
+        public int getStatus()
+        {
+            return _status;
+        }
 
-                if (len > 0)
-                    total+=len;
+        public String getReason()
+        {
+            return _reason;
+        }
 
-                to_flush = flushMask();
-            }
-            // loop while progress is being made (OR we have prepared some buffers that might make progress)
-            while (len>0 || (to_flush!=0 && last_flush==0));
+        public boolean isHead()
+        {
+            return _head;
+        }
 
-            return total;
+        @Override
+        public String toString()
+        {
+            return String.format("ResponseInfo{%s %s %s,%d,%b}",_httpVersion,_status,_reason,_contentLength,_head);
         }
-        catch (IOException e)
+    } 
+
+    private static void putSanitisedName(String s,ByteBuffer buffer)
+    {
+        int l=s.length();
+        for (int i=0;i<l;i++)
         {
-            LOG.ignore(e);
-            throw (e instanceof EofException) ? e:new EofException(e);
+            char c=s.charAt(i);
+            
+            if (c<0 || c>0xff || c=='\r' || c=='\n'|| c==':')
+                buffer.put((byte)'?');
+            else
+                buffer.put((byte)(0xff&c));
         }
     }
 
-    /* ------------------------------------------------------------ */
-    private int flushMask()
+    private static void putSanitisedValue(String s,ByteBuffer buffer)
     {
-        return  ((_header != null && _header.length() > 0)?4:0)
-        | ((_buffer != null && _buffer.length() > 0)?2:0)
-        | ((_bypass && _content != null && _content.length() > 0)?1:0);
+        int l=s.length();
+        for (int i=0;i<l;i++)
+        {
+            char c=s.charAt(i);
+            
+            if (c<0 || c>0xff || c=='\r' || c=='\n')
+                buffer.put((byte)' ');
+            else
+                buffer.put((byte)(0xff&c));
+        }
     }
 
-    /* ------------------------------------------------------------ */
-    private void prepareBuffers()
+    public static void putTo(HttpField field, ByteBuffer bufferInFillMode)
     {
-        // if we are not flushing an existing chunk
-        if (!_bufferChunked)
+        if (field instanceof CachedHttpField)
+        {
+            ((CachedHttpField)field).putTo(bufferInFillMode);
+        }
+        else
         {
-            // Refill buffer if possible
-            if (!_bypass && _content != null && _content.length() > 0 && _buffer != null && _buffer.space() > 0)
+            HttpHeader header=field.getHeader();
+            if (header!=null)
             {
-                int len = _buffer.put(_content);
-                _content.skip(len);
-                if (_content.length() == 0)
-                    _content = null;
+                bufferInFillMode.put(header.getBytesColonSpace());
+                putSanitisedValue(field.getValue(),bufferInFillMode);
             }
-
-            // Chunk buffer if need be
-            if (_contentLength == HttpTokens.CHUNKED_CONTENT)
+            else
             {
-                if (_bypass && (_buffer==null||_buffer.length()==0) && _content!=null)
-                {
-                    // this is a bypass write
-                    int size = _content.length();
-                    _bufferChunked = true;
-
-                    if (_header == null)
-                        _header = _buffers.getHeader();
-
-                    // if we need CRLF add this to header
-                    if (_needCRLF)
-                    {
-                        if (_header.length() > 0) throw new IllegalStateException("EOC");
-                        _header.put(HttpTokens.CRLF);
-                        _needCRLF = false;
-                    }
-                    // Add the chunk size to the header
-                    BufferUtil.putHexInt(_header, size);
-                    _header.put(HttpTokens.CRLF);
-
-                    // Need a CRLF after the content
-                    _needCRLF=true;
-                }
-                else if (_buffer!=null)
-                {
-                    int size = _buffer.length();
-                    if (size > 0)
-                    {
-                        // Prepare a chunk!
-                        _bufferChunked = true;
-
-                        // Did we leave space at the start of the buffer.
-                        //noinspection ConstantConditions
-                        if (_buffer.getIndex() == CHUNK_SPACE)
-                        {
-                            // Oh yes, goodie! let's use it then!
-                            _buffer.poke(_buffer.getIndex() - 2, HttpTokens.CRLF, 0, 2);
-                            _buffer.setGetIndex(_buffer.getIndex() - 2);
-                            BufferUtil.prependHexInt(_buffer, size);
-
-                            if (_needCRLF)
-                            {
-                                _buffer.poke(_buffer.getIndex() - 2, HttpTokens.CRLF, 0, 2);
-                                _buffer.setGetIndex(_buffer.getIndex() - 2);
-                                _needCRLF = false;
-                            }
-                        }
-                        else
-                        {
-                            // No space so lets use a header buffer.
-                            if (_header == null)
-                                _header = _buffers.getHeader();
-
-                            if (_needCRLF)
-                            {
-                                if (_header.length() > 0) throw new IllegalStateException("EOC");
-                                _header.put(HttpTokens.CRLF);
-                                _needCRLF = false;
-                            }
-                            BufferUtil.putHexInt(_header, size);
-                            _header.put(HttpTokens.CRLF);
-                        }
-
-                        // Add end chunk trailer.
-                        if (_buffer.space() >= 2)
-                            _buffer.put(HttpTokens.CRLF);
-                        else
-                            _needCRLF = true;
-                    }
-                }
-
-                // If we need EOC and everything written
-                if (_needEOC && (_content == null || _content.length() == 0))
-                {
-                    if (_header == null && _buffer == null)
-                        _header = _buffers.getHeader();
-
-                    if (_needCRLF)
-                    {
-                        if (_buffer == null && _header != null && _header.space() >= HttpTokens.CRLF.length)
-                        {
-                            _header.put(HttpTokens.CRLF);
-                            _needCRLF = false;
-                        }
-                        else if (_buffer!=null && _buffer.space() >= HttpTokens.CRLF.length)
-                        {
-                            _buffer.put(HttpTokens.CRLF);
-                            _needCRLF = false;
-                        }
-                    }
-
-                    if (!_needCRLF && _needEOC)
-                    {
-                        if (_buffer == null && _header != null && _header.space() >= LAST_CHUNK.length)
-                        {
-                            if (!_head)
-                            {
-                                _header.put(LAST_CHUNK);
-                                _bufferChunked=true;
-                            }
-                            _needEOC = false;
-                        }
-                        else if (_buffer!=null && _buffer.space() >= LAST_CHUNK.length)
-                        {
-                            if (!_head)
-                            {
-                                _buffer.put(LAST_CHUNK);
-                                _bufferChunked=true;
-                            }
-                            _needEOC = false;
-                        }
-                    }
-                }
+                putSanitisedName(field.getName(),bufferInFillMode);
+                bufferInFillMode.put(__colon_space);
+                putSanitisedValue(field.getValue(),bufferInFillMode);
             }
-        }
-
-        if (_content != null && _content.length() == 0)
-            _content = null;
 
+            BufferUtil.putCRLF(bufferInFillMode);
+        }
     }
 
-    public int getBytesBuffered()
+    public static void putTo(HttpFields fields, ByteBuffer bufferInFillMode) 
     {
-        return(_header==null?0:_header.length())+
-        (_buffer==null?0:_buffer.length())+
-        (_content==null?0:_content.length());
+        for (HttpField field : fields)
+        {
+            if (field != null)
+                putTo(field,bufferInFillMode);
+        }
+        BufferUtil.putCRLF(bufferInFillMode);
     }
-
-    public boolean isEmpty()
+    
+    public static class CachedHttpField extends HttpField
     {
-        return (_header==null||_header.length()==0) &&
-        (_buffer==null||_buffer.length()==0) &&
-        (_content==null||_content.length()==0);
-    }
-
-    @Override
-    public String toString()
-    {
-        Buffer header=_header;
-        Buffer buffer=_buffer;
-        Buffer content=_content;
-        return String.format("%s{s=%d,h=%d,b=%d,c=%d}",
-                getClass().getSimpleName(),
-                _state,
-                header == null ? -1 : header.length(),
-                buffer == null ? -1 : buffer.length(),
-                content == null ? -1 : content.length());
+        private final byte[] _bytes;
+        public CachedHttpField(HttpHeader header,String value)
+        {
+            super(header,value);
+            int cbl=header.getBytesColonSpace().length;
+            _bytes=Arrays.copyOf(header.getBytesColonSpace(), cbl+value.length()+2);
+            System.arraycopy(value.getBytes(StandardCharsets.ISO_8859_1),0,_bytes,cbl,value.length());
+            _bytes[_bytes.length-2]=(byte)'\r';
+            _bytes[_bytes.length-1]=(byte)'\n';
+        }
+        
+        public void putTo(ByteBuffer bufferInFillMode)
+        {
+            bufferInFillMode.put(_bytes);
+        }
     }
 }
diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpHeader.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpHeader.java
new file mode 100644
index 0000000..a9c7fa0
--- /dev/null
+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpHeader.java
@@ -0,0 +1,178 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.ArrayTrie;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.Trie;
+
+
+public enum HttpHeader
+{
+    /* ------------------------------------------------------------ */
+    /** General Fields.
+     */
+    CONNECTION("Connection"),
+    CACHE_CONTROL("Cache-Control"),
+    DATE("Date"),
+    PRAGMA("Pragma"),
+    PROXY_CONNECTION ("Proxy-Connection"),
+    TRAILER("Trailer"),
+    TRANSFER_ENCODING("Transfer-Encoding"),
+    UPGRADE("Upgrade"),
+    VIA("Via"),
+    WARNING("Warning"),
+    NEGOTIATE("Negotiate"),
+
+    /* ------------------------------------------------------------ */
+    /** Entity Fields.
+     */
+    ALLOW("Allow"),
+    CONTENT_ENCODING("Content-Encoding"),
+    CONTENT_LANGUAGE("Content-Language"),
+    CONTENT_LENGTH("Content-Length"),
+    CONTENT_LOCATION("Content-Location"),
+    CONTENT_MD5("Content-MD5"),
+    CONTENT_RANGE("Content-Range"),
+    CONTENT_TYPE("Content-Type"),
+    EXPIRES("Expires"),
+    LAST_MODIFIED("Last-Modified"),
+
+    /* ------------------------------------------------------------ */
+    /** Request Fields.
+     */
+    ACCEPT("Accept"),
+    ACCEPT_CHARSET("Accept-Charset"),
+    ACCEPT_ENCODING("Accept-Encoding"),
+    ACCEPT_LANGUAGE("Accept-Language"),
+    AUTHORIZATION("Authorization"),
+    EXPECT("Expect"),
+    FORWARDED("Forwarded"),
+    FROM("From"),
+    HOST("Host"),
+    IF_MATCH("If-Match"),
+    IF_MODIFIED_SINCE("If-Modified-Since"),
+    IF_NONE_MATCH("If-None-Match"),
+    IF_RANGE("If-Range"),
+    IF_UNMODIFIED_SINCE("If-Unmodified-Since"),
+    KEEP_ALIVE("Keep-Alive"),
+    MAX_FORWARDS("Max-Forwards"),
+    PROXY_AUTHORIZATION("Proxy-Authorization"),
+    RANGE("Range"),
+    REQUEST_RANGE("Request-Range"),
+    REFERER("Referer"),
+    TE("TE"),
+    USER_AGENT("User-Agent"),
+    X_FORWARDED_FOR("X-Forwarded-For"),
+    X_FORWARDED_PROTO("X-Forwarded-Proto"),
+    X_FORWARDED_SERVER("X-Forwarded-Server"),
+    X_FORWARDED_HOST("X-Forwarded-Host"),
+
+    /* ------------------------------------------------------------ */
+    /** Response Fields.
+     */
+    ACCEPT_RANGES("Accept-Ranges"),
+    AGE("Age"),
+    ETAG("ETag"),
+    LOCATION("Location"),
+    PROXY_AUTHENTICATE("Proxy-Authenticate"),
+    RETRY_AFTER("Retry-After"),
+    SERVER("Server"),
+    SERVLET_ENGINE("Servlet-Engine"),
+    VARY("Vary"),
+    WWW_AUTHENTICATE("WWW-Authenticate"),
+
+    /* ------------------------------------------------------------ */
+    /** Other Fields.
+     */
+    COOKIE("Cookie"),
+    SET_COOKIE("Set-Cookie"),
+    SET_COOKIE2("Set-Cookie2"),
+    MIME_VERSION("MIME-Version"),
+    IDENTITY("identity"),
+    
+    X_POWERED_BY("X-Powered-By"),
+
+    UNKNOWN("::UNKNOWN::");
+
+
+    /* ------------------------------------------------------------ */
+    public final static Trie<HttpHeader> CACHE= new ArrayTrie<>(512);
+    static
+    {
+        for (HttpHeader header : HttpHeader.values())
+            if (header!=UNKNOWN)
+                CACHE.put(header.toString(),header);
+    }
+    
+    private final String _string;
+    private final byte[] _bytes;
+    private final byte[] _bytesColonSpace;
+    private final ByteBuffer _buffer;
+
+    /* ------------------------------------------------------------ */
+    HttpHeader(String s)
+    {
+        _string=s;
+        _bytes=StringUtil.getBytes(s);
+        _bytesColonSpace=StringUtil.getBytes(s+": ");
+        _buffer=ByteBuffer.wrap(_bytes);
+    }
+
+    /* ------------------------------------------------------------ */
+    public ByteBuffer toBuffer()
+    {
+        return _buffer.asReadOnlyBuffer();
+    }
+
+    /* ------------------------------------------------------------ */
+    public byte[] getBytes()
+    {
+        return _bytes;
+    }
+
+    /* ------------------------------------------------------------ */
+    public byte[] getBytesColonSpace()
+    {
+        return _bytesColonSpace;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean is(String s)
+    {
+        return _string.equalsIgnoreCase(s);    
+    }
+
+    /* ------------------------------------------------------------ */
+    public String asString()
+    {
+        return _string;
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        return _string;
+    }
+    
+}
+
diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpHeaderValue.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpHeaderValue.java
new file mode 100644
index 0000000..b33050a
--- /dev/null
+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpHeaderValue.java
@@ -0,0 +1,104 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.nio.ByteBuffer;
+import java.util.EnumSet;
+
+import org.eclipse.jetty.util.ArrayTrie;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Trie;
+
+
+/**
+ * 
+ */
+public enum HttpHeaderValue
+{
+    CLOSE("close"),
+    CHUNKED("chunked"),
+    GZIP("gzip"),
+    IDENTITY("identity"),
+    KEEP_ALIVE("keep-alive"),
+    CONTINUE("100-continue"),
+    PROCESSING("102-processing"),
+    TE("TE"),
+    BYTES("bytes"),
+    NO_CACHE("no-cache"),
+    UPGRADE("Upgrade"),
+    UNKNOWN("::UNKNOWN::");
+
+    /* ------------------------------------------------------------ */
+    public final static Trie<HttpHeaderValue> CACHE= new ArrayTrie<HttpHeaderValue>();
+    static
+    {
+        for (HttpHeaderValue value : HttpHeaderValue.values())
+            if (value!=UNKNOWN)
+                CACHE.put(value.toString(),value);
+    }
+
+    private final String _string;
+    private final ByteBuffer _buffer;
+
+    /* ------------------------------------------------------------ */
+    HttpHeaderValue(String s)
+    {
+        _string=s;
+        _buffer=BufferUtil.toBuffer(s);
+    }
+
+    /* ------------------------------------------------------------ */
+    public ByteBuffer toBuffer()
+    {
+        return _buffer.asReadOnlyBuffer();
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean is(String s)
+    {
+        return _string.equalsIgnoreCase(s);
+    }
+    
+    /* ------------------------------------------------------------ */
+    public String asString()
+    {
+        return _string;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        return _string;
+    }
+
+    /* ------------------------------------------------------------ */
+    private static EnumSet<HttpHeader> __known =
+            EnumSet.of(HttpHeader.CONNECTION,
+                    HttpHeader.TRANSFER_ENCODING,
+                    HttpHeader.CONTENT_ENCODING);
+
+    /* ------------------------------------------------------------ */
+    public static boolean hasKnownValues(HttpHeader header)
+    {
+        if (header==null)
+            return false;
+        return __known.contains(header);
+    }
+}
diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpHeaderValues.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpHeaderValues.java
deleted file mode 100644
index 1339b2a..0000000
--- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpHeaderValues.java
+++ /dev/null
@@ -1,88 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.http;
-
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.BufferCache;
-import org.eclipse.jetty.io.ByteArrayBuffer;
-
-/**
- * Cached HTTP Header values.
- * This class caches the conversion of common HTTP Header values to and from {@link ByteArrayBuffer} instances.
- * The resource "/org/eclipse/jetty/useragents" is checked for a list of common user agents, so that repeated
- * creation of strings for these agents can be avoided.
- * 
- * 
- */
-public class HttpHeaderValues extends BufferCache
-{
-    public final static String
-        CLOSE="close",
-        CHUNKED="chunked",
-        GZIP="gzip",
-        IDENTITY="identity",
-        KEEP_ALIVE="keep-alive",
-        CONTINUE="100-continue",
-        PROCESSING="102-processing",
-        TE="TE",
-        BYTES="bytes",
-        NO_CACHE="no-cache",
-        UPGRADE="Upgrade";
-
-    public final static int
-        CLOSE_ORDINAL=1,
-        CHUNKED_ORDINAL=2,
-        GZIP_ORDINAL=3,
-        IDENTITY_ORDINAL=4,
-        KEEP_ALIVE_ORDINAL=5,
-        CONTINUE_ORDINAL=6,
-        PROCESSING_ORDINAL=7,
-        TE_ORDINAL=8,
-        BYTES_ORDINAL=9,
-        NO_CACHE_ORDINAL=10,
-        UPGRADE_ORDINAL=11;
-    
-    public final static HttpHeaderValues CACHE= new HttpHeaderValues();
-
-    public final static Buffer 
-        CLOSE_BUFFER=CACHE.add(CLOSE,CLOSE_ORDINAL),
-        CHUNKED_BUFFER=CACHE.add(CHUNKED,CHUNKED_ORDINAL),
-        GZIP_BUFFER=CACHE.add(GZIP,GZIP_ORDINAL),
-        IDENTITY_BUFFER=CACHE.add(IDENTITY,IDENTITY_ORDINAL),
-        KEEP_ALIVE_BUFFER=CACHE.add(KEEP_ALIVE,KEEP_ALIVE_ORDINAL),
-        CONTINUE_BUFFER=CACHE.add(CONTINUE, CONTINUE_ORDINAL),
-        PROCESSING_BUFFER=CACHE.add(PROCESSING, PROCESSING_ORDINAL),
-        TE_BUFFER=CACHE.add(TE,TE_ORDINAL),
-        BYTES_BUFFER=CACHE.add(BYTES,BYTES_ORDINAL),
-        NO_CACHE_BUFFER=CACHE.add(NO_CACHE,NO_CACHE_ORDINAL),
-        UPGRADE_BUFFER=CACHE.add(UPGRADE,UPGRADE_ORDINAL);
-        
-
-    public static boolean hasKnownValues(int httpHeaderOrdinal)
-    {
-        switch(httpHeaderOrdinal)
-        {
-            case HttpHeaders.CONNECTION_ORDINAL:
-            case HttpHeaders.TRANSFER_ENCODING_ORDINAL:
-            case HttpHeaders.CONTENT_ENCODING_ORDINAL:
-                return true;
-        }
-        return false;
-    }
-}
diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpHeaders.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpHeaders.java
deleted file mode 100644
index 5ba3d95..0000000
--- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpHeaders.java
+++ /dev/null
@@ -1,241 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.http;
-
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.BufferCache;
-
-/* ------------------------------------------------------------------------------- */
-/** 
- */
-public class HttpHeaders extends BufferCache
-{
-    /* ------------------------------------------------------------ */
-    /** General Fields.
-     */
-    public final static String 
-        CONNECTION= "Connection",
-        CACHE_CONTROL= "Cache-Control",
-        DATE= "Date",
-        PRAGMA= "Pragma",
-        PROXY_CONNECTION = "Proxy-Connection",
-        TRAILER= "Trailer",
-        TRANSFER_ENCODING= "Transfer-Encoding",
-        UPGRADE= "Upgrade",
-        VIA= "Via",
-        WARNING= "Warning",
-        NEGOTIATE= "Negotiate";
-
-    /* ------------------------------------------------------------ */
-    /** Entity Fields.
-     */
-    public final static String ALLOW= "Allow",
-        CONTENT_ENCODING= "Content-Encoding",
-        CONTENT_LANGUAGE= "Content-Language",
-        CONTENT_LENGTH= "Content-Length",
-        CONTENT_LOCATION= "Content-Location",
-        CONTENT_MD5= "Content-MD5",
-        CONTENT_RANGE= "Content-Range",
-        CONTENT_TYPE= "Content-Type",
-        EXPIRES= "Expires",
-        LAST_MODIFIED= "Last-Modified";
-
-    /* ------------------------------------------------------------ */
-    /** Request Fields.
-     */
-    public final static String ACCEPT= "Accept",
-        ACCEPT_CHARSET= "Accept-Charset",
-        ACCEPT_ENCODING= "Accept-Encoding",
-        ACCEPT_LANGUAGE= "Accept-Language",
-        AUTHORIZATION= "Authorization",
-        EXPECT= "Expect",
-        FORWARDED= "Forwarded",
-        FROM= "From",
-        HOST= "Host",
-        IF_MATCH= "If-Match",
-        IF_MODIFIED_SINCE= "If-Modified-Since",
-        IF_NONE_MATCH= "If-None-Match",
-        IF_RANGE= "If-Range",
-        IF_UNMODIFIED_SINCE= "If-Unmodified-Since",
-        KEEP_ALIVE= "Keep-Alive",
-        MAX_FORWARDS= "Max-Forwards",
-        PROXY_AUTHORIZATION= "Proxy-Authorization",
-        RANGE= "Range",
-        REQUEST_RANGE= "Request-Range",
-        REFERER= "Referer",
-        TE= "TE",
-        USER_AGENT= "User-Agent",
-        X_FORWARDED_FOR= "X-Forwarded-For",
-        X_FORWARDED_PROTO= "X-Forwarded-Proto",
-        X_FORWARDED_SERVER= "X-Forwarded-Server",
-        X_FORWARDED_HOST= "X-Forwarded-Host";
-
-    /* ------------------------------------------------------------ */
-    /** Response Fields.
-     */
-    public final static String ACCEPT_RANGES= "Accept-Ranges",
-        AGE= "Age",
-        ETAG= "ETag",
-        LOCATION= "Location",
-        PROXY_AUTHENTICATE= "Proxy-Authenticate",
-        RETRY_AFTER= "Retry-After",
-        SERVER= "Server",
-        SERVLET_ENGINE= "Servlet-Engine",
-        VARY= "Vary",
-        WWW_AUTHENTICATE= "WWW-Authenticate";
-
-    /* ------------------------------------------------------------ */
-    /** Other Fields.
-     */
-    public final static String COOKIE= "Cookie",
-        SET_COOKIE= "Set-Cookie",
-        SET_COOKIE2= "Set-Cookie2",
-        MIME_VERSION= "MIME-Version",
-        IDENTITY= "identity";
-
-    public final static int CONNECTION_ORDINAL= 1,
-        DATE_ORDINAL= 2,
-        PRAGMA_ORDINAL= 3,
-        TRAILER_ORDINAL= 4,
-        TRANSFER_ENCODING_ORDINAL= 5,
-        UPGRADE_ORDINAL= 6,
-        VIA_ORDINAL= 7,
-        WARNING_ORDINAL= 8,
-        ALLOW_ORDINAL= 9,
-        CONTENT_ENCODING_ORDINAL= 10,
-        CONTENT_LANGUAGE_ORDINAL= 11,
-        CONTENT_LENGTH_ORDINAL= 12,
-        CONTENT_LOCATION_ORDINAL= 13,
-        CONTENT_MD5_ORDINAL= 14,
-        CONTENT_RANGE_ORDINAL= 15,
-        CONTENT_TYPE_ORDINAL= 16,
-        EXPIRES_ORDINAL= 17,
-        LAST_MODIFIED_ORDINAL= 18,
-        ACCEPT_ORDINAL= 19,
-        ACCEPT_CHARSET_ORDINAL= 20,
-        ACCEPT_ENCODING_ORDINAL= 21,
-        ACCEPT_LANGUAGE_ORDINAL= 22,
-        AUTHORIZATION_ORDINAL= 23,
-        EXPECT_ORDINAL= 24,
-        FORWARDED_ORDINAL= 25,
-        FROM_ORDINAL= 26,
-        HOST_ORDINAL= 27,
-        IF_MATCH_ORDINAL= 28,
-        IF_MODIFIED_SINCE_ORDINAL= 29,
-        IF_NONE_MATCH_ORDINAL= 30,
-        IF_RANGE_ORDINAL= 31,
-        IF_UNMODIFIED_SINCE_ORDINAL= 32,
-        KEEP_ALIVE_ORDINAL= 33,
-        MAX_FORWARDS_ORDINAL= 34,
-        PROXY_AUTHORIZATION_ORDINAL= 35,
-        RANGE_ORDINAL= 36,
-        REQUEST_RANGE_ORDINAL= 37,
-        REFERER_ORDINAL= 38,
-        TE_ORDINAL= 39,
-        USER_AGENT_ORDINAL= 40,
-        X_FORWARDED_FOR_ORDINAL= 41,
-        ACCEPT_RANGES_ORDINAL= 42,
-        AGE_ORDINAL= 43,
-        ETAG_ORDINAL= 44,
-        LOCATION_ORDINAL= 45,
-        PROXY_AUTHENTICATE_ORDINAL= 46,
-        RETRY_AFTER_ORDINAL= 47,
-        SERVER_ORDINAL= 48,
-        SERVLET_ENGINE_ORDINAL= 49,
-        VARY_ORDINAL= 50,
-        WWW_AUTHENTICATE_ORDINAL= 51,
-        COOKIE_ORDINAL= 52,
-        SET_COOKIE_ORDINAL= 53,
-        SET_COOKIE2_ORDINAL= 54,
-        MIME_VERSION_ORDINAL= 55,
-        IDENTITY_ORDINAL= 56,
-        CACHE_CONTROL_ORDINAL=57,
-        PROXY_CONNECTION_ORDINAL=58,
-        X_FORWARDED_PROTO_ORDINAL=59,
-        X_FORWARDED_SERVER_ORDINAL=60,
-        X_FORWARDED_HOST_ORDINAL=61;
-
-    public final static HttpHeaders CACHE= new HttpHeaders();
-    
-    public final static Buffer
-        HOST_BUFFER=CACHE.add(HOST,HOST_ORDINAL),
-        ACCEPT_BUFFER=CACHE.add(ACCEPT,ACCEPT_ORDINAL),
-        ACCEPT_CHARSET_BUFFER=CACHE.add(ACCEPT_CHARSET,ACCEPT_CHARSET_ORDINAL),
-        ACCEPT_ENCODING_BUFFER=CACHE.add(ACCEPT_ENCODING,ACCEPT_ENCODING_ORDINAL),
-        ACCEPT_LANGUAGE_BUFFER=CACHE.add(ACCEPT_LANGUAGE,ACCEPT_LANGUAGE_ORDINAL),
-        
-        CONTENT_LENGTH_BUFFER=CACHE.add(CONTENT_LENGTH,CONTENT_LENGTH_ORDINAL),
-        CONNECTION_BUFFER=CACHE.add(CONNECTION,CONNECTION_ORDINAL),
-        CACHE_CONTROL_BUFFER=CACHE.add(CACHE_CONTROL,CACHE_CONTROL_ORDINAL),
-        DATE_BUFFER=CACHE.add(DATE,DATE_ORDINAL),
-        PRAGMA_BUFFER=CACHE.add(PRAGMA,PRAGMA_ORDINAL),
-        TRAILER_BUFFER=CACHE.add(TRAILER,TRAILER_ORDINAL),
-        TRANSFER_ENCODING_BUFFER=CACHE.add(TRANSFER_ENCODING,TRANSFER_ENCODING_ORDINAL),
-        UPGRADE_BUFFER=CACHE.add(UPGRADE,UPGRADE_ORDINAL),
-        VIA_BUFFER=CACHE.add(VIA,VIA_ORDINAL),
-        WARNING_BUFFER=CACHE.add(WARNING,WARNING_ORDINAL),
-        ALLOW_BUFFER=CACHE.add(ALLOW,ALLOW_ORDINAL),
-        CONTENT_ENCODING_BUFFER=CACHE.add(CONTENT_ENCODING,CONTENT_ENCODING_ORDINAL),
-        CONTENT_LANGUAGE_BUFFER=CACHE.add(CONTENT_LANGUAGE,CONTENT_LANGUAGE_ORDINAL),
-        CONTENT_LOCATION_BUFFER=CACHE.add(CONTENT_LOCATION,CONTENT_LOCATION_ORDINAL),
-        CONTENT_MD5_BUFFER=CACHE.add(CONTENT_MD5,CONTENT_MD5_ORDINAL),
-        CONTENT_RANGE_BUFFER=CACHE.add(CONTENT_RANGE,CONTENT_RANGE_ORDINAL),
-        CONTENT_TYPE_BUFFER=CACHE.add(CONTENT_TYPE,CONTENT_TYPE_ORDINAL),
-        EXPIRES_BUFFER=CACHE.add(EXPIRES,EXPIRES_ORDINAL),
-        LAST_MODIFIED_BUFFER=CACHE.add(LAST_MODIFIED,LAST_MODIFIED_ORDINAL),
-        AUTHORIZATION_BUFFER=CACHE.add(AUTHORIZATION,AUTHORIZATION_ORDINAL),
-        EXPECT_BUFFER=CACHE.add(EXPECT,EXPECT_ORDINAL),
-        FORWARDED_BUFFER=CACHE.add(FORWARDED,FORWARDED_ORDINAL),
-        FROM_BUFFER=CACHE.add(FROM,FROM_ORDINAL),
-        IF_MATCH_BUFFER=CACHE.add(IF_MATCH,IF_MATCH_ORDINAL),
-        IF_MODIFIED_SINCE_BUFFER=CACHE.add(IF_MODIFIED_SINCE,IF_MODIFIED_SINCE_ORDINAL),
-        IF_NONE_MATCH_BUFFER=CACHE.add(IF_NONE_MATCH,IF_NONE_MATCH_ORDINAL),
-        IF_RANGE_BUFFER=CACHE.add(IF_RANGE,IF_RANGE_ORDINAL),
-        IF_UNMODIFIED_SINCE_BUFFER=CACHE.add(IF_UNMODIFIED_SINCE,IF_UNMODIFIED_SINCE_ORDINAL),
-        KEEP_ALIVE_BUFFER=CACHE.add(KEEP_ALIVE,KEEP_ALIVE_ORDINAL),
-        MAX_FORWARDS_BUFFER=CACHE.add(MAX_FORWARDS,MAX_FORWARDS_ORDINAL),
-        PROXY_AUTHORIZATION_BUFFER=CACHE.add(PROXY_AUTHORIZATION,PROXY_AUTHORIZATION_ORDINAL),
-        RANGE_BUFFER=CACHE.add(RANGE,RANGE_ORDINAL),
-        REQUEST_RANGE_BUFFER=CACHE.add(REQUEST_RANGE,REQUEST_RANGE_ORDINAL),
-        REFERER_BUFFER=CACHE.add(REFERER,REFERER_ORDINAL),
-        TE_BUFFER=CACHE.add(TE,TE_ORDINAL),
-        USER_AGENT_BUFFER=CACHE.add(USER_AGENT,USER_AGENT_ORDINAL),
-        X_FORWARDED_FOR_BUFFER=CACHE.add(X_FORWARDED_FOR,X_FORWARDED_FOR_ORDINAL),
-        X_FORWARDED_PROTO_BUFFER=CACHE.add(X_FORWARDED_PROTO,X_FORWARDED_PROTO_ORDINAL),
-        X_FORWARDED_SERVER_BUFFER=CACHE.add(X_FORWARDED_SERVER,X_FORWARDED_SERVER_ORDINAL),
-        X_FORWARDED_HOST_BUFFER=CACHE.add(X_FORWARDED_HOST,X_FORWARDED_HOST_ORDINAL),
-        ACCEPT_RANGES_BUFFER=CACHE.add(ACCEPT_RANGES,ACCEPT_RANGES_ORDINAL),
-        AGE_BUFFER=CACHE.add(AGE,AGE_ORDINAL),
-        ETAG_BUFFER=CACHE.add(ETAG,ETAG_ORDINAL),
-        LOCATION_BUFFER=CACHE.add(LOCATION,LOCATION_ORDINAL),
-        PROXY_AUTHENTICATE_BUFFER=CACHE.add(PROXY_AUTHENTICATE,PROXY_AUTHENTICATE_ORDINAL),
-        RETRY_AFTER_BUFFER=CACHE.add(RETRY_AFTER,RETRY_AFTER_ORDINAL),
-        SERVER_BUFFER=CACHE.add(SERVER,SERVER_ORDINAL),
-        SERVLET_ENGINE_BUFFER=CACHE.add(SERVLET_ENGINE,SERVLET_ENGINE_ORDINAL),
-        VARY_BUFFER=CACHE.add(VARY,VARY_ORDINAL),
-        WWW_AUTHENTICATE_BUFFER=CACHE.add(WWW_AUTHENTICATE,WWW_AUTHENTICATE_ORDINAL),
-        COOKIE_BUFFER=CACHE.add(COOKIE,COOKIE_ORDINAL),
-        SET_COOKIE_BUFFER=CACHE.add(SET_COOKIE,SET_COOKIE_ORDINAL),
-        SET_COOKIE2_BUFFER=CACHE.add(SET_COOKIE2,SET_COOKIE2_ORDINAL),
-        MIME_VERSION_BUFFER=CACHE.add(MIME_VERSION,MIME_VERSION_ORDINAL),
-        IDENTITY_BUFFER=CACHE.add(IDENTITY,IDENTITY_ORDINAL),
-        PROXY_CONNECTION_BUFFER=CACHE.add(PROXY_CONNECTION,PROXY_CONNECTION_ORDINAL);
-    
-    
-}
diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpMethod.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpMethod.java
new file mode 100644
index 0000000..7904932
--- /dev/null
+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpMethod.java
@@ -0,0 +1,187 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.ArrayTrie;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.Trie;
+
+
+/* ------------------------------------------------------------------------------- */
+/**
+ */
+public enum HttpMethod
+{
+    GET,
+    POST,
+    HEAD,
+    PUT,
+    OPTIONS,
+    DELETE,
+    TRACE,
+    CONNECT,
+    MOVE,
+    PROXY,
+    PRI;
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Optimized lookup to find a method name and trailing space in a byte array.
+     * @param bytes Array containing ISO-8859-1 characters
+     * @param position The first valid index
+     * @param limit The first non valid index
+     * @return A HttpMethod if a match or null if no easy match.
+     */
+    public static HttpMethod lookAheadGet(byte[] bytes, final int position, int limit)
+    {
+        int length=limit-position;
+        if (length<4)
+            return null;
+        switch(bytes[position])
+        {
+            case 'G':
+                if (bytes[position+1]=='E' && bytes[position+2]=='T' && bytes[position+3]==' ')
+                    return GET;
+                break;
+            case 'P':
+                if (bytes[position+1]=='O' && bytes[position+2]=='S' && bytes[position+3]=='T' && length>=5 && bytes[position+4]==' ')
+                    return POST;
+                if (bytes[position+1]=='R' && bytes[position+2]=='O' && bytes[position+3]=='X' && length>=6 && bytes[position+4]=='Y' && bytes[position+5]==' ')
+                    return PROXY;
+                if (bytes[position+1]=='U' && bytes[position+2]=='T' && bytes[position+3]==' ')
+                    return PUT;
+                if (bytes[position+1]=='R' && bytes[position+2]=='I' && bytes[position+3]==' ')
+                    return PRI;
+                break;
+            case 'H':
+                if (bytes[position+1]=='E' && bytes[position+2]=='A' && bytes[position+3]=='D' && length>=5 && bytes[position+4]==' ')
+                    return HEAD;
+                break;
+            case 'O':
+                if (bytes[position+1]=='P' && bytes[position+2]=='T' && bytes[position+3]=='I' && length>=8 &&
+                    bytes[position+4]=='O' && bytes[position+5]=='N' && bytes[position+6]=='S' && bytes[position+7]==' ' )
+                    return OPTIONS;
+                break;
+            case 'D':
+                if (bytes[position+1]=='E' && bytes[position+2]=='L' && bytes[position+3]=='E' && length>=7 &&
+                    bytes[position+4]=='T' && bytes[position+5]=='E' && bytes[position+6]==' ' )
+                    return DELETE;
+                break;
+            case 'T':
+                if (bytes[position+1]=='R' && bytes[position+2]=='A' && bytes[position+3]=='C' && length>=6 &&
+                    bytes[position+4]=='E' && bytes[position+5]==' ' )
+                    return TRACE;
+                break;
+            case 'C':
+                if (bytes[position+1]=='O' && bytes[position+2]=='N' && bytes[position+3]=='N' && length>=8 &&
+                    bytes[position+4]=='E' && bytes[position+5]=='C' && bytes[position+6]=='T' && bytes[position+7]==' ' )
+                    return CONNECT;
+                break;
+            case 'M':
+                if (bytes[position+1]=='O' && bytes[position+2]=='V' && bytes[position+3]=='E' && length>=5 && bytes[position+4]==' ')
+                    return MOVE;
+                break;
+
+            default:
+                break;
+        }
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Optimized lookup to find a method name and trailing space in a byte array.
+     * @param buffer buffer containing ISO-8859-1 characters, it is not modified.
+     * @return A HttpMethod if a match or null if no easy match.
+     */
+    public static HttpMethod lookAheadGet(ByteBuffer buffer)
+    {
+        if (buffer.hasArray())
+            return lookAheadGet(buffer.array(),buffer.arrayOffset()+buffer.position(),buffer.arrayOffset()+buffer.limit());
+        
+        int l = buffer.remaining();
+        if (l>=4)
+        {
+            HttpMethod m = CACHE.getBest(buffer,0,l);
+            if (m!=null)
+            {
+                int ml = m.asString().length();
+                if (l>ml && buffer.get(buffer.position()+ml)==' ')
+                    return m;
+            }
+        }
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    public final static Trie<HttpMethod> CACHE= new ArrayTrie<>();
+    static
+    {
+        for (HttpMethod method : HttpMethod.values())
+            CACHE.put(method.toString(),method);
+    }
+
+    /* ------------------------------------------------------------ */
+    private final ByteBuffer _buffer;
+    private final byte[] _bytes;
+
+    /* ------------------------------------------------------------ */
+    HttpMethod()
+    {
+        _bytes=StringUtil.getBytes(toString());
+        _buffer=ByteBuffer.wrap(_bytes);
+    }
+
+    /* ------------------------------------------------------------ */
+    public byte[] getBytes()
+    {
+        return _bytes;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean is(String s)
+    {
+        return toString().equalsIgnoreCase(s);
+    }
+
+    /* ------------------------------------------------------------ */
+    public ByteBuffer asBuffer()
+    {
+        return _buffer.asReadOnlyBuffer();
+    }
+
+    /* ------------------------------------------------------------ */
+    public String asString()
+    {
+        return toString();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Converts the given String parameter to an HttpMethod
+     * @param method the String to get the equivalent HttpMethod from
+     * @return the HttpMethod or null if the parameter method is unknown
+     */
+    public static HttpMethod fromString(String method)
+    {
+        return CACHE.get(method);
+    }
+}
diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpMethods.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpMethods.java
deleted file mode 100644
index 71912cf..0000000
--- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpMethods.java
+++ /dev/null
@@ -1,64 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.http;
-
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.BufferCache;
-
-/* ------------------------------------------------------------------------------- */
-/** 
- * 
- * 
- */
-public class HttpMethods
-{
-    public final static String GET= "GET",
-        POST= "POST",
-        HEAD= "HEAD",
-        PUT= "PUT",
-        OPTIONS= "OPTIONS",
-        DELETE= "DELETE",
-        TRACE= "TRACE",
-        CONNECT= "CONNECT",
-        MOVE= "MOVE";
-
-    public final static int GET_ORDINAL= 1,
-        POST_ORDINAL= 2,
-        HEAD_ORDINAL= 3,
-        PUT_ORDINAL= 4,
-        OPTIONS_ORDINAL= 5,
-        DELETE_ORDINAL= 6,
-        TRACE_ORDINAL= 7,
-        CONNECT_ORDINAL= 8,
-        MOVE_ORDINAL= 9;
-
-    public final static BufferCache CACHE= new BufferCache();
-
-    public final static Buffer 
-        GET_BUFFER= CACHE.add(GET, GET_ORDINAL),
-        POST_BUFFER= CACHE.add(POST, POST_ORDINAL),
-        HEAD_BUFFER= CACHE.add(HEAD, HEAD_ORDINAL),
-        PUT_BUFFER= CACHE.add(PUT, PUT_ORDINAL),
-        OPTIONS_BUFFER= CACHE.add(OPTIONS, OPTIONS_ORDINAL),
-        DELETE_BUFFER= CACHE.add(DELETE, DELETE_ORDINAL),
-        TRACE_BUFFER= CACHE.add(TRACE, TRACE_ORDINAL),
-        CONNECT_BUFFER= CACHE.add(CONNECT, CONNECT_ORDINAL),
-        MOVE_BUFFER= CACHE.add(MOVE, MOVE_ORDINAL);
-
-}
diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java
index dc09339..f5ff98f 100644
--- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java
+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java
@@ -18,104 +18,236 @@
 
 package org.eclipse.jetty.http;
 
-import java.io.IOException;
-
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.BufferCache.CachedBuffer;
-import org.eclipse.jetty.io.BufferUtil;
-import org.eclipse.jetty.io.Buffers;
-import org.eclipse.jetty.io.ByteArrayBuffer;
-import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.io.EofException;
-import org.eclipse.jetty.io.View;
-import org.eclipse.jetty.io.bio.StreamEndPoint;
+import static org.eclipse.jetty.http.HttpTokens.*;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+
+import org.eclipse.jetty.http.HttpTokens.EndOfContent;
+import org.eclipse.jetty.util.ArrayTernaryTrie;
+import org.eclipse.jetty.util.ArrayTrie;
+import org.eclipse.jetty.util.BufferUtil;
 import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.Trie;
+import org.eclipse.jetty.util.TypeUtil;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
 
-public class HttpParser implements Parser
+
+/* ------------------------------------------------------------ */
+/** A Parser for HTTP 0.9, 1.0 and 1.1
+ * <p>
+ * This parser parses HTTP client and server messages from buffers
+ * passed in the {@link #parseNext(ByteBuffer)} method.  The parsed
+ * elements of the HTTP message are passed as event calls to the 
+ * {@link HttpHandler} instance the parser is constructed with.
+ * If the passed handler is a {@link RequestHandler} then server side
+ * parsing is performed and if it is a {@link ResponseHandler}, then 
+ * client side parsing is done.
+ * </p>
+ * <p>
+ * The contract of the {@link HttpHandler} API is that if a call returns 
+ * true then the call to {@link #parseNext(ByteBuffer)} will return as 
+ * soon as possible also with a true response.  Typically this indicates
+ * that the parsing has reached a stage where the caller should process 
+ * the events accumulated by the handler.    It is the preferred calling
+ * style that handling such as calling a servlet to process a request, 
+ * should be done after a true return from {@link #parseNext(ByteBuffer)}
+ * rather than from within the scope of a call like 
+ * {@link RequestHandler#messageComplete()}
+ * </p>
+ * <p>
+ * For performance, the parse is heavily dependent on the 
+ * {@link Trie#getBest(ByteBuffer, int, int)} method to look ahead in a
+ * single pass for both the structure ( : and CRLF ) and semantic (which
+ * header and value) of a header.  Specifically the static {@link HttpHeader#CACHE}
+ * is used to lookup common combinations of headers and values 
+ * (eg. "Connection: close"), or just header names (eg. "Connection:" ).
+ * For headers who's value is not known statically (eg. Host, COOKIE) then a
+ * per parser dynamic Trie of {@link HttpFields} from previous parsed messages
+ * is used to help the parsing of subsequent messages.
+ * </p>
+ * <p>
+ * If the system property "org.eclipse.jetty.http.HttpParser.STRICT" is set to true,
+ * then the parser will strictly pass on the exact strings received for methods and header
+ * fields.  Otherwise a fast case insensitive string lookup is used that may alter the
+ * case of the method and/or headers
+ * </p>
+ */
+public class HttpParser
 {
-    private static final Logger LOG = Log.getLogger(HttpParser.class);
+    public static final Logger LOG = Log.getLogger(HttpParser.class);
+    public final static boolean __STRICT=Boolean.getBoolean("org.eclipse.jetty.http.HttpParser.STRICT"); 
+    public final static int INITIAL_URI_LENGTH=256;
 
+    /**
+     * Cache of common {@link HttpField}s including: <UL>
+     * <LI>Common static combinations such as:<UL>
+     *   <li>Connection: close
+     *   <li>Accept-Encoding: gzip
+     *   <li>Content-Length: 0
+     * </ul>
+     * <li>Combinations of Content-Type header for common mime types by common charsets
+     * <li>Most common headers with null values so that a lookup will at least
+     * determine the header name even if the name:value combination is not cached
+     * </ul>
+     */
+    public final static Trie<HttpField> CACHE = new ArrayTrie<>(2048);
+    
     // States
-    public static final int STATE_START=-14;
-    public static final int STATE_FIELD0=-13;
-    public static final int STATE_SPACE1=-12;
-    public static final int STATE_STATUS=-11;
-    public static final int STATE_URI=-10;
-    public static final int STATE_SPACE2=-9;
-    public static final int STATE_END0=-8;
-    public static final int STATE_END1=-7;
-    public static final int STATE_FIELD2=-6;
-    public static final int STATE_HEADER=-5;
-    public static final int STATE_HEADER_NAME=-4;
-    public static final int STATE_HEADER_IN_NAME=-3;
-    public static final int STATE_HEADER_VALUE=-2;
-    public static final int STATE_HEADER_IN_VALUE=-1;
-    public static final int STATE_END=0;
-    public static final int STATE_EOF_CONTENT=1;
-    public static final int STATE_CONTENT=2;
-    public static final int STATE_CHUNKED_CONTENT=3;
-    public static final int STATE_CHUNK_SIZE=4;
-    public static final int STATE_CHUNK_PARAMS=5;
-    public static final int STATE_CHUNK=6;
-    public static final int STATE_SEEKING_EOF=7;
-
-    private final EventHandler _handler;
-    private final Buffers _buffers; // source of buffers
-    private final EndPoint _endp;
-    private Buffer _header; // Buffer for header data (and small _content)
-    private Buffer _body; // Buffer for large content
-    private Buffer _buffer; // The current buffer in use (either _header or _content)
-    private CachedBuffer _cached;
-    private final View.CaseInsensitive _tok0; // Saved token: header name, request method or response version
-    private final View.CaseInsensitive _tok1; // Saved token: header value, request URI or response code
-    private String _multiLineValue;
-    private int _responseStatus; // If >0 then we are parsing a response
-    private boolean _forceContentBuffer;
-    private boolean _persistent;
+    public enum State
+    {
+        START,
+        METHOD,
+        RESPONSE_VERSION,
+        SPACE1,
+        STATUS,
+        URI,
+        SPACE2,
+        REQUEST_VERSION,
+        REASON,
+        PROXY,
+        HEADER,
+        HEADER_IN_NAME,
+        HEADER_VALUE,
+        HEADER_IN_VALUE,
+        CONTENT,
+        EOF_CONTENT,
+        CHUNKED_CONTENT,
+        CHUNK_SIZE,
+        CHUNK_PARAMS,
+        CHUNK,
+        CHUNK_END,
+        END,
+        CLOSED
+    }
+
+    private final boolean DEBUG=LOG.isDebugEnabled(); // Cache debug to help branch prediction
+    private final HttpHandler<ByteBuffer> _handler;
+    private final RequestHandler<ByteBuffer> _requestHandler;
+    private final ResponseHandler<ByteBuffer> _responseHandler;
+    private final int _maxHeaderBytes;
+    private final boolean _strict;
+    private HttpField _field;
+    private HttpHeader _header;
+    private String _headerString;
+    private HttpHeaderValue _value;
+    private String _valueString;
+    private int _responseStatus;
+    private int _headerBytes;
+    private boolean _host;
 
     /* ------------------------------------------------------------------------------- */
-    protected final View  _contentView=new View(); // View of the content in the buffer for {@link Input}
-    protected int _state=STATE_START;
-    protected byte _eol;
-    protected int _length;
-    protected long _contentLength;
-    protected long _contentPosition;
-    protected int _chunkLength;
-    protected int _chunkPosition;
+    private volatile State _state=State.START;
+    private volatile boolean _eof;
+    private volatile boolean _closed;
+    private HttpMethod _method;
+    private String _methodString;
+    private HttpVersion _version;
+    private ByteBuffer _uri=ByteBuffer.allocate(INITIAL_URI_LENGTH); // Tune?
+    private EndOfContent _endOfContent;
+    private long _contentLength;
+    private long _contentPosition;
+    private int _chunkLength;
+    private int _chunkPosition;
     private boolean _headResponse;
+    private boolean _cr;
+    private ByteBuffer _contentChunk;
+    private Trie<HttpField> _connectionFields;
+
+    private int _length;
+    private final StringBuilder _string=new StringBuilder();
+
+    static
+    {
+        CACHE.put(new HttpField(HttpHeader.CONNECTION,HttpHeaderValue.CLOSE));
+        CACHE.put(new HttpField(HttpHeader.CONNECTION,HttpHeaderValue.KEEP_ALIVE));
+        CACHE.put(new HttpField(HttpHeader.CONNECTION,HttpHeaderValue.UPGRADE));
+        CACHE.put(new HttpField(HttpHeader.ACCEPT_ENCODING,"gzip"));
+        CACHE.put(new HttpField(HttpHeader.ACCEPT_ENCODING,"gzip, deflate"));
+        CACHE.put(new HttpField(HttpHeader.ACCEPT_ENCODING,"gzip,deflate,sdch"));
+        CACHE.put(new HttpField(HttpHeader.ACCEPT_LANGUAGE,"en-US,en;q=0.5"));
+        CACHE.put(new HttpField(HttpHeader.ACCEPT_LANGUAGE,"en-GB,en-US;q=0.8,en;q=0.6"));
+        CACHE.put(new HttpField(HttpHeader.ACCEPT_CHARSET,"ISO-8859-1,utf-8;q=0.7,*;q=0.3"));
+        CACHE.put(new HttpField(HttpHeader.ACCEPT,"*/*"));
+        CACHE.put(new HttpField(HttpHeader.ACCEPT,"image/png,image/*;q=0.8,*/*;q=0.5"));
+        CACHE.put(new HttpField(HttpHeader.ACCEPT,"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"));
+        CACHE.put(new HttpField(HttpHeader.PRAGMA,"no-cache"));
+        CACHE.put(new HttpField(HttpHeader.CACHE_CONTROL,"private, no-cache, no-cache=Set-Cookie, proxy-revalidate"));
+        CACHE.put(new HttpField(HttpHeader.CACHE_CONTROL,"no-cache"));
+        CACHE.put(new HttpField(HttpHeader.CONTENT_LENGTH,"0"));
+        CACHE.put(new HttpField(HttpHeader.CONTENT_ENCODING,"gzip"));
+        CACHE.put(new HttpField(HttpHeader.CONTENT_ENCODING,"deflate"));
+        CACHE.put(new HttpField(HttpHeader.TRANSFER_ENCODING,"chunked"));
+        CACHE.put(new HttpField(HttpHeader.EXPIRES,"Fri, 01 Jan 1990 00:00:00 GMT"));
+        
+        // Add common Content types as fields
+        for (String type : new String[]{"text/plain","text/html","text/xml","text/json","application/json","application/x-www-form-urlencoded"})
+        {
+            HttpField field=new HttpGenerator.CachedHttpField(HttpHeader.CONTENT_TYPE,type);
+            CACHE.put(field);
+            
+            for (String charset : new String[]{"UTF-8","ISO-8859-1"})
+            {
+                CACHE.put(new HttpGenerator.CachedHttpField(HttpHeader.CONTENT_TYPE,type+";charset="+charset));
+                CACHE.put(new HttpGenerator.CachedHttpField(HttpHeader.CONTENT_TYPE,type+"; charset="+charset));
+            }
+        }
+    
+        // Add headers with null values so HttpParser can avoid looking up name again for unknown values
+        for (HttpHeader h:HttpHeader.values())
+            if (!CACHE.put(new HttpField(h,(String)null)))
+                throw new IllegalStateException("CACHE FULL");
+        // Add some more common headers
+        CACHE.put(new HttpField(HttpHeader.REFERER,(String)null));
+        CACHE.put(new HttpField(HttpHeader.IF_MODIFIED_SINCE,(String)null));
+        CACHE.put(new HttpField(HttpHeader.IF_NONE_MATCH,(String)null));
+        CACHE.put(new HttpField(HttpHeader.AUTHORIZATION,(String)null));
+        CACHE.put(new HttpField(HttpHeader.COOKIE,(String)null));
+    }
 
     /* ------------------------------------------------------------------------------- */
-    /**
-     * Constructor.
-     */
-    public HttpParser(Buffer buffer, EventHandler handler)
+    public HttpParser(RequestHandler<ByteBuffer> handler)
     {
-        _endp=null;
-        _buffers=null;
-        _header=buffer;
-        _buffer=buffer;
-        _handler=handler;
+        this(handler,-1,__STRICT);
+    }
 
-        _tok0=new View.CaseInsensitive(_header);
-        _tok1=new View.CaseInsensitive(_header);
+    /* ------------------------------------------------------------------------------- */
+    public HttpParser(ResponseHandler<ByteBuffer> handler)
+    {
+        this(handler,-1,__STRICT);
     }
 
     /* ------------------------------------------------------------------------------- */
-    /**
-     * Constructor.
-     * @param buffers the buffers to use
-     * @param endp the endpoint
-     * @param handler the even handler
-     */
-    public HttpParser(Buffers buffers, EndPoint endp, EventHandler handler)
+    public HttpParser(RequestHandler<ByteBuffer> handler,int maxHeaderBytes)
+    {
+        this(handler,maxHeaderBytes,__STRICT);
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    public HttpParser(ResponseHandler<ByteBuffer> handler,int maxHeaderBytes)
+    {
+        this(handler,maxHeaderBytes,__STRICT);
+    }
+    
+    /* ------------------------------------------------------------------------------- */
+    public HttpParser(RequestHandler<ByteBuffer> handler,int maxHeaderBytes,boolean strict)
+    {
+        _handler=handler;
+        _requestHandler=handler;
+        _responseHandler=null;
+        _maxHeaderBytes=maxHeaderBytes;
+        _strict=strict;
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    public HttpParser(ResponseHandler<ByteBuffer> handler,int maxHeaderBytes,boolean strict)
     {
-        _buffers=buffers;
-        _endp=endp;
         _handler=handler;
-        _tok0=new View.CaseInsensitive();
-        _tok1=new View.CaseInsensitive();
+        _requestHandler=null;
+        _responseHandler=handler;
+        _maxHeaderBytes=maxHeaderBytes;
+        _strict=strict;
     }
 
     /* ------------------------------------------------------------------------------- */
@@ -140,7 +272,13 @@ public class HttpParser implements Parser
     }
 
     /* ------------------------------------------------------------------------------- */
-    public int getState()
+    protected void setResponseStatus(int status)
+    {
+        _responseStatus=status;
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    public State getState()
     {
         return _state;
     }
@@ -148,1132 +286,1437 @@ public class HttpParser implements Parser
     /* ------------------------------------------------------------------------------- */
     public boolean inContentState()
     {
-        return _state > 0;
+        return _state.ordinal()>=State.CONTENT.ordinal() && _state.ordinal()<State.END.ordinal();
     }
 
     /* ------------------------------------------------------------------------------- */
     public boolean inHeaderState()
     {
-        return _state < 0;
+        return _state.ordinal() < State.CONTENT.ordinal();
     }
 
     /* ------------------------------------------------------------------------------- */
     public boolean isChunking()
     {
-        return _contentLength==HttpTokens.CHUNKED_CONTENT;
+        return _endOfContent==EndOfContent.CHUNKED_CONTENT;
     }
 
     /* ------------------------------------------------------------ */
-    public boolean isIdle()
+    public boolean isStart()
     {
-        return isState(STATE_START);
+        return isState(State.START);
     }
 
     /* ------------------------------------------------------------ */
-    public boolean isComplete()
+    public boolean isClosed()
     {
-        return isState(STATE_END);
+        return isState(State.CLOSED);
     }
 
     /* ------------------------------------------------------------ */
-    public boolean isMoreInBuffer()
-    throws IOException
+    public boolean isIdle()
     {
-        return ( _header!=null && _header.hasContent() ||
-             _body!=null && _body.hasContent());
+        return isState(State.START)||isState(State.END)||isState(State.CLOSED);
     }
 
-    /* ------------------------------------------------------------------------------- */
-    public boolean isState(int state)
+    /* ------------------------------------------------------------ */
+    public boolean isComplete()
     {
-        return _state == state;
+        return isState(State.END)||isState(State.CLOSED);
     }
 
     /* ------------------------------------------------------------------------------- */
-    public boolean isPersistent()
+    public boolean isState(State state)
     {
-        return _persistent;
+        return _state == state;
     }
 
     /* ------------------------------------------------------------------------------- */
-    public void setPersistent(boolean persistent)
+    @SuppressWarnings("serial")
+    private static class BadMessageException extends RuntimeException
     {
-        _persistent = persistent;
-        if (!_persistent &&(_state==STATE_END || _state==STATE_START))
-            _state=STATE_SEEKING_EOF;
-    }
+        private final int _code;
 
-    /* ------------------------------------------------------------------------------- */
-    /**
-     * Parse until {@link #STATE_END END} state.
-     * If the parser is already in the END state, then it is {@link #reset reset} and re-parsed.
-     * @throws IllegalStateException If the buffers have already been partially parsed.
-     */
-    public void parse() throws IOException
-    {
-        if (_state==STATE_END)
-            reset();
-        if (_state!=STATE_START)
-            throw new IllegalStateException("!START");
-
-        // continue parsing
-        while (_state != STATE_END)
-            if (parseNext()<0)
-                return;
+        private BadMessageException()
+        {
+            this(400,null);
+        }
+        
+        private BadMessageException(int code)
+        {
+            this(code,null);
+        }
+        
+        private BadMessageException(String message)
+        {
+            this(400,message);
+        }
+        
+        private BadMessageException(int code,String message)
+        {
+            super(message);
+            _code=code;
+        }
     }
-
+    
     /* ------------------------------------------------------------------------------- */
-    /**
-     * Parse until END state.
-     * This method will parse any remaining content in the current buffer as long as there is
-     * no unconsumed content. It does not care about the {@link #getState current state} of the parser.
-     * @see #parse
-     * @see #parseNext
-     */
-    public boolean parseAvailable() throws IOException
+    private byte next(ByteBuffer buffer)
     {
-        boolean progress=parseNext()>0;
+        byte ch = buffer.get();
+        
+        if (_cr)
+        {
+            if (ch!=LINE_FEED)
+                throw new BadMessageException("Bad EOL");
+            _cr=false;
+            return ch;
+        }
 
-        // continue parsing
-        while (!isComplete() && _buffer!=null && _buffer.length()>0 && !_contentView.hasContent())
+        if (ch>=0 && ch<SPACE)
         {
-            progress |= parseNext()>0;
+            if (ch==CARRIAGE_RETURN)
+            {
+                if (buffer.hasRemaining())
+                {
+                    if(_maxHeaderBytes>0 && _state.ordinal()<State.END.ordinal())
+                        _headerBytes++;
+                    ch=buffer.get();
+                    if (ch!=LINE_FEED)
+                        throw new BadMessageException("Bad EOL");
+                }
+                else
+                {
+                    _cr=true;
+                    // Can return 0 here to indicate the need for more characters, 
+                    // because a real 0 in the buffer would cause a BadMessage below 
+                    return 0;
+                }
+            }
+            // Only LF or TAB acceptable special characters
+            else if (!(ch==LINE_FEED || ch==TAB))
+                throw new IllegalCharacterException(_state,ch,buffer);
         }
-        return progress;
+        
+        return ch;
     }
-
-
+    
     /* ------------------------------------------------------------------------------- */
-    /**
-     * Parse until next Event.
-     * @return an indication of progress <0 EOF, 0 no progress, >0 progress.
+    /* Quick lookahead for the start state looking for a request method or a HTTP version,
+     * otherwise skip white space until something else to parse.
      */
-    public int parseNext() throws IOException
-    {
-        try
+    private boolean quickStart(ByteBuffer buffer)
+    {    	
+        if (_requestHandler!=null)
         {
-            int progress=0;
-
-            if (_state == STATE_END)
-                return 0;
-
-            if (_buffer==null)
-                _buffer=getHeaderBuffer();
-
+            _method = HttpMethod.lookAheadGet(buffer);
+            if (_method!=null)
+            {
+                _methodString = _method.asString();
+                buffer.position(buffer.position()+_methodString.length()+1);
+                
+                setState(State.SPACE1);
+                return false;
+            }
+        }
+        else if (_responseHandler!=null)
+        {
+            _version = HttpVersion.lookAheadGet(buffer);
+            if (_version!=null)
+            {
+                buffer.position(buffer.position()+_version.asString().length()+1);
+                setState(State.SPACE1);
+                return false;
+            }
+        }
+        
+        // Quick start look
+        while (_state==State.START && buffer.hasRemaining())
+        {
+            int ch=next(buffer);
 
-            if (_state == STATE_CONTENT && _contentPosition == _contentLength)
+            if (ch > SPACE)
+            {
+                _string.setLength(0);
+                _string.append((char)ch);
+                setState(_requestHandler!=null?State.METHOD:State.RESPONSE_VERSION);
+                return false;
+            }
+            else if (ch==0)
+                break;
+            else if (ch<0)
+                throw new BadMessageException();
+            
+            // count this white space as a header byte to avoid DOS
+            if (_maxHeaderBytes>0 && ++_headerBytes>_maxHeaderBytes)
             {
-                _state=STATE_END;
-                _handler.messageComplete(_contentPosition);
-                return 1;
+                LOG.warn("padding is too large >"+_maxHeaderBytes);
+                throw new BadMessageException(HttpStatus.BAD_REQUEST_400);
             }
+        }
+        return false;
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    private void setString(String s)
+    {
+        _string.setLength(0);
+        _string.append(s);
+        _length=s.length();
+    }
+    
+    /* ------------------------------------------------------------------------------- */
+    private String takeString()
+    {
+        _string.setLength(_length);
+        String s =_string.toString();
+        _string.setLength(0);
+        _length=-1;
+        return s;
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    /* Parse a request or response line
+     */
+    private boolean parseLine(ByteBuffer buffer)
+    {
+        boolean handle=false;
 
-            int length=_buffer.length();
+        // Process headers
+        while (_state.ordinal()<State.HEADER.ordinal() && buffer.hasRemaining() && !handle)
+        {
+            // process each character
+            byte ch=next(buffer);
+            if (ch==0)
+                break;
 
-            // Fill buffer if we can
-            if (length == 0)
+            if (_maxHeaderBytes>0 && ++_headerBytes>_maxHeaderBytes)
             {
-                int filled=-1;
-                IOException ex=null;
-                try
+                if (_state==State.URI)
                 {
-                    filled=fill();
-                    LOG.debug("filled {}/{}",filled,_buffer.length());
+                    LOG.warn("URI is too large >"+_maxHeaderBytes);
+                    throw new BadMessageException(HttpStatus.REQUEST_URI_TOO_LONG_414);
                 }
-                catch(IOException e)
+                else
                 {
-                    LOG.debug(this.toString(),e);
-                    ex=e;
+                    if (_requestHandler!=null)
+                        LOG.warn("request is too large >"+_maxHeaderBytes);
+                    else
+                        LOG.warn("response is too large >"+_maxHeaderBytes);
+                    throw new BadMessageException(HttpStatus.REQUEST_ENTITY_TOO_LARGE_413);
                 }
+            }
 
-                if (filled > 0 )
-                    progress++;
-                else if (filled < 0 )
-                {
-                    _persistent=false;
+            switch (_state)
+            {
+                case METHOD:
+                    if (ch == SPACE)
+                    {
+                        _length=_string.length();
+                        _methodString=takeString();
+                        HttpMethod method=HttpMethod.CACHE.get(_methodString);
+                        if (method!=null && !_strict)
+                            _methodString=method.asString();
+                        setState(State.SPACE1);
+                    }
+                    else if (ch < SPACE)
+                    {
+                        if (ch==LINE_FEED)
+                            throw new BadMessageException("No URI");
+                        else
+                            throw new IllegalCharacterException(_state,ch,buffer);
+                    }
+                    else
+                        _string.append((char)ch);
+                    break;
 
-                    // do we have content to deliver?
-                    if (_state>STATE_END)
+                case RESPONSE_VERSION:
+                    if (ch == HttpTokens.SPACE)
+                    {
+                        _length=_string.length();
+                        String version=takeString();
+                        _version=HttpVersion.CACHE.get(version);
+                        if (_version==null)
+                            throw new BadMessageException(HttpStatus.BAD_REQUEST_400,"Unknown Version");
+                        setState(State.SPACE1);
+                    }
+                    else if (ch < HttpTokens.SPACE)
+                        throw new IllegalCharacterException(_state,ch,buffer);
+                    else
+                        _string.append((char)ch);
+                    break;
+
+                case SPACE1:
+                    if (ch > HttpTokens.SPACE || ch<0)
                     {
-                        if (_buffer.length()>0 && !_headResponse)
+                        if (_responseHandler!=null)
+                        {
+                            setState(State.STATUS);
+                            setResponseStatus(ch-'0');
+                        }
+                        else
                         {
-                            Buffer chunk=_buffer.get(_buffer.length());
-                            _contentPosition += chunk.length();
-                            _contentView.update(chunk);
-                            _handler.content(chunk); // May recurse here
+                            _uri.clear();
+                            setState(State.URI);
+                            // quick scan for space or EoBuffer
+                            if (buffer.hasArray())
+                            {
+                                byte[] array=buffer.array();
+                                int p=buffer.arrayOffset()+buffer.position();
+                                int l=buffer.arrayOffset()+buffer.limit();
+                                int i=p;
+                                while (i<l && array[i]>HttpTokens.SPACE)
+                                    i++;
+
+                                int len=i-p;
+                                _headerBytes+=len;
+                                
+                                if (_maxHeaderBytes>0 && ++_headerBytes>_maxHeaderBytes)
+                                {
+                                    LOG.warn("URI is too large >"+_maxHeaderBytes);
+                                    throw new BadMessageException(HttpStatus.REQUEST_URI_TOO_LONG_414);
+                                }
+                                if (_uri.remaining()<=len)
+                                {
+                                    ByteBuffer uri = ByteBuffer.allocate(_uri.capacity()+2*len);
+                                    _uri.flip();
+                                    uri.put(_uri);
+                                    _uri=uri;
+                                }
+                                _uri.put(array,p-1,len+1);
+                                buffer.position(i-buffer.arrayOffset());
+                            }
+                            else
+                                _uri.put(ch);
                         }
                     }
-
-                    // was this unexpected?
-                    switch(_state)
+                    else if (ch < HttpTokens.SPACE)
                     {
-                        case STATE_END:
-                        case STATE_SEEKING_EOF:
-                            _state=STATE_END;
-                            break;
-
-                        case STATE_EOF_CONTENT:
-                            _state=STATE_END;
-                            _handler.messageComplete(_contentPosition);
-                            break;
-
-                        default:
-                            _state=STATE_END;
-                            if (!_headResponse)
-                                _handler.earlyEOF();
-                            _handler.messageComplete(_contentPosition);
+                        throw new BadMessageException(HttpStatus.BAD_REQUEST_400,_requestHandler!=null?"No URI":"No Status");
                     }
+                    break;
 
-                    if (ex!=null)
-                        throw ex;
-
-                    if (!isComplete() && !isIdle())
-                        throw new EofException();
-
-                    return -1;
-                }
-                length=_buffer.length();
-            }
-
-
-            // Handle header states
-            byte ch;
-            byte[] array=_buffer.array();
-            int last=_state;
-            while (_state<STATE_END && length-->0)
-            {
-                if (last!=_state)
-                {
-                    progress++;
-                    last=_state;
-                }
-
-                ch=_buffer.get();
-
-                if (_eol == HttpTokens.CARRIAGE_RETURN)
-                {
-                    if (ch == HttpTokens.LINE_FEED)
+                case STATUS:
+                    if (ch == HttpTokens.SPACE)
                     {
-                        _eol=HttpTokens.LINE_FEED;
-                        continue;
+                        setState(State.SPACE2);
                     }
-                    throw new HttpException(HttpStatus.BAD_REQUEST_400);
-                }
-                _eol=0;
+                    else if (ch>='0' && ch<='9')
+                    {
+                        _responseStatus=_responseStatus*10+(ch-'0');
+                    }
+                    else if (ch < HttpTokens.SPACE && ch>=0)
+                    {
+                        handle=_responseHandler.startResponse(_version, _responseStatus, null)||handle;
+                        setState(State.HEADER);
+                    }
+                    else
+                    {
+                        throw new BadMessageException();
+                    }
+                    break;
 
-                switch (_state)
-                {
-                    case STATE_START:
-                        _contentLength=HttpTokens.UNKNOWN_CONTENT;
-                        _cached=null;
-                        if (ch > HttpTokens.SPACE || ch<0)
+                case URI:
+                    if (ch == HttpTokens.SPACE)
+                    {
+                        setState(State.SPACE2);
+                    }
+                    else if (ch < HttpTokens.SPACE && ch>=0)
+                    {
+                        // HTTP/0.9
+                        _uri.flip();
+                        handle=_requestHandler.startRequest(_method,_methodString,_uri,null)||handle;
+                        setState(State.END);
+                        BufferUtil.clear(buffer);
+                        handle=_handler.headerComplete()||handle;
+                        handle=_handler.messageComplete()||handle;
+                        return handle;
+                    }
+                    else
+                    {
+                        if (!_uri.hasRemaining())
                         {
-                            _buffer.mark();
-                            _state=STATE_FIELD0;
+                            ByteBuffer uri = ByteBuffer.allocate(_uri.capacity()*2);
+                            _uri.flip();
+                            uri.put(_uri);
+                            _uri=uri;
                         }
-                        break;
+                        _uri.put(ch);
+                    }
+                    break;
 
-                    case STATE_FIELD0:
-                        if (ch == HttpTokens.SPACE)
+                case SPACE2:
+                    if (ch > HttpTokens.SPACE)
+                    {
+                        _string.setLength(0);
+                        _string.append((char)ch);
+                        if (_responseHandler!=null)
                         {
-                            _tok0.update(_buffer.markIndex(), _buffer.getIndex() - 1);
-                            _responseStatus=HttpVersions.CACHE.get(_tok0)==null?-1:0;
-                            _state=STATE_SPACE1;
-                            continue;
+                            _length=1;
+                            setState(State.REASON);
                         }
-                        else if (ch < HttpTokens.SPACE && ch>=0)
+                        else
                         {
-                            throw new HttpException(HttpStatus.BAD_REQUEST_400);
-                        }
-                        break;
+                            setState(State.REQUEST_VERSION);
 
-                    case STATE_SPACE1:
-                        if (ch > HttpTokens.SPACE || ch<0)
-                        {
-                            _buffer.mark();
-                            if (_responseStatus>=0)
+                            // try quick look ahead for HTTP Version
+                            HttpVersion version;
+                            if (buffer.position()>0 && buffer.hasArray())
+                                version=HttpVersion.lookAheadGet(buffer.array(),buffer.arrayOffset()+buffer.position()-1,buffer.arrayOffset()+buffer.limit());
+                            else
+                                version=HttpVersion.CACHE.getBest(buffer,0,buffer.remaining());
+                            if (version==null)
                             {
-                                _state=STATE_STATUS;
-                                _responseStatus=ch-'0';
+                                if (_method==HttpMethod.PROXY)
+                                {
+                                    if (!(_requestHandler instanceof ProxyHandler))
+                                        throw new BadMessageException();
+                                    
+                                    _uri.flip();
+                                    String protocol=BufferUtil.toString(_uri);
+                                    // This is the proxy protocol, so we can assume entire first line is in buffer else 400
+                                    buffer.position(buffer.position()-1);
+                                    String sAddr = getProxyField(buffer);
+                                    String dAddr = getProxyField(buffer);
+                                    int sPort = BufferUtil.takeInt(buffer);
+                                    next(buffer);
+                                    int dPort = BufferUtil.takeInt(buffer);
+                                    next(buffer);
+                                    _state=State.START;
+                                    ((ProxyHandler)_requestHandler).proxied(protocol,sAddr,dAddr,sPort,dPort);
+                                    return false;
+                                }
                             }
                             else
-                                _state=STATE_URI;
-                        }
-                        else if (ch < HttpTokens.SPACE)
-                        {
-                            throw new HttpException(HttpStatus.BAD_REQUEST_400);
-                        }
-                        break;
-
-                    case STATE_STATUS:
-                        if (ch == HttpTokens.SPACE)
-                        {
-                            _tok1.update(_buffer.markIndex(), _buffer.getIndex() - 1);
-                            _state=STATE_SPACE2;
-                            continue;
-                        }
-                        else if (ch>='0' && ch<='9')
-                        {
-                            _responseStatus=_responseStatus*10+(ch-'0');
-                            continue;
-                        }
-                        else if (ch < HttpTokens.SPACE && ch>=0)
-                        {
-                            _handler.startResponse(HttpMethods.CACHE.lookup(_tok0), _responseStatus, null);
-                            _eol=ch;
-                            _state=STATE_HEADER;
-                            _tok0.setPutIndex(_tok0.getIndex());
-                            _tok1.setPutIndex(_tok1.getIndex());
-                            _multiLineValue=null;
-                            continue;
+                            {
+                                int pos = buffer.position()+version.asString().length()-1;
+                                if (pos<buffer.limit())
+                                {
+                                    byte n=buffer.get(pos);
+                                    if (n==HttpTokens.CARRIAGE_RETURN)
+                                    {
+                                        _cr=true;
+                                        _version=version;
+                                        _string.setLength(0);
+                                        buffer.position(pos+1);
+                                    }
+                                    else if (n==HttpTokens.LINE_FEED)
+                                    {
+                                        _version=version;
+                                        _string.setLength(0);
+                                        buffer.position(pos);
+                                    }
+                                }
+                            }
                         }
-                        // not a digit, so must be a URI
-                        _state=STATE_URI;
-                        _responseStatus=-1;
-                        break;
-
-                    case STATE_URI:
-                        if (ch == HttpTokens.SPACE)
+                    }
+                    else if (ch == HttpTokens.LINE_FEED)
+                    {
+                        if (_responseHandler!=null)
                         {
-                            _tok1.update(_buffer.markIndex(), _buffer.getIndex() - 1);
-                            _state=STATE_SPACE2;
-                            continue;
+                            handle=_responseHandler.startResponse(_version, _responseStatus, null)||handle;
+                            setState(State.HEADER);
                         }
-                        else if (ch < HttpTokens.SPACE && ch>=0)
+                        else
                         {
                             // HTTP/0.9
-                            _handler.startRequest(HttpMethods.CACHE.lookup(_tok0), _buffer.sliceFromMark(), null);
-                            _persistent=false;
-                            _state=STATE_SEEKING_EOF;
-                            _handler.headerComplete();
-                            _handler.messageComplete(_contentPosition);
-                            return 1;
+                            _uri.flip();
+                            handle=_requestHandler.startRequest(_method,_methodString,_uri, null)||handle;
+                            setState(State.END);
+                            BufferUtil.clear(buffer);
+                            handle=_handler.headerComplete()||handle;
+                            handle=_handler.messageComplete()||handle;
+                            return handle;
                         }
-                        break;
+                    }
+                    else if (ch<0)
+                        throw new BadMessageException();
+                    break;
 
-                    case STATE_SPACE2:
-                        if (ch > HttpTokens.SPACE || ch<0)
-                        {
-                            _buffer.mark();
-                            _state=STATE_FIELD2;
-                        }
-                        else if (ch < HttpTokens.SPACE)
+                case REQUEST_VERSION:
+                    if (ch == HttpTokens.LINE_FEED)
+                    {
+                        if (_version==null)
                         {
-                            if (_responseStatus>0)
-                            {
-                                _handler.startResponse(HttpMethods.CACHE.lookup(_tok0), _responseStatus, null);
-                                _eol=ch;
-                                _state=STATE_HEADER;
-                                _tok0.setPutIndex(_tok0.getIndex());
-                                _tok1.setPutIndex(_tok1.getIndex());
-                                _multiLineValue=null;
-                            }
-                            else
-                            {
-                                // HTTP/0.9
-                                _handler.startRequest(HttpMethods.CACHE.lookup(_tok0), _tok1, null);
-                                _persistent=false;
-                                _state=STATE_SEEKING_EOF;
-                                _handler.headerComplete();
-                                _handler.messageComplete(_contentPosition);
-                                return 1;
-                            }
+                            _length=_string.length();
+                            _version=HttpVersion.CACHE.get(takeString());
                         }
-                        break;
-
-                    case STATE_FIELD2:
-                        if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED)
+                        if (_version==null)
+                            throw new BadMessageException(HttpStatus.BAD_REQUEST_400,"Unknown Version");
+                        
+                        // Should we try to cache header fields?
+                        if (_connectionFields==null && _version.getVersion()>=HttpVersion.HTTP_1_1.getVersion())
                         {
-                            Buffer version;
-                            if (_responseStatus>0)
-                                _handler.startResponse(version=HttpVersions.CACHE.lookup(_tok0), _responseStatus,_buffer.sliceFromMark());
-                            else
-                                _handler.startRequest(HttpMethods.CACHE.lookup(_tok0), _tok1, version=HttpVersions.CACHE.lookup(_buffer.sliceFromMark()));
-                            _eol=ch;
-                            _persistent=HttpVersions.CACHE.getOrdinal(version)>=HttpVersions.HTTP_1_1_ORDINAL;
-                            _state=STATE_HEADER;
-                            _tok0.setPutIndex(_tok0.getIndex());
-                            _tok1.setPutIndex(_tok1.getIndex());
-                            _multiLineValue=null;
-                            continue;
+                            int header_cache = _handler.getHeaderCacheSize();
+                            _connectionFields=new ArrayTernaryTrie<>(header_cache);                            
                         }
-                        break;
 
-                    case STATE_HEADER:
-                        switch(ch)
-                        {
-                            case HttpTokens.COLON:
-                            case HttpTokens.SPACE:
-                            case HttpTokens.TAB:
-                            {
-                                // header value without name - continuation?
-                                _length=-1;
-                                _state=STATE_HEADER_VALUE;
-                                break;
-                            }
+                        setState(State.HEADER);
+                        _uri.flip();
+                        handle=_requestHandler.startRequest(_method,_methodString,_uri, _version)||handle;
+                        continue;
+                    }
+                    else if (ch>=HttpTokens.SPACE)
+                        _string.append((char)ch);
+                    else
+                        throw new BadMessageException();
 
-                            default:
-                            {
-                                // handler last header if any
-                                if (_cached!=null || _tok0.length() > 0 || _tok1.length() > 0 || _multiLineValue != null)
-                                {
-                                    Buffer header=_cached!=null?_cached:HttpHeaders.CACHE.lookup(_tok0);
-                                    _cached=null;
-                                    Buffer value=_multiLineValue == null ? _tok1 : new ByteArrayBuffer(_multiLineValue);
+                    break;
 
-                                    int ho=HttpHeaders.CACHE.getOrdinal(header);
-                                    if (ho >= 0)
-                                    {
-                                        int vo;
+                case REASON:
+                    if (ch == HttpTokens.LINE_FEED)
+                    {
+                        String reason=takeString();
 
-                                        switch (ho)
-                                        {
-                                            case HttpHeaders.CONTENT_LENGTH_ORDINAL:
-                                                if (_contentLength != HttpTokens.CHUNKED_CONTENT )
-                                                {
-                                                    try
-                                                    {
-                                                        _contentLength=BufferUtil.toLong(value);
-                                                    }
-                                                    catch(NumberFormatException e)
-                                                    {
-                                                        LOG.ignore(e);
-                                                        throw new HttpException(HttpStatus.BAD_REQUEST_400);
-                                                    }
-                                                    if (_contentLength <= 0)
-                                                        _contentLength=HttpTokens.NO_CONTENT;
-                                                }
-                                                break;
+                        setState(State.HEADER);
+                        handle=_responseHandler.startResponse(_version, _responseStatus, reason)||handle;
+                        continue;
+                    }
+                    else if (ch>=HttpTokens.SPACE)
+                    {
+                        _string.append((char)ch);
+                        if (ch!=' '&&ch!='\t')
+                            _length=_string.length();
+                    } 
+                    else
+                        throw new BadMessageException();
+                    break;
 
-                                            case HttpHeaders.TRANSFER_ENCODING_ORDINAL:
-                                                value=HttpHeaderValues.CACHE.lookup(value);
-                                                vo=HttpHeaderValues.CACHE.getOrdinal(value);
-                                                if (HttpHeaderValues.CHUNKED_ORDINAL == vo)
-                                                    _contentLength=HttpTokens.CHUNKED_CONTENT;
-                                                else
-                                                {
-                                                    String c=value.toString(StringUtil.__ISO_8859_1);
-                                                    if (c.endsWith(HttpHeaderValues.CHUNKED))
-                                                        _contentLength=HttpTokens.CHUNKED_CONTENT;
+                default:
+                    throw new IllegalStateException(_state.toString());
 
-                                                    else if (c.indexOf(HttpHeaderValues.CHUNKED) >= 0)
-                                                        throw new HttpException(400,null);
-                                                }
-                                                break;
+            }
+        }
 
-                                            case HttpHeaders.CONNECTION_ORDINAL:
-                                                switch(HttpHeaderValues.CACHE.getOrdinal(value))
-                                                {
-                                                    case HttpHeaderValues.CLOSE_ORDINAL:
-                                                        _persistent=false;
-                                                        break;
-
-                                                    case HttpHeaderValues.KEEP_ALIVE_ORDINAL:
-                                                        _persistent=true;
-                                                        break;
-
-                                                    case -1: // No match, may be multi valued
-                                                    {
-                                                        for (String v : value.toString().split(","))
-                                                        {
-                                                            switch(HttpHeaderValues.CACHE.getOrdinal(v.trim()))
-                                                            {
-                                                                case HttpHeaderValues.CLOSE_ORDINAL:
-                                                                    _persistent=false;
-                                                                    break;
-
-                                                                case HttpHeaderValues.KEEP_ALIVE_ORDINAL:
-                                                                    _persistent=true;
-                                                                    break;
-                                                            }
-                                                        }
-                                                        break;
-                                                    }
-                                                }
-                                        }
-                                    }
+        return handle;
+    }
 
-                                    _handler.parsedHeader(header, value);
-                                    _tok0.setPutIndex(_tok0.getIndex());
-                                    _tok1.setPutIndex(_tok1.getIndex());
-                                    _multiLineValue=null;
-                                }
-                                _buffer.setMarkIndex(-1);
+    private boolean handleKnownHeaders(ByteBuffer buffer)
+    {
+        boolean add_to_connection_trie=false;
+        switch (_header)
+        {
+            case CONTENT_LENGTH:
+                if (_endOfContent != EndOfContent.CHUNKED_CONTENT)
+                {
+                    try
+                    {
+                        _contentLength=Long.parseLong(_valueString);
+                    }
+                    catch(NumberFormatException e)
+                    {
+                        LOG.ignore(e);
+                        throw new BadMessageException(HttpStatus.BAD_REQUEST_400,"Bad Content-Length");
+                    }
+                    if (_contentLength <= 0)
+                        _endOfContent=EndOfContent.NO_CONTENT;
+                    else
+                        _endOfContent=EndOfContent.CONTENT_LENGTH;
+                }
+                break;
 
-                                // now handle ch
-                                if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED)
-                                {
-                                    // is it a response that cannot have a body?
-                                    if (_responseStatus > 0  && // response  
-                                       (_responseStatus == 304  || // not-modified response
-                                        _responseStatus == 204 || // no-content response
-                                        _responseStatus < 200)) // 1xx response
-                                        _contentLength=HttpTokens.NO_CONTENT; // ignore any other headers set
-                                    // else if we don't know framing
-                                    else if (_contentLength == HttpTokens.UNKNOWN_CONTENT)
-                                    {
-                                        if (_responseStatus == 0  // request
-                                                || _responseStatus == 304 // not-modified response
-                                                || _responseStatus == 204 // no-content response
-                                                || _responseStatus < 200) // 1xx response
-                                            _contentLength=HttpTokens.NO_CONTENT;
-                                        else
-                                            _contentLength=HttpTokens.EOF_CONTENT;
-                                    }
+            case TRANSFER_ENCODING:
+                if (_value==HttpHeaderValue.CHUNKED)
+                    _endOfContent=EndOfContent.CHUNKED_CONTENT;
+                else
+                {
+                    if (_valueString.endsWith(HttpHeaderValue.CHUNKED.toString()))
+                        _endOfContent=EndOfContent.CHUNKED_CONTENT;
+                    else if (_valueString.contains(HttpHeaderValue.CHUNKED.toString()))
+                    {
+                        throw new BadMessageException(HttpStatus.BAD_REQUEST_400,"Bad chunking");
+                    }
+                }
+                break;
+
+            case HOST:
+                add_to_connection_trie=_connectionFields!=null && _field==null;
+                _host=true;
+                String host=_valueString;
+                int port=0;
+                if (host==null || host.length()==0)
+                {
+                    throw new BadMessageException(HttpStatus.BAD_REQUEST_400,"Bad Host header");
+                }
 
-                                    _contentPosition=0;
-                                    _eol=ch;
-                                    if (_eol==HttpTokens.CARRIAGE_RETURN && _buffer.hasContent() && _buffer.peek()==HttpTokens.LINE_FEED)
-                                        _eol=_buffer.get();
+                int len=host.length();
+                loop: for (int i = len; i-- > 0;)
+                {
+                    char c2 = (char)(0xff & host.charAt(i));
+                    switch (c2)
+                    {
+                        case ']':
+                            break loop;
 
-                                    // We convert _contentLength to an int for this switch statement because
-                                    // we don't care about the amount of data available just whether there is some.
-                                    switch (_contentLength > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) _contentLength)
-                                    {
-                                        case HttpTokens.EOF_CONTENT:
-                                            _state=STATE_EOF_CONTENT;
-                                            _handler.headerComplete(); // May recurse here !
-                                            break;
-
-                                        case HttpTokens.CHUNKED_CONTENT:
-                                            _state=STATE_CHUNKED_CONTENT;
-                                            _handler.headerComplete(); // May recurse here !
-                                            break;
-
-                                        case HttpTokens.NO_CONTENT:
-                                            _handler.headerComplete();
-                                            _state=_persistent||(_responseStatus>=100&&_responseStatus<200)?STATE_END:STATE_SEEKING_EOF;
-                                            _handler.messageComplete(_contentPosition);
-                                            return 1;
-
-                                        default:
-                                            _state=STATE_CONTENT;
-                                            _handler.headerComplete(); // May recurse here !
-                                            break;
-                                    }
-                                    return 1;
-                                }
-                                else
-                                {
-                                    // New header
-                                    _length=1;
-                                    _buffer.mark();
-                                    _state=STATE_HEADER_NAME;
-
-                                    // try cached name!
-                                    if (array!=null)
-                                    {
-                                        _cached=HttpHeaders.CACHE.getBest(array, _buffer.markIndex(), length+1);
-
-                                        if (_cached!=null)
-                                        {
-                                            _length=_cached.length();
-                                            _buffer.setGetIndex(_buffer.markIndex()+_length);
-                                            length=_buffer.length();
-                                        }
-                                    }
-                                }
+                        case ':':
+                            try
+                            {
+                                len=i;
+                                port = StringUtil.toInt(host.substring(i+1));
                             }
-                        }
-
-                        break;
-
-                    case STATE_HEADER_NAME:
-                        switch(ch)
-                        {
-                            case HttpTokens.CARRIAGE_RETURN:
-                            case HttpTokens.LINE_FEED:
-                                if (_length > 0)
-                                    _tok0.update(_buffer.markIndex(), _buffer.markIndex() + _length);
-                                _eol=ch;
-                                _state=STATE_HEADER;
-                                break;
-                            case HttpTokens.COLON:
-                                if (_length > 0 && _cached==null)
-                                    _tok0.update(_buffer.markIndex(), _buffer.markIndex() + _length);
-                                _length=-1;
-                                _state=STATE_HEADER_VALUE;
-                                break;
-                            case HttpTokens.SPACE:
-                            case HttpTokens.TAB:
-                                break;
-                            default:
+                            catch (NumberFormatException e)
                             {
-                                _cached=null;
-                                if (_length == -1)
-                                    _buffer.mark();
-                                _length=_buffer.getIndex() - _buffer.markIndex();
-                                _state=STATE_HEADER_IN_NAME;
+                                if (DEBUG)
+                                    LOG.debug(e);
+                                throw new BadMessageException(HttpStatus.BAD_REQUEST_400,"Bad Host header");
                             }
-                        }
+                            break loop;
+                    }
+                }
+                if (host.charAt(0)=='[')
+                {
+                    if (host.charAt(len-1)!=']') 
+                    {
+                        throw new BadMessageException(HttpStatus.BAD_REQUEST_400,"Bad IPv6 Host header");
+                    }
+                    host = host.substring(0,len);
+                }
+                else if (len!=host.length())
+                    host = host.substring(0,len);
+                
+                if (_requestHandler!=null)
+                    _requestHandler.parsedHostHeader(host,port);
+                
+              break;
+              
+            case CONNECTION:
+                // Don't cache if not persistent
+                if (_valueString!=null && _valueString.contains("close"))
+                {
+                    _closed=true;
+                    _connectionFields=null;
+                }
+                break;
+
+            case AUTHORIZATION:
+            case ACCEPT:
+            case ACCEPT_CHARSET:
+            case ACCEPT_ENCODING:
+            case ACCEPT_LANGUAGE:
+            case COOKIE:
+            case CACHE_CONTROL:
+            case USER_AGENT:
+                add_to_connection_trie=_connectionFields!=null && _field==null;
+                break;
+                
+            default: break;
+        }
+    
+        if (add_to_connection_trie && !_connectionFields.isFull() && _header!=null && _valueString!=null)
+        {
+            _field=new HttpField(_header,_valueString);
+            _connectionFields.put(_field);
+        }
+        
+        return false;
+    }
+    
+    
+    /* ------------------------------------------------------------------------------- */
+    /*
+     * Parse the message headers and return true if the handler has signaled for a return
+     */
+    protected boolean parseHeaders(ByteBuffer buffer)
+    {
+        boolean handle=false;
 
-                        break;
+        // Process headers
+        while (_state.ordinal()<State.CONTENT.ordinal() && buffer.hasRemaining() && !handle)
+        {
+            // process each character
+            byte ch=next(buffer);
+            if (ch==0)
+                break;
+            
+            if (_maxHeaderBytes>0 && ++_headerBytes>_maxHeaderBytes)
+            {
+                LOG.warn("Header is too large >"+_maxHeaderBytes);
+                throw new BadMessageException(HttpStatus.REQUEST_ENTITY_TOO_LARGE_413);
+            }
 
-                    case STATE_HEADER_IN_NAME:
-                        switch(ch)
+            switch (_state)
+            {
+                case HEADER:
+                    switch(ch)
+                    {
+                        case HttpTokens.COLON:
+                        case HttpTokens.SPACE:
+                        case HttpTokens.TAB:
                         {
-                            case HttpTokens.CARRIAGE_RETURN:
-                            case HttpTokens.LINE_FEED:
-                                if (_length > 0)
-                                    _tok0.update(_buffer.markIndex(), _buffer.markIndex() + _length);
-                                _eol=ch;
-                                _state=STATE_HEADER;
-                                break;
-                            case HttpTokens.COLON:
-                                if (_length > 0 && _cached==null)
-                                    _tok0.update(_buffer.markIndex(), _buffer.markIndex() + _length);
-                                _length=-1;
-                                _state=STATE_HEADER_VALUE;
-                                break;
-                            case HttpTokens.SPACE:
-                            case HttpTokens.TAB:
-                                _state=STATE_HEADER_NAME;
-                                break;
-                            default:
+                            // header value without name - continuation?
+                            if (_valueString==null)
                             {
-                                _cached=null;
-                                _length++;
+                                _string.setLength(0);
+                                _length=0;
                             }
-                        }
-                        break;
-
-                    case STATE_HEADER_VALUE:
-                        switch(ch)
-                        {
-                            case HttpTokens.CARRIAGE_RETURN:
-                            case HttpTokens.LINE_FEED:
-                                if (_length > 0)
-                                {
-                                    if (_tok1.length() == 0)
-                                        _tok1.update(_buffer.markIndex(), _buffer.markIndex() + _length);
-                                    else
-                                    {
-                                        // Continuation line!
-                                        if (_multiLineValue == null) _multiLineValue=_tok1.toString(StringUtil.__ISO_8859_1);
-                                        _tok1.update(_buffer.markIndex(), _buffer.markIndex() + _length);
-                                        _multiLineValue += " " + _tok1.toString(StringUtil.__ISO_8859_1);
-                                    }
-                                }
-                                _eol=ch;
-                                _state=STATE_HEADER;
-                                break;
-                            case HttpTokens.SPACE:
-                            case HttpTokens.TAB:
-                                break;
-                            default:
+                            else
                             {
-                                if (_length == -1)
-                                    _buffer.mark();
-                                _length=_buffer.getIndex() - _buffer.markIndex();
-                                _state=STATE_HEADER_IN_VALUE;
+                                setString(_valueString);
+                                _string.append(' ');
+                                _length++;
+                                _valueString=null;
                             }
+                            setState(State.HEADER_VALUE);
+                            break;
                         }
-                        break;
 
-                    case STATE_HEADER_IN_VALUE:
-                        switch(ch)
+                        default:
                         {
-                            case HttpTokens.CARRIAGE_RETURN:
-                            case HttpTokens.LINE_FEED:
-                                if (_length > 0)
+                            // handler last header if any.  Delayed to here just in case there was a continuation line (above)
+                            if (_headerString!=null || _valueString!=null)
+                            {
+                                // Handle known headers
+                                if (_header!=null && handleKnownHeaders(buffer))
                                 {
-                                    if (_tok1.length() == 0)
-                                        _tok1.update(_buffer.markIndex(), _buffer.markIndex() + _length);
-                                    else
-                                    {
-                                        // Continuation line!
-                                        if (_multiLineValue == null) _multiLineValue=_tok1.toString(StringUtil.__ISO_8859_1);
-                                        _tok1.update(_buffer.markIndex(), _buffer.markIndex() + _length);
-                                        _multiLineValue += " " + _tok1.toString(StringUtil.__ISO_8859_1);
-                                    }
+                                    _headerString=_valueString=null;
+                                    _header=null;
+                                    _value=null;
+                                    _field=null;
+                                    return true;
                                 }
-                                _eol=ch;
-                                _state=STATE_HEADER;
-                                break;
-                            case HttpTokens.SPACE:
-                            case HttpTokens.TAB:
-                                _state=STATE_HEADER_VALUE;
-                                break;
-                            default:
-                                _length++;
-                        }
-                        break;
-                }
-            } // end of HEADER states loop
+                                handle=_handler.parsedHeader(_field!=null?_field:new HttpField(_header,_headerString,_valueString))||handle;
+                            }
+                            _headerString=_valueString=null;
+                            _header=null;
+                            _value=null;
+                            _field=null;
 
-            // ==========================
+                            // now handle the ch
+                            if (ch == HttpTokens.LINE_FEED)
+                            {
+                                _contentPosition=0;
 
-            // Handle HEAD response
-            if (_responseStatus>0 && _headResponse)
-            {
-                _state=_persistent||(_responseStatus>=100&&_responseStatus<200)?STATE_END:STATE_SEEKING_EOF;
-                _handler.messageComplete(_contentLength);
-            }
+                                // End of headers!
 
+                                // Was there a required host header?
+                                if (!_host && _version==HttpVersion.HTTP_1_1 && _requestHandler!=null)
+                                {
+                                    throw new BadMessageException(HttpStatus.BAD_REQUEST_400,"No Host");
+                                }
 
-            // ==========================
+                                // is it a response that cannot have a body?
+                                if (_responseHandler !=null  && // response  
+                                    (_responseStatus == 304  || // not-modified response
+                                    _responseStatus == 204 || // no-content response
+                                    _responseStatus < 200)) // 1xx response
+                                    _endOfContent=EndOfContent.NO_CONTENT; // ignore any other headers set
+                                
+                                // else if we don't know framing
+                                else if (_endOfContent == EndOfContent.UNKNOWN_CONTENT)
+                                {
+                                    if (_responseStatus == 0  // request
+                                            || _responseStatus == 304 // not-modified response
+                                            || _responseStatus == 204 // no-content response
+                                            || _responseStatus < 200) // 1xx response
+                                        _endOfContent=EndOfContent.NO_CONTENT;
+                                    else
+                                        _endOfContent=EndOfContent.EOF_CONTENT;
+                                }
 
-            // Handle _content
-            length=_buffer.length();
-            Buffer chunk;
-            last=_state;
-            while (_state > STATE_END && length > 0)
-            {
-                if (last!=_state)
-                {
-                    progress++;
-                    last=_state;
-                }
+                                // How is the message ended?
+                                switch (_endOfContent)
+                                {
+                                    case EOF_CONTENT:
+                                        setState(State.EOF_CONTENT);
+                                        handle=_handler.headerComplete()||handle;
+                                        return handle;
+
+                                    case CHUNKED_CONTENT:
+                                        setState(State.CHUNKED_CONTENT);
+                                        handle=_handler.headerComplete()||handle;
+                                        return handle;
+
+                                    case NO_CONTENT:
+                                        handle=_handler.headerComplete()||handle;
+                                        setState(State.END);
+                                        handle=_handler.messageComplete()||handle;
+                                        return handle;
+
+                                    default:
+                                        setState(State.CONTENT);
+                                        handle=_handler.headerComplete()||handle;
+                                        return handle;
+                                }
+                            }
+                            else if (ch<=HttpTokens.SPACE)
+                                throw new BadMessageException();
+                            else
+                            {
+                                if (buffer.hasRemaining())
+                                {
+                                    // Try a look ahead for the known header name and value.
+                                    HttpField field=_connectionFields==null?null:_connectionFields.getBest(buffer,-1,buffer.remaining());
+                                    if (field==null)
+                                        field=CACHE.getBest(buffer,-1,buffer.remaining());
+                                        
+                                    if (field!=null)
+                                    {
+                                        final String n;
+                                        final String v;
 
-                if (_eol == HttpTokens.CARRIAGE_RETURN && _buffer.peek() == HttpTokens.LINE_FEED)
-                {
-                    _eol=_buffer.get();
-                    length=_buffer.length();
-                    continue;
-                }
-                _eol=0;
-                switch (_state)
-                {
-                    case STATE_EOF_CONTENT:
-                        chunk=_buffer.get(_buffer.length());
-                        _contentPosition += chunk.length();
-                        _contentView.update(chunk);
-                        _handler.content(chunk); // May recurse here
-                        // TODO adjust the _buffer to keep unconsumed content
-                        return 1;
-
-                    case STATE_CONTENT:
-                    {
-                        long remaining=_contentLength - _contentPosition;
-                        if (remaining == 0)
-                        {
-                            _state=_persistent?STATE_END:STATE_SEEKING_EOF;
-                            _handler.messageComplete(_contentPosition);
-                            return 1;
-                        }
+                                        if (_strict)
+                                        {
+                                            // Have to get the fields exactly from the buffer to match case
+                                            String fn=field.getName();
+                                            String fv=field.getValue();
+                                            n=BufferUtil.toString(buffer,buffer.position()-1,fn.length(),StandardCharsets.US_ASCII);
+                                            if (fv==null)
+                                                v=null;
+                                            else
+                                            {
+                                                v=BufferUtil.toString(buffer,buffer.position()+fn.length()+1,fv.length(),StandardCharsets.ISO_8859_1);
+                                                field=new HttpField(field.getHeader(),n,v);
+                                            }
+                                        }
+                                        else
+                                        {
+                                            n=field.getName();
+                                            v=field.getValue(); 
+                                        }
+                                        
+                                        _header=field.getHeader();
+                                        _headerString=n;
+         
+                                        if (v==null)
+                                        {
+                                            // Header only
+                                            setState(State.HEADER_VALUE);
+                                            _string.setLength(0);
+                                            _length=0;
+                                            buffer.position(buffer.position()+n.length()+1);
+                                            break;
+                                        }
+                                        else
+                                        {
+                                            // Header and value
+                                            int pos=buffer.position()+n.length()+v.length()+1;
+                                            byte b=buffer.get(pos);
 
-                        if (length > remaining)
-                        {
-                            // We can cast reamining to an int as we know that it is smaller than
-                            // or equal to length which is already an int.
-                            length=(int)remaining;
-                        }
+                                            if (b==HttpTokens.CARRIAGE_RETURN || b==HttpTokens.LINE_FEED)
+                                            {                     
+                                                _field=field;
+                                                _valueString=v;
+                                                setState(State.HEADER_IN_VALUE);
 
-                        chunk=_buffer.get(length);
-                        _contentPosition += chunk.length();
-                        _contentView.update(chunk);
-                        _handler.content(chunk); // May recurse here
+                                                if (b==HttpTokens.CARRIAGE_RETURN)
+                                                {
+                                                    _cr=true;
+                                                    buffer.position(pos+1);
+                                                }
+                                                else
+                                                    buffer.position(pos);
+                                                break;
+                                            }
+                                            else
+                                            {
+                                                setState(State.HEADER_IN_VALUE);
+                                                setString(v);
+                                                buffer.position(pos);
+                                                break;
+                                            }
+                                        }
+                                    }
+                                }
 
-                        if(_contentPosition == _contentLength)
-                        {
-                            _state=_persistent?STATE_END:STATE_SEEKING_EOF;
-                            _handler.messageComplete(_contentPosition);
+                                // New header
+                                setState(State.HEADER_IN_NAME);
+                                _string.setLength(0);
+                                _string.append((char)ch);
+                                _length=1;
+                            }
                         }
-                        // TODO adjust the _buffer to keep unconsumed content
-                        return 1;
                     }
+                    break;
 
-                    case STATE_CHUNKED_CONTENT:
+                case HEADER_IN_NAME:
+                    if (ch==HttpTokens.COLON || ch==HttpTokens.LINE_FEED)
                     {
-                        ch=_buffer.peek();
-                        if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED)
-                            _eol=_buffer.get();
-                        else if (ch <= HttpTokens.SPACE)
-                            _buffer.get();
-                        else
+                        if (_headerString==null)
                         {
-                            _chunkLength=0;
-                            _chunkPosition=0;
-                            _state=STATE_CHUNK_SIZE;
+                            _headerString=takeString();
+                            _header=HttpHeader.CACHE.get(_headerString);
                         }
+                        _length=-1;
+
+                        setState(ch==HttpTokens.LINE_FEED?State.HEADER:State.HEADER_VALUE);
                         break;
                     }
-
-                    case STATE_CHUNK_SIZE:
+                    
+                    if (ch>=HttpTokens.SPACE || ch==HttpTokens.TAB)
                     {
-                        ch=_buffer.get();
-                        if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED)
+                        if (_header!=null)
                         {
-                            _eol=ch;
-
-                            if (_chunkLength == 0)
-                            {
-                                if (_eol==HttpTokens.CARRIAGE_RETURN && _buffer.hasContent() && _buffer.peek()==HttpTokens.LINE_FEED)
-                                    _eol=_buffer.get();
-                                _state=_persistent?STATE_END:STATE_SEEKING_EOF;
-                                _handler.messageComplete(_contentPosition);
-                                return 1;
-                            }
-                            else
-                                _state=STATE_CHUNK;
+                            setString(_header.asString());
+                            _header=null;
+                            _headerString=null;
                         }
-                        else if (ch <= HttpTokens.SPACE || ch == HttpTokens.SEMI_COLON)
-                            _state=STATE_CHUNK_PARAMS;
-                        else if (ch >= '0' && ch <= '9')
-                            _chunkLength=_chunkLength * 16 + (ch - '0');
-                        else if (ch >= 'a' && ch <= 'f')
-                            _chunkLength=_chunkLength * 16 + (10 + ch - 'a');
-                        else if (ch >= 'A' && ch <= 'F')
-                            _chunkLength=_chunkLength * 16 + (10 + ch - 'A');
-                        else
-                            throw new IOException("bad chunk char: " + ch);
+
+                        _string.append((char)ch);
+                        if (ch>HttpTokens.SPACE)
+                            _length=_string.length();
                         break;
                     }
 
-                    case STATE_CHUNK_PARAMS:
+                    throw new IllegalCharacterException(_state,ch,buffer);
+
+                case HEADER_VALUE:
+                    if (ch>HttpTokens.SPACE || ch<0)
                     {
-                        ch=_buffer.get();
-                        if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED)
-                        {
-                            _eol=ch;
-                            if (_chunkLength == 0)
-                            {
-                                if (_eol==HttpTokens.CARRIAGE_RETURN && _buffer.hasContent() && _buffer.peek()==HttpTokens.LINE_FEED)
-                                    _eol=_buffer.get();
-                                _state=_persistent?STATE_END:STATE_SEEKING_EOF;
-                                _handler.messageComplete(_contentPosition);
-                                return 1;
-                            }
-                            else
-                                _state=STATE_CHUNK;
-                        }
+                        _string.append((char)(0xff&ch));
+                        _length=_string.length();
+                        setState(State.HEADER_IN_VALUE);
                         break;
                     }
-
-                    case STATE_CHUNK:
+                    
+                    if (ch==HttpTokens.SPACE || ch==HttpTokens.TAB)
+                        break;
+                    
+                    if (ch==HttpTokens.LINE_FEED)
                     {
-                        int remaining=_chunkLength - _chunkPosition;
-                        if (remaining == 0)
+                        if (_length > 0)
                         {
-                            _state=STATE_CHUNKED_CONTENT;
-                            break;
+                            _value=null;
+                            _valueString=(_valueString==null)?takeString():(_valueString+" "+takeString());
                         }
-                        else if (length > remaining)
-                            length=remaining;
-                        chunk=_buffer.get(length);
-                        _contentPosition += chunk.length();
-                        _chunkPosition += chunk.length();
-                        _contentView.update(chunk);
-                        _handler.content(chunk); // May recurse here
-                        // TODO adjust the _buffer to keep unconsumed content
-                        return 1;
+                        setState(State.HEADER);
+                        break; 
                     }
+                    
+                    throw new IllegalCharacterException(_state,ch,buffer);
 
-                    case STATE_SEEKING_EOF:
-                    {                        
-                        // Close if there is more data than CRLF
-                        if (_buffer.length()>2)
+                case HEADER_IN_VALUE:
+                    if (ch>=HttpTokens.SPACE || ch<0 || ch==HttpTokens.TAB)
+                    {
+                        if (_valueString!=null)
                         {
-                            _state=STATE_END;
-                            _endp.close();
+                            setString(_valueString);
+                            _valueString=null;
+                            _field=null;
                         }
-                        else  
+                        _string.append((char)(0xff&ch));
+                        if (ch>HttpTokens.SPACE || ch<0)
+                            _length=_string.length();
+                        break;
+                    }
+                    
+                    if (ch==HttpTokens.LINE_FEED)
+                    {
+                        if (_length > 0)
                         {
-                            // or if the data is not white space
-                            while (_buffer.length()>0)
-                                if (!Character.isWhitespace(_buffer.get()))
-                                {
-                                    _state=STATE_END;
-                                    _endp.close();
-                                    _buffer.clear();
-                                }
+                            _value=null;
+                            _valueString=takeString();
+                            _length=-1;
                         }
-                        
-                        _buffer.clear();
+                        setState(State.HEADER);
                         break;
                     }
-                }
 
-                length=_buffer.length();
-            }
+                    throw new IllegalCharacterException(_state,ch,buffer);
+                    
+                default:
+                    throw new IllegalStateException(_state.toString());
 
-            return progress;
-        }
-        catch(HttpException e)
-        {
-            _persistent=false;
-            _state=STATE_SEEKING_EOF;
-            throw e;
+            }
         }
+
+        return handle;
     }
 
     /* ------------------------------------------------------------------------------- */
-    /** fill the buffers from the endpoint
-     *
+    /**
+     * Parse until next Event.
+     * @return True if an {@link RequestHandler} method was called and it returned true;
      */
-    protected int fill() throws IOException
+    public boolean parseNext(ByteBuffer buffer)
     {
-        // Do we have a buffer?
-        if (_buffer==null)
-            _buffer=getHeaderBuffer();
-
-        // Is there unconsumed content in body buffer
-        if (_state>STATE_END && _buffer==_header && _header!=null && !_header.hasContent() && _body!=null && _body.hasContent())
-        {
-            _buffer=_body;
-            return _buffer.length();
-        }
-
-        // Shall we switch to a body buffer?
-        if (_buffer==_header && _state>STATE_END && _header.length()==0 && (_forceContentBuffer || (_contentLength-_contentPosition)>_header.capacity()) && (_body!=null||_buffers!=null))
-        {
-            if (_body==null)
-                _body=_buffers.getBuffer();
-            _buffer=_body;
-        }
-
-        // Do we have somewhere to fill from?
-        if (_endp != null )
+        if (DEBUG)
+            LOG.debug("parseNext s={} {}",_state,BufferUtil.toDetailString(buffer));
+        try
         {
-            // Shall we compact the body?
-            if (_buffer==_body || _state>STATE_END)
+            // Start a request/response
+            if (_state==State.START)
             {
-                _buffer.compact();
+                _version=null;
+                _method=null;
+                _methodString=null;
+                _endOfContent=EndOfContent.UNKNOWN_CONTENT;
+                _header=null;
+                if (quickStart(buffer))
+                    return true;
             }
-
-            // Are we full?
-            if (_buffer.space() == 0)
+            
+            // Request/response line
+            if (_state.ordinal()>= State.START.ordinal() && _state.ordinal()<State.HEADER.ordinal())
             {
-                LOG.warn("HttpParser Full for {} ",_endp);
-                _buffer.clear();
-                throw new HttpException(HttpStatus.REQUEST_ENTITY_TOO_LARGE_413, "Request Entity Too Large: "+(_buffer==_body?"body":"head"));
+                if (parseLine(buffer))
+                    return true;
             }
 
-            try
+            // parse headers
+            if (_state.ordinal()>= State.HEADER.ordinal() && _state.ordinal()<State.CONTENT.ordinal())
             {
-                int filled = _endp.fill(_buffer);
-                return filled;
+                if (parseHeaders(buffer))
+                    return true;
             }
-            catch(IOException e)
+            
+            // parse content
+            if (_state.ordinal()>= State.CONTENT.ordinal() && _state.ordinal()<State.END.ordinal())
             {
-                LOG.debug(e);
-                throw (e instanceof EofException) ? e:new EofException(e);
+                // Handle HEAD response
+                if (_responseStatus>0 && _headResponse)
+                {
+                    setState(State.END);
+                    return _handler.messageComplete();
+                }
+                else
+                {
+                    if (parseContent(buffer))
+                        return true;
+                }
+            }
+            
+            // handle end states
+            if (_state==State.END)
+            {
+                // eat white space
+                while (buffer.remaining()>0 && buffer.get(buffer.position())<=HttpTokens.SPACE)
+                    buffer.get();
+            }
+            else if (_state==State.CLOSED)
+            {
+                if (BufferUtil.hasContent(buffer))
+                {
+                    // Just ignore data when closed
+                    _headerBytes+=buffer.remaining();
+                    BufferUtil.clear(buffer);
+                    if (_maxHeaderBytes>0 && _headerBytes>_maxHeaderBytes)
+                    {
+                        // Don't want to waste time reading data of a closed request
+                        throw new IllegalStateException("too much data after closed");
+                    }
+                }
+            }
+            
+            // Handle EOF
+            if (_eof && !buffer.hasRemaining())
+            {
+                switch(_state)
+                {
+                    case CLOSED:
+                        break;
+                        
+                    case START:
+                        setState(State.CLOSED);
+                        _handler.earlyEOF();
+                        break;
+                        
+                    case END:
+                        setState(State.CLOSED);
+                        break;
+                        
+                    case EOF_CONTENT:
+                        setState(State.CLOSED);
+                        return _handler.messageComplete();
+
+                    case  CONTENT:
+                    case  CHUNKED_CONTENT:
+                    case  CHUNK_SIZE:
+                    case  CHUNK_PARAMS:
+                    case  CHUNK:
+                        setState(State.CLOSED);
+                        _handler.earlyEOF();
+                        break;
+
+                    default:
+                        if (DEBUG)
+                            LOG.debug("{} EOF in {}",this,_state);
+                        setState(State.CLOSED);
+                        _handler.badMessage(400,null);
+                        break;
+                }
             }
+            
+            return false;
         }
+        catch(BadMessageException e)
+        {
+            BufferUtil.clear(buffer);
 
-        return -1;
-    }
+            LOG.warn("badMessage: "+e._code+(e.getMessage()!=null?" "+e.getMessage():"")+" for "+_handler);
+            if (DEBUG)
+                LOG.debug(e);
+            setState(State.CLOSED);
+            _handler.badMessage(e._code, e.getMessage());
+            return false;
+        }
+        catch(Exception e)
+        {
+            BufferUtil.clear(buffer);
 
-    /* ------------------------------------------------------------------------------- */
-    public void reset()
-    {
-        // reset state
-        _contentView.setGetIndex(_contentView.putIndex());
-        _state=_persistent?STATE_START:(_endp.isInputShutdown()?STATE_END:STATE_SEEKING_EOF);
-        _contentLength=HttpTokens.UNKNOWN_CONTENT;
-        _contentPosition=0;
-        _length=0;
-        _responseStatus=0;
+            LOG.warn("badMessage: "+e.toString()+" for "+_handler);
+            if (DEBUG)
+                LOG.debug(e);
+            
+            if (_state.ordinal()<=State.END.ordinal())
+            {
+                setState(State.CLOSED);
+                _handler.badMessage(400,null);
+            }
+            else
+            {
+                _handler.earlyEOF();
+                setState(State.CLOSED);
+            }
 
-        // Consume LF if CRLF
-        if (_eol == HttpTokens.CARRIAGE_RETURN && _buffer!=null && _buffer.hasContent() && _buffer.peek() == HttpTokens.LINE_FEED)
-            _eol=_buffer.get();
+            return false;
+        }
+    }
 
-        if (_body!=null && _body.hasContent())
+    protected boolean parseContent(ByteBuffer buffer)
+    {
+        int remaining=buffer.remaining();
+        if (remaining==0 && _state==State.CONTENT)
         {
-            // There is content in the body after the end of the request.
-            // This is probably a pipelined header of the next request, so we need to
-            // copy it to the header buffer.
-            if (_header==null)
-                getHeaderBuffer();
-            else
+            long content=_contentLength - _contentPosition;
+            if (content == 0)
             {
-                _header.setMarkIndex(-1);
-                _header.compact();
+                setState(State.END);
+                return _handler.messageComplete();
             }
-            int take=_header.space();
-            if (take>_body.length())
-                take=_body.length();
-            _body.peek(_body.getIndex(),take);
-            _body.skip(_header.put(_body.peek(_body.getIndex(),take)));
         }
-
-        if (_header!=null)
+        
+        // Handle _content
+        byte ch;
+        while (_state.ordinal() < State.END.ordinal() && remaining>0)
         {
-            _header.setMarkIndex(-1);
-            _header.compact();
+            switch (_state)
+            {
+                case EOF_CONTENT:
+                    _contentChunk=buffer.asReadOnlyBuffer();
+                    _contentPosition += remaining;
+                    buffer.position(buffer.position()+remaining);
+                    if (_handler.content(_contentChunk))
+                        return true;
+                    break;
+
+                case CONTENT:
+                {
+                    long content=_contentLength - _contentPosition;
+                    if (content == 0)
+                    {
+                        setState(State.END);
+                        return _handler.messageComplete();
+                    }
+                    else
+                    {
+                        _contentChunk=buffer.asReadOnlyBuffer();
+
+                        // limit content by expected size
+                        if (remaining > content)
+                        {
+                            // We can cast remaining to an int as we know that it is smaller than
+                            // or equal to length which is already an int.
+                            _contentChunk.limit(_contentChunk.position()+(int)content);
+                        }
+
+                        _contentPosition += _contentChunk.remaining();
+                        buffer.position(buffer.position()+_contentChunk.remaining());
+
+                        if (_handler.content(_contentChunk))
+                            return true;
+
+                        if(_contentPosition == _contentLength)
+                        {
+                            setState(State.END);
+                            return _handler.messageComplete();
+                        }
+                    }
+                    break;
+                }
+
+                case CHUNKED_CONTENT:
+                {
+                    ch=next(buffer);
+                    if (ch>HttpTokens.SPACE)
+                    {
+                        _chunkLength=TypeUtil.convertHexDigit(ch);
+                        _chunkPosition=0;
+                        setState(State.CHUNK_SIZE);
+                    }
+
+                    break;
+                }
+
+                case CHUNK_SIZE:
+                {
+                    ch=next(buffer);
+                    if (ch==0)
+                        break;
+                    if (ch == HttpTokens.LINE_FEED)
+                    {
+                        if (_chunkLength == 0)
+                            setState(State.CHUNK_END);
+                        else
+                            setState(State.CHUNK);
+                    }
+                    else if (ch <= HttpTokens.SPACE || ch == HttpTokens.SEMI_COLON)
+                        setState(State.CHUNK_PARAMS);
+                    else
+                        _chunkLength=_chunkLength * 16 + TypeUtil.convertHexDigit(ch);
+                    break;
+                }
+
+                case CHUNK_PARAMS:
+                {
+                    ch=next(buffer);
+                    if (ch == HttpTokens.LINE_FEED)
+                    {
+                        if (_chunkLength == 0)
+                            setState(State.CHUNK_END);
+                        else
+                            setState(State.CHUNK);
+                    }
+                    break;
+                }
+
+                case CHUNK:
+                {
+                    int chunk=_chunkLength - _chunkPosition;
+                    if (chunk == 0)
+                    {
+                        setState(State.CHUNKED_CONTENT);
+                    }
+                    else
+                    {
+                        _contentChunk=buffer.asReadOnlyBuffer();
+
+                        if (remaining > chunk)
+                            _contentChunk.limit(_contentChunk.position()+chunk);
+                        chunk=_contentChunk.remaining();
+
+                        _contentPosition += chunk;
+                        _chunkPosition += chunk;
+                        buffer.position(buffer.position()+chunk);
+                        if (_handler.content(_contentChunk))
+                            return true;
+                    }
+                    break;
+                }
+                
+                case CHUNK_END:
+                {
+                    // TODO handle chunk trailer
+                    ch=next(buffer);
+                    if (ch==0)
+                        break;
+                    if (ch == HttpTokens.LINE_FEED)
+                    {
+                        setState(State.END);
+                        return _handler.messageComplete();
+                    }
+                    throw new IllegalCharacterException(_state,ch,buffer);
+                }
+                
+                case CLOSED:
+                {
+                    BufferUtil.clear(buffer);
+                    return false;
+                }
+
+                default: 
+                    break;
+                    
+            }
+            
+            remaining=buffer.remaining();
         }
-        if (_body!=null)
-            _body.setMarkIndex(-1);
+        return false;
+    }
 
-        _buffer=_header;
-        returnBuffers();
+    /* ------------------------------------------------------------------------------- */
+    public boolean isAtEOF()
+ 
+    {
+        return _eof;
     }
+    
+    /* ------------------------------------------------------------------------------- */
+    public void atEOF()
 
+    {        
+        if (DEBUG)
+            LOG.debug("atEOF {}", this);
+        _eof=true;
+    }
 
     /* ------------------------------------------------------------------------------- */
-    public void returnBuffers()
+    public void close()
     {
-        if (_body!=null && !_body.hasContent() && _body.markIndex()==-1 && _buffers!=null)
-        {
-            if (_buffer==_body)
-                _buffer=_header;
-            if (_buffers!=null)
-                _buffers.returnBuffer(_body);
-            _body=null;
-        }
-
-        if (_header!=null && !_header.hasContent() && _header.markIndex()==-1 && _buffers!=null)
+        if (DEBUG)
+            LOG.debug("close {}", this);
+        setState(State.CLOSED);
+    }
+    
+    /* ------------------------------------------------------------------------------- */
+    public void reset()
+    {
+        if (DEBUG)
+            LOG.debug("reset {}", this);
+        // reset state
+        if (_state==State.CLOSED)
+            return;
+        if (_closed)
         {
-            if (_buffer==_header)
-                _buffer=null;
-            _buffers.returnBuffer(_header);
-            _header=null;
+            setState(State.CLOSED);
+            return;
         }
+        
+        setState(State.START);
+        _endOfContent=EndOfContent.UNKNOWN_CONTENT;
+        _contentLength=-1;
+        _contentPosition=0;
+        _responseStatus=0;
+        _contentChunk=null;
+        _headerBytes=0;
+        _host=false;
     }
 
     /* ------------------------------------------------------------------------------- */
-    public void setState(int state)
+    protected void setState(State state)
     {
-        this._state=state;
-        _contentLength=HttpTokens.UNKNOWN_CONTENT;
+        if (DEBUG)
+            LOG.debug("{} --> {}",_state,state);
+        _state=state;
     }
 
     /* ------------------------------------------------------------------------------- */
-    public String toString(Buffer buf)
+    public Trie<HttpField> getFieldCache()
     {
-        return "state=" + _state + " length=" + _length + " buf=" + buf.hashCode();
+        return _connectionFields;
     }
 
     /* ------------------------------------------------------------------------------- */
+    private String getProxyField(ByteBuffer buffer)
+    {
+        _string.setLength(0);
+        _length=0;
+        
+        while (buffer.hasRemaining())
+        {
+            // process each character
+            byte ch=next(buffer);
+            if (ch<=' ')
+                return _string.toString();
+            _string.append((char)ch);    
+        }
+        throw new BadMessageException();
+    }
+    
+    /* ------------------------------------------------------------------------------- */
     @Override
     public String toString()
     {
-        return String.format("%s{s=%d,l=%d,c=%d}",
+        return String.format("%s{s=%s,%d of %d}",
                 getClass().getSimpleName(),
                 _state,
-                _length,
+                _contentPosition,
                 _contentLength);
     }
 
     /* ------------------------------------------------------------ */
-    public Buffer getHeaderBuffer()
-    {
-        if (_header == null)
-        {
-            _header=_buffers.getHeader();
-            _tok0.update(_header);
-            _tok1.update(_header);
-        }
-        return _header;
-    }
-
     /* ------------------------------------------------------------ */
-    public Buffer getBodyBuffer()
-    {
-        return _body;
-    }
-
     /* ------------------------------------------------------------ */
-    /**
-     * @param force True if a new buffer will be forced to be used for content and the header buffer will not be used.
+    /* Event Handler interface
+     * These methods return true if the caller should process the events
+     * so far received (eg return from parseNext and call HttpChannel.handle).
+     * If multiple callbacks are called in sequence (eg 
+     * headerComplete then messageComplete) from the same point in the parsing
+     * then it is sufficient for the caller to process the events only once.
      */
-    public void setForceContentBuffer(boolean force)
-    {
-        _forceContentBuffer=force;
-    }
-
-    /* ------------------------------------------------------------ */
-    public Buffer blockForContent(long maxIdleTime) throws IOException
+    public interface HttpHandler<T>
     {
-        if (_contentView.length()>0)
-            return _contentView;
+        public boolean content(T item);
 
-        if (getState() <= STATE_END || isState(STATE_SEEKING_EOF))
-            return null;
+        public boolean headerComplete();
 
-        try
-        {
-            parseNext();
-
-            // parse until some progress is made (or IOException thrown for timeout)
-            while(_contentView.length() == 0 && !(isState(HttpParser.STATE_END)||isState(HttpParser.STATE_SEEKING_EOF)) && _endp!=null && _endp.isOpen())
-            {
-                if (!_endp.isBlocking())
-                {
-                    if (parseNext()>0)
-                        continue;
+        public boolean messageComplete();
 
-                    if (!_endp.blockReadable(maxIdleTime))
-                    {
-                        _endp.close();
-                        throw new EofException("timeout");
-                    }
-                }
+        /**
+         * This is the method called by parser when a HTTP Header name and value is found
+         * @param field The field parsed
+         * @return True if the parser should return to its caller
+         */
+        public boolean parsedHeader(HttpField field);
 
-                parseNext();
-            }
-        }
-        catch(IOException e)
-        {
-            // TODO is this needed?
-            _endp.close();
-            throw e;
-        }
+        /* ------------------------------------------------------------ */
+        /** Called to signal that an EOF was received unexpectedly
+         * during the parsing of a HTTP message
+         */
+        public void earlyEOF();
 
-        return _contentView.length()>0?_contentView:null;
+        /* ------------------------------------------------------------ */
+        /** Called to signal that a bad HTTP message has been received.
+         * @param status The bad status to send
+         * @param reason The textual reason for badness
+         */
+        public void badMessage(int status, String reason);
+        
+        /* ------------------------------------------------------------ */
+        /** @return the size in bytes of the per parser header cache
+         */
+        public int getHeaderCacheSize();
     }
 
-    /* ------------------------------------------------------------ */
-    /* (non-Javadoc)
-     * @see java.io.InputStream#available()
-     */
-    public int available() throws IOException
+    /* ------------------------------------------------------------------------------- */
+    /* ------------------------------------------------------------------------------- */
+    /* ------------------------------------------------------------------------------- */
+    public interface ProxyHandler 
     {
-        if (_contentView!=null && _contentView.length()>0)
-            return _contentView.length();
-
-        if (_endp.isBlocking())
-        {
-            if (_state>0 && _endp instanceof StreamEndPoint)
-                return ((StreamEndPoint)_endp).getInputStream().available()>0?1:0;
-
-            return 0;
-        }
-
-        parseNext();
-        return _contentView==null?0:_contentView.length();
+        void proxied(String protocol, String sAddr, String dAddr, int sPort, int dPort);
     }
 
-    /* ------------------------------------------------------------ */
-    /* ------------------------------------------------------------ */
-    /* ------------------------------------------------------------ */
-    public static abstract class EventHandler
+    /* ------------------------------------------------------------------------------- */
+    /* ------------------------------------------------------------------------------- */
+    /* ------------------------------------------------------------------------------- */
+    public interface RequestHandler<T> extends HttpHandler<T>
     {
-        public abstract void content(Buffer ref) throws IOException;
-
-        public void headerComplete() throws IOException
-        {
-        }
-
-        public void messageComplete(long contentLength) throws IOException
-        {
-        }
-
         /**
-         * This is the method called by parser when a HTTP Header name and value is found
+         * This is the method called by parser when the HTTP request line is parsed
+         * @param method The method as enum if of a known type
+         * @param methodString The method as a string
+         * @param uri The raw bytes of the URI.  These are copied into a ByteBuffer that will not be changed until this parser is reset and reused.
+         * @param version
+         * @return true if handling parsing should return.
          */
-        public void parsedHeader(Buffer name, Buffer value) throws IOException
-        {
-        }
+        public abstract boolean startRequest(HttpMethod method, String methodString, ByteBuffer uri, HttpVersion version);
 
         /**
-         * This is the method called by parser when the HTTP request line is parsed
+         * This is the method called by the parser after it has parsed the host header (and checked it's format). This is
+         * called after the {@link HttpHandler#parsedHeader(HttpField)} methods and before
+         * HttpHandler#headerComplete();
          */
-        public abstract void startRequest(Buffer method, Buffer url, Buffer version)
-                throws IOException;
+        public abstract boolean parsedHostHeader(String host,int port);
+    }
 
+    /* ------------------------------------------------------------------------------- */
+    /* ------------------------------------------------------------------------------- */
+    /* ------------------------------------------------------------------------------- */
+    public interface ResponseHandler<T> extends HttpHandler<T>
+    {
         /**
          * This is the method called by parser when the HTTP request line is parsed
          */
-        public abstract void startResponse(Buffer version, int status, Buffer reason)
-                throws IOException;
-
-        public void earlyEOF()
-        {}
+        public abstract boolean startResponse(HttpVersion version, int status, String reason);
     }
 
-
-
-
+    /* ------------------------------------------------------------------------------- */
+    @SuppressWarnings("serial")
+    private static class IllegalCharacterException extends BadMessageException
+    {
+        private IllegalCharacterException(State state,byte ch,ByteBuffer buffer)
+        {
+            super(400,String.format("Illegal character 0x%X",ch));
+            // Bug #460642 - don't reveal buffers to end user
+            LOG.warn(String.format("Illegal character 0x%X in state=%s for buffer %s",ch,state,BufferUtil.toDetailString(buffer)));
+        }
+    }
 }
diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpScheme.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpScheme.java
new file mode 100644
index 0000000..32a21a7
--- /dev/null
+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpScheme.java
@@ -0,0 +1,79 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.ArrayTrie;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Trie;
+
+/* ------------------------------------------------------------------------------- */
+/**
+ */
+public enum HttpScheme
+{
+    HTTP("http"),
+    HTTPS("https"),
+    WS("ws"),
+    WSS("wss");
+
+    /* ------------------------------------------------------------ */
+    public final static Trie<HttpScheme> CACHE= new ArrayTrie<HttpScheme>();
+    static
+    {
+        for (HttpScheme version : HttpScheme.values())
+            CACHE.put(version.asString(),version);
+    }
+
+    private final String _string;
+    private final ByteBuffer _buffer;
+
+    /* ------------------------------------------------------------ */
+    HttpScheme(String s)
+    {
+        _string=s;
+        _buffer=BufferUtil.toBuffer(s);
+    }
+
+    /* ------------------------------------------------------------ */
+    public ByteBuffer asByteBuffer()
+    {
+        return _buffer.asReadOnlyBuffer();
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean is(String s)
+    {
+        return _string.equalsIgnoreCase(s);
+    }
+
+    public String asString()
+    {
+        return _string;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        return _string;
+    }
+
+}
diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpSchemes.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpSchemes.java
deleted file mode 100644
index 385aad2..0000000
--- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpSchemes.java
+++ /dev/null
@@ -1,38 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.http;
-
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.ByteArrayBuffer;
-
-/* ------------------------------------------------------------------------------- */
-/** 
- * 
- * 
- */
-public class HttpSchemes
-{
-    public final static String
-        HTTP ="http",
-        HTTPS="https";
-    
-    public final static Buffer
-        HTTP_BUFFER = new ByteArrayBuffer(HTTP),
-        HTTPS_BUFFER = new ByteArrayBuffer(HTTPS);
-}
diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpTester.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpTester.java
new file mode 100644
index 0000000..4eee70c
--- /dev/null
+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpTester.java
@@ -0,0 +1,367 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+
+import org.eclipse.jetty.http.HttpGenerator.RequestInfo;
+import org.eclipse.jetty.http.HttpGenerator.ResponseInfo;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.StringUtil;
+
+public class HttpTester
+{
+    private HttpTester()
+    {
+    }
+
+    public static Request newRequest()
+    {
+        return new Request();
+    }
+
+    public static Request parseRequest(String request)
+    {
+        Request r=new Request();
+        HttpParser parser =new HttpParser(r);
+        parser.parseNext(BufferUtil.toBuffer(request));
+        return r;
+    }
+
+    public static Request parseRequest(ByteBuffer request)
+    {
+        Request r=new Request();
+        HttpParser parser =new HttpParser(r);
+        parser.parseNext(request);
+        return r;
+    }
+
+    public static Response parseResponse(String response)
+    {
+        Response r=new Response();
+        HttpParser parser =new HttpParser(r);
+        parser.parseNext(BufferUtil.toBuffer(response));
+        return r;
+    }
+
+    public static Response parseResponse(ByteBuffer response)
+    {
+        Response r=new Response();
+        HttpParser parser =new HttpParser(r);
+        parser.parseNext(response);
+        return r;
+    }
+
+
+    public abstract static class Message extends HttpFields implements HttpParser.HttpHandler<ByteBuffer>
+    {
+        ByteArrayOutputStream _content;
+        HttpVersion _version=HttpVersion.HTTP_1_0;
+
+        public HttpVersion getVersion()
+        {
+            return _version;
+        }
+
+        public void setVersion(String version)
+        {
+            setVersion(HttpVersion.CACHE.get(version));
+        }
+
+        public void setVersion(HttpVersion version)
+        {
+            _version=version;
+        }
+
+        public void setContent(byte[] bytes)
+        {
+            try
+            {
+                _content=new ByteArrayOutputStream();
+                _content.write(bytes);
+            }
+            catch (IOException e)
+            {
+                throw new RuntimeException(e);
+            }
+        }
+
+        public void setContent(String content)
+        {
+            try
+            {
+                _content=new ByteArrayOutputStream();
+                _content.write(StringUtil.getBytes(content));
+            }
+            catch (IOException e)
+            {
+                throw new RuntimeException(e);
+            }
+        }
+
+        public void setContent(ByteBuffer content)
+        {
+            try
+            {
+                _content=new ByteArrayOutputStream();
+                _content.write(BufferUtil.toArray(content));
+            }
+            catch (IOException e)
+            {
+                throw new RuntimeException(e);
+            }
+        }
+        @Override
+        public boolean parsedHeader(HttpField field)
+        {
+            put(field.getName(),field.getValue());
+            return false;
+        }
+
+        @Override
+        public boolean messageComplete()
+        {
+            return true;
+        }
+
+        @Override
+        public boolean headerComplete()
+        {
+            _content=new ByteArrayOutputStream();
+            return false;
+        }
+
+        @Override
+        public void earlyEOF()
+        {
+        }
+
+        @Override
+        public boolean content(ByteBuffer ref)
+        {
+            try
+            {
+                _content.write(BufferUtil.toArray(ref));
+            }
+            catch (IOException e)
+            {
+                throw new RuntimeException(e);
+            }
+            return false;
+        }
+
+        @Override
+        public void badMessage(int status, String reason)
+        {
+            throw new RuntimeException(reason);
+        }
+
+        public ByteBuffer generate()
+        {
+            try
+            {
+                HttpGenerator generator = new HttpGenerator();
+                HttpGenerator.Info info = getInfo();
+                // System.err.println(info.getClass());
+                // System.err.println(info);
+
+                ByteArrayOutputStream out = new ByteArrayOutputStream();
+                ByteBuffer header=null;
+                ByteBuffer chunk=null;
+                ByteBuffer content=_content==null?null:ByteBuffer.wrap(_content.toByteArray());
+
+
+                loop: while(!generator.isEnd())
+                {
+                    HttpGenerator.Result result =  info instanceof RequestInfo
+                        ?generator.generateRequest((RequestInfo)info,header,chunk,content,true)
+                        :generator.generateResponse((ResponseInfo)info,header,chunk,content,true);
+                    switch(result)
+                    {
+                        case NEED_HEADER:
+                            header=BufferUtil.allocate(8192);
+                            continue;
+
+                        case NEED_CHUNK:
+                            chunk=BufferUtil.allocate(HttpGenerator.CHUNK_SIZE);
+                            continue;
+
+                        case NEED_INFO:
+                            throw new IllegalStateException();
+
+                        case FLUSH:
+                            if (BufferUtil.hasContent(header))
+                            {
+                                out.write(BufferUtil.toArray(header));
+                                BufferUtil.clear(header);
+                            }
+                            if (BufferUtil.hasContent(chunk))
+                            {
+                                out.write(BufferUtil.toArray(chunk));
+                                BufferUtil.clear(chunk);
+                            }
+                            if (BufferUtil.hasContent(content))
+                            {
+                                out.write(BufferUtil.toArray(content));
+                                BufferUtil.clear(content);
+                            }
+                            break;
+
+                        case SHUTDOWN_OUT:
+                            break loop;
+                    }
+                }
+
+                return ByteBuffer.wrap(out.toByteArray());
+            }
+            catch (IOException e)
+            {
+                throw new RuntimeException(e);
+            }
+
+        }
+        abstract public HttpGenerator.Info getInfo();
+
+        @Override
+        public int getHeaderCacheSize()
+        {
+            return 0;
+        }
+
+    }
+
+    public static class Request extends Message implements HttpParser.RequestHandler<ByteBuffer>
+    {
+        private String _method;
+        private String _uri;
+
+        @Override
+        public boolean startRequest(HttpMethod method, String methodString, ByteBuffer uri, HttpVersion version)
+        {
+            _method=methodString;
+            _uri=BufferUtil.toUTF8String(uri);
+            _version=version;
+            return false;
+        }
+
+        public String getMethod()
+        {
+            return _method;
+        }
+
+        public String getUri()
+        {
+            return _uri;
+        }
+
+        public void setMethod(String method)
+        {
+            _method=method;
+        }
+
+        public void setURI(String uri)
+        {
+            _uri=uri;
+        }
+
+        @Override
+        public HttpGenerator.RequestInfo getInfo()
+        {
+            return new HttpGenerator.RequestInfo(_version,this,_content==null?0:_content.size(),_method,_uri);
+        }
+
+        @Override
+        public String toString()
+        {
+            return String.format("%s %s %s\n%s\n",_method,_uri,_version,super.toString());
+        }
+
+        public void setHeader(String name, String value)
+        {
+            put(name,value);
+        }
+
+        @Override
+        public boolean parsedHostHeader(String host,int port)
+        {
+            return false;
+        }
+    }
+
+    public static class Response extends Message implements HttpParser.ResponseHandler<ByteBuffer>
+    {
+        private int _status;
+        private String _reason;
+
+        @Override
+        public boolean startResponse(HttpVersion version, int status, String reason)
+        {
+            _version=version;
+            _status=status;
+            _reason=reason;
+            return false;
+        }
+
+        public int getStatus()
+        {
+            return _status;
+        }
+
+        public String getReason()
+        {
+            return _reason;
+        }
+
+        public byte[] getContentBytes()
+        {
+            if (_content==null)
+                return null;
+            return _content.toByteArray();
+        }
+
+        public String getContent()
+        {
+            if (_content==null)
+                return null;
+            byte[] bytes=_content.toByteArray();
+
+            String content_type=get(HttpHeader.CONTENT_TYPE);
+            String encoding=MimeTypes.getCharsetFromContentType(content_type);
+            Charset charset=encoding==null?StandardCharsets.UTF_8:Charset.forName(encoding);
+
+            return new String(bytes,charset);
+        }
+
+        @Override
+        public HttpGenerator.ResponseInfo getInfo()
+        {
+            return new HttpGenerator.ResponseInfo(_version,this,_content==null?-1:_content.size(),_status,_reason,false);
+        }
+
+        @Override
+        public String toString()
+        {
+            return String.format("%s %s %s\n%s\n",_version,_status,_reason,super.toString());
+        }
+    }
+}
diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpTokens.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpTokens.java
index 165063d..b19f595 100644
--- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpTokens.java
+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpTokens.java
@@ -25,18 +25,14 @@ public interface HttpTokens
 {
     // Terminal symbols.
     static final byte COLON= (byte)':';
-    static final byte SPACE= 0x20;
-    static final byte CARRIAGE_RETURN= 0x0D;
+    static final byte TAB= 0x09;
     static final byte LINE_FEED= 0x0A;
+    static final byte CARRIAGE_RETURN= 0x0D;
+    static final byte SPACE= 0x20;
     static final byte[] CRLF = {CARRIAGE_RETURN,LINE_FEED};
     static final byte SEMI_COLON= (byte)';';
-    static final byte TAB= 0x09;
 
-    public static final int SELF_DEFINING_CONTENT= -4;
-    public static final int UNKNOWN_CONTENT= -3;
-    public static final int CHUNKED_CONTENT= -2;
-    public static final int EOF_CONTENT= -1;
-    public static final int NO_CONTENT= 0;
+    public enum EndOfContent { UNKNOWN_CONTENT,NO_CONTENT,EOF_CONTENT,CONTENT_LENGTH,CHUNKED_CONTENT,SELF_DEFINING_CONTENT }
 
-    
 }
+
diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpURI.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpURI.java
index c4155a8..dbdca99 100644
--- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpURI.java
+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpURI.java
@@ -20,6 +20,8 @@ package org.eclipse.jetty.http;
 
 import java.io.UnsupportedEncodingException;
 import java.net.URI;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
 
 import org.eclipse.jetty.util.MultiMap;
 import org.eclipse.jetty.util.StringUtil;
@@ -60,6 +62,7 @@ public class HttpURI
     QUERY=9,
     ASTERISK=10;
 
+    final Charset _charset;
     boolean _partial=false;
     byte[] _raw=__empty;
     String _rawString;
@@ -75,11 +78,14 @@ public class HttpURI
     int _end;
     boolean _encoded=false;
 
-    final Utf8StringBuilder _utf8b = new Utf8StringBuilder(64);
-
     public HttpURI()
     {
+        _charset = URIUtil.__CHARSET;
+    }
 
+    public HttpURI(Charset charset)
+    {
+        _charset = charset;
     }
 
     /* ------------------------------------------------------------ */
@@ -89,40 +95,43 @@ public class HttpURI
     public HttpURI(boolean parsePartialAuth)
     {
         _partial=parsePartialAuth;
+        _charset = URIUtil.__CHARSET;
     }
 
     public HttpURI(String raw)
     {
         _rawString=raw;
-        byte[] b;
-        try
-        {
-            b = raw.getBytes(StringUtil.__UTF8);
-        }
-        catch (UnsupportedEncodingException e)
-        {
-           throw new RuntimeException(e.getMessage());
-        }
+        byte[] b = raw.getBytes(StandardCharsets.UTF_8);
         parse(b,0,b.length);
+        _charset = URIUtil.__CHARSET;
     }
 
     public HttpURI(byte[] raw,int offset, int length)
     {
         parse2(raw,offset,length);
+        _charset = URIUtil.__CHARSET;
     }
-    
+
     public HttpURI(URI uri)
     {
         parse(uri.toASCIIString());
+        _charset = URIUtil.__CHARSET;
     }
 
     public void parse(String raw)
     {
-        byte[] b = raw.getBytes();
+        byte[] b = StringUtil.getUtf8Bytes(raw);
         parse2(b,0,b.length);
         _rawString=raw;
     }
 
+    public void parseConnect(String raw)
+    {
+        byte[] b = StringUtil.getBytes(raw);
+        parseConnect(b,0,b.length);
+        _rawString=raw;
+    }
+
     public void parse(byte[] raw,int offset, int length)
     {
         _rawString=null;
@@ -180,7 +189,7 @@ public class HttpURI
                     {
                         case '/':
                         {
-                            throw new IllegalArgumentException("No closing ']' for " + StringUtil.toString(_raw,offset,length,URIUtil.__CHARSET));
+                            throw new IllegalArgumentException("No closing ']' for " + new String(_raw,offset,length,_charset));
                         }
                         case ']':
                         {
@@ -399,7 +408,7 @@ public class HttpURI
                     {
                         case '/':
                         {
-                            throw new IllegalArgumentException("No closing ']' for " + StringUtil.toString(_raw,offset,length,URIUtil.__CHARSET));
+                            throw new IllegalArgumentException("No closing ']' for " + new String(_raw,offset,length,_charset));
                         }
                         case ']':
                         {
@@ -497,47 +506,40 @@ public class HttpURI
             _portValue=TypeUtil.parseInt(_raw, _port+1, _path-_port-1,10);
     }
 
-    private String toUtf8String(int offset,int length)
-    {
-        _utf8b.reset();
-        _utf8b.append(_raw,offset,length);
-        return _utf8b.toString();
-    }
-
     public String getScheme()
     {
         if (_scheme==_authority)
             return null;
         int l=_authority-_scheme;
         if (l==5 &&
-            _raw[_scheme]=='h' &&
-            _raw[_scheme+1]=='t' &&
-            _raw[_scheme+2]=='t' &&
-            _raw[_scheme+3]=='p' )
-            return HttpSchemes.HTTP;
+                _raw[_scheme]=='h' &&
+                _raw[_scheme+1]=='t' &&
+                _raw[_scheme+2]=='t' &&
+                _raw[_scheme+3]=='p' )
+            return HttpScheme.HTTP.asString();
         if (l==6 &&
-            _raw[_scheme]=='h' &&
-            _raw[_scheme+1]=='t' &&
-            _raw[_scheme+2]=='t' &&
-            _raw[_scheme+3]=='p' &&
-            _raw[_scheme+4]=='s' )
-            return HttpSchemes.HTTPS;
-
-        return toUtf8String(_scheme,_authority-_scheme-1);
+                _raw[_scheme]=='h' &&
+                _raw[_scheme+1]=='t' &&
+                _raw[_scheme+2]=='t' &&
+                _raw[_scheme+3]=='p' &&
+                _raw[_scheme+4]=='s' )
+            return HttpScheme.HTTPS.asString();
+
+        return new String(_raw,_scheme,_authority-_scheme-1,_charset);
     }
 
     public String getAuthority()
     {
         if (_authority==_path)
             return null;
-        return toUtf8String(_authority,_path-_authority);
+        return new String(_raw,_authority,_path-_authority,_charset);
     }
 
     public String getHost()
     {
         if (_host==_port)
             return null;
-        return toUtf8String(_host,_port-_host);
+        return new String(_raw,_host,_port-_host,_charset);
     }
 
     public int getPort()
@@ -549,7 +551,7 @@ public class HttpURI
     {
         if (_path==_param)
             return null;
-        return toUtf8String(_path,_param-_path);
+        return new String(_raw,_path,_param-_path,_charset);
     }
 
     public String getDecodedPath()
@@ -557,8 +559,7 @@ public class HttpURI
         if (_path==_param)
             return null;
 
-        int length = _param-_path;
-        boolean decoding=false;
+        Utf8StringBuilder utf8b=null;
 
         for (int i=_path;i<_param;i++)
         {
@@ -566,11 +567,10 @@ public class HttpURI
 
             if (b=='%')
             {
-                if (!decoding)
+                if (utf8b==null)
                 {
-                    _utf8b.reset();
-                    _utf8b.append(_raw,_path,i-_path);
-                    decoding=true;
+                    utf8b=new Utf8StringBuilder();
+                    utf8b.append(_raw,_path,i-_path);
                 }
                 
                 if ((i+2)>=_param)
@@ -582,7 +582,7 @@ public class HttpURI
                     try
                     {
                         String unicode = new String(Character.toChars(TypeUtil.parseInt(_raw,i+2,4,16)));
-                        _utf8b.getStringBuilder().append(unicode);
+                        utf8b.getStringBuilder().append(unicode);
                         i+=5;
                     }
                     catch(Exception e)
@@ -593,24 +593,29 @@ public class HttpURI
                 else
                 {
                     b=(byte)(0xff&TypeUtil.parseInt(_raw,i+1,2,16));
-                    _utf8b.append(b);
+                    utf8b.append(b);
                     i+=2;
                 }
                 continue;
             }
-            else if (decoding)
+            else if (utf8b!=null)
             {
-                _utf8b.append(b);
+                utf8b.append(b);
             }
         }
 
-        if (!decoding)
-            return toUtf8String(_path,length);
-        return _utf8b.toString();
+        if (utf8b==null)
+            return StringUtil.toUTF8String(_raw, _path, _param-_path);
+        return utf8b.toString();
     }
-    
+
     public String getDecodedPath(String encoding)
     {
+        return getDecodedPath(Charset.forName(encoding));
+    }
+
+    public String getDecodedPath(Charset encoding)
+    {
         if (_path==_param)
             return null;
 
@@ -669,43 +674,37 @@ public class HttpURI
 
 
         if (bytes==null)
-            return StringUtil.toString(_raw,_path,_param-_path,encoding);
+            return new String(_raw,_path,_param-_path,encoding);
 
-        return StringUtil.toString(bytes,0,n,encoding);
+        return new String(bytes,0,n,encoding);
     }
-    
-    
-    
-    
-    
-
 
     public String getPathAndParam()
     {
         if (_path==_query)
             return null;
-        return toUtf8String(_path,_query-_path);
+        return new String(_raw,_path,_query-_path,_charset);
     }
 
     public String getCompletePath()
     {
         if (_path==_end)
             return null;
-        return toUtf8String(_path,_end-_path);
+        return new String(_raw,_path,_end-_path,_charset);
     }
 
     public String getParam()
     {
         if (_param==_query)
             return null;
-        return toUtf8String(_param+1,_query-_param-1);
+        return new String(_raw,_param+1,_query-_param-1,_charset);
     }
 
     public String getQuery()
     {
         if (_query==_fragment)
             return null;
-        return toUtf8String(_query+1,_fragment-_query-1);
+        return new String(_raw,_query+1,_fragment-_query-1,_charset);
     }
 
     public String getQuery(String encoding)
@@ -724,19 +723,20 @@ public class HttpURI
     {
         if (_fragment==_end)
             return null;
-        return toUtf8String(_fragment+1,_end-_fragment-1);
+        return new String(_raw,_fragment+1,_end-_fragment-1,_charset);
     }
 
-    public void decodeQueryTo(MultiMap parameters)
+    public void decodeQueryTo(MultiMap<String> parameters)
     {
         if (_query==_fragment)
             return;
-        _utf8b.reset();
-        UrlEncoded.decodeUtf8To(_raw,_query+1,_fragment-_query-1,parameters,_utf8b);
+        if (_charset.equals(StandardCharsets.UTF_8))
+            UrlEncoded.decodeUtf8To(_raw,_query+1,_fragment-_query-1,parameters);
+        else
+            UrlEncoded.decodeTo(new String(_raw,_query+1,_fragment-_query-1,_charset),parameters,_charset,-1);
     }
 
-    public void decodeQueryTo(MultiMap parameters, String encoding)
-        throws UnsupportedEncodingException
+    public void decodeQueryTo(MultiMap<String> parameters, String encoding) throws UnsupportedEncodingException
     {
         if (_query==_fragment)
             return;
@@ -744,7 +744,18 @@ public class HttpURI
         if (encoding==null || StringUtil.isUTF8(encoding))
             UrlEncoded.decodeUtf8To(_raw,_query+1,_fragment-_query-1,parameters);
         else
-            UrlEncoded.decodeTo(StringUtil.toString(_raw,_query+1,_fragment-_query-1,encoding),parameters,encoding);
+            UrlEncoded.decodeTo(StringUtil.toString(_raw,_query+1,_fragment-_query-1,encoding),parameters,encoding,-1);
+    }
+
+    public void decodeQueryTo(MultiMap<String> parameters, Charset encoding) throws UnsupportedEncodingException
+    {
+        if (_query==_fragment)
+            return;
+
+        if (encoding==null || StandardCharsets.UTF_8.equals(encoding))
+            UrlEncoded.decodeUtf8To(_raw,_query+1,_fragment-_query-1,parameters);
+        else
+            UrlEncoded.decodeTo(new String(_raw,_query+1,_fragment-_query-1,encoding),parameters,encoding,-1);
     }
 
     public void clear()
@@ -759,7 +770,7 @@ public class HttpURI
     public String toString()
     {
         if (_rawString==null)
-            _rawString=toUtf8String(_scheme,_end-_scheme);
+            _rawString=new String(_raw,_scheme,_end-_scheme,_charset);
         return _rawString;
     }
 
diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpVersion.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpVersion.java
new file mode 100644
index 0000000..9e39feb
--- /dev/null
+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpVersion.java
@@ -0,0 +1,173 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.ArrayTrie;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.Trie;
+
+
+/* ------------------------------------------------------------------------------- */
+public enum HttpVersion
+{
+    HTTP_0_9("HTTP/0.9",9),
+    HTTP_1_0("HTTP/1.0",10),
+    HTTP_1_1("HTTP/1.1",11),
+    HTTP_2("HTTP/2.0",20);
+
+    /* ------------------------------------------------------------ */
+    public final static Trie<HttpVersion> CACHE= new ArrayTrie<HttpVersion>();
+    static
+    {
+        for (HttpVersion version : HttpVersion.values())
+            CACHE.put(version.toString(),version);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** 
+     * Optimised lookup to find a Http Version and whitespace in a byte array.
+     * @param bytes Array containing ISO-8859-1 characters
+     * @param position The first valid index
+     * @param limit The first non valid index
+     * @return A HttpMethod if a match or null if no easy match.
+     */
+    public static HttpVersion lookAheadGet(byte[] bytes, int position, int limit)
+    {
+        int length=limit-position;
+        if (length<9)
+            return null;
+
+        if (bytes[position+4]=='/' && bytes[position+6]=='.' && Character.isWhitespace((char)bytes[position+8]) &&
+            ((bytes[position]=='H' &&  bytes[position+1]=='T' && bytes[position+2]=='T' && bytes[position+3]=='P') ||
+             (bytes[position]=='h' &&  bytes[position+1]=='t' && bytes[position+2]=='t' && bytes[position+3]=='p')))
+        {
+            switch(bytes[position+5])
+            {
+                case '1':
+                    switch(bytes[position+7])
+                    {
+                        case '0':
+                            return HTTP_1_0;
+                        case '1':
+                            return HTTP_1_1;
+                    }
+                    break;
+                case '2':
+                    switch(bytes[position+7])
+                    {
+                        case '0':
+                            return HTTP_2;
+                    }
+                    break;
+            }
+        }
+        
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** 
+     * Optimised lookup to find a HTTP Version and trailing white space in a byte array.
+     * @param buffer buffer containing ISO-8859-1 characters
+     * @return A HttpVersion if a match or null if no easy match.
+     */
+    public static HttpVersion lookAheadGet(ByteBuffer buffer)
+    {
+        if (buffer.hasArray())
+            return lookAheadGet(buffer.array(),buffer.arrayOffset()+buffer.position(),buffer.arrayOffset()+buffer.limit());
+        return null;
+    }
+    
+    
+    private final String _string;
+    private final byte[] _bytes;
+    private final ByteBuffer _buffer;
+    private final int _version;
+
+    /* ------------------------------------------------------------ */
+    HttpVersion(String s,int version)
+    {
+        _string=s;
+        _bytes=StringUtil.getBytes(s);
+        _buffer=ByteBuffer.wrap(_bytes);
+        _version=version;
+    }
+
+    /* ------------------------------------------------------------ */
+    public byte[] toBytes()
+    {
+        return _bytes;
+    }
+
+    /* ------------------------------------------------------------ */
+    public ByteBuffer toBuffer()
+    {
+        return _buffer.asReadOnlyBuffer();
+    }
+
+    /* ------------------------------------------------------------ */
+    public int getVersion()
+    {
+        return _version;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean is(String s)
+    {
+        return _string.equalsIgnoreCase(s);    
+    }
+    
+    /* ------------------------------------------------------------ */
+    public String asString()
+    {
+        return _string;
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        return _string;
+    }
+
+    /**
+     * Case insensitive fromString() conversion
+     * @param version the String to convert to enum constant
+     * @return the enum constant or null if version unknown
+     */
+    public static HttpVersion fromString(String version)
+    {
+        return CACHE.get(version);
+    }
+
+    /* ------------------------------------------------------------ */
+    public static HttpVersion fromVersion(int version)
+    {
+        switch(version)
+        {
+            case 9: return HttpVersion.HTTP_0_9;
+            case 10: return HttpVersion.HTTP_1_0;
+            case 11: return HttpVersion.HTTP_1_1;
+            case 20: return HttpVersion.HTTP_2;
+            default: throw new IllegalArgumentException();
+        }
+    }
+}
diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpVersions.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpVersions.java
deleted file mode 100644
index 1abba86..0000000
--- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpVersions.java
+++ /dev/null
@@ -1,47 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.http;
-
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.BufferCache;
-
-/* ------------------------------------------------------------------------------- */
-/** 
- * 
- * 
- */
-public class HttpVersions
-{
-	public final static String
-		HTTP_0_9 = "",
-		HTTP_1_0 = "HTTP/1.0",
-		HTTP_1_1 = "HTTP/1.1";
-		
-	public final static int
-		HTTP_0_9_ORDINAL=9,
-		HTTP_1_0_ORDINAL=10,
-		HTTP_1_1_ORDINAL=11;
-	
-	public final static BufferCache CACHE = new BufferCache();
-	
-    public final static Buffer 
-        HTTP_0_9_BUFFER=CACHE.add(HTTP_0_9,HTTP_0_9_ORDINAL),
-        HTTP_1_0_BUFFER=CACHE.add(HTTP_1_0,HTTP_1_0_ORDINAL),
-        HTTP_1_1_BUFFER=CACHE.add(HTTP_1_1,HTTP_1_1_ORDINAL);
-}
diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/MimeTypes.java b/jetty-http/src/main/java/org/eclipse/jetty/http/MimeTypes.java
index 9fc0772..9a6b0cc 100644
--- a/jetty-http/src/main/java/org/eclipse/jetty/http/MimeTypes.java
+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/MimeTypes.java
@@ -18,122 +18,181 @@
 
 package org.eclipse.jetty.http;
 
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
 import java.util.Enumeration;
 import java.util.HashMap;
-import java.util.Iterator;
+import java.util.HashSet;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.MissingResourceException;
 import java.util.ResourceBundle;
+import java.util.Set;
 
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.BufferCache;
-import org.eclipse.jetty.io.BufferCache.CachedBuffer;
+import org.eclipse.jetty.util.ArrayTrie;
+import org.eclipse.jetty.util.BufferUtil;
 import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.Trie;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
 
 
 /* ------------------------------------------------------------ */
-/** 
+/**
  * 
  */
 public class MimeTypes
 {
-    private static final Logger LOG = Log.getLogger(MimeTypes.class);
+    public enum Type
+    {
+        FORM_ENCODED("application/x-www-form-urlencoded"),
+        MESSAGE_HTTP("message/http"),
+        MULTIPART_BYTERANGES("multipart/byteranges"),
+
+        TEXT_HTML("text/html"),
+        TEXT_PLAIN("text/plain"),
+        TEXT_XML("text/xml"),
+        TEXT_JSON("text/json",StandardCharsets.UTF_8),
+        APPLICATION_JSON("application/json",StandardCharsets.UTF_8),
 
-    public final static String
-      FORM_ENCODED="application/x-www-form-urlencoded",
-      MESSAGE_HTTP="message/http",
-      MULTIPART_BYTERANGES="multipart/byteranges",
-      
-      TEXT_HTML="text/html",
-      TEXT_PLAIN="text/plain",
-      TEXT_XML="text/xml",
-      TEXT_JSON="text/json",
-      
-      TEXT_HTML_8859_1="text/html;charset=ISO-8859-1",
-      TEXT_PLAIN_8859_1="text/plain;charset=ISO-8859-1",
-      TEXT_XML_8859_1="text/xml;charset=ISO-8859-1",
-      
-      TEXT_HTML_UTF_8="text/html;charset=UTF-8",
-      TEXT_PLAIN_UTF_8="text/plain;charset=UTF-8",
-      TEXT_XML_UTF_8="text/xml;charset=UTF-8",
-      TEXT_JSON_UTF_8="text/json;charset=UTF-8";
-
-    private final static String
-      TEXT_HTML__8859_1="text/html; charset=ISO-8859-1",
-      TEXT_PLAIN__8859_1="text/plain; charset=ISO-8859-1",
-      TEXT_XML__8859_1="text/xml; charset=ISO-8859-1",
-      TEXT_HTML__UTF_8="text/html; charset=UTF-8",
-      TEXT_PLAIN__UTF_8="text/plain; charset=UTF-8",
-      TEXT_XML__UTF_8="text/xml; charset=UTF-8",
-      TEXT_JSON__UTF_8="text/json; charset=UTF-8";
-
-    private final static int
-	FORM_ENCODED_ORDINAL=1,
-    	MESSAGE_HTTP_ORDINAL=2,
-    	MULTIPART_BYTERANGES_ORDINAL=3,
-    	
-    	TEXT_HTML_ORDINAL=4,
-	TEXT_PLAIN_ORDINAL=5,
-	TEXT_XML_ORDINAL=6,
-        TEXT_JSON_ORDINAL=7,
-	
-        TEXT_HTML_8859_1_ORDINAL=8,
-        TEXT_PLAIN_8859_1_ORDINAL=9,
-        TEXT_XML_8859_1_ORDINAL=10,
+        TEXT_HTML_8859_1("text/html; charset=ISO-8859-1",TEXT_HTML),
+        TEXT_HTML_UTF_8("text/html; charset=UTF-8",TEXT_HTML),
         
-        TEXT_HTML_UTF_8_ORDINAL=11,
-        TEXT_PLAIN_UTF_8_ORDINAL=12,
-        TEXT_XML_UTF_8_ORDINAL=13,
-        TEXT_JSON_UTF_8_ORDINAL=14;
-    
-    private static int __index=15;
-    
-    public final static BufferCache CACHE = new BufferCache(); 
+        TEXT_PLAIN_8859_1("text/plain; charset=ISO-8859-1",TEXT_PLAIN),
+        TEXT_PLAIN_UTF_8("text/plain; charset=UTF-8",TEXT_PLAIN),
+        
+        TEXT_XML_8859_1("text/xml; charset=ISO-8859-1",TEXT_XML),
+        TEXT_XML_UTF_8("text/xml; charset=UTF-8",TEXT_XML),
+        
+        TEXT_JSON_8859_1("text/json; charset=ISO-8859-1",TEXT_JSON),
+        TEXT_JSON_UTF_8("text/json; charset=UTF-8",TEXT_JSON),
+        
+        APPLICATION_JSON_8859_1("text/json; charset=ISO-8859-1",APPLICATION_JSON),
+        APPLICATION_JSON_UTF_8("text/json; charset=UTF-8",APPLICATION_JSON);
+
+
+        /* ------------------------------------------------------------ */
+        private final String _string;
+        private final Type _base;
+        private final ByteBuffer _buffer;
+        private final Charset _charset;
+        private final boolean _assumedCharset;
+        private final HttpField _field;
+
+        /* ------------------------------------------------------------ */
+        Type(String s)
+        {
+            _string=s;
+            _buffer=BufferUtil.toBuffer(s);
+            _base=this;
+            _charset=null;
+            _assumedCharset=false;
+            _field=new HttpGenerator.CachedHttpField(HttpHeader.CONTENT_TYPE,_string);
+        } 
+
+        /* ------------------------------------------------------------ */
+        Type(String s,Type base)
+        {
+            _string=s;
+            _buffer=BufferUtil.toBuffer(s);
+            _base=base;
+            int i=s.indexOf("; charset=");
+            _charset=Charset.forName(s.substring(i+10));
+            _assumedCharset=false;
+            _field=new HttpGenerator.CachedHttpField(HttpHeader.CONTENT_TYPE,_string);
+        }
+
+        /* ------------------------------------------------------------ */
+        Type(String s,Charset cs)
+        {
+            _string=s;
+            _base=this;
+            _buffer=BufferUtil.toBuffer(s);
+            _charset=cs;
+            _assumedCharset=true;
+            _field=new HttpGenerator.CachedHttpField(HttpHeader.CONTENT_TYPE,_string);
+        }
 
-    public final static CachedBuffer
-    	FORM_ENCODED_BUFFER=CACHE.add(FORM_ENCODED,FORM_ENCODED_ORDINAL),
-    	MESSAGE_HTTP_BUFFER=CACHE.add(MESSAGE_HTTP, MESSAGE_HTTP_ORDINAL),
-    	MULTIPART_BYTERANGES_BUFFER=CACHE.add(MULTIPART_BYTERANGES,MULTIPART_BYTERANGES_ORDINAL),
+        /* ------------------------------------------------------------ */
+        public ByteBuffer asBuffer()
+        {
+            return _buffer.asReadOnlyBuffer();
+        }
         
-        TEXT_HTML_BUFFER=CACHE.add(TEXT_HTML,TEXT_HTML_ORDINAL),
-        TEXT_PLAIN_BUFFER=CACHE.add(TEXT_PLAIN,TEXT_PLAIN_ORDINAL),
-        TEXT_XML_BUFFER=CACHE.add(TEXT_XML,TEXT_XML_ORDINAL),
-        TEXT_JSON_BUFFER=CACHE.add(TEXT_JSON,TEXT_JSON_ORDINAL),
-
-        TEXT_HTML_8859_1_BUFFER=CACHE.add(TEXT_HTML_8859_1,TEXT_HTML_8859_1_ORDINAL),
-        TEXT_PLAIN_8859_1_BUFFER=CACHE.add(TEXT_PLAIN_8859_1,TEXT_PLAIN_8859_1_ORDINAL),
-        TEXT_XML_8859_1_BUFFER=CACHE.add(TEXT_XML_8859_1,TEXT_XML_8859_1_ORDINAL),
+        /* ------------------------------------------------------------ */
+        public Charset getCharset()
+        {
+            return _charset;
+        }
         
-        TEXT_HTML_UTF_8_BUFFER=CACHE.add(TEXT_HTML_UTF_8,TEXT_HTML_UTF_8_ORDINAL),
-        TEXT_PLAIN_UTF_8_BUFFER=CACHE.add(TEXT_PLAIN_UTF_8,TEXT_PLAIN_UTF_8_ORDINAL),
-        TEXT_XML_UTF_8_BUFFER=CACHE.add(TEXT_XML_UTF_8,TEXT_XML_UTF_8_ORDINAL),
-        TEXT_JSON_UTF_8_BUFFER=CACHE.add(TEXT_JSON_UTF_8,TEXT_JSON_UTF_8_ORDINAL),
-
-        TEXT_HTML__8859_1_BUFFER=CACHE.add(TEXT_HTML__8859_1,TEXT_HTML_8859_1_ORDINAL),
-        TEXT_PLAIN__8859_1_BUFFER=CACHE.add(TEXT_PLAIN__8859_1,TEXT_PLAIN_8859_1_ORDINAL),
-        TEXT_XML__8859_1_BUFFER=CACHE.add(TEXT_XML__8859_1,TEXT_XML_8859_1_ORDINAL),
+        /* ------------------------------------------------------------ */
+        public boolean is(String s)
+        {
+            return _string.equalsIgnoreCase(s);    
+        }
+
+        /* ------------------------------------------------------------ */
+        public String asString()
+        {
+            return _string;
+        }
         
-        TEXT_HTML__UTF_8_BUFFER=CACHE.add(TEXT_HTML__UTF_8,TEXT_HTML_UTF_8_ORDINAL),
-        TEXT_PLAIN__UTF_8_BUFFER=CACHE.add(TEXT_PLAIN__UTF_8,TEXT_PLAIN_UTF_8_ORDINAL),
-        TEXT_XML__UTF_8_BUFFER=CACHE.add(TEXT_XML__UTF_8,TEXT_XML_UTF_8_ORDINAL),
-        TEXT_JSON__UTF_8_BUFFER=CACHE.add(TEXT_JSON__UTF_8,TEXT_JSON_UTF_8_ORDINAL);
+        /* ------------------------------------------------------------ */
+        @Override
+        public String toString()
+        {
+            return _string;
+        }
+
+        /* ------------------------------------------------------------ */
+        public boolean isCharsetAssumed()
+        {
+            return _assumedCharset;
+        }
+
+        /* ------------------------------------------------------------ */
+        public HttpField getContentTypeField()
+        {
+            return _field;
+        }
+
+        /* ------------------------------------------------------------ */
+        public Type getBaseType()
+        {
+            return _base;
+        }
+    }
 
-    
-    /* ------------------------------------------------------------ */
     /* ------------------------------------------------------------ */
-    private final static Map __dftMimeMap = new HashMap();
-    private final static Map __encodings = new HashMap();
+    private static final Logger LOG = Log.getLogger(MimeTypes.class);
+    public  final static Trie<MimeTypes.Type> CACHE= new ArrayTrie<>(512);
+    private final static Trie<ByteBuffer> TYPES= new ArrayTrie<ByteBuffer>(512);
+    private final static Map<String,String> __dftMimeMap = new HashMap<String,String>();
+    private final static Map<String,String> __encodings = new HashMap<String,String>();
+
     static
     {
+        for (MimeTypes.Type type : MimeTypes.Type.values())
+        {
+            CACHE.put(type.toString(),type);
+            TYPES.put(type.toString(),type.asBuffer());
+
+            int charset=type.toString().indexOf(";charset=");
+            if (charset>0)
+            {
+                CACHE.put(type.toString().replace(";charset=","; charset="),type);
+                TYPES.put(type.toString().replace(";charset=","; charset="),type.asBuffer());
+            }
+        }
+
         try
         {
             ResourceBundle mime = ResourceBundle.getBundle("org/eclipse/jetty/http/mime");
-            Enumeration i = mime.getKeys();
+            Enumeration<String> i = mime.getKeys();
             while(i.hasMoreElements())
             {
-                String ext = (String)i.nextElement();
+                String ext = i.nextElement();
                 String m = mime.getString(ext);
                 __dftMimeMap.put(StringUtil.asciiToLowerCase(ext),normalizeMimeType(m));
             }
@@ -147,11 +206,11 @@ public class MimeTypes
         try
         {
             ResourceBundle encoding = ResourceBundle.getBundle("org/eclipse/jetty/http/encoding");
-            Enumeration i = encoding.getKeys();
+            Enumeration<String> i = encoding.getKeys();
             while(i.hasMoreElements())
             {
-                Buffer type = normalizeMimeType((String)i.nextElement());
-                __encodings.put(type,encoding.getString(type.toString()));
+                String type = i.nextElement();
+                __encodings.put(type,encoding.getString(type));
             }
         }
         catch(MissingResourceException e)
@@ -159,40 +218,12 @@ public class MimeTypes
             LOG.warn(e.toString());
             LOG.debug(e);
         }
-
-        
-        TEXT_HTML_BUFFER.setAssociate("ISO-8859-1",TEXT_HTML_8859_1_BUFFER);
-        TEXT_HTML_BUFFER.setAssociate("ISO_8859_1",TEXT_HTML_8859_1_BUFFER);
-        TEXT_HTML_BUFFER.setAssociate("iso-8859-1",TEXT_HTML_8859_1_BUFFER);
-        TEXT_PLAIN_BUFFER.setAssociate("ISO-8859-1",TEXT_PLAIN_8859_1_BUFFER);
-        TEXT_PLAIN_BUFFER.setAssociate("ISO_8859_1",TEXT_PLAIN_8859_1_BUFFER);
-        TEXT_PLAIN_BUFFER.setAssociate("iso-8859-1",TEXT_PLAIN_8859_1_BUFFER);
-        TEXT_XML_BUFFER.setAssociate("ISO-8859-1",TEXT_XML_8859_1_BUFFER);
-        TEXT_XML_BUFFER.setAssociate("ISO_8859_1",TEXT_XML_8859_1_BUFFER);
-        TEXT_XML_BUFFER.setAssociate("iso-8859-1",TEXT_XML_8859_1_BUFFER);
-
-        TEXT_HTML_BUFFER.setAssociate("UTF-8",TEXT_HTML_UTF_8_BUFFER);
-        TEXT_HTML_BUFFER.setAssociate("UTF8",TEXT_HTML_UTF_8_BUFFER);
-        TEXT_HTML_BUFFER.setAssociate("utf8",TEXT_HTML_UTF_8_BUFFER);
-        TEXT_HTML_BUFFER.setAssociate("utf-8",TEXT_HTML_UTF_8_BUFFER);
-        TEXT_PLAIN_BUFFER.setAssociate("UTF-8",TEXT_PLAIN_UTF_8_BUFFER);
-        TEXT_PLAIN_BUFFER.setAssociate("UTF8",TEXT_PLAIN_UTF_8_BUFFER);
-        TEXT_PLAIN_BUFFER.setAssociate("utf8",TEXT_PLAIN_UTF_8_BUFFER);
-        TEXT_PLAIN_BUFFER.setAssociate("utf-8",TEXT_PLAIN_UTF_8_BUFFER);
-        TEXT_XML_BUFFER.setAssociate("UTF-8",TEXT_XML_UTF_8_BUFFER);
-        TEXT_XML_BUFFER.setAssociate("UTF8",TEXT_XML_UTF_8_BUFFER);
-        TEXT_XML_BUFFER.setAssociate("utf8",TEXT_XML_UTF_8_BUFFER);
-        TEXT_XML_BUFFER.setAssociate("utf-8",TEXT_XML_UTF_8_BUFFER);
-        TEXT_JSON_BUFFER.setAssociate("UTF-8",TEXT_JSON_UTF_8_BUFFER);
-        TEXT_JSON_BUFFER.setAssociate("UTF8",TEXT_JSON_UTF_8_BUFFER);
-        TEXT_JSON_BUFFER.setAssociate("utf8",TEXT_JSON_UTF_8_BUFFER);
-        TEXT_JSON_BUFFER.setAssociate("utf-8",TEXT_JSON_UTF_8_BUFFER);
     }
 
 
     /* ------------------------------------------------------------ */
-    private Map _mimeMap;
-    
+    private final Map<String,String> _mimeMap=new HashMap<String,String>();
+
     /* ------------------------------------------------------------ */
     /** Constructor.
      */
@@ -201,7 +232,7 @@ public class MimeTypes
     }
 
     /* ------------------------------------------------------------ */
-    public synchronized Map getMimeMap()
+    public synchronized Map<String,String> getMimeMap()
     {
         return _mimeMap;
     }
@@ -210,33 +241,25 @@ public class MimeTypes
     /**
      * @param mimeMap A Map of file extension to mime-type.
      */
-    public void setMimeMap(Map mimeMap)
+    public void setMimeMap(Map<String,String> mimeMap)
     {
-        if (mimeMap==null)
-        {
-            _mimeMap=null;
-            return;
-        }
-        
-        Map m=new HashMap();
-        Iterator i=mimeMap.entrySet().iterator();
-        while (i.hasNext())
+        _mimeMap.clear();
+        if (mimeMap!=null)
         {
-            Map.Entry entry = (Map.Entry)i.next();
-            m.put(entry.getKey(),normalizeMimeType(entry.getValue().toString()));
+            for (Entry<String, String> ext : mimeMap.entrySet())
+                _mimeMap.put(StringUtil.asciiToLowerCase(ext.getKey()),normalizeMimeType(ext.getValue()));
         }
-        _mimeMap=m;
     }
-
+    
     /* ------------------------------------------------------------ */
     /** Get the MIME type by filename extension.
      * @param filename A file name
      * @return MIME type matching the longest dot extension of the
      * file name.
      */
-    public Buffer getMimeByExtension(String filename)
+    public String getMimeByExtension(String filename)
     {
-        Buffer type=null;
+        String type=null;
 
         if (filename!=null)
         {
@@ -250,18 +273,18 @@ public class MimeTypes
 
                 String ext=StringUtil.asciiToLowerCase(filename.substring(i+1));
                 if (_mimeMap!=null)
-                    type = (Buffer)_mimeMap.get(ext);
+                    type=_mimeMap.get(ext);
                 if (type==null)
-                    type=(Buffer)__dftMimeMap.get(ext);
+                    type=__dftMimeMap.get(ext);
             }
         }
 
         if (type==null)
         {
             if (_mimeMap!=null)
-                type=(Buffer)_mimeMap.get("*");
-             if (type==null)
-                 type=(Buffer)__dftMimeMap.get("*");
+                type=_mimeMap.get("*");
+            if (type==null)
+                type=__dftMimeMap.get("*");
         }
 
         return type;
@@ -274,57 +297,46 @@ public class MimeTypes
      */
     public void addMimeMapping(String extension,String type)
     {
-        if (_mimeMap==null)
-            _mimeMap=new HashMap();
-        
         _mimeMap.put(StringUtil.asciiToLowerCase(extension),normalizeMimeType(type));
     }
 
     /* ------------------------------------------------------------ */
-    private static synchronized Buffer normalizeMimeType(String type)
+    public static Set<String> getKnownMimeTypes()
     {
-        Buffer b =CACHE.get(type);
-        if (b==null)
-            b=CACHE.add(type,__index++);
-        return b;
+        return new HashSet<>(__dftMimeMap.values());
+    }
+    
+    /* ------------------------------------------------------------ */
+    private static String normalizeMimeType(String type)
+    {
+        MimeTypes.Type t =CACHE.get(type);
+        if (t!=null)
+            return t.asString();
+
+        return StringUtil.asciiToLowerCase(type);
     }
 
     /* ------------------------------------------------------------ */
-    public static String getCharsetFromContentType(Buffer value)
+    public static String getCharsetFromContentType(String value)
     {
-        if (value instanceof CachedBuffer)
-        {
-            switch(((CachedBuffer)value).getOrdinal())
-            {
-                case TEXT_HTML_8859_1_ORDINAL:
-                case TEXT_PLAIN_8859_1_ORDINAL:
-                case TEXT_XML_8859_1_ORDINAL:
-                    return StringUtil.__ISO_8859_1;
-
-                case TEXT_HTML_UTF_8_ORDINAL:
-                case TEXT_PLAIN_UTF_8_ORDINAL:
-                case TEXT_XML_UTF_8_ORDINAL:
-                case TEXT_JSON_UTF_8_ORDINAL:
-                    return StringUtil.__UTF8;
-            }
-        }
-        
-        int i=value.getIndex();
-        int end=value.putIndex();
+        if (value==null)
+            return null;
+        int end=value.length();
         int state=0;
         int start=0;
         boolean quote=false;
+        int i=0;
         for (;i<end;i++)
         {
-            byte b = value.peek(i);
-            
+            char b = value.charAt(i);
+
             if (quote && state!=10)
             {
                 if ('"'==b)
                     quote=false;
                 continue;
             }
-                
+
             switch(state)
             {
                 case 0:
@@ -346,11 +358,11 @@ public class MimeTypes
                 case 7: if ('t'==b) state=8; else state=0;break;
 
                 case 8: if ('='==b) state=9; else if (' '!=b) state=0; break;
-                
-                case 9: 
-                    if (' '==b) 
+
+                case 9:
+                    if (' '==b)
                         break;
-                    if ('"'==b) 
+                    if ('"'==b)
                     {
                         quote=true;
                         start=i+1;
@@ -360,17 +372,114 @@ public class MimeTypes
                     start=i;
                     state=10;
                     break;
-                    
+
                 case 10:
                     if (!quote && (';'==b || ' '==b )||
-                        (quote && '"'==b ))
-                        return CACHE.lookup(value.peek(start,i-start)).toString(StringUtil.__UTF8);
+                            (quote && '"'==b ))
+                        return StringUtil.normalizeCharset(value,start,i-start);
             }
-        }    
-        
+        }
+
         if (state==10)
-            return CACHE.lookup(value.peek(start,i-start)).toString(StringUtil.__UTF8);
-        
-        return (String)__encodings.get(value);
+            return StringUtil.normalizeCharset(value,start,i-start);
+
+        return null;
+    }
+
+    public static String inferCharsetFromContentType(String value)
+    {
+        return __encodings.get(value);
+    }
+    
+    public static String getContentTypeWithoutCharset(String value)
+    {
+        int end=value.length();
+        int state=0;
+        int start=0;
+        boolean quote=false;
+        int i=0;
+        StringBuilder builder=null;
+        for (;i<end;i++)
+        {
+            char b = value.charAt(i);
+
+            if ('"'==b)
+            {
+                if (quote)
+                {
+                    quote=false;
+                }
+                else
+                {
+                    quote=true;
+                }
+                
+                switch(state)
+                {
+                    case 11:
+                        builder.append(b);break;
+                    case 10:
+                        break;
+                    case 9:
+                        builder=new StringBuilder();
+                        builder.append(value,0,start+1);
+                        state=10;
+                        break;
+                    default:
+                        start=i;
+                        state=0;           
+                }
+                continue;
+            }
+            
+            if (quote)
+            {
+                if (builder!=null && state!=10)
+                    builder.append(b);
+                continue;
+            }
+
+            switch(state)
+            {
+                case 0:
+                    if (';'==b)
+                        state=1;
+                    else if (' '!=b)
+                        start=i;
+                    break;
+
+                case 1: if ('c'==b) state=2; else if (' '!=b) state=0; break;
+                case 2: if ('h'==b) state=3; else state=0;break;
+                case 3: if ('a'==b) state=4; else state=0;break;
+                case 4: if ('r'==b) state=5; else state=0;break;
+                case 5: if ('s'==b) state=6; else state=0;break;
+                case 6: if ('e'==b) state=7; else state=0;break;
+                case 7: if ('t'==b) state=8; else state=0;break;
+                case 8: if ('='==b) state=9; else if (' '!=b) state=0; break;
+
+                case 9:
+                    if (' '==b)
+                        break;
+                    builder=new StringBuilder();
+                    builder.append(value,0,start+1);
+                    state=10;
+                    break;
+
+                case 10:
+                    if (';'==b)
+                    {
+                        builder.append(b);
+                        state=11;
+                    }
+                    break;
+                case 11:
+                    if (' '!=b)
+                        builder.append(b);
+            }
+        }
+        if (builder==null)
+            return value;
+        return builder.toString();
+
     }
 }
diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/Parser.java b/jetty-http/src/main/java/org/eclipse/jetty/http/Parser.java
deleted file mode 100644
index 08fc41c..0000000
--- a/jetty-http/src/main/java/org/eclipse/jetty/http/Parser.java
+++ /dev/null
@@ -1,47 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.http;
-
-import java.io.IOException;
-
-/**
- * Abstract interface for a connection Parser for use by Jetty.
- */
-public interface Parser
-{
-    void returnBuffers();
-    void reset();
-
-    boolean isComplete();
-
-    /**
-     * @return True if progress made
-     * @throws IOException
-     */
-    boolean parseAvailable() throws IOException;
-
-    boolean isMoreInBuffer() throws IOException;
-
-    boolean isIdle();
-    
-    boolean isPersistent();
-    
-    void setPersistent(boolean persistent);
-
-}
diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/PathMap.java b/jetty-http/src/main/java/org/eclipse/jetty/http/PathMap.java
index f878f52..2c68dce 100644
--- a/jetty-http/src/main/java/org/eclipse/jetty/http/PathMap.java
+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/PathMap.java
@@ -18,16 +18,15 @@
 
 package org.eclipse.jetty.http;
 
-import java.io.Externalizable;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 import java.util.StringTokenizer;
 
-import org.eclipse.jetty.util.LazyList;
-import org.eclipse.jetty.util.StringMap;
+import org.eclipse.jetty.util.ArrayTernaryTrie;
+import org.eclipse.jetty.util.Trie;
 import org.eclipse.jetty.util.URIUtil;
 
 /* ------------------------------------------------------------ */
@@ -40,6 +39,7 @@ import org.eclipse.jetty.util.URIUtil;
  * /foo/*             - a prefix path specification (must end '/*').
  * *.ext              - a suffix path specification.
  * /                  - the default path specification.
+ * ""                 - the / path specification
  * </PRE>
  * Matching is performed in the following order <NL>
  * <LI>Exact match.
@@ -61,7 +61,7 @@ import org.eclipse.jetty.util.URIUtil;
  *
  *
  */
-public class PathMap extends HashMap implements Externalizable
+public class PathMap<O> extends HashMap<String,O>
 {
     /* ------------------------------------------------------------ */
     private static String __pathSpecSeparators = ":,";
@@ -79,67 +79,46 @@ public class PathMap extends HashMap implements Externalizable
     }
 
     /* --------------------------------------------------------------- */
-    final StringMap _prefixMap=new StringMap();
-    final StringMap _suffixMap=new StringMap();
-    final StringMap _exactMap=new StringMap();
-
-    List _defaultSingletonList=null;
-    Entry _prefixDefault=null;
-    Entry _default=null;
-    final Set _entrySet;
+    Trie<MappedEntry<O>> _prefixMap=new ArrayTernaryTrie<>(false);
+    Trie<MappedEntry<O>> _suffixMap=new ArrayTernaryTrie<>(false);
+    final Map<String,MappedEntry<O>> _exactMap=new HashMap<>();
+
+    List<MappedEntry<O>> _defaultSingletonList=null;
+    MappedEntry<O> _prefixDefault=null;
+    MappedEntry<O> _default=null;
     boolean _nodefault=false;
 
     /* --------------------------------------------------------------- */
-    /** Construct empty PathMap.
-     */
     public PathMap()
     {
-        super(11);
-        _entrySet=entrySet();
+        this(11);
     }
 
     /* --------------------------------------------------------------- */
-    /** Construct empty PathMap.
-     */
-    public PathMap(boolean nodefault)
+    public PathMap(boolean noDefault)
     {
-        super(11);
-        _entrySet=entrySet();
-        _nodefault=nodefault;
+        this(11, noDefault);
     }
 
     /* --------------------------------------------------------------- */
-    /** Construct empty PathMap.
-     */
     public PathMap(int capacity)
     {
-        super (capacity);
-        _entrySet=entrySet();
+        this(capacity, false);
     }
 
     /* --------------------------------------------------------------- */
-    /** Construct from dictionary PathMap.
-     */
-    public PathMap(Map m)
-    {
-        putAll(m);
-        _entrySet=entrySet();
-    }
-
-    /* ------------------------------------------------------------ */
-    public void writeExternal(java.io.ObjectOutput out)
-        throws java.io.IOException
+    private PathMap(int capacity, boolean noDefault)
     {
-        HashMap map = new HashMap(this);
-        out.writeObject(map);
+        super(capacity);
+        _nodefault=noDefault;
     }
 
-    /* ------------------------------------------------------------ */
-    public void readExternal(java.io.ObjectInput in)
-        throws java.io.IOException, ClassNotFoundException
+    /* --------------------------------------------------------------- */
+    /** Construct from dictionary PathMap.
+     */
+    public PathMap(Map<String, ? extends O> m)
     {
-        HashMap map = (HashMap)in.readObject();
-        this.putAll(map);
+        putAll(m);
     }
 
     /* --------------------------------------------------------------- */
@@ -149,19 +128,18 @@ public class PathMap extends HashMap implements Externalizable
      * @param object The object the path maps to
      */
     @Override
-    public Object put(Object pathSpec, Object object)
+    public O put(String pathSpec, O object)
     {
-        String str = pathSpec.toString();
-        if ("".equals(str.trim()))
-        {          
-            Entry entry = new Entry("",object);
+        if ("".equals(pathSpec.trim()))
+        {
+            MappedEntry<O> entry = new MappedEntry<>("",object);
             entry.setMapped("");
             _exactMap.put("", entry);
             return super.put("", object);
         }
-        
-        StringTokenizer tok = new StringTokenizer(str,__pathSpecSeparators);
-        Object old =null;
+
+        StringTokenizer tok = new StringTokenizer(pathSpec,__pathSpecSeparators);
+        O old =null;
 
         while (tok.hasMoreTokens())
         {
@@ -173,7 +151,7 @@ public class PathMap extends HashMap implements Externalizable
             old = super.put(spec,object);
 
             // Make entry that was just created.
-            Entry entry = new Entry(spec,object);
+            MappedEntry<O> entry = new MappedEntry<>(spec,object);
 
             if (entry.getKey().equals(spec))
             {
@@ -183,12 +161,15 @@ public class PathMap extends HashMap implements Externalizable
                 {
                     String mapped=spec.substring(0,spec.length()-2);
                     entry.setMapped(mapped);
-                    _prefixMap.put(mapped,entry);
-                    _exactMap.put(mapped,entry);
-                    _exactMap.put(spec.substring(0,spec.length()-1),entry);
+                    while (!_prefixMap.put(mapped,entry))
+                        _prefixMap=new ArrayTernaryTrie<>((ArrayTernaryTrie<MappedEntry<O>>)_prefixMap,1.5);
                 }
                 else if (spec.startsWith("*."))
-                    _suffixMap.put(spec.substring(2),entry);
+                {
+                    String suffix=spec.substring(2);
+                    while(!_suffixMap.put(suffix,entry))
+                        _suffixMap=new ArrayTernaryTrie<>((ArrayTernaryTrie<MappedEntry<O>>)_suffixMap,1.5);
+                }
                 else if (spec.equals(URIUtil.SLASH))
                 {
                     if (_nodefault)
@@ -196,8 +177,7 @@ public class PathMap extends HashMap implements Externalizable
                     else
                     {
                         _default=entry;
-                        _defaultSingletonList=
-                            Collections.singletonList(_default);
+                        _defaultSingletonList=Collections.singletonList(_default);
                     }
                 }
                 else
@@ -216,9 +196,9 @@ public class PathMap extends HashMap implements Externalizable
      * @param path the path.
      * @return Best matched object or null.
      */
-    public Object match(String path)
+    public O match(String path)
     {
-        Map.Entry entry = getMatch(path);
+        MappedEntry<O> entry = getMatch(path);
         if (entry!=null)
             return entry.getValue();
         return null;
@@ -230,35 +210,40 @@ public class PathMap extends HashMap implements Externalizable
      * @param path the path.
      * @return Map.Entry of the best matched  or null.
      */
-    public Entry getMatch(String path)
+    public MappedEntry<O> getMatch(String path)
     {
-        Map.Entry entry=null;
-
         if (path==null)
             return null;
 
         int l=path.length();
-        
+
+        MappedEntry<O> entry=null;
+
         //special case
         if (l == 1 && path.charAt(0)=='/')
         {
-            entry = (Map.Entry)_exactMap.get("");
+            entry = _exactMap.get("");
             if (entry != null)
-                return (Entry)entry;
+                return entry;
         }
-        
+
         // try exact match
-        entry=_exactMap.getEntry(path,0,l);
+        entry=_exactMap.get(path);
         if (entry!=null)
-            return (Entry) entry.getValue();
+            return entry;
 
         // prefix search
         int i=l;
-        while((i=path.lastIndexOf('/',i-1))>=0)
+        final Trie<PathMap.MappedEntry<O>> prefix_map=_prefixMap;
+        while(i>=0)
         {
-            entry=_prefixMap.getEntry(path,0,i);
-            if (entry!=null)
-                return (Entry) entry.getValue();
+            entry=prefix_map.getBest(path,0,i);
+            if (entry==null)
+                break;
+            String key = entry.getKey();
+            if (key.length()-2>=path.length() || path.charAt(key.length()-2)=='/')
+                return entry;
+            i=key.length()-3;
         }
 
         // Prefix Default
@@ -267,11 +252,12 @@ public class PathMap extends HashMap implements Externalizable
 
         // Extension search
         i=0;
+        final Trie<PathMap.MappedEntry<O>> suffix_map=_suffixMap;
         while ((i=path.indexOf('.',i+1))>0)
         {
-            entry=_suffixMap.getEntry(path,i+1,l-i-1);
+            entry=suffix_map.get(path,i+1,l-i-1);
             if (entry!=null)
-                return (Entry) entry.getValue();
+                return entry;
         }
 
         // Default
@@ -282,68 +268,68 @@ public class PathMap extends HashMap implements Externalizable
     /** Get all entries matched by the path.
      * Best match first.
      * @param path Path to match
-     * @return LazyList of Map.Entry instances key=pathSpec
+     * @return List of Map.Entry instances key=pathSpec
      */
-    public Object getLazyMatches(String path)
+    public List<? extends Map.Entry<String,O>> getMatches(String path)
     {
-        Map.Entry entry;
-        Object entries=null;
+        MappedEntry<O> entry;
+        List<MappedEntry<O>> entries=new ArrayList<>();
 
         if (path==null)
-            return LazyList.getList(entries);
-
-        int l=path.length();
+            return entries;
+        if (path.length()==0)
+            return _defaultSingletonList;
 
         // try exact match
-        entry=_exactMap.getEntry(path,0,l);
+        entry=_exactMap.get(path);
         if (entry!=null)
-            entries=LazyList.add(entries,entry.getValue());
+            entries.add(entry);
 
         // prefix search
-        int i=l-1;
-        while((i=path.lastIndexOf('/',i-1))>=0)
+        int l=path.length();
+        int i=l;
+        final Trie<PathMap.MappedEntry<O>> prefix_map=_prefixMap;
+        while(i>=0)
         {
-            entry=_prefixMap.getEntry(path,0,i);
-            if (entry!=null)
-                entries=LazyList.add(entries,entry.getValue());
+            entry=prefix_map.getBest(path,0,i);
+            if (entry==null)
+                break;
+            String key = entry.getKey();
+            if (key.length()-2>=path.length() || path.charAt(key.length()-2)=='/')
+                entries.add(entry);
+
+            i=key.length()-3;
         }
 
         // Prefix Default
         if (_prefixDefault!=null)
-            entries=LazyList.add(entries,_prefixDefault);
+            entries.add(_prefixDefault);
 
         // Extension search
         i=0;
+        final Trie<PathMap.MappedEntry<O>> suffix_map=_suffixMap;
         while ((i=path.indexOf('.',i+1))>0)
         {
-            entry=_suffixMap.getEntry(path,i+1,l-i-1);
+            entry=suffix_map.get(path,i+1,l-i-1);
             if (entry!=null)
-                entries=LazyList.add(entries,entry.getValue());
+                entries.add(entry);
         }
 
-        // Default
-        if (_default!=null)
+        // root match
+        if ("/".equals(path))
         {
-            // Optimization for just the default
-            if (entries==null)
-                return _defaultSingletonList;
-
-            entries=LazyList.add(entries,_default);
+            entry=_exactMap.get("");
+            if (entry!=null)
+                entries.add(entry);
         }
+            
+        // Default
+        if (_default!=null)
+            entries.add(_default);
 
         return entries;
     }
 
-    /* --------------------------------------------------------------- */
-    /** Get all entries matched by the path.
-     * Best match first.
-     * @param path Path to match
-     * @return List of Map.Entry instances key=pathSpec
-     */
-    public List getMatches(String path)
-    {
-        return LazyList.getList(getLazyMatches(path));
-    }
 
     /* --------------------------------------------------------------- */
     /** Return whether the path matches any entries in the PathMap,
@@ -353,13 +339,13 @@ public class PathMap extends HashMap implements Externalizable
      */
     public boolean containsMatch(String path)
     {
-    	Entry match = getMatch(path);
-    	return match!=null && !match.equals(_default);
+        MappedEntry<?> match = getMatch(path);
+        return match!=null && !match.equals(_default);
     }
 
     /* --------------------------------------------------------------- */
     @Override
-    public Object remove(Object pathSpec)
+    public O remove(Object pathSpec)
     {
         if (pathSpec!=null)
         {
@@ -367,11 +353,7 @@ public class PathMap extends HashMap implements Externalizable
             if (spec.equals("/*"))
                 _prefixDefault=null;
             else if (spec.endsWith("/*"))
-            {
                 _prefixMap.remove(spec.substring(0,spec.length()-2));
-                _exactMap.remove(spec.substring(0,spec.length()-1));
-                _exactMap.remove(spec.substring(0,spec.length()-2));
-            }
             else if (spec.startsWith("*."))
                 _suffixMap.remove(spec.substring(2));
             else if (spec.equals(URIUtil.SLASH))
@@ -390,10 +372,11 @@ public class PathMap extends HashMap implements Externalizable
     public void clear()
     {
         _exactMap.clear();
-        _prefixMap.clear();
-        _suffixMap.clear();
+        _prefixMap=new ArrayTernaryTrie<>(false);
+        _suffixMap=new ArrayTernaryTrie<>(false);
         _default=null;
         _defaultSingletonList=null;
+        _prefixDefault=null;
         super.clear();
     }
 
@@ -412,7 +395,7 @@ public class PathMap extends HashMap implements Externalizable
      * @return true if match.
      */
     public static boolean match(String pathSpec, String path, boolean noDefault)
-    throws IllegalArgumentException
+        throws IllegalArgumentException
     {
         if (pathSpec.length()==0)
             return "/".equals(path);
@@ -428,7 +411,7 @@ public class PathMap extends HashMap implements Externalizable
         }
         else if (c=='*')
             return path.regionMatches(path.length()-pathSpec.length()+1,
-                                      pathSpec,1,pathSpec.length()-1);
+                    pathSpec,1,pathSpec.length()-1);
         return false;
     }
 
@@ -468,7 +451,7 @@ public class PathMap extends HashMap implements Externalizable
         else if (c=='*')
         {
             if (path.regionMatches(path.length()-(pathSpec.length()-1),
-                                   pathSpec,1,pathSpec.length()-1))
+                    pathSpec,1,pathSpec.length()-1))
                 return path;
         }
         return null;
@@ -482,7 +465,7 @@ public class PathMap extends HashMap implements Externalizable
     {
         if ("".equals(pathSpec))
             return path; //servlet 3 spec sec 12.2 will be '/'
-        
+
         char c = pathSpec.charAt(0);
 
         if (c=='/')
@@ -515,8 +498,8 @@ public class PathMap extends HashMap implements Externalizable
      * @return base plus path with pathspec removed
      */
     public static String relativePath(String base,
-                                      String pathSpec,
-                                      String path )
+            String pathSpec,
+            String path )
     {
         String info=pathInfo(pathSpec,path);
         if (info==null)
@@ -540,30 +523,32 @@ public class PathMap extends HashMap implements Externalizable
     /* ------------------------------------------------------------ */
     /* ------------------------------------------------------------ */
     /* ------------------------------------------------------------ */
-    public static class Entry implements Map.Entry
+    public static class MappedEntry<O> implements Map.Entry<String,O>
     {
-        private final Object key;
-        private final Object value;
+        private final String key;
+        private final O value;
         private String mapped;
-        private transient String string;
 
-        Entry(Object key, Object value)
+        MappedEntry(String key, O value)
         {
             this.key=key;
             this.value=value;
         }
 
-        public Object getKey()
+        @Override
+        public String getKey()
         {
             return key;
         }
 
-        public Object getValue()
+        @Override
+        public O getValue()
         {
             return value;
         }
 
-        public Object setValue(Object o)
+        @Override
+        public O setValue(O o)
         {
             throw new UnsupportedOperationException();
         }
@@ -571,9 +556,7 @@ public class PathMap extends HashMap implements Externalizable
         @Override
         public String toString()
         {
-            if (string==null)
-                string=key+"="+value;
-            return string;
+            return key+"="+value;
         }
 
         public String getMapped()
diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/gzip/AbstractCompressedStream.java b/jetty-http/src/main/java/org/eclipse/jetty/http/gzip/AbstractCompressedStream.java
deleted file mode 100644
index 2a584c7..0000000
--- a/jetty-http/src/main/java/org/eclipse/jetty/http/gzip/AbstractCompressedStream.java
+++ /dev/null
@@ -1,388 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.http.gzip;
-
-import java.io.IOException;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.io.PrintWriter;
-import java.io.UnsupportedEncodingException;
-import java.util.zip.DeflaterOutputStream;
-
-import javax.servlet.ServletOutputStream;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jetty.util.ByteArrayOutputStream2;
-
-/* ------------------------------------------------------------ */
-/**
- * Skeletal implementation of a CompressedStream. This class adds compression features to a ServletOutputStream and takes care of setting response headers, etc.
- * Major work and configuration is done here. Subclasses using different kinds of compression only have to implement the abstract methods doCompress() and
- * setContentEncoding() using the desired compression and setting the appropriate Content-Encoding header string.
- */
-public abstract class AbstractCompressedStream extends ServletOutputStream 
-{
-    private final String _encoding;
-    protected final String _vary;
-    protected final CompressedResponseWrapper _wrapper;
-    protected final HttpServletResponse _response;
-    protected OutputStream _out;
-    protected ByteArrayOutputStream2 _bOut;
-    protected DeflaterOutputStream _compressedOutputStream;
-    protected boolean _closed;
-    protected boolean _doNotCompress;
-
-    /**
-     * Instantiates a new compressed stream.
-     * 
-     */
-    public AbstractCompressedStream(String encoding,HttpServletRequest request, CompressedResponseWrapper wrapper,String vary)
-            throws IOException
-    {
-        _encoding=encoding;
-        _wrapper = wrapper;
-        _response = (HttpServletResponse)wrapper.getResponse();
-        _vary=vary;
-        
-        if (_wrapper.getMinCompressSize()==0)
-            doCompress();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Reset buffer.
-     */
-    public void resetBuffer()
-    {
-        if (_response.isCommitted() || _compressedOutputStream!=null )
-            throw new IllegalStateException("Committed");
-        _closed = false;
-        _out = null;
-        _bOut = null;
-        _doNotCompress = false;
-    }
-
-    /* ------------------------------------------------------------ */
-    public void setBufferSize(int bufferSize)
-    {
-        if (_bOut!=null && _bOut.getBuf().length<bufferSize)
-        {
-            ByteArrayOutputStream2 b = new ByteArrayOutputStream2(bufferSize);
-            b.write(_bOut.getBuf(),0,_bOut.size());
-            _bOut=b;
-        }
-    }
-    
-    /* ------------------------------------------------------------ */
-    public void setContentLength()
-    {
-        if (_doNotCompress)
-        {
-            long length=_wrapper.getContentLength();
-            if (length>=0)
-            {
-                if (length < Integer.MAX_VALUE)
-                    _response.setContentLength((int)length);
-                else
-                    _response.setHeader("Content-Length",Long.toString(length));
-            }
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see java.io.OutputStream#flush()
-     */
-    @Override
-    public void flush() throws IOException
-    {
-        if (_out == null || _bOut != null)
-        {
-            long length=_wrapper.getContentLength();
-            if (length > 0 && length < _wrapper.getMinCompressSize())
-                doNotCompress(false);
-            else
-                doCompress();
-        }
-
-        _out.flush();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see java.io.OutputStream#close()
-     */
-    @Override
-    public void close() throws IOException
-    {
-        if (_closed)
-            return;
-
-        if (_wrapper.getRequest().getAttribute("javax.servlet.include.request_uri") != null)
-            flush();
-        else
-        {
-            if (_bOut != null)
-            {
-                long length=_wrapper.getContentLength();
-                if (length < 0)
-                {
-                    length = _bOut.getCount();
-                    _wrapper.setContentLength(length);
-                }
-                if (length < _wrapper.getMinCompressSize())
-                    doNotCompress(false);
-                else
-                    doCompress();
-            }
-            else if (_out == null)
-            {
-                // No output
-                doNotCompress(false);
-            }
-
-            if (_compressedOutputStream != null)
-                _compressedOutputStream.close();
-            else
-                _out.close();
-            _closed = true;
-        }
-    }
-
-    /**
-     * Finish.
-     * 
-     * @throws IOException
-     *             Signals that an I/O exception has occurred.
-     */
-    public void finish() throws IOException
-    {
-        if (!_closed)
-        {
-            if (_out == null || _bOut != null)
-            {
-                long length=_wrapper.getContentLength();
-                if (length >= 0 && length < _wrapper.getMinCompressSize())
-                    doNotCompress(false);
-                else
-                    doCompress();
-            }
-
-            if (_compressedOutputStream != null && !_closed)
-            {
-                _closed = true;
-                _compressedOutputStream.close();
-            }
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see java.io.OutputStream#write(int)
-     */
-    @Override
-    public void write(int b) throws IOException
-    {
-        checkOut(1);
-        _out.write(b);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see java.io.OutputStream#write(byte[])
-     */
-    @Override
-    public void write(byte b[]) throws IOException
-    {
-        checkOut(b.length);
-        _out.write(b);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see java.io.OutputStream#write(byte[], int, int)
-     */
-    @Override
-    public void write(byte b[], int off, int len) throws IOException
-    {
-        checkOut(len);
-        _out.write(b,off,len);
-    }
-    
-    /**
-     * Do compress.
-     *
-     * @throws IOException Signals that an I/O exception has occurred.
-     */
-    public void doCompress() throws IOException
-    {
-        if (_compressedOutputStream==null) 
-        {
-            if (_response.isCommitted())
-                throw new IllegalStateException();
-            
-            if (_encoding!=null)
-            {
-                setHeader("Content-Encoding", _encoding);            
-                if (_response.containsHeader("Content-Encoding"))
-                {
-                    addHeader("Vary",_vary);
-                    _out=_compressedOutputStream=createStream();
-                    if (_out!=null)
-                    {
-                        if (_bOut!=null)
-                        {
-                            _out.write(_bOut.getBuf(),0,_bOut.getCount());
-                            _bOut=null;
-                        }
-
-                        String etag=_wrapper.getETag();
-                        if (etag!=null)
-                            setHeader("ETag",etag.substring(0,etag.length()-1)+'-'+_encoding+'"');
-                        return;
-                    }
-                }
-            }
-            
-            doNotCompress(true); // Send vary as it could have been compressed if encoding was present
-        }
-    }
-
-    /**
-     * Do not compress.
-     * 
-     * @throws IOException
-     *             Signals that an I/O exception has occurred.
-     */
-    public void doNotCompress(boolean sendVary) throws IOException
-    {
-        if (_compressedOutputStream != null)
-            throw new IllegalStateException("Compressed output stream is already assigned.");
-        if (_out == null || _bOut != null)
-        {
-            if (sendVary)
-                addHeader("Vary",_vary);
-            if (_wrapper.getETag()!=null)
-                setHeader("ETag",_wrapper.getETag());
-                
-            _doNotCompress = true;
-
-            _out = _response.getOutputStream();
-            setContentLength();
-
-            if (_bOut != null)
-                _out.write(_bOut.getBuf(),0,_bOut.getCount());
-            _bOut = null;
-        }
-    }
-
-    /**
-     * Check out.
-     * 
-     * @param lengthToWrite
-     *            the length
-     * @throws IOException
-     *             Signals that an I/O exception has occurred.
-     */
-    private void checkOut(int lengthToWrite) throws IOException
-    {
-        if (_closed)
-            throw new IOException("CLOSED");
-
-        if (_out == null)
-        {            
-            // If this first write is larger than buffer size, then we are committing now
-            if (lengthToWrite>_wrapper.getBufferSize())
-            {
-                // if we know this is all the content and it is less than minimum, then do not compress, otherwise do compress
-                long length=_wrapper.getContentLength();
-                if (length>=0 && length<_wrapper.getMinCompressSize())
-                    doNotCompress(false);  // Not compressing by size, so no vary on request headers
-                else
-                    doCompress();
-            }
-            else
-            {
-                // start aggregating writes into a buffered output stream
-                _out = _bOut = new ByteArrayOutputStream2(_wrapper.getBufferSize());
-            }
-        }
-        // else are we aggregating writes?
-        else if (_bOut !=null)
-        {
-            // We are aggregating into the buffered output stream.  
-
-            // If this write fills the buffer, then we are committing
-            if (lengthToWrite>=(_bOut.getBuf().length - _bOut.getCount()))
-            {
-                // if we know this is all the content and it is less than minimum, then do not compress, otherwise do compress
-                long length=_wrapper.getContentLength();
-                if (length>=0 && length<_wrapper.getMinCompressSize())
-                    doNotCompress(false);  // Not compressing by size, so no vary on request headers
-                else
-                    doCompress();
-            }
-        }
-    }
-
-    /**
-     * @see org.eclipse.jetty.http.gzip.CompressedStream#getOutputStream()
-     */
-    public OutputStream getOutputStream()
-    {
-        return _out;
-    }
-
-    /**
-     * @see org.eclipse.jetty.http.gzip.CompressedStream#isClosed()
-     */
-    public boolean isClosed()
-    {
-        return _closed;
-    }
-    
-    /**
-     * Allows derived implementations to replace PrintWriter implementation.
-     */
-    protected PrintWriter newWriter(OutputStream out, String encoding) throws UnsupportedEncodingException
-    {
-        return encoding == null?new PrintWriter(out):new PrintWriter(new OutputStreamWriter(out,encoding));
-    }
-
-    protected void addHeader(String name,String value)
-    {
-        _response.addHeader(name, value);
-    }
-
-    protected void setHeader(String name,String value)
-    {
-        _response.setHeader(name, value);
-    }
-    
-    /**
-     * Create the stream fitting to the underlying compression type.
-     * 
-     * @throws IOException
-     *             Signals that an I/O exception has occurred.
-     */
-    protected abstract DeflaterOutputStream createStream() throws IOException;
-
-
-}
diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/gzip/CompressedResponseWrapper.java b/jetty-http/src/main/java/org/eclipse/jetty/http/gzip/CompressedResponseWrapper.java
deleted file mode 100644
index f81a3fc..0000000
--- a/jetty-http/src/main/java/org/eclipse/jetty/http/gzip/CompressedResponseWrapper.java
+++ /dev/null
@@ -1,487 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.http.gzip;
-
-import java.io.IOException;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.io.PrintWriter;
-import java.io.UnsupportedEncodingException;
-import java.util.Set;
-
-import javax.servlet.ServletOutputStream;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import javax.servlet.http.HttpServletResponseWrapper;
-
-import org.eclipse.jetty.util.StringUtil;
-
-/*------------------------------------------------------------ */
-/**
- */
-public abstract class CompressedResponseWrapper extends HttpServletResponseWrapper
-{
-    
-    public static final int DEFAULT_BUFFER_SIZE = 8192;
-    public static final int DEFAULT_MIN_COMPRESS_SIZE = 256;
-    
-    private Set<String> _mimeTypes;
-    private int _bufferSize=DEFAULT_BUFFER_SIZE;
-    private int _minCompressSize=DEFAULT_MIN_COMPRESS_SIZE;
-    protected HttpServletRequest _request;
-
-    private PrintWriter _writer;
-    private AbstractCompressedStream _compressedStream;
-    private String _etag;
-    private long _contentLength=-1;
-    private boolean _noCompression;
-
-    /* ------------------------------------------------------------ */
-    public CompressedResponseWrapper(HttpServletRequest request, HttpServletResponse response)
-    {
-        super(response);
-        _request = request;
-    }
-
-
-    /* ------------------------------------------------------------ */
-    public long getContentLength()
-    {
-        return _contentLength;
-    }
-
-    /* ------------------------------------------------------------ */
-    public int getBufferSize()
-    {
-        return _bufferSize;
-    }
-    
-    /* ------------------------------------------------------------ */
-    public int getMinCompressSize()
-    {
-        return _minCompressSize;
-    }
-    
-    /* ------------------------------------------------------------ */
-    public String getETag()
-    {
-        return _etag;
-    }
-
-    /* ------------------------------------------------------------ */
-    public HttpServletRequest getRequest()
-    {
-        return _request;
-    }
-    
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#setMimeTypes(java.util.Set)
-     */
-    public void setMimeTypes(Set<String> mimeTypes)
-    {
-        _mimeTypes = mimeTypes;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#setBufferSize(int)
-     */
-    @Override
-    public void setBufferSize(int bufferSize)
-    {
-        _bufferSize = bufferSize;
-        if (_compressedStream!=null)
-            _compressedStream.setBufferSize(bufferSize);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#setMinCompressSize(int)
-     */
-    public void setMinCompressSize(int minCompressSize)
-    {
-        _minCompressSize = minCompressSize;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#setContentType(java.lang.String)
-     */
-    @Override
-    public void setContentType(String ct)
-    {
-        super.setContentType(ct);
-    
-        if (!_noCompression)
-        {
-            if (ct!=null)
-            {
-                int colon=ct.indexOf(";");
-                if (colon>0)
-                    ct=ct.substring(0,colon);
-            }
-
-            if ((_compressedStream==null || _compressedStream.getOutputStream()==null) && 
-                    (_mimeTypes==null && ct!=null && ct.contains("gzip") ||
-                    _mimeTypes!=null && (ct==null||!_mimeTypes.contains(StringUtil.asciiToLowerCase(ct)))))
-            {
-                noCompression();
-            }
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#setStatus(int, java.lang.String)
-     */
-    @Override
-    public void setStatus(int sc, String sm)
-    {
-        super.setStatus(sc,sm);
-        if (sc<200 || sc==204 || sc==205 || sc>=300)
-            noCompression();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#setStatus(int)
-     */
-    @Override
-    public void setStatus(int sc)
-    {
-        super.setStatus(sc);
-        if (sc<200 || sc==204 || sc==205 || sc>=300)
-            noCompression();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#setContentLength(int)
-     */
-    @Override
-    public void setContentLength(int length)
-    {
-        if (_noCompression)
-            super.setContentLength(length);
-        else
-            setContentLength((long)length);
-    }
-    
-    /* ------------------------------------------------------------ */
-    protected void setContentLength(long length)
-    {
-        _contentLength=length;
-        if (_compressedStream!=null)
-            _compressedStream.setContentLength();
-        else if (_noCompression && _contentLength>=0)
-        {
-            HttpServletResponse response = (HttpServletResponse)getResponse();
-            if(_contentLength<Integer.MAX_VALUE)
-            {
-                response.setContentLength((int)_contentLength);
-            }
-            else
-            {
-                response.setHeader("Content-Length", Long.toString(_contentLength));
-            }
-        }
-    }
-    
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#addHeader(java.lang.String, java.lang.String)
-     */
-    @Override
-    public void addHeader(String name, String value)
-    {
-        if ("content-length".equalsIgnoreCase(name))
-        {
-            _contentLength=Long.parseLong(value);
-            if (_compressedStream!=null)
-                _compressedStream.setContentLength();
-        }
-        else if ("content-type".equalsIgnoreCase(name))
-        {   
-            setContentType(value);
-        }
-        else if ("content-encoding".equalsIgnoreCase(name))
-        {   
-            super.addHeader(name,value);
-            if (!isCommitted())
-            {
-                noCompression();
-            }
-        }
-        else if ("etag".equalsIgnoreCase(name))
-            _etag=value;
-        else
-            super.addHeader(name,value);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#flushBuffer()
-     */
-    @Override
-    public void flushBuffer() throws IOException
-    {
-        if (_writer!=null)
-            _writer.flush();
-        if (_compressedStream!=null)
-            _compressedStream.flush();
-        else
-            getResponse().flushBuffer();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#reset()
-     */
-    @Override
-    public void reset()
-    {
-        super.reset();
-        if (_compressedStream!=null)
-            _compressedStream.resetBuffer();
-        _writer=null;
-        _compressedStream=null;
-        _noCompression=false;
-        _contentLength=-1;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#resetBuffer()
-     */
-    @Override
-    public void resetBuffer()
-    {
-        super.resetBuffer();
-        if (_compressedStream!=null)
-            _compressedStream.resetBuffer();
-        _writer=null;
-        _compressedStream=null;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#sendError(int, java.lang.String)
-     */
-    @Override
-    public void sendError(int sc, String msg) throws IOException
-    {
-        resetBuffer();
-        super.sendError(sc,msg);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#sendError(int)
-     */
-    @Override
-    public void sendError(int sc) throws IOException
-    {
-        resetBuffer();
-        super.sendError(sc);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#sendRedirect(java.lang.String)
-     */
-    @Override
-    public void sendRedirect(String location) throws IOException
-    {
-        resetBuffer();
-        super.sendRedirect(location);
-    }
-    
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#noCompression()
-     */
-    public void noCompression()
-    {
-        if (!_noCompression)
-            setDeferredHeaders();
-        _noCompression=true;
-        if (_compressedStream!=null)
-        {
-            try
-            {
-                _compressedStream.doNotCompress(false);
-            }
-            catch (IOException e)
-            {
-                throw new IllegalStateException(e);
-            }
-        }
-    }
-    
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#finish()
-     */
-    public void finish() throws IOException
-    {
-        if (_writer!=null && !_compressedStream.isClosed())
-            _writer.flush();
-        if (_compressedStream!=null)
-            _compressedStream.finish();
-        else 
-            setDeferredHeaders();
-    }
-
-    /* ------------------------------------------------------------ */
-    private void setDeferredHeaders()
-    {
-        if (!isCommitted())
-        {
-            if (_contentLength>=0)
-            {
-                if (_contentLength < Integer.MAX_VALUE)
-                    super.setContentLength((int)_contentLength);
-                else
-                    super.setHeader("Content-Length",Long.toString(_contentLength));
-            }
-            if(_etag!=null)
-                super.setHeader("ETag",_etag);
-        }
-    }
-    
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#setHeader(java.lang.String, java.lang.String)
-     */
-    @Override
-    public void setHeader(String name, String value)
-    {
-        if (_noCompression)
-            super.setHeader(name,value);
-        else if ("content-length".equalsIgnoreCase(name))
-        {
-            setContentLength(Long.parseLong(value));
-        }
-        else if ("content-type".equalsIgnoreCase(name))
-        {   
-            setContentType(value);
-        }
-        else if ("content-encoding".equalsIgnoreCase(name))
-        {   
-            super.setHeader(name,value);
-            if (!isCommitted())
-            {
-                noCompression();
-            }
-        }
-        else if ("etag".equalsIgnoreCase(name))
-            _etag=value;
-        else
-            super.setHeader(name,value);
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public boolean containsHeader(String name)
-    {
-        if (!_noCompression && "etag".equalsIgnoreCase(name) && _etag!=null)
-            return true;
-        return super.containsHeader(name);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#getOutputStream()
-     */
-    @Override
-    public ServletOutputStream getOutputStream() throws IOException
-    {
-        if (_compressedStream==null)
-        {
-            if (getResponse().isCommitted() || _noCompression)
-                return getResponse().getOutputStream();
-            
-            _compressedStream=newCompressedStream(_request,(HttpServletResponse)getResponse());
-        }
-        else if (_writer!=null)
-            throw new IllegalStateException("getWriter() called");
-        
-        return _compressedStream;   
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#getWriter()
-     */
-    @Override
-    public PrintWriter getWriter() throws IOException
-    {
-        if (_writer==null)
-        { 
-            if (_compressedStream!=null)
-                throw new IllegalStateException("getOutputStream() called");
-            
-            if (getResponse().isCommitted() || _noCompression)
-                return getResponse().getWriter();
-            
-            _compressedStream=newCompressedStream(_request,(HttpServletResponse)getResponse());
-            _writer=newWriter(_compressedStream,getCharacterEncoding());
-        }
-        return _writer;   
-    }
-    
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#setIntHeader(java.lang.String, int)
-     */
-    @Override
-    public void setIntHeader(String name, int value)
-    {
-        if ("content-length".equalsIgnoreCase(name))
-        {
-            _contentLength=value;
-            if (_compressedStream!=null)
-                _compressedStream.setContentLength();
-        }
-        else
-            super.setIntHeader(name,value);
-    }
-    
-    /* ------------------------------------------------------------ */
-    /**
-     * Allows derived implementations to replace PrintWriter implementation.
-     *
-     * @param out the out
-     * @param encoding the encoding
-     * @return the prints the writer
-     * @throws UnsupportedEncodingException the unsupported encoding exception
-     */
-    protected PrintWriter newWriter(OutputStream out,String encoding) throws UnsupportedEncodingException
-    {
-        return encoding==null?new PrintWriter(out):new PrintWriter(new OutputStreamWriter(out,encoding));
-    }
-    
-    /* ------------------------------------------------------------ */
-    /**
-     *@return the underlying CompressedStream implementation 
-     */
-    protected abstract AbstractCompressedStream newCompressedStream(HttpServletRequest _request, HttpServletResponse response) throws IOException;
-
-}
diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/package-info.java b/jetty-http/src/main/java/org/eclipse/jetty/http/package-info.java
new file mode 100644
index 0000000..7ca67d8
--- /dev/null
+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Http : Tools for Http processing
+ */
+package org.eclipse.jetty.http;
+
diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/ssl/SslContextFactory.java b/jetty-http/src/main/java/org/eclipse/jetty/http/ssl/SslContextFactory.java
deleted file mode 100644
index 03e7f40..0000000
--- a/jetty-http/src/main/java/org/eclipse/jetty/http/ssl/SslContextFactory.java
+++ /dev/null
@@ -1,41 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.http.ssl;
-
-/* ------------------------------------------------------------ */
-/**
- * @deprecated Use org.eclipse.jetty.util.ssl.SslContextFactory
- */
-public class SslContextFactory extends org.eclipse.jetty.util.ssl.SslContextFactory
-{
-    public SslContextFactory()
-    {
-        super();
-    }
-
-    public SslContextFactory(boolean trustAll)
-    {
-        super(trustAll);
-    }
-
-    public SslContextFactory(String keyStorePath)
-    {
-        super(keyStorePath);
-    }
-}
diff --git a/jetty-http/src/main/resources/org/eclipse/jetty/http/mime.properties b/jetty-http/src/main/resources/org/eclipse/jetty/http/mime.properties
index 8425ac1..654454c 100644
--- a/jetty-http/src/main/resources/org/eclipse/jetty/http/mime.properties
+++ b/jetty-http/src/main/resources/org/eclipse/jetty/http/mime.properties
@@ -1,181 +1,184 @@
-ai	= application/postscript
-aif	= audio/x-aiff
-aifc	= audio/x-aiff
-aiff	= audio/x-aiff
-apk = application/vnd.android.package-archive
-asc	= text/plain
-asf     = video/x.ms.asf
-asx     = video/x.ms.asx
-au	= audio/basic
-avi	= video/x-msvideo
-bcpio	= application/x-bcpio
-bin	= application/octet-stream
-cab     = application/x-cabinet
-cdf	= application/x-netcdf
-class	= application/java-vm
-cpio	= application/x-cpio
-cpt	= application/mac-compactpro
-crt	= application/x-x509-ca-cert
-csh	= application/x-csh
-css	= text/css
-csv     = text/comma-separated-values
-dcr	= application/x-director
-dir	= application/x-director
-dll     = application/x-msdownload
-dms	= application/octet-stream
-doc	= application/msword
-dtd     = application/xml-dtd
-dvi	= application/x-dvi
-dxr	= application/x-director
-eps	= application/postscript
-etx	= text/x-setext
-exe	= application/octet-stream
-ez	= application/andrew-inset
-gif	= image/gif
-gtar	= application/x-gtar
-gz	= application/gzip
-gzip	= application/gzip
-hdf	= application/x-hdf
-hqx	= application/mac-binhex40
-htc = text/x-component
-htm	= text/html
-html	= text/html
-ice	= x-conference/x-cooltalk
-ico	= image/x-icon
-ief	= image/ief
-iges	= model/iges
-igs	= model/iges
-jad     = text/vnd.sun.j2me.app-descriptor
-jar     = application/java-archive
-java	= text/plain
-jnlp	= application/x-java-jnlp-file
-jpe	= image/jpeg
-jpeg	= image/jpeg
-jpg	= image/jpeg
-js	= application/x-javascript
-jsp	= text/html
-kar	= audio/midi
-latex	= application/x-latex
-lha	= application/octet-stream
-lzh	= application/octet-stream
-man	= application/x-troff-man
-mathml	= application/mathml+xml
-me	= application/x-troff-me
-mesh	= model/mesh
-mid	= audio/midi
-midi	= audio/midi
-mif	= application/vnd.mif
-mol     = chemical/x-mdl-molfile
-mov	= video/quicktime
-movie	= video/x-sgi-movie
-mp2	= audio/mpeg
-mp3	= audio/mpeg
-mpe	= video/mpeg
-mpeg	= video/mpeg
-mpg	= video/mpeg
-mpga	= audio/mpeg
-ms	= application/x-troff-ms
-msh	= model/mesh
-msi	= application/octet-stream
-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
-ogg	= application/ogg
-otc     = application/vnd.oasis.opendocument.chart-template
-otf     = application/vnd.oasis.opendocument.formula-template
-otg     = application/vnd.oasis.opendocument.graphics-template
-oth     = application/vnd.oasis.opendocument.text-web
-oti     = application/vnd.oasis.opendocument.image-template
-otp     = application/vnd.oasis.opendocument.presentation-template
-ots     = application/vnd.oasis.opendocument.spreadsheet-template
-ott     = application/vnd.oasis.opendocument.text-template
-pbm	= image/x-portable-bitmap
-pdb	= chemical/x-pdb
-pdf	= application/pdf
-pgm	= image/x-portable-graymap
-pgn	= application/x-chess-pgn
-png	= image/png
-pnm	= image/x-portable-anymap
-ppm	= image/x-portable-pixmap
-pps     = application/vnd.ms-powerpoint
-ppt	= application/vnd.ms-powerpoint
-ps	= application/postscript
-qt	= video/quicktime
-ra	= audio/x-pn-realaudio
-ram	= audio/x-pn-realaudio
-ras	= image/x-cmu-raster
-rdf	= application/rdf+xml
-rgb	= image/x-rgb
-rm	= audio/x-pn-realaudio
-roff	= application/x-troff
-rpm	= application/x-rpm
-rtf	= application/rtf
-rtx	= text/richtext
-rv      = video/vnd.rn-realvideo
-ser     = application/java-serialized-object
-sgm	= text/sgml
-sgml	= text/sgml
-sh	= application/x-sh
-shar	= application/x-shar
-silo	= model/mesh
-sit	= application/x-stuffit
-skd	= application/x-koan
-skm	= application/x-koan
-skp	= application/x-koan
-skt	= application/x-koan
-smi	= application/smil
-smil	= application/smil
-snd	= audio/basic
-spl	= application/x-futuresplash
-src	= application/x-wais-source
-sv4cpio	= application/x-sv4cpio
-sv4crc	= application/x-sv4crc
-svg	= image/svg+xml
-swf	= application/x-shockwave-flash
-t	= application/x-troff
-tar	= application/x-tar
-tar.gz	= application/x-gtar
-tcl	= application/x-tcl
-tex	= application/x-tex
-texi	= application/x-texinfo
-texinfo	= application/x-texinfo
-tgz	= application/x-gtar
-tif	= image/tiff
-tiff	= image/tiff
-tr	= application/x-troff
-tsv	= text/tab-separated-values
-txt	= text/plain
-ustar	= application/x-ustar
-vcd	= application/x-cdlink
-vrml	= model/vrml
-vxml	= application/voicexml+xml
-wav	= audio/x-wav
-wbmp	= image/vnd.wap.wbmp
-wml	= text/vnd.wap.wml
-wmlc	= application/vnd.wap.wmlc
-wmls	= text/vnd.wap.wmlscript
-wmlsc	= application/vnd.wap.wmlscriptc
-wrl	= model/vrml
-wtls-ca-certificate	= application/vnd.wap.wtls-ca-certificate
-xbm	= image/x-xbitmap
-xht	= application/xhtml+xml
-xhtml	= application/xhtml+xml
-xls	= application/vnd.ms-excel
-xml	= application/xml
-xpm	= image/x-xpixmap
-xsd	= application/xml
-xsl	= application/xml
-xslt	= application/xslt+xml
-xul	= application/vnd.mozilla.xul+xml
-xwd	= image/x-xwindowdump
-xyz	= chemical/x-xyz
-z	= application/compress
-zip	= application/zip
+ai=application/postscript
+aif=audio/x-aiff
+aifc=audio/x-aiff
+aiff=audio/x-aiff
+apk=application/vnd.android.package-archive
+asc=text/plain
+asf=video/x.ms.asf
+asx=video/x.ms.asx
+au=audio/basic
+avi=video/x-msvideo
+bcpio=application/x-bcpio
+bin=application/octet-stream
+cab=application/x-cabinet
+cdf=application/x-netcdf
+class=application/java-vm
+cpio=application/x-cpio
+cpt=application/mac-compactpro
+crt=application/x-x509-ca-cert
+csh=application/x-csh
+css=text/css
+csv=text/comma-separated-values
+dcr=application/x-director
+dir=application/x-director
+dll=application/x-msdownload
+dms=application/octet-stream
+doc=application/msword
+dtd=application/xml-dtd
+dvi=application/x-dvi
+dxr=application/x-director
+eps=application/postscript
+etx=text/x-setext
+exe=application/octet-stream
+ez=application/andrew-inset
+gif=image/gif
+gtar=application/x-gtar
+gz=application/gzip
+gzip=application/gzip
+hdf=application/x-hdf
+hqx=application/mac-binhex40
+htc=text/x-component
+htm=text/html
+html=text/html
+ice=x-conference/x-cooltalk
+ico=image/x-icon
+ief=image/ief
+iges=model/iges
+igs=model/iges
+jad=text/vnd.sun.j2me.app-descriptor
+jar=application/java-archive
+java=text/plain
+jnlp=application/x-java-jnlp-file
+jpe=image/jpeg
+jpeg=image/jpeg
+jpg=image/jpeg
+js=application/javascript
+json=application/json
+jsp=text/html
+kar=audio/midi
+latex=application/x-latex
+lha=application/octet-stream
+lzh=application/octet-stream
+man=application/x-troff-man
+mathml=application/mathml+xml
+me=application/x-troff-me
+mesh=model/mesh
+mid=audio/midi
+midi=audio/midi
+mif=application/vnd.mif
+mol=chemical/x-mdl-molfile
+mov=video/quicktime
+movie=video/x-sgi-movie
+mp2=audio/mpeg
+mp3=audio/mpeg
+mpe=video/mpeg
+mpeg=video/mpeg
+mpg=video/mpeg
+mpga=audio/mpeg
+ms=application/x-troff-ms
+msh=model/mesh
+msi=application/octet-stream
+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
+ogg=application/ogg
+otc=application/vnd.oasis.opendocument.chart-template
+otf=application/vnd.oasis.opendocument.formula-template
+otg=application/vnd.oasis.opendocument.graphics-template
+oth=application/vnd.oasis.opendocument.text-web
+oti=application/vnd.oasis.opendocument.image-template
+otp=application/vnd.oasis.opendocument.presentation-template
+ots=application/vnd.oasis.opendocument.spreadsheet-template
+ott=application/vnd.oasis.opendocument.text-template
+pbm=image/x-portable-bitmap
+pdb=chemical/x-pdb
+pdf=application/pdf
+pgm=image/x-portable-graymap
+pgn=application/x-chess-pgn
+png=image/png
+pnm=image/x-portable-anymap
+ppm=image/x-portable-pixmap
+pps=application/vnd.ms-powerpoint
+ppt=application/vnd.ms-powerpoint
+ps=application/postscript
+qml=text/x-qml
+qt=video/quicktime
+ra=audio/x-pn-realaudio
+ram=audio/x-pn-realaudio
+ras=image/x-cmu-raster
+rdf=application/rdf+xml
+rgb=image/x-rgb
+rm=audio/x-pn-realaudio
+roff=application/x-troff
+rpm=application/x-rpm
+rtf=application/rtf
+rtx=text/richtext
+rv=video/vnd.rn-realvideo
+ser=application/java-serialized-object
+sgm=text/sgml
+sgml=text/sgml
+sh=application/x-sh
+shar=application/x-shar
+silo=model/mesh
+sit=application/x-stuffit
+skd=application/x-koan
+skm=application/x-koan
+skp=application/x-koan
+skt=application/x-koan
+smi=application/smil
+smil=application/smil
+snd=audio/basic
+spl=application/x-futuresplash
+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=application/x-troff
+tar=application/x-tar
+tar.gz=application/x-gtar
+tcl=application/x-tcl
+tex=application/x-tex
+texi=application/x-texinfo
+texinfo=application/x-texinfo
+tgz=application/x-gtar
+tif=image/tiff
+tiff=image/tiff
+tr=application/x-troff
+tsv=text/tab-separated-values
+txt=text/plain
+ustar=application/x-ustar
+vcd=application/x-cdlink
+vrml=model/vrml
+vxml=application/voicexml+xml
+wav=audio/x-wav
+wbmp=image/vnd.wap.wbmp
+wml=text/vnd.wap.wml
+wmlc=application/vnd.wap.wmlc
+wmls=text/vnd.wap.wmlscript
+wmlsc=application/vnd.wap.wmlscriptc
+wrl=model/vrml
+wtls-ca-certificate=application/vnd.wap.wtls-ca-certificate
+xbm=image/x-xbitmap
+xht=application/xhtml+xml
+xhtml=application/xhtml+xml
+xls=application/vnd.ms-excel
+xml=application/xml
+xpm=image/x-xpixmap
+xsd=application/xml
+xsl=application/xml
+xslt=application/xslt+xml
+xul=application/vnd.mozilla.xul+xml
+xwd=image/x-xwindowdump
+xyz=chemical/x-xyz
+z=application/compress
+zip=application/zip
diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpFieldsTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpFieldsTest.java
index ebb4d86..8f88000 100644
--- a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpFieldsTest.java
+++ b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpFieldsTest.java
@@ -18,20 +18,18 @@
 
 package org.eclipse.jetty.http;
 
+import static org.hamcrest.Matchers.containsString;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
 
+import java.nio.ByteBuffer;
 import java.util.Enumeration;
-import java.util.HashSet;
-import java.util.Locale;
-import java.util.Set;
-
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.BufferCache.CachedBuffer;
-import org.eclipse.jetty.io.ByteArrayBuffer;
-import org.eclipse.jetty.io.View;
+import org.eclipse.jetty.util.BufferUtil;
+import org.hamcrest.Matchers;
+import org.junit.Assert;
 import org.junit.Test;
 
 /**
@@ -44,15 +42,15 @@ public class HttpFieldsTest
     {
         HttpFields header = new HttpFields();
 
-        header.put("name0", "value0");
+        header.put("name0", "value:0");
         header.put("name1", "value1");
 
-        assertEquals("value0",header.getStringField("name0"));
+        assertEquals("value:0",header.getStringField("name0"));
         assertEquals("value1",header.getStringField("name1"));
         assertNull(header.getStringField("name2"));
 
         int matches=0;
-        Enumeration e = header.getFieldNames();
+        Enumeration<String> e = header.getFieldNames();
         while (e.hasMoreElements())
         {
             Object o=e.nextElement();
@@ -65,24 +63,45 @@ public class HttpFieldsTest
 
         e = header.getValues("name0");
         assertEquals(true, e.hasMoreElements());
-        assertEquals(e.nextElement(), "value0");
+        assertEquals(e.nextElement(), "value:0");
         assertEquals(false, e.hasMoreElements());
     }
-    
+
+    @Test
+    public void testPutTo() throws Exception
+    {
+        HttpFields header = new HttpFields();
+
+        header.put("name0", "value0");
+        header.put("name1", "value:A");
+        header.add("name1", "value:B");
+        header.add("name2", "");
+
+        ByteBuffer buffer=BufferUtil.allocate(1024);
+        BufferUtil.flipToFill(buffer);
+        HttpGenerator.putTo(header,buffer);
+        BufferUtil.flipToFlush(buffer,0);
+        String result=BufferUtil.toString(buffer);
+
+        assertThat(result,Matchers.containsString("name0: value0"));
+        assertThat(result,Matchers.containsString("name1: value:A"));
+        assertThat(result,Matchers.containsString("name1: value:B"));
+    }
+
     @Test
     public void testGet() throws Exception
     {
         HttpFields header = new HttpFields();
 
         header.put("name0", "value0");
-        header.put(new ByteArrayBuffer("name1"), new ByteArrayBuffer("value1"));
+        header.put("name1", "value1");
 
         assertEquals("value0",header.getStringField("name0"));
         assertEquals("value0",header.getStringField("Name0"));
         assertEquals("value1",header.getStringField("name1"));
         assertEquals("value1",header.getStringField("Name1"));
     }
-    
+
     @Test
     public void testCRLF() throws Exception
     {
@@ -92,11 +111,14 @@ public class HttpFieldsTest
         header.put("name\r\n1", "value1");
         header.put("name:2", "value:\r\n2");
 
-        ByteArrayBuffer buffer = new ByteArrayBuffer(1024);
-        header.putTo(buffer);
-        assertTrue(buffer.toString().contains("name0: value0"));
-        assertTrue(buffer.toString().contains("name1: value1"));
-        assertTrue(buffer.toString().contains("name2: value:2"));
+        ByteBuffer buffer = BufferUtil.allocate(1024);
+        BufferUtil.flipToFill(buffer);
+        HttpGenerator.putTo(header,buffer);
+        BufferUtil.flipToFlush(buffer,0);
+        String out = BufferUtil.toString(buffer);
+        assertThat(out,containsString("name0: value  0"));
+        assertThat(out,containsString("name??1: value1"));
+        assertThat(out,containsString("name?2: value:  2"));
     }
 
     @Test
@@ -104,20 +126,19 @@ public class HttpFieldsTest
     {
         HttpFields header = new HttpFields();
 
-        header.put("Connection", "keep-alive");
-        assertEquals(HttpHeaderValues.KEEP_ALIVE, header.getStringField(HttpHeaders.CONNECTION));
+        header.put("Connection", "Keep-Alive");
+        header.put("tRansfer-EncOding", "CHUNKED");
+        header.put("CONTENT-ENCODING", "gZIP");
 
-        int matches=0;
-        Enumeration e = header.getFieldNames();
-        while (e.hasMoreElements())
-        {
-            Object o=e.nextElement();
-            if (o==HttpHeaders.CONTENT_TYPE)
-                matches++;
-            if (o==HttpHeaders.CONNECTION)
-                matches++;
-        }
-        assertEquals(1, matches);
+        ByteBuffer buffer = BufferUtil.allocate(1024);
+        BufferUtil.flipToFill(buffer);
+        HttpGenerator.putTo(header,buffer);
+        BufferUtil.flipToFlush(buffer,0);
+        String out = BufferUtil.toString(buffer).toLowerCase();
+
+        Assert.assertThat(out,Matchers.containsString((HttpHeader.CONNECTION+": "+HttpHeaderValue.KEEP_ALIVE).toLowerCase()));
+        Assert.assertThat(out,Matchers.containsString((HttpHeader.TRANSFER_ENCODING+": "+HttpHeaderValue.CHUNKED).toLowerCase()));
+        Assert.assertThat(out,Matchers.containsString((HttpHeader.CONTENT_ENCODING+": "+HttpHeaderValue.GZIP).toLowerCase()));
     }
 
     @Test
@@ -154,6 +175,7 @@ public class HttpFieldsTest
         }
         assertEquals(3, matches);
 
+
         e = header.getValues("name1");
         assertEquals(true, e.hasMoreElements());
         assertEquals(e.nextElement(), "value1");
@@ -181,7 +203,7 @@ public class HttpFieldsTest
         assertNull(header.getStringField("name3"));
 
         int matches=0;
-        Enumeration e = header.getFieldNames();
+        Enumeration<String> e = header.getFieldNames();
         while (e.hasMoreElements())
         {
             Object o=e.nextElement();
@@ -219,7 +241,7 @@ public class HttpFieldsTest
         assertNull(fields.getStringField("name3"));
 
         int matches=0;
-        Enumeration e = fields.getFieldNames();
+        Enumeration<String> e = fields.getFieldNames();
         while (e.hasMoreElements())
         {
             Object o=e.nextElement();
@@ -240,227 +262,46 @@ public class HttpFieldsTest
         assertEquals(false, e.hasMoreElements());
     }
 
+
     @Test
-    public void testReuse() throws Exception
+    public void testGetValues() throws Exception
     {
-        HttpFields header = new HttpFields();
-        Buffer n1=new ByteArrayBuffer("name1");
-        Buffer va=new ByteArrayBuffer("value1");
-        Buffer vb=new ByteArrayBuffer(10);
-        vb.put((byte)'v');
-        vb.put((byte)'a');
-        vb.put((byte)'l');
-        vb.put((byte)'u');
-        vb.put((byte)'e');
-        vb.put((byte)'1');
-
-        header.put("name0", "value0");
-        header.put(n1,va);
-        header.put("name2", "value2");
-
-        assertEquals("value0",header.getStringField("name0"));
-        assertEquals("value1",header.getStringField("name1"));
-        assertEquals("value2",header.getStringField("name2"));
-        assertNull(header.getStringField("name3"));
+        HttpFields fields = new HttpFields();
 
-        header.remove(n1);
-        assertNull(header.getStringField("name1"));
-        header.put(n1,vb);
-        assertEquals("value1",header.getStringField("name1"));
+        fields.put("name0", "value0A,value0B");
+        fields.add("name0", "value0C,value0D");
+        fields.put("name1", "value1A, \"value\t, 1B\" ");
+        fields.add("name1", "\"value1C\",\tvalue1D");
 
-        int matches=0;
-        Enumeration e = header.getFieldNames();
-        while (e.hasMoreElements())
-        {
-            Object o=e.nextElement();
-            if ("name0".equals(o))
-                matches++;
-            if ("name1".equals(o))
-                matches++;
-            if ("name2".equals(o))
-                matches++;
-        }
-        assertEquals(3, matches);
-
-        e = header.getValues("name1");
+        Enumeration<String> e = fields.getValues("name0");
         assertEquals(true, e.hasMoreElements());
-        assertEquals(e.nextElement(), "value1");
+        assertEquals(e.nextElement(), "value0A,value0B");
+        assertEquals(true, e.hasMoreElements());
+        assertEquals(e.nextElement(), "value0C,value0D");
         assertEquals(false, e.hasMoreElements());
-    }
-
-    @Test
-    public void testCase() throws Exception
-    {
-        HttpFields fields= new HttpFields();
-        Set s;
-        //         0123456789012345678901234567890
-        byte[] b ="Message-IDmessage-idvalueVALUE".getBytes();
-        ByteArrayBuffer buf= new ByteArrayBuffer(512);
-        buf.put(b);
-
-        View headUC= new View.CaseInsensitive(buf);
-        View headLC= new View.CaseInsensitive(buf);
-        View valUC = new View(buf);
-        View valLC = new View(buf);
-        headUC.update(0,10);
-        headLC.update(10,20);
-        valUC.update(20,25);
-        valLC.update(25,30);
-
-        fields.add("header","value");
-        fields.add(headUC,valLC);
-        fields.add("other","data");
-        s=enum2set(fields.getFieldNames());
-        assertEquals(3,s.size());
-        assertTrue(s.contains("message-id"));
-        assertEquals("value",fields.getStringField("message-id").toLowerCase(Locale.ENGLISH));
-        assertEquals("value",fields.getStringField("Message-ID").toLowerCase(Locale.ENGLISH));
-
-        fields.clear();
-
-        fields.add("header","value");
-        fields.add(headLC,valLC);
-        fields.add("other","data");
-        s=enum2set(fields.getFieldNames());
-        assertEquals(3,s.size());
-        assertTrue(s.contains("message-id"));
-        assertEquals("value",fields.getStringField("Message-ID").toLowerCase(Locale.ENGLISH));
-        assertEquals("value",fields.getStringField("message-id").toLowerCase(Locale.ENGLISH));
-
-        fields.clear();
-
-        fields.add("header","value");
-        fields.add(headUC,valUC);
-        fields.add("other","data");
-        s=enum2set(fields.getFieldNames());
-        assertEquals(3,s.size());
-        assertTrue(s.contains("message-id"));
-        assertEquals("value",fields.getStringField("message-id").toLowerCase(Locale.ENGLISH));
-        assertEquals("value",fields.getStringField("Message-ID").toLowerCase(Locale.ENGLISH));
-
-        fields.clear();
-
-        fields.add("header","value");
-        fields.add(headLC,valUC);
-        fields.add("other","data");
-        s=enum2set(fields.getFieldNames());
-        assertEquals(3,s.size());
-        assertTrue(s.contains("message-id"));
-        assertEquals("value",fields.getStringField("Message-ID").toLowerCase(Locale.ENGLISH));
-        assertEquals("value",fields.getStringField("message-id").toLowerCase(Locale.ENGLISH));
-    }
-
-    @Test
-    public void testHttpHeaderValues() throws Exception
-    {
-        assertTrue(((CachedBuffer)HttpHeaderValues.CACHE.lookup("unknown value")).getOrdinal()<0);
-        assertTrue(((CachedBuffer)HttpHeaderValues.CACHE.lookup("close")).getOrdinal()>=0);
-    }
 
-    @Test
-    public void testSetCookie() throws Exception
-    {
-        HttpFields fields = new HttpFields();
-        fields.addSetCookie("minimal","value",null,null,-1,null,false,false,-1);
-        assertEquals("minimal=value",fields.getStringField("Set-Cookie"));
-
-        fields.clear();
-        //test cookies with same name, domain and path, only 1 allowed
-        fields.addSetCookie("everything","wrong","domain","path",0,"to be replaced",true,true,0);
-        fields.addSetCookie("everything","value","domain","path",0,"comment",true,true,0);
-        assertEquals("everything=value;Comment=comment;Path=path;Domain=domain;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Secure;HttpOnly",fields.getStringField("Set-Cookie"));
-        Enumeration<String> e =fields.getValues("Set-Cookie");
-        assertTrue(e.hasMoreElements());
-        assertEquals("everything=value;Comment=comment;Path=path;Domain=domain;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Secure;HttpOnly",e.nextElement());
-        assertFalse(e.hasMoreElements());
-        assertEquals("Thu, 01 Jan 1970 00:00:00 GMT",fields.getStringField("Expires")); 
-        assertFalse(e.hasMoreElements());
-        
-        //test cookies with same name, different domain
-        fields.clear();
-        fields.addSetCookie("everything","other","domain1","path",0,"blah",true,true,0);
-        fields.addSetCookie("everything","value","domain2","path",0,"comment",true,true,0);
-        e =fields.getValues("Set-Cookie");
-        assertTrue(e.hasMoreElements());
-        assertEquals("everything=other;Comment=blah;Path=path;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Secure;HttpOnly",e.nextElement());
-        assertTrue(e.hasMoreElements());
-        assertEquals("everything=value;Comment=comment;Path=path;Domain=domain2;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Secure;HttpOnly",e.nextElement());
-        assertFalse(e.hasMoreElements());
-        
-        //test cookies with same name, same path, one with domain, one without
-        fields.clear();
-        fields.addSetCookie("everything","other","domain1","path",0,"blah",true,true,0);
-        fields.addSetCookie("everything","value","","path",0,"comment",true,true,0);
-        e =fields.getValues("Set-Cookie");
-        assertTrue(e.hasMoreElements());
-        assertEquals("everything=other;Comment=blah;Path=path;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Secure;HttpOnly",e.nextElement());
-        assertTrue(e.hasMoreElements());
-        assertEquals("everything=value;Comment=comment;Path=path;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Secure;HttpOnly",e.nextElement());
-        assertFalse(e.hasMoreElements());
-        
-        
-        //test cookies with same name, different path
-        fields.clear();
-        fields.addSetCookie("everything","other","domain1","path1",0,"blah",true,true,0);
-        fields.addSetCookie("everything","value","domain1","path2",0,"comment",true,true,0);
-        e =fields.getValues("Set-Cookie");
-        assertTrue(e.hasMoreElements());
-        assertEquals("everything=other;Comment=blah;Path=path1;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Secure;HttpOnly",e.nextElement());
-        assertTrue(e.hasMoreElements());
-        assertEquals("everything=value;Comment=comment;Path=path2;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Secure;HttpOnly",e.nextElement());
-        assertFalse(e.hasMoreElements());
-        
-        //test cookies with same name, same domain, one with path, one without
-        fields.clear();
-        fields.addSetCookie("everything","other","domain1","path1",0,"blah",true,true,0);
-        fields.addSetCookie("everything","value","domain1","",0,"comment",true,true,0);
-        e =fields.getValues("Set-Cookie");
-        assertTrue(e.hasMoreElements());
-        assertEquals("everything=other;Comment=blah;Path=path1;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Secure;HttpOnly",e.nextElement());
-        assertTrue(e.hasMoreElements());
-        assertEquals("everything=value;Comment=comment;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Secure;HttpOnly",e.nextElement());
-        assertFalse(e.hasMoreElements());
-        
-        //test cookies same name only, no path, no domain
-        fields.clear();
-        fields.addSetCookie("everything","other","","",0,"blah",true,true,0);
-        fields.addSetCookie("everything","value","","",0,"comment",true,true,0);
-        e =fields.getValues("Set-Cookie");
-        assertTrue(e.hasMoreElements());
-        assertEquals("everything=value;Comment=comment;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Secure;HttpOnly",e.nextElement());
-        assertFalse(e.hasMoreElements());
+        e = fields.getValues("name0",",");
+        assertEquals(true, e.hasMoreElements());
+        assertEquals(e.nextElement(), "value0A");
+        assertEquals(true, e.hasMoreElements());
+        assertEquals(e.nextElement(), "value0B");
+        assertEquals(true, e.hasMoreElements());
+        assertEquals(e.nextElement(), "value0C");
+        assertEquals(true, e.hasMoreElements());
+        assertEquals(e.nextElement(), "value0D");
+        assertEquals(false, e.hasMoreElements());
         
-        fields.clear();
-        fields.addSetCookie("ev erything","va lue","do main","pa th",1,"co mment",true,true,2);
-        String setCookie=fields.getStringField("Set-Cookie");
-        assertTrue(setCookie.startsWith("\"ev erything\"=\"va lue\";Comment=\"co mment\";Path=\"pa th\";Domain=\"do main\";Expires="));
-        assertTrue(setCookie.endsWith("GMT;Max-Age=1;Secure;HttpOnly"));
+        e = fields.getValues("name1",",");
+        assertEquals(true, e.hasMoreElements());
+        assertEquals(e.nextElement(), "value1A");
+        assertEquals(true, e.hasMoreElements());
+        assertEquals(e.nextElement(), "value\t, 1B");
+        assertEquals(true, e.hasMoreElements());
+        assertEquals(e.nextElement(), "value1C");
+        assertEquals(true, e.hasMoreElements());
+        assertEquals(e.nextElement(), "value1D");
+        assertEquals(false, e.hasMoreElements());
         
-        fields.clear();
-        fields.addSetCookie("json","{\"services\":[\"cwa\", \"aa\"]}",null,null,-1,null,false,false,-1);
-        assertEquals("json=\"{\\\"services\\\":[\\\"cwa\\\", \\\"aa\\\"]}\"",fields.getStringField("Set-Cookie"));
-
-        fields.clear();
-        fields.addSetCookie("name","value","domain",null,-1,null,false,false,-1);
-        fields.addSetCookie("name","other","domain",null,-1,null,false,false,-1);
-        assertEquals("name=other;Domain=domain",fields.getStringField("Set-Cookie"));
-        fields.addSetCookie("name","more","domain",null,-1,null,false,false,-1);
-        assertEquals("name=more;Domain=domain",fields.getStringField("Set-Cookie"));
-        fields.addSetCookie("foo","bar","domain",null,-1,null,false,false,-1);
-        fields.addSetCookie("foo","bob","domain",null,-1,null,false,false,-1);
-        assertEquals("name=more;Domain=domain",fields.getStringField("Set-Cookie"));
-
-        e=fields.getValues("Set-Cookie");
-        assertEquals("name=more;Domain=domain",e.nextElement());
-        assertEquals("foo=bob;Domain=domain",e.nextElement());
-    }
-
-    private Set<String> enum2set(Enumeration<String> e)
-    {
-        Set<String> s=new HashSet<String>();
-        while(e.hasMoreElements())
-            s.add(e.nextElement().toLowerCase(Locale.ENGLISH));
-        return s;
     }
 
     @Test
@@ -515,10 +356,10 @@ public class HttpFieldsTest
 
         fields.putDateField("Dminus",-1);
         assertEquals("Wed, 31 Dec 1969 23:59:59 GMT",fields.getStringField("Dminus"));
-        
+
         fields.putDateField("Dminus",-1000);
         assertEquals("Wed, 31 Dec 1969 23:59:59 GMT",fields.getStringField("Dminus"));
-        
+
         fields.putDateField("Dancient",Long.MIN_VALUE);
         assertEquals("Sun, 02 Dec 55 16:47:04 GMT",fields.getStringField("Dancient"));
     }
@@ -571,16 +412,24 @@ public class HttpFieldsTest
     }
 
     @Test
-    public void testToString() throws Exception
+    public void testContains() throws Exception
     {
         HttpFields header = new HttpFields();
 
-        header.put(new ByteArrayBuffer("name0"), new View(new ByteArrayBuffer("value0")));
-        header.put(new ByteArrayBuffer("name1"), new View(new ByteArrayBuffer("value1".getBytes())));
-        String s1=header.toString();
-        String s2=header.toString();
-        //System.err.println(s1);
-        //System.err.println(s2);
-        assertEquals(s1,s2);
+        header.add("0", "");
+        header.add("1", ",");
+        header.add("2", ",,");
+        header.add("3", "abc");
+        header.add("4", "def");
+        header.add("5", "abc,def,hig");
+        header.add("6", "abc");
+        header.add("6", "def");
+        header.add("6", "hig");
+
+        for (int i=0;i<7;i++)
+        {
+            assertFalse(""+i,header.contains(""+i,"xyz"));
+            assertEquals(""+i,i>=4,header.contains(""+i,"def"));
+        }
     }
 }
diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpGeneratorClientTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpGeneratorClientTest.java
index c746fce..0fc87e1 100644
--- a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpGeneratorClientTest.java
+++ b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpGeneratorClientTest.java
@@ -18,401 +18,241 @@
 
 package org.eclipse.jetty.http;
 
-import java.io.IOException;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.Buffers.Type;
-import org.eclipse.jetty.io.ByteArrayBuffer;
-import org.eclipse.jetty.io.ByteArrayEndPoint;
-import org.eclipse.jetty.io.PooledBuffers;
-import org.eclipse.jetty.io.SimpleBuffers;
-import org.eclipse.jetty.io.View;
-import org.junit.Test;
+import java.nio.ByteBuffer;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
+import org.eclipse.jetty.util.BufferUtil;
+import org.hamcrest.Matchers;
+import org.junit.Assert;
+import org.junit.Test;
 
 public class HttpGeneratorClientTest
 {
-    public final static String CONTENT="The quick brown fox jumped over the lazy dog.\nNow is the time for all good men to come to the aid of the party\nThe moon is blue to a fish in love.\n";
     public final static String[] connect={null,"keep-alive","close"};
 
-    @Test
-    public void testContentLength() throws Exception
+    class Info extends HttpGenerator.RequestInfo
     {
-        Buffer bb=new ByteArrayBuffer(8096);
-        Buffer sb=new ByteArrayBuffer(1500);
-        ByteArrayEndPoint endp = new ByteArrayEndPoint(new byte[0],4096);
-        HttpGenerator generator = new HttpGenerator(new SimpleBuffers(sb,bb),endp);
-
-        generator.setRequest("GET","/usr");
-
-        HttpFields fields = new HttpFields();
-        fields.add("Header","Value");
-        fields.add("Content-Type","text/plain");
-
-        String content = "The quick brown fox jumped over the lazy dog";
-        fields.addLongField("Content-Length",content.length());
-
-        generator.completeHeader(fields,false);
-
-        generator.addContent(new ByteArrayBuffer(content).asMutableBuffer(),true);
-        generator.flushBuffer();
-        generator.complete();
-        generator.flushBuffer();
+        Info(String method,String uri)
+        {
+            super(HttpVersion.HTTP_1_1,new HttpFields(),-1,method,uri);
+        }
 
-        String result=endp.getOut().toString().replace("\r\n","|").replace('\r','|').replace('\n','|');
-        assertEquals("GET /usr HTTP/1.1|Header: Value|Content-Type: text/plain|Content-Length: 44||"+content,result);
+        public Info(String method,String uri, int contentLength)
+        {
+            super(HttpVersion.HTTP_1_1,new HttpFields(),contentLength,method,uri);
+        }
     }
 
     @Test
-    public void testAutoContentLength() throws Exception
+    public void testRequestNoContent() throws Exception
     {
-        Buffer bb=new ByteArrayBuffer(8096);
-        Buffer sb=new ByteArrayBuffer(1500);
-        ByteArrayEndPoint endp = new ByteArrayEndPoint(new byte[0],4096);
-        HttpGenerator generator = new HttpGenerator(new SimpleBuffers(sb,bb),endp);
-
-        generator.setRequest("GET","/usr");
-
-        HttpFields fields = new HttpFields();
-        fields.add("Header","Value");
-        fields.add("Content-Type","text/plain");
-
-        String content = "The quick brown fox jumped over the lazy dog";
-
-        generator.addContent(new ByteArrayBuffer(content).asMutableBuffer(),true);
-        generator.completeHeader(fields,true);
+        ByteBuffer header=BufferUtil.allocate(2048);
+        HttpGenerator gen = new HttpGenerator();
+
+        HttpGenerator.Result
+        result=gen.generateRequest(null,null,null,null, true);
+        Assert.assertEquals(HttpGenerator.Result.NEED_INFO, result);
+        Assert.assertEquals(HttpGenerator.State.START, gen.getState());
+
+        Info info = new Info("GET","/index.html");
+        info.getHttpFields().add("Host","something");
+        info.getHttpFields().add("User-Agent","test");
+        Assert.assertTrue(!gen.isChunking());
+
+        result=gen.generateRequest(info,null,null,null, true);
+        Assert.assertEquals(HttpGenerator.Result.NEED_HEADER, result);
+        Assert.assertEquals(HttpGenerator.State.START, gen.getState());
+
+        result=gen.generateRequest(info,header,null,null, true);
+        Assert.assertEquals(HttpGenerator.Result.FLUSH, result);
+        Assert.assertEquals(HttpGenerator.State.COMPLETING, gen.getState());
+        Assert.assertTrue(!gen.isChunking());
+        String out = BufferUtil.toString(header);
+        BufferUtil.clear(header);
+
+        result=gen.generateResponse(null,null,null,null, false);
+        Assert.assertEquals(HttpGenerator.Result.DONE, result);
+        Assert.assertEquals(HttpGenerator.State.END, gen.getState());
+        Assert.assertTrue(!gen.isChunking());
+
+        Assert.assertEquals(0, gen.getContentPrepared());
+        Assert.assertThat(out, Matchers.containsString("GET /index.html HTTP/1.1"));
+        Assert.assertThat(out, Matchers.not(Matchers.containsString("Content-Length")));
 
-        generator.flushBuffer();
-        generator.complete();
-        generator.flushBuffer();
-
-        String result=endp.getOut().toString().replace("\r\n","|").replace('\r','|').replace('\n','|');
-        assertEquals("GET /usr HTTP/1.1|Header: Value|Content-Type: text/plain|Content-Length: 44||"+content,result);
     }
 
     @Test
-    public void testChunked() throws Exception
+    public void testRequestWithContent() throws Exception
     {
-        Buffer bb=new ByteArrayBuffer(8096);
-        Buffer sb=new ByteArrayBuffer(1500);
-        ByteArrayEndPoint endp = new ByteArrayEndPoint(new byte[0],4096);
-        HttpGenerator generator = new HttpGenerator(new SimpleBuffers(sb,bb),endp);
-
-        generator.setRequest("GET","/usr");
-
-        HttpFields fields = new HttpFields();
-        fields.add("Header","Value");
-        fields.add("Content-Type","text/plain");
-
-        String content = "The quick brown fox jumped over the lazy dog";
-
-        generator.completeHeader(fields,false);
-
-        generator.addContent(new ByteArrayBuffer(content).asMutableBuffer(),false);
-        generator.flushBuffer();
-        generator.complete();
-        generator.flushBuffer();
-
-        String result=endp.getOut().toString().replace("\r\n","|").replace('\r','|').replace('\n','|');
-        assertEquals("GET /usr HTTP/1.1|Header: Value|Content-Type: text/plain|Transfer-Encoding: chunked||2C|"+content+"|0||",result);
+        String out;
+        ByteBuffer header=BufferUtil.allocate(4096);
+        ByteBuffer content0=BufferUtil.toBuffer("Hello World. The quick brown fox jumped over the lazy dog.");
+        HttpGenerator gen = new HttpGenerator();
+
+        HttpGenerator.Result
+        result=gen.generateRequest(null,null,null,content0, true);
+        Assert.assertEquals(HttpGenerator.Result.NEED_INFO, result);
+        Assert.assertEquals(HttpGenerator.State.START, gen.getState());
+
+        Info info = new Info("POST","/index.html");
+        info.getHttpFields().add("Host","something");
+        info.getHttpFields().add("User-Agent","test");
+
+        result=gen.generateRequest(info,null,null,content0, true);
+        Assert.assertEquals(HttpGenerator.Result.NEED_HEADER, result);
+        Assert.assertEquals(HttpGenerator.State.START, gen.getState());
+
+        result=gen.generateRequest(info,header,null,content0, true);
+        Assert.assertEquals(HttpGenerator.Result.FLUSH, result);
+        Assert.assertEquals(HttpGenerator.State.COMPLETING, gen.getState());
+        Assert.assertTrue(!gen.isChunking());
+        out = BufferUtil.toString(header);
+        BufferUtil.clear(header);
+        out+=BufferUtil.toString(content0);
+        BufferUtil.clear(content0);
+
+        result=gen.generateResponse(null,null,null,null, false);
+        Assert.assertEquals(HttpGenerator.Result.DONE, result);
+        Assert.assertEquals(HttpGenerator.State.END, gen.getState());
+        Assert.assertTrue(!gen.isChunking());
+
+
+        Assert.assertThat(out, Matchers.containsString("POST /index.html HTTP/1.1"));
+        Assert.assertThat(out, Matchers.containsString("Host: something"));
+        Assert.assertThat(out, Matchers.containsString("Content-Length: 58"));
+        Assert.assertThat(out, Matchers.containsString("Hello World. The quick brown fox jumped over the lazy dog."));
+
+        Assert.assertEquals(58, gen.getContentPrepared());
     }
 
-    /**
-     * When the endpoint experiences back pressure, check that chunked transfer does not
-     * screw up the chunking by leaving out the second chunk header.
-     */
     @Test
-    public void testChunkedWithBackPressure() throws Exception
+    public void testRequestWithChunkedContent() throws Exception
     {
-        final AtomicInteger availableChannelBytes = new AtomicInteger(500);
-        ByteArrayEndPoint endp = new ByteArrayEndPoint(new byte[0],4096)
-        {
-            @Override
-            public int flush(Buffer buffer) throws IOException
-            {
-                // Simulate a socket that can only take 500 bytes at a time
-                View view = new View(buffer, buffer.markIndex(), buffer.getIndex(),
-                        Math.min(buffer.putIndex(), buffer.getIndex()+availableChannelBytes.get()), buffer.isReadOnly()?Buffer.READONLY:Buffer.READWRITE);
-                int read = super.flush(view);
-                buffer.skip(read);
-                availableChannelBytes.getAndAdd(-1*read);
-                return read;
-            }
-        };
-        PooledBuffers pool = new PooledBuffers(Type.BYTE_ARRAY,1416,Type.BYTE_ARRAY,8096,Type.BYTE_ARRAY,10240);
-        HttpGenerator generator = new HttpGenerator(pool,endp);
-
-        generator.setRequest("GET","/usr");
-
-        HttpFields fields = new HttpFields();
-        fields.add("Header","Value");
-        fields.add("Content-Type","text/plain");
-
-        String content = "The quick brown fox jumped, ";
-        // addContent only goes into "bypass" mode if the content is longer than 1024 characters.
-        while (content.length() < 1024)
-        {
-            content = content + content;
-        }
-        String content2 = "over the lazy dog";
-
-        generator.completeHeader(fields,false);
-
-        generator.addContent(new ByteArrayBuffer(content).asMutableBuffer(),false);
-        generator.addContent(new ByteArrayBuffer(content2).asMutableBuffer(),false);
+        String out;
+        ByteBuffer header=BufferUtil.allocate(4096);
+        ByteBuffer chunk=BufferUtil.allocate(HttpGenerator.CHUNK_SIZE);
+        ByteBuffer content0=BufferUtil.toBuffer("Hello World. ");
+        ByteBuffer content1=BufferUtil.toBuffer("The quick brown fox jumped over the lazy dog.");
+        HttpGenerator gen = new HttpGenerator();
+
+        HttpGenerator.Result
+        result=gen.generateRequest(null,null,null,content0, false);
+        Assert.assertEquals(HttpGenerator.Result.NEED_INFO, result);
+        Assert.assertEquals(HttpGenerator.State.START, gen.getState());
+
+        Info info = new Info("POST","/index.html");
+        info.getHttpFields().add("Host","something");
+        info.getHttpFields().add("User-Agent","test");
+
+        result=gen.generateRequest(info,null,null,content0, false);
+        Assert.assertEquals(HttpGenerator.Result.NEED_HEADER, result);
+        Assert.assertEquals(HttpGenerator.State.START, gen.getState());
+
+        result=gen.generateRequest(info,header,null,content0, false);
+        Assert.assertEquals(HttpGenerator.Result.FLUSH, result);
+        Assert.assertEquals(HttpGenerator.State.COMMITTED, gen.getState());
+        Assert.assertTrue(gen.isChunking());
+        out = BufferUtil.toString(header);
+        BufferUtil.clear(header);
+        out+=BufferUtil.toString(content0);
+        BufferUtil.clear(content0);
+
+        result=gen.generateRequest(null,header,null,content1, false);
+        Assert.assertEquals(HttpGenerator.Result.NEED_CHUNK, result);
+        Assert.assertEquals(HttpGenerator.State.COMMITTED, gen.getState());
+
+        result=gen.generateRequest(null,null,chunk,content1, false);
+        Assert.assertEquals(HttpGenerator.Result.FLUSH, result);
+        Assert.assertEquals(HttpGenerator.State.COMMITTED, gen.getState());
+        Assert.assertTrue(gen.isChunking());
+        out+=BufferUtil.toString(chunk);
+        BufferUtil.clear(chunk);
+        out+=BufferUtil.toString(content1);
+        BufferUtil.clear(content1);
+
+        result=gen.generateResponse(null,null,chunk,null, true);
+        Assert.assertEquals(HttpGenerator.Result.CONTINUE, result);
+        Assert.assertEquals(HttpGenerator.State.COMPLETING, gen.getState());
+        Assert.assertTrue(gen.isChunking());
+
+        result=gen.generateResponse(null,null,chunk,null, true);
+        Assert.assertEquals(HttpGenerator.Result.FLUSH, result);
+        Assert.assertEquals(HttpGenerator.State.COMPLETING, gen.getState());
+        out+=BufferUtil.toString(chunk);
+        BufferUtil.clear(chunk);
+        Assert.assertTrue(!gen.isChunking());
+
+        result=gen.generateResponse(null,null,chunk,null, true);
+        Assert.assertEquals(HttpGenerator.Result.DONE, result);
+        Assert.assertEquals(HttpGenerator.State.END, gen.getState());
+
+        Assert.assertThat(out, Matchers.containsString("POST /index.html HTTP/1.1"));
+        Assert.assertThat(out, Matchers.containsString("Host: something"));
+        Assert.assertThat(out, Matchers.containsString("Transfer-Encoding: chunked"));
+        Assert.assertThat(out, Matchers.containsString("\r\nD\r\nHello World. \r\n"));
+        Assert.assertThat(out, Matchers.containsString("\r\n2D\r\nThe quick brown fox jumped over the lazy dog.\r\n"));
+        Assert.assertThat(out, Matchers.containsString("\r\n0\r\n\r\n"));
+
+        Assert.assertEquals(58, gen.getContentPrepared());
 
-        // Now we'll allow more bytes to flow
-        availableChannelBytes.set(5000);
-        generator.flushBuffer();
-        generator.complete();
-        generator.flushBuffer();
-
-        String result=endp.getOut().toString();
-        System.err.println("result:"+result);
-        result=result.replace("\r\n","|").replace('\r','|').replace('\n','|');
-        assertEquals("GET /usr HTTP/1.1|Header: Value|Content-Type: text/plain|Transfer-Encoding: chunked||700|"+content+"|11|"+content2+"|0||",result);
     }
 
     @Test
-    public void testHTTP() throws Exception
+    public void testRequestWithKnownContent() throws Exception
     {
-        Buffer bb=new ByteArrayBuffer(8096);
-        Buffer sb=new ByteArrayBuffer(1500);
-        HttpFields fields = new HttpFields();
-        ByteArrayEndPoint endp = new ByteArrayEndPoint(new byte[0],4096);
-        HttpGenerator hb = new HttpGenerator(new SimpleBuffers(sb,bb),endp);
-        Handler handler = new Handler();
-        HttpParser parser=null;
-
-        // For HTTP version
-        for (int v=9;v<=11;v++)
-        {
-            // For each test result
-            for (int r=0;r<tr.length;r++)
-            {
-                // chunks = 1 to 3
-                for (int chunks=1;chunks<=6;chunks++)
-                {
-                    // For none, keep-alive, close
-                    for (int c=0;c<connect.length;c++)
-                    {
-                        String t="v="+v+",r="+r+",chunks="+chunks+",c="+c+",tr="+tr[r];
-                        // System.err.println(t);
-
-                        hb.reset();
-                        endp.reset();
-                        fields.clear();
-
-                        // System.out.println("TEST: "+t);
-
-                        try
-                        {
-                            tr[r].build(v,hb,connect[c],null,chunks, fields);
-                        }
-                        catch(IllegalStateException e)
-                        {
-                            if (v<10 || v==10 && chunks>2)
-                                continue;
-                            System.err.println(t);
-                            throw e;
-                        }
-                        String request=endp.getOut().toString();
-                        // System.out.println(request+(hb.isPersistent()?"...\n":"---\n"));
-
-                        assertTrue(t,hb.isPersistent());
-
-                        if (v==9)
-                        {
-                            assertEquals(t,"GET /context/path/info\r\n", request);
-                            continue;
-                        }
-
-                        parser=new HttpParser(new ByteArrayBuffer(request.getBytes()), handler);
-                        try
-                        {
-                            parser.parse();
-                        }
-                        catch(IOException e)
-                        {
-                            if (tr[r].body!=null)
-                                throw e;
-                            continue;
-                        }
-
-                        if (tr[r].body!=null)
-                            assertEquals(t,tr[r].body, this.content);
-                        if (v==10)
-                            assertTrue(t,hb.isPersistent() || tr[r].values[1]==null || c==2 || c==0);
-                        else
-                            assertTrue(t,hb.isPersistent() ||  c==2);
-
-                        assertTrue(t,tr[r].values[1]==null || content.length()==Integer.parseInt(tr[r].values[1]));
-                    }
-                }
-            }
-        }
-    }
+        String out;
+        ByteBuffer header=BufferUtil.allocate(4096);
+        ByteBuffer chunk=BufferUtil.allocate(HttpGenerator.CHUNK_SIZE);
+        ByteBuffer content0=BufferUtil.toBuffer("Hello World. ");
+        ByteBuffer content1=BufferUtil.toBuffer("The quick brown fox jumped over the lazy dog.");
+        HttpGenerator gen = new HttpGenerator();
+
+        HttpGenerator.Result
+        result=gen.generateRequest(null,null,null,content0, false);
+        Assert.assertEquals(HttpGenerator.Result.NEED_INFO, result);
+        Assert.assertEquals(HttpGenerator.State.START, gen.getState());
+
+        Info info = new Info("POST","/index.html",58);
+        info.getHttpFields().add("Host","something");
+        info.getHttpFields().add("User-Agent","test");
+
+        result=gen.generateRequest(info,null,null,content0, false);
+        Assert.assertEquals(HttpGenerator.Result.NEED_HEADER, result);
+        Assert.assertEquals(HttpGenerator.State.START, gen.getState());
+
+        result=gen.generateRequest(info,header,null,content0, false);
+        Assert.assertEquals(HttpGenerator.Result.FLUSH, result);
+        Assert.assertEquals(HttpGenerator.State.COMMITTED, gen.getState());
+        Assert.assertTrue(!gen.isChunking());
+        out = BufferUtil.toString(header);
+        BufferUtil.clear(header);
+        out+=BufferUtil.toString(content0);
+        BufferUtil.clear(content0);
+
+        result=gen.generateRequest(null,null,null,content1, false);
+        Assert.assertEquals(HttpGenerator.Result.FLUSH, result);
+        Assert.assertEquals(HttpGenerator.State.COMMITTED, gen.getState());
+        Assert.assertTrue(!gen.isChunking());
+        out+=BufferUtil.toString(content1);
+        BufferUtil.clear(content1);
+
+        result=gen.generateResponse(null,null,null,null, true);
+        Assert.assertEquals(HttpGenerator.Result.CONTINUE, result);
+        Assert.assertEquals(HttpGenerator.State.COMPLETING, gen.getState());
+        Assert.assertTrue(!gen.isChunking());
+
+        result=gen.generateResponse(null,null,null,null, true);
+        Assert.assertEquals(HttpGenerator.Result.DONE, result);
+        Assert.assertEquals(HttpGenerator.State.END, gen.getState());
+        out+=BufferUtil.toString(chunk);
+        BufferUtil.clear(chunk);
+
+        Assert.assertThat(out, Matchers.containsString("POST /index.html HTTP/1.1"));
+        Assert.assertThat(out, Matchers.containsString("Host: something"));
+        Assert.assertThat(out, Matchers.containsString("Content-Length: 58"));
+        Assert.assertThat(out, Matchers.containsString("\r\n\r\nHello World. The quick brown fox jumped over the lazy dog."));
+
+        Assert.assertEquals(58, gen.getContentPrepared());
 
-    private static final String[] headers= { "Content-Type","Content-Length","Connection","Transfer-Encoding","Other"};
-    private static class TR
-    {
-        private String[] values=new String[headers.length];
-        private String body;
-
-        private TR(String ct, String cl ,String content)
-        {
-            values[0]=ct;
-            values[1]=cl;
-            values[4]="value";
-            this.body=content;
-        }
-
-        private void build(int version,HttpGenerator hb, String connection, String te, int chunks, HttpFields fields)
-                throws Exception
-        {
-            values[2]=connection;
-            values[3]=te;
-
-            hb.setRequest(HttpMethods.GET,"/context/path/info");
-            hb.setVersion(version);
-
-            for (int i=0;i<headers.length;i++)
-            {
-                if (values[i]==null)
-                    continue;
-                fields.put(new ByteArrayBuffer(headers[i]),new ByteArrayBuffer(values[i]));
-            }
-
-            if (body!=null)
-            {
-                int inc=1+body.length()/chunks;
-                Buffer buf=new ByteArrayBuffer(body);
-                View view = new View(buf);
-                for (int i=1;i<chunks;i++)
-                {
-                    view.setPutIndex(i*inc);
-                    view.setGetIndex((i-1)*inc);
-                    hb.addContent(view,Generator.MORE);
-                    if (hb.isBufferFull() && hb.isState(AbstractGenerator.STATE_HEADER))
-                        hb.completeHeader(fields, Generator.MORE);
-                    if (i%2==0)
-                    {
-                        if (hb.isState(AbstractGenerator.STATE_HEADER))
-                        {
-                            if (version<11)
-                                fields.addLongField("Content-Length",body.length());
-                            hb.completeHeader(fields, Generator.MORE);
-                        }
-                        hb.flushBuffer();
-                    }
-                }
-                view.setPutIndex(buf.putIndex());
-                view.setGetIndex((chunks-1)*inc);
-                hb.addContent(view,Generator.LAST);
-                if(hb.isState(AbstractGenerator.STATE_HEADER))
-                    hb.completeHeader(fields, Generator.LAST);
-            }
-            else
-            {
-                hb.completeHeader(fields, Generator.LAST);
-            }
-            hb.complete();
-            while(!hb.isComplete())
-                hb.flushBuffer();
-        }
-
-        @Override
-        public String toString()
-        {
-            return "["+values[0]+","+values[1]+","+(body==null?"none":"_content")+"]";
-        }
     }
 
-    private final TR[] tr =
-    {
-      /* 0 */  new TR(null,null,null),
-      /* 1 */  new TR(null,null,CONTENT),
-      /* 3 */  new TR(null,""+CONTENT.length(),CONTENT),
-      /* 4 */  new TR("text/html",null,null),
-      /* 5 */  new TR("text/html",null,CONTENT),
-      /* 7 */  new TR("text/html",""+CONTENT.length(),CONTENT),
-    };
-
-
-    private String content;
-    private String f0;
-    private String f1;
-    private String f2;
-    private String[] hdr;
-    private String[] val;
-    private int h;
-
-    private class Handler extends HttpParser.EventHandler
-    {
-        private int index=0;
-
-        @Override
-        public void content(Buffer ref)
-        {
-            if (index == 0)
-                content= "";
-            content= content.substring(0, index) + ref;
-            index+=ref.length();
-        }
-
-        @Override
-        public void startRequest(Buffer tok0, Buffer tok1, Buffer tok2)
-        {
-            h= -1;
-            hdr= new String[9];
-            val= new String[9];
-            f0= tok0.toString();
-            f1= tok1.toString();
-            if (tok2!=null)
-                f2= tok2.toString();
-            else
-                f2=null;
-            index=0;
-            // System.out.println(f0+" "+f1+" "+f2);
-        }
-
-        /* (non-Javadoc)
-         * @see org.eclipse.jetty.EventHandler#startResponse(org.eclipse.io.Buffer, int, org.eclipse.io.Buffer)
-         */
-        @Override
-        public void startResponse(Buffer version, int status, Buffer reason)
-        {
-            h= -1;
-            hdr= new String[9];
-            val= new String[9];
-            f0= version.toString();
-            f1= ""+status;
-            if (reason!=null)
-                f2= reason.toString();
-            else
-                f2=null;
-            index=0;
-        }
-
-        @Override
-        public void parsedHeader(Buffer name,Buffer value)
-        {
-            hdr[++h]= name.toString();
-            val[h]= value.toString();
-        }
-
-        @Override
-        public void headerComplete()
-        {
-            content= null;
-        }
-
-        @Override
-        public void messageComplete(long contentLength)
-        {
-        }
-    }
 }
diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpGeneratorServerHTTPTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpGeneratorServerHTTPTest.java
new file mode 100644
index 0000000..8ff3051
--- /dev/null
+++ b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpGeneratorServerHTTPTest.java
@@ -0,0 +1,368 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.EnumSet;
+import java.util.List;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+import static org.hamcrest.Matchers.either;
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+ at RunWith(Parameterized.class)
+public class HttpGeneratorServerHTTPTest
+{
+    @Parameter(value = 0)
+    public Run run;
+    private String _content;
+    private String _reason;
+
+    @Test
+    public void testHTTP() throws Exception
+    {
+        Handler handler = new Handler();
+
+        HttpGenerator gen = new HttpGenerator();
+
+        String t = run.toString();
+
+        run.result.getHttpFields().clear();
+
+        String response = run.result.build(run.httpVersion, gen, "OK\r\nTest", run.connection.val, null, run.chunks);
+
+        if (run.httpVersion == 9)
+        {
+            assertFalse(t, gen.isPersistent());
+            if (run.result._body != null)
+                assertEquals(t, run.result._body, response);
+            return;
+        }
+
+        HttpParser parser = new HttpParser(handler);
+        parser.setHeadResponse(run.result._head);
+
+        parser.parseNext(BufferUtil.toBuffer(response));
+
+        if (run.result._body != null)
+            assertEquals(t, run.result._body, this._content);
+
+        if (run.httpVersion == 10)
+            assertTrue(t, gen.isPersistent() || run.result._contentLength >= 0 || EnumSet.of(ConnectionType.CLOSE, ConnectionType.KEEP_ALIVE, ConnectionType.NONE).contains(run.connection));
+        else
+            assertTrue(t, gen.isPersistent() || EnumSet.of(ConnectionType.CLOSE, ConnectionType.TE_CLOSE).contains(run.connection));
+
+        if (run.httpVersion > 9)
+            assertEquals("OK??Test", _reason);
+
+        if (_content == null)
+            assertTrue(t, run.result._body == null);
+        else
+            assertThat(t, run.result._contentLength, either(equalTo(_content.length())).or(equalTo(-1)));
+    }
+
+    private static class Result
+    {
+        private HttpFields _fields = new HttpFields();
+        private final String _body;
+        private final int _code;
+        private String _connection;
+        private int _contentLength;
+        private String _contentType;
+        private final boolean _head;
+        private String _other;
+        private String _te;
+
+        private Result(int code, String contentType, int contentLength, String content, boolean head)
+        {
+            _code = code;
+            _contentType = contentType;
+            _contentLength = contentLength;
+            _other = "value";
+            _body = content;
+            _head = head;
+        }
+
+        private String build(int version, HttpGenerator gen, String reason, String connection, String te, int nchunks) throws Exception
+        {
+            String response = "";
+            _connection = connection;
+            _te = te;
+
+            if (_contentType != null)
+                _fields.put("Content-Type", _contentType);
+            if (_contentLength >= 0)
+                _fields.put("Content-Length", "" + _contentLength);
+            if (_connection != null)
+                _fields.put("Connection", _connection);
+            if (_te != null)
+                _fields.put("Transfer-Encoding", _te);
+            if (_other != null)
+                _fields.put("Other", _other);
+
+            ByteBuffer source = _body == null ? null : BufferUtil.toBuffer(_body);
+            ByteBuffer[] chunks = new ByteBuffer[nchunks];
+            ByteBuffer content = null;
+            int c = 0;
+            if (source != null)
+            {
+                for (int i = 0; i < nchunks; i++)
+                {
+                    chunks[i] = source.duplicate();
+                    chunks[i].position(i * (source.capacity() / nchunks));
+                    if (i > 0)
+                        chunks[i - 1].limit(chunks[i].position());
+                }
+                content = chunks[c++];
+            }
+            ByteBuffer header = null;
+            ByteBuffer chunk = null;
+            HttpGenerator.ResponseInfo info = null;
+
+            loop:
+            while (true)
+            {
+                // if we have unwritten content
+                if (source != null && content != null && content.remaining() == 0 && c < nchunks)
+                    content = chunks[c++];
+
+                // Generate
+                boolean last = !BufferUtil.hasContent(content);
+
+                HttpGenerator.Result result = gen.generateResponse(info, header, chunk, content, last);
+
+                switch (result)
+                {
+                    case NEED_INFO:
+                        info = new HttpGenerator.ResponseInfo(HttpVersion.fromVersion(version), _fields, _contentLength, _code, reason, _head);
+                        continue;
+
+                    case NEED_HEADER:
+                        header = BufferUtil.allocate(2048);
+                        continue;
+
+                    case NEED_CHUNK:
+                        chunk = BufferUtil.allocate(HttpGenerator.CHUNK_SIZE);
+                        continue;
+
+                    case FLUSH:
+                        if (BufferUtil.hasContent(header))
+                        {
+                            response += BufferUtil.toString(header);
+                            header.position(header.limit());
+                        }
+                        if (BufferUtil.hasContent(chunk))
+                        {
+                            response += BufferUtil.toString(chunk);
+                            chunk.position(chunk.limit());
+                        }
+                        if (BufferUtil.hasContent(content))
+                        {
+                            response += BufferUtil.toString(content);
+                            content.position(content.limit());
+                        }
+                        break;
+
+                    case CONTINUE:
+                        continue;
+
+                    case SHUTDOWN_OUT:
+                        break;
+
+                    case DONE:
+                        break loop;
+                }
+            }
+            return response;
+        }
+
+        @Override
+        public String toString()
+        {
+            return "[" + _code + "," + _contentType + "," + _contentLength + "," + (_body == null ? "null" : "content") + "]";
+        }
+
+        public HttpFields getHttpFields()
+        {
+            return _fields;
+        }
+    }
+
+    private class Handler implements HttpParser.ResponseHandler<ByteBuffer>
+    {
+        @Override
+        public boolean content(ByteBuffer ref)
+        {
+            if (_content == null)
+                _content = "";
+            _content += BufferUtil.toString(ref);
+            ref.position(ref.limit());
+            return false;
+        }
+
+        @Override
+        public void earlyEOF()
+        {
+        }
+
+        @Override
+        public boolean headerComplete()
+        {
+            _content = null;
+            return false;
+        }
+
+        @Override
+        public boolean messageComplete()
+        {
+            return true;
+        }
+
+        @Override
+        public boolean parsedHeader(HttpField field)
+        {
+            return false;
+        }
+
+        @Override
+        public boolean startResponse(HttpVersion version, int status, String reason)
+        {
+            _reason = reason;
+            return false;
+        }
+
+        @Override
+        public void badMessage(int status, String reason)
+        {
+            throw new IllegalStateException(reason);
+        }
+
+        @Override
+        public int getHeaderCacheSize()
+        {
+            return 256;
+        }
+    }
+
+    public final static String CONTENT = "The quick brown fox jumped over the lazy dog.\nNow is the time for all good men to come to the aid of the party\nThe moon is blue to a fish in love.\n";
+
+    private static class Run
+    {
+        public static Run[] as(Result result, int ver, int chunks, ConnectionType connection)
+        {
+            Run run = new Run();
+            run.result = result;
+            run.httpVersion = ver;
+            run.chunks = chunks;
+            run.connection = connection;
+            return new Run[]{run};
+        }
+
+        private Result result;
+        private ConnectionType connection;
+        private int httpVersion;
+        private int chunks;
+
+        @Override
+        public String toString()
+        {
+            return String.format("result=%s,version=%d,chunks=%d,connection=%s", result, httpVersion, chunks, connection.name());
+        }
+    }
+
+    private enum ConnectionType
+    {
+        NONE(null, 9, 10, 11),
+        KEEP_ALIVE("keep-alive", 9, 10, 11),
+        CLOSE("close", 9, 10, 11),
+        TE_CLOSE("TE, close", 11);
+
+        private String val;
+        private int[] supportedHttpVersions;
+
+        private ConnectionType(String val, int... supportedHttpVersions)
+        {
+            this.val = val;
+            this.supportedHttpVersions = supportedHttpVersions;
+        }
+
+        public boolean isSupportedByHttp(int version)
+        {
+            for (int supported : supportedHttpVersions)
+            {
+                if (supported == version)
+                {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
+    @Parameters(name = "{0}")
+    public static Collection<Run[]> data()
+    {
+        Result[] results = {
+                new Result(200, null, -1, null, false),
+                new Result(200, null, -1, CONTENT, false),
+                new Result(200, null, CONTENT.length(), null, true),
+                new Result(200, null, CONTENT.length(), CONTENT, false),
+                new Result(200, "text/html", -1, null, true),
+                new Result(200, "text/html", -1, CONTENT, false),
+                new Result(200, "text/html", CONTENT.length(), null, true),
+                new Result(200, "text/html", CONTENT.length(), CONTENT, false)
+        };
+
+        List<Run[]> data = new ArrayList<>();
+
+        // For each test result
+        for (Result result : results)
+        {
+            // Loop over HTTP versions
+            for (int v = 9; v <= 11; v++)
+            {
+                // Loop over chunks
+                for (int chunks = 1; chunks <= 6; chunks++)
+                {
+                    // Loop over Connection values
+                    for (ConnectionType connection : ConnectionType.values())
+                    {
+                        if (connection.isSupportedByHttp(v))
+                        {
+                            data.add(Run.as(result, v, chunks, connection));
+                        }
+                    }
+                }
+            }
+        }
+        return data;
+    }
+}
diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpGeneratorServerTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpGeneratorServerTest.java
new file mode 100644
index 0000000..fc6d149
--- /dev/null
+++ b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpGeneratorServerTest.java
@@ -0,0 +1,452 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.http.HttpGenerator.ResponseInfo;
+import org.eclipse.jetty.util.BufferUtil;
+import org.junit.Assert;
+import org.junit.Test;
+
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.not;
+import static org.hamcrest.Matchers.startsWith;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+public class HttpGeneratorServerTest
+{ 
+    
+    @Test
+    public void testSimple() throws Exception
+    {
+        ByteBuffer header = BufferUtil.allocate(8096);
+        ByteBuffer content = BufferUtil.toBuffer("0123456789");
+
+        HttpGenerator gen = new HttpGenerator();
+
+        HttpGenerator.Result result = gen.generateResponse(null, null, null, content, true);
+        assertEquals(HttpGenerator.Result.NEED_INFO, result);
+        assertEquals(HttpGenerator.State.START, gen.getState());
+
+        ResponseInfo info = new ResponseInfo(HttpVersion.HTTP_1_1, new HttpFields(), 10, 200, null, false);
+        info.getHttpFields().add("Content-Type", "test/data");
+        info.getHttpFields().add("Last-Modified", DateGenerator.__01Jan1970);
+
+        result = gen.generateResponse(info, null, null, content, true);
+        assertEquals(HttpGenerator.Result.NEED_HEADER, result);
+
+        result = gen.generateResponse(info, header, null, content, true);
+        assertEquals(HttpGenerator.Result.FLUSH, result);
+        assertEquals(HttpGenerator.State.COMPLETING, gen.getState());
+        String response = BufferUtil.toString(header);
+        BufferUtil.clear(header);
+        response += BufferUtil.toString(content);
+        BufferUtil.clear(content);        
+
+        result = gen.generateResponse(null, null, null, content, false);
+        assertEquals(HttpGenerator.Result.DONE, result);
+        assertEquals(HttpGenerator.State.END, gen.getState());
+        
+        assertEquals(10, gen.getContentPrepared());
+        
+        assertThat(response, containsString("HTTP/1.1 200 OK"));
+        assertThat(response, containsString("Last-Modified: Thu, 01 Jan 1970 00:00:00 GMT"));
+        assertThat(response, containsString("Content-Length: 10"));
+        assertThat(response, containsString("\r\n0123456789"));
+    }
+    
+    @Test
+    public void test204() throws Exception
+    {
+        ByteBuffer header = BufferUtil.allocate(8096);
+        ByteBuffer content = BufferUtil.toBuffer("0123456789");
+
+        HttpGenerator gen = new HttpGenerator();
+        
+        ResponseInfo info = new ResponseInfo(HttpVersion.HTTP_1_1, new HttpFields(), 10, 204, "Foo", false);
+        info.getHttpFields().add("Content-Type", "test/data");
+        info.getHttpFields().add("Last-Modified", DateGenerator.__01Jan1970);
+
+        HttpGenerator.Result result = gen.generateResponse(info, header, null, content, true);
+     
+        assertEquals(gen.isNoContent(), true);    
+        assertEquals(HttpGenerator.Result.FLUSH, result);
+        assertEquals(HttpGenerator.State.COMPLETING, gen.getState());
+        String responseheaders = BufferUtil.toString(header);
+        BufferUtil.clear(header);     
+
+        result = gen.generateResponse(null, null, null, content, false);
+        assertEquals(HttpGenerator.Result.DONE, result);
+        assertEquals(HttpGenerator.State.END, gen.getState());
+        
+        assertThat(responseheaders, containsString("HTTP/1.1 204 Foo"));
+        assertThat(responseheaders, containsString("Last-Modified: Thu, 01 Jan 1970 00:00:00 GMT"));
+        assertThat(responseheaders, not(containsString("Content-Length: 10")));
+
+        //Note: the HttpConnection.process() method is responsible for actually
+        //excluding the content from the response based on generator.isNoContent()==true
+    }
+    
+    
+    @Test
+    public void testComplexChars() throws Exception
+    {
+        ByteBuffer header = BufferUtil.allocate(8096);
+        ByteBuffer content = BufferUtil.toBuffer("0123456789");
+
+        HttpGenerator gen = new HttpGenerator();
+
+        HttpGenerator.Result result = gen.generateResponse(null, null, null, content, true);
+        assertEquals(HttpGenerator.Result.NEED_INFO, result);
+        assertEquals(HttpGenerator.State.START, gen.getState());
+
+        ResponseInfo info = new ResponseInfo(HttpVersion.HTTP_1_1, new HttpFields(), 10, 200, null, false);
+        info.getHttpFields().add("Content-Type", "test/data;\r\nextra=value");
+        info.getHttpFields().add("Last-Modified", DateGenerator.__01Jan1970);
+
+        result = gen.generateResponse(info, null, null, content, true);
+        assertEquals(HttpGenerator.Result.NEED_HEADER, result);
+
+        result = gen.generateResponse(info, header, null, content, true);
+        assertEquals(HttpGenerator.Result.FLUSH, result);
+        assertEquals(HttpGenerator.State.COMPLETING, gen.getState());
+        String response = BufferUtil.toString(header);
+        BufferUtil.clear(header);
+        response += BufferUtil.toString(content);
+        BufferUtil.clear(content);        
+
+        result = gen.generateResponse(null, null, null, content, false);
+        assertEquals(HttpGenerator.Result.DONE, result);
+        assertEquals(HttpGenerator.State.END, gen.getState());
+                
+        assertEquals(10, gen.getContentPrepared());
+        
+        assertThat(response, containsString("HTTP/1.1 200 OK"));
+        assertThat(response, containsString("Last-Modified: Thu, 01 Jan 1970 00:00:00 GMT"));
+        assertThat(response, containsString("Content-Type: test/data;  extra=value"));
+        assertThat(response, containsString("Content-Length: 10"));
+        assertThat(response, containsString("\r\n0123456789"));
+    }
+
+    @Test
+    public void testSendServerXPoweredBy() throws Exception
+    {
+        ByteBuffer header = BufferUtil.allocate(8096);
+        ResponseInfo info = new ResponseInfo(HttpVersion.HTTP_1_1, new HttpFields(), -1, 200, null, false);
+        HttpFields fields = new HttpFields();
+        fields.add(HttpHeader.SERVER, "SomeServer");
+        fields.add(HttpHeader.X_POWERED_BY, "SomePower");
+        ResponseInfo infoF = new ResponseInfo(HttpVersion.HTTP_1_1, fields, -1, 200, null, false);
+        String head;
+
+        HttpGenerator gen = new HttpGenerator(true, true);
+        gen.generateResponse(info, header, null, null, true);
+        head = BufferUtil.toString(header);
+        BufferUtil.clear(header);
+        assertThat(head, containsString("HTTP/1.1 200 OK"));
+        assertThat(head, containsString("Server: Jetty(9.x.x)"));
+        assertThat(head, containsString("X-Powered-By: Jetty(9.x.x)"));
+        gen.reset();
+        gen.generateResponse(infoF, header, null, null, true);
+        head = BufferUtil.toString(header);
+        BufferUtil.clear(header);
+        assertThat(head, containsString("HTTP/1.1 200 OK"));
+        assertThat(head, not(containsString("Server: Jetty(9.x.x)")));
+        assertThat(head, containsString("Server: SomeServer"));
+        assertThat(head, containsString("X-Powered-By: Jetty(9.x.x)"));
+        assertThat(head, containsString("X-Powered-By: SomePower"));
+        gen.reset();
+
+        gen = new HttpGenerator(false, false);
+        gen.generateResponse(info, header, null, null, true);
+        head = BufferUtil.toString(header);
+        BufferUtil.clear(header);
+        assertThat(head, containsString("HTTP/1.1 200 OK"));
+        assertThat(head, not(containsString("Server: Jetty(9.x.x)")));
+        assertThat(head, not(containsString("X-Powered-By: Jetty(9.x.x)")));
+        gen.reset();
+        gen.generateResponse(infoF, header, null, null, true);
+        head = BufferUtil.toString(header);
+        BufferUtil.clear(header);
+        assertThat(head, containsString("HTTP/1.1 200 OK"));
+        assertThat(head, not(containsString("Server: Jetty(9.x.x)")));
+        assertThat(head, containsString("Server: SomeServer"));
+        assertThat(head, not(containsString("X-Powered-By: Jetty(9.x.x)")));
+        assertThat(head, containsString("X-Powered-By: SomePower"));
+        gen.reset();
+    }
+
+    @Test
+    public void testResponseNoContent() throws Exception
+    {
+        ByteBuffer header = BufferUtil.allocate(8096);
+
+        HttpGenerator gen = new HttpGenerator();
+
+        HttpGenerator.Result result = gen.generateResponse(null, null, null, null, true);
+        assertEquals(HttpGenerator.Result.NEED_INFO, result);
+        assertEquals(HttpGenerator.State.START, gen.getState());
+
+        ResponseInfo info = new ResponseInfo(HttpVersion.HTTP_1_1, new HttpFields(), -1, 200, null, false);
+        info.getHttpFields().add("Last-Modified", DateGenerator.__01Jan1970);
+
+        result = gen.generateResponse(info, null, null, null, true);
+        assertEquals(HttpGenerator.Result.NEED_HEADER, result);
+
+        result = gen.generateResponse(info, header, null, null, true);
+        assertEquals(HttpGenerator.Result.FLUSH, result);
+        assertEquals(HttpGenerator.State.COMPLETING, gen.getState());
+        String head = BufferUtil.toString(header);
+        BufferUtil.clear(header);
+
+        result = gen.generateResponse(null, null, null, null, false);
+        assertEquals(HttpGenerator.Result.DONE, result);
+        assertEquals(HttpGenerator.State.END, gen.getState());
+
+        assertEquals(0, gen.getContentPrepared());
+        assertThat(head, containsString("HTTP/1.1 200 OK"));
+        assertThat(head, containsString("Last-Modified: Thu, 01 Jan 1970 00:00:00 GMT"));
+        assertThat(head, containsString("Content-Length: 0"));
+    }
+
+    @Test
+    public void testResponseUpgrade() throws Exception
+    {
+        ByteBuffer header = BufferUtil.allocate(8096);
+
+        HttpGenerator gen = new HttpGenerator();
+
+        HttpGenerator.Result result = gen.generateResponse(null, null, null, null, true);
+        assertEquals(HttpGenerator.Result.NEED_INFO, result);
+        assertEquals(HttpGenerator.State.START, gen.getState());
+
+        ResponseInfo info = new ResponseInfo(HttpVersion.HTTP_1_1, new HttpFields(), -1, 101, null, false);
+        info.getHttpFields().add("Upgrade", "WebSocket");
+        info.getHttpFields().add("Connection", "Upgrade");
+        info.getHttpFields().add("Sec-WebSocket-Accept", "123456789==");
+
+        result = gen.generateResponse(info, header, null, null, true);
+        assertEquals(HttpGenerator.Result.FLUSH, result);
+        assertEquals(HttpGenerator.State.COMPLETING, gen.getState());
+        String head = BufferUtil.toString(header);
+        BufferUtil.clear(header);
+
+        result = gen.generateResponse(info, null, null, null, false);
+        assertEquals(HttpGenerator.Result.DONE, result);
+        assertEquals(HttpGenerator.State.END, gen.getState());
+
+        assertEquals(0, gen.getContentPrepared());
+
+        assertThat(head, startsWith("HTTP/1.1 101 Switching Protocols"));
+        assertThat(head, containsString("Upgrade: WebSocket\r\n"));
+        assertThat(head, containsString("Connection: Upgrade\r\n"));
+    }
+
+    @Test
+    public void testResponseWithChunkedContent() throws Exception
+    {
+        ByteBuffer header = BufferUtil.allocate(4096);
+        ByteBuffer chunk = BufferUtil.allocate(HttpGenerator.CHUNK_SIZE);
+        ByteBuffer content0 = BufferUtil.toBuffer("Hello World! ");
+        ByteBuffer content1 = BufferUtil.toBuffer("The quick brown fox jumped over the lazy dog. ");
+        HttpGenerator gen = new HttpGenerator();
+
+        HttpGenerator.Result result = gen.generateResponse(null, null, null, content0, false);
+        assertEquals(HttpGenerator.Result.NEED_INFO, result);
+        assertEquals(HttpGenerator.State.START, gen.getState());
+
+        ResponseInfo info = new ResponseInfo(HttpVersion.HTTP_1_1, new HttpFields(), -1, 200, null, false);
+        info.getHttpFields().add("Last-Modified", DateGenerator.__01Jan1970);
+        result = gen.generateResponse(info, null, null, content0, false);
+        assertEquals(HttpGenerator.Result.NEED_HEADER, result);
+        assertEquals(HttpGenerator.State.START, gen.getState());
+
+        result = gen.generateResponse(info, header, null, content0, false);
+        assertEquals(HttpGenerator.Result.FLUSH, result);
+        assertEquals(HttpGenerator.State.COMMITTED, gen.getState());
+
+        String out = BufferUtil.toString(header);
+        BufferUtil.clear(header);
+        out += BufferUtil.toString(content0);
+        BufferUtil.clear(content0);
+
+        result = gen.generateResponse(null, null, chunk, content1, false);
+        assertEquals(HttpGenerator.Result.FLUSH, result);
+        assertEquals(HttpGenerator.State.COMMITTED, gen.getState());
+        out += BufferUtil.toString(chunk);
+        BufferUtil.clear(chunk);
+        out += BufferUtil.toString(content1);
+        BufferUtil.clear(content1);
+
+        result = gen.generateResponse(null, null, chunk, null, true);
+        assertEquals(HttpGenerator.Result.CONTINUE, result);
+        assertEquals(HttpGenerator.State.COMPLETING, gen.getState());
+
+        result = gen.generateResponse(null, null, chunk, null, true);
+        assertEquals(HttpGenerator.Result.FLUSH, result);
+        assertEquals(HttpGenerator.State.COMPLETING, gen.getState());
+        out += BufferUtil.toString(chunk);
+        BufferUtil.clear(chunk);
+
+        result = gen.generateResponse(null, null, chunk, null, true);
+        assertEquals(HttpGenerator.Result.DONE, result);
+        assertEquals(HttpGenerator.State.END, gen.getState());
+
+        assertThat(out, containsString("HTTP/1.1 200 OK"));
+        assertThat(out, containsString("Last-Modified: Thu, 01 Jan 1970 00:00:00 GMT"));
+        assertThat(out, not(containsString("Content-Length")));
+        assertThat(out, containsString("Transfer-Encoding: chunked"));
+        assertThat(out, containsString("\r\n\r\nD\r\n"));
+        assertThat(out, containsString("\r\nHello World! \r\n"));
+        assertThat(out, containsString("\r\n2E\r\n"));
+        assertThat(out, containsString("\r\nThe quick brown fox jumped over the lazy dog. \r\n"));
+        assertThat(out, containsString("\r\n0\r\n"));
+    }
+
+    @Test
+    public void testResponseWithKnownContent() throws Exception
+    {
+        ByteBuffer header = BufferUtil.allocate(4096);
+        ByteBuffer content0 = BufferUtil.toBuffer("Hello World! ");
+        ByteBuffer content1 = BufferUtil.toBuffer("The quick brown fox jumped over the lazy dog. ");
+        HttpGenerator gen = new HttpGenerator();
+
+        HttpGenerator.Result result = gen.generateResponse(null, null, null, content0, false);
+        assertEquals(HttpGenerator.Result.NEED_INFO, result);
+        assertEquals(HttpGenerator.State.START, gen.getState());
+
+        ResponseInfo info = new ResponseInfo(HttpVersion.HTTP_1_1, new HttpFields(), 59, 200, null, false);
+        info.getHttpFields().add("Last-Modified", DateGenerator.__01Jan1970);
+        result = gen.generateResponse(info, null, null, content0, false);
+        assertEquals(HttpGenerator.Result.NEED_HEADER, result);
+        assertEquals(HttpGenerator.State.START, gen.getState());
+
+        result = gen.generateResponse(info, header, null, content0, false);
+        assertEquals(HttpGenerator.Result.FLUSH, result);
+        assertEquals(HttpGenerator.State.COMMITTED, gen.getState());
+
+        String out = BufferUtil.toString(header);
+        BufferUtil.clear(header);
+        out += BufferUtil.toString(content0);
+        BufferUtil.clear(content0);
+
+        result = gen.generateResponse(null, null, null, content1, false);
+        assertEquals(HttpGenerator.Result.FLUSH, result);
+        assertEquals(HttpGenerator.State.COMMITTED, gen.getState());
+        out += BufferUtil.toString(content1);
+        BufferUtil.clear(content1);
+
+        result = gen.generateResponse(null, null, null, null, true);
+        assertEquals(HttpGenerator.Result.CONTINUE, result);
+        assertEquals(HttpGenerator.State.COMPLETING, gen.getState());
+
+        result = gen.generateResponse(null, null, null, null, true);
+        assertEquals(HttpGenerator.Result.DONE, result);
+        assertEquals(HttpGenerator.State.END, gen.getState());
+
+        assertThat(out, containsString("HTTP/1.1 200 OK"));
+        assertThat(out, containsString("Last-Modified: Thu, 01 Jan 1970 00:00:00 GMT"));
+        assertThat(out, not(containsString("chunked")));
+        assertThat(out, containsString("Content-Length: 59"));
+        assertThat(out, containsString("\r\n\r\nHello World! The quick brown fox jumped over the lazy dog. "));
+    }
+
+    @Test
+    public void test100ThenResponseWithContent() throws Exception
+    {
+        ByteBuffer header = BufferUtil.allocate(4096);
+        ByteBuffer content0 = BufferUtil.toBuffer("Hello World! ");
+        ByteBuffer content1 = BufferUtil.toBuffer("The quick brown fox jumped over the lazy dog. ");
+        HttpGenerator gen = new HttpGenerator();
+
+        HttpGenerator.Result result = gen.generateResponse(HttpGenerator.CONTINUE_100_INFO, null, null, null, false);
+        assertEquals(HttpGenerator.Result.NEED_HEADER, result);
+        assertEquals(HttpGenerator.State.START, gen.getState());
+
+        result = gen.generateResponse(HttpGenerator.CONTINUE_100_INFO, header, null, null, false);
+        assertEquals(HttpGenerator.Result.FLUSH, result);
+        assertEquals(HttpGenerator.State.COMPLETING_1XX, gen.getState());
+        String out = BufferUtil.toString(header);
+
+        result = gen.generateResponse(null, null, null, null, false);
+        assertEquals(HttpGenerator.Result.DONE, result);
+        assertEquals(HttpGenerator.State.START, gen.getState());
+
+        assertThat(out, containsString("HTTP/1.1 100 Continue"));
+
+        result = gen.generateResponse(null, null, null, content0, false);
+        assertEquals(HttpGenerator.Result.NEED_INFO, result);
+        assertEquals(HttpGenerator.State.START, gen.getState());
+
+        ResponseInfo info = new ResponseInfo(HttpVersion.HTTP_1_1, new HttpFields(), 59, 200, null, false);
+        info.getHttpFields().add("Last-Modified", DateGenerator.__01Jan1970);
+        result = gen.generateResponse(info, null, null, content0, false);
+        assertEquals(HttpGenerator.Result.NEED_HEADER, result);
+        assertEquals(HttpGenerator.State.START, gen.getState());
+
+        result = gen.generateResponse(info, header, null, content0, false);
+        assertEquals(HttpGenerator.Result.FLUSH, result);
+        assertEquals(HttpGenerator.State.COMMITTED, gen.getState());
+
+        out = BufferUtil.toString(header);
+        BufferUtil.clear(header);
+        out += BufferUtil.toString(content0);
+        BufferUtil.clear(content0);
+
+        result = gen.generateResponse(null, null, null, content1, false);
+        assertEquals(HttpGenerator.Result.FLUSH, result);
+        assertEquals(HttpGenerator.State.COMMITTED, gen.getState());
+        out += BufferUtil.toString(content1);
+        BufferUtil.clear(content1);
+
+        result = gen.generateResponse(null, null, null, null, true);
+        assertEquals(HttpGenerator.Result.CONTINUE, result);
+        assertEquals(HttpGenerator.State.COMPLETING, gen.getState());
+
+        result = gen.generateResponse(null, null, null, null, true);
+        assertEquals(HttpGenerator.Result.DONE, result);
+        assertEquals(HttpGenerator.State.END, gen.getState());
+
+        assertThat(out, containsString("HTTP/1.1 200 OK"));
+        assertThat(out, containsString("Last-Modified: Thu, 01 Jan 1970 00:00:00 GMT"));
+        assertThat(out, not(containsString("chunked")));
+        assertThat(out, containsString("Content-Length: 59"));
+        assertThat(out, containsString("\r\n\r\nHello World! The quick brown fox jumped over the lazy dog. "));
+    }
+
+    @Test
+    public void testConnectionKeepAliveWithAdditionalCustomValue() throws Exception
+    {
+        HttpGenerator generator = new HttpGenerator();
+
+        HttpFields fields = new HttpFields();
+        fields.put(HttpHeader.CONNECTION, HttpHeaderValue.KEEP_ALIVE);
+        String customValue = "test";
+        fields.add(HttpHeader.CONNECTION, customValue);
+        ResponseInfo info = new ResponseInfo(HttpVersion.HTTP_1_0, fields, -1, 200, "OK", false);
+        ByteBuffer header = BufferUtil.allocate(4096);
+        HttpGenerator.Result result = generator.generateResponse(info, header, null, null, true);
+        Assert.assertSame(HttpGenerator.Result.FLUSH, result);
+        String headers = BufferUtil.toString(header);
+        Assert.assertTrue(headers.contains(HttpHeaderValue.KEEP_ALIVE.asString()));
+        Assert.assertTrue(headers.contains(customValue));
+    }
+}
diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpGeneratorTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpGeneratorTest.java
deleted file mode 100644
index 0892b55..0000000
--- a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpGeneratorTest.java
+++ /dev/null
@@ -1,313 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.http;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import java.io.IOException;
-
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.ByteArrayBuffer;
-import org.eclipse.jetty.io.ByteArrayEndPoint;
-import org.eclipse.jetty.io.SimpleBuffers;
-import org.eclipse.jetty.io.View;
-import org.junit.Test;
-
-/**
- *
- *
- * To change the template for this generated type comment go to
- * Window - Preferences - Java - Code Generation - Code and Comments
- */
-public class HttpGeneratorTest
-{
-    public final static String CONTENT="The quick brown fox jumped over the lazy dog.\nNow is the time for all good men to come to the aid of the party\nThe moon is blue to a fish in love.\n";
-    public final static String[] connect={null,"keep-alive","close","TE, close"};
-
-    @Test
-    public void testRequest() throws Exception
-    {
-        Buffer bb=new ByteArrayBuffer(8096);
-        Buffer sb=new ByteArrayBuffer(1500);
-        HttpFields fields = new HttpFields();
-        ByteArrayEndPoint endp = new ByteArrayEndPoint(new byte[0],4096);
-        HttpGenerator hg = new HttpGenerator(new SimpleBuffers(sb,bb),endp);
-
-        fields.add("Host","something");
-        fields.add("User-Agent","test");
-
-        hg.setRequest("GET","/index.html");
-        hg.setVersion(11);
-        hg.completeHeader(fields,true);
-        hg.complete();
-
-        assertTrue(endp.getOut().toString().indexOf("GET /index.html HTTP/1.1")==0);
-        assertTrue(endp.getOut().toString().indexOf("Content-Length")==-1);
-    }
-
-    @Test
-    public void testHTTP() throws Exception
-    {
-        Buffer bb=new ByteArrayBuffer(8096);
-        Buffer sb=new ByteArrayBuffer(1500);
-        HttpFields fields = new HttpFields();
-        ByteArrayEndPoint endp = new ByteArrayEndPoint(new byte[0],4096);
-        HttpGenerator hb = new HttpGenerator(new SimpleBuffers(sb,bb),endp);
-        Handler handler = new Handler();
-        HttpParser parser=null;
-
-        
-        
-        // For HTTP version
-        for (int v=9;v<=11;v++)
-        {
-            // For each test result
-            for (int r=0;r<tr.length;r++)
-            {
-                // chunks = 1 to 3
-                for (int chunks=1;chunks<=6;chunks++)
-                {
-                    // For none, keep-alive, close
-                    for (int c=0;c<(v==11?connect.length:(connect.length-1));c++)
-                    {
-                        String t="v="+v+",r="+r+",chunks="+chunks+",connect="+c+",tr="+tr[r];
-                        // System.err.println(t);
-
-                        hb.reset();
-                        endp.reset();
-                        fields.clear();
-
-                        tr[r].build(v,hb,"OK\r\nTest",connect[c],null,chunks, fields);
-                        String response=endp.getOut().toString();
-                        //System.out.println("RESPONSE: "+t+"\n"+response+(hb.isPersistent()?"...\n":"---\n"));
-
-                        if (v==9)
-                        {
-                            assertFalse(t,hb.isPersistent());
-                            if (tr[r]._body!=null)
-                                assertEquals(t,tr[r]._body, response);
-                            continue;
-                        }
-
-                        parser=new HttpParser(new ByteArrayBuffer(response.getBytes()), handler);
-                        parser.setHeadResponse(tr[r]._head);
-                                
-                        try
-                        {
-                            parser.parse();
-                        }
-                        catch(IOException e)
-                        {
-                            if (tr[r]._body!=null)
-                                throw new Exception(t,e);
-                            continue;
-                        }
-
-                        if (tr[r]._body!=null)
-                            assertEquals(t,tr[r]._body, this.content);
-                        
-                        if (v==10)
-                            assertTrue(t,hb.isPersistent() || tr[r]._contentLength==null || c==2 || c==0);
-                        else
-                            assertTrue(t,hb.isPersistent() ||  c==2 || c==3);
-
-                        if (v>9)
-                            assertEquals("OK  Test",f2);
-
-                        if (content==null)
-                            assertTrue(t,tr[r]._body==null);
-                        else
-                            assertTrue(t,tr[r]._contentLength==null || content.length()==Integer.parseInt(tr[r]._contentLength));
-                    }
-                }
-            }
-        }
-    }
-
-    private static final String[] headers= { "Content-Type","Content-Length","Connection","Transfer-Encoding","Other"};
-    private static class TR
-    {
-        private int _code;
-        private String _body;
-        private boolean _head;
-        String _contentType;
-        String _contentLength;
-        String _connection;
-        String _te;
-        String _other;
-
-        private TR(int code,String contentType, String contentLength ,String content,boolean head)
-        {
-            _code=code;
-            _contentType=contentType;
-            _contentLength=contentLength;
-            _other="value";
-            _body=content;
-            _head=head;
-        }
-
-        private void build(int version,HttpGenerator hb,String reason, String connection, String te, int chunks, HttpFields fields) throws Exception
-        {
-            _connection=connection;
-            _te=te;
-            hb.setVersion(version);
-            hb.setResponse(_code,reason);
-            hb.setHead(_head);
-           
-            if (_contentType!=null)
-                fields.put(new ByteArrayBuffer("Content-Type"),new ByteArrayBuffer(_contentType));
-            if (_contentLength!=null)
-                fields.put(new ByteArrayBuffer("Content-Length"),new ByteArrayBuffer(_contentLength));
-            if (_connection!=null)
-                fields.put(new ByteArrayBuffer("Connection"),new ByteArrayBuffer(_connection));
-            if (_te!=null)
-                fields.put(new ByteArrayBuffer("Transfer-Encoding"),new ByteArrayBuffer(_te));
-            if (_other!=null)
-                fields.put(new ByteArrayBuffer("Other"),new ByteArrayBuffer(_other));
-            
-            if (_body!=null)
-            {
-                int inc=1+_body.length()/chunks;
-                Buffer buf=new ByteArrayBuffer(_body);
-                View view = new View(buf);
-                for (int i=1;i<chunks;i++)
-                {
-                    view.setPutIndex(i*inc);
-                    view.setGetIndex((i-1)*inc);
-                    hb.addContent(view,Generator.MORE);
-                    if (hb.isBufferFull() && hb.isState(AbstractGenerator.STATE_HEADER))
-                        hb.completeHeader(fields, Generator.MORE);
-                    if (i%2==0)
-                    {
-                        if (hb.isState(AbstractGenerator.STATE_HEADER))
-                            hb.completeHeader(fields, Generator.MORE);
-                        hb.flushBuffer();
-                    }
-                }
-                view.setPutIndex(buf.putIndex());
-                view.setGetIndex((chunks-1)*inc);
-                hb.addContent(view,Generator.LAST);
-                if(hb.isState(AbstractGenerator.STATE_HEADER))
-                    hb.completeHeader(fields, Generator.LAST);
-            }
-            else
-            {
-                hb.completeHeader(fields, Generator.LAST);
-            }
-            hb.complete();
-            
-            while(!hb.isComplete())
-                hb.flushBuffer();
-        }
-
-        @Override
-        public String toString()
-        {
-            return "["+_code+","+_contentType+","+_contentLength+","+(_body==null?"null":"content")+"]";
-        }
-    }
-
-    private final TR[] tr =
-    {
-      /* 0 */  new TR(200,null,null,null,false),
-      /* 1 */  new TR(200,null,null,CONTENT,false),
-      /* 2 */  new TR(200,null,""+CONTENT.length(),null,true),
-      /* 3 */  new TR(200,null,""+CONTENT.length(),CONTENT,false),
-      /* 4 */  new TR(200,"text/html",null,null,true),
-      /* 5 */  new TR(200,"text/html",null,CONTENT,false),
-      /* 6 */  new TR(200,"text/html",""+CONTENT.length(),null,true),
-      /* 7 */  new TR(200,"text/html",""+CONTENT.length(),CONTENT,false),
-    };
-
-    private String content;
-    private String f0;
-    private String f1;
-    private String f2;
-    private String[] hdr;
-    private String[] val;
-    private int h;
-
-    private class Handler extends HttpParser.EventHandler
-    {
-        private int index=0;
-
-        @Override
-        public void content(Buffer ref)
-        {
-            if (index == 0)
-                content= "";
-            content= content.substring(0, index) + ref;
-            index+=ref.length();
-        }
-
-        @Override
-        public void startRequest(Buffer tok0, Buffer tok1, Buffer tok2)
-        {
-            h= -1;
-            hdr= new String[9];
-            val= new String[9];
-            f0= tok0.toString();
-            f1= tok1.toString();
-            if (tok2!=null)
-                f2= tok2.toString();
-            else
-                f2=null;
-            index=0;
-            // System.out.println(f0+" "+f1+" "+f2);
-        }
-
-        /* (non-Javadoc)
-         * @see org.eclipse.jetty.EventHandler#startResponse(org.eclipse.io.Buffer, int, org.eclipse.io.Buffer)
-         */
-        @Override
-        public void startResponse(Buffer version, int status, Buffer reason)
-        {
-            h= -1;
-            hdr= new String[9];
-            val= new String[9];
-            f0= version.toString();
-            f1= ""+status;
-            if (reason!=null)
-                f2= reason.toString();
-            else
-                f2=null;
-            index=0;
-        }
-
-        @Override
-        public void parsedHeader(Buffer name,Buffer value)
-        {
-            hdr[++h]= name.toString();
-            val[h]= value.toString();
-        }
-
-        @Override
-        public void headerComplete()
-        {
-            content= null;
-        }
-
-        @Override
-        public void messageComplete(long contentLength)
-        {
-        }
-    }
-}
diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpParserTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpParserTest.java
index 894b1ba..c9abb0d 100644
--- a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpParserTest.java
+++ b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpParserTest.java
@@ -18,18 +18,18 @@
 
 package org.eclipse.jetty.http;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import java.io.UnsupportedEncodingException;
-
-import junit.framework.Assert;
-
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.ByteArrayBuffer;
-import org.eclipse.jetty.io.SimpleBuffers;
-import org.eclipse.jetty.io.bio.StringEndPoint;
-import org.eclipse.jetty.util.StringUtil;
+import static org.junit.Assert.*;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jetty.http.HttpParser.State;
+import org.eclipse.jetty.util.BufferUtil;
+import org.hamcrest.Matchers;
+import org.junit.Assert;
+import org.junit.Before;
 import org.junit.Test;
 
 /**
@@ -37,686 +37,1677 @@ import org.junit.Test;
  */
 public class HttpParserTest
 {
+    /* ------------------------------------------------------------------------------- */
+    /**
+     * Parse until {@link State#END} state.
+     * If the parser is already in the END state, then it is {@link HttpParser#reset()} and re-parsed.
+     * @param parser The parser to test
+     * @throws IllegalStateException If the buffers have already been partially parsed.
+     */
+    public static void parseAll(HttpParser parser, ByteBuffer buffer)
+    {
+        if (parser.isState(State.END))
+            parser.reset();
+        if (!parser.isState(State.START))
+            throw new IllegalStateException("!START");
+
+        // continue parsing
+        int remaining=buffer.remaining();
+        while (!parser.isState(State.END) && remaining>0)
+        {
+            int was_remaining=remaining;
+            parser.parseNext(buffer);
+            remaining=buffer.remaining();
+            if (remaining==was_remaining)
+                break;
+        }
+    }
+
+    @Test
+    public void HttpMethodTest()
+    {
+        assertNull(HttpMethod.lookAheadGet(BufferUtil.toBuffer("Wibble ")));
+        assertNull(HttpMethod.lookAheadGet(BufferUtil.toBuffer("GET")));
+        assertNull(HttpMethod.lookAheadGet(BufferUtil.toBuffer("MO")));
+        
+        assertEquals(HttpMethod.GET,HttpMethod.lookAheadGet(BufferUtil.toBuffer("GET ")));
+        assertEquals(HttpMethod.MOVE,HttpMethod.lookAheadGet(BufferUtil.toBuffer("MOVE ")));
+        
+        ByteBuffer b = BufferUtil.allocateDirect(128);
+        BufferUtil.append(b,BufferUtil.toBuffer("GET"));
+        assertNull(HttpMethod.lookAheadGet(b));
+        
+        BufferUtil.append(b,BufferUtil.toBuffer(" "));
+        assertEquals(HttpMethod.GET,HttpMethod.lookAheadGet(b));
+    }
+    
+    
     @Test
     public void testLineParse0() throws Exception
     {
-        StringEndPoint io=new StringEndPoint();
-        io.setInput("POST /foo HTTP/1.0\015\012" + "\015\012");
-        ByteArrayBuffer buffer= new ByteArrayBuffer(4096);
-        SimpleBuffers buffers=new SimpleBuffers(buffer,null);
-
-        Handler handler = new Handler();
-        HttpParser parser= new HttpParser(buffers,io, handler);
-        parser.parse();
-        assertEquals("POST", f0);
-        assertEquals("/foo", f1);
-        assertEquals("HTTP/1.0", f2);
-        assertEquals(-1, h);
+        ByteBuffer buffer= BufferUtil.toBuffer("POST /foo HTTP/1.0\015\012" + "\015\012");
+
+        HttpParser.RequestHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
+        parseAll(parser,buffer);
+        assertEquals("POST", _methodOrVersion);
+        assertEquals("/foo", _uriOrStatus);
+        assertEquals("HTTP/1.0", _versionOrReason);
+        assertEquals(-1, _headers);
     }
 
     @Test
     public void testLineParse1() throws Exception
     {
-        StringEndPoint io=new StringEndPoint();
-        io.setInput("GET /999\015\012");
-        ByteArrayBuffer buffer= new ByteArrayBuffer(4096);
-        SimpleBuffers buffers=new SimpleBuffers(buffer,null);
-
-        f2= null;
-        Handler handler = new Handler();
-        HttpParser parser= new HttpParser(buffers,io, handler);
-        parser.parse();
-        assertEquals("GET", f0);
-        assertEquals("/999", f1);
-        assertEquals(null, f2);
-        assertEquals(-1, h);
+        ByteBuffer buffer= BufferUtil.toBuffer("GET /999\015\012");
+
+        _versionOrReason= null;
+        HttpParser.RequestHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
+        parseAll(parser,buffer);
+        assertEquals("GET", _methodOrVersion);
+        assertEquals("/999", _uriOrStatus);
+        assertEquals(null, _versionOrReason);
+        assertEquals(-1, _headers);
     }
 
     @Test
     public void testLineParse2() throws Exception
     {
-        StringEndPoint io=new StringEndPoint();
-        io.setInput("POST /222  \015\012");
-        ByteArrayBuffer buffer= new ByteArrayBuffer(4096);
-        SimpleBuffers buffers=new SimpleBuffers(buffer,null);
-
-        f2= null;
-        Handler handler = new Handler();
-        HttpParser parser= new HttpParser(buffers,io, handler);
-        parser.parse();
-        assertEquals("POST", f0);
-        assertEquals("/222", f1);
-        assertEquals(null, f2);
-        assertEquals(-1, h);
+        ByteBuffer buffer= BufferUtil.toBuffer("POST /222  \015\012");
+
+        _versionOrReason= null;
+        HttpParser.RequestHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
+        parseAll(parser,buffer);
+        assertEquals("POST", _methodOrVersion);
+        assertEquals("/222", _uriOrStatus);
+        assertEquals(null, _versionOrReason);
+        assertEquals(-1, _headers);
     }
 
     @Test
     public void testLineParse3() throws Exception
     {
-        StringEndPoint io=new StringEndPoint();
-        io.setInput("POST /fo\u0690 HTTP/1.0\015\012" + "\015\012");
-        ByteArrayBuffer buffer= new ByteArrayBuffer(4096);
-        SimpleBuffers buffers=new SimpleBuffers(buffer,null);
-
-        Handler handler = new Handler();
-        HttpParser parser= new HttpParser(buffers,io, handler);
-        parser.parse();
-        assertEquals("POST", f0);
-        assertEquals("/fo\u0690", f1);
-        assertEquals("HTTP/1.0", f2);
-        assertEquals(-1, h);
+        ByteBuffer buffer= BufferUtil.toBuffer("POST /fo\u0690 HTTP/1.0\015\012" + "\015\012",StandardCharsets.UTF_8);
+
+        HttpParser.RequestHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
+        parseAll(parser,buffer);
+        assertEquals("POST", _methodOrVersion);
+        assertEquals("/fo\u0690", _uriOrStatus);
+        assertEquals("HTTP/1.0", _versionOrReason);
+        assertEquals(-1, _headers);
     }
 
     @Test
     public void testLineParse4() throws Exception
     {
-        StringEndPoint io=new StringEndPoint();
-        io.setInput("POST /foo?param=\u0690 HTTP/1.0\015\012" + "\015\012");
-        ByteArrayBuffer buffer= new ByteArrayBuffer(4096);
-        SimpleBuffers buffers=new SimpleBuffers(buffer,null);
-
-        Handler handler = new Handler();
-        HttpParser parser= new HttpParser(buffers,io, handler);
-        parser.parse();
-        assertEquals("POST", f0);
-        assertEquals("/foo?param=\u0690", f1);
-        assertEquals("HTTP/1.0", f2);
-        assertEquals(-1, h);
+        ByteBuffer buffer= BufferUtil.toBuffer("POST /foo?param=\u0690 HTTP/1.0\015\012" + "\015\012",StandardCharsets.UTF_8);
+
+        HttpParser.RequestHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
+        parseAll(parser,buffer);
+        assertEquals("POST", _methodOrVersion);
+        assertEquals("/foo?param=\u0690", _uriOrStatus);
+        assertEquals("HTTP/1.0", _versionOrReason);
+        assertEquals(-1, _headers);
     }
 
     @Test
+    public void testLongURLParse() throws Exception
+    {
+        ByteBuffer buffer= BufferUtil.toBuffer("POST /123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/ HTTP/1.0\015\012" + "\015\012");
+
+        HttpParser.RequestHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
+        parseAll(parser,buffer);
+        assertEquals("POST", _methodOrVersion);
+        assertEquals("/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/", _uriOrStatus);
+        assertEquals("HTTP/1.0", _versionOrReason);
+        assertEquals(-1, _headers);
+    }
+    
+    @Test
     public void testConnect() throws Exception
     {
-        StringEndPoint io=new StringEndPoint();
-        io.setInput("CONNECT 192.168.1.2:80 HTTP/1.1\015\012" + "\015\012");
-        ByteArrayBuffer buffer= new ByteArrayBuffer(4096);
-        SimpleBuffers buffers=new SimpleBuffers(buffer,null);
-
-        Handler handler = new Handler();
-        HttpParser parser= new HttpParser(buffers,io, handler);
-        parser.parse();
-        assertTrue(handler.request);
-        assertEquals("CONNECT", f0);
-        assertEquals("192.168.1.2:80", f1);
-        assertEquals("HTTP/1.1", f2);
-        assertEquals(-1, h);
-    }
-
-    @Test
-    public void testHeaderParse() throws Exception
-    {
-        StringEndPoint io=new StringEndPoint();
-        io.setInput(
-            "GET / HTTP/1.0\015\012"
-                + "Host: localhost\015\012"
-                + "Header1: value1\015\012"
-                + "Header2  :   value 2a  \015\012"
-                + "                    value 2b  \015\012"
-                + "Header3: \015\012"
-                + "Header4 \015\012"
-                + "  value4\015\012"
-                + "Server5: notServer\015\012"
-                + "\015\012");
-        ByteArrayBuffer buffer= new ByteArrayBuffer(4096);
-        SimpleBuffers buffers=new SimpleBuffers(buffer,null);
-
-        Handler handler = new Handler();
-        HttpParser parser= new HttpParser(buffers,io, handler);
-        parser.parse();
-        assertEquals("GET", f0);
-        assertEquals("/", f1);
-        assertEquals("HTTP/1.0", f2);
-        assertEquals("Host", hdr[0]);
-        assertEquals("localhost", val[0]);
-        assertEquals("Header1", hdr[1]);
-        assertEquals("value1", val[1]);
-        assertEquals("Header2", hdr[2]);
-        assertEquals("value 2a value 2b", val[2]);
-        assertEquals("Header3", hdr[3]);
-        assertEquals("", val[3]);
-        assertEquals("Header4", hdr[4]);
-        assertEquals("value4", val[4]);
-        assertEquals("Server5", hdr[5]);
-        assertEquals("notServer", val[5]);
-        assertEquals(5, h);
+        ByteBuffer buffer= BufferUtil.toBuffer("CONNECT 192.168.1.2:80 HTTP/1.1\015\012" + "\015\012");
+        HttpParser.RequestHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
+        parseAll(parser,buffer);
+        assertEquals("CONNECT", _methodOrVersion);
+        assertEquals("192.168.1.2:80", _uriOrStatus);
+        assertEquals("HTTP/1.1", _versionOrReason);
+        assertEquals(-1, _headers);
     }
 
     @Test
+    public void testSimple() throws Exception
+    {
+        ByteBuffer buffer= BufferUtil.toBuffer(
+                "GET / HTTP/1.0\015\012" +
+                "Host: localhost\015\012" +
+                "Connection: close\015\012" +
+                "\015\012");
+        
+        HttpParser.RequestHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
+        parseAll(parser,buffer);
+
+        assertTrue(_headerCompleted);
+        assertTrue(_messageCompleted);
+        assertEquals("GET", _methodOrVersion);
+        assertEquals("/", _uriOrStatus);
+        assertEquals("HTTP/1.0", _versionOrReason);
+        assertEquals("Host", _hdr[0]);
+        assertEquals("localhost", _val[0]);
+        assertEquals("Connection", _hdr[1]);
+        assertEquals("close", _val[1]);
+        assertEquals(1, _headers);
+    }
+
+    @Test
+    public void testHeaderParseDirect() throws Exception
+    {
+        ByteBuffer b0= BufferUtil.toBuffer(
+                "GET / HTTP/1.0\015\012" +
+                        "Host: localhost\015\012" +
+                        "Header1: value1\015\012" +
+                        "Header 2  :   value 2a  \015\012" +
+                        "    value 2b  \015\012" +
+                        "Header3: \015\012" +
+                        "Header4 \015\012" +
+                        "  value4\015\012" +
+                        "Server5 : notServer\015\012" +
+                        "Host Header: notHost\015\012" +
+                        "Connection: close\015\012" +
+                        "Accept-Encoding: gzip, deflated\015\012" +
+                        "Accept: unknown\015\012" +
+                "\015\012");
+        ByteBuffer buffer = BufferUtil.allocateDirect(b0.capacity());
+        int pos=BufferUtil.flipToFill(buffer);
+        BufferUtil.put(b0,buffer);
+        BufferUtil.flipToFlush(buffer,pos);
+        
+        HttpParser.RequestHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
+        parseAll(parser,buffer);
+
+        assertEquals("GET", _methodOrVersion);
+        assertEquals("/", _uriOrStatus);
+        assertEquals("HTTP/1.0", _versionOrReason);
+        assertEquals("Host", _hdr[0]);
+        assertEquals("localhost", _val[0]);
+        assertEquals("Header1", _hdr[1]);
+        assertEquals("value1", _val[1]);
+        assertEquals("Header 2", _hdr[2]);
+        assertEquals("value 2a value 2b", _val[2]);
+        assertEquals("Header3", _hdr[3]);
+        assertEquals(null, _val[3]);
+        assertEquals("Header4", _hdr[4]);
+        assertEquals("value4", _val[4]);
+        assertEquals("Server5", _hdr[5]);
+        assertEquals("notServer", _val[5]);
+        assertEquals("Host Header", _hdr[6]);
+        assertEquals("notHost", _val[6]);
+        assertEquals("Connection", _hdr[7]);
+        assertEquals("close", _val[7]);
+        assertEquals("Accept-Encoding", _hdr[8]);
+        assertEquals("gzip, deflated", _val[8]);
+        assertEquals("Accept", _hdr[9]);
+        assertEquals("unknown", _val[9]);
+        assertEquals(9, _headers);
+    }
+    
+    @Test
+    public void testHeaderParseCRLF() throws Exception
+    {
+        ByteBuffer buffer= BufferUtil.toBuffer(
+                "GET / HTTP/1.0\015\012" +
+                        "Host: localhost\015\012" +
+                        "Header1: value1\015\012" +
+                        "Header 2  :   value 2a  \015\012" +
+                        "    value 2b  \015\012" +
+                        "Header3: \015\012" +
+                        "Header4 \015\012" +
+                        "  value4\015\012" +
+                        "Server5 : notServer\015\012" +
+                        "Host Header: notHost\015\012" +
+                        "Connection: close\015\012" +
+                        "Accept-Encoding: gzip, deflated\015\012" +
+                        "Accept: unknown\015\012" +
+                "\015\012");
+        HttpParser.RequestHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
+        parseAll(parser,buffer);
+
+        assertEquals("GET", _methodOrVersion);
+        assertEquals("/", _uriOrStatus);
+        assertEquals("HTTP/1.0", _versionOrReason);
+        assertEquals("Host", _hdr[0]);
+        assertEquals("localhost", _val[0]);
+        assertEquals("Header1", _hdr[1]);
+        assertEquals("value1", _val[1]);
+        assertEquals("Header 2", _hdr[2]);
+        assertEquals("value 2a value 2b", _val[2]);
+        assertEquals("Header3", _hdr[3]);
+        assertEquals(null, _val[3]);
+        assertEquals("Header4", _hdr[4]);
+        assertEquals("value4", _val[4]);
+        assertEquals("Server5", _hdr[5]);
+        assertEquals("notServer", _val[5]);
+        assertEquals("Host Header", _hdr[6]);
+        assertEquals("notHost", _val[6]);
+        assertEquals("Connection", _hdr[7]);
+        assertEquals("close", _val[7]);
+        assertEquals("Accept-Encoding", _hdr[8]);
+        assertEquals("gzip, deflated", _val[8]);
+        assertEquals("Accept", _hdr[9]);
+        assertEquals("unknown", _val[9]);
+        assertEquals(9, _headers);
+    }
+
+    
+    
+    @Test
     public void testHeaderParseLF() throws Exception
     {
-        StringEndPoint io=new StringEndPoint();
-        io.setInput(
-            "GET / HTTP/1.0\012"
-                + "Host: localhost\012"
-                + "Header1: value1\012"
-                + "Header2  :   value 2a  \012"
-                + "                    value 2b  \012"
-                + "Header3: \012"
-                + "Header4 \012"
-                + "  value4\012"
-                + "Server5: notServer\012"
-                + "\012");
-        ByteArrayBuffer buffer= new ByteArrayBuffer(4096);
-        SimpleBuffers buffers=new SimpleBuffers(buffer,null);
-
-        Handler handler = new Handler();
-        HttpParser parser= new HttpParser(buffers,io, handler);
-        parser.parse();
-        assertEquals("GET", f0);
-        assertEquals("/", f1);
-        assertEquals("HTTP/1.0", f2);
-        assertEquals("Host", hdr[0]);
-        assertEquals("localhost", val[0]);
-        assertEquals("Header1", hdr[1]);
-        assertEquals("value1", val[1]);
-        assertEquals("Header2", hdr[2]);
-        assertEquals("value 2a value 2b", val[2]);
-        assertEquals("Header3", hdr[3]);
-        assertEquals("", val[3]);
-        assertEquals("Header4", hdr[4]);
-        assertEquals("value4", val[4]);
-        assertEquals("Server5", hdr[5]);
-        assertEquals("notServer", val[5]);
-        assertEquals(5, h);
-    }
-    
-    @Test
-    public void testHeaderParseCR() throws Exception
-    {
-        StringEndPoint io=new StringEndPoint();
-        io.setInput(
-            "GET / HTTP/1.0\015"
-                + "Host: localhost\015"
-                + "Header1: value1\015"
-                + "\015");
-        ByteArrayBuffer buffer= new ByteArrayBuffer(4096);
-        SimpleBuffers buffers=new SimpleBuffers(buffer,null);
-
-        Handler handler = new Handler();
-        HttpParser parser= new HttpParser(buffers,io, handler);
-        try
-        { 
-            parser.parse();
-            Assert.fail();
-        }
-        catch(HttpException e)
-        {
-            assertEquals(400,e._status);
-        }
+        ByteBuffer buffer= BufferUtil.toBuffer(
+                "GET / HTTP/1.0\n" +
+                        "Host: localhost\n" +
+                        "Header1: value1\n" +
+                        "Header 2  :   value 2a  \n" +
+                        "    value 2b  \n" +
+                        "Header3: \n" +
+                        "Header4 \n" +
+                        "  value4\n" +
+                        "Server5 : notServer\n" +
+                        "Host Header: notHost\n" +
+                        "Connection: close\n" +
+                        "Accept-Encoding: gzip, deflated\n" +
+                        "Accept: unknown\n" +
+                "\n");
+        HttpParser.RequestHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
+        parseAll(parser,buffer);
+
+        assertEquals("GET", _methodOrVersion);
+        assertEquals("/", _uriOrStatus);
+        assertEquals("HTTP/1.0", _versionOrReason);
+        assertEquals("Host", _hdr[0]);
+        assertEquals("localhost", _val[0]);
+        assertEquals("Header1", _hdr[1]);
+        assertEquals("value1", _val[1]);
+        assertEquals("Header 2", _hdr[2]);
+        assertEquals("value 2a value 2b", _val[2]);
+        assertEquals("Header3", _hdr[3]);
+        assertEquals(null, _val[3]);
+        assertEquals("Header4", _hdr[4]);
+        assertEquals("value4", _val[4]);
+        assertEquals("Server5", _hdr[5]);
+        assertEquals("notServer", _val[5]);
+        assertEquals("Host Header", _hdr[6]);
+        assertEquals("notHost", _val[6]);
+        assertEquals("Connection", _hdr[7]);
+        assertEquals("close", _val[7]);
+        assertEquals("Accept-Encoding", _hdr[8]);
+        assertEquals("gzip, deflated", _val[8]);
+        assertEquals("Accept", _hdr[9]);
+        assertEquals("unknown", _val[9]);
+        assertEquals(9, _headers);
+    }
+
+    @Test
+    public void testQuoted() throws Exception
+    {
+        ByteBuffer buffer= BufferUtil.toBuffer(
+                "GET / HTTP/1.0\n" +
+                        "Name0: \"value0\"\t\n" +
+                        "Name1: \"value\t1\"\n" +
+                        "Name2: \"value\t2A\",\"value,2B\"\t\n" +
+                "\n");
+        HttpParser.RequestHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
+        parseAll(parser,buffer);
+
+        assertEquals("GET", _methodOrVersion);
+        assertEquals("/", _uriOrStatus);
+        assertEquals("HTTP/1.0", _versionOrReason);
+        assertEquals("Name0", _hdr[0]);
+        assertEquals("\"value0\"", _val[0]);
+        assertEquals("Name1", _hdr[1]);
+        assertEquals("\"value\t1\"", _val[1]);
+        assertEquals("Name2", _hdr[2]);
+        assertEquals("\"value\t2A\",\"value,2B\"", _val[2]);
+        assertEquals(2, _headers);
+    }
+
+    @Test
+    public void testEncodedHeader() throws Exception
+    {
+        ByteBuffer buffer=BufferUtil.allocate(4096);
+        BufferUtil.flipToFill(buffer); 
+        BufferUtil.put(BufferUtil.toBuffer("GET "),buffer);
+        buffer.put("/foo/\u0690/".getBytes(StandardCharsets.UTF_8));
+        BufferUtil.put(BufferUtil.toBuffer(" HTTP/1.0\r\n"),buffer);
+        BufferUtil.put(BufferUtil.toBuffer("Header1: "),buffer);
+        buffer.put("\u00e6 \u00e6".getBytes(StandardCharsets.ISO_8859_1));
+        BufferUtil.put(BufferUtil.toBuffer("  \r\n\r\n"),buffer);
+        BufferUtil.flipToFlush(buffer,0);
+                    
+        HttpParser.RequestHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
+        parseAll(parser,buffer);
+
+        assertEquals("GET", _methodOrVersion);
+        assertEquals("/foo/\u0690/", _uriOrStatus);
+        assertEquals("HTTP/1.0", _versionOrReason);
+        assertEquals("Header1", _hdr[0]);
+        assertEquals("\u00e6 \u00e6", _val[0]);
+        assertEquals(0, _headers);
+        assertEquals(null,_bad);
+    }
+    
+    
+
+    @Test
+    public void testBadMethodEncoding() throws Exception
+    {
+        ByteBuffer buffer= BufferUtil.toBuffer(
+            "G\u00e6T / HTTP/1.0\r\nHeader0: value0\r\n\n\n");
         
-        io.setInput(
-            "GET / HTTP/1.0\r\n"
-                + "Host: localhost\r\r\n"
-                + "Header1: value1\r\n"
-                + "\r\n");
-
-        parser= new HttpParser(buffers,io, handler);
-        try
-        { 
-            parser.parse();
-            Assert.fail();
-        }
-        catch(HttpException e)
+        HttpParser.RequestHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
+        parseAll(parser,buffer);
+        assertThat(_bad,Matchers.notNullValue());
+    }
+
+    @Test
+    public void testBadVersionEncoding() throws Exception
+    {
+        ByteBuffer buffer= BufferUtil.toBuffer(
+            "GET / H\u00e6P/1.0\r\nHeader0: value0\r\n\n\n");
+        
+        HttpParser.RequestHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
+        parseAll(parser,buffer);
+        assertThat(_bad,Matchers.notNullValue());
+    }
+
+
+    @Test
+    public void testBadHeaderEncoding() throws Exception
+    {
+        ByteBuffer buffer= BufferUtil.toBuffer(
+            "GET / HTTP/1.0\r\nH\u00e6der0: value0\r\n\n\n");
+        
+        HttpParser.RequestHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
+        parseAll(parser,buffer);
+        assertThat(_bad,Matchers.notNullValue());
+    } 
+
+    @Test
+    public void testHeaderTab() throws Exception
+    {
+        ByteBuffer buffer= BufferUtil.toBuffer(
+            "GET / HTTP/1.1\r\n" +
+            "Host: localhost\r\n" +
+            "Header: value\talternate\r\n" +
+            "\n\n");
+        
+        HttpParser.RequestHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
+        parseAll(parser,buffer);
+        
+        assertEquals("GET", _methodOrVersion);
+        assertEquals("/", _uriOrStatus);
+        assertEquals("HTTP/1.1", _versionOrReason);
+        assertEquals("Host", _hdr[0]);
+        assertEquals("localhost", _val[0]);
+        assertEquals("Header", _hdr[1]);
+        assertEquals("value\talternate", _val[1]);
+    } 
+    
+    @Test
+    public void testNonStrict() throws Exception
+    {
+        ByteBuffer buffer= BufferUtil.toBuffer(
+                "get / http/1.0\015\012" +
+                "HOST: localhost\015\012" +
+                "cOnNeCtIoN: ClOsE\015\012"+
+                "\015\012");
+        HttpParser.RequestHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler,-1,false);
+        parseAll(parser,buffer);
+
+        assertEquals("GET", _methodOrVersion);
+        assertEquals("/", _uriOrStatus);
+        assertEquals("HTTP/1.0", _versionOrReason);
+        assertEquals("Host", _hdr[0]);
+        assertEquals("localhost", _val[0]);
+        assertEquals("Connection", _hdr[1]);
+        assertEquals("close", _val[1]);
+        assertEquals(1, _headers);
+    }
+    
+    @Test
+    public void testStrict() throws Exception
+    {
+        ByteBuffer buffer= BufferUtil.toBuffer(
+                "gEt / http/1.0\015\012" +
+                "HOST: localhost\015\012" +
+                "cOnNeCtIoN: ClOsE\015\012"+
+                "\015\012");
+        HttpParser.RequestHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler,-1,true);
+        parseAll(parser,buffer);
+
+        assertEquals("gEt", _methodOrVersion);
+        assertEquals("/", _uriOrStatus);
+        assertEquals("HTTP/1.0", _versionOrReason);
+        assertEquals("HOST", _hdr[0]);
+        assertEquals("localhost", _val[0]);
+        assertEquals("cOnNeCtIoN", _hdr[1]);
+        assertEquals("ClOsE", _val[1]);
+        assertEquals(1, _headers);
+    }
+    
+    @Test
+    public void testSplitHeaderParse() throws Exception
+    {
+        ByteBuffer buffer= BufferUtil.toBuffer(
+                "XXXXSPLIT / HTTP/1.0\015\012" +
+                    "Host: localhost\015\012" +
+                    "Header1: value1\015\012" +
+                    "Header2  :   value 2a  \015\012" +
+                    "                    value 2b  \015\012" +
+                    "Header3: \015\012" +
+                    "Header4 \015\012" +
+                    "  value4\015\012" +
+                    "Server5: notServer\015\012" +
+                    "\015\012ZZZZ");
+        buffer.position(2);
+        buffer.limit(buffer.capacity()-2);
+        buffer=buffer.slice();
+
+        for (int i=0;i<buffer.capacity()-4;i++)
         {
-            assertEquals(400,e._status);
+            HttpParser.RequestHandler<ByteBuffer> handler  = new Handler();
+            HttpParser parser= new HttpParser(handler);
+
+            // System.err.println(BufferUtil.toDetailString(buffer));
+            buffer.position(2);
+            buffer.limit(2+i);
+
+            if (!parser.parseNext(buffer))
+            {
+                // consumed all
+                assertEquals(0,buffer.remaining());
+
+                // parse the rest
+                buffer.limit(buffer.capacity()-2);
+                parser.parseNext(buffer);
+            }
+
+            assertEquals("SPLIT", _methodOrVersion);
+            assertEquals("/", _uriOrStatus);
+            assertEquals("HTTP/1.0", _versionOrReason);
+            assertEquals("Host", _hdr[0]);
+            assertEquals("localhost", _val[0]);
+            assertEquals("Header1", _hdr[1]);
+            assertEquals("value1", _val[1]);
+            assertEquals("Header2", _hdr[2]);
+            assertEquals("value 2a value 2b", _val[2]);
+            assertEquals("Header3", _hdr[3]);
+            assertEquals(null, _val[3]);
+            assertEquals("Header4", _hdr[4]);
+            assertEquals("value4", _val[4]);
+            assertEquals("Server5", _hdr[5]);
+            assertEquals("notServer", _val[5]);
+            assertEquals(5, _headers);
         }
-        
     }
 
+
     @Test
     public void testChunkParse() throws Exception
     {
-        StringEndPoint io=new StringEndPoint();
-        io.setInput(
-            "GET /chunk HTTP/1.0\015\012"
-                + "Header1: value1\015\012"
-                + "Transfer-Encoding: chunked\015\012"
-                + "\015\012"
-                + "a;\015\012"
-                + "0123456789\015\012"
-                + "1a\015\012"
-                + "ABCDEFGHIJKLMNOPQRSTUVWXYZ\015\012"
-                + "0\015\012");
-        ByteArrayBuffer buffer= new ByteArrayBuffer(4096);
-        ByteArrayBuffer content=new ByteArrayBuffer(8192);
-        SimpleBuffers buffers=new SimpleBuffers(buffer,content);
-
-        Handler handler = new Handler();
-        HttpParser parser= new HttpParser(buffers,io, handler);
-        parser.parse();
-        assertEquals("GET", f0);
-        assertEquals("/chunk", f1);
-        assertEquals("HTTP/1.0", f2);
-        assertEquals(1, h);
-        assertEquals("Header1", hdr[0]);
-        assertEquals("value1", val[0]);
+        ByteBuffer buffer= BufferUtil.toBuffer(
+                "GET /chunk HTTP/1.0\015\012"
+                        + "Header1: value1\015\012"
+                        + "Transfer-Encoding: chunked\015\012"
+                        + "\015\012"
+                        + "a;\015\012"
+                        + "0123456789\015\012"
+                        + "1a\015\012"
+                        + "ABCDEFGHIJKLMNOPQRSTUVWXYZ\015\012"
+                        + "0\015\012"
+                        + "\015\012");
+        HttpParser.RequestHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
+        parseAll(parser,buffer);
+
+        assertEquals("GET", _methodOrVersion);
+        assertEquals("/chunk", _uriOrStatus);
+        assertEquals("HTTP/1.0", _versionOrReason);
+        assertEquals(1, _headers);
+        assertEquals("Header1", _hdr[0]);
+        assertEquals("value1", _val[0]);
         assertEquals("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ", _content);
+
+        assertTrue(_headerCompleted);
+        assertTrue(_messageCompleted);
+    }
+
+    @Test
+    public void testStartEOF() throws Exception
+    {
+        HttpParser.RequestHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
+        parser.atEOF();
+        parser.parseNext(BufferUtil.EMPTY_BUFFER);
+
+        assertTrue(_early);
+        assertEquals(null,_bad);
+    }
+
+    @Test
+    public void testEarlyEOF() throws Exception
+    {
+        ByteBuffer buffer= BufferUtil.toBuffer(
+                "GET /uri HTTP/1.0\015\012"
+                        + "Content-Length: 20\015\012"
+                        + "\015\012"
+                        + "0123456789");
+        HttpParser.RequestHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
+        parser.atEOF();
+        parseAll(parser,buffer);
+
+        assertEquals("GET", _methodOrVersion);
+        assertEquals("/uri", _uriOrStatus);
+        assertEquals("HTTP/1.0", _versionOrReason);
+        assertEquals("0123456789", _content);
+        
+        assertTrue(_early);
+    }
+
+    @Test
+    public void testChunkEarlyEOF() throws Exception
+    {
+        ByteBuffer buffer= BufferUtil.toBuffer(
+                "GET /chunk HTTP/1.0\015\012"
+                        + "Header1: value1\015\012"
+                        + "Transfer-Encoding: chunked\015\012"
+                        + "\015\012"
+                        + "a;\015\012"
+                        + "0123456789\015\012");
+        HttpParser.RequestHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
+        parser.atEOF();
+        parseAll(parser,buffer);
+
+        assertEquals("GET", _methodOrVersion);
+        assertEquals("/chunk", _uriOrStatus);
+        assertEquals("HTTP/1.0", _versionOrReason);
+        assertEquals(1, _headers);
+        assertEquals("Header1", _hdr[0]);
+        assertEquals("value1", _val[0]);
+        assertEquals("0123456789", _content);
+        
+        assertTrue(_early);
+        
     }
 
+    
     @Test
     public void testMultiParse() throws Exception
     {
-        StringEndPoint io=new StringEndPoint();
-        io.setInput(
-            "GET /mp HTTP/1.0\015\012"
-                + "Connection: Keep-Alive\015\012"
-                + "Header1: value1\015\012"
-                + "Transfer-Encoding: chunked\015\012"
-                + "\015\012"
-                + "a;\015\012"
-                + "0123456789\015\012"
-                + "1a\015\012"
-                + "ABCDEFGHIJKLMNOPQRSTUVWXYZ\015\012"
-                + "0\015\012"
-                + "POST /foo HTTP/1.0\015\012"
-                + "Connection: Keep-Alive\015\012"
-                + "Header2: value2\015\012"
-                + "Content-Length: 0\015\012"
-                + "\015\012"
-                + "PUT /doodle HTTP/1.0\015\012"
-                + "Connection: close\015\012"
-                + "Header3: value3\015\012"
-                + "Content-Length: 10\015\012"
-                + "\015\012"
-                + "0123456789\015\012");
-
-        ByteArrayBuffer buffer= new ByteArrayBuffer(4096);
-        ByteArrayBuffer content=new ByteArrayBuffer(8192);
-        SimpleBuffers buffers=new SimpleBuffers(buffer,content);
-
-        Handler handler = new Handler();
-        HttpParser parser= new HttpParser(buffers,io, handler);
-        parser.parse();
-        assertEquals("GET", f0);
-        assertEquals("/mp", f1);
-        assertEquals("HTTP/1.0", f2);
-        assertEquals(2, h);
-        assertEquals("Header1", hdr[1]);
-        assertEquals("value1", val[1]);
+        ByteBuffer buffer= BufferUtil.toBuffer(
+                          "GET /mp HTTP/1.0\015\012"
+                        + "Connection: Keep-Alive\015\012"
+                        + "Header1: value1\015\012"
+                        + "Transfer-Encoding: chunked\015\012"
+                        + "\015\012"
+                        + "a;\015\012"
+                        + "0123456789\015\012"
+                        + "1a\015\012"
+                        + "ABCDEFGHIJKLMNOPQRSTUVWXYZ\015\012"
+                        + "0\015\012"
+
+                        + "\015\012"
+
+                        + "POST /foo HTTP/1.0\015\012"
+                        + "Connection: Keep-Alive\015\012"
+                        + "Header2: value2\015\012"
+                        + "Content-Length: 0\015\012"
+                        + "\015\012"
+
+                        + "PUT /doodle HTTP/1.0\015\012"
+                        + "Connection: close\015\012"
+                        + "Header3: value3\015\012"
+                        + "Content-Length: 10\015\012"
+                        + "\015\012"
+                        + "0123456789\015\012");
+
+        HttpParser.RequestHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
+        parser.parseNext(buffer);
+        assertEquals("GET", _methodOrVersion);
+        assertEquals("/mp", _uriOrStatus);
+        assertEquals("HTTP/1.0", _versionOrReason);
+        assertEquals(2, _headers);
+        assertEquals("Header1", _hdr[1]);
+        assertEquals("value1", _val[1]);
         assertEquals("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ", _content);
 
-        parser.parse();
-        assertEquals("POST", f0);
-        assertEquals("/foo", f1);
-        assertEquals("HTTP/1.0", f2);
-        assertEquals(2, h);
-        assertEquals("Header2", hdr[1]);
-        assertEquals("value2", val[1]);
+        parser.reset();
+        init();
+        parser.parseNext(buffer);
+        assertEquals("POST", _methodOrVersion);
+        assertEquals("/foo", _uriOrStatus);
+        assertEquals("HTTP/1.0", _versionOrReason);
+        assertEquals(2, _headers);
+        assertEquals("Header2", _hdr[1]);
+        assertEquals("value2", _val[1]);
         assertEquals(null, _content);
 
-        parser.parse();
-        assertEquals("PUT", f0);
-        assertEquals("/doodle", f1);
-        assertEquals("HTTP/1.0", f2);
-        assertEquals(2, h);
-        assertEquals("Header3", hdr[1]);
-        assertEquals("value3", val[1]);
+        parser.reset();
+        init();
+        parser.parseNext(buffer);
+        parser.atEOF();
+        assertEquals("PUT", _methodOrVersion);
+        assertEquals("/doodle", _uriOrStatus);
+        assertEquals("HTTP/1.0", _versionOrReason);
+        assertEquals(2, _headers);
+        assertEquals("Header3", _hdr[1]);
+        assertEquals("value3", _val[1]);
         assertEquals("0123456789", _content);
     }
+    
 
     @Test
-    public void testStreamParse() throws Exception
+    public void testMultiParseEarlyEOF() throws Exception
     {
-        StringEndPoint io=new StringEndPoint();
-        String http="GET / HTTP/1.1\015\012"
-                + "Host: test\015\012"
-                + "Header1: value1\015\012"
-                + "Transfer-Encoding: chunked\015\012"
-                + "\015\012"
-                + "a;\015\012"
-                + "0123456789\015\012"
-                + "1a\015\012"
-                + "ABCDEFGHIJKLMNOPQRSTUVWXYZ\015\012"
-                + "0\015\012"
-                + "POST /foo HTTP/1.1\015\012"
-                + "Host: test\015\012"
-                + "Header2: value2\015\012"
-                + "Content-Length: 0\015\012"
-                + "\015\012"
-                + "PUT /doodle HTTP/1.1\015\012"
-                + "Host: test\015\012"
-                + "Connection: close\015\012"
-                + "Header3: value3\015\012"
-                + "Content-Length: 10\015\012"
-                + "\015\012"
-                + "0123456789\015\012";
+        ByteBuffer buffer0= BufferUtil.toBuffer(
+                          "GET /mp HTTP/1.0\015\012"
+                        + "Connection: Keep-Alive\015\012");
+
+        ByteBuffer buffer1= BufferUtil.toBuffer("Header1: value1\015\012"
+                        + "Transfer-Encoding: chunked\015\012"
+                        + "\015\012"
+                        + "a;\015\012"
+                        + "0123456789\015\012"
+                        + "1a\015\012"
+                        + "ABCDEFGHIJKLMNOPQRSTUVWXYZ\015\012"
+                        + "0\015\012"
+
+                        + "\015\012"
+
+                        + "POST /foo HTTP/1.0\015\012"
+                        + "Connection: Keep-Alive\015\012"
+                        + "Header2: value2\015\012"
+                        + "Content-Length: 0\015\012"
+                        + "\015\012"
+
+                        + "PUT /doodle HTTP/1.0\015\012"
+                        + "Connection: close\015\012"
+                        + "Header3: value3\015\012"
+                        + "Content-Length: 10\015\012"
+                        + "\015\012"
+                        + "0123456789\015\012");
+
+
+        HttpParser.RequestHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
+        parser.parseNext(buffer0);
+        parser.atEOF();
+        parser.parseNext(buffer1);
+        assertEquals("GET", _methodOrVersion);
+        assertEquals("/mp", _uriOrStatus);
+        assertEquals("HTTP/1.0", _versionOrReason);
+        assertEquals(2, _headers);
+        assertEquals("Header1", _hdr[1]);
+        assertEquals("value1", _val[1]);
+        assertEquals("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ", _content);
 
-        int[] tests=
-            {
-                1024,
-                http.length() + 3,
-                http.length() + 2,
-                http.length() + 1,
-                http.length() + 0,
-                http.length() - 1,
-                http.length() - 2,
-                http.length() / 2,
-                http.length() / 3,
-                128,
-                32
-            };
-
-        for (int t= 0; t < tests.length; t++)
-        {
-            String tst="t"+t+"="+tests[t];
-            try
-            { 
-                f0=f1=f2=null;
-                h=0;
-                ByteArrayBuffer buffer= new ByteArrayBuffer(tests[t]);
-                ByteArrayBuffer content=new ByteArrayBuffer(8192);
-                SimpleBuffers buffers=new SimpleBuffers(buffer,content);
-
-                Handler handler = new Handler();
-                HttpParser parser= new HttpParser(buffers,io, handler);
-
-                io.setInput(http);
-
-                // System.err.println(tst);
-                parser.parse();
-                assertEquals(tst,"GET", f0);
-                assertEquals(tst,"/", f1);
-                assertEquals(tst,"HTTP/1.1", f2);
-                assertEquals(tst,2, h);
-                assertEquals(tst,"Header1", hdr[1]);
-                assertEquals(tst,"value1", val[1]);
-                assertEquals(tst,"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ", _content);
-
-                parser.parse();
-                assertEquals(tst,"POST", f0);
-                assertEquals(tst,"/foo", f1);
-                assertEquals(tst,"HTTP/1.1", f2);
-                assertEquals(tst,2, h);
-                assertEquals(tst,"Header2", hdr[1]);
-                assertEquals(tst,"value2", val[1]);
-                assertEquals(tst,null, _content);
-
-                parser.parse();
-                assertEquals(tst,"PUT", f0);
-                assertEquals(tst,"/doodle", f1);
-                assertEquals(tst,"HTTP/1.1", f2);
-                assertEquals(tst,3, h);
-                assertEquals(tst,"Header3", hdr[2]);
-                assertEquals(tst,"value3", val[2]);
-                assertEquals(tst,"0123456789", _content);
-            }
-            catch(Exception e)
-            {
-                if (t+1 < tests.length)
-                    throw e;
-                assertTrue(e.toString().indexOf("Request Entity Too Large")>=0);
-            }
-        }
-    }
+        parser.reset();
+        init();
+        parser.parseNext(buffer1);
+        assertEquals("POST", _methodOrVersion);
+        assertEquals("/foo", _uriOrStatus);
+        assertEquals("HTTP/1.0", _versionOrReason);
+        assertEquals(2, _headers);
+        assertEquals("Header2", _hdr[1]);
+        assertEquals("value2", _val[1]);
+        assertEquals(null, _content);
 
+        parser.reset();
+        init();
+        parser.parseNext(buffer1);
+        assertEquals("PUT", _methodOrVersion);
+        assertEquals("/doodle", _uriOrStatus);
+        assertEquals("HTTP/1.0", _versionOrReason);
+        assertEquals(2, _headers);
+        assertEquals("Header3", _hdr[1]);
+        assertEquals("value3", _val[1]);
+        assertEquals("0123456789", _content);
+    }
+    
     @Test
     public void testResponseParse0() throws Exception
     {
-        StringEndPoint io=new StringEndPoint();
-        io.setInput(
-        "HTTP/1.1 200 Correct\015\012"
-        + "Content-Length: 10\015\012"
-        + "Content-Type: text/plain\015\012"
-        + "\015\012"
-        + "0123456789\015\012");
-        ByteArrayBuffer buffer= new ByteArrayBuffer(4096);
-        SimpleBuffers buffers=new SimpleBuffers(buffer,null);
-
-        Handler handler = new Handler();
-        HttpParser parser= new HttpParser(buffers,io, handler);
-        parser.parse();
-        assertEquals("HTTP/1.1", f0);
-        assertEquals("200", f1);
-        assertEquals("Correct", f2);
-        assertEquals(_content.length(), 10);
-        assertTrue(headerCompleted);
-        assertTrue(messageCompleted);
+        ByteBuffer buffer= BufferUtil.toBuffer(
+                "HTTP/1.1 200 Correct\015\012"
+                        + "Content-Length: 10\015\012"
+                        + "Content-Type: text/plain\015\012"
+                        + "\015\012"
+                        + "0123456789\015\012");
+
+        HttpParser.ResponseHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
+        parser.parseNext(buffer);
+        assertEquals("HTTP/1.1", _methodOrVersion);
+        assertEquals("200", _uriOrStatus);
+        assertEquals("Correct", _versionOrReason);
+        assertEquals(10,_content.length());
+        assertTrue(_headerCompleted);
+        assertTrue(_messageCompleted);
     }
 
     @Test
     public void testResponseParse1() throws Exception
     {
-        StringEndPoint io=new StringEndPoint();
-        io.setInput(
-        "HTTP/1.1 304 Not-Modified\015\012"
-        + "Connection: close\015\012"
-        + "\015\012");
-        ByteArrayBuffer buffer= new ByteArrayBuffer(4096);
-        SimpleBuffers buffers=new SimpleBuffers(buffer,null);
-
-        Handler handler = new Handler();
-        HttpParser parser= new HttpParser(buffers,io, handler);
-        parser.parse();
-        assertEquals("HTTP/1.1", f0);
-        assertEquals("304", f1);
-        assertEquals("Not-Modified", f2);
-        assertTrue(headerCompleted);
-        assertTrue(messageCompleted);
+        ByteBuffer buffer= BufferUtil.toBuffer(
+                "HTTP/1.1 304 Not-Modified\015\012"
+                        + "Connection: close\015\012"
+                        + "\015\012");
+
+        HttpParser.ResponseHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
+        parser.parseNext(buffer);
+        assertEquals("HTTP/1.1", _methodOrVersion);
+        assertEquals("304", _uriOrStatus);
+        assertEquals("Not-Modified", _versionOrReason);
+        assertTrue(_headerCompleted);
+        assertTrue(_messageCompleted);
     }
 
     @Test
     public void testResponseParse2() throws Exception
     {
-        StringEndPoint io=new StringEndPoint();
-        io.setInput(
-        "HTTP/1.1 204 No-Content\015\012"
-        + "Header: value\015\012"
-        + "\015\012"
-        + "HTTP/1.1 200 Correct\015\012"
-        + "Content-Length: 10\015\012"
-        + "Content-Type: text/plain\015\012"
-        + "\015\012"
-        + "0123456789\015\012");
-        ByteArrayBuffer buffer= new ByteArrayBuffer(4096);
-        SimpleBuffers buffers=new SimpleBuffers(buffer,null);
-
-        Handler handler = new Handler();
-        HttpParser parser= new HttpParser(buffers,io, handler);
-        parser.parse();
-        assertEquals("HTTP/1.1", f0);
-        assertEquals("204", f1);
-        assertEquals("No-Content", f2);
-        assertTrue(headerCompleted);
-        assertTrue(messageCompleted);
-
-        parser.parse();
-        assertEquals("HTTP/1.1", f0);
-        assertEquals("200", f1);
-        assertEquals("Correct", f2);
+        ByteBuffer buffer= BufferUtil.toBuffer(
+                          "HTTP/1.1 204 No-Content\015\012"
+                        + "Header: value\015\012"
+                        + "\015\012"
+
+                        + "HTTP/1.1 200 Correct\015\012"
+                        + "Content-Length: 10\015\012"
+                        + "Content-Type: text/plain\015\012"
+                        + "\015\012"
+                        + "0123456789\015\012");
+
+        HttpParser.ResponseHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
+        parser.parseNext(buffer);
+        assertEquals("HTTP/1.1", _methodOrVersion);
+        assertEquals("204", _uriOrStatus);
+        assertEquals("No-Content", _versionOrReason);
+        assertTrue(_headerCompleted);
+        assertTrue(_messageCompleted);
+
+        parser.reset();
+        init();
+
+        parser.parseNext(buffer);
+        parser.atEOF();
+        assertEquals("HTTP/1.1", _methodOrVersion);
+        assertEquals("200", _uriOrStatus);
+        assertEquals("Correct", _versionOrReason);
         assertEquals(_content.length(), 10);
-        assertTrue(headerCompleted);
-        assertTrue(messageCompleted);
+        assertTrue(_headerCompleted);
+        assertTrue(_messageCompleted);
     }
 
 
     @Test
     public void testResponseParse3() throws Exception
     {
-        StringEndPoint io=new StringEndPoint();
-        io.setInput(
-        "HTTP/1.1 200\015\012"
-        + "Content-Length: 10\015\012"
-        + "Content-Type: text/plain\015\012"
-        + "\015\012"
-        + "0123456789\015\012");
-        ByteArrayBuffer buffer= new ByteArrayBuffer(4096);
-        SimpleBuffers buffers=new SimpleBuffers(buffer,null);
-
-        Handler handler = new Handler();
-        HttpParser parser= new HttpParser(buffers,io, handler);
-        parser.parse();
-        assertEquals("HTTP/1.1", f0);
-        assertEquals("200", f1);
-        assertEquals(null, f2);
+        ByteBuffer buffer= BufferUtil.toBuffer(
+                "HTTP/1.1 200\015\012"
+                        + "Content-Length: 10\015\012"
+                        + "Content-Type: text/plain\015\012"
+                        + "\015\012"
+                        + "0123456789\015\012");
+
+        HttpParser.ResponseHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
+        parser.parseNext(buffer);
+        assertEquals("HTTP/1.1", _methodOrVersion);
+        assertEquals("200", _uriOrStatus);
+        assertEquals(null, _versionOrReason);
         assertEquals(_content.length(), 10);
-        assertTrue(headerCompleted);
-        assertTrue(messageCompleted);
+        assertTrue(_headerCompleted);
+        assertTrue(_messageCompleted);
     }
-    
+
     @Test
     public void testResponseParse4() throws Exception
     {
-        StringEndPoint io=new StringEndPoint();
-        io.setInput(
-        "HTTP/1.1 200 \015\012"
-        + "Content-Length: 10\015\012"
-        + "Content-Type: text/plain\015\012"
-        + "\015\012"
-        + "0123456789\015\012");
-        ByteArrayBuffer buffer= new ByteArrayBuffer(4096);
-        SimpleBuffers buffers=new SimpleBuffers(buffer,null);
-
-        Handler handler = new Handler();
-        HttpParser parser= new HttpParser(buffers,io, handler);
-        parser.parse();
-        assertEquals("HTTP/1.1", f0);
-        assertEquals("200", f1);
-        assertEquals(null, f2);
+        ByteBuffer buffer= BufferUtil.toBuffer(
+                "HTTP/1.1 200 \015\012"
+                        + "Content-Length: 10\015\012"
+                        + "Content-Type: text/plain\015\012"
+                        + "\015\012"
+                        + "0123456789\015\012");
+
+        HttpParser.ResponseHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
+        parser.parseNext(buffer);
+        assertEquals("HTTP/1.1", _methodOrVersion);
+        assertEquals("200", _uriOrStatus);
+        assertEquals(null, _versionOrReason);
         assertEquals(_content.length(), 10);
-        assertTrue(headerCompleted);
-        assertTrue(messageCompleted);
+        assertTrue(_headerCompleted);
+        assertTrue(_messageCompleted);
     }
+
+    @Test
+    public void testResponseEOFContent() throws Exception
+    {
+        ByteBuffer buffer= BufferUtil.toBuffer(
+                "HTTP/1.1 200 \015\012"
+                        + "Content-Type: text/plain\015\012"
+                        + "\015\012"
+                        + "0123456789\015\012");
+
+        HttpParser.ResponseHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
+        parser.atEOF();
+        parser.parseNext(buffer);
+        
+        assertEquals("HTTP/1.1", _methodOrVersion);
+        assertEquals("200", _uriOrStatus);
+        assertEquals(null, _versionOrReason);
+        assertEquals(12,_content.length());
+        assertEquals("0123456789\015\012",_content);
+        assertTrue(_headerCompleted);
+        assertTrue(_messageCompleted);
+    }    
     
     @Test
     public void testResponse304WithContentLength() throws Exception
     {
-        StringEndPoint io=new StringEndPoint();
-        io.setInput(
-        "HTTP/1.1 304 found\015\012"
-        + "Content-Length: 10\015\012"
-        + "\015\012");
-        ByteArrayBuffer buffer= new ByteArrayBuffer(4096);
-        SimpleBuffers buffers=new SimpleBuffers(buffer,null);
-
-        Handler handler = new Handler();
-        HttpParser parser= new HttpParser(buffers,io, handler);
-        parser.parse();
-        assertEquals("HTTP/1.1", f0);
-        assertEquals("304", f1);
-        assertEquals("found", f2);
+        ByteBuffer buffer= BufferUtil.toBuffer(
+                "HTTP/1.1 304 found\015\012"
+                        + "Content-Length: 10\015\012"
+                        + "\015\012");
+
+        HttpParser.ResponseHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
+        parser.parseNext(buffer);
+        assertEquals("HTTP/1.1", _methodOrVersion);
+        assertEquals("304", _uriOrStatus);
+        assertEquals("found", _versionOrReason);
         assertEquals(null,_content);
-        assertTrue(headerCompleted);
-        assertTrue(messageCompleted);
+        assertTrue(_headerCompleted);
+        assertTrue(_messageCompleted);
+    }
+
+    @Test
+    public void testResponse101WithTransferEncoding() throws Exception
+    {
+        ByteBuffer buffer= BufferUtil.toBuffer(
+                "HTTP/1.1 101 switching protocols\015\012"
+                        + "Transfer-Encoding: chunked\015\012"
+                        + "\015\012");
+
+        HttpParser.ResponseHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
+        parser.parseNext(buffer);
+        assertEquals("HTTP/1.1", _methodOrVersion);
+        assertEquals("101", _uriOrStatus);
+        assertEquals("switching protocols", _versionOrReason);
+        assertEquals(null,_content);
+        assertTrue(_headerCompleted);
+        assertTrue(_messageCompleted);
     }
     
     @Test
     public void testSeekEOF() throws Exception
     {
-        StringEndPoint io=new StringEndPoint();
-        io.setInput(
-        "HTTP/1.1 200 OK\015\012"
-        + "Content-Length: 0\015\012"
-        + "Connection: close\015\012"
-        + "\015\012"
-        + "\015\012" // extra CRLF ignored
-        + "HTTP/1.1 400 OK\015\012");  // extra data causes close
+        ByteBuffer buffer= BufferUtil.toBuffer(
+                "HTTP/1.1 200 OK\015\012"
+                        + "Content-Length: 0\015\012"
+                        + "Connection: close\015\012"
+                        + "\015\012"
+                        + "\015\012" // extra CRLF ignored
+                        + "HTTP/1.1 400 OK\015\012");  // extra data causes close ??
+
+
+        HttpParser.ResponseHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
+
+        parser.parseNext(buffer);
+        assertEquals("HTTP/1.1", _methodOrVersion);
+        assertEquals("200", _uriOrStatus);
+        assertEquals("OK", _versionOrReason);
+        assertEquals(null,_content);
+        assertTrue(_headerCompleted);
+        assertTrue(_messageCompleted);
+
+        parser.reset();
+        parser.parseNext(buffer);
+        assertFalse(buffer.hasRemaining());
+        assertTrue(parser.isClosed());
+    }
+    
+    
+
+    @Test
+    public void testNoURI() throws Exception
+    {
+        ByteBuffer buffer= BufferUtil.toBuffer(
+                "GET\015\012"
+                        + "Content-Length: 0\015\012"
+                        + "Connection: close\015\012"
+                        + "\015\012");
+
+        HttpParser.RequestHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
+
+        parser.parseNext(buffer);
+        assertEquals(null,_methodOrVersion);
+        assertEquals("No URI",_bad);
+        assertFalse(buffer.hasRemaining());
+        assertEquals(HttpParser.State.CLOSED,parser.getState());
+    }
+
+
+    @Test
+    public void testNoURI2() throws Exception
+    {
+        ByteBuffer buffer= BufferUtil.toBuffer(
+                "GET \015\012"
+                        + "Content-Length: 0\015\012"
+                        + "Connection: close\015\012"
+                        + "\015\012");
+
+        HttpParser.RequestHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
+
+        parser.parseNext(buffer);
+        assertEquals(null,_methodOrVersion);
+        assertEquals("No URI",_bad);
+        assertFalse(buffer.hasRemaining());
+        assertEquals(HttpParser.State.CLOSED,parser.getState());
+    }
+
+    @Test
+    public void testUnknownReponseVersion() throws Exception
+    {
+        ByteBuffer buffer= BufferUtil.toBuffer(
+                "HPPT/7.7 200 OK\015\012"
+                        + "Content-Length: 0\015\012"
+                        + "Connection: close\015\012"
+                        + "\015\012");
+
+        HttpParser.ResponseHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
+
+        parser.parseNext(buffer);
+        assertEquals(null,_methodOrVersion);
+        assertEquals("Unknown Version",_bad);
+        assertFalse(buffer.hasRemaining());
+        assertEquals(HttpParser.State.CLOSED,parser.getState());
+    }
+
+    @Test
+    public void testNoStatus() throws Exception
+    {
+        ByteBuffer buffer= BufferUtil.toBuffer(
+                "HTTP/1.1\015\012"
+                        + "Content-Length: 0\015\012"
+                        + "Connection: close\015\012"
+                        + "\015\012");
+
+        HttpParser.ResponseHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
+
+        parser.parseNext(buffer);
+        assertEquals(null,_methodOrVersion);
+        assertEquals("No Status",_bad);
+        assertFalse(buffer.hasRemaining());
+        assertEquals(HttpParser.State.CLOSED,parser.getState());
+    }
+
+    @Test
+    public void testNoStatus2() throws Exception
+    {
+        ByteBuffer buffer= BufferUtil.toBuffer(
+                "HTTP/1.1 \015\012"
+                        + "Content-Length: 0\015\012"
+                        + "Connection: close\015\012"
+                        + "\015\012");
+
+        HttpParser.ResponseHandler<ByteBuffer> handler = new Handler();
+        HttpParser parser= new HttpParser(handler);
         
+        parser.parseNext(buffer);
+        assertEquals(null,_methodOrVersion);
+        assertEquals("No Status",_bad);
+        assertFalse(buffer.hasRemaining());
+        assertEquals(HttpParser.State.CLOSED,parser.getState());
+    }
+
+    @Test
+    public void testBadRequestVersion() throws Exception
+    {
+        ByteBuffer buffer= BufferUtil.toBuffer(
+                "GET / HPPT/7.7\015\012"
+                        + "Content-Length: 0\015\012"
+                        + "Connection: close\015\012"
+                        + "\015\012");
+
+        HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
+        HttpParser parser= new HttpParser(handler);
+
+        parser.parseNext(buffer);
+        assertEquals(null,_methodOrVersion);
+        assertEquals("Unknown Version",_bad);
+        assertFalse(buffer.hasRemaining());
+        assertEquals(HttpParser.State.CLOSED,parser.getState()); 
         
-        ByteArrayBuffer buffer= new ByteArrayBuffer(4096);
-        SimpleBuffers buffers=new SimpleBuffers(buffer,null);
+        buffer= BufferUtil.toBuffer(
+            "GET / HTTP/1.01\015\012"
+                + "Content-Length: 0\015\012"
+                + "Connection: close\015\012"
+                + "\015\012");
+
+        handler = new Handler();handler = new Handler();
+        parser= new HttpParser(handler);
+
+        parser.parseNext(buffer);
+        assertEquals(null,_methodOrVersion);
+        assertEquals("Unknown Version",_bad);
+        assertFalse(buffer.hasRemaining());
+        assertEquals(HttpParser.State.CLOSED,parser.getState());
+    }
+    
+    @Test
+    public void testBadCR() throws Exception
+    {
+        ByteBuffer buffer= BufferUtil.toBuffer(
+                "GET / HTTP/1.0\r\n"
+                        + "Content-Length: 0\r"
+                        + "Connection: close\r"
+                        + "\r");
+
+        HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
+        HttpParser parser= new HttpParser(handler);
+
+        parser.parseNext(buffer);
+        assertEquals("Bad EOL",_bad);
+        assertFalse(buffer.hasRemaining());
+        assertEquals(HttpParser.State.CLOSED,parser.getState());
+
+
+        buffer= BufferUtil.toBuffer(
+            "GET / HTTP/1.0\r"
+                + "Content-Length: 0\r"
+                + "Connection: close\r"
+                + "\r");
+
+        handler = new Handler();
+        parser= new HttpParser(handler);
+
+        parser.parseNext(buffer);
+        assertEquals("Bad EOL",_bad);
+        assertFalse(buffer.hasRemaining());
+        assertEquals(HttpParser.State.CLOSED,parser.getState());
+    }
+    
+    
+    
 
-        Handler handler = new Handler();
-        HttpParser parser= new HttpParser(buffers,io, handler);
+    @Test
+    public void testBadContentLength0() throws Exception
+    {
+        ByteBuffer buffer= BufferUtil.toBuffer(
+                "GET / HTTP/1.0\015\012"
+                        + "Content-Length: abc\015\012"
+                        + "Connection: close\015\012"
+                        + "\015\012");
+
+        HttpParser.RequestHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
+
+        parser.parseNext(buffer);
+        assertEquals("GET",_methodOrVersion);
+        assertEquals("Bad Content-Length",_bad);
+        assertFalse(buffer.hasRemaining());
+        assertEquals(HttpParser.State.CLOSED,parser.getState());
+    }
+
+    @Test
+    public void testBadContentLength1() throws Exception
+    {
+        ByteBuffer buffer= BufferUtil.toBuffer(
+                "GET / HTTP/1.0\015\012"
+                        + "Content-Length: 9999999999999999999999999999999999999999999999\015\012"
+                        + "Connection: close\015\012"
+                        + "\015\012");
+
+        HttpParser.RequestHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
+
+        parser.parseNext(buffer);
+        assertEquals("GET",_methodOrVersion);
+        assertEquals("Bad Content-Length",_bad);
+        assertFalse(buffer.hasRemaining());
+        assertEquals(HttpParser.State.CLOSED,parser.getState());
+    }
+
+    @Test
+    public void testBadContentLength2() throws Exception
+    {
+        ByteBuffer buffer= BufferUtil.toBuffer(
+                "GET / HTTP/1.0\015\012"
+                        + "Content-Length: 1.5\015\012"
+                        + "Connection: close\015\012"
+                        + "\015\012");
+
+        HttpParser.RequestHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
+
+        parser.parseNext(buffer);
+        assertEquals("GET",_methodOrVersion);
+        assertEquals("Bad Content-Length",_bad);
+        assertFalse(buffer.hasRemaining());
+        assertEquals(HttpParser.State.CLOSED,parser.getState());
+    }
+    
+    @Test
+    public void testHost() throws Exception
+    {
+        ByteBuffer buffer= BufferUtil.toBuffer(
+                "GET / HTTP/1.1\015\012"
+                        + "Host: host\015\012"
+                        + "Connection: close\015\012"
+                        + "\015\012");
+
+        HttpParser.RequestHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
+        parser.parseNext(buffer);
+        assertEquals("host",_host);
+        assertEquals(0,_port);
+    }
+    
+    @Test
+    public void testUriHost11() throws Exception
+    {
+        ByteBuffer buffer= BufferUtil.toBuffer(
+                "GET http://host/ HTTP/1.1\015\012"
+                        + "Connection: close\015\012"
+                        + "\015\012");
+
+        HttpParser.RequestHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
+        parser.parseNext(buffer);
+        assertEquals("No Host",_bad);
+        assertEquals("http://host/",_uriOrStatus);
+        assertEquals(0,_port);
+    }
+    
+    @Test
+    public void testUriHost10() throws Exception
+    {
+        ByteBuffer buffer= BufferUtil.toBuffer(
+                "GET http://host/ HTTP/1.0\015\012"
+                        + "\015\012");
+
+        HttpParser.RequestHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
+        parser.parseNext(buffer);
+        Assert.assertNull(_bad);
+        assertEquals("http://host/",_uriOrStatus);
+        assertEquals(0,_port);
+    }
+    
+    @Test
+    public void testNoHost() throws Exception
+    {
+        ByteBuffer buffer= BufferUtil.toBuffer(
+                "GET / HTTP/1.1\015\012"
+                        + "Connection: close\015\012"
+                        + "\015\012");
+
+        HttpParser.RequestHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
+        parser.parseNext(buffer);
+        assertEquals("No Host",_bad);
+    }
+    
+    @Test
+    public void testIPHost() throws Exception
+    {
+        ByteBuffer buffer= BufferUtil.toBuffer(
+                "GET / HTTP/1.1\015\012"
+                        + "Host: 192.168.0.1\015\012"
+                        + "Connection: close\015\012"
+                        + "\015\012");
+
+        HttpParser.RequestHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
+        parser.parseNext(buffer);
+        assertEquals("192.168.0.1",_host);
+        assertEquals(0,_port);
+    }
+    
+    @Test
+    public void testIPv6Host() throws Exception
+    {
+        ByteBuffer buffer= BufferUtil.toBuffer(
+                "GET / HTTP/1.1\015\012"
+                        + "Host: [::1]\015\012"
+                        + "Connection: close\015\012"
+                        + "\015\012");
+
+        HttpParser.RequestHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
+        parser.parseNext(buffer);
+        assertEquals("[::1]",_host);
+        assertEquals(0,_port);
+    }
+    
+    @Test
+    public void testBadIPv6Host() throws Exception
+    {
+        ByteBuffer buffer= BufferUtil.toBuffer(
+                "GET / HTTP/1.1\015\012"
+                        + "Host: [::1\015\012"
+                        + "Connection: close\015\012"
+                        + "\015\012");
+
+        HttpParser.RequestHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
+        parser.parseNext(buffer);
+        assertEquals("Bad IPv6 Host header",_bad);
+    }
+    
+    @Test
+    public void testHostPort() throws Exception
+    {
+        ByteBuffer buffer= BufferUtil.toBuffer(
+                "GET / HTTP/1.1\015\012"
+                        + "Host: myhost:8888\015\012"
+                        + "Connection: close\015\012"
+                        + "\015\012");
+
+        HttpParser.RequestHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
+        parser.parseNext(buffer);
+        assertEquals("myhost",_host);
+        assertEquals(8888,_port);
+    }
+    
+    @Test
+    public void testHostBadPort() throws Exception
+    {
+        ByteBuffer buffer= BufferUtil.toBuffer(
+                "GET / HTTP/1.1\015\012"
+                        + "Host: myhost:xxx\015\012"
+                        + "Connection: close\015\012"
+                        + "\015\012");
+
+        HttpParser.RequestHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
+        parser.parseNext(buffer);
+        assertEquals("Bad Host header",_bad);
+    }
+
+    @Test
+    public void testIPHostPort() throws Exception
+    {
+        ByteBuffer buffer= BufferUtil.toBuffer(
+                "GET / HTTP/1.1\015\012"
+                        + "Host: 192.168.0.1:8888\015\012"
+                        + "Connection: close\015\012"
+                        + "\015\012");
+
+        HttpParser.RequestHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
+        parser.parseNext(buffer);
+        assertEquals("192.168.0.1",_host);
+        assertEquals(8888,_port);
+    }
+
+    @Test
+    public void testIPv6HostPort() throws Exception
+    {
+        ByteBuffer buffer= BufferUtil.toBuffer(
+                "GET / HTTP/1.1\015\012"
+                        + "Host: [::1]:8888\015\012"
+                        + "Connection: close\015\012"
+                        + "\015\012");
+
+        HttpParser.RequestHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
+        parser.parseNext(buffer);
+        assertEquals("[::1]",_host);
+        assertEquals(8888,_port);
+    }
+
+    @Test
+    public void testCachedField() throws Exception
+    {
+        ByteBuffer buffer= BufferUtil.toBuffer(
+            "GET / HTTP/1.1\r\n"+
+            "Host: www.smh.com.au\r\n"+
+            "\r\n");
         
-        parser.parse();
-        assertEquals("HTTP/1.1", f0);
-        assertEquals("200", f1);
-        assertEquals("OK", f2);
-        assertEquals(null,_content);
-        assertTrue(headerCompleted);
-        assertTrue(messageCompleted);
+        HttpParser.RequestHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
+        parseAll(parser,buffer);
+        assertEquals("www.smh.com.au",parser.getFieldCache().get("Host: www.smh.com.au").getValue());
+        HttpField field=_fields.get(0);
         
+        //System.err.println(parser.getFieldCache());
         
+        buffer.position(0);
+        parseAll(parser,buffer);
+        assertTrue(field==_fields.get(0));
+        
+    }
+
+    @Test
+    public void testProxyProtocol() throws Exception
+    {
+        ByteBuffer buffer=BufferUtil
+            .toBuffer("PROXY TCP4 107.47.45.254 10.0.1.116 27689 80\015\012"
+                +"GET / HTTP/1.1\015\012"
+                +"Host: localhost \015\012"
+                +"Connection: close\015\012"+"\015\012"+"\015\012");
+
+        Handler handler=new Handler();
+        HttpParser parser=new HttpParser((HttpParser.RequestHandler)handler);
+        parseAll(parser, buffer);
+
+        assertTrue(_headerCompleted);
+        assertTrue(_messageCompleted);
+        assertEquals("GET", _methodOrVersion);
+        assertEquals("/", _uriOrStatus);
+        assertEquals("HTTP/1.1", _versionOrReason);
+        assertEquals("PROXY TCP4 107.47.45.254 10.0.1.116 27689 80", handler._proxy);
+        assertEquals("Host", _hdr[0]);
+        assertEquals("localhost", _val[0]);
+        assertEquals("Connection", _hdr[1]);
+        assertEquals("close", _val[1]);
+        assertEquals(1, _headers);
+    }
+
+    @Test
+    public void testSplitProxyHeaderParseTest() throws Exception
+    {
+        Handler handler=new Handler();
+        HttpParser parser=new HttpParser((HttpParser.RequestHandler)handler);
+
+        ByteBuffer buffer=BufferUtil.toBuffer("PROXY TCP4 207.47.45.254 10.0.1.116 27689 80\015\012");
+        parser.parseNext(buffer);
+
+        buffer=BufferUtil.toBuffer(
+            "GET / HTTP/1.1\015\012"
+                +"Host: localhost \015\012"
+                +"Connection: close\015\012"
+                +"\015\012"
+                +"\015\012");
+
+        parser.parseNext(buffer);
+        assertTrue(_headerCompleted);
+        assertTrue(_messageCompleted);
+        assertEquals("GET", _methodOrVersion);
+        assertEquals("/", _uriOrStatus);
+        assertEquals("HTTP/1.1", _versionOrReason);
+        assertEquals("PROXY TCP4 207.47.45.254 10.0.1.116 27689 80", handler._proxy);
+        assertEquals("Host", _hdr[0]);
+        assertEquals("localhost", _val[0]);
+        assertEquals("Connection", _hdr[1]);
+        assertEquals("close", _val[1]);
+        assertEquals(1, _headers);
     }
     
-    private String _content;
-    private String f0;
-    private String f1;
-    private String f2;
-    private String[] hdr;
-    private String[] val;
-    private int h;
 
-    private boolean headerCompleted;
-    private boolean messageCompleted;
+    @Test
+    public void testFolded() throws Exception
+    {
+        ByteBuffer buffer= BufferUtil.toBuffer(
+                "GET / HTTP/1.0\015\012" +
+                "Host: localhost\015\012" +
+                "Connection: close\015\012" +
+                "Content-Type: application/soap+xml; charset=utf-8; \015\012"+
+                "\taction=\"xxx\" \015\012" +
+                "\015\012");
+        
+        HttpParser.RequestHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
+        parseAll(parser,buffer);
+
+        assertTrue(_headerCompleted);
+        assertTrue(_messageCompleted);
+        assertEquals("GET", _methodOrVersion);
+        assertEquals("/", _uriOrStatus);
+        assertEquals("HTTP/1.0", _versionOrReason);
+        assertEquals("Host", _hdr[0]);
+        assertEquals("localhost", _val[0]);
+        assertEquals("Connection", _hdr[1]);
+        assertEquals("close", _val[1]);
+        assertEquals("Content-Type", _hdr[2]);
+        assertEquals("application/soap+xml; charset=utf-8; action=\"xxx\"", _val[2]);
+        assertEquals(2, _headers);
+    }
+
+    
+    @Test
+    public void testParseRequest() throws Exception
+    {
+        ByteBuffer buffer = BufferUtil.toBuffer(
+                "GET / HTTP/1.1\r\n" + 
+                "Host: localhost\r\n" + 
+                "Header1: value1\r\n" + 
+                "Connection: close\r\n" + 
+                "Accept-Encoding: gzip, deflated\r\n" + 
+                "Accept: unknown\r\n" + 
+                "\r\n");
+
+        HttpParser.RequestHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
+        parser.parseNext(buffer);
+
+        assertEquals("GET",_methodOrVersion);
+        assertEquals("/",_uriOrStatus);
+        assertEquals("HTTP/1.1",_versionOrReason);
+        assertEquals("Host",_hdr[0]);
+        assertEquals("localhost",_val[0]);
+        assertEquals("Connection",_hdr[2]);
+        assertEquals("close",_val[2]);
+        assertEquals("Accept-Encoding",_hdr[3]);
+        assertEquals("gzip, deflated",_val[3]);
+        assertEquals("Accept",_hdr[4]);
+        assertEquals("unknown",_val[4]);
+    }
+    
 
-    private class Handler extends HttpParser.EventHandler
+    @Test
+    public void testHTTP2Preface() throws Exception
+    {
+        ByteBuffer buffer= BufferUtil.toBuffer(
+                "PRI * HTTP/2.0\015\012" +
+                "\015\012" +
+                "SM\015\012"+
+                "\015\012");
+        
+        HttpParser.RequestHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
+        parseAll(parser,buffer);
+
+        assertTrue(_headerCompleted);
+        assertTrue(_messageCompleted);
+        assertEquals("PRI", _methodOrVersion);
+        assertEquals("*", _uriOrStatus);
+        assertEquals("HTTP/2.0", _versionOrReason);
+        assertEquals(-1, _headers);
+        assertEquals(null, _bad);
+    }
+    
+    
+    @Before
+    public void init()
+    {
+        _bad=null;
+        _content=null;
+        _methodOrVersion=null;
+        _uriOrStatus=null;
+        _versionOrReason=null;
+        _hdr=null;
+        _val=null;
+        _headers=0;
+        _headerCompleted=false;
+        _messageCompleted=false;
+    }
+
+    private String _host;
+    private int _port;
+    private String _bad;
+    private String _content;
+    private String _methodOrVersion;
+    private String _uriOrStatus;
+    private String _versionOrReason;
+    private List<HttpField> _fields=new ArrayList<>();
+    private String[] _hdr;
+    private String[] _val;
+    private int _headers;
+    
+    private boolean _early;
+    private boolean _headerCompleted;
+    private boolean _messageCompleted;
+
+    private class Handler implements HttpParser.RequestHandler<ByteBuffer>, HttpParser.ResponseHandler<ByteBuffer>, HttpParser.ProxyHandler
     {
         private HttpFields fields;
-        private boolean request;
+        String _proxy;
 
-        public void content(Buffer ref)
+        @Override
+        public boolean content(ByteBuffer ref)
         {
             if (_content==null)
                 _content="";
-            _content= _content + ref;
+            String c = BufferUtil.toString(ref,StandardCharsets.UTF_8);
+            //System.err.println("content '"+c+"'");
+            _content= _content + c;
+            ref.position(ref.limit());
+            return false;
         }
 
-        public void startRequest(Buffer tok0, Buffer tok1, Buffer tok2)
+        @Override
+        public boolean startRequest(HttpMethod httpMethod, String method, ByteBuffer uri, HttpVersion version)
         {
-            try
-            {
-                request=true;
-                h= -1;
-                hdr= new String[9];
-                val= new String[9];
-                f0= tok0.toString();
-                f1=new String(tok1.array(),tok1.getIndex(),tok1.length(),StringUtil.__UTF8);
-                if (tok2!=null)
-                    f2= tok2.toString();
-                else
-                    f2=null;
-
-                fields=new HttpFields();
-            }
-            catch (UnsupportedEncodingException e)
-            {
-                throw new RuntimeException(e);
-            }
+            _fields.clear();
+            _headers= -1;
+            _hdr= new String[10];
+            _val= new String[10];
+            _methodOrVersion= method;
+            _uriOrStatus= BufferUtil.toUTF8String(uri);
+            _versionOrReason= version==null?null:version.asString();
 
-            messageCompleted = false;
-            headerCompleted = false;
+            fields=new HttpFields();
+            _messageCompleted = false;
+            _headerCompleted = false;
+            _early=false;
+            return false;
         }
 
-        public void parsedHeader(Buffer name, Buffer value)
+        @Override
+        public boolean parsedHeader(HttpField field)
         {
-            hdr[++h]= name.toString(StringUtil.__ISO_8859_1);
-            val[h]= value.toString(StringUtil.__ISO_8859_1);
+            _fields.add(field);
+            //System.err.println("header "+name+": "+value);
+            _hdr[++_headers]= field.getName();
+            _val[_headers]= field.getValue();
+            return false;
         }
 
-        public void headerComplete()
+        @Override
+        public boolean parsedHostHeader(String host,int port)
         {
+            _host=host;
+            _port=port;
+            return false;
+        }
+
+        @Override
+        public boolean headerComplete()
+        {
+            //System.err.println("headerComplete");
             _content= null;
             String s0=fields.toString();
             String s1=fields.toString();
             if (!s0.equals(s1))
             {
-                //System.err.println(s0);
-                //System.err.println(s1);
                 throw new IllegalStateException();
             }
 
-            headerCompleted = true;
+            _headerCompleted = true;
+            return false;
+        }
+
+        @Override
+        public boolean messageComplete()
+        {
+            //System.err.println("messageComplete");
+            _messageCompleted = true;
+            return true;
         }
 
-        public void messageComplete(long contentLength)
+        @Override
+        public void badMessage(int status, String reason)
         {
-            messageCompleted = true;
+            _bad=reason==null?(""+status):reason;
         }
 
-        public void startResponse(Buffer version, int status, Buffer reason)
+        @Override
+        public boolean startResponse(HttpVersion version, int status, String reason)
         {
-            request=false;
-            f0 = version.toString();
-            f1 = Integer.toString(status);
-            f2 = reason==null?null:reason.toString();
+            _fields.clear();
+            _methodOrVersion = version.asString();
+            _uriOrStatus = Integer.toString(status);
+            _versionOrReason = reason;
 
             fields=new HttpFields();
-            hdr= new String[9];
-            val= new String[9];
+            _hdr= new String[9];
+            _val= new String[9];
 
-            messageCompleted = false;
-            headerCompleted = false;
+            _messageCompleted = false;
+            _headerCompleted = false;
+            return false;
+        }
+
+        @Override
+        public void earlyEOF()
+        {
+            _early=true;
+        }
+
+        @Override
+        public int getHeaderCacheSize()
+        {
+            return 512;
+        }
+
+        @Override
+        public void proxied(String protocol, String sAddr, String dAddr, int sPort, int dPort)
+        {
+            _proxy="PROXY "+protocol+" "+sAddr+" "+dAddr+" "+sPort+" "+dPort;
         }
     }
 }
diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpStatusCodeTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpStatusCodeTest.java
index 30affab..1554a65 100644
--- a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpStatusCodeTest.java
+++ b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpStatusCodeTest.java
@@ -34,6 +34,6 @@ public class HttpStatusCodeTest
 
     public void testHttpMethod()
     {
-        assertEquals("GET",HttpMethods.GET_BUFFER.toString());
+        assertEquals("GET",HttpMethod.GET.toString());
     }
 }
diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpURITest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpURITest.java
index 5e497b7..419bb3d 100644
--- a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpURITest.java
+++ b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpURITest.java
@@ -29,39 +29,51 @@ import org.junit.Test;
 /* ------------------------------------------------------------ */
 public class HttpURITest
 {
-    public static final String __input = "http://example.com:8080/path/to/context?parameter=%22value%22#fragment"; 
-    public static final String __scheme = "http";
-    public static final String __host = "example.com";
-    public static final int    __port = 8080;
-    public static final String __path = "/path/to/context";
-    public static final String __query = "parameter=%22value%22";
-    public static final String __fragment = "fragment";
+    String[][] tests=
+    {
+        {"/path/to/context",null,null,"-1","/path/to/context",null,null,null},
+        {"http://example.com/path/to/context;param?query=%22value%22#fragment","http","example.com","-1","/path/to/context","param","query=%22value%22","fragment"},
+        {"http://[::1]/path/to/context;param?query=%22value%22#fragment","http","[::1]","-1","/path/to/context","param","query=%22value%22","fragment"},
+        {"http://example.com:8080/path/to/context;param?query=%22value%22#fragment","http","example.com","8080","/path/to/context","param","query=%22value%22","fragment"},
+        {"http://[::1]:8080/path/to/context;param?query=%22value%22#fragment","http","[::1]","8080","/path/to/context","param","query=%22value%22","fragment"},
+    };
     
+    public static int
+    INPUT=0,SCHEME=1,HOST=2,PORT=3,PATH=4,PARAM=5,QUERY=6,FRAGMENT=7;
+
     /* ------------------------------------------------------------ */
     @Test
     public void testFromString() throws Exception
     {
-        HttpURI uri = new HttpURI(__input);
-        
-        assertEquals(__scheme, uri.getScheme());
-        assertEquals(__host,uri.getHost());
-        assertEquals(__port,uri.getPort());
-        assertEquals(__path,uri.getPath());
-        assertEquals(__query,uri.getQuery());
-        assertEquals(__fragment,uri.getFragment());
+        for (String[] test:tests)
+        {
+            HttpURI uri = new HttpURI(test[INPUT]);
+
+            assertEquals(test[SCHEME], uri.getScheme());
+            assertEquals(test[HOST], uri.getHost());
+            assertEquals(Integer.parseInt(test[PORT]), uri.getPort());
+            assertEquals(test[PATH], uri.getPath());
+            assertEquals(test[PARAM], uri.getParam());
+            assertEquals(test[QUERY], uri.getQuery());
+            assertEquals(test[FRAGMENT], uri.getFragment());
+        }
     }
-    
+
     /* ------------------------------------------------------------ */
     @Test
     public void testFromURI() throws Exception
     {
-        HttpURI uri = new HttpURI(new URI(__input));
-        
-        assertEquals(__scheme, uri.getScheme());
-        assertEquals(__host,uri.getHost());
-        assertEquals(__port,uri.getPort());
-        assertEquals(__path,uri.getPath());
-        assertEquals(__query,uri.getQuery());
-        assertEquals(__fragment,uri.getFragment());
+        for (String[] test:tests)
+        {
+            HttpURI uri = new HttpURI(new URI(test[INPUT]));
+
+            assertEquals(test[SCHEME], uri.getScheme());
+            assertEquals(test[HOST], uri.getHost());
+            assertEquals(Integer.parseInt(test[PORT]), uri.getPort());
+            assertEquals(test[PATH], uri.getPath());
+            assertEquals(test[PARAM], uri.getParam());
+            assertEquals(test[QUERY], uri.getQuery());
+            assertEquals(test[FRAGMENT], uri.getFragment());
+        }
     }
 }
diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/MimeTypesTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/MimeTypesTest.java
index 4274215..493ee75 100644
--- a/jetty-http/src/test/java/org/eclipse/jetty/http/MimeTypesTest.java
+++ b/jetty-http/src/test/java/org/eclipse/jetty/http/MimeTypesTest.java
@@ -18,8 +18,10 @@
 
 package org.eclipse.jetty.http;
 
-import org.eclipse.jetty.io.Buffer;
-import org.junit.Assert;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
 import org.junit.Test;
 
 public class MimeTypesTest
@@ -56,13 +58,60 @@ public class MimeTypesTest
         assertMimeTypeByExtension("text/plain","test.txt");
         assertMimeTypeByExtension("text/plain","TEST.TXT");
     }
+    
+    @Test
+    public void testGetMimeByExtension_NoExtension()
+    {
+        MimeTypes mimetypes = new MimeTypes();
+        String contentType = mimetypes.getMimeByExtension("README");
+        assertNull(contentType);
+    }
 
     private void assertMimeTypeByExtension(String expectedMimeType, String filename)
     {
         MimeTypes mimetypes = new MimeTypes();
-        Buffer contentType = mimetypes.getMimeByExtension(filename);
+        String contentType = mimetypes.getMimeByExtension(filename);
         String prefix = "MimeTypes.getMimeByExtension(" + filename + ")";
-        Assert.assertNotNull(prefix,contentType);
-        Assert.assertEquals(prefix,expectedMimeType,contentType.toString());
+        assertNotNull(prefix,contentType);
+        assertEquals(prefix,expectedMimeType,contentType);
+    }
+
+    @Test
+    public void testCharsetFromContentType()
+    {
+        assertEquals("abc",MimeTypes.getCharsetFromContentType("foo/bar;charset=abc;some=else"));
+        assertEquals("abc",MimeTypes.getCharsetFromContentType("foo/bar;charset=abc"));
+        assertEquals("abc",MimeTypes.getCharsetFromContentType("foo/bar ; charset = abc"));
+        assertEquals("abc",MimeTypes.getCharsetFromContentType("foo/bar ; charset = abc ; some=else"));
+        assertEquals("abc",MimeTypes.getCharsetFromContentType("foo/bar;other=param;charset=abc;some=else"));
+        assertEquals("abc",MimeTypes.getCharsetFromContentType("foo/bar;other=param;charset=abc"));
+        assertEquals("abc",MimeTypes.getCharsetFromContentType("foo/bar other = param ; charset = abc"));
+        assertEquals("abc",MimeTypes.getCharsetFromContentType("foo/bar other = param ; charset = abc ; some=else"));
+        assertEquals("abc",MimeTypes.getCharsetFromContentType("foo/bar other = param ; charset = abc"));
+        assertEquals("abc",MimeTypes.getCharsetFromContentType("foo/bar other = param ; charset = \"abc\" ; some=else"));
+        assertEquals(null,MimeTypes.getCharsetFromContentType("foo/bar"));
+        assertEquals("UTF-8",MimeTypes.getCharsetFromContentType("foo/bar;charset=uTf8"));
+        assertEquals("UTF-8",MimeTypes.getCharsetFromContentType("foo/bar;other=\"charset=abc\";charset=uTf8"));
+        assertEquals("UTF-8",MimeTypes.getCharsetFromContentType("text/html;charset=utf-8"));
+
+    }
+
+    @Test
+    public void testContentTypeWithoutCharset()
+    {
+        assertEquals("foo/bar;some=else",MimeTypes.getContentTypeWithoutCharset("foo/bar;charset=abc;some=else"));
+        assertEquals("foo/bar",MimeTypes.getContentTypeWithoutCharset("foo/bar;charset=abc"));
+        assertEquals("foo/bar",MimeTypes.getContentTypeWithoutCharset("foo/bar ; charset = abc"));
+        assertEquals("foo/bar;some=else",MimeTypes.getContentTypeWithoutCharset("foo/bar ; charset = abc ; some=else"));
+        assertEquals("foo/bar;other=param;some=else",MimeTypes.getContentTypeWithoutCharset("foo/bar;other=param;charset=abc;some=else"));
+        assertEquals("foo/bar;other=param",MimeTypes.getContentTypeWithoutCharset("foo/bar;other=param;charset=abc"));
+        assertEquals("foo/bar ; other = param",MimeTypes.getContentTypeWithoutCharset("foo/bar ; other = param ; charset = abc"));
+        assertEquals("foo/bar ; other = param;some=else",MimeTypes.getContentTypeWithoutCharset("foo/bar ; other = param ; charset = abc ; some=else"));
+        assertEquals("foo/bar ; other = param",MimeTypes.getContentTypeWithoutCharset("foo/bar ; other = param ; charset = abc"));
+        assertEquals("foo/bar ; other = param;some=else",MimeTypes.getContentTypeWithoutCharset("foo/bar ; other = param ; charset = \"abc\" ; some=else"));
+        assertEquals("foo/bar",MimeTypes.getContentTypeWithoutCharset("foo/bar"));
+        assertEquals("foo/bar",MimeTypes.getContentTypeWithoutCharset("foo/bar;charset=uTf8"));
+        assertEquals("foo/bar;other=\"charset=abc\"",MimeTypes.getContentTypeWithoutCharset("foo/bar;other=\"charset=abc\";charset=uTf8"));
+        assertEquals("text/html",MimeTypes.getContentTypeWithoutCharset("text/html;charset=utf-8"));
     }
 }
diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/PathMapTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/PathMapTest.java
index 7476219..d9f706e 100644
--- a/jetty-http/src/test/java/org/eclipse/jetty/http/PathMapTest.java
+++ b/jetty-http/src/test/java/org/eclipse/jetty/http/PathMapTest.java
@@ -18,19 +18,22 @@
 
 package org.eclipse.jetty.http;
 
-import junit.framework.TestCase;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 
+import org.junit.Ignore;
 import org.junit.Test;
 
 /**
  *
  */
-public class PathMapTest extends TestCase
+public class PathMapTest
 {
     @Test
     public void testPathMap() throws Exception
     {
-        PathMap p = new PathMap();
+        PathMap<String> p = new PathMap<>();
 
         p.put("/abs/path", "1");
         p.put("/abs/path/longer", "2");
@@ -42,25 +45,29 @@ public class PathMapTest extends TestCase
         p.put("/", "8");
         p.put("/XXX:/YYY", "9");
         p.put("", "10");
+        p.put("/\u20ACuro/*", "11");
 
         String[][] tests = {
-                        { "/abs/path", "1"},
-                        { "/abs/path/xxx", "8"},
-                        { "/abs/pith", "8"},
-                        { "/abs/path/longer", "2"},
-                        { "/abs/path/", "8"},
-                        { "/abs/path/xxx", "8"},
-                        { "/animal/bird/eagle/bald", "3"},
-                        { "/animal/fish/shark/grey", "4"},
-                        { "/animal/insect/bug", "5"},
-                        { "/animal", "5"},
-                        { "/animal/", "5"},
-                        { "/animal/x", "5"},
-                        { "/animal/*", "5"},
-                        { "/suffix/path.tar.gz", "6"},
-                        { "/suffix/path.gz", "7"},
-                        { "/animal/path.gz", "5"},
-                        { "/Other/path", "8"},};
+                { "/abs/path", "1"},
+                { "/abs/path/xxx", "8"},
+                { "/abs/pith", "8"},
+                { "/abs/path/longer", "2"},
+                { "/abs/path/", "8"},
+                { "/abs/path/xxx", "8"},
+                { "/animal/bird/eagle/bald", "3"},
+                { "/animal/fish/shark/grey", "4"},
+                { "/animal/insect/bug", "5"},
+                { "/animal", "5"},
+                { "/animal/", "5"},
+                { "/animal/x", "5"},
+                { "/animal/*", "5"},
+                { "/suffix/path.tar.gz", "6"},
+                { "/suffix/path.gz", "7"},
+                { "/animal/path.gz", "5"},
+                { "/Other/path", "8"},
+                { "/\u20ACuro/path", "11"},
+                { "/", "10"},
+                };
 
         for (String[] test : tests)
         {
@@ -70,11 +77,11 @@ public class PathMapTest extends TestCase
         assertEquals("Get absolute path", "1", p.get("/abs/path"));
         assertEquals("Match absolute path", "/abs/path", p.getMatch("/abs/path").getKey());
         assertEquals("all matches", "[/animal/bird/*=3, /animal/*=5, *.tar.gz=6, *.gz=7, /=8]",
-                                    p.getMatches("/animal/bird/path.tar.gz").toString());
+                p.getMatches("/animal/bird/path.tar.gz").toString());
         assertEquals("Dir matches", "[/animal/fish/*=4, /animal/*=5, /=8]", p.getMatches("/animal/fish/").toString());
         assertEquals("Dir matches", "[/animal/fish/*=4, /animal/*=5, /=8]", p.getMatches("/animal/fish").toString());
-        assertEquals("Dir matches", "[/=8]", p.getMatches("/").toString());
-        assertEquals("Dir matches", "[=10, /=8]", p.getMatches("").toString());
+        assertEquals("Root matches", "[=10, /=8]",p.getMatches("/").toString());
+        assertEquals("Dir matches", "[/=8]", p.getMatches("").toString());
 
         assertEquals("pathMatch exact", "/Foo/bar", PathMap.pathMatch("/Foo/bar", "/Foo/bar"));
         assertEquals("pathMatch prefix", "/Foo", PathMap.pathMatch("/Foo/*", "/Foo/bar"));
@@ -159,6 +166,30 @@ public class PathMapTest extends TestCase
         assertNotMatch(spec, "/xyz?123"); // as if the ? was encoded and part of the path
     }
 
+    @Test
+    public void testPrecidenceVsOrdering() throws Exception
+    {
+        PathMap<String> p = new PathMap<>();
+        p.put("/dump/gzip/*","prefix");
+        p.put("*.txt","suffix");
+       
+        assertEquals(null,p.getMatch("/foo/bar"));
+        assertEquals("prefix",p.getMatch("/dump/gzip/something").getValue());
+        assertEquals("suffix",p.getMatch("/foo/something.txt").getValue());
+        assertEquals("prefix",p.getMatch("/dump/gzip/something.txt").getValue());
+        
+        p = new PathMap<>();
+        p.put("*.txt","suffix");
+        p.put("/dump/gzip/*","prefix");
+       
+        assertEquals(null,p.getMatch("/foo/bar"));
+        assertEquals("prefix",p.getMatch("/dump/gzip/something").getValue());
+        assertEquals("suffix",p.getMatch("/foo/something.txt").getValue());
+        assertEquals("prefix",p.getMatch("/dump/gzip/something.txt").getValue());
+    }
+    
+    
+    
     private void assertMatch(String spec, String path)
     {
         boolean match = PathMap.match(spec, path);
diff --git a/jetty-io/pom.xml b/jetty-io/pom.xml
index 4d87761..35bc04f 100644
--- a/jetty-io/pom.xml
+++ b/jetty-io/pom.xml
@@ -2,7 +2,7 @@
   <parent>
     <artifactId>jetty-project</artifactId>
     <groupId>org.eclipse.jetty</groupId>
-    <version>8.1.17.v20150415</version>
+    <version>9.2.13.v20150730</version>
   </parent>
   <modelVersion>4.0.0</modelVersion>
   <artifactId>jetty-io</artifactId>
@@ -22,6 +22,11 @@
       <artifactId>jetty-test-helper</artifactId>
       <scope>test</scope>
     </dependency>
+    <dependency>
+      <groupId>org.mockito</groupId>
+      <artifactId>mockito-core</artifactId>
+      <scope>test</scope>
+    </dependency>
   </dependencies>
   <build>
     <plugins>
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractBuffer.java b/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractBuffer.java
deleted file mode 100644
index f357869..0000000
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractBuffer.java
+++ /dev/null
@@ -1,728 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.io;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.nio.charset.Charset;
-
-import org.eclipse.jetty.util.TypeUtil;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-
-/**
- * 
- *  
- */
-public abstract class AbstractBuffer implements Buffer
-{
-    private static final Logger LOG = Log.getLogger(AbstractBuffer.class);
-
-    private final static boolean __boundsChecking = Boolean.getBoolean("org.eclipse.jetty.io.AbstractBuffer.boundsChecking");
-    
-    protected final static String 
-    __IMMUTABLE = "IMMUTABLE", 
-    __READONLY = "READONLY",
-    __READWRITE = "READWRITE", 
-    __VOLATILE = "VOLATILE";
-    
-    protected int _access;
-    protected boolean _volatile;
-
-    protected int _get;
-    protected int _put;
-    protected int _hash;
-    protected int _hashGet;
-    protected int _hashPut;
-    protected int _mark;
-    protected String _string;
-    protected View _view;
-
-    /**
-     * Constructor for BufferView
-     * 
-     * @param access 0==IMMUTABLE, 1==READONLY, 2==READWRITE
-     */
-    public AbstractBuffer(int access, boolean isVolatile)
-    {
-        if (access == IMMUTABLE && isVolatile)
-                throw new IllegalArgumentException("IMMUTABLE && VOLATILE");
-        setMarkIndex(-1);
-        _access = access;
-        _volatile = isVolatile;
-    }
-
-    /*
-     * @see org.eclipse.io.Buffer#toArray()
-     */
-    public byte[] asArray()
-    {
-        byte[] bytes = new byte[length()];
-        byte[] array = array();
-        if (array != null)
-            System.arraycopy(array, getIndex(), bytes, 0, bytes.length);
-        else
-            peek(getIndex(), bytes, 0, length());
-        return bytes;
-    }
-
-    public ByteArrayBuffer duplicate(int access)
-    {
-        Buffer b=this.buffer();
-        if (this instanceof Buffer.CaseInsensitve || b instanceof Buffer.CaseInsensitve)
-            return new ByteArrayBuffer.CaseInsensitive(asArray(), 0, length(),access);
-        else
-            return new ByteArrayBuffer(asArray(), 0, length(), access);
-    }
-    
-    /*
-     * @see org.eclipse.io.Buffer#asNonVolatile()
-     */
-    public Buffer asNonVolatileBuffer()
-    {
-        if (!isVolatile()) return this;
-        return duplicate(_access);
-    }
-
-    public Buffer asImmutableBuffer()
-    {
-        if (isImmutable()) return this;
-        return duplicate(IMMUTABLE);
-    }
-
-    /*
-     * @see org.eclipse.util.Buffer#asReadOnlyBuffer()
-     */
-    public Buffer asReadOnlyBuffer()
-    {
-        if (isReadOnly()) return this;
-        return new View(this, markIndex(), getIndex(), putIndex(), READONLY);
-    }
-
-    public Buffer asMutableBuffer()
-    {
-        if (!isImmutable()) return this;
-        
-        Buffer b=this.buffer();
-        if (b.isReadOnly())
-        {
-            return duplicate(READWRITE);
-        }
-        return new View(b, markIndex(), getIndex(), putIndex(), _access);
-    }
-
-    public Buffer buffer()
-    {
-        return this;
-    }
-
-    public void clear()
-    {
-        setMarkIndex(-1);
-        setGetIndex(0);
-        setPutIndex(0);
-    }
-
-    public void compact()
-    {
-        if (isReadOnly()) throw new IllegalStateException(__READONLY);
-        int s = markIndex() >= 0 ? markIndex() : getIndex();
-        if (s > 0)
-        {
-            byte array[] = array();
-            int length = putIndex() - s;
-            if (length > 0)
-            {
-                if (array != null)
-                    System.arraycopy(array(), s, array(), 0, length);
-                else
-                    poke(0, peek(s, length));
-            }
-            if (markIndex() > 0) setMarkIndex(markIndex() - s);
-            setGetIndex(getIndex() - s);
-            setPutIndex(putIndex() - s);
-        }
-    }
-
-    @Override
-    public boolean equals(Object obj)
-    {
-        if (obj==this)
-            return true;
-        
-        // reject non buffers;
-        if (obj == null || !(obj instanceof Buffer)) return false;
-        Buffer b = (Buffer) obj;
-
-        if (this instanceof Buffer.CaseInsensitve ||  b instanceof Buffer.CaseInsensitve)
-            return equalsIgnoreCase(b);
-        
-        // reject different lengths
-        if (b.length() != length()) return false;
-
-        // reject AbstractBuffer with different hash value
-        if (_hash != 0 && obj instanceof AbstractBuffer)
-        {
-            AbstractBuffer ab = (AbstractBuffer) obj;
-            if (ab._hash != 0 && _hash != ab._hash) return false;
-        }
-
-        // Nothing for it but to do the hard grind.
-        int get=getIndex();
-        int bi=b.putIndex();
-        for (int i = putIndex(); i-->get;)
-        {
-            byte b1 = peek(i);
-            byte b2 = b.peek(--bi);
-            if (b1 != b2) return false;
-        }
-        return true;
-    }
-
-    public boolean equalsIgnoreCase(Buffer b)
-    {
-        if (b==this)
-            return true;
-        
-        // reject different lengths
-        if (b.length() != length()) return false;
-
-        // reject AbstractBuffer with different hash value
-        if (_hash != 0 && b instanceof AbstractBuffer)
-        {
-            AbstractBuffer ab = (AbstractBuffer) b;
-            if (ab._hash != 0 && _hash != ab._hash) return false;
-        }
-
-        // Nothing for it but to do the hard grind.
-        int get=getIndex();
-        int bi=b.putIndex();
-        
-        byte[] array = array();
-        byte[] barray= b.array();
-        if (array!=null && barray!=null)
-        {
-            for (int i = putIndex(); i-->get;)
-            {
-                byte b1 = array[i];
-                byte b2 = barray[--bi];
-                if (b1 != b2)
-                {
-                    if ('a' <= b1 && b1 <= 'z') b1 = (byte) (b1 - 'a' + 'A');
-                    if ('a' <= b2 && b2 <= 'z') b2 = (byte) (b2 - 'a' + 'A');
-                    if (b1 != b2) return false;
-                }
-            }
-        }
-        else
-        {
-            for (int i = putIndex(); i-->get;)
-            {
-                byte b1 = peek(i);
-                byte b2 = b.peek(--bi);
-                if (b1 != b2)
-                {
-                    if ('a' <= b1 && b1 <= 'z') b1 = (byte) (b1 - 'a' + 'A');
-                    if ('a' <= b2 && b2 <= 'z') b2 = (byte) (b2 - 'a' + 'A');
-                    if (b1 != b2) return false;
-                }
-            }
-        }
-        return true;
-    }
-
-    public byte get()
-    {
-        return peek(_get++);
-    }
-
-    public int get(byte[] b, int offset, int length)
-    {
-        int gi = getIndex();
-        int l=length();
-        if (l==0)
-            return -1;
-        
-        if (length>l)
-            length=l;
-        
-        length = peek(gi, b, offset, length);
-        if (length>0)
-            setGetIndex(gi + length);
-        return length;
-    }
-
-    public Buffer get(int length)
-    {
-        int gi = getIndex();
-        Buffer view = peek(gi, length);
-        setGetIndex(gi + length);
-        return view;
-    }
-
-    public final int getIndex()
-    {
-        return _get;
-    }
-
-    public boolean hasContent()
-    {
-        return _put > _get;
-    }
-    
-    @Override
-    public int hashCode()
-    {
-        if (_hash == 0 || _hashGet!=_get || _hashPut!=_put) 
-        {
-            int get=getIndex();
-            byte[] array = array();
-            if (array==null)
-            {
-                for (int i = putIndex(); i-- >get;)
-                {
-                    byte b = peek(i);
-                    if ('a' <= b && b <= 'z') 
-                        b = (byte) (b - 'a' + 'A');
-                    _hash = 31 * _hash + b;
-                }
-            }
-            else
-            {
-                for (int i = putIndex(); i-- >get;)
-                {
-                    byte b = array[i];
-                    if ('a' <= b && b <= 'z') 
-                        b = (byte) (b - 'a' + 'A');
-                    _hash = 31 * _hash + b;
-                }
-            }
-            if (_hash == 0) 
-                _hash = -1;
-            _hashGet=_get;
-            _hashPut=_put;
-            
-        }
-        return _hash;
-    }
-
-    public boolean isImmutable()
-    {
-        return _access <= IMMUTABLE;
-    }
-
-    public boolean isReadOnly()
-    {
-        return _access <= READONLY;
-    }
-
-    public boolean isVolatile()
-    {
-        return _volatile;
-    }
-
-    public int length()
-    {
-        return _put - _get;
-    }
-
-    public void mark()
-    {
-        setMarkIndex(_get - 1);
-    }
-
-    public void mark(int offset)
-    {
-        setMarkIndex(_get + offset);
-    }
-
-    public int markIndex()
-    {
-        return _mark;
-    }
-
-    public byte peek()
-    {
-        return peek(_get);
-    }
-
-    public Buffer peek(int index, int length)
-    {
-        if (_view == null)
-        {
-            _view = new View(this, -1, index, index + length, isReadOnly() ? READONLY : READWRITE);
-        }
-        else
-        {
-            _view.update(this.buffer());
-            _view.setMarkIndex(-1);
-            _view.setGetIndex(0);
-            _view.setPutIndex(index + length);
-            _view.setGetIndex(index);
-            
-        }
-        return _view;
-    }
-
-    public int poke(int index, Buffer src)
-    {
-        _hash=0;
-        /* 
-        if (isReadOnly()) 
-            throw new IllegalStateException(__READONLY);
-        if (index < 0) 
-            throw new IllegalArgumentException("index<0: " + index + "<0");
-        */
-        
-        int length=src.length();
-        if (index + length > capacity())
-        {
-            length=capacity()-index;
-            /*
-            if (length<0)
-                throw new IllegalArgumentException("index>capacity(): " + index + ">" + capacity());
-            */
-        }
-        
-        byte[] src_array = src.array();
-        byte[] dst_array = array();
-        if (src_array != null && dst_array != null)
-            System.arraycopy(src_array, src.getIndex(), dst_array, index, length);
-        else if (src_array != null)
-        {
-            int s=src.getIndex();
-            for (int i=0;i<length;i++)
-                poke(index++,src_array[s++]);
-        }
-        else if (dst_array != null)
-        {
-            int s=src.getIndex();
-            for (int i=0;i<length;i++)
-                dst_array[index++]=src.peek(s++);
-        }
-        else
-        {
-            int s=src.getIndex();
-            for (int i=0;i<length;i++)
-                poke(index++,src.peek(s++));
-        }
-        
-        return length;
-    }
-    
-
-    public int poke(int index, byte[] b, int offset, int length)
-    {
-        _hash=0;
-        /*
-        if (isReadOnly()) 
-            throw new IllegalStateException(__READONLY);
-        if (index < 0) 
-            throw new IllegalArgumentException("index<0: " + index + "<0");
-        */
-        if (index + length > capacity())
-        {
-            length=capacity()-index;
-            /* if (length<0)
-                throw new IllegalArgumentException("index>capacity(): " + index + ">" + capacity());
-            */
-        }
-        
-        byte[] dst_array = array();
-        if (dst_array != null)
-            System.arraycopy(b, offset, dst_array, index, length);
-        else
-        {
-            int s=offset;
-            for (int i=0;i<length;i++)
-                poke(index++,b[s++]);
-        }
-        return length;
-    }
-
-    public int put(Buffer src)
-    {
-        int pi = putIndex();
-        int l=poke(pi, src);
-        setPutIndex(pi + l);
-        return l;
-    }
-
-    public void put(byte b)
-    {
-        int pi = putIndex();
-        poke(pi, b);
-        setPutIndex(pi + 1);
-    }
-
-    public int put(byte[] b, int offset, int length)
-    {
-        int pi = putIndex();
-        int l = poke(pi, b, offset, length);
-        setPutIndex(pi + l);
-        return l;
-    }
-    
-    public int put(byte[] b)
-    {
-        int pi = putIndex();
-        int l = poke(pi, b, 0, b.length);
-        setPutIndex(pi + l);
-        return l;
-    }
-
-    public final int putIndex()
-    {
-        return _put;
-    }
-
-    public void reset()
-    {
-        if (markIndex() >= 0) setGetIndex(markIndex());
-    }
-
-    public void rewind()
-    {
-        setGetIndex(0);
-        setMarkIndex(-1);
-    }
-
-    public void setGetIndex(int getIndex)
-    {
-        /* bounds checking
-        if (isImmutable()) 
-            throw new IllegalStateException(__IMMUTABLE);
-        if (getIndex < 0)
-            throw new IllegalArgumentException("getIndex<0: " + getIndex + "<0");
-        if (getIndex > putIndex())
-            throw new IllegalArgumentException("getIndex>putIndex: " + getIndex + ">" + putIndex());
-         */
-        _get = getIndex;
-        _hash=0;
-    }
-
-    public void setMarkIndex(int index)
-    {
-        /*
-        if (index>=0 && isImmutable()) 
-            throw new IllegalStateException(__IMMUTABLE);
-        */
-        _mark = index;
-    }
-
-    public void setPutIndex(int putIndex)
-    {
-        /* bounds checking
-        if (isImmutable()) 
-            throw new IllegalStateException(__IMMUTABLE);
-        if (putIndex > capacity())
-                throw new IllegalArgumentException("putIndex>capacity: " + putIndex + ">" + capacity());
-        if (getIndex() > putIndex)
-                throw new IllegalArgumentException("getIndex>putIndex: " + getIndex() + ">" + putIndex);
-         */
-        _put = putIndex;
-        _hash=0;
-    }
-
-    public int skip(int n)
-    {
-        if (length() < n) n = length();
-        setGetIndex(getIndex() + n);
-        return n;
-    }
-
-    public Buffer slice()
-    {
-        return peek(getIndex(), length());
-    }
-
-    public Buffer sliceFromMark()
-    {
-        return sliceFromMark(getIndex() - markIndex() - 1);
-    }
-
-    public Buffer sliceFromMark(int length)
-    {
-        if (markIndex() < 0) return null;
-        Buffer view = peek(markIndex(), length);
-        setMarkIndex(-1);
-        return view;
-    }
-
-    public int space()
-    {
-        return capacity() - _put;
-    }
-
-    public String toDetailString()
-    {
-        StringBuilder buf = new StringBuilder();
-        buf.append("[");
-        buf.append(super.hashCode());
-        buf.append(",");
-        buf.append(this.buffer().hashCode());
-        buf.append(",m=");
-        buf.append(markIndex());
-        buf.append(",g=");
-        buf.append(getIndex());
-        buf.append(",p=");
-        buf.append(putIndex());
-        buf.append(",c=");
-        buf.append(capacity());
-        buf.append("]={");
-        if (markIndex() >= 0)
-        {
-            for (int i = markIndex(); i < getIndex(); i++)
-            {
-                byte b =  peek(i);
-                TypeUtil.toHex(b,buf);
-            }
-            buf.append("}{");
-        }
-        int count = 0;
-        for (int i = getIndex(); i < putIndex(); i++)
-        {
-            byte b =  peek(i);
-            TypeUtil.toHex(b,buf);
-            if (count++ == 50)
-            {
-                if (putIndex() - i > 20)
-                {
-                    buf.append(" ... ");
-                    i = putIndex() - 20;
-                }
-            }
-        }
-        buf.append('}');
-        return buf.toString();
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public String toString()
-    {
-        if (isImmutable())
-        {
-            if (_string == null) 
-                _string = new String(asArray(), 0, length());
-            return _string;
-        }
-        return new String(asArray(), 0, length());
-    }
-
-    /* ------------------------------------------------------------ */
-    public String toString(String charset)
-    {
-        try
-        {
-            byte[] bytes=array();
-            if (bytes!=null)
-                return new String(bytes,getIndex(),length(),charset);
-            return new String(asArray(), 0, length(),charset);
-            
-        }
-        catch(Exception e)
-        {
-            LOG.warn(e);
-            return new String(asArray(), 0, length());
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    public String toString(Charset charset)
-    {
-        try
-        {
-            byte[] bytes=array();
-            if (bytes!=null)
-                return new String(bytes,getIndex(),length(),charset);
-            return new String(asArray(), 0, length(),charset);
-        }
-        catch(Exception e)
-        {
-            LOG.warn(e);
-            return new String(asArray(), 0, length());
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    public String toDebugString()
-    {
-        return getClass()+"@"+super.hashCode();
-    }
-
-    /* ------------------------------------------------------------ */
-    public void writeTo(OutputStream out)
-    	throws IOException
-    {
-        byte[] array = array();
-        
-        if (array!=null)
-        {
-            out.write(array,getIndex(),length());
-        }
-        else
-        {
-            int len = this.length();
-            byte[] buf=new byte[len>1024?1024:len];
-            int offset=_get;
-            while (len>0)
-            {
-                int l=peek(offset,buf,0,len>buf.length?buf.length:len);
-                out.write(buf,0,l);
-                offset+=l;
-                len-=l;
-            }
-        } 
-        clear();
-    }
-    
-    /* ------------------------------------------------------------ */
-    public int readFrom(InputStream in,int max) throws IOException
-    {
-        byte[] array = array();
-        int s=space();
-        if (s>max)
-            s=max;
-
-        if (array!=null)
-        {
-            int l=in.read(array,_put,s);
-            if (l>0)
-                _put+=l;
-            return l;
-        }
-        else
-        {
-            byte[] buf=new byte[s>1024?1024:s];
-            int total=0;
-            while (s>0)
-            {
-                int l=in.read(buf,0,buf.length);
-                if (l<0)
-                    return total>0?total:-1;
-                int p=put(buf,0,l);
-                assert l==p;
-                s-=l;
-            }
-            return total; 
-        }
-    }
-}
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractBuffers.java b/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractBuffers.java
deleted file mode 100644
index 22498c8..0000000
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractBuffers.java
+++ /dev/null
@@ -1,168 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.io;
-
-import org.eclipse.jetty.io.nio.DirectNIOBuffer;
-import org.eclipse.jetty.io.nio.IndirectNIOBuffer;
-
-public abstract class AbstractBuffers implements Buffers
-{
-    protected final Buffers.Type _headerType;
-    protected final int _headerSize;
-    protected final Buffers.Type _bufferType;
-    protected final int _bufferSize;
-    protected final Buffers.Type _otherType;
-
-    /* ------------------------------------------------------------ */
-    public AbstractBuffers(Buffers.Type headerType, int headerSize, Buffers.Type bufferType, int bufferSize, Buffers.Type otherType)
-    {
-        _headerType=headerType;
-        _headerSize=headerSize;
-        _bufferType=bufferType;
-        _bufferSize=bufferSize;
-        _otherType=otherType;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return Returns the buffer size in bytes.
-     */
-    public int getBufferSize()
-    {
-        return _bufferSize;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return Returns the header size in bytes.
-     */
-    public int getHeaderSize()
-    {
-        return _headerSize;
-    }
-
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Create a new header Buffer
-     * @return new Buffer
-     */
-    final protected Buffer newHeader()
-    {
-        switch(_headerType)
-        {
-            case BYTE_ARRAY:
-                return new ByteArrayBuffer(_headerSize);
-            case DIRECT:
-                return new DirectNIOBuffer(_headerSize);
-            case INDIRECT:
-                return new IndirectNIOBuffer(_headerSize);
-        }
-        throw new IllegalStateException();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Create a new content Buffer
-     * @return new Buffer
-     */
-    final protected Buffer newBuffer()
-    {
-       switch(_bufferType)
-       {
-           case BYTE_ARRAY:
-               return new ByteArrayBuffer(_bufferSize);
-           case DIRECT:
-               return new DirectNIOBuffer(_bufferSize);
-           case INDIRECT:
-               return new IndirectNIOBuffer(_bufferSize);
-       }
-       throw new IllegalStateException();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Create a new content Buffer
-     * @param size
-     * @return new Buffer
-     */
-    final protected Buffer newBuffer(int size)
-    {
-       switch(_otherType)
-       {
-           case BYTE_ARRAY:
-               return new ByteArrayBuffer(size);
-           case DIRECT:
-               return new DirectNIOBuffer(size);
-           case INDIRECT:
-               return new IndirectNIOBuffer(size);
-       }
-       throw new IllegalStateException();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param buffer
-     * @return True if the buffer is the correct type to be a Header buffer
-     */
-    public final boolean isHeader(Buffer buffer)
-    {
-        if (buffer.capacity()==_headerSize)
-        {
-            switch(_headerType)
-            {
-                case BYTE_ARRAY:
-                    return buffer instanceof ByteArrayBuffer && !(buffer instanceof  IndirectNIOBuffer);
-                case DIRECT:
-                    return buffer instanceof  DirectNIOBuffer;
-                case INDIRECT:
-                    return buffer instanceof  IndirectNIOBuffer;
-            }
-        }
-        return false;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param buffer
-     * @return True if the buffer is the correct type to be a Header buffer
-     */
-    public final boolean isBuffer(Buffer buffer)
-    {
-        if (buffer.capacity()==_bufferSize)
-        {
-            switch(_bufferType)
-            {
-                case BYTE_ARRAY:
-                    return buffer instanceof ByteArrayBuffer && !(buffer instanceof  IndirectNIOBuffer);
-                case DIRECT:
-                    return buffer instanceof  DirectNIOBuffer;
-                case INDIRECT:
-                    return buffer instanceof  IndirectNIOBuffer;
-            }
-        }
-        return false;
-    }
-
-    /* ------------------------------------------------------------ */
-    public String toString()
-    {
-        return String.format("%s [%d,%d]", getClass().getSimpleName(), _headerSize, _bufferSize);
-    }
-}
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractConnection.java b/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractConnection.java
index 017609d..2af1939 100644
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractConnection.java
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractConnection.java
@@ -18,68 +18,576 @@
 
 package org.eclipse.jetty.io;
 
-import java.io.IOException;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Executor;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicReference;
 
+import org.eclipse.jetty.util.Callback;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.NonBlockingThread;
 
-
+/**
+ * <p>A convenience base implementation of {@link Connection}.</p>
+ * <p>This class uses the capabilities of the {@link EndPoint} API to provide a
+ * more traditional style of async reading.  A call to {@link #fillInterested()}
+ * will schedule a callback to {@link #onFillable()} or {@link #onFillInterestedFailed(Throwable)}
+ * as appropriate.</p>
+ */
 public abstract class AbstractConnection implements Connection
 {
     private static final Logger LOG = Log.getLogger(AbstractConnection.class);
+    
+    public static final boolean EXECUTE_ONFILLABLE=true;
 
-    private final long _timeStamp;
-    protected final EndPoint _endp;
+    private final List<Listener> listeners = new CopyOnWriteArrayList<>();
+    private final AtomicReference<State> _state = new AtomicReference<>(IDLE);
+    private final long _created=System.currentTimeMillis();
+    private final EndPoint _endPoint;
+    private final Executor _executor;
+    private final Callback _readCallback;
+    private final boolean _executeOnfillable;
+    private int _inputBufferSize=2048;
 
-    public AbstractConnection(EndPoint endp)
+    protected AbstractConnection(EndPoint endp, Executor executor)
+    {
+        this(endp,executor,EXECUTE_ONFILLABLE);
+    }
+    
+    protected AbstractConnection(EndPoint endp, Executor executor, final boolean executeOnfillable)
     {
-        _endp=(EndPoint)endp;
-        _timeStamp = System.currentTimeMillis();
+        if (executor == null)
+            throw new IllegalArgumentException("Executor must not be null!");
+        _endPoint = endp;
+        _executor = executor;
+        _readCallback = new ReadCallback();
+        _executeOnfillable=executeOnfillable;
+        _state.set(IDLE);
     }
 
-    public AbstractConnection(EndPoint endp,long timestamp)
+    @Override
+    public void addListener(Listener listener)
     {
-        _endp=(EndPoint)endp;
-        _timeStamp = timestamp;
+        listeners.add(listener);
     }
 
-    public long getTimeStamp()
+    public int getInputBufferSize()
     {
-        return _timeStamp;
+        return _inputBufferSize;
     }
 
+    public void setInputBufferSize(int inputBufferSize)
+    {
+        _inputBufferSize = inputBufferSize;
+    }
+
+    protected Executor getExecutor()
+    {
+        return _executor;
+    }
+    
+    protected void failedCallback(final Callback callback, final Throwable x)
+    {
+        if (NonBlockingThread.isNonBlockingThread())
+        {
+            try
+            {
+                getExecutor().execute(new Runnable()
+                {
+                    @Override
+                    public void run()
+                    {
+                        callback.failed(x);
+                    }
+                });
+            }
+            catch(RejectedExecutionException e)
+            {
+                LOG.debug(e);
+                callback.failed(x);
+            }
+        }
+        else
+        {
+            callback.failed(x);
+        }
+    }
+    
+    /**
+     * <p>Utility method to be called to register read interest.</p>
+     * <p>After a call to this method, {@link #onFillable()} or {@link #onFillInterestedFailed(Throwable)}
+     * will be called back as appropriate.</p>
+     * @see #onFillable()
+     */
+    public void fillInterested()
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("fillInterested {}",this);
+        
+        while(true)
+        {
+            State state=_state.get();
+            if (next(state,state.fillInterested()))
+                break;
+        }
+    }
+    
+    public void fillInterested(Callback callback)
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("fillInterested {}",this);
+
+        while(true)
+        {
+            State state=_state.get();
+            // TODO yuck
+            if (state instanceof FillingInterestedCallback && ((FillingInterestedCallback)state)._callback==callback)
+                break;
+            State next=new FillingInterestedCallback(callback,state);
+            if (next(state,next))
+                break;
+        }
+    }
+    
+    /**
+     * <p>Callback method invoked when the endpoint is ready to be read.</p>
+     * @see #fillInterested()
+     */
+    public abstract void onFillable();
+
+    /**
+     * <p>Callback method invoked when the endpoint failed to be ready to be read.</p>
+     * @param cause the exception that caused the failure
+     */
+    protected void onFillInterestedFailed(Throwable cause)
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("{} onFillInterestedFailed {}", this, cause);
+        if (_endPoint.isOpen())
+        {
+            boolean close = true;
+            if (cause instanceof TimeoutException)
+                close = onReadTimeout();
+            if (close)
+            {
+                if (_endPoint.isOutputShutdown())
+                    _endPoint.close();
+                else
+                    _endPoint.shutdownOutput();
+            }
+        }
+
+        if (_endPoint.isOpen())
+            fillInterested();        
+    }
+
+    /**
+     * <p>Callback method invoked when the endpoint failed to be ready to be read after a timeout</p>
+     * @return true to signal that the endpoint must be closed, false to keep the endpoint open
+     */
+    protected boolean onReadTimeout()
+    {
+        return true;
+    }
+
+    @Override
+    public void onOpen()
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("onOpen {}", this);
+
+        for (Listener listener : listeners)
+            listener.onOpened(this);
+    }
+
+    @Override
+    public void onClose()
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("onClose {}",this);
+
+        for (Listener listener : listeners)
+            listener.onClosed(this);
+    }
+
+    @Override
     public EndPoint getEndPoint()
     {
-        return _endp;
+        return _endPoint;
     }
 
-    public void onIdleExpired(long idleForMs)
+    @Override
+    public void close()
     {
-        try
+        getEndPoint().close();
+    }
+
+    @Override
+    public int getMessagesIn()
+    {
+        return -1;
+    }
+
+    @Override
+    public int getMessagesOut()
+    {
+        return -1;
+    }
+
+    @Override
+    public long getBytesIn()
+    {
+        return -1;
+    }
+
+    @Override
+    public long getBytesOut()
+    {
+        return -1;
+    }
+
+    @Override
+    public long getCreatedTimeStamp()
+    {
+        return _created;
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s@%x{%s}", getClass().getSimpleName(), hashCode(), _state.get());
+    }
+    
+    public boolean next(State state, State next)
+    {
+        if (next==null)
+            return true;
+        if(_state.compareAndSet(state,next))
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("{}-->{} {}",state,next,this);
+            if (next!=state)
+                next.onEnter(AbstractConnection.this);
+            return true;
+        }
+        return false;
+    }
+    
+    private static final class IdleState extends State
+    {
+        private IdleState()
         {
-            LOG.debug("onIdleExpired {}ms {} {}",idleForMs,this,_endp);
-            if (_endp.isInputShutdown() || _endp.isOutputShutdown())
-                _endp.close();
+            super("IDLE");
+        }
+
+        @Override
+        State fillInterested()
+        {
+            return FILL_INTERESTED;
+        }
+    }
+
+
+    private static final class FillInterestedState extends State
+    {
+        private FillInterestedState()
+        {
+            super("FILL_INTERESTED");
+        }
+
+        @Override
+        public void onEnter(AbstractConnection connection)
+        {
+            connection.getEndPoint().fillInterested(connection._readCallback);
+        }
+
+        @Override
+        State fillInterested()
+        {
+            return this;
+        }
+
+        @Override
+        public State onFillable()
+        {
+            return FILLING;
+        }
+
+        @Override
+        State onFailed()
+        {
+            return IDLE;
+        }
+    }
+
+
+    private static final class RefillingState extends State
+    {
+        private RefillingState()
+        {
+            super("REFILLING");
+        }
+
+        @Override
+        State fillInterested()
+        {
+            return FILLING_FILL_INTERESTED;
+        }
+
+        @Override
+        public State onFilled()
+        {
+            return IDLE;
+        }
+    }
+
+
+    private static final class FillingFillInterestedState extends State
+    {
+        private FillingFillInterestedState(String name)
+        {
+            super(name);
+        }
+
+        @Override
+        State fillInterested()
+        {
+            return this;
+        }
+
+        State onFilled()
+        {
+            return FILL_INTERESTED;
+        }
+    }
+
+
+    private static final class FillingState extends State
+    {
+        private FillingState()
+        {
+            super("FILLING");
+        }
+
+        @Override
+        public void onEnter(AbstractConnection connection)
+        {
+            if (connection._executeOnfillable)
+                connection.getExecutor().execute(connection._runOnFillable);
             else
-                _endp.shutdownOutput();
+                connection._runOnFillable.run();
+        }
+
+        @Override
+        State fillInterested()
+        {
+            return FILLING_FILL_INTERESTED;
+        }
+
+        @Override
+        public State onFilled()
+        {
+            return IDLE;
+        }
+    }
+
+
+    public static class State
+    {
+        private final String _name;
+        State(String name)
+        {
+            _name=name;
+        }
+
+        @Override
+        public String toString()
+        {
+            return _name;
+        }
+        
+        void onEnter(AbstractConnection connection)
+        {
+        }
+        
+        State fillInterested()
+        {
+            throw new IllegalStateException(this.toString());
+        }
+
+        State onFillable()
+        {
+            throw new IllegalStateException(this.toString());
+        }
+
+        State onFilled()
+        {
+            throw new IllegalStateException(this.toString());
+        }
+        
+        State onFailed()
+        {
+            throw new IllegalStateException(this.toString());
+        }
+    }
+    
+
+    public static final State IDLE=new IdleState();
+    
+    public static final State FILL_INTERESTED=new FillInterestedState();
+    
+    public static final State FILLING=new FillingState();
+    
+    public static final State REFILLING=new RefillingState();
+
+    public static final State FILLING_FILL_INTERESTED=new FillingFillInterestedState("FILLING_FILL_INTERESTED");
+    
+    public class NestedState extends State
+    {
+        private final State _nested;
+        
+        NestedState(State nested)
+        {
+            super("NESTED("+nested+")");
+            _nested=nested;
+        }
+        NestedState(String name,State nested)
+        {
+            super(name+"("+nested+")");
+            _nested=nested;
+        }
+
+        @Override
+        State fillInterested()
+        {
+            return new NestedState(_nested.fillInterested());
+        }
+
+        @Override
+        State onFillable()
+        {
+            return new NestedState(_nested.onFillable());
+        }
+        
+        @Override
+        State onFilled()
+        {
+            return new NestedState(_nested.onFilled());
+        }
+    }
+    
+    
+    public class FillingInterestedCallback extends NestedState
+    {
+        private final Callback _callback;
+        
+        FillingInterestedCallback(Callback callback,State nested)
+        {
+            super("FILLING_INTERESTED_CALLBACK",nested==FILLING?REFILLING:nested);
+            _callback=callback;
         }
-        catch(IOException e)
+
+        @Override
+        void onEnter(final AbstractConnection connection)
         {
-            LOG.ignore(e);
+            Callback callback=new Callback()
+            {
+                @Override
+                public void succeeded()
+                {
+                    while(true)
+                    {
+                        State state = connection._state.get();
+                        if (!(state instanceof NestedState))
+                            break;
+                        State nested=((NestedState)state)._nested;
+                        if (connection.next(state,nested))
+                            break;
+                    }
+                    _callback.succeeded();
+                }
 
+                @Override
+                public void failed(Throwable x)
+                {
+                    while(true)
+                    {
+                        State state = connection._state.get();
+                        if (!(state instanceof NestedState))
+                            break;
+                        State nested=((NestedState)state)._nested;
+                        if (connection.next(state,nested))
+                            break;
+                    }
+                    _callback.failed(x);
+                }  
+            };
+            
+            connection.getEndPoint().fillInterested(callback);
+        }
+    }
+    
+    private final Runnable _runOnFillable = new Runnable()
+    {
+        @Override
+        public void run()
+        {
             try
             {
-                _endp.close();
+                onFillable();
             }
-            catch(IOException e2)
+            finally
             {
-                LOG.ignore(e2);
+                while(true)
+                {
+                    State state=_state.get();
+                    if (next(state,state.onFilled()))
+                        break;
+                }
+            }
+        }
+    };
+    
+    
+    private class ReadCallback implements Callback
+    {   
+        @Override
+        public void succeeded()
+        {
+            while(true)
+            {
+                State state=_state.get();
+                if (next(state,state.onFillable()))
+                    break;
             }
         }
-    }
 
-    public String toString()
-    {
-        return String.format("%s@%x", getClass().getSimpleName(), hashCode());
-    }
+        @Override
+        public void failed(final Throwable x)
+        {
+            _executor.execute(new Runnable()
+            {
+                @Override
+                public void run()
+                {
+                    while(true)
+                    {
+                        State state=_state.get();
+                        if (next(state,state.onFailed()))
+                            break;
+                    }
+                    onFillInterestedFailed(x);
+                }
+            });
+        }
+        
+        @Override
+        public String toString()
+        {
+            return String.format("AC.ReadCB@%x{%s}", AbstractConnection.this.hashCode(),AbstractConnection.this);
+        }
+    };
 }
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractEndPoint.java b/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractEndPoint.java
new file mode 100644
index 0000000..19abf0b
--- /dev/null
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractEndPoint.java
@@ -0,0 +1,205 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.util.concurrent.TimeoutException;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+public abstract class AbstractEndPoint extends IdleTimeout implements EndPoint
+{
+    private static final Logger LOG = Log.getLogger(AbstractEndPoint.class);
+    private final long _created=System.currentTimeMillis();
+    private final InetSocketAddress _local;
+    private final InetSocketAddress _remote;
+    private volatile Connection _connection;
+
+    private final FillInterest _fillInterest = new FillInterest()
+    {
+        @Override
+        protected boolean needsFill() throws IOException
+        {
+            return AbstractEndPoint.this.needsFill();
+        }
+    };
+    
+    private final WriteFlusher _writeFlusher = new WriteFlusher(this)
+    {
+        @Override
+        protected void onIncompleteFlushed()
+        {
+            AbstractEndPoint.this.onIncompleteFlush();
+        }
+    };
+
+    protected AbstractEndPoint(Scheduler scheduler,InetSocketAddress local,InetSocketAddress remote)
+    {
+        super(scheduler);
+        _local=local;
+        _remote=remote;
+    }
+
+    @Override
+    public long getCreatedTimeStamp()
+    {
+        return _created;
+    }
+
+    @Override
+    public InetSocketAddress getLocalAddress()
+    {
+        return _local;
+    }
+
+    @Override
+    public InetSocketAddress getRemoteAddress()
+    {
+        return _remote;
+    }
+    
+    @Override
+    public Connection getConnection()
+    {
+        return _connection;
+    }
+
+    @Override
+    public void setConnection(Connection connection)
+    {
+        _connection = connection;
+    }
+
+    @Override
+    public void onOpen()
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("onOpen {}",this);
+        super.onOpen();
+    }
+
+    @Override
+    public void onClose()
+    {
+        super.onClose();
+        if (LOG.isDebugEnabled())
+            LOG.debug("onClose {}",this);
+        _writeFlusher.onClose();
+        _fillInterest.onClose();
+    }
+    
+    @Override
+    public void close()
+    {
+        onClose();
+    }
+
+    @Override
+    public void fillInterested(Callback callback) throws IllegalStateException
+    {
+        notIdle();
+        _fillInterest.register(callback);
+    }
+
+    @Override
+    public void write(Callback callback, ByteBuffer... buffers) throws IllegalStateException
+    {
+        _writeFlusher.write(callback, buffers);
+    }
+
+    protected abstract void onIncompleteFlush();
+
+    protected abstract boolean needsFill() throws IOException;
+
+    protected FillInterest getFillInterest()
+    {
+        return _fillInterest;
+    }
+
+    protected WriteFlusher getWriteFlusher()
+    {
+        return _writeFlusher;
+    }
+
+    @Override
+    protected void onIdleExpired(TimeoutException timeout)
+    {
+        boolean output_shutdown=isOutputShutdown();
+        boolean input_shutdown=isInputShutdown();
+        boolean fillFailed = _fillInterest.onFail(timeout);
+        boolean writeFailed = _writeFlusher.onFail(timeout);
+        
+        // If the endpoint is half closed and there was no fill/write handling, then close here.
+        // This handles the situation where the connection has completed its close handling 
+        // and the endpoint is half closed, but the other party does not complete the close.
+        // This perhaps should not check for half closed, however the servlet spec case allows
+        // for a dispatched servlet or suspended request to extend beyond the connections idle 
+        // time.  So if this test would always close an idle endpoint that is not handled, then 
+        // we would need a mode to ignore timeouts for some HTTP states
+        if (isOpen() && (output_shutdown || input_shutdown) && !(fillFailed || writeFailed))
+            close();
+        else 
+            LOG.debug("Ignored idle endpoint {}",this);
+    }
+
+    @Override
+    public void upgrade(Connection newConnection)
+    {
+        Connection old_connection = getConnection();
+
+        if (LOG.isDebugEnabled())
+            LOG.debug("{} upgradeing from {} to {}", this, old_connection, newConnection);
+        
+        ByteBuffer prefilled = (old_connection instanceof Connection.UpgradeFrom)
+                ?((Connection.UpgradeFrom)old_connection).onUpgradeFrom():null;
+        old_connection.onClose();
+        old_connection.getEndPoint().setConnection(newConnection);
+        
+        if (newConnection instanceof Connection.UpgradeTo)
+            ((Connection.UpgradeTo)newConnection).onUpgradeTo(prefilled);
+        else if (BufferUtil.hasContent(prefilled))
+            throw new IllegalStateException();
+
+        newConnection.onOpen();
+    }
+    
+    @Override
+    public String toString()
+    {
+        return String.format("%s@%x{%s<->%d,%s,%s,%s,%s,%s,%d/%d,%s}",
+                getClass().getSimpleName(),
+                hashCode(),
+                getRemoteAddress(),
+                getLocalAddress().getPort(),
+                isOpen()?"Open":"CLOSED",
+                isInputShutdown()?"ISHUT":"in",
+                isOutputShutdown()?"OSHUT":"out",
+                _fillInterest.isInterested()?"R":"-",
+                _writeFlusher.isInProgress()?"W":"-",
+                getIdleFor(),
+                getIdleTimeout(),
+                getConnection()==null?null:getConnection().getClass().getSimpleName());
+    }
+}
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/ArrayByteBufferPool.java b/jetty-io/src/main/java/org/eclipse/jetty/io/ArrayByteBufferPool.java
new file mode 100644
index 0000000..32acd9d
--- /dev/null
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/ArrayByteBufferPool.java
@@ -0,0 +1,133 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.nio.ByteBuffer;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+import org.eclipse.jetty.util.BufferUtil;
+
+public class ArrayByteBufferPool implements ByteBufferPool
+{
+    private final int _min;
+    private final Bucket[] _direct;
+    private final Bucket[] _indirect;
+    private final int _inc;
+
+    public ArrayByteBufferPool()
+    {
+        this(0,1024,64*1024);
+    }
+
+    public ArrayByteBufferPool(int minSize, int increment, int maxSize)
+    {
+        if (minSize>=increment)
+            throw new IllegalArgumentException("minSize >= increment");
+        if ((maxSize%increment)!=0 || increment>=maxSize)
+            throw new IllegalArgumentException("increment must be a divisor of maxSize");
+        _min=minSize;
+        _inc=increment;
+
+        _direct=new Bucket[maxSize/increment];
+        _indirect=new Bucket[maxSize/increment];
+
+        int size=0;
+        for (int i=0;i<_direct.length;i++)
+        {
+            size+=_inc;
+            _direct[i]=new Bucket(size);
+            _indirect[i]=new Bucket(size);
+        }
+    }
+
+    @Override
+    public ByteBuffer acquire(int size, boolean direct)
+    {
+        Bucket bucket = bucketFor(size,direct);
+        ByteBuffer buffer = bucket==null?null:bucket._queue.poll();
+
+        if (buffer == null)
+        {
+            int capacity = bucket==null?size:bucket._size;
+            buffer = direct ? BufferUtil.allocateDirect(capacity) : BufferUtil.allocate(capacity);
+        }
+
+        return buffer;
+    }
+
+    @Override
+    public void release(ByteBuffer buffer)
+    {
+        if (buffer!=null)
+        {    
+            Bucket bucket = bucketFor(buffer.capacity(),buffer.isDirect());
+            if (bucket!=null)
+            {
+                BufferUtil.clear(buffer);
+                bucket._queue.offer(buffer);
+            }
+        }
+    }
+
+    public void clear()
+    {
+        for (int i=0;i<_direct.length;i++)
+        {
+            _direct[i]._queue.clear();
+            _indirect[i]._queue.clear();
+        }
+    }
+
+    private Bucket bucketFor(int size,boolean direct)
+    {
+        if (size<=_min)
+            return null;
+        int b=(size-1)/_inc;
+        if (b>=_direct.length)
+            return null;
+        Bucket bucket = direct?_direct[b]:_indirect[b];
+                
+        return bucket;
+    }
+
+    public static class Bucket
+    {
+        public final int _size;
+        public final Queue<ByteBuffer> _queue= new ConcurrentLinkedQueue<>();
+
+        Bucket(int size)
+        {
+            _size=size;
+        }
+        
+        @Override
+        public String toString()
+        {
+            return String.format("Bucket@%x{%d,%d}",hashCode(),_size,_queue.size());
+        }
+    }
+    
+
+    // Package local for testing
+    Bucket[] bucketsFor(boolean direct)
+    {
+        return direct ? _direct : _indirect;
+    }
+}
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/AsyncEndPoint.java b/jetty-io/src/main/java/org/eclipse/jetty/io/AsyncEndPoint.java
deleted file mode 100644
index 02a4363..0000000
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/AsyncEndPoint.java
+++ /dev/null
@@ -1,83 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.io;
-
-import org.eclipse.jetty.util.thread.Timeout;
-
-public interface AsyncEndPoint extends ConnectedEndPoint
-{
-    /* ------------------------------------------------------------ */
-    /**
-     * Dispatch the endpoint if it is not already dispatched
-     * 
-     */
-    public void dispatch();
-    
-    /* ------------------------------------------------------------ */
-    /**
-     * Dispatch the endpoint. If it is already dispatched, schedule a redispatch
-     * 
-     */
-    public void asyncDispatch();
-    
-    /* ------------------------------------------------------------ */
-    /** Schedule a write dispatch.
-     * Set the endpoint to not be writable and schedule a dispatch when
-     * it becomes writable.
-     */
-    public void scheduleWrite();  
-
-    /* ------------------------------------------------------------ */
-    /** Callback when idle.
-     * <p>An endpoint is idle if there has been no IO activity for 
-     * {@link #getMaxIdleTime()} and {@link #isCheckForIdle()} is true.
-     * @param idleForMs TODO
-     */
-    public void onIdleExpired(long idleForMs);
-
-    /* ------------------------------------------------------------ */
-    /** Set if the endpoint should be checked for idleness
-     */
-    public void setCheckForIdle(boolean check);
-
-    /* ------------------------------------------------------------ */
-    /** Get if the endpoint should be checked for idleness
-     */
-    public boolean isCheckForIdle();
-
-    
-    /* ------------------------------------------------------------ */
-    public boolean isWritable();
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return True if IO has been successfully performed since the last call to {@link #hasProgressed()}
-     */
-    public boolean hasProgressed();
-    
-    /* ------------------------------------------------------------ */
-    /**
-     */
-    public void scheduleTimeout(Timeout.Task task, long timeoutMs);
-
-    /* ------------------------------------------------------------ */
-    /**
-     */
-    public void cancelTimeout(Timeout.Task task);
-}
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/Buffer.java b/jetty-io/src/main/java/org/eclipse/jetty/io/Buffer.java
deleted file mode 100644
index 2e0e8cb..0000000
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/Buffer.java
+++ /dev/null
@@ -1,380 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.io;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.nio.charset.Charset;
-
-
-/**
- * Byte Buffer interface.
- * 
- * This is a byte buffer that is designed to work like a FIFO for bytes. Puts and Gets operate on different
- * pointers into the buffer and the valid _content of the buffer is always between the getIndex and the putIndex.
- * 
- * This buffer interface is designed to be similar, but not dependent on the java.nio buffers, which may
- * be used to back an implementation of this Buffer. The main difference is that NIO buffer after a put have 
- * their valid _content before the position and a flip is required to access that data.
- *
- * For this buffer it is always true that:
- *  markValue <= getIndex <= putIndex <= capacity
- *  
- *
- * @version 1.0
- */
-public interface Buffer extends Cloneable
-{
-    public final static int 
-      IMMUTABLE=0,  // neither indexes or contexts can be changed
-      READONLY=1,   // indexes may be changed, but not content
-      READWRITE=2;  // anything can be changed
-    public final boolean VOLATILE=true;     // The buffer may change outside of current scope.
-    public final boolean NON_VOLATILE=false;
-
-    /**
-     *  Get the underlying array, if one exists.
-     * @return a <code>byte[]</code> backing this buffer or null if none exists.
-     */
-    byte[] array();
-    
-    /**
-     * 
-     * @return a <code>byte[]</code> value of the bytes from the getIndex to the putIndex.
-     */
-    byte[] asArray();
-    
-    /** 
-     * Get the underlying buffer. If this buffer wraps a backing buffer.
-     * @return The root backing buffer or this if there is no backing buffer;
-     */
-    Buffer buffer();
-    
-    /**
-     * 
-     * @return a non volatile version of this <code>Buffer</code> value
-     */
-    Buffer asNonVolatileBuffer();
-
-    /**
-     *
-     * @return a readonly version of this <code>Buffer</code>.
-     */
-    Buffer asReadOnlyBuffer();
-
-    /**
-     *
-     * @return an immutable version of this <code>Buffer</code>.
-     */
-    Buffer asImmutableBuffer();
-
-    /**
-     *
-     * @return an immutable version of this <code>Buffer</code>.
-     */
-    Buffer asMutableBuffer();
-    
-    /**
-     * 
-     * The capacity of the buffer. This is the maximum putIndex that may be set.
-     * @return an <code>int</code> value
-     */
-    int capacity();
-    
-    /**
-     * the space remaining in the buffer.
-     * @return capacity - putIndex
-     */
-    int space();
-    
-    /**
-     * Clear the buffer. getIndex=0, putIndex=0.
-     */
-    void clear();
-
-    /**
-     * Compact the buffer by discarding bytes before the postion (or mark if set).
-     * Bytes from the getIndex (or mark) to the putIndex are moved to the beginning of 
-     * the buffer and the values adjusted accordingly.
-     */
-    void compact();
-    
-    /**
-     * Get the byte at the current getIndex and increment it.
-     * @return The <code>byte</code> value from the current getIndex.
-     */
-    byte get();
-    
-    /**
-     * Get bytes from the current postion and put them into the passed byte array.
-     * The getIndex is incremented by the number of bytes copied into the array.
-     * @param b The byte array to fill.
-     * @param offset Offset in the array.
-     * @param length The max number of bytes to read.
-     * @return The number of bytes actually read.
-     */
-    int get(byte[] b, int offset, int length);
-
-    /**
-     * 
-     * @param length an <code>int</code> value
-     * @return a <code>Buffer</code> value
-     */
-    Buffer get(int length);
-
-    /**
-     * The index within the buffer that will next be read or written.
-     * @return an <code>int</code> value >=0 <= putIndex()
-     */
-    int getIndex();
-    
-    /**
-     * @return true of putIndex > getIndex
-     */
-    boolean hasContent();
-    
-    /**
-     * 
-     * @return a <code>boolean</code> value true if case sensitive comparison on this buffer
-     */
-    boolean equalsIgnoreCase(Buffer buffer);
-
-
-    /**
-     * 
-     * @return a <code>boolean</code> value true if the buffer is immutable and that neither
-     * the buffer contents nor the indexes may be changed.
-     */
-    boolean isImmutable();
-    
-    /**
-     * 
-     * @return a <code>boolean</code> value true if the buffer is readonly. The buffer indexes may
-     * be modified, but the buffer contents may not. For example a View onto an immutable Buffer will be
-     * read only.
-     */
-    boolean isReadOnly();
-    
-    /**
-     * 
-     * @return a <code>boolean</code> value true if the buffer contents may change 
-     * via alternate paths than this buffer.  If the contents of this buffer are to be used outside of the
-     * current context, then a copy must be made.
-     */
-    boolean isVolatile();
-
-    /**
-     * The number of bytes from the getIndex to the putIndex
-     * @return an <code>int</code> == putIndex()-getIndex()
-     */
-    int length();
-    
-    /**
-     * Set the mark to the current getIndex.
-     */
-    void mark();
-    
-    /**
-     * Set the mark relative to the current getIndex
-     * @param offset an <code>int</code> value to add to the current getIndex to obtain the mark value.
-     */
-    void mark(int offset);
-
-    /**
-     * The current index of the mark.
-     * @return an <code>int</code> index in the buffer or -1 if the mark is not set.
-     */
-    int markIndex();
-
-    /**
-     * Get the byte at the current getIndex without incrementing the getIndex.
-     * @return The <code>byte</code> value from the current getIndex.
-     */
-    byte peek();
-  
-    /**
-     * Get the byte at a specific index in the buffer.
-     * @param index an <code>int</code> value
-     * @return a <code>byte</code> value
-     */
-    byte peek(int index);
-
-    /**
-     * 
-     * @param index an <code>int</code> value
-     * @param length an <code>int</code> value
-     * @return The <code>Buffer</code> value from the requested getIndex.
-     */
-    Buffer peek(int index, int length);
-
-    /**
-     * 
-     * @param index an <code>int</code> value
-     * @param b The byte array to peek into
-     * @param offset The offset into the array to start peeking
-     * @param length an <code>int</code> value
-     * @return The number of bytes actually peeked
-     */
-    int peek(int index, byte[] b, int offset, int length);
-    
-    /**
-     * Put the contents of the buffer at the specific index.
-     * @param index an <code>int</code> value
-     * @param src a <code>Buffer</code>. If the source buffer is not modified
-    
-     * @return The number of bytes actually poked
-     */
-    int poke(int index, Buffer src);
-    
-    /**
-     * Put a specific byte to a specific getIndex.
-     * @param index an <code>int</code> value
-     * @param b a <code>byte</code> value
-     */
-    void poke(int index, byte b);
-    
-    /**
-     * Put a specific byte to a specific getIndex.
-     * @param index an <code>int</code> value
-     * @param b a <code>byte array</code> value
-     * @return The number of bytes actually poked
-     */
-    int poke(int index, byte b[], int offset, int length);
-    
-    /**
-     * Write the bytes from the source buffer to the current getIndex.
-     * @param src The source <code>Buffer</code> it is not modified.
-     * @return The number of bytes actually poked
-     */
-    int put(Buffer src);
-
-    /**
-     * Put a byte to the current getIndex and increment the getIndex.
-     * @param b a <code>byte</code> value
-     */
-    void put(byte b);
-    
-    /**
-     * Put a byte to the current getIndex and increment the getIndex.
-     * @param b a <code>byte</code> value
-     * @return The number of bytes actually poked
-     */
-    int put(byte[] b,int offset, int length);
-
-    /**
-     * Put a byte to the current getIndex and increment the getIndex.
-     * @param b a <code>byte</code> value
-     * @return The number of bytes actually poked
-     */
-    int put(byte[] b);
-
-    /**
-     * The index of the first element that should not be read.
-     * @return an <code>int</code> value >= getIndex() 
-     */
-    int putIndex();
-    
-    /**
-     * Reset the current getIndex to the mark 
-     */
-    void reset();
-    
-    /**
-     * Set the buffers start getIndex.
-     * @param newStart an <code>int</code> value
-     */
-    void setGetIndex(int newStart);
-    
-    /**
-     * Set a specific value for the mark.
-     * @param newMark an <code>int</code> value
-     */
-    void setMarkIndex(int newMark);
-    
-    /**
-     * 
-     * @param newLimit an <code>int</code> value
-     */
-    void setPutIndex(int newLimit);
-    
-    /**
-     * Skip _content. The getIndex is updated by min(remaining(), n)
-     * @param n The number of bytes to skip
-     * @return the number of bytes skipped.
-     */
-    int skip(int n);
-
-    /**
-     * 
-     * @return a volitile <code>Buffer</code> from the postion to the putIndex.
-     */
-    Buffer slice();
-    
-    /**
-     * 
-     *
-     * @return a volitile <code>Buffer</code> value from the mark to the putIndex
-     */
-    Buffer sliceFromMark();
-    
-    /**
-     * 
-     *
-     * @param length an <code>int</code> value
-     * @return a valitile <code>Buffer</code> value from the mark of the length requested.
-     */
-    Buffer sliceFromMark(int length);
-    
-    /**
-     * 
-     * @return a <code>String</code> value describing the state and contents of the buffer.
-     */
-    String toDetailString();
-
-    /* ------------------------------------------------------------ */
-    /** Write the buffer's contents to the output stream
-     * @param out
-     */
-    void writeTo(OutputStream out) throws IOException;
-
-    /* ------------------------------------------------------------ */
-    /** Read the buffer's contents from the input stream
-     * @param in input stream
-     * @param max maximum number of bytes that may be read
-     * @return actual number of bytes read or -1 for EOF
-     */
-    int readFrom(InputStream in, int max) throws IOException;
-    
-
-    /* ------------------------------------------------------------ */
-    String toString(String charset);
-    
-    /* ------------------------------------------------------------ */
-    String toString(Charset charset);
-
-    /*
-     * Buffers implementing this interface should be compared with case insensitive equals
-     *
-     */
-    public interface CaseInsensitve
-    {}
-
-    
-}
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/BufferCache.java b/jetty-io/src/main/java/org/eclipse/jetty/io/BufferCache.java
deleted file mode 100644
index 4f38f5f..0000000
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/BufferCache.java
+++ /dev/null
@@ -1,169 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.io;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Map.Entry;
-
-import org.eclipse.jetty.util.StringMap;
-
-/* ------------------------------------------------------------------------------- */
-/** 
- * Stores a collection of {@link Buffer} objects.
- * Buffers are stored in an ordered collection and can retreived by index or value
- * 
- */
-public class BufferCache
-{
-    private final HashMap _bufferMap=new HashMap();
-    private final StringMap _stringMap=new StringMap(StringMap.CASE_INSENSTIVE);
-    private final ArrayList _index= new ArrayList();
-
-    /* ------------------------------------------------------------------------------- */
-    /** Add a buffer to the cache at the specified index.
-     * @param value The content of the buffer.
-     */
-    public CachedBuffer add(String value, int ordinal)
-    {
-        CachedBuffer buffer= new CachedBuffer(value, ordinal);
-        _bufferMap.put(buffer, buffer);
-        _stringMap.put(value, buffer);
-        while ((ordinal - _index.size()) >= 0)
-            _index.add(null);
-        if (_index.get(ordinal)==null)
-            _index.add(ordinal, buffer);
-        return buffer;
-    }
-
-    public CachedBuffer get(int ordinal)
-    {
-        if (ordinal < 0 || ordinal >= _index.size())
-            return null;
-        return (CachedBuffer)_index.get(ordinal);
-    }
-
-    public CachedBuffer get(Buffer buffer)
-    {
-        return (CachedBuffer)_bufferMap.get(buffer);
-    }
-
-    public CachedBuffer get(String value)
-    {
-        return (CachedBuffer)_stringMap.get(value);
-    }
-
-    public Buffer lookup(Buffer buffer)
-    {
-        if (buffer instanceof CachedBuffer)
-            return buffer;
-        
-        Buffer b= get(buffer);
-        if (b == null)
-        {
-            if (buffer instanceof Buffer.CaseInsensitve)
-                return buffer;
-            return new ByteArrayBuffer.CaseInsensitive(buffer.asArray(),0,buffer.length(),Buffer.IMMUTABLE);
-        }
-
-        return b;
-    }
-    
-    public CachedBuffer getBest(byte[] value, int offset, int maxLength)
-    {
-        Entry entry = _stringMap.getBestEntry(value, offset, maxLength);
-        if (entry!=null)
-            return (CachedBuffer)entry.getValue();
-        return null;
-    }
-
-    public Buffer lookup(String value)
-    {
-        Buffer b= get(value);
-        if (b == null)
-        {
-            return new CachedBuffer(value,-1);
-        }
-        return b;
-    }
-
-    public String toString(Buffer buffer)
-    {
-        return lookup(buffer).toString();
-    }
-
-    public int getOrdinal(String value)
-    {
-        CachedBuffer buffer = (CachedBuffer)_stringMap.get(value);
-        return buffer==null?-1:buffer.getOrdinal();
-    }
-    
-    public int getOrdinal(Buffer buffer)
-    {
-        if (buffer instanceof CachedBuffer)
-            return ((CachedBuffer)buffer).getOrdinal();
-        buffer=lookup(buffer);
-        if (buffer!=null && buffer instanceof CachedBuffer)
-            return ((CachedBuffer)buffer).getOrdinal();
-        return -1;
-    }
-    
-    public static class CachedBuffer extends ByteArrayBuffer.CaseInsensitive
-    {
-        private final int _ordinal;
-        private HashMap _associateMap=null;
-        
-        public CachedBuffer(String value, int ordinal)
-        {
-            super(value);
-            _ordinal= ordinal;
-        }
-
-        public int getOrdinal()
-        {
-            return _ordinal;
-        }
-
-        public CachedBuffer getAssociate(Object key)
-        {
-            if (_associateMap==null)
-                return null;
-            return (CachedBuffer)_associateMap.get(key);
-        }
-
-        // TODO Replace Associate with a mime encoding specific solution
-        public void setAssociate(Object key, CachedBuffer associate)
-        {
-            if (_associateMap==null)
-                _associateMap=new HashMap();
-            _associateMap.put(key,associate);
-        }
-    }
-    
-    
-    @Override
-    public String toString()
-    {
-        return "CACHE["+
-        	"bufferMap="+_bufferMap+
-        	",stringMap="+_stringMap+
-        	",index="+_index+
-        	"]";
-    }
-}
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/BufferDateCache.java b/jetty-io/src/main/java/org/eclipse/jetty/io/BufferDateCache.java
deleted file mode 100644
index 11b8ae2..0000000
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/BufferDateCache.java
+++ /dev/null
@@ -1,61 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.io;
-
-import java.text.DateFormatSymbols;
-import java.util.Locale;
-
-import org.eclipse.jetty.util.DateCache;
-
-public class BufferDateCache extends DateCache
-{
-    Buffer _buffer;
-    String _last;
-    
-    public BufferDateCache()
-    {
-        super();
-    }
-
-    public BufferDateCache(String format, DateFormatSymbols s)
-    {
-        super(format,s);
-    }
-
-    public BufferDateCache(String format, Locale l)
-    {
-        super(format,l);
-    }
-
-    public BufferDateCache(String format)
-    {
-        super(format);
-    }
-
-    public synchronized Buffer formatBuffer(long date)
-    {
-        String d = super.format(date);
-        if (d==_last)
-            return _buffer;
-        _last=d;
-        _buffer=new ByteArrayBuffer(d);
-        
-        return _buffer;
-    }
-}
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/BufferUtil.java b/jetty-io/src/main/java/org/eclipse/jetty/io/BufferUtil.java
deleted file mode 100644
index d3c450c..0000000
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/BufferUtil.java
+++ /dev/null
@@ -1,359 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.io;
-
-import org.eclipse.jetty.io.BufferCache.CachedBuffer;
-import org.eclipse.jetty.util.StringUtil;
-
-/* ------------------------------------------------------------------------------- */
-/** Buffer utility methods.
- * 
- * 
- */
-public class BufferUtil
-{
-    static final byte SPACE= 0x20;
-    static final byte MINUS= '-';
-    static final byte[] DIGIT=
-    {(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'};
-
-    /**
-     * Convert buffer to an integer.
-     * Parses up to the first non-numeric character. If no number is found an
-     * IllegalArgumentException is thrown
-     * @param buffer A buffer containing an integer. The position is not changed.
-     * @return an int 
-     */
-    public static int toInt(Buffer buffer)
-    {
-        int val= 0;
-        boolean started= false;
-        boolean minus= false;
-        for (int i= buffer.getIndex(); i < buffer.putIndex(); i++)
-        {
-            byte b= buffer.peek(i);
-            if (b <= SPACE)
-            {
-                if (started)
-                    break;
-            }
-            else if (b >= '0' && b <= '9')
-            {
-                val= val * 10 + (b - '0');
-                started= true;
-            }
-            else if (b == MINUS && !started)
-            {
-                minus= true;
-            }
-            else
-                break;
-        }
-
-        if (started)
-            return minus ? (-val) : val;
-        throw new NumberFormatException(buffer.toString());
-    }
-    
-    /**
-     * Convert buffer to an long.
-     * Parses up to the first non-numeric character. If no number is found an
-     * IllegalArgumentException is thrown
-     * @param buffer A buffer containing an integer. The position is not changed.
-     * @return an int 
-     */
-    public static long toLong(Buffer buffer)
-    {
-        long val= 0;
-        boolean started= false;
-        boolean minus= false;
-        for (int i= buffer.getIndex(); i < buffer.putIndex(); i++)
-        {
-            byte b= buffer.peek(i);
-            if (b <= SPACE)
-            {
-                if (started)
-                    break;
-            }
-            else if (b >= '0' && b <= '9')
-            {
-                val= val * 10L + (b - '0');
-                started= true;
-            }
-            else if (b == MINUS && !started)
-            {
-                minus= true;
-            }
-            else
-                break;
-        }
-
-        if (started)
-            return minus ? (-val) : val;
-        throw new NumberFormatException(buffer.toString());
-    }
-
-    public static void putHexInt(Buffer buffer, int n)
-    {
-
-        if (n < 0)
-        {
-            buffer.put((byte)'-');
-
-            if (n == Integer.MIN_VALUE)
-            {
-                buffer.put((byte)(0x7f&'8'));
-                buffer.put((byte)(0x7f&'0'));
-                buffer.put((byte)(0x7f&'0'));
-                buffer.put((byte)(0x7f&'0'));
-                buffer.put((byte)(0x7f&'0'));
-                buffer.put((byte)(0x7f&'0'));
-                buffer.put((byte)(0x7f&'0'));
-                buffer.put((byte)(0x7f&'0'));
-                
-                return;
-            }
-            n= -n;
-        }
-
-        if (n < 0x10)
-        {
-            buffer.put(DIGIT[n]);
-        }
-        else
-        {
-            boolean started= false;
-            // This assumes constant time int arithmatic
-            for (int i= 0; i < hexDivisors.length; i++)
-            {
-                if (n < hexDivisors[i])
-                {
-                    if (started)
-                        buffer.put((byte)'0');
-                    continue;
-                }
-
-                started= true;
-                int d= n / hexDivisors[i];
-                buffer.put(DIGIT[d]);
-                n= n - d * hexDivisors[i];
-            }
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Add hex integer BEFORE current getIndex.
-     * @param buffer
-     * @param n
-     */
-    public static void prependHexInt(Buffer buffer, int n)
-    {
-        if (n==0)
-        {
-            int gi=buffer.getIndex();
-            buffer.poke(--gi,(byte)'0');
-            buffer.setGetIndex(gi);
-        }
-        else
-        {
-            boolean minus=false;
-            if (n<0)
-            {
-                minus=true;
-                n=-n;
-            }
-
-            int gi=buffer.getIndex();
-            while(n>0)
-            {
-                int d = 0xf&n;
-                n=n>>4;
-                buffer.poke(--gi,DIGIT[d]);
-            }
-            
-            if (minus)
-                buffer.poke(--gi,(byte)'-');
-            buffer.setGetIndex(gi);
-        }
-    }
-    
-
-    /* ------------------------------------------------------------ */
-    public static void putDecInt(Buffer buffer, int n)
-    {
-        if (n < 0)
-        {
-            buffer.put((byte)'-');
-
-            if (n == Integer.MIN_VALUE)
-            {
-                buffer.put((byte)'2');
-                n= 147483648;
-            }
-            else
-                n= -n;
-        }
-
-        if (n < 10)
-        {
-            buffer.put(DIGIT[n]);
-        }
-        else
-        {
-            boolean started= false;
-            // This assumes constant time int arithmatic
-            for (int i= 0; i < decDivisors.length; i++)
-            {
-                if (n < decDivisors[i])
-                {
-                    if (started)
-                        buffer.put((byte)'0');
-                    continue;
-                }
-
-                started= true;
-                int d= n / decDivisors[i];
-                buffer.put(DIGIT[d]);
-                n= n - d * decDivisors[i];
-            }
-        }
-    }
-    
-    public static void putDecLong(Buffer buffer, long n)
-    {
-        if (n < 0)
-        {
-            buffer.put((byte)'-');
-
-            if (n == Long.MIN_VALUE)
-            {
-                buffer.put((byte)'9');
-                n= 223372036854775808L;
-            }
-            else
-                n= -n;
-        }
-
-        if (n < 10)
-        {
-            buffer.put(DIGIT[(int)n]);
-        }
-        else
-        {
-            boolean started= false;
-            // This assumes constant time int arithmatic
-            for (int i= 0; i < decDivisorsL.length; i++)
-            {
-                if (n < decDivisorsL[i])
-                {
-                    if (started)
-                        buffer.put((byte)'0');
-                    continue;
-                }
-
-                started= true;
-                long d= n / decDivisorsL[i];
-                buffer.put(DIGIT[(int)d]);
-                n= n - d * decDivisorsL[i];
-            }
-        }
-    }
-    
-    public static Buffer toBuffer(long value)
-    {
-        ByteArrayBuffer buf=new ByteArrayBuffer(32);
-        putDecLong(buf, value);
-        return buf;
-    }
-
-    private final static int[] decDivisors=
-    { 
-        1000000000,
-        100000000,
-        10000000,
-        1000000,
-        100000,
-        10000,
-        1000,
-        100,
-        10,
-        1 
-    };
-
-    private final static int[] hexDivisors=
-    {
-        0x10000000,
-        0x1000000, 
-        0x100000, 
-        0x10000, 
-        0x1000, 
-        0x100, 
-        0x10, 
-        0x1 
-    };
-
-    private final static long[] decDivisorsL=
-    { 
-        1000000000000000000L,
-        100000000000000000L,
-        10000000000000000L,
-        1000000000000000L,
-        100000000000000L,
-        10000000000000L,
-        1000000000000L,
-        100000000000L,
-        10000000000L,
-        1000000000L,
-        100000000L,
-        10000000L,
-        1000000L,
-        100000L,
-        10000L,
-        1000L,
-        100L,
-        10L,
-        1L 
-    };
-
-
-    public static void putCRLF(Buffer buffer)
-    {
-        buffer.put((byte)13);
-        buffer.put((byte)10);
-    }
-    
-    public static boolean isPrefix(Buffer prefix,Buffer buffer)
-    {
-        if (prefix.length()>buffer.length())
-            return false;
-        int bi=buffer.getIndex();
-        for (int i=prefix.getIndex(); i<prefix.putIndex();i++)
-            if (prefix.peek(i)!=buffer.peek(bi++))
-                return false;
-        return true;
-    }
-
-    public static String to8859_1_String(Buffer buffer)
-    {
-        if (buffer instanceof CachedBuffer)
-            return buffer.toString();
-        return buffer.toString(StringUtil.__ISO_8859_1_CHARSET);
-    }
-}
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/Buffers.java b/jetty-io/src/main/java/org/eclipse/jetty/io/Buffers.java
deleted file mode 100644
index 6dcf0e5..0000000
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/Buffers.java
+++ /dev/null
@@ -1,38 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.io;
-
-
-/* ------------------------------------------------------------ */
-/** BufferSource.
- * Represents a pool or other source of buffers and abstracts the creation
- * of specific types of buffers (eg NIO).   The concept of big and little buffers
- * is supported, but these terms have no absolute meaning and must be determined by context.
- * 
- */
-public interface Buffers
-{
-    enum Type { BYTE_ARRAY, DIRECT, INDIRECT } ;
-    
-    Buffer getHeader();
-    Buffer getBuffer();
-    Buffer getBuffer(int size);
-    
-    void returnBuffer(Buffer buffer);
-}
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/BuffersFactory.java b/jetty-io/src/main/java/org/eclipse/jetty/io/BuffersFactory.java
deleted file mode 100644
index a214796..0000000
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/BuffersFactory.java
+++ /dev/null
@@ -1,29 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.io;
-
-public class BuffersFactory
-{
-    public static Buffers newBuffers(Buffers.Type headerType, int headerSize, Buffers.Type bufferType, int bufferSize, Buffers.Type otherType,int maxSize)
-    {
-        if (maxSize>=0)
-            return new PooledBuffers(headerType,headerSize,bufferType,bufferSize,otherType,maxSize);
-        return new ThreadLocalBuffers(headerType,headerSize,bufferType,bufferSize,otherType);
-    }
-}
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/ByteArrayBuffer.java b/jetty-io/src/main/java/org/eclipse/jetty/io/ByteArrayBuffer.java
deleted file mode 100644
index 8e29da6..0000000
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/ByteArrayBuffer.java
+++ /dev/null
@@ -1,439 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.io;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.UnsupportedEncodingException;
-
-import org.eclipse.jetty.util.StringUtil;
-
-/* ------------------------------------------------------------------------------- */
-/**
- * 
- */
-public class ByteArrayBuffer extends AbstractBuffer
-{
-    // Set a maximum size to a write for the writeTo method, to ensure that very large content is not
-    // written as a single write (which may fall foul to write timeouts if consumed slowly).
-    final static int MAX_WRITE=Integer.getInteger("org.eclipse.jetty.io.ByteArrayBuffer.MAX_WRITE",128*1024);
-    final protected byte[] _bytes;
-
-    protected ByteArrayBuffer(int size, int access, boolean isVolatile)
-    {
-        this(new byte[size],0,0,access, isVolatile);
-    }
-    
-    public ByteArrayBuffer(byte[] bytes)
-    {
-        this(bytes, 0, bytes.length, READWRITE);
-    }
-
-    public ByteArrayBuffer(byte[] bytes, int index, int length)
-    {
-        this(bytes, index, length, READWRITE);
-    }
-
-    public ByteArrayBuffer(byte[] bytes, int index, int length, int access)
-    {
-        super(READWRITE, NON_VOLATILE);
-        _bytes = bytes;
-        setPutIndex(index + length);
-        setGetIndex(index);
-        _access = access;
-    }
-
-    public ByteArrayBuffer(byte[] bytes, int index, int length, int access, boolean isVolatile)
-    {
-        super(READWRITE, isVolatile);
-        _bytes = bytes;
-        setPutIndex(index + length);
-        setGetIndex(index);
-        _access = access;
-    }
-
-    public ByteArrayBuffer(int size)
-    {
-        this(new byte[size], 0, 0, READWRITE);
-        setPutIndex(0);
-    }
-
-    public ByteArrayBuffer(String value)
-    {
-        super(READWRITE,NON_VOLATILE);
-        _bytes = StringUtil.getBytes(value);
-        setGetIndex(0);
-        setPutIndex(_bytes.length);
-        _access=IMMUTABLE;
-        _string = value;
-    }
-    
-    public ByteArrayBuffer(String value,boolean immutable)
-    {
-        super(READWRITE,NON_VOLATILE);
-        _bytes = StringUtil.getBytes(value);
-        setGetIndex(0);
-        setPutIndex(_bytes.length);
-        if (immutable)
-        {
-            _access=IMMUTABLE;
-            _string = value;
-        }
-    }
-
-    public ByteArrayBuffer(String value,String encoding) throws UnsupportedEncodingException
-    {
-        super(READWRITE,NON_VOLATILE);
-        _bytes = value.getBytes(encoding);
-        setGetIndex(0);
-        setPutIndex(_bytes.length);
-        _access=IMMUTABLE;
-        _string = value;
-    }
-
-    public byte[] array()
-    {
-        return _bytes;
-    }
-
-    public int capacity()
-    {
-        return _bytes.length;
-    }
-    
-    @Override
-    public void compact()
-    {
-        if (isReadOnly()) 
-            throw new IllegalStateException(__READONLY);
-        int s = markIndex() >= 0 ? markIndex() : getIndex();
-        if (s > 0)
-        {
-            int length = putIndex() - s;
-            if (length > 0)
-            {
-                System.arraycopy(_bytes, s,_bytes, 0, length);
-            }
-            if (markIndex() > 0) setMarkIndex(markIndex() - s);
-            setGetIndex(getIndex() - s);
-            setPutIndex(putIndex() - s);
-        }
-    }
-
-
-    @Override
-    public boolean equals(Object obj)
-    {
-        if (obj==this)
-            return true;
-
-        if (obj == null || !(obj instanceof Buffer)) 
-            return false;
-        
-        if (obj instanceof Buffer.CaseInsensitve)
-            return equalsIgnoreCase((Buffer)obj);
-        
-
-        Buffer b = (Buffer) obj;
-        
-        // reject different lengths
-        if (b.length() != length()) 
-            return false;
-
-        // reject AbstractBuffer with different hash value
-        if (_hash != 0 && obj instanceof AbstractBuffer)
-        {
-            AbstractBuffer ab = (AbstractBuffer) obj;
-            if (ab._hash != 0 && _hash != ab._hash) 
-                return false;
-        }
-
-        // Nothing for it but to do the hard grind.
-        int get=getIndex();
-        int bi=b.putIndex();
-        for (int i = putIndex(); i-->get;)
-        {
-            byte b1 = _bytes[i];
-            byte b2 = b.peek(--bi);
-            if (b1 != b2) return false;
-        }
-        return true;
-    }
-
-
-    @Override
-    public boolean equalsIgnoreCase(Buffer b)
-    {
-        if (b==this)
-            return true;
-        
-        // reject different lengths
-        if (b==null || b.length() != length()) 
-            return false;
-
-        // reject AbstractBuffer with different hash value
-        if (_hash != 0 && b instanceof AbstractBuffer)
-        {
-            AbstractBuffer ab = (AbstractBuffer) b;
-            if (ab._hash != 0 && _hash != ab._hash) return false;
-        }
-
-        // Nothing for it but to do the hard grind.
-        int get=getIndex();
-        int bi=b.putIndex();
-        byte[] barray=b.array();
-        if (barray==null)
-        {
-            for (int i = putIndex(); i-->get;)
-            {
-                byte b1 = _bytes[i];
-                byte b2 = b.peek(--bi);
-                if (b1 != b2)
-                {
-                    if ('a' <= b1 && b1 <= 'z') b1 = (byte) (b1 - 'a' + 'A');
-                    if ('a' <= b2 && b2 <= 'z') b2 = (byte) (b2 - 'a' + 'A');
-                    if (b1 != b2) return false;
-                }
-            }
-        }
-        else
-        {
-            for (int i = putIndex(); i-->get;)
-            {
-                byte b1 = _bytes[i];
-                byte b2 = barray[--bi];
-                if (b1 != b2)
-                {
-                    if ('a' <= b1 && b1 <= 'z') b1 = (byte) (b1 - 'a' + 'A');
-                    if ('a' <= b2 && b2 <= 'z') b2 = (byte) (b2 - 'a' + 'A');
-                    if (b1 != b2) return false;
-                }
-            }
-        }
-        return true;
-    }
-
-    @Override
-    public byte get()
-    {
-        return _bytes[_get++];
-    }
-
-    @Override
-    public int hashCode()
-    {
-        if (_hash == 0 || _hashGet!=_get || _hashPut!=_put) 
-        {
-            int get=getIndex();
-            for (int i = putIndex(); i-- >get;)
-            {
-                byte b = _bytes[i];
-                if ('a' <= b && b <= 'z') 
-                    b = (byte) (b - 'a' + 'A');
-                _hash = 31 * _hash + b;
-            }
-            if (_hash == 0) 
-                _hash = -1;
-            _hashGet=_get;
-            _hashPut=_put;
-        }
-        return _hash;
-    }
-    
-    
-    public byte peek(int index)
-    {
-        return _bytes[index];
-    }
-    
-    public int peek(int index, byte[] b, int offset, int length)
-    {
-        int l = length;
-        if (index + l > capacity())
-        {
-            l = capacity() - index;
-            if (l==0)
-                return -1;
-        }
-        
-        if (l < 0) 
-            return -1;
-        
-        System.arraycopy(_bytes, index, b, offset, l);
-        return l;
-    }
-
-    public void poke(int index, byte b)
-    {
-        /* 
-        if (isReadOnly()) 
-            throw new IllegalStateException(__READONLY);
-        
-        if (index < 0) 
-            throw new IllegalArgumentException("index<0: " + index + "<0");
-        if (index > capacity())
-                throw new IllegalArgumentException("index>capacity(): " + index + ">" + capacity());
-        */
-        _bytes[index] = b;
-    }
-    
-    @Override
-    public int poke(int index, Buffer src)
-    {
-        _hash=0;
-        
-        /* 
-        if (isReadOnly()) 
-            throw new IllegalStateException(__READONLY);
-        if (index < 0) 
-            throw new IllegalArgumentException("index<0: " + index + "<0");
-        */
-        
-        int length=src.length();
-        if (index + length > capacity())
-        {
-            length=capacity()-index;
-            /*
-            if (length<0)
-                throw new IllegalArgumentException("index>capacity(): " + index + ">" + capacity());
-            */
-        }
-        
-        byte[] src_array = src.array();
-        if (src_array != null)
-            System.arraycopy(src_array, src.getIndex(), _bytes, index, length);
-        else 
-        {
-            int s=src.getIndex();
-            for (int i=0;i<length;i++)
-                _bytes[index++]=src.peek(s++);
-        }
-        
-        return length;
-    }
-    
-
-    @Override
-    public int poke(int index, byte[] b, int offset, int length)
-    {
-        _hash=0;
-        /*
-        if (isReadOnly()) 
-            throw new IllegalStateException(__READONLY);
-        if (index < 0) 
-            throw new IllegalArgumentException("index<0: " + index + "<0");
-        */
-        
-        if (index + length > capacity())
-        {
-            length=capacity()-index;
-            /* if (length<0)
-                throw new IllegalArgumentException("index>capacity(): " + index + ">" + capacity());
-            */
-        }
-        
-        System.arraycopy(b, offset, _bytes, index, length);
-        
-        return length;
-    }
-    
-    /* ------------------------------------------------------------ */
-    @Override
-    public void writeTo(OutputStream out)
-        throws IOException
-    {
-        int len=length();
-        if (MAX_WRITE>0 && len>MAX_WRITE)
-        {
-            int off=getIndex();
-            while(len>0)
-            {
-                int c=len>MAX_WRITE?MAX_WRITE:len;
-                out.write(_bytes,off,c);
-                off+=c;
-                len-=c;
-            }
-        }
-        else
-            out.write(_bytes,getIndex(),len);
-        if (!isImmutable())
-            clear();
-    }
-    
-    /* ------------------------------------------------------------ */
-    @Override
-    public int readFrom(InputStream in,int max) throws IOException
-    {
-        if (max<0||max>space())
-            max=space();
-        int p = putIndex();
-        
-        int len=0, total=0, available=max;
-        while (total<max) 
-        {
-            len=in.read(_bytes,p,available);
-            if (len<0)
-                break;
-            else if (len>0)
-            {
-                p += len;
-                total += len;
-                available -= len;
-                setPutIndex(p);
-            }
-            if (in.available()<=0)
-                break;
-        }
-        if (len<0 && total==0)
-            return -1;
-        return total;
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public int space()
-    {
-        return _bytes.length - _put;
-    }
-
-    
-    /* ------------------------------------------------------------ */
-    /* ------------------------------------------------------------ */
-    /* ------------------------------------------------------------ */
-    public static class CaseInsensitive extends ByteArrayBuffer implements Buffer.CaseInsensitve
-    {
-        public CaseInsensitive(String s)
-        {
-            super(s);
-        }
-        
-        public CaseInsensitive(byte[] b, int o, int l, int rw)
-        {
-            super(b,o,l,rw);
-        }
-
-        @Override
-        public boolean equals(Object obj)
-        {
-            return obj instanceof Buffer && equalsIgnoreCase((Buffer)obj);
-        }
-        
-    }
-}
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/ByteArrayEndPoint.java b/jetty-io/src/main/java/org/eclipse/jetty/io/ByteArrayEndPoint.java
index ed36016..331eb25 100644
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/ByteArrayEndPoint.java
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/ByteArrayEndPoint.java
@@ -19,24 +19,33 @@
 package org.eclipse.jetty.io;
 
 import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.channels.ClosedChannelException;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
 
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.Scheduler;
 
 
 /* ------------------------------------------------------------ */
 /** ByteArrayEndPoint.
  *
- *
  */
-public class ByteArrayEndPoint implements ConnectedEndPoint
+public class ByteArrayEndPoint extends AbstractEndPoint
 {
-    protected byte[] _inBytes;
-    protected ByteArrayBuffer _in;
-    protected ByteArrayBuffer _out;
-    protected boolean _closed;
-    protected boolean _nonBlocking;
-    protected boolean _growOutput;
-    protected Connection _connection;
-    protected int _maxIdleTime;
+    static final Logger LOG = Log.getLogger(ByteArrayEndPoint.class);
+    public final static InetSocketAddress NOIP=new InetSocketAddress(0);
+
+    protected volatile ByteBuffer _in;
+    protected volatile ByteBuffer _out;
+    protected volatile boolean _ishut;
+    protected volatile boolean _oshut;
+    protected volatile boolean _closed;
+    protected volatile boolean _growOutput;
 
 
     /* ------------------------------------------------------------ */
@@ -45,330 +54,340 @@ public class ByteArrayEndPoint implements ConnectedEndPoint
      */
     public ByteArrayEndPoint()
     {
+        this(null,0,null,null);
     }
 
     /* ------------------------------------------------------------ */
     /**
-     * @see org.eclipse.jetty.io.ConnectedEndPoint#getConnection()
+     *
      */
-    public Connection getConnection()
+    public ByteArrayEndPoint(byte[] input, int outputSize)
     {
-        return _connection;
+        this(null,0,input!=null?BufferUtil.toBuffer(input):null,BufferUtil.allocate(outputSize));
     }
 
     /* ------------------------------------------------------------ */
     /**
-     * @see org.eclipse.jetty.io.ConnectedEndPoint#setConnection(org.eclipse.jetty.io.Connection)
+     *
      */
-    public void setConnection(Connection connection)
+    public ByteArrayEndPoint(String input, int outputSize)
     {
-        _connection=connection;
+        this(null,0,input!=null?BufferUtil.toBuffer(input):null,BufferUtil.allocate(outputSize));
     }
 
     /* ------------------------------------------------------------ */
-    /**
-     * @return the nonBlocking
-     */
-    public boolean isNonBlocking()
+    public ByteArrayEndPoint(Scheduler scheduler, long idleTimeoutMs)
     {
-        return _nonBlocking;
+        this(scheduler,idleTimeoutMs,null,null);
     }
 
     /* ------------------------------------------------------------ */
-    /**
-     * @param nonBlocking the nonBlocking to set
-     */
-    public void setNonBlocking(boolean nonBlocking)
+    public ByteArrayEndPoint(Scheduler timer, long idleTimeoutMs, byte[] input, int outputSize)
     {
-        _nonBlocking=nonBlocking;
+        this(timer,idleTimeoutMs,input!=null?BufferUtil.toBuffer(input):null,BufferUtil.allocate(outputSize));
     }
 
     /* ------------------------------------------------------------ */
-    /**
-     *
-     */
-    public ByteArrayEndPoint(byte[] input, int outputSize)
+    public ByteArrayEndPoint(Scheduler timer, long idleTimeoutMs, String input, int outputSize)
     {
-        _inBytes=input;
-        _in=new ByteArrayBuffer(input);
-        _out=new ByteArrayBuffer(outputSize);
+        this(timer,idleTimeoutMs,input!=null?BufferUtil.toBuffer(input):null,BufferUtil.allocate(outputSize));
     }
 
     /* ------------------------------------------------------------ */
-    /**
-     * @return Returns the in.
-     */
-    public ByteArrayBuffer getIn()
+    public ByteArrayEndPoint(Scheduler timer, long idleTimeoutMs, ByteBuffer input, ByteBuffer output)
     {
-        return _in;
+        super(timer,NOIP,NOIP);
+        _in=input==null?BufferUtil.EMPTY_BUFFER:input;
+        _out=output==null?BufferUtil.allocate(1024):output;
+        setIdleTimeout(idleTimeoutMs);
     }
+
+
+
+
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void onIncompleteFlush()
+    {
+        // Don't need to do anything here as takeOutput does the signalling.
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected boolean needsFill() throws IOException
+    {
+        if (_closed)
+            throw new ClosedChannelException();
+        return _in == null || BufferUtil.hasContent(_in);
+    }
+
     /* ------------------------------------------------------------ */
     /**
-     * @param in The in to set.
+     * @return Returns the in.
      */
-    public void setIn(ByteArrayBuffer in)
+    public ByteBuffer getIn()
     {
-        _in = in;
+        return _in;
     }
+
     /* ------------------------------------------------------------ */
     /**
-     * @return Returns the out.
      */
-    public ByteArrayBuffer getOut()
+    public void setInputEOF()
     {
-        return _out;
+        _in = null;
     }
+
     /* ------------------------------------------------------------ */
     /**
-     * @param out The out to set.
+     * @param in The in to set.
      */
-    public void setOut(ByteArrayBuffer out)
+    public void setInput(ByteBuffer in)
     {
-        _out = out;
+        _in = in;
+        if (in == null || BufferUtil.hasContent(in))
+            getFillInterest().fillable();
     }
 
     /* ------------------------------------------------------------ */
-    /*
-     * @see org.eclipse.io.EndPoint#isOpen()
-     */
-    public boolean isOpen()
+    public void setInput(String s)
     {
-        return !_closed;
+        setInput(BufferUtil.toBuffer(s,StandardCharsets.UTF_8));
     }
 
     /* ------------------------------------------------------------ */
-    /*
-     *  @see org.eclipse.jetty.io.EndPoint#isInputShutdown()
-     */
-    public boolean isInputShutdown()
+    public void setInput(String s,Charset charset)
     {
-        return _closed;
+        setInput(BufferUtil.toBuffer(s,charset));
     }
 
     /* ------------------------------------------------------------ */
-    /*
-     *  @see org.eclipse.jetty.io.EndPoint#isOutputShutdown()
+    /**
+     * @return Returns the out.
      */
-    public boolean isOutputShutdown()
+    public ByteBuffer getOutput()
     {
-        return _closed;
+        return _out;
     }
 
     /* ------------------------------------------------------------ */
-    /*
-     * @see org.eclipse.io.EndPoint#isBlocking()
+    /**
+     * @return Returns the out.
      */
-    public boolean isBlocking()
+    public String getOutputString()
     {
-        return !_nonBlocking;
+        return getOutputString(StandardCharsets.UTF_8);
     }
 
     /* ------------------------------------------------------------ */
-    public boolean blockReadable(long millisecs)
+    /**
+     * @return Returns the out.
+     */
+    public String getOutputString(Charset charset)
     {
-        return true;
+        return BufferUtil.toString(_out,charset);
     }
 
     /* ------------------------------------------------------------ */
-    public boolean blockWritable(long millisecs)
+    /**
+     * @return Returns the out.
+     */
+    public ByteBuffer takeOutput()
     {
-        return true;
+        ByteBuffer b=_out;
+        _out=BufferUtil.allocate(b.capacity());
+        getWriteFlusher().completeWrite();
+        return b;
     }
 
     /* ------------------------------------------------------------ */
-    /*
-     * @see org.eclipse.io.EndPoint#shutdownOutput()
+    /**
+     * @return Returns the out.
      */
-    public void shutdownOutput() throws IOException
+    public String takeOutputString()
     {
-        close();
+        return takeOutputString(StandardCharsets.UTF_8);
     }
 
     /* ------------------------------------------------------------ */
-    /*
-     * @see org.eclipse.io.EndPoint#shutdownInput()
+    /**
+     * @return Returns the out.
      */
-    public void shutdownInput() throws IOException
+    public String takeOutputString(Charset charset)
     {
-        close();
+        ByteBuffer buffer=takeOutput();
+        return BufferUtil.toString(buffer,charset);
     }
 
     /* ------------------------------------------------------------ */
-    /*
-     * @see org.eclipse.io.EndPoint#close()
+    /**
+     * @param out The out to set.
      */
-    public void close() throws IOException
+    public void setOutput(ByteBuffer out)
     {
-        _closed=true;
+        _out = out;
+        getWriteFlusher().completeWrite();
     }
 
     /* ------------------------------------------------------------ */
     /*
-     * @see org.eclipse.io.EndPoint#fill(org.eclipse.io.Buffer)
+     * @see org.eclipse.io.EndPoint#isOpen()
      */
-    public int fill(Buffer buffer) throws IOException
+    @Override
+    public boolean isOpen()
     {
-        if (_closed)
-            throw new IOException("CLOSED");
-
-        if (_in!=null && _in.length()>0)
-        {
-            int len = buffer.put(_in);
-            _in.skip(len);
-            return len;
-        }
-
-        if (_in!=null && _in.length()==0 && _nonBlocking)
-            return 0;
-
-        close();
-        return -1;
+        return !_closed;
     }
 
     /* ------------------------------------------------------------ */
     /*
-     * @see org.eclipse.io.EndPoint#flush(org.eclipse.io.Buffer)
      */
-    public int flush(Buffer buffer) throws IOException
+    @Override
+    public boolean isInputShutdown()
     {
-        if (_closed)
-            throw new IOException("CLOSED");
-        if (_growOutput && buffer.length()>_out.space())
-        {
-            _out.compact();
-
-            if (buffer.length()>_out.space())
-            {
-                ByteArrayBuffer n = new ByteArrayBuffer(_out.putIndex()+buffer.length());
-
-                n.put(_out.peek(0,_out.putIndex()));
-                if (_out.getIndex()>0)
-                {
-                    n.mark();
-                    n.setGetIndex(_out.getIndex());
-                }
-                _out=n;
-            }
-        }
-        int len = _out.put(buffer);
-        if (!buffer.isImmutable())
-            buffer.skip(len);
-        return len;
+        return _ishut||_closed;
     }
 
     /* ------------------------------------------------------------ */
     /*
-     * @see org.eclipse.io.EndPoint#flush(org.eclipse.io.Buffer, org.eclipse.io.Buffer, org.eclipse.io.Buffer)
      */
-    public int flush(Buffer header, Buffer buffer, Buffer trailer) throws IOException
+    @Override
+    public boolean isOutputShutdown()
     {
-        if (_closed)
-            throw new IOException("CLOSED");
-
-        int flushed=0;
-
-        if (header!=null && header.length()>0)
-            flushed=flush(header);
-
-        if (header==null || header.length()==0)
-        {
-            if (buffer!=null && buffer.length()>0)
-                flushed+=flush(buffer);
-
-            if (buffer==null || buffer.length()==0)
-            {
-                if (trailer!=null && trailer.length()>0)
-                {
-                    flushed+=flush(trailer);
-                }
-            }
-        }
-
-        return flushed;
+        return _oshut||_closed;
     }
 
     /* ------------------------------------------------------------ */
-    /**
-     *
-     */
-    public void reset()
+    private void shutdownInput()
     {
-        _closed=false;
-        _in.clear();
-        _out.clear();
-        if (_inBytes!=null)
-            _in.setPutIndex(_inBytes.length);
+        _ishut=true;
+        if (_oshut)
+            close();
     }
 
     /* ------------------------------------------------------------ */
     /*
-     * @see org.eclipse.io.EndPoint#getLocalAddr()
+     * @see org.eclipse.io.EndPoint#shutdownOutput()
      */
-    public String getLocalAddr()
+    @Override
+    public void shutdownOutput()
     {
-        return null;
+        _oshut=true;
+        if (_ishut)
+            close();
     }
 
     /* ------------------------------------------------------------ */
     /*
-     * @see org.eclipse.io.EndPoint#getLocalHost()
+     * @see org.eclipse.io.EndPoint#close()
      */
-    public String getLocalHost()
+    @Override
+    public void close()
     {
-        return null;
+        super.close();
+        _closed=true;
     }
 
     /* ------------------------------------------------------------ */
-    /*
-     * @see org.eclipse.io.EndPoint#getLocalPort()
+    /**
+     * @return <code>true</code> if there are bytes remaining to be read from the encoded input
      */
-    public int getLocalPort()
+    public boolean hasMore()
     {
-        return 0;
+        return getOutput().position()>0;
     }
 
     /* ------------------------------------------------------------ */
     /*
-     * @see org.eclipse.io.EndPoint#getRemoteAddr()
+     * @see org.eclipse.io.EndPoint#fill(org.eclipse.io.Buffer)
      */
-    public String getRemoteAddr()
+    @Override
+    public int fill(ByteBuffer buffer) throws IOException
     {
-        return null;
+        if (_closed)
+            throw new EofException("CLOSED");
+        if (_in==null)
+            shutdownInput();
+        if (_ishut)
+            return -1;
+        int filled=BufferUtil.append(buffer,_in);
+        if (filled>0)
+            notIdle();
+        return filled;
     }
 
     /* ------------------------------------------------------------ */
     /*
-     * @see org.eclipse.io.EndPoint#getRemoteHost()
+     * @see org.eclipse.io.EndPoint#flush(org.eclipse.io.Buffer, org.eclipse.io.Buffer, org.eclipse.io.Buffer)
      */
-    public String getRemoteHost()
+    @Override
+    public boolean flush(ByteBuffer... buffers) throws IOException
     {
-        return null;
+        if (_closed)
+            throw new IOException("CLOSED");
+        if (_oshut)
+            throw new IOException("OSHUT");
+
+        boolean flushed=true;
+        boolean idle=true;
+
+        for (ByteBuffer b : buffers)
+        {
+            if (BufferUtil.hasContent(b))
+            {
+                if (_growOutput && b.remaining()>BufferUtil.space(_out))
+                {
+                    BufferUtil.compact(_out);
+                    if (b.remaining()>BufferUtil.space(_out))
+                    {
+                        ByteBuffer n = BufferUtil.allocate(_out.capacity()+b.remaining()*2);
+                        BufferUtil.append(n,_out);
+                        _out=n;
+                    }
+                }
+
+                if (BufferUtil.append(_out,b)>0)
+                    idle=false;
+
+                if (BufferUtil.hasContent(b))
+                {
+                    flushed=false;
+                    break;
+                }
+            }
+        }
+        if (!idle)
+            notIdle();
+        return flushed;
     }
 
     /* ------------------------------------------------------------ */
-    /*
-     * @see org.eclipse.io.EndPoint#getRemotePort()
+    /**
+     *
      */
-    public int getRemotePort()
+    public void reset()
     {
-        return 0;
+        getFillInterest().onClose();
+        getWriteFlusher().onClose();
+        _ishut=false;
+        _oshut=false;
+        _closed=false;
+        _in=null;
+        BufferUtil.clear(_out);
     }
 
     /* ------------------------------------------------------------ */
     /*
      * @see org.eclipse.io.EndPoint#getConnection()
      */
+    @Override
     public Object getTransport()
     {
-        return _inBytes;
+        return null;
     }
 
     /* ------------------------------------------------------------ */
-    public void flush() throws IOException
-    {
-    }
-    
-    /* ------------------------------------------------------------ */
     /**
      * @return the growOutput
      */
@@ -386,23 +405,5 @@ public class ByteArrayEndPoint implements ConnectedEndPoint
         _growOutput=growOutput;
     }
 
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.io.EndPoint#getMaxIdleTime()
-     */
-    public int getMaxIdleTime()
-    {
-        return _maxIdleTime;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.io.EndPoint#setMaxIdleTime(int)
-     */
-    public void setMaxIdleTime(int timeMs) throws IOException
-    {
-        _maxIdleTime=timeMs;
-    }
-
 
 }
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/ByteBufferPool.java b/jetty-io/src/main/java/org/eclipse/jetty/io/ByteBufferPool.java
new file mode 100644
index 0000000..6f75ed2
--- /dev/null
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/ByteBufferPool.java
@@ -0,0 +1,51 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.nio.ByteBuffer;
+
+/**
+ * <p>A {@link ByteBuffer} pool.</p>
+ * <p>Acquired buffers may be {@link #release(ByteBuffer) released} but they do not need to;
+ * if they are released, they may be recycled and reused, otherwise they will be garbage
+ * collected as usual.</p>
+ */
+public interface ByteBufferPool
+{
+    /**
+     * <p>Requests a {@link ByteBuffer} of the given size.</p>
+     * <p>The returned buffer may have a bigger capacity than the size being
+     * requested but it will have the limit set to the given size.</p>
+     *
+     * @param size   the size of the buffer
+     * @param direct whether the buffer must be direct or not
+     * @return the requested buffer
+     * @see #release(ByteBuffer)
+     */
+    public ByteBuffer acquire(int size, boolean direct);
+
+    /**
+     * <p>Returns a {@link ByteBuffer}, usually obtained with {@link #acquire(int, boolean)}
+     * (but not necessarily), making it available for recycling and reuse.</p>
+     *
+     * @param buffer the buffer to return
+     * @see #acquire(int, boolean)
+     */
+    public void release(ByteBuffer buffer);
+}
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/ChannelEndPoint.java b/jetty-io/src/main/java/org/eclipse/jetty/io/ChannelEndPoint.java
new file mode 100644
index 0000000..40e4ba4
--- /dev/null
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/ChannelEndPoint.java
@@ -0,0 +1,232 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.nio.ByteBuffer;
+import java.nio.channels.ByteChannel;
+import java.nio.channels.GatheringByteChannel;
+import java.nio.channels.SocketChannel;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+/**
+ * Channel End Point.
+ * <p>Holds the channel and socket for an NIO endpoint.
+ */
+public class ChannelEndPoint extends AbstractEndPoint
+{
+    private static final Logger LOG = Log.getLogger(ChannelEndPoint.class);
+
+    private final ByteChannel _channel;
+    private final Socket _socket;
+    private volatile boolean _ishut;
+    private volatile boolean _oshut;
+
+    public ChannelEndPoint(Scheduler scheduler,SocketChannel channel)
+    {
+        super(scheduler,
+            (InetSocketAddress)channel.socket().getLocalSocketAddress(),
+            (InetSocketAddress)channel.socket().getRemoteSocketAddress());
+        _channel = channel;
+        _socket=channel.socket();
+    }
+
+    @Override
+    public boolean isOpen()
+    {
+        return _channel.isOpen();
+    }
+
+    protected void shutdownInput()
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("ishut {}", this);
+        _ishut=true;
+        if (_oshut)
+            close();
+    }
+
+    @Override
+    public void shutdownOutput()
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("oshut {}", this);
+        _oshut = true;
+        if (_channel.isOpen())
+        {
+            try
+            {
+                if (!_socket.isOutputShutdown())
+                    _socket.shutdownOutput();
+            }
+            catch (IOException e)
+            {
+                LOG.debug(e);
+            }
+            finally
+            {
+                if (_ishut)
+                {
+                    close();
+                }
+            }
+        }
+    }
+
+    @Override
+    public boolean isOutputShutdown()
+    {
+        return _oshut || !_channel.isOpen() || _socket.isOutputShutdown();
+    }
+
+    @Override
+    public boolean isInputShutdown()
+    {
+        return _ishut || !_channel.isOpen() || _socket.isInputShutdown();
+    }
+
+    @Override
+    public void close()
+    {
+        super.close();
+        if (LOG.isDebugEnabled())
+            LOG.debug("close {}", this);
+        try
+        {
+            _channel.close();
+        }
+        catch (IOException e)
+        {
+            LOG.debug(e);
+        }
+        finally
+        {
+            _ishut=true;
+            _oshut=true;
+        }
+    }
+
+    @Override
+    public int fill(ByteBuffer buffer) throws IOException
+    {
+        if (_ishut)
+            return -1;
+
+        int pos=BufferUtil.flipToFill(buffer);
+        try
+        {
+            int filled = _channel.read(buffer);
+            if (LOG.isDebugEnabled()) // Avoid boxing of variable 'filled'
+                LOG.debug("filled {} {}", filled, this);
+
+            if (filled>0)
+                notIdle();
+            else if (filled==-1)
+                shutdownInput();
+
+            return filled;
+        }
+        catch(IOException e)
+        {
+            LOG.debug(e);
+            shutdownInput();
+            return -1;
+        }
+        finally
+        {
+            BufferUtil.flipToFlush(buffer,pos);
+        }
+    }
+
+    @Override
+    public boolean flush(ByteBuffer... buffers) throws IOException
+    {
+        int flushed=0;
+        try
+        {
+            if (buffers.length==1)
+                flushed=_channel.write(buffers[0]);
+            else if (buffers.length>1 && _channel instanceof GatheringByteChannel)
+                flushed= (int)((GatheringByteChannel)_channel).write(buffers,0,buffers.length);
+            else
+            {
+                for (ByteBuffer b : buffers)
+                {
+                    if (b.hasRemaining())
+                    {
+                        int l=_channel.write(b);
+                        if (l>0)
+                            flushed+=l;
+                        if (b.hasRemaining())
+                            break;
+                    }
+                }
+            }
+            if (LOG.isDebugEnabled())
+                LOG.debug("flushed {} {}", flushed, this);
+        }
+        catch (IOException e)
+        {
+            throw new EofException(e);
+        }
+
+        if (flushed>0)
+            notIdle();
+
+        for (ByteBuffer b : buffers)
+            if (!BufferUtil.isEmpty(b))
+                return false;
+
+        return true;
+    }
+
+    public ByteChannel getChannel()
+    {
+        return _channel;
+    }
+
+    @Override
+    public Object getTransport()
+    {
+        return _channel;
+    }
+
+    public Socket getSocket()
+    {
+        return _socket;
+    }
+
+    @Override
+    protected void onIncompleteFlush()
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    protected boolean needsFill() throws IOException
+    {
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnectionFactory.java b/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnectionFactory.java
new file mode 100644
index 0000000..1d96155
--- /dev/null
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnectionFactory.java
@@ -0,0 +1,90 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.Map;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * Factory for client-side {@link Connection} instances.
+ */
+public interface ClientConnectionFactory
+{
+    /**
+     *
+     * @param endPoint the {@link org.eclipse.jetty.io.EndPoint} to link the newly created connection to
+     * @param context the context data to create the connection
+     * @return a new {@link Connection}
+     * @throws IOException if the connection cannot be created
+     */
+    public Connection newConnection(EndPoint endPoint, Map<String, Object> context) throws IOException;
+
+    public static class Helper
+    {
+        private static Logger LOG = Log.getLogger(Helper.class);
+
+        private Helper()
+        {
+        }
+
+        /**
+         * Replaces the given {@code oldConnection} with the given {@code newConnection} on the
+         * {@link EndPoint} associated with {@code oldConnection}, performing connection lifecycle management.
+         * <p />
+         * The {@code oldConnection} will be closed by invoking {@link org.eclipse.jetty.io.Connection#onClose()}
+         * and the {@code newConnection} will be opened by invoking {@link org.eclipse.jetty.io.Connection#onOpen(ByteBuffer)}.
+         * @param oldConnection the old connection to replace
+         * @param newConnection the new connection replacement
+         */
+        public static void replaceConnection(Connection oldConnection, Connection newConnection)
+        {
+            close(oldConnection);
+            oldConnection.getEndPoint().setConnection(newConnection);
+            open(newConnection);
+        }
+
+        private static void open(Connection connection)
+        {
+            try
+            {
+                connection.onOpen();
+            }
+            catch (Throwable x)
+            {
+                LOG.debug(x);
+            }
+        }
+
+        private static void close(Connection connection)
+        {
+            try
+            {
+                connection.onClose();
+            }
+            catch (Throwable x)
+            {
+                LOG.debug(x);
+            }
+        }
+    }
+}
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/ConnectedEndPoint.java b/jetty-io/src/main/java/org/eclipse/jetty/io/ConnectedEndPoint.java
deleted file mode 100644
index 8959c0b..0000000
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/ConnectedEndPoint.java
+++ /dev/null
@@ -1,25 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.io;
-
-public interface ConnectedEndPoint extends EndPoint
-{
-    Connection getConnection();
-    void setConnection(Connection connection);
-}
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/Connection.java b/jetty-io/src/main/java/org/eclipse/jetty/io/Connection.java
index a60e516..41336d5 100644
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/Connection.java
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/Connection.java
@@ -18,60 +18,91 @@
 
 package org.eclipse.jetty.io;
 
-import java.io.IOException;
+import java.io.Closeable;
+import java.nio.ByteBuffer;
 
-/* ------------------------------------------------------------ */
-/** Abstract Connection used by Jetty Connectors.
- * <p>
- * Jetty will call the handle method of a connection when there is work
- * to be done on the connection.  For blocking connections, this is soon
- * as the connection is open and handle will keep being called until the
- * connection is closed.   For non-blocking connections, handle will only
- * be called if there are bytes to be read or the connection becomes writable
- * after being write blocked.
- *
- * @see org.eclipse.jetty.io.nio.SelectorManager
+/**
+ * <p>A {@link Connection} is associated to an {@link EndPoint} so that I/O events
+ * happening on the {@link EndPoint} can be processed by the {@link Connection}.</p>
+ * <p>A typical implementation of {@link Connection} overrides {@link #onOpen(ByteBuffer)} to
+ * {@link EndPoint#fillInterested(Callback) set read interest} on the {@link EndPoint},
+ * and when the {@link EndPoint} signals read readyness, this {@link Connection} can
+ * read bytes from the network and interpret them.</p>
  */
-public interface Connection
+public interface Connection extends Closeable
 {
-    /* ------------------------------------------------------------ */
-    /**
-     * Handle the connection.
-     * @return The Connection to use for the next handling of the connection.
-     * This allows protocol upgrades and support for CONNECT.
-     * @throws IOException if the handling of I/O operations fail
-     */
-    Connection handle() throws IOException;
+    public void addListener(Listener listener);
 
-    /**
-     * @return the timestamp at which the connection was created
-     */
-    long getTimeStamp();
+    public void onOpen();
 
     /**
-     * @return whether this connection is idle, that is not parsing and not generating
-     * @see #onIdleExpired(long)
+     * <p>Callback method invoked when this {@link Connection} is closed.</p>
+     * <p>Creators of the connection implementation are responsible for calling this method.</p>
      */
-    boolean isIdle();
+    public void onClose();
 
     /**
-     * <p>The semantic of this method is to return true to indicate interest in further reads,
-     * or false otherwise, but it is misnamed and should be really called <code>isReadInterested()</code>.</p>
-     *
-     * @return true to indicate interest in further reads, false otherwise
+     * @return the {@link EndPoint} associated with this {@link Connection}
      */
-    // TODO: rename to isReadInterested() in the next release
-    boolean isSuspended();
-
+    public EndPoint getEndPoint();
+    
     /**
-     * Called after the connection is closed
+     * <p>Performs a logical close of this connection.</p>
+     * <p>For simple connections, this may just mean to delegate the close to the associated
+     * {@link EndPoint} but, for example, SSL connections should write the SSL close message
+     * before closing the associated {@link EndPoint}.</p>
      */
-    void onClose();
+    @Override
+    public void close();
 
-    /**
-     * Called when the connection idle timeout expires
-     * @param idleForMs how long the connection has been idle
-     * @see #isIdle()
-     */
-    void onIdleExpired(long idleForMs);
+    public int getMessagesIn();
+    public int getMessagesOut();
+    public long getBytesIn();
+    public long getBytesOut();
+    public long getCreatedTimeStamp();
+    
+    public interface UpgradeFrom extends Connection
+    {
+        /* ------------------------------------------------------------ */
+        /** Take the input buffer from the connection on upgrade.
+         * <p>This method is used to take any unconsumed input from
+         * a connection during an upgrade.
+         * @return A buffer of unconsumed input. The caller must return the buffer
+         * to the bufferpool when consumed and this connection must not.
+         */
+        ByteBuffer onUpgradeFrom();
+    }
+    
+    public interface UpgradeTo extends Connection
+    {
+        /**
+         * <p>Callback method invoked when this {@link Connection} is upgraded.</p>
+         * <p>This must be called before {@link #onOpen()}.</p>
+         * @param prefilledBuffer An optional buffer that can contain prefilled data. Typically this
+         * results from an upgrade of one protocol to the other where the old connection has buffered
+         * data destined for the new connection.  The new connection must take ownership of the buffer
+         * and is responsible for returning it to the buffer pool
+         */
+        void onUpgradeTo(ByteBuffer prefilled);
+    }
+    
+    public interface Listener
+    {
+        public void onOpened(Connection connection);
+
+        public void onClosed(Connection connection);
+
+        public static class Adapter implements Listener
+        {
+            @Override
+            public void onOpened(Connection connection)
+            {
+            }
+
+            @Override
+            public void onClosed(Connection connection)
+            {
+            }
+        }
+    }
 }
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/EndPoint.java b/jetty-io/src/main/java/org/eclipse/jetty/io/EndPoint.java
index 56bf1e7..2eb75a5 100644
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/EndPoint.java
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/EndPoint.java
@@ -18,158 +18,233 @@
 
 package org.eclipse.jetty.io;
 
+import java.io.Closeable;
 import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.channels.ReadPendingException;
+import java.nio.channels.WritePendingException;
 
+import org.eclipse.jetty.util.Callback;
 
 /**
  *
  * A transport EndPoint
+ * 
+ * <h3>Asynchronous Methods</h3>
+ * <p>The asynchronous scheduling methods of {@link EndPoint} 
+ * has been influenced by NIO.2 Futures and Completion
+ * handlers, but does not use those actual interfaces because they have
+ * some inefficiencies.</p>
+ * <p>This class will frequently be used in conjunction with some of the utility
+ * implementations of {@link Callback}, such as {@link FutureCallback} and
+ * {@link ExecutorCallback}. Examples are:</p>
+ *
+ * <h3>Blocking Read</h3>
+ * <p>A FutureCallback can be used to block until an endpoint is ready to be filled
+ * from:
+ * <blockquote><pre>
+ * FutureCallback<String> future = new FutureCallback<>();
+ * endpoint.fillInterested("ContextObj",future);
+ * ...
+ * String context = future.get(); // This blocks
+ * int filled=endpoint.fill(mybuffer);
+ * </pre></blockquote></p>
+ *
+ * <h3>Dispatched Read</h3>
+ * <p>By using a different callback, the read can be done asynchronously in its own dispatched thread:
+ * <blockquote><pre>
+ * endpoint.fillInterested("ContextObj",new ExecutorCallback<String>(executor)
+ * {
+ *   public void onCompleted(String context)
+ *   {
+ *     int filled=endpoint.fill(mybuffer);
+ *     ...
+ *   }
+ *   public void onFailed(String context,Throwable cause) {...}
+ * });
+ * </pre></blockquote></p>
+ * <p>The executor callback can also be customized to not dispatch in some circumstances when
+ * it knows it can use the callback thread and does not need to dispatch.</p>
+ *
+ * <h3>Blocking Write</h3>
+ * <p>The write contract is that the callback complete is not called until all data has been
+ * written or there is a failure.  For blocking this looks like:
+ * <blockquote><pre>
+ * FutureCallback<String> future = new FutureCallback<>();
+ * endpoint.write("ContextObj",future,headerBuffer,contentBuffer);
+ * String context = future.get(); // This blocks
+ * </pre></blockquote></p>
+ *
+ * <h3>Dispatched Write</h3>
+ * <p>Note also that multiple buffers may be passed in write so that gather writes
+ * can be done:
+ * <blockquote><pre>
+ * endpoint.write("ContextObj",new ExecutorCallback<String>(executor)
+ * {
+ *   public void onCompleted(String context)
+ *   {
+ *     int filled=endpoint.fill(mybuffer);
+ *     ...
+ *   }
+ *   public void onFailed(String context,Throwable cause) {...}
+ * },headerBuffer,contentBuffer);
+ * </pre></blockquote></p>
  */
-public interface EndPoint
+public interface EndPoint extends Closeable
 {
+    /* ------------------------------------------------------------ */
     /**
-     * Shutdown any backing output stream associated with the endpoint
+     * @return The local Inet address to which this <code>EndPoint</code> is bound, or <code>null</code>
+     * if this <code>EndPoint</code> does not represent a network connection.
      */
-    void shutdownOutput() throws IOException;
-
-    boolean isOutputShutdown();
+    InetSocketAddress getLocalAddress();
 
+    /* ------------------------------------------------------------ */
     /**
-     * Shutdown any backing input stream associated with the endpoint
+     * @return The remote Inet address to which this <code>EndPoint</code> is bound, or <code>null</code>
+     * if this <code>EndPoint</code> does not represent a network connection.
+     */
+    InetSocketAddress getRemoteAddress();
+
+    /* ------------------------------------------------------------ */
+    boolean isOpen();
+
+    /* ------------------------------------------------------------ */
+    long getCreatedTimeStamp();
+
+    /* ------------------------------------------------------------ */
+    /** Shutdown the output.
+     * <p>This call indicates that no more data will be sent on this endpoint that
+     * that the remote end should read an EOF once all previously sent data has been
+     * consumed. Shutdown may be done either at the TCP/IP level, as a protocol exchange (Eg
+     * TLS close handshake) or both.
+     * <p>
+     * If the endpoint has {@link #isInputShutdown()} true, then this call has the same effect
+     * as {@link #close()}.
      */
-    void shutdownInput() throws IOException;
+    void shutdownOutput();
 
+    /* ------------------------------------------------------------ */
+    /** Test if output is shutdown.
+     * The output is shutdown by a call to {@link #shutdownOutput()}
+     * or {@link #close()}.
+     * @return true if the output is shutdown or the endpoint is closed.
+     */
+    boolean isOutputShutdown();
+
+    /* ------------------------------------------------------------ */
+    /** Test if the input is shutdown.
+     * The input is shutdown if an EOF has been read while doing
+     * a {@link #fill(ByteBuffer)}.   Once the input is shutdown, all calls to
+     * {@link #fill(ByteBuffer)} will  return -1, until such time as the
+     * end point is close, when they will return {@link EofException}.
+     * @return True if the input is shutdown or the endpoint is closed.
+     */
     boolean isInputShutdown();
 
     /**
      * Close any backing stream associated with the endpoint
      */
-    void close() throws IOException;
+    @Override
+    void close();
 
     /**
-     * Fill the buffer from the current putIndex to it's capacity from whatever
-     * byte source is backing the buffer. The putIndex is increased if bytes filled.
-     * The buffer may chose to do a compact before filling.
+     * Fill the passed buffer with data from this endpoint.  The bytes are appended to any
+     * data already in the buffer by writing from the buffers limit up to it's capacity.
+     * The limit is updated to include the filled bytes.
+     *
+     * @param buffer The buffer to fill. The position and limit are modified during the fill. After the
+     * operation, the position is unchanged and the limit is increased to reflect the new data filled.
      * @return an <code>int</code> value indicating the number of bytes
-     * filled or -1 if EOF is reached.
-     * @throws EofException If input is shutdown or the endpoint is closed.
+     * filled or -1 if EOF is read or the input is shutdown.
+     * @throws EofException If the endpoint is closed.
      */
-    int fill(Buffer buffer) throws IOException;
+    int fill(ByteBuffer buffer) throws IOException;
 
 
     /**
-     * Flush the buffer from the current getIndex to it's putIndex using whatever byte
-     * sink is backing the buffer. The getIndex is updated with the number of bytes flushed.
-     * Any mark set is cleared.
-     * If the entire contents of the buffer are flushed, then an implicit empty() is done.
+     * Flush data from the passed header/buffer to this endpoint.  As many bytes as can be consumed
+     * are taken from the header/buffer position up until the buffer limit.  The header/buffers position
+     * is updated to indicate how many bytes have been consumed.
+     * @return True IFF all the buffers have been consumed and the endpoint has flushed the data to its 
+     * destination (ie is not buffering any data).
      *
-     * @param buffer The buffer to flush. This buffers getIndex is updated.
-     * @return  the number of bytes written
      * @throws EofException If the endpoint is closed or output is shutdown.
      */
-    int flush(Buffer buffer) throws IOException;
+    boolean flush(ByteBuffer... buffer) throws IOException;
 
+    /* ------------------------------------------------------------ */
     /**
-     * Flush the buffer from the current getIndex to it's putIndex using whatever byte
-     * sink is backing the buffer. The getIndex is updated with the number of bytes flushed.
-     * Any mark set is cleared.
-     * If the entire contents of the buffer are flushed, then an implicit empty() is done.
-     * The passed header/trailer buffers are written before/after the contents of this buffer. This may be done
-     * either as gather writes, as a poke into this buffer or as several writes. The implementation is free to
-     * select the optimal mechanism.
-     * @param header A buffer to write before flushing this buffer. This buffers getIndex is updated.
-     * @param buffer The buffer to flush. This buffers getIndex is updated.
-     * @param trailer A buffer to write after flushing this buffer. This buffers getIndex is updated.
-     * @return the total number of bytes written.
+     * @return The underlying transport object (socket, channel, etc.)
      */
-    int flush(Buffer header, Buffer buffer, Buffer trailer) throws IOException;
-
+    Object getTransport();
 
     /* ------------------------------------------------------------ */
-    /**
-     * @return The local IP address to which this <code>EndPoint</code> is bound, or <code>null</code>
-     * if this <code>EndPoint</code> does not represent a network connection.
+    /** Get the max idle time in ms.
+     * <p>The max idle time is the time the endpoint can be idle before
+     * extraordinary handling takes place.
+     * @return the max idle time in ms or if ms <= 0 implies an infinite timeout
      */
-    public String getLocalAddr();
+    long getIdleTimeout();
 
     /* ------------------------------------------------------------ */
-    /**
-     * @return The local host name to which this <code>EndPoint</code> is bound, or <code>null</code>
-     * if this <code>EndPoint</code> does not represent a network connection.
+    /** Set the idle timeout.
+     * @param idleTimeout the idle timeout in MS. Timeout <= 0 implies an infinite timeout
      */
-    public String getLocalHost();
+    void setIdleTimeout(long idleTimeout);
+
 
-    /* ------------------------------------------------------------ */
     /**
-     * @return The local port number on which this <code>EndPoint</code> is listening, or <code>0</code>
-     * if this <code>EndPoint</code> does not represent a network connection.
+     * <p>Requests callback methods to be invoked when a call to {@link #fill(ByteBuffer)} would return data or EOF.</p>
+     *
+     * @param callback the callback to call when an error occurs or we are readable.
+     * @throws ReadPendingException if another read operation is concurrent.
      */
-    public int getLocalPort();
+    void fillInterested(Callback callback) throws ReadPendingException;
 
-    /* ------------------------------------------------------------ */
     /**
-     * @return The remote IP address to which this <code>EndPoint</code> is connected, or <code>null</code>
-     * if this <code>EndPoint</code> does not represent a network connection.
+     * <p>Writes the given buffers via {@link #flush(ByteBuffer...)} and invokes callback methods when either
+     * all the data has been flushed or an error occurs.</p>
+     *
+     * @param callback the callback to call when an error occurs or the write completed.
+     * @param buffers one or more {@link ByteBuffer}s that will be flushed.
+     * @throws WritePendingException if another write operation is concurrent.
      */
-    public String getRemoteAddr();
+    void write(Callback callback, ByteBuffer... buffers) throws WritePendingException;
 
-    /* ------------------------------------------------------------ */
     /**
-     * @return The host name of the remote machine to which this <code>EndPoint</code> is connected, or <code>null</code>
-     * if this <code>EndPoint</code> does not represent a network connection.
+     * @return the {@link Connection} associated with this {@link EndPoint}
+     * @see #setConnection(Connection)
      */
-    public String getRemoteHost();
+    Connection getConnection();
 
-    /* ------------------------------------------------------------ */
     /**
-     * @return The remote port number to which this <code>EndPoint</code> is connected, or <code>0</code>
-     * if this <code>EndPoint</code> does not represent a network connection.
+     * @param connection the {@link Connection} associated with this {@link EndPoint}
+     * @see #getConnection()
+     * @see #upgrade(Connection)
      */
-    public int getRemotePort();
-
-    /* ------------------------------------------------------------ */
-    public boolean isBlocking();
-
-    /* ------------------------------------------------------------ */
-    public boolean blockReadable(long millisecs) throws IOException;
-
-    /* ------------------------------------------------------------ */
-    public boolean blockWritable(long millisecs) throws IOException;
+    void setConnection(Connection connection);
 
-    /* ------------------------------------------------------------ */
-    public boolean isOpen();
-
-    /* ------------------------------------------------------------ */
     /**
-     * @return The underlying transport object (socket, channel, etc.)
+     * <p>Callback method invoked when this {@link EndPoint} is opened.</p>
+     * @see #onClose()
      */
-    public Object getTransport();
+    void onOpen();
 
-    /* ------------------------------------------------------------ */
-    /** Flush any buffered output.
-     * May fail to write all data if endpoint is non-blocking
-     * @throws EofException If the endpoint is closed or output is shutdown.
+    /**
+     * <p>Callback method invoked when this {@link EndPoint} is close.</p>
+     * @see #onOpen()
      */
-    public void flush() throws IOException;
+    void onClose();
 
-    /* ------------------------------------------------------------ */
-    /** Get the max idle time in ms.
-     * <p>The max idle time is the time the endpoint can be idle before
-     * extraordinary handling takes place.  This loosely corresponds to
-     * the {@link java.net.Socket#getSoTimeout()} for blocking connections,
-     * but {@link AsyncEndPoint} implementations must use other mechanisms
-     * to implement the max idle time.
-     * @return the max idle time in ms or if ms <= 0 implies an infinite timeout
-     */
-    public int getMaxIdleTime();
 
-    /* ------------------------------------------------------------ */
-    /** Set the max idle time.
-     * @param timeMs the max idle time in MS. Timeout <= 0 implies an infinite timeout
-     * @throws IOException if the timeout cannot be set.
+    /** Upgrade connections.
+     * Close the old connection, update the endpoint and open the new connection.
+     * If the oldConnection is an instance of {@link Connection.UpgradeFrom} then
+     * a prefilled buffer is requested and passed to the newConnection if it is an instance
+     * of {@link Connection.UpgradeTo}
+     * @param newConnection The connection to upgrade to
      */
-    public void setMaxIdleTime(int timeMs) throws IOException;
-
-
-
+    public void upgrade(Connection newConnection);
 }
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/FillInterest.java b/jetty-io/src/main/java/org/eclipse/jetty/io/FillInterest.java
new file mode 100644
index 0000000..5294c17
--- /dev/null
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/FillInterest.java
@@ -0,0 +1,135 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.io.IOException;
+import java.nio.channels.ClosedChannelException;
+import java.nio.channels.ReadPendingException;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/* ------------------------------------------------------------ */
+/** 
+ * A Utility class to help implement {@link EndPoint#fillInterested(Callback)}
+ * by keeping state and calling the context and callback objects.
+ * 
+ */
+public abstract class FillInterest
+{
+    private final static Logger LOG = Log.getLogger(FillInterest.class);
+    private final AtomicReference<Callback> _interested = new AtomicReference<>(null);
+
+    /* ------------------------------------------------------------ */
+    protected FillInterest()
+    {
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Call to register interest in a callback when a read is possible.
+     * The callback will be called either immediately if {@link #needsFill()} 
+     * returns true or eventually once {@link #fillable()} is called.
+     * @param callback
+     * @throws ReadPendingException
+     */
+    public <C> void register(Callback callback) throws ReadPendingException
+    {
+        if (callback==null)
+            throw new IllegalArgumentException();
+        
+        if (!_interested.compareAndSet(null,callback))
+        {
+            LOG.warn("Read pending for "+_interested.get()+" prevented "+callback);
+            throw new ReadPendingException();
+        }
+        try
+        {
+            if (needsFill())
+                fillable();
+        }
+        catch(IOException e)
+        {
+            onFail(e);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Call to signal that a read is now possible.
+     */
+    public void fillable()
+    {
+        Callback callback=_interested.get();
+        if (callback!=null && _interested.compareAndSet(callback,null))
+            callback.succeeded();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return True if a read callback has been registered
+     */
+    public boolean isInterested()
+    {
+        return _interested.get()!=null;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Call to signal a failure to a registered interest
+     * @return true if the cause was passed to a {@link Callback} instance
+     */
+    public boolean onFail(Throwable cause)
+    {
+        Callback callback=_interested.get();
+        if (callback!=null && _interested.compareAndSet(callback,null))
+        {
+            callback.failed(cause);
+            return true;
+        }
+        return false;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public void onClose()
+    {
+        Callback callback=_interested.get();
+        if (callback!=null && _interested.compareAndSet(callback,null))
+            callback.failed(new ClosedChannelException());
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        return String.format("FillInterest@%x{%b,%s}",hashCode(),_interested.get(),_interested.get());
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Register the read interest 
+     * Abstract method to be implemented by the Specific ReadInterest to
+     * enquire if a read is immediately possible and if not to schedule a future
+     * call to {@link #fillable()} or {@link #onFail(Throwable)}
+     * @return true if a read is possible
+     * @throws IOException
+     */
+    abstract protected boolean needsFill() throws IOException;
+    
+    
+}
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/IdleTimeout.java b/jetty-io/src/main/java/org/eclipse/jetty/io/IdleTimeout.java
new file mode 100644
index 0000000..a8ee171
--- /dev/null
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/IdleTimeout.java
@@ -0,0 +1,189 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+/**
+ * An Abstract implementation of an Idle Timeout.
+ * <p/>
+ * This implementation is optimised that timeout operations are not cancelled on
+ * every operation. Rather timeout are allowed to expire and a check is then made
+ * to see when the last operation took place.  If the idle timeout has not expired,
+ * the timeout is rescheduled for the earliest possible time a timeout could occur.
+ */
+public abstract class IdleTimeout
+{
+    private static final Logger LOG = Log.getLogger(IdleTimeout.class);
+    private final Scheduler _scheduler;
+    private final AtomicReference<Scheduler.Task> _timeout = new AtomicReference<>();
+    private volatile long _idleTimeout;
+    private volatile long _idleTimestamp = System.currentTimeMillis();
+
+    private final Runnable _idleTask = new Runnable()
+    {
+        @Override
+        public void run()
+        {
+            long idleLeft = checkIdleTimeout();
+            if (idleLeft >= 0)
+                scheduleIdleTimeout(idleLeft > 0 ? idleLeft : getIdleTimeout());
+        }
+    };
+
+    /**
+     * @param scheduler A scheduler used to schedule checks for the idle timeout.
+     */
+    public IdleTimeout(Scheduler scheduler)
+    {
+        _scheduler = scheduler;
+    }
+
+    public long getIdleTimestamp()
+    {
+        return _idleTimestamp;
+    }
+
+    public long getIdleFor()
+    {
+        return System.currentTimeMillis() - getIdleTimestamp();
+    }
+
+    public long getIdleTimeout()
+    {
+        return _idleTimeout;
+    }
+
+    public void setIdleTimeout(long idleTimeout)
+    {
+        long old = _idleTimeout;
+        _idleTimeout = idleTimeout;
+
+        // Do we have an old timeout
+        if (old > 0)
+        {
+            // if the old was less than or equal to the new timeout, then nothing more to do
+            if (old <= idleTimeout)
+                return;
+
+            // old timeout is too long, so cancel it.
+            deactivate();
+        }
+
+        // If we have a new timeout, then check and reschedule
+        if (isOpen())
+            activate();
+    }
+
+    /**
+     * This method should be called when non-idle activity has taken place.
+     */
+    public void notIdle()
+    {
+        _idleTimestamp = System.currentTimeMillis();
+    }
+
+    private void scheduleIdleTimeout(long delay)
+    {
+        Scheduler.Task newTimeout = null;
+        if (isOpen() && delay > 0 && _scheduler != null)
+            newTimeout = _scheduler.schedule(_idleTask, delay, TimeUnit.MILLISECONDS);
+        Scheduler.Task oldTimeout = _timeout.getAndSet(newTimeout);
+        if (oldTimeout != null)
+            oldTimeout.cancel();
+    }
+
+    public void onOpen()
+    {
+        activate();
+    }
+
+    private void activate()
+    {
+        if (_idleTimeout > 0)
+            _idleTask.run();
+    }
+
+    public void onClose()
+    {
+        deactivate();
+    }
+
+    private void deactivate()
+    {
+        Scheduler.Task oldTimeout = _timeout.getAndSet(null);
+        if (oldTimeout != null)
+            oldTimeout.cancel();
+    }
+
+    protected long checkIdleTimeout()
+    {
+        if (isOpen())
+        {
+            long idleTimestamp = getIdleTimestamp();
+            long idleTimeout = getIdleTimeout();
+            long idleElapsed = System.currentTimeMillis() - idleTimestamp;
+            long idleLeft = idleTimeout - idleElapsed;
+
+            if (LOG.isDebugEnabled())
+                LOG.debug("{} idle timeout check, elapsed: {} ms, remaining: {} ms", this, idleElapsed, idleLeft);
+
+            if (idleTimestamp != 0 && idleTimeout > 0)
+            {
+                if (idleLeft <= 0)
+                {
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("{} idle timeout expired", this);
+                    try
+                    {
+                        onIdleExpired(new TimeoutException("Idle timeout expired: " + idleElapsed + "/" + idleTimeout + " ms"));
+                    }
+                    finally
+                    {
+                        notIdle();
+                    }
+                }
+            }
+
+            return idleLeft >= 0 ? idleLeft : 0;
+        }
+        return -1;
+    }
+
+    /**
+     * This abstract method is called when the idle timeout has expired.
+     *
+     * @param timeout a TimeoutException
+     */
+    protected abstract void onIdleExpired(TimeoutException timeout);
+
+    /**
+     * This abstract method should be called to check if idle timeouts
+     * should still be checked.
+     *
+     * @return True if the entity monitored should still be checked for idle timeouts
+     */
+    public abstract boolean isOpen();
+}
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/LeakTrackingByteBufferPool.java b/jetty-io/src/main/java/org/eclipse/jetty/io/LeakTrackingByteBufferPool.java
new file mode 100644
index 0000000..49db096
--- /dev/null
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/LeakTrackingByteBufferPool.java
@@ -0,0 +1,125 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.nio.ByteBuffer;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.LeakDetector;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public class LeakTrackingByteBufferPool extends ContainerLifeCycle implements ByteBufferPool
+{
+    private static final Logger LOG = Log.getLogger(LeakTrackingByteBufferPool.class);
+
+    private final LeakDetector<ByteBuffer> leakDetector = new LeakDetector<ByteBuffer>()
+    {
+        public String id(ByteBuffer resource)
+        {
+            return BufferUtil.toIDString(resource);
+        }
+
+        @Override
+        protected void leaked(LeakInfo leakInfo)
+        {
+            leaked.incrementAndGet();
+            LeakTrackingByteBufferPool.this.leaked(leakInfo);
+        }
+    };
+
+    private final static boolean NOISY = Boolean.getBoolean(LeakTrackingByteBufferPool.class.getName() + ".NOISY");
+    private final ByteBufferPool delegate;
+    private final AtomicLong leakedReleases = new AtomicLong(0);
+    private final AtomicLong leakedAcquires = new AtomicLong(0);
+    private final AtomicLong leaked = new AtomicLong(0);
+
+    public LeakTrackingByteBufferPool(ByteBufferPool delegate)
+    {
+        this.delegate = delegate;
+        addBean(leakDetector);
+        addBean(delegate);
+    }
+
+    @Override
+    public ByteBuffer acquire(int size, boolean direct)
+    {
+        ByteBuffer buffer = delegate.acquire(size, direct);
+        boolean leaked = leakDetector.acquired(buffer);
+        if (NOISY || !leaked)
+        {
+            leakedAcquires.incrementAndGet();
+            LOG.info(String.format("ByteBuffer acquire %s leaked.acquired=%s", leakDetector.id(buffer), leaked ? "normal" : "LEAK"),
+                    new Throwable("LeakStack.Acquire"));
+        }
+        return buffer;
+    }
+
+    @Override
+    public void release(ByteBuffer buffer)
+    {
+        if (buffer == null)
+            return;
+        boolean leaked = leakDetector.released(buffer);
+        if (NOISY || !leaked)
+        {
+            leakedReleases.incrementAndGet();
+            LOG.info(String.format("ByteBuffer release %s leaked.released=%s", leakDetector.id(buffer), leaked ? "normal" : "LEAK"), new Throwable(
+                    "LeakStack.Release"));
+        }
+        delegate.release(buffer);
+    }
+
+    public void clearTracking()
+    {
+        leakedAcquires.set(0);
+        leakedReleases.set(0);
+    }
+
+    /**
+     * @return count of BufferPool.acquire() calls that detected a leak
+     */
+    public long getLeakedAcquires()
+    {
+        return leakedAcquires.get();
+    }
+
+    /**
+     * @return count of BufferPool.release() calls that detected a leak
+     */
+    public long getLeakedReleases()
+    {
+        return leakedReleases.get();
+    }
+
+    /**
+     * @return count of resources that were acquired but not released
+     */
+    public long getLeakedResources()
+    {
+        return leaked.get();
+    }
+
+    protected void leaked(LeakDetector<ByteBuffer>.LeakInfo leakInfo)
+    {
+        LOG.warn("ByteBuffer " + leakInfo.getResourceDescription() + " leaked at:", leakInfo.getStackFrames());
+    }
+}
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/MappedByteBufferPool.java b/jetty-io/src/main/java/org/eclipse/jetty/io/MappedByteBufferPool.java
new file mode 100644
index 0000000..7fd9471
--- /dev/null
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/MappedByteBufferPool.java
@@ -0,0 +1,134 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.nio.ByteBuffer;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.eclipse.jetty.util.BufferUtil;
+
+public class MappedByteBufferPool implements ByteBufferPool
+{
+    private final ConcurrentMap<Integer, Queue<ByteBuffer>> directBuffers = new ConcurrentHashMap<>();
+    private final ConcurrentMap<Integer, Queue<ByteBuffer>> heapBuffers = new ConcurrentHashMap<>();
+    private final int factor;
+
+    public MappedByteBufferPool()
+    {
+        this(1024);
+    }
+
+    public MappedByteBufferPool(int factor)
+    {
+        this.factor = factor;
+    }
+
+    @Override
+    public ByteBuffer acquire(int size, boolean direct)
+    {
+        int bucket = bucketFor(size);
+        ConcurrentMap<Integer, Queue<ByteBuffer>> buffers = buffersFor(direct);
+
+        ByteBuffer result = null;
+        Queue<ByteBuffer> byteBuffers = buffers.get(bucket);
+        if (byteBuffers != null)
+            result = byteBuffers.poll();
+
+        if (result == null)
+        {
+            int capacity = bucket * factor;
+            result = newByteBuffer(capacity, direct);
+        }
+
+        BufferUtil.clear(result);
+        return result;
+    }
+
+    protected ByteBuffer newByteBuffer(int capacity, boolean direct)
+    {
+        return direct ? BufferUtil.allocateDirect(capacity)
+                      : BufferUtil.allocate(capacity);
+    }
+
+    @Override
+    public void release(ByteBuffer buffer)
+    {
+        if (buffer == null)
+            return; // nothing to do
+        
+        // validate that this buffer is from this pool
+        assert((buffer.capacity() % factor) == 0);
+        
+        int bucket = bucketFor(buffer.capacity());
+        ConcurrentMap<Integer, Queue<ByteBuffer>> buffers = buffersFor(buffer.isDirect());
+
+        // Avoid to create a new queue every time, just to be discarded immediately
+        Queue<ByteBuffer> byteBuffers = buffers.get(bucket);
+        if (byteBuffers == null)
+        {
+            byteBuffers = new ConcurrentLinkedQueue<>();
+            Queue<ByteBuffer> existing = buffers.putIfAbsent(bucket, byteBuffers);
+            if (existing != null)
+                byteBuffers = existing;
+        }
+
+        BufferUtil.clear(buffer);
+        byteBuffers.offer(buffer);
+    }
+
+    public void clear()
+    {
+        directBuffers.clear();
+        heapBuffers.clear();
+    }
+
+    private int bucketFor(int size)
+    {
+        int bucket = size / factor;
+        if (size % factor > 0)
+            ++bucket;
+        return bucket;
+    }
+
+    // Package local for testing
+    ConcurrentMap<Integer, Queue<ByteBuffer>> buffersFor(boolean direct)
+    {
+        return direct ? directBuffers : heapBuffers;
+    }
+
+    public static class Tagged extends MappedByteBufferPool
+    {
+        private final AtomicInteger tag = new AtomicInteger();
+
+        @Override
+        protected ByteBuffer newByteBuffer(int capacity, boolean direct)
+        {
+            ByteBuffer buffer = super.newByteBuffer(capacity + 4, direct);
+            buffer.limit(buffer.capacity());
+            buffer.putInt(tag.incrementAndGet());
+            ByteBuffer slice = buffer.slice();
+            BufferUtil.clear(slice);
+            return slice;
+        }
+    }
+}
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/NegotiatingClientConnection.java b/jetty-io/src/main/java/org/eclipse/jetty/io/NegotiatingClientConnection.java
new file mode 100644
index 0000000..f72714f
--- /dev/null
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/NegotiatingClientConnection.java
@@ -0,0 +1,130 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.Map;
+import java.util.concurrent.Executor;
+
+import javax.net.ssl.SSLEngine;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public abstract class NegotiatingClientConnection extends AbstractConnection
+{
+    private static final Logger LOG = Log.getLogger(NegotiatingClientConnection.class);
+
+    private final SSLEngine engine;
+    private final ClientConnectionFactory connectionFactory;
+    private final Map<String, Object> context;
+    private volatile boolean completed;
+
+    protected NegotiatingClientConnection(EndPoint endp, Executor executor, SSLEngine sslEngine, ClientConnectionFactory connectionFactory, Map<String, Object> context)
+    {
+        super(endp, executor);
+        this.engine = sslEngine;
+        this.connectionFactory = connectionFactory;
+        this.context = context;
+    }
+
+    protected SSLEngine getSSLEngine()
+    {
+        return engine;
+    }
+
+    protected void completed()
+    {
+        completed = true;
+    }
+
+    @Override
+    public void onOpen()
+    {
+        super.onOpen();
+        try
+        {
+            getEndPoint().flush(BufferUtil.EMPTY_BUFFER);
+            if (completed)
+                replaceConnection();
+            else
+                fillInterested();
+        }
+        catch (IOException x)
+        {
+            close();
+            throw new RuntimeIOException(x);
+        }
+    }
+
+    @Override
+    public void onFillable()
+    {
+        while (true)
+        {
+            int filled = fill();
+            if (filled == 0 && !completed)
+                fillInterested();
+            if (filled <= 0 || completed)
+                break;
+        }
+        if (completed)
+            replaceConnection();
+    }
+
+    private int fill()
+    {
+        try
+        {
+            return getEndPoint().fill(BufferUtil.EMPTY_BUFFER);
+        }
+        catch (IOException x)
+        {
+            LOG.debug(x);
+            close();
+            return -1;
+        }
+    }
+
+    private void replaceConnection()
+    {
+        EndPoint endPoint = getEndPoint();
+        try
+        {
+            Connection oldConnection = endPoint.getConnection();
+            Connection newConnection = connectionFactory.newConnection(endPoint, context);
+            ClientConnectionFactory.Helper.replaceConnection(oldConnection, newConnection);
+        }
+        catch (Throwable x)
+        {
+            LOG.debug(x);
+            close();
+        }
+    }
+
+    @Override
+    public void close()
+    {
+        // Gentler close for SSL.
+        getEndPoint().shutdownOutput();
+        super.close();
+    }
+}
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/NegotiatingClientConnectionFactory.java b/jetty-io/src/main/java/org/eclipse/jetty/io/NegotiatingClientConnectionFactory.java
new file mode 100644
index 0000000..4dfe100
--- /dev/null
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/NegotiatingClientConnectionFactory.java
@@ -0,0 +1,35 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+
+public abstract class NegotiatingClientConnectionFactory implements ClientConnectionFactory
+{
+    private final ClientConnectionFactory connectionFactory;
+
+    protected NegotiatingClientConnectionFactory(ClientConnectionFactory connectionFactory)
+    {
+        this.connectionFactory = connectionFactory;
+    }
+
+    public ClientConnectionFactory getClientConnectionFactory()
+    {
+        return connectionFactory;
+    }
+}
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/NetworkTrafficListener.java b/jetty-io/src/main/java/org/eclipse/jetty/io/NetworkTrafficListener.java
index 54dfc6c..d73bb5c 100644
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/NetworkTrafficListener.java
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/NetworkTrafficListener.java
@@ -19,6 +19,7 @@
 package org.eclipse.jetty.io;
 
 import java.net.Socket;
+import java.nio.ByteBuffer;
 
 /**
  * <p>A listener for raw network traffic within Jetty.</p>
@@ -52,7 +53,7 @@ public interface NetworkTrafficListener
      * @param socket the socket associated with the remote client
      * @param bytes  the read-only buffer containing the incoming bytes
      */
-    public void incoming(Socket socket, Buffer bytes);
+    public void incoming(Socket socket, ByteBuffer bytes);
 
     /**
      * <p>Callback method invoked when bytes are sent to a remote client from the server.</p>
@@ -61,7 +62,7 @@ public interface NetworkTrafficListener
      * @param socket the socket associated with the remote client
      * @param bytes  the read-only buffer containing the outgoing bytes
      */
-    public void outgoing(Socket socket, Buffer bytes);
+    public void outgoing(Socket socket, ByteBuffer bytes);
 
     /**
      * <p>Callback method invoked when a connection to a remote client has been closed.</p>
@@ -78,17 +79,17 @@ public interface NetworkTrafficListener
     /**
      * <p>A commodity class that implements {@link NetworkTrafficListener} with empty methods.</p>
      */
-    public static class Empty implements NetworkTrafficListener
+    public static class Adapter implements NetworkTrafficListener
     {
         public void opened(Socket socket)
         {
         }
 
-        public void incoming(Socket socket, Buffer bytes)
+        public void incoming(Socket socket, ByteBuffer bytes)
         {
         }
 
-        public void outgoing(Socket socket, Buffer bytes)
+        public void outgoing(Socket socket, ByteBuffer bytes)
         {
         }
 
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/NetworkTrafficSelectChannelEndPoint.java b/jetty-io/src/main/java/org/eclipse/jetty/io/NetworkTrafficSelectChannelEndPoint.java
new file mode 100644
index 0000000..f6a6843
--- /dev/null
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/NetworkTrafficSelectChannelEndPoint.java
@@ -0,0 +1,154 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.io.IOException;
+import java.net.Socket;
+import java.nio.ByteBuffer;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.SocketChannel;
+import java.util.List;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+public class NetworkTrafficSelectChannelEndPoint extends SelectChannelEndPoint
+{
+    private static final Logger LOG = Log.getLogger(NetworkTrafficSelectChannelEndPoint.class);
+
+    private final List<NetworkTrafficListener> listeners;
+
+    public NetworkTrafficSelectChannelEndPoint(SocketChannel channel, SelectorManager.ManagedSelector selectSet, SelectionKey key, Scheduler scheduler, long idleTimeout, List<NetworkTrafficListener> listeners) throws IOException
+    {
+        super(channel, selectSet, key, scheduler, idleTimeout);
+        this.listeners = listeners;
+    }
+
+    @Override
+    public int fill(ByteBuffer buffer) throws IOException
+    {
+        int read = super.fill(buffer);
+        notifyIncoming(buffer, read);
+        return read;
+    }
+
+    @Override
+    public boolean flush(ByteBuffer... buffers) throws IOException
+    {
+        boolean flushed=true;
+        for (ByteBuffer b : buffers)
+        {
+            if (b.hasRemaining())
+            {
+                int position = b.position();
+                ByteBuffer view=b.slice();
+                flushed&=super.flush(b);
+                int l=b.position()-position;
+                view.limit(view.position()+l);
+                notifyOutgoing(view);
+                if (!flushed)
+                    break;
+            }
+        }
+        return flushed;
+    }
+
+    
+
+    @Override
+    public void onOpen()
+    {
+        super.onOpen();
+        if (listeners != null && !listeners.isEmpty())
+        {
+            for (NetworkTrafficListener listener : listeners)
+            {
+                try
+                {
+                    listener.opened(getSocket());
+                }
+                catch (Exception x)
+                {
+                    LOG.warn(x);
+                }
+            }
+        }
+    }
+
+    @Override
+    public void onClose()
+    {
+        super.onClose();
+        if (listeners != null && !listeners.isEmpty())
+        {
+            for (NetworkTrafficListener listener : listeners)
+            {
+                try
+                {
+                    listener.closed(getSocket());
+                }
+                catch (Exception x)
+                {
+                    LOG.warn(x);
+                }
+            }
+        }
+    }
+
+
+    public void notifyIncoming(ByteBuffer buffer, int read)
+    {
+        if (listeners != null && !listeners.isEmpty() && read > 0)
+        {
+            for (NetworkTrafficListener listener : listeners)
+            {
+                try
+                {
+                    ByteBuffer view = buffer.asReadOnlyBuffer();
+                    listener.incoming(getSocket(), view);
+                }
+                catch (Exception x)
+                {
+                    LOG.warn(x);
+                }
+            }
+        }
+    }
+
+    public void notifyOutgoing(ByteBuffer view)
+    {
+        if (listeners != null && !listeners.isEmpty() && view.hasRemaining())
+        {
+            Socket socket=getSocket();
+            for (NetworkTrafficListener listener : listeners)
+            {
+                try
+                {
+                    listener.outgoing(socket, view);   
+                }
+                catch (Exception x)
+                {
+                    LOG.warn(x);
+                }
+            }
+        }
+    }
+
+}
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/PooledBuffers.java b/jetty-io/src/main/java/org/eclipse/jetty/io/PooledBuffers.java
deleted file mode 100644
index e50715e..0000000
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/PooledBuffers.java
+++ /dev/null
@@ -1,122 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.io;
-
-import java.util.Queue;
-import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.concurrent.atomic.AtomicInteger;
-
-public class PooledBuffers extends AbstractBuffers
-{
-    private final Queue<Buffer> _headers;
-    private final Queue<Buffer> _buffers;
-    private final Queue<Buffer> _others;
-    private final AtomicInteger _size = new AtomicInteger();
-    private final int _maxSize;
-    private final boolean _otherHeaders;
-    private final boolean _otherBuffers;
-
-    /* ------------------------------------------------------------ */
-    public PooledBuffers(Buffers.Type headerType, int headerSize, Buffers.Type bufferType, int bufferSize, Buffers.Type otherType,int maxSize)
-    {
-        super(headerType,headerSize,bufferType,bufferSize,otherType);
-        _headers=new ConcurrentLinkedQueue<Buffer>();
-        _buffers=new ConcurrentLinkedQueue<Buffer>();
-        _others=new ConcurrentLinkedQueue<Buffer>();
-        _otherHeaders=headerType==otherType;
-        _otherBuffers=bufferType==otherType;
-        _maxSize=maxSize;
-    }
-
-    /* ------------------------------------------------------------ */
-    public Buffer getHeader()
-    {
-        Buffer buffer = _headers.poll();
-        if (buffer==null)
-            buffer=newHeader();
-        else
-            _size.decrementAndGet();
-        return buffer;
-    }
-
-    /* ------------------------------------------------------------ */
-    public Buffer getBuffer()
-    {
-        Buffer buffer = _buffers.poll();
-        if (buffer==null)
-            buffer=newBuffer();
-        else
-            _size.decrementAndGet();
-        return buffer;
-    }
-
-    /* ------------------------------------------------------------ */
-    public Buffer getBuffer(int size)
-    {
-        if (_otherHeaders && size==getHeaderSize())
-            return getHeader();
-        if (_otherBuffers && size==getBufferSize())
-            return getBuffer();
-
-        // Look for an other buffer
-        Buffer buffer = _others.poll();
-
-        // consume all other buffers until one of the right size is found
-        while (buffer!=null && buffer.capacity()!=size)
-        {
-            _size.decrementAndGet();
-            buffer = _others.poll();
-        }
-
-        if (buffer==null)
-            buffer=newBuffer(size);
-        else
-            _size.decrementAndGet();
-        return buffer;
-    }
-
-    /* ------------------------------------------------------------ */
-    public void returnBuffer(Buffer buffer)
-    {
-        buffer.clear();
-        if (buffer.isVolatile() || buffer.isImmutable())
-            return;
-
-        if (_size.incrementAndGet() > _maxSize)
-            _size.decrementAndGet();
-        else
-        {
-            if (isHeader(buffer))
-                _headers.add(buffer);
-            else if (isBuffer(buffer))
-                _buffers.add(buffer);
-            else
-                _others.add(buffer);
-        }
-    }
-
-    public String toString()
-    {
-        return String.format("%s [%d/%d@%d,%d/%d@%d,%d/%d at -]",
-                getClass().getSimpleName(),
-                _headers.size(),_maxSize,_headerSize,
-                _buffers.size(),_maxSize,_bufferSize,
-                _others.size(),_maxSize);
-    }
-}
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/SelectChannelEndPoint.java b/jetty-io/src/main/java/org/eclipse/jetty/io/SelectChannelEndPoint.java
new file mode 100644
index 0000000..4dc4778
--- /dev/null
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/SelectChannelEndPoint.java
@@ -0,0 +1,211 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.nio.channels.CancelledKeyException;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.SocketChannel;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.eclipse.jetty.io.SelectorManager.ManagedSelector;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+/**
+ * An ChannelEndpoint that can be scheduled by {@link SelectorManager}.
+ */
+public class SelectChannelEndPoint extends ChannelEndPoint implements SelectorManager.SelectableEndPoint
+{
+    public static final Logger LOG = Log.getLogger(SelectChannelEndPoint.class);
+
+    private final Runnable _updateTask = new Runnable()
+    {
+        @Override
+        public void run()
+        {
+            try
+            {
+                if (getChannel().isOpen())
+                {
+                    int oldInterestOps = _key.interestOps();
+                    int newInterestOps = _interestOps.get();
+                    if (newInterestOps != oldInterestOps)
+                        setKeyInterests(oldInterestOps, newInterestOps);
+                }
+            }
+            catch (CancelledKeyException x)
+            {
+                LOG.debug("Ignoring key update for concurrently closed channel {}", this);
+                close();
+            }
+            catch (Exception x)
+            {
+                LOG.warn("Ignoring key update for " + this, x);
+                close();
+            }
+        }
+    };
+
+    /**
+     * true if {@link ManagedSelector#destroyEndPoint(EndPoint)} has not been called
+     */
+    private final AtomicBoolean _open = new AtomicBoolean();
+    private final SelectorManager.ManagedSelector _selector;
+    private final SelectionKey _key;
+    /**
+     * The desired value for {@link SelectionKey#interestOps()}
+     */
+    private final AtomicInteger _interestOps = new AtomicInteger();
+
+    public SelectChannelEndPoint(SocketChannel channel, ManagedSelector selector, SelectionKey key, Scheduler scheduler, long idleTimeout)
+    {
+        super(scheduler,channel);
+        _selector = selector;
+        _key = key;
+        setIdleTimeout(idleTimeout);
+    }
+
+    @Override
+    protected boolean needsFill()
+    {
+        updateLocalInterests(SelectionKey.OP_READ, true);
+        return false;
+    }
+
+    @Override
+    protected void onIncompleteFlush()
+    {
+        updateLocalInterests(SelectionKey.OP_WRITE, true);
+    }
+
+    @Override
+    public void onSelected()
+    {
+        assert _selector.isSelectorThread();
+        int oldInterestOps = _key.interestOps();
+        int readyOps = _key.readyOps();
+        int newInterestOps = oldInterestOps & ~readyOps;
+        setKeyInterests(oldInterestOps, newInterestOps);
+        updateLocalInterests(readyOps, false);
+        if (_key.isReadable())
+            getFillInterest().fillable();
+        if (_key.isWritable())
+            getWriteFlusher().completeWrite();
+    }
+
+
+    private void updateLocalInterests(int operation, boolean add)
+    {
+        while (true)
+        {
+            int oldInterestOps = _interestOps.get();
+            int newInterestOps;
+            if (add)
+                newInterestOps = oldInterestOps | operation;
+            else
+                newInterestOps = oldInterestOps & ~operation;
+
+            if (isInputShutdown())
+                newInterestOps &= ~SelectionKey.OP_READ;
+            if (isOutputShutdown())
+                newInterestOps &= ~SelectionKey.OP_WRITE;
+
+            if (newInterestOps != oldInterestOps)
+            {
+                if (_interestOps.compareAndSet(oldInterestOps, newInterestOps))
+                {
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("Local interests updating {} -> {} for {}", oldInterestOps, newInterestOps, this);
+                    _selector.updateKey(_updateTask);
+                }
+                else
+                {
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("Local interests update conflict: now {}, was {}, attempted {} for {}", _interestOps.get(), oldInterestOps, newInterestOps, this);
+                    continue;
+                }
+            }
+            else
+            {
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Ignoring local interests update {} -> {} for {}", oldInterestOps, newInterestOps, this);
+            }
+            break;
+        }
+    }
+
+
+    private void setKeyInterests(int oldInterestOps, int newInterestOps)
+    {
+        _key.interestOps(newInterestOps);
+        if (LOG.isDebugEnabled())
+            LOG.debug("Key interests updated {} -> {} on {}", oldInterestOps, newInterestOps, this);
+    }
+
+    @Override
+    public void close()
+    {
+        if (_open.compareAndSet(true, false))
+        {
+            super.close();
+            _selector.destroyEndPoint(this);
+        }
+    }
+
+    @Override
+    public boolean isOpen()
+    {
+        // We cannot rely on super.isOpen(), because there is a race between calls to close() and isOpen():
+        // a thread may call close(), which flips the boolean but has not yet called super.close(), and
+        // another thread calls isOpen() which would return true - wrong - if based on super.isOpen().
+        return _open.get();
+    }
+
+    @Override
+    public void onOpen()
+    {
+        if (_open.compareAndSet(false, true))
+            super.onOpen();
+    }
+
+    @Override
+    public String toString()
+    {
+        // Do NOT use synchronized (this)
+        // because it's very easy to deadlock when debugging is enabled.
+        // We do a best effort to print the right toString() and that's it.
+        try
+        {
+            boolean valid = _key!=null && _key.isValid();
+            int keyInterests = valid ? _key.interestOps() : -1;
+            int keyReadiness = valid ? _key.readyOps() : -1;
+            return String.format("%s{io=%d,kio=%d,kro=%d}",
+                    super.toString(),
+                    _interestOps.get(),
+                    keyInterests,
+                    keyReadiness);
+        }
+        catch (CancelledKeyException x)
+        {
+            return String.format("%s{io=%s,kio=-2,kro=-2}", super.toString(), _interestOps.get());
+        }
+    }
+}
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/SelectorManager.java b/jetty-io/src/main/java/org/eclipse/jetty/io/SelectorManager.java
new file mode 100644
index 0000000..e421571
--- /dev/null
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/SelectorManager.java
@@ -0,0 +1,1064 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.net.ConnectException;
+import java.net.Socket;
+import java.net.SocketAddress;
+import java.net.SocketTimeoutException;
+import java.nio.channels.CancelledKeyException;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.Selector;
+import java.nio.channels.ServerSocketChannel;
+import java.nio.channels.SocketChannel;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Queue;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.jetty.util.ConcurrentArrayQueue;
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.component.Dumpable;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.NonBlockingThread;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+/**
+ * <p>{@link SelectorManager} manages a number of {@link ManagedSelector}s that
+ * simplify the non-blocking primitives provided by the JVM via the {@code java.nio} package.</p>
+ * <p>{@link SelectorManager} subclasses implement methods to return protocol-specific
+ * {@link EndPoint}s and {@link Connection}s.</p>
+ */
+public abstract class SelectorManager extends AbstractLifeCycle implements Dumpable
+{
+    public static final String SUBMIT_KEY_UPDATES = "org.eclipse.jetty.io.SelectorManager.submitKeyUpdates";
+    public static final int DEFAULT_CONNECT_TIMEOUT = 15000;
+    protected static final Logger LOG = Log.getLogger(SelectorManager.class);
+    private final static boolean __submitKeyUpdates = Boolean.valueOf(System.getProperty(SUBMIT_KEY_UPDATES, "true"));
+    
+    private final Executor executor;
+    private final Scheduler scheduler;
+    private final ManagedSelector[] _selectors;
+    private long _connectTimeout = DEFAULT_CONNECT_TIMEOUT;
+    private long _selectorIndex;
+    private int _priorityDelta;
+    
+    protected SelectorManager(Executor executor, Scheduler scheduler)
+    {
+        this(executor, scheduler, (Runtime.getRuntime().availableProcessors() + 1) / 2);
+    }
+
+    protected SelectorManager(Executor executor, Scheduler scheduler, int selectors)
+    {
+        if (selectors<=0)
+            throw new IllegalArgumentException("No selectors");
+        this.executor = executor;
+        this.scheduler = scheduler;
+        _selectors = new ManagedSelector[selectors];
+    }
+
+    public Executor getExecutor()
+    {
+        return executor;
+    }
+
+    public Scheduler getScheduler()
+    {
+        return scheduler;
+    }
+
+    /**
+     * Get the connect timeout
+     *
+     * @return the connect timeout (in milliseconds)
+     */
+    public long getConnectTimeout()
+    {
+        return _connectTimeout;
+    }
+
+    /**
+     * Set the connect timeout (in milliseconds)
+     *
+     * @param milliseconds the number of milliseconds for the timeout
+     */
+    public void setConnectTimeout(long milliseconds)
+    {
+        _connectTimeout = milliseconds;
+    }
+
+
+    @ManagedAttribute("The priority delta to apply to selector threads")
+    public int getSelectorPriorityDelta()
+    {
+        return _priorityDelta;
+    }
+
+    /**
+     * Sets the selector thread priority delta to the given amount.
+     * <p>This allows the selector threads to run at a different priority.
+     * Typically this would be used to lower the priority to give preference
+     * to handling previously accepted connections rather than accepting
+     * new connections.</p>
+     *
+     * @param selectorPriorityDelta the amount to change the thread priority
+     *                              delta to (may be negative)
+     * @see Thread#getPriority()
+     */
+    public void setSelectorPriorityDelta(int selectorPriorityDelta)
+    {
+        int oldDelta = _priorityDelta;
+        _priorityDelta = selectorPriorityDelta;
+        if (oldDelta != selectorPriorityDelta && isStarted())
+        {
+            for (ManagedSelector selector : _selectors)
+            {
+                Thread thread = selector._thread;
+                if (thread != null)
+                {
+                    int deltaDiff = selectorPriorityDelta - oldDelta;
+                    thread.setPriority(Math.max(Thread.MIN_PRIORITY, Math.min(Thread.MAX_PRIORITY, thread.getPriority() - deltaDiff)));
+                }
+            }
+        }
+    }
+    
+    /**
+     * Executes the given task in a different thread.
+     *
+     * @param task the task to execute
+     */
+    protected void execute(Runnable task)
+    {
+        executor.execute(task);
+    }
+
+    /**
+     * @return the number of selectors in use
+     */
+    public int getSelectorCount()
+    {
+        return _selectors.length;
+    }
+
+    private ManagedSelector chooseSelector()
+    {
+        // The ++ increment here is not atomic, but it does not matter,
+        // so long as the value changes sometimes, then connections will
+        // be distributed over the available selectors.
+        long s = _selectorIndex++;
+        int index = (int)(s % getSelectorCount());
+        return _selectors[index];
+    }
+
+    /**
+     * <p>Registers a channel to perform a non-blocking connect.</p>
+     * <p>The channel must be set in non-blocking mode, {@link SocketChannel#connect(SocketAddress)}
+     * must be called prior to calling this method, and the connect operation must not be completed
+     * (the return value of {@link SocketChannel#connect(SocketAddress)} must be false).</p>
+     *
+     * @param channel    the channel to register
+     * @param attachment the attachment object
+     * @see #accept(SocketChannel, Object)
+     */
+    public void connect(SocketChannel channel, Object attachment)
+    {
+        ManagedSelector set = chooseSelector();
+        set.submit(set.new Connect(channel, attachment));
+    }
+
+    /**
+     * @see #accept(SocketChannel, Object)
+     */
+    public void accept(SocketChannel channel)
+    {
+        accept(channel, null);
+    }
+
+    /**
+     * <p>Registers a channel to perform non-blocking read/write operations.</p>
+     * <p>This method is called just after a channel has been accepted by {@link ServerSocketChannel#accept()},
+     * or just after having performed a blocking connect via {@link Socket#connect(SocketAddress, int)}, or
+     * just after a non-blocking connect via {@link SocketChannel#connect(SocketAddress)} that completed
+     * successfully.</p>
+     *
+     * @param channel the channel to register
+     * @param attachment the attachment object
+     */
+    public void accept(SocketChannel channel, Object attachment)
+    {
+        final ManagedSelector selector = chooseSelector();
+        selector.submit(selector.new Accept(channel, attachment));
+    }
+    
+    /**
+     * <p>Registers a server channel for accept operations.
+     * When a {@link SocketChannel} is accepted from the given {@link ServerSocketChannel}
+     * then the {@link #accepted(SocketChannel)} method is called, which must be
+     * overridden by a derivation of this class to handle the accepted channel
+     * 
+     * @param server the server channel to register
+     */
+    public void acceptor(ServerSocketChannel server)
+    {
+        final ManagedSelector selector = chooseSelector();
+        selector.submit(selector.new Acceptor(server));
+    }
+    
+    /**
+     * Callback method when a channel is accepted from the {@link ServerSocketChannel}
+     * passed to {@link #acceptor(ServerSocketChannel)}.
+     * The default impl throws an {@link UnsupportedOperationException}, so it must
+     * be overridden by subclasses if a server channel is provided.
+     *
+     * @param channel the
+     * @throws IOException
+     */
+    protected void accepted(SocketChannel channel) throws IOException
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    protected void doStart() throws Exception
+    {
+        super.doStart();
+        for (int i = 0; i < _selectors.length; i++)
+        {
+            ManagedSelector selector = newSelector(i);
+            _selectors[i] = selector;
+            selector.start();
+            execute(new NonBlockingThread(selector));
+        }
+    }
+
+    /**
+     * <p>Factory method for {@link ManagedSelector}.</p>
+     *
+     * @param id an identifier for the {@link ManagedSelector to create}
+     * @return a new {@link ManagedSelector}
+     */
+    protected ManagedSelector newSelector(int id)
+    {
+        return new ManagedSelector(id);
+    }
+
+    @Override
+    protected void doStop() throws Exception
+    {
+        for (ManagedSelector selector : _selectors)
+            selector.stop();
+        super.doStop();
+    }
+
+    /**
+     * <p>Callback method invoked when an endpoint is opened.</p>
+     *
+     * @param endpoint the endpoint being opened
+     */
+    protected void endPointOpened(EndPoint endpoint)
+    {
+        endpoint.onOpen();
+    }
+
+    /**
+     * <p>Callback method invoked when an endpoint is closed.</p>
+     *
+     * @param endpoint the endpoint being closed
+     */
+    protected void endPointClosed(EndPoint endpoint)
+    {
+        endpoint.onClose();
+    }
+
+    /**
+     * <p>Callback method invoked when a connection is opened.</p>
+     *
+     * @param connection the connection just opened
+     */
+    public void connectionOpened(Connection connection)
+    {
+        try
+        {
+            connection.onOpen();
+        }
+        catch (Throwable x)
+        {
+            if (isRunning())
+                LOG.warn("Exception while notifying connection " + connection, x);
+            else
+                LOG.debug("Exception while notifying connection " + connection, x);
+        }
+    }
+
+    /**
+     * <p>Callback method invoked when a connection is closed.</p>
+     *
+     * @param connection the connection just closed
+     */
+    public void connectionClosed(Connection connection)
+    {
+        try
+        {
+            connection.onClose();
+        }
+        catch (Throwable x)
+        {
+            LOG.debug("Exception while notifying connection " + connection, x);
+        }
+    }
+
+    protected boolean finishConnect(SocketChannel channel) throws IOException
+    {
+        return channel.finishConnect();
+    }
+
+    /**
+     * <p>Callback method invoked when a non-blocking connect cannot be completed.</p>
+     * <p>By default it just logs with level warning.</p>
+     *
+     * @param channel the channel that attempted the connect
+     * @param ex the exception that caused the connect to fail
+     * @param attachment the attachment object associated at registration
+     */
+    protected void connectionFailed(SocketChannel channel, Throwable ex, Object attachment)
+    {
+        LOG.warn(String.format("%s - %s", channel, attachment), ex);
+    }
+
+    /**
+     * <p>Factory method to create {@link EndPoint}.</p>
+     * <p>This method is invoked as a result of the registration of a channel via {@link #connect(SocketChannel, Object)}
+     * or {@link #accept(SocketChannel)}.</p>
+     *
+     * @param channel   the channel associated to the endpoint
+     * @param selector the selector the channel is registered to
+     * @param selectionKey      the selection key
+     * @return a new endpoint
+     * @throws IOException if the endPoint cannot be created
+     * @see #newConnection(SocketChannel, EndPoint, Object)
+     */
+    protected abstract EndPoint newEndPoint(SocketChannel channel, SelectorManager.ManagedSelector selector, SelectionKey selectionKey) throws IOException;
+
+    /**
+     * <p>Factory method to create {@link Connection}.</p>
+     *
+     * @param channel    the channel associated to the connection
+     * @param endpoint   the endpoint
+     * @param attachment the attachment
+     * @return a new connection
+     * @throws IOException
+     * @see #newEndPoint(SocketChannel, ManagedSelector, SelectionKey)
+     */
+    public abstract Connection newConnection(SocketChannel channel, EndPoint endpoint, Object attachment) throws IOException;
+
+    @Override
+    public String dump()
+    {
+        return ContainerLifeCycle.dump(this);
+    }
+
+    @Override
+    public void dump(Appendable out, String indent) throws IOException
+    {
+        ContainerLifeCycle.dumpObject(out, this);
+        ContainerLifeCycle.dump(out, indent, TypeUtil.asList(_selectors));
+    }
+
+    private enum State
+    {
+        CHANGES, MORE_CHANGES, SELECT, WAKEUP, PROCESS
+    }
+
+    /**
+     * <p>{@link ManagedSelector} wraps a {@link Selector} simplifying non-blocking operations on channels.</p>
+     * <p>{@link ManagedSelector} runs the select loop, which waits on {@link Selector#select()} until events
+     * happen for registered channels. When events happen, it notifies the {@link EndPoint} associated
+     * with the channel.</p>
+     */
+    public class ManagedSelector extends AbstractLifeCycle implements Runnable, Dumpable
+    {
+        private final AtomicReference<State> _state= new AtomicReference<>(State.PROCESS);
+        private final Queue<Runnable> _changes = new ConcurrentArrayQueue<>();
+        private final int _id;
+        private Selector _selector;
+        private volatile Thread _thread;
+
+        public ManagedSelector(int id)
+        {
+            _id = id;
+            setStopTimeout(5000);
+        }
+
+        @Override
+        protected void doStart() throws Exception
+        {
+            super.doStart();
+            _selector = Selector.open();
+            _state.set(State.PROCESS);
+        }
+
+        @Override
+        protected void doStop() throws Exception
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("Stopping {}", this);
+            Stop stop = new Stop();
+            submit(stop);
+            stop.await(getStopTimeout());
+            if (LOG.isDebugEnabled())
+                LOG.debug("Stopped {}", this);
+        }
+
+        /**
+         * Submit a task to update a selector key.  If the System property {@link SelectorManager#SUBMIT_KEY_UPDATES}
+         * is set true (default is false), the task is passed to {@link #submit(Runnable)}.   Otherwise it is run immediately and the selector 
+         * woken up if need be.   
+         * @param update the update to a key
+         */
+        public void updateKey(Runnable update)
+        {
+            if (__submitKeyUpdates)
+            {
+                submit(update);
+            }
+            else
+            {
+                // Run only 1 change at once
+                synchronized (this)
+                {
+                    runChange(update);
+                }
+                if (_state.compareAndSet(State.SELECT, State.WAKEUP))
+                   wakeup();
+            }
+        }
+        
+        /**
+         * <p>Submits a change to be executed in the selector thread.</p>
+         * <p>Changes may be submitted from any thread, and the selector thread woken up
+         * (if necessary) to execute the change.</p>
+         *
+         * @param change the change to submit
+         */
+        public void submit(Runnable change)
+        {
+            // This method may be called from the selector thread, and therefore
+            // we could directly run the change without queueing, but this may
+            // lead to stack overflows on a busy server, so we always offer the
+            // change to the queue and process the state.
+
+            _changes.offer(change);
+            if (LOG.isDebugEnabled())
+                LOG.debug("Queued change {}", change);
+
+            out: while (true)
+            {
+                switch (_state.get())
+                {
+                    case SELECT:
+                        // Avoid multiple wakeup() calls if we the CAS fails
+                        if (!_state.compareAndSet(State.SELECT, State.WAKEUP))
+                            continue;
+                        wakeup();
+                        break out;
+                    case CHANGES:
+                        // Tell the selector thread that we have more changes.
+                        // If we fail to CAS, we possibly need to wakeup(), so loop.
+                        if (_state.compareAndSet(State.CHANGES, State.MORE_CHANGES))
+                            break out;
+                        continue;
+                    case WAKEUP:
+                        // Do nothing, we have already a wakeup scheduled
+                        break out;
+                    case MORE_CHANGES:
+                        // Do nothing, we already notified the selector thread of more changes
+                        break out;
+                    case PROCESS:
+                        // Do nothing, the changes will be run after the processing
+                        break out;
+                    default:
+                        throw new IllegalStateException();
+                }
+            }
+        }
+
+        private void runChanges()
+        {
+            Runnable change;
+            while ((change = _changes.poll()) != null)
+                runChange(change);
+        }
+
+        protected void runChange(Runnable change)
+        {
+            try
+            {
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Running change {}", change);
+                change.run();
+            }
+            catch (Throwable x)
+            {
+                LOG.debug("Could not run change " + change, x);
+            }
+        }
+
+        @Override
+        public void run()
+        {
+            _thread = Thread.currentThread();
+            String name = _thread.getName();
+            int priority = _thread.getPriority();
+            try
+            {
+                if (_priorityDelta != 0)
+                    _thread.setPriority(Math.max(Thread.MIN_PRIORITY, Math.min(Thread.MAX_PRIORITY, priority + _priorityDelta)));
+
+                _thread.setName(String.format("%s-selector-%s@%h/%d", name, SelectorManager.this.getClass().getSimpleName(), SelectorManager.this.hashCode(), _id));
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Starting {} on {}", _thread, this);
+                while (isRunning())
+                    select();
+                while(isStopping())
+                    runChanges();
+            }
+            finally
+            {
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Stopped {} on {}", _thread, this);
+                _thread.setName(name);
+                if (_priorityDelta != 0)
+                    _thread.setPriority(priority);
+            }
+        }
+
+        /**
+         * <p>Process changes and waits on {@link Selector#select()}.</p>
+         *
+         * @see #submit(Runnable)
+         */
+        public void select()
+        {
+            boolean debug = LOG.isDebugEnabled();
+            try
+            {
+                _state.set(State.CHANGES);
+
+                // Run the changes, and only exit if we ran all changes
+                out: while(true)
+                {
+                    switch (_state.get())
+                    {
+                        case CHANGES:
+                            runChanges();
+                            if (_state.compareAndSet(State.CHANGES, State.SELECT))
+                                break out;
+                            continue;
+                        case MORE_CHANGES:
+                            runChanges();
+                            _state.set(State.CHANGES);
+                            continue;
+                        default:
+                            throw new IllegalStateException();    
+                    }
+                }
+                // Must check first for SELECT and *then* for WAKEUP
+                // because we read the state twice in the assert, and
+                // it could change from SELECT to WAKEUP in between.
+                assert _state.get() == State.SELECT || _state.get() == State.WAKEUP;
+
+                if (debug)
+                    LOG.debug("Selector loop waiting on select");
+                int selected = _selector.select();
+                if (debug)
+                    LOG.debug("Selector loop woken up from select, {}/{} selected", selected, _selector.keys().size());
+
+                _state.set(State.PROCESS);
+
+                Set<SelectionKey> selectedKeys = _selector.selectedKeys();
+                for (SelectionKey key : selectedKeys)
+                {
+                    if (key.isValid())
+                    {
+                        processKey(key);
+                    }
+                    else
+                    {
+                        if (debug)
+                            LOG.debug("Selector loop ignoring invalid key for channel {}", key.channel());
+                        Object attachment = key.attachment();
+                        if (attachment instanceof EndPoint)
+                            ((EndPoint)attachment).close();
+                    }
+                }
+                selectedKeys.clear();
+            }
+            catch (Throwable x)
+            {
+                if (isRunning())
+                    LOG.warn(x);
+                else
+                    LOG.ignore(x);
+            }
+        }
+
+        private void processKey(SelectionKey key)
+        {
+            Object attachment = key.attachment();
+            try
+            {
+                if (attachment instanceof SelectableEndPoint)
+                {
+                    ((SelectableEndPoint)attachment).onSelected();
+                }
+                else if (key.isConnectable())
+                {
+                    processConnect(key, (Connect)attachment);
+                }
+                else if (key.isAcceptable())
+                {
+                    processAccept(key);
+                }
+                else
+                {
+                    throw new IllegalStateException();
+                }
+            }
+            catch (CancelledKeyException x)
+            {
+                LOG.debug("Ignoring cancelled key for channel {}", key.channel());
+                if (attachment instanceof EndPoint)
+                    closeNoExceptions((EndPoint)attachment);
+            }
+            catch (Throwable x)
+            {
+                LOG.warn("Could not process key for channel " + key.channel(), x);
+                if (attachment instanceof EndPoint)
+                    closeNoExceptions((EndPoint)attachment);
+            }
+        }
+
+        private void processConnect(SelectionKey key, Connect connect)
+        {
+            SocketChannel channel = (SocketChannel)key.channel();
+            try
+            {
+                key.attach(connect.attachment);
+                boolean connected = finishConnect(channel);
+                if (connected)
+                {
+                    if (connect.timeout.cancel())
+                    {
+                        key.interestOps(0);
+                        EndPoint endpoint = createEndPoint(channel, key);
+                        key.attach(endpoint);
+                    }
+                    else
+                    {
+                        throw new SocketTimeoutException("Concurrent Connect Timeout");
+                    }
+                }
+                else
+                {
+                    throw new ConnectException();
+                }
+            }
+            catch (Throwable x)
+            {
+                connect.failed(x);
+            }
+        }
+        
+        private void processAccept(SelectionKey key)
+        {
+            ServerSocketChannel server = (ServerSocketChannel)key.channel();
+            SocketChannel channel = null;
+            try
+            {
+                while ((channel = server.accept()) != null)
+                {
+                    accepted(channel);
+                }
+            }
+            catch (Throwable x)
+            {
+                closeNoExceptions(channel);
+                LOG.warn("Accept failed for channel " + channel, x);
+            }
+        }
+
+        private void closeNoExceptions(Closeable closeable)
+        {
+            try
+            {
+                if (closeable != null)
+                    closeable.close();
+            }
+            catch (Throwable x)
+            {
+                LOG.ignore(x);
+            }
+        }
+
+        public void wakeup()
+        {
+            _selector.wakeup();
+        }
+
+        public boolean isSelectorThread()
+        {
+            return Thread.currentThread() == _thread;
+        }
+
+        private EndPoint createEndPoint(SocketChannel channel, SelectionKey selectionKey) throws IOException
+        {
+            EndPoint endPoint = newEndPoint(channel, this, selectionKey);
+            endPointOpened(endPoint);
+            Connection connection = newConnection(channel, endPoint, selectionKey.attachment());
+            endPoint.setConnection(connection);
+            connectionOpened(connection);
+            if (LOG.isDebugEnabled())
+                LOG.debug("Created {}", endPoint);
+            return endPoint;
+        }
+
+        public void destroyEndPoint(EndPoint endPoint)
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("Destroyed {}", endPoint);
+            Connection connection = endPoint.getConnection();
+            if (connection != null)
+                connectionClosed(connection);
+            endPointClosed(endPoint);
+        }
+
+        @Override
+        public String dump()
+        {
+            return ContainerLifeCycle.dump(this);
+        }
+
+        @Override
+        public void dump(Appendable out, String indent) throws IOException
+        {
+            out.append(String.valueOf(this)).append(" id=").append(String.valueOf(_id)).append("\n");
+
+            Thread selecting = _thread;
+
+            Object where = "not selecting";
+            StackTraceElement[] trace = selecting == null ? null : selecting.getStackTrace();
+            if (trace != null)
+            {
+                for (StackTraceElement t : trace)
+                    if (t.getClassName().startsWith("org.eclipse.jetty."))
+                    {
+                        where = t;
+                        break;
+                    }
+            }
+
+            Selector selector = _selector;
+            if (selector != null && selector.isOpen())
+            {
+                final ArrayList<Object> dump = new ArrayList<>(selector.keys().size() * 2);
+                dump.add(where);
+
+                DumpKeys dumpKeys = new DumpKeys(dump);
+                submit(dumpKeys);
+                dumpKeys.await(5, TimeUnit.SECONDS);
+
+                ContainerLifeCycle.dump(out, indent, dump);
+            }
+        }
+
+        public void dumpKeysState(List<Object> dumps)
+        {
+            Selector selector = _selector;
+            Set<SelectionKey> keys = selector.keys();
+            dumps.add(selector + " keys=" + keys.size());
+            for (SelectionKey key : keys)
+            {
+                if (key.isValid())
+                    dumps.add(key.attachment() + " iOps=" + key.interestOps() + " rOps=" + key.readyOps());
+                else
+                    dumps.add(key.attachment() + " iOps=-1 rOps=-1");
+            }
+        }
+
+        @Override
+        public String toString()
+        {
+            Selector selector = _selector;
+            return String.format("%s keys=%d selected=%d",
+                    super.toString(),
+                    selector != null && selector.isOpen() ? selector.keys().size() : -1,
+                    selector != null && selector.isOpen() ? selector.selectedKeys().size() : -1);
+        }
+
+        private class DumpKeys implements Runnable
+        {
+            private final CountDownLatch latch = new CountDownLatch(1);
+            private final List<Object> _dumps;
+
+            private DumpKeys(List<Object> dumps)
+            {
+                this._dumps = dumps;
+            }
+
+            @Override
+            public void run()
+            {
+                dumpKeysState(_dumps);
+                latch.countDown();
+            }
+
+            public boolean await(long timeout, TimeUnit unit)
+            {
+                try
+                {
+                    return latch.await(timeout, unit);
+                }
+                catch (InterruptedException x)
+                {
+                    return false;
+                }
+            }
+        }
+
+        private class Acceptor implements Runnable
+        {
+            private final ServerSocketChannel _channel;
+
+            public Acceptor(ServerSocketChannel channel)
+            {
+                this._channel = channel;
+            }
+
+            @Override
+            public void run()
+            {
+                try
+                {
+                    SelectionKey key = _channel.register(_selector, SelectionKey.OP_ACCEPT, null);
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("{} acceptor={}", this, key);
+                }
+                catch (Throwable x)
+                {
+                    closeNoExceptions(_channel);
+                    LOG.warn(x);
+                }
+            }
+        }
+
+        private class Accept implements Runnable
+        {
+            private final SocketChannel channel;
+            private final Object attachment;
+
+            private Accept(SocketChannel channel, Object attachment)
+            {
+                this.channel = channel;
+                this.attachment = attachment;
+            }
+
+            @Override
+            public void run()
+            {
+                try
+                {
+                    SelectionKey key = channel.register(_selector, 0, attachment);
+                    EndPoint endpoint = createEndPoint(channel, key);
+                    key.attach(endpoint);
+                }
+                catch (Throwable x)
+                {
+                    closeNoExceptions(channel);
+                    LOG.debug(x);
+                }
+            }
+        }
+
+        private class Connect implements Runnable
+        {
+            private final AtomicBoolean failed = new AtomicBoolean();
+            private final SocketChannel channel;
+            private final Object attachment;
+            private final Scheduler.Task timeout;
+
+            private Connect(SocketChannel channel, Object attachment)
+            {
+                this.channel = channel;
+                this.attachment = attachment;
+                this.timeout = scheduler.schedule(new ConnectTimeout(this), getConnectTimeout(), TimeUnit.MILLISECONDS);
+            }
+
+            @Override
+            public void run()
+            {
+                try
+                {
+                    channel.register(_selector, SelectionKey.OP_CONNECT, this);
+                }
+                catch (Throwable x)
+                {
+                    failed(x);
+                }
+            }
+
+            private void failed(Throwable failure)
+            {
+                if (failed.compareAndSet(false, true))
+                {
+                    timeout.cancel();
+                    closeNoExceptions(channel);
+                    connectionFailed(channel, failure, attachment);
+                }
+            }
+        }
+
+        private class ConnectTimeout implements Runnable
+        {
+            private final Connect connect;
+
+            private ConnectTimeout(Connect connect)
+            {
+                this.connect = connect;
+            }
+
+            @Override
+            public void run()
+            {
+                SocketChannel channel = connect.channel;
+                if (channel.isConnectionPending())
+                {
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("Channel {} timed out while connecting, closing it", channel);
+                    connect.failed(new SocketTimeoutException("Connect Timeout"));
+                }
+            }
+        }
+
+        private class Stop implements Runnable
+        {
+            private final CountDownLatch latch = new CountDownLatch(1);
+
+            @Override
+            public void run()
+            {
+                try
+                {
+                    for (SelectionKey key : _selector.keys())
+                    {
+                        Object attachment = key.attachment();
+                        if (attachment instanceof EndPoint)
+                        {
+                            EndPointCloser closer = new EndPointCloser((EndPoint)attachment);
+                            execute(closer);
+                            // We are closing the SelectorManager, so we want to block the
+                            // selector thread here until we have closed all EndPoints.
+                            // This is different than calling close() directly, because close()
+                            // can wait forever, while here we are limited by the stop timeout.
+                            closer.await(getStopTimeout());
+                        }
+                    }
+
+                    closeNoExceptions(_selector);
+                }
+                finally
+                {
+                    latch.countDown();
+                }
+            }
+
+            public boolean await(long timeout)
+            {
+                try
+                {
+                    return latch.await(timeout, TimeUnit.MILLISECONDS);
+                }
+                catch (InterruptedException x)
+                {
+                    return false;
+                }
+            }
+        }
+
+        private class EndPointCloser implements Runnable
+        {
+            private final CountDownLatch latch = new CountDownLatch(1);
+            private final EndPoint endPoint;
+
+            private EndPointCloser(EndPoint endPoint)
+            {
+                this.endPoint = endPoint;
+            }
+
+            @Override
+            public void run()
+            {
+                try
+                {
+                    closeNoExceptions(endPoint.getConnection());
+                }
+                finally
+                {
+                    latch.countDown();
+                }
+            }
+
+            private boolean await(long timeout)
+            {
+                try
+                {
+                    return latch.await(timeout, TimeUnit.MILLISECONDS);
+                }
+                catch (InterruptedException x)
+                {
+                    return false;
+                }
+            }
+        }
+    }
+
+    /**
+     * A {@link SelectableEndPoint} is an {@link EndPoint} that wish to be notified of
+     * non-blocking events by the {@link ManagedSelector}.
+     */
+    public interface SelectableEndPoint extends EndPoint
+    {
+        /**
+         * <p>Callback method invoked when a read or write events has been detected by the {@link ManagedSelector}
+         * for this endpoint.</p>
+         */
+        void onSelected();
+    }
+}
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/SimpleBuffers.java b/jetty-io/src/main/java/org/eclipse/jetty/io/SimpleBuffers.java
deleted file mode 100644
index 30025f8..0000000
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/SimpleBuffers.java
+++ /dev/null
@@ -1,117 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.io;
-
-/* ------------------------------------------------------------ */
-/** SimpleBuffers.
- * Simple implementation of Buffers holder.
- * 
- *
- */
-public class SimpleBuffers implements Buffers
-{   
-    final Buffer _header;
-    final Buffer _buffer;
-    boolean _headerOut;
-    boolean _bufferOut;
-    
-    /* ------------------------------------------------------------ */
-    /**
-     * 
-     */
-    public SimpleBuffers(Buffer header, Buffer buffer)
-    {
-        _header=header;
-        _buffer=buffer;
-    }
-
-    /* ------------------------------------------------------------ */
-    public Buffer getBuffer()
-    {
-        synchronized(this)
-        {
-            if (_buffer!=null && !_bufferOut)
-            {
-                _bufferOut=true;
-                return _buffer;
-            }
-            
-            if (_buffer!=null && _header!=null && _header.capacity()==_buffer.capacity() && !_headerOut)
-            {
-                _headerOut=true;
-                return _header;
-            }
-            
-            if (_buffer!=null)
-                return new ByteArrayBuffer(_buffer.capacity());
-            return new ByteArrayBuffer(4096);
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    public Buffer getHeader()
-    {
-        synchronized(this)
-        {
-            if (_header!=null && !_headerOut)
-            {
-                _headerOut=true;
-                return _header;
-            }
-            
-            if (_buffer!=null && _header!=null && _header.capacity()==_buffer.capacity() && !_bufferOut)
-            {
-                _bufferOut=true;
-                return _buffer;
-            }
-            
-            if (_header!=null)
-                return new ByteArrayBuffer(_header.capacity());
-            return new ByteArrayBuffer(4096);
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    public Buffer getBuffer(int size)
-    {
-        synchronized(this)
-        {
-            if (_header!=null && _header.capacity()==size)
-                return getHeader();
-            if (_buffer!=null && _buffer.capacity()==size)
-                return getBuffer();
-            return null;            
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    public void returnBuffer(Buffer buffer)
-    {
-        synchronized(this)
-        {
-            buffer.clear();
-            if (buffer==_header)
-                _headerOut=false;
-            if (buffer==_buffer)
-                _bufferOut=false;
-        }
-    }
-
-
-}
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/ThreadLocalBuffers.java b/jetty-io/src/main/java/org/eclipse/jetty/io/ThreadLocalBuffers.java
deleted file mode 100644
index d42c35f..0000000
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/ThreadLocalBuffers.java
+++ /dev/null
@@ -1,135 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.io;
-
-
-
-/* ------------------------------------------------------------ */
-/** Abstract Buffer pool.
- * simple unbounded pool of buffers for header, request and response sizes.
- *
- */
-public class ThreadLocalBuffers extends AbstractBuffers 
-{
-    /* ------------------------------------------------------------ */
-    private final ThreadLocal<ThreadBuffers> _buffers=new ThreadLocal<ThreadBuffers>()
-    {
-        @Override
-        protected ThreadBuffers initialValue()
-        {
-            return new ThreadBuffers();
-        }
-    };
-
-    /* ------------------------------------------------------------ */
-    public ThreadLocalBuffers(Buffers.Type headerType, int headerSize, Buffers.Type bufferType, int bufferSize, Buffers.Type otherType)
-    {
-        super(headerType,headerSize,bufferType,bufferSize,otherType);
-    }
-
-    /* ------------------------------------------------------------ */
-    public Buffer getBuffer()
-    {
-        ThreadBuffers buffers = _buffers.get();
-        if (buffers._buffer!=null)
-        {
-            Buffer b=buffers._buffer;
-            buffers._buffer=null;
-            return b;
-        }
-
-        if (buffers._other!=null && isBuffer(buffers._other))
-        {
-            Buffer b=buffers._other;
-            buffers._other=null;
-            return b;
-        }
-
-        return newBuffer();
-    }
-
-    /* ------------------------------------------------------------ */
-    public Buffer getHeader()
-    {
-        ThreadBuffers buffers = _buffers.get();
-        if (buffers._header!=null)
-        {
-            Buffer b=buffers._header;
-            buffers._header=null;
-            return b;
-        }
-
-        if (buffers._other!=null && isHeader(buffers._other))
-        {
-            Buffer b=buffers._other;
-            buffers._other=null;
-            return b;
-        }
-
-        return newHeader();
-    }
-
-    /* ------------------------------------------------------------ */
-    public Buffer getBuffer(int size)
-    {
-        ThreadBuffers buffers = _buffers.get();
-        if (buffers._other!=null && buffers._other.capacity()==size)
-        {
-            Buffer b=buffers._other;
-            buffers._other=null;
-            return b;
-        }
-
-        return newBuffer(size);
-    }
-
-    /* ------------------------------------------------------------ */
-    public void returnBuffer(Buffer buffer)
-    {
-        buffer.clear();
-        if (buffer.isVolatile() || buffer.isImmutable())
-            return;
-        
-        ThreadBuffers buffers = _buffers.get();
-        
-        if (buffers._header==null && isHeader(buffer))
-            buffers._header=buffer;
-        else if (buffers._buffer==null && isBuffer(buffer))
-            buffers._buffer=buffer;
-        else
-            buffers._other=buffer;
-    }
-
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public String toString()
-    {
-        return "{{"+getHeaderSize()+","+getBufferSize()+"}}";
-    }
-    
-    /* ------------------------------------------------------------ */
-    /* ------------------------------------------------------------ */
-    protected static class ThreadBuffers
-    {
-        Buffer _buffer;
-        Buffer _header;
-        Buffer _other;
-    }
-}
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/UncheckedIOException.java b/jetty-io/src/main/java/org/eclipse/jetty/io/UncheckedIOException.java
deleted file mode 100644
index d7b0f60..0000000
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/UncheckedIOException.java
+++ /dev/null
@@ -1,48 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-
-package org.eclipse.jetty.io;
-
-/* ------------------------------------------------------------ */
-/**
- * Subclass of {@link java.lang.RuntimeException} used to signal that there
- * was an {@link java.io.IOException} thrown by underlying {@link UncheckedPrintWriter}
- */
-public class UncheckedIOException extends RuntimeException
-{
-    public UncheckedIOException()
-    {
-        super();
-    }
-
-    public UncheckedIOException(String message)
-    {
-        super(message);
-    }
-
-    public UncheckedIOException(Throwable cause)
-    {
-        super(cause);
-    }
-
-    public UncheckedIOException(String message, Throwable cause)
-    {
-        super(message,cause);
-    }
-}
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/UncheckedPrintWriter.java b/jetty-io/src/main/java/org/eclipse/jetty/io/UncheckedPrintWriter.java
index 4ef8407..dd10e47 100644
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/UncheckedPrintWriter.java
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/UncheckedPrintWriter.java
@@ -129,7 +129,8 @@ public class UncheckedPrintWriter extends PrintWriter
             _ioException.initCause(th);
         }
 
-        LOG.debug(th);
+        if (LOG.isDebugEnabled())
+            LOG.debug(th);
     }
 
 
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/View.java b/jetty-io/src/main/java/org/eclipse/jetty/io/View.java
deleted file mode 100644
index a791917..0000000
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/View.java
+++ /dev/null
@@ -1,251 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.io;
-
-/**
- * A View on another buffer.  Allows operations that do not change the _content or
- * indexes of the backing buffer.
- * 
- * 
- * 
- */
-public class View extends AbstractBuffer
-{
-    Buffer _buffer;
-
-    /**
-     * @param buffer The <code>Buffer</code> on which we are presenting a <code>View</code>.
-     * @param mark The initial value of the {@link Buffer#markIndex mark index}
-     * @param get The initial value of the {@link Buffer#getIndex get index}
-     * @param put The initial value of the {@link Buffer#putIndex put index}
-     * @param access The access level - one of the constants from {@link Buffer}.
-     */
-    public View(Buffer buffer, int mark, int get, int put,int access)
-    {
-        super(READWRITE,!buffer.isImmutable());
-        _buffer=buffer.buffer();
-        setPutIndex(put);
-        setGetIndex(get);
-        setMarkIndex(mark);
-        _access=access;
-    }
-    
-    public View(Buffer buffer)
-    {
-        super(READWRITE,!buffer.isImmutable());
-        _buffer=buffer.buffer();
-        setPutIndex(buffer.putIndex());
-        setGetIndex(buffer.getIndex());
-        setMarkIndex(buffer.markIndex());
-        _access=buffer.isReadOnly()?READONLY:READWRITE;
-    }
-
-    public View()
-    {
-        super(READWRITE,true);
-    }
-    
-    /**
-     * Update view to buffer
-     */
-    public void update(Buffer buffer)
-    {
-        _access=READWRITE;
-        _buffer=buffer.buffer();
-        setGetIndex(0);
-        setPutIndex(buffer.putIndex());
-        setGetIndex(buffer.getIndex());
-        setMarkIndex(buffer.markIndex());
-        _access=buffer.isReadOnly()?READONLY:READWRITE;
-    }
-
-    public void update(int get, int put)
-    {
-        int a=_access;
-        _access=READWRITE;
-        setGetIndex(0);
-        setPutIndex(put);
-        setGetIndex(get);
-        setMarkIndex(-1);
-        _access=a;
-    }
-
-    /**
-     * @return The {@link Buffer#array()} from the underlying buffer.
-     */
-    public byte[] array()
-    {
-        return _buffer.array();
-    }
-
-    /**
-     * @return The {@link Buffer#buffer()} from the underlying buffer.
-     */
-    @Override
-    public Buffer buffer()
-    {
-        return _buffer.buffer();
-    }
-
-    /**
-     * @return The {@link Buffer#capacity} of the underlying buffer.
-     */
-    public int capacity()
-    {
-        return _buffer.capacity();
-    }
-
-    /**
-     *  
-     */
-    @Override
-    public void clear()
-    {
-        setMarkIndex(-1);
-        setGetIndex(0);
-        setPutIndex(_buffer.getIndex());
-        setGetIndex(_buffer.getIndex());
-    }
-
-    /**
-     *  
-     */
-    @Override
-    public void compact()
-    {
-        // TODO
-    }
-
-    /*
-     * (non-Javadoc)
-     * 
-     * @see java.lang.Object#equals(java.lang.Object)
-     */
-    @Override
-    public boolean equals(Object obj)
-    {
-        return  this==obj ||((obj instanceof Buffer)&& obj.equals(this)) || super.equals(obj);
-    }
-
-    /**
-     * @return Whether the underlying buffer is {@link Buffer#isReadOnly read only}
-     */
-    @Override
-    public boolean isReadOnly()
-    {
-        return _buffer.isReadOnly();
-    }
-
-    /**
-     * @return Whether the underlying buffer is {@link Buffer#isVolatile volatile}
-     */
-    @Override
-    public boolean isVolatile()
-    {
-        return true;
-    }
-
-    /**
-     * @return The result of calling {@link Buffer#peek(int)} on the underlying buffer
-     */
-    public byte peek(int index)
-    {
-        return _buffer.peek(index);
-    }
-
-    /**
-     * @return The result of calling {@link Buffer#peek(int, byte[], int, int)} on the underlying buffer
-     */
-    public int peek(int index, byte[] b, int offset, int length)
-    {
-        return _buffer.peek(index,b,offset,length);
-    }
-
-    /**
-     * @return The result of calling {@link Buffer#peek(int, int)} on the underlying buffer
-     */
-    @Override
-    public Buffer peek(int index, int length)
-    {
-        return _buffer.peek(index, length);
-    }
-    
-    /**
-     * @param index
-     * @param src
-     */
-    @Override
-    public int poke(int index, Buffer src)
-    {
-        return _buffer.poke(index,src); 
-    }
-
-    /**
-     * @param index
-     * @param b
-     */
-    public void poke(int index, byte b)
-    {
-        _buffer.poke(index,b);
-    }
-
-    /**
-     * @param index
-     * @param b
-     * @param offset
-     * @param length
-     */
-    @Override
-    public int poke(int index, byte[] b, int offset, int length)
-    {
-        return _buffer.poke(index,b,offset,length);
-    }
-    
-    @Override
-    public String toString()
-    {
-        if (_buffer==null)
-            return "INVALID";
-        return super.toString();
-    }
-    
-    public static class CaseInsensitive extends View implements Buffer.CaseInsensitve
-    {
-        public CaseInsensitive()
-        {
-            super();
-        }
-
-        public CaseInsensitive(Buffer buffer, int mark, int get, int put, int access)
-        {
-            super(buffer,mark,get,put,access);
-        }
-
-        public CaseInsensitive(Buffer buffer)
-        {
-            super(buffer);
-        }
-        
-        @Override
-        public boolean equals(Object obj)
-        {
-            return  this==obj ||((obj instanceof Buffer)&&((Buffer)obj).equalsIgnoreCase(this)) || super.equals(obj);
-        }
-    }
-}
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/WriteFlusher.java b/jetty-io/src/main/java/org/eclipse/jetty/io/WriteFlusher.java
new file mode 100644
index 0000000..93ff173
--- /dev/null
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/WriteFlusher.java
@@ -0,0 +1,508 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.ClosedChannelException;
+import java.nio.channels.WritePendingException;
+import java.util.Arrays;
+import java.util.EnumMap;
+import java.util.EnumSet;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/**
+ * A Utility class to help implement {@link EndPoint#write(Callback, ByteBuffer...)} by calling
+ * {@link EndPoint#flush(ByteBuffer...)} until all content is written.
+ * The abstract method {@link #onIncompleteFlushed()} is called when not all content has been written after a call to
+ * flush and should organise for the {@link #completeWrite()} method to be called when a subsequent call to flush
+ * should  be able to make more progress.
+ * <p>
+ */
+abstract public class WriteFlusher
+{
+    private static final Logger LOG = Log.getLogger(WriteFlusher.class);
+    private static final boolean DEBUG = LOG.isDebugEnabled(); // Easy for the compiler to remove the code if DEBUG==false
+    private static final ByteBuffer[] EMPTY_BUFFERS = new ByteBuffer[]{BufferUtil.EMPTY_BUFFER};
+    private static final EnumMap<StateType, Set<StateType>> __stateTransitions = new EnumMap<>(StateType.class);
+    private static final State __IDLE = new IdleState();
+    private static final State __WRITING = new WritingState();
+    private static final State __COMPLETING = new CompletingState();
+    private final EndPoint _endPoint;
+    private final AtomicReference<State> _state = new AtomicReference<>();
+
+    static
+    {
+        // fill the state machine
+        __stateTransitions.put(StateType.IDLE, EnumSet.of(StateType.WRITING));
+        __stateTransitions.put(StateType.WRITING, EnumSet.of(StateType.IDLE, StateType.PENDING, StateType.FAILED));
+        __stateTransitions.put(StateType.PENDING, EnumSet.of(StateType.COMPLETING,StateType.IDLE));
+        __stateTransitions.put(StateType.COMPLETING, EnumSet.of(StateType.IDLE, StateType.PENDING, StateType.FAILED));
+        __stateTransitions.put(StateType.FAILED, EnumSet.of(StateType.IDLE));
+    }
+
+    // A write operation may either complete immediately:
+    //     IDLE-->WRITING-->IDLE
+    // Or it may not completely flush and go via the PENDING state
+    //     IDLE-->WRITING-->PENDING-->COMPLETING-->IDLE
+    // Or it may take several cycles to complete
+    //     IDLE-->WRITING-->PENDING-->COMPLETING-->PENDING-->COMPLETING-->IDLE
+    //
+    // If a failure happens while in IDLE, it is a noop since there is no operation to tell of the failure.
+    // If a failure happens while in WRITING, but the the write has finished successfully or with an IOExceptions,
+    // the callback's complete or respectively failed methods will be called.
+    // If a failure happens in PENDING state, then the fail method calls the pending callback and moves to IDLE state
+    //
+    //   IDLE--(fail)-->IDLE
+    //   IDLE-->WRITING--(fail)-->FAILED-->IDLE
+    //   IDLE-->WRITING-->PENDING--(fail)-->IDLE
+    //   IDLE-->WRITING-->PENDING-->COMPLETING--(fail)-->FAILED-->IDLE
+    //
+    // So a call to fail in the PENDING state will be directly handled and the state changed to IDLE
+    // A call to fail in the WRITING or COMPLETING states will just set the state to FAILED and the failure will be
+    // handled with the write or completeWrite methods try to move the state from what they thought it was.
+    //
+
+    protected WriteFlusher(EndPoint endPoint)
+    {
+        _state.set(__IDLE);
+        _endPoint = endPoint;
+    }
+
+    private enum StateType
+    {
+        IDLE,
+        WRITING,
+        PENDING,
+        COMPLETING,
+        FAILED
+    }
+
+    /**
+     * Tries to update the current state to the given new state.
+     * @param previous the expected current state
+     * @param next the desired new state
+     * @return the previous state or null if the state transition failed
+     * @throws WritePendingException if currentState is WRITING and new state is WRITING (api usage error)
+     */
+    private boolean updateState(State previous,State next)
+    {
+        if (!isTransitionAllowed(previous,next))
+            throw new IllegalStateException();
+
+        boolean updated = _state.compareAndSet(previous, next);
+        if (DEBUG)
+            LOG.debug("update {}:{}{}{}", this, previous, updated?"-->":"!->",next);
+        return updated;
+    }
+
+    private void fail(PendingState pending)
+    {
+        State current = _state.get();
+        if (current.getType()==StateType.FAILED)
+        {
+            FailedState failed=(FailedState)current;
+            if (updateState(failed,__IDLE))
+            {
+                pending.fail(failed.getCause());
+                return;
+            }
+        }
+        throw new IllegalStateException();
+    }
+
+    private void ignoreFail()
+    {
+        State current = _state.get();
+        while (current.getType()==StateType.FAILED)
+        {
+            if (updateState(current,__IDLE))
+                return;
+            current = _state.get();
+        }
+    }
+
+    private boolean isTransitionAllowed(State currentState, State newState)
+    {
+        Set<StateType> allowedNewStateTypes = __stateTransitions.get(currentState.getType());
+        if (!allowedNewStateTypes.contains(newState.getType()))
+        {
+            LOG.warn("{}: {} -> {} not allowed", this, currentState, newState);
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * State represents a State of WriteFlusher.
+     */
+    private static class State
+    {
+        private final StateType _type;
+
+        private State(StateType stateType)
+        {
+            _type = stateType;
+        }
+
+        public StateType getType()
+        {
+            return _type;
+        }
+
+        @Override
+        public String toString()
+        {
+            return String.format("%s", _type);
+        }
+    }
+
+    /**
+     * In IdleState WriteFlusher is idle and accepts new writes
+     */
+    private static class IdleState extends State
+    {
+        private IdleState()
+        {
+            super(StateType.IDLE);
+        }
+    }
+
+    /**
+     * In WritingState WriteFlusher is currently writing.
+     */
+    private static class WritingState extends State
+    {
+        private WritingState()
+        {
+            super(StateType.WRITING);
+        }
+    }
+
+    /**
+     * In FailedState no more operations are allowed. The current implementation will never recover from this state.
+     */
+    private static class FailedState extends State
+    {
+        private final Throwable _cause;
+        private FailedState(Throwable cause)
+        {
+            super(StateType.FAILED);
+            _cause=cause;
+        }
+
+        public Throwable getCause()
+        {
+            return _cause;
+        }
+    }
+
+    /**
+     * In CompletingState WriteFlusher is flushing buffers that have not been fully written in write(). If write()
+     * didn't flush all buffers in one go, it'll switch the State to PendingState. completeWrite() will then switch to
+     * this state and try to flush the remaining buffers.
+     */
+    private static class CompletingState extends State
+    {
+        private CompletingState()
+        {
+            super(StateType.COMPLETING);
+        }
+    }
+
+    /**
+     * In PendingState not all buffers could be written in one go. Then write() will switch to PendingState() and
+     * preserve the state by creating a new PendingState object with the given parameters.
+     */
+    private class PendingState extends State
+    {
+        private final Callback _callback;
+        private final ByteBuffer[] _buffers;
+
+        private PendingState(ByteBuffer[] buffers, Callback callback)
+        {
+            super(StateType.PENDING);
+            _buffers = buffers;
+            _callback = callback;
+        }
+
+        public ByteBuffer[] getBuffers()
+        {
+            return _buffers;
+        }
+
+        protected boolean fail(Throwable cause)
+        {
+            if (_callback!=null)
+            {
+                _callback.failed(cause);
+                return true;
+            }
+            return false;
+        }
+
+        protected void complete()
+        {
+            if (_callback!=null)
+                _callback.succeeded();
+        }
+    }
+
+    /**
+     * Abstract call to be implemented by specific WriteFlushers. It should schedule a call to {@link #completeWrite()}
+     * or {@link #onFail(Throwable)} when appropriate.
+     */
+    abstract protected void onIncompleteFlushed();
+
+    /**
+     * Tries to switch state to WRITING. If successful it writes the given buffers to the EndPoint. If state transition
+     * fails it'll fail the callback.
+     *
+     * If not all buffers can be written in one go it creates a new <code>PendingState</code> object to preserve the state
+     * and then calls {@link #onIncompleteFlushed()}. The remaining buffers will be written in {@link #completeWrite()}.
+     *
+     * If all buffers have been written it calls callback.complete().
+     *
+     * @param callback the callback to call on either failed or complete
+     * @param buffers the buffers to flush to the endpoint
+     */
+    public void write(Callback callback, ByteBuffer... buffers) throws WritePendingException
+    {
+        if (DEBUG)
+            LOG.debug("write: {} {}", this, BufferUtil.toDetailString(buffers));
+
+        if (!updateState(__IDLE,__WRITING))
+            throw new WritePendingException();
+
+        try
+        {
+            buffers=flush(buffers);
+
+            // if we are incomplete?
+            if (buffers!=null)
+            {
+                if (DEBUG)
+                    LOG.debug("flushed incomplete");
+                PendingState pending=new PendingState(buffers, callback);
+                if (updateState(__WRITING,pending))
+                    onIncompleteFlushed();
+                else
+                    fail(pending);
+                return;
+            }
+
+            // If updateState didn't succeed, we don't care as our buffers have been written
+            if (!updateState(__WRITING,__IDLE))
+                ignoreFail();
+            if (callback!=null)
+                callback.succeeded();
+        }
+        catch (IOException e)
+        {
+            if (DEBUG)
+                LOG.debug("write exception", e);
+            if (updateState(__WRITING,__IDLE))
+            {
+                if (callback!=null)
+                    callback.failed(e);
+            }
+            else
+                fail(new PendingState(buffers, callback));
+        }
+    }
+
+
+    /**
+     * Complete a write that has not completed and that called {@link #onIncompleteFlushed()} to request a call to this
+     * method when a call to {@link EndPoint#flush(ByteBuffer...)} is likely to be able to progress.
+     *
+     * It tries to switch from PENDING to COMPLETING. If state transition fails, then it does nothing as the callback
+     * should have been already failed. That's because the only way to switch from PENDING outside this method is
+     * {@link #onFail(Throwable)} or {@link #onClose()}
+     */
+    public void completeWrite()
+    {         
+        if (DEBUG)
+            LOG.debug("completeWrite: {}", this);
+
+        State previous = _state.get();
+
+        if (previous.getType()!=StateType.PENDING)
+            return; // failure already handled.
+
+        PendingState pending = (PendingState)previous;
+        if (!updateState(pending,__COMPLETING))
+            return; // failure already handled.
+
+        try
+        {
+            ByteBuffer[] buffers = pending.getBuffers();
+
+            buffers=flush(buffers);
+
+            // if we are incomplete?
+            if (buffers!=null)
+            {
+                if (DEBUG)
+                    LOG.debug("flushed incomplete {}",BufferUtil.toDetailString(buffers));
+                if (buffers!=pending.getBuffers())
+                    pending=new PendingState(buffers, pending._callback);
+                if (updateState(__COMPLETING,pending))
+                    onIncompleteFlushed();
+                else
+                    fail(pending);
+                return;
+            }
+
+            // If updateState didn't succeed, we don't care as our buffers have been written
+            if (!updateState(__COMPLETING,__IDLE))
+                ignoreFail();
+            pending.complete();
+        }
+        catch (IOException e)
+        {
+            if (DEBUG)
+                LOG.debug("completeWrite exception", e);
+            if(updateState(__COMPLETING,__IDLE))
+                pending.fail(e);
+            else
+                fail(pending);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Flush the buffers iteratively until no progress is made
+     * @param buffers The buffers to flush
+     * @return The unflushed buffers, or null if all flushed
+     * @throws IOException
+     */
+    protected ByteBuffer[] flush(ByteBuffer[] buffers) throws IOException
+    {
+        boolean progress=true;
+        while(progress && buffers!=null)
+        {
+            int before=buffers.length==0?0:buffers[0].remaining();
+            boolean flushed=_endPoint.flush(buffers);
+            int r=buffers.length==0?0:buffers[0].remaining();
+            
+            if (flushed)
+                return null;
+            
+            progress=before!=r;
+            
+            int not_empty=0;
+            while(r==0)
+            {
+                if (++not_empty==buffers.length)
+                {
+                    buffers=null;
+                    not_empty=0;
+                    break;
+                }
+                progress=true;
+                r=buffers[not_empty].remaining();
+            }
+
+            if (not_empty>0)
+                buffers=Arrays.copyOfRange(buffers,not_empty,buffers.length);
+        }        
+        
+        // If buffers is null, then flush has returned false but has consumed all the data!
+        // This is probably SSL being unable to flush the encrypted buffer, so return EMPTY_BUFFERS
+        // and that will keep this WriteFlusher pending.
+        return buffers==null?EMPTY_BUFFERS:buffers;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Notify the flusher of a failure
+     * @param cause The cause of the failure
+     * @return true if the flusher passed the failure to a {@link Callback} instance
+     */
+    public boolean onFail(Throwable cause)
+    {
+        // Keep trying to handle the failure until we get to IDLE or FAILED state
+        while(true)
+        {
+            State current=_state.get();
+            switch(current.getType())
+            {
+                case IDLE:
+                case FAILED:
+                    if (DEBUG)
+                        LOG.debug("ignored: {} {}", this, cause);
+                    return false;
+
+                case PENDING:
+                    if (DEBUG)
+                        LOG.debug("failed: {} {}", this, cause);
+
+                    PendingState pending = (PendingState)current;
+                    if (updateState(pending,__IDLE))
+                        return pending.fail(cause);
+                    break;
+
+                default:
+                    if (DEBUG)
+                        LOG.debug("failed: {} {}", this, cause);
+
+                    if (updateState(current,new FailedState(cause)))
+                        return false;
+                    break;
+            }
+        }
+    }
+
+    public void onClose()
+    {
+        if (_state.get()==__IDLE)
+            return;
+        onFail(new ClosedChannelException());
+    }
+
+    boolean isIdle()
+    {
+        return _state.get().getType() == StateType.IDLE;
+    }
+
+    public boolean isInProgress()
+    {
+        switch(_state.get().getType())
+        {
+            case WRITING:
+            case PENDING:
+            case COMPLETING:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("WriteFlusher@%x{%s}", hashCode(), _state.get());
+    }
+}
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/WriterOutputStream.java b/jetty-io/src/main/java/org/eclipse/jetty/io/WriterOutputStream.java
index edb1c33..7b74412 100644
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/WriterOutputStream.java
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/WriterOutputStream.java
@@ -21,6 +21,7 @@ package org.eclipse.jetty.io;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.io.Writer;
+import java.nio.charset.Charset;
 
 
 /* ------------------------------------------------------------ */
@@ -33,14 +34,14 @@ import java.io.Writer;
 public class WriterOutputStream extends OutputStream
 {
     protected final Writer _writer;
-    protected final String _encoding;
+    protected final Charset _encoding;
     private final byte[] _buf=new byte[1];
     
     /* ------------------------------------------------------------ */
     public WriterOutputStream(Writer writer, String encoding)
     {
         _writer=writer;
-        _encoding=encoding;
+        _encoding=encoding==null?null:Charset.forName(encoding);
     }
     
     /* ------------------------------------------------------------ */
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/bio/SocketEndPoint.java b/jetty-io/src/main/java/org/eclipse/jetty/io/bio/SocketEndPoint.java
deleted file mode 100644
index a93085e..0000000
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/bio/SocketEndPoint.java
+++ /dev/null
@@ -1,286 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.io.bio;
-
-import java.io.IOException;
-import java.net.InetAddress;
-import java.net.InetSocketAddress;
-import java.net.Socket;
-
-import javax.net.ssl.SSLSocket;
-
-import org.eclipse.jetty.util.StringUtil;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-
-public class SocketEndPoint extends StreamEndPoint
-{
-    private static final Logger LOG = Log.getLogger(SocketEndPoint.class);
-
-    final Socket _socket;
-    final InetSocketAddress _local;
-    final InetSocketAddress _remote;
-
-    /* ------------------------------------------------------------ */
-    /**
-     *
-     */
-    public SocketEndPoint(Socket socket)
-    	throws IOException
-    {
-        super(socket.getInputStream(),socket.getOutputStream());
-        _socket=socket;
-        _local=(InetSocketAddress)_socket.getLocalSocketAddress();
-        _remote=(InetSocketAddress)_socket.getRemoteSocketAddress();
-        super.setMaxIdleTime(_socket.getSoTimeout());
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     *
-     */
-    protected SocketEndPoint(Socket socket, int maxIdleTime)
-        throws IOException
-    {
-        super(socket.getInputStream(),socket.getOutputStream());
-        _socket=socket;
-        _local=(InetSocketAddress)_socket.getLocalSocketAddress();
-        _remote=(InetSocketAddress)_socket.getRemoteSocketAddress();
-        _socket.setSoTimeout(maxIdleTime>0?maxIdleTime:0);
-        super.setMaxIdleTime(maxIdleTime);
-    }
-
-    /* ------------------------------------------------------------ */
-    /* (non-Javadoc)
-     * @see org.eclipse.io.BufferIO#isClosed()
-     */
-    @Override
-    public boolean isOpen()
-    {
-        return super.isOpen() && _socket!=null && !_socket.isClosed();
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public boolean isInputShutdown()
-    {
-        if (_socket instanceof SSLSocket)
-            return super.isInputShutdown();
-        return _socket.isClosed() || _socket.isInputShutdown();
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public boolean isOutputShutdown()
-    {
-        if (_socket instanceof SSLSocket)
-            return super.isOutputShutdown();
-
-        return _socket.isClosed() || _socket.isOutputShutdown();
-    }
-
-
-    /* ------------------------------------------------------------ */
-    /*
-     */
-    protected final void shutdownSocketOutput() throws IOException
-    {
-        if (!_socket.isClosed())
-        {
-            if (!_socket.isOutputShutdown())
-                _socket.shutdownOutput();
-            if (_socket.isInputShutdown())
-                _socket.close();
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    /*
-     * @see org.eclipse.jetty.io.bio.StreamEndPoint#shutdownOutput()
-     */
-    @Override
-    public void shutdownOutput() throws IOException
-    {
-        if (_socket instanceof SSLSocket)
-            super.shutdownOutput();
-        else
-            shutdownSocketOutput();
-    }
-
-
-    /* ------------------------------------------------------------ */
-    /*
-     */
-    public void shutdownSocketInput() throws IOException
-    {
-        if (!_socket.isClosed())
-        {
-            if (!_socket.isInputShutdown())
-                _socket.shutdownInput();
-            if (_socket.isOutputShutdown())
-                _socket.close();
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    /*
-     * @see org.eclipse.jetty.io.bio.StreamEndPoint#shutdownOutput()
-     */
-    @Override
-    public void shutdownInput() throws IOException
-    {
-        if (_socket instanceof SSLSocket)
-            super.shutdownInput();
-        else
-            shutdownSocketInput();
-    }
-
-    /* ------------------------------------------------------------ */
-    /* (non-Javadoc)
-     * @see org.eclipse.io.BufferIO#close()
-     */
-    @Override
-    public void close() throws IOException
-    {
-        _socket.close();
-        _in=null;
-        _out=null;
-    }
-
-
-    /* ------------------------------------------------------------ */
-    /*
-     * @see org.eclipse.io.EndPoint#getLocalAddr()
-     */
-    @Override
-    public String getLocalAddr()
-    {
-       if (_local==null || _local.getAddress()==null || _local.getAddress().isAnyLocalAddress())
-           return StringUtil.ALL_INTERFACES;
-
-        return _local.getAddress().getHostAddress();
-    }
-
-    /* ------------------------------------------------------------ */
-    /*
-     * @see org.eclipse.io.EndPoint#getLocalHost()
-     */
-    @Override
-    public String getLocalHost()
-    {
-       if (_local==null || _local.getAddress()==null || _local.getAddress().isAnyLocalAddress())
-           return StringUtil.ALL_INTERFACES;
-
-        return _local.getAddress().getCanonicalHostName();
-    }
-
-    /* ------------------------------------------------------------ */
-    /*
-     * @see org.eclipse.io.EndPoint#getLocalPort()
-     */
-    @Override
-    public int getLocalPort()
-    {
-        if (_local==null)
-            return -1;
-        return _local.getPort();
-    }
-
-    /* ------------------------------------------------------------ */
-    /*
-     * @see org.eclipse.io.EndPoint#getRemoteAddr()
-     */
-    @Override
-    public String getRemoteAddr()
-    {
-        if (_remote==null)
-            return null;
-        InetAddress addr = _remote.getAddress();
-        return ( addr == null ? null : addr.getHostAddress() );
-    }
-
-    /* ------------------------------------------------------------ */
-    /*
-     * @see org.eclipse.io.EndPoint#getRemoteHost()
-     */
-    @Override
-    public String getRemoteHost()
-    {
-        if (_remote==null)
-            return null;
-        return _remote.getAddress().getCanonicalHostName();
-    }
-
-    /* ------------------------------------------------------------ */
-    /*
-     * @see org.eclipse.io.EndPoint#getRemotePort()
-     */
-    @Override
-    public int getRemotePort()
-    {
-        if (_remote==null)
-            return -1;
-        return _remote.getPort();
-    }
-
-    /* ------------------------------------------------------------ */
-    /*
-     * @see org.eclipse.io.EndPoint#getConnection()
-     */
-    @Override
-    public Object getTransport()
-    {
-        return _socket;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.io.bio.StreamEndPoint#setMaxIdleTime(int)
-     */
-    @Override
-    public void setMaxIdleTime(int timeMs) throws IOException
-    {
-        if (timeMs!=getMaxIdleTime())
-            _socket.setSoTimeout(timeMs>0?timeMs:0);
-        super.setMaxIdleTime(timeMs);
-    }
-
-
-    /* ------------------------------------------------------------ */
-    @Override
-    protected void idleExpired() throws IOException
-    {
-        try
-        {
-            if (!isInputShutdown())
-                shutdownInput();
-        }
-        catch(IOException e)
-        {
-            LOG.ignore(e);
-            _socket.close();
-        }
-    }
-
-    @Override
-    public String toString()
-    {
-        return _local + " <--> " + _remote;
-    }
-}
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/bio/StreamEndPoint.java b/jetty-io/src/main/java/org/eclipse/jetty/io/bio/StreamEndPoint.java
deleted file mode 100644
index 6ff73c3..0000000
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/bio/StreamEndPoint.java
+++ /dev/null
@@ -1,325 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-
-package org.eclipse.jetty.io.bio;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.SocketTimeoutException;
-
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.EndPoint;
-
-public class StreamEndPoint implements EndPoint
-{
-    InputStream _in;
-    OutputStream _out;
-    int _maxIdleTime;
-    boolean _ishut;
-    boolean _oshut;
-
-    /**
-     *
-     */
-    public StreamEndPoint(InputStream in, OutputStream out)
-    {
-        _in=in;
-        _out=out;
-    }
-
-    public boolean isBlocking()
-    {
-        return true;
-    }
-
-    public boolean blockReadable(long millisecs) throws IOException
-    {
-        return true;
-    }
-
-    public boolean blockWritable(long millisecs) throws IOException
-    {
-        return true;
-    }
-
-    /*
-     * @see org.eclipse.io.BufferIO#isOpen()
-     */
-    public boolean isOpen()
-    {
-        return _in!=null;
-    }
-
-    /*
-     * @see org.eclipse.io.BufferIO#isOpen()
-     */
-    public final boolean isClosed()
-    {
-        return !isOpen();
-    }
-
-    public void shutdownOutput() throws IOException
-    {
-        _oshut = true;
-        if (_ishut && _out!=null)
-            _out.close();
-    }
-
-    public boolean isInputShutdown()
-    {
-        return _ishut;
-    }
-
-    public void shutdownInput() throws IOException
-    {
-        _ishut = true;
-        if (_oshut&&_in!=null)
-            _in.close();
-    }
-
-    public boolean isOutputShutdown()
-    {
-        return _oshut;
-    }
-
-    /*
-     * @see org.eclipse.io.BufferIO#close()
-     */
-    public void close() throws IOException
-    {
-        if (_in!=null)
-            _in.close();
-        _in=null;
-        if (_out!=null)
-            _out.close();
-        _out=null;
-    }
-
-    protected void idleExpired() throws IOException
-    {
-        if (_in!=null)
-            _in.close();
-    }
-
-    /* (non-Javadoc)
-     * @see org.eclipse.io.BufferIO#fill(org.eclipse.io.Buffer)
-     */
-    public int fill(Buffer buffer) throws IOException
-    {
-        if (_ishut)
-            return -1;
-        if (_in==null)
-            return 0;
-
-        int space=buffer.space();
-        if (space<=0)
-        {
-            if (buffer.hasContent())
-                return 0;
-            throw new IOException("FULL");
-        }
-
-        try
-        {
-            int filled=buffer.readFrom(_in, space);
-            if (filled<0)
-                shutdownInput();
-            return filled;
-        }
-        catch(SocketTimeoutException e)
-        {
-            idleExpired();
-            return -1;
-        }
-    }
-
-    /* (non-Javadoc)
-     * @see org.eclipse.io.BufferIO#flush(org.eclipse.io.Buffer)
-     */
-    public int flush(Buffer buffer) throws IOException
-    {
-        if (_oshut)
-            return -1;
-        if (_out==null)
-            return 0;
-        int length=buffer.length();
-        if (length>0)
-            buffer.writeTo(_out);
-        if (!buffer.isImmutable())
-            buffer.clear();
-        return length;
-    }
-
-    /* (non-Javadoc)
-     * @see org.eclipse.io.BufferIO#flush(org.eclipse.io.Buffer, org.eclipse.io.Buffer, org.eclipse.io.Buffer)
-     */
-    public int flush(Buffer header, Buffer buffer, Buffer trailer) throws IOException
-    {
-        int len=0;
-
-        if (header!=null)
-        {
-            int tw=header.length();
-            if (tw>0)
-            {
-                int f=flush(header);
-                len=f;
-                if (f<tw)
-                    return len;
-            }
-        }
-
-        if (buffer!=null)
-        {
-            int tw=buffer.length();
-            if (tw>0)
-            {
-                int f=flush(buffer);
-                if (f<0)
-                    return len>0?len:f;
-                len+=f;
-                if (f<tw)
-                    return len;
-            }
-        }
-
-        if (trailer!=null)
-        {
-            int tw=trailer.length();
-            if (tw>0)
-            {
-                int f=flush(trailer);
-                if (f<0)
-                    return len>0?len:f;
-                len+=f;
-            }
-        }
-        return len;
-    }
-
-    /* ------------------------------------------------------------ */
-    /*
-     * @see org.eclipse.io.EndPoint#getLocalAddr()
-     */
-    public String getLocalAddr()
-    {
-        return null;
-    }
-
-    /* ------------------------------------------------------------ */
-    /*
-     * @see org.eclipse.io.EndPoint#getLocalHost()
-     */
-    public String getLocalHost()
-    {
-        return null;
-    }
-
-    /* ------------------------------------------------------------ */
-    /*
-     * @see org.eclipse.io.EndPoint#getLocalPort()
-     */
-    public int getLocalPort()
-    {
-        return 0;
-    }
-
-    /* ------------------------------------------------------------ */
-    /*
-     * @see org.eclipse.io.EndPoint#getRemoteAddr()
-     */
-    public String getRemoteAddr()
-    {
-        return null;
-    }
-
-    /* ------------------------------------------------------------ */
-    /*
-     * @see org.eclipse.io.EndPoint#getRemoteHost()
-     */
-    public String getRemoteHost()
-    {
-        return null;
-    }
-
-    /* ------------------------------------------------------------ */
-    /*
-     * @see org.eclipse.io.EndPoint#getRemotePort()
-     */
-    public int getRemotePort()
-    {
-        return 0;
-    }
-
-    /* ------------------------------------------------------------ */
-    /*
-     * @see org.eclipse.io.EndPoint#getConnection()
-     */
-    public Object getTransport()
-    {
-        return null;
-    }
-
-    /* ------------------------------------------------------------ */
-    public InputStream getInputStream()
-    {
-        return _in;
-    }
-
-    /* ------------------------------------------------------------ */
-    public void setInputStream(InputStream in)
-    {
-        _in=in;
-    }
-
-    /* ------------------------------------------------------------ */
-    public OutputStream getOutputStream()
-    {
-        return _out;
-    }
-
-    /* ------------------------------------------------------------ */
-    public void setOutputStream(OutputStream out)
-    {
-        _out=out;
-    }
-
-
-    /* ------------------------------------------------------------ */
-    public void flush()
-        throws IOException
-    {
-        if (_out != null)
-            _out.flush();
-    }
-
-    /* ------------------------------------------------------------ */
-    public int getMaxIdleTime()
-    {
-        return _maxIdleTime;
-    }
-
-    /* ------------------------------------------------------------ */
-    public void setMaxIdleTime(int timeMs) throws IOException
-    {
-        _maxIdleTime=timeMs;
-    }
-
-}
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/bio/StringEndPoint.java b/jetty-io/src/main/java/org/eclipse/jetty/io/bio/StringEndPoint.java
deleted file mode 100644
index 4226947..0000000
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/bio/StringEndPoint.java
+++ /dev/null
@@ -1,94 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.io.bio;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-
-import org.eclipse.jetty.util.StringUtil;
-
-/**
- * 
- *
- * To change the template for this generated type comment go to
- * Window - Preferences - Java - Code Generation - Code and Comments
- */
-public class StringEndPoint extends StreamEndPoint
-{
-    String _encoding=StringUtil.__UTF8;
-    ByteArrayInputStream _bin = new ByteArrayInputStream(new byte[0]);
-    ByteArrayOutputStream _bout = new ByteArrayOutputStream();
-    
-    public StringEndPoint()
-    {
-        super(null,null);
-        _in=_bin;
-        _out=_bout;
-    }
-    
-    public StringEndPoint(String encoding)
-    {
-        this();
-        if (encoding!=null)
-            _encoding=encoding;
-    }
-    
-    public void setInput(String s) 
-    {
-        try
-        {
-            byte[] bytes = s.getBytes(_encoding);
-            _bin=new ByteArrayInputStream(bytes);
-            _in=_bin;
-            _bout = new ByteArrayOutputStream();
-            _out=_bout;
-            _ishut=false;
-            _oshut=false;
-        }
-        catch(Exception e)
-        {
-            throw new IllegalStateException(e.toString());
-        }
-    }
-    
-    public String getOutput() 
-    {
-        try
-        {
-            String s = new String(_bout.toByteArray(),_encoding);
-            _bout.reset();
-      	  return s;
-        }
-        catch(final Exception e)
-        {
-            throw new IllegalStateException(_encoding)
-            {
-                {initCause(e);}
-            };
-        }
-    }
-
-    /**
-     * @return <code>true</code> if there are bytes remaining to be read from the encoded input
-     */
-    public boolean hasMore()
-    {
-        return _bin.available()>0;
-    }   
-}
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/nio/AsyncConnection.java b/jetty-io/src/main/java/org/eclipse/jetty/io/nio/AsyncConnection.java
deleted file mode 100644
index 3282fe5..0000000
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/nio/AsyncConnection.java
+++ /dev/null
@@ -1,28 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.io.nio;
-
-import java.io.IOException;
-
-import org.eclipse.jetty.io.Connection;
-
-public interface AsyncConnection extends Connection
-{
-    void onInputShutdown() throws IOException;
-}
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/nio/ChannelEndPoint.java b/jetty-io/src/main/java/org/eclipse/jetty/io/nio/ChannelEndPoint.java
deleted file mode 100644
index 385a721..0000000
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/nio/ChannelEndPoint.java
+++ /dev/null
@@ -1,509 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.io.nio;
-
-import java.io.IOException;
-import java.net.InetSocketAddress;
-import java.net.Socket;
-import java.net.SocketException;
-import java.nio.ByteBuffer;
-import java.nio.channels.ByteChannel;
-import java.nio.channels.GatheringByteChannel;
-import java.nio.channels.SelectableChannel;
-import java.nio.channels.SocketChannel;
-
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.util.StringUtil;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-
-/**
- * Channel End Point.
- * <p>Holds the channel and socket for an NIO endpoint.
- *
- */
-public class ChannelEndPoint implements EndPoint
-{
-    private static final Logger LOG = Log.getLogger(ChannelEndPoint.class);
-
-    protected final ByteChannel _channel;
-    protected final ByteBuffer[] _gather2=new ByteBuffer[2];
-    protected final Socket _socket;
-    protected final InetSocketAddress _local;
-    protected final InetSocketAddress _remote;
-    protected volatile int _maxIdleTime;
-    private volatile boolean _ishut;
-    private volatile boolean _oshut;
-
-    public ChannelEndPoint(ByteChannel channel) throws IOException
-    {
-        super();
-        this._channel = channel;
-        _socket=(channel instanceof SocketChannel)?((SocketChannel)channel).socket():null;
-        if (_socket!=null)
-        {
-            _local=(InetSocketAddress)_socket.getLocalSocketAddress();
-            _remote=(InetSocketAddress)_socket.getRemoteSocketAddress();
-            _maxIdleTime=_socket.getSoTimeout();
-        }
-        else
-        {
-            _local=_remote=null;
-        }
-    }
-
-    protected ChannelEndPoint(ByteChannel channel, int maxIdleTime) throws IOException
-    {
-        this._channel = channel;
-        _maxIdleTime=maxIdleTime;
-        _socket=(channel instanceof SocketChannel)?((SocketChannel)channel).socket():null;
-        if (_socket!=null)
-        {
-            _local=(InetSocketAddress)_socket.getLocalSocketAddress();
-            _remote=(InetSocketAddress)_socket.getRemoteSocketAddress();
-            _socket.setSoTimeout(_maxIdleTime);
-        }
-        else
-        {
-            _local=_remote=null;
-        }
-    }
-
-    public boolean isBlocking()
-    {
-        return  !(_channel instanceof SelectableChannel) || ((SelectableChannel)_channel).isBlocking();
-    }
-
-    public boolean blockReadable(long millisecs) throws IOException
-    {
-        return true;
-    }
-
-    public boolean blockWritable(long millisecs) throws IOException
-    {
-        return true;
-    }
-
-    /*
-     * @see org.eclipse.io.EndPoint#isOpen()
-     */
-    public boolean isOpen()
-    {
-        return _channel.isOpen();
-    }
-
-    /** Shutdown the channel Input.
-     * Cannot be overridden. To override, see {@link #shutdownInput()}
-     * @throws IOException
-     */
-    protected final void shutdownChannelInput() throws IOException
-    {
-        LOG.debug("ishut {}", this);
-        _ishut = true;
-        if (_channel.isOpen())
-        {
-            if (_socket != null)
-            {
-                try
-                {
-                    if (!_socket.isInputShutdown())
-                    {
-                        _socket.shutdownInput();
-                    }
-                }
-                catch (SocketException e)
-                {
-                    LOG.debug(e.toString());
-                    LOG.ignore(e);
-                }
-                finally
-                {
-                    if (_oshut)
-                    {
-                        close();
-                    }
-                }
-            }
-        }
-    }
-
-    /* (non-Javadoc)
-     * @see org.eclipse.io.EndPoint#close()
-     */
-    public void shutdownInput() throws IOException
-    {
-        shutdownChannelInput();
-    }
-
-    protected final void shutdownChannelOutput() throws IOException
-    {
-        LOG.debug("oshut {}",this);
-        _oshut = true;
-        if (_channel.isOpen())
-        {
-            if (_socket != null)
-            {
-                try
-                {
-                    if (!_socket.isOutputShutdown())
-                    {
-                        _socket.shutdownOutput();
-                    }
-                }
-                catch (SocketException e)
-                {
-                    LOG.debug(e.toString());
-                    LOG.ignore(e);
-                }
-                finally
-                {
-                    if (_ishut)
-                    {
-                        close();
-                    }
-                }
-            }
-        }
-    }
-
-    /* (non-Javadoc)
-     * @see org.eclipse.io.EndPoint#close()
-     */
-    public void shutdownOutput() throws IOException
-    {
-        shutdownChannelOutput();
-    }
-
-    public boolean isOutputShutdown()
-    {
-        return _oshut || !_channel.isOpen() || _socket != null && _socket.isOutputShutdown();
-    }
-
-    public boolean isInputShutdown()
-    {
-        return _ishut || !_channel.isOpen() || _socket != null && _socket.isInputShutdown();
-    }
-
-    /* (non-Javadoc)
-     * @see org.eclipse.io.EndPoint#close()
-     */
-    public void close() throws IOException
-    {
-        LOG.debug("close {}",this);
-        _channel.close();
-    }
-
-    /* (non-Javadoc)
-     * @see org.eclipse.io.EndPoint#fill(org.eclipse.io.Buffer)
-     */
-    public int fill(Buffer buffer) throws IOException
-    {
-        if (_ishut)
-            return -1;
-        Buffer buf = buffer.buffer();
-        int len=0;
-        if (buf instanceof NIOBuffer)
-        {
-            final NIOBuffer nbuf = (NIOBuffer)buf;
-            final ByteBuffer bbuf=nbuf.getByteBuffer();
-
-            //noinspection SynchronizationOnLocalVariableOrMethodParameter
-            try
-            {
-                synchronized(bbuf)
-                {
-                    try
-                    {
-                        bbuf.position(buffer.putIndex());
-                        len=_channel.read(bbuf);
-                    }
-                    finally
-                    {
-                        buffer.setPutIndex(bbuf.position());
-                        bbuf.position(0);
-                    }
-                }
-
-                if (len<0 && isOpen())
-                {
-                    if (!isInputShutdown())
-                        shutdownInput();
-                    if (isOutputShutdown())
-                        _channel.close();
-                }
-            }
-            catch (IOException x)
-            {
-                LOG.debug("Exception while filling", x);
-                try
-                {
-                    if (_channel.isOpen())
-                        _channel.close();
-                }
-                catch (Exception xx)
-                {
-                    LOG.ignore(xx);
-                }
-
-                if (len>0)
-                    throw x;
-                len=-1;
-            }
-        }
-        else
-        {
-            throw new IOException("Not Implemented");
-        }
-
-        return len;
-    }
-
-    /* (non-Javadoc)
-     * @see org.eclipse.io.EndPoint#flush(org.eclipse.io.Buffer)
-     */
-    public int flush(Buffer buffer) throws IOException
-    {
-        Buffer buf = buffer.buffer();
-        int len=0;
-        if (buf instanceof NIOBuffer)
-        {
-            final NIOBuffer nbuf = (NIOBuffer)buf;
-            final ByteBuffer bbuf=nbuf.getByteBuffer().asReadOnlyBuffer();
-            try
-            {
-                bbuf.position(buffer.getIndex());
-                bbuf.limit(buffer.putIndex());
-                len=_channel.write(bbuf);
-            }
-            finally
-            {
-                if (len>0)
-                    buffer.skip(len);
-            }
-        }
-        else if (buf instanceof RandomAccessFileBuffer)
-        {
-            len = ((RandomAccessFileBuffer)buf).writeTo(_channel,buffer.getIndex(),buffer.length());
-            if (len>0)
-                buffer.skip(len);
-        }
-        else if (buffer.array()!=null)
-        {
-            ByteBuffer b = ByteBuffer.wrap(buffer.array(), buffer.getIndex(), buffer.length());
-            len=_channel.write(b);
-            if (len>0)
-                buffer.skip(len);
-        }
-        else
-        {
-            throw new IOException("Not Implemented");
-        }
-        return len;
-    }
-
-    /* (non-Javadoc)
-     * @see org.eclipse.io.EndPoint#flush(org.eclipse.io.Buffer, org.eclipse.io.Buffer, org.eclipse.io.Buffer)
-     */
-    public int flush(Buffer header, Buffer buffer, Buffer trailer) throws IOException
-    {
-        int length=0;
-
-        Buffer buf0 = header==null?null:header.buffer();
-        Buffer buf1 = buffer==null?null:buffer.buffer();
-
-        if (_channel instanceof GatheringByteChannel &&
-            header!=null && header.length()!=0 && buf0 instanceof NIOBuffer &&
-            buffer!=null && buffer.length()!=0 && buf1 instanceof NIOBuffer)
-        {
-            length = gatheringFlush(header,((NIOBuffer)buf0).getByteBuffer(),buffer,((NIOBuffer)buf1).getByteBuffer());
-        }
-        else
-        {
-            // flush header
-            if (header!=null && header.length()>0)
-                length=flush(header);
-
-            // flush buffer
-            if ((header==null || header.length()==0) &&
-                 buffer!=null && buffer.length()>0)
-                length+=flush(buffer);
-
-            // flush trailer
-            if ((header==null || header.length()==0) &&
-                (buffer==null || buffer.length()==0) &&
-                 trailer!=null && trailer.length()>0)
-                length+=flush(trailer);
-        }
-
-        return length;
-    }
-
-    protected int gatheringFlush(Buffer header, ByteBuffer bbuf0, Buffer buffer, ByteBuffer bbuf1) throws IOException
-    {
-        int length;
-
-        synchronized(this)
-        {
-            // Adjust position indexs of buf0 and buf1
-            bbuf0=bbuf0.asReadOnlyBuffer();
-            bbuf0.position(header.getIndex());
-            bbuf0.limit(header.putIndex());
-            bbuf1=bbuf1.asReadOnlyBuffer();
-            bbuf1.position(buffer.getIndex());
-            bbuf1.limit(buffer.putIndex());
-
-            _gather2[0]=bbuf0;
-            _gather2[1]=bbuf1;
-
-            // do the gathering write.
-            length=(int)((GatheringByteChannel)_channel).write(_gather2);
-
-            int hl=header.length();
-            if (length>hl)
-            {
-                header.clear();
-                buffer.skip(length-hl);
-            }
-            else if (length>0)
-            {
-                header.skip(length);
-            }
-        }
-        return length;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return Returns the channel.
-     */
-    public ByteChannel getChannel()
-    {
-        return _channel;
-    }
-
-
-    /* ------------------------------------------------------------ */
-    /*
-     * @see org.eclipse.io.EndPoint#getLocalAddr()
-     */
-    public String getLocalAddr()
-    {
-        if (_socket==null)
-            return null;
-       if (_local==null || _local.getAddress()==null || _local.getAddress().isAnyLocalAddress())
-           return StringUtil.ALL_INTERFACES;
-        return _local.getAddress().getHostAddress();
-    }
-
-    /* ------------------------------------------------------------ */
-    /*
-     * @see org.eclipse.io.EndPoint#getLocalHost()
-     */
-    public String getLocalHost()
-    {
-        if (_socket==null)
-            return null;
-       if (_local==null || _local.getAddress()==null || _local.getAddress().isAnyLocalAddress())
-           return StringUtil.ALL_INTERFACES;
-        return _local.getAddress().getCanonicalHostName();
-    }
-
-    /* ------------------------------------------------------------ */
-    /*
-     * @see org.eclipse.io.EndPoint#getLocalPort()
-     */
-    public int getLocalPort()
-    {
-        if (_socket==null)
-            return 0;
-        if (_local==null)
-            return -1;
-        return _local.getPort();
-    }
-
-    /* ------------------------------------------------------------ */
-    /*
-     * @see org.eclipse.io.EndPoint#getRemoteAddr()
-     */
-    public String getRemoteAddr()
-    {
-        if (_socket==null)
-            return null;
-        if (_remote==null)
-            return null;
-        return _remote.getAddress().getHostAddress();
-    }
-
-    /* ------------------------------------------------------------ */
-    /*
-     * @see org.eclipse.io.EndPoint#getRemoteHost()
-     */
-    public String getRemoteHost()
-    {
-        if (_socket==null)
-            return null;
-        if (_remote==null)
-            return null;
-        return _remote.getAddress().getCanonicalHostName();
-    }
-
-    /* ------------------------------------------------------------ */
-    /*
-     * @see org.eclipse.io.EndPoint#getRemotePort()
-     */
-    public int getRemotePort()
-    {
-        if (_socket==null)
-            return 0;
-        return _remote==null?-1:_remote.getPort();
-    }
-
-    /* ------------------------------------------------------------ */
-    /*
-     * @see org.eclipse.io.EndPoint#getConnection()
-     */
-    public Object getTransport()
-    {
-        return _channel;
-    }
-
-    /* ------------------------------------------------------------ */
-    public void flush()
-        throws IOException
-    {
-    }
-
-    /* ------------------------------------------------------------ */
-    public int getMaxIdleTime()
-    {
-        return _maxIdleTime;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.io.bio.StreamEndPoint#setMaxIdleTime(int)
-     */
-    public void setMaxIdleTime(int timeMs) throws IOException
-    {
-        if (_socket!=null && timeMs!=_maxIdleTime)
-            _socket.setSoTimeout(timeMs>0?timeMs:0);
-        _maxIdleTime=timeMs;
-    }
-}
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/nio/DirectNIOBuffer.java b/jetty-io/src/main/java/org/eclipse/jetty/io/nio/DirectNIOBuffer.java
deleted file mode 100644
index b1d0912..0000000
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/nio/DirectNIOBuffer.java
+++ /dev/null
@@ -1,354 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.io.nio;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.nio.ByteBuffer;
-import java.nio.channels.Channels;
-import java.nio.channels.FileChannel;
-import java.nio.channels.ReadableByteChannel;
-import java.nio.channels.WritableByteChannel;
-
-import org.eclipse.jetty.io.AbstractBuffer;
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.util.IO;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-
-/* ------------------------------------------------------------------------------- */
-/** 
- * 
- * 
- */
-public class DirectNIOBuffer extends AbstractBuffer implements NIOBuffer
-{ 	
-    private static final Logger LOG = Log.getLogger(DirectNIOBuffer.class);
-
-    protected final ByteBuffer _buf;
-    private ReadableByteChannel _in;
-    private InputStream _inStream;
-    private WritableByteChannel _out;
-    private OutputStream _outStream;
-
-    public DirectNIOBuffer(int size)
-    {
-        super(READWRITE,NON_VOLATILE);
-        _buf = ByteBuffer.allocateDirect(size);
-        _buf.position(0);
-        _buf.limit(_buf.capacity());
-    }
-    
-    public DirectNIOBuffer(ByteBuffer buffer,boolean immutable)
-    {
-        super(immutable?IMMUTABLE:READWRITE,NON_VOLATILE);
-        if (!buffer.isDirect())
-            throw new IllegalArgumentException();
-        _buf = buffer;
-        setGetIndex(buffer.position());
-        setPutIndex(buffer.limit());
-    }
-
-    /**
-     * @param file
-     */
-    public DirectNIOBuffer(File file) throws IOException
-    {
-        super(READONLY,NON_VOLATILE);
-        FileInputStream fis = null;
-        FileChannel fc = null;
-        try
-        {
-            fis = new FileInputStream(file);
-            fc = fis.getChannel();
-            _buf = fc.map(FileChannel.MapMode.READ_ONLY, 0, file.length());
-            setGetIndex(0);
-            setPutIndex((int)file.length());
-            _access=IMMUTABLE;
-        }
-        finally
-        {
-            if (fc != null) try {fc.close();} catch (IOException e){LOG.ignore(e);}
-            IO.close(fis);
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    public boolean isDirect()
-    {
-        return true;
-    }
-
-    /* ------------------------------------------------------------ */
-    public byte[] array()
-    {
-        return null;
-    }
-
-    /* ------------------------------------------------------------ */
-    public int capacity()
-    {
-        return _buf.capacity();
-    }
-
-    /* ------------------------------------------------------------ */
-    public byte peek(int position)
-    {
-        return _buf.get(position);
-    }
-
-    public int peek(int index, byte[] b, int offset, int length)
-    {
-        int l = length;
-        if (index+l > capacity())
-        {
-            l=capacity()-index;
-            if (l==0)
-                return -1;
-        }
-        
-        if (l < 0) 
-            return -1;
-        try
-        {
-            _buf.position(index);
-            _buf.get(b,offset,l);
-        }
-        finally
-        {
-            _buf.position(0);
-        }
-        
-        return l;
-    }
-
-    public void poke(int index, byte b)
-    {
-        if (isReadOnly()) throw new IllegalStateException(__READONLY);
-        if (index < 0) throw new IllegalArgumentException("index<0: " + index + "<0");
-        if (index > capacity())
-                throw new IllegalArgumentException("index>capacity(): " + index + ">" + capacity());
-        _buf.put(index,b);
-    }
-
-    @Override
-    public int poke(int index, Buffer src)
-    {
-        if (isReadOnly()) throw new IllegalStateException(__READONLY);
-
-        byte[] array=src.array();
-        if (array!=null)
-        {
-            return poke(index,array,src.getIndex(),src.length());
-        }
-        else
-        {
-            Buffer src_buf=src.buffer();
-            if (src_buf instanceof DirectNIOBuffer)
-            {
-                ByteBuffer src_bytebuf = ((DirectNIOBuffer)src_buf)._buf;
-                if (src_bytebuf==_buf)
-                    src_bytebuf=_buf.duplicate();
-                try
-                {   
-                    _buf.position(index);
-                    int space = _buf.remaining();
-                    
-                    int length=src.length();
-                    if (length>space)    
-                        length=space;
-                    
-                    src_bytebuf.position(src.getIndex());
-                    src_bytebuf.limit(src.getIndex()+length);
-                    
-                    _buf.put(src_bytebuf);
-                    return length;
-                }
-                finally
-                {
-                    _buf.position(0);
-                    src_bytebuf.limit(src_bytebuf.capacity());
-                    src_bytebuf.position(0);
-                }
-            }
-            else
-                return super.poke(index,src);
-        }
-    }
-    
-    @Override
-    public int poke(int index, byte[] b, int offset, int length)
-    {
-        if (isReadOnly()) throw new IllegalStateException(__READONLY);
-
-        if (index < 0) throw new IllegalArgumentException("index<0: " + index + "<0");
-
-        if (index + length > capacity())
-        {
-            length=capacity()-index;
-            if (length<0)
-                throw new IllegalArgumentException("index>capacity(): " + index + ">" + capacity());
-        }
-
-        try
-        {
-            _buf.position(index);
-            
-            int space=_buf.remaining();
-            
-            if (length>space)
-                length=space;
-            if (length>0)
-                _buf.put(b,offset,length);
-            return length;
-        }
-        finally
-        {
-            _buf.position(0);
-        }
-    }
-    
-    /* ------------------------------------------------------------ */
-    public ByteBuffer getByteBuffer()
-    {
-        return _buf;
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public int readFrom(InputStream in, int max) throws IOException
-    {
-        if (_in==null || !_in.isOpen() || in!=_inStream)
-        {
-            _in=Channels.newChannel(in);
-            _inStream=in;
-        }
-
-        if (max<0 || max>space())
-            max=space();
-        int p = putIndex();
-        
-        try
-        {
-            int len=0, total=0, available=max;
-            int loop=0;
-            while (total<max) 
-            {
-                _buf.position(p);
-                _buf.limit(p+available);
-                len=_in.read(_buf);
-                if (len<0)
-                {
-                    _in=null;
-                    _inStream=in;
-                    break;
-                }
-                else if (len>0)
-                {
-                    p += len;
-                    total += len;
-                    available -= len;
-                    setPutIndex(p);
-                    loop=0;
-                }
-                else if (loop++>1)
-                    break;
-                if (in.available()<=0)
-                    break;
-            }
-            if (len<0 && total==0)
-                return -1;
-            return total;
-            
-        }
-        catch(IOException e)
-        {
-            _in=null;
-            _inStream=in;
-            throw e;
-        }
-        finally
-        {
-            if (_in!=null && !_in.isOpen())
-            {
-                _in=null;
-                _inStream=in;
-            }
-            _buf.position(0);
-            _buf.limit(_buf.capacity());
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public void writeTo(OutputStream out) throws IOException
-    {
-        if (_out==null || !_out.isOpen() || out!=_outStream)
-        {
-            _out=Channels.newChannel(out);
-            _outStream=out;
-        }
-
-        synchronized (_buf)
-        {
-            try
-            {
-                int loop=0;
-                while(hasContent() && _out.isOpen())
-                {
-                    _buf.position(getIndex());
-                    _buf.limit(putIndex());
-                    int len=_out.write(_buf);
-                    if (len<0)
-                        break;
-                    else if (len>0)
-                    {
-                        skip(len);
-                        loop=0;
-                    }
-                    else if (loop++>1)
-                        break;
-                }
-
-            }
-            catch(IOException e)
-            {
-                _out=null;
-                _outStream=null;
-                throw e;
-            }
-            finally
-            {
-                if (_out!=null && !_out.isOpen())
-                {
-                    _out=null;
-                    _outStream=null;
-                }
-                _buf.position(0);
-                _buf.limit(_buf.capacity());
-            }
-        }
-    }
-
-    
-    
-}
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/nio/IndirectNIOBuffer.java b/jetty-io/src/main/java/org/eclipse/jetty/io/nio/IndirectNIOBuffer.java
deleted file mode 100644
index 62135a5..0000000
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/nio/IndirectNIOBuffer.java
+++ /dev/null
@@ -1,62 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.io.nio;
-
-import java.nio.ByteBuffer;
-
-import org.eclipse.jetty.io.ByteArrayBuffer;
-
-public class IndirectNIOBuffer extends ByteArrayBuffer implements NIOBuffer
-{
-    protected final ByteBuffer _buf;
-
-    /* ------------------------------------------------------------ */
-    public IndirectNIOBuffer(int size)
-    {
-        super(size,READWRITE,NON_VOLATILE);
-        _buf = ByteBuffer.wrap(_bytes);
-        _buf.position(0);
-        _buf.limit(_buf.capacity());
-    }
-
-    /* ------------------------------------------------------------ */
-    public IndirectNIOBuffer(ByteBuffer buffer,boolean immutable)
-    {
-        super(buffer.array(),0,0, immutable?IMMUTABLE:READWRITE,NON_VOLATILE);
-        if (buffer.isDirect())
-            throw new IllegalArgumentException();
-        _buf = buffer;
-        _get=buffer.position();
-        _put=buffer.limit();
-        buffer.position(0);
-        buffer.limit(buffer.capacity());
-    }
-    
-    /* ------------------------------------------------------------ */
-    public ByteBuffer getByteBuffer()
-    {
-        return _buf;
-    }
-
-    /* ------------------------------------------------------------ */
-    public boolean isDirect()
-    {
-        return false;
-    }
-}
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/nio/NIOBuffer.java b/jetty-io/src/main/java/org/eclipse/jetty/io/nio/NIOBuffer.java
deleted file mode 100644
index ddbd250..0000000
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/nio/NIOBuffer.java
+++ /dev/null
@@ -1,37 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.io.nio;
-
-import java.nio.ByteBuffer;
-
-import org.eclipse.jetty.io.Buffer;
-
-/* ------------------------------------------------------------------------------- */
-/** 
- * 
- * 
- */
-public interface NIOBuffer extends Buffer
-{
-    /* ------------------------------------------------------------ */
-    public ByteBuffer getByteBuffer();
-
-    /* ------------------------------------------------------------ */
-    public boolean isDirect();
-}
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/nio/NetworkTrafficSelectChannelEndPoint.java b/jetty-io/src/main/java/org/eclipse/jetty/io/nio/NetworkTrafficSelectChannelEndPoint.java
deleted file mode 100644
index b934afe..0000000
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/nio/NetworkTrafficSelectChannelEndPoint.java
+++ /dev/null
@@ -1,148 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.io.nio;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.channels.SelectionKey;
-import java.nio.channels.SocketChannel;
-import java.util.List;
-
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.NetworkTrafficListener;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-
-public class NetworkTrafficSelectChannelEndPoint extends SelectChannelEndPoint
-{
-    private static final Logger LOG = Log.getLogger(NetworkTrafficSelectChannelEndPoint.class);
-
-    private final List<NetworkTrafficListener> listeners;
-
-    public NetworkTrafficSelectChannelEndPoint(SocketChannel channel, SelectorManager.SelectSet selectSet, SelectionKey key, int maxIdleTime, List<NetworkTrafficListener> listeners) throws IOException
-    {
-        super(channel, selectSet, key, maxIdleTime);
-        this.listeners = listeners;
-    }
-
-    @Override
-    public int fill(Buffer buffer) throws IOException
-    {
-        int read = super.fill(buffer);
-        notifyIncoming(buffer, read);
-        return read;
-    }
-
-    @Override
-    public int flush(Buffer buffer) throws IOException
-    {
-        int position = buffer.getIndex();
-        int written = super.flush(buffer);
-        notifyOutgoing(buffer, position, written);
-        return written;
-    }
-
-    @Override
-    protected int gatheringFlush(Buffer header, ByteBuffer bbuf0, Buffer buffer, ByteBuffer bbuf1) throws IOException
-    {
-        int headerPosition = header.getIndex();
-        int headerLength = header.length();
-        int bufferPosition = buffer.getIndex();
-        int written = super.gatheringFlush(header, bbuf0, buffer,bbuf1);
-        notifyOutgoing(header, headerPosition, written > headerLength ? headerLength : written);
-        notifyOutgoing(buffer, bufferPosition, written > headerLength ? written - headerLength : 0);
-        return written;
-    }
-
-    public void notifyOpened()
-    {
-        if (listeners != null && !listeners.isEmpty())
-        {
-            for (NetworkTrafficListener listener : listeners)
-            {
-                try
-                {
-                    listener.opened(_socket);
-                }
-                catch (Exception x)
-                {
-                    LOG.warn(x);
-                }
-            }
-        }
-    }
-
-    public void notifyIncoming(Buffer buffer, int read)
-    {
-        if (listeners != null && !listeners.isEmpty() && read > 0)
-        {
-            for (NetworkTrafficListener listener : listeners)
-            {
-                try
-                {
-                    Buffer view = buffer.asReadOnlyBuffer();
-                    listener.incoming(_socket, view);
-                }
-                catch (Exception x)
-                {
-                    LOG.warn(x);
-                }
-            }
-        }
-    }
-
-    public void notifyOutgoing(Buffer buffer, int position, int written)
-    {
-        if (listeners != null && !listeners.isEmpty() && written > 0)
-        {
-            for (NetworkTrafficListener listener : listeners)
-            {
-                try
-                {
-                    Buffer view = buffer.asReadOnlyBuffer();
-                    view.setGetIndex(position);
-                    view.setPutIndex(position + written);
-                    listener.outgoing(_socket, view);
-                }
-                catch (Exception x)
-                {
-                    LOG.warn(x);
-                }
-            }
-        }
-    }
-
-    public void notifyClosed()
-    {
-        if (listeners != null && !listeners.isEmpty())
-        {
-            for (NetworkTrafficListener listener : listeners)
-            {
-                try
-                {
-                    listener.closed(_socket);
-                }
-                catch (Exception x)
-                {
-                    LOG.warn(x);
-                }
-            }
-        }
-    }
-}
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/nio/RandomAccessFileBuffer.java b/jetty-io/src/main/java/org/eclipse/jetty/io/nio/RandomAccessFileBuffer.java
deleted file mode 100644
index 798461a..0000000
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/nio/RandomAccessFileBuffer.java
+++ /dev/null
@@ -1,196 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.io.nio;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.RandomAccessFile;
-import java.nio.channels.FileChannel;
-import java.nio.channels.WritableByteChannel;
-
-import org.eclipse.jetty.io.AbstractBuffer;
-import org.eclipse.jetty.io.Buffer;
-
-public class RandomAccessFileBuffer extends AbstractBuffer implements Buffer
-{
-    final RandomAccessFile _file;
-    final FileChannel _channel;
-    final int _capacity;
-
-    public RandomAccessFileBuffer(File file) 
-        throws FileNotFoundException
-    {
-        super(READWRITE,true);
-        assert file.length()<=Integer.MAX_VALUE;
-        _file = new RandomAccessFile(file,"rw");
-        _channel=_file.getChannel();
-        _capacity=Integer.MAX_VALUE;
-        setGetIndex(0);
-        setPutIndex((int)file.length());
-    }
-    
-    public RandomAccessFileBuffer(File file,int capacity) 
-        throws FileNotFoundException
-    {
-        super(READWRITE,true);
-        assert capacity>=file.length();
-        assert file.length()<=Integer.MAX_VALUE;
-        _capacity=capacity;
-        _file = new RandomAccessFile(file,"rw");
-        _channel=_file.getChannel();
-        setGetIndex(0);
-        setPutIndex((int)file.length());
-    }
-    
-    public RandomAccessFileBuffer(File file,int capacity,int access) 
-        throws FileNotFoundException
-    {
-        super(access,true);
-        assert capacity>=file.length();
-        assert file.length()<=Integer.MAX_VALUE;
-        _capacity=capacity;
-        _file = new RandomAccessFile(file,access==READWRITE?"rw":"r");
-        _channel=_file.getChannel();
-        setGetIndex(0);
-        setPutIndex((int)file.length());
-    }
-
-    public byte[] array()
-    {
-        return null;
-    }
-
-    public int capacity()
-    {
-        return _capacity;
-    }
-
-    @Override
-    public void clear()
-    {
-        try
-        {
-            synchronized (_file)
-            {
-                super.clear();
-                _file.setLength(0);
-            }
-        }
-        catch(Exception e)
-        {
-            throw new RuntimeException(e);
-        }
-    }
-
-
-    @Override
-    public byte peek()
-    {
-        synchronized (_file)
-        {
-            try
-            {
-                if (_get!=_file.getFilePointer())
-                    _file.seek(_get);
-                return _file.readByte();
-            }
-            catch(Exception e)
-            {
-                throw new RuntimeException(e);
-            }
-        }
-    }
-
-    public byte peek(int index)
-    {
-        synchronized (_file)
-        {
-            try
-            {
-                _file.seek(index);
-                return _file.readByte();
-            }
-            catch(Exception e)
-            {
-                throw new RuntimeException(e);
-            }
-        }
-    }
-
-    public int peek(int index, byte[] b, int offset, int length)
-    {
-        synchronized (_file)
-        {
-            try
-            {
-                _file.seek(index);
-                return _file.read(b,offset,length);
-            }
-            catch(Exception e)
-            {
-                throw new RuntimeException(e);
-            }
-        }
-    }
-
-    public void poke(int index, byte b)
-    {
-        synchronized (_file)
-        {
-            try
-            {
-                _file.seek(index);
-                _file.writeByte(b);
-            }
-            catch(Exception e)
-            {
-                throw new RuntimeException(e);
-            }
-        }
-    }
-
-    @Override
-    public int poke(int index, byte[] b, int offset, int length)
-    {
-        synchronized (_file)
-        {
-            try
-            {
-                _file.seek(index);
-                _file.write(b,offset,length);
-                return length;
-            }
-            catch(Exception e)
-            {
-                throw new RuntimeException(e);
-            }
-        }
-    }
-    
-    public int writeTo(WritableByteChannel channel,int index, int length)
-        throws IOException
-    {
-        synchronized (_file)
-        {
-            return (int)_channel.transferTo(index,length,channel);
-        }
-    }
-    
-}
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/nio/SelectChannelEndPoint.java b/jetty-io/src/main/java/org/eclipse/jetty/io/nio/SelectChannelEndPoint.java
deleted file mode 100644
index 2fbd532..0000000
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/nio/SelectChannelEndPoint.java
+++ /dev/null
@@ -1,868 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.io.nio;
-
-import java.io.IOException;
-import java.io.InterruptedIOException;
-import java.nio.channels.ClosedChannelException;
-import java.nio.channels.SelectableChannel;
-import java.nio.channels.SelectionKey;
-import java.nio.channels.SocketChannel;
-import java.util.Locale;
-
-import org.eclipse.jetty.io.AsyncEndPoint;
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.ConnectedEndPoint;
-import org.eclipse.jetty.io.Connection;
-import org.eclipse.jetty.io.EofException;
-import org.eclipse.jetty.io.nio.SelectorManager.SelectSet;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.util.thread.Timeout.Task;
-
-/* ------------------------------------------------------------ */
-/**
- * An Endpoint that can be scheduled by {@link SelectorManager}.
- */
-public class SelectChannelEndPoint extends ChannelEndPoint implements AsyncEndPoint, ConnectedEndPoint
-{
-    public static final Logger LOG=Log.getLogger("org.eclipse.jetty.io.nio");
-
-    private final boolean WORK_AROUND_JVM_BUG_6346658 = System.getProperty("os.name").toLowerCase(Locale.ENGLISH).contains("win");
-    private final SelectorManager.SelectSet _selectSet;
-    private final SelectorManager _manager;
-    private  SelectionKey _key;
-    private final Runnable _handler = new Runnable()
-        {
-            public void run() { handle(); }
-        };
-
-    /** The desired value for {@link SelectionKey#interestOps()} */
-    private int _interestOps;
-
-    /**
-     * The connection instance is the handler for any IO activity on the endpoint.
-     * There is a different type of connection for HTTP, AJP, WebSocket and
-     * ProxyConnect.   The connection may change for an SCEP as it is upgraded
-     * from HTTP to proxy connect or websocket.
-     */
-    private volatile AsyncConnection _connection;
-
-    private static final int STATE_NEEDS_DISPATCH=-1;
-    private static final int STATE_UNDISPATCHED=0;
-    private static final int STATE_DISPATCHED=1;
-    private static final int STATE_ASYNC=2;
-    private int _state;
-    
-    private boolean _onIdle;
-
-    /** true if the last write operation succeed and wrote all offered bytes */
-    private volatile boolean _writable = true;
-
-
-    /** True if a thread has is blocked in {@link #blockReadable(long)} */
-    private boolean _readBlocked;
-
-    /** True if a thread has is blocked in {@link #blockWritable(long)} */
-    private boolean _writeBlocked;
-
-    /** true if {@link SelectSet#destroyEndPoint(SelectChannelEndPoint)} has not been called */
-    private boolean _open;
-
-    private volatile long _idleTimestamp;
-    private volatile boolean _checkIdle;
-    
-    private boolean _interruptable;
-
-    private boolean _ishut;
-
-    /* ------------------------------------------------------------ */
-    public SelectChannelEndPoint(SocketChannel channel, SelectSet selectSet, SelectionKey key, int maxIdleTime)
-        throws IOException
-    {
-        super(channel, maxIdleTime);
-
-        _manager = selectSet.getManager();
-        _selectSet = selectSet;
-        _state=STATE_UNDISPATCHED;
-        _onIdle=false;
-        _open=true;
-        _key = key;
-
-        setCheckForIdle(true);
-    }
-
-    /* ------------------------------------------------------------ */
-    public SelectionKey getSelectionKey()
-    {
-        synchronized (this)
-        {
-            return _key;
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    public SelectorManager getSelectManager()
-    {
-        return _manager;
-    }
-
-    /* ------------------------------------------------------------ */
-    public Connection getConnection()
-    {
-        return _connection;
-    }
-
-    /* ------------------------------------------------------------ */
-    public void setConnection(Connection connection)
-    {
-        Connection old=_connection;
-        _connection=(AsyncConnection)connection;
-        if (old!=null && old!=_connection)
-            _manager.endPointUpgraded(this,old);
-    }
-
-    /* ------------------------------------------------------------ */
-    public long getIdleTimestamp()
-    {
-        return _idleTimestamp;
-    }
-
-    /* ------------------------------------------------------------ */
-    /** Called by selectSet to schedule handling
-     *
-     */
-    public void schedule()
-    {
-        synchronized (this)
-        {
-            // If there is no key, then do nothing
-            if (_key == null || !_key.isValid())
-            {
-                _readBlocked=false;
-                _writeBlocked=false;
-                this.notifyAll();
-                return;
-            }
-
-            // If there are threads dispatched reading and writing
-            if (_readBlocked || _writeBlocked)
-            {
-                // assert _dispatched;
-                if (_readBlocked && _key.isReadable())
-                    _readBlocked=false;
-                if (_writeBlocked && _key.isWritable())
-                    _writeBlocked=false;
-
-                // wake them up is as good as a dispatched.
-                this.notifyAll();
-
-                // we are not interested in further selecting
-                _key.interestOps(0);
-                if (_state<STATE_DISPATCHED)
-                    updateKey();
-                return;
-            }
-
-            // Remove writeable op
-            if ((_key.readyOps() & SelectionKey.OP_WRITE) == SelectionKey.OP_WRITE && (_key.interestOps() & SelectionKey.OP_WRITE) == SelectionKey.OP_WRITE)
-            {
-                // Remove writeable op
-                _interestOps = _key.interestOps() & ~SelectionKey.OP_WRITE;
-                _key.interestOps(_interestOps);
-                _writable = true; // Once writable is in ops, only removed with dispatch.
-            }
-
-            // If dispatched, then deregister interest
-            if (_state>=STATE_DISPATCHED)
-                _key.interestOps(0);
-            else
-            {
-                // other wise do the dispatch
-                dispatch();
-                if (_state>=STATE_DISPATCHED && !_selectSet.getManager().isDeferringInterestedOps0())
-                {
-                    _key.interestOps(0);
-                }
-            }
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    public void asyncDispatch()
-    {
-        synchronized(this)
-        {
-            switch(_state)
-            {
-                case STATE_NEEDS_DISPATCH:
-                case STATE_UNDISPATCHED:
-                    dispatch();
-                    break;
-                    
-                case STATE_DISPATCHED:
-                case STATE_ASYNC:
-                    _state=STATE_ASYNC;
-                    break;
-            }
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    public void dispatch()
-    {
-        synchronized(this)
-        {
-            if (_state<=STATE_UNDISPATCHED)
-            {
-                if (_onIdle)
-                    _state = STATE_NEEDS_DISPATCH;
-                else
-                {
-                    _state = STATE_DISPATCHED;
-                    boolean dispatched = _manager.dispatch(_handler);
-                    if(!dispatched)
-                    {
-                        _state = STATE_NEEDS_DISPATCH;
-                        LOG.warn("Dispatched Failed! "+this+" to "+_manager);
-                        updateKey();
-                    }
-                }
-            }
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Called when a dispatched thread is no longer handling the endpoint.
-     * The selection key operations are updated.
-     * @return If false is returned, the endpoint has been redispatched and
-     * thread must keep handling the endpoint.
-     */
-    protected boolean undispatch()
-    {
-        synchronized (this)
-        {
-            switch(_state)
-            {
-                case STATE_ASYNC:
-                    _state=STATE_DISPATCHED;
-                    return false;
-
-                default:
-                    _state=STATE_UNDISPATCHED;
-                    updateKey();
-                    return true;
-            }
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    public void cancelTimeout(Task task)
-    {
-        getSelectSet().cancelTimeout(task);
-    }
-
-    /* ------------------------------------------------------------ */
-    public void scheduleTimeout(Task task, long timeoutMs)
-    {
-        getSelectSet().scheduleTimeout(task,timeoutMs);
-    }
-
-    /* ------------------------------------------------------------ */
-    public void setCheckForIdle(boolean check)
-    {
-        if (check)
-        {
-            _idleTimestamp=System.currentTimeMillis();
-            _checkIdle=true;
-        }
-        else
-            _checkIdle=false;
-    }
-
-    /* ------------------------------------------------------------ */
-    public boolean isCheckForIdle()
-    {
-        return _checkIdle;
-    }
-
-    /* ------------------------------------------------------------ */
-    protected void notIdle()
-    {
-        _idleTimestamp=System.currentTimeMillis();
-    }
-
-    /* ------------------------------------------------------------ */
-    public void checkIdleTimestamp(long now)
-    {
-        if (isCheckForIdle() && _maxIdleTime>0)
-        {
-            final long idleForMs=now-_idleTimestamp;
-
-            if (idleForMs>_maxIdleTime)
-            {
-                // Don't idle out again until onIdleExpired task completes.
-                setCheckForIdle(false);
-                _manager.dispatch(new Runnable()
-                {
-                    public void run()
-                    {
-                        try
-                        {
-                            onIdleExpired(idleForMs);
-                        }
-                        finally
-                        {
-                            setCheckForIdle(true);
-                        }
-                    }
-                });
-            }
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    public void onIdleExpired(long idleForMs)
-    {
-        try
-        {
-            synchronized (this)
-            {
-                _onIdle=true;
-            }
-
-            _connection.onIdleExpired(idleForMs);
-        }
-        finally
-        {
-            synchronized (this)
-            {
-                _onIdle=false;
-                if (_state==STATE_NEEDS_DISPATCH)
-                    dispatch();
-            }
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public int fill(Buffer buffer) throws IOException
-    {
-        int fill=super.fill(buffer);
-        if (fill>0)
-            notIdle();
-        return fill;
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public int flush(Buffer header, Buffer buffer, Buffer trailer) throws IOException
-    {
-        int l = super.flush(header, buffer, trailer);
-
-        // If there was something to write and it wasn't written, then we are not writable.
-        if (l==0 && ( header!=null && header.hasContent() || buffer!=null && buffer.hasContent() || trailer!=null && trailer.hasContent()))
-        {
-            synchronized (this)
-            {   
-                _writable=false;
-                if (_state<STATE_DISPATCHED)
-                    updateKey();
-            }
-        }
-        else if (l>0)
-        {
-            _writable=true;
-            notIdle();
-        }
-        return l;
-    }
-
-    /* ------------------------------------------------------------ */
-    /*
-     */
-    @Override
-    public int flush(Buffer buffer) throws IOException
-    {
-        int l = super.flush(buffer);
-
-        // If there was something to write and it wasn't written, then we are not writable.
-        if (l==0 && buffer!=null && buffer.hasContent())
-        {
-            synchronized (this)
-            {   
-                _writable=false;
-                if (_state<STATE_DISPATCHED)
-                    updateKey();
-            }
-        }
-        else if (l>0)
-        {
-            _writable=true;
-            notIdle();
-        }
-
-        return l;
-    }
-
-    /* ------------------------------------------------------------ */
-    /*
-     * Allows thread to block waiting for further events.
-     */
-    @Override
-    public boolean blockReadable(long timeoutMs) throws IOException
-    {
-        synchronized (this)
-        {
-            if (isInputShutdown())
-                throw new EofException();
-
-            long now=_selectSet.getNow();
-            long end=now+timeoutMs;
-            boolean check=isCheckForIdle();
-            setCheckForIdle(true);
-            try
-            {
-                _readBlocked=true;
-                while (!isInputShutdown() && _readBlocked)
-                {
-                    try
-                    {
-                        updateKey();
-                        this.wait(timeoutMs>0?(end-now):10000);
-                    }
-                    catch (final InterruptedException e)
-                    {
-                        LOG.warn(e);
-                        if (_interruptable)
-                            throw new InterruptedIOException(){{this.initCause(e);}};
-                    }
-                    finally
-                    {
-                        now=_selectSet.getNow();
-                    }
-
-                    if (_readBlocked && timeoutMs>0 && now>=end)
-                        return false;
-                }
-            }
-            finally
-            {
-                _readBlocked=false;
-                setCheckForIdle(check);
-            }
-        }
-        return true;
-    }
-
-    /* ------------------------------------------------------------ */
-    /*
-     * Allows thread to block waiting for further events.
-     */
-    @Override
-    public boolean blockWritable(long timeoutMs) throws IOException
-    {
-        synchronized (this)
-        {
-            if (isOutputShutdown())
-                throw new EofException();
-
-            long now=_selectSet.getNow();
-            long end=now+timeoutMs;
-            boolean check=isCheckForIdle();
-            setCheckForIdle(true);
-            try
-            {
-                _writeBlocked=true;
-                while (_writeBlocked && !isOutputShutdown())
-                {
-                    try
-                    {
-                        updateKey();
-                        this.wait(timeoutMs>0?(end-now):10000);
-                    }
-                    catch (final InterruptedException e)
-                    {
-                        LOG.warn(e);
-                        if (_interruptable)
-                            throw new InterruptedIOException(){{this.initCause(e);}};
-                    }
-                    finally
-                    {
-                        now=_selectSet.getNow();
-                    }
-                    if (_writeBlocked && timeoutMs>0 && now>=end)
-                        return false;
-                }
-            }
-            finally
-            {
-                _writeBlocked=false;
-                setCheckForIdle(check);
-            }
-        }
-        return true;
-    }
-
-    /* ------------------------------------------------------------ */
-    /** Set the interruptable mode of the endpoint.
-     * If set to false (default), then interrupts are assumed to be spurious 
-     * and blocking operations continue unless the endpoint has been closed.
-     * If true, then interrupts of blocking operations result in InterruptedIOExceptions
-     * being thrown.
-     * @param interupable
-     */
-    public void setInterruptable(boolean interupable)
-    {
-        synchronized (this)
-        {
-            _interruptable=interupable;
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    public boolean isInterruptable()
-    {
-        return _interruptable;
-    }
-    
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.io.AsyncEndPoint#scheduleWrite()
-     */
-    public void scheduleWrite()
-    {
-        if (_writable)
-            LOG.debug("Required scheduleWrite {}",this);
-
-        _writable=false;
-        updateKey();
-    }
-
-    /* ------------------------------------------------------------ */
-    public boolean isWritable()
-    {
-        return _writable;
-    }
-
-    /* ------------------------------------------------------------ */
-    public boolean hasProgressed()
-    {
-        return false;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Updates selection key. Adds operations types to the selection key as needed. No operations
-     * are removed as this is only done during dispatch. This method records the new key and
-     * schedules a call to doUpdateKey to do the keyChange
-     */
-    private void updateKey()
-    {
-        final boolean changed;
-        synchronized (this)
-        {
-            int current_ops=-1;
-            if (getChannel().isOpen())
-            {
-                boolean read_interest = _readBlocked || (_state<STATE_DISPATCHED && !_connection.isSuspended());
-                boolean write_interest= _writeBlocked || (_state<STATE_DISPATCHED && !_writable);
-
-                _interestOps =
-                    ((!_socket.isInputShutdown() && read_interest ) ? SelectionKey.OP_READ  : 0)
-                |   ((!_socket.isOutputShutdown()&& write_interest) ? SelectionKey.OP_WRITE : 0);
-                try
-                {
-                    current_ops = ((_key!=null && _key.isValid())?_key.interestOps():-1);
-                }
-                catch(Exception e)
-                {
-                    _key=null;
-                    LOG.ignore(e);
-                }
-            }
-            changed=_interestOps!=current_ops;
-        }
-
-        if(changed)
-        {
-            _selectSet.addChange(this);
-            _selectSet.wakeup();
-        }
-    }
-
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Synchronize the interestOps with the actual key. Call is scheduled by a call to updateKey
-     */
-    void doUpdateKey()
-    {
-        synchronized (this)
-        {
-            if (getChannel().isOpen())
-            {
-                if (_interestOps>0)
-                {
-                    if (_key==null || !_key.isValid())
-                    {
-                        SelectableChannel sc = (SelectableChannel)getChannel();
-                        if (sc.isRegistered())
-                        {
-                            updateKey();
-                        }
-                        else
-                        {
-                            try
-                            {
-                                _key=((SelectableChannel)getChannel()).register(_selectSet.getSelector(),_interestOps,this);
-                            }
-                            catch (Exception e)
-                            {
-                                LOG.ignore(e);
-                                if (_key!=null && _key.isValid())
-                                {
-                                    _key.cancel();
-                                }
-
-                                if (_open)
-                                {
-                                    _selectSet.destroyEndPoint(this);
-                                }
-                                _open=false;
-                                _key = null;
-                            }
-                        }
-                    }
-                    else
-                    {
-                        _key.interestOps(_interestOps);
-                    }
-                }
-                else
-                {
-                    if (_key!=null && _key.isValid())
-                        _key.interestOps(0);
-                    else
-                        _key=null;
-                }
-            }
-            else
-            {
-                if (_key!=null && _key.isValid())
-                    _key.cancel();
-
-                if (_open)
-                {
-                    _open=false;
-                    _selectSet.destroyEndPoint(this);
-                }
-                _key = null;
-            }
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    /*
-     */
-    protected void handle()
-    {
-        boolean dispatched=true;
-        try
-        {
-            while(dispatched)
-            {
-                try
-                {
-                    while(true)
-                    {
-                        final AsyncConnection next = (AsyncConnection)_connection.handle();
-                        if (next!=_connection)
-                        {
-                            LOG.debug("{} replaced {}",next,_connection);
-                            Connection old=_connection;
-                            _connection=next;
-                            _manager.endPointUpgraded(this,old);
-                            continue;
-                        }
-                        break;
-                    }
-                }
-                catch (ClosedChannelException e)
-                {
-                    LOG.ignore(e);
-                }
-                catch (EofException e)
-                {
-                    LOG.debug("EOF", e);
-                    try{close();}
-                    catch(IOException e2){LOG.ignore(e2);}
-                }
-                catch (IOException e)
-                {
-                    LOG.warn(e.toString());
-                    try{close();}
-                    catch(IOException e2){LOG.ignore(e2);}
-                }
-                catch (Throwable e)
-                {
-                    LOG.warn("handle failed", e);
-                    try{close();}
-                    catch(IOException e2){LOG.ignore(e2);}
-                }
-                finally
-                {
-                    if (!_ishut && isInputShutdown() && isOpen())
-                    {
-                        _ishut=true;
-                        try
-                        {
-                            _connection.onInputShutdown();
-                        }
-                        catch(Throwable x)
-                        {
-                            LOG.warn("onInputShutdown failed", x);
-                            try{close();}
-                            catch(IOException e2){LOG.ignore(e2);}
-                        }
-                        finally
-                        {
-                            updateKey();
-                        }
-                    }
-                    dispatched=!undispatch();
-                }
-            }
-        }
-        finally
-        {
-            if (dispatched)
-            {
-                dispatched=!undispatch();
-                while (dispatched)
-                {
-                    LOG.warn("SCEP.run() finally DISPATCHED");
-                    dispatched=!undispatch();
-                }
-            }
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    /*
-     * @see org.eclipse.io.nio.ChannelEndPoint#close()
-     */
-    @Override
-    public void close() throws IOException
-    {
-        // On unix systems there is a JVM issue that if you cancel before closing, it can 
-        // cause the selector to block waiting for a channel to close and that channel can 
-        // block waiting for the remote end.  But on windows, if you don't cancel before a 
-        // close, then the selector can block anyway!
-        // https://bugs.eclipse.org/bugs/show_bug.cgi?id=357318
-        if (WORK_AROUND_JVM_BUG_6346658)
-        {
-            try
-            {
-                SelectionKey key = _key;
-                if (key!=null)
-                    key.cancel();
-            }
-            catch (Throwable e)
-            {
-                LOG.ignore(e);
-            }
-        }
-
-        try
-        {
-            super.close();
-        }
-        catch (IOException e)
-        {
-            LOG.ignore(e);
-        }
-        finally
-        {
-            updateKey();
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public String toString()
-    {
-        // Do NOT use synchronized (this)
-        // because it's very easy to deadlock when debugging is enabled.
-        // We do a best effort to print the right toString() and that's it.
-        SelectionKey key = _key;
-        String keyString = "";
-        if (key != null)
-        {
-            if (key.isValid())
-            {
-                if (key.isReadable())
-                    keyString += "r";
-                if (key.isWritable())
-                    keyString += "w";
-            }
-            else
-            {
-                keyString += "!";
-            }
-        }
-        else
-        {
-            keyString += "-";
-        }
-        return String.format("SCEP@%x{l(%s)<->r(%s),s=%d,open=%b,ishut=%b,oshut=%b,rb=%b,wb=%b,w=%b,i=%d%s}-{%s}",
-                hashCode(),
-                _socket.getRemoteSocketAddress(),
-                _socket.getLocalSocketAddress(),
-                _state,
-                isOpen(),
-                isInputShutdown(),
-                isOutputShutdown(),
-                _readBlocked,
-                _writeBlocked,
-                _writable,
-                _interestOps,
-                keyString,
-                _connection);
-    }
-
-    /* ------------------------------------------------------------ */
-    public SelectSet getSelectSet()
-    {
-        return _selectSet;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Don't set the SoTimeout
-     * @see org.eclipse.jetty.io.nio.ChannelEndPoint#setMaxIdleTime(int)
-     */
-    @Override
-    public void setMaxIdleTime(int timeMs) throws IOException
-    {
-        _maxIdleTime=timeMs;
-    }
-
-}
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/nio/SelectorManager.java b/jetty-io/src/main/java/org/eclipse/jetty/io/nio/SelectorManager.java
deleted file mode 100644
index 1c38c52..0000000
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/nio/SelectorManager.java
+++ /dev/null
@@ -1,1034 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.io.nio;
-
-import java.io.IOException;
-import java.nio.channels.CancelledKeyException;
-import java.nio.channels.Channel;
-import java.nio.channels.ClosedSelectorException;
-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.util.ArrayList;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-import org.eclipse.jetty.io.AsyncEndPoint;
-import org.eclipse.jetty.io.ConnectedEndPoint;
-import org.eclipse.jetty.io.Connection;
-import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.util.TypeUtil;
-import org.eclipse.jetty.util.component.AbstractLifeCycle;
-import org.eclipse.jetty.util.component.AggregateLifeCycle;
-import org.eclipse.jetty.util.component.Dumpable;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.util.thread.Timeout;
-import org.eclipse.jetty.util.thread.Timeout.Task;
-
-
-/* ------------------------------------------------------------ */
-/**
- * The Selector Manager manages and number of SelectSets to allow
- * NIO scheduling to scale to large numbers of connections.
- * <p>
- */
-public abstract class SelectorManager extends AbstractLifeCycle implements Dumpable
-{
-    public static final Logger LOG=Log.getLogger("org.eclipse.jetty.io.nio");
-
-    private static final int __MONITOR_PERIOD=Integer.getInteger("org.eclipse.jetty.io.nio.MONITOR_PERIOD",1000).intValue();
-    private static final int __MAX_SELECTS=Integer.getInteger("org.eclipse.jetty.io.nio.MAX_SELECTS",100000).intValue();
-    private static final int __BUSY_PAUSE=Integer.getInteger("org.eclipse.jetty.io.nio.BUSY_PAUSE",50).intValue();
-    private static final int __IDLE_TICK=Integer.getInteger("org.eclipse.jetty.io.nio.IDLE_TICK",400).intValue();
-
-    private int _maxIdleTime;
-    private int _lowResourcesMaxIdleTime;
-    private long _lowResourcesConnections;
-    private SelectSet[] _selectSet;
-    private int _selectSets=1;
-    private volatile int _set=0;
-    private boolean _deferringInterestedOps0=true;
-    private int _selectorPriorityDelta=0;
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param maxIdleTime The maximum period in milli seconds that a connection may be idle before it is closed.
-     * @see #setLowResourcesMaxIdleTime(long)
-     */
-    public void setMaxIdleTime(long maxIdleTime)
-    {
-        _maxIdleTime=(int)maxIdleTime;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param selectSets number of select sets to create
-     */
-    public void setSelectSets(int selectSets)
-    {
-        long lrc = _lowResourcesConnections * _selectSets;
-        _selectSets=selectSets;
-        _lowResourcesConnections=lrc/_selectSets;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return the max idle time
-     */
-    public long getMaxIdleTime()
-    {
-        return _maxIdleTime;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return the number of select sets in use
-     */
-    public int getSelectSets()
-    {
-        return _selectSets;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param i
-     * @return The select set
-     */
-    public SelectSet getSelectSet(int i)
-    {
-        return _selectSet[i];
-    }
-
-    /* ------------------------------------------------------------ */
-    /** Register a channel
-     * @param channel
-     * @param att Attached Object
-     */
-    public void register(SocketChannel channel, Object att)
-    {
-        // The ++ increment here is not atomic, but it does not matter.
-        // so long as the value changes sometimes, then connections will
-        // be distributed over the available sets.
-
-        int s=_set++;
-        if (s<0)
-            s=-s;
-        s=s%_selectSets;
-        SelectSet[] sets=_selectSet;
-        if (sets!=null)
-        {
-            SelectSet set=sets[s];
-            set.addChange(channel,att);
-            set.wakeup();
-        }
-    }
-
-
-    /* ------------------------------------------------------------ */
-    /** Register a channel
-     * @param channel
-     */
-    public void register(SocketChannel channel)
-    {
-        // The ++ increment here is not atomic, but it does not matter.
-        // so long as the value changes sometimes, then connections will
-        // be distributed over the available sets.
-
-        int s=_set++;
-        if (s<0)
-            s=-s;
-        s=s%_selectSets;
-        SelectSet[] sets=_selectSet;
-        if (sets!=null)
-        {
-            SelectSet set=sets[s];
-            set.addChange(channel);
-            set.wakeup();
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    /** Register a {@link ServerSocketChannel}
-     * @param acceptChannel
-     */
-    public void register(ServerSocketChannel acceptChannel)
-    {
-        int s=_set++;
-        if (s<0)
-            s=-s;
-        s=s%_selectSets;
-        SelectSet set=_selectSet[s];
-        set.addChange(acceptChannel);
-        set.wakeup();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return delta The value to add to the selector thread priority.
-     */
-    public int getSelectorPriorityDelta()
-    {
-        return _selectorPriorityDelta;
-    }
-
-    /* ------------------------------------------------------------ */
-    /** Set the selector thread priorty delta.
-     * @param delta The value to add to the selector thread priority.
-     */
-    public void setSelectorPriorityDelta(int delta)
-    {
-        _selectorPriorityDelta=delta;
-    }
-
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return the lowResourcesConnections
-     */
-    public long getLowResourcesConnections()
-    {
-        return _lowResourcesConnections*_selectSets;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Set the number of connections, which if exceeded places this manager in low resources state.
-     * This is not an exact measure as the connection count is averaged over the select sets.
-     * @param lowResourcesConnections the number of connections
-     * @see #setLowResourcesMaxIdleTime(long)
-     */
-    public void setLowResourcesConnections(long lowResourcesConnections)
-    {
-        _lowResourcesConnections=(lowResourcesConnections+_selectSets-1)/_selectSets;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return the lowResourcesMaxIdleTime
-     */
-    public long getLowResourcesMaxIdleTime()
-    {
-        return _lowResourcesMaxIdleTime;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param lowResourcesMaxIdleTime the period in ms that a connection is allowed to be idle when this SelectSet has more connections than {@link #getLowResourcesConnections()}
-     * @see #setMaxIdleTime(long)
-     */
-    public void setLowResourcesMaxIdleTime(long lowResourcesMaxIdleTime)
-    {
-        _lowResourcesMaxIdleTime=(int)lowResourcesMaxIdleTime;
-    }
-
-
-    /* ------------------------------------------------------------------------------- */
-    public abstract boolean dispatch(Runnable task);
-
-    /* ------------------------------------------------------------ */
-    /* (non-Javadoc)
-     * @see org.eclipse.component.AbstractLifeCycle#doStart()
-     */
-    @Override
-    protected void doStart() throws Exception
-    {
-        _selectSet = new SelectSet[_selectSets];
-        for (int i=0;i<_selectSet.length;i++)
-            _selectSet[i]= new SelectSet(i);
-
-        super.doStart();
-
-        // start a thread to Select
-        for (int i=0;i<getSelectSets();i++)
-        {
-            final int id=i;
-            boolean selecting=dispatch(new Runnable()
-            {
-                public void run()
-                {
-                    String name=Thread.currentThread().getName();
-                    int priority=Thread.currentThread().getPriority();
-                    try
-                    {
-                        SelectSet[] sets=_selectSet;
-                        if (sets==null)
-                            return;
-                        SelectSet set=sets[id];
-
-                        Thread.currentThread().setName(name+" Selector"+id);
-                        if (getSelectorPriorityDelta()!=0)
-                            Thread.currentThread().setPriority(Thread.currentThread().getPriority()+getSelectorPriorityDelta());
-                        LOG.debug("Starting {} on {}",Thread.currentThread(),this);
-                        while (isRunning())
-                        {
-                            try
-                            {
-                                set.doSelect();
-                            }
-                            catch(IOException e)
-                            {
-                                LOG.ignore(e);
-                            }
-                            catch(Exception e)
-                            {
-                                LOG.warn(e);
-                            }
-                        }
-                    }
-                    finally
-                    {
-                        LOG.debug("Stopped {} on {}",Thread.currentThread(),this);
-                        Thread.currentThread().setName(name);
-                        if (getSelectorPriorityDelta()!=0)
-                            Thread.currentThread().setPriority(priority);
-                    }
-                }
-
-            });
-
-            if (!selecting)
-                throw new IllegalStateException("!Selecting");
-        }
-    }
-
-
-    /* ------------------------------------------------------------------------------- */
-    @Override
-    protected void doStop() throws Exception
-    {
-        SelectSet[] sets= _selectSet;
-        _selectSet=null;
-        if (sets!=null)
-        {
-            for (SelectSet set : sets)
-            {
-                if (set!=null)
-                    set.stop();
-            }
-        }
-        super.doStop();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param endpoint
-     */
-    protected abstract void endPointClosed(SelectChannelEndPoint endpoint);
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param endpoint
-     */
-    protected abstract void endPointOpened(SelectChannelEndPoint endpoint);
-
-    /* ------------------------------------------------------------ */
-    protected abstract void endPointUpgraded(ConnectedEndPoint endpoint,Connection oldConnection);
-
-    /* ------------------------------------------------------------------------------- */
-    public abstract AsyncConnection newConnection(SocketChannel channel, AsyncEndPoint endpoint, Object attachment);
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Create a new end point
-     * @param channel
-     * @param selectSet
-     * @param sKey the selection key
-     * @return the new endpoint {@link SelectChannelEndPoint}
-     * @throws IOException
-     */
-    protected abstract SelectChannelEndPoint newEndPoint(SocketChannel channel, SelectorManager.SelectSet selectSet, SelectionKey sKey) throws IOException;
-
-    /* ------------------------------------------------------------------------------- */
-    protected void connectionFailed(SocketChannel channel,Throwable ex,Object attachment)
-    {
-        LOG.warn(ex+","+channel+","+attachment);
-        LOG.debug(ex);
-    }
-
-    /* ------------------------------------------------------------ */
-    public String dump()
-    {
-        return AggregateLifeCycle.dump(this);
-    }
-
-    /* ------------------------------------------------------------ */
-    public void dump(Appendable out, String indent) throws IOException
-    {
-        AggregateLifeCycle.dumpObject(out,this);
-        AggregateLifeCycle.dump(out,indent,TypeUtil.asList(_selectSet));
-    }
-
-
-    /* ------------------------------------------------------------------------------- */
-    /* ------------------------------------------------------------------------------- */
-    /* ------------------------------------------------------------------------------- */
-    public class SelectSet implements Dumpable
-    {
-        private final int _setID;
-        private final Timeout _timeout;
-
-        private final ConcurrentLinkedQueue<Object> _changes = new ConcurrentLinkedQueue<Object>();
-
-        private volatile Selector _selector;
-
-        private volatile Thread _selecting;
-        private int _busySelects;
-        private long _monitorNext;
-        private boolean _pausing;
-        private boolean _paused;
-        private volatile long _idleTick;
-        private ConcurrentMap<SelectChannelEndPoint,Object> _endPoints = new ConcurrentHashMap<SelectChannelEndPoint, Object>();
-
-        /* ------------------------------------------------------------ */
-        SelectSet(int acceptorID) throws Exception
-        {
-            _setID=acceptorID;
-
-            _idleTick = System.currentTimeMillis();
-            _timeout = new Timeout(this);
-            _timeout.setDuration(0L);
-
-            // create a selector;
-            _selector = Selector.open();
-            _monitorNext=System.currentTimeMillis()+__MONITOR_PERIOD;
-        }
-
-        /* ------------------------------------------------------------ */
-        public void addChange(Object change)
-        {
-            _changes.add(change);
-        }
-
-        /* ------------------------------------------------------------ */
-        public void addChange(SelectableChannel channel, Object att)
-        {
-            if (att==null)
-                addChange(channel);
-            else if (att instanceof EndPoint)
-                addChange(att);
-            else
-                addChange(new ChannelAndAttachment(channel,att));
-        }
-
-        /* ------------------------------------------------------------ */
-        /**
-         * Select and dispatch tasks found from changes and the selector.
-         *
-         * @throws IOException
-         */
-        public void doSelect() throws IOException
-        {
-            try
-            {
-                _selecting=Thread.currentThread();
-                final Selector selector=_selector;
-                // Stopped concurrently ?
-                if (selector == null)
-                    return;
-
-                // Make any key changes required
-                Object change;
-                int changes=_changes.size();
-                while (changes-->0 && (change=_changes.poll())!=null)
-                {
-                    Channel ch=null;
-                    SelectionKey key=null;
-
-                    try
-                    {
-                        if (change instanceof EndPoint)
-                        {
-                            // Update the operations for a key.
-                            SelectChannelEndPoint endpoint = (SelectChannelEndPoint)change;
-                            ch=endpoint.getChannel();
-                            endpoint.doUpdateKey();
-                        }
-                        else if (change instanceof ChannelAndAttachment)
-                        {
-                            // finish accepting/connecting this connection
-                            final ChannelAndAttachment asc = (ChannelAndAttachment)change;
-                            final SelectableChannel channel=asc._channel;
-                            ch=channel;
-                            final Object att = asc._attachment;
-
-                            if ((channel instanceof SocketChannel) && ((SocketChannel)channel).isConnected())
-                            {
-                                key = channel.register(selector,SelectionKey.OP_READ,att);
-                                SelectChannelEndPoint endpoint = createEndPoint((SocketChannel)channel,key);
-                                key.attach(endpoint);
-                                endpoint.schedule();
-                            }
-                            else if (channel.isOpen())
-                            {
-                                key = channel.register(selector,SelectionKey.OP_CONNECT,att);
-                            }
-                        }
-                        else if (change instanceof SocketChannel)
-                        {
-                            // Newly registered channel
-                            final SocketChannel channel=(SocketChannel)change;
-                            ch=channel;
-                            key = channel.register(selector,SelectionKey.OP_READ,null);
-                            SelectChannelEndPoint endpoint = createEndPoint(channel,key);
-                            key.attach(endpoint);
-                            endpoint.schedule();
-                        }
-                        else if (change instanceof ChangeTask)
-                        {
-                            ((Runnable)change).run();
-                        }
-                        else if (change instanceof Runnable)
-                        {
-                            dispatch((Runnable)change);
-                        }
-                        else
-                            throw new IllegalArgumentException(change.toString());
-                    }
-                    catch (CancelledKeyException e)
-                    {
-                        LOG.ignore(e);
-                    }
-                    catch (Throwable e)
-                    {
-                        if (isRunning())
-                            LOG.warn(e);
-                        else
-                            LOG.debug(e);
-
-                        try
-                        {
-                            if (ch!=null)
-                                ch.close();
-                        }
-                        catch(IOException e2)
-                        {
-                            LOG.debug(e2);
-                        }
-                    }
-                }
-
-
-                // Do and instant select to see if any connections can be handled.
-                int selected=selector.selectNow();
-
-                long now=System.currentTimeMillis();
-
-                // if no immediate things to do
-                if (selected==0 && selector.selectedKeys().isEmpty())
-                {
-                    // If we are in pausing mode
-                    if (_pausing)
-                    {
-                        try
-                        {
-                            Thread.sleep(__BUSY_PAUSE); // pause to reduce impact of  busy loop
-                        }
-                        catch(InterruptedException e)
-                        {
-                            LOG.ignore(e);
-                        }
-                        now=System.currentTimeMillis();
-                    }
-
-                    // workout how long to wait in select
-                    _timeout.setNow(now);
-                    long to_next_timeout=_timeout.getTimeToNext();
-
-                    long wait = _changes.size()==0?__IDLE_TICK:0L;
-                    if (wait > 0 && to_next_timeout >= 0 && wait > to_next_timeout)
-                        wait = to_next_timeout;
-
-                    // If we should wait with a select
-                    if (wait>0)
-                    {
-                        long before=now;
-                        selector.select(wait);
-                        now = System.currentTimeMillis();
-                        _timeout.setNow(now);
-
-                        // If we are monitoring for busy selector
-                        // and this select did not wait more than 1ms
-                        if (__MONITOR_PERIOD>0 && now-before <=1)
-                        {
-                            // count this as a busy select and if there have been too many this monitor cycle
-                            if (++_busySelects>__MAX_SELECTS)
-                            {
-                                // Start injecting pauses
-                                _pausing=true;
-
-                                // if this is the first pause
-                                if (!_paused)
-                                {
-                                    // Log and dump some status
-                                    _paused=true;
-                                    LOG.warn("Selector {} is too busy, pausing!",this);
-                                }
-                            }
-                        }
-                    }
-                }
-
-                // have we been destroyed while sleeping
-                if (_selector==null || !selector.isOpen())
-                    return;
-
-                // Look for things to do
-                for (SelectionKey key: selector.selectedKeys())
-                {
-                    SocketChannel channel=null;
-
-                    try
-                    {
-                        if (!key.isValid())
-                        {
-                            key.cancel();
-                            SelectChannelEndPoint endpoint = (SelectChannelEndPoint)key.attachment();
-                            if (endpoint != null)
-                                endpoint.doUpdateKey();
-                            continue;
-                        }
-
-                        Object att = key.attachment();
-                        if (att instanceof SelectChannelEndPoint)
-                        {
-                            if (key.isReadable()||key.isWritable())
-                                ((SelectChannelEndPoint)att).schedule();
-                        }
-                        else if (key.isConnectable())
-                        {
-                            // Complete a connection of a registered channel
-                            channel = (SocketChannel)key.channel();
-                            boolean connected=false;
-                            try
-                            {
-                                connected=channel.finishConnect();
-                            }
-                            catch(Exception e)
-                            {
-                                connectionFailed(channel,e,att);
-                            }
-                            finally
-                            {
-                                if (connected)
-                                {
-                                    key.interestOps(SelectionKey.OP_READ);
-                                    SelectChannelEndPoint endpoint = createEndPoint(channel,key);
-                                    key.attach(endpoint);
-                                    endpoint.schedule();
-                                }
-                                else
-                                {
-                                    key.cancel();
-                                    channel.close();
-                                }
-                            }
-                        }
-                        else
-                        {
-                            // Wrap readable registered channel in an endpoint
-                            channel = (SocketChannel)key.channel();
-                            SelectChannelEndPoint endpoint = createEndPoint(channel,key);
-                            key.attach(endpoint);
-                            if (key.isReadable())
-                                endpoint.schedule();
-                        }
-                        key = null;
-                    }
-                    catch (CancelledKeyException e)
-                    {
-                        LOG.ignore(e);
-                    }
-                    catch (Exception e)
-                    {
-                        if (isRunning())
-                            LOG.warn(e);
-                        else
-                            LOG.ignore(e);
-
-                        try
-                        {
-                            if (channel!=null)
-                                channel.close();
-                        }
-                        catch(IOException e2)
-                        {
-                            LOG.debug(e2);
-                        }
-
-                        if (key != null && !(key.channel() instanceof ServerSocketChannel) && key.isValid())
-                            key.cancel();
-                    }
-                }
-
-                // Everything always handled
-                selector.selectedKeys().clear();
-
-                now=System.currentTimeMillis();
-                _timeout.setNow(now);
-                Task task = _timeout.expired();
-                while (task!=null)
-                {
-                    if (task instanceof Runnable)
-                        dispatch((Runnable)task);
-                    task = _timeout.expired();
-                }
-
-                // Idle tick
-                if (now-_idleTick>__IDLE_TICK)
-                {
-                    _idleTick=now;
-
-                    final long idle_now=((_lowResourcesConnections>0 && selector.keys().size()>_lowResourcesConnections))
-                        ?(now+_maxIdleTime-_lowResourcesMaxIdleTime)
-                        :now;
-
-                    dispatch(new Runnable()
-                    {
-                        public void run()
-                        {
-                            for (SelectChannelEndPoint endp:_endPoints.keySet())
-                            {
-                                endp.checkIdleTimestamp(idle_now);
-                            }
-                        }
-                        public String toString() {return "Idle-"+super.toString();}
-                    });
-
-                }
-
-                // Reset busy select monitor counts
-                if (__MONITOR_PERIOD>0 && now>_monitorNext)
-                {
-                    _busySelects=0;
-                    _pausing=false;
-                    _monitorNext=now+__MONITOR_PERIOD;
-
-                }
-            }
-            catch (ClosedSelectorException e)
-            {
-                if (isRunning())
-                    LOG.warn(e);
-                else
-                    LOG.ignore(e);
-            }
-            catch (CancelledKeyException e)
-            {
-                LOG.ignore(e);
-            }
-            finally
-            {
-                _selecting=null;
-            }
-        }
-
-
-        /* ------------------------------------------------------------ */
-        private void renewSelector()
-        {
-            try
-            {
-                synchronized (this)
-                {
-                    Selector selector=_selector;
-                    if (selector==null)
-                        return;
-                    final Selector new_selector = Selector.open();
-                    for (SelectionKey k: selector.keys())
-                    {
-                        if (!k.isValid() || k.interestOps()==0)
-                            continue;
-
-                        final SelectableChannel channel = k.channel();
-                        final Object attachment = k.attachment();
-
-                        if (attachment==null)
-                            addChange(channel);
-                        else
-                            addChange(channel,attachment);
-                    }
-                    _selector.close();
-                    _selector=new_selector;
-                }
-            }
-            catch(IOException e)
-            {
-                throw new RuntimeException("recreating selector",e);
-            }
-        }
-
-        /* ------------------------------------------------------------ */
-        public SelectorManager getManager()
-        {
-            return SelectorManager.this;
-        }
-
-        /* ------------------------------------------------------------ */
-        public long getNow()
-        {
-            return _timeout.getNow();
-        }
-
-        /* ------------------------------------------------------------ */
-        /**
-         * @param task The task to timeout. If it implements Runnable, then
-         * expired will be called from a dispatched thread.
-         *
-         * @param timeoutMs
-         */
-        public void scheduleTimeout(Timeout.Task task, long timeoutMs)
-        {
-            if (!(task instanceof Runnable))
-                throw new IllegalArgumentException("!Runnable");
-            _timeout.schedule(task, timeoutMs);
-        }
-
-        /* ------------------------------------------------------------ */
-        public void cancelTimeout(Timeout.Task task)
-        {
-            task.cancel();
-        }
-
-        /* ------------------------------------------------------------ */
-        public void wakeup()
-        {
-            try
-            {
-                Selector selector = _selector;
-                if (selector!=null)
-                    selector.wakeup();
-            }
-            catch(Exception e)
-            {
-                addChange(new ChangeTask()
-                {
-                    public void run()
-                    {
-                        renewSelector();
-                    }
-                });
-
-                renewSelector();
-            }
-        }
-
-        /* ------------------------------------------------------------ */
-        private SelectChannelEndPoint createEndPoint(SocketChannel channel, SelectionKey sKey) throws IOException
-        {
-            SelectChannelEndPoint endp = newEndPoint(channel,this,sKey);
-            LOG.debug("created {}",endp);
-            endPointOpened(endp);
-            _endPoints.put(endp,this);
-            return endp;
-        }
-
-        /* ------------------------------------------------------------ */
-        public void destroyEndPoint(SelectChannelEndPoint endp)
-        {
-            LOG.debug("destroyEndPoint {}",endp);
-            _endPoints.remove(endp);
-            endPointClosed(endp);
-        }
-
-        /* ------------------------------------------------------------ */
-        Selector getSelector()
-        {
-            return _selector;
-        }
-
-        /* ------------------------------------------------------------ */
-        void stop() throws Exception
-        {
-            // Spin for a while waiting for selector to complete
-            // to avoid unneccessary closed channel exceptions
-            try
-            {
-                for (int i=0;i<100 && _selecting!=null;i++)
-                {
-                    wakeup();
-                    Thread.sleep(10);
-                }
-            }
-            catch(Exception e)
-            {
-                LOG.ignore(e);
-            }
-
-            // close endpoints and selector
-            synchronized (this)
-            {
-                Selector selector=_selector;
-                for (SelectionKey key:selector.keys())
-                {
-                    if (key==null)
-                        continue;
-                    Object att=key.attachment();
-                    if (att instanceof EndPoint)
-                    {
-                        EndPoint endpoint = (EndPoint)att;
-                        try
-                        {
-                            endpoint.close();
-                        }
-                        catch(IOException e)
-                        {
-                            LOG.ignore(e);
-                        }
-                    }
-                }
-
-
-                _timeout.cancelAll();
-                try
-                {
-                    selector=_selector;
-                    if (selector != null)
-                        selector.close();
-                }
-                catch (IOException e)
-                {
-                    LOG.ignore(e);
-                }
-                _selector=null;
-            }
-        }
-
-        /* ------------------------------------------------------------ */
-        public String dump()
-        {
-            return AggregateLifeCycle.dump(this);
-        }
-
-        /* ------------------------------------------------------------ */
-        public void dump(Appendable out, String indent) throws IOException
-        {
-            out.append(String.valueOf(this)).append(" id=").append(String.valueOf(_setID)).append("\n");
-
-            Thread selecting = _selecting;
-
-            Object where = "not selecting";
-            StackTraceElement[] trace =selecting==null?null:selecting.getStackTrace();
-            if (trace!=null)
-            {
-                for (StackTraceElement t:trace)
-                    if (t.getClassName().startsWith("org.eclipse.jetty."))
-                    {
-                        where=t;
-                        break;
-                    }
-            }
-
-            Selector selector=_selector;
-            if (selector!=null)
-            {
-                final ArrayList<Object> dump = new ArrayList<Object>(selector.keys().size()*2);
-                dump.add(where);
-
-                final CountDownLatch latch = new CountDownLatch(1);
-
-                addChange(new ChangeTask()
-                {
-                    public void run()
-                    {
-                        dumpKeyState(dump);
-                        latch.countDown();
-                    }
-                });
-
-                try
-                {
-                    latch.await(5,TimeUnit.SECONDS);
-                }
-                catch(InterruptedException e)
-                {
-                    LOG.ignore(e);
-                }
-
-                AggregateLifeCycle.dump(out,indent,dump);
-            }
-        }
-
-        /* ------------------------------------------------------------ */
-        public void dumpKeyState(List<Object> dumpto)
-        {
-            Selector selector=_selector;
-            Set<SelectionKey> keys = selector.keys();
-            dumpto.add(selector + " keys=" + keys.size());
-            for (SelectionKey key: keys)
-            {
-                if (key.isValid())
-                    dumpto.add(key.attachment()+" iOps="+key.interestOps()+" rOps="+key.readyOps());
-                else
-                    dumpto.add(key.attachment()+" iOps=-1 rOps=-1");
-            }
-        }
-
-        /* ------------------------------------------------------------ */
-        public String toString()
-        {
-            Selector selector=_selector;
-            return String.format("%s keys=%d selected=%d",
-                    super.toString(),
-                    selector != null && selector.isOpen() ? selector.keys().size() : -1,
-                    selector != null && selector.isOpen() ? selector.selectedKeys().size() : -1);
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    private static class ChannelAndAttachment
-    {
-        final SelectableChannel _channel;
-        final Object _attachment;
-
-        public ChannelAndAttachment(SelectableChannel channel, Object attachment)
-        {
-            super();
-            _channel = channel;
-            _attachment = attachment;
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    public boolean isDeferringInterestedOps0()
-    {
-        return _deferringInterestedOps0;
-    }
-
-    /* ------------------------------------------------------------ */
-    public void setDeferringInterestedOps0(boolean deferringInterestedOps0)
-    {
-        _deferringInterestedOps0 = deferringInterestedOps0;
-    }
-
-
-    /* ------------------------------------------------------------ */
-    /* ------------------------------------------------------------ */
-    /* ------------------------------------------------------------ */
-    private interface ChangeTask extends Runnable
-    {}
-
-}
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/nio/SslConnection.java b/jetty-io/src/main/java/org/eclipse/jetty/io/nio/SslConnection.java
deleted file mode 100644
index 0363c35..0000000
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/nio/SslConnection.java
+++ /dev/null
@@ -1,902 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.io.nio;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.util.concurrent.atomic.AtomicBoolean;
-import javax.net.ssl.SSLEngine;
-import javax.net.ssl.SSLEngineResult;
-import javax.net.ssl.SSLEngineResult.HandshakeStatus;
-import javax.net.ssl.SSLException;
-import javax.net.ssl.SSLSession;
-
-import org.eclipse.jetty.io.AbstractConnection;
-import org.eclipse.jetty.io.AsyncEndPoint;
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.Connection;
-import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.util.thread.Timeout.Task;
-
-/* ------------------------------------------------------------ */
-/** SSL Connection.
- * An AysyncConnection that acts as an interceptor between and EndPoint and another
- * Connection, that implements TLS encryption using an {@link SSLEngine}.
- * <p>
- * The connector uses an {@link AsyncEndPoint} (like {@link SelectChannelEndPoint}) as
- * it's source/sink of encrypted data.   It then provides {@link #getSslEndPoint()} to
- * expose a source/sink of unencrypted data to another connection (eg HttpConnection).
- */
-public class SslConnection extends AbstractConnection implements AsyncConnection
-{
-    private final Logger _logger = Log.getLogger("org.eclipse.jetty.io.nio.ssl");
-
-    private static final NIOBuffer __ZERO_BUFFER=new IndirectNIOBuffer(0);
-
-    private static final ThreadLocal<SslBuffers> __buffers = new ThreadLocal<SslBuffers>();
-    private final SSLEngine _engine;
-    private final SSLSession _session;
-    private AsyncConnection _connection;
-    private final SslEndPoint _sslEndPoint;
-    private int _allocations;
-    private SslBuffers _buffers;
-    private NIOBuffer _inbound;
-    private NIOBuffer _unwrapBuf;
-    private NIOBuffer _outbound;
-    private AsyncEndPoint _aEndp;
-    private boolean _allowRenegotiate=true;
-    private boolean _handshook;
-    private boolean _ishut;
-    private boolean _oshut;
-    private final AtomicBoolean _progressed = new AtomicBoolean();
-
-    /* ------------------------------------------------------------ */
-    /* this is a half baked buffer pool
-     */
-    private static class SslBuffers
-    {
-        final NIOBuffer _in;
-        final NIOBuffer _out;
-        final NIOBuffer _unwrap;
-
-        SslBuffers(int packetSize, int appSize)
-        {
-            _in=new IndirectNIOBuffer(packetSize);
-            _out=new IndirectNIOBuffer(packetSize);
-            _unwrap=new IndirectNIOBuffer(appSize);
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    public SslConnection(SSLEngine engine,EndPoint endp)
-    {
-        this(engine,endp,System.currentTimeMillis());
-    }
-
-    /* ------------------------------------------------------------ */
-    public SslConnection(SSLEngine engine,EndPoint endp, long timeStamp)
-    {
-        super(endp,timeStamp);
-        _engine=engine;
-        _session=_engine.getSession();
-        _aEndp=(AsyncEndPoint)endp;
-        _sslEndPoint = newSslEndPoint();
-    }
-
-    /* ------------------------------------------------------------ */
-    protected SslEndPoint newSslEndPoint()
-    {
-        return new SslEndPoint();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return True if SSL re-negotiation is allowed (default false)
-     */
-    public boolean isAllowRenegotiate()
-    {
-        return _allowRenegotiate;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Set if SSL re-negotiation is allowed. CVE-2009-3555 discovered
-     * a vulnerability in SSL/TLS with re-negotiation.  If your JVM
-     * does not have CVE-2009-3555 fixed, then re-negotiation should
-     * not be allowed.  CVE-2009-3555 was fixed in Sun java 1.6 with a ban
-     * of renegotiates in u19 and with RFC5746 in u22.
-     *
-     * @param allowRenegotiate
-     *            true if re-negotiation is allowed (default false)
-     */
-    public void setAllowRenegotiate(boolean allowRenegotiate)
-    {
-        _allowRenegotiate = allowRenegotiate;
-    }
-
-    /* ------------------------------------------------------------ */
-    private void allocateBuffers()
-    {
-        synchronized (this)
-        {
-            if (_allocations++==0)
-            {
-                if (_buffers==null)
-                {
-                    _buffers=__buffers.get();
-                    if (_buffers==null)
-                        _buffers=new SslBuffers(_session.getPacketBufferSize()*2,_session.getApplicationBufferSize()*2);
-                    _inbound=_buffers._in;
-                    _outbound=_buffers._out;
-                    _unwrapBuf=_buffers._unwrap;
-                    __buffers.set(null);
-                }
-            }
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    private void releaseBuffers()
-    {
-        synchronized (this)
-        {
-            if (--_allocations==0)
-            {
-                if (_buffers!=null &&
-                    _inbound.length()==0 &&
-                    _outbound.length()==0 &&
-                    _unwrapBuf.length()==0)
-                {
-                    _inbound=null;
-                    _outbound=null;
-                    _unwrapBuf=null;
-                    __buffers.set(_buffers);
-                    _buffers=null;
-                }
-            }
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    public Connection handle() throws IOException
-    {
-        try
-        {
-            allocateBuffers();
-
-            boolean progress=true;
-
-            while (progress)
-            {
-                progress=false;
-
-                // If we are handshook let the delegate connection
-                if (_engine.getHandshakeStatus()!=HandshakeStatus.NOT_HANDSHAKING)
-                    progress=process(null,null);
-
-                // handle the delegate connection
-                AsyncConnection next = (AsyncConnection)_connection.handle();
-                if (next!=_connection && next!=null)
-                {
-                    _connection=next;
-                    progress=true;
-                }
-
-                _logger.debug("{} handle {} progress={}", _session, this, progress);
-            }
-        }
-        finally
-        {
-            releaseBuffers();
-
-            if (!_ishut && _sslEndPoint.isInputShutdown() && _sslEndPoint.isOpen())
-            {
-                _ishut=true;
-                try
-                {
-                    _connection.onInputShutdown();
-                }
-                catch(Throwable x)
-                {
-                    _logger.warn("onInputShutdown failed", x);
-                    try{_sslEndPoint.close();}
-                    catch(IOException e2){
-                        _logger.ignore(e2);}
-                }
-            }
-        }
-
-        return this;
-    }
-
-    /* ------------------------------------------------------------ */
-    public boolean isIdle()
-    {
-        return false;
-    }
-
-    /* ------------------------------------------------------------ */
-    public boolean isSuspended()
-    {
-        return false;
-    }
-
-    /* ------------------------------------------------------------ */
-    public void onClose()
-    {
-        Connection connection = _sslEndPoint.getConnection();
-        if (connection != null && connection != this)
-            connection.onClose();
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public void onIdleExpired(long idleForMs)
-    {
-        try
-        {
-            _logger.debug("onIdleExpired {}ms on {}",idleForMs,this);
-            if (_endp.isOutputShutdown())
-                _sslEndPoint.close();
-            else
-                _sslEndPoint.shutdownOutput();
-        }
-        catch (IOException e)
-        {
-            _logger.warn(e);
-            super.onIdleExpired(idleForMs);
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    public void onInputShutdown() throws IOException
-    {
-
-    }
-
-    /* ------------------------------------------------------------ */
-    private synchronized boolean process(Buffer toFill, Buffer toFlush) throws IOException
-    {
-        boolean some_progress=false;
-        try
-        {
-            // We need buffers to progress
-            allocateBuffers();
-
-            // if we don't have a buffer to put received data into
-            if (toFill==null)
-            {
-                // use the unwrapbuffer to hold received data.
-                _unwrapBuf.compact();
-                toFill=_unwrapBuf;
-            }
-            // Else if the fill buffer is too small for the SSL session
-            else if (toFill.capacity()<_session.getApplicationBufferSize())
-            {
-                // fill to the temporary unwrapBuffer
-                boolean progress=process(null,toFlush);
-
-                // if we received any data,
-                if (_unwrapBuf!=null && _unwrapBuf.hasContent())
-                {
-                    // transfer from temp buffer to fill buffer
-                    _unwrapBuf.skip(toFill.put(_unwrapBuf));
-                    return true;
-                }
-                else
-                    // return progress from recursive call
-                    return progress;
-            }
-            // Else if there is some temporary data
-            else if (_unwrapBuf!=null && _unwrapBuf.hasContent())
-            {
-                // transfer from temp buffer to fill buffer
-                _unwrapBuf.skip(toFill.put(_unwrapBuf));
-                return true;
-            }
-
-            // If we are here, we have a buffer ready into which we can put some read data.
-
-            // If we have no data to flush, flush the empty buffer
-            if (toFlush==null)
-                toFlush=__ZERO_BUFFER;
-
-            // While we are making progress processing SSL engine
-            boolean progress=true;
-            while (progress)
-            {
-                progress=false;
-
-                // Do any real IO
-                int filled=0,flushed=0;
-                try
-                {
-                    // Read any available data
-                    if (_inbound.space() > 0 && (filled = _endp.fill(_inbound)) > 0)
-                        progress = true;
-
-                    // flush any output data
-                    if (_outbound.hasContent() && (flushed=_endp.flush(_outbound)) > 0)
-                        progress = true;
-                }
-                catch (IOException e)
-                {
-                    _endp.close();
-                    throw e;
-                }
-                finally
-                {
-                    _logger.debug("{} {} {} filled={}/{} flushed={}/{}",_session,this,_engine.getHandshakeStatus(),filled,_inbound.length(),flushed,_outbound.length());
-                }
-
-                // handle the current hand share status
-                switch(_engine.getHandshakeStatus())
-                {
-                    case FINISHED:
-                        throw new IllegalStateException();
-
-                    case NOT_HANDSHAKING:
-                    {
-                        // Try unwrapping some application data
-                        if (toFill.space()>0 && _inbound.hasContent() && unwrap(toFill))
-                            progress=true;
-
-                        // Try wrapping some application data
-                        if (toFlush.hasContent() && _outbound.space()>0 && wrap(toFlush))
-                            progress=true;
-                    }
-                    break;
-
-                    case NEED_TASK:
-                    {
-                        // A task needs to be run, so run it!
-                        Runnable task;
-                        while ((task=_engine.getDelegatedTask())!=null)
-                        {
-                            progress=true;
-                            task.run();
-                        }
-
-                    }
-                    break;
-
-                    case NEED_WRAP:
-                    {
-                        // The SSL needs to send some handshake data to the other side
-                        if (_handshook && !_allowRenegotiate)
-                            _endp.close();
-                        else if (wrap(toFlush))
-                            progress=true;
-                    }
-                    break;
-
-                    case NEED_UNWRAP:
-                    {
-                        // The SSL needs to receive some handshake data from the other side
-                        if (_handshook && !_allowRenegotiate)
-                            _endp.close();
-                        else if (!_inbound.hasContent()&&filled==-1)
-                        {
-                            // No more input coming
-                            _endp.shutdownInput();
-                        }
-                        else if (unwrap(toFill))
-                            progress=true;
-                    }
-                    break;
-                }
-
-                // pass on ishut/oshut state
-                if (_endp.isOpen() && _endp.isInputShutdown() && !_inbound.hasContent())
-                    closeInbound();
-
-                if (_endp.isOpen() && _engine.isOutboundDone() && !_outbound.hasContent())
-                    _endp.shutdownOutput();
-
-                // remember if any progress has been made
-                some_progress|=progress;
-            }
-
-            // If we are reading into the temp buffer and it has some content, then we should be dispatched.
-            if (toFill==_unwrapBuf && _unwrapBuf.hasContent() && !_connection.isSuspended())
-                _aEndp.dispatch();
-        }
-        finally
-        {
-            releaseBuffers();
-            if (some_progress)
-                _progressed.set(true);
-        }
-        return some_progress;
-    }
-
-    private void closeInbound()
-    {
-        try
-        {
-            _engine.closeInbound();
-        }
-        catch (SSLException x)
-        {
-            _logger.debug(x);
-        }
-    }
-
-    private synchronized boolean wrap(final Buffer buffer) throws IOException
-    {
-        ByteBuffer bbuf=extractByteBuffer(buffer);
-        final SSLEngineResult result;
-        int encrypted_produced = 0;
-        int decrypted_consumed = 0;
-        synchronized(bbuf)
-        {
-            _outbound.compact();
-            ByteBuffer out_buffer=_outbound.getByteBuffer();
-            synchronized(out_buffer)
-            {
-                try
-                {
-                    bbuf.position(buffer.getIndex());
-                    bbuf.limit(buffer.putIndex());
-                    int decrypted_position = bbuf.position();
-
-                    out_buffer.position(_outbound.putIndex());
-                    out_buffer.limit(out_buffer.capacity());
-                    int encrypted_position = out_buffer.position();
-
-                    result=_engine.wrap(bbuf,out_buffer);
-                    if (_logger.isDebugEnabled())
-                        _logger.debug("{} wrap {} {} consumed={} produced={}",
-                            _session,
-                            result.getStatus(),
-                            result.getHandshakeStatus(),
-                            result.bytesConsumed(),
-                            result.bytesProduced());
-
-                    decrypted_consumed = bbuf.position() - decrypted_position;
-                    buffer.skip(decrypted_consumed);
-
-                    encrypted_produced = out_buffer.position() - encrypted_position;
-                    _outbound.setPutIndex(_outbound.putIndex() + encrypted_produced);
-                }
-                catch(SSLException e)
-                {
-                    _logger.debug(String.valueOf(_endp), e);
-                    _endp.close();
-                    throw e;
-                }
-                catch (IOException x)
-                {
-                    throw x;
-                }
-                catch (Exception x)
-                {
-                    throw new IOException(x);
-                }
-                finally
-                {
-                    out_buffer.position(0);
-                    out_buffer.limit(out_buffer.capacity());
-                    bbuf.position(0);
-                    bbuf.limit(bbuf.capacity());
-                }
-            }
-        }
-
-        switch(result.getStatus())
-        {
-            case BUFFER_UNDERFLOW:
-                throw new IllegalStateException();
-
-            case BUFFER_OVERFLOW:
-                break;
-
-            case OK:
-                if (result.getHandshakeStatus()==HandshakeStatus.FINISHED)
-                    _handshook=true;
-                break;
-
-            case CLOSED:
-                _logger.debug("wrap CLOSE {} {}",this,result);
-                if (result.getHandshakeStatus()==HandshakeStatus.FINISHED)
-                    _endp.close();
-                break;
-
-            default:
-                _logger.debug("{} wrap default {}",_session,result);
-            throw new IOException(result.toString());
-        }
-
-        return decrypted_consumed > 0 || encrypted_produced > 0;
-    }
-
-    private synchronized boolean unwrap(final Buffer buffer) throws IOException
-    {
-        if (!_inbound.hasContent())
-            return false;
-
-        ByteBuffer bbuf=extractByteBuffer(buffer);
-        final SSLEngineResult result;
-        int encrypted_consumed = 0;
-        int decrypted_produced = 0;
-        synchronized(bbuf)
-        {
-            ByteBuffer in_buffer=_inbound.getByteBuffer();
-            synchronized(in_buffer)
-            {
-                try
-                {
-                    bbuf.position(buffer.putIndex());
-                    bbuf.limit(buffer.capacity());
-                    int decrypted_position = bbuf.position();
-
-                    in_buffer.position(_inbound.getIndex());
-                    in_buffer.limit(_inbound.putIndex());
-                    int encrypted_position = in_buffer.position();
-
-                    result=_engine.unwrap(in_buffer,bbuf);
-                    if (_logger.isDebugEnabled())
-                        _logger.debug("{} unwrap {} {} consumed={} produced={}",
-                            _session,
-                            result.getStatus(),
-                            result.getHandshakeStatus(),
-                            result.bytesConsumed(),
-                            result.bytesProduced());
-
-                    encrypted_consumed = in_buffer.position() - encrypted_position;
-                    _inbound.skip(encrypted_consumed);
-                    _inbound.compact();
-
-                    decrypted_produced = bbuf.position() - decrypted_position;
-                    buffer.setPutIndex(buffer.putIndex() + decrypted_produced);
-                }
-                catch(SSLException e)
-                {
-                    _logger.debug(String.valueOf(_endp), e);
-                    _endp.close();
-                    throw e;
-                }
-                catch (IOException x)
-                {
-                    throw x;
-                }
-                catch (Exception x)
-                {
-                    throw new IOException(x);
-                }
-                finally
-                {
-                    in_buffer.position(0);
-                    in_buffer.limit(in_buffer.capacity());
-                    bbuf.position(0);
-                    bbuf.limit(bbuf.capacity());
-                }
-            }
-        }
-
-        switch(result.getStatus())
-        {
-            case BUFFER_UNDERFLOW:
-                if (_endp.isInputShutdown())
-                    _inbound.clear();
-                break;
-
-            case BUFFER_OVERFLOW:
-                if (_logger.isDebugEnabled()) _logger.debug("{} unwrap {} {}->{}",_session,result.getStatus(),_inbound.toDetailString(),buffer.toDetailString());
-                break;
-
-            case OK:
-                if (result.getHandshakeStatus()==HandshakeStatus.FINISHED)
-                    _handshook=true;
-                break;
-
-            case CLOSED:
-                _logger.debug("unwrap CLOSE {} {}",this,result);
-                if (result.getHandshakeStatus()==HandshakeStatus.FINISHED)
-                    _endp.close();
-                break;
-
-            default:
-                _logger.debug("{} wrap default {}",_session,result);
-            throw new IOException(result.toString());
-        }
-
-        //if (LOG.isDebugEnabled() && result.bytesProduced()>0)
-        //    LOG.debug("{} unwrapped '{}'",_session,buffer);
-
-        return encrypted_consumed > 0 || decrypted_produced > 0;
-    }
-
-
-    /* ------------------------------------------------------------ */
-    private ByteBuffer extractByteBuffer(Buffer buffer)
-    {
-        if (buffer.buffer() instanceof NIOBuffer)
-            return ((NIOBuffer)buffer.buffer()).getByteBuffer();
-        return ByteBuffer.wrap(buffer.array());
-    }
-
-    /* ------------------------------------------------------------ */
-    public AsyncEndPoint getSslEndPoint()
-    {
-        return _sslEndPoint;
-    }
-
-    /* ------------------------------------------------------------ */
-    public String toString()
-    {
-        return String.format("%s %s", super.toString(), _sslEndPoint);
-    }
-
-    /* ------------------------------------------------------------ */
-    /* ------------------------------------------------------------ */
-    public class SslEndPoint implements AsyncEndPoint
-    {
-        public SSLEngine getSslEngine()
-        {
-            return _engine;
-        }
-
-        public AsyncEndPoint getEndpoint()
-        {
-            return _aEndp;
-        }
-
-        public void shutdownOutput() throws IOException
-        {
-            synchronized (SslConnection.this)
-            {
-                try
-                {
-                    _logger.debug("{} ssl endp.oshut {}",_session,this);
-                    _oshut=true;
-                    _engine.closeOutbound();
-                }
-                catch (Exception x)
-                {
-                    throw new IOException(x);
-                }
-            }
-            flush();
-        }
-
-        public boolean isOutputShutdown()
-        {
-            synchronized (SslConnection.this)
-            {
-                return _oshut||!isOpen()||_engine.isOutboundDone();
-            }
-        }
-
-        public void shutdownInput() throws IOException
-        {
-            _logger.debug("{} ssl endp.ishut!",_session);
-            // We do not do a closeInput here, as SSL does not support half close.
-            // isInputShutdown works it out itself from buffer state and underlying endpoint state.
-        }
-
-        public boolean isInputShutdown()
-        {
-            synchronized (SslConnection.this)
-            {
-                return _endp.isInputShutdown() &&
-                !(_unwrapBuf!=null&&_unwrapBuf.hasContent()) &&
-                !(_inbound!=null&&_inbound.hasContent());
-            }
-        }
-
-        public void close() throws IOException
-        {
-            _logger.debug("{} ssl endp.close",_session);
-            _endp.close();
-        }
-
-        public int fill(Buffer buffer) throws IOException
-        {
-            int size=buffer.length();
-            process(buffer, null);
-
-            int filled=buffer.length()-size;
-
-            if (filled==0 && isInputShutdown())
-                return -1;
-            return filled;
-        }
-
-        public int flush(Buffer buffer) throws IOException
-        {
-            int size = buffer.length();
-            process(null, buffer);
-            return size-buffer.length();
-        }
-
-        public int flush(Buffer header, Buffer buffer, Buffer trailer) throws IOException
-        {
-            if (header!=null && header.hasContent())
-                return flush(header);
-            if (buffer!=null && buffer.hasContent())
-                return flush(buffer);
-            if (trailer!=null && trailer.hasContent())
-                return flush(trailer);
-            return 0;
-        }
-
-        public boolean blockReadable(long millisecs) throws IOException
-        {
-            long now = System.currentTimeMillis();
-            long end=millisecs>0?(now+millisecs):Long.MAX_VALUE;
-
-            while (now<end)
-            {
-                if (process(null,null))
-                    break;
-                _endp.blockReadable(end-now);
-                now = System.currentTimeMillis();
-            }
-
-            return now<end;
-        }
-
-        public boolean blockWritable(long millisecs) throws IOException
-        {
-            return _endp.blockWritable(millisecs);
-        }
-
-        public boolean isOpen()
-        {
-            return _endp.isOpen();
-        }
-
-        public Object getTransport()
-        {
-            return _endp;
-        }
-
-        public void flush() throws IOException
-        {
-            process(null, null);
-        }
-
-        public void dispatch()
-        {
-            _aEndp.dispatch();
-        }
-
-        public void asyncDispatch()
-        {
-            _aEndp.asyncDispatch();
-        }
-
-        public void scheduleWrite()
-        {
-            _aEndp.scheduleWrite();
-        }
-
-        public void onIdleExpired(long idleForMs)
-        {
-            _aEndp.onIdleExpired(idleForMs);
-        }
-
-        public void setCheckForIdle(boolean check)
-        {
-            _aEndp.setCheckForIdle(check);
-        }
-
-        public boolean isCheckForIdle()
-        {
-            return _aEndp.isCheckForIdle();
-        }
-
-        public void scheduleTimeout(Task task, long timeoutMs)
-        {
-            _aEndp.scheduleTimeout(task,timeoutMs);
-        }
-
-        public void cancelTimeout(Task task)
-        {
-            _aEndp.cancelTimeout(task);
-        }
-
-        public boolean isWritable()
-        {
-            return _aEndp.isWritable();
-        }
-
-        public boolean hasProgressed()
-        {
-            return _progressed.getAndSet(false);
-        }
-
-        public String getLocalAddr()
-        {
-            return _aEndp.getLocalAddr();
-        }
-
-        public String getLocalHost()
-        {
-            return _aEndp.getLocalHost();
-        }
-
-        public int getLocalPort()
-        {
-            return _aEndp.getLocalPort();
-        }
-
-        public String getRemoteAddr()
-        {
-            return _aEndp.getRemoteAddr();
-        }
-
-        public String getRemoteHost()
-        {
-            return _aEndp.getRemoteHost();
-        }
-
-        public int getRemotePort()
-        {
-            return _aEndp.getRemotePort();
-        }
-
-        public boolean isBlocking()
-        {
-            return false;
-        }
-
-        public int getMaxIdleTime()
-        {
-            return _aEndp.getMaxIdleTime();
-        }
-
-        public void setMaxIdleTime(int timeMs) throws IOException
-        {
-            _aEndp.setMaxIdleTime(timeMs);
-        }
-
-        public Connection getConnection()
-        {
-            return _connection;
-        }
-
-        public void setConnection(Connection connection)
-        {
-            _connection=(AsyncConnection)connection;
-        }
-
-        public String toString()
-        {
-            // Do NOT use synchronized (SslConnection.this)
-            // because it's very easy to deadlock when debugging is enabled.
-            // We do a best effort to print the right toString() and that's it.
-            Buffer inbound = _inbound;
-            Buffer outbound = _outbound;
-            Buffer unwrap = _unwrapBuf;
-            int i = inbound == null? -1 : inbound.length();
-            int o = outbound == null ? -1 : outbound.length();
-            int u = unwrap == null ? -1 : unwrap.length();
-            return String.format("SSL %s i/o/u=%d/%d/%d ishut=%b oshut=%b {%s}",
-                    _engine.getHandshakeStatus(),
-                    i, o, u,
-                    _ishut, _oshut,
-                    _connection);
-        }
-
-    }
-}
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/package-info.java b/jetty-io/src/main/java/org/eclipse/jetty/io/package-info.java
new file mode 100644
index 0000000..8a49dbf
--- /dev/null
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty IO : Core classes for Jetty IO subsystem
+ */
+package org.eclipse.jetty.io;
+
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslClientConnectionFactory.java b/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslClientConnectionFactory.java
new file mode 100644
index 0000000..265aa99
--- /dev/null
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslClientConnectionFactory.java
@@ -0,0 +1,73 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io.ssl;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.concurrent.Executor;
+
+import javax.net.ssl.SSLEngine;
+
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.ClientConnectionFactory;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+
+public class SslClientConnectionFactory implements ClientConnectionFactory
+{
+    public static final String SSL_PEER_HOST_CONTEXT_KEY = "ssl.peer.host";
+    public static final String SSL_PEER_PORT_CONTEXT_KEY = "ssl.peer.port";
+    public static final String SSL_ENGINE_CONTEXT_KEY = "ssl.engine";
+
+    private final SslContextFactory sslContextFactory;
+    private final ByteBufferPool byteBufferPool;
+    private final Executor executor;
+    private final ClientConnectionFactory connectionFactory;
+
+    public SslClientConnectionFactory(SslContextFactory sslContextFactory, ByteBufferPool byteBufferPool, Executor executor, ClientConnectionFactory connectionFactory)
+    {
+        this.sslContextFactory = sslContextFactory;
+        this.byteBufferPool = byteBufferPool;
+        this.executor = executor;
+        this.connectionFactory = connectionFactory;
+    }
+
+    @Override
+    public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map<String, Object> context) throws IOException
+    {
+        String host = (String)context.get(SSL_PEER_HOST_CONTEXT_KEY);
+        int port = (Integer)context.get(SSL_PEER_PORT_CONTEXT_KEY);
+        SSLEngine engine = sslContextFactory.newSSLEngine(host, port);
+        engine.setUseClientMode(true);
+        context.put(SSL_ENGINE_CONTEXT_KEY, engine);
+
+        SslConnection sslConnection = newSslConnection(byteBufferPool, executor, endPoint, engine);
+        sslConnection.setRenegotiationAllowed(sslContextFactory.isRenegotiationAllowed());
+        endPoint.setConnection(sslConnection);
+        EndPoint appEndPoint = sslConnection.getDecryptedEndPoint();
+        appEndPoint.setConnection(connectionFactory.newConnection(appEndPoint, context));
+
+        return sslConnection;
+    }
+
+    protected SslConnection newSslConnection(ByteBufferPool byteBufferPool, Executor executor, EndPoint endPoint, SSLEngine engine)
+    {
+        return new SslConnection(byteBufferPool, executor, endPoint, engine);
+    }
+}
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java b/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java
new file mode 100644
index 0000000..0527438
--- /dev/null
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java
@@ -0,0 +1,942 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io.ssl;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.ClosedChannelException;
+import java.util.Arrays;
+import java.util.concurrent.Executor;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLEngineResult;
+import javax.net.ssl.SSLEngineResult.HandshakeStatus;
+import javax.net.ssl.SSLEngineResult.Status;
+import javax.net.ssl.SSLException;
+
+import org.eclipse.jetty.io.AbstractConnection;
+import org.eclipse.jetty.io.AbstractEndPoint;
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.EofException;
+import org.eclipse.jetty.io.FillInterest;
+import org.eclipse.jetty.io.RuntimeIOException;
+import org.eclipse.jetty.io.SelectChannelEndPoint;
+import org.eclipse.jetty.io.WriteFlusher;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * A Connection that acts as an interceptor between an EndPoint providing SSL encrypted data
+ * and another consumer of an EndPoint (typically an {@link Connection} like HttpConnection) that
+ * wants unencrypted data.
+ * <p>
+ * The connector uses an {@link EndPoint} (typically {@link SelectChannelEndPoint}) as
+ * it's source/sink of encrypted data.   It then provides an endpoint via {@link #getDecryptedEndPoint()} to
+ * expose a source/sink of unencrypted data to another connection (eg HttpConnection).
+ * <p>
+ * The design of this class is based on a clear separation between the passive methods, which do not block nor schedule any
+ * asynchronous callbacks, and active methods that do schedule asynchronous callbacks.
+ * <p>
+ * The passive methods are {@link DecryptedEndPoint#fill(ByteBuffer)} and {@link DecryptedEndPoint#flush(ByteBuffer...)}. They make best
+ * effort attempts to progress the connection using only calls to the encrypted {@link EndPoint#fill(ByteBuffer)} and {@link EndPoint#flush(ByteBuffer...)}
+ * methods.  They will never block nor schedule any readInterest or write callbacks.   If a fill/flush cannot progress either because
+ * of network congestion or waiting for an SSL handshake message, then the fill/flush will simply return with zero bytes filled/flushed.
+ * Specifically, if a flush cannot proceed because it needs to receive a handshake message, then the flush will attempt to fill bytes from the
+ * encrypted endpoint, but if insufficient bytes are read it will NOT call {@link EndPoint#fillInterested(Callback)}.
+ * <p>
+ * It is only the active methods : {@link DecryptedEndPoint#fillInterested(Callback)} and
+ * {@link DecryptedEndPoint#write(Callback, ByteBuffer...)} that may schedule callbacks by calling the encrypted
+ * {@link EndPoint#fillInterested(Callback)} and {@link EndPoint#write(Callback, ByteBuffer...)}
+ * methods.  For normal data handling, the decrypted fillInterest method will result in an encrypted fillInterest and a decrypted
+ * write will result in an encrypted write. However, due to SSL handshaking requirements, it is also possible for a decrypted fill
+ * to call the encrypted write and for the decrypted flush to call the encrypted fillInterested methods.
+ * <p>
+ * MOST IMPORTANTLY, the encrypted callbacks from the active methods (#onFillable() and WriteFlusher#completeWrite()) do no filling or flushing
+ * themselves.  Instead they simple make the callbacks to the decrypted callbacks, so that the passive encrypted fill/flush will
+ * be called again and make another best effort attempt to progress the connection.
+ *
+ */
+public class SslConnection extends AbstractConnection
+{
+    private static final Logger LOG = Log.getLogger(SslConnection.class);
+    private static final boolean DEBUG = LOG.isDebugEnabled(); // Easy for the compiler to remove the code if DEBUG==false
+    private static final ByteBuffer __FILL_CALLED_FLUSH= BufferUtil.allocate(0);
+    private static final ByteBuffer __FLUSH_CALLED_FILL= BufferUtil.allocate(0);
+    private final ByteBufferPool _bufferPool;
+    private final SSLEngine _sslEngine;
+    private final DecryptedEndPoint _decryptedEndPoint;
+    private ByteBuffer _decryptedInput;
+    private ByteBuffer _encryptedInput;
+    private ByteBuffer _encryptedOutput;
+    private final boolean _encryptedDirectBuffers = false;
+    private final boolean _decryptedDirectBuffers = false;
+    private final Runnable _runCompletWrite = new Runnable()
+    {
+        @Override
+        public void run()
+        {
+            _decryptedEndPoint.getWriteFlusher().completeWrite();
+        }
+    };
+    private boolean _renegotiationAllowed;
+
+    public SslConnection(ByteBufferPool byteBufferPool, Executor executor, EndPoint endPoint, SSLEngine sslEngine)
+    {
+        // This connection does not execute calls to onfillable, so they will be called by the selector thread.
+        // onfillable does not block and will only wakeup another thread to do the actual reading and handling.
+        super(endPoint, executor, !EXECUTE_ONFILLABLE);
+        this._bufferPool = byteBufferPool;
+        this._sslEngine = sslEngine;
+        this._decryptedEndPoint = newDecryptedEndPoint();
+    }
+
+    protected DecryptedEndPoint newDecryptedEndPoint()
+    {
+        return new DecryptedEndPoint();
+    }
+
+    public SSLEngine getSSLEngine()
+    {
+        return _sslEngine;
+    }
+
+    public DecryptedEndPoint getDecryptedEndPoint()
+    {
+        return _decryptedEndPoint;
+    }
+
+    public boolean isRenegotiationAllowed()
+    {
+        return _renegotiationAllowed;
+    }
+
+    public void setRenegotiationAllowed(boolean renegotiationAllowed)
+    {
+        this._renegotiationAllowed = renegotiationAllowed;
+    }
+
+    @Override
+    public void onOpen()
+    {
+        try
+        {
+            // Begin the handshake
+            _sslEngine.beginHandshake();
+            super.onOpen();
+            getDecryptedEndPoint().getConnection().onOpen();
+        }
+        catch (SSLException x)
+        {
+            getEndPoint().close();
+            throw new RuntimeIOException(x);
+        }
+    }
+
+    @Override
+    public void onClose()
+    {
+        _decryptedEndPoint.getConnection().onClose();
+        super.onClose();
+    }
+
+    @Override
+    public void close()
+    {
+        getDecryptedEndPoint().getConnection().close();
+    }
+
+    @Override
+    public void onFillable()
+    {
+        // onFillable means that there are encrypted bytes ready to be filled.
+        // however we do not fill them here on this callback, but instead wakeup
+        // the decrypted readInterest and/or writeFlusher so that they will attempt
+        // to do the fill and/or flush again and these calls will do the actually
+        // filling.
+
+        if (DEBUG)
+            LOG.debug("onFillable enter {}", _decryptedEndPoint);
+
+        // We have received a close handshake, close the end point to send FIN.
+        if (_decryptedEndPoint.isInputShutdown())
+            _decryptedEndPoint.close();
+
+        // wake up whoever is doing the fill or the flush so they can
+        // do all the filling, unwrapping, wrapping and flushing
+        _decryptedEndPoint.getFillInterest().fillable();
+
+        // If we are handshaking, then wake up any waiting write as well as it may have been blocked on the read
+        synchronized(_decryptedEndPoint)
+        {
+            if (_decryptedEndPoint._flushRequiresFillToProgress)
+            {
+                _decryptedEndPoint._flushRequiresFillToProgress = false;
+                getExecutor().execute(_runCompletWrite);
+            }
+        }
+
+        if (DEBUG)
+            LOG.debug("onFillable exit {}", _decryptedEndPoint);
+    }
+
+    @Override
+    public void onFillInterestedFailed(Throwable cause)
+    {
+        // this means that the fill interest in encrypted bytes has failed.
+        // However we do not handle that here on this callback, but instead wakeup
+        // the decrypted readInterest and/or writeFlusher so that they will attempt
+        // to do the fill and/or flush again and these calls will do the actually
+        // handle the cause.
+        _decryptedEndPoint.getFillInterest().onFail(cause);
+
+        boolean failFlusher = false;
+        synchronized(_decryptedEndPoint)
+        {
+            if (_decryptedEndPoint._flushRequiresFillToProgress)
+            {
+                _decryptedEndPoint._flushRequiresFillToProgress = false;
+                failFlusher = true;
+            }
+        }
+        if (failFlusher)
+            _decryptedEndPoint.getWriteFlusher().onFail(cause);
+    }
+
+    @Override
+    public String toString()
+    {
+        ByteBuffer b = _encryptedInput;
+        int ei=b==null?-1:b.remaining();
+        b = _encryptedOutput;
+        int eo=b==null?-1:b.remaining();
+        b = _decryptedInput;
+        int di=b==null?-1:b.remaining();
+
+        return String.format("SslConnection@%x{%s,eio=%d/%d,di=%d} -> %s",
+                hashCode(),
+                _sslEngine.getHandshakeStatus(),
+                ei,eo,di,
+                _decryptedEndPoint.getConnection());
+    }
+
+    public class DecryptedEndPoint extends AbstractEndPoint
+    {
+        private boolean _fillRequiresFlushToProgress;
+        private boolean _flushRequiresFillToProgress;
+        private boolean _cannotAcceptMoreAppDataToFlush;
+        private boolean _handshaken;
+        private boolean _underFlown;
+
+        private final Callback _writeCallback = new Callback()
+        {
+            @Override
+            public void succeeded()
+            {
+                // This means that a write of encrypted data has completed.  Writes are done
+                // only if there is a pending writeflusher or a read needed to write
+                // data.  In either case the appropriate callback is passed on.
+                boolean fillable = false;
+                synchronized (DecryptedEndPoint.this)
+                {
+                    if (DEBUG)
+                        LOG.debug("write.complete {}", SslConnection.this.getEndPoint());
+
+                    releaseEncryptedOutputBuffer();
+
+                    _cannotAcceptMoreAppDataToFlush = false;
+
+                    if (_fillRequiresFlushToProgress)
+                    {
+                        _fillRequiresFlushToProgress = false;
+                        fillable = true;
+                    }
+                }
+                if (fillable)
+                    getFillInterest().fillable();
+                getExecutor().execute(_runCompletWrite);
+            }
+
+            @Override
+            public void failed(final Throwable x)
+            {
+                // This means that a write of data has failed.  Writes are done
+                // only if there is an active writeflusher or a read needed to write
+                // data.  In either case the appropriate callback is passed on.
+                boolean fail_filler = false;
+                synchronized (DecryptedEndPoint.this)
+                {
+                    if (DEBUG)
+                        LOG.debug("{} write.failed", SslConnection.this, x);
+                    BufferUtil.clear(_encryptedOutput);
+                    releaseEncryptedOutputBuffer();
+
+                    _cannotAcceptMoreAppDataToFlush = false;
+
+                    if (_fillRequiresFlushToProgress)
+                    {
+                        _fillRequiresFlushToProgress = false;
+                        fail_filler = true;
+                    }
+                }
+
+                final boolean filler_failed=fail_filler;
+
+                failedCallback(new Callback()
+                {
+                    @Override
+                    public void succeeded()
+                    {
+                    }
+
+                    @Override
+                    public void failed(Throwable x)
+                    {
+                        if (filler_failed)
+                            getFillInterest().onFail(x);
+                        getWriteFlusher().onFail(x);
+                    }
+
+                },x);
+            }
+        };
+
+        public DecryptedEndPoint()
+        {
+            super(null,getEndPoint().getLocalAddress(), getEndPoint().getRemoteAddress());
+            setIdleTimeout(getEndPoint().getIdleTimeout());
+        }
+
+        @Override
+        protected FillInterest getFillInterest()
+        {
+            return super.getFillInterest();
+        }
+
+        @Override
+        public void setIdleTimeout(long idleTimeout)
+        {
+            super.setIdleTimeout(idleTimeout);
+            getEndPoint().setIdleTimeout(idleTimeout);
+        }
+
+        @Override
+        protected WriteFlusher getWriteFlusher()
+        {
+            return super.getWriteFlusher();
+        }
+
+        @Override
+        protected void onIncompleteFlush()
+        {
+            // This means that the decrypted endpoint write method was called and not
+            // all data could be wrapped. So either we need to write some encrypted data,
+            // OR if we are handshaking we need to read some encrypted data OR
+            // if neither then we should just try the flush again.
+            boolean try_again = false;
+            synchronized (DecryptedEndPoint.this)
+            {
+                if (DEBUG)
+                    LOG.debug("onIncompleteFlush {}", getEndPoint());
+                // If we have pending output data,
+                if (BufferUtil.hasContent(_encryptedOutput))
+                {
+                    // write it
+                    _cannotAcceptMoreAppDataToFlush = true;
+                    getEndPoint().write(_writeCallback, _encryptedOutput);
+                }
+                // If we are handshaking and need to read,
+                else if (_sslEngine.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP)
+                {
+                    // check if we are actually read blocked in order to write
+                    _flushRequiresFillToProgress = true;
+                    SslConnection.this.fillInterested();
+                }
+                else
+                {
+                    // We can get here because the WriteFlusher might not see progress
+                    // when it has just flushed the encrypted data, but not consumed anymore
+                    // of the application buffers.  This is mostly avoided by another iteration
+                    // within DecryptedEndPoint flush(), but I cannot convince myself that
+                    // this is never ever the case.
+                    try_again = true;
+                }
+            }
+
+
+            if (try_again)
+            {
+                // If the output is closed,
+                if (isOutputShutdown())
+                {
+                    // don't bother writing, just notify of close
+                    getWriteFlusher().onClose();
+                }
+                // Else,
+                else
+                {
+                    // try to flush what is pending
+                    // because this is a special case (see above) we could probably
+                    // avoid the dispatch, but best to be sure
+                    getExecutor().execute(_runCompletWrite);
+                }
+            }
+        }
+
+        @Override
+        protected boolean needsFill() throws IOException
+        {
+            // This means that the decrypted data consumer has called the fillInterested
+            // method on the DecryptedEndPoint, so we have to work out if there is
+            // decrypted data to be filled or what callbacks to setup to be told when there
+            // might be more encrypted data available to attempt another call to fill
+
+            synchronized (DecryptedEndPoint.this)
+            {
+                // Do we already have some app data, then app can fill now so return true
+                if (BufferUtil.hasContent(_decryptedInput))
+                    return true;
+
+                // If we have no encrypted data to decrypt OR we have some, but it is not enough
+                if (BufferUtil.isEmpty(_encryptedInput) || _underFlown)
+                {
+                    // We are not ready to read data
+
+                    // Are we actually write blocked?
+                    if (_fillRequiresFlushToProgress)
+                    {
+                        // we must be blocked trying to write before we can read
+
+                        // Do we have data to write
+                        if (BufferUtil.hasContent(_encryptedOutput))
+                        {
+                            // write it
+                            _cannotAcceptMoreAppDataToFlush = true;
+                            getEndPoint().write(_writeCallback, _encryptedOutput);
+                        }
+                        else
+                        {
+                            // we have already written the net data
+                            // pretend we are readable so the wrap is done by next readable callback
+                            _fillRequiresFlushToProgress = false;
+                            return true;
+                        }
+                    }
+                    else
+                    {
+                        // Normal readable callback
+                        // Get called back on onfillable when then is more data to fill
+                        SslConnection.this.fillInterested();
+                    }
+
+                    return false;
+                }
+                else
+                {
+                    // We are ready to read data
+                    return true;
+                }
+            }
+        }
+
+        @Override
+        public void setConnection(Connection connection)
+        {
+            if (connection instanceof AbstractConnection)
+            {
+                AbstractConnection a = (AbstractConnection)connection;
+                if (a.getInputBufferSize()<_sslEngine.getSession().getApplicationBufferSize())
+                    a.setInputBufferSize(_sslEngine.getSession().getApplicationBufferSize());
+            }
+            super.setConnection(connection);
+        }
+
+        public SslConnection getSslConnection()
+        {
+            return SslConnection.this;
+        }
+
+        @Override
+        public synchronized int fill(ByteBuffer buffer) throws IOException
+        {
+            if (DEBUG)
+                LOG.debug("{} fill enter", SslConnection.this);
+            try
+            {
+                // Do we already have some decrypted data?
+                if (BufferUtil.hasContent(_decryptedInput))
+                    return BufferUtil.append(buffer,_decryptedInput);
+
+                // We will need a network buffer
+                if (_encryptedInput == null)
+                    _encryptedInput = _bufferPool.acquire(_sslEngine.getSession().getPacketBufferSize(), _encryptedDirectBuffers);
+                else
+                    BufferUtil.compact(_encryptedInput);
+
+                // We also need an app buffer, but can use the passed buffer if it is big enough
+                ByteBuffer app_in;
+                if (BufferUtil.space(buffer) > _sslEngine.getSession().getApplicationBufferSize())
+                    app_in = buffer;
+                else if (_decryptedInput == null)
+                    app_in = _decryptedInput = _bufferPool.acquire(_sslEngine.getSession().getApplicationBufferSize(), _decryptedDirectBuffers);
+                else
+                    app_in = _decryptedInput;
+
+                // loop filling and unwrapping until we have something
+                while (true)
+                {
+                    // Let's try reading some encrypted data... even if we have some already.
+                    int net_filled = getEndPoint().fill(_encryptedInput);
+                    if (DEBUG)
+                        LOG.debug("{} filled {} encrypted bytes", SslConnection.this, net_filled);
+
+                    decryption: while (true)
+                    {
+                        // Let's unwrap even if we have no net data because in that
+                        // case we want to fall through to the handshake handling
+                        int pos = BufferUtil.flipToFill(app_in);
+                        SSLEngineResult unwrapResult;
+                        try
+                        {
+                            unwrapResult = _sslEngine.unwrap(_encryptedInput, app_in);
+                        }
+                        finally
+                        {
+                            BufferUtil.flipToFlush(app_in, pos);
+                        }
+                        if (DEBUG)
+                            LOG.debug("{} unwrap {}", SslConnection.this, unwrapResult);
+
+                        HandshakeStatus handshakeStatus = _sslEngine.getHandshakeStatus();
+                        HandshakeStatus unwrapHandshakeStatus = unwrapResult.getHandshakeStatus();
+                        Status unwrapResultStatus = unwrapResult.getStatus();
+
+                        // Extra check on unwrapResultStatus == OK with zero length buffer is due
+                        // to SSL client on android (see bug #454773)
+                        _underFlown = unwrapResultStatus == Status.BUFFER_UNDERFLOW || unwrapResultStatus == Status.OK && unwrapResult.bytesConsumed()==0 && unwrapResult.bytesProduced()==0;
+
+                        if (_underFlown)
+                        {
+                            if (net_filled < 0)
+                                closeInbound();
+                            if (net_filled <= 0)
+                                return net_filled;
+                        }
+
+                        switch (unwrapResultStatus)
+                        {
+                            case CLOSED:
+                            {
+                                switch (handshakeStatus)
+                                {
+                                    case NOT_HANDSHAKING:
+                                    {
+                                        // We were not handshaking, so just tell the app we are closed
+                                        return -1;
+                                    }
+                                    case NEED_TASK:
+                                    {
+                                        _sslEngine.getDelegatedTask().run();
+                                        continue;
+                                    }
+                                    case NEED_WRAP:
+                                    {
+                                        // We need to send some handshake data (probably the close handshake).
+                                        // We return -1 so that the application can drive the close by flushing
+                                        // or shutting down the output.
+                                        return -1;
+                                    }
+                                    case NEED_UNWRAP:
+                                    {
+                                        // We expected to read more, but we got closed.
+                                        // Return -1 to indicate to the application to drive the close.
+                                        return -1;
+                                    }
+                                    default:
+                                    {
+                                        throw new IllegalStateException();
+                                    }
+                                }
+                            }
+                            case BUFFER_UNDERFLOW:
+                            case OK:
+                            {
+                                if (unwrapHandshakeStatus == HandshakeStatus.FINISHED && !_handshaken)
+                                {
+                                    _handshaken = true;
+                                    if (DEBUG)
+                                        LOG.debug("{} {} handshake completed", SslConnection.this,
+                                                _sslEngine.getUseClientMode() ? "client-side" : "resumed session server-side");
+                                }
+
+                                // Check whether renegotiation is allowed
+                                if (_handshaken && handshakeStatus != HandshakeStatus.NOT_HANDSHAKING && !isRenegotiationAllowed())
+                                {
+                                    if (DEBUG)
+                                        LOG.debug("{} renegotiation denied", SslConnection.this);
+                                    closeInbound();
+                                    return -1;
+                                }
+
+                                // If bytes were produced, don't bother with the handshake status;
+                                // pass the decrypted data to the application, which will perform
+                                // another call to fill() or flush().
+                                if (unwrapResult.bytesProduced() > 0)
+                                {
+                                    if (app_in == buffer)
+                                        return unwrapResult.bytesProduced();
+                                    return BufferUtil.append(buffer,_decryptedInput);
+                                }
+
+                                switch (handshakeStatus)
+                                {
+                                    case NOT_HANDSHAKING:
+                                    {
+                                        if (_underFlown)
+                                            break decryption;
+                                        continue;
+                                    }
+                                    case NEED_TASK:
+                                    {
+                                        _sslEngine.getDelegatedTask().run();
+                                        continue;
+                                    }
+                                    case NEED_WRAP:
+                                    {
+                                        // If we are called from flush()
+                                        // return to let it do the wrapping.
+                                        if (buffer == __FLUSH_CALLED_FILL)
+                                            return 0;
+
+                                        _fillRequiresFlushToProgress = true;
+                                        flush(__FILL_CALLED_FLUSH);
+                                        if (BufferUtil.isEmpty(_encryptedOutput))
+                                        {
+                                            // The flush wrote all the encrypted bytes so continue to fill
+                                            _fillRequiresFlushToProgress = false;
+                                            continue;
+                                        }
+                                        else
+                                        {
+                                            // The flush did not complete, return from fill()
+                                            // and let the write completion mechanism to kick in.
+                                            return 0;
+                                        }
+                                    }
+                                    case NEED_UNWRAP:
+                                    {
+                                        if (_underFlown)
+                                            break decryption;
+                                        continue;
+                                    }
+                                    default:
+                                    {
+                                        throw new IllegalStateException();
+                                    }
+                                }
+                            }
+                            default:
+                            {
+                                throw new IllegalStateException();
+                            }
+                        }
+                    }
+                }
+            }
+            catch (Exception e)
+            {
+                getEndPoint().close();
+                throw e;
+            }
+            finally
+            {
+                // If we are handshaking, then wake up any waiting write as well as it may have been blocked on the read
+                if (_flushRequiresFillToProgress)
+                {
+                    _flushRequiresFillToProgress = false;
+                    getExecutor().execute(_runCompletWrite);
+                }
+
+                if (_encryptedInput != null && !_encryptedInput.hasRemaining())
+                {
+                    _bufferPool.release(_encryptedInput);
+                    _encryptedInput = null;
+                }
+                if (_decryptedInput != null && !_decryptedInput.hasRemaining())
+                {
+                    _bufferPool.release(_decryptedInput);
+                    _decryptedInput = null;
+                }
+                if (DEBUG)
+                    LOG.debug("{} fill exit", SslConnection.this);
+            }
+        }
+
+        private void closeInbound()
+        {
+            try
+            {
+                _sslEngine.closeInbound();
+            }
+            catch (SSLException x)
+            {
+                LOG.ignore(x);
+            }
+        }
+
+        @Override
+        public synchronized boolean flush(ByteBuffer... appOuts) throws IOException
+        {
+            // The contract for flush does not require that all appOuts bytes are written
+            // or even that any appOut bytes are written!  If the connection is write block
+            // or busy handshaking, then zero bytes may be taken from appOuts and this method
+            // will return 0 (even if some handshake bytes were flushed and filled).
+            // it is the applications responsibility to call flush again - either in a busy loop
+            // or better yet by using EndPoint#write to do the flushing.
+
+            if (DEBUG)
+                LOG.debug("{} flush enter {}", SslConnection.this, Arrays.toString(appOuts));
+            int consumed=0;
+            try
+            {
+                if (_cannotAcceptMoreAppDataToFlush)
+                {
+                    if (_sslEngine.isOutboundDone())
+                        throw new EofException(new ClosedChannelException());
+                    return false;
+                }
+
+                // We will need a network buffer
+                if (_encryptedOutput == null)
+                    _encryptedOutput = _bufferPool.acquire(_sslEngine.getSession().getPacketBufferSize(), _encryptedDirectBuffers);
+
+                while (true)
+                {
+                    // We call sslEngine.wrap to try to take bytes from appOut buffers and encrypt them into the _netOut buffer
+                    BufferUtil.compact(_encryptedOutput);
+                    int pos = BufferUtil.flipToFill(_encryptedOutput);
+                    SSLEngineResult wrapResult;
+                    try
+                    {
+                        wrapResult=_sslEngine.wrap(appOuts, _encryptedOutput);
+                    }
+                    finally
+                    {
+                        BufferUtil.flipToFlush(_encryptedOutput, pos);
+                    }
+                    
+                    if (DEBUG)
+                        LOG.debug("{} wrap {}", SslConnection.this, wrapResult);
+                    if (wrapResult.bytesConsumed()>0)
+                        consumed+=wrapResult.bytesConsumed();
+                    Status wrapResultStatus = wrapResult.getStatus();
+
+                    boolean allConsumed=true;
+                    for (ByteBuffer b : appOuts)
+                        if (BufferUtil.hasContent(b))
+                            allConsumed=false;
+
+                    // and deal with the results returned from the sslEngineWrap
+                    switch (wrapResultStatus)
+                    {
+                        case CLOSED:
+                            // The SSL engine has close, but there may be close handshake that needs to be written
+                            if (BufferUtil.hasContent(_encryptedOutput))
+                            {
+                                _cannotAcceptMoreAppDataToFlush = true;
+                                getEndPoint().flush(_encryptedOutput);
+                                getEndPoint().shutdownOutput();
+                                // If we failed to flush the close handshake then we will just pretend that
+                                // the write has progressed normally and let a subsequent call to flush
+                                // (or WriteFlusher#onIncompleteFlushed) to finish writing the close handshake.
+                                // The caller will find out about the close on a subsequent flush or fill.
+                                if (BufferUtil.hasContent(_encryptedOutput))
+                                    return false;
+                            }
+                            // otherwise we have written, and the caller will close the underlying connection
+                            else
+                            {
+                                getEndPoint().shutdownOutput();
+                            }
+                            return allConsumed;
+
+                        case BUFFER_UNDERFLOW:
+                            throw new IllegalStateException();
+
+                        default:
+                            if (DEBUG)
+                                LOG.debug("{} {} {}", this, wrapResultStatus, BufferUtil.toDetailString(_encryptedOutput));
+
+                            if (wrapResult.getHandshakeStatus() == HandshakeStatus.FINISHED && !_handshaken)
+                            {
+                                _handshaken = true;
+                                if (DEBUG)
+                                    LOG.debug("{} {} handshake completed", SslConnection.this, "server-side");
+                            }
+
+                            HandshakeStatus handshakeStatus = _sslEngine.getHandshakeStatus();
+
+                            // Check whether renegotiation is allowed
+                            if (_handshaken && handshakeStatus != HandshakeStatus.NOT_HANDSHAKING && !isRenegotiationAllowed())
+                            {
+                                if (DEBUG)
+                                    LOG.debug("{} renegotiation denied", SslConnection.this);
+                                getEndPoint().shutdownOutput();
+                                return allConsumed;
+                            }
+
+                            // if we have net bytes, let's try to flush them
+                            if (BufferUtil.hasContent(_encryptedOutput))
+                                if (!getEndPoint().flush(_encryptedOutput))
+                                    getEndPoint().flush(_encryptedOutput); // one retry
+
+                            // But we also might have more to do for the handshaking state.
+                            switch (handshakeStatus)
+                            {
+                                case NOT_HANDSHAKING:
+                                    // If we have not consumed all and had just finished handshaking, then we may
+                                    // have just flushed the last handshake in the encrypted buffers, so we should
+                                    // try again.
+                                    if (!allConsumed && wrapResult.getHandshakeStatus()==HandshakeStatus.FINISHED && BufferUtil.isEmpty(_encryptedOutput))
+                                        continue;
+
+                                    // Return true if we consumed all the bytes and encrypted are all flushed
+                                    return allConsumed && BufferUtil.isEmpty(_encryptedOutput);
+
+                                case NEED_TASK:
+                                    // run the task and continue
+                                    _sslEngine.getDelegatedTask().run();
+                                    continue;
+
+                                case NEED_WRAP:
+                                    // Hey we just wrapped! Oh well who knows what the sslEngine is thinking, so continue and we will wrap again
+                                    continue;
+
+                                case NEED_UNWRAP:
+                                    // Ah we need to fill some data so we can write.
+                                    // So if we were not called from fill and the app is not reading anyway
+                                    if (appOuts[0]!=__FILL_CALLED_FLUSH && !getFillInterest().isInterested())
+                                    {
+                                        // Tell the onFillable method that there might be a write to complete
+                                        _flushRequiresFillToProgress = true;
+                                        fill(__FLUSH_CALLED_FILL);
+                                        // Check if after the fill() we need to wrap again
+                                        if (handshakeStatus == HandshakeStatus.NEED_WRAP)
+                                            continue;
+                                    }
+                                    return allConsumed && BufferUtil.isEmpty(_encryptedOutput);
+
+                                case FINISHED:
+                                    throw new IllegalStateException();
+                            }
+                    }
+                }
+            }
+            finally
+            {
+                if (DEBUG)
+                    LOG.debug("{} flush exit, consumed {}", SslConnection.this, consumed);
+                releaseEncryptedOutputBuffer();
+            }
+        }
+
+        private void releaseEncryptedOutputBuffer()
+        {
+            if (!Thread.holdsLock(DecryptedEndPoint.this))
+                throw new IllegalStateException();
+            if (_encryptedOutput != null && !_encryptedOutput.hasRemaining())
+            {
+                _bufferPool.release(_encryptedOutput);
+                _encryptedOutput = null;
+            }
+        }
+
+        @Override
+        public void shutdownOutput()
+        {
+            boolean ishut = isInputShutdown();
+            boolean oshut = isOutputShutdown();
+            if (DEBUG)
+                LOG.debug("{} shutdownOutput: oshut={}, ishut={}", SslConnection.this, oshut, ishut);
+            if (ishut)
+            {
+                // Aggressively close, since inbound close alert has already been processed
+                // and the TLS specification allows to close the connection directly, which
+                // is what most other implementations expect: a FIN rather than a TLS close
+                // reply. If a TLS close reply is sent, most implementations send a RST.
+                getEndPoint().close();
+            }
+            else if (!oshut)
+            {
+                try
+                {
+                    _sslEngine.closeOutbound();
+                    flush(BufferUtil.EMPTY_BUFFER); // Send close handshake
+                    SslConnection.this.fillInterested(); // seek reply FIN or RST or close handshake
+                }
+                catch (Exception e)
+                {
+                    LOG.ignore(e);
+                    getEndPoint().close();
+                }
+            }
+        }
+
+        @Override
+        public boolean isOutputShutdown()
+        {
+            return _sslEngine.isOutboundDone() || getEndPoint().isOutputShutdown();
+        }
+
+        @Override
+        public void close()
+        {
+            super.close();
+            // First send the TLS Close Alert, then the FIN
+            shutdownOutput();
+            getEndPoint().close();
+        }
+
+        @Override
+        public boolean isOpen()
+        {
+            return getEndPoint().isOpen();
+        }
+
+        @Override
+        public Object getTransport()
+        {
+            return getEndPoint();
+        }
+
+        @Override
+        public boolean isInputShutdown()
+        {
+            return _sslEngine.isInboundDone();
+        }
+
+        @Override
+        public String toString()
+        {
+            return super.toString()+"->"+getEndPoint().toString();
+        }
+    }
+}
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/package-info.java b/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/package-info.java
new file mode 100644
index 0000000..08f8706
--- /dev/null
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty IO : Core SSL Support
+ */
+package org.eclipse.jetty.io.ssl;
+
diff --git a/jetty-io/src/test/java/org/eclipse/jetty/io/ArrayByteBufferPoolTest.java b/jetty-io/src/test/java/org/eclipse/jetty/io/ArrayByteBufferPoolTest.java
new file mode 100644
index 0000000..a1803bb
--- /dev/null
+++ b/jetty-io/src/test/java/org/eclipse/jetty/io/ArrayByteBufferPoolTest.java
@@ -0,0 +1,147 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import static org.hamcrest.Matchers.greaterThanOrEqualTo;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import java.nio.ByteBuffer;
+
+import org.hamcrest.Matchers;
+import org.junit.Test;
+
+public class ArrayByteBufferPoolTest
+{
+    @Test
+    public void testMinimumRelease() throws Exception
+    {
+        ArrayByteBufferPool bufferPool = new ArrayByteBufferPool(10,100,1000);
+        ArrayByteBufferPool.Bucket[] buckets = bufferPool.bucketsFor(true);
+
+        for (int size=1;size<=9;size++)
+        {
+            ByteBuffer buffer = bufferPool.acquire(size, true);
+
+            assertTrue(buffer.isDirect());
+            assertEquals(size,buffer.capacity());
+            for (ArrayByteBufferPool.Bucket bucket : buckets)
+                assertTrue(bucket._queue.isEmpty());
+
+            bufferPool.release(buffer);
+
+            for (ArrayByteBufferPool.Bucket bucket : buckets)
+                assertTrue(bucket._queue.isEmpty());
+        }
+    }
+
+    @Test
+    public void testMaxRelease() throws Exception
+    {
+        ArrayByteBufferPool bufferPool = new ArrayByteBufferPool(10,100,1000);
+        ArrayByteBufferPool.Bucket[] buckets = bufferPool.bucketsFor(true);
+
+        for (int size=999;size<=1001;size++)
+        {
+            bufferPool.clear();
+            ByteBuffer buffer = bufferPool.acquire(size, true);
+
+            assertTrue(buffer.isDirect());
+            assertThat(buffer.capacity(),greaterThanOrEqualTo(size));
+            for (ArrayByteBufferPool.Bucket bucket : buckets)
+                assertTrue(bucket._queue.isEmpty());
+
+            bufferPool.release(buffer);
+
+            int pooled=0;
+            for (ArrayByteBufferPool.Bucket bucket : buckets)
+            {
+                pooled+=bucket._queue.size();
+            }
+            assertEquals(size<=1000,1==pooled);
+        }
+    }
+
+    @Test
+    public void testAcquireRelease() throws Exception
+    {
+        ArrayByteBufferPool bufferPool = new ArrayByteBufferPool(10,100,1000);
+        ArrayByteBufferPool.Bucket[] buckets = bufferPool.bucketsFor(true);
+
+        for (int size=390;size<=510;size++)
+        {
+            bufferPool.clear();
+            ByteBuffer buffer = bufferPool.acquire(size, true);
+
+            assertTrue(buffer.isDirect());
+            assertThat(buffer.capacity(), greaterThanOrEqualTo(size));
+            for (ArrayByteBufferPool.Bucket bucket : buckets)
+                assertTrue(bucket._queue.isEmpty());
+
+            bufferPool.release(buffer);
+
+            int pooled=0;
+            for (ArrayByteBufferPool.Bucket bucket : buckets)
+            {
+                if (!bucket._queue.isEmpty())
+                {
+                    pooled+=bucket._queue.size();
+                    assertThat(bucket._size,greaterThanOrEqualTo(size));
+                    assertThat(bucket._size,Matchers.lessThan(size+100));
+                }
+            }
+            assertEquals(1,pooled);
+        }
+    }
+
+    @Test
+    public void testAcquireReleaseAcquire() throws Exception
+    {
+        ArrayByteBufferPool bufferPool = new ArrayByteBufferPool(10,100,1000);
+        ArrayByteBufferPool.Bucket[] buckets = bufferPool.bucketsFor(true);
+
+        for (int size=390;size<=510;size++)
+        {
+            bufferPool.clear();
+            ByteBuffer buffer1 = bufferPool.acquire(size, true);
+            bufferPool.release(buffer1);
+            ByteBuffer buffer2 = bufferPool.acquire(size, true);
+            bufferPool.release(buffer2);
+            ByteBuffer buffer3 = bufferPool.acquire(size, false);
+            bufferPool.release(buffer3);
+
+            int pooled=0;
+            for (ArrayByteBufferPool.Bucket bucket : buckets)
+            {
+                if (!bucket._queue.isEmpty())
+                {
+                    pooled+=bucket._queue.size();
+                    assertThat(bucket._size,greaterThanOrEqualTo(size));
+                    assertThat(bucket._size,Matchers.lessThan(size+100));
+                }
+            }
+            assertEquals(1,pooled);
+
+            assertTrue(buffer1==buffer2);
+            assertTrue(buffer1!=buffer3);
+        }
+    }
+
+}
diff --git a/jetty-io/src/test/java/org/eclipse/jetty/io/BufferCacheTest.java b/jetty-io/src/test/java/org/eclipse/jetty/io/BufferCacheTest.java
deleted file mode 100644
index 0aa3148..0000000
--- a/jetty-io/src/test/java/org/eclipse/jetty/io/BufferCacheTest.java
+++ /dev/null
@@ -1,154 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.io;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotSame;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-
-import org.junit.Before;
-import org.junit.Test;
-
-/**
- *
- */
-public class BufferCacheTest
-{
-    private final static String[] S = {"S0", "S1", "s2", "s3" };
-
-    private BufferCache cache;
-
-    @Before
-    public void init() throws Exception
-    {
-        cache=new BufferCache();
-        cache.add(S[1],1);
-        cache.add(S[2],2);
-        cache.add(S[3],3);
-    }
-
-    @Test
-    public void testLookupIndex()
-    {
-        for (int i=0; i<S.length; i++)
-        {
-            String s="S0S1s2s3";
-            ByteArrayBuffer buf=new ByteArrayBuffer(s.getBytes(),i*2,2);
-            BufferCache.CachedBuffer b=cache.get(buf);
-            int index=b==null?-1:b.getOrdinal();
-
-            if (i>0)
-                assertEquals(i,index);
-            else
-                assertEquals(-1,index);
-        }
-    }
-
-    @Test
-    public void testGetBuffer()
-    {
-        for (int i=0; i<S.length; i++)
-        {
-            String s="S0S1s2s3";
-            ByteArrayBuffer buf=new ByteArrayBuffer(s.getBytes(),i*2,2);
-            Buffer b=cache.get(buf);
-
-            if (i>0)
-                assertEquals(i,b.peek(1)-'0');
-            else
-                assertEquals(null,b);
-        }
-    }
-
-    @Test
-    public void testLookupBuffer()
-    {
-        for (int i=0; i<S.length; i++)
-        {
-            String s="S0S1s2s3";
-            ByteArrayBuffer buf=new ByteArrayBuffer(s.getBytes(),i*2,2);
-            Buffer b=cache.lookup(buf);
-
-            assertEquals(S[i],b.toString());
-            if (i>0)
-                assertSame(""+i, S[i], b.toString());
-            else
-            {
-                assertNotSame(""+i, S[i], b.toString());
-                assertEquals(""+i, S[i], b.toString());
-            }
-        }
-    }
-
-    @Test
-    public void testLookupPartialBuffer()
-    {
-        cache.add("44444",4);
-
-        ByteArrayBuffer buf=new ByteArrayBuffer("44444");
-        Buffer b=cache.lookup(buf);
-        assertEquals("44444",b.toString());
-        assertEquals(4,cache.getOrdinal(b));
-
-        buf=new ByteArrayBuffer("4444");
-        b=cache.lookup(buf);
-        assertEquals(-1,cache.getOrdinal(b));
-
-        buf=new ByteArrayBuffer("44444x");
-        b=cache.lookup(buf);
-        assertEquals(-1,cache.getOrdinal(b));
-
-
-    }
-
-    @Test
-    public void testInsensitiveLookupBuffer()
-    {
-        for (int i=0; i<S.length; i++)
-        {
-            String s="s0s1S2S3";
-            ByteArrayBuffer buf=new ByteArrayBuffer(s.getBytes(),i*2,2);
-            Buffer b=cache.lookup(buf);
-
-            assertTrue("test"+i,S[i].equalsIgnoreCase(b.toString()));
-            if (i>0)
-                assertSame("test"+i, S[i], b.toString());
-            else
-                assertNotSame("test"+i, S[i], b.toString());
-        }
-    }
-
-    @Test
-    public void testToString()
-    {
-        for (int i=0; i<S.length; i++)
-        {
-            String s="S0S1s2s3";
-            ByteArrayBuffer buf=new ByteArrayBuffer(s.getBytes(),i*2,2);
-            String b=cache.toString(buf);
-
-            assertEquals(S[i],b);
-            if (i>0)
-                assertSame(S[i], b);
-            else
-                assertNotSame(S[i], b);
-        }
-    }
-}
diff --git a/jetty-io/src/test/java/org/eclipse/jetty/io/BufferTest.java b/jetty-io/src/test/java/org/eclipse/jetty/io/BufferTest.java
deleted file mode 100644
index 9e75c12..0000000
--- a/jetty-io/src/test/java/org/eclipse/jetty/io/BufferTest.java
+++ /dev/null
@@ -1,265 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.io;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import java.io.File;
-
-import org.eclipse.jetty.io.nio.DirectNIOBuffer;
-import org.eclipse.jetty.io.nio.IndirectNIOBuffer;
-import org.eclipse.jetty.io.nio.RandomAccessFileBuffer;
-import org.eclipse.jetty.util.StringUtil;
-import org.junit.Before;
-import org.junit.Test;
-
-/**
- *
- */
-public class BufferTest
-{
-    private Buffer[] buffer;
-
-    @Before
-    public void init() throws Exception
-    {
-        File file = File.createTempFile("test",".buf");
-        file.deleteOnExit();
-
-        buffer=new Buffer[]{
-          new RandomAccessFileBuffer(file,10),
-          new ByteArrayBuffer(10),
-          new IndirectNIOBuffer(10),
-          new DirectNIOBuffer(10)
-        };
-    }
-
-    @Test
-    public void testBuffer() throws Exception
-    {
-        for (int i=0;i<buffer.length;i++)
-        {
-            String t="t"+i;
-            Buffer b = buffer[i];
-
-            assertEquals(t,0,b.length());
-            assertEquals(t,10,b.capacity());
-            assertEquals(t,10,b.space());
-
-            b.put((byte)0);
-            b.put((byte)1);
-            b.put((byte)2);
-            assertEquals(t,3,b.length());
-            assertEquals(t,10,b.capacity());
-            assertEquals(t,7,b.space());
-
-            assertEquals(t,0,b.get());
-            assertEquals(t,1,b.get());
-            assertEquals(t,1,b.length());
-            assertEquals(t,10,b.capacity());
-            assertEquals(t,7,b.space());
-            b.compact();
-            assertEquals(t,9,b.space());
-
-            byte[] ba = { (byte)-1, (byte)3,(byte)4,(byte)5,(byte)6 };
-
-            b.put(ba,1,3);
-            assertEquals(t,4,b.length());
-            assertEquals(t,6,b.space());
-
-            byte[] bg = new byte[4];
-            b.get(bg,1,2);
-            assertEquals(t,2,bg[1]);
-            assertEquals(t,3,bg[2]);
-
-            //test getting 0 bytes returns 0
-            int count = b.get(bg,0,0);
-            assertEquals(t,0, count);
-
-            //read up to end
-            count = b.get(bg,0,2);
-            assertEquals(t, 2, count);
-
-            //test reading past end returns -1
-            count = b.get(bg,0,1);
-            assertEquals(t, -1, count);
-        }
-    }
-
-    @Test
-    public void testHash() throws Exception
-    {
-        Buffer[] b=
-        {
-                new ByteArrayBuffer("Test1234 "),
-                new ByteArrayBuffer("tEST1234 "),
-                new DirectNIOBuffer(4096),
-        };
-        b[2].put("TeSt1234 ".getBytes(StringUtil.__UTF8));
-
-        for (int i=0;i<b.length;i++)
-            assertEquals("t"+i,b[0].hashCode(),b[i].hashCode());
-    }
-
-    @Test
-    public void testGet () throws Exception
-    {
-        Buffer buff = new ByteArrayBuffer(new byte[]{(byte)0,(byte)1,(byte)2,(byte)3,(byte)4,(byte)5});
-
-        byte[] readbuff = new byte[2];
-
-        int count = buff.get(readbuff, 0, 2);
-        assertEquals(2, count);
-        assertEquals(readbuff[0], (byte)0);
-        assertEquals(readbuff[1], (byte)1);
-
-        count = buff.get(readbuff, 0, 2);
-        assertEquals(2, count);
-        assertEquals(readbuff[0], (byte)2);
-        assertEquals(readbuff[1], (byte)3);
-
-        count = buff.get(readbuff, 0, 0);
-        assertEquals(0, count);
-
-        readbuff[0]=(byte)9;
-        readbuff[1]=(byte)9;
-
-        count = buff.get(readbuff, 0, 2);
-        assertEquals(2, count);
-
-        count = buff.get(readbuff, 0, 2);
-        assertEquals(-1, count);
-
-    }
-
-    @Test
-    public void testInsensitive()
-    {
-        Buffer cs0 = new ByteArrayBuffer("Test 1234");
-        Buffer cs1 = new ByteArrayBuffer("Test 1234");
-        Buffer cs2 = new ByteArrayBuffer("tEst 1234");
-        Buffer cs3 = new ByteArrayBuffer("Other    ");
-        Buffer ci0 = new ByteArrayBuffer.CaseInsensitive("Test 1234");
-        Buffer ci1 = new ByteArrayBuffer.CaseInsensitive("Test 1234");
-        Buffer ci2 = new ByteArrayBuffer.CaseInsensitive("tEst 1234");
-        Buffer ci3 = new ByteArrayBuffer.CaseInsensitive("oTher    ");
-
-        assertTrue( cs0.equals(cs0));
-        assertTrue( cs0.equals(cs1));
-        assertTrue(!cs0.equals(cs2));
-        assertTrue(!cs0.equals(cs3));
-        assertTrue( cs0.equals(ci0));
-        assertTrue( cs0.equals(ci1));
-        assertTrue( cs0.equals(ci2));
-        assertTrue(!cs0.equals(ci3));
-
-        assertTrue( cs1.equals(cs0));
-        assertTrue( cs1.equals(cs1));
-        assertTrue(!cs1.equals(cs2));
-        assertTrue(!cs1.equals(cs3));
-        assertTrue( cs1.equals(ci0));
-        assertTrue( cs1.equals(ci1));
-        assertTrue( cs1.equals(ci2));
-        assertTrue(!cs1.equals(ci3));
-
-        assertTrue(!cs2.equals(cs0));
-        assertTrue(!cs2.equals(cs1));
-        assertTrue( cs2.equals(cs2));
-        assertTrue(!cs2.equals(cs3));
-        assertTrue( cs2.equals(ci0));
-        assertTrue( cs2.equals(ci1));
-        assertTrue( cs2.equals(ci2));
-        assertTrue(!cs2.equals(ci3));
-
-        assertTrue(!cs3.equals(cs0));
-        assertTrue(!cs3.equals(cs1));
-        assertTrue(!cs3.equals(cs2));
-        assertTrue( cs3.equals(cs3));
-        assertTrue(!cs3.equals(ci0));
-        assertTrue(!cs3.equals(ci1));
-        assertTrue(!cs3.equals(ci2));
-        assertTrue( cs3.equals(ci3));
-
-
-        assertTrue( ci0.equals(cs0));
-        assertTrue( ci0.equals(cs1));
-        assertTrue( ci0.equals(cs2));
-        assertTrue(!ci0.equals(cs3));
-        assertTrue( ci0.equals(ci0));
-        assertTrue( ci0.equals(ci1));
-        assertTrue( ci0.equals(ci2));
-        assertTrue(!ci0.equals(ci3));
-
-        assertTrue( ci1.equals(cs0));
-        assertTrue( ci1.equals(cs1));
-        assertTrue( ci1.equals(cs2));
-        assertTrue(!ci1.equals(cs3));
-        assertTrue( ci1.equals(ci0));
-        assertTrue( ci1.equals(ci1));
-        assertTrue( ci1.equals(ci2));
-        assertTrue(!ci1.equals(ci3));
-
-        assertTrue( ci2.equals(cs0));
-        assertTrue( ci2.equals(cs1));
-        assertTrue( ci2.equals(cs2));
-        assertTrue(!ci2.equals(cs3));
-        assertTrue( ci2.equals(ci0));
-        assertTrue( ci2.equals(ci1));
-        assertTrue( ci2.equals(ci2));
-        assertTrue(!ci2.equals(ci3));
-
-        assertTrue(!ci3.equals(cs0));
-        assertTrue(!ci3.equals(cs1));
-        assertTrue(!ci3.equals(cs2));
-        assertTrue( ci3.equals(cs3));
-        assertTrue(!ci3.equals(ci0));
-        assertTrue(!ci3.equals(ci1));
-        assertTrue(!ci3.equals(ci2));
-        assertTrue( ci3.equals(ci3));
-
-    }
-
-    @Test
-    public void testView()
-    {
-        Buffer b = new ByteArrayBuffer(" Test 1234 ".getBytes());
-        b.setGetIndex(b.getIndex()+1);
-        b.setPutIndex(b.putIndex()-1);
-        View v0 = new View(b);
-        View v1 = new View(b);
-        View v2 = new View(v0);
-
-        String s=b.toString();
-        String s0=v0.toString();
-        String s1=v1.toString();
-        String s2=v2.toString();
-        String s3=v0.toString();
-        String s4=v1.toString();
-        String s5=v2.toString();
-
-        assertEquals(s, s0);
-        assertEquals(s0, s1);
-        assertEquals(s1, s2);
-        assertEquals(s2, s3);
-        assertEquals(s3, s4);
-        assertEquals(s4, s5);
-
-    }
-}
diff --git a/jetty-io/src/test/java/org/eclipse/jetty/io/BufferUtilTest.java b/jetty-io/src/test/java/org/eclipse/jetty/io/BufferUtilTest.java
deleted file mode 100644
index 8e4442d..0000000
--- a/jetty-io/src/test/java/org/eclipse/jetty/io/BufferUtilTest.java
+++ /dev/null
@@ -1,122 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.io;
-
-
-import static org.junit.Assert.assertEquals;
-
-import org.junit.Test;
-
-/**
- *
- */
-public class BufferUtilTest
-{
-    @Test
-    public void testToInt() throws Exception
-    {
-        Buffer buf[] =
-        {
-            new ByteArrayBuffer("0"),
-            new ByteArrayBuffer(" 42 "),
-            new ByteArrayBuffer("   43abc"),
-            new ByteArrayBuffer("-44"),
-            new ByteArrayBuffer(" - 45;"),
-            new ByteArrayBuffer("-2147483648"),
-            new ByteArrayBuffer("2147483647"),
-        };
-
-        int val[] =
-        {
-            0,42,43,-44,-45,-2147483648,2147483647
-        };
-
-        for (int i=0;i<buf.length;i++)
-            assertEquals("t"+i, val[i], BufferUtil.toInt(buf[i]));
-    }
-
-    @Test
-    public void testPutInt() throws Exception
-    {
-        int val[] =
-        {
-            0,42,43,-44,-45,Integer.MIN_VALUE,Integer.MAX_VALUE
-        };
-
-        String str[] =
-        {
-            "0","42","43","-44","-45",""+Integer.MIN_VALUE,""+Integer.MAX_VALUE
-        };
-
-        Buffer buffer = new ByteArrayBuffer(12);
-
-        for (int i=0;i<val.length;i++)
-        {
-            buffer.clear();
-            BufferUtil.putDecInt(buffer,val[i]);
-            assertEquals("t"+i,str[i],BufferUtil.to8859_1_String(buffer));
-        }
-    }
-
-    @Test
-    public void testPutLong() throws Exception
-    {
-        long val[] =
-        {
-                0L,42L,43L,-44L,-45L,Long.MIN_VALUE,Long.MAX_VALUE
-        };
-
-        String str[] =
-        {
-                "0","42","43","-44","-45",""+Long.MIN_VALUE,""+Long.MAX_VALUE
-        };
-
-        Buffer buffer = new ByteArrayBuffer(50);
-
-        for (int i=0;i<val.length;i++)
-        {
-            buffer.clear();
-            BufferUtil.putDecLong(buffer,val[i]);
-            assertEquals("t"+i,str[i],BufferUtil.to8859_1_String(buffer));
-        }
-    }
-
-    @Test
-    public void testPutHexInt() throws Exception
-    {
-        int val[] =
-        {
-            0,42,43,-44,-45,-2147483648,2147483647
-        };
-
-        String str[] =
-        {
-            "0","2A","2B","-2C","-2D","-80000000","7FFFFFFF"
-        };
-
-        Buffer buffer = new ByteArrayBuffer(12);
-
-        for (int i=0;i<val.length;i++)
-        {
-            buffer.clear();
-            BufferUtil.putHexInt(buffer,val[i]);
-            assertEquals("t"+i,str[i],BufferUtil.to8859_1_String(buffer));
-        }
-    }
-}
diff --git a/jetty-io/src/test/java/org/eclipse/jetty/io/ByteArrayEndPointTest.java b/jetty-io/src/test/java/org/eclipse/jetty/io/ByteArrayEndPointTest.java
new file mode 100644
index 0000000..c10c9a5
--- /dev/null
+++ b/jetty-io/src/test/java/org/eclipse/jetty/io/ByteArrayEndPointTest.java
@@ -0,0 +1,338 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import static org.hamcrest.Matchers.*;
+import static org.hamcrest.Matchers.instanceOf;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+
+import org.eclipse.jetty.toolchain.test.AdvancedRunner;
+import org.eclipse.jetty.toolchain.test.annotation.Slow;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.FutureCallback;
+import org.eclipse.jetty.util.thread.Scheduler;
+import org.eclipse.jetty.util.thread.TimerScheduler;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+ at RunWith(AdvancedRunner.class)
+public class ByteArrayEndPointTest
+{
+    private Scheduler _scheduler;
+
+    @Before
+    public void before() throws Exception
+    {
+        _scheduler = new TimerScheduler();
+        _scheduler.start();
+    }
+
+    @After
+    public void after() throws Exception
+    {
+        _scheduler.stop();
+    }
+
+    @Test
+    public void testFill() throws Exception
+    {
+        ByteArrayEndPoint endp = new ByteArrayEndPoint();
+        endp.setInput("test input");
+
+        ByteBuffer buffer = BufferUtil.allocate(1024);
+
+        assertEquals(10,endp.fill(buffer));
+        assertEquals("test input",BufferUtil.toString(buffer));
+
+        assertEquals(0,endp.fill(buffer));
+
+        endp.setInput(" more");
+        assertEquals(5,endp.fill(buffer));
+        assertEquals("test input more",BufferUtil.toString(buffer));
+
+        assertEquals(0,endp.fill(buffer));
+
+        endp.setInput((ByteBuffer)null);
+
+        assertEquals(-1,endp.fill(buffer));
+
+        endp.close();
+
+        try
+        {
+            endp.fill(buffer);
+            fail();
+        }
+        catch(IOException e)
+        {
+            assertThat(e.getMessage(),containsString("CLOSED"));
+        }
+
+        endp.reset();
+        endp.setInput("and more");
+        buffer = BufferUtil.allocate(4);
+
+        assertEquals(4,endp.fill(buffer));
+        assertEquals("and ",BufferUtil.toString(buffer));
+        assertEquals(0,endp.fill(buffer));
+        BufferUtil.clear(buffer);
+        assertEquals(4,endp.fill(buffer));
+        assertEquals("more",BufferUtil.toString(buffer));
+
+    }
+
+    @Test
+    public void testGrowingFlush() throws Exception
+    {
+        ByteArrayEndPoint endp = new ByteArrayEndPoint((byte[])null,15);
+        endp.setGrowOutput(true);
+
+        assertEquals(true,endp.flush(BufferUtil.toBuffer("some output")));
+        assertEquals("some output",endp.getOutputString());
+
+        assertEquals(true,endp.flush(BufferUtil.toBuffer(" some more")));
+        assertEquals("some output some more",endp.getOutputString());
+
+        assertEquals(true,endp.flush());
+        assertEquals("some output some more",endp.getOutputString());
+
+        assertEquals(true,endp.flush(BufferUtil.EMPTY_BUFFER));
+        assertEquals("some output some more",endp.getOutputString());
+
+        assertEquals(true,endp.flush(BufferUtil.EMPTY_BUFFER,BufferUtil.toBuffer(" and"),BufferUtil.toBuffer(" more")));
+        assertEquals("some output some more and more",endp.getOutputString());
+        endp.close();
+    }
+
+    @Test
+    public void testFlush() throws Exception
+    {
+        ByteArrayEndPoint endp = new ByteArrayEndPoint((byte[])null,15);
+        endp.setGrowOutput(false);
+        endp.setOutput(BufferUtil.allocate(10));
+
+        ByteBuffer data = BufferUtil.toBuffer("Some more data.");
+        assertEquals(false,endp.flush(data));
+        assertEquals("Some more ",endp.getOutputString());
+        assertEquals("data.",BufferUtil.toString(data));
+
+        assertEquals("Some more ",endp.takeOutputString());
+
+        assertEquals(true,endp.flush(data));
+        assertEquals("data.",BufferUtil.toString(endp.takeOutput()));
+        endp.close();
+    }
+
+
+    @Test
+    public void testReadable() throws Exception
+    {
+        ByteArrayEndPoint endp = new ByteArrayEndPoint(_scheduler, 5000);
+        endp.setInput("test input");
+
+        ByteBuffer buffer = BufferUtil.allocate(1024);
+        FutureCallback fcb = new FutureCallback();
+
+        endp.fillInterested(fcb);
+        assertTrue(fcb.isDone());
+        assertEquals(null, fcb.get());
+        assertEquals(10, endp.fill(buffer));
+        assertEquals("test input", BufferUtil.toString(buffer));
+
+        fcb = new FutureCallback();
+        endp.fillInterested(fcb);
+        assertFalse(fcb.isDone());
+        assertEquals(0, endp.fill(buffer));
+
+        endp.setInput(" more");
+        assertTrue(fcb.isDone());
+        assertEquals(null, fcb.get());
+        assertEquals(5, endp.fill(buffer));
+        assertEquals("test input more", BufferUtil.toString(buffer));
+
+        fcb = new FutureCallback();
+        endp.fillInterested(fcb);
+        assertFalse(fcb.isDone());
+        assertEquals(0, endp.fill(buffer));
+
+        endp.setInput((ByteBuffer)null);
+        assertTrue(fcb.isDone());
+        assertEquals(null, fcb.get());
+        assertEquals(-1, endp.fill(buffer));
+
+        fcb = new FutureCallback();
+        endp.fillInterested(fcb);
+        assertTrue(fcb.isDone());
+        assertEquals(null, fcb.get());
+        assertEquals(-1, endp.fill(buffer));
+
+        endp.close();
+
+        fcb = new FutureCallback();
+        endp.fillInterested(fcb);
+        assertTrue(fcb.isDone());
+        try
+        {
+            fcb.get();
+            fail();
+        }
+        catch (ExecutionException e)
+        {
+            assertThat(e.toString(), containsString("Closed"));
+        }
+    }
+
+    @Test
+    public void testWrite() throws Exception
+    {
+        ByteArrayEndPoint endp = new ByteArrayEndPoint(_scheduler, 5000, (byte[])null, 15);
+        endp.setGrowOutput(false);
+        endp.setOutput(BufferUtil.allocate(10));
+
+        ByteBuffer data = BufferUtil.toBuffer("Data.");
+        ByteBuffer more = BufferUtil.toBuffer(" Some more.");
+
+        FutureCallback fcb = new FutureCallback();
+        endp.write( fcb, data);
+        assertTrue(fcb.isDone());
+        assertEquals(null, fcb.get());
+        assertEquals("Data.", endp.getOutputString());
+
+        fcb = new FutureCallback();
+        endp.write(fcb, more);
+        assertFalse(fcb.isDone());
+
+        assertEquals("Data. Some", endp.getOutputString());
+        assertEquals("Data. Some", endp.takeOutputString());
+
+        assertTrue(fcb.isDone());
+        assertEquals(null, fcb.get());
+        assertEquals(" more.", endp.getOutputString());
+        endp.close();
+    }
+    
+    /**
+     * Simulate AbstractConnection.ReadCallback.failed()
+     */
+    public static class Closer extends FutureCallback
+    {
+        private EndPoint endp;
+
+        public Closer(EndPoint endp)
+        {
+            this.endp = endp;
+        }
+
+        @Override
+        public void failed(Throwable cause)
+        {
+            endp.close();
+            super.failed(cause);
+        }
+    }
+
+    @Slow
+    @Test
+    public void testIdle() throws Exception
+    {
+        long idleTimeout = 500;
+        ByteArrayEndPoint endp = new ByteArrayEndPoint(_scheduler, idleTimeout);
+        endp.setInput("test");
+        endp.setGrowOutput(false);
+        endp.setOutput(BufferUtil.allocate(5));
+
+        // no idle check
+        assertTrue(endp.isOpen());
+        Thread.sleep(idleTimeout * 2);
+        assertTrue(endp.isOpen());
+
+        // normal read
+        ByteBuffer buffer = BufferUtil.allocate(1024);
+        FutureCallback fcb = new FutureCallback();
+
+        endp.fillInterested(fcb);
+        assertTrue(fcb.isDone());
+        assertEquals(null, fcb.get());
+        assertEquals(4, endp.fill(buffer));
+        assertEquals("test", BufferUtil.toString(buffer));
+
+        // read timeout
+        fcb = new FutureCallback();
+        endp.fillInterested(fcb);
+        long start = System.currentTimeMillis();
+        try
+        {
+            fcb.get();
+            fail();
+        }
+        catch (ExecutionException t)
+        {
+            assertThat(t.getCause(), instanceOf(TimeoutException.class));
+        }
+        assertThat(System.currentTimeMillis() - start, greaterThan(idleTimeout / 2));
+        assertThat("Endpoint open", endp.isOpen(), is(true));
+
+        // We need to delay the write timeout test below from the read timeout test above.
+        // The reason is that the scheduler thread that fails the endPoint WriteFlusher
+        // because of the read timeout above runs concurrently with the write below, and
+        // if it runs just after the write below, the test fails because the write callback
+        // below fails immediately rather than after the idle timeout.
+        Thread.sleep(idleTimeout / 2);
+
+        // write timeout
+        fcb = new FutureCallback();
+        endp.write(fcb, BufferUtil.toBuffer("This is too long"));
+        start = System.currentTimeMillis();
+        try
+        {
+            fcb.get();
+            fail();
+        }
+        catch (ExecutionException t)
+        {
+            assertThat(t.getCause(), instanceOf(TimeoutException.class));
+        }
+        assertThat(System.currentTimeMillis() - start, greaterThan(idleTimeout / 2));
+        assertThat("Endpoint open", endp.isOpen(), is(true));
+
+        endp.fillInterested(new Closer(endp));
+        
+        // Still no idle close (wait half the time)
+        Thread.sleep(idleTimeout / 2);
+        assertThat("Endpoint open", endp.isOpen(), is(true));
+
+        // shutdown out
+        endp.shutdownOutput();
+
+        // idle close (wait double the time)
+        Thread.sleep(idleTimeout * 2);
+        assertThat("Endpoint open", endp.isOpen(), is(false));
+    }
+}
diff --git a/jetty-io/src/test/java/org/eclipse/jetty/io/ChannelEndPointTest.java b/jetty-io/src/test/java/org/eclipse/jetty/io/ChannelEndPointTest.java
new file mode 100644
index 0000000..0a437ec
--- /dev/null
+++ b/jetty-io/src/test/java/org/eclipse/jetty/io/ChannelEndPointTest.java
@@ -0,0 +1,60 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.nio.channels.ServerSocketChannel;
+import java.nio.channels.SocketChannel;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+
+public class ChannelEndPointTest extends EndPointTest<ChannelEndPoint>
+{
+    static ServerSocketChannel connector;
+
+    @BeforeClass
+    public static void open() throws Exception
+    {
+        connector = ServerSocketChannel.open();
+        connector.socket().bind(null);
+    }
+
+    @AfterClass
+    public static void close() throws Exception
+    {
+        connector.close();
+        connector=null;
+    }
+
+    @Override
+    protected EndPointPair<ChannelEndPoint> newConnection() throws Exception
+    {
+        EndPointPair<ChannelEndPoint> c = new EndPointPair<>();
+
+        c.client=new ChannelEndPoint(null,SocketChannel.open(connector.socket().getLocalSocketAddress()));
+        c.server=new ChannelEndPoint(null,connector.accept());
+        return c;
+    }
+
+    @Override
+    public void testClientServerExchange() throws Exception
+    {
+        super.testClientServerExchange();
+    }
+}
diff --git a/jetty-io/src/test/java/org/eclipse/jetty/io/EndPointTest.java b/jetty-io/src/test/java/org/eclipse/jetty/io/EndPointTest.java
index 3cd244b..613c087 100644
--- a/jetty-io/src/test/java/org/eclipse/jetty/io/EndPointTest.java
+++ b/jetty-io/src/test/java/org/eclipse/jetty/io/EndPointTest.java
@@ -22,7 +22,9 @@ import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
-import org.eclipse.jetty.io.nio.IndirectNIOBuffer;
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.BufferUtil;
 import org.junit.Test;
 
 public abstract class EndPointTest<T extends EndPoint>
@@ -32,144 +34,124 @@ public abstract class EndPointTest<T extends EndPoint>
         public T client;
         public T server;
     }
-    
+
     protected abstract EndPointPair<T> newConnection() throws Exception;
-   
+
 
     @Test
     public void testClientServerExchange() throws Exception
     {
         EndPointPair<T> c = newConnection();
-        Buffer buffer = new IndirectNIOBuffer(4096);
-        
+        ByteBuffer buffer = BufferUtil.allocate(4096);
+
         // Client sends a request
-        c.client.flush(new ByteArrayBuffer("request"));
-        
+        c.client.flush(BufferUtil.toBuffer("request"));
+
         // Server receives the request
         int len = c.server.fill(buffer);
         assertEquals(7,len);
-        assertEquals("request",buffer.toString());
+        assertEquals("request",BufferUtil.toString(buffer));
 
         // Client and server are open
         assertTrue(c.client.isOpen());
-        assertFalse(c.client.isInputShutdown());
         assertFalse(c.client.isOutputShutdown());
         assertTrue(c.server.isOpen());
-        assertFalse(c.server.isInputShutdown());
         assertFalse(c.server.isOutputShutdown());
-        
+
         // Server sends response and closes output
-        c.server.flush(new ByteArrayBuffer("response"));
+        c.server.flush(BufferUtil.toBuffer("response"));
         c.server.shutdownOutput();
-        
+
         // client server are open, server is oshut
         assertTrue(c.client.isOpen());
-        assertFalse(c.client.isInputShutdown());
         assertFalse(c.client.isOutputShutdown());
         assertTrue(c.server.isOpen());
-        assertFalse(c.server.isInputShutdown());
         assertTrue(c.server.isOutputShutdown());
-        
+
         // Client reads response
-        buffer.clear();
+        BufferUtil.clear(buffer);
         len = c.client.fill(buffer);
         assertEquals(8,len);
-        assertEquals("response",buffer.toString());
+        assertEquals("response",BufferUtil.toString(buffer));
 
         // Client and server are open, server is oshut
         assertTrue(c.client.isOpen());
-        assertFalse(c.client.isInputShutdown());
         assertFalse(c.client.isOutputShutdown());
         assertTrue(c.server.isOpen());
-        assertFalse(c.server.isInputShutdown());
         assertTrue(c.server.isOutputShutdown());
-        
+
         // Client reads -1
-        buffer.clear();
+        BufferUtil.clear(buffer);
         len = c.client.fill(buffer);
         assertEquals(-1,len);
 
         // Client and server are open, server is oshut, client is ishut
         assertTrue(c.client.isOpen());
-        assertTrue(c.client.isInputShutdown());
         assertFalse(c.client.isOutputShutdown());
         assertTrue(c.server.isOpen());
-        assertFalse(c.server.isInputShutdown());
         assertTrue(c.server.isOutputShutdown());
-        
+
         // Client shutsdown output, which is a close because already ishut
         c.client.shutdownOutput();
 
         // Client is closed. Server is open and oshut
         assertFalse(c.client.isOpen());
-        assertTrue(c.client.isInputShutdown());
         assertTrue(c.client.isOutputShutdown());
         assertTrue(c.server.isOpen());
-        assertFalse(c.server.isInputShutdown());
         assertTrue(c.server.isOutputShutdown());
 
         // Server reads close
-        buffer.clear();
+        BufferUtil.clear(buffer);
         len = c.server.fill(buffer);
         assertEquals(-1,len);
 
         // Client and Server are closed
         assertFalse(c.client.isOpen());
-        assertTrue(c.client.isInputShutdown());
         assertTrue(c.client.isOutputShutdown());
         assertFalse(c.server.isOpen());
-        assertTrue(c.server.isInputShutdown());
         assertTrue(c.server.isOutputShutdown());
-        
+
     }
-    
+
 
 
     @Test
     public void testClientClose() throws Exception
     {
         EndPointPair<T> c = newConnection();
-        Buffer buffer = new IndirectNIOBuffer(4096);
-        
-        c.client.flush(new ByteArrayBuffer("request"));
+        ByteBuffer buffer = BufferUtil.allocate(4096);
+
+        c.client.flush(BufferUtil.toBuffer("request"));
         int len = c.server.fill(buffer);
         assertEquals(7,len);
-        assertEquals("request",buffer.toString());
+        assertEquals("request",BufferUtil.toString(buffer));
 
         assertTrue(c.client.isOpen());
-        assertFalse(c.client.isInputShutdown());
         assertFalse(c.client.isOutputShutdown());
         assertTrue(c.server.isOpen());
-        assertFalse(c.server.isInputShutdown());
-        assertFalse(c.server.isOutputShutdown());        
-        
+        assertFalse(c.server.isOutputShutdown());
+
         c.client.close();
 
         assertFalse(c.client.isOpen());
-        assertTrue(c.client.isInputShutdown());
         assertTrue(c.client.isOutputShutdown());
         assertTrue(c.server.isOpen());
-        assertFalse(c.server.isInputShutdown());
-        assertFalse(c.server.isOutputShutdown());  
-        
+        assertFalse(c.server.isOutputShutdown());
+
         len = c.server.fill(buffer);
         assertEquals(-1,len);
 
         assertFalse(c.client.isOpen());
-        assertTrue(c.client.isInputShutdown());
         assertTrue(c.client.isOutputShutdown());
         assertTrue(c.server.isOpen());
-        assertTrue(c.server.isInputShutdown());
-        assertFalse(c.server.isOutputShutdown());  
-        
+        assertFalse(c.server.isOutputShutdown());
+
         c.server.shutdownOutput();
 
         assertFalse(c.client.isOpen());
-        assertTrue(c.client.isInputShutdown());
         assertTrue(c.client.isOutputShutdown());
         assertFalse(c.server.isOpen());
-        assertTrue(c.server.isInputShutdown());
-        assertTrue(c.server.isOutputShutdown());  
-    }   
-    
+        assertTrue(c.server.isOutputShutdown());
+    }
+
 }
diff --git a/jetty-io/src/test/java/org/eclipse/jetty/io/IOTest.java b/jetty-io/src/test/java/org/eclipse/jetty/io/IOTest.java
index 8a2bb5b..573d1b0 100644
--- a/jetty-io/src/test/java/org/eclipse/jetty/io/IOTest.java
+++ b/jetty-io/src/test/java/org/eclipse/jetty/io/IOTest.java
@@ -25,140 +25,169 @@ import static org.junit.Assert.fail;
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
+import java.io.File;
 import java.io.IOException;
+import java.net.InetSocketAddress;
 import java.net.ServerSocket;
 import java.net.Socket;
 import java.net.SocketException;
+import java.nio.ByteBuffer;
+import java.nio.channels.AsynchronousServerSocketChannel;
+import java.nio.channels.AsynchronousSocketChannel;
+import java.nio.channels.FileChannel;
 import java.nio.channels.ServerSocketChannel;
 import java.nio.channels.SocketChannel;
-
-import junit.framework.Assert;
-
+import java.nio.charset.StandardCharsets;
+import java.nio.file.StandardOpenOption;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.io.ClientConnectionFactory.Helper;
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
 import org.eclipse.jetty.toolchain.test.OS;
+import org.eclipse.jetty.util.BufferUtil;
 import org.eclipse.jetty.util.IO;
+import org.junit.Assert;
 import org.junit.Test;
 
-/**
- *
- */
 public class IOTest
 {
     @Test
-    public void testIO() throws InterruptedException
+    public void testIO() throws Exception
     {
         // Only a little test
-        ByteArrayInputStream in = new ByteArrayInputStream
-            ("The quick brown fox jumped over the lazy dog".getBytes());
+        ByteArrayInputStream in = new ByteArrayInputStream("The quick brown fox jumped over the lazy dog".getBytes());
         ByteArrayOutputStream out = new ByteArrayOutputStream();
 
-        IO.copyThread(in,out);
-        Thread.sleep(1500);
-        // System.err.println(out);
+        IO.copy(in, out);
 
-        assertEquals( "copyThread",
-                      out.toString(),
-                      "The quick brown fox jumped over the lazy dog");
+        assertEquals("copyThread", out.toString(), "The quick brown fox jumped over the lazy dog");
     }
-    
+
     @Test
     public void testHalfClose() throws Exception
     {
         ServerSocket connector = new ServerSocket(0);
-        
-        Socket client = new Socket("localhost",connector.getLocalPort());
+
+        Socket client = new Socket("localhost", connector.getLocalPort());
         Socket server = connector.accept();
-        
+
         // we can write both ways
         client.getOutputStream().write(1);
-        assertEquals(1,server.getInputStream().read());
+        assertEquals(1, server.getInputStream().read());
         server.getOutputStream().write(1);
-        assertEquals(1,client.getInputStream().read());
-        
+        assertEquals(1, client.getInputStream().read());
+
         // shutdown output results in read -1
         client.shutdownOutput();
-        assertEquals(-1,server.getInputStream().read());
-        
-        // Even though EOF has been read, the server input is not seen as shutdown 
+        assertEquals(-1, server.getInputStream().read());
+
+        // Even though EOF has been read, the server input is not seen as shutdown
         assertFalse(server.isInputShutdown());
-        
+
         // and we can read -1 again
-        assertEquals(-1,server.getInputStream().read());
+        assertEquals(-1, server.getInputStream().read());
 
         // but cannot write
-        try { client.getOutputStream().write(1); fail("exception expected"); } catch (SocketException e) {}
-   
+        try
+        {
+            client.getOutputStream().write(1);
+            fail("exception expected");
+        }
+        catch (SocketException e)
+        {
+        }
+
         // but can still write in opposite direction.
         server.getOutputStream().write(1);
-        assertEquals(1,client.getInputStream().read());
-   
-        
+        assertEquals(1, client.getInputStream().read());
+
         // server can shutdown input to match the shutdown out of client
         server.shutdownInput();
-        
+
         // now we EOF instead of reading -1
-        try { server.getInputStream().read(); fail("exception expected"); } catch (SocketException e) {}
-        
+        try
+        {
+            server.getInputStream().read();
+            fail("exception expected");
+        }
+        catch (SocketException e)
+        {
+        }
 
         // but can still write in opposite direction.
         server.getOutputStream().write(1);
-        assertEquals(1,client.getInputStream().read());
-        
+        assertEquals(1, client.getInputStream().read());
+
         // client can shutdown input
         client.shutdownInput();
 
         // now we EOF instead of reading -1
-        try { client.getInputStream().read(); fail("exception expected"); } catch (SocketException e) {}        
-        
-        // But we can still write at the server (data which will never be read) 
+        try
+        {
+            client.getInputStream().read();
+            fail("exception expected");
+        }
+        catch (SocketException e)
+        {
+        }
+
+        // But we can still write at the server (data which will never be read)
         server.getOutputStream().write(1);
-        
+
         // and the server output is not shutdown
-        assertFalse( server.isOutputShutdown() );
-        
+        assertFalse(server.isOutputShutdown());
+
         // until we explictly shut it down
         server.shutdownOutput();
-        
+
         // and now we can't write
-        try { server.getOutputStream().write(1); fail("exception expected"); } catch (SocketException e) {}
-        
+        try
+        {
+            server.getOutputStream().write(1);
+            fail("exception expected");
+        }
+        catch (SocketException e)
+        {
+        }
+
         // but the sockets are still open
         assertFalse(client.isClosed());
         assertFalse(server.isClosed());
-        
+
         // but if we close one end
         client.close();
 
         // it is seen as closed.
         assertTrue(client.isClosed());
-        
+
         // but not the other end
         assertFalse(server.isClosed());
-        
+
         // which has to be closed explictly
         server.close();
         assertTrue(server.isClosed());
-            
     }
 
-    
     @Test
     public void testHalfCloseClientServer() throws Exception
     {
         ServerSocketChannel connector = ServerSocketChannel.open();
         connector.socket().bind(null);
-        
+
         Socket client = SocketChannel.open(connector.socket().getLocalSocketAddress()).socket();
         client.setSoTimeout(1000);
-        client.setSoLinger(false,-1);
+        client.setSoLinger(false, -1);
         Socket server = connector.accept().socket();
         server.setSoTimeout(1000);
-        server.setSoLinger(false,-1);
-        
+        server.setSoLinger(false, -1);
+
         // Write from client to server
         client.getOutputStream().write(1);
-        
-        // Server reads 
-        assertEquals(1,server.getInputStream().read());
+
+        // Server reads
+        assertEquals(1, server.getInputStream().read());
 
         // Write from server to client with oshut
         server.getOutputStream().write(1);
@@ -166,12 +195,12 @@ public class IOTest
         server.shutdownOutput();
 
         // Client reads response
-        assertEquals(1,client.getInputStream().read());
+        assertEquals(1, client.getInputStream().read());
 
         try
         {
             // Client reads -1 and does ishut
-            assertEquals(-1,client.getInputStream().read());
+            assertEquals(-1, client.getInputStream().read());
             assertFalse(client.isInputShutdown());
             //System.err.println("ISHUT "+client);
             client.shutdownInput();
@@ -183,7 +212,7 @@ public class IOTest
             client.close();
 
             // Server reads -1, does ishut and then close
-            assertEquals(-1,server.getInputStream().read());
+            assertEquals(-1, server.getInputStream().read());
             assertFalse(server.isInputShutdown());
             //System.err.println("ISHUT "+server);
 
@@ -191,7 +220,7 @@ public class IOTest
             {
                 server.shutdownInput();
             }
-            catch(SocketException e)
+            catch (SocketException e)
             {
                 // System.err.println(e);
             }
@@ -199,7 +228,7 @@ public class IOTest
             server.close();
 
         }
-        catch(Exception e)
+        catch (Exception e)
         {
             System.err.println(e);
             assertTrue(OS.IS_OSX);
@@ -211,19 +240,19 @@ public class IOTest
     {
         ServerSocketChannel connector = ServerSocketChannel.open();
         connector.socket().bind(null);
-        
+
         Socket client = SocketChannel.open(connector.socket().getLocalSocketAddress()).socket();
         client.setSoTimeout(1000);
-        client.setSoLinger(false,-1);
+        client.setSoLinger(false, -1);
         Socket server = connector.accept().socket();
         server.setSoTimeout(1000);
-        server.setSoLinger(false,-1);
-        
+        server.setSoLinger(false, -1);
+
         // Write from client to server
         client.getOutputStream().write(1);
-        
-        // Server reads 
-        assertEquals(1,server.getInputStream().read());
+
+        // Server reads
+        assertEquals(1, server.getInputStream().read());
 
         // Write from server to client with oshut
         server.getOutputStream().write(1);
@@ -233,39 +262,39 @@ public class IOTest
         try
         {
             // Client reads response
-            assertEquals(1,client.getInputStream().read());
+            assertEquals(1, client.getInputStream().read());
 
-            // Client reads -1 
-            assertEquals(-1,client.getInputStream().read());
+            // Client reads -1
+            assertEquals(-1, client.getInputStream().read());
             assertFalse(client.isInputShutdown());
 
             // Client can still write as we are half closed
             client.getOutputStream().write(1);
 
-            // Server can still read 
-            assertEquals(1,server.getInputStream().read());
+            // Server can still read
+            assertEquals(1, server.getInputStream().read());
 
-            // Server now closes 
+            // Server now closes
             server.close();
 
             // Client still reads -1 (not broken pipe !!)
-            assertEquals(-1,client.getInputStream().read());
+            assertEquals(-1, client.getInputStream().read());
             assertFalse(client.isInputShutdown());
 
             Thread.sleep(100);
 
             // Client still reads -1 (not broken pipe !!)
-            assertEquals(-1,client.getInputStream().read());
+            assertEquals(-1, client.getInputStream().read());
             assertFalse(client.isInputShutdown());
 
             // Client can still write data even though server is closed???
             client.getOutputStream().write(1);
 
             // Client eventually sees Broken Pipe
-            int i=0;
+            int i = 0;
             try
             {
-                for (i=0;i<100000;i++)
+                for (i = 0; i < 100000; i++)
                     client.getOutputStream().write(1);
 
                 Assert.fail();
@@ -284,44 +313,179 @@ public class IOTest
     }
 
     @Test
-    public void testReset() throws Exception
+    public void testServerChannelInterrupt() throws Exception
     {
-        ServerSocket connector;
-        Socket client;
-        Socket server;
-       
-        connector = new ServerSocket(0);
-        client = new Socket("127.0.0.1",connector.getLocalPort());
-        server = connector.accept();
-        client.setTcpNoDelay(true);
-        client.setSoLinger(true,0);
-        server.setTcpNoDelay(true);
-        server.setSoLinger(true,0);
-       
+        final ServerSocketChannel connector = ServerSocketChannel.open();
+        connector.configureBlocking(true);
+        connector.socket().bind(null);
+
+        Socket client = SocketChannel.open(connector.socket().getLocalSocketAddress()).socket();
+        client.setSoTimeout(2000);
+        client.setSoLinger(false, -1);
+        Socket server = connector.accept().socket();
+        server.setSoTimeout(2000);
+        server.setSoLinger(false, -1);
+
+        // Write from client to server
         client.getOutputStream().write(1);
-        assertEquals(1,server.getInputStream().read());
+        // Server reads
+        assertEquals(1, server.getInputStream().read());
+
+        // Write from server to client
         server.getOutputStream().write(1);
-        assertEquals(1,client.getInputStream().read());
-       
-        // Server generator shutdowns output after non persistent sending response.
-        server.shutdownOutput();
-       
-        // client endpoint reads EOF and shutdown input as result
-        assertEquals(-1,client.getInputStream().read());
-        client.shutdownInput();
-       
-        // client connection see's EOF and shutsdown output as no more requests to be sent.
-        client.shutdownOutput();
-       
-        // Since input already shutdown, client also closes socket.
+        // Client reads
+        assertEquals(1, client.getInputStream().read());
+
+
+        // block a thread in accept
+        final CountDownLatch alatch=new CountDownLatch(2);
+        Thread acceptor = new Thread()
+        {
+            @Override
+            public void run()
+            {
+                try
+                {
+                    alatch.countDown();
+                    connector.accept();
+                }
+                catch (Throwable e)
+                {
+                }
+                finally
+                {
+                    alatch.countDown();
+                }
+            }
+        };
+        acceptor.start();
+        while (alatch.getCount()==2)
+            Thread.sleep(10);
+
+        // interrupt the acceptor
+        acceptor.interrupt();
+
+        // wait for acceptor to exit
+        assertTrue(alatch.await(10,TimeUnit.SECONDS));
+
+        // connector is closed
+        assertFalse(connector.isOpen());
+
+        // but connection is still open
+        assertFalse(client.isClosed());
+        assertFalse(server.isClosed());
+
+        // Write from client to server
+        client.getOutputStream().write(42);
+        // Server reads
+        assertEquals(42, server.getInputStream().read());
+
+        // Write from server to client
+        server.getOutputStream().write(43);
+        // Client reads
+        assertEquals(43, client.getInputStream().read());
+
         client.close();
-       
-        // Server reads the EOF from client oshut and shut's down it's input
-        assertEquals(-1,server.getInputStream().read());
-        server.shutdownInput();
-       
-        // Since output was already shutdown, server closes
-        server.close();
+
+    }
+
+
+
+    @Test
+    public void testReset() throws Exception
+    {
+        try (ServerSocket connector = new ServerSocket(0);
+            Socket client = new Socket("127.0.0.1", connector.getLocalPort());
+            Socket server = connector.accept();)
+        {
+            client.setTcpNoDelay(true);
+            client.setSoLinger(true, 0);
+            server.setTcpNoDelay(true);
+            server.setSoLinger(true, 0);
+
+            client.getOutputStream().write(1);
+            assertEquals(1, server.getInputStream().read());
+            server.getOutputStream().write(1);
+            assertEquals(1, client.getInputStream().read());
+
+            // Server generator shutdowns output after non persistent sending response.
+            server.shutdownOutput();
+
+            // client endpoint reads EOF and shutdown input as result
+            assertEquals(-1, client.getInputStream().read());
+            client.shutdownInput();
+
+            // client connection see's EOF and shutsdown output as no more requests to be sent.
+            client.shutdownOutput();
+
+            // Since input already shutdown, client also closes socket.
+            client.close();
+
+            // Server reads the EOF from client oshut and shut's down it's input
+            assertEquals(-1, server.getInputStream().read());
+            server.shutdownInput();
+
+            // Since output was already shutdown, server closes
+            server.close();
+        }
+    }
+
+    @Test
+    public void testAsyncSocketChannel() throws Exception
+    {
+        AsynchronousServerSocketChannel connector = AsynchronousServerSocketChannel.open();
+        connector.bind(null);
+        InetSocketAddress addr=(InetSocketAddress)connector.getLocalAddress();
+        Future<AsynchronousSocketChannel> acceptor = connector.accept();
+        
+        AsynchronousSocketChannel client = AsynchronousSocketChannel.open();
+        
+        client.connect(new InetSocketAddress("127.0.0.1",addr.getPort())).get(5, TimeUnit.SECONDS);
+
+        AsynchronousSocketChannel server = acceptor.get(5, TimeUnit.SECONDS);
+
+        ByteBuffer read = ByteBuffer.allocate(1024);
+        Future<Integer> reading = server.read(read);
+
+        byte[] data = "Testing 1 2 3".getBytes(StandardCharsets.UTF_8);
+        ByteBuffer write = BufferUtil.toBuffer(data);
+        Future<Integer> writing = client.write(write);
+
+        writing.get(5, TimeUnit.SECONDS);
+        reading.get(5, TimeUnit.SECONDS);
+        read.flip();
+
+        Assert.assertEquals(ByteBuffer.wrap(data), read);
     }
+    
+    @Test
+    public void testGatherWrite() throws Exception
+    {
+        File dir = MavenTestingUtils.getTargetTestingDir();
+        if (!dir.exists())
+            dir.mkdir();
+        
+        File file = File.createTempFile("test",".txt",dir);
+        file.deleteOnExit();
+        FileChannel out = FileChannel.open(file.toPath(),
+                StandardOpenOption.CREATE,
+                StandardOpenOption.READ,
+                StandardOpenOption.WRITE,
+                StandardOpenOption.DELETE_ON_CLOSE);
+        
+        ByteBuffer[] buffers = new ByteBuffer[4096];
+        long expected=0;
+        for (int i=0;i<buffers.length;i++)
+        {
+            buffers[i]=BufferUtil.toBuffer(i);
+            expected+=buffers[i].remaining();
+        }
+        
+        long wrote = IO.write(out,buffers,0,buffers.length);
+        
+        assertEquals(expected,wrote);
 
+        for (int i=0;i<buffers.length;i++)
+            assertEquals(0,buffers[i].remaining());
+    }
 }
diff --git a/jetty-io/src/test/java/org/eclipse/jetty/io/IdleTimeoutTest.java b/jetty-io/src/test/java/org/eclipse/jetty/io/IdleTimeoutTest.java
new file mode 100644
index 0000000..f845428
--- /dev/null
+++ b/jetty-io/src/test/java/org/eclipse/jetty/io/IdleTimeoutTest.java
@@ -0,0 +1,158 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+
+import java.util.concurrent.TimeoutException;
+
+import org.eclipse.jetty.util.thread.TimerScheduler;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class IdleTimeoutTest
+{
+    volatile boolean _open;
+    volatile TimeoutException _expired;
+
+    TimerScheduler _timer;
+    IdleTimeout _timeout;
+
+    @Before
+    public void setUp() throws Exception
+    {
+        _open=true;
+        _expired=null;
+        _timer=new TimerScheduler();
+        _timer.start();
+        _timeout=new IdleTimeout(_timer)
+        {
+            @Override
+            protected void onIdleExpired(TimeoutException timeout)
+            {
+                _expired=timeout;
+            }
+
+            @Override
+            public boolean isOpen()
+            {
+                return _open;
+            }
+        };
+        _timeout.setIdleTimeout(1000);
+    }
+
+    @After
+    public void tearDown() throws Exception
+    {
+        _open=false;
+        _timer.stop();
+
+    }
+
+    @Test
+    public void testNotIdle() throws Exception
+    {
+        for (int i=0;i<20;i++)
+        {
+            Thread.sleep(100);
+            _timeout.notIdle();
+        }
+
+        Assert.assertNull(_expired);
+    }
+
+    @Test
+    public void testIdle() throws Exception
+    {
+        for (int i=0;i<5;i++)
+        {
+            Thread.sleep(100);
+            _timeout.notIdle();
+        }
+        Thread.sleep(1500);
+        Assert.assertNotNull(_expired);
+    }
+
+    @Test
+    public void testClose() throws Exception
+    {
+        for (int i=0;i<5;i++)
+        {
+            Thread.sleep(100);
+            _timeout.notIdle();
+        }
+        _timeout.onClose();
+        Thread.sleep(1500);
+        Assert.assertNull(_expired);
+    }
+
+    @Test
+    public void testClosed() throws Exception
+    {
+        for (int i=0;i<5;i++)
+        {
+            Thread.sleep(100);
+            _timeout.notIdle();
+        }
+        _open=false;
+        Thread.sleep(1500);
+        Assert.assertNull(_expired);
+    }
+
+    @Test
+    public void testShorten() throws Exception
+    {
+        for (int i=0;i<5;i++)
+        {
+            Thread.sleep(100);
+            _timeout.notIdle();
+        }
+        _timeout.setIdleTimeout(100);
+        Thread.sleep(400);
+        Assert.assertNotNull(_expired);
+    }
+
+    @Test
+    public void testLengthen() throws Exception
+    {
+        for (int i=0;i<5;i++)
+        {
+            Thread.sleep(100);
+            _timeout.notIdle();
+        }
+        _timeout.setIdleTimeout(10000);
+        Thread.sleep(1500);
+        Assert.assertNull(_expired);
+    }
+
+    @Test
+    public void testMultiple() throws Exception
+    {
+        Thread.sleep(1500);
+        Assert.assertNotNull(_expired);
+        _expired=null;
+        Thread.sleep(1000);
+        Assert.assertNotNull(_expired);
+    }
+
+
+
+}
diff --git a/jetty-io/src/test/java/org/eclipse/jetty/io/MappedByteBufferPoolTest.java b/jetty-io/src/test/java/org/eclipse/jetty/io/MappedByteBufferPoolTest.java
new file mode 100644
index 0000000..3f49157
--- /dev/null
+++ b/jetty-io/src/test/java/org/eclipse/jetty/io/MappedByteBufferPoolTest.java
@@ -0,0 +1,131 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.greaterThanOrEqualTo;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.nio.ByteBuffer;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentMap;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.StringUtil;
+import org.hamcrest.Matchers;
+import org.junit.Test;
+
+public class MappedByteBufferPoolTest
+{
+    @Test
+    public void testAcquireRelease() throws Exception
+    {
+        MappedByteBufferPool bufferPool = new MappedByteBufferPool();
+        ConcurrentMap<Integer,Queue<ByteBuffer>> buffers = bufferPool.buffersFor(true);
+
+        int size = 512;
+        ByteBuffer buffer = bufferPool.acquire(size, true);
+
+        assertTrue(buffer.isDirect());
+        assertThat(buffer.capacity(), greaterThanOrEqualTo(size));
+        assertTrue(buffers.isEmpty());
+
+        bufferPool.release(buffer);
+
+        assertEquals(1, buffers.size());
+    }
+
+    @Test
+    public void testAcquireReleaseAcquire() throws Exception
+    {
+        MappedByteBufferPool bufferPool = new MappedByteBufferPool();
+        ConcurrentMap<Integer,Queue<ByteBuffer>> buffers = bufferPool.buffersFor(false);
+
+        ByteBuffer buffer1 = bufferPool.acquire(512, false);
+        bufferPool.release(buffer1);
+        ByteBuffer buffer2 = bufferPool.acquire(512, false);
+
+        assertSame(buffer1, buffer2);
+
+        bufferPool.release(buffer2);
+
+        assertEquals(1, buffers.size());
+    }
+
+    @Test
+    public void testAcquireReleaseClear() throws Exception
+    {
+        MappedByteBufferPool bufferPool = new MappedByteBufferPool();
+        ConcurrentMap<Integer,Queue<ByteBuffer>> buffers = bufferPool.buffersFor(true);
+
+        ByteBuffer buffer = bufferPool.acquire(512, true);
+        bufferPool.release(buffer);
+
+        assertEquals(1, buffers.size());
+
+        bufferPool.clear();
+
+        assertTrue(buffers.isEmpty());
+    }
+    
+    /**
+     * In a scenario where MappedByteBufferPool is being used improperly, such as releasing a buffer that wasn't created/acquired by the MappedByteBufferPool,
+     * an assertion is tested for.
+     */
+    @Test
+    public void testReleaseAssertion() throws Exception
+    {
+        int factor = 1024;
+        MappedByteBufferPool bufferPool = new MappedByteBufferPool(factor);
+
+        try
+        {
+            // Release a few small non-pool buffers
+            bufferPool.release(ByteBuffer.wrap(StringUtil.getUtf8Bytes("Hello")));
+
+            /* NOTES: 
+             * 
+             * 1) This test will pass on command line maven build, as its surefire setup uses "-ea" already.
+             * 2) In Eclipse, goto the "Run Configuration" for this test case.
+             *    Select the "Arguments" tab, and make sure "-ea" is present in the text box titled "VM arguments"
+             */
+            fail("Expected java.lang.AssertionError, do you have '-ea' JVM command line option enabled?");
+        }
+        catch (java.lang.AssertionError e)
+        {
+            // Expected path.
+        }
+    }
+    
+    @Test
+    public void testTagged()
+    {
+        MappedByteBufferPool pool = new MappedByteBufferPool.Tagged();
+
+        ByteBuffer buffer = pool.acquire(1024,false);
+
+        assertThat(BufferUtil.toDetailString(buffer),containsString("@T00000001"));
+        buffer = pool.acquire(1024,false);
+        assertThat(BufferUtil.toDetailString(buffer),containsString("@T00000002"));
+    }
+}
diff --git a/jetty-io/src/test/java/org/eclipse/jetty/io/NIOTest.java b/jetty-io/src/test/java/org/eclipse/jetty/io/NIOTest.java
new file mode 100644
index 0000000..f22f578
--- /dev/null
+++ b/jetty-io/src/test/java/org/eclipse/jetty/io/NIOTest.java
@@ -0,0 +1,136 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.nio.ByteBuffer;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.Selector;
+import java.nio.channels.SocketChannel;
+
+import org.junit.Test;
+
+/**
+ *
+ */
+public class NIOTest
+{
+    @Test
+    public void testSelector() throws Exception
+    {
+        ServerSocket acceptor = new ServerSocket(0);
+
+        Selector selector = Selector.open();
+
+        // Create client server socket pair
+        SocketChannel client = SocketChannel.open(acceptor.getLocalSocketAddress());
+        Socket server = acceptor.accept();
+        server.setTcpNoDelay(true);
+
+        // Make the client non blocking and register it with selector for reads
+        client.configureBlocking(false);
+        SelectionKey key = client.register(selector,SelectionKey.OP_READ);
+
+        // assert it is not selected
+        assertTrue(key.isValid());
+        assertFalse(key.isReadable());
+        assertEquals(0,key.readyOps());
+
+        // try selecting and assert nothing selected
+        int selected = selector.selectNow();
+        assertEquals(0,selected);
+        assertEquals(0,selector.selectedKeys().size());
+        assertTrue(key.isValid());
+        assertFalse(key.isReadable());
+        assertEquals(0,key.readyOps());
+
+        // Write a byte from server to client
+        server.getOutputStream().write(42);
+        server.getOutputStream().flush();
+
+        // select again and assert selection found for read
+        selected = selector.select(1000);
+        assertEquals(1,selected);
+        assertEquals(1,selector.selectedKeys().size());
+        assertTrue(key.isValid());
+        assertTrue(key.isReadable());
+        assertEquals(1,key.readyOps());
+
+        // select again and see that it is not reselect, but stays selected
+        selected = selector.select(100);
+        assertEquals(0,selected);
+        assertEquals(1,selector.selectedKeys().size());
+        assertTrue(key.isValid());
+        assertTrue(key.isReadable());
+        assertEquals(1,key.readyOps());
+
+        // read the byte
+        ByteBuffer buf = ByteBuffer.allocate(1024);
+        int len=client.read(buf);
+        assertEquals(1,len);
+        buf.flip();
+        assertEquals(42,buf.get());
+        buf.clear();
+
+        // But this does not change the key
+        assertTrue(key.isValid());
+        assertTrue(key.isReadable());
+        assertEquals(1,key.readyOps());
+
+        // Even if we select again ?
+        selected = selector.select(100);
+        assertEquals(0,selected);
+        assertEquals(1,selector.selectedKeys().size());
+        assertTrue(key.isValid());
+        assertTrue(key.isReadable());
+        assertEquals(1,key.readyOps());
+
+        // Unless we remove the key from the select set
+        // and then it is still flagged as isReadable()
+        selector.selectedKeys().clear();
+        assertEquals(0,selector.selectedKeys().size());
+        assertTrue(key.isValid());
+        assertTrue(key.isReadable());
+        assertEquals(1,key.readyOps());
+
+        // Now if we select again - it is still flagged as readable!!!
+        selected = selector.select(100);
+        assertEquals(0,selected);
+        assertEquals(0,selector.selectedKeys().size());
+        assertTrue(key.isValid());
+        assertTrue(key.isReadable());
+        assertEquals(1,key.readyOps());
+
+        // Only when it is selected for something else does that state change.
+        key.interestOps(SelectionKey.OP_READ|SelectionKey.OP_WRITE);
+        selected = selector.select(1000);
+        assertEquals(1,selected);
+        assertEquals(1,selector.selectedKeys().size());
+        assertTrue(key.isValid());
+        assertTrue(key.isWritable());
+        assertFalse(key.isReadable());
+        assertEquals(SelectionKey.OP_WRITE,key.readyOps());
+    }
+
+}
diff --git a/jetty-io/src/test/java/org/eclipse/jetty/io/SelectChannelEndPointInterestsTest.java b/jetty-io/src/test/java/org/eclipse/jetty/io/SelectChannelEndPointInterestsTest.java
new file mode 100644
index 0000000..ddec080
--- /dev/null
+++ b/jetty-io/src/test/java/org/eclipse/jetty/io/SelectChannelEndPointInterestsTest.java
@@ -0,0 +1,213 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.nio.ByteBuffer;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.ServerSocketChannel;
+import java.nio.channels.SocketChannel;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.eclipse.jetty.util.thread.Scheduler;
+import org.eclipse.jetty.util.thread.TimerScheduler;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class SelectChannelEndPointInterestsTest
+{
+    private QueuedThreadPool threadPool;
+    private Scheduler scheduler;
+    private ServerSocketChannel connector;
+    private SelectorManager selectorManager;
+
+    public void init(final Interested interested) throws Exception
+    {
+        threadPool = new QueuedThreadPool();
+        threadPool.start();
+
+        scheduler = new TimerScheduler();
+        scheduler.start();
+
+        connector = ServerSocketChannel.open();
+        connector.bind(new InetSocketAddress("localhost", 0));
+
+        selectorManager = new SelectorManager(threadPool, scheduler)
+        {
+            @Override
+            protected EndPoint newEndPoint(SocketChannel channel, ManagedSelector selector, SelectionKey selectionKey) throws IOException
+            {
+                return new SelectChannelEndPoint(channel, selector, selectionKey, getScheduler(), 60000)
+                {
+                    @Override
+                    protected void onIncompleteFlush()
+                    {
+                        super.onIncompleteFlush();
+                        interested.onIncompleteFlush();
+                    }
+                };
+            }
+
+            @Override
+            public Connection newConnection(SocketChannel channel, final EndPoint endPoint, Object attachment)
+            {
+                return new AbstractConnection(endPoint, getExecutor())
+                {
+                    @Override
+                    public void onOpen()
+                    {
+                        super.onOpen();
+                        fillInterested();
+                    }
+
+                    @Override
+                    public void onFillable()
+                    {
+                        interested.onFillable(endPoint, this);
+                    }
+                };
+            }
+        };
+        selectorManager.start();
+    }
+
+    @After
+    public void destroy() throws Exception
+    {
+        if (scheduler!=null)
+            scheduler.stop();
+        if (selectorManager != null)
+            selectorManager.stop();
+        if (connector != null)
+            connector.close();
+        if (threadPool != null)
+            threadPool.stop();
+    }
+
+    @Test
+    public void testReadBlockedThenWriteBlockedThenReadableThenWritable() throws Exception
+    {
+        final AtomicInteger size = new AtomicInteger(1024 * 1024);
+        final AtomicReference<Exception> failure = new AtomicReference<>();
+        final CountDownLatch latch1 = new CountDownLatch(1);
+        final CountDownLatch latch2 = new CountDownLatch(1);
+        final AtomicBoolean writeBlocked = new AtomicBoolean();
+        init(new Interested()
+        {
+            @Override
+            public void onFillable(EndPoint endPoint, AbstractConnection connection)
+            {
+                ByteBuffer input = BufferUtil.allocate(2);
+                int read = fill(endPoint, input);
+
+                if (read == 1)
+                {
+                    byte b = input.get();
+                    if (b == 1)
+                    {
+                        connection.fillInterested();
+
+                        ByteBuffer output = ByteBuffer.allocate(size.get());
+                        endPoint.write(new Callback.Adapter(), output);
+
+                        latch1.countDown();
+                    }
+                    else
+                    {
+                        latch2.countDown();
+                    }
+                }
+                else
+                {
+                    failure.set(new Exception("Unexpectedly read " + read + " bytes"));
+                }
+            }
+
+            @Override
+            public void onIncompleteFlush()
+            {
+                writeBlocked.set(true);
+            }
+
+            private int fill(EndPoint endPoint, ByteBuffer buffer)
+            {
+                try
+                {
+                    return endPoint.fill(buffer);
+                }
+                catch (IOException x)
+                {
+                    failure.set(x);
+                    return 0;
+                }
+            }
+        });
+
+        Socket client = new Socket();
+        client.connect(connector.getLocalAddress());
+        client.setSoTimeout(5000);
+
+        SocketChannel server = connector.accept();
+        server.configureBlocking(false);
+        selectorManager.accept(server);
+
+        OutputStream clientOutput = client.getOutputStream();
+        clientOutput.write(1);
+        clientOutput.flush();
+        Assert.assertTrue(latch1.await(5, TimeUnit.SECONDS));
+
+        // We do not read to keep the socket write blocked
+
+        clientOutput.write(2);
+        clientOutput.flush();
+        Assert.assertTrue(latch2.await(5, TimeUnit.SECONDS));
+
+        // Sleep before reading to allow waking up the server only for read
+        Thread.sleep(1000);
+
+        // Now read what was written, waking up the server for write
+        InputStream clientInput = client.getInputStream();
+        while (size.getAndDecrement() > 0)
+            clientInput.read();
+
+        client.close();
+
+        Assert.assertNull(failure.get());
+    }
+
+    private interface Interested
+    {
+        void onFillable(EndPoint endPoint, AbstractConnection connection);
+
+        void onIncompleteFlush();
+    }
+
+}
diff --git a/jetty-io/src/test/java/org/eclipse/jetty/io/SelectChannelEndPointSslTest.java b/jetty-io/src/test/java/org/eclipse/jetty/io/SelectChannelEndPointSslTest.java
new file mode 100644
index 0000000..2367ed6
--- /dev/null
+++ b/jetty-io/src/test/java/org/eclipse/jetty/io/SelectChannelEndPointSslTest.java
@@ -0,0 +1,348 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import static org.hamcrest.Matchers.greaterThan;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.Socket;
+import java.nio.ByteBuffer;
+import java.nio.channels.SocketChannel;
+import java.nio.charset.StandardCharsets;
+
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLEngineResult;
+import javax.net.ssl.SSLEngineResult.HandshakeStatus;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLSocket;
+
+import org.eclipse.jetty.io.ssl.SslConnection;
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.eclipse.jetty.toolchain.test.annotation.Stress;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Ignore;
+import org.junit.Test;
+
+
+public class SelectChannelEndPointSslTest extends SelectChannelEndPointTest
+{
+    private static SslContextFactory __sslCtxFactory=new SslContextFactory();
+    private static ByteBufferPool __byteBufferPool = new MappedByteBufferPool();
+
+    @BeforeClass
+    public static void initSslEngine() throws Exception
+    {
+        File keystore = MavenTestingUtils.getTestResourceFile("keystore");
+        __sslCtxFactory.setKeyStorePath(keystore.getAbsolutePath());
+        __sslCtxFactory.setKeyStorePassword("storepwd");
+        __sslCtxFactory.setKeyManagerPassword("keypwd");
+        __sslCtxFactory.setEndpointIdentificationAlgorithm("");
+        __sslCtxFactory.start();
+    }
+
+    @Override
+    protected Socket newClient() throws IOException
+    {
+        SSLSocket socket = __sslCtxFactory.newSslSocket();
+        socket.connect(_connector.socket().getLocalSocketAddress());
+        return socket;
+    }
+
+    @Override
+    protected Connection newConnection(SocketChannel channel, EndPoint endpoint)
+    {
+        SSLEngine engine = __sslCtxFactory.newSSLEngine();
+        engine.setUseClientMode(false);
+        SslConnection sslConnection = new SslConnection(__byteBufferPool, _threadPool, endpoint, engine);
+        sslConnection.setRenegotiationAllowed(__sslCtxFactory.isRenegotiationAllowed());
+        Connection appConnection = super.newConnection(channel,sslConnection.getDecryptedEndPoint());
+        sslConnection.getDecryptedEndPoint().setConnection(appConnection);
+        return sslConnection;
+    }
+
+    @Test
+    @Override
+    public void testEcho() throws Exception
+    {
+        super.testEcho();
+    }
+
+
+    @Ignore // SSL does not do half closes
+    @Override
+    public void testShutdown() throws Exception
+    {
+    }
+
+
+    @Test
+    public void testTcpClose() throws Exception
+    {
+        // This test replaces SSLSocket() with a very manual SSL client
+        // so we can close TCP underneath SSL.
+
+        SocketChannel client = SocketChannel.open(_connector.socket().getLocalSocketAddress());
+        client.socket().setSoTimeout(500);
+
+        SocketChannel server = _connector.accept();
+        server.configureBlocking(false);
+        _manager.accept(server);
+
+        SSLEngine engine = __sslCtxFactory.newSSLEngine();
+        engine.setUseClientMode(true);
+        engine.beginHandshake();
+
+        ByteBuffer appOut = ByteBuffer.allocate(engine.getSession().getApplicationBufferSize());
+        ByteBuffer sslOut = ByteBuffer.allocate(engine.getSession().getPacketBufferSize()*2);
+        ByteBuffer appIn = ByteBuffer.allocate(engine.getSession().getApplicationBufferSize());
+        ByteBuffer sslIn = ByteBuffer.allocate(engine.getSession().getPacketBufferSize()*2);
+
+        boolean debug=false;
+
+        if (debug) System.err.println(engine.getHandshakeStatus());
+        int loop=20;
+        while (engine.getHandshakeStatus()!=HandshakeStatus.NOT_HANDSHAKING)
+        {
+            if (--loop==0)
+                throw new IllegalStateException();
+
+            if (engine.getHandshakeStatus()==HandshakeStatus.NEED_WRAP)
+            {
+                if (debug) System.err.printf("sslOut %d-%d-%d%n",sslOut.position(),sslOut.limit(),sslOut.capacity());
+                if (debug) System.err.printf("appOut %d-%d-%d%n",appOut.position(),appOut.limit(),appOut.capacity());
+                SSLEngineResult result =engine.wrap(appOut,sslOut);
+                if (debug) System.err.println(result);
+                sslOut.flip();
+                int flushed=client.write(sslOut);
+                if (debug) System.err.println("out="+flushed);
+                sslOut.clear();
+            }
+
+            if (engine.getHandshakeStatus()==HandshakeStatus.NEED_UNWRAP)
+            {
+                if (debug) System.err.printf("sslIn %d-%d-%d%n",sslIn.position(),sslIn.limit(),sslIn.capacity());
+                if (sslIn.position()==0)
+                {
+                    int filled=client.read(sslIn);
+                    if (debug) System.err.println("in="+filled);
+                }
+                sslIn.flip();
+                if (debug) System.err.printf("sslIn %d-%d-%d%n",sslIn.position(),sslIn.limit(),sslIn.capacity());
+                SSLEngineResult result =engine.unwrap(sslIn,appIn);
+                if (debug) System.err.println(result);
+                if (debug) System.err.printf("sslIn %d-%d-%d%n",sslIn.position(),sslIn.limit(),sslIn.capacity());
+                if (sslIn.hasRemaining())
+                    sslIn.compact();
+                else
+                    sslIn.clear();
+                if (debug) System.err.printf("sslIn %d-%d-%d%n",sslIn.position(),sslIn.limit(),sslIn.capacity());
+            }
+
+            if (engine.getHandshakeStatus()==HandshakeStatus.NEED_TASK)
+            {
+                Runnable task;
+                while ((task=engine.getDelegatedTask())!=null)
+                    task.run();
+                if (debug) System.err.println(engine.getHandshakeStatus());
+            }
+        }
+
+        if (debug) System.err.println("\nSay Hello");
+
+        // write a message
+        appOut.put("HelloWorld".getBytes(StandardCharsets.UTF_8));
+        appOut.flip();
+        SSLEngineResult result =engine.wrap(appOut,sslOut);
+        if (debug) System.err.println(result);
+        sslOut.flip();
+        int flushed=client.write(sslOut);
+        if (debug) System.err.println("out="+flushed);
+        sslOut.clear();
+        appOut.clear();
+
+        // read the response
+        int filled=client.read(sslIn);
+        if (debug) System.err.println("in="+filled);
+        sslIn.flip();
+        result =engine.unwrap(sslIn,appIn);
+        if (debug) System.err.println(result);
+        if (sslIn.hasRemaining())
+            sslIn.compact();
+        else
+            sslIn.clear();
+
+        appIn.flip();
+        String reply= new String(appIn.array(),appIn.arrayOffset(),appIn.remaining());
+        appIn.clear();
+
+        Assert.assertEquals("HelloWorld",reply);
+
+        if (debug) System.err.println("Shutting down output");
+        client.socket().shutdownOutput();
+
+        filled=client.read(sslIn);
+        if (debug) System.err.println("in="+filled);
+        
+        if (filled>=0)
+        {
+            // this is the old behaviour. 
+            sslIn.flip();
+            try
+            {
+                // Since the client closed abruptly, the server is sending a close alert with a failure
+                engine.unwrap(sslIn, appIn);
+                Assert.fail();
+            }
+            catch (SSLException x)
+            {
+                // Expected
+            }
+        }
+
+        sslIn.clear();
+        filled=client.read(sslIn);
+        Assert.assertEquals(-1,filled);
+
+        Assert.assertFalse(server.isOpen());
+    }
+
+    @Test
+    @Override
+    public void testWriteBlocked() throws Exception
+    {
+        super.testWriteBlocked();
+    }
+
+    @Override
+    public void testReadBlocked() throws Exception
+    {
+        super.testReadBlocked();
+    }
+
+    @Override
+    public void testIdle() throws Exception
+    {
+        super.testIdle();
+    }
+
+    @Test
+    @Override
+    @Stress("Requires a relatively idle (network wise) environment")
+    public void testStress() throws Exception
+    {
+        super.testStress();
+    }
+
+    @Test
+    public void checkSslEngineBehaviour() throws Exception
+    {
+        SSLEngine server = __sslCtxFactory.newSSLEngine();
+        SSLEngine client = __sslCtxFactory.newSSLEngine();
+
+        ByteBuffer netC2S = ByteBuffer.allocate(server.getSession().getPacketBufferSize());
+        ByteBuffer netS2C = ByteBuffer.allocate(server.getSession().getPacketBufferSize());
+        ByteBuffer serverIn = ByteBuffer.allocate(server.getSession().getApplicationBufferSize());
+        ByteBuffer serverOut = ByteBuffer.allocate(server.getSession().getApplicationBufferSize());
+        ByteBuffer clientIn = ByteBuffer.allocate(client.getSession().getApplicationBufferSize());
+
+        SSLEngineResult result;
+
+        // start the client
+        client.setUseClientMode(true);
+        client.beginHandshake();
+        Assert.assertEquals(HandshakeStatus.NEED_WRAP,client.getHandshakeStatus());
+
+        // what if we try an unwrap?
+        netS2C.flip();
+        result=client.unwrap(netS2C,clientIn);
+        // unwrap is a noop
+        assertEquals(SSLEngineResult.Status.OK,result.getStatus());
+        assertEquals(0,result.bytesConsumed());
+        assertEquals(0,result.bytesProduced());
+        assertEquals(HandshakeStatus.NEED_WRAP,result.getHandshakeStatus());
+        netS2C.clear();
+
+        // do the needed WRAP of empty buffer
+        result=client.wrap(BufferUtil.EMPTY_BUFFER,netC2S);
+        // unwrap is a noop
+        assertEquals(SSLEngineResult.Status.OK,result.getStatus());
+        assertEquals(0,result.bytesConsumed());
+        assertThat(result.bytesProduced(),greaterThan(0));
+        assertEquals(HandshakeStatus.NEED_UNWRAP,result.getHandshakeStatus());
+        netC2S.flip();
+        assertEquals(netC2S.remaining(),result.bytesProduced());
+
+
+        // start the server
+        server.setUseClientMode(false);
+        server.beginHandshake();
+        Assert.assertEquals(HandshakeStatus.NEED_UNWRAP,server.getHandshakeStatus());
+
+
+        // what if we try a needless wrap?
+        serverOut.put(BufferUtil.toBuffer("Hello World"));
+        serverOut.flip();
+        result=server.wrap(serverOut,netS2C);
+        // wrap is a noop
+        assertEquals(SSLEngineResult.Status.OK,result.getStatus());
+        assertEquals(0,result.bytesConsumed());
+        assertEquals(0,result.bytesProduced());
+        assertEquals(HandshakeStatus.NEED_UNWRAP,result.getHandshakeStatus());
+
+
+        // Do the needed unwrap, to an empty buffer
+        result=server.unwrap(netC2S,BufferUtil.EMPTY_BUFFER);
+        assertEquals(SSLEngineResult.Status.BUFFER_OVERFLOW,result.getStatus());
+        assertEquals(0,result.bytesConsumed());
+        assertEquals(0,result.bytesProduced());
+        assertEquals(HandshakeStatus.NEED_UNWRAP,result.getHandshakeStatus());
+
+
+        // Do the needed unwrap, to a full buffer
+        serverIn.position(serverIn.limit());
+        result=server.unwrap(netC2S,serverIn);
+        assertEquals(SSLEngineResult.Status.BUFFER_OVERFLOW,result.getStatus());
+        assertEquals(0,result.bytesConsumed());
+        assertEquals(0,result.bytesProduced());
+        assertEquals(HandshakeStatus.NEED_UNWRAP,result.getHandshakeStatus());
+
+
+        // Do the needed unwrap, to an empty buffer
+        serverIn.clear();
+        result=server.unwrap(netC2S,serverIn);
+        assertEquals(SSLEngineResult.Status.OK,result.getStatus());
+        assertThat(result.bytesConsumed(),greaterThan(0));
+        assertEquals(0,result.bytesProduced());
+        assertEquals(HandshakeStatus.NEED_TASK,result.getHandshakeStatus());
+
+        server.getDelegatedTask().run();
+
+        assertEquals(HandshakeStatus.NEED_WRAP,server.getHandshakeStatus());
+
+
+
+
+    }
+}
diff --git a/jetty-io/src/test/java/org/eclipse/jetty/io/SelectChannelEndPointTest.java b/jetty-io/src/test/java/org/eclipse/jetty/io/SelectChannelEndPointTest.java
new file mode 100644
index 0000000..ac42bbd
--- /dev/null
+++ b/jetty-io/src/test/java/org/eclipse/jetty/io/SelectChannelEndPointTest.java
@@ -0,0 +1,646 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import static org.hamcrest.Matchers.greaterThan;
+import static org.hamcrest.Matchers.greaterThanOrEqualTo;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+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.SocketTimeoutException;
+import java.nio.ByteBuffer;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.ServerSocketChannel;
+import java.nio.channels.SocketChannel;
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.FutureCallback;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.eclipse.jetty.util.thread.Scheduler;
+import org.eclipse.jetty.util.thread.TimerScheduler;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class SelectChannelEndPointTest
+{
+    private static final Logger LOG = Log.getLogger(SelectChannelEndPointTest.class);
+    protected CountDownLatch _lastEndPointLatch;
+    protected volatile EndPoint _lastEndPoint;
+    protected ServerSocketChannel _connector;
+    protected QueuedThreadPool _threadPool = new QueuedThreadPool();
+    protected Scheduler _scheduler = new TimerScheduler();
+    protected SelectorManager _manager = new SelectorManager(_threadPool, _scheduler)
+    {
+        @Override
+        public Connection newConnection(SocketChannel channel, EndPoint endpoint, Object attachment)
+        {
+            return SelectChannelEndPointTest.this.newConnection(channel, endpoint);
+        }
+
+        @Override
+        protected SelectChannelEndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey selectionKey) throws IOException
+        {
+            SelectChannelEndPoint endp = new SelectChannelEndPoint(channel, selectSet, selectionKey, getScheduler(), 60000);
+            _lastEndPoint = endp;
+            _lastEndPointLatch.countDown();
+            return endp;
+        }
+    };
+
+    // Must be volatile or the test may fail spuriously
+    protected volatile int _blockAt = 0;
+    private volatile int _writeCount = 1;
+
+    @Before
+    public void startManager() throws Exception
+    {
+        _writeCount = 1;
+        _lastEndPoint = null;
+        _lastEndPointLatch = new CountDownLatch(1);
+        _connector = ServerSocketChannel.open();
+        _connector.socket().bind(null);
+        _scheduler.start();
+        _threadPool.start();
+        _manager.start();
+    }
+
+    @After
+    public void stopManager() throws Exception
+    {
+        _scheduler.stop();
+        _manager.stop();
+        _threadPool.stop();
+        _connector.close();
+    }
+
+    protected Socket newClient() throws IOException
+    {
+        return new Socket(_connector.socket().getInetAddress(), _connector.socket().getLocalPort());
+    }
+
+    protected Connection newConnection(SocketChannel channel, EndPoint endpoint)
+    {
+        return new TestConnection(endpoint);
+    }
+
+    public class TestConnection extends AbstractConnection
+    {
+        ByteBuffer _in = BufferUtil.allocate(32 * 1024);
+        ByteBuffer _out = BufferUtil.allocate(32 * 1024);
+        long _last = -1;
+
+        public TestConnection(EndPoint endp)
+        {
+            super(endp, _threadPool);
+        }
+
+        @Override
+        public void onOpen()
+        {
+            super.onOpen();
+            fillInterested();
+        }
+
+        @Override
+        public synchronized void onFillable()
+        {
+            EndPoint _endp = getEndPoint();
+            try
+            {
+                _last = System.currentTimeMillis();
+                boolean progress = true;
+                while (progress)
+                {
+                    progress = false;
+
+                    // Fill the input buffer with everything available
+                    if (BufferUtil.isFull(_in))
+                        throw new IllegalStateException("FULL " + BufferUtil.toDetailString(_in));
+                    int filled = _endp.fill(_in);
+                    if (filled > 0)
+                        progress = true;
+
+                    // If the tests wants to block, then block
+                    while (_blockAt > 0 && _endp.isOpen() && _in.remaining() < _blockAt)
+                    {
+                        FutureCallback blockingRead = new FutureCallback();
+                        fillInterested(blockingRead);
+                        blockingRead.get();
+                        filled = _endp.fill(_in);
+                        progress |= filled > 0;
+                    }
+
+                    // Copy to the out buffer
+                    if (BufferUtil.hasContent(_in) && BufferUtil.append(_out, _in) > 0)
+                        progress = true;
+
+                    // Blocking writes
+                    if (BufferUtil.hasContent(_out))
+                    {
+                        ByteBuffer out = _out.duplicate();
+                        BufferUtil.clear(_out);
+                        for (int i = 0; i < _writeCount; i++)
+                        {
+                            FutureCallback blockingWrite = new FutureCallback();
+                            _endp.write(blockingWrite, out.asReadOnlyBuffer());
+                            blockingWrite.get();
+                        }
+                        progress = true;
+                    }
+
+                    // are we done?
+                    if (_endp.isInputShutdown())
+                        _endp.shutdownOutput();
+                }
+            }
+            catch (ExecutionException e)
+            {
+                // Timeout does not close, so echo exception then shutdown
+                try
+                {
+                    FutureCallback blockingWrite = new FutureCallback();
+                    _endp.write(blockingWrite, BufferUtil.toBuffer("EE: " + BufferUtil.toString(_in)));
+                    blockingWrite.get();
+                    _endp.shutdownOutput();
+                }
+                catch (Exception e2)
+                {
+                    // e2.printStackTrace();
+                }
+            }
+            catch (InterruptedException | EofException e)
+            {
+                SelectChannelEndPoint.LOG.ignore(e);
+            }
+            catch (Exception e)
+            {
+                SelectChannelEndPoint.LOG.warn(e);
+            }
+            finally
+            {
+                if (_endp.isOpen())
+                    fillInterested();
+            }
+        }
+    }
+
+    @Test
+    public void testEcho() throws Exception
+    {
+        Socket client = newClient();
+
+        client.setSoTimeout(60000);
+
+        SocketChannel server = _connector.accept();
+        server.configureBlocking(false);
+
+        _manager.accept(server);
+
+        // Write client to server
+        client.getOutputStream().write("HelloWorld".getBytes(StandardCharsets.UTF_8));
+
+        // Verify echo server to client
+        for (char c : "HelloWorld".toCharArray())
+        {
+            int b = client.getInputStream().read();
+            assertTrue(b > 0);
+            assertEquals(c, (char)b);
+        }
+
+        // wait for read timeout
+        client.setSoTimeout(500);
+        long start = System.currentTimeMillis();
+        try
+        {
+            client.getInputStream().read();
+            Assert.fail();
+        }
+        catch (SocketTimeoutException e)
+        {
+            long duration = System.currentTimeMillis() - start;
+            Assert.assertThat("timeout duration", duration, greaterThanOrEqualTo(400L));
+        }
+
+        // write then shutdown
+        client.getOutputStream().write("Goodbye Cruel TLS".getBytes(StandardCharsets.UTF_8));
+
+        // Verify echo server to client
+        for (char c : "Goodbye Cruel TLS".toCharArray())
+        {
+            int b = client.getInputStream().read();
+            Assert.assertThat("expect valid char integer", b, greaterThan(0));
+            assertEquals("expect characters to be same", c, (char)b);
+        }
+        client.close();
+
+        for (int i = 0; i < 10; ++i)
+        {
+            if (server.isOpen())
+                Thread.sleep(10);
+            else
+                break;
+        }
+        assertFalse(server.isOpen());
+    }
+
+    @Test
+    public void testShutdown() throws Exception
+    {
+        Socket client = newClient();
+
+        client.setSoTimeout(500);
+
+        SocketChannel server = _connector.accept();
+        server.configureBlocking(false);
+
+        _manager.accept(server);
+
+        // Write client to server
+        client.getOutputStream().write("HelloWorld".getBytes(StandardCharsets.UTF_8));
+
+        // Verify echo server to client
+        for (char c : "HelloWorld".toCharArray())
+        {
+            int b = client.getInputStream().read();
+            assertTrue(b > 0);
+            assertEquals(c, (char)b);
+        }
+
+        // wait for read timeout
+        long start = System.currentTimeMillis();
+        try
+        {
+            client.getInputStream().read();
+            Assert.fail();
+        }
+        catch (SocketTimeoutException e)
+        {
+            assertTrue(System.currentTimeMillis() - start >= 400);
+        }
+
+        // write then shutdown
+        client.getOutputStream().write("Goodbye Cruel TLS".getBytes(StandardCharsets.UTF_8));
+        client.shutdownOutput();
+
+        // Verify echo server to client
+        for (char c : "Goodbye Cruel TLS".toCharArray())
+        {
+            int b = client.getInputStream().read();
+            assertTrue(b > 0);
+            assertEquals(c, (char)b);
+        }
+
+        // Read close
+        assertEquals(-1, client.getInputStream().read());
+    }
+
+    @Test
+    public void testReadBlocked() throws Exception
+    {
+        Socket client = newClient();
+
+        SocketChannel server = _connector.accept();
+        server.configureBlocking(false);
+
+        _manager.accept(server);
+
+        OutputStream clientOutputStream = client.getOutputStream();
+        InputStream clientInputStream = client.getInputStream();
+
+        int specifiedTimeout = 1000;
+        client.setSoTimeout(specifiedTimeout);
+
+        // Write 8 and cause block waiting for 10
+        _blockAt = 10;
+        clientOutputStream.write("12345678".getBytes(StandardCharsets.UTF_8));
+        clientOutputStream.flush();
+
+        Assert.assertTrue(_lastEndPointLatch.await(1, TimeUnit.SECONDS));
+        _lastEndPoint.setIdleTimeout(10 * specifiedTimeout);
+        Thread.sleep((11 * specifiedTimeout) / 10);
+
+        long start = System.currentTimeMillis();
+        try
+        {
+            int b = clientInputStream.read();
+            Assert.fail("Should have timed out waiting for a response, but read " + b);
+        }
+        catch (SocketTimeoutException e)
+        {
+            int elapsed = Long.valueOf(System.currentTimeMillis() - start).intValue();
+            Assert.assertThat("Expected timeout", elapsed, greaterThanOrEqualTo(3 * specifiedTimeout / 4));
+        }
+
+        // write remaining characters
+        clientOutputStream.write("90ABCDEF".getBytes(StandardCharsets.UTF_8));
+        clientOutputStream.flush();
+
+        // Verify echo server to client
+        for (char c : "1234567890ABCDEF".toCharArray())
+        {
+            int b = clientInputStream.read();
+            assertTrue(b > 0);
+            assertEquals(c, (char)b);
+        }
+    }
+
+    @Test
+    public void testIdle() throws Exception
+    {
+        Socket client = newClient();
+
+        client.setSoTimeout(3000);
+
+        SocketChannel server = _connector.accept();
+        server.configureBlocking(false);
+
+        _manager.accept(server);
+
+        // Write client to server
+        client.getOutputStream().write("HelloWorld".getBytes(StandardCharsets.UTF_8));
+
+        // Verify echo server to client
+        for (char c : "HelloWorld".toCharArray())
+        {
+            int b = client.getInputStream().read();
+            assertTrue(b > 0);
+            assertEquals(c, (char)b);
+        }
+
+        Assert.assertTrue(_lastEndPointLatch.await(1, TimeUnit.SECONDS));
+        int idleTimeout = 500;
+        _lastEndPoint.setIdleTimeout(idleTimeout);
+
+        // read until idle shutdown received
+        long start = System.currentTimeMillis();
+        int b = client.getInputStream().read();
+        assertEquals(-1, b);
+        long idle = System.currentTimeMillis() - start;
+        assertTrue(idle > idleTimeout / 2);
+        assertTrue(idle < idleTimeout * 2);
+
+        // But endpoint may still be open for a little bit.
+        for (int i = 0; i < 10; ++i)
+        {
+            if (_lastEndPoint.isOpen())
+                Thread.sleep(2 * idleTimeout / 10);
+            else
+                break;
+        }
+        assertFalse(_lastEndPoint.isOpen());
+    }
+
+    @Test
+    public void testBlockedReadIdle() throws Exception
+    {
+        Socket client = newClient();
+        InputStream clientInputStream = client.getInputStream();
+        OutputStream clientOutputStream = client.getOutputStream();
+
+        client.setSoTimeout(5000);
+
+        SocketChannel server = _connector.accept();
+        server.configureBlocking(false);
+
+        _manager.accept(server);
+
+        // Write client to server
+        clientOutputStream.write("HelloWorld".getBytes(StandardCharsets.UTF_8));
+
+        // Verify echo server to client
+        for (char c : "HelloWorld".toCharArray())
+        {
+            int b = clientInputStream.read();
+            assertTrue(b > 0);
+            assertEquals(c, (char)b);
+        }
+
+        Assert.assertTrue(_lastEndPointLatch.await(1, TimeUnit.SECONDS));
+        int idleTimeout = 500;
+        _lastEndPoint.setIdleTimeout(idleTimeout);
+
+        // Write 8 and cause block waiting for 10
+        _blockAt = 10;
+        clientOutputStream.write("12345678".getBytes(StandardCharsets.UTF_8));
+        clientOutputStream.flush();
+
+        // read until idle shutdown received
+        long start = System.currentTimeMillis();
+        int b = clientInputStream.read();
+        assertEquals('E', b);
+        long idle = System.currentTimeMillis() - start;
+        assertTrue(idle > idleTimeout / 2);
+        assertTrue(idle < idleTimeout * 2);
+
+        for (char c : "E: 12345678".toCharArray())
+        {
+            b = clientInputStream.read();
+            assertTrue(b > 0);
+            assertEquals(c, (char)b);
+        }
+        b = clientInputStream.read();
+        assertEquals(-1,b);
+
+        // But endpoint is still open.
+        if(_lastEndPoint.isOpen())
+            // Wait for another idle callback
+            Thread.sleep(idleTimeout * 2);
+
+        // endpoint is closed.
+        assertFalse(_lastEndPoint.isOpen());
+    }
+
+    @Test
+    public void testStress() throws Exception
+    {
+        Socket client = newClient();
+        client.setSoTimeout(30000);
+
+        SocketChannel server = _connector.accept();
+        server.configureBlocking(false);
+
+        _manager.accept(server);
+        final int writes = 200000;
+
+        final byte[] bytes = "HelloWorld-".getBytes(StandardCharsets.UTF_8);
+        byte[] count = "0\n".getBytes(StandardCharsets.UTF_8);
+        BufferedOutputStream out = new BufferedOutputStream(client.getOutputStream());
+        final CountDownLatch latch = new CountDownLatch(writes);
+        final InputStream in = new BufferedInputStream(client.getInputStream());
+        final long start = System.currentTimeMillis();
+        out.write(bytes);
+        out.write(count);
+        out.flush();
+
+        Assert.assertTrue(_lastEndPointLatch.await(1, TimeUnit.SECONDS));
+        _lastEndPoint.setIdleTimeout(5000);
+
+        new Thread()
+        {
+            @Override
+            public void run()
+            {
+                Thread.currentThread().setPriority(MAX_PRIORITY);
+                long last = -1;
+                int count = -1;
+                try
+                {
+                    while (latch.getCount() > 0)
+                    {
+                        // Verify echo server to client
+                        for (byte b0 : bytes)
+                        {
+                            int b = in.read();
+                            Assert.assertThat(b, greaterThan(0));
+                            assertEquals(0xff & b0, b);
+                        }
+
+                        count = 0;
+                        int b = in.read();
+                        while (b > 0 && b != '\n')
+                        {
+                            count = count * 10 + (b - '0');
+                            b = in.read();
+                        }
+                        last = System.currentTimeMillis();
+
+                        //if (latch.getCount()%1000==0)
+                        //    System.out.println(writes-latch.getCount());
+
+                        latch.countDown();
+                    }
+                }
+                catch (Throwable e)
+                {
+
+                    long now = System.currentTimeMillis();
+                    System.err.println("count=" + count);
+                    System.err.println("latch=" + latch.getCount());
+                    System.err.println("time=" + (now - start));
+                    System.err.println("last=" + (now - last));
+                    System.err.println("endp=" + _lastEndPoint);
+                    System.err.println("conn=" + _lastEndPoint.getConnection());
+
+                    e.printStackTrace();
+                }
+            }
+        }.start();
+
+        // Write client to server
+        for (int i = 1; i < writes; i++)
+        {
+            out.write(bytes);
+            out.write(Integer.toString(i).getBytes(StandardCharsets.ISO_8859_1));
+            out.write('\n');
+            if (i % 1000 == 0)
+            {
+                //System.err.println(i+"/"+writes);
+                out.flush();
+            }
+            Thread.yield();
+        }
+        out.flush();
+
+        long last = latch.getCount();
+        while (!latch.await(5, TimeUnit.SECONDS))
+        {
+            //System.err.println(latch.getCount());
+            if (latch.getCount() == last)
+                Assert.fail();
+            last = latch.getCount();
+        }
+
+        assertEquals(0, latch.getCount());
+    }
+
+    @Test
+    public void testWriteBlocked() throws Exception
+    {
+        Socket client = newClient();
+
+        client.setSoTimeout(10000);
+
+        SocketChannel server = _connector.accept();
+        server.configureBlocking(false);
+
+        _manager.accept(server);
+
+        // Write client to server
+        _writeCount = 10000;
+        String data = "Now is the time for all good men to come to the aid of the party";
+        client.getOutputStream().write(data.getBytes(StandardCharsets.UTF_8));
+        BufferedInputStream in = new BufferedInputStream(client.getInputStream());
+
+        int byteNum = 0;
+        try
+        {
+            for (int i = 0; i < _writeCount; i++)
+            {
+                if (i % 1000 == 0)
+                    TimeUnit.MILLISECONDS.sleep(200);
+
+                // Verify echo server to client
+                for (int j = 0; j < data.length(); j++)
+                {
+                    char c = data.charAt(j);
+                    int b = in.read();
+                    byteNum++;
+                    assertTrue(b > 0);
+                    assertEquals("test-" + i + "/" + j,c,(char)b);
+                }
+
+                if (i == 0)
+                    _lastEndPoint.setIdleTimeout(60000);
+            }
+        }
+        catch (SocketTimeoutException e)
+        {
+            System.err.println("SelectorManager.dump() = " + _manager.dump());
+            LOG.warn("Server: " + server);
+            LOG.warn("Error reading byte #" + byteNum,e);
+            throw e;
+        }
+
+        client.close();
+
+        for (int i = 0; i < 10; ++i)
+        {
+            if (server.isOpen())
+                Thread.sleep(10);
+            else
+                break;
+        }
+        assertFalse(server.isOpen());
+    }
+}
diff --git a/jetty-io/src/test/java/org/eclipse/jetty/io/SelectorManagerTest.java b/jetty-io/src/test/java/org/eclipse/jetty/io/SelectorManagerTest.java
new file mode 100644
index 0000000..d36ef49
--- /dev/null
+++ b/jetty-io/src/test/java/org/eclipse/jetty/io/SelectorManagerTest.java
@@ -0,0 +1,161 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.ServerSocketChannel;
+import java.nio.channels.SocketChannel;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.eclipse.jetty.toolchain.test.annotation.Slow;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.eclipse.jetty.util.thread.TimerScheduler;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class SelectorManagerTest
+{
+    private QueuedThreadPool executor = new QueuedThreadPool();
+    private TimerScheduler scheduler = new TimerScheduler();
+
+    @Before
+    public void prepare() throws Exception
+    {
+        executor.start();
+        scheduler.start();
+    }
+
+    @After
+    public void dispose() throws Exception
+    {
+        scheduler.stop();
+        executor.stop();
+    }
+
+    @Slow
+    @Test
+    public void testConnectTimeoutBeforeSuccessfulConnect() throws Exception
+    {
+        ServerSocketChannel server = ServerSocketChannel.open();
+        server.bind(new InetSocketAddress("localhost", 0));
+        SocketAddress address = server.getLocalAddress();
+
+        final AtomicLong timeoutConnection = new AtomicLong();
+        final long connectTimeout = 1000;
+        SelectorManager selectorManager = new SelectorManager(executor, scheduler)
+        {
+            @Override
+            protected EndPoint newEndPoint(SocketChannel channel, ManagedSelector selector, SelectionKey selectionKey) throws IOException
+            {
+                return new SelectChannelEndPoint(channel, selector, selectionKey, getScheduler(), connectTimeout / 2);
+            }
+
+            @Override
+            protected boolean finishConnect(SocketChannel channel) throws IOException
+            {
+                try
+                {
+                    long timeout = timeoutConnection.get();
+                    if (timeout > 0)
+                        TimeUnit.MILLISECONDS.sleep(timeout);
+                    return super.finishConnect(channel);
+                }
+                catch (InterruptedException e)
+                {
+                    return false;
+                }
+            }
+
+            @Override
+            public Connection newConnection(SocketChannel channel, EndPoint endpoint, Object attachment) throws IOException
+            {
+                ((Callback)attachment).succeeded();
+                return new AbstractConnection(endpoint, executor)
+                {
+                    @Override
+                    public void onFillable()
+                    {
+                    }
+                };
+            }
+
+            @Override
+            protected void connectionFailed(SocketChannel channel, Throwable ex, Object attachment)
+            {
+                ((Callback)attachment).failed(ex);
+            }
+        };
+        selectorManager.setConnectTimeout(connectTimeout);
+        selectorManager.start();
+
+        try
+        {
+            SocketChannel client1 = SocketChannel.open();
+            client1.configureBlocking(false);
+            client1.connect(address);
+            long timeout = connectTimeout * 2;
+            timeoutConnection.set(timeout);
+            final CountDownLatch latch1 = new CountDownLatch(1);
+            selectorManager.connect(client1, new Callback.Adapter()
+            {
+                @Override
+                public void failed(Throwable x)
+                {
+                    latch1.countDown();
+                }
+            });
+            Assert.assertTrue(latch1.await(connectTimeout * 3, TimeUnit.MILLISECONDS));
+            Assert.assertFalse(client1.isOpen());
+
+            // Wait for the first connect to finish, as the selector thread is waiting in finishConnect().
+            Thread.sleep(timeout);
+
+            // Verify that after the failure we can connect successfully.
+            try (SocketChannel client2 = SocketChannel.open())
+            {
+                client2.configureBlocking(false);
+                client2.connect(address);
+                timeoutConnection.set(0);
+                final CountDownLatch latch2 = new CountDownLatch(1);
+                selectorManager.connect(client2, new Callback.Adapter()
+                {
+                    @Override
+                    public void succeeded()
+                    {
+                        latch2.countDown();
+                    }
+                });
+                Assert.assertTrue(latch2.await(connectTimeout * 5, TimeUnit.MILLISECONDS));
+                Assert.assertTrue(client2.isOpen());
+            }
+        }
+        finally
+        {
+            selectorManager.stop();
+        }
+    }
+}
diff --git a/jetty-io/src/test/java/org/eclipse/jetty/io/SslConnectionTest.java b/jetty-io/src/test/java/org/eclipse/jetty/io/SslConnectionTest.java
new file mode 100644
index 0000000..24b3811
--- /dev/null
+++ b/jetty-io/src/test/java/org/eclipse/jetty/io/SslConnectionTest.java
@@ -0,0 +1,391 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.Socket;
+import java.nio.ByteBuffer;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.ServerSocketChannel;
+import java.nio.channels.SocketChannel;
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLSocket;
+
+import org.eclipse.jetty.io.SelectorManager.ManagedSelector;
+import org.eclipse.jetty.io.ssl.SslConnection;
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.FutureCallback;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.eclipse.jetty.util.thread.Scheduler;
+import org.eclipse.jetty.util.thread.TimerScheduler;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+
+public class SslConnectionTest
+{
+    private static SslContextFactory __sslCtxFactory=new SslContextFactory();
+    private static ByteBufferPool __byteBufferPool = new LeakTrackingByteBufferPool(new MappedByteBufferPool.Tagged());
+
+    protected volatile EndPoint _lastEndp;
+    private volatile boolean _testFill=true;
+    private volatile FutureCallback _writeCallback;
+    protected ServerSocketChannel _connector;
+    final AtomicInteger _dispatches = new AtomicInteger();
+    protected QueuedThreadPool _threadPool = new QueuedThreadPool()
+    {
+        @Override
+        public void execute(Runnable job)
+        {
+            _dispatches.incrementAndGet();
+            super.execute(job);
+        }
+
+    };
+    protected Scheduler _scheduler = new TimerScheduler();
+    protected SelectorManager _manager = new SelectorManager(_threadPool, _scheduler)
+    {
+        @Override
+        public Connection newConnection(SocketChannel channel, EndPoint endpoint, Object attachment)
+        {
+            SSLEngine engine = __sslCtxFactory.newSSLEngine();
+            engine.setUseClientMode(false);
+            SslConnection sslConnection = new SslConnection(__byteBufferPool, getExecutor(), endpoint, engine);
+            sslConnection.setRenegotiationAllowed(__sslCtxFactory.isRenegotiationAllowed());
+            Connection appConnection = new TestConnection(sslConnection.getDecryptedEndPoint());
+            sslConnection.getDecryptedEndPoint().setConnection(appConnection);
+            return sslConnection;
+        }
+
+        @Override
+        protected SelectChannelEndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey selectionKey) throws IOException
+        {
+            SelectChannelEndPoint endp = new TestEP(channel,selectSet, selectionKey, getScheduler(), 60000);
+            _lastEndp=endp;
+            return endp;
+        }
+    };
+
+    static final AtomicInteger __startBlocking = new AtomicInteger();
+    static final AtomicInteger __blockFor = new AtomicInteger();
+    private static class TestEP extends SelectChannelEndPoint
+    {
+       
+        public TestEP(SocketChannel channel, ManagedSelector selector, SelectionKey key, Scheduler scheduler, long idleTimeout)
+        {
+            super(channel,selector,key,scheduler,idleTimeout);
+        }
+
+        @Override
+        protected void onIncompleteFlush()
+        {
+            super.onIncompleteFlush();
+        }
+        
+
+        @Override
+        public boolean flush(ByteBuffer... buffers) throws IOException
+        {
+            if (__startBlocking.get()==0 || __startBlocking.decrementAndGet()==0)
+            {
+                if (__blockFor.get()>0 && __blockFor.getAndDecrement()>0)
+                {
+                    return false;
+                }
+            }
+            String s=BufferUtil.toDetailString(buffers[0]);
+            boolean flushed=super.flush(buffers);
+            return flushed;
+        }
+    }
+    
+
+    @BeforeClass
+    public static void initSslEngine() throws Exception
+    {
+        File keystore = MavenTestingUtils.getTestResourceFile("keystore");
+        __sslCtxFactory.setKeyStorePath(keystore.getAbsolutePath());
+        __sslCtxFactory.setKeyStorePassword("storepwd");
+        __sslCtxFactory.setKeyManagerPassword("keypwd");
+        __sslCtxFactory.start();
+    }
+
+    @Before
+    public void startManager() throws Exception
+    {
+        _testFill=true;
+        _writeCallback=null;
+        _lastEndp=null;
+        _connector = ServerSocketChannel.open();
+        _connector.socket().bind(null);
+        _threadPool.start();
+        _scheduler.start();
+        _manager.start();
+
+    }
+
+    @After
+    public void stopManager() throws Exception
+    {
+        if (_lastEndp.isOpen())
+            _lastEndp.close();
+        _manager.stop();
+        _scheduler.stop();
+        _threadPool.stop();
+        _connector.close();
+    }
+
+    public class TestConnection extends AbstractConnection
+    {
+        ByteBuffer _in = BufferUtil.allocate(8*1024);
+
+        public TestConnection(EndPoint endp)
+        {
+            super(endp, _threadPool,true);
+        }
+
+        @Override
+        public void onOpen()
+        {
+            super.onOpen();
+            if (_testFill)
+                fillInterested();
+            else
+            {
+                getExecutor().execute(new Runnable()
+                {
+
+                    @Override
+                    public void run()
+                    {
+                        getEndPoint().write(_writeCallback,BufferUtil.toBuffer("Hello Client"));
+                    }
+                });
+            }
+        }
+
+        @Override
+        public void onClose()
+        {
+            super.onClose();
+        }
+
+        @Override
+        public synchronized void onFillable()
+        {
+            EndPoint endp = getEndPoint();
+            try
+            {
+                boolean progress=true;
+                while(progress)
+                {
+                    progress=false;
+
+                    // Fill the input buffer with everything available
+                    int filled=endp.fill(_in);
+                    while (filled>0)
+                    {
+                        progress=true;
+                        filled=endp.fill(_in);
+                    }
+
+                    // Write everything
+                    int l=_in.remaining();
+                    if (l>0)
+                    {
+                        FutureCallback blockingWrite= new FutureCallback();
+                        endp.write(blockingWrite,_in);
+                        blockingWrite.get();
+                    }
+
+                    // are we done?
+                    if (endp.isInputShutdown())
+                    {
+                        endp.shutdownOutput();
+                    }
+                }
+            }
+            catch(InterruptedException|EofException e)
+            {
+                SelectChannelEndPoint.LOG.ignore(e);
+            }
+            catch(Exception e)
+            {
+                SelectChannelEndPoint.LOG.warn(e);
+            }
+            finally
+            {
+                if (endp.isOpen())
+                    fillInterested();
+            }
+        }
+    }
+    protected Socket newClient() throws IOException
+    {
+        SSLSocket socket = __sslCtxFactory.newSslSocket();
+        socket.connect(_connector.socket().getLocalSocketAddress());
+        return socket;
+    }
+
+    @Test
+    public void testHelloWorld() throws Exception
+    {
+        Socket client = newClient();
+        client.setSoTimeout(60000);
+
+        SocketChannel server = _connector.accept();
+        server.configureBlocking(false);
+        _manager.accept(server);
+
+        client.getOutputStream().write("Hello".getBytes(StandardCharsets.UTF_8));
+        byte[] buffer = new byte[1024];
+        int len=client.getInputStream().read(buffer);
+        Assert.assertEquals(5, len);
+        Assert.assertEquals("Hello",new String(buffer,0,len,StandardCharsets.UTF_8));
+
+        _dispatches.set(0);
+        client.getOutputStream().write("World".getBytes(StandardCharsets.UTF_8));
+        len=5;
+        while(len>0)
+            len-=client.getInputStream().read(buffer);
+        Assert.assertEquals(1, _dispatches.get());
+
+        client.close();
+    }
+
+
+    @Test
+    public void testWriteOnConnect() throws Exception
+    {
+        _testFill=false;
+
+        _writeCallback = new FutureCallback();
+        Socket client = newClient();
+        client.setSoTimeout(10000);
+
+        SocketChannel server = _connector.accept();
+        server.configureBlocking(false);
+        _manager.accept(server);
+
+        byte[] buffer = new byte[1024];
+        int len=client.getInputStream().read(buffer);
+        Assert.assertEquals("Hello Client",new String(buffer,0,len,StandardCharsets.UTF_8));
+        Assert.assertEquals(null,_writeCallback.get(100,TimeUnit.MILLISECONDS));
+        client.close();
+    }
+    
+
+
+    @Test
+    public void testBlockedWrite() throws Exception
+    {
+        Socket client = newClient();
+        client.setSoTimeout(60000);
+
+        SocketChannel server = _connector.accept();
+        server.configureBlocking(false);
+        _manager.accept(server);
+
+        __startBlocking.set(5);
+        __blockFor.set(3);
+        
+        client.getOutputStream().write("Hello".getBytes(StandardCharsets.UTF_8));
+        byte[] buffer = new byte[1024];
+        int len=client.getInputStream().read(buffer);
+        Assert.assertEquals(5, len);
+        Assert.assertEquals("Hello",new String(buffer,0,len,StandardCharsets.UTF_8));
+
+        _dispatches.set(0);
+        client.getOutputStream().write("World".getBytes(StandardCharsets.UTF_8));
+        len=5;
+        while(len>0)
+            len-=client.getInputStream().read(buffer);
+        Assert.assertEquals(0, len);
+        client.close();
+    }
+
+    @Test
+    public void testManyLines() throws Exception
+    {
+        final Socket client = newClient();
+        client.setSoTimeout(10000);
+
+        SocketChannel server = _connector.accept();
+        server.configureBlocking(false);
+        _manager.accept(server);
+
+        final int LINES=20;
+        final CountDownLatch count=new CountDownLatch(LINES);
+
+
+        new Thread()
+        {
+            @Override
+            public void run()
+            {
+                try
+                {
+                    BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream(),StandardCharsets.UTF_8));
+                    while(count.getCount()>0)
+                    {
+                        String line=in.readLine();
+                        if (line==null)
+                            break;
+                        // System.err.println(line);
+                        count.countDown();
+                    }
+                }
+                catch(IOException e)
+                {
+                    e.printStackTrace();
+                }
+            }
+        }.start();
+
+        for (int i=0;i<LINES;i++)
+        {
+            client.getOutputStream().write(("HelloWorld "+i+"\n").getBytes(StandardCharsets.UTF_8));
+            // System.err.println("wrote");
+            if (i%1000==0)
+            {
+                client.getOutputStream().flush();
+                Thread.sleep(10);
+            }
+        }
+
+        Assert.assertTrue(count.await(20,TimeUnit.SECONDS));
+        client.close();
+
+    }
+
+
+}
diff --git a/jetty-io/src/test/java/org/eclipse/jetty/io/ThreadLocalBuffersTest.java b/jetty-io/src/test/java/org/eclipse/jetty/io/ThreadLocalBuffersTest.java
deleted file mode 100644
index 268a750..0000000
--- a/jetty-io/src/test/java/org/eclipse/jetty/io/ThreadLocalBuffersTest.java
+++ /dev/null
@@ -1,228 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.io;
-
-import static org.junit.Assert.assertTrue;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.atomic.AtomicLong;
-
-import org.eclipse.jetty.toolchain.test.PropertyFlag;
-import org.junit.Test;
-
-public class ThreadLocalBuffersTest
-{
-    private Buffers httpBuffers;
-    private List<Thread> threadList = new ArrayList<Thread>();
-    private int numThreads =  PropertyFlag.isEnabled("test.stress")?100:10;
-    private int runTestLength = PropertyFlag.isEnabled("test.stress")?5000:1000;
-    private boolean runTest = false;
-    private AtomicLong buffersRetrieved;
-
-    private void execAbstractBuffer() throws Exception
-    {
-        threadList.clear();
-        buffersRetrieved = new AtomicLong( 0 );
-        httpBuffers = new InnerBuffers(1024,4096);
-
-        for ( int i = 0; i < numThreads; ++i )
-        {
-            threadList.add( new BufferPeeper( "BufferPeeper: " + i ) );
-        }
-
-        System.gc();
-        System.gc();
-        System.gc();
-        System.gc();
-        System.gc();
-        System.gc();
-        System.gc();
-        System.gc();
-        long mem0 = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
-        runTest = true;
-
-        Thread.sleep( runTestLength );
-
-        System.gc();
-        System.gc();
-        System.gc();
-        System.gc();
-        System.gc();
-        System.gc();
-        System.gc();
-        System.gc();
-        long mem1 = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
-
-        runTest = false;
-
-        long totalBuffersRetrieved = buffersRetrieved.get();
-
-        System.out.println( "Buffers Retrieved: " + totalBuffersRetrieved );
-        System.out.println( "Memory Used: " + ( mem1 - mem0 ) );
-
-        for (Thread t : threadList)
-            t.stop();
-    }
-
-    @Test
-    public void testAbstractBuffers() throws Exception
-    {
-        execAbstractBuffer( );
-    }
-
-    @Test
-    public void testDifferentSizes() throws Exception
-    {
-        InnerBuffers buffers = new InnerBuffers(128,256);
-
-        Buffer h1 = buffers.getHeader();
-        Buffer h2 = buffers.getHeader();
-        Buffer b1 = buffers.getBuffer();
-        Buffer b2 = buffers.getBuffer();
-        Buffer b3 = buffers.getBuffer(512);
-
-        buffers.returnBuffer(h1);
-        buffers.returnBuffer(h2);
-        buffers.returnBuffer(b1);
-        buffers.returnBuffer(b2);
-        buffers.returnBuffer(b3);
-
-        assertTrue(h1==buffers.getHeader()); // pooled header
-        assertTrue(h2!=buffers.getHeader()); // b2 replaced h2 in other slot
-        assertTrue(b1==buffers.getBuffer()); // pooled buffer
-        assertTrue(b2!=buffers.getBuffer()); // b3 replaced b2 in other slot
-        assertTrue(b3==buffers.getBuffer(512)); // b2 from other slot
-
-        buffers.returnBuffer(h1);
-        buffers.returnBuffer(h2);
-        buffers.returnBuffer(b1);
-
-        assertTrue(h1==buffers.getHeader()); // pooled header
-        assertTrue(h2==buffers.getHeader()); // h2 in other slot
-        assertTrue(b1==buffers.getBuffer()); // pooled buffer
-        assertTrue(b2!=buffers.getBuffer()); // new buffer
-        assertTrue(b3!=buffers.getBuffer(512)); // new buffer
-
-        // check that sizes are respected
-        buffers.returnBuffer(b3);
-        buffers.returnBuffer(b1);
-        buffers.returnBuffer(b2);
-        buffers.returnBuffer(h1);
-        buffers.returnBuffer(h2);
-
-        assertTrue(h1==buffers.getHeader()); // pooled header
-        assertTrue(h2==buffers.getHeader()); // h2 in other slot
-        assertTrue(b1==buffers.getBuffer()); // pooled buffer
-        assertTrue(b2!=buffers.getBuffer()); // new buffer
-        assertTrue(b3!=buffers.getBuffer(512)); // new buffer
-    }
-
-    @Test
-    public void testSameSizes() throws Exception
-    {
-        InnerBuffers buffers = new InnerBuffers(128,128);
-
-        Buffer h1 = buffers.getHeader();
-        Buffer h2 = buffers.getHeader();
-        Buffer b1 = buffers.getBuffer();
-        Buffer b2 = buffers.getBuffer();
-        Buffer b3 = buffers.getBuffer(128);
-        List<Buffer> known = new ArrayList<Buffer>();
-        known.add(h1);
-        known.add(h2);
-        known.add(b1);
-        known.add(b2);
-        known.add(b3);
-
-        buffers.returnBuffer(h1); // header slot  *
-        buffers.returnBuffer(h2); // other slot
-        buffers.returnBuffer(b1); // buffer slot  *
-        buffers.returnBuffer(b2); // other slot
-        buffers.returnBuffer(b3); // other slot   *
-
-        assertTrue(h1==buffers.getHeader()); // pooled header
-        Buffer buffer = buffers.getHeader();
-        for (Buffer b:known) assertTrue(b!=buffer); // new buffer
-        assertTrue(b1==buffers.getBuffer()); // b1 used from buffer slot
-        buffer = buffers.getBuffer();
-        for (Buffer b:known) assertTrue(b!=buffer); // new buffer
-        
-        assertTrue(b3==buffers.getBuffer(128)); // b3 from other slot
-
-    }
-
-    private static class HeaderBuffer extends ByteArrayBuffer
-    {
-        public HeaderBuffer(int size)
-        {
-            super(size);
-        }
-    }
-
-    private static class InnerBuffers extends ThreadLocalBuffers
-    {
-        InnerBuffers(int headerSize,int bufferSize)
-        {
-            super(Type.DIRECT,headerSize,Type.BYTE_ARRAY,bufferSize,Type.INDIRECT);
-        }
-    }
-
-    private class BufferPeeper extends Thread
-    {
-        private final String _bufferName;
-
-        public BufferPeeper( String bufferName )
-        {
-            _bufferName = bufferName;
-            start();
-        }
-
-        @Override
-        public void run()
-        {
-            while ( true )
-            {
-                try
-                {
-                    if ( runTest )
-                    {
-                        Buffer buf = httpBuffers.getHeader();
-                        buffersRetrieved.getAndIncrement();
-
-                        buf.put(new Byte("2"));
-
-                        // sleep( threadWaitTime );
-
-                        httpBuffers.returnBuffer(buf);
-                    }
-                    else
-                    {
-                        sleep( 1 );
-                    }
-                }
-                catch ( Exception e )
-                {
-                    e.printStackTrace();
-                    break;
-                }
-            }
-        }
-    }
-}
diff --git a/jetty-io/src/test/java/org/eclipse/jetty/io/WriteFlusherTest.java b/jetty-io/src/test/java/org/eclipse/jetty/io/WriteFlusherTest.java
new file mode 100644
index 0000000..3ec6d0e
--- /dev/null
+++ b/jetty-io/src/test/java/org/eclipse/jetty/io/WriteFlusherTest.java
@@ -0,0 +1,735 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.WritePendingException;
+import java.nio.charset.StandardCharsets;
+import java.security.SecureRandom;
+import java.util.Arrays;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Exchanger;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.eclipse.jetty.util.BlockingCallback;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.FutureCallback;
+import org.hamcrest.Matchers;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.mockito.stubbing.Answer;
+
+ at RunWith(MockitoJUnitRunner.class)
+public class WriteFlusherTest
+{
+    private final AtomicBoolean _flushIncomplete = new AtomicBoolean(false);
+    private final ExecutorService executor = Executors.newFixedThreadPool(16);
+    @Mock
+    private EndPoint _endPointMock;
+    private WriteFlusher _flusher;
+    private ByteArrayEndPoint _endp;
+
+    @Before
+    public void before()
+    {
+        _endp = new ByteArrayEndPoint(new byte[]{}, 10);
+        _flushIncomplete.set(false);
+        _flusher = new WriteFlusher(_endp)
+        {
+            @Override
+            protected void onIncompleteFlushed()
+            {
+                _flushIncomplete.set(true);
+            }
+        };
+    }
+
+    @Test
+    public void testIgnorePreviousFailures() throws Exception
+    {
+        _endp.setGrowOutput(true);
+
+        FutureCallback callback = new FutureCallback();
+        _flusher.onFail(new IOException("Ignored because no operation in progress"));
+        _flusher.write(callback, BufferUtil.toBuffer("How "), BufferUtil.toBuffer("now "), BufferUtil.toBuffer("brown "), BufferUtil.toBuffer("cow!"));
+        assertCallbackIsDone(callback);
+        assertFlushIsComplete();
+        assertThat("context and callback.get() are equal",callback.get() , equalTo(null));
+        assertThat("string in endpoint matches expected string", "How now brown cow!",
+                equalTo(_endp.takeOutputString()));
+        assertTrue(_flusher.isIdle());
+    }
+
+    @Test
+    public void testCompleteNoBlocking() throws Exception
+    {
+        _endp.setGrowOutput(true);
+
+        FutureCallback callback = new FutureCallback();
+        _flusher.write(callback, BufferUtil.toBuffer("How "), BufferUtil.toBuffer("now "), BufferUtil.toBuffer("brown "), BufferUtil.toBuffer("cow!"));
+        assertCallbackIsDone(callback);
+        assertFlushIsComplete();
+        assertThat("context and callback.get() are equal", callback.get(), equalTo(null));
+        assertThat("string in endpoint matches expected string", "How now brown cow!",
+                equalTo(_endp.takeOutputString()));
+        assertTrue(_flusher.isIdle());
+    }
+
+    private void assertFlushIsComplete()
+    {
+        assertThat("flush is complete", _flushIncomplete.get(), is(false));
+    }
+
+    private void assertCallbackIsDone(FutureCallback callback)
+    {
+        assertThat("callback is done", callback.isDone(), is(true));
+    }
+
+    @Test
+    public void testClosedNoBlocking() throws Exception
+    {
+        _endp.close();
+
+        FutureCallback callback = new FutureCallback();
+        _flusher.write(callback, BufferUtil.toBuffer("How "), BufferUtil.toBuffer("now "), BufferUtil.toBuffer("brown "), BufferUtil.toBuffer("cow!"));
+        assertCallbackIsDone(callback);
+        assertFlushIsComplete();
+        try
+        {
+            assertEquals(callback.get(),null);
+            Assert.fail();
+        }
+        catch (ExecutionException e)
+        {
+            Throwable cause = e.getCause();
+            Assert.assertTrue(cause instanceof IOException);
+            Assert.assertThat(cause.getMessage(), Matchers.containsString("CLOSED"));
+        }
+        assertEquals("", _endp.takeOutputString());
+        assertTrue(_flusher.isIdle());
+    }
+
+
+    @Test
+    public void testCompleteBlocking() throws Exception
+    {
+        FutureCallback callback = new FutureCallback();
+        _flusher.write(callback, BufferUtil.toBuffer("How "), BufferUtil.toBuffer("now "), BufferUtil.toBuffer("brown "), BufferUtil.toBuffer("cow!"));
+        assertFalse(callback.isDone());
+        assertFalse(callback.isCancelled());
+
+        assertTrue(_flushIncomplete.get());
+        try
+        {
+            assertEquals(callback.get(10, TimeUnit.MILLISECONDS),null);
+            Assert.fail();
+        }
+        catch (TimeoutException to)
+        {
+            _flushIncomplete.set(false);
+        }
+
+        assertEquals("How now br", _endp.takeOutputString());
+        _flusher.completeWrite();
+        assertCallbackIsDone(callback);
+        assertEquals(callback.get(),null);
+        assertEquals("own cow!", _endp.takeOutputString());
+        assertFlushIsComplete();
+        assertTrue(_flusher.isIdle());
+    }
+
+    @Test
+    public void testCloseWhileBlocking() throws Exception
+    {
+        FutureCallback callback = new FutureCallback();
+        _flusher.write(callback, BufferUtil.toBuffer("How "), BufferUtil.toBuffer("now "), BufferUtil.toBuffer("brown "), BufferUtil.toBuffer("cow!"));
+
+        assertFalse(callback.isDone());
+        assertFalse(callback.isCancelled());
+
+        assertTrue(_flushIncomplete.get());
+        try
+        {
+            assertEquals(callback.get(10, TimeUnit.MILLISECONDS),null);
+            Assert.fail();
+        }
+        catch (TimeoutException to)
+        {
+            _flushIncomplete.set(false);
+        }
+
+        assertEquals("How now br", _endp.takeOutputString());
+        _endp.close();
+        _flusher.completeWrite();
+        assertCallbackIsDone(callback);
+        assertFlushIsComplete();
+        try
+        {
+            assertEquals(callback.get(),null);
+            Assert.fail();
+        }
+        catch (ExecutionException e)
+        {
+            Throwable cause = e.getCause();
+            Assert.assertTrue(cause instanceof IOException);
+            Assert.assertThat(cause.getMessage(), Matchers.containsString("CLOSED"));
+        }
+        assertEquals("", _endp.takeOutputString());
+        assertTrue(_flusher.isIdle());
+    }
+
+    @Test
+    public void testFailWhileBlocking() throws Exception
+    {
+        FutureCallback callback = new FutureCallback();
+        _flusher.write(callback, BufferUtil.toBuffer("How "), BufferUtil.toBuffer("now "), BufferUtil.toBuffer("brown "), BufferUtil.toBuffer("cow!"));
+
+        assertFalse(callback.isDone());
+        assertFalse(callback.isCancelled());
+
+        assertTrue(_flushIncomplete.get());
+        try
+        {
+            assertEquals(callback.get(10, TimeUnit.MILLISECONDS),null);
+            Assert.fail();
+        }
+        catch (TimeoutException to)
+        {
+            _flushIncomplete.set(false);
+        }
+
+        assertEquals("How now br", _endp.takeOutputString());
+        _flusher.onFail(new IOException("Failure"));
+        _flusher.completeWrite();
+        assertCallbackIsDone(callback);
+        assertFlushIsComplete();
+        try
+        {
+            assertEquals(callback.get(),null);
+            Assert.fail();
+        }
+        catch (ExecutionException e)
+        {
+            Throwable cause = e.getCause();
+            Assert.assertTrue(cause instanceof IOException);
+            Assert.assertThat(cause.getMessage(), Matchers.containsString("Failure"));
+        }
+        assertEquals("", _endp.takeOutputString());
+
+        assertTrue(_flusher.isIdle());
+    }
+
+    private static class ConcurrentFlusher extends WriteFlusher implements Runnable
+    {
+        final ByteArrayEndPoint _endp;
+        final SecureRandom _random;
+        final ScheduledThreadPoolExecutor _scheduler;
+        final StringBuilder _content = new StringBuilder();
+
+        ConcurrentFlusher(ByteArrayEndPoint endp, SecureRandom random, ScheduledThreadPoolExecutor scheduler)
+        {
+            super(endp);
+            _endp = endp;
+            _random = random;
+            _scheduler = scheduler;
+        }
+
+        @Override
+        protected void onIncompleteFlushed()
+        {
+            _scheduler.schedule(this, 1 + _random.nextInt(9), TimeUnit.MILLISECONDS);
+        }
+
+        @Override
+        public synchronized void run()
+        {
+            _content.append(_endp.takeOutputString());
+            completeWrite();
+        }
+
+        @Override
+        public synchronized String toString()
+        {
+            _content.append(_endp.takeOutputString());
+            return _content.toString();
+        }
+    }
+
+    @Test
+    public void testConcurrent() throws Exception
+    {
+        final SecureRandom random = new SecureRandom();
+        final ScheduledThreadPoolExecutor scheduler = new ScheduledThreadPoolExecutor(100);
+
+
+        ConcurrentFlusher[] flushers = new ConcurrentFlusher[50000];
+        FutureCallback[] futures = new FutureCallback[flushers.length];
+        for (int i = 0; i < flushers.length; i++)
+        {
+            int size = 5 + random.nextInt(15);
+            ByteArrayEndPoint endp = new ByteArrayEndPoint(new byte[]{}, size);
+
+            final ConcurrentFlusher flusher = new ConcurrentFlusher(endp, random, scheduler);
+            flushers[i] = flusher;
+            final FutureCallback callback = new FutureCallback();
+            futures[i] = callback;
+            scheduler.schedule(new Runnable()
+            {
+                @Override
+                public void run()
+                {
+                    flusher.onFail(new Throwable("THE CAUSE"));
+                }
+            }
+                    , random.nextInt(75) + 1, TimeUnit.MILLISECONDS);
+            flusher.write(callback, BufferUtil.toBuffer("How Now Brown Cow."), BufferUtil.toBuffer(" The quick brown fox jumped over the lazy dog!"));
+        }
+
+        int completed = 0;
+        int failed = 0;
+
+        for (int i = 0; i < flushers.length; i++)
+        {
+            try
+            {
+                futures[i].get();
+                assertEquals("How Now Brown Cow. The quick brown fox jumped over the lazy dog!", flushers[i].toString());
+                completed++;
+            }
+            catch (Exception e)
+            {
+                assertThat(e.getMessage(), Matchers.containsString("THE CAUSE"));
+                failed++;
+            }
+        }
+
+        assertThat(completed, Matchers.greaterThan(0));
+        assertThat(failed, Matchers.greaterThan(0));
+
+        scheduler.shutdown();
+    }
+
+    @Test
+    public void testConcurrentAccessToWriteAndOnFail() throws Exception
+    {
+        // TODO review this test - It was changed for the boolean flush return, but not really well inspected
+
+        final CountDownLatch failedCalledLatch = new CountDownLatch(1);
+        final CountDownLatch writeCalledLatch = new CountDownLatch(1);
+        final CountDownLatch writeCompleteLatch = new CountDownLatch(1);
+
+        final WriteFlusher writeFlusher = new WriteFlusher(_endPointMock)
+        {
+            @Override
+            public void write(Callback callback, ByteBuffer... buffers)
+            {
+                super.write(callback, buffers);
+                writeCompleteLatch.countDown();
+            }
+
+            @Override
+            protected void onIncompleteFlushed()
+            {
+            }
+        };
+
+        endPointFlushExpectation(writeCalledLatch, failedCalledLatch);
+
+        ExposingStateCallback callback = new ExposingStateCallback();
+        executor.submit(new Writer(writeFlusher, callback));
+        assertThat("Write has been called.", writeCalledLatch.await(5, TimeUnit.SECONDS), is(true));
+        executor.submit(new FailedCaller(writeFlusher, failedCalledLatch)).get();
+
+
+        // callback failed is NOT called because in WRITING state failed() doesn't know about the callback. However
+        // either the write succeeds or we get an IOException which will call callback.failed()
+        assertThat("write complete", writeCompleteLatch.await(5, TimeUnit.SECONDS), is(true));
+
+
+        // in this testcase we more or less emulate that the write has successfully finished and we return from
+        // EndPoint.flush() back to WriteFlusher.write(). Then someone calls failed. So the callback should have been
+        // completed.
+        try
+        {
+            callback.get(5,TimeUnit.SECONDS);
+            assertThat("callback completed", callback.isCompleted(), is(true));
+            assertThat("callback failed", callback.isFailed(), is(false));
+        }
+        catch(ExecutionException e)
+        {
+            // ignored because failure is expected
+            assertThat("callback failed", callback.isFailed(), is(true));
+        }
+        assertThat("callback completed", callback.isDone(), is(true));
+    }
+
+    @Test
+    public void testPendingWriteDoesNotStoreConsumedBuffers() throws Exception
+    {
+        int toWrite = _endp.getOutput().capacity();
+        byte[] chunk1 = new byte[toWrite / 2];
+        Arrays.fill(chunk1, (byte)1);
+        ByteBuffer buffer1 = ByteBuffer.wrap(chunk1);
+        byte[] chunk2 = new byte[toWrite];
+        Arrays.fill(chunk1, (byte)2);
+        ByteBuffer buffer2 = ByteBuffer.wrap(chunk2);
+
+        _flusher.write(new Callback.Adapter(), buffer1, buffer2);
+        assertTrue(_flushIncomplete.get());
+        assertFalse(buffer1.hasRemaining());
+
+        // Reuse buffer1
+        buffer1.clear();
+        Arrays.fill(chunk1, (byte)3);
+        int remaining1 = buffer1.remaining();
+
+        // Complete the write
+        _endp.takeOutput();
+        _flusher.completeWrite();
+
+        // Make sure buffer1 is unchanged
+        assertEquals(remaining1, buffer1.remaining());
+    }
+
+    private class ExposingStateCallback extends FutureCallback
+    {
+        private boolean failed = false;
+        private boolean completed = false;
+
+        @Override
+        public void succeeded()
+        {
+            completed = true;
+            super.succeeded();
+        }
+
+        @Override
+        public void failed(Throwable cause)
+        {
+            failed = true;
+            super.failed(cause);
+        }
+
+        public boolean isFailed()
+        {
+            return failed;
+        }
+
+        public boolean isCompleted()
+        {
+            return completed;
+        }
+    }
+
+    @Test(expected = WritePendingException.class)
+    public void testConcurrentAccessToWrite() throws Throwable
+    {
+        final CountDownLatch flushCalledLatch = new CountDownLatch(1);
+
+        final WriteFlusher writeFlusher = new WriteFlusher(_endPointMock)
+        {
+            @Override
+            protected void onIncompleteFlushed()
+            {
+            }
+        };
+
+        // in this test we just want to make sure that we called write twice at the same time
+        when(_endPointMock.flush(any(ByteBuffer[].class))).thenAnswer(new Answer<Object>()
+        {
+            @Override
+            public Object answer(InvocationOnMock invocation) throws Throwable
+            {
+                flushCalledLatch.countDown();
+                // make sure we stay here, so write is called twice at the same time
+                Thread.sleep(5000);
+                return Boolean.TRUE;
+            }
+        });
+
+        executor.submit(new Writer(writeFlusher, new FutureCallback()));
+        // make sure that we call .get() on the write that executed second by waiting on this latch
+        assertThat("Flush has been called once", flushCalledLatch.await(5, TimeUnit.SECONDS), is(true));
+        try
+        {
+            executor.submit(new Writer(writeFlusher, new FutureCallback())).get();
+        }
+        catch (ExecutionException e)
+        {
+            throw e.getCause();
+        }
+    }
+
+    private void endPointFlushExpectation(final CountDownLatch writeCalledLatch,
+                                          final CountDownLatch failedCalledLatch) throws IOException
+    {
+        when(_endPointMock.flush(any(ByteBuffer[].class))).thenAnswer(new Answer<Object>()
+        {
+            @Override
+            public Object answer(InvocationOnMock invocation) throws Throwable
+            {
+                Object[] arguments = invocation.getArguments();
+                ByteBuffer byteBuffer = (ByteBuffer)arguments[0];
+                BufferUtil.flipToFill(byteBuffer); // pretend everything has been written
+                writeCalledLatch.countDown();
+                failedCalledLatch.await(5, TimeUnit.SECONDS);
+                return Boolean.TRUE;
+            }
+        });
+    }
+
+    @Test
+    public void testConcurrentAccessToIncompleteWriteAndOnFail() throws Exception
+    {
+        final CountDownLatch failedCalledLatch = new CountDownLatch(1);
+        final CountDownLatch onIncompleteFlushedCalledLatch = new CountDownLatch(1);
+        final CountDownLatch writeCalledLatch = new CountDownLatch(1);
+        final CountDownLatch completeWrite = new CountDownLatch(1);
+
+        final WriteFlusher writeFlusher = new WriteFlusher(new EndPointConcurrentAccessToIncompleteWriteAndOnFailMock(writeCalledLatch, failedCalledLatch))
+        {
+            @Override
+            protected void onIncompleteFlushed()
+            {
+                onIncompleteFlushedCalledLatch.countDown();
+                try
+                {
+                    failedCalledLatch.await(5, TimeUnit.SECONDS);
+                }
+                catch (InterruptedException e)
+                {
+                    e.printStackTrace();
+                }
+                completeWrite();
+                completeWrite.countDown();
+            }
+        };
+
+        ExposingStateCallback callback = new ExposingStateCallback();
+        executor.submit(new Writer(writeFlusher, callback));
+        assertThat("Write has been called.", writeCalledLatch.await(5, TimeUnit.SECONDS), is(true));
+        // make sure we're in pending state when calling onFail
+        assertThat("onIncompleteFlushed has been called.", onIncompleteFlushedCalledLatch.await(5,
+                TimeUnit.SECONDS), is(true));
+        executor.submit(new FailedCaller(writeFlusher, failedCalledLatch));
+        assertThat("Failed has been called.", failedCalledLatch.await(5, TimeUnit.SECONDS), is(true));
+        assertThat("completeWrite done", completeWrite.await(5, TimeUnit.SECONDS), is(true));
+        // when we fail in PENDING state, we should have called callback.failed()
+        assertThat("callback failed has been called", callback.isFailed(), is(true));
+        assertThat("callback complete has not been called", callback.isCompleted(), is(false));
+    }
+
+    private static class EndPointConcurrentAccessToIncompleteWriteAndOnFailMock extends ByteArrayEndPoint
+    {
+        private final CountDownLatch writeCalledLatch;
+        private final CountDownLatch failedCalledLatch;
+        private final AtomicBoolean stalled=new AtomicBoolean(false);
+
+        public EndPointConcurrentAccessToIncompleteWriteAndOnFailMock(CountDownLatch writeCalledLatch, CountDownLatch failedCalledLatch)
+        {
+            this.writeCalledLatch = writeCalledLatch;
+            this.failedCalledLatch = failedCalledLatch;
+        }
+
+        @Override
+        public boolean flush(ByteBuffer... buffers) throws IOException
+        {
+            writeCalledLatch.countDown();
+            ByteBuffer byteBuffer = buffers[0];
+            int oldPos = byteBuffer.position();
+            if (byteBuffer.remaining() == 2)
+            {
+                // make sure we stall at least once
+                if (!stalled.get())
+                {
+                    stalled.set(true);
+                    return false;
+                }
+                
+                // make sure failed is called before we go on
+                try
+                {
+                    failedCalledLatch.await(5, TimeUnit.SECONDS);
+                }
+                catch (InterruptedException e)
+                {
+                    e.printStackTrace();
+                }
+                BufferUtil.flipToFill(byteBuffer);
+            }
+            else if (byteBuffer.remaining() == 3)
+            {
+                byteBuffer.position(1); // pretend writing one byte
+            }
+            else
+            {
+                byteBuffer.position(byteBuffer.limit());
+            }
+
+            for (ByteBuffer b: buffers)
+                if (BufferUtil.hasContent(b))
+                    return false;
+            return true;
+        }
+    }
+
+    @Test
+    public void testIterationOnNonBlockedStall() throws Exception
+    {
+        final Exchanger<Integer> exchange = new Exchanger<>();
+        final AtomicInteger window = new AtomicInteger(10);
+        EndPointIterationOnNonBlockedStallMock endp=new EndPointIterationOnNonBlockedStallMock(window);
+        final WriteFlusher writeFlusher = new WriteFlusher(endp)
+        {
+            @Override
+            protected void onIncompleteFlushed()
+            {
+                executor.submit(new Runnable() 
+                { 
+                    public void run() 
+                    {
+                        try
+                        {
+                            while(window.get()==0)
+                                window.addAndGet(exchange.exchange(0));
+                            completeWrite(); 
+                        }
+                        catch(Throwable th)
+                        {
+                            th.printStackTrace();
+                        }
+                    }
+                });
+
+            }
+        };
+
+        BlockingCallback callback = new BlockingCallback();
+        writeFlusher.write(callback,BufferUtil.toBuffer("How "),BufferUtil.toBuffer("now "),BufferUtil.toBuffer("brown "),BufferUtil.toBuffer("cow."));
+        exchange.exchange(0);
+        
+        Assert.assertThat(endp.takeOutputString(StandardCharsets.US_ASCII),Matchers.equalTo("How now br"));
+        
+        exchange.exchange(1);
+        exchange.exchange(0);
+        
+        Assert.assertThat(endp.takeOutputString(StandardCharsets.US_ASCII),Matchers.equalTo("o"));
+        
+        exchange.exchange(8);
+        callback.block();
+        
+        Assert.assertThat(endp.takeOutputString(StandardCharsets.US_ASCII),Matchers.equalTo("wn cow."));
+        
+    }
+
+    private static class EndPointIterationOnNonBlockedStallMock extends ByteArrayEndPoint
+    {
+        final AtomicInteger _window;
+        
+        public EndPointIterationOnNonBlockedStallMock(AtomicInteger window)
+        {
+            _window=window;
+        }
+
+        @Override
+        public boolean flush(ByteBuffer... buffers) throws IOException
+        {
+            ByteBuffer byteBuffer = buffers[0];
+            
+            if (_window.get()>0 && byteBuffer.hasRemaining())
+            {
+                // consume 1 byte
+                byte one = byteBuffer.get(byteBuffer.position());
+                if (super.flush(ByteBuffer.wrap(new byte[]{one})))
+                {
+                    _window.decrementAndGet();
+                    byteBuffer.position(byteBuffer.position()+1);
+                }
+            }
+            for (ByteBuffer b: buffers)
+                if (BufferUtil.hasContent(b))
+                    return false;
+            return true;
+        }
+    }
+    
+
+    private static class FailedCaller implements Callable<FutureCallback>
+    {
+        private final WriteFlusher writeFlusher;
+        private CountDownLatch failedCalledLatch;
+
+        public FailedCaller(WriteFlusher writeFlusher, CountDownLatch failedCalledLatch)
+        {
+            this.writeFlusher = writeFlusher;
+            this.failedCalledLatch = failedCalledLatch;
+        }
+
+        @Override
+        public FutureCallback call()
+        {
+            writeFlusher.onFail(new IllegalStateException());
+            failedCalledLatch.countDown();
+            return null;
+        }
+    }
+
+    private class Writer implements Callable<FutureCallback>
+    {
+        private final WriteFlusher writeFlusher;
+        private FutureCallback callback;
+
+        public Writer(WriteFlusher writeFlusher, FutureCallback callback)
+        {
+            this.writeFlusher = writeFlusher;
+            this.callback = callback;
+        }
+
+        @Override
+        public FutureCallback call()
+        {
+            writeFlusher.write(callback, BufferUtil.toBuffer("foo"));
+            return callback;
+        }
+    }
+}
diff --git a/jetty-io/src/test/java/org/eclipse/jetty/io/bio/SocketEndPointTest.java b/jetty-io/src/test/java/org/eclipse/jetty/io/bio/SocketEndPointTest.java
deleted file mode 100644
index 255bfa8..0000000
--- a/jetty-io/src/test/java/org/eclipse/jetty/io/bio/SocketEndPointTest.java
+++ /dev/null
@@ -1,56 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.io.bio;
-
-import java.net.ServerSocket;
-import java.net.Socket;
-
-import org.eclipse.jetty.io.EndPointTest;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-
-public class SocketEndPointTest extends EndPointTest<SocketEndPoint>
-{
-    static ServerSocket connector;
-    
-    @BeforeClass
-    public static void open() throws Exception
-    {
-        connector = new ServerSocket();
-        connector.bind(null);
-    }
-
-    @AfterClass
-    public static void close() throws Exception
-    {
-        connector.close();
-        connector=null;
-    }
-
-    @Override
-    protected EndPointPair<SocketEndPoint> newConnection() throws Exception
-    {
-        EndPointPair<SocketEndPoint> c = new EndPointPair<SocketEndPoint>();
-        c.client=new SocketEndPoint(new Socket(connector.getInetAddress(),connector.getLocalPort()));
-        c.server=new SocketEndPoint(connector.accept());
-        return c;
-    }
-    
-
-}
diff --git a/jetty-io/src/test/java/org/eclipse/jetty/io/nio/ChannelEndPointTest.java b/jetty-io/src/test/java/org/eclipse/jetty/io/nio/ChannelEndPointTest.java
deleted file mode 100644
index c15dec6..0000000
--- a/jetty-io/src/test/java/org/eclipse/jetty/io/nio/ChannelEndPointTest.java
+++ /dev/null
@@ -1,61 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.io.nio;
-
-import java.nio.channels.ServerSocketChannel;
-import java.nio.channels.SocketChannel;
-
-import org.eclipse.jetty.io.EndPointTest;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-
-public class ChannelEndPointTest extends EndPointTest<ChannelEndPoint>
-{
-    static ServerSocketChannel connector;
-    
-    @BeforeClass
-    public static void open() throws Exception
-    {
-        connector = ServerSocketChannel.open();
-        connector.socket().bind(null);
-    }
-
-    @AfterClass
-    public static void close() throws Exception
-    {
-        connector.close();
-        connector=null;
-    }
-
-    @Override
-    protected EndPointPair<ChannelEndPoint> newConnection() throws Exception
-    {
-        EndPointPair<ChannelEndPoint> c = new EndPointPair<ChannelEndPoint>();
-        
-        c.client=new ChannelEndPoint(SocketChannel.open(connector.socket().getLocalSocketAddress()));
-        c.server=new ChannelEndPoint(connector.accept());
-        return c;
-    }
-
-    @Override
-    public void testClientServerExchange() throws Exception
-    {
-        super.testClientServerExchange();
-    }
-}
diff --git a/jetty-io/src/test/java/org/eclipse/jetty/io/nio/NIOTest.java b/jetty-io/src/test/java/org/eclipse/jetty/io/nio/NIOTest.java
deleted file mode 100644
index 6a4efc7..0000000
--- a/jetty-io/src/test/java/org/eclipse/jetty/io/nio/NIOTest.java
+++ /dev/null
@@ -1,136 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.io.nio;
-
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
-
-import java.net.ServerSocket;
-import java.net.Socket;
-import java.nio.ByteBuffer;
-import java.nio.channels.SelectionKey;
-import java.nio.channels.Selector;
-import java.nio.channels.SocketChannel;
-
-import org.junit.Test;
-
-/**
- *
- */
-public class NIOTest
-{
-    @Test
-    public void testSelector() throws Exception
-    {
-        ServerSocket acceptor = new ServerSocket(0);
-        
-        Selector selector = Selector.open();
-        
-        // Create client server socket pair
-        SocketChannel client = SocketChannel.open(acceptor.getLocalSocketAddress());
-        Socket server = acceptor.accept();
-        server.setTcpNoDelay(true);
-        
-        // Make the client non blocking and register it with selector for reads
-        client.configureBlocking(false);
-        SelectionKey key = client.register(selector,SelectionKey.OP_READ);
-        
-        // assert it is not selected
-        assertTrue(key.isValid());
-        assertFalse(key.isReadable());
-        assertEquals(0,key.readyOps());
-        
-        // try selecting and assert nothing selected
-        int selected = selector.selectNow();
-        assertEquals(0,selected);
-        assertEquals(0,selector.selectedKeys().size());
-        assertTrue(key.isValid());
-        assertFalse(key.isReadable());
-        assertEquals(0,key.readyOps());
-        
-        // Write a byte from server to client
-        server.getOutputStream().write(42);
-        server.getOutputStream().flush();
-        
-        // select again and assert selection found for read
-        selected = selector.select(1000);
-        assertEquals(1,selected);
-        assertEquals(1,selector.selectedKeys().size());
-        assertTrue(key.isValid());
-        assertTrue(key.isReadable());
-        assertEquals(1,key.readyOps());
-
-        // select again and see that it is not reselect, but stays selected
-        selected = selector.select(100);
-        assertEquals(0,selected);
-        assertEquals(1,selector.selectedKeys().size());
-        assertTrue(key.isValid());
-        assertTrue(key.isReadable());
-        assertEquals(1,key.readyOps());
-        
-        // read the byte
-        ByteBuffer buf = ByteBuffer.allocate(1024);
-        int len=client.read(buf);
-        assertEquals(1,len);
-        buf.flip();
-        assertEquals(42,buf.get());
-        buf.clear();
-        
-        // But this does not change the key
-        assertTrue(key.isValid());
-        assertTrue(key.isReadable());
-        assertEquals(1,key.readyOps());
-        
-        // Even if we select again ?
-        selected = selector.select(100);
-        assertEquals(0,selected);
-        assertEquals(1,selector.selectedKeys().size());
-        assertTrue(key.isValid());
-        assertTrue(key.isReadable());
-        assertEquals(1,key.readyOps());
-        
-        // Unless we remove the key from the select set
-        // and then it is still flagged as isReadable()
-        selector.selectedKeys().clear();
-        assertEquals(0,selector.selectedKeys().size());
-        assertTrue(key.isValid());
-        assertTrue(key.isReadable());
-        assertEquals(1,key.readyOps());
-        
-        // Now if we select again - it is still flagged as readable!!!
-        selected = selector.select(100);
-        assertEquals(0,selected);
-        assertEquals(0,selector.selectedKeys().size());
-        assertTrue(key.isValid());
-        assertTrue(key.isReadable());
-        assertEquals(1,key.readyOps());
-        
-        // Only when it is selected for something else does that state change.
-        key.interestOps(SelectionKey.OP_READ|SelectionKey.OP_WRITE);
-        selected = selector.select(1000);
-        assertEquals(1,selected);
-        assertEquals(1,selector.selectedKeys().size());
-        assertTrue(key.isValid());
-        assertTrue(key.isWritable());
-        assertFalse(key.isReadable());
-        assertEquals(SelectionKey.OP_WRITE,key.readyOps());
-    }
-
-}
diff --git a/jetty-io/src/test/java/org/eclipse/jetty/io/nio/SelectChannelEndPointSslTest.java b/jetty-io/src/test/java/org/eclipse/jetty/io/nio/SelectChannelEndPointSslTest.java
deleted file mode 100644
index dd868a3..0000000
--- a/jetty-io/src/test/java/org/eclipse/jetty/io/nio/SelectChannelEndPointSslTest.java
+++ /dev/null
@@ -1,221 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.io.nio;
-
-import java.io.File;
-import java.io.IOException;
-import java.net.Socket;
-import java.nio.ByteBuffer;
-import java.nio.channels.SocketChannel;
-import javax.net.ssl.SSLEngine;
-import javax.net.ssl.SSLEngineResult;
-import javax.net.ssl.SSLEngineResult.HandshakeStatus;
-import javax.net.ssl.SSLException;
-import javax.net.ssl.SSLSocket;
-
-import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
-import org.eclipse.jetty.util.ssl.SslContextFactory;
-import org.junit.Assert;
-import org.junit.BeforeClass;
-import org.junit.Test;
-
-
-public class SelectChannelEndPointSslTest extends SelectChannelEndPointTest
-{
-    static SslContextFactory __sslCtxFactory=new SslContextFactory();
-
-    @BeforeClass
-    public static void initSslEngine() throws Exception
-    {
-        File keystore = MavenTestingUtils.getTestResourceFile("keystore");
-        __sslCtxFactory.setKeyStorePath(keystore.getAbsolutePath());
-        __sslCtxFactory.setKeyStorePassword("storepwd");
-        __sslCtxFactory.setKeyManagerPassword("keypwd");
-        __sslCtxFactory.start();
-    }
-
-    @Override
-    protected Socket newClient() throws IOException
-    {
-        SSLSocket socket = __sslCtxFactory.newSslSocket();
-        socket.connect(_connector.socket().getLocalSocketAddress());
-        return socket;
-    }
-
-    @Override
-    protected AsyncConnection newConnection(SocketChannel channel, EndPoint endpoint)
-    {
-        SSLEngine engine = __sslCtxFactory.newSslEngine();
-        engine.setUseClientMode(false);
-        SslConnection connection = new SslConnection(engine,endpoint);
-
-        AsyncConnection delegate = super.newConnection(channel,connection.getSslEndPoint());
-        connection.getSslEndPoint().setConnection(delegate);
-        return connection;
-    }
-
-    @Test
-    @Override
-    public void testEcho() throws Exception
-    {
-        super.testEcho();
-    }
-
-
-    @Test
-    @Override
-    public void testShutdown() throws Exception
-    {
-        // SSL does not do half closes
-    }
-
-    @Test
-    public void testTcpClose() throws Exception
-    {
-
-        // This test replaces SSLSocket() with a very manual SSL client
-        // so we can close TCP underneath SSL.
-
-        SocketChannel client = SocketChannel.open(_connector.socket().getLocalSocketAddress());
-        client.socket().setSoTimeout(500);
-
-        SocketChannel server = _connector.accept();
-        server.configureBlocking(false);
-        _manager.register(server);
-
-        SSLEngine engine = __sslCtxFactory.newSslEngine();
-        engine.setUseClientMode(true);
-        engine.beginHandshake();
-
-        ByteBuffer appOut = ByteBuffer.allocate(engine.getSession().getApplicationBufferSize());
-        ByteBuffer sslOut = ByteBuffer.allocate(engine.getSession().getPacketBufferSize()*2);
-        ByteBuffer appIn = ByteBuffer.allocate(engine.getSession().getApplicationBufferSize());
-        ByteBuffer sslIn = ByteBuffer.allocate(engine.getSession().getPacketBufferSize()*2);
-
-        boolean debug=false;
-
-        if (debug) System.err.println(engine.getHandshakeStatus());
-        int loop=20;
-        while (engine.getHandshakeStatus()!=HandshakeStatus.NOT_HANDSHAKING)
-        {
-            if (--loop==0)
-                throw new IllegalStateException();
-
-            if (engine.getHandshakeStatus()==HandshakeStatus.NEED_WRAP)
-            {
-                if (debug) System.err.printf("sslOut %d-%d-%d%n",sslOut.position(),sslOut.limit(),sslOut.capacity());
-                if (debug) System.err.printf("appOut %d-%d-%d%n",appOut.position(),appOut.limit(),appOut.capacity());
-                SSLEngineResult result =engine.wrap(appOut,sslOut);
-                if (debug) System.err.println(result);
-                sslOut.flip();
-                int flushed=client.write(sslOut);
-                if (debug) System.err.println("out="+flushed);
-                sslOut.clear();
-            }
-
-            if (engine.getHandshakeStatus()==HandshakeStatus.NEED_UNWRAP)
-            {
-                if (debug) System.err.printf("sslIn %d-%d-%d%n",sslIn.position(),sslIn.limit(),sslIn.capacity());
-                if (sslIn.position()==0)
-                {
-                    int filled=client.read(sslIn);
-                    if (debug) System.err.println("in="+filled);
-                }
-                sslIn.flip();
-                if (debug) System.err.printf("sslIn %d-%d-%d%n",sslIn.position(),sslIn.limit(),sslIn.capacity());
-                SSLEngineResult result =engine.unwrap(sslIn,appIn);
-                if (debug) System.err.println(result);
-                if (debug) System.err.printf("sslIn %d-%d-%d%n",sslIn.position(),sslIn.limit(),sslIn.capacity());
-                if (sslIn.hasRemaining())
-                    sslIn.compact();
-                else
-                    sslIn.clear();
-                if (debug) System.err.printf("sslIn %d-%d-%d%n",sslIn.position(),sslIn.limit(),sslIn.capacity());
-            }
-
-            if (engine.getHandshakeStatus()==HandshakeStatus.NEED_TASK)
-            {
-                Runnable task;
-                while ((task=engine.getDelegatedTask())!=null)
-                    task.run();
-                if (debug) System.err.println(engine.getHandshakeStatus());
-            }
-        }
-
-        if (debug) System.err.println("\nSay Hello");
-
-        // write a message
-        appOut.put("HelloWorld".getBytes("UTF-8"));
-        appOut.flip();
-        SSLEngineResult result =engine.wrap(appOut,sslOut);
-        if (debug) System.err.println(result);
-        sslOut.flip();
-        int flushed=client.write(sslOut);
-        if (debug) System.err.println("out="+flushed);
-        sslOut.clear();
-        appOut.clear();
-
-        // read the response
-        int filled=client.read(sslIn);
-        if (debug) System.err.println("in="+filled);
-        sslIn.flip();
-        result =engine.unwrap(sslIn,appIn);
-        if (debug) System.err.println(result);
-        if (sslIn.hasRemaining())
-            sslIn.compact();
-        else
-            sslIn.clear();
-
-        appIn.flip();
-        String reply= new String(appIn.array(),appIn.arrayOffset(),appIn.remaining());
-        appIn.clear();
-
-        Assert.assertEquals("HelloWorld",reply);
-
-        if (debug) System.err.println("Shutting down output");
-        client.socket().shutdownOutput();
-
-        filled=client.read(sslIn);
-        if (debug) System.err.println("in="+filled);
-        sslIn.flip();
-        try
-        {
-            // Since the client closed abruptly, the server is sending a close alert with a failure
-            engine.unwrap(sslIn, appIn);
-            Assert.fail();
-        }
-        catch (SSLException x)
-        {
-            // Expected
-        }
-
-        sslIn.clear();
-        filled = client.read(sslIn);
-        Assert.assertEquals(-1, filled);
-
-        Assert.assertFalse(server.isOpen());
-    }
-
-    @Test
-    public void testStress() throws Exception
-    {
-        super.testStress();
-    }
-}
diff --git a/jetty-io/src/test/java/org/eclipse/jetty/io/nio/SelectChannelEndPointTest.java b/jetty-io/src/test/java/org/eclipse/jetty/io/nio/SelectChannelEndPointTest.java
deleted file mode 100644
index 2eec600..0000000
--- a/jetty-io/src/test/java/org/eclipse/jetty/io/nio/SelectChannelEndPointTest.java
+++ /dev/null
@@ -1,457 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.io.nio;
-
-import static org.hamcrest.Matchers.greaterThanOrEqualTo;
-import static org.hamcrest.Matchers.greaterThan;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.assertTrue;
-
-import java.io.BufferedInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.Socket;
-import java.net.SocketTimeoutException;
-import java.nio.channels.SelectionKey;
-import java.nio.channels.ServerSocketChannel;
-import java.nio.channels.SocketChannel;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-import org.eclipse.jetty.io.AbstractConnection;
-import org.eclipse.jetty.io.AsyncEndPoint;
-import org.eclipse.jetty.io.ConnectedEndPoint;
-import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.util.thread.QueuedThreadPool;
-import org.hamcrest.Matchers;
-import org.junit.After;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-
-public class SelectChannelEndPointTest
-{
-    protected SelectChannelEndPoint _lastEndp;
-    protected ServerSocketChannel _connector;
-    protected QueuedThreadPool _threadPool = new QueuedThreadPool();
-    protected SelectorManager _manager = new SelectorManager()
-    {
-        @Override
-        public boolean dispatch(Runnable task)
-        {
-            return _threadPool.dispatch(task);
-        }
-
-        @Override
-        protected void endPointClosed(SelectChannelEndPoint endpoint)
-        {
-        }
-
-        @Override
-        protected void endPointOpened(SelectChannelEndPoint endpoint)
-        {
-        }
-
-        @Override
-        protected void endPointUpgraded(ConnectedEndPoint endpoint, org.eclipse.jetty.io.Connection oldConnection)
-        {
-        }
-
-        @Override
-        public AsyncConnection newConnection(SocketChannel channel, AsyncEndPoint endpoint, Object attachment)
-        {
-            return SelectChannelEndPointTest.this.newConnection(channel,endpoint);
-        }
-
-        @Override
-        protected SelectChannelEndPoint newEndPoint(SocketChannel channel, SelectSet selectSet, SelectionKey key) throws IOException
-        {
-            SelectChannelEndPoint endp = new SelectChannelEndPoint(channel,selectSet,key,2000);
-            endp.setConnection(selectSet.getManager().newConnection(channel,endp, key.attachment()));
-            _lastEndp=endp;
-            return endp;
-        }
-    };
-
-    // Must be volatile or the test may fail spuriously
-    private volatile int _blockAt=0;
-
-    @Before
-    public void startManager() throws Exception
-    {
-        _connector = ServerSocketChannel.open();
-        _connector.socket().bind(null);
-        _threadPool.start();
-        _manager.start();
-    }
-
-    @After
-    public void stopManager() throws Exception
-    {
-        _manager.stop();
-        _threadPool.stop();
-        _connector.close();
-    }
-
-    protected Socket newClient() throws IOException
-    {
-        return new Socket(_connector.socket().getInetAddress(),_connector.socket().getLocalPort());
-    }
-
-    protected AsyncConnection newConnection(SocketChannel channel, EndPoint endpoint)
-    {
-        return new TestConnection(endpoint);
-    }
-
-    public class TestConnection extends AbstractConnection implements AsyncConnection
-    {
-        NIOBuffer _in = new IndirectNIOBuffer(32*1024);
-        NIOBuffer _out = new IndirectNIOBuffer(32*1024);
-
-        public TestConnection(EndPoint endp)
-        {
-            super(endp);
-        }
-
-        public org.eclipse.jetty.io.Connection handle() throws IOException
-        {
-            boolean progress=true;
-            while(progress)
-            {
-                progress=false;
-                _in.compact();
-                if (_in.space()>0 && _endp.fill(_in)>0)
-                    progress=true;
-
-                while (_blockAt>0 && _in.length()>0 && _in.length()<_blockAt)
-                {
-                    _endp.blockReadable(10000);
-                    if (_in.space()>0 && _endp.fill(_in)>0)
-                        progress=true;
-                }
-
-                if (_in.hasContent() && _in.skip(_out.put(_in))>0)
-                    progress=true;
-
-                if (_out.hasContent() && _endp.flush(_out)>0)
-                    progress=true;
-
-                _out.compact();
-
-                if (!_out.hasContent() && _endp.isInputShutdown())
-                    _endp.shutdownOutput();
-            }
-            return this;
-        }
-
-        public boolean isIdle()
-        {
-            return false;
-        }
-
-        public boolean isSuspended()
-        {
-            return false;
-        }
-
-        public void onClose()
-        {
-            // System.err.println("onClose");
-        }
-
-        public void onInputShutdown() throws IOException
-        {
-            // System.err.println("onInputShutdown");
-        }
-
-    }
-
-    @Test
-    public void testEcho() throws Exception
-    {
-        Socket client = newClient();
-
-        client.setSoTimeout(500);
-
-        SocketChannel server = _connector.accept();
-        server.configureBlocking(false);
-
-        _manager.register(server);
-
-        // Write client to server
-        client.getOutputStream().write("HelloWorld".getBytes("UTF-8"));
-
-        // Verify echo server to client
-        for (char c : "HelloWorld".toCharArray())
-        {
-            int b = client.getInputStream().read();
-            assertTrue(b>0);
-            assertEquals(c,(char)b);
-        }
-
-        // wait for read timeout
-        long start=System.currentTimeMillis();
-        try
-        {
-            client.getInputStream().read();
-            Assert.fail();
-        }
-        catch(SocketTimeoutException e)
-        {
-            long duration = System.currentTimeMillis()-start;
-            Assert.assertThat("timeout duration", duration, greaterThanOrEqualTo(400L));
-        }
-
-        // write then shutdown
-        client.getOutputStream().write("Goodbye Cruel TLS".getBytes("UTF-8"));
-
-        // Verify echo server to client
-        for (char c : "Goodbye Cruel TLS".toCharArray())
-        {
-            int b = client.getInputStream().read();
-            Assert.assertThat("expect valid char integer", b, greaterThan(0));
-            assertEquals("expect characters to be same", c,(char)b);
-        }
-        client.close();
-
-        int i=0;
-        while (server.isOpen())
-        {
-            assert(i++<10);
-            Thread.sleep(10);
-        }
-
-    }
-
-
-    @Test
-    public void testShutdown() throws Exception
-    {
-        Socket client = newClient();
-
-        client.setSoTimeout(500);
-
-        SocketChannel server = _connector.accept();
-        server.configureBlocking(false);
-
-        _manager.register(server);
-
-        // Write client to server
-        client.getOutputStream().write("HelloWorld".getBytes("UTF-8"));
-
-        // Verify echo server to client
-        for (char c : "HelloWorld".toCharArray())
-        {
-            int b = client.getInputStream().read();
-            assertTrue(b>0);
-            assertEquals(c,(char)b);
-        }
-
-        // wait for read timeout
-        long start=System.currentTimeMillis();
-        try
-        {
-            client.getInputStream().read();
-            Assert.fail();
-        }
-        catch(SocketTimeoutException e)
-        {
-            assertTrue(System.currentTimeMillis()-start>=400);
-        }
-
-        // write then shutdown
-        client.getOutputStream().write("Goodbye Cruel TLS".getBytes("UTF-8"));
-        client.shutdownOutput();
-
-
-        // Verify echo server to client
-        for (char c : "Goodbye Cruel TLS".toCharArray())
-        {
-            int b = client.getInputStream().read();
-            assertTrue(b>0);
-            assertEquals(c,(char)b);
-        }
-
-        // Read close
-        assertEquals(-1,client.getInputStream().read());
-
-    }
-
-
-
-    @Test
-    public void testBlockIn() throws Exception
-    {
-        Socket client = newClient();
-
-        SocketChannel server = _connector.accept();
-        server.configureBlocking(false);
-
-        _manager.register(server);
-
-        OutputStream clientOutputStream = client.getOutputStream();
-        InputStream clientInputStream = client.getInputStream();
-
-        int specifiedTimeout = 400;
-        client.setSoTimeout(specifiedTimeout);
-
-        // Write 8 and cause block for 10
-        _blockAt=10;
-        clientOutputStream.write("12345678".getBytes("UTF-8"));
-        clientOutputStream.flush();
-
-        Thread.sleep(2 * specifiedTimeout);
-
-        // No echo as blocking for 10
-        long start=System.currentTimeMillis();
-        try
-        {
-            int b = clientInputStream.read();
-            Assert.fail("Should have timed out waiting for a response, but read "+b);
-        }
-        catch(SocketTimeoutException e)
-        {
-            int elapsed = Long.valueOf(System.currentTimeMillis() - start).intValue();
-            System.err.println("blocked for " + elapsed+ "ms");
-            Assert.assertThat("Expected timeout", elapsed, greaterThanOrEqualTo(3*specifiedTimeout/4));
-        }
-
-        // write remaining characters
-        clientOutputStream.write("90ABCDEF".getBytes("UTF-8"));
-        clientOutputStream.flush();
-
-        // Verify echo server to client
-        for (char c : "1234567890ABCDEF".toCharArray())
-        {
-            int b = clientInputStream.read();
-            assertTrue(b>0);
-            assertEquals(c,(char)b);
-        }
-    }
-    
-    @Test
-    public void testIdle() throws Exception
-    {
-        Socket client = newClient();
-
-        client.setSoTimeout(3000);
-
-        SocketChannel server = _connector.accept();
-        server.configureBlocking(false);
-
-        _manager.register(server);
-
-        // Write client to server
-        client.getOutputStream().write("HelloWorld".getBytes("UTF-8"));
-
-        // Verify echo server to client
-        for (char c : "HelloWorld".toCharArray())
-        {
-            int b = client.getInputStream().read();
-            assertTrue(b>0);
-            assertEquals(c,(char)b);
-        }
-
-        // Set Max idle
-        _lastEndp.setMaxIdleTime(500);
-
-        // read until idle shutdown received
-        long start=System.currentTimeMillis();
-        int b=client.getInputStream().read();
-        assertEquals(-1,b);
-        long idle=System.currentTimeMillis()-start;
-        assertThat(idle,Matchers.greaterThan(400L));
-        assertThat(idle,Matchers.lessThan(3000L));
-        
-        if (_lastEndp.isOpen())
-        {
-            // half close so wait another idle period
-            assertTrue(_lastEndp.isOutputShutdown());
-            Thread.sleep(2000);
-        }
-        
-        // endpoint is closed.
-        assertFalse(_lastEndp.isOpen());
-        
-    }
-
-
-
-    @Test
-    public void testStress() throws Exception
-    {
-        Socket client = newClient();
-        client.setSoTimeout(30000);
-
-        SocketChannel server = _connector.accept();
-        server.configureBlocking(false);
-
-        _manager.register(server);
-        int writes = 100000;
-
-        final byte[] bytes="HelloWorld".getBytes("UTF-8");
-        final CountDownLatch latch = new CountDownLatch(writes);
-        final InputStream in = new BufferedInputStream(client.getInputStream());
-        final long start = System.currentTimeMillis();
-        client.getOutputStream().write(bytes);
-        client.getOutputStream().flush();
-
-        new Thread()
-        {
-            public void run()
-            {
-                try
-                {
-                    while (latch.getCount()>0)
-                    {
-                        // Verify echo server to client
-                        for (byte b0 : bytes)
-                        {
-                            int b = in.read();
-                            assertTrue(b>0);
-                            assertEquals(0xff&b0,b);
-                        }
-                        latch.countDown();
-                    }
-                }
-                catch(Throwable e)
-                {
-                    System.err.println("latch="+latch.getCount());
-                    System.err.println("time="+(System.currentTimeMillis()-start));
-                    e.printStackTrace();
-                }
-            }
-        }.start();
-
-
-        // Write client to server
-        for (int i=1;i<writes;i++)
-        {
-            client.getOutputStream().write(bytes);
-            Thread.yield();
-        }
-        client.getOutputStream().flush();
-
-        assertTrue(latch.await(100,TimeUnit.SECONDS));
-    }
-}
diff --git a/jetty-io/src/test/resources/jetty-logging.properties b/jetty-io/src/test/resources/jetty-logging.properties
new file mode 100644
index 0000000..d4922ad
--- /dev/null
+++ b/jetty-io/src/test/resources/jetty-logging.properties
@@ -0,0 +1,2 @@
+org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
+org.eclipse.jetty.LEVEL=INFO
diff --git a/jetty-jaas/pom.xml b/jetty-jaas/pom.xml
new file mode 100644
index 0000000..9e1ba6f
--- /dev/null
+++ b/jetty-jaas/pom.xml
@@ -0,0 +1,84 @@
+<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">
+  <parent>
+    <groupId>org.eclipse.jetty</groupId>
+    <artifactId>jetty-project</artifactId>
+    <version>9.2.13.v20150730</version>
+  </parent>
+  <modelVersion>4.0.0</modelVersion>
+  <artifactId>jetty-jaas</artifactId>
+  <name>Jetty :: JAAS</name>
+  <description>Jetty JAAS support</description>
+  <properties>
+    <bundle-symbolic-name>${project.groupId}.jaas</bundle-symbolic-name>
+  </properties>
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>maven-bundle-plugin</artifactId>
+        <extensions>true</extensions>
+        <executions>
+          <execution>
+            <goals>
+              <goal>manifest</goal>
+            </goals>
+            <configuration>
+              <instructions>
+               <_versionpolicy> </_versionpolicy>
+               <Import-Package>javax.sql.*,javax.security.*,javax.naming.*,
+               javax.servlet.*;version="[2.6.0,3.2)",
+               *</Import-Package>
+              </instructions>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-jar-plugin</artifactId>
+        <configuration>
+          <archive>
+            <manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
+          </archive>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-assembly-plugin</artifactId>
+        <executions>
+          <execution>
+            <phase>package</phase>
+            <goals>
+              <goal>single</goal>
+            </goals>
+            <configuration>
+              <descriptorRefs>
+                <descriptorRef>config</descriptorRef>
+              </descriptorRefs>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+      <!-- always include the sources to be able to prepare the eclipse-jetty-SDK feature
+      with a snapshot. -->
+      <plugin>
+         <groupId>org.apache.maven.plugins</groupId>
+         <artifactId>maven-source-plugin</artifactId>
+      </plugin>
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>findbugs-maven-plugin</artifactId>
+        <configuration>
+          <onlyAnalyze>org.eclipse.jetty.jaas.*</onlyAnalyze>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+  <dependencies>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-security</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/jetty-jaas/src/main/config/etc/jetty-jaas.xml b/jetty-jaas/src/main/config/etc/jetty-jaas.xml
new file mode 100644
index 0000000..9381d10
--- /dev/null
+++ b/jetty-jaas/src/main/config/etc/jetty-jaas.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0"?>
+<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
+
+<Configure id="Server" class="org.eclipse.jetty.server.Server">
+
+
+    <!-- ======================================================== -->
+    <!-- java.security.auth.login.config System property          -->
+    <!-- This is usually a runtime parameter to the jvm, but      -->
+    <!-- it is placed here for convenience.                       -->
+    <!-- ======================================================== -->
+    <Call class="java.lang.System" name="setProperty">
+      <Arg>java.security.auth.login.config</Arg>
+      <Arg><Property name="jetty.base" default="." />/<Property name="jaas.login.conf" default="etc/login.conf"/></Arg>
+    </Call>
+
+</Configure>
diff --git a/jetty-jaas/src/main/config/modules/jaas.mod b/jetty-jaas/src/main/config/modules/jaas.mod
new file mode 100644
index 0000000..4932140
--- /dev/null
+++ b/jetty-jaas/src/main/config/modules/jaas.mod
@@ -0,0 +1,16 @@
+#
+# JAAS Module
+#
+
+[depend]
+server
+
+[lib]
+lib/jetty-jaas-${jetty.version}.jar
+
+[xml]
+etc/jetty-jaas.xml
+
+[ini-template]
+## JAAS Configuration
+jaas.login.conf=etc/login.conf
diff --git a/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/JAASGroup.java b/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/JAASGroup.java
new file mode 100644
index 0000000..94d09f2
--- /dev/null
+++ b/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/JAASGroup.java
@@ -0,0 +1,152 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.jaas;
+
+import java.security.Principal;
+import java.security.acl.Group;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Iterator;
+
+
+public class JAASGroup implements Group 
+{
+    public static final String ROLES = "__roles__";
+    
+    private String _name = null;
+    private HashSet<Principal> _members = null;
+    
+    
+   
+    public JAASGroup(String n)
+    {
+        this._name = n;
+        this._members = new HashSet<Principal>();
+    }
+   
+    /* ------------------------------------------------------------ */
+    /**
+     *
+     * @param principal <description>
+     * @return <description>
+     */
+    public synchronized boolean addMember(Principal principal)
+    {
+        return _members.add(principal);
+    }
+
+    /**
+     *
+     * @param principal <description>
+     * @return <description>
+     */
+    public synchronized boolean removeMember(Principal principal)
+    {
+        return _members.remove(principal);
+    }
+
+    /**
+     *
+     * @param principal <description>
+     * @return <description>
+     */
+    public boolean isMember(Principal principal)
+    {
+        return _members.contains(principal);
+    }
+
+
+    
+    /**
+     *
+     * @return <description>
+     */
+    public Enumeration<? extends Principal> members()
+    {
+
+        class MembersEnumeration implements Enumeration<Principal>
+        {
+            private Iterator<? extends Principal> itor;
+            
+            public MembersEnumeration (Iterator<? extends Principal> itor)
+            {
+                this.itor = itor;
+            }
+            
+            public boolean hasMoreElements ()
+            {
+                return this.itor.hasNext();
+            }
+
+
+            public Principal nextElement ()
+            {
+                return this.itor.next();
+            }
+            
+        }
+
+        return new MembersEnumeration (_members.iterator());
+    }
+
+
+    /**
+     *
+     * @return <description>
+     */
+    public int hashCode()
+    {
+        return getName().hashCode();
+    }
+
+
+    
+    /**
+     *
+     * @param object <description>
+          * @return <description>
+     */
+    public boolean equals(Object object)
+    {
+        if (! (object instanceof JAASGroup))
+            return false;
+
+        return ((JAASGroup)object).getName().equals(getName());
+    }
+
+    /**
+     *
+     * @return <description>
+     */
+    public String toString()
+    {
+        return getName();
+    }
+
+    /**
+     *
+     * @return <description>
+     */
+    public String getName()
+    {
+        
+        return _name;
+    }
+
+}
diff --git a/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/JAASLoginService.java b/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/JAASLoginService.java
new file mode 100644
index 0000000..b0d05d9
--- /dev/null
+++ b/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/JAASLoginService.java
@@ -0,0 +1,334 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.jaas;
+
+import java.io.IOException;
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import javax.security.auth.Subject;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+
+import org.eclipse.jetty.jaas.callback.ObjectCallback;
+import org.eclipse.jetty.jaas.callback.RequestParameterCallback;
+import org.eclipse.jetty.security.DefaultIdentityService;
+import org.eclipse.jetty.security.IdentityService;
+import org.eclipse.jetty.security.LoginService;
+import org.eclipse.jetty.server.HttpChannel;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.util.Loader;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ---------------------------------------------------- */
+/** JAASLoginService
+ *
+ * @org.apache.xbean.XBean element="jaasUserRealm" description="Creates a UserRealm suitable for use with JAAS"
+ */
+public class JAASLoginService extends AbstractLifeCycle implements LoginService
+{
+    private static final Logger LOG = Log.getLogger(JAASLoginService.class);
+
+    public static final String DEFAULT_ROLE_CLASS_NAME = "org.eclipse.jetty.jaas.JAASRole";
+    public static final String[] DEFAULT_ROLE_CLASS_NAMES = {DEFAULT_ROLE_CLASS_NAME};
+
+    protected String[] _roleClassNames = DEFAULT_ROLE_CLASS_NAMES;
+    protected String _callbackHandlerClass;
+    protected String _realmName;
+    protected String _loginModuleName;
+    protected JAASUserPrincipal _defaultUser = new JAASUserPrincipal(null, null, null);
+    protected IdentityService _identityService;
+
+    /* ---------------------------------------------------- */
+    /**
+     * Constructor.
+     *
+     */
+    public JAASLoginService()
+    {
+    }
+
+
+    /* ---------------------------------------------------- */
+    /**
+     * Constructor.
+     *
+     * @param name the name of the realm
+     */
+    public JAASLoginService(String name)
+    {
+        this();
+        _realmName = name;
+        _loginModuleName = name;
+    }
+
+
+    /* ---------------------------------------------------- */
+    /**
+     * Get the name of the realm.
+     *
+     * @return name or null if not set.
+     */
+    public String getName()
+    {
+        return _realmName;
+    }
+
+
+    /* ---------------------------------------------------- */
+    /**
+     * Set the name of the realm
+     *
+     * @param name a <code>String</code> value
+     */
+    public void setName (String name)
+    {
+        _realmName = name;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Get the identityService.
+     * @return the identityService
+     */
+    public IdentityService getIdentityService()
+    {
+        return _identityService;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the identityService.
+     * @param identityService the identityService to set
+     */
+    public void setIdentityService(IdentityService identityService)
+    {
+        _identityService = identityService;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the name to use to index into the config
+     * file of LoginModules.
+     *
+     * @param name a <code>String</code> value
+     */
+    public void setLoginModuleName (String name)
+    {
+        _loginModuleName = name;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setCallbackHandlerClass (String classname)
+    {
+        _callbackHandlerClass = classname;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setRoleClassNames (String[] classnames)
+    {
+        ArrayList<String> tmp = new ArrayList<String>();
+
+        if (classnames != null)
+            tmp.addAll(Arrays.asList(classnames));
+
+        if (!tmp.contains(DEFAULT_ROLE_CLASS_NAME))
+            tmp.add(DEFAULT_ROLE_CLASS_NAME);
+        _roleClassNames = tmp.toArray(new String[tmp.size()]);
+    }
+
+    /* ------------------------------------------------------------ */
+    public String[] getRoleClassNames()
+    {
+        return _roleClassNames;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart()
+     */
+    protected void doStart() throws Exception
+    {
+        if (_identityService==null)
+            _identityService=new DefaultIdentityService();
+        super.doStart();
+    }
+
+    /* ------------------------------------------------------------ */
+    public UserIdentity login(final String username,final Object credentials)
+    {
+        try
+        {
+            CallbackHandler callbackHandler = null;
+
+
+            if (_callbackHandlerClass == null)
+            {
+                callbackHandler = new CallbackHandler()
+                {
+                    public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException
+                    {
+                        for (Callback callback: callbacks)
+                        {
+                            if (callback instanceof NameCallback)
+                            {
+                                ((NameCallback)callback).setName(username);
+                            }
+                            else if (callback instanceof PasswordCallback)
+                            {
+                                ((PasswordCallback)callback).setPassword((char[]) credentials.toString().toCharArray());
+                            }
+                            else if (callback instanceof ObjectCallback)
+                            {
+                                ((ObjectCallback)callback).setObject(credentials);
+                            }
+                            else if (callback instanceof RequestParameterCallback)
+                            {
+                                HttpChannel channel = HttpChannel.getCurrentHttpChannel();
+
+                                if (channel == null)
+                                    return;
+                                Request request = channel.getRequest();
+
+                                if (request != null)
+                                {
+                                    RequestParameterCallback rpc = (RequestParameterCallback)callback;
+                                    rpc.setParameterValues(Arrays.asList(request.getParameterValues(rpc.getParameterName())));
+                                }
+                            }
+                            else
+                                throw new UnsupportedCallbackException(callback);
+                        }
+                    }
+                };
+            }
+            else
+            {
+                Class clazz = Loader.loadClass(getClass(), _callbackHandlerClass);
+                callbackHandler = (CallbackHandler)clazz.newInstance();
+            }
+            //set up the login context
+            //TODO jaspi requires we provide the Configuration parameter
+            Subject subject = new Subject();
+            LoginContext loginContext = new LoginContext(_loginModuleName, subject, callbackHandler);
+
+            loginContext.login();
+
+            //login success
+            JAASUserPrincipal userPrincipal = new JAASUserPrincipal(getUserName(callbackHandler), subject, loginContext);
+            subject.getPrincipals().add(userPrincipal);
+
+            return _identityService.newUserIdentity(subject,userPrincipal,getGroups(subject));
+        }
+        catch (LoginException e)
+        {
+            LOG.warn(e);
+        }
+        catch (IOException e)
+        {
+            LOG.warn(e);
+        }
+        catch (UnsupportedCallbackException e)
+        {
+           LOG.warn(e);
+        }
+        catch (InstantiationException e)
+        {
+            LOG.warn(e);
+        }
+        catch (IllegalAccessException e)
+        {
+            LOG.warn(e);
+        }
+        catch (ClassNotFoundException e)
+        {
+            LOG.warn(e);
+        }
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean validate(UserIdentity user)
+    {
+        // TODO optionally check user is still valid
+        return true;
+    }
+
+    /* ------------------------------------------------------------ */
+    private String getUserName(CallbackHandler callbackHandler) throws IOException, UnsupportedCallbackException
+    {
+        NameCallback nameCallback = new NameCallback("foo");
+        callbackHandler.handle(new Callback[] {nameCallback});
+        return nameCallback.getName();
+    }
+
+    /* ------------------------------------------------------------ */
+    public void logout(UserIdentity user)
+    {
+        Set<JAASUserPrincipal> userPrincipals = user.getSubject().getPrincipals(JAASUserPrincipal.class);
+        LoginContext loginContext = userPrincipals.iterator().next().getLoginContext();
+        try
+        {
+            loginContext.logout();
+        }
+        catch (LoginException e)
+        {
+            LOG.warn(e);
+        }
+    }
+
+
+    /* ------------------------------------------------------------ */
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    private String[] getGroups (Subject subject)
+    {
+        //get all the roles of the various types
+        String[] roleClassNames = getRoleClassNames();
+        Collection<String> groups = new LinkedHashSet<String>();
+        try
+        {
+            for (String roleClassName : roleClassNames)
+            {
+                Class load_class = Thread.currentThread().getContextClassLoader().loadClass(roleClassName);
+                Set<Principal> rolesForType = subject.getPrincipals(load_class);
+                for (Principal principal : rolesForType)
+                {
+                    groups.add(principal.getName());
+                }
+            }
+
+            return groups.toArray(new String[groups.size()]);
+        }
+        catch (ClassNotFoundException e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+
+}
diff --git a/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/JAASPrincipal.java b/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/JAASPrincipal.java
new file mode 100644
index 0000000..6b72097
--- /dev/null
+++ b/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/JAASPrincipal.java
@@ -0,0 +1,89 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.jaas;
+
+import java.io.Serializable;
+import java.security.Principal;
+
+
+
+/* ---------------------------------------------------- */
+/** JAASPrincipal
+ * <p>Impl class of Principal interface.
+ *
+ * <p><h4>Notes</h4>
+ * <p>
+ *
+ * <p><h4>Usage</h4>
+ * <pre>
+ */
+/*
+ * </pre>
+ *
+ * @see
+ * @version 1.0 Tue Apr 15 2003
+ * 
+ */
+public class JAASPrincipal implements Principal, Serializable
+{
+    /**
+     * 
+     */
+    private static final long serialVersionUID = -5538962177019315479L;
+    
+    private String _name = null;
+    
+    
+    public JAASPrincipal(String userName)
+    {
+        this._name = userName;
+    }
+
+
+    public boolean equals (Object p)
+    {
+        if (! (p instanceof JAASPrincipal))
+            return false;
+
+        return getName().equals(((JAASPrincipal)p).getName());
+    }
+
+
+    public int hashCode ()
+    {
+        return getName().hashCode();
+    }
+
+
+    public String getName ()
+    {
+        return this._name;
+    }
+
+
+    public String toString ()
+    {
+        return getName();
+    }
+    
+
+    
+}
+
+    
diff --git a/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/JAASRole.java b/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/JAASRole.java
new file mode 100644
index 0000000..1f8fbd4
--- /dev/null
+++ b/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/JAASRole.java
@@ -0,0 +1,42 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.jaas;
+
+
+public class JAASRole extends JAASPrincipal
+{
+    
+    /**
+     * 
+     */
+    private static final long serialVersionUID = 3465114254970134526L;
+
+    public JAASRole(String name)
+    {
+        super (name);
+    }
+
+    public boolean equals (Object o)
+    {
+        if (! (o instanceof JAASRole))
+            return false;
+
+        return getName().equals(((JAASRole)o).getName());
+    }
+}
diff --git a/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/JAASUserPrincipal.java b/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/JAASUserPrincipal.java
new file mode 100644
index 0000000..cbc72ca
--- /dev/null
+++ b/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/JAASUserPrincipal.java
@@ -0,0 +1,79 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.jaas;
+
+import java.security.Principal;
+
+import javax.security.auth.Subject;
+import javax.security.auth.login.LoginContext;
+
+
+
+/* ---------------------------------------------------- */
+/** JAASUserPrincipal
+ * <p>Implements the JAAS version of the
+ *  org.eclipse.jetty.http.UserPrincipal interface.
+ *
+ * @version $Id: JAASUserPrincipal.java 4780 2009-03-17 15:36:08Z jesse $
+ *
+ */
+public class JAASUserPrincipal implements Principal
+{
+    private final String _name;
+    private final Subject _subject;
+    private final LoginContext _loginContext;
+
+    /* ------------------------------------------------ */
+
+    public JAASUserPrincipal(String name, Subject subject, LoginContext loginContext)
+    {
+        this._name = name;
+        this._subject = subject;
+        this._loginContext = loginContext;
+    }
+
+    /* ------------------------------------------------ */
+    /** Get the name identifying the user
+     */
+    public String getName ()
+    {
+        return _name;
+    }
+
+
+    /* ------------------------------------------------ */
+    /** Provide access to the Subject
+     * @return subject
+     */
+    public Subject getSubject ()
+    {
+        return this._subject;
+    }
+
+    LoginContext getLoginContext ()
+    {
+        return this._loginContext;
+    }
+
+    public String toString()
+    {
+        return getName();
+    }
+
+}
diff --git a/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/RoleCheckPolicy.java b/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/RoleCheckPolicy.java
new file mode 100644
index 0000000..7d96d9a
--- /dev/null
+++ b/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/RoleCheckPolicy.java
@@ -0,0 +1,36 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.jaas;
+
+import java.security.Principal;
+import java.security.acl.Group;
+
+
+public interface RoleCheckPolicy 
+{
+    /* ------------------------------------------------ */
+    /** Check if a role is either a runAsRole or in a set of roles
+     * @param roleName the role to check
+     * @param runAsRole a pushed role (can be null)
+     * @param roles a Group whose Principals are role names
+     * @return <code>true</code> if <code>role</code> equals <code>runAsRole</code> or is a member of <code>roles</code>.
+     */
+    public boolean checkRole (String roleName, Principal runAsRole, Group roles);
+    
+}
diff --git a/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/StrictRoleCheckPolicy.java b/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/StrictRoleCheckPolicy.java
new file mode 100644
index 0000000..5e2961a
--- /dev/null
+++ b/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/StrictRoleCheckPolicy.java
@@ -0,0 +1,63 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.jaas;
+
+import java.security.Principal;
+import java.security.acl.Group;
+import java.util.Enumeration;
+
+
+/* ---------------------------------------------------- */
+/** StrictRoleCheckPolicy
+ * <p>Enforces that if a runAsRole is present, then the
+ * role to check must be the same as that runAsRole and
+ * the set of static roles is ignored.
+ * 
+ *
+ * 
+ * @org.apache.xbean.XBean description ="Check only topmost role in stack of roles for user"
+ */
+public class StrictRoleCheckPolicy implements RoleCheckPolicy
+{
+
+    public boolean checkRole (String roleName, Principal runAsRole, Group roles)
+    {
+        //check if this user has had any temporary role pushed onto
+        //them. If so, then only check if the user has that role.
+        if (runAsRole != null)
+        {
+            return (roleName.equals(runAsRole.getName()));
+        }
+        else
+        {
+            if (roles == null)
+                return false;
+            Enumeration<? extends Principal> rolesEnum = roles.members();
+            boolean found = false;
+            while (rolesEnum.hasMoreElements() && !found)
+            {
+                Principal p = (Principal)rolesEnum.nextElement();
+                found = roleName.equals(p.getName());
+            }
+            return found;
+        }
+        
+    }
+    
+}
diff --git a/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/callback/AbstractCallbackHandler.java b/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/callback/AbstractCallbackHandler.java
new file mode 100644
index 0000000..e238bc3
--- /dev/null
+++ b/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/callback/AbstractCallbackHandler.java
@@ -0,0 +1,60 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.jaas.callback;
+
+import java.io.IOException;
+
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.UnsupportedCallbackException;
+
+
+public abstract class AbstractCallbackHandler implements CallbackHandler
+{
+    protected String _userName;
+    protected Object _credential;
+
+    public void setUserName (String userName)
+    {
+        _userName = userName;
+    }
+
+    public String getUserName ()
+    {
+        return _userName;
+    }
+
+
+    public void setCredential (Object credential)
+    {
+        _credential = credential;
+    }
+
+    public Object getCredential ()
+    {
+        return _credential;
+    }
+
+    public  void handle (Callback[] callbacks)
+        throws IOException, UnsupportedCallbackException
+    {
+    }
+
+
+}
diff --git a/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/callback/DefaultCallbackHandler.java b/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/callback/DefaultCallbackHandler.java
new file mode 100644
index 0000000..7aa65fe
--- /dev/null
+++ b/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/callback/DefaultCallbackHandler.java
@@ -0,0 +1,97 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.jaas.callback;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.util.security.Password;
+
+
+
+/* ---------------------------------------------------- */
+/** DefaultUsernameCredentialCallbackHandler
+ * <p>
+ *
+ * <p><h4>Notes</h4>
+ * <p>
+ *
+ * <p><h4>Usage</h4>
+ * <pre>
+ */
+/*
+ * </pre>
+ *
+ * @see
+ * @version 1.0 Tue Apr 15 2003
+ *
+ */
+public class DefaultCallbackHandler extends AbstractCallbackHandler
+{
+
+    private Request _request;
+
+    public void setRequest (Request request)
+    {
+        this._request = request;
+    }
+
+    public void handle (Callback[] callbacks)
+        throws IOException, UnsupportedCallbackException
+    {
+        for (int i=0; i < callbacks.length; i++)
+        {
+            if (callbacks[i] instanceof NameCallback)
+            {
+                ((NameCallback)callbacks[i]).setName(getUserName());
+            }
+            else if (callbacks[i] instanceof ObjectCallback)
+            {
+                ((ObjectCallback)callbacks[i]).setObject(getCredential());
+            }
+            else if (callbacks[i] instanceof PasswordCallback)
+            {
+                if (getCredential() instanceof Password)
+                    ((PasswordCallback)callbacks[i]).setPassword (((Password)getCredential()).toString().toCharArray());
+                else if (getCredential() instanceof String)
+                {
+                    ((PasswordCallback)callbacks[i]).setPassword (((String)getCredential()).toCharArray());
+                }
+                else
+                    throw new UnsupportedCallbackException (callbacks[i], "User supplied credentials cannot be converted to char[] for PasswordCallback: try using an ObjectCallback instead");
+            }
+            else if (callbacks[i] instanceof RequestParameterCallback)
+            {
+                RequestParameterCallback callback = (RequestParameterCallback)callbacks[i];
+                callback.setParameterValues(Arrays.asList(_request.getParameterValues(callback.getParameterName())));
+            }
+            else
+                throw new UnsupportedCallbackException(callbacks[i]);
+        }
+
+    }
+
+}
+
diff --git a/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/callback/ObjectCallback.java b/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/callback/ObjectCallback.java
new file mode 100644
index 0000000..cb99be1
--- /dev/null
+++ b/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/callback/ObjectCallback.java
@@ -0,0 +1,67 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.jaas.callback;
+
+import javax.security.auth.callback.Callback;
+
+
+/* ---------------------------------------------------- */
+/** ObjectCallback
+ *
+ * <p>Can be used as a LoginModule Callback to
+ * obtain a user's credential as an Object, rather than
+ * a char[], to which some credentials may not be able
+ * to be converted
+ *
+ * <p><h4>Notes</h4>
+ * <p>
+ *
+ * <p><h4>Usage</h4>
+ * <pre>
+ */
+/*
+ * </pre>
+ *
+ * @see
+ * @version 1.0 Tue Apr 15 2003
+ * 
+ */
+public class ObjectCallback implements Callback
+{
+
+    protected Object _object;
+    
+    public void setObject(Object o)
+    {
+        _object = o;
+    }
+
+    public Object getObject ()
+    {
+        return _object;
+    }
+
+
+    public void clearObject ()
+    {
+        _object = null;
+    }
+    
+    
+}
diff --git a/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/callback/RequestParameterCallback.java b/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/callback/RequestParameterCallback.java
new file mode 100644
index 0000000..38f0c01
--- /dev/null
+++ b/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/callback/RequestParameterCallback.java
@@ -0,0 +1,61 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.jaas.callback;
+
+import java.util.List;
+
+import javax.security.auth.callback.Callback;
+
+
+/**
+ *
+ * RequestParameterCallback
+ *
+ * Allows a JAAS callback handler to access any parameter from the j_security_check FORM.
+ * This means that a LoginModule can access form fields other than the j_username and j_password
+ * fields, and use it, for example, to authenticate a user.
+ *
+ *
+ * @version $Revision: 4780 $ $Date: 2009-03-17 16:36:08 +0100 (Tue, 17 Mar 2009) $
+ *
+ */
+public class RequestParameterCallback implements Callback
+{
+    private String _paramName;
+    private List<?> _paramValues;
+
+    public void setParameterName (String name)
+    {
+        _paramName = name;
+    }
+    public String getParameterName ()
+    {
+        return _paramName;
+    }
+
+    public void setParameterValues (List<?> values)
+    {
+        _paramValues = values;
+    }
+
+    public List<?> getParameterValues ()
+    {
+        return _paramValues;
+    }
+}
diff --git a/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/callback/package-info.java b/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/callback/package-info.java
new file mode 100644
index 0000000..8f502d0
--- /dev/null
+++ b/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/callback/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Jaas : Jaas Callbacks
+ */
+package org.eclipse.jetty.jaas.callback;
+
diff --git a/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/package-info.java b/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/package-info.java
new file mode 100644
index 0000000..b81f47a
--- /dev/null
+++ b/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Jaas : Support for Jaas
+ */
+package org.eclipse.jetty.jaas;
+
diff --git a/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/spi/AbstractDatabaseLoginModule.java b/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/spi/AbstractDatabaseLoginModule.java
new file mode 100644
index 0000000..0a5b49e
--- /dev/null
+++ b/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/spi/AbstractDatabaseLoginModule.java
@@ -0,0 +1,143 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.jaas.spi;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import javax.security.auth.Subject;
+import javax.security.auth.callback.CallbackHandler;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.security.Credential;
+
+/**
+ * AbstractDatabaseLoginModule
+ *
+ * Abstract base class for LoginModules that interact with a
+ * database to retrieve authentication and authorization information.
+ * Used by the JDBCLoginModule and DataSourceLoginModule.
+ *
+ */
+public abstract class AbstractDatabaseLoginModule extends AbstractLoginModule
+{
+    private static final Logger LOG = Log.getLogger(AbstractDatabaseLoginModule.class);
+
+    private String userQuery;
+    private String rolesQuery;
+    private String dbUserTable;
+    private String dbUserTableUserField;
+    private String dbUserTableCredentialField;
+    private String dbUserRoleTable;
+    private String dbUserRoleTableUserField;
+    private String dbUserRoleTableRoleField;
+
+
+
+
+    /**
+     * @return a java.sql.Connection from the database
+     * @throws Exception
+     */
+    public abstract Connection getConnection () throws Exception;
+
+
+
+    /* ------------------------------------------------ */
+    /** Load info from database
+     * @param userName user info to load
+     * @exception SQLException
+     */
+    public UserInfo getUserInfo (String userName)
+        throws Exception
+    {
+        try (Connection connection = getConnection())
+        {
+
+            //query for credential
+            String dbCredential = null;
+            try (PreparedStatement statement = connection.prepareStatement (userQuery))
+            {
+                statement.setString (1, userName);
+                try (ResultSet results = statement.executeQuery())
+                {
+                    if (results.next())
+                    {
+                        dbCredential = results.getString(1);
+                    }
+                }
+            }
+
+            if (dbCredential==null)
+            {
+                return null;
+            }
+
+            //query for role names
+            List<String> roles = new ArrayList<String>();
+            try (PreparedStatement statement = connection.prepareStatement (rolesQuery))
+            {
+                statement.setString (1, userName);
+                try (ResultSet results = statement.executeQuery())
+                {
+                    while (results.next())
+                    {
+                        String roleName = results.getString (1);
+                        roles.add (roleName);
+                    }
+                }
+            }
+
+            return new UserInfo (userName, Credential.getCredential(dbCredential), roles);
+        }
+    }
+
+
+    public void initialize(Subject subject,
+            CallbackHandler callbackHandler,
+            Map<String,?> sharedState,
+            Map<String,?> options)
+    {
+        super.initialize(subject, callbackHandler, sharedState, options);
+
+        //get the user credential query out of the options
+        dbUserTable = (String)options.get("userTable");
+        dbUserTableUserField = (String)options.get("userField");
+        dbUserTableCredentialField = (String)options.get("credentialField");
+
+        userQuery = "select "+dbUserTableCredentialField+" from "+dbUserTable+" where "+dbUserTableUserField+"=?";
+
+
+        //get the user roles query out of the options
+        dbUserRoleTable = (String)options.get("userRoleTable");
+        dbUserRoleTableUserField = (String)options.get("userRoleUserField");
+        dbUserRoleTableRoleField = (String)options.get("userRoleRoleField");
+
+        rolesQuery = "select "+dbUserRoleTableRoleField+" from "+dbUserRoleTable+" where "+dbUserRoleTableUserField+"=?";
+
+        if(LOG.isDebugEnabled())LOG.debug("userQuery = "+userQuery);
+        if(LOG.isDebugEnabled())LOG.debug("rolesQuery = "+rolesQuery);
+    }
+}
diff --git a/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/spi/AbstractLoginModule.java b/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/spi/AbstractLoginModule.java
new file mode 100644
index 0000000..5f6624d
--- /dev/null
+++ b/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/spi/AbstractLoginModule.java
@@ -0,0 +1,303 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.jaas.spi;
+
+import java.io.IOException;
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import javax.security.auth.Subject;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.auth.login.FailedLoginException;
+import javax.security.auth.login.LoginException;
+import javax.security.auth.spi.LoginModule;
+
+import org.eclipse.jetty.jaas.JAASPrincipal;
+import org.eclipse.jetty.jaas.JAASRole;
+import org.eclipse.jetty.jaas.callback.ObjectCallback;
+
+/**
+ * AbstractLoginModule
+ *
+ * Abstract base class for all LoginModules. Subclasses should
+ * just need to implement getUserInfo method.
+ *
+ */
+public abstract class AbstractLoginModule implements LoginModule
+{
+    private CallbackHandler callbackHandler;
+
+    private boolean authState = false;
+    private boolean commitState = false;
+    private JAASUserInfo currentUser;
+    private Subject subject;
+
+    public class JAASUserInfo
+    {
+        private UserInfo user;
+        private Principal principal;
+        private List<JAASRole> roles;
+
+        public JAASUserInfo (UserInfo u)
+        {
+            setUserInfo(u);
+        }
+
+        public String getUserName ()
+        {
+            return this.user.getUserName();
+        }
+
+        public Principal getPrincipal()
+        {
+            return this.principal;
+        }
+
+        public void setUserInfo (UserInfo u)
+        {
+            this.user = u;
+            this.principal = new JAASPrincipal(u.getUserName());
+            this.roles = new ArrayList<JAASRole>();
+            if (u.getRoleNames() != null)
+            {
+                Iterator<String> itor = u.getRoleNames().iterator();
+                while (itor.hasNext())
+                    this.roles.add(new JAASRole((String)itor.next()));
+            }
+        }
+
+        public void setJAASInfo (Subject subject)
+        {
+            subject.getPrincipals().add(this.principal);
+            subject.getPrivateCredentials().add(this.user.getCredential());
+            subject.getPrincipals().addAll(roles);
+        }
+
+        public void unsetJAASInfo (Subject subject)
+        {
+            subject.getPrincipals().remove(this.principal);
+            subject.getPrivateCredentials().remove(this.user.getCredential());
+            subject.getPrincipals().removeAll(this.roles);
+        }
+
+        public boolean checkCredential (Object suppliedCredential)
+        {
+            return this.user.checkCredential(suppliedCredential);
+        }
+    }
+
+
+
+    public Subject getSubject ()
+    {
+        return this.subject;
+    }
+
+    public void setSubject (Subject s)
+    {
+        this.subject = s;
+    }
+
+    public JAASUserInfo getCurrentUser()
+    {
+        return this.currentUser;
+    }
+
+    public void setCurrentUser (JAASUserInfo u)
+    {
+        this.currentUser = u;
+    }
+
+    public CallbackHandler getCallbackHandler()
+    {
+        return this.callbackHandler;
+    }
+
+    public void setCallbackHandler(CallbackHandler h)
+    {
+        this.callbackHandler = h;
+    }
+
+    public boolean isAuthenticated()
+    {
+        return this.authState;
+    }
+
+    public boolean isCommitted ()
+    {
+        return this.commitState;
+    }
+
+    public void setAuthenticated (boolean authState)
+    {
+        this.authState = authState;
+    }
+
+    public void setCommitted (boolean commitState)
+    {
+        this.commitState = commitState;
+    }
+    /**
+     * @see javax.security.auth.spi.LoginModule#abort()
+     * @throws LoginException
+     */
+    public boolean abort() throws LoginException
+    {
+        this.currentUser = null;
+        return (isAuthenticated() && isCommitted());
+    }
+
+    /**
+     * @see javax.security.auth.spi.LoginModule#commit()
+     * @return true if committed, false if not (likely not authenticated)
+     * @throws LoginException
+     */
+    public boolean commit() throws LoginException
+    {
+
+        if (!isAuthenticated())
+        {
+            currentUser = null;
+            setCommitted(false);
+            return false;
+        }
+
+        setCommitted(true);
+        currentUser.setJAASInfo(subject);
+        return true;
+    }
+
+
+    public Callback[] configureCallbacks ()
+    {
+
+        Callback[] callbacks = new Callback[3];
+        callbacks[0] = new NameCallback("Enter user name");
+        callbacks[1] = new ObjectCallback();
+        callbacks[2] = new PasswordCallback("Enter password", false); //only used if framework does not support the ObjectCallback
+        return callbacks;
+    }
+    
+    
+    public boolean isIgnored ()
+    {
+        return false;
+    }
+    
+    
+    public abstract UserInfo getUserInfo (String username) throws Exception;
+
+
+
+    /**
+     * @see javax.security.auth.spi.LoginModule#login()
+     * @return true if is authenticated, false otherwise
+     * @throws LoginException
+     */
+    public boolean login() throws LoginException
+    {
+        try
+        {  
+            if (isIgnored())
+                return false;
+            
+            if (callbackHandler == null)
+                throw new LoginException ("No callback handler");
+
+            Callback[] callbacks = configureCallbacks();
+            callbackHandler.handle(callbacks);
+
+            String webUserName = ((NameCallback)callbacks[0]).getName();
+            Object webCredential = null;
+
+            webCredential = ((ObjectCallback)callbacks[1]).getObject(); //first check if ObjectCallback has the credential
+            if (webCredential == null)
+                webCredential = ((PasswordCallback)callbacks[2]).getPassword(); //use standard PasswordCallback
+
+            if ((webUserName == null) || (webCredential == null))
+            {
+                setAuthenticated(false);
+                throw new FailedLoginException();
+            }
+
+            UserInfo userInfo = getUserInfo(webUserName);
+
+            if (userInfo == null)
+            {
+                setAuthenticated(false);
+                throw new FailedLoginException();
+            }
+
+            currentUser = new JAASUserInfo(userInfo);
+            setAuthenticated(currentUser.checkCredential(webCredential));
+          
+            if (isAuthenticated())
+                return true;
+            else
+                throw new FailedLoginException();
+        }
+        catch (IOException e)
+        {
+            throw new LoginException (e.toString());
+        }
+        catch (UnsupportedCallbackException e)
+        {
+            throw new LoginException (e.toString());
+        }
+        catch (Exception e)
+        {
+            if (e instanceof LoginException)
+                throw (LoginException)e;
+            throw new LoginException (e.toString());
+        }
+    }
+
+    /**
+     * @see javax.security.auth.spi.LoginModule#logout()
+     * @return true always
+     * @throws LoginException
+     */
+    public boolean logout() throws LoginException
+    {
+        this.currentUser.unsetJAASInfo(this.subject);
+        return true;
+    }
+
+    /**
+     * @see javax.security.auth.spi.LoginModule#initialize(javax.security.auth.Subject, javax.security.auth.callback.CallbackHandler, java.util.Map, java.util.Map)
+     * @param subject
+     * @param callbackHandler
+     * @param sharedState
+     * @param options
+     */
+    public void initialize(Subject subject, CallbackHandler callbackHandler,
+            Map<String,?> sharedState, Map<String,?> options)
+    {
+        this.callbackHandler = callbackHandler;
+        this.subject = subject;
+    }
+
+}
diff --git a/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/spi/DataSourceLoginModule.java b/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/spi/DataSourceLoginModule.java
new file mode 100644
index 0000000..5035b38
--- /dev/null
+++ b/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/spi/DataSourceLoginModule.java
@@ -0,0 +1,90 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.jaas.spi;
+
+import java.sql.Connection;
+import java.util.Map;
+
+import javax.naming.InitialContext;
+import javax.naming.NamingException;
+import javax.security.auth.Subject;
+import javax.security.auth.callback.CallbackHandler;
+import javax.sql.DataSource;
+
+/**
+ * DataSourceLoginModule
+ *
+ * A LoginModule that uses a DataSource to retrieve user authentication
+ * and authorisation information.
+ *
+ * @see JDBCLoginModule
+ */
+public class DataSourceLoginModule extends AbstractDatabaseLoginModule
+{
+
+    private String dbJNDIName;
+    private DataSource dataSource;
+
+    /* ------------------------------------------------ */
+    /** Init LoginModule.
+     * Called once by JAAS after new instance created.
+     * @param subject
+     * @param callbackHandler
+     * @param sharedState
+     * @param options
+     */
+    public void initialize(Subject subject,
+                           CallbackHandler callbackHandler,
+                           Map<String,?> sharedState,
+                           Map<String,?> options)
+    {
+        try
+        {
+            super.initialize(subject, callbackHandler, sharedState, options);
+
+            //get the datasource jndi name
+            dbJNDIName = (String)options.get("dbJNDIName");
+
+            InitialContext ic = new InitialContext();
+            dataSource = (DataSource)ic.lookup("java:comp/env/"+dbJNDIName);
+        }
+        catch (NamingException e)
+        {
+            throw new IllegalStateException (e.toString());
+        }
+    }
+
+
+    /**
+     * Get a connection from the DataSource
+     * @see AbstractDatabaseLoginModule#getConnection()
+     * @return the connection for the datasource
+     * @throws Exception
+     */
+    public Connection getConnection ()
+    throws Exception
+    {
+        return dataSource.getConnection();
+    }
+
+
+
+
+
+}
diff --git a/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/spi/JDBCLoginModule.java b/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/spi/JDBCLoginModule.java
new file mode 100644
index 0000000..6a0c106
--- /dev/null
+++ b/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/spi/JDBCLoginModule.java
@@ -0,0 +1,127 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.jaas.spi;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.util.Map;
+
+import javax.security.auth.Subject;
+import javax.security.auth.callback.CallbackHandler;
+
+import org.eclipse.jetty.util.Loader;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+
+/* ---------------------------------------------------- */
+/** JDBCLoginModule
+ * <p>JAAS LoginModule to retrieve user information from
+ *  a database and authenticate the user.
+ *
+ * <p><h4>Notes</h4>
+ * <p>This version uses plain old JDBC connections NOT
+ * Datasources.
+ *
+ * <p><h4>Usage</h4>
+ * <pre>
+ * </pre>
+ *
+ * @version 1.0 Tue Apr 15 2003
+ */
+public class JDBCLoginModule extends AbstractDatabaseLoginModule
+{
+    private static final Logger LOG = Log.getLogger(JDBCLoginModule.class);
+
+    private String dbDriver;
+    private String dbUrl;
+    private String dbUserName;
+    private String dbPassword;
+
+
+    /**
+     * Get a connection from the DriverManager
+     * @see AbstractDatabaseLoginModule#getConnection()
+     * @return the connection for this datasource
+     * @throws Exception
+     */
+    public Connection getConnection ()
+    throws Exception
+    {
+        if (!((dbDriver != null)
+                &&
+                (dbUrl != null)))
+            throw new IllegalStateException ("Database connection information not configured");
+
+        if(LOG.isDebugEnabled())LOG.debug("Connecting using dbDriver="+dbDriver+"+ dbUserName="+dbUserName+", dbPassword="+dbUrl);
+
+        return DriverManager.getConnection (dbUrl,
+                dbUserName,
+                dbPassword);
+    }
+
+
+
+    /* ------------------------------------------------ */
+    /** Init LoginModule.
+     * Called once by JAAS after new instance created.
+     * @param subject
+     * @param callbackHandler
+     * @param sharedState
+     * @param options
+     */
+    public void initialize(Subject subject,
+                           CallbackHandler callbackHandler,
+                           Map<String,?> sharedState,
+                           Map<String,?> options)
+    {
+        try
+        {
+            super.initialize(subject, callbackHandler, sharedState, options);
+
+            //get the jdbc  username/password, jdbc url out of the options
+            dbDriver = (String)options.get("dbDriver");
+            dbUrl = (String)options.get("dbUrl");
+            dbUserName = (String)options.get("dbUserName");
+            dbPassword = (String)options.get("dbPassword");
+
+            if (dbUserName == null)
+                dbUserName = "";
+
+            if (dbPassword == null)
+                dbPassword = "";
+
+            if (dbDriver != null)
+                Loader.loadClass(this.getClass(), dbDriver).newInstance();
+        }
+        catch (ClassNotFoundException e)
+        {
+            throw new IllegalStateException (e.toString());
+        }
+        catch (InstantiationException e)
+        {
+            throw new IllegalStateException (e.toString());
+        }
+        catch (IllegalAccessException e)
+        {
+            throw new IllegalStateException (e.toString());
+        }
+    }
+}
diff --git a/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/spi/LdapLoginModule.java b/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/spi/LdapLoginModule.java
new file mode 100644
index 0000000..d297520
--- /dev/null
+++ b/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/spi/LdapLoginModule.java
@@ -0,0 +1,689 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.jaas.spi;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Properties;
+
+import javax.naming.Context;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.DirContext;
+import javax.naming.directory.InitialDirContext;
+import javax.naming.directory.SearchControls;
+import javax.naming.directory.SearchResult;
+import javax.security.auth.Subject;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.auth.login.LoginException;
+
+import org.eclipse.jetty.jaas.callback.ObjectCallback;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.security.Credential;
+
+/**
+ * A LdapLoginModule for use with JAAS setups
+ * <p/>
+ * The jvm should be started with the following parameter:
+ * <br><br>
+ * <code>
+ * -Djava.security.auth.login.config=etc/ldap-loginModule.conf
+ * </code>
+ * <br><br>
+ * and an example of the ldap-loginModule.conf would be:
+ * <br><br>
+ * <pre>
+ * ldaploginmodule {
+ *    org.eclipse.jetty.server.server.plus.jaas.spi.LdapLoginModule required
+ *    debug="true"
+ *    useLdaps="false"
+ *    contextFactory="com.sun.jndi.ldap.LdapCtxFactory"
+ *    hostname="ldap.example.com"
+ *    port="389"
+ *    bindDn="cn=Directory Manager"
+ *    bindPassword="directory"
+ *    authenticationMethod="simple"
+ *    forceBindingLogin="false"
+ *    userBaseDn="ou=people,dc=alcatel"
+ *    userRdnAttribute="uid"
+ *    userIdAttribute="uid"
+ *    userPasswordAttribute="userPassword"
+ *    userObjectClass="inetOrgPerson"
+ *    roleBaseDn="ou=groups,dc=example,dc=com"
+ *    roleNameAttribute="cn"
+ *    roleMemberAttribute="uniqueMember"
+ *    roleObjectClass="groupOfUniqueNames";
+ *    };
+ *  </pre>
+ *
+ *
+ *
+ *
+ */
+public class LdapLoginModule extends AbstractLoginModule
+{
+    private static final Logger LOG = Log.getLogger(LdapLoginModule.class);
+
+    /**
+     * hostname of the ldap server
+     */
+    private String _hostname;
+
+    /**
+     * port of the ldap server
+     */
+    private int _port;
+
+    /**
+     * Context.SECURITY_AUTHENTICATION
+     */
+    private String _authenticationMethod;
+
+    /**
+     * Context.INITIAL_CONTEXT_FACTORY
+     */
+    private String _contextFactory;
+
+    /**
+     * root DN used to connect to
+     */
+    private String _bindDn;
+
+    /**
+     * password used to connect to the root ldap context
+     */
+    private String _bindPassword;
+
+    /**
+     * object class of a user
+     */
+    private String _userObjectClass = "inetOrgPerson";
+
+    /**
+     * attribute that the principal is located
+     */
+    private String _userRdnAttribute = "uid";
+
+    /**
+     * attribute that the principal is located
+     */
+    private String _userIdAttribute = "cn";
+
+    /**
+     * name of the attribute that a users password is stored under
+     * <p/>
+     * NOTE: not always accessible, see force binding login
+     */
+    private String _userPasswordAttribute = "userPassword";
+
+    /**
+     * base DN where users are to be searched from
+     */
+    private String _userBaseDn;
+
+    /**
+     * base DN where role membership is to be searched from
+     */
+    private String _roleBaseDn;
+
+    /**
+     * object class of roles
+     */
+    private String _roleObjectClass = "groupOfUniqueNames";
+
+    /**
+     * name of the attribute that a username would be under a role class
+     */
+    private String _roleMemberAttribute = "uniqueMember";
+
+    /**
+     * the name of the attribute that a role would be stored under
+     */
+    private String _roleNameAttribute = "roleName";
+
+    private boolean _debug;
+
+    /**
+     * if the getUserInfo can pull a password off of the user then
+     * password comparison is an option for authn, to force binding
+     * login checks, set this to true
+     */
+    private boolean _forceBindingLogin = false;
+
+    /**
+     * When true changes the protocol to ldaps
+     */
+    private boolean _useLdaps = false;
+
+    private DirContext _rootContext;
+
+    /**
+     * get the available information about the user
+     * <p/>
+     * for this LoginModule, the credential can be null which will result in a
+     * binding ldap authentication scenario
+     * <p/>
+     * roles are also an optional concept if required
+     *
+     * @param username
+     * @return the userinfo for the username
+     * @throws Exception
+     */
+    public UserInfo getUserInfo(String username) throws Exception
+    {
+        String pwdCredential = getUserCredentials(username);
+
+        if (pwdCredential == null)
+        {
+            return null;
+        }
+
+        pwdCredential = convertCredentialLdapToJetty(pwdCredential);
+        Credential credential = Credential.getCredential(pwdCredential);
+        List<String> roles = getUserRoles(_rootContext, username);
+
+        return new UserInfo(username, credential, roles);
+    }
+
+    protected String doRFC2254Encoding(String inputString)
+    {
+        StringBuffer buf = new StringBuffer(inputString.length());
+        for (int i = 0; i < inputString.length(); i++)
+        {
+            char c = inputString.charAt(i);
+            switch (c)
+            {
+                case '\\':
+                    buf.append("\\5c");
+                    break;
+                case '*':
+                    buf.append("\\2a");
+                    break;
+                case '(':
+                    buf.append("\\28");
+                    break;
+                case ')':
+                    buf.append("\\29");
+                    break;
+                case '\0':
+                    buf.append("\\00");
+                    break;
+                default:
+                    buf.append(c);
+                    break;
+            }
+        }
+        return buf.toString();
+    }
+
+    /**
+     * attempts to get the users credentials from the users context
+     * <p/>
+     * NOTE: this is not an user authenticated operation
+     *
+     * @param username
+     * @return
+     * @throws LoginException
+     */
+    private String getUserCredentials(String username) throws LoginException
+    {
+        String ldapCredential = null;
+
+        SearchControls ctls = new SearchControls();
+        ctls.setCountLimit(1);
+        ctls.setDerefLinkFlag(true);
+        ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);
+
+        String filter = "(&(objectClass={0})({1}={2}))";
+
+        LOG.debug("Searching for users with filter: \'" + filter + "\'" + " from base dn: " + _userBaseDn);
+
+        try
+        {
+            Object[] filterArguments = {_userObjectClass, _userIdAttribute, username};
+            NamingEnumeration<SearchResult> results = _rootContext.search(_userBaseDn, filter, filterArguments, ctls);
+
+            LOG.debug("Found user?: " + results.hasMoreElements());
+
+            if (!results.hasMoreElements())
+            {
+                throw new LoginException("User not found.");
+            }
+
+            SearchResult result = findUser(username);
+
+            Attributes attributes = result.getAttributes();
+
+            Attribute attribute = attributes.get(_userPasswordAttribute);
+            if (attribute != null)
+            {
+                try
+                {
+                    byte[] value = (byte[]) attribute.get();
+
+                    ldapCredential = new String(value);
+                }
+                catch (NamingException e)
+                {
+                    LOG.debug("no password available under attribute: " + _userPasswordAttribute);
+                }
+            }
+        }
+        catch (NamingException e)
+        {
+            throw new LoginException("Root context binding failure.");
+        }
+
+        LOG.debug("user cred is: " + ldapCredential);
+
+        return ldapCredential;
+    }
+
+    /**
+     * attempts to get the users roles from the root context
+     * <p/>
+     * NOTE: this is not an user authenticated operation
+     *
+     * @param dirContext
+     * @param username
+     * @return
+     * @throws LoginException
+     */
+    private List<String> getUserRoles(DirContext dirContext, String username) throws LoginException, NamingException
+    {
+        String userDn = _userRdnAttribute + "=" + username + "," + _userBaseDn;
+
+        return getUserRolesByDn(dirContext, userDn);
+    }
+
+    private List<String> getUserRolesByDn(DirContext dirContext, String userDn) throws LoginException, NamingException
+    {
+        List<String> roleList = new ArrayList<String>();
+
+        if (dirContext == null || _roleBaseDn == null || _roleMemberAttribute == null || _roleObjectClass == null)
+        {
+            return roleList;
+        }
+
+        SearchControls ctls = new SearchControls();
+        ctls.setDerefLinkFlag(true);
+        ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);
+        ctls.setReturningAttributes(new String[]{_roleNameAttribute});
+
+        String filter = "(&(objectClass={0})({1}={2}))";
+        Object[] filterArguments = {_roleObjectClass, _roleMemberAttribute, userDn};
+        NamingEnumeration<SearchResult> results = dirContext.search(_roleBaseDn, filter, filterArguments, ctls);
+
+        LOG.debug("Found user roles?: " + results.hasMoreElements());
+
+        while (results.hasMoreElements())
+        {
+            SearchResult result = (SearchResult) results.nextElement();
+
+            Attributes attributes = result.getAttributes();
+
+            if (attributes == null)
+            {
+                continue;
+            }
+
+            Attribute roleAttribute = attributes.get(_roleNameAttribute);
+
+            if (roleAttribute == null)
+            {
+                continue;
+            }
+
+            NamingEnumeration<?> roles = roleAttribute.getAll();
+            while (roles.hasMore())
+            {
+                roleList.add(roles.next().toString());
+            }
+        }
+
+        return roleList;
+    }
+
+
+    /**
+     * since ldap uses a context bind for valid authentication checking, we override login()
+     * <p/>
+     * if credentials are not available from the users context or if we are forcing the binding check
+     * then we try a binding authentication check, otherwise if we have the users encoded password then
+     * we can try authentication via that mechanic
+     *
+     * @return true if authenticated, false otherwise
+     * @throws LoginException
+     */
+    public boolean login() throws LoginException
+    {
+        try
+        {
+            if (getCallbackHandler() == null)
+            {
+                throw new LoginException("No callback handler");
+            }
+
+            Callback[] callbacks = configureCallbacks();
+            getCallbackHandler().handle(callbacks);
+
+            String webUserName = ((NameCallback) callbacks[0]).getName();
+            Object webCredential = ((ObjectCallback) callbacks[1]).getObject();
+
+            if (webUserName == null || webCredential == null)
+            {
+                setAuthenticated(false);
+                return isAuthenticated();
+            }
+
+            if (_forceBindingLogin)
+            {
+                return bindingLogin(webUserName, webCredential);
+            }
+
+            // This sets read and the credential
+            UserInfo userInfo = getUserInfo(webUserName);
+
+            if (userInfo == null)
+            {
+                setAuthenticated(false);
+                return false;
+            }
+
+            setCurrentUser(new JAASUserInfo(userInfo));
+
+            if (webCredential instanceof String)
+            {
+                return credentialLogin(Credential.getCredential((String) webCredential));
+            }
+
+            return credentialLogin(webCredential);
+        }
+        catch (UnsupportedCallbackException e)
+        {
+            throw new LoginException("Error obtaining callback information.");
+        }
+        catch (IOException e)
+        {
+            if (_debug)
+            {
+                e.printStackTrace();
+            }
+            throw new LoginException("IO Error performing login.");
+        }
+        catch (Exception e)
+        {
+            if (_debug)
+            {
+                e.printStackTrace();
+            }
+            throw new LoginException("Error obtaining user info.");
+        }
+    }
+
+    /**
+     * password supplied authentication check
+     *
+     * @param webCredential
+     * @return true if authenticated
+     * @throws LoginException
+     */
+    protected boolean credentialLogin(Object webCredential) throws LoginException
+    {
+        setAuthenticated(getCurrentUser().checkCredential(webCredential));
+        return isAuthenticated();
+    }
+
+    /**
+     * binding authentication check
+     * This method of authentication works only if the user branch of the DIT (ldap tree)
+     * has an ACI (access control instruction) that allow the access to any user or at least
+     * for the user that logs in.
+     *
+     * @param username
+     * @param password
+     * @return true always
+     * @throws LoginException
+     */
+    public boolean bindingLogin(String username, Object password) throws LoginException, NamingException
+    {
+        SearchResult searchResult = findUser(username);
+
+        String userDn = searchResult.getNameInNamespace();
+
+        LOG.info("Attempting authentication: " + userDn);
+
+        Hashtable<Object,Object> environment = getEnvironment();
+        environment.put(Context.SECURITY_PRINCIPAL, userDn);
+        environment.put(Context.SECURITY_CREDENTIALS, password);
+
+        DirContext dirContext = new InitialDirContext(environment);
+        List<String> roles = getUserRolesByDn(dirContext, userDn);
+
+        UserInfo userInfo = new UserInfo(username, null, roles);
+        setCurrentUser(new JAASUserInfo(userInfo));
+        setAuthenticated(true);
+
+        return true;
+    }
+
+    private SearchResult findUser(String username) throws NamingException, LoginException
+    {
+        SearchControls ctls = new SearchControls();
+        ctls.setCountLimit(1);
+        ctls.setDerefLinkFlag(true);
+        ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);
+
+        String filter = "(&(objectClass={0})({1}={2}))";
+
+        LOG.info("Searching for users with filter: \'" + filter + "\'" + " from base dn: " + _userBaseDn);
+
+        Object[] filterArguments = new Object[]{
+            _userObjectClass,
+            _userIdAttribute,
+            username
+        };
+        NamingEnumeration<SearchResult> results = _rootContext.search(_userBaseDn, filter, filterArguments, ctls);
+
+        LOG.info("Found user?: " + results.hasMoreElements());
+
+        if (!results.hasMoreElements())
+        {
+            throw new LoginException("User not found.");
+        }
+
+        return (SearchResult) results.nextElement();
+    }
+
+
+    /**
+     * Init LoginModule.
+     * Called once by JAAS after new instance is created.
+     *
+     * @param subject
+     * @param callbackHandler
+     * @param sharedState
+     * @param options
+     */
+    public void initialize(Subject subject,
+                           CallbackHandler callbackHandler,
+                           Map<String,?> sharedState,
+                           Map<String,?> options)
+    {
+        super.initialize(subject, callbackHandler, sharedState, options);
+
+        _hostname = (String) options.get("hostname");
+        _port = Integer.parseInt((String) options.get("port"));
+        _contextFactory = (String) options.get("contextFactory");
+        _bindDn = (String) options.get("bindDn");
+        _bindPassword = (String) options.get("bindPassword");
+        _authenticationMethod = (String) options.get("authenticationMethod");
+
+        _userBaseDn = (String) options.get("userBaseDn");
+
+        _roleBaseDn = (String) options.get("roleBaseDn");
+
+        if (options.containsKey("forceBindingLogin"))
+        {
+            _forceBindingLogin = Boolean.parseBoolean((String) options.get("forceBindingLogin"));
+        }
+
+        if (options.containsKey("useLdaps"))
+        {
+            _useLdaps = Boolean.parseBoolean((String) options.get("useLdaps"));
+        }
+
+        _userObjectClass = getOption(options, "userObjectClass", _userObjectClass);
+        _userRdnAttribute = getOption(options, "userRdnAttribute", _userRdnAttribute);
+        _userIdAttribute = getOption(options, "userIdAttribute", _userIdAttribute);
+        _userPasswordAttribute = getOption(options, "userPasswordAttribute", _userPasswordAttribute);
+        _roleObjectClass = getOption(options, "roleObjectClass", _roleObjectClass);
+        _roleMemberAttribute = getOption(options, "roleMemberAttribute", _roleMemberAttribute);
+        _roleNameAttribute = getOption(options, "roleNameAttribute", _roleNameAttribute);
+        _debug = Boolean.parseBoolean(String.valueOf(getOption(options, "debug", Boolean.toString(_debug))));
+
+        try
+        {
+            _rootContext = new InitialDirContext(getEnvironment());
+        }
+        catch (NamingException ex)
+        {
+            throw new IllegalStateException("Unable to establish root context", ex);
+        }
+    }
+
+    public boolean commit() throws LoginException
+    {
+        try
+        {
+            _rootContext.close();
+        }
+        catch (NamingException e)
+        {
+            throw new LoginException( "error closing root context: " + e.getMessage() );
+        }
+
+        return super.commit();
+    }
+
+    public boolean abort() throws LoginException
+    {
+        try
+        {
+            _rootContext.close();
+        }
+        catch (NamingException e)
+        {
+            throw new LoginException( "error closing root context: " + e.getMessage() );
+        }
+
+        return super.abort();
+    }
+
+    private String getOption(Map<String,?> options, String key, String defaultValue)
+    {
+        Object value = options.get(key);
+
+        if (value == null)
+        {
+            return defaultValue;
+        }
+
+        return (String) value;
+    }
+
+    /**
+     * get the context for connection
+     *
+     * @return the environment details for the context
+     */
+    public Hashtable<Object, Object> getEnvironment()
+    {
+        Properties env = new Properties();
+
+        env.put(Context.INITIAL_CONTEXT_FACTORY, _contextFactory);
+
+        if (_hostname != null)
+        {
+            env.put(Context.PROVIDER_URL, (_useLdaps?"ldaps://":"ldap://") + _hostname + (_port==0?"":":"+_port) +"/");
+        }
+
+        if (_authenticationMethod != null)
+        {
+            env.put(Context.SECURITY_AUTHENTICATION, _authenticationMethod);
+        }
+
+        if (_bindDn != null)
+        {
+            env.put(Context.SECURITY_PRINCIPAL, _bindDn);
+        }
+
+        if (_bindPassword != null)
+        {
+            env.put(Context.SECURITY_CREDENTIALS, _bindPassword);
+        }
+
+        return env;
+    }
+
+    public static String convertCredentialJettyToLdap(String encryptedPassword)
+    {
+        if ("MD5:".startsWith(encryptedPassword.toUpperCase(Locale.ENGLISH)))
+        {
+            return "{MD5}" + encryptedPassword.substring("MD5:".length(), encryptedPassword.length());
+        }
+
+        if ("CRYPT:".startsWith(encryptedPassword.toUpperCase(Locale.ENGLISH)))
+        {
+            return "{CRYPT}" + encryptedPassword.substring("CRYPT:".length(), encryptedPassword.length());
+        }
+
+        return encryptedPassword;
+    }
+
+    public static String convertCredentialLdapToJetty(String encryptedPassword)
+    {
+        if (encryptedPassword == null)
+        {
+            return encryptedPassword;
+        }
+
+        if ("{MD5}".startsWith(encryptedPassword.toUpperCase(Locale.ENGLISH)))
+        {
+            return "MD5:" + encryptedPassword.substring("{MD5}".length(), encryptedPassword.length());
+        }
+
+        if ("{CRYPT}".startsWith(encryptedPassword.toUpperCase(Locale.ENGLISH)))
+        {
+            return "CRYPT:" + encryptedPassword.substring("{CRYPT}".length(), encryptedPassword.length());
+        }
+
+        return encryptedPassword;
+    }
+}
diff --git a/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/spi/PropertyFileLoginModule.java b/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/spi/PropertyFileLoginModule.java
new file mode 100644
index 0000000..828c75c
--- /dev/null
+++ b/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/spi/PropertyFileLoginModule.java
@@ -0,0 +1,135 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.jaas.spi;
+
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.security.auth.Subject;
+import javax.security.auth.callback.CallbackHandler;
+
+import org.eclipse.jetty.security.PropertyUserStore;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.security.Credential;
+
+/**
+ * PropertyFileLoginModule
+ *
+ *
+ */
+public class PropertyFileLoginModule extends AbstractLoginModule
+{
+    public static final String DEFAULT_FILENAME = "realm.properties";
+
+    private static final Logger LOG = Log.getLogger(PropertyFileLoginModule.class);
+
+    private static ConcurrentHashMap<String, PropertyUserStore> _propertyUserStores = new ConcurrentHashMap<String, PropertyUserStore>();
+
+    private int _refreshInterval = 0;
+    private String _filename = DEFAULT_FILENAME;
+
+    /**
+     * Read contents of the configured property file.
+     *
+     * @see javax.security.auth.spi.LoginModule#initialize(javax.security.auth.Subject, javax.security.auth.callback.CallbackHandler, java.util.Map,
+     *      java.util.Map)
+     * @param subject
+     * @param callbackHandler
+     * @param sharedState
+     * @param options
+     */
+    public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState, Map<String, ?> options)
+    {
+        super.initialize(subject,callbackHandler,sharedState,options);
+        setupPropertyUserStore(options);
+    }
+
+    private void setupPropertyUserStore(Map<String, ?> options)
+    {
+        parseConfig(options);
+
+        if (_propertyUserStores.get(_filename) == null)
+        {
+            PropertyUserStore propertyUserStore = new PropertyUserStore();
+            propertyUserStore.setConfig(_filename);
+            propertyUserStore.setRefreshInterval(_refreshInterval);
+
+            PropertyUserStore prev = _propertyUserStores.putIfAbsent(_filename, propertyUserStore);
+            if (prev == null)
+            {
+                LOG.debug("setupPropertyUserStore: Starting new PropertyUserStore. PropertiesFile: " + _filename + " refreshInterval: " + _refreshInterval);
+
+                try
+                {
+                    propertyUserStore.start();
+                }
+                catch (Exception e)
+                {
+                    LOG.warn("Exception while starting propertyUserStore: ",e);
+                }
+            }
+        }
+    }
+
+    private void parseConfig(Map<String, ?> options)
+    {
+        String tmp = (String)options.get("file");
+        _filename = (tmp == null? DEFAULT_FILENAME : tmp);
+        tmp = (String)options.get("refreshInterval");
+        _refreshInterval = (tmp == null?_refreshInterval:Integer.parseInt(tmp));
+    }
+
+    /**
+     * Don't implement this as we want to pre-fetch all of the users.
+     *
+     * @param userName
+     * @throws Exception
+     */
+    public UserInfo getUserInfo(String userName) throws Exception
+    {
+        PropertyUserStore propertyUserStore = _propertyUserStores.get(_filename);
+        if (propertyUserStore == null)
+            throw new IllegalStateException("PropertyUserStore should never be null here!");
+        
+        LOG.debug("Checking PropertyUserStore "+_filename+" for "+userName);
+        UserIdentity userIdentity = propertyUserStore.getUserIdentity(userName);
+        if (userIdentity==null)
+            return null;
+
+        Set<Principal> principals = userIdentity.getSubject().getPrincipals();
+
+        List<String> roles = new ArrayList<String>();
+
+        for ( Principal principal : principals )
+        {
+            roles.add( principal.getName() );
+        }
+
+        Credential credential = (Credential)userIdentity.getSubject().getPrivateCredentials().iterator().next();
+        LOG.debug("Found: " + userName + " in PropertyUserStore "+_filename);
+        return new UserInfo(userName, credential, roles);
+    }
+
+}
diff --git a/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/spi/UserInfo.java b/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/spi/UserInfo.java
new file mode 100644
index 0000000..c15e3ba
--- /dev/null
+++ b/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/spi/UserInfo.java
@@ -0,0 +1,73 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.jaas.spi;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jetty.util.security.Credential;
+
+/**
+ * UserInfo
+ *
+ * This is the information read from the external source
+ * about a user.
+ * 
+ * Can be cached by a UserInfoCache implementation
+ */
+public class UserInfo
+{
+    
+    private String _userName;
+    private Credential _credential;
+    private List<String> _roleNames;
+    
+    
+    public UserInfo (String userName, Credential credential, List<String> roleNames)
+    {
+        _userName = userName;
+        _credential = credential;
+        _roleNames = new ArrayList<String>();
+        if (roleNames != null)
+        {
+            _roleNames.addAll(roleNames);
+        }
+    }
+    
+    public String getUserName()
+    {
+        return this._userName;
+    }
+    
+    public List<String> getRoleNames ()
+    {
+        return new ArrayList<String>(_roleNames);
+    }
+    
+    public boolean checkCredential (Object suppliedCredential)
+    {
+        return _credential.check(suppliedCredential);
+    }
+    
+    protected Credential getCredential ()
+    {
+        return _credential;
+    }
+    
+}
diff --git a/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/spi/package-info.java b/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/spi/package-info.java
new file mode 100644
index 0000000..7435726
--- /dev/null
+++ b/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/spi/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Jaas : Various Jaas Implementations for Jetty
+ */
+package org.eclipse.jetty.jaas.spi;
+
diff --git a/jetty-jaspi/pom.xml b/jetty-jaspi/pom.xml
index c61d21f..55a4b53 100644
--- a/jetty-jaspi/pom.xml
+++ b/jetty-jaspi/pom.xml
@@ -2,7 +2,7 @@
   <parent>
     <groupId>org.eclipse.jetty</groupId>
     <artifactId>jetty-project</artifactId>
-    <version>8.1.17.v20150415</version>
+    <version>9.2.13.v20150730</version>
   </parent>
   <modelVersion>4.0.0</modelVersion>
   <artifactId>jetty-jaspi</artifactId>
@@ -14,6 +14,23 @@
   </properties>
   <build>
     <plugins>
+     <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-assembly-plugin</artifactId>
+        <executions>
+          <execution>
+            <phase>package</phase>
+            <goals>
+              <goal>single</goal>
+            </goals>
+            <configuration>
+              <descriptorRefs>
+                <descriptorRef>config</descriptorRef>
+              </descriptorRefs>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
       <plugin>
         <groupId>org.apache.felix</groupId>
         <artifactId>maven-bundle-plugin</artifactId>
@@ -25,7 +42,7 @@
             </goals>
             <configuration>
               <instructions>
-                <Import-Package>javax.servlet.*;version="2.6.0",*</Import-Package>
+                <Import-Package>javax.servlet.*;version="[2.6.0,3.2)",*</Import-Package>
                 <Export-Package>org.eclipse.jetty.security.jaspi.*;version="${parsedVersion.osgiVersion}"</Export-Package>
               </instructions>
             </configuration>
@@ -60,13 +77,26 @@
       <version>${project.version}</version>
     </dependency>
     <dependency>
-      <groupId>junit</groupId>
-      <artifactId>junit</artifactId>
+      <groupId>org.eclipse.jetty.toolchain</groupId>
+      <artifactId>jetty-test-helper</artifactId>
       <scope>test</scope>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jetty.orbit</groupId>
       <artifactId>javax.security.auth.message</artifactId>
     </dependency>
+    
+    <dependency>
+      <groupId>org.apache.geronimo.components</groupId>
+      <artifactId>geronimo-jaspi</artifactId>
+      <version>2.0.0</version>
+      <exclusions>
+        <exclusion>
+          <groupId>org.apache.geronimo.specs</groupId>
+          <artifactId>geronimo-jaspic_1.0_spec</artifactId>
+        </exclusion>
+      </exclusions>
+      <scope>test</scope>
+    </dependency>
   </dependencies>
 </project>
diff --git a/jetty-jaspi/src/main/config/modules/jaspi.mod b/jetty-jaspi/src/main/config/modules/jaspi.mod
new file mode 100644
index 0000000..e7019ae
--- /dev/null
+++ b/jetty-jaspi/src/main/config/modules/jaspi.mod
@@ -0,0 +1,10 @@
+#
+# Jetty JASPI Module
+#
+
+[depend]
+security
+
+[lib]
+lib/jetty-jaspi-${jetty.version}.jar
+lib/jaspi/*.jar
diff --git a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/JaspiAuthenticator.java b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/JaspiAuthenticator.java
index ad5d17f..682a1df 100644
--- a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/JaspiAuthenticator.java
+++ b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/JaspiAuthenticator.java
@@ -18,6 +18,7 @@
 
 package org.eclipse.jetty.security.jaspi;
 
+import java.io.IOException;
 import java.security.Principal;
 import java.util.Map;
 import java.util.Set;
@@ -32,19 +33,18 @@ import javax.security.auth.message.config.ServerAuthContext;
 import javax.servlet.ServletRequest;
 import javax.servlet.ServletResponse;
 import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpSession;
 
-import org.eclipse.jetty.security.Authenticator;
 import org.eclipse.jetty.security.IdentityService;
 import org.eclipse.jetty.security.ServerAuthException;
 import org.eclipse.jetty.security.UserAuthentication;
 import org.eclipse.jetty.security.authentication.DeferredAuthentication;
 import org.eclipse.jetty.security.authentication.LoginAuthenticator;
 import org.eclipse.jetty.security.authentication.SessionAuthentication;
-import org.eclipse.jetty.security.jaspi.modules.BaseAuthModule;
 import org.eclipse.jetty.server.Authentication;
-import org.eclipse.jetty.server.UserIdentity;
 import org.eclipse.jetty.server.Authentication.User;
+import org.eclipse.jetty.server.UserIdentity;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
 
@@ -194,10 +194,16 @@ public class JaspiAuthenticator extends LoginAuthenticator
                 // we are processing a message in a secureResponse dialog.
                 return Authentication.SEND_SUCCESS;
             }
+            if (authStatus == AuthStatus.FAILURE)
+            {
+                HttpServletResponse response = (HttpServletResponse) messageInfo.getResponseMessage();
+                response.sendError(HttpServletResponse.SC_FORBIDDEN);
+                return Authentication.SEND_FAILURE;
+            }
             // should not happen
-            throw new NullPointerException("No AuthStatus returned");
+            throw new IllegalStateException("No AuthStatus returned");
         }
-        catch (AuthException e)
+        catch (IOException|AuthException e)
         {
             throw new ServerAuthException(e);
         }
diff --git a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/JaspiAuthenticatorFactory.java b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/JaspiAuthenticatorFactory.java
index 6a94b60..0e35e99 100644
--- a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/JaspiAuthenticatorFactory.java
+++ b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/JaspiAuthenticatorFactory.java
@@ -33,10 +33,10 @@ import javax.security.auth.message.config.ServerAuthConfig;
 import javax.servlet.ServletContext;
 
 import org.eclipse.jetty.security.Authenticator;
+import org.eclipse.jetty.security.Authenticator.AuthConfiguration;
 import org.eclipse.jetty.security.DefaultAuthenticatorFactory;
 import org.eclipse.jetty.security.IdentityService;
 import org.eclipse.jetty.security.LoginService;
-import org.eclipse.jetty.security.Authenticator.AuthConfiguration;
 import org.eclipse.jetty.server.Server;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
@@ -102,6 +102,9 @@ public class JaspiAuthenticatorFactory extends DefaultAuthenticatorFactory
 
             Subject serviceSubject=findServiceSubject(server);
             String serverName=findServerName(server,serviceSubject);
+            String contextPath=context.getContextPath();
+            if (contextPath==null || contextPath.length()==0)
+                contextPath="/";
             String appContext = serverName + " " + context.getContextPath();
             
             AuthConfigProvider authConfigProvider = authConfigFactory.getConfigProvider(MESSAGE_LAYER,appContext,listener);
@@ -137,7 +140,7 @@ public class JaspiAuthenticatorFactory extends DefaultAuthenticatorFactory
     {
         if (_serviceSubject!=null)
             return _serviceSubject;
-        List subjects = server.getBeans(Subject.class);
+        List<Subject> subjects = (List<Subject>)server.getBeans(Subject.class);
         if (subjects.size()>0)
             return (Subject)subjects.get(0);
         return null;
diff --git a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/ServletCallbackHandler.java b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/ServletCallbackHandler.java
index 17039bf..ea4136d 100644
--- a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/ServletCallbackHandler.java
+++ b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/ServletCallbackHandler.java
@@ -126,14 +126,14 @@ public class ServletCallbackHandler implements CallbackHandler
     public CallerPrincipalCallback getThreadCallerPrincipalCallback()
     {
         CallerPrincipalCallback callerPrincipalCallback = _callerPrincipals.get();
-        _callerPrincipals.remove();
+        _callerPrincipals.set(null);
         return callerPrincipalCallback;
     }
 
     public GroupPrincipalCallback getThreadGroupPrincipalCallback()
     {
         GroupPrincipalCallback groupPrincipalCallback = _groupPrincipals.get();
-        _groupPrincipals.remove();
+        _groupPrincipals.set(null);
         return groupPrincipalCallback;
     }
 }
diff --git a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/callback/package-info.java b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/callback/package-info.java
new file mode 100644
index 0000000..f3c5fd6
--- /dev/null
+++ b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/callback/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Jaspi : Callbacks
+ */
+package org.eclipse.jetty.security.jaspi.callback;
+
diff --git a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/BaseAuthModule.java b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/BaseAuthModule.java
index 1aac6f1..3de4e33 100644
--- a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/BaseAuthModule.java
+++ b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/BaseAuthModule.java
@@ -19,6 +19,7 @@
 package org.eclipse.jetty.security.jaspi.modules;
 
 import java.io.IOException;
+import java.nio.charset.StandardCharsets;
 import java.util.Map;
 import java.util.Set;
 
@@ -37,16 +38,14 @@ import javax.security.auth.message.module.ServerAuthModule;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import org.eclipse.jetty.util.security.Credential;
-import org.eclipse.jetty.util.security.Password;
 import org.eclipse.jetty.security.authentication.LoginCallbackImpl;
 import org.eclipse.jetty.security.jaspi.JaspiMessageInfo;
 import org.eclipse.jetty.security.jaspi.callback.CredentialValidationCallback;
 import org.eclipse.jetty.util.B64Code;
-import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.security.Credential;
+import org.eclipse.jetty.util.security.Password;
 
 /**
- * @deprecated use *ServerAuthentication
  * @version $Rev: 4792 $ $Date: 2009-03-18 22:55:52 +0100 (Wed, 18 Mar 2009) $
  */
 public class BaseAuthModule implements ServerAuthModule, ServerAuthContext
@@ -119,7 +118,7 @@ public class BaseAuthModule implements ServerAuthModule, ServerAuthContext
     throws IOException, UnsupportedCallbackException
     {
         credentials = credentials.substring(credentials.indexOf(' ')+1);
-        credentials = B64Code.decode(credentials,StringUtil.__ISO_8859_1);
+        credentials = B64Code.decode(credentials, StandardCharsets.ISO_8859_1);
         int i = credentials.indexOf(':');
         String userName = credentials.substring(0,i);
         String password = credentials.substring(i+1);
diff --git a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/BasicAuthModule.java b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/BasicAuthModule.java
index 0fec14d..4821833 100644
--- a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/BasicAuthModule.java
+++ b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/BasicAuthModule.java
@@ -31,10 +31,10 @@ import javax.security.auth.message.MessagePolicy;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import org.eclipse.jetty.http.HttpHeaders;
-import org.eclipse.jetty.util.security.Constraint;
+import org.eclipse.jetty.http.HttpHeader;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.security.Constraint;
 
 /**
  * @deprecated use *ServerAuthentication
@@ -75,7 +75,7 @@ public class BasicAuthModule extends BaseAuthModule
     {
         HttpServletRequest request = (HttpServletRequest) messageInfo.getRequestMessage();
         HttpServletResponse response = (HttpServletResponse) messageInfo.getResponseMessage();
-        String credentials = request.getHeader(HttpHeaders.AUTHORIZATION);
+        String credentials = request.getHeader(HttpHeader.AUTHORIZATION.asString());
 
         try
         {
@@ -87,7 +87,7 @@ public class BasicAuthModule extends BaseAuthModule
             }
 
             if (!isMandatory(messageInfo)) { return AuthStatus.SUCCESS; }
-            response.setHeader(HttpHeaders.WWW_AUTHENTICATE, "basic realm=\"" + realmName + '"');
+            response.setHeader(HttpHeader.WWW_AUTHENTICATE.asString(), "basic realm=\"" + realmName + '"');
             response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
             return AuthStatus.SEND_CONTINUE;
         }
diff --git a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/ClientCertAuthModule.java b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/ClientCertAuthModule.java
index 3304944..94dc89c 100644
--- a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/ClientCertAuthModule.java
+++ b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/ClientCertAuthModule.java
@@ -30,9 +30,9 @@ import javax.security.auth.message.MessageInfo;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
+import org.eclipse.jetty.util.B64Code;
 import org.eclipse.jetty.util.security.Constraint;
 import org.eclipse.jetty.util.security.Password;
-import org.eclipse.jetty.util.B64Code;
 
 /**
  * @deprecated use *ServerAuthentication
diff --git a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/DigestAuthModule.java b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/DigestAuthModule.java
index 348d2be..800cbc9 100644
--- a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/DigestAuthModule.java
+++ b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/DigestAuthModule.java
@@ -19,6 +19,7 @@
 package org.eclipse.jetty.security.jaspi.modules;
 
 import java.io.IOException;
+import java.nio.charset.StandardCharsets;
 import java.security.MessageDigest;
 import java.util.Map;
 
@@ -32,15 +33,14 @@ import javax.security.auth.message.MessagePolicy;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import org.eclipse.jetty.http.HttpHeaders;
-import org.eclipse.jetty.util.security.Constraint;
-import org.eclipse.jetty.util.security.Credential;
+import org.eclipse.jetty.http.HttpHeader;
 import org.eclipse.jetty.util.B64Code;
 import org.eclipse.jetty.util.QuotedStringTokenizer;
-import org.eclipse.jetty.util.StringUtil;
 import org.eclipse.jetty.util.TypeUtil;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.security.Constraint;
+import org.eclipse.jetty.util.security.Credential;
 
 /**
  * @deprecated use *ServerAuthentication
@@ -87,7 +87,7 @@ public class DigestAuthModule extends BaseAuthModule
     {
         HttpServletRequest request = (HttpServletRequest) messageInfo.getRequestMessage();
         HttpServletResponse response = (HttpServletResponse) messageInfo.getResponseMessage();
-        String credentials = request.getHeader(HttpHeaders.AUTHORIZATION);
+        String credentials = request.getHeader(HttpHeader.AUTHORIZATION.asString());
 
         try
         {
@@ -155,7 +155,7 @@ public class DigestAuthModule extends BaseAuthModule
             if (!isMandatory(messageInfo)) { return AuthStatus.SUCCESS; }
             String domain = request.getContextPath();
             if (domain == null) domain = "/";
-            response.setHeader(HttpHeaders.WWW_AUTHENTICATE, "Digest realm=\"" + realmName
+            response.setHeader(HttpHeader.WWW_AUTHENTICATE.asString(), "Digest realm=\"" + realmName
                                                              + "\", domain=\""
                                                              + domain
                                                              + "\", nonce=\""
@@ -306,18 +306,18 @@ public class DigestAuthModule extends BaseAuthModule
                 else
                 {
                     // calc A1 digest
-                    md.update(username.getBytes(StringUtil.__ISO_8859_1));
+                    md.update(username.getBytes(StandardCharsets.ISO_8859_1));
                     md.update((byte) ':');
-                    md.update(realm.getBytes(StringUtil.__ISO_8859_1));
+                    md.update(realm.getBytes(StandardCharsets.ISO_8859_1));
                     md.update((byte) ':');
-                    md.update(password.getBytes(StringUtil.__ISO_8859_1));
+                    md.update(password.getBytes(StandardCharsets.ISO_8859_1));
                     ha1 = md.digest();
                 }
                 // calc A2 digest
                 md.reset();
-                md.update(method.getBytes(StringUtil.__ISO_8859_1));
+                md.update(method.getBytes(StandardCharsets.ISO_8859_1));
                 md.update((byte) ':');
-                md.update(uri.getBytes(StringUtil.__ISO_8859_1));
+                md.update(uri.getBytes(StandardCharsets.ISO_8859_1));
                 byte[] ha2 = md.digest();
 
                 // calc digest
@@ -327,17 +327,17 @@ public class DigestAuthModule extends BaseAuthModule
                 // request-digest = <"> < KD ( H(A1), unq(nonce-value) ":" H(A2)
                 // ) > <">
 
-                md.update(TypeUtil.toString(ha1, 16).getBytes(StringUtil.__ISO_8859_1));
+                md.update(TypeUtil.toString(ha1, 16).getBytes(StandardCharsets.ISO_8859_1));
                 md.update((byte) ':');
-                md.update(nonce.getBytes(StringUtil.__ISO_8859_1));
+                md.update(nonce.getBytes(StandardCharsets.ISO_8859_1));
                 md.update((byte) ':');
-                md.update(nc.getBytes(StringUtil.__ISO_8859_1));
+                md.update(nc.getBytes(StandardCharsets.ISO_8859_1));
                 md.update((byte) ':');
-                md.update(cnonce.getBytes(StringUtil.__ISO_8859_1));
+                md.update(cnonce.getBytes(StandardCharsets.ISO_8859_1));
                 md.update((byte) ':');
-                md.update(qop.getBytes(StringUtil.__ISO_8859_1));
+                md.update(qop.getBytes(StandardCharsets.ISO_8859_1));
                 md.update((byte) ':');
-                md.update(TypeUtil.toString(ha2, 16).getBytes(StringUtil.__ISO_8859_1));
+                md.update(TypeUtil.toString(ha2, 16).getBytes(StandardCharsets.ISO_8859_1));
                 byte[] digest = md.digest();
 
                 // check digest
diff --git a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/FormAuthModule.java b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/FormAuthModule.java
index c0e7074..7b35848 100644
--- a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/FormAuthModule.java
+++ b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/FormAuthModule.java
@@ -19,9 +19,6 @@
 package org.eclipse.jetty.security.jaspi.modules;
 
 import java.io.IOException;
-import java.io.Serializable;
-import java.security.Principal;
-import java.util.Arrays;
 import java.util.Map;
 import java.util.Set;
 
@@ -35,22 +32,18 @@ import javax.security.auth.message.MessagePolicy;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpSession;
-import javax.servlet.http.HttpSessionBindingEvent;
-import javax.servlet.http.HttpSessionBindingListener;
 
-import org.eclipse.jetty.util.security.Constraint;
-import org.eclipse.jetty.util.security.Password;
 import org.eclipse.jetty.security.CrossContextPsuedoSession;
 import org.eclipse.jetty.security.authentication.DeferredAuthentication;
 import org.eclipse.jetty.security.authentication.LoginCallbackImpl;
 import org.eclipse.jetty.security.authentication.SessionAuthentication;
-import org.eclipse.jetty.security.jaspi.callback.CredentialValidationCallback;
-import org.eclipse.jetty.server.Authentication;
 import org.eclipse.jetty.server.UserIdentity;
 import org.eclipse.jetty.util.StringUtil;
 import org.eclipse.jetty.util.URIUtil;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.security.Constraint;
+import org.eclipse.jetty.util.security.Password;
 
 /**
  * @deprecated use *ServerAuthentication
@@ -99,6 +92,9 @@ public class FormAuthModule extends BaseAuthModule
         setErrorPage(errorPage);
     }
 
+    /**
+     * @deprecated
+     */
     public FormAuthModule(CallbackHandler callbackHandler, CrossContextPsuedoSession<UserInfo> ssoSource, 
                           String loginPage, String errorPage)
     {
@@ -168,7 +164,8 @@ public class FormAuthModule extends BaseAuthModule
         HttpSession session = request.getSession(mandatory);
         
         // not mandatory or its the login or login error page don't authenticate
-        if (!mandatory || isLoginOrErrorPage(URIUtil.addPaths(request.getServletPath(),request.getPathInfo()))) return AuthStatus.SUCCESS;
+        if (!mandatory || isLoginOrErrorPage(URIUtil.addPaths(request.getServletPath(),request.getPathInfo()))) 
+            return AuthStatus.SUCCESS;  // TODO return null for do nothing?
 
         try
         {
diff --git a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/package-info.java b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/package-info.java
new file mode 100644
index 0000000..9dd52ea
--- /dev/null
+++ b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Jaspi : Authentication Modules
+ */
+package org.eclipse.jetty.security.jaspi.modules;
+
diff --git a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/package-info.java b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/package-info.java
new file mode 100644
index 0000000..6085a4a
--- /dev/null
+++ b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Jaspi : Java Authentication SPI
+ */
+package org.eclipse.jetty.security.jaspi;
+
diff --git a/jetty-jaspi/src/test/java/org/eclipse/jetty/security/jaspi/HttpHeaderAuthModule.java b/jetty-jaspi/src/test/java/org/eclipse/jetty/security/jaspi/HttpHeaderAuthModule.java
new file mode 100644
index 0000000..36a3035
--- /dev/null
+++ b/jetty-jaspi/src/test/java/org/eclipse/jetty/security/jaspi/HttpHeaderAuthModule.java
@@ -0,0 +1,125 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security.jaspi;
+
+import java.util.Map;
+
+import javax.security.auth.Subject;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.message.AuthException;
+import javax.security.auth.message.AuthStatus;
+import javax.security.auth.message.MessageInfo;
+import javax.security.auth.message.MessagePolicy;
+import javax.security.auth.message.callback.CallerPrincipalCallback;
+import javax.security.auth.message.callback.GroupPrincipalCallback;
+import javax.security.auth.message.module.ServerAuthModule;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Example JASPI Auth Module based on http://www.trajano.net/2014/06/creating-a-simple-jaspic-auth-module/
+ */
+public class HttpHeaderAuthModule implements ServerAuthModule
+{
+
+    /**
+     * Supported message types. For our case we only need to deal with HTTP servlet request and responses. On Java EE 7 this will handle WebSockets as well.
+     */
+    private static final Class<?>[] SUPPORTED_MESSAGE_TYPES = new Class<?>[]
+    { HttpServletRequest.class, HttpServletResponse.class };
+
+    /**
+     * Callback handler that is passed in initialize by the container. This processes the callbacks which are objects that populate the "subject".
+     */
+    private CallbackHandler handler;
+
+    /**
+     * Does nothing of note for what we need.
+     */
+    @Override
+    public void cleanSubject(final MessageInfo messageInfo, final Subject subject) throws AuthException
+    {
+    }
+
+    @SuppressWarnings("rawtypes")
+    @Override
+    public Class[] getSupportedMessageTypes()
+    {
+        return SUPPORTED_MESSAGE_TYPES;
+    }
+
+    /**
+     * Initializes the module. Allows you to pass in options.
+     * 
+     * @param requestPolicy
+     *            request policy, ignored
+     * @param responsePolicy
+     *            response policy, ignored
+     * @param h
+     *            callback handler
+     * @param options
+     *            options
+     */
+    @Override
+    public void initialize(final MessagePolicy requestPolicy, MessagePolicy responsePolicy, CallbackHandler h, Map options) throws AuthException
+    {
+        handler = h;
+    }
+
+    /**
+     * @return AuthStatus.SEND_SUCCESS
+     */
+    @Override
+    public AuthStatus secureResponse(final MessageInfo paramMessageInfo, final Subject subject) throws AuthException
+    {
+        return AuthStatus.SEND_SUCCESS;
+    }
+
+    /**
+     * Validation occurs here.
+     */
+    @Override
+    public AuthStatus validateRequest(final MessageInfo messageInfo, final Subject client, final Subject serviceSubject) throws AuthException
+    {
+
+        // Take the request from the messageInfo structure.
+        final HttpServletRequest req = (HttpServletRequest)messageInfo.getRequestMessage();
+        try
+        {
+            // Get the user name from the header. If not there then fail authentication.
+            final String userName = req.getHeader("X-Forwarded-User");
+            if (userName == null)
+            {
+                return AuthStatus.FAILURE;
+            }
+
+            // Store the user name that was in the header and also set a group.
+            handler.handle(new Callback[]
+            { new CallerPrincipalCallback(client,userName), new GroupPrincipalCallback(client,new String[]
+            { "users" }) });
+            return AuthStatus.SUCCESS;
+        }
+        catch (final Exception e)
+        {
+            throw new AuthException(e.getMessage());
+        }
+    }
+
+}
diff --git a/jetty-jaspi/src/test/java/org/eclipse/jetty/security/jaspi/JaspiTest.java b/jetty-jaspi/src/test/java/org/eclipse/jetty/security/jaspi/JaspiTest.java
new file mode 100644
index 0000000..4bc51cb
--- /dev/null
+++ b/jetty-jaspi/src/test/java/org/eclipse/jetty/security/jaspi/JaspiTest.java
@@ -0,0 +1,167 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security.jaspi;
+
+import static org.hamcrest.Matchers.startsWith;
+import static org.junit.Assert.assertThat;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.security.ConstraintMapping;
+import org.eclipse.jetty.security.ConstraintSecurityHandler;
+import org.eclipse.jetty.security.HashLoginService;
+import org.eclipse.jetty.server.LocalConnector;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.ContextHandlerCollection;
+import org.eclipse.jetty.util.B64Code;
+import org.eclipse.jetty.util.security.Constraint;
+import org.eclipse.jetty.util.security.Password;
+import org.hamcrest.Matchers;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class JaspiTest
+{
+    Server _server;
+    LocalConnector _connector;
+    
+    @Before
+    public void before() throws Exception
+    {
+        System.setProperty("org.apache.geronimo.jaspic.configurationFile","src/test/resources/jaspi.xml");
+        _server = new Server();
+        _connector = new LocalConnector(_server);
+        _server.addConnector(_connector);
+        
+        ContextHandlerCollection contexts = new ContextHandlerCollection();
+        _server.setHandler(contexts);
+        
+        HashLoginService loginService = new HashLoginService("TestRealm");
+        loginService.putUser("user",new Password("password"),new String[]{"users"});
+        loginService.putUser("admin",new Password("secret"),new String[]{"users","admins"});
+        _server.addBean(loginService);
+        
+        ContextHandler context = new ContextHandler();
+        contexts.addHandler(context);
+        context.setContextPath("/ctx");
+        
+        JaspiAuthenticatorFactory jaspiAuthFactory = new JaspiAuthenticatorFactory();
+        
+        ConstraintSecurityHandler security = new ConstraintSecurityHandler();
+        context.setHandler(security);
+        security.setAuthenticatorFactory(jaspiAuthFactory);
+        // security.setAuthenticator(new BasicAuthenticator());
+       
+        Constraint constraint = new Constraint("All","users");
+        constraint.setAuthenticate(true);
+        ConstraintMapping mapping = new ConstraintMapping();
+        mapping.setPathSpec("/jaspi/*");
+        mapping.setConstraint(constraint);
+        security.addConstraintMapping(mapping);
+        
+        TestHandler handler = new TestHandler();
+        security.setHandler(handler);
+        
+        ContextHandler other = new ContextHandler();
+        contexts.addHandler(other);
+        other.setContextPath("/other");
+        ConstraintSecurityHandler securityOther = new ConstraintSecurityHandler();
+        other.setHandler(securityOther);
+        securityOther.setAuthenticatorFactory(jaspiAuthFactory);
+        securityOther.addConstraintMapping(mapping);        
+        securityOther.setHandler(new TestHandler());
+        
+        _server.start();
+    }
+    
+    @After
+    public void after() throws Exception
+    {
+        _server.stop();
+    }
+    
+    @Test
+    public void testNoConstraint() throws Exception
+    {
+        String response = _connector.getResponses("GET /ctx/test HTTP/1.0\n\n");
+        assertThat(response,startsWith("HTTP/1.1 200 OK"));
+    }
+    
+    @Test
+    public void testConstraintNoAuth() throws Exception
+    {
+        String response = _connector.getResponses("GET /ctx/jaspi/test HTTP/1.0\n\n");
+        assertThat(response,startsWith("HTTP/1.1 401 Unauthorized"));
+        assertThat(response,Matchers.containsString("WWW-Authenticate: basic realm=\"TestRealm\""));
+    }
+    
+    @Test
+    public void testConstraintWrongAuth() throws Exception
+    {
+        String response = _connector.getResponses("GET /ctx/jaspi/test HTTP/1.0\n"+
+                                                  "Authorization: Basic " + B64Code.encode("user:wrong") + "\n\n");
+        assertThat(response,startsWith("HTTP/1.1 401 Unauthorized"));
+        assertThat(response,Matchers.containsString("WWW-Authenticate: basic realm=\"TestRealm\""));
+    }
+    
+    @Test
+    public void testConstraintAuth() throws Exception
+    {
+        String response = _connector.getResponses("GET /ctx/jaspi/test HTTP/1.0\n"+
+                                                  "Authorization: Basic " + B64Code.encode("user:password") + "\n\n");
+        assertThat(response,startsWith("HTTP/1.1 200 OK"));
+    }
+    
+    @Test
+    public void testOtherNoAuth() throws Exception
+    {
+        String response = _connector.getResponses("GET /other/test HTTP/1.0\n\n");
+        assertThat(response,startsWith("HTTP/1.1 403 Forbidden"));
+    }
+    
+    @Test
+    public void testOtherAuth() throws Exception
+    {
+        String response = _connector.getResponses("GET /other/test HTTP/1.0\n"+
+                                                  "X-Forwarded-User: user\n\n");
+        assertThat(response,startsWith("HTTP/1.1 200 OK"));
+    }
+    
+    public class TestHandler extends AbstractHandler
+    {
+
+        @Override
+        public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+        {
+            baseRequest.setHandled(true);
+            response.setStatus(200);
+            response.setContentType("text/plain");
+            response.getWriter().println("All OK");
+            response.getWriter().println("requestURI="+request.getRequestURI());
+        }
+    }
+}
diff --git a/jetty-jaspi/src/test/resources/jaspi.xml b/jetty-jaspi/src/test/resources/jaspi.xml
new file mode 100644
index 0000000..23a2ba5
--- /dev/null
+++ b/jetty-jaspi/src/test/resources/jaspi.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<jaspi xmlns="http://geronimo.apache.org/xml/ns/geronimo-jaspi">
+    <configProvider>
+        <messageLayer>HTTP</messageLayer>
+        <appContext>server /ctx</appContext>
+        <description>description</description>
+        <serverAuthConfig>
+            <authenticationContextID>authenticationContextID1</authenticationContextID>
+            <protected>true</protected>
+            <serverAuthContext>
+                <serverAuthModule>
+                    <className>org.eclipse.jetty.security.jaspi.modules.BasicAuthModule</className>
+                    <options>
+                       org.eclipse.jetty.security.jaspi.modules.RealmName=TestRealm
+                    </options>
+                </serverAuthModule>
+            </serverAuthContext>
+        </serverAuthConfig>
+        <persistent>true</persistent>
+    </configProvider>
+    <configProvider>
+        <messageLayer>HTTP</messageLayer>
+        <appContext>server /other</appContext>
+        <description>description</description>
+        <serverAuthConfig>
+            <authenticationContextID>authenticationContextID2</authenticationContextID>
+            <protected>true</protected>
+            <serverAuthContext>
+                <serverAuthModule>
+                    <className>org.eclipse.jetty.security.jaspi.HttpHeaderAuthModule</className>
+                    <options>
+                    </options>
+                </serverAuthModule>
+            </serverAuthContext>
+        </serverAuthConfig>
+        <persistent>true</persistent>
+    </configProvider>
+</jaspi>
diff --git a/jetty-jmx/pom.xml b/jetty-jmx/pom.xml
index eb3db8c..30b5aef 100644
--- a/jetty-jmx/pom.xml
+++ b/jetty-jmx/pom.xml
@@ -2,7 +2,7 @@
   <parent>
     <groupId>org.eclipse.jetty</groupId>
     <artifactId>jetty-project</artifactId>
-    <version>8.1.17.v20150415</version>
+    <version>9.2.13.v20150730</version>
   </parent>
   <modelVersion>4.0.0</modelVersion>
   <artifactId>jetty-jmx</artifactId>
@@ -71,8 +71,8 @@
   </build>
   <dependencies>
     <dependency>
-      <groupId>junit</groupId>
-      <artifactId>junit</artifactId>
+      <groupId>org.eclipse.jetty.toolchain</groupId>
+      <artifactId>jetty-test-helper</artifactId>
       <scope>test</scope>
     </dependency>
     <dependency>
@@ -81,4 +81,5 @@
       <version>${project.version}</version>
     </dependency>
   </dependencies>
+
 </project>
diff --git a/jetty-jmx/src/main/config/etc/jetty-jmx-remote.xml b/jetty-jmx/src/main/config/etc/jetty-jmx-remote.xml
new file mode 100644
index 0000000..3527d8b
--- /dev/null
+++ b/jetty-jmx/src/main/config/etc/jetty-jmx-remote.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0"?>
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
+
+<Configure id="Server" class="org.eclipse.jetty.server.Server">
+  <!-- Add a remote JMX connector. The parameters of the constructor
+       below specify the JMX service URL, and the object name string for the
+       connector server bean. The parameters of the JMXServiceURL constructor
+       specify the protocol that clients will use to connect to the remote JMX
+       connector (RMI), the hostname of the server (local hostname), port number
+       (automatically assigned), and the URL path. Note that URL path contains
+       the RMI registry hostname and port number, that may need to be modified
+       in order to comply with the firewall requirements.
+  -->
+  <Call name="addBean">
+    <Arg>
+      <New id="ConnectorServer" class="org.eclipse.jetty.jmx.ConnectorServer">
+	<Arg>
+	  <New class="javax.management.remote.JMXServiceURL">
+	    <Arg type="java.lang.String">rmi</Arg>
+	    <Arg type="java.lang.String" />
+	    <Arg type="java.lang.Integer"><Property name="jetty.jmxrmiport" default="1099"/></Arg>
+	    <Arg type="java.lang.String">/jndi/rmi://<Property name="jetty.jmxrmihost" default="localhost"/>:<Property name="jetty.jmxrmiport" default="1099"/>/jmxrmi</Arg>
+	  </New>
+	</Arg>
+	<Arg>org.eclipse.jetty.jmx:name=rmiconnectorserver</Arg>
+      </New>
+    </Arg>
+  </Call>
+</Configure>
diff --git a/jetty-jmx/src/main/config/etc/jetty-jmx.xml b/jetty-jmx/src/main/config/etc/jetty-jmx.xml
index 4db0dbb..aca96f7 100644
--- a/jetty-jmx/src/main/config/etc/jetty-jmx.xml
+++ b/jetty-jmx/src/main/config/etc/jetty-jmx.xml
@@ -1,103 +1,43 @@
 <?xml version="1.0"?>
-<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
 
-<!-- ============================================================================ -->
-<!-- To correctly start Jetty with JMX module enabled, this configuration         -->
-<!-- file must appear first in the list of the configuration files.               -->
-<!-- The simplest way to achieve this is to add etc/jetty-jmx.xml as the          -->
-<!-- first file in configuration file list at the end of start.ini file.          -->
-<!-- ============================================================================ -->
 <Configure id="Server" class="org.eclipse.jetty.server.Server">
 
   <!-- =========================================================== -->
   <!-- Set the java.rmi.server.hostname property in case you've    -->
   <!-- got a misconfigured /etc/hosts entry or the like.           -->
   <!-- =========================================================== -->
-  <!-- 
+  <!--
   <Call class="java.lang.System" name="setProperty">
     <Arg>java.rmi.server.hostname</Arg>
     <Arg>127.0.0.1</Arg>
   </Call>
   -->
-  
+
   <!-- =========================================================== -->
-  <!-- Initialize an mbean server                                  -->
+  <!-- Get the platform mbean server                               -->
   <!-- =========================================================== -->
   <Call id="MBeanServer" class="java.lang.management.ManagementFactory"
     name="getPlatformMBeanServer" />
 
   <!-- =========================================================== -->
-  <!-- Initialize the Jetty MBean container                        -->
+  <!-- Initialize the Jetty MBean container -->
   <!-- =========================================================== -->
-  <New id="MBeanContainer" class="org.eclipse.jetty.jmx.MBeanContainer">
-    <Arg><Ref id="MBeanServer" /></Arg>
-    <Call name="start"/>
-  </New>
-
-  <!-- Add to the Server to listen for object events -->
-  <Get id="Container" name="container">
-    <Call name="addEventListener">
-      <Arg><Ref id="MBeanContainer" /></Arg>
-    </Call>
-  </Get>
-
-  <!-- Add to the Server as a managed lifecycle -->
   <Call name="addBean">
-    <Arg><Ref id="MBeanContainer"/></Arg>
-    <Arg type="boolean">true</Arg>
+    <Arg>
+      <New id="MBeanContainer" class="org.eclipse.jetty.jmx.MBeanContainer">
+        <Arg>
+          <Ref refid="MBeanServer" />
+        </Arg>
+      </New>
+    </Arg>
   </Call>
 
   <!-- Add the static log -->
-  <Ref id="MBeanContainer">
-    <Call name="addBean">
-      <Arg>
-        <New class="org.eclipse.jetty.util.log.Log"/>
-      </Arg>
-    </Call>
-  </Ref>
-  
-  <!-- In order to connect to the JMX server remotely from a different
-       process, possibly running on a different host, Jetty JMX module
-       can create a remote JMX connector. It requires RMI registry to
-       be started prior to creating the connector server because the
-       JMX specification uses RMI to facilitate connections.        
-   -->
-
-  <!-- Optionally start the RMI registry. Normally RMI registry runs on
-       port 1099. The argument below can be changed in order to comply
-       with the firewall requirements.
-  -->
-  <!--
-  <Call name="createRegistry" class="java.rmi.registry.LocateRegistry">
-    <Arg type="java.lang.Integer"><SystemProperty name="jetty.jmxrmiport" default="1099"/></Arg>
-    <Call name="sleep" class="java.lang.Thread">
-       <Arg type="java.lang.Integer">1000</Arg>
-    </Call>
-  </Call>
-  -->
- 
-  <!-- Optionally add a remote JMX connector. The parameters of the constructor
-       below specify the JMX service URL, and the object name string for the
-       connector server bean. The parameters of the JMXServiceURL constructor 
-       specify the protocol that clients will use to connect to the remote JMX
-       connector (RMI), the hostname of the server (local hostname), port number
-       (automatically assigned), and the URL path. Note that URL path contains
-       the RMI registry hostname and port number, that may need to be modified
-       in order to comply with the firewall requirements. 
-  -->
-  <!--
-  <New id="ConnectorServer" class="org.eclipse.jetty.jmx.ConnectorServer">
+  <Call name="addBean">
     <Arg>
-      <New class="javax.management.remote.JMXServiceURL">
-        <Arg type="java.lang.String">rmi</Arg>
-        <Arg type="java.lang.String" />
-        <Arg type="java.lang.Integer"><SystemProperty name="jetty.jmxrmiport" default="1099"/></Arg>
-        <Arg type="java.lang.String">/jndi/rmi://<SystemProperty name="jetty.jmxrmihost" default="localhost"/>:<SystemProperty name="jetty.jmxrmiport" default="1099"/>/jmxrmi</Arg>
-      </New>
+      <New class="org.eclipse.jetty.util.log.Log" />
     </Arg>
-    <Arg>org.eclipse.jetty.jmx:name=rmiconnectorserver</Arg>
-    <Call name="start" />
-  </New>
-  -->
+  </Call>
 </Configure>
 
diff --git a/jetty-jmx/src/main/config/modules/jmx-remote.mod b/jetty-jmx/src/main/config/modules/jmx-remote.mod
new file mode 100644
index 0000000..b6be74a
--- /dev/null
+++ b/jetty-jmx/src/main/config/modules/jmx-remote.mod
@@ -0,0 +1,18 @@
+#
+# JMX Remote Module
+#
+
+[depend]
+jmx
+
+[xml]
+etc/jetty-jmx-remote.xml
+
+[ini-template]
+## JMX Configuration
+## Enable for an open port accessible by remote machines
+# jetty.jmxrmihost=localhost
+# jetty.jmxrmiport=1099
+## Strictly speaking you shouldn't need --exec to use this in most environments.
+## If this isn't working, make sure you enable --exec as well
+# -Dcom.sun.management.jmxremote
diff --git a/jetty-jmx/src/main/config/modules/jmx.mod b/jetty-jmx/src/main/config/modules/jmx.mod
new file mode 100644
index 0000000..ee091c7
--- /dev/null
+++ b/jetty-jmx/src/main/config/modules/jmx.mod
@@ -0,0 +1,13 @@
+#
+# JMX Module
+#
+
+[depend]
+server
+
+[lib]
+lib/jetty-jmx-${jetty.version}.jar
+
+[xml]
+etc/jetty-jmx.xml
+
diff --git a/jetty-jmx/src/main/java/org/eclipse/jetty/jmx/ConnectorServer.java b/jetty-jmx/src/main/java/org/eclipse/jetty/jmx/ConnectorServer.java
index 94f56ae..9257478 100644
--- a/jetty-jmx/src/main/java/org/eclipse/jetty/jmx/ConnectorServer.java
+++ b/jetty-jmx/src/main/java/org/eclipse/jetty/jmx/ConnectorServer.java
@@ -48,13 +48,13 @@ public class ConnectorServer extends AbstractLifeCycle
 
     JMXConnectorServer _connectorServer;
     Registry _registry;
-    
+
     /* ------------------------------------------------------------ */
     /**
      * Constructs connector server
-     * 
+     *
      * @param serviceURL the address of the new connector server.
-     * The actual address of the new connector server, as returned 
+     * The actual address of the new connector server, as returned
      * by its getAddress method, will not necessarily be exactly the same.
      * @param name object name string to be assigned to connector server bean
      * @throws Exception
@@ -64,41 +64,41 @@ public class ConnectorServer extends AbstractLifeCycle
     {
         this(serviceURL, null, name);
     }
-    
+
     /* ------------------------------------------------------------ */
     /**
      * Constructs connector server
-     * 
+     *
      * @param svcUrl the address of the new connector server.
-     * The actual address of the new connector server, as returned 
+     * The actual address of the new connector server, as returned
      * by its getAddress method, will not necessarily be exactly the same.
      * @param environment  a set of attributes to control the new connector
      * server's behavior. This parameter can be null. Keys in this map must
      * be Strings. The appropriate type of each associated value depends on
-     * the attribute. The contents of environment are not changed by this call. 
+     * the attribute. The contents of environment are not changed by this call.
      * @param name object name string to be assigned to connector server bean
      * @throws Exception
      */
     public ConnectorServer(JMXServiceURL svcUrl, Map<String,?> environment, String name)
          throws Exception
     {
-    	String urlPath = svcUrl.getURLPath();
-    	int idx = urlPath.indexOf("rmi://");
-    	if (idx > 0)
-    	{
-    	    String hostPort = urlPath.substring(idx+6, urlPath.indexOf('/', idx+6));
-    	    String regHostPort = startRegistry(hostPort);
-    	    if (regHostPort != null) {
-    	        urlPath = urlPath.replace(hostPort,regHostPort);
-    	        svcUrl = new JMXServiceURL(svcUrl.getProtocol(), svcUrl.getHost(), svcUrl.getPort(), urlPath);
-    	    }
-    	}
+        String urlPath = svcUrl.getURLPath();
+        int idx = urlPath.indexOf("rmi://");
+        if (idx > 0)
+        {
+            String hostPort = urlPath.substring(idx+6, urlPath.indexOf('/', idx+6));
+            String regHostPort = startRegistry(hostPort);
+            if (regHostPort != null) {
+                urlPath = urlPath.replace(hostPort,regHostPort);
+                svcUrl = new JMXServiceURL(svcUrl.getProtocol(), svcUrl.getHost(), svcUrl.getPort(), urlPath);
+            }
+        }
         MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer();
         _connectorServer = JMXConnectorServerFactory.newJMXConnectorServer(svcUrl, environment, mbeanServer);
         mbeanServer.registerMBean(_connectorServer,new ObjectName(name));
     }
 
-	/* ------------------------------------------------------------ */
+        /* ------------------------------------------------------------ */
     /**
      * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart()
      */
@@ -107,11 +107,11 @@ public class ConnectorServer extends AbstractLifeCycle
         throws Exception
     {
         _connectorServer.start();
-        ShutdownThread.register(0, this);       
-        
+        ShutdownThread.register(0, this);
+
         LOG.info("JMX Remote URL: {}", _connectorServer.getAddress().toString());
     }
-    
+
     /* ------------------------------------------------------------ */
     /**
      * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStop()
@@ -127,7 +127,7 @@ public class ConnectorServer extends AbstractLifeCycle
 
     /**
      * Check that local RMI registry is used, and ensure it is started. If local RMI registry is being used and not started, start it.
-     * 
+     *
      * @param hostPath
      *            hostname and port number of RMI registry
      * @throws Exception
@@ -170,11 +170,11 @@ public class ConnectorServer extends AbstractLifeCycle
 
             _registry = LocateRegistry.createRegistry(rmiPort);
             Thread.sleep(1000);
-            
+
             rmiHost = InetAddress.getLocalHost().getCanonicalHostName();
             return rmiHost + ':' + Integer.toString(rmiPort);
         }
-        
+
         return null;
     }
 
diff --git a/jetty-jmx/src/main/java/org/eclipse/jetty/jmx/MBeanContainer.java b/jetty-jmx/src/main/java/org/eclipse/jetty/jmx/MBeanContainer.java
index 6bb3253..50d3871 100644
--- a/jetty-jmx/src/main/java/org/eclipse/jetty/jmx/MBeanContainer.java
+++ b/jetty-jmx/src/main/java/org/eclipse/jetty/jmx/MBeanContainer.java
@@ -19,49 +19,40 @@
 package org.eclipse.jetty.jmx;
 
 import java.io.IOException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
 import java.util.Locale;
 import java.util.Map;
-import java.util.Set;
 import java.util.WeakHashMap;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicInteger;
 
+import javax.management.InstanceNotFoundException;
+import javax.management.MBeanRegistrationException;
 import javax.management.MBeanServer;
 import javax.management.ObjectInstance;
 import javax.management.ObjectName;
 
-import org.eclipse.jetty.util.component.AbstractLifeCycle;
-import org.eclipse.jetty.util.component.AggregateLifeCycle;
 import org.eclipse.jetty.util.component.Container;
-import org.eclipse.jetty.util.component.Container.Relationship;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
 import org.eclipse.jetty.util.component.Dumpable;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.util.log.StdErrLog;
-import org.eclipse.jetty.util.thread.ShutdownThread;
 
 /**
  * Container class for the MBean instances
  */
-public class MBeanContainer extends AbstractLifeCycle implements Container.Listener, Dumpable
+public class MBeanContainer implements Container.InheritedListener, Dumpable
 {
     private final static Logger LOG = Log.getLogger(MBeanContainer.class.getName());
-    private final static HashMap<String, Integer> __unique = new HashMap<String, Integer>();
-    
-    public final static void resetUnique()
+    private final static ConcurrentMap<String, AtomicInteger> __unique = new ConcurrentHashMap<String, AtomicInteger>();
+
+    public static void resetUnique()
     {
-        synchronized (__unique)
-        {
-            __unique.clear();
-        }
+        __unique.clear();
     }
     
-    private final MBeanServer _server;
+    private final MBeanServer _mbeanServer;
     private final WeakHashMap<Object, ObjectName> _beans = new WeakHashMap<Object, ObjectName>();
-    private final WeakHashMap<ObjectName,List<Container.Relationship>> _relations = new WeakHashMap<ObjectName,List<Container.Relationship>>();
     private String _domain = null;
 
     /**
@@ -100,7 +91,7 @@ public class MBeanContainer extends AbstractLifeCycle implements Container.Liste
      */
     public MBeanContainer(MBeanServer server)
     {
-        _server = server;
+        _mbeanServer = server;
     }
 
     /**
@@ -110,7 +101,7 @@ public class MBeanContainer extends AbstractLifeCycle implements Container.Liste
      */
     public MBeanServer getMBeanServer()
     {
-        return _server;
+        return _mbeanServer;
     }
 
     /**
@@ -133,123 +124,38 @@ public class MBeanContainer extends AbstractLifeCycle implements Container.Liste
         return _domain;
     }
 
-    /**
-     * Implementation of Container.Listener interface
-     *
-     * @see org.eclipse.jetty.util.component.Container.Listener#add(org.eclipse.jetty.util.component.Container.Relationship)
-     */
-    public synchronized void add(Relationship relationship)
-    {
-        LOG.debug("add {}",relationship);
-        ObjectName parent = _beans.get(relationship.getParent());
-        if (parent == null)
-        {
-            addBean(relationship.getParent());
-            parent = _beans.get(relationship.getParent());
-        }
-
-        ObjectName child = _beans.get(relationship.getChild());
-        if (child == null)
-        {
-            addBean(relationship.getChild());
-            child = _beans.get(relationship.getChild());
-        }
-
-        if (parent != null && child != null)
-        {
-            List<Container.Relationship> rels = _relations.get(parent);
-            if (rels==null)
-            {
-                rels=new ArrayList<Container.Relationship>();
-                _relations.put(parent,rels);
-            }
-            rels.add(relationship);
-        }
-    }
-
-    /**
-     * Implementation of Container.Listener interface
-     *
-     * @see org.eclipse.jetty.util.component.Container.Listener#remove(org.eclipse.jetty.util.component.Container.Relationship)
-     */
-    public synchronized void remove(Relationship relationship)
-    {
-        LOG.debug("remove {}",relationship);
-        ObjectName parent = _beans.get(relationship.getParent());
-        ObjectName child = _beans.get(relationship.getChild());
-
-        if (parent != null && child != null)
-        {
-            List<Container.Relationship> rels = _relations.get(parent);
-            if (rels!=null)
-            {
-                for (Iterator<Container.Relationship> i=rels.iterator();i.hasNext();)
-                {
-                    Container.Relationship r = i.next();
-                    if (relationship.equals(r) || r.getChild()==null)
-                        i.remove();
-                }
-            }
-        }
-    }
 
-    /**
-     * Implementation of Container.Listener interface
-     *
-     * @see org.eclipse.jetty.util.component.Container.Listener#removeBean(java.lang.Object)
-     */
-    public synchronized void removeBean(Object obj)
+    @Override
+    public void beanAdded(Container parent, Object obj)
     {
-        LOG.debug("removeBean {}",obj);
-        ObjectName bean = _beans.remove(obj);
-
-        if (bean != null)
+        if (LOG.isDebugEnabled())
+            LOG.debug("beanAdded {}->{}",parent,obj);
+        
+        // Is their an object name for the parent
+        ObjectName pname=null;
+        if (parent!=null)
         {
-            List<Container.Relationship> beanRelations= _relations.remove(bean);
-            if (beanRelations != null)
-            {
-                LOG.debug("Unregister {}", beanRelations);
-                List<?> removeList = new ArrayList<Object>(beanRelations);
-                for (Object r : removeList)
-                {
-                    Container.Relationship relation = (Relationship)r;
-                    relation.getContainer().update(relation.getParent(), relation.getChild(), null, relation.getRelationship(), true);
-                }
-            }
-
-            try
-            {
-                _server.unregisterMBean(bean);
-                LOG.debug("Unregistered {}", bean);
-            }
-            catch (javax.management.InstanceNotFoundException e)
-            {
-                LOG.ignore(e);
-            }
-            catch (Exception e)
+            pname=_beans.get(parent);
+            if (pname==null)
             {
-                LOG.warn(e);
+                // create the parent bean
+                beanAdded(null,parent);
+                pname=_beans.get(parent);
             }
         }
-    }
-
-    /**
-     * Implementation of Container.Listener interface
-     *
-     * @see org.eclipse.jetty.util.component.Container.Listener#addBean(java.lang.Object)
-     */
-    public synchronized void addBean(Object obj)
-    {
-        LOG.debug("addBean {}",obj);
+        
+        // Does an mbean already exist?
+        if (obj == null || _beans.containsKey(obj))
+            return;
+        
         try
         {
-            if (obj == null || _beans.containsKey(obj))
-                return;
-
+            // Create an MBean for the object
             Object mbean = ObjectMBean.mbeanFor(obj);
             if (mbean == null)
                 return;
 
+            
             ObjectName oname = null;
             if (mbean instanceof ObjectMBean)
             {
@@ -259,56 +165,50 @@ public class MBeanContainer extends AbstractLifeCycle implements Container.Liste
 
             //no override mbean object name, so make a generic one
             if (oname == null)
-            {
+            {      
+                //if no explicit domain, create one
+                String domain = _domain;
+                if (domain == null)
+                    domain = obj.getClass().getPackage().getName();
+
+
                 String type = obj.getClass().getName().toLowerCase(Locale.ENGLISH);
                 int dot = type.lastIndexOf('.');
                 if (dot >= 0)
                     type = type.substring(dot + 1);
 
-                String context = null;
-                if (mbean instanceof ObjectMBean)
-                {
-                    context = makeName(((ObjectMBean)mbean).getObjectContextBasis());
-                }
-
-                String name = null;
-                if (mbean instanceof ObjectMBean)
-                {
-                    name = makeName(((ObjectMBean)mbean).getObjectNameBasis());
-                }
 
                 StringBuffer buf = new StringBuffer();
-                buf.append("type=").append(type);
+
+                String context = (mbean instanceof ObjectMBean)?makeName(((ObjectMBean)mbean).getObjectContextBasis()):null;
+                if (context==null && pname!=null)
+                    context=pname.getKeyProperty("context");
+                                
                 if (context != null && context.length()>1)
-                {
-                    buf.append(buf.length()>0 ? ",":"");
-                    buf.append("context=").append(context);
-                }
+                    buf.append("context=").append(context).append(",");
+                
+                buf.append("type=").append(type);
+
+                String name = (mbean instanceof ObjectMBean)?makeName(((ObjectMBean)mbean).getObjectNameBasis()):context;
                 if (name != null && name.length()>1)
-                {
-                    buf.append(buf.length()>0 ? ",":"");
-                    buf.append("name=").append(name);
-                }
-                    
+                    buf.append(",").append("name=").append(name);
+
                 String basis = buf.toString();
-                Integer count;
-                synchronized (__unique)
+                
+                AtomicInteger count = __unique.get(basis);
+                if (count==null)
                 {
-                    count = __unique.get(basis);
-                    count = count == null ? 0 : 1 + count;
-                    __unique.put(basis, count);
+                    count=__unique.putIfAbsent(basis,new AtomicInteger());
+                    if (count==null)
+                        count=__unique.get(basis);
                 }
-
-                //if no explicit domain, create one
-                String domain = _domain;
-                if (domain == null)
-                    domain = obj.getClass().getPackage().getName();
-
-                oname = ObjectName.getInstance(domain + ":" + basis + ",id=" + count);
+                
+                oname = ObjectName.getInstance(domain + ":" + basis + ",id=" + count.getAndIncrement());
             }
 
-            ObjectInstance oinstance = _server.registerMBean(mbean, oname);
-            LOG.debug("Registered {}", oinstance.getObjectName());
+            ObjectInstance oinstance = _mbeanServer.registerMBean(mbean, oname);
+            if (LOG.isDebugEnabled())
+                LOG.debug("Registered {}", oinstance.getObjectName());
             _beans.put(obj, oinstance.getObjectName());
 
         }
@@ -318,6 +218,32 @@ public class MBeanContainer extends AbstractLifeCycle implements Container.Liste
         }
     }
 
+    @Override
+    public void beanRemoved(Container parent, Object obj)
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("beanRemoved {}",obj);
+        ObjectName bean = _beans.remove(obj);
+
+        if (bean != null)
+        {
+            try
+            {
+                _mbeanServer.unregisterMBean(bean);
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Unregistered {}", bean);
+            }
+            catch (javax.management.InstanceNotFoundException e)
+            {
+                LOG.ignore(e);
+            }
+            catch (Exception e)
+            {
+                LOG.warn(e);
+            }
+        }
+    }
+
     /**
      * @param basis name to strip of special characters.
      * @return normalized name
@@ -329,38 +255,32 @@ public class MBeanContainer extends AbstractLifeCycle implements Container.Liste
         return basis.replace(':', '_').replace('*', '_').replace('?', '_').replace('=', '_').replace(',', '_').replace(' ', '_');
     }
 
-    /**
-     * Perform actions needed to start lifecycle
-     *
-     * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart()
-     */
-    public void doStart()
-    {
-        ShutdownThread.register(this);
-    }
-
-    /**
-     * Perform actions needed to stop lifecycle
-     *
-     * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStop()
-     */
-    public void doStop()
+    @Override
+    public void dump(Appendable out, String indent) throws IOException
     {
-        Set<Object> removeSet = new HashSet<Object>(_beans.keySet());
-        for (Object removeObj : removeSet)
-        {
-            removeBean(removeObj);
-        }
+        ContainerLifeCycle.dumpObject(out,this);
+        ContainerLifeCycle.dump(out, indent, _beans.entrySet());
     }
 
-    public void dump(Appendable out, String indent) throws IOException
+    @Override
+    public String dump()
     {
-        AggregateLifeCycle.dumpObject(out,this);
-        AggregateLifeCycle.dump(out, indent, _beans.entrySet());
+        return ContainerLifeCycle.dump(this);
     }
 
-    public String dump()
+    public void destroy()
     {
-        return AggregateLifeCycle.dump(this);
+        for (ObjectName oname : _beans.values())
+            if (oname!=null)
+            {
+                try
+                {
+                    _mbeanServer.unregisterMBean(oname);
+                }
+                catch (MBeanRegistrationException | InstanceNotFoundException e)
+                {
+                    LOG.warn(e);
+                }
+            }
     }
 }
diff --git a/jetty-jmx/src/main/java/org/eclipse/jetty/jmx/ObjectMBean.java b/jetty-jmx/src/main/java/org/eclipse/jetty/jmx/ObjectMBean.java
index fcc8c55..376ab56 100644
--- a/jetty-jmx/src/main/java/org/eclipse/jetty/jmx/ObjectMBean.java
+++ b/jetty-jmx/src/main/java/org/eclipse/jetty/jmx/ObjectMBean.java
@@ -18,20 +18,20 @@
 
 package org.eclipse.jetty.jmx;
 
+import java.lang.annotation.Annotation;
 import java.lang.reflect.Array;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
+import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Enumeration;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Locale;
 import java.util.Map;
-import java.util.MissingResourceException;
-import java.util.ResourceBundle;
 import java.util.Set;
 
 import javax.management.Attribute;
@@ -50,9 +50,12 @@ import javax.management.ObjectName;
 import javax.management.ReflectionException;
 import javax.management.modelmbean.ModelMBean;
 
-import org.eclipse.jetty.util.LazyList;
 import org.eclipse.jetty.util.Loader;
 import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.annotation.ManagedOperation;
+import org.eclipse.jetty.util.annotation.Name;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
 
@@ -73,14 +76,20 @@ public class ObjectMBean implements DynamicMBean
 {
     private static final Logger LOG = Log.getLogger(ObjectMBean.class);
 
-    private static Class[] OBJ_ARG = new Class[]{Object.class};
+    private static Class<?>[] OBJ_ARG = new Class[]{Object.class};
 
     protected Object _managed;
     private MBeanInfo _info;
-    private Map _getters=new HashMap();
-    private Map _setters=new HashMap();
-    private Map _methods=new HashMap();
-    private Set _convert=new HashSet();
+    private Map<String, Method> _getters=new HashMap<String, Method>();
+    private Map<String, Method> _setters=new HashMap<String, Method>();
+    private Map<String, Method> _methods=new HashMap<String, Method>();
+
+    // set of attributes mined from influence hierarchy
+    private Set<String> _attributes = new HashSet<String>();
+
+    // set of attributes that are automatically converted to ObjectName
+    // as they represent other managed beans which can be linked to
+    private Set<String> _convert=new HashSet<String>();
     private ClassLoader _loader;
     private MBeanContainer _mbeanContainer;
 
@@ -110,25 +119,25 @@ public class ObjectMBean implements DynamicMBean
     {
         try
         {
-            Class oClass = o.getClass();
+            Class<?> oClass = o.getClass();
             Object mbean = null;
 
-            while (mbean == null && oClass != null)
+            while ( mbean == null && oClass != null )
             {
                 String pName = oClass.getPackage().getName();
                 String cName = oClass.getName().substring(pName.length() + 1);
                 String mName = pName + ".jmx." + cName + "MBean";
-                
 
                 try
                 {
-                    Class mClass = (Object.class.equals(oClass))?oClass=ObjectMBean.class:Loader.loadClass(oClass,mName,true);
+                    Class<?> mClass = (Object.class.equals(oClass))?oClass=ObjectMBean.class:Loader.loadClass(oClass,mName);
+
                     if (LOG.isDebugEnabled())
-                        LOG.debug("mbeanFor " + o + " mClass=" + mClass);
+                        LOG.debug("ObjectMbean: mbeanFor {} mClass={}", o, mClass);
 
                     try
                     {
-                        Constructor constructor = mClass.getConstructor(OBJ_ARG);
+                        Constructor<?> constructor = mClass.getConstructor(OBJ_ARG);
                         mbean=constructor.newInstance(new Object[]{o});
                     }
                     catch(Exception e)
@@ -142,13 +151,14 @@ public class ObjectMBean implements DynamicMBean
                     }
 
                     if (LOG.isDebugEnabled())
-                        LOG.debug("mbeanFor " + o + " is " + mbean);
+                        LOG.debug("mbeanFor {} is {}", o, mbean);
+
                     return mbean;
                 }
                 catch (ClassNotFoundException e)
                 {
-                    // The code below was modified to fix bugs 332200 and JETTY-1416 
-                    // The issue was caused by additional information added to the 
+                    // The code below was modified to fix bugs 332200 and JETTY-1416
+                    // The issue was caused by additional information added to the
                     // message after the class name when running in Apache Felix,
                     // as well as before the class name when running in JBoss.
                     if (e.getMessage().contains(mName))
@@ -174,6 +184,7 @@ public class ObjectMBean implements DynamicMBean
         {
             LOG.ignore(e);
         }
+
         return null;
     }
 
@@ -183,22 +194,22 @@ public class ObjectMBean implements DynamicMBean
         _managed = managedObject;
         _loader = Thread.currentThread().getContextClassLoader();
     }
-    
+
     public Object getManagedObject()
     {
         return _managed;
     }
-    
+
     public ObjectName getObjectName()
     {
         return null;
     }
-    
+
     public String getObjectContextBasis()
     {
         return null;
     }
-    
+
     public String getObjectNameBasis()
     {
         return null;
@@ -213,8 +224,8 @@ public class ObjectMBean implements DynamicMBean
     {
         return this._mbeanContainer;
     }
-    
-    
+
+
     public MBeanInfo getMBeanInfo()
     {
         try
@@ -223,84 +234,91 @@ public class ObjectMBean implements DynamicMBean
             {
                 // Start with blank lazy lists attributes etc.
                 String desc=null;
-                Object attributes=null;
-                Object constructors=null;
-                Object operations=null;
-                Object notifications=null;
+                List<MBeanAttributeInfo> attributes = new ArrayList<MBeanAttributeInfo>();
+                List<MBeanConstructorInfo> constructors = new ArrayList<MBeanConstructorInfo>();
+                List<MBeanOperationInfo> operations = new ArrayList<MBeanOperationInfo>();
+                List<MBeanNotificationInfo> notifications = new ArrayList<MBeanNotificationInfo>();
 
                 // Find list of classes that can influence the mbean
-                Class o_class=_managed.getClass();
-                Object influences = findInfluences(null, _managed.getClass());
+                Class<?> o_class=_managed.getClass();
+                List<Class<?>> influences = new ArrayList<Class<?>>();
+                influences.add(this.getClass()); // always add MBean itself
+                influences = findInfluences(influences, _managed.getClass());
+
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Influence Count: {}", influences.size() );
+
+                // Process Type Annotations
+                ManagedObject primary = o_class.getAnnotation( ManagedObject.class);
+
+                if ( primary != null )
+                {
+                    desc = primary.value();
+                }
+                else
+                {
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("No @ManagedObject declared on {}", _managed.getClass());
+                }
 
-                // Set to record defined items
-                Set defined=new HashSet();
 
                 // For each influence
-                for (int i=0;i<LazyList.size(influences);i++)
+                for (int i=0;i<influences.size();i++)
                 {
-                    Class oClass = (Class)LazyList.get(influences, i);
+                    Class<?> oClass = influences.get(i);
 
-                    // look for a bundle defining methods
-                    if (Object.class.equals(oClass))
-                        oClass=ObjectMBean.class;
-                    String pName = oClass.getPackage().getName();
-                    String cName = oClass.getName().substring(pName.length() + 1);
-                    String rName = pName.replace('.', '/') + "/jmx/" + cName+"-mbean";
+                    ManagedObject typeAnnotation = oClass.getAnnotation( ManagedObject.class );
 
-                    try
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("Influenced by: " + oClass.getCanonicalName() );
+
+                    if ( typeAnnotation == null )
                     {
-                        LOG.debug(rName);
-                        ResourceBundle bundle = Loader.getResourceBundle(o_class, rName,true,Locale.getDefault());
+                        if (LOG.isDebugEnabled())
+                            LOG.debug("Annotations not found for: {}", oClass.getCanonicalName() );
+                        continue;
+                    }
 
-                        
-                        // Extract meta data from bundle
-                        Enumeration e = bundle.getKeys();
-                        while (e.hasMoreElements())
-                        {
-                            String key = (String)e.nextElement();
-                            String value = bundle.getString(key);
+                    // Process Method Annotations
 
-                            // Determin if key is for mbean , attribute or for operation
-                            if (key.equals(cName))
-                            {
-                                // set the mbean description
-                                if (desc==null)
-                                    desc=value;
-                            }
-                            else if (key.indexOf('(')>0)
+                    for (Method method : oClass.getDeclaredMethods())
+                    {
+                        ManagedAttribute methodAttributeAnnotation = method.getAnnotation(ManagedAttribute.class);
+
+                        if (methodAttributeAnnotation != null)
+                        {
+                            // TODO sort out how a proper name could get here, its a method name as an attribute at this point.
+                            if (LOG.isDebugEnabled())
+                                LOG.debug("Attribute Annotation found for: {}", method.getName());
+                            MBeanAttributeInfo mai = defineAttribute(method,methodAttributeAnnotation);
+                            if ( mai != null )
                             {
-                                // define an operation
-                                if (!defined.contains(key) && key.indexOf('[')<0)
-                                {
-                                    defined.add(key);
-                                    operations=LazyList.add(operations,defineOperation(key, value, bundle));
-                                }
+                                attributes.add(mai);
                             }
-                            else
+                        }
+
+                        ManagedOperation methodOperationAnnotation = method.getAnnotation(ManagedOperation.class);
+
+                        if (methodOperationAnnotation != null)
+                        {
+                            if (LOG.isDebugEnabled())
+                                LOG.debug("Method Annotation found for: {}", method.getName());
+                            MBeanOperationInfo oi = defineOperation(method,methodOperationAnnotation);
+                            if (oi != null)
                             {
-                                // define an attribute
-                                if (!defined.contains(key))
-                                {
-                                    defined.add(key);
-                                    MBeanAttributeInfo info=defineAttribute(key, value);
-                                    if (info!=null)
-                                        attributes=LazyList.add(attributes,info);
-                                }
+                                operations.add(oi);
                             }
                         }
                     }
-                    catch(MissingResourceException e)
-                    {
-                        LOG.ignore(e);
-                    }
+
                 }
 
                 _info = new MBeanInfo(o_class.getName(),
                                 desc,
-                                (MBeanAttributeInfo[])LazyList.toArray(attributes, MBeanAttributeInfo.class),
-                                (MBeanConstructorInfo[])LazyList.toArray(constructors, MBeanConstructorInfo.class),
-                                (MBeanOperationInfo[])LazyList.toArray(operations, MBeanOperationInfo.class),
-                                (MBeanNotificationInfo[])LazyList.toArray(notifications, MBeanNotificationInfo.class));
+                                (MBeanAttributeInfo[])attributes.toArray(new MBeanAttributeInfo[attributes.size()]),
+                                (MBeanConstructorInfo[])constructors.toArray(new MBeanConstructorInfo[constructors.size()]),
+                                (MBeanOperationInfo[])operations.toArray(new MBeanOperationInfo[operations.size()]),
+                                (MBeanNotificationInfo[])notifications.toArray(new MBeanNotificationInfo[notifications.size()]));
             }
         }
         catch(RuntimeException e)
@@ -317,7 +335,10 @@ public class ObjectMBean implements DynamicMBean
     {
         Method getter = (Method) _getters.get(name);
         if (getter == null)
+        {
             throw new AttributeNotFoundException(name);
+        }
+
         try
         {
             Object o = _managed;
@@ -327,33 +348,63 @@ public class ObjectMBean implements DynamicMBean
             // get the attribute
             Object r=getter.invoke(o, (java.lang.Object[]) null);
 
-            // convert to ObjectName if need be.
-            if (r!=null && _convert.contains(name))
+            // convert to ObjectName if the type has the @ManagedObject annotation
+            if (r!=null )
             {
                 if (r.getClass().isArray())
                 {
-                    ObjectName[] on = new ObjectName[Array.getLength(r)];
-                    for (int i=0;i<on.length;i++)
-                        on[i]=_mbeanContainer.findMBean(Array.get(r, i));
-                    r=on;
+                    if (r.getClass().getComponentType().isAnnotationPresent(ManagedObject.class))
+                    {
+                        ObjectName[] on = new ObjectName[Array.getLength(r)];
+                        for (int i = 0; i < on.length; i++)
+                        {
+                            on[i] = _mbeanContainer.findMBean(Array.get(r,i));
+                        }
+                        r = on;
+                    }
                 }
                 else if (r instanceof Collection<?>)
                 {
+                    @SuppressWarnings("unchecked")
                     Collection<Object> c = (Collection<Object>)r;
-                    ObjectName[] on = new ObjectName[c.size()];
-                    int i=0;
-                    for (Object obj :c)
-                        on[i++]=_mbeanContainer.findMBean(obj);
-                    r=on;
+
+                    if (!c.isEmpty() && c.iterator().next().getClass().isAnnotationPresent(ManagedObject.class))
+                    {
+                        // check the first thing out
+
+                        ObjectName[] on = new ObjectName[c.size()];
+                        int i = 0;
+                        for (Object obj : c)
+                        {
+                            on[i++] = _mbeanContainer.findMBean(obj);
+                        }
+                        r = on;
+                    }
                 }
                 else
                 {
-                    ObjectName mbean = _mbeanContainer.findMBean(r);
-                    if (mbean==null)
-                        return null;
-                    r=mbean;
+                    Class<?> clazz = r.getClass();
+                    
+                    while (clazz != null)
+                    {
+                        if (clazz.isAnnotationPresent(ManagedObject.class))
+                        {
+                            ObjectName mbean = _mbeanContainer.findMBean(r);
+
+                            if (mbean != null)
+                            {    
+                                return mbean;
+                            }
+                            else
+                            {
+                                return null;
+                            }
+                        }                   
+                        clazz = clazz.getSuperclass();
+                    }              
                 }
             }
+
             return r;
         }
         catch (IllegalAccessException e)
@@ -411,7 +462,7 @@ public class ObjectMBean implements DynamicMBean
             {
                 if (value.getClass().isArray())
                 {
-                    Class t=setter.getParameterTypes()[0].getComponentType();
+                    Class<?> t=setter.getParameterTypes()[0].getComponentType();
                     Object na = Array.newInstance(t,Array.getLength(value));
                     for (int i=Array.getLength(value);i-->0;)
                         Array.set(na, i, _mbeanContainer.findBean((ObjectName)Array.get(value, i)));
@@ -439,10 +490,11 @@ public class ObjectMBean implements DynamicMBean
     /* ------------------------------------------------------------ */
     public AttributeList setAttributes(AttributeList attrs)
     {
-        LOG.debug("setAttributes");
+        if (LOG.isDebugEnabled())
+            LOG.debug("setAttributes");
 
         AttributeList results = new AttributeList(attrs.size());
-        Iterator iter = attrs.iterator();
+        Iterator<Object> iter = attrs.iterator();
         while (iter.hasNext())
         {
             try
@@ -463,7 +515,7 @@ public class ObjectMBean implements DynamicMBean
     public Object invoke(String name, Object[] params, String[] signature) throws MBeanException, ReflectionException
     {
         if (LOG.isDebugEnabled())
-            LOG.debug("invoke " + name);
+            LOG.debug("ObjectMBean:invoke " + name);
 
         String methodKey = name + "(";
         if (signature != null)
@@ -480,8 +532,11 @@ public class ObjectMBean implements DynamicMBean
                 throw new NoSuchMethodException(methodKey);
 
             Object o = _managed;
+
             if (method.getDeclaringClass().isInstance(this))
+            {
                 o = this;
+            }
             return method.invoke(o, params);
         }
         catch (NoSuchMethodException e)
@@ -505,32 +560,40 @@ public class ObjectMBean implements DynamicMBean
         }
     }
 
-    private static Object findInfluences(Object influences, Class aClass)
+    private static List<Class<?>> findInfluences(List<Class<?>> influences, Class<?> aClass)
     {
-        if (aClass!=null)
+        if (aClass != null)
         {
-            // This class is an influence
-            influences=LazyList.add(influences,aClass);
+            if (!influences.contains(aClass))
+            {
+                // This class is a new influence
+                influences.add(aClass);
+            }
 
             // So are the super classes
-            influences=findInfluences(influences,aClass.getSuperclass());
+            influences = findInfluences(influences,aClass.getSuperclass());
 
             // So are the interfaces
-            Class[] ifs = aClass.getInterfaces();
-            for (int i=0;ifs!=null && i<ifs.length;i++)
-                influences=findInfluences(influences,ifs[i]);
+            Class<?>[] ifs = aClass.getInterfaces();
+            for (int i = 0; ifs != null && i < ifs.length; i++)
+            {
+                influences = findInfluences(influences,ifs[i]);
+            }
         }
+
         return influences;
     }
 
     /* ------------------------------------------------------------ */
     /**
+     * TODO update to new behavior
+     *
      * Define an attribute on the managed object. The meta data is defined by looking for standard
      * getter and setter methods. Descriptions are obtained with a call to findDescription with the
      * attribute name.
      *
-     * @param name
-     * @param metaData "description" or "access:description" or "type:access:description"  where type is
+     * @param method
+     * @param attributeAnnotation "description" or "access:description" or "type:access:description"  where type is
      * one of: <ul>
      * <li>"Object" The field/method is on the managed object.
      * <li>"MBean" The field/method is on the mbean proxy object
@@ -539,143 +602,151 @@ public class ObjectMBean implements DynamicMBean
      * </ul>
      * the access is either "RW" or "RO".
      */
-    public MBeanAttributeInfo defineAttribute(String name, String metaData)
+    public MBeanAttributeInfo defineAttribute(Method method, ManagedAttribute attributeAnnotation)
     {
-        String description = "";
-        boolean writable = true;
-        boolean onMBean = false;
+        // determine the name of the managed attribute
+        String name = attributeAnnotation.name();
+
+        if ("".equals(name))
+        {
+            name = toVariableName(method.getName());
+        }
+
+        if ( _attributes.contains(name))
+        {
+            return null; // we have an attribute named this already
+        }
+
+        String description = attributeAnnotation.value();
+        boolean readonly = attributeAnnotation.readonly();
+        boolean onMBean = attributeAnnotation.proxied();
+
         boolean convert = false;
 
-        if (metaData!= null)
+        // determine if we should convert
+        Class<?> return_type = method.getReturnType();
+
+        // get the component type
+        Class<?> component_type = return_type;
+        while ( component_type.isArray() )
         {
-            String[] tokens = metaData.split(":", 3);
-            for (int t=0;t<tokens.length-1;t++)
-            {
-                tokens[t]=tokens[t].trim();
-                if ("RO".equals(tokens[t]))
-                    writable=false;
-                else 
-                {
-                    onMBean=("MMBean".equalsIgnoreCase(tokens[t]) || "MBean".equalsIgnoreCase(tokens[t]));
-                    convert=("MMBean".equalsIgnoreCase(tokens[t]) || "MObject".equalsIgnoreCase(tokens[t]));
-                }
-            }
-            description=tokens[tokens.length-1];
+            component_type = component_type.getComponentType();
         }
+           
+        // Test to see if the returnType or any of its super classes are managed objects
+        convert = isAnnotationPresent(component_type, ManagedObject.class);       
         
-
         String uName = name.substring(0, 1).toUpperCase(Locale.ENGLISH) + name.substring(1);
-        Class oClass = onMBean ? this.getClass() : _managed.getClass();
+        Class<?> oClass = onMBean ? this.getClass() : _managed.getClass();
 
         if (LOG.isDebugEnabled())
-            LOG.debug("defineAttribute "+name+" "+onMBean+":"+writable+":"+oClass+":"+description);
+            LOG.debug("defineAttribute {} {}:{}:{}:{}",name,onMBean,readonly,oClass,description);
 
-        Class type = null;
-        Method getter = null;
         Method setter = null;
-        Method[] methods = oClass.getMethods();
-        for (int m = 0; m < methods.length; m++)
+
+        // dig out a setter if one exists
+        if (!readonly)
         {
-            if ((methods[m].getModifiers() & Modifier.PUBLIC) == 0)
-                continue;
+            String declaredSetter = attributeAnnotation.setter();
 
-            // Look for a getter
-            if (methods[m].getName().equals("get" + uName) && methods[m].getParameterTypes().length == 0)
-            {
-                if (getter != null)
-                {
-		    LOG.warn("Multiple mbean getters for attr " + name+ " in "+oClass);
-		    continue;
-		}
-                getter = methods[m];
-                if (type != null && !type.equals(methods[m].getReturnType()))
-                {
-		    LOG.warn("Type conflict for mbean attr " + name+ " in "+oClass);
-		    continue;
-		}
-                type = methods[m].getReturnType();
-            }
+            if (LOG.isDebugEnabled())
+                LOG.debug("DeclaredSetter: {}", declaredSetter);
 
-            // Look for an is getter
-            if (methods[m].getName().equals("is" + uName) && methods[m].getParameterTypes().length == 0)
+            Method[] methods = oClass.getMethods();
+            for (int m = 0; m < methods.length; m++)
             {
-                if (getter != null)
-                {
-		    LOG.warn("Multiple mbean getters for attr " + name+ " in "+oClass);
-		    continue;
-		}
-                getter = methods[m];
-                if (type != null && !type.equals(methods[m].getReturnType()))
-                {
-		    LOG.warn("Type conflict for mbean attr " + name+ " in "+oClass);
-		    continue;
-		}
-                type = methods[m].getReturnType();
-            }
+                if ((methods[m].getModifiers() & Modifier.PUBLIC) == 0)
+                    continue;
 
-            // look for a setter
-            if (writable && methods[m].getName().equals("set" + uName) && methods[m].getParameterTypes().length == 1)
-            {
-                if (setter != null)
+                if (!"".equals(declaredSetter))
                 {
-		    LOG.warn("Multiple setters for mbean attr " + name+ " in "+oClass);
-		    continue;
-		}
-                setter = methods[m];
-                if (type != null && !type.equals(methods[m].getParameterTypes()[0]))
+
+                    // look for a declared setter
+                    if (methods[m].getName().equals(declaredSetter) && methods[m].getParameterTypes().length == 1)
+                    {
+                        if (setter != null)
+                        {
+                            LOG.warn("Multiple setters for mbean attr {} in {}", name, oClass);
+                            continue;
+                        }
+                        setter = methods[m];
+                        if ( !component_type.equals(methods[m].getParameterTypes()[0]))
+                        {
+                            LOG.warn("Type conflict for mbean attr {} in {}", name, oClass);
+                            continue;
+                        }
+                        if (LOG.isDebugEnabled())
+                            LOG.debug("Declared Setter: " + declaredSetter);
+                    }
+                }
+
+                // look for a setter
+                if ( methods[m].getName().equals("set" + uName) && methods[m].getParameterTypes().length == 1)
                 {
-		    LOG.warn("Type conflict for mbean attr " + name+ " in "+oClass);
-		    continue;
-		}
-                type = methods[m].getParameterTypes()[0];
+                    if (setter != null)
+                    {
+                        LOG.warn("Multiple setters for mbean attr {} in {}", name, oClass);
+                        continue;
+                    }
+                    setter = methods[m];
+                    if ( !return_type.equals(methods[m].getParameterTypes()[0]))
+                    {                            
+                        LOG.warn("Type conflict for mbean attr {} in {}", name, oClass);
+                        continue;
+                    }
+                }
             }
         }
-        
+
         if (convert)
         {
-            if (type==null)
+            if (component_type==null)
             {
-	        LOG.warn("No mbean type for " + name+" on "+_managed.getClass());
-		return null;
-	    }
-                
-            if (type.isPrimitive() && !type.isArray())
+                LOG.warn("No mbean type for {} on {}", name, _managed.getClass());
+                return null;
+            }
+
+            if (component_type.isPrimitive() && !component_type.isArray())
             {
-	        LOG.warn("Cannot convert mbean primative " + name);
-		return null;
-	    }
+                LOG.warn("Cannot convert mbean primative {}", name);
+                return null;
+            }
+            if (LOG.isDebugEnabled())
+                LOG.debug("passed convert checks {} for type {}", name, component_type);
         }
 
-        if (getter == null && setter == null)
-        {
-	    LOG.warn("No mbean getter or setters found for " + name+ " in "+oClass);
-	    return null;
-	}
-
         try
         {
             // Remember the methods
-            _getters.put(name, getter);
+            _getters.put(name, method);
             _setters.put(name, setter);
 
             MBeanAttributeInfo info=null;
             if (convert)
             {
                 _convert.add(name);
-                if (type.isArray())
-                    info= new MBeanAttributeInfo(name,OBJECT_NAME_ARRAY_CLASS,description,getter!=null,setter!=null,getter!=null&&getter.getName().startsWith("is"));
 
+                if (component_type.isArray())
+                {
+                    info= new MBeanAttributeInfo(name,OBJECT_NAME_ARRAY_CLASS,description,true,setter!=null,method.getName().startsWith("is"));
+                }
                 else
-                    info= new MBeanAttributeInfo(name,OBJECT_NAME_CLASS,description,getter!=null,setter!=null,getter!=null&&getter.getName().startsWith("is"));
+                {
+                    info= new MBeanAttributeInfo(name,OBJECT_NAME_CLASS,description,true,setter!=null,method.getName().startsWith("is"));
+                }
             }
             else
-                info= new MBeanAttributeInfo(name,description,getter,setter);
+            {
+                info= new MBeanAttributeInfo(name,description,method,setter);
+            }
 
+            _attributes.add(name);
+            
             return info;
         }
         catch (Exception e)
         {
-            LOG.warn(name+": "+metaData, e);
+            LOG.warn(e);
             throw new IllegalArgumentException(e.toString());
         }
     }
@@ -683,6 +754,8 @@ public class ObjectMBean implements DynamicMBean
 
     /* ------------------------------------------------------------ */
     /**
+     *  TODO update to new behavior
+     *
      * Define an operation on the managed object. Defines an operation with parameters. Refection is
      * used to determine find the method and it's return type. The description of the method is
      * found with a call to findDescription on "name(signature)". The name and description of each
@@ -694,77 +767,97 @@ public class ObjectMBean implements DynamicMBean
      * the "Object","MBean", "MMBean" or "MObject" to indicate the method is on the object, the MBean or on the
      * object but converted to an MBean reference, and impact is either "ACTION","INFO","ACTION_INFO" or "UNKNOWN".
      */
-    private MBeanOperationInfo defineOperation(String signature, String metaData, ResourceBundle bundle)
+    private MBeanOperationInfo defineOperation(Method method, ManagedOperation methodAnnotation)
     {
-        String[] tokens=metaData.split(":",3);
-        int i=tokens.length-1;
-        String description=tokens[i--];
-        String impact_name = i<0?"UNKNOWN":tokens[i--].trim();
-        if (i==0)
-            tokens[0]=tokens[0].trim();
-        boolean onMBean= i==0 && ("MBean".equalsIgnoreCase(tokens[0])||"MMBean".equalsIgnoreCase(tokens[0]));
-        boolean convert= i==0 && ("MObject".equalsIgnoreCase(tokens[0])||"MMBean".equalsIgnoreCase(tokens[0]));
+        String description = methodAnnotation.value();
+        boolean onMBean = methodAnnotation.proxied();
+
+        boolean convert = false;
+
+        // determine if we should convert
+        Class<?> returnType = method.getReturnType();
+
+        if ( returnType.isArray() )
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("returnType is array, get component type");
+            returnType = returnType.getComponentType();
+        }
+
+        if ( returnType.isAnnotationPresent(ManagedObject.class))
+        {
+            convert = true;
+        }
+
+        String impactName = methodAnnotation.impact();
 
         if (LOG.isDebugEnabled())
-            LOG.debug("defineOperation "+signature+" "+onMBean+":"+impact_name+":"+description);
+            LOG.debug("defineOperation {} {}:{}:{}", method.getName(), onMBean, impactName, description);
 
-        Class oClass = onMBean ? this.getClass() : _managed.getClass();
+        String signature = method.getName();
 
         try
         {
             // Resolve the impact
             int impact=MBeanOperationInfo.UNKNOWN;
-            if (impact_name==null || impact_name.equals("UNKNOWN"))
+            if (impactName==null || impactName.equals("UNKNOWN"))
                 impact=MBeanOperationInfo.UNKNOWN;
-            else if (impact_name.equals("ACTION"))
+            else if (impactName.equals("ACTION"))
                 impact=MBeanOperationInfo.ACTION;
-            else if (impact_name.equals("INFO"))
+            else if (impactName.equals("INFO"))
                 impact=MBeanOperationInfo.INFO;
-            else if (impact_name.equals("ACTION_INFO"))
+            else if (impactName.equals("ACTION_INFO"))
                 impact=MBeanOperationInfo.ACTION_INFO;
             else
-                LOG.warn("Unknown impact '"+impact_name+"' for "+signature);
+                LOG.warn("Unknown impact '"+impactName+"' for "+signature);
 
 
-            // split the signature
-            String[] parts=signature.split("[\\(\\)]");
-            String method_name=parts[0];
-            String arguments=parts.length==2?parts[1]:null;
-            String[] args=arguments==null?new String[0]:arguments.split(" *, *");
+            Annotation[][] allParameterAnnotations = method.getParameterAnnotations();
+            Class<?>[] methodTypes = method.getParameterTypes();
+            MBeanParameterInfo[] pInfo = new MBeanParameterInfo[allParameterAnnotations.length];
 
-            // Check types and normalize signature.
-            Class[] types = new Class[args.length];
-            MBeanParameterInfo[] pInfo = new MBeanParameterInfo[args.length];
-            signature=method_name;
-            for (i = 0; i < args.length; i++)
+            for ( int i = 0 ; i < allParameterAnnotations.length ; ++i )
             {
-                Class type = TypeUtil.fromName(args[i]);
-                if (type == null)
-                    type = Thread.currentThread().getContextClassLoader().loadClass(args[i]);
-                types[i] = type;
-                args[i] = type.isPrimitive() ? TypeUtil.toName(type) : args[i];
-                signature+=(i>0?",":"(")+args[i];
+                Annotation[] parameterAnnotations = allParameterAnnotations[i];
+
+                for ( Annotation anno : parameterAnnotations )
+                {
+                    if ( anno instanceof Name )
+                    {
+                        Name nameAnnotation = (Name) anno;
+
+                        pInfo[i] = new MBeanParameterInfo(nameAnnotation.value(),methodTypes[i].getName(),nameAnnotation.description());
+                    }
+                }
             }
-            signature+=(i>0?")":"()");
 
-            // Build param infos
-            for (i = 0; i < args.length; i++)
+            signature += "(";
+            for ( int i = 0 ; i < methodTypes.length ; ++i )
             {
-                String param_desc = bundle.getString(signature + "[" + i + "]");
-                parts=param_desc.split(" *: *",2);
-                if (LOG.isDebugEnabled())
-                    LOG.debug(parts[0]+": "+parts[1]);
-                pInfo[i] = new MBeanParameterInfo(parts[0].trim(), args[i], parts[1].trim());
+                signature += methodTypes[i].getName();
+
+                if ( i != methodTypes.length - 1 )
+                {
+                    signature += ",";
+                }
+            }
+            signature += ")";
+
+            Class<?> returnClass = method.getReturnType();
+
+            if (LOG.isDebugEnabled())
+                LOG.debug("Method Cache: " + signature );
+
+            if ( _methods.containsKey(signature) )
+            {
+                return null; // we have an operation for this already
             }
 
-            // build the operation info
-            Method method = oClass.getMethod(method_name, types);
-            Class returnClass = method.getReturnType();
             _methods.put(signature, method);
             if (convert)
                 _convert.add(signature);
 
-            return new MBeanOperationInfo(method_name, description, pInfo, returnClass.isPrimitive() ? TypeUtil.toName(returnClass) : (returnClass.getName()), impact);
+            return new MBeanOperationInfo(method.getName(), description, pInfo, returnClass.isPrimitive() ? TypeUtil.toName(returnClass) : (returnClass.getName()), impact);
         }
         catch (Exception e)
         {
@@ -774,4 +867,39 @@ public class ObjectMBean implements DynamicMBean
 
     }
 
+    protected String toVariableName( String methodName )
+    {
+        String variableName = methodName;
+
+        if ( methodName.startsWith("get") || methodName.startsWith("set") )
+        {
+            variableName = variableName.substring(3);
+        }
+        else if ( methodName.startsWith("is") )
+        {
+            variableName = variableName.substring(2);
+        }
+
+        variableName = variableName.substring(0,1).toLowerCase(Locale.ENGLISH) + variableName.substring(1);
+
+        return variableName;
+    }
+    
+    protected boolean isAnnotationPresent(Class<?> clazz, Class<? extends Annotation> annotation)
+    {
+        Class<?> test = clazz;
+        
+        while (test != null )
+        {  
+            if ( test.isAnnotationPresent(annotation))
+            {
+                return true;
+            }
+            else
+            {
+                test = test.getSuperclass();
+            }
+        }
+        return false;
+    }
 }
diff --git a/jetty-jmx/src/main/java/org/eclipse/jetty/jmx/package-info.java b/jetty-jmx/src/main/java/org/eclipse/jetty/jmx/package-info.java
new file mode 100644
index 0000000..70300a0
--- /dev/null
+++ b/jetty-jmx/src/main/java/org/eclipse/jetty/jmx/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty JMX : Integration for JMX in Jetty
+ */
+package org.eclipse.jetty.jmx;
+
diff --git a/jetty-jmx/src/main/java/org/eclipse/jetty/util/log/jmx/LogMBean.java b/jetty-jmx/src/main/java/org/eclipse/jetty/util/log/jmx/LogMBean.java
index b74cc46..2d25813 100644
--- a/jetty-jmx/src/main/java/org/eclipse/jetty/util/log/jmx/LogMBean.java
+++ b/jetty-jmx/src/main/java/org/eclipse/jetty/util/log/jmx/LogMBean.java
@@ -22,31 +22,38 @@ import java.util.ArrayList;
 import java.util.List;
 
 import org.eclipse.jetty.jmx.ObjectMBean;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.annotation.ManagedOperation;
+import org.eclipse.jetty.util.annotation.Name;
 import org.eclipse.jetty.util.log.Log;
 
 /* ------------------------------------------------------------ */
 /**
  */
+ at ManagedObject("Jetty Logging")
 public class LogMBean extends ObjectMBean
 {
-
     public LogMBean(Object managedObject)
     {
         super(managedObject);
     }
 
+    @ManagedAttribute(value="list of instantiated loggers")
     public List<String> getLoggers()
     {
         List<String> keySet = new ArrayList<String>(Log.getLoggers().keySet());
         return keySet;
     }
 
-    public boolean isDebugEnabled(String logger)
+    @ManagedOperation(value="true if debug enabled for the given logger")
+    public boolean isDebugEnabled(@Name("logger") String logger)
     {
         return Log.getLogger(logger).isDebugEnabled();
     }
-
-    public void setDebugEnabled(String logger, Boolean enabled)
+    
+    @ManagedOperation(value="Set debug enabled for given logger")
+    public void setDebugEnabled(@Name("logger")String logger, @Name("enabled") Boolean enabled)
     {
         Log.getLogger(logger).setDebugEnabled(enabled);
     }
diff --git a/jetty-jmx/src/main/java/org/eclipse/jetty/util/log/jmx/package-info.java b/jetty-jmx/src/main/java/org/eclipse/jetty/util/log/jmx/package-info.java
new file mode 100644
index 0000000..0e410e1
--- /dev/null
+++ b/jetty-jmx/src/main/java/org/eclipse/jetty/util/log/jmx/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty JMX : Jetty Logging JMX Integration
+ */
+package org.eclipse.jetty.util.log.jmx;
+
diff --git a/jetty-jmx/src/main/resources/org/eclipse/jetty/util/component/jmx/AggregateLifeCycle-mbean.properties b/jetty-jmx/src/main/resources/org/eclipse/jetty/util/component/jmx/AggregateLifeCycle-mbean.properties
deleted file mode 100644
index 437f426..0000000
--- a/jetty-jmx/src/main/resources/org/eclipse/jetty/util/component/jmx/AggregateLifeCycle-mbean.properties
+++ /dev/null
@@ -1,2 +0,0 @@
-AggregateLifeCycle: A LifeCycle holding other LifeCycles
-dumpStdErr():Object:INFO:Dump the nested Object state to StdErr
\ No newline at end of file
diff --git a/jetty-jmx/src/main/resources/org/eclipse/jetty/util/component/jmx/Dumpable-mbean.properties b/jetty-jmx/src/main/resources/org/eclipse/jetty/util/component/jmx/Dumpable-mbean.properties
deleted file mode 100644
index 6015f73..0000000
--- a/jetty-jmx/src/main/resources/org/eclipse/jetty/util/component/jmx/Dumpable-mbean.properties
+++ /dev/null
@@ -1,2 +0,0 @@
-Dumpable: Dumpable Object
-dump():Object:INFO:Dump the nested Object state as a String
\ No newline at end of file
diff --git a/jetty-jmx/src/main/resources/org/eclipse/jetty/util/component/jmx/LifeCycle-mbean.properties b/jetty-jmx/src/main/resources/org/eclipse/jetty/util/component/jmx/LifeCycle-mbean.properties
deleted file mode 100644
index 0c0ab61..0000000
--- a/jetty-jmx/src/main/resources/org/eclipse/jetty/util/component/jmx/LifeCycle-mbean.properties
+++ /dev/null
@@ -1,9 +0,0 @@
-LifeCycle: Startable object
-start(): Starts the instance
-stop(): Stops the instance
-running: Instance is started or starting
-started: Instance is started
-starting: Instance is starting
-stopping: Instance is stopping
-stopped: Instance is stopped
-failed: Instance is failed
\ No newline at end of file
diff --git a/jetty-jmx/src/main/resources/org/eclipse/jetty/util/log/jmx/Log-mbean.properties b/jetty-jmx/src/main/resources/org/eclipse/jetty/util/log/jmx/Log-mbean.properties
deleted file mode 100644
index 18abcb1..0000000
--- a/jetty-jmx/src/main/resources/org/eclipse/jetty/util/log/jmx/Log-mbean.properties
+++ /dev/null
@@ -1,8 +0,0 @@
-Log: Jetty Logging implementaton
-loggers:MBean: List of all instantiated loggers
-debugEnabled:RW: True if debug enabled for root logger Log.LOG
-isDebugEnabled(java.lang.String):MBean:INFO: True if debug is enabled for the given logger
-isDebugEnabled(java.lang.String)[0]:loggerName: Name of the logger to return isDebugEnabled for
-setDebugEnabled(java.lang.String,java.lang.Boolean):MBean:ACTION: Set debug enabled for the given logger
-setDebugEnabled(java.lang.String,java.lang.Boolean)[0]:loggerName: Name of the logger to set debug enabled
-setDebugEnabled(java.lang.String,java.lang.Boolean)[1]:enabled: true to enable debug, false otherwise
diff --git a/jetty-jmx/src/main/resources/org/eclipse/jetty/util/thread/jmx/QueuedThreadPool-mbean.properties b/jetty-jmx/src/main/resources/org/eclipse/jetty/util/thread/jmx/QueuedThreadPool-mbean.properties
deleted file mode 100644
index 9bdffd8..0000000
--- a/jetty-jmx/src/main/resources/org/eclipse/jetty/util/thread/jmx/QueuedThreadPool-mbean.properties
+++ /dev/null
@@ -1,16 +0,0 @@
-QueuedThreadPool: A thread pool with no max bound by default
-minThreads: Minimum number of threads in the pool
-maxThreads: Maximum number threads in the pool
-maxQueued: The maximum size of the job queue or -1 for unbounded.
-name: Name of the thread pool
-daemon: Is pool thread using daemon thread
-threadsPriority: The priority of threads in the pool
-maxIdleTimeMs: Maximum time a thread may be idle in ms
-detailedDump: Full stack detail in dump output
-dump(): Dump thread state
-stopThread(long): Stop a pool thread
-stopThread(long)[0]: id:Thread ID
-interruptThread(long): Interrupt a pool thread
-interruptThread(long)[0]: id:Thread ID
-dumpThread(long): Dump a pool thread stack
-dumpThread(long)[0]: id:Thread ID
diff --git a/jetty-jmx/src/main/resources/org/eclipse/jetty/util/thread/jmx/ThreadPool-mbean.properties b/jetty-jmx/src/main/resources/org/eclipse/jetty/util/thread/jmx/ThreadPool-mbean.properties
deleted file mode 100644
index 9ee55bf..0000000
--- a/jetty-jmx/src/main/resources/org/eclipse/jetty/util/thread/jmx/ThreadPool-mbean.properties
+++ /dev/null
@@ -1,4 +0,0 @@
-ThreadPool: Pool of threads
-threads: RO:Number of Threads in pool
-idleThreads: RO:Number of idle Threads in pool
-lowOnThreads: RO:Indicates the pool is low on available threads.
diff --git a/jetty-jmx/src/test/java/com/acme/Derived.java b/jetty-jmx/src/test/java/com/acme/Derived.java
index 3516548..db0f766 100644
--- a/jetty-jmx/src/test/java/com/acme/Derived.java
+++ b/jetty-jmx/src/test/java/com/acme/Derived.java
@@ -18,11 +18,21 @@
 
 package com.acme;
 
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.annotation.ManagedOperation;
+import org.eclipse.jetty.util.annotation.Name;
 
+ at ManagedObject(value="Test the mbean stuff")
 public class Derived extends Base implements Signature
 {
     String fname="Full Name";
 
+    Managed managedInstance = new Managed();
+    
+    SuperManaged superManagedInstance = new SuperManaged();
+    
+    @ManagedAttribute(value="The full name of something", name="fname", setter="setFullName")
     public String getFullName()
     {
         return fname;
@@ -33,18 +43,39 @@ public class Derived extends Base implements Signature
         fname=name;
     }
 
+    @ManagedOperation("publish something")
     public void publish()
     {
         System.err.println("publish");
     }
     
-    public void doodle(String doodle)
+    @ManagedOperation("Doodle something")
+    public void doodle(@Name(value="doodle", description="A description of the argument") String doodle)
     {
         System.err.println("doodle "+doodle);
     }
 
-    public void somethingElse()
+    public String bad()
     {
-        
+        return "bad";
     }
+
+    @ManagedAttribute("sample managed object")
+    public Managed getManagedInstance()
+    {
+        return managedInstance;
+    }
+
+    public void setManagedInstance(Managed managedInstance)
+    {
+        this.managedInstance = managedInstance;
+    }
+    
+    
+    @ManagedAttribute("sample super managed object")
+    public SuperManaged getSuperManagedInstance()
+    {
+        return superManagedInstance;
+    }
+
 }
diff --git a/jetty-jmx/src/test/java/com/acme/Managed.java b/jetty-jmx/src/test/java/com/acme/Managed.java
new file mode 100644
index 0000000..32aa9e1
--- /dev/null
+++ b/jetty-jmx/src/test/java/com/acme/Managed.java
@@ -0,0 +1,46 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package com.acme;
+
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+
+ at ManagedObject(value="Managed Object")
+public class Managed
+{
+    String managed = "foo";
+    
+    @ManagedAttribute("Managed Attribute")
+    public String getManaged()
+    {
+        return managed;
+    }
+
+    public void setManaged(String managed)
+    {
+        this.managed = managed;
+    }
+       
+    
+    public String bad()
+    {
+        return "bad";
+    }
+    
+}
diff --git a/jetty-jmx/src/test/java/com/acme/SuperManaged.java b/jetty-jmx/src/test/java/com/acme/SuperManaged.java
new file mode 100644
index 0000000..437dd60
--- /dev/null
+++ b/jetty-jmx/src/test/java/com/acme/SuperManaged.java
@@ -0,0 +1,29 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package com.acme;
+
+public class SuperManaged extends Managed
+{
+    
+    public String superized()
+    {
+        return "super";
+    }
+    
+}
diff --git a/jetty-jmx/src/test/java/com/acme/jmx/DerivedMBean.java b/jetty-jmx/src/test/java/com/acme/jmx/DerivedMBean.java
new file mode 100644
index 0000000..75dc4c2
--- /dev/null
+++ b/jetty-jmx/src/test/java/com/acme/jmx/DerivedMBean.java
@@ -0,0 +1,52 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package com.acme.jmx;
+
+import org.eclipse.jetty.jmx.ObjectMBean;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.annotation.ManagedOperation;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+import com.acme.Derived;
+
+ at ManagedObject("Derived MBean Wrapper")
+public class DerivedMBean extends ObjectMBean
+{
+    private static final Logger LOG = Log.getLogger(DerivedMBean.class);
+
+    public DerivedMBean(Object managedObject)
+    {
+        super(managedObject);
+    }
+
+    @ManagedOperation("test of proxy operations")
+    public String good()
+    {
+        return "not " + ((Derived)_managed).bad();
+    }
+
+    @ManagedAttribute(value="test of proxy attributes", proxied=true)
+    public String goop()
+    {
+        return "goop";
+    }
+
+}
diff --git a/jetty-jmx/src/test/java/com/acme/jmx/ManagedMBean.java b/jetty-jmx/src/test/java/com/acme/jmx/ManagedMBean.java
new file mode 100644
index 0000000..75825a0
--- /dev/null
+++ b/jetty-jmx/src/test/java/com/acme/jmx/ManagedMBean.java
@@ -0,0 +1,47 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package com.acme.jmx;
+
+import org.eclipse.jetty.jmx.ObjectMBean;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.annotation.ManagedOperation;
+
+import com.acme.Managed;
+
+ at ManagedObject("Managed MBean Wrapper")
+public class ManagedMBean extends ObjectMBean
+{
+    public ManagedMBean(Object managedObject)
+    {
+        super(managedObject);
+    }
+
+    @ManagedOperation("test of proxy operations")
+    public String good()
+    {
+        return "not managed " + ((Managed)_managed).bad();
+    }
+
+    @ManagedAttribute(value="test of proxy attributes", proxied=true)
+    public String goop()
+    {
+        return "goop";
+    }
+}
diff --git a/jetty-jmx/src/test/java/org/eclipse/jetty/jmx/ObjectMBeanTest.java b/jetty-jmx/src/test/java/org/eclipse/jetty/jmx/ObjectMBeanTest.java
index f8979b7..698761d 100644
--- a/jetty-jmx/src/test/java/org/eclipse/jetty/jmx/ObjectMBeanTest.java
+++ b/jetty-jmx/src/test/java/org/eclipse/jetty/jmx/ObjectMBeanTest.java
@@ -18,19 +18,214 @@
 
 package org.eclipse.jetty.jmx;
 
-import static org.junit.Assert.assertTrue;
+import java.lang.management.ManagementFactory;
 
+import javax.management.Attribute;
+import javax.management.MBeanInfo;
+import javax.management.MBeanOperationInfo;
+import javax.management.MBeanParameterInfo;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 
 import com.acme.Derived;
 
+
 public class ObjectMBeanTest
 {
+    private static final Logger LOG = Log.getLogger(ObjectMBeanTest.class);
+
+    private static MBeanContainer container;
+
+    @Before
+    public void before() throws Exception
+    {
+        container = new MBeanContainer(ManagementFactory.getPlatformMBeanServer());
+    }
+
+    @After
+    public void after() throws Exception
+    {
+        container.destroy();
+        container = null;
+    }
+
+    /*
+     * this test uses the com.acme.Derived test classes
+     */
     @Test
-    public void testMbeanInfo()
+    public void testDerivedAttributes() throws Exception
     {
         Derived derived = new Derived();
-        ObjectMBean mbean = new ObjectMBean(derived);
-        assertTrue(mbean.getMBeanInfo()!=null); // TODO do more than just run it
+        ObjectMBean mbean = (ObjectMBean)ObjectMBean.mbeanFor(derived);
+
+        ObjectMBean managed = (ObjectMBean)ObjectMBean.mbeanFor(derived.getManagedInstance());
+        mbean.setMBeanContainer(container);
+        managed.setMBeanContainer(container);
+
+        container.beanAdded(null,derived);
+        container.beanAdded(null,derived.getManagedInstance());
+
+        MBeanInfo toss = managed.getMBeanInfo();
+
+        Assert.assertNotNull(mbean.getMBeanInfo());
+
+        MBeanInfo info = mbean.getMBeanInfo();
+
+        Assert.assertEquals("name does not match", "com.acme.Derived", info.getClassName());
+        Assert.assertEquals("description does not match", "Test the mbean stuff", info.getDescription());
+
+        //for ( MBeanAttributeInfo i : info.getAttributes())
+        //{
+        //    LOG.debug(i.toString());
+        //}
+
+        /*
+         * 2 attributes from lifecycle and 2 from Derived and 1 from MBean
+         */
+        Assert.assertEquals("attribute count does not match", 6, info.getAttributes().length);
+
+        Assert.assertEquals("attribute values does not match", "Full Name", mbean.getAttribute("fname") );
+
+        mbean.setAttribute( new Attribute("fname","Fuller Name"));
+
+        Assert.assertEquals("set attribute value does not match", "Fuller Name", mbean.getAttribute("fname") );
+
+        Assert.assertEquals("proxy attribute values do not match", "goop", mbean.getAttribute("goop") );
+
+        //Thread.sleep(100000);
     }
+
+    @Test
+    public void testDerivedOperations() throws Exception
+    {
+        Derived derived = new Derived();
+        ObjectMBean mbean = (ObjectMBean)ObjectMBean.mbeanFor(derived);
+
+        mbean.setMBeanContainer(container);
+
+        container.beanAdded(null,derived);
+
+        MBeanInfo info = mbean.getMBeanInfo();
+
+        Assert.assertEquals("operation count does not match", 5, info.getOperations().length);
+
+        MBeanOperationInfo[] opinfos = info.getOperations();
+        boolean publish = false;
+        boolean doodle = false;
+        boolean good = false;
+        for ( int i = 0 ; i < opinfos.length; ++i )
+        {
+            MBeanOperationInfo opinfo = opinfos[i];
+
+            if ("publish".equals(opinfo.getName()))
+            {
+                publish = true;
+                Assert.assertEquals("description doesn't match", "publish something", opinfo.getDescription());
+            }
+
+            if ("doodle".equals(opinfo.getName()))
+            {
+                doodle = true;
+                Assert.assertEquals("description doesn't match", "Doodle something", opinfo.getDescription());
+
+                MBeanParameterInfo[] pinfos = opinfo.getSignature();
+
+                Assert.assertEquals("parameter description doesn't match", "A description of the argument", pinfos[0].getDescription());
+                Assert.assertEquals("parameter name doesn't match", "doodle", pinfos[0].getName());
+            }
+
+            // This is a proxied operation on the JMX wrapper
+            if ("good".equals(opinfo.getName()))
+            {
+                good = true;
+
+                Assert.assertEquals("description does not match", "test of proxy operations", opinfo.getDescription());
+                Assert.assertEquals("execution contexts wrong", "not bad", mbean.invoke("good", new Object[] {}, new String[] {}));
+            }
+        }
+
+        Assert.assertTrue("publish operation was not not found", publish);
+        Assert.assertTrue("doodle operation was not not found", doodle);
+        Assert.assertTrue("good operation was not not found", good);
+
+    }
+
+    @Test
+    public void testDerivedObjectAttributes() throws Exception
+    {
+        Derived derived = new Derived();
+        ObjectMBean mbean = (ObjectMBean)ObjectMBean.mbeanFor(derived);
+
+        ObjectMBean managed = (ObjectMBean)ObjectMBean.mbeanFor(derived.getManagedInstance());
+        mbean.setMBeanContainer(container);
+        managed.setMBeanContainer(container);
+
+        Assert.assertNotNull(mbean.getMBeanInfo());
+
+        container.beanAdded(null,derived);
+        container.beanAdded(null,derived.getManagedInstance());
+        container.beanAdded(null,mbean);
+        container.beanAdded(null,managed);
+
+        //Managed managedInstance = (Managed)mbean.getAttribute("managedInstance");
+        //Assert.assertNotNull(managedInstance);
+        //Assert.assertEquals("managed instance returning nonsense", "foo", managedInstance.getManaged());
+
+
+
+    }
+
+    @Test
+    @Ignore("ignore, used in testing jconsole atm")
+    public void testThreadPool() throws Exception
+    {
+
+        Derived derived = new Derived();
+        ObjectMBean mbean = (ObjectMBean)ObjectMBean.mbeanFor(derived);
+
+        ObjectMBean managed = (ObjectMBean)ObjectMBean.mbeanFor(derived.getManagedInstance());
+        mbean.setMBeanContainer(container);
+        managed.setMBeanContainer(container);
+
+        QueuedThreadPool qtp = new QueuedThreadPool();
+
+        ObjectMBean bqtp = (ObjectMBean)ObjectMBean.mbeanFor(qtp);
+
+        bqtp.getMBeanInfo();
+
+        container.beanAdded(null,derived);
+        container.beanAdded(null,derived.getManagedInstance());
+        container.beanAdded(null,mbean);
+        container.beanAdded(null,managed);
+        container.beanAdded(null,qtp);
+
+
+        Thread.sleep(10000000);
+
+    }
+
+    @Test
+    public void testMethodNameMining() throws Exception
+    {
+        ObjectMBean mbean = new ObjectMBean(new Derived());
+
+        Assert.assertEquals("fullName",mbean.toVariableName("getFullName"));
+        Assert.assertEquals("fullName",mbean.toVariableName("getfullName"));
+        Assert.assertEquals("fullName",mbean.toVariableName("isFullName"));
+        Assert.assertEquals("fullName",mbean.toVariableName("isfullName"));
+        Assert.assertEquals("fullName",mbean.toVariableName("setFullName"));
+        Assert.assertEquals("fullName",mbean.toVariableName("setfullName"));
+        Assert.assertEquals("fullName",mbean.toVariableName("FullName"));
+        Assert.assertEquals("fullName",mbean.toVariableName("fullName"));
+    }
+
+
 }
+
diff --git a/jetty-jmx/src/test/resources/org/eclipse/jetty/com/acme/jmx/Derived-mbean.properties b/jetty-jmx/src/test/resources/com/acme/jmx/Derived-mbean.properties
similarity index 100%
rename from jetty-jmx/src/test/resources/org/eclipse/jetty/com/acme/jmx/Derived-mbean.properties
rename to jetty-jmx/src/test/resources/com/acme/jmx/Derived-mbean.properties
diff --git a/jetty-jmx/src/test/resources/jetty-logging.properties b/jetty-jmx/src/test/resources/jetty-logging.properties
new file mode 100644
index 0000000..3d40866
--- /dev/null
+++ b/jetty-jmx/src/test/resources/jetty-logging.properties
@@ -0,0 +1,2 @@
+org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
+org.eclipse.jetty.jmx.LEVEL=WARN
diff --git a/jetty-jndi/pom.xml b/jetty-jndi/pom.xml
index 6ea6009..1705071 100644
--- a/jetty-jndi/pom.xml
+++ b/jetty-jndi/pom.xml
@@ -2,7 +2,7 @@
   <parent>
     <groupId>org.eclipse.jetty</groupId>
     <artifactId>jetty-project</artifactId>
-    <version>8.1.17.v20150415</version>
+    <version>9.2.13.v20150730</version>
   </parent>
   <modelVersion>4.0.0</modelVersion>
   <artifactId>jetty-jndi</artifactId>
@@ -15,6 +15,23 @@
   <build>
     <plugins>
       <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-assembly-plugin</artifactId>
+        <executions>
+          <execution>
+            <phase>package</phase>
+            <goals>
+              <goal>single</goal>
+            </goals>
+            <configuration>
+              <descriptorRefs>
+                <descriptorRef>config</descriptorRef>
+              </descriptorRefs>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
         <groupId>org.apache.felix</groupId>
         <artifactId>maven-bundle-plugin</artifactId>
         <extensions>true</extensions>
@@ -54,32 +71,32 @@
   </build>
   <dependencies>
     <dependency>
-      <groupId>junit</groupId>
-      <artifactId>junit</artifactId>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-util</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.toolchain</groupId>
+      <artifactId>jetty-test-helper</artifactId>
       <scope>test</scope>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-server</artifactId>
+      <artifactId>jetty-webapp</artifactId>
       <version>${project.version}</version>
+      <scope>provided</scope>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jetty.orbit</groupId>
       <artifactId>javax.mail.glassfish</artifactId>
-    </dependency>
-  </dependencies>
-  <profiles>
-    <profile>
-      <id>below-jdk1.6</id>
-      <activation>
-       <jdk>(,1.6)</jdk>
-      </activation>
-      <dependencies>
-        <dependency>
+      <version>1.4.1.v201005082020</version>
+      <scope>provided</scope>
+      <exclusions>
+        <exclusion>
           <groupId>org.eclipse.jetty.orbit</groupId>
           <artifactId>javax.activation</artifactId>
-        </dependency>
-      </dependencies>
-    </profile>
-  </profiles>
+        </exclusion>
+      </exclusions>
+    </dependency>
+  </dependencies>
 </project>
diff --git a/jetty-jndi/src/main/config/modules/jndi.mod b/jetty-jndi/src/main/config/modules/jndi.mod
new file mode 100644
index 0000000..33c077c
--- /dev/null
+++ b/jetty-jndi/src/main/config/modules/jndi.mod
@@ -0,0 +1,11 @@
+#
+# JNDI Support
+#
+
+[depend]
+server
+
+[lib]
+lib/jetty-jndi-${jetty.version}.jar
+lib/jndi/*.jar
+
diff --git a/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/BindingEnumeration.java b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/BindingEnumeration.java
index ef7be29..4d56ee1 100644
--- a/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/BindingEnumeration.java
+++ b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/BindingEnumeration.java
@@ -32,10 +32,10 @@ import javax.naming.NamingException;
  * <p>Used to return results of Context.listBindings();
  *
  * <p><h4>Usage</h4>
- * 
+ *
  */
 public class BindingEnumeration implements NamingEnumeration<Binding>
-{       
+{
     Iterator<Binding> _delegate;
 
     public BindingEnumeration (Iterator<Binding> e)
diff --git a/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/ContextFactory.java b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/ContextFactory.java
index 7344360..8be866c 100644
--- a/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/ContextFactory.java
+++ b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/ContextFactory.java
@@ -32,7 +32,6 @@ import javax.naming.StringRefAddr;
 import javax.naming.spi.ObjectFactory;
 
 import org.eclipse.jetty.server.handler.ContextHandler;
-
 import org.eclipse.jetty.util.log.Logger;
 
 
@@ -41,47 +40,54 @@ import org.eclipse.jetty.util.log.Logger;
  * ContextFactory.java
  *
  * This is an object factory that produces a jndi naming
- * context based on a classloader. 
- * 
+ * context based on a classloader.
+ *
  *  It is used for the java:comp context.
- *  
+ *
  *  This object factory is bound at java:comp. When a
  *  lookup arrives for java:comp,  this object factory
  *  is invoked and will return a context specific to
  *  the caller's environment (so producing the java:comp/env
  *  specific to a webapp).
- *  
+ *
  *  The context selected is based on classloaders. First
  *  we try looking at the thread context classloader if it is set, and walk its
  *  hierarchy, creating a context if none is found. If the thread context classloader
  *  is not set, then we use the classloader associated with the current Context.
  *  
  *  If there is no current context, or no classloader, we return null.
- * 
+ *
  * Created: Fri Jun 27 09:26:40 2003
  *
- * 
- * 
+ *
+ *
  */
 public class ContextFactory implements ObjectFactory
 {
     private static Logger __log = NamingUtil.__log;
-    
+
     /**
      * Map of classloaders to contexts.
      */
     private static final WeakHashMap __contextMap = new WeakHashMap();
-    
+
     /**
      * Threadlocal for injecting a context to use
      * instead of looking up the map.
      */
-    private static final ThreadLocal __threadContext = new ThreadLocal();
-
+    private static final ThreadLocal<Context> __threadContext = new ThreadLocal<Context>();
+    
+    /**
+     * Threadlocal for setting a classloader which must be used 
+     * when finding the comp context.
+     */
+    private static final ThreadLocal<ClassLoader> __threadClassLoader = new ThreadLocal<ClassLoader>();
     
-    /** 
+
+
+    /**
      * Find or create a context which pertains to a classloader.
-     * 
+     *
      * If the thread context classloader is set, we try to find an already-created naming context
      * for it. If one does not exist, we walk its classloader hierarchy until one is found, or we 
      * run out of parent classloaders. In the latter case, we will create a new naming context associated
@@ -102,16 +108,31 @@ public class ContextFactory implements ObjectFactory
     {
         //First, see if we have had a context injected into us to use.
         Context ctx = (Context)__threadContext.get();
-        if (ctx != null) 
+        if (ctx != null)
         {
             if(__log.isDebugEnabled()) __log.debug("Using the Context that is bound on the thread");
             return ctx;
         }
-        
+
+        //See if there is a classloader to use for finding the comp context
+        //Don't use its parent hierarchy if set.
+        ClassLoader loader = (ClassLoader)__threadClassLoader.get();
+        if (loader != null)
+        {
+            if (__log.isDebugEnabled() && loader != null) __log.debug("Using threadlocal classloader");
+            ctx = getContextForClassLoader(loader);
+            if (ctx == null)
+            {
+                ctx = newNamingContext(obj, loader, env, name, nameCtx);
+                __contextMap.put (loader, ctx);
+                if(__log.isDebugEnabled())__log.debug("Made context "+name.get(0)+" for classloader: "+loader);
+            }
+            return ctx;
+        }
        
-        ClassLoader tccl = Thread.currentThread().getContextClassLoader();
-        ClassLoader loader = tccl;
         //If the thread context classloader is set, then try its hierarchy to find a matching context
+        ClassLoader tccl = Thread.currentThread().getContextClassLoader();
+        loader = tccl;      
         if (loader != null)
         {
             if (__log.isDebugEnabled() && loader != null) __log.debug("Trying thread context classloader");
@@ -161,7 +182,6 @@ public class ContextFactory implements ObjectFactory
      * @param env
      * @param name
      * @param parentCtx
-     * @return
      * @throws Exception
      */
     public NamingContext newNamingContext(Object obj, ClassLoader loader, Hashtable env, Name name, Context parentCtx)
@@ -182,7 +202,6 @@ public class ContextFactory implements ObjectFactory
     /**
      * Find the naming Context for the given classloader
      * @param loader
-     * @return
      */
     public Context getContextForClassLoader(ClassLoader loader)
     {
@@ -191,29 +210,35 @@ public class ContextFactory implements ObjectFactory
         
         return (Context)__contextMap.get(loader);
     }
-    
 
     /**
      * Associate the given Context with the current thread.
-     * resetComponentContext method should be called to reset the context.
+     * disassociate method should be called to reset the context.
      * @param ctx the context to associate to the current thread.
      * @return the previous context associated on the thread (can be null)
      */
-    public static Context setComponentContext(final Context ctx) 
+    public static Context associateContext(final Context ctx)
     {
         Context previous = (Context)__threadContext.get();
         __threadContext.set(ctx);
         return previous;
     }
 
-    /**
-     * Set back the context with the given value.
-     * Don't return the previous context, use setComponentContext() method for this.
-     * @param ctx the context to associate to the current thread.
-     */
-    public static void resetComponentContext(final Context ctx) 
+    public static void disassociateContext(final Context ctx)
     {
-        __threadContext.set(ctx);
+        __threadContext.set(null);
+    }
+    
+    public static ClassLoader associateClassLoader(final ClassLoader loader)
+    {
+        ClassLoader prev = (ClassLoader)__threadClassLoader.get();
+        __threadClassLoader.set(loader);
+        return prev;
+    }
+    
+    public static void disassociateClassLoader ()
+    {
+        __threadClassLoader.set(null);
     }
 
     public static void dump(Appendable out, String indent) throws IOException
@@ -226,10 +251,9 @@ public class ContextFactory implements ObjectFactory
             boolean last=++i==size;
             ClassLoader loader=entry.getKey();
             out.append(indent).append(" +- ").append(loader.getClass().getSimpleName()).append("@").append(Long.toHexString(loader.hashCode())).append(": ");
-            
+
             NamingContext context = entry.getValue();
             context.dump(out,indent+(last?"    ":" |  "));
         }
     }
-
-} 
+}
diff --git a/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/DataSourceCloser.java b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/DataSourceCloser.java
index 195c706..e50aa9d 100644
--- a/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/DataSourceCloser.java
+++ b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/DataSourceCloser.java
@@ -19,19 +19,19 @@
 package org.eclipse.jetty.jndi;
 
 import java.lang.reflect.Method;
+import java.sql.Connection;
 import java.sql.Statement;
 
 import javax.sql.DataSource;
 
-import org.eclipse.jetty.util.component.AggregateLifeCycle;
 import org.eclipse.jetty.util.component.Destroyable;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
 
 /**
  * Close a DataSource.
- * Some {@link DataSource}'s need to be close (eg. Atomikos).  This bean is a {@link Destroyable} and 
- * may be added to any {@link AggregateLifeCycle} so that {@link #destroy()}
+ * Some {@link DataSource}'s need to be close (eg. Atomikos).  This bean is a {@link Destroyable} and
+ * may be added to any {@link ContainerLifeCycle} so that {@link #destroy()}
  * will be called.   The {@link #destroy()} method calls any no-arg method called "close" on the passed DataSource.
  *
  */
@@ -41,7 +41,7 @@ public class DataSourceCloser implements Destroyable
 
     final DataSource _datasource;
     final String _shutdown;
-    
+
     public DataSourceCloser(DataSource datasource)
     {
         if (datasource==null)
@@ -49,7 +49,7 @@ public class DataSourceCloser implements Destroyable
         _datasource=datasource;
         _shutdown=null;
     }
-    
+
     public DataSourceCloser(DataSource datasource,String shutdownSQL)
     {
         if (datasource==null)
@@ -57,7 +57,8 @@ public class DataSourceCloser implements Destroyable
         _datasource=datasource;
         _shutdown=shutdownSQL;
     }
-    
+
+    @Override
     public void destroy()
     {
         try
@@ -65,16 +66,18 @@ public class DataSourceCloser implements Destroyable
             if (_shutdown!=null)
             {
                 LOG.info("Shutdown datasource {}",_datasource);
-                Statement stmt = _datasource.getConnection().createStatement();
-                stmt.executeUpdate(_shutdown);
-                stmt.close();
+                try (Connection connection = _datasource.getConnection();
+                        Statement stmt = connection.createStatement())
+                {
+                    stmt.executeUpdate(_shutdown);
+                }
             }
         }
         catch (Exception e)
         {
             LOG.warn(e);
         }
-        
+
         try
         {
             Method close = _datasource.getClass().getMethod("close", new Class[]{});
diff --git a/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/InitialContextFactory.java b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/InitialContextFactory.java
index b05d7e3..181306a 100644
--- a/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/InitialContextFactory.java
+++ b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/InitialContextFactory.java
@@ -32,24 +32,24 @@ import org.eclipse.jetty.jndi.local.localContextRoot;
 import org.eclipse.jetty.util.log.Logger;
 
 
-/*------------------------------------------------*/    
+/*------------------------------------------------*/
 /**
  * InitialContextFactory.java
  *
  * Factory for the default InitialContext.
  * Created: Tue Jul  1 19:08:08 2003
  *
- * 
+ *
  * @version 1.0
  */
 public class InitialContextFactory implements javax.naming.spi.InitialContextFactory
 {
     private static Logger __log = NamingUtil.__log;
-    
+
     public static class DefaultParser implements NameParser
-    { 
-        static Properties syntax = new Properties();   
-        static 
+    {
+        static Properties syntax = new Properties();
+        static
         {
             syntax.put("jndi.syntax.direction", "left_to_right");
             syntax.put("jndi.syntax.separator", "/");
@@ -61,10 +61,10 @@ public class InitialContextFactory implements javax.naming.spi.InitialContextFac
             return new CompoundName (name, syntax);
         }
     };
-    
 
 
-    /*------------------------------------------------*/    
+
+    /*------------------------------------------------*/
     /**
      * Get Context that has access to default Namespace.
      * This method won't be called if a name URL beginning
@@ -74,7 +74,7 @@ public class InitialContextFactory implements javax.naming.spi.InitialContextFac
      * @param env a <code>Hashtable</code> value
      * @return a <code>Context</code> value
      */
-    public Context getInitialContext(Hashtable env) 
+    public Context getInitialContext(Hashtable env)
     {
         __log.debug("InitialContextFactory.getInitialContext()");
 
@@ -83,4 +83,4 @@ public class InitialContextFactory implements javax.naming.spi.InitialContextFac
 
         return ctx;
     }
-} 
+}
diff --git a/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/NamingContext.java b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/NamingContext.java
index ab15371..980d89f 100644
--- a/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/NamingContext.java
+++ b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/NamingContext.java
@@ -49,31 +49,32 @@ import org.eclipse.jetty.util.component.Dumpable;
 import org.eclipse.jetty.util.log.Logger;
 
 
-/*------------------------------------------------*/    
+/*------------------------------------------------*/
 /** NamingContext
  * <p>Implementation of Context interface.
  *
  * <p><h4>Notes</h4>
  * <p>All Names are expected to be Compound, not Composite.
- *
- * 
  */
+ at SuppressWarnings("unchecked")
 public class NamingContext implements Context, Cloneable, Dumpable
 {
     private final static Logger __log=NamingUtil.__log;
     private final static List<Binding> __empty = Collections.emptyList();
-    public static final String LOCK_PROPERTY = "org.eclipse.jndi.lock";
-    public static final String UNLOCK_PROPERTY = "org.eclipse.jndi.unlock";
-    
+    public static final String DEEP_BINDING = "org.eclipse.jetty.jndi.deepBinding";
+    public static final String LOCK_PROPERTY = "org.eclipse.jetty.jndi.lock";
+    public static final String UNLOCK_PROPERTY = "org.eclipse.jetty.jndi.unlock";
+
     protected final Hashtable<String,Object> _env = new Hashtable<String,Object>();
+    private boolean _supportDeepBinding = false;
     protected Map<String,Binding> _bindings = new HashMap<String,Binding>();
 
     protected NamingContext _parent = null;
     protected String _name = null;
     protected NameParser _parser = null;
-    private Collection<Listener> _listeners; 
+    private Collection<Listener> _listeners;
 
-    /*------------------------------------------------*/    
+    /*------------------------------------------------*/
     /**
      * Naming Context Listener.
      */
@@ -87,7 +88,7 @@ public class NamingContext implements Context, Cloneable, Dumpable
          * @return The binding to bind, or null if the binding should be ignored.
          */
         Binding bind(NamingContext ctx, Binding binding);
-        
+
         /**
          * @param ctx The context to unbind from
          * @param binding The binding that was unbound.
@@ -95,7 +96,7 @@ public class NamingContext implements Context, Cloneable, Dumpable
         void unbind(NamingContext ctx, Binding binding);
     }
 
-    /*------------------------------------------------*/    
+    /*------------------------------------------------*/
     /**
      * Constructor
      *
@@ -104,19 +105,37 @@ public class NamingContext implements Context, Cloneable, Dumpable
      * @param parent immediate ancestor Context (can be null)
      * @param parser NameParser for this Context
      */
-    public NamingContext(Hashtable<String,Object> env, 
-                         String name, 
-                         NamingContext parent, 
-                         NameParser parser) 
+    public NamingContext(Hashtable<String,Object> env,
+                         String name,
+                         NamingContext parent,
+                         NameParser parser)
     {
         if (env != null)
+        {
             _env.putAll(env);
+            // look for deep binding support in _env
+            Object support = _env.get(DEEP_BINDING);
+            if (support == null)
+                _supportDeepBinding = false;
+            else
+                _supportDeepBinding = support != null?Boolean.parseBoolean(support.toString()):false;
+        }
+        else
+        {
+            // no env?  likely this is a root context (java or local) that
+            // was created without an _env.  Look for a system property.
+            String value = System.getProperty(DEEP_BINDING,"false");
+            _supportDeepBinding = Boolean.parseBoolean(value);
+            // put what we discovered into the _env for later sub-contexts
+            // to utilize.
+            _env.put(DEEP_BINDING,_supportDeepBinding);
+        }
         _name = name;
         _parent = parent;
         _parser = parser;
-    } 
-
-
+        if(__log.isDebugEnabled())
+            __log.debug("supportDeepBinding={}",_supportDeepBinding);
+    }
 
 
     /*------------------------------------------------*/
@@ -162,21 +181,24 @@ public class NamingContext implements Context, Cloneable, Dumpable
     /**
      * Setter for _parser
      *
-     * 
+     *
      */
     public void setNameParser (NameParser parser)
     {
         _parser = parser;
     }
 
-    
-    public void setEnv (Hashtable<String,Object> env)
+
+    public final void setEnv (Hashtable<String,Object> env)
     {
         _env.clear();
+        if(env == null)
+            return;
         _env.putAll(env);
+        _supportDeepBinding = _env.containsKey(DEEP_BINDING);
     }
 
-    
+
     public Map<String,Binding> getBindings ()
     {
         return _bindings;
@@ -186,7 +208,7 @@ public class NamingContext implements Context, Cloneable, Dumpable
     {
         _bindings = bindings;
     }
-    
+
     /*------------------------------------------------*/
     /**
      * Bind a name to an object
@@ -195,17 +217,17 @@ public class NamingContext implements Context, Cloneable, Dumpable
      * @param obj object to bind
      * @exception NamingException if an error occurs
      */
-    public void bind(Name name, Object obj) 
+    public void bind(Name name, Object obj)
         throws NamingException
     {
         if (isLocked())
             throw new NamingException ("This context is immutable");
 
         Name cname = toCanonicalName(name);
-        
+
         if (cname == null)
             throw new NamingException ("Name is null");
-        
+
         if (cname.size() == 0)
             throw new NamingException ("Name is empty");
 
@@ -216,21 +238,21 @@ public class NamingContext implements Context, Cloneable, Dumpable
             //get the object to be bound
             Object objToBind = NamingManager.getStateToBind(obj, name,this, _env);
             // Check for Referenceable
-            if (objToBind instanceof Referenceable) 
+            if (objToBind instanceof Referenceable)
             {
                 objToBind = ((Referenceable)objToBind).getReference();
             }
-            
-            //anything else we should be able to bind directly    
+
+            //anything else we should be able to bind directly
             addBinding (cname, objToBind);
         }
         else
         {
             if(__log.isDebugEnabled())__log.debug("Checking for existing binding for name="+cname+" for first element of name="+cname.get(0));
-          
-            //walk down the subcontext hierarchy       
+
+            //walk down the subcontext hierarchy
             //need to ignore trailing empty "" name components
-                    
+
             String firstComponent = cname.get(0);
             Object ctx = null;
 
@@ -240,13 +262,24 @@ public class NamingContext implements Context, Cloneable, Dumpable
             {
 
                 Binding  binding = getBinding (firstComponent);
-                if (binding == null)
-                    throw new NameNotFoundException (firstComponent+ " is not bound");
-                
+                if (binding == null) {
+                    if (_supportDeepBinding)
+                    {
+                        Name subname = _parser.parse(firstComponent);
+                        Context subctx = new NamingContext((Hashtable)_env.clone(),firstComponent,this,_parser);
+                        addBinding(subname,subctx);
+                        binding = getBinding(subname);
+                    }
+                    else
+                    {
+                        throw new NameNotFoundException(firstComponent + " is not bound");
+                    }
+                }
+
                 ctx = binding.getObject();
-                
+
                 if (ctx instanceof Reference)
-                {  
+                {
                     //deference the object
                     try
                     {
@@ -284,7 +317,7 @@ public class NamingContext implements Context, Cloneable, Dumpable
      * @param obj an <code>Object</code> value
      * @exception NamingException if an error occurs
      */
-    public void bind(String name, Object obj) 
+    public void bind(String name, Object obj)
         throws NamingException
     {
         bind (_parser.parse(name), obj);
@@ -304,11 +337,11 @@ public class NamingContext implements Context, Cloneable, Dumpable
     {
         if (isLocked())
         {
-            NamingException ne = new NamingException ("This context is immutable"); 
+            NamingException ne = new NamingException ("This context is immutable");
             ne.setRemainingName(name);
             throw ne;
         }
-        
+
         Name cname = toCanonicalName (name);
 
         if (cname == null)
@@ -327,10 +360,10 @@ public class NamingContext implements Context, Cloneable, Dumpable
             addBinding (cname, ctx);
             return ctx;
         }
-        
-            
+
+
         //If the name has multiple subcontexts, walk the hierarchy by
-        //fetching the first one. All intermediate subcontexts in the 
+        //fetching the first one. All intermediate subcontexts in the
         //name must already exist.
         String firstComponent = cname.get(0);
         Object ctx = null;
@@ -342,11 +375,11 @@ public class NamingContext implements Context, Cloneable, Dumpable
             Binding binding = getBinding (firstComponent);
             if (binding == null)
                 throw new NameNotFoundException (firstComponent + " is not bound");
-            
+
             ctx = binding.getObject();
-            
+
             if (ctx instanceof Reference)
-            {  
+            {
                 //deference the object
                 if(__log.isDebugEnabled())__log.debug("Object bound at "+firstComponent +" is a Reference");
                 try
@@ -364,7 +397,7 @@ public class NamingContext implements Context, Cloneable, Dumpable
                 }
             }
         }
-        
+
         if (ctx instanceof Context)
         {
             return ((Context)ctx).createSubcontext (cname.getSuffix(1));
@@ -392,7 +425,7 @@ public class NamingContext implements Context, Cloneable, Dumpable
 
     /*------------------------------------------------*/
     /**
-     * 
+     *
      *
      * @param name name of subcontext to remove
      * @exception NamingException if an error occurs
@@ -407,7 +440,7 @@ public class NamingContext implements Context, Cloneable, Dumpable
 
     /*------------------------------------------------*/
     /**
-     * 
+     *
      *
      * @param name name of subcontext to remove
      * @exception NamingException if an error occurs
@@ -439,8 +472,8 @@ public class NamingContext implements Context, Cloneable, Dumpable
             return ctx;
         }
 
-    
-      
+
+
         if (cname.size() == 1)
         {
             Binding binding = getBinding (cname);
@@ -450,7 +483,7 @@ public class NamingContext implements Context, Cloneable, Dumpable
                 nnfe.setRemainingName(cname);
                 throw nnfe;
             }
-                
+
 
             Object o = binding.getObject();
 
@@ -490,7 +523,7 @@ public class NamingContext implements Context, Cloneable, Dumpable
         }
 
         //it is a multipart name, recurse to the first subcontext
-   
+
         String firstComponent = cname.get(0);
         Object ctx = null;
 
@@ -498,7 +531,7 @@ public class NamingContext implements Context, Cloneable, Dumpable
             ctx = this;
         else
         {
-            
+
             Binding binding = getBinding (firstComponent);
             if (binding == null)
             {
@@ -506,14 +539,14 @@ public class NamingContext implements Context, Cloneable, Dumpable
                 nnfe.setRemainingName(cname);
                 throw nnfe;
             }
-            
-            //as we have bound a reference to an object factory 
+
+            //as we have bound a reference to an object factory
             //for the component specific contexts
             //at "comp" we need to resolve the reference
             ctx = binding.getObject();
-            
+
             if (ctx instanceof Reference)
-            {  
+            {
                 //deference the object
                 try
                 {
@@ -562,8 +595,8 @@ public class NamingContext implements Context, Cloneable, Dumpable
      * @exception NamingException if an error occurs
      */
     public Object lookupLink (Name name)
-        throws NamingException 
-    {      
+        throws NamingException
+    {
         Name cname = toCanonicalName(name);
 
         if (cname == null)
@@ -613,7 +646,7 @@ public class NamingContext implements Context, Cloneable, Dumpable
         //it is a multipart name, recurse to the first subcontext
         String firstComponent = cname.get(0);
         Object ctx = null;
-        
+
         if (firstComponent.equals(""))
             ctx = this;
         else
@@ -621,11 +654,11 @@ public class NamingContext implements Context, Cloneable, Dumpable
             Binding binding = getBinding (firstComponent);
             if (binding == null)
                 throw new NameNotFoundException ();
-            
+
             ctx = binding.getObject();
 
             if (ctx instanceof Reference)
-            {  
+            {
                 //deference the object
                 try
                 {
@@ -679,20 +712,20 @@ public class NamingContext implements Context, Cloneable, Dumpable
         if(__log.isDebugEnabled())__log.debug("list() on Context="+getName()+" for name="+name);
         Name cname = toCanonicalName(name);
 
-     
+
 
         if (cname == null)
         {
             return new NameEnumeration(__empty.iterator());
         }
 
-        
+
         if (cname.size() == 0)
         {
-           return new NameEnumeration (_bindings.values().iterator()); 
+           return new NameEnumeration (_bindings.values().iterator());
         }
 
-      
+
 
         //multipart name
         String firstComponent = cname.get(0);
@@ -705,11 +738,11 @@ public class NamingContext implements Context, Cloneable, Dumpable
             Binding binding = getBinding (firstComponent);
             if (binding == null)
                 throw new NameNotFoundException ();
-            
+
             ctx = binding.getObject();
-            
+
             if (ctx instanceof Reference)
-            {  
+            {
                 //deference the object
                 if(__log.isDebugEnabled())__log.debug("Dereferencing Reference for "+name.get(0));
                 try
@@ -731,7 +764,7 @@ public class NamingContext implements Context, Cloneable, Dumpable
         if (!(ctx instanceof Context))
             throw new NotContextException();
 
-        return ((Context)ctx).list (cname.getSuffix(1));       
+        return ((Context)ctx).list (cname.getSuffix(1));
     }
 
 
@@ -742,7 +775,7 @@ public class NamingContext implements Context, Cloneable, Dumpable
      * @param name a <code>Name</code> value
      * @return a <code>NamingEnumeration</code> value
      * @exception NamingException if an error occurs
-     */       
+     */
     public NamingEnumeration list(String name)
         throws NamingException
     {
@@ -761,7 +794,7 @@ public class NamingContext implements Context, Cloneable, Dumpable
      */
     public NamingEnumeration listBindings(Name name)
         throws NamingException
-    {  
+    {
         Name cname = toCanonicalName (name);
 
         if (cname == null)
@@ -771,11 +804,11 @@ public class NamingContext implements Context, Cloneable, Dumpable
 
         if (cname.size() == 0)
         {
-           return new BindingEnumeration (_bindings.values().iterator()); 
+           return new BindingEnumeration (_bindings.values().iterator());
         }
 
-      
-        
+
+
         //multipart name
         String firstComponent = cname.get(0);
         Object ctx = null;
@@ -790,11 +823,11 @@ public class NamingContext implements Context, Cloneable, Dumpable
             Binding binding = getBinding (firstComponent);
             if (binding == null)
                 throw new NameNotFoundException ();
-        
+
             ctx = binding.getObject();
 
             if (ctx instanceof Reference)
-            {  
+            {
                 //deference the object
                 try
                 {
@@ -846,7 +879,7 @@ public class NamingContext implements Context, Cloneable, Dumpable
     public void rebind(Name name,
                        Object obj)
         throws NamingException
-    {    
+    {
         if (isLocked())
             throw new NamingException ("This context is immutable");
 
@@ -854,17 +887,17 @@ public class NamingContext implements Context, Cloneable, Dumpable
 
         if (cname == null)
             throw new NamingException ("Name is null");
-        
+
         if (cname.size() == 0)
             throw new NamingException ("Name is empty");
 
 
         //if no subcontexts, just bind it
         if (cname.size() == 1)
-        {      
+        {
             //check if it is a Referenceable
             Object objToBind = NamingManager.getStateToBind(obj, name, this, _env);
-            
+
             if (objToBind instanceof Referenceable)
             {
                 objToBind = ((Referenceable)objToBind).getReference();
@@ -873,14 +906,14 @@ public class NamingContext implements Context, Cloneable, Dumpable
             addBinding (cname, objToBind);
         }
         else
-        { 
+        {
             //walk down the subcontext hierarchy
             if(__log.isDebugEnabled())__log.debug("Checking for existing binding for name="+cname+" for first element of name="+cname.get(0));
-                    
+
             String firstComponent = cname.get(0);
             Object ctx = null;
 
-            
+
             if (firstComponent.equals(""))
                 ctx = this;
             else
@@ -888,12 +921,12 @@ public class NamingContext implements Context, Cloneable, Dumpable
                 Binding  binding = getBinding (name.get(0));
                 if (binding == null)
                     throw new NameNotFoundException (name.get(0)+ " is not bound");
-            
+
                 ctx = binding.getObject();
 
 
                 if (ctx instanceof Reference)
-                {  
+                {
                     //deference the object
                     try
                     {
@@ -961,8 +994,8 @@ public class NamingContext implements Context, Cloneable, Dumpable
     {
         if (name.size() == 0)
             return;
-        
-        
+
+
         if (isLocked())
             throw new NamingException ("This context is immutable");
 
@@ -970,25 +1003,25 @@ public class NamingContext implements Context, Cloneable, Dumpable
 
         if (cname == null)
             throw new NamingException ("Name is null");
-        
+
         if (cname.size() == 0)
             throw new NamingException ("Name is empty");
 
 
         //if no subcontexts, just unbind it
         if (cname.size() == 1)
-        {         
+        {
             removeBinding (cname);
         }
         else
-        { 
+        {
             //walk down the subcontext hierarchy
             if(__log.isDebugEnabled())__log.debug("Checking for existing binding for name="+cname+" for first element of name="+cname.get(0));
-                    
+
             String firstComponent = cname.get(0);
             Object ctx = null;
 
-            
+
             if (firstComponent.equals(""))
                 ctx = this;
             else
@@ -996,11 +1029,11 @@ public class NamingContext implements Context, Cloneable, Dumpable
                 Binding  binding = getBinding (name.get(0));
                 if (binding == null)
                     throw new NameNotFoundException (name.get(0)+ " is not bound");
-            
+
                 ctx = binding.getObject();
 
                 if (ctx instanceof Reference)
-                {  
+                {
                     //deference the object
                     try
                     {
@@ -1024,7 +1057,7 @@ public class NamingContext implements Context, Cloneable, Dumpable
             }
             else
                 throw new NotContextException ("Object bound at "+firstComponent +" is not a Context");
-        } 
+        }
     }
 
     /*------------------------------------------------*/
@@ -1042,7 +1075,7 @@ public class NamingContext implements Context, Cloneable, Dumpable
         throw new OperationNotSupportedException();
     }
 
-    
+
     /*------------------------------------------------*/
     /**
      * Not supported
@@ -1084,7 +1117,7 @@ public class NamingContext implements Context, Cloneable, Dumpable
 
 
 
-    /*------------------------------------------------*/    
+    /*------------------------------------------------*/
     /** Join two names together. These are treated as
      * CompoundNames.
      *
@@ -1096,7 +1129,7 @@ public class NamingContext implements Context, Cloneable, Dumpable
     public String composeName (String name,
                                String prefix)
         throws NamingException
-    {       
+    {
         if (name == null)
             throw new NamingException ("Name cannot be null");
         if (prefix == null)
@@ -1108,7 +1141,7 @@ public class NamingContext implements Context, Cloneable, Dumpable
     }
 
 
-    /*------------------------------------------------*/    
+    /*------------------------------------------------*/
     /**
      * Do nothing
      *
@@ -1116,11 +1149,11 @@ public class NamingContext implements Context, Cloneable, Dumpable
      */
     public void close ()
         throws NamingException
-    {  
+    {
     }
 
 
-    /*------------------------------------------------*/    
+    /*------------------------------------------------*/
     /**
      * Return a NameParser for this Context.
      *
@@ -1132,20 +1165,20 @@ public class NamingContext implements Context, Cloneable, Dumpable
         return _parser;
     }
 
-    /*------------------------------------------------*/    
+    /*------------------------------------------------*/
     /**
      * Return a NameParser for this Context.
      *
      * @param name a <code>Name</code> value
      * @return a <code>NameParser</code> value
-     */    
+     */
     public NameParser getNameParser (String name)
     {
         return _parser;
     }
-    
 
-    /*------------------------------------------------*/    
+
+    /*------------------------------------------------*/
     /**
      * Get the full name of this Context node
      * by visiting it's ancestors back to root.
@@ -1173,7 +1206,7 @@ public class NamingContext implements Context, Cloneable, Dumpable
     }
 
 
-    /*------------------------------------------------*/    
+    /*------------------------------------------------*/
     /**
      * Add an environment setting to this Context
      *
@@ -1188,12 +1221,12 @@ public class NamingContext implements Context, Cloneable, Dumpable
     {
         if (isLocked() && !(propName.equals(UNLOCK_PROPERTY)))
             throw new NamingException ("This context is immutable");
-        
+
         return _env.put (propName, propVal);
     }
 
 
-    /*------------------------------------------------*/    
+    /*------------------------------------------------*/
     /**
      * Remove a property from this Context's environment.
      *
@@ -1206,12 +1239,12 @@ public class NamingContext implements Context, Cloneable, Dumpable
     {
         if (isLocked())
             throw new NamingException ("This context is immutable");
-        
+
         return _env.remove (propName);
     }
 
 
-    /*------------------------------------------------*/    
+    /*------------------------------------------------*/
     /**
      * Get the environment of this Context.
      *
@@ -1222,7 +1255,7 @@ public class NamingContext implements Context, Cloneable, Dumpable
         return (Hashtable)_env.clone();
     }
 
-    /*------------------------------------------------*/    
+    /*------------------------------------------------*/
     /**
      * Add a name to object binding to this Context.
      *
@@ -1233,9 +1266,9 @@ public class NamingContext implements Context, Cloneable, Dumpable
     {
         String key = name.toString();
         Binding binding=new Binding (key, obj);
-        
+
         Collection<Listener> list = findListeners();
-        
+
         for (Listener listener : list)
         {
             binding=listener.bind(this,binding);
@@ -1245,16 +1278,23 @@ public class NamingContext implements Context, Cloneable, Dumpable
 
         if(__log.isDebugEnabled())
             __log.debug("Adding binding with key="+key+" obj="+obj+" for context="+_name+" as "+binding);
-        
+
         if (binding!=null)
         {
-            if (_bindings.containsKey(key))
+            if (_bindings.containsKey(key)) {
+                if(_supportDeepBinding) {
+                    // quietly return (no exception)
+                    // this is jndi spec breaking, but is added to support broken
+                    // jndi users like openejb.
+                    return;
+                }
                 throw new NameAlreadyBoundException(name.toString());
-            _bindings.put(key,binding);  
+            }
+            _bindings.put(key,binding);
         }
     }
 
-    /*------------------------------------------------*/    
+    /*------------------------------------------------*/
     /**
      * Get a name to object binding from this Context
      *
@@ -1267,7 +1307,7 @@ public class NamingContext implements Context, Cloneable, Dumpable
     }
 
 
-    /*------------------------------------------------*/    
+    /*------------------------------------------------*/
     /**
      * Get a name to object binding from this Context
      *
@@ -1279,11 +1319,11 @@ public class NamingContext implements Context, Cloneable, Dumpable
         return (Binding) _bindings.get(name);
     }
 
-    /*------------------------------------------------*/    
+    /*------------------------------------------------*/
     public void removeBinding (Name name)
     {
         String key = name.toString();
-        if (__log.isDebugEnabled()) 
+        if (__log.isDebugEnabled())
             __log.debug("Removing binding with key="+key);
         Binding binding = _bindings.remove(key);
         if (binding!=null)
@@ -1294,7 +1334,7 @@ public class NamingContext implements Context, Cloneable, Dumpable
         }
     }
 
-    /*------------------------------------------------*/    
+    /*------------------------------------------------*/
     /**
      * Remove leading or trailing empty components from
      * name. Eg "/comp/env/" -> "comp/env"
@@ -1312,30 +1352,30 @@ public class NamingContext implements Context, Cloneable, Dumpable
             {
                 if (canonicalName.get(0).equals(""))
                     canonicalName = canonicalName.getSuffix(1);
- 
+
                 if (canonicalName.get(canonicalName.size()-1).equals(""))
-                    canonicalName = canonicalName.getPrefix(canonicalName.size()-1);               
+                    canonicalName = canonicalName.getPrefix(canonicalName.size()-1);
             }
         }
 
         return canonicalName;
     }
-    
+
     /* ------------------------------------------------------------ */
     public boolean isLocked()
     {
        if ((_env.get(LOCK_PROPERTY) == null) && (_env.get(UNLOCK_PROPERTY) == null))
            return false;
-       
+
        Object lockKey = _env.get(LOCK_PROPERTY);
        Object unlockKey = _env.get(UNLOCK_PROPERTY);
-       
+
        if ((lockKey != null) && (unlockKey != null) && (lockKey.equals(unlockKey)))
            return false;
        return true;
     }
-    
-    
+
+
     /* ------------------------------------------------------------ */
     public String dump()
     {
@@ -1350,7 +1390,7 @@ public class NamingContext implements Context, Cloneable, Dumpable
         }
         return buf.toString();
     }
-    
+
 
     /* ------------------------------------------------------------ */
     public void dump(Appendable out,String indent) throws IOException
@@ -1362,10 +1402,10 @@ public class NamingContext implements Context, Cloneable, Dumpable
         {
             boolean last=++i==size;
             out.append(indent).append(" +- ").append(entry.getKey()).append(": ");
-            
+
             Binding binding = entry.getValue();
             Object value = binding.getObject();
-            
+
             if ("comp".equals(entry.getKey()) && value instanceof Reference && "org.eclipse.jetty.jndi.ContextFactory".equals(((Reference)value).getFactoryClassName()))
             {
                 ContextFactory.dump(out,indent+(last?"    ":" |  "));
@@ -1382,7 +1422,7 @@ public class NamingContext implements Context, Cloneable, Dumpable
             }
         }
     }
-  
+
     private Collection<Listener> findListeners()
     {
         Collection<Listener> list = new ArrayList<Listener>();
@@ -1395,16 +1435,40 @@ public class NamingContext implements Context, Cloneable, Dumpable
         }
         return list;
     }
-    
+
     public void addListener(Listener listener)
     {
         if (_listeners==null)
             _listeners=new ArrayList<Listener>();
         _listeners.add(listener);
     }
-    
+
     public boolean removeListener(Listener listener)
-    {   
+    {
         return _listeners.remove(listener);
     }
-} 
+    
+    @Override
+    public String toString()
+    {
+        StringBuilder buf = new StringBuilder();
+        buf.append(this.getClass().getName()).append('@').append(Integer.toHexString(this.hashCode()));
+        buf.append("[name=").append(this._name);
+        buf.append(",parent=");
+        if (this._parent != null)
+        {
+            buf.append(this._parent.getClass().getName()).append('@').append(Integer.toHexString(this._parent.hashCode()));
+        }
+        buf.append(",bindings");
+        if (this._bindings == null)
+        {
+            buf.append("=<null>");
+        }
+        else
+        {
+            buf.append(".size=").append(this._bindings.size());
+        }
+        buf.append(']');
+        return buf.toString();
+    }
+}
diff --git a/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/NamingUtil.java b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/NamingUtil.java
index 503f7ac..1a98c41 100644
--- a/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/NamingUtil.java
+++ b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/NamingUtil.java
@@ -38,16 +38,16 @@ import org.eclipse.jetty.util.log.Logger;
  *
  * Created: Tue Jul  1 18:26:17 2003
  *
- * 
+ *
  * @version 1.0
  */
-public class NamingUtil 
+public class NamingUtil
 {
     public final static Logger __log=org.eclipse.jetty.util.log.Log.getLogger("jndi");
-    
+
     /* ------------------------------------------------------------ */
     /**
-     * Bind an object to a context ensuring all sub-contexts 
+     * Bind an object to a context ensuring all sub-contexts
      * are created if necessary
      *
      * @param ctx the context into which to bind
@@ -60,12 +60,12 @@ public class NamingUtil
     {
         Name name = ctx.getNameParser("").parse(nameStr);
 
-        //no name, nothing to do 
+        //no name, nothing to do
         if (name.size() == 0)
             return null;
 
         Context subCtx = ctx;
-        
+
         //last component of the name will be the name to bind
         for (int i=0; i < name.size() - 1; i++)
         {
@@ -87,14 +87,14 @@ public class NamingUtil
         if(__log.isDebugEnabled())
             __log.debug("Bound object to "+name.get(name.size() - 1));
         return subCtx;
-    } 
-    
+    }
+
     public static void unbind (Context ctx)
     throws NamingException
     {
         //unbind everything in the context and all of its subdirectories
         NamingEnumeration ne = ctx.listBindings(ctx.getNameInNamespace());
-        
+
         while (ne.hasMoreElements())
         {
             Binding b = (Binding)ne.nextElement();
@@ -106,12 +106,12 @@ public class NamingUtil
                 ctx.unbind(b.getName());
         }
     }
-    
+
     /**
      * Do a deep listing of the bindings for a context.
      * @param ctx the context containing the name for which to list the bindings
      * @param name the name in the context to list
-     * @return map: key is fully qualified name, value is the bound object 
+     * @return map: key is fully qualified name, value is the bound object
      * @throws NamingException
      */
     public static Map flattenBindings (Context ctx, String name)
@@ -137,10 +137,10 @@ public class NamingUtil
                 compoundName.add(b.getName());
                 map.put (compoundName.toString(), b.getObject());
             }
-            
+
         }
-        
+
         return map;
     }
-    
+
 }
diff --git a/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/factories/MailSessionReference.java b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/factories/MailSessionReference.java
index 89777ba..141dd3a 100644
--- a/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/factories/MailSessionReference.java
+++ b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/factories/MailSessionReference.java
@@ -39,9 +39,9 @@ import org.eclipse.jetty.util.security.Password;
 
 /**
  * MailSessionReference
- * 
+ *
  * This is a subclass of javax.mail.Reference and an ObjectFactory for javax.mail.Session objects.
- * 
+ *
  * The subclassing of Reference allows all of the setup for a javax.mail.Session
  * to be captured without necessitating first instantiating a Session object. The
  * reference is bound into JNDI and it is only when the reference is looked up that
@@ -51,7 +51,7 @@ import org.eclipse.jetty.util.security.Password;
  */
 public class MailSessionReference extends Reference implements ObjectFactory
 {
- 
+
 
     public static class PasswordAuthenticator extends Authenticator
     {
@@ -61,9 +61,9 @@ public class MailSessionReference extends Reference implements ObjectFactory
 
         public PasswordAuthenticator()
         {
-            
+
         }
-        
+
         public PasswordAuthenticator(String user, String password)
         {
             passwordAuthentication = new PasswordAuthentication (user, (password.startsWith(Password.__OBFUSCATE)?Password.deobfuscate(password):password));
@@ -73,7 +73,7 @@ public class MailSessionReference extends Reference implements ObjectFactory
         {
             return passwordAuthentication;
         }
-        
+
         public void setUser (String user)
         {
             this.user = user;
@@ -82,7 +82,7 @@ public class MailSessionReference extends Reference implements ObjectFactory
         {
             return this.user;
         }
-        
+
         public String getPassword ()
         {
             return this.password;
@@ -93,23 +93,23 @@ public class MailSessionReference extends Reference implements ObjectFactory
             this.password = password;
         }
 
-       
+
     };
-    
-    
-  
 
-    
+
+
+
+
     /**
-     * 
+     *
      */
     public MailSessionReference()
     {
-       super ("javax.mail.Session", MailSessionReference.class.getName(), null); 
+       super ("javax.mail.Session", MailSessionReference.class.getName(), null);
     }
 
 
-    /** 
+    /**
      * Create a javax.mail.Session instance based on the information passed in the Reference
      * @see javax.naming.spi.ObjectFactory#getObjectInstance(java.lang.Object, javax.naming.Name, javax.naming.Context, java.util.Hashtable)
      * @param ref the Reference
@@ -123,19 +123,19 @@ public class MailSessionReference extends Reference implements ObjectFactory
     {
         if (ref == null)
         return null;
-        
+
         Reference reference = (Reference)ref;
-        
+
 
         Properties props = new Properties();
         String user = null;
         String password = null;
-        
+
         Enumeration refs = reference.getAll();
         while (refs.hasMoreElements())
         {
             RefAddr refAddr = (RefAddr)refs.nextElement();
-            String name = refAddr.getType();           
+            String name = refAddr.getType();
             String value =  (String)refAddr.getContent();
             if (name.equalsIgnoreCase("user"))
                 user = value;
@@ -150,8 +150,8 @@ public class MailSessionReference extends Reference implements ObjectFactory
         else
             return Session.getInstance(props, new PasswordAuthenticator(user, password));
     }
-    
-    
+
+
     public void setUser (String user)
     {
        StringRefAddr addr =  (StringRefAddr)get("user");
@@ -161,7 +161,7 @@ public class MailSessionReference extends Reference implements ObjectFactory
        }
        add(new StringRefAddr("user", user));
     }
-    
+
     public void setPassword (String password)
     {
         StringRefAddr addr = (StringRefAddr)get("pwd");
@@ -169,7 +169,7 @@ public class MailSessionReference extends Reference implements ObjectFactory
             throw new RuntimeException ("password already set on SessionReference, can't be changed");
         add(new StringRefAddr ("pwd", password));
     }
-    
+
     public void setProperties (Properties properties)
     {
         Iterator entries = properties.entrySet().iterator();
@@ -182,7 +182,7 @@ public class MailSessionReference extends Reference implements ObjectFactory
             add(new StringRefAddr((String)e.getKey(), (String)e.getValue()));
         }
     }
-    
-  
-    
+
+
+
 }
diff --git a/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/factories/package-info.java b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/factories/package-info.java
new file mode 100644
index 0000000..e3b4592
--- /dev/null
+++ b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/factories/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Jndi : Factories
+ */
+package org.eclipse.jetty.jndi.factories;
+
diff --git a/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/java/javaNameParser.java b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/java/javaNameParser.java
index 8761646..0ecf129 100644
--- a/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/java/javaNameParser.java
+++ b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/java/javaNameParser.java
@@ -35,7 +35,7 @@ public class javaNameParser implements NameParser
 
     static Properties syntax = new Properties();
 
-    static 
+    static
     {
       syntax.put("jndi.syntax.direction", "left_to_right");
       syntax.put("jndi.syntax.separator", "/");
diff --git a/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/java/javaRootURLContext.java b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/java/javaRootURLContext.java
index 9162d80..6dc0907 100644
--- a/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/java/javaRootURLContext.java
+++ b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/java/javaRootURLContext.java
@@ -46,18 +46,10 @@ import org.eclipse.jetty.util.log.Logger;
  * <p><h4>Usage</h4>
  * <pre>
  */
-/*
-* </pre>
-*
-* @see
-*
-* 
-* @version 1.0
-*/
 public class javaRootURLContext implements Context
 {
     private static Logger __log = NamingUtil.__log;
-    
+
     public static final String URL_PREFIX = "java:";
 
     protected Hashtable _env;
@@ -66,22 +58,22 @@ public class javaRootURLContext implements Context
 
     protected static NameParser __javaNameParser;
 
-    
-    static 
-    {   
+
+    static
+    {
         try
         {
-            __javaNameParser = new javaNameParser();       
+            __javaNameParser = new javaNameParser();
             __nameRoot = new NamingContext(null,null,null,__javaNameParser);
-          
+
             StringRefAddr parserAddr = new StringRefAddr("parser", __javaNameParser.getClass().getName());
-            
+
             Reference ref = new Reference ("javax.naming.Context",
                                            parserAddr,
                                            ContextFactory.class.getName(),
                                            (String)null);
 
-            //bind special object factory at comp
+            // bind special object factory at comp
             __nameRoot.bind ("comp", ref);
         }
         catch (Exception e)
@@ -98,34 +90,33 @@ public class javaRootURLContext implements Context
      *
      * @param env a <code>Hashtable</code> value
      */
-    public javaRootURLContext(Hashtable env) 
+    public javaRootURLContext(Hashtable env)
     {
         _env = env;
-    } 
-    
+    }
 
-    public Object lookup(Name name) 
+    public Object lookup(Name name)
         throws NamingException
     {
         return getRoot().lookup(stripProtocol(name));
     }
 
 
-    public Object lookup(String name) 
+    public Object lookup(String name)
         throws NamingException
     {
         return getRoot().lookup(stripProtocol(name));
     }
 
-    public void bind(Name name, Object obj) 
+    public void bind(Name name, Object obj)
         throws NamingException
     {
         getRoot().bind(stripProtocol(name), obj);
     }
 
-    public void bind(String name, Object obj) 
+    public void bind(String name, Object obj)
         throws NamingException
-    { 
+    {
         getRoot().bind(stripProtocol(name), obj);
     }
 
@@ -134,7 +125,7 @@ public class javaRootURLContext implements Context
     {
         getRoot().unbind(stripProtocol(name));
     }
-    
+
     public void unbind (Name name)
         throws NamingException
     {
@@ -177,7 +168,7 @@ public class javaRootURLContext implements Context
     {
         return getRoot().lookupLink(stripProtocol(name));
     }
-   
+
 
     public Context createSubcontext (Name name)
         throws NamingException
@@ -193,7 +184,7 @@ public class javaRootURLContext implements Context
 
 
     public void destroySubcontext (Name name)
-        throws NamingException    
+        throws NamingException
     {
         getRoot().destroySubcontext(stripProtocol(name));
     }
@@ -230,7 +221,7 @@ public class javaRootURLContext implements Context
         return getRoot().listBindings(stripProtocol(name));
     }
 
-    
+
     public Name composeName (Name name,
                              Name prefix)
         throws NamingException
@@ -246,7 +237,7 @@ public class javaRootURLContext implements Context
     }
 
 
-    public void close ()       
+    public void close ()
         throws NamingException
     {
     }
@@ -262,8 +253,8 @@ public class javaRootURLContext implements Context
     {
         return __javaNameParser;
     }
-    
-    public NameParser getNameParser (String name) 
+
+    public NameParser getNameParser (String name)
         throws NamingException
     {
         return __javaNameParser;
@@ -300,7 +291,7 @@ public class javaRootURLContext implements Context
         if ((name != null) && (name.size() > 0))
         {
             String head = name.get(0);
-            
+
             if(__log.isDebugEnabled())__log.debug("Head element of name is: "+head);
 
             if (head.startsWith(URL_PREFIX))
@@ -313,7 +304,7 @@ public class javaRootURLContext implements Context
                 if(__log.isDebugEnabled())__log.debug("name modified to "+name.toString());
             }
         }
-        
+
         return name;
     }
 
@@ -328,8 +319,8 @@ public class javaRootURLContext implements Context
             if (name.startsWith(URL_PREFIX))
                newName = name.substring(URL_PREFIX.length());
         }
-        
+
         return newName;
     }
 
-} 
+}
diff --git a/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/java/javaURLContextFactory.java b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/java/javaURLContextFactory.java
index 68fb244..3215c79 100644
--- a/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/java/javaURLContextFactory.java
+++ b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/java/javaURLContextFactory.java
@@ -38,10 +38,10 @@ import org.eclipse.jetty.util.log.Logger;
  * <p><h4>Usage</h4>
  * <pre>
  */
-public class javaURLContextFactory implements ObjectFactory 
+public class javaURLContextFactory implements ObjectFactory
 {
     private static final Logger LOG = Log.getLogger(javaURLContextFactory.class);
-        
+
     /**
      * Either return a new context or the resolution of a url.
      *
@@ -53,7 +53,7 @@ public class javaURLContextFactory implements ObjectFactory
      * @exception Exception if an error occurs
      */
     public Object getObjectInstance(Object url, Name name, Context ctx, Hashtable env)
-        throws Exception 
+        throws Exception
     {
         // null object means return a root context for doing resolutions
         if (url == null)
@@ -61,7 +61,7 @@ public class javaURLContextFactory implements ObjectFactory
             if(LOG.isDebugEnabled())LOG.debug(">>> new root context requested ");
             return new javaRootURLContext(env);
         }
-        
+
         // return the resolution of the url
         if (url instanceof String)
         {
@@ -74,7 +74,7 @@ public class javaURLContextFactory implements ObjectFactory
         if (url instanceof String[])
         {
             if(LOG.isDebugEnabled())LOG.debug(">>> resolution of array of urls requested");
-            String[] urls = (String[])url; 
+            String[] urls = (String[])url;
             Context rootctx = new javaRootURLContext (env);
             Object object = null;
             NamingException e = null;
diff --git a/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/java/package-info.java b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/java/package-info.java
new file mode 100644
index 0000000..053980c
--- /dev/null
+++ b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/java/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Jndi : Mappings for java
+ */
+package org.eclipse.jetty.jndi.java;
+
diff --git a/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/local/localContextRoot.java b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/local/localContextRoot.java
index 69d4eaa..b36cfaf 100644
--- a/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/local/localContextRoot.java
+++ b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/local/localContextRoot.java
@@ -19,10 +19,8 @@
 package org.eclipse.jetty.jndi.local;
 
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.Hashtable;
 import java.util.List;
-import java.util.Map;
 import java.util.Properties;
 
 import javax.naming.Binding;
@@ -32,7 +30,6 @@ import javax.naming.InitialContext;
 import javax.naming.LinkRef;
 import javax.naming.Name;
 import javax.naming.NameAlreadyBoundException;
-import javax.naming.NameClassPair;
 import javax.naming.NameNotFoundException;
 import javax.naming.NameParser;
 import javax.naming.NamingEnumeration;
@@ -50,21 +47,21 @@ import org.eclipse.jetty.jndi.NamingUtil;
 import org.eclipse.jetty.util.log.Logger;
 
 /**
- * 
+ *
  * localContext
- * 
- * Implementation of the delegate for InitialContext for the local namespace. 
- * 
- * 
+ *
+ * Implementation of the delegate for InitialContext for the local namespace.
+ *
+ *
  * @version $Revision: 4780 $ $Date: 2009-03-17 16:36:08 +0100 (Tue, 17 Mar 2009) $
- * 
+ *
  */
 public class localContextRoot implements Context
 {
     private final static Logger __log=NamingUtil.__log;
     protected final static NamingContext __root = new NamingRoot();
     private final Hashtable<String,Object> _env;
-  
+
 
     static class NamingRoot extends NamingContext
     {
@@ -73,8 +70,8 @@ public class localContextRoot implements Context
             super (null,null,null,new LocalNameParser());
         }
     }
-    
-    
+
+
 
     static class LocalNameParser implements NameParser
     {
@@ -92,19 +89,19 @@ public class localContextRoot implements Context
             return new CompoundName(name, syntax);
         }
     }
-    
-    
+
+
     /*
      * Root has to use the localContextRoot's  env for all operations.
      * So, if createSubcontext in the root, use the env of the localContextRoot.
      * If lookup binding in the root, use the env of the localContextRoot.
-     * 
+     *
      */
-    
-  
-    
-    
-   
+
+
+
+
+
 
     public static NamingContext getRoot()
     {
@@ -117,8 +114,8 @@ public class localContextRoot implements Context
     }
 
     /**
-     * 
-     * 
+     *
+     *
      * @see javax.naming.Context#close()
      */
     public void close() throws NamingException
@@ -127,8 +124,8 @@ public class localContextRoot implements Context
     }
 
     /**
-     * 
-     * 
+     *
+     *
      * @see javax.naming.Context#getNameInNamespace()
      */
     public String getNameInNamespace() throws NamingException
@@ -136,39 +133,39 @@ public class localContextRoot implements Context
         return "";
     }
 
-    
+
     /**
-     * 
-     * 
+     *
+     *
      * @see javax.naming.Context#destroySubcontext(javax.naming.Name)
      */
     public void destroySubcontext(Name name) throws NamingException
     {
         synchronized (__root)
         {
-            __root.destroySubcontext(getSuffix(name));   
+            __root.destroySubcontext(getSuffix(name));
         }
     }
-    
-    
+
+
     /**
-     * 
-     * 
+     *
+     *
      * @see javax.naming.Context#destroySubcontext(java.lang.String)
      */
     public void destroySubcontext(String name) throws NamingException
     {
         synchronized (__root)
         {
-           
+
            destroySubcontext(__root.getNameParser("").parse(getSuffix(name)));
         }
     }
 
-  
+
     /**
-     * 
-     * 
+     *
+     *
      * @see javax.naming.Context#getEnvironment()
      */
     public Hashtable getEnvironment() throws NamingException
@@ -176,11 +173,11 @@ public class localContextRoot implements Context
         return _env;
     }
 
- 
+
 
     /**
-     * 
-     * 
+     *
+     *
      * @see javax.naming.Context#unbind(javax.naming.Name)
      */
     public void unbind(Name name) throws NamingException
@@ -188,11 +185,11 @@ public class localContextRoot implements Context
         synchronized (__root)
         {
             //__root.unbind(getSuffix(name));
-            
+
             if (name.size() == 0)
                 return;
-            
-            
+
+
             if (__root.isLocked())
                 throw new NamingException ("This context is immutable");
 
@@ -200,25 +197,25 @@ public class localContextRoot implements Context
 
             if (cname == null)
                 throw new NamingException ("Name is null");
-            
+
             if (cname.size() == 0)
                 throw new NamingException ("Name is empty");
 
 
             //if no subcontexts, just unbind it
             if (cname.size() == 1)
-            {         
+            {
                 __root.removeBinding (cname);
             }
             else
-            { 
+            {
                 //walk down the subcontext hierarchy
                 if(__log.isDebugEnabled())__log.debug("Checking for existing binding for name="+cname+" for first element of name="+cname.get(0));
-                        
+
                 String firstComponent = cname.get(0);
                 Object ctx = null;
 
-                
+
                 if (firstComponent.equals(""))
                     ctx = this;
                 else
@@ -226,11 +223,11 @@ public class localContextRoot implements Context
                     Binding  binding = __root.getBinding (name.get(0));
                     if (binding == null)
                         throw new NameNotFoundException (name.get(0)+ " is not bound");
-                
+
                     ctx = binding.getObject();
 
                     if (ctx instanceof Reference)
-                    {  
+                    {
                         //deference the object
                         try
                         {
@@ -254,16 +251,16 @@ public class localContextRoot implements Context
                 }
                 else
                     throw new NotContextException ("Object bound at "+firstComponent +" is not a Context");
-            } 
-            
-            
-            
+            }
+
+
+
         }
     }
 
     /**
-     * 
-     * 
+     *
+     *
      * @see javax.naming.Context#unbind(java.lang.String)
      */
     public void unbind(String name) throws NamingException
@@ -271,11 +268,11 @@ public class localContextRoot implements Context
         unbind(__root.getNameParser("").parse(getSuffix(name)));
     }
 
-    
+
 
     /**
-     * 
-     * 
+     *
+     *
      * @see javax.naming.Context#lookupLink(java.lang.String)
      */
     public Object lookupLink(String name) throws NamingException
@@ -287,8 +284,8 @@ public class localContextRoot implements Context
     }
 
     /**
-     * 
-     * 
+     *
+     *
      * @see javax.naming.Context#lookupLink(javax.naming.Name)
      */
     public Object lookupLink(Name name) throws NamingException
@@ -296,8 +293,8 @@ public class localContextRoot implements Context
         synchronized (__root)
         {
             //return __root.lookupLink(getSuffix(name));
-            
-            
+
+
             Name cname = __root.toCanonicalName(name);
 
             if (cname == null)
@@ -307,7 +304,7 @@ public class localContextRoot implements Context
                 ctx.setBindings(__root.getBindings());
                 return ctx;
             }
-            
+
             if (cname.size() == 0)
                 throw new NamingException ("Name is empty");
 
@@ -349,7 +346,7 @@ public class localContextRoot implements Context
             //it is a multipart name, recurse to the first subcontext
             String firstComponent = cname.get(0);
             Object ctx = null;
-            
+
             if (firstComponent.equals(""))
                 ctx = this;
             else
@@ -357,11 +354,11 @@ public class localContextRoot implements Context
                 Binding binding = __root.getBinding (firstComponent);
                 if (binding == null)
                     throw new NameNotFoundException ();
-                
+
                 ctx = binding.getObject();
 
                 if (ctx instanceof Reference)
-                {  
+                {
                     //deference the object
                     try
                     {
@@ -382,16 +379,16 @@ public class localContextRoot implements Context
             if (!(ctx instanceof Context))
                 throw new NotContextException();
 
-            return ((Context)ctx).lookup (cname.getSuffix(1)); 
-            
-            
+            return ((Context)ctx).lookup (cname.getSuffix(1));
+
+
         }
     }
 
-    
+
     /**
-     * 
-     *       
+     *
+     *
      * @see javax.naming.Context#removeFromEnvironment(java.lang.String)
      */
     public Object removeFromEnvironment(String propName) throws NamingException
@@ -401,8 +398,8 @@ public class localContextRoot implements Context
 
 
     /**
-     * 
-     * 
+     *
+     *
      * @see javax.naming.Context#lookup(javax.naming.Name)
      */
     public Object lookup(Name name) throws NamingException
@@ -410,7 +407,7 @@ public class localContextRoot implements Context
         synchronized (__root)
         {
             //return __root.lookup(getSuffix(name));
-            
+
             if(__log.isDebugEnabled())__log.debug("Looking up name=\""+name+"\"");
             Name cname = __root.toCanonicalName(name);
 
@@ -422,8 +419,8 @@ public class localContextRoot implements Context
                 return ctx;
             }
 
-        
-          
+
+
             if (cname.size() == 1)
             {
                 Binding binding = __root.getBinding (cname);
@@ -433,7 +430,7 @@ public class localContextRoot implements Context
                     nnfe.setRemainingName(cname);
                     throw nnfe;
                 }
-                    
+
 
                 Object o = binding.getObject();
 
@@ -464,7 +461,7 @@ public class localContextRoot implements Context
                     }
                     catch (final Exception e)
                     {
-                        throw new NamingException (e.getMessage()) 
+                        throw new NamingException (e.getMessage())
                         {
                             { initCause(e);}
                         };
@@ -475,7 +472,7 @@ public class localContextRoot implements Context
             }
 
             //it is a multipart name, get the first subcontext
-       
+
             String firstComponent = cname.get(0);
             Object ctx = null;
 
@@ -483,7 +480,7 @@ public class localContextRoot implements Context
                 ctx = this;
             else
             {
-                
+
                 Binding binding = __root.getBinding (firstComponent);
                 if (binding == null)
                 {
@@ -491,14 +488,14 @@ public class localContextRoot implements Context
                     nnfe.setRemainingName(cname);
                     throw nnfe;
                 }
-                
-                //as we have bound a reference to an object factory 
+
+                //as we have bound a reference to an object factory
                 //for the component specific contexts
                 //at "comp" we need to resolve the reference
                 ctx = binding.getObject();
-                
+
                 if (ctx instanceof Reference)
-                {  
+                {
                     //deference the object
                     try
                     {
@@ -519,14 +516,14 @@ public class localContextRoot implements Context
                 throw new NotContextException();
 
             return ((Context)ctx).lookup (cname.getSuffix(1));
-            
+
         }
     }
 
-    
+
     /**
-     * 
-     * 
+     *
+     *
      * @see javax.naming.Context#lookup(java.lang.String)
      */
     public Object lookup(String name) throws NamingException
@@ -536,11 +533,11 @@ public class localContextRoot implements Context
             return lookup(__root.getNameParser("").parse(getSuffix(name)));
         }
     }
-    
+
 
     /**
-     * 
-     * 
+     *
+     *
      * @see javax.naming.Context#bind(java.lang.String, java.lang.Object)
      */
     public void bind(String name, Object obj) throws NamingException
@@ -548,14 +545,14 @@ public class localContextRoot implements Context
         synchronized (__root)
         {
            bind(__root.getNameParser("").parse(getSuffix(name)), obj);
-            
+
         }
     }
 
 
     /**
-     * 
-     * 
+     *
+     *
      * @see javax.naming.Context#bind(javax.naming.Name, java.lang.Object)
      */
     public void bind(Name name, Object obj) throws NamingException
@@ -563,16 +560,16 @@ public class localContextRoot implements Context
         synchronized (__root)
         {
            // __root.bind(getSuffix(name), obj);
-            
-            
+
+
             if (__root.isLocked())
                 throw new NamingException ("This context is immutable");
 
             Name cname = __root.toCanonicalName(name);
-            
+
             if (cname == null)
                 throw new NamingException ("Name is null");
-            
+
             if (cname.size() == 0)
                 throw new NamingException ("Name is empty");
 
@@ -583,21 +580,21 @@ public class localContextRoot implements Context
                 //get the object to be bound
                 Object objToBind = NamingManager.getStateToBind(obj, name,this, _env);
                 // Check for Referenceable
-                if (objToBind instanceof Referenceable) 
+                if (objToBind instanceof Referenceable)
                 {
                     objToBind = ((Referenceable)objToBind).getReference();
                 }
-                
-                //anything else we should be able to bind directly    
+
+                //anything else we should be able to bind directly
                 __root.addBinding (cname, objToBind);
             }
             else
             {
                 if(__log.isDebugEnabled())__log.debug("Checking for existing binding for name="+cname+" for first element of name="+cname.get(0));
-              
-                //walk down the subcontext hierarchy       
+
+                //walk down the subcontext hierarchy
                 //need to ignore trailing empty "" name components
-                        
+
                 String firstComponent = cname.get(0);
                 Object ctx = null;
 
@@ -609,11 +606,11 @@ public class localContextRoot implements Context
                     Binding  binding = __root.getBinding (firstComponent);
                     if (binding == null)
                         throw new NameNotFoundException (firstComponent+ " is not bound");
-                    
+
                     ctx = binding.getObject();
-                    
+
                     if (ctx instanceof Reference)
-                    {  
+                    {
                         //deference the object
                         try
                         {
@@ -644,7 +641,7 @@ public class localContextRoot implements Context
 
     /**
      *
-     * 
+     *
      * @see javax.naming.Context#rebind(javax.naming.Name, java.lang.Object)
      */
     public void rebind(Name name, Object obj) throws NamingException
@@ -652,8 +649,8 @@ public class localContextRoot implements Context
         synchronized (__root)
         {
             //__root.rebind(getSuffix(name), obj);
-            
-            
+
+
             if (__root.isLocked())
                 throw new NamingException ("This context is immutable");
 
@@ -661,17 +658,17 @@ public class localContextRoot implements Context
 
             if (cname == null)
                 throw new NamingException ("Name is null");
-            
+
             if (cname.size() == 0)
                 throw new NamingException ("Name is empty");
 
 
             //if no subcontexts, just bind it
             if (cname.size() == 1)
-            {      
+            {
                 //check if it is a Referenceable
                 Object objToBind = NamingManager.getStateToBind(obj, name, __root, _env);
-                
+
                 if (objToBind instanceof Referenceable)
                 {
                     objToBind = ((Referenceable)objToBind).getReference();
@@ -680,14 +677,14 @@ public class localContextRoot implements Context
                 __root.addBinding (cname, objToBind);
             }
             else
-            { 
+            {
                 //walk down the subcontext hierarchy
                 if(__log.isDebugEnabled())__log.debug("Checking for existing binding for name="+cname+" for first element of name="+cname.get(0));
-                        
+
                 String firstComponent = cname.get(0);
                 Object ctx = null;
 
-                
+
                 if (firstComponent.equals(""))
                     ctx = this;
                 else
@@ -695,12 +692,12 @@ public class localContextRoot implements Context
                     Binding  binding = __root.getBinding (name.get(0));
                     if (binding == null)
                         throw new NameNotFoundException (name.get(0)+ " is not bound");
-                
+
                     ctx = binding.getObject();
 
 
                     if (ctx instanceof Reference)
-                    {  
+                    {
                         //deference the object
                         try
                         {
@@ -729,8 +726,8 @@ public class localContextRoot implements Context
     }
 
     /**
-     * 
-     * 
+     *
+     *
      * @see javax.naming.Context#rebind(java.lang.String, java.lang.Object)
      */
     public void rebind(String name, Object obj) throws NamingException
@@ -741,8 +738,8 @@ public class localContextRoot implements Context
         }
     }
     /**
-     * 
-     * 
+     *
+     *
      * @see javax.naming.Context#rename(javax.naming.Name, javax.naming.Name)
      */
     public void rename(Name oldName, Name newName) throws NamingException
@@ -754,8 +751,8 @@ public class localContextRoot implements Context
     }
 
     /**
-     * 
-     * 
+     *
+     *
      * @see javax.naming.Context#rename(java.lang.String, java.lang.String)
      */
     public void rename(String oldName, String newName) throws NamingException
@@ -767,8 +764,8 @@ public class localContextRoot implements Context
     }
 
     /**
-     * 
-     * 
+     *
+     *
      * @see javax.naming.Context#createSubcontext(java.lang.String)
      */
     public Context createSubcontext(String name) throws NamingException
@@ -782,20 +779,20 @@ public class localContextRoot implements Context
             //if (ctx.getParent() == __root)
             //    ctx.setEnv(_env);
             //return ctx;
-            
+
             return createSubcontext(__root.getNameParser("").parse(name));
         }
     }
 
     /**
-     * 
-     * 
+     *
+     *
      * @see javax.naming.Context#createSubcontext(javax.naming.Name)
      */
     public Context createSubcontext(Name name) throws NamingException
     {
         synchronized (__root)
-        {            
+        {
             //if the subcontext comes directly off the root, use the env of the InitialContext
             //as the root itself has no environment. Otherwise, it inherits the env of the parent
             //Context further down the tree.
@@ -803,17 +800,17 @@ public class localContextRoot implements Context
             //if (ctx.getParent() == __root)
             //    ctx.setEnv(_env);
             //return ctx;
-            
-            
-            
-            
+
+
+
+
             if (__root.isLocked())
             {
-                NamingException ne = new NamingException ("This context is immutable"); 
+                NamingException ne = new NamingException ("This context is immutable");
                 ne.setRemainingName(name);
                 throw ne;
             }
-            
+
             Name cname = __root.toCanonicalName (name);
 
             if (cname == null)
@@ -833,10 +830,10 @@ public class localContextRoot implements Context
                 __root.addBinding (cname, ctx);
                 return ctx;
             }
-            
-                
+
+
             //If the name has multiple subcontexts, walk the hierarchy by
-            //fetching the first one. All intermediate subcontexts in the 
+            //fetching the first one. All intermediate subcontexts in the
             //name must already exist.
             String firstComponent = cname.get(0);
             Object ctx = null;
@@ -848,11 +845,11 @@ public class localContextRoot implements Context
                 Binding binding = __root.getBinding (firstComponent);
                 if (binding == null)
                     throw new NameNotFoundException (firstComponent + " is not bound");
-                
+
                 ctx = binding.getObject();
-                
+
                 if (ctx instanceof Reference)
-                {  
+                {
                     //deference the object
                     if(__log.isDebugEnabled())__log.debug("Object bound at "+firstComponent +" is a Reference");
                     try
@@ -870,7 +867,7 @@ public class localContextRoot implements Context
                     }
                 }
             }
-            
+
             if (ctx instanceof Context)
             {
                 return ((Context)ctx).createSubcontext (cname.getSuffix(1));
@@ -880,10 +877,10 @@ public class localContextRoot implements Context
         }
     }
 
-  
+
     /**
      *
-     * 
+     *
      * @see javax.naming.Context#getNameParser(java.lang.String)
      */
     public NameParser getNameParser(String name) throws NamingException
@@ -892,8 +889,8 @@ public class localContextRoot implements Context
     }
 
     /**
-     * 
-     * 
+     *
+     *
      * @see javax.naming.Context#getNameParser(javax.naming.Name)
      */
     public NameParser getNameParser(Name name) throws NamingException
@@ -902,8 +899,8 @@ public class localContextRoot implements Context
     }
 
     /**
-     * 
-     * 
+     *
+     *
      * @see javax.naming.Context#list(java.lang.String)
      */
     public NamingEnumeration list(String name) throws NamingException
@@ -917,7 +914,7 @@ public class localContextRoot implements Context
 
     /**
      *
-     * 
+     *
      * @see javax.naming.Context#list(javax.naming.Name)
      */
     public NamingEnumeration list(Name name) throws NamingException
@@ -925,8 +922,8 @@ public class localContextRoot implements Context
         synchronized (__root)
         {
             //return __root.list(getSuffix(name));
-            
-           
+
+
             Name cname = __root.toCanonicalName(name);
 
             if (cname == null)
@@ -935,13 +932,13 @@ public class localContextRoot implements Context
                 return new NameEnumeration(empty.iterator());
             }
 
-            
+
             if (cname.size() == 0)
             {
-               return new NameEnumeration (__root.getBindings().values().iterator()); 
+               return new NameEnumeration (__root.getBindings().values().iterator());
             }
 
-          
+
 
             //multipart name
             String firstComponent = cname.get(0);
@@ -954,11 +951,11 @@ public class localContextRoot implements Context
                 Binding binding = __root.getBinding (firstComponent);
                 if (binding == null)
                     throw new NameNotFoundException ();
-                
+
                 ctx = binding.getObject();
-                
+
                 if (ctx instanceof Reference)
-                {  
+                {
                     //deference the object
                     if(__log.isDebugEnabled())__log.debug("Dereferencing Reference for "+name.get(0));
                     try
@@ -980,14 +977,14 @@ public class localContextRoot implements Context
             if (!(ctx instanceof Context))
                 throw new NotContextException();
 
-            return ((Context)ctx).list (cname.getSuffix(1));       
-            
+            return ((Context)ctx).list (cname.getSuffix(1));
+
         }
     }
 
     /**
      *
-     * 
+     *
      * @see javax.naming.Context#listBindings(javax.naming.Name)
      */
     public NamingEnumeration listBindings(Name name) throws NamingException
@@ -995,7 +992,7 @@ public class localContextRoot implements Context
         synchronized (__root)
         {
             //return __root.listBindings(getSuffix(name));
-            
+
             Name cname = __root.toCanonicalName (name);
 
             if (cname == null)
@@ -1006,11 +1003,11 @@ public class localContextRoot implements Context
 
             if (cname.size() == 0)
             {
-               return new BindingEnumeration (__root.getBindings().values().iterator()); 
+               return new BindingEnumeration (__root.getBindings().values().iterator());
             }
 
-          
-            
+
+
             //multipart name
             String firstComponent = cname.get(0);
             Object ctx = null;
@@ -1025,11 +1022,11 @@ public class localContextRoot implements Context
                 Binding binding = __root.getBinding (firstComponent);
                 if (binding == null)
                     throw new NameNotFoundException ();
-            
+
                 ctx = binding.getObject();
 
                 if (ctx instanceof Reference)
-                {  
+                {
                     //deference the object
                     try
                     {
@@ -1051,14 +1048,14 @@ public class localContextRoot implements Context
                 throw new NotContextException();
 
             return ((Context)ctx).listBindings (cname.getSuffix(1));
-            
+
         }
     }
 
 
     /**
      *
-     * 
+     *
      * @see javax.naming.Context#listBindings(java.lang.String)
      */
     public NamingEnumeration listBindings(String name) throws NamingException
@@ -1068,11 +1065,11 @@ public class localContextRoot implements Context
             return listBindings(__root.getNameParser("").parse(getSuffix(name)));
         }
     }
-    
-    
+
+
     /**
      *
-     * 
+     *
      * @see javax.naming.Context#addToEnvironment(java.lang.String,
      *      java.lang.Object)
      */
@@ -1084,7 +1081,7 @@ public class localContextRoot implements Context
 
     /**
      *
-     * 
+     *
      * @see javax.naming.Context#composeName(java.lang.String, java.lang.String)
      */
     public String composeName(String name, String prefix)
@@ -1095,7 +1092,7 @@ public class localContextRoot implements Context
 
     /**
      *
-     * 
+     *
      * @see javax.naming.Context#composeName(javax.naming.Name,
      *      javax.naming.Name)
      */
diff --git a/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/local/package-info.java b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/local/package-info.java
new file mode 100644
index 0000000..17b3386
--- /dev/null
+++ b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/local/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Jndi : Mappings for local
+ */
+package org.eclipse.jetty.jndi.local;
+
diff --git a/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/package-info.java b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/package-info.java
new file mode 100644
index 0000000..f7281a6
--- /dev/null
+++ b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Jndi : Java Naming Directory Interface 
+ */
+package org.eclipse.jetty.jndi;
+
diff --git a/jetty-jndi/src/test/java/org/eclipse/jetty/jndi/factories/TestMailSessionReference.java b/jetty-jndi/src/test/java/org/eclipse/jetty/jndi/factories/TestMailSessionReference.java
index 316c7b4..326417e 100644
--- a/jetty-jndi/src/test/java/org/eclipse/jetty/jndi/factories/TestMailSessionReference.java
+++ b/jetty-jndi/src/test/java/org/eclipse/jetty/jndi/factories/TestMailSessionReference.java
@@ -18,7 +18,12 @@
 
 package org.eclipse.jetty.jndi.factories;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
 import java.util.Properties;
+
 import javax.mail.Session;
 import javax.naming.Context;
 import javax.naming.InitialContext;
@@ -29,10 +34,6 @@ import javax.naming.NameParser;
 import org.eclipse.jetty.jndi.NamingUtil;
 import org.junit.Test;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-
 /**
  *
  */
diff --git a/jetty-jndi/src/test/java/org/eclipse/jetty/jndi/java/TestJNDI.java b/jetty-jndi/src/test/java/org/eclipse/jetty/jndi/java/TestJNDI.java
index 42e13bf..608d091 100644
--- a/jetty-jndi/src/test/java/org/eclipse/jetty/jndi/java/TestJNDI.java
+++ b/jetty-jndi/src/test/java/org/eclipse/jetty/jndi/java/TestJNDI.java
@@ -18,12 +18,16 @@
 
 package org.eclipse.jetty.jndi.java;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
 import java.net.URL;
 import java.net.URLClassLoader;
 import java.util.HashMap;
 import java.util.Hashtable;
 
-import javax.naming.Binding;
 import javax.naming.Context;
 import javax.naming.InitialContext;
 import javax.naming.LinkRef;
@@ -39,21 +43,13 @@ import javax.naming.spi.ObjectFactory;
 import javax.servlet.ServletContextEvent;
 import javax.servlet.ServletContextListener;
 
-import org.eclipse.jetty.jndi.ContextFactory;
 import org.eclipse.jetty.jndi.NamingContext;
-import org.eclipse.jetty.jndi.NamingUtil;
-import org.eclipse.jetty.jndi.local.localContextRoot;
+import org.eclipse.jetty.server.Server;
 import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.HandlerList;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
-import org.junit.Ignore;
 import org.junit.Test;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNotSame;
 /**
  *
  */
@@ -63,9 +59,9 @@ public class TestJNDI
 
     static
     {
-        // NamingUtil.__log.setDebugEnabled(true);    
+        // NamingUtil.__log.setDebugEnabled(true);
     }
-    
+
     public static class MyObjectFactory implements ObjectFactory
     {
         public static String myString = "xxx";
@@ -74,7 +70,7 @@ public class TestJNDI
         {
             return myString;
         }
-        
+
     }
     
     
@@ -82,17 +78,24 @@ public class TestJNDI
     public void testThreadContextClassloaderAndCurrentContext()
     throws Exception
     {
+
+        
         //create a jetty context, and start it so that its classloader it created
         //and it is the current context
         ClassLoader currentLoader = Thread.currentThread().getContextClassLoader();
         ContextHandler ch = new ContextHandler();
         URLClassLoader chLoader = new URLClassLoader(new URL[0], currentLoader);
         ch.setClassLoader(chLoader);
-        
+        Server server = new Server();      
+        HandlerList hl = new HandlerList();
+        server.setHandler(hl);
+        hl.addHandler(ch);
+              
         //Create another one
         ContextHandler ch2 = new ContextHandler();
         URLClassLoader ch2Loader = new URLClassLoader(new URL[0], currentLoader);
         ch2.setClassLoader(ch2Loader);
+        hl.addHandler(ch2);
         
         try
         {
@@ -137,6 +140,7 @@ public class TestJNDI
             });
             //Starting the context makes it current and creates a classloader for it
             ch.start();
+
             
             ch2.setContextPath("/ch2");
             ch2.addEventListener(new ServletContextListener()
@@ -442,6 +446,7 @@ public class TestJNDI
                 //expected failure to modify immutable context
             }
             
+
             initCtx.close();
         }
         finally
diff --git a/jetty-jndi/src/test/java/org/eclipse/jetty/jndi/java/TestLocalJNDI.java b/jetty-jndi/src/test/java/org/eclipse/jetty/jndi/java/TestLocalJNDI.java
index 30b193d..991636e 100644
--- a/jetty-jndi/src/test/java/org/eclipse/jetty/jndi/java/TestLocalJNDI.java
+++ b/jetty-jndi/src/test/java/org/eclipse/jetty/jndi/java/TestLocalJNDI.java
@@ -18,6 +18,10 @@
 
 package org.eclipse.jetty.jndi.java;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
 import java.util.Hashtable;
 
 import javax.naming.Context;
@@ -36,10 +40,6 @@ import org.eclipse.jetty.jndi.NamingUtil;
 import org.junit.After;
 import org.junit.Test;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.fail;
-
 /**
  *
  */
@@ -47,23 +47,23 @@ public class TestLocalJNDI
 {
     public static class FruitFactory implements ObjectFactory
     {
-        public FruitFactory() 
+        public FruitFactory()
         {
         }
-        
-        public Object getObjectInstance(Object obj, Name name, Context ctx, Hashtable env) throws Exception 
+
+        public Object getObjectInstance(Object obj, Name name, Context ctx, Hashtable env) throws Exception
         {
-            
+
             if (!env.containsKey("flavour"))
                 throw new Exception ("No flavour!");
-            
-            if (obj instanceof Reference) 
+
+            if (obj instanceof Reference)
             {
                 Reference ref = (Reference)obj;
-                if (ref.getClassName().equals(Fruit.class.getName())) 
+                if (ref.getClassName().equals(Fruit.class.getName()))
                 {
                     RefAddr addr = ref.get("fruit");
-                    if (addr != null) 
+                    if (addr != null)
                     {
                         return new Fruit((String)addr.getContent());
                     }
@@ -72,18 +72,18 @@ public class TestLocalJNDI
             return null;
          }
     }
-    
-    
-    public static class Fruit implements Referenceable 
+
+
+    public static class Fruit implements Referenceable
     {
         String fruit;
-        
-        public Fruit(String f) 
+
+        public Fruit(String f)
         {
             fruit = f;
         }
-        
-        public Reference getReference() throws NamingException 
+
+        public Reference getReference() throws NamingException
         {
             return new Reference(
                 Fruit.class.getName(),
@@ -92,36 +92,36 @@ public class TestLocalJNDI
                 null);          // Factory location
         }
 
-        public String toString() 
+        public String toString()
         {
             return fruit;
         }
     }
-    
-    
-    
-    
-    
-    
-    
-    
+
+
+
+
+
+
+
+
     @After
     public void tearDown() throws Exception
     {
         InitialContext ic = new InitialContext();
         ic.destroySubcontext("a");
     }
-    
-    
+
+
     @Test
     public void testLocalReferenceable() throws Exception
     {
         Hashtable<String,String> env1 = new Hashtable<String,String>();
         env1.put("flavour", "orange");
         InitialContext ic1 = new InitialContext(env1);
-        
+
         ic1.bind("valencia", new Fruit("orange"));
-        
+
         Object o = ic1.lookup("valencia");
 
         Hashtable<String,String> env2 = new Hashtable<String,String>();
@@ -144,21 +144,21 @@ public class TestLocalJNDI
         Hashtable<String,String> env1 = new Hashtable<String,String>();
         env1.put("make", "holden");
         env1.put("model", "commodore");
-        
+
         Object car1 = new Object();
-        
+
         InitialContext ic = new InitialContext(env1);
         ic.bind("car1", car1);
         assertNotNull(ic.lookup("car1"));
         assertEquals(car1, ic.lookup("car1"));
-        
+
         Context carz = ic.createSubcontext("carz");
         assertNotNull(carz);
         Hashtable ht = carz.getEnvironment();
         assertNotNull(ht);
         assertEquals("holden", ht.get("make"));
         assertEquals("commodore", ht.get("model"));
-        
+
         Hashtable<String,String> env2 = new Hashtable<String,String>();
         env2.put("flavour", "strawberry");
         InitialContext ic2 = new InitialContext(env2);
@@ -168,12 +168,12 @@ public class TestLocalJNDI
         ht = c.getEnvironment();
         assertEquals("holden", ht.get("make"));
         assertEquals("commodore", ht.get("model"));
-        
+
         Context icecreamz = ic2.createSubcontext("icecreamz");
         ht = icecreamz.getEnvironment();
         assertNotNull(ht);
         assertEquals("strawberry", ht.get("flavour"));
-        
+
         Context hatchbackz = ic2.createSubcontext("carz/hatchbackz");
         assertNotNull(hatchbackz);
         ht = hatchbackz.getEnvironment();
@@ -181,16 +181,16 @@ public class TestLocalJNDI
         assertEquals("holden", ht.get("make"));
         assertEquals("commodore", ht.get("model"));
         assertEquals(null, ht.get("flavour"));
-        
+
         c = (Context)ic.lookup("carz/hatchbackz");
         assertNotNull(c);
         assertEquals(hatchbackz, c);
-        
+
     }
-    
-    
-    
-    
+
+
+
+
     @Test
     public void testLocal () throws Exception
     {
diff --git a/jetty-jsp/pom.xml b/jetty-jsp/pom.xml
index 815e4db..a83dcf6 100644
--- a/jetty-jsp/pom.xml
+++ b/jetty-jsp/pom.xml
@@ -2,58 +2,103 @@
   <parent>
     <groupId>org.eclipse.jetty</groupId>
     <artifactId>jetty-project</artifactId>
-    <version>8.1.17.v20150415</version>
+    <version>9.2.13.v20150730</version>
   </parent>
   <modelVersion>4.0.0</modelVersion>
   <artifactId>jetty-jsp</artifactId>
-  <name>Jetty :: JSP dependencies</name>
+  <name>Jetty :: Glassfish JSP Implementation</name>
   <url>http://www.eclipse.org/jetty</url>
   <packaging>jar</packaging>
   <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-assembly-plugin</artifactId>
+        <executions>
+          <execution>
+            <phase>package</phase>
+            <goals>
+              <goal>single</goal>
+            </goals>
+            <configuration>
+              <descriptorRefs>
+                <descriptorRef>config</descriptorRef>
+              </descriptorRefs>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
   </build>
 
   <dependencies>
+
+    <dependency>
+        <groupId>org.eclipse.jetty</groupId>
+        <artifactId>jetty-util</artifactId>
+        <version>${project.version}</version>
+        <scope>provided</scope>
+    </dependency>
+    <dependency>
+        <groupId>org.eclipse.jetty</groupId>
+        <artifactId>jetty-server</artifactId>
+        <version>${project.version}</version>
+        <scope>provided</scope>
+    </dependency>
+
+    <!-- Schemas -->
+    <dependency>
+      <groupId>org.eclipse.jetty.toolchain</groupId>
+      <artifactId>jetty-schemas</artifactId>
+    </dependency>
+
+    <!-- servlet api -->
+    <dependency>
+       <groupId>javax.servlet</groupId>
+       <artifactId>javax.servlet-api</artifactId>
+    </dependency>
+
     <!-- JSP Api -->
     <dependency>
-      <groupId>org.eclipse.jetty.orbit</groupId>
-      <artifactId>javax.servlet.jsp</artifactId>
-      <version>2.2.0.v201112011158</version>
+      <groupId>javax.servlet.jsp</groupId>
+      <artifactId>javax.servlet.jsp-api</artifactId>
     </dependency>
     <!-- JSP Impl -->
     <dependency>
-       <groupId>org.eclipse.jetty.orbit</groupId>
-       <artifactId>org.apache.jasper.glassfish</artifactId>
-       <version>2.2.2.v201112011158</version>
+      <groupId>org.glassfish.web</groupId>
+      <artifactId>javax.servlet.jsp</artifactId>
     </dependency>
+
     <!-- JSTL Api -->
     <dependency>
        <groupId>org.eclipse.jetty.orbit</groupId>
        <artifactId>javax.servlet.jsp.jstl</artifactId>
-       <version>1.2.0.v201105211821</version>
     </dependency>
     <!-- JSTL Impl -->
     <dependency>
-       <groupId>org.eclipse.jetty.orbit</groupId>
-       <artifactId>org.apache.taglibs.standard.glassfish</artifactId>
-       <version>1.2.0.v201112081803</version>
+       <groupId>org.glassfish.web</groupId>
+       <artifactId>javax.servlet.jsp.jstl</artifactId>
     </dependency>
+
     <!-- EL Api -->
+    <!-- Not needed as glassfish impl jars contain also the api classes
     <dependency>
-      <groupId>org.eclipse.jetty.orbit</groupId>
-      <artifactId>javax.el</artifactId>
-      <version>2.2.0.v201108011116</version>
+      <groupId>javax.el</groupId>
+      <artifactId>javax.el-api</artifactId>
     </dependency>
+    -->
+
     <!-- EL Impl -->
     <dependency>
-      <groupId>org.eclipse.jetty.orbit</groupId>
-      <artifactId>com.sun.el</artifactId>
-      <version>2.2.0.v201108011116</version>
+        <groupId>org.glassfish</groupId>
+        <artifactId>javax.el</artifactId>
     </dependency>
+
+
     <!-- Eclipse Java Compiler (for JSP Compilation) -->
     <dependency>
       <groupId>org.eclipse.jetty.orbit</groupId>
       <artifactId>org.eclipse.jdt.core</artifactId>
-      <version>3.7.1</version>
     </dependency>
   </dependencies>
 </project>
diff --git a/jetty-jsp/src/main/config/modules/jsp-impl/glassfish-jsp.mod b/jetty-jsp/src/main/config/modules/jsp-impl/glassfish-jsp.mod
new file mode 100644
index 0000000..130d2b3
--- /dev/null
+++ b/jetty-jsp/src/main/config/modules/jsp-impl/glassfish-jsp.mod
@@ -0,0 +1,8 @@
+#
+# Glassfish JSP Module
+#
+[name]
+jsp-impl
+
+[lib]
+lib/jsp/*.jar
diff --git a/jetty-jsp/src/main/config/modules/jsp-impl/glassfish-jstl.mod b/jetty-jsp/src/main/config/modules/jsp-impl/glassfish-jstl.mod
new file mode 100644
index 0000000..4b8e6f3
--- /dev/null
+++ b/jetty-jsp/src/main/config/modules/jsp-impl/glassfish-jstl.mod
@@ -0,0 +1,6 @@
+#
+# Glassfish JSTL
+[name]
+jstl-impl
+
+# This file is empty as glassfish jstl is provided by glassfish jsp
diff --git a/jetty-jsp/src/main/java/org/eclipse/jetty/jsp/JettyJspServlet.java b/jetty-jsp/src/main/java/org/eclipse/jetty/jsp/JettyJspServlet.java
new file mode 100644
index 0000000..05a9d51
--- /dev/null
+++ b/jetty-jsp/src/main/java/org/eclipse/jetty/jsp/JettyJspServlet.java
@@ -0,0 +1,107 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.jsp;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.jasper.servlet.JspServlet;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.resource.Resource;
+
+
+/**
+ * JettyJspServlet
+ *
+ * Wrapper for the jsp servlet that handles receiving requests mapped from 
+ * jsp-property-groups. Mappings could be wildcard urls like "/*", which would
+ * include welcome files, but we need those to be handled by the DefaultServlet.
+ */
+public class JettyJspServlet extends JspServlet
+{
+
+    /**
+     * 
+     */
+    private static final long serialVersionUID = -5387857473125086791L;
+
+    @Override
+    public void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+    {
+        
+        
+        HttpServletRequest request = null;
+        if (req instanceof HttpServletRequest)
+            request = (HttpServletRequest)req;
+        else
+            throw new ServletException("Request not HttpServletRequest");
+
+        String servletPath=null;
+        String pathInfo=null;
+        if (request.getAttribute("javax.servlet.include.request_uri")!=null)
+        {
+            servletPath=(String)request.getAttribute("javax.servlet.include.servlet_path");
+            pathInfo=(String)request.getAttribute("javax.servlet.include.path_info");
+            if (servletPath==null)
+            {
+                servletPath=request.getServletPath();
+                pathInfo=request.getPathInfo();
+            }
+        }
+        else
+        {
+            servletPath = request.getServletPath();
+            pathInfo = request.getPathInfo();
+        }
+        
+        String pathInContext = URIUtil.addPaths(servletPath,pathInfo);
+        String jspFile = getInitParameter("jspFile");
+        
+        //if the request is for a jsp file then fall through to the jsp servlet
+        if (jspFile == null)
+        {
+            if (pathInContext.endsWith("/"))
+            {
+                //dispatch via forward to the default servlet
+                getServletContext().getNamedDispatcher("default").forward(req, resp);
+                return;
+            }
+            else
+            {      
+                //check if it resolves to a directory
+                Resource resource = ((ContextHandler.Context)getServletContext()).getContextHandler().getResource(pathInContext);           
+                if (resource!=null && resource.isDirectory())
+                {
+                    //dispatch via forward to the default servlet
+                    getServletContext().getNamedDispatcher("default").forward(req, resp);
+                    return;
+                }
+            }
+        }
+
+        //fall through to the normal jsp servlet handling
+        super.service(req, resp);
+    }
+
+    
+}
diff --git a/jetty-jspc-maven-plugin/pom.xml b/jetty-jspc-maven-plugin/pom.xml
new file mode 100644
index 0000000..e0872c8
--- /dev/null
+++ b/jetty-jspc-maven-plugin/pom.xml
@@ -0,0 +1,115 @@
+<?xml version="1.0" encoding="UTF-8"?><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">
+  <parent>
+    <groupId>org.eclipse.jetty</groupId>
+    <artifactId>jetty-project</artifactId>
+    <version>9.2.13.v20150730</version>
+  </parent>
+  <modelVersion>4.0.0</modelVersion>
+  <artifactId>jetty-jspc-maven-plugin</artifactId>
+  <packaging>maven-plugin</packaging>
+  <name>Jetty :: Jetty JSPC Maven Plugin</name>
+  <properties>
+  </properties>
+  <build>
+    <plugins>
+      <plugin>
+        <artifactId>maven-surefire-plugin</artifactId>
+        <configuration>
+          <skip>true</skip>
+        </configuration>
+      </plugin>
+      <plugin>
+        <artifactId>maven-plugin-plugin</artifactId>
+        <version>2.9</version>
+        <executions>
+          <execution>
+            <id>exec-plugin-doc</id>
+            <phase>generate-sources</phase>
+            <goals>
+              <goal>xdoc</goal>
+              <goal>helpmojo</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+  <dependencies>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-util</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.maven</groupId>
+      <artifactId>maven-project</artifactId>
+      <version>2.0.3</version>
+      <exclusions>
+        <exclusion>
+          <groupId>junit</groupId>
+          <artifactId>junit</artifactId>
+        </exclusion>
+      </exclusions>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.maven</groupId>
+      <artifactId>maven-plugin-api</artifactId>
+      <version>2.0.3</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.maven</groupId>
+      <artifactId>maven-artifact</artifactId>
+      <version>2.0.3</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.maven.plugin-tools</groupId>
+      <artifactId>maven-plugin-tools-api</artifactId>
+      <version>3.1</version>
+      <exclusions>
+        <exclusion>
+          <groupId>junit</groupId>
+          <artifactId>junit</artifactId>
+        </exclusion>
+      </exclusions>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>apache-jsp</artifactId>
+      <version>${project.version}</version>
+     </dependency>
+    <dependency>
+      <groupId>org.apache.ant</groupId>
+      <artifactId>ant</artifactId>
+      <version>1.8.4</version>
+    </dependency>
+    <dependency>
+        <groupId>org.eclipse.jetty</groupId>
+        <artifactId>apache-jstl</artifactId>
+        <version>${project.version}</version>
+      </dependency>
+    </dependencies>
+  <reporting>
+  <plugins>
+    <plugin>
+      <groupId>org.apache.maven.plugins</groupId>
+      <artifactId>maven-project-info-reports-plugin</artifactId>
+      <version>2.1</version>
+      <configuration>
+        <dependencyLocationEnabled>false</dependencyLocationEnabled>
+       </configuration>
+         <reportSets>
+          <reportSet>
+            <reports>
+              <report>project-team</report>
+              <report>mailing-list</report>
+              <report>cim</report>
+              <report>issue-tracking</report>
+              <report>license</report>
+              <report>scm</report>
+            </reports>
+          </reportSet>
+        </reportSets>
+    </plugin>
+  </plugins>
+  </reporting>
+</project>
diff --git a/jetty-jspc-maven-plugin/src/main/java/org/eclipse/jetty/jspc/plugin/JspcMojo.java b/jetty-jspc-maven-plugin/src/main/java/org/eclipse/jetty/jspc/plugin/JspcMojo.java
new file mode 100644
index 0000000..a6e8755
--- /dev/null
+++ b/jetty-jspc-maven-plugin/src/main/java/org/eclipse/jetty/jspc/plugin/JspcMojo.java
@@ -0,0 +1,617 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.jspc.plugin;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileFilter;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.net.URI;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+import org.apache.jasper.JspC;
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.plugin.AbstractMojo;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.MojoFailureException;
+import org.apache.maven.project.MavenProject;
+import org.codehaus.plexus.util.FileUtils;
+import org.codehaus.plexus.util.StringUtils;
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.PatternMatcher;
+import org.eclipse.jetty.util.resource.Resource;
+
+/**
+ * <p>
+ * This goal will compile jsps for a webapp so that they can be included in a
+ * war.
+ * </p>
+ * <p>
+ * At runtime, the plugin will use the jsp2.0 jspc compiler if you are running
+ * on a 1.4 or lower jvm. If you are using a 1.5 jvm, then the jsp2.1 compiler
+ * will be selected. (this is the same behaviour as the <a
+ * href="http://jetty.mortbay.org/maven-plugin">jetty plugin</a> for executing
+ * webapps).
+ * </p>
+ * <p>
+ * Note that the same java compiler will be used as for on-the-fly compiled
+ * jsps, which will be the Eclipse java compiler.
+ * </p>
+ * 
+ * <p>
+ * See <a
+ * href="http://docs.codehaus.org/display/JETTY/Maven+Jetty+Jspc+Plugin">Usage
+ * Guide</a> for instructions on using this plugin.
+ * </p>
+ * 
+ * @author janb
+ * 
+ * @goal jspc
+ * @phase process-classes
+ * @requiresDependencyResolution compile
+ * @description Runs jspc compiler to produce .java and .class files
+ */
+public class JspcMojo extends AbstractMojo
+{
+    public static final String END_OF_WEBAPP = "</web-app>";
+    public static final String PRECOMPILED_FLAG = "org.eclipse.jetty.jsp.precompiled";
+
+
+    /**
+     * JettyJspC
+     *
+     * Add some extra setters to standard JspC class to help configure it
+     * for running in maven.
+     */
+    public static class JettyJspC extends JspC
+    {
+        public void setClassLoader (ClassLoader loader)
+        {
+            this.loader = loader;
+        }
+    }
+    
+    
+    /**
+     * Whether or not to include dependencies on the plugin's classpath with <scope>provided</scope>
+     * Use WITH CAUTION as you may wind up with duplicate jars/classes.
+     * 
+     * @since jetty-7.6.3
+     * @parameter  default-value="false"
+     */
+    private boolean useProvidedScope;
+    
+    /**
+     * The artifacts for the project.
+     * 
+     * @since jetty-7.6.3
+     * @parameter expression="${project.artifacts}"
+     * @readonly
+     */
+    private Set projectArtifacts;
+    
+    
+    /**
+     * The maven project.
+     * 
+     * @parameter expression="${project}"
+     * @required
+     * @readonly
+     */
+    private MavenProject project;
+
+    
+
+    /**
+     * The artifacts for the plugin itself.
+     * 
+     * @parameter expression="${plugin.artifacts}"
+     * @readonly
+     */
+    private List pluginArtifacts;
+    
+    
+    /**
+     * File into which to generate the <servlet> and
+     * <servlet-mapping> tags for the compiled jsps
+     * 
+     * @parameter default-value="${basedir}/target/webfrag.xml"
+     */
+    private String webXmlFragment;
+
+    /**
+     * Optional. A marker string in the src web.xml file which indicates where
+     * to merge in the generated web.xml fragment. Note that the marker string
+     * will NOT be preserved during the insertion. Can be left blank, in which
+     * case the generated fragment is inserted just before the </web-app>
+     * line
+     * 
+     * @parameter
+     */
+    private String insertionMarker;
+
+    /**
+     * Merge the generated fragment file with the web.xml from
+     * webAppSourceDirectory. The merged file will go into the same directory as
+     * the webXmlFragment.
+     * 
+     * @parameter default-value="true"
+     */
+    private boolean mergeFragment;
+
+    /**
+     * The destination directory into which to put the compiled jsps.
+     * 
+     * @parameter default-value="${project.build.outputDirectory}"
+     */
+    private String generatedClasses;
+
+    /**
+     * Controls whether or not .java files generated during compilation will be
+     * preserved.
+     * 
+     * @parameter default-value="false"
+     */
+    private boolean keepSources;
+
+
+    /**
+     * Root directory for all html/jsp etc files
+     * 
+     * @parameter default-value="${basedir}/src/main/webapp"
+     * 
+     */
+    private String webAppSourceDirectory;
+    
+   
+    
+    /**
+     * Location of web.xml. Defaults to src/main/webapp/web.xml.
+     * @parameter default-value="${basedir}/src/main/webapp/WEB-INF/web.xml"
+     */
+    private String webXml;
+
+
+    /**
+     * The comma separated list of patterns for file extensions to be processed. By default
+     * will include all .jsp and .jspx files.
+     * 
+     * @parameter default-value="**\/*.jsp, **\/*.jspx"
+     */
+    private String includes;
+
+    /**
+     * The comma separated list of file name patters to exclude from compilation.
+     * 
+     * @parameter default_value="**\/.svn\/**";
+     */
+    private String excludes;
+
+    /**
+     * The location of the compiled classes for the webapp
+     * 
+     * @parameter expression="${project.build.outputDirectory}"
+     */
+    private File classesDirectory;
+
+    
+    /**
+     * Patterns of jars on the system path that contain tlds. Use | to separate each pattern.
+     * 
+     * @parameter default-value=".*taglibs[^/]*\.jar|.*jstl[^/]*\.jar$
+     */
+    private String tldJarNamePatterns;
+    
+    
+    /**
+     * 
+     * The JspC instance being used to compile the jsps.
+     * 
+     * @parameter
+     */
+    private JettyJspC jspc;
+
+
+
+    
+
+    public void execute() throws MojoExecutionException, MojoFailureException
+    {
+        if (getLog().isDebugEnabled())
+        {
+
+            getLog().info("webAppSourceDirectory=" + webAppSourceDirectory);
+            getLog().info("generatedClasses=" + generatedClasses);
+            getLog().info("webXmlFragment=" + webXmlFragment);
+            getLog().info("webXml="+webXml);
+            getLog().info("insertionMarker="+ (insertionMarker == null || insertionMarker.equals("") ? END_OF_WEBAPP : insertionMarker));
+            getLog().info("keepSources=" + keepSources);
+            getLog().info("mergeFragment=" + mergeFragment);            
+        }
+        try
+        {
+            prepare();
+            compile();
+            cleanupSrcs();
+            mergeWebXml();
+        }
+        catch (Exception e)
+        {
+            throw new MojoExecutionException("Failure processing jsps", e);
+        }
+    }
+
+    public void compile() throws Exception
+    {
+        ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader();
+
+        //set up the classpath of the webapp
+        List<URL> webAppUrls = setUpWebAppClassPath();
+        
+        //set up the classpath of the container (ie jetty and jsp jars)
+        String sysClassPath = setUpSysClassPath();
+        
+        //get the list of system classpath jars that contain tlds
+        List<URL> tldJarUrls = getSystemJarsWithTlds();
+        
+        for (URL u:tldJarUrls)
+        {
+            if (getLog().isDebugEnabled())
+                getLog().debug(" sys jar with tlds: "+u);
+            webAppUrls.add(u);
+        }
+
+      
+        //use the classpaths as the classloader
+        URLClassLoader webAppClassLoader = new URLClassLoader((URL[]) webAppUrls.toArray(new URL[0]), currentClassLoader);
+        StringBuffer webAppClassPath = new StringBuffer();
+
+        for (int i = 0; i < webAppUrls.size(); i++)
+        {
+            if (getLog().isDebugEnabled())
+                getLog().debug("webappclassloader contains: " + webAppUrls.get(i));                
+            webAppClassPath.append(new File(webAppUrls.get(i).toURI()).getCanonicalPath());
+            if (getLog().isDebugEnabled())
+                getLog().debug("added to classpath: " + ((URL) webAppUrls.get(i)).getFile());
+            if (i+1<webAppUrls.size())
+                webAppClassPath.append(System.getProperty("path.separator"));
+        }
+        
+        //Interpose a fake classloader as the webapp class loader. This is because the Apache JspC class
+        //uses a TldScanner which ignores jars outside of the WEB-INF/lib path on the webapp classloader.
+        //It will, however, look at all jars on the parents of the webapp classloader.
+        URLClassLoader fakeWebAppClassLoader = new URLClassLoader(new URL[0], webAppClassLoader);
+        Thread.currentThread().setContextClassLoader(fakeWebAppClassLoader);
+  
+        if (jspc == null)
+            jspc = new JettyJspC();
+        
+        jspc.setWebXmlFragment(webXmlFragment);
+        jspc.setUriroot(webAppSourceDirectory);     
+        jspc.setOutputDir(generatedClasses);
+        jspc.setClassPath(sysClassPath+System.getProperty("path.separator")+webAppClassPath.toString());
+        jspc.setClassLoader(fakeWebAppClassLoader);
+        jspc.setCompile(true);
+
+        // JspC#setExtensions() does not exist, so 
+        // always set concrete list of files that will be processed.
+        String jspFiles = getJspFiles(webAppSourceDirectory);
+        getLog().info("Compiling "+jspFiles);
+        getLog().info("Includes="+includes);
+        getLog().info("Excludes="+excludes);
+        jspc.setJspFiles(jspFiles);
+
+        getLog().info("Files selected to precompile: " + jspFiles);
+
+        jspc.execute();
+
+        Thread.currentThread().setContextClassLoader(currentClassLoader);
+    }
+
+    private String getJspFiles(String webAppSourceDirectory)
+    throws Exception
+    {
+        List fileNames =  FileUtils.getFileNames(new File(webAppSourceDirectory),includes, excludes, false);
+        return StringUtils.join(fileNames.toArray(new String[0]), ",");
+
+    }
+
+    /**
+     * Until Jasper supports the option to generate the srcs in a different dir
+     * than the classes, this is the best we can do.
+     * 
+     * @throws Exception
+     */
+    public void cleanupSrcs() throws Exception
+    {
+        // delete the .java files - depending on keepGenerated setting
+        if (!keepSources)
+        {
+            File generatedClassesDir = new File(generatedClasses);
+
+            if(generatedClassesDir.exists() && generatedClassesDir.isDirectory())
+            {
+                delete(generatedClassesDir, new FileFilter()
+                {
+                    public boolean accept(File f)
+                    {
+                        return f.isDirectory() || f.getName().endsWith(".java");
+                    }                
+                });
+            }
+        }
+    }
+    
+    static void delete(File dir, FileFilter filter)
+    {
+        File[] files = dir.listFiles(filter);
+        if (files != null)
+        {
+            for(File f: files)
+            {
+                if(f.isDirectory())
+                    delete(f, filter);
+                else
+                    f.delete();
+            }
+        }
+    }
+
+    /**
+     * Take the web fragment and put it inside a copy of the web.xml.
+     * 
+     * You can specify the insertion point by specifying the string in the
+     * insertionMarker configuration entry.
+     * 
+     * If you dont specify the insertionMarker, then the fragment will be
+     * inserted at the end of the file just before the </webapp>
+     * 
+     * @throws Exception
+     */
+    public void mergeWebXml() throws Exception
+    {
+        if (mergeFragment)
+        {
+            // open the src web.xml
+            File webXml = getWebXmlFile();
+           
+            if (!webXml.exists())
+            {
+                getLog().info(webXml.toString() + " does not exist, cannot merge with generated fragment");
+                return;
+            }
+
+            File fragmentWebXml = new File(webXmlFragment);
+            if (!fragmentWebXml.exists())
+            {
+                getLog().info("No fragment web.xml file generated");
+            }
+            File mergedWebXml = new File(fragmentWebXml.getParentFile(),
+            "web.xml");
+            try (BufferedReader webXmlReader = new BufferedReader(new FileReader(
+                    webXml));
+                 PrintWriter mergedWebXmlWriter = new PrintWriter(new FileWriter(
+                    mergedWebXml))) {
+
+                // read up to the insertion marker or the </webapp> if there is no
+                // marker
+                boolean atInsertPoint = false;
+                boolean atEOF = false;
+                String marker = (insertionMarker == null
+                        || insertionMarker.equals("") ? END_OF_WEBAPP : insertionMarker);
+                while (!atInsertPoint && !atEOF)
+                {
+                    String line = webXmlReader.readLine();
+                    if (line == null)
+                        atEOF = true;
+                    else if (line.indexOf(marker) >= 0)
+                    {
+                        atInsertPoint = true;
+                    }
+                    else
+                    {
+                        mergedWebXmlWriter.println(line);
+                    }
+                }
+                
+                if (atEOF && !atInsertPoint)
+                    throw new IllegalStateException("web.xml does not contain insertionMarker "+insertionMarker);
+                
+                //put in a context init-param to flag that the contents have been precompiled
+                mergedWebXmlWriter.println("<context-param><param-name>"+PRECOMPILED_FLAG+"</param-name><param-value>true</param-value></context-param>");
+                
+
+                // put in the generated fragment
+                try (BufferedReader fragmentWebXmlReader = new BufferedReader(
+                        new FileReader(fragmentWebXml))) {
+                    IO.copy(fragmentWebXmlReader, mergedWebXmlWriter);
+
+                    // if we inserted just before the </web-app>, put it back in
+                    if (marker.equals(END_OF_WEBAPP))
+                        mergedWebXmlWriter.println(END_OF_WEBAPP);
+
+                    // copy in the rest of the original web.xml file
+                    IO.copy(webXmlReader, mergedWebXmlWriter);
+                }
+            }
+        }
+    }
+
+    private void prepare() throws Exception
+    {
+        // For some reason JspC doesn't like it if the dir doesn't
+        // already exist and refuses to create the web.xml fragment
+        File generatedSourceDirectoryFile = new File(generatedClasses);
+        if (!generatedSourceDirectoryFile.exists())
+            generatedSourceDirectoryFile.mkdirs();
+    }
+
+    /**
+     * Set up the execution classpath for Jasper.
+     * 
+     * Put everything in the classesDirectory and all of the dependencies on the
+     * classpath.
+     * 
+     * @returns a list of the urls of the dependencies
+     * @throws Exception
+     */
+    private List<URL> setUpWebAppClassPath() throws Exception
+    {
+        //add any classes from the webapp
+        List<URL> urls = new ArrayList<URL>();
+        String classesDir = classesDirectory.getCanonicalPath();
+        classesDir = classesDir + (classesDir.endsWith(File.pathSeparator) ? "" : File.separator);
+        urls.add(Resource.toURL(new File(classesDir)));
+
+        if (getLog().isDebugEnabled())
+            getLog().debug("Adding to classpath classes dir: " + classesDir);
+
+        //add the dependencies of the webapp (which will form WEB-INF/lib)
+        for (Iterator<Artifact> iter = project.getArtifacts().iterator(); iter.hasNext();)
+        {
+            Artifact artifact = (Artifact)iter.next();
+
+            // Include runtime and compile time libraries
+            if (!Artifact.SCOPE_TEST.equals(artifact.getScope()) && !Artifact.SCOPE_PROVIDED.equals(artifact.getScope()))
+            {
+                String filePath = artifact.getFile().getCanonicalPath();
+                if (getLog().isDebugEnabled())
+                    getLog().debug("Adding to classpath dependency file: " + filePath);
+
+                urls.add(Resource.toURL(artifact.getFile()));
+            }
+        }
+        return urls;
+    }
+    
+    
+    private String setUpSysClassPath () throws Exception
+    {
+        StringBuffer buff = new StringBuffer();
+        
+        //Put each of the plugin's artifacts onto the system classpath for jspc
+        for (Iterator<Artifact> iter = pluginArtifacts.iterator(); iter.hasNext(); )
+        {
+            Artifact pluginArtifact = iter.next();
+            if ("jar".equalsIgnoreCase(pluginArtifact.getType()))
+            {
+                if (getLog().isDebugEnabled()) { getLog().debug("Adding plugin artifact "+pluginArtifact);}
+                buff.append(pluginArtifact.getFile().getAbsolutePath());
+                if (iter.hasNext())
+                    buff.append(File.pathSeparator);
+            }
+        }
+        
+        
+        if (useProvidedScope)
+        {
+            for ( Iterator<Artifact> iter = projectArtifacts.iterator(); iter.hasNext(); )
+            {                   
+                Artifact artifact = iter.next();
+                if (Artifact.SCOPE_PROVIDED.equals(artifact.getScope()))
+                {
+                    //test to see if the provided artifact was amongst the plugin artifacts
+                    String path = artifact.getFile().getAbsolutePath();
+                    if (! buff.toString().contains(path))
+                    {
+                        if (buff.length() != 0)
+                            buff.append(File.pathSeparator);
+                        buff.append(path);
+                        if (getLog().isDebugEnabled()) { getLog().debug("Adding provided artifact: "+artifact);}
+                    }  
+                    else
+                    {
+                        if (getLog().isDebugEnabled()) { getLog().debug("Skipping provided artifact: "+artifact);}
+                    }
+                }
+            }
+        }
+
+        return buff.toString();
+    }
+
+    
+    /**
+     * Glassfish jsp requires that we set up the list of system jars that have
+     * tlds in them.
+     * 
+     * This method is a little fragile, as it relies on knowing that the jstl jars
+     * are the only ones in the system path that contain tlds.
+     * @return
+     * @throws Exception
+     */
+    private List<URL> getSystemJarsWithTlds() throws Exception
+    {
+        getLog().debug("tld pattern=" + tldJarNamePatterns);   
+        final List<URL> list = new ArrayList<URL>();
+        List<URI> artifactUris = new ArrayList<URI>();
+        Pattern pattern = Pattern.compile(tldJarNamePatterns);
+        for (Iterator<Artifact> iter = pluginArtifacts.iterator(); iter.hasNext(); )
+        {
+            Artifact pluginArtifact = iter.next();
+            Resource res = Resource.newResource(pluginArtifact.getFile());
+            getLog().debug("scan jar: "+res.getURI());
+            artifactUris.add(res.getURI());
+        }
+        
+        PatternMatcher matcher = new PatternMatcher()
+        {
+            public void matched(URI uri) throws Exception
+            {
+                //uri of system artifact matches pattern defining list of jars known to contain tlds
+                list.add(uri.toURL());
+            }
+        };
+        matcher.match(pattern, artifactUris.toArray(new URI[artifactUris.size()]), false);
+        
+        return list;
+    }
+    
+    private File getWebXmlFile ()
+    throws IOException
+    {
+        File file = null;
+        File baseDir = project.getBasedir().getCanonicalFile();
+        File defaultWebAppSrcDir = new File (baseDir, "src/main/webapp").getCanonicalFile();
+        File webAppSrcDir = new File (webAppSourceDirectory).getCanonicalFile();
+        File defaultWebXml = new File (defaultWebAppSrcDir, "web.xml").getCanonicalFile();
+        
+        //If the web.xml has been changed from the default, try that
+        File webXmlFile = new File (webXml).getCanonicalFile();
+        if (webXmlFile.compareTo(defaultWebXml) != 0)
+        {
+            file = new File (webXml);
+            return file;
+        }
+        
+        //If the web app src directory has not been changed from the default, use whatever
+        //is set for the web.xml location
+        file = new File (webAppSrcDir, "web.xml");
+        return file;
+    }
+}
diff --git a/jetty-jspc-maven-plugin/src/main/java/org/eclipse/jetty/jspc/plugin/package-info.java b/jetty-jspc-maven-plugin/src/main/java/org/eclipse/jetty/jspc/plugin/package-info.java
new file mode 100644
index 0000000..394bcb3
--- /dev/null
+++ b/jetty-jspc-maven-plugin/src/main/java/org/eclipse/jetty/jspc/plugin/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Jspc Maven Plugin : Support for precompiling jsps
+ */
+package org.eclipse.jetty.jspc.plugin;
+
diff --git a/jetty-jspc-maven-plugin/src/main/resources/META-INF/m2e/lifecycle-mapping-metadata.xml b/jetty-jspc-maven-plugin/src/main/resources/META-INF/m2e/lifecycle-mapping-metadata.xml
new file mode 100644
index 0000000..dd49e82
--- /dev/null
+++ b/jetty-jspc-maven-plugin/src/main/resources/META-INF/m2e/lifecycle-mapping-metadata.xml
@@ -0,0 +1,30 @@
+<!-- ========================================================================   -->
+<!--    Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.                   -->
+<!--                                                                            -->
+<!--    All rights reserved. This program and the accompanying materials        -->
+<!--    are made available under the terms of the Eclipse Public License v1.0   -->
+<!--    and Apache License v2.0 which accompanies this distribution.            --> 
+<!--                                                                            -->
+<!--        The Eclipse Public License is available at                          -->
+<!--        http://www.eclipse.org/legal/epl-v10.html                           -->
+<!--                                                                            -->
+<!--        The Apache License v2.0 is available at                             -->
+<!--        http://www.opensource.org/licenses/apache2.0.php                    -->
+<!--                                                                            -->
+<!--    You may elect to redistribute this code under either of these licenses. -->
+<!-- ========================================================================   -->
+
+<lifecycleMappingMetadata>
+  <pluginExecutions>
+    <pluginExecution>
+      <pluginExecutionFilter>
+        <goals>
+          <goal>jspc</goal>
+        </goals>
+      </pluginExecutionFilter>
+      <action>
+        <ignore />
+      </action>
+    </pluginExecution>
+  </pluginExecutions>
+</lifecycleMappingMetadata>
diff --git a/jetty-maven-plugin/.gitignore b/jetty-maven-plugin/.gitignore
new file mode 100644
index 0000000..929d903
--- /dev/null
+++ b/jetty-maven-plugin/.gitignore
@@ -0,0 +1,5 @@
+.classpath
+.project
+.settings
+target
+*.swp
diff --git a/jetty-maven-plugin/pom.xml b/jetty-maven-plugin/pom.xml
new file mode 100644
index 0000000..c1c3b70
--- /dev/null
+++ b/jetty-maven-plugin/pom.xml
@@ -0,0 +1,168 @@
+<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">
+  <parent>
+    <groupId>org.eclipse.jetty</groupId>
+    <artifactId>jetty-project</artifactId>
+    <version>9.2.13.v20150730</version>
+  </parent>
+  <modelVersion>4.0.0</modelVersion>
+  <artifactId>jetty-maven-plugin</artifactId>
+  <packaging>maven-plugin</packaging>
+  <name>Jetty :: Jetty Maven Plugin</name>
+  <properties>
+    <mavenVersion>3.0.3</mavenVersion>
+    <pluginToolsVersion>3.1</pluginToolsVersion>
+  </properties>
+  <build>
+    <plugins>
+      <plugin>
+        <artifactId>maven-surefire-plugin</artifactId>
+        <configuration>
+          <skip>true</skip>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-plugin-plugin</artifactId>
+        <version>${pluginToolsVersion}</version>
+        <executions>
+          <execution>
+            <id>exec-plugin-doc</id>
+            <phase>generate-sources</phase>
+            <goals>
+              <goal>xdoc</goal>
+              <goal>helpmojo</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.maven</groupId>
+      <artifactId>maven-plugin-api</artifactId>
+      <version>${mavenVersion}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.maven</groupId>
+      <artifactId>maven-artifact</artifactId>
+      <version>${mavenVersion}</version>
+    </dependency>
+     <dependency>
+      <groupId>org.apache.maven</groupId>
+      <artifactId>maven-core</artifactId>
+      <version>${mavenVersion}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.maven.plugin-tools</groupId>
+      <artifactId>maven-plugin-tools-api</artifactId>
+      <version>${pluginToolsVersion}</version>
+      <exclusions>
+        <exclusion>
+          <groupId>junit</groupId>
+          <artifactId>junit</artifactId>
+        </exclusion>
+      </exclusions>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-util</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-webapp</artifactId>
+      <version>${project.version}</version>
+      <exclusions>
+        <exclusion>
+          <groupId>javax.servlet</groupId>
+          <artifactId>servlet-api</artifactId>
+        </exclusion>
+      </exclusions>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-quickstart</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-jaas</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-plus</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-jndi</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-server</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-jmx</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-annotations</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.websocket</groupId>
+      <artifactId>javax-websocket-server-impl</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.websocket</groupId>
+      <artifactId>websocket-server</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>apache-jsp</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>apache-jstl</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+     <dependency>
+       <groupId>javax.transaction</groupId>
+       <artifactId>javax.transaction-api</artifactId>
+       <scope>compile</scope>
+     </dependency>
+  </dependencies>
+  <reporting>
+  <plugins>
+    <plugin>
+      <groupId>org.apache.maven.plugins</groupId>
+      <artifactId>maven-project-info-reports-plugin</artifactId>
+      <version>2.1</version>
+      <configuration>
+        <dependencyLocationEnabled>false</dependencyLocationEnabled>
+       </configuration>
+         <reportSets>
+          <reportSet>
+            <reports>
+              <report>project-team</report>
+              <report>mailing-list</report>
+              <report>cim</report>
+              <report>issue-tracking</report>
+              <report>license</report>
+              <report>scm</report>
+            </reports>
+          </reportSet>
+        </reportSets>
+    </plugin>
+  </plugins>
+  </reporting>
+</project>
diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/AbstractJettyMojo.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/AbstractJettyMojo.java
new file mode 100644
index 0000000..3e373a1
--- /dev/null
+++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/AbstractJettyMojo.java
@@ -0,0 +1,842 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.maven.plugin;
+
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Properties;
+import java.util.Set;
+
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.plugin.AbstractMojo;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.MojoFailureException;
+import org.apache.maven.project.MavenProject;
+import org.codehaus.plexus.util.FileUtils;
+import org.eclipse.jetty.security.LoginService;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.RequestLog;
+import org.eclipse.jetty.server.ShutdownMonitor;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.ContextHandlerCollection;
+import org.eclipse.jetty.server.handler.HandlerCollection;
+import org.eclipse.jetty.util.Scanner;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.xml.XmlConfiguration;
+
+
+
+/**
+ * AbstractJettyMojo
+ *
+ * Common base class for most jetty mojos.
+ * 
+ * 
+ */
+public abstract class AbstractJettyMojo extends AbstractMojo
+{
+    /**
+     * 
+     */
+    public String PORT_SYSPROPERTY = "jetty.port";
+    
+    
+    /**
+     * Whether or not to include dependencies on the plugin's classpath with <scope>provided</scope>
+     * Use WITH CAUTION as you may wind up with duplicate jars/classes.
+     * 
+     * @since jetty-7.5.2
+     * @parameter  default-value="false"
+     */
+    protected boolean useProvidedScope;
+    
+    
+    /**
+     * List of goals that are NOT to be used
+     * 
+     * @since jetty-7.5.2
+     * @parameter
+     */
+    protected String[] excludedGoals;
+    
+
+  
+    
+    /**
+     * List of other contexts to set up. Consider using instead
+     * the <jettyXml> element to specify external jetty xml config file. 
+     * Optional.
+     * 
+     * 
+     * @parameter
+     */
+    protected ContextHandler[] contextHandlers;
+    
+    
+    /**
+     * List of security realms to set up. Consider using instead
+     * the <jettyXml> element to specify external jetty xml config file. 
+     * Optional.
+     * 
+     * 
+     * @parameter
+     */
+    protected LoginService[] loginServices;
+    
+
+    /**
+     * A RequestLog implementation to use for the webapp at runtime.
+     * Consider using instead the <jettyXml> element to specify external jetty xml config file. 
+     * Optional.
+     * 
+     *
+     * @parameter
+     */
+    protected RequestLog requestLog;
+    
+    
+    /**
+     * An instance of org.eclipse.jetty.webapp.WebAppContext that represents the webapp.
+     * Use any of its setters to configure the webapp. This is the preferred and most
+     * flexible method of configuration, rather than using the (deprecated) individual
+     * parameters like "tmpDirectory", "contextPath" etc.
+     * 
+     * @parameter alias="webAppConfig"
+     */
+    protected JettyWebAppContext webApp;
+
+
+    /**
+     * The interval in seconds to scan the webapp for changes 
+     * and restart the context if necessary. Ignored if reload
+     * is enabled. Disabled by default.
+     * 
+     * @parameter expression="${jetty.scanIntervalSeconds}" default-value="0"
+     * @required
+     */
+    protected int scanIntervalSeconds;
+    
+    
+    /**
+     * reload can be set to either 'automatic' or 'manual'
+     *
+     * if 'manual' then the context can be reloaded by a linefeed in the console
+     * if 'automatic' then traditional reloading on changed files is enabled.
+     * 
+     * @parameter expression="${jetty.reload}" default-value="automatic"
+     */
+    protected String reload;
+
+    
+    /**
+     * File containing system properties to be set before execution
+     * 
+     * Note that these properties will NOT override System properties
+     * that have been set on the command line, by the JVM, or directly 
+     * in the POM via systemProperties. Optional.
+     * 
+     * @parameter expression="${jetty.systemPropertiesFile}"
+     */
+    protected File systemPropertiesFile;
+
+    
+    /**
+     * System properties to set before execution. 
+     * Note that these properties will NOT override System properties 
+     * that have been set on the command line or by the JVM. They WILL 
+     * override System properties that have been set via systemPropertiesFile.
+     * Optional.
+     * @parameter
+     */
+    protected SystemProperties systemProperties;
+    
+    
+    /**
+     * Comma separated list of a jetty xml configuration files whose contents 
+     * will be applied before any plugin configuration. Optional.
+     * 
+     * 
+     * @parameter alias="jettyConfig"
+     */
+    protected String jettyXml;
+    
+    
+    /**
+     * Port to listen to stop jetty on executing -DSTOP.PORT=<stopPort> 
+     * -DSTOP.KEY=<stopKey> -jar start.jar --stop
+     * 
+     * @parameter
+     */
+    protected int stopPort;
+    
+    
+    /**
+     * Key to provide when stopping jetty on executing java -DSTOP.KEY=<stopKey> 
+     * -DSTOP.PORT=<stopPort> -jar start.jar --stop
+     * 
+     * @parameter
+     */
+    protected String stopKey;
+
+    /**
+     * Use the dump() facility of jetty to print out the server configuration to logging
+     * 
+     * @parameter expression"${dumponStart}" default-value="false"
+     */
+    protected boolean dumpOnStart;
+    
+   
+    
+    
+    /**  
+     * Skip this mojo execution.
+     * 
+     * @parameter expression="${jetty.skip}" default-value="false"
+     */
+    protected boolean skip;
+
+    
+    /**
+     * Location of a context xml configuration file whose contents
+     * will be applied to the webapp AFTER anything in <webApp>.Optional.
+     * 
+     * 
+     * @parameter alias="webAppXml"
+     */
+    protected String contextXml;
+
+
+    /**
+     * The maven project.
+     *
+     * @parameter expression="${project}"
+     * @readonly
+     */
+    protected MavenProject project;
+
+    
+    /**
+     * The artifacts for the project.
+     * 
+     * @parameter expression="${project.artifacts}"
+     * @readonly
+     */
+    protected Set projectArtifacts;
+    
+    
+    /** 
+     * @parameter expression="${mojoExecution}" 
+     * @readonly
+     */
+    protected org.apache.maven.plugin.MojoExecution execution;
+    
+
+    /**
+     * The artifacts for the plugin itself.
+     * 
+     * @parameter expression="${plugin.artifacts}"
+     * @readonly
+     */
+    protected List pluginArtifacts;
+    
+    
+
+    /**
+     * A ServerConnector to use.
+     * 
+     * @parameter
+     */
+    protected MavenServerConnector httpConnector;
+    
+    
+    /**
+     * A wrapper for the Server object
+     */
+    protected JettyServer server = new JettyServer();
+    
+    
+    /**
+     * A scanner to check for changes to the webapp
+     */
+    protected Scanner scanner;
+    
+    
+    /**
+     *  List of files and directories to scan
+     */
+    protected ArrayList<File> scanList;
+    
+    
+    /**
+     * List of Listeners for the scanner
+     */
+    protected ArrayList<Scanner.BulkListener> scannerListeners;
+    
+    
+    /**
+     * A scanner to check ENTER hits on the console
+     */
+    protected Thread consoleScanner;
+    
+    
+    /**
+     * <p>
+     * Determines whether or not the server blocks when started. The default
+     * behavior (false) will cause the server to pause other processes
+     * while it continues to handle web requests. This is useful when starting the
+     * server with the intent to work with it interactively. This is the 
+     * behaviour of the jetty:run, jetty:run-war, jetty:run-war-exploded goals. 
+     * </p><p>
+     * If true, the server will not block the execution of subsequent code. This
+     * is the behaviour of the jetty:start and default behaviour of the jetty:deploy goals.
+     * </p>
+     */
+    protected boolean nonblocking = false;
+      
+    
+    public abstract void restartWebApp(boolean reconfigureScanner) throws Exception;
+
+    
+    public abstract void checkPomConfiguration() throws MojoExecutionException;    
+    
+    
+    public abstract void configureScanner () throws MojoExecutionException;
+    
+
+    
+
+
+    /** 
+     * @see org.apache.maven.plugin.Mojo#execute()
+     */
+    public void execute() throws MojoExecutionException, MojoFailureException
+    {
+        getLog().info("Configuring Jetty for project: " + this.project.getName());
+        if (skip)
+        {
+            getLog().info("Skipping Jetty start: jetty.skip==true");
+            return;
+        }
+
+        if (isExcluded(execution.getMojoDescriptor().getGoal()))
+        {
+            getLog().info("The goal \""+execution.getMojoDescriptor().getFullGoalName()+
+                          "\" has been made unavailable for this web application by an <excludedGoal> configuration.");
+            return;
+        }
+        
+        configurePluginClasspath();
+        PluginLog.setLog(getLog());
+        checkPomConfiguration();
+        startJetty();
+    }
+    
+    
+    
+    
+    /**
+     * @throws MojoExecutionException
+     */
+    public void configurePluginClasspath() throws MojoExecutionException
+    {  
+        //if we are configured to include the provided dependencies on the plugin's classpath
+        //(which mimics being on jetty's classpath vs being on the webapp's classpath), we first
+        //try and filter out ones that will clash with jars that are plugin dependencies, then
+        //create a new classloader that we setup in the parent chain.
+        if (useProvidedScope)
+        {
+            try
+            {
+                List<URL> provided = new ArrayList<URL>();
+                URL[] urls = null;
+               
+                for ( Iterator<Artifact> iter = projectArtifacts.iterator(); iter.hasNext(); )
+                {                   
+                    Artifact artifact = iter.next();
+                    if (Artifact.SCOPE_PROVIDED.equals(artifact.getScope()) && !isPluginArtifact(artifact))
+                    {
+                        provided.add(artifact.getFile().toURI().toURL());
+                        if (getLog().isDebugEnabled()) { getLog().debug("Adding provided artifact: "+artifact);}
+                    }
+                }
+
+                if (!provided.isEmpty())
+                {
+                    urls = new URL[provided.size()];
+                    provided.toArray(urls);
+                    URLClassLoader loader  = new URLClassLoader(urls, getClass().getClassLoader());
+                    Thread.currentThread().setContextClassLoader(loader);
+                    getLog().info("Plugin classpath augmented with <scope>provided</scope> dependencies: "+Arrays.toString(urls));
+                }
+            }
+            catch (MalformedURLException e)
+            {
+                throw new MojoExecutionException("Invalid url", e);
+            }
+        }
+    }
+    
+    
+    
+    
+    /**
+     * @param artifact
+     * @return
+     */
+    public boolean isPluginArtifact(Artifact artifact)
+    {
+        if (pluginArtifacts == null || pluginArtifacts.isEmpty())
+            return false;
+        
+        boolean isPluginArtifact = false;
+        for (Iterator<Artifact> iter = pluginArtifacts.iterator(); iter.hasNext() && !isPluginArtifact; )
+        {
+            Artifact pluginArtifact = iter.next();
+            if (getLog().isDebugEnabled()) { getLog().debug("Checking "+pluginArtifact);}
+            if (pluginArtifact.getGroupId().equals(artifact.getGroupId()) && pluginArtifact.getArtifactId().equals(artifact.getArtifactId()))
+                isPluginArtifact = true;
+        }
+        
+        return isPluginArtifact;
+    }
+
+    
+    
+    
+    /**
+     * @throws Exception
+     */
+    public void finishConfigurationBeforeStart() throws Exception
+    {
+        HandlerCollection contexts = (HandlerCollection)server.getChildHandlerByClass(ContextHandlerCollection.class);
+        if (contexts==null)
+            contexts = (HandlerCollection)server.getChildHandlerByClass(HandlerCollection.class);
+        
+        for (int i=0; (this.contextHandlers != null) && (i < this.contextHandlers.length); i++)
+        {
+            contexts.addHandler(this.contextHandlers[i]);
+        }
+    }
+
+   
+   
+
+    /**
+     * @throws Exception
+     */
+    public void applyJettyXml() throws Exception
+    {
+        if (getJettyXmlFiles() == null)
+            return;
+
+        this.server.applyXmlConfigurations(getJettyXmlFiles());
+    }
+
+
+
+    
+    /**
+     * @throws MojoExecutionException
+     */
+    public void startJetty () throws MojoExecutionException
+    {
+        try
+        {
+            getLog().debug("Starting Jetty Server ...");
+         
+            configureMonitor();
+            
+            printSystemProperties();
+            
+            //apply any config from a jetty.xml file first which is able to
+            //be overwritten by config in the pom.xml
+            applyJettyXml ();      
+
+            // if a <httpConnector> was specified in the pom, use it
+            if (httpConnector != null)
+            {
+                // check that its port was set
+                if (httpConnector.getPort() <= 0)
+                {
+                    //use any jetty.port settings provided
+                    String tmp = System.getProperty(PORT_SYSPROPERTY, MavenServerConnector.DEFAULT_PORT_STR); 
+                    httpConnector.setPort(Integer.parseInt(tmp.trim()));
+                }  
+                if (httpConnector.getServer() == null)
+                    httpConnector.setServer(this.server);
+                this.server.addConnector(httpConnector);
+            }
+
+            // if the user hasn't configured the connectors in a jetty.xml file so use a default one
+            Connector[] connectors = this.server.getConnectors();
+            if (connectors == null|| connectors.length == 0)
+            {
+                //if <httpConnector> not configured in the pom, create one
+                if (httpConnector == null)
+                {
+                    httpConnector = new MavenServerConnector();               
+                    //use any jetty.port settings provided
+                    String tmp = System.getProperty(PORT_SYSPROPERTY, MavenServerConnector.DEFAULT_PORT_STR);
+                    httpConnector.setPort(Integer.parseInt(tmp.trim()));
+                }
+                if (httpConnector.getServer() == null)
+                    httpConnector.setServer(this.server);
+                this.server.setConnectors(new Connector[] {httpConnector});
+            }
+
+            //set up a RequestLog if one is provided
+            if (this.requestLog != null)
+                this.server.setRequestLog(this.requestLog);
+
+            //set up the webapp and any context provided
+            this.server.configureHandlers();
+            configureWebApplication();
+            this.server.addWebApplication(webApp);
+
+            // set up security realms
+            for (int i = 0; (this.loginServices != null) && i < this.loginServices.length; i++)
+            {
+                getLog().debug(this.loginServices[i].getClass().getName() + ": "+ this.loginServices[i].toString());
+                this.server.addBean(this.loginServices[i]);
+            }
+
+            //do any other configuration required by the
+            //particular Jetty version
+            finishConfigurationBeforeStart();
+
+            // start Jetty
+            this.server.start();
+
+            getLog().info("Started Jetty Server");
+           
+            
+            if ( dumpOnStart )
+            {
+                getLog().info(this.server.dump());
+            }
+            
+            // start the scanner thread (if necessary) on the main webapp
+            configureScanner ();
+            startScanner();
+            
+            // start the new line scanner thread if necessary
+            startConsoleScanner();
+
+            // keep the thread going if not in daemon mode
+            if (!nonblocking )
+            {
+                server.join();
+            }
+        }
+        catch (Exception e)
+        {
+            throw new MojoExecutionException("Failure", e);
+        }
+        finally
+        {
+            if (!nonblocking )
+            {
+                getLog().info("Jetty server exiting.");
+            }            
+        }        
+    }
+    
+    
+    public void configureMonitor()
+    { 
+        if(stopPort>0 && stopKey!=null)
+        {
+            ShutdownMonitor monitor = ShutdownMonitor.getInstance();
+            monitor.setPort(stopPort);
+            monitor.setKey(stopKey);
+            monitor.setExitVm(!nonblocking);
+        }
+    }
+
+    
+    /**
+     * Subclasses should invoke this to setup basic info
+     * on the webapp
+     * 
+     * @throws MojoExecutionException
+     */
+    public void configureWebApplication () throws Exception
+    {
+        //As of jetty-7, you must use a <webApp> element
+        if (webApp == null)
+            webApp = new JettyWebAppContext();
+        
+        //Apply any context xml file to set up the webapp
+        //CAUTION: if you've defined a <webApp> element then the
+        //context xml file can OVERRIDE those settings
+        if (contextXml != null)
+        {
+            File file = FileUtils.getFile(contextXml);
+            XmlConfiguration xmlConfiguration = new XmlConfiguration(Resource.toURL(file));
+            getLog().info("Applying context xml file "+contextXml);
+            xmlConfiguration.configure(webApp);   
+        }
+        
+        //If no contextPath was specified, go with default of project artifactid
+        String cp = webApp.getContextPath();
+        if (cp == null || "".equals(cp))
+        {
+            cp = "/"+project.getArtifactId();
+            webApp.setContextPath(cp);
+        }        
+
+        //If no tmp directory was specified, and we have one, use it
+        if (webApp.getTempDirectory() == null)
+        {
+            File target = new File(project.getBuild().getDirectory());
+            File tmp = new File(target,"tmp");
+            if (!tmp.exists())
+                tmp.mkdirs();            
+            webApp.setTempDirectory(tmp);
+        }
+      
+        getLog().info("Context path = " + webApp.getContextPath());
+        getLog().info("Tmp directory = "+ (webApp.getTempDirectory()== null? " determined at runtime": webApp.getTempDirectory()));
+        getLog().info("Web defaults = "+(webApp.getDefaultsDescriptor()==null?" jetty default":webApp.getDefaultsDescriptor()));
+        getLog().info("Web overrides = "+(webApp.getOverrideDescriptor()==null?" none":webApp.getOverrideDescriptor()));
+    }
+
+
+
+    
+    /**
+     * Run a scanner thread on the given list of files and directories, calling
+     * stop/start on the given list of LifeCycle objects if any of the watched
+     * files change.
+     *
+     */
+    private void startScanner() throws Exception
+    {
+        // check if scanning is enabled
+        if (scanIntervalSeconds <= 0) return;
+
+        // check if reload is manual. It disables file scanning
+        if ( "manual".equalsIgnoreCase( reload ) )
+        {
+            // issue a warning if both scanIntervalSeconds and reload
+            // are enabled
+            getLog().warn("scanIntervalSeconds is set to " + scanIntervalSeconds + " but will be IGNORED due to manual reloading");
+            return;
+        }
+
+        scanner = new Scanner();
+        scanner.setReportExistingFilesOnStartup(false);
+        scanner.setScanInterval(scanIntervalSeconds);
+        scanner.setScanDirs(scanList);
+        scanner.setRecursive(true);
+        Iterator itor = (this.scannerListeners==null?null:this.scannerListeners.iterator());
+        while (itor!=null && itor.hasNext())
+            scanner.addListener((Scanner.Listener)itor.next());
+        getLog().info("Starting scanner at interval of " + scanIntervalSeconds + " seconds.");
+        scanner.start();
+    }
+    
+    
+    
+    
+    /**
+     * Run a thread that monitors the console input to detect ENTER hits.
+     */
+    protected void startConsoleScanner() throws Exception
+    {
+        if ( "manual".equalsIgnoreCase( reload ) )
+        {
+            getLog().info("Console reloading is ENABLED. Hit ENTER on the console to restart the context.");
+            consoleScanner = new ConsoleScanner(this);
+            consoleScanner.start();
+        }       
+    }
+
+    
+    
+    
+    /**
+     * 
+     */
+    protected void printSystemProperties ()
+    {
+        // print out which system properties were set up
+        if (getLog().isDebugEnabled())
+        {
+            if (systemProperties != null)
+            {
+                Iterator itor = systemProperties.getSystemProperties().iterator();
+                while (itor.hasNext())
+                {
+                    SystemProperty prop = (SystemProperty)itor.next();
+                    getLog().debug("Property "+prop.getName()+"="+prop.getValue()+" was "+ (prop.isSet() ? "set" : "skipped"));
+                }
+            }
+        }
+    }
+
+    
+    
+    
+    /**
+     * Try and find a jetty-web.xml file, using some
+     * historical naming conventions if necessary.
+     * @param webInfDir
+     * @return the jetty web xml file
+     */
+    public File findJettyWebXmlFile (File webInfDir)
+    {
+        if (webInfDir == null)
+            return null;
+        if (!webInfDir.exists())
+            return null;
+
+        File f = new File (webInfDir, "jetty-web.xml");
+        if (f.exists())
+            return f;
+
+        //try some historical alternatives
+        f = new File (webInfDir, "web-jetty.xml");
+        if (f.exists())
+            return f;
+        
+        return null;
+    }
+
+
+   
+    
+    /**
+     * @param file
+     * @throws Exception
+     */
+    public void setSystemPropertiesFile(File file) throws Exception
+    {
+        this.systemPropertiesFile = file;
+        Properties properties = new Properties();
+        try (InputStream propFile = new FileInputStream(systemPropertiesFile))
+        {
+            properties.load(propFile);
+        }
+        if (this.systemProperties == null )
+            this.systemProperties = new SystemProperties();
+        
+        for (Enumeration<?> keys = properties.keys(); keys.hasMoreElements();  )
+        {
+            String key = (String)keys.nextElement();
+            if ( ! systemProperties.containsSystemProperty(key) )
+            {
+                SystemProperty prop = new SystemProperty();
+                prop.setKey(key);
+                prop.setValue(properties.getProperty(key));
+                
+                this.systemProperties.setSystemProperty(prop);
+            }
+        } 
+    }
+    
+    
+    
+    
+    /**
+     * @param systemProperties
+     */
+    public void setSystemProperties(SystemProperties systemProperties)
+    {
+        if (this.systemProperties == null)
+            this.systemProperties = systemProperties;
+        else
+        {
+            for (SystemProperty prop: systemProperties.getSystemProperties())
+            {
+                this.systemProperties.setSystemProperty(prop);
+            }   
+        }
+    }
+    
+
+    
+
+    
+    
+    
+    /**
+     * @return
+     */
+    public List<File> getJettyXmlFiles()
+    {
+        if ( this.jettyXml == null )
+        {
+            return null;
+        }
+        
+        List<File> jettyXmlFiles = new ArrayList<File>();
+        
+        if ( this.jettyXml.indexOf(',') == -1 )
+        {
+            jettyXmlFiles.add( new File( this.jettyXml ) );
+        }
+        else
+        {
+            String[] files = this.jettyXml.split(",");
+            
+            for ( String file : files )
+            {
+                jettyXmlFiles.add( new File(file) );
+            }
+        }
+        
+        return jettyXmlFiles;
+    }
+
+    
+    
+    /**
+     * @param goal
+     * @return
+     */
+    public boolean isExcluded (String goal)
+    {
+        if (excludedGoals == null || goal == null)
+            return false;
+        
+        goal = goal.trim();
+        if ("".equals(goal))
+            return false;
+        
+        boolean excluded = false;
+        for (int i=0; i<excludedGoals.length && !excluded; i++)
+        {
+            if (excludedGoals[i].equalsIgnoreCase(goal))
+                excluded = true;
+        }
+        
+        return excluded;
+    }
+}
diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/ConsoleScanner.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/ConsoleScanner.java
new file mode 100644
index 0000000..65a9dc5
--- /dev/null
+++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/ConsoleScanner.java
@@ -0,0 +1,161 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.maven.plugin;
+
+import java.io.IOException;
+
+
+
+
+/**
+ * ConsoleScanner
+ *
+ * Read input from stdin
+ */
+public class ConsoleScanner extends Thread 
+{
+    
+    private final AbstractJettyMojo mojo;
+    
+    
+    
+    
+    
+    /**
+     * @param mojo
+     */
+    public ConsoleScanner(AbstractJettyMojo mojo) 
+    {
+        this.mojo = mojo;
+        setName("Console scanner");
+        setDaemon(true);
+    }
+    
+    
+    
+    
+    /** 
+     * @see java.lang.Thread#run()
+     */
+    public void run() 
+    {  
+        try 
+        {
+            while (true) 
+            {
+                checkSystemInput();
+                getSomeSleep();
+            }
+        } 
+        catch (IOException e) 
+        {
+            mojo.getLog().warn(e);
+        }
+    }
+    
+    
+    
+    
+    /**
+     * 
+     */
+    private void getSomeSleep() 
+    {
+        try 
+        {
+            Thread.sleep(500);
+        } 
+        catch (InterruptedException e) 
+        {
+            mojo.getLog().debug(e);
+        }
+    }
+    
+    
+    
+    
+    /**
+     * @throws IOException
+     */
+    private void checkSystemInput() throws IOException 
+    {     
+        while (System.in.available() > 0) {
+            int inputByte = System.in.read();
+            if (inputByte >= 0) 
+            {
+                char c = (char)inputByte;
+                if (c == '\n') {
+                    restartWebApp();
+                }
+            }
+        }
+    }
+    
+    
+    
+    
+    /**
+     * Skip buffered bytes of system console.
+     */
+    private void clearInputBuffer() 
+    {
+        try
+        {
+            while (System.in.available() > 0)
+            {
+                // System.in.skip doesn't work properly. I don't know why
+                long available = System.in.available();
+                for (int i = 0; i < available; i++)
+                {
+                    if (System.in.read() == -1)
+                    {
+                        break;
+                    }
+                }
+            }
+        }
+        catch (IOException e)
+        {
+            mojo.getLog().warn("Error discarding console input buffer", e);
+        }      
+    }
+    
+    
+    
+    
+    /**
+     * 
+     */
+    private void restartWebApp()
+    {
+        try
+        {
+            mojo.restartWebApp(false);
+            // Clear input buffer to discard anything entered on the console
+            // while the application was being restarted.
+            clearInputBuffer();
+        }
+        catch (Exception e)
+        {
+            mojo.getLog().error(
+                            "Error reconfiguring/restarting webapp after a new line on the console",
+                            e);
+        }
+    }
+}
diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyDeployWar.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyDeployWar.java
new file mode 100644
index 0000000..48d7762
--- /dev/null
+++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyDeployWar.java
@@ -0,0 +1,81 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.maven.plugin;
+
+import java.io.File;
+
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.MojoFailureException;
+
+/**
+ * <p>
+ * This goal is used to run Jetty with a pre-assembled war.
+ * </p>
+ * <p>
+ * It accepts exactly the same options as the <a href="run-war-mojo.html">run-war</a> goal. 
+ * However, it doesn't assume that the current artifact is a
+ * webapp and doesn't try to assemble it into a war before its execution. 
+ * So using it makes sense only when used in conjunction with the 
+ * <a href="run-war-mojo.html#webApp">war</a> configuration parameter pointing to a pre-built WAR.
+ * </p>
+ * <p>
+ * This goal is useful e.g. for launching a web app in Jetty as a target for unit-tested 
+ * HTTP client components.
+ * </p>
+ * 
+ * @goal deploy-war
+ * @requiresDependencyResolution runtime
+ * @execute phase="validate"
+ * @description Deploy a pre-assembled war
+ * 
+ */
+public class JettyDeployWar extends JettyRunWarMojo
+{
+
+    
+    /**
+     * If true, the plugin should continue and not block. Otherwise the
+     * plugin will block further execution and you will need to use
+     * cntrl-c to stop it.
+     * 
+     * 
+     * @parameter  default-value="true"
+     */
+    protected boolean daemon = true;
+    
+    
+    @Override
+    public void execute() throws MojoExecutionException, MojoFailureException
+    {
+        nonblocking = daemon; 
+        super.execute();
+    }
+    
+
+
+    @Override
+    public void finishConfigurationBeforeStart() throws Exception
+    {
+        super.finishConfigurationBeforeStart();
+        //only stop the server at shutdown if we are blocking
+        server.setStopAtShutdown(!nonblocking);
+       
+    }
+
+}
diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyEffectiveWebXml.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyEffectiveWebXml.java
new file mode 100644
index 0000000..b837722
--- /dev/null
+++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyEffectiveWebXml.java
@@ -0,0 +1,156 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.maven.plugin;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.MojoFailureException;
+import org.eclipse.jetty.annotations.AnnotationConfiguration;
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+
+/**
+ * JettyEffectiveWebXml
+ *
+ * @goal effective-web-xml
+ * @requiresDependencyResolution test
+ * @execute phase="test-compile"
+ * @description Runs jetty on the unassembled webapp to generate the effective web.xml
+ */
+public class JettyEffectiveWebXml extends JettyRunMojo
+{
+    /**
+     * The target directory
+     * 
+     * @parameter expression="${project.build.directory}"
+     * @required
+     * @readonly
+     */
+    protected File target;
+    
+    /**
+     * The target directory
+     * 
+     * @parameter 
+     */
+    protected File effectiveWebXml;
+    
+    
+    protected boolean deleteOnExit = true;
+    
+
+    /**
+     * @see org.apache.maven.plugin.Mojo#execute()
+     */
+    public void execute() throws MojoExecutionException, MojoFailureException
+    {
+        super.execute();
+    }
+    
+    
+    @Override
+    public void startJetty() throws MojoExecutionException
+    {
+        //Only do enough setup to be able to produce a quickstart-web.xml file 
+        
+        //if the user didn't nominate a file to generate into, pick the name and
+        //make sure that it is deleted on exit
+        if (effectiveWebXml == null)
+        {
+            deleteOnExit = true;
+            effectiveWebXml = new File(target, "effective-web.xml");
+            effectiveWebXml.deleteOnExit();
+        }
+        
+        Resource descriptor = Resource.newResource(effectiveWebXml);
+        
+        QueuedThreadPool tpool = null;
+        
+        try
+        {
+            printSystemProperties();
+
+            //apply any config from a jetty.xml file first to our "fake" server instance
+            //TODO probably not necessary
+            applyJettyXml ();  
+
+        
+            server.configureHandlers();
+                   
+            //ensure config of the webapp based on settings in plugin
+            configureWebApplication();
+            
+            
+            //set the webapp up to do very little other than generate the quickstart-web.xml
+            webApp.setCopyWebDir(false);
+            webApp.setCopyWebInf(false);
+            webApp.setGenerateQuickStart(true);
+    
+            if (!effectiveWebXml.getParentFile().exists())
+                effectiveWebXml.getParentFile().mkdirs();
+            if (!effectiveWebXml.exists())
+                effectiveWebXml.createNewFile();
+            
+            webApp.setQuickStartWebDescriptor(descriptor);
+            
+            server.addWebApplication(webApp);
+                       
+            //if our server has a thread pool associated we can do any annotation scanning multithreaded,
+            //otherwise scanning will be single threaded
+            tpool = server.getBean(QueuedThreadPool.class);
+            if (tpool != null)
+                tpool.start();
+            else
+                webApp.setAttribute(AnnotationConfiguration.MULTI_THREADED, Boolean.FALSE.toString());
+            
+             webApp.start(); //just enough to generate the quickstart           
+           
+        }
+        catch (Exception e)
+        {
+            throw new MojoExecutionException("Effective web.xml generation failed", e);
+        }
+        finally
+        {
+            try {webApp.stop();}catch (Exception x) {};
+            
+            try {if (tpool != null) tpool.stop();} catch (Exception x) {};
+        }
+         
+       
+        if (deleteOnExit)
+        {
+            try
+            {
+                //just show the result in the log
+                getLog().info(IO.toString(descriptor.getInputStream()));
+            }
+            catch (IOException e)
+            {
+               throw new MojoExecutionException("Unable to output effective web.xml", e);
+            }
+            
+        }
+        
+    }
+}
diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyRunForkedMojo.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyRunForkedMojo.java
new file mode 100644
index 0000000..b1d267e
--- /dev/null
+++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyRunForkedMojo.java
@@ -0,0 +1,853 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.maven.plugin;
+
+import java.io.BufferedOutputStream;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.LineNumberReader;
+import java.io.OutputStream;
+import java.net.MalformedURLException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Random;
+import java.util.Set;
+
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.MojoFailureException;
+import org.apache.maven.plugin.descriptor.PluginDescriptor;
+import org.eclipse.jetty.annotations.AnnotationConfiguration;
+import org.eclipse.jetty.quickstart.QuickStartDescriptorGenerator;
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.util.resource.ResourceCollection;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+
+
+/**
+ * <p>
+ *  This goal is used to deploy your unassembled webapp into a forked JVM.
+ *  </p>
+ *  <p>
+ *  You need to define a jetty.xml file to configure connectors etc. You can use the normal setters of o.e.j.webapp.WebAppContext on the <b>webApp</b>
+ *  configuration element for this plugin. You may also need context xml file for any particularly complex webapp setup.
+ *  about your webapp.
+ *  </p>
+ *  <p>
+ *  Unlike the other jetty goals, this does NOT support the <b>scanIntervalSeconds</b> parameter: the webapp will be deployed only once.
+ *  </p>
+ *  <p>
+ *  The <b>stopKey</b>, <b>stopPort</b> configuration elements can be used to control the stopping of the forked process. By default, this plugin will launch
+ *  the forked jetty instance and wait for it to complete (in which case it acts much like the <b>jetty:run</b> goal, and you will need to Cntrl-C to stop).
+ *  By setting the configuration element <b>waitForChild</b> to <b>false</b>, the plugin will terminate after having forked the jetty process. In this case
+ *  you can use the <b>jetty:stop</b> goal to terminate the process.
+ *  <p>
+ *  See <a href="http://www.eclipse.org/jetty/documentation/">http://www.eclipse.org/jetty/documentation</a> for more information on this and other jetty plugins.
+ *  </p>
+ * 
+ * @goal run-forked
+ * @requiresDependencyResolution test
+ * @execute phase="test-compile"
+ * @description Runs Jetty in forked JVM on an unassembled webapp
+ *
+ */
+public class JettyRunForkedMojo extends JettyRunMojo
+{    
+    public static final String DEFAULT_WEBAPP_SRC = "src"+File.separator+"main"+File.separator+"webapp";
+    public static final String FAKE_WEBAPP = "webapp-tmp";
+    
+    
+    public String PORT_SYSPROPERTY = "jetty.port";
+
+    
+ 
+    
+    
+    
+    /**
+     * The target directory
+     * 
+     * @parameter expression="${project.build.directory}"
+     * @required
+     * @readonly
+     */
+    protected File target;
+    
+    /**
+     * The file into which to generate the quickstart web xml for the forked process to use
+     * 
+     * @parameter expression="${project.build.directory}/fork-web.xml"
+     */
+    protected File forkWebXml;
+    
+    
+    /**
+     * Arbitrary jvm args to pass to the forked process
+     * @parameter expression="${jetty.jvmArgs}"
+     */
+    private String jvmArgs;
+    
+    
+    /**
+     * @parameter expression="${plugin.artifacts}"
+     * @readonly
+     */
+    private List pluginArtifacts;
+    
+    
+    /**
+     * @parameter expression="${plugin}"
+     * @readonly
+     */
+    private PluginDescriptor plugin;
+    
+    
+    /**
+     * @parameter expression="true" default-value="true"
+     */
+    private boolean waitForChild;
+
+    /**
+     * @parameter default-value="50"
+     */
+    private int maxStartupLines;
+    
+    
+    /**
+     * Extra environment variables to be passed to the forked process
+     * 
+     * @parameter
+     */
+    private Map<String,String> env = new HashMap<String,String>();
+
+    /**
+     * The forked jetty instance
+     */
+    private Process forkedProcess;
+    
+    
+    /**
+     * Random number generator
+     */
+    private Random random;    
+    
+ 
+    
+    private Resource originalBaseResource;
+    private boolean originalPersistTemp;
+    
+    
+    /**
+     * ShutdownThread
+     *
+     *
+     */
+    public class ShutdownThread extends Thread
+    {
+        public ShutdownThread()
+        {
+            super("RunForkedShutdown");
+        }
+        
+        public void run ()
+        {
+            if (forkedProcess != null && waitForChild)
+            {
+                forkedProcess.destroy();
+            }
+        }
+    }
+    
+
+    
+    
+    /**
+     * ConsoleStreamer
+     * 
+     * Simple streamer for the console output from a Process
+     */
+    private static class ConsoleStreamer implements Runnable
+    {
+        private String mode;
+        private BufferedReader reader;
+
+        public ConsoleStreamer(String mode, InputStream is)
+        {
+            this.mode = mode;
+            this.reader = new BufferedReader(new InputStreamReader(is));
+        }
+
+
+        public void run()
+        {
+            String line;
+            try
+            {
+                while ((line = reader.readLine()) != (null))
+                {
+                    System.out.println("[" + mode + "] " + line);
+                }
+            }
+            catch (IOException ignore)
+            {
+                /* ignore */
+            }
+            finally
+            {
+                IO.close(reader);
+            }
+        }
+    }
+    
+    
+    
+    
+    
+    /**
+     * @see org.apache.maven.plugin.Mojo#execute()
+     */
+    public void execute() throws MojoExecutionException, MojoFailureException
+    {
+        Runtime.getRuntime().addShutdownHook(new ShutdownThread());
+        random = new Random();
+        super.execute();
+    }
+    
+    
+
+
+    @Override
+    public void startJetty() throws MojoExecutionException
+    {
+        //Only do enough setup to be able to produce a quickstart-web.xml file to
+        //pass onto the forked process to run     
+        
+        if (forkWebXml == null)
+            forkWebXml = new File (target, "fork-web.xml");
+        
+        try
+        {
+            printSystemProperties();
+
+            //do NOT apply the jettyXml configuration - as the jvmArgs may be needed for it to work 
+
+            //ensure handler structure enabled
+            server.configureHandlers();
+                   
+            //ensure config of the webapp based on settings in plugin
+            configureWebApplication();
+            
+            //copy the base resource as configured by the plugin
+            originalBaseResource = webApp.getBaseResource();
+            
+            //get the original persistance setting
+            originalPersistTemp = webApp.isPersistTempDirectory();
+            
+            //set the webapp up to do very little other than generate the quickstart-web.xml
+            webApp.setCopyWebDir(false);
+            webApp.setCopyWebInf(false);
+            webApp.setGenerateQuickStart(true);
+         
+            if (!forkWebXml.getParentFile().exists())
+                forkWebXml.getParentFile().mkdirs();
+            if (!forkWebXml.exists())
+                forkWebXml.createNewFile();
+            
+            webApp.setQuickStartWebDescriptor(Resource.newResource(forkWebXml));
+            
+            //add webapp to our fake server instance
+            server.addWebApplication(webApp);
+                       
+            //if our server has a thread pool associated we can do annotation scanning multithreaded,
+            //otherwise scanning will be single threaded
+            QueuedThreadPool tpool = server.getBean(QueuedThreadPool.class);
+            if (tpool != null)
+                tpool.start();
+            else
+                webApp.setAttribute(AnnotationConfiguration.MULTI_THREADED, Boolean.FALSE.toString());
+
+            //leave everything unpacked for the forked process to use
+            webApp.setPersistTempDirectory(true);
+            
+            webApp.start(); //just enough to generate the quickstart           
+           
+            //save config of the webapp BEFORE we stop
+            File props = prepareConfiguration();
+            
+            webApp.stop();
+            
+            if (tpool != null)
+                tpool.stop();
+            
+            List<String> cmd = new ArrayList<String>();
+            cmd.add(getJavaBin());
+            
+            if (jvmArgs != null)
+            {
+                String[] args = jvmArgs.split(" ");
+                for (int i=0;args != null && i<args.length;i++)
+                {
+                    if (args[i] !=null && !"".equals(args[i]))
+                        cmd.add(args[i].trim());
+                }
+            }
+            
+            String classPath = getContainerClassPath();
+            if (classPath != null && classPath.length() > 0)
+            {
+                cmd.add("-cp");
+                cmd.add(classPath);
+            }
+            cmd.add(Starter.class.getCanonicalName());
+            
+            if (stopPort > 0 && stopKey != null)
+            {
+                cmd.add("--stop-port");
+                cmd.add(Integer.toString(stopPort));
+                cmd.add("--stop-key");
+                cmd.add(stopKey);
+            }
+            if (jettyXml != null)
+            {
+                cmd.add("--jetty-xml");
+                cmd.add(jettyXml);
+            }
+        
+            if (contextXml != null)
+            {
+                cmd.add("--context-xml");
+                cmd.add(contextXml);
+            }
+            
+            cmd.add("--props");
+            cmd.add(props.getAbsolutePath());
+            
+            String token = createToken();
+            cmd.add("--token");
+            cmd.add(token);
+            
+            ProcessBuilder builder = new ProcessBuilder(cmd);
+            builder.directory(project.getBasedir());
+            
+            if (PluginLog.getLog().isDebugEnabled())
+                PluginLog.getLog().debug(Arrays.toString(cmd.toArray()));
+            
+            PluginLog.getLog().info("Forked process starting");
+            
+            //set up extra environment vars if there are any
+            if (!env.isEmpty())
+            {
+                builder.environment().putAll(env);
+            }
+            
+            if (waitForChild)
+            {
+                forkedProcess = builder.start();
+                startPump("STDOUT",forkedProcess.getInputStream());
+                startPump("STDERR",forkedProcess.getErrorStream());
+                int exitcode = forkedProcess.waitFor();            
+                PluginLog.getLog().info("Forked execution exit: "+exitcode);
+            }
+            else
+            {   //merge stderr and stdout from child
+                builder.redirectErrorStream(true);
+                forkedProcess = builder.start();
+
+                //wait for the child to be ready before terminating.
+                //child indicates it has finished starting by printing on stdout the token passed to it
+                try
+                {
+                    String line = "";
+                    try (InputStream is = forkedProcess.getInputStream();
+                            LineNumberReader reader = new LineNumberReader(new InputStreamReader(is)))
+                    {
+                        int attempts = maxStartupLines; //max lines we'll read trying to get token
+                        while (attempts>0 && line != null)
+                        {
+                            --attempts;
+                            line = reader.readLine();
+                            if (line != null && line.startsWith(token))
+                                break;
+                        }
+
+                    }
+
+                    if (line != null && line.trim().equals(token))
+                        PluginLog.getLog().info("Forked process started.");
+                    else
+                    {
+                        String err = (line == null?"":(line.startsWith(token)?line.substring(token.length()):line));
+                        PluginLog.getLog().info("Forked process startup errors"+(!"".equals(err)?", received: "+err:""));
+                    }
+                }
+                catch (Exception e)
+                {
+                    throw new MojoExecutionException ("Problem determining if forked process is ready: "+e.getMessage());
+                }
+
+            }
+        }
+        catch (InterruptedException ex)
+        {
+            if (forkedProcess != null && waitForChild)
+                forkedProcess.destroy();
+            
+            throw new MojoExecutionException("Failed to start Jetty within time limit");
+        }
+        catch (Exception ex)
+        {
+            if (forkedProcess != null && waitForChild)
+                forkedProcess.destroy();
+            
+            throw new MojoExecutionException("Failed to create Jetty process", ex);
+        }
+    }
+
+
+
+
+    /**
+     * @return
+     * @throws MojoExecutionException
+     */
+    public List<String> getProvidedJars() throws MojoExecutionException
+    {  
+        //if we are configured to include the provided dependencies on the plugin's classpath
+        //(which mimics being on jetty's classpath vs being on the webapp's classpath), we first
+        //try and filter out ones that will clash with jars that are plugin dependencies, then
+        //create a new classloader that we setup in the parent chain.
+        if (useProvidedScope)
+        {
+            
+                List<String> provided = new ArrayList<String>();        
+                for ( Iterator<Artifact> iter = project.getArtifacts().iterator(); iter.hasNext(); )
+                {                   
+                    Artifact artifact = iter.next();
+                    if (Artifact.SCOPE_PROVIDED.equals(artifact.getScope()) && !isPluginArtifact(artifact))
+                    {
+                        provided.add(artifact.getFile().getAbsolutePath());
+                        if (getLog().isDebugEnabled()) { getLog().debug("Adding provided artifact: "+artifact);}
+                    }
+                }
+                return provided;
+
+        }
+        else
+            return null;
+    }
+    
+   
+    
+    
+    /**
+     * @return
+     * @throws MojoExecutionException
+     */
+    public File prepareConfiguration() throws MojoExecutionException
+    {
+        try
+        {   
+            //work out the configuration based on what is configured in the pom
+            File propsFile = new File (target, "fork.props");
+            if (propsFile.exists())
+                propsFile.delete();   
+
+            propsFile.createNewFile();
+            //propsFile.deleteOnExit();
+
+            Properties props = new Properties();
+
+
+            //web.xml
+            if (webApp.getDescriptor() != null)
+            {
+                props.put("web.xml", webApp.getDescriptor());
+            }
+            
+            if (webApp.getQuickStartWebDescriptor() != null)
+            {
+                props.put("quickstart.web.xml", webApp.getQuickStartWebDescriptor().getFile().getAbsolutePath());
+            }
+
+            //sort out the context path
+            if (webApp.getContextPath() != null)
+            {
+                props.put("context.path", webApp.getContextPath());       
+            }
+
+            //tmp dir
+            props.put("tmp.dir", webApp.getTempDirectory().getAbsolutePath());
+            props.put("tmp.dir.persist", Boolean.toString(originalPersistTemp));
+
+            //send over the original base resources before any overlays were added
+            if (originalBaseResource instanceof ResourceCollection)
+                props.put("base.dirs.orig", toCSV(((ResourceCollection)originalBaseResource).getResources()));
+            else
+                props.put("base.dirs.orig", originalBaseResource.toString());
+
+            //send over the calculated resource bases that includes unpacked overlays, but none of the
+            //meta-inf resources
+            Resource postOverlayResources = (Resource)webApp.getAttribute(MavenWebInfConfiguration.RESOURCE_BASES_POST_OVERLAY);
+            if (postOverlayResources instanceof ResourceCollection)
+                props.put("base.dirs", toCSV(((ResourceCollection)postOverlayResources).getResources()));
+            else
+                props.put("base.dirs", postOverlayResources.toString());
+        
+            
+            //web-inf classes
+            if (webApp.getClasses() != null)
+            {
+                props.put("classes.dir",webApp.getClasses().getAbsolutePath());
+            }
+            
+            if (useTestScope && webApp.getTestClasses() != null)
+            {
+                props.put("testClasses.dir", webApp.getTestClasses().getAbsolutePath());
+            }
+
+            //web-inf lib
+            List<File> deps = webApp.getWebInfLib();
+            StringBuffer strbuff = new StringBuffer();
+            for (int i=0; i<deps.size(); i++)
+            {
+                File d = deps.get(i);
+                strbuff.append(d.getAbsolutePath());
+                if (i < deps.size()-1)
+                    strbuff.append(",");
+            }
+            props.put("lib.jars", strbuff.toString());
+
+            //any war files
+            List<Artifact> warArtifacts = getWarArtifacts(); 
+            for (int i=0; i<warArtifacts.size(); i++)
+            {
+                strbuff.setLength(0);           
+                Artifact a  = warArtifacts.get(i);
+                strbuff.append(a.getGroupId()+",");
+                strbuff.append(a.getArtifactId()+",");
+                strbuff.append(a.getFile().getAbsolutePath());
+                props.put("maven.war.artifact."+i, strbuff.toString());
+            }
+          
+            
+            //any overlay configuration
+            WarPluginInfo warPlugin = new WarPluginInfo(project);
+            
+            //add in the war plugins default includes and excludes
+            props.put("maven.war.includes", toCSV(warPlugin.getDependentMavenWarIncludes()));
+            props.put("maven.war.excludes", toCSV(warPlugin.getDependentMavenWarExcludes()));
+            
+            
+            List<OverlayConfig> configs = warPlugin.getMavenWarOverlayConfigs();
+            int i=0;
+            for (OverlayConfig c:configs)
+            {
+                props.put("maven.war.overlay."+(i++), c.toString());
+            }
+            
+            try (OutputStream out = new BufferedOutputStream(new FileOutputStream(propsFile)))
+            {
+                props.store(out, "properties for forked webapp");
+            }
+            return propsFile;
+        }
+        catch (Exception e)
+        {
+            throw new MojoExecutionException("Prepare webapp configuration", e);
+        }
+    }
+    
+
+    
+    
+  
+    
+    /**
+     * @return
+     * @throws MalformedURLException
+     * @throws IOException
+     */
+    private List<Artifact> getWarArtifacts()
+    throws MalformedURLException, IOException
+    {
+        List<Artifact> warArtifacts = new ArrayList<Artifact>();
+        for ( Iterator<Artifact> iter = project.getArtifacts().iterator(); iter.hasNext(); )
+        {
+            Artifact artifact = (Artifact) iter.next();  
+            
+            if (artifact.getType().equals("war"))
+                warArtifacts.add(artifact);
+        }
+
+        return warArtifacts;
+    }
+    
+    
+    
+    
+    
+    
+    /**
+     * @param artifact
+     * @return
+     */
+    public boolean isPluginArtifact(Artifact artifact)
+    {
+        if (pluginArtifacts == null || pluginArtifacts.isEmpty())
+            return false;
+        
+        boolean isPluginArtifact = false;
+        for (Iterator<Artifact> iter = pluginArtifacts.iterator(); iter.hasNext() && !isPluginArtifact; )
+        {
+            Artifact pluginArtifact = iter.next();
+            if (getLog().isDebugEnabled()) { getLog().debug("Checking "+pluginArtifact);}
+            if (pluginArtifact.getGroupId().equals(artifact.getGroupId()) && pluginArtifact.getArtifactId().equals(artifact.getArtifactId()))
+                isPluginArtifact = true;
+        }
+        
+        return isPluginArtifact;
+    }
+    
+    
+    
+    
+    /**
+     * @return
+     * @throws Exception
+     */
+    private Set<Artifact> getExtraJars()
+    throws Exception
+    {
+        Set<Artifact> extraJars = new HashSet<Artifact>();
+  
+        
+        List l = pluginArtifacts;
+        Artifact pluginArtifact = null;
+
+        if (l != null)
+        {
+            Iterator itor = l.iterator();
+            while (itor.hasNext() && pluginArtifact == null)
+            {              
+                Artifact a = (Artifact)itor.next();
+                if (a.getArtifactId().equals(plugin.getArtifactId())) //get the jetty-maven-plugin jar
+                {
+                    extraJars.add(a);
+                }
+            }
+        }
+
+        return extraJars;
+    }
+
+    
+
+   
+
+    
+    /**
+     * @return
+     * @throws Exception
+     */
+    public String getContainerClassPath() throws Exception
+    {
+        StringBuilder classPath = new StringBuilder();
+        for (Object obj : pluginArtifacts)
+        {
+            Artifact artifact = (Artifact) obj;
+            if ("jar".equals(artifact.getType()))
+            {
+                if (classPath.length() > 0)
+                {
+                    classPath.append(File.pathSeparator);
+                }
+                classPath.append(artifact.getFile().getAbsolutePath());
+
+            }
+        }
+        
+        //Any jars that we need from the plugin environment (like the ones containing Starter class)
+        Set<Artifact> extraJars = getExtraJars();
+        for (Artifact a:extraJars)
+        { 
+            classPath.append(File.pathSeparator);
+            classPath.append(a.getFile().getAbsolutePath());
+        }
+        
+        
+        //Any jars that we need from the project's dependencies because we're useProvided
+        List<String> providedJars = getProvidedJars();
+        if (providedJars != null && !providedJars.isEmpty())
+        {
+            for (String jar:providedJars)
+            {
+                classPath.append(File.pathSeparator);
+                classPath.append(jar);
+                if (getLog().isDebugEnabled()) getLog().debug("Adding provided jar: "+jar);
+            }
+        }
+
+        return classPath.toString();
+    }
+
+    
+
+    
+    /**
+     * @return
+     */
+    private String getJavaBin()
+    {
+        String javaexes[] = new String[]
+        { "java", "java.exe" };
+
+        File javaHomeDir = new File(System.getProperty("java.home"));
+        for (String javaexe : javaexes)
+        {
+            File javabin = new File(javaHomeDir,fileSeparators("bin/" + javaexe));
+            if (javabin.exists() && javabin.isFile())
+            {
+                return javabin.getAbsolutePath();
+            }
+        }
+
+        return "java";
+    }
+    
+
+    
+    
+    /**
+     * @param path
+     * @return
+     */
+    public static String fileSeparators(String path)
+    {
+        StringBuilder ret = new StringBuilder();
+        for (char c : path.toCharArray())
+        {
+            if ((c == '/') || (c == '\\'))
+            {
+                ret.append(File.separatorChar);
+            }
+            else
+            {
+                ret.append(c);
+            }
+        }
+        return ret.toString();
+    }
+
+
+    
+    
+    /**
+     * @param path
+     * @return
+     */
+    public static String pathSeparators(String path)
+    {
+        StringBuilder ret = new StringBuilder();
+        for (char c : path.toCharArray())
+        {
+            if ((c == ',') || (c == ':'))
+            {
+                ret.append(File.pathSeparatorChar);
+            }
+            else
+            {
+                ret.append(c);
+            }
+        }
+        return ret.toString();
+    }
+
+
+    
+    
+    /**
+     * @return
+     */
+    private String createToken ()
+    {
+        return Long.toString(random.nextLong()^System.currentTimeMillis(), 36).toUpperCase(Locale.ENGLISH);
+    }
+    
+
+    
+    
+    /**
+     * @param mode
+     * @param inputStream
+     */
+    private void startPump(String mode, InputStream inputStream)
+    {
+        ConsoleStreamer pump = new ConsoleStreamer(mode,inputStream);
+        Thread thread = new Thread(pump,"ConsoleStreamer/" + mode);
+        thread.setDaemon(true);
+        thread.start();
+    }
+
+
+    
+    
+    /**
+     * @param strings
+     * @return
+     */
+    private String toCSV (List<String> strings)
+    {
+        if (strings == null)
+            return "";
+        StringBuffer strbuff = new StringBuffer();
+        Iterator<String> itor = strings.iterator();
+        while (itor.hasNext())
+        {
+            strbuff.append(itor.next());
+            if (itor.hasNext())
+                strbuff.append(",");
+        }
+        return strbuff.toString();
+    }
+
+    private String toCSV (Resource[] resources)
+    {
+        StringBuffer rb = new StringBuffer();
+
+        for (Resource r:resources)
+        {
+            if (rb.length() > 0) rb.append(",");
+            rb.append(r.toString());
+        }        
+
+        return rb.toString();
+    }
+}
diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyRunMojo.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyRunMojo.java
new file mode 100644
index 0000000..2383daf
--- /dev/null
+++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyRunMojo.java
@@ -0,0 +1,630 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.maven.plugin;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.MojoFailureException;
+import org.codehaus.plexus.util.FileUtils;
+import org.eclipse.jetty.util.Scanner;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.webapp.WebAppContext;
+
+
+/**
+ *  <p>
+ *  This goal is used in-situ on a Maven project without first requiring that the project 
+ *  is assembled into a war, saving time during the development cycle.
+ *  The plugin forks a parallel lifecycle to ensure that the "compile" phase has been completed before invoking Jetty. This means
+ *  that you do not need to explicity execute a "mvn compile" first. It also means that a "mvn clean jetty:run" will ensure that
+ *  a full fresh compile is done before invoking Jetty.
+ *  </p>
+ *  <p>
+ *  Once invoked, the plugin can be configured to run continuously, scanning for changes in the project and automatically performing a 
+ *  hot redeploy when necessary. This allows the developer to concentrate on coding changes to the project using their IDE of choice and have those changes
+ *  immediately and transparently reflected in the running web container, eliminating development time that is wasted on rebuilding, reassembling and redeploying.
+ *  </p>
+ *  <p>
+ *  You may also specify the location of a jetty.xml file whose contents will be applied before any plugin configuration.
+ *  This can be used, for example, to deploy a static webapp that is not part of your maven build. 
+ *  </p>
+ *  <p>
+ *  There is a <a href="http://www.eclipse.org/jetty/documentation/current/maven-and-jetty.html">reference guide</a> to the configuration parameters for this plugin.
+ *  </p>
+ * 
+ * 
+ * @goal run
+ * @requiresDependencyResolution test
+ * @execute phase="test-compile"
+ * @description Runs jetty directly from a maven project
+ */
+public class JettyRunMojo extends AbstractJettyMojo
+{
+    public static final String DEFAULT_WEBAPP_SRC = "src"+File.separator+"main"+File.separator+"webapp";
+    public static final String FAKE_WEBAPP = "webapp-tmp";
+    
+    
+
+    /**
+     * If true, the <testOutputDirectory>
+     * and the dependencies of <scope>test<scope>
+     * will be put first on the runtime classpath.
+     * 
+     * @parameter alias="useTestClasspath" default-value="false"
+     */
+    protected boolean useTestScope;
+    
+  
+    /**
+     * The default location of the web.xml file. Will be used
+     * if <webApp><descriptor> is not set.
+     * 
+     * @parameter expression="${maven.war.webxml}"
+     * @readonly
+     */
+    protected String webXml;
+    
+    
+    /**
+     * The directory containing generated classes.
+     *
+     * @parameter expression="${project.build.outputDirectory}"
+     * @required
+     * 
+     */
+    protected File classesDirectory;
+    
+    
+    /**
+     * The directory containing generated test classes.
+     * 
+     * @parameter expression="${project.build.testOutputDirectory}"
+     * @required
+     */
+    protected File testClassesDirectory;
+    
+    
+    /**
+     * Root directory for all html/jsp etc files
+     *
+     * @parameter expression="${maven.war.src}"
+     * 
+     */
+    protected File webAppSourceDirectory;
+    
+ 
+    /**
+     * List of files or directories to additionally periodically scan for changes. Optional.
+     * @parameter
+     */
+    protected File[] scanTargets;
+    
+    
+    /**
+     * List of directories with ant-style <include> and <exclude> patterns
+     * for extra targets to periodically scan for changes. Can be used instead of,
+     * or in conjunction with <scanTargets>.Optional.
+     * @parameter
+     */
+    protected ScanTargetPattern[] scanTargetPatterns;
+
+    
+    /**
+     * Extra scan targets as a list
+     */
+    protected List<File> extraScanTargets;
+    
+    
+    /**
+     * maven-war-plugin reference
+     */
+    protected WarPluginInfo warPluginInfo;
+    
+    
+    /**
+     * List of deps that are wars
+     */
+    protected List<Artifact> warArtifacts;
+    
+    
+    
+    
+    
+    
+    /** 
+     * @see org.eclipse.jetty.maven.plugin.AbstractJettyMojo#execute()
+     */
+    @Override
+    public void execute() throws MojoExecutionException, MojoFailureException
+    {
+        warPluginInfo = new WarPluginInfo(project);
+        super.execute();
+    }
+    
+    
+    
+    
+    /**
+     * Verify the configuration given in the pom.
+     * 
+     * @see AbstractJettyMojo#checkPomConfiguration()
+     */
+    public void checkPomConfiguration () throws MojoExecutionException
+    {
+        // check the location of the static content/jsps etc
+        try
+        {
+            if ((webAppSourceDirectory == null) || !webAppSourceDirectory.exists())
+            {  
+                getLog().info("webAppSourceDirectory"+(webAppSourceDirectory == null ? " not set." : (webAppSourceDirectory.getAbsolutePath()+" does not exist."))+" Trying "+DEFAULT_WEBAPP_SRC);
+                webAppSourceDirectory = new File (project.getBasedir(), DEFAULT_WEBAPP_SRC);             
+                if (!webAppSourceDirectory.exists())
+                {
+                    getLog().info("webAppSourceDirectory "+webAppSourceDirectory.getAbsolutePath()+" does not exist. Trying "+project.getBuild().getDirectory()+File.separator+FAKE_WEBAPP);
+                    
+                    //try last resort of making a fake empty dir
+                    File target = new File(project.getBuild().getDirectory());
+                    webAppSourceDirectory = new File(target, FAKE_WEBAPP);
+                    if (!webAppSourceDirectory.exists())
+                        webAppSourceDirectory.mkdirs();              
+                }
+            }
+            else
+                getLog().info( "Webapp source directory = " + webAppSourceDirectory.getCanonicalPath());
+        }
+        catch (IOException e)
+        {
+            throw new MojoExecutionException("Webapp source directory does not exist", e);
+        }
+        
+        // check reload mechanic
+        if ( !"automatic".equalsIgnoreCase( reload ) && !"manual".equalsIgnoreCase( reload ) )
+        {
+            throw new MojoExecutionException( "invalid reload mechanic specified, must be 'automatic' or 'manual'" );
+        }
+        else
+        {
+            getLog().info("Reload Mechanic: " + reload );
+        }
+
+
+        // check the classes to form a classpath with
+        try
+        {
+            //allow a webapp with no classes in it (just jsps/html)
+            if (classesDirectory != null)
+            {
+                if (!classesDirectory.exists())
+                    getLog().info( "Classes directory "+ classesDirectory.getCanonicalPath()+ " does not exist");
+                else
+                    getLog().info("Classes = " + classesDirectory.getCanonicalPath());
+            }
+            else
+                getLog().info("Classes directory not set");         
+        }
+        catch (IOException e)
+        {
+            throw new MojoExecutionException("Location of classesDirectory does not exist");
+        }
+        
+        extraScanTargets = new ArrayList<File>();
+        if (scanTargets != null)
+        {            
+            for (int i=0; i< scanTargets.length; i++)
+            {
+                getLog().info("Added extra scan target:"+ scanTargets[i]);
+                extraScanTargets.add(scanTargets[i]);
+            }            
+        }
+        
+        if (scanTargetPatterns!=null)
+        {
+            for (int i=0;i<scanTargetPatterns.length; i++)
+            {
+                Iterator itor = scanTargetPatterns[i].getIncludes().iterator();
+                StringBuffer strbuff = new StringBuffer();
+                while (itor.hasNext())
+                {
+                    strbuff.append((String)itor.next());
+                    if (itor.hasNext())
+                        strbuff.append(",");
+                }
+                String includes = strbuff.toString();
+                
+                itor = scanTargetPatterns[i].getExcludes().iterator();
+                strbuff= new StringBuffer();
+                while (itor.hasNext())
+                {
+                    strbuff.append((String)itor.next());
+                    if (itor.hasNext())
+                        strbuff.append(",");
+                }
+                String excludes = strbuff.toString();
+
+                try
+                {
+                    List<File> files = FileUtils.getFiles(scanTargetPatterns[i].getDirectory(), includes, excludes);
+                    itor = files.iterator();
+                    while (itor.hasNext())
+                        getLog().info("Adding extra scan target from pattern: "+itor.next());
+                    List<File> currentTargets = extraScanTargets;
+                    if(currentTargets!=null && !currentTargets.isEmpty())
+                        currentTargets.addAll(files);
+                    else
+                        extraScanTargets = files;
+                }
+                catch (IOException e)
+                {
+                    throw new MojoExecutionException(e.getMessage());
+                }
+            }
+        }
+    }
+
+   
+
+
+    @Override
+    public void finishConfigurationBeforeStart() throws Exception
+    {
+        server.setStopAtShutdown(true); //as we will normally be stopped with a cntrl-c, ensure server stopped 
+        super.finishConfigurationBeforeStart();
+    }
+
+
+
+
+    /** 
+     * @see org.eclipse.jetty.maven.plugin.AbstractJettyMojo#configureWebApplication()
+     */
+    public void configureWebApplication() throws Exception
+    {
+       super.configureWebApplication();
+       
+       //Set up the location of the webapp.
+       //There are 2 parts to this: setWar() and setBaseResource(). On standalone jetty,
+       //the former could be the location of a packed war, while the latter is the location
+       //after any unpacking. With this mojo, you are running an unpacked, unassembled webapp,
+       //so the two locations should be equal.
+       Resource webAppSourceDirectoryResource = Resource.newResource(webAppSourceDirectory.getCanonicalPath());
+       if (webApp.getWar() == null)
+           webApp.setWar(webAppSourceDirectoryResource.toString());
+       
+       if (webApp.getBaseResource() == null)
+               webApp.setBaseResource(webAppSourceDirectoryResource);
+
+       if (classesDirectory != null)
+           webApp.setClasses (classesDirectory);
+       if (useTestScope && (testClassesDirectory != null))
+           webApp.setTestClasses (testClassesDirectory);
+       
+       webApp.setWebInfLib (getDependencyFiles());
+
+       //get copy of a list of war artifacts
+       Set<Artifact> matchedWarArtifacts = new HashSet<Artifact>();
+
+       //make sure each of the war artifacts is added to the scanner
+       for (Artifact a:getWarArtifacts())
+           extraScanTargets.add(a.getFile());
+
+       //process any overlays and the war type artifacts
+       List<Overlay> overlays = new ArrayList<Overlay>();
+       for (OverlayConfig config:warPluginInfo.getMavenWarOverlayConfigs())
+       {
+           //overlays can be individually skipped
+           if (config.isSkip())
+               continue;
+
+           //an empty overlay refers to the current project - important for ordering
+           if (config.isCurrentProject())
+           {
+               Overlay overlay = new Overlay(config, null);
+               overlays.add(overlay);
+               continue;
+           }
+
+           //if a war matches an overlay config
+           Artifact a = getArtifactForOverlay(config, getWarArtifacts());
+           if (a != null)
+           {
+               matchedWarArtifacts.add(a);
+               SelectiveJarResource r = new SelectiveJarResource(new URL("jar:"+Resource.toURL(a.getFile()).toString()+"!/"));
+               r.setIncludes(config.getIncludes());
+               r.setExcludes(config.getExcludes());
+               Overlay overlay = new Overlay(config, r);
+               overlays.add(overlay);
+           }
+       }
+
+       //iterate over the left over war artifacts and unpack them (without include/exclude processing) as necessary
+       for (Artifact a: getWarArtifacts())
+       {
+           if (!matchedWarArtifacts.contains(a))
+           {
+               Overlay overlay = new Overlay(null, Resource.newResource(new URL("jar:"+Resource.toURL(a.getFile()).toString()+"!/")));
+               overlays.add(overlay);
+           }
+       }
+
+       webApp.setOverlays(overlays);
+       
+        //if we have not already set web.xml location, need to set one up
+        if (webApp.getDescriptor() == null)
+        {
+            //Has an explicit web.xml file been configured to use?
+            if (webXml != null)
+            {
+                Resource r = Resource.newResource(webXml);
+                if (r.exists() && !r.isDirectory())
+                {
+                    webApp.setDescriptor(r.toString());
+                }
+            }
+            
+            //Still don't have a web.xml file: try the resourceBase of the webapp, if it is set
+            if (webApp.getDescriptor() == null && webApp.getBaseResource() != null)
+            {
+                Resource r = webApp.getBaseResource().addPath("WEB-INF/web.xml");
+                if (r.exists() && !r.isDirectory())
+                {
+                    webApp.setDescriptor(r.toString());
+                }
+            }
+            
+            //Still don't have a web.xml file: finally try the configured static resource directory if there is one
+            if (webApp.getDescriptor() == null && (webAppSourceDirectory != null))
+            {
+                File f = new File (new File (webAppSourceDirectory, "WEB-INF"), "web.xml");
+                if (f.exists() && f.isFile())
+                {
+                   webApp.setDescriptor(f.getCanonicalPath());
+                }
+            }
+        }
+        getLog().info( "web.xml file = "+webApp.getDescriptor());       
+        getLog().info("Webapp directory = " + webAppSourceDirectory.getCanonicalPath());
+    }
+    
+    
+
+    
+    /** 
+     * @see org.eclipse.jetty.maven.plugin.AbstractJettyMojo#configureScanner()
+     */
+    public void configureScanner ()
+    throws MojoExecutionException
+    {
+        // start the scanner thread (if necessary) on the main webapp
+        scanList = new ArrayList<File>();
+        if (webApp.getDescriptor() != null)
+        {
+            try (Resource r = Resource.newResource(webApp.getDescriptor());)
+            {
+                scanList.add(r.getFile());
+            }
+            catch (IOException e)
+            {
+                throw new MojoExecutionException("Problem configuring scanner for web.xml", e);
+            }
+        }
+
+        if (webApp.getJettyEnvXml() != null)
+        {
+            try (Resource r = Resource.newResource(webApp.getJettyEnvXml());)
+            {
+                scanList.add(r.getFile());
+            }
+            catch (IOException e)
+            {
+                throw new MojoExecutionException("Problem configuring scanner for jetty-env.xml", e);
+            }
+        }
+
+        if (webApp.getDefaultsDescriptor() != null)
+        {
+            try (Resource r = Resource.newResource(webApp.getDefaultsDescriptor());)
+            {
+                if (!WebAppContext.WEB_DEFAULTS_XML.equals(webApp.getDefaultsDescriptor()))
+                    scanList.add(r.getFile());
+            }
+            catch (IOException e)
+            {
+                throw new MojoExecutionException("Problem configuring scanner for webdefaults.xml", e);
+            }
+        }
+        
+        if (webApp.getOverrideDescriptor() != null)
+        {
+            try (Resource r = Resource.newResource(webApp.getOverrideDescriptor());)
+            {
+                scanList.add(r.getFile());
+            }
+            catch (IOException e)
+            {
+                throw new MojoExecutionException("Problem configuring scanner for webdefaults.xml", e);
+            }
+        }
+        
+        
+        File jettyWebXmlFile = findJettyWebXmlFile(new File(webAppSourceDirectory,"WEB-INF"));
+        if (jettyWebXmlFile != null)
+            scanList.add(jettyWebXmlFile);
+        scanList.addAll(extraScanTargets);
+        scanList.add(project.getFile());
+        if (webApp.getTestClasses() != null)
+            scanList.add(webApp.getTestClasses());
+        if (webApp.getClasses() != null)
+        scanList.add(webApp.getClasses());
+        scanList.addAll(webApp.getWebInfLib());
+     
+        scannerListeners = new ArrayList<Scanner.BulkListener>();
+        scannerListeners.add(new Scanner.BulkListener()
+        {
+            public void filesChanged (List changes)
+            {
+                try
+                {
+                    boolean reconfigure = changes.contains(project.getFile().getCanonicalPath());
+                    restartWebApp(reconfigure);
+                }
+                catch (Exception e)
+                {
+                    getLog().error("Error reconfiguring/restarting webapp after change in watched files",e);
+                }
+            }
+        });
+    }
+
+    
+    
+    
+    /** 
+     * @see org.eclipse.jetty.maven.plugin.AbstractJettyMojo#restartWebApp(boolean)
+     */
+    public void restartWebApp(boolean reconfigureScanner) throws Exception 
+    {
+        getLog().info("restarting "+webApp);
+        getLog().debug("Stopping webapp ...");
+        webApp.stop();
+        getLog().debug("Reconfiguring webapp ...");
+ 
+        checkPomConfiguration();
+        configureWebApplication();
+
+        // check if we need to reconfigure the scanner,
+        // which is if the pom changes
+        if (reconfigureScanner)
+        {
+            getLog().info("Reconfiguring scanner after change to pom.xml ...");
+            scanList.clear();
+            if (webApp.getDescriptor() != null)
+                scanList.add(new File(webApp.getDescriptor()));
+            if (webApp.getJettyEnvXml() != null)
+                scanList.add(new File(webApp.getJettyEnvXml()));
+            scanList.addAll(extraScanTargets);
+            scanList.add(project.getFile());
+            if (webApp.getTestClasses() != null)
+                scanList.add(webApp.getTestClasses());
+            if (webApp.getClasses() != null)
+            scanList.add(webApp.getClasses());
+            scanList.addAll(webApp.getWebInfLib());
+            scanner.setScanDirs(scanList);
+        }
+
+        getLog().debug("Restarting webapp ...");
+        webApp.start();
+        getLog().info("Restart completed at "+new Date().toString());
+    }
+    
+    
+    
+    
+    /**
+     * @return
+     */
+    private List<File> getDependencyFiles ()
+    {
+        List<File> dependencyFiles = new ArrayList<File>();
+        for ( Iterator<Artifact> iter = projectArtifacts.iterator(); iter.hasNext(); )
+        {
+            Artifact artifact = (Artifact) iter.next();
+            
+            // Include runtime and compile time libraries, and possibly test libs too
+            if(artifact.getType().equals("war"))
+            {
+                continue;
+            }
+
+            if (Artifact.SCOPE_PROVIDED.equals(artifact.getScope()))
+                continue; //never add dependencies of scope=provided to the webapp's classpath (see also <useProvidedScope> param)
+
+            if (Artifact.SCOPE_TEST.equals(artifact.getScope()) && !useTestScope)
+                continue; //only add dependencies of scope=test if explicitly required
+
+            dependencyFiles.add(artifact.getFile());
+            getLog().debug( "Adding artifact " + artifact.getFile().getName() + " with scope "+artifact.getScope()+" for WEB-INF/lib " );   
+        }
+              
+        return dependencyFiles; 
+    }
+    
+    
+    
+    
+    /**
+     * @return
+     */
+    private List<Artifact> getWarArtifacts ()
+    {
+        if (warArtifacts != null)
+            return warArtifacts;       
+        
+        warArtifacts = new ArrayList<Artifact>();
+        for ( Iterator<Artifact> iter = projectArtifacts.iterator(); iter.hasNext(); )
+        {
+            Artifact artifact = (Artifact) iter.next();            
+            if (artifact.getType().equals("war"))
+            {
+                try
+                {                  
+                    warArtifacts.add(artifact);
+                    getLog().info("Dependent war artifact "+artifact.getId());
+                }
+                catch(Exception e)
+                {
+                    throw new RuntimeException(e);
+                }
+            }
+        }
+        return warArtifacts;
+    }
+
+    
+    
+    /**
+     * @param o
+     * @param warArtifacts
+     * @return
+     */
+    protected Artifact getArtifactForOverlay (OverlayConfig o, List<Artifact> warArtifacts)
+    {
+        if (o == null || warArtifacts == null || warArtifacts.isEmpty())
+            return null;
+        
+        for (Artifact a:warArtifacts)
+        {
+            if (o.matchesArtifact (a.getGroupId(), a.getArtifactId(), a.getClassifier()))
+            {
+               return a;
+            }
+        }
+        
+        return null;
+    }
+}
diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyRunWarExplodedMojo.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyRunWarExplodedMojo.java
new file mode 100644
index 0000000..c3beb38
--- /dev/null
+++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyRunWarExplodedMojo.java
@@ -0,0 +1,181 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.maven.plugin;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.MojoFailureException;
+import org.eclipse.jetty.util.Scanner;
+
+/**
+ * 
+ *  <p>
+ *  This goal is used to assemble your webapp into an exploded war and automatically deploy it to Jetty.
+ *  </p>
+ *  <p>
+ *  Once invoked, the plugin runs continuously, and can be configured to scan for changes in the pom.xml and 
+ *  to WEB-INF/web.xml, WEB-INF/classes or WEB-INF/lib and hot redeploy when a change is detected. 
+ *  </p>
+ *  <p>
+ *  You may also specify the location of a jetty.xml file whose contents will be applied before any plugin configuration.
+ *  This can be used, for example, to deploy a static webapp that is not part of your maven build. 
+ *  </p>
+ *
+ *@goal run-exploded
+ *@requiresDependencyResolution compile+runtime
+ *@execute phase=package
+ */
+public class JettyRunWarExplodedMojo extends AbstractJettyMojo
+{
+
+    
+    
+    /**
+     * The location of the war file.
+     * 
+     * @parameter expression="${project.build.directory}/${project.build.finalName}"
+     * @required
+     */
+    private File war;
+
+    
+   
+  
+   
+    /** 
+     * @see org.eclipse.jetty.maven.plugin.AbstractJettyMojo#execute()
+     */
+    public void execute () throws MojoExecutionException, MojoFailureException
+    {
+        super.execute();
+    }
+    
+    
+
+    @Override
+    public void finishConfigurationBeforeStart() throws Exception
+    {
+        server.setStopAtShutdown(true); //as we will normally be stopped with a cntrl-c, ensure server stopped 
+        super.finishConfigurationBeforeStart();
+    }
+
+    
+    /**
+     * 
+     * @see AbstractJettyMojo#checkPomConfiguration()
+     */
+    public void checkPomConfiguration() throws MojoExecutionException
+    {
+        return;
+    }
+
+    
+    
+    
+    /**
+     * @see AbstractJettyMojo#configureScanner()
+     */
+    public void configureScanner() throws MojoExecutionException
+    {
+        scanList = new ArrayList<File>();
+        scanList.add(project.getFile());
+        File webInfDir = new File(war,"WEB-INF");
+        scanList.add(new File(webInfDir, "web.xml"));
+        File jettyWebXmlFile = findJettyWebXmlFile(webInfDir);
+        if (jettyWebXmlFile != null)
+            scanList.add(jettyWebXmlFile);
+        File jettyEnvXmlFile = new File(webInfDir, "jetty-env.xml");
+        if (jettyEnvXmlFile.exists())
+            scanList.add(jettyEnvXmlFile);
+        scanList.add(new File(webInfDir, "classes"));
+        scanList.add(new File(webInfDir, "lib"));
+
+        scannerListeners = new ArrayList<Scanner.BulkListener>();
+        scannerListeners.add(new Scanner.BulkListener()
+        {
+            public void filesChanged(List changes)
+            {
+                try
+                {
+                    boolean reconfigure = changes.contains(project.getFile().getCanonicalPath());
+                    restartWebApp(reconfigure);
+                }
+                catch (Exception e)
+                {
+                    getLog().error("Error reconfiguring/restarting webapp after change in watched files",e);
+                }
+            }
+        });
+    }
+
+    
+    
+    
+    /** 
+     * @see org.eclipse.jetty.maven.plugin.AbstractJettyMojo#restartWebApp(boolean)
+     */
+    public void restartWebApp(boolean reconfigureScanner) throws Exception 
+    {
+        getLog().info("Restarting webapp");
+        getLog().debug("Stopping webapp ...");
+        webApp.stop();
+        getLog().debug("Reconfiguring webapp ...");
+
+        checkPomConfiguration();
+
+        // check if we need to reconfigure the scanner,
+        // which is if the pom changes
+        if (reconfigureScanner)
+        {
+            getLog().info("Reconfiguring scanner after change to pom.xml ...");
+            scanList.clear();
+            scanList.add(project.getFile());
+            File webInfDir = new File(war,"WEB-INF");
+            scanList.add(new File(webInfDir, "web.xml"));
+            File jettyWebXmlFile = findJettyWebXmlFile(webInfDir);
+            if (jettyWebXmlFile != null)
+                scanList.add(jettyWebXmlFile);
+            File jettyEnvXmlFile = new File(webInfDir, "jetty-env.xml");
+            if (jettyEnvXmlFile.exists())
+                scanList.add(jettyEnvXmlFile);
+            scanList.add(new File(webInfDir, "classes"));
+            scanList.add(new File(webInfDir, "lib"));
+            scanner.setScanDirs(scanList);
+        }
+
+        getLog().debug("Restarting webapp ...");
+        webApp.start();
+        getLog().info("Restart completed.");
+    }
+
+   
+
+    
+    /** 
+     * @see org.eclipse.jetty.maven.plugin.AbstractJettyMojo#configureWebApplication()
+     */
+    public void configureWebApplication () throws Exception
+    {
+        super.configureWebApplication();        
+        webApp.setWar(war.getCanonicalPath());
+    }
+}
diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyRunWarMojo.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyRunWarMojo.java
new file mode 100644
index 0000000..b2b699b
--- /dev/null
+++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyRunWarMojo.java
@@ -0,0 +1,156 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.maven.plugin;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.MojoFailureException;
+import org.eclipse.jetty.util.Scanner;
+
+/**
+ * <p>
+ *  This goal is used to assemble your webapp into a war and automatically deploy it to Jetty.
+ *  </p>
+ *  <p>
+ *  Once invoked, the plugin runs continuously and can be configured to scan for changes in the project and to the
+ *  war file and automatically perform a hot redeploy when necessary. 
+ *  </p>
+ *  <p>
+ *  You may also specify the location of a jetty.xml file whose contents will be applied before any plugin configuration.
+ *  This can be used, for example, to deploy a static webapp that is not part of your maven build. 
+ *  </p>
+ * 
+ * @goal run-war
+ * @requiresDependencyResolution compile+runtime
+ * @execute phase="package"
+ * @description Runs jetty on a war file
+ *
+ */
+public class JettyRunWarMojo extends AbstractJettyMojo
+{
+
+    /**
+     * The location of the war file.
+     * @parameter expression="${project.build.directory}/${project.build.finalName}.war"
+     * @required
+     */
+    private File war;
+
+    
+    /**
+     * @see org.apache.maven.plugin.Mojo#execute()
+     */
+    public void execute() throws MojoExecutionException, MojoFailureException
+    {
+        super.execute();  
+    }
+
+
+    @Override
+    public void finishConfigurationBeforeStart() throws Exception
+    {
+        server.setStopAtShutdown(true); //as we will normally be stopped with a cntrl-c, ensure server stopped 
+        super.finishConfigurationBeforeStart();
+    }
+
+
+    
+    public void configureWebApplication () throws Exception
+    {
+        super.configureWebApplication();
+        
+        webApp.setWar(war.getCanonicalPath());
+    }
+ 
+
+
+    
+    /**
+     * @see AbstractJettyMojo#checkPomConfiguration()
+     */
+    public void checkPomConfiguration() throws MojoExecutionException
+    {
+       return;        
+    }
+
+
+
+    
+    /**
+     * @see AbstractJettyMojo#configureScanner()
+     */
+    public void configureScanner() throws MojoExecutionException
+    {
+        scanList = new ArrayList();
+        scanList.add(project.getFile());
+        scanList.add(war);
+        
+        scannerListeners = new ArrayList();
+        scannerListeners.add(new Scanner.BulkListener()
+        {
+            public void filesChanged(List changes)
+            {
+                try
+                {
+                    boolean reconfigure = changes.contains(project.getFile().getCanonicalPath());
+                    restartWebApp(reconfigure);
+                }
+                catch (Exception e)
+                {
+                    getLog().error("Error reconfiguring/restarting webapp after change in watched files",e);
+                }
+            }
+        });
+    }
+
+
+    
+    
+    /** 
+     * @see org.eclipse.jetty.maven.plugin.AbstractJettyMojo#restartWebApp(boolean)
+     */
+    public void restartWebApp(boolean reconfigureScanner) throws Exception 
+    {
+        getLog().info("Restarting webapp ...");
+        getLog().debug("Stopping webapp ...");
+        webApp.stop();
+        getLog().debug("Reconfiguring webapp ...");
+
+        checkPomConfiguration();
+
+        // check if we need to reconfigure the scanner,
+        // which is if the pom changes
+        if (reconfigureScanner)
+        {
+            getLog().info("Reconfiguring scanner after change to pom.xml ...");
+            scanList.clear();
+            scanList.add(project.getFile());
+            scanList.add(war);
+            scanner.setScanDirs(scanList);
+        }
+
+        getLog().debug("Restarting webapp ...");
+        webApp.start();
+        getLog().info("Restart completed.");
+    }
+
+}
diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyServer.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyServer.java
new file mode 100644
index 0000000..cab7e3e
--- /dev/null
+++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyServer.java
@@ -0,0 +1,156 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.maven.plugin;
+
+
+import java.io.File;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.RequestLog;
+import org.eclipse.jetty.server.handler.ContextHandlerCollection;
+import org.eclipse.jetty.server.handler.DefaultHandler;
+import org.eclipse.jetty.server.handler.HandlerCollection;
+import org.eclipse.jetty.server.handler.RequestLogHandler;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.webapp.WebAppContext;
+import org.eclipse.jetty.xml.XmlConfiguration;
+
+
+/**
+ * JettyServer
+ * 
+ * Maven jetty plugin version of a wrapper for the Server class.
+ * 
+ */
+public class JettyServer extends org.eclipse.jetty.server.Server
+{ 
+    private RequestLog requestLog;
+    private ContextHandlerCollection contexts;
+    
+    
+    
+    /**
+     * 
+     */
+    public JettyServer()
+    {
+        super();
+        //make sure Jetty does not use URLConnection caches with the plugin
+        Resource.setDefaultUseCaches(false);
+    }
+
+   
+    public void setRequestLog (RequestLog requestLog)
+    {
+        this.requestLog = requestLog;
+    }
+
+    /**
+     * @see org.eclipse.jetty.server.Server#doStart()
+     */
+    public void doStart() throws Exception
+    {
+        super.doStart();
+    }
+
+ 
+    /**
+     * @see org.eclipse.jetty.server.handler.HandlerCollection#addHandler(org.eclipse.jetty.server.Handler)
+     */
+    public void addWebApplication(WebAppContext webapp) throws Exception
+    {  
+        contexts.addHandler (webapp);
+    }
+
+    
+    /**
+     * Set up the handler structure to receive a webapp.
+     * Also put in a DefaultHandler so we get a nice page
+     * than a 404 if we hit the root and the webapp's
+     * context isn't at root.
+     * @throws Exception
+     */
+    public void configureHandlers () throws Exception 
+    {
+        DefaultHandler defaultHandler = new DefaultHandler();
+        RequestLogHandler requestLogHandler = new RequestLogHandler();
+        if (this.requestLog != null)
+            requestLogHandler.setRequestLog(this.requestLog);
+        
+        contexts = (ContextHandlerCollection)super.getChildHandlerByClass(ContextHandlerCollection.class);
+        if (contexts==null)
+        {   
+            contexts = new ContextHandlerCollection();
+            HandlerCollection handlers = (HandlerCollection)super.getChildHandlerByClass(HandlerCollection.class);
+            if (handlers==null)
+            {
+                handlers = new HandlerCollection();               
+                super.setHandler(handlers);                            
+                handlers.setHandlers(new Handler[]{contexts, defaultHandler, requestLogHandler});
+            }
+            else
+            {
+                handlers.addHandler(contexts);
+            }
+        }  
+    }
+
+    /**
+     * Apply xml files to server startup, passing in ourselves as the 
+     * "Server" instance.
+     * 
+     * @param files
+     * @throws Exception
+     */
+    public  void applyXmlConfigurations (List<File> files) 
+    throws Exception
+    {
+        if (files == null || files.isEmpty())
+            return;
+
+       Map<String,Object> lastMap = Collections.singletonMap("Server", (Object)this);
+
+        for ( File xmlFile : files )
+        {
+            if (PluginLog.getLog() != null)
+                PluginLog.getLog().info( "Configuring Jetty from xml configuration file = " + xmlFile.getCanonicalPath() );   
+
+
+            XmlConfiguration xmlConfiguration = new XmlConfiguration(Resource.toURL(xmlFile));
+
+            //chain ids from one config file to another
+            if (lastMap != null)
+                xmlConfiguration.getIdMap().putAll(lastMap); 
+
+            //Set the system properties each time in case the config file set a new one
+            Enumeration<?> ensysprop = System.getProperties().propertyNames();
+            while (ensysprop.hasMoreElements())
+            {
+                String name = (String)ensysprop.nextElement();
+                xmlConfiguration.getProperties().put(name,System.getProperty(name));
+            }
+            xmlConfiguration.configure(); 
+            lastMap = xmlConfiguration.getIdMap();
+        }
+    }
+}
diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyStartMojo.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyStartMojo.java
new file mode 100644
index 0000000..9155646
--- /dev/null
+++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyStartMojo.java
@@ -0,0 +1,59 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.maven.plugin;
+
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.MojoFailureException;
+
+
+/**
+ *  <p>
+ *  This goal is similar to the jetty:run goal, EXCEPT that it is designed to be bound to an execution inside your pom, rather
+ *  than being run from the command line. 
+ *  </p>
+ *  <p>
+ *  When using it, be careful to ensure that you bind it to a phase in which all necessary generated files and classes for the webapp
+ *  will have been created. If you run it from the command line, then also ensure that all necessary generated files and classes for
+ *  the webapp already exist.
+ *  </p>
+ * 
+ * @goal start
+ * @requiresDependencyResolution test
+ * @execute phase="validate"
+ * @description Runs jetty directly from a maven project from a binding to an execution in your pom
+ */
+public class JettyStartMojo extends JettyRunMojo
+{
+
+    @Override
+    public void execute() throws MojoExecutionException, MojoFailureException
+    {
+        nonblocking = true; //ensure that starting jetty won't hold up the thread
+        super.execute();
+    }
+    
+
+    @Override
+    public void finishConfigurationBeforeStart() throws Exception
+    {
+        super.finishConfigurationBeforeStart();
+        server.setStopAtShutdown(false); //as we will normally be stopped with a cntrl-c, ensure server stopped 
+    }
+    
+}
diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyStopMojo.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyStopMojo.java
new file mode 100644
index 0000000..49b92ee
--- /dev/null
+++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyStopMojo.java
@@ -0,0 +1,136 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.maven.plugin;
+
+import java.io.InputStreamReader;
+import java.io.LineNumberReader;
+import java.io.OutputStream;
+import java.net.ConnectException;
+import java.net.InetAddress;
+import java.net.Socket;
+
+import org.apache.maven.plugin.AbstractMojo;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.MojoFailureException;
+
+/**
+ * JettyStopMojo - stops a running instance of jetty.
+ * The ff are required:
+ * -DstopKey=someKey
+ * -DstopPort=somePort
+ * 
+ * @goal stop
+ * @description Stops jetty that is configured with <stopKey> and <stopPort>.
+ */
+
+public class JettyStopMojo extends AbstractMojo
+{
+    
+    /**
+     * Port to listen to stop jetty on sending stop command
+     * @parameter
+     * @required
+     */
+    protected int stopPort;
+    
+    /**
+     * Key to provide when stopping jetty on executing java -DSTOP.KEY=<stopKey> 
+     * -DSTOP.PORT=<stopPort> -jar start.jar --stop
+     * @parameter
+     * @required
+     */
+    protected String stopKey;
+    
+    /**
+     * Max time in seconds that the plugin will wait for confirmation that jetty has stopped.
+     * @parameter
+     */
+    protected int stopWait;
+    
+ 
+    
+    
+    
+
+    public void execute() throws MojoExecutionException, MojoFailureException 
+    {
+        if (stopPort <= 0)
+            throw new MojoExecutionException("Please specify a valid port"); 
+        if (stopKey == null)
+            throw new MojoExecutionException("Please specify a valid stopKey");  
+
+        //Ensure jetty Server instance stops. Whether or not the remote process
+        //also stops depends whether or not it was started with ShutdownMonitor.exitVm=true
+        String command = "forcestop"; 
+       
+        try
+        {        
+            Socket s=new Socket(InetAddress.getByName("127.0.0.1"),stopPort);
+            s.setSoLinger(false, 0);
+
+            OutputStream out=s.getOutputStream();
+            out.write((stopKey+"\r\n"+command+"\r\n").getBytes());
+            out.flush();
+
+            if (stopWait > 0)
+            {      
+                s.setSoTimeout(stopWait * 1000);
+                s.getInputStream();
+
+                getLog().info("Waiting "+stopWait+" seconds for jetty to stop");
+                LineNumberReader lin = new LineNumberReader(new InputStreamReader(s.getInputStream()));
+                String response;
+                boolean stopped = false;
+                while (!stopped && ((response = lin.readLine()) != null))
+                {
+                    if ("Stopped".equals(response))
+                    {
+                        stopped = true;
+                        getLog().info("Server reports itself as stopped");
+                    }                
+                }
+            }
+            s.close();
+        }
+        catch (ConnectException e)
+        {
+            getLog().info("Jetty not running!");
+        }
+        catch (Exception e)
+        {
+            getLog().error(e);
+        }
+    }
+
+    public int getStopPort() {
+        return stopPort;
+    }
+
+    public void setStopPort(int stopPort) {
+        this.stopPort = stopPort;
+    }
+
+    public String getStopKey() {
+        return stopKey;
+    }
+
+    public void setStopKey(String stopKey) {
+        this.stopKey = stopKey;
+    }
+}
diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyWebAppContext.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyWebAppContext.java
new file mode 100644
index 0000000..0d56f05
--- /dev/null
+++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyWebAppContext.java
@@ -0,0 +1,526 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.maven.plugin;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.util.ArrayList;
+import java.util.EventListener;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.eclipse.jetty.annotations.AnnotationConfiguration;
+import org.eclipse.jetty.plus.webapp.EnvConfiguration;
+import org.eclipse.jetty.plus.webapp.PlusConfiguration;
+import org.eclipse.jetty.quickstart.PreconfigureDescriptorProcessor;
+import org.eclipse.jetty.quickstart.QuickStartDescriptorGenerator;
+import org.eclipse.jetty.servlet.FilterHolder;
+import org.eclipse.jetty.servlet.FilterMapping;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jetty.servlet.ServletMapping;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.util.resource.ResourceCollection;
+import org.eclipse.jetty.webapp.Configuration;
+import org.eclipse.jetty.webapp.FragmentConfiguration;
+import org.eclipse.jetty.webapp.JettyWebXmlConfiguration;
+import org.eclipse.jetty.webapp.MetaInfConfiguration;
+import org.eclipse.jetty.webapp.WebAppContext;
+import org.eclipse.jetty.webapp.WebInfConfiguration;
+import org.eclipse.jetty.webapp.WebXmlConfiguration;
+
+/**
+ * JettyWebAppContext
+ * 
+ * Extends the WebAppContext to specialize for the maven environment.
+ * We pass in the list of files that should form the classpath for
+ * the webapp when executing in the plugin, and any jetty-env.xml file
+ * that may have been configured.
+ *
+ */
+public class JettyWebAppContext extends WebAppContext
+{
+    private static final Logger LOG = Log.getLogger(JettyWebAppContext.class);
+    
+   
+
+    private static final String DEFAULT_CONTAINER_INCLUDE_JAR_PATTERN = ".*/javax.servlet-[^/]*\\.jar$|.*/servlet-api-[^/]*\\.jar$|.*javax.servlet.jsp.jstl-[^/]*\\.jar|.*taglibs-standard-impl-.*\\.jar";
+    private static final String WEB_INF_CLASSES_PREFIX = "/WEB-INF/classes";
+    private static final String WEB_INF_LIB_PREFIX = "/WEB-INF/lib";
+
+    private final Configuration[] _defaultConfigurations = {
+                                                             new MavenWebInfConfiguration(),
+                                                             new WebXmlConfiguration(),
+                                                             new MetaInfConfiguration(),
+                                                             new FragmentConfiguration(),
+                                                             new EnvConfiguration(),
+                                                             new PlusConfiguration(),
+                                                             new AnnotationConfiguration(),
+                                                             new JettyWebXmlConfiguration()
+                                                            };
+
+    private final Configuration[] _quickStartConfigurations = {
+                                                                new MavenQuickStartConfiguration(),
+                                                                new EnvConfiguration(),
+                                                                new PlusConfiguration(),
+                                                                new JettyWebXmlConfiguration()
+                                                               };
+
+    private File _classes = null;
+    private File _testClasses = null;
+    private final List<File> _webInfClasses = new ArrayList<File>();
+    private final List<File> _webInfJars = new ArrayList<File>();
+    private final Map<String, File> _webInfJarMap = new HashMap<String, File>();
+    private List<File> _classpathFiles;  //webInfClasses+testClasses+webInfJars
+    private String _jettyEnvXml;
+    private List<Overlay> _overlays;
+    private Resource _quickStartWebXml;
+    
+ 
+    
+    /**
+     * Set the "org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern" with a pattern for matching jars on
+     * container classpath to scan. This is analogous to the WebAppContext.setAttribute() call.
+     */
+    private String _containerIncludeJarPattern = null;
+    
+    /**
+     * Set the "org.eclipse.jetty.server.webapp.WebInfIncludeJarPattern" with a pattern for matching jars on
+     * webapp's classpath to scan. This is analogous to the WebAppContext.setAttribute() call.
+     */
+    private String _webInfIncludeJarPattern = null;
+  
+
+    
+    /**
+     * If there is no maven-war-plugin config for ordering of the current project in the
+     * sequence of overlays, use this to control whether the current project is added 
+     * first or last in list of overlaid resources
+     */
+    private boolean _baseAppFirst = true;
+
+
+
+    private boolean _isGenerateQuickStart;
+    private PreconfigureDescriptorProcessor _preconfigProcessor;
+   
+
+  
+
+    public JettyWebAppContext ()
+    throws Exception
+    {
+        super();   
+        // Turn off copyWebInf option as it is not applicable for plugin.
+        super.setCopyWebInf(false);
+    }
+    public void setContainerIncludeJarPattern(String pattern)
+    {
+        _containerIncludeJarPattern = pattern;
+    }
+    
+    public String getContainerIncludeJarPattern()
+    {
+        return _containerIncludeJarPattern;
+    }
+    
+    
+    public String getWebInfIncludeJarPattern()
+    {
+        return _webInfIncludeJarPattern;
+    }
+    public void setWebInfIncludeJarPattern(String pattern)
+    {
+        _webInfIncludeJarPattern = pattern;
+    }
+   
+   
+    public List<File> getClassPathFiles()
+    {
+        return this._classpathFiles;
+    }
+    
+    
+    public void setJettyEnvXml (String jettyEnvXml)
+    {
+        this._jettyEnvXml = jettyEnvXml;
+    }
+    
+    public String getJettyEnvXml()
+    {
+        return this._jettyEnvXml;
+    }
+
+   
+    public void setClasses(File dir)
+    {
+        _classes = dir;
+    }
+    
+    public File getClasses()
+    {
+        return _classes;
+    }
+    
+    public void setWebInfLib (List<File> jars)
+    {
+        _webInfJars.addAll(jars);
+    }
+    
+    
+    public void setTestClasses (File dir)
+    {
+        _testClasses = dir;
+    }
+    
+    
+    public File getTestClasses ()
+    {
+        return _testClasses;
+    }
+    
+    /**
+     * Ordered list of wars to overlay on top of the current project. The list
+     * may contain an overlay that represents the current project.
+     * @param overlays
+     */
+    public void setOverlays (List<Overlay> overlays)
+    {
+        _overlays = overlays;
+    }
+    
+    public List<Overlay> getOverlays()
+    {
+        return _overlays;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setBaseAppFirst(boolean value)
+    {
+        _baseAppFirst = value;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean getBaseAppFirst()
+    {
+        return _baseAppFirst;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public void setQuickStartWebDescriptor (Resource quickStartWebXml)
+    {
+        _quickStartWebXml = quickStartWebXml;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public Resource getQuickStartWebDescriptor ()
+    {
+        return _quickStartWebXml;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * This method is provided as a convenience for jetty maven plugin configuration 
+     * @param resourceBases Array of resources strings to set as a {@link ResourceCollection}. Each resource string may be a comma separated list of resources
+     * @see Resource
+     */
+    public void setResourceBases(String[] resourceBases)
+    {
+        List<String> resources = new ArrayList<String>();
+        for (String rl:resourceBases)
+        {
+            String[] rs = rl.split(" *, *");
+            for (String r:rs)
+                resources.add(r);
+        }
+        
+        setBaseResource(new ResourceCollection(resources.toArray(new String[resources.size()])));
+    }
+
+    public List<File> getWebInfLib()
+    {
+        return _webInfJars;
+    }
+    
+    public void setGenerateQuickStart (boolean quickStart)
+    {
+        _isGenerateQuickStart = quickStart;
+    }
+    
+    public boolean isGenerateQuickStart()
+    {
+        return _isGenerateQuickStart;
+    }
+    
+   
+    
+    
+    @Override
+    protected void startWebapp() throws Exception
+    {
+        if (isGenerateQuickStart())
+        {
+            if (getQuickStartWebDescriptor() == null)
+                throw new IllegalStateException ("No location to generate quickstart descriptor");
+
+            QuickStartDescriptorGenerator generator = new QuickStartDescriptorGenerator(this, _preconfigProcessor.getXML());
+            try (FileOutputStream fos = new FileOutputStream(getQuickStartWebDescriptor().getFile()))
+            {
+                generator.generateQuickStartWebXml(fos);
+            }
+        }
+        else
+            super.startWebapp();
+    }
+    
+
+    @Override
+    public void doStart () throws Exception
+    {
+        //choose if this will be a quickstart or normal start
+        if (!isGenerateQuickStart() && getQuickStartWebDescriptor() != null)
+            setConfigurations(_quickStartConfigurations);
+        else
+        {
+            setConfigurations(_defaultConfigurations);
+            if (isGenerateQuickStart())
+            {
+                _preconfigProcessor = new PreconfigureDescriptorProcessor();
+                getMetaData().addDescriptorProcessor(_preconfigProcessor);
+            }
+        }
+        
+
+        //inject configurations with config from maven plugin    
+        for (Configuration c:getConfigurations())
+        {
+            if (c instanceof EnvConfiguration && getJettyEnvXml() != null)
+                ((EnvConfiguration)c).setJettyEnvXml(Resource.toURL(new File(getJettyEnvXml())));
+            else if (c instanceof MavenQuickStartConfiguration && getQuickStartWebDescriptor() != null)
+                ((MavenQuickStartConfiguration)c).setQuickStartWebXml(getQuickStartWebDescriptor());         
+        }
+
+        //Set up the pattern that tells us where the jars are that need scanning
+
+        //Allow user to set up pattern for names of jars from the container classpath
+        //that will be scanned - note that by default NO jars are scanned
+        String tmp = _containerIncludeJarPattern;
+        if (tmp==null || "".equals(tmp))
+            tmp = (String)getAttribute(WebInfConfiguration.CONTAINER_JAR_PATTERN);           
+        
+        tmp = addPattern(tmp, DEFAULT_CONTAINER_INCLUDE_JAR_PATTERN);
+        setAttribute(WebInfConfiguration.CONTAINER_JAR_PATTERN, tmp);
+        
+        //Allow user to set up pattern of jar names from WEB-INF that will be scanned.
+        //Note that by default ALL jars considered to be in WEB-INF will be scanned - setting
+        //a pattern restricts scanning
+        if (_webInfIncludeJarPattern != null)
+            setAttribute(WebInfConfiguration.WEBINF_JAR_PATTERN, _webInfIncludeJarPattern);
+   
+        //Set up the classes dirs that comprises the equivalent of WEB-INF/classes
+        if (_testClasses != null)
+            _webInfClasses.add(_testClasses);
+        if (_classes != null)
+            _webInfClasses.add(_classes);
+        
+        // Set up the classpath
+        _classpathFiles = new ArrayList<File>();
+        _classpathFiles.addAll(_webInfClasses);
+        _classpathFiles.addAll(_webInfJars);
+
+        // Initialize map containing all jars in /WEB-INF/lib
+        _webInfJarMap.clear();
+        for (File file : _webInfJars)
+        {
+            // Return all jar files from class path
+            String fileName = file.getName();
+            if (fileName.endsWith(".jar"))
+                _webInfJarMap.put(fileName, file);
+        }
+        
+        // CHECK setShutdown(false);
+        super.doStart();
+    }
+     
+    public void doStop () throws Exception
+    { 
+        if (_classpathFiles != null)
+            _classpathFiles.clear();
+        _classpathFiles = null;
+        
+        _classes = null;
+        _testClasses = null;
+        
+        if (_webInfJarMap != null)
+            _webInfJarMap.clear();
+       
+        _webInfClasses.clear();
+        _webInfJars.clear();
+        
+        
+        
+        // CHECK setShutdown(true);
+        //just wait a little while to ensure no requests are still being processed
+        Thread.currentThread().sleep(500L);
+        super.doStop();
+        
+        //remove all listeners, servlets and filters. This is because we will re-apply
+        //any context xml file, which means they would potentially be added multiple times.
+        setEventListeners(new EventListener[0]);
+        getServletHandler().setFilters(new FilterHolder[0]);
+        getServletHandler().setFilterMappings(new FilterMapping[0]);
+        getServletHandler().setServlets(new ServletHolder[0]);
+        getServletHandler().setServletMappings(new ServletMapping[0]);
+    }
+
+    @Override
+    public Resource getResource(String uriInContext) throws MalformedURLException
+    {
+        Resource resource = null;
+        // Try to get regular resource
+        resource = super.getResource(uriInContext);
+
+        // If no regular resource exists check for access to /WEB-INF/lib or /WEB-INF/classes
+        if ((resource == null || !resource.exists()) && uriInContext != null && _classes != null)
+        {
+            String uri = URIUtil.canonicalPath(uriInContext);
+            if (uri == null)
+                return null;
+
+            try
+            {
+                // Replace /WEB-INF/classes with candidates for the classpath
+                if (uri.startsWith(WEB_INF_CLASSES_PREFIX))
+                {
+                    if (uri.equalsIgnoreCase(WEB_INF_CLASSES_PREFIX) || uri.equalsIgnoreCase(WEB_INF_CLASSES_PREFIX+"/"))
+                    {
+                        //exact match for a WEB-INF/classes, so preferentially return the resource matching the web-inf classes
+                        //rather than the test classes
+                        if (_classes != null)
+                            return Resource.newResource(_classes);
+                        else if (_testClasses != null)
+                            return Resource.newResource(_testClasses);
+                    }
+                    else
+                    {
+                        //try matching                       
+                        Resource res = null;
+                        int i=0;
+                        while (res == null && (i < _webInfClasses.size()))
+                        {
+                            String newPath = uri.replace(WEB_INF_CLASSES_PREFIX, _webInfClasses.get(i).getPath());
+                            res = Resource.newResource(newPath);
+                            if (!res.exists())
+                            {
+                                res = null; 
+                                i++;
+                            }
+                        }
+                        return res;
+                    }
+                }       
+                else if (uri.startsWith(WEB_INF_LIB_PREFIX))
+                {
+                    // Return the real jar file for all accesses to
+                    // /WEB-INF/lib/*.jar
+                    String jarName = uri.replace(WEB_INF_LIB_PREFIX, "");
+                    if (jarName.startsWith("/") || jarName.startsWith("\\")) 
+                        jarName = jarName.substring(1);
+                    if (jarName.length()==0) 
+                        return null;
+                    File jarFile = _webInfJarMap.get(jarName);
+                    if (jarFile != null)
+                        return Resource.newResource(jarFile.getPath());
+
+                    return null;
+                }
+            }
+            catch (MalformedURLException e)
+            {
+                throw e;
+            }
+            catch (IOException e)
+            {
+                LOG.ignore(e);
+            }
+        }
+        return resource;
+    }
+
+    @Override
+    public Set<String> getResourcePaths(String path)
+    {
+        // Try to get regular resource paths - this will get appropriate paths from any overlaid wars etc
+        Set<String> paths = super.getResourcePaths(path);
+        
+        if (path != null)
+        {
+            TreeSet<String> allPaths = new TreeSet<String>();
+            allPaths.addAll(paths);
+            
+            //add in the dependency jars as a virtual WEB-INF/lib entry
+            if (path.startsWith(WEB_INF_LIB_PREFIX))
+            {
+                for (String fileName : _webInfJarMap.keySet())
+                {
+                    // Return all jar files from class path
+                    allPaths.add(WEB_INF_LIB_PREFIX + "/" + fileName);
+                }
+            }
+            else if (path.startsWith(WEB_INF_CLASSES_PREFIX))
+            {
+                int i=0;
+               
+                while (i < _webInfClasses.size())
+                {
+                    String newPath = path.replace(WEB_INF_CLASSES_PREFIX, _webInfClasses.get(i).getPath());
+                    allPaths.addAll(super.getResourcePaths(newPath));
+                    i++;
+                }
+            }
+            return allPaths;
+        }
+        return paths;
+    }
+    
+    
+    public String addPattern (String s, String pattern)
+    {
+        if (s == null)
+            s = "";
+        else
+            s = s.trim();
+        
+        if (!s.contains(pattern))
+        {
+            if (s.length() != 0)
+                s = s + "|";
+            s = s + pattern;
+        }
+        
+        return s;
+    }
+}
diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/MavenQuickStartConfiguration.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/MavenQuickStartConfiguration.java
new file mode 100644
index 0000000..c0212dc
--- /dev/null
+++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/MavenQuickStartConfiguration.java
@@ -0,0 +1,132 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.maven.plugin;
+
+import java.io.File;
+import java.util.Iterator;
+
+import org.eclipse.jetty.quickstart.QuickStartConfiguration;
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.util.resource.ResourceCollection;
+import org.eclipse.jetty.webapp.WebAppClassLoader;
+import org.eclipse.jetty.webapp.WebAppContext;
+
+/**
+ * MavenQuickStartConfiguration
+ *
+ *
+ */
+public class MavenQuickStartConfiguration extends QuickStartConfiguration
+{
+    private static final Logger LOG = Log.getLogger(QuickStartConfiguration.class);
+    
+    private Resource _quickStartWebXml;
+
+
+    public void setQuickStartWebXml (Resource r)
+    {
+        _quickStartWebXml = r;
+    }
+    
+   
+    
+    @Override
+    public Resource getQuickStartWebXml(WebAppContext context) throws Exception
+    {
+        return _quickStartWebXml;
+    }
+
+    @Override
+    public void preConfigure(WebAppContext context) throws Exception
+    {
+        //check that webapp is suitable for quick start 
+        if (context.getBaseResource() == null)
+            throw new IllegalStateException ("No location for webapp");  
+
+        
+        //look for quickstart-web.xml in WEB-INF of webapp
+        Resource quickStartWebXml = getQuickStartWebXml(context);
+        LOG.debug("quickStartWebXml={}",quickStartWebXml);
+        
+        context.getMetaData().setWebXml(quickStartWebXml);
+    }
+
+
+    @Override
+    public void configure(WebAppContext context) throws Exception
+    {
+        
+       JettyWebAppContext jwac = (JettyWebAppContext)context;
+        
+        //put the classes dir and all dependencies into the classpath
+        if (jwac.getClassPathFiles() != null)
+        {
+            if (LOG.isDebugEnabled()) LOG.debug("Setting up classpath ...");
+            Iterator itor = jwac.getClassPathFiles().iterator();
+            while (itor.hasNext())
+                ((WebAppClassLoader)context.getClassLoader()).addClassPath(((File)itor.next()).getCanonicalPath());
+        }
+        
+        //Set up the quickstart environment for the context
+        super.configure(context);
+        
+        // knock out environmental maven and plexus classes from webAppContext
+        String[] existingServerClasses = context.getServerClasses();
+        String[] newServerClasses = new String[2+(existingServerClasses==null?0:existingServerClasses.length)];
+        newServerClasses[0] = "org.apache.maven.";
+        newServerClasses[1] = "org.codehaus.plexus.";
+        System.arraycopy( existingServerClasses, 0, newServerClasses, 2, existingServerClasses.length );
+        if (LOG.isDebugEnabled())
+        {
+            LOG.debug("Server classes:");
+            for (int i=0;i<newServerClasses.length;i++)
+                LOG.debug(newServerClasses[i]);
+        }
+        context.setServerClasses( newServerClasses ); 
+    }
+    
+    @Override
+    public void deconfigure(WebAppContext context) throws Exception
+    {
+        //if we're not persisting the temp dir, get rid of any overlays
+        if (!context.isPersistTempDirectory())
+        {
+            Resource originalBases = (Resource)context.getAttribute("org.eclipse.jetty.resources.originalBases");
+            String originalBaseStr = originalBases.toString();
+
+            //Iterate over all of the resource bases and ignore any that were original bases, just
+            //deleting the overlays
+            Resource res = context.getBaseResource();
+            if (res instanceof ResourceCollection)
+            {
+                for (Resource r:((ResourceCollection)res).getResources())
+                {
+                    if (originalBaseStr.contains(r.toString()))
+                        continue;
+                    IO.delete(r.getFile());
+                }
+            }
+        }
+    }
+    
+}
diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/MavenServerConnector.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/MavenServerConnector.java
new file mode 100644
index 0000000..acfa1d8
--- /dev/null
+++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/MavenServerConnector.java
@@ -0,0 +1,277 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.maven.plugin;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Future;
+
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.server.ConnectionFactory;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+
+
+
+/**
+ * MavenServerConnector
+ *
+ * As the ServerConnector class does not have a no-arg constructor, and moreover requires
+ * the server instance passed in to all its constructors, it cannot
+ * be referenced in the pom.xml. This class wraps a ServerConnector, delaying setting the
+ * server instance. Only a few of the setters from the ServerConnector class are supported.
+ */
+public class MavenServerConnector extends AbstractLifeCycle implements Connector
+{
+    public static final int DEFAULT_PORT = 8080;
+    public static final String DEFAULT_PORT_STR = String.valueOf(DEFAULT_PORT);
+    public static final int DEFAULT_MAX_IDLE_TIME = 30000;
+    
+    private Server server;
+    private ServerConnector delegate;
+    private String host;
+    private String name;
+    private int port;
+    private long idleTimeout;
+    private int lingerTime;
+    
+    
+    public MavenServerConnector()
+    {
+    }
+    
+    public void setServer(Server server)
+    {
+        this.server = server;
+    }
+
+    public void setHost(String host)
+    {
+        this.host = host;
+    }
+   
+    public String getHost()
+    {
+        return this.host;
+    }
+
+    public void setPort(int port)
+    {
+        this.port = port;
+    }
+    
+    public int getPort ()
+    {
+        return this.port;
+    }
+
+    public void setName (String name)
+    {
+        this.name = name;
+    }
+    
+    public void setIdleTimeout(long idleTimeout)
+    {
+        this.idleTimeout = idleTimeout;
+    }
+    
+    public void setSoLingerTime(int lingerTime)
+    {
+        this.lingerTime = lingerTime;
+    }
+    
+    @Override
+    protected void doStart() throws Exception
+    {
+
+        if (this.server == null)
+            throw new IllegalStateException("Server not set for MavenServerConnector");
+
+        this.delegate = new ServerConnector(this.server);
+        this.delegate.setName(this.name);
+        this.delegate.setPort(this.port);
+        this.delegate.setHost(this.host);
+        this.delegate.setIdleTimeout(idleTimeout);
+        this.delegate.setSoLingerTime(lingerTime);
+        this.delegate.start();
+
+        super.doStart();
+    }
+
+    @Override
+    protected void doStop() throws Exception
+    {
+        this.delegate.stop();
+        super.doStop();
+        this.delegate = null;
+    }
+
+    /** 
+     * @see org.eclipse.jetty.util.component.Graceful#shutdown()
+     */
+    @Override
+    public Future<Void> shutdown()
+    {
+        checkDelegate();
+        return this.delegate.shutdown();
+    }
+
+    /** 
+     * @see org.eclipse.jetty.server.Connector#getServer()
+     */
+    @Override
+    public Server getServer()
+    {
+        return this.server;
+    }
+
+    /** 
+     * @see org.eclipse.jetty.server.Connector#getExecutor()
+     */
+    @Override
+    public Executor getExecutor()
+    {
+        checkDelegate();
+        return this.delegate.getExecutor();
+    }
+
+    /** 
+     * @see org.eclipse.jetty.server.Connector#getScheduler()
+     */
+    @Override
+    public Scheduler getScheduler()
+    {
+        checkDelegate();
+        return this.delegate.getScheduler();
+    }
+
+    /** 
+     * @see org.eclipse.jetty.server.Connector#getByteBufferPool()
+     */
+    @Override
+    public ByteBufferPool getByteBufferPool()
+    {
+        checkDelegate();
+        return this.delegate.getByteBufferPool();
+    }
+
+    /** 
+     * @see org.eclipse.jetty.server.Connector#getConnectionFactory(java.lang.String)
+     */
+    @Override
+    public ConnectionFactory getConnectionFactory(String nextProtocol)
+    {
+        checkDelegate();
+        return this.delegate.getConnectionFactory(nextProtocol);
+    }
+
+    /** 
+     * @see org.eclipse.jetty.server.Connector#getConnectionFactory(java.lang.Class)
+     */
+    @Override
+    public <T> T getConnectionFactory(Class<T> factoryType)
+    {
+        checkDelegate();
+        return this.delegate.getConnectionFactory(factoryType);
+    }
+
+    /** 
+     * @see org.eclipse.jetty.server.Connector#getDefaultConnectionFactory()
+     */
+    @Override
+    public ConnectionFactory getDefaultConnectionFactory()
+    {
+        checkDelegate();
+        return this.delegate.getDefaultConnectionFactory();
+    }
+
+    /** 
+     * @see org.eclipse.jetty.server.Connector#getConnectionFactories()
+     */
+    @Override
+    public Collection<ConnectionFactory> getConnectionFactories()
+    {
+        checkDelegate();
+        return this.delegate.getConnectionFactories();
+    }
+
+    /** 
+     * @see org.eclipse.jetty.server.Connector#getProtocols()
+     */
+    @Override
+    public List<String> getProtocols()
+    {
+        checkDelegate();
+        return this.delegate.getProtocols();
+    }
+
+    /** 
+     * @see org.eclipse.jetty.server.Connector#getIdleTimeout()
+     */
+    @Override
+    @ManagedAttribute("maximum time a connection can be idle before being closed (in ms)")
+    public long getIdleTimeout()
+    {
+        checkDelegate();
+        return this.delegate.getIdleTimeout();
+    }
+
+    /** 
+     * @see org.eclipse.jetty.server.Connector#getTransport()
+     */
+    @Override
+    public Object getTransport()
+    {
+        checkDelegate();
+        return this.delegate.getTransport();
+    }
+
+    /** 
+     * @see org.eclipse.jetty.server.Connector#getConnectedEndPoints()
+     */
+    @Override
+    public Collection<EndPoint> getConnectedEndPoints()
+    {
+        checkDelegate();
+        return this.delegate.getConnectedEndPoints();
+    }
+
+    /** 
+     * @see org.eclipse.jetty.server.Connector#getName()
+     */
+    @Override
+    public String getName()
+    {
+        return this.name;
+    }
+    
+    private void checkDelegate() throws IllegalStateException
+    {
+        if (this.delegate == null)
+            throw new IllegalStateException ("MavenServerConnector delegate not ready");
+    }
+}
diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/MavenWebInfConfiguration.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/MavenWebInfConfiguration.java
new file mode 100644
index 0000000..909402b
--- /dev/null
+++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/MavenWebInfConfiguration.java
@@ -0,0 +1,302 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.maven.plugin;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.util.resource.ResourceCollection;
+import org.eclipse.jetty.webapp.WebAppClassLoader;
+import org.eclipse.jetty.webapp.WebAppContext;
+import org.eclipse.jetty.webapp.WebInfConfiguration;
+
+
+
+/**
+ * MavenWebInfConfiguration
+ * 
+ * WebInfConfiguration to take account of overlaid wars expressed as project dependencies and
+ * potentiall configured via the maven-war-plugin.
+ *
+ */
+public class MavenWebInfConfiguration extends WebInfConfiguration
+{
+    private static final Logger LOG = Log.getLogger(MavenWebInfConfiguration.class);
+
+    public static final String RESOURCE_BASES_POST_OVERLAY = "org.eclipse.jetty.resource.postOverlay";
+    protected static int COUNTER = 0; 
+    protected Resource _originalResourceBase;
+    protected List<Resource>  _unpackedOverlayResources;
+  
+    
+    
+    
+    /** 
+     * @see org.eclipse.jetty.webapp.WebInfConfiguration#configure(org.eclipse.jetty.webapp.WebAppContext)
+     */
+    public void configure(WebAppContext context) throws Exception
+    {
+        JettyWebAppContext jwac = (JettyWebAppContext)context;
+        
+        //put the classes dir and all dependencies into the classpath
+        if (jwac.getClassPathFiles() != null)
+        {
+            if (LOG.isDebugEnabled()) LOG.debug("Setting up classpath ...");
+            Iterator itor = jwac.getClassPathFiles().iterator();
+            while (itor.hasNext())
+                ((WebAppClassLoader)context.getClassLoader()).addClassPath(((File)itor.next()).getCanonicalPath());
+        }
+        
+        super.configure(context);
+        
+        // knock out environmental maven and plexus classes from webAppContext
+        String[] existingServerClasses = context.getServerClasses();
+        String[] newServerClasses = new String[2+(existingServerClasses==null?0:existingServerClasses.length)];
+        newServerClasses[0] = "org.apache.maven.";
+        newServerClasses[1] = "org.codehaus.plexus.";
+        System.arraycopy( existingServerClasses, 0, newServerClasses, 2, existingServerClasses.length );
+        if (LOG.isDebugEnabled())
+        {
+            LOG.debug("Server classes:");
+            for (int i=0;i<newServerClasses.length;i++)
+                LOG.debug(newServerClasses[i]);
+        }
+        context.setServerClasses( newServerClasses ); 
+    }
+
+    
+    
+
+    /** 
+     * @see org.eclipse.jetty.webapp.WebInfConfiguration#preConfigure(org.eclipse.jetty.webapp.WebAppContext)
+     */
+    public void preConfigure(WebAppContext context) throws Exception
+    {
+        super.preConfigure(context);
+
+    }
+    
+    
+    
+    
+    /** 
+     * @see org.eclipse.jetty.webapp.AbstractConfiguration#postConfigure(org.eclipse.jetty.webapp.WebAppContext)
+     */
+    public void postConfigure(WebAppContext context) throws Exception
+    {
+        super.postConfigure(context);
+    }
+
+
+    
+    
+    /** 
+     * @see org.eclipse.jetty.webapp.WebInfConfiguration#deconfigure(org.eclipse.jetty.webapp.WebAppContext)
+     */
+    public void deconfigure(WebAppContext context) throws Exception
+    {   
+        super.deconfigure(context);
+        //restore whatever the base resource was before we might have included overlaid wars
+        context.setBaseResource(_originalResourceBase);
+        //undo the setting of the overlayed resources
+        context.removeAttribute(RESOURCE_BASES_POST_OVERLAY);
+    }
+
+    
+    
+
+    /** 
+     * @see org.eclipse.jetty.webapp.WebInfConfiguration#unpack(org.eclipse.jetty.webapp.WebAppContext)
+     */
+    @Override
+    public void unpack(WebAppContext context) throws IOException
+    {
+        //Unpack and find base resource as normal
+        super.unpack(context);
+        
+        //Get the base resource for the "virtual" webapp
+        _originalResourceBase = context.getBaseResource();
+
+        JettyWebAppContext jwac = (JettyWebAppContext)context;
+
+        //determine sequencing of overlays
+        _unpackedOverlayResources = new ArrayList<Resource>();
+
+       
+
+        if (jwac.getOverlays() != null && !jwac.getOverlays().isEmpty())
+        {
+            List<Resource> resourceBaseCollection = new ArrayList<Resource>();
+
+            for (Overlay o:jwac.getOverlays())
+            {
+                //can refer to the current project in list of overlays for ordering purposes
+                if (o.getConfig() != null && o.getConfig().isCurrentProject() && _originalResourceBase.exists())
+                {
+                    resourceBaseCollection.add(_originalResourceBase); 
+                    LOG.debug("Adding virtual project to resource base list");
+                    continue;
+                }
+
+                Resource unpacked = unpackOverlay(jwac,o);
+                _unpackedOverlayResources.add(unpacked); //remember the unpacked overlays for later so we can delete the tmp files
+                resourceBaseCollection.add(unpacked); //add in the selectively unpacked overlay in the correct order to the webapps resource base
+                LOG.debug("Adding "+unpacked+" to resource base list");
+            }
+
+            if (!resourceBaseCollection.contains(_originalResourceBase) && _originalResourceBase.exists())
+            {
+                if (jwac.getBaseAppFirst())
+                {
+                    LOG.debug("Adding virtual project first in resource base list");
+                    resourceBaseCollection.add(0, _originalResourceBase);
+                }
+                else
+                {
+                    LOG.debug("Adding virtual project last in resource base list");
+                    resourceBaseCollection.add(_originalResourceBase);
+                }
+            }
+            jwac.setBaseResource(new ResourceCollection(resourceBaseCollection.toArray(new Resource[resourceBaseCollection.size()])));
+        }
+        
+        jwac.setAttribute(RESOURCE_BASES_POST_OVERLAY, jwac.getBaseResource());
+    }
+
+
+    /**
+     * Get the jars to examine from the files from which we have
+     * synthesized the classpath. Note that the classpath is not
+     * set at this point, so we cannot get them from the classpath.
+     * @param context
+     * @return the list of jars found
+     */
+    @Override
+    protected List<Resource> findJars (WebAppContext context)
+    throws Exception
+    {
+        List<Resource> list = new ArrayList<Resource>();
+        JettyWebAppContext jwac = (JettyWebAppContext)context;
+        if (jwac.getClassPathFiles() != null)
+        {
+            for (File f: jwac.getClassPathFiles())
+            {
+                if (f.getName().toLowerCase(Locale.ENGLISH).endsWith(".jar"))
+                {
+                    try
+                    {
+                        list.add(Resource.newResource(f.toURI()));
+                    }
+                    catch (Exception e)
+                    {
+                        LOG.warn("Bad url ", e);
+                    }
+                }
+            }
+        }
+
+        List<Resource> superList = super.findJars(context);
+        if (superList != null)
+            list.addAll(superList);
+        return list;
+    }
+    
+    
+    
+    
+
+    /** 
+     * Add in the classes dirs from test/classes and target/classes
+     * @see org.eclipse.jetty.webapp.WebInfConfiguration#findClassDirs(org.eclipse.jetty.webapp.WebAppContext)
+     */
+    @Override
+    protected List<Resource> findClassDirs(WebAppContext context) throws Exception
+    {
+        List<Resource> list = new ArrayList<Resource>();
+        
+        JettyWebAppContext jwac = (JettyWebAppContext)context;
+        if (jwac.getClassPathFiles() != null)
+        {
+            for (File f: jwac.getClassPathFiles())
+            {
+                if (f.exists() && f.isDirectory())
+                {
+                    try
+                    {
+                        list.add(Resource.newResource(f.toURI()));
+                    }
+                    catch (Exception e)
+                    {
+                        LOG.warn("Bad url ", e);
+                    }
+                }
+            }
+        }
+        
+        List<Resource> classesDirs = super.findClassDirs(context);
+        if (classesDirs != null)
+            list.addAll(classesDirs);
+        return list;
+    }
+
+
+
+
+
+    protected  Resource unpackOverlay (WebAppContext context, Overlay overlay)
+    throws IOException
+    {
+        LOG.debug("Unpacking overlay: " + overlay);
+        
+        if (overlay.getResource() == null)
+            return null; //nothing to unpack
+   
+        //Get the name of the overlayed war and unpack it to a dir of the
+        //same name in the temporary directory
+        String name = overlay.getResource().getName();
+        if (name.endsWith("!/"))
+            name = name.substring(0,name.length()-2);
+        int i = name.lastIndexOf('/');
+        if (i>0)
+            name = name.substring(i+1,name.length());
+        name = name.replace('.', '_');
+        name = name+(++COUNTER); //add some digits to ensure uniqueness
+        File dir = new File(context.getTempDirectory(), name); 
+        
+        //if specified targetPath, unpack to that subdir instead
+        File unpackDir = dir;
+        if (overlay.getConfig() != null && overlay.getConfig().getTargetPath() != null)
+            unpackDir = new File (dir, overlay.getConfig().getTargetPath());
+        
+        overlay.getResource().copyTo(unpackDir);
+        //use top level of unpacked content
+        Resource unpackedOverlay = Resource.newResource(dir.getCanonicalPath());
+        
+        LOG.debug("Unpacked overlay: "+overlay+" to "+unpackedOverlay);
+        return  unpackedOverlay;
+    }
+}
diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/Overlay.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/Overlay.java
new file mode 100644
index 0000000..f04ac57
--- /dev/null
+++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/Overlay.java
@@ -0,0 +1,74 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.maven.plugin;
+
+import org.eclipse.jetty.util.resource.Resource;
+
+/**
+ * Overlay
+ *
+ *
+ */
+public class Overlay
+{
+    private OverlayConfig _config;
+    private Resource _resource;
+    
+    public Overlay (OverlayConfig config, Resource resource)
+    {
+        _config = config;
+        _resource = resource;
+    }
+
+    public Overlay (OverlayConfig config)
+    {
+        _config = config;
+    }
+    
+    
+    public void setResource (Resource r)
+    {
+        _resource = r;
+    }
+    
+    public Resource getResource ()
+    {
+        return _resource;
+    }
+    
+    public OverlayConfig getConfig ()
+    {
+        return _config;
+    }
+    
+    public String toString()
+    {
+        StringBuffer strbuff = new StringBuffer();
+        if (_resource != null)
+            strbuff.append(_resource);
+        if (_config != null)
+        {
+            strbuff.append(" [");
+            strbuff.append(_config);
+            strbuff.append("]");
+        }
+        return strbuff.toString();
+    }
+}
diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/OverlayConfig.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/OverlayConfig.java
new file mode 100644
index 0000000..287ba13
--- /dev/null
+++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/OverlayConfig.java
@@ -0,0 +1,341 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.maven.plugin;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+
+import org.codehaus.plexus.util.xml.Xpp3Dom;
+
+/**
+ * OverlayConfig
+ *
+ *
+ */
+public class OverlayConfig
+{
+    private String targetPath;
+    private String groupId;
+    private String artifactId;
+    private String classifier;
+    private List<String> includes;
+    private List<String> excludes;
+    private boolean skip;
+    private boolean filtered;
+    
+    public OverlayConfig() {}
+    
+    public OverlayConfig(String fmt, List<String> defaultIncludes, List<String> defaultExcludes)
+    {
+        if (fmt == null)
+            return;
+        String[] atoms = fmt.split(",");
+        for (int i=0;i<atoms.length;i++)
+        {
+            String s = atoms[i].trim();
+            switch (i)
+            {
+                case 0: 
+                {
+                    if (!"".equals(s))
+                        groupId = s;
+                    break;
+                }
+                case 1:
+                {
+                    if (!"".equals(s))
+                        artifactId = s;
+                    break;
+                }
+                case 2:
+                {
+                    if (!"".equals(s))
+                        classifier = s;
+                    break;
+                }
+                case 3: 
+                { 
+                    if (!"".equals(s))
+                        targetPath = s;
+                    break;
+                }
+                case 4:
+                {
+                    if ("".equals(s))
+                        skip = false;
+                    else
+                        skip = Boolean.valueOf(s);
+                    break;
+                }
+                case 5:
+                {
+                    if ("".equals(s))
+                        filtered = false;
+                    else
+                        filtered = Boolean.valueOf(s);
+                    break;
+                }
+                case 6:
+                {
+                    if ("".equals(s))
+                        break;
+                    String[] incs = s.split(";");
+                    if (incs.length > 0)
+                        includes = Arrays.asList(incs);
+                    break;
+                }
+                case 7:
+                { 
+                    if ("".equals(s))
+                        break;
+                    String[] exs = s.split(";");
+                    if (exs.length > 0)
+                        excludes = Arrays.asList(exs);
+                    break;
+                }
+            }
+        }
+    }
+
+    public OverlayConfig(Xpp3Dom root, List<String> defaultIncludes, List<String> defaultExcludes)
+    {
+        Xpp3Dom node = root.getChild("groupId");
+        setGroupId(node==null?null:node.getValue());
+        
+        node = root.getChild("artifactId");
+        setArtifactId(node==null?null:node.getValue());   
+        
+        node = root.getChild("classifier");
+        setClassifier(node==null?null:node.getValue());
+       
+        node = root.getChild("targetPath");
+        setTargetPath(node==null?null:node.getValue());
+        
+        node = root.getChild("skip");
+        setSkip(node==null?false:Boolean.valueOf(node.getValue()));
+
+        node = root.getChild("filtered");
+        setFiltered(node==null?false:Boolean.valueOf(node.getValue()));
+
+        node = root.getChild("includes");
+        List<String> includes = null;
+        if (node != null && node.getChildCount() > 0)
+        {
+            Xpp3Dom[] list = node.getChildren("include");
+            for (int j=0; list != null && j < list.length;j++)
+            {
+                if (includes == null)
+                    includes = new ArrayList<String>();
+                includes.add(list[j].getValue());
+            }
+        }
+        if (includes == null && defaultIncludes != null)
+        {
+            includes = new ArrayList<String>();
+            includes.addAll(defaultIncludes);
+        }
+        setIncludes(includes);
+        
+       
+        node = root.getChild("excludes");
+        List<String> excludes = null;
+        if (node != null && node.getChildCount() > 0)
+        {
+            Xpp3Dom[] list = node.getChildren("exclude");
+            for (int j=0; list != null && j < list.length;j++)
+            {
+                if (excludes == null)
+                    excludes = new ArrayList<String>();
+                excludes.add(list[j].getValue());
+            }
+        }
+        if (excludes == null && defaultExcludes != null)
+        {
+            excludes = new ArrayList<String>();
+            excludes.addAll(defaultExcludes);
+        }
+        setExcludes(excludes);
+    }
+    
+    public String getTargetPath()
+    {
+        return targetPath;
+    }
+
+    public void setTargetPath(String targetPath)
+    {
+        this.targetPath = targetPath;
+    }
+
+    public String getGroupId()
+    {
+        return groupId;
+    }
+
+    public void setGroupId(String groupId)
+    {
+        this.groupId = groupId;
+    }
+
+    public String getArtifactId()
+    {
+        return artifactId;
+    }
+
+    public void setArtifactId(String artifactId)
+    {
+        this.artifactId = artifactId;
+    }
+
+    public String getClassifier()
+    {
+        return classifier;
+    }
+
+    public void setClassifier(String classifier)
+    {
+        this.classifier = classifier;
+    }
+
+    public List<String> getIncludes()
+    {
+        return includes;
+    }
+
+    public void setIncludes(List<String> includes)
+    {
+        this.includes = includes;
+    }
+
+    public List<String> getExcludes()
+    {
+        return excludes;
+    }
+
+    public void setExcludes(List<String> excludes)
+    {
+        this.excludes = excludes;
+    }
+
+    public boolean isSkip()
+    {
+        return skip;
+    }
+
+    public void setSkip(boolean skip)
+    {
+        this.skip = skip;
+    }
+
+    public boolean isFiltered()
+    {
+        return filtered;
+    }
+
+    public void setFiltered(boolean filtered)
+    {
+        this.filtered = filtered;
+    }
+    
+    public boolean isCurrentProject()
+    {
+        if (this.groupId == null && this.artifactId == null)
+            return true;
+        return false;
+    }
+    
+
+    /**
+     * Check if this overlay configuration matches an Artifact's info
+     * 
+     * @param gid Artifact groupId
+     * @param aid Artifact artifactId
+     * @param cls Artifact classifier
+     * @return
+     */
+    public boolean matchesArtifact (String gid, String aid, String cls)
+    {
+        if (((getGroupId() == null && gid == null) || (getGroupId() != null && getGroupId().equals(gid)))
+           &&((getArtifactId() == null && aid == null) || (getArtifactId() != null && getArtifactId().equals(aid)))
+           &&((getClassifier() == null) || (getClassifier().equals(cls))))
+            return true;
+
+        return false;
+    }
+    
+    /**
+     * Check if this overlay configuration matches an Artifact's info
+     * 
+     * @param gid
+     * @param aid
+     * @return
+     */
+    public boolean matchesArtifact (String gid, String aid)
+    {
+        if (((getGroupId() == null && gid == null) || (getGroupId() != null && getGroupId().equals(gid)))
+           &&((getArtifactId() == null && aid == null) || (getArtifactId() != null && getArtifactId().equals(aid))))
+            return true;
+
+        return false;
+    }
+    
+    
+    
+    
+    public String toString()
+    {
+        StringBuffer strbuff = new StringBuffer();
+        strbuff.append((groupId != null ? groupId : "")+",");
+        strbuff.append((artifactId != null ? artifactId : "")+",");
+        strbuff.append((classifier != null ? classifier : "")+",");
+        strbuff.append((targetPath != null ? targetPath : "")+",");
+        strbuff.append(""+skip+",");
+        strbuff.append(""+filtered+",");
+     
+        if (includes != null)
+        {
+            Iterator<String> itor = includes.iterator();
+            while (itor.hasNext())
+            {
+                strbuff.append(itor.next());
+                if (itor.hasNext())
+                    strbuff.append(";");
+            }
+        }
+        
+        strbuff.append(", ");
+
+        if (excludes != null)
+        {
+            Iterator<String> itor = excludes.iterator();
+            while (itor.hasNext())
+            {
+                strbuff.append(itor.next());
+                if (itor.hasNext())
+                    strbuff.append(";");
+            }
+        }
+  
+        return strbuff.toString();
+    }
+}
+
diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/PluginLog.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/PluginLog.java
new file mode 100644
index 0000000..f527a90
--- /dev/null
+++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/PluginLog.java
@@ -0,0 +1,44 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.maven.plugin;
+
+import org.apache.maven.plugin.logging.Log;
+
+/**
+ * PluginLog
+ * 
+ * Convenience class to provide access to the plugin
+ * Log for non-mojo classes.
+ *
+ */
+public class PluginLog
+{
+    private static Log log = null;
+    
+    public static void setLog(Log l)
+    {
+        log = l;
+    }
+    
+    public static Log getLog()
+    {
+        return log;
+    }
+
+}
diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/ScanTargetPattern.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/ScanTargetPattern.java
new file mode 100644
index 0000000..d6c4d51
--- /dev/null
+++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/ScanTargetPattern.java
@@ -0,0 +1,88 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.maven.plugin;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * ScanTargetPattern
+ *
+ * Utility class to provide the ability for the mvn jetty:run 
+ * mojo to be able to specify filesets of extra files to 
+ * regularly scan for changes in order to redeploy the webapp.
+ * 
+ * For example:
+ * 
+ * <scanTargetPattern>
+ *   <directory>/some/place</directory>
+ *   <includes>
+ *     <include>some ant pattern here </include>
+ *     <include>some ant pattern here </include> 
+ *   </includes>
+ *   <excludes>
+ *     <exclude>some ant pattern here </exclude>
+ *     <exclude>some ant pattern here </exclude>
+ *   </excludes>
+ * </scanTargetPattern>
+ */
+public class ScanTargetPattern
+{
+    private File _directory;
+    private List _includes = Collections.EMPTY_LIST;
+    private List _excludes = Collections.EMPTY_LIST;
+
+    /**
+     * @return the _directory
+     */
+    public File getDirectory()
+    {
+        return _directory;
+    }
+
+    /**
+     * @param directory the directory to set
+     */
+    public void setDirectory(File directory)
+    {
+        this._directory = directory;
+    }
+    
+    public void setIncludes (List includes)
+    {
+        _includes= includes;
+    }
+    
+    public void setExcludes(List excludes)
+    {
+        _excludes = excludes;
+    }
+    
+    public List getIncludes()
+    {
+        return _includes;
+    }
+    
+    public List getExcludes()
+    {
+        return _excludes;
+    }
+
+}
diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/SelectiveJarResource.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/SelectiveJarResource.java
new file mode 100644
index 0000000..11df37a
--- /dev/null
+++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/SelectiveJarResource.java
@@ -0,0 +1,232 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.maven.plugin;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URL;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.jar.JarEntry;
+import java.util.jar.JarInputStream;
+import java.util.jar.Manifest;
+
+import org.codehaus.plexus.util.SelectorUtils;
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.JarResource;
+
+
+
+/**
+ * SelectiveJarResource
+ *
+ * Selectively copies resources from a jar file based on includes/excludes.
+ * 
+ */
+public class SelectiveJarResource extends JarResource
+{  
+    private static final Logger LOG = Log.getLogger(SelectiveJarResource.class);
+    public static final List<String> DEFAULT_INCLUDES = Arrays.asList(new String[]{"**"});// No includes supplied, so set it to 'matches all'
+    public static final List<String> DEFAULT_EXCLUDES = Collections.emptyList(); //No includes, set to no exclusions
+
+
+    List<String> _includes = null;
+    List<String> _excludes = null;
+    boolean _caseSensitive = false;
+    
+
+    /**
+     * @param url
+     */
+    public SelectiveJarResource(URL url)
+    {
+        super(url);
+    }
+  
+    /**
+     * @param url
+     * @param useCaches
+     */
+    public SelectiveJarResource(URL url, boolean useCaches)
+    {
+        super(url, useCaches);
+    }
+    
+    
+    public void setCaseSensitive (boolean caseSensitive)
+    {
+        _caseSensitive = caseSensitive;
+    }
+    
+    public void setIncludes (List<String> patterns)
+    {
+        _includes = patterns;
+    }
+    
+    
+    public void setExcludes (List<String> patterns)
+    {
+        _excludes = patterns;
+    }
+    
+    
+    protected boolean isIncluded (String name)
+    {    
+        for (String include:_includes)
+        {
+            if (SelectorUtils.matchPath(include, name, _caseSensitive))
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+    
+    protected boolean isExcluded (String name)
+    {
+        for (String exclude:_excludes)
+        {
+            if (SelectorUtils.matchPath (exclude, name, _caseSensitive))
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+    
+    
+  
+
+    /** 
+     * @see org.eclipse.jetty.util.resource.JarResource#copyTo(java.io.File)
+     */
+    @Override
+    public void copyTo(File directory) throws IOException
+    {
+        if (_includes == null)
+            _includes = DEFAULT_INCLUDES;
+        if (_excludes == null)
+            _excludes = DEFAULT_EXCLUDES;
+        
+        //Copy contents of the jar file to the given directory, 
+        //using the includes and excludes patterns to control which
+        //parts of the jar file are copied
+        if (!exists())
+            return;
+        
+        String urlString = this.getURL().toExternalForm().trim();
+        int endOfJarUrl = urlString.indexOf("!/");
+        int startOfJarUrl = (endOfJarUrl >= 0?4:0);
+        
+        if (endOfJarUrl < 0)
+            throw new IOException("Not a valid jar url: "+urlString);
+        
+        URL jarFileURL = new URL(urlString.substring(startOfJarUrl, endOfJarUrl));
+     
+        try (InputStream is = jarFileURL.openConnection().getInputStream();
+                JarInputStream jin = new JarInputStream(is))
+        {
+            JarEntry entry;
+
+            while((entry=jin.getNextJarEntry())!=null)
+            {
+                String entryName = entry.getName();
+
+                LOG.debug("Looking at "+entryName);
+                String dotCheck = entryName.replace('\\', '/');
+                dotCheck = URIUtil.canonicalPath(dotCheck);
+                if (dotCheck == null)
+                {
+                    LOG.info("Invalid entry: "+entryName);
+                    continue;
+                }
+
+                File file=new File(directory,entryName);
+
+                if (entry.isDirectory())
+                {
+                    if (isIncluded(entryName))
+                    {
+                        if (!isExcluded(entryName))
+                        {
+                            // Make directory
+                            if (!file.exists())
+                                file.mkdirs();
+                        }
+                        else
+                            LOG.debug("{} dir is excluded", entryName);
+                    }
+                    else
+                        LOG.debug("{} dir is NOT included", entryName);
+                }
+                else
+                {
+                    //entry is a file, is it included?
+                    if (isIncluded(entryName))
+                    {
+                        if (!isExcluded(entryName))
+                        {
+                            // make directory (some jars don't list dirs)
+                            File dir = new File(file.getParent());
+                            if (!dir.exists())
+                                dir.mkdirs();
+
+                            // Make file
+                            try (OutputStream fout = new FileOutputStream(file))
+                            {
+                                IO.copy(jin,fout);
+                            }
+
+                            // touch the file.
+                            if (entry.getTime()>=0)
+                                file.setLastModified(entry.getTime());
+                        }
+                        else
+                            LOG.debug("{} file is excluded", entryName);
+                    }
+                    else
+                        LOG.debug("{} file is NOT included", entryName);
+                }
+            }
+
+            Manifest manifest = jin.getManifest();
+            if (manifest != null)
+            {
+                if (isIncluded("META-INF") && !isExcluded("META-INF"))
+                {
+                    File metaInf = new File (directory, "META-INF");
+                    metaInf.mkdir();
+                    File f = new File(metaInf, "MANIFEST.MF");
+                    try (OutputStream fout = new FileOutputStream(f))
+                    {
+                        manifest.write(fout);
+                    }
+                }
+            }
+        }
+    }
+    
+}
diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/Starter.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/Starter.java
new file mode 100644
index 0000000..7240449
--- /dev/null
+++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/Starter.java
@@ -0,0 +1,529 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.maven.plugin;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Properties;
+import java.util.Set;
+import java.util.TreeMap;
+
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.ShutdownMonitor;
+import org.eclipse.jetty.server.handler.HandlerCollection;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.util.resource.ResourceCollection;
+import org.eclipse.jetty.xml.XmlConfiguration;
+
+
+
+/**
+ * Starter
+ * 
+ * Class which is exec'ed to create a new jetty process. Used by the JettyRunForked mojo.
+ *
+ */
+public class Starter
+{ 
+    public static final String PORT_SYSPROPERTY = "jetty.port";
+    private static final Logger LOG = Log.getLogger(Starter.class);
+
+    private List<File> jettyXmls; // list of jetty.xml config files to apply - Mandatory
+    private File contextXml; //name of context xml file to configure the webapp - Mandatory
+
+    private JettyServer server = new JettyServer();
+    private JettyWebAppContext webApp;
+
+    
+    private int stopPort=0;
+    private String stopKey=null;
+    private Properties props;
+    private String token;
+
+    
+    /**
+     * Artifact
+     *
+     * A mock maven Artifact class as the maven jars are not put onto the classpath for the
+     * execution of this class.
+     *
+     */
+    public class Artifact
+    {
+        public String gid;
+        public String aid;
+        public String path;
+        public Resource resource;
+        
+        public Artifact (String csv)
+        {
+            if (csv != null && !"".equals(csv))
+            {
+                String[] atoms = csv.split(",");
+                if (atoms.length >= 3)
+                {
+                    gid = atoms[0].trim();
+                    aid = atoms[1].trim();
+                    path = atoms[2].trim();
+                }
+            }
+        }
+        
+        public Artifact (String gid, String aid, String path)
+        {
+            this.gid = gid;
+            this.aid = aid;
+            this.path = path;
+        }
+        
+        public boolean equals(Object o)
+        {
+            if (!(o instanceof Artifact))
+                return false;
+            
+            Artifact ao = (Artifact)o;
+            return (((gid == null && ao.gid == null) || (gid != null && gid.equals(ao.gid)))
+                    &&  ((aid == null && ao.aid == null) || (aid != null && aid.equals(ao.aid))));      
+        }
+    }
+    
+    
+    
+    /**
+     * @throws Exception
+     */
+    public void configureJetty () throws Exception
+    {
+        LOG.debug("Starting Jetty Server ...");
+
+        //apply any configs from jetty.xml files first 
+        applyJettyXml ();
+
+        // if the user hasn't configured a connector in the jetty.xml
+        //then use a default
+        Connector[] connectors = this.server.getConnectors();
+        if (connectors == null|| connectors.length == 0)
+        {
+            //if a SystemProperty -Djetty.port=<portnum> has been supplied, use that as the default port
+            MavenServerConnector httpConnector = new MavenServerConnector();
+            httpConnector.setServer(this.server);
+            String tmp = System.getProperty(PORT_SYSPROPERTY, MavenServerConnector.DEFAULT_PORT_STR);
+            httpConnector.setPort(Integer.parseInt(tmp.trim()));
+            connectors = new Connector[] {httpConnector};
+            this.server.setConnectors(connectors);
+        }
+
+        //check that everything got configured, and if not, make the handlers
+        HandlerCollection handlers = (HandlerCollection) server.getChildHandlerByClass(HandlerCollection.class);
+        if (handlers == null)
+        {
+            handlers = new HandlerCollection();
+            server.setHandler(handlers);
+        }
+
+        //check if contexts already configured, create if not
+        this.server.configureHandlers();
+
+        webApp = new JettyWebAppContext();
+        
+        //configure webapp from properties file describing unassembled webapp
+        configureWebApp();
+        
+        //make it a quickstart if the quickstart-web.xml file exists
+        if (webApp.getTempDirectory() != null)
+        {
+            File qs = new File (webApp.getTempDirectory(), "quickstart-web.xml");
+            if (qs.exists() && qs.isFile())
+                webApp.setQuickStartWebDescriptor(Resource.newResource(qs));
+        }
+        
+        //set up the webapp from the context xml file provided
+        //NOTE: just like jetty:run mojo this means that the context file can
+        //potentially override settings made in the pom. Ideally, we'd like
+        //the pom to override the context xml file, but as the other mojos all
+        //configure a WebAppContext in the pom (the <webApp> element), it is 
+        //already configured by the time the context xml file is applied.
+        if (contextXml != null)
+        {
+            XmlConfiguration xmlConfiguration = new XmlConfiguration(Resource.toURL(contextXml));
+            xmlConfiguration.getIdMap().put("Server",server);
+            xmlConfiguration.configure(webApp);
+        }
+
+        this.server.addWebApplication(webApp);
+
+        if(stopPort>0 && stopKey!=null)
+        {
+            ShutdownMonitor monitor = ShutdownMonitor.getInstance();
+            monitor.setPort(stopPort);
+            monitor.setKey(stopKey);
+            monitor.setExitVm(true);
+        }
+    }
+    
+    
+    /**
+     * @throws Exception
+     */
+    public void configureWebApp ()
+    throws Exception
+    {
+        if (props == null)
+            return;
+        
+        //apply a properties file that defines the things that we configure in the jetty:run plugin:
+        // - the context path
+        String str = (String)props.get("context.path");
+        if (str != null)
+            webApp.setContextPath(str);
+        
+        
+        // - web.xml
+        str = (String)props.get("web.xml");
+        if (str != null)
+            webApp.setDescriptor(str); 
+        
+        str = (String)props.get("quickstart.web.xml");
+        if (str != null)
+            webApp.setQuickStartWebDescriptor(Resource.newResource(new File(str)));
+        
+        // - the tmp directory
+        str = (String)props.getProperty("tmp.dir");
+        if (str != null)
+            webApp.setTempDirectory(new File(str.trim()));
+
+        str = (String)props.getProperty("tmp.dir.persist");
+        if (str != null)
+            webApp.setPersistTempDirectory(Boolean.valueOf(str));
+        
+        //Get the calculated base dirs which includes the overlays
+        str = (String)props.getProperty("base.dirs");
+        if (str != null && !"".equals(str.trim()))
+        {
+            ResourceCollection bases = new ResourceCollection(str.split(","));
+            webApp.setWar(null);
+            webApp.setBaseResource(bases);
+        }
+
+        //Get the original base dirs without the overlays
+        str = (String)props.get("base.dirs.orig");
+        if (str != null && !"".equals(str.trim()))
+        {
+            ResourceCollection bases = new ResourceCollection(str.split(","));
+            webApp.setAttribute ("org.eclipse.jetty.resources.originalBases", bases);
+        }
+        
+        //For overlays
+        str = (String)props.getProperty("maven.war.includes");
+        List<String> defaultWarIncludes = fromCSV(str);
+        str = (String)props.getProperty("maven.war.excludes");
+        List<String> defaultWarExcludes = fromCSV(str);
+       
+        //List of war artifacts
+        List<Artifact> wars = new ArrayList<Artifact>();
+        
+        //List of OverlayConfigs
+        TreeMap<String, OverlayConfig> orderedConfigs = new TreeMap<String, OverlayConfig>();
+        Enumeration<String> pnames = (Enumeration<String>)props.propertyNames();
+        while (pnames.hasMoreElements())
+        {
+            String n = pnames.nextElement();
+            if (n.startsWith("maven.war.artifact"))
+            {
+                Artifact a = new Artifact((String)props.get(n));
+                a.resource = Resource.newResource("jar:"+Resource.toURL(new File(a.path)).toString()+"!/");
+                wars.add(a);
+            }
+            else if (n.startsWith("maven.war.overlay"))
+            {
+                OverlayConfig c = new OverlayConfig ((String)props.get(n), defaultWarIncludes, defaultWarExcludes);
+                orderedConfigs.put(n,c);
+            }
+        }
+        
+    
+        Set<Artifact> matchedWars = new HashSet<Artifact>();
+        
+        //process any overlays and the war type artifacts
+        List<Overlay> overlays = new ArrayList<Overlay>();
+        for (OverlayConfig config:orderedConfigs.values())
+        {
+            //overlays can be individually skipped
+            if (config.isSkip())
+                continue;
+
+            //an empty overlay refers to the current project - important for ordering
+            if (config.isCurrentProject())
+            {
+                Overlay overlay = new Overlay(config, null);
+                overlays.add(overlay);
+                continue;
+            }
+
+            //if a war matches an overlay config
+            Artifact a = getArtifactForOverlayConfig(config, wars);
+            if (a != null)
+            {
+                matchedWars.add(a);
+                SelectiveJarResource r = new SelectiveJarResource(new URL("jar:"+Resource.toURL(new File(a.path)).toString()+"!/"));
+                r.setIncludes(config.getIncludes());
+                r.setExcludes(config.getExcludes());
+                Overlay overlay = new Overlay(config, r);
+                overlays.add(overlay);
+            }
+        }
+
+        //iterate over the left over war artifacts and unpack them (without include/exclude processing) as necessary
+        for (Artifact a: wars)
+        {
+            if (!matchedWars.contains(a))
+            {
+                Overlay overlay = new Overlay(null, a.resource);
+                overlays.add(overlay);
+            }
+        }
+
+        webApp.setOverlays(overlays);
+     
+
+        // - the equivalent of web-inf classes
+        str = (String)props.getProperty("classes.dir");
+        if (str != null && !"".equals(str.trim()))
+        {
+            webApp.setClasses(new File(str));
+        }
+        
+        str = (String)props.getProperty("testClasses.dir"); 
+        if (str != null && !"".equals(str.trim()))
+        {
+            webApp.setTestClasses(new File(str));
+        }
+
+
+        // - the equivalent of web-inf lib
+        str = (String)props.getProperty("lib.jars");
+        if (str != null && !"".equals(str.trim()))
+        {
+            List<File> jars = new ArrayList<File>();
+            String[] names = str.split(",");
+            for (int j=0; names != null && j < names.length; j++)
+                jars.add(new File(names[j].trim()));
+            webApp.setWebInfLib(jars);
+        }
+        
+    }
+
+    /**
+     * @param args
+     * @throws Exception
+     */
+    public void getConfiguration (String[] args)
+    throws Exception
+    {
+        for (int i=0; i<args.length; i++)
+        {
+            //--stop-port
+            if ("--stop-port".equals(args[i]))
+                stopPort = Integer.parseInt(args[++i]);
+
+            //--stop-key
+            if ("--stop-key".equals(args[i]))
+                stopKey = args[++i];
+
+            //--jettyXml
+            if ("--jetty-xml".equals(args[i]))
+            {
+                jettyXmls = new ArrayList<File>();
+                String[] names = args[++i].split(",");
+                for (int j=0; names!= null && j < names.length; j++)
+                {
+                    jettyXmls.add(new File(names[j].trim()));
+                }  
+            }
+
+            //--context-xml
+            if ("--context-xml".equals(args[i]))
+            {
+                contextXml = new File(args[++i]);
+            }
+
+            //--props
+            if ("--props".equals(args[i]))
+            {
+                File f = new File(args[++i].trim());
+                props = new Properties();
+                try (InputStream in = new FileInputStream(f))
+                {
+                    props.load(in);
+                }
+            }
+            
+            //--token
+            if ("--token".equals(args[i]))
+            {
+                token = args[++i].trim();
+            }
+        }
+    }
+
+
+    /**
+     * @throws Exception
+     */
+    public void run() throws Exception
+    {
+        LOG.info("Started Jetty Server");
+        server.start();  
+    }
+
+    
+    /**
+     * @throws Exception
+     */
+    public void join () throws Exception
+    {
+        server.join();
+    }
+    
+    
+    /**
+     * @param e
+     */
+    public void communicateStartupResult (Exception e)
+    {
+        if (token != null)
+        {
+            if (e==null)
+                System.out.println(token);
+            else
+                System.out.println(token+"\t"+e.getMessage());
+        }
+    }
+    
+    
+    /**
+     * Apply any jetty xml files given
+     * @throws Exception
+     */
+    public void applyJettyXml() throws Exception
+    {
+        if (jettyXmls == null)
+            return;
+        this.server.applyXmlConfigurations(jettyXmls);
+    }
+
+
+
+
+    /**
+     * @param handler
+     * @param handlers
+     */
+    protected void prependHandler (Handler handler, HandlerCollection handlers)
+    {
+        if (handler == null || handlers == null)
+            return;
+
+        Handler[] existing = handlers.getChildHandlers();
+        Handler[] children = new Handler[existing.length + 1];
+        children[0] = handler;
+        System.arraycopy(existing, 0, children, 1, existing.length);
+        handlers.setHandlers(children);
+    }
+    
+    
+    
+    /**
+     * @param c
+     * @param wars
+     * @return
+     */
+    protected Artifact getArtifactForOverlayConfig (OverlayConfig c, List<Artifact> wars)
+    {
+        if (wars == null || wars.isEmpty() || c == null)
+            return null;
+
+        Artifact war = null;
+        Iterator<Artifact> itor = wars.iterator();
+        while(itor.hasNext() && war == null)
+        {
+            Artifact a = itor.next();
+            if (c.matchesArtifact(a.gid, a.aid, null))
+                war = a;
+        }
+        return war;
+    }
+
+
+    /**
+     * @param csv
+     * @return
+     */
+    private List<String> fromCSV (String csv)
+    {
+        if (csv == null || "".equals(csv.trim()))
+            return null;
+        String[] atoms = csv.split(",");
+        List<String> list = new ArrayList<String>();
+        for (String a:atoms)
+        {
+            list.add(a.trim());
+        }
+        return list;
+    }
+    
+    
+    
+    /**
+     * @param args
+     */
+    public static final void main(String[] args)
+    {
+        if (args == null)
+           System.exit(1);
+       
+       Starter starter = null;
+       try
+       {
+           starter = new Starter();
+           starter.getConfiguration(args);
+           starter.configureJetty();
+           starter.run();
+           starter.communicateStartupResult(null);
+           starter.join();
+       }
+       catch (Exception e)
+       {
+           starter.communicateStartupResult(e);
+           e.printStackTrace();
+           System.exit(1);
+       }
+
+    }
+}
diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/SystemProperties.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/SystemProperties.java
new file mode 100644
index 0000000..fe11402
--- /dev/null
+++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/SystemProperties.java
@@ -0,0 +1,79 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.maven.plugin;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * SystemProperties
+ *
+ * Map of name to SystemProperty.
+ * 
+ * When a SystemProperty instance is added, if it has not
+ * been already set (eg via the command line java system property)
+ * then it will be set.
+ */
+public class SystemProperties
+{
+    private final Map<String, SystemProperty> properties;
+    private boolean force;
+    
+    public SystemProperties()
+    {
+        properties = new HashMap<>();
+    }
+    
+    public void setForce (boolean force)
+    {
+        this.force = force;
+    }
+    
+    public boolean getForce ()
+    {
+        return this.force;
+    }
+    
+    
+    public void setSystemProperty (SystemProperty prop)
+    {
+        properties.put(prop.getName(), prop);
+        if (!force)
+            prop.setIfNotSetAlready();
+        else
+            prop.setAnyway();
+    }
+    
+    public SystemProperty getSystemProperty(String name)
+    {
+        return properties.get(name);
+    }
+    
+    public boolean containsSystemProperty(String name)
+    {
+       return properties.containsKey(name); 
+    }
+    
+    public List<SystemProperty> getSystemProperties ()
+    {
+        return new ArrayList<>(properties.values());
+    }
+}
diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/SystemProperty.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/SystemProperty.java
new file mode 100644
index 0000000..2e5e938
--- /dev/null
+++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/SystemProperty.java
@@ -0,0 +1,103 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.maven.plugin;
+
+/**
+ * SystemProperty
+ * 
+ * Provides the ability to set System properties
+ * for the mojo execution. A value will only 
+ * be set if it is not set already. That is, if
+ * it was set on the command line or by the system,
+ * it won't be overridden by settings in the 
+ * plugin's configuration.
+ *
+ */
+public class SystemProperty
+{
+
+
+    private String name;
+    private String value;
+    private boolean isSet;
+    
+    /**
+     * @return Returns the name.
+     */
+    public String getName()
+    {
+        return this.name;
+    }
+    /**
+     * @param name The name to set.
+     */
+    public void setName(String name)
+    {
+        this.name = name;
+    }
+
+    public String getKey()
+    {
+        return this.name;
+    }
+
+    public void setKey (String name)
+    {
+        this.name = name;
+    }
+    /**
+     * @return Returns the value.
+     */
+    public String getValue()
+    {
+        return this.value;
+    }
+    /**
+     * @param value The value to set.
+     */
+    public void setValue(String value)
+    {
+        this.value = value;
+    }
+
+    
+    public boolean isSet ()
+    {
+        return isSet;
+    }
+    
+    /** Set a System.property with this value
+     * if it is not already set.
+     */
+    void setIfNotSetAlready()
+    {
+        if (System.getProperty(getName()) == null)
+        {
+            System.setProperty(getName(), (getValue()==null?"":getValue()));
+            isSet=true;
+        }
+    }
+    
+    void setAnyway()
+    {
+        System.setProperty(getName(), (getValue()==null?"":getValue()));
+        isSet=true;
+    }
+
+}
diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/WarPluginInfo.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/WarPluginInfo.java
new file mode 100644
index 0000000..f939a22
--- /dev/null
+++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/WarPluginInfo.java
@@ -0,0 +1,203 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.maven.plugin;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.maven.model.Plugin;
+import org.apache.maven.project.MavenProject;
+import org.codehaus.plexus.util.xml.Xpp3Dom;
+
+
+
+/**
+ * WarPluginInfo
+ *
+ * Information about the maven-war-plugin contained in the pom
+ */
+public class WarPluginInfo
+{
+    private MavenProject _project;
+    private Plugin _plugin;
+    private List<String> _dependentMavenWarIncludes;
+    private List<String> _dependentMavenWarExcludes;
+    private List<OverlayConfig> _overlayConfigs;
+    
+    
+    
+    /**
+     * @param project
+     */
+    public WarPluginInfo (MavenProject project)
+    {
+        _project = project;
+    }
+
+    
+    
+    
+    /**
+     * Find the maven-war-plugin, if one is configured
+     * @return
+     */
+    public Plugin getPlugin()
+    {
+        if (_plugin == null)
+        {
+            List plugins = _project.getBuildPlugins();
+            if (plugins == null)
+                return null;
+
+
+            Iterator itor = plugins.iterator();
+            while (itor.hasNext() && _plugin==null)
+            {
+                Plugin plugin = (Plugin)itor.next();
+                if ("maven-war-plugin".equals(plugin.getArtifactId()))
+                    _plugin = plugin;
+            }
+        }
+        return _plugin;
+    }
+
+    
+    
+
+    /**
+     * Get value of dependentWarIncludes for maven-war-plugin
+     * @return
+     */
+    public List<String> getDependentMavenWarIncludes()
+    {
+        if (_dependentMavenWarIncludes == null)
+        {
+            getPlugin();
+
+            if (_plugin == null)
+                return null;
+
+            Xpp3Dom node = (Xpp3Dom)_plugin.getConfiguration();
+            if (node == null)
+                return null;
+
+            node = node.getChild("dependentWarIncludes");
+            if (node == null)
+                return null;
+            String val = node.getValue();
+            _dependentMavenWarIncludes = Arrays.asList(val.split(",")); 
+        }
+        return _dependentMavenWarIncludes;
+    }
+
+
+    
+    
+    /**
+     * Get value of dependentWarExcludes for maven-war-plugin
+     * @return
+     */
+    public List<String> getDependentMavenWarExcludes()
+    {
+        if (_dependentMavenWarExcludes == null)
+        {
+            getPlugin();
+
+            if (_plugin == null)
+                return null;
+
+            Xpp3Dom node = (Xpp3Dom)_plugin.getConfiguration();
+            if (node == null)
+                return null;
+
+            node = node.getChild("dependentWarExcludes");
+            if (node == null)
+                return null;
+            String val = node.getValue();
+            _dependentMavenWarExcludes = Arrays.asList(val.split(","));
+        }
+        return _dependentMavenWarExcludes;
+    }
+
+    
+    
+    
+    /**
+     * Get config for any overlays that have been declared for the maven-war-plugin.
+     * 
+     * @return
+     */
+    public List<OverlayConfig> getMavenWarOverlayConfigs ()
+    {
+        if (_overlayConfigs == null)
+        {
+            getPlugin();
+
+            if (_plugin == null)
+                return Collections.emptyList();
+
+            getDependentMavenWarIncludes();
+            getDependentMavenWarExcludes();
+
+            Xpp3Dom node = (Xpp3Dom)_plugin.getConfiguration();
+            if (node == null)
+                return Collections.emptyList();
+
+            node = node.getChild("overlays");
+            if (node == null)
+                return Collections.emptyList();
+
+            Xpp3Dom[] nodes = node.getChildren("overlay");
+            if (nodes == null)
+                return Collections.emptyList();
+
+            _overlayConfigs = new ArrayList<OverlayConfig>();
+            for (int i=0;i<nodes.length;i++)
+            {
+                OverlayConfig overlayConfig = new OverlayConfig(nodes[i], _dependentMavenWarIncludes, _dependentMavenWarExcludes);
+                _overlayConfigs.add(overlayConfig);
+            }
+        }
+
+        return _overlayConfigs;
+    }
+    
+    
+    
+    
+    /**
+     * @return the xml as a string
+     */
+    public String getMavenWarOverlayConfigAsString ()
+    {
+        getPlugin();
+
+        if (_plugin == null)
+            return "";
+        
+        Xpp3Dom node = (Xpp3Dom)_plugin.getConfiguration();
+        if (node == null)
+            return "";
+        return node.toString();
+    }
+}
diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/package-info.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/package-info.java
new file mode 100644
index 0000000..e3b1161
--- /dev/null
+++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Maven Plugin : Support for Jetty in Maven build lifecycle
+ */
+package org.eclipse.jetty.maven.plugin;
+
diff --git a/jetty-monitor/README.txt b/jetty-monitor/README.TXT
similarity index 100%
rename from jetty-monitor/README.txt
rename to jetty-monitor/README.TXT
diff --git a/jetty-monitor/pom.xml b/jetty-monitor/pom.xml
index 6211de0..5f738d6 100644
--- a/jetty-monitor/pom.xml
+++ b/jetty-monitor/pom.xml
@@ -19,7 +19,7 @@
   <parent>
     <groupId>org.eclipse.jetty</groupId>
     <artifactId>jetty-project</artifactId>
-    <version>8.1.17.v20150415</version>
+    <version>9.2.13.v20150730</version>
   </parent>
   <modelVersion>4.0.0</modelVersion>
   <artifactId>jetty-monitor</artifactId>
@@ -125,12 +125,12 @@
       <version>${project.version}</version>
       <scope>test</scope>
     </dependency>
-    <dependency>
+    <!--dependency>
       <groupId>org.eclipse.jetty</groupId>
       <artifactId>jetty-websocket</artifactId>
       <version>${project.version}</version>
       <scope>test</scope>
-    </dependency>
+    </dependency-->
     <dependency>
       <groupId>org.eclipse.jetty</groupId>
       <artifactId>jetty-server</artifactId>
@@ -142,10 +142,5 @@
       <artifactId>jetty-test-helper</artifactId>
       <scope>test</scope>
     </dependency>
-    <dependency>
-      <groupId>junit</groupId>
-      <artifactId>junit</artifactId>
-      <scope>test</scope>
-    </dependency>
   </dependencies>
 </project>
diff --git a/jetty-monitor/src/main/config/etc/jetty-monitor.xml b/jetty-monitor/src/main/config/etc/jetty-monitor.xml
index dc97f88..61270b5 100644
--- a/jetty-monitor/src/main/config/etc/jetty-monitor.xml
+++ b/jetty-monitor/src/main/config/etc/jetty-monitor.xml
@@ -8,14 +8,14 @@
       <New class="org.eclipse.jetty.monitor.ThreadMonitor">
         <Set name="scanInterval">2000</Set>
         <Set name="busyThreshold">90</Set>
-        <Set name="stackDepth">5</Set>
+        <Set name="stackDepth">3</Set>
         <Set name="trailLength">2</Set>
         <!-- To enable logging CPU utilization for threads above specified threshold, -->
         <!-- uncomment the following lines, changing log interval (in milliseconds)  -->
         <!-- and log threshold (in percent) as desired.                              -->
         <!-- 
         <Set name="logInterval">10000</Set>
-        <Set name="logThreshold">65</Set>
+        <Set name="logThreshold">1</Set>
         -->
         
         <!-- To enable detail dump of the server whenever a thread is detected as spinning, -->
diff --git a/jetty-monitor/src/main/config/modules/monitor.mod b/jetty-monitor/src/main/config/modules/monitor.mod
new file mode 100644
index 0000000..09132c7
--- /dev/null
+++ b/jetty-monitor/src/main/config/modules/monitor.mod
@@ -0,0 +1,13 @@
+#
+# Jetty Monitor module
+#
+
+[depend]
+server
+client
+
+[lib]
+lib/monitor/jetty-monitor-${jetty.version}.jar
+
+[xml]
+etc/jetty-monitor.xml
diff --git a/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/ThreadMonitor.java b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/ThreadMonitor.java
index 7b1d35a..98042e9 100644
--- a/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/ThreadMonitor.java
+++ b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/ThreadMonitor.java
@@ -16,7 +16,6 @@
 //  ========================================================================
 //
 
-
 package org.eclipse.jetty.monitor;
 
 import java.lang.management.ManagementFactory;
@@ -30,14 +29,13 @@ import java.util.Map;
 
 import org.eclipse.jetty.monitor.thread.ThreadMonitorException;
 import org.eclipse.jetty.monitor.thread.ThreadMonitorInfo;
+import org.eclipse.jetty.util.annotation.ManagedObject;
 import org.eclipse.jetty.util.component.AbstractLifeCycle;
 import org.eclipse.jetty.util.component.Dumpable;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.util.log.Logger;
-
 
-/* ------------------------------------------------------------ */
+ at ManagedObject("Busy Thread Monitor")
 public class ThreadMonitor extends AbstractLifeCycle implements Runnable
 {
     private static final Logger LOG = Log.getLogger(ThreadMonitor.class);
diff --git a/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/integration/JavaMonitorAction.java b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/integration/JavaMonitorAction.java
index 3202012..92b62b3 100644
--- a/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/integration/JavaMonitorAction.java
+++ b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/integration/JavaMonitorAction.java
@@ -35,11 +35,10 @@ import javax.management.MBeanServerConnection;
 import javax.management.MalformedObjectNameException;
 import javax.management.ObjectName;
 
-import org.eclipse.jetty.client.ContentExchange;
 import org.eclipse.jetty.client.HttpClient;
-import org.eclipse.jetty.http.HttpMethods;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.util.BytesContentProvider;
 import org.eclipse.jetty.http.HttpStatus;
-import org.eclipse.jetty.io.ByteArrayBuffer;
 import org.eclipse.jetty.monitor.JMXMonitor;
 import org.eclipse.jetty.monitor.jmx.EventNotifier;
 import org.eclipse.jetty.monitor.jmx.EventState;
@@ -49,6 +48,7 @@ import org.eclipse.jetty.monitor.jmx.MonitorAction;
 import org.eclipse.jetty.monitor.triggers.AggregateEventTrigger;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
 
 
 /* ------------------------------------------------------------ */
@@ -83,9 +83,10 @@ public class JavaMonitorAction extends MonitorAction
         _uuid = uuid;
         _appid = appid;
         
+        QueuedThreadPool executor = new QueuedThreadPool();
+        executor.setName(executor.getName() + "-monitor");
         _client = new HttpClient();
-        _client.setTimeout(60000);
-        _client.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);
+        _client.setExecutor(executor);
         
         try
         {
@@ -182,24 +183,19 @@ public class JavaMonitorAction extends MonitorAction
         Properties response = null;
     
         try {
-            ContentExchange reqEx = new ContentExchange();
-            reqEx.setURL(_url);
-            reqEx.setMethod(HttpMethods.POST);
-            reqEx.addRequestHeader("Connection","close");
-            
             reqStream = new ByteArrayOutputStream();
             request.storeToXML(reqStream,null);
-            ByteArrayBuffer reqBuff = new ByteArrayBuffer(reqStream.toByteArray());
-
-            reqEx.setRequestContent(reqBuff);
-            _client.send(reqEx);
-        
-            reqEx.waitForDone();
             
-            if (reqEx.getResponseStatus() == HttpStatus.OK_200)
+            ContentResponse r3sponse = _client.POST(_url)
+                .header("Connection","close")
+                .content(new BytesContentProvider(reqStream.toByteArray()))
+                .send();
+            
+                        
+            if (r3sponse.getStatus() == HttpStatus.OK_200)
             {
                 response = new Properties();
-                resStream = new ByteArrayInputStream(reqEx.getResponseContentBytes());
+                resStream = new ByteArrayInputStream(r3sponse.getContent());
                 response.loadFromXML(resStream);               
             }
         }
diff --git a/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/integration/JavaMonitorTools.java b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/integration/JavaMonitorTools.java
index 8f7b2db..d172bbb 100644
--- a/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/integration/JavaMonitorTools.java
+++ b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/integration/JavaMonitorTools.java
@@ -27,6 +27,9 @@ import java.security.Security;
 import java.util.HashMap;
 import java.util.Map;
 
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.annotation.ManagedOperation;
+
 /* ------------------------------------------------------------ */
 /**
  * Derived from the JMX bean classes created by Kees Jan Koster for the java-monitor
@@ -34,6 +37,7 @@ import java.util.Map;
  * 
  * @author kjkoster <kjkoster at gmail.com>
  */
+ at ManagedObject("Java Monitoring Tools")
 public class JavaMonitorTools
 {
     private static final ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
@@ -78,7 +82,7 @@ public class JavaMonitorTools
         final ThreadInfo[] threads = threadMXBean.getThreadInfo(threadIds,Integer.MAX_VALUE);
         return threads;
     }
-
+    @ManagedOperation(value="Detailed report on the deadlocked threads.", impact="ACTION_INFO")
     public String getDeadlockStacktraces()
     {
         try
@@ -134,6 +138,7 @@ public class JavaMonitorTools
 
     private final Map<Thread.State, Integer> states = new HashMap<Thread.State, Integer>();
 
+    @ManagedOperation(value="Number of Blocked Threads")
     public int getThreadsBlocked()
     {
         sampleThreads();
@@ -141,13 +146,15 @@ public class JavaMonitorTools
         return states.get(Thread.State.BLOCKED);
     }
 
+    @ManagedOperation(value="Number of New Threads", impact="ACTION_INFO")
     public int getThreadsNew()
     {
         sampleThreads();
 
         return states.get(Thread.State.NEW);
     }
-
+    
+    @ManagedOperation(value="Number of Terminated Threads", impact="ACTION_INFO")
     public int getThreadsTerminated()
     {
         sampleThreads();
@@ -155,6 +162,7 @@ public class JavaMonitorTools
         return states.get(Thread.State.TERMINATED);
     }
 
+    @ManagedOperation(value="Number of Sleeping and Waiting threads")
     public int getThreadsTimedWaiting()
     {
         sampleThreads();
@@ -162,6 +170,7 @@ public class JavaMonitorTools
         return states.get(Thread.State.TIMED_WAITING);
     }
 
+    @ManagedOperation(value="Number of Waiting Threads", impact="ACTION_INFO")
     public int getThreadsWaiting()
     {
         sampleThreads();
@@ -169,6 +178,7 @@ public class JavaMonitorTools
         return states.get(Thread.State.WAITING);
     }
 
+    @ManagedOperation(value="Number of Runnable Threads", impact="ACTION_INFO")
     public int getThreadsRunnable()
     {
         sampleThreads();
@@ -203,6 +213,7 @@ public class JavaMonitorTools
 
     private static final String POLICY = "sun.net.InetAddressCachePolicy";
 
+    @ManagedOperation(value="Amount of time successful DNS queries are cached for.")
     public int getCacheSeconds() throws ClassNotFoundException,
             IllegalAccessException, InvocationTargetException,
             NoSuchMethodException {
@@ -214,6 +225,7 @@ public class JavaMonitorTools
         return seconds.intValue();
     }
 
+    @ManagedOperation(value="Amount of time failed DNS queries are cached for")
     public int getCacheNegativeSeconds() throws ClassNotFoundException,
             IllegalAccessException, InvocationTargetException,
             NoSuchMethodException {
@@ -241,6 +253,7 @@ public class JavaMonitorTools
 
     private static final String SYSTEM_NEGATIVE_TTL = "sun.net.inetaddr.negative.ttl";
 
+    @ManagedOperation(value="Cache policy for successful DNS lookups was changed from the hard-coded default")
     public String getCacheTweakedFrom() {
         if (Security.getProperty(SECURITY_TTL) != null) {
             if (System.getProperty(SYSTEM_TTL) != null) {
@@ -257,6 +270,7 @@ public class JavaMonitorTools
         return DEFAULT;
     }
 
+    @ManagedOperation(value="Cache policy for failed DNS lookups was changed from the hard-coded default")
     public String getCacheNegativeTweakedFrom() {
         if (Security.getProperty(SECURITY_NEGATIVE_TTL) != null) {
             if (System.getProperty(SYSTEM_NEGATIVE_TTL) != null) {
diff --git a/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/integration/JavaMonitorTrigger.java b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/integration/JavaMonitorTrigger.java
index 49c3d68..4f95190 100644
--- a/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/integration/JavaMonitorTrigger.java
+++ b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/integration/JavaMonitorTrigger.java
@@ -18,7 +18,6 @@
 
 package org.eclipse.jetty.monitor.integration;
 
-import javax.management.MalformedObjectNameException;
 import javax.management.ObjectName;
 
 import org.eclipse.jetty.monitor.triggers.AttrEventTrigger;
diff --git a/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/integration/package-info.java b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/integration/package-info.java
new file mode 100644
index 0000000..5f7de68
--- /dev/null
+++ b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/integration/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Monitor : Intregation with Java Monitor
+ */
+package org.eclipse.jetty.monitor.integration;
+
diff --git a/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/jmx/MonitorTask.java b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/jmx/MonitorTask.java
index c85e055..f8c4bfb 100644
--- a/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/jmx/MonitorTask.java
+++ b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/jmx/MonitorTask.java
@@ -101,7 +101,7 @@ public class MonitorTask extends TimerTask
         final long timestamp = System.currentTimeMillis();
         final EventTrigger trigger = _action.getTrigger();
 
-        _callback.dispatch(new Runnable() {
+        _callback.execute(new Runnable() {
             public void run()
             {
                 try
diff --git a/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/jmx/NotifierGroup.java b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/jmx/NotifierGroup.java
index caae99a..b249d10 100644
--- a/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/jmx/NotifierGroup.java
+++ b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/jmx/NotifierGroup.java
@@ -16,7 +16,6 @@
 //  ========================================================================
 //
 
-
 package org.eclipse.jetty.monitor.jmx;
 
 import java.util.Collection;
diff --git a/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/jmx/ServiceConnection.java b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/jmx/ServiceConnection.java
index 2fe6cb1..44bf414 100644
--- a/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/jmx/ServiceConnection.java
+++ b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/jmx/ServiceConnection.java
@@ -82,7 +82,7 @@ public class ServiceConnection
      */
     public String getServiceUrl()
     {
-    	return _serviceUrl;
+        return _serviceUrl;
     }
     
     /* ------------------------------------------------------------ */
diff --git a/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/jmx/package-info.java b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/jmx/package-info.java
new file mode 100644
index 0000000..cdc3cbc
--- /dev/null
+++ b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/jmx/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Monitor : JMX Integration
+ */
+package org.eclipse.jetty.monitor.jmx;
+
diff --git a/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/package-info.java b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/package-info.java
new file mode 100644
index 0000000..88e7ef2
--- /dev/null
+++ b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Monitor : Tool for Monitoring Threads
+ */
+package org.eclipse.jetty.monitor;
+
diff --git a/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/thread/ThreadMonitorException.java b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/thread/ThreadMonitorException.java
index 437377b..bbd450d 100644
--- a/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/thread/ThreadMonitorException.java
+++ b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/thread/ThreadMonitorException.java
@@ -16,7 +16,6 @@
 //  ========================================================================
 //
 
-
 package org.eclipse.jetty.monitor.thread;
 
 
diff --git a/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/thread/ThreadMonitorInfo.java b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/thread/ThreadMonitorInfo.java
index 7a9b84d..c9887c6 100644
--- a/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/thread/ThreadMonitorInfo.java
+++ b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/thread/ThreadMonitorInfo.java
@@ -16,7 +16,6 @@
 //  ========================================================================
 //
 
-
 package org.eclipse.jetty.monitor.thread;
 
 
diff --git a/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/thread/package-info.java b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/thread/package-info.java
new file mode 100644
index 0000000..d767c63
--- /dev/null
+++ b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/thread/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Monitor : Thread Monitoring
+ */
+package org.eclipse.jetty.monitor.thread;
+
diff --git a/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/AggregateEventTrigger.java b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/AggregateEventTrigger.java
index 5cca00d..e24ea95 100644
--- a/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/AggregateEventTrigger.java
+++ b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/AggregateEventTrigger.java
@@ -16,7 +16,6 @@
 //  ========================================================================
 //
 
-
 package org.eclipse.jetty.monitor.triggers;
 
 import java.util.ArrayList;
diff --git a/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/AndEventTrigger.java b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/AndEventTrigger.java
index 9596caa..dc3c060 100644
--- a/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/AndEventTrigger.java
+++ b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/AndEventTrigger.java
@@ -16,7 +16,6 @@
 //  ========================================================================
 //
 
-
 package org.eclipse.jetty.monitor.triggers;
 
 import java.util.Arrays;
@@ -26,6 +25,7 @@ import org.eclipse.jetty.monitor.jmx.EventState;
 import org.eclipse.jetty.monitor.jmx.EventTrigger;
 
 
+
 /* ------------------------------------------------------------ */
 /**
  * AndEventTrigger 
diff --git a/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/AttrEventTrigger.java b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/AttrEventTrigger.java
index 1c750d1..61e35c2 100644
--- a/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/AttrEventTrigger.java
+++ b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/AttrEventTrigger.java
@@ -16,7 +16,6 @@
 //  ========================================================================
 //
 
-
 package org.eclipse.jetty.monitor.triggers;
 
 import java.util.Map;
diff --git a/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/EqualToAttrEventTrigger.java b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/EqualToAttrEventTrigger.java
index 75420c4..b988701 100644
--- a/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/EqualToAttrEventTrigger.java
+++ b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/EqualToAttrEventTrigger.java
@@ -16,7 +16,6 @@
 //  ========================================================================
 //
 
-
 package org.eclipse.jetty.monitor.triggers;
 
 import javax.management.MalformedObjectNameException;
diff --git a/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/GreaterThanAttrEventTrigger.java b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/GreaterThanAttrEventTrigger.java
index 6b9e8ae..7b9db46 100644
--- a/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/GreaterThanAttrEventTrigger.java
+++ b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/GreaterThanAttrEventTrigger.java
@@ -16,7 +16,6 @@
 //  ========================================================================
 //
 
-
 package org.eclipse.jetty.monitor.triggers;
 
 import javax.management.MalformedObjectNameException;
diff --git a/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/GreaterThanOrEqualToAttrEventTrigger.java b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/GreaterThanOrEqualToAttrEventTrigger.java
index b2054a5..95b32c1 100644
--- a/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/GreaterThanOrEqualToAttrEventTrigger.java
+++ b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/GreaterThanOrEqualToAttrEventTrigger.java
@@ -16,7 +16,6 @@
 //  ========================================================================
 //
 
-
 package org.eclipse.jetty.monitor.triggers;
 
 import javax.management.MalformedObjectNameException;
diff --git a/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/LessThanAttrEventTrigger.java b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/LessThanAttrEventTrigger.java
index 348a7ad..d64fe9f 100644
--- a/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/LessThanAttrEventTrigger.java
+++ b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/LessThanAttrEventTrigger.java
@@ -16,7 +16,6 @@
 //  ========================================================================
 //
 
-
 package org.eclipse.jetty.monitor.triggers;
 
 import javax.management.MalformedObjectNameException;
diff --git a/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/LessThanOrEqualToAttrEventTrigger.java b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/LessThanOrEqualToAttrEventTrigger.java
index 2f1b91c..ecf46f2 100644
--- a/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/LessThanOrEqualToAttrEventTrigger.java
+++ b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/LessThanOrEqualToAttrEventTrigger.java
@@ -16,7 +16,6 @@
 //  ========================================================================
 //
 
-
 package org.eclipse.jetty.monitor.triggers;
 
 import javax.management.MalformedObjectNameException;
diff --git a/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/RangeAttrEventTrigger.java b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/RangeAttrEventTrigger.java
index 228057d..a11b560 100644
--- a/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/RangeAttrEventTrigger.java
+++ b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/RangeAttrEventTrigger.java
@@ -16,7 +16,6 @@
 //  ========================================================================
 //
 
-
 package org.eclipse.jetty.monitor.triggers;
 
 import javax.management.MalformedObjectNameException;
diff --git a/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/RangeInclAttrEventTrigger.java b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/RangeInclAttrEventTrigger.java
index c167d8f..e3e8095 100644
--- a/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/RangeInclAttrEventTrigger.java
+++ b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/RangeInclAttrEventTrigger.java
@@ -16,7 +16,6 @@
 //  ========================================================================
 //
 
-
 package org.eclipse.jetty.monitor.triggers;
 
 import javax.management.MalformedObjectNameException;
diff --git a/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/package-info.java b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/package-info.java
new file mode 100644
index 0000000..a93ee74
--- /dev/null
+++ b/jetty-monitor/src/main/java/org/eclipse/jetty/monitor/triggers/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Monitor : Triggers for Monitor Events
+ */
+package org.eclipse.jetty.monitor.triggers;
+
diff --git a/jetty-monitor/src/main/resources/org/eclipse/jetty/monitor/integration/jmx/JavaMonitorTools-mbean.properties b/jetty-monitor/src/main/resources/org/eclipse/jetty/monitor/integration/jmx/JavaMonitorTools-mbean.properties
deleted file mode 100644
index 4722fda..0000000
--- a/jetty-monitor/src/main/resources/org/eclipse/jetty/monitor/integration/jmx/JavaMonitorTools-mbean.properties
+++ /dev/null
@@ -1,12 +0,0 @@
-JavaMonitorTools: Retrieves additional information required by java-monitor
-DeadlockStacktraces: RO:Detailed report on the deadlocked threads.
-ThreadsNew: RO:Number of new threads
-ThreadsRunnable: RO:Number of runnable threads
-ThreadsBlocked: RO:Number of blocked threads
-ThreadsWaiting: RO:Number of waiting threads
-ThreadsTimedWaiting: RO:Number of sleeping and waiting threads
-ThreadsTerminated: RO:Number of terminated threads
-CacheSeconds: RO:Amount of time successful DNS queries are cached for
-CacheTweakedFrom: RO:Cache policy for successful DNS lookups was changed from the hard-coded default
-CacheNegativeSeconds: RO:Amount of time failed DNS queries are cached for
-CacheNegativeTweakedFrom: RO:Cache policy for failed DNS lookups was changed from the hard-coded default
diff --git a/jetty-monitor/src/main/resources/org/eclipse/jetty/monitor/jmx/ThreadMonitor-mbean.properties b/jetty-monitor/src/main/resources/org/eclipse/jetty/monitor/jmx/ThreadMonitor-mbean.properties
deleted file mode 100644
index c6f5a04..0000000
--- a/jetty-monitor/src/main/resources/org/eclipse/jetty/monitor/jmx/ThreadMonitor-mbean.properties
+++ /dev/null
@@ -1 +0,0 @@
-ThreadMonitor: Detect and report spinning and deadlocked threads
diff --git a/jetty-monitor/src/test/java/org/eclipse/jetty/monitor/AttrEventTriggerTest.java b/jetty-monitor/src/test/java/org/eclipse/jetty/monitor/AttrEventTriggerTest.java
index 0fcac69..1f3860c 100644
--- a/jetty-monitor/src/test/java/org/eclipse/jetty/monitor/AttrEventTriggerTest.java
+++ b/jetty-monitor/src/test/java/org/eclipse/jetty/monitor/AttrEventTriggerTest.java
@@ -18,6 +18,8 @@
 
 package org.eclipse.jetty.monitor;
 
+import static org.junit.Assert.assertEquals;
+
 import java.io.File;
 import java.io.IOException;
 import java.io.PrintWriter;
@@ -25,16 +27,14 @@ import java.lang.management.ManagementFactory;
 import java.util.Collection;
 import java.util.Iterator;
 import java.util.TreeSet;
+
 import javax.management.MBeanServer;
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import org.eclipse.jetty.client.ContentExchange;
 import org.eclipse.jetty.client.HttpClient;
-import org.eclipse.jetty.client.security.Realm;
-import org.eclipse.jetty.client.security.SimpleRealmResolver;
-import org.eclipse.jetty.http.HttpMethods;
+import org.eclipse.jetty.client.api.ContentResponse;
 import org.eclipse.jetty.http.HttpStatus;
 import org.eclipse.jetty.jmx.MBeanContainer;
 import org.eclipse.jetty.monitor.jmx.ConsoleNotifier;
@@ -53,29 +53,32 @@ import org.eclipse.jetty.monitor.triggers.LessThanOrEqualToAttrEventTrigger;
 import org.eclipse.jetty.monitor.triggers.OrEventTrigger;
 import org.eclipse.jetty.monitor.triggers.RangeAttrEventTrigger;
 import org.eclipse.jetty.monitor.triggers.RangeInclAttrEventTrigger;
+import org.eclipse.jetty.server.Connector;
 import org.eclipse.jetty.server.Request;
 import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
 import org.eclipse.jetty.server.handler.AbstractHandler;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
 import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 
-import static org.junit.Assert.assertEquals;
-
 
 /* ------------------------------------------------------------ */
 /**
  */
 public class AttrEventTriggerTest
 {
+    private static final Logger LOG = Log.getLogger(AttrEventTriggerTest.class);
+
     private Server _server;
     private TestHandler _handler;
     private RequestCounter _counter;
     private JMXMonitor _monitor;
     private HttpClient _client;
     private String _requestUrl;
+    private MBeanContainer _mBeanContainer;
 
     @Before
     public void setUp()
@@ -88,25 +91,25 @@ public class AttrEventTriggerTest
         System.setProperty("org.eclipse.jetty.util.log.DEBUG","");
         _server = new Server();
 
-        SelectChannelConnector connector = new SelectChannelConnector();
-        _server.addConnector(connector);
-
+        ServerConnector connector = new ServerConnector(_server);
+        connector.setPort(0);
+        _server.setConnectors(new Connector[] {connector});
+        
         _handler = new TestHandler();
         _server.setHandler(_handler);
 
-        MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();
-        MBeanContainer mBeanContainer = new MBeanContainer(mBeanServer);
-        mBeanContainer.addBean(Log.getLog());
         MBeanContainer.resetUnique();
-
+        MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();
+        _mBeanContainer = new MBeanContainer(mBeanServer);
+        _server.addBean(_mBeanContainer,true);
+        _server.addBean(Log.getLog());
+        
         _counter = _handler.getRequestCounter();
-        mBeanContainer.addBean(_counter);
+        _server.addBean(_counter);
 
-        _server.addBean(mBeanContainer, true);
-        _server.getContainer().addEventListener(mBeanContainer);
         _server.start();
 
-        startClient(null);
+        startClient();
 
         _monitor = new JMXMonitor();
 
@@ -120,6 +123,8 @@ public class AttrEventTriggerTest
     {
         stopClient();
 
+        _mBeanContainer.destroy();
+        
         if (_server != null)
         {
             _server.stop();
@@ -364,21 +369,28 @@ public class AttrEventTriggerTest
         {
             try
             {
-                ContentExchange getExchange = new ContentExchange();
-                getExchange.setURL(_requestUrl);
-                getExchange.setMethod(HttpMethods.GET);
+                //LOG.debug("Request: %s", _requestUrl);
+                ContentResponse r3sponse = _client.GET(_requestUrl);           
+                
+                //ContentExchange getExchange = new ContentExchange();
+                //getExchange.setURL(_requestUrl);
+                //getExchange.setMethod(HttpMethods.GET);
 
-                _client.send(getExchange);
-                int state = getExchange.waitForDone();
+                //_client.send(getExchange);
+                //int state = getExchange.waitForDone();
 
                 String content = "";
-                int responseStatus = getExchange.getResponseStatus();
-                if (responseStatus == HttpStatus.OK_200)
+                //int responseStatus = getExchange.getResponseStatus();
+                if (r3sponse.getStatus() == HttpStatus.OK_200)
+                {
+                    content = r3sponse.getContentAsString();
+                }
+                else 
                 {
-                    content = getExchange.getResponseContent();
+                    LOG.info("response status", r3sponse.getStatus());
                 }
 
-                assertEquals(HttpStatus.OK_200,responseStatus);
+                assertEquals(HttpStatus.OK_200,r3sponse.getStatus());
                 Thread.sleep(interval);
             }
             catch (InterruptedException ex)
@@ -392,13 +404,14 @@ public class AttrEventTriggerTest
         _monitor.removeActions(action);
     }
 
-    protected void startClient(Realm realm)
+    protected void startClient()//Realm realm)
         throws Exception
     {
         _client = new HttpClient();
-        _client.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);
-        if (realm != null)
-            _client.setRealmResolver(new SimpleRealmResolver(realm));
+        //_client.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);
+        //if (realm != null){
+//            _client.setRealmResolver(new SimpleRealmResolver(realm));
+        //}
         _client.start();
     }
 
diff --git a/jetty-monitor/src/test/java/org/eclipse/jetty/monitor/RequestCounter.java b/jetty-monitor/src/test/java/org/eclipse/jetty/monitor/RequestCounter.java
index 3333650..ed90672 100644
--- a/jetty-monitor/src/test/java/org/eclipse/jetty/monitor/RequestCounter.java
+++ b/jetty-monitor/src/test/java/org/eclipse/jetty/monitor/RequestCounter.java
@@ -18,21 +18,29 @@
 
 package org.eclipse.jetty.monitor;
 
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.annotation.ManagedOperation;
 
+
+ at ManagedObject("TEST: Request Counter")
 public class RequestCounter
 {  
     public long _counter;
     
+    @ManagedAttribute("Get the value of the counter")
     public synchronized long getCounter()
     {
         return _counter;
     }
     
+    @ManagedOperation("Increment the value of the counter")
     public synchronized void increment()
     {
         _counter++;
     }
 
+    @ManagedOperation("Reset the counter")
     public synchronized void reset()
     {
         _counter = 0;
diff --git a/jetty-monitor/src/test/java/org/eclipse/jetty/monitor/ThreadMonitorTest.java b/jetty-monitor/src/test/java/org/eclipse/jetty/monitor/ThreadMonitorTest.java
index 9814cdd..73f8ff6 100644
--- a/jetty-monitor/src/test/java/org/eclipse/jetty/monitor/ThreadMonitorTest.java
+++ b/jetty-monitor/src/test/java/org/eclipse/jetty/monitor/ThreadMonitorTest.java
@@ -16,7 +16,6 @@
 //  ========================================================================
 //
 
-
 package org.eclipse.jetty.monitor;
 
 import static org.junit.Assert.assertTrue;
diff --git a/jetty-monitor/src/test/resources/jetty-logging.properties b/jetty-monitor/src/test/resources/jetty-logging.properties
new file mode 100644
index 0000000..2071498
--- /dev/null
+++ b/jetty-monitor/src/test/resources/jetty-logging.properties
@@ -0,0 +1,3 @@
+org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
+#org.eclipse.jetty.LEVEL=DEBUG
+#org.eclipse.jetty.monitor.LEVEL=DEBUG
diff --git a/jetty-monitor/src/test/resources/org/eclipse/jetty/monitor/jmx/RequestCounter-mbean.properties b/jetty-monitor/src/test/resources/org/eclipse/jetty/monitor/jmx/RequestCounter-mbean.properties
deleted file mode 100644
index 1685bfd..0000000
--- a/jetty-monitor/src/test/resources/org/eclipse/jetty/monitor/jmx/RequestCounter-mbean.properties
+++ /dev/null
@@ -1,3 +0,0 @@
-RequestCounter: Request counter
-counter: current value of the counter
-increment(): increment the counter
\ No newline at end of file
diff --git a/jetty-nested/pom.xml b/jetty-nested/pom.xml
deleted file mode 100644
index b4115fa..0000000
--- a/jetty-nested/pom.xml
+++ /dev/null
@@ -1,64 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<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>
-  <parent>
-    <groupId>org.eclipse.jetty</groupId>
-    <artifactId>jetty-project</artifactId>
-    <version>8.1.17.v20150415</version>
-  </parent>
-  <artifactId>jetty-nested</artifactId>
-  <name>Jetty :: Nested</name>
-  <packaging>jar</packaging>
-  <description>Local Servlet Connector for jetty.</description>
-  <url>http://www.eclipse.org/jetty</url>
-  <properties>
-    <bundle-symbolic-name>${project.groupId}.nested</bundle-symbolic-name>
-  </properties>
-  <build>
-    <plugins>
-      <plugin>
-        <groupId>org.apache.felix</groupId>
-        <artifactId>maven-bundle-plugin</artifactId>
-        <extensions>true</extensions>
-        <executions>
-          <execution>
-            <goals>
-              <goal>manifest</goal>
-            </goals>
-            <configuration>
-              <instructions>
-                <Import-Package>javax.servlet.*;version="2.6.0",*</Import-Package>
-              </instructions>
-            </configuration>
-           </execution>
-        </executions>
-      </plugin>
-      <plugin>
-        <!--
-        Required for OSGI
-        -->
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-jar-plugin</artifactId>
-        <configuration>
-          <archive>
-            <manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
-          </archive>
-        </configuration>
-      </plugin>
-      <plugin>
-        <groupId>org.codehaus.mojo</groupId>
-        <artifactId>findbugs-maven-plugin</artifactId>
-        <configuration>
-          <onlyAnalyze>org.eclipse.jetty.nested.*</onlyAnalyze>
-        </configuration>
-      </plugin>
-    </plugins>
-  </build>
-  <dependencies>
-    <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-server</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-  </dependencies>
-</project>
diff --git a/jetty-nested/src/main/java/org/eclipse/jetty/nested/NestedConnection.java b/jetty-nested/src/main/java/org/eclipse/jetty/nested/NestedConnection.java
deleted file mode 100644
index 9e8a858..0000000
--- a/jetty-nested/src/main/java/org/eclipse/jetty/nested/NestedConnection.java
+++ /dev/null
@@ -1,121 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.nested;
-
-import java.io.IOException;
-import java.util.Enumeration;
-
-import javax.servlet.DispatcherType;
-import javax.servlet.ServletException;
-import javax.servlet.ServletInputStream;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jetty.http.HttpFields;
-import org.eclipse.jetty.http.HttpURI;
-import org.eclipse.jetty.io.Connection;
-import org.eclipse.jetty.server.AbstractHttpConnection;
-
-
-public class NestedConnection extends AbstractHttpConnection
-{
-    protected NestedConnection(final NestedConnector connector, final NestedEndPoint endp, final HttpServletRequest outerRequest, HttpServletResponse outerResponse,String nestedIn)
-        throws IOException
-    {
-        super(connector,
-              endp,
-              connector.getServer(),
-              new NestedParser(),
-              new NestedGenerator(connector.getResponseBuffers(),endp,outerResponse,nestedIn),
-              new NestedRequest(outerRequest));
-
-        ((NestedRequest)_request).setConnection(this);
-        
-        // Set the request line
-        _request.setDispatcherType(DispatcherType.REQUEST);
-        _request.setScheme(outerRequest.getScheme());
-        _request.setMethod(outerRequest.getMethod());
-        String uri=outerRequest.getQueryString()==null?outerRequest.getRequestURI():(outerRequest.getRequestURI()+"?"+outerRequest.getQueryString());
-        _request.setUri(new HttpURI(uri));
-        _request.setPathInfo(outerRequest.getRequestURI());
-        _request.setQueryString(outerRequest.getQueryString());
-        _request.setProtocol(outerRequest.getProtocol());
-        
-        // Set the headers
-        HttpFields fields = getRequestFields();
-        for (Enumeration<String> e=outerRequest.getHeaderNames();e.hasMoreElements();)
-        {
-            String header=e.nextElement();
-            String value=outerRequest.getHeader(header);
-            fields.add(header,value);
-        }
-        
-        // Let outer parse the cookies
-        _request.setCookies(outerRequest.getCookies());
-        
-        // copy request attributes
-        for (Enumeration<String> e=outerRequest.getAttributeNames();e.hasMoreElements();)
-        {
-            String attr=e.nextElement();
-            _request.setAttribute(attr,outerRequest.getAttribute(attr));
-        }
-        
-        // customize the request
-        connector.customize(endp,_request);
-        
-        // System.err.println(_request.getMethod()+" "+_request.getUri()+" "+_request.getProtocol());
-        // System.err.println(fields.toString());
-    }
-
-    void service() throws IOException, ServletException
-    {
-        setCurrentConnection(this);
-        try
-        {
-            getServer().handle(this);
-            completeResponse();
-            while (!_generator.isComplete() && _endp.isOpen())
-                _generator.flushBuffer();
-            _endp.flush();
-        }
-        finally
-        {
-            setCurrentConnection(null);
-        }
-    }
-
-    /* (non-Javadoc)
-     * @see org.eclipse.jetty.server.HttpConnection#getInputStream()
-     */
-    @Override
-    public ServletInputStream getInputStream() throws IOException
-    {
-        return ((NestedEndPoint)_endp).getServletInputStream();
-    }
-
-    /* (non-Javadoc)
-     * @see org.eclipse.jetty.server.HttpConnection#handle()
-     */
-    @Override
-    public Connection handle() throws IOException
-    {
-        throw new IllegalStateException();
-    }
-
-}
diff --git a/jetty-nested/src/main/java/org/eclipse/jetty/nested/NestedConnector.java b/jetty-nested/src/main/java/org/eclipse/jetty/nested/NestedConnector.java
deleted file mode 100644
index c29d98f..0000000
--- a/jetty-nested/src/main/java/org/eclipse/jetty/nested/NestedConnector.java
+++ /dev/null
@@ -1,90 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.nested;
-
-import java.io.IOException;
-
-import javax.servlet.ServletException;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.server.AbstractConnector;
-import org.eclipse.jetty.server.Connector;
-import org.eclipse.jetty.server.Request;
-
-/**
- * Nested Jetty Connector
- * <p>
- * This Jetty {@link Connector} allows a jetty instance to be nested inside another servlet container.
- * Requests received by the outer servlet container should be passed to jetty using the {@link #service(ServletRequest, ServletResponse)} method of this connector. 
- *
- */
-public class NestedConnector extends AbstractConnector
-{
-    String _serverInfo;
-    
-    public NestedConnector()
-    {
-        setAcceptors(0);
-        setForwarded(true);
-    }
-    
-    public void open() throws IOException
-    {
-    }
-
-    public void close() throws IOException
-    {
-    }
-
-    public int getLocalPort()
-    {
-        return -1;
-    }
-
-    public Object getConnection()
-    {
-        return null;
-    }
-
-    @Override
-    protected void accept(int acceptorID) throws IOException, InterruptedException
-    {
-        throw new IllegalStateException();
-    }
-    
-    /**
-     * Service a request of the outer servlet container by passing it to the nested instance of Jetty.
-     * @param outerRequest
-     * @param outerResponse
-     * @throws IOException
-     * @throws ServletException
-     */
-    public void service(ServletRequest outerRequest, ServletResponse outerResponse) throws IOException, ServletException
-    {
-        HttpServletRequest outerServletRequest = (HttpServletRequest)outerRequest;
-        HttpServletResponse outerServletResponse = (HttpServletResponse)outerResponse;
-        NestedConnection connection=new NestedConnection(this,new NestedEndPoint(outerServletRequest,outerServletResponse),outerServletRequest,outerServletResponse,_serverInfo);
-        connection.service();
-    }
-
-}
diff --git a/jetty-nested/src/main/java/org/eclipse/jetty/nested/NestedEndPoint.java b/jetty-nested/src/main/java/org/eclipse/jetty/nested/NestedEndPoint.java
deleted file mode 100644
index 43c5bab..0000000
--- a/jetty-nested/src/main/java/org/eclipse/jetty/nested/NestedEndPoint.java
+++ /dev/null
@@ -1,77 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.nested;
-
-import java.io.IOException;
-import javax.servlet.ServletInputStream;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jetty.io.bio.StreamEndPoint;
-
-public class NestedEndPoint extends StreamEndPoint
-{
-    private final HttpServletRequest _outerRequest;
-
-    public NestedEndPoint(HttpServletRequest outerRequest, HttpServletResponse outerResponse)
-        throws IOException
-    {
-        super(outerRequest.getInputStream(),outerResponse.getOutputStream());
-        _outerRequest=outerRequest;
-    }
-
-    public ServletInputStream getServletInputStream()
-    {
-        return (ServletInputStream)getInputStream();
-    }
-    @Override
-    public String getLocalAddr()
-    {
-        return _outerRequest.getLocalAddr();
-    }
-
-    @Override
-    public String getLocalHost()
-    {
-        return _outerRequest.getLocalName();
-    }
-
-    @Override
-    public int getLocalPort()
-    {
-        return _outerRequest.getLocalPort();
-    }
-
-    @Override
-    public String getRemoteAddr()
-    {
-        return _outerRequest.getRemoteAddr();
-    }
-
-    @Override
-    public String getRemoteHost()
-    {
-        return _outerRequest.getRemoteHost();
-    }
-    @Override
-    public int getRemotePort()
-    {
-        return _outerRequest.getRemotePort();
-    }
-}
diff --git a/jetty-nested/src/main/java/org/eclipse/jetty/nested/NestedGenerator.java b/jetty-nested/src/main/java/org/eclipse/jetty/nested/NestedGenerator.java
deleted file mode 100644
index 2888388..0000000
--- a/jetty-nested/src/main/java/org/eclipse/jetty/nested/NestedGenerator.java
+++ /dev/null
@@ -1,308 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.nested;
-
-import java.io.IOException;
-
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jetty.http.AbstractGenerator;
-import org.eclipse.jetty.http.HttpFields;
-import org.eclipse.jetty.http.HttpHeaders;
-import org.eclipse.jetty.http.HttpVersions;
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.Buffers;
-import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-
-public class NestedGenerator extends AbstractGenerator
-{
-    private static final Logger LOG = Log.getLogger(NestedGenerator.class);
-
-    final HttpServletResponse _response;
-    final String _nestedIn;
-    
-    public NestedGenerator(Buffers buffers, EndPoint io, HttpServletResponse response, String nestedIn)
-    {
-        super(buffers,io);
-        _response=response;
-        _nestedIn=nestedIn;
-    }
-
-    public void addContent(Buffer content, boolean last) throws IOException
-    {
-        LOG.debug("addContent {} {}",content.length(),last);
-        if (_noContent)
-        {
-            content.clear();
-            return;
-        }
-
-        if (content.isImmutable())
-            throw new IllegalArgumentException("immutable");
-
-        if (_last || _state == STATE_END)
-        {
-            LOG.debug("Ignoring extra content {}", content);
-            content.clear();
-            return;
-        }
-        _last = last;
-
-        if(!_endp.isOpen())
-        {
-            _state = STATE_END;
-            return;
-        }
-
-        // Handle any unfinished business?
-        if (_content != null && _content.length() > 0)
-        {
-            flushBuffer();
-            if (_content != null && _content.length() > 0)
-                throw new IllegalStateException("FULL");
-        }
-
-        _content = content;
-
-        _contentWritten += content.length();
-
-        // Handle the _content
-        if (_head)
-        {
-            content.clear();
-            _content = null;
-        }
-        else if (!last || _buffer!=null)
-        {
-            // Yes - so we better check we have a buffer
-            initBuffer();
-            // Copy _content to buffer;
-            int len = 0;
-            len = _buffer.put(_content);
-
-            // make sure there is space for a trailing null   (???)
-            if (len > 0 && _buffer.space() == 0)
-            {
-                len--;
-                _buffer.setPutIndex(_buffer.putIndex() - 1);
-            }
-            
-            LOG.debug("copied {} to buffer",len);
-
-            _content.skip(len);
-
-            if (_content.length() == 0)
-                _content = null;
-        }
-    }
-
-    public boolean addContent(byte b) throws IOException
-    {
-        // LOG.debug("addContent 1");
-        if (_noContent)
-            return false;
-
-        if (_last || _state == STATE_END)
-            throw new IllegalStateException("Closed");
-
-
-        if(!_endp.isOpen())
-        {
-            _state = STATE_END;
-            return false;
-        }
-
-        // Handle any unfinished business?
-        if (_content != null && _content.length() > 0)
-        {
-            flushBuffer();
-            if (_content != null && _content.length() > 0)
-                throw new IllegalStateException("FULL");
-        }
-
-        _contentWritten++;
-
-        // Handle the _content
-        if (_head)
-            return false;
-
-        // we better check we have a buffer
-        initBuffer();
-
-        // Copy _content to buffer;
-
-        _buffer.put(b);
-
-        return _buffer.space() <= 1;
-    }
-
-    /* ------------------------------------------------------------ */
-    private void initBuffer() throws IOException
-    {
-        if (_buffer == null)
-        {
-            // LOG.debug("initContent");
-            _buffer = _buffers.getBuffer();
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public boolean isRequest()
-    {
-        return false;
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public boolean isResponse()
-    {
-        return true;
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public int prepareUncheckedAddContent() throws IOException
-    {
-        initBuffer();
-        return _buffer.space();
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public void completeHeader(HttpFields fields, boolean allContentAdded) throws IOException
-    {
-        if (LOG.isDebugEnabled())
-            LOG.debug("completeHeader: {}",fields.toString().trim().replace("\r\n","|"));
-        if (_state != STATE_HEADER)
-            return;
-
-        if (_last && !allContentAdded)
-            throw new IllegalStateException("last?");
-        _last = _last | allContentAdded;
-
-        if (_persistent==null)
-            _persistent=(_version > HttpVersions.HTTP_1_0_ORDINAL);
-
-
-        if (_reason == null)
-            _response.setStatus(_status);
-        else
-            _response.setStatus(_status,_reason.toString());
-
-        if (_status == 100 || _status == 204 || _status == 304)
-        {
-            _noContent = true;
-            _content = null;
-        }
-
-
-        boolean has_server = false;
-        if (fields != null)
-        {
-            // Add headers
-            int s=fields.size();
-            for (int f=0;f<s;f++)
-            {
-                HttpFields.Field field = fields.getField(f);
-                if (field==null)
-                    continue;
-                _response.setHeader(field.getName(),field.getValue());
-            }
-        }
-
-        if (!has_server && _status > 100 && getSendServerVersion())
-            _response.setHeader(HttpHeaders.SERVER,"Jetty("+Server.getVersion()+",nested in "+_nestedIn+")");
-
-        _state = STATE_CONTENT;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Complete the message.
-     *
-     * @throws IOException
-     */
-    @Override
-    public void complete() throws IOException
-    {
-        if (_state == STATE_END)
-            return;
-
-        super.complete();
-
-        if (_state < STATE_FLUSHING)
-            _state = STATE_FLUSHING;
-
-        flushBuffer();
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public int flushBuffer() throws IOException
-    {
-        if (_state == STATE_HEADER)
-            throw new IllegalStateException("State==HEADER");
-        
-        int len = 0;
-        
-        if (_buffer==null)
-        {
-            
-            if (_content!=null && _content.length()>0)
-            {
-                // flush content directly
-                len = _endp.flush(_content);
-                if (len>0)
-                    _content.skip(len);
-            }
-        }
-        else
-        {
-            if (_buffer.length()==0 && _content!=null && _content.length()>0)
-            {
-                // Copy content to buffer
-                _content.skip(_buffer.put(_content));
-            }
-
-            int size=_buffer.length();
-            len =_endp.flush(_buffer);
-            LOG.debug("flushBuffer {} of {}",len,size);
-            if (len>0)
-                _buffer.skip(len);
-        }
-        
-        if (_content!=null && _content.length()==0)
-            _content=null;
-        if (_buffer!=null && _buffer.length()==0 && _content==null)
-        {
-            _buffers.returnBuffer(_buffer);
-            _buffer=null;
-        }
-        
-        if (_state==STATE_FLUSHING && _buffer==null && _content==null)
-            _state=STATE_END;
-
-        return len;
-    }
-
-}
diff --git a/jetty-nested/src/main/java/org/eclipse/jetty/nested/NestedParser.java b/jetty-nested/src/main/java/org/eclipse/jetty/nested/NestedParser.java
deleted file mode 100644
index cddbb23..0000000
--- a/jetty-nested/src/main/java/org/eclipse/jetty/nested/NestedParser.java
+++ /dev/null
@@ -1,71 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.nested;
-
-import java.io.IOException;
-
-import javax.servlet.http.HttpServletRequest;
-
-import org.eclipse.jetty.http.Parser;
-
-public class NestedParser implements Parser
-{
-
-    public NestedParser()
-    {
-    }
-
-    public void reset()
-    {
-    }
-
-    public void returnBuffers()
-    {
-    }
-
-    public boolean isComplete()
-    {
-        return false;
-    }
-
-    public boolean parseAvailable() throws IOException
-    {
-        return false;
-    }
-
-    public boolean isMoreInBuffer() throws IOException
-    {
-        return false;
-    }
-
-    public boolean isIdle()
-    {
-        return false;
-    }
-
-    public boolean isPersistent()
-    {
-        return false;
-    }
-
-    public void setPersistent(boolean persistent)
-    {
-    }
-
-}
diff --git a/jetty-nested/src/main/java/org/eclipse/jetty/nested/NestedRequest.java b/jetty-nested/src/main/java/org/eclipse/jetty/nested/NestedRequest.java
deleted file mode 100644
index 7aece48..0000000
--- a/jetty-nested/src/main/java/org/eclipse/jetty/nested/NestedRequest.java
+++ /dev/null
@@ -1,45 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.nested;
-
-import javax.servlet.http.HttpServletRequest;
-
-import org.eclipse.jetty.http.HttpSchemes;
-import org.eclipse.jetty.server.Request;
-
-public class NestedRequest extends Request
-{
-    private final HttpServletRequest _outer;
-    
-    public NestedRequest(HttpServletRequest outer)
-    {
-        _outer=outer;
-    }
-    
-    void setConnection(NestedConnection connection)
-    {
-        super.setConnection(connection);
-    }
-
-    public boolean isSecure()
-    {
-        return _outer.isSecure() || HttpSchemes.HTTPS.equals(getScheme());
-    }
-    
-}
diff --git a/jetty-nosql/pom.xml b/jetty-nosql/pom.xml
index 3eb517f..7aab1a9 100644
--- a/jetty-nosql/pom.xml
+++ b/jetty-nosql/pom.xml
@@ -2,7 +2,7 @@
   <parent>
     <groupId>org.eclipse.jetty</groupId>
     <artifactId>jetty-project</artifactId>
-    <version>8.1.17.v20150415</version>
+    <version>9.2.13.v20150730</version>
   </parent>
   <modelVersion>4.0.0</modelVersion>
   <artifactId>jetty-nosql</artifactId>
@@ -14,22 +14,29 @@
   <build>
     <defaultGoal>install</defaultGoal>
     <plugins>
-      <!-- Jetty 7 is JDK5 +
       <plugin>
-        <artifactId>maven-compiler-plugin</artifactId>
-        <configuration>
-          <source>1.6</source>
-          <target>1.6</target>
-          <verbose>false</verbose>
-        </configuration>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-assembly-plugin</artifactId>
+        <executions>
+          <execution>
+            <phase>package</phase>
+            <goals>
+              <goal>single</goal>
+            </goals>
+            <configuration>
+              <descriptorRefs>
+                <descriptorRef>config</descriptorRef>
+              </descriptorRefs>
+            </configuration>
+          </execution>
+        </executions>
       </plugin>
-       -->
       <plugin>
         <groupId>org.apache.felix</groupId>
         <artifactId>maven-bundle-plugin</artifactId>
         <configuration>
           <instructions>
-            <Import-Package>javax.servlet.*;version="2.6.0",org.eclipse.jetty.server.session.jmx;version="8.0.0";resolution:=optional,,org.eclipse.jetty.*;version="8.0.0",*</Import-Package>
+            <Import-Package>javax.servlet.*;version="[2.6.0,3.2)",org.eclipse.jetty.server.session.jmx;version="9.1";resolution:=optional,,org.eclipse.jetty.*;version="9.1",*</Import-Package>
           </instructions>
         </configuration>
         <extensions>true</extensions>
@@ -67,8 +74,8 @@
       <version>${project.version}</version>
     </dependency>
     <dependency>
-      <groupId>junit</groupId>
-      <artifactId>junit</artifactId>
+      <groupId>org.eclipse.jetty.toolchain</groupId>
+      <artifactId>jetty-test-helper</artifactId>
       <scope>test</scope>
     </dependency>
     <dependency>
diff --git a/jetty-nosql/src/main/config/modules/nosql.mod b/jetty-nosql/src/main/config/modules/nosql.mod
new file mode 100644
index 0000000..a4189c9
--- /dev/null
+++ b/jetty-nosql/src/main/config/modules/nosql.mod
@@ -0,0 +1,9 @@
+#
+# Jetty Nosql module
+#
+
+[depend]
+webapp
+
+[lib]
+lib/jetty-nosql-${jetty.version}.jar
\ No newline at end of file
diff --git a/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/NoSqlSession.java b/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/NoSqlSession.java
index 39b66b5..149caa3 100644
--- a/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/NoSqlSession.java
+++ b/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/NoSqlSession.java
@@ -24,13 +24,13 @@ import java.util.concurrent.atomic.AtomicInteger;
 
 import javax.servlet.http.HttpServletRequest;
 
-import org.eclipse.jetty.server.session.AbstractSession;
+import org.eclipse.jetty.server.session.MemSession;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
 
 
 /* ------------------------------------------------------------ */
-public class NoSqlSession extends AbstractSession
+public class NoSqlSession extends MemSession
 {
     private final static Logger __log = Log.getLogger("org.eclipse.jetty.server.session");
 
@@ -45,7 +45,6 @@ public class NoSqlSession extends AbstractSession
     {
         super(manager, request);
         _manager=manager;
-        save(true);
         _active.incrementAndGet();
     }
     
@@ -78,7 +77,11 @@ public class NoSqlSession extends AbstractSession
     @Override
     public void setAttribute(String name, Object value)
     {
-        if ( updateAttribute(name,value) )
+        Object old = changeAttribute(name,value);
+        if (value == null && old == null)
+            return; //not dirty, no change
+        
+        if (value==null || !value.equals(old))
         {
             if (_dirty==null)
             {
@@ -88,31 +91,16 @@ public class NoSqlSession extends AbstractSession
             _dirty.add(name);
         }
     }
+    
+    
 
-    /*
-     * a boolean version of the setAttribute method that lets us manage the _dirty set
-     */
-    protected boolean updateAttribute (String name, Object value)
+    @Override
+    protected void timeout() throws IllegalStateException
     {
-        Object old=null;
-        synchronized (this)
-        {
-            checkValid();
-            old=doPutOrRemove(name,value);
-        }
+        super.timeout();
+    }
 
-        if (value==null || !value.equals(old))
-        {
-            if (old!=null)
-                unbindValue(name,old);
-            if (value!=null)
-                bindValue(name,value);
 
-            _manager.doSessionAttributeListeners(this,name,old,value);
-            return true;
-        }
-        return false;
-    }
     
     /* ------------------------------------------------------------ */
     @Override
@@ -125,7 +113,7 @@ public class NoSqlSession extends AbstractSession
     @Override
     protected boolean access(long time)
     {
-        __log.debug("NoSqlSession:access:active "+_active);
+        __log.debug("NoSqlSession:access:active {} time {}", _active, time);
         if (_active.incrementAndGet()==1)
         {
             long period=_manager.getStalePeriod()*1000L;
@@ -169,6 +157,7 @@ public class NoSqlSession extends AbstractSession
     protected void doInvalidate() throws IllegalStateException
     {
         super.doInvalidate();
+        //jb why save here? if the session is invalidated it should be removed
         save(false);
     }
     
@@ -218,7 +207,18 @@ public class NoSqlSession extends AbstractSession
     /* ------------------------------------------------------------ */
     public Object getVersion()
     {
-    	return _version;
+        return _version;
+    }
+
+    @Override
+    public void setClusterId(String clusterId)
+    {
+        super.setClusterId(clusterId);
+    }
+
+    @Override
+    public void setNodeId(String nodeId)
+    {
+        super.setNodeId(nodeId);
     }
-    
 }
diff --git a/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/NoSqlSessionManager.java b/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/NoSqlSessionManager.java
index 7ea2ae6..74cd138 100644
--- a/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/NoSqlSessionManager.java
+++ b/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/NoSqlSessionManager.java
@@ -21,6 +21,7 @@ package org.eclipse.jetty.nosql;
 import java.util.ArrayList;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.TimeUnit;
 
 import javax.servlet.http.HttpServletRequest;
 
@@ -30,6 +31,12 @@ import org.eclipse.jetty.server.session.AbstractSessionManager;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
 
+/**
+ * NoSqlSessionManager
+ *
+ * Base class for SessionManager implementations using nosql frameworks
+ * 
+ */
 public abstract class NoSqlSessionManager extends AbstractSessionManager implements SessionManager
 {
     private final static Logger __log = Log.getLogger("org.eclipse.jetty.server.session");
@@ -40,11 +47,11 @@ public abstract class NoSqlSessionManager extends AbstractSessionManager impleme
     private int _savePeriod=0;
     private int _idlePeriod=-1;
     private boolean _invalidateOnStop;
-    private boolean _preserveOnStop;
+    private boolean _preserveOnStop = true;
     private boolean _saveAllAttributes;
     
     /* ------------------------------------------------------------ */
-    /* (non-Javadoc)
+    /**
      * @see org.eclipse.jetty.server.session.AbstractSessionManager#doStart()
      */
     @Override
@@ -59,7 +66,12 @@ public abstract class NoSqlSessionManager extends AbstractSessionManager impleme
     protected void addSession(AbstractSession session)
     {
         if (isRunning())
+        {
+            //add into memory
             _sessions.put(session.getClusterId(),(NoSqlSession)session);
+            //add into db
+            ((NoSqlSession)session).save(true);
+        }
     }
 
     /* ------------------------------------------------------------ */
@@ -67,15 +79,16 @@ public abstract class NoSqlSessionManager extends AbstractSessionManager impleme
     public AbstractSession getSession(String idInCluster)
     {
         NoSqlSession session = _sessions.get(idInCluster);
-        
-        __log.debug("getSession: " + session );
+        __log.debug("getSession {} ", session );
         
         if (session==null)
         {
+            //session not in this node's memory, load it
             session=loadSession(idInCluster);
             
             if (session!=null)
             {
+                //session exists, check another request thread hasn't loaded it too
                 NoSqlSession race=_sessions.putIfAbsent(idInCluster,session);
                 if (race!=null)
                 {
@@ -83,103 +96,145 @@ public abstract class NoSqlSessionManager extends AbstractSessionManager impleme
                     session.clearAttributes();
                     session=race;
                 }
+                else
+                    __log.debug("session loaded ", idInCluster);
+                
+                //check if the session we just loaded has actually expired, maybe while we weren't running
+                if (getMaxInactiveInterval() > 0 && session.getAccessed() > 0 && ((getMaxInactiveInterval()*1000L)+session.getAccessed()) < System.currentTimeMillis())
+                {
+                    __log.debug("session expired ", idInCluster);
+                    expire(idInCluster);
+                    session = null;
+                }
             }
+            else
+                __log.debug("session does not exist {}", idInCluster);
         }
-        
+
         return session;
     }
     
     /* ------------------------------------------------------------ */
     @Override
-    protected void invalidateSessions() throws Exception
+    protected void shutdownSessions() throws Exception
     {
-        // Invalidate all sessions to cause unbind events
+        //If we are stopping, and we're preserving sessions, then we want to
+        //save all of the sessions (including those that have been added during this method call)
+        //and then just remove them from memory.
+        
+        //If we don't wish to preserve sessions and we're stopping, then we should invalidate
+        //the session (which may remove it).
+        long gracefulStopMs = getContextHandler().getServer().getStopTimeout();
+        long stopTime = 0;
+        if (gracefulStopMs > 0)
+            stopTime = System.nanoTime() + (TimeUnit.NANOSECONDS.convert(gracefulStopMs, TimeUnit.MILLISECONDS));        
+        
         ArrayList<NoSqlSession> sessions=new ArrayList<NoSqlSession>(_sessions.values());
-        int loop=100;
-        while (sessions.size()>0 && loop-->0)
+
+        // loop while there are sessions, and while there is stop time remaining, or if no stop time, just 1 loop
+        while (sessions.size() > 0 && ((stopTime > 0 && (System.nanoTime() < stopTime)) || (stopTime == 0)))
         {
-            // If we are called from doStop
-            if (isStopping())
+            for (NoSqlSession session : sessions)
             {
-                // Then we only save and remove the session - it is not invalidated.
-                for (NoSqlSession session : sessions)
+                if (isPreserveOnStop())
                 {
+                    //we don't want to delete the session, so save the session
+                    //and remove from memory
                     session.save(false);
-
-                    if (!_preserveOnStop) {
-                        removeSession(session,false);
-                    }
+                    _sessions.remove(session.getClusterId());
+                }
+                else
+                {
+                  //invalidate the session so listeners will be called and also removes the session
+                  session.invalidate();
                 }
             }
-            else
+            
+            //check if we should terminate our loop if we're not using the stop timer
+            if (stopTime == 0)
             {
-                for (NoSqlSession session : sessions)
-                    session.invalidate();
+                break;
             }
-            
-            // check that no new sessions were created while we were iterating
+            // Get any sessions that were added by other requests during processing and go around the loop again
             sessions=new ArrayList<NoSqlSession>(_sessions.values());
         }
     }
     
+
     /* ------------------------------------------------------------ */
     @Override
     protected AbstractSession newSession(HttpServletRequest request)
     {
-        long created=System.currentTimeMillis();
         return new NoSqlSession(this,request);
     }
 
     /* ------------------------------------------------------------ */
+    /** Remove the session from the in-memory list for this context.
+     * Also remove the context sub-document for this session id from the db.
+     * @see org.eclipse.jetty.server.session.AbstractSessionManager#removeSession(java.lang.String)
+     */
     @Override
     protected boolean removeSession(String idInCluster)
     {
-        synchronized (this)
-        {
-            NoSqlSession session = _sessions.remove(idInCluster);
+        NoSqlSession session = _sessions.remove(idInCluster);
 
-            try
-            {
-                if (session != null)
-                {
-                    return remove(session);
-                }
-            }
-            catch (Exception e)
+        try
+        {
+            if (session != null)
             {
-                __log.warn("Problem deleting session id=" + idInCluster,e);
+                return remove(session);
             }
-
-            return session != null;
         }
+        catch (Exception e)
+        {
+            __log.warn("Problem deleting session {}", idInCluster,e);
+        }
+
+        return session != null;
+
     }
 
     /* ------------------------------------------------------------ */
-    protected void invalidateSession( String idInCluster )
+    protected void expire( String idInCluster )
     {
-        synchronized (this)
-        {
-            NoSqlSession session = _sessions.remove(idInCluster);
+        //get the session from memory
+        NoSqlSession session = _sessions.get(idInCluster);
 
-            try
+        try
+        {
+            if (session == null)
             {
-                if (session != null)
-                {
-                    remove(session);
-                }
+                //we need to expire the session with its listeners, so load it
+                session = loadSession(idInCluster);
             }
-            catch (Exception e)
+
+            if (session != null)
+                session.timeout();
+        }
+        catch (Exception e)
+        {
+            __log.warn("Problem expiring session {}", idInCluster,e);
+        }
+    }
+
+
+    public void invalidateSession (String idInCluster)
+    {
+        NoSqlSession session = _sessions.get(idInCluster);
+        try
+        {
+            __log.debug("invalidating session {}", idInCluster);
+            if (session != null)
             {
-                __log.warn("Problem deleting session id=" + idInCluster,e);
+                session.invalidate();
             }
         }
-        
-        /*
-         * ought we not go to cluster and mark it invalid?
-         */
-        
+        catch (Exception e)
+        {
+            __log.warn("Problem invalidating session {}", idInCluster,e); 
+        }
     }
-    
+
     
     /* ------------------------------------------------------------ */
     /**
@@ -332,6 +387,35 @@ public abstract class NoSqlSessionManager extends AbstractSessionManager impleme
     }
     
     /* ------------------------------------------------------------ */
+    @Override
+    public void renewSessionId(String oldClusterId, String oldNodeId, String newClusterId, String newNodeId)
+    {
+        
+        // Take the old session out of the list of sessions
+        // Change to the new id
+        // Put it back into the list of sessions
+        // Update permanent storage
+
+        synchronized (this)
+        {
+            try
+            {
+                NoSqlSession session = _sessions.remove(oldClusterId);
+                update (session, newClusterId, newNodeId);
+                session.setClusterId(newClusterId);
+                session.setNodeId(newNodeId);
+                _sessions.put(newClusterId, session);
+            }
+            catch (Exception e)
+            {
+                __log.warn(e);
+            }
+        }
+        super.renewSessionId(oldClusterId, oldNodeId, newClusterId, newNodeId);
+    }
+
+    
+    /* ------------------------------------------------------------ */
     abstract protected NoSqlSession loadSession(String clusterId);
     
     /* ------------------------------------------------------------ */
@@ -342,5 +426,8 @@ public abstract class NoSqlSessionManager extends AbstractSessionManager impleme
 
     /* ------------------------------------------------------------ */
     abstract protected boolean remove(NoSqlSession session);
+
+    /* ------------------------------------------------------------ */
+    abstract protected void update(NoSqlSession session, String newClusterId, String newNodeId) throws Exception;
     
 }
diff --git a/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/mongodb/MongoSessionIdManager.java b/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/mongodb/MongoSessionIdManager.java
index 1f58f47..0038267 100644
--- a/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/mongodb/MongoSessionIdManager.java
+++ b/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/mongodb/MongoSessionIdManager.java
@@ -23,8 +23,7 @@ import java.net.UnknownHostException;
 import java.util.HashSet;
 import java.util.Random;
 import java.util.Set;
-import java.util.Timer;
-import java.util.TimerTask;
+import java.util.concurrent.TimeUnit;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpSession;
@@ -37,6 +36,8 @@ import org.eclipse.jetty.server.session.AbstractSessionIdManager;
 import org.eclipse.jetty.server.session.SessionHandler;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
+import org.eclipse.jetty.util.thread.Scheduler;
 
 import com.mongodb.BasicDBObject;
 import com.mongodb.BasicDBObjectBuilder;
@@ -47,16 +48,15 @@ import com.mongodb.Mongo;
 import com.mongodb.MongoException;
 
 /**
- * Based partially on the jdbc session id manager...
+ * Based partially on the JDBCSessionIdManager.
  *
  * Theory is that we really only need the session id manager for the local 
  * instance so we have something to scavenge on, namely the list of known ids
  * 
- * this class has a timer that runs at the scavenge delay that runs a query
- *  for all id's known to this node and that have and old accessed value greater
- *  then the scavengeDelay.
+ * This class has a timer that runs a periodic scavenger thread to query
+ *  for all id's known to this node whose precalculated expiry time has passed.
  *  
- * these found sessions are then run through the invalidateAll(id) method that 
+ * These found sessions are then run through the invalidateAll(id) method that 
  * is a bit hinky but is supposed to notify all handlers this id is now DOA and 
  * ought to be cleaned up.  this ought to result in a save operation on the session
  * that will change the valid field to false (this conjecture is unvalidated atm)
@@ -69,18 +69,19 @@ public class MongoSessionIdManager extends AbstractSessionIdManager
     final static DBObject __valid_false = new BasicDBObject(MongoSessionManager.__VALID,false);
     final static DBObject __valid_true = new BasicDBObject(MongoSessionManager.__VALID,true);
 
+    final static long __defaultScavengePeriod = 30 * 60 * 1000; // every 30 minutes
+   
     
     final DBCollection _sessions;
     protected Server _server;
-    private Timer _scavengeTimer;
-    private Timer _purgeTimer;
-    private TimerTask _scavengerTask;
-    private TimerTask _purgeTask;
+    private Scheduler _scheduler;
+    private boolean _ownScheduler;
+    private Scheduler.Task _scavengerTask;
+    private Scheduler.Task _purgerTask;
+ 
 
     
-    
-    private long _scavengeDelay = 30 * 60 * 1000; // every 30 minutes
-    private long _scavengePeriod = 10 * 6 * 1000; // wait at least 10 minutes
+    private long _scavengePeriod = __defaultScavengePeriod;
     
 
     /** 
@@ -113,6 +114,52 @@ public class MongoSessionIdManager extends AbstractSessionIdManager
      */
     protected final Set<String> _sessionsIds = new HashSet<String>();
     
+    
+    /**
+     * Scavenger
+     *
+     */
+    protected class Scavenger implements Runnable
+    {
+        @Override
+        public void run()
+        {
+            try
+            {
+                scavenge();
+            }
+            finally
+            {
+                if (_scheduler != null && _scheduler.isRunning())
+                    _scavengerTask = _scheduler.schedule(this, _scavengePeriod, TimeUnit.MILLISECONDS);
+            }
+        } 
+    }
+    
+    
+    /**
+     * Purger
+     *
+     */
+    protected class Purger implements Runnable
+    {
+        @Override
+        public void run()
+        {
+            try
+            {
+                purge();
+            }
+            finally
+            {
+                if (_scheduler != null && _scheduler.isRunning())
+                    _purgerTask = _scheduler.schedule(this, _purgeDelay, TimeUnit.MILLISECONDS);
+            }
+        }
+    }
+    
+    
+    
 
     /* ------------------------------------------------------------ */
     public MongoSessionIdManager(Server server) throws UnknownHostException, MongoException
@@ -145,40 +192,39 @@ public class MongoSessionIdManager extends AbstractSessionIdManager
      */
     protected void scavenge()
     {
-        __log.debug("SessionIdManager:scavenge:called with delay" + _scavengeDelay);
-                
+        long now = System.currentTimeMillis();
+        __log.debug("SessionIdManager:scavenge:at {}", now);        
         synchronized (_sessionsIds)
         {         
             /*
              * run a query returning results that:
              *  - are in the known list of sessionIds
-             *  - have an accessed time less then current time - the scavenger period
+             *  - the expiry time has passed
              *  
              *  we limit the query to return just the __ID so we are not sucking back full sessions
              */
             BasicDBObject query = new BasicDBObject();     
             query.put(MongoSessionManager.__ID,new BasicDBObject("$in", _sessionsIds ));
-            query.put(MongoSessionManager.__ACCESSED, new BasicDBObject("$lt",System.currentTimeMillis() - _scavengeDelay));
+            query.put(MongoSessionManager.__EXPIRY, new BasicDBObject("$gt", 0));
+            query.put(MongoSessionManager.__EXPIRY, new BasicDBObject("$lt", now));
+        
             
             DBCursor checkSessions = _sessions.find(query, new BasicDBObject(MongoSessionManager.__ID, 1));
                         
             for ( DBObject session : checkSessions )
             {             
-                __log.debug("SessionIdManager:scavenge: invalidating " + (String)session.get(MongoSessionManager.__ID));
-                invalidateAll((String)session.get(MongoSessionManager.__ID));
+                __log.debug("SessionIdManager:scavenge: expiring session {}", (String)session.get(MongoSessionManager.__ID));
+                expireAll((String)session.get(MongoSessionManager.__ID));
             }
-        } 
-        
+        }      
     }
     
     /* ------------------------------------------------------------ */
     /**
-     * ScavengeFully is a process that periodically checks the tracked session
-     * ids of this given instance of the session id manager to see if they 
-     * are past the point of expiration.
+     * ScavengeFully will expire all sessions. In most circumstances
+     * you should never need to call this method.
      * 
-     * NOTE: this is potentially devastating and may lead to serious session
-     * coherence issues, not to be used in a running cluster
+     * <b>USE WITH CAUTION</b>
      */
     protected void scavengeFully()
     {        
@@ -188,7 +234,7 @@ public class MongoSessionIdManager extends AbstractSessionIdManager
 
         for (DBObject session : checkSessions)
         {
-            invalidateAll((String)session.get(MongoSessionManager.__ID));
+            expireAll((String)session.get(MongoSessionManager.__ID));
         }
 
     }
@@ -200,7 +246,7 @@ public class MongoSessionIdManager extends AbstractSessionIdManager
      * 
      * There are two checks being done here:
      * 
-     *  - if the accessed time is older then the current time minus the purge invalid age
+     *  - if the accessed time is older than the current time minus the purge invalid age
      *    and it is no longer valid then remove that session
      *  - if the accessed time is older then the current time minus the purge valid age
      *    then we consider this a lost record and remove it
@@ -213,6 +259,7 @@ public class MongoSessionIdManager extends AbstractSessionIdManager
      */
     protected void purge()
     {
+        __log.debug("PURGING");
         BasicDBObject invalidQuery = new BasicDBObject();
 
         invalidQuery.put(MongoSessionManager.__ACCESSED, new BasicDBObject("$lt",System.currentTimeMillis() - _purgeInvalidAge));
@@ -224,7 +271,7 @@ public class MongoSessionIdManager extends AbstractSessionIdManager
         {
             String id = (String)session.get("id");
             
-            __log.debug("MongoSessionIdManager:purging invalid " + id);
+            __log.debug("MongoSessionIdManager:purging invalid session {}", id);
             
             _sessions.remove(session);
         }
@@ -242,7 +289,7 @@ public class MongoSessionIdManager extends AbstractSessionIdManager
             {
                 String id = (String)session.get(MongoSessionManager.__ID);
 
-                __log.debug("MongoSessionIdManager:purging valid " + id);
+                __log.debug("MongoSessionIdManager:purging valid session {}", id);
 
                 _sessions.remove(session);
             }
@@ -259,7 +306,6 @@ public class MongoSessionIdManager extends AbstractSessionIdManager
     protected void purgeFully()
     {
         BasicDBObject invalidQuery = new BasicDBObject();
-
         invalidQuery.put(MongoSessionManager.__VALID, false);
         
         DBCursor oldSessions = _sessions.find(invalidQuery, new BasicDBObject(MongoSessionManager.__ID, 1));
@@ -268,7 +314,7 @@ public class MongoSessionIdManager extends AbstractSessionIdManager
         {
             String id = (String)session.get(MongoSessionManager.__ID);
             
-            __log.debug("MongoSessionIdManager:purging invalid " + id);
+            __log.debug("MongoSessionIdManager:purging invalid session {}", id);
             
             _sessions.remove(session);
         }
@@ -295,22 +341,21 @@ public class MongoSessionIdManager extends AbstractSessionIdManager
         this._purge = purge;
     }
 
-    /* ------------------------------------------------------------ */
-    /**
-     * sets the scavengeDelay
-     */
-    public void setScavengeDelay(long scavengeDelay)
-    {
-        this._scavengeDelay = scavengeDelay;  
-    }
-
 
     /* ------------------------------------------------------------ */
+    /** 
+     * The period in seconds between scavenge checks.
+     * 
+     * @param scavengePeriod
+     */
     public void setScavengePeriod(long scavengePeriod)
     {
-        this._scavengePeriod = scavengePeriod;
+        if (scavengePeriod <= 0)
+            _scavengePeriod = __defaultScavengePeriod;
+        else
+            _scavengePeriod = TimeUnit.SECONDS.toMillis(scavengePeriod);
     }
-    
+
     /* ------------------------------------------------------------ */
     public void setPurgeDelay(long purgeDelay)
     {
@@ -361,84 +406,84 @@ public class MongoSessionIdManager extends AbstractSessionIdManager
     protected void doStart() throws Exception
     {
         __log.debug("MongoSessionIdManager:starting");
-     
-        /*
-         * setup the scavenger thread
-         */
-        if (_scavengeDelay > 0)
+
+
+        synchronized (this)
         {
-            _scavengeTimer = new Timer("MongoSessionIdScavenger",true);
+            //try and use a common scheduler, fallback to own
+            _scheduler =_server.getBean(Scheduler.class);
+            if (_scheduler == null)
+            {
+                _scheduler = new ScheduledExecutorScheduler();
+                _ownScheduler = true;
+                _scheduler.start();
+            }   
+            else if (!_scheduler.isStarted())
+                throw new IllegalStateException("Shared scheduler not started");
+            
 
-            synchronized (this)
+            //setup the scavenger thread
+            if (_scavengePeriod > 0)
             {
                 if (_scavengerTask != null)
                 {
                     _scavengerTask.cancel();
+                    _scavengerTask = null;
                 }
-                
-                _scavengerTask = new TimerTask()
-                {
-                    @Override
-                    public void run()
-                    {
-                        scavenge();
-                    }
-                };
-                
-                _scavengeTimer.schedule(_scavengerTask,_scavengeDelay,_scavengePeriod);
+
+                _scavengerTask = _scheduler.schedule(new Scavenger(), _scavengePeriod, TimeUnit.MILLISECONDS);
             }
-        }
-        
-        /*
-         * if purging is enabled, setup the purge thread
-         */
-        if ( _purge )
-        {
-            _purgeTimer = new Timer("MongoSessionPurger", true);
-            
-            synchronized (this)
-            {
-                if (_purgeTask != null)
+            else if (__log.isDebugEnabled())
+                __log.debug("Scavenger disabled");
+
+
+            //if purging is enabled, setup the purge thread
+            if ( _purge )
+            { 
+                if (_purgerTask != null)
                 {
-                    _purgeTask.cancel();
+                    _purgerTask.cancel();
+                    _purgerTask = null;
                 }
-                _purgeTask = new TimerTask()
-                {
-                    @Override
-                    public void run()
-                    {
-                        purge();
-                    }
-                };
-               
-                _purgeTimer.schedule(_purgeTask,0,_purgeDelay);
+                _purgerTask = _scheduler.schedule(new Purger(), _purgeDelay, TimeUnit.MILLISECONDS);
             }
+            else if (__log.isDebugEnabled())
+                __log.debug("Purger disabled");
         }
     }
-    
+
     /* ------------------------------------------------------------ */
     @Override
     protected void doStop() throws Exception
     {
-        if (_scavengeTimer != null)
+        synchronized (this)
         {
-            _scavengeTimer.cancel();
-            _scavengeTimer = null;
-        }
-        
-        if (_purgeTimer != null)
-        {
-            _purgeTimer.cancel();
-            _purgeTimer = null;
+            if (_scavengerTask != null)
+            {
+                _scavengerTask.cancel();
+                _scavengerTask = null;
+            }
+ 
+            if (_purgerTask != null)
+            {
+                _purgerTask.cancel();
+                _purgerTask = null;
+            }
+            
+            if (_ownScheduler && _scheduler != null)
+            {
+                _scheduler.stop();
+                _scheduler = null;
+            }
         }
-        
         super.doStop();
     }
 
     /* ------------------------------------------------------------ */
     /**
-     * is the session id known to mongo, and is it valid
+     * Searches database to find if the session id known to mongo, and is it valid
      */
+    @Override
     public boolean idInUse(String sessionId)
     {        
         /*
@@ -449,11 +494,10 @@ public class MongoSessionIdManager extends AbstractSessionIdManager
         if ( o != null )
         {                    
             Boolean valid = (Boolean)o.get(MongoSessionManager.__VALID);
-            
             if ( valid == null )
             {
                 return false;
-            }
+            }            
             
             return valid;
         }
@@ -462,6 +506,7 @@ public class MongoSessionIdManager extends AbstractSessionIdManager
     }
 
     /* ------------------------------------------------------------ */
+    @Override
     public void addSession(HttpSession session)
     {
         if (session == null)
@@ -473,7 +518,7 @@ public class MongoSessionIdManager extends AbstractSessionIdManager
          * already a part of the index in mongo...
          */
         
-        __log.debug("MongoSessionIdManager:addSession:" + session.getId());
+        __log.debug("MongoSessionIdManager:addSession {}", session.getId());
         
         synchronized (_sessionsIds)
         {
@@ -483,6 +528,7 @@ public class MongoSessionIdManager extends AbstractSessionIdManager
     }
 
     /* ------------------------------------------------------------ */
+    @Override
     public void removeSession(HttpSession session)
     {
         if (session == null)
@@ -497,19 +543,24 @@ public class MongoSessionIdManager extends AbstractSessionIdManager
     }
 
     /* ------------------------------------------------------------ */
+    /** Remove the session id from the list of in-use sessions.
+     * Inform all other known contexts that sessions with the same id should be
+     * invalidated.
+     * @see org.eclipse.jetty.server.SessionIdManager#invalidateAll(java.lang.String)
+     */
+    @Override
     public void invalidateAll(String sessionId)
     {
         synchronized (_sessionsIds)
         {
             _sessionsIds.remove(sessionId);
-            
-            
+                
             //tell all contexts that may have a session object with this id to
             //get rid of them
             Handler[] contexts = _server.getChildHandlersByClass(ContextHandler.class);
             for (int i=0; contexts!=null && i<contexts.length; i++)
             {
-                SessionHandler sessionHandler = (SessionHandler)((ContextHandler)contexts[i]).getChildHandlerByClass(SessionHandler.class);
+                SessionHandler sessionHandler = ((ContextHandler)contexts[i]).getChildHandlerByClass(SessionHandler.class);
                 if (sessionHandler != null) 
                 {
                     SessionManager manager = sessionHandler.getSessionManager();
@@ -521,24 +572,68 @@ public class MongoSessionIdManager extends AbstractSessionIdManager
                 }
             }
         }      
-    }
+    } 
 
     /* ------------------------------------------------------------ */
-    // TODO not sure if this is correct
-    public String getClusterId(String nodeId)
+    /**
+     * Expire this session for all contexts that are sharing the session 
+     * id.
+     * @param sessionId
+     */
+    public void expireAll (String sessionId)
     {
-        int dot=nodeId.lastIndexOf('.');
-        return (dot>0)?nodeId.substring(0,dot):nodeId;
-    }
+        synchronized (_sessionsIds)
+        {
+            _sessionsIds.remove(sessionId);
+            
+            
+            //tell all contexts that may have a session object with this id to
+            //get rid of them
+            Handler[] contexts = _server.getChildHandlersByClass(ContextHandler.class);
+            for (int i=0; contexts!=null && i<contexts.length; i++)
+            {
+                SessionHandler sessionHandler = ((ContextHandler)contexts[i]).getChildHandlerByClass(SessionHandler.class);
+                if (sessionHandler != null) 
+                {
+                    SessionManager manager = sessionHandler.getSessionManager();
 
+                    if (manager != null && manager instanceof MongoSessionManager)
+                    {
+                        ((MongoSessionManager)manager).expire(sessionId);
+                    }
+                }
+            }
+        }      
+    }
+    
     /* ------------------------------------------------------------ */
-    // TODO not sure if this is correct
-    public String getNodeId(String clusterId, HttpServletRequest request)
+    @Override
+    public void renewSessionId(String oldClusterId, String oldNodeId, HttpServletRequest request)
     {
-        if (_workerName!=null)
-            return clusterId+'.'+_workerName;
+        //generate a new id
+        String newClusterId = newSessionId(request.hashCode());
 
-        return clusterId;
+        synchronized (_sessionsIds)
+        {
+            _sessionsIds.remove(oldClusterId);//remove the old one from the list
+            _sessionsIds.add(newClusterId); //add in the new session id to the list
+
+            //tell all contexts to update the id 
+            Handler[] contexts = _server.getChildHandlersByClass(ContextHandler.class);
+            for (int i=0; contexts!=null && i<contexts.length; i++)
+            {
+                SessionHandler sessionHandler = ((ContextHandler)contexts[i]).getChildHandlerByClass(SessionHandler.class);
+                if (sessionHandler != null) 
+                {
+                    SessionManager manager = sessionHandler.getSessionManager();
+
+                    if (manager != null && manager instanceof MongoSessionManager)
+                    {
+                        ((MongoSessionManager)manager).renewSessionId(oldClusterId, oldNodeId, newClusterId, getNodeId(newClusterId, request));
+                    }
+                }
+            }
+        }
     }
 
 }
diff --git a/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/mongodb/MongoSessionManager.java b/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/mongodb/MongoSessionManager.java
index ff72c9e..8feff53 100644
--- a/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/mongodb/MongoSessionManager.java
+++ b/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/mongodb/MongoSessionManager.java
@@ -32,6 +32,9 @@ import java.util.Set;
 import org.eclipse.jetty.nosql.NoSqlSession;
 import org.eclipse.jetty.nosql.NoSqlSessionManager;
 import org.eclipse.jetty.server.SessionIdManager;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.annotation.ManagedOperation;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
 
@@ -39,7 +42,60 @@ import com.mongodb.BasicDBObject;
 import com.mongodb.DBCollection;
 import com.mongodb.DBObject;
 import com.mongodb.MongoException;
-
+import com.mongodb.WriteConcern;
+import com.mongodb.WriteResult;
+
+
+/**
+ * MongoSessionManager
+ *
+ * Clustered session manager using MongoDB as the shared DB instance.
+ * The document model is an outer object that contains the elements:
+ * <ul>
+ *  <li>"id"      : session_id </li>
+ *  <li>"created" : create_time </li>
+ *  <li>"accessed": last_access_time </li>
+ *  <li>"maxIdle" : max_idle_time setting as session was created </li>
+ *  <li>"expiry"  : time at which session should expire </li>
+ *  <li>"valid"   : session_valid </li>
+ *  <li>"context" : a nested object containing 1 nested object per context for which the session id is in use
+ * </ul>
+ * Each of the nested objects inside the "context" element contains:
+ * <ul>
+ *  <li>unique_context_name : nested object containing name:value pairs of the session attributes for that context</li>
+ * </ul>
+ * <p>
+ * One of the name:value attribute pairs will always be the special attribute "__metadata__". The value 
+ * is an object representing a version counter which is incremented every time the attributes change.
+ * </p>
+ * <p>
+ * For example:
+ * <code>
+ * { "_id"       : ObjectId("52845534a40b66410f228f23"), 
+ *    "accessed" :  NumberLong("1384818548903"), 
+ *    "maxIdle"  : 1,
+ *    "context"  : { "::/contextA" : { "A"            : "A", 
+ *                                     "__metadata__" : { "version" : NumberLong(2) } 
+ *                                   },
+ *                   "::/contextB" : { "B"            : "B", 
+ *                                     "__metadata__" : { "version" : NumberLong(1) } 
+ *                                   } 
+ *                 }, 
+ *    "created"  : NumberLong("1384818548903"),
+ *    "expiry"   : NumberLong("1384818549903"),
+ *    "id"       : "w01ijx2vnalgv1sqrpjwuirprp7", 
+ *    "valid"    : true 
+ * }
+ * </code>
+ * </p>
+ * <p>
+ * In MongoDB, the nesting level is indicated by "." separators for the key name. Thus to
+ * interact with a session attribute, the key is composed of:
+ * "context".unique_context_name.attribute_name
+ *  Eg  "context"."::/contextA"."A"
+ *  </p>
+ */
+ at ManagedObject("Mongo Session Manager")
 public class MongoSessionManager extends NoSqlSessionManager
 {
     private static final Logger LOG = Log.getLogger(MongoSessionManager.class);
@@ -49,14 +105,56 @@ public class MongoSessionManager extends NoSqlSessionManager
     /*
      * strings used as keys or parts of keys in mongo
      */
+    /**
+     * Special attribute for a session that is context-specific
+     */
     private final static String __METADATA = "__metadata__";
 
+    
+    /**
+     * Session id
+     */
     public final static String __ID = "id";
+    
+    /**
+     * Time of session creation
+     */
     private final static String __CREATED = "created";
+    
+    /**
+     * Whether or not session is valid
+     */
     public final static String __VALID = "valid";
+    
+    /**
+     * Time at which session was invalidated
+     */
     public final static String __INVALIDATED = "invalidated";
+    
+    /**
+     * Last access time of session
+     */
     public final static String __ACCESSED = "accessed";
+    
+    /**
+     * Time this session will expire, based on last access time and maxIdle
+     */
+    public final static String __EXPIRY = "expiry";
+    
+    /**
+     * The max idle time of a session (smallest value across all contexts which has a session with the same id)
+     */
+    public final static String __MAX_IDLE = "maxIdle";
+    
+    /**
+     * Name of nested document field containing 1 sub document per context for which the session id is in use
+     */
     private final static String __CONTEXT = "context";   
+    
+    
+    /**
+     * Special attribute per session per context, incremented each time attributes are modified
+     */
     public final static String __VERSION = __METADATA + ".version";
 
     /**
@@ -65,8 +163,16 @@ public class MongoSessionManager extends NoSqlSessionManager
     private String _contextId = null;
 
     
-    private DBCollection _sessions;
-    private DBObject __version_1;
+    /**
+     * Access to MongoDB
+     */
+    private DBCollection _dbSessions;
+    
+    
+    /**
+     * Utility value of 1 for a session version for this context
+     */
+    private DBObject _version_1;
 
 
     /* ------------------------------------------------------------ */
@@ -83,8 +189,7 @@ public class MongoSessionManager extends NoSqlSessionManager
     {
         super.doStart();
         String[] hosts = getContextHandler().getVirtualHosts();
-        if (hosts == null || hosts.length == 0)
-            hosts = getContextHandler().getConnectorNames();
+
         if (hosts == null || hosts.length == 0)
             hosts = new String[]
             { "::" }; // IPv6 equiv of 0.0.0.0
@@ -96,19 +201,18 @@ public class MongoSessionManager extends NoSqlSessionManager
         }
 
         _contextId = createContextId(hosts,contextPath);
-
-        __version_1 = new BasicDBObject(getContextKey(__VERSION),1);
+        _version_1 = new BasicDBObject(getContextAttributeKey(__VERSION),1);
     }
 
     /* ------------------------------------------------------------ */
-    /* (non-Javadoc)
+    /**
      * @see org.eclipse.jetty.server.session.AbstractSessionManager#setSessionIdManager(org.eclipse.jetty.server.SessionIdManager)
      */
     @Override
     public void setSessionIdManager(SessionIdManager metaManager)
     {
         MongoSessionIdManager msim = (MongoSessionIdManager)metaManager;
-        _sessions=msim.getSessions();
+        _dbSessions=msim.getSessions();
         super.setSessionIdManager(metaManager);
         
     }
@@ -119,7 +223,7 @@ public class MongoSessionManager extends NoSqlSessionManager
     {
         try
         {
-            __log.debug("MongoSessionManager:save:" + session);
+            __log.debug("MongoSessionManager:save session {}", session.getClusterId());
             session.willPassivate();
 
             // Form query for upsert
@@ -131,9 +235,13 @@ public class MongoSessionManager extends NoSqlSessionManager
             BasicDBObject sets = new BasicDBObject();
             BasicDBObject unsets = new BasicDBObject();
 
+            
             // handle valid or invalid
             if (session.isValid())
             {
+                long expiry = (session.getMaxInactiveInterval() > 0?(session.getAccessed()+(1000L*getMaxInactiveInterval())):0);
+                __log.debug("MongoSessionManager: calculated expiry {} for session {}", expiry, session.getId());
+                
                 // handle new or existing
                 if (version == null)
                 {
@@ -142,12 +250,29 @@ public class MongoSessionManager extends NoSqlSessionManager
                     version = new Long(1);
                     sets.put(__CREATED,session.getCreationTime());
                     sets.put(__VALID,true);
-                    sets.put(getContextKey(__VERSION),version);
+                   
+                    sets.put(getContextAttributeKey(__VERSION),version);
+                    sets.put(__MAX_IDLE, getMaxInactiveInterval());
+                    sets.put(__EXPIRY, expiry);
                 }
                 else
                 {
                     version = new Long(((Number)version).longValue() + 1);
-                    update.put("$inc",__version_1); 
+                    update.put("$inc",_version_1); 
+                    //if max idle time and/or expiry is smaller for this context, then choose that for the whole session doc
+                    BasicDBObject fields = new BasicDBObject();
+                    fields.append(__MAX_IDLE, true);
+                    fields.append(__EXPIRY, true);
+                    DBObject o = _dbSessions.findOne(new BasicDBObject("id",session.getClusterId()), fields);
+                    if (o != null)
+                    {
+                        Integer currentMaxIdle = (Integer)o.get(__MAX_IDLE);
+                        Long currentExpiry = (Long)o.get(__EXPIRY);
+                        if (currentMaxIdle != null && getMaxInactiveInterval() > 0 && getMaxInactiveInterval() < currentMaxIdle)
+                            sets.put(__MAX_IDLE, getMaxInactiveInterval());
+                        if (currentExpiry != null && expiry > 0 && expiry != currentExpiry)
+                            sets.put(__EXPIRY, expiry);
+                    }
                 }
                 
                 sets.put(__ACCESSED,session.getAccessed());
@@ -179,9 +304,11 @@ public class MongoSessionManager extends NoSqlSessionManager
             if (!unsets.isEmpty())
                 update.put("$unset",unsets);
 
-            _sessions.update(key,update,upsert,false);
-            __log.debug("MongoSessionManager:save:db.sessions.update(" + key + "," + update + ",true)");
+            _dbSessions.update(key,update,upsert,false,WriteConcern.SAFE);
 
+            if (__log.isDebugEnabled())
+                __log.debug("MongoSessionManager:save:db.sessions.update( {}, {} )", key, update);
+           
             if (activateAfterSave)
                 session.didActivate();
 
@@ -198,20 +325,20 @@ public class MongoSessionManager extends NoSqlSessionManager
     @Override
     protected Object refresh(NoSqlSession session, Object version)
     {
-        __log.debug("MongoSessionManager:refresh " + session);
+        __log.debug("MongoSessionManager:refresh session {}", session.getId());
 
         // check if our in memory version is the same as what is on the disk
         if (version != null)
         {
-            DBObject o = _sessions.findOne(new BasicDBObject(__ID,session.getClusterId()),__version_1);
+            DBObject o = _dbSessions.findOne(new BasicDBObject(__ID,session.getClusterId()),_version_1);
 
             if (o != null)
             {
-                Object saved = getNestedValue(o, getContextKey(__VERSION));
+                Object saved = getNestedValue(o, getContextAttributeKey(__VERSION));
                 
                 if (saved != null && saved.equals(version))
                 {
-                    __log.debug("MongoSessionManager:refresh not needed");
+                    __log.debug("MongoSessionManager:refresh not needed session {}", session.getId());
                     return version;
                 }
                 version = saved;
@@ -219,12 +346,12 @@ public class MongoSessionManager extends NoSqlSessionManager
         }
 
         // If we are here, we have to load the object
-        DBObject o = _sessions.findOne(new BasicDBObject(__ID,session.getClusterId()));
+        DBObject o = _dbSessions.findOne(new BasicDBObject(__ID,session.getClusterId()));
 
         // If it doesn't exist, invalidate
         if (o == null)
         {
-            __log.debug("MongoSessionManager:refresh:marking invalid, no object");
+            __log.debug("MongoSessionManager:refresh:marking session {} invalid, no object", session.getClusterId());
             session.invalidate();
             return null;
         }
@@ -233,7 +360,7 @@ public class MongoSessionManager extends NoSqlSessionManager
         Boolean valid = (Boolean)o.get(__VALID);
         if (valid == null || !valid)
         {
-            __log.debug("MongoSessionManager:refresh:marking invalid, valid flag " + valid);
+            __log.debug("MongoSessionManager:refresh:marking session {} invalid, valid flag {}", session.getClusterId(), valid);
             session.invalidate();
             return null;
         }
@@ -242,41 +369,44 @@ public class MongoSessionManager extends NoSqlSessionManager
         // followed by bindings and then activation.
         session.willPassivate();
         try
-        {
-            session.clearAttributes();
-            
-            DBObject attrs = (DBObject)getNestedValue(o,getContextKey());
-            
-            
-            if (attrs != null)
+        {     
+            DBObject attrs = (DBObject)getNestedValue(o,getContextKey());    
+            //if disk version now has no attributes, get rid of them
+            if (attrs == null || attrs.keySet().size() == 0)
             {
+                session.clearAttributes();
+            }
+            else
+            {
+                //iterate over the names of the attributes on the disk version, updating the value
                 for (String name : attrs.keySet())
                 {
+                    //skip special metadata field which is not one of the session attributes
                     if (__METADATA.equals(name))
-                    {
                         continue;
-                    }
 
                     String attr = decodeName(name);
                     Object value = decodeValue(attrs.get(name));
 
-                    if (attrs.keySet().contains(name))
-                    {
+                    //session does not already contain this attribute, so bind it
+                    if (session.getAttribute(attr) == null)
+                    { 
                         session.doPutOrRemove(attr,value);
                         session.bindValue(attr,value);
                     }
-                    else
+                    else //session already contains this attribute, update its value
                     {
                         session.doPutOrRemove(attr,value);
                     }
+
                 }
                 // cleanup, remove values from session, that don't exist in data anymore:
-                for (String name : session.getNames())
+                for (String str : session.getNames())
                 {
-                    if (!attrs.keySet().contains(name))
-                    {
-                        session.doPutOrRemove(name,null);
-                        session.unbindValue(name,session.getAttribute(name));
+                   if (!attrs.keySet().contains(encodeName(str)))
+                   {
+                        session.doPutOrRemove(str,null);
+                        session.unbindValue(str,session.getAttribute(str));
                     }
                 }
             }
@@ -295,7 +425,7 @@ public class MongoSessionManager extends NoSqlSessionManager
                 update.put("$set",sets);
             }            
             
-            _sessions.update(key,update,false,false);
+            _dbSessions.update(key,update,false,false,WriteConcern.SAFE);
             
             session.didActivate();
 
@@ -313,51 +443,51 @@ public class MongoSessionManager extends NoSqlSessionManager
     @Override
     protected synchronized NoSqlSession loadSession(String clusterId)
     {
-        DBObject o = _sessions.findOne(new BasicDBObject(__ID,clusterId));
-        
-        __log.debug("MongoSessionManager:loaded " + o);
+        DBObject o = _dbSessions.findOne(new BasicDBObject(__ID,clusterId));
         
+        __log.debug("MongoSessionManager:id={} loaded={}", clusterId, o);
         if (o == null)
-        {
             return null;
-        }
         
         Boolean valid = (Boolean)o.get(__VALID);
+        __log.debug("MongoSessionManager:id={} valid={}", clusterId, valid);
         if (valid == null || !valid)
-        {
             return null;
-        }
         
         try
         {
-            Object version = o.get(getContextKey(__VERSION));
+            Object version = o.get(getContextAttributeKey(__VERSION));
             Long created = (Long)o.get(__CREATED);
             Long accessed = (Long)o.get(__ACCESSED);
           
-            NoSqlSession session = new NoSqlSession(this,created,accessed,clusterId,version);
+            NoSqlSession session = null;
 
-            // get the attributes for the context
+            // get the session for the context
             DBObject attrs = (DBObject)getNestedValue(o,getContextKey());
 
-            __log.debug("MongoSessionManager:attrs: " + attrs);
+            __log.debug("MongoSessionManager:attrs {}", attrs);
             if (attrs != null)
             {
+                __log.debug("MongoSessionManager: session {} present for context {}", clusterId, getContextKey());
+                //only load a session if it exists for this context
+                session = new NoSqlSession(this,created,accessed,clusterId,version);
+                
                 for (String name : attrs.keySet())
                 {
+                    //skip special metadata attribute which is not one of the actual session attributes
                     if ( __METADATA.equals(name) )
-                    {
                         continue;
-                    }
                     
                     String attr = decodeName(name);
                     Object value = decodeValue(attrs.get(name));
 
                     session.doPutOrRemove(attr,value);
                     session.bindValue(attr,value);
-                    
                 }
+                session.didActivate();
             }
-            session.didActivate();
+            else
+                __log.debug("MongoSessionManager: session  {} not present for context {}",clusterId, getContextKey());        
 
             return session;
         }
@@ -368,11 +498,17 @@ public class MongoSessionManager extends NoSqlSessionManager
         return null;
     }
 
+    
+    
     /*------------------------------------------------------------ */
+    /** 
+     * Remove the per-context sub document for this session id.
+     * @see org.eclipse.jetty.nosql.NoSqlSessionManager#remove(org.eclipse.jetty.nosql.NoSqlSession)
+     */
     @Override
     protected boolean remove(NoSqlSession session)
     {
-        __log.debug("MongoSessionManager:remove:session " + session.getClusterId());
+        __log.debug("MongoSessionManager:remove:session {} for context {}",session.getClusterId(), getContextKey());
 
         /*
          * Check if the session exists and if it does remove the context
@@ -380,7 +516,7 @@ public class MongoSessionManager extends NoSqlSessionManager
          */
         BasicDBObject key = new BasicDBObject(__ID,session.getClusterId());
         
-        DBObject o = _sessions.findOne(key,__version_1);
+        DBObject o = _dbSessions.findOne(key,_version_1);
 
         if (o != null)
         {
@@ -388,7 +524,7 @@ public class MongoSessionManager extends NoSqlSessionManager
             BasicDBObject unsets = new BasicDBObject();
             unsets.put(getContextKey(),1);
             remove.put("$unset",unsets);
-            _sessions.update(key,remove);
+            _dbSessions.update(key,remove,false,false,WriteConcern.SAFE);
 
             return true;
         }
@@ -398,20 +534,22 @@ public class MongoSessionManager extends NoSqlSessionManager
         }
     }
 
-    /*------------------------------------------------------------ */
+    
+    
+    /** 
+     * @see org.eclipse.jetty.nosql.NoSqlSessionManager#expire(java.lang.String)
+     */
     @Override
-    protected void invalidateSession(String idInCluster)
+    protected void expire (String idInCluster)
     {
-        __log.debug("MongoSessionManager:invalidateSession:invalidating " + idInCluster);
-        
-        super.invalidateSession(idInCluster);
+        __log.debug("MongoSessionManager:expire session {} ", idInCluster);
+
+        //Expire the session for this context
+        super.expire(idInCluster);
         
-        /*
-         * pull back the 'valid' value, we can check if its false, if is we don't need to
-         * reset it to false
-         */
+        //If the outer session document has not already been marked invalid, do so.
         DBObject validKey = new BasicDBObject(__VALID, true);       
-        DBObject o = _sessions.findOne(new BasicDBObject(__ID,idInCluster), validKey);
+        DBObject o = _dbSessions.findOne(new BasicDBObject(__ID,idInCluster), validKey);
         
         if (o != null && (Boolean)o.get(__VALID))
         {
@@ -422,11 +560,26 @@ public class MongoSessionManager extends NoSqlSessionManager
             update.put("$set",sets);
                         
             BasicDBObject key = new BasicDBObject(__ID,idInCluster);
-
-            _sessions.update(key,update);
+            _dbSessions.update(key,update,false,false,WriteConcern.SAFE);
         }       
     }
     
+    
+    /*------------------------------------------------------------ */
+    /** 
+     * Change the session id. Note that this will change the session id for all contexts for which the session id is in use.
+     * @see org.eclipse.jetty.nosql.NoSqlSessionManager#update(org.eclipse.jetty.nosql.NoSqlSession, java.lang.String, java.lang.String)
+     */
+    @Override
+    protected void update(NoSqlSession session, String newClusterId, String newNodeId) throws Exception
+    {
+        BasicDBObject key = new BasicDBObject(__ID, session.getClusterId());
+        BasicDBObject sets = new BasicDBObject();
+        BasicDBObject update = new BasicDBObject(__ID, newClusterId);
+        sets.put("$set", update);
+        _dbSessions.update(key, sets, false, false,WriteConcern.SAFE);
+    }
+
     /*------------------------------------------------------------ */
     protected String encodeName(String name)
     {
@@ -505,30 +658,43 @@ public class MongoSessionManager extends NoSqlSessionManager
     /*------------------------------------------------------------ */
     private String getContextKey()
     {
-    	return __CONTEXT + "." + _contextId;
+        return __CONTEXT + "." + _contextId;
     }
     
     /*------------------------------------------------------------ */
-    private String getContextKey(String keybit)
+    /** Get a dot separated key for 
+     * @param key
+     * @return
+     */
+    private String getContextAttributeKey(String attr)
     {
-    	return __CONTEXT + "." + _contextId + "." + keybit;
+        return getContextKey()+ "." + attr;
     }
     
+    /*------------------------------------------------------------ */
+    @ManagedOperation(value="purge invalid sessions in the session store based on normal criteria", impact="ACTION")
     public void purge()
     {   
         ((MongoSessionIdManager)_sessionIdManager).purge();
     }
     
+    
+    /*------------------------------------------------------------ */
+    @ManagedOperation(value="full purge of invalid sessions in the session store", impact="ACTION")
     public void purgeFully()
     {   
         ((MongoSessionIdManager)_sessionIdManager).purgeFully();
     }
     
+    /*------------------------------------------------------------ */
+    @ManagedOperation(value="scavenge sessions known to this manager", impact="ACTION")
     public void scavenge()
     {
         ((MongoSessionIdManager)_sessionIdManager).scavenge();
     }
     
+    /*------------------------------------------------------------ */
+    @ManagedOperation(value="scanvenge all sessions", impact="ACTION")
     public void scavengeFully()
     {
         ((MongoSessionIdManager)_sessionIdManager).scavengeFully();
@@ -541,9 +707,10 @@ public class MongoSessionManager extends NoSqlSessionManager
      * the count() operation itself is optimized to perform on the server side
      * and avoid loading to client side.
      */
+    @ManagedAttribute("total number of known sessions in the store")
     public long getSessionStoreCount()
     {
-        return _sessions.find().count();      
+        return _dbSessions.find().count();      
     }
     
     /*------------------------------------------------------------ */
@@ -565,6 +732,7 @@ public class MongoSessionManager extends NoSqlSessionManager
         return contextId;
     }
 
+    /*------------------------------------------------------------ */
     /**
      * Dig through a given dbObject for the nested value
      */
@@ -588,6 +756,7 @@ public class MongoSessionManager extends NoSqlSessionManager
     }
 
     
+    /*------------------------------------------------------------ */
      /**
      * ClassLoadingObjectInputStream
      *
@@ -619,4 +788,5 @@ public class MongoSessionManager extends NoSqlSessionManager
         }
     }
 
+
 }
diff --git a/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/mongodb/jmx/MongoSessionManagerMBean.java b/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/mongodb/jmx/MongoSessionManagerMBean.java
index 390904f..58a2b44 100644
--- a/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/mongodb/jmx/MongoSessionManagerMBean.java
+++ b/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/mongodb/jmx/MongoSessionManagerMBean.java
@@ -21,10 +21,11 @@ package org.eclipse.jetty.nosql.mongodb.jmx;
 import org.eclipse.jetty.nosql.mongodb.MongoSessionManager;
 import org.eclipse.jetty.server.handler.AbstractHandlerContainer;
 import org.eclipse.jetty.server.handler.ContextHandler;
-import org.eclipse.jetty.server.session.AbstractSessionManager;
 import org.eclipse.jetty.server.session.SessionHandler;
 import org.eclipse.jetty.server.session.jmx.AbstractSessionManagerMBean;
+import org.eclipse.jetty.util.annotation.ManagedObject;
 
+ at ManagedObject("Mongo Session Manager MBean")
 public class MongoSessionManagerMBean extends AbstractSessionManagerMBean
 {
 
diff --git a/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/mongodb/jmx/package-info.java b/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/mongodb/jmx/package-info.java
new file mode 100644
index 0000000..6e73b03
--- /dev/null
+++ b/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/mongodb/jmx/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty NoSql : MongoDB Sessions JMX Integration
+ */
+package org.eclipse.jetty.nosql.mongodb.jmx;
+
diff --git a/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/mongodb/package-info.java b/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/mongodb/package-info.java
new file mode 100644
index 0000000..9b42286
--- /dev/null
+++ b/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/mongodb/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty NoSql : MongoDB Integration
+ */
+package org.eclipse.jetty.nosql.mongodb;
+
diff --git a/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/package-info.java b/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/package-info.java
new file mode 100644
index 0000000..bb2dd89
--- /dev/null
+++ b/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty NoSql : Generic Nosql Session Management
+ */
+package org.eclipse.jetty.nosql;
+
diff --git a/jetty-nosql/src/main/resources/org/eclipse/jetty/nosql/mongodb/jmx/MongoSessionManager-mbean.properties b/jetty-nosql/src/main/resources/org/eclipse/jetty/nosql/mongodb/jmx/MongoSessionManager-mbean.properties
deleted file mode 100644
index dbce43a..0000000
--- a/jetty-nosql/src/main/resources/org/eclipse/jetty/nosql/mongodb/jmx/MongoSessionManager-mbean.properties
+++ /dev/null
@@ -1,6 +0,0 @@
-MongoSessionManager: Mongo Session Manager
-sessionStoreCount: total number of known sessions in the store
-purge(): force a purge() of invalid sessions in the session store based on normal criteria
-purgeFully(): force a full purge of invalid sessions in the session store
-scavenge(): force a scavenge() of sessions known to this manager in the session store
-scavengeFully(): force a scavenge of all sessions in the session store
diff --git a/jetty-osgi/jetty-osgi-alpn/pom.xml b/jetty-osgi/jetty-osgi-alpn/pom.xml
new file mode 100644
index 0000000..dbf2b84
--- /dev/null
+++ b/jetty-osgi/jetty-osgi-alpn/pom.xml
@@ -0,0 +1,50 @@
+<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">
+  <parent>
+    <groupId>org.eclipse.jetty.osgi</groupId>
+    <artifactId>jetty-osgi-project</artifactId>
+    <version>9.2.13.v20150730</version>
+  </parent>
+  <modelVersion>4.0.0</modelVersion>
+  <artifactId>jetty-osgi-alpn</artifactId>
+  <name>Jetty :: OSGi ALPN Fragment</name>
+  <packaging>jar</packaging>
+  <properties>
+    <bundle-symbolic-name>org.eclipse.jetty.osgi.alpn.fragment</bundle-symbolic-name>
+  </properties>
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>build-helper-maven-plugin</artifactId>
+        <version>1.7</version>
+        <executions>
+          <execution>
+            <id>parse-version</id>
+            <goals>
+              <goal>parse-version</goal>
+            </goals>
+            <configuration>
+              <versionString>${alpn.api.version}</versionString>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-jar-plugin</artifactId>
+        <configuration>
+          <archive>
+            <manifestEntries>
+              <Bundle-ManifestVersion>2</Bundle-ManifestVersion>
+              <Bundle-SymbolicName>${bundle-symbolic-name};singleton:=true</Bundle-SymbolicName>
+              <Bundle-Name>Jetty OSGi ALPN Fragment</Bundle-Name>
+              <Bundle-Version>${parsedVersion.majorVersion}.${parsedVersion.minorVersion}.${parsedVersion.incrementalVersion}</Bundle-Version>
+              <Export-Package>org.eclipse.jetty.alpn;version="${parsedVersion.majorVersion}.${parsedVersion.minorVersion}.${parsedVersion.incrementalVersion}"</Export-Package>
+              <Fragment-Host>system.bundle;extension:=framework</Fragment-Host>
+            </manifestEntries>
+          </archive>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+</project>
diff --git a/jetty-osgi/jetty-osgi-boot-jsp/pom.xml b/jetty-osgi/jetty-osgi-boot-jsp/pom.xml
index 218fe8d..c192f1c 100644
--- a/jetty-osgi/jetty-osgi-boot-jsp/pom.xml
+++ b/jetty-osgi/jetty-osgi-boot-jsp/pom.xml
@@ -2,8 +2,7 @@
   <parent>
     <groupId>org.eclipse.jetty.osgi</groupId>
     <artifactId>jetty-osgi-project</artifactId>
-    <version>8.1.17.v20150415</version>
-    <relativePath>../pom.xml</relativePath>
+    <version>9.2.13.v20150730</version>
   </parent>
   <modelVersion>4.0.0</modelVersion>
   <artifactId>jetty-osgi-boot-jsp</artifactId>
@@ -34,16 +33,37 @@
     </dependency>
     <!-- Orbit Servlet Deps -->
     <dependency>
-      <groupId>org.eclipse.jetty.orbit</groupId>
-      <artifactId>javax.servlet</artifactId>
+      <groupId>javax.servlet</groupId>
+      <artifactId>javax.servlet-api</artifactId>
     </dependency>
     <!-- Orbit JSP Deps -->
     <dependency>
       <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-jsp</artifactId>
+      <artifactId>apache-jsp</artifactId>
       <version>${project.version}</version>
     </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.orbit</groupId>
+      <artifactId>javax.servlet.jsp.jstl</artifactId>
+    </dependency>
+   <dependency>
+       <groupId>org.glassfish.web</groupId>
+       <artifactId>javax.servlet.jsp.jstl</artifactId>
+    </dependency>
+   <dependency>
+       <groupId>org.mortbay.jasper</groupId>
+       <artifactId>apache-el</artifactId>
+       <version>8.0.9.M3</version>
+    </dependency>
+  </dependencies>
+<!--
+   <dependency>
+       <groupId>javax.el</groupId>
+       <artifactId>javax.el-api</artifactId>
+       <version>3.0.0</version>
+    </dependency>
   </dependencies>
+-->
 
   <build>
     <plugins>
@@ -85,66 +105,75 @@
           </executions>
           <configuration>
               <instructions>
-                <Bundle-SymbolicName>org.eclipse.jetty.osgi.boot.jsp</Bundle-SymbolicName>
                 <Bundle-Name>Jetty-OSGi-Jasper Integration</Bundle-Name>
                 <Bundle-Classpath />
                 <Fragment-Host>org.eclipse.jetty.osgi.boot</Fragment-Host>
                 <Export-Package>!org.eclipse.jetty.osgi.boot.*</Export-Package>
-                <Import-Package>com.sun.el;resolution:=optional,
+                <Import-Package>org.eclipse.jdt.*;resolution:=optional,
+                com.sun.el;resolution:=optional,
  com.sun.el.lang;resolution:=optional,
  com.sun.el.parser;resolution:=optional,
  com.sun.el.util;resolution:=optional,
- javax.el;version="2.2.0";resolution:=optional,
- javax.servlet;version="2.6.0",
- javax.servlet.jsp;version="2.2.0",
- javax.servlet.jsp.el;version="2.2.0",
- javax.servlet.jsp.jstl.core;version="1.2.0";resolution:=optional,
- javax.servlet.jsp.jstl.fmt;version="1.2.0";resolution:=optional,
- javax.servlet.jsp.jstl.sql;version="1.2.0";resolution:=optional,
- javax.servlet.jsp.jstl.tlv;version="1.2.0";resolution:=optional,
- javax.servlet.jsp.resources;version="2.2.0",
- javax.servlet.jsp.tagext;version="2.2.0",
- javax.servlet.resources;version="2.6.0",
- org.apache.jasper;version="2.2.2";resolution:=optional,
- org.apache.jasper.compiler;version="2.2.2";resolution:=optional,
- org.apache.jasper.compiler.tagplugin;version="2.2.2";resolution:=optional,
- org.apache.jasper.runtime;version="2.2.2";resolution:=optional,
- org.apache.jasper.security;version="2.2.2";resolution:=optional,
- org.apache.jasper.servlet;version="2.2.2";resolution:=optional,
- org.apache.jasper.tagplugins.jstl;version="2.2.2";resolution:=optional,
- org.apache.jasper.util;version="2.2.2";resolution:=optional,
- org.apache.jasper.xmlparser;version="2.2.2";resolution:=optional,
- org.glassfish.jsp.api;version="2.2.2";resolution:=optional,
- org.apache.taglibs.standard;version="1.2.0";resolution:=optional,
- org.apache.taglibs.standard.extra.spath;version="1.2.0";resolution:=optional,
- org.apache.taglibs.standard.functions;version="1.2.0";resolution:=optional,
- org.apache.taglibs.standard.lang.jstl;version="1.2.0";resolution:=optional,
- org.apache.taglibs.standard.lang.jstl.parser;version="1.2.0";resolution:=optional,
- org.apache.taglibs.standard.lang.jstl.test;version="1.2.0";resolution:=optional,
- org.apache.taglibs.standard.lang.jstl.test.beans;version="1.2.0";resolution:=optional,
- org.apache.taglibs.standard.lang.support;version="1.2.0";resolution:=optional,
- org.apache.taglibs.standard.resources;version="1.2.0";resolution:=optional,
- org.apache.taglibs.standard.tag.common.core;version="1.2.0";resolution:=optional,
- org.apache.taglibs.standard.tag.common.fmt;version="1.2.0";resolution:=optional,
- org.apache.taglibs.standard.tag.common.sql;version="1.2.0";resolution:=optional,
- org.apache.taglibs.standard.tag.common.xml;version="1.2.0";resolution:=optional,
- org.apache.taglibs.standard.tag.el.core;version="1.2.0";resolution:=optional,
- org.apache.taglibs.standard.tag.el.fmt;version="1.2.0";resolution:=optional,
- org.apache.taglibs.standard.tag.el.sql;version="1.2.0";resolution:=optional,
- org.apache.taglibs.standard.tag.el.xml;version="1.2.0";resolution:=optional,
- org.apache.taglibs.standard.tag.rt.core;version="1.2.0";resolution:=optional,
- org.apache.taglibs.standard.tag.rt.fmt;version="1.2.0";resolution:=optional,
- org.apache.taglibs.standard.tag.rt.sql;version="1.2.0";resolution:=optional,
- org.apache.taglibs.standard.tag.rt.xml;version="1.2.0";resolution:=optional,
- org.apache.taglibs.standard.tei;version="1.2.0";resolution:=optional,
- org.apache.taglibs.standard.tlv;version="1.2.0";resolution:=optional,
- !org.osgi.*,
- !org.xml.*,
- !org.eclipse.jetty.*,
- *
-                </Import-Package>
-                <_nouses>true</_nouses>
-                <!-- DynamicImport-Package>org.apache.jasper.*;version="2.2.2"</DynamicImport-Package -->
+ javax.el;version="[3.0,3.1)",
+ javax.servlet;version="[3.1,3.2)",
+ javax.servlet.resources;version="[3.1,3.2)",
+ javax.servlet.jsp.resources;version="[2.3,4)",
+ javax.servlet.jsp;version="[2.3,2.4)",
+ javax.servlet.jsp.el;version="[2.3,2.4)",
+ javax.servlet.jsp.tagext;version="[2.3,2.4)",
+ javax.servlet.jsp.jstl.core;version="1.2";resolution:=optional,
+ javax.servlet.jsp.jstl.fmt;version="1.2";resolution:=optional,
+ javax.servlet.jsp.jstl.sql;version="1.2";resolution:=optional,
+ javax.servlet.jsp.jstl.tlv;version="1.2";resolution:=optional,
+ org.apache.el;version="[8.0.9,9)";resolution:=optional,
+ org.apache.el.lang;version="[8.0.9,9)";resolution:=optional,
+ org.apache.el.stream;version="[8.0.9,9)";resolution:=optional,
+ org.apache.el.util;version="[8.0.9,9)";resolution:=optional,
+ org.apache.el.parser;version="[8.0.9,9)";resolution:=optional,
+ org.apache.jasper;version="[8.0.9,9)";resolution:=optional,
+ org.apache.jasper.compiler;version="[8.0.9,9)";resolution:=optional,
+ org.apache.jasper.compiler.tagplugin;version="[8.0.9,9)";resolution:=optional,
+ org.apache.jasper.runtime;version="[8.0.9,9)";resolution:=optional,
+ org.apache.jasper.security;version="[8.0.9,9)";resolution:=optional,
+ org.apache.jasper.servlet;version="[8.0.9,9)";resolution:=optional,
+ org.apache.jasper.tagplugins.jstl;version="[8.0.9,9)";resolution:=optional,
+ org.apache.jasper.util;version="[8.0.9,9)";resolution:=optional,
+ org.apache.jasper.xmlparser;version="[8.0.9,9)";resolution:=optional,
+ org.apache.taglibs.standard;version="1.2";resolution:=optional,
+ org.apache.taglibs.standard.extra.spath;version="1.2";resolution:=optional,
+ org.apache.taglibs.standard.functions;version="1.2";resolution:=optional,
+ org.apache.taglibs.standard.lang.jstl;version="1.2";resolution:=optional,
+ org.apache.taglibs.standard.lang.jstl.parser;version="1.2";resolution:=optional,
+ org.apache.taglibs.standard.lang.jstl.test;version="1.2";resolution:=optional,
+ org.apache.taglibs.standard.lang.jstl.test.beans;version="1.2";resolution:=optional,
+ org.apache.taglibs.standard.lang.support;version="1.2";resolution:=optional,
+ org.apache.taglibs.standard.resources;version="1.2";resolution:=optional,
+ org.apache.taglibs.standard.tag.common.core;version="1.2";resolution:=optional,
+ org.apache.taglibs.standard.tag.common.fmt;version="1.2";resolution:=optional,
+ org.apache.taglibs.standard.tag.common.sql;version="1.2";resolution:=optional,
+ org.apache.taglibs.standard.tag.common.xml;version="1.2";resolution:=optional,
+ org.apache.taglibs.standard.tag.el.core;version="1.2";resolution:=optional,
+ org.apache.taglibs.standard.tag.el.fmt;version="1.2";resolution:=optional,
+ org.apache.taglibs.standard.tag.el.sql;version="1.2";resolution:=optional,
+ org.apache.taglibs.standard.tag.el.xml;version="1.2";resolution:=optional,
+ org.apache.taglibs.standard.tag.rt.core;version="1.2";resolution:=optional,
+ org.apache.taglibs.standard.tag.rt.fmt;version="1.2";resolution:=optional,
+ org.apache.taglibs.standard.tag.rt.sql;version="1.2";resolution:=optional,
+ org.apache.taglibs.standard.tag.rt.xml;version="1.2";resolution:=optional,
+ org.apache.taglibs.standard.tei;version="1.2";resolution:=optional,
+ org.apache.taglibs.standard.tlv;version="1.2";resolution:=optional,
+ org.apache.tomcat;version="[8.0.9,9)";resolution:=optional,
+ org.eclipse.jetty.jsp;version="[9.2,10)";resolution:=optional,
+ org.osgi.*,
+ org.xml.*;resolution:=optional,
+ org.xml.sax.*;resolution:=optional,
+ javax.xml.*;resolution:=optional,
+ org.w3c.dom;resolution:=optional,
+ org.w3c.dom.ls;resolution:=optional,
+ javax.xml.parser;resolution:=optional
+ </Import-Package>
+               <_nouses>true</_nouses>
+               <DynamicImport-Package>org.eclipse.jetty.jsp.*;version="9.2.6",org.apache.jasper.*;version="8.0.9",org.apache.el.*;version="8.0.9"</DynamicImport-Package>
               </instructions>
           </configuration>
       </plugin>
diff --git a/jetty-osgi/jetty-osgi-boot-jsp/src/main/java/org/eclipse/jetty/osgi/boot/jasper/ContainerTldBundleDiscoverer.java b/jetty-osgi/jetty-osgi-boot-jsp/src/main/java/org/eclipse/jetty/osgi/boot/jasper/ContainerTldBundleDiscoverer.java
new file mode 100644
index 0000000..e471a78
--- /dev/null
+++ b/jetty-osgi/jetty-osgi-boot-jsp/src/main/java/org/eclipse/jetty/osgi/boot/jasper/ContainerTldBundleDiscoverer.java
@@ -0,0 +1,263 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.osgi.boot.jasper;
+
+import java.io.File;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.StringTokenizer;
+import java.util.regex.Pattern;
+
+import javax.servlet.jsp.JspFactory;
+
+import org.eclipse.jetty.deploy.DeploymentManager;
+import org.eclipse.jetty.osgi.boot.JettyBootstrapActivator;
+import org.eclipse.jetty.osgi.boot.OSGiWebInfConfiguration;
+import org.eclipse.jetty.osgi.boot.utils.BundleFileLocatorHelper;
+import org.eclipse.jetty.osgi.boot.utils.TldBundleDiscoverer;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.FrameworkUtil;
+
+
+
+/**
+ * ContainerTldBundleDiscoverer
+ * 
+ * Finds bundles that are considered as on the container classpath that
+ * contain tlds.
+ * 
+ * The System property org.eclipse.jetty.osgi.tldbundles is a comma
+ * separated list of exact symbolic names of bundles that have container classpath
+ * tlds.
+ * 
+ * The DeploymentManager context attribute "org.eclipse.jetty.server.webapp.containerIncludeBundlePattern"
+ * can be used to define a pattern of symbolic names of bundles that contain container 
+ * classpath tlds.
+ * 
+ * The matching bundles are converted to URLs that are put onto a special classloader that acts as the
+ * parent classloader for contexts deployed by the jetty Server instance (see ServerInstanceWrapper).
+ * 
+ * It also discovers the bundle that contains the jstl taglib and adds it into the 
+ * "org.eclipse.jetty.server.webapp.containerIncludeBundlePattern" (if it is not already there) so
+ * that the WebInfOSGiConfiguration class will add the jstl taglib bundle into the list of container
+ * resources.
+ * 
+ * Eg:
+ * -Dorg.eclipse.jetty.osgi.tldbundles=org.springframework.web.servlet,com.opensymphony.module.sitemesh
+ * 
+ */
+public class ContainerTldBundleDiscoverer implements TldBundleDiscoverer
+{
+
+    private static final Logger LOG = Log.getLogger(ContainerTldBundleDiscoverer.class);
+    
+
+    private static String DEFAULT_JSP_FACTORY_IMPL_CLASS = "org.apache.jasper.runtime.JspFactoryImpl";
+    /**
+     * Default name of a class that belongs to the jstl bundle. From that class
+     * we locate the corresponding bundle and register it as a bundle that
+     * contains tld files.
+     */
+    private static String DEFAULT_JSTL_BUNDLE_CLASS = "org.apache.taglibs.standard.tag.rt.core.WhenTag";
+
+    private Bundle jstlBundle = null;
+    
+    /**
+     * Check the System property "org.eclipse.jetty.osgi.tldbundles" for names of
+     * bundles that contain tlds and convert to URLs.
+     * 
+     * @return The location of the jars that contain tld files as URLs.
+     */
+    public URL[] getUrlsForBundlesWithTlds(DeploymentManager deploymentManager, BundleFileLocatorHelper locatorHelper) throws Exception
+    {        
+        if (!isJspAvailable())
+        {
+            return new URL[0];
+        }
+
+        if (jstlBundle == null)
+            jstlBundle = findJstlBundle();
+
+        Bundle[] bundles = FrameworkUtil.getBundle(ContainerTldBundleDiscoverer.class).getBundleContext().getBundles();
+        HashSet<URL> urls = new HashSet<URL>();
+        String tmp = System.getProperty(OSGiWebInfConfiguration.SYS_PROP_TLD_BUNDLES); //comma separated exact names
+        List<String> sysNames =   new ArrayList<String>();
+        if (tmp != null)
+        {
+            StringTokenizer tokenizer = new StringTokenizer(tmp, ", \n\r\t", false);
+            while (tokenizer.hasMoreTokens())
+                sysNames.add(tokenizer.nextToken());
+        }
+        tmp = (String) deploymentManager.getContextAttribute(OSGiWebInfConfiguration.CONTAINER_BUNDLE_PATTERN); //bundle name patterns
+    
+        Pattern pattern = (tmp==null? null : Pattern.compile(tmp));
+        
+        //check that the jstl bundle is not already included in the pattern, and include it if it is not because
+        //subsequent classes such as OSGiWebInfConfiguration use this pattern to determine which jars are
+        //considered to be on the container classpath
+        if (jstlBundle != null) 
+        {
+            if (pattern == null)
+            {
+                pattern = Pattern.compile(jstlBundle.getSymbolicName());
+                deploymentManager.setContextAttribute(OSGiWebInfConfiguration.CONTAINER_BUNDLE_PATTERN, jstlBundle.getSymbolicName());
+            }
+            else if (!(pattern.matcher(jstlBundle.getSymbolicName()).matches()))
+            {
+                String s = tmp+"|"+jstlBundle.getSymbolicName();
+                pattern = Pattern.compile(s);
+                deploymentManager.setContextAttribute(OSGiWebInfConfiguration.CONTAINER_BUNDLE_PATTERN, s);
+            }
+        }
+
+        
+        for (Bundle bundle : bundles)
+        {
+            if (sysNames.contains(bundle.getSymbolicName()))
+                convertBundleLocationToURL(locatorHelper, bundle, urls);           
+            else if (pattern != null && pattern.matcher(bundle.getSymbolicName()).matches())
+                convertBundleLocationToURL(locatorHelper, bundle, urls);
+        }
+
+        return urls.toArray(new URL[urls.size()]);
+
+    }
+
+    /**
+     * Check that jsp is on the classpath
+     * @return
+     */
+    public boolean isJspAvailable()
+    {
+        try
+        {
+            getClass().getClassLoader().loadClass("org.apache.jasper.servlet.JspServlet");
+        }
+        catch (Exception e)
+        {
+            LOG.warn("Unable to locate the JspServlet: jsp support unavailable.", e);
+            return false;
+        }
+        return true;
+    }
+    
+    
+    /**
+     * 
+     * Some versions of JspFactory do Class.forName, which probably won't work in an 
+     * OSGi environment.
+     */
+    public void fixJspFactory ()
+    {   
+        try
+        {
+            Class<javax.servlet.ServletContext> servletContextClass = javax.servlet.ServletContext.class;
+            // bug #299733
+            JspFactory fact = JspFactory.getDefaultFactory();
+            if (fact == null)
+            { // bug #299733
+              // JspFactory does a simple
+              // Class.getForName("org.apache.jasper.runtime.JspFactoryImpl")
+              // however its bundles does not import the jasper package
+              // so it fails. let's help things out:
+                fact = (JspFactory) JettyBootstrapActivator.class.getClassLoader().loadClass(DEFAULT_JSP_FACTORY_IMPL_CLASS).newInstance();
+                JspFactory.setDefaultFactory(fact);
+            }
+        }
+        catch (Exception e)
+        {
+            LOG.warn("Unable to set the JspFactory: jsp support incomplete.", e);
+        }
+    }
+    
+    
+    /**
+     * Find the bundle that contains a jstl implementation class, which assumes that
+     * the jstl taglibs will be inside the same bundle.
+     * @return
+     */
+    public Bundle findJstlBundle ()
+    {
+        Class<?> jstlClass = null;
+    
+        try
+        {
+            jstlClass = JSTLBundleDiscoverer.class.getClassLoader().loadClass(DEFAULT_JSTL_BUNDLE_CLASS);
+        }
+        catch (ClassNotFoundException e)
+        {
+            LOG.info("jstl not on classpath", e);
+        }
+        
+        if (jstlClass != null)
+            //get the bundle containing jstl
+            return FrameworkUtil.getBundle(jstlClass);
+        
+        return null;
+    }
+    
+    /**
+     * Resolves a bundle that contains tld files as a URL. The URLs are
+     * used by jasper to discover the tld files.
+     * 
+     * Support only 2 types of packaging for the bundle: - the bundle is a jar
+     * (recommended for runtime.) - the bundle is a folder and contain jars in
+     * the root and/or in the lib folder (nice for PDE developement situations)
+     * Unsupported: the bundle is a jar that embeds more jars.
+     * 
+     * @param locatorHelper
+     * @param bundle
+     * @param urls
+     * @throws Exception
+     */
+    private void convertBundleLocationToURL(BundleFileLocatorHelper locatorHelper, Bundle bundle, Set<URL> urls) throws Exception
+    {
+        File jasperLocation = locatorHelper.getBundleInstallLocation(bundle);
+        if (jasperLocation.isDirectory())
+        {
+            for (File f : jasperLocation.listFiles())
+            {
+                if (f.getName().endsWith(".jar") && f.isFile())
+                {
+                    urls.add(f.toURI().toURL());
+                }
+                else if (f.isDirectory() && f.getName().equals("lib"))
+                {
+                    for (File f2 : jasperLocation.listFiles())
+                    {
+                        if (f2.getName().endsWith(".jar") && f2.isFile())
+                        {
+                            urls.add(f2.toURI().toURL());
+                        }
+                    }
+                }
+            }
+            urls.add(jasperLocation.toURI().toURL());
+        }
+        else
+        {
+            urls.add(jasperLocation.toURI().toURL());
+        }
+    }
+}
diff --git a/jetty-osgi/jetty-osgi-boot-jsp/src/main/java/org/eclipse/jetty/osgi/boot/jasper/JSTLBundleDiscoverer.java b/jetty-osgi/jetty-osgi-boot-jsp/src/main/java/org/eclipse/jetty/osgi/boot/jasper/JSTLBundleDiscoverer.java
new file mode 100644
index 0000000..9c9352f
--- /dev/null
+++ b/jetty-osgi/jetty-osgi-boot-jsp/src/main/java/org/eclipse/jetty/osgi/boot/jasper/JSTLBundleDiscoverer.java
@@ -0,0 +1,295 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.osgi.boot.jasper;
+
+import java.io.File;
+import java.io.InputStream;
+import java.lang.reflect.Field;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.servlet.Servlet;
+import javax.servlet.jsp.JspContext;
+import javax.servlet.jsp.JspFactory;
+
+import org.apache.jasper.Constants;
+import org.apache.jasper.compiler.Localizer;
+import org.eclipse.jetty.deploy.DeploymentManager;
+import org.eclipse.jetty.osgi.boot.JettyBootstrapActivator;
+import org.eclipse.jetty.osgi.boot.utils.BundleFileLocatorHelper;
+import org.eclipse.jetty.osgi.boot.utils.TldBundleDiscoverer;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.FrameworkUtil;
+import org.xml.sax.EntityResolver;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+/**
+ * 
+ * JSTLBundleDiscoverer
+ * 
+ * Fix various shortcomings with the way jasper parses the tld files. Plugs the
+ * JSTL tlds assuming that they are packaged with the bundle that contains the
+ * JSTL classes.
+ * <p>
+ * Pluggable tlds at the server level are handled by
+ * {@link ContainerTldBundleDiscoverer}.
+ * </p>
+ */
+public class JSTLBundleDiscoverer implements TldBundleDiscoverer
+{
+    private static final Logger LOG = Log.getLogger(JSTLBundleDiscoverer.class);
+    
+
+    /**
+     * Default name of a class that belongs to the jstl bundle. From that class
+     * we locate the corresponding bundle and register it as a bundle that
+     * contains tld files.
+     */
+    private static String DEFAULT_JSTL_BUNDLE_CLASS = "org.apache.taglibs.standard.tag.el.core.WhenTag";
+
+    // used to be "org.apache.jasper.runtime.JspFactoryImpl" but now
+    // the standard tag library implementation are stored in a separate bundle.
+
+    // DISABLED please use the tld bundle argument for the OSGiAppProvider
+    // /**
+    // * Default name of a class that belongs to the bundle where the Java
+    // server Faces tld files are defined.
+    // * This is the sun's reference implementation.
+    // */
+    // private static String DEFAUT_JSF_IMPL_CLASS =
+    // "com.sun.faces.config.ConfigureListener";
+
+    /**
+     * Default jsp factory implementation. Idally jasper is osgified and we can
+     * use services. In the mean time we statically set the jsp factory
+     * implementation. bug #299733
+     */
+    private static String DEFAULT_JSP_FACTORY_IMPL_CLASS = "org.apache.jasper.runtime.JspFactoryImpl";
+    
+    private static final Set<URL> __tldBundleCache = new HashSet<URL>();
+
+    public JSTLBundleDiscoverer()
+    {
+        //fixupDtdResolution();
+
+        try
+        {
+            // sanity check:
+            Class cl = getClass().getClassLoader().loadClass("org.apache.jasper.servlet.JspServlet");
+        }
+        catch (Exception e)
+        {
+            LOG.warn("Unable to locate the JspServlet: jsp support unavailable.", e);
+            return;
+        }
+        try
+        {
+            Class<javax.servlet.ServletContext> servletContextClass = javax.servlet.ServletContext.class;
+            // bug #299733
+            JspFactory fact = JspFactory.getDefaultFactory();
+            if (fact == null)
+            { // bug #299733
+              // JspFactory does a simple
+              // Class.getForName("org.apache.jasper.runtime.JspFactoryImpl")
+              // however its bundles does not import the jasper package
+              // so it fails. let's help things out:
+                fact = (JspFactory) JettyBootstrapActivator.class.getClassLoader().loadClass(DEFAULT_JSP_FACTORY_IMPL_CLASS).newInstance();
+                JspFactory.setDefaultFactory(fact);
+            }
+
+        }
+        catch (Exception e)
+        {
+            LOG.warn("Unable to set the JspFactory: jsp support incomplete.", e);
+        }
+    }
+
+    /**
+     * The jasper TldScanner expects a URLClassloader to parse a jar for the
+     * /META-INF/*.tld it may contain. We place the bundles that we know contain
+     * such tag-libraries. Please note that it will work if and only if the
+     * bundle is a jar (!) Currently we just hardcode the bundle that contains
+     * the jstl implemenation.
+     * 
+     * A workaround when the tld cannot be parsed with this method is to copy
+     * and paste it inside the WEB-INF of the webapplication where it is used.
+     * 
+     * Support only 2 types of packaging for the bundle: - the bundle is a jar
+     * (recommended for runtime.) - the bundle is a folder and contain jars in
+     * the root and/or in the lib folder (nice for PDE developement situations)
+     * Unsupported: the bundle is a jar that embeds more jars.
+     * 
+     * @return array of URLs
+     * @throws Exception
+     */
+    public URL[] getUrlsForBundlesWithTlds(DeploymentManager deployer, BundleFileLocatorHelper locatorHelper) throws Exception
+    {
+
+        ArrayList<URL> urls = new ArrayList<URL>();
+        Class<?> jstlClass = null;
+
+        // Look for the jstl bundle
+        // We assume the jstl's tlds are defined there.
+        // We assume that the jstl bundle is imported by this bundle
+        // So we can look for this class using this bundle's classloader:
+        try
+        {
+            jstlClass = JSTLBundleDiscoverer.class.getClassLoader().loadClass(DEFAULT_JSTL_BUNDLE_CLASS);
+        }
+        catch (ClassNotFoundException e)
+        {
+            LOG.info("jstl not on classpath", e);
+        }
+        
+        if (jstlClass != null)
+        {
+            //get the bundle containing jstl
+            Bundle tldBundle = FrameworkUtil.getBundle(jstlClass);
+            File tldBundleLocation = locatorHelper.getBundleInstallLocation(tldBundle);
+            
+            System.err.println("jstl bundle: "+tldBundle);
+            System.err.println("jstl bundle location: "+tldBundleLocation);
+            if (tldBundleLocation != null && tldBundleLocation.isDirectory())
+            {
+                // try to find the jar files inside this folder
+                for (File f : tldBundleLocation.listFiles())
+                {
+                    if (f.getName().endsWith(".jar") && f.isFile())
+                    {
+                        System.err.println("Tld jar in dir: "+f.toURI());
+                        urls.add(f.toURI().toURL());
+                    }
+                    else if (f.isDirectory() && f.getName().equals("lib"))
+                    {
+                        for (File f2 : tldBundleLocation.listFiles())
+                        {
+                            if (f2.getName().endsWith(".jar") && f2.isFile())
+                            {
+                                System.err.println("Tld jar in lib dir: "+f2.toURI());
+                                urls.add(f2.toURI().toURL());
+                            }
+                        }
+                    }
+                }
+
+            }
+            else if (tldBundleLocation != null)
+            {
+                System.err.println("Tld bundle uri: "+tldBundleLocation.toURI());
+                urls.add(tldBundleLocation.toURI().toURL());
+              
+                String pattern = (String)deployer.getContextAttribute("org.eclipse.jetty.server.webapp.containerIncludeBundlePattern");
+                pattern = (pattern==null?"":pattern);
+                if (!pattern.contains(tldBundle.getSymbolicName()))
+                {
+                    pattern += "|"+tldBundle.getSymbolicName();
+                    deployer.setContextAttribute("org.eclipse.jetty.server.webapp.containerIncludeBundlePattern", pattern);
+                }
+                System.err.println("PATTERN: "+pattern);
+            }
+        }
+        
+        return urls.toArray(new URL[urls.size()]);
+    }
+
+    /**
+     * Jasper resolves the dtd when it parses a taglib descriptor. It uses this
+     * code to do that:
+     * ParserUtils.getClass().getResourceAsStream(resourcePath); where
+     * resourcePath is for example:
+     * /javax/servlet/jsp/resources/web-jsptaglibrary_1_2.dtd Unfortunately, the
+     * dtd file is not in the exact same classloader as ParserUtils class and
+     * the dtds are packaged in 2 separate bundles. OSGi does not look in the
+     * dependencies' classloader when a resource is searched.
+     * <p>
+     * The workaround consists of setting the entity resolver. That is a patch
+     * added to the version of glassfish-jasper-jetty. IT is also present in the
+     * latest version of glassfish jasper. Could not use introspection to set
+     * new value on a static friendly field :(
+     * </p>
+     */
+   void fixupDtdResolution()
+    {
+        try
+        {
+           // ParserUtils.setEntityResolver(new MyFixedupEntityResolver());
+         
+
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+
+    }
+
+    /**
+     * Instead of using the ParserUtil's classloader, we use a class that is
+     * indeed next to the resource for sure.
+     */
+    //static class MyFixedupEntityResolver implements EntityResolver
+    //{
+        /**
+         * Same values than in ParserUtils...
+         */
+      /*  static final String[] CACHED_DTD_PUBLIC_IDS = { Constants.TAGLIB_DTD_PUBLIC_ID_11, Constants.TAGLIB_DTD_PUBLIC_ID_12,
+                                                       Constants.WEBAPP_DTD_PUBLIC_ID_22, Constants.WEBAPP_DTD_PUBLIC_ID_23, };
+
+        static final String[] CACHED_DTD_RESOURCE_PATHS = { Constants.TAGLIB_DTD_RESOURCE_PATH_11, Constants.TAGLIB_DTD_RESOURCE_PATH_12,
+                                                           Constants.WEBAPP_DTD_RESOURCE_PATH_22, Constants.WEBAPP_DTD_RESOURCE_PATH_23, };
+
+        static final String[] CACHED_SCHEMA_RESOURCE_PATHS = { Constants.TAGLIB_SCHEMA_RESOURCE_PATH_20, Constants.TAGLIB_SCHEMA_RESOURCE_PATH_21,
+                                                              Constants.WEBAPP_SCHEMA_RESOURCE_PATH_24, Constants.WEBAPP_SCHEMA_RESOURCE_PATH_25, };*/
+
+      /*  public InputSource resolveEntity(String publicId, String systemId) throws SAXException
+        {
+            for (int i = 0; i < CACHED_DTD_PUBLIC_IDS.length; i++)
+            {
+                String cachedDtdPublicId = CACHED_DTD_PUBLIC_IDS[i];
+                if (cachedDtdPublicId.equals(publicId))
+                {
+                    String resourcePath = CACHED_DTD_RESOURCE_PATHS[i];
+                    InputStream input = null;
+                    input = Servlet.class.getResourceAsStream(resourcePath);
+                    if (input == null)
+                    {
+                        input = JspContext.class.getResourceAsStream(resourcePath);
+                        if (input == null)
+                        {*/
+                            // if that failed try again with the original code:
+                            // although it is likely not changed.
+                   /*         input = this.getClass().getResourceAsStream(resourcePath);
+                      }
+                    }
+                    if (input == null) { throw new SAXException(Localizer.getMessage("jsp.error.internal.filenotfound", resourcePath)); }
+                    InputSource isrc = new InputSource(input);
+                    return isrc;
+                }
+            }
+
+            return null;
+        }
+    }*/
+
+}
diff --git a/jetty-osgi/jetty-osgi-boot-jsp/src/main/java/org/eclipse/jetty/osgi/boot/jasper/PluggableWebAppRegistrationCustomizerImpl.java b/jetty-osgi/jetty-osgi-boot-jsp/src/main/java/org/eclipse/jetty/osgi/boot/jasper/PluggableWebAppRegistrationCustomizerImpl.java
deleted file mode 100644
index 212d94f..0000000
--- a/jetty-osgi/jetty-osgi-boot-jsp/src/main/java/org/eclipse/jetty/osgi/boot/jasper/PluggableWebAppRegistrationCustomizerImpl.java
+++ /dev/null
@@ -1,187 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.osgi.boot.jasper;
-
-import java.io.File;
-import java.net.URL;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.StringTokenizer;
-import java.util.regex.Pattern;
-
-import org.eclipse.jetty.deploy.DeploymentManager;
-import org.eclipse.jetty.osgi.boot.OSGiWebInfConfiguration;
-import org.eclipse.jetty.osgi.boot.utils.BundleFileLocatorHelper;
-import org.eclipse.jetty.osgi.boot.utils.WebappRegistrationCustomizer;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.FrameworkUtil;
-
-/**
- * Plug bundles that contains tld files so that jasper will discover them and
- * set them up in jetty.
- * 
- * For example:
- * -Dorg.eclipse.jetty.osgi.tldbundles=org.springframework.web.servlet
- * ,com.opensymphony.module.sitemesh Otherwise use an attribute to the
- * WebAppDeployer <New
- * class="org.eclipse.jetty.deploy.providers.WebAppProvider"> .... <Set
- * name="tldBundles">&ltProperty name="org.eclipse.jetty.osgi.tldsbundles"
- * default="" /></Set> <New>
- */
-public class PluggableWebAppRegistrationCustomizerImpl implements WebappRegistrationCustomizer
-{
-    /**
-     * To plug into jasper bundles that contain tld files please use a list of
-     * bundle's symbolic names:
-     * -Djetty.osgi.tldbundles=org.springframework.web.servlet
-     * ,com.opensymphony.module.sitemesh
-     */
-    public static final String SYS_PROP_TLD_BUNDLES = "org.eclipse.jetty.osgi.tldbundles";
-
-    /**
-     * Union of the tld bundles defined system wide and the one defines as an
-     * attribute of the AppProvider.
-     * 
-     * @param provider
-     * @return
-     */
-    private static Collection<String> getTldBundles(DeploymentManager deploymentManager)
-    {
-        String sysprop = System.getProperty(SYS_PROP_TLD_BUNDLES);
-        String att = (String) deploymentManager.getContextAttribute(OSGiWebInfConfiguration.CONTAINER_BUNDLE_PATTERN);
-        if (sysprop == null && att == null) { return Collections.emptySet(); }
-        if (att == null)
-        {
-            att = sysprop;
-        }
-        else if (sysprop != null)
-        {
-            att = att + "," + sysprop;
-        }
-
-        Collection<String> tldbundles = new HashSet<String>();
-        StringTokenizer tokenizer = new StringTokenizer(att, ", \n\r\t", false);
-        while (tokenizer.hasMoreTokens())
-        {
-            tldbundles.add(tokenizer.nextToken());
-        }
-        return tldbundles;
-    }
-
-    /**
-     * @return The location of the jars that contain tld files. Jasper will
-     *         discover them.
-     */
-    public URL[] getJarsWithTlds(DeploymentManager deploymentManager, BundleFileLocatorHelper locatorHelper) throws Exception
-    {
-        // naive way of finding those bundles.
-        // lots of assumptions: for example we assume a single version of each
-        // bundle that would contain tld files.
-        // this is probably good enough as those tlds are loaded system-wide on
-        // jetty.
-        // to do better than this we need to do it on a per webapp basis.
-        // probably using custom properties in the ContextHandler service
-        // and mirroring those in the MANIFEST.MF
-
-        Bundle[] bundles = FrameworkUtil.getBundle(PluggableWebAppRegistrationCustomizerImpl.class).getBundleContext().getBundles();
-        HashSet<URL> urls = new HashSet<URL>();
-        String tmp = System.getProperty(SYS_PROP_TLD_BUNDLES); //comma separated exact names
-        List<String> sysNames =   new ArrayList<String>();
-        if (tmp != null)
-        {
-            StringTokenizer tokenizer = new StringTokenizer(tmp, ", \n\r\t", false);
-            while (tokenizer.hasMoreTokens())
-                sysNames.add(tokenizer.nextToken());
-        }
-        tmp = (String) deploymentManager.getContextAttribute(OSGiWebInfConfiguration.CONTAINER_BUNDLE_PATTERN); //bundle name patterns
-        Pattern pattern = (tmp==null? null : Pattern.compile(tmp));
-        for (Bundle bundle : bundles)
-        {
-            if (sysNames.contains(bundle.getSymbolicName()))
-                registerTldBundle(locatorHelper, bundle, urls);
-           
-            if (pattern != null && pattern.matcher(bundle.getSymbolicName()).matches())
-                registerTldBundle(locatorHelper, bundle, urls);
-        }
-
-        return urls.toArray(new URL[urls.size()]);
-
-    }
-
-    /**
-     * Resolves the bundle that contains tld files as a set of URLs that will be
-     * passed to jasper as a URLClassLoader later on. Usually that would be a
-     * single URL per bundle. But we do some more work if there are jars
-     * embedded in the bundle.
-     * 
-     * The jasper TldScanner expects a URLClassloader to parse a jar for the
-     * /META-INF/*.tld it may contain. We place the bundles that we know contain
-     * such tag-libraries. Please note that it will work if and only if the
-     * bundle is a jar (!) Currently we just hardcode the bundle that contains
-     * the jstl implemenation.
-     * 
-     * A workaround when the tld cannot be parsed with this method is to copy
-     * and paste it inside the WEB-INF of the webapplication where it is used.
-     * 
-     * Support only 2 types of packaging for the bundle: - the bundle is a jar
-     * (recommended for runtime.) - the bundle is a folder and contain jars in
-     * the root and/or in the lib folder (nice for PDE developement situations)
-     * Unsupported: the bundle is a jar that embeds more jars.
-     * 
-     * @param locatorHelper
-     * @param bundle
-     * @param urls
-     * @throws Exception
-     */
-    private void registerTldBundle(BundleFileLocatorHelper locatorHelper, Bundle bundle, Set<URL> urls) throws Exception
-    {
-        File jasperLocation = locatorHelper.getBundleInstallLocation(bundle);
-        if (jasperLocation.isDirectory())
-        {
-            for (File f : jasperLocation.listFiles())
-            {
-                if (f.getName().endsWith(".jar") && f.isFile())
-                {
-                    urls.add(f.toURI().toURL());
-                }
-                else if (f.isDirectory() && f.getName().equals("lib"))
-                {
-                    for (File f2 : jasperLocation.listFiles())
-                    {
-                        if (f2.getName().endsWith(".jar") && f2.isFile())
-                        {
-                            urls.add(f2.toURI().toURL());
-                        }
-                    }
-                }
-            }
-            urls.add(jasperLocation.toURI().toURL());
-        }
-        else
-        {
-            urls.add(jasperLocation.toURI().toURL());
-        }
-
-    }
-
-}
diff --git a/jetty-osgi/jetty-osgi-boot-jsp/src/main/java/org/eclipse/jetty/osgi/boot/jasper/WebappRegistrationCustomizerImpl.java b/jetty-osgi/jetty-osgi-boot-jsp/src/main/java/org/eclipse/jetty/osgi/boot/jasper/WebappRegistrationCustomizerImpl.java
deleted file mode 100644
index 42ae450..0000000
--- a/jetty-osgi/jetty-osgi-boot-jsp/src/main/java/org/eclipse/jetty/osgi/boot/jasper/WebappRegistrationCustomizerImpl.java
+++ /dev/null
@@ -1,271 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.osgi.boot.jasper;
-
-import java.io.File;
-import java.io.InputStream;
-import java.net.URL;
-import java.util.ArrayList;
-import java.util.HashSet;
-
-import javax.servlet.Servlet;
-import javax.servlet.jsp.JspContext;
-import javax.servlet.jsp.JspFactory;
-
-import org.apache.jasper.Constants;
-import org.apache.jasper.compiler.Localizer;
-import org.apache.jasper.xmlparser.ParserUtils;
-import org.eclipse.jetty.deploy.DeploymentManager;
-import org.eclipse.jetty.osgi.boot.JettyBootstrapActivator;
-import org.eclipse.jetty.osgi.boot.utils.BundleFileLocatorHelper;
-import org.eclipse.jetty.osgi.boot.utils.WebappRegistrationCustomizer;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.FrameworkUtil;
-import org.xml.sax.EntityResolver;
-import org.xml.sax.InputSource;
-import org.xml.sax.SAXException;
-
-/**
- * Fix various shortcomings with the way jasper parses the tld files. Plugs the
- * JSTL tlds assuming that they are packaged with the bundle that contains the
- * JSTL classes.
- * <p>
- * Pluggable tlds at the server level are handled by
- * {@link PluggableWebAppRegistrationCustomizerImpl}.
- * </p>
- */
-public class WebappRegistrationCustomizerImpl implements WebappRegistrationCustomizer
-{
-    private static final Logger LOG = Log.getLogger(WebappRegistrationCustomizerImpl.class);
-    
-
-    /**
-     * Default name of a class that belongs to the jstl bundle. From that class
-     * we locate the corresponding bundle and register it as a bundle that
-     * contains tld files.
-     */
-    private static String DEFAULT_JSTL_BUNDLE_CLASS = "org.apache.taglibs.standard.tag.el.core.WhenTag";
-
-    // used to be "org.apache.jasper.runtime.JspFactoryImpl" but now
-    // the standard tag library implementation are stored in a separate bundle.
-
-    // DISABLED please use the tld bundle argument for the OSGiAppProvider
-    // /**
-    // * Default name of a class that belongs to the bundle where the Java
-    // server Faces tld files are defined.
-    // * This is the sun's reference implementation.
-    // */
-    // private static String DEFAUT_JSF_IMPL_CLASS =
-    // "com.sun.faces.config.ConfigureListener";
-
-    /**
-     * Default jsp factory implementation. Idally jasper is osgified and we can
-     * use services. In the mean time we statically set the jsp factory
-     * implementation. bug #299733
-     */
-    private static String DEFAULT_JSP_FACTORY_IMPL_CLASS = "org.apache.jasper.runtime.JspFactoryImpl";
-
-    public WebappRegistrationCustomizerImpl()
-    {
-        fixupDtdResolution();
-
-        try
-        {
-            // sanity check:
-            Class cl = getClass().getClassLoader().loadClass("org.apache.jasper.servlet.JspServlet");
-        }
-        catch (Exception e)
-        {
-            LOG.warn("Unable to locate the JspServlet: jsp support unavailable.", e);
-            return;
-        }
-        try
-        {
-            // bug #299733
-            JspFactory fact = JspFactory.getDefaultFactory();
-            if (fact == null)
-            { // bug #299733
-              // JspFactory does a simple
-              // Class.getForName("org.apache.jasper.runtime.JspFactoryImpl")
-              // however its bundles does not import the jasper package
-              // so it fails. let's help things out:
-                fact = (JspFactory) JettyBootstrapActivator.class.getClassLoader().loadClass(DEFAULT_JSP_FACTORY_IMPL_CLASS).newInstance();
-                JspFactory.setDefaultFactory(fact);
-            }
-
-        }
-        catch (Exception e)
-        {
-            LOG.warn("Unable to set the JspFactory: jsp support incomplete.", e);
-        }
-    }
-
-    /**
-     * The jasper TldScanner expects a URLClassloader to parse a jar for the
-     * /META-INF/*.tld it may contain. We place the bundles that we know contain
-     * such tag-libraries. Please note that it will work if and only if the
-     * bundle is a jar (!) Currently we just hardcode the bundle that contains
-     * the jstl implemenation.
-     * 
-     * A workaround when the tld cannot be parsed with this method is to copy
-     * and paste it inside the WEB-INF of the webapplication where it is used.
-     * 
-     * Support only 2 types of packaging for the bundle: - the bundle is a jar
-     * (recommended for runtime.) - the bundle is a folder and contain jars in
-     * the root and/or in the lib folder (nice for PDE developement situations)
-     * Unsupported: the bundle is a jar that embeds more jars.
-     * 
-     * @return array of URLs
-     * @throws Exception
-     */
-    public URL[] getJarsWithTlds(DeploymentManager deployer, BundleFileLocatorHelper locatorHelper) throws Exception
-    {
-
-        ArrayList<URL> urls = new ArrayList<URL>();
-        HashSet<Class<?>> classesToAddToTheTldBundles = new HashSet<Class<?>>();
-
-        // Look for the jstl bundle
-        // We assume the jstl's tlds are defined there.
-        // We assume that the jstl bundle is imported by this bundle
-        // So we can look for this class using this bundle's classloader:
-        try
-        {
-            Class<?> jstlClass = WebappRegistrationCustomizerImpl.class.getClassLoader().loadClass(DEFAULT_JSTL_BUNDLE_CLASS);
-
-            classesToAddToTheTldBundles.add(jstlClass);
-        }
-        catch (ClassNotFoundException e)
-        {
-            LOG.info("jstl not on classpath", e);
-        }
-        for (Class<?> cl : classesToAddToTheTldBundles)
-        {
-            Bundle tldBundle = FrameworkUtil.getBundle(cl);
-            File tldBundleLocation = locatorHelper.getBundleInstallLocation(tldBundle);
-            if (tldBundleLocation != null && tldBundleLocation.isDirectory())
-            {
-                // try to find the jar files inside this folder
-                for (File f : tldBundleLocation.listFiles())
-                {
-                    if (f.getName().endsWith(".jar") && f.isFile())
-                    {
-                        urls.add(f.toURI().toURL());
-                    }
-                    else if (f.isDirectory() && f.getName().equals("lib"))
-                    {
-                        for (File f2 : tldBundleLocation.listFiles())
-                        {
-                            if (f2.getName().endsWith(".jar") && f2.isFile())
-                            {
-                                urls.add(f2.toURI().toURL());
-                            }
-                        }
-                    }
-                }
-
-            }
-            else if (tldBundleLocation != null)
-            {
-                urls.add(tldBundleLocation.toURI().toURL());
-            }
-        }
-        return urls.toArray(new URL[urls.size()]);
-    }
-
-    /**
-     * Jasper resolves the dtd when it parses a taglib descriptor. It uses this
-     * code to do that:
-     * ParserUtils.getClass().getResourceAsStream(resourcePath); where
-     * resourcePath is for example:
-     * /javax/servlet/jsp/resources/web-jsptaglibrary_1_2.dtd Unfortunately, the
-     * dtd file is not in the exact same classloader as ParserUtils class and
-     * the dtds are packaged in 2 separate bundles. OSGi does not look in the
-     * dependencies' classloader when a resource is searched.
-     * <p>
-     * The workaround consists of setting the entity resolver. That is a patch
-     * added to the version of glassfish-jasper-jetty. IT is also present in the
-     * latest version of glassfish jasper. Could not use introspection to set
-     * new value on a static friendly field :(
-     * </p>
-     */
-    void fixupDtdResolution()
-    {
-        try
-        {
-            ParserUtils.setEntityResolver(new MyFixedupEntityResolver());
-
-        }
-        catch (Exception e)
-        {
-            e.printStackTrace();
-        }
-
-    }
-
-    /**
-     * Instead of using the ParserUtil's classloader, we use a class that is
-     * indeed next to the resource for sure.
-     */
-    static class MyFixedupEntityResolver implements EntityResolver
-    {
-        /**
-         * Same values than in ParserUtils...
-         */
-        static final String[] CACHED_DTD_PUBLIC_IDS = { Constants.TAGLIB_DTD_PUBLIC_ID_11, Constants.TAGLIB_DTD_PUBLIC_ID_12,
-                                                       Constants.WEBAPP_DTD_PUBLIC_ID_22, Constants.WEBAPP_DTD_PUBLIC_ID_23, };
-
-        static final String[] CACHED_DTD_RESOURCE_PATHS = { Constants.TAGLIB_DTD_RESOURCE_PATH_11, Constants.TAGLIB_DTD_RESOURCE_PATH_12,
-                                                           Constants.WEBAPP_DTD_RESOURCE_PATH_22, Constants.WEBAPP_DTD_RESOURCE_PATH_23, };
-
-        static final String[] CACHED_SCHEMA_RESOURCE_PATHS = { Constants.TAGLIB_SCHEMA_RESOURCE_PATH_20, Constants.TAGLIB_SCHEMA_RESOURCE_PATH_21,
-                                                              Constants.WEBAPP_SCHEMA_RESOURCE_PATH_24, Constants.WEBAPP_SCHEMA_RESOURCE_PATH_25, };
-
-        public InputSource resolveEntity(String publicId, String systemId) throws SAXException
-        {
-            for (int i = 0; i < CACHED_DTD_PUBLIC_IDS.length; i++)
-            {
-                String cachedDtdPublicId = CACHED_DTD_PUBLIC_IDS[i];
-                if (cachedDtdPublicId.equals(publicId))
-                {
-                    String resourcePath = CACHED_DTD_RESOURCE_PATHS[i];
-                    InputStream input = null;
-                    input = Servlet.class.getResourceAsStream(resourcePath);
-                    if (input == null)
-                    {
-                        input = JspContext.class.getResourceAsStream(resourcePath);
-                        if (input == null)
-                        {
-                            // if that failed try again with the original code:
-                            // although it is likely not changed.
-                            input = this.getClass().getResourceAsStream(resourcePath);
-                        }
-                    }
-                    if (input == null) { throw new SAXException(Localizer.getMessage("jsp.error.internal.filenotfound", resourcePath)); }
-                    InputSource isrc = new InputSource(input);
-                    return isrc;
-                }
-            }
-
-            return null;
-        }
-    }
-
-}
diff --git a/jetty-osgi/jetty-osgi-boot-jsp/src/main/java/org/eclipse/jetty/osgi/boot/jsp/FragmentActivator.java b/jetty-osgi/jetty-osgi-boot-jsp/src/main/java/org/eclipse/jetty/osgi/boot/jsp/FragmentActivator.java
index ae36798..e578222 100644
--- a/jetty-osgi/jetty-osgi-boot-jsp/src/main/java/org/eclipse/jetty/osgi/boot/jsp/FragmentActivator.java
+++ b/jetty-osgi/jetty-osgi-boot-jsp/src/main/java/org/eclipse/jetty/osgi/boot/jsp/FragmentActivator.java
@@ -18,22 +18,26 @@
 
 package org.eclipse.jetty.osgi.boot.jsp;
 
-import org.eclipse.jetty.osgi.boot.BundleWebAppProvider;
-import org.eclipse.jetty.osgi.boot.internal.webapp.WebBundleTrackerCustomizer;
-import org.eclipse.jetty.osgi.boot.jasper.PluggableWebAppRegistrationCustomizerImpl;
-import org.eclipse.jetty.osgi.boot.jasper.WebappRegistrationCustomizerImpl;
+
+import org.eclipse.jetty.osgi.boot.internal.serverfactory.ServerInstanceWrapper;
+import org.eclipse.jetty.osgi.boot.jasper.ContainerTldBundleDiscoverer;
 import org.osgi.framework.BundleActivator;
 import org.osgi.framework.BundleContext;
 
 /**
- * Pseudo fragment activator. Called by the main org.eclipse.jetty.osgi.boot
- * bundle. Please note: this is not a real BundleActivator. Simply something
- * called back by the host bundle.
- * <p>
- * It must be placed in the org.eclipse.jetty.osgi.boot.jsp package: this is
- * because org.eclipse.jetty.osgi.boot.jsp is the symbolic-name of this
- * fragment. From that name, the PackageadminTracker will call this class. IN a
- * different package it won't be called.
+ * FragmentActivator
+ * 
+ * Sets up support for jsp and jstl. All relevant jsp jars must also be installed
+ * into the osgi environment.
+ *  <p>
+ * Note that as this is part of a bundle fragment, this activator is NOT
+ * called by the OSGi environment. Instead, the org.eclipse.jetty.osgi.boot.utils.internal.PackageAdminTracker
+ * simulates fragment activation and causes this class's start() method to
+ * be called.
+ * </p>
+ *  <p>
+ * The package of this class MUST match the Bundle-SymbolicName of this fragment
+ * in order for the PackageAdminTracker to find it.
  * </p>
  */
 public class FragmentActivator implements BundleActivator
@@ -43,12 +47,13 @@ public class FragmentActivator implements BundleActivator
      */
     public void start(BundleContext context) throws Exception
     {
+        //jsr199 compilation does not work in osgi
         System.setProperty("org.apache.jasper.compiler.disablejsr199", Boolean.TRUE.toString());
-        WebBundleTrackerCustomizer.JSP_REGISTRATION_HELPERS.add(new WebappRegistrationCustomizerImpl());
-        WebBundleTrackerCustomizer.JSP_REGISTRATION_HELPERS.add(new PluggableWebAppRegistrationCustomizerImpl());
-        //Put in the support for the tag libs
-        addTagLibSupport();
-      
+        
+        //set up some classes that will look for bundles with tlds that must be converted
+        //to urls and treated as if they are on the Jetty container's classpath so that 
+        //jasper can deal with them
+        ServerInstanceWrapper.addContainerTldBundleDiscoverer(new ContainerTldBundleDiscoverer());      
     }
 
     /**
@@ -58,12 +63,4 @@ public class FragmentActivator implements BundleActivator
     {
 
     }
-    
-    public void addTagLibSupport ()
-    {
-        String[] defaultConfigurations = new String[BundleWebAppProvider.getDefaultConfigurations().length+1];
-        System.arraycopy(BundleWebAppProvider.getDefaultConfigurations(), 0, defaultConfigurations, 0, BundleWebAppProvider.getDefaultConfigurations().length);
-        defaultConfigurations[defaultConfigurations.length-1] = "org.eclipse.jetty.osgi.boot.jsp.TagLibOSGiConfiguration";
-        BundleWebAppProvider.setDefaultConfigurations(defaultConfigurations);
-    }
 }
diff --git a/jetty-osgi/jetty-osgi-boot-jsp/src/main/java/org/eclipse/jetty/osgi/boot/jsp/TagLibOSGiConfiguration.java b/jetty-osgi/jetty-osgi-boot-jsp/src/main/java/org/eclipse/jetty/osgi/boot/jsp/TagLibOSGiConfiguration.java
deleted file mode 100644
index 70975c3..0000000
--- a/jetty-osgi/jetty-osgi-boot-jsp/src/main/java/org/eclipse/jetty/osgi/boot/jsp/TagLibOSGiConfiguration.java
+++ /dev/null
@@ -1,161 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.osgi.boot.jsp;
-
-import java.io.IOException;
-import java.net.URL;
-import java.util.Collection;
-import java.util.Enumeration;
-import java.util.LinkedHashSet;
-
-import org.eclipse.jetty.osgi.boot.OSGiWebappConstants;
-import org.eclipse.jetty.osgi.boot.internal.webapp.BundleFileLocatorHelperFactory;
-import org.eclipse.jetty.osgi.boot.utils.internal.DefaultFileLocatorHelper;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.util.resource.Resource;
-import org.eclipse.jetty.webapp.TagLibConfiguration;
-import org.eclipse.jetty.webapp.WebAppContext;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleReference;
-import org.osgi.service.packageadmin.PackageAdmin;
-import org.osgi.util.tracker.ServiceTracker;
-
-/**
- * <p>
- * Replacement for {@link TagLibConfiguration} for the OSGi integration.
- * </p>
- * <p>
- * In the case of a WAB, tlds can be located in OSGi bundles that are
- * dependencies of the WAB. It is expected that each WAB lists the
- * symbolic-names of the bundles that contain tld files. The list is defined as
- * the value of the header 'Require-TldBundle'
- * </p>
- * <p>
- * Discussions about this are logged in
- * https://bugs.eclipse.org/bugs/show_bug.cgi?id=306971
- * </p>
- */
-public class TagLibOSGiConfiguration extends TagLibConfiguration
-{
-    private static final Logger LOG = Log.getLogger(TagLibOSGiConfiguration.class);
-
-    private ServiceTracker packageAdminServiceTracker = null;
-
-    /**
-     * Override the preConfigure; locates the bundles that contain tld files
-     * according to the value of the manifest header Require-TldBundle.
-     * <p>
-     * Set or add to the property TldProcessor.TLDResources the list of located
-     * jars so that the super class will scan those.
-     * </p>
-     */
-    public void preConfigure(WebAppContext context) throws Exception
-    {
-        String requireTldBundle = (String) context.getAttribute(OSGiWebappConstants.REQUIRE_TLD_BUNDLE);
-        if (requireTldBundle != null)
-        {
-            Collection<Resource> resources = getRequireTldBundleAsJettyResources(context, requireTldBundle);
-            if (resources != null && !resources.isEmpty())
-            {
-                Collection<Resource> previouslySet = (Collection<Resource>) context.getAttribute(TagLibConfiguration.TLD_RESOURCES);
-                if (previouslySet != null)
-                {
-                    resources.addAll(previouslySet);
-                }
-                context.setAttribute(TagLibConfiguration.TLD_RESOURCES, resources);
-            }
-        }
-        super.preConfigure(context);
-
-    }
-
-    /**
-     * @param requireTldBundle The comma separated list of bundles' symbolic
-     *            names that contain tld for this osgi webapp.
-     * @return The collection of jars or folders that match those bundles.
-     */
-    private Collection<Resource> getRequireTldBundleAsJettyResources(WebAppContext context, String requireTldBundle)
-    {
-        Bundle bundle = (Bundle) context.getAttribute(OSGiWebappConstants.JETTY_OSGI_BUNDLE);
-        PackageAdmin packAdmin = getBundleAdmin();
-        String[] symbNames = requireTldBundle.split(", ");
-        Collection<Resource> tlds = new LinkedHashSet<Resource>();
-        for (String symbName : symbNames)
-        {
-            Bundle[] bs = packAdmin.getBundles(symbName, null);
-            if (bs == null || bs.length == 0) 
-            { 
-                throw new IllegalArgumentException("Unable to locate the bundle '" + symbName
-                                                                                   + "' specified in the "
-                                                                                   + OSGiWebappConstants.REQUIRE_TLD_BUNDLE
-                                                                                   + " of the manifest of "
-                                                                                   + bundle.getSymbolicName()); 
-            }
-            // take the first one as it is the most recent version?
-            Enumeration<URL> en = bs[0].findEntries("META-INF", "*.tld", false);
-            boolean atLeastOneTldFound = false;
-            while (en.hasMoreElements())
-            {
-                atLeastOneTldFound = true;
-                URL oriUrl = en.nextElement();
-                URL url = BundleFileLocatorHelperFactory.getFactory().getHelper().getLocalURL(oriUrl);
-                Resource tldResource;
-                try
-                {
-                    tldResource = Resource.newResource(url);
-                }
-                catch (IOException e)
-                {
-                    throw new IllegalArgumentException("Unable to locate the " + "tld resource in '"
-                                                       + url.toString()
-                                                       + "' in the bundle '"
-                                                       + bs[0].getSymbolicName()
-                                                       + "' while registering the "
-                                                       + OSGiWebappConstants.REQUIRE_TLD_BUNDLE
-                                                       + " of the manifest of "
-                                                       + bundle.getSymbolicName(), e);
-                }
-                tlds.add(tldResource);
-            }
-            if (!atLeastOneTldFound)
-            {
-                LOG.warn("No '/META-INF/*.tld' resources were found " + " in the bundle '"
-                         + bs[0].getSymbolicName()
-                         + "' while registering the "
-                         + OSGiWebappConstants.REQUIRE_TLD_BUNDLE
-                         + " of the manifest of "
-                         + bundle.getSymbolicName());
-            }
-        }
-        return tlds;
-    }
-
-    private PackageAdmin getBundleAdmin()
-    {
-        if (packageAdminServiceTracker == null)
-        {
-            Bundle bootBundle = ((BundleReference) OSGiWebappConstants.class.getClassLoader()).getBundle();
-            packageAdminServiceTracker = new ServiceTracker(bootBundle.getBundleContext(), PackageAdmin.class.getName(), null);
-            packageAdminServiceTracker.open();
-        }
-        return (PackageAdmin) packageAdminServiceTracker.getService();
-    }
-
-}
diff --git a/jetty-osgi/jetty-osgi-boot-logback/pom.xml b/jetty-osgi/jetty-osgi-boot-logback/pom.xml
deleted file mode 100644
index 02a8c76..0000000
--- a/jetty-osgi/jetty-osgi-boot-logback/pom.xml
+++ /dev/null
@@ -1,127 +0,0 @@
-<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">
-  <parent>
-    <groupId>org.eclipse.jetty.osgi</groupId>
-    <artifactId>jetty-osgi-project</artifactId>
-    <version>8.1.17-SNAPSHOT</version>
-    <relativePath>../pom.xml</relativePath>
-  </parent>
-  <modelVersion>4.0.0</modelVersion>
-  <artifactId>jetty-osgi-boot-logback</artifactId>
-  <name>Jetty :: OSGi :: Boot Logback</name>
-  <description>Jetty OSGi Boot Logback bundle</description>
-  <url>http://www.eclipse.org/jetty</url>
-  <properties>
-    <bundle-symbolic-name>${project.groupId}.boot.logback</bundle-symbolic-name>
-  </properties>
-  <dependencies>
-    <dependency>
-      <groupId>org.eclipse.jetty.osgi</groupId>
-      <artifactId>jetty-osgi-boot</artifactId>
-      <version>${project.version}</version>
-      <scope>provided</scope>
-    </dependency>
-    <dependency>
-    	<groupId>org.eclipse.osgi</groupId>
-    	<artifactId>org.eclipse.osgi</artifactId>
-    </dependency>
-    <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-webapp</artifactId>
-    </dependency>
-    <dependency>
-        <groupId>org.eclipse.osgi</groupId>
-        <artifactId>org.eclipse.osgi.services</artifactId>
-    </dependency>
-      <dependency>
-        <groupId>org.slf4j</groupId>
-        <artifactId>slf4j-api</artifactId>
-      </dependency>
-      <dependency>
-        <groupId>org.slf4j</groupId>
-        <artifactId>jcl-over-slf4j</artifactId>
-      </dependency>
-      <dependency>
-        <groupId>org.slf4j</groupId>
-        <artifactId>log4j-over-slf4j</artifactId>
-      </dependency>
-      <dependency>
-        <groupId>ch.qos.logback</groupId>
-        <artifactId>logback-core</artifactId>
-      </dependency>
-      <dependency>
-        <groupId>ch.qos.logback</groupId>
-        <artifactId>logback-classic</artifactId>
-      </dependency>
-  </dependencies>
-
-  <build>
-    <plugins>
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-jar-plugin</artifactId>
-        <executions>
-          <execution>
-            <id>artifact-jar</id>
-            <goals>
-              <goal>jar</goal>
-            </goals>
-          </execution>
-          <execution>
-            <id>test-jar</id>
-            <goals>
-              <goal>test-jar</goal>
-            </goals>
-          </execution>
-        </executions>
-        <configuration>
-          <archive>
-            <manifestFile>target/classes/META-INF/MANIFEST.MF</manifestFile>
-          </archive>
-        </configuration>
-      </plugin>
-      <plugin>
-          <groupId>org.apache.felix</groupId>
-          <artifactId>maven-bundle-plugin</artifactId>
-          <extensions>true</extensions>
-          <executions>
-              <execution>
-                  <id>bundle-manifest</id>
-                  <phase>process-classes</phase>
-                  <goals>
-                      <goal>manifest</goal>
-                  </goals>
-              </execution>
-          </executions>
-          <configuration>
-              <instructions>
-                <Bundle-SymbolicName>org.eclipse.jetty.osgi.boot.logback;singleton:=true</Bundle-SymbolicName>
-                <Bundle-Name>Jetty-OSGi-Logback Integration</Bundle-Name>
-                <Fragment-Host>org.eclipse.jetty.osgi.boot</Fragment-Host>
-                <Import-Package>
-ch.qos.logback.access.jetty;version="[0.9,1.1)";resolution:=optional,
-ch.qos.logback.access.jetty.v7;version="[0.9,1.1)";resolution:=optional,
-ch.qos.logback.*;version="[0.9,1.1)",
-org.osgi.framework.*,
-org.slf4j.*,
-*;resolution:=optional
-                </Import-Package>
-                <Export-Package>
-!org.eclipse.jetty.osgi.boot.logback.internal.*,
-org.eclipse.jetty.osgi.boot.logback.*;version="${parsedVersion.osgiVersion}"
-                </Export-Package>
-                <_nouses>true</_nouses>
-              </instructions>
-          </configuration>
-      </plugin>
-      <plugin>
-        <groupId>org.codehaus.mojo</groupId>
-        <artifactId>findbugs-maven-plugin</artifactId>
-        <configuration>
-          <onlyAnalyze>org.eclipse.jetty.osgi.boot.logback.*</onlyAnalyze>
-        </configuration>
-      </plugin>
-    </plugins>
-  </build>
-
-
-</project>
diff --git a/jetty-osgi/jetty-osgi-boot-logback/src/main/java/org/eclipse/jetty/osgi/boot/logback/FragmentActivator.java b/jetty-osgi/jetty-osgi-boot-logback/src/main/java/org/eclipse/jetty/osgi/boot/logback/FragmentActivator.java
deleted file mode 100644
index 043274e..0000000
--- a/jetty-osgi/jetty-osgi-boot-logback/src/main/java/org/eclipse/jetty/osgi/boot/logback/FragmentActivator.java
+++ /dev/null
@@ -1,92 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.osgi.boot.logback;
-
-import java.io.File;
-import java.util.Map;
-
-import org.eclipse.jetty.osgi.boot.internal.webapp.LibExtClassLoaderHelper;
-import org.eclipse.jetty.osgi.boot.internal.webapp.OSGiWebappClassLoader;
-import org.eclipse.jetty.osgi.boot.internal.webapp.LibExtClassLoaderHelper.IFilesInJettyHomeResourcesProcessor;
-import org.eclipse.jetty.osgi.boot.logback.internal.LogbackInitializer;
-import org.osgi.framework.BundleActivator;
-import org.osgi.framework.BundleContext;
-
-
-/**
- * Pseudo fragment activator.
- * Called by the main org.eclipse.jetty.osgi.boot bundle.
- * Please note: this is not a real BundleActivator. Simply something called back by
- * the host bundle.
- * The fragment is in charge of placing a hook to configure logback
- * when the files inside jettyhome/resources are parsed.
- */
-public class FragmentActivator implements BundleActivator, IFilesInJettyHomeResourcesProcessor
-{
-    /**
-     * 
-     */
-    public void start(BundleContext context) throws Exception
-    {
-        LibExtClassLoaderHelper.registeredFilesInJettyHomeResourcesProcessors.add(this);
-        
-        //now let's make sure no log4j, no slf4j and no commons.logging
-        //get inserted as a library that is not an osgi library
-        OSGiWebappClassLoader.addClassThatIdentifiesAJarThatMustBeRejected("org.apache.commons.logging.Log");
-        OSGiWebappClassLoader.addClassThatIdentifiesAJarThatMustBeRejected("org.apache.log4j.Logger");
-        OSGiWebappClassLoader.addClassThatIdentifiesAJarThatMustBeRejected("org.slf4j.Logger");
-        //OSGiWebappClassLoader.addClassThatIdentifiesAJarThatMustBeRejected(java.util.logging.Logger.class);
-        
-    }
-
-    /**
-     * Called when this bundle is stopped so the Framework can perform the
-     * bundle-specific activities necessary to stop the bundle. In general, this
-     * method should undo the work that the <code>BundleActivator.start</code>
-     * method started. There should be no active threads that were started by
-     * this bundle when this bundle returns. A stopped bundle must not call any
-     * Framework objects.
-     * 
-     * <p>
-     * This method must complete and return to its caller in a timely manner.
-     * 
-     * @param context The execution context of the bundle being stopped.
-     * @throws Exception If this method throws an exception, the
-     *         bundle is still marked as stopped, and the Framework will remove
-     *         the bundle's listeners, unregister all services registered by the
-     *         bundle, and release all services used by the bundle.
-     */
-    public void stop(BundleContext context) throws Exception
-    {
-    	LibExtClassLoaderHelper.registeredFilesInJettyHomeResourcesProcessors.remove(this);
-    }
-    
-    public void processFilesInResourcesFolder(File jettyHome, Map<String,File> files)
-    {
-    	try
-    	{
-    		LogbackInitializer.processFilesInResourcesFolder(jettyHome, files);
-    	}
-    	catch (Throwable t)
-    	{
-    		t.printStackTrace();
-    	}
-    }
-    
-}
diff --git a/jetty-osgi/jetty-osgi-boot-logback/src/main/java/org/eclipse/jetty/osgi/boot/logback/internal/LogbackInitializer.java b/jetty-osgi/jetty-osgi-boot-logback/src/main/java/org/eclipse/jetty/osgi/boot/logback/internal/LogbackInitializer.java
deleted file mode 100644
index 26f50b4..0000000
--- a/jetty-osgi/jetty-osgi-boot-logback/src/main/java/org/eclipse/jetty/osgi/boot/logback/internal/LogbackInitializer.java
+++ /dev/null
@@ -1,102 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.osgi.boot.logback.internal;
-
-import java.io.File;
-import java.util.Map;
-
-import org.slf4j.LoggerFactory;
-
-import ch.qos.logback.classic.LoggerContext;
-import ch.qos.logback.classic.joran.JoranConfigurator;
-import ch.qos.logback.core.joran.JoranConfiguratorBase;
-import ch.qos.logback.core.joran.spi.JoranException;
-import ch.qos.logback.core.util.StatusPrinter;
-
-/**
- * Setup logback eventually located in the config file inside jettyhome/resources
- * All logback related code is done in this separate class for better debug
- * and isolation when it does not load.
- */
-public class LogbackInitializer {
-
-    /**
-     * @return true when we are currently being run by the pde in development mode.
-     */
-    private static boolean isPDEDevelopment()
-    {
-        String eclipseCommands = System.getProperty("eclipse.commands");
-        // detect if we are being run from the pde: ie during development.
-        return eclipseCommands != null && eclipseCommands.indexOf("-dev") != -1
-                && (eclipseCommands.indexOf("-dev\n") != -1
-                        || eclipseCommands.indexOf("-dev\r") != -1
-                        || eclipseCommands.indexOf("-dev ") != -1);
-    }
-
-    
-    /**
-     * Follow the configuration for logback.
-     * unless the system propery was set in which case it
-     * was assume it was already setup.
-     */
-    public static void processFilesInResourcesFolder(File jettyHome, Map<String,File> files)
-    {
-    	String logbackConf = System.getProperty("logback.configurationFile");
-    	if (logbackConf != null)
-    	{
-    		File confFile = new File(logbackConf);
-    		if (confFile.exists())
-    		{
-    			//assume logback was configured by this one?
-    			return;
-    		}
-    	}
-    	
-    	File logConf = isPDEDevelopment() ? files.get("logback-dev.xml") : null;
-	    if (logConf == null)
-	    {
-	        logConf = files.get("logback-test.xml");
-	    }
-	    if (logConf == null)
-	    {
-	        logConf = files.get("logback.xml");
-	    }
-	    if (logConf == null)
-	    {
-	        return;
-	    }
-	 // assume SLF4J is bound to logback in the current environment
-	    LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
-	    
-	    try
-	    {
-	      JoranConfiguratorBase configurator = new JoranConfigurator();
-	      configurator.setContext(lc);
-	      lc.reset(); 
-	      configurator.doConfigure(logConf.getAbsoluteFile().getAbsolutePath());
-	    }
-	    catch (JoranException je)
-	    {
-	       je.printStackTrace();
-	    }
-	    StatusPrinter.printIfErrorsOccured(lc);
-	    
-    }
-
-}
diff --git a/jetty-osgi/jetty-osgi-boot-warurl/pom.xml b/jetty-osgi/jetty-osgi-boot-warurl/pom.xml
index 44f2866..f8d034f 100644
--- a/jetty-osgi/jetty-osgi-boot-warurl/pom.xml
+++ b/jetty-osgi/jetty-osgi-boot-warurl/pom.xml
@@ -2,7 +2,7 @@
   <parent>
     <groupId>org.eclipse.jetty.osgi</groupId>
     <artifactId>jetty-osgi-project</artifactId>
-    <version>8.1.17.v20150415</version>
+    <version>9.2.13.v20150730</version>
     <relativePath>../pom.xml</relativePath>
   </parent>
   <modelVersion>4.0.0</modelVersion>
@@ -64,10 +64,8 @@
           </executions>
           <configuration>
               <instructions>
-                  <Bundle-SymbolicName>org.eclipse.jetty.osgi.boot.warurl;singleton:=true</Bundle-SymbolicName>
                   <Bundle-Name>RFC66 War URL</Bundle-Name>
                   <Bundle-Activator>org.eclipse.jetty.osgi.boot.warurl.WarUrlActivator</Bundle-Activator>
-                  <_nouses>true</_nouses>
               </instructions>
           </configuration>
       </plugin>
diff --git a/jetty-osgi/jetty-osgi-boot/jettyhome/etc/jetty-deployer.xml b/jetty-osgi/jetty-osgi-boot/jettyhome/etc/jetty-deployer.xml
index 5a4fce4..b03a648 100644
--- a/jetty-osgi/jetty-osgi-boot/jettyhome/etc/jetty-deployer.xml
+++ b/jetty-osgi/jetty-osgi-boot/jettyhome/etc/jetty-deployer.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0"?>
-<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
 
 <Configure id="Server" class="org.eclipse.jetty.server.Server">
 
@@ -9,50 +9,13 @@
     <Call name="addBean">
       <Arg>
         <New id="DeploymentManager" class="org.eclipse.jetty.deploy.DeploymentManager">
-          <Set name="useStandardBindings">false</Set>
-          <Set name="lifeCycleBindings">
-            <Array type="org.eclipse.jetty.deploy.AppLifeCycle$Binding">
-              <Item>
-                <New class="org.eclipse.jetty.osgi.boot.OSGiDeployer"/>
-              </Item>
-              <Item>
-               <New class="org.eclipse.jetty.deploy.bindings.StandardStarter"/>
-              </Item>
-              <Item>
-                <New class="org.eclipse.jetty.deploy.bindings.StandardStopper"/>
-              </Item>
-              <Item>
-              <New class="org.eclipse.jetty.osgi.boot.OSGiUndeployer"/>
-              </Item>
-            </Array>
-          </Set>
           <Set name="contexts">
-            <Ref id="Contexts" />
+            <Ref refid="Contexts" />
           </Set>
           <Call name="setContextAttribute">
             <Arg>org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern</Arg>
             <Arg>.*/jsp-api-[^/]*\.jar$|.*/jsp-[^/]*\.jar$</Arg>
           </Call>
-          <!-- Providers of OSGi Apps -->
-          <!-- Call name="addAppProvider" -->
-            <!-- Arg -->
-              <!-- New class="org.eclipse.jetty.osgi.boot.OSGiAppProvider" -->
-              <!--
-                <Set name="defaultsDescriptor"><Property name="jetty.home" default="."/>/etc/webdefault.xml</Set>
-              -->
-              <!--
-                <Set name="scanInterval">5</Set>
-                <Set name="contextXmlDir"><Property name="jetty.home" default="." />/contexts</Set>
-                -->
-                <!-- comma separated list of bundle symbolic names that contain custom tag libraries (*.tld files) -->
-                <!-- if those bundles don't exist or can't be loaded no errors or warning will be issued!          -->
-                <!-- This default value plugs in the tld files of the reference implementation of JSF              -->
-                <!--
-                 <Set name="tldBundles"><Property name="org.eclipse.jetty.osgi.tldbundles" default="javax.faces.jsf-impl" /></Set>
-              </New>
-            </Arg>
-          </Call>
-          -->
         </New>
       </Arg>
     </Call>
diff --git a/jetty-osgi/jetty-osgi-boot/jettyhome/etc/jetty-nested.xml b/jetty-osgi/jetty-osgi-boot/jettyhome/etc/jetty-nested.xml
deleted file mode 100644
index c11cec1..0000000
--- a/jetty-osgi/jetty-osgi-boot/jettyhome/etc/jetty-nested.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<?xml version="1.0"?>
-<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
-
-<!-- =============================================================== -->
-<!-- Configure the Jetty Server Nested inside another                -->
-<!--  Servlet Container.                                             -->
-<!--                                                                 -->
-<!-- Documentation of this file format can be found at:              -->
-<!-- http://wiki.eclipse.org/Jetty/Reference/jetty.xml_syntax        -->
-<!--                                                                 -->
-<!-- =============================================================== -->
-
-<Configure id="Server" class="org.eclipse.jetty.server.Server">
-
-    <Call name="addConnector">
-      <Arg>
-          <New id="NestedConnector" class="org.eclipse.jetty.nested.NestedConnector">
-            <Set name="statsOn">false</Set>
-            <Set name="forwarded">true</Set>
-            <Set name="forwardedHostHeader">x-forwarded_for</Set>
-            <Set name="forwardedCipherSuiteHeader">sslclientcipher</Set>
-            <Set name="forwardedSslSessionIdHeader">sslsessionid</Set>
-            <Call name="addLifeCycleListener">
-              <Arg>
-                <New class="org.eclipse.jetty.osgi.nested.NestedConnectorListener" id="NestedConnectorListener">
-                  <Set name="nestedConnector"><Ref id="NestedConnector"/></Set>
-                </New>
-              </Arg>
-            </Call>
-          </New>
-      </Arg>
-    </Call>
-
-</Configure>
diff --git a/jetty-osgi/jetty-osgi-boot/jettyhome/etc/jetty-selector.xml b/jetty-osgi/jetty-osgi-boot/jettyhome/etc/jetty-selector.xml
index 46ccc85..67cd87c 100644
--- a/jetty-osgi/jetty-osgi-boot/jettyhome/etc/jetty-selector.xml
+++ b/jetty-osgi/jetty-osgi-boot/jettyhome/etc/jetty-selector.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0"?>
-<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
 
 <Configure id="Server" class="org.eclipse.jetty.server.Server">
 
@@ -10,17 +10,22 @@
 
     <Call name="addConnector">
       <Arg>
-          <New class="org.eclipse.jetty.server.nio.SelectChannelConnector">
+          <New class="org.eclipse.jetty.server.ServerConnector">
+            <Arg><Ref refid="Server" /></Arg>
+             <Arg name="factories">
+               <Array type="org.eclipse.jetty.server.ConnectionFactory">
+                <Item>
+                  <New class="org.eclipse.jetty.server.HttpConnectionFactory">
+                   <Arg name="config"><Ref refid="httpConfig" /></Arg>
+                  </New>
+                </Item>
+              </Array>
+             </Arg>
             <Set name="host"><Property name="jetty.host" /></Set>
             <Set name="port"><Property name="jetty.port" default="8080"/></Set>
-            <Set name="maxIdleTime">300000</Set>
-            <Set name="Acceptors">2</Set>
-            <Set name="statsOn">false</Set>
-            <Set name="confidentialPort">8443</Set>
-        <Set name="lowResourcesConnections">20000</Set>
-        <Set name="lowResourcesMaxIdleTime">5000</Set>
+            <Set name="idleTimeout">300000</Set>
           </New>
       </Arg>
     </Call>
-    
+
 </Configure>
diff --git a/jetty-osgi/jetty-osgi-boot/jettyhome/etc/jetty.xml b/jetty-osgi/jetty-osgi-boot/jettyhome/etc/jetty.xml
index 30f8ea9..5c882b0 100644
--- a/jetty-osgi/jetty-osgi-boot/jettyhome/etc/jetty.xml
+++ b/jetty-osgi/jetty-osgi-boot/jettyhome/etc/jetty.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0"?>
-<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
 
 
 <!-- =============================================================== -->
@@ -14,18 +14,14 @@
     <!-- =========================================================== -->
     <!-- Server Thread Pool                                          -->
     <!-- =========================================================== -->
-    <Set name="ThreadPool">
-      <!-- Default queued blocking threadpool -->
-      <New class="org.eclipse.jetty.util.thread.QueuedThreadPool">
-        <Set name="minThreads">10</Set>
-        <Set name="maxThreads">200</Set>
-        <Set name="detailedDump">false</Set>
-      </New>
-    </Set>
+    <Get name="ThreadPool">
+      <Set name="minThreads">10</Set>
+      <Set name="maxThreads">200</Set>
+    </Get>
 
 
     <!-- =========================================================== -->
-    <!-- Set handler Collection Structure                            --> 
+    <!-- Set handler Collection Structure                            -->
     <!-- =========================================================== -->
     <Set name="handler">
       <New id="Handlers" class="org.eclipse.jetty.server.handler.HandlerCollection">
@@ -45,21 +41,44 @@
       </New>
     </Set>
 
+    <New id="httpConfig" class="org.eclipse.jetty.server.HttpConfiguration">
+      <Set name="secureScheme">https</Set>
+      <Set name="securePort"><Property name="jetty.secure.port" default="8443" /></Set>
+      <Set name="outputBufferSize">32768</Set>
+      <Set name="requestHeaderSize">8192</Set>
+      <Set name="responseHeaderSize">8192</Set>
+      <Set name="sendServerVersion">true</Set>
+      <Set name="sendDateHeader">false</Set>
+      <Set name="headerCacheSize">512</Set>
+    </New>
+
 
     <!-- =========================================================== -->
     <!-- extra options                                               -->
     <!-- =========================================================== -->
     <Set name="stopAtShutdown">true</Set>
-    <Set name="sendServerVersion">true</Set>
-    <Set name="sendDateHeader">true</Set>
-    <Set name="gracefulShutdown">1000</Set>
+    <Set name="stopTimeout">1000</Set>
     <Set name="dumpAfterStart">false</Set>
     <Set name="dumpBeforeStop">false</Set>
 
-    
+
     <!-- =========================================================== -->
     <!-- jetty-jndi by default                                       -->
     <!-- =========================================================== -->
+    <Call class="org.eclipse.jetty.webapp.Configuration$ClassList" name="setServerDefault">
+      <Arg><Ref refid="Server" /></Arg>
+      <Call name="addAfter">
+        <Arg name="afterClass">org.eclipse.jetty.webapp.FragmentConfiguration</Arg>
+        <Arg>
+          <Array type="String">
+            <Item>org.eclipse.jetty.plus.webapp.EnvConfiguration</Item>
+            <Item>org.eclipse.jetty.plus.webapp.PlusConfiguration</Item>
+            <Item>org.eclipse.jetty.annotations.AnnotationConfiguration</Item>
+          </Array>
+        </Arg>
+      </Call>
+    </Call>
+
     <Call class="java.lang.System" name="setProperty">
       <Arg>java.naming.factory.initial</Arg>
       <Arg><Property name="java.naming.factory.initial" default="org.eclipse.jetty.jndi.InitialContextFactory"/></Arg>
diff --git a/jetty-osgi/jetty-osgi-boot/jettyhome/etc/webdefault.xml b/jetty-osgi/jetty-osgi-boot/jettyhome/etc/webdefault.xml
deleted file mode 100644
index 0078e77..0000000
--- a/jetty-osgi/jetty-osgi-boot/jettyhome/etc/webdefault.xml
+++ /dev/null
@@ -1,404 +0,0 @@
-<?xml version="1.0" encoding="ISO-8859-1"?>
-
-<!-- ===================================================================== -->
-<!-- This file contains the default descriptor for web applications.       -->
-<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
-<!-- The intent of this descriptor is to include jetty specific or common  -->
-<!-- configuration for all webapps.   If a context has a webdefault.xml    -->
-<!-- descriptor, it is applied before the contexts own web.xml file        -->
-<!--                                                                       -->
-<!-- A context may be assigned a default descriptor by:                    -->
-<!--  + Calling WebApplicationContext.setDefaultsDescriptor                -->
-<!--  + Passed an arg to addWebApplications                                -->
-<!--                                                                       -->
-<!-- This file is used both as the resource within the jetty.jar (which is -->
-<!-- used as the default if no explicit defaults descriptor is set) and it -->
-<!-- is copied to the etc directory of the Jetty distro and explicitly     -->
-<!-- by the jetty.xml file.                                                -->
-<!--                                                                       -->
-<!-- ===================================================================== -->
-<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" 
-   metadata-complete="true"
-   version="2.5"> 
-
-  <description>
-    Default web.xml file.  
-    This file is applied to a Web application before it's own WEB_INF/web.xml file
-  </description>
-
-
-  <!-- ==================================================================== -->
-  <!-- Context params to control Session Cookies                            -->
-  <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -->
-  <!-- UNCOMMENT TO ACTIVATE
-  <context-param>
-    <param-name>org.eclipse.jetty.servlet.SessionDomain</param-name>
-    <param-value>127.0.0.1</param-value>
-  </context-param>
-
-  <context-param>
-    <param-name>org.eclipse.jetty.servlet.SessionPath</param-name>
-    <param-value>/</param-value>
-  </context-param>
-
-  <context-param>
-    <param-name>org.eclipse.jetty.servlet.MaxAge</param-name>
-    <param-value>-1</param-value>
-  </context-param>
-  -->
-
-
-  <!-- ==================================================================== -->
-  <!-- The default servlet.                                                 -->
-  <!-- This servlet, normally mapped to /, provides the handling for static -->
-  <!-- content, OPTIONS and TRACE methods for the context.                  -->
-  <!-- The following initParameters are supported:                          -->
-  <!--                                                                      -->
-  <!--   acceptRanges     If true, range requests and responses are         -->
-  <!--                    supported                                         -->
-  <!--                                                                      -->
-  <!--   dirAllowed       If true, directory listings are returned if no    -->
-  <!--                    welcome file is found. Else 403 Forbidden.        -->
-  <!--                                                                      -->
-  <!--   welcomeServlets  If true, attempt to dispatch to welcome files     -->
-  <!--                    that are servlets, if no matching static          -->
-  <!--                    resources can be found.                           -->
-  <!--                                                                      -->
-  <!--   redirectWelcome  If true, redirect welcome file requests           -->
-  <!--                    else use request dispatcher forwards              -->
-  <!--                                                                      -->
-  <!--   gzip             If set to true, then static content will be served--> 
-  <!--                    as gzip content encoded if a matching resource is -->
-  <!--                    found ending with ".gz"                           -->
-  <!--                                                                      -->
-  <!--   resoureBase      Can be set to replace the context resource base   -->
-  <!--                                                                      -->
-  <!--   relativeResourceBase                                               -->
-  <!--                    Set with a pathname relative to the base of the   -->
-  <!--                    servlet context root. Useful for only serving     -->
-  <!--                    static content from only specific subdirectories. -->
-  <!--                                                                      -->
-  <!--   useFileMappedBuffer                                                -->
-  <!--                    If set to true (the default), a  memory mapped    -->
-  <!--                    file buffer will be used to serve static content  -->
-  <!--                    when using an NIO connector. Setting this value   -->
-  <!--                    to false means that a direct buffer will be used  -->
-  <!--                    instead. If you are having trouble with Windows   -->
-  <!--                    file locking, set this to false.                  -->
-  <!--                                                                      -->
-  <!--  cacheControl      If set, all static content will have this value   -->
-  <!--                    set as the cache-control header.                  -->
-  <!--                                                                      -->
-  <!--  maxCacheSize      Maximum size of the static resource cache         -->
-  <!--                                                                      -->
-  <!--  maxCachedFileSize Maximum size of any single file in the cache      -->
-  <!--                                                                      -->
-  <!--  maxCachedFiles    Maximum number of files in the cache              -->
-  <!--                                                                      -->
-  <!--  cacheType         "nio", "bio" or "both" to determine the type(s)   -->
-  <!--                    of resource cache. A bio cached buffer may be used-->
-  <!--                    by nio but is not as efficient as a nio buffer.   -->
-  <!--                    An nio cached buffer may not be used by bio.      -->
-  <!--                                                                      -->
-  <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -->
-  <servlet>
-    <servlet-name>default</servlet-name>
-    <servlet-class>org.eclipse.jetty.servlet.DefaultServlet</servlet-class>
-    <init-param>
-      <param-name>acceptRanges</param-name>
-      <param-value>true</param-value>
-    </init-param>
-    <init-param>
-      <param-name>dirAllowed</param-name>
-      <param-value>true</param-value>
-    </init-param>
-    <init-param>
-      <param-name>welcomeServlets</param-name>
-        <param-value>false</param-value>
-      </init-param>
-    <init-param>
-      <param-name>redirectWelcome</param-name>
-      <param-value>false</param-value>
-    </init-param>
-    <init-param>
-      <param-name>maxCacheSize</param-name>
-      <param-value>256000000</param-value>
-    </init-param>
-    <init-param>
-      <param-name>maxCachedFileSize</param-name>
-      <param-value>10000000</param-value>
-    </init-param>
-    <init-param>
-      <param-name>maxCachedFiles</param-name>
-      <param-value>1000</param-value>
-    </init-param>
-    <init-param>
-      <param-name>cacheType</param-name>
-      <param-value>both</param-value>
-    </init-param>
-    <init-param>
-      <param-name>gzip</param-name>
-      <param-value>true</param-value>
-    </init-param>
-    <init-param>
-      <param-name>useFileMappedBuffer</param-name>
-      <param-value>true</param-value>
-    </init-param>  
-    <!--
-    <init-param>
-      <param-name>cacheControl</param-name>
-      <param-value>max-age=3600,public</param-value>
-    </init-param>
-    -->
-    <load-on-startup>0</load-on-startup>
-  </servlet> 
-
-  <servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
-  
-
-  <!-- ==================================================================== -->
-  <!-- JSP Servlet                                                          -->
-  <!-- This is the jasper JSP servlet from the jakarta project              -->
-  <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -->
-  <!-- The JSP page compiler and execution servlet, which is the mechanism  -->
-  <!-- used by Glassfish to support JSP pages.  Traditionally, this servlet -->
-  <!-- is mapped to URL patterh "*.jsp".  This servlet supports the         -->
-  <!-- following initialization parameters (default values are in square    -->
-  <!-- brackets):                                                           -->
-  <!--                                                                      -->
-  <!--   checkInterval       If development is false and reloading is true, -->
-  <!--                       background compiles are enabled. checkInterval -->
-  <!--                       is the time in seconds between checks to see   -->
-  <!--                       if a JSP page needs to be recompiled. [300]    -->
-  <!--                                                                      -->
-  <!--   compiler            Which compiler Ant should use to compile JSP   -->
-  <!--                       pages.  See the Ant documenation for more      -->
-  <!--                       information. [javac]                           -->
-  <!--                                                                      -->
-  <!--   classdebuginfo      Should the class file be compiled with         -->
-  <!--                       debugging information?  [true]                 -->
-  <!--                                                                      -->
-  <!--   classpath           What class path should I use while compiling   -->
-  <!--                       generated servlets?  [Created dynamically      -->
-  <!--                       based on the current web application]          -->
-  <!--                       Set to ? to make the container explicitly set  -->
-  <!--                       this parameter.                                -->
-  <!--                                                                      -->
-  <!--   development         Is Jasper used in development mode (will check -->
-  <!--                       for JSP modification on every access)?  [true] -->
-  <!--                                                                      -->
-  <!--   enablePooling       Determines whether tag handler pooling is      -->
-  <!--                       enabled  [true]                                -->
-  <!--                                                                      -->
-  <!--   fork                Tell Ant to fork compiles of JSP pages so that -->
-  <!--                       a separate JVM is used for JSP page compiles   -->
-  <!--                       from the one Tomcat is running in. [true]      -->
-  <!--                                                                      -->
-  <!--   ieClassId           The class-id value to be sent to Internet      -->
-  <!--                       Explorer when using <jsp:plugin> tags.         -->
-  <!--                       [clsid:8AD9C840-044E-11D1-B3E9-00805F499D93]   -->
-  <!--                                                                      -->
-  <!--   javaEncoding        Java file encoding to use for generating java  -->
-  <!--                       source files. [UTF-8]                          -->
-  <!--                                                                      -->
-  <!--   keepgenerated       Should we keep the generated Java source code  -->
-  <!--                       for each page instead of deleting it? [true]   -->
-  <!--                                                                      -->
-  <!--   logVerbosityLevel   The level of detailed messages to be produced  -->
-  <!--                       by this servlet.  Increasing levels cause the  -->
-  <!--                       generation of more messages.  Valid values are -->
-  <!--                       FATAL, ERROR, WARNING, INFORMATION, and DEBUG. -->
-  <!--                       [WARNING]                                      -->
-  <!--                                                                      -->
-  <!--   mappedfile          Should we generate static content with one     -->
-  <!--                       print statement per input line, to ease        -->
-  <!--                       debugging?  [false]                            -->
-  <!--                                                                      -->
-  <!--                                                                      -->
-  <!--   reloading           Should Jasper check for modified JSPs?  [true] -->
-  <!--                                                                      -->
-  <!--   suppressSmap        Should the generation of SMAP info for JSR45   -->
-  <!--                       debugging be suppressed?  [false]              -->
-  <!--                                                                      -->
-  <!--   dumpSmap            Should the SMAP info for JSR45 debugging be    -->
-  <!--                       dumped to a file? [false]                      -->
-  <!--                       False if suppressSmap is true                  -->
-  <!--                                                                      -->
-  <!--   scratchdir          What scratch directory should we use when      -->
-  <!--                       compiling JSP pages?  [default work directory  -->
-  <!--                       for the current web application]               -->
-  <!--                                                                      -->
-  <!--   tagpoolMaxSize      The maximum tag handler pool size  [5]         -->
-  <!--                                                                      -->
-  <!--   xpoweredBy          Determines whether X-Powered-By response       -->
-  <!--                       header is added by generated servlet  [false]  -->
-  <!--                                                                      -->
-  <!-- If you wish to use Jikes to compile JSP pages:                       -->
-  <!--   Set the init parameter "compiler" to "jikes".  Define              -->
-  <!--   the property "-Dbuild.compiler.emacs=true" when starting Jetty     -->
-  <!--   to cause Jikes to emit error messages in a format compatible with  -->
-  <!--   Jasper.                                                            -->
-  <!--   If you get an error reporting that jikes can't use UTF-8 encoding, -->
-  <!--   try setting the init parameter "javaEncoding" to "ISO-8859-1".     -->
-  <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -->
-  <servlet id="jsp">
-    <servlet-name>jsp</servlet-name>
-    <servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
-    <init-param>
-        <param-name>logVerbosityLevel</param-name>
-        <param-value>DEBUG</param-value>
-    </init-param>
-    <init-param>
-        <param-name>fork</param-name>
-        <param-value>false</param-value>
-    </init-param>
-    <init-param>
-        <param-name>xpoweredBy</param-name>
-        <param-value>false</param-value>
-    </init-param>
-    <!--  
-    <init-param>
-        <param-name>classpath</param-name>
-        <param-value>?</param-value>
-    </init-param>
-    -->
-    <load-on-startup>0</load-on-startup>
-  </servlet>
-
-  <servlet-mapping> 
-    <servlet-name>jsp</servlet-name> 
-    <url-pattern>*.jsp</url-pattern> 
-    <url-pattern>*.jspf</url-pattern>
-    <url-pattern>*.jspx</url-pattern>
-    <url-pattern>*.xsp</url-pattern>
-    <url-pattern>*.JSP</url-pattern> 
-    <url-pattern>*.JSPF</url-pattern>
-    <url-pattern>*.JSPX</url-pattern>
-    <url-pattern>*.XSP</url-pattern>
-  </servlet-mapping>
-  
-  <!-- ==================================================================== -->
-  <!-- Dynamic Servlet Invoker.                                             -->
-  <!-- This servlet invokes anonymous servlets that have not been defined   -->
-  <!-- in the web.xml or by other means. The first element of the pathInfo  -->
-  <!-- of a request passed to the envoker is treated as a servlet name for  -->
-  <!-- an existing servlet, or as a class name of a new servlet.            -->
-  <!-- This servlet is normally mapped to /servlet/*                        -->
-  <!-- This servlet support the following initParams:                       -->
-  <!--                                                                      -->
-  <!--  nonContextServlets       If false, the invoker can only load        -->
-  <!--                           servlets from the contexts classloader.    -->
-  <!--                           This is false by default and setting this  -->
-  <!--                           to true may have security implications.    -->
-  <!--                                                                      -->
-  <!--  verbose                  If true, log dynamic loads                 -->
-  <!--                                                                      -->
-  <!--  *                        All other parameters are copied to the     -->
-  <!--                           each dynamic servlet as init parameters    -->
-  <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -->
-  <!-- Uncomment for dynamic invocation
-  <servlet>
-    <servlet-name>invoker</servlet-name>
-    <servlet-class>org.eclipse.jetty.servlet.Invoker</servlet-class>
-    <init-param>
-      <param-name>verbose</param-name>
-      <param-value>false</param-value>
-    </init-param>
-    <init-param>
-      <param-name>nonContextServlets</param-name>
-      <param-value>false</param-value>
-    </init-param>
-    <init-param>
-      <param-name>dynamicParam</param-name>
-      <param-value>anyValue</param-value>
-    </init-param>
-    <load-on-startup>0</load-on-startup>
-  </servlet>
-
-  <servlet-mapping> <servlet-name>invoker</servlet-name> <url-pattern>/servlet/*</url-pattern> </servlet-mapping>
-  -->
-
-
-
-  <!-- ==================================================================== -->
-  <session-config>
-    <session-timeout>30</session-timeout>
-  </session-config>
-
-  <!-- ==================================================================== -->
-  <!-- Default MIME mappings                                                -->
-  <!-- The default MIME mappings are provided by the mime.properties        -->
-  <!-- resource in the org.eclipse.jetty.server.jar file.  Additional or modified  -->
-  <!-- mappings may be specified here                                       -->
-  <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -->
-  <!-- UNCOMMENT TO ACTIVATE
-  <mime-mapping>
-    <extension>mysuffix</extension>
-    <mime-type>mymime/type</mime-type>
-  </mime-mapping>
-  -->
-
-  <!-- ==================================================================== -->
-  <welcome-file-list>
-    <welcome-file>index.html</welcome-file>
-    <welcome-file>index.htm</welcome-file>
-    <welcome-file>index.jsp</welcome-file>
-  </welcome-file-list>
-
-  <!-- ==================================================================== -->
-  <locale-encoding-mapping-list>
-    <locale-encoding-mapping><locale>ar</locale><encoding>ISO-8859-6</encoding></locale-encoding-mapping>
-    <locale-encoding-mapping><locale>be</locale><encoding>ISO-8859-5</encoding></locale-encoding-mapping>
-    <locale-encoding-mapping><locale>bg</locale><encoding>ISO-8859-5</encoding></locale-encoding-mapping>
-    <locale-encoding-mapping><locale>ca</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
-    <locale-encoding-mapping><locale>cs</locale><encoding>ISO-8859-2</encoding></locale-encoding-mapping>
-    <locale-encoding-mapping><locale>da</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
-    <locale-encoding-mapping><locale>de</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
-    <locale-encoding-mapping><locale>el</locale><encoding>ISO-8859-7</encoding></locale-encoding-mapping>
-    <locale-encoding-mapping><locale>en</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
-    <locale-encoding-mapping><locale>es</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
-    <locale-encoding-mapping><locale>et</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
-    <locale-encoding-mapping><locale>fi</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
-    <locale-encoding-mapping><locale>fr</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
-    <locale-encoding-mapping><locale>hr</locale><encoding>ISO-8859-2</encoding></locale-encoding-mapping>
-    <locale-encoding-mapping><locale>hu</locale><encoding>ISO-8859-2</encoding></locale-encoding-mapping>
-    <locale-encoding-mapping><locale>is</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
-    <locale-encoding-mapping><locale>it</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
-    <locale-encoding-mapping><locale>iw</locale><encoding>ISO-8859-8</encoding></locale-encoding-mapping>
-    <locale-encoding-mapping><locale>ja</locale><encoding>Shift_JIS</encoding></locale-encoding-mapping>
-    <locale-encoding-mapping><locale>ko</locale><encoding>EUC-KR</encoding></locale-encoding-mapping>     
-    <locale-encoding-mapping><locale>lt</locale><encoding>ISO-8859-2</encoding></locale-encoding-mapping>
-    <locale-encoding-mapping><locale>lv</locale><encoding>ISO-8859-2</encoding></locale-encoding-mapping>
-    <locale-encoding-mapping><locale>mk</locale><encoding>ISO-8859-5</encoding></locale-encoding-mapping>
-    <locale-encoding-mapping><locale>nl</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
-    <locale-encoding-mapping><locale>no</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
-    <locale-encoding-mapping><locale>pl</locale><encoding>ISO-8859-2</encoding></locale-encoding-mapping>
-    <locale-encoding-mapping><locale>pt</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
-    <locale-encoding-mapping><locale>ro</locale><encoding>ISO-8859-2</encoding></locale-encoding-mapping>
-    <locale-encoding-mapping><locale>ru</locale><encoding>ISO-8859-5</encoding></locale-encoding-mapping>
-    <locale-encoding-mapping><locale>sh</locale><encoding>ISO-8859-5</encoding></locale-encoding-mapping>
-    <locale-encoding-mapping><locale>sk</locale><encoding>ISO-8859-2</encoding></locale-encoding-mapping>
-    <locale-encoding-mapping><locale>sl</locale><encoding>ISO-8859-2</encoding></locale-encoding-mapping>
-    <locale-encoding-mapping><locale>sq</locale><encoding>ISO-8859-2</encoding></locale-encoding-mapping>
-    <locale-encoding-mapping><locale>sr</locale><encoding>ISO-8859-5</encoding></locale-encoding-mapping>
-    <locale-encoding-mapping><locale>sv</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
-    <locale-encoding-mapping><locale>tr</locale><encoding>ISO-8859-9</encoding></locale-encoding-mapping>
-    <locale-encoding-mapping><locale>uk</locale><encoding>ISO-8859-5</encoding></locale-encoding-mapping>
-    <locale-encoding-mapping><locale>zh</locale><encoding>GB2312</encoding></locale-encoding-mapping>
-    <locale-encoding-mapping><locale>zh_TW</locale><encoding>Big5</encoding></locale-encoding-mapping>   
-  </locale-encoding-mapping-list>
-  
-  <security-constraint>
-    <web-resource-collection>
-      <web-resource-name>Disable TRACE</web-resource-name>
-      <url-pattern>/</url-pattern>
-      <http-method>TRACE</http-method>
-    </web-resource-collection>
-    <auth-constraint/>
-  </security-constraint>
-  
-</web-app>
-
diff --git a/jetty-osgi/jetty-osgi-boot/pom.xml b/jetty-osgi/jetty-osgi-boot/pom.xml
index bd936ca..5e62e3f 100644
--- a/jetty-osgi/jetty-osgi-boot/pom.xml
+++ b/jetty-osgi/jetty-osgi-boot/pom.xml
@@ -2,8 +2,7 @@
   <parent>
     <groupId>org.eclipse.jetty.osgi</groupId>
     <artifactId>jetty-osgi-project</artifactId>
-    <version>8.1.17.v20150415</version>
-    <relativePath>../pom.xml</relativePath>
+    <version>9.2.13.v20150730</version>
   </parent>
   <modelVersion>4.0.0</modelVersion>
   <artifactId>jetty-osgi-boot</artifactId>
@@ -31,54 +30,50 @@
       <artifactId>jetty-jmx</artifactId>
     </dependency>
     <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-nested</artifactId>
-    </dependency>
-    <dependency>
     	<groupId>org.eclipse.osgi</groupId>
     	<artifactId>org.eclipse.osgi</artifactId>
     </dependency>
     <dependency>
-        <groupId>org.eclipse.osgi</groupId>
-        <artifactId>org.eclipse.osgi.services</artifactId>
+      <groupId>org.eclipse.osgi</groupId>
+      <artifactId>org.eclipse.osgi.services</artifactId>
     </dependency>
   </dependencies>
 
-      <build> 
-        <plugins> 
-            <plugin> 
-                <artifactId>maven-antrun-plugin</artifactId> 
-                <executions> 
-                    <execution> 
-                        <phase>process-resources</phase> 
-                        <configuration> 
+      <build>
+        <plugins>
+            <plugin>
+                <artifactId>maven-antrun-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <phase>process-resources</phase>
+                        <configuration>
                             <tasks>
                                 <!--delete file="target/classes/META-INF/MANIFEST.MF" /-->
-                                <copy todir="target/classes/jettyhome"> 
-                                    <fileset dir="jettyhome"> 
-                                        <exclude name="**/*.log" /> 
-                                    </fileset> 
-                                </copy> 
-                            </tasks> 
-                        </configuration> 
-                        <goals> 
-                            <goal>run</goal> 
+                                <copy todir="target/classes/jettyhome">
+                                    <fileset dir="jettyhome">
+                                        <exclude name="**/*.log" />
+                                    </fileset>
+                                </copy>
+                            </tasks>
+                        </configuration>
+                        <goals>
+                            <goal>run</goal>
                         </goals>
                     </execution>
                 </executions>
             </plugin>
             <plugin>
-                <groupId>org.apache.maven.plugins</groupId> 
-                <artifactId>maven-jar-plugin</artifactId> 
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-jar-plugin</artifactId>
                 <executions>
                     <execution>
-                        <id>artifact-jar</id> 
+                        <id>artifact-jar</id>
                         <goals>
-                            <goal>jar</goal> 
+                            <goal>jar</goal>
                         </goals>
                     </execution>
                     <execution>
-                        <id>test-jar</id> 
+                        <id>test-jar</id>
                         <goals>
                             <goal>test-jar</goal>
                         </goals>
@@ -86,7 +81,7 @@
                 </executions>
                 <configuration>
                     <archive>
-                        <manifestFile>target/classes/META-INF/MANIFEST.MF</manifestFile> 
+                        <manifestFile>target/classes/META-INF/MANIFEST.MF</manifestFile>
                     </archive>
                 </configuration>
             </plugin>
@@ -99,48 +94,41 @@
                         <id>bundle-manifest</id>
                         <phase>process-classes</phase>
                         <goals>
-                            <goal>manifest</goal> 
+                            <goal>manifest</goal>
                         </goals>
                     </execution>
                 </executions>
                 <configuration>
                     <instructions>
                         <Bundle-SymbolicName>org.eclipse.jetty.osgi.boot;singleton:=true</Bundle-SymbolicName>
-                        <Bundle-Name>Jetty OSGi Boot</Bundle-Name>
                         <Bundle-Activator>org.eclipse.jetty.osgi.boot.JettyBootstrapActivator</Bundle-Activator>
-                        <Bundle-RequiredExecutionEnvironment>J2SE-1.5</Bundle-RequiredExecutionEnvironment>
-                        <Export-Package>org.eclipse.jetty.osgi.boot;version="${parsedVersion.osgiVersion}",org.eclipse.jetty.osgi.boot.utils,org.eclipse.jetty.osgi.nested;version="${parsedVersion.osgiVersion}"</Export-Package> 
-                        <!-- disable the uses directive: jetty will accomodate pretty much any versions
-                        of the packages it uses; no need to reflect some tight dependency determined at
-                        compilation time. --> 
-                        <_nouses>true</_nouses>
+                        <DynamicImport-Package>org.eclipse.jetty.*;version="[9.1,10.0)"</DynamicImport-Package>
                         <Import-Package>javax.mail;version="1.4.0";resolution:=optional,
  javax.mail.event;version="1.4.0";resolution:=optional,
  javax.mail.internet;version="1.4.0";resolution:=optional,
  javax.mail.search;version="1.4.0";resolution:=optional,
  javax.mail.util;version="1.4.0";resolution:=optional,
- javax.servlet;version="2.6.0",
- javax.servlet.http;version="2.6.0",
+ javax.servlet;version="[3.1,3.2)",
+ javax.servlet.http;version="[3.1,3.2)",
  javax.transaction;version="1.1.0";resolution:=optional,
  javax.transaction.xa;version="1.1.0";resolution:=optional,
- org.eclipse.jetty.nested;version="[8.1,9)";resolution:=optional,
- org.eclipse.jetty.annotations;version="[8.1,9)";resolution:=optional,
+ org.eclipse.jetty.annotations;version="9.1";resolution:=optional,
+ org.eclipse.jetty.plus.webapp;version="9.1";resolution:=optional,
+ org.objectweb.asm;version=4;resolution:=optional,
  org.osgi.framework,
  org.osgi.service.cm;version="1.2.0",
  org.osgi.service.packageadmin,
- org.osgi.service.startlevel;version="1.0.o",
+ org.osgi.service.startlevel;version="1.0.0",
  org.osgi.service.url;version="1.0.0",
  org.osgi.util.tracker;version="1.3.0",
  org.slf4j;resolution:=optional,
- org.slf4j.spi;resolution:=optional, 
+ org.slf4j.spi;resolution:=optional,
  org.slf4j.helpers;resolution:=optional,
  org.xml.sax,
  org.xml.sax.helpers,
  *
                         </Import-Package>
-                        <DynamicImport-Package>org.eclipse.jetty.*;version="[8.1,9)"</DynamicImport-Package>
-                        <!--Require-Bundle/-->
-                        <!-- Bundle-RequiredExecutionEnvironment>JavaSE-1.6</Bundle-RequiredExecutionEnvironment--> 
+                        <_nouses>true</_nouses>
                     </instructions>
                 </configuration>
             </plugin>
@@ -150,8 +138,8 @@
                 <configuration>
                     <onlyAnalyze>org.eclipse.jetty.osgi.boot.*</onlyAnalyze>
                 </configuration>
-            </plugin> 
+            </plugin>
         </plugins>
     </build>
-  
+
 </project>
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/annotations/AnnotationConfiguration.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/annotations/AnnotationConfiguration.java
index 8110508..a1c618c 100644
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/annotations/AnnotationConfiguration.java
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/annotations/AnnotationConfiguration.java
@@ -18,16 +18,17 @@
 
 package org.eclipse.jetty.osgi.annotations;
 
-import java.util.ArrayList;
-import java.util.List;
+import java.util.HashSet;
+import java.util.Set;
 
-import org.eclipse.jetty.annotations.AbstractDiscoverableAnnotationHandler;
-import org.eclipse.jetty.annotations.AnnotationParser.DiscoverableAnnotationHandler;
+import org.eclipse.jetty.annotations.AnnotationParser.Handler;
 import org.eclipse.jetty.annotations.ClassNameResolver;
+import org.eclipse.jetty.osgi.boot.OSGiWebInfConfiguration;
 import org.eclipse.jetty.osgi.boot.OSGiWebappConstants;
 import org.eclipse.jetty.osgi.boot.utils.internal.PackageAdminServiceTracker;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
 import org.eclipse.jetty.util.resource.Resource;
-import org.eclipse.jetty.webapp.DiscoveredAnnotation;
 import org.eclipse.jetty.webapp.WebAppContext;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.Constants;
@@ -39,7 +40,37 @@ import org.osgi.framework.Constants;
  */
 public class AnnotationConfiguration extends org.eclipse.jetty.annotations.AnnotationConfiguration
 {
+    private static final Logger LOG = Log.getLogger(org.eclipse.jetty.annotations.AnnotationConfiguration.class);
+    
+    public class BundleParserTask extends ParserTask
+    {
+        
+        public BundleParserTask (AnnotationParser parser, Set<? extends Handler>handlers, Resource resource, ClassNameResolver resolver)
+        {
+            super(parser, handlers, resource, resolver);
+        }
 
+        public Void call() throws Exception
+        {
+            if (_parser != null)
+            {
+                org.eclipse.jetty.osgi.annotations.AnnotationParser osgiAnnotationParser = (org.eclipse.jetty.osgi.annotations.AnnotationParser)_parser;
+                Bundle bundle = osgiAnnotationParser.getBundle(_resource);
+                if (_stat != null)
+                    _stat.start();
+                osgiAnnotationParser.parse(_handlers, bundle,  _resolver); 
+                if (_stat != null)
+                    _stat.end();
+            }
+            return null;
+        }
+    }
+    
+    
+    public AnnotationConfiguration()
+    {
+    }
+    
     /**
      * This parser scans the bundles using the OSGi APIs instead of assuming a jar.
      */
@@ -66,30 +97,31 @@ public class AnnotationConfiguration extends org.eclipse.jetty.annotations.Annot
         AnnotationParser oparser = (AnnotationParser)parser;
         
         Bundle webbundle = (Bundle) context.getAttribute(OSGiWebappConstants.JETTY_OSGI_BUNDLE);
-        Bundle[] fragAndRequiredBundles = PackageAdminServiceTracker.INSTANCE.getFragmentsAndRequiredBundles(webbundle);
+        Set<Bundle> fragAndRequiredBundles = (Set<Bundle>)context.getAttribute(OSGiWebInfConfiguration.FRAGMENT_AND_REQUIRED_BUNDLES);
         if (fragAndRequiredBundles != null)
         {
-            //index:
+            //index and scan fragments
             for (Bundle bundle : fragAndRequiredBundles)
             {
+                //skip bundles that have been uninstalled since we discovered them
+                if (bundle.getState() == Bundle.UNINSTALLED)
+                    continue;
+                
                 Resource bundleRes = oparser.indexBundle(bundle);
                 if (!context.getMetaData().getWebInfJars().contains(bundleRes))
                 {
                     context.getMetaData().addWebInfJar(bundleRes);
                 }
-            }
-        
-            //scan the fragments
-            for (Bundle fragmentBundle : fragAndRequiredBundles)
-            {
-                if (fragmentBundle.getHeaders().get(Constants.FRAGMENT_HOST) != null)
+                
+                if (bundle.getHeaders().get(Constants.FRAGMENT_HOST) != null)
                 {
                     //a fragment indeed:
-                    parseFragmentBundle(context,oparser,webbundle,fragmentBundle);
+                    parseFragmentBundle(context,oparser,webbundle,bundle);
                 }
             }
         }
         //scan ourselves
+        oparser.indexBundle(webbundle);
         parseWebBundle(context,oparser,webbundle);
         
         //scan the WEB-INF/lib
@@ -99,6 +131,10 @@ public class AnnotationConfiguration extends org.eclipse.jetty.annotations.Annot
             //scan the required bundles
             for (Bundle requiredBundle : fragAndRequiredBundles)
             {
+                //skip bundles that have been uninstalled since we discovered them
+                if (requiredBundle.getState() == Bundle.UNINSTALLED)
+                    continue;
+                
                 if (requiredBundle.getHeaders().get(Constants.FRAGMENT_HOST) == null)
                 {
                     //a bundle indeed:
@@ -151,27 +187,24 @@ public class AnnotationConfiguration extends org.eclipse.jetty.annotations.Annot
     }
     
     protected void parseBundle(WebAppContext context, AnnotationParser parser,
-            Bundle webbundle, Bundle bundle) throws Exception
-    {
-        
-        Resource bundleRes = parser.getResource(bundle);
-        
-        parser.clearHandlers();
-        for (DiscoverableAnnotationHandler h:_discoverableAnnotationHandlers)
+                               Bundle webbundle, Bundle bundle) throws Exception
+                               {
+
+        Resource bundleRes = parser.getResource(bundle);  
+        Set<Handler> handlers = new HashSet<Handler>();
+        handlers.addAll(_discoverableAnnotationHandlers);
+        if (_classInheritanceHandler != null)
+            handlers.add(_classInheritanceHandler);
+        handlers.addAll(_containerInitializerAnnotationHandlers);
+
+        ClassNameResolver classNameResolver = createClassNameResolver(context);
+        if (_parserTasks != null)
         {
-            if (h instanceof AbstractDiscoverableAnnotationHandler)
-            {
-                if (webbundle == bundle)                    
-                ((AbstractDiscoverableAnnotationHandler)h).setResource(null); 
-                else
-                    ((AbstractDiscoverableAnnotationHandler)h).setResource(bundleRes);  
-            }
+            BundleParserTask task = new BundleParserTask(parser, handlers, bundleRes, classNameResolver);
+            _parserTasks.add(task);
+            if (LOG.isDebugEnabled())
+                task.setStatistic(new TimeStatistic());
         }
-        parser.registerHandlers(_discoverableAnnotationHandlers);
-        parser.registerHandler(_classInheritanceHandler);
-        parser.registerHandlers(_containerInitializerAnnotationHandlers);
-      
-        parser.parse(bundle,createClassNameResolver(context));
     }
     
     /**
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/annotations/AnnotationParser.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/annotations/AnnotationParser.java
index 677cda2..06a518c 100644
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/annotations/AnnotationParser.java
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/annotations/AnnotationParser.java
@@ -19,19 +19,20 @@
 package org.eclipse.jetty.osgi.annotations;
 
 import java.io.File;
+import java.io.InputStream;
 import java.net.URI;
 import java.net.URL;
 import java.util.Comparator;
 import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
 import java.util.Set;
 import java.util.StringTokenizer;
 import java.util.TreeSet;
+import java.util.concurrent.ConcurrentHashMap;
 
 import org.eclipse.jetty.annotations.ClassNameResolver;
 import org.eclipse.jetty.osgi.boot.utils.BundleFileLocatorHelper;
+import org.eclipse.jetty.osgi.boot.utils.BundleFileLocatorHelperFactory;
+import org.eclipse.jetty.util.ConcurrentHashSet;
 import org.eclipse.jetty.util.resource.Resource;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.Constants;
@@ -41,11 +42,13 @@ import org.osgi.framework.Constants;
  */
 public class AnnotationParser extends org.eclipse.jetty.annotations.AnnotationParser
 {
-    private Set<URI> _alreadyParsed = new HashSet<URI>();
+    private Set<URI> _alreadyParsed = new ConcurrentHashSet<URI>();
+    
+    private ConcurrentHashMap<URI,Bundle> _uriToBundle = new ConcurrentHashMap<URI, Bundle>();
+    private ConcurrentHashMap<Bundle,Resource> _bundleToResource = new ConcurrentHashMap<Bundle,Resource>();
+    private ConcurrentHashMap<Resource, Bundle> _resourceToBundle = new ConcurrentHashMap<Resource, Bundle>();
+    private ConcurrentHashMap<Bundle,URI> _bundleToUri = new ConcurrentHashMap<Bundle, URI>();
     
-    private Map<URI,Bundle> _uriToBundle = new HashMap<URI, Bundle>();
-    private Map<Bundle,Resource> _resourceToBundle = new HashMap<Bundle,Resource>();
-    private Map<Bundle,URI> _bundleToUri = new HashMap<Bundle, URI>();
     
     /**
      * Keep track of a jetty URI Resource and its associated OSGi bundle.
@@ -55,12 +58,13 @@ public class AnnotationParser extends org.eclipse.jetty.annotations.AnnotationPa
      */
     protected Resource indexBundle(Bundle bundle) throws Exception
     {
-        File bundleFile = BundleFileLocatorHelper.DEFAULT.getBundleInstallLocation(bundle);
+        File bundleFile = BundleFileLocatorHelperFactory.getFactory().getHelper().getBundleInstallLocation(bundle);
         Resource resource = Resource.newResource(bundleFile.toURI());
         URI uri = resource.getURI();
-        _uriToBundle.put(uri,bundle);
-        _bundleToUri.put(bundle,uri);
-        _resourceToBundle.put(bundle,resource);
+        _uriToBundle.putIfAbsent(uri,bundle);
+        _bundleToUri.putIfAbsent(bundle,uri);
+        _bundleToResource.putIfAbsent(bundle,resource);
+        _resourceToBundle.putIfAbsent(resource,bundle);
         return resource;
     }
     protected URI getURI(Bundle bundle)
@@ -69,13 +73,19 @@ public class AnnotationParser extends org.eclipse.jetty.annotations.AnnotationPa
     }
     protected Resource getResource(Bundle bundle)
     {
-        return _resourceToBundle.get(bundle);
+        return _bundleToResource.get(bundle);
+    }
+    protected Bundle getBundle (Resource resource)
+    {
+        return _resourceToBundle.get(resource);
     }
+    
+    
     /**
      * 
      */
     @Override
-    public void parse (URI[] uris, ClassNameResolver resolver)
+    public void parse (Set<? extends Handler> handlers, URI[] uris, ClassNameResolver resolver)
     throws Exception
     {
         for (URI uri : uris)
@@ -89,16 +99,16 @@ public class AnnotationParser extends org.eclipse.jetty.annotations.AnnotationPa
                 }
                 //a jar in WEB-INF/lib or the WEB-INF/classes
                 //use the behavior of the super class for a standard jar.
-                super.parse(new URI[] {uri},resolver);
+                super.parse(handlers, new URI[] {uri},resolver);
             }
             else
             {
-                parse(associatedBundle,resolver);
+                parse(handlers, associatedBundle,resolver);
             }
         }
     }
     
-    protected void parse(Bundle bundle, ClassNameResolver resolver)
+    protected void parse(Set<? extends Handler> handlers, Bundle bundle, ClassNameResolver resolver)
     throws Exception
     {
         URI uri = _bundleToUri.get(bundle);
@@ -187,11 +197,21 @@ public class AnnotationParser extends org.eclipse.jetty.annotations.AnnotationPa
                 //remove the starting '/'
                 name = path.substring(1);
             }
+            if (name == null)
+            {
+                //found some .class file in the archive that was not under one of the prefix paths
+                //or the bundle classpath wasn't simply ".", so skip it
+                continue;
+            }
             //transform into a classname to pass to the resolver
             String shortName =  name.replace('/', '.').substring(0,name.length()-6);
-            if ((resolver == null)|| (!resolver.isExcluded(shortName) && (!isParsed(shortName) || resolver.shouldOverride(shortName))))
-                scanClass(classUrl.openStream());
+            if ((resolver == null) || (!resolver.isExcluded(shortName) && (!isParsed(shortName) || resolver.shouldOverride(shortName))))
+            {
+                try (InputStream classInputStream = classUrl.openStream())
+                {
+                    scanClass(handlers, getResource(bundle), classInputStream);
+                }
+            }
         }
     }
-    
 }
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/AbstractContextProvider.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/AbstractContextProvider.java
index 9e2f136..cae6d32 100644
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/AbstractContextProvider.java
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/AbstractContextProvider.java
@@ -22,14 +22,12 @@ import java.io.File;
 import java.net.URL;
 import java.util.Dictionary;
 import java.util.HashMap;
-import java.util.Hashtable;
 
 import org.eclipse.jetty.deploy.App;
 import org.eclipse.jetty.deploy.AppProvider;
 import org.eclipse.jetty.deploy.DeploymentManager;
 import org.eclipse.jetty.osgi.boot.internal.serverfactory.ServerInstanceWrapper;
-import org.eclipse.jetty.osgi.boot.internal.webapp.BundleFileLocatorHelperFactory;
-import org.eclipse.jetty.osgi.boot.utils.EventSender;
+import org.eclipse.jetty.osgi.boot.utils.BundleFileLocatorHelperFactory;
 import org.eclipse.jetty.osgi.boot.utils.OSGiClassLoader;
 import org.eclipse.jetty.server.handler.ContextHandler;
 import org.eclipse.jetty.util.component.AbstractLifeCycle;
@@ -37,11 +35,8 @@ import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
 import org.eclipse.jetty.util.resource.JarResource;
 import org.eclipse.jetty.util.resource.Resource;
-import org.eclipse.jetty.webapp.WebAppContext;
 import org.eclipse.jetty.xml.XmlConfiguration;
 import org.osgi.framework.Bundle;
-import org.osgi.framework.FrameworkUtil;
-import org.osgi.framework.ServiceRegistration;
 
 
 
@@ -49,14 +44,15 @@ import org.osgi.framework.ServiceRegistration;
 /**
  * AbstractContextProvider
  *
- *
+ * Base class for DeploymentManager Providers that can deploy ContextHandlers into 
+ * Jetty that have been discovered via OSGI either as bundles or services.
+ * 
  */
 public abstract class AbstractContextProvider extends AbstractLifeCycle implements AppProvider
 {
     private static final Logger LOG = Log.getLogger(AbstractContextProvider.class);
     
-    private DeploymentManager _deploymentManager;
-    
+    private DeploymentManager _deploymentManager;    
     
     private ServerInstanceWrapper _serverWrapper;
     
@@ -65,7 +61,7 @@ public abstract class AbstractContextProvider extends AbstractLifeCycle implemen
     
     /* ------------------------------------------------------------ */
     /**
-     * BundleApp
+     * OSGiApp
      *
      *
      */
@@ -147,60 +143,12 @@ public abstract class AbstractContextProvider extends AbstractLifeCycle implemen
             {   
                 //apply the contextFile, creating the ContextHandler, the DeploymentManager will register it in the ContextHandlerCollection
                 Resource res = null;
-
-                //try to find the context file in the filesystem
-                if (_contextFile.startsWith("/"))
-                    res = getFileAsResource(_contextFile);
-
-                //try to find it relative to jetty home
-                if (res == null)
-                {
-                    //See if the specific server we are related to has jetty.home set
-                    String jettyHome = (String)getServerInstanceWrapper().getServer().getAttribute(OSGiServerConstants.JETTY_HOME);
-                    if (jettyHome != null)
-                        res = getFileAsResource(jettyHome, _contextFile);
-
-                    //try to see if a SystemProperty for jetty.home is set
-                    if (res == null)
-                    {
-                        jettyHome =  System.getProperty(OSGiServerConstants.JETTY_HOME);
-
-                        if (jettyHome != null)
-                        {
-                            if (jettyHome.startsWith("\"") || jettyHome.startsWith("'"))
-                                jettyHome = jettyHome.substring(1);
-                            if (jettyHome.endsWith("\"") || (jettyHome.endsWith("'")))
-                                jettyHome = jettyHome.substring(0,jettyHome.length()-1);
-
-                            res = getFileAsResource(jettyHome, _contextFile); 
-                            if (LOG.isDebugEnabled()) LOG.debug("jetty home context file:"+res);
-                        }
-                    }
-                }
-
-                //try to find it relative to an override location that has been specified
-                if (res == null)
-                {                 
-                    if (bundleOverrideLocation != null)
-                    { 
-                        res = getFileAsResource(Resource.newResource(bundleOverrideLocation).getFile(), _contextFile);
-                        if (LOG.isDebugEnabled()) LOG.debug("Bundle override location context file:"+res);
-                    }
-                }         
-
-                //try to find it relative to the bundle in which it is being deployed
-                if (res == null)
-                {
-                    if (_contextFile.startsWith("./"))
-                        _contextFile = _contextFile.substring(1);
-
-                    if (!_contextFile.startsWith("/"))
-                        _contextFile = "/" + _contextFile;
-
-                    URL contextURL = _bundle.getEntry(_contextFile);
-                    if (contextURL != null)
-                        res = Resource.newResource(contextURL);
-                }
+                
+                String jettyHome = (String)getServerInstanceWrapper().getServer().getAttribute(OSGiServerConstants.JETTY_HOME);
+                if (jettyHome == null)
+                    jettyHome =  System.getProperty(OSGiServerConstants.JETTY_HOME);
+                
+                res = findFile(_contextFile, jettyHome, bundleOverrideLocation, _bundle);
 
                 //apply the context xml file, either to an existing ContextHandler, or letting the
                 //it create the ContextHandler as necessary
@@ -218,7 +166,7 @@ public abstract class AbstractContextProvider extends AbstractLifeCycle implemen
                         //put the server instance in
                         properties.put("Server", getServerInstanceWrapper().getServer());
                         //put in the location of the bundle root
-                        properties.put("bundle.root", rootResource.toString());
+                        properties.put(OSGiWebappConstants.JETTY_BUNDLE_ROOT, rootResource.toString());
                         
                         // insert the bundle's location as a property.
                         xmlConfiguration.getProperties().putAll(properties);
@@ -267,54 +215,6 @@ public abstract class AbstractContextProvider extends AbstractLifeCycle implemen
            
         }
 
-
-        private Resource getFileAsResource (String dir, String file)
-        {
-            Resource r = null;
-            try
-            {
-                File asFile = new File (dir, file);
-                if (asFile.exists())
-                    r = Resource.newResource(asFile);
-            }
-            catch (Exception e)
-            {
-                r = null;
-            } 
-            return r;
-        }
-        
-        private Resource getFileAsResource (String file)
-        {
-            Resource r = null;
-            try
-            {
-                File asFile = new File (file);
-                if (asFile.exists())
-                    r = Resource.newResource(asFile);
-            }
-            catch (Exception e)
-            {
-                r = null;
-            } 
-            return r;
-        }
-        
-        private Resource getFileAsResource (File dir, String file)
-        {
-            Resource r = null;
-            try
-            {
-                File asFile = new File (dir, file);
-                if (asFile.exists())
-                    r = Resource.newResource(asFile);
-            } 
-            catch (Exception e)
-            {
-                r = null;
-            } 
-            return r;
-        }
     }
     
     /* ------------------------------------------------------------ */
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/AbstractOSGiApp.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/AbstractOSGiApp.java
index a5e8925..174275c 100644
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/AbstractOSGiApp.java
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/AbstractOSGiApp.java
@@ -18,6 +18,8 @@
 
 package org.eclipse.jetty.osgi.boot;
 
+import java.io.File;
+import java.net.URL;
 import java.util.Dictionary;
 import java.util.Hashtable;
 
@@ -25,6 +27,9 @@ import org.eclipse.jetty.deploy.App;
 import org.eclipse.jetty.deploy.AppProvider;
 import org.eclipse.jetty.deploy.DeploymentManager;
 import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.FrameworkUtil;
 import org.osgi.framework.ServiceRegistration;
@@ -32,12 +37,15 @@ import org.osgi.framework.ServiceRegistration;
 
 
 /**
- * AbstractBundleApp
- *
+ * AbstractOSGiApp
  *
+ * Base class representing info about a webapp/ContextHandler that is deployed into Jetty.
+ * 
  */
 public abstract class AbstractOSGiApp extends App
 {      
+    private static final Logger LOG = Log.getLogger(AbstractOSGiApp.class);
+    
     protected Bundle _bundle;
     protected Dictionary _properties;
     protected ServiceRegistration _registration;
@@ -117,4 +125,91 @@ public abstract class AbstractOSGiApp extends App
         _registration = null;
     }
 
+    protected Resource getFileAsResource (String dir, String file)
+    {
+        Resource r = null;
+        try
+        {
+            File asFile = new File (dir, file);
+            if (asFile.exists())
+                r = Resource.newResource(asFile);
+        }
+        catch (Exception e)
+        {
+            r = null;
+        } 
+        return r;
+    }
+    
+    protected Resource getFileAsResource (String file)
+    {
+        Resource r = null;
+        try
+        {
+            File asFile = new File (file);
+            if (asFile.exists())
+                r = Resource.newResource(asFile);
+        }
+        catch (Exception e)
+        {
+            r = null;
+        } 
+        return r;
+    }
+    
+    protected Resource findFile (String fileName, String jettyHome, String bundleOverrideLocation, Bundle containingBundle)
+    {
+        Resource res = null;
+
+        //try to find the context file in the filesystem
+        if (fileName.startsWith("/"))
+            res = getFileAsResource(fileName);
+        if (res != null)
+            return res;
+
+        //try to find it relative to jetty home
+        if (jettyHome != null)
+        {
+            if (jettyHome.startsWith("\"") || jettyHome.startsWith("'"))
+                jettyHome = jettyHome.substring(1);
+            if (jettyHome.endsWith("\"") || (jettyHome.endsWith("'")))
+                jettyHome = jettyHome.substring(0,jettyHome.length()-1);
+
+            res = getFileAsResource(jettyHome, fileName); 
+        }
+        if (res != null)
+            return res;
+       
+
+        //try to find it relative to an override location that has been specified               
+        if (bundleOverrideLocation != null)
+        { 
+            try(Resource location=Resource.newResource(bundleOverrideLocation))
+            {
+                res=location.addPath(fileName);
+            }
+            catch (Exception e)
+            {
+                LOG.warn(e);
+            }
+        }        
+        if (res != null)
+            return res;
+        
+        //try to find it relative to the bundle in which it is being deployed
+        if (containingBundle != null)
+        {
+            if (fileName.startsWith("./"))
+                fileName = fileName.substring(1);
+
+            if (!fileName.startsWith("/"))
+                fileName = "/" + fileName;
+
+            URL entry = _bundle.getEntry(fileName);
+            if (entry != null)
+                res = Resource.newResource(entry);
+        }
+        
+        return res;
+    }
 }
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/AbstractWebAppProvider.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/AbstractWebAppProvider.java
index 5874325..ba69878 100644
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/AbstractWebAppProvider.java
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/AbstractWebAppProvider.java
@@ -25,28 +25,27 @@ import java.util.Arrays;
 import java.util.Dictionary;
 import java.util.Enumeration;
 import java.util.HashMap;
-import java.util.Hashtable;
-import java.util.Map;
-
+import java.util.List;
 
 import org.eclipse.jetty.deploy.App;
 import org.eclipse.jetty.deploy.AppProvider;
 import org.eclipse.jetty.deploy.DeploymentManager;
 import org.eclipse.jetty.osgi.boot.internal.serverfactory.ServerInstanceWrapper;
-import org.eclipse.jetty.osgi.boot.internal.webapp.BundleFileLocatorHelperFactory;
 import org.eclipse.jetty.osgi.boot.internal.webapp.OSGiWebappClassLoader;
-import org.eclipse.jetty.osgi.boot.utils.EventSender;
+import org.eclipse.jetty.osgi.boot.utils.BundleFileLocatorHelperFactory;
 import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.util.ArrayUtil;
 import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.Loader;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.JarResource;
+import org.eclipse.jetty.util.resource.Resource;
 import org.eclipse.jetty.webapp.WebAppContext;
 import org.eclipse.jetty.xml.XmlConfiguration;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
-import org.osgi.framework.FrameworkUtil;
 import org.osgi.framework.ServiceReference;
-import org.osgi.framework.ServiceRegistration;
 import org.osgi.service.packageadmin.PackageAdmin;
 
 
@@ -55,7 +54,9 @@ import org.osgi.service.packageadmin.PackageAdmin;
 /**
  * AbstractWebAppProvider
  *
- *
+ * Base class for Jetty DeploymentManager Providers that are capable of deploying a webapp,
+ * either from a bundle or an OSGi service.
+ * 
  */
 public abstract class AbstractWebAppProvider extends AbstractLifeCycle implements AppProvider
 {
@@ -64,10 +65,9 @@ public abstract class AbstractWebAppProvider extends AbstractLifeCycle implement
     public static String __defaultConfigurations[] = {
                                                             "org.eclipse.jetty.osgi.boot.OSGiWebInfConfiguration",
                                                             "org.eclipse.jetty.webapp.WebXmlConfiguration",
-                                                            "org.eclipse.jetty.osgi.boot.OSGiMetaInfConfiguration",
+                                                            "org.eclipse.jetty.webapp.MetaInfConfiguration",
                                                             "org.eclipse.jetty.webapp.FragmentConfiguration",
-                                                            "org.eclipse.jetty.webapp.JettyWebXmlConfiguration"//,
-                                                            //"org.eclipse.jetty.osgi.boot.jsp.TagLibOSGiConfiguration"                            
+                                                            "org.eclipse.jetty.webapp.JettyWebXmlConfiguration"                          
                                                      };
     
     public static void setDefaultConfigurations (String[] defaultConfigs)
@@ -77,7 +77,58 @@ public abstract class AbstractWebAppProvider extends AbstractLifeCycle implement
     
     public static String[] getDefaultConfigurations ()
     {
-        return __defaultConfigurations;
+        List<String> configs = ArrayUtil.asMutableList(__defaultConfigurations);
+        if (annotationsAvailable())
+        {
+            //add before JettyWebXmlConfiguration
+            int i = configs.indexOf("org.eclipse.jetty.webapp.JettyWebXmlConfiguration");
+            configs.add(i, "org.eclipse.jetty.osgi.annotations.AnnotationConfiguration");
+        }
+        
+        if (jndiAvailable())
+        {
+            //add in EnvConfiguration and PlusConfiguration just after FragmentConfiguration
+            int i = configs.indexOf("org.eclipse.jetty.webapp.FragmentConfiguration");
+            configs.add(++i, "org.eclipse.jetty.plus.webapp.EnvConfiguration");
+            configs.add(++i, "org.eclipse.jetty.plus.webapp.PlusConfiguration");
+        }
+
+        return configs.toArray(new String[configs.size()]);
+    }
+
+    private static boolean annotationsAvailable()
+    {
+        boolean result = false;
+        try
+        {
+            Loader.loadClass(AbstractWebAppProvider.class,"org.eclipse.jetty.annotations.AnnotationConfiguration");
+            result = true;
+            LOG.debug("Annotation support detected");
+        }
+        catch (ClassNotFoundException e)
+        {
+            result = false;
+            LOG.debug("No annotation support detected");
+        }
+
+        return result;
+    }
+    
+    
+    private static boolean jndiAvailable()
+    {
+        try
+        {
+            Loader.loadClass(AbstractWebAppProvider.class, "org.eclipse.jetty.plus.jndi.Resource");
+            Loader.loadClass(AbstractWebAppProvider.class, "org.eclipse.jetty.plus.webapp.EnvConfiguration");
+            LOG.debug("JNDI support detected");
+            return true;
+        }
+        catch (ClassNotFoundException e)
+        {
+            LOG.debug("No JNDI support detected");
+            return false;
+        }
     }
     
 
@@ -201,12 +252,28 @@ public abstract class AbstractWebAppProvider extends AbstractLifeCycle implement
                 (overrideBundleInstallLocation == null 
                         ? BundleFileLocatorHelperFactory.getFactory().getHelper().getBundleInstallLocation(_bundle) 
                         : new File(overrideBundleInstallLocation));
+            
+            if (LOG.isDebugEnabled())
+            {
+                LOG.debug("Bundle location is {}, install location: {}", _bundle.getLocation(), bundleInstallLocation);
+            }
+            
             URL url = null;
-
+            Resource rootResource = Resource.newResource(BundleFileLocatorHelperFactory.getFactory().getHelper().getLocalURL(bundleInstallLocation.toURI().toURL()));
+            //try and make sure the rootResource is useable - if its a jar then make it a jar file url
+            if (rootResource.exists()&& !rootResource.isDirectory() && !rootResource.toString().startsWith("jar:"))
+            {
+               Resource jarResource = JarResource.newJarResource(rootResource);
+               if (jarResource.exists() && jarResource.isDirectory())
+                   rootResource = jarResource;
+            }
+            
             //if the path wasn't set or it was ., then it is the root of the bundle's installed location
             if (_webAppPath == null || _webAppPath.length() == 0 || ".".equals(_webAppPath))
             {
                 url = bundleInstallLocation.toURI().toURL();
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Webapp base using bundle install location: {}", url);
             }
             else
             {
@@ -214,16 +281,24 @@ public abstract class AbstractWebAppProvider extends AbstractLifeCycle implement
                 if (_webAppPath.startsWith("/") || _webAppPath.startsWith("file:"))
                 {
                     url = new File(_webAppPath).toURI().toURL();
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("Webapp base using absolute location: {}", url);
                 }
                 else if (bundleInstallLocation != null && bundleInstallLocation.isDirectory())
                 {
                     url = new File(bundleInstallLocation, _webAppPath).toURI().toURL();
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("Webapp base using path relative to bundle unpacked install location: {}", url);
                 }
                 else if (bundleInstallLocation != null)
                 {
                     Enumeration<URL> urls = BundleFileLocatorHelperFactory.getFactory().getHelper().findEntries(_bundle, _webAppPath);
                     if (urls != null && urls.hasMoreElements())
+                    {
                         url = urls.nextElement();
+                        if (LOG.isDebugEnabled())
+                            LOG.debug("Webapp base using path relative to packed bundle location: {}", url);
+                    }
                 }
             }
 
@@ -234,6 +309,7 @@ public abstract class AbstractWebAppProvider extends AbstractLifeCycle implement
                                                    + (bundleInstallLocation != null ? bundleInstallLocation.getAbsolutePath() : "unlocated bundle '" + _bundle.getSymbolicName()+ "'"));
             }
 
+            //Sets the location of the war file
             // converts bundleentry: protocol if necessary
             _webApp.setWar(BundleFileLocatorHelperFactory.getFactory().getHelper().getLocalURL(url).toString());
 
@@ -243,7 +319,7 @@ public abstract class AbstractWebAppProvider extends AbstractLifeCycle implement
             if (getConfigurationClasses() != null)
                 _webApp.setConfigurationClasses(getConfigurationClasses());
             else
-                _webApp.setConfigurationClasses(__defaultConfigurations);
+                _webApp.setConfigurationClasses(getDefaultConfigurations());
 
             if (getDefaultsDescriptor() != null)
                 _webApp.setDefaultsDescriptor(getDefaultsDescriptor());
@@ -268,8 +344,13 @@ public abstract class AbstractWebAppProvider extends AbstractLifeCycle implement
             if (tmp != null)
             {
                 File defaultWebXml = getFile (tmp, bundleInstallLocation);
-                if (defaultWebXml != null && defaultWebXml.exists())
-                    _webApp.setDefaultsDescriptor(defaultWebXml.getAbsolutePath());
+                if (defaultWebXml != null)
+                {
+                    if (defaultWebXml.exists())
+                        _webApp.setDefaultsDescriptor(defaultWebXml.getAbsolutePath());
+                    else
+                        LOG.warn(defaultWebXml.getAbsolutePath()+" does not exist");
+                }
             }
 
             //Handle Require-TldBundle
@@ -290,10 +371,8 @@ public abstract class AbstractWebAppProvider extends AbstractLifeCycle implement
 
             // apply any META-INF/context.xml file that is found to configure
             // the webapp first
-            applyMetaInfContextXml();
+            applyMetaInfContextXml(rootResource, overrideBundleInstallLocation);
 
-            // pass the value of the require tld bundle so that the TagLibOSGiConfiguration
-            // can pick it up.
             _webApp.setAttribute(OSGiWebappConstants.REQUIRE_TLD_BUNDLE, requireTldBundles);
 
             //Set up some attributes
@@ -324,7 +403,7 @@ public abstract class AbstractWebAppProvider extends AbstractLifeCycle implement
                 throw new IllegalStateException("Unable to get PackageAdmin reference to locate required Tld bundles");
 
             StringBuilder paths = new StringBuilder();         
-            String[] symbNames = requireTldBundles.split(", ");
+            String[] symbNames = requireTldBundles.split("[, ]");
 
             for (String symbName : symbNames)
             {
@@ -348,7 +427,7 @@ public abstract class AbstractWebAppProvider extends AbstractLifeCycle implement
         }
         
         
-        protected void applyMetaInfContextXml()
+        protected void applyMetaInfContextXml(Resource rootResource, String overrideBundleInstallLocation)
         throws Exception
         {
             if (_bundle == null) return;
@@ -362,8 +441,31 @@ public abstract class AbstractWebAppProvider extends AbstractLifeCycle implement
                 Thread.currentThread().setContextClassLoader(_webApp.getClassLoader());
 
                 //TODO replace this with getting the InputStream so we don't cache in URL
-                // find if there is a META-INF/context.xml file
+                //Try looking for a context xml file in META-INF with a specific name
                 URL contextXmlUrl = _bundle.getEntry("/META-INF/jetty-webapp-context.xml");
+                
+                if (contextXmlUrl == null)
+                {
+                    //Didn't find specially named file, try looking for a property that names a context xml file to use
+                    if (_properties != null)
+                    {
+                        String tmp = (String)_properties.get(OSGiWebappConstants.JETTY_CONTEXT_FILE_PATH);
+                        if (tmp != null)
+                        {
+                            String[] filenames = tmp.split("[,;]");
+                            if (filenames != null && filenames.length > 0)
+                            {
+                                String filename = filenames[0]; //should only be 1 filename in this usage
+                                String jettyHome = (String)getServerInstanceWrapper().getServer().getAttribute(OSGiServerConstants.JETTY_HOME);
+                                if (jettyHome == null)
+                                    jettyHome =  System.getProperty(OSGiServerConstants.JETTY_HOME);
+                                Resource res = findFile(filename, jettyHome, overrideBundleInstallLocation, _bundle);
+                                if (res != null)
+                                    contextXmlUrl = res.getURL();
+                            }
+                        }
+                    }
+                }
                 if (contextXmlUrl == null) return;
 
                 // Apply it just as the standard jetty ContextProvider would do
@@ -372,6 +474,8 @@ public abstract class AbstractWebAppProvider extends AbstractLifeCycle implement
                 XmlConfiguration xmlConfiguration = new XmlConfiguration(contextXmlUrl);
                 HashMap properties = new HashMap();
                 properties.put("Server", getDeploymentManager().getServer());
+                properties.put(OSGiWebappConstants.JETTY_BUNDLE_ROOT, rootResource.toString());
+                properties.put(OSGiServerConstants.JETTY_HOME, getDeploymentManager().getServer().getAttribute(OSGiServerConstants.JETTY_HOME));
                 xmlConfiguration.getProperties().putAll(properties);
                 xmlConfiguration.configure(_webApp);
             }
@@ -385,11 +489,21 @@ public abstract class AbstractWebAppProvider extends AbstractLifeCycle implement
         {
             if (file == null)
                 return null;
-            
-            if (file.startsWith("/") || file.startsWith("file:/"))
+
+            if (file.startsWith("/") || file.startsWith("file:/")) //absolute location
                 return new File(file);
             else
-                return new File(bundleInstall, file);
+            {
+                //relative location
+                //try inside the bundle first
+                File f = new File (bundleInstall, file);
+                if (f.exists()) return f;
+                String jettyHome = (String)getDeploymentManager().getServer().getAttribute(OSGiServerConstants.JETTY_HOME);
+                if (jettyHome != null)
+                    return new File(jettyHome, file);
+            }
+            
+            return null;
         }
     }
    
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/BundleContextProvider.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/BundleContextProvider.java
index 54639dd..1a648d2 100644
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/BundleContextProvider.java
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/BundleContextProvider.java
@@ -26,16 +26,11 @@ import java.util.List;
 import java.util.Map;
 
 import org.eclipse.jetty.deploy.App;
-import org.eclipse.jetty.deploy.DeploymentManager;
 import org.eclipse.jetty.osgi.boot.internal.serverfactory.ServerInstanceWrapper;
-import org.eclipse.jetty.osgi.boot.utils.EventSender;
-import org.eclipse.jetty.server.handler.ContextHandler;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.webapp.WebAppContext;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.FrameworkUtil;
-import org.osgi.framework.ServiceReference;
 import org.osgi.framework.ServiceRegistration;
 
 
@@ -43,7 +38,7 @@ import org.osgi.framework.ServiceRegistration;
 /**
  * BundleContextProvider
  *
- * Handles deploying bundles that define a context xml file for configuring them.
+ * Handles deploying OSGi bundles that define a context xml file for configuring them.
  * 
  *
  */
@@ -111,6 +106,13 @@ public class BundleContextProvider extends AbstractContextProvider implements Bu
         if (bundle == null)
             return false;
 
+        //If the bundle defines a Web-ContextPath then its probably a webapp and the BundleWebAppProvider should deploy it
+        if ((String)bundle.getHeaders().get(OSGiWebappConstants.RFC66_WEB_CONTEXTPATH) != null)
+        {
+            if (LOG.isDebugEnabled()) LOG.debug("BundleContextProvider ignoring bundle {} with {} set", bundle.getSymbolicName(), OSGiWebappConstants.RFC66_WEB_CONTEXTPATH);
+            return false;
+        }
+        
         String contextFiles  = (String)bundle.getHeaders().get(OSGiWebappConstants.JETTY_CONTEXT_FILE_PATH);
         if (contextFiles == null)
             contextFiles = (String)bundle.getHeaders().get(OSGiWebappConstants.SERVICE_PROP_CONTEXT_FILE_PATH);
@@ -118,11 +120,12 @@ public class BundleContextProvider extends AbstractContextProvider implements Bu
         if (contextFiles == null)
             return false;
         
+        
         boolean added = false;
         //bundle defines JETTY_CONTEXT_FILE_PATH header,
         //a comma separated list of context xml files that each define a ContextHandler
         //TODO: (could be WebAppContexts)       
-        String[] tmp = contextFiles.split(",;");
+        String[] tmp = contextFiles.split("[,;]");
         for (String contextFile : tmp)
         {
             String originId = bundle.getSymbolicName() + "-" + bundle.getVersion().toString() + "-"+contextFile;
@@ -136,6 +139,7 @@ public class BundleContextProvider extends AbstractContextProvider implements Bu
             }
             apps.add(app);
             getDeploymentManager().addApp(app);
+            added = true;
         }
 
         return added; //true if even 1 context from this bundle was added
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/BundleProvider.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/BundleProvider.java
index e1c7915..1a8d3c9 100644
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/BundleProvider.java
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/BundleProvider.java
@@ -20,6 +20,11 @@ package org.eclipse.jetty.osgi.boot;
 
 import org.osgi.framework.Bundle;
 
+/**
+ * BundleProvider
+ *
+ * Jetty DeploymentManager Provider api for webapps or ContextHandlers that are discovered as osgi bundles.
+ */
 public interface BundleProvider
 {
     public boolean bundleAdded (Bundle bundle) throws Exception;
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/BundleWebAppProvider.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/BundleWebAppProvider.java
index f39f3ed..9117927 100644
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/BundleWebAppProvider.java
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/BundleWebAppProvider.java
@@ -25,7 +25,6 @@ import java.util.Map;
 
 import org.eclipse.jetty.deploy.App;
 import org.eclipse.jetty.osgi.boot.internal.serverfactory.ServerInstanceWrapper;
-import org.eclipse.jetty.osgi.boot.utils.EventSender;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
 import org.osgi.framework.Bundle;
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/JettyBootstrapActivator.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/JettyBootstrapActivator.java
index 45e2b19..630f20e 100644
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/JettyBootstrapActivator.java
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/JettyBootstrapActivator.java
@@ -18,45 +18,36 @@
 
 package org.eclipse.jetty.osgi.boot;
 
-import java.util.Dictionary;
-import java.util.Hashtable;
-
 import org.eclipse.jetty.osgi.boot.internal.serverfactory.DefaultJettyAtJettyHomeHelper;
 import org.eclipse.jetty.osgi.boot.internal.serverfactory.JettyServerServiceTracker;
-import org.eclipse.jetty.osgi.boot.internal.webapp.IWebBundleDeployerHelper;
-import org.eclipse.jetty.osgi.boot.internal.webapp.JettyContextHandlerServiceTracker;
-import org.eclipse.jetty.osgi.boot.internal.webapp.WebBundleTrackerCustomizer;
+import org.eclipse.jetty.osgi.boot.internal.webapp.BundleWatcher;
+import org.eclipse.jetty.osgi.boot.internal.webapp.ServiceWatcher;
 import org.eclipse.jetty.osgi.boot.utils.internal.PackageAdminServiceTracker;
 import org.eclipse.jetty.server.Server;
 import org.eclipse.jetty.server.handler.ContextHandler;
-import org.eclipse.jetty.webapp.WebAppContext;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleActivator;
 import org.osgi.framework.BundleContext;
-import org.osgi.framework.BundleException;
-import org.osgi.framework.FrameworkUtil;
 import org.osgi.framework.ServiceRegistration;
 import org.osgi.util.tracker.BundleTracker;
+import org.osgi.util.tracker.ServiceTracker;
 
 /**
+ * JettyBootstrapActivator
+ * 
  * Bootstrap jetty and publish a default Server instance as an OSGi service.
  * 
  * Listen for other Server instances to be published as services and support them as deployment targets.
  * 
- * Listen for Bundles to be activated, and deploy those that represent webapps to one of the known Server instances.
+ * Listen for Bundles to be activated, and deploy those that represent webapps/ContextHandlers to one of the known Server instances.
  * 
- * <ol>
- * <li>basic servlet [ok]</li>
- * <li>basic jetty.xml [ok]</li>
- * <li>basic jetty.xml and jetty-plus.xml [ok]</li>
- * <li>basic jsp [ok]</li>
- * <li>jsp with tag-libs [ok]</li>
- * <li>test-jndi with atomikos and derby inside ${jetty.home}/lib/ext [ok]</li>
- * </ul>
  */
 public class JettyBootstrapActivator implements BundleActivator
 {
-
+    private static final Logger LOG = Log.getLogger(JettyBootstrapActivator.class);
+    
     private static JettyBootstrapActivator INSTANCE = null;
 
     public static JettyBootstrapActivator getInstance()
@@ -66,16 +57,17 @@ public class JettyBootstrapActivator implements BundleActivator
 
     private ServiceRegistration _registeredServer;
 
-    private JettyContextHandlerServiceTracker _jettyContextHandlerTracker;
-
+    private ServiceTracker _contextHandlerTracker;
+    
     private PackageAdminServiceTracker _packageAdminServiceTracker;
 
     private BundleTracker _webBundleTracker;
 
-    private BundleContext _bundleContext;
-
     private JettyServerServiceTracker _jettyServerServiceTracker;
-
+    
+    
+    
+    /* ------------------------------------------------------------ */
     /**
      * Setup a new jetty Server, registers it as a service. Setup the Service
      * tracker for the jetty ContextHandlers that are in charge of deploying the
@@ -84,10 +76,10 @@ public class JettyBootstrapActivator implements BundleActivator
      * 
      * @param context
      */
-    public void start(BundleContext context) throws Exception
+    public void start(final BundleContext context) throws Exception
     {
+        try {
         INSTANCE = this;
-        _bundleContext = context;
 
         // track other bundles and fragments attached to this bundle that we
         // should activate.
@@ -97,19 +89,25 @@ public class JettyBootstrapActivator implements BundleActivator
         _jettyServerServiceTracker = new JettyServerServiceTracker();
         context.addServiceListener(_jettyServerServiceTracker, "(objectclass=" + Server.class.getName() + ")");
 
-        // track ContextHandler class instances and deploy them to one of the known Servers
-        _jettyContextHandlerTracker = new JettyContextHandlerServiceTracker();
-        context.addServiceListener(_jettyContextHandlerTracker, "(objectclass=" + ContextHandler.class.getName() + ")");
-
         // Create a default jetty instance right now.
-        DefaultJettyAtJettyHomeHelper.startJettyAtJettyHome(context);
-
-        // track Bundles and deploy those that represent webapps to one of the known Servers
-        WebBundleTrackerCustomizer customizer = new WebBundleTrackerCustomizer();
-        _webBundleTracker = new BundleTracker(context, Bundle.ACTIVE | Bundle.STOPPING, customizer);
-        customizer.setAndOpenWebBundleTracker(_webBundleTracker);
+        Server defaultServer = DefaultJettyAtJettyHomeHelper.startJettyAtJettyHome(context);
+        
+        // track ContextHandler class instances and deploy them to one of the known Servers
+        _contextHandlerTracker = new ServiceTracker(context, context.createFilter("(objectclass=" + ContextHandler.class.getName() + ")"), new ServiceWatcher());
+        _contextHandlerTracker.open();
+
+        //Create a bundle tracker to help deploy webapps and ContextHandlers
+        BundleWatcher bundleTrackerCustomizer = new BundleWatcher();
+        bundleTrackerCustomizer.setWaitForDefaultServer(defaultServer != null);
+        _webBundleTracker =  new BundleTracker(context, Bundle.ACTIVE | Bundle.STOPPING, bundleTrackerCustomizer);
+        bundleTrackerCustomizer.setBundleTracker(_webBundleTracker);
+        bundleTrackerCustomizer.open();
+        } catch (Exception e) { e.printStackTrace();}
     }
-
+    
+    
+    
+    /* ------------------------------------------------------------ */
     /**
      * Stop the activator.
      * 
@@ -120,16 +118,15 @@ public class JettyBootstrapActivator implements BundleActivator
     {
         try
         {
-
             if (_webBundleTracker != null)
             {
                 _webBundleTracker.close();
                 _webBundleTracker = null;
             }
-            if (_jettyContextHandlerTracker != null)
+            if (_contextHandlerTracker != null)
             {
-                context.removeServiceListener(_jettyContextHandlerTracker);
-                _jettyContextHandlerTracker = null;
+                _contextHandlerTracker.close();
+                _contextHandlerTracker = null;
             }
             if (_jettyServerServiceTracker != null)
             {
@@ -164,122 +161,4 @@ public class JettyBootstrapActivator implements BundleActivator
             INSTANCE = null;
         }
     }
-
-    /**
-     * Helper method that creates a new org.jetty.webapp.WebAppContext and
-     * registers it as an OSGi service. The tracker
-     * {@link JettyContextHandlerServiceTracker} will do the actual deployment.
-     * 
-     * @param contributor The bundle
-     * @param webappFolderPath The path to the root of the webapp. Must be a
-     *            path relative to bundle; either an absolute path.
-     * @param contextPath The context path. Must start with "/"
-     * @throws Exception
-     */
-    public static void registerWebapplication(Bundle contributor, String webappFolderPath, String contextPath) throws Exception
-    {
-        checkBundleActivated();
-        WebAppContext contextHandler = new WebAppContext();
-        Dictionary<String,String> dic = new Hashtable<String,String>();
-        dic.put(OSGiWebappConstants.SERVICE_PROP_WAR, webappFolderPath);
-        dic.put(OSGiWebappConstants.SERVICE_PROP_CONTEXT_PATH, contextPath);
-        String requireTldBundle = (String) contributor.getHeaders().get(OSGiWebappConstants.REQUIRE_TLD_BUNDLE);
-        if (requireTldBundle != null)
-        {
-            dic.put(OSGiWebappConstants.SERVICE_PROP_REQUIRE_TLD_BUNDLE, requireTldBundle);
-        }
-        contributor.getBundleContext().registerService(ContextHandler.class.getName(), contextHandler, dic);
-    }
-
-    /**
-     * Helper method that creates a new org.jetty.webapp.WebAppContext and
-     * registers it as an OSGi service. The tracker
-     * {@link JettyContextHandlerServiceTracker} will do the actual deployment.
-     * 
-     * @param contributor The bundle
-     * @param webappFolderPath The path to the root of the webapp. Must be a
-     *            path relative to bundle; either an absolute path.
-     * @param contextPath The context path. Must start with "/"
-     * @param dic TODO: parameter description
-     * @throws Exception
-     */
-    public static void registerWebapplication(Bundle contributor, String webappFolderPath, String contextPath, Dictionary<String, String> dic) throws Exception
-    {
-        checkBundleActivated();
-        WebAppContext contextHandler = new WebAppContext();
-        dic.put(OSGiWebappConstants.SERVICE_PROP_WAR, webappFolderPath);
-        dic.put(OSGiWebappConstants.SERVICE_PROP_CONTEXT_PATH, contextPath);
-        contributor.getBundleContext().registerService(ContextHandler.class.getName(), contextHandler, dic);
-    }
-
-    /**
-     * Helper method that creates a new skeleton of a ContextHandler and
-     * registers it as an OSGi service. The tracker
-     * {@link JettyContextHandlerServiceTracker} will do the actual deployment.
-     * 
-     * @param contributor The bundle that registers a new context
-     * @param contextFilePath The path to the file inside the bundle that
-     *            defines the context.
-     * @throws Exception
-     */
-    public static void registerContext(Bundle contributor, String contextFilePath) throws Exception
-    {
-        registerContext(contributor, contextFilePath, new Hashtable<String, String>());
-    }
-
-    /**
-     * Helper method that creates a new skeleton of a ContextHandler and
-     * registers it as an OSGi service. The tracker
-     * {@link JettyContextHandlerServiceTracker} will do the actual deployment.
-     * 
-     * @param contributor The bundle that registers a new context
-     * @param contextFilePath The path to the file inside the bundle that
-     *            defines the context.
-     * @param dic TODO: parameter description
-     * @throws Exception
-     */
-    public static void registerContext(Bundle contributor, String contextFilePath, Dictionary<String, String> dic) throws Exception
-    {
-        checkBundleActivated();
-        ContextHandler contextHandler = new ContextHandler();
-        dic.put(OSGiWebappConstants.SERVICE_PROP_CONTEXT_FILE_PATH, contextFilePath);
-        dic.put(IWebBundleDeployerHelper.INTERNAL_SERVICE_PROP_UNKNOWN_CONTEXT_HANDLER_TYPE, Boolean.TRUE.toString());
-        contributor.getBundleContext().registerService(ContextHandler.class.getName(), contextHandler, dic);
-    }
-
-    public static void unregister(String contextPath)
-    {
-        // todo
-    }
-
-    /**
-     * Since org.eclipse.jetty.osgi.boot does not have a lazy activation policy
-     * when one of the static methods to register a webapp is called we should
-     * make sure that the bundle is started.
-     */
-    private static void checkBundleActivated()
-    {
-        if (INSTANCE == null)
-        {
-            Bundle thisBundle = FrameworkUtil.getBundle(JettyBootstrapActivator.class);
-            try
-            {
-                thisBundle.start();
-            }
-            catch (BundleException e)
-            {
-                // nevermind.
-            }
-        }
-    }
-
-    /**
-     * @return The bundle context for this bundle.
-     */
-    public static BundleContext getBundleContext()
-    {
-        checkBundleActivated();
-        return INSTANCE._bundleContext;
-    }
-
 }
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiDeployer.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiDeployer.java
index f16e695..bf345b1 100644
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiDeployer.java
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiDeployer.java
@@ -21,17 +21,29 @@ package org.eclipse.jetty.osgi.boot;
 import org.eclipse.jetty.deploy.App;
 import org.eclipse.jetty.deploy.bindings.StandardDeployer;
 import org.eclipse.jetty.deploy.graph.Node;
+import org.eclipse.jetty.osgi.boot.internal.serverfactory.ServerInstanceWrapper;
 import org.eclipse.jetty.osgi.boot.utils.EventSender;
 
 
 /**
  * OSGiDeployer
  *
- *
+ * Extension of standard Jetty deployer that emits OSGi EventAdmin 
+ * events whenever a webapp is deployed into OSGi via Jetty.
+ * 
  */
 public class OSGiDeployer extends StandardDeployer
 {
     
+    private ServerInstanceWrapper _server;
+
+    /* ------------------------------------------------------------ */
+    public OSGiDeployer (ServerInstanceWrapper server)
+    {
+        _server = server; 
+    }
+    
+    
     /* ------------------------------------------------------------ */
     public void processBinding(Node node, App app) throws Exception
     {
@@ -39,14 +51,14 @@ public class OSGiDeployer extends StandardDeployer
         //OSGi Enterprise Spec only wants an event sent if its a webapp bundle (ie not a ContextHandler)
         if (!(app instanceof AbstractOSGiApp))
         {
-            super.processBinding(node,app);
+           doProcessBinding(node,app);
         }
         else
         {
             EventSender.getInstance().send(EventSender.DEPLOYING_EVENT, ((AbstractOSGiApp)app).getBundle(), app.getContextPath());
             try
             {
-                super.processBinding(node,app);
+                doProcessBinding(node,app);
                 ((AbstractOSGiApp)app).registerAsOSGiService();
                 EventSender.getInstance().send(EventSender.DEPLOYED_EVENT, ((AbstractOSGiApp)app).getBundle(), app.getContextPath());
             }
@@ -56,6 +68,21 @@ public class OSGiDeployer extends StandardDeployer
                 throw e;
             }
         }
-        
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    protected void doProcessBinding (Node node, App app) throws Exception
+    {
+        ClassLoader old = Thread.currentThread().getContextClassLoader();
+        Thread.currentThread().setContextClassLoader(_server.getParentClassLoaderForWebapps());
+        try
+        {
+            super.processBinding(node,app);
+        }
+        finally 
+        {
+            Thread.currentThread().setContextClassLoader(old);
+        }
     }
 }
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiMetaInfConfiguration.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiMetaInfConfiguration.java
deleted file mode 100644
index 05aaa6a..0000000
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiMetaInfConfiguration.java
+++ /dev/null
@@ -1,116 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.osgi.boot;
-
-import java.net.URL;
-import java.util.ArrayList;
-import java.util.Enumeration;
-import java.util.List;
-
-import org.eclipse.jetty.osgi.boot.internal.webapp.BundleFileLocatorHelperFactory;
-import org.eclipse.jetty.osgi.boot.utils.internal.PackageAdminServiceTracker;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.util.resource.Resource;
-import org.eclipse.jetty.webapp.MetaInfConfiguration;
-import org.eclipse.jetty.webapp.WebAppContext;
-import org.osgi.framework.Bundle;
-
-public class OSGiMetaInfConfiguration extends MetaInfConfiguration
-{
-    private static final Logger LOG = Log.getLogger(OSGiMetaInfConfiguration.class);
-
-
-    /** 
-     * Inspect bundle fragments associated with the bundle of the webapp for web-fragment, resources, tlds.
-     *
-     * @see org.eclipse.jetty.webapp.MetaInfConfiguration#preConfigure(org.eclipse.jetty.webapp.WebAppContext)
-     */
-    @Override
-    public void preConfigure(final WebAppContext context) throws Exception
-    {
-        List<Resource> frags = (List<Resource>) context.getAttribute(METAINF_FRAGMENTS);
-        List<Resource> resfrags = (List<Resource>) context.getAttribute(METAINF_RESOURCES);
-        List<Resource> tldfrags = (List<Resource>) context.getAttribute(METAINF_TLDS);
-
-        Bundle[] fragments = PackageAdminServiceTracker.INSTANCE.getFragmentsAndRequiredBundles((Bundle)context.getAttribute(OSGiWebappConstants.JETTY_OSGI_BUNDLE));
-        //TODO not convinced we need to do this, as we added any fragment jars to the MetaData.webInfJars in OSGiWebInfConfiguration, 
-        //so surely the web-fragments and resources tlds etc can be discovered normally?
-        for (Bundle frag : fragments)
-        {
-            URL webFrag = frag.getEntry("/META-INF/web-fragment.xml");
-            Enumeration<URL> resEnum = frag.findEntries("/META-INF/resources", "*", true);
-            Enumeration<URL> tldEnum = frag.findEntries("/META-INF", "*.tld", false);
-            if (webFrag != null || (resEnum != null && resEnum.hasMoreElements()) || (tldEnum != null && tldEnum.hasMoreElements()))
-            {
-                try
-                {
-                    if (webFrag != null)
-                    {
-                        if (frags == null)
-                        {
-                            frags = new ArrayList<Resource>();
-                            context.setAttribute(METAINF_FRAGMENTS, frags);
-                        }
-                        frags.add(Resource.newResource(BundleFileLocatorHelperFactory.getFactory().getHelper().getBundleInstallLocation(frag).toURI()));
-                    }
-                    if (resEnum != null && resEnum.hasMoreElements())
-                    {
-                        URL resourcesEntry = frag.getEntry("/META-INF/resources/");
-                        if (resourcesEntry == null)
-                        {
-                            // probably we found some fragments to a
-                            // bundle.
-                            // those are already contributed.
-                            // so we skip this.
-                        }
-                        else
-                        {
-                            if (resfrags == null)
-                            {
-                                resfrags = new ArrayList<Resource>();
-                                context.setAttribute(METAINF_RESOURCES, resfrags);
-                            }
-                            resfrags.add(Resource.newResource(BundleFileLocatorHelperFactory.getFactory().getHelper().getLocalURL(resourcesEntry)));
-                        }
-                    }
-                    if (tldEnum != null && tldEnum.hasMoreElements())
-                    {
-                        if (tldfrags == null)
-                        {
-                            tldfrags = new ArrayList<Resource>();
-                            context.setAttribute(METAINF_TLDS, tldfrags);
-                        }
-                        while (tldEnum.hasMoreElements())
-                        {
-                            URL tldUrl = tldEnum.nextElement();
-                            tldfrags.add(Resource.newResource(BundleFileLocatorHelperFactory.getFactory().getHelper().getLocalURL(tldUrl)));
-                        }
-                    }
-                } 
-                catch (Exception e)
-                {
-                    LOG.warn("Unable to locate the bundle " + frag.getBundleId(), e);
-                }
-            }
-        }
-        
-        super.preConfigure(context);
-    }
-}
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiServerConstants.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiServerConstants.java
index 94d42d4..b52672a 100644
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiServerConstants.java
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiServerConstants.java
@@ -19,6 +19,8 @@
 package org.eclipse.jetty.osgi.boot;
 
 /**
+ * OSGiServerConstants
+ * 
  * Name of the properties that configure a jetty Server OSGi service.
  */
 public class OSGiServerConstants
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiUndeployer.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiUndeployer.java
index ab9cb88..b5d4988 100644
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiUndeployer.java
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiUndeployer.java
@@ -21,6 +21,7 @@ package org.eclipse.jetty.osgi.boot;
 import org.eclipse.jetty.deploy.App;
 import org.eclipse.jetty.deploy.bindings.StandardUndeployer;
 import org.eclipse.jetty.deploy.graph.Node;
+import org.eclipse.jetty.osgi.boot.internal.serverfactory.ServerInstanceWrapper;
 import org.eclipse.jetty.osgi.boot.utils.EventSender;
 
 
@@ -29,15 +30,36 @@ import org.eclipse.jetty.osgi.boot.utils.EventSender;
 /**
  * OSGiUndeployer
  *
- *
+ * Extension of the Jetty Undeployer which emits OSGi EventAdmin events
+ * whenever a webapp is undeployed from Jetty.
+ * 
  */
 public class OSGiUndeployer extends StandardUndeployer
 {
+    private ServerInstanceWrapper _server;
+
+    
+    /* ------------------------------------------------------------ */
+    public OSGiUndeployer (ServerInstanceWrapper server)
+    {
+        _server = server;
+    }
+    
+    
     /* ------------------------------------------------------------ */
     public void processBinding(Node node, App app) throws Exception
     {
         EventSender.getInstance().send(EventSender.UNDEPLOYING_EVENT, ((AbstractOSGiApp)app).getBundle(), app.getContextPath());
-        super.processBinding(node,app);
+        ClassLoader old = Thread.currentThread().getContextClassLoader();
+        Thread.currentThread().setContextClassLoader(_server.getParentClassLoaderForWebapps());
+        try
+        {
+            super.processBinding(node,app);
+        }
+        finally 
+        {
+            Thread.currentThread().setContextClassLoader(old);
+        }
         EventSender.getInstance().send(EventSender.UNDEPLOYED_EVENT, ((AbstractOSGiApp)app).getBundle(), app.getContextPath());
         ((AbstractOSGiApp)app).deregisterAsOSGiService();
     }
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiWebInfConfiguration.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiWebInfConfiguration.java
index eb0f0c2..2048780 100644
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiWebInfConfiguration.java
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiWebInfConfiguration.java
@@ -23,11 +23,12 @@ import java.net.URL;
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 import java.util.StringTokenizer;
 import java.util.TreeMap;
 import java.util.regex.Pattern;
 
-import org.eclipse.jetty.osgi.boot.internal.webapp.BundleFileLocatorHelperFactory;
+import org.eclipse.jetty.osgi.boot.utils.BundleFileLocatorHelperFactory;
 import org.eclipse.jetty.osgi.boot.utils.internal.PackageAdminServiceTracker;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
@@ -49,10 +50,20 @@ public class OSGiWebInfConfiguration extends WebInfConfiguration
 {
     private static final Logger LOG = Log.getLogger(WebInfConfiguration.class);
     
-    
+    /**
+     * Comma separated list of symbolic names of bundles that contain tlds that should be considered
+     * as on the container classpath
+     */
+    public static final String SYS_PROP_TLD_BUNDLES = "org.eclipse.jetty.osgi.tldbundles";
+    /**
+     * Regex of symbolic names of bundles that should be considered to be on the container classpath
+     */
     public static final String CONTAINER_BUNDLE_PATTERN = "org.eclipse.jetty.server.webapp.containerIncludeBundlePattern";
+    public static final String FRAGMENT_AND_REQUIRED_BUNDLES = "org.eclipse.jetty.osgi.fragmentAndRequiredBundles";
+    public static final String FRAGMENT_AND_REQUIRED_RESOURCES = "org.eclipse.jetty.osgi.fragmentAndRequiredResources";
     
     
+    /* ------------------------------------------------------------ */
     /** 
      * Check to see if there have been any bundle symbolic names added of bundles that should be
      * regarded as being on the container classpath, and scanned for fragments, tlds etc etc.
@@ -63,7 +74,7 @@ public class OSGiWebInfConfiguration extends WebInfConfiguration
      *  </ol>
      *  
      *  We also allow individual bundles to specify particular bundles that might include TLDs via the Require-Tlds
-     *  MANIFEST.MF header. This is processed in the TagLibOSGiConfiguration class.
+     *  MANIFEST.MF header. 
      *  
      * @see org.eclipse.jetty.webapp.WebInfConfiguration#preConfigure(org.eclipse.jetty.webapp.WebAppContext)
      */
@@ -80,14 +91,13 @@ public class OSGiWebInfConfiguration extends WebInfConfiguration
         String tmp = (String)context.getAttribute(CONTAINER_BUNDLE_PATTERN);
         Pattern pattern = (tmp==null?null:Pattern.compile(tmp));
         List<String> names = new ArrayList<String>();
-        tmp = System.getProperty("org.eclipse.jetty.osgi.tldbundles");
+        tmp = System.getProperty(SYS_PROP_TLD_BUNDLES);
         if (tmp != null)
         {
             StringTokenizer tokenizer = new StringTokenizer(tmp, ", \n\r\t", false);
             while (tokenizer.hasMoreTokens())
                 names.add(tokenizer.nextToken());
         }
-
         HashSet<Resource> matchingResources = new HashSet<Resource>();
         if ( !names.isEmpty() || pattern != null)
         {
@@ -95,6 +105,7 @@ public class OSGiWebInfConfiguration extends WebInfConfiguration
            
             for (Bundle bundle : bundles)
             {
+                LOG.debug("Checking bundle {}:{}", bundle.getBundleId(), bundle.getSymbolicName());
                 if (pattern != null)
                 {
                     // if bundle symbolic name matches the pattern
@@ -111,16 +122,22 @@ public class OSGiWebInfConfiguration extends WebInfConfiguration
                         matchingResources.addAll(getBundleAsResource(bundle));
                 }
             }
-        }
-        
+        }        
         for (Resource r:matchingResources)
         {
-            context.getMetaData().addContainerJar(r);
+            context.getMetaData().addContainerResource(r);
         }
     }
     
+    @Override
+    public void postConfigure(WebAppContext context) throws Exception
+    {
+        context.setAttribute(FRAGMENT_AND_REQUIRED_BUNDLES, null); 
+        context.setAttribute(FRAGMENT_AND_REQUIRED_RESOURCES, null);
+        super.postConfigure(context);
+    }
     
-    
+    /* ------------------------------------------------------------ */
     /** 
      * Consider the fragment bundles associated with the bundle of the webapp being deployed.
      * 
@@ -137,18 +154,44 @@ public class OSGiWebInfConfiguration extends WebInfConfiguration
         if (webInfJars != null)
             mergedResources.addAll(webInfJars);
         
-        //add fragment jars as if in WEB-INF/lib of the associated webapp
-        Bundle[] fragments = PackageAdminServiceTracker.INSTANCE.getFragmentsAndRequiredBundles((Bundle)context.getAttribute(OSGiWebappConstants.JETTY_OSGI_BUNDLE));
-        for (Bundle frag : fragments)
+        //add fragment jars and any Required-Bundles as if in WEB-INF/lib of the associated webapp
+        Bundle[] bundles = PackageAdminServiceTracker.INSTANCE.getFragmentsAndRequiredBundles((Bundle)context.getAttribute(OSGiWebappConstants.JETTY_OSGI_BUNDLE));
+        if (bundles != null && bundles.length > 0)
         {
-            File fragFile = BundleFileLocatorHelperFactory.getFactory().getHelper().getBundleInstallLocation(frag);
-            mergedResources.add(Resource.newResource(fragFile.toURI()));  
+            Set<Bundle> fragsAndReqsBundles = (Set<Bundle>)context.getAttribute(FRAGMENT_AND_REQUIRED_BUNDLES);
+            if (fragsAndReqsBundles == null)
+            {
+                fragsAndReqsBundles = new HashSet<Bundle>();
+                context.setAttribute(FRAGMENT_AND_REQUIRED_BUNDLES, fragsAndReqsBundles);
+            }
+            
+            Set<Resource> fragsAndReqsResources = (Set<Resource>)context.getAttribute(FRAGMENT_AND_REQUIRED_RESOURCES);
+            if (fragsAndReqsResources == null)
+            {
+                fragsAndReqsResources = new HashSet<Resource>();
+                context.setAttribute(FRAGMENT_AND_REQUIRED_RESOURCES, fragsAndReqsResources);
+            }
+            
+            for (Bundle b : bundles)
+            {
+                //skip bundles that are not installed
+                if (b.getState() == Bundle.UNINSTALLED)
+                    continue;
+                
+                //add to context attribute storing associated fragments and required bundles
+                fragsAndReqsBundles.add(b);
+                File f = BundleFileLocatorHelperFactory.getFactory().getHelper().getBundleInstallLocation(b);
+                Resource r = Resource.newResource(f.toURI());
+                //add to convenience context attribute storing fragments and required bundles as Resources
+                fragsAndReqsResources.add(r);
+                mergedResources.add(r);
+            }
         }
         
         return mergedResources;
     }
     
-    
+    /* ------------------------------------------------------------ */
     /** 
      * Allow fragments to supply some resources that are added to the baseResource of the webapp.
      * 
@@ -165,9 +208,8 @@ public class OSGiWebInfConfiguration extends WebInfConfiguration
         Bundle bundle = (Bundle)context.getAttribute(OSGiWebappConstants.JETTY_OSGI_BUNDLE);
         if (bundle != null)
         {
-            //TODO anything we need to do to improve PackageAdminServiceTracker?
-            Bundle[] fragments = PackageAdminServiceTracker.INSTANCE.getFragmentsAndRequiredBundles(bundle);
-            if (fragments != null && fragments.length != 0)
+            Set<Bundle> fragments = (Set<Bundle>)context.getAttribute(FRAGMENT_AND_REQUIRED_BUNDLES);
+            if (fragments != null && !fragments.isEmpty())
             {
                 // sorted extra resource base found in the fragments.
                 // the resources are either overriding the resourcebase found in the
@@ -212,7 +254,7 @@ public class OSGiWebInfConfiguration extends WebInfConfiguration
                     }
                 }
                 if (!appendedResourcesPath.isEmpty())
-                    context.setAttribute(WebInfConfiguration.RESOURCE_URLS, new ArrayList<Resource>(appendedResourcesPath.values()));
+                    context.setAttribute(WebInfConfiguration.RESOURCE_DIRS, new HashSet<Resource>(appendedResourcesPath.values()));
             }
         }
         
@@ -227,11 +269,10 @@ public class OSGiWebInfConfiguration extends WebInfConfiguration
             resources[resources.length-1] = context.getBaseResource();
             context.setBaseResource(new ResourceCollection(resources));
         }
-        
     }
 
     
-    
+    /* ------------------------------------------------------------ */
     /**
     * Resolves the bundle. Usually that would be a single URL per bundle. But we do some more work if there are jars
     * embedded in the bundle.
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiWebappConstants.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiWebappConstants.java
index 3bf9b25..bb4e1fa 100644
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiWebappConstants.java
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiWebappConstants.java
@@ -19,7 +19,12 @@
 package org.eclipse.jetty.osgi.boot;
 
 /**
- * Name of the service properties for a ContextHandler that configure a webapp deployed on jetty OSGi.
+ * OSGiWebappConstants
+ * 
+ * 
+ * Constants (MANIFEST headers, service properties etc) associated with deploying
+ * webapps into OSGi via Jetty.
+ * 
  */
 public class OSGiWebappConstants
 {
@@ -74,6 +79,10 @@ public class OSGiWebappConstants
     public static final String JETTY_WAR_PATCH_FRAGMENT_FOLDER_PATH = "Jetty-WarPatchFragmentFolderPath";
 
   
+    /** installation path of webapp bundle
+     *
+     */
+    public static final String JETTY_BUNDLE_ROOT = "bundle.root";
     /** 
      * web app context path 
      * @deprecated see RFC66_WEB_CONTEXTPATH
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/ServiceContextProvider.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/ServiceContextProvider.java
index 67fef35..eb3e079 100644
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/ServiceContextProvider.java
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/ServiceContextProvider.java
@@ -31,6 +31,7 @@ import org.eclipse.jetty.server.handler.ContextHandler;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
 import org.osgi.framework.Bundle;
+import org.osgi.framework.Constants;
 import org.osgi.framework.FrameworkUtil;
 import org.osgi.framework.ServiceReference;
 import org.osgi.framework.ServiceRegistration;
@@ -38,7 +39,9 @@ import org.osgi.framework.ServiceRegistration;
 /**
  * ServiceContextProvider
  *
- *
+ * Jetty DeploymentManager Provider that is able to deploy ContextHandlers discovered via OSGi as services.
+ * 
+ * 
  */
 public class ServiceContextProvider extends AbstractContextProvider implements ServiceProvider
 { 
@@ -94,6 +97,9 @@ public class ServiceContextProvider extends AbstractContextProvider implements S
         if (context == null || serviceRef == null)
             return false;
         
+        if (context instanceof org.eclipse.jetty.webapp.WebAppContext)
+            return false; //the ServiceWebAppProvider will deploy it
+        
         String watermark = (String)serviceRef.getProperty(OSGiWebappConstants.WATERMARK);
         if (watermark != null && !"".equals(watermark))
             return false;  //this service represents a contexthandler that has already been registered as a service by another of our deployers
@@ -115,7 +121,7 @@ public class ServiceContextProvider extends AbstractContextProvider implements S
                     properties.put(key, serviceRef.getProperty(key));
             }
             Bundle bundle = serviceRef.getBundle();                
-            String originId = bundle.getSymbolicName() + "-" + bundle.getVersion().toString() + "-"+contextFile;
+            String originId = bundle.getSymbolicName() + "-" + bundle.getVersion().toString() + "-"+(contextFile!=null?contextFile:serviceRef.getProperty(Constants.SERVICE_ID));
             ServiceApp app = new ServiceApp(getDeploymentManager(), this, bundle, properties, contextFile, originId);         
             app.setHandler(context); //set the pre=made ContextHandler instance
             _serviceMap.put(serviceRef, app);
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/ServiceProvider.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/ServiceProvider.java
index cbee686..1388c23 100644
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/ServiceProvider.java
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/ServiceProvider.java
@@ -21,6 +21,11 @@ package org.eclipse.jetty.osgi.boot;
 import org.eclipse.jetty.server.handler.ContextHandler;
 import org.osgi.framework.ServiceReference;
 
+/**
+ * ServiceProvider
+ *
+ * Jetty DeploymentManager Provider api for webapps or ContextHandlers that are discovered as OSGi services.
+ */
 public interface ServiceProvider
 {
     public boolean serviceAdded (ServiceReference ref, ContextHandler handler) throws Exception;
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/ServiceWebAppProvider.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/ServiceWebAppProvider.java
index 4749f5e..08de2b2 100644
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/ServiceWebAppProvider.java
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/ServiceWebAppProvider.java
@@ -27,7 +27,6 @@ import org.eclipse.jetty.deploy.App;
 import org.eclipse.jetty.deploy.AppProvider;
 import org.eclipse.jetty.deploy.DeploymentManager;
 import org.eclipse.jetty.osgi.boot.internal.serverfactory.ServerInstanceWrapper;
-import org.eclipse.jetty.osgi.boot.utils.EventSender;
 import org.eclipse.jetty.server.handler.ContextHandler;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/jsp/TldLocatableURLClassloader.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/jsp/TldLocatableURLClassloader.java
deleted file mode 100644
index ca4b649..0000000
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/jsp/TldLocatableURLClassloader.java
+++ /dev/null
@@ -1,65 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.osgi.boot.internal.jsp;
-
-import java.net.URL;
-import java.net.URLClassLoader;
-
-/**
- * Tricky url classloader. In fact we don't want a real URLClassLoader: we want
- * OSGi to provide its classloader and let it does. But to let
- * {@link org.apache.jasper.compiler.TldLocationsCache} find the core tlds
- * inside the jars we must be a URLClassLoader that returns an array of jars
- * where tlds are stored when the method getURLs is called.
- */
-public class TldLocatableURLClassloader extends URLClassLoader
-{
-
-    private URL[] _jarsWithTldsInside;
-
-    public TldLocatableURLClassloader(ClassLoader osgiClassLoader, URL[] jarsWithTldsInside)
-    {
-        super(new URL[] {},osgiClassLoader);
-        _jarsWithTldsInside = jarsWithTldsInside;
-    }
-
-    /**
-     * @return the jars that contains tlds so that TldLocationsCache or
-     *         TldScanner can find them.
-     */
-    @Override
-    public URL[] getURLs()
-    {
-        return _jarsWithTldsInside;
-    }
-
-    public String toString()
-    {
-        StringBuilder builder = new StringBuilder();
-
-        if (_jarsWithTldsInside != null)
-        {
-            for (URL u:_jarsWithTldsInside)
-                builder.append(" "+u.toString());
-            return builder.toString();
-        }
-        else
-            return super.toString();
-    }
-}
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/jsp/TldLocatableURLClassloaderWithInsertedJettyClassloader.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/jsp/TldLocatableURLClassloaderWithInsertedJettyClassloader.java
deleted file mode 100644
index 19a1f1e..0000000
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/jsp/TldLocatableURLClassloaderWithInsertedJettyClassloader.java
+++ /dev/null
@@ -1,69 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.osgi.boot.internal.jsp;
-
-import java.net.URL;
-
-/**
- * Add a classloader to the
- * org.apache.jasper.compiler.TldLocatableURLClassloader. Hopefuly not
- * necessary: still experimenting.
- * 
- * @see TldLocatableURLClassloader
- */
-public class TldLocatableURLClassloaderWithInsertedJettyClassloader extends TldLocatableURLClassloader
-{
-
-    private ClassLoader _internalClassLoader;
-
-    /**
-     * 
-     * @param osgiClassLoaderParent
-     *            The parent classloader
-     * @param internalClassLoader
-     *            The classloader that will be at the same level than the
-     *            jarsWithTldsInside
-     * @param jarsWithTldsInside
-     *            jars that are scanned for tld files.
-     */
-    public TldLocatableURLClassloaderWithInsertedJettyClassloader(ClassLoader osgiClassLoaderParent, ClassLoader internalClassLoader, URL[] jarsWithTldsInside)
-    {
-        super(osgiClassLoaderParent,jarsWithTldsInside);
-        _internalClassLoader = internalClassLoader;
-    }
-
-    protected Class<?> findClass(String name) throws ClassNotFoundException
-    {
-        try
-        {
-            return super.findClass(name);
-        }
-        catch (ClassNotFoundException cne)
-        {
-            if (_internalClassLoader != null)
-            {
-                return _internalClassLoader.loadClass(name);
-            }
-            else
-            {
-                throw cne;
-            }
-        }
-    }
-}
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/DefaultJettyAtJettyHomeHelper.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/DefaultJettyAtJettyHomeHelper.java
index 89da682..ff1a774 100644
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/DefaultJettyAtJettyHomeHelper.java
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/DefaultJettyAtJettyHomeHelper.java
@@ -20,19 +20,25 @@ package org.eclipse.jetty.osgi.boot.internal.serverfactory;
 
 import java.io.File;
 import java.net.MalformedURLException;
+import java.net.URI;
 import java.net.URL;
+import java.util.ArrayList;
 import java.util.Dictionary;
 import java.util.Enumeration;
 import java.util.Hashtable;
+import java.util.List;
 import java.util.StringTokenizer;
 
 import org.eclipse.jetty.osgi.boot.JettyBootstrapActivator;
 import org.eclipse.jetty.osgi.boot.OSGiServerConstants;
-import org.eclipse.jetty.osgi.boot.internal.webapp.BundleFileLocatorHelperFactory;
-import org.eclipse.jetty.osgi.boot.utils.BundleFileLocatorHelper;
+import org.eclipse.jetty.osgi.boot.utils.BundleFileLocatorHelperFactory;
+import org.eclipse.jetty.osgi.boot.utils.OSGiClassLoader;
+import org.eclipse.jetty.osgi.boot.utils.Util;
 import org.eclipse.jetty.server.Server;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.JarResource;
+import org.eclipse.jetty.util.resource.Resource;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
 
@@ -40,18 +46,19 @@ import org.osgi.framework.BundleContext;
  * DefaultJettyAtJettyHomeHelper
  * 
  * 
+ * Creates a default instance of Jetty, based on the values of the
+ * System properties "jetty.home" or "jetty.home.bundle", one of which
+ * must be specified in order to create the default instance.
+ * 
  * Called by the {@link JettyBootstrapActivator} during the starting of the
- * bundle. If the system property 'jetty.home' is defined and points to a
- * folder, then setup the corresponding jetty server.
+ * bundle. 
  */
 public class DefaultJettyAtJettyHomeHelper
 {
     private static final Logger LOG = Log.getLogger(DefaultJettyAtJettyHomeHelper.class);
 
     /**
-     * contains a comma separated list of pathes to the etc/jetty-*.xml files
-     * used to configure jetty. By default the value is 'etc/jetty.xml' when the
-     * path is relative the file is resolved relatively to jettyhome.
+     * contains a comma separated list of paths to the etc/jetty-*.xml files
      */
     public static final String JETTY_ETC_FILES = OSGiServerConstants.MANAGED_JETTY_XML_CONFIG_URLS;
 
@@ -66,6 +73,7 @@ public class DefaultJettyAtJettyHomeHelper
     public static final String DEFAULT_JETTYHOME = "/jettyhome";
     
     
+    
     /* ------------------------------------------------------------ */
     /**
      * Called by the JettyBootStrapActivator. If the system property jetty.home
@@ -79,9 +87,7 @@ public class DefaultJettyAtJettyHomeHelper
      * In both cases reads the system property 'jetty.etc.config.urls' to locate
      * the configuration files for the deployed jetty. It is a comma separated
      * list of URLs or relative paths inside the bundle or folder to the config
-     * files. If undefined it defaults to 'etc/jetty.xml'. In the case of the jetty.home.bundle,
-     * if no etc/jetty.xml file is found in the bundle, it will look for 
-     * /jettyhome/etc/jetty-osgi-default.xml
+     * files.
      * </p>
      * <p>
      * In both cases the system properties jetty.host, jetty.port and
@@ -89,36 +95,42 @@ public class DefaultJettyAtJettyHomeHelper
      * as part of their properties.
      * </p>
      */
-    public static void startJettyAtJettyHome(BundleContext bundleContext) throws Exception
+    public static Server startJettyAtJettyHome(BundleContext bundleContext) throws Exception
     {
         String jettyHomeSysProp = System.getProperty(OSGiServerConstants.JETTY_HOME);
         String jettyHomeBundleSysProp = System.getProperty(OSGiServerConstants.JETTY_HOME_BUNDLE);
-        File jettyHome = null;
+        File jettyHomeDir = null;
         Bundle jettyHomeBundle = null;
+     
+        Dictionary<String,String> properties = new Hashtable<String,String>();
         if (jettyHomeSysProp != null)
         {
-            jettyHomeSysProp = resolvePropertyValue(jettyHomeSysProp);
+            jettyHomeSysProp = Util.resolvePropertyValue(jettyHomeSysProp);
             // bug 329621
             if (jettyHomeSysProp.startsWith("\"") && jettyHomeSysProp.endsWith("\"") || (jettyHomeSysProp.startsWith("'") && jettyHomeSysProp.endsWith("'")))
-            {
                 jettyHomeSysProp = jettyHomeSysProp.substring(1, jettyHomeSysProp.length() - 1);
-            }
+         
             if (jettyHomeBundleSysProp != null)
-            {
                 LOG.warn("Both jetty.home and jetty.home.bundle property defined: jetty.home.bundle ignored.");
-            }
-            jettyHome = new File(jettyHomeSysProp);
-            if (!jettyHome.exists() || !jettyHome.isDirectory())
+           
+            jettyHomeDir = new File(jettyHomeSysProp);
+            if (!jettyHomeDir.exists() || !jettyHomeDir.isDirectory())
             {
                 LOG.warn("Unable to locate the jetty.home folder " + jettyHomeSysProp);
-                return;
+                return null;
             }
+            
+            //set jetty.home
+            Util.setProperty(properties, OSGiServerConstants.JETTY_HOME, jettyHomeDir.getAbsolutePath());
         }
         else if (jettyHomeBundleSysProp != null)
         {
-            jettyHomeBundleSysProp = resolvePropertyValue(jettyHomeBundleSysProp);
+            jettyHomeBundleSysProp = Util.resolvePropertyValue(jettyHomeBundleSysProp);
             for (Bundle b : bundleContext.getBundles())
             {
+                if (b.getState() == Bundle.UNINSTALLED)
+                    continue;
+                
                 if (b.getSymbolicName().equals(jettyHomeBundleSysProp))
                 {
                     jettyHomeBundle = b;
@@ -128,37 +140,54 @@ public class DefaultJettyAtJettyHomeHelper
             if (jettyHomeBundle == null)
             {
                 LOG.warn("Unable to find the jetty.home.bundle named " + jettyHomeSysProp);
-                return;
+                return null;
             }
-
         }
-        if (jettyHome == null && jettyHomeBundle == null)
+        
+        if (jettyHomeDir == null && jettyHomeBundle == null)
         {
             LOG.warn("No default jetty created.");
-            return;
+            return null;
         }
+        
+        
+        
+        //configure the server here rather than letting the JettyServerServiceTracker do it, because we want to be able to
+        //configure the ThreadPool, which can only be done via the constructor, ie from within the xml configuration processing
+        List<URL> configURLs = jettyHomeDir != null ? getJettyConfigurationURLs(jettyHomeDir) : getJettyConfigurationURLs(jettyHomeBundle, properties);
 
-        Server server = new Server();
-        Dictionary<String,String> properties = new Hashtable<String,String>();
-        properties.put(OSGiServerConstants.MANAGED_JETTY_SERVER_NAME, OSGiServerConstants.MANAGED_JETTY_SERVER_DEFAULT_NAME);
-
-        String configURLs = jettyHome != null ? getJettyConfigurationURLs(jettyHome) : getJettyConfigurationURLs(jettyHomeBundle);
-        properties.put(OSGiServerConstants.MANAGED_JETTY_XML_CONFIG_URLS, configURLs);
-
-        LOG.info("Configuring the default jetty server with " + configURLs);
-
-        // these properties usually are the ones passed to this type of
-        // configuration.
-        setProperty(properties, OSGiServerConstants.JETTY_HOME, System.getProperty(OSGiServerConstants.JETTY_HOME));
-        setProperty(properties, OSGiServerConstants.JETTY_HOST, System.getProperty(OSGiServerConstants.JETTY_HOST));
-        setProperty(properties, OSGiServerConstants.JETTY_PORT, System.getProperty(OSGiServerConstants.JETTY_PORT));
-        setProperty(properties, OSGiServerConstants.JETTY_PORT_SSL, System.getProperty(OSGiServerConstants.JETTY_PORT_SSL));
+        LOG.info("Configuring the default jetty server with {}",configURLs);
+        LOG.info("JETTY.HOME="+properties.get(OSGiServerConstants.JETTY_HOME));
+        ClassLoader contextCl = Thread.currentThread().getContextClassLoader();
+        try
+        {
+            Thread.currentThread().setContextClassLoader(JettyBootstrapActivator.class.getClassLoader());
+            
+            // these properties usually are the ones passed to this type of
+            // configuration.
+            properties.put(OSGiServerConstants.MANAGED_JETTY_SERVER_NAME, OSGiServerConstants.MANAGED_JETTY_SERVER_DEFAULT_NAME);
+            Util.setProperty(properties, OSGiServerConstants.JETTY_HOST, System.getProperty(OSGiServerConstants.JETTY_HOST));
+            Util.setProperty(properties, OSGiServerConstants.JETTY_PORT, System.getProperty(OSGiServerConstants.JETTY_PORT));
+            Util.setProperty(properties, OSGiServerConstants.JETTY_PORT_SSL, System.getProperty(OSGiServerConstants.JETTY_PORT_SSL));
 
-        //register the Server instance as an OSGi service.
-        bundleContext.registerService(Server.class.getName(), server, properties);
+            Server server = ServerInstanceWrapper.configure(null, configURLs, properties);
+            //ensure jetty.home is set
+            server.setAttribute(OSGiServerConstants.JETTY_HOME, properties.get(OSGiServerConstants.JETTY_HOME));
+            
+            //Register the default Server instance as an OSGi service.
+            //The JettyServerServiceTracker will notice it and set it up to deploy bundles as wars etc
+            bundleContext.registerService(Server.class.getName(), server, properties);
+            LOG.info("Default jetty server configured");
+            return server;
+        }
+        finally
+        {
+            Thread.currentThread().setContextClassLoader(contextCl);
+        }
     }
+
     
-    
+   
     
     /* ------------------------------------------------------------ */
     /**
@@ -169,33 +198,26 @@ public class DefaultJettyAtJettyHomeHelper
      * 
      * @param jettyhome
      * @return
+     * @throws MalformedURLException 
      */
-    private static String getJettyConfigurationURLs(File jettyhome)
+    private static List<URL> getJettyConfigurationURLs(File jettyhome) 
+    throws MalformedURLException
     {
+        List<URL> configURLs = new ArrayList<URL>();
         String jettyetc = System.getProperty(JETTY_ETC_FILES, DEFAULT_JETTY_ETC_FILES);
         StringTokenizer tokenizer = new StringTokenizer(jettyetc, ";,", false);
-        StringBuilder res = new StringBuilder();
         while (tokenizer.hasMoreTokens())
         {
             String next = tokenizer.nextToken().trim();
-            if (!next.startsWith("/") && next.indexOf(':') == -1)
-            {
-                try
-                {
-                    next = new File(jettyhome, next).toURI().toURL().toString();
-                }
-                catch (MalformedURLException e)
-                {
-                    LOG.warn(e);
-                    continue;
-                }
-            }
-            appendToCommaSeparatedList(res, next);
+            //etc files can either be relative to jetty.home or absolute disk locations
+            if (!next.startsWith("/") && (next.indexOf(':') == -1))    
+                configURLs.add(new File(jettyhome, next).toURI().toURL());
+            else 
+                configURLs.add(new URL(next));
         }
-        return res.toString();
+        return configURLs;
     }
-    
-    
+
     /* ------------------------------------------------------------ */
     /**
      * Minimum setup for the location of the configuration files given a
@@ -206,118 +228,90 @@ public class DefaultJettyAtJettyHomeHelper
      * @param jettyhome
      * @return
      */
-    private static String getJettyConfigurationURLs(Bundle configurationBundle)
+    private static List<URL> getJettyConfigurationURLs(Bundle configurationBundle, Dictionary properties)
+    throws Exception
     {
-        String files = System.getProperty(JETTY_ETC_FILES, DEFAULT_JETTY_ETC_FILES);
-       
+        List<URL> configURLs = new ArrayList<URL>();
+        String files = System.getProperty(JETTY_ETC_FILES, DEFAULT_JETTY_ETC_FILES);       
         StringTokenizer tokenizer = new StringTokenizer(files, ";,", false);
-        StringBuilder res = new StringBuilder();
 
         while (tokenizer.hasMoreTokens())
         {
             String etcFile = tokenizer.nextToken().trim();
+
+            //file path is absolute
             if (etcFile.startsWith("/") || etcFile.indexOf(":") != -1)
+                configURLs.add(new URL(etcFile));
+            else //relative file path
             {
-                //file path is absolute
-                appendToCommaSeparatedList(res, etcFile);
-            }
-            else
-            {
-                //relative file path
                 Enumeration<URL> enUrls = BundleFileLocatorHelperFactory.getFactory().getHelper().findEntries(configurationBundle, etcFile);
-                      
+
+                String home = null;
                 // default for org.eclipse.osgi.boot where we look inside
-                // jettyhome for the default embedded configuration.
-                // default inside jettyhome. this way fragments to the bundle
-                // can define their own configuration.
+                // jettyhome/ for the default embedded configuration.
                 if ((enUrls == null || !enUrls.hasMoreElements()))
                 {
+                    home = DEFAULT_JETTYHOME;
                     String tmp = DEFAULT_JETTYHOME+(DEFAULT_JETTYHOME.endsWith("/")?"":"/")+etcFile;
                     enUrls = BundleFileLocatorHelperFactory.getFactory().getHelper().findEntries(configurationBundle, tmp);                    
-                    LOG.info("Configuring jetty from bundle: "
-                                       + configurationBundle.getSymbolicName()
-                                       + " with "+tmp);
+                    LOG.info("Configuring jetty from bundle: {} with {}", configurationBundle.getSymbolicName(),tmp);
+                }          
+
+                //lazily ensure jetty.home value is set based on location of etc files
+                if (properties.get(OSGiServerConstants.JETTY_HOME) == null)
+                {
+                    Resource res = findDir(configurationBundle, home);
+                    if (res != null)
+                        properties.put(OSGiServerConstants.JETTY_HOME, res.toString());
                 }
+         
                 if (enUrls == null || !enUrls.hasMoreElements())
-                {
                     throw new IllegalStateException ("Unable to locate a jetty configuration file for " + etcFile);
-                }
-                if (enUrls != null)
-                {
-                    while (enUrls.hasMoreElements())
-                    {
-                        URL url = BundleFileLocatorHelperFactory.getFactory().getHelper().getFileURL(enUrls.nextElement());
-                        appendToCommaSeparatedList(res, url.toString());
-                    }
-                }
+
+                URL url = BundleFileLocatorHelperFactory.getFactory().getHelper().getFileURL(enUrls.nextElement());
+                configURLs.add(url);
+           
             }
         }
-        return res.toString();
-    }
-    
-    
-    /* ------------------------------------------------------------ */
-    private static void appendToCommaSeparatedList(StringBuilder buffer, String value)
-    {
-        if (buffer.length() != 0)
-        {
-            buffer.append(",");
-        }
-        buffer.append(value);
+        return configURLs;
     }
     
-    
-    /* ------------------------------------------------------------ */
-    private static void setProperty(Dictionary<String,String> properties, String key, String value)
-    {
-        if (value != null)
-        {
-            properties.put(key, value);
-        }
-    }
-    
-    
     /* ------------------------------------------------------------ */
     /**
-     * recursively substitute the ${sysprop} by their actual system property.
-     * ${sysprop,defaultvalue} will use 'defaultvalue' as the value if no
-     * sysprop is defined. Not the most efficient code but we are shooting for
-     * simplicity and speed of development here.
-     * 
-     * @param value
+     * Get a resource representing a directory inside a bundle. If the dir is null,
+     * return a resource representing the installation location of the bundle.
+     * @param bundle
+     * @param dir
      * @return
      */
-    public static String resolvePropertyValue(String value)
+    public static Resource findDir (Bundle bundle, String dir)
     {
-        int ind = value.indexOf("${");
-        if (ind == -1) { return value; }
-        int ind2 = value.indexOf('}', ind);
-        if (ind2 == -1) { return value; }
-        String sysprop = value.substring(ind + 2, ind2);
-        String defaultValue = null;
-        int comma = sysprop.indexOf(',');
-        if (comma != -1 && comma + 1 != sysprop.length())
-        {
-            defaultValue = sysprop.substring(comma + 1);
-            defaultValue = resolvePropertyValue(defaultValue);
-            sysprop = sysprop.substring(0, comma);
-        }
-        else
-        {
-            defaultValue = "${" + sysprop + "}";
-        }
-
-        String v = System.getProperty(sysprop);
+        if (bundle == null)
+            return null;
 
-        String reminder = value.length() > ind2 + 1 ? value.substring(ind2 + 1) : "";
-        reminder = resolvePropertyValue(reminder);
-        if (v != null)
+        try
         {
-            return value.substring(0, ind) + v + reminder;
+            File f = BundleFileLocatorHelperFactory.getFactory().getHelper().getBundleInstallLocation(bundle);
+            URL u = f.toURI().toURL();
+            u = BundleFileLocatorHelperFactory.getFactory().getHelper().getLocalURL(u);
+            Resource res = Resource.newResource(u);
+            String s = res.toString();
+   
+            //check if it is an unarchived bundle
+            if (s.endsWith(".jar") && s.startsWith("file:"))
+                res = JarResource.newJarResource(res);
+            
+            //if looking for a directory 
+            if (dir != null)
+                res = res.addPath(dir);
+            
+            return res;
+          
         }
-        else
+        catch (Exception e)
         {
-            return value.substring(0, ind) + defaultValue + reminder;
+            LOG.warn("Bad bundle location" , e);
+            return null;
         }
     }
 }
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/IManagedJettyServerRegistry.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/IManagedJettyServerRegistry.java
deleted file mode 100644
index 70d0a98..0000000
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/IManagedJettyServerRegistry.java
+++ /dev/null
@@ -1,34 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.osgi.boot.internal.serverfactory;
-
-/**
- * Keeps track of the running jetty servers. They are named.
- */
-public interface IManagedJettyServerRegistry
-{
-
-    /**
-     * @param managedServerName The server name
-     * @return the corresponding jetty server wrapped with its deployment
-     *         properties.
-     */
-    public ServerInstanceWrapper getServerInstanceWrapper(String managedServerName);
-
-}
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/JettyServerServiceTracker.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/JettyServerServiceTracker.java
index 521387e..9c02da4 100644
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/JettyServerServiceTracker.java
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/JettyServerServiceTracker.java
@@ -32,18 +32,16 @@ import org.osgi.framework.ServiceListener;
 import org.osgi.framework.ServiceReference;
 
 /**
- * Deploy the jetty server instances when they are registered as an OSGi
- * service.
+ * JettyServerServiceTracker
+ * 
+ * Tracks instances of Jetty Servers, and configures them so that they can deploy 
+ * webapps or ContextHandlers discovered from the OSGi environment.
+ * 
  */
-public class JettyServerServiceTracker implements ServiceListener, IManagedJettyServerRegistry
+public class JettyServerServiceTracker implements ServiceListener
 {
     private static Logger LOG = Log.getLogger(JettyServerServiceTracker.class.getName());
 
-    /**
-     * Servers indexed by PIDs. PIDs are generated by the ConfigurationAdmin
-     * service.
-     */
-    private Map<String, ServerInstanceWrapper> _serversIndexedByName = new HashMap<String, ServerInstanceWrapper>();
 
     /** The context-handler to deactivate indexed by ServerInstanceWrapper */
     private Map<ServiceReference, ServerInstanceWrapper> _indexByServiceReference = new HashMap<ServiceReference, ServerInstanceWrapper>();
@@ -53,8 +51,7 @@ public class JettyServerServiceTracker implements ServiceListener, IManagedJetty
      */
     public void stop()
     {
-        // not sure that this is really useful but here we go.
-        for (ServerInstanceWrapper wrapper : _serversIndexedByName.values())
+        for (ServerInstanceWrapper wrapper : _indexByServiceReference.values())
         {
             try
             {
@@ -133,7 +130,6 @@ public class JettyServerServiceTracker implements ServiceListener, IManagedJetty
         if (name == null) { throw new IllegalArgumentException("The property " + OSGiServerConstants.MANAGED_JETTY_SERVER_NAME + " is mandatory"); }
         ServerInstanceWrapper wrapper = new ServerInstanceWrapper(name);
         _indexByServiceReference.put(sr, wrapper);
-        _serversIndexedByName.put(name, wrapper);
         return wrapper;
     }
 
@@ -148,25 +144,10 @@ public class JettyServerServiceTracker implements ServiceListener, IManagedJetty
         ServerInstanceWrapper handler = _indexByServiceReference.remove(sr);
         if (handler == null)
         {
-            // a warning?
+            LOG.warn("Unknown Jetty Server ServiceReference: ", sr);
             return null;
         }
-        String name = handler.getManagedServerName();
-        if (name != null)
-        {
-            _serversIndexedByName.remove(name);
-        }
+       
         return handler;
     }
-
-    /**
-     * @param managedServerName The server name
-     * @return the corresponding jetty server wrapped with its deployment
-     *         properties.
-     */
-    public ServerInstanceWrapper getServerInstanceWrapper(String managedServerName)
-    {
-        return _serversIndexedByName.get(managedServerName == null ? OSGiServerConstants.MANAGED_JETTY_SERVER_DEFAULT_NAME : managedServerName);
-    }
-
 }
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/ServerInstanceWrapper.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/ServerInstanceWrapper.java
index f21208b..0ffab2a 100644
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/ServerInstanceWrapper.java
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/ServerInstanceWrapper.java
@@ -19,15 +19,17 @@
 package org.eclipse.jetty.osgi.boot.internal.serverfactory;
 
 import java.io.File;
-import java.io.IOException;
 import java.io.InputStream;
 import java.net.URL;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Dictionary;
 import java.util.Enumeration;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.StringTokenizer;
 
 import org.eclipse.jetty.deploy.AppLifeCycle;
@@ -43,11 +45,11 @@ import org.eclipse.jetty.osgi.boot.OSGiServerConstants;
 import org.eclipse.jetty.osgi.boot.OSGiUndeployer;
 import org.eclipse.jetty.osgi.boot.ServiceContextProvider;
 import org.eclipse.jetty.osgi.boot.ServiceWebAppProvider;
-import org.eclipse.jetty.osgi.boot.internal.jsp.TldLocatableURLClassloader;
-import org.eclipse.jetty.osgi.boot.internal.webapp.BundleFileLocatorHelperFactory;
 import org.eclipse.jetty.osgi.boot.internal.webapp.LibExtClassLoaderHelper;
-import org.eclipse.jetty.osgi.boot.internal.webapp.WebBundleTrackerCustomizer;
-import org.eclipse.jetty.osgi.boot.utils.WebappRegistrationCustomizer;
+import org.eclipse.jetty.osgi.boot.utils.BundleFileLocatorHelperFactory;
+import org.eclipse.jetty.osgi.boot.utils.FakeURLClassLoader;
+import org.eclipse.jetty.osgi.boot.utils.TldBundleDiscoverer;
+import org.eclipse.jetty.osgi.boot.utils.Util;
 import org.eclipse.jetty.server.Server;
 import org.eclipse.jetty.server.handler.ContextHandlerCollection;
 import org.eclipse.jetty.util.IO;
@@ -71,6 +73,9 @@ public class ServerInstanceWrapper
      * support the case where the bundle is zipped.
      */
     public static final String PROPERTY_THIS_JETTY_XML_FOLDER_URL = "this.jetty.xml.parent.folder.url";
+    
+    
+    private static Collection<TldBundleDiscoverer> __containerTldBundleDiscoverers = new ArrayList<TldBundleDiscoverer>();
 
     private static Logger LOG = Log.getLogger(ServerInstanceWrapper.class.getName());
     
@@ -95,6 +100,102 @@ public class ServerInstanceWrapper
     private DeploymentManager _deploymentManager;
     
     
+    
+    /* ------------------------------------------------------------ */
+    public static void addContainerTldBundleDiscoverer (TldBundleDiscoverer tldBundleDiscoverer)
+    {
+        __containerTldBundleDiscoverers.add(tldBundleDiscoverer);
+    }
+    
+    /* ------------------------------------------------------------ */
+    public static Collection<TldBundleDiscoverer> getContainerTldBundleDiscoverers()
+    {
+        return __containerTldBundleDiscoverers;
+    }
+    
+ 
+
+    
+    /* ------------------------------------------------------------ */
+    public static Server configure(Server server, List<URL> jettyConfigurations, Dictionary props) throws Exception
+    {
+       
+        if (jettyConfigurations == null || jettyConfigurations.isEmpty()) { return server; }
+        
+        Map<String, Object> id_map = new HashMap<String, Object>();
+        if (server != null)
+        {
+            //Put in a mapping for the id "Server" and the name of the server as the instance being configured
+            id_map.put("Server", server);
+            id_map.put((String)props.get(OSGiServerConstants.MANAGED_JETTY_SERVER_NAME), server);
+        }
+
+        Map<String, String> properties = new HashMap<String, String>();
+        if (props != null)
+        {
+            Enumeration<Object> en = props.keys();
+            while (en.hasMoreElements())
+            {
+                Object key = en.nextElement();
+                Object value = props.get(key);
+                String keyStr = String.valueOf(key);
+                String valStr = String.valueOf(value);
+                properties.put(keyStr, valStr);
+                if (server != null) server.setAttribute(keyStr, valStr);
+            }
+        }
+
+        for (URL jettyConfiguration : jettyConfigurations)
+        {
+            InputStream is = null;
+            try
+            {
+                // Execute a Jetty configuration file
+                Resource r = Resource.newResource(jettyConfiguration);
+                if (!r.exists())
+                {
+                    LOG.warn("File does not exist "+r);
+                    throw new IllegalStateException("No such jetty server config file: "+r);
+                }
+                is = r.getInputStream();
+                XmlConfiguration config = new XmlConfiguration(is);
+                config.getIdMap().putAll(id_map);
+                config.getProperties().putAll(properties);
+                
+                // #334062 compute the URL of the folder that contains the
+                // conf file and set it as a property so we can compute relative paths
+                // from it.
+                String urlPath = jettyConfiguration.toString();
+                int lastSlash = urlPath.lastIndexOf('/');
+                if (lastSlash > 4)
+                {
+                    urlPath = urlPath.substring(0, lastSlash);
+                    config.getProperties().put(PROPERTY_THIS_JETTY_XML_FOLDER_URL, urlPath);
+                }
+     
+                Object o = config.configure();
+                if (server == null)
+                    server = (Server)o;
+                
+                id_map = config.getIdMap();
+            }
+            catch (SAXParseException saxparse)
+            {
+                LOG.warn("Unable to configure the jetty/etc file " + jettyConfiguration, saxparse);
+                throw saxparse;
+            }
+            finally
+            {
+                IO.close(is);
+            }
+        }
+
+        return server;
+    }
+    
+    
+    
+    
     /* ------------------------------------------------------------ */
     public ServerInstanceWrapper(String managedServerName)
     {
@@ -163,18 +264,41 @@ public class ServerInstanceWrapper
             String sharedURLs = (String) props.get(OSGiServerConstants.MANAGED_JETTY_SHARED_LIB_FOLDER_URLS);
 
             List<File> shared = sharedURLs != null ? extractFiles(sharedURLs) : null;
-            libExtClassLoader = LibExtClassLoaderHelper.createLibExtClassLoader(shared, null, server, JettyBootstrapActivator.class.getClassLoader());
+            libExtClassLoader = LibExtClassLoaderHelper.createLibExtClassLoader(shared, null,JettyBootstrapActivator.class.getClassLoader());
 
             if (LOG.isDebugEnabled()) LOG.debug("LibExtClassLoader = "+libExtClassLoader);
             
             Thread.currentThread().setContextClassLoader(libExtClassLoader);
 
-            configure(server, props);
+            String jettyConfigurationUrls = (String) props.get(OSGiServerConstants.MANAGED_JETTY_XML_CONFIG_URLS);
+            List<URL> jettyConfigurations = jettyConfigurationUrls != null ? Util.fileNamesAsURLs(jettyConfigurationUrls, Util.DEFAULT_DELIMS) : null;
+            
+            _server = configure(server, jettyConfigurations, props);
 
             init();
+            
+            //if support for jsp is enabled, we need to convert locations of bundles that contain tlds into urls.
+            //these are tlds that we want jasper to treat as if they are on the container's classpath. Web bundles
+            //can use the Require-TldBundle MANIFEST header to name other tld-containing bundles that should be regarded
+            //as on the webapp classpath.
+            if (!__containerTldBundleDiscoverers.isEmpty())
+            {
+                Set<URL> urls = new HashSet<URL>();
+                //discover bundles with tlds that need to be on the container's classpath as URLs
+                for (TldBundleDiscoverer d:__containerTldBundleDiscoverers)
+                {
+                    URL[] list = d.getUrlsForBundlesWithTlds(_deploymentManager, BundleFileLocatorHelperFactory.getFactory().getHelper());
+                    if (list != null)
+                    {
+                        for (URL u:list)
+                            urls.add(u);
+                    }
+                }
+                _commonParentClassLoaderForWebapps =  new FakeURLClassLoader(libExtClassLoader, urls.toArray(new URL[urls.size()]));
+            }
+            else
+                _commonParentClassLoaderForWebapps = libExtClassLoader;
 
-            URL[] jarsWithTlds = getJarsWithTlds();
-            _commonParentClassLoaderForWebapps = jarsWithTlds == null ? libExtClassLoader : new TldLocatableURLClassloader(libExtClassLoader, jarsWithTlds);
             
             if (LOG.isDebugEnabled()) LOG.debug("common classloader = "+_commonParentClassLoaderForWebapps);
 
@@ -218,128 +342,9 @@ public class ServerInstanceWrapper
     }
     
     
-    /* ------------------------------------------------------------ */
-    /**
-     * TODO: right now only the jetty-jsp bundle is scanned for common taglibs.
-     * Should support a way to plug more bundles that contain taglibs.
-     * 
-     * The jasper TldScanner expects a URLClassloader to parse a jar for the
-     * /META-INF/*.tld it may contain. We place the bundles that we know contain
-     * such tag-libraries. Please note that it will work if and only if the
-     * bundle is a jar (!) Currently we just hardcode the bundle that contains
-     * the jstl implementation.
-     * 
-     * A workaround when the tld cannot be parsed with this method is to copy
-     * and paste it inside the WEB-INF of the webapplication where it is used.
-     * 
-     * Support only 2 types of packaging for the bundle: - the bundle is a jar
-     * (recommended for runtime.) - the bundle is a folder and contain jars in
-     * the root and/or in the lib folder (nice for PDE development situations)
-     * Unsupported: the bundle is a jar that embeds more jars.
-     * 
-     * @return
-     * @throws Exception
-     */
-    private URL[] getJarsWithTlds() throws Exception
-    {
-        
-        //Jars that are added onto the equivalent of the container classpath are:
-        // jstl jars: identified by the class WhenTag (and the boot-bundle manifest imports the jstl packages
-        // bundles identified by System property org.eclipse.jetty.osgi.tldbundles
-        // bundle symbolic name patterns defined in the DeploymentManager
-        //
-        // Any bundles mentioned in the Require-TldBundle manifest header of the webapp bundle MUST ALSO HAVE Import-Bundle
-        // in order to get them onto the classpath of the webapp.
-        
-        ArrayList<URL> res = new ArrayList<URL>();
-        for (WebappRegistrationCustomizer regCustomizer : WebBundleTrackerCustomizer.JSP_REGISTRATION_HELPERS)
-        {
-            URL[] urls = regCustomizer.getJarsWithTlds(_deploymentManager, BundleFileLocatorHelperFactory.getFactory().getHelper());
-            for (URL url : urls)
-            {
-                if (!res.contains(url)) res.add(url);
-            }
-        }
-        if (!res.isEmpty())
-            return res.toArray(new URL[res.size()]);
-        else
-            return null;
-    }
-    
+   
     
     /* ------------------------------------------------------------ */
-    private void configure(Server server, Dictionary props) throws Exception
-    {
-        String jettyConfigurationUrls = (String) props.get(OSGiServerConstants.MANAGED_JETTY_XML_CONFIG_URLS);
-        List<URL> jettyConfigurations = jettyConfigurationUrls != null ? extractResources(jettyConfigurationUrls) : null;
-        if (jettyConfigurations == null || jettyConfigurations.isEmpty()) { return; }
-        Map<String, Object> id_map = new HashMap<String, Object>();
-        
-        //Put in a mapping for the id "Server" and the name of the server as the instance being configured
-        id_map.put("Server", server);
-        id_map.put((String)props.get(OSGiServerConstants.MANAGED_JETTY_SERVER_NAME), server);
-        
-        Map<String, String> properties = new HashMap<String, String>();
-        Enumeration<Object> en = props.keys();
-        while (en.hasMoreElements())
-        {
-            Object key = en.nextElement();
-            Object value = props.get(key);
-            String keyStr = String.valueOf(key);
-            String valStr = String.valueOf(value);
-            properties.put(keyStr, valStr);
-            server.setAttribute(keyStr, valStr);
-        }
-
-        for (URL jettyConfiguration : jettyConfigurations)
-        {
-            InputStream is = null;
-            try
-            {
-                // Execute a Jetty configuration file
-                Resource r = Resource.newResource(jettyConfiguration);
-                if (!r.exists())
-                {
-                    LOG.warn("File does not exist "+r);
-                    continue;
-                }
-                is = r.getInputStream();
-                XmlConfiguration config = new XmlConfiguration(is);
-                config.getIdMap().putAll(id_map);
-
-                // #334062 compute the URL of the folder that contains the
-                // jetty.xml conf file
-                // and set it as a property so we can compute relative paths
-                // from it.
-                String urlPath = jettyConfiguration.toString();
-                int lastSlash = urlPath.lastIndexOf('/');
-                if (lastSlash > 4)
-                {
-                    urlPath = urlPath.substring(0, lastSlash);
-                    Map<String, String> properties2 = new HashMap<String, String>(properties);
-                    properties2.put(PROPERTY_THIS_JETTY_XML_FOLDER_URL, urlPath);
-                    config.getProperties().putAll(properties2);
-                }
-                else
-                {
-                    config.getProperties().putAll(properties);
-                }
-                config.configure();
-                id_map = config.getIdMap();
-            }
-            catch (SAXParseException saxparse)
-            {
-                LOG.warn("Unable to configure the jetty/etc file " + jettyConfiguration, saxparse);
-                throw saxparse;
-            }
-            finally
-            {
-                IO.close(is);
-            }
-        }
-
-    }
-
     /**
      * Must be called after the server is configured. 
      * 
@@ -357,10 +362,10 @@ public class ServerInstanceWrapper
         List<String> providerClassNames = new ArrayList<String>();
         
         // get a deployerManager and some providers
-        List<DeploymentManager> deployers = _server.getBeans(DeploymentManager.class);
+        Collection<DeploymentManager> deployers = _server.getBeans(DeploymentManager.class);
         if (deployers != null && !deployers.isEmpty())
         {
-            _deploymentManager = deployers.get(0);
+            _deploymentManager = deployers.iterator().next();
             
             for (AppProvider provider : _deploymentManager.getAppProviders())
             {
@@ -377,10 +382,10 @@ public class ServerInstanceWrapper
 
         _deploymentManager.setUseStandardBindings(false);
         List<AppLifeCycle.Binding> deploymentLifeCycleBindings = new ArrayList<AppLifeCycle.Binding>();
-        deploymentLifeCycleBindings.add(new OSGiDeployer());
+        deploymentLifeCycleBindings.add(new OSGiDeployer(this));
         deploymentLifeCycleBindings.add(new StandardStarter());
         deploymentLifeCycleBindings.add(new StandardStopper());
-        deploymentLifeCycleBindings.add(new OSGiUndeployer());
+        deploymentLifeCycleBindings.add(new OSGiUndeployer(this));
         _deploymentManager.setLifeCycleBindings(deploymentLifeCycleBindings);
         
         if (!providerClassNames.contains(BundleWebAppProvider.class.getName()))
@@ -437,54 +442,12 @@ public class ServerInstanceWrapper
             }
         }
     }
+    
 
-    /**
-     * @return The default folder in which the context files of the osgi bundles
-     *         are located and watched. Or null when the system property
-     *         "jetty.osgi.contexts.home" is not defined. If the configuration
-     *         file defines the OSGiAppProvider's context. This will not be
-     *         taken into account.
-     */
-    File getDefaultOSGiContextsHome(File jettyHome)
-    {
-        String jettyContextsHome = System.getProperty("jetty.osgi.contexts.home");
-        if (jettyContextsHome != null)
-        {
-            File contextsHome = new File(jettyContextsHome);
-            if (!contextsHome.exists() || !contextsHome.isDirectory())
-            { 
-                throw new IllegalArgumentException("the ${jetty.osgi.contexts.home} '" 
-                                                   + jettyContextsHome
-                                                   + " must exist and be a folder"); 
-            }
-            return contextsHome;
-        }
-        return new File(jettyHome, "/contexts");
-    }
-
-
-    /**
-     * @return the urls in this string.
-     */
-    private List<URL> extractResources(String propertyValue)
-    {
-        StringTokenizer tokenizer = new StringTokenizer(propertyValue, ",;", false);
-        List<URL> urls = new ArrayList<URL>();
-        while (tokenizer.hasMoreTokens())
-        {
-            String tok = tokenizer.nextToken();
-            try
-            {
-                urls.add(BundleFileLocatorHelperFactory.getFactory().getHelper().getLocalURL(new URL(tok)));
-            }
-            catch (Throwable mfe)
-            {
-                LOG.warn(mfe);
-            }
-        }
-        return urls;
-    }
-
+  
+    
+    
+    /* ------------------------------------------------------------ */
     /**
      * Get the folders that might contain jars for the legacy J2EE shared
      * libraries
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/BundleFileLocatorHelperFactory.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/BundleFileLocatorHelperFactory.java
deleted file mode 100644
index 6c9cf4e..0000000
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/BundleFileLocatorHelperFactory.java
+++ /dev/null
@@ -1,58 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.osgi.boot.internal.webapp;
-
-import org.eclipse.jetty.osgi.boot.utils.BundleFileLocatorHelper;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-
-/**
- * BundleFileLocatorHelperFactory
- *
- * Obtain a helper for locating files based on the bundle.
- */
-public class BundleFileLocatorHelperFactory
-{ 
-    private static final Logger LOG = Log.getLogger(BundleFileLocatorHelperFactory.class);
-    
-    private static BundleFileLocatorHelperFactory _instance = new BundleFileLocatorHelperFactory();
-    
-    private BundleFileLocatorHelperFactory() {}
-    
-    public static BundleFileLocatorHelperFactory getFactory()
-    {
-        return _instance;
-    }
-    
-    public BundleFileLocatorHelper getHelper()
-    {
-        BundleFileLocatorHelper helper = BundleFileLocatorHelper.DEFAULT;
-        try
-        {
-            //see if a fragment has supplied an alternative
-            helper = (BundleFileLocatorHelper) Class.forName(BundleFileLocatorHelper.CLASS_NAME).newInstance();
-        }
-        catch (Throwable t)
-        {
-            LOG.ignore(t);
-        }
-        return helper;
-    }
-
-}
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/BundleWatcher.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/BundleWatcher.java
new file mode 100644
index 0000000..9ae3183
--- /dev/null
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/BundleWatcher.java
@@ -0,0 +1,317 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.osgi.boot.internal.webapp;
+
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.eclipse.jetty.osgi.boot.BundleProvider;
+import org.eclipse.jetty.osgi.boot.OSGiServerConstants;
+import org.eclipse.jetty.osgi.boot.utils.TldBundleDiscoverer;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleEvent;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.ServiceReference;
+import org.osgi.util.tracker.BundleTracker;
+import org.osgi.util.tracker.BundleTrackerCustomizer;
+import org.osgi.util.tracker.ServiceTracker;
+
+/**
+ * BundleWatcher
+ * 
+ * 
+ * Tracks the installation and removal of Bundles in the OSGi environment. Any bundles
+ * that are added are passed to the set of Jetty DeploymentManager providers to see if
+ * the bundle should be deployed as a webapp or ContextHandler into Jetty.
+ * 
+ * @author hmalphettes
+ */
+public class BundleWatcher implements BundleTrackerCustomizer
+{
+    private static final Logger LOG = Log.getLogger(BundleWatcher.class);
+    
+    public static final Collection<TldBundleDiscoverer> JSP_REGISTRATION_HELPERS = new ArrayList<TldBundleDiscoverer>();
+
+
+    public static final String FILTER = "(objectclass=" + BundleProvider.class.getName() + ")";
+    private ServiceTracker _serviceTracker;
+    private BundleTracker _bundleTracker;
+    private boolean _waitForDefaultServer = true;
+    private boolean _defaultServerReady = false;
+    private Bundle _bundle = null;
+    
+ 
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @throws Exception
+     */
+    public BundleWatcher() throws Exception
+    {
+        _bundle = FrameworkUtil.getBundle(this.getClass());
+        //Track all BundleProviders (Jetty DeploymentManager Providers that can deploy bundles)
+        _serviceTracker = new ServiceTracker(_bundle.getBundleContext(), FrameworkUtil.createFilter(FILTER),null);
+        _serviceTracker.open();
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    public boolean isWaitForDefaultServer()
+    {
+        return _waitForDefaultServer;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public void setWaitForDefaultServer(boolean waitForDefaultServer)
+    {
+        _waitForDefaultServer = waitForDefaultServer;
+    }
+
+    
+    /* ------------------------------------------------------------ */
+    public void setBundleTracker (BundleTracker bundleTracker)
+    {
+        _bundleTracker = bundleTracker;
+    }
+
+    
+    /* ------------------------------------------------------------ */
+    public void open () throws Exception
+    {
+        if (_waitForDefaultServer && !_defaultServerReady)
+        {
+            String filter = "(&(objectclass=" + BundleProvider.class.getName() + ")"+
+                    "("+OSGiServerConstants.MANAGED_JETTY_SERVER_NAME+"="+OSGiServerConstants.MANAGED_JETTY_SERVER_DEFAULT_NAME+"))";
+            
+            ServiceTracker defaultServerTracker = new ServiceTracker(_bundle.getBundleContext(), 
+                                                                     FrameworkUtil.createFilter(filter),null)
+            {
+                public Object addingService(ServiceReference reference)
+                {
+                    try
+                    {
+                        Object object = super.addingService(reference);
+                        LOG.debug("Default Jetty Server registered {}", reference);
+                        _defaultServerReady = true;
+                        openBundleTracker();
+                        return object;
+                    }
+                    catch (Exception e)
+                    {
+                        throw new IllegalStateException(e);
+                    }
+                }
+            };
+            defaultServerTracker.open();
+        }
+        else
+            openBundleTracker();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param managedServerName
+     * @return
+     */
+    public Map<ServiceReference, BundleProvider> getDeployers(String managedServerName)
+    {
+        if (managedServerName == null)
+            managedServerName = OSGiServerConstants.MANAGED_JETTY_SERVER_DEFAULT_NAME;
+        
+        Map<ServiceReference, BundleProvider> candidates = new HashMap<ServiceReference, BundleProvider>();
+        
+        ServiceReference[] references = _serviceTracker.getServiceReferences();
+        if (references != null)
+        {
+            for (ServiceReference ref:references)
+            {
+                String name = (String)ref.getProperty(OSGiServerConstants.MANAGED_JETTY_SERVER_NAME);                
+                if (managedServerName.equalsIgnoreCase(name))
+                {
+                    BundleProvider candidate = (BundleProvider)_serviceTracker.getService(ref);
+                    if (candidate != null)
+                        candidates.put(ref, candidate);
+                }
+            }
+        }
+       return candidates;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * A bundle is being added to the <code>BundleTracker</code>.
+     * 
+     * <p>
+     * This method is called before a bundle which matched the search parameters
+     * of the <code>BundleTracker</code> is added to the
+     * <code>BundleTracker</code>. This method should return the object to be
+     * tracked for the specified <code>Bundle</code>. The returned object is
+     * stored in the <code>BundleTracker</code> and is available from the
+     * {@link BundleTracker#getObject(Bundle) getObject} method.
+     * 
+     * @param bundle The <code>Bundle</code> being added to the
+     *            <code>BundleTracker</code>.
+     * @param event The bundle event which caused this customizer method to be
+     *            called or <code>null</code> if there is no bundle event
+     *            associated with the call to this method.
+     * @return The object to be tracked for the specified <code>Bundle</code>
+     *         object or <code>null</code> if the specified <code>Bundle</code>
+     *         object should not be tracked.
+     */
+    public Object addingBundle(Bundle bundle, BundleEvent event)
+    {
+        if (bundle.getState() == Bundle.ACTIVE)
+        {
+            register(bundle);          
+        }
+        else if (bundle.getState() == Bundle.STOPPING)
+        {
+            unregister(bundle);
+        }
+        else
+        {
+            // we should not be called in that state as
+            // we are registered only for ACTIVE and STOPPING
+        }
+        return null;
+    }
+
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * A bundle tracked by the <code>BundleTracker</code> has been modified.
+     * 
+     * <p>
+     * This method is called when a bundle being tracked by the
+     * <code>BundleTracker</code> has had its state modified.
+     * 
+     * @param bundle The <code>Bundle</code> whose state has been modified.
+     * @param event The bundle event which caused this customizer method to be
+     *            called or <code>null</code> if there is no bundle event
+     *            associated with the call to this method.
+     * @param object The tracked object for the specified bundle.
+     */
+    public void modifiedBundle(Bundle bundle, BundleEvent event, Object object)
+    {
+        if (bundle.getState() == Bundle.STOPPING || bundle.getState() == Bundle.ACTIVE)
+        {
+            unregister(bundle);
+        }
+        if (bundle.getState() == Bundle.ACTIVE)
+        {
+            register(bundle);
+        }
+    }
+
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * A bundle tracked by the <code>BundleTracker</code> has been removed.
+     * 
+     * <p>
+     * This method is called after a bundle is no longer being tracked by the
+     * <code>BundleTracker</code>.
+     * 
+     * @param bundle The <code>Bundle</code> that has been removed.
+     * @param event The bundle event which caused this customizer method to be
+     *            called or <code>null</code> if there is no bundle event
+     *            associated with the call to this method.
+     * @param object The tracked object for the specified bundle.
+     */
+    public void removedBundle(Bundle bundle, BundleEvent event, Object object)
+    {
+        unregister(bundle);
+    }
+
+    
+    protected void openBundleTracker()
+    {
+        _bundleTracker.open();
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param bundle
+     * @return true if this bundle can be deployed into Jetty
+     */
+    private boolean register(Bundle bundle)
+    {
+        if (bundle == null)
+            return false;
+
+        //It might be a bundle that is deployable by Jetty.
+        //Use any named Server instance provided, defaulting to the default Server instance if none supplied
+        boolean deployed = false;
+        String serverName = (String)bundle.getHeaders().get(OSGiServerConstants.MANAGED_JETTY_SERVER_NAME);
+        Map<ServiceReference, BundleProvider> candidates = getDeployers(serverName);
+        if (candidates != null)
+        {
+            Iterator<Entry<ServiceReference, BundleProvider>> itor = candidates.entrySet().iterator();
+            while (!deployed && itor.hasNext())
+            {
+                Entry<ServiceReference, BundleProvider> e = itor.next();
+                try
+                {           
+                    deployed = e.getValue().bundleAdded(bundle);
+                }
+                catch (Exception x)
+                {
+                    LOG.warn("Error deploying bundle for jetty context", x);
+                }
+            }
+        }
+
+        return deployed;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param bundle
+     */
+    private void unregister(Bundle bundle)
+    { 
+        boolean undeployed = false;
+        String serverName = (String)bundle.getHeaders().get(OSGiServerConstants.MANAGED_JETTY_SERVER_NAME);    
+        Map<ServiceReference, BundleProvider> candidates = getDeployers(serverName);
+        if (candidates != null)
+        {
+            Iterator<Entry<ServiceReference, BundleProvider>> itor = candidates.entrySet().iterator();
+            while (!undeployed && itor.hasNext())
+            {
+                Entry<ServiceReference, BundleProvider> e = itor.next();
+                try
+                {
+                    undeployed = e.getValue().bundleRemoved(bundle);
+                }
+                catch (Exception x)
+                {
+                    LOG.warn("Error undeploying Bundle representing jetty deployable ", x);
+                }
+            }
+        }
+    }
+}
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/IWebBundleDeployerHelper.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/IWebBundleDeployerHelper.java
deleted file mode 100644
index a6194f0..0000000
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/IWebBundleDeployerHelper.java
+++ /dev/null
@@ -1,88 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.osgi.boot.internal.webapp;
-
-import org.eclipse.jetty.deploy.ContextDeployer;
-import org.eclipse.jetty.server.handler.ContextHandler;
-import org.eclipse.jetty.webapp.WebAppContext;
-import org.osgi.framework.Bundle;
-
-/**
- * Internal interface for the class that deploys a webapp on a server. Used as
- * we migrate from the single instance of the jety server to multiple jetty
- * servers.
- */
-public interface IWebBundleDeployerHelper
-{
-
-    /**
-     * when this property is present, the type of context handler registered is
-     * not known in advance.
-     */
-    public static final String INTERNAL_SERVICE_PROP_UNKNOWN_CONTEXT_HANDLER_TYPE = "unknownContextHandlerType";
-
-    /**
-     * Deploy a new web application on the jetty server.
-     * 
-     * @param bundle The bundle
-     * @param webappFolderPath The path to the root of the webapp. Must be a
-     *            path relative to bundle; either an absolute path.
-     * @param contextPath The context path. Must start with "/"
-     * @param extraClasspath
-     * @param overrideBundleInstallLocation
-     * @param requireTldBundle The list of bundles's symbolic names that contain
-     *            tld files that are required by this WAB.
-     * @param webXmlPath
-     * @param defaultWebXmlPath TODO: parameter description
-     * @return The contexthandler created and started
-     * @throws Exception
-     */
-    public abstract WebAppContext registerWebapplication(Bundle bundle, String webappFolderPath, String contextPath, String extraClasspath,
-                                                         String overrideBundleInstallLocation, String requireTldBundle, String webXmlPath,
-                                                         String defaultWebXmlPath, WebAppContext webAppContext) throws Exception;
-
-    /**
-     * Stop a ContextHandler and remove it from the collection.
-     * 
-     * @see ContextDeployer#undeploy
-     * @param contextHandler
-     * @throws Exception
-     */
-    public abstract void unregister(ContextHandler contextHandler) throws Exception;
-
-    /**
-     * This type of registration relies on jetty's complete context xml file.
-     * Context encompasses jndi and all other things. This makes the definition
-     * of the webapp a lot more self-contained.
-     * 
-     * @param contributor
-     * @param contextFileRelativePath
-     * @param extraClasspath
-     * @param overrideBundleInstallLocation
-     * @param requireTldBundle The list of bundles'symbolic name that contain
-     *            tld files for this webapp.
-     * @param handler the context handler passed in the server reference that
-     *            will be configured, deployed and started.
-     * @return The contexthandler created and started
-     * @throws Exception
-     */
-    public abstract ContextHandler registerContext(Bundle contributor, String contextFileRelativePath, String extraClasspath,
-                                                   String overrideBundleInstallLocation, String requireTldBundle, ContextHandler handler) throws Exception;
-
-}
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/JettyContextHandlerServiceTracker.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/JettyContextHandlerServiceTracker.java
deleted file mode 100644
index 2e0c9cb..0000000
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/JettyContextHandlerServiceTracker.java
+++ /dev/null
@@ -1,211 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.osgi.boot.internal.webapp;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-
-import org.eclipse.jetty.osgi.boot.BundleWebAppProvider;
-import org.eclipse.jetty.osgi.boot.JettyBootstrapActivator;
-import org.eclipse.jetty.osgi.boot.OSGiServerConstants;
-import org.eclipse.jetty.osgi.boot.OSGiWebappConstants;
-import org.eclipse.jetty.osgi.boot.ServiceProvider;
-import org.eclipse.jetty.osgi.boot.internal.serverfactory.DefaultJettyAtJettyHomeHelper;
-import org.eclipse.jetty.osgi.boot.internal.serverfactory.IManagedJettyServerRegistry;
-import org.eclipse.jetty.osgi.boot.internal.serverfactory.ServerInstanceWrapper;
-import org.eclipse.jetty.server.handler.ContextHandler;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.util.Scanner;
-import org.eclipse.jetty.webapp.WebAppContext;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.FrameworkUtil;
-import org.osgi.framework.ServiceEvent;
-import org.osgi.framework.ServiceListener;
-import org.osgi.framework.ServiceReference;
-import org.osgi.util.tracker.ServiceTracker;
-
-/**
- * JettyContextHandlerServiceTracker
- * 
- * When a {@link ContextHandler} is activated as an osgi service we find a jetty deployer
- * for it. The ContextHandler could be either a WebAppContext or any other derivative of 
- * ContextHandler.
- * 
- * ContextHandlers and WebApps can also be deployed into jetty without creating them as
- * osgi services. Instead, they can be deployed via manifest headers inside bundles. See
- * {@link WebBundleTrackerCustomizer}.
- */
-public class JettyContextHandlerServiceTracker implements ServiceListener
-{
-    private static Logger LOG = Log.getLogger(JettyContextHandlerServiceTracker.class);
-    
-    public static final String FILTER = "(objectclass=" + ServiceProvider.class.getName() + ")";
-
-    
-    //track all instances of deployers of webapps as bundles       
-    ServiceTracker _serviceTracker;
-    
-    
-     
-    /* ------------------------------------------------------------ */
-    /**
-     * @param registry
-     */
-    public JettyContextHandlerServiceTracker() throws Exception
-    {
-        //track all instances of deployers of webapps
-        Bundle myBundle = FrameworkUtil.getBundle(this.getClass());
-        _serviceTracker = new ServiceTracker(myBundle.getBundleContext(), FrameworkUtil.createFilter(FILTER),null);
-        _serviceTracker.open();
-    }
-
-
-   
-    /* ------------------------------------------------------------ */
-    /**
-     * @param managedServerName
-     * @return
-     */
-    public Map<ServiceReference, ServiceProvider> getDeployers(String managedServerName)
-    {
-        if (managedServerName == null)
-            managedServerName = OSGiServerConstants.MANAGED_JETTY_SERVER_DEFAULT_NAME;
-        
-        Map<ServiceReference, ServiceProvider> candidates = new HashMap<ServiceReference, ServiceProvider>();
-        
-        ServiceReference[] references = _serviceTracker.getServiceReferences();
-        if (references != null)
-        {
-            for (ServiceReference ref:references)
-            {
-                String name = (String)ref.getProperty(OSGiServerConstants.MANAGED_JETTY_SERVER_NAME);
-                if (managedServerName.equalsIgnoreCase(name))
-                {
-                    ServiceProvider candidate = (ServiceProvider)_serviceTracker.getService(ref);
-                    if (candidate != null)
-                        candidates.put(ref, candidate);
-                }
-            }
-        }
-       return candidates;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Receives notification that a service has had a lifecycle change.
-     * 
-     * @param ev The <code>ServiceEvent</code> object.
-     */
-    /** 
-     * @see org.osgi.framework.ServiceListener#serviceChanged(org.osgi.framework.ServiceEvent)
-     */
-    public void serviceChanged(ServiceEvent ev)
-    {
-        ServiceReference sr = ev.getServiceReference();
-        switch (ev.getType())
-        {
-            case ServiceEvent.MODIFIED:
-            case ServiceEvent.UNREGISTERING:
-            {
-                BundleContext context = FrameworkUtil.getBundle(JettyBootstrapActivator.class).getBundleContext();
-                ContextHandler contextHandler = (ContextHandler) context.getService(sr);
-
-                //if this was not a service that another of our deployers may have deployed (in which case they will undeploy it)
-                String watermark = (String)sr.getProperty(OSGiWebappConstants.WATERMARK);
-
-                //Get a jetty deployer targetted to the named server instance, or the default one if not named
-                //The individual deployer  will decide if it can remove the context or not
-                String serverName = (String)sr.getProperty(OSGiServerConstants.MANAGED_JETTY_SERVER_NAME);    
-                Map<ServiceReference, ServiceProvider> candidates = getDeployers(serverName);
-                if (candidates != null)
-                {
-                    boolean removed = false;
-                    Iterator<Entry<ServiceReference, ServiceProvider>> itor = candidates.entrySet().iterator();
-                    while (!removed && itor.hasNext())
-                    {
-                        Entry<ServiceReference, ServiceProvider> e = itor.next();
-                        try
-                        {
-                            removed = e.getValue().serviceRemoved(sr, contextHandler);
-                        }
-                        catch (Exception x)
-                        {
-                            LOG.warn("Error undeploying service representing jetty context ", x);
-                        }
-                    }
-                }
-            }
-            if (ev.getType() == ServiceEvent.UNREGISTERING)
-            {
-                break;
-            }
-            else
-            {
-                // modified, meaning: we reload it. now that we stopped it;
-                // we can register it.
-            }
-            case ServiceEvent.REGISTERED:
-            {
-                Bundle contributor = sr.getBundle();
-                BundleContext context = FrameworkUtil.getBundle(JettyBootstrapActivator.class).getBundleContext();
-                ContextHandler contextHandler = (ContextHandler) context.getService(sr);
-                if (contextHandler.getServer() != null)
-                {
-                    // is configured elsewhere.
-                    return;
-                }
-                String watermark = (String)sr.getProperty(OSGiWebappConstants.WATERMARK);
-                if (watermark != null && !"".equals(watermark))
-                    return; //one of our deployers just registered the context as an OSGi service, so we can ignore it
-                
-                //Get a jetty deployer targetted to the named server instance, or the default one if not named
-                String serverName = (String)sr.getProperty(OSGiServerConstants.MANAGED_JETTY_SERVER_NAME);    
-                Map<ServiceReference, ServiceProvider> candidates = getDeployers(serverName);
-                if (candidates != null)
-                {
-                    boolean added = false;
-                    Iterator<Entry<ServiceReference, ServiceProvider>> itor = candidates.entrySet().iterator();
-                    while (!added && itor.hasNext())
-                    {
-                        Entry<ServiceReference, ServiceProvider> e = itor.next();
-                        try
-                        {
-                            added = e.getValue().serviceAdded(sr, contextHandler);
-                            if (added && LOG.isDebugEnabled())
-                                LOG.debug("Provider "+e.getValue()+" deployed "+contextHandler);
-                        }
-                        catch (Exception x)
-                        {
-                            LOG.warn("Error deploying service representing jetty context", x);
-                        }
-                    }
-                }
-                break;
-            }
-        }
-    }
-}
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/LibExtClassLoaderHelper.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/LibExtClassLoaderHelper.java
index a620a11..d10b6f5 100644
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/LibExtClassLoaderHelper.java
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/LibExtClassLoaderHelper.java
@@ -33,6 +33,9 @@ import java.util.Set;
 import org.eclipse.jetty.server.Server;
 
 /**
+ * LibExtClassLoaderHelper
+ * 
+ * 
  * Helper to create a URL class-loader with the jars inside
  * ${jetty.home}/lib/ext and ${jetty.home}/resources. In an ideal world, every
  * library is an OSGi bundle that does loads nicely. To support standard jars or
@@ -40,57 +43,40 @@ import org.eclipse.jetty.server.Server;
  * inserting the jars in the usual jetty/lib/ext folders in the proper classpath
  * for the webapps.
  * <p>
- * Also the folder resources typically contains central configuration files for
- * things like: log config and others. We enable fragments to register classes
- * that are called back and passed those resources to do what they need to do.
+ * The drawback is that those jars will not be available in the OSGi
+ * classloader.
  * </p>
  * <p>
- * For example the test-jndi webapplication depends on derby, derbytools,
- * atomikos none of them are osgi bundles. we can either re-package them or we
- * can place them in the usual lib/ext. <br/>
- * In fact jasper's jsp libraries should maybe place in lib/ext too.
- * </p>
- * <p>
- * The drawback is that those libraries will not be available in the OSGi
- * classloader. Note that we could have setup those jars as embedded jars of the
- * current bundle. However, we would need to know in advance what are those jars
- * which was not acceptable. Also having those jars in a URLClassLoader seem to
- * be required for some cases. For example jaspers' TldLocationsCache (replaced
- * by TldScanner for servlet-3.0). <br/>
- * Also all the dependencies of those libraries must be resolvable directly from
- * the JettyBootstrapActivator bundle as it is set as the parent classloader. For
- * example: if atomikos is placed in lib/ext it will work if and only if
- * JettyBootstrapActivator import the necessary packages from javax.naming*,
- * javax.transaction*, javax.mail* etc Most of the common cases of javax are
- * added as optional import packages into jetty bootstrapper plugin. When there
- * are not covered: please make a request or create a fragment or register a
- * bundle with a buddy-policy onto the jetty bootstrapper..
- * </p>
- * <p>
- * Alternatives to placing jars in lib/ext
+ * Alternatives to placing jars in lib/ext:
  * <ol>
- * <li>Bundle the jars in an osgi bundle. Have the webapp(s) that context
- * depends on them depend on that bundle. Things will go well for jetty.</li>
+ * <li>Bundle the jars in an osgi bundle. Have the webapp(s) that need these jars
+ * depend on that bundle.</li>
  * <li>Bundle those jars in an osgi bundle-fragment that targets the
  * jetty-bootstrap bundle</li>
  * <li>Use equinox Buddy-Policy: register a buddy of the jetty bootstrapper
- * bundle. (least favorite: it will work only on equinox)</li>
+ * bundle. (Note: it will work only on equinox)</li>
  * </ol>
  * </p>
  */
 public class LibExtClassLoaderHelper
 {
-
+    /* ------------------------------------------------------------ */
     /**
-     * Class called back
+     * IFilesInJettyHomeResourcesProcessor
+     * 
+     * Interface for callback impls
      */
     public interface IFilesInJettyHomeResourcesProcessor
     {
         void processFilesInResourcesFolder(File jettyHome, Map<String, File> filesInResourcesFolder);
     }
 
-    public static Set<IFilesInJettyHomeResourcesProcessor> registeredFilesInJettyHomeResourcesProcessors = new HashSet<IFilesInJettyHomeResourcesProcessor>();
+    
+    
+    public static final Set<IFilesInJettyHomeResourcesProcessor> registeredFilesInJettyHomeResourcesProcessors = new HashSet<IFilesInJettyHomeResourcesProcessor>();
 
+    
+    /* ------------------------------------------------------------ */
     /**
      * @param server
      * @return a url classloader with the jars of resources, lib/ext and the
@@ -98,7 +84,7 @@ public class LibExtClassLoaderHelper
      *         is the JettyBootStrapper (an osgi classloader.
      * @throws MalformedURLException
      */
-    public static ClassLoader createLibEtcClassLoader(File jettyHome, Server server, ClassLoader parentClassLoader) throws MalformedURLException
+    public static ClassLoader createLibEtcClassLoader(File jettyHome, ClassLoader parentClassLoader) throws MalformedURLException
     {
         if (jettyHome == null) { return parentClassLoader; }
         ArrayList<URL> urls = new ArrayList<URL>();
@@ -145,6 +131,8 @@ public class LibExtClassLoaderHelper
         return new URLClassLoader(urls.toArray(new URL[urls.size()]), parentClassLoader);
     }
 
+    
+    /* ------------------------------------------------------------ */
     /**
      * @param server
      * @return a url classloader with the jars of resources, lib/ext and the
@@ -153,7 +141,7 @@ public class LibExtClassLoaderHelper
      *         extra jars to insert, then just return the parentClassLoader.
      * @throws MalformedURLException
      */
-    public static ClassLoader createLibExtClassLoader(List<File> jarsContainerOrJars, List<URL> otherJarsOrFolder, Server server, ClassLoader parentClassLoader) 
+    public static ClassLoader createLibExtClassLoader(List<File> jarsContainerOrJars, List<URL> otherJarsOrFolder, ClassLoader parentClassLoader) 
     throws MalformedURLException
     {
         if (jarsContainerOrJars == null && otherJarsOrFolder == null) { return parentClassLoader; }
@@ -188,6 +176,7 @@ public class LibExtClassLoaderHelper
         return new URLClassLoader(urls.toArray(new URL[urls.size()]), parentClassLoader);
     }
 
+    /* ------------------------------------------------------------ */
     /**
      * When we find files typically used for central logging configuration we do
      * what it takes in this method to do what the user expects. Without
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/OSGiWebappClassLoader.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/OSGiWebappClassLoader.java
index 6326755..2f4b19b 100644
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/OSGiWebappClassLoader.java
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/OSGiWebappClassLoader.java
@@ -33,7 +33,6 @@ import java.util.jar.JarFile;
 
 import javax.servlet.http.HttpServlet;
 
-import org.eclipse.jetty.osgi.boot.utils.BundleClassLoaderHelper;
 import org.eclipse.jetty.osgi.boot.utils.BundleClassLoaderHelperFactory;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
@@ -44,19 +43,21 @@ import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleReference;
 
 /**
- * Extends the webappclassloader to insert the classloader provided by the osgi
- * bundle at the same level than any other jars palced in the webappclassloader.
+ * OSGiWebappClassLoader
+ * 
+ * 
+ * Extends the webapp classloader to also use the classloader of the Bundle defining the webapp.
  */
 public class OSGiWebappClassLoader extends WebAppClassLoader implements BundleReference
 {
 
-    private Logger __logger = Log.getLogger(OSGiWebappClassLoader.class.getName().toString());
+    private static final Logger __logger = Log.getLogger(OSGiWebappClassLoader.class.getName());
 
     /**
      * when a logging framework is setup in the osgi classloaders, it can access
      * this and register the classes that must not be found in the jar.
      */
-    public static Set<String> JAR_WITH_SUCH_CLASS_MUST_BE_EXCLUDED = new HashSet<String>();
+    public static final Set<String> JAR_WITH_SUCH_CLASS_MUST_BE_EXCLUDED = new HashSet<String>();
 
     public static void addClassThatIdentifiesAJarThatMustBeRejected(Class<?> zclass)
     {
@@ -79,10 +80,9 @@ public class OSGiWebappClassLoader extends WebAppClassLoader implements BundleRe
 
     private boolean _lookInOsgiFirst = true;
 
-    private Set<String> _libsAlreadyInManifest = new HashSet<String>();
-
+    /* ------------------------------------------------------------ */
     /**
-     * @param parent The parent classloader. In this case
+     * @param parent The parent classloader.
      * @param context The WebAppContext
      * @param contributor The bundle that defines this web-application.
      * @throws IOException
@@ -94,7 +94,16 @@ public class OSGiWebappClassLoader extends WebAppClassLoader implements BundleRe
         _contributor = contributor;
         _osgiBundleClassLoader = BundleClassLoaderHelperFactory.getFactory().getHelper().getBundleClassLoader(contributor);
     }
+    
+    
+    
+    @Override
+    public Class<?> loadClass(String name) throws ClassNotFoundException
+    {
+        return super.loadClass(name);
+    }
 
+    /* ------------------------------------------------------------ */
     /**
      * Returns the <code>Bundle</code> that defined this web-application.
      * 
@@ -106,17 +115,7 @@ public class OSGiWebappClassLoader extends WebAppClassLoader implements BundleRe
         return _contributor;
     }
 
-    /**
-     * Reads the manifest. If the manifest is already configured to loads a few
-     * libs we should not add them to the classpath of the webapp. Not really
-     * important as we resolve classes through the osgi classloader first and
-     * then default on the libs of the webapp.
-     */
-    private void computeLibsAlreadyInOSGiClassLoader()
-    {
-        // TODO
-    }
-
+    /* ------------------------------------------------------------ */
     @Override
     public Enumeration<URL> getResources(String name) throws IOException
     {
@@ -131,7 +130,10 @@ public class OSGiWebappClassLoader extends WebAppClassLoader implements BundleRe
             return Collections.enumeration(toList(urls, osgiUrls));
         }
     }
-
+    
+    
+    
+    /* ------------------------------------------------------------ */
     @Override
     public URL getResource(String name)
     {
@@ -146,7 +148,10 @@ public class OSGiWebappClassLoader extends WebAppClassLoader implements BundleRe
             return url != null ? url : _osgiBundleClassLoader.getResource(name);
         }
     }
-
+    
+    
+    
+    /* ------------------------------------------------------------ */
     private List<URL> toList(Enumeration<URL> e, Enumeration<URL> e2)
     {
         List<URL> list = new ArrayList<URL>();
@@ -157,9 +162,8 @@ public class OSGiWebappClassLoader extends WebAppClassLoader implements BundleRe
         return list;
     }
 
-    /**
-     * 
-     */
+    
+    /* ------------------------------------------------------------ */
     protected Class<?> findClass(String name) throws ClassNotFoundException
     {
         try
@@ -178,7 +182,10 @@ public class OSGiWebappClassLoader extends WebAppClassLoader implements BundleRe
             }
         }
     }
-
+    
+    
+    
+    /* ------------------------------------------------------------ */
     /**
      * Parse the classpath ourselves to be able to filter things. This is a
      * derivative work of the super class
@@ -207,6 +214,8 @@ public class OSGiWebappClassLoader extends WebAppClassLoader implements BundleRe
 
     }
 
+    
+    /* ------------------------------------------------------------ */
     /**
      * @param lib
      * @return true if the lib should be included in the webapp classloader.
@@ -255,6 +264,8 @@ public class OSGiWebappClassLoader extends WebAppClassLoader implements BundleRe
 
     private static Field _contextField;
 
+    
+    /* ------------------------------------------------------------ */
     /**
      * In the case of the generation of a webapp via a jetty context file we
      * need a proper classloader to setup the app before we have the
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/ServiceWatcher.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/ServiceWatcher.java
new file mode 100644
index 0000000..7838b4f
--- /dev/null
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/ServiceWatcher.java
@@ -0,0 +1,230 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.osgi.boot.internal.webapp;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.eclipse.jetty.osgi.boot.JettyBootstrapActivator;
+import org.eclipse.jetty.osgi.boot.OSGiServerConstants;
+import org.eclipse.jetty.osgi.boot.OSGiWebappConstants;
+import org.eclipse.jetty.osgi.boot.ServiceProvider;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.ServiceReference;
+import org.osgi.util.tracker.ServiceTracker;
+import org.osgi.util.tracker.ServiceTrackerCustomizer;
+
+/**
+ * ServiceWatcher
+ * 
+ * When a {@link ContextHandler} is activated as an osgi service we find a jetty deployer
+ * for it. The ContextHandler could be either a WebAppContext or any other derivative of 
+ * ContextHandler.
+ * 
+ * ContextHandlers and WebApps can also be deployed into jetty without creating them as
+ * osgi services. Instead, they can be deployed via manifest headers inside bundles. See
+ * {@link BundleWatcher}.
+ */
+public class ServiceWatcher implements ServiceTrackerCustomizer
+{
+    private static Logger LOG = Log.getLogger(ServiceWatcher.class);
+    
+    public static final String FILTER = "(objectclass=" + ServiceProvider.class.getName() + ")";
+
+    
+    //track all instances of deployers of webapps as bundles       
+    ServiceTracker _serviceTracker;
+    
+    
+     
+    /* ------------------------------------------------------------ */
+    /**
+     * @param registry
+     */
+    public ServiceWatcher() throws Exception
+    {
+        //track all instances of deployers of webapps
+        Bundle myBundle = FrameworkUtil.getBundle(this.getClass());
+        _serviceTracker = new ServiceTracker(myBundle.getBundleContext(), FrameworkUtil.createFilter(FILTER),null);
+        _serviceTracker.open();
+    }
+
+
+   
+    /* ------------------------------------------------------------ */
+    /**
+     * @param managedServerName
+     * @return
+     */
+    public Map<ServiceReference, ServiceProvider> getDeployers(String managedServerName)
+    {
+        if (managedServerName == null)
+            managedServerName = OSGiServerConstants.MANAGED_JETTY_SERVER_DEFAULT_NAME;
+        
+        Map<ServiceReference, ServiceProvider> candidates = new HashMap<ServiceReference, ServiceProvider>();
+        
+        ServiceReference[] references = _serviceTracker.getServiceReferences();
+        if (references != null)
+        {
+            for (ServiceReference ref:references)
+            {
+                String name = (String)ref.getProperty(OSGiServerConstants.MANAGED_JETTY_SERVER_NAME);
+                if (managedServerName.equalsIgnoreCase(name))
+                {
+                    ServiceProvider candidate = (ServiceProvider)_serviceTracker.getService(ref);
+                    if (candidate != null)
+                        candidates.put(ref, candidate);
+                }
+            }
+        }
+       return candidates;
+    }
+
+    
+
+
+    /* ------------------------------------------------------------ */
+    /** 
+     * A Service that is a ContextHandler is detected.
+     * @see org.osgi.util.tracker.ServiceTrackerCustomizer#addingService(org.osgi.framework.ServiceReference)
+     */
+    @Override
+    public Object addingService(ServiceReference reference)
+    {
+        BundleContext context = FrameworkUtil.getBundle(JettyBootstrapActivator.class).getBundleContext();
+        ContextHandler contextHandler = (ContextHandler) context.getService(reference); 
+        return addService(context, contextHandler, reference);
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /** 
+     * A Service that is a ContextHandler has been modified. We
+     * undeploy and then redeploy the ContextHandler.
+     * 
+     * @see org.osgi.util.tracker.ServiceTrackerCustomizer#modifiedService(org.osgi.framework.ServiceReference, java.lang.Object)
+     */
+    @Override
+    public void modifiedService(ServiceReference reference, Object service)
+    {
+        BundleContext context = FrameworkUtil.getBundle(JettyBootstrapActivator.class).getBundleContext();
+        ContextHandler contextHandler = (ContextHandler) context.getService(reference);
+        removeService (context, contextHandler, reference);
+        addService (context, contextHandler, reference);
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /** 
+     * A Service that is a ContextHandler is being removed.
+     * @see org.osgi.util.tracker.ServiceTrackerCustomizer#removedService(org.osgi.framework.ServiceReference, java.lang.Object)
+     */
+    @Override
+    public void removedService(ServiceReference reference, Object service)
+    {
+        BundleContext context = FrameworkUtil.getBundle(JettyBootstrapActivator.class).getBundleContext();
+        ContextHandler contextHandler = (ContextHandler) context.getService(reference); 
+       removeService (context, contextHandler, reference);
+    }
+    
+    
+    
+    /* ------------------------------------------------------------ */
+    /** Deploy ContextHandler that is a Service.
+     * 
+     * @param reference
+     * @return
+     */
+    public Object addService (BundleContext context, ContextHandler contextHandler, ServiceReference reference)
+    {
+        if (contextHandler.getServer() != null)
+        {
+            // is configured elsewhere.
+            return context.getService(reference);
+        }
+        String watermark = (String)reference.getProperty(OSGiWebappConstants.WATERMARK);
+        if (watermark != null && !"".equals(watermark))
+            return context.getService(reference); //one of our deployers just registered the context as an OSGi service, so we can ignore it
+        
+        //Get a jetty deployer targetted to the named server instance, or the default one if not named
+        String serverName = (String)reference.getProperty(OSGiServerConstants.MANAGED_JETTY_SERVER_NAME);    
+        Map<ServiceReference, ServiceProvider> candidates = getDeployers(serverName);
+        if (candidates != null)
+        {
+            boolean added = false;
+            Iterator<Entry<ServiceReference, ServiceProvider>> itor = candidates.entrySet().iterator();
+            while (!added && itor.hasNext())
+            {
+                Entry<ServiceReference, ServiceProvider> e = itor.next();
+                try
+                {
+                    added = e.getValue().serviceAdded(reference, contextHandler);
+                    if (added && LOG.isDebugEnabled())
+                        LOG.debug("Provider "+e.getValue()+" deployed "+contextHandler);
+                }
+                catch (Exception x)
+                {
+                    LOG.warn("Error deploying service representing jetty context", x);
+                }
+            }
+        }
+        return context.getService(reference);
+    }
+    
+    
+    
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Undeploy a ContextHandler that is a Service.
+     * 
+     * @param reference
+     */
+    public void removeService (BundleContext context, ContextHandler contextHandler, ServiceReference reference)
+    {
+        //Get a jetty deployer targetted to the named server instance, or the default one if not named
+        //The individual deployer  will decide if it can remove the context or not
+        String serverName = (String)reference.getProperty(OSGiServerConstants.MANAGED_JETTY_SERVER_NAME);    
+        Map<ServiceReference, ServiceProvider> candidates = getDeployers(serverName);
+        if (candidates != null)
+        {
+            boolean removed = false;
+            Iterator<Entry<ServiceReference, ServiceProvider>> itor = candidates.entrySet().iterator();
+            while (!removed && itor.hasNext())
+            {
+                Entry<ServiceReference, ServiceProvider> e = itor.next();
+                try
+                {
+                    removed = e.getValue().serviceRemoved(reference, contextHandler);
+                }
+                catch (Exception x)
+                {
+                    LOG.warn("Error undeploying service representing jetty context ", x);
+                }
+            }
+        }
+    }
+}
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/WebBundleTrackerCustomizer.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/WebBundleTrackerCustomizer.java
deleted file mode 100644
index 104c674..0000000
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/WebBundleTrackerCustomizer.java
+++ /dev/null
@@ -1,250 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.osgi.boot.internal.webapp;
-
-
-import java.util.ArrayList;
-import java.util.Collection;
-
-import org.eclipse.jetty.osgi.boot.BundleProvider;
-import org.eclipse.jetty.osgi.boot.OSGiServerConstants;
-import org.eclipse.jetty.osgi.boot.utils.WebappRegistrationCustomizer;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleEvent;
-import org.osgi.framework.FrameworkUtil;
-import org.osgi.framework.ServiceReference;
-import org.osgi.util.tracker.BundleTracker;
-import org.osgi.util.tracker.BundleTrackerCustomizer;
-import org.osgi.util.tracker.ServiceTracker;
-
-/**
- * WebBundleTrackerCustomizer
- * 
- * 
- * Support bundles that declare a webpp or context directly through headers in their
- * manifest. They will be deployed to the default jetty Server instance.
- * 
- * If you wish to deploy a context or webapp to a different jetty Server instance,
- * register your context/webapp as an osgi service, and set the property OSGiServerConstants.MANAGED_JETTY_SERVER_NAME
- * with the name of the Server instance you wish to depoy to.
- * 
- * @author hmalphettes
- */
-public class WebBundleTrackerCustomizer implements BundleTrackerCustomizer
-{
-    private static final Logger LOG = Log.getLogger(WebBundleTrackerCustomizer.class);
-    
-    public static Collection<WebappRegistrationCustomizer> JSP_REGISTRATION_HELPERS = new ArrayList<WebappRegistrationCustomizer>();
-    public static final String FILTER = "(&(objectclass=" + BundleProvider.class.getName() + ")"+
-                                          "("+OSGiServerConstants.MANAGED_JETTY_SERVER_NAME+"="+OSGiServerConstants.MANAGED_JETTY_SERVER_DEFAULT_NAME+"))";
-
-    private ServiceTracker _serviceTracker;
-    private BundleTracker _bundleTracker;
-    
-    /* ------------------------------------------------------------ */
-    /**
-     * @throws Exception
-     */
-    public WebBundleTrackerCustomizer ()
-    throws Exception
-    {
-        Bundle myBundle = FrameworkUtil.getBundle(this.getClass());
-        
-        //track all instances of deployers of webapps/contexts as bundles       
-        _serviceTracker = new ServiceTracker(myBundle.getBundleContext(), FrameworkUtil.createFilter(FILTER),null) {
-            public Object addingService(ServiceReference reference) {
-                Object object = super.addingService(reference);
-                LOG.debug("Deployer registered {}", reference);
-                openBundleTracker();
-                return object;
-            }
-        };
-        _serviceTracker.open();
-
-    }
-    
-    
-    /* ------------------------------------------------------------ */
-    /**
-     * A bundle is being added to the <code>BundleTracker</code>.
-     * 
-     * <p>
-     * This method is called before a bundle which matched the search parameters
-     * of the <code>BundleTracker</code> is added to the
-     * <code>BundleTracker</code>. This method should return the object to be
-     * tracked for the specified <code>Bundle</code>. The returned object is
-     * stored in the <code>BundleTracker</code> and is available from the
-     * {@link BundleTracker#getObject(Bundle) getObject} method.
-     * 
-     * @param bundle The <code>Bundle</code> being added to the
-     *            <code>BundleTracker</code>.
-     * @param event The bundle event which caused this customizer method to be
-     *            called or <code>null</code> if there is no bundle event
-     *            associated with the call to this method.
-     * @return The object to be tracked for the specified <code>Bundle</code>
-     *         object or <code>null</code> if the specified <code>Bundle</code>
-     *         object should not be tracked.
-     */
-    public Object addingBundle(Bundle bundle, BundleEvent event)
-    {
-        if (bundle.getState() == Bundle.ACTIVE)
-        {
-            register(bundle);          
-        }
-        else if (bundle.getState() == Bundle.STOPPING)
-        {
-            unregister(bundle);
-        }
-        else
-        {
-            // we should not be called in that state as
-            // we are registered only for ACTIVE and STOPPING
-        }
-        return null;
-    }
-
-    
-    /* ------------------------------------------------------------ */
-    /**
-     * A bundle tracked by the <code>BundleTracker</code> has been modified.
-     * 
-     * <p>
-     * This method is called when a bundle being tracked by the
-     * <code>BundleTracker</code> has had its state modified.
-     * 
-     * @param bundle The <code>Bundle</code> whose state has been modified.
-     * @param event The bundle event which caused this customizer method to be
-     *            called or <code>null</code> if there is no bundle event
-     *            associated with the call to this method.
-     * @param object The tracked object for the specified bundle.
-     */
-    public void modifiedBundle(Bundle bundle, BundleEvent event, Object object)
-    {
-        // nothing the web-bundle was already track. something changed.
-        // we only reload the webapps if the bundle is stopped and restarted.
-        if (bundle.getState() == Bundle.STOPPING || bundle.getState() == Bundle.ACTIVE)
-        {
-            unregister(bundle);
-        }
-        if (bundle.getState() == Bundle.ACTIVE)
-        {
-            register(bundle);
-        }
-    }
-
-    
-    /* ------------------------------------------------------------ */
-    /**
-     * A bundle tracked by the <code>BundleTracker</code> has been removed.
-     * 
-     * <p>
-     * This method is called after a bundle is no longer being tracked by the
-     * <code>BundleTracker</code>.
-     * 
-     * @param bundle The <code>Bundle</code> that has been removed.
-     * @param event The bundle event which caused this customizer method to be
-     *            called or <code>null</code> if there is no bundle event
-     *            associated with the call to this method.
-     * @param object The tracked object for the specified bundle.
-     */
-    public void removedBundle(Bundle bundle, BundleEvent event, Object object)
-    {
-        unregister(bundle);
-    }
-
-    
-    /* ------------------------------------------------------------ */
-    /**
-     * @param bundle
-     * @return true if this bundle in indeed a web-bundle.
-     */
-    private boolean register(Bundle bundle)
-    {
-        if (bundle == null)
-            return false;
-
-        //It might be a bundle that we can deploy to our default jetty server instance
-        boolean deployed = false;
-        Object[] deployers = _serviceTracker.getServices();
-        if (deployers != null)
-        {
-            int i=0;
-            while (!deployed && i<deployers.length)
-            {
-
-                BundleProvider p = (BundleProvider)deployers[i];
-                try
-                {
-                    deployed = p.bundleAdded(bundle);
-                }
-                catch (Exception x)
-                {
-                    LOG.warn("Error deploying bundle for jetty context", x);
-                }
-                i++;
-            }
-        }
-
-        return deployed;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param bundle
-     */
-    private void unregister(Bundle bundle)
-    { 
-        Object[] deployers = _serviceTracker.getServices();
-        boolean undeployed = false;
-        if (deployers != null)
-        {
-            int i=0;
-            while (!undeployed && i<deployers.length)
-            {
-                try
-                {
-                    undeployed = ((BundleProvider)deployers[i++]).bundleRemoved(bundle);
-                }
-                catch (Exception x)
-                {
-                    LOG.warn("Error undeploying bundle for jetty context", x);
-                }
-            }
-        }
-    }
-
-    public void setAndOpenWebBundleTracker(BundleTracker bundleTracker) {
-        if(_bundleTracker == null) {
-            _bundleTracker = bundleTracker;
-            LOG.debug("Bundle tracker is set");
-            openBundleTracker();
-        }
-    }
-
-    private void openBundleTracker() {
-        if(_bundleTracker != null && _serviceTracker.getServices() != null &&
-                _serviceTracker.getServices().length > 0) {
-            _bundleTracker.open();
-            LOG.debug("Bundle tracker has been opened");
-        }
-    }
-
-}
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/BundleClassLoaderHelper.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/BundleClassLoaderHelper.java
index bb59b0c..421226d 100644
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/BundleClassLoaderHelper.java
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/BundleClassLoaderHelper.java
@@ -22,6 +22,10 @@ import org.eclipse.jetty.osgi.boot.utils.internal.DefaultBundleClassLoaderHelper
 import org.osgi.framework.Bundle;
 
 /**
+ * 
+ * BundleClassLoaderHelper
+ * 
+ * 
  * Is there a clean OSGi way to go from the Bundle object to the classloader of
  * the Bundle ? You can certainly take a class inside the bundle and get the
  * bundle's classloader that way. Getting the classloader directly from the
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/BundleClassLoaderHelperFactory.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/BundleClassLoaderHelperFactory.java
index fa1c9ce..9ca4b00 100644
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/BundleClassLoaderHelperFactory.java
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/BundleClassLoaderHelperFactory.java
@@ -32,15 +32,19 @@ public class BundleClassLoaderHelperFactory
     
     private static BundleClassLoaderHelperFactory _instance = new BundleClassLoaderHelperFactory();
     
+    
+    /* ------------------------------------------------------------ */
     public static BundleClassLoaderHelperFactory getFactory()
     {
         return _instance;
     }
     
+    /* ------------------------------------------------------------ */
     private BundleClassLoaderHelperFactory()
     {
     }
     
+    /* ------------------------------------------------------------ */
     public BundleClassLoaderHelper getHelper()
     {
         //use the default
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/BundleFileLocatorHelper.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/BundleFileLocatorHelper.java
index 8a1a8f2..68add7c 100644
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/BundleFileLocatorHelper.java
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/BundleFileLocatorHelper.java
@@ -19,6 +19,7 @@
 package org.eclipse.jetty.osgi.boot.utils;
 
 import java.io.File;
+import java.io.IOException;
 import java.net.URL;
 import java.util.Enumeration;
 
@@ -26,6 +27,9 @@ import org.eclipse.jetty.osgi.boot.utils.internal.DefaultFileLocatorHelper;
 import org.osgi.framework.Bundle;
 
 /**
+ * BundleFileLocatorHelper
+ * 
+ * 
  * From a bundle to its location on the filesystem. Assumes the bundle is not a
  * jar.
  * 
@@ -100,7 +104,7 @@ public interface BundleFileLocatorHelper
      * 
      * @return a URL to the bundle entry that uses a common protocol
      */
-    public URL getLocalURL(URL url);
+    public URL getLocalURL(URL url) throws Exception;
     
     /**
      * Only useful for equinox: on felix we get the file:// url already. Other
@@ -113,7 +117,9 @@ public interface BundleFileLocatorHelper
      * @return a URL to the content of the bundle entry that uses the file:
      *         protocol
      *         </p>
+     * @throws IOException 
+     * @throws Exception 
      */
-    public URL getFileURL(URL url);
+    public URL getFileURL(URL url) throws Exception;
 
 }
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/BundleFileLocatorHelperFactory.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/BundleFileLocatorHelperFactory.java
new file mode 100644
index 0000000..e2c7f00
--- /dev/null
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/BundleFileLocatorHelperFactory.java
@@ -0,0 +1,57 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.osgi.boot.utils;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * BundleFileLocatorHelperFactory
+ *
+ * Obtain a helper for locating files based on the bundle.
+ */
+public class BundleFileLocatorHelperFactory
+{ 
+    private static final Logger LOG = Log.getLogger(BundleFileLocatorHelperFactory.class);
+    
+    private static BundleFileLocatorHelperFactory _instance = new BundleFileLocatorHelperFactory();
+    
+    private BundleFileLocatorHelperFactory() {}
+    
+    public static BundleFileLocatorHelperFactory getFactory()
+    {
+        return _instance;
+    }
+    
+    public BundleFileLocatorHelper getHelper()
+    {
+        BundleFileLocatorHelper helper = BundleFileLocatorHelper.DEFAULT;
+        try
+        {
+            //see if a fragment has supplied an alternative
+            helper = (BundleFileLocatorHelper) Class.forName(BundleFileLocatorHelper.CLASS_NAME).newInstance();
+        }
+        catch (Throwable t)
+        {
+            LOG.ignore(t);
+        }
+        return helper;
+    }
+
+}
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/EventSender.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/EventSender.java
index 719c469..0d612d8 100644
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/EventSender.java
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/EventSender.java
@@ -21,14 +21,18 @@ package org.eclipse.jetty.osgi.boot.utils;
 import java.util.Dictionary;
 import java.util.Hashtable;
 
-import javax.security.auth.login.FailedLoginException;
-
 import org.osgi.framework.Bundle;
 import org.osgi.framework.FrameworkUtil;
 import org.osgi.framework.ServiceReference;
 import org.osgi.service.event.Event;
 import org.osgi.service.event.EventAdmin;
 
+/**
+ * EventSender
+ *
+ * Utility class for emiting OSGi EventAdmin events
+ * 
+ */
 public class EventSender
 {    
     //OSGi Event Admin events for webapps
@@ -43,6 +47,13 @@ public class EventSender
     private Bundle _myBundle;
     private EventAdmin _eventAdmin;
     
+    
+    
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * 
+     */
     private EventSender ()
     {
         _myBundle = FrameworkUtil.getBundle(EventSender.class);
@@ -50,13 +61,27 @@ public class EventSender
         if (ref != null)
             _eventAdmin = (EventAdmin)_myBundle.getBundleContext().getService(ref);
     }
-
     
+    
+    
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return
+     */
     public static EventSender getInstance()
     {
         return __instance;
     }
 
+    
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param topic
+     * @param wab
+     * @param contextPath
+     */
     public  void send (String topic, Bundle wab, String contextPath)
     {
         if (topic==null || wab==null || contextPath==null)
@@ -66,6 +91,14 @@ public class EventSender
     }
     
     
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param topic
+     * @param wab
+     * @param contextPath
+     * @param ex
+     */
     public  void send (String topic, Bundle wab, String contextPath, Exception ex)
     {        
         if (_eventAdmin == null)
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/FakeURLClassLoader.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/FakeURLClassLoader.java
new file mode 100644
index 0000000..e44deae
--- /dev/null
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/FakeURLClassLoader.java
@@ -0,0 +1,83 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.osgi.boot.utils;
+
+import java.net.URL;
+import java.net.URLClassLoader;
+
+/**
+ * 
+ * FakeURLClassLoader
+ * 
+ * A URLClassloader that overrides the getURLs() method to return the list
+ * of urls passed in to the constructor, but otherwise acts as if it has no
+ * urls, which would cause it to delegate to the parent classloader (in this
+ * case an OSGi classloader).
+ * 
+ * The main use of this class is with jars containing tlds. Jasper expects a
+ * URL classloader to inspect for jars with tlds.
+ * 
+ */
+public class FakeURLClassLoader extends URLClassLoader
+{
+
+    private URL[] _jars;
+    
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param osgiClassLoader
+     * @param jars
+     */
+    public FakeURLClassLoader(ClassLoader osgiClassLoader, URL[] jars)
+    {
+        super(new URL[] {},osgiClassLoader);
+        _jars = jars;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the jars that contains tlds so that TldLocationsCache or
+     *         TldScanner can find them.
+     */
+    @Override
+    public URL[] getURLs()
+    {
+        return _jars;
+    }
+
+    
+    /* ------------------------------------------------------------ */
+    /** 
+     * @see java.lang.Object#toString()
+     */
+    public String toString()
+    {
+        StringBuilder builder = new StringBuilder();
+
+        if (_jars != null)
+        {
+            for (URL u:_jars)
+                builder.append(" "+u.toString());
+            return builder.toString();
+        }
+        else
+            return super.toString();
+    }
+}
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/OSGiClassLoader.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/OSGiClassLoader.java
index 147e805..5029c43 100644
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/OSGiClassLoader.java
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/OSGiClassLoader.java
@@ -78,8 +78,7 @@ public class OSGiClassLoader extends URLClassLoader
         }
 
         if (url == null)
-        {
-            
+        {           
             url = _osgiBundleClassLoader.getResource(name);
 
             if (url == null && name.startsWith("/"))
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/TldBundleDiscoverer.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/TldBundleDiscoverer.java
new file mode 100644
index 0000000..7516b29
--- /dev/null
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/TldBundleDiscoverer.java
@@ -0,0 +1,43 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.osgi.boot.utils;
+
+import java.net.URL;
+
+import org.eclipse.jetty.deploy.DeploymentManager;
+
+
+/**
+ * TldBundleDiscoverer
+ * 
+ * Convert bundles that contain tlds into URL locations for consumption by jasper.
+ */
+public interface TldBundleDiscoverer
+{
+    /**
+     * Find bundles that contain tlds and convert into URL references to their location.
+     * 
+     * @param manager
+     * @param fileLocator
+     * @return array of URLs representing locations of tld containing bundles
+     * @throws Exception
+     */
+    URL[] getUrlsForBundlesWithTlds(DeploymentManager manager, BundleFileLocatorHelper fileLocator) throws Exception;
+
+}
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/Util.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/Util.java
new file mode 100644
index 0000000..7fe815e
--- /dev/null
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/Util.java
@@ -0,0 +1,118 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.osgi.boot.utils;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.List;
+import java.util.StringTokenizer;
+
+/**
+ * Util
+ *
+ * Various useful functions used widely.
+ */
+public class Util
+{
+    public static final String DEFAULT_DELIMS = ",;";
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Treating the string as a separated list of filenames,
+     * convert and return the list of urls.
+     * 
+     * @param val the separated list
+     * @param delims the separators (default is ,;)
+     * @return
+     * @throws MalformedURLException 
+     */
+    public static List<URL> fileNamesAsURLs(String val, String delims) 
+    throws Exception
+    {
+        String separators = DEFAULT_DELIMS;
+        if (delims == null)
+            delims = separators;
+
+        StringTokenizer tokenizer = new StringTokenizer(val, delims, false);
+        List<URL> urls = new ArrayList<URL>();
+        while (tokenizer.hasMoreTokens())
+        {
+            urls.add(BundleFileLocatorHelperFactory.getFactory().getHelper().getLocalURL(new URL(tokenizer.nextToken())));
+        }
+        return urls;
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    public static void setProperty(Dictionary<String,String> properties, String key, String value)
+    {
+        if (value != null)
+        {
+            properties.put(key, value);
+        }
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * recursively substitute the ${sysprop} by their actual system property.
+     * ${sysprop,defaultvalue} will use 'defaultvalue' as the value if no
+     * sysprop is defined. Not the most efficient code but we are shooting for
+     * simplicity and speed of development here.
+     * 
+     * @param value
+     * @return
+     */
+    public static String resolvePropertyValue(String value)
+    {
+        int ind = value.indexOf("${");
+        if (ind == -1) { return value; }
+        int ind2 = value.indexOf('}', ind);
+        if (ind2 == -1) { return value; }
+        String sysprop = value.substring(ind + 2, ind2);
+        String defaultValue = null;
+        int comma = sysprop.indexOf(',');
+        if (comma != -1 && comma + 1 != sysprop.length())
+        {
+            defaultValue = sysprop.substring(comma + 1);
+            defaultValue = resolvePropertyValue(defaultValue);
+            sysprop = sysprop.substring(0, comma);
+        }
+        else
+        {
+            defaultValue = "${" + sysprop + "}";
+        }
+
+        String v = System.getProperty(sysprop);
+
+        String reminder = value.length() > ind2 + 1 ? value.substring(ind2 + 1) : "";
+        reminder = resolvePropertyValue(reminder);
+        if (v != null)
+        {
+            return value.substring(0, ind) + v + reminder;
+        }
+        else
+        {
+            return value.substring(0, ind) + defaultValue + reminder;
+        }
+    }
+}
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/WebappRegistrationCustomizer.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/WebappRegistrationCustomizer.java
deleted file mode 100644
index e51a7a5..0000000
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/WebappRegistrationCustomizer.java
+++ /dev/null
@@ -1,61 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.osgi.boot.utils;
-
-import java.net.URL;
-
-import org.eclipse.jetty.deploy.DeploymentManager;
-
-
-/**
- * Fix various shortcomings with the way jasper parses the tld files.
- */
-public interface WebappRegistrationCustomizer
-{
-    /**
-     * we could do something a lot more pluggable with a custom header in the
-     * manifest or some customer declarative services let's keep it simple for
-     * now. hopefully the rest of the world won't need to customize this.
-     */
-    public static final String CLASS_NAME = "org.eclipse.jetty.osgi.boot.jasper.WebappRegistrationCustomizerImpl";
-
-    /**
-     * TODO: right now only the jetty-jsp bundle is scanned for common taglibs.
-     * Should support a way to plug more bundles that contain taglibs.
-     * 
-     * The jasper TldScanner expects a URLClassloader to parse a jar for the
-     * /META-INF/*.tld it may contain. We place the bundles that we know contain
-     * such tag-libraries. Please note that it will work if and only if the
-     * bundle is a jar (!) Currently we just hardcode the bundle that contains
-     * the jstl implemenation.
-     * 
-     * A workaround when the tld cannot be parsed with this method is to copy
-     * and paste it inside the WEB-INF of the webapplication where it is used.
-     * 
-     * Support only 2 types of packaging for the bundle: - the bundle is a jar
-     * (recommended for runtime.) - the bundle is a folder and contain jars in
-     * the root and/or in the lib folder (nice for PDE developement situations)
-     * Unsupported: the bundle is a jar that embeds more jars.
-     * 
-     * @return array of URLs
-     * @throws Exception
-     */
-    URL[] getJarsWithTlds(DeploymentManager manager, BundleFileLocatorHelper fileLocator) throws Exception;
-
-}
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/internal/DefaultBundleClassLoaderHelper.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/internal/DefaultBundleClassLoaderHelper.java
index f817201..8a532fd 100644
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/internal/DefaultBundleClassLoaderHelper.java
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/internal/DefaultBundleClassLoaderHelper.java
@@ -23,45 +23,95 @@ import java.lang.reflect.Method;
 import java.util.List;
 
 import org.eclipse.jetty.osgi.boot.utils.BundleClassLoaderHelper;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
 import org.osgi.framework.Bundle;
 
 /**
+ * DefaultBundleClassLoaderHelper
+ * 
+ * 
  * Default implementation of the BundleClassLoaderHelper. Uses introspection to
  * support equinox-3.5 and felix-2.0.0
  */
 public class DefaultBundleClassLoaderHelper implements BundleClassLoaderHelper
 {
-
-    private static boolean identifiedOsgiImpl = false;
-
-    private static boolean isEquinox = false;
-
-    private static boolean isFelix = false;
-
-    private static void init(Bundle bundle)
+    private static final Logger LOG = Log.getLogger(BundleClassLoaderHelper.class);
+    private static enum OSGiContainerType {EquinoxOld, EquinoxLuna, FelixOld, Felix403};
+    private static OSGiContainerType osgiContainer;
+    private static Class Equinox_BundleHost_Class;
+    private static Class Equinox_EquinoxBundle_Class;
+    private static Class Felix_BundleImpl_Class;
+    private static Class Felix_BundleWiring_Class;
+    //old equinox
+    private static Method Equinox_BundleHost_getBundleLoader_method;
+    private static Method Equinox_BundleLoader_createClassLoader_method;
+    //new equinox
+    private static Method Equinox_EquinoxBundle_getModuleClassLoader_Method;
+  
+    //new felix
+    private static Method Felix_BundleImpl_Adapt_Method;
+    //old felix
+    private static Field Felix_BundleImpl_m_Modules_Field;
+    private static Field Felix_ModuleImpl_m_ClassLoader_Field;
+    private static Method Felix_BundleWiring_getClassLoader_Method;
+    
+    
+    private static void checkContainerType (Bundle bundle)
     {
-        identifiedOsgiImpl = true;
+        if (osgiContainer != null)
+            return;
+        
         try
         {
-            isEquinox = bundle.getClass().getClassLoader().loadClass("org.eclipse.osgi.framework.internal.core.BundleHost") != null;
+            Equinox_BundleHost_Class = bundle.getClass().getClassLoader().loadClass("org.eclipse.osgi.framework.internal.core.BundleHost");
+            osgiContainer = OSGiContainerType.EquinoxOld;
+            return;
+        }
+        catch (ClassNotFoundException e)
+        {
+            LOG.ignore(e);
         }
-        catch (Throwable t)
+
+        try
         {
-            isEquinox = false;
+            Equinox_EquinoxBundle_Class = bundle.getClass().getClassLoader().loadClass("org.eclipse.osgi.internal.framework.EquinoxBundle");
+            osgiContainer = OSGiContainerType.EquinoxLuna;
+            return;
         }
-        if (!isEquinox)
+        catch (ClassNotFoundException e)
         {
+            LOG.ignore(e);
+        }
+        
+        try
+        {       
+            //old felix or new felix?
+            Felix_BundleImpl_Class = bundle.getClass().getClassLoader().loadClass("org.apache.felix.framework.BundleImpl");  
             try
             {
-                isFelix = bundle.getClass().getClassLoader().loadClass("org.apache.felix.framework.BundleImpl") != null;
+                Felix_BundleImpl_Adapt_Method = Felix_BundleImpl_Class.getDeclaredMethod("adapt", new Class[] {Class.class});
+                osgiContainer = OSGiContainerType.Felix403;
+                return;
             }
-            catch (Throwable t2)
+            catch (NoSuchMethodException e)
             {
-                isFelix = false;
+                osgiContainer = OSGiContainerType.FelixOld;
+                return;
             }
         }
+        catch (ClassNotFoundException e)
+        {
+            LOG.warn("Unknown OSGi container type");
+            return;
+        }
+        
     }
 
+  
+    
+    
+
     /**
      * Assuming the bundle is started.
      * 
@@ -71,6 +121,7 @@ public class DefaultBundleClassLoaderHelper implements BundleClassLoaderHelper
     public ClassLoader getBundleClassLoader(Bundle bundle)
     {
         String bundleActivator = (String) bundle.getHeaders().get("Bundle-Activator");
+   
         if (bundleActivator == null)
         {
             bundleActivator = (String) bundle.getHeaders().get("Jetty-ClassInBundle");
@@ -83,115 +134,233 @@ public class DefaultBundleClassLoaderHelper implements BundleClassLoaderHelper
             }
             catch (ClassNotFoundException e)
             {
-                // should not happen as we are called if the bundle is started
-                // anyways.
-                e.printStackTrace();
+                LOG.warn(e);
             }
         }
-        // resort to introspection
-        if (!identifiedOsgiImpl)
+        
+        // resort to introspection     
+        return getBundleClassLoaderForContainer(bundle);
+    }
+    
+    /**
+     * @param bundle
+     * @return
+     */
+    private ClassLoader getBundleClassLoaderForContainer (Bundle bundle)
+    {
+        checkContainerType (bundle);
+        if (osgiContainer == null)
         {
-            init(bundle);
+            LOG.warn("No classloader for unknown OSGi container type");
+            return null;
         }
-        if (isEquinox)
+        
+        switch (osgiContainer)
         {
-            return internalGetEquinoxBundleClassLoader(bundle);
-        }
-        else if (isFelix) { return internalGetFelixBundleClassLoader(bundle); }
-        return null;
-    }
+            case EquinoxOld:
+            case EquinoxLuna:
+            {
+                return internalGetEquinoxBundleClassLoader(bundle);
+            }
 
-    private static Method Equinox_BundleHost_getBundleLoader_method;
+            case FelixOld:
+            case Felix403:
+            {
+                return internalGetFelixBundleClassLoader(bundle); 
+            }
+            default:
+            {
+                LOG.warn("No classloader found for bundle "+bundle.getSymbolicName());
+                return null;
 
-    private static Method Equinox_BundleLoader_createClassLoader_method;
+            }
+        }
+    }
+    
+    
 
+    /**
+     * @param bundle
+     * @return
+     */
     private static ClassLoader internalGetEquinoxBundleClassLoader(Bundle bundle)
     {
-        // assume equinox:
-        try
+        if (osgiContainer == OSGiContainerType.EquinoxOld)
         {
-            if (Equinox_BundleHost_getBundleLoader_method == null)
+            try
+            {
+                if (Equinox_BundleHost_getBundleLoader_method == null)
+                {
+                    Equinox_BundleHost_getBundleLoader_method = 
+                            Equinox_BundleHost_Class.getDeclaredMethod("getBundleLoader", new Class[] {});
+                    Equinox_BundleHost_getBundleLoader_method.setAccessible(true);
+                }
+                Object bundleLoader = Equinox_BundleHost_getBundleLoader_method.invoke(bundle, new Object[] {});
+                if (Equinox_BundleLoader_createClassLoader_method == null && bundleLoader != null)
+                {
+                    Equinox_BundleLoader_createClassLoader_method = 
+                            bundleLoader.getClass().getClassLoader().loadClass("org.eclipse.osgi.internal.loader.BundleLoader").getDeclaredMethod("createClassLoader", new Class[] {});
+                    Equinox_BundleLoader_createClassLoader_method.setAccessible(true);
+                }
+                return (ClassLoader) Equinox_BundleLoader_createClassLoader_method.invoke(bundleLoader, new Object[] {});
+            }
+            catch (ClassNotFoundException t)
             {
-                Equinox_BundleHost_getBundleLoader_method = 
-                    bundle.getClass().getClassLoader().loadClass("org.eclipse.osgi.framework.internal.core.BundleHost").getDeclaredMethod("getBundleLoader", new Class[] {});
-                Equinox_BundleHost_getBundleLoader_method.setAccessible(true);
+                LOG.warn(t);
+                return null;
             }
-            Object bundleLoader = Equinox_BundleHost_getBundleLoader_method.invoke(bundle, new Object[] {});
-            if (Equinox_BundleLoader_createClassLoader_method == null && bundleLoader != null)
+            catch (Throwable t)
             {
-                Equinox_BundleLoader_createClassLoader_method = 
-                    bundleLoader.getClass().getClassLoader().loadClass("org.eclipse.osgi.internal.loader.BundleLoader").getDeclaredMethod("createClassLoader", new Class[] {});
-                Equinox_BundleLoader_createClassLoader_method.setAccessible(true);
+                LOG.warn(t);
+                return null;
             }
-            return (ClassLoader) Equinox_BundleLoader_createClassLoader_method.invoke(bundleLoader, new Object[] {});
         }
-        catch (Throwable t)
+        
+        if (osgiContainer == OSGiContainerType.EquinoxLuna)
         {
-            t.printStackTrace();
+            try
+            {
+                if (Equinox_EquinoxBundle_getModuleClassLoader_Method == null)
+                    Equinox_EquinoxBundle_getModuleClassLoader_Method = Equinox_EquinoxBundle_Class.getDeclaredMethod("getModuleClassLoader", new Class[] {Boolean.TYPE});
+
+                Equinox_EquinoxBundle_getModuleClassLoader_Method.setAccessible(true);
+                return (ClassLoader)Equinox_EquinoxBundle_getModuleClassLoader_Method.invoke(bundle, new Object[] {Boolean.FALSE});
+            }
+            catch (Exception e)
+            {
+                LOG.warn(e);
+                return null;
+            }
         }
+        
+        LOG.warn("No classloader for equinox platform for bundle "+bundle.getSymbolicName());
         return null;
     }
 
-    private static Field Felix_BundleImpl_m_modules_field;
+   
 
-    private static Field Felix_ModuleImpl_m_classLoader_field;
 
+    /**
+     * @param bundle
+     * @return
+     */
     private static ClassLoader internalGetFelixBundleClassLoader(Bundle bundle)
     {
-        // assume felix:
-        try
+        
+        if (osgiContainer == OSGiContainerType.Felix403)
         {
-            // now get the current module from the bundle.
-            // and return the private field m_classLoader of ModuleImpl
-            if (Felix_BundleImpl_m_modules_field == null)
-            {
-                Felix_BundleImpl_m_modules_field = bundle.getClass().getClassLoader().loadClass("org.apache.felix.framework.BundleImpl").getDeclaredField("m_modules");
-                Felix_BundleImpl_m_modules_field.setAccessible(true);
-            }
-
-            // Figure out which version of the modules is exported
-            Object currentModuleImpl;
             try
             {
-                Object[] moduleArray = (Object[]) Felix_BundleImpl_m_modules_field.get(bundle);
-                currentModuleImpl = moduleArray[moduleArray.length - 1];
+                if (Felix_BundleWiring_Class == null)
+                    Felix_BundleWiring_Class = bundle.getClass().getClassLoader().loadClass("org.osgi.framework.wiring.BundleWiring");
+
+
+                Felix_BundleImpl_Adapt_Method.setAccessible(true);
+
+                if (Felix_BundleWiring_getClassLoader_Method == null)
+                {
+                    Felix_BundleWiring_getClassLoader_Method = Felix_BundleWiring_Class.getDeclaredMethod("getClassLoader");
+                    Felix_BundleWiring_getClassLoader_Method.setAccessible(true);
+                }
+
+
+                Object wiring = Felix_BundleImpl_Adapt_Method.invoke(bundle, new Object[] {Felix_BundleWiring_Class});
+                return (ClassLoader)Felix_BundleWiring_getClassLoader_Method.invoke(wiring);
             }
-            catch (Throwable t2)
+            catch (Exception e)
             {
-                @SuppressWarnings("unchecked")
-                List<Object> moduleArray = (List<Object>) Felix_BundleImpl_m_modules_field.get(bundle);
-                currentModuleImpl = moduleArray.get(moduleArray.size() - 1);
+                LOG.warn(e);
+                return null;
             }
+        }
 
-            if (Felix_ModuleImpl_m_classLoader_field == null && currentModuleImpl != null)
-            {
-                Felix_ModuleImpl_m_classLoader_field = bundle.getClass().getClassLoader().loadClass("org.apache.felix.framework.ModuleImpl").getDeclaredField("m_classLoader");
-                Felix_ModuleImpl_m_classLoader_field.setAccessible(true);
-            }
-            // first make sure that the classloader is ready:
-            // the m_classLoader field must be initialized by the
-            // ModuleImpl.getClassLoader() private method.
-            ClassLoader cl = (ClassLoader) Felix_ModuleImpl_m_classLoader_field.get(currentModuleImpl);
-            if (cl == null)
+
+        if (osgiContainer == OSGiContainerType.FelixOld)
+        {     
+            try
             {
+                if (Felix_BundleImpl_m_Modules_Field == null)
+                {
+                    Felix_BundleImpl_m_Modules_Field = Felix_BundleImpl_Class.getDeclaredField("m_modules");
+                    Felix_BundleImpl_m_Modules_Field.setAccessible(true);
+                }
+
+                // Figure out which version of the modules is exported
+                Object currentModuleImpl;
+
+                try
+                {
+                    Object[] moduleArray = (Object[]) Felix_BundleImpl_m_Modules_Field.get(bundle);
+                    currentModuleImpl = moduleArray[moduleArray.length - 1];
+                }
+                catch (Throwable t2)
+                {
+                    try
+                    {
+                        List<Object> moduleArray = (List<Object>) Felix_BundleImpl_m_Modules_Field.get(bundle);
+                        currentModuleImpl = moduleArray.get(moduleArray.size() - 1);
+                    }
+                    catch (Exception e)
+                    {
+                        LOG.warn(e);
+                        return null;
+                    }
+                }
+
+                if (Felix_ModuleImpl_m_ClassLoader_Field == null && currentModuleImpl != null)
+                {
+                    try
+                    {
+                        Felix_ModuleImpl_m_ClassLoader_Field = bundle.getClass().getClassLoader().loadClass("org.apache.felix.framework.ModuleImpl").getDeclaredField("m_classLoader");
+                        Felix_ModuleImpl_m_ClassLoader_Field.setAccessible(true);
+                    }
+                    catch (Exception e)
+                    {
+                        LOG.warn(e);
+                        return null;
+                    }
+                }
+
+                // first make sure that the classloader is ready:
+                // the m_classLoader field must be initialized by the
+                // ModuleImpl.getClassLoader() private method.
+                ClassLoader cl = null;
+                try
+                {
+                    cl = (ClassLoader) Felix_ModuleImpl_m_ClassLoader_Field.get(currentModuleImpl);
+                    if (cl != null)
+                        return cl;
+                }
+                catch (Exception e)
+                {
+                    LOG.warn(e);
+                    return null;
+                }
+
                 // looks like it was not ready:
                 // the m_classLoader field must be initialized by the
                 // ModuleImpl.getClassLoader() private method.
                 // this call will do that.
-                bundle.loadClass("java.lang.Object");
-                cl = (ClassLoader) Felix_ModuleImpl_m_classLoader_field.get(currentModuleImpl);
-                return cl;
-            }
-            else
+                try
+                {
+                    bundle.loadClass("java.lang.Object");
+                    cl = (ClassLoader) Felix_ModuleImpl_m_ClassLoader_Field.get(currentModuleImpl);
+                    return cl;
+                }
+                catch (Exception e)
+                {
+                    LOG.warn(e);
+                    return null;
+                }
+            }  
+            catch (Exception e)
             {
-                return cl;
+                LOG.warn(e);
+                return null;
             }
         }
-        catch (Throwable t)
-        {
-            t.printStackTrace();
-        }
+        
+        LOG.warn("No classloader for felix platform for bundle "+bundle.getSymbolicName());
         return null;
     }
-
 }
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/internal/DefaultFileLocatorHelper.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/internal/DefaultFileLocatorHelper.java
index f21e52a..75a658c 100644
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/internal/DefaultFileLocatorHelper.java
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/internal/DefaultFileLocatorHelper.java
@@ -19,6 +19,7 @@
 package org.eclipse.jetty.osgi.boot.utils.internal;
 
 import java.io.File;
+import java.io.IOException;
 import java.lang.reflect.Field;
 import java.lang.reflect.Method;
 import java.net.URI;
@@ -31,11 +32,14 @@ import java.util.zip.ZipFile;
 
 import org.eclipse.jetty.osgi.boot.utils.BundleFileLocatorHelper;
 import org.eclipse.jetty.util.URIUtil;
-import org.eclipse.jetty.util.resource.Resource;
 import org.eclipse.jetty.util.resource.FileResource;
+import org.eclipse.jetty.util.resource.Resource;
 import org.osgi.framework.Bundle;
 
 /**
+ * DefaultFileLocatorHelper
+ * 
+ * 
  * From a bundle to its location on the filesystem. Assumes the bundle is not a
  * jar.
  * 
@@ -59,7 +63,25 @@ public class DefaultFileLocatorHelper implements BundleFileLocatorHelper
     // DirZipBundleEntry
 
     private static Field ZIP_FILE_FILED_FOR_ZIP_BUNDLE_FILE = null;// ZipFile
+    
+    private static final String[] FILE_BUNDLE_ENTRY_CLASSES = {"org.eclipse.osgi.baseadaptor.bundlefile.FileBundleEntry","org.eclipse.osgi.storage.bundlefile.FileBundleEntry"};
+    private static final String[] ZIP_BUNDLE_ENTRY_CLASSES = {"org.eclipse.osgi.baseadaptor.bundlefile.ZipBundleEntry","org.eclipse.osgi.storage.bundlefile.ZipBundleEntry"};
+    private static final String[] DIR_ZIP_BUNDLE_ENTRY_CLASSES = {"org.eclipse.osgi.baseadaptor.bundlefile.DirZipBundleEntry","org.eclipse.osgi.storage.bundlefile.DirZipBundleEntry"};
+    private static final String[] BUNDLE_URL_CONNECTION_CLASSES = {"org.eclipse.osgi.framework.internal.core.BundleURLConnection", "org.eclipse.osgi.storage.url.BundleURLConnection"};
+
 
+    public static boolean match (String name, String... names)
+    {
+        if (name == null || names == null)
+            return false;
+        boolean matched = false;
+        for (int i=0; i< names.length && !matched; i++)
+            if (name.equals(names[i]))
+                matched = true;
+        return matched;
+    }
+    
+    
     /**
      * Works with equinox, felix, nuxeo and probably more. Not exactly in the
      * spirit of OSGi but quite necessary to support self-contained webapps and
@@ -103,7 +125,8 @@ public class DefaultFileLocatorHelper implements BundleFileLocatorHelper
                 BUNDLE_ENTRY_FIELD.setAccessible(true);
             }
             Object bundleEntry = BUNDLE_ENTRY_FIELD.get(con);
-            if (bundleEntry.getClass().getName().equals("org.eclipse.osgi.baseadaptor.bundlefile.FileBundleEntry"))
+           
+            if (match(bundleEntry.getClass().getName(), FILE_BUNDLE_ENTRY_CLASSES))
             {
                 if (FILE_FIELD == null)
                 {
@@ -113,7 +136,7 @@ public class DefaultFileLocatorHelper implements BundleFileLocatorHelper
                 File f = (File) FILE_FIELD.get(bundleEntry);
                 return f.getParentFile().getParentFile();
             }
-            else if (bundleEntry.getClass().getName().equals("org.eclipse.osgi.baseadaptor.bundlefile.ZipBundleEntry"))
+            else if (match(bundleEntry.getClass().getName(), ZIP_BUNDLE_ENTRY_CLASSES))
             {
                 url = bundle.getEntry("/");
 
@@ -140,7 +163,7 @@ public class DefaultFileLocatorHelper implements BundleFileLocatorHelper
                 ZipFile zipFile = (ZipFile) ZIP_FILE_FILED_FOR_ZIP_BUNDLE_FILE.get(zipBundleFile);
                 return new File(zipFile.getName());
             }
-            else if (bundleEntry.getClass().getName().equals("org.eclipse.osgi.baseadaptor.bundlefile.DirZipBundleEntry"))
+            else if (match (bundleEntry.getClass().getName(), DIR_ZIP_BUNDLE_ENTRY_CLASSES))
             {
                 // that will not happen as we did ask for the manifest not a
                 // directory.
@@ -298,25 +321,19 @@ public class DefaultFileLocatorHelper implements BundleFileLocatorHelper
      * @return a URL to the bundle entry that uses a common protocol
      */
     public URL getLocalURL(URL url)
+    throws Exception
     {
         if ("bundleresource".equals(url.getProtocol()) || "bundleentry".equals(url.getProtocol()))
         {
-            try
-            {
-                URLConnection conn = url.openConnection();
-                conn.setDefaultUseCaches(Resource.getDefaultUseCaches());
-                if (BUNDLE_URL_CONNECTION_getLocalURL == null && conn.getClass().getName().equals("org.eclipse.osgi.framework.internal.core.BundleURLConnection"))
-                {
-                    BUNDLE_URL_CONNECTION_getLocalURL = conn.getClass().getMethod("getLocalURL", null);
-                    BUNDLE_URL_CONNECTION_getLocalURL.setAccessible(true);
-                }
-                if (BUNDLE_URL_CONNECTION_getLocalURL != null) { return (URL) BUNDLE_URL_CONNECTION_getLocalURL.invoke(conn, null); }
-            }
-            catch (Throwable t)
+
+            URLConnection conn = url.openConnection();
+            conn.setDefaultUseCaches(Resource.getDefaultUseCaches());
+            if (BUNDLE_URL_CONNECTION_getLocalURL == null && match(conn.getClass().getName(), BUNDLE_URL_CONNECTION_CLASSES))
             {
-                System.err.println("Unable to locate the OSGi url: '" + url + "'.");
-                t.printStackTrace();
+                BUNDLE_URL_CONNECTION_getLocalURL = conn.getClass().getMethod("getLocalURL", null);
+                BUNDLE_URL_CONNECTION_getLocalURL.setAccessible(true);
             }
+            if (BUNDLE_URL_CONNECTION_getLocalURL != null) { return (URL) BUNDLE_URL_CONNECTION_getLocalURL.invoke(conn, null); }
         }
         return url;
     }
@@ -332,26 +349,25 @@ public class DefaultFileLocatorHelper implements BundleFileLocatorHelper
      * @return a URL to the content of the bundle entry that uses the file:
      *         protocol
      *         </p>
+     * @throws IOException 
      */
-    public URL getFileURL(URL url)
+    public URL getFileURL(URL url) throws Exception
+ 
     {
         if ("bundleresource".equals(url.getProtocol()) || "bundleentry".equals(url.getProtocol()))
         {
-            try
-            {
-                URLConnection conn = url.openConnection();
-                conn.setDefaultUseCaches(Resource.getDefaultUseCaches());
-                if (BUNDLE_URL_CONNECTION_getFileURL == null && conn.getClass().getName().equals("org.eclipse.osgi.framework.internal.core.BundleURLConnection"))
-                {
-                    BUNDLE_URL_CONNECTION_getFileURL = conn.getClass().getMethod("getFileURL", null);
-                    BUNDLE_URL_CONNECTION_getFileURL.setAccessible(true);
-                }
-                if (BUNDLE_URL_CONNECTION_getFileURL != null) { return (URL) BUNDLE_URL_CONNECTION_getFileURL.invoke(conn, null); }
-            }
-            catch (Throwable t)
+
+            URLConnection conn = url.openConnection();
+            conn.setDefaultUseCaches(Resource.getDefaultUseCaches());
+            if (BUNDLE_URL_CONNECTION_getFileURL == null 
+                && 
+                match (conn.getClass().getName(), BUNDLE_URL_CONNECTION_CLASSES))
             {
-                t.printStackTrace();
+                BUNDLE_URL_CONNECTION_getFileURL = conn.getClass().getMethod("getFileURL", null);
+                BUNDLE_URL_CONNECTION_getFileURL.setAccessible(true);
             }
+            if (BUNDLE_URL_CONNECTION_getFileURL != null) { return (URL) BUNDLE_URL_CONNECTION_getFileURL.invoke(conn, null); }
+
         }
         return url;
     }
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/internal/PackageAdminServiceTracker.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/internal/PackageAdminServiceTracker.java
index 100601e..d265e5b 100644
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/internal/PackageAdminServiceTracker.java
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/internal/PackageAdminServiceTracker.java
@@ -35,8 +35,14 @@ import org.osgi.service.packageadmin.PackageAdmin;
 import org.osgi.service.startlevel.StartLevel;
 
 /**
+ * PackageAdminServiceTracker
+ * 
+ * 
  * When the PackageAdmin service is activated we can look for the fragments
- * attached to this bundle and "activate" them.
+ * attached to this bundle and do a fake "activate" on them.
+ * 
+ * See particularly the jetty-osgi-boot-jsp fragment bundle that uses this
+ * facility.
  */
 public class PackageAdminServiceTracker implements ServiceListener
 {
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/nested/NestedConnectorListener.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/nested/NestedConnectorListener.java
deleted file mode 100644
index 6c0a7d6..0000000
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/nested/NestedConnectorListener.java
+++ /dev/null
@@ -1,268 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.osgi.nested;
-
-import java.lang.reflect.Method;
-
-import javax.servlet.http.HttpServlet;
-
-import org.eclipse.jetty.nested.NestedConnector;
-import org.eclipse.jetty.util.component.AbstractLifeCycle.AbstractLifeCycleListener;
-import org.eclipse.jetty.util.component.LifeCycle;
-import org.osgi.framework.FrameworkUtil;
-
-/**
- * Listens to the start and stop of the NestedConnector to register and
- * unregister the NestedConnector with the BridgeServlet.
- * <p>
- * All interactions with the BridgeServlet are done via introspection to avoid
- * depending on it directly. The BridgeServlet lives in the bootstrap-webapp;
- * not inside equinox.
- * </p>
- */
-public class NestedConnectorListener extends AbstractLifeCycleListener
-{
-
-    /**
-     * Name of the BridgeServlet class. By default
-     * org.eclipse.equinox.servletbridge.BridgeServlet
-     */
-    private String bridgeServletClassName = "org.eclipse.equinox.servletbridge.BridgeServlet";
-
-    /**
-     * Name of the static method on the BridgeServlet class to register the
-     * servlet delegate. By default 'registerServletDelegate'
-     */
-    private String registerServletDelegateMethodName = "registerServletDelegate";
-
-    /**
-     * Name of the static method on the BridgeServlet class to register the
-     * servlet delegate. By default 'unregisterServletDelegate'
-     */
-    private String unregisterServletDelegateMethodName = "unregisterServletDelegate";
-
-    /**
-     * servlet that wraps this NestedConnector and uses the NestedConnector to
-     * service the requests.
-     */
-    private NestedConnectorServletDelegate _servletDelegate;
-
-    /**
-     * The NestedConnector listened to.
-     */
-    private NestedConnector nestedConnector;
-
-    /**
-     * @param bridgeServletClassName Name of the class that is the
-     *            BridgeServlet. By default
-     *            org.eclipse.equinox.servletbridge.BridgeServlet
-     */
-    public void setBridgeServletClassName(String bridgeServletClassName)
-    {
-        this.bridgeServletClassName = bridgeServletClassName;
-    }
-
-    public String getBridgeServletClassName()
-    {
-        return this.bridgeServletClassName;
-    }
-
-    public String getRegisterServletDelegateMethodName()
-    {
-        return this.registerServletDelegateMethodName;
-    }
-
-    public String getUnregisterServletDelegateMethodName()
-    {
-        return this.unregisterServletDelegateMethodName;
-    }
-
-    /**
-     * @param registerServletDelegateMethodName Name of the static method on the
-     *            BridgeServlet class to register the servlet delegate.
-     */
-    public void setRegisterServletDelegateMethodName(String registerServletDelegateMethodName)
-    {
-        this.registerServletDelegateMethodName = registerServletDelegateMethodName;
-    }
-
-    /**
-     * @param unregisterServletDelegateMethodName Name of the static method on
-     *            the BridgeServlet class to unregister the servlet delegate.
-     */
-    public void setUnregisterServletDelegateMethodName(String unregisterServletDelegateMethodName)
-    {
-        this.unregisterServletDelegateMethodName = unregisterServletDelegateMethodName;
-    }
-
-    /**
-     * @param nestedConnector The NestedConnector that we are listening to here.
-     */
-    public void setNestedConnector(NestedConnector nestedConnector)
-    {
-        this.nestedConnector = nestedConnector;
-    }
-
-    /**
-     * @return The NestedConnector that we are listening to here.
-     */
-    public NestedConnector getNestedConnector()
-    {
-        return this.nestedConnector;
-    }
-
-    @Override
-    public void lifeCycleStarted(LifeCycle event)
-    {
-        try
-        {
-            registerWithBridgeServlet();
-        }
-        catch (Exception e)
-        {
-            if (e instanceof RuntimeException) { throw (RuntimeException) e; }
-            throw new RuntimeException("Unable to register the servlet delegate into the BridgeServlet.", e);
-        }
-    }
-
-    @Override
-    public void lifeCycleStopping(LifeCycle event)
-    {
-        try
-        {
-            unregisterWithBridgeServlet();
-        }
-        catch (Exception e)
-        {
-            if (e instanceof RuntimeException) { throw (RuntimeException) e; }
-            throw new RuntimeException("Unable to unregister the servlet delegate into the BridgeServlet.", e);
-        }
-    }
-
-    /**
-     * Hook into the BridgeServlet
-     */
-    protected void registerWithBridgeServlet() throws Exception
-    {
-        _servletDelegate = new NestedConnectorServletDelegate(getNestedConnector());
-        try
-        {
-            invokeStaticMethod(getBridgeServletClassName(), getRegisterServletDelegateMethodName(), new Class[] { HttpServlet.class }, _servletDelegate);
-        }
-        catch (Throwable t)
-        {
-            _servletDelegate.destroy();
-            _servletDelegate = null;
-            if (t instanceof Exception) { throw (Exception) t; }
-            throw new RuntimeException("Unable to register the servlet delegate into the BridgeServlet.", t);
-        }
-    }
-
-    /**
-     * Unhook into the BridgeServlet
-     */
-    protected void unregisterWithBridgeServlet() throws Exception
-    {
-        if (_servletDelegate != null)
-        {
-            try
-            {
-                invokeStaticMethod(getBridgeServletClassName(), getUnregisterServletDelegateMethodName(), new Class[] { HttpServlet.class }, _servletDelegate);
-            }
-            catch (Throwable t)
-            {
-                if (t instanceof Exception) { throw (Exception) t; }
-                throw new RuntimeException("Unable to unregister the servlet delegate from the BridgeServlet.", t);
-            }
-            finally
-            {
-                _servletDelegate.destroy();
-                _servletDelegate = null;
-            }
-        }
-    }
-
-    /**
-     * 
-     * @param clName
-     * @param methName
-     * @param argType
-     * @throws Exception
-     */
-    private static void invokeStaticMethod(String clName, String methName, Class[] argType, Object... args) throws Exception
-    {
-        Method m = getMethod(clName, methName, argType);
-        m.invoke(null, args);
-    }
-
-    /**
-     * 
-     * @param clName Class that belongs to the parent classloader of the OSGi
-     *            framework.
-     * @param methName Name of the method to find.
-     * @param argType Argument types of the method to find.
-     * @throws Exception
-     */
-    private static Method getMethod(String clName, String methName, Class... argType) throws Exception
-    {
-        Class bridgeServletClass = FrameworkUtil.class.getClassLoader().loadClass(clName);
-        return getMethod(bridgeServletClass, methName, argType);
-    }
-
-    private static Method getMethod(Class cl, String methName, Class... argType) throws Exception
-    {
-        Method meth = null;
-        try
-        {
-            meth = cl.getMethod(methName, argType);
-            return meth;
-        }
-        catch (Exception e)
-        {
-            for (Method m : cl.getMethods())
-            {
-                if (m.getName().equals(methName) && m.getParameterTypes().length == argType.length)
-                {
-                    int i = 0;
-                    for (Class p : m.getParameterTypes())
-                    {
-                        Class ap = argType[i];
-                        if (p.getName().equals(ap.getName()) && !p.equals(ap)) 
-                        { 
-                            throw new IllegalStateException("The method \"" + m.toGenericString()
-                                                            + "\" was found. but the parameter class "
-                                                            + p.getName()
-                                                            + " is not the same "
-                                                            + " inside OSGi classloader ("
-                                                            + ap.getClassLoader()
-                                                            + ") and inside the "
-                                                            + cl.getName()
-                                                            + " classloader ("
-                                                            + p.getClassLoader()
-                                                            + ")."
-                                                            + " Are the ExtensionBundles correctly defined?");
-
-                        }
-                    }
-                }
-            }
-            throw e;
-        }
-    }
-}
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/nested/NestedConnectorServletDelegate.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/nested/NestedConnectorServletDelegate.java
deleted file mode 100644
index 1dab4a0..0000000
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/nested/NestedConnectorServletDelegate.java
+++ /dev/null
@@ -1,51 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.osgi.nested;
-
-import java.io.IOException;
-
-import javax.servlet.ServletException;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
-import javax.servlet.http.HttpServlet;
-
-import org.eclipse.jetty.nested.NestedConnector;
-
-/**
- * Wraps a NestedConnector into a servlet that can be plugged into
- * BridgeServlet#registerServletDelegate
- */
-public class NestedConnectorServletDelegate extends HttpServlet
-{
-    private static final long serialVersionUID = 1L;
-
-    private final NestedConnector _nestedConnector;
-
-    public NestedConnectorServletDelegate(NestedConnector nestedConnector)
-    {
-        _nestedConnector = nestedConnector;
-    }
-
-    @Override
-    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException
-    {
-        _nestedConnector.service(req, res);
-    }
-
-}
diff --git a/jetty-osgi/jetty-osgi-equinoxtools/pom.xml b/jetty-osgi/jetty-osgi-equinoxtools/pom.xml
deleted file mode 100644
index 73a696b..0000000
--- a/jetty-osgi/jetty-osgi-equinoxtools/pom.xml
+++ /dev/null
@@ -1,121 +0,0 @@
-<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">
-  <parent>
-    <groupId>org.eclipse.jetty.osgi</groupId>
-    <artifactId>jetty-osgi-project</artifactId>
-    <version>8.1.17-SNAPSHOT</version>
-    <relativePath>../pom.xml</relativePath>
-  </parent>
-  <modelVersion>4.0.0</modelVersion>
-  <artifactId>jetty-osgi-equinoxtools</artifactId>
-  <name>Jetty :: OSGi :: Example Equinox Tools</name>
-  <description>Jetty OSGi Example Equinox Tools</description>
-  <url>http://www.eclipse.org/jetty</url>
-  <properties>
-    <bundle-symbolic-name>${project.groupId}.equinoxtools</bundle-symbolic-name>
-  </properties>
-  <dependencies>
-    <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-webapp</artifactId>
-    </dependency>
-    <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-continuation</artifactId>
-    </dependency>
-    <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-websocket</artifactId>
-    </dependency>
-    <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-servlet</artifactId>
-    </dependency>
-    <dependency>
-    	<groupId>org.eclipse.osgi</groupId>
-    	<artifactId>org.eclipse.osgi</artifactId>
-    </dependency>
-    <dependency>
-        <groupId>org.eclipse.osgi</groupId>
-        <artifactId>org.eclipse.osgi.services</artifactId>
-    </dependency>
-  </dependencies>
-
-   <build>
-    <plugins>
-      <plugin>
-        <artifactId>maven-antrun-plugin</artifactId>
-        <executions>
-          <execution>
-            <phase>process-resources</phase>
-            <configuration>
-              <tasks>
-                <copy todir="target/classes/equinoxconsole">
-                    <fileset dir="equinoxconsole" />
-                </copy>
-              </tasks>
-            </configuration>
-            <goals>
-              <goal>run</goal>
-            </goals>
-          </execution>
-        </executions>
-      </plugin>
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-jar-plugin</artifactId>
-        <executions>
-          <execution>
-            <id>artifact-jar</id>
-            <goals>
-              <goal>jar</goal>
-            </goals>
-          </execution>
-          <execution>
-            <id>test-jar</id>
-            <goals>
-              <goal>test-jar</goal>
-            </goals>
-          </execution>
-        </executions>
-        <configuration>
-          <archive>
-            <manifestFile>target/classes/META-INF/MANIFEST.MF</manifestFile>
-          </archive>
-        </configuration>
-      </plugin>
-      <plugin>
-          <groupId>org.apache.felix</groupId>
-          <artifactId>maven-bundle-plugin</artifactId>
-          <extensions>true</extensions>
-          <executions>
-              <execution>
-                  <id>bundle-manifest</id>
-                  <phase>process-classes</phase>
-                  <goals>
-                      <goal>manifest</goal> 
-                  </goals>
-              </execution>
-          </executions>
-          <configuration>
-              <instructions>
-                <Bundle-SymbolicName>org.eclipse.jetty.osgi.equinoxtools</Bundle-SymbolicName>
-                <Bundle-Name>Console</Bundle-Name>
-                <Bundle-Activator>org.eclipse.jetty.osgi.equinoxtools.WebEquinoxToolsActivator</Bundle-Activator>
-                <Export-Package>org.eclipse.jetty.osgi.equinoxtools;x-internal:=true;version="${parsedVersion.osgiVersion}",
-                                org.eclipse.jetty.osgi.equinoxtools.console;x-internal:=true;version="${parsedVersion.osgiVersion}"
-                </Export-Package>
-                <_nouses>true</_nouses>
-              </instructions>
-          </configuration>
-      </plugin>
-      <plugin>
-        <groupId>org.codehaus.mojo</groupId>
-        <artifactId>findbugs-maven-plugin</artifactId>
-        <configuration>
-          <onlyAnalyze>org.eclipse.jetty.osgi.equinoxtools.*</onlyAnalyze>
-        </configuration>
-      </plugin>
-    </plugins>
-  </build>
-
-</project>
diff --git a/jetty-osgi/jetty-osgi-equinoxtools/src/main/java/org/eclipse/jetty/osgi/equinoxtools/WebEquinoxToolsActivator.java b/jetty-osgi/jetty-osgi-equinoxtools/src/main/java/org/eclipse/jetty/osgi/equinoxtools/WebEquinoxToolsActivator.java
deleted file mode 100644
index 6656b8f..0000000
--- a/jetty-osgi/jetty-osgi-equinoxtools/src/main/java/org/eclipse/jetty/osgi/equinoxtools/WebEquinoxToolsActivator.java
+++ /dev/null
@@ -1,133 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.osgi.equinoxtools;
-
-import javax.servlet.ServletException;
-
-import org.eclipse.jetty.osgi.equinoxtools.console.EquinoxChattingSupport;
-import org.eclipse.jetty.osgi.equinoxtools.console.EquinoxConsoleContinuationServlet;
-import org.eclipse.jetty.osgi.equinoxtools.console.EquinoxConsoleSyncServlet;
-import org.eclipse.jetty.osgi.equinoxtools.console.EquinoxConsoleWebSocketServlet;
-import org.eclipse.jetty.osgi.equinoxtools.console.WebConsoleSession;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.osgi.framework.console.ConsoleSession;
-import org.osgi.framework.BundleActivator;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.ServiceReference;
-import org.osgi.service.http.HttpService;
-import org.osgi.service.http.NamespaceException;
-import org.osgi.util.tracker.ServiceTracker;
-import org.osgi.util.tracker.ServiceTrackerCustomizer;
-
-/**
- * When started will register on the HttpService 3 servlets for 3 different styles of equinox consoles.
- */
-public class WebEquinoxToolsActivator implements BundleActivator
-{
-    private static final Logger LOG = Log.getLogger(WebEquinoxToolsActivator.class);
-
-
-    private static BundleContext context;
-    public static BundleContext getContext()
-    {
-        return context;
-    }
-
-    private HttpService _httpService;
-    private ServiceTracker _tracker;
-    private EquinoxChattingSupport _equinoxChattingSupport;
-    
-
-    /*
-     * (non-Javadoc)
-     * 
-     * @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext)
-     */
-    public void start(BundleContext bundleContext) throws Exception
-    {
-        WebEquinoxToolsActivator.context = bundleContext;
-                
-        ServiceTrackerCustomizer httpServiceTrackerCustomizer = new ServiceTrackerCustomizer()
-        {
-            public void removedService(ServiceReference reference, Object service)
-            {
-                _httpService = null;
-            }
-
-            public void modifiedService(ServiceReference reference, Object service)
-            {
-                _httpService = (HttpService)context.getService(reference);
-            }
-
-            public Object addingService(ServiceReference reference)
-            {
-                _httpService = (HttpService)context.getService(reference);
-                try
-                {
-                    //TODO; some effort to use the same console session on the 2 async console servlets?
-
-                    //websocket:
-//                    WebConsoleSession wsSession = new WebConsoleSession();
-//                    WebEquinoxConsoleActivator.context.registerService(ConsoleSession.class.getName(), wsSession, null);
-//                    EquinoxChattingSupport wsEquinoxChattingSupport = new EquinoxChattingSupport(wsSession);
-                    _httpService.registerResources("/equinoxconsole/ws/index.html","/equinoxconsole/ws/index.html",null);
-                    _httpService.registerServlet("/equinoxconsole/ws",new EquinoxConsoleWebSocketServlet(/*wsSession, wsEquinoxChattingSupport*/),null,null);
-                    
-                    //continuations:
-//                    WebConsoleSession contSession = new WebConsoleSession();
-//                    WebEquinoxConsoleActivator.context.registerService(ConsoleSession.class.getName(), contSession, null);
-//                    EquinoxChattingSupport contEquinoxChattingSupport = new EquinoxChattingSupport(contSession);
-                    _httpService.registerResources("/equinoxconsole/index.html","/equinoxconsole/index.html",null);
-                    _httpService.registerServlet("/equinoxconsole",new EquinoxConsoleContinuationServlet(/*contSession, contEquinoxChattingSupport*/),null,null);
-                    
-                    //legacy synchroneous; keep it in a separate console session.
-                    WebConsoleSession syncSession = new WebConsoleSession();
-                    WebEquinoxToolsActivator.context.registerService(ConsoleSession.class.getName(), syncSession, null);
-                    _httpService.registerServlet("/equinoxconsole/sync",new EquinoxConsoleSyncServlet(syncSession),null,null);
-                }
-                catch (ServletException e)
-                {
-                    LOG.warn(e);
-                }
-                catch (NamespaceException e)
-                {
-                    LOG.warn(e);
-                }
-                return _httpService;
-            }
-        };
-
-        _tracker = new ServiceTracker(context,HttpService.class.getName(),httpServiceTrackerCustomizer);
-        _tracker.open();
-    }
-
-    /*
-     * (non-Javadoc)
-     * 
-     * @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext)
-     */
-    public void stop(BundleContext bundleContext) throws Exception
-    {
-        _tracker.close();
-        WebEquinoxToolsActivator.context = null;
-    }
-    
-
-}
diff --git a/jetty-osgi/jetty-osgi-equinoxtools/src/main/java/org/eclipse/jetty/osgi/equinoxtools/console/EquinoxChattingSupport.java b/jetty-osgi/jetty-osgi-equinoxtools/src/main/java/org/eclipse/jetty/osgi/equinoxtools/console/EquinoxChattingSupport.java
deleted file mode 100644
index 4cd850d..0000000
--- a/jetty-osgi/jetty-osgi-equinoxtools/src/main/java/org/eclipse/jetty/osgi/equinoxtools/console/EquinoxChattingSupport.java
+++ /dev/null
@@ -1,156 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.osgi.equinoxtools.console;
-
-import java.util.LinkedList;
-import java.util.Queue;
-
-import org.eclipse.jetty.osgi.equinoxtools.console.WebConsoleWriterOutputStream.OnFlushListener;
-
-/**
- * Processing of the messages to be received and sent to the chat servlets.
- * Made to be extended for filtering of the messages and commands.
- */
-public class EquinoxChattingSupport
-{
-    
-    private WebConsoleSession _consoleSession;
-    
-    public EquinoxChattingSupport(WebConsoleSession consoleSession)
-    {
-        _consoleSession = consoleSession;
-    }
-    
-    /**
-     * Split the output into multiple lines.
-     * Format them for the json messages sent to the chat.
-     * Empties the console output from what is already displayed in the chat.
-     * @return The lines to add to the message queue of each client.
-     */
-    protected Queue<String> processConsoleOutput(boolean escape, OnFlushListener onflush)
-    {
-        Queue<String> result = new LinkedList<String>();
-        String toDisplay = _consoleSession.getOutputAsWriter().getBuffer().toString();
-        //the last listener to be called is in charge of clearing the console.
-        boolean clearConsole = _consoleSession.getOnFlushListeners().indexOf(onflush) == _consoleSession.getOnFlushListeners().size();
-        if (clearConsole)
-        {
-            _consoleSession.clearOutput();
-        }
-        boolean lastLineIsComplete = toDisplay.endsWith("\n") || toDisplay.endsWith("\r");
-        String[] lines = toDisplay.split("\n");
-        String lastLine = lastLineIsComplete ? null : lines[lines.length-1];
-        if (clearConsole)
-        {
-            _consoleSession.getOutputAsWriter().append(lastLine);
-        }
-        for (int lnNb = 0; lnNb < (lastLineIsComplete ? lines.length : lines.length-1); lnNb++) 
-        {
-            String line = lines[lnNb];
-            while (line.trim().startsWith("null"))
-            {//hum..
-                line = line.trim().substring("null".length()).trim();
-            }
-            if (line.startsWith("osgi>"))
-            {
-                result.add("osgi>");
-                result.add(escape ? jsonEscapeString(line.substring("osgi>".length())) : line.substring("osgi>".length()));
-            }
-            else
-            {
-                result.add("
");
-                result.add(escape ? jsonEscapeString(line) : line);
-            }
-        }
-        return result;
-    }
-    
-    /**
-     * http://www.ietf.org/rfc/rfc4627.txt
-     * @param str
-     * @return The same string escaped according to the JSON RFC.
-     */
-    public static String jsonEscapeString(String str)
-    {
-        StringBuilder sb = new StringBuilder();
-        char[] asChars = str.toCharArray();
-        for (char ch : asChars)
-        {
-            switch (ch)
-            {
-                //the reserved characters
-                case '"':
-                    sb.append("\\\"");
-                    break;
-                case '\\':
-                    sb.append("\\\\");
-                    break;
-                case '\b':
-                    sb.append("\\b");
-                    break;
-                case '\f':
-                    sb.append("\\f");
-                    break;
-                case '\n':
-                    sb.append("\\n");
-                    break;
-                case '\r':
-                    sb.append("\\r");
-                    break;
-                case '\t':
-                    sb.append("\\t");
-                    break;
-                case '/':
-                    sb.append("\\/");
-                    break;
-                default:
-                    //The non reserved characters
-                    if (ch >= '\u0000' && ch <= '\u001F')
-                    {
-                        //escape as a unicode number when out of range.
-                        String ss = Integer.toHexString(ch);
-                        sb.append("\\u");
-                        for (int i = 0; i < 4 - ss.length(); i++)
-                        {
-                            //padding
-                            sb.append('0');
-                        }
-                        sb.append(ss.toUpperCase());
-                    }
-                    else
-                    {
-                        sb.append(ch);
-                    }
-            }
-        }
-        return sb.toString();
-    }
-    
-    public void broadcast(OnFlushListener source)
-    {
-        for (OnFlushListener onflush : _consoleSession.getOnFlushListeners())
-        {
-            if (onflush != source)
-            {
-                onflush.onFlush();
-            }
-        }
-    }
-
-}
diff --git a/jetty-osgi/jetty-osgi-equinoxtools/src/main/java/org/eclipse/jetty/osgi/equinoxtools/console/EquinoxConsoleContinuationServlet.java b/jetty-osgi/jetty-osgi-equinoxtools/src/main/java/org/eclipse/jetty/osgi/equinoxtools/console/EquinoxConsoleContinuationServlet.java
deleted file mode 100644
index 3845325..0000000
--- a/jetty-osgi/jetty-osgi-equinoxtools/src/main/java/org/eclipse/jetty/osgi/equinoxtools/console/EquinoxConsoleContinuationServlet.java
+++ /dev/null
@@ -1,253 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.osgi.equinoxtools.console;
-
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.Map;
-import java.util.Queue;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jetty.continuation.Continuation;
-import org.eclipse.jetty.continuation.ContinuationSupport;
-import org.eclipse.jetty.osgi.equinoxtools.WebEquinoxToolsActivator;
-import org.eclipse.jetty.osgi.equinoxtools.console.WebConsoleWriterOutputStream.OnFlushListener;
-import org.eclipse.osgi.framework.console.ConsoleSession;
-
-/**
- * Async servlet with jetty continuations to interact with the equinox console.
- * Ported from jetty's example 'ChatServlet'
- */
-public class EquinoxConsoleContinuationServlet extends HttpServlet implements OnFlushListener
-{
-
-    private static final long serialVersionUID = 1L;
-    private Map<String,ConsoleUser> _consoleUsers = new HashMap<String, ConsoleUser>();
-    private WebConsoleSession _consoleSession;
-    private EquinoxChattingSupport _support;
-    
-    /**
-     * @param consoleSession
-     */
-    public EquinoxConsoleContinuationServlet()
-    {
-        
-    }
-    /**
-     * @param consoleSession
-     */
-    public EquinoxConsoleContinuationServlet(WebConsoleSession consoleSession, EquinoxChattingSupport support)
-    {
-        _consoleSession = consoleSession;
-        _support = support;
-    }
-    @Override
-    public void init() throws ServletException
-    {
-        if (_consoleSession == null)
-        {
-            _consoleSession = new WebConsoleSession();
-            WebEquinoxToolsActivator.getContext().registerService(ConsoleSession.class.getName(), _consoleSession, null);
-        }
-        if (_support == null)
-        {
-            _support = new EquinoxChattingSupport(_consoleSession);
-        }
-        _consoleSession.addOnFlushListener(this);
-    }
-    @Override
-    public void destroy()
-    {
-        _consoleSession.removeOnFlushListener(this);
-    }
-
-    // Serve the HTML with embedded CSS and Javascript.
-    // This should be static content and should use real JS libraries.
-    @Override
-    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
-    {
-        if (request.getParameter("action")!=null)
-            doPost(request,response);
-        else
-            response.sendRedirect(request.getContextPath() + request.getServletPath()
-                + (request.getPathInfo() != null ? request.getPathInfo() : "") +  "/index.html");
-    }
-
-    // Handle Ajax calls from browser
-    @Override
-    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
-    {   
-        // Ajax calls are form encoded
-        String action = request.getParameter("action");
-        String message = request.getParameter("message");
-        String username = request.getParameter("user");
-
-        if (action.equals("join"))
-            join(request,response,username);
-        else if (action.equals("poll"))
-            poll(request,response,username);
-        else if (action.equals("chat"))
-            chat(request,response,username,message);
-    }
-
-    private synchronized void join(HttpServletRequest request,HttpServletResponse response,String username)
-    throws IOException
-    {
-        ConsoleUser member = new ConsoleUser(username);
-        _consoleUsers.put(username,member); 
-        response.setContentType("text/json;charset=utf-8");
-        PrintWriter out=response.getWriter();
-        out.print("{action:\"join\"}");
-    }
-
-    private synchronized void poll(HttpServletRequest request,HttpServletResponse response,String username)
-    throws IOException
-    {
-        ConsoleUser member = _consoleUsers.get(username);
-        if (member==null)
-        {
-            response.sendError(503);
-            return;
-        }
-
-        synchronized(member)
-        {
-            if (member.getMessageQueue().size()>0)
-            {
-                // Send one chat message
-                response.setContentType("text/json;charset=utf-8");
-                StringBuilder buf=new StringBuilder();
-
-                buf.append("{\"action\":\"poll\",");
-                buf.append("\"from\":\"");
-                buf.append(member.getMessageQueue().poll());
-                buf.append("\",");
-
-                String message = member.getMessageQueue().poll();
-                int quote=message.indexOf('"');
-                while (quote>=0)
-                {
-                    message=message.substring(0,quote)+'\\'+message.substring(quote);
-                    quote=message.indexOf('"',quote+2);
-                }
-                buf.append("\"chat\":\"");
-                buf.append(message);
-                buf.append("\"}");
-                byte[] bytes = buf.toString().getBytes("utf-8");
-                response.setContentLength(bytes.length);
-                response.getOutputStream().write(bytes);
-            }
-            else 
-            {
-                Continuation continuation = ContinuationSupport.getContinuation(request);
-                if (continuation.isInitial()) 
-                {
-                    // No chat in queue, so suspend and wait for timeout or chat
-                    continuation.setTimeout(20000);
-                    continuation.suspend();
-                    member.setContinuation(continuation);
-                }
-                else
-                {
-                    // Timeout so send empty response
-                    response.setContentType("text/json;charset=utf-8");
-                    PrintWriter out=response.getWriter();
-                    out.print("{action:\"poll\"}");
-                }
-            }
-        }
-    }
-
-    private synchronized void chat(HttpServletRequest request,HttpServletResponse response,String username,String message)
-    throws IOException
-    {
-        if (!message.endsWith("has joined!"))
-        {
-            _consoleSession.processCommand(message, false);
-        }
-        // Post chat to all members
-        onFlush();
-
-        response.setContentType("text/json;charset=utf-8");
-        PrintWriter out=response.getWriter();
-        out.print("{action:\"chat\"}");  
-    }
-
-    /**
-     * Called right after the flush method on the output stream has been executed.
-     */
-    public void onFlush()
-    {
-        Queue<String> pendingConsoleOutputMessages = _support.processConsoleOutput(true, this);
-        for (ConsoleUser m:_consoleUsers.values())
-        {
-            synchronized (m)
-            {
-//                m.getMessageQueue().add("osgi>"); // from
-//                m.getMessageQueue().add("something was printed");  // chat
-                m.getMessageQueue().addAll(pendingConsoleOutputMessages);
-                
-                // wakeup member if polling
-                if (m.getContinuation()!=null)
-                {
-                    m.getContinuation().resume();
-                    m.setContinuation(null);
-                }
-            }
-        }
-    }
-    
-    class ConsoleUser
-    {
-        private String _name;
-        private Continuation _continuation;
-        private Queue<String> _queue = new LinkedList<String>();
-        
-        public ConsoleUser(String name)
-        {
-            _name = name;
-        }
-        
-        public String getName() 
-        {
-            return _name;
-        }
-        
-        public void setContinuation(Continuation continuation)
-        {
-            _continuation = continuation;
-        }
-        
-        public Continuation getContinuation()
-        {
-            return _continuation;
-        }
-        public Queue<String> getMessageQueue()
-        {
-            return _queue;
-        }
-        
-    }
-}
diff --git a/jetty-osgi/jetty-osgi-equinoxtools/src/main/java/org/eclipse/jetty/osgi/equinoxtools/console/EquinoxConsoleSyncServlet.java b/jetty-osgi/jetty-osgi-equinoxtools/src/main/java/org/eclipse/jetty/osgi/equinoxtools/console/EquinoxConsoleSyncServlet.java
deleted file mode 100644
index e4b175f..0000000
--- a/jetty-osgi/jetty-osgi-equinoxtools/src/main/java/org/eclipse/jetty/osgi/equinoxtools/console/EquinoxConsoleSyncServlet.java
+++ /dev/null
@@ -1,82 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.osgi.equinoxtools.console;
-
-import java.io.IOException;
-import java.io.PrintWriter;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-
-/**
- * Take the example ChatServlet and use it to  make an Equinox Console on the web.
- */
-public class EquinoxConsoleSyncServlet extends HttpServlet
-{
-
-    private static final long serialVersionUID = 1L;
-    
-    private WebConsoleSession _consoleSession;
-
-    public EquinoxConsoleSyncServlet(WebConsoleSession consoleSession)
-    {
-    	_consoleSession = consoleSession;
-    }
-    
-    @Override
-    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
-    {
-    	String cmd = request.getParameter("cmd");
-    	String Action = request.getParameter("Action");
-    	if (Action != null && Action.toLowerCase().indexOf("clear") != -1)
-    	{
-            _consoleSession.clearOutput();
-    	}
-    	if (cmd != null)
-    	{
-    	    _consoleSession.processCommand(cmd, true);
-    	}
-    	
-        response.setContentType("text/html;charset=utf-8");
-    	PrintWriter p = response.getWriter();
-        p.println("<html><head><title>Equinox Console (Synchroneous)</title></head><body>");
-        p.println("<textarea rows=\"30\" cols=\"110\">");
-    	p.println(_consoleSession.getOutputAsWriter().toString());
-        p.println("</textarea>");
-        p.println("<form method=\"GET\" action=\""+response.encodeURL(getURI(request))+"\">");
-        p.println("osgi> <input type=\"text\" name=\"cmd\" value=\"\"/><br/>\n");
-        p.println("<input type=\"submit\" name=\"Action\" value=\"Submit or Refresh\"><br/>");
-        p.println("<input type=\"submit\" name=\"Action\" value=\"Clear and Submit\"><br/>");
-        p.println("</form>");
-        p.println("<br/>");
-    }
-    
-    
-    private String getURI(HttpServletRequest request)
-    {
-        String uri= (String)request.getAttribute("javax.servlet.forward.request_uri");
-        if (uri == null)
-            uri= request.getRequestURI();
-        return uri;
-    }
-
-}
diff --git a/jetty-osgi/jetty-osgi-equinoxtools/src/main/java/org/eclipse/jetty/osgi/equinoxtools/console/EquinoxConsoleWebSocketServlet.java b/jetty-osgi/jetty-osgi-equinoxtools/src/main/java/org/eclipse/jetty/osgi/equinoxtools/console/EquinoxConsoleWebSocketServlet.java
deleted file mode 100644
index c83c3d5..0000000
--- a/jetty-osgi/jetty-osgi-equinoxtools/src/main/java/org/eclipse/jetty/osgi/equinoxtools/console/EquinoxConsoleWebSocketServlet.java
+++ /dev/null
@@ -1,182 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.osgi.equinoxtools.console;
-
-import java.io.IOException;
-import java.util.Queue;
-import java.util.Set;
-import java.util.concurrent.CopyOnWriteArraySet;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jetty.osgi.equinoxtools.WebEquinoxToolsActivator;
-import org.eclipse.jetty.osgi.equinoxtools.console.WebConsoleWriterOutputStream.OnFlushListener;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.websocket.WebSocket;
-import org.eclipse.jetty.websocket.WebSocketServlet;
-import org.eclipse.osgi.framework.console.ConsoleSession;
-
-/**
- * Websocket version of the Chat with equinox.
- * Ported from jetty's example 'WebSocketChatServlet'
- */
-public class EquinoxConsoleWebSocketServlet extends WebSocketServlet implements OnFlushListener
-{
-    private static final Logger LOG = Log.getLogger(EquinoxConsoleWebSocketServlet.class);
-
-    private final Set<ChatWebSocket> _members = new CopyOnWriteArraySet<ChatWebSocket>();
-    private static final long serialVersionUID = 1L;
-    private WebConsoleSession _consoleSession;
-    private EquinoxChattingSupport _support;
-
-    public EquinoxConsoleWebSocketServlet()
-    {
-        
-    }
-    public EquinoxConsoleWebSocketServlet(WebConsoleSession consoleSession, EquinoxChattingSupport support)
-    {
-        _consoleSession = consoleSession;
-        _support = support;
-    }
-    @Override
-    public void init() throws ServletException
-    {
-        if (_consoleSession == null)
-        {
-            _consoleSession = new WebConsoleSession();
-            WebEquinoxToolsActivator.getContext().registerService(ConsoleSession.class.getName(), _consoleSession, null);
-        }
-        if (_support == null)
-        {
-            _support = new EquinoxChattingSupport(_consoleSession);
-        }
-        super.init();
-        _consoleSession.addOnFlushListener(this);
-    }
-    
-    @Override
-    public void destroy()
-    {
-        _consoleSession.removeOnFlushListener(this);
-    }
-
-    
-    protected void doGet(HttpServletRequest request, HttpServletResponse response) 
-        throws javax.servlet.ServletException ,IOException 
-    {
-        //getServletContext().getNamedDispatcher("default").forward(request,response);
-        response.sendRedirect(request.getContextPath() + request.getServletPath()
-                + (request.getPathInfo() != null ? request.getPathInfo() : "") +  "/index.html");
-    };
-    
-    public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol)
-    {
-        return new ChatWebSocket();
-    }
-    
-    /* ------------------------------------------------------------ */
-    /* ------------------------------------------------------------ */
-    class ChatWebSocket implements WebSocket.OnTextMessage
-    {
-        Connection _connection;
-        String _username;
-        
-        public void onOpen(Connection connection)
-        {
-            // LOG.info(this+" onConnect");
-            _connection=connection;
-            _members.add(this);
-        }
-        
-        public void onMessage(byte frame, byte[] data,int offset, int length)
-        {
-            // LOG.info(this+" onMessage: "+TypeUtil.toHexString(data,offset,length));
-        }
-
-        public void onMessage(String data)
-        {
-            LOG.info("onMessage: {}",data);
-            if (data.indexOf("disconnect")>=0)
-                _connection.disconnect();
-            else
-            {
-                if (!data.endsWith(":has joined!"))
-                {
-                    if (_username != null)
-                    {
-                        if (data.startsWith(_username + ":"))
-                        {
-                            data = data.substring(_username.length()+1);
-                        }
-                        else
-                        {
-                            //we should not be here?
-                        }
-                    }
-                    _consoleSession.processCommand(data, false);
-                }
-                else
-                {
-                    _username = data.substring(0, data.length()-":has joined!".length());
-                }
-                // LOG.info(this+" onMessage: "+data);
-                onFlush();
-            }
-        }
-
-        public void onClose(int code, String message)
-        {
-            // LOG.info(this+" onDisconnect");
-            _members.remove(this);
-        }
-
-        public void onError(String message, Throwable ex)
-        {
-            
-        }
-
-    }
-    
-    
-    /**
-     * Called right after the flush method on the output stream has been executed.
-     */
-    public void onFlush()
-    {
-        Queue<String> pendingConsoleOutputMessages = _support.processConsoleOutput(false, this);
-        for (ChatWebSocket member : _members)
-        {
-            try
-            {
-                for (String line : pendingConsoleOutputMessages)
-                {
-                    member._connection.sendMessage(line);
-                }
-            }
-            catch(IOException e)
-            {
-                LOG.warn(e);
-            }
-        }
-    }    
-
-}
diff --git a/jetty-osgi/jetty-osgi-equinoxtools/src/main/java/org/eclipse/jetty/osgi/equinoxtools/console/WebConsoleSession.java b/jetty-osgi/jetty-osgi-equinoxtools/src/main/java/org/eclipse/jetty/osgi/equinoxtools/console/WebConsoleSession.java
deleted file mode 100644
index a8912d8..0000000
--- a/jetty-osgi/jetty-osgi-equinoxtools/src/main/java/org/eclipse/jetty/osgi/equinoxtools/console/WebConsoleSession.java
+++ /dev/null
@@ -1,189 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.osgi.equinoxtools.console;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.PipedInputStream;
-import java.io.PipedOutputStream;
-import java.io.PrintStream;
-import java.io.StringWriter;
-import java.util.List;
-
-import org.eclipse.jetty.osgi.equinoxtools.console.WebConsoleWriterOutputStream.OnFlushListener;
-import org.eclipse.osgi.framework.console.ConsoleSession;
-
-/**
- * A simple console session for equinox.
- * 
- * @author hmalphettes
- */
-public class WebConsoleSession extends ConsoleSession
-{
-
-    private OutputStream _out;
-    private StringWriter _outWriter;
-    private PrintStream _source;
-    private InputStream _in;
-
-    public WebConsoleSession()
-    {
-        _outWriter = new StringWriter();
-        _out = new WebConsoleWriterOutputStream(_outWriter,"UTF-8");
-        try
-        {
-            PipedOutputStream source = new PipedOutputStream();
-            PipedInputStream sink = new PipedInputStream(source);
-            _in = sink;
-            _source = new PrintStream(source);
-        }
-        catch (IOException e)
-        {
-            //this never happens?
-            e.printStackTrace();
-        }
-    }
-    
-    @Override
-    protected void doClose()
-    {
-        if (_out != null) try { _out.close(); } catch (IOException e) {}
-        if (_in != null) try { _in.close(); } catch (IOException ioe) {}
-    }
-
-    @Override
-    public InputStream getInput()
-    {
-        return _in;
-    }
-
-    @Override
-    public OutputStream getOutput()
-    {
-        return _out;
-    }
-
-    /**
-     * For the output we are using a string writer in fact.
-     * @return
-     */
-    public StringWriter getOutputAsWriter()
-    {
-        return _outWriter;
-    }
-    
-    /**
-     * @return The print stream where commands can be written.
-     */
-    public PrintStream getSource()
-    {
-        return _source;
-    }
-    
-    /**
-     * Issue a command on the console.
-     * @param cmd
-     */
-    public void issueCommand(String cmd)
-    {
-        if (cmd != null)
-        {
-            getSource().println(cmd);
-        }
-    }
-    
-    /**
-     * @param flushListener Object that is called back when the outputstream is flushed.
-     */
-    public void addOnFlushListener(OnFlushListener flushListener)
-    {
-        ((WebConsoleWriterOutputStream)_out).addOnFlushListener(flushListener);
-    }
-    /**
-     * @param flushListener Object that is called back when the outputstream is flushed.
-     */
-    public boolean removeOnFlushListener(OnFlushListener flushListener)
-    {
-        return ((WebConsoleWriterOutputStream)_out).removeOnFlushListener(flushListener);
-    }
-    
-    /**
-     * Process command comming from a web UI
-     * @param cmd
-     */
-    public void processCommand(String cmd, boolean wait)
-    {
-        cmd = cmd.trim();
-        while (cmd.startsWith("osgi>"))
-        {
-            cmd = cmd.substring("osgi>".length()).trim();
-        }
-        if (cmd.equals("clear"))
-        {
-            clearOutput();
-        }
-        else
-        {
-            getOutputAsWriter().append(cmd + "\n");
-            int originalOutputLength = getOutputAsWriter().getBuffer().length();
-            issueCommand(cmd);
-            
-            if (wait)
-            {
-                //it does not get uglier than this:
-                //give a little time to equinox to interpret the command so we see the response
-                //we could do a lot better, but we might as well use the async servlets anyways.
-                int waitLoopNumber = 0;
-                int lastWaitOutputLength = -1;
-                while (waitLoopNumber < 10)
-                {
-                    waitLoopNumber++;
-                    try
-                    {
-                        Thread.sleep(100);
-                    }
-                    catch (InterruptedException e)
-                    {
-                        break;
-                    }
-                    int newOutputLength = getOutputAsWriter().getBuffer().length();
-                    if (newOutputLength > originalOutputLength && newOutputLength == lastWaitOutputLength)
-                    {
-                        break;
-                    }
-                    lastWaitOutputLength = newOutputLength;
-                }
-            }
-        }
-
-    }
-    
-    public void clearOutput()
-    {
-        StringBuffer buf = getOutputAsWriter().getBuffer();
-        if (buf.length() > 0) buf.delete(0,buf.length()-1);
-    }
-    
-    public List<OnFlushListener> getOnFlushListeners()
-    {
-        return ((WebConsoleWriterOutputStream)_out).getOnFlushListeners();
-    }
-    
-}
diff --git a/jetty-osgi/jetty-osgi-equinoxtools/src/main/java/org/eclipse/jetty/osgi/equinoxtools/console/WebConsoleWriterOutputStream.java b/jetty-osgi/jetty-osgi-equinoxtools/src/main/java/org/eclipse/jetty/osgi/equinoxtools/console/WebConsoleWriterOutputStream.java
deleted file mode 100644
index 24fe645..0000000
--- a/jetty-osgi/jetty-osgi-equinoxtools/src/main/java/org/eclipse/jetty/osgi/equinoxtools/console/WebConsoleWriterOutputStream.java
+++ /dev/null
@@ -1,93 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.osgi.equinoxtools.console;
-
-import java.io.IOException;
-import java.io.Writer;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Can be set with a listener that is called back right after the flush method is called.
- */
-public class WebConsoleWriterOutputStream extends org.eclipse.jetty.io.WriterOutputStream
-{
-    
-    /**
-     * Interface called back after the outputstream is flushed.
-     */
-    public interface OnFlushListener
-    {
-        /**
-         * Called right after the flush method on the output stream has been executed.
-         */
-        public void onFlush();
-
-    }
-    
-    public interface MessageBroadcaster
-    {
-        public void broadcast();
-    }
-
-    private List<OnFlushListener> _callBacks;
-
-    public WebConsoleWriterOutputStream(Writer writer, String encoding)
-    {
-        super(writer, encoding);
-    }
-    
-    @Override
-    public synchronized void flush() throws IOException
-    {
-        super.flush();
-        if (_callBacks != null)
-        {
-            for (OnFlushListener listener : _callBacks)
-            {
-                listener.onFlush();
-            }
-        }
-    }
-    
-    public synchronized void addOnFlushListener(OnFlushListener callback)
-    {
-        if (_callBacks == null)
-        {
-            _callBacks = new ArrayList<WebConsoleWriterOutputStream.OnFlushListener>();
-        }
-        if (!_callBacks.contains(callback))
-        {
-            _callBacks.add(callback);
-        }
-    }
-    public synchronized boolean removeOnFlushListener(OnFlushListener callback)
-    {
-        if (_callBacks != null)
-        {
-            return _callBacks.remove(callback);
-        }
-        return false;
-    }
-    public synchronized List<OnFlushListener> getOnFlushListeners()
-    {
-        return _callBacks;
-    }
-    
-}
diff --git a/jetty-osgi/jetty-osgi-httpservice/contexts/httpservice.xml b/jetty-osgi/jetty-osgi-httpservice/contexts/httpservice.xml
index 211a8e8..6aae6e9 100644
--- a/jetty-osgi/jetty-osgi-httpservice/contexts/httpservice.xml
+++ b/jetty-osgi/jetty-osgi-httpservice/contexts/httpservice.xml
@@ -1,16 +1,16 @@
-<?xml version="1.0"  encoding="ISO-8859-1"?>
-<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
 <!--
  Copyright (c) 2009-2011 Intalio, Inc.
 
  All rights reserved. This program and the accompanying materials
  are made available under the terms of the Eclipse Public License v1.0
  and Apache License v2.0 which accompanies this distribution.
- The Eclipse Public License is available at 
+ The Eclipse Public License is available at
  http://www.eclipse.org/legal/epl-v10.html
  The Apache License v2.0 is available at
  http://www.opensource.org/licenses/apache2.0.php
- You may elect to redistribute this code under either of these licenses. 
+ You may elect to redistribute this code under either of these licenses.
  Contributors:
     Hugues Malphettes - initial API and implementation
 -->
@@ -23,7 +23,7 @@
   <Call name="addServlet">
     <Arg>org.eclipse.equinox.http.servlet.HttpServiceServlet</Arg>
     <Arg>/*</Arg>
-    <Set name="InitOrder">0</Set>
+    <Set name="InitOrder">1</Set>
   </Call>
 <!-- custom pluggable error handler -->
   <Set name="ErrorHandler">
diff --git a/jetty-osgi/jetty-osgi-httpservice/pom.xml b/jetty-osgi/jetty-osgi-httpservice/pom.xml
index 3b894fe..d3d007f 100644
--- a/jetty-osgi/jetty-osgi-httpservice/pom.xml
+++ b/jetty-osgi/jetty-osgi-httpservice/pom.xml
@@ -2,8 +2,7 @@
   <parent>
     <groupId>org.eclipse.jetty.osgi</groupId>
     <artifactId>jetty-osgi-project</artifactId>
-    <version>8.1.17.v20150415</version>
-    <relativePath>../pom.xml</relativePath>
+    <version>9.2.13.v20150730</version>
   </parent>
   <modelVersion>4.0.0</modelVersion>
   <artifactId>jetty-httpservice</artifactId>
@@ -29,10 +28,11 @@
     <dependency>
       <groupId>org.eclipse.osgi</groupId>
       <artifactId>org.eclipse.osgi</artifactId>
+      <scope>provided</scope>
     </dependency>
     <dependency>
-      <groupId>org.eclipse.jetty.orbit</groupId>
-      <artifactId>javax.servlet</artifactId>
+      <groupId>javax.servlet</groupId>
+      <artifactId>javax.servlet-api</artifactId>
     </dependency>
   </dependencies>
 
@@ -88,7 +88,7 @@
                   <id>bundle-manifest</id>
                   <phase>process-classes</phase>
                   <goals>
-                      <goal>manifest</goal> 
+                      <goal>manifest</goal>
                   </goals>
               </execution>
           </executions>
@@ -97,8 +97,10 @@
                 <Bundle-SymbolicName>org.eclipse.jetty.osgi.httpservice</Bundle-SymbolicName>
                 <Bundle-Name>OSGi HttpService</Bundle-Name>
                 <Jetty-ContextFilePath>contexts/httpservice.xml</Jetty-ContextFilePath>
-                <Import-Package>org.eclipse.jetty.server.handler;version="[8.1,9)",
-org.eclipse.jetty.util.component;version="[8.1,9)",
+                <Import-Package>org.eclipse.jetty.server.handler;version="[9.1,10.0)",
+org.eclipse.jetty.util.component;version="[9.1,10.0)",
+org.eclipse.jetty.server.session;version="[9.1,10.0)",
+org.eclipse.jetty.servlet;version="[9.1,10.0)",
 org.eclipse.equinox.http.servlet,
 *
                  </Import-Package>
diff --git a/jetty-osgi/jetty-osgi-httpservice/src/main/java/org/eclipse/jetty/osgi/httpservice/HttpServiceErrorHandlerHelper.java b/jetty-osgi/jetty-osgi-httpservice/src/main/java/org/eclipse/jetty/osgi/httpservice/HttpServiceErrorHandlerHelper.java
index 96b3e8f..489e9ed 100644
--- a/jetty-osgi/jetty-osgi-httpservice/src/main/java/org/eclipse/jetty/osgi/httpservice/HttpServiceErrorHandlerHelper.java
+++ b/jetty-osgi/jetty-osgi-httpservice/src/main/java/org/eclipse/jetty/osgi/httpservice/HttpServiceErrorHandlerHelper.java
@@ -30,16 +30,16 @@ import javax.servlet.http.HttpServlet;
  */
 public class HttpServiceErrorHandlerHelper
 {
-	private static HttpServlet _customErrorHandler;
+        private static HttpServlet _customErrorHandler;
 
-	public static HttpServlet getCustomErrorHandler()
-	{
-		return _customErrorHandler;
-	}
-	
-	public static void setHttpServiceErrorHandler(HttpServlet servlet)
-	{
-		_customErrorHandler = servlet;
-	}
-	
+        public static HttpServlet getCustomErrorHandler()
+        {
+                return _customErrorHandler;
+        }
+        
+        public static void setHttpServiceErrorHandler(HttpServlet servlet)
+        {
+                _customErrorHandler = servlet;
+        }
+        
 }
diff --git a/jetty-osgi/jetty-osgi-httpservice/src/main/java/org/eclipse/jetty/osgi/httpservice/HttpServiceErrorPageErrorHandler.java b/jetty-osgi/jetty-osgi-httpservice/src/main/java/org/eclipse/jetty/osgi/httpservice/HttpServiceErrorPageErrorHandler.java
index a8f2ad5..c84855f 100644
--- a/jetty-osgi/jetty-osgi-httpservice/src/main/java/org/eclipse/jetty/osgi/httpservice/HttpServiceErrorPageErrorHandler.java
+++ b/jetty-osgi/jetty-osgi-httpservice/src/main/java/org/eclipse/jetty/osgi/httpservice/HttpServiceErrorPageErrorHandler.java
@@ -35,46 +35,46 @@ import org.eclipse.jetty.servlet.ErrorPageErrorHandler;
 public class HttpServiceErrorPageErrorHandler extends ErrorPageErrorHandler
 {
 
-	private static HttpServiceErrorPageErrorHandler INSTANCE;
-	
-	public static HttpServiceErrorPageErrorHandler getInstance()
-	{
-		return INSTANCE;
-	}
-	
-	public HttpServiceErrorPageErrorHandler()
-	{
-		INSTANCE = this;
-	}
+        private static HttpServiceErrorPageErrorHandler INSTANCE;
+        
+        public static HttpServiceErrorPageErrorHandler getInstance()
+        {
+                return INSTANCE;
+        }
+        
+        public HttpServiceErrorPageErrorHandler()
+        {
+                INSTANCE = this;
+        }
 
-	@Override
-	public void handle(String target, Request baseRequest,
-			HttpServletRequest request, HttpServletResponse response)
-			throws IOException {
-		if (HttpServiceErrorHandlerHelper.getCustomErrorHandler() != null)
-		{
-			try
-			{
-				HttpServiceErrorHandlerHelper.getCustomErrorHandler().service(request, response);
-			}
-			catch (ServletException e)
-			{
-				//well
-			}
-		}
-		if (!response.isCommitted())
-		{
-			super.handle(target, baseRequest, request, response);
-		}
-	}
+        @Override
+        public void handle(String target, Request baseRequest,
+                        HttpServletRequest request, HttpServletResponse response)
+                        throws IOException {
+                if (HttpServiceErrorHandlerHelper.getCustomErrorHandler() != null)
+                {
+                        try
+                        {
+                                HttpServiceErrorHandlerHelper.getCustomErrorHandler().service(request, response);
+                        }
+                        catch (ServletException e)
+                        {
+                                //well
+                        }
+                }
+                if (!response.isCommitted())
+                {
+                        super.handle(target, baseRequest, request, response);
+                }
+        }
 
-	@Override
-	protected void doStop() throws Exception
-	{
-		INSTANCE = null;
-		super.doStop();
-	}
-	
-	
-	
+        @Override
+        protected void doStop() throws Exception
+        {
+                INSTANCE = null;
+                super.doStop();
+        }
+        
+        
+        
 }
diff --git a/jetty-osgi/jetty-osgi-npn/pom.xml b/jetty-osgi/jetty-osgi-npn/pom.xml
new file mode 100644
index 0000000..092278f
--- /dev/null
+++ b/jetty-osgi/jetty-osgi-npn/pom.xml
@@ -0,0 +1,47 @@
+<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">
+  <parent>
+    <groupId>org.eclipse.jetty.osgi</groupId>
+    <artifactId>jetty-osgi-project</artifactId>
+    <version>9.2.11-SNAPSHOT</version>
+  </parent>
+  <modelVersion>4.0.0</modelVersion>
+  <artifactId>jetty-osgi-npn</artifactId>
+  <name>Jetty :: OSGi NPN Fragment</name>
+  <packaging>jar</packaging>
+  <properties>
+    <bundle-symbolic-name>org.eclipse.jetty.osgi.npn.fragment</bundle-symbolic-name>
+  </properties>
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>build-helper-maven-plugin</artifactId>
+        <version>1.7</version>
+        <executions>
+          <execution>
+            <id>parse-version</id>
+            <goals>
+              <goal>parse-version</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-jar-plugin</artifactId>
+        <configuration>
+          <archive>
+            <manifestEntries>
+              <Bundle-ManifestVersion>2</Bundle-ManifestVersion>
+              <Bundle-SymbolicName>${bundle-symbolic-name};singleton:=true</Bundle-SymbolicName>
+              <Bundle-Name>Jetty OSGi NPN Fragment</Bundle-Name>
+              <Bundle-Version>${parsedVersion.osgiVersion}</Bundle-Version>
+              <Export-Package>org.eclipse.jetty.npn</Export-Package>
+              <Fragment-Host>system.bundle;extension:=framework</Fragment-Host>
+            </manifestEntries>
+          </archive>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+</project>
diff --git a/jetty-osgi/pom.xml b/jetty-osgi/pom.xml
index 842d283..7dc9926 100644
--- a/jetty-osgi/pom.xml
+++ b/jetty-osgi/pom.xml
@@ -3,8 +3,7 @@
   <parent>
     <groupId>org.eclipse.jetty</groupId>
     <artifactId>jetty-project</artifactId>
-    <version>8.1.17.v20150415</version>
-    <relativePath>../pom.xml</relativePath>
+    <version>9.2.13.v20150730</version>
   </parent>
   <groupId>org.eclipse.jetty.osgi</groupId>
   <artifactId>jetty-osgi-project</artifactId>
@@ -16,8 +15,8 @@
     <osgi-services-version>3.2.100.v20100503</osgi-services-version>
     <equinox-http-servlet-version>1.0.0-v20070606</equinox-http-servlet-version>
     <!--equinox-servletbridge-version>1.0.0-v20070523</equinox-servletbridge-version-->
-    <logback-version>0.9.18</logback-version>
-    <slf4j-version>1.5.11</slf4j-version>
+    <logback-version>0.9.29</logback-version>
+    <slf4j-version>1.6.1</slf4j-version>
   </properties>
   <modules>
     <module>jetty-osgi-boot</module>
@@ -27,7 +26,21 @@
     <module>test-jetty-osgi-webapp</module>
     <module>test-jetty-osgi-context</module>
     <module>test-jetty-osgi</module>
+    <module>jetty-osgi-alpn</module>
   </modules>
+  <profiles>
+    <profile>
+      <id>npn</id>
+      <activation>
+        <jdk>1.7</jdk>
+      </activation>
+      <modules>
+<!--
+        <module>jetty-osgi-npn</module>
+-->
+      </modules>
+    </profile>
+  </profiles>
   <build>
     <resources>
       <resource>
@@ -58,13 +71,6 @@
     <plugins>
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-surefire-plugin</artifactId>
-        <configuration>
-          <skipTests>true</skipTests>
-        </configuration>
-      </plugin>
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-eclipse-plugin</artifactId>
         <version>2.8</version>
         <configuration>
@@ -134,6 +140,13 @@
         <groupId>org.eclipse.osgi</groupId>
         <artifactId>org.eclipse.osgi.services</artifactId>
         <version>${osgi-services-version}</version>
+        <exclusions>
+          <exclusion>
+            <!-- we use the servlet jar from orbit -->
+            <groupId>javax.servlet</groupId>
+            <artifactId>servlet-api</artifactId>
+          </exclusion>
+        </exclusions>
       </dependency>
       <dependency>
         <groupId>org.eclipse.osgi</groupId>
@@ -145,28 +158,6 @@
         <artifactId>servlet</artifactId>
         <version>${equinox-http-servlet-version}</version>
       </dependency>
-      <!--dependency>
-        <groupId>org.eclipse.equinox</groupId>
-        <artifactId>servletbridge</artifactId>
-        <version>${equinox-servletbridge-version}</version>
-      </dependency-->
-      <!-- not ready <dependency>
-        <groupId>org.mortbay.jetty</groupId>
-        <artifactId>jsp-impl</artifactId>
-        <version>${jsp-impl-2.2-glassfish-version}</version>
-      </dependency-->
-<!--
-      <dependency>
-        <groupId>org.mortbay.jetty</groupId>
-        <artifactId>jsp-2.1-glassfish</artifactId>
-        <version>${jsp-2.1-glassfish-version}</version>
-      </dependency>
-      <dependency>
-        <groupId>org.mortbay.jetty</groupId>
-        <artifactId>jsp-api-2.1-glassfish</artifactId>
-        <version>${jsp-2.1-glassfish-version}</version>
-      </dependency>
--->
       <dependency>
         <groupId>org.slf4j</groupId>
         <artifactId>slf4j-api</artifactId>
diff --git a/jetty-osgi/test-jetty-osgi-context/pom.xml b/jetty-osgi/test-jetty-osgi-context/pom.xml
index 69bfaff..82c0fdf 100644
--- a/jetty-osgi/test-jetty-osgi-context/pom.xml
+++ b/jetty-osgi/test-jetty-osgi-context/pom.xml
@@ -2,8 +2,7 @@
   <parent>
     <groupId>org.eclipse.jetty.osgi</groupId>
     <artifactId>jetty-osgi-project</artifactId>
-    <version>8.1.17.v20150415</version>
-    <relativePath>../pom.xml</relativePath>
+    <version>9.2.13.v20150730</version>
   </parent>
   <modelVersion>4.0.0</modelVersion>
   <artifactId>test-jetty-osgi-context</artifactId>
@@ -22,10 +21,16 @@
     <dependency>
     	<groupId>org.eclipse.osgi</groupId>
     	<artifactId>org.eclipse.osgi</artifactId>
+        <scope>provided</scope>
     </dependency>
     <dependency>
         <groupId>org.eclipse.osgi</groupId>
         <artifactId>org.eclipse.osgi.services</artifactId>
+        <scope>provided</scope>
+    </dependency>
+    <dependency>
+        <groupId>org.eclipse.jetty.toolchain</groupId>
+        <artifactId>jetty-schemas</artifactId>
     </dependency>
   </dependencies>
 
@@ -39,29 +44,15 @@
           </resource>
         </resources>
 
-        <plugins> 
-<!--
-            <plugin> 
-                <artifactId>maven-antrun-plugin</artifactId> 
-                <executions> 
-                    <execution> 
-                        <phase>process-resources</phase> 
-                        <configuration> 
-                            <tasks>
-                                <copy todir="target/classes/jettyhome"> 
-                                    <fileset dir="jettyhome"> 
-                                        <exclude name="**/*.log" /> 
-                                    </fileset> 
-                                </copy> 
-                            </tasks> 
-                        </configuration> 
-                        <goals> 
-                            <goal>run</goal> 
-                        </goals>
-                    </execution>
-                </executions>
+        <plugins>
+          <plugin> 
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-deploy-plugin</artifactId>
+              <configuration>
+              <!-- DO NOT DEPLOY (or Release) -->
+                <skip>true</skip>
+              </configuration>
             </plugin>
--->
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId> 
                 <artifactId>maven-jar-plugin</artifactId> 
@@ -109,8 +100,8 @@
                         compilation time. --> 
                         <_nouses>true</_nouses>
                         <Import-Package>
- javax.servlet;version="2.6.0",
- javax.servlet.resources;version="2.6.0",
+ javax.servlet;version="[3.1,3.2)",
+ javax.servlet.resources;version="[3.1,3.2)",
  org.osgi.framework,
  org.osgi.service.cm;version="1.2.0",
  org.osgi.service.packageadmin,
@@ -124,9 +115,7 @@
  org.xml.sax.helpers,
  *
                         </Import-Package>
-                        <DynamicImport-Package>org.eclipse.jetty.*;version="[8.1,9)"</DynamicImport-Package>
-                        <!--Require-Bundle/-->
-                        <!-- Bundle-RequiredExecutionEnvironment>J2SE-1.5</Bundle-RequiredExecutionEnvironment --> 
+                        <DynamicImport-Package>org.eclipse.jetty.*;version="[9.1,10.0)"</DynamicImport-Package>
                     </instructions>
                 </configuration>
             </plugin>
diff --git a/jetty-osgi/test-jetty-osgi-context/src/main/context/acme.xml b/jetty-osgi/test-jetty-osgi-context/src/main/context/acme.xml
index 53bbbdb..1ff97a4 100644
--- a/jetty-osgi/test-jetty-osgi-context/src/main/context/acme.xml
+++ b/jetty-osgi/test-jetty-osgi-context/src/main/context/acme.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0"  encoding="ISO-8859-1"?>
+<?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
 
 <Configure class="org.eclipse.jetty.server.handler.ContextHandler">
diff --git a/jetty-osgi/test-jetty-osgi-context/src/main/java/com/acme/osgi/Activator.java b/jetty-osgi/test-jetty-osgi-context/src/main/java/com/acme/osgi/Activator.java
index ec3677b..ca47ddd 100644
--- a/jetty-osgi/test-jetty-osgi-context/src/main/java/com/acme/osgi/Activator.java
+++ b/jetty-osgi/test-jetty-osgi-context/src/main/java/com/acme/osgi/Activator.java
@@ -49,17 +49,20 @@ public class Activator implements BundleActivator
     public void start(final BundleContext context) throws Exception
     {
         ContextHandler ch = new ContextHandler();
-        ch.addEventListener(new ServletContextListener () 
-        {
+        ch.addEventListener(new ServletContextListener () {
+
+            @Override
             public void contextInitialized(ServletContextEvent sce)
             {
-               System.err.println("Context is initialized");
+               // System.err.println("Context is initialized");
             }
 
+            @Override
             public void contextDestroyed(ServletContextEvent sce)
             {
-                System.err.println("CONTEXT IS DESTROYED!");                
+                // System.err.println("CONTEXT IS DESTROYED!");                
             }
+            
         });
         Dictionary props = new Hashtable();
         props.put("contextPath","/acme");
diff --git a/jetty-osgi/test-jetty-osgi-webapp/pom.xml b/jetty-osgi/test-jetty-osgi-webapp/pom.xml
index b14d773..e7ef3e1 100644
--- a/jetty-osgi/test-jetty-osgi-webapp/pom.xml
+++ b/jetty-osgi/test-jetty-osgi-webapp/pom.xml
@@ -2,7 +2,7 @@
   <parent>
     <groupId>org.eclipse.jetty.osgi</groupId>
     <artifactId>jetty-osgi-project</artifactId>
-    <version>8.1.17.v20150415</version>
+    <version>9.2.13.v20150730</version>
     <relativePath>../pom.xml</relativePath>
   </parent>
   <modelVersion>4.0.0</modelVersion>
@@ -21,10 +21,12 @@
     <dependency>
     	<groupId>org.eclipse.osgi</groupId>
     	<artifactId>org.eclipse.osgi</artifactId>
+        <scope>provided</scope>
     </dependency>
     <dependency>
         <groupId>org.eclipse.osgi</groupId>
         <artifactId>org.eclipse.osgi.services</artifactId>
+        <scope>provided</scope>
     </dependency>
   </dependencies>
 
@@ -35,29 +37,15 @@
           </resource>
         </resources>
 
-        <plugins> 
-<!--
-            <plugin> 
-                <artifactId>maven-antrun-plugin</artifactId> 
-                <executions> 
-                    <execution> 
-                        <phase>process-resources</phase> 
-                        <configuration> 
-                            <tasks>
-                                <copy todir="target/classes/jettyhome"> 
-                                    <fileset dir="jettyhome"> 
-                                        <exclude name="**/*.log" /> 
-                                    </fileset> 
-                                </copy> 
-                            </tasks> 
-                        </configuration> 
-                        <goals> 
-                            <goal>run</goal> 
-                        </goals>
-                    </execution>
-                </executions>
-            </plugin>
--->
+        <plugins>
+           <plugin>
+             <groupId>org.apache.maven.plugins</groupId>
+             <artifactId>maven-deploy-plugin</artifactId>
+             <configuration>
+               <!-- DO NOT DEPLOY (or Release) -->
+               <skip>true</skip>
+             </configuration>
+            </plugin> 
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId> 
                 <artifactId>maven-jar-plugin</artifactId> 
@@ -118,9 +106,7 @@
  org.xml.sax.helpers,
  *
                         </Import-Package>
-                        <DynamicImport-Package>org.eclipse.jetty.*;version="[8.1,9)"</DynamicImport-Package>
-                        <!--Require-Bundle/-->
-                        <!-- Bundle-RequiredExecutionEnvironment>J2SE-1.5</Bundle-RequiredExecutionEnvironment --> 
+                        <DynamicImport-Package>org.eclipse.jetty.*;version="[9.1,10.0)"</DynamicImport-Package>
                     </instructions>
                 </configuration>
             </plugin>
diff --git a/jetty-osgi/test-jetty-osgi-webapp/src/main/java/com/acme/osgi/Activator.java b/jetty-osgi/test-jetty-osgi-webapp/src/main/java/com/acme/osgi/Activator.java
index 5681ce9..ee2488a 100644
--- a/jetty-osgi/test-jetty-osgi-webapp/src/main/java/com/acme/osgi/Activator.java
+++ b/jetty-osgi/test-jetty-osgi-webapp/src/main/java/com/acme/osgi/Activator.java
@@ -46,11 +46,30 @@ public class Activator implements BundleActivator
      */
     public void start(BundleContext context) throws Exception
     {
+        String serverName = "defaultJettyServer";
+        
+        /* Uncomment to create a different server instance to deploy to. Also change
+         * TestJettyOSGiBootWebAppAsService to use the port 9999
+         
+        Server server = new Server();
+        //do any setup on Server in here
+        serverName = "fooServer";
+        Dictionary serverProps = new Hashtable();
+        //define the unique name of the server instance
+        serverProps.put("managedServerName", serverName);
+        serverProps.put("jetty.port", "9999");
+        //let Jetty apply some configuration files to the Server instance
+        serverProps.put("jetty.etc.config.urls", "file:/opt/jetty/etc/jetty.xml,file:/opt/jetty/etc/jetty-selector.xml,file:/opt/jetty/etc/jetty-deployer.xml");
+        //register as an OSGi Service for Jetty to find 
+        context.registerService(Server.class.getName(), server, serverProps);
+        */
+        
+        //Create a webapp context as a Service and target it at the Server created above
         WebAppContext webapp = new WebAppContext();
         Dictionary props = new Hashtable();
         props.put("war",".");
         props.put("contextPath","/acme");
-        //uiProps.put(OSGiServerConstants.MANAGED_JETTY_SERVER_NAME, serverName);
+        props.put("managedServerName", serverName);
         context.registerService(ContextHandler.class.getName(),webapp,props);
     }
 
diff --git a/jetty-osgi/test-jetty-osgi/pom.xml b/jetty-osgi/test-jetty-osgi/pom.xml
index a9aab5d..4292171 100644
--- a/jetty-osgi/test-jetty-osgi/pom.xml
+++ b/jetty-osgi/test-jetty-osgi/pom.xml
@@ -2,7 +2,7 @@
   <parent>
     <groupId>org.eclipse.jetty.osgi</groupId>
     <artifactId>jetty-osgi-project</artifactId>
-    <version>8.1.17.v20150415</version>
+    <version>9.2.13.v20150730</version>
     <relativePath>../pom.xml</relativePath>
   </parent>
   <modelVersion>4.0.0</modelVersion>
@@ -11,48 +11,174 @@
   <description>Jetty OSGi Integration test</description>
   <url>http://www.eclipse.org/jetty</url>
   <properties>
-    <bundle-symbolic-name>${project.groupId}.boot.test</bundle-symbolic-name>
+    <bundle-symbolic-name>${project.groupId}.boot.test.spdy</bundle-symbolic-name>
     <jetty-orbit-url>http://download.eclipse.org/jetty/orbit/</jetty-orbit-url>
     <assembly-directory>target/distribution</assembly-directory>
-    <paxexam-version>1.2.0</paxexam-version>
+    <exam.version>3.5.0</exam.version>
+    <url.version>1.5.2</url.version>
+    <injection.bundle.version>1.0</injection.bundle.version>
   </properties>
   <dependencies>
-    <!-- Orbit Servlet Deps -->
+    <!-- Pax Exam Dependencies -->
+
+    <dependency>
+      <groupId>org.ops4j.pax.exam</groupId>
+      <artifactId>pax-exam</artifactId>
+      <version>${exam.version}</version>
+      <scope>test</scope>
+    </dependency>
     <dependency>
-      <groupId>org.eclipse.jetty.orbit</groupId>
-      <artifactId>javax.servlet</artifactId>
+      <groupId>org.ops4j.pax.exam</groupId>
+      <artifactId>pax-exam-inject</artifactId>
+      <version>${exam.version}</version>
+      <scope>test</scope>
     </dependency>
+    
 
-    <!-- Orbit JSP Deps -->
+    <!-- use the forked container so we can pass it system properties eg for npn/alpn -->
     <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-jsp</artifactId>
-      <version>${project.version}</version>
+        <groupId>org.ops4j.pax.exam</groupId>
+        <artifactId>pax-exam-container-forked</artifactId>
+        <version>${exam.version}</version>
+        <scope>test</scope>
+    </dependency>
+ 
+    <dependency>
+      <groupId>org.ops4j.pax.exam</groupId>
+      <artifactId>pax-exam-junit4</artifactId>
+      <version>${exam.version}</version>
+      <scope>test</scope>
+      <exclusions>
+        <exclusion>
+          <groupId>junit</groupId>
+          <artifactId>junit</artifactId>
+        </exclusion>
+      </exclusions>
+    </dependency>
+    <dependency>
+      <groupId>org.ops4j.pax.exam</groupId>
+      <artifactId>pax-exam-link-mvn</artifactId>
+      <version>${exam.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.ops4j.pax.url</groupId>
+      <artifactId>pax-url-aether</artifactId>
+      <version>${url.version}</version>
+      <scope>test</scope>
     </dependency>
 
-    <!-- OSGi Deps -->
+    <!-- OSGi R4 frameworks -->
+<!--
+    <dependency>
+      <groupId>org.apache.felix</groupId>
+      <artifactId>org.apache.felix.framework</artifactId>
+      <version>4.4.0</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.osgi</groupId>
+      <artifactId>org.osgi.enterprise</artifactId>
+      <version>5.0.0</version>
+      <scope>test</scope>
+    </dependency>
+-->
+    <dependency>
+      <groupId>org.eclipse</groupId>
+      <artifactId>osgi</artifactId>
+      <version>3.10.0-v20140606-1445</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+        <groupId>org.eclipse.osgi</groupId>
+        <artifactId>org.eclipse.osgi.services</artifactId>
+        <scope>test</scope>
+    </dependency>
+
+
+    <!-- Jetty OSGi Deps -->
     <dependency>
       <groupId>org.eclipse.jetty.osgi</groupId>
       <artifactId>jetty-osgi-boot</artifactId>
       <version>${project.version}</version>
-      <scope>provided</scope>
+      <scope>test</scope>
+      <exclusions>
+        <exclusion>
+          <groupId>org.eclipse.osgi</groupId>
+          <artifactId>org.eclipse.osgi</artifactId>
+        </exclusion>
+        <exclusion>
+          <groupId>org.eclipse.osgi</groupId>
+          <artifactId>org.eclipse.osgi.services</artifactId>
+        </exclusion>
+      </exclusions>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jetty.osgi</groupId>
       <artifactId>jetty-osgi-boot-jsp</artifactId>
       <version>${project.version}</version>
-      <scope>provided</scope>
+      <scope>test</scope>
+      <exclusions>
+        <exclusion>
+          <groupId>org.eclipse.osgi</groupId>
+          <artifactId>org.eclipse.osgi</artifactId>
+        </exclusion>
+        <exclusion>
+          <groupId>org.eclipse.osgi</groupId>
+          <artifactId>org.eclipse.osgi.services</artifactId>
+        </exclusion>
+      </exclusions>
     </dependency>
+
     <dependency>
       <groupId>org.eclipse.jetty.osgi</groupId>
       <artifactId>jetty-httpservice</artifactId>
       <version>${project.version}</version>
-      <scope>provided</scope>
+      <scope>test</scope>
     </dependency>
 
+    <dependency>
+      <groupId>org.eclipse.jetty.toolchain</groupId>
+      <artifactId>jetty-osgi-servlet-api</artifactId>
+      <version>3.1.0.M3</version>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.geronimo.specs</groupId>
+      <artifactId>geronimo-jta_1.1_spec</artifactId>
+      <version>1.1.1</version>
+      <scope>test</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.geronimo.specs</groupId>
+      <artifactId>geronimo-atinject_1.0_spec</artifactId>
+      <version>${injection.bundle.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+     <groupId>org.apache.aries.spifly</groupId>
+      <artifactId>org.apache.aries.spifly.dynamic.bundle</artifactId>
+      <version>1.0.1</version>
+     <scope>test</scope>
+    </dependency>
+
+    <dependency>
+     <groupId>org.mortbay.jasper</groupId>
+      <artifactId>apache-el</artifactId>
+      <version>8.0.9.M3</version>
+     <scope>test</scope>
+    </dependency>
+
+
     <!-- Jetty Deps -->
     <dependency>
       <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-annotations</artifactId>
+      <scope>runtime</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
       <artifactId>jetty-webapp</artifactId>
       <scope>runtime</scope>
     </dependency>
@@ -79,6 +205,12 @@
     </dependency>
     <dependency>
       <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-security</artifactId>
+      <version>${project.version}</version>
+      <scope>runtime</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
       <artifactId>jetty-xml</artifactId>
       <version>${project.version}</version>
       <scope>runtime</scope>
@@ -100,81 +232,149 @@
       <scope>runtime</scope>
     </dependency>
     <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-websocket</artifactId>
+      <groupId>org.eclipse.jetty.websocket</groupId>
+      <artifactId>websocket-api</artifactId>
+      <version>${project.version}</version>
       <scope>runtime</scope>
     </dependency>
     <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-plus</artifactId>
+      <groupId>org.eclipse.jetty.websocket</groupId>
+      <artifactId>websocket-common</artifactId>
       <version>${project.version}</version>
       <scope>runtime</scope>
     </dependency>
-
-    <!-- Eclipse OSGi Deps -->
     <dependency>
-      <groupId>org.eclipse.osgi</groupId>
-      <artifactId>org.eclipse.osgi</artifactId>
+      <groupId>org.eclipse.jetty.websocket</groupId>
+      <artifactId>websocket-client</artifactId>
+      <version>${project.version}</version>
       <scope>runtime</scope>
     </dependency>
     <dependency>
-      <groupId>org.eclipse.osgi</groupId>
-      <artifactId>org.eclipse.osgi.services</artifactId>
+      <groupId>org.eclipse.jetty.websocket</groupId>
+      <artifactId>websocket-servlet</artifactId>
+      <version>${project.version}</version>
       <scope>runtime</scope>
     </dependency>
     <dependency>
-      <groupId>org.eclipse.equinox.http</groupId>
-      <artifactId>servlet</artifactId>
+      <groupId>org.eclipse.jetty.websocket</groupId>
+      <artifactId>websocket-server</artifactId>
+      <version>${project.version}</version>
       <scope>runtime</scope>
     </dependency>
-    
     <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>test-jetty-webapp</artifactId>
+      <groupId>javax.websocket</groupId>
+      <artifactId>javax.websocket-api</artifactId>
+      <scope>runtime</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.websocket</groupId>
+      <artifactId>javax-websocket-server-impl</artifactId>
+      <version>${project.version}</version>
+      <scope>runtime</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.websocket</groupId>
+      <artifactId>javax-websocket-client-impl</artifactId>
+      <version>${project.version}</version>
+      <scope>runtime</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.spdy</groupId>
+      <artifactId>spdy-core</artifactId>
+      <version>${project.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.spdy</groupId>
+      <artifactId>spdy-server</artifactId>
+      <version>${project.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.spdy</groupId>
+      <artifactId>spdy-http-server</artifactId>
       <version>${project.version}</version>
-      <classifier>webbundle</classifier>
       <scope>test</scope>
     </dependency>
-    
-   <!-- test osgi webapp service -->
+    <dependency>
+      <groupId>org.eclipse.jetty.spdy</groupId>
+      <artifactId>spdy-client</artifactId>
+      <version>${project.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.mortbay.jetty.alpn</groupId>
+      <artifactId>alpn-boot</artifactId>
+      <version>${alpn.version}</version>
+      <scope>test</scope>
+    </dependency>
     <dependency>
       <groupId>org.eclipse.jetty.osgi</groupId>
-      <artifactId>test-jetty-osgi-webapp</artifactId>
+      <artifactId>jetty-osgi-alpn</artifactId>
       <version>${project.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-alpn-server</artifactId>
+      <version>${project.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.toolchain</groupId>
+      <artifactId>jetty-schemas</artifactId>
       <scope>runtime</scope>
     </dependency>
 
- <!-- test osgi contexthandler  service -->
+
     <dependency>
-      <groupId>org.eclipse.jetty.osgi</groupId>
-      <artifactId>test-jetty-osgi-context</artifactId>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-plus</artifactId>
       <version>${project.version}</version>
       <scope>runtime</scope>
     </dependency>
 
-
+    <!-- Eclipse OSGi Deps -->
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>test-jetty-webapp</artifactId>
+      <version>${project.version}</version>
+      <classifier>webbundle</classifier>
+      <scope>test</scope>
+    </dependency>
 
     <dependency>
-      <groupId>org.ops4j.pax.exam</groupId>
-      <artifactId>pax-exam</artifactId>
-      <version>${paxexam-version}</version>
+      <groupId>org.eclipse.jetty.tests</groupId>
+      <artifactId>test-spec-webapp</artifactId>
+      <version>${project.version}</version>
+      <type>war</type>
       <scope>test</scope>
     </dependency>
+
     <dependency>
-      <groupId>org.ops4j.pax.exam</groupId>
-      <artifactId>pax-exam-junit</artifactId>
-      <version>${paxexam-version}</version>
+      <groupId>org.eclipse.jetty.tests</groupId>
+      <artifactId>test-container-initializer</artifactId>
+      <version>${project.version}</version>
       <scope>test</scope>
     </dependency>
+
+
     <dependency>
-      <groupId>org.ops4j.pax.exam</groupId>
-      <artifactId>pax-exam-container-default</artifactId>
-      <version>${paxexam-version}</version>
+      <groupId>org.eclipse.jetty.tests</groupId>
+      <artifactId>test-mock-resources</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+
+    <dependency>
+      <groupId>org.eclipse.jetty.osgi</groupId>
+      <artifactId>test-jetty-osgi-context</artifactId>
+      <version>${project.version}</version>
       <scope>test</scope>
     </dependency>
     <dependency>
-      <groupId>junit</groupId>
-      <artifactId>junit</artifactId>
+      <groupId>org.eclipse.jetty.osgi</groupId>
+      <artifactId>test-jetty-osgi-webapp</artifactId>
+      <version>${project.version}</version>
       <scope>test</scope>
     </dependency>
     <dependency>
@@ -182,97 +382,68 @@
       <artifactId>jetty-test-helper</artifactId>
       <scope>test</scope>
     </dependency>
-
   </dependencies>
-
   <build>
     <plugins>
       <plugin>
-        <groupId>org.ops4j.pax.exam</groupId>
-        <artifactId>maven-paxexam-plugin</artifactId>
-        <version>${paxexam-version}</version>
-        <executions>
-          <execution>
-            <id>generate-config</id>
-            <goals>
-              <goal>generate-depends-file</goal>
-              <goal>generate-config</goal>
-            </goals>
-          </execution>
-        </executions>
+        <artifactId>maven-surefire-plugin</artifactId>
         <configuration>
-          <options>
-            <workingDirectory>${project.build.directory}/paxexam</workingDirectory>
-          </options>
+          <!-- No point defining -Xbootclasspath as the actual OSGi VM is run as a forked process by pax-exam -->
+          <!-- But we do pass the sys property of the alpn-boot jar so that it can be configued inside tests -->
+          <argLine>-Dmortbay-alpn-boot=${settings.localRepository}/org/mortbay/jetty/alpn/alpn-boot/${alpn.version}/alpn-boot-${alpn.version}.jar</argLine>
         </configuration>
       </plugin>
-
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-dependency-plugin</artifactId>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <version>2.5.1</version>
+        <configuration>
+          <source>1.7</source>
+          <target>1.7</target>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.servicemix.tooling</groupId>
+        <artifactId>depends-maven-plugin</artifactId>
+        <version>1.2</version>
         <executions>
           <execution>
-            <id>copy-orbit-servlet-api-deps</id>
-            <phase>generate-resources</phase>
-            <goals>
-              <goal>copy</goal>
-            </goals>
-            <configuration>
-              <artifactItems>
-                <artifactItem>
-                  <groupId>org.eclipse.jetty.orbit</groupId>
-                  <artifactId>javax.servlet</artifactId>
-                  <version>${orbit-servlet-api-version}</version>
-                  <overWrite>true</overWrite>
-                  <outputDirectory>${assembly-directory}/lib</outputDirectory>
-                  <destFileName>servlet-api-3.0.jar</destFileName>
-                </artifactItem>
-              </artifactItems>
-            </configuration>
-          </execution>
-          <execution>
-            <id>copy-orbit-lib-jndi-deps</id>
-            <phase>generate-resources</phase>
-            <goals>
-              <goal>copy-dependencies</goal>
-            </goals>
-            <configuration>
-              <includeGroupIds>org.eclipse.jetty.orbit</includeGroupIds>
-              <includeArtifactIds>javax.mail.glassfish,javax.activation</includeArtifactIds>
-              <includeTypes>jar</includeTypes>
-              <outputDirectory>${assembly-directory}/lib/jndi</outputDirectory>
-            </configuration>
-          </execution>
-          <execution>
-            <id>copy-orbit-lib-jsp-deps</id>
-            <phase>generate-resources</phase>
+            <id>generate-depends-file</id>
             <goals>
-              <goal>copy-dependencies</goal>
+              <goal>generate-depends-file</goal>
             </goals>
-            <configuration>
-              <includeGroupIds>org.eclipse.jetty.orbit</includeGroupIds>
-              <includeArtifactIds>com.sun.el,javax.el,javax.servlet.jsp,javax.servlet.jsp.jstl,org.apache.jasper.glassfish,org.apache.taglibs.standard.glassfish,org.eclipse.jdt.core</includeArtifactIds>
-              <includeTypes>jar</includeTypes>
-              <outputDirectory>${assembly-directory}/lib/jsp</outputDirectory>
-            </configuration>
           </execution>
         </executions>
       </plugin>
-
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-eclipse-plugin</artifactId>
-        <version>2.8</version>
-        <configuration>
-          <manifest>prevent/overwriting/by/pointing/to/nonexisting/MANIFEST.MF</manifest>
-          <pde>false</pde>
-          <downloadSources>true</downloadSources>
-          <sourceExcludes>
-            <sourceExclude>**/.svn/**</sourceExclude>
-          </sourceExcludes>
-        </configuration>
-      </plugin>
     </plugins>
+    <pluginManagement>
+      <plugins>
+        <!--This plugin's configuration is used to store Eclipse m2e settings only. It has no influence on the Maven build itself.-->
+        <plugin>
+          <groupId>org.eclipse.m2e</groupId>
+          <artifactId>lifecycle-mapping</artifactId>
+          <version>1.0.0</version>
+          <configuration>
+            <lifecycleMappingMetadata>
+              <pluginExecutions>
+                <pluginExecution>
+                  <pluginExecutionFilter>
+                    <groupId>org.apache.servicemix.tooling</groupId>
+                    <artifactId>depends-maven-plugin</artifactId>
+                    <versionRange>[1.2,)</versionRange>
+                    <goals>
+                      <goal>generate-depends-file</goal>
+                    </goals>
+                  </pluginExecutionFilter>
+                  <action>
+                    <ignore />
+                  </action>
+                </pluginExecution>
+              </pluginExecutions>
+            </lifecycleMappingMetadata>
+          </configuration>
+        </plugin>
+      </plugins>
+    </pluginManagement>
   </build>
-
 </project>
diff --git a/jetty-osgi/test-jetty-osgi/src/main/resources/jetty-logging.properties b/jetty-osgi/test-jetty-osgi/src/main/resources/jetty-logging.properties
new file mode 100644
index 0000000..5250a08
--- /dev/null
+++ b/jetty-osgi/test-jetty-osgi/src/main/resources/jetty-logging.properties
@@ -0,0 +1,2 @@
+org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
+org.eclipse.jetty.spdy.LEVEL=WARN
diff --git a/jetty-spdy/spdy-jetty/src/test/resources/keystore.jks b/jetty-osgi/test-jetty-osgi/src/main/resources/keystore.jks
similarity index 100%
rename from jetty-spdy/spdy-jetty/src/test/resources/keystore.jks
rename to jetty-osgi/test-jetty-osgi/src/main/resources/keystore.jks
diff --git a/jetty-spdy/spdy-jetty/src/test/resources/truststore.jks b/jetty-osgi/test-jetty-osgi/src/main/resources/truststore.jks
similarity index 100%
rename from jetty-spdy/spdy-jetty/src/test/resources/truststore.jks
rename to jetty-osgi/test-jetty-osgi/src/main/resources/truststore.jks
diff --git a/jetty-osgi/test-jetty-osgi/src/test/config/etc/jetty-deployer.xml b/jetty-osgi/test-jetty-osgi/src/test/config/etc/jetty-deployer.xml
index ac35276..b03a648 100644
--- a/jetty-osgi/test-jetty-osgi/src/test/config/etc/jetty-deployer.xml
+++ b/jetty-osgi/test-jetty-osgi/src/test/config/etc/jetty-deployer.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0"?>
-<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
 
 <Configure id="Server" class="org.eclipse.jetty.server.Server">
 
@@ -10,32 +10,12 @@
       <Arg>
         <New id="DeploymentManager" class="org.eclipse.jetty.deploy.DeploymentManager">
           <Set name="contexts">
-            <Ref id="Contexts" />
+            <Ref refid="Contexts" />
           </Set>
           <Call name="setContextAttribute">
             <Arg>org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern</Arg>
             <Arg>.*/jsp-api-[^/]*\.jar$|.*/jsp-[^/]*\.jar$</Arg>
           </Call>
-          <!-- Providers of OSGi Apps -->
-          <!-- Call name="addAppProvider" -->
-            <!-- Arg -->
-              <!-- New class="org.eclipse.jetty.osgi.boot.OSGiAppProvider" -->
-              <!--
-                <Set name="defaultsDescriptor"><Property name="jetty.home" default="."/>/etc/webdefault.xml</Set>
-              -->
-              <!--
-                <Set name="scanInterval">5</Set>
-                <Set name="contextXmlDir"><Property name="jetty.home" default="." />/contexts</Set>
-                -->
-                <!-- comma separated list of bundle symbolic names that contain custom tag libraries (*.tld files) -->
-                <!-- if those bundles don't exist or can't be loaded no errors or warning will be issued!          -->
-                <!-- This default value plugs in the tld files of the reference implementation of JSF              -->
-                <!-- 
-                 <Set name="tldBundles"><Property name="org.eclipse.jetty.osgi.tldbundles" default="javax.faces.jsf-impl" /></Set>
-              </New>
-            </Arg>
-          </Call>
-          -->
         </New>
       </Arg>
     </Call>
diff --git a/jetty-osgi/test-jetty-osgi/src/test/config/etc/jetty-https.xml b/jetty-osgi/test-jetty-osgi/src/test/config/etc/jetty-https.xml
new file mode 100644
index 0000000..a6bef16
--- /dev/null
+++ b/jetty-osgi/test-jetty-osgi/src/test/config/etc/jetty-https.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0"?>
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
+
+<!-- ============================================================= -->
+<!-- Configure a HTTPS connector.                                  -->
+<!-- This configuration must be used in conjunction with jetty.xml -->
+<!-- and jetty-ssl.xml.                                            -->
+<!-- ============================================================= -->
+<Configure id="Server" class="org.eclipse.jetty.server.Server">
+
+  <!-- =========================================================== -->
+  <!-- Add a HTTPS Connector.                                      -->
+  <!-- Configure an o.e.j.server.ServerConnector with connection   -->
+  <!-- factories for TLS (aka SSL) and HTTP to provide HTTPS.      -->
+  <!-- All accepted TLS connections are wired to a HTTP connection.-->
+  <!--                                                             -->
+  <!-- Consult the javadoc of o.e.j.server.ServerConnector,        -->
+  <!-- o.e.j.server.SslConnectionFactory and                       -->
+  <!-- o.e.j.server.HttpConnectionFactory for all configuration    -->
+  <!-- that may be set here.                                       -->
+  <!-- =========================================================== -->
+  <Call id="httpsConnector" name="addConnector">
+    <Arg>
+      <New class="org.eclipse.jetty.server.ServerConnector">
+        <Arg name="server"><Ref refid="Server" /></Arg>
+          <Arg name="factories">
+            <Array type="org.eclipse.jetty.server.ConnectionFactory">
+              <Item>
+                <New class="org.eclipse.jetty.server.SslConnectionFactory">
+                  <Arg name="next">http/1.1</Arg>
+                  <Arg name="sslContextFactory"><Ref refid="sslContextFactory"/></Arg>
+                </New>
+              </Item>
+              <Item>
+                <New class="org.eclipse.jetty.server.HttpConnectionFactory">
+                  <Arg name="config"><Ref refid="sslHttpConfig"/></Arg>
+                </New>
+              </Item>
+            </Array>
+          </Arg>
+          <Set name="host"><Property name="jetty.host" /></Set>
+          <Set name="port"><Property name="jetty.https.port" default="8443" /></Set>
+          <Set name="idleTimeout">30000</Set>
+        </New>
+    </Arg>
+  </Call>
+</Configure>
diff --git a/jetty-osgi/test-jetty-osgi/src/test/config/etc/jetty-selector.xml b/jetty-osgi/test-jetty-osgi/src/test/config/etc/jetty-selector.xml
index 46ccc85..67cd87c 100644
--- a/jetty-osgi/test-jetty-osgi/src/test/config/etc/jetty-selector.xml
+++ b/jetty-osgi/test-jetty-osgi/src/test/config/etc/jetty-selector.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0"?>
-<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
 
 <Configure id="Server" class="org.eclipse.jetty.server.Server">
 
@@ -10,17 +10,22 @@
 
     <Call name="addConnector">
       <Arg>
-          <New class="org.eclipse.jetty.server.nio.SelectChannelConnector">
+          <New class="org.eclipse.jetty.server.ServerConnector">
+            <Arg><Ref refid="Server" /></Arg>
+             <Arg name="factories">
+               <Array type="org.eclipse.jetty.server.ConnectionFactory">
+                <Item>
+                  <New class="org.eclipse.jetty.server.HttpConnectionFactory">
+                   <Arg name="config"><Ref refid="httpConfig" /></Arg>
+                  </New>
+                </Item>
+              </Array>
+             </Arg>
             <Set name="host"><Property name="jetty.host" /></Set>
             <Set name="port"><Property name="jetty.port" default="8080"/></Set>
-            <Set name="maxIdleTime">300000</Set>
-            <Set name="Acceptors">2</Set>
-            <Set name="statsOn">false</Set>
-            <Set name="confidentialPort">8443</Set>
-        <Set name="lowResourcesConnections">20000</Set>
-        <Set name="lowResourcesMaxIdleTime">5000</Set>
+            <Set name="idleTimeout">300000</Set>
           </New>
       </Arg>
     </Call>
-    
+
 </Configure>
diff --git a/jetty-osgi/test-jetty-osgi/src/test/config/etc/jetty-spdy.xml b/jetty-osgi/test-jetty-osgi/src/test/config/etc/jetty-spdy.xml
new file mode 100644
index 0000000..2bc1fe3
--- /dev/null
+++ b/jetty-osgi/test-jetty-osgi/src/test/config/etc/jetty-spdy.xml
@@ -0,0 +1,115 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
+
+<Configure id="Server" class="org.eclipse.jetty.server.Server">
+
+    <!-- =========================================================== -->
+    <!-- Add HTTP Customizer for Secure request                      -->
+    <!-- =========================================================== -->
+    <New id="httpConfig" class="org.eclipse.jetty.server.HttpConfiguration">
+        <Set name="secureScheme">https</Set>
+        <Set name="securePort">
+            <SystemProperty name="jetty.spdy.port" default="8443"/>
+        </Set>
+        <Set name="outputBufferSize">32768</Set>
+        <Set name="requestHeaderSize">8192</Set>
+        <Set name="responseHeaderSize">8192</Set>
+        <Call name="addCustomizer">
+            <Arg>
+                <New class="org.eclipse.jetty.server.SecureRequestCustomizer"/>
+            </Arg>
+        </Call>
+    </New>
+
+
+    <!-- =========================================================== -->
+    <!-- Setup a SSL Context factory                                 -->
+    <!-- =========================================================== -->
+    <New id="sslContextFactory" class="org.eclipse.jetty.util.ssl.SslContextFactory">
+        <Set name="KeyStorePath"><Property name="jetty.home" default="."/>/etc/keystore
+        </Set>
+        <Set name="KeyStorePassword">OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4</Set>
+        <Set name="KeyManagerPassword">OBF:1u2u1wml1z7s1z7a1wnl1u2g</Set>
+        <Set name="TrustStorePath"><Property name="jetty.home" default="."/>/etc/keystore
+        </Set>
+        <Set name="TrustStorePassword">OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4</Set>
+    </New>
+
+    <!-- =========================================================== -->
+    <!-- Set connectors                                              -->
+    <!-- =========================================================== -->
+    <Call id="sslConnector" name="addConnector">
+        <Arg>
+            <New class="org.eclipse.jetty.server.ServerConnector">
+                <Arg name="server">
+                    <Ref refid="Server"/>
+                </Arg>
+                <Arg name="factories">
+                    <Array type="org.eclipse.jetty.server.ConnectionFactory">
+                        <Item>
+                            <New class="org.eclipse.jetty.server.SslConnectionFactory">
+                                <Arg name="next">alpn</Arg>
+                                <Arg name="sslContextFactory">
+                                    <Ref refid="sslContextFactory"/>
+                                </Arg>
+                            </New>
+                        </Item>
+
+                        <Item>
+                            <New class="org.eclipse.jetty.alpn.server.ALPNServerConnectionFactory">
+                                <Arg name="protocols">
+                                    <Array type="String">
+                                        <Item>spdy/3</Item>
+                                        <Item>spdy/2</Item>
+                                        <Item>http/1.1</Item>
+                                    </Array>
+                                </Arg>
+                                <Set name="defaultProtocol">http/1.1</Set>
+                            </New>
+                        </Item>
+
+                        <Item>
+                            <New class="org.eclipse.jetty.spdy.server.http.HTTPSPDYServerConnectionFactory">
+                                <Arg name="version" type="int">3</Arg>
+                                <Arg name="config">
+                                    <Ref refid="httpConfig"/>
+                                </Arg>
+                                <!-- Set the initial window size for this SPDY connector. -->
+                                <!-- See: http://www.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3#TOC-2.6.8-WINDOW_UPDATE -->
+                                <Set name="initialWindowSize">65536</Set>
+                            </New>
+                        </Item>
+
+                        <Item>
+                            <New class="org.eclipse.jetty.spdy.server.http.HTTPSPDYServerConnectionFactory">
+                                <Arg name="version" type="int">2</Arg>
+                                <Arg name="config">
+                                    <Ref refid="httpConfig"/>
+                                </Arg>
+                                <!-- Set the initial window size for this SPDY connector. -->
+                                <!-- See: http://www.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3#TOC-2.6.8-WINDOW_UPDATE -->
+                                <Set name="initialWindowSize">65536</Set>
+                            </New>
+                        </Item>
+
+                        <Item>
+                            <New class="org.eclipse.jetty.server.HttpConnectionFactory">
+                                <Arg name="config">
+                                    <Ref refid="httpConfig"/>
+                                </Arg>
+                            </New>
+                        </Item>
+                    </Array>
+                </Arg>
+                <Set name="host">
+                    <Property name="jetty.host"/>
+                </Set>
+                <Set name="port">
+                    <SystemProperty name="jetty.spdy.port" default="8443"/>
+                </Set>
+                <Set name="idleTimeout">30000</Set>
+            </New>
+        </Arg>
+    </Call>
+
+</Configure>
diff --git a/jetty-osgi/test-jetty-osgi/src/test/config/etc/jetty-ssl.xml b/jetty-osgi/test-jetty-osgi/src/test/config/etc/jetty-ssl.xml
new file mode 100644
index 0000000..b4c3551
--- /dev/null
+++ b/jetty-osgi/test-jetty-osgi/src/test/config/etc/jetty-ssl.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0"?>
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
+
+<!-- ============================================================= -->
+<!-- Configure a TLS (SSL) Context Factory                         -->
+<!-- This configuration must be used in conjunction with jetty.xml -->
+<!-- and either jetty-https.xml or jetty-spdy.xml (but not both)   -->
+<!-- ============================================================= -->
+<Configure id="sslContextFactory" class="org.eclipse.jetty.util.ssl.SslContextFactory">
+  <Set name="KeyStorePath"><Property name="jetty.home" default="." />/<Property name="jetty.keystore" default="etc/keystore"/></Set>
+  <Set name="KeyStorePassword"><Property name="jetty.keystore.password" default="OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4"/></Set>
+  <Set name="KeyManagerPassword"><Property name="jetty.keymanager.password" default="OBF:1u2u1wml1z7s1z7a1wnl1u2g"/></Set>
+  <Set name="TrustStorePath"><Property name="jetty.home" default="." />/<Property name="jetty.truststore" default="etc/keystore"/></Set>
+  <Set name="TrustStorePassword"><Property name="jetty.truststore.password" default="OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4"/></Set>
+  <Set name="EndpointIdentificationAlgorithm"></Set>
+  <Set name="ExcludeCipherSuites">
+    <Array type="String">
+      <Item>SSL_RSA_WITH_DES_CBC_SHA</Item>
+      <Item>SSL_DHE_RSA_WITH_DES_CBC_SHA</Item>
+      <Item>SSL_DHE_DSS_WITH_DES_CBC_SHA</Item>
+      <Item>SSL_RSA_EXPORT_WITH_RC4_40_MD5</Item>
+      <Item>SSL_RSA_EXPORT_WITH_DES40_CBC_SHA</Item>
+      <Item>SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA</Item>
+      <Item>SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA</Item>
+    </Array>
+  </Set>
+
+  <!-- =========================================================== -->
+  <!-- Create a TLS specific HttpConfiguration based on the        -->
+  <!-- common HttpConfiguration defined in jetty.xml               -->
+  <!-- Add a SecureRequestCustomizer to extract certificate and    -->
+  <!-- session information                                         -->
+  <!-- =========================================================== -->
+  <New id="sslHttpConfig" class="org.eclipse.jetty.server.HttpConfiguration">
+    <Arg><Ref refid="httpConfig"/></Arg>
+    <Call name="addCustomizer">
+      <Arg><New class="org.eclipse.jetty.server.SecureRequestCustomizer"/></Arg>
+    </Call>
+  </New>
+
+</Configure>
diff --git a/jetty-osgi/test-jetty-osgi/src/test/config/etc/jetty-testrealm.xml b/jetty-osgi/test-jetty-osgi/src/test/config/etc/jetty-testrealm.xml
index a9de4e6..d26b427 100644
--- a/jetty-osgi/test-jetty-osgi/src/test/config/etc/jetty-testrealm.xml
+++ b/jetty-osgi/test-jetty-osgi/src/test/config/etc/jetty-testrealm.xml
@@ -1,13 +1,11 @@
 <?xml version="1.0"?>
-<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
-
+<!DOCTYPE Configure PUBLIC "-" "http://www.eclipse.org/jetty/configure_9_0.dtd">
 <Configure id="Server" class="org.eclipse.jetty.server.Server">
-
     <!-- =========================================================== -->
     <!-- Configure Authentication Login Service                      -->
     <!-- Realms may be configured for the entire server here, or     -->
     <!-- they can be configured for a specific web app in a context  -->
-    <!-- configuration (see $(jetty.home)/contexts/test.xml for an   -->
+    <!-- configuration (see $(jetty.home)/webapps/test.xml for an    -->
     <!-- example).                                                   -->
     <!-- =========================================================== -->
     <Call name="addBean">
@@ -19,5 +17,4 @@
         </New>
       </Arg>
     </Call>
-
 </Configure>
diff --git a/jetty-osgi/test-jetty-osgi/src/test/config/etc/jetty.xml b/jetty-osgi/test-jetty-osgi/src/test/config/etc/jetty.xml
index 30f8ea9..170530b 100644
--- a/jetty-osgi/test-jetty-osgi/src/test/config/etc/jetty.xml
+++ b/jetty-osgi/test-jetty-osgi/src/test/config/etc/jetty.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0"?>
-<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
 
 
 <!-- =============================================================== -->
@@ -14,18 +14,14 @@
     <!-- =========================================================== -->
     <!-- Server Thread Pool                                          -->
     <!-- =========================================================== -->
-    <Set name="ThreadPool">
+    <Get name="ThreadPool">
       <!-- Default queued blocking threadpool -->
-      <New class="org.eclipse.jetty.util.thread.QueuedThreadPool">
-        <Set name="minThreads">10</Set>
-        <Set name="maxThreads">200</Set>
-        <Set name="detailedDump">false</Set>
-      </New>
-    </Set>
-
+      <Set name="minThreads">10</Set>
+      <Set name="maxThreads">200</Set>
+    </Get>
 
     <!-- =========================================================== -->
-    <!-- Set handler Collection Structure                            --> 
+    <!-- Set handler Collection Structure                            -->
     <!-- =========================================================== -->
     <Set name="handler">
       <New id="Handlers" class="org.eclipse.jetty.server.handler.HandlerCollection">
@@ -45,21 +41,44 @@
       </New>
     </Set>
 
+    <New id="httpConfig" class="org.eclipse.jetty.server.HttpConfiguration">
+      <Set name="secureScheme">https</Set>
+      <Set name="securePort"><Property name="jetty.secure.port" default="8443" /></Set>
+      <Set name="outputBufferSize">32768</Set>
+      <Set name="requestHeaderSize">8192</Set>
+      <Set name="responseHeaderSize">8192</Set>
+      <Set name="sendServerVersion">true</Set>
+      <Set name="sendDateHeader">false</Set>
+      <Set name="headerCacheSize">512</Set>
+    </New>
+
 
     <!-- =========================================================== -->
     <!-- extra options                                               -->
     <!-- =========================================================== -->
     <Set name="stopAtShutdown">true</Set>
-    <Set name="sendServerVersion">true</Set>
-    <Set name="sendDateHeader">true</Set>
-    <Set name="gracefulShutdown">1000</Set>
+    <Set name="stopTimeout">1000</Set>
     <Set name="dumpAfterStart">false</Set>
     <Set name="dumpBeforeStop">false</Set>
 
-    
+
     <!-- =========================================================== -->
     <!-- jetty-jndi by default                                       -->
     <!-- =========================================================== -->
+    <Call class="org.eclipse.jetty.webapp.Configuration$ClassList" name="setServerDefault">
+      <Arg><Ref refid="Server" /></Arg>
+      <Call name="addAfter">
+        <Arg name="afterClass">org.eclipse.jetty.webapp.FragmentConfiguration</Arg>
+        <Arg>
+          <Array type="String">
+            <Item>org.eclipse.jetty.plus.webapp.EnvConfiguration</Item>
+            <Item>org.eclipse.jetty.plus.webapp.PlusConfiguration</Item>
+            <Item>org.eclipse.jetty.annotations.AnnotationConfiguration</Item>
+          </Array>
+        </Arg>
+      </Call>
+    </Call>
+
     <Call class="java.lang.System" name="setProperty">
       <Arg>java.naming.factory.initial</Arg>
       <Arg><Property name="java.naming.factory.initial" default="org.eclipse.jetty.jndi.InitialContextFactory"/></Arg>
diff --git a/jetty-osgi/test-jetty-osgi/src/test/config/etc/keystore b/jetty-osgi/test-jetty-osgi/src/test/config/etc/keystore
new file mode 100644
index 0000000..08f6cda
Binary files /dev/null and b/jetty-osgi/test-jetty-osgi/src/test/config/etc/keystore differ
diff --git a/jetty-osgi/test-jetty-osgi/src/test/config/etc/webdefault.xml b/jetty-osgi/test-jetty-osgi/src/test/config/etc/webdefault.xml
new file mode 100644
index 0000000..1d61c93
--- /dev/null
+++ b/jetty-osgi/test-jetty-osgi/src/test/config/etc/webdefault.xml
@@ -0,0 +1,404 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!-- ===================================================================== -->
+<!-- This file contains the default descriptor for web applications.       -->
+<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+<!-- The intent of this descriptor is to include jetty specific or common  -->
+<!-- configuration for all webapps.   If a context has a webdefault.xml    -->
+<!-- descriptor, it is applied before the contexts own web.xml file        -->
+<!--                                                                       -->
+<!-- A context may be assigned a default descriptor by:                    -->
+<!--  + Calling WebApplicationContext.setDefaultsDescriptor                -->
+<!--  + Passed an arg to addWebApplications                                -->
+<!--                                                                       -->
+<!-- This file is used both as the resource within the jetty.jar (which is -->
+<!-- used as the default if no explicit defaults descriptor is set) and it -->
+<!-- is copied to the etc directory of the Jetty distro and explicitly     -->
+<!-- by the jetty.xml file.                                                -->
+<!--                                                                       -->
+<!-- ===================================================================== -->
+<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"
+   metadata-complete="true"
+   version="2.5">
+
+  <description>
+    Default web.xml file.
+    This file is applied to a Web application before it's own WEB_INF/web.xml file
+  </description>
+
+
+  <!-- ==================================================================== -->
+  <!-- Context params to control Session Cookies                            -->
+  <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -->
+  <!-- UNCOMMENT TO ACTIVATE
+  <context-param>
+    <param-name>org.eclipse.jetty.servlet.SessionDomain</param-name>
+    <param-value>127.0.0.1</param-value>
+  </context-param>
+
+  <context-param>
+    <param-name>org.eclipse.jetty.servlet.SessionPath</param-name>
+    <param-value>/</param-value>
+  </context-param>
+
+  <context-param>
+    <param-name>org.eclipse.jetty.servlet.MaxAge</param-name>
+    <param-value>-1</param-value>
+  </context-param>
+  -->
+
+
+  <!-- ==================================================================== -->
+  <!-- The default servlet.                                                 -->
+  <!-- This servlet, normally mapped to /, provides the handling for static -->
+  <!-- content, OPTIONS and TRACE methods for the context.                  -->
+  <!-- The following initParameters are supported:                          -->
+  <!--                                                                      -->
+  <!--   acceptRanges     If true, range requests and responses are         -->
+  <!--                    supported                                         -->
+  <!--                                                                      -->
+  <!--   dirAllowed       If true, directory listings are returned if no    -->
+  <!--                    welcome file is found. Else 403 Forbidden.        -->
+  <!--                                                                      -->
+  <!--   welcomeServlets  If true, attempt to dispatch to welcome files     -->
+  <!--                    that are servlets, if no matching static          -->
+  <!--                    resources can be found.                           -->
+  <!--                                                                      -->
+  <!--   redirectWelcome  If true, redirect welcome file requests           -->
+  <!--                    else use request dispatcher forwards              -->
+  <!--                                                                      -->
+  <!--   gzip             If set to true, then static content will be served-->
+  <!--                    as gzip content encoded if a matching resource is -->
+  <!--                    found ending with ".gz"                           -->
+  <!--                                                                      -->
+  <!--   resoureBase      Can be set to replace the context resource base   -->
+  <!--                                                                      -->
+  <!--   relativeResourceBase                                               -->
+  <!--                    Set with a pathname relative to the base of the   -->
+  <!--                    servlet context root. Useful for only serving     -->
+  <!--                    static content from only specific subdirectories. -->
+  <!--                                                                      -->
+  <!--   useFileMappedBuffer                                                -->
+  <!--                    If set to true (the default), a  memory mapped    -->
+  <!--                    file buffer will be used to serve static content  -->
+  <!--                    when using an NIO connector. Setting this value   -->
+  <!--                    to false means that a direct buffer will be used  -->
+  <!--                    instead. If you are having trouble with Windows   -->
+  <!--                    file locking, set this to false.                  -->
+  <!--                                                                      -->
+  <!--  cacheControl      If set, all static content will have this value   -->
+  <!--                    set as the cache-control header.                  -->
+  <!--                                                                      -->
+  <!--  maxCacheSize      Maximum size of the static resource cache         -->
+  <!--                                                                      -->
+  <!--  maxCachedFileSize Maximum size of any single file in the cache      -->
+  <!--                                                                      -->
+  <!--  maxCachedFiles    Maximum number of files in the cache              -->
+  <!--                                                                      -->
+  <!--  cacheType         "nio", "bio" or "both" to determine the type(s)   -->
+  <!--                    of resource cache. A bio cached buffer may be used-->
+  <!--                    by nio but is not as efficient as a nio buffer.   -->
+  <!--                    An nio cached buffer may not be used by bio.      -->
+  <!--                                                                      -->
+  <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -->
+  <servlet>
+    <servlet-name>default</servlet-name>
+    <servlet-class>org.eclipse.jetty.servlet.DefaultServlet</servlet-class>
+    <init-param>
+      <param-name>acceptRanges</param-name>
+      <param-value>true</param-value>
+    </init-param>
+    <init-param>
+      <param-name>dirAllowed</param-name>
+      <param-value>true</param-value>
+    </init-param>
+    <init-param>
+      <param-name>welcomeServlets</param-name>
+      <param-value>false</param-value>
+    </init-param>
+    <init-param>
+      <param-name>redirectWelcome</param-name>
+      <param-value>false</param-value>
+    </init-param>
+    <init-param>
+      <param-name>maxCacheSize</param-name>
+      <param-value>256000000</param-value>
+    </init-param>
+    <init-param>
+      <param-name>maxCachedFileSize</param-name>
+      <param-value>10000000</param-value>
+    </init-param>
+    <init-param>
+      <param-name>maxCachedFiles</param-name>
+      <param-value>1000</param-value>
+    </init-param>
+    <init-param>
+      <param-name>cacheType</param-name>
+      <param-value>both</param-value>
+    </init-param>
+    <init-param>
+      <param-name>gzip</param-name>
+      <param-value>true</param-value>
+    </init-param>
+    <init-param>
+      <param-name>useFileMappedBuffer</param-name>
+      <param-value>true</param-value>
+    </init-param>
+    <!--
+    <init-param>
+      <param-name>cacheControl</param-name>
+      <param-value>max-age=3600,public</param-value>
+    </init-param>
+    -->
+    <load-on-startup>0</load-on-startup>
+  </servlet>
+
+  <servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
+
+
+  <!-- ==================================================================== -->
+  <!-- JSP Servlet                                                          -->
+  <!-- This is the jasper JSP servlet from the jakarta project              -->
+  <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -->
+  <!-- The JSP page compiler and execution servlet, which is the mechanism  -->
+  <!-- used by Glassfish to support JSP pages.  Traditionally, this servlet -->
+  <!-- is mapped to URL patterh "*.jsp".  This servlet supports the         -->
+  <!-- following initialization parameters (default values are in square    -->
+  <!-- brackets):                                                           -->
+  <!--                                                                      -->
+  <!--   checkInterval       If development is false and reloading is true, -->
+  <!--                       background compiles are enabled. checkInterval -->
+  <!--                       is the time in seconds between checks to see   -->
+  <!--                       if a JSP page needs to be recompiled. [300]    -->
+  <!--                                                                      -->
+  <!--   compiler            Which compiler Ant should use to compile JSP   -->
+  <!--                       pages.  See the Ant documenation for more      -->
+  <!--                       information. [javac]                           -->
+  <!--                                                                      -->
+  <!--   classdebuginfo      Should the class file be compiled with         -->
+  <!--                       debugging information?  [true]                 -->
+  <!--                                                                      -->
+  <!--   classpath           What class path should I use while compiling   -->
+  <!--                       generated servlets?  [Created dynamically      -->
+  <!--                       based on the current web application]          -->
+  <!--                       Set to ? to make the container explicitly set  -->
+  <!--                       this parameter.                                -->
+  <!--                                                                      -->
+  <!--   development         Is Jasper used in development mode (will check -->
+  <!--                       for JSP modification on every access)?  [true] -->
+  <!--                                                                      -->
+  <!--   enablePooling       Determines whether tag handler pooling is      -->
+  <!--                       enabled  [true]                                -->
+  <!--                                                                      -->
+  <!--   fork                Tell Ant to fork compiles of JSP pages so that -->
+  <!--                       a separate JVM is used for JSP page compiles   -->
+  <!--                       from the one Tomcat is running in. [true]      -->
+  <!--                                                                      -->
+  <!--   ieClassId           The class-id value to be sent to Internet      -->
+  <!--                       Explorer when using <jsp:plugin> tags.         -->
+  <!--                       [clsid:8AD9C840-044E-11D1-B3E9-00805F499D93]   -->
+  <!--                                                                      -->
+  <!--   javaEncoding        Java file encoding to use for generating java  -->
+  <!--                       source files. [UTF-8]                          -->
+  <!--                                                                      -->
+  <!--   keepgenerated       Should we keep the generated Java source code  -->
+  <!--                       for each page instead of deleting it? [true]   -->
+  <!--                                                                      -->
+  <!--   logVerbosityLevel   The level of detailed messages to be produced  -->
+  <!--                       by this servlet.  Increasing levels cause the  -->
+  <!--                       generation of more messages.  Valid values are -->
+  <!--                       FATAL, ERROR, WARNING, INFORMATION, and DEBUG. -->
+  <!--                       [WARNING]                                      -->
+  <!--                                                                      -->
+  <!--   mappedfile          Should we generate static content with one     -->
+  <!--                       print statement per input line, to ease        -->
+  <!--                       debugging?  [false]                            -->
+  <!--                                                                      -->
+  <!--                                                                      -->
+  <!--   reloading           Should Jasper check for modified JSPs?  [true] -->
+  <!--                                                                      -->
+  <!--   suppressSmap        Should the generation of SMAP info for JSR45   -->
+  <!--                       debugging be suppressed?  [false]              -->
+  <!--                                                                      -->
+  <!--   dumpSmap            Should the SMAP info for JSR45 debugging be    -->
+  <!--                       dumped to a file? [false]                      -->
+  <!--                       False if suppressSmap is true                  -->
+  <!--                                                                      -->
+  <!--   scratchdir          What scratch directory should we use when      -->
+  <!--                       compiling JSP pages?  [default work directory  -->
+  <!--                       for the current web application]               -->
+  <!--                                                                      -->
+  <!--   tagpoolMaxSize      The maximum tag handler pool size  [5]         -->
+  <!--                                                                      -->
+  <!--   xpoweredBy          Determines whether X-Powered-By response       -->
+  <!--                       header is added by generated servlet  [false]  -->
+  <!--                                                                      -->
+  <!-- If you wish to use Jikes to compile JSP pages:                       -->
+  <!--   Set the init parameter "compiler" to "jikes".  Define              -->
+  <!--   the property "-Dbuild.compiler.emacs=true" when starting Jetty     -->
+  <!--   to cause Jikes to emit error messages in a format compatible with  -->
+  <!--   Jasper.                                                            -->
+  <!--   If you get an error reporting that jikes can't use UTF-8 encoding, -->
+  <!--   try setting the init parameter "javaEncoding" to "ISO-8859-1".     -->
+  <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -->
+  <servlet id="jsp">
+    <servlet-name>jsp</servlet-name>
+    <servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
+    <init-param>
+        <param-name>logVerbosityLevel</param-name>
+        <param-value>DEBUG</param-value>
+    </init-param>
+    <init-param>
+        <param-name>fork</param-name>
+        <param-value>false</param-value>
+    </init-param>
+    <init-param>
+        <param-name>xpoweredBy</param-name>
+        <param-value>false</param-value>
+    </init-param>
+    <!--
+    <init-param>
+        <param-name>classpath</param-name>
+        <param-value>?</param-value>
+    </init-param>
+    -->
+    <load-on-startup>0</load-on-startup>
+  </servlet>
+
+  <servlet-mapping>
+    <servlet-name>jsp</servlet-name>
+    <url-pattern>*.jsp</url-pattern>
+    <url-pattern>*.jspf</url-pattern>
+    <url-pattern>*.jspx</url-pattern>
+    <url-pattern>*.xsp</url-pattern>
+    <url-pattern>*.JSP</url-pattern>
+    <url-pattern>*.JSPF</url-pattern>
+    <url-pattern>*.JSPX</url-pattern>
+    <url-pattern>*.XSP</url-pattern>
+  </servlet-mapping>
+
+  <!-- ==================================================================== -->
+  <!-- Dynamic Servlet Invoker.                                             -->
+  <!-- This servlet invokes anonymous servlets that have not been defined   -->
+  <!-- in the web.xml or by other means. The first element of the pathInfo  -->
+  <!-- of a request passed to the envoker is treated as a servlet name for  -->
+  <!-- an existing servlet, or as a class name of a new servlet.            -->
+  <!-- This servlet is normally mapped to /servlet/*                        -->
+  <!-- This servlet support the following initParams:                       -->
+  <!--                                                                      -->
+  <!--  nonContextServlets       If false, the invoker can only load        -->
+  <!--                           servlets from the contexts classloader.    -->
+  <!--                           This is false by default and setting this  -->
+  <!--                           to true may have security implications.    -->
+  <!--                                                                      -->
+  <!--  verbose                  If true, log dynamic loads                 -->
+  <!--                                                                      -->
+  <!--  *                        All other parameters are copied to the     -->
+  <!--                           each dynamic servlet as init parameters    -->
+  <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -->
+  <!-- Uncomment for dynamic invocation
+  <servlet>
+    <servlet-name>invoker</servlet-name>
+    <servlet-class>org.eclipse.jetty.servlet.Invoker</servlet-class>
+    <init-param>
+      <param-name>verbose</param-name>
+      <param-value>false</param-value>
+    </init-param>
+    <init-param>
+      <param-name>nonContextServlets</param-name>
+      <param-value>false</param-value>
+    </init-param>
+    <init-param>
+      <param-name>dynamicParam</param-name>
+      <param-value>anyValue</param-value>
+    </init-param>
+    <load-on-startup>0</load-on-startup>
+  </servlet>
+
+  <servlet-mapping> <servlet-name>invoker</servlet-name> <url-pattern>/servlet/*</url-pattern> </servlet-mapping>
+  -->
+
+
+
+  <!-- ==================================================================== -->
+  <session-config>
+    <session-timeout>30</session-timeout>
+  </session-config>
+
+  <!-- ==================================================================== -->
+  <!-- Default MIME mappings                                                -->
+  <!-- The default MIME mappings are provided by the mime.properties        -->
+  <!-- resource in the org.eclipse.jetty.server.jar file.  Additional or modified  -->
+  <!-- mappings may be specified here                                       -->
+  <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -->
+  <!-- UNCOMMENT TO ACTIVATE
+  <mime-mapping>
+    <extension>mysuffix</extension>
+    <mime-type>mymime/type</mime-type>
+  </mime-mapping>
+  -->
+
+  <!-- ==================================================================== -->
+  <welcome-file-list>
+    <welcome-file>index.html</welcome-file>
+    <welcome-file>index.htm</welcome-file>
+    <welcome-file>index.jsp</welcome-file>
+  </welcome-file-list>
+
+  <!-- ==================================================================== -->
+  <locale-encoding-mapping-list>
+    <locale-encoding-mapping><locale>ar</locale><encoding>ISO-8859-6</encoding></locale-encoding-mapping>
+    <locale-encoding-mapping><locale>be</locale><encoding>ISO-8859-5</encoding></locale-encoding-mapping>
+    <locale-encoding-mapping><locale>bg</locale><encoding>ISO-8859-5</encoding></locale-encoding-mapping>
+    <locale-encoding-mapping><locale>ca</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
+    <locale-encoding-mapping><locale>cs</locale><encoding>ISO-8859-2</encoding></locale-encoding-mapping>
+    <locale-encoding-mapping><locale>da</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
+    <locale-encoding-mapping><locale>de</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
+    <locale-encoding-mapping><locale>el</locale><encoding>ISO-8859-7</encoding></locale-encoding-mapping>
+    <locale-encoding-mapping><locale>en</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
+    <locale-encoding-mapping><locale>es</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
+    <locale-encoding-mapping><locale>et</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
+    <locale-encoding-mapping><locale>fi</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
+    <locale-encoding-mapping><locale>fr</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
+    <locale-encoding-mapping><locale>hr</locale><encoding>ISO-8859-2</encoding></locale-encoding-mapping>
+    <locale-encoding-mapping><locale>hu</locale><encoding>ISO-8859-2</encoding></locale-encoding-mapping>
+    <locale-encoding-mapping><locale>is</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
+    <locale-encoding-mapping><locale>it</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
+    <locale-encoding-mapping><locale>iw</locale><encoding>ISO-8859-8</encoding></locale-encoding-mapping>
+    <locale-encoding-mapping><locale>ja</locale><encoding>Shift_JIS</encoding></locale-encoding-mapping>
+    <locale-encoding-mapping><locale>ko</locale><encoding>EUC-KR</encoding></locale-encoding-mapping>
+    <locale-encoding-mapping><locale>lt</locale><encoding>ISO-8859-2</encoding></locale-encoding-mapping>
+    <locale-encoding-mapping><locale>lv</locale><encoding>ISO-8859-2</encoding></locale-encoding-mapping>
+    <locale-encoding-mapping><locale>mk</locale><encoding>ISO-8859-5</encoding></locale-encoding-mapping>
+    <locale-encoding-mapping><locale>nl</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
+    <locale-encoding-mapping><locale>no</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
+    <locale-encoding-mapping><locale>pl</locale><encoding>ISO-8859-2</encoding></locale-encoding-mapping>
+    <locale-encoding-mapping><locale>pt</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
+    <locale-encoding-mapping><locale>ro</locale><encoding>ISO-8859-2</encoding></locale-encoding-mapping>
+    <locale-encoding-mapping><locale>ru</locale><encoding>ISO-8859-5</encoding></locale-encoding-mapping>
+    <locale-encoding-mapping><locale>sh</locale><encoding>ISO-8859-5</encoding></locale-encoding-mapping>
+    <locale-encoding-mapping><locale>sk</locale><encoding>ISO-8859-2</encoding></locale-encoding-mapping>
+    <locale-encoding-mapping><locale>sl</locale><encoding>ISO-8859-2</encoding></locale-encoding-mapping>
+    <locale-encoding-mapping><locale>sq</locale><encoding>ISO-8859-2</encoding></locale-encoding-mapping>
+    <locale-encoding-mapping><locale>sr</locale><encoding>ISO-8859-5</encoding></locale-encoding-mapping>
+    <locale-encoding-mapping><locale>sv</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
+    <locale-encoding-mapping><locale>tr</locale><encoding>ISO-8859-9</encoding></locale-encoding-mapping>
+    <locale-encoding-mapping><locale>uk</locale><encoding>ISO-8859-5</encoding></locale-encoding-mapping>
+    <locale-encoding-mapping><locale>zh</locale><encoding>GB2312</encoding></locale-encoding-mapping>
+    <locale-encoding-mapping><locale>zh_TW</locale><encoding>Big5</encoding></locale-encoding-mapping>
+  </locale-encoding-mapping-list>
+
+  <security-constraint>
+    <web-resource-collection>
+      <web-resource-name>Disable TRACE</web-resource-name>
+      <url-pattern>/</url-pattern>
+      <http-method>TRACE</http-method>
+    </web-resource-collection>
+    <auth-constraint/>
+  </security-constraint>
+
+</web-app>
+
diff --git a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/boot/JettyOSGiBootContextAsService.java b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/boot/JettyOSGiBootContextAsService.java
deleted file mode 100644
index e0f9588..0000000
--- a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/boot/JettyOSGiBootContextAsService.java
+++ /dev/null
@@ -1,194 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.osgi.boot;
-
-import static org.ops4j.pax.exam.CoreOptions.*;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.Map;
-
-import junit.framework.Assert;
-
-import org.eclipse.jetty.client.ContentExchange;
-import org.eclipse.jetty.client.HttpClient;
-import org.eclipse.jetty.client.HttpExchange;
-import org.eclipse.jetty.http.HttpMethods;
-import org.eclipse.jetty.http.HttpStatus;
-import org.eclipse.jetty.server.handler.ContextHandler;
-import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.ops4j.pax.exam.CoreOptions;
-import org.ops4j.pax.exam.Inject;
-import org.ops4j.pax.exam.Option;
-import org.ops4j.pax.exam.container.def.PaxRunnerOptions;
-import org.ops4j.pax.exam.junit.Configuration;
-import org.ops4j.pax.exam.junit.JUnit4TestRunner;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.ServiceReference;
-
-/**
- * TestJettyOSGiBootContextAsService
- * 
- * Tests deployment of a ContextHandler as an osgi Service.
- * 
- * Tests the ServiceContextProvider.
- * 
- */
- at RunWith( JUnit4TestRunner.class )
-public class JettyOSGiBootContextAsService
-{
-    private static final boolean LOGGING_ENABLED = false;
-    private static final boolean REMOTE_DEBUGGING = false;
-    
-    @Inject
-    BundleContext bundleContext = null;
-
-    @Configuration
-    public static Option[] configure()
-    {
-    	ArrayList<Option> options = new ArrayList<Option>();
-    	options.addAll(TestJettyOSGiBootCore.provisionCoreJetty());
-        options.add(CoreOptions.bootDelegationPackages("org.xml.sax", "org.xml.*",
-                                                       "org.w3c.*", "javax.xml.*"));
-       
-    	   File base = MavenTestingUtils.getBasedir();
-           File src = new File (base, "src");
-           File tst = new File (src, "test");
-           File config = new File (tst, "config");
-           
-    	
-    	// Enable Logging
-    	if(LOGGING_ENABLED) {
-    	    options.addAll(Arrays.asList(options(
-                // install log service using pax runners profile abstraction (there are more profiles, like DS)
-        	// logProfile(),
-        	// this is how you set the default log level when using pax logging (logProfile)
-        	systemProperty( "org.ops4j.pax.logging.DefaultServiceLog.level" ).value( "INFO" )
-    	    )));
-    	}
-    	
-    	// Remote JDWP Debugging
-    	if(REMOTE_DEBUGGING) {
-    	    options.addAll(Arrays.asList(options(
-    	        // this just adds all what you write here to java vm argumenents of the (new) osgi process.
-    	        PaxRunnerOptions.vmOption( "-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5006" )
-    	    )));
-    	}
-
-    	// Standard Options
-    	   options.addAll(Arrays.asList(options(
-    	                                    PaxRunnerOptions.vmOption("-Djetty.port=9876 -Djetty.home="+config.getAbsolutePath()+" -D" + OSGiServerConstants.MANAGED_JETTY_XML_CONFIG_URLS + 
-    	                                        "=etc/jetty.xml;etc/jetty-deployer.xml;etc/jetty-selector.xml;etc/jetty-testrealm.xml"),
-
-            /* orbit deps */
-            mavenBundle().groupId( "org.eclipse.jetty.orbit" ).artifactId( "javax.servlet.jsp" ).versionAsInProject(),
-            mavenBundle().groupId( "org.eclipse.jetty.orbit" ).artifactId( "javax.servlet.jsp.jstl" ).versionAsInProject(),
-            mavenBundle().groupId( "org.eclipse.jetty.orbit" ).artifactId( "javax.el" ).versionAsInProject(),
-            mavenBundle().groupId( "org.eclipse.jetty.orbit" ).artifactId( "com.sun.el" ).versionAsInProject(),
-    	    mavenBundle().groupId( "org.eclipse.jetty.orbit" ).artifactId( "org.apache.jasper.glassfish" ).versionAsInProject(),
-            mavenBundle().groupId( "org.eclipse.jetty.orbit" ).artifactId( "org.apache.taglibs.standard.glassfish" ).versionAsInProject(),
-            mavenBundle().groupId( "org.eclipse.jetty.orbit" ).artifactId( "org.eclipse.jdt.core" ).versionAsInProject(),
-    	    
-    	    /* jetty-osgi deps */
-    	    mavenBundle().groupId( "org.eclipse.jetty.osgi" ).artifactId( "jetty-osgi-boot" ).versionAsInProject().start(),
-            mavenBundle().groupId( "org.eclipse.jetty.osgi" ).artifactId( "jetty-osgi-boot-jsp" ).versionAsInProject().start(),
-
-            //a bundle that registers a webapp as a service for the jetty osgi core to pick up and deploy
-            mavenBundle().groupId( "org.eclipse.jetty.osgi" ).artifactId( "test-jetty-osgi-context" ).versionAsInProject().start()
-            // mavenBundle().groupId( "org.eclipse.equinox.http" ).artifactId( "servlet" ).versionAsInProject().start()     
-        )));
-    	
-    	return options.toArray(new Option[options.size()]);
-    }
-
-    /**
-     * You will get a list of bundles installed by default
-     * plus your testcase, wrapped into a bundle called pax-exam-probe
-     */
-    @Test
-    public void listBundles() throws Exception
-    {
-    	Map<String,Bundle> bundlesIndexedBySymbolicName = new HashMap<String, Bundle>();
-        for( Bundle b : bundleContext.getBundles() )
-        {
-        	bundlesIndexedBySymbolicName.put(b.getSymbolicName(), b);
-        	System.err.println("Got " + b.getSymbolicName() + " " + b.getVersion().toString() + " " + b.getState());
-        }
-        
-        Bundle osgiBoot = bundlesIndexedBySymbolicName.get("org.eclipse.jetty.osgi.boot");
-        Assert.assertNotNull("Could not find the org.eclipse.jetty.osgi.boot bundle", osgiBoot);
-        Assert.assertTrue(osgiBoot.getState() == Bundle.ACTIVE);
-        
-        Bundle testWebBundle = bundlesIndexedBySymbolicName.get("org.eclipse.jetty.osgi.testcontext");
-        Assert.assertNotNull("Could not find the org.eclipse.jetty.test-jetty-osgi-context.jar bundle", testWebBundle);
-        Assert.assertTrue("The bundle org.eclipse.jetty.testcontext is not correctly resolved", testWebBundle.getState() == Bundle.ACTIVE);
-        
-        //now test the context
-        HttpClient client = new HttpClient();
-        client.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);
-        try
-        {
-            client.start();
-            
-            ContentExchange getExchange = new ContentExchange();
-            getExchange.setURL("http://127.0.0.1:9876/acme/index.html");
-            getExchange.setMethod(HttpMethods.GET);
-     
-            client.send(getExchange);
-            int state = getExchange.waitForDone();
-            Assert.assertEquals("state should be done", HttpExchange.STATUS_COMPLETED, state);
-            
-            String content = null;
-            int responseStatus = getExchange.getResponseStatus();
-            Assert.assertEquals(HttpStatus.OK_200, responseStatus);
-            if (responseStatus == HttpStatus.OK_200) {
-                content = getExchange.getResponseContent();
-            }
-            Assert.assertTrue(content.indexOf("<h1>Test OSGi Context</h1>") != -1);
-        }
-        finally
-        {
-            client.stop();
-        }
-        
-        ServiceReference[] refs = bundleContext.getServiceReferences(ContextHandler.class.getName(), null);
-        Assert.assertNotNull(refs);
-        Assert.assertEquals(1,refs.length);
-        String[] keys = refs[0].getPropertyKeys();
-        if (keys != null)
-        {
-            for (String k:keys)
-                System.err.println("service property: "+k+", "+refs[0].getProperty(k));
-        }
-        ContextHandler ch = (ContextHandler)bundleContext.getService(refs[0]);
-        Assert.assertEquals("/acme", ch.getContextPath());
-        
-        testWebBundle.stop();
-        
-        //Check you can see CONTEXT DESTROYED on stderr. TODO: think of a better way to communicate this to the test
-    }
-
-	
-}
diff --git a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/boot/TestJettyOSGiBootCore.java b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/boot/TestJettyOSGiBootCore.java
deleted file mode 100644
index 67d5af8..0000000
--- a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/boot/TestJettyOSGiBootCore.java
+++ /dev/null
@@ -1,218 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.osgi.boot;
-
-import static org.ops4j.pax.exam.CoreOptions.*;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import junit.framework.Assert;
-
-import org.eclipse.jetty.client.ContentExchange;
-import org.eclipse.jetty.client.HttpClient;
-import org.eclipse.jetty.client.HttpExchange;
-import org.eclipse.jetty.http.HttpMethods;
-import org.eclipse.jetty.http.HttpStatus;
-import org.eclipse.jetty.osgi.boot.internal.serverfactory.DefaultJettyAtJettyHomeHelper;
-import org.eclipse.jetty.server.handler.ContextHandler;
-import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.ops4j.pax.exam.Inject;
-import org.ops4j.pax.exam.Option;
-import org.ops4j.pax.exam.container.def.PaxRunnerOptions;
-import org.ops4j.pax.exam.junit.Configuration;
-import org.ops4j.pax.exam.junit.JUnit4TestRunner;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.ServiceReference;
-import org.osgi.service.http.HttpService;
-
-
-/**
- * TestJettyOSGiBootCore
- * 
- * Tests deploying a bundle (org.eclipse.jetty.osgi.httpservice) that contains a context xml file
- * that starts up the equinox http servlet. 
- * 
- * This tests the BundleContextProvider.
- * 
- * Pax-Exam to make sure the jetty-osgi-boot can be started along with the httpservice web-bundle.
- * Then make sure we can deploy an OSGi service on the top of this.
- */
- at RunWith( JUnit4TestRunner.class )
-public class TestJettyOSGiBootCore
-{
-    /**
-     * Jetty-osgi including webapp support and also jetty-client.
-     * Sets the system property jetty.home.bunde=org.eclipse.jetty.osgi.boot
-     * to use the jetty server configuration embedded in 
-     * 
-     * @return list of options
-     */
-    public static List<Option> provisionCoreJetty()
-    {
-        File base = MavenTestingUtils.getBasedir();
-        File src = new File (base, "src");
-        File tst = new File (src, "test");
-        File config = new File (tst, "config");
-        
-        return Arrays.asList(options(
-                // get the jetty home config from the osgi boot bundle.
-               // PaxRunnerOptions.vmOptions("-Djetty.port=9876 -D" + DefaultJettyAtJettyHomeHelper.SYS_PROP_JETTY_HOME_BUNDLE + "=org.eclipse.jetty.osgi.boot"),
-               
-                
-                PaxRunnerOptions.vmOptions("-Djetty.port=9876 -Djetty.home=" +  config.getAbsolutePath()),
-                // CoreOptions.equinox(),
-                
-                mavenBundle().groupId( "org.eclipse.jetty.orbit" ).artifactId( "javax.servlet" ).versionAsInProject().noStart(),
-                mavenBundle().groupId( "org.eclipse.osgi" ).artifactId( "org.eclipse.osgi" ).versionAsInProject().noStart(),
-                mavenBundle().groupId( "org.eclipse.osgi" ).artifactId( "org.eclipse.osgi.services" ).versionAsInProject().noStart(),
-
-                mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "jetty-deploy" ).versionAsInProject().noStart(),   
-                mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "jetty-server" ).versionAsInProject().noStart(),   
-                mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "jetty-servlet" ).versionAsInProject().noStart(),  
-                mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "jetty-util" ).versionAsInProject().noStart(), 
-                mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "jetty-http" ).versionAsInProject().noStart(), 
-                mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "jetty-xml" ).versionAsInProject().noStart(),  
-                mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "jetty-webapp" ).versionAsInProject().noStart(),
-                mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "jetty-io" ).versionAsInProject().noStart(),
-                mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "jetty-continuation" ).versionAsInProject().noStart(),
-                mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "jetty-security" ).versionAsInProject().noStart(),
-                mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "jetty-websocket" ).versionAsInProject().noStart(),
-                mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "jetty-servlets" ).versionAsInProject().noStart(),
-                
-                mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "jetty-client" ).versionAsInProject().noStart()
-        ));
-    }
-    
-    @Inject
-    BundleContext bundleContext = null;
-
-
-    @Configuration
-    public static Option[] configure()
-    {
-        ArrayList<Option> options = new ArrayList<Option>();
-        options.addAll(provisionCoreJetty());
-        options.addAll(Arrays.asList(options(
-            // install log service using pax runners profile abstraction (there are more profiles, like DS)
-            //logProfile(),
-            // this is how you set the default log level when using pax logging (logProfile)
-            //systemProperty( "org.ops4j.pax.logging.DefaultServiceLog.level" ).value( "INFO" ),
-            
-        //	CoreOptions.equinox(), CoreOptions.felix(),//.version("3.0.0"),
-        		
-            mavenBundle().groupId( "org.eclipse.jetty.osgi" ).artifactId( "jetty-osgi-boot" ).versionAsInProject().start(),
-            mavenBundle().groupId( "org.eclipse.jetty.osgi" ).artifactId( "jetty-httpservice" ).versionAsInProject().start(),
-            
-            mavenBundle().groupId( "org.eclipse.equinox.http" ).artifactId( "servlet" ).versionAsInProject().start()
-        )));
-        return options.toArray(new Option[options.size()]);
-    }
-
-    /**
-     * You will get a list of bundles installed by default
-     * plus your testcase, wrapped into a bundle called pax-exam-probe
-     */
-    @Test
-    public void testHttpService() throws Exception
-    {
-//      ServletContextHandler sch = null;
-//      sch.addServlet("className", "pathSpec").setInitOrder("0");
-        
-        
-        Map<String,Bundle> bundlesIndexedBySymbolicName = new HashMap<String, Bundle>();
-        for( Bundle b : bundleContext.getBundles() )
-        {
-            bundlesIndexedBySymbolicName.put(b.getSymbolicName(), b);
-        }
-        Bundle osgiBoot = bundlesIndexedBySymbolicName.get("org.eclipse.jetty.osgi.boot");
-        Assert.assertNotNull("Could not find the org.eclipse.jetty.osgi.boot bundle", osgiBoot);
-        Assert.assertTrue(osgiBoot.getState() == Bundle.ACTIVE);
-
-        Bundle httpServiceOSGiBundle = bundlesIndexedBySymbolicName.get("org.eclipse.jetty.osgi.httpservice");
-        Assert.assertNotNull(httpServiceOSGiBundle);
-        Assert.assertTrue(httpServiceOSGiBundle.getState() == Bundle.ACTIVE);
-
-        Bundle equinoxServlet = bundlesIndexedBySymbolicName.get("org.eclipse.equinox.http.servlet");
-        Assert.assertNotNull(equinoxServlet);
-        //interestingly with equinox the bundle is not started. probably a difference in pax-exam and
-        //the way the bundles are activated. the rest of the test goes fine.
-        Assert.assertTrue(equinoxServlet.getState() == Bundle.ACTIVE);
-        
-        //in the OSGi world this would be bad code and we should use a bundle tracker.
-        //here we purposely want to make sure that the httpService is actually ready.
-        ServiceReference sr  =  bundleContext.getServiceReference(HttpService.class.getName());
-        Assert.assertNotNull("The httpServiceOSGiBundle is started and should have deployed a service reference for HttpService" ,sr);
-        HttpService http = (HttpService)bundleContext.getService(sr);
-        http.registerServlet("/greetings", new HttpServlet() {
-            private static final long serialVersionUID = 1L;
-            @Override
-            protected void doGet(HttpServletRequest req,
-                    HttpServletResponse resp) throws ServletException,
-                    IOException {
-                resp.getWriter().append("Hello");
-            }
-        }, null, null);
-        
-        //now test the servlet
-        HttpClient client = new HttpClient();
-        client.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);
-        try
-        {
-            client.start();
-            
-            ContentExchange getExchange = new ContentExchange();
-            getExchange.setURL("http://127.0.0.1:9876/greetings");
-            getExchange.setMethod(HttpMethods.GET);
-     
-            client.send(getExchange);
-            int state = getExchange.waitForDone();
-            Assert.assertEquals("state should be done", HttpExchange.STATUS_COMPLETED, state);
-     
-            String content = null;
-            int responseStatus = getExchange.getResponseStatus();
-            Assert.assertEquals(HttpStatus.OK_200, responseStatus);
-            if (responseStatus == HttpStatus.OK_200) {
-                content = getExchange.getResponseContent();
-            }
-            Assert.assertEquals("Hello", content);
-        }
-        finally
-        {
-            client.stop();
-        }
-        ServiceReference[] refs = bundleContext.getServiceReferences(ContextHandler.class.getName(), null);
-        Assert.assertNotNull(refs);
-        Assert.assertEquals(1,refs.length);
-    }
-    
-}
diff --git a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/boot/TestJettyOSGiBootWebAppAsService.java b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/boot/TestJettyOSGiBootWebAppAsService.java
deleted file mode 100644
index 8468fb0..0000000
--- a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/boot/TestJettyOSGiBootWebAppAsService.java
+++ /dev/null
@@ -1,186 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.osgi.boot;
-
-import static org.ops4j.pax.exam.CoreOptions.*;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.Map;
-
-import junit.framework.Assert;
-
-import org.eclipse.jetty.client.ContentExchange;
-import org.eclipse.jetty.client.HttpClient;
-import org.eclipse.jetty.client.HttpExchange;
-import org.eclipse.jetty.http.HttpMethods;
-import org.eclipse.jetty.http.HttpStatus;
-import org.eclipse.jetty.server.handler.ContextHandler;
-import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
-import org.eclipse.jetty.webapp.WebAppContext;
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.ops4j.pax.exam.CoreOptions;
-import org.ops4j.pax.exam.Inject;
-import org.ops4j.pax.exam.Option;
-import org.ops4j.pax.exam.container.def.PaxRunnerOptions;
-import org.ops4j.pax.exam.junit.Configuration;
-import org.ops4j.pax.exam.junit.JUnit4TestRunner;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.ServiceReference;
-
-/**
- * TestJettyOSGiBootWebAppAsService
- * 
- * Tests deployment of a WebAppContext as an osgi Service.
- * 
- * Tests the ServiceWebAppProvider.
- * 
- * Pax-Exam to make sure the jetty-osgi-boot can be started along with the httpservice web-bundle.
- * Then make sure we can deploy an OSGi service on the top of this.
- */
- at RunWith( JUnit4TestRunner.class )
-public class TestJettyOSGiBootWebAppAsService
-{
-    private static final boolean LOGGING_ENABLED = false;
-    private static final boolean REMOTE_DEBUGGING = false;
-    
-    @Inject
-    BundleContext bundleContext = null;
-
-    @Configuration
-    public static Option[] configure()
-    {
-    	ArrayList<Option> options = new ArrayList<Option>();
-    	options.addAll(TestJettyOSGiBootCore.provisionCoreJetty());
-        options.add(CoreOptions.bootDelegationPackages("org.xml.sax", "org.xml.*",
-                                                       "org.w3c.*", "javax.xml.*"));
-    	   File base = MavenTestingUtils.getBasedir();
-           File src = new File (base, "src");
-           File tst = new File (src, "test");
-           File config = new File (tst, "config");
-           
-    	
-    	// Enable Logging
-    	if(LOGGING_ENABLED) {
-    	    options.addAll(Arrays.asList(options(
-                // install log service using pax runners profile abstraction (there are more profiles, like DS)
-        	// logProfile(),
-        	// this is how you set the default log level when using pax logging (logProfile)
-        	systemProperty( "org.ops4j.pax.logging.DefaultServiceLog.level" ).value( "INFO" )
-    	    )));
-    	}
-    	
-    	// Remote JDWP Debugging
-    	if(REMOTE_DEBUGGING) {
-    	    options.addAll(Arrays.asList(options(
-    	        // this just adds all what you write here to java vm argumenents of the (new) osgi process.
-    	        PaxRunnerOptions.vmOption( "-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5006" )
-    	    )));
-    	}
-
-    	// Standard Options
-    	   options.addAll(Arrays.asList(options(
-    	                                    PaxRunnerOptions.vmOption("-Djetty.port=9876 -Djetty.home="+config.getAbsolutePath()+" -D" + OSGiServerConstants.MANAGED_JETTY_XML_CONFIG_URLS + 
-    	                                        "=etc/jetty.xml;etc/jetty-deployer.xml;etc/jetty-selector.xml;etc/jetty-testrealm.xml"),
-
-            /* orbit deps */
-            mavenBundle().groupId( "org.eclipse.jetty.orbit" ).artifactId( "javax.servlet.jsp" ).versionAsInProject(),
-            mavenBundle().groupId( "org.eclipse.jetty.orbit" ).artifactId( "javax.servlet.jsp.jstl" ).versionAsInProject(),
-            mavenBundle().groupId( "org.eclipse.jetty.orbit" ).artifactId( "javax.el" ).versionAsInProject(),
-            mavenBundle().groupId( "org.eclipse.jetty.orbit" ).artifactId( "com.sun.el" ).versionAsInProject(),
-    	    mavenBundle().groupId( "org.eclipse.jetty.orbit" ).artifactId( "org.apache.jasper.glassfish" ).versionAsInProject(),
-            mavenBundle().groupId( "org.eclipse.jetty.orbit" ).artifactId( "org.apache.taglibs.standard.glassfish" ).versionAsInProject(),
-            mavenBundle().groupId( "org.eclipse.jetty.orbit" ).artifactId( "org.eclipse.jdt.core" ).versionAsInProject(),
-    	    
-    	    /* jetty-osgi deps */
-    	    mavenBundle().groupId( "org.eclipse.jetty.osgi" ).artifactId( "jetty-osgi-boot" ).versionAsInProject().start(),
-            mavenBundle().groupId( "org.eclipse.jetty.osgi" ).artifactId( "jetty-osgi-boot-jsp" ).versionAsInProject().start(),
-
-            //a bundle that registers a webapp as a service for the jetty osgi core to pick up and deploy
-            mavenBundle().groupId( "org.eclipse.jetty.osgi" ).artifactId( "test-jetty-osgi-webapp" ).versionAsInProject().start()
-            // mavenBundle().groupId( "org.eclipse.equinox.http" ).artifactId( "servlet" ).versionAsInProject().start()
-        )));
-    	
-    	return options.toArray(new Option[options.size()]);
-    }
-
-    /**
-     * You will get a list of bundles installed by default
-     * plus your testcase, wrapped into a bundle called pax-exam-probe
-     */
-    @Test
-    public void listBundles() throws Exception
-    {
-    	Map<String,Bundle> bundlesIndexedBySymbolicName = new HashMap<String, Bundle>();
-        for( Bundle b : bundleContext.getBundles() )
-        {
-        	bundlesIndexedBySymbolicName.put(b.getSymbolicName(), b);
-        	System.err.println("Got " + b.getSymbolicName() + " " + b.getVersion().toString() + " " + b.getState());
-        }
-        
-        Bundle osgiBoot = bundlesIndexedBySymbolicName.get("org.eclipse.jetty.osgi.boot");
-        Assert.assertNotNull("Could not find the org.eclipse.jetty.osgi.boot bundle", osgiBoot);
-        Assert.assertTrue(osgiBoot.getState() == Bundle.ACTIVE);
-        
-        Bundle testWebBundle = bundlesIndexedBySymbolicName.get("org.eclipse.jetty.osgi.testapp");
-        Assert.assertNotNull("Could not find the org.eclipse.jetty.test-jetty-osgi-webapp.jar bundle", testWebBundle);
-        Assert.assertTrue("The bundle org.eclipse.jetty.testapp is not correctly resolved", testWebBundle.getState() == Bundle.ACTIVE);
-        
-        //now test the jsp/dump.jsp
-        HttpClient client = new HttpClient();
-        client.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);
-        try
-        {
-            client.start();
-            
-            ContentExchange getExchange = new ContentExchange();
-            getExchange.setURL("http://127.0.0.1:9876/acme/index.html");
-            getExchange.setMethod(HttpMethods.GET);
-     
-            client.send(getExchange);
-            int state = getExchange.waitForDone();
-            Assert.assertEquals("state should be done", HttpExchange.STATUS_COMPLETED, state);
-            
-            String content = null;
-            int responseStatus = getExchange.getResponseStatus();
-            Assert.assertEquals(HttpStatus.OK_200, responseStatus);
-            if (responseStatus == HttpStatus.OK_200) {
-                content = getExchange.getResponseContent();
-            }
-            Assert.assertTrue(content.indexOf("<h1>Test OSGi WebApp</h1>") != -1);
-        }
-        finally
-        {
-            client.stop();
-        }
-        
-        ServiceReference[] refs = bundleContext.getServiceReferences(ContextHandler.class.getName(), null);
-        Assert.assertNotNull(refs);
-        Assert.assertEquals(1,refs.length);
-        WebAppContext wac = (WebAppContext)bundleContext.getService(refs[0]);
-        Assert.assertEquals("/acme", wac.getContextPath());
-    }
-
-	
-}
diff --git a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/boot/TestJettyOSGiBootWithJsp.java b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/boot/TestJettyOSGiBootWithJsp.java
deleted file mode 100644
index 7f4a9ab..0000000
--- a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/boot/TestJettyOSGiBootWithJsp.java
+++ /dev/null
@@ -1,195 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.osgi.boot;
-
-import static org.ops4j.pax.exam.CoreOptions.*;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.Map;
-
-import junit.framework.Assert;
-
-import org.eclipse.jetty.client.ContentExchange;
-import org.eclipse.jetty.client.HttpClient;
-import org.eclipse.jetty.client.HttpExchange;
-import org.eclipse.jetty.http.HttpMethods;
-import org.eclipse.jetty.http.HttpStatus;
-import org.eclipse.jetty.server.handler.ContextHandler;
-import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
-import org.eclipse.jetty.webapp.WebAppContext;
-
-
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.ops4j.pax.exam.CoreOptions;
-import org.ops4j.pax.exam.Inject;
-import org.ops4j.pax.exam.Option;
-import org.ops4j.pax.exam.container.def.PaxRunnerOptions;
-import org.ops4j.pax.exam.junit.Configuration;
-import org.ops4j.pax.exam.junit.JUnit4TestRunner;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.ServiceReference;
-
-/**
- * TestJettyOSGiBootWithJsp
- * 
- * 
- * Tests deploying a war (standard jetty test webapp). Tests the BundleWebAppProvider.
- * 
- * 
- * Pax-Exam to make sure the jetty-osgi-boot can be started along with the httpservice web-bundle.
- * Then make sure we can deploy an OSGi service on the top of this.
- */
- at RunWith( JUnit4TestRunner.class )
-public class TestJettyOSGiBootWithJsp
-{
-    private static final boolean LOGGING_ENABLED = false;
-    private static final boolean REMOTE_DEBUGGING = false;
-    
-    @Inject
-    BundleContext bundleContext = null;
-
-    @Configuration
-    public static Option[] configure()
-    {
-    	File testrealm = new File("src/test/config/etc/jetty-testrealm.xml");
-    	File base = MavenTestingUtils.getBasedir();
-    	File src = new File (base, "src");
-    	File tst = new File (src, "test");
-    	File config = new File (tst, "config");
-    	
-    	
-
-    	ArrayList<Option> options = new ArrayList<Option>();
-    	options.addAll(TestJettyOSGiBootCore.provisionCoreJetty());
-        options.add(CoreOptions.bootDelegationPackages("org.xml.sax", "org.xml.*",
-                                                       "org.w3c.*", "javax.xml.*"));
-    	// Enable Logging
-    	if(LOGGING_ENABLED) {
-    	    options.addAll(Arrays.asList(options(
-                // install log service using pax runners profile abstraction (there are more profiles, like DS)
-        	// logProfile(),
-        	// this is how you set the default log level when using pax logging (logProfile)
-        	systemProperty( "org.ops4j.pax.logging.DefaultServiceLog.level" ).value( "INFO" )
-    	    )));
-    	}
-    	
-    	// Remote JDWP Debugging
-    	if(REMOTE_DEBUGGING) {
-    	    options.addAll(Arrays.asList(options(
-    	        // this just adds all what you write here to java vm argumenents of the (new) osgi process.
-    	        PaxRunnerOptions.vmOption( "-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5006" )
-    	    )));
-    	}
-
-    	// Standard Options
-    	options.addAll(Arrays.asList(options(
-            PaxRunnerOptions.vmOption("-Djetty.port=9876 -Djetty.home="+config.getAbsolutePath()+" -D" + OSGiServerConstants.MANAGED_JETTY_XML_CONFIG_URLS + 
-                "=etc/jetty.xml;etc/jetty-deployer.xml;etc/jetty-selector.xml;etc/jetty-testrealm.xml"),
-
-            /* orbit deps */
-            mavenBundle().groupId( "org.eclipse.jetty.orbit" ).artifactId( "javax.servlet.jsp" ).versionAsInProject(),
-            mavenBundle().groupId( "org.eclipse.jetty.orbit" ).artifactId( "javax.servlet.jsp.jstl" ).versionAsInProject(),
-            mavenBundle().groupId( "org.eclipse.jetty.orbit" ).artifactId( "javax.el" ).versionAsInProject(),
-            mavenBundle().groupId( "org.eclipse.jetty.orbit" ).artifactId( "com.sun.el" ).versionAsInProject(),
-    	    mavenBundle().groupId( "org.eclipse.jetty.orbit" ).artifactId( "org.apache.jasper.glassfish" ).versionAsInProject(),
-            mavenBundle().groupId( "org.eclipse.jetty.orbit" ).artifactId( "org.apache.taglibs.standard.glassfish" ).versionAsInProject(),
-            mavenBundle().groupId( "org.eclipse.jetty.orbit" ).artifactId( "org.eclipse.jdt.core" ).versionAsInProject(),
-    	    
-    	    /* jetty-osgi deps */
-    	    mavenBundle().groupId( "org.eclipse.jetty.osgi" ).artifactId( "jetty-osgi-boot" ).versionAsInProject().start(),
-            mavenBundle().groupId( "org.eclipse.jetty.osgi" ).artifactId( "jetty-osgi-boot-jsp" ).versionAsInProject().start(),
-
-            mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "test-jetty-webapp" ).classifier("webbundle").versionAsInProject()
-            
-            // mavenBundle().groupId( "org.eclipse.equinox.http" ).artifactId( "servlet" ).versionAsInProject().start()
-        )));
-    	
-    	return options.toArray(new Option[options.size()]);
-    }
-
-    /**
-     * You will get a list of bundles installed by default
-     * plus your testcase, wrapped into a bundle called pax-exam-probe
-     */
-    @Test
-    public void listBundles() throws Exception
-    {
-    	Map<String,Bundle> bundlesIndexedBySymbolicName = new HashMap<String, Bundle>();
-        for( Bundle b : bundleContext.getBundles() )
-        {
-        	bundlesIndexedBySymbolicName.put(b.getSymbolicName(), b);
-        	System.err.println("Got " + b.getSymbolicName());
-        }
-        
-        Bundle osgiBoot = bundlesIndexedBySymbolicName.get("org.eclipse.jetty.osgi.boot");
-        Assert.assertNotNull("Could not find the org.eclipse.jetty.osgi.boot bundle", osgiBoot);
-        Assert.assertTrue(osgiBoot.getState() == Bundle.ACTIVE);
-        
-        Bundle osgiBootJsp = bundlesIndexedBySymbolicName.get("org.eclipse.jetty.osgi.boot.jsp");
-        Assert.assertNotNull("Could not find the org.eclipse.jetty.osgi.boot.jsp bundle", osgiBootJsp);
-        Assert.assertTrue("The fragment jsp is not correctly resolved", osgiBootJsp.getState() == Bundle.RESOLVED);
-        
-        Bundle testWebBundle = bundlesIndexedBySymbolicName.get("org.eclipse.jetty.test-jetty-webapp");
-        Assert.assertNotNull("Could not find the test-jetty-webapp bundle", testWebBundle);
-        Assert.assertTrue("The test-jetty-webapp bundle is not correctly resolved", testWebBundle.getState() == Bundle.ACTIVE);
-        
-        //now test the jsp/dump.jsp
-        HttpClient client = new HttpClient();
-        client.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);
-        try
-        {
-            client.start();
-            
-            ContentExchange getExchange = new ContentExchange();
-            getExchange.setURL("http://127.0.0.1:9876/jsp/dump.jsp");
-            getExchange.setMethod(HttpMethods.GET);
-     
-            client.send(getExchange);
-            int state = getExchange.waitForDone();
-            Assert.assertEquals("state should be done", HttpExchange.STATUS_COMPLETED, state);
-            
-            String content = null;
-            int responseStatus = getExchange.getResponseStatus();
-            Assert.assertEquals(HttpStatus.OK_200, responseStatus);
-            if (responseStatus == HttpStatus.OK_200) {
-                content = getExchange.getResponseContent();
-            }
-
-            Assert.assertTrue(content.indexOf("<tr><th>ServletPath:</th><td>/jsp/dump.jsp</td></tr>") != -1);
-        }
-        finally
-        {
-            client.stop();
-        }
-        
-        ServiceReference[] refs = bundleContext.getServiceReferences(ContextHandler.class.getName(), null);
-        Assert.assertNotNull(refs);
-        Assert.assertEquals(1,refs.length);
-        
-        //TODO check that the events got sent
-    }
-
-	
-}
diff --git a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootContextAsService.java b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootContextAsService.java
new file mode 100644
index 0000000..1f80c7b
--- /dev/null
+++ b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootContextAsService.java
@@ -0,0 +1,151 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.osgi.test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
+import static org.ops4j.pax.exam.CoreOptions.options;
+import static org.ops4j.pax.exam.CoreOptions.systemProperty;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import javax.inject.Inject;
+
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.osgi.boot.OSGiServerConstants;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.CoreOptions;
+import org.ops4j.pax.exam.Option;
+import org.ops4j.pax.exam.Configuration;
+import org.ops4j.pax.exam.junit.PaxExam;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+
+/**
+ * TestJettyOSGiBootContextAsService
+ * 
+ * Tests deployment of a ContextHandler as an osgi Service.
+ * 
+ * Tests the ServiceContextProvider.
+ * 
+ */
+ at RunWith(PaxExam.class)
+public class TestJettyOSGiBootContextAsService
+{
+    private static final String LOG_LEVEL = "WARN";
+
+
+    @Inject
+    BundleContext bundleContext = null;
+
+    @Configuration
+    public static Option[] configure()
+    {
+        ArrayList<Option> options = new ArrayList<Option>();
+        options.add(CoreOptions.junitBundles());
+        options.addAll(configureJettyHomeAndPort("jetty-selector.xml"));
+        options.add(CoreOptions.bootDelegationPackages("org.xml.sax", "org.xml.*", "org.w3c.*", "javax.xml.*"));
+        options.addAll(TestJettyOSGiBootCore.coreJettyDependencies());
+
+        // a bundle that registers a webapp as a service for the jetty osgi core
+        // to pick up and deploy
+        options.add(mavenBundle().groupId("org.eclipse.jetty.osgi").artifactId("test-jetty-osgi-context").versionAsInProject().start());
+
+        options.addAll(Arrays.asList(options(systemProperty("pax.exam.logging").value("none"))));
+        options.addAll(Arrays.asList(options(systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value(LOG_LEVEL))));
+        options.addAll(Arrays.asList(options(systemProperty("org.eclipse.jetty.LEVEL").value(LOG_LEVEL))));
+        
+        return options.toArray(new Option[options.size()]);
+    }
+
+    public static List<Option> configureJettyHomeAndPort(String jettySelectorFileName)
+    {
+        File etcFolder = new File("src/test/config/etc");
+        String etc = "file://" + etcFolder.getAbsolutePath();
+        List<Option> options = new ArrayList<Option>();
+        options.add(systemProperty(OSGiServerConstants.MANAGED_JETTY_XML_CONFIG_URLS).value(etc     + "/jetty.xml;"
+                + etc
+                + "/"
+                + jettySelectorFileName
+                + ";"
+                + etc
+                + "/jetty-deployer.xml;"
+                + etc
+                + "/jetty-testrealm.xml"));
+        options.add(systemProperty("jetty.port").value(String.valueOf(TestJettyOSGiBootCore.DEFAULT_JETTY_HTTP_PORT)));
+        options.add(systemProperty("jetty.home").value(etcFolder.getParentFile().getAbsolutePath()));
+        return options;
+    }
+
+    @Ignore
+    @Test
+    public void assertAllBundlesActiveOrResolved()
+    {
+        TestOSGiUtil.assertAllBundlesActiveOrResolved(bundleContext);
+    }
+
+
+    /**
+     */
+    @Test
+    public void testContextHandlerAsOSGiService() throws Exception
+    {
+        // now test the context
+        HttpClient client = new HttpClient();
+        try
+        {
+            client.start();
+            ContentResponse response = client.GET("http://127.0.0.1:" + TestJettyOSGiBootCore.DEFAULT_JETTY_HTTP_PORT + "/acme/index.html");
+            assertEquals(HttpStatus.OK_200, response.getStatus());
+
+            String content = new String(response.getContent());
+            assertTrue(content.indexOf("<h1>Test OSGi Context</h1>") != -1);
+        }
+        finally
+        {
+            client.stop();
+        }
+
+        ServiceReference[] refs = bundleContext.getServiceReferences(ContextHandler.class.getName(), null);
+        assertNotNull(refs);
+        assertEquals(1, refs.length);
+        ContextHandler ch = (ContextHandler) bundleContext.getService(refs[0]);
+        assertEquals("/acme", ch.getContextPath());
+
+        // Stop the bundle with the ContextHandler in it and check the jetty
+        // Context is destroyed for it.
+        // TODO: think of a better way to communicate this to the test, other
+        // than checking stderr output
+        Bundle testWebBundle = TestOSGiUtil.getBundle(bundleContext, "org.eclipse.jetty.osgi.testcontext");
+        assertNotNull("Could not find the org.eclipse.jetty.test-jetty-osgi-context.jar bundle", testWebBundle);
+        assertTrue("The bundle org.eclipse.jetty.testcontext is not correctly resolved", testWebBundle.getState() == Bundle.ACTIVE);
+        testWebBundle.stop();
+    }
+}
diff --git a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootCore.java b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootCore.java
new file mode 100644
index 0000000..8f74dc3
--- /dev/null
+++ b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootCore.java
@@ -0,0 +1,175 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.osgi.test;
+ 
+import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
+import static org.ops4j.pax.exam.CoreOptions.options;
+import static org.ops4j.pax.exam.CoreOptions.systemProperty;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import javax.inject.Inject;
+
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.Configuration;
+import org.ops4j.pax.exam.CoreOptions;
+import org.ops4j.pax.exam.MavenUtils;
+import org.ops4j.pax.exam.Option;
+import org.ops4j.pax.exam.junit.PaxExam;
+import org.ops4j.pax.exam.options.MavenUrlReference.VersionResolver;
+import org.osgi.framework.BundleContext;
+
+
+/**
+ * Default OSGi setup integration test
+ */
+ at RunWith( PaxExam.class )
+public class TestJettyOSGiBootCore
+{
+    private static final String LOG_LEVEL = "WARN";
+    public static int DEFAULT_JETTY_HTTP_PORT = 9876;
+
+    @Inject
+    private BundleContext bundleContext;
+
+    @Configuration
+    public Option[] config()
+    {
+        VersionResolver resolver = MavenUtils.asInProject();
+        ArrayList<Option> options = new ArrayList<Option>();
+        options.addAll(provisionCoreJetty());
+        options.add(CoreOptions.junitBundles());
+        options.addAll(httpServiceJetty());
+        options.addAll(Arrays.asList(options(systemProperty("pax.exam.logging").value("none"))));
+        options.addAll(Arrays.asList(options(systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value(LOG_LEVEL))));
+        options.addAll(Arrays.asList(options(systemProperty("org.eclipse.jetty.LEVEL").value(LOG_LEVEL))));
+        options.addAll(Arrays.asList(options(systemProperty("org.eclipse.jetty.annotations.LEVEL").value("DEBUG"))));
+        return options.toArray(new Option[options.size()]);
+    }
+
+    public static List<Option> provisionCoreJetty()
+    { 
+        List<Option> res = new ArrayList<Option>();
+        // get the jetty home config from the osgi boot bundle.
+        res.add(CoreOptions.systemProperty("jetty.port").value(String.valueOf(DEFAULT_JETTY_HTTP_PORT)));
+        res.add(CoreOptions.systemProperty("jetty.home.bundle").value("org.eclipse.jetty.osgi.boot"));
+        res.addAll(coreJettyDependencies());
+        return res;
+    }
+ 
+     
+    public static List<Option> coreJettyDependencies()
+    {
+        List<Option> res = new ArrayList<Option>();
+
+        res.add(mavenBundle().groupId( "org.ow2.asm" ).artifactId( "asm" ).versionAsInProject().start());
+        res.add(mavenBundle().groupId( "org.ow2.asm" ).artifactId( "asm-commons" ).versionAsInProject().start());
+        res.add(mavenBundle().groupId( "org.ow2.asm" ).artifactId( "asm-tree" ).versionAsInProject().start());
+        res.add(mavenBundle().groupId( "org.apache.aries" ).artifactId( "org.apache.aries.util" ).versionAsInProject().start());
+        res.add(mavenBundle().groupId( "org.apache.aries.spifly" ).artifactId( "org.apache.aries.spifly.dynamic.bundle" ).versionAsInProject().start());
+
+        res.add(mavenBundle().groupId( "org.eclipse.jetty.toolchain" ).artifactId( "jetty-osgi-servlet-api" ).versionAsInProject().noStart());
+        res.add(mavenBundle().groupId( "javax.annotation" ).artifactId( "javax.annotation-api" ).versionAsInProject().noStart());
+        res.add(mavenBundle().groupId( "org.apache.geronimo.specs" ).artifactId( "geronimo-jta_1.1_spec" ).version("1.1.1").noStart());
+        res.add(mavenBundle().groupId( "org.eclipse.jetty.orbit" ).artifactId( "javax.mail.glassfish" ).version( "1.4.1.v201005082020" ).noStart());
+
+        res.add(mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "jetty-deploy" ).versionAsInProject().noStart());
+        res.add(mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "jetty-server" ).versionAsInProject().noStart());  
+        res.add(mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "jetty-servlet" ).versionAsInProject().noStart());  
+        res.add(mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "jetty-util" ).versionAsInProject().noStart());
+        res.add(mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "jetty-http" ).versionAsInProject().noStart());
+        res.add(mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "jetty-xml" ).versionAsInProject().noStart());
+        res.add(mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "jetty-webapp" ).versionAsInProject().noStart());
+        res.add(mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "jetty-io" ).versionAsInProject().noStart());
+        res.add(mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "jetty-continuation" ).versionAsInProject().noStart());
+        res.add(mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "jetty-security" ).versionAsInProject().noStart());
+        res.add(mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "jetty-servlets" ).versionAsInProject().noStart());
+        res.add(mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "jetty-client" ).versionAsInProject().noStart());  
+        res.add(mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "jetty-jndi" ).versionAsInProject().noStart());
+        res.add(mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "jetty-plus" ).versionAsInProject());
+        res.add(mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "jetty-annotations" ).versionAsInProject().start());
+        res.add(mavenBundle().groupId( "org.eclipse.jetty.websocket" ).artifactId( "websocket-api" ).versionAsInProject().noStart());
+        res.add(mavenBundle().groupId( "org.eclipse.jetty.websocket" ).artifactId( "websocket-common" ).versionAsInProject().noStart());
+        res.add(mavenBundle().groupId( "org.eclipse.jetty.websocket" ).artifactId( "websocket-servlet" ).versionAsInProject());
+        res.add(mavenBundle().groupId( "org.eclipse.jetty.websocket" ).artifactId( "websocket-server" ).versionAsInProject());
+        res.add(mavenBundle().groupId( "org.eclipse.jetty.websocket" ).artifactId( "websocket-client" ).versionAsInProject().noStart());
+        res.add(mavenBundle().groupId( "javax.websocket" ).artifactId( "javax.websocket-api" ).versionAsInProject().noStart());
+        res.add(mavenBundle().groupId( "org.eclipse.jetty.websocket" ).artifactId( "javax-websocket-client-impl").versionAsInProject().noStart());
+        res.add(mavenBundle().groupId( "org.eclipse.jetty.websocket" ).artifactId( "javax-websocket-server-impl").versionAsInProject().start());
+        res.add(mavenBundle().groupId( "org.eclipse.jetty.osgi" ).artifactId( "jetty-osgi-boot" ).versionAsInProject().start());
+        return res;
+    }
+    
+    public static List<Option> consoleDependencies()
+    {
+        List<Option> res = new ArrayList<Option>();
+        res.add(systemProperty("osgi.console").value("6666"));
+        res.add(systemProperty("osgi.console.enable.builtin").value("true")); 
+        return res;
+    }
+    
+    
+    
+    public static List<Option> jspDependencies()
+    {
+        List<Option> res = new ArrayList<Option>();
+  
+        //jetty jsp bundles  
+        res.add(mavenBundle().groupId("org.eclipse.jetty.toolchain").artifactId("jetty-schemas").versionAsInProject());
+        res.add(mavenBundle().groupId("org.eclipse.jetty.orbit").artifactId("javax.servlet.jsp.jstl").versionAsInProject());
+        res.add(mavenBundle().groupId("org.mortbay.jasper").artifactId("apache-el").versionAsInProject());
+        res.add(mavenBundle().groupId("org.mortbay.jasper").artifactId("apache-jsp").versionAsInProject());
+        res.add(mavenBundle().groupId("org.eclipse.jetty").artifactId("apache-jsp").versionAsInProject());
+        res.add(mavenBundle().groupId("org.glassfish.web").artifactId("javax.servlet.jsp.jstl").versionAsInProject());
+        res.add(mavenBundle().groupId("org.eclipse.jetty.orbit").artifactId("org.eclipse.jdt.core").versionAsInProject());
+        res.add(mavenBundle().groupId("org.eclipse.jetty.osgi").artifactId("jetty-osgi-boot-jsp").versionAsInProject().noStart());
+        return res;
+    }
+     
+    public static List<Option> httpServiceJetty()
+    {
+        List<Option> res = new ArrayList<Option>();
+        res.add(mavenBundle().groupId( "org.eclipse.jetty.osgi" ).artifactId( "jetty-httpservice" ).versionAsInProject().start());
+        res.add(mavenBundle().groupId( "org.eclipse.equinox.http" ).artifactId( "servlet" ).versionAsInProject().start());
+        return res;
+    }
+     
+    @Ignore
+    @Test
+    public void assertAllBundlesActiveOrResolved() throws Exception
+    {
+        
+        TestOSGiUtil.debugBundles(bundleContext);
+        TestOSGiUtil.assertAllBundlesActiveOrResolved(bundleContext);
+    }
+     
+    /**
+     * You will get a list of bundles installed by default
+     * plus your testcase, wrapped into a bundle called pax-exam-probe
+     */
+    @Test
+    public void testHttpService() throws Exception
+    {
+        TestOSGiUtil.testHttpServiceGreetings(bundleContext, "http", DEFAULT_JETTY_HTTP_PORT);
+    }
+}
diff --git a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootSpdy.java b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootSpdy.java
new file mode 100644
index 0000000..ff8760f
--- /dev/null
+++ b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootSpdy.java
@@ -0,0 +1,120 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.osgi.test;
+
+import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
+import static org.ops4j.pax.exam.CoreOptions.systemProperty;
+import static org.ops4j.pax.exam.CoreOptions.options;
+
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import javax.inject.Inject;
+
+import org.junit.Assert;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.CoreOptions;
+import org.ops4j.pax.exam.Option;
+import org.ops4j.pax.exam.Configuration;
+import org.ops4j.pax.exam.junit.PaxExam;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+
+/**
+ * SPDY setup.
+ */
+ at RunWith(PaxExam.class)
+public class TestJettyOSGiBootSpdy
+{
+    private static final String LOG_LEVEL = "WARN";
+    
+    private static final String JETTY_SPDY_PORT = "jetty.spdy.port";
+
+    private static final int DEFAULT_JETTY_SPDY_PORT = 9877;
+
+    @Inject
+    private BundleContext bundleContext;
+
+    @Configuration
+    public Option[] config()
+    {
+        ArrayList<Option> options = new ArrayList<Option>();
+        options.addAll(TestJettyOSGiBootWithJsp.configureJettyHomeAndPort("jetty-spdy.xml"));
+        options.addAll(TestJettyOSGiBootCore.coreJettyDependencies());
+        options.addAll(spdyJettyDependencies());
+        options.add(CoreOptions.junitBundles());
+        options.addAll(TestJettyOSGiBootCore.httpServiceJetty());
+        options.addAll(Arrays.asList(options(systemProperty("pax.exam.logging").value("none"))));
+        options.addAll(Arrays.asList(options(systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value(LOG_LEVEL))));
+        options.addAll(Arrays.asList(options(systemProperty("org.eclipse.jetty.LEVEL").value(LOG_LEVEL))));
+        return options.toArray(new Option[options.size()]);
+    }
+
+    public static List<Option> spdyJettyDependencies()
+    {
+        List<Option> res = new ArrayList<Option>();
+        res.add(CoreOptions.systemProperty(JETTY_SPDY_PORT).value(String.valueOf(DEFAULT_JETTY_SPDY_PORT)));
+
+        String alpnBoot = System.getProperty("mortbay-alpn-boot");
+        if (alpnBoot == null) { throw new IllegalStateException("Define path to alpn boot jar as system property -Dmortbay-alpn-boot"); }
+        File checkALPNBoot = new File(alpnBoot);
+        if (!checkALPNBoot.exists()) { throw new IllegalStateException("Unable to find the alpn boot jar here: " + alpnBoot); }
+
+
+        res.add(CoreOptions.vmOptions("-Xbootclasspath/p:" + alpnBoot));
+
+        res.add(mavenBundle().groupId("org.eclipse.jetty.osgi").artifactId("jetty-osgi-alpn").versionAsInProject().noStart());
+        res.add(mavenBundle().groupId("org.eclipse.jetty").artifactId("jetty-alpn-server").versionAsInProject().start());
+
+        res.add(mavenBundle().groupId("org.eclipse.jetty.spdy").artifactId("spdy-client").versionAsInProject().noStart());
+        res.add(mavenBundle().groupId("org.eclipse.jetty.spdy").artifactId("spdy-core").versionAsInProject().noStart());
+        res.add(mavenBundle().groupId("org.eclipse.jetty.spdy").artifactId("spdy-server").versionAsInProject().noStart());
+        res.add(mavenBundle().groupId("org.eclipse.jetty.spdy").artifactId("spdy-http-common").versionAsInProject().noStart());
+        res.add(mavenBundle().groupId("org.eclipse.jetty.spdy").artifactId("spdy-http-server").versionAsInProject().noStart());
+        return res;
+    }
+
+    @Test
+    public void checkALPNBootOnBootstrapClasspath() throws Exception
+    {
+        Class<?> alpn = Thread.currentThread().getContextClassLoader().loadClass("org.eclipse.jetty.alpn.ALPN");
+        Assert.assertNotNull(alpn);
+        Assert.assertNull(alpn.getClassLoader());
+    }
+
+    @Ignore
+    @Test
+    public void assertAllBundlesActiveOrResolved()
+    {
+        TestOSGiUtil.debugBundles(bundleContext);
+        TestOSGiUtil.assertAllBundlesActiveOrResolved(bundleContext);
+    }
+
+    @Test
+    public void testSpdyOnHttpService() throws Exception
+    {
+        TestOSGiUtil.testHttpServiceGreetings(bundleContext, "https", DEFAULT_JETTY_SPDY_PORT);
+    }
+
+}
diff --git a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootWebAppAsService.java b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootWebAppAsService.java
new file mode 100644
index 0000000..da84573
--- /dev/null
+++ b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootWebAppAsService.java
@@ -0,0 +1,158 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.osgi.test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
+import static org.ops4j.pax.exam.CoreOptions.options;
+import static org.ops4j.pax.exam.CoreOptions.systemProperty;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import javax.inject.Inject;
+
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.osgi.boot.OSGiServerConstants;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.webapp.WebAppContext;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.CoreOptions;
+import org.ops4j.pax.exam.Option;
+import org.ops4j.pax.exam.Configuration;
+import org.ops4j.pax.exam.junit.PaxExam;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+
+/**
+ * TestJettyOSGiBootWebAppAsService
+ * 
+ * Tests deployment of a WebAppContext as an osgi Service.
+ * 
+ * Tests the ServiceWebAppProvider.
+ * 
+ * Pax-Exam to make sure the jetty-osgi-boot can be started along with the
+ * httpservice web-bundle. Then make sure we can deploy an OSGi service on the
+ * top of this.
+ */
+ at RunWith(PaxExam.class)
+public class TestJettyOSGiBootWebAppAsService
+{
+    private static final String LOG_LEVEL = "WARN";
+
+    @Inject
+    BundleContext bundleContext = null;
+
+    @Configuration
+    public static Option[] configure()
+    {
+        ArrayList<Option> options = new ArrayList<Option>();
+        options.add(CoreOptions.junitBundles());
+        options.addAll(configureJettyHomeAndPort("jetty-selector.xml"));
+        options.add(CoreOptions.bootDelegationPackages("org.xml.sax", "org.xml.*", "org.w3c.*", "javax.xml.*"));
+        options.add(CoreOptions.systemPackages("com.sun.org.apache.xalan.internal.res","com.sun.org.apache.xml.internal.utils",
+                                               "com.sun.org.apache.xml.internal.utils", "com.sun.org.apache.xpath.internal",
+                                               "com.sun.org.apache.xpath.internal.jaxp", "com.sun.org.apache.xpath.internal.objects"));
+     
+        options.addAll(TestJettyOSGiBootCore.coreJettyDependencies());
+        options.addAll(Arrays.asList(options(systemProperty("pax.exam.logging").value("none"))));
+        options.addAll(Arrays.asList(options(systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value(LOG_LEVEL))));
+        options.addAll(Arrays.asList(options(systemProperty("org.eclipse.jetty.LEVEL").value(LOG_LEVEL))));
+
+        options.addAll(jspDependencies());
+        return options.toArray(new Option[options.size()]);
+    }
+    
+
+    public static List<Option> configureJettyHomeAndPort(String jettySelectorFileName)
+    {
+        File etcFolder = new File("src/test/config/etc");
+        String etc = "file://" + etcFolder.getAbsolutePath();
+        List<Option> options = new ArrayList<Option>();
+        options.add(systemProperty(OSGiServerConstants.MANAGED_JETTY_XML_CONFIG_URLS).value(etc     + "/jetty.xml;"
+                + etc
+                + "/"
+                + jettySelectorFileName
+                + ";"
+                + etc
+                + "/jetty-deployer.xml;"
+                + etc
+                + "/jetty-testrealm.xml"));
+        options.add(systemProperty("jetty.port").value(String.valueOf(TestJettyOSGiBootCore.DEFAULT_JETTY_HTTP_PORT)));
+        options.add(systemProperty("jetty.home").value(etcFolder.getParentFile().getAbsolutePath()));
+        return options;
+    }
+
+    public static List<Option> jspDependencies()
+    {
+        List<Option> res = new ArrayList<Option>();
+
+        res.addAll(TestJettyOSGiBootCore.jspDependencies());
+
+        // a bundle that registers a webapp as a service for the jetty osgi core
+        // to pick up and deploy
+        res.add(mavenBundle().groupId("org.eclipse.jetty.osgi").artifactId("test-jetty-osgi-webapp").versionAsInProject().start());
+        return res;
+    }
+
+    @Ignore
+    @Test
+    public void assertAllBundlesActiveOrResolved()
+    {
+        TestOSGiUtil.assertAllBundlesActiveOrResolved(bundleContext);
+    }
+
+    /**
+     */
+    @Test
+    public void testBundle() throws Exception
+    {
+        // now test getting a static file
+        HttpClient client = new HttpClient();
+        try
+        {
+            client.start();
+
+            ContentResponse response = client.GET("http://127.0.0.1:" + TestJettyOSGiBootCore.DEFAULT_JETTY_HTTP_PORT + "/acme/index.html");
+            assertEquals(HttpStatus.OK_200, response.getStatus());
+
+            String content = new String(response.getContent());
+            assertTrue(content.indexOf("<h1>Test OSGi WebApp</h1>") != -1);
+        }
+        finally
+        {
+            client.stop();
+        }
+
+        ServiceReference[] refs = bundleContext.getServiceReferences(ContextHandler.class.getName(), null);
+        assertNotNull(refs);
+        assertEquals(1, refs.length);
+        WebAppContext wac = (WebAppContext) bundleContext.getService(refs[0]);
+        assertEquals("/acme", wac.getContextPath());
+    }
+
+}
diff --git a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootWithAnnotations.java b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootWithAnnotations.java
new file mode 100644
index 0000000..acdd7d8
--- /dev/null
+++ b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootWithAnnotations.java
@@ -0,0 +1,167 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.osgi.test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
+import static org.ops4j.pax.exam.CoreOptions.options;
+import static org.ops4j.pax.exam.CoreOptions.systemProperty;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import javax.inject.Inject;
+
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.osgi.boot.OSGiServerConstants;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.Configuration;
+import org.ops4j.pax.exam.CoreOptions;
+import org.ops4j.pax.exam.Option;
+import org.ops4j.pax.exam.junit.PaxExam;
+import org.osgi.framework.BundleContext;
+
+/**
+ * Pax-Exam to make sure the jetty-osgi-boot can be started along with the
+ * httpservice web-bundle. Then make sure we can deploy an OSGi service on the
+ * top of this.
+ */
+ at RunWith(PaxExam.class)
+
+public class TestJettyOSGiBootWithAnnotations
+{
+    private static final String LOG_LEVEL = "WARN";
+
+    @Inject
+    BundleContext bundleContext = null;
+
+    @Configuration
+    public static Option[] configure()
+    {
+
+        ArrayList<Option> options = new ArrayList<Option>();
+        options.add(CoreOptions.junitBundles());
+        options.addAll(configureJettyHomeAndPort("jetty-selector.xml"));
+        options.add(CoreOptions.bootDelegationPackages("org.xml.sax", "org.xml.*", "org.w3c.*", "javax.sql.*","javax.xml.*", "javax.activation.*"));
+        options.add(CoreOptions.systemPackages("com.sun.org.apache.xalan.internal.res","com.sun.org.apache.xml.internal.utils",
+                                               "com.sun.org.apache.xml.internal.utils", "com.sun.org.apache.xpath.internal",
+                                               "com.sun.org.apache.xpath.internal.jaxp", "com.sun.org.apache.xpath.internal.objects"));
+     
+        options.addAll(TestJettyOSGiBootCore.coreJettyDependencies());
+        options.addAll(Arrays.asList(options(systemProperty("pax.exam.logging").value("none"))));
+        options.addAll(Arrays.asList(options(systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value(LOG_LEVEL))));
+        options.addAll(Arrays.asList(options(systemProperty("org.eclipse.jetty.annotations.LEVEL").value(LOG_LEVEL))));
+        //options.addAll(TestJettyOSGiBootCore.consoleDependencies());
+        options.addAll(jspDependencies());
+        options.addAll(annotationDependencies());
+        return options.toArray(new Option[options.size()]);
+    }
+
+    public static List<Option> configureJettyHomeAndPort(String jettySelectorFileName)
+    {
+        File etcFolder = new File("src/test/config/etc");
+        String etc = "file://" + etcFolder.getAbsolutePath();
+        List<Option> options = new ArrayList<Option>();
+        String xmlConfigs = etc     + "/jetty.xml;"
+                + etc
+                + "/"
+                + jettySelectorFileName
+                + ";"
+                + etc
+                + "/jetty-ssl.xml;"
+                + etc
+                + "/jetty-https.xml;"
+                + etc
+                + "/jetty-deployer.xml;"
+                + etc
+                + "/jetty-testrealm.xml";
+
+        options.add(systemProperty(OSGiServerConstants.MANAGED_JETTY_XML_CONFIG_URLS).value(xmlConfigs));
+        options.add(systemProperty("jetty.port").value(String.valueOf(TestJettyOSGiBootCore.DEFAULT_JETTY_HTTP_PORT)));
+        options.add(systemProperty("jetty.home").value(etcFolder.getParentFile().getAbsolutePath()));
+        return options;
+    }
+
+    public static List<Option> jspDependencies()
+    {
+        return TestJettyOSGiBootCore.jspDependencies();
+    }
+
+    public static List<Option> annotationDependencies()
+    {
+        List<Option> res = new ArrayList<Option>();
+        res.add(mavenBundle().groupId("org.eclipse.jetty.tests").artifactId("test-container-initializer").versionAsInProject());
+        res.add(mavenBundle().groupId("org.eclipse.jetty.tests").artifactId("test-mock-resources").versionAsInProject());
+        //test webapp bundle
+        res.add(mavenBundle().groupId("org.eclipse.jetty.tests").artifactId("test-spec-webapp").classifier("webbundle").versionAsInProject());
+        return res;
+    }
+    
+
+    @Ignore
+    @Test
+    public void assertAllBundlesActiveOrResolved()
+    {
+        TestOSGiUtil.assertAllBundlesActiveOrResolved(bundleContext);
+    }
+
+    // at the moment can't run httpservice with jsp at the same time.
+    // that is a regression in jetty-9
+    @Ignore
+    @Test
+    public void testHttpService() throws Exception
+    {
+        TestOSGiUtil.testHttpServiceGreetings(bundleContext, "http", TestJettyOSGiBootCore.DEFAULT_JETTY_HTTP_PORT);
+    }
+
+ 
+
+    @Test
+    public void testIndex() throws Exception
+    {        
+        HttpClient client = new HttpClient();
+        try
+        {
+            client.start();
+            ContentResponse response = client.GET("http://127.0.0.1:" + TestJettyOSGiBootCore.DEFAULT_JETTY_HTTP_PORT + "/index.html");
+            assertEquals(HttpStatus.OK_200, response.getStatus());
+
+            String content = new String(response.getContent());
+            assertTrue(content.contains("<h1>Servlet 3.1 Test WebApp</h1>"));
+            
+            Request req = client.POST("http://127.0.0.1:" + TestJettyOSGiBootCore.DEFAULT_JETTY_HTTP_PORT + "/test");
+            response = req.send();
+            content = new String(response.getContent());
+            assertTrue(content.contains("<p><b>Result: <span class=\"pass\">PASS</span></p>"));
+        }
+        finally
+        {
+            client.stop();
+        }
+    }
+
+}
diff --git a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootWithJsp.java b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootWithJsp.java
new file mode 100644
index 0000000..0145a1a
--- /dev/null
+++ b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootWithJsp.java
@@ -0,0 +1,153 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.osgi.test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
+import static org.ops4j.pax.exam.CoreOptions.options;
+import static org.ops4j.pax.exam.CoreOptions.systemProperty;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import javax.inject.Inject;
+
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.osgi.boot.OSGiServerConstants;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.Configuration;
+import org.ops4j.pax.exam.CoreOptions;
+import org.ops4j.pax.exam.Option;
+import org.ops4j.pax.exam.junit.PaxExam;
+import org.osgi.framework.BundleContext;
+
+/**
+ * Pax-Exam to make sure the jetty-osgi-boot can be started along with the
+ * httpservice web-bundle. Then make sure we can deploy an OSGi service on the
+ * top of this.
+ */
+ at RunWith(PaxExam.class)
+public class TestJettyOSGiBootWithJsp
+{
+    private static final String LOG_LEVEL = "INFO";
+
+    @Inject
+    BundleContext bundleContext = null;
+
+    @Configuration
+    public static Option[] configure()
+    {
+
+        ArrayList<Option> options = new ArrayList<Option>();
+        options.add(CoreOptions.junitBundles());
+        options.addAll(configureJettyHomeAndPort("jetty-selector.xml"));
+        options.add(CoreOptions.bootDelegationPackages("org.xml.sax", "org.xml.*", "org.w3c.*", "javax.xml.*", "javax.activation.*"));
+        options.add(CoreOptions.systemPackages("com.sun.org.apache.xalan.internal.res","com.sun.org.apache.xml.internal.utils",
+                                               "com.sun.org.apache.xml.internal.utils", "com.sun.org.apache.xpath.internal",
+                                               "com.sun.org.apache.xpath.internal.jaxp", "com.sun.org.apache.xpath.internal.objects"));
+     
+        options.addAll(TestJettyOSGiBootCore.coreJettyDependencies());
+        options.addAll(Arrays.asList(options(systemProperty("pax.exam.logging").value("none"))));
+        options.addAll(Arrays.asList(options(systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value(LOG_LEVEL))));
+        options.addAll(Arrays.asList(options(systemProperty("org.eclipse.jetty.LEVEL").value(LOG_LEVEL))));
+        options.addAll(Arrays.asList(options(systemProperty("org.eclipse.jetty.annotations.LEVEL").value("DEBUG"))));
+        options.addAll(jspDependencies());
+        return options.toArray(new Option[options.size()]);
+    }
+
+    public static List<Option> configureJettyHomeAndPort(String jettySelectorFileName)
+    {
+        File etcFolder = new File("src/test/config/etc");
+        String etc = "file://" + etcFolder.getAbsolutePath();
+        List<Option> options = new ArrayList<Option>();
+        String xmlConfigs = etc     + "/jetty.xml;"
+                + etc
+                + "/"
+                + jettySelectorFileName
+                + ";"
+                + etc
+                + "/jetty-ssl.xml;"
+                + etc
+                + "/jetty-https.xml;"
+                + etc
+                + "/jetty-deployer.xml;"
+                + etc
+                + "/jetty-testrealm.xml";
+
+        options.add(systemProperty(OSGiServerConstants.MANAGED_JETTY_XML_CONFIG_URLS).value(xmlConfigs));
+        options.add(systemProperty("jetty.port").value(String.valueOf(TestJettyOSGiBootCore.DEFAULT_JETTY_HTTP_PORT)));
+        options.add(systemProperty("jetty.home").value(etcFolder.getParentFile().getAbsolutePath()));
+        return options;
+    }
+
+    public static List<Option> jspDependencies()
+    {
+        List<Option> res = new ArrayList<Option>();
+        res.addAll(TestJettyOSGiBootCore.jspDependencies());
+        //test webapp bundle
+        res.add(mavenBundle().groupId("org.eclipse.jetty").artifactId("test-jetty-webapp").classifier("webbundle").versionAsInProject());
+        
+        return res;
+    }
+
+
+    @Test
+    @Ignore
+    public void assertAllBundlesActiveOrResolved()
+    {
+        TestOSGiUtil.debugBundles(bundleContext);
+        TestOSGiUtil.assertAllBundlesActiveOrResolved(bundleContext);
+    }
+
+    // at the moment can't run httpservice with jsp at the same time.
+    // that is a regression in jetty-9
+    @Ignore
+    @Test
+    public void testHttpService() throws Exception
+    {
+        TestOSGiUtil.testHttpServiceGreetings(bundleContext, "http", TestJettyOSGiBootCore.DEFAULT_JETTY_HTTP_PORT);
+    }
+
+ 
+    @Test
+    public void testJspDump() throws Exception
+    {
+   
+        HttpClient client = new HttpClient();
+        try
+        {
+            client.start();
+            ContentResponse response = client.GET("http://127.0.0.1:" + TestJettyOSGiBootCore.DEFAULT_JETTY_HTTP_PORT + "/jsp/jstl.jsp");
+            assertEquals(HttpStatus.OK_200, response.getStatus());
+            String content = new String(response.getContent());
+            assertTrue(content.contains("JSTL Example"));           
+        }
+        finally
+        {
+            client.stop();
+        }
+    }
+}
diff --git a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestOSGiUtil.java b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestOSGiUtil.java
new file mode 100644
index 0000000..82f5d8f
--- /dev/null
+++ b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestOSGiUtil.java
@@ -0,0 +1,189 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.osgi.test;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.junit.Assert;
+import org.ops4j.pax.exam.Option;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.http.HttpService;
+
+/**
+ * Helper methods for pax-exam tests
+ */
+public class TestOSGiUtil
+{
+
+    protected static Bundle getBundle(BundleContext bundleContext, String symbolicName)
+    {
+            Map<String,Bundle> _bundles = new HashMap<String, Bundle>();
+            for (Bundle b : bundleContext.getBundles())
+            {
+                Bundle prevBundle = _bundles.put(b.getSymbolicName(), b);
+                String err = prevBundle != null ? "2 versions of the bundle " + b.getSymbolicName()
+                                                + " "
+                                                + b.getHeaders().get("Bundle-Version")
+                                                + " and "
+                                                + prevBundle.getHeaders().get("Bundle-Version") : "";
+                                                Assert.assertNull(err, prevBundle);
+            }
+        return _bundles.get(symbolicName);
+    }
+
+    protected static void assertActiveBundle(BundleContext bundleContext, String symbolicName) throws Exception
+    {
+        Bundle b = getBundle(bundleContext, symbolicName);
+        Assert.assertNotNull(b);
+        Assert.assertEquals(b.getSymbolicName() + " must be active.", Bundle.ACTIVE, b.getState());
+    }
+
+    protected static void assertActiveOrResolvedBundle(BundleContext bundleContext, String symbolicName) throws Exception
+    {
+        Bundle b = getBundle(bundleContext, symbolicName);
+        Assert.assertNotNull(b);
+        if (b.getHeaders().get("Fragment-Host") == null) diagnoseNonActiveOrNonResolvedBundle(b);
+        Assert.assertTrue(b.getSymbolicName() + " must be active or resolved. It was " + b.getState(),
+                          b.getState() == Bundle.ACTIVE || b.getState() == Bundle.RESOLVED);
+    }
+
+    protected static void assertAllBundlesActiveOrResolved(BundleContext bundleContext)
+    {
+        for (Bundle b : bundleContext.getBundles())
+        {
+            if (b.getState() == Bundle.INSTALLED)
+            {
+                diagnoseNonActiveOrNonResolvedBundle(b);
+            }
+            Assert.assertTrue("Bundle: " + b
+                              + " (state should be "
+                              + "ACTIVE["
+                              + Bundle.ACTIVE
+                              + "] or RESOLVED["
+                              + Bundle.RESOLVED
+                              + "]"
+                              + ", but was ["
+                              + b.getState()
+                              + "])", (b.getState() == Bundle.ACTIVE) || (b.getState() == Bundle.RESOLVED));
+        }
+    }
+
+    protected static boolean diagnoseNonActiveOrNonResolvedBundle(Bundle b)
+    {
+        if (b.getState() != Bundle.ACTIVE && b.getHeaders().get("Fragment-Host") == null)
+        {
+            try
+            {
+                System.err.println("Trying to start the bundle " + b.getSymbolicName() + " that was supposed to be active or resolved.");
+                b.start();
+                System.err.println(b.getSymbolicName() + " did start");
+                return true;
+            }
+            catch (Throwable t)
+            {
+                System.err.println(b.getSymbolicName() + " failed to start");
+                t.printStackTrace(System.err);
+                return false;
+            }
+        }
+        System.err.println(b.getSymbolicName() + " was already started");
+        return false;
+    }
+
+    protected static void debugBundles(BundleContext bundleContext)
+    {
+        Map<String, Bundle> bundlesIndexedBySymbolicName = new HashMap<String, Bundle>();
+        System.err.println("Active " + Bundle.ACTIVE);
+        System.err.println("RESOLVED " + Bundle.RESOLVED);
+        System.err.println("INSTALLED " + Bundle.INSTALLED);
+        for (Bundle b : bundleContext.getBundles())
+        {
+            bundlesIndexedBySymbolicName.put(b.getSymbolicName(), b);
+            System.err.println("    " + b.getSymbolicName() + " " + b.getLocation() + " " + b.getVersion()+ " " + b.getState());
+        }
+    }
+   
+    protected static ServiceReference[] getServices (String service, BundleContext bundleContext) throws Exception
+    {
+       return bundleContext.getAllServiceReferences(service, null);
+    }
+
+    protected static SslContextFactory newSslContextFactory()
+    {
+        SslContextFactory sslContextFactory = new SslContextFactory(true);
+        sslContextFactory.setEndpointIdentificationAlgorithm("");
+        return sslContextFactory;
+    }
+
+    protected static void testHttpServiceGreetings(BundleContext bundleContext, String protocol, int port) throws Exception
+    {
+        assertActiveBundle(bundleContext, "org.eclipse.jetty.osgi.boot");
+
+        assertActiveBundle(bundleContext, "org.eclipse.jetty.osgi.httpservice");
+        assertActiveBundle(bundleContext, "org.eclipse.equinox.http.servlet");
+
+        // in the OSGi world this would be bad code and we should use a bundle
+        // tracker.
+        // here we purposely want to make sure that the httpService is actually
+        // ready.
+        ServiceReference sr = bundleContext.getServiceReference(HttpService.class.getName());
+        Assert.assertNotNull("The httpServiceOSGiBundle is started and should " + "have deployed a service reference for HttpService", sr);
+        HttpService http = (HttpService) bundleContext.getService(sr);
+        http.registerServlet("/greetings", new HttpServlet()
+        {
+            private static final long serialVersionUID = 1L;
+
+            @Override
+            protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+            {
+                resp.getWriter().write("Hello");
+            }
+        }, null, null);
+
+        // now test the servlet
+        HttpClient client = protocol.equals("https") ? new HttpClient(newSslContextFactory()) : new HttpClient();
+        try
+        {
+            client.start();
+            ContentResponse response = client.GET(protocol + "://127.0.0.1:" + port + "/greetings");
+            Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
+
+            String content = new String(response.getContent());
+            Assert.assertEquals("Hello", content);
+        }
+        finally
+        {
+            client.stop();
+        }
+    }
+}
diff --git a/jetty-osgi/test-jetty-osgi/src/test/resources/log4j.properties b/jetty-osgi/test-jetty-osgi/src/test/resources/log4j.properties
new file mode 100644
index 0000000..f9eff1f
--- /dev/null
+++ b/jetty-osgi/test-jetty-osgi/src/test/resources/log4j.properties
@@ -0,0 +1,13 @@
+# LOG4J levels: OFF, FATAL, ERROR, WARN, INFO, DEBUG, ALL
+#
+log4j.rootLogger=ALL,CONSOLE
+
+log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
+#log4j.appender.CONSOLE.threshold=INFO
+log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
+#log4j.appender.CONSOLE.layout.ConversionPattern=%d %t [%5p][%c{1}] %m%n
+log4j.appender.CONSOLE.layout.ConversionPattern=%d [%5p][%c] %m%n
+
+# Level tuning
+log4j.logger.org.eclipse.jetty=INFO
+log4j.logger.org.ops4j=WARN
diff --git a/jetty-overlay-deployer/logs/jtrac.log b/jetty-overlay-deployer/logs/jtrac.log
deleted file mode 100644
index 8c4c974..0000000
--- a/jetty-overlay-deployer/logs/jtrac.log
+++ /dev/null
@@ -1,24 +0,0 @@
-2010-11-17 17:49:57,785 [main] INFO [info.jtrac.config.JtracConfigurer] - found 'jtrac-init.properties' on classpath, processing...
-2010-11-17 17:49:57,785 [main] INFO [info.jtrac.config.JtracConfigurer] - 'jtrac.home' property initialized from 'jtrac-init.properties' as '/tmp/jtrac-red'
-2010-11-17 17:49:57,785 [main] INFO [info.jtrac.config.JtracConfigurer] - locales available configured are 'en'
-2010-11-17 17:49:57,785 [main] INFO [info.jtrac.config.JtracConfigurer] - directory already exists: '/tmp/jtrac-red'
-2010-11-17 17:49:57,785 [main] INFO [info.jtrac.config.JtracConfigurer] - directory already exists: '/tmp/jtrac-red/attachments'
-2010-11-17 17:49:57,785 [main] INFO [info.jtrac.config.JtracConfigurer] - directory already exists: '/tmp/jtrac-red/indexes'
-2010-11-17 17:49:57,785 [main] INFO [info.jtrac.config.JtracConfigurer] - 'jtrac.properties' file exists: '/tmp/jtrac-red/jtrac.properties'
-2010-11-17 17:49:57,786 [main] INFO [info.jtrac.config.JtracConfigurer] - found 'jtrac-version.properties' on classpath, processing...
-2010-11-17 17:49:57,786 [main] INFO [info.jtrac.config.JtracConfigurer] - jtrac.version = '2.1.0'
-2010-11-17 17:49:57,786 [main] INFO [info.jtrac.config.JtracConfigurer] - jtrac.timestamp = '200803022120'
-2010-11-17 17:49:57,786 [main] INFO [info.jtrac.config.JtracConfigurer] - Loading properties file from file [/tmp/jtrac-red/jtrac.properties]
-2010-11-17 17:49:57,804 [main] INFO [info.jtrac.config.DataSourceFactoryBean] - embedded HSQLDB mode detected, switching on spring single connection data source
-2010-11-17 17:49:57,950 [main] INFO [info.jtrac.hibernate.HibernateJtracDao] - database schema exists, normal startup
-2010-11-17 17:49:57,952 [main] INFO [info.jtrac.JtracImpl] - available locales configured {en=en - English}
-2010-11-17 17:49:57,954 [main] WARN [info.jtrac.mail.MailSender] - 'mail.server.host' config is null, mail sender not initialized
-2010-11-17 17:49:57,954 [main] WARN [info.jtrac.JtracImpl] - invalid default locale configured = 'null', using en
-2010-11-17 17:49:57,954 [main] INFO [info.jtrac.JtracImpl] - default locale set to 'en'
-2010-11-17 17:49:57,954 [main] WARN [info.jtrac.JtracImpl] - invalid attachment max size 'null', using 5
-2010-11-17 17:49:57,954 [main] INFO [info.jtrac.JtracImpl] - attachment max size set to 5 MB
-2010-11-17 17:49:57,954 [main] WARN [info.jtrac.JtracImpl] - invalid session timeout 'null', using 30
-2010-11-17 17:49:57,954 [main] INFO [info.jtrac.JtracImpl] - session timeout set to 30 minutes
-2010-11-17 17:49:57,975 [main] INFO [info.jtrac.wicket.JtracApplication] - casProxyTicketValidator not found in application context, CAS single-sign-on is not being used
-2010-11-17 17:53:44,141 [Scanner-0] INFO [info.jtrac.config.DataSourceFactoryBean] - attempting to shut down embedded HSQLDB database
-2010-11-17 17:53:44,253 [Scanner-0] INFO [info.jtrac.config.DataSourceFactoryBean] - embedded HSQLDB database shut down successfully
diff --git a/jetty-overlay-deployer/pom.xml b/jetty-overlay-deployer/pom.xml
index c691c91..a531f46 100644
--- a/jetty-overlay-deployer/pom.xml
+++ b/jetty-overlay-deployer/pom.xml
@@ -2,7 +2,7 @@
   <parent>
     <groupId>org.eclipse.jetty</groupId>
     <artifactId>jetty-project</artifactId>
-    <version>8.1.17.v20150415</version>
+    <version>9.1.0-SNAPSHOT</version>
   </parent>
   <modelVersion>4.0.0</modelVersion>
   <artifactId>jetty-overlay-deployer</artifactId>
@@ -49,13 +49,13 @@
       <version>${project.version}</version>
     </dependency>
     <dependency>
-      <groupId>org.eclipse.jetty.orbit</groupId>
-      <artifactId>javax.transaction</artifactId>
+      <groupId>javax.transaction</groupId>
+      <artifactId>javax.transaction-api</artifactId>
       <scope>test</scope>
     </dependency>
     <dependency>
-      <groupId>junit</groupId>
-      <artifactId>junit</artifactId>
+      <groupId>org.eclipse.jetty.toolchain</groupId>
+      <artifactId>jetty-test-helper</artifactId>
       <scope>test</scope>
     </dependency>
   </dependencies>
diff --git a/jetty-overlay-deployer/src/main/config/modules/overlay.mod b/jetty-overlay-deployer/src/main/config/modules/overlay.mod
new file mode 100644
index 0000000..87bf9e1
--- /dev/null
+++ b/jetty-overlay-deployer/src/main/config/modules/overlay.mod
@@ -0,0 +1,12 @@
+#
+# Jetty Overlay module
+#
+
+[depend]
+deploy
+
+[lib]
+lib/jetty-overlay-deployer-${jetty.version}.jar
+
+[xml]
+etc/jetty-overlay.xml
diff --git a/jetty-overlay-deployer/src/main/java/org/eclipse/jetty/overlays/TemplateContext.java b/jetty-overlay-deployer/src/main/java/org/eclipse/jetty/overlays/TemplateContext.java
index c905d6a..71e0a9a 100644
--- a/jetty-overlay-deployer/src/main/java/org/eclipse/jetty/overlays/TemplateContext.java
+++ b/jetty-overlay-deployer/src/main/java/org/eclipse/jetty/overlays/TemplateContext.java
@@ -25,7 +25,7 @@ import java.util.Map;
 import org.eclipse.jetty.http.MimeTypes;
 import org.eclipse.jetty.server.ResourceCache;
 import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.util.component.AggregateLifeCycle;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
 import org.eclipse.jetty.util.component.Destroyable;
 import org.eclipse.jetty.util.resource.Resource;
 import org.eclipse.jetty.webapp.ClasspathPattern;
@@ -42,7 +42,7 @@ import org.eclipse.jetty.xml.XmlConfiguration;
  * This class is an AggregateLifeCycle, so dependent beans may be added to the template and will be started, stopped and destroyed with the template.
  * The template is started after the template.xml file have been applied. It is stopped and destroyed after the last instance using the template is undeployed.
  */
-public class TemplateContext extends AggregateLifeCycle implements WebAppClassLoader.Context, Destroyable
+public class TemplateContext extends ContainerLifeCycle implements WebAppClassLoader.Context, Destroyable
 {
     private final ClassLoader _libLoader;
     
diff --git a/jetty-overlay-deployer/src/main/java/org/eclipse/jetty/overlays/package-info.java b/jetty-overlay-deployer/src/main/java/org/eclipse/jetty/overlays/package-info.java
new file mode 100644
index 0000000..60dfd98
--- /dev/null
+++ b/jetty-overlay-deployer/src/main/java/org/eclipse/jetty/overlays/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Overlay : Overlay Deployment Provider
+ */
+package org.eclipse.jetty.overlays;
+
diff --git a/jetty-overlay-deployer/src/test/java/org/eclipse/jetty/overlays/OverlayServer.java b/jetty-overlay-deployer/src/test/java/org/eclipse/jetty/overlays/OverlayServer.java
index 09cfbab..4694d08 100644
--- a/jetty-overlay-deployer/src/test/java/org/eclipse/jetty/overlays/OverlayServer.java
+++ b/jetty-overlay-deployer/src/test/java/org/eclipse/jetty/overlays/OverlayServer.java
@@ -25,12 +25,12 @@ import org.eclipse.jetty.jndi.NamingUtil;
 import org.eclipse.jetty.overlays.OverlayedAppProvider;
 import org.eclipse.jetty.server.Handler;
 import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
 import org.eclipse.jetty.server.handler.ContextHandlerCollection;
 import org.eclipse.jetty.server.handler.DefaultHandler;
 import org.eclipse.jetty.server.handler.HandlerCollection;
 import org.eclipse.jetty.server.handler.RequestLogHandler;
 import org.eclipse.jetty.server.handler.StatisticsHandler;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
 
 public class OverlayServer
 {
@@ -51,12 +51,11 @@ public class OverlayServer
                     org.eclipse.jetty.plus.webapp.EnvConfiguration.class.getCanonicalName(),
                     org.eclipse.jetty.plus.webapp.PlusConfiguration.class.getCanonicalName(),
                     org.eclipse.jetty.webapp.JettyWebXmlConfiguration.class.getCanonicalName(),
-                    org.eclipse.jetty.webapp.TagLibConfiguration.class.getCanonicalName()
                 }
         );
         
         // Setup Connectors
-        SelectChannelConnector connector = new SelectChannelConnector();
+        ServerConnector connector = new ServerConnector(server);
         connector.setPort(8080);
         server.addConnector(connector);
         
@@ -85,9 +84,9 @@ public class OverlayServer
         deployer.addAppProvider(provider);
 
         server.setStopAtShutdown(true);
-        server.setSendServerVersion(true);
+        //server.setSendServerVersion(true);
         
-	// Uncomment to work with JNDI examples
+        // Uncomment to work with JNDI examples
         // new org.eclipse.jetty.plus.jndi.Transaction(new com.atomikos.icatch.jta.UserTransactionImp());
         
 
diff --git a/jetty-overlay-deployer/src/test/java/org/eclipse/jetty/overlays/OverlayedAppProviderTest.java b/jetty-overlay-deployer/src/test/java/org/eclipse/jetty/overlays/OverlayedAppProviderTest.java
index 4e6e76c..08d70f8 100644
--- a/jetty-overlay-deployer/src/test/java/org/eclipse/jetty/overlays/OverlayedAppProviderTest.java
+++ b/jetty-overlay-deployer/src/test/java/org/eclipse/jetty/overlays/OverlayedAppProviderTest.java
@@ -20,6 +20,7 @@ package org.eclipse.jetty.overlays;
 
 import java.io.File;
 import java.io.FileOutputStream;
+import java.io.OutputStream;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.util.Set;
@@ -577,9 +578,10 @@ public class OverlayedAppProviderTest
         try
         {
             IO.delete(file);
-            FileOutputStream out = new FileOutputStream(file,false);
-            out.write("<h1>Hello</h1>".getBytes());
-            out.close();
+            try (OutputStream out = new FileOutputStream(file,false))
+            {
+                out.write("<h1>Hello</h1>".getBytes());
+            }
         }
         catch(Exception e)
         {
diff --git a/jetty-overlay-deployer/src/test/resources/home/overlays/instances/myfoo=blue/WEB-INF/overlay.xml b/jetty-overlay-deployer/src/test/resources/home/overlays/instances/myfoo=blue/WEB-INF/overlay.xml
index 9d5eaea..11c70ac 100644
--- a/jetty-overlay-deployer/src/test/resources/home/overlays/instances/myfoo=blue/WEB-INF/overlay.xml
+++ b/jetty-overlay-deployer/src/test/resources/home/overlays/instances/myfoo=blue/WEB-INF/overlay.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0"  encoding="ISO-8859-1"?>
+<?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
 
 <Configure class="org.eclipse.jetty.webapp.WebAppContext">
diff --git a/jetty-overlay-deployer/src/test/resources/home/overlays/instances/myfoo=blue/WEB-INF/web-overlay.xml b/jetty-overlay-deployer/src/test/resources/home/overlays/instances/myfoo=blue/WEB-INF/web-overlay.xml
index 8499944..6eee674 100644
--- a/jetty-overlay-deployer/src/test/resources/home/overlays/instances/myfoo=blue/WEB-INF/web-overlay.xml
+++ b/jetty-overlay-deployer/src/test/resources/home/overlays/instances/myfoo=blue/WEB-INF/web-overlay.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="ISO-8859-1"?>
+<?xml version="1.0" encoding="UTF-8"?>
 <web-app 
    xmlns="http://java.sun.com/xml/ns/javaee" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
diff --git a/jetty-overlay-deployer/src/test/resources/home/overlays/instances/myfoo=green/WEB-INF/overlay.xml b/jetty-overlay-deployer/src/test/resources/home/overlays/instances/myfoo=green/WEB-INF/overlay.xml
index 08ab945..f6cfcf6 100644
--- a/jetty-overlay-deployer/src/test/resources/home/overlays/instances/myfoo=green/WEB-INF/overlay.xml
+++ b/jetty-overlay-deployer/src/test/resources/home/overlays/instances/myfoo=green/WEB-INF/overlay.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0"  encoding="ISO-8859-1"?>
+<?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
 
 <Configure class="org.eclipse.jetty.webapp.WebAppContext">
diff --git a/jetty-overlay-deployer/src/test/resources/home/overlays/instances/myfoo=green/WEB-INF/web-overlay.xml b/jetty-overlay-deployer/src/test/resources/home/overlays/instances/myfoo=green/WEB-INF/web-overlay.xml
index ddda538..92ed729 100644
--- a/jetty-overlay-deployer/src/test/resources/home/overlays/instances/myfoo=green/WEB-INF/web-overlay.xml
+++ b/jetty-overlay-deployer/src/test/resources/home/overlays/instances/myfoo=green/WEB-INF/web-overlay.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="ISO-8859-1"?>
+<?xml version="1.0" encoding="UTF-8"?>
 <web-app 
    xmlns="http://java.sun.com/xml/ns/javaee" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
diff --git a/jetty-overlay-deployer/src/test/resources/home/overlays/instances/myfoo=red/WEB-INF/overlay.xml b/jetty-overlay-deployer/src/test/resources/home/overlays/instances/myfoo=red/WEB-INF/overlay.xml
index 6494f38..6a71455 100644
--- a/jetty-overlay-deployer/src/test/resources/home/overlays/instances/myfoo=red/WEB-INF/overlay.xml
+++ b/jetty-overlay-deployer/src/test/resources/home/overlays/instances/myfoo=red/WEB-INF/overlay.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0"  encoding="ISO-8859-1"?>
+<?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
 
 <Configure class="org.eclipse.jetty.webapp.WebAppContext">
diff --git a/jetty-overlay-deployer/src/test/resources/home/overlays/instances/myfoo=red/WEB-INF/web-overlay.xml b/jetty-overlay-deployer/src/test/resources/home/overlays/instances/myfoo=red/WEB-INF/web-overlay.xml
index 4092789..c8d9dcb 100644
--- a/jetty-overlay-deployer/src/test/resources/home/overlays/instances/myfoo=red/WEB-INF/web-overlay.xml
+++ b/jetty-overlay-deployer/src/test/resources/home/overlays/instances/myfoo=red/WEB-INF/web-overlay.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="ISO-8859-1"?>
+<?xml version="1.0" encoding="UTF-8"?>
 <web-app 
    xmlns="http://java.sun.com/xml/ns/javaee" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
diff --git a/jetty-overlay-deployer/src/test/resources/home/overlays/instances/root=root/WEB-INF/overlay.xml b/jetty-overlay-deployer/src/test/resources/home/overlays/instances/root=root/WEB-INF/overlay.xml
index 47ad8a4..287e1e0 100644
--- a/jetty-overlay-deployer/src/test/resources/home/overlays/instances/root=root/WEB-INF/overlay.xml
+++ b/jetty-overlay-deployer/src/test/resources/home/overlays/instances/root=root/WEB-INF/overlay.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0"  encoding="ISO-8859-1"?>
+<?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
 
 <Configure class="org.eclipse.jetty.server.handler.ContextHandler">
diff --git a/jetty-overlay-deployer/src/test/resources/home/overlays/nodes/nodeA/WEB-INF/web-overlay.xml b/jetty-overlay-deployer/src/test/resources/home/overlays/nodes/nodeA/WEB-INF/web-overlay.xml
index 27c9960..e9f509a 100644
--- a/jetty-overlay-deployer/src/test/resources/home/overlays/nodes/nodeA/WEB-INF/web-overlay.xml
+++ b/jetty-overlay-deployer/src/test/resources/home/overlays/nodes/nodeA/WEB-INF/web-overlay.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="ISO-8859-1"?>
+<?xml version="1.0" encoding="UTF-8"?>
 <web-app 
    xmlns="http://java.sun.com/xml/ns/javaee" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
diff --git a/jetty-overlay-deployer/src/test/resources/home/overlays/nodes/nodeB/WEB-INF/web.xml b/jetty-overlay-deployer/src/test/resources/home/overlays/nodes/nodeB/WEB-INF/web.xml
index d43dd8b..5cf803a 100644
--- a/jetty-overlay-deployer/src/test/resources/home/overlays/nodes/nodeB/WEB-INF/web.xml
+++ b/jetty-overlay-deployer/src/test/resources/home/overlays/nodes/nodeB/WEB-INF/web.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="ISO-8859-1"?>
+<?xml version="1.0" encoding="UTF-8"?>
 <web-app 
    xmlns="http://java.sun.com/xml/ns/javaee" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
diff --git a/jetty-overlay-deployer/src/test/resources/home/overlays/templates/myfoo=foo/WEB-INF/jetty-web.xml b/jetty-overlay-deployer/src/test/resources/home/overlays/templates/myfoo=foo/WEB-INF/jetty-web.xml
index 3352664..16cba2a 100644
--- a/jetty-overlay-deployer/src/test/resources/home/overlays/templates/myfoo=foo/WEB-INF/jetty-web.xml
+++ b/jetty-overlay-deployer/src/test/resources/home/overlays/templates/myfoo=foo/WEB-INF/jetty-web.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0"  encoding="ISO-8859-1"?>
+<?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
 
 <Configure class="org.eclipse.jetty.webapp.WebAppContext">
diff --git a/jetty-overlay-deployer/src/test/resources/home/overlays/templates/myfoo=foo/WEB-INF/template.xml b/jetty-overlay-deployer/src/test/resources/home/overlays/templates/myfoo=foo/WEB-INF/template.xml
index ec8d822..3104fca 100644
--- a/jetty-overlay-deployer/src/test/resources/home/overlays/templates/myfoo=foo/WEB-INF/template.xml
+++ b/jetty-overlay-deployer/src/test/resources/home/overlays/templates/myfoo=foo/WEB-INF/template.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0"  encoding="ISO-8859-1"?>
+<?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
 
 <Configure class="org.eclipse.jetty.overlays.TemplateContext">
diff --git a/jetty-overlay-deployer/src/test/resources/home/overlays/templates/myfoo=foo/WEB-INF/web-default.xml b/jetty-overlay-deployer/src/test/resources/home/overlays/templates/myfoo=foo/WEB-INF/web-default.xml
index 6a11653..22f82f4 100644
--- a/jetty-overlay-deployer/src/test/resources/home/overlays/templates/myfoo=foo/WEB-INF/web-default.xml
+++ b/jetty-overlay-deployer/src/test/resources/home/overlays/templates/myfoo=foo/WEB-INF/web-default.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="ISO-8859-1"?>
+<?xml version="1.0" encoding="UTF-8"?>
 
   <!-- ===================================================================== -->
   <!-- This file contains the default descriptor for web applications.       -->
diff --git a/jetty-overlay-deployer/src/test/resources/home/overlays/templates/myfoo=foo/WEB-INF/web-overlay.xml b/jetty-overlay-deployer/src/test/resources/home/overlays/templates/myfoo=foo/WEB-INF/web-overlay.xml
index c03f435..a554231 100644
--- a/jetty-overlay-deployer/src/test/resources/home/overlays/templates/myfoo=foo/WEB-INF/web-overlay.xml
+++ b/jetty-overlay-deployer/src/test/resources/home/overlays/templates/myfoo=foo/WEB-INF/web-overlay.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="ISO-8859-1"?>
+<?xml version="1.0" encoding="UTF-8"?>
 <web-app 
    xmlns="http://java.sun.com/xml/ns/javaee" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
diff --git a/jetty-overlay-deployer/src/test/resources/home/overlays/templates/root/WEB-INF/overlay.xml b/jetty-overlay-deployer/src/test/resources/home/overlays/templates/root/WEB-INF/overlay.xml
index 2ed2154..813ca58 100644
--- a/jetty-overlay-deployer/src/test/resources/home/overlays/templates/root/WEB-INF/overlay.xml
+++ b/jetty-overlay-deployer/src/test/resources/home/overlays/templates/root/WEB-INF/overlay.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0"  encoding="ISO-8859-1"?>
+<?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://jetty.eclipse.org/configure.dtd">
 
 
diff --git a/jetty-overlay-deployer/src/test/resources/home/overlays/webapps/foo/WEB-INF/web.xml b/jetty-overlay-deployer/src/test/resources/home/overlays/webapps/foo/WEB-INF/web.xml
index 5be798c..b804a9d 100644
--- a/jetty-overlay-deployer/src/test/resources/home/overlays/webapps/foo/WEB-INF/web.xml
+++ b/jetty-overlay-deployer/src/test/resources/home/overlays/webapps/foo/WEB-INF/web.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="ISO-8859-1"?>
+<?xml version="1.0" encoding="UTF-8"?>
 <web-app 
    xmlns="http://java.sun.com/xml/ns/javaee" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
diff --git a/jetty-plus/pom.xml b/jetty-plus/pom.xml
index 645d5e5..5ef12c4 100644
--- a/jetty-plus/pom.xml
+++ b/jetty-plus/pom.xml
@@ -2,7 +2,7 @@
   <parent>
     <groupId>org.eclipse.jetty</groupId>
     <artifactId>jetty-project</artifactId>
-    <version>8.1.17.v20150415</version>
+    <version>9.2.13.v20150730</version>
   </parent>
   <modelVersion>4.0.0</modelVersion>
   <artifactId>jetty-plus</artifactId>
@@ -14,9 +14,6 @@
   </properties>
   <build>
     <plugins>
-<!--
-  COMMENTED OUT UNTIL CORRECT CONFIG IS FOUND FOR Export uses clauses
--->
       <plugin>
         <groupId>org.apache.felix</groupId>
         <artifactId>maven-bundle-plugin</artifactId>
@@ -28,8 +25,17 @@
             </goals>
             <configuration>
               <instructions>
-               <_versionpolicy> </_versionpolicy>
-               <Import-Package>javax.sql.*,javax.security.*,javax.naming.*,javax.servlet.*;version="2.6.0",javax.transaction.*;version="[1.1,1.2)",*</Import-Package>
+               <_nouses>true</_nouses>
+               <!-- Export-Package>
+                 org.eclipse.jetty.plus.annotation;version="${parsedVersion.majorVersion}.${parsedVersion.minorVersion}.${parsedVersion.incrementalVersion}",
+                 org.eclipse.jetty.plus.webapp;version="${parsedVersion.majorVersion}.${parsedVersion.minorVersion}.${parsedVersion.incrementalVersion}",
+                 org.eclipse.jetty.plus.jndi;version="${parsedVersion.majorVersion}.${parsedVersion.minorVersion}.${parsedVersion.incrementalVersion}",
+                 org.eclipse.jetty.plus.security;version="${parsedVersion.majorVersion}.${parsedVersion.minorVersion}.${parsedVersion.incrementalVersion}"
+               </Export-Package -->
+               <Import-Package>javax.sql.*,javax.security.*,javax.naming.*,
+               javax.servlet.*;version="[2.6.0,3.2)",javax.transaction.*;version="[1.1,1.3)",
+               *
+               </Import-Package>
               </instructions>
             </configuration>
           </execution>
@@ -84,13 +90,13 @@
       <scope>test</scope>
     </dependency>
     <dependency>
-      <groupId>junit</groupId>
-      <artifactId>junit</artifactId>
+      <groupId>org.eclipse.jetty.toolchain</groupId>
+      <artifactId>jetty-test-helper</artifactId>
       <scope>test</scope>
     </dependency>
     <dependency>
-      <groupId>org.eclipse.jetty.orbit</groupId>
-      <artifactId>javax.transaction</artifactId>
+      <groupId>javax.transaction</groupId>
+      <artifactId>javax.transaction-api</artifactId>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jetty</groupId>
diff --git a/jetty-plus/src/main/config/etc/jetty-plus.xml b/jetty-plus/src/main/config/etc/jetty-plus.xml
index 9000876..bfbcce5 100644
--- a/jetty-plus/src/main/config/etc/jetty-plus.xml
+++ b/jetty-plus/src/main/config/etc/jetty-plus.xml
@@ -1,110 +1,26 @@
 <?xml version="1.0"?>
-<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
 
 <!-- =============================================================== -->
-<!-- Configure Jetty Plus features                                   -->
-<!--                                                                 -->
-<!-- This file sets up a WebAppDeployer to automatically deploy all  -->
-<!-- webapps in $jetty.home/webapps-plus at startup time, and to     -->
-<!-- enable all of them with Plus features (jndi etc).               -->
-<!--                                                                 -->
-<!-- You can instead configure individual webapps with Jetty Plus    -->
-<!-- features by using the ContextDeployer (configured in            -->
-<!-- $jetty.home/etc/jetty.xml), and ensuring that you set the       -->
-<!-- same set of classes listed below in the "plusConfig" as the     -->
-<!-- webapp's configurationClasses.                                  -->
-<!--                                                                 -->
-<!-- For more information about Jetty Plus, see the Jetty wiki at    -->
-<!-- http://docs.codehaus.org/display/JETTY/Jetty+Wiki               -->
+<!-- Configure extended support for webapps                          -->
 <!-- =============================================================== -->
 <Configure id="Server" class="org.eclipse.jetty.server.Server">
 
   <!-- =========================================================== -->
-  <!-- Example JAAS realm setup.                                   -->
-  <!-- The LoginModuleName must be exactly the same as in the      -->
-  <!-- login.conf file, and the realm Name must be the same as in  -->
-  <!-- the web.xml file.                                           -->
+  <!-- Add plus Configuring classes to all webapps for this Server -->
   <!-- =========================================================== -->
-  <!-- 
-  <Call name="addBean">
-    <Arg>
-      <New class="org.eclipse.jetty.plus.jaas.JAASLoginService">
-	      <Set name="name">xyzrealm</Set>
-	      <Set name="LoginModuleName">xyz</Set>
-	    </New>
-    </Arg>
-  </Call>
-  -->
-
-
-  <!-- =========================================================== -->
-  <!-- Enabling plus feature.                                      -->
-  <!-- Choose one of the following methods. NOTE that by default   -->
-  <!-- the last method (enabled for all webapps on this Server) is -->
-  <!-- enabled.                                                    -->
-  <!--                                                             -->
-  <!-- For a single webapp:                                        -->
-  <!-- Use a context xml file to call                              -->
-  <!--  setConfigurationClasses(plusConfig).                       -->
-  <!--                                                             -->
-  <!-- For all webapps in a directory:                             -->
-  <!-- Uncomment the section entitled  "Apply plusConfig to all    -->
-  <!-- webapps in webapps-plus".                                   -->
-  <!--                                                             -->
-  <!-- For all webapps deployed via this Server instance:          -->
-  <!-- Uncomment the section entitled "Apply plusConfig to all     -->
-  <!-- webapps for this Server". NOTE: this is the default.        -->
-  <!-- =========================================================== -->
-  
-
-  <!-- =========================================================== -->
-  <!-- Sequence of configurations to defining Plus features.       -->
-  <!-- =========================================================== -->
-  <Array id="plusConfig" type="java.lang.String">
-    <Item>org.eclipse.jetty.webapp.WebInfConfiguration</Item>
-    <Item>org.eclipse.jetty.webapp.WebXmlConfiguration</Item>
-    <Item>org.eclipse.jetty.webapp.MetaInfConfiguration</Item>
-    <Item>org.eclipse.jetty.webapp.FragmentConfiguration</Item>
-    <Item>org.eclipse.jetty.plus.webapp.EnvConfiguration</Item>
-    <Item>org.eclipse.jetty.plus.webapp.PlusConfiguration</Item>
-    <Item>org.eclipse.jetty.annotations.AnnotationConfiguration</Item>
-    <Item>org.eclipse.jetty.webapp.JettyWebXmlConfiguration</Item>
-    <Item>org.eclipse.jetty.webapp.TagLibConfiguration</Item>
-  </Array>
-
-  <!-- =========================================================== -->
-  <!-- Apply plusConfig to all webapps in webapps-plus             -->
-  <!-- =========================================================== -->
-  <!-- Uncomment the following to set up a deployer that will      -->
-  <!-- deploy webapps from a directory called webapps-plus. Note   -->
-  <!-- that you will need to create this directory first!          -->
-  <!--
-  <Ref id="DeploymentManager">
-      <Call name="addAppProvider">
-        <Arg>
-          <New class="org.eclipse.jetty.deploy.providers.WebAppProvider">
-            <Set name="monitoredDirName"><Property name="jetty.home" default="." />/webapps-plus</Set>
-            <Set name="defaultsDescriptor"><Property name="jetty.home" default="."/>/etc/webdefault.xml</Set>
-            <Set name="scanInterval">5</Set>
-            <Set name="contextXmlDir"><Property name="jetty.home" default="." />/contexts</Set>
-            <Set name="parentLoaderPriority">false</Set>
-            <Set name="extractWars">true</Set>
-            <Set name="configurationClasses"><Ref id="plusConfig"/></Set>
-          </New>
-        </Arg>
-      </Call>
-  </Ref>
-  -->
-
-  <!-- =========================================================== -->
-  <!-- Apply plusConfig to all webapps for this Server             -->
-  <!-- =========================================================== -->
-    <Call name="setAttribute">
-      <Arg>org.eclipse.jetty.webapp.configuration</Arg>
+  <Call class="org.eclipse.jetty.webapp.Configuration$ClassList" name="setServerDefault">
+    <Arg><Ref refid="Server" /></Arg>
+    <Call name="addAfter">
+      <Arg name="afterClass">org.eclipse.jetty.webapp.FragmentConfiguration</Arg>
       <Arg>
-          <Ref id="plusConfig"/>
+        <Array type="String">
+          <Item>org.eclipse.jetty.plus.webapp.EnvConfiguration</Item>
+          <Item>org.eclipse.jetty.plus.webapp.PlusConfiguration</Item>
+        </Array>
       </Arg>
     </Call>
+  </Call>
 
 </Configure>
 
diff --git a/jetty-plus/src/main/config/modules/plus.mod b/jetty-plus/src/main/config/modules/plus.mod
new file mode 100644
index 0000000..aac0f8f
--- /dev/null
+++ b/jetty-plus/src/main/config/modules/plus.mod
@@ -0,0 +1,15 @@
+#
+# Jetty Plus module
+#
+
+[depend]
+server
+security
+jndi
+webapp
+
+[lib]
+lib/jetty-plus-${jetty.version}.jar
+
+[xml]
+etc/jetty-plus.xml
diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/ContainerInitializer.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/ContainerInitializer.java
index 445d81c..8150e92 100644
--- a/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/ContainerInitializer.java
+++ b/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/ContainerInitializer.java
@@ -18,26 +18,63 @@
 
 package org.eclipse.jetty.plus.annotation;
 
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 import javax.servlet.ServletContainerInitializer;
-import javax.servlet.ServletContext;
 
+import org.eclipse.jetty.util.ConcurrentHashSet;
 import org.eclipse.jetty.util.Loader;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
 import org.eclipse.jetty.webapp.WebAppContext;
 
 public class ContainerInitializer
 {
-    protected ServletContainerInitializer _target;
-    protected Class[] _interestedTypes;
-    protected Set<String> _applicableTypeNames;
-    protected Set<String> _annotatedTypeNames;
-
+    private static final Logger LOG = Log.getLogger(ContainerInitializer.class);
     
-    public void setTarget (ServletContainerInitializer target)
+    final protected ServletContainerInitializer _target;
+    final protected Class<?>[] _interestedTypes;
+    final protected Set<String> _applicableTypeNames = new ConcurrentHashSet<String>();
+    final protected Set<String> _annotatedTypeNames = new ConcurrentHashSet<String>();
+
+
+    public ContainerInitializer (ServletContainerInitializer target, Class<?>[] classes)
     {
         _target = target;
+        _interestedTypes = classes;
+    }
+    
+    public ContainerInitializer (ClassLoader loader, String toString)
+    {
+        Matcher m = Pattern.compile("ContainerInitializer\\{(.*),interested=(.*),applicable=(.*),annotated=(.*)\\}").matcher(toString);
+        if (!m.matches())
+            throw new IllegalArgumentException(toString);
+
+        try
+        {
+            _target = (ServletContainerInitializer)loader.loadClass(m.group(1)).newInstance();
+            String[] interested = StringUtil.arrayFromString(m.group(2));
+            _interestedTypes = new Class<?>[interested.length];
+            for (int i=0;i<interested.length;i++)
+                _interestedTypes[i]=loader.loadClass(interested[i]);
+            for (String s:StringUtil.arrayFromString(m.group(3)))
+                _applicableTypeNames.add(s);
+            for (String s:StringUtil.arrayFromString(m.group(4)))
+                _annotatedTypeNames.add(s);
+        }
+        catch(Exception e)
+        {
+            throw new IllegalArgumentException(toString, e);
+        }
     }
     
     public ServletContainerInitializer getTarget ()
@@ -49,66 +86,127 @@ public class ContainerInitializer
     {
         return _interestedTypes;
     }
-    
-    public void setInterestedTypes (Class[] interestedTypes)
-    {
-        _interestedTypes = interestedTypes;
-    }
-    
+
+
     /**
-     * A class has been found that has an annotation of interest 
+     * A class has been found that has an annotation of interest
      * to this initializer.
      * @param className
      */
     public void addAnnotatedTypeName (String className)
     {
-        if (_annotatedTypeNames == null)
-            _annotatedTypeNames = new HashSet<String>();
         _annotatedTypeNames.add(className);
     }
-    
+
     public Set<String> getAnnotatedTypeNames ()
     {
-        return _annotatedTypeNames;
+        return Collections.unmodifiableSet(_annotatedTypeNames);
     }
-    
+
     public void addApplicableTypeName (String className)
     {
-        if (_applicableTypeNames == null)
-            _applicableTypeNames = new HashSet<String>();
         _applicableTypeNames.add(className);
     }
-    
+
     public Set<String> getApplicableTypeNames ()
     {
-        return _applicableTypeNames;
+        return Collections.unmodifiableSet(_applicableTypeNames);
     }
-    
-    
+
+
     public void callStartup(WebAppContext context)
     throws Exception
     {
         if (_target != null)
         {
             Set<Class<?>> classes = new HashSet<Class<?>>();
-          
+
             ClassLoader oldLoader = Thread.currentThread().getContextClassLoader();
             Thread.currentThread().setContextClassLoader(context.getClassLoader());
 
             try
             {
-                if (_applicableTypeNames != null)
+                for (String s : _applicableTypeNames)
+                    classes.add(Loader.loadClass(context.getClass(), s));
+
+                context.getServletContext().setExtendedListenerTypes(true);
+                if (LOG.isDebugEnabled())
                 {
-                    for (String s : _applicableTypeNames)
-                        classes.add(Loader.loadClass(context.getClass(), s));
+                    long start = System.nanoTime();
+                    _target.onStartup(classes, context.getServletContext());
+                    LOG.debug("ContainerInitializer {} called in {}ms", _target.getClass().getName(), TimeUnit.MILLISECONDS.convert(System.nanoTime()-start, TimeUnit.NANOSECONDS));
                 }
-
-                _target.onStartup(classes, context.getServletContext());
+                else
+                    _target.onStartup(classes, context.getServletContext());
             }
             finally
-            {
+            { 
+                context.getServletContext().setExtendedListenerTypes(false);
                 Thread.currentThread().setContextClassLoader(oldLoader);
             }
         }
     }
+
+    public String toString()
+    {
+        List<String> interested = Collections.emptyList();
+        if (_interestedTypes != null)
+        {
+            interested = new ArrayList<>(_interestedTypes.length);
+            for (Class<?> c : _interestedTypes)
+                interested.add(c.getName());
+        }
+
+        return String.format("ContainerInitializer{%s,interested=%s,applicable=%s,annotated=%s}",_target.getClass().getName(),interested,_applicableTypeNames,_annotatedTypeNames);
+    }
+
+    public void resolveClasses(WebAppContext context, Map<String, Set<String>> classMap) 
+    {
+        //We have already found the classes that directly have an annotation that was in the HandlesTypes
+        //annotation of the ServletContainerInitializer. For each of those classes, walk the inheritance
+        //hierarchy to find classes that extend or implement them.
+        Set<String> annotatedClassNames = getAnnotatedTypeNames();
+        if (annotatedClassNames != null && !annotatedClassNames.isEmpty())
+        {
+            for (String name : annotatedClassNames)
+            {
+                //add the class that has the annotation
+                addApplicableTypeName(name);
+
+                //find and add the classes that inherit the annotation               
+                addInheritedTypes(classMap, (Set<String>)classMap.get(name));
+            }
+        }
+
+
+        //Now we need to look at the HandlesTypes classes that were not annotations. We need to
+        //find all classes that extend or implement them.
+        if (getInterestedTypes() != null)
+        {
+            for (Class<?> c : getInterestedTypes())
+            {
+                if (!c.isAnnotation())
+                {
+                    //find and add the classes that implement or extend the class.
+                    //but not including the class itself
+                    addInheritedTypes(classMap, (Set<String>)classMap.get(c.getName()));
+                }
+            }
+        }
+    }
+
+    private void addInheritedTypes(Map<String, Set<String>> classMap,Set<String> names)
+    {
+        if (names == null || names.isEmpty())
+            return;
+
+        for (String s : names)
+        {
+            //add the name of the class
+            addApplicableTypeName(s);
+
+            //walk the hierarchy and find all types that extend or implement the class
+            addInheritedTypes(classMap, (Set<String>)classMap.get(s));
+        }
+    }
 }
diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/Injection.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/Injection.java
index b4f7678..b533a38 100644
--- a/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/Injection.java
+++ b/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/Injection.java
@@ -49,11 +49,11 @@ public class Injection
     private Class<?> _paramClass;
     private Class<?> _resourceClass;
 
-    
+
     public Injection ()
     {
     }
-    
+
 
     /**
      * @return the _className
@@ -67,22 +67,22 @@ public class Injection
     {
         return _paramClass;
     }
-   
+
     public Class<?> getResourceClass ()
     {
         return _resourceClass;
     }
-    
+
     public boolean isField ()
     {
         return (_target != null && _target instanceof Field);
     }
-    
+
     public boolean isMethod ()
     {
         return (_target != null && _target instanceof Method);
     }
-    
+
     /**
      * @return the jndiName
      */
@@ -111,7 +111,7 @@ public class Injection
     {
         this._mappingName = mappingName;
     }
-    
+
     /**
      * @return the target
      */
@@ -119,7 +119,7 @@ public class Injection
     {
         return _target;
     }
-    
+
 
     public void setTarget(Class<?> clazz, Field field, Class<?> resourceType)
     {
@@ -127,7 +127,7 @@ public class Injection
         _target = field;
         _resourceClass = resourceType;
     }
-    
+
     public void setTarget(Class<?> clazz, Method method, Class<?> arg, Class<?> resourceType)
     {
         _targetClass = clazz;
@@ -135,12 +135,12 @@ public class Injection
         _resourceClass = resourceType;
         _paramClass = arg;
     }
-   
+
     public void setTarget (Class<?> clazz, String target, Class<?> resourceType)
     {
         _targetClass = clazz;
         _resourceClass = resourceType;
-        
+
         //first look for a javabeans style setter matching the targetName
         String setter = "set"+target.substring(0,1).toUpperCase(Locale.ENGLISH)+target.substring(1);
         try
@@ -165,13 +165,13 @@ public class Injection
         }
 
     }
-    
+
     /**
      * Inject a value for a Resource from JNDI into an object
      * @param injectable
      */
     public void inject (Object injectable)
-    { 
+    {
         if (_target != null)
         {
             if (_target instanceof Field)
@@ -183,7 +183,7 @@ public class Injection
             throw new IllegalStateException ("No method or field to inject with "+getJndiName());
     }
 
-    
+
     /**
      * The Resource must already exist in the ENC of this webapp.
      * @return the injected valud
@@ -195,7 +195,7 @@ public class Injection
         InitialContext context = new InitialContext();
         return context.lookup("java:comp/env/"+getJndiName());
     }
-    
+
 
 
     /**
@@ -204,7 +204,7 @@ public class Injection
      * @param injectable
      */
     protected void injectField (Field field, Object injectable)
-    {        
+    {
         try
         {
             boolean accessibility = field.isAccessible();
@@ -239,5 +239,5 @@ public class Injection
             throw new IllegalStateException("Inject failed for method "+method.getName());
         }
     }
-   
+
 }
diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/LifeCycleCallback.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/LifeCycleCallback.java
index a9d0e91..f226179 100644
--- a/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/LifeCycleCallback.java
+++ b/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/LifeCycleCallback.java
@@ -24,6 +24,7 @@ import java.lang.reflect.Modifier;
 
 import org.eclipse.jetty.util.IntrospectionUtil;
 import org.eclipse.jetty.util.Loader;
+import org.eclipse.jetty.util.TypeUtil;
 
 
 
@@ -39,8 +40,8 @@ public abstract class LifeCycleCallback
     private Class<?> _targetClass;
     private String _className;
     private String _methodName;
-    
-    
+
+
     public LifeCycleCallback()
     {
     }
@@ -53,17 +54,17 @@ public abstract class LifeCycleCallback
     {
         return _targetClass;
     }
-    
+
     public String getTargetClassName()
     {
         return _className;
     }
-    
+
     public String getMethodName()
     {
         return _methodName;
     }
-    
+
     /**
      * @return the target
      */
@@ -71,8 +72,8 @@ public abstract class LifeCycleCallback
     {
         return _target;
     }
-    
-    
+
+
     public void setTarget (String className, String methodName)
     {
         _className = className;
@@ -97,18 +98,18 @@ public abstract class LifeCycleCallback
     }
 
 
-    
-    
-    public void callback (Object instance) 
+
+
+    public void callback (Object instance)
     throws SecurityException, NoSuchMethodException, ClassNotFoundException, IllegalArgumentException, IllegalAccessException, InvocationTargetException
     {
         if (_target == null)
         {
             if (_targetClass == null)
                 _targetClass = Loader.loadClass(null, _className);
-            _target = _targetClass.getDeclaredMethod(_methodName, new Class[]{}); //TODO
+            _target = _targetClass.getDeclaredMethod(_methodName, TypeUtil.NO_ARGS);
         }
-        
+
         if (_target != null)
         {
             boolean accessibility = getTarget().isAccessible();
@@ -118,15 +119,15 @@ public abstract class LifeCycleCallback
         }
     }
 
-    
+
 
     /**
      * Find a method of the given name either directly in the given
      * class, or inherited.
-     * 
+     *
      * @param pack the package of the class under inspection
      * @param clazz the class under inspection
-     * @param methodName the method to find 
+     * @param methodName the method to find
      * @param checkInheritance false on first entry, true if a superclass is being introspected
      * @return the method
      */
@@ -137,7 +138,7 @@ public abstract class LifeCycleCallback
 
         try
         {
-            Method method = clazz.getDeclaredMethod(methodName, null);
+            Method method = clazz.getDeclaredMethod(methodName);
             if (checkInheritance)
             {
                 int modifiers = method.getModifiers();
@@ -161,7 +162,7 @@ public abstract class LifeCycleCallback
         if (!(o instanceof LifeCycleCallback))
             return false;
         LifeCycleCallback callback = (LifeCycleCallback)o;
-        
+
         if (callback.getTargetClass()==null)
         {
             if (getTargetClass() != null)
@@ -176,9 +177,9 @@ public abstract class LifeCycleCallback
         }
         else if (!callback.getTarget().equals(getTarget()))
             return false;
-        
+
         return true;
     }
-    
+
     public abstract void validate (Class<?> clazz, Method m);
 }
diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/LifeCycleCallbackCollection.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/LifeCycleCallbackCollection.java
index 7cd257f..99f2b5c 100644
--- a/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/LifeCycleCallbackCollection.java
+++ b/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/LifeCycleCallbackCollection.java
@@ -19,6 +19,8 @@
 package org.eclipse.jetty.plus.annotation;
 
 import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -136,4 +138,51 @@ public class LifeCycleCallbackCollection
         for (int i=0;i<callbacks.size();i++)
             ((LifeCycleCallback)callbacks.get(i)).callback(o);
     }
+    
+    /**
+     * Generate a read-only view of the post-construct callbacks
+     * @return
+     */
+    public Map<String, List<LifeCycleCallback>> getPostConstructCallbackMap()
+    {
+        return Collections.unmodifiableMap(postConstructCallbacksMap);
+    }
+    
+    /**
+     * Generate a read-only view of the pre-destroy callbacks
+     * @return
+     */
+    public Map<String, List<LifeCycleCallback>> getPreDestroyCallbackMap()
+    {
+        return Collections.unmodifiableMap(preDestroyCallbacksMap);
+    }
+    
+    /**
+     * Amalgamate all post-construct callbacks and return a read only list
+     * @return
+     */
+    public Collection<LifeCycleCallback> getPostConstructCallbacks()
+    {
+        List<LifeCycleCallback> list = new ArrayList<LifeCycleCallback>();
+        for (String s:postConstructCallbacksMap.keySet())
+        {
+            list.addAll(postConstructCallbacksMap.get(s));
+        }
+        return Collections.unmodifiableCollection(list);
+    }
+    
+    /**
+     * Amalgamate all pre-destroy callbacks and return a read only list
+     * @return
+     */
+    public Collection<LifeCycleCallback> getPreDestroyCallbacks()
+    {
+        List<LifeCycleCallback> list = new ArrayList<LifeCycleCallback>();
+        for (String s:preDestroyCallbacksMap.keySet())
+        {
+            list.addAll(preDestroyCallbacksMap.get(s));
+        }
+        return Collections.unmodifiableCollection(list);
+    }
+    
 }
diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/RunAs.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/RunAs.java
index b6c84a7..eb9876f 100644
--- a/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/RunAs.java
+++ b/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/RunAs.java
@@ -18,8 +18,6 @@
 
 package org.eclipse.jetty.plus.annotation;
 
-import javax.servlet.ServletException;
-
 import org.eclipse.jetty.servlet.ServletHolder;
 
 /**
@@ -57,8 +55,10 @@ public class RunAs
     }
 
 
+    /**
+     * @param holder
+     */
     public void setRunAs (ServletHolder holder)
-    throws ServletException
     {
         if (holder == null)
             return;
diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/RunAsCollection.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/RunAsCollection.java
index 53a52cf..4238d6a 100644
--- a/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/RunAsCollection.java
+++ b/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/RunAsCollection.java
@@ -20,8 +20,6 @@ package org.eclipse.jetty.plus.annotation;
 
 import java.util.HashMap;
 
-import javax.servlet.ServletException;
-
 import org.eclipse.jetty.servlet.ServletHolder;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
@@ -38,41 +36,39 @@ public class RunAsCollection
 
     public static final String RUNAS_COLLECTION = "org.eclipse.jetty.runAsCollection";
     private HashMap<String, RunAs> _runAsMap = new HashMap<String, RunAs>();//map of classname to run-as
-  
-    
-    
+
+
+
     public void add (RunAs runAs)
     {
-        if ((runAs==null) || (runAs.getTargetClassName()==null)) 
+        if ((runAs==null) || (runAs.getTargetClassName()==null))
             return;
-        
+
         if (LOG.isDebugEnabled())
             LOG.debug("Adding run-as for class="+runAs.getTargetClassName());
         _runAsMap.put(runAs.getTargetClassName(), runAs);
     }
 
     public RunAs getRunAs (Object o)
-    throws ServletException
     {
         if (o==null)
             return null;
-        
+
         return (RunAs)_runAsMap.get(o.getClass().getCanonicalName());
     }
-    
+
     public void setRunAs(Object o)
-    throws ServletException
     {
         if (o == null)
             return;
-        
+
         if (!ServletHolder.class.isAssignableFrom(o.getClass()))
             return;
-      
+
         RunAs runAs = (RunAs)_runAsMap.get(o.getClass().getName());
         if (runAs == null)
             return;
-        
+
         runAs.setRunAs((ServletHolder)o);
     }
 
diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/package-info.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/package-info.java
new file mode 100644
index 0000000..3c9c2a0
--- /dev/null
+++ b/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Plus : Limited JEE Annotation Support
+ */
+package org.eclipse.jetty.plus.annotation;
+
diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/JAASGroup.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/JAASGroup.java
deleted file mode 100644
index f16d948..0000000
--- a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/JAASGroup.java
+++ /dev/null
@@ -1,152 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.plus.jaas;
-
-import java.security.Principal;
-import java.security.acl.Group;
-import java.util.Enumeration;
-import java.util.HashSet;
-import java.util.Iterator;
-
-
-public class JAASGroup implements Group 
-{
-    public static final String ROLES = "__roles__";
-    
-    private String _name = null;
-    private HashSet<Principal> _members = null;
-    
-    
-   
-    public JAASGroup(String n)
-    {
-        this._name = n;
-        this._members = new HashSet<Principal>();
-    }
-   
-    /* ------------------------------------------------------------ */
-    /**
-     *
-     * @param principal <description>
-     * @return <description>
-     */
-    public synchronized boolean addMember(Principal principal)
-    {
-        return _members.add(principal);
-    }
-
-    /**
-     *
-     * @param principal <description>
-     * @return <description>
-     */
-    public synchronized boolean removeMember(Principal principal)
-    {
-        return _members.remove(principal);
-    }
-
-    /**
-     *
-     * @param principal <description>
-     * @return <description>
-     */
-    public boolean isMember(Principal principal)
-    {
-        return _members.contains(principal);
-    }
-
-
-    
-    /**
-     *
-     * @return <description>
-     */
-    public Enumeration<? extends Principal> members()
-    {
-
-        class MembersEnumeration implements Enumeration<Principal>
-        {
-            private Iterator<? extends Principal> itor;
-            
-            public MembersEnumeration (Iterator<? extends Principal> itor)
-            {
-                this.itor = itor;
-            }
-            
-            public boolean hasMoreElements ()
-            {
-                return this.itor.hasNext();
-            }
-
-
-            public Principal nextElement ()
-            {
-                return this.itor.next();
-            }
-            
-        }
-
-        return new MembersEnumeration (_members.iterator());
-    }
-
-
-    /**
-     *
-     * @return <description>
-     */
-    public int hashCode()
-    {
-        return getName().hashCode();
-    }
-
-
-    
-    /**
-     *
-     * @param object <description>
-          * @return <description>
-     */
-    public boolean equals(Object object)
-    {
-        if (! (object instanceof JAASGroup))
-            return false;
-
-        return ((JAASGroup)object).getName().equals(getName());
-    }
-
-    /**
-     *
-     * @return <description>
-     */
-    public String toString()
-    {
-        return getName();
-    }
-
-    /**
-     *
-     * @return <description>
-     */
-    public String getName()
-    {
-        
-        return _name;
-    }
-
-}
diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/JAASLoginService.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/JAASLoginService.java
deleted file mode 100644
index 6668306..0000000
--- a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/JAASLoginService.java
+++ /dev/null
@@ -1,336 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.plus.jaas;
-
-import java.io.IOException;
-import java.security.Principal;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.LinkedHashSet;
-import java.util.Set;
-
-import javax.security.auth.Subject;
-import javax.security.auth.callback.Callback;
-import javax.security.auth.callback.CallbackHandler;
-import javax.security.auth.callback.NameCallback;
-import javax.security.auth.callback.PasswordCallback;
-import javax.security.auth.callback.UnsupportedCallbackException;
-import javax.security.auth.login.LoginContext;
-import javax.security.auth.login.LoginException;
-
-import org.eclipse.jetty.plus.jaas.callback.ObjectCallback;
-import org.eclipse.jetty.plus.jaas.callback.RequestParameterCallback;
-import org.eclipse.jetty.security.DefaultIdentityService;
-import org.eclipse.jetty.security.IdentityService;
-import org.eclipse.jetty.security.LoginService;
-import org.eclipse.jetty.server.AbstractHttpConnection;
-import org.eclipse.jetty.server.Request;
-import org.eclipse.jetty.server.UserIdentity;
-import org.eclipse.jetty.util.Loader;
-import org.eclipse.jetty.util.component.AbstractLifeCycle;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-
-/* ---------------------------------------------------- */
-/** JAASLoginService
- * 
- * @org.apache.xbean.XBean element="jaasUserRealm" description="Creates a UserRealm suitable for use with JAAS"
- */
-public class JAASLoginService extends AbstractLifeCycle implements LoginService
-{
-    private static final Logger LOG = Log.getLogger(JAASLoginService.class);
-
-    public static String DEFAULT_ROLE_CLASS_NAME = "org.eclipse.jetty.plus.jaas.JAASRole";
-    public static String[] DEFAULT_ROLE_CLASS_NAMES = {DEFAULT_ROLE_CLASS_NAME};
-	
-    protected String[] _roleClassNames = DEFAULT_ROLE_CLASS_NAMES;
-    protected String _callbackHandlerClass;
-    protected String _realmName;
-    protected String _loginModuleName;
-    protected JAASUserPrincipal _defaultUser = new JAASUserPrincipal(null, null, null);
-    protected IdentityService _identityService;
- 
-    /* ---------------------------------------------------- */
-    /**
-     * Constructor.
-     *
-     */
-    public JAASLoginService()
-    {
-    }
-    
-
-    /* ---------------------------------------------------- */
-    /**
-     * Constructor.
-     *
-     * @param name the name of the realm
-     */
-    public JAASLoginService(String name)
-    {
-        this();
-        _realmName = name;
-        _loginModuleName = name;
-    }
-
-
-    /* ---------------------------------------------------- */
-    /**
-     * Get the name of the realm.
-     *
-     * @return name or null if not set.
-     */
-    public String getName()
-    {
-        return _realmName;
-    }
-
-
-    /* ---------------------------------------------------- */
-    /**
-     * Set the name of the realm
-     *
-     * @param name a <code>String</code> value
-     */
-    public void setName (String name)
-    {
-        _realmName = name;
-    }
-
-    /* ------------------------------------------------------------ */
-    /** Get the identityService.
-     * @return the identityService
-     */
-    public IdentityService getIdentityService()
-    {
-        return _identityService;
-    }
-
-    /* ------------------------------------------------------------ */
-    /** Set the identityService.
-     * @param identityService the identityService to set
-     */
-    public void setIdentityService(IdentityService identityService)
-    {
-        _identityService = identityService;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Set the name to use to index into the config
-     * file of LoginModules.
-     *
-     * @param name a <code>String</code> value
-     */
-    public void setLoginModuleName (String name)
-    {
-        _loginModuleName = name;
-    }
-
-    /* ------------------------------------------------------------ */
-    public void setCallbackHandlerClass (String classname)
-    {
-        _callbackHandlerClass = classname;
-    }
-
-    /* ------------------------------------------------------------ */
-    public void setRoleClassNames (String[] classnames)
-    {
-        ArrayList<String> tmp = new ArrayList<String>();
-        
-        if (classnames != null)
-            tmp.addAll(Arrays.asList(classnames));
-         
-        if (!tmp.contains(DEFAULT_ROLE_CLASS_NAME))
-            tmp.add(DEFAULT_ROLE_CLASS_NAME);
-        _roleClassNames = tmp.toArray(new String[tmp.size()]);
-    }
-
-    /* ------------------------------------------------------------ */
-    public String[] getRoleClassNames()
-    {
-        return _roleClassNames;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart()
-     */
-    protected void doStart() throws Exception
-    {
-        if (_identityService==null)
-            _identityService=new DefaultIdentityService();
-        super.doStart();
-    }
-
-    /* ------------------------------------------------------------ */
-    public UserIdentity login(final String username,final Object credentials)
-    {
-        try
-        {
-            CallbackHandler callbackHandler = null;
-            
-            
-            if (_callbackHandlerClass == null)
-            {
-                callbackHandler = new CallbackHandler()
-                {
-                    public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException
-                    {
-                        for (Callback callback: callbacks)
-                        {
-                            if (callback instanceof NameCallback)
-                            {
-                                ((NameCallback)callback).setName(username);
-                            }
-                            else if (callback instanceof PasswordCallback)
-                            {
-                                ((PasswordCallback)callback).setPassword((char[]) credentials.toString().toCharArray());
-                            }
-                            else if (callback instanceof ObjectCallback)
-                            {
-                                ((ObjectCallback)callback).setObject(credentials);
-                            }
-                            else if (callback instanceof RequestParameterCallback)
-                            {
-                                AbstractHttpConnection connection = AbstractHttpConnection.getCurrentConnection();
-                                Request request = (connection == null? null : connection.getRequest());
-                                
-                                if (request != null)
-                                {
-                                    RequestParameterCallback rpc = (RequestParameterCallback)callback;
-                                    rpc.setParameterValues(Arrays.asList(request.getParameterValues(rpc.getParameterName())));
-                                }
-                            }
-                            else 
-                                throw new UnsupportedCallbackException(callback);
-                        }
-                    }
-                };
-            }
-            else
-            {
-                Class clazz = Loader.loadClass(getClass(), _callbackHandlerClass);
-                callbackHandler = (CallbackHandler)clazz.newInstance();
-            }
-            //set up the login context
-            //TODO jaspi requires we provide the Configuration parameter
-            Subject subject = new Subject();
-            LoginContext loginContext = new LoginContext(_loginModuleName, subject, callbackHandler);
-
-            loginContext.login();
-
-            //login success
-            JAASUserPrincipal userPrincipal = new JAASUserPrincipal(getUserName(callbackHandler), subject, loginContext);
-            subject.getPrincipals().add(userPrincipal);
-            
-            return _identityService.newUserIdentity(subject,userPrincipal,getGroups(subject));
-        }
-        catch (LoginException e)
-        {
-            LOG.debug(e);
-        }
-        catch (IOException e)
-        {
-            LOG.info(e.getMessage());
-            LOG.debug(e);
-        }
-        catch (UnsupportedCallbackException e)
-        {
-            LOG.info(e.getMessage());
-            LOG.debug(e);
-        }
-        catch (InstantiationException e)
-        {
-            LOG.info(e.getMessage());
-            LOG.debug(e);
-        }
-        catch (IllegalAccessException e)
-        {
-            LOG.info(e.getMessage());
-            LOG.debug(e);
-        }
-        catch (ClassNotFoundException e)
-        {
-            LOG.info(e.getMessage());
-            LOG.debug(e);
-        }
-        return null;
-    }
-
-    /* ------------------------------------------------------------ */
-    public boolean validate(UserIdentity user)
-    {
-        // TODO optionally check user is still valid
-        return true;
-    }
-
-    /* ------------------------------------------------------------ */
-    private String getUserName(CallbackHandler callbackHandler) throws IOException, UnsupportedCallbackException
-    {
-        NameCallback nameCallback = new NameCallback("foo");
-        callbackHandler.handle(new Callback[] {nameCallback});
-        return nameCallback.getName();
-    }
-
-    /* ------------------------------------------------------------ */
-    public void logout(UserIdentity user)
-    {
-        Set<JAASUserPrincipal> userPrincipals = user.getSubject().getPrincipals(JAASUserPrincipal.class);
-        LoginContext loginContext = userPrincipals.iterator().next().getLoginContext();
-        try
-        {
-            loginContext.logout();
-        }
-        catch (LoginException e)
-        {
-            LOG.warn(e);
-        }
-    }
-
-
-    /* ------------------------------------------------------------ */
-    @SuppressWarnings({ "unchecked", "rawtypes" })
-    private String[] getGroups (Subject subject)
-    {
-        //get all the roles of the various types
-        String[] roleClassNames = getRoleClassNames();
-        Collection<String> groups = new LinkedHashSet<String>();
-        try
-        {
-            for (String roleClassName : roleClassNames)
-            {
-                Class load_class = Thread.currentThread().getContextClassLoader().loadClass(roleClassName);
-                Set<Principal> rolesForType = subject.getPrincipals(load_class);
-                for (Principal principal : rolesForType)
-                {
-                    groups.add(principal.getName());
-                }
-            }
-            
-            return groups.toArray(new String[groups.size()]);
-        }
-        catch (ClassNotFoundException e)
-        {
-            throw new RuntimeException(e);
-        }
-    }
-
-}
diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/JAASPrincipal.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/JAASPrincipal.java
deleted file mode 100644
index 454f256..0000000
--- a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/JAASPrincipal.java
+++ /dev/null
@@ -1,89 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.plus.jaas;
-
-import java.io.Serializable;
-import java.security.Principal;
-
-
-
-/* ---------------------------------------------------- */
-/** JAASPrincipal
- * <p>Impl class of Principal interface.
- *
- * <p><h4>Notes</h4>
- * <p>
- *
- * <p><h4>Usage</h4>
- * <pre>
- */
-/*
- * </pre>
- *
- * @see
- * @version 1.0 Tue Apr 15 2003
- * 
- */
-public class JAASPrincipal implements Principal, Serializable
-{
-    /**
-     * 
-     */
-    private static final long serialVersionUID = -5538962177019315479L;
-    
-    private String _name = null;
-    
-    
-    public JAASPrincipal(String userName)
-    {
-        this._name = userName;
-    }
-
-
-    public boolean equals (Object p)
-    {
-        if (! (p instanceof JAASPrincipal))
-            return false;
-
-        return getName().equals(((JAASPrincipal)p).getName());
-    }
-
-
-    public int hashCode ()
-    {
-        return getName().hashCode();
-    }
-
-
-    public String getName ()
-    {
-        return this._name;
-    }
-
-
-    public String toString ()
-    {
-        return getName();
-    }
-    
-
-    
-}
-
-    
diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/JAASRole.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/JAASRole.java
deleted file mode 100644
index a4f8816..0000000
--- a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/JAASRole.java
+++ /dev/null
@@ -1,42 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.plus.jaas;
-
-
-public class JAASRole extends JAASPrincipal
-{
-    
-    /**
-     * 
-     */
-    private static final long serialVersionUID = 3465114254970134526L;
-
-    public JAASRole(String name)
-    {
-        super (name);
-    }
-
-    public boolean equals (Object o)
-    {
-        if (! (o instanceof JAASRole))
-            return false;
-
-        return getName().equals(((JAASRole)o).getName());
-    }
-}
diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/JAASUserPrincipal.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/JAASUserPrincipal.java
deleted file mode 100644
index 35a1f3f..0000000
--- a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/JAASUserPrincipal.java
+++ /dev/null
@@ -1,79 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.plus.jaas;
-
-import java.security.Principal;
-
-import javax.security.auth.Subject;
-import javax.security.auth.login.LoginContext;
-
-
-
-/* ---------------------------------------------------- */
-/** JAASUserPrincipal
- * <p>Implements the JAAS version of the 
- *  org.eclipse.jetty.http.UserPrincipal interface.
- *
- * @version $Id: JAASUserPrincipal.java 4780 2009-03-17 15:36:08Z jesse $
- * 
- */
-public class JAASUserPrincipal implements Principal 
-{
-    private final String _name;
-    private final Subject _subject;
-    private final LoginContext _loginContext;
-
-    /* ------------------------------------------------ */
-
-    public JAASUserPrincipal(String name, Subject subject, LoginContext loginContext)
-    {
-        this._name = name;
-        this._subject = subject;
-        this._loginContext = loginContext;
-    }
-
-    /* ------------------------------------------------ */
-    /** Get the name identifying the user
-     */
-    public String getName ()
-    {
-        return _name;
-    }
-    
-    
-    /* ------------------------------------------------ */
-    /** Provide access to the Subject
-     * @return subject
-     */
-    public Subject getSubject ()
-    {
-        return this._subject;
-    }
-    
-    LoginContext getLoginContext ()
-    {
-        return this._loginContext;
-    }
-    
-    public String toString()
-    {
-        return getName();
-    }
-    
-}
diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/RoleCheckPolicy.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/RoleCheckPolicy.java
deleted file mode 100644
index 9fd4ea1..0000000
--- a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/RoleCheckPolicy.java
+++ /dev/null
@@ -1,36 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.plus.jaas;
-
-import java.security.Principal;
-import java.security.acl.Group;
-
-
-public interface RoleCheckPolicy 
-{
-    /* ------------------------------------------------ */
-    /** Check if a role is either a runAsRole or in a set of roles
-     * @param roleName the role to check
-     * @param runAsRole a pushed role (can be null)
-     * @param roles a Group whose Principals are role names
-     * @return <code>true</code> if <code>role</code> equals <code>runAsRole</code> or is a member of <code>roles</code>.
-     */
-    public boolean checkRole (String roleName, Principal runAsRole, Group roles);
-    
-}
diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/StrictRoleCheckPolicy.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/StrictRoleCheckPolicy.java
deleted file mode 100644
index 51c867e..0000000
--- a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/StrictRoleCheckPolicy.java
+++ /dev/null
@@ -1,63 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.plus.jaas;
-
-import java.security.Principal;
-import java.security.acl.Group;
-import java.util.Enumeration;
-
-
-/* ---------------------------------------------------- */
-/** StrictRoleCheckPolicy
- * <p>Enforces that if a runAsRole is present, then the
- * role to check must be the same as that runAsRole and
- * the set of static roles is ignored.
- * 
- *
- * 
- * @org.apache.xbean.XBean description ="Check only topmost role in stack of roles for user"
- */
-public class StrictRoleCheckPolicy implements RoleCheckPolicy
-{
-
-    public boolean checkRole (String roleName, Principal runAsRole, Group roles)
-    {
-        //check if this user has had any temporary role pushed onto
-        //them. If so, then only check if the user has that role.
-        if (runAsRole != null)
-        {
-            return (roleName.equals(runAsRole.getName()));
-        }
-        else
-        {
-            if (roles == null)
-                return false;
-            Enumeration<? extends Principal> rolesEnum = roles.members();
-            boolean found = false;
-            while (rolesEnum.hasMoreElements() && !found)
-            {
-                Principal p = (Principal)rolesEnum.nextElement();
-                found = roleName.equals(p.getName());
-            }
-            return found;
-        }
-        
-    }
-    
-}
diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/callback/AbstractCallbackHandler.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/callback/AbstractCallbackHandler.java
deleted file mode 100644
index 5ec3694..0000000
--- a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/callback/AbstractCallbackHandler.java
+++ /dev/null
@@ -1,60 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.plus.jaas.callback;
-
-import java.io.IOException;
-
-import javax.security.auth.callback.Callback;
-import javax.security.auth.callback.CallbackHandler;
-import javax.security.auth.callback.UnsupportedCallbackException;
-
-
-public abstract class AbstractCallbackHandler implements CallbackHandler
-{
-    protected String _userName;
-    protected Object _credential;
-
-    public void setUserName (String userName)
-    {
-        _userName = userName;
-    }
-
-    public String getUserName ()
-    {
-        return _userName;
-    }
-    
-
-    public void setCredential (Object credential)
-    {
-        _credential = credential;
-    }
-
-    public Object getCredential ()
-    {
-        return _credential;
-    }
-    
-    public  void handle (Callback[] callbacks)
-        throws IOException, UnsupportedCallbackException
-    {
-    }
-    
-    
-}
diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/callback/DefaultCallbackHandler.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/callback/DefaultCallbackHandler.java
deleted file mode 100644
index ec89802..0000000
--- a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/callback/DefaultCallbackHandler.java
+++ /dev/null
@@ -1,97 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.plus.jaas.callback;
-
-import java.io.IOException;
-import java.util.Arrays;
-
-import javax.security.auth.callback.Callback;
-import javax.security.auth.callback.NameCallback;
-import javax.security.auth.callback.PasswordCallback;
-import javax.security.auth.callback.UnsupportedCallbackException;
-
-import org.eclipse.jetty.util.security.Password;
-import org.eclipse.jetty.server.Request;
-
-
-
-/* ---------------------------------------------------- */
-/** DefaultUsernameCredentialCallbackHandler
- * <p>
- *
- * <p><h4>Notes</h4>
- * <p>
- *
- * <p><h4>Usage</h4>
- * <pre>
- */
-/*
- * </pre>
- *
- * @see
- * @version 1.0 Tue Apr 15 2003
- * 
- */
-public class DefaultCallbackHandler extends AbstractCallbackHandler
-{
-     
-    private Request _request;
-    
-    public void setRequest (Request request)
-    {
-        this._request = request;
-    }
-    
-    public void handle (Callback[] callbacks)
-        throws IOException, UnsupportedCallbackException
-    {
-        for (int i=0; i < callbacks.length; i++)
-        {
-            if (callbacks[i] instanceof NameCallback)
-            {
-                ((NameCallback)callbacks[i]).setName(getUserName());
-            }
-            else if (callbacks[i] instanceof ObjectCallback)
-            {
-                ((ObjectCallback)callbacks[i]).setObject(getCredential());
-            }
-            else if (callbacks[i] instanceof PasswordCallback)
-            {
-                if (getCredential() instanceof Password)
-                    ((PasswordCallback)callbacks[i]).setPassword (((Password)getCredential()).toString().toCharArray());
-                else if (getCredential() instanceof String)
-                {
-                    ((PasswordCallback)callbacks[i]).setPassword (((String)getCredential()).toCharArray());
-                }
-                else
-                    throw new UnsupportedCallbackException (callbacks[i], "User supplied credentials cannot be converted to char[] for PasswordCallback: try using an ObjectCallback instead");
-            }
-            else if (callbacks[i] instanceof RequestParameterCallback)
-            {
-                RequestParameterCallback callback = (RequestParameterCallback)callbacks[i];
-                callback.setParameterValues(Arrays.asList(_request.getParameterValues(callback.getParameterName())));
-            }
-            else
-                throw new UnsupportedCallbackException(callbacks[i]);
-        }
-        
-    }
-    
-}
-        
diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/callback/ObjectCallback.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/callback/ObjectCallback.java
deleted file mode 100644
index e37f59a..0000000
--- a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/callback/ObjectCallback.java
+++ /dev/null
@@ -1,67 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.plus.jaas.callback;
-
-import javax.security.auth.callback.Callback;
-
-
-/* ---------------------------------------------------- */
-/** ObjectCallback
- *
- * <p>Can be used as a LoginModule Callback to
- * obtain a user's credential as an Object, rather than
- * a char[], to which some credentials may not be able
- * to be converted
- *
- * <p><h4>Notes</h4>
- * <p>
- *
- * <p><h4>Usage</h4>
- * <pre>
- */
-/*
- * </pre>
- *
- * @see
- * @version 1.0 Tue Apr 15 2003
- * 
- */
-public class ObjectCallback implements Callback
-{
-
-    protected Object _object;
-    
-    public void setObject(Object o)
-    {
-        _object = o;
-    }
-
-    public Object getObject ()
-    {
-        return _object;
-    }
-
-
-    public void clearObject ()
-    {
-        _object = null;
-    }
-    
-    
-}
diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/callback/RequestParameterCallback.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/callback/RequestParameterCallback.java
deleted file mode 100644
index 9d697f8..0000000
--- a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/callback/RequestParameterCallback.java
+++ /dev/null
@@ -1,61 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.plus.jaas.callback;
-
-import java.util.List;
-
-import javax.security.auth.callback.Callback;
-
-
-/**
- * 
- * RequestParameterCallback
- * 
- * Allows a JAAS callback handler to access any parameter from the j_security_check FORM.
- * This means that a LoginModule can access form fields other than the j_username and j_password
- * fields, and use it, for example, to authenticate a user.
- *
- * 
- * @version $Revision: 4780 $ $Date: 2009-03-17 16:36:08 +0100 (Tue, 17 Mar 2009) $
- *
- */
-public class RequestParameterCallback implements Callback
-{
-    private String _paramName;
-    private List<?> _paramValues;
-    
-    public void setParameterName (String name)
-    {
-        _paramName = name;
-    }
-    public String getParameterName ()
-    {
-        return _paramName;
-    }
-    
-    public void setParameterValues (List<?> values)
-    {
-        _paramValues = values;
-    }
-    
-    public List<?> getParameterValues ()
-    {
-        return _paramValues;
-    }
-}
diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/spi/AbstractDatabaseLoginModule.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/spi/AbstractDatabaseLoginModule.java
deleted file mode 100644
index 454b86d..0000000
--- a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/spi/AbstractDatabaseLoginModule.java
+++ /dev/null
@@ -1,144 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.plus.jaas.spi;
-
-import java.sql.Connection;
-import java.sql.PreparedStatement;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-
-import javax.security.auth.Subject;
-import javax.security.auth.callback.CallbackHandler;
-
-import org.eclipse.jetty.util.security.Credential;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-
-/**
- * AbstractDatabaseLoginModule
- *
- * Abstract base class for LoginModules that interact with a 
- * database to retrieve authentication and authorization information.
- * Used by the JDBCLoginModule and DataSourceLoginModule.
- *
- */
-public abstract class AbstractDatabaseLoginModule extends AbstractLoginModule
-{
-    private static final Logger LOG = Log.getLogger(AbstractDatabaseLoginModule.class);
-
-    private String userQuery;
-    private String rolesQuery;
-    private String dbUserTable;
-    private String dbUserTableUserField;
-    private String dbUserTableCredentialField;
-    private String dbUserRoleTable;
-    private String dbUserRoleTableUserField;
-    private String dbUserRoleTableRoleField;
-    
-    
-    
-    
-    /**
-     * @return a java.sql.Connection from the database
-     * @throws Exception
-     */
-    public abstract Connection getConnection () throws Exception;
-    
-   
-    
-    /* ------------------------------------------------ */
-    /** Load info from database
-     * @param userName user info to load
-     * @exception SQLException 
-     */
-    public UserInfo getUserInfo (String userName)
-        throws Exception
-    {
-        Connection connection = null;
-        
-        try
-        {
-            connection = getConnection();
-            
-            //query for credential
-            PreparedStatement statement = connection.prepareStatement (userQuery);
-            statement.setString (1, userName);
-            ResultSet results = statement.executeQuery();
-            String dbCredential = null;
-            if (results.next())
-            {
-                dbCredential = results.getString(1);
-            }
-            results.close();
-            statement.close();
-            
-            //query for role names
-            statement = connection.prepareStatement (rolesQuery);
-            statement.setString (1, userName);
-            results = statement.executeQuery();
-            List<String> roles = new ArrayList<String>();
-            
-            while (results.next())
-            {
-                String roleName = results.getString (1);
-                roles.add (roleName);
-            }
-            
-            results.close();
-            statement.close();
-            
-            return dbCredential==null ? null : new UserInfo (userName, 
-                    Credential.getCredential(dbCredential), roles);
-        }
-        finally
-        {
-            if (connection != null) connection.close();
-        }
-    }
-    
-
-    public void initialize(Subject subject,
-            CallbackHandler callbackHandler,
-            Map<String,?> sharedState,
-            Map<String,?> options)
-    {
-        super.initialize(subject, callbackHandler, sharedState, options);
-        
-        //get the user credential query out of the options
-        dbUserTable = (String)options.get("userTable");
-        dbUserTableUserField = (String)options.get("userField");
-        dbUserTableCredentialField = (String)options.get("credentialField");
-        
-        userQuery = "select "+dbUserTableCredentialField+" from "+dbUserTable+" where "+dbUserTableUserField+"=?";
-        
-        
-        //get the user roles query out of the options
-        dbUserRoleTable = (String)options.get("userRoleTable");
-        dbUserRoleTableUserField = (String)options.get("userRoleUserField");
-        dbUserRoleTableRoleField = (String)options.get("userRoleRoleField");
-        
-        rolesQuery = "select "+dbUserRoleTableRoleField+" from "+dbUserRoleTable+" where "+dbUserRoleTableUserField+"=?";
-        
-        if(LOG.isDebugEnabled())LOG.debug("userQuery = "+userQuery);
-        if(LOG.isDebugEnabled())LOG.debug("rolesQuery = "+rolesQuery);
-    }
-}
diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/spi/AbstractLoginModule.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/spi/AbstractLoginModule.java
deleted file mode 100644
index 66ec00e..0000000
--- a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/spi/AbstractLoginModule.java
+++ /dev/null
@@ -1,303 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.plus.jaas.spi;
-
-import java.io.IOException;
-import java.security.Principal;
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-
-import javax.security.auth.Subject;
-import javax.security.auth.callback.Callback;
-import javax.security.auth.callback.CallbackHandler;
-import javax.security.auth.callback.NameCallback;
-import javax.security.auth.callback.PasswordCallback;
-import javax.security.auth.callback.UnsupportedCallbackException;
-import javax.security.auth.login.FailedLoginException;
-import javax.security.auth.login.LoginException;
-import javax.security.auth.spi.LoginModule;
-
-import org.eclipse.jetty.plus.jaas.JAASPrincipal;
-import org.eclipse.jetty.plus.jaas.JAASRole;
-import org.eclipse.jetty.plus.jaas.callback.ObjectCallback;
-
-/**
- * AbstractLoginModule
- *
- * Abstract base class for all LoginModules. Subclasses should 
- * just need to implement getUserInfo method.
- *
- */
-public abstract class AbstractLoginModule implements LoginModule
-{
-    private CallbackHandler callbackHandler;
-    
-    private boolean authState = false;
-    private boolean commitState = false;
-    private JAASUserInfo currentUser;
-    private Subject subject;
-    
-    public class JAASUserInfo
-    {
-        private UserInfo user;
-        private Principal principal;
-        private List<JAASRole> roles;
-              
-        public JAASUserInfo (UserInfo u)
-        {
-            setUserInfo(u);
-        }
-        
-        public String getUserName ()
-        {
-            return this.user.getUserName();
-        }
-        
-        public Principal getPrincipal()
-        {
-            return this.principal;
-        }
-        
-        public void setUserInfo (UserInfo u)
-        {
-            this.user = u;
-            this.principal = new JAASPrincipal(u.getUserName());
-            this.roles = new ArrayList<JAASRole>();
-            if (u.getRoleNames() != null)
-            {
-                Iterator<String> itor = u.getRoleNames().iterator();
-                while (itor.hasNext())
-                    this.roles.add(new JAASRole((String)itor.next()));
-            }
-        }
-               
-        public void setJAASInfo (Subject subject)
-        {
-            subject.getPrincipals().add(this.principal);
-            subject.getPrivateCredentials().add(this.user.getCredential());
-            subject.getPrincipals().addAll(roles);
-        }
-        
-        public void unsetJAASInfo (Subject subject)
-        {
-            subject.getPrincipals().remove(this.principal);
-            subject.getPrivateCredentials().remove(this.user.getCredential());
-            subject.getPrincipals().removeAll(this.roles);
-        }
-        
-        public boolean checkCredential (Object suppliedCredential)
-        {
-            return this.user.checkCredential(suppliedCredential);
-        }
-    }
-    
-    
-    
-    public Subject getSubject ()
-    {
-        return this.subject;
-    }
-    
-    public void setSubject (Subject s)
-    {
-        this.subject = s;
-    }
-    
-    public JAASUserInfo getCurrentUser()
-    {
-        return this.currentUser;
-    }
-    
-    public void setCurrentUser (JAASUserInfo u)
-    {
-        this.currentUser = u;
-    }
-    
-    public CallbackHandler getCallbackHandler()
-    {
-        return this.callbackHandler;
-    }
-    
-    public void setCallbackHandler(CallbackHandler h)
-    {
-        this.callbackHandler = h; 
-    }
-    
-    public boolean isAuthenticated()
-    {
-        return this.authState;
-    }
-    
-    public boolean isCommitted ()
-    {
-        return this.commitState;
-    }
-    
-    public void setAuthenticated (boolean authState)
-    {
-        this.authState = authState;
-    }
-    
-    public void setCommitted (boolean commitState)
-    {
-        this.commitState = commitState;
-    }
-    /** 
-     * @see javax.security.auth.spi.LoginModule#abort()
-     * @throws LoginException
-     */
-    public boolean abort() throws LoginException
-    {
-        this.currentUser = null;
-        return (isAuthenticated() && isCommitted());
-    }
-
-    /** 
-     * @see javax.security.auth.spi.LoginModule#commit()
-     * @return true if committed, false if not (likely not authenticated)
-     * @throws LoginException
-     */
-    public boolean commit() throws LoginException
-    {
-
-        if (!isAuthenticated())
-        {
-            currentUser = null;
-            setCommitted(false);
-            return false;
-        }
-        
-        setCommitted(true);
-        currentUser.setJAASInfo(subject);
-        return true;
-    }
-
-    
-    public Callback[] configureCallbacks ()
-    {
-     
-        Callback[] callbacks = new Callback[3];
-        callbacks[0] = new NameCallback("Enter user name");
-        callbacks[1] = new ObjectCallback();
-        callbacks[2] = new PasswordCallback("Enter password", false); //only used if framework does not support the ObjectCallback
-        return callbacks;
-    }
-    
-    
-    public boolean isIgnored ()
-    {
-        return false;
-    }
-    
-    
-    public abstract UserInfo getUserInfo (String username) throws Exception;
-    
-    
-    
-    /** 
-     * @see javax.security.auth.spi.LoginModule#login()
-     * @return true if is authenticated, false otherwise
-     * @throws LoginException
-     */
-    public boolean login() throws LoginException
-    {
-        try
-        {  
-            if (isIgnored())
-                return false;
-            
-            if (callbackHandler == null)
-                throw new LoginException ("No callback handler");
-            
-            Callback[] callbacks = configureCallbacks();
-            callbackHandler.handle(callbacks);
-
-            String webUserName = ((NameCallback)callbacks[0]).getName();
-            Object webCredential = null;
-            
-            webCredential = ((ObjectCallback)callbacks[1]).getObject(); //first check if ObjectCallback has the credential
-            if (webCredential == null)
-                webCredential = ((PasswordCallback)callbacks[2]).getPassword(); //use standard PasswordCallback
-
-            if ((webUserName == null) || (webCredential == null))
-            {
-                setAuthenticated(false);
-                throw new FailedLoginException();
-            }
-
-            UserInfo userInfo = getUserInfo(webUserName);
-
-            if (userInfo == null)
-            {
-                setAuthenticated(false);
-                throw new FailedLoginException();
-            }
-
-            currentUser = new JAASUserInfo(userInfo);
-            setAuthenticated(currentUser.checkCredential(webCredential));
-          
-            if (isAuthenticated())
-                return true;
-            else
-                throw new FailedLoginException();
-        }
-        catch (IOException e)
-        {
-            throw new LoginException (e.toString());
-        }
-        catch (UnsupportedCallbackException e)
-        {
-            throw new LoginException (e.toString());
-        }
-        catch (Exception e)
-        {
-            if (e instanceof LoginException)
-                throw (LoginException)e;
-            throw new LoginException (e.toString());
-        }
-    }
-
-    /** 
-     * @see javax.security.auth.spi.LoginModule#logout()
-     * @return true always
-     * @throws LoginException
-     */
-    public boolean logout() throws LoginException
-    {
-        this.currentUser.unsetJAASInfo(this.subject);
-        return true;
-    }
-
-    /** 
-     * @see javax.security.auth.spi.LoginModule#initialize(javax.security.auth.Subject, javax.security.auth.callback.CallbackHandler, java.util.Map, java.util.Map)
-     * @param subject
-     * @param callbackHandler
-     * @param sharedState
-     * @param options
-     */
-    public void initialize(Subject subject, CallbackHandler callbackHandler,
-            Map<String,?> sharedState, Map<String,?> options)
-    {
-        this.callbackHandler = callbackHandler;
-        this.subject = subject;
-    }
-
-}
diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/spi/DataSourceLoginModule.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/spi/DataSourceLoginModule.java
deleted file mode 100644
index fea9d7f..0000000
--- a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/spi/DataSourceLoginModule.java
+++ /dev/null
@@ -1,89 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.plus.jaas.spi;
-import java.sql.Connection;
-import java.util.Map;
-
-import javax.naming.InitialContext;
-import javax.naming.NamingException;
-import javax.security.auth.Subject;
-import javax.security.auth.callback.CallbackHandler;
-import javax.sql.DataSource;
-
-/**
- * DataSourceLoginModule
- *
- * A LoginModule that uses a DataSource to retrieve user authentication
- * and authorisation information.
- * 
- * @see JDBCLoginModule
- */
-public class DataSourceLoginModule extends AbstractDatabaseLoginModule
-{
-
-    private String dbJNDIName;
-    private DataSource dataSource;
-    
-    /* ------------------------------------------------ */
-    /** Init LoginModule.
-     * Called once by JAAS after new instance created.
-     * @param subject 
-     * @param callbackHandler 
-     * @param sharedState 
-     * @param options 
-     */
-    public void initialize(Subject subject,
-                           CallbackHandler callbackHandler,
-                           Map<String,?> sharedState,
-                           Map<String,?> options)
-    {
-        try
-        {
-            super.initialize(subject, callbackHandler, sharedState, options);
-            
-            //get the datasource jndi name
-            dbJNDIName = (String)options.get("dbJNDIName");
-            
-            InitialContext ic = new InitialContext();
-            dataSource = (DataSource)ic.lookup("java:comp/env/"+dbJNDIName);
-        }
-        catch (NamingException e)
-        {
-            throw new IllegalStateException (e.toString());
-        }
-    }
-
-
-    /** 
-     * Get a connection from the DataSource
-     * @see AbstractDatabaseLoginModule#getConnection()
-     * @return the connection for the datasource 
-     * @throws Exception
-     */
-    public Connection getConnection ()
-    throws Exception
-    {
-        return dataSource.getConnection();
-    }
-
-
-    
-  
-
-}
diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/spi/JDBCLoginModule.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/spi/JDBCLoginModule.java
deleted file mode 100644
index fd45ec3..0000000
--- a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/spi/JDBCLoginModule.java
+++ /dev/null
@@ -1,127 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.plus.jaas.spi;
-
-import java.sql.Connection;
-import java.sql.DriverManager;
-import java.util.Map;
-
-import javax.security.auth.Subject;
-import javax.security.auth.callback.CallbackHandler;
-
-import org.eclipse.jetty.util.Loader;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-
-
-
-/* ---------------------------------------------------- */
-/** JDBCLoginModule
- * <p>JAAS LoginModule to retrieve user information from
- *  a database and authenticate the user.
- *
- * <p><h4>Notes</h4>
- * <p>This version uses plain old JDBC connections NOT
- * Datasources.
- *
- * <p><h4>Usage</h4>
- * <pre>
- * </pre>
- *
- * @version 1.0 Tue Apr 15 2003
- */
-public class JDBCLoginModule extends AbstractDatabaseLoginModule
-{
-    private static final Logger LOG = Log.getLogger(JDBCLoginModule.class);
-
-    private String dbDriver;
-    private String dbUrl;
-    private String dbUserName;
-    private String dbPassword;
-
-    
-    /** 
-     * Get a connection from the DriverManager
-     * @see AbstractDatabaseLoginModule#getConnection()
-     * @return the connection for this datasource
-     * @throws Exception
-     */
-    public Connection getConnection ()
-    throws Exception
-    {
-        if (!((dbDriver != null)
-                &&
-                (dbUrl != null)))
-            throw new IllegalStateException ("Database connection information not configured");
-        
-        if(LOG.isDebugEnabled())LOG.debug("Connecting using dbDriver="+dbDriver+"+ dbUserName="+dbUserName+", dbPassword="+dbUrl);
-        
-        return DriverManager.getConnection (dbUrl,
-                dbUserName,
-                dbPassword);
-    }
-   
-   
-    
-    /* ------------------------------------------------ */
-    /** Init LoginModule.
-     * Called once by JAAS after new instance created.
-     * @param subject 
-     * @param callbackHandler 
-     * @param sharedState 
-     * @param options 
-     */
-    public void initialize(Subject subject,
-                           CallbackHandler callbackHandler,
-                           Map<String,?> sharedState,
-                           Map<String,?> options)
-    {
-        try
-        {
-            super.initialize(subject, callbackHandler, sharedState, options);
-            
-            //get the jdbc  username/password, jdbc url out of the options
-            dbDriver = (String)options.get("dbDriver");
-            dbUrl = (String)options.get("dbUrl");
-            dbUserName = (String)options.get("dbUserName");
-            dbPassword = (String)options.get("dbPassword");
-
-            if (dbUserName == null)
-                dbUserName = "";
-
-            if (dbPassword == null)
-                dbPassword = "";
-            
-            if (dbDriver != null)
-                Loader.loadClass(this.getClass(), dbDriver).newInstance();
-        }
-        catch (ClassNotFoundException e)
-        {
-            throw new IllegalStateException (e.toString());
-        }
-        catch (InstantiationException e)
-        {
-            throw new IllegalStateException (e.toString());
-        }
-        catch (IllegalAccessException e)
-        {
-            throw new IllegalStateException (e.toString());
-        }
-    }
-}
diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/spi/LdapLoginModule.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/spi/LdapLoginModule.java
deleted file mode 100644
index 2242554..0000000
--- a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/spi/LdapLoginModule.java
+++ /dev/null
@@ -1,689 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.plus.jaas.spi;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Hashtable;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Properties;
-
-import javax.naming.Context;
-import javax.naming.NamingEnumeration;
-import javax.naming.NamingException;
-import javax.naming.directory.Attribute;
-import javax.naming.directory.Attributes;
-import javax.naming.directory.DirContext;
-import javax.naming.directory.InitialDirContext;
-import javax.naming.directory.SearchControls;
-import javax.naming.directory.SearchResult;
-import javax.security.auth.Subject;
-import javax.security.auth.callback.Callback;
-import javax.security.auth.callback.CallbackHandler;
-import javax.security.auth.callback.NameCallback;
-import javax.security.auth.callback.UnsupportedCallbackException;
-import javax.security.auth.login.LoginException;
-
-import org.eclipse.jetty.util.security.Credential;
-import org.eclipse.jetty.plus.jaas.callback.ObjectCallback;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-
-/**
- * A LdapLoginModule for use with JAAS setups
- * <p/>
- * The jvm should be started with the following parameter:
- * <br><br>
- * <code>
- * -Djava.security.auth.login.config=etc/ldap-loginModule.conf
- * </code>
- * <br><br>
- * and an example of the ldap-loginModule.conf would be:
- * <br><br>
- * <pre>
- * ldaploginmodule {
- *    org.eclipse.jetty.server.server.plus.jaas.spi.LdapLoginModule required
- *    debug="true"
- *    useLdaps="false"
- *    contextFactory="com.sun.jndi.ldap.LdapCtxFactory"
- *    hostname="ldap.example.com"
- *    port="389"
- *    bindDn="cn=Directory Manager"
- *    bindPassword="directory"
- *    authenticationMethod="simple"
- *    forceBindingLogin="false"
- *    userBaseDn="ou=people,dc=alcatel"
- *    userRdnAttribute="uid"
- *    userIdAttribute="uid"
- *    userPasswordAttribute="userPassword"
- *    userObjectClass="inetOrgPerson"
- *    roleBaseDn="ou=groups,dc=example,dc=com"
- *    roleNameAttribute="cn"
- *    roleMemberAttribute="uniqueMember"
- *    roleObjectClass="groupOfUniqueNames";
- *    };
- *  </pre>
- *
- * 
- * 
- * 
- */
-public class LdapLoginModule extends AbstractLoginModule
-{
-    private static final Logger LOG = Log.getLogger(LdapLoginModule.class);
-
-    /**
-     * hostname of the ldap server
-     */
-    private String _hostname;
-
-    /**
-     * port of the ldap server
-     */
-    private int _port;
-
-    /**
-     * Context.SECURITY_AUTHENTICATION
-     */
-    private String _authenticationMethod;
-
-    /**
-     * Context.INITIAL_CONTEXT_FACTORY
-     */
-    private String _contextFactory;
-
-    /**
-     * root DN used to connect to
-     */
-    private String _bindDn;
-
-    /**
-     * password used to connect to the root ldap context
-     */
-    private String _bindPassword;
-
-    /**
-     * object class of a user
-     */
-    private String _userObjectClass = "inetOrgPerson";
-
-    /**
-     * attribute that the principal is located
-     */
-    private String _userRdnAttribute = "uid";
-
-    /**
-     * attribute that the principal is located
-     */
-    private String _userIdAttribute = "cn";
-
-    /**
-     * name of the attribute that a users password is stored under
-     * <p/>
-     * NOTE: not always accessible, see force binding login
-     */
-    private String _userPasswordAttribute = "userPassword";
-
-    /**
-     * base DN where users are to be searched from
-     */
-    private String _userBaseDn;
-
-    /**
-     * base DN where role membership is to be searched from
-     */
-    private String _roleBaseDn;
-
-    /**
-     * object class of roles
-     */
-    private String _roleObjectClass = "groupOfUniqueNames";
-
-    /**
-     * name of the attribute that a username would be under a role class
-     */
-    private String _roleMemberAttribute = "uniqueMember";
-
-    /**
-     * the name of the attribute that a role would be stored under
-     */
-    private String _roleNameAttribute = "roleName";
-
-    private boolean _debug;
-
-    /**
-     * if the getUserInfo can pull a password off of the user then
-     * password comparison is an option for authn, to force binding
-     * login checks, set this to true
-     */
-    private boolean _forceBindingLogin = false;
-    
-    /**
-     * When true changes the protocol to ldaps
-     */
-    private boolean _useLdaps = false;
-
-    private DirContext _rootContext;
-
-    /**
-     * get the available information about the user
-     * <p/>
-     * for this LoginModule, the credential can be null which will result in a
-     * binding ldap authentication scenario
-     * <p/>
-     * roles are also an optional concept if required
-     *
-     * @param username
-     * @return the userinfo for the username
-     * @throws Exception
-     */
-    public UserInfo getUserInfo(String username) throws Exception
-    {
-        String pwdCredential = getUserCredentials(username);
-
-        if (pwdCredential == null)
-        {
-            return null;
-        }
-
-        pwdCredential = convertCredentialLdapToJetty(pwdCredential);
-        Credential credential = Credential.getCredential(pwdCredential);
-        List<String> roles = getUserRoles(_rootContext, username);
-
-        return new UserInfo(username, credential, roles);
-    }
-
-    protected String doRFC2254Encoding(String inputString)
-    {
-        StringBuffer buf = new StringBuffer(inputString.length());
-        for (int i = 0; i < inputString.length(); i++)
-        {
-            char c = inputString.charAt(i);
-            switch (c)
-            {
-                case '\\':
-                    buf.append("\\5c");
-                    break;
-                case '*':
-                    buf.append("\\2a");
-                    break;
-                case '(':
-                    buf.append("\\28");
-                    break;
-                case ')':
-                    buf.append("\\29");
-                    break;
-                case '\0':
-                    buf.append("\\00");
-                    break;
-                default:
-                    buf.append(c);
-                    break;
-            }
-        }
-        return buf.toString();
-    }
-
-    /**
-     * attempts to get the users credentials from the users context
-     * <p/>
-     * NOTE: this is not an user authenticated operation
-     *
-     * @param username
-     * @return
-     * @throws LoginException
-     */
-    private String getUserCredentials(String username) throws LoginException
-    {
-        String ldapCredential = null;
-
-        SearchControls ctls = new SearchControls();
-        ctls.setCountLimit(1);
-        ctls.setDerefLinkFlag(true);
-        ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);
-
-        String filter = "(&(objectClass={0})({1}={2}))";
-
-        LOG.debug("Searching for users with filter: \'" + filter + "\'" + " from base dn: " + _userBaseDn);
-
-        try
-        {
-            Object[] filterArguments = {_userObjectClass, _userIdAttribute, username};
-            NamingEnumeration<SearchResult> results = _rootContext.search(_userBaseDn, filter, filterArguments, ctls);
-
-            LOG.debug("Found user?: " + results.hasMoreElements());
-
-            if (!results.hasMoreElements())
-            {
-                throw new LoginException("User not found.");
-            }
-
-            SearchResult result = findUser(username);
-
-            Attributes attributes = result.getAttributes();
-
-            Attribute attribute = attributes.get(_userPasswordAttribute);
-            if (attribute != null)
-            {
-                try
-                {
-                    byte[] value = (byte[]) attribute.get();
-
-                    ldapCredential = new String(value);
-                }
-                catch (NamingException e)
-                {
-                    LOG.debug("no password available under attribute: " + _userPasswordAttribute);
-                }
-            }
-        }
-        catch (NamingException e)
-        {
-            throw new LoginException("Root context binding failure.");
-        }
-
-        LOG.debug("user cred is: " + ldapCredential);
-
-        return ldapCredential;
-    }
-
-    /**
-     * attempts to get the users roles from the root context
-     * <p/>
-     * NOTE: this is not an user authenticated operation
-     *
-     * @param dirContext
-     * @param username
-     * @return
-     * @throws LoginException
-     */
-    private List<String> getUserRoles(DirContext dirContext, String username) throws LoginException, NamingException
-    {
-        String userDn = _userRdnAttribute + "=" + username + "," + _userBaseDn;
-
-        return getUserRolesByDn(dirContext, userDn);
-    }
-
-    private List<String> getUserRolesByDn(DirContext dirContext, String userDn) throws LoginException, NamingException
-    {
-        List<String> roleList = new ArrayList<String>();
-
-        if (dirContext == null || _roleBaseDn == null || _roleMemberAttribute == null || _roleObjectClass == null)
-        {
-            return roleList;
-        }
-
-        SearchControls ctls = new SearchControls();
-        ctls.setDerefLinkFlag(true);
-        ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);
-        ctls.setReturningAttributes(new String[]{_roleNameAttribute});
-
-        String filter = "(&(objectClass={0})({1}={2}))";
-        Object[] filterArguments = {_roleObjectClass, _roleMemberAttribute, userDn};
-        NamingEnumeration<SearchResult> results = dirContext.search(_roleBaseDn, filter, filterArguments, ctls);
-
-        LOG.debug("Found user roles?: " + results.hasMoreElements());
-
-        while (results.hasMoreElements())
-        {
-            SearchResult result = (SearchResult) results.nextElement();
-
-            Attributes attributes = result.getAttributes();
-
-            if (attributes == null)
-            {
-                continue;
-            }
-
-            Attribute roleAttribute = attributes.get(_roleNameAttribute);
-
-            if (roleAttribute == null)
-            {
-                continue;
-            }
-
-            NamingEnumeration<?> roles = roleAttribute.getAll();
-            while (roles.hasMore())
-            {
-                roleList.add(roles.next().toString());
-            }
-        }
-
-        return roleList;
-    }
-
-
-    /**
-     * since ldap uses a context bind for valid authentication checking, we override login()
-     * <p/>
-     * if credentials are not available from the users context or if we are forcing the binding check
-     * then we try a binding authentication check, otherwise if we have the users encoded password then
-     * we can try authentication via that mechanic
-     *
-     * @return true if authenticated, false otherwise
-     * @throws LoginException
-     */
-    public boolean login() throws LoginException
-    {
-        try
-        {
-            if (getCallbackHandler() == null)
-            {
-                throw new LoginException("No callback handler");
-            }
-
-            Callback[] callbacks = configureCallbacks();
-            getCallbackHandler().handle(callbacks);
-
-            String webUserName = ((NameCallback) callbacks[0]).getName();
-            Object webCredential = ((ObjectCallback) callbacks[1]).getObject();
-
-            if (webUserName == null || webCredential == null)
-            {
-                setAuthenticated(false);
-                return isAuthenticated();
-            }
-
-            if (_forceBindingLogin)
-            {
-                return bindingLogin(webUserName, webCredential);
-            }
-
-            // This sets read and the credential
-            UserInfo userInfo = getUserInfo(webUserName);
-
-            if (userInfo == null)
-            {
-                setAuthenticated(false);
-                return false;
-            }
-
-            setCurrentUser(new JAASUserInfo(userInfo));
-
-            if (webCredential instanceof String)
-            {
-                return credentialLogin(Credential.getCredential((String) webCredential));
-            }
-
-            return credentialLogin(webCredential);
-        }
-        catch (UnsupportedCallbackException e)
-        {
-            throw new LoginException("Error obtaining callback information.");
-        }
-        catch (IOException e)
-        {
-            if (_debug)
-            {
-                e.printStackTrace();
-            }
-            throw new LoginException("IO Error performing login.");
-        }
-        catch (Exception e)
-        {
-            if (_debug)
-            {
-                e.printStackTrace();
-            }
-            throw new LoginException("Error obtaining user info.");
-        }
-    }
-
-    /**
-     * password supplied authentication check
-     *
-     * @param webCredential
-     * @return true if authenticated
-     * @throws LoginException
-     */
-    protected boolean credentialLogin(Object webCredential) throws LoginException
-    {
-        setAuthenticated(getCurrentUser().checkCredential(webCredential));
-        return isAuthenticated();
-    }
-
-    /**
-     * binding authentication check
-     * This method of authentication works only if the user branch of the DIT (ldap tree)
-     * has an ACI (access control instruction) that allow the access to any user or at least
-     * for the user that logs in.
-     *
-     * @param username
-     * @param password
-     * @return true always
-     * @throws LoginException
-     */
-    public boolean bindingLogin(String username, Object password) throws LoginException, NamingException
-    {
-        SearchResult searchResult = findUser(username);
-
-        String userDn = searchResult.getNameInNamespace();
-
-        LOG.info("Attempting authentication: " + userDn);
-
-        Hashtable<Object,Object> environment = getEnvironment();
-        environment.put(Context.SECURITY_PRINCIPAL, userDn);
-        environment.put(Context.SECURITY_CREDENTIALS, password);
-
-        DirContext dirContext = new InitialDirContext(environment);
-        List<String> roles = getUserRolesByDn(dirContext, userDn);
-
-        UserInfo userInfo = new UserInfo(username, null, roles);
-        setCurrentUser(new JAASUserInfo(userInfo));
-        setAuthenticated(true);
-
-        return true;
-    }
-
-    private SearchResult findUser(String username) throws NamingException, LoginException
-    {
-        SearchControls ctls = new SearchControls();
-        ctls.setCountLimit(1);
-        ctls.setDerefLinkFlag(true);
-        ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);
-
-        String filter = "(&(objectClass={0})({1}={2}))";
-
-        LOG.info("Searching for users with filter: \'" + filter + "\'" + " from base dn: " + _userBaseDn);
-
-        Object[] filterArguments = new Object[]{
-            _userObjectClass,
-            _userIdAttribute,
-            username
-        };
-        NamingEnumeration<SearchResult> results = _rootContext.search(_userBaseDn, filter, filterArguments, ctls);
-
-        LOG.info("Found user?: " + results.hasMoreElements());
-
-        if (!results.hasMoreElements())
-        {
-            throw new LoginException("User not found.");
-        }
-
-        return (SearchResult) results.nextElement();
-    }
-
-
-    /**
-     * Init LoginModule.
-     * Called once by JAAS after new instance is created.
-     *
-     * @param subject
-     * @param callbackHandler
-     * @param sharedState
-     * @param options
-     */
-    public void initialize(Subject subject,
-                           CallbackHandler callbackHandler,
-                           Map<String,?> sharedState,
-                           Map<String,?> options)
-    {
-        super.initialize(subject, callbackHandler, sharedState, options);
-
-        _hostname = (String) options.get("hostname");
-        _port = Integer.parseInt((String) options.get("port"));
-        _contextFactory = (String) options.get("contextFactory");
-        _bindDn = (String) options.get("bindDn");
-        _bindPassword = (String) options.get("bindPassword");
-        _authenticationMethod = (String) options.get("authenticationMethod");
-
-        _userBaseDn = (String) options.get("userBaseDn");
-
-        _roleBaseDn = (String) options.get("roleBaseDn");
-
-        if (options.containsKey("forceBindingLogin"))
-        {
-            _forceBindingLogin = Boolean.parseBoolean((String) options.get("forceBindingLogin"));
-        }
-        
-        if (options.containsKey("useLdaps"))
-        {
-            _useLdaps = Boolean.parseBoolean((String) options.get("useLdaps"));
-        }     
-        
-        _userObjectClass = getOption(options, "userObjectClass", _userObjectClass);
-        _userRdnAttribute = getOption(options, "userRdnAttribute", _userRdnAttribute);
-        _userIdAttribute = getOption(options, "userIdAttribute", _userIdAttribute);
-        _userPasswordAttribute = getOption(options, "userPasswordAttribute", _userPasswordAttribute);
-        _roleObjectClass = getOption(options, "roleObjectClass", _roleObjectClass);
-        _roleMemberAttribute = getOption(options, "roleMemberAttribute", _roleMemberAttribute);
-        _roleNameAttribute = getOption(options, "roleNameAttribute", _roleNameAttribute);
-        _debug = Boolean.parseBoolean(String.valueOf(getOption(options, "debug", Boolean.toString(_debug))));
-
-        try
-        {
-            _rootContext = new InitialDirContext(getEnvironment());
-        }
-        catch (NamingException ex)
-        {
-            throw new IllegalStateException("Unable to establish root context", ex);
-        }
-    }
-
-    public boolean commit() throws LoginException 
-    {
-        try 
-        {
-            _rootContext.close();
-        } 
-        catch (NamingException e) 
-        {
-            throw new LoginException( "error closing root context: " + e.getMessage() );
-        }
-
-        return super.commit();
-    }
-
-    public boolean abort() throws LoginException 
-    {
-        try 
-        {
-            _rootContext.close();
-        } 
-        catch (NamingException e) 
-        {
-            throw new LoginException( "error closing root context: " + e.getMessage() );
-        }
-
-        return super.abort();
-    }
-
-    private String getOption(Map<String,?> options, String key, String defaultValue)
-    {
-        Object value = options.get(key);
-
-        if (value == null)
-        {
-            return defaultValue;
-        }
-
-        return (String) value;
-    }
-
-    /**
-     * get the context for connection
-     *
-     * @return the environment details for the context
-     */
-    public Hashtable<Object, Object> getEnvironment()
-    {
-        Properties env = new Properties();
-
-        env.put(Context.INITIAL_CONTEXT_FACTORY, _contextFactory);
-
-        if (_hostname != null)
-        {
-            env.put(Context.PROVIDER_URL, (_useLdaps?"ldaps://":"ldap://") + _hostname + (_port==0?"":":"+_port) +"/");
-        }
-
-        if (_authenticationMethod != null)
-        {
-            env.put(Context.SECURITY_AUTHENTICATION, _authenticationMethod);
-        }
-
-        if (_bindDn != null)
-        {
-            env.put(Context.SECURITY_PRINCIPAL, _bindDn);
-        }
-
-        if (_bindPassword != null)
-        {
-            env.put(Context.SECURITY_CREDENTIALS, _bindPassword);
-        }
-
-        return env;
-    }
-
-    public static String convertCredentialJettyToLdap(String encryptedPassword)
-    {
-        if ("MD5:".startsWith(encryptedPassword.toUpperCase(Locale.ENGLISH)))
-        {
-            return "{MD5}" + encryptedPassword.substring("MD5:".length(), encryptedPassword.length());
-        }
-
-        if ("CRYPT:".startsWith(encryptedPassword.toUpperCase(Locale.ENGLISH)))
-        {
-            return "{CRYPT}" + encryptedPassword.substring("CRYPT:".length(), encryptedPassword.length());
-        }
-
-        return encryptedPassword;
-    }
-
-    public static String convertCredentialLdapToJetty(String encryptedPassword)
-    {
-        if (encryptedPassword == null)
-        {
-            return encryptedPassword;
-        }
-
-        if ("{MD5}".startsWith(encryptedPassword.toUpperCase(Locale.ENGLISH)))
-        {
-            return "MD5:" + encryptedPassword.substring("{MD5}".length(), encryptedPassword.length());
-        }
-
-        if ("{CRYPT}".startsWith(encryptedPassword.toUpperCase(Locale.ENGLISH)))
-        {
-            return "CRYPT:" + encryptedPassword.substring("{CRYPT}".length(), encryptedPassword.length());
-        }
-
-        return encryptedPassword;
-    }
-}
diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/spi/PropertyFileLoginModule.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/spi/PropertyFileLoginModule.java
deleted file mode 100644
index 126f541..0000000
--- a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/spi/PropertyFileLoginModule.java
+++ /dev/null
@@ -1,135 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.plus.jaas.spi;
-
-import java.security.Principal;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-
-import javax.security.auth.Subject;
-import javax.security.auth.callback.CallbackHandler;
-
-import org.eclipse.jetty.util.security.Credential;
-import org.eclipse.jetty.security.PropertyUserStore;
-import org.eclipse.jetty.server.UserIdentity;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-
-/**
- * PropertyFileLoginModule
- * 
- * 
- */
-public class PropertyFileLoginModule extends AbstractLoginModule
-{
-    public static final String DEFAULT_FILENAME = "realm.properties";
-
-    private static final Logger LOG = Log.getLogger(PropertyFileLoginModule.class);
-
-    private static ConcurrentHashMap<String, PropertyUserStore> _propertyUserStores = new ConcurrentHashMap<String, PropertyUserStore>();
-
-    private int _refreshInterval = 0;
-    private String _filename = DEFAULT_FILENAME;
-
-    /**
-     * Read contents of the configured property file.
-     * 
-     * @see javax.security.auth.spi.LoginModule#initialize(javax.security.auth.Subject, javax.security.auth.callback.CallbackHandler, java.util.Map,
-     *      java.util.Map)
-     * @param subject
-     * @param callbackHandler
-     * @param sharedState
-     * @param options
-     */
-    public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState, Map<String, ?> options)
-    {
-        super.initialize(subject,callbackHandler,sharedState,options);
-        setupPropertyUserStore(options);
-    }
-
-    private void setupPropertyUserStore(Map<String, ?> options)
-    {
-        parseConfig(options);
-
-        if (_propertyUserStores.get(_filename) == null)
-        {
-            PropertyUserStore propertyUserStore = new PropertyUserStore();
-            propertyUserStore.setConfig(_filename);
-            propertyUserStore.setRefreshInterval(_refreshInterval);
-
-            PropertyUserStore prev = _propertyUserStores.putIfAbsent(_filename, propertyUserStore);
-            if (prev == null)
-            {
-                LOG.debug("setupPropertyUserStore: Starting new PropertyUserStore. PropertiesFile: " + _filename + " refreshInterval: " + _refreshInterval);
-
-                try
-                {
-                    propertyUserStore.start();
-                }
-                catch (Exception e)
-                {
-                    LOG.warn("Exception while starting propertyUserStore: ",e);
-                }
-            }
-        }
-    }
-
-    private void parseConfig(Map<String, ?> options)
-    {
-        _filename = (String)options.get("file");
-        _filename = (_filename == null? DEFAULT_FILENAME : _filename);
-        String refreshIntervalString = (String)options.get("refreshInterval");
-        _refreshInterval = refreshIntervalString == null?_refreshInterval:Integer.parseInt(refreshIntervalString);
-    }
-
-    /**
-     * Don't implement this as we want to pre-fetch all of the users.
-     * 
-     * @param userName
-     * @throws Exception
-     */
-    public UserInfo getUserInfo(String userName) throws Exception
-    {
-        PropertyUserStore propertyUserStore = _propertyUserStores.get(_filename);
-        if (propertyUserStore == null)
-            throw new IllegalStateException("PropertyUserStore should never be null here!");
-        
-        LOG.debug("Checking PropertyUserStore "+_filename+" for "+userName);
-        UserIdentity userIdentity = propertyUserStore.getUserIdentity(userName);
-        if (userIdentity==null)
-            return null;
-        
-        Set<Principal> principals = userIdentity.getSubject().getPrincipals();
-        
-        List<String> roles = new ArrayList<String>();
-        
-        for ( Principal principal : principals )
-        {
-            roles.add( principal.getName() );
-        }
-        
-        Credential credential = (Credential)userIdentity.getSubject().getPrivateCredentials().iterator().next();
-        LOG.debug("Found: " + userName + " in PropertyUserStore "+_filename);
-        return new UserInfo(userName, credential, roles);
-    }
-
-}
diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/spi/UserInfo.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/spi/UserInfo.java
deleted file mode 100644
index 15dd83a..0000000
--- a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/spi/UserInfo.java
+++ /dev/null
@@ -1,73 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.plus.jaas.spi;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import org.eclipse.jetty.util.security.Credential;
-
-/**
- * UserInfo
- *
- * This is the information read from the external source
- * about a user.
- * 
- * Can be cached by a UserInfoCache implementation
- */
-public class UserInfo
-{
-    
-    private String _userName;
-    private Credential _credential;
-    private List<String> _roleNames;
-    
-    
-    public UserInfo (String userName, Credential credential, List<String> roleNames)
-    {
-        _userName = userName;
-        _credential = credential;
-        _roleNames = new ArrayList<String>();
-        if (roleNames != null)
-        {
-            _roleNames.addAll(roleNames);
-        }
-    }
-    
-    public String getUserName()
-    {
-        return this._userName;
-    }
-    
-    public List<String> getRoleNames ()
-    {
-        return new ArrayList<String>(_roleNames);
-    }
-    
-    public boolean checkCredential (Object suppliedCredential)
-    {
-        return _credential.check(suppliedCredential);
-    }
-    
-    protected Credential getCredential ()
-    {
-        return _credential;
-    }
-    
-}
diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jndi/NamingEntryUtil.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jndi/NamingEntryUtil.java
index 9b72be7..e3a3ae4 100644
--- a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jndi/NamingEntryUtil.java
+++ b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jndi/NamingEntryUtil.java
@@ -38,11 +38,11 @@ import org.eclipse.jetty.util.log.Logger;
 public class NamingEntryUtil
 {
     private static Logger __log = NamingUtil.__log;
-    
+
     /**
      * Link a name in a webapp's java:/comp/evn namespace to a pre-existing
      * resource. The pre-existing resource can be either in the webapp's
-     * naming environment, or in the container's naming environment. Webapp's 
+     * naming environment, or in the container's naming environment. Webapp's
      * environment takes precedence over the server's namespace.
      *
      * @param scope the scope of the lookup
@@ -52,28 +52,28 @@ public class NamingEntryUtil
      */
     public static boolean bindToENC (Object scope, String asName, String mappedName)
     throws NamingException
-    {  
+    {
         if (asName==null||asName.trim().equals(""))
             throw new NamingException ("No name for NamingEntry");
 
         if (mappedName==null || "".equals(mappedName))
             mappedName=asName;
-        
+
         NamingEntry entry = lookupNamingEntry (scope, mappedName);
         if (entry == null)
             return false;
-        
+
         entry.bindToENC(asName);
         return true;
      }
 
-    
-    
- 
+
+
+
 
     /**
      * Find a NamingEntry in the given scope.
-     * 
+     *
      * @param scope
      * @param jndiName
      * @return the naming entry for the given scope
@@ -84,12 +84,12 @@ public class NamingEntryUtil
     {
         NamingEntry entry = null;
         try
-        {         
+        {
             Name scopeName = getNameForScope(scope);
-            InitialContext ic = new InitialContext();   
+            InitialContext ic = new InitialContext();
             NameParser parser = ic.getNameParser("");
-            Name namingEntryName = makeNamingEntryName(parser, jndiName);  
-            scopeName.addAll(namingEntryName);           
+            Name namingEntryName = makeNamingEntryName(parser, jndiName);
+            scopeName.addAll(namingEntryName);
             entry =  (NamingEntry)ic.lookup(scopeName);
         }
         catch (NameNotFoundException ee)
@@ -98,7 +98,7 @@ public class NamingEntryUtil
 
         return entry;
     }
-    
+
     public static Object lookup(Object scope, String jndiName) throws NamingException
     {
         Name scopeName = getNameForScope(scope);
@@ -108,18 +108,18 @@ public class NamingEntryUtil
         return ic.lookup(scopeName);
     }
 
-    /** 
+    /**
      * Get all NameEntries of a certain type in the given naming
      * environment scope (server-wide names or context-specific names)
-     * 
-     * @param scope 
+     *
+     * @param scope
      * @param clazz the type of the entry
      * @return all NameEntries of a certain type in the given naming environment scope (server-wide names or context-specific names)
      * @throws NamingException
      */
     public static List<Object> lookupNamingEntries (Object scope, Class<?> clazz)
     throws NamingException
-    { 
+    {
         try
         {
             Context scopeContext = getContextForScope(scope);
@@ -130,35 +130,35 @@ public class NamingEntryUtil
         }
         catch (NameNotFoundException e)
         {
-            return Collections.emptyList(); 
+            return Collections.emptyList();
         }
     }
-    
-    
+
+
     public static Name makeNamingEntryName (NameParser parser, NamingEntry namingEntry)
     throws NamingException
     {
         return makeNamingEntryName(parser, (namingEntry==null?null:namingEntry.getJndiName()));
     }
-    
+
     public static Name makeNamingEntryName (NameParser parser, String jndiName)
     throws NamingException
     {
         if (jndiName==null)
             return null;
-        
+
         if (parser==null)
         {
             InitialContext ic = new InitialContext();
             parser = ic.getNameParser("");
         }
-        
+
         Name name = parser.parse("");
         name.add(NamingEntry.__contextName);
         name.addAll(parser.parse(jndiName));
         return name;
     }
-    
+
 
     public static Name getNameForScope (Object scope)
     {
@@ -170,7 +170,7 @@ public class NamingEntryUtil
             if (scope != null)
             {
                 name.add(canonicalizeScope(scope));
-            }  
+            }
             return name;
         }
         catch (NamingException e)
@@ -189,10 +189,10 @@ public class NamingEntryUtil
         if (scope != null)
         {
             name.add(canonicalizeScope(scope));
-        }  
+        }
         return (Context)ic.lookup(name);
     }
-    
+
     public static Context getContextForNamingEntries (Object scope)
     throws NamingException
     {
@@ -202,7 +202,7 @@ public class NamingEntryUtil
 
     /**
      * Build up a list of NamingEntry objects that are of a specific type.
-     * 
+     *
      * @param list
      * @param context
      * @param clazz
diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jndi/package-info.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jndi/package-info.java
new file mode 100644
index 0000000..e8fafd1
--- /dev/null
+++ b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jndi/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Plus : Limited JEE Jndi Support
+ */
+package org.eclipse.jetty.plus.jndi;
+
diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/security/DataSourceLoginService.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/security/DataSourceLoginService.java
index 8ed9731..5073b52 100644
--- a/jetty-plus/src/main/java/org/eclipse/jetty/plus/security/DataSourceLoginService.java
+++ b/jetty-plus/src/main/java/org/eclipse/jetty/plus/security/DataSourceLoginService.java
@@ -24,6 +24,7 @@ import java.sql.DatabaseMetaData;
 import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.SQLException;
+import java.sql.Statement;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Locale;
@@ -40,12 +41,11 @@ import org.eclipse.jetty.server.Server;
 import org.eclipse.jetty.server.UserIdentity;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.util.security.Password;
+import org.eclipse.jetty.util.security.Credential;
 
 
 /**
  *
- * //TODO JASPI cf JDBCLoginService
  * DataSourceUserRealm
  *
  * Obtain user/password/role information from a database
@@ -69,6 +69,7 @@ public class DataSourceLoginService extends MappedLoginService
     private String _userRoleTableUserKey = "user_id";
     private String _userRoleTableRoleKey = "role_id";
     private int _cacheMs = 30000;
+    private long _lastPurge = 0;
     private String _userSql;
     private String _roleSql;
     private boolean _createTables = false;
@@ -77,13 +78,13 @@ public class DataSourceLoginService extends MappedLoginService
     public DataSourceLoginService()
     {
     }
-    
+
     /* ------------------------------------------------------------ */
     public DataSourceLoginService(String name)
     {
         setName(name);
     }
-    
+
     /* ------------------------------------------------------------ */
     public DataSourceLoginService(String name, IdentityService identityService)
     {
@@ -281,39 +282,44 @@ public class DataSourceLoginService extends MappedLoginService
     protected void loadUsers()
     {
     }
-
+    
+ 
+    
     /* ------------------------------------------------------------ */
     /** Load user's info from database.
-     * 
+     *
      * @param userName
      */
     @Override
     protected UserIdentity loadUser (String userName)
     {
-        Connection connection = null;
         try
-        {        
-            initDb();
-            connection = getConnection();
-            
-            PreparedStatement statement = connection.prepareStatement(_userSql);
-            statement.setObject(1, userName);
-            ResultSet rs = statement.executeQuery();
-    
-            if (rs.next())
+        {
+            try (Connection connection = getConnection();
+                 PreparedStatement statement1 = connection.prepareStatement(_userSql))
             {
-                int key = rs.getInt(_userTableKey);
-                String credentials = rs.getString(_userTablePasswordField); 
-                statement.close();
-                
-                statement = connection.prepareStatement(_roleSql);
-                statement.setInt(1, key);
-                rs = statement.executeQuery();
-                List<String> roles = new ArrayList<String>();
-                while (rs.next())
-                    roles.add(rs.getString(_roleTableRoleField));    
-                statement.close(); 
-                return putUser(userName,new Password(credentials), roles.toArray(new String[roles.size()]));
+                statement1.setObject(1, userName);
+                try (ResultSet rs1 = statement1.executeQuery())
+                {
+                    if (rs1.next())
+                    {
+                        int key = rs1.getInt(_userTableKey);
+                        String credentials = rs1.getString(_userTablePasswordField);
+                        List<String> roles = new ArrayList<String>();
+                        try (PreparedStatement statement2 = connection.prepareStatement(_roleSql))
+                        {
+                            statement2.setInt(1, key);
+                            try (ResultSet rs2 = statement2.executeQuery())
+                            {
+                                while (rs2.next())
+                                {
+                                    roles.add(rs2.getString(_roleTableRoleField));
+                                }
+                            }
+                        }
+                        return putUser(userName, Credential.getCredential(credentials), roles.toArray(new String[roles.size()]));
+                    }
+                }
             }
         }
         catch (NamingException e)
@@ -324,46 +330,44 @@ public class DataSourceLoginService extends MappedLoginService
         {
             LOG.warn("Problem loading user info for "+userName, e);
         }
-        finally
+        return null;
+    }
+    
+    
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public UserIdentity login(String username, Object credentials)
+    {
+        long now = System.currentTimeMillis();
+        if (now - _lastPurge > _cacheMs || _cacheMs == 0)
         {
-            if (connection != null)
-            {
-                try
-                {
-                    connection.close();
-                }
-                catch (SQLException x)
-                {
-                    LOG.warn("Problem closing connection", x);
-                }
-                finally
-                {
-                    connection = null;
-                }
-            }
+            _users.clear();
+            _lastPurge = now;
         }
-        return null;
+ 
+        return super.login(username,credentials);
     }
-   
+
     /* ------------------------------------------------------------ */
     /**
      * Lookup the datasource for the jndiName and formulate the
      * necessary sql query strings based on the configured table
      * and column names.
-     * 
+     *
      * @throws NamingException
      */
     public void initDb() throws NamingException, SQLException
     {
         if (_datasource != null)
             return;
-        
+
         @SuppressWarnings("unused")
         InitialContext ic = new InitialContext();
         assert ic!=null;
-        
-        //TODO webapp scope?
-        
+
+        //TODO Should we try webapp scope too?
+
         //try finding the datasource in the Server scope
         if (_server != null)
         {
@@ -376,7 +380,7 @@ public class DataSourceLoginService extends MappedLoginService
                 //next try the jvm scope
             }
         }
-        
+
 
         //try finding the datasource in the jvm scope
         if (_datasource==null)
@@ -385,110 +389,111 @@ public class DataSourceLoginService extends MappedLoginService
         }
 
         // set up the select statements based on the table and column names configured
-        _userSql = "select " + _userTableKey + "," + _userTablePasswordField 
-                  + " from " + _userTableName 
+        _userSql = "select " + _userTableKey + "," + _userTablePasswordField
+                  + " from " + _userTableName
                   + " where "+ _userTableUserField + " = ?";
-        
+
         _roleSql = "select r." + _roleTableRoleField
-                  + " from " + _roleTableName + " r, " + _userRoleTableName 
+                  + " from " + _roleTableName + " r, " + _userRoleTableName
                   + " u where u."+ _userRoleTableUserKey + " = ?"
                   + " and r." + _roleTableKey + " = u." + _userRoleTableRoleKey;
-        
+
         prepareTables();
     }
-    
-    
-    
+
+
+
     private void prepareTables()
     throws NamingException, SQLException
     {
-        Connection connection = null;
-        boolean autocommit = true; 
-        
         if (_createTables)
         {
-            try
+            boolean autocommit = true;
+            Connection connection = getConnection();
+            try (Statement stmt = connection.createStatement())
             {
-                connection = getConnection();
                 autocommit = connection.getAutoCommit();
                 connection.setAutoCommit(false);
                 DatabaseMetaData metaData = connection.getMetaData();
-                
+
                 //check if tables exist
                 String tableName = (metaData.storesLowerCaseIdentifiers()? _userTableName.toLowerCase(Locale.ENGLISH): (metaData.storesUpperCaseIdentifiers()?_userTableName.toUpperCase(Locale.ENGLISH): _userTableName));
-                ResultSet result = metaData.getTables(null, null, tableName, null);
-                if (!result.next())
-                {                
-                    //user table default
-                    /*
-                     * create table _userTableName (_userTableKey integer,
-                     * _userTableUserField varchar(100) not null unique,
-                     * _userTablePasswordField varchar(20) not null, primary key(_userTableKey));
-                     */
-                    connection.createStatement().executeUpdate("create table "+_userTableName+ "("+_userTableKey+" integer,"+
-                            _userTableUserField+" varchar(100) not null unique,"+
-                            _userTablePasswordField+" varchar(20) not null, primary key("+_userTableKey+"))");
-                    if (LOG.isDebugEnabled()) LOG.debug("Created table "+_userTableName);
+                try (ResultSet result = metaData.getTables(null, null, tableName, null))
+                {
+                    if (!result.next())
+                    {
+                        //user table default
+                        /*
+                         * create table _userTableName (_userTableKey integer,
+                         * _userTableUserField varchar(100) not null unique,
+                         * _userTablePasswordField varchar(20) not null, primary key(_userTableKey));
+                         */
+                        stmt.executeUpdate("create table "+_userTableName+ "("+_userTableKey+" integer,"+
+                                _userTableUserField+" varchar(100) not null unique,"+
+                                _userTablePasswordField+" varchar(20) not null, primary key("+_userTableKey+"))");
+                        if (LOG.isDebugEnabled()) LOG.debug("Created table "+_userTableName);
+                    }
                 }
-                
-                result.close();
 
                 tableName = (metaData.storesLowerCaseIdentifiers()? _roleTableName.toLowerCase(Locale.ENGLISH): (metaData.storesUpperCaseIdentifiers()?_roleTableName.toUpperCase(Locale.ENGLISH): _roleTableName));
-                result = metaData.getTables(null, null, tableName, null);
-                if (!result.next())
+                try (ResultSet result = metaData.getTables(null, null, tableName, null))
                 {
-                    //role table default
-                    /*
-                     * create table _roleTableName (_roleTableKey integer,
-                     * _roleTableRoleField varchar(100) not null unique, primary key(_roleTableKey));
-                     */
-                    String str = "create table "+_roleTableName+" ("+_roleTableKey+" integer, "+
-                    _roleTableRoleField+" varchar(100) not null unique, primary key("+_roleTableKey+"))";
-                    connection.createStatement().executeUpdate(str);
-                    if (LOG.isDebugEnabled()) LOG.debug("Created table "+_roleTableName);
+                    if (!result.next())
+                    {
+                        //role table default
+                        /*
+                         * create table _roleTableName (_roleTableKey integer,
+                         * _roleTableRoleField varchar(100) not null unique, primary key(_roleTableKey));
+                         */
+                        String str = "create table "+_roleTableName+" ("+_roleTableKey+" integer, "+
+                        _roleTableRoleField+" varchar(100) not null unique, primary key("+_roleTableKey+"))";
+                        stmt.executeUpdate(str);
+                        if (LOG.isDebugEnabled()) LOG.debug("Created table "+_roleTableName);
+                    }
                 }
-                
-                result.close();
 
                 tableName = (metaData.storesLowerCaseIdentifiers()? _userRoleTableName.toLowerCase(Locale.ENGLISH): (metaData.storesUpperCaseIdentifiers()?_userRoleTableName.toUpperCase(Locale.ENGLISH): _userRoleTableName));
-                result = metaData.getTables(null, null, tableName, null);
-                if (!result.next())
+                try (ResultSet result = metaData.getTables(null, null, tableName, null))
                 {
-                    //user-role table
-                    /*
-                     * create table _userRoleTableName (_userRoleTableUserKey integer,
-                     * _userRoleTableRoleKey integer,
-                     * primary key (_userRoleTableUserKey, _userRoleTableRoleKey));
-                     * 
-                     * create index idx_user_role on _userRoleTableName (_userRoleTableUserKey);
-                     */
-                    connection.createStatement().executeUpdate("create table "+_userRoleTableName+" ("+_userRoleTableUserKey+" integer, "+
-                            _userRoleTableRoleKey+" integer, "+
-                            "primary key ("+_userRoleTableUserKey+", "+_userRoleTableRoleKey+"))");                   
-                    connection.createStatement().executeUpdate("create index indx_user_role on "+_userRoleTableName+"("+_userRoleTableUserKey+")");
-                    if (LOG.isDebugEnabled()) LOG.debug("Created table "+_userRoleTableName +" and index");
+                    if (!result.next())
+                    {
+                        //user-role table
+                        /*
+                         * create table _userRoleTableName (_userRoleTableUserKey integer,
+                         * _userRoleTableRoleKey integer,
+                         * primary key (_userRoleTableUserKey, _userRoleTableRoleKey));
+                         *
+                         * create index idx_user_role on _userRoleTableName (_userRoleTableUserKey);
+                         */
+                        stmt.executeUpdate("create table "+_userRoleTableName+" ("+_userRoleTableUserKey+" integer, "+
+                                _userRoleTableRoleKey+" integer, "+
+                                "primary key ("+_userRoleTableUserKey+", "+_userRoleTableRoleKey+"))");
+                        stmt.executeUpdate("create index indx_user_role on "+_userRoleTableName+"("+_userRoleTableUserKey+")");
+                        if (LOG.isDebugEnabled()) LOG.debug("Created table "+_userRoleTableName +" and index");
+                    }
                 }
-                
-                result.close();   
                 connection.commit();
             }
             finally
             {
-                if (connection != null)
+                try
+                {
+                    connection.setAutoCommit(autocommit);
+                }
+                catch (SQLException e)
+                {
+                    if (LOG.isDebugEnabled()) LOG.debug("Prepare tables", e);
+                }
+                finally
                 {
                     try
                     {
-                        connection.setAutoCommit(autocommit);
                         connection.close();
                     }
                     catch (SQLException e)
                     {
                         if (LOG.isDebugEnabled()) LOG.debug("Prepare tables", e);
                     }
-                    finally
-                    {
-                        connection = null;
-                    }
                 }
             }
         }
@@ -497,9 +502,9 @@ public class DataSourceLoginService extends MappedLoginService
             LOG.debug("createTables false");
         }
     }
-    
-    
-    private Connection getConnection () 
+
+
+    private Connection getConnection ()
     throws NamingException, SQLException
     {
         initDb();
diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/security/package-info.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/security/package-info.java
new file mode 100644
index 0000000..5f5d529
--- /dev/null
+++ b/jetty-plus/src/main/java/org/eclipse/jetty/plus/security/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Plus : Limited JEE Security Support
+ */
+package org.eclipse.jetty.plus.security;
+
diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/servlet/ServletHandler.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/servlet/ServletHandler.java
deleted file mode 100644
index 35066cf..0000000
--- a/jetty-plus/src/main/java/org/eclipse/jetty/plus/servlet/ServletHandler.java
+++ /dev/null
@@ -1,76 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.plus.servlet;
-
-import org.eclipse.jetty.plus.annotation.InjectionCollection;
-import org.eclipse.jetty.plus.annotation.LifeCycleCallbackCollection;
-
-/**
- * ServletHandler
- *
- *
- */
-public class ServletHandler extends org.eclipse.jetty.servlet.ServletHandler
-{
-
-    private InjectionCollection _injections = null;
-    private LifeCycleCallbackCollection _callbacks = null;
-    
-
-
-    /**
-     * @return the callbacks
-     */
-    public LifeCycleCallbackCollection getCallbacks()
-    {
-        return _callbacks;
-    }
-
-
-
-    /**
-     * @param callbacks the callbacks to set
-     */
-    public void setCallbacks(LifeCycleCallbackCollection callbacks)
-    {
-        this._callbacks = callbacks;
-    }
-
-
-
-    /**
-     * @return the injections
-     */
-    public InjectionCollection getInjections()
-    {
-        return _injections;
-    }
-
-
-
-    /**
-     * @param injections the injections to set
-     */
-    public void setInjections(InjectionCollection injections)
-    {
-        this._injections = injections;
-    }
-    
-
-}
diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/webapp/EnvConfiguration.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/webapp/EnvConfiguration.java
index 797bcf7..8983111 100644
--- a/jetty-plus/src/main/java/org/eclipse/jetty/plus/webapp/EnvConfiguration.java
+++ b/jetty-plus/src/main/java/org/eclipse/jetty/plus/webapp/EnvConfiguration.java
@@ -31,6 +31,7 @@ import javax.naming.Name;
 import javax.naming.NameNotFoundException;
 import javax.naming.NamingException;
 
+import org.eclipse.jetty.jndi.ContextFactory;
 import org.eclipse.jetty.jndi.NamingContext;
 import org.eclipse.jetty.jndi.NamingUtil;
 import org.eclipse.jetty.jndi.local.localContextRoot;
@@ -39,7 +40,6 @@ import org.eclipse.jetty.plus.jndi.NamingEntryUtil;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
 import org.eclipse.jetty.webapp.AbstractConfiguration;
-import org.eclipse.jetty.webapp.Configuration;
 import org.eclipse.jetty.webapp.WebAppContext;
 import org.eclipse.jetty.xml.XmlConfiguration;
 
@@ -61,26 +61,26 @@ public class EnvConfiguration extends AbstractConfiguration
         this.jettyEnvXmlUrl = url;
     }
 
-    /** 
+    /**
      * @see Configuration#configure(WebAppContext)
      * @throws Exception
      */
     @Override
     public void preConfigure (WebAppContext context) throws Exception
-    {        
+    {
         //create a java:comp/env
         createEnvContext(context);
     }
 
-    /** 
+    /**
      * @throws Exception
      */
     @Override
     public void configure (WebAppContext context) throws Exception
-    {  
+    {
         if (LOG.isDebugEnabled())
             LOG.debug("Created java:comp/env for webapp "+context.getContextPath());
-        
+
         //check to see if an explicit file has been set, if not,
         //look in WEB-INF/jetty-env.xml
         if (jettyEnvXmlUrl == null)
@@ -97,7 +97,7 @@ public class EnvConfiguration extends AbstractConfiguration
                 }
             }
         }
-        
+
         if (jettyEnvXmlUrl != null)
         {
             synchronized (localContextRoot.getRoot())
@@ -135,8 +135,8 @@ public class EnvConfiguration extends AbstractConfiguration
         bindEnvEntries(context);
     }
 
-    
-    /** 
+
+    /**
      * Remove jndi setup from start
      * @see Configuration#deconfigure(WebAppContext)
      * @throws Exception
@@ -147,6 +147,7 @@ public class EnvConfiguration extends AbstractConfiguration
         //get rid of any bindings for comp/env for webapp
         ClassLoader oldLoader = Thread.currentThread().getContextClassLoader();
         Thread.currentThread().setContextClassLoader(context.getClassLoader());
+        ContextFactory.associateClassLoader(context.getClassLoader());
         try
         {
             Context ic = new InitialContext();
@@ -170,12 +171,13 @@ public class EnvConfiguration extends AbstractConfiguration
         }
         finally
         {
+            ContextFactory.disassociateClassLoader();
             Thread.currentThread().setContextClassLoader(oldLoader);
         }
     }
 
-    
-    /** 
+
+    /**
      * Remove all jndi setup
      * @see Configuration#deconfigure(WebAppContext)
      * @throws Exception
@@ -184,22 +186,26 @@ public class EnvConfiguration extends AbstractConfiguration
     public void destroy (WebAppContext context) throws Exception
     {
         try
-        {            
-            //unbind any NamingEntries that were configured in this webapp's name space
+        {
+            //unbind any NamingEntries that were configured in this webapp's name space           
             NamingContext scopeContext = (NamingContext)NamingEntryUtil.getContextForScope(context);
             scopeContext.getParent().destroySubcontext(scopeContext.getName());
         }
         catch (NameNotFoundException e)
         {
             LOG.ignore(e);
-            LOG.debug("No naming entries configured in environment for webapp "+context);
+            LOG.debug("No jndi entries scoped to webapp {}", context);
+        }
+        catch (NamingException e)
+        {
+            LOG.debug("Error unbinding jndi entries scoped to webapp "+context, e);
         }
     }
-    
+
     /**
      * Bind all EnvEntries that have been declared, so that the processing of the
      * web.xml file can potentially override them.
-     * 
+     *
      * We first bind EnvEntries declared in Server scope, then WebAppContext scope.
      * @throws NamingException
      */
@@ -217,11 +223,11 @@ public class EnvConfiguration extends AbstractConfiguration
             EnvEntry ee = (EnvEntry)itor.next();
             ee.bindToENC(ee.getJndiName());
             Name namingEntryName = NamingEntryUtil.makeNamingEntryName(null, ee);
-            NamingUtil.bind(envCtx, namingEntryName.toString(), ee);//also save the EnvEntry in the context so we can check it later          
+            NamingUtil.bind(envCtx, namingEntryName.toString(), ee);//also save the EnvEntry in the context so we can check it later
         }
-        
+
         LOG.debug("Binding env entries from the server scope");
-        
+
         scope = context.getServer();
         list = NamingEntryUtil.lookupNamingEntries(scope, EnvEntry.class);
         itor = list.iterator();
@@ -230,9 +236,9 @@ public class EnvConfiguration extends AbstractConfiguration
             EnvEntry ee = (EnvEntry)itor.next();
             ee.bindToENC(ee.getJndiName());
             Name namingEntryName = NamingEntryUtil.makeNamingEntryName(null, ee);
-            NamingUtil.bind(envCtx, namingEntryName.toString(), ee);//also save the EnvEntry in the context so we can check it later          
+            NamingUtil.bind(envCtx, namingEntryName.toString(), ee);//also save the EnvEntry in the context so we can check it later
         }
-        
+
         LOG.debug("Binding env entries from the context scope");
         scope = context;
         list = NamingEntryUtil.lookupNamingEntries(scope, EnvEntry.class);
@@ -244,25 +250,27 @@ public class EnvConfiguration extends AbstractConfiguration
             Name namingEntryName = NamingEntryUtil.makeNamingEntryName(null, ee);
             NamingUtil.bind(envCtx, namingEntryName.toString(), ee);//also save the EnvEntry in the context so we can check it later
         }
-    }  
-    
+    }
+
     protected void createEnvContext (WebAppContext wac)
     throws NamingException
     {
         ClassLoader old_loader = Thread.currentThread().getContextClassLoader();
         Thread.currentThread().setContextClassLoader(wac.getClassLoader());
+        ContextFactory.associateClassLoader(wac.getClassLoader());
         try
         {
             Context context = new InitialContext();
             Context compCtx =  (Context)context.lookup ("java:comp");
             compCtx.createSubcontext("env");
         }
-        finally 
+        finally
         {
-           Thread.currentThread().setContextClassLoader(old_loader);
-       }
+            ContextFactory.disassociateClassLoader();
+            Thread.currentThread().setContextClassLoader(old_loader);
+        }
     }
-    
+
     private static class Bound
     {
         final NamingContext _context;
diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/webapp/PlusConfiguration.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/webapp/PlusConfiguration.java
index 8c351fd..bac0d91 100644
--- a/jetty-plus/src/main/java/org/eclipse/jetty/plus/webapp/PlusConfiguration.java
+++ b/jetty-plus/src/main/java/org/eclipse/jetty/plus/webapp/PlusConfiguration.java
@@ -43,18 +43,18 @@ public class PlusConfiguration extends AbstractConfiguration
     private static final Logger LOG = Log.getLogger(PlusConfiguration.class);
 
     private Integer _key;
-    
+
     @Override
     public void preConfigure (WebAppContext context)
     throws Exception
-    {      
-        context.addDecorator(new PlusDecorator(context));  
+    {
+        context.addDecorator(new PlusDecorator(context));
     }
- 
+
     @Override
     public void cloneConfigure(WebAppContext template, WebAppContext context) throws Exception
     {
-        context.addDecorator(new PlusDecorator(context));  
+        context.addDecorator(new PlusDecorator(context));
     }
 
     @Override
@@ -62,7 +62,7 @@ public class PlusConfiguration extends AbstractConfiguration
     throws Exception
     {
         bindUserTransaction(context);
-        
+
         context.getMetaData().addDescriptorProcessor(new PlusDescriptorProcessor());
     }
 
@@ -82,7 +82,7 @@ public class PlusConfiguration extends AbstractConfiguration
         context.setAttribute(InjectionCollection.INJECTION_COLLECTION,null);
         context.setAttribute(LifeCycleCallbackCollection.LIFECYCLE_CALLBACK_COLLECTION,null);
     }
-    
+
     public void bindUserTransaction (WebAppContext context)
     throws Exception
     {
@@ -92,12 +92,12 @@ public class PlusConfiguration extends AbstractConfiguration
         }
         catch (NameNotFoundException e)
         {
-            LOG.info("No Transaction manager found - if your webapp requires one, please configure one.");
+            LOG.debug("No Transaction manager found - if your webapp requires one, please configure one.");
         }
     }
-    
- 
-  
+
+
+
     protected void lockCompEnv (WebAppContext wac)
     throws Exception
     {
@@ -109,14 +109,14 @@ public class PlusConfiguration extends AbstractConfiguration
             _key = new Integer(random.nextInt());
             Context context = new InitialContext();
             Context compCtx = (Context)context.lookup("java:comp");
-            compCtx.addToEnvironment("org.eclipse.jndi.lock", _key);
+            compCtx.addToEnvironment("org.eclipse.jetty.jndi.lock", _key);
         }
         finally
         {
             Thread.currentThread().setContextClassLoader(old_loader);
         }
     }
-    
+
     protected void unlockCompEnv (WebAppContext wac)
     throws Exception
     {
@@ -129,7 +129,7 @@ public class PlusConfiguration extends AbstractConfiguration
             {
                 Context context = new InitialContext();
                 Context compCtx = (Context)context.lookup("java:comp");
-                compCtx.addToEnvironment("org.eclipse.jndi.unlock", _key); 
+                compCtx.addToEnvironment("org.eclipse.jetty.jndi.unlock", _key);
             }
             finally
             {
diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/webapp/PlusDecorator.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/webapp/PlusDecorator.java
index 7d9826b..a29bea4 100644
--- a/jetty-plus/src/main/java/org/eclipse/jetty/plus/webapp/PlusDecorator.java
+++ b/jetty-plus/src/main/java/org/eclipse/jetty/plus/webapp/PlusDecorator.java
@@ -18,24 +18,16 @@
 
 package org.eclipse.jetty.plus.webapp;
 
-import java.util.EventListener;
-
-import javax.servlet.Filter;
-import javax.servlet.Servlet;
-import javax.servlet.ServletException;
-
 import org.eclipse.jetty.plus.annotation.InjectionCollection;
 import org.eclipse.jetty.plus.annotation.LifeCycleCallbackCollection;
 import org.eclipse.jetty.plus.annotation.RunAsCollection;
-import org.eclipse.jetty.servlet.FilterHolder;
 import org.eclipse.jetty.servlet.ServletContextHandler.Decorator;
-import org.eclipse.jetty.servlet.ServletHolder;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
 import org.eclipse.jetty.webapp.WebAppContext;
 
 /**
- * WebAppDecorator
+ * PlusDecorator
  *
  *
  */
@@ -50,89 +42,13 @@ public class PlusDecorator implements Decorator
         _context = context;
     }
 
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.servlet.ServletContextHandler.Decorator#decorateFilterHolder(org.eclipse.jetty.servlet.FilterHolder)
-     */
-    public void decorateFilterHolder(FilterHolder filter) throws ServletException
-    {
-    }
-    
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.servlet.ServletContextHandler.Decorator#decorateFilterInstance(javax.servlet.Filter)
-     */
-    public <T extends Filter> T decorateFilterInstance(T filter) throws ServletException
-    {
-        decorate(filter);
-        return filter;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.servlet.ServletContextHandler.Decorator#decorateListenerInstance(java.util.EventListener)
-     */
-    public <T extends EventListener> T decorateListenerInstance(T listener) throws ServletException
+    public Object decorate (Object o)
     {
-        decorate(listener);
-        return listener;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.servlet.ServletContextHandler.Decorator#decorateServletHolder(org.eclipse.jetty.servlet.ServletHolder)
-     */
-    public void decorateServletHolder(ServletHolder holder) throws ServletException
-    {
-        decorate(holder);
-    }
-    
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.servlet.ServletContextHandler.Decorator#decorateServletInstance(javax.servlet.Servlet)
-     */
-    public <T extends Servlet> T decorateServletInstance(T servlet) throws ServletException
-    {
-        decorate(servlet);
-        return servlet;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.servlet.ServletContextHandler.Decorator#destroyFilterInstance(javax.servlet.Filter)
-     */
-    public void destroyFilterInstance(Filter f)
-    {
-        destroy(f);
-    }
-
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.servlet.ServletContextHandler.Decorator#destroyServletInstance(javax.servlet.Servlet)
-     */
-    public void destroyServletInstance(Servlet s)
-    {
-        destroy(s);
-    }
-
-    /** 
-     * @see org.eclipse.jetty.servlet.ServletContextHandler.Decorator#destroyListenerInstance(java.util.EventListener)
-     */
-    public void destroyListenerInstance(EventListener l)
-    {
-        destroy(l);
-    }
-
-
-    protected void decorate (Object o) 
-    throws ServletException
-    {       
 
         RunAsCollection runAses = (RunAsCollection)_context.getAttribute(RunAsCollection.RUNAS_COLLECTION);
         if (runAses != null)
             runAses.setRunAs(o);
-        
+
         InjectionCollection injections = (InjectionCollection)_context.getAttribute(InjectionCollection.INJECTION_COLLECTION);
         if (injections != null)
             injections.inject(o);
@@ -146,14 +62,15 @@ public class PlusDecorator implements Decorator
             }
             catch (Exception e)
             {
-                throw new ServletException(e);
+                throw new RuntimeException(e);
             }
         }
-    } 
-    
-    protected void destroy (Object o)
+        return o;
+    }
+
+    public void destroy (Object o)
     {
-        LifeCycleCallbackCollection callbacks = (LifeCycleCallbackCollection)_context.getAttribute(LifeCycleCallbackCollection.LIFECYCLE_CALLBACK_COLLECTION); 
+        LifeCycleCallbackCollection callbacks = (LifeCycleCallbackCollection)_context.getAttribute(LifeCycleCallbackCollection.LIFECYCLE_CALLBACK_COLLECTION);
         if (callbacks != null)
         {
             try
diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/webapp/PlusDescriptorProcessor.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/webapp/PlusDescriptorProcessor.java
index 671dbec..7ca6b03 100644
--- a/jetty-plus/src/main/java/org/eclipse/jetty/plus/webapp/PlusDescriptorProcessor.java
+++ b/jetty-plus/src/main/java/org/eclipse/jetty/plus/webapp/PlusDescriptorProcessor.java
@@ -60,12 +60,12 @@ public class PlusDescriptorProcessor extends IterativeDescriptorProcessor
     {
         try
         {
-            registerVisitor("env-entry", getClass().getDeclaredMethod("visitEnvEntry", __signature));
-            registerVisitor("resource-ref", getClass().getDeclaredMethod("visitResourceRef", __signature));
-            registerVisitor("resource-env-ref", getClass().getDeclaredMethod("visitResourceEnvRef", __signature));
-            registerVisitor("message-destination-ref", getClass().getDeclaredMethod("visitMessageDestinationRef", __signature));
-            registerVisitor("post-construct", getClass().getDeclaredMethod("visitPostConstruct", __signature));
-            registerVisitor("pre-destroy", getClass().getDeclaredMethod("visitPreDestroy", __signature));
+            registerVisitor("env-entry", getClass().getMethod("visitEnvEntry", __signature));
+            registerVisitor("resource-ref", getClass().getMethod("visitResourceRef", __signature));
+            registerVisitor("resource-env-ref", getClass().getMethod("visitResourceEnvRef", __signature));
+            registerVisitor("message-destination-ref", getClass().getMethod("visitMessageDestinationRef", __signature));
+            registerVisitor("post-construct", getClass().getMethod("visitPostConstruct", __signature));
+            registerVisitor("pre-destroy", getClass().getMethod("visitPreDestroy", __signature));
         }
         catch (Exception e)
         {
@@ -73,11 +73,11 @@ public class PlusDescriptorProcessor extends IterativeDescriptorProcessor
         }
     }
 
-    /** 
+    /**
      * @see org.eclipse.jetty.webapp.IterativeDescriptorProcessor#start(WebAppContext, org.eclipse.jetty.webapp.Descriptor)
      */
     public void start(WebAppContext context, Descriptor descriptor)
-    { 
+    {
 
         InjectionCollection injections = (InjectionCollection)context.getAttribute(InjectionCollection.INJECTION_COLLECTION);
         if (injections == null)
@@ -97,24 +97,24 @@ public class PlusDescriptorProcessor extends IterativeDescriptorProcessor
         if (runAsCollection == null)
         {
             runAsCollection = new RunAsCollection();
-            context.setAttribute(RunAsCollection.RUNAS_COLLECTION, runAsCollection);  
+            context.setAttribute(RunAsCollection.RUNAS_COLLECTION, runAsCollection);
         }
     }
 
-    
-    /** 
+
+    /**
      * {@inheritDoc}
      */
     public void end(WebAppContext context,Descriptor descriptor)
     {
     }
 
-    
-   
-    
+
+
+
     /**
-     * JavaEE 5.4.1.3 
-     * 
+     * JavaEE 5.4.1.3
+     *
      * @param node
      * @throws Exception
      */
@@ -124,7 +124,7 @@ public class PlusDescriptorProcessor extends IterativeDescriptorProcessor
         String name=node.getString("env-entry-name",false,true);
         String type = node.getString("env-entry-type",false,true);
         String valueStr = node.getString("env-entry-value",false,true);
-        
+
         //if there's no value there's no point in making a jndi entry
         //nor processing injection entries
         if (valueStr==null || valueStr.equals(""))
@@ -132,7 +132,7 @@ public class PlusDescriptorProcessor extends IterativeDescriptorProcessor
             LOG.warn("No value for env-entry-name "+name);
             return;
         }
-        
+
         Origin o = context.getMetaData().getOrigin("env-entry."+name);
         switch (o)
         {
@@ -147,7 +147,7 @@ public class PlusDescriptorProcessor extends IterativeDescriptorProcessor
                 //of the first one?
                 addInjections (context, descriptor, node, name, TypeUtil.fromName(type));
                 Object value = TypeUtil.valueOf(type,valueStr);
-                bindEnvEntry(name, value);   
+                bindEnvEntry(name, value);
                 break;
             }
             case WebXml:
@@ -164,7 +164,7 @@ public class PlusDescriptorProcessor extends IterativeDescriptorProcessor
                     context.getMetaData().setOrigin("env-entry."+name, descriptor);
                     addInjections (context, descriptor, node, name, TypeUtil.fromName(type));
                     Object value = TypeUtil.valueOf(type,valueStr);
-                    bindEnvEntry(name, value);   
+                    bindEnvEntry(name, value);
                 }
                 else
                 {
@@ -184,8 +184,8 @@ public class PlusDescriptorProcessor extends IterativeDescriptorProcessor
             }
         }
     }
-    
-    
+
+
     /**
      * Common Annotations Spec section 2.3:
      *  resource-ref is for:
@@ -198,11 +198,11 @@ public class PlusDescriptorProcessor extends IterativeDescriptorProcessor
      *    - javax.resource.cci.ConnectionFactory
      *    - org.omg.CORBA_2_3.ORB
      *    - any other connection factory defined by a resource adapter
-     *    
+     *
      * TODO
      * If web.xml contains a resource-ref with injection targets, all resource-ref entries
      * of the same name are ignored in web fragments. If web.xml does not contain any
-     * injection-targets, then they are merged from all the fragments. 
+     * injection-targets, then they are merged from all the fragments.
      * If web.xml does not contain a resource-ref element of same name, but 2 fragments
      * declare the same name it is an error.
      * resource-ref entries are ONLY for connection factories
@@ -211,20 +211,20 @@ public class PlusDescriptorProcessor extends IterativeDescriptorProcessor
      * a real resource in the environment. At the moment, we insist that the
      * jetty.xml file name of the resource has to be exactly the same as the
      * name in web.xml deployment descriptor, but it shouldn't have to be
-     * 
+     *
      * Maintenance update 3.0a to spec:
-     *   Update Section 8.2.3.h.ii with the following -  If a resource reference 
-     *   element is specified in two fragments, while absent from the main web.xml, 
-     *   and all the attributes and child elements of the resource reference element 
-     *   are identical, the resource reference will be merged  into the main web.xml. 
-     *   It is considered an error if a resource reference element has the same name 
-     *   specified in two fragments, while absent from the main web.xml and the attributes 
-     *   and child elements are not identical in the two fragments. For example, if two 
-     *   web fragments declare a <resource-ref> with the same <resource-ref-name> element 
-     *   but the type in one is specified as javax.sql.DataSource while the type in the 
-     *   other is that of a java mail resource, then an error must be reported and the 
+     *   Update Section 8.2.3.h.ii with the following -  If a resource reference
+     *   element is specified in two fragments, while absent from the main web.xml,
+     *   and all the attributes and child elements of the resource reference element
+     *   are identical, the resource reference will be merged  into the main web.xml.
+     *   It is considered an error if a resource reference element has the same name
+     *   specified in two fragments, while absent from the main web.xml and the attributes
+     *   and child elements are not identical in the two fragments. For example, if two
+     *   web fragments declare a <resource-ref> with the same <resource-ref-name> element
+     *   but the type in one is specified as javax.sql.DataSource while the type in the
+     *   other is that of a java mail resource, then an error must be reported and the
      *   application MUST fail to deploy.
-     * 
+     *
      * @param node
      * @throws Exception
      */
@@ -235,7 +235,7 @@ public class PlusDescriptorProcessor extends IterativeDescriptorProcessor
         String type = node.getString("res-type", false, true);
         String auth = node.getString("res-auth", false, true);
         String shared = node.getString("res-sharing-scope", false, true);
-        
+
         Origin o = context.getMetaData().getOrigin("resource-ref."+jndiName);
         switch (o)
         {
@@ -243,45 +243,45 @@ public class PlusDescriptorProcessor extends IterativeDescriptorProcessor
             {
                 //No descriptor or annotation previously declared a resource-ref of this name.
                 context.getMetaData().setOrigin("resource-ref."+jndiName, descriptor);
-                
+
                 //check for <injection> elements
                 Class<?> typeClass = TypeUtil.fromName(type);
                 if (typeClass==null)
                     typeClass = context.loadClass(type);
-                addInjections(context, descriptor, node, jndiName, typeClass);                  
+                addInjections(context, descriptor, node, jndiName, typeClass);
                 bindResourceRef(context,jndiName, typeClass);
                 break;
             }
             case WebXml:
             case WebDefaults:
-            case WebOverride:   
+            case WebOverride:
             {
-                //A web xml previously declared the resource-ref.    
+                //A web xml previously declared the resource-ref.
                 if (!(descriptor instanceof FragmentDescriptor))
                 {
                     //We're processing web-defaults, web.xml or web-override. Any of them can
                     //set or change the resource-ref.
                     context.getMetaData().setOrigin("resource-ref."+jndiName, descriptor);
-     
+
                     //check for <injection> elements
                     Class<?> typeClass = TypeUtil.fromName(type);
                     if (typeClass==null)
                         typeClass = context.loadClass(type);
-                    
+
                     addInjections(context, descriptor, node, jndiName, typeClass);
-                   
+
                     //bind the entry into jndi
                     bindResourceRef(context,jndiName, typeClass);
                 }
                 else
                 {
-                    //A web xml declared the resource-ref and we're processing a 
-                    //web-fragment. Check to see if any injections were declared for it by web.xml. 
+                    //A web xml declared the resource-ref and we're processing a
+                    //web-fragment. Check to see if any injections were declared for it by web.xml.
                     //If any injection was declared in web.xml then don't merge any injections.
                     //If it was declared in a web-fragment, then we can keep merging fragments.
                     Descriptor d = context.getMetaData().getOriginDescriptor("resource-ref."+jndiName+".injection");
                     if (d==null || d instanceof FragmentDescriptor)
-                    { 
+                    {
                         Class<?> typeClass = TypeUtil.fromName(type);
                         if (typeClass==null)
                             typeClass = context.loadClass(type);
@@ -291,7 +291,7 @@ public class PlusDescriptorProcessor extends IterativeDescriptorProcessor
                 break;
             }
             case WebFragment:
-            { 
+            {
                 Descriptor otherFragment = context.getMetaData().getOriginDescriptor("resource-ref."+jndiName);
                 XmlParser.Node otherFragmentRoot = otherFragment.getRoot();
                 Iterator<Object> iter = otherFragmentRoot.iterator();
@@ -304,7 +304,7 @@ public class PlusDescriptorProcessor extends IterativeDescriptorProcessor
                     if ("resource-ref".equals(n.getTag()) && jndiName.equals(n.getString("res-ref-name",false,true)))
                         otherNode = n;
                 }
-                
+
                 //If declared in another web-fragment
                 if (otherNode != null)
                 {
@@ -312,7 +312,7 @@ public class PlusDescriptorProcessor extends IterativeDescriptorProcessor
                     String otherType = otherNode.getString("res-type", false, true);
                     String otherAuth = otherNode.getString("res-auth", false, true);
                     String otherShared = otherNode.getString("res-sharing-scope", false, true);
-                    
+
                     //otherType, otherAuth and otherShared must be the same as type, auth, shared
                     type = (type == null?"":type);
                     otherType = (otherType == null?"":otherType);
@@ -320,7 +320,7 @@ public class PlusDescriptorProcessor extends IterativeDescriptorProcessor
                     otherAuth = (otherAuth == null?"":otherAuth);
                     shared = (shared == null?"":shared);
                     otherShared = (otherShared == null?"":otherShared);
-                    
+
                     //ServletSpec p.75. No declaration of resource-ref in web xml, but different in multiple web-fragments. Error.
                     if (!type.equals(otherType) || !auth.equals(otherAuth) || !shared.equals(otherShared))
                         throw new IllegalStateException("Conflicting resource-ref "+jndiName+" in "+descriptor.getResource());
@@ -329,20 +329,20 @@ public class PlusDescriptorProcessor extends IterativeDescriptorProcessor
                 }
                 else
                     throw new IllegalStateException("resource-ref."+jndiName+" not found in declaring descriptor "+otherFragment);
-                
+
             }
         }
-      
+
     }
-    
-    
+
+
     /**
      * Common Annotations Spec section 2.3:
      *   resource-env-ref is for:
      *     - javax.transaction.UserTransaction
      *     - javax.resource.cci.InteractionSpec
      *     - anything else that is not a connection factory
-     *     
+     *
      * @param node
      * @throws Exception
      */
@@ -351,7 +351,7 @@ public class PlusDescriptorProcessor extends IterativeDescriptorProcessor
     {
         String jndiName = node.getString("resource-env-ref-name",false,true);
         String type = node.getString("resource-env-ref-type", false, true);
-        
+
         Origin o = context.getMetaData().getOrigin("resource-env-ref."+jndiName);
         switch (o)
         {
@@ -369,7 +369,7 @@ public class PlusDescriptorProcessor extends IterativeDescriptorProcessor
             }
             case WebXml:
             case WebDefaults:
-            case WebOverride:   
+            case WebOverride:
             {
                 //A resource-env-ref of this name has been declared first in a web xml.
                 //Only allow other web-default, web.xml, web-override to change it.
@@ -382,7 +382,7 @@ public class PlusDescriptorProcessor extends IterativeDescriptorProcessor
                     if (typeClass==null)
                         typeClass = context.loadClass(type);
                     addInjections (context, descriptor, node, jndiName, typeClass);
-                    bindResourceEnvRef(context,jndiName, typeClass); 
+                    bindResourceEnvRef(context,jndiName, typeClass);
                 }
                 else
                 {
@@ -424,8 +424,8 @@ public class PlusDescriptorProcessor extends IterativeDescriptorProcessor
 
                     //ServletSpec p.75. No declaration of resource-ref in web xml, but different in multiple web-fragments. Error.
                     if (!type.equals(otherType))
-                        throw new IllegalStateException("Conflicting resource-env-ref "+jndiName+" in "+descriptor.getResource());   
-                    
+                        throw new IllegalStateException("Conflicting resource-env-ref "+jndiName+" in "+descriptor.getResource());
+
                     //same in multiple web-fragments, merge the injections
                     addInjections(context, descriptor, node, jndiName, TypeUtil.fromName(type));
                 }
@@ -450,25 +450,25 @@ public class PlusDescriptorProcessor extends IterativeDescriptorProcessor
         String jndiName = node.getString("message-destination-ref-name",false,true);
         String type = node.getString("message-destination-type",false,true);
         String usage = node.getString("message-destination-usage",false,true);
-        
+
         Origin o = context.getMetaData().getOrigin("message-destination-ref."+jndiName);
         switch (o)
         {
             case NotSet:
-            {       
+            {
                 //A message-destination-ref of this name has not been previously declared
                 Class<?> typeClass = TypeUtil.fromName(type);
                 if (typeClass==null)
                     typeClass = context.loadClass(type);
-                addInjections(context, descriptor, node, jndiName, typeClass);   
+                addInjections(context, descriptor, node, jndiName, typeClass);
                 bindMessageDestinationRef(context,jndiName, typeClass);
                 context.getMetaData().setOrigin("message-destination-ref."+jndiName, descriptor);
                 break;
             }
             case WebXml:
             case WebDefaults:
-            case WebOverride:   
-            {               
+            case WebOverride:
+            {
                 //A message-destination-ref of this name has been declared first in a web xml.
                 //Only allow other web-default, web.xml, web-override to change it.
                 if (!(descriptor instanceof FragmentDescriptor))
@@ -476,7 +476,7 @@ public class PlusDescriptorProcessor extends IterativeDescriptorProcessor
                     Class<?> typeClass = TypeUtil.fromName(type);
                     if (typeClass==null)
                         typeClass = context.loadClass(type);
-                    addInjections(context, descriptor, node, jndiName, typeClass);   
+                    addInjections(context, descriptor, node, jndiName, typeClass);
                     bindMessageDestinationRef(context,jndiName, typeClass);
                     context.getMetaData().setOrigin("message-destination-ref."+jndiName, descriptor);
                 }
@@ -486,11 +486,11 @@ public class PlusDescriptorProcessor extends IterativeDescriptorProcessor
                     //It can only contribute injections, and only if the web xml didn't declare any.
                     Descriptor d = context.getMetaData().getOriginDescriptor("message-destination-ref."+jndiName+".injection");
                     if (d == null || d instanceof FragmentDescriptor)
-                    { 
+                    {
                         Class<?> typeClass = TypeUtil.fromName(type);
                         if (typeClass==null)
                             typeClass = context.loadClass(type);
-                        addInjections(context, descriptor, node, jndiName, typeClass);   
+                        addInjections(context, descriptor, node, jndiName, typeClass);
                     }
                 }
                 break;
@@ -518,7 +518,7 @@ public class PlusDescriptorProcessor extends IterativeDescriptorProcessor
                     usage = (usage==null?"":usage);
                     if (!type.equals(otherType) || !usage.equalsIgnoreCase(otherUsage))
                         throw new IllegalStateException("Conflicting message-destination-ref "+jndiName+" in "+descriptor.getResource());
-                    
+
                     //same in multiple web-fragments, merge the injections
                     addInjections(context, descriptor, node, jndiName, TypeUtil.fromName(type));
                 }
@@ -528,8 +528,8 @@ public class PlusDescriptorProcessor extends IterativeDescriptorProcessor
         }
 
     }
-    
-    
+
+
 
     /**
      * If web.xml has at least 1 post-construct, then all post-constructs in fragments
@@ -542,7 +542,7 @@ public class PlusDescriptorProcessor extends IterativeDescriptorProcessor
     {
         String className = node.getString("lifecycle-callback-class", false, true);
         String methodName = node.getString("lifecycle-callback-method", false, true);
-        
+
         if (className==null || className.equals(""))
         {
             LOG.warn("No lifecycle-callback-class specified");
@@ -553,7 +553,7 @@ public class PlusDescriptorProcessor extends IterativeDescriptorProcessor
             LOG.warn("No lifecycle-callback-method specified for class "+className);
             return;
         }
-       
+
         //ServletSpec 3.0 p80 If web.xml declares a post-construct then all post-constructs
         //in fragments must be ignored. Otherwise, they are additive.
         Origin o = context.getMetaData().getOrigin("post-construct");
@@ -563,7 +563,7 @@ public class PlusDescriptorProcessor extends IterativeDescriptorProcessor
             {
                 //No post-constructs have been declared previously.
                 context.getMetaData().setOrigin("post-construct", descriptor);
-                
+
                 try
                 {
                     Class<?> clazz = context.loadClass(className);
@@ -579,7 +579,7 @@ public class PlusDescriptorProcessor extends IterativeDescriptorProcessor
             }
             case WebXml:
             case WebDefaults:
-            case WebOverride:   
+            case WebOverride:
             {
                 //A web xml first declared a post-construct. Only allow other web xml files (web-defaults, web-overrides etc)
                 //to add to it
@@ -616,12 +616,12 @@ public class PlusDescriptorProcessor extends IterativeDescriptorProcessor
                 break;
             }
         }
-      
+
     }
-    
+
 
     /**
-     * 
+     *
      * pre-destroy is the name of a class and method to call just as
      * the instance is being destroyed
      * @param node
@@ -639,8 +639,8 @@ public class PlusDescriptorProcessor extends IterativeDescriptorProcessor
         {
             LOG.warn("No lifecycle-callback-method specified for pre-destroy class "+className);
             return;
-        } 
-      
+        }
+
         Origin o = context.getMetaData().getOrigin("pre-destroy");
         switch(o)
         {
@@ -664,7 +664,7 @@ public class PlusDescriptorProcessor extends IterativeDescriptorProcessor
             }
             case WebXml:
             case WebDefaults:
-            case WebOverride:   
+            case WebOverride:
             {
                 //A web xml file previously declared a pre-destroy. Only allow other web xml files
                 //(not web-fragments) to add to them.
@@ -680,7 +680,7 @@ public class PlusDescriptorProcessor extends IterativeDescriptorProcessor
                     catch (ClassNotFoundException e)
                     {
                         LOG.warn("Couldn't load pre-destory target class "+className);
-                    } 
+                    }
                 }
                 break;
             }
@@ -697,16 +697,16 @@ public class PlusDescriptorProcessor extends IterativeDescriptorProcessor
                 catch (ClassNotFoundException e)
                 {
                     LOG.warn("Couldn't load pre-destory target class "+className);
-                } 
+                }
                 break;
             }
-        }  
+        }
     }
-    
-    
+
+
     /**
      * Iterate over the <injection-target> entries for a node
-     * 
+     *
      * @param descriptor
      * @param node
      * @param jndiName
@@ -715,10 +715,10 @@ public class PlusDescriptorProcessor extends IterativeDescriptorProcessor
     public void addInjections (WebAppContext context, Descriptor descriptor, XmlParser.Node node, String jndiName, Class<?> valueClass)
     {
         Iterator<XmlParser.Node>  itor = node.iterator("injection-target");
-        
+
         while(itor.hasNext())
         {
-            XmlParser.Node injectionNode = itor.next(); 
+            XmlParser.Node injectionNode = itor.next();
             String targetClassName = injectionNode.getString("injection-target-class", false, true);
             String targetName = injectionNode.getString("injection-target-name", false, true);
             if ((targetClassName==null) || targetClassName.equals(""))
@@ -747,7 +747,7 @@ public class PlusDescriptorProcessor extends IterativeDescriptorProcessor
                 injection.setJndiName(jndiName);
                 injection.setTarget(clazz, targetName, valueClass);
                 injections.add(injection);
-                
+
                 //Record which was the first descriptor to declare an injection for this name
                 if (context.getMetaData().getOriginDescriptor(node.getTag()+"."+jndiName+".injection") == null)
                     context.getMetaData().setOrigin(node.getTag()+"."+jndiName+".injection", descriptor);
@@ -758,17 +758,17 @@ public class PlusDescriptorProcessor extends IterativeDescriptorProcessor
             }
         }
     }
-    
-  
 
-    
-    /** 
+
+
+
+    /**
      * @param name
      * @param value
      * @throws Exception
      */
     public void bindEnvEntry(String name, Object value) throws Exception
-    {    
+    {
         InitialContext ic = null;
         boolean bound = false;
         //check to see if we bound a value and an EnvEntry with this name already
@@ -797,12 +797,12 @@ public class PlusDescriptorProcessor extends IterativeDescriptorProcessor
         }
     }
 
-    /** 
+    /**
      * Bind a resource reference.
-     * 
+     *
      * If a resource reference with the same name is in a jetty-env.xml
      * file, it will already have been bound.
-     * 
+     *
      * @param name
      * @throws Exception
      */
@@ -812,7 +812,7 @@ public class PlusDescriptorProcessor extends IterativeDescriptorProcessor
         bindEntry(context, name, typeClass);
     }
 
-    /** 
+    /**
      * @param name
      * @throws Exception
      */
@@ -821,28 +821,28 @@ public class PlusDescriptorProcessor extends IterativeDescriptorProcessor
     {
         bindEntry(context, name, typeClass);
     }
-    
-    
+
+
     public void bindMessageDestinationRef(WebAppContext context, String name, Class<?> typeClass)
     throws Exception
     {
         bindEntry(context, name, typeClass);
     }
-   
-    
+
+
     /**
      * Bind a resource with the given name from web.xml of the given type
-     * with a jndi resource from either the server or the webapp's naming 
+     * with a jndi resource from either the server or the webapp's naming
      * environment.
-     * 
+     *
      * As the servlet spec does not cover the mapping of names in web.xml with
      * names from the execution environment, jetty uses the concept of a Link, which is
      * a subclass of the NamingEntry class. A Link defines a mapping of a name
      * from web.xml with a name from the execution environment (ie either the server or the
      * webapp's naming environment).
-     * 
+     *
      * @param name name of the resource from web.xml
-     * @param typeClass 
+     * @param typeClass
      * @throws Exception
      */
     protected void bindEntry (WebAppContext context, String name, Class<?> typeClass)
@@ -850,12 +850,12 @@ public class PlusDescriptorProcessor extends IterativeDescriptorProcessor
     {
         String nameInEnvironment = name;
         boolean bound = false;
-        
+
         //check if the name in web.xml has been mapped to something else
         //check a context-specific naming environment first
         Object scope = context;
         NamingEntry ne = NamingEntryUtil.lookupNamingEntry(scope, name);
-    
+
         if (ne!=null && (ne instanceof Link))
         {
             //if we found a mapping, get out name it is mapped to in the environment
@@ -865,10 +865,10 @@ public class PlusDescriptorProcessor extends IterativeDescriptorProcessor
         //try finding that mapped name in the webapp's environment first
         scope = context;
         bound = NamingEntryUtil.bindToENC(scope, name, nameInEnvironment);
-        
+
         if (bound)
             return;
-        
+
         //try the server's environment
         scope = context.getServer();
         bound = NamingEntryUtil.bindToENC(scope, name, nameInEnvironment);
@@ -888,12 +888,12 @@ public class PlusDescriptorProcessor extends IterativeDescriptorProcessor
         NamingEntry defaultNE = NamingEntryUtil.lookupNamingEntry(context.getServer(), nameInEnvironment);
         if (defaultNE==null)
             defaultNE = NamingEntryUtil.lookupNamingEntry(null, nameInEnvironment);
-        
+
         if (defaultNE!=null)
             defaultNE.bindToENC(name);
         else
-            throw new IllegalStateException("Nothing to bind for name "+nameInEnvironment);
+            throw new IllegalStateException("Nothing to bind for name " + name);
     }
 
- 
+
 }
diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/webapp/package-info.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/webapp/package-info.java
new file mode 100644
index 0000000..70bbccd
--- /dev/null
+++ b/jetty-plus/src/main/java/org/eclipse/jetty/plus/webapp/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Plus : Limited Additional JEE Webapp Support
+ */
+package org.eclipse.jetty.plus.webapp;
+
diff --git a/jetty-plus/src/test/java/org/eclipse/jetty/plus/jndi/TestNamingEntries.java b/jetty-plus/src/test/java/org/eclipse/jetty/plus/jndi/TestNamingEntries.java
index bda3010..cdacea1 100644
--- a/jetty-plus/src/test/java/org/eclipse/jetty/plus/jndi/TestNamingEntries.java
+++ b/jetty-plus/src/test/java/org/eclipse/jetty/plus/jndi/TestNamingEntries.java
@@ -18,6 +18,12 @@
 
 package org.eclipse.jetty.plus.jndi;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
 import java.util.ArrayList;
 import java.util.Hashtable;
 import java.util.List;
@@ -38,12 +44,6 @@ import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-
 /**
  *
  */
@@ -135,16 +135,16 @@ public class TestNamingEntries
     public void init()
     {
         this.someObject = new SomeObject(4);
-        
-        
 
-        
+
+
+
     }
-    
-    /** 
+
+    /**
      * after each test we should scrape out any lingering bindings to prevent cross test pollution
      * as observed when running java 7
-     * 
+     *
      * @throws Exception
      */
     @After
@@ -246,14 +246,14 @@ public class TestNamingEntries
     {
         ScopeA scope = new ScopeA();
         InitialContext icontext = new InitialContext();
-        Link link = new Link ("resourceA", "resourceB");
-        NamingEntry ne = NamingEntryUtil.lookupNamingEntry(null, "resourceA");
+        Link link = new Link ("linked-resourceA", "resourceB");
+        NamingEntry ne = NamingEntryUtil.lookupNamingEntry(null, "linked-resourceA");
         assertNotNull(ne);
         assertTrue(ne instanceof Link);
-        assertEquals(icontext.lookup("resourceA"), "resourceB");
+        assertEquals(icontext.lookup("linked-resourceA"), "resourceB");
 
-        link = new Link (scope, "jdbc/resourceX", "jdbc/resourceY");
-        ne = NamingEntryUtil.lookupNamingEntry(scope, "jdbc/resourceX");
+        link = new Link (scope, "jdbc/linked-resourceX", "jdbc/linked-resourceY");
+        ne = NamingEntryUtil.lookupNamingEntry(scope, "jdbc/linked-resourceX");
         assertNotNull(ne);
         assertTrue(ne instanceof Link);
     }
diff --git a/jetty-plus/src/test/java/org/eclipse/jetty/plus/jndi/TestNamingEntryUtil.java b/jetty-plus/src/test/java/org/eclipse/jetty/plus/jndi/TestNamingEntryUtil.java
index 7e80b46..55a55af 100644
--- a/jetty-plus/src/test/java/org/eclipse/jetty/plus/jndi/TestNamingEntryUtil.java
+++ b/jetty-plus/src/test/java/org/eclipse/jetty/plus/jndi/TestNamingEntryUtil.java
@@ -18,7 +18,14 @@
 
 package org.eclipse.jetty.plus.jndi;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
 import java.util.List;
+
 import javax.naming.Context;
 import javax.naming.InitialContext;
 import javax.naming.Name;
@@ -27,12 +34,6 @@ import javax.naming.NamingException;
 
 import org.junit.Test;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
 public class TestNamingEntryUtil
 {
     public class MyNamingEntry extends NamingEntry
diff --git a/jetty-plus/src/test/java/org/eclipse/jetty/plus/webapp/PlusDescriptorProcessorTest.java b/jetty-plus/src/test/java/org/eclipse/jetty/plus/webapp/PlusDescriptorProcessorTest.java
index b6b557b..90407ce 100644
--- a/jetty-plus/src/test/java/org/eclipse/jetty/plus/webapp/PlusDescriptorProcessorTest.java
+++ b/jetty-plus/src/test/java/org/eclipse/jetty/plus/webapp/PlusDescriptorProcessorTest.java
@@ -19,8 +19,7 @@
 package org.eclipse.jetty.plus.webapp;
 
 
-
-
+import java.lang.reflect.InvocationTargetException;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
@@ -50,8 +49,9 @@ public class PlusDescriptorProcessorTest
 {
     protected WebDescriptor webDescriptor;
     protected FragmentDescriptor fragDescriptor1;
-    protected FragmentDescriptor fragDescriptor2;    
+    protected FragmentDescriptor fragDescriptor2;
     protected FragmentDescriptor fragDescriptor3;
+    protected FragmentDescriptor fragDescriptor4;
     protected WebAppContext context;
     /**
      * @throws java.lang.Exception
@@ -67,13 +67,13 @@ public class PlusDescriptorProcessorTest
         Context compCtx =  (Context)icontext.lookup ("java:comp");
         compCtx.createSubcontext("env");
         Thread.currentThread().setContextClassLoader(oldLoader);
-        
+
         org.eclipse.jetty.plus.jndi.Resource ds = new org.eclipse.jetty.plus.jndi.Resource (context, "jdbc/mydatasource", new Object());
-        
+
         URL webXml = Thread.currentThread().getContextClassLoader().getResource("web.xml");
         webDescriptor = new WebDescriptor(org.eclipse.jetty.util.resource.Resource.newResource(webXml));
         webDescriptor.parse();
-        
+
         URL frag1Xml = Thread.currentThread().getContextClassLoader().getResource("web-fragment-1.xml");
         fragDescriptor1 = new FragmentDescriptor(org.eclipse.jetty.util.resource.Resource.newResource(frag1Xml));
         fragDescriptor1.parse();
@@ -83,11 +83,14 @@ public class PlusDescriptorProcessorTest
         URL frag3Xml = Thread.currentThread().getContextClassLoader().getResource("web-fragment-3.xml");
         fragDescriptor3 = new FragmentDescriptor(org.eclipse.jetty.util.resource.Resource.newResource(frag3Xml));
         fragDescriptor3.parse();
+        URL frag4Xml = Thread.currentThread().getContextClassLoader().getResource("web-fragment-4.xml");
+        fragDescriptor4 = new FragmentDescriptor(org.eclipse.jetty.util.resource.Resource.newResource(frag4Xml));
+        fragDescriptor4.parse();
     }
-    
+
     @After
     public void tearDown() throws Exception
-    {       
+    {
         ClassLoader oldLoader = Thread.currentThread().getContextClassLoader();
         Thread.currentThread().setContextClassLoader(context.getClassLoader());
         Context ic = new InitialContext();
@@ -97,9 +100,35 @@ public class PlusDescriptorProcessorTest
     }
 
     @Test
+    public void testMissingResourceDeclaration()
+    throws Exception
+    {
+        ClassLoader oldLoader = Thread.currentThread().getContextClassLoader();
+        Thread.currentThread().setContextClassLoader(context.getClassLoader());
+
+        try
+        {
+            PlusDescriptorProcessor pdp = new PlusDescriptorProcessor();
+            pdp.process(context, fragDescriptor4);
+            fail("Expected missing resource declaration");
+        }
+        catch (InvocationTargetException ex)
+        {
+            Throwable cause = ex.getCause();
+            assertNotNull(cause);
+            assertNotNull(cause.getMessage());
+            assertTrue(cause.getMessage().contains("jdbc/mymissingdatasource"));
+        }
+        finally
+        {
+            Thread.currentThread().setContextClassLoader(oldLoader);
+        }
+    }
+
+    @Test
     public void testWebXmlResourceDeclarations()
-    throws Exception 
-    { 
+    throws Exception
+    {
         //if declared in web.xml, fragment declarations ignored
         ClassLoader oldLoader = Thread.currentThread().getContextClassLoader();
         Thread.currentThread().setContextClassLoader(context.getClassLoader());
@@ -110,7 +139,7 @@ public class PlusDescriptorProcessorTest
             Descriptor d = context.getMetaData().getOriginDescriptor("resource-ref.jdbc/mydatasource");
             assertNotNull(d);
             assertTrue(d == webDescriptor);
-            
+
             pdp.process(context, fragDescriptor1);
             pdp.process(context, fragDescriptor2);
         }
@@ -119,12 +148,12 @@ public class PlusDescriptorProcessorTest
             Thread.currentThread().setContextClassLoader(oldLoader);
         }
     }
- 
-    
+
+
     @Test
     public void testMismatchedFragmentResourceDeclarations ()
     throws Exception
-    { 
+    {
         //if declared in more than 1 fragment, declarations must be the same
         ClassLoader oldLoader = Thread.currentThread().getContextClassLoader();
         Thread.currentThread().setContextClassLoader(context.getClassLoader());
@@ -136,7 +165,7 @@ public class PlusDescriptorProcessorTest
             assertNotNull(d);
             assertTrue(d == fragDescriptor1);
             assertEquals(Origin.WebFragment, context.getMetaData().getOrigin("resource-ref.jdbc/mydatasource"));
-            
+
             pdp.process(context, fragDescriptor2);
             fail("Expected conflicting resource-ref declaration");
         }
@@ -149,7 +178,7 @@ public class PlusDescriptorProcessorTest
             Thread.currentThread().setContextClassLoader(oldLoader);
         }
     }
-    
+
     @Test
     public void testMatchingFragmentResourceDeclarations ()
     throws Exception
@@ -167,7 +196,7 @@ public class PlusDescriptorProcessorTest
             assertEquals(Origin.WebFragment, context.getMetaData().getOrigin("resource-ref.jdbc/mydatasource"));
             pdp.process(context, fragDescriptor3);
         }
-       
+
         finally
         {
             Thread.currentThread().setContextClassLoader(oldLoader);
diff --git a/jetty-plus/src/test/java/org/eclipse/jetty/plus/webapp/TestConfiguration.java b/jetty-plus/src/test/java/org/eclipse/jetty/plus/webapp/TestConfiguration.java
index dc5efb9..2750b65 100644
--- a/jetty-plus/src/test/java/org/eclipse/jetty/plus/webapp/TestConfiguration.java
+++ b/jetty-plus/src/test/java/org/eclipse/jetty/plus/webapp/TestConfiguration.java
@@ -18,6 +18,9 @@
 
 package org.eclipse.jetty.plus.webapp;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
 import javax.naming.Context;
 import javax.naming.InitialContext;
 
@@ -25,14 +28,11 @@ import org.eclipse.jetty.plus.jndi.EnvEntry;
 import org.eclipse.jetty.plus.jndi.NamingEntry;
 import org.eclipse.jetty.plus.jndi.NamingEntryUtil;
 import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.webapp.MetaData;
 import org.eclipse.jetty.webapp.WebAppClassLoader;
 import org.eclipse.jetty.webapp.WebAppContext;
-import org.eclipse.jetty.webapp.MetaData;
 import org.junit.Test;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-
 public class TestConfiguration
 {
     @Test
@@ -49,9 +49,9 @@ public class TestConfiguration
             WebAppContext wac = new WebAppContext();
             wac.setServer(server);
             wac.setClassLoader(new WebAppClassLoader(Thread.currentThread().getContextClassLoader(), wac));
-            
+
             MetaData metaData = new MetaData();
-            
+
             PlusDescriptorProcessor plusProcessor = new PlusDescriptorProcessor();
 
             //bind some EnvEntrys at the server level
diff --git a/jetty-plus/src/test/resources/web-fragment-1.xml b/jetty-plus/src/test/resources/web-fragment-1.xml
index b213172..767a64c 100644
--- a/jetty-plus/src/test/resources/web-fragment-1.xml
+++ b/jetty-plus/src/test/resources/web-fragment-1.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="ISO-8859-1"?>
+<?xml version="1.0" encoding="UTF-8"?>
 
 <web-fragment 
    xmlns="http://java.sun.com/xml/ns/javaee" 
@@ -9,7 +9,7 @@
   <name>Fragment1</name>
   
   <ordering>
-    <after>others</after>
+    <after><others/></after>
   </ordering>
 
   <resource-ref>
diff --git a/jetty-plus/src/test/resources/web-fragment-2.xml b/jetty-plus/src/test/resources/web-fragment-2.xml
index e2fff67..7f84099 100644
--- a/jetty-plus/src/test/resources/web-fragment-2.xml
+++ b/jetty-plus/src/test/resources/web-fragment-2.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="ISO-8859-1"?>
+<?xml version="1.0" encoding="UTF-8"?>
 
 <web-fragment 
    xmlns="http://java.sun.com/xml/ns/javaee" 
@@ -9,13 +9,13 @@
   <name>Fragment2</name>
   
   <ordering>
-    <after>others</after>
+    <after><others/></after>
   </ordering>
 
   <resource-ref>
     <res-ref-name>jdbc/mydatasource</res-ref-name>
     <res-type>javax.sql.DataSource</res-type>
-    <res-auth>User</res-auth>
+    <res-auth>Application</res-auth>
 <!--
     <injection-target>
       <injection-target-class>com.acme.Bar</injection-target-class>
diff --git a/jetty-plus/src/test/resources/web-fragment-3.xml b/jetty-plus/src/test/resources/web-fragment-3.xml
index da1f3d5..06c7fa7 100644
--- a/jetty-plus/src/test/resources/web-fragment-3.xml
+++ b/jetty-plus/src/test/resources/web-fragment-3.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="ISO-8859-1"?>
+<?xml version="1.0" encoding="UTF-8"?>
 
 <web-fragment 
    xmlns="http://java.sun.com/xml/ns/javaee" 
@@ -9,7 +9,7 @@
   <name>Fragment3</name>
   
   <ordering>
-    <after>others</after>
+    <after><others/></after>
   </ordering>
 
   <resource-ref>
diff --git a/jetty-plus/src/test/resources/web-fragment-4.xml b/jetty-plus/src/test/resources/web-fragment-4.xml
new file mode 100644
index 0000000..c2158ea
--- /dev/null
+++ b/jetty-plus/src/test/resources/web-fragment-4.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<web-fragment
+   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-fragment_3_0.xsd"
+   version="3.0">
+
+    <name>Fragment 4</name>
+
+    <resource-ref>
+        <res-ref-name>jdbc/mymissingdatasource</res-ref-name>
+        <res-type>javax.sql.DataSource</res-type>
+        <res-auth>Container</res-auth>
+    </resource-ref>
+</web-fragment>
diff --git a/jetty-plus/src/test/resources/web.xml b/jetty-plus/src/test/resources/web.xml
index aa40eb3..f641299 100644
--- a/jetty-plus/src/test/resources/web.xml
+++ b/jetty-plus/src/test/resources/web.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="ISO-8859-1"?>
+<?xml version="1.0" encoding="UTF-8"?>
 <web-app 
    xmlns="http://java.sun.com/xml/ns/javaee" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
diff --git a/jetty-policy/pom.xml b/jetty-policy/pom.xml
deleted file mode 100644
index edeb699..0000000
--- a/jetty-policy/pom.xml
+++ /dev/null
@@ -1,139 +0,0 @@
-<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>
-  <parent>
-    <groupId>org.eclipse.jetty</groupId>
-    <artifactId>jetty-project</artifactId>
-    <version>8.1.17.v20150415</version>
-  </parent>
-  <artifactId>jetty-policy</artifactId>
-  <name>Jetty :: Policy Tool</name>
-  <packaging>jar</packaging>
-  <url>http://www.eclipse.org/jetty</url>
-  <properties>
-    <jetty.test.policy.loc>target/test-policy</jetty.test.policy.loc>
-    <bundle-symbolic-name>${project.groupId}.policy</bundle-symbolic-name>
-  </properties>
-  <build>
-    <plugins>
-      <plugin>
-        <groupId>org.apache.felix</groupId>
-        <artifactId>maven-bundle-plugin</artifactId>
-        <extensions>true</extensions>
-        <executions>
-          <execution>
-            <id>generate-manifest</id>
-            <goals>
-              <goal>manifest</goal>
-            </goals>
-            <configuration>
-              <instructions>
-                <Export-Package>org.eclipse.jetty.policy.*;version="${parsedVersion.osgiVersion}"</Export-Package>
-              </instructions>
-            </configuration>
-          </execution>
-        </executions>
-      </plugin>
-      <plugin>
-        <!--
-        Required for OSGI
-        -->
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-jar-plugin</artifactId>
-        <configuration>
-          <archive>
-            <manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
-          </archive>
-        </configuration>
-      </plugin>
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-assembly-plugin</artifactId>
-        <executions>
-          <execution>
-            <phase>package</phase>
-            <goals>
-              <goal>single</goal>
-            </goals>
-            <configuration>
-              <descriptorRefs>
-                <descriptorRef>config</descriptorRef>
-              </descriptorRefs>
-            </configuration>
-          </execution>
-        </executions>
-      </plugin>
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-dependency-plugin</artifactId>
-        <executions>
-          <execution>
-            <id>unpack</id>
-            <phase>generate-test-resources</phase>
-            <goals>
-              <goal>unpack</goal>
-            </goals>
-            <configuration>
-              <artifactItems>
-                <artifactItem>
-                  <groupId>org.eclipse.jetty.toolchain</groupId>
-                  <artifactId>jetty-test-policy</artifactId>
-                  <version>${jetty-test-policy-version}</version>
-                  <type>jar</type>
-                  <overWrite>true</overWrite>
-                  <includes>**/*.keystore</includes>
-                  <outputDirectory>${jetty.test.policy.loc}</outputDirectory>
-                </artifactItem>
-              </artifactItems>
-            </configuration>
-          </execution>
-          <execution>
-            <id>copy</id>
-            <phase>generate-test-resources</phase>
-            <goals>
-              <goal>copy</goal>
-            </goals>
-            <configuration>
-              <artifactItems>
-                <artifactItem>
-                  <groupId>org.eclipse.jetty.toolchain</groupId>
-                  <artifactId>jetty-test-policy</artifactId>
-                  <version>${jetty-test-policy-version}</version>
-                  <type>jar</type>
-                  <overWrite>true</overWrite>
-                  <includes>**</includes>
-                  <outputDirectory>${jetty.test.policy.loc}</outputDirectory>
-                  <destFileName>jetty-test-policy.jar</destFileName>
-                </artifactItem>
-              </artifactItems>
-            </configuration>
-          </execution>
-        </executions>
-      </plugin>
-      <plugin>
-        <groupId>org.codehaus.mojo</groupId>
-        <artifactId>findbugs-maven-plugin</artifactId>
-        <configuration>
-          <onlyAnalyze>org.eclipse.jetty.policy.*</onlyAnalyze>
-        </configuration>
-      </plugin>
-    </plugins>
-  </build>
-  <dependencies>   
-    <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-util</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-    <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-jmx</artifactId> 
-      <version>${project.version}</version>
-      <optional>true</optional>
-    </dependency>
-    <dependency>
-      <groupId>org.eclipse.jetty.toolchain</groupId>
-      <artifactId>jetty-test-helper</artifactId>
-      <scope>test</scope>
-    </dependency>
-  </dependencies>
-</project>
diff --git a/jetty-policy/src/main/config/etc/jetty-policy.xml b/jetty-policy/src/main/config/etc/jetty-policy.xml
deleted file mode 100644
index c122da3..0000000
--- a/jetty-policy/src/main/config/etc/jetty-policy.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0"?>
-<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
-
-<!-- =============================================================== -->
-<!-- mechanic for starting jetty policy                              -->
-<!--                                                                 -->
-<!-- =============================================================== -->
-
-
-
-<Configure id="Policy" class="org.eclipse.jetty.policy.JettyPolicyConfigurator">
-
-    <Call name="setPolicyDirectory">
-       <Arg><Property name="jetty.home"/>/lib/policy</Arg>
-    </Call>
-
-    <Call name="addProperty">
-      <Arg>jetty.home</Arg>
-      <Arg><Property name="jetty.home"/></Arg>
-    </Call>
-    
-    <Call name="initialize"/>
-</Configure>
diff --git a/jetty-policy/src/main/config/lib/policy/global.policy b/jetty-policy/src/main/config/lib/policy/global.policy
deleted file mode 100644
index 4ad9340..0000000
--- a/jetty-policy/src/main/config/lib/policy/global.policy
+++ /dev/null
@@ -1,43 +0,0 @@
-// 
-// These permissions are granted to all codebases whilst a security 
-// manager is installed and JettyPolicy is in play.
-// 
-// Note the lack of codebase declared.
-
-grant { 
-
-   // allows anyone to listen on un-privileged ports
-   permission java.net.SocketPermission "localhost:1024-", "listen";
-   permission java.net.SocketPermission "localhost:1024-", "accept"; 
-
-   permission java.security.SecurityPermission "putProviderProperty.SunJCE";
-   permission java.util.PropertyPermission "org.eclipse.jetty.io.nio.JVMBUG_THRESHHOLD", "read, write";
-
-   // "standard" properties that can be read by anyone
-   permission java.util.PropertyPermission "entityExpansionLimit", "read";
-   permission java.util.PropertyPermission "elementAttributeLimit", "read";
-   permission java.util.PropertyPermission "maxOccurLimit", "read";
-   permission java.util.PropertyPermission "java.version", "read";
-   permission java.util.PropertyPermission "java.vendor", "read";
-   permission java.util.PropertyPermission "java.vendor.url", "read";
-   permission java.util.PropertyPermission "java.class.version", "read";
-   permission java.util.PropertyPermission "os.name", "read";
-   permission java.util.PropertyPermission "os.version", "read";
-   permission java.util.PropertyPermission "os.arch", "read";
-   permission java.util.PropertyPermission "file.separator", "read";
-   permission java.util.PropertyPermission "path.separator", "read";
-   permission java.util.PropertyPermission "line.separator", "read";
-   permission java.util.PropertyPermission "java.io.tmpdir", "read";
-     
-   permission java.util.PropertyPermission "java.specification.version", "read";
-   permission java.util.PropertyPermission "java.specification.vendor", "read";
-   permission java.util.PropertyPermission "java.specification.name", "read";
- 
-   permission java.util.PropertyPermission "java.vm.specification.version", "read";
-   permission java.util.PropertyPermission "java.vm.specification.vendor", "read";
-   permission java.util.PropertyPermission "java.vm.specification.name", "read";
-   permission java.util.PropertyPermission "java.vm.version", "read";
-   permission java.util.PropertyPermission "java.vm.vendor", "read";
-   permission java.util.PropertyPermission "java.vm.name", "read";
-
-};
diff --git a/jetty-policy/src/main/config/lib/policy/jetty-jmx.policy b/jetty-policy/src/main/config/lib/policy/jetty-jmx.policy
deleted file mode 100644
index b591fc8..0000000
--- a/jetty-policy/src/main/config/lib/policy/jetty-jmx.policy
+++ /dev/null
@@ -1,54 +0,0 @@
-// This file contains permissions related to jmx support
-
-grant codeBase "file:${jetty.home}${/}lib${/}-" {
-
-   // related to using JMX
-   permission javax.management.MBeanTrustPermission "register";
-
-   permission javax.management.MBeanPermission "org.eclipse.jetty.client.HttpClient#-[org.eclipse.jetty.client:*,type=httpclient]", "registerMBean,unregisterMBean";
-
-   permission javax.management.MBeanPermission "org.eclipse.jetty.deploy.ContextDeployer#-[org.eclipse.jetty.deploy:id=0,type=contextdeployer]", "registerMBean,unregisterMBean";
-   permission javax.management.MBeanPermission "org.eclipse.jetty.deploy.DeploymentManager#-[org.eclipse.jetty.deploy:id=0,type=deploymentmanager]", "registerMBean,unregisterMBean";
-   permission javax.management.MBeanPermission "org.eclipse.jetty.deploy.WebAppDeployer#-[org.eclipse.jetty.deploy:id=0,type=webappdeployer]", "registerMBean,unregisterMBean";
-   permission javax.management.MBeanPermission "org.eclipse.jetty.deploy.providers.ContextProvider#-[org.eclipse.jetty.deploy.providers:id=0,type=contextprovider]", "registerMBean,unregisterMBean";
-
-   permission javax.management.MBeanPermission "org.eclipse.jetty.jmx.MBeanContainer#-[org.eclipse.jetty.jmx:id=0,type=mbeancontainer]", "registerMBean,unregisterMBean";
-
-   permission javax.management.MBeanPermission "org.eclipse.jetty.server.handler.ContextHandler#-[org.eclipse.jetty.server.handler:*]", "registerMBean,unregisterMBean";
-   permission javax.management.MBeanPermission "org.eclipse.jetty.server.handler.ResourceHandler#-[org.eclipse.jetty.server.handler:*]", "registerMBean,unregisterMBean";
-
-   permission javax.management.MBeanPermission "org.eclipse.jetty.server.handler.ContextHandlerCollection#-[org.eclipse.jetty.server.handler:*]", "registerMBean,unregisterMBean";
-   permission javax.management.MBeanPermission "org.eclipse.jetty.server.handler.DefaultHandler#-[org.eclipse.jetty.server.handler:*]", "registerMBean,unregisterMBean";
-   permission javax.management.MBeanPermission "org.eclipse.jetty.server.handler.ErrorHandler#-[org.eclipse.jetty.server.handler:*]", "registerMBean,unregisterMBean";
-   permission javax.management.MBeanPermission "org.eclipse.jetty.server.handler.HandlerCollection#-[org.eclipse.jetty.server.handler:*]", "registerMBean,unregisterMBean";
-   permission javax.management.MBeanPermission "org.eclipse.jetty.server.handler.MovedContextHandler#-[org.eclipse.jetty.server.handler:*]", "registerMBean,unregisterMBean";
-   permission javax.management.MBeanPermission "org.eclipse.jetty.server.handler.MovedContextHandler$Redirector#-[org.eclipse.jetty.server.handler:*]", "registerMBean,unregisterMBean";
-   permission javax.management.MBeanPermission "org.eclipse.jetty.server.handler.RequestLogHandler#-[org.eclipse.jetty.server.handler:*]", "registerMBean,unregisterMBean";
-   permission javax.management.MBeanPermission "org.eclipse.jetty.server.NCSARequestLog#-[org.eclipse.jetty.server:id=0,type=ncsarequestlog]", "registerMBean,unregisterMBean";
-   permission javax.management.MBeanPermission "org.eclipse.jetty.server.session.HashSessionIdManager#-[org.eclipse.jetty.server.session:*]", "registerMBean,unregisterMBean";
-   permission javax.management.MBeanPermission "org.eclipse.jetty.server.session.HashSessionManager#-[org.eclipse.jetty.server.session:*]", "registerMBean,unregisterMBean";
-   permission javax.management.MBeanPermission "org.eclipse.jetty.server.session.SessionHandler#-[org.eclipse.jetty.server.session:*]", "registerMBean,unregisterMBean";
-   permission javax.management.MBeanPermission "org.eclipse.jetty.server.Server#-[org.eclipse.jetty.server:*]", "registerMBean,unregisterMBean";
-   permission javax.management.MBeanPermission "org.eclipse.jetty.server.ssl.SslSelectChannelConnector#-[org.eclipse.jetty.server.ssl:id=0,type=sslselectchannelconnector]", "registerMBean,unregisterMBean";
-
-   permission javax.management.MBeanPermission "org.eclipse.jetty.servlet.ServletMapping#-[org.eclipse.jetty.servlet:*]", "registerMBean,unregisterMBean";
-   permission javax.management.MBeanPermission "org.eclipse.jetty.servlet.ServletHolder#-[org.eclipse.jetty.servlet:*]", "registerMBean,unregisterMBean";
-   permission javax.management.MBeanPermission "org.eclipse.jetty.servlet.ServletHandler#-[org.eclipse.jetty.servlet:*]", "registerMBean,unregisterMBean";
-
-   permission javax.management.MBeanPermission "org.eclipse.jetty.security.ConstraintSecurityHandler#-[org.eclipse.jetty.security:*]", "registerMBean,unregisterMBean";
-   permission javax.management.MBeanPermission "org.eclipse.jetty.security.HashLoginService#-[org.eclipse.jetty.security:*]", "registerMBean,unregisterMBean";
-
-   permission javax.management.MBeanPermission "org.eclipse.jetty.servlet.ErrorPageErrorHandler#-[org.eclipse.jetty.servlet:*]", "registerMBean,unregisterMBean";
-   permission javax.management.MBeanPermission "org.eclipse.jetty.servlet.FilterHolder#-[org.eclipse.jetty.servlet:*]", "registerMBean,unregisterMBean";
-   permission javax.management.MBeanPermission "org.eclipse.jetty.servlet.FilterMapping#-[org.eclipse.jetty.servlet:*]", "registerMBean,unregisterMBean";
-
-   permission javax.management.MBeanPermission "org.eclipse.jetty.util.log.Slf4jLog#-[org.eclipse.jetty.util.log:*]", "registerMBean,unregisterMBean";
-   permission javax.management.MBeanPermission "org.eclipse.jetty.util.log.StdErrLog#-[org.eclipse.jetty.util.log:*]", "registerMBean,unregisterMBean";
-   permission javax.management.MBeanPermission "org.eclipse.jetty.util.Scanner#-[org.eclipse.jetty.util:*]", "registerMBean,unregisterMBean";
-   permission javax.management.MBeanPermission "org.eclipse.jetty.util.thread.QueuedThreadPool#-[org.eclipse.jetty.util.thread:*]", "registerMBean,unregisterMBean";
-
-   permission javax.management.MBeanPermission "org.eclipse.jetty.webapp.WebAppContext#-[org.eclipse.jetty.webapp:*]", "registerMBean,unregisterMBean";
-
-   permission javax.management.MBeanPermission "org.eclipse.jetty.policy.JettyPolicy#-[org.eclipse.jetty.policy:*]", "registerMBean,unregisterMBean";
-
-}
\ No newline at end of file
diff --git a/jetty-policy/src/main/config/lib/policy/jetty-start.policy b/jetty-policy/src/main/config/lib/policy/jetty-start.policy
deleted file mode 100644
index 0cf6655..0000000
--- a/jetty-policy/src/main/config/lib/policy/jetty-start.policy
+++ /dev/null
@@ -1,40 +0,0 @@
-// This file contains permissions necessary for jetty-start to operate and to 
-// bootstrap jetty
-// 
-// Once the required processing in jetty-start has completed jetty itself
-// is started via XmlConfiguration which executes under AccessController 
-// doPrivledged freeing any further permissions being required to this 
-// module.
-
-grant codeBase "file:${jetty.home}${/}start.jar" {
-
-   permission java.io.FilePermission "${jetty.home}${/}-", "read";
-   
-   permission java.lang.RuntimePermission "createClassLoader";
-   permission java.lang.RuntimePermission "setContextClassLoader";    
-   permission java.security.SecurityPermission "getPolicy";
-   permission java.lang.RuntimePermission "accessDeclaredMembers";
-   
-   permission java.util.PropertyPermission "jetty.home", "read, write";
-    
-   permission java.util.PropertyPermission "user.home", "read";
-     
-   permission java.util.PropertyPermission "jetty.class.path", "read, write";
-   permission java.util.PropertyPermission "java.class.path", "read, write";
-     
-   permission java.util.PropertyPermission "repository", "read, write";
-     
-   permission java.util.PropertyPermission "jetty.lib", "read";
-   permission java.util.PropertyPermission "jetty.server", "read";
-   permission java.util.PropertyPermission "jetty.host", "read";
-   permission java.util.PropertyPermission "jetty.port", "read";
-   permission java.util.PropertyPermission "start.class", "read";
-     
-   permission java.util.PropertyPermission "main.class", "read";  
-   permission java.util.PropertyPermission "ISO_8859_1", "read";	 
-   permission javax.security.auth.AuthPermission "modifyPrincipals";
-	 
-	permission javax.security.auth.AuthPermission "modifyPrivateCredentials";
-	permission javax.security.auth.AuthPermission "setReadOnly"; 	 
-	permission java.lang.RuntimePermission "getClassLoader";	
-}
\ No newline at end of file
diff --git a/jetty-policy/src/main/config/lib/policy/jetty-work.policy b/jetty-policy/src/main/config/lib/policy/jetty-work.policy
deleted file mode 100644
index b1c2279..0000000
--- a/jetty-policy/src/main/config/lib/policy/jetty-work.policy
+++ /dev/null
@@ -1,19 +0,0 @@
-//
-// This file contains permissions for the work directory of jetty.
-//
-// Typical usage of secured jetty implies usage of a standard work
-// style directory that web applications are unpacked into.  These specific
-// web applications should have their own policy files however this 
-// file exists to provide a codebase that all webapps under a certain code
-// base can be given should there not be a more exclusive policy file
-// provided.
-
-
-grant codeBase "file:${jetty.home${/}work${/}-" {
-   permission java.io.FilePermission "${jetty.home}${/}webapps${/}-", "read"; // Ought to go up a specific codebase
-   permission java.io.FilePermission "${jetty.home}${/}work${/}-", "read";
-   permission java.util.PropertyPermission "org.eclipse.jetty.util.log.class", "read";  
-   permission java.util.PropertyPermission "org.eclipse.jetty.util.log.stderr.DEBUG", "read";  
-   permission java.util.PropertyPermission "org.eclipse.jetty.util.log.IGNORED", "read";
-   
-};
diff --git a/jetty-policy/src/main/config/lib/policy/jetty.policy b/jetty-policy/src/main/config/lib/policy/jetty.policy
deleted file mode 100644
index 632f7a7..0000000
--- a/jetty-policy/src/main/config/lib/policy/jetty.policy
+++ /dev/null
@@ -1,96 +0,0 @@
-// This file governs the permissions directly granted to all jar files
-// listed under the jetty.home/lib directory.
-//
-// Review of this file is recommended and possible tweaking of the codeBase
-// is likely in the future.
-
-grant codeBase "file:${jetty.home}${/}lib${/}-" {
-	 
-   permission java.lang.RuntimePermission "getClassLoader";
-	 
-   permission java.util.PropertyPermission "org.eclipse.jetty.webapp.WebAppClassLoader.extensions", "read";
-   permission java.util.PropertyPermission "org.eclipse.jetty.ajp.PathMap.separators", "read";
- 
-   permission java.util.PropertyPermission "ROLLOVERFILE_BACKUP_FORMAT", "read";
-	 
-   permission java.util.PropertyPermission "org.eclipse.jetty.server.webapp.parentLoaderPriority", "read";
-   permission java.util.PropertyPermission "org.eclipse.jetty.server.Request.maxFormContentSize", "read";
-	 
-   permission javax.security.auth.AuthPermission "modifyPrincipals";	 
-   permission javax.security.auth.AuthPermission "modifyPrivateCredentials";
-   permission javax.security.auth.AuthPermission "setReadOnly";
-  
-   permission java.io.FilePermission "${jetty.home}${/}-", "read";
-   permission java.io.FilePermission "${java.io.tmpdir}", "read, write";
-   permission java.io.FilePermission "${java.io.tmpdir}${/}-", "read, write";
-   permission java.io.FilePermission "${/}private${/}${java.io.tmpdir}", "read, write";
-   permission java.io.FilePermission "${/}private${/}${java.io.tmpdir}${/}-", "read, write";
-   permission java.io.FilePermission "${jetty.home}${/}lib${/}policy${/}-", "read";
-   
-   
-   permission java.io.FilePermission "${java.io.tmpdir}${/}-", "delete";
-
-   
-   permission java.io.FilePermission "${jetty.home}${/}logs", "read, write";
-   permission java.io.FilePermission "${jetty.home}${/}logs${/}*", "read, write";
-     
-   permission java.lang.RuntimePermission "createClassLoader";
-   permission java.lang.RuntimePermission "setContextClassLoader";
-     
-   permission java.security.SecurityPermission "getPolicy";
-   permission java.lang.RuntimePermission "accessDeclaredMembers";
-     
-   // jetty specific properties
-   permission java.util.PropertyPermission "org.eclipse.jetty.util.log.class", "read";
-   permission java.util.PropertyPermission "org.eclipse.jetty.util.log.stderr.DEBUG", "read";
-   permission java.util.PropertyPermission "org.eclipse.jetty.util.log.DEBUG", "read";
-   permission java.util.PropertyPermission "START", "read";
-   permission java.util.PropertyPermission "STOP.PORT", "read";
-   permission java.util.PropertyPermission "STOP.KEY", "read";
-   permission java.util.PropertyPermission "org.eclipse.jetty.util.log.IGNORED", "read";      
-   permission java.util.PropertyPermission "CLASSPATH", "read";
-   permission java.util.PropertyPermission "OPTIONS", "read";
-   permission java.util.PropertyPermission "JETTY_NO_SHUTDOWN_HOOK", "read";
-   permission java.util.PropertyPermission "ISO_8859_1", "read";
-   permission java.util.PropertyPermission "jetty.home", "read, write";
-     
-   permission java.util.PropertyPermission "user.home", "read";
-   permission java.util.PropertyPermission "user.dir", "read";
-   
-     
-   permission java.util.PropertyPermission "jetty.class.path", "read, write";
-   permission java.util.PropertyPermission "java.class.path", "read, write";
-     
-   permission java.util.PropertyPermission "jetty.lib", "read";
-   permission java.util.PropertyPermission "jetty.server", "read";
-   permission java.util.PropertyPermission "jetty.host", "read";
-   permission java.util.PropertyPermission "jetty.port", "read";
-     
-   permission java.util.PropertyPermission "org.eclipse.jetty.util.log.stderr.class", "read";
-   		
-   permission java.util.PropertyPermission "org.eclipse.jetty.util.URI.charset", "read";
-   		
-   permission java.util.PropertyPermission "org.eclipse.jetty.util.FileResource.checkAliases", "read";
-   		
-   permission java.util.PropertyPermission "org.eclipse.jetty.xml.XmlParser.Validating", "read";
-   		
-   permission java.util.PropertyPermission "org.eclipse.jetty.io.nio.JVMBUG_THRESHHOLD", "read, write";
-    
-   permission java.util.PropertyPermission "org.eclipse.jetty.util.TypeUtil.IntegerCacheSize", "read, write";
-       
-   permission java.util.PropertyPermission "org.eclipse.jetty.util.TypeUtil.LongCacheSize", "read";
-       
-   permission java.util.PropertyPermission "org.eclipse.jetty.io.AbstractBuffer.boundsChecking", "read";
-       
-   // provides access to webapps
-   permission java.io.FilePermission "${jetty.home}${/}webapps${/}-", "read"; // Ought to go up a specific codebase
-              
-              
-   // Allows any thread to stop itself using the java.lang.Thread.stop()
-   // method that takes no argument.
-   permission java.lang.RuntimePermission "stopThread";    
-   
-    // jsp support   
-   permission java.net.SocketPermission "java.sun.com:80", "connect,resolve";
-	 
-};
\ No newline at end of file
diff --git a/jetty-policy/src/main/config/lib/policy/temp-dirs.policy b/jetty-policy/src/main/config/lib/policy/temp-dirs.policy
deleted file mode 100644
index 128e78d..0000000
--- a/jetty-policy/src/main/config/lib/policy/temp-dirs.policy
+++ /dev/null
@@ -1,30 +0,0 @@
-// This file contains permissions for various temporary directories that 
-// jetty might operate under.  
-//
-// Careful auditing of this file is recommended for your particular use case
-
-//
-// the tmp directory is where webapps are unpacked by default so setup their restricted permissions
-//
-grant codeBase "file:${java.io.tmpdir}${/}" {
-
-   permission java.io.FilePermission "${jetty.home}${/}webapps${/}-", "read"; // Ought to go up a specific codebases
-   permission java.io.FilePermission "${java.io.tmpdir}${/}-", "read";
-   permission java.util.PropertyPermission "org.eclipse.jetty.util.log.class", "read";   
-   permission java.util.PropertyPermission "org.eclipse.jetty.util.log.IGNORED", "read";   
-   permission java.util.PropertyPermission "org.eclipse.jetty.util.log.stderr.DEBUG", "read";
-   
-};
-
-//
-// some operating systems have tmp as a symbolic link to /private/tmp
-//
-grant codeBase "file:/private${java.io.tmpdir}${/}-" {
-
-   permission java.io.FilePermission "${jetty.home}${/}webapps${/}-", "read"; // Ought to go up a specific codebase
-   permission java.io.FilePermission "/private/${java.io.tmpdir}${/}-", "read";
-   permission java.util.PropertyPermission "org.eclipse.jetty.util.log.class", "read";  
-   permission java.util.PropertyPermission "org.eclipse.jetty.util.log.stderr.DEBUG", "read";  
-   permission java.util.PropertyPermission "org.eclipse.jetty.util.log.IGNORED", "read";
-   
-};
\ No newline at end of file
diff --git a/jetty-policy/src/main/java/org/eclipse/jetty/policy/JettyPolicy.java b/jetty-policy/src/main/java/org/eclipse/jetty/policy/JettyPolicy.java
deleted file mode 100644
index dc099f9..0000000
--- a/jetty-policy/src/main/java/org/eclipse/jetty/policy/JettyPolicy.java
+++ /dev/null
@@ -1,462 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.policy;
-
-import java.io.PrintStream;
-import java.io.PrintWriter;
-import java.security.AccessControlException;
-import java.security.CodeSource;
-import java.security.Permission;
-import java.security.PermissionCollection;
-import java.security.Permissions;
-import java.security.Policy;
-import java.security.Principal;
-import java.security.ProtectionDomain;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateException;
-import java.util.Enumeration;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.util.security.CertificateValidator;
-
-
-/**
- * Policy implementation that will load a set of policy files and manage the mapping of permissions and protection domains
- * 
- * Features of JettyPolicy are:
- * 
- * - we are able to follow the startup mechanic that jetty uses with jetty-start using OPTIONS=policy,default to be able to startup a security manager and policy implementation without have to rely on the existing JVM cli options 
- * - support for specifying multiple policy files to source permissions from
- * - support for merging protection domains across multiple policy files for the same codesource
- * - support for directories of policy files, just specify directory and all *.policy files will be loaded.
-
- * Possible additions are: 
- * - scan policy directory for new policy files being added
- * - jmx reporting
- * - proxying of system security policy where we can proxy access to the system policy should the jvm have been started with one, I had support for this but ripped it
- * out to add in again later 
- * - an xml policy file parser, had originally added this using modello but tore it out since it would have been a
- * nightmare to get its dependencies through IP validation, could do this with jvm xml parser instead sometime 
- * - check performance of the synch'd map I am using for the protection domain mapping
- */
-public class JettyPolicy extends Policy
-{
-    private static final Logger LOG = Log.getLogger(JettyPolicy.class);
-    
-    private static boolean __DEBUG = false;
-    private static boolean __RELOAD = false;
-
-    private boolean _STARTED = false;
-    
-    private String _policyDirectory;
-    
-    private final Set<PolicyBlock> _grants = new HashSet<PolicyBlock>();
-
-    /*
-     * TODO: make into a proper cache
-     */
-    private final Map<Object, PermissionCollection> _cache = new ConcurrentHashMap<Object, PermissionCollection>();
-
-    private final static PolicyContext _context = new PolicyContext();
-
-    private CertificateValidator _validator = null;
-        
-    private PolicyMonitor _policyMonitor = new PolicyMonitor()
-    {        
-        @Override
-        public void onPolicyChange(PolicyBlock grant)
-        {
-            boolean setGrant = true;     
-            
-            if ( _validator != null )
-            {
-                if (grant.getCertificates() != null)
-                {
-                    for ( Certificate cert : grant.getCertificates() )
-                    {
-                        try
-                        {
-                            _validator.validate(_context.getKeystore(), cert);
-                        }
-                        catch ( CertificateException ce )
-                        {
-                            setGrant = false;
-                        }
-                    }                
-                }
-            }
-                  
-            if ( setGrant )
-            {
-                _grants.add( grant );
-                _cache.clear();
-            }
-        }
-    };
-    
-    public JettyPolicy(String policyDirectory, Map<String, String> properties)
-    {
-        try
-        {
-            __RELOAD = Boolean.getBoolean("org.eclipse.jetty.policy.RELOAD");
-            __DEBUG = Boolean.getBoolean("org.eclipse.jetty.policy.DEBUG");
-        }
-        catch (AccessControlException ace)
-        {
-            __RELOAD = false;
-            __DEBUG = false;
-        }
-        
-        _policyDirectory = policyDirectory;
-        _context.setProperties(properties);
-        
-        try
-        {
-            _policyMonitor.setPolicyDirectory(_policyDirectory);
-            //_policyMonitor.setReload( __RELOAD );
-        }
-        catch ( Exception e)
-        {
-            throw new PolicyException(e);
-        }
-    }
-    
-    
-    
-    @Override
-    public void refresh()
-    {        
-        if ( !_STARTED )
-        {
-            initialize();
-        }
-    }
-
-    /**
-     * required for the jetty policy to start function, initializes the 
-     * policy monitor and blocks for a full cycle of policy grant updates
-     */
-    public void initialize()
-    {
-        if ( _STARTED )
-        {
-            return;         
-        }
-        
-        try
-        {
-            _policyMonitor.start();
-            _policyMonitor.waitForScan();
-        }
-        catch (Exception e)
-        {
-            e.printStackTrace();
-            throw new PolicyException(e);
-        }
-        
-        _STARTED = true;
-    }
-    
-    @Override
-    public PermissionCollection getPermissions(ProtectionDomain domain)
-    {
-
-        if (!_STARTED)
-        {
-            throw new PolicyException("JettyPolicy must be started.");
-        }
-
-        synchronized (_cache)
-        {
-            if (_cache.containsKey(domain))
-            {
-                return copyOf(_cache.get(domain));
-            }
-
-            PermissionCollection perms = new Permissions();
-
-            for (Iterator<PolicyBlock> i = _grants.iterator(); i.hasNext();)
-            {
-                PolicyBlock policyBlock = i.next();
-                ProtectionDomain grantPD = policyBlock.toProtectionDomain();
-
-                if (__DEBUG)
-                {
-                    debug("----START----");
-                    debug("PDCS: " + policyBlock.getCodeSource());
-                    debug("CS: " + domain.getCodeSource());
-
-                }
-
-                // 1) if protection domain codesource is null, it is the global permissions (grant {})
-                // 2) if protection domain codesource implies target codesource and there are no prinicpals
-                if (grantPD.getCodeSource() == null 
-                        || 
-                        grantPD.getCodeSource().implies(domain.getCodeSource()) 
-                        && 
-                        grantPD.getPrincipals() == null 
-                        || 
-                        grantPD.getCodeSource().implies(domain.getCodeSource()) 
-                        && 
-                        validate(grantPD.getPrincipals(),domain.getPrincipals()))
-
-                {
-
-                    for (Enumeration<Permission> e = policyBlock.getPermissions().elements(); e.hasMoreElements();)
-                    {
-                        Permission perm = e.nextElement();
-                        if (__DEBUG)
-                        {
-                            debug("D: " + perm);
-                        }
-                        perms.add(perm);
-                    }
-                }
-                if (__DEBUG)
-                {
-                    debug("----STOP----");
-                }
-            }
-
-            _cache.put(domain,perms);
-
-            return copyOf(perms);
-        }
-    }
-
-    @Override
-    public PermissionCollection getPermissions(CodeSource codesource)
-    {
-        if (!_STARTED)
-        {
-            throw new PolicyException("JettyPolicy must be started.");
-        }
-
-        synchronized (_cache)
-        {
-            if (_cache.containsKey(codesource))
-            {
-                return copyOf(_cache.get(codesource));
-            }
-
-            PermissionCollection perms = new Permissions();
-
-            for (Iterator<PolicyBlock> i = _grants.iterator(); i.hasNext();)
-            {
-                PolicyBlock policyBlock = i.next();
-                ProtectionDomain grantPD = policyBlock.toProtectionDomain();
-
-                if (grantPD.getCodeSource() == null 
-                        || 
-                        grantPD.getCodeSource().implies(codesource))
-                {
-                    if (__DEBUG)
-                    {
-                        debug("----START----");
-                        debug("PDCS: " + grantPD.getCodeSource());
-                        debug("CS: " + codesource);
-                    }
-
-                    for (Enumeration<Permission> e = policyBlock.getPermissions().elements(); e.hasMoreElements();)
-                    {
-                        Permission perm = e.nextElement();
-                        if (__DEBUG)
-                        {
-                            debug("D: " + perm);
-                        }
-                        perms.add(perm);
-                    }
-
-                    if (__DEBUG)
-                    {
-                        debug("----STOP----");
-                    }
-                }
-            }
-
-            _cache.put(codesource,perms);
-
-            return copyOf(perms);
-        }
-    }
-
-    @Override
-    public boolean implies(ProtectionDomain domain, Permission permission)
-    {
-        if (!_STARTED)
-        {
-            throw new PolicyException("JettyPolicy must be started.");
-        }
-        
-        PermissionCollection pc = getPermissions(domain);
-        
-        return (pc == null ? false : pc.implies(permission));
-    }
-    
-
-    private static boolean validate(Principal[] permCerts, Principal[] classCerts)
-    {
-        if (classCerts == null)
-        {
-            return false;
-        }
-
-        for (int i = 0; i < permCerts.length; ++i)
-        {
-            boolean found = false;
-            for (int j = 0; j < classCerts.length; ++j)
-            {
-                if (permCerts[i].equals(classCerts[j]))
-                {
-                    found = true;
-                    break;
-                }
-            }
-            // if we didn't find the permCert in the classCerts then we don't match up
-            if (found == false)
-            {
-                return false;
-            }
-        }
-
-        return true;
-    }
-
-
-    /**
-     * returns the policy context which contains the map of properties that
-     * can be referenced in policy files and the keystore for validation
-     * 
-     * @return the policy context
-     */
-    public static PolicyContext getContext()
-    {
-        return _context;
-    }
-    
-   
-    
-    /**
-     * Try and log to normal logging channels and should that not be allowed
-     * debug to system.out
-     * 
-     * @param message
-     */
-    private void debug( String message )
-    {
-        try
-        {
-            LOG.info(message);
-        }
-        catch ( AccessControlException ace )
-        {
-            System.out.println( "[DEBUG] " +  message );
-        }
-        catch ( NoClassDefFoundError ace )
-        {
-            System.out.println( "[DEBUG] " + message );
-            //ace.printStackTrace();
-        }
-    }
-    /**
-     * Try and log to normal logging channels and should that not be allowed
-     * log to system.out
-     * 
-     * @param message
-     */
-    private void log( String message )
-    {
-        log( message, null );
-    }
-    
-    /**
-     * Try and log to normal logging channels and should that not be allowed
-     * log to system.out
-     * 
-     * @param message
-     */
-    private void log( String message, Throwable t )
-    {
-        try
-        {
-            LOG.info(message, t);
-        }
-        catch ( AccessControlException ace )
-        {
-            System.out.println( message );
-            t.printStackTrace();
-        }
-        catch ( NoClassDefFoundError ace )
-        {
-            System.out.println( message );
-            t.printStackTrace();
-        }
-    }
-    
-
-    public void dump(PrintStream out)
-    {
-        PrintWriter write = new PrintWriter(out);
-        write.println("JettyPolicy: policy settings dump");
-
-        synchronized (_cache)
-        {
-            for (Iterator<Object> i = _cache.keySet().iterator(); i.hasNext();)
-            {
-                Object o = i.next();
-                write.println(o.toString());
-            }
-        }
-        write.flush();
-    }
-    
-    private PermissionCollection copyOf(final PermissionCollection in)
-    {
-        PermissionCollection out  = new Permissions();
-        synchronized (in)
-        {
-            for (Enumeration<Permission> el = in.elements() ; el.hasMoreElements() ;)
-            {
-                out.add((Permission)el.nextElement());
-            }
-        }
-        return out;
-    }
-
-    public CertificateValidator getCertificateValidator()
-    {
-        return _validator;
-    }
-
-    public void setCertificateValidator(CertificateValidator validator)
-    {
-        if (_STARTED)
-        {
-            throw new PolicyException("JettyPolicy already started, unable to set validator on running policy");
-        }
-        
-        _validator = validator;
-    }
-}
diff --git a/jetty-policy/src/main/java/org/eclipse/jetty/policy/JettyPolicyConfigurator.java b/jetty-policy/src/main/java/org/eclipse/jetty/policy/JettyPolicyConfigurator.java
deleted file mode 100644
index fbd0fa9..0000000
--- a/jetty-policy/src/main/java/org/eclipse/jetty/policy/JettyPolicyConfigurator.java
+++ /dev/null
@@ -1,58 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.policy;
-
-import java.security.Policy;
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * 
- *
- */
-public class JettyPolicyConfigurator
-{
-    String _policyDirectory;
-    Map<String, String> _properties = new HashMap<String,String>();
-    
-    public JettyPolicyConfigurator()
-    {
-        
-    }
-    
-    public void setPolicyDirectory( String policyDirectory )
-    {
-        _policyDirectory = policyDirectory;
-    }
-    
-    public void addProperty( String name, String value )
-    {
-        _properties.put(name,value);
-    }
-    
-    public void initialize()
-    {
-        JettyPolicy jpolicy = new JettyPolicy(_policyDirectory,_properties);
-
-        jpolicy.refresh();
-        Policy.setPolicy(jpolicy);
-        System.setSecurityManager(new SecurityManager());
-    }
-
-}
diff --git a/jetty-policy/src/main/java/org/eclipse/jetty/policy/PolicyBlock.java b/jetty-policy/src/main/java/org/eclipse/jetty/policy/PolicyBlock.java
deleted file mode 100644
index dbb573c..0000000
--- a/jetty-policy/src/main/java/org/eclipse/jetty/policy/PolicyBlock.java
+++ /dev/null
@@ -1,102 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.policy;
-
-import java.security.CodeSource;
-import java.security.KeyStore;
-import java.security.PermissionCollection;
-import java.security.Principal;
-import java.security.ProtectionDomain;
-import java.security.cert.Certificate;
-import java.util.Set;
-
-public class PolicyBlock
-{
-    public CodeSource codesource;
-    
-    public KeyStore keyStore;
-    
-    public Set<Certificate> certificates;
-
-    public Principal[] principals;
-    
-    public PermissionCollection permissions;
-    
-    private ProtectionDomain protectionDomain;
-    
-    public ProtectionDomain toProtectionDomain()
-    {
-        if ( protectionDomain == null )
-        {
-            protectionDomain = new ProtectionDomain(codesource,null,Thread.currentThread().getContextClassLoader(),principals);
-        }
-                
-        return protectionDomain;
-    }
-   
-    public KeyStore getKeyStore()
-    {
-        return keyStore;
-    }
-
-    public void setKeyStore(KeyStore keyStore)
-    {
-        this.keyStore = keyStore;
-    }
-
-    public CodeSource getCodeSource()
-    {
-        return codesource;
-    }
-
-    public void setCodeSource( CodeSource codesource )
-    {
-        this.codesource = codesource;
-    }
-
-    public Set<Certificate> getCertificates()
-    {
-        return certificates;
-    }
-
-    public void setCertificates( Set<Certificate> certificates )
-    {
-        this.certificates = certificates;
-    }
-
-    public Principal[] getPrincipals()
-    {
-        return principals;
-    }
-
-    public void setPrincipals( Principal[] principals )
-    {
-        this.principals = principals;
-    }
-
-    public PermissionCollection getPermissions()
-    {
-        return permissions;
-    }
-
-    public void setPermissions( PermissionCollection permissions )
-    {
-        this.permissions = permissions;
-    }
-}
diff --git a/jetty-policy/src/main/java/org/eclipse/jetty/policy/PolicyContext.java b/jetty-policy/src/main/java/org/eclipse/jetty/policy/PolicyContext.java
deleted file mode 100644
index fdda5b6..0000000
--- a/jetty-policy/src/main/java/org/eclipse/jetty/policy/PolicyContext.java
+++ /dev/null
@@ -1,200 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.policy;
-
-import java.io.File;
-import java.security.KeyStore;
-import java.security.Principal;
-import java.security.cert.Certificate;
-import java.security.cert.X509Certificate;
-import java.util.HashMap;
-import java.util.Map;
-
-public class PolicyContext
-{
-    private Map<String, String> properties = new HashMap<String, String>();
-    
-    private Principal[] principals;
-    private KeyStore keystore;
-    
-    public PolicyContext()
-    {
-        // special property case for resolving ${/} to native separator
-        properties.put( "/", File.separator );
-    }
-    
-    public void addProperty( String name, String value )
-    {
-        this.properties.put( name, value );
-    }
-    
-    public void setProperties( Map<String,String> properties )
-    {
-        this.properties.putAll( properties );
-    }
-
-    public KeyStore getKeystore()
-    {
-        return keystore;
-    }
-
-    public void setKeystore( KeyStore keystore )
-    {
-        this.keystore = keystore;
-    }  
-
-    public Principal[] getPrincipals()
-    {
-        return principals;
-    }
-
-    public void setPrincipals( Principal[] principals )
-    {
-        this.principals = principals;
-    }
-
-    public String evaluate(String s) throws PolicyException
-    {       
-        s = processProtocols( s );
-        
-        int i1=0;
-        int i2=0;
-
-        while (s!=null)
-        {
-            i1=s.indexOf("${");
-            if (i1<0)
-            {
-                break;
-            }
-            
-            i2=s.indexOf("}",i1+2);
-            if (i2<0)
-            {
-                break;
-            }
-     
-            String property=getProperty(s.substring(i1+2,i2));
-       
-            s=s.substring(0,i1)+property+s.substring(i2+1);         
-        }
-        
-        return s;
-    }
-    
-    private String processProtocols( String s ) throws PolicyException
-    {
-        int i1=0;
-        int i2=0;
-
-        while (s!=null)
-        {
-            i1=s.indexOf("${{");
-            if (i1<0)
-            {
-                break;
-            }
-            
-            i2=s.indexOf("}}",i1+2);
-            if (i2<0)
-            {
-                break;
-            }
-     
-            String property;
-            String target = s.substring(i1+3,i2);
-            
-            if ( target.indexOf( ":" ) >= 0 )
-            {
-                String[] resolve = target.split( ":" );
-                property = resolve(resolve[0], resolve[1] );
-            }
-            else
-            {
-                property = resolve( target, null );
-            }
-            s=s.substring(0,i1)+property+s.substring(i2+2);
-        }
-        
-        return s;
-    }
-    
-    
-    public String getProperty(String name)
-    {       
-        if (properties.containsKey(name))
-        {
-            return properties.get(name);
-        }
-        
-        return System.getProperty(name);
-    }
-    
-    private String resolve( String protocol, String data ) throws PolicyException
-    {
-
-        if ( "self".equals( protocol ) ) 
-        { 
-            // need expanding to list of principals in grant clause
-            if ( principals != null && principals.length != 0 )
-            {
-                StringBuilder sb = new StringBuilder();
-                for ( int i = 0; i < principals.length; ++i )
-                {
-                    sb.append( principals[i].getClass().getName() );
-                    sb.append( " \"" );
-                    sb.append( principals[i].getName() );
-                    sb.append( "\" " );
-                }
-                return sb.toString();
-            }
-            else
-            {
-                throw new PolicyException( "self can not be expanded, missing principals" );
-            }
-        }
-        if ( "alias".equals( protocol ) ) 
-        { 
-            try
-            {
-                 Certificate cert = keystore.getCertificate(data);
-               
-                 if ( cert instanceof X509Certificate )
-                 {
-                     Principal principal = ((X509Certificate) cert).getSubjectX500Principal(); 
-                     StringBuilder sb = new StringBuilder();
-                     sb.append( principal.getClass().getName() );
-                     sb.append( " \"" );
-                     sb.append( principal.getName() );
-                     sb.append( "\" " );
-                     return sb.toString();
-                 }
-                 else
-                 {
-                     throw new PolicyException( "alias can not be expanded, bad cert" );
-                 }
-            }
-            catch ( Exception e )
-            {
-                throw new PolicyException( "alias can not be expanded: " + data );
-            }
-        }
-        throw new PolicyException( "unknown protocol: " + protocol );
-    }    
-}
diff --git a/jetty-policy/src/main/java/org/eclipse/jetty/policy/PolicyException.java b/jetty-policy/src/main/java/org/eclipse/jetty/policy/PolicyException.java
deleted file mode 100644
index b629a18..0000000
--- a/jetty-policy/src/main/java/org/eclipse/jetty/policy/PolicyException.java
+++ /dev/null
@@ -1,45 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.policy;
-
- at SuppressWarnings("serial")
-public class PolicyException extends RuntimeException 
-{
-
-    public PolicyException()
-    {
-            super();
-    }
-
-    public PolicyException( final String message, final Throwable cause)
-    {
-            super( message, cause );
-    }
-
-    public PolicyException( final String message )
-    {
-            super( message );
-    }
-
-    public PolicyException( final Throwable cause )
-    {
-            super( cause );
-    }
-	
-}
diff --git a/jetty-policy/src/main/java/org/eclipse/jetty/policy/PolicyMonitor.java b/jetty-policy/src/main/java/org/eclipse/jetty/policy/PolicyMonitor.java
deleted file mode 100644
index c9a72c9..0000000
--- a/jetty-policy/src/main/java/org/eclipse/jetty/policy/PolicyMonitor.java
+++ /dev/null
@@ -1,328 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.policy;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import org.eclipse.jetty.policy.loader.DefaultPolicyLoader;
-import org.eclipse.jetty.util.Scanner;
-import org.eclipse.jetty.util.component.AbstractLifeCycle;
-
-/**
- * PolicyMonitor watches a directory for files ending in the *.policy extension,
- * loads them and detects when they change.  PolicyGrants are peeped out the
- * onPolicyChange method to whoever is using this monitor.
- *
- */
-public abstract class PolicyMonitor extends AbstractLifeCycle
-{    
-
-    /** 
-     * the directory to be scanned for policy files.
-     */
-    private String _policyDirectory;
-    
-    /** 
-     * instance of the scanner that detects policy files
-     */
-    private Scanner _scanner;
-
-    /** 
-     * true if updates to policy grants will be pushed through the 
-     * onPolicyChange() method
-     */
-    private boolean _reload = true;
-    
-    /**
-     * scan interval in seconds for policy file changes
-     */
-    private int _scanInterval = 1;
-            
-    /**
-     * specialized listener enabling waitForScan() functionality
-     */
-    private LatchScannerListener _scanningListener;
-    
-    /**
-     * true if the scanner has completed one cycle.
-     */
-    private boolean _initialized = false;
-        
-    /**
-     * record of the number of scans that have been made
-     */
-    private AtomicInteger _scanCount = new AtomicInteger(0);
-    
-    /**
-     * empty constructor
-     */
-    public PolicyMonitor()
-    {
-        
-    }
-    
-    /**
-     * construtor with a predetermined directory to monitor
-     * 
-     * @param directory
-     */
-    public PolicyMonitor( String directory )
-    {
-        this();
-        _policyDirectory = directory;
-    }
-    
-    /**
-     * set the policy directory to scan on a non-running monitor
-     * 
-     * @param directory
-     */
-    public void setPolicyDirectory( String directory )
-    {
-        if (isRunning())
-        {
-            throw new PolicyException("policy monitor is running, unable to set policy directory");
-        }
-        
-        _policyDirectory = directory;
-    }
-    
-    /**
-     * gets the scanner interval
-     * 
-     * @return the scan interval
-     */
-    public int getScanInterval()
-    {
-        return _scanInterval;
-    }
-    
-    /**
-     * sets the scanner interval on a non-running instance of the monitor
-     * 
-     * @param scanInterval in seconds
-     * @see Scanner#setScanInterval(int)
-     */
-    public void setScanInterval( int scanInterval )
-    {
-        if (isRunning())
-        {
-            throw new PolicyException("policy monitor is running, unable to set scan interval");
-        }
-        
-        _scanInterval = scanInterval;
-    }
-    
-    /**
-     * true of the monitor is initialized, meaning that at least one
-     * scan cycle has completed and any policy grants found have been chirped
-     * 
-     * @return true if initialized
-     */
-    public boolean isInitialized()
-    {
-        return _initialized;
-    }
-    
-    /**
-     * gets the number of times the scan has been run
-     * 
-     * @return scan count
-     */
-    public int getScanCount()
-    {
-        return _scanCount.get();
-    }
-    
-    /**
-     * initiates a scan and blocks until it has been completed
-     * 
-     * @throws Exception
-     */
-    public synchronized void waitForScan() throws Exception
-    {
-        // wait for 2 scans for stable files
-        CountDownLatch latch = new CountDownLatch(2);
-        
-       _scanningListener.setScanningLatch(latch);
-       _scanner.scan();
-       latch.await();
-    }  
-    
-    /**
-     * true of reload is enabled, false otherwise
-     * 
-     * @return true if reload is enabled
-     */
-    public boolean isReloadEnabled()
-    {
-        return _reload;
-    }
-
-    /**
-     * sets the monitor to reload or not, but only if the monitor isn't already running
-     * 
-     * TODO this doesn't really _have_ to be on a non-running monitor
-     * 
-     * @param reload
-     */
-    public void setReload(boolean reload)
-    {
-        if (isRunning())
-        {
-            throw new PolicyException("policy monitor is running, unable to set reload at this time");
-        }
-        
-        _reload = reload;
-    }
-
-    /**
-     * processes a policy file via the default policy loader and chirps
-     * changes to the onPolicyChange() abstract method
-     * 
-     * @param filename
-     */
-    private void processPolicyFile(String filename)
-    {
-        try
-        {
-            File policyFile = new File(filename);
-
-            Set<PolicyBlock> policyBlocks = DefaultPolicyLoader.load(new FileInputStream(policyFile),JettyPolicy.getContext());
-
-            for (PolicyBlock policy : policyBlocks)
-            {
-                onPolicyChange(policy);
-            }
-        }
-        catch (Exception e)
-        {
-            e.printStackTrace();
-        }
-    }
-
-    /**
-     * called by the abstract lifecycle to start the monitor
-     */
-    @Override
-    protected void doStart() throws Exception
-    {
-        super.doStart();
-        
-        _scanner = new Scanner();
-
-        List<File> scanDirs = new ArrayList<File>();
-
-        scanDirs.add(new File( _policyDirectory ) );
-        
-        //System.out.println("Scanning: " + _policyDirectory );
-        
-        _scanner.addListener(new Scanner.DiscreteListener()
-        {
-
-            public void fileRemoved(String filename) throws Exception
-            {
-
-            }
-
-            /* will trigger when files are changed, not on load time, just when changed */
-            public void fileChanged(String filename) throws Exception
-            {
-               if (_reload && filename.endsWith("policy"))
-               {
-                  // System.out.println("PolicyMonitor: policy file");
-                   processPolicyFile(filename);
-               }
-            }
-
-            public void fileAdded(String filename) throws Exception
-            {
-                if (filename.endsWith("policy"))
-                {
-                   // System.out.println("PolicyMonitor: added policy file");
-                    processPolicyFile(filename);
-                }
-            }
-        });
-        
-        _scanningListener = new LatchScannerListener();
-        
-        _scanner.addListener(_scanningListener);
-
-        _scanner.setScanDirs(scanDirs);
-        _scanner.setReportExistingFilesOnStartup(true);
-        _scanner.start();
-        _scanner.setScanInterval(_scanInterval);
-    }
-    
-    /**
-     * called by the abstract life cycle to turn off the monitor
-     */
-    @Override
-    protected void doStop() throws Exception
-    {
-        super.doStop();
-        
-        _scanner.stop();
-    }
-    
-    /**
-     * latch listener that can taken in a countdownlatch and notify other 
-     * blocking threads that the scan has been completed
-     *
-     */
-    private class LatchScannerListener implements Scanner.ScanCycleListener
-    {
-        CountDownLatch _latch;
-        
-        public void scanStarted(int cycle) throws Exception
-        {
-
-        }
-        
-        public void scanEnded(int cycle) throws Exception
-        {
-            _initialized = true; // just really needed the first time
-            _scanCount.incrementAndGet();
-            if ( _latch != null )
-            {
-                _latch.countDown();
-            }
-        }
-        
-        public void setScanningLatch( CountDownLatch latch )
-        {
-            _latch = latch;
-        }
-    }
-    
-    /**
-     * implemented by the user of the policy monitor to handle custom logic 
-     * related to the usage of the policy grant instance/s.
-     * 
-     * @param grant
-     */
-    public abstract void onPolicyChange(PolicyBlock grant);
-}
diff --git a/jetty-policy/src/main/java/org/eclipse/jetty/policy/entry/AbstractEntry.java b/jetty-policy/src/main/java/org/eclipse/jetty/policy/entry/AbstractEntry.java
deleted file mode 100644
index 13df45a..0000000
--- a/jetty-policy/src/main/java/org/eclipse/jetty/policy/entry/AbstractEntry.java
+++ /dev/null
@@ -1,52 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.policy.entry;
-
-import org.eclipse.jetty.policy.PolicyContext;
-import org.eclipse.jetty.policy.PolicyException;
-
-public abstract class AbstractEntry
-{
-    private boolean isDirty = false;
-    private boolean isExpanded = false;
-    
-    public abstract void expand( PolicyContext context ) throws PolicyException;
-
-    public boolean isDirty()
-    {
-        return isDirty;
-    }
-
-    public void setDirty( boolean isDirty )
-    {
-        this.isDirty = isDirty;
-    }
-
-    public boolean isExpanded()
-    {
-        return isExpanded;
-    }
-
-    public void setExpanded( boolean isExpanded )
-    {
-        this.isExpanded = isExpanded;
-    }
-    
-    
-}
diff --git a/jetty-policy/src/main/java/org/eclipse/jetty/policy/entry/GrantEntry.java b/jetty-policy/src/main/java/org/eclipse/jetty/policy/entry/GrantEntry.java
deleted file mode 100644
index a269ce1..0000000
--- a/jetty-policy/src/main/java/org/eclipse/jetty/policy/entry/GrantEntry.java
+++ /dev/null
@@ -1,209 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.policy.entry;
-
-import java.net.URI;
-import java.net.URL;
-import java.security.CodeSource;
-import java.security.KeyStore;
-import java.security.KeyStoreException;
-import java.security.PermissionCollection;
-import java.security.Permissions;
-import java.security.Principal;
-import java.security.cert.Certificate;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.Set;
-import java.util.StringTokenizer;
-
-import org.eclipse.jetty.policy.PolicyContext;
-import org.eclipse.jetty.policy.PolicyException;
-
-public class GrantEntry extends AbstractEntry
-{
-
-    /**
-     * The signers part of grant clause. This is a comma-separated list of certificate aliases.
-     */
-    private String signers;
-
-    /**
-     * The codebase part of grant clause. This is an URL from which code originates.
-     */
-    private String codebase;
-
-    /**
-     * Collection of PrincipalEntries of grant clause.
-     */
-    private Collection<PrincipalEntry> principalNodes;
-
-    /**
-     * Collection of PermissionEntries of grant clause.
-     */
-    private Collection<PermissionEntry> permissionNodes;
-
-    // cached permissions
-    private PermissionCollection permissions;
-    private Certificate[] signerArray;
-    private CodeSource codesource;
-    private Principal[] principals;
-    
-    /**
-     * Adds specified element to the <code>principals</code> collection. If collection does not exist yet, creates a
-     * new one.
-     */
-    public void addPrincipal( PrincipalEntry pe )
-    {
-        if ( principalNodes == null )
-        {
-            principalNodes = new HashSet<PrincipalEntry>();
-        }
-        principalNodes.add( pe );
-    }
-
-    public void expand( PolicyContext context ) throws PolicyException
-    {
-        if ( signers != null )
-        {
-            signerArray = resolveToCertificates( context.getKeystore(), signers );  // TODO alter to support self:: etc
-        }
-        codebase = context.evaluate( codebase );
-        
-        if ( principalNodes != null )
-        {
-            Set<Principal> principalSet = new HashSet<Principal>();
-            for ( Iterator<PrincipalEntry> i = principalNodes.iterator(); i.hasNext(); )
-            {
-                PrincipalEntry node = i.next();
-                node.expand( context );
-                principalSet.add( node.toPrincipal( context ) );
-            }
-            principals = principalSet.toArray( new Principal[principalSet.size()] );
-        }
-        
-        context.setPrincipals( principals );
-        permissions = new Permissions();
-        for ( Iterator<PermissionEntry> i = permissionNodes.iterator(); i.hasNext(); )
-        {
-            PermissionEntry node = i.next();
-            node.expand( context );
-            permissions.add( node.toPermission() );
-        }
-        context.setPrincipals( null );
-        
-        setExpanded( true );
-    }    
-    
-    public PermissionCollection getPermissions() throws PolicyException
-    {
-        return permissions;
-    }    
-    
-    public Principal[] getPrincipals() throws PolicyException
-    {
-        return principals;
-    }
-    
-    public CodeSource getCodeSource() throws PolicyException
-    {
-        if ( !isExpanded() )
-        {
-            throw new PolicyException("GrantNode needs to be expanded.");
-        }
-        
-        try
-        {
-            if ( codesource == null && codebase != null )
-            {
-                URL url = new URI( codebase ).toURL();
-                codesource = new CodeSource( url, signerArray );
-            }
-
-            return codesource;
-        }
-        catch ( Exception e )
-        {
-            throw new PolicyException( e );
-        }
-    }
-    
-    /**
-     * resolve signers into an array of certificates using a given keystore
-     * 
-     * @param keyStore
-     * @param signers
-     * @return
-     * @throws Exception
-     */
-    private Certificate[] resolveToCertificates( KeyStore keyStore, String signers ) throws PolicyException
-    {               
-        if ( keyStore == null )
-        {
-            Certificate[] certs = null;
-            return certs;
-        }
-                
-        Set<Certificate> certificateSet = new HashSet<Certificate>();       
-        StringTokenizer strTok = new StringTokenizer( signers, ",");
-        
-        for ( int i = 0; strTok.hasMoreTokens(); ++i )
-        {
-            try
-            {               
-                Certificate certificate = keyStore.getCertificate( strTok.nextToken().trim() );
-                
-                if ( certificate != null )
-                {
-                    certificateSet.add( certificate );
-                }               
-            }
-            catch ( KeyStoreException kse )
-            {
-                throw new PolicyException( kse );
-            }
-        }
-        
-        return certificateSet.toArray( new Certificate[certificateSet.size()] );
-    }
-    
-
-    public void setSigners( String signers )
-    {
-        this.signers = signers;
-    }
-
-    public void setCodebase( String codebase )
-    {
-        this.codebase = codebase;
-    }
-
-    public void setPrincipals( Collection<PrincipalEntry> principals )
-    {
-        this.principalNodes = principals;
-    }
-
-    public void setPermissions( Collection<PermissionEntry> permissions )
-    {
-        this.permissionNodes = permissions;
-    }
-
-    
-    
-}
diff --git a/jetty-policy/src/main/java/org/eclipse/jetty/policy/entry/KeystoreEntry.java b/jetty-policy/src/main/java/org/eclipse/jetty/policy/entry/KeystoreEntry.java
deleted file mode 100644
index 3fdefbb..0000000
--- a/jetty-policy/src/main/java/org/eclipse/jetty/policy/entry/KeystoreEntry.java
+++ /dev/null
@@ -1,98 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.policy.entry;
-
-import java.io.InputStream;
-import java.net.URL;
-import java.security.KeyStore;
-
-import org.eclipse.jetty.policy.PolicyContext;
-import org.eclipse.jetty.policy.PolicyException;
-import org.eclipse.jetty.util.resource.Resource;
-
-public class KeystoreEntry extends AbstractEntry
-{
-    /**
-     * The URL part of keystore clause.
-     */
-    private String url;
-
-    /**
-     * The typename part of keystore clause.
-     */
-    private String type;
-    
-    // cached value
-    private KeyStore keystore;
-
-    public KeyStore toKeyStore() throws PolicyException
-    { 
-        if ( keystore != null && !isDirty() )
-        {
-            return keystore;
-        }
-        
-        try 
-        {           
-            keystore = KeyStore.getInstance( type );
-            
-            URL keyStoreLocation = new URL ( url );
-            Resource r = Resource.newResource(keyStoreLocation);
-            InputStream istream = r.getInputStream();
-            
-            keystore.load( istream, null );
-            
-            
-        }
-        catch ( Exception e )
-        {
-            throw new PolicyException( e );
-        }
-        
-        return keystore; 
-    }
-    
-    @Override
-    public void expand( PolicyContext context ) throws PolicyException
-    {
-        url = context.evaluate( url );
-        
-        setExpanded( true );
-    }
-
-    public String getUrl()
-    {
-        return url;
-    }
-
-    public void setUrl( String url )
-    {
-        this.url = url;
-    }
-
-    public String getType()
-    {
-        return type;
-    }
-
-    public void setType( String type )
-    {
-        this.type = type;
-    }    
-}
diff --git a/jetty-policy/src/main/java/org/eclipse/jetty/policy/entry/PermissionEntry.java b/jetty-policy/src/main/java/org/eclipse/jetty/policy/entry/PermissionEntry.java
deleted file mode 100644
index 5fefe9d..0000000
--- a/jetty-policy/src/main/java/org/eclipse/jetty/policy/entry/PermissionEntry.java
+++ /dev/null
@@ -1,226 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.policy.entry;
-
-import java.lang.reflect.Constructor;
-import java.security.KeyStore;
-import java.security.KeyStoreException;
-import java.security.Permission;
-import java.security.cert.Certificate;
-import java.util.HashSet;
-import java.util.Set;
-import java.util.StringTokenizer;
-
-import org.eclipse.jetty.policy.PolicyContext;
-import org.eclipse.jetty.policy.PolicyException;
-
-public class PermissionEntry extends AbstractEntry
-{
-    /**
-     * The classname part of permission clause.
-     */
-    private String klass;
-
-    /**
-     * The name part of permission clause.
-     */
-    private String name;
-
-    /**
-     * The actions part of permission clause.
-     */
-    private String actions;
-
-    /**
-     * The signers part of permission clause. This is a comma-separated list of certificate aliases.
-     */
-    private String signers;
-    
-    
-    private Certificate[] signerArray;
-    
-    public Permission toPermission() throws PolicyException
-    {
-        try
-        {
-            Class<?> clazz = Class.forName(klass);
-            
-            if ( signerArray != null && !validate( signerArray, (Certificate[])clazz.getSigners() ) )
-            {
-                throw new PolicyException( "Unvalidated Permissions: " + klass + "/" + name );
-            }
-            
-            Permission permission = null;
-
-            if ( name == null && actions == null )
-            {
-                permission = (Permission) clazz.newInstance();
-            }
-            else if ( name != null && actions == null )
-            {
-                Constructor<?> c = clazz.getConstructor(new Class[]
-                { String.class });
-                permission = (Permission) c.newInstance( name );
-            }
-            else if ( name != null && actions != null )
-            {
-                Constructor<?> c = clazz.getConstructor(new Class[]
-                { String.class, String.class });
-                permission = (Permission) c.newInstance( name, actions );
-            }
-          
-            return permission;    
-        }
-        catch ( Exception e )
-        {
-            throw new PolicyException( e );
-        }
-    }
-    
-    @Override
-    public void expand( PolicyContext context ) throws PolicyException
-    {
-        if ( name != null )
-        {
-            name = context.evaluate( name ).trim();
-        }
-        
-        if ( actions != null )
-        {
-            actions = context.evaluate( actions ).trim();
-        }
-        
-        if ( signers != null )
-        {
-            signerArray = resolveCertificates( context.getKeystore(), signers );
-        }
-        
-        setExpanded( true );
-    }
-    
-    /**
-     * validate that all permission certs are present in the class certs
-     * 
-     * @param permCerts
-     * @param classCerts
-     * @return true if the permissions match up
-     */
-    private static boolean validate( Certificate[] permCerts, Certificate[] classCerts )
-    {
-        if ( classCerts == null )
-        {
-            return false;
-        }
-        
-        for ( int i = 0; i < permCerts.length; ++i )
-        {
-            boolean found = false;           
-            for ( int j = 0; j < classCerts.length; ++j )
-            {
-                if ( permCerts[i].equals( classCerts[j] ) )
-                {
-                    found = true;
-                    break;
-                }
-            }
-            // if we didn't find the permCert in the classCerts then we don't match up
-            if ( found == false )
-            {
-                return false;
-            }
-        }
-        
-        // we found all the permCerts in classCerts so return true
-        return true;
-    }
-    
-    private static Certificate[] resolveCertificates( KeyStore keyStore, String signers ) throws PolicyException
-    {               
-        if ( keyStore == null )
-        {
-            Certificate[] certs = null;
-            return certs;
-        }
-                
-        Set<Certificate> certificateSet = new HashSet<Certificate>();       
-        StringTokenizer strTok = new StringTokenizer( signers, ",");
-        
-        for ( int i = 0; strTok.hasMoreTokens(); ++i )
-        {
-            try
-            {               
-                Certificate certificate = keyStore.getCertificate( strTok.nextToken().trim() );
-                
-                if ( certificate != null )
-                {
-                    certificateSet.add( certificate );
-                }               
-            }
-            catch ( KeyStoreException kse )
-            {
-                throw new PolicyException( kse );
-            }
-        }
-        
-        return certificateSet.toArray( new Certificate[certificateSet.size()]);
-    }
-
-    public String getKlass()
-    {
-        return klass;
-    }
-
-    public void setKlass( String klass )
-    {
-        this.klass = klass;
-    }
-
-    public String getName()
-    {
-        return name;
-    }
-
-    public void setName( String name )
-    {
-        this.name = name;
-    }
-
-    public String getActions()
-    {
-        return actions;
-    }
-
-    public void setActions( String actions )
-    {
-        this.actions = actions;
-    }
-
-    public String getSigners()
-    {
-        return signers;
-    }
-
-    public void setSigners( String signers )
-    {
-        this.signers = signers;
-    }
-    
-    
-    
-}
diff --git a/jetty-policy/src/main/java/org/eclipse/jetty/policy/entry/PrincipalEntry.java b/jetty-policy/src/main/java/org/eclipse/jetty/policy/entry/PrincipalEntry.java
deleted file mode 100644
index 75e2be6..0000000
--- a/jetty-policy/src/main/java/org/eclipse/jetty/policy/entry/PrincipalEntry.java
+++ /dev/null
@@ -1,115 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.policy.entry;
-
-import java.security.KeyStoreException;
-import java.security.Principal;
-import java.security.cert.Certificate;
-import java.security.cert.X509Certificate;
-
-import org.eclipse.jetty.policy.PolicyContext;
-import org.eclipse.jetty.policy.PolicyException;
-
-public class PrincipalEntry extends AbstractEntry
-{
-    /**
-     * Wildcard value denotes any class and/or any name. Must be asterisk, for proper general expansion and
-     * PrivateCredentialsPermission wildcarding
-     */
-    public static final String WILDCARD = "*"; //$NON-NLS-1$
-
-    /**
-     * The classname part of principal clause.
-     */
-    private String klass;
-
-    /**
-     * The name part of principal clause.
-     */
-    private String name;
-    
-    /**
-     * cached principal if already computed
-     */
-    private Principal principal;
-    
-    public Principal toPrincipal( PolicyContext context ) throws PolicyException
-    {
-        if ( principal != null && !isDirty() )
-        {
-            return principal;
-        }
-        
-        // if there is no keystore, there is no way to obtain a principal object 
-        // TODO validate we need this check
-        if ( context.getKeystore() == null )
-        {
-            return null;
-        }
-
-        try
-        {
-            Certificate certificate = context.getKeystore().getCertificate( name );
-
-            if ( certificate instanceof X509Certificate )
-            {
-                principal = ( (X509Certificate) certificate ).getSubjectX500Principal();
-                return principal;
-            }
-            else
-            {
-                throw new PolicyException( "Unknown Certificate, unable to obtain Principal: " + certificate.getType() );
-            }
-        }
-        catch ( KeyStoreException kse )
-        {
-            throw new PolicyException( kse );
-        }
-    }
-
-    public void expand( PolicyContext context )
-        throws PolicyException
-    {
-        name = context.evaluate( name );
-        
-        setExpanded(true);
-    }
-
-    public String getKlass()
-    {
-        return klass;
-    }
-
-    public void setKlass( String klass )
-    {
-        this.klass = klass;
-    }
-
-    public String getName()
-    {
-        return name;
-    }
-
-    public void setName( String name )
-    {
-        this.name = name;
-    }
-    
-    
-}
diff --git a/jetty-policy/src/main/java/org/eclipse/jetty/policy/loader/DefaultPolicyLoader.java b/jetty-policy/src/main/java/org/eclipse/jetty/policy/loader/DefaultPolicyLoader.java
deleted file mode 100644
index 19d5027..0000000
--- a/jetty-policy/src/main/java/org/eclipse/jetty/policy/loader/DefaultPolicyLoader.java
+++ /dev/null
@@ -1,102 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//  Portions of this file adapted for use from Apache Harmony code by written
-//  and contributed to that project by Alexey V. Varlamov under the ASL
-//  ========================================================================
-//
-
-package org.eclipse.jetty.policy.loader;
-
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.security.KeyStore;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Set;
-
-import org.eclipse.jetty.policy.PolicyBlock;
-import org.eclipse.jetty.policy.PolicyContext;
-import org.eclipse.jetty.policy.PolicyException;
-import org.eclipse.jetty.policy.entry.GrantEntry;
-import org.eclipse.jetty.policy.entry.KeystoreEntry;
-
-/**
- * Load the policies within the stream and resolve into protection domains and permission collections 
- * 
- */
-public class DefaultPolicyLoader
-{
-    
-    public static Set<PolicyBlock> load( InputStream policyStream, PolicyContext context ) throws PolicyException
-    {
-        Set<PolicyBlock> policies = new HashSet<PolicyBlock>();
-        KeyStore keystore = null;
-        
-        try
-        {
-            PolicyFileScanner loader = new PolicyFileScanner();
-            
-            Collection<GrantEntry> grantEntries = new ArrayList<GrantEntry>();
-            List<KeystoreEntry> keystoreEntries = new ArrayList<KeystoreEntry>();
-            
-            loader.scanStream( new InputStreamReader(policyStream), grantEntries, keystoreEntries );
-            
-            for ( Iterator<KeystoreEntry> i = keystoreEntries.iterator(); i.hasNext();)
-            {
-                KeystoreEntry node = i.next();
-                node.expand( context );
-                
-                keystore = node.toKeyStore();
-                
-                if ( keystore != null )
-                {
-                    // we only process the first valid keystore
-                    context.setKeystore( keystore );
-                    break;
-                }
-            }
-            
-            for ( Iterator<GrantEntry> i = grantEntries.iterator(); i.hasNext(); )
-            {            
-                GrantEntry grant = i.next();
-                grant.expand( context );
-                
-                PolicyBlock policy = new PolicyBlock();             
-                
-                policy.setCodeSource( grant.getCodeSource() );
-                policy.setPrincipals( grant.getPrincipals() );
-                policy.setPermissions( grant.getPermissions() );
-                
-                policies.add(policy);
-            }      
-            
-            return policies;
-        }
-        catch ( Exception e )
-        {
-            throw new PolicyException( e );
-        }
-    }
-}
-
-
-
-
-
diff --git a/jetty-policy/src/main/java/org/eclipse/jetty/policy/loader/PolicyFileScanner.java b/jetty-policy/src/main/java/org/eclipse/jetty/policy/loader/PolicyFileScanner.java
deleted file mode 100644
index be7853f..0000000
--- a/jetty-policy/src/main/java/org/eclipse/jetty/policy/loader/PolicyFileScanner.java
+++ /dev/null
@@ -1,459 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//  This file adapted for use from Apache Harmony code by written and contributed 
-//  to that project by Alexey V. Varlamov under the ASL-2.0
-//  See CQ3380
-//  ========================================================================
-
-package org.eclipse.jetty.policy.loader;
-
-
-import java.io.IOException;
-import java.io.Reader;
-import java.io.StreamTokenizer;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.List;
-
-import org.eclipse.jetty.policy.entry.GrantEntry;
-import org.eclipse.jetty.policy.entry.KeystoreEntry;
-import org.eclipse.jetty.policy.entry.PermissionEntry;
-import org.eclipse.jetty.policy.entry.PrincipalEntry;
-
-
-/**
- * This is a basic high-level tokenizer of policy files. It takes in a stream, analyzes data read from it and returns a
- * set of structured tokens. <br>
- * This implementation recognizes text files, consisting of clauses with the following syntax:
- * 
- * <pre>
- * 
- *     keystore "some_keystore_url", "keystore_type";
- * 
- * </pre>
- * 
- * <pre>
- * 
- *     grant [SignedBy "signer_names"] [, CodeBase "URL"]
- *      [, Principal [principal_class_name] "principal_name"]
- *      [, Principal [principal_class_name] "principal_name"] ... {
- *      permission permission_class_name [ "target_name" ] [, "action"] 
- *      [, SignedBy "signer_names"];
- *      permission ...
- *      };
- * 
- * </pre>
- * 
- * For semantical details of this format, see org.apache.harmony.security.DefaultPolicy javadoc. <br>
- * 
- * Keywords are case-insensitive in contrast to quoted string literals. Comma-separation rule is quite forgiving, most
- * commas may be just omitted. Whitespaces, line- and block comments are ignored. Symbol-level tokenization is delegated
- * to java.io.StreamTokenizer. <br>
- * <br>
- * This implementation is effectively thread-safe, as it has no field references to data being processed (that is,
- * passes all the data as method parameters).
- * 
- * This implementation is a bit more strict in enforcing format then the default policy scanner as implemented in the sun jdk.
- */
-public class PolicyFileScanner
-{
-
-    /**
-     * Specific exception class to signal policy file syntax error.
-     */
-    public static class InvalidFormatException
-        extends Exception
-    {
-
-        /**
-         * @serial
-         */
-        private static final long serialVersionUID = 5789786270390222184L;
-
-        /**
-         * Constructor with detailed message parameter.
-         */
-        public InvalidFormatException( String arg0 )
-        {
-            super( arg0 );
-        }
-    }
-
-    /**
-     * Configures passed tokenizer accordingly to supported syntax.
-     */
-    protected StreamTokenizer configure( StreamTokenizer st )
-    {
-        st.slashSlashComments( true );
-        st.slashStarComments( true );
-        st.wordChars( '_', '_' );
-        st.wordChars( '$', '$' );
-        return st;
-    }
-
-    /**
-     * Performs the main parsing loop. Starts with creating and configuring a StreamTokenizer instance; then tries to
-     * recognize <i>keystore </i> or <i>grant </i> keyword. When found, invokes read method corresponding to the clause
-     * and collects result to the passed collection.
-     * 
-     * @param r policy stream reader
-     * @param grantEntries a collection to accumulate parsed GrantEntries
-     * @param keystoreEntries a collection to accumulate parsed KeystoreEntries
-     * @throws IOException if stream reading failed
-     * @throws InvalidFormatException if unexpected or unknown token encountered
-     */
-    public void scanStream( Reader r, Collection<GrantEntry> grantEntries, List<KeystoreEntry> keystoreEntries )
-        throws IOException, InvalidFormatException
-    {
-        StreamTokenizer st = configure( new StreamTokenizer( r ) );
-        // main parsing loop
-        parsing: while ( true )
-        {
-            switch ( st.nextToken() )
-            {
-                case StreamTokenizer.TT_EOF: // we've done the job
-                    break parsing;
-
-                case StreamTokenizer.TT_WORD:
-                    if ( Util.equalsIgnoreCase( "keystore", st.sval ) ) { //$NON-NLS-1$
-                        keystoreEntries.add( readKeystoreNode( st ) );
-                    }
-                    else if ( Util.equalsIgnoreCase( "grant", st.sval ) ) { //$NON-NLS-1$
-                        grantEntries.add( readGrantNode( st ) );
-                    }
-                    else
-                    {
-                        handleUnexpectedToken( st, "Expected entries are : \"grant\" or \"keystore\"" ); //$NON-NLS-1$
-
-                    }
-                    break;
-
-                case ';': // just delimiter of entries
-                    break;
-
-                default:
-                    handleUnexpectedToken( st );
-                    break;
-            }
-        }
-    }
-
-    /**
-     * Tries to read <i>keystore </i> clause fields. The expected syntax is
-     * 
-     * <pre>
-     * 
-     *     "some_keystore_url"[, "keystore_type"];
-     * 
-     * </pre>
-     * 
-     * @return successfully parsed KeystoreNode
-     * @throws IOException if stream reading failed
-     * @throws InvalidFormatException if unexpected or unknown token encountered
-     */
-    protected KeystoreEntry readKeystoreNode( StreamTokenizer st )
-        throws IOException, InvalidFormatException
-    {
-        KeystoreEntry ke = new KeystoreEntry();
-        if ( st.nextToken() == '"' )
-        {
-            ke.setUrl( st.sval );
-            if ( ( st.nextToken() == '"' ) || ( ( st.ttype == ',' ) && ( st.nextToken() == '"' ) ) )
-            {
-                ke.setType( st.sval );
-            }
-            else
-            { // handle token in the main loop
-                st.pushBack();
-            }
-        }
-        else
-        {
-            handleUnexpectedToken( st, "Expected syntax is : keystore \"url\"[, \"type\"]" ); //$NON-NLS-1$
-
-        }
-        return ke;
-    }
-
-    /**
-     * Tries to read <i>grant </i> clause. <br>
-     * First, it reads <i>codebase </i>, <i>signedby </i>, <i>principal </i> entries till the '{' (opening curly brace)
-     * symbol. Then it calls readPermissionEntries() method to read the permissions of this clause. <br>
-     * Principal entries (if any) are read by invoking readPrincipalNode() method, obtained PrincipalEntries are
-     * accumulated. <br>
-     * The expected syntax is
-     * 
-     * <pre>
-     * 
-     *     [ [codebase "url"] | [signedby "name1,...,nameN"] | 
-     *          principal ...] ]* { ... }
-     * 
-     * </pre>
-     * 
-     * @return successfully parsed GrantNode
-     * @throws IOException if stream reading failed
-     * @throws InvalidFormatException if unexpected or unknown token encountered
-     */
-    protected GrantEntry readGrantNode( StreamTokenizer st )
-        throws IOException, InvalidFormatException
-    {
-        GrantEntry ge = new GrantEntry();
-        parsing: while ( true )
-        {
-            switch ( st.nextToken() )
-            {
-
-                case StreamTokenizer.TT_WORD:
-                    if ( Util.equalsIgnoreCase( "signedby", st.sval ) ) { //$NON-NLS-1$
-                        if ( st.nextToken() == '"' )
-                        {
-                            ge.setSigners( st.sval );
-                        }
-                        else
-                        {
-                            handleUnexpectedToken( st, "Expected syntax is : signedby \"name1,...,nameN\"" ); //$NON-NLS-1$
-                        }
-                    }
-                    else if ( Util.equalsIgnoreCase( "codebase", st.sval ) ) { //$NON-NLS-1$
-                        if ( st.nextToken() == '"' )
-                        {
-                            ge.setCodebase( st.sval );
-                        }
-                        else
-                        {
-                            handleUnexpectedToken( st, "Expected syntax is : codebase \"url\"" ); //$NON-NLS-1$
-                        }
-                    }
-                    else if ( Util.equalsIgnoreCase( "principal", st.sval ) ) { //$NON-NLS-1$
-                        ge.addPrincipal( readPrincipalNode( st ) );
-                    }
-                    else
-                    {
-                        handleUnexpectedToken( st );
-                    }
-                    break;
-
-                case ',': // just delimiter of entries
-                    break;
-
-                case '{':
-                    ge.setPermissions( readPermissionEntries( st ) );
-                    break parsing;
-
-                default: // handle token in the main loop
-                    st.pushBack();
-                    break parsing;
-            }
-        }
-
-        return ge;
-    }
-
-    /**
-     * Tries to read <i>Principal </i> Node fields. The expected syntax is
-     * 
-     * <pre>
-     * 
-     *     [ principal_class_name ] "principal_name"
-     * 
-     * </pre>
-     * 
-     * Both class and name may be wildcards, wildcard names should not surrounded by quotes.
-     * 
-     * @return successfully parsed PrincipalNode
-     * @throws IOException if stream reading failed
-     * @throws InvalidFormatException if unexpected or unknown token encountered
-     */
-    protected PrincipalEntry readPrincipalNode( StreamTokenizer st )
-        throws IOException, InvalidFormatException
-    {
-        PrincipalEntry pe = new PrincipalEntry();
-        if ( st.nextToken() == StreamTokenizer.TT_WORD )
-        {
-            pe.setKlass( st.sval );
-            st.nextToken();
-        }
-        else if ( st.ttype == '*' )
-        {
-            pe.setKlass( PrincipalEntry.WILDCARD );
-            st.nextToken();
-        }
-        if ( st.ttype == '"' )
-        {
-            pe.setName( st.sval );
-        }
-        else if ( st.ttype == '*' )
-        {
-            pe.setName( PrincipalEntry.WILDCARD );
-        }
-        else
-        {
-            handleUnexpectedToken( st, "Expected syntax is : principal [class_name] \"principal_name\"" ); //$NON-NLS-1$
-        }
-        return pe;
-    }
-
-    /**
-     * Tries to read a list of <i>permission </i> entries. The expected syntax is
-     * 
-     * <pre>
-     * 
-     *     permission permission_class_name
-     *          [ "target_name" ] [, "action_list"]
-     *          [, signedby "name1,name2,..."];
-     * 
-     * </pre>
-     * 
-     * List is terminated by '}' (closing curly brace) symbol.
-     * 
-     * @return collection of successfully parsed PermissionEntries
-     * @throws IOException if stream reading failed
-     * @throws InvalidFormatException if unexpected or unknown token encountered
-     */
-    protected Collection<PermissionEntry> readPermissionEntries( StreamTokenizer st )
-        throws IOException, InvalidFormatException
-    {
-        Collection<PermissionEntry> permissions = new HashSet<PermissionEntry>();
-        parsing: while ( true )
-        {
-            switch ( st.nextToken() )
-            {
-
-                case StreamTokenizer.TT_WORD:
-                    if ( Util.equalsIgnoreCase( "permission", st.sval ) ) { //$NON-NLS-1$
-                        PermissionEntry pe = new PermissionEntry();
-                        if ( st.nextToken() == StreamTokenizer.TT_WORD )
-                        {
-                            pe.setKlass( st.sval );
-                            if ( st.nextToken() == '"' )
-                            {
-                                pe.setName( st.sval );
-                                st.nextToken();
-                            }
-                            if ( st.ttype == ',' )
-                            {
-                                st.nextToken();
-                            }
-                            if ( st.ttype == '"' )
-                            {
-                                pe.setActions( st.sval );
-                                if ( st.nextToken() == ',' )
-                                {
-                                    st.nextToken();
-                                }
-                            }
-                            if ( st.ttype == StreamTokenizer.TT_WORD && Util.equalsIgnoreCase( "signedby", st.sval ) ) { //$NON-NLS-1$
-                                if ( st.nextToken() == '"' )
-                                {
-                                    pe.setSigners( st.sval );
-                                }
-                                else
-                                {
-                                    handleUnexpectedToken( st );
-                                }
-                            }
-                            else
-                            { // handle token in the next iteration
-                                st.pushBack();
-                            }
-                            permissions.add( pe );
-                            continue parsing;
-                        }
-                    }
-                    handleUnexpectedToken(
-                                           st,
-                                           "Expected syntax is : permission permission_class_name [\"target_name\"] [, \"action_list\"] [, signedby \"name1,...,nameN\"]" ); //$NON-NLS-1$
-                    break;
-
-                case ';': // just delimiter of entries
-                    break;
-
-                case '}': // end of list
-                    break parsing;
-
-                default: // invalid token
-                    handleUnexpectedToken( st );
-                    break;
-            }
-        }
-
-        return permissions;
-    }
-
-    /**
-     * Formats a detailed description of tokenizer status: current token, current line number, etc.
-     */
-    protected String composeStatus( StreamTokenizer st )
-    {
-        return st.toString();
-    }
-
-    /**
-     * Throws InvalidFormatException with detailed diagnostics.
-     * 
-     * @param st a tokenizer holding the erroneous token
-     * @param message a user-friendly comment, probably explaining expected syntax. Should not be <code>null</code>- use
-     *            the overloaded single-parameter method instead.
-     */
-    protected final void handleUnexpectedToken( StreamTokenizer st, String message )
-        throws InvalidFormatException
-    {
-        throw new InvalidFormatException( "Unexpected token encountered: " + composeStatus( st ) + ". " + message );
-    }
-
-    /**
-     * Throws InvalidFormatException with error status: which token is unexpected on which line.
-     * 
-     * @param st a tokenizer holding the erroneous token
-     */
-    protected final void handleUnexpectedToken( StreamTokenizer st )
-        throws InvalidFormatException
-    {
-        throw new InvalidFormatException( "Unexpected token encountered: " + composeStatus( st ) );
-    }
-
-
-    private static class Util
-    {
-        public static String toUpperCase( String s )
-        {
-            int len = s.length();
-            StringBuilder buffer = new StringBuilder( len );
-            for ( int i = 0; i < len; i++ )
-            {
-                char c = s.charAt( i );
-                if ( 'a' <= c && c <= 'z' )
-                {
-                    buffer.append( (char) ( c - ( 'a' - 'A' ) ) );
-                }
-                else
-                {
-                    buffer.append( c );
-                }
-            }
-            return buffer.toString();
-        }
-
-        public static boolean equalsIgnoreCase( String s1, String s2 )
-        {
-            s1 = toUpperCase( s1 );
-            s2 = toUpperCase( s2 );
-            return s1.equals( s2 );
-        }
-    }
-
-}
diff --git a/jetty-policy/src/test/java/org/eclipse/jetty/policy/JettyPolicyRuntimeTest.java b/jetty-policy/src/test/java/org/eclipse/jetty/policy/JettyPolicyRuntimeTest.java
deleted file mode 100644
index 4192aa4..0000000
--- a/jetty-policy/src/test/java/org/eclipse/jetty/policy/JettyPolicyRuntimeTest.java
+++ /dev/null
@@ -1,265 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.policy;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import java.io.File;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.net.URL;
-import java.net.URLClassLoader;
-import java.security.AccessControlException;
-import java.security.Policy;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Set;
-
-import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
-import org.eclipse.jetty.toolchain.test.OS;
-import org.eclipse.jetty.util.IO;
-import org.junit.After;
-import org.junit.Assume;
-import org.junit.Before;
-import org.junit.Test;
-
-public class JettyPolicyRuntimeTest
-{
-    private HashMap<String, String> evaluator = new HashMap<String, String>();
-
-    @Before
-    public void init() throws Exception
-    {
-        System.setSecurityManager(null);
-        Policy.setPolicy(null);
-
-        evaluator.put("jetty.home",MavenTestingUtils.getBaseURI().toASCIIString());
-        evaluator.put("basedir",MavenTestingUtils.getBaseURI().toASCIIString());
-    }
-
-    @After
-    public void destroy() throws Exception
-    {
-        System.setSecurityManager(null);
-        Policy.setPolicy(null);
-        IO.delete(new File ("/tmp", "foo"));
-    }
-
-    @Test
-    public void testSimplePolicyReplacement() throws Exception
-    {
-    	Assume.assumeTrue(!OS.IS_WINDOWS); // Ignore test if running under windows.
-        JettyPolicy ap = new JettyPolicy(MavenTestingUtils.getTestResourceDir("runtime-test-1").getAbsolutePath(), evaluator);
-        ap.refresh();
-
-        Policy.setPolicy( ap );
-        System.setSecurityManager( new SecurityManager() );
-
-        File test = new File( "/tmp" );
-
-        assertTrue( test.canRead() );
-    }
-
-    @Test
-    public void testRepeatedPolicyReplacement() throws Exception
-    {
-    	Assume.assumeTrue(!OS.IS_WINDOWS); // Ignore test if running under windows.
-        JettyPolicy ap = new JettyPolicy(MavenTestingUtils.getTestResourceDir("runtime-test-2/a").getAbsolutePath(),evaluator);
-        ap.refresh();
-
-        Policy.setPolicy( ap );
-        System.setSecurityManager( new SecurityManager() );
-
-        // Test that the all permission policy allows us to do this
-        try
-        {
-            File test3 = new File( "/tmp/foo/bar/do" );
-            test3.mkdirs();
-            test3.delete();
-        }
-        catch ( AccessControlException ace )
-        {
-            ace.printStackTrace(System.err);
-            fail("Should NOT have thrown an AccessControlException");
-        }
-
-        JettyPolicy ap2 = new JettyPolicy(MavenTestingUtils.getTestResourceDir("runtime-test-2/b").getAbsolutePath(),evaluator);
-        ap2.refresh();
-
-        Policy.setPolicy( ap2 );
-
-        // Test that the new policy does replace the old one and we are now not allowed
-        try
-        {
-            File test3 = new File( "/tmp/foo/bar/do" );
-            test3.mkdirs();
-
-            fail("Should have thrown an AccessControlException");
-        }
-        catch ( AccessControlException ace )
-        {
-            // Expected Path
-        }
-    }
-
-    @Test
-    public void testPolicyRestrictive() throws Exception
-    {
-        // TODO - temporary, create alternate file to load for windows
-    	Assume.assumeTrue(!OS.IS_WINDOWS); // Ignore test if running under windows.
-
-        JettyPolicy ap = new JettyPolicy(MavenTestingUtils.getTestResourceDir("runtime-test-3").getAbsolutePath(),evaluator);
-        ap.refresh();
-
-        Policy.setPolicy( ap );
-        System.setSecurityManager( new SecurityManager() );
-
-        File test = new File( "/tmp" );
-
-        assertTrue ( test.canRead() );
-
-        File test2 = new File( "/tmp/foo" );
-        test2.mkdirs();
-        assertTrue ( test2.canRead() );
-
-        try
-        {
-            File test3 = new File("/tmp/foo/bar/do");
-            test3.mkdirs();
-
-            fail("Should have thrown an AccessControlException");
-        }
-        catch (AccessControlException ace)
-        {
-            // Expected Path
-        }
-    }
-
-    @Test
-    public void testCertificateLoader() throws Exception
-    {
-        // TODO - temporary, create alternate file to load for windows
-    	Assume.assumeTrue(!OS.IS_WINDOWS); // Ignore test if running under windows.
-
-        JettyPolicy ap = new JettyPolicy(MavenTestingUtils.getTestResourceDir("runtime-test-4").getAbsolutePath(),evaluator);
-        ap.refresh();
-
-    
-        URL url = MavenTestingUtils.getTargetURL("test-policy/jetty-test-policy.jar");
-
-        //System.out.println(url.toURI().toASCIIString());
-        //System.out.println(MavenTestingUtils.getBaseURI().toASCIIString());
-
-        URLClassLoader loader ;
-        if (Thread.currentThread().getContextClassLoader() != null )
-        {
-            loader = new URLClassLoader( new URL[]{ url }, Thread.currentThread().getContextClassLoader() );
-        }
-        else
-        {
-            loader = new URLClassLoader( new URL[]{ url }, ClassLoader.getSystemClassLoader() );
-        }
-
-        Thread.currentThread().setContextClassLoader(loader);
-
-        Policy.setPolicy( ap );
-        System.setSecurityManager( new SecurityManager() );
-
-        
-        ap.refresh();
-
-        ap.dump(System.out);
-
-        
-        Class<?> clazz = loader.loadClass("org.eclipse.jetty.toolchain.test.policy.Tester");
-
-        Method m = clazz.getMethod("testEcho",new Class[]
-        { String.class });
-
-        String foo = (String)m.invoke(clazz.newInstance(), "foo");
-
-        assertEquals("foo",foo);
-
-        Method m2 = clazz.getMethod("testReadSystemProperty",new Class[]
-        { String.class });
-
-        m2.invoke(clazz.newInstance(), "foo");
-
-        assertTrue("system property access was granted",true);
-
-        // ap.dump(System.out);
-    }
-
-    @Test
-    public void testBadCertificateLoader() throws Exception
-    {
-        // TODO - temporary, create alternate file to load for windows
-    	Assume.assumeTrue(!OS.IS_WINDOWS); // Ignore test if running under windows.
-
-        JettyPolicy ap = new JettyPolicy(MavenTestingUtils.getTestResourceDir("runtime-test-5").getAbsolutePath(),evaluator);
-        ap.refresh();
-
-        Policy.setPolicy( ap );
-        System.setSecurityManager( new SecurityManager() );
-
-        URL url = MavenTestingUtils.getTargetURL("test-policy/jetty-test-policy.jar");
-
-        URLClassLoader loader ;
-        if (Thread.currentThread().getContextClassLoader() != null )
-        {
-            loader = new URLClassLoader( new URL[]{ url }, Thread.currentThread().getContextClassLoader() );
-        }
-        else
-        {
-            loader = new URLClassLoader( new URL[]{ url }, ClassLoader.getSystemClassLoader() );
-        }
-
-        Thread.currentThread().setContextClassLoader(loader);
-
-        ap.refresh();
-
-        try
-        {
-            Class<?> clazz = loader.loadClass("org.eclipse.jetty.toolchain.test.policy.Tester");
-
-            Method m = clazz.getMethod( "testEcho", new Class[] {String.class} );
-
-            String foo = (String)m.invoke( clazz.newInstance(), "foo");
-
-            assertEquals("foo", foo );
-
-            Method m2 = clazz.getMethod( "testReadSystemProperty", new Class[] {String.class} );
-
-            m2.invoke(clazz.newInstance(), "foobar");
-
-            fail("Should have thrown an InvocationTargetException");
-        }
-        catch ( InvocationTargetException e )
-        {
-            assertTrue(e.getCause().getMessage().contains( "access denied" ));
-        }
-    }
-
-    private Set<String> getSinglePolicy(String name)
-    {
-        return Collections.singleton(MavenTestingUtils.getTestResourceFile(name).getAbsolutePath());
-    }
-}
diff --git a/jetty-policy/src/test/java/org/eclipse/jetty/policy/JettyPolicyTest.java b/jetty-policy/src/test/java/org/eclipse/jetty/policy/JettyPolicyTest.java
deleted file mode 100644
index 865ad15..0000000
--- a/jetty-policy/src/test/java/org/eclipse/jetty/policy/JettyPolicyTest.java
+++ /dev/null
@@ -1,424 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.policy;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-
-import java.io.FilePermission;
-import java.net.URL;
-import java.security.CodeSource;
-import java.security.Permission;
-import java.security.PermissionCollection;
-import java.security.ProtectionDomain;
-import java.security.cert.Certificate;
-import java.util.HashMap;
-import java.util.PropertyPermission;
-
-import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
-import org.junit.Before;
-import org.junit.Test;
-
-public class JettyPolicyTest
-{
-    private HashMap<String, String> evaluator = new HashMap<String, String>();
-
-    @Before
-    public void setUp() throws Exception
-    {
-        evaluator.put("jetty.home",MavenTestingUtils.getBaseURI().toASCIIString());
-        evaluator.put("basedir",MavenTestingUtils.getBaseURI().toASCIIString());
-    }
-
-
-    /**
-     * Simple test for loading a policy file and validating that the AllPermission
-     * was granted successfully.
-     */
-    @Test
-    public void testGlobalAllPermissionLoader() throws Exception
-    {
-        JettyPolicy ap = new JettyPolicy(  MavenTestingUtils.getTestResourceDir("policy-test-1").getAbsolutePath(), evaluator );
-
-        ap.refresh();
-
-        PermissionCollection pc = ap.getPermissions(new ProtectionDomain(null,null));
-
-        assertNotNull(pc);
-
-        Permission testPerm = new FilePermission("/tmp","read");
-
-        assertTrue(pc.implies(testPerm));
-
-    }
-
-    /** 
-     * Simple test of loading a policy file with a single codebase defined that grants specific 
-     * FilePermission.  Then test that read and write were granted but delete was not.
-     */
-    @Test
-    public void testSingleCodebaseFilePermissionLoader() throws Exception
-    {
-        JettyPolicy ap = new JettyPolicy(  MavenTestingUtils.getTestResourceDir("policy-test-2").getAbsolutePath(), evaluator );
-        
-        ap.refresh();
-
-        URL url = new URL("file:///foo.jar");
-        CodeSource cs = new CodeSource(url,new Certificate[0]);
-
-        PermissionCollection pc = ap.getPermissions(cs);
-
-        assertNotNull(pc);
-
-        Permission testReadPerm = new FilePermission( "/tmp/*", "read" );
-        Permission testWritePerm = new FilePermission( "/tmp/*", "write" );
-        Permission testDeletePerm = new FilePermission( "/tmp/*", "delete" );
-
-        assertTrue( pc.implies( testReadPerm ) );
-        assertTrue( pc.implies( testWritePerm ) );  
-        assertFalse(pc.implies( testDeletePerm ) );
-
-    }
-
-    /**
-     * Tests multiple codebases in a single policy file are loaded correctly and that the various 
-     * grants do indeed work accordingly
-     * 
-     * @throws Exception
-     */
-    @Test
-    public void testMultipleCodebaseFilePermissionLoader() throws Exception
-    {
-        JettyPolicy ap = new JettyPolicy(  MavenTestingUtils.getTestResourceDir("policy-test-3").getAbsolutePath(), evaluator );
-
-        ap.refresh();
-        
-        // test the bar.jar codebase grant
-        URL url = new URL("file:///bar.jar");
-        CodeSource cs = new CodeSource(url,new Certificate[0]);
-
-        PermissionCollection barPermissionCollection = ap.getPermissions(cs);
-
-        assertNotNull( barPermissionCollection );
-
-        Permission testBarPerm = new FilePermission("/tmp/*","read,write");
-        Permission testBarPerm2 = new FilePermission("/usr/*","read"); // only read was granted
-        Permission testBarPerm3 = new FilePermission("/usr/*","write"); // only read was granted
-
-        assertTrue( barPermissionCollection.implies( testBarPerm ) );
-        assertTrue( barPermissionCollection.implies( testBarPerm2 ) );
-        assertFalse( barPermissionCollection.implies( testBarPerm3 ) );
-        
-        // test the global permission grant
-        PermissionCollection globalPermissionCollection = ap.getPermissions( new ProtectionDomain( null, null ) );
-        
-        assertNotNull( globalPermissionCollection );
-        
-        Permission testPropertyPermission = new PropertyPermission("main.class","read");
-        assertTrue( globalPermissionCollection.implies(testPropertyPermission));
-        // its global so it ought to be global, double check that
-        assertTrue( barPermissionCollection.implies(testPropertyPermission));
-        
-        // test the foo.jar codebase grant
-        URL fooUrl = new URL( "file:///foo.jar" );
-        CodeSource fooCodeSource = new CodeSource( fooUrl, new Certificate[0]);
-
-        PermissionCollection fooPermissionCollection = ap.getPermissions( fooCodeSource );
-
-        assertNotNull( fooPermissionCollection );
-        
-        Permission testFooPerm = new FilePermission( "/tmp/*", "read,write" );
-        Permission testFooPerm2 = new FilePermission( "/tmp/*", "read,write,delete" );
-
-        assertTrue( fooPermissionCollection.implies(testFooPerm) );
-        assertFalse( fooPermissionCollection.implies(testFooPerm2) );
-
-        // make sure that the foo codebase isn't getting bar permissions
-        assertFalse( fooPermissionCollection.implies(testBarPerm2) );
-        // but make sure that foo codebase is getting global
-        assertTrue( fooPermissionCollection.implies(testPropertyPermission));        
-    }
-
-    @Test
-    public void testMultipleCodebaseMixedPermissionLoader() throws Exception
-    {
-        JettyPolicy ap = new JettyPolicy(  MavenTestingUtils.getTestResourceDir("policy-test-4").getAbsolutePath(), evaluator );
-
-        ap.refresh();
-
-        // test the bar.jar codebase grant
-        URL url = new URL( "file:///bar.jar" );
-        CodeSource cs = new CodeSource( url, new Certificate[0]);
-
-        PermissionCollection barPermissionCollection = ap.getPermissions( cs );
-
-        assertNotNull( barPermissionCollection );
-
-        Permission testBarPerm = new FilePermission( "/tmp/*", "read,write" );
-        Permission testBarPerm2 = new FilePermission( "/usr/*", "read" );
-
-        assertTrue( barPermissionCollection.implies( testBarPerm ) );
-        assertTrue( barPermissionCollection.implies( testBarPerm2 ) );
-        
-        // test the global permission grant
-        PermissionCollection globalPermissionCollection = ap.getPermissions( new ProtectionDomain( null, null ) );
-        
-        assertNotNull( globalPermissionCollection );
-        
-        Permission testPropertyPermission = new PropertyPermission("main.class","read");
-        assertTrue( globalPermissionCollection.implies(testPropertyPermission));
-        // its global so it ought to be global, double check that
-        assertTrue( barPermissionCollection.implies(testPropertyPermission));
-        
-        // test the foo.jar codebase grant
-        URL fooUrl = new URL( "file:///foo.jar" );
-        CodeSource fooCodeSource = new CodeSource( fooUrl, new Certificate[0]);
-
-        PermissionCollection fooPermissionCollection = ap.getPermissions( fooCodeSource );
-
-        assertNotNull( fooPermissionCollection );
-        
-        Permission testFooPerm = new FilePermission( "/tmp/*", "read,write" );
-        Permission testFooPerm2 = new FilePermission( "/tmp/*", "read,write,delete" );
-
-        assertTrue( fooPermissionCollection.implies(testFooPerm) );
-        assertFalse( fooPermissionCollection.implies(testFooPerm2) );
-
-        // make sure that the foo codebase isn't getting bar permissions
-        assertFalse( fooPermissionCollection.implies(testBarPerm2) );
-        // but make sure that foo codebase is getting global
-        assertTrue( fooPermissionCollection.implies(testPropertyPermission));    
-
-    }
-
-    /**
-     * Sanity check that jetty policy file parses
-     * 
-     * TODO insert typical jetty requirements in here to test
-     * 
-     * @throws Exception
-     */
-    @Test
-    public void testSCLoader() throws Exception
-    {
-        JettyPolicy ap = new JettyPolicy(MavenTestingUtils.getProjectDir("src/main/config/lib/policy").getAbsolutePath(),evaluator);
-
-        ap.refresh();
-    }
-
-    /**
-     * Test the simple loading of multiple files with no overlapping of security permission code sources
-     * @throws Exception
-     */
-    @Test
-    public void testMultipleFilePermissionLoader() throws Exception
-    {
-        JettyPolicy ap = new JettyPolicy(  MavenTestingUtils.getTestResourceDir("policy-test-5").getAbsolutePath(), evaluator );
-
-        ap.refresh();
-
-        URL url = new URL("file:///bar.jar");
-        CodeSource cs = new CodeSource(url,new Certificate[0]);
-
-        PermissionCollection pc = ap.getPermissions(cs);
-
-        assertNotNull(pc);
-
-        Permission testPerm = new FilePermission("/tmp/*","read");
-        Permission testPerm2 = new FilePermission("/usr/*","write"); //
-
-        assertTrue(pc.implies(testPerm));
-        assertFalse(pc.implies(testPerm2));
-    }
-    
-    /**
-     * Tests the aggregation of multiple policy files into the same protection 
-     * domain of a granted codesource
-     * 
-     * @throws Exception
-     */
-    @Test
-    public void testAggregateMultipleFilePermissionLoader() throws Exception
-    {
-        JettyPolicy ap = new JettyPolicy(  MavenTestingUtils.getTestResourceDir("policy-test-6").getAbsolutePath(), evaluator );
-
-        ap.refresh();
-
-        URL url = new URL( "file:///bar.jar" );
-        CodeSource cs = new CodeSource( url, new Certificate[0]);
-
-        PermissionCollection pc = ap.getPermissions( cs );
-
-        assertNotNull( pc );
-
-        Permission testPerm = new FilePermission( "/tmp/*", "read, write" );
-        Permission testPerm2 = new FilePermission( "/usr/*", "write" );
-
-        // this tests that two policy files granting to the same codebase aggregate
-        // together their permissions, /tmp/* should be read, write after loading policy 2 and 3
-        assertTrue( pc.implies( testPerm ) );
-        assertFalse( pc.implies( testPerm2 ) );
-               
-    }
-    
-    
-    /**
-     * test the resolution of the loading of the policy files
-     * 
-     * @throws Exception
-     */
-//    @Test
-//    public void testPolicyDirectories() throws Exception
-//    {
-//        Set<String> files = new HashSet<String>();
-//
-//        files.add( MavenTestingUtils.getTestResourceFile("single-codebase-file-permission.policy").getAbsolutePath() );
-//        files.add( MavenTestingUtils.getTestResourceDir("context").getAbsolutePath() );
-//
-//        JettyPolicy ap = new JettyPolicy( files, evaluator );
-//
-//        Assert.assertEquals(3, ap.getKnownPolicyFiles().size());      
-//               
-//    }
-    
-//    /**
-//     * test the discovery and loading of template files
-//     * 
-//     * @throws Exception
-//     */
-//    @Test
-//    public void testTemplateDirectories() throws Exception
-//    {
-//        Set<String> policyFiles = new HashSet<String>();
-//        Set<String> templateFiles = new HashSet<String>();
-//
-//        policyFiles.add(MavenTestingUtils.getTestResourceFile("single-codebase-file-permission.policy").getAbsolutePath());
-//        policyFiles.add(MavenTestingUtils.getTestResourceDir("context").getAbsolutePath());
-//
-//        templateFiles.add(MavenTestingUtils.getTestResourceDir("template").getAbsolutePath());
-//
-//        JettyPolicy ap = new JettyPolicy(policyFiles,templateFiles,evaluator);
-//
-//        Assert.assertEquals(3,ap.getKnownPolicyFiles().size());
-//
-//        Assert.assertEquals(2,ap.getKnownTemplateFiles().size());
-//
-//    }
-//
-//    /**
-//     * tests the assigning of a template to a codesource
-//     * 
-//     * @throws Exception
-//     */
-//    @Test
-//    public void testTemplateAssign() throws Exception
-//    {
-//        Set<String> policyFiles = new HashSet<String>();
-//        Set<String> templateFiles = new HashSet<String>();
-//
-//        policyFiles.add(MavenTestingUtils.getTestResourceFile("single-codebase-file-permission.policy").getAbsolutePath());
-//        policyFiles.add(MavenTestingUtils.getTestResourceDir("context").getAbsolutePath());
-//
-//        templateFiles.add(MavenTestingUtils.getTestResourceDir("template").getAbsolutePath());
-//
-//        JettyPolicy ap = new JettyPolicy(policyFiles,templateFiles,evaluator);
-//
-//        ap.assignTemplate("file:///template.jar",new String[]
-//        { "template1", "template2" });
-//
-//        Assert.assertEquals(2,ap.getAssignedTemplates("file:///template.jar").length);
-//
-//    }
-//
-//    /**
-//     * tests the assigning of a template to a codesource
-//     * 
-//     * @throws Exception
-//     */
-//    @Test
-//    public void testTemplateRemove() throws Exception
-//    {
-//        Set<String> policyFiles = new HashSet<String>();
-//        Set<String> templateFiles = new HashSet<String>();
-//
-//        policyFiles.add(MavenTestingUtils.getTestResourceFile("single-codebase-file-permission.policy").getAbsolutePath());
-//        policyFiles.add(MavenTestingUtils.getTestResourceDir("context").getAbsolutePath());
-//
-//        templateFiles.add(MavenTestingUtils.getTestResourceDir("template").getAbsolutePath());
-//
-//        JettyPolicy ap = new JettyPolicy(policyFiles,templateFiles,evaluator);
-//
-//        ap.assignTemplate("file:///template.jar",new String[]
-//        { "template1", "template2" });
-//
-//        Assert.assertEquals(2,ap.getAssignedTemplates("file:///template.jar").length);
-//
-//        ap.unassignTemplates("file:///template.jar");
-//
-//        Assert.assertEquals(0,ap.getAssignedTemplates("file:///template.jar").length);
-//
-//    }
-//
-//    @Test
-//    public void testTemplatePermissions() throws Exception
-//    {
-//        Set<String> policyFiles = new HashSet<String>();
-//        Set<String> templateFiles = new HashSet<String>();
-//
-//        policyFiles.add(MavenTestingUtils.getTestResourceFile("single-codebase-file-permission.policy").getAbsolutePath());
-//        policyFiles.add(MavenTestingUtils.getTestResourceDir("context").getAbsolutePath());
-//
-//        templateFiles.add(MavenTestingUtils.getTestResourceDir("template").getAbsolutePath());
-//
-//        JettyPolicy ap = new JettyPolicy(policyFiles,templateFiles,evaluator);
-//        
-//        URL url = new URL("file:///template.jar");
-//        CodeSource cs = new CodeSource(url,new Certificate[0]);
-//
-//        PermissionCollection pc = ap.getPermissions(cs);
-//
-//        assertNotNull(pc);
-//
-//        Permission testPerm = new FilePermission("/tmp/*","read");
-//        Permission testPerm2 = new FilePermission("/tmp/*","write");
-//
-//        // no templates have been assigned
-//        assertFalse(pc.implies(testPerm));
-//
-//        ap.assignTemplate("file:///template.jar",new String[] {"template1"});
-//        
-//        PermissionCollection pc2 = ap.getPermissions(cs);
-//
-//        assertNotNull(pc2);
-//        
-//        assertTrue(pc2.implies(testPerm));
-//        assertFalse(pc2.implies(testPerm2));
-//        
-//        
-//        ap.assignTemplate("file:///template.jar",new String[] {"template1", "template2"});
-//        
-//        PermissionCollection pc3 = ap.getPermissions(cs);
-//
-//        assertNotNull(pc3);
-//        
-//        assertTrue(pc3.implies(testPerm));
-//        assertTrue(pc3.implies(testPerm2));
-//    }
-}
diff --git a/jetty-policy/src/test/java/org/eclipse/jetty/policy/PolicyContextTest.java b/jetty-policy/src/test/java/org/eclipse/jetty/policy/PolicyContextTest.java
deleted file mode 100644
index db1cb7c..0000000
--- a/jetty-policy/src/test/java/org/eclipse/jetty/policy/PolicyContextTest.java
+++ /dev/null
@@ -1,120 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.policy;
-
-import static org.junit.Assert.assertEquals;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.InputStreamReader;
-import java.security.Permission;
-import java.util.ArrayList;
-import java.util.List;
-
-import org.eclipse.jetty.policy.entry.GrantEntry;
-import org.eclipse.jetty.policy.entry.KeystoreEntry;
-import org.eclipse.jetty.policy.loader.PolicyFileScanner;
-import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
-import org.eclipse.jetty.toolchain.test.OS;
-import org.junit.Before;
-import org.junit.Test;
-
-public class PolicyContextTest
-{
-    public static final String __PRINCIPAL = "javax.security.auth.x500.X500Principal \"CN=Jetty Policy,OU=Artifact,O=Jetty Project,L=Earth,ST=Internet,C=US\"";
-
-    @Before
-    public void init() throws Exception
-    {
-        System.setProperty( "basedir", MavenTestingUtils.getBaseURI().toASCIIString() );
-    }
-
-    @Test
-    public void testSelfPropertyExpansion() throws Exception
-    {
-        PolicyContext context = new PolicyContext();
-        PolicyFileScanner loader = new PolicyFileScanner();
-        List<GrantEntry> grantEntries = new ArrayList<GrantEntry>();
-        List<KeystoreEntry> keystoreEntries = new ArrayList<KeystoreEntry>();
-
-        File policyFile = MavenTestingUtils.getTestResourceFile("context/jetty-certificate.policy");
-
-        loader.scanStream( new InputStreamReader( new FileInputStream( policyFile ) ), grantEntries, keystoreEntries );
-
-        if ( !OS.IS_WINDOWS ) //temporary, create alternate file to load for windows
-        {
-            for (KeystoreEntry node : keystoreEntries)
-            {
-                node.expand(context);
-
-                context.setKeystore(node.toKeyStore());
-            }
-
-            GrantEntry grant = grantEntries.get( 0 );
-            grant.expand( context );
-
-            Permission perm = grant.getPermissions().elements().nextElement();
-
-            assertEquals( __PRINCIPAL, perm.getName() );
-        }
-    }
-
-    @Test
-    public void testAliasPropertyExpansion() throws Exception
-    {
-        PolicyContext context = new PolicyContext();
-        PolicyFileScanner loader = new PolicyFileScanner();
-        List<GrantEntry> grantEntries = new ArrayList<GrantEntry>();
-        List<KeystoreEntry> keystoreEntries = new ArrayList<KeystoreEntry>();
-
-        File policyFile = MavenTestingUtils.getTestResourceFile("context/jetty-certificate-alias.policy");
-
-        loader.scanStream( new InputStreamReader( new FileInputStream( policyFile ) ), grantEntries, keystoreEntries );
-
-        if ( !OS.IS_WINDOWS ) //temporary, create alternate file to load for windows
-        {
-            for (KeystoreEntry node : keystoreEntries)
-            {
-                node.expand(context);
-
-                context.setKeystore(node.toKeyStore());
-            }
-
-            GrantEntry grant = grantEntries.get( 0 );
-            grant.expand( context );
-
-            Permission perm = grant.getPermissions().elements().nextElement();
-
-            assertEquals( __PRINCIPAL, perm.getName() );
-        }
-    }
-
-    @Test
-    public void testFileSeparatorExpansion() throws Exception
-    {
-        PolicyContext context = new PolicyContext();
-        context.addProperty( "foo", "bar" );
-
-        assertEquals(File.separator, context.evaluate( "${/}" ) );
-
-        assertEquals(File.separator + "bar" + File.separator, context.evaluate( "${/}${foo}${/}" ) );
-
-        assertEquals(File.separator + File.separator, context.evaluate( "${/}${/}" ) );
-    }
-}
diff --git a/jetty-policy/src/test/java/org/eclipse/jetty/policy/PolicyMonitorTest.java b/jetty-policy/src/test/java/org/eclipse/jetty/policy/PolicyMonitorTest.java
deleted file mode 100644
index 4cbe62e..0000000
--- a/jetty-policy/src/test/java/org/eclipse/jetty/policy/PolicyMonitorTest.java
+++ /dev/null
@@ -1,138 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.policy;
-
-import java.io.File;
-import java.util.HashMap;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import junit.framework.Assert;
-
-import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
-import org.eclipse.jetty.toolchain.test.OS;
-import org.junit.Before;
-import org.junit.Test;
-
-public class PolicyMonitorTest
-{    
-    
-    private HashMap<String, String> evaluator = new HashMap<String, String>();
-
-    @Before
-    public void init() throws Exception
-    {
-        System.setProperty( "basedir", MavenTestingUtils.getBaseURI().toASCIIString() );
-    }
-    
-    @Test
-    public void testSimpleLoading() throws Exception
-    {
-        final AtomicInteger count = new AtomicInteger(0);
-        
-        PolicyMonitor monitor = new PolicyMonitor(new File(MavenTestingUtils.getTargetDir(),
-                "test-classes/monitor-test-1").getAbsolutePath())
-        {
-            
-            @Override
-            public void onPolicyChange(PolicyBlock grant)
-            {
-                count.incrementAndGet();
-            }
-        };
-        monitor.setScanInterval(1);
-        
-        monitor.start();
-        
-        while (!monitor.isInitialized() )
-        {
-            Thread.sleep(100);
-        }
-
-        Assert.assertEquals(1,count.get());
-        monitor.stop();
-    }
-    
-    @Test
-    public void testSimpleReloading() throws Exception
-    {
-        if (OS.IS_WINDOWS)
-        {
-            return;
-        }
-        
-        final AtomicInteger count = new AtomicInteger(0);
-        
-        PolicyMonitor monitor = new PolicyMonitor(new File(MavenTestingUtils.getTargetDir(),
-                "test-classes/monitor-test-2").getAbsolutePath())
-        {       
-            @Override
-            public void onPolicyChange(PolicyBlock grant)
-            {
-                count.incrementAndGet();
-            }
-        };
-
-        monitor.setScanInterval(1);
-        
-        monitor.start();
-        monitor.waitForScan();
-        monitor.waitForScan();
-
-        File permFile =new File(MavenTestingUtils.getTargetDir(),
-                "test-classes/monitor-test-2/global-all-permission.policy");
-        
-	    // Wait so that time is definitely different
-        monitor.waitForScan();
-        permFile.setLastModified(System.currentTimeMillis());
-                        
-        monitor.waitForScan();
-        monitor.waitForScan();
-
-        Assert.assertEquals(2,count.get());
-        monitor.stop();
-    }
-    
-    @Test
-    public void testLoading() throws Exception
-    {
-        final AtomicInteger count = new AtomicInteger(0);
-        
-        PolicyMonitor monitor = new PolicyMonitor(new File(MavenTestingUtils.getTargetDir(),
-            "test-classes/monitor-test-3").getAbsolutePath())
-        {       
-            @Override
-            public void onPolicyChange(PolicyBlock grant)
-            {
-                count.incrementAndGet();
-            }
-        };
-        
-        monitor.setScanInterval(1);
-        
-        monitor.start();
-        
-        while (! monitor.isInitialized() )
-        {
-            Thread.sleep(100);
-        }
-        
-        Assert.assertEquals(16,count.get());
-        monitor.stop();
-    }
-}
diff --git a/jetty-policy/src/test/resources/context/jetty-certificate-alias.policy b/jetty-policy/src/test/resources/context/jetty-certificate-alias.policy
deleted file mode 100644
index d7e53c4..0000000
--- a/jetty-policy/src/test/resources/context/jetty-certificate-alias.policy
+++ /dev/null
@@ -1,6 +0,0 @@
-keystore "${basedir}/target/test-policy/jetty-policy.keystore", "jks";
-
-grant signedBy "jetty-policy-bad", codeBase "${basedir}/target/test-policy/jetty-test-policy-1.0-SNAPSHOT.jar"
-{
-  permission java.util.PropertyPermission "${{alias:jetty-policy}}", "read";
-};
diff --git a/jetty-policy/src/test/resources/context/jetty-certificate.policy b/jetty-policy/src/test/resources/context/jetty-certificate.policy
deleted file mode 100644
index c583d78..0000000
--- a/jetty-policy/src/test/resources/context/jetty-certificate.policy
+++ /dev/null
@@ -1,6 +0,0 @@
-keystore "${basedir}/target/test-policy/jetty-policy.keystore", "jks";
-
-grant principal "jetty-policy"
-{
-  permission java.util.PropertyPermission "${{self}}", "read";
-};
diff --git a/jetty-policy/src/test/resources/monitor-test-1/global-all-permission.policy b/jetty-policy/src/test/resources/monitor-test-1/global-all-permission.policy
deleted file mode 100644
index ae56d98..0000000
--- a/jetty-policy/src/test/resources/monitor-test-1/global-all-permission.policy
+++ /dev/null
@@ -1,3 +0,0 @@
-grant {
-  permission java.security.AllPermission;
-}
\ No newline at end of file
diff --git a/jetty-policy/src/test/resources/monitor-test-2/global-all-permission.policy b/jetty-policy/src/test/resources/monitor-test-2/global-all-permission.policy
deleted file mode 100644
index ae56d98..0000000
--- a/jetty-policy/src/test/resources/monitor-test-2/global-all-permission.policy
+++ /dev/null
@@ -1,3 +0,0 @@
-grant {
-  permission java.security.AllPermission;
-}
\ No newline at end of file
diff --git a/jetty-policy/src/test/resources/monitor-test-2/template1.template b/jetty-policy/src/test/resources/monitor-test-2/template1.template
deleted file mode 100644
index cdfae27..0000000
--- a/jetty-policy/src/test/resources/monitor-test-2/template1.template
+++ /dev/null
@@ -1,3 +0,0 @@
-grant codeBase "TEMPLATE" {
-  permission java.io.FilePermission "/tmp/*", "read";
-}
\ No newline at end of file
diff --git a/jetty-policy/src/test/resources/monitor-test-3/global-all-permission.policy b/jetty-policy/src/test/resources/monitor-test-3/global-all-permission.policy
deleted file mode 100644
index ae56d98..0000000
--- a/jetty-policy/src/test/resources/monitor-test-3/global-all-permission.policy
+++ /dev/null
@@ -1,3 +0,0 @@
-grant {
-  permission java.security.AllPermission;
-}
\ No newline at end of file
diff --git a/jetty-policy/src/test/resources/monitor-test-3/global-file-read-only-tmp-permission.policy b/jetty-policy/src/test/resources/monitor-test-3/global-file-read-only-tmp-permission.policy
deleted file mode 100644
index 96da8d6..0000000
--- a/jetty-policy/src/test/resources/monitor-test-3/global-file-read-only-tmp-permission.policy
+++ /dev/null
@@ -1,10 +0,0 @@
-grant {
-  permission java.lang.RuntimePermission "setContextClassLoader";
-  permission java.lang.RuntimePermission "setIO";
-  permission java.lang.RuntimePermission "setSecurityManager";
-  permission java.security.SecurityPermission "getPolicy";
-  
-  permission java.io.FilePermission "/tmp", "read,write";
-  permission java.io.FilePermission "/tmp/*", "read,write"; 
-  permission java.io.FilePermission "/tmp/foo/bar/*", "read";
-}
\ No newline at end of file
diff --git a/jetty-policy/src/test/resources/monitor-test-3/jetty-bad-certificate.policy b/jetty-policy/src/test/resources/monitor-test-3/jetty-bad-certificate.policy
deleted file mode 100644
index 1b3310b..0000000
--- a/jetty-policy/src/test/resources/monitor-test-3/jetty-bad-certificate.policy
+++ /dev/null
@@ -1,25 +0,0 @@
-
-keystore "${basedir}target/test-policy/jetty-policy-nobody.keystore", "jks";
-
-grant signedBy "jetty-policy-bad", codeBase "${basedir}target/test-policy/jetty-test-policy.jar"
-{
-	permission java.util.PropertyPermission "foo", "read";
-}
-
-
-grant {
-
-  permission java.io.FilePermission "/-", "read, write";
-
-  permission java.lang.RuntimePermission "setContextClassLoader";
-  permission java.lang.RuntimePermission "setSecurityManager";
-  permission java.security.SecurityPermission "getPolicy";
-  permission java.lang.RuntimePermission "createClassLoader";
-  permission java.lang.RuntimePermission "setIO";
-  
-  permission java.util.PropertyPermission "java.class.path", "read,write";
-  
-  permission java.util.PropertyPermission "basedir", "read";
-  
-
-}
\ No newline at end of file
diff --git a/jetty-policy/src/test/resources/monitor-test-3/jetty-certificate.policy b/jetty-policy/src/test/resources/monitor-test-3/jetty-certificate.policy
deleted file mode 100644
index 56a5401..0000000
--- a/jetty-policy/src/test/resources/monitor-test-3/jetty-certificate.policy
+++ /dev/null
@@ -1,31 +0,0 @@
-keystore "${basedir}target/test-policy/jetty-policy.keystore", "jks";
-
-grant signedBy "jetty-policy", codeBase "${basedir}target/test-policy/jetty-test-policy.jar"
-{
-	permission java.util.PropertyPermission "foo", "read";
-};
-
-
-grant {
-
-  permission java.io.FilePermission "/-", "read, write";
-
-  permission java.lang.RuntimePermission "setContextClassLoader";
-  permission java.lang.RuntimePermission "setSecurityManager";
-  permission java.security.SecurityPermission "getPolicy";
-  permission java.lang.RuntimePermission "createClassLoader";
-  permission java.lang.RuntimePermission "setIO";
-  
-  permission java.util.PropertyPermission "java.class.path", "read,write";
-  
-  permission java.util.PropertyPermission "org.eclipse.jetty.policy.DEBUG", "read";
-  permission java.util.PropertyPermission "org.eclipse.jetty.util.log.stderr.DEBUG", "read";
-  permission java.util.PropertyPermission "org.eclipse.jetty.util.log.stderr.SOURCE", "read";
-  
-  permission java.util.PropertyPermission "org.eclipse.jetty.util.log.DEBUG", "read";
-  permission java.util.PropertyPermission "org.eclipse.jetty.util.log.SOURCE", "read";
-  permission java.util.PropertyPermission "org.eclipse.jetty.util.log.class", "read";
-  permission java.util.PropertyPermission "org.eclipse.jetty.util.log.IGNORED", "read";
-  
-  permission java.util.PropertyPermission "basedir", "read";
-};
\ No newline at end of file
diff --git a/jetty-policy/src/test/resources/monitor-test-3/multiple-codebase-file-permission.policy b/jetty-policy/src/test/resources/monitor-test-3/multiple-codebase-file-permission.policy
deleted file mode 100644
index cac9470..0000000
--- a/jetty-policy/src/test/resources/monitor-test-3/multiple-codebase-file-permission.policy
+++ /dev/null
@@ -1,13 +0,0 @@
-grant codeBase "file:///foo.jar" {
-  permission java.io.FilePermission "/tmp/*", "read,write";
-}
-
-grant codeBase "file:///bar.jar" {
-  permission java.io.FilePermission "/tmp/*", "read,write";
-  permission java.io.FilePermission "/usr/*", "read";
-}
-
-grant {
-
-  permission java.util.PropertyPermission "main.class", "read";
-}
diff --git a/jetty-policy/src/test/resources/monitor-test-3/multiple-codebase-mixed-permission.policy b/jetty-policy/src/test/resources/monitor-test-3/multiple-codebase-mixed-permission.policy
deleted file mode 100644
index 7865e07..0000000
--- a/jetty-policy/src/test/resources/monitor-test-3/multiple-codebase-mixed-permission.policy
+++ /dev/null
@@ -1,16 +0,0 @@
-grant codeBase "file:///foo.jar" {
-  permission java.io.FilePermission "/tmp/*", "read,write";
-}
-
-grant codeBase "file:///bar.jar" {
-  permission java.security.AllPermission;
-}
-
-grant codeBase "file:///snap.jar" {
-  permission java.io.FilePermission "/tmp/*", "read,write";
-  permission java.io.FilePermission "/usr/*", "read,write";
-}
-
-grant {
-  permission java.util.PropertyPermission "main.class", "read";
-}
\ No newline at end of file
diff --git a/jetty-policy/src/test/resources/monitor-test-3/single-codebase-file-permission-2.policy b/jetty-policy/src/test/resources/monitor-test-3/single-codebase-file-permission-2.policy
deleted file mode 100644
index ea34900..0000000
--- a/jetty-policy/src/test/resources/monitor-test-3/single-codebase-file-permission-2.policy
+++ /dev/null
@@ -1,3 +0,0 @@
-grant codeBase "file:///bar.jar" {
-  permission java.io.FilePermission "/tmp/*", "read";
-}
\ No newline at end of file
diff --git a/jetty-policy/src/test/resources/monitor-test-3/single-codebase-file-permission-3.policy b/jetty-policy/src/test/resources/monitor-test-3/single-codebase-file-permission-3.policy
deleted file mode 100644
index 4505309..0000000
--- a/jetty-policy/src/test/resources/monitor-test-3/single-codebase-file-permission-3.policy
+++ /dev/null
@@ -1,3 +0,0 @@
-grant codeBase "file:///bar.jar" {
-  permission java.io.FilePermission "/tmp/*", "write";
-}
\ No newline at end of file
diff --git a/jetty-policy/src/test/resources/monitor-test-3/single-codebase-file-permission.policy b/jetty-policy/src/test/resources/monitor-test-3/single-codebase-file-permission.policy
deleted file mode 100644
index 646df09..0000000
--- a/jetty-policy/src/test/resources/monitor-test-3/single-codebase-file-permission.policy
+++ /dev/null
@@ -1,3 +0,0 @@
-grant codeBase "file:///foo.jar" {
-  permission java.io.FilePermission "/tmp/*", "read,write";
-}
\ No newline at end of file
diff --git a/jetty-policy/src/test/resources/monitor-test-3/template1.template b/jetty-policy/src/test/resources/monitor-test-3/template1.template
deleted file mode 100644
index cdfae27..0000000
--- a/jetty-policy/src/test/resources/monitor-test-3/template1.template
+++ /dev/null
@@ -1,3 +0,0 @@
-grant codeBase "TEMPLATE" {
-  permission java.io.FilePermission "/tmp/*", "read";
-}
\ No newline at end of file
diff --git a/jetty-policy/src/test/resources/policy-test-1/global-all-permission.policy b/jetty-policy/src/test/resources/policy-test-1/global-all-permission.policy
deleted file mode 100644
index ae56d98..0000000
--- a/jetty-policy/src/test/resources/policy-test-1/global-all-permission.policy
+++ /dev/null
@@ -1,3 +0,0 @@
-grant {
-  permission java.security.AllPermission;
-}
\ No newline at end of file
diff --git a/jetty-policy/src/test/resources/policy-test-2/single-codebase-file-permission.policy b/jetty-policy/src/test/resources/policy-test-2/single-codebase-file-permission.policy
deleted file mode 100644
index 646df09..0000000
--- a/jetty-policy/src/test/resources/policy-test-2/single-codebase-file-permission.policy
+++ /dev/null
@@ -1,3 +0,0 @@
-grant codeBase "file:///foo.jar" {
-  permission java.io.FilePermission "/tmp/*", "read,write";
-}
\ No newline at end of file
diff --git a/jetty-policy/src/test/resources/policy-test-3/multiple-codebase-file-permission.policy b/jetty-policy/src/test/resources/policy-test-3/multiple-codebase-file-permission.policy
deleted file mode 100644
index cac9470..0000000
--- a/jetty-policy/src/test/resources/policy-test-3/multiple-codebase-file-permission.policy
+++ /dev/null
@@ -1,13 +0,0 @@
-grant codeBase "file:///foo.jar" {
-  permission java.io.FilePermission "/tmp/*", "read,write";
-}
-
-grant codeBase "file:///bar.jar" {
-  permission java.io.FilePermission "/tmp/*", "read,write";
-  permission java.io.FilePermission "/usr/*", "read";
-}
-
-grant {
-
-  permission java.util.PropertyPermission "main.class", "read";
-}
diff --git a/jetty-policy/src/test/resources/policy-test-4/multiple-codebase-mixed-permission.policy b/jetty-policy/src/test/resources/policy-test-4/multiple-codebase-mixed-permission.policy
deleted file mode 100644
index 7865e07..0000000
--- a/jetty-policy/src/test/resources/policy-test-4/multiple-codebase-mixed-permission.policy
+++ /dev/null
@@ -1,16 +0,0 @@
-grant codeBase "file:///foo.jar" {
-  permission java.io.FilePermission "/tmp/*", "read,write";
-}
-
-grant codeBase "file:///bar.jar" {
-  permission java.security.AllPermission;
-}
-
-grant codeBase "file:///snap.jar" {
-  permission java.io.FilePermission "/tmp/*", "read,write";
-  permission java.io.FilePermission "/usr/*", "read,write";
-}
-
-grant {
-  permission java.util.PropertyPermission "main.class", "read";
-}
\ No newline at end of file
diff --git a/jetty-policy/src/test/resources/policy-test-5/single-codebase-file-permission-2.policy b/jetty-policy/src/test/resources/policy-test-5/single-codebase-file-permission-2.policy
deleted file mode 100644
index ea34900..0000000
--- a/jetty-policy/src/test/resources/policy-test-5/single-codebase-file-permission-2.policy
+++ /dev/null
@@ -1,3 +0,0 @@
-grant codeBase "file:///bar.jar" {
-  permission java.io.FilePermission "/tmp/*", "read";
-}
\ No newline at end of file
diff --git a/jetty-policy/src/test/resources/policy-test-5/single-codebase-file-permission.policy b/jetty-policy/src/test/resources/policy-test-5/single-codebase-file-permission.policy
deleted file mode 100644
index 646df09..0000000
--- a/jetty-policy/src/test/resources/policy-test-5/single-codebase-file-permission.policy
+++ /dev/null
@@ -1,3 +0,0 @@
-grant codeBase "file:///foo.jar" {
-  permission java.io.FilePermission "/tmp/*", "read,write";
-}
\ No newline at end of file
diff --git a/jetty-policy/src/test/resources/policy-test-6/single-codebase-file-permission-2.policy b/jetty-policy/src/test/resources/policy-test-6/single-codebase-file-permission-2.policy
deleted file mode 100644
index ea34900..0000000
--- a/jetty-policy/src/test/resources/policy-test-6/single-codebase-file-permission-2.policy
+++ /dev/null
@@ -1,3 +0,0 @@
-grant codeBase "file:///bar.jar" {
-  permission java.io.FilePermission "/tmp/*", "read";
-}
\ No newline at end of file
diff --git a/jetty-policy/src/test/resources/policy-test-6/single-codebase-file-permission-3.policy b/jetty-policy/src/test/resources/policy-test-6/single-codebase-file-permission-3.policy
deleted file mode 100644
index 4505309..0000000
--- a/jetty-policy/src/test/resources/policy-test-6/single-codebase-file-permission-3.policy
+++ /dev/null
@@ -1,3 +0,0 @@
-grant codeBase "file:///bar.jar" {
-  permission java.io.FilePermission "/tmp/*", "write";
-}
\ No newline at end of file
diff --git a/jetty-policy/src/test/resources/policy-test-6/single-codebase-file-permission.policy b/jetty-policy/src/test/resources/policy-test-6/single-codebase-file-permission.policy
deleted file mode 100644
index 646df09..0000000
--- a/jetty-policy/src/test/resources/policy-test-6/single-codebase-file-permission.policy
+++ /dev/null
@@ -1,3 +0,0 @@
-grant codeBase "file:///foo.jar" {
-  permission java.io.FilePermission "/tmp/*", "read,write";
-}
\ No newline at end of file
diff --git a/jetty-policy/src/test/resources/runtime-test-1/global-all-permission.policy b/jetty-policy/src/test/resources/runtime-test-1/global-all-permission.policy
deleted file mode 100644
index ae56d98..0000000
--- a/jetty-policy/src/test/resources/runtime-test-1/global-all-permission.policy
+++ /dev/null
@@ -1,3 +0,0 @@
-grant {
-  permission java.security.AllPermission;
-}
\ No newline at end of file
diff --git a/jetty-policy/src/test/resources/runtime-test-2/a/global-all-permission.policy b/jetty-policy/src/test/resources/runtime-test-2/a/global-all-permission.policy
deleted file mode 100644
index ae56d98..0000000
--- a/jetty-policy/src/test/resources/runtime-test-2/a/global-all-permission.policy
+++ /dev/null
@@ -1,3 +0,0 @@
-grant {
-  permission java.security.AllPermission;
-}
\ No newline at end of file
diff --git a/jetty-policy/src/test/resources/runtime-test-2/b/global-file-read-only-tmp-permission.policy b/jetty-policy/src/test/resources/runtime-test-2/b/global-file-read-only-tmp-permission.policy
deleted file mode 100644
index 96da8d6..0000000
--- a/jetty-policy/src/test/resources/runtime-test-2/b/global-file-read-only-tmp-permission.policy
+++ /dev/null
@@ -1,10 +0,0 @@
-grant {
-  permission java.lang.RuntimePermission "setContextClassLoader";
-  permission java.lang.RuntimePermission "setIO";
-  permission java.lang.RuntimePermission "setSecurityManager";
-  permission java.security.SecurityPermission "getPolicy";
-  
-  permission java.io.FilePermission "/tmp", "read,write";
-  permission java.io.FilePermission "/tmp/*", "read,write"; 
-  permission java.io.FilePermission "/tmp/foo/bar/*", "read";
-}
\ No newline at end of file
diff --git a/jetty-policy/src/test/resources/runtime-test-3/global-file-read-only-tmp-permission.policy b/jetty-policy/src/test/resources/runtime-test-3/global-file-read-only-tmp-permission.policy
deleted file mode 100644
index 96da8d6..0000000
--- a/jetty-policy/src/test/resources/runtime-test-3/global-file-read-only-tmp-permission.policy
+++ /dev/null
@@ -1,10 +0,0 @@
-grant {
-  permission java.lang.RuntimePermission "setContextClassLoader";
-  permission java.lang.RuntimePermission "setIO";
-  permission java.lang.RuntimePermission "setSecurityManager";
-  permission java.security.SecurityPermission "getPolicy";
-  
-  permission java.io.FilePermission "/tmp", "read,write";
-  permission java.io.FilePermission "/tmp/*", "read,write"; 
-  permission java.io.FilePermission "/tmp/foo/bar/*", "read";
-}
\ No newline at end of file
diff --git a/jetty-policy/src/test/resources/runtime-test-4/jetty-certificate.policy b/jetty-policy/src/test/resources/runtime-test-4/jetty-certificate.policy
deleted file mode 100644
index 56a5401..0000000
--- a/jetty-policy/src/test/resources/runtime-test-4/jetty-certificate.policy
+++ /dev/null
@@ -1,31 +0,0 @@
-keystore "${basedir}target/test-policy/jetty-policy.keystore", "jks";
-
-grant signedBy "jetty-policy", codeBase "${basedir}target/test-policy/jetty-test-policy.jar"
-{
-	permission java.util.PropertyPermission "foo", "read";
-};
-
-
-grant {
-
-  permission java.io.FilePermission "/-", "read, write";
-
-  permission java.lang.RuntimePermission "setContextClassLoader";
-  permission java.lang.RuntimePermission "setSecurityManager";
-  permission java.security.SecurityPermission "getPolicy";
-  permission java.lang.RuntimePermission "createClassLoader";
-  permission java.lang.RuntimePermission "setIO";
-  
-  permission java.util.PropertyPermission "java.class.path", "read,write";
-  
-  permission java.util.PropertyPermission "org.eclipse.jetty.policy.DEBUG", "read";
-  permission java.util.PropertyPermission "org.eclipse.jetty.util.log.stderr.DEBUG", "read";
-  permission java.util.PropertyPermission "org.eclipse.jetty.util.log.stderr.SOURCE", "read";
-  
-  permission java.util.PropertyPermission "org.eclipse.jetty.util.log.DEBUG", "read";
-  permission java.util.PropertyPermission "org.eclipse.jetty.util.log.SOURCE", "read";
-  permission java.util.PropertyPermission "org.eclipse.jetty.util.log.class", "read";
-  permission java.util.PropertyPermission "org.eclipse.jetty.util.log.IGNORED", "read";
-  
-  permission java.util.PropertyPermission "basedir", "read";
-};
\ No newline at end of file
diff --git a/jetty-policy/src/test/resources/runtime-test-5/jetty-bad-certificate.policy b/jetty-policy/src/test/resources/runtime-test-5/jetty-bad-certificate.policy
deleted file mode 100644
index 1b3310b..0000000
--- a/jetty-policy/src/test/resources/runtime-test-5/jetty-bad-certificate.policy
+++ /dev/null
@@ -1,25 +0,0 @@
-
-keystore "${basedir}target/test-policy/jetty-policy-nobody.keystore", "jks";
-
-grant signedBy "jetty-policy-bad", codeBase "${basedir}target/test-policy/jetty-test-policy.jar"
-{
-	permission java.util.PropertyPermission "foo", "read";
-}
-
-
-grant {
-
-  permission java.io.FilePermission "/-", "read, write";
-
-  permission java.lang.RuntimePermission "setContextClassLoader";
-  permission java.lang.RuntimePermission "setSecurityManager";
-  permission java.security.SecurityPermission "getPolicy";
-  permission java.lang.RuntimePermission "createClassLoader";
-  permission java.lang.RuntimePermission "setIO";
-  
-  permission java.util.PropertyPermission "java.class.path", "read,write";
-  
-  permission java.util.PropertyPermission "basedir", "read";
-  
-
-}
\ No newline at end of file
diff --git a/jetty-proxy/pom.xml b/jetty-proxy/pom.xml
new file mode 100644
index 0000000..42348c6
--- /dev/null
+++ b/jetty-proxy/pom.xml
@@ -0,0 +1,106 @@
+<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">
+  <parent>
+    <groupId>org.eclipse.jetty</groupId>
+    <artifactId>jetty-project</artifactId>
+    <version>9.2.13.v20150730</version>
+  </parent>
+  <modelVersion>4.0.0</modelVersion>
+  <artifactId>jetty-proxy</artifactId>
+  <name>Jetty :: Proxy</name>
+  <description>Jetty Proxy</description>
+  <url>http://www.eclipse.org/jetty</url>
+  <properties>
+    <bundle-symbolic-name>${project.groupId}.proxy</bundle-symbolic-name>
+  </properties>
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>maven-bundle-plugin</artifactId>
+        <extensions>true</extensions>
+        <executions>
+          <execution>
+            <goals>
+              <goal>manifest</goal>
+            </goals>
+            <configuration>
+              <instructions>
+                <Import-Package>javax.servlet.*;version="[2.6.0,3.2)",*</Import-Package>
+              </instructions>
+            </configuration>
+           </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <!--
+        Required for OSGI
+        -->
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-jar-plugin</artifactId>
+        <configuration>
+          <archive>
+            <manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
+          </archive>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-assembly-plugin</artifactId>
+        <executions>
+          <execution>
+            <phase>package</phase>
+            <goals>
+              <goal>single</goal>
+            </goals>
+            <configuration>
+              <descriptorRefs>
+                <descriptorRef>config</descriptorRef>
+              </descriptorRefs>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>findbugs-maven-plugin</artifactId>
+        <configuration>
+          <onlyAnalyze>org.eclipse.jetty.proxy.*</onlyAnalyze>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+  <dependencies>
+    <dependency>
+      <groupId>javax.servlet</groupId>
+      <artifactId>javax.servlet-api</artifactId>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-servlet</artifactId>
+      <version>${project.version}</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-util</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-client</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-util-ajax</artifactId>
+      <version>${project.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.toolchain</groupId>
+      <artifactId>jetty-test-helper</artifactId>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/jetty-proxy/src/main/config/etc/jetty-proxy.xml b/jetty-proxy/src/main/config/etc/jetty-proxy.xml
new file mode 100644
index 0000000..0e96724
--- /dev/null
+++ b/jetty-proxy/src/main/config/etc/jetty-proxy.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0"?>
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
+
+<Configure id="Server" class="org.eclipse.jetty.server.Server">
+
+    <Set name="handler">
+        <New class="org.eclipse.jetty.proxy.ConnectHandler">
+            <Set name="handler">
+                <New class="org.eclipse.jetty.servlet.ServletHandler">
+                    <Call id="proxyHolder" name="addServletWithMapping">
+                        <Arg><Property name="jetty.proxy.servletClass" default="org.eclipse.jetty.proxy.ProxyServlet"/></Arg>
+                        <Arg><Property name="jetty.proxy.servletMapping" default="/*"/></Arg>
+                        <Call name="setInitParameter">
+                            <Arg>maxThreads</Arg>
+                            <Arg><Property name="jetty.proxy.maxThreads" default="128" /></Arg>
+                        </Call>
+                        <Call name="setInitParameter">
+                            <Arg>maxConnections</Arg>
+                            <Arg><Property name="jetty.proxy.maxConnections" default="256" /></Arg>
+                        </Call>
+                        <Call name="setInitParameter">
+                            <Arg>idleTimeout</Arg>
+                            <Arg><Property name="jetty.proxy.idleTimeout" default="30000" /></Arg>
+                        </Call>
+                        <Call name="setInitParameter">
+                            <Arg>timeout</Arg>
+                            <Arg><Property name="jetty.proxy.timeout" default="60000" /></Arg>
+                        </Call>
+                    </Call>
+                </New>
+            </Set>
+        </New>
+    </Set>
+
+</Configure>
diff --git a/jetty-proxy/src/main/config/modules/proxy.mod b/jetty-proxy/src/main/config/modules/proxy.mod
new file mode 100644
index 0000000..a879ae1
--- /dev/null
+++ b/jetty-proxy/src/main/config/modules/proxy.mod
@@ -0,0 +1,22 @@
+#
+# Jetty Proxy module
+#
+
+[depend]
+servlet
+client
+
+[lib]
+lib/jetty-proxy-${jetty.version}.jar
+
+[xml]
+etc/jetty-proxy.xml
+
+[ini-template]
+## Proxy Configuration
+#jetty.proxy.servletClass=org.eclipse.jetty.proxy.ProxyServlet
+#jetty.proxy.servletMapping=/*
+#jetty.proxy.maxThreads=128
+#jetty.proxy.maxConnections=256
+#jetty.proxy.idleTimeout=30000
+#jetty.proxy.timeout=60000
diff --git a/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/AbstractProxyServlet.java b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/AbstractProxyServlet.java
new file mode 100644
index 0000000..49f4d54
--- /dev/null
+++ b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/AbstractProxyServlet.java
@@ -0,0 +1,731 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.proxy;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.URI;
+import java.net.UnknownHostException;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeoutException;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.UnavailableException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.http.HttpField;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpHeaderValue;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.util.HttpCookieStore;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+
+/**
+ * <p>Abstract base class for proxy servlets.</p>
+ * <p>Forwards requests to another server either as a standard web reverse
+ * proxy or as a transparent reverse proxy (as defined by RFC 7230).</p>
+ * <p>To facilitate JMX monitoring, the {@link HttpClient} instance is set
+ * as ServletContext attribute, prefixed with this servlet's name and
+ * exposed by the mechanism provided by
+ * {@link ServletContext#setAttribute(String, Object)}.</p>
+ * <p>The following init parameters may be used to configure the servlet:</p>
+ * <ul>
+ * <li>preserveHost - the host header specified by the client is forwarded to the server</li>
+ * <li>hostHeader - forces the host header to a particular value</li>
+ * <li>viaHost - the name to use in the Via header: Via: http/1.1 <viaHost></li>
+ * <li>whiteList - comma-separated list of allowed proxy hosts</li>
+ * <li>blackList - comma-separated list of forbidden proxy hosts</li>
+ * </ul>
+ * <p>In addition, see {@link #createHttpClient()} for init parameters
+ * used to configure the {@link HttpClient} instance.</p>
+ * <p>NOTE: By default the Host header sent to the server by this proxy
+ * servlet is the server's host name. However, this breaks redirects.
+ * Set {@code preserveHost} to {@code true} to make redirects working,
+ * although this may break server's virtual host selection.</p>
+ * <p>The default behavior of not preserving the Host header mimics
+ * the default behavior of Apache httpd and Nginx, which both have
+ * a way to be configured to preserve the Host header.</p>
+ */
+public abstract class AbstractProxyServlet extends HttpServlet
+{
+    protected static final Set<String> HOP_HEADERS;
+    static
+    {
+        Set<String> hopHeaders = new HashSet<>();
+        hopHeaders.add("connection");
+        hopHeaders.add("keep-alive");
+        hopHeaders.add("proxy-authorization");
+        hopHeaders.add("proxy-authenticate");
+        hopHeaders.add("proxy-connection");
+        hopHeaders.add("transfer-encoding");
+        hopHeaders.add("te");
+        hopHeaders.add("trailer");
+        hopHeaders.add("upgrade");
+        HOP_HEADERS = Collections.unmodifiableSet(hopHeaders);
+    }
+
+    private final Set<String> _whiteList = new HashSet<>();
+    private final Set<String> _blackList = new HashSet<>();
+    protected Logger _log;
+    private boolean _preserveHost;
+    private String _hostHeader;
+    private String _viaHost;
+    private HttpClient _client;
+    private long _timeout;
+
+    @Override
+    public void init() throws ServletException
+    {
+        _log = createLogger();
+
+        ServletConfig config = getServletConfig();
+
+        _preserveHost = Boolean.parseBoolean(config.getInitParameter("preserveHost"));
+
+        _hostHeader = config.getInitParameter("hostHeader");
+
+        _viaHost = config.getInitParameter("viaHost");
+        if (_viaHost == null)
+            _viaHost = viaHost();
+
+        try
+        {
+            _client = createHttpClient();
+
+            // Put the HttpClient in the context to leverage ContextHandler.MANAGED_ATTRIBUTES
+            getServletContext().setAttribute(config.getServletName() + ".HttpClient", _client);
+
+            String whiteList = config.getInitParameter("whiteList");
+            if (whiteList != null)
+                getWhiteListHosts().addAll(parseList(whiteList));
+
+            String blackList = config.getInitParameter("blackList");
+            if (blackList != null)
+                getBlackListHosts().addAll(parseList(blackList));
+        }
+        catch (Exception e)
+        {
+            throw new ServletException(e);
+        }
+    }
+
+    @Override
+    public void destroy()
+    {
+        try
+        {
+            _client.stop();
+        }
+        catch (Exception x)
+        {
+            if (_log.isDebugEnabled())
+                _log.debug(x);
+        }
+    }
+
+    public String getHostHeader()
+    {
+        return _hostHeader;
+    }
+
+    public String getViaHost()
+    {
+        return _viaHost;
+    }
+
+    private static String viaHost()
+    {
+        try
+        {
+            return InetAddress.getLocalHost().getHostName();
+        }
+        catch (UnknownHostException x)
+        {
+            return "localhost";
+        }
+    }
+
+    public long getTimeout()
+    {
+        return _timeout;
+    }
+
+    public void setTimeout(long timeout)
+    {
+        this._timeout = timeout;
+    }
+
+    public Set<String> getWhiteListHosts()
+    {
+        return _whiteList;
+    }
+
+    public Set<String> getBlackListHosts()
+    {
+        return _blackList;
+    }
+
+    /**
+     * @return a logger instance with a name derived from this servlet's name.
+     */
+    protected Logger createLogger()
+    {
+        String servletName = getServletConfig().getServletName();
+        servletName = servletName.replace('-', '.');
+        if ((getClass().getPackage() != null) && !servletName.startsWith(getClass().getPackage().getName()))
+        {
+            servletName = getClass().getName() + "." + servletName;
+        }
+        return Log.getLogger(servletName);
+    }
+
+    /**
+     * <p>Creates a {@link HttpClient} instance, configured with init parameters of this servlet.</p>
+     * <p>The init parameters used to configure the {@link HttpClient} instance are:</p>
+     * <table>
+     * <thead>
+     * <tr>
+     * <th>init-param</th>
+     * <th>default</th>
+     * <th>description</th>
+     * </tr>
+     * </thead>
+     * <tbody>
+     * <tr>
+     * <td>maxThreads</td>
+     * <td>256</td>
+     * <td>The max number of threads of HttpClient's Executor.  If not set, or set to the value of "-", then the
+     * Jetty server thread pool will be used.</td>
+     * </tr>
+     * <tr>
+     * <td>maxConnections</td>
+     * <td>32768</td>
+     * <td>The max number of connections per destination, see {@link HttpClient#setMaxConnectionsPerDestination(int)}</td>
+     * </tr>
+     * <tr>
+     * <td>idleTimeout</td>
+     * <td>30000</td>
+     * <td>The idle timeout in milliseconds, see {@link HttpClient#setIdleTimeout(long)}</td>
+     * </tr>
+     * <tr>
+     * <td>timeout</td>
+     * <td>60000</td>
+     * <td>The total timeout in milliseconds, see {@link Request#timeout(long, java.util.concurrent.TimeUnit)}</td>
+     * </tr>
+     * <tr>
+     * <td>requestBufferSize</td>
+     * <td>HttpClient's default</td>
+     * <td>The request buffer size, see {@link HttpClient#setRequestBufferSize(int)}</td>
+     * </tr>
+     * <tr>
+     * <td>responseBufferSize</td>
+     * <td>HttpClient's default</td>
+     * <td>The response buffer size, see {@link HttpClient#setResponseBufferSize(int)}</td>
+     * </tr>
+     * </tbody>
+     * </table>
+     *
+     * @return a {@link HttpClient} configured from the {@link #getServletConfig() servlet configuration}
+     * @throws ServletException if the {@link HttpClient} cannot be created
+     */
+    protected HttpClient createHttpClient() throws ServletException
+    {
+        ServletConfig config = getServletConfig();
+
+        HttpClient client = newHttpClient();
+
+        // Redirects must be proxied as is, not followed.
+        client.setFollowRedirects(false);
+
+        // Must not store cookies, otherwise cookies of different clients will mix.
+        client.setCookieStore(new HttpCookieStore.Empty());
+
+        Executor executor;
+        String value = config.getInitParameter("maxThreads");
+        if (value == null || "-".equals(value))
+        {
+            executor = (Executor)getServletContext().getAttribute("org.eclipse.jetty.server.Executor");
+            if (executor==null)
+                throw new IllegalStateException("No server executor for proxy");
+        }
+        else
+        {
+            QueuedThreadPool qtp= new QueuedThreadPool(Integer.parseInt(value));
+            String servletName = config.getServletName();
+            int dot = servletName.lastIndexOf('.');
+            if (dot >= 0)
+                servletName = servletName.substring(dot + 1);
+            qtp.setName(servletName);
+            executor=qtp;
+        }
+
+        client.setExecutor(executor);
+
+        value = config.getInitParameter("maxConnections");
+        if (value == null)
+            value = "256";
+        client.setMaxConnectionsPerDestination(Integer.parseInt(value));
+
+        value = config.getInitParameter("idleTimeout");
+        if (value == null)
+            value = "30000";
+        client.setIdleTimeout(Long.parseLong(value));
+
+        value = config.getInitParameter("timeout");
+        if (value == null)
+            value = "60000";
+        _timeout = Long.parseLong(value);
+
+        value = config.getInitParameter("requestBufferSize");
+        if (value != null)
+            client.setRequestBufferSize(Integer.parseInt(value));
+
+        value = config.getInitParameter("responseBufferSize");
+        if (value != null)
+            client.setResponseBufferSize(Integer.parseInt(value));
+
+        try
+        {
+            client.start();
+
+            // Content must not be decoded, otherwise the client gets confused.
+            client.getContentDecoderFactories().clear();
+
+            // No protocol handlers, pass everything to the client.
+            client.getProtocolHandlers().clear();
+
+            return client;
+        }
+        catch (Exception x)
+        {
+            throw new ServletException(x);
+        }
+    }
+
+    /**
+     * @return a new HttpClient instance
+     */
+    protected HttpClient newHttpClient()
+    {
+        return new HttpClient();
+    }
+
+    protected HttpClient getHttpClient()
+    {
+        return _client;
+    }
+
+    private Set<String> parseList(String list)
+    {
+        Set<String> result = new HashSet<>();
+        String[] hosts = list.split(",");
+        for (String host : hosts)
+        {
+            host = host.trim();
+            if (host.length() == 0)
+                continue;
+            result.add(host);
+        }
+        return result;
+    }
+
+    /**
+     * Checks the given {@code host} and {@code port} against whitelist and blacklist.
+     *
+     * @param host the host to check
+     * @param port the port to check
+     * @return true if it is allowed to be proxy to the given host and port
+     */
+    public boolean validateDestination(String host, int port)
+    {
+        String hostPort = host + ":" + port;
+        if (!_whiteList.isEmpty())
+        {
+            if (!_whiteList.contains(hostPort))
+            {
+                if (_log.isDebugEnabled())
+                    _log.debug("Host {}:{} not whitelisted", host, port);
+                return false;
+            }
+        }
+        if (!_blackList.isEmpty())
+        {
+            if (_blackList.contains(hostPort))
+            {
+                if (_log.isDebugEnabled())
+                    _log.debug("Host {}:{} blacklisted", host, port);
+                return false;
+            }
+        }
+        return true;
+    }
+
+    protected String rewriteTarget(HttpServletRequest clientRequest)
+    {
+        if (!validateDestination(clientRequest.getServerName(), clientRequest.getServerPort()))
+            return null;
+
+        StringBuffer target = clientRequest.getRequestURL();
+        String query = clientRequest.getQueryString();
+        if (query != null)
+            target.append("?").append(query);
+        return target.toString();
+    }
+
+    /**
+     * <p>Callback method invoked when the URI rewrite performed
+     * in {@link #rewriteTarget(HttpServletRequest)} returns null
+     * indicating that no rewrite can be performed.</p>
+     * <p>It is possible to use blocking API in this method,
+     * like {@link HttpServletResponse#sendError(int)}.</p>
+     *
+     * @param clientRequest the client request
+     * @param clientResponse the client response
+     */
+    protected void onProxyRewriteFailed(HttpServletRequest clientRequest, HttpServletResponse clientResponse)
+    {
+        clientResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
+    }
+
+    protected boolean hasContent(HttpServletRequest clientRequest)
+    {
+        return clientRequest.getContentLength() > 0 ||
+                clientRequest.getContentType() != null ||
+                clientRequest.getHeader(HttpHeader.TRANSFER_ENCODING.asString()) != null;
+    }
+
+    protected void copyRequestHeaders(HttpServletRequest clientRequest, Request proxyRequest)
+    {
+        // First clear possibly existing headers, as we are going to copy those from the client request.
+        proxyRequest.getHeaders().clear();
+
+        Set<String> headersToRemove = findConnectionHeaders(clientRequest);
+
+        for (Enumeration<String> headerNames = clientRequest.getHeaderNames(); headerNames.hasMoreElements();)
+        {
+            String headerName = headerNames.nextElement();
+            String lowerHeaderName = headerName.toLowerCase(Locale.ENGLISH);
+
+            if (HttpHeader.HOST.is(headerName) && !_preserveHost)
+                continue;
+
+            // Remove hop-by-hop headers.
+            if (HOP_HEADERS.contains(lowerHeaderName))
+                continue;
+            if (headersToRemove != null && headersToRemove.contains(lowerHeaderName))
+                continue;
+
+            for (Enumeration<String> headerValues = clientRequest.getHeaders(headerName); headerValues.hasMoreElements();)
+            {
+                String headerValue = headerValues.nextElement();
+                if (headerValue != null)
+                    proxyRequest.header(headerName, headerValue);
+            }
+        }
+
+        // Force the Host header if configured
+        if (_hostHeader != null)
+            proxyRequest.header(HttpHeader.HOST, _hostHeader);
+    }
+
+    protected Set<String> findConnectionHeaders(HttpServletRequest clientRequest)
+    {
+        // Any header listed by the Connection header must be removed:
+        // http://tools.ietf.org/html/rfc7230#section-6.1.
+        Set<String> hopHeaders = null;
+        Enumeration<String> connectionHeaders = clientRequest.getHeaders(HttpHeader.CONNECTION.asString());
+        while (connectionHeaders.hasMoreElements())
+        {
+            String value = connectionHeaders.nextElement();
+            String[] values = value.split(",");
+            for (String name : values)
+            {
+                name = name.trim().toLowerCase(Locale.ENGLISH);
+                if (hopHeaders == null)
+                    hopHeaders = new HashSet<>();
+                hopHeaders.add(name);
+            }
+        }
+        return hopHeaders;
+    }
+
+    protected void addProxyHeaders(HttpServletRequest clientRequest, Request proxyRequest)
+    {
+        addViaHeader(proxyRequest);
+        addXForwardedHeaders(clientRequest, proxyRequest);
+    }
+
+    protected void addViaHeader(Request proxyRequest)
+    {
+        proxyRequest.header(HttpHeader.VIA, "http/1.1 " + getViaHost());
+    }
+
+    protected void addXForwardedHeaders(HttpServletRequest clientRequest, Request proxyRequest)
+    {
+        proxyRequest.header(HttpHeader.X_FORWARDED_FOR, clientRequest.getRemoteAddr());
+        proxyRequest.header(HttpHeader.X_FORWARDED_PROTO, clientRequest.getScheme());
+        proxyRequest.header(HttpHeader.X_FORWARDED_HOST, clientRequest.getHeader(HttpHeader.HOST.asString()));
+        proxyRequest.header(HttpHeader.X_FORWARDED_SERVER, clientRequest.getLocalName());
+    }
+
+    protected void sendProxyRequest(HttpServletRequest clientRequest, HttpServletResponse proxyResponse, Request proxyRequest)
+    {
+        if (_log.isDebugEnabled())
+        {
+            StringBuilder builder = new StringBuilder(clientRequest.getMethod());
+            builder.append(" ").append(clientRequest.getRequestURI());
+            String query = clientRequest.getQueryString();
+            if (query != null)
+                builder.append("?").append(query);
+            builder.append(" ").append(clientRequest.getProtocol()).append(System.lineSeparator());
+            for (Enumeration<String> headerNames = clientRequest.getHeaderNames(); headerNames.hasMoreElements();)
+            {
+                String headerName = headerNames.nextElement();
+                builder.append(headerName).append(": ");
+                for (Enumeration<String> headerValues = clientRequest.getHeaders(headerName); headerValues.hasMoreElements();)
+                {
+                    String headerValue = headerValues.nextElement();
+                    if (headerValue != null)
+                        builder.append(headerValue);
+                    if (headerValues.hasMoreElements())
+                        builder.append(",");
+                }
+                builder.append(System.lineSeparator());
+            }
+            builder.append(System.lineSeparator());
+
+            _log.debug("{} proxying to upstream:{}{}{}{}",
+                    getRequestId(clientRequest),
+                    System.lineSeparator(),
+                    builder,
+                    proxyRequest,
+                    System.lineSeparator(),
+                    proxyRequest.getHeaders().toString().trim());
+        }
+
+        proxyRequest.send(newProxyResponseListener(clientRequest, proxyResponse));
+    }
+
+    protected abstract Response.CompleteListener newProxyResponseListener(HttpServletRequest clientRequest, HttpServletResponse proxyResponse);
+
+    protected void onClientRequestFailure(HttpServletRequest clientRequest, Request proxyRequest, HttpServletResponse proxyResponse, Throwable failure)
+    {
+        boolean aborted = proxyRequest.abort(failure);
+        if (!aborted)
+        {
+            int status = failure instanceof TimeoutException ?
+                    HttpStatus.REQUEST_TIMEOUT_408 :
+                    HttpStatus.INTERNAL_SERVER_ERROR_500;
+            proxyResponse.setStatus(status);
+            clientRequest.getAsyncContext().complete();
+        }
+    }
+
+    protected void onServerResponseHeaders(HttpServletRequest clientRequest, HttpServletResponse proxyResponse, Response serverResponse)
+    {
+        for (HttpField field : serverResponse.getHeaders())
+        {
+            String headerName = field.getName();
+            String lowerHeaderName = headerName.toLowerCase(Locale.ENGLISH);
+            if (HOP_HEADERS.contains(lowerHeaderName))
+                continue;
+
+            String newHeaderValue = filterServerResponseHeader(clientRequest, serverResponse, headerName, field.getValue());
+            if (newHeaderValue == null || newHeaderValue.trim().length() == 0)
+                continue;
+
+            proxyResponse.addHeader(headerName, newHeaderValue);
+        }
+
+        if (_log.isDebugEnabled())
+        {
+            StringBuilder builder = new StringBuilder(System.lineSeparator());
+            builder.append(clientRequest.getProtocol()).append(" ").append(proxyResponse.getStatus())
+                    .append(" ").append(serverResponse.getReason()).append(System.lineSeparator());
+            for (String headerName : proxyResponse.getHeaderNames())
+            {
+                builder.append(headerName).append(": ");
+                for (Iterator<String> headerValues = proxyResponse.getHeaders(headerName).iterator(); headerValues.hasNext(); )
+                {
+                    String headerValue = headerValues.next();
+                    if (headerValue != null)
+                        builder.append(headerValue);
+                    if (headerValues.hasNext())
+                        builder.append(",");
+                }
+                builder.append(System.lineSeparator());
+            }
+            _log.debug("{} proxying to downstream:{}{}{}{}{}",
+                    getRequestId(clientRequest),
+                    System.lineSeparator(),
+                    serverResponse,
+                    System.lineSeparator(),
+                    serverResponse.getHeaders().toString().trim(),
+                    System.lineSeparator(),
+                    builder);
+        }
+    }
+
+    protected String filterServerResponseHeader(HttpServletRequest clientRequest, Response serverResponse, String headerName, String headerValue)
+    {
+        return headerValue;
+    }
+
+    protected void onProxyResponseSuccess(HttpServletRequest clientRequest, HttpServletResponse proxyResponse, Response serverResponse)
+    {
+        if (_log.isDebugEnabled())
+            _log.debug("{} proxying successful", getRequestId(clientRequest));
+
+        AsyncContext asyncContext = clientRequest.getAsyncContext();
+        asyncContext.complete();
+    }
+
+    protected void onProxyResponseFailure(HttpServletRequest clientRequest, HttpServletResponse proxyResponse, Response serverResponse, Throwable failure)
+    {
+        if (_log.isDebugEnabled())
+            _log.debug(getRequestId(clientRequest) + " proxying failed", failure);
+
+        if (proxyResponse.isCommitted())
+        {
+            try
+            {
+                // Use Jetty specific behavior to close connection.
+                proxyResponse.sendError(-1);
+                AsyncContext asyncContext = clientRequest.getAsyncContext();
+                asyncContext.complete();
+            }
+            catch (IOException x)
+            {
+                if (_log.isDebugEnabled())
+                    _log.debug(getRequestId(clientRequest) + " could not close the connection", failure);
+            }
+        }
+        else
+        {
+            proxyResponse.resetBuffer();
+            if (failure instanceof TimeoutException)
+                proxyResponse.setStatus(HttpServletResponse.SC_GATEWAY_TIMEOUT);
+            else
+                proxyResponse.setStatus(HttpServletResponse.SC_BAD_GATEWAY);
+            proxyResponse.setHeader(HttpHeader.CONNECTION.asString(), HttpHeaderValue.CLOSE.asString());
+            AsyncContext asyncContext = clientRequest.getAsyncContext();
+            asyncContext.complete();
+        }
+    }
+
+    protected int getRequestId(HttpServletRequest clientRequest)
+    {
+        return System.identityHashCode(clientRequest);
+    }
+
+    /**
+     * <p>Utility class that implement transparent proxy functionalities.</p>
+     * <p>Configuration parameters:</p>
+     * <ul>
+     * <li>{@code proxyTo} - a mandatory URI like http://host:80/context to which the request is proxied.</li>
+     * <li>{@code prefix} - an optional URI prefix that is stripped from the start of the forwarded URI.</li>
+     * </ul>
+     * <p>For example, if a request is received at "/foo/bar", the {@code proxyTo} parameter is
+     * "http://host:80/context" and the {@code prefix} parameter is "/foo", then the request would
+     * be proxied to "http://host:80/context/bar".
+     */
+    protected static class TransparentDelegate
+    {
+        private final ProxyServlet proxyServlet;
+        private String _proxyTo;
+        private String _prefix;
+
+        protected TransparentDelegate(ProxyServlet proxyServlet)
+        {
+            this.proxyServlet = proxyServlet;
+        }
+
+        protected void init(ServletConfig config) throws ServletException
+        {
+            _proxyTo = config.getInitParameter("proxyTo");
+            if (_proxyTo == null)
+                throw new UnavailableException("Init parameter 'proxyTo' is required.");
+
+            String prefix = config.getInitParameter("prefix");
+            if (prefix != null)
+            {
+                if (!prefix.startsWith("/"))
+                    throw new UnavailableException("Init parameter 'prefix' must start with a '/'.");
+                _prefix = prefix;
+            }
+
+            // Adjust prefix value to account for context path
+            String contextPath = config.getServletContext().getContextPath();
+            _prefix = _prefix == null ? contextPath : (contextPath + _prefix);
+
+            if (proxyServlet._log.isDebugEnabled())
+                proxyServlet._log.debug(config.getServletName() + " @ " + _prefix + " to " + _proxyTo);
+        }
+
+        protected String rewriteTarget(HttpServletRequest request)
+        {
+            String path = request.getRequestURI();
+            if (!path.startsWith(_prefix))
+                return null;
+
+            StringBuilder uri = new StringBuilder(_proxyTo);
+            if (_proxyTo.endsWith("/"))
+                uri.setLength(uri.length() - 1);
+            String rest = path.substring(_prefix.length());
+            if (!rest.isEmpty())
+            {
+                if (!rest.startsWith("/"))
+                    uri.append("/");
+                uri.append(rest);
+            }
+
+            String query = request.getQueryString();
+            if (query != null)
+            {
+                // Is there at least one path segment ?
+                String separator = "://";
+                if (uri.indexOf("/", uri.indexOf(separator) + separator.length()) < 0)
+                    uri.append("/");
+                uri.append("?").append(query);
+            }
+            URI rewrittenURI = URI.create(uri.toString()).normalize();
+
+            if (!proxyServlet.validateDestination(rewrittenURI.getHost(), rewrittenURI.getPort()))
+                return null;
+
+            return rewrittenURI.toString();
+        }
+    }
+}
diff --git a/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/AfterContentTransformer.java b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/AfterContentTransformer.java
new file mode 100644
index 0000000..b0b919c
--- /dev/null
+++ b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/AfterContentTransformer.java
@@ -0,0 +1,494 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.proxy;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.channels.Channels;
+import java.nio.channels.FileChannel;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
+import java.nio.file.attribute.FileAttribute;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.component.Destroyable;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * <p>A specialized transformer for {@link AsyncMiddleManServlet} that performs
+ * the transformation when the whole content has been received.</p>
+ * <p>The content is buffered in memory up to a configurable {@link #getMaxInputBufferSize() maximum size},
+ * after which it is overflown to a file on disk. The overflow file is saved
+ * in the {@link #getOverflowDirectory() overflow directory} as a
+ * {@link Files#createTempFile(Path, String, String, FileAttribute[]) temporary file}
+ * with a name starting with the {@link #getInputFilePrefix() input prefix}
+ * and default suffix.</p>
+ * <p>Application must implement the {@link #transform(Source, Sink) transformation method}
+ * to transform the content.</p>
+ * <p>The transformed content is buffered in memory up to a configurable {@link #getMaxOutputBufferSize() maximum size}
+ * after which it is overflown to a file on disk. The overflow file is saved
+ * in the {@link #getOverflowDirectory() overflow directory} as a
+ * {@link Files#createTempFile(Path, String, String, FileAttribute[]) temporary file}
+ * with a name starting with the {@link #getOutputFilePrefix()} output prefix}
+ * and default suffix.</p>
+ */
+public abstract class AfterContentTransformer implements AsyncMiddleManServlet.ContentTransformer, Destroyable
+{
+    private static final Logger LOG = Log.getLogger(AfterContentTransformer.class);
+
+    private final List<ByteBuffer> sourceBuffers = new ArrayList<>();
+    private Path overflowDirectory = Paths.get(System.getProperty("java.io.tmpdir"));
+    private String inputFilePrefix = "amms_adct_in_";
+    private String outputFilePrefix = "amms_adct_out_";
+    private long maxInputBufferSize = 1024 * 1024;
+    private long inputBufferSize;
+    private FileChannel inputFile;
+    private long maxOutputBufferSize = maxInputBufferSize;
+    private long outputBufferSize;
+    private FileChannel outputFile;
+
+    /**
+     * <p>Returns the directory where input and output are overflown to
+     * temporary files if they exceed, respectively, the
+     * {@link #getMaxInputBufferSize() max input size} or the
+     * {@link #getMaxOutputBufferSize() max output size}.</p>
+     * <p>Defaults to the directory pointed by the {@code java.io.tmpdir}
+     * system property.</p>
+     *
+     * @return the overflow directory path
+     * @see #setOverflowDirectory(Path)
+     */
+    public Path getOverflowDirectory()
+    {
+        return overflowDirectory;
+    }
+
+    /**
+     * @param overflowDirectory the overflow directory path
+     * @see #getOverflowDirectory()
+     */
+    public void setOverflowDirectory(Path overflowDirectory)
+    {
+        this.overflowDirectory = overflowDirectory;
+    }
+
+    /**
+     * @return the prefix of the input overflow temporary files
+     * @see #setInputFilePrefix(String)
+     */
+    public String getInputFilePrefix()
+    {
+        return inputFilePrefix;
+    }
+
+    /**
+     * @param inputFilePrefix the prefix of the input overflow temporary files
+     * @see #getInputFilePrefix()
+     */
+    public void setInputFilePrefix(String inputFilePrefix)
+    {
+        this.inputFilePrefix = inputFilePrefix;
+    }
+
+    /**
+     * <p>Returns the maximum input buffer size, after which the input is overflown to disk.</p>
+     * <p>Defaults to 1 MiB, i.e. 1048576 bytes.</p>
+     *
+     * @return the max input buffer size
+     * @see #setMaxInputBufferSize(long)
+     */
+    public long getMaxInputBufferSize()
+    {
+        return maxInputBufferSize;
+    }
+
+    /**
+     * @param maxInputBufferSize the max input buffer size
+     * @see #getMaxInputBufferSize()
+     */
+    public void setMaxInputBufferSize(long maxInputBufferSize)
+    {
+        this.maxInputBufferSize = maxInputBufferSize;
+    }
+
+    /**
+     * @return the prefix of the output overflow temporary files
+     * @see #setOutputFilePrefix(String)
+     */
+    public String getOutputFilePrefix()
+    {
+        return outputFilePrefix;
+    }
+
+    /**
+     * @param outputFilePrefix the prefix of the output overflow temporary files
+     * @see #getOutputFilePrefix()
+     */
+    public void setOutputFilePrefix(String outputFilePrefix)
+    {
+        this.outputFilePrefix = outputFilePrefix;
+    }
+
+    /**
+     * <p>Returns the maximum output buffer size, after which the output is overflown to disk.</p>
+     * <p>Defaults to 1 MiB, i.e. 1048576 bytes.</p>
+     *
+     * @return the max output buffer size
+     * @see #setMaxOutputBufferSize(long)
+     */
+    public long getMaxOutputBufferSize()
+    {
+        return maxOutputBufferSize;
+    }
+
+    /**
+     * @param maxOutputBufferSize the max output buffer size
+     */
+    public void setMaxOutputBufferSize(long maxOutputBufferSize)
+    {
+        this.maxOutputBufferSize = maxOutputBufferSize;
+    }
+
+    @Override
+    public final void transform(ByteBuffer input, boolean finished, List<ByteBuffer> output) throws IOException
+    {
+        int remaining = input.remaining();
+        if (remaining > 0)
+        {
+            inputBufferSize += remaining;
+            long max = getMaxInputBufferSize();
+            if (max >= 0 && inputBufferSize > max)
+            {
+                overflow(input);
+            }
+            else
+            {
+                ByteBuffer copy = ByteBuffer.allocate(input.remaining());
+                copy.put(input).flip();
+                sourceBuffers.add(copy);
+            }
+        }
+
+        if (finished)
+        {
+            Source source = new Source();
+            Sink sink = new Sink();
+            if (transform(source, sink))
+                sink.drainTo(output);
+            else
+                source.drainTo(output);
+        }
+    }
+
+    /**
+     * <p>Transforms the original content read from the {@code source} into
+     * transformed content written to the {@code sink}.</p>
+     * <p>The transformation must happen synchronously in the context of a call
+     * to this method (it is not supported to perform the transformation in another
+     * thread spawned during the call to this method).</p>
+     * <p>Differently from {@link #transform(ByteBuffer, boolean, List)}, this
+     * method is invoked only when the whole content is available, and offers
+     * a blocking API via the InputStream and OutputStream that can be obtained
+     * from {@link Source} and {@link Sink} respectively.</p>
+     * <p>Implementations may read the source, inspect the input bytes and decide
+     * that no transformation is necessary, and therefore the source must be copied
+     * unchanged to the sink. In such case, the implementation must return false to
+     * indicate that it wishes to just pipe the bytes from the source to the sink.</p>
+     * <p>Typical implementations:</p>
+     * <pre>
+     * // Identity transformation (no transformation, the input is copied to the output)
+     * public boolean transform(Source source, Sink sink)
+     * {
+     *     org.eclipse.jetty.util.IO.copy(source.getInputStream(), sink.getOutputStream());
+     *     return true;
+     * }
+     * </pre>
+     *
+     * @param source where the original content is read
+     * @param sink where the transformed content is written
+     * @return true if the transformation happened and the transformed bytes have
+     * been written to the sink, false if no transformation happened and the source
+     * must be copied to the sink.
+     * @throws IOException if the transformation fails
+     */
+    public abstract boolean transform(Source source, Sink sink) throws IOException;
+
+    private void overflow(ByteBuffer input) throws IOException
+    {
+        if (inputFile == null)
+        {
+            Path path = Files.createTempFile(getOverflowDirectory(), getInputFilePrefix(), null);
+            inputFile = FileChannel.open(path,
+                    StandardOpenOption.CREATE,
+                    StandardOpenOption.READ,
+                    StandardOpenOption.WRITE,
+                    StandardOpenOption.DELETE_ON_CLOSE);
+            int size = sourceBuffers.size();
+            if (size > 0)
+            {
+                ByteBuffer[] buffers = sourceBuffers.toArray(new ByteBuffer[size]);
+                sourceBuffers.clear();
+                IO.write(inputFile,buffers,0,buffers.length);
+            }
+        }
+        inputFile.write(input);
+    }
+
+    @Override
+    public void destroy()
+    {
+        close(inputFile);
+        close(outputFile);
+    }
+
+    private void drain(FileChannel file, List<ByteBuffer> output) throws IOException
+    {
+        long position = 0;
+        long length = file.size();
+        file.position(position);
+        while (length > 0)
+        {
+            // At most 1 GiB file maps.
+            long size = Math.min(1024 * 1024 * 1024, length);
+            ByteBuffer buffer = file.map(FileChannel.MapMode.READ_ONLY, position, size);
+            output.add(buffer);
+            position += size;
+            length -= size;
+        }
+    }
+
+    private void close(Closeable closeable)
+    {
+        try
+        {
+            if (closeable != null)
+                closeable.close();
+        }
+        catch (IOException x)
+        {
+            LOG.ignore(x);
+        }
+    }
+
+    /**
+     * <p>The source from where the original content is read to be transformed.</p>
+     * <p>The {@link #getInputStream() input stream} provided by this
+     * class supports the {@link InputStream#reset()} method so that
+     * the stream can be rewound to the beginning.</p>
+     */
+    public class Source
+    {
+        private final InputStream stream;
+
+        private Source() throws IOException
+        {
+            if (inputFile != null)
+            {
+                inputFile.force(true);
+                stream = new ChannelInputStream();
+            }
+            else
+            {
+                stream = new MemoryInputStream();
+            }
+            stream.reset();
+        }
+
+        /**
+         * @return an input stream to read the original content from
+         */
+        public InputStream getInputStream()
+        {
+            return stream;
+        }
+
+        private void drainTo(List<ByteBuffer> output) throws IOException
+        {
+            if (inputFile == null)
+            {
+                output.addAll(sourceBuffers);
+                sourceBuffers.clear();
+            }
+            else
+            {
+                drain(inputFile, output);
+            }
+        }
+    }
+
+    private class ChannelInputStream extends InputStream
+    {
+        private final InputStream stream = Channels.newInputStream(inputFile);
+
+        @Override
+        public int read(byte[] b, int off, int len) throws IOException
+        {
+            return stream.read(b, off, len);
+        }
+
+        @Override
+        public int read() throws IOException
+        {
+            return stream.read();
+        }
+
+        @Override
+        public void reset() throws IOException
+        {
+            inputFile.position(0);
+        }
+    }
+
+    private class MemoryInputStream extends InputStream
+    {
+        private final byte[] oneByte = new byte[1];
+        private int index;
+        private ByteBuffer slice;
+
+        @Override
+        public int read(byte[] b, int off, int len) throws IOException
+        {
+            if (len == 0)
+                return 0;
+            if (index == sourceBuffers.size())
+                return -1;
+
+            if (slice == null)
+                slice = sourceBuffers.get(index).slice();
+
+            int size = Math.min(len, slice.remaining());
+            slice.get(b, off, size);
+
+            if (!slice.hasRemaining())
+            {
+                ++index;
+                slice = null;
+            }
+
+            return size;
+        }
+
+        @Override
+        public int read() throws IOException
+        {
+            int read = read(oneByte, 0, 1);
+            return read < 0 ? read : oneByte[0] & 0xFF;
+        }
+
+        @Override
+        public void reset() throws IOException
+        {
+            index = 0;
+            slice = null;
+        }
+    }
+
+    /**
+     * <p>The target to where the transformed content is written after the transformation.</p>
+     */
+    public class Sink
+    {
+        private final List<ByteBuffer> sinkBuffers = new ArrayList<>();
+        private final OutputStream stream = new SinkOutputStream();
+
+        /**
+         * @return an output stream to write the transformed content to
+         */
+        public OutputStream getOutputStream()
+        {
+            return stream;
+        }
+
+        
+        private void overflow(ByteBuffer output) throws IOException
+        {
+            if (outputFile == null)
+            {
+                Path path = Files.createTempFile(getOverflowDirectory(), getOutputFilePrefix(), null);
+                outputFile = FileChannel.open(path,
+                        StandardOpenOption.CREATE,
+                        StandardOpenOption.READ,
+                        StandardOpenOption.WRITE,
+                        StandardOpenOption.DELETE_ON_CLOSE);
+                int size = sinkBuffers.size();
+                if (size > 0)
+                {
+                    ByteBuffer[] buffers = sinkBuffers.toArray(new ByteBuffer[size]);
+                    sinkBuffers.clear();
+                    
+                    IO.write(outputFile,buffers,0,buffers.length);
+                }
+            }
+            outputFile.write(output);
+        }
+
+        private void drainTo(List<ByteBuffer> output) throws IOException
+        {
+            if (outputFile == null)
+            {
+                output.addAll(sinkBuffers);
+                sinkBuffers.clear();
+            }
+            else
+            {
+                outputFile.force(true);
+                drain(outputFile, output);
+            }
+        }
+
+        private class SinkOutputStream extends OutputStream
+        {
+            @Override
+            public void write(byte[] b, int off, int len) throws IOException
+            {
+                if (len <= 0)
+                    return;
+
+                outputBufferSize += len;
+                long max = getMaxOutputBufferSize();
+                if (max >= 0 && outputBufferSize > max)
+                {
+                    overflow(ByteBuffer.wrap(b, off, len));
+                }
+                else
+                {
+                    // The array may be reused by the
+                    // application so we need to copy it.
+                    byte[] copy = new byte[len];
+                    System.arraycopy(b, off, copy, 0, len);
+                    sinkBuffers.add(ByteBuffer.wrap(copy));
+                }
+            }
+
+            @Override
+            public void write(int b) throws IOException
+            {
+                write(new byte[]{(byte)b}, 0, 1);
+            }
+        }
+    }
+}
diff --git a/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/AsyncMiddleManServlet.java b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/AsyncMiddleManServlet.java
new file mode 100644
index 0000000..143464a
--- /dev/null
+++ b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/AsyncMiddleManServlet.java
@@ -0,0 +1,786 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.proxy;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Queue;
+import java.util.concurrent.TimeUnit;
+import java.util.zip.GZIPOutputStream;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.ReadListener;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletInputStream;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.WriteListener;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.client.ContentDecoder;
+import org.eclipse.jetty.client.GZIPContentDecoder;
+import org.eclipse.jetty.client.api.ContentProvider;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.client.api.Result;
+import org.eclipse.jetty.client.util.DeferredContentProvider;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.io.RuntimeIOException;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.CountingCallback;
+import org.eclipse.jetty.util.IteratingCallback;
+import org.eclipse.jetty.util.component.Destroyable;
+
+/**
+ * <p>Servlet 3.1 asynchronous proxy servlet with capability
+ * to intercept and modify request/response content.</p>
+ * <p>Both the request processing and the I/O are asynchronous.</p>
+ *
+ * @see ProxyServlet
+ * @see AsyncProxyServlet
+ * @see ConnectHandler
+ */
+public class AsyncMiddleManServlet extends AbstractProxyServlet
+{
+    private static final String PROXY_REQUEST_COMMITTED = AsyncMiddleManServlet.class.getName() + ".proxyRequestCommitted";
+    private static final String CLIENT_TRANSFORMER = AsyncMiddleManServlet.class.getName() + ".clientTransformer";
+    private static final String SERVER_TRANSFORMER = AsyncMiddleManServlet.class.getName() + ".serverTransformer";
+
+    @Override
+    protected void service(HttpServletRequest clientRequest, HttpServletResponse proxyResponse) throws ServletException, IOException
+    {
+        String rewrittenTarget = rewriteTarget(clientRequest);
+        if (_log.isDebugEnabled())
+        {
+            StringBuffer target = clientRequest.getRequestURL();
+            if (clientRequest.getQueryString() != null)
+                target.append("?").append(clientRequest.getQueryString());
+            _log.debug("{} rewriting: {} -> {}", getRequestId(clientRequest), target, rewrittenTarget);
+        }
+        if (rewrittenTarget == null)
+        {
+            onProxyRewriteFailed(clientRequest, proxyResponse);
+            return;
+        }
+
+        final Request proxyRequest = getHttpClient().newRequest(rewrittenTarget)
+                .method(clientRequest.getMethod())
+                .version(HttpVersion.fromString(clientRequest.getProtocol()));
+
+        boolean hasContent = hasContent(clientRequest);
+
+        copyRequestHeaders(clientRequest, proxyRequest);
+
+        addProxyHeaders(clientRequest, proxyRequest);
+
+        final AsyncContext asyncContext = clientRequest.startAsync();
+        // We do not timeout the continuation, but the proxy request.
+        asyncContext.setTimeout(0);
+        proxyRequest.timeout(getTimeout(), TimeUnit.MILLISECONDS);
+
+        // If there is content, the send of the proxy request
+        // is delayed and performed when the content arrives,
+        // to allow optimization of the Content-Length header.
+        if (hasContent)
+            proxyRequest.content(newProxyContentProvider(clientRequest, proxyResponse, proxyRequest));
+        else
+            sendProxyRequest(clientRequest, proxyResponse, proxyRequest);
+    }
+
+    protected ContentProvider newProxyContentProvider(final HttpServletRequest clientRequest, HttpServletResponse proxyResponse, Request proxyRequest) throws IOException
+    {
+        ServletInputStream input = clientRequest.getInputStream();
+        DeferredContentProvider provider = new DeferredContentProvider()
+        {
+            @Override
+            public boolean offer(ByteBuffer buffer, Callback callback)
+            {
+                if (_log.isDebugEnabled())
+                    _log.debug("{} proxying content to upstream: {} bytes", getRequestId(clientRequest), buffer.remaining());
+                return super.offer(buffer, callback);
+            }
+        };
+        input.setReadListener(newProxyReadListener(clientRequest, proxyResponse, proxyRequest, provider));
+        return provider;
+    }
+
+    protected ReadListener newProxyReadListener(HttpServletRequest clientRequest, HttpServletResponse proxyResponse, Request proxyRequest, DeferredContentProvider provider)
+    {
+        return new ProxyReader(clientRequest, proxyResponse, proxyRequest, provider);
+    }
+
+    protected ProxyWriter newProxyWriteListener(HttpServletRequest clientRequest, Response proxyResponse)
+    {
+        return new ProxyWriter(clientRequest, proxyResponse);
+    }
+
+    protected Response.CompleteListener newProxyResponseListener(HttpServletRequest clientRequest, HttpServletResponse proxyResponse)
+    {
+        return new ProxyResponseListener(clientRequest, proxyResponse);
+    }
+
+    protected ContentTransformer newClientRequestContentTransformer(HttpServletRequest clientRequest, Request proxyRequest)
+    {
+        return ContentTransformer.IDENTITY;
+    }
+
+    protected ContentTransformer newServerResponseContentTransformer(HttpServletRequest clientRequest, HttpServletResponse proxyResponse, Response serverResponse)
+    {
+        return ContentTransformer.IDENTITY;
+    }
+
+    private void transform(ContentTransformer transformer, ByteBuffer input, boolean finished, List<ByteBuffer> output) throws IOException
+    {
+        try
+        {
+            transformer.transform(input, finished, output);
+        }
+        catch (Throwable x)
+        {
+            _log.info("Exception while transforming " + transformer, x);
+            throw x;
+        }
+    }
+
+    int readClientRequestContent(ServletInputStream input, byte[] buffer) throws IOException
+    {
+        return input.read(buffer);
+    }
+
+    void writeProxyResponseContent(ServletOutputStream output, ByteBuffer content) throws IOException
+    {
+        write(output, content);
+    }
+
+    private static void write(OutputStream output, ByteBuffer content) throws IOException
+    {
+        int length = content.remaining();
+        int offset = 0;
+        byte[] buffer;
+        if (content.hasArray())
+        {
+            offset = content.arrayOffset();
+            buffer = content.array();
+        }
+        else
+        {
+            buffer = new byte[length];
+            content.get(buffer);
+        }
+        output.write(buffer, offset, length);
+    }
+
+    private void cleanup(HttpServletRequest clientRequest)
+    {
+        ContentTransformer clientTransformer = (ContentTransformer)clientRequest.getAttribute(CLIENT_TRANSFORMER);
+        if (clientTransformer instanceof Destroyable)
+            ((Destroyable)clientTransformer).destroy();
+        ContentTransformer serverTransformer = (ContentTransformer)clientRequest.getAttribute(SERVER_TRANSFORMER);
+        if (serverTransformer instanceof Destroyable)
+            ((Destroyable)serverTransformer).destroy();
+    }
+
+    /**
+     * <p>Convenience extension of {@link AsyncMiddleManServlet} that offers transparent proxy functionalities.</p>
+     *
+     * @see TransparentDelegate
+     */
+    public static class Transparent extends ProxyServlet
+    {
+        private final TransparentDelegate delegate = new TransparentDelegate(this);
+
+        @Override
+        public void init(ServletConfig config) throws ServletException
+        {
+            super.init(config);
+            delegate.init(config);
+        }
+
+        @Override
+        protected String rewriteTarget(HttpServletRequest request)
+        {
+            return delegate.rewriteTarget(request);
+        }
+    }
+
+    protected class ProxyReader extends IteratingCallback implements ReadListener
+    {
+        private final byte[] buffer = new byte[getHttpClient().getRequestBufferSize()];
+        private final List<ByteBuffer> buffers = new ArrayList<>();
+        private final HttpServletRequest clientRequest;
+        private final HttpServletResponse proxyResponse;
+        private final Request proxyRequest;
+        private final DeferredContentProvider provider;
+        private final int contentLength;
+        private int length;
+
+        protected ProxyReader(HttpServletRequest clientRequest, HttpServletResponse proxyResponse, Request proxyRequest, DeferredContentProvider provider)
+        {
+            this.clientRequest = clientRequest;
+            this.proxyResponse = proxyResponse;
+            this.proxyRequest = proxyRequest;
+            this.provider = provider;
+            this.contentLength = clientRequest.getContentLength();
+        }
+
+        @Override
+        public void onDataAvailable() throws IOException
+        {
+            iterate();
+        }
+
+        @Override
+        public void onAllDataRead() throws IOException
+        {
+            if (!provider.isClosed())
+            {
+                process(BufferUtil.EMPTY_BUFFER, new Adapter()
+                {
+                    @Override
+                    public void failed(Throwable x)
+                    {
+                        onError(x);
+                    }
+                }, true);
+            }
+
+            if (_log.isDebugEnabled())
+                _log.debug("{} proxying content to upstream completed", getRequestId(clientRequest));
+        }
+
+        @Override
+        public void onError(Throwable t)
+        {
+            cleanup(clientRequest);
+            onClientRequestFailure(clientRequest, proxyRequest, proxyResponse, t);
+        }
+
+        @Override
+        protected Action process() throws Exception
+        {
+            ServletInputStream input = clientRequest.getInputStream();
+            while (input.isReady() && !input.isFinished())
+            {
+                int read = readClientRequestContent(input, buffer);
+                if (_log.isDebugEnabled())
+                    _log.debug("{} asynchronous read {} bytes on {}", getRequestId(clientRequest), read, input);
+
+                if (contentLength > 0 && read > 0)
+                    length += read;
+
+                ByteBuffer content = read > 0 ? ByteBuffer.wrap(buffer, 0, read) : BufferUtil.EMPTY_BUFFER;
+                boolean finished = read < 0 || length == contentLength;
+                process(content, this, finished);
+
+                if (read > 0)
+                    return Action.SCHEDULED;
+            }
+
+            if (input.isFinished())
+            {
+                if (_log.isDebugEnabled())
+                    _log.debug("{} asynchronous read complete on {}", getRequestId(clientRequest), input);
+                return Action.SUCCEEDED;
+            }
+            else
+            {
+                if (_log.isDebugEnabled())
+                    _log.debug("{} asynchronous read pending on {}", getRequestId(clientRequest), input);
+                return Action.IDLE;
+            }
+        }
+
+        private void process(ByteBuffer content, Callback callback, boolean finished) throws IOException
+        {
+            ContentTransformer transformer = (ContentTransformer)clientRequest.getAttribute(CLIENT_TRANSFORMER);
+            if (transformer == null)
+            {
+                transformer = newClientRequestContentTransformer(clientRequest, proxyRequest);
+                clientRequest.setAttribute(CLIENT_TRANSFORMER, transformer);
+            }
+
+            boolean committed = clientRequest.getAttribute(PROXY_REQUEST_COMMITTED) != null;
+
+            int contentBytes = content.remaining();
+
+            // Skip transformation for empty non-last buffers.
+            if (contentBytes == 0 && !finished)
+            {
+                callback.succeeded();
+                return;
+            }
+
+            transform(transformer, content, finished, buffers);
+
+            int newContentBytes = 0;
+            int size = buffers.size();
+            if (size > 0)
+            {
+                CountingCallback counter = new CountingCallback(callback, size);
+                for (int i = 0; i < size; ++i)
+                {
+                    ByteBuffer buffer = buffers.get(i);
+                    newContentBytes += buffer.remaining();
+                    provider.offer(buffer, counter);
+                }
+                buffers.clear();
+            }
+
+            if (finished)
+                provider.close();
+
+            if (_log.isDebugEnabled())
+                _log.debug("{} upstream content transformation {} -> {} bytes", getRequestId(clientRequest), contentBytes, newContentBytes);
+
+            if (!committed && (size > 0 || finished))
+            {
+                proxyRequest.header(HttpHeader.CONTENT_LENGTH, null);
+                clientRequest.setAttribute(PROXY_REQUEST_COMMITTED, true);
+                sendProxyRequest(clientRequest, proxyResponse, proxyRequest);
+            }
+
+            if (size == 0)
+                callback.succeeded();
+        }
+
+        @Override
+        protected void onCompleteFailure(Throwable x)
+        {
+            onError(x);
+        }
+    }
+
+    protected class ProxyResponseListener extends Response.Listener.Adapter implements Callback
+    {
+        private final String WRITE_LISTENER_ATTRIBUTE = AsyncMiddleManServlet.class.getName() + ".writeListener";
+
+        private final Callback complete = new CountingCallback(this, 2);
+        private final List<ByteBuffer> buffers = new ArrayList<>();
+        private final HttpServletRequest clientRequest;
+        private final HttpServletResponse proxyResponse;
+        private boolean hasContent;
+        private long contentLength;
+        private long length;
+        private Response response;
+
+        protected ProxyResponseListener(HttpServletRequest clientRequest, HttpServletResponse proxyResponse)
+        {
+            this.clientRequest = clientRequest;
+            this.proxyResponse = proxyResponse;
+        }
+
+        @Override
+        public void onBegin(Response serverResponse)
+        {
+            proxyResponse.setStatus(serverResponse.getStatus());
+        }
+
+        @Override
+        public void onHeaders(Response serverResponse)
+        {
+            contentLength = serverResponse.getHeaders().getLongField(HttpHeader.CONTENT_LENGTH.asString());
+            onServerResponseHeaders(clientRequest, proxyResponse, serverResponse);
+        }
+
+        @Override
+        public void onContent(final Response serverResponse, ByteBuffer content, final Callback callback)
+        {
+            try
+            {
+                int contentBytes = content.remaining();
+                if (_log.isDebugEnabled())
+                    _log.debug("{} received server content: {} bytes", getRequestId(clientRequest), contentBytes);
+
+                hasContent = true;
+
+                ProxyWriter proxyWriter = (ProxyWriter)clientRequest.getAttribute(WRITE_LISTENER_ATTRIBUTE);
+                boolean committed = proxyWriter != null;
+                if (proxyWriter == null)
+                {
+                    proxyWriter = newProxyWriteListener(clientRequest, serverResponse);
+                    clientRequest.setAttribute(WRITE_LISTENER_ATTRIBUTE, proxyWriter);
+                }
+
+                ContentTransformer transformer = (ContentTransformer)clientRequest.getAttribute(SERVER_TRANSFORMER);
+                if (transformer == null)
+                {
+                    transformer = newServerResponseContentTransformer(clientRequest, proxyResponse, serverResponse);
+                    clientRequest.setAttribute(SERVER_TRANSFORMER, transformer);
+                }
+
+                length += contentBytes;
+
+                boolean finished = contentLength >= 0 && length == contentLength;
+                transform(transformer, content, finished, buffers);
+
+                int newContentBytes = 0;
+                int size = buffers.size();
+                if (size > 0)
+                {
+                    Callback counter = size == 1 ? callback : new CountingCallback(callback, size);
+                    for (int i = 0; i < size; ++i)
+                    {
+                        ByteBuffer buffer = buffers.get(i);
+                        newContentBytes += buffer.remaining();
+                        proxyWriter.offer(buffer, counter);
+                    }
+                    buffers.clear();
+                }
+                else
+                {
+                    proxyWriter.offer(BufferUtil.EMPTY_BUFFER, callback);
+                }
+                if (finished)
+                    proxyWriter.offer(BufferUtil.EMPTY_BUFFER, complete);
+
+                if (_log.isDebugEnabled())
+                    _log.debug("{} downstream content transformation {} -> {} bytes", getRequestId(clientRequest), contentBytes, newContentBytes);
+
+                if (committed)
+                {
+                    proxyWriter.onWritePossible();
+                }
+                else
+                {
+                    if (contentLength >= 0)
+                        proxyResponse.setContentLength(-1);
+
+                    // Setting the WriteListener triggers an invocation to
+                    // onWritePossible(), possibly on a different thread.
+                    // We cannot succeed the callback from here, otherwise
+                    // we run into a race where the different thread calls
+                    // onWritePossible() and succeeding the callback causes
+                    // this method to be called again, which also may call
+                    // onWritePossible().
+                    proxyResponse.getOutputStream().setWriteListener(proxyWriter);
+                }
+            }
+            catch (Throwable x)
+            {
+                callback.failed(x);
+            }
+        }
+
+        @Override
+        public void onSuccess(final Response serverResponse)
+        {
+            try
+            {
+                if (hasContent)
+                {
+                    // If we had unknown length content, we need to call the
+                    // transformer to signal that the content is finished.
+                    if (contentLength < 0)
+                    {
+                        ProxyWriter proxyWriter = (ProxyWriter)clientRequest.getAttribute(WRITE_LISTENER_ATTRIBUTE);
+                        ContentTransformer transformer = (ContentTransformer)clientRequest.getAttribute(SERVER_TRANSFORMER);
+
+                        transform(transformer, BufferUtil.EMPTY_BUFFER, true, buffers);
+
+                        long newContentBytes = 0;
+                        int size = buffers.size();
+                        if (size > 0)
+                        {
+                            Callback callback = size == 1 ? complete : new CountingCallback(complete, size);
+                            for (int i = 0; i < size; ++i)
+                            {
+                                ByteBuffer buffer = buffers.get(i);
+                                newContentBytes += buffer.remaining();
+                                proxyWriter.offer(buffer, callback);
+                            }
+                            buffers.clear();
+                        }
+                        else
+                        {
+                            proxyWriter.offer(BufferUtil.EMPTY_BUFFER, complete);
+                        }
+
+                        if (_log.isDebugEnabled())
+                            _log.debug("{} downstream content transformation to {} bytes", getRequestId(clientRequest), newContentBytes);
+
+                        proxyWriter.onWritePossible();
+                    }
+                }
+                else
+                {
+                    complete.succeeded();
+                }
+            }
+            catch (Throwable x)
+            {
+                complete.failed(x);
+            }
+        }
+
+        @Override
+        public void onComplete(Result result)
+        {
+            response = result.getResponse();
+            if (result.isSucceeded())
+                complete.succeeded();
+            else
+                complete.failed(result.getFailure());
+        }
+
+        @Override
+        public void succeeded()
+        {
+            cleanup(clientRequest);
+            onProxyResponseSuccess(clientRequest, proxyResponse, response);
+        }
+
+        @Override
+        public void failed(Throwable failure)
+        {
+            cleanup(clientRequest);
+            onProxyResponseFailure(clientRequest, proxyResponse, response, failure);
+        }
+    }
+
+    protected class ProxyWriter implements WriteListener
+    {
+        private final Queue<DeferredContentProvider.Chunk> chunks = new ArrayDeque<>();
+        private final HttpServletRequest clientRequest;
+        private final Response serverResponse;
+        private DeferredContentProvider.Chunk chunk;
+        private boolean writePending;
+
+        protected ProxyWriter(HttpServletRequest clientRequest, Response serverResponse)
+        {
+            this.clientRequest = clientRequest;
+            this.serverResponse = serverResponse;
+        }
+
+        public boolean offer(ByteBuffer content, Callback callback)
+        {
+            if (_log.isDebugEnabled())
+                _log.debug("{} proxying content to downstream: {} bytes {}", getRequestId(clientRequest), content.remaining(), callback);
+            return chunks.offer(new DeferredContentProvider.Chunk(content, callback));
+        }
+
+        @Override
+        public void onWritePossible() throws IOException
+        {
+            ServletOutputStream output = clientRequest.getAsyncContext().getResponse().getOutputStream();
+
+            // If we had a pending write, let's succeed it.
+            if (writePending)
+            {
+                if (_log.isDebugEnabled())
+                    _log.debug("{} pending async write complete of {} on {}", getRequestId(clientRequest), chunk, output);
+                writePending = false;
+                if (succeed(chunk.callback))
+                    return;
+            }
+
+            int length = 0;
+            DeferredContentProvider.Chunk chunk = null;
+            while (output.isReady())
+            {
+                if (chunk != null)
+                {
+                    if (_log.isDebugEnabled())
+                        _log.debug("{} async write complete of {} ({} bytes) on {}", getRequestId(clientRequest), chunk, length, output);
+                    if (succeed(chunk.callback))
+                        return;
+                }
+
+                this.chunk = chunk = chunks.poll();
+                if (chunk == null)
+                    return;
+
+                length = chunk.buffer.remaining();
+                if (length > 0)
+                    writeProxyResponseContent(output, chunk.buffer);
+            }
+
+            if (_log.isDebugEnabled())
+                _log.debug("{} async write pending of {} ({} bytes) on {}", getRequestId(clientRequest), chunk, length, output);
+            writePending = true;
+        }
+
+        private boolean succeed(Callback callback)
+        {
+            // Succeeding the callback may cause to reenter in onWritePossible()
+            // because typically the callback is the one that controls whether the
+            // content received from the server has been consumed, so succeeding
+            // the callback causes more content to be received from the server,
+            // and hence more to be written to the client by onWritePossible().
+            // A reentrant call to onWritePossible() performs another write,
+            // which may remain pending, which means that the reentrant call
+            // to onWritePossible() returns all the way back to just after the
+            // succeed of the callback. There, we cannot just loop attempting
+            // write, but we need to check whether we are write pending.
+            callback.succeeded();
+            return writePending;
+        }
+
+        @Override
+        public void onError(Throwable failure)
+        {
+            DeferredContentProvider.Chunk chunk = this.chunk;
+            if (chunk != null)
+                chunk.callback.failed(failure);
+            else
+                serverResponse.abort(failure);
+        }
+    }
+
+    /**
+     * <p>Allows applications to transform upstream and downstream content.</p>
+     * <p>Typical use cases of transformations are URL rewriting of HTML anchors
+     * (where the value of the <code>href</code> attribute of <a> elements
+     * is modified by the proxy), field renaming of JSON documents, etc.</p>
+     * <p>Applications should override {@link #newClientRequestContentTransformer(HttpServletRequest, Request)}
+     * and/or {@link #newServerResponseContentTransformer(HttpServletRequest, HttpServletResponse, Response)}
+     * to provide the transformer implementation.</p>
+     */
+    public interface ContentTransformer
+    {
+        /**
+         * The identity transformer that does not perform any transformation.
+         */
+        public static final ContentTransformer IDENTITY = new IdentityContentTransformer();
+
+        /**
+         * <p>Transforms the given input byte buffers into (possibly multiple) byte buffers.</p>
+         * <p>The transformation must happen synchronously in the context of a call
+         * to this method (it is not supported to perform the transformation in another
+         * thread spawned during the call to this method).
+         * The transformation may happen or not, depending on the transformer implementation.
+         * For example, a buffering transformer may buffer the input aside, and only
+         * perform the transformation when the whole input is provided (by looking at the
+         * {@code finished} flag).</p>
+         * <p>The input buffer will be cleared and reused after the call to this method.
+         * Implementations that want to buffer aside the input (or part of it) must copy
+         * the input bytes that they want to buffer.</p>
+         * <p>Typical implementations:</p>
+         * <pre>
+         * // Identity transformation (no transformation, the input is copied to the output)
+         * public void transform(ByteBuffer input, boolean finished, List<ByteBuffer> output)
+         * {
+         *     output.add(input);
+         * }
+         *
+         * // Discard transformation (all input is discarded)
+         * public void transform(ByteBuffer input, boolean finished, List<ByteBuffer> output)
+         * {
+         *     // Empty
+         * }
+         *
+         * // Buffering identity transformation (all input is buffered aside until it is finished)
+         * public void transform(ByteBuffer input, boolean finished, List<ByteBuffer> output)
+         * {
+         *     ByteBuffer copy = ByteBuffer.allocate(input.remaining());
+         *     copy.put(input).flip();
+         *     store(copy);
+         *
+         *     if (finished)
+         *     {
+         *         List<ByteBuffer> copies = retrieve();
+         *         output.addAll(copies);
+         *     }
+         * }
+         * </pre>
+         *
+         * @param input the input content to transform (may be of length zero)
+         * @param finished whether the input content is finished or more will come
+         * @param output where to put the transformed output content
+         * @throws IOException in case of transformation failures
+         */
+        public void transform(ByteBuffer input, boolean finished, List<ByteBuffer> output) throws IOException;
+    }
+
+    private static class IdentityContentTransformer implements ContentTransformer
+    {
+        @Override
+        public void transform(ByteBuffer input, boolean finished, List<ByteBuffer> output)
+        {
+            output.add(input);
+        }
+    }
+
+    public static class GZIPContentTransformer implements ContentTransformer
+    {
+        private final List<ByteBuffer> buffers = new ArrayList<>(2);
+        private final ContentDecoder decoder = new GZIPContentDecoder();
+        private final ContentTransformer transformer;
+        private final ByteArrayOutputStream out;
+        private final GZIPOutputStream gzipOut;
+
+        public GZIPContentTransformer(ContentTransformer transformer)
+        {
+            try
+            {
+                this.transformer = transformer;
+                this.out = new ByteArrayOutputStream();
+                this.gzipOut = new GZIPOutputStream(out);
+            }
+            catch (IOException x)
+            {
+                throw new RuntimeIOException(x);
+            }
+        }
+
+        @Override
+        public void transform(ByteBuffer input, boolean finished, List<ByteBuffer> output) throws IOException
+        {
+            if (!input.hasRemaining())
+            {
+                if (finished)
+                    transformer.transform(input, true, buffers);
+            }
+            else
+            {
+                while (input.hasRemaining())
+                {
+                    ByteBuffer decoded = decoder.decode(input);
+                    if (decoded.hasRemaining())
+                        transformer.transform(decoded, finished && !input.hasRemaining(), buffers);
+                }
+            }
+
+            if (!buffers.isEmpty() || finished)
+            {
+                ByteBuffer result = gzip(buffers, finished);
+                buffers.clear();
+                output.add(result);
+            }
+        }
+
+        private ByteBuffer gzip(List<ByteBuffer> buffers, boolean finished) throws IOException
+        {
+            for (ByteBuffer buffer : buffers)
+                write(gzipOut, buffer);
+            if (finished)
+                gzipOut.close();
+            byte[] gzipBytes = out.toByteArray();
+            out.reset();
+            return ByteBuffer.wrap(gzipBytes);
+        }
+    }
+}
diff --git a/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/AsyncProxyServlet.java b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/AsyncProxyServlet.java
new file mode 100644
index 0000000..1ca252c
--- /dev/null
+++ b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/AsyncProxyServlet.java
@@ -0,0 +1,303 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.proxy;
+
+import java.io.IOException;
+import java.net.URI;
+import java.nio.ByteBuffer;
+import java.nio.channels.WritePendingException;
+
+import javax.servlet.ReadListener;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletInputStream;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.WriteListener;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.client.api.ContentProvider;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.client.util.DeferredContentProvider;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.IteratingCallback;
+
+/**
+ * <p>Servlet 3.1 asynchronous proxy servlet.</p>
+ * <p>Both the request processing and the I/O are asynchronous.</p>
+ *
+ * @see ProxyServlet
+ * @see AsyncMiddleManServlet
+ * @see ConnectHandler
+ */
+public class AsyncProxyServlet extends ProxyServlet
+{
+    private static final String WRITE_LISTENER_ATTRIBUTE = AsyncProxyServlet.class.getName() + ".writeListener";
+
+    @Override
+    protected ContentProvider proxyRequestContent(Request proxyRequest, HttpServletRequest request) throws IOException
+    {
+        ServletInputStream input = request.getInputStream();
+        DeferredContentProvider provider = new DeferredContentProvider();
+        input.setReadListener(newReadListener(proxyRequest, request, provider));
+        return provider;
+    }
+
+    protected ReadListener newReadListener(Request proxyRequest, HttpServletRequest request, DeferredContentProvider provider)
+    {
+        return new StreamReader(proxyRequest, request, provider);
+    }
+
+    @Override
+    protected void onResponseContent(HttpServletRequest request, HttpServletResponse response, Response proxyResponse, byte[] buffer, int offset, int length, Callback callback)
+    {
+        try
+        {
+            if (_log.isDebugEnabled())
+                _log.debug("{} proxying content to downstream: {} bytes", getRequestId(request), length);
+            StreamWriter writeListener = (StreamWriter)request.getAttribute(WRITE_LISTENER_ATTRIBUTE);
+            if (writeListener == null)
+            {
+                writeListener = newWriteListener(request, proxyResponse);
+                request.setAttribute(WRITE_LISTENER_ATTRIBUTE, writeListener);
+
+                // Set the data to write before calling setWriteListener(), because
+                // setWriteListener() may trigger the call to onWritePossible() on
+                // a different thread and we would have a race.
+                writeListener.data(buffer, offset, length, callback);
+
+                // Setting the WriteListener triggers an invocation to onWritePossible().
+                response.getOutputStream().setWriteListener(writeListener);
+            }
+            else
+            {
+                writeListener.data(buffer, offset, length, callback);
+                writeListener.onWritePossible();
+            }
+        }
+        catch (Throwable x)
+        {
+            callback.failed(x);
+            proxyResponse.abort(x);
+        }
+    }
+
+    protected StreamWriter newWriteListener(HttpServletRequest request, Response proxyResponse)
+    {
+        return new StreamWriter(request, proxyResponse);
+    }
+
+    /**
+     * <p>Convenience extension of {@link AsyncProxyServlet} that offers transparent proxy functionalities.</p>
+     *
+     * @see TransparentDelegate
+     */
+    public static class Transparent extends AsyncProxyServlet
+    {
+        private final TransparentDelegate delegate = new TransparentDelegate(this);
+
+        @Override
+        public void init(ServletConfig config) throws ServletException
+        {
+            super.init(config);
+            delegate.init(config);
+        }
+
+        @Override
+        protected URI rewriteURI(HttpServletRequest request)
+        {
+            return URI.create(delegate.rewriteTarget(request));
+        }
+    }
+
+    protected class StreamReader extends IteratingCallback implements ReadListener
+    {
+        private final byte[] buffer = new byte[getHttpClient().getRequestBufferSize()];
+        private final Request proxyRequest;
+        private final HttpServletRequest request;
+        private final DeferredContentProvider provider;
+
+        protected StreamReader(Request proxyRequest, HttpServletRequest request, DeferredContentProvider provider)
+        {
+            this.proxyRequest = proxyRequest;
+            this.request = request;
+            this.provider = provider;
+        }
+
+        @Override
+        public void onDataAvailable() throws IOException
+        {
+            iterate();
+        }
+
+        @Override
+        public void onAllDataRead() throws IOException
+        {
+            if (_log.isDebugEnabled())
+                _log.debug("{} proxying content to upstream completed", getRequestId(request));
+            provider.close();
+        }
+
+        @Override
+        public void onError(Throwable t)
+        {
+            onClientRequestFailure(proxyRequest, request, t);
+        }
+
+        @Override
+        protected Action process() throws Exception
+        {
+            int requestId = _log.isDebugEnabled() ? getRequestId(request) : 0;
+            ServletInputStream input = request.getInputStream();
+
+            // First check for isReady() because it has
+            // side effects, and then for isFinished().
+            while (input.isReady() && !input.isFinished())
+            {
+                int read = input.read(buffer);
+                if (_log.isDebugEnabled())
+                    _log.debug("{} asynchronous read {} bytes on {}", requestId, read, input);
+                if (read > 0)
+                {
+                    if (_log.isDebugEnabled())
+                        _log.debug("{} proxying content to upstream: {} bytes", requestId, read);
+                    onRequestContent(proxyRequest, request, provider, buffer, 0, read, this);
+                    return Action.SCHEDULED;
+                }
+            }
+
+            if (input.isFinished())
+            {
+                if (_log.isDebugEnabled())
+                    _log.debug("{} asynchronous read complete on {}", requestId, input);
+                return Action.SUCCEEDED;
+            }
+            else
+            {
+                if (_log.isDebugEnabled())
+                    _log.debug("{} asynchronous read pending on {}", requestId, input);
+                return Action.IDLE;
+            }
+        }
+
+        protected void onRequestContent(Request proxyRequest, HttpServletRequest request, DeferredContentProvider provider, byte[] buffer, int offset, int length, Callback callback)
+        {
+            provider.offer(ByteBuffer.wrap(buffer, offset, length), callback);
+        }
+
+        @Override
+        public void failed(Throwable x)
+        {
+            super.failed(x);
+            onError(x);
+        }
+    }
+
+    protected class StreamWriter implements WriteListener
+    {
+        private final HttpServletRequest request;
+        private final Response proxyResponse;
+        private WriteState state;
+        private byte[] buffer;
+        private int offset;
+        private int length;
+        private Callback callback;
+
+        protected StreamWriter(HttpServletRequest request, Response proxyResponse)
+        {
+            this.request = request;
+            this.proxyResponse = proxyResponse;
+            this.state = WriteState.IDLE;
+        }
+
+        protected void data(byte[] bytes, int offset, int length, Callback callback)
+        {
+            if (state != WriteState.IDLE)
+                throw new WritePendingException();
+            this.state = WriteState.READY;
+            this.buffer = bytes;
+            this.offset = offset;
+            this.length = length;
+            this.callback = callback;
+        }
+
+        @Override
+        public void onWritePossible() throws IOException
+        {
+            int requestId = getRequestId(request);
+            ServletOutputStream output = request.getAsyncContext().getResponse().getOutputStream();
+            if (state == WriteState.READY)
+            {
+                // There is data to write.
+                if (_log.isDebugEnabled())
+                    _log.debug("{} asynchronous write start of {} bytes on {}", requestId, length, output);
+                output.write(buffer, offset, length);
+                state = WriteState.PENDING;
+                if (output.isReady())
+                {
+                    if (_log.isDebugEnabled())
+                        _log.debug("{} asynchronous write of {} bytes completed on {}", requestId, length, output);
+                    complete();
+                }
+                else
+                {
+                    if (_log.isDebugEnabled())
+                        _log.debug("{} asynchronous write of {} bytes pending on {}", requestId, length, output);
+                }
+            }
+            else if (state == WriteState.PENDING)
+            {
+                // The write blocked but is now complete.
+                if (_log.isDebugEnabled())
+                    _log.debug("{} asynchronous write of {} bytes completing on {}", requestId, length, output);
+                complete();
+            }
+            else
+            {
+                throw new IllegalStateException();
+            }
+        }
+
+        protected void complete()
+        {
+            buffer = null;
+            offset = 0;
+            length = 0;
+            Callback c = callback;
+            callback = null;
+            state = WriteState.IDLE;
+            // Call the callback only after the whole state has been reset,
+            // because the callback may trigger a reentrant call and
+            // the state must already be the new one that we reset here.
+            c.succeeded();
+        }
+
+        @Override
+        public void onError(Throwable failure)
+        {
+            proxyResponse.abort(failure);
+        }
+    }
+
+    private enum WriteState
+    {
+        READY, PENDING, IDLE
+    }
+}
diff --git a/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/BalancerServlet.java b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/BalancerServlet.java
new file mode 100644
index 0000000..eaac8f0
--- /dev/null
+++ b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/BalancerServlet.java
@@ -0,0 +1,314 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.proxy;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicLong;
+import javax.servlet.ServletException;
+import javax.servlet.UnavailableException;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+
+import org.eclipse.jetty.util.URIUtil;
+
+public class BalancerServlet extends ProxyServlet
+{
+    private static final String BALANCER_MEMBER_PREFIX = "balancerMember.";
+    private static final List<String> FORBIDDEN_CONFIG_PARAMETERS;
+
+    static
+    {
+        List<String> params = new LinkedList<>();
+        params.add("hostHeader");
+        params.add("whiteList");
+        params.add("blackList");
+        FORBIDDEN_CONFIG_PARAMETERS = Collections.unmodifiableList(params);
+    }
+
+    private static final List<String> REVERSE_PROXY_HEADERS;
+
+    static
+    {
+        List<String> params = new LinkedList<>();
+        params.add("Location");
+        params.add("Content-Location");
+        params.add("URI");
+        REVERSE_PROXY_HEADERS = Collections.unmodifiableList(params);
+    }
+
+    private static final String JSESSIONID = "jsessionid";
+    private static final String JSESSIONID_URL_PREFIX = JSESSIONID + "=";
+
+    private final List<BalancerMember> _balancerMembers = new ArrayList<>();
+    private final AtomicLong counter = new AtomicLong();
+    private boolean _stickySessions;
+    private boolean _proxyPassReverse;
+
+    @Override
+    public void init() throws ServletException
+    {
+        validateConfig();
+        super.init();
+        initStickySessions();
+        initBalancers();
+        initProxyPassReverse();
+    }
+
+    private void validateConfig() throws ServletException
+    {
+        for (String initParameterName : Collections.list(getServletConfig().getInitParameterNames()))
+        {
+            if (FORBIDDEN_CONFIG_PARAMETERS.contains(initParameterName))
+            {
+                throw new UnavailableException(initParameterName + " not supported in " + getClass().getName());
+            }
+        }
+    }
+
+    private void initStickySessions()
+    {
+        _stickySessions = Boolean.parseBoolean(getServletConfig().getInitParameter("stickySessions"));
+    }
+
+    private void initBalancers() throws ServletException
+    {
+        Set<BalancerMember> members = new HashSet<>();
+        for (String balancerName : getBalancerNames())
+        {
+            String memberProxyToParam = BALANCER_MEMBER_PREFIX + balancerName + ".proxyTo";
+            String proxyTo = getServletConfig().getInitParameter(memberProxyToParam);
+            if (proxyTo == null || proxyTo.trim().length() == 0)
+                throw new UnavailableException(memberProxyToParam + " parameter is empty.");
+            members.add(new BalancerMember(balancerName, proxyTo));
+        }
+        _balancerMembers.addAll(members);
+    }
+
+    private void initProxyPassReverse()
+    {
+        _proxyPassReverse = Boolean.parseBoolean(getServletConfig().getInitParameter("proxyPassReverse"));
+    }
+
+    private Set<String> getBalancerNames() throws ServletException
+    {
+        Set<String> names = new HashSet<>();
+        for (String initParameterName : Collections.list(getServletConfig().getInitParameterNames()))
+        {
+            if (!initParameterName.startsWith(BALANCER_MEMBER_PREFIX))
+                continue;
+
+            int endOfNameIndex = initParameterName.lastIndexOf(".");
+            if (endOfNameIndex <= BALANCER_MEMBER_PREFIX.length())
+                throw new UnavailableException(initParameterName + " parameter does not provide a balancer member name");
+
+            names.add(initParameterName.substring(BALANCER_MEMBER_PREFIX.length(), endOfNameIndex));
+        }
+        return names;
+    }
+
+    @Override
+    protected URI rewriteURI(HttpServletRequest request)
+    {
+        BalancerMember balancerMember = selectBalancerMember(request);
+        if (_log.isDebugEnabled())
+            _log.debug("Selected {}", balancerMember);
+        String path = request.getRequestURI();
+        String query = request.getQueryString();
+        if (query != null)
+            path += "?" + query;
+        return URI.create(balancerMember.getProxyTo() + "/" + path).normalize();
+    }
+
+    private BalancerMember selectBalancerMember(HttpServletRequest request)
+    {
+        if (_stickySessions)
+        {
+            String name = getBalancerMemberNameFromSessionId(request);
+            if (name != null)
+            {
+                BalancerMember balancerMember = findBalancerMemberByName(name);
+                if (balancerMember != null)
+                    return balancerMember;
+            }
+        }
+        int index = (int)(counter.getAndIncrement() % _balancerMembers.size());
+        return _balancerMembers.get(index);
+    }
+
+    private BalancerMember findBalancerMemberByName(String name)
+    {
+        for (BalancerMember balancerMember : _balancerMembers)
+        {
+            if (balancerMember.getName().equals(name))
+                return balancerMember;
+        }
+        return null;
+    }
+
+    private String getBalancerMemberNameFromSessionId(HttpServletRequest request)
+    {
+        String name = getBalancerMemberNameFromSessionCookie(request);
+        if (name == null)
+            name = getBalancerMemberNameFromURL(request);
+        return name;
+    }
+
+    private String getBalancerMemberNameFromSessionCookie(HttpServletRequest request)
+    {
+        Cookie[] cookies = request.getCookies();
+        if (cookies != null)
+        {
+            for (Cookie cookie : cookies)
+            {
+                if (JSESSIONID.equalsIgnoreCase(cookie.getName()))
+                    return extractBalancerMemberNameFromSessionId(cookie.getValue());
+            }
+        }
+        return null;
+    }
+
+    private String getBalancerMemberNameFromURL(HttpServletRequest request)
+    {
+        String requestURI = request.getRequestURI();
+        int idx = requestURI.lastIndexOf(";");
+        if (idx > 0)
+        {
+            String requestURISuffix = requestURI.substring(idx + 1);
+            if (requestURISuffix.startsWith(JSESSIONID_URL_PREFIX))
+                return extractBalancerMemberNameFromSessionId(requestURISuffix.substring(JSESSIONID_URL_PREFIX.length()));
+        }
+        return null;
+    }
+
+    private String extractBalancerMemberNameFromSessionId(String sessionId)
+    {
+        int idx = sessionId.lastIndexOf(".");
+        if (idx > 0)
+        {
+            String sessionIdSuffix = sessionId.substring(idx + 1);
+            return sessionIdSuffix.length() > 0 ? sessionIdSuffix : null;
+        }
+        return null;
+    }
+
+    @Override
+    protected String filterResponseHeader(HttpServletRequest request, String headerName, String headerValue)
+    {
+        if (_proxyPassReverse && REVERSE_PROXY_HEADERS.contains(headerName))
+        {
+            URI locationURI = URI.create(headerValue).normalize();
+            if (locationURI.isAbsolute() && isBackendLocation(locationURI))
+            {
+                StringBuilder newURI = URIUtil.newURIBuilder(request.getScheme(), request.getServerName(), request.getServerPort());
+                String component = locationURI.getRawPath();
+                if (component != null)
+                    newURI.append(component);
+                component = locationURI.getRawQuery();
+                if (component != null)
+                    newURI.append('?').append(component);
+                component = locationURI.getRawFragment();
+                if (component != null)
+                    newURI.append('#').append(component);
+                return URI.create(newURI.toString()).normalize().toString();
+            }
+        }
+        return headerValue;
+    }
+
+    private boolean isBackendLocation(URI locationURI)
+    {
+        for (BalancerMember balancerMember : _balancerMembers)
+        {
+            URI backendURI = balancerMember.getBackendURI();
+            if (backendURI.getHost().equals(locationURI.getHost()) &&
+                    backendURI.getScheme().equals(locationURI.getScheme())
+                    && backendURI.getPort() == locationURI.getPort())
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public boolean validateDestination(String host, int port)
+    {
+        return true;
+    }
+
+    private static class BalancerMember
+    {
+        private final String _name;
+        private final String _proxyTo;
+        private final URI _backendURI;
+
+        public BalancerMember(String name, String proxyTo)
+        {
+            _name = name;
+            _proxyTo = proxyTo;
+            _backendURI = URI.create(_proxyTo).normalize();
+        }
+
+        public String getName()
+        {
+            return _name;
+        }
+
+        public String getProxyTo()
+        {
+            return _proxyTo;
+        }
+
+        public URI getBackendURI()
+        {
+            return _backendURI;
+        }
+
+        @Override
+        public String toString()
+        {
+            return String.format("%s[name=%s,proxyTo=%s]", getClass().getSimpleName(), _name, _proxyTo);
+        }
+
+        @Override
+        public int hashCode()
+        {
+            return _name.hashCode();
+        }
+
+        @Override
+        public boolean equals(Object obj)
+        {
+            if (this == obj)
+                return true;
+            if (obj == null)
+                return false;
+            if (getClass() != obj.getClass())
+                return false;
+            BalancerMember that = (BalancerMember)obj;
+            return _name.equals(that._name);
+        }
+    }
+}
diff --git a/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ConnectHandler.java b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ConnectHandler.java
new file mode 100644
index 0000000..cc2a9f7
--- /dev/null
+++ b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ConnectHandler.java
@@ -0,0 +1,634 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.proxy;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.SocketChannel;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.Executor;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpHeaderValue;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.MappedByteBufferPool;
+import org.eclipse.jetty.io.SelectChannelEndPoint;
+import org.eclipse.jetty.io.SelectorManager;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.HttpConnection;
+import org.eclipse.jetty.server.HttpTransport;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.handler.HandlerWrapper;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+/**
+ * <p>Implementation of a {@link Handler} that supports HTTP CONNECT.</p>
+ */
+public class ConnectHandler extends HandlerWrapper
+{
+    protected static final Logger LOG = Log.getLogger(ConnectHandler.class);
+
+    private final Set<String> whiteList = new HashSet<>();
+    private final Set<String> blackList = new HashSet<>();
+    private Executor executor;
+    private Scheduler scheduler;
+    private ByteBufferPool bufferPool;
+    private SelectorManager selector;
+    private long connectTimeout = 15000;
+    private long idleTimeout = 30000;
+    private int bufferSize = 4096;
+
+    public ConnectHandler()
+    {
+        this(null);
+    }
+
+    public ConnectHandler(Handler handler)
+    {
+        setHandler(handler);
+    }
+
+    public Executor getExecutor()
+    {
+        return executor;
+    }
+
+    public void setExecutor(Executor executor)
+    {
+        this.executor = executor;
+    }
+
+    public Scheduler getScheduler()
+    {
+        return scheduler;
+    }
+
+    public void setScheduler(Scheduler scheduler)
+    {
+        this.scheduler = scheduler;
+    }
+
+    public ByteBufferPool getByteBufferPool()
+    {
+        return bufferPool;
+    }
+
+    public void setByteBufferPool(ByteBufferPool bufferPool)
+    {
+        this.bufferPool = bufferPool;
+    }
+
+    /**
+     * @return the timeout, in milliseconds, to connect to the remote server
+     */
+    public long getConnectTimeout()
+    {
+        return connectTimeout;
+    }
+
+    /**
+     * @param connectTimeout the timeout, in milliseconds, to connect to the remote server
+     */
+    public void setConnectTimeout(long connectTimeout)
+    {
+        this.connectTimeout = connectTimeout;
+    }
+
+    /**
+     * @return the idle timeout, in milliseconds
+     */
+    public long getIdleTimeout()
+    {
+        return idleTimeout;
+    }
+
+    /**
+     * @param idleTimeout the idle timeout, in milliseconds
+     */
+    public void setIdleTimeout(long idleTimeout)
+    {
+        this.idleTimeout = idleTimeout;
+    }
+
+    public int getBufferSize()
+    {
+        return bufferSize;
+    }
+
+    public void setBufferSize(int bufferSize)
+    {
+        this.bufferSize = bufferSize;
+    }
+
+    @Override
+    protected void doStart() throws Exception
+    {
+        if (executor == null)
+        {
+            setExecutor(getServer().getThreadPool());
+        }
+        if (scheduler == null)
+        {
+            setScheduler(new ScheduledExecutorScheduler());
+            addBean(getScheduler());
+        }
+        if (bufferPool == null)
+        {
+            setByteBufferPool(new MappedByteBufferPool());
+            addBean(getByteBufferPool());
+        }
+        addBean(selector = newSelectorManager());
+        selector.setConnectTimeout(getConnectTimeout());
+        super.doStart();
+    }
+
+    protected SelectorManager newSelectorManager()
+    {
+        return new ConnectManager(getExecutor(), getScheduler(), 1);
+    }
+
+    @Override
+    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+    {
+        if (HttpMethod.CONNECT.is(request.getMethod()))
+        {
+            String serverAddress = request.getRequestURI();
+            if (LOG.isDebugEnabled())
+                LOG.debug("CONNECT request for {}", serverAddress);
+            try
+            {
+                handleConnect(baseRequest, request, response, serverAddress);
+            }
+            catch (Exception x)
+            {
+                // TODO
+                LOG.warn("ConnectHandler " + baseRequest.getUri() + " " + x);
+                LOG.debug(x);
+            }
+        }
+        else
+        {
+            super.handle(target, baseRequest, request, response);
+        }
+    }
+
+    /**
+     * <p>Handles a CONNECT request.</p>
+     * <p>CONNECT requests may have authentication headers such as {@code Proxy-Authorization}
+     * that authenticate the client with the proxy.</p>
+     *
+     * @param baseRequest  Jetty-specific http request
+     * @param request       the http request
+     * @param response      the http response
+     * @param serverAddress the remote server address in the form {@code host:port}
+     */
+    protected void handleConnect(Request baseRequest, HttpServletRequest request, HttpServletResponse response, String serverAddress)
+    {
+        baseRequest.setHandled(true);
+        try
+        {
+            boolean proceed = handleAuthentication(request, response, serverAddress);
+            if (!proceed)
+            {
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Missing proxy authentication");
+                sendConnectResponse(request, response, HttpServletResponse.SC_PROXY_AUTHENTICATION_REQUIRED);
+                return;
+            }
+
+            String host = serverAddress;
+            int port = 80;
+            int colon = serverAddress.indexOf(':');
+            if (colon > 0)
+            {
+                host = serverAddress.substring(0, colon);
+                port = Integer.parseInt(serverAddress.substring(colon + 1));
+            }
+
+            if (!validateDestination(host, port))
+            {
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Destination {}:{} forbidden", host, port);
+                sendConnectResponse(request, response, HttpServletResponse.SC_FORBIDDEN);
+                return;
+            }
+
+            SocketChannel channel = SocketChannel.open();
+            channel.socket().setTcpNoDelay(true);
+            channel.configureBlocking(false);
+
+            AsyncContext asyncContext = request.startAsync();
+            asyncContext.setTimeout(0);
+
+            HttpTransport transport = baseRequest.getHttpChannel().getHttpTransport();
+            
+            // TODO Handle CONNECT over HTTP2!
+            if (!(transport instanceof HttpConnection))
+            {
+                if (LOG.isDebugEnabled())
+                    LOG.debug("CONNECT forbidden for {}", transport);
+                sendConnectResponse(request, response, HttpServletResponse.SC_FORBIDDEN);
+                return;
+            }
+
+            InetSocketAddress address = newConnectAddress(host, port);
+            if (LOG.isDebugEnabled())
+                LOG.debug("Connecting to {}", address);
+            ConnectContext connectContext = new ConnectContext(request, response, asyncContext, (HttpConnection)transport);
+            if (channel.connect(address))
+                selector.accept(channel, connectContext);
+            else
+                selector.connect(channel, connectContext);
+        }
+        catch (Exception x)
+        {
+            onConnectFailure(request, response, null, x);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Create the address the connect channel will connect to.
+     * @param host The host from the connect request
+     * @param port The port from the connect request
+     * @return The InetSocketAddress to connect to.
+     */
+    protected InetSocketAddress newConnectAddress(String host, int port)
+    {
+        return new InetSocketAddress(host, port);
+    }
+    
+    protected void onConnectSuccess(ConnectContext connectContext, UpstreamConnection upstreamConnection)
+    {
+        HttpConnection httpConnection = connectContext.getHttpConnection();
+        ByteBuffer requestBuffer = httpConnection.getRequestBuffer();
+        ByteBuffer buffer = BufferUtil.EMPTY_BUFFER;
+        int remaining = requestBuffer.remaining();
+        if (remaining > 0)
+        {
+            buffer = bufferPool.acquire(remaining, requestBuffer.isDirect());
+            BufferUtil.flipToFill(buffer);
+            buffer.put(requestBuffer);
+            buffer.flip();
+        }
+
+        ConcurrentMap<String, Object> context = connectContext.getContext();
+        HttpServletRequest request = connectContext.getRequest();
+        prepareContext(request, context);
+
+        EndPoint downstreamEndPoint = httpConnection.getEndPoint();
+        DownstreamConnection downstreamConnection = newDownstreamConnection(downstreamEndPoint, context, buffer);
+        downstreamConnection.setInputBufferSize(getBufferSize());
+
+        upstreamConnection.setConnection(downstreamConnection);
+        downstreamConnection.setConnection(upstreamConnection);
+        if (LOG.isDebugEnabled())
+            LOG.debug("Connection setup completed: {}<->{}", downstreamConnection, upstreamConnection);
+
+        HttpServletResponse response = connectContext.getResponse();
+        sendConnectResponse(request, response, HttpServletResponse.SC_OK);
+
+        upgradeConnection(request, response, downstreamConnection);
+        connectContext.getAsyncContext().complete();
+    }
+
+    protected void onConnectFailure(HttpServletRequest request, HttpServletResponse response, AsyncContext asyncContext, Throwable failure)
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("CONNECT failed", failure);
+        sendConnectResponse(request, response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+        if (asyncContext != null)
+            asyncContext.complete();
+    }
+
+    private void sendConnectResponse(HttpServletRequest request, HttpServletResponse response, int statusCode)
+    {
+        try
+        {
+            response.setStatus(statusCode);
+            if (statusCode != HttpServletResponse.SC_OK)
+                response.setHeader(HttpHeader.CONNECTION.asString(), HttpHeaderValue.CLOSE.asString());
+            response.getOutputStream().close();
+            if (LOG.isDebugEnabled())
+                LOG.debug("CONNECT response sent {} {}", request.getProtocol(), response.getStatus());
+        }
+        catch (IOException x)
+        {
+            // TODO: nothing we can do, close the connection
+        }
+    }
+
+    /**
+     * <p>Handles the authentication before setting up the tunnel to the remote server.</p>
+     * <p>The default implementation returns true.</p>
+     *
+     * @param request  the HTTP request
+     * @param response the HTTP response
+     * @param address  the address of the remote server in the form {@code host:port}.
+     * @return true to allow to connect to the remote host, false otherwise
+     */
+    protected boolean handleAuthentication(HttpServletRequest request, HttpServletResponse response, String address)
+    {
+        return true;
+    }
+
+    protected DownstreamConnection newDownstreamConnection(EndPoint endPoint, ConcurrentMap<String, Object> context, ByteBuffer buffer)
+    {
+        return new DownstreamConnection(endPoint, getExecutor(), getByteBufferPool(), context, buffer);
+    }
+
+    protected UpstreamConnection newUpstreamConnection(EndPoint endPoint, ConnectContext connectContext)
+    {
+        return new UpstreamConnection(endPoint, getExecutor(), getByteBufferPool(), connectContext);
+    }
+
+    protected void prepareContext(HttpServletRequest request, ConcurrentMap<String, Object> context)
+    {
+    }
+
+    private void upgradeConnection(HttpServletRequest request, HttpServletResponse response, Connection connection)
+    {
+        // Set the new connection as request attribute and change the status to 101
+        // so that Jetty understands that it has to upgrade the connection
+        request.setAttribute(HttpConnection.UPGRADE_CONNECTION_ATTRIBUTE, connection);
+        response.setStatus(HttpServletResponse.SC_SWITCHING_PROTOCOLS);
+        if (LOG.isDebugEnabled())
+            LOG.debug("Upgraded connection to {}", connection);
+    }
+
+    /**
+     * <p>Reads (with non-blocking semantic) into the given {@code buffer} from the given {@code endPoint}.</p>
+     *
+     * @param endPoint the endPoint to read from
+     * @param buffer   the buffer to read data into
+     * @return the number of bytes read (possibly 0 since the read is non-blocking)
+     *         or -1 if the channel has been closed remotely
+     * @throws IOException if the endPoint cannot be read
+     */
+    protected int read(EndPoint endPoint, ByteBuffer buffer) throws IOException
+    {
+        return endPoint.fill(buffer);
+    }
+
+    /**
+     * <p>Writes (with non-blocking semantic) the given buffer of data onto the given endPoint.</p>
+     *
+     * @param endPoint the endPoint to write to
+     * @param buffer   the buffer to write
+     * @param callback the completion callback to invoke
+     */
+    protected void write(EndPoint endPoint, ByteBuffer buffer, Callback callback)
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("{} writing {} bytes", this, buffer.remaining());
+        endPoint.write(callback, buffer);
+    }
+
+    public Set<String> getWhiteListHosts()
+    {
+        return whiteList;
+    }
+
+    public Set<String> getBlackListHosts()
+    {
+        return blackList;
+    }
+
+    /**
+     * Checks the given {@code host} and {@code port} against whitelist and blacklist.
+     *
+     * @param host the host to check
+     * @param port the port to check
+     * @return true if it is allowed to connect to the given host and port
+     */
+    public boolean validateDestination(String host, int port)
+    {
+        String hostPort = host + ":" + port;
+        if (!whiteList.isEmpty())
+        {
+            if (!whiteList.contains(hostPort))
+            {
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Host {}:{} not whitelisted", host, port);
+                return false;
+            }
+        }
+        if (!blackList.isEmpty())
+        {
+            if (blackList.contains(hostPort))
+            {
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Host {}:{} blacklisted", host, port);
+                return false;
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public void dump(Appendable out, String indent) throws IOException
+    {
+        dumpThis(out);
+        dump(out, indent, getBeans(), TypeUtil.asList(getHandlers()));
+    }
+
+    protected class ConnectManager extends SelectorManager
+    {
+        protected ConnectManager(Executor executor, Scheduler scheduler, int selectors)
+        {
+            super(executor, scheduler, selectors);
+        }
+
+        @Override
+        protected EndPoint newEndPoint(SocketChannel channel, ManagedSelector selector, SelectionKey selectionKey) throws IOException
+        {
+            return new SelectChannelEndPoint(channel, selector, selectionKey, getScheduler(), getIdleTimeout());
+        }
+
+        @Override
+        public Connection newConnection(SocketChannel channel, EndPoint endpoint, Object attachment) throws IOException
+        {
+            if (ConnectHandler.LOG.isDebugEnabled())
+                ConnectHandler.LOG.debug("Connected to {}", channel.getRemoteAddress());
+            ConnectContext connectContext = (ConnectContext)attachment;
+            UpstreamConnection connection = newUpstreamConnection(endpoint, connectContext);
+            connection.setInputBufferSize(getBufferSize());
+            return connection;
+        }
+
+        @Override
+        protected void connectionFailed(SocketChannel channel, final Throwable ex, final Object attachment)
+        {
+            getExecutor().execute(new Runnable()
+            {
+                public void run()
+                {
+                    ConnectContext connectContext = (ConnectContext)attachment;
+                    onConnectFailure(connectContext.request, connectContext.response, connectContext.asyncContext, ex);
+                }
+            });
+        }
+    }
+
+    protected static class ConnectContext
+    {
+        private final ConcurrentMap<String, Object> context = new ConcurrentHashMap<>();
+        private final HttpServletRequest request;
+        private final HttpServletResponse response;
+        private final AsyncContext asyncContext;
+        private final HttpConnection httpConnection;
+
+        public ConnectContext(HttpServletRequest request, HttpServletResponse response, AsyncContext asyncContext, HttpConnection httpConnection)
+        {
+            this.request = request;
+            this.response = response;
+            this.asyncContext = asyncContext;
+            this.httpConnection = httpConnection;
+        }
+
+        public ConcurrentMap<String, Object> getContext()
+        {
+            return context;
+        }
+
+        public HttpServletRequest getRequest()
+        {
+            return request;
+        }
+
+        public HttpServletResponse getResponse()
+        {
+            return response;
+        }
+
+        public AsyncContext getAsyncContext()
+        {
+            return asyncContext;
+        }
+
+        public HttpConnection getHttpConnection()
+        {
+            return httpConnection;
+        }
+    }
+
+    public class UpstreamConnection extends ProxyConnection
+    {
+        private ConnectContext connectContext;
+
+        public UpstreamConnection(EndPoint endPoint, Executor executor, ByteBufferPool bufferPool, ConnectContext connectContext)
+        {
+            super(endPoint, executor, bufferPool, connectContext.getContext());
+            this.connectContext = connectContext;
+        }
+
+        @Override
+        public void onOpen()
+        {
+            super.onOpen();
+            getExecutor().execute(new Runnable()
+            {
+                public void run()
+                {
+                    onConnectSuccess(connectContext, UpstreamConnection.this);
+                    fillInterested();
+                }
+            });
+        }
+
+        @Override
+        protected int read(EndPoint endPoint, ByteBuffer buffer) throws IOException
+        {
+            return ConnectHandler.this.read(endPoint, buffer);
+        }
+
+        @Override
+        protected void write(EndPoint endPoint, ByteBuffer buffer,Callback callback)
+        {
+            ConnectHandler.this.write(endPoint, buffer, callback);
+        }
+    }
+
+    public class DownstreamConnection extends ProxyConnection
+    {
+        private final ByteBuffer buffer;
+
+        public DownstreamConnection(EndPoint endPoint, Executor executor, ByteBufferPool bufferPool, ConcurrentMap<String, Object> context, ByteBuffer buffer)
+        {
+            super(endPoint, executor, bufferPool, context);
+            this.buffer = buffer;
+        }
+
+        @Override
+        public void onOpen()
+        {
+            super.onOpen();
+            final int remaining = buffer.remaining();
+            write(getConnection().getEndPoint(), buffer, new Callback()
+            {
+                @Override
+                public void succeeded()
+                {
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("{} wrote initial {} bytes to server", DownstreamConnection.this, remaining);
+                    fillInterested();
+                }
+
+                @Override
+                public void failed(Throwable x)
+                {
+                    if (LOG.isDebugEnabled())
+                        LOG.debug(this + " failed to write initial " + remaining + " bytes to server", x);
+                    close();
+                    getConnection().close();
+                }
+            });
+        }
+
+        @Override
+        protected int read(EndPoint endPoint, ByteBuffer buffer) throws IOException
+        {
+            return ConnectHandler.this.read(endPoint, buffer);
+        }
+
+        @Override
+        protected void write(EndPoint endPoint, ByteBuffer buffer, Callback callback)
+        {
+            ConnectHandler.this.write(endPoint, buffer, callback);
+        }
+    }
+}
diff --git a/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ProxyConnection.java b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ProxyConnection.java
new file mode 100644
index 0000000..90c5649
--- /dev/null
+++ b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ProxyConnection.java
@@ -0,0 +1,158 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.proxy;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.Executor;
+
+import org.eclipse.jetty.io.AbstractConnection;
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.IteratingCallback;
+import org.eclipse.jetty.util.log.Logger;
+
+public abstract class ProxyConnection extends AbstractConnection
+{
+    protected static final Logger LOG = ConnectHandler.LOG;
+    private final IteratingCallback pipe = new ProxyIteratingCallback();
+    private final ByteBufferPool bufferPool;
+    private final ConcurrentMap<String, Object> context;
+    private Connection connection;
+
+    protected ProxyConnection(EndPoint endp, Executor executor, ByteBufferPool bufferPool, ConcurrentMap<String, Object> context)
+    {
+        super(endp, executor);
+        this.bufferPool = bufferPool;
+        this.context = context;
+    }
+
+    public ByteBufferPool getByteBufferPool()
+    {
+        return bufferPool;
+    }
+
+    public ConcurrentMap<String, Object> getContext()
+    {
+        return context;
+    }
+
+    public Connection getConnection()
+    {
+        return connection;
+    }
+
+    public void setConnection(Connection connection)
+    {
+        this.connection = connection;
+    }
+
+    @Override
+    public void onFillable()
+    {
+        pipe.iterate();
+    }
+
+    protected abstract int read(EndPoint endPoint, ByteBuffer buffer) throws IOException;
+
+    protected abstract void write(EndPoint endPoint, ByteBuffer buffer, Callback callback);
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s[l:%d<=>r:%d]",
+                super.toString(),
+                getEndPoint().getLocalAddress().getPort(),
+                getEndPoint().getRemoteAddress().getPort());
+    }
+
+    private class ProxyIteratingCallback extends IteratingCallback
+    {
+        private ByteBuffer buffer;
+        private int filled;
+
+        @Override
+        protected Action process() throws Exception
+        {
+            buffer = bufferPool.acquire(getInputBufferSize(), true);
+            try
+            {
+                int filled = this.filled = read(getEndPoint(), buffer);
+                if (LOG.isDebugEnabled())
+                    LOG.debug("{} filled {} bytes", ProxyConnection.this, filled);
+                if (filled > 0)
+                {
+                    write(connection.getEndPoint(), buffer, this);
+                    return Action.SCHEDULED;
+                }
+                else if (filled == 0)
+                {
+                    bufferPool.release(buffer);
+                    fillInterested();
+                    return Action.IDLE;
+                }
+                else
+                {
+                    bufferPool.release(buffer);
+                    connection.getEndPoint().shutdownOutput();
+                    return Action.SUCCEEDED;
+                }
+            }
+            catch (IOException x)
+            {
+                if (LOG.isDebugEnabled())
+                    LOG.debug(ProxyConnection.this + " could not fill", x);
+                disconnect();
+                return Action.SUCCEEDED;
+            }
+        }
+
+        @Override
+        public void succeeded()
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("{} wrote {} bytes", ProxyConnection.this, filled);
+            bufferPool.release(buffer);
+            super.succeeded();
+        }
+
+        @Override
+        protected void onCompleteSuccess()
+        {
+        }
+
+        @Override
+        protected void onCompleteFailure(Throwable x)
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug(ProxyConnection.this + " failed to write " + filled + " bytes", x);
+            disconnect();
+        }
+
+        private void disconnect()
+        {
+            bufferPool.release(buffer);
+            ProxyConnection.this.close();
+            connection.close();
+        }
+    }
+}
diff --git a/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ProxyServlet.java b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ProxyServlet.java
new file mode 100644
index 0000000..d374c35
--- /dev/null
+++ b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ProxyServlet.java
@@ -0,0 +1,343 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.proxy;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.nio.ByteBuffer;
+import java.util.concurrent.TimeUnit;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.client.api.ContentProvider;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.client.api.Result;
+import org.eclipse.jetty.client.util.InputStreamContentProvider;
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.util.Callback;
+
+/**
+ * <p>Servlet 3.0 asynchronous proxy servlet.</p>
+ * <p>The request processing is asynchronous, but the I/O is blocking.</p>
+ *
+ * @see AsyncProxyServlet
+ * @see AsyncMiddleManServlet
+ * @see ConnectHandler
+ */
+public class ProxyServlet extends AbstractProxyServlet
+{
+    @Override
+    protected void service(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException
+    {
+        final int requestId = getRequestId(request);
+
+        URI rewrittenURI = rewriteURI(request);
+
+        if (_log.isDebugEnabled())
+        {
+            StringBuffer uri = request.getRequestURL();
+            if (request.getQueryString() != null)
+                uri.append("?").append(request.getQueryString());
+            if (_log.isDebugEnabled())
+                _log.debug("{} rewriting: {} -> {}", requestId, uri, rewrittenURI);
+        }
+
+        if (rewrittenURI == null)
+        {
+            onRewriteFailed(request, response);
+            return;
+        }
+
+        final Request proxyRequest = getHttpClient().newRequest(rewrittenURI)
+                .method(request.getMethod())
+                .version(HttpVersion.fromString(request.getProtocol()));
+
+        copyHeaders(request, proxyRequest);
+
+        addProxyHeaders(request, proxyRequest);
+
+        final AsyncContext asyncContext = request.startAsync();
+        // We do not timeout the continuation, but the proxy request
+        asyncContext.setTimeout(0);
+        proxyRequest.timeout(getTimeout(), TimeUnit.MILLISECONDS);
+
+        if (hasContent(request))
+            proxyRequest.content(proxyRequestContent(proxyRequest, request));
+
+        customizeProxyRequest(proxyRequest, request);
+
+        sendProxyRequest(request, response, proxyRequest);
+    }
+
+    /**
+     * @deprecated use {@link #copyRequestHeaders(HttpServletRequest, Request)} instead
+     */
+    @Deprecated
+    protected void copyHeaders(HttpServletRequest clientRequest, Request proxyRequest)
+    {
+        copyRequestHeaders(clientRequest, proxyRequest);
+    }
+
+    protected ContentProvider proxyRequestContent(final Request proxyRequest, final HttpServletRequest request) throws IOException
+    {
+        return new ProxyInputStreamContentProvider(proxyRequest, request, request.getInputStream());
+    }
+
+    protected Response.Listener newProxyResponseListener(HttpServletRequest request, HttpServletResponse response)
+    {
+        return new ProxyResponseListener(request, response);
+    }
+
+    protected void onClientRequestFailure(Request proxyRequest, HttpServletRequest request, Throwable failure)
+    {
+        if (_log.isDebugEnabled())
+            _log.debug(getRequestId(request) + " client request failure", failure);
+        proxyRequest.abort(failure);
+    }
+
+    /**
+     * @deprecated use {@link #onProxyRewriteFailed(HttpServletRequest, HttpServletResponse)}
+     */
+    @Deprecated
+    protected void onRewriteFailed(HttpServletRequest request, HttpServletResponse response) throws IOException
+    {
+        onProxyRewriteFailed(request, response);
+    }
+
+    /**
+     * @deprecated use {@link #onServerResponseHeaders(HttpServletRequest, HttpServletResponse, Response)}
+     */
+    @Deprecated
+    protected void onResponseHeaders(HttpServletRequest request, HttpServletResponse response, Response proxyResponse)
+    {
+        onServerResponseHeaders(request, response, proxyResponse);
+    }
+
+    // TODO: remove in Jetty 9.3, only here for backward compatibility.
+    @Override
+    protected String filterServerResponseHeader(HttpServletRequest clientRequest, Response serverResponse, String headerName, String headerValue)
+    {
+        return filterResponseHeader(clientRequest, headerName, headerValue);
+    }
+
+    protected void onResponseContent(HttpServletRequest request, HttpServletResponse response, Response proxyResponse, byte[] buffer, int offset, int length, Callback callback)
+    {
+        try
+        {
+            if (_log.isDebugEnabled())
+                _log.debug("{} proxying content to downstream: {} bytes", getRequestId(request), length);
+            response.getOutputStream().write(buffer, offset, length);
+            callback.succeeded();
+        }
+        catch (Throwable x)
+        {
+            callback.failed(x);
+        }
+    }
+
+    /**
+     * @deprecated Use {@link #onProxyResponseSuccess(HttpServletRequest, HttpServletResponse, Response)}
+     */
+    @Deprecated
+    protected void onResponseSuccess(HttpServletRequest request, HttpServletResponse response, Response proxyResponse)
+    {
+        onProxyResponseSuccess(request, response, proxyResponse);
+    }
+
+    /**
+     * @deprecated Use {@link #onProxyResponseFailure(HttpServletRequest, HttpServletResponse, Response, Throwable)}
+     */
+    @Deprecated
+    protected void onResponseFailure(HttpServletRequest request, HttpServletResponse response, Response proxyResponse, Throwable failure)
+    {
+        onProxyResponseFailure(request, response, proxyResponse, failure);
+    }
+
+    /**
+     * @deprecated use {@link #rewriteTarget(HttpServletRequest)}
+     */
+    @Deprecated
+    protected URI rewriteURI(HttpServletRequest request)
+    {
+        String newTarget = rewriteTarget(request);
+        return newTarget == null ? null : URI.create(newTarget);
+    }
+
+    /**
+     * @deprecated use {@link #sendProxyRequest(HttpServletRequest, HttpServletResponse, Request)}
+     */
+    @Deprecated
+    protected void customizeProxyRequest(Request proxyRequest, HttpServletRequest request)
+    {
+    }
+
+    /**
+     * Extension point for remote server response header filtering.
+     * The default implementation returns the header value as is.
+     * If null is returned, this header won't be forwarded back to the client.
+     *
+     * @param headerName the header name
+     * @param headerValue the header value
+     * @param request the request to proxy
+     * @return filteredHeaderValue the new header value
+     * @deprecated use {@link #filterServerResponseHeader(HttpServletRequest, Response, String, String)} instead
+     */
+    @Deprecated
+    protected String filterResponseHeader(HttpServletRequest request, String headerName, String headerValue)
+    {
+        return headerValue;
+    }
+
+    /**
+     * <p>Convenience extension of {@link ProxyServlet} that offers transparent proxy functionalities.</p>
+     *
+     * @see TransparentDelegate
+     */
+    public static class Transparent extends ProxyServlet
+    {
+        private final TransparentDelegate delegate = new TransparentDelegate(this);
+
+        @Override
+        public void init(ServletConfig config) throws ServletException
+        {
+            super.init(config);
+            delegate.init(config);
+        }
+
+        @Override
+        protected URI rewriteURI(HttpServletRequest request)
+        {
+            return URI.create(delegate.rewriteTarget(request));
+        }
+    }
+
+    protected class ProxyResponseListener extends Response.Listener.Adapter
+    {
+        private final HttpServletRequest request;
+        private final HttpServletResponse response;
+
+        protected ProxyResponseListener(HttpServletRequest request, HttpServletResponse response)
+        {
+            this.request = request;
+            this.response = response;
+        }
+
+        @Override
+        public void onBegin(Response proxyResponse)
+        {
+            response.setStatus(proxyResponse.getStatus());
+        }
+
+        @Override
+        public void onHeaders(Response proxyResponse)
+        {
+            onResponseHeaders(request, response, proxyResponse);
+        }
+
+        @Override
+        public void onContent(final Response proxyResponse, ByteBuffer content, final Callback callback)
+        {
+            byte[] buffer;
+            int offset;
+            int length = content.remaining();
+            if (content.hasArray())
+            {
+                buffer = content.array();
+                offset = content.arrayOffset();
+            }
+            else
+            {
+                buffer = new byte[length];
+                content.get(buffer);
+                offset = 0;
+            }
+
+            onResponseContent(request, response, proxyResponse, buffer, offset, length, new Callback()
+            {
+                @Override
+                public void succeeded()
+                {
+                    callback.succeeded();
+                }
+
+                @Override
+                public void failed(Throwable x)
+                {
+                    callback.failed(x);
+                    proxyResponse.abort(x);
+                }
+            });
+        }
+
+        @Override
+        public void onComplete(Result result)
+        {
+            if (result.isSucceeded())
+                onResponseSuccess(request, response, result.getResponse());
+            else
+                onResponseFailure(request, response, result.getResponse(), result.getFailure());
+            if (_log.isDebugEnabled())
+                _log.debug("{} proxying complete", getRequestId(request));
+        }
+    }
+
+    protected class ProxyInputStreamContentProvider extends InputStreamContentProvider
+    {
+        private final Request proxyRequest;
+        private final HttpServletRequest request;
+
+        protected ProxyInputStreamContentProvider(Request proxyRequest, HttpServletRequest request, InputStream input)
+        {
+            super(input);
+            this.proxyRequest = proxyRequest;
+            this.request = request;
+        }
+
+        @Override
+        public long getLength()
+        {
+            return request.getContentLength();
+        }
+
+        @Override
+        protected ByteBuffer onRead(byte[] buffer, int offset, int length)
+        {
+            if (_log.isDebugEnabled())
+                _log.debug("{} proxying content to upstream: {} bytes", getRequestId(request), length);
+            return onRequestContent(proxyRequest, request, buffer, offset, length);
+        }
+
+        protected ByteBuffer onRequestContent(Request proxyRequest, final HttpServletRequest request, byte[] buffer, int offset, int length)
+        {
+            return super.onRead(buffer, offset, length);
+        }
+
+        @Override
+        protected void onReadFailure(Throwable failure)
+        {
+            onClientRequestFailure(proxyRequest, request, failure);
+        }
+    }
+}
diff --git a/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/package-info.java b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/package-info.java
new file mode 100644
index 0000000..d1e8469
--- /dev/null
+++ b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Proxy : Async Proxy Support
+ */
+package org.eclipse.jetty.proxy;
+
diff --git a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/AbstractConnectHandlerTest.java b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/AbstractConnectHandlerTest.java
new file mode 100644
index 0000000..c1b2852
--- /dev/null
+++ b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/AbstractConnectHandlerTest.java
@@ -0,0 +1,79 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.proxy;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.net.Socket;
+
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.NetworkConnector;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.toolchain.test.http.SimpleHttpParser;
+import org.eclipse.jetty.toolchain.test.http.SimpleHttpResponse;
+import org.junit.After;
+
+public abstract class AbstractConnectHandlerTest
+{
+    protected Server server;
+    protected ServerConnector serverConnector;
+    protected Server proxy;
+    protected Connector proxyConnector;
+    protected ConnectHandler connectHandler;
+
+    protected void prepareProxy() throws Exception
+    {
+        proxy = new Server();
+        proxyConnector = new ServerConnector(proxy);
+        proxy.addConnector(proxyConnector);
+        connectHandler = new ConnectHandler();
+        proxy.setHandler(connectHandler);
+        proxy.start();
+    }
+
+    @After
+    public void dispose() throws Exception
+    {
+        disposeServer();
+        disposeProxy();
+    }
+
+    protected void disposeServer() throws Exception
+    {
+        server.stop();
+    }
+
+    protected void disposeProxy() throws Exception
+    {
+        proxy.stop();
+    }
+
+    protected SimpleHttpResponse readResponse(BufferedReader reader) throws IOException
+    {
+        return new SimpleHttpParser().readResponse(reader);
+    }
+
+    protected Socket newSocket() throws IOException
+    {
+        Socket socket = new Socket("localhost", ((NetworkConnector)proxyConnector).getLocalPort());
+        socket.setSoTimeout(5000);
+        return socket;
+    }
+}
diff --git a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/AsyncMiddleManServletTest.java b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/AsyncMiddleManServletTest.java
new file mode 100644
index 0000000..a100690
--- /dev/null
+++ b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/AsyncMiddleManServletTest.java
@@ -0,0 +1,1703 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.proxy;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.GZIPOutputStream;
+
+import javax.servlet.ServletException;
+import javax.servlet.ServletInputStream;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.HttpProxy;
+import org.eclipse.jetty.client.api.ContentProvider;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.client.api.Result;
+import org.eclipse.jetty.client.util.BytesContentProvider;
+import org.eclipse.jetty.client.util.DeferredContentProvider;
+import org.eclipse.jetty.client.util.FutureResponseListener;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpHeaderValue;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.io.RuntimeIOException;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.HttpConnectionFactory;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.eclipse.jetty.toolchain.test.TestTracker;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.Utf8StringBuilder;
+import org.eclipse.jetty.util.ajax.JSON;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.log.StdErrLog;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class AsyncMiddleManServletTest
+{
+    private static final Logger LOG = Log.getLogger(AsyncMiddleManServletTest.class);
+    @Rule
+    public final TestTracker tracker = new TestTracker();
+    private HttpClient client;
+    private Server proxy;
+    private ServerConnector proxyConnector;
+    private Server server;
+    private ServerConnector serverConnector;
+
+    private void startServer(HttpServlet servlet) throws Exception
+    {
+        QueuedThreadPool serverPool = new QueuedThreadPool();
+        serverPool.setName("server");
+        server = new Server(serverPool);
+        serverConnector = new ServerConnector(server);
+        server.addConnector(serverConnector);
+
+        ServletContextHandler appCtx = new ServletContextHandler(server, "/", true, false);
+        ServletHolder appServletHolder = new ServletHolder(servlet);
+        appCtx.addServlet(appServletHolder, "/*");
+
+        server.start();
+    }
+
+    private void startProxy(AsyncMiddleManServlet proxyServlet) throws Exception
+    {
+        startProxy(proxyServlet, new HashMap<String, String>());
+    }
+
+    private void startProxy(AsyncMiddleManServlet proxyServlet, Map<String, String> initParams) throws Exception
+    {
+        QueuedThreadPool proxyPool = new QueuedThreadPool();
+        proxyPool.setName("proxy");
+        proxy = new Server(proxyPool);
+
+        HttpConfiguration configuration = new HttpConfiguration();
+        configuration.setSendDateHeader(false);
+        configuration.setSendServerVersion(false);
+        String value = initParams.get("outputBufferSize");
+        if (value != null)
+            configuration.setOutputBufferSize(Integer.valueOf(value));
+        proxyConnector = new ServerConnector(proxy, new HttpConnectionFactory(configuration));
+        proxy.addConnector(proxyConnector);
+
+        ServletContextHandler proxyContext = new ServletContextHandler(proxy, "/", true, false);
+        ServletHolder proxyServletHolder = new ServletHolder(proxyServlet);
+        proxyServletHolder.setInitParameters(initParams);
+        proxyContext.addServlet(proxyServletHolder, "/*");
+
+        proxy.start();
+
+        ((StdErrLog)proxyServlet._log).setHideStacks(true);
+    }
+
+    private void startClient() throws Exception
+    {
+        QueuedThreadPool clientPool = new QueuedThreadPool();
+        clientPool.setName("client");
+        client = new HttpClient();
+        client.setExecutor(clientPool);
+        client.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", proxyConnector.getLocalPort()));
+        client.start();
+    }
+
+    @After
+    public void dispose() throws Exception
+    {
+        client.stop();
+        proxy.stop();
+        server.stop();
+    }
+
+    @Test
+    public void testZeroContentLength() throws Exception
+    {
+        startServer(new EchoHttpServlet());
+        startProxy(new AsyncMiddleManServlet());
+        startClient();
+
+        ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        Assert.assertEquals(200, response.getStatus());
+    }
+
+    @Test
+    public void testClientRequestSmallContentKnownLengthGzipped() throws Exception
+    {
+        // Lengths smaller than the buffer sizes preserve the Content-Length header.
+        testClientRequestContentKnownLengthGzipped(1024, false);
+    }
+
+    @Test
+    public void testClientRequestLargeContentKnownLengthGzipped() throws Exception
+    {
+        // Lengths bigger than the buffer sizes will force chunked mode.
+        testClientRequestContentKnownLengthGzipped(1024 * 1024, true);
+    }
+
+    private void testClientRequestContentKnownLengthGzipped(int length, final boolean expectChunked) throws Exception
+    {
+        byte[] bytes = new byte[length];
+        new Random().nextBytes(bytes);
+
+        startServer(new EchoHttpServlet()
+        {
+            @Override
+            protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+            {
+                String transferEncoding = request.getHeader(HttpHeader.TRANSFER_ENCODING.asString());
+                if (expectChunked)
+                    Assert.assertNotNull(transferEncoding);
+                else
+                    Assert.assertNull(transferEncoding);
+                response.setHeader(HttpHeader.CONTENT_ENCODING.asString(), "gzip");
+                super.service(request, response);
+            }
+        });
+        startProxy(new AsyncMiddleManServlet()
+        {
+            @Override
+            protected ContentTransformer newClientRequestContentTransformer(HttpServletRequest clientRequest, Request proxyRequest)
+            {
+                return new GZIPContentTransformer(ContentTransformer.IDENTITY);
+            }
+        });
+        startClient();
+
+        byte[] gzipBytes = gzip(bytes);
+        ContentProvider gzipContent = new BytesContentProvider(gzipBytes);
+
+        ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
+                .header(HttpHeader.CONTENT_ENCODING, "gzip")
+                .content(gzipContent)
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        Assert.assertEquals(200, response.getStatus());
+        Assert.assertArrayEquals(bytes, response.getContent());
+    }
+
+    @Test
+    public void testServerResponseContentKnownLengthGzipped() throws Exception
+    {
+        byte[] bytes = new byte[1024];
+        new Random().nextBytes(bytes);
+        final byte[] gzipBytes = gzip(bytes);
+
+        startServer(new HttpServlet()
+        {
+            @Override
+            protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+            {
+                response.setHeader(HttpHeader.CONTENT_ENCODING.asString(), "gzip");
+                response.getOutputStream().write(gzipBytes);
+            }
+        });
+        startProxy(new AsyncMiddleManServlet()
+        {
+            @Override
+            protected ContentTransformer newServerResponseContentTransformer(HttpServletRequest clientRequest, HttpServletResponse proxyResponse, Response serverResponse)
+            {
+                return new GZIPContentTransformer(ContentTransformer.IDENTITY);
+            }
+        });
+        startClient();
+
+        ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        Assert.assertEquals(200, response.getStatus());
+        Assert.assertArrayEquals(bytes, response.getContent());
+    }
+
+    @Test
+    public void testTransformUpstreamAndDownstreamKnownContentLengthGzipped() throws Exception
+    {
+        String data = "<a href=\"http://google.com\">Google</a>";
+        byte[] bytes = data.getBytes(StandardCharsets.UTF_8);
+
+        startServer(new EchoHttpServlet()
+        {
+            @Override
+            protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+            {
+                response.setHeader(HttpHeader.CONTENT_ENCODING.asString(), "gzip");
+                super.service(request, response);
+            }
+        });
+        startProxy(new AsyncMiddleManServlet()
+        {
+            @Override
+            protected ContentTransformer newClientRequestContentTransformer(HttpServletRequest clientRequest, Request proxyRequest)
+            {
+                return new GZIPContentTransformer(new HrefTransformer.Client());
+            }
+
+            @Override
+            protected ContentTransformer newServerResponseContentTransformer(HttpServletRequest clientRequest, HttpServletResponse proxyResponse, Response serverResponse)
+            {
+                return new GZIPContentTransformer(new HrefTransformer.Server());
+            }
+        });
+        startClient();
+
+        ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
+                .header(HttpHeader.CONTENT_ENCODING, "gzip")
+                .content(new BytesContentProvider(gzip(bytes)))
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        Assert.assertEquals(200, response.getStatus());
+        Assert.assertArrayEquals(bytes, response.getContent());
+    }
+
+    @Test
+    public void testTransformGzippedHead() throws Exception
+    {
+        startServer(new HttpServlet()
+        {
+            @Override
+            protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+            {
+                response.setHeader(HttpHeader.CONTENT_ENCODING.asString(), "gzip");
+
+                String sample = "<a href=\"http://webtide.com/\">Webtide</a>\n<a href=\"http://google.com\">Google</a>\n";
+                byte[] bytes = sample.getBytes(StandardCharsets.UTF_8);
+
+                ServletOutputStream out = response.getOutputStream();
+                out.write(gzip(bytes));
+
+                // create a byte buffer larger enough to create 2 (or more) transforms.
+                byte[] randomFiller = new byte[64 * 1024];
+                /* fill with nonsense
+                 * Using random data to ensure compressed buffer size is large
+                 * enough to trigger at least 2 transform() events.
+                 */
+                new Random().nextBytes(randomFiller);
+
+                out.write(gzip(randomFiller));
+            }
+        });
+        startProxy(new AsyncMiddleManServlet()
+        {
+            @Override
+            protected ContentTransformer newServerResponseContentTransformer(HttpServletRequest clientRequest, HttpServletResponse proxyResponse, Response serverResponse)
+            {
+                return new GZIPContentTransformer(new HeadTransformer());
+            }
+        });
+        startClient();
+
+        ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
+                .header(HttpHeader.CONTENT_ENCODING, "gzip")
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        Assert.assertEquals(200, response.getStatus());
+
+        String expectedStr = "<a href=\"http://webtide.com/\">Webtide</a>";
+        byte[] expected = expectedStr.getBytes(StandardCharsets.UTF_8);
+        Assert.assertArrayEquals(expected, response.getContent());
+    }
+
+    @Test
+    public void testManySequentialTransformations() throws Exception
+    {
+        for (int i = 0; i < 8; ++i)
+            testTransformUpstreamAndDownstreamKnownContentLengthGzipped();
+    }
+
+    @Test
+    public void testUpstreamTransformationBufferedGzipped() throws Exception
+    {
+        startServer(new EchoHttpServlet()
+        {
+            @Override
+            protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+            {
+                response.setHeader(HttpHeader.CONTENT_ENCODING.asString(), "gzip");
+                super.service(request, response);
+            }
+        });
+        startProxy(new AsyncMiddleManServlet()
+        {
+            @Override
+            protected ContentTransformer newClientRequestContentTransformer(HttpServletRequest clientRequest, Request proxyRequest)
+            {
+                return new GZIPContentTransformer(new BufferingContentTransformer());
+            }
+        });
+        startClient();
+
+        DeferredContentProvider content = new DeferredContentProvider();
+        Request request = client.newRequest("localhost", serverConnector.getLocalPort());
+        FutureResponseListener listener = new FutureResponseListener(request);
+        request.header(HttpHeader.CONTENT_ENCODING, "gzip")
+                .content(content)
+                .send(listener);
+        byte[] bytes = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".getBytes(StandardCharsets.UTF_8);
+        content.offer(ByteBuffer.wrap(gzip(bytes)));
+        sleep(1000);
+        content.close();
+
+        ContentResponse response = listener.get(5, TimeUnit.SECONDS);
+        Assert.assertEquals(200, response.getStatus());
+        Assert.assertArrayEquals(bytes, response.getContent());
+    }
+
+    @Test
+    public void testDownstreamTransformationBufferedGzipped() throws Exception
+    {
+        startServer(new HttpServlet()
+        {
+            @Override
+            protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+            {
+                response.setHeader(HttpHeader.CONTENT_ENCODING.asString(), "gzip");
+
+                ServletInputStream input = request.getInputStream();
+                ServletOutputStream output = response.getOutputStream();
+                int read;
+                while ((read = input.read()) >= 0)
+                {
+                    output.write(read);
+                    output.flush();
+                }
+            }
+        });
+        startProxy(new AsyncMiddleManServlet()
+        {
+            @Override
+            protected ContentTransformer newServerResponseContentTransformer(HttpServletRequest clientRequest, HttpServletResponse proxyResponse, Response serverResponse)
+            {
+                return new GZIPContentTransformer(new BufferingContentTransformer());
+            }
+        });
+        startClient();
+
+        byte[] bytes = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".getBytes(StandardCharsets.UTF_8);
+        ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
+                .header(HttpHeader.CONTENT_ENCODING, "gzip")
+                .content(new BytesContentProvider(gzip(bytes)))
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        Assert.assertEquals(200, response.getStatus());
+        Assert.assertArrayEquals(bytes, response.getContent());
+    }
+
+    @Test
+    public void testDiscardUpstreamAndDownstreamKnownContentLengthGzipped() throws Exception
+    {
+        final byte[] bytes = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".getBytes(StandardCharsets.UTF_8);
+        startServer(new HttpServlet()
+        {
+            @Override
+            protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+            {
+                // decode input stream thru gzip
+                ByteArrayOutputStream bos = new ByteArrayOutputStream();
+                IO.copy(new GZIPInputStream(request.getInputStream()), bos);
+                // ensure decompressed is 0 length
+                Assert.assertEquals(0, bos.toByteArray().length);
+                response.setHeader(HttpHeader.CONTENT_ENCODING.asString(), "gzip");
+                response.getOutputStream().write(gzip(bytes));
+            }
+        });
+        startProxy(new AsyncMiddleManServlet()
+        {
+            @Override
+            protected ContentTransformer newClientRequestContentTransformer(HttpServletRequest clientRequest, Request proxyRequest)
+            {
+                return new GZIPContentTransformer(new DiscardContentTransformer());
+            }
+
+            @Override
+            protected ContentTransformer newServerResponseContentTransformer(HttpServletRequest clientRequest, HttpServletResponse proxyResponse, Response serverResponse)
+            {
+                return new GZIPContentTransformer(new DiscardContentTransformer());
+            }
+        });
+        startClient();
+
+        ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
+                .header(HttpHeader.CONTENT_ENCODING, "gzip")
+                .content(new BytesContentProvider(gzip(bytes)))
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        Assert.assertEquals(200, response.getStatus());
+        Assert.assertEquals(0, response.getContent().length);
+    }
+
+    @Test
+    public void testUpstreamTransformationThrowsBeforeCommittingProxyRequest() throws Exception
+    {
+        startServer(new EchoHttpServlet());
+        startProxy(new AsyncMiddleManServlet()
+        {
+            @Override
+            protected ContentTransformer newClientRequestContentTransformer(HttpServletRequest clientRequest, Request proxyRequest)
+            {
+                return new ContentTransformer()
+                {
+                    @Override
+                    public void transform(ByteBuffer input, boolean finished, List<ByteBuffer> output) throws IOException
+                    {
+                        throw new NullPointerException("explicitly_thrown_by_test");
+                    }
+                };
+            }
+        });
+        startClient();
+
+        byte[] bytes = new byte[1024];
+        ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
+                .content(new BytesContentProvider(bytes))
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        Assert.assertEquals(500, response.getStatus());
+    }
+
+    @Test
+    public void testUpstreamTransformationThrowsAfterCommittingProxyRequest() throws Exception
+    {
+        startServer(new EchoHttpServlet());
+        startProxy(new AsyncMiddleManServlet()
+        {
+            @Override
+            protected ContentTransformer newClientRequestContentTransformer(HttpServletRequest clientRequest, Request proxyRequest)
+            {
+                return new ContentTransformer()
+                {
+                    private int count;
+
+                    @Override
+                    public void transform(ByteBuffer input, boolean finished, List<ByteBuffer> output) throws IOException
+                    {
+                        if (++count < 2)
+                            output.add(input);
+                        else
+                            throw new NullPointerException("explicitly_thrown_by_test");
+                    }
+                };
+            }
+        });
+        startClient();
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        DeferredContentProvider content = new DeferredContentProvider();
+        client.newRequest("localhost", serverConnector.getLocalPort())
+                .content(content)
+                .send(new Response.CompleteListener()
+                {
+                    @Override
+                    public void onComplete(Result result)
+                    {
+                        if (result.isSucceeded() && result.getResponse().getStatus() == 502)
+                            latch.countDown();
+                    }
+                });
+
+        content.offer(ByteBuffer.allocate(512));
+        sleep(1000);
+        content.offer(ByteBuffer.allocate(512));
+        content.close();
+
+        Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testDownstreamTransformationThrowsAtOnContent() throws Exception
+    {
+        testDownstreamTransformationThrows(new HttpServlet()
+        {
+            @Override
+            protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+            {
+                // To trigger the test failure we need that onContent()
+                // is called twice, so the second time the test throws.
+                ServletOutputStream output = response.getOutputStream();
+                output.write(new byte[512]);
+                output.flush();
+                output.write(new byte[512]);
+                output.flush();
+            }
+        });
+    }
+
+    @Test
+    public void testDownstreamTransformationThrowsAtOnSuccess() throws Exception
+    {
+        testDownstreamTransformationThrows(new HttpServlet()
+        {
+            @Override
+            protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+            {
+                // To trigger the test failure we need that onContent()
+                // is called only once, so the the test throws from onSuccess().
+                ServletOutputStream output = response.getOutputStream();
+                output.write(new byte[512]);
+                output.flush();
+            }
+        });
+    }
+
+    private void testDownstreamTransformationThrows(HttpServlet serverServlet) throws Exception
+    {
+        startServer(serverServlet);
+        startProxy(new AsyncMiddleManServlet()
+        {
+            @Override
+            protected ContentTransformer newServerResponseContentTransformer(HttpServletRequest clientRequest, HttpServletResponse proxyResponse, Response serverResponse)
+            {
+                return new ContentTransformer()
+                {
+                    private int count;
+
+                    @Override
+                    public void transform(ByteBuffer input, boolean finished, List<ByteBuffer> output) throws IOException
+                    {
+                        if (++count < 2)
+                            output.add(input);
+                        else
+                            throw new NullPointerException("explicitly_thrown_by_test");
+                    }
+                };
+            }
+        });
+        startClient();
+
+        ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        Assert.assertEquals(502, response.getStatus());
+    }
+
+    @Test
+    public void testLargeChunkedBufferedDownstreamTransformation() throws Exception
+    {
+        // Tests the race between a incomplete write performed from ProxyResponseListener.onSuccess()
+        // and ProxyResponseListener.onComplete() being called before the write has completed.
+
+        startServer(new HttpServlet()
+        {
+            @Override
+            protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+            {
+                ServletOutputStream output = response.getOutputStream();
+                byte[] chunk = new byte[1024 * 1024];
+                for (int i = 0; i < 16; ++i)
+                {
+                    output.write(chunk);
+                    output.flush();
+                }
+            }
+        });
+        startProxy(new AsyncMiddleManServlet()
+        {
+            @Override
+            protected ContentTransformer newServerResponseContentTransformer(HttpServletRequest clientRequest, HttpServletResponse proxyResponse, Response serverResponse)
+            {
+                return new BufferingContentTransformer();
+            }
+        });
+        startClient();
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        client.newRequest("localhost", serverConnector.getLocalPort())
+                .onResponseContent(new Response.ContentListener()
+                {
+                    @Override
+                    public void onContent(Response response, ByteBuffer content)
+                    {
+                        // Slow down the reader so that the
+                        // write from the proxy gets congested.
+                        sleep(1);
+                    }
+                })
+                .send(new Response.CompleteListener()
+                {
+                    @Override
+                    public void onComplete(Result result)
+                    {
+                        Assert.assertTrue(result.isSucceeded());
+                        Assert.assertEquals(200, result.getResponse().getStatus());
+                        latch.countDown();
+                    }
+                });
+
+        Assert.assertTrue(latch.await(15, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testDownstreamTransformationKnownContentLengthDroppingLastChunk() throws Exception
+    {
+        startServer(new HttpServlet()
+        {
+            @Override
+            protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+            {
+                byte[] chunk = new byte[1024];
+                int contentLength = 2 * chunk.length;
+                response.setContentLength(contentLength);
+                ServletOutputStream output = response.getOutputStream();
+                output.write(chunk);
+                output.flush();
+                sleep(1000);
+                output.write(chunk);
+            }
+        });
+        startProxy(new AsyncMiddleManServlet()
+        {
+            @Override
+            protected ContentTransformer newServerResponseContentTransformer(HttpServletRequest clientRequest, HttpServletResponse proxyResponse, Response serverResponse)
+            {
+                return new ContentTransformer()
+                {
+                    @Override
+                    public void transform(ByteBuffer input, boolean finished, List<ByteBuffer> output) throws IOException
+                    {
+                        if (!finished)
+                            output.add(input);
+                    }
+                };
+            }
+        });
+        startClient();
+
+        ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        Assert.assertEquals(200, response.getStatus());
+    }
+
+    @Test
+    public void testClientRequestReadFailsOnFirstRead() throws Exception
+    {
+        startServer(new EchoHttpServlet());
+        startProxy(new AsyncMiddleManServlet()
+        {
+            @Override
+            protected int readClientRequestContent(ServletInputStream input, byte[] buffer) throws IOException
+            {
+                throw new IOException("explicitly_thrown_by_test");
+            }
+        });
+        startClient();
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        DeferredContentProvider content = new DeferredContentProvider();
+        client.newRequest("localhost", serverConnector.getLocalPort())
+                .content(content)
+                .send(new Response.CompleteListener()
+                {
+                    @Override
+                    public void onComplete(Result result)
+                    {
+                        System.err.println(result);
+                        if (result.getResponse().getStatus() == 500)
+                            latch.countDown();
+                    }
+                });
+        content.offer(ByteBuffer.allocate(512));
+        sleep(1000);
+        content.offer(ByteBuffer.allocate(512));
+        content.close();
+
+        Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testClientRequestReadFailsOnSecondRead() throws Exception
+    {
+        startServer(new EchoHttpServlet());
+        startProxy(new AsyncMiddleManServlet()
+        {
+            private int count;
+
+            @Override
+            protected int readClientRequestContent(ServletInputStream input, byte[] buffer) throws IOException
+            {
+                if (++count < 2)
+                    return super.readClientRequestContent(input, buffer);
+                else
+                    throw new IOException("explicitly_thrown_by_test");
+            }
+        });
+        startClient();
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        DeferredContentProvider content = new DeferredContentProvider();
+        client.newRequest("localhost", serverConnector.getLocalPort())
+                .content(content)
+                .send(new Response.CompleteListener()
+                {
+                    @Override
+                    public void onComplete(Result result)
+                    {
+                        if (result.getResponse().getStatus() == 502)
+                            latch.countDown();
+                    }
+                });
+        content.offer(ByteBuffer.allocate(512));
+        sleep(1000);
+        content.offer(ByteBuffer.allocate(512));
+        content.close();
+
+        Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testProxyResponseWriteFailsOnFirstWrite() throws Exception
+    {
+        testProxyResponseWriteFails(1);
+    }
+
+    @Test
+    public void testProxyResponseWriteFailsOnSecondWrite() throws Exception
+    {
+        testProxyResponseWriteFails(2);
+    }
+
+    private void testProxyResponseWriteFails(final int writeCount) throws Exception
+    {
+        startServer(new HttpServlet()
+        {
+            @Override
+            protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+            {
+                ServletOutputStream output = response.getOutputStream();
+                output.write(new byte[512]);
+                output.flush();
+                output.write(new byte[512]);
+            }
+        });
+        startProxy(new AsyncMiddleManServlet()
+        {
+            private int count;
+
+            @Override
+            protected void writeProxyResponseContent(ServletOutputStream output, ByteBuffer content) throws IOException
+            {
+                if (++count < writeCount)
+                    super.writeProxyResponseContent(output, content);
+                else
+                    throw new IOException("explicitly_thrown_by_test");
+            }
+        });
+        startClient();
+
+        ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        Assert.assertEquals(502, response.getStatus());
+    }
+
+    @Test
+    public void testAfterContentTransformer() throws Exception
+    {
+        final String key0 = "id";
+        long value0 = 1;
+        final String key1 = "channel";
+        String value1 = "foo";
+        final String json = "{ \"" + key0 + "\":" + value0 + ", \"" + key1 + "\":\"" + value1 + "\" }";
+        startServer(new HttpServlet()
+        {
+            @Override
+            protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+            {
+                response.getOutputStream().write(json.getBytes(StandardCharsets.UTF_8));
+            }
+        });
+        final String key2 = "c";
+        startProxy(new AsyncMiddleManServlet()
+        {
+            @Override
+            protected ContentTransformer newServerResponseContentTransformer(HttpServletRequest clientRequest, HttpServletResponse proxyResponse, Response serverResponse)
+            {
+                return new AfterContentTransformer()
+                {
+                    @Override
+                    public boolean transform(Source source, Sink sink) throws IOException
+                    {
+                        InputStream input = source.getInputStream();
+                        @SuppressWarnings("unchecked")
+                        Map<String, Object> obj = (Map<String, Object>)JSON.parse(new InputStreamReader(input, "UTF-8"));
+                        // Transform the object.
+                        obj.put(key2, obj.remove(key1));
+                        try (OutputStream output = sink.getOutputStream())
+                        {
+                            output.write(JSON.toString(obj).getBytes(StandardCharsets.UTF_8));
+                            return true;
+                        }
+                    }
+                };
+            }
+        });
+        startClient();
+
+        ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        Assert.assertEquals(200, response.getStatus());
+        @SuppressWarnings("unchecked")
+        Map<String, Object> obj = (Map<String, Object>)JSON.parse(response.getContentAsString());
+        Assert.assertNotNull(obj);
+        Assert.assertEquals(2, obj.size());
+        Assert.assertEquals(value0, obj.get(key0));
+        Assert.assertEquals(value1, obj.get(key2));
+    }
+
+    @Test
+    public void testAfterContentTransformerMemoryInputStreamReset() throws Exception
+    {
+        testAfterContentTransformerInputStreamReset(false);
+    }
+
+    @Test
+    public void testAfterContentTransformerDiskInputStreamReset() throws Exception
+    {
+        testAfterContentTransformerInputStreamReset(true);
+    }
+
+    private void testAfterContentTransformerInputStreamReset(final boolean overflow) throws Exception
+    {
+        final byte[] data = new byte[]{'c', 'o', 'f', 'f', 'e', 'e'};
+        startServer(new HttpServlet()
+        {
+            @Override
+            protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+            {
+                // Write the content in two chunks.
+                int chunk = data.length / 2;
+                ServletOutputStream output = response.getOutputStream();
+                output.write(data, 0, chunk);
+                sleep(1000);
+                output.write(data, chunk, data.length - chunk);
+            }
+        });
+        startProxy(new AsyncMiddleManServlet()
+        {
+            @Override
+            protected ContentTransformer newServerResponseContentTransformer(HttpServletRequest clientRequest, HttpServletResponse proxyResponse, Response serverResponse)
+            {
+                return new AfterContentTransformer()
+                {
+                    {
+                        setMaxInputBufferSize(overflow ? data.length / 2 : data.length * 2);
+                    }
+
+                    @Override
+                    public boolean transform(Source source, Sink sink) throws IOException
+                    {
+                        // Consume the stream once.
+                        InputStream input = source.getInputStream();
+                        IO.copy(input, IO.getNullStream());
+
+                        // Reset the stream and re-read it.
+                        input.reset();
+                        IO.copy(input, sink.getOutputStream());
+                        return true;
+                    }
+                };
+            }
+        });
+        startClient();
+
+        ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
+        Assert.assertArrayEquals(data, response.getContent());
+    }
+
+    @Test
+    public void testAfterContentTransformerOverflowingToDisk() throws Exception
+    {
+        // Make sure the temporary directory we use exists and it's empty.
+        final Path targetTestsDir = prepareTargetTestsDir();
+
+        final String key0 = "id";
+        long value0 = 1;
+        final String key1 = "channel";
+        String value1 = "foo";
+        final String json = "{ \"" + key0 + "\":" + value0 + ", \"" + key1 + "\":\"" + value1 + "\" }";
+        startServer(new HttpServlet()
+        {
+            @Override
+            protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+            {
+                response.getOutputStream().write(json.getBytes(StandardCharsets.UTF_8));
+            }
+        });
+        final String inputPrefix = "in_";
+        final String outputPrefix = "out_";
+        final String key2 = "c";
+        startProxy(new AsyncMiddleManServlet()
+        {
+            @Override
+            protected ContentTransformer newServerResponseContentTransformer(HttpServletRequest clientRequest, HttpServletResponse proxyResponse, Response serverResponse)
+            {
+                AfterContentTransformer transformer = new AfterContentTransformer()
+                {
+                    @Override
+                    public boolean transform(Source source, Sink sink) throws IOException
+                    {
+                        InputStream input = source.getInputStream();
+                        @SuppressWarnings("unchecked")
+                        Map<String, Object> obj = (Map<String, Object>)JSON.parse(new InputStreamReader(input, "UTF-8"));
+                        // Transform the object.
+                        obj.put(key2, obj.remove(key1));
+                        try (OutputStream output = sink.getOutputStream())
+                        {
+                            output.write(JSON.toString(obj).getBytes(StandardCharsets.UTF_8));
+                            return true;
+                        }
+                    }
+                };
+                transformer.setOverflowDirectory(targetTestsDir);
+                int maxBufferSize = json.length() / 4;
+                transformer.setMaxInputBufferSize(maxBufferSize);
+                transformer.setInputFilePrefix(inputPrefix);
+                transformer.setMaxOutputBufferSize(maxBufferSize);
+                transformer.setOutputFilePrefix(outputPrefix);
+                return transformer;
+            }
+        });
+        startClient();
+
+        ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        Assert.assertEquals(200, response.getStatus());
+        @SuppressWarnings("unchecked")
+        Map<String, Object> obj = (Map<String, Object>)JSON.parse(response.getContentAsString());
+        Assert.assertNotNull(obj);
+        Assert.assertEquals(2, obj.size());
+        Assert.assertEquals(value0, obj.get(key0));
+        Assert.assertEquals(value1, obj.get(key2));
+        // Make sure the files do not exist.
+        try (DirectoryStream<Path> paths = Files.newDirectoryStream(targetTestsDir, inputPrefix + "*.*"))
+        {
+            Assert.assertFalse(paths.iterator().hasNext());
+        }
+        try (DirectoryStream<Path> paths = Files.newDirectoryStream(targetTestsDir, outputPrefix + "*.*"))
+        {
+            Assert.assertFalse(paths.iterator().hasNext());
+        }
+    }
+
+    @Test
+    public void testAfterContentTransformerClosingFilesOnClientRequestException() throws Exception
+    {
+        final Path targetTestsDir = prepareTargetTestsDir();
+
+        startServer(new HttpServlet()
+        {
+            @Override
+            protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+            {
+                IO.copy(request.getInputStream(), IO.getNullStream());
+            }
+        });
+        final CountDownLatch destroyLatch = new CountDownLatch(1);
+        startProxy(new AsyncMiddleManServlet()
+        {
+            @Override
+            protected ContentTransformer newClientRequestContentTransformer(HttpServletRequest clientRequest, Request proxyRequest)
+            {
+                return new AfterContentTransformer()
+                {
+                    {
+                        setOverflowDirectory(targetTestsDir);
+                        setMaxInputBufferSize(0);
+                        setMaxOutputBufferSize(0);
+                    }
+
+                    @Override
+                    public boolean transform(Source source, Sink sink) throws IOException
+                    {
+                        IO.copy(source.getInputStream(), sink.getOutputStream());
+                        return true;
+                    }
+
+                    @Override
+                    public void destroy()
+                    {
+                        super.destroy();
+                        destroyLatch.countDown();
+                    }
+                };
+            }
+        });
+        long idleTimeout = 1000;
+        proxyConnector.setIdleTimeout(idleTimeout);
+        startClient();
+
+        // Send only part of the content; the proxy will idle timeout.
+        final byte[] data = new byte[]{'c', 'a', 'f', 'e'};
+        ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
+                .header(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString())
+                .content(new BytesContentProvider(data)
+                {
+                    @Override
+                    public long getLength()
+                    {
+                        return data.length + 1;
+                    }
+                })
+                .timeout(5 * idleTimeout, TimeUnit.MILLISECONDS)
+                .send();
+
+        Assert.assertTrue(destroyLatch.await(5 * idleTimeout, TimeUnit.MILLISECONDS));
+        Assert.assertEquals(HttpStatus.REQUEST_TIMEOUT_408, response.getStatus());
+    }
+
+    @Test
+    public void testAfterContentTransformerClosingFilesOnServerResponseException() throws Exception
+    {
+        final Path targetTestsDir = prepareTargetTestsDir();
+
+        final CountDownLatch serviceLatch = new CountDownLatch(1);
+        startServer(new HttpServlet()
+        {
+            @Override
+            protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+            {
+                response.setHeader(HttpHeader.CONNECTION.asString(), HttpHeaderValue.CLOSE.asString());
+                response.setContentLength(2);
+                // Send only part of the content.
+                OutputStream output = response.getOutputStream();
+                output.write('x');
+                output.flush();
+                serviceLatch.countDown();
+            }
+        });
+        final CountDownLatch destroyLatch = new CountDownLatch(1);
+        startProxy(new AsyncMiddleManServlet()
+        {
+            @Override
+            protected ContentTransformer newServerResponseContentTransformer(HttpServletRequest clientRequest, HttpServletResponse proxyResponse, Response serverResponse)
+            {
+                return new AfterContentTransformer()
+                {
+                    {
+                        setOverflowDirectory(targetTestsDir);
+                        setMaxInputBufferSize(0);
+                        setMaxOutputBufferSize(0);
+                    }
+
+                    @Override
+                    public boolean transform(Source source, Sink sink) throws IOException
+                    {
+                        IO.copy(source.getInputStream(), sink.getOutputStream());
+                        return true;
+                    }
+
+                    @Override
+                    public void destroy()
+                    {
+                        super.destroy();
+                        destroyLatch.countDown();
+                    }
+                };
+            }
+        });
+        startClient();
+
+        ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        Assert.assertTrue(serviceLatch.await(5, TimeUnit.SECONDS));
+        Assert.assertTrue(destroyLatch.await(5, TimeUnit.SECONDS));
+        Assert.assertEquals(HttpStatus.BAD_GATEWAY_502, response.getStatus());
+    }
+
+    @Test
+    public void testAfterContentTransformerDoNotReadSourceDoNotTransform() throws Exception
+    {
+        testAfterContentTransformerDoNoTransform(false, false);
+    }
+
+    @Test
+    public void testAfterContentTransformerReadSourceDoNotTransform() throws Exception
+    {
+        testAfterContentTransformerDoNoTransform(true, true);
+    }
+
+    private void testAfterContentTransformerDoNoTransform(final boolean readSource, final boolean useDisk) throws Exception
+    {
+        final String key0 = "id";
+        long value0 = 1;
+        final String key1 = "channel";
+        String value1 = "foo";
+        final String json = "{ \"" + key0 + "\":" + value0 + ", \"" + key1 + "\":\"" + value1 + "\" }";
+        startServer(new HttpServlet()
+        {
+            @Override
+            protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+            {
+                response.getOutputStream().write(json.getBytes(StandardCharsets.UTF_8));
+            }
+        });
+        startProxy(new AsyncMiddleManServlet()
+        {
+            @Override
+            protected ContentTransformer newServerResponseContentTransformer(HttpServletRequest clientRequest, HttpServletResponse proxyResponse, Response serverResponse)
+            {
+                return new AfterContentTransformer()
+                {
+                    {
+                        if (useDisk)
+                            setMaxInputBufferSize(0);
+                    }
+
+                    @Override
+                    public boolean transform(Source source, Sink sink) throws IOException
+                    {
+                        if (readSource)
+                        {
+                            InputStream input = source.getInputStream();
+                            JSON.parse(new InputStreamReader(input, "UTF-8"));
+                        }
+                        // No transformation.
+                        return false;
+                    }
+                };
+            }
+        });
+        startClient();
+
+        ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        Assert.assertEquals(200, response.getStatus());
+        @SuppressWarnings("unchecked")
+        Map<String, Object> obj = (Map<String, Object>)JSON.parse(response.getContentAsString());
+        Assert.assertNotNull(obj);
+        Assert.assertEquals(2, obj.size());
+        Assert.assertEquals(value0, obj.get(key0));
+        Assert.assertEquals(value1, obj.get(key1));
+    }
+
+    @Test
+    public void testServer401() throws Exception
+    {
+        startServer(new HttpServlet()
+        {
+            @Override
+            protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+            {
+                response.setStatus(HttpStatus.UNAUTHORIZED_401);
+                response.setHeader(HttpHeader.WWW_AUTHENTICATE.asString(), "Basic realm=\"test\"");
+            }
+        });
+        final AtomicBoolean transformed = new AtomicBoolean();
+        startProxy(new AsyncMiddleManServlet()
+        {
+            @Override
+            protected ContentTransformer newServerResponseContentTransformer(HttpServletRequest clientRequest, HttpServletResponse proxyResponse, Response serverResponse)
+            {
+                return new AfterContentTransformer()
+                {
+                    @Override
+                    public boolean transform(Source source, Sink sink) throws IOException
+                    {
+                        transformed.set(true);
+                        return false;
+                    }
+                };
+            }
+        });
+        startClient();
+
+        ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        Assert.assertEquals(HttpStatus.UNAUTHORIZED_401, response.getStatus());
+        Assert.assertFalse(transformed.get());
+    }
+
+    @Test
+    public void testProxyRequestHeadersSentWhenDiscardingContent() throws Exception
+    {
+        startServer(new EchoHttpServlet());
+        final CountDownLatch proxyRequestLatch = new CountDownLatch(1);
+        startProxy(new AsyncMiddleManServlet()
+        {
+            @Override
+            protected ContentTransformer newClientRequestContentTransformer(HttpServletRequest clientRequest, Request proxyRequest)
+            {
+                return new DiscardContentTransformer();
+            }
+
+            @Override
+            protected void sendProxyRequest(HttpServletRequest clientRequest, HttpServletResponse proxyResponse, Request proxyRequest)
+            {
+                proxyRequestLatch.countDown();
+                super.sendProxyRequest(clientRequest, proxyResponse, proxyRequest);
+            }
+        });
+        startClient();
+
+        DeferredContentProvider content = new DeferredContentProvider();
+        Request request = client.newRequest("localhost", serverConnector.getLocalPort())
+                .timeout(5, TimeUnit.SECONDS)
+                .content(content);
+        FutureResponseListener listener = new FutureResponseListener(request);
+        request.send(listener);
+
+        // Send one chunk of content, the proxy request must not be sent.
+        ByteBuffer chunk1 = ByteBuffer.allocate(1024);
+        content.offer(chunk1);
+        Assert.assertFalse(proxyRequestLatch.await(1, TimeUnit.SECONDS));
+
+        // Send another chunk of content, the proxy request must not be sent.
+        ByteBuffer chunk2 = ByteBuffer.allocate(512);
+        content.offer(chunk2);
+        Assert.assertFalse(proxyRequestLatch.await(1, TimeUnit.SECONDS));
+
+        // Finish the content, request must be sent.
+        content.close();
+        Assert.assertTrue(proxyRequestLatch.await(1, TimeUnit.SECONDS));
+
+        ContentResponse response = listener.get(5, TimeUnit.SECONDS);
+        Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
+        Assert.assertEquals(0, response.getContent().length);
+    }
+
+    @Test
+    public void testProxyRequestHeadersNotSentUntilContent() throws Exception
+    {
+        startServer(new EchoHttpServlet());
+        final CountDownLatch proxyRequestLatch = new CountDownLatch(1);
+        startProxy(new AsyncMiddleManServlet()
+        {
+            @Override
+            protected ContentTransformer newClientRequestContentTransformer(HttpServletRequest clientRequest, Request proxyRequest)
+            {
+                return new BufferingContentTransformer();
+            }
+
+            @Override
+            protected void sendProxyRequest(HttpServletRequest clientRequest, HttpServletResponse proxyResponse, Request proxyRequest)
+            {
+                proxyRequestLatch.countDown();
+                super.sendProxyRequest(clientRequest, proxyResponse, proxyRequest);
+            }
+        });
+        startClient();
+
+        DeferredContentProvider content = new DeferredContentProvider();
+        Request request = client.newRequest("localhost", serverConnector.getLocalPort())
+                .timeout(5, TimeUnit.SECONDS)
+                .content(content);
+        FutureResponseListener listener = new FutureResponseListener(request);
+        request.send(listener);
+
+        // Send one chunk of content, the proxy request must not be sent.
+        ByteBuffer chunk1 = ByteBuffer.allocate(1024);
+        content.offer(chunk1);
+        Assert.assertFalse(proxyRequestLatch.await(1, TimeUnit.SECONDS));
+
+        // Send another chunk of content, the proxy request must not be sent.
+        ByteBuffer chunk2 = ByteBuffer.allocate(512);
+        content.offer(chunk2);
+        Assert.assertFalse(proxyRequestLatch.await(1, TimeUnit.SECONDS));
+
+        // Finish the content, request must be sent.
+        content.close();
+        Assert.assertTrue(proxyRequestLatch.await(1, TimeUnit.SECONDS));
+
+        ContentResponse response = listener.get(5, TimeUnit.SECONDS);
+        Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
+        Assert.assertEquals(chunk1.capacity() + chunk2.capacity(), response.getContent().length);
+    }
+
+    @Test
+    public void testProxyRequestHeadersNotSentUntilFirstContent() throws Exception
+    {
+        startServer(new EchoHttpServlet());
+        final CountDownLatch proxyRequestLatch = new CountDownLatch(1);
+        startProxy(new AsyncMiddleManServlet()
+        {
+            @Override
+            protected ContentTransformer newClientRequestContentTransformer(HttpServletRequest clientRequest, Request proxyRequest)
+            {
+                return new ContentTransformer()
+                {
+                    private ByteBuffer buffer;
+
+                    @Override
+                    public void transform(ByteBuffer input, boolean finished, List<ByteBuffer> output) throws IOException
+                    {
+                        // Buffer only the first chunk.
+                        if (buffer == null)
+                        {
+                            buffer = ByteBuffer.allocate(input.remaining());
+                            buffer.put(input).flip();
+                        }
+                        else if (buffer.hasRemaining())
+                        {
+                            output.add(buffer);
+                            output.add(input);
+                        }
+                        else
+                        {
+                            output.add(input);
+                        }
+                    }
+                };
+            }
+
+            @Override
+            protected void sendProxyRequest(HttpServletRequest clientRequest, HttpServletResponse proxyResponse, Request proxyRequest)
+            {
+                proxyRequestLatch.countDown();
+                super.sendProxyRequest(clientRequest, proxyResponse, proxyRequest);
+            }
+        });
+        startClient();
+
+        DeferredContentProvider content = new DeferredContentProvider();
+        Request request = client.newRequest("localhost", serverConnector.getLocalPort())
+                .timeout(5, TimeUnit.SECONDS)
+                .content(content);
+        FutureResponseListener listener = new FutureResponseListener(request);
+        request.send(listener);
+
+        // Send one chunk of content, the proxy request must not be sent.
+        ByteBuffer chunk1 = ByteBuffer.allocate(1024);
+        content.offer(chunk1);
+        Assert.assertFalse(proxyRequestLatch.await(1, TimeUnit.SECONDS));
+
+        // Send another chunk of content, the proxy request must be sent.
+        ByteBuffer chunk2 = ByteBuffer.allocate(512);
+        content.offer(chunk2);
+        Assert.assertTrue(proxyRequestLatch.await(5, TimeUnit.SECONDS));
+
+        // Finish the content.
+        content.close();
+
+        ContentResponse response = listener.get(5, TimeUnit.SECONDS);
+        Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
+        Assert.assertEquals(chunk1.capacity() + chunk2.capacity(), response.getContent().length);
+    }
+
+    private Path prepareTargetTestsDir() throws IOException
+    {
+        final Path targetTestsDir = MavenTestingUtils.getTargetTestingDir().toPath();
+        Files.createDirectories(targetTestsDir);
+        try (DirectoryStream<Path> files = Files.newDirectoryStream(targetTestsDir, "*.*"))
+        {
+            for (Path file : files)
+            {
+                if (!Files.isDirectory(file))
+                    Files.delete(file);
+            }
+        }
+        return targetTestsDir;
+    }
+
+    private void sleep(long delay)
+    {
+        try
+        {
+            Thread.sleep(delay);
+        }
+        catch (InterruptedException x)
+        {
+            throw new RuntimeIOException(x);
+        }
+    }
+
+    private byte[] gzip(byte[] bytes) throws IOException
+    {
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        try (GZIPOutputStream gzipOut = new GZIPOutputStream(out))
+        {
+            gzipOut.write(bytes);
+        }
+        return out.toByteArray();
+    }
+
+    private static abstract class HrefTransformer implements AsyncMiddleManServlet.ContentTransformer
+    {
+        private static final String PREFIX = "http://localhost/q=";
+        private final HrefParser parser = new HrefParser();
+        private final List<ByteBuffer> matches = new ArrayList<>();
+        private boolean matching;
+
+        @Override
+        public void transform(ByteBuffer input, boolean finished, List<ByteBuffer> output) throws IOException
+        {
+            int position = input.position();
+            while (input.hasRemaining())
+            {
+                boolean match = parser.parse(input);
+
+                // Get the slice of what has been parsed so far.
+                int limit = input.limit();
+                input.limit(input.position());
+                input.position(position);
+                ByteBuffer slice = input.slice();
+                input.position(input.limit());
+                input.limit(limit);
+                position = input.position();
+
+                if (matching)
+                {
+                    if (match)
+                    {
+                        ByteBuffer copy = ByteBuffer.allocate(slice.remaining());
+                        copy.put(slice).flip();
+                        matches.add(copy);
+                    }
+                    else
+                    {
+                        matching = false;
+
+                        // Transform the matches.
+                        Utf8StringBuilder builder = new Utf8StringBuilder();
+                        for (ByteBuffer buffer : matches)
+                            builder.append(buffer);
+                        String transformed = transform(builder.toString());
+                        output.add(ByteBuffer.wrap(transformed.getBytes(StandardCharsets.UTF_8)));
+                        output.add(slice);
+                    }
+                }
+                else
+                {
+                    if (match)
+                    {
+                        matching = true;
+                        ByteBuffer copy = ByteBuffer.allocate(slice.remaining());
+                        copy.put(slice).flip();
+                        matches.add(copy);
+                    }
+                    else
+                    {
+                        output.add(slice);
+                    }
+                }
+            }
+        }
+
+        protected abstract String transform(String value) throws IOException;
+
+        private static class Client extends HrefTransformer
+        {
+            @Override
+            protected String transform(String value) throws IOException
+            {
+                String result = PREFIX + URLEncoder.encode(value, "UTF-8");
+                LOG.debug("{} -> {}", value, result);
+                return result;
+            }
+        }
+
+        private static class Server extends HrefTransformer
+        {
+            @Override
+            protected String transform(String value) throws IOException
+            {
+                String result = URLDecoder.decode(value.substring(PREFIX.length()), "UTF-8");
+                LOG.debug("{} <- {}", value, result);
+                return result;
+            }
+        }
+    }
+
+    private static class HrefParser
+    {
+        private final byte[] token = {'h', 'r', 'e', 'f', '=', '"'};
+        private int state;
+
+        private boolean parse(ByteBuffer buffer)
+        {
+            while (buffer.hasRemaining())
+            {
+                int current = buffer.get() & 0xFF;
+                if (state < token.length)
+                {
+                    if (Character.toLowerCase(current) != token[state])
+                    {
+                        state = 0;
+                        continue;
+                    }
+
+                    ++state;
+                    if (state == token.length)
+                        return false;
+                }
+                else
+                {
+                    // Look for the ending quote.
+                    if (current == '"')
+                    {
+                        buffer.position(buffer.position() - 1);
+                        state = 0;
+                        return true;
+                    }
+                }
+            }
+            return state == token.length;
+        }
+    }
+
+    private static class BufferingContentTransformer implements AsyncMiddleManServlet.ContentTransformer
+    {
+        private final List<ByteBuffer> buffers = new ArrayList<>();
+
+        @Override
+        public void transform(ByteBuffer input, boolean finished, List<ByteBuffer> output) throws IOException
+        {
+            if (input.hasRemaining())
+            {
+                ByteBuffer copy = ByteBuffer.allocate(input.remaining());
+                copy.put(input).flip();
+                buffers.add(copy);
+            }
+
+            if (finished)
+            {
+                Assert.assertFalse(buffers.isEmpty());
+                output.addAll(buffers);
+                buffers.clear();
+            }
+        }
+    }
+
+    /**
+     * A transformer that discards all but the first line of text.
+     */
+    private static class HeadTransformer implements AsyncMiddleManServlet.ContentTransformer
+    {
+        private StringBuilder head = new StringBuilder();
+
+        @Override
+        public void transform(ByteBuffer input, boolean finished, List<ByteBuffer> output) throws IOException
+        {
+            if (input.hasRemaining() && head != null)
+            {
+                int lnPos = findLineFeed(input);
+                if (lnPos == -1)
+                {
+                    // no linefeed found, copy it all
+                    copyHeadBytes(input,input.limit());
+                }
+                else
+                {
+                    // found linefeed
+                    copyHeadBytes(input,lnPos);
+                    output.addAll(getHeadBytes());
+                    // mark head as sent
+                    head = null;
+                }
+            }
+
+            if (finished && head != null)
+            {
+                output.addAll(getHeadBytes());
+            }
+        }
+
+        private void copyHeadBytes(ByteBuffer input, int pos)
+        {
+            ByteBuffer dup = input.duplicate();
+            dup.limit(pos);
+            String str = BufferUtil.toUTF8String(dup);
+            head.append(str);
+        }
+
+        private int findLineFeed(ByteBuffer input)
+        {
+            for (int i = input.position(); i < input.limit(); i++)
+            {
+                byte b = input.get(i);
+                if ((b == (byte)'\n') || (b == (byte)'\r'))
+                {
+                    return i;
+                }
+            }
+            return -1;
+        }
+
+        private List<ByteBuffer> getHeadBytes()
+        {
+            ByteBuffer buf = BufferUtil.toBuffer(head.toString(),StandardCharsets.UTF_8);
+            return Collections.singletonList(buf);
+        }
+    }
+
+    private static class DiscardContentTransformer implements AsyncMiddleManServlet.ContentTransformer
+    {
+        @Override
+        public void transform(ByteBuffer input, boolean finished, List<ByteBuffer> output) throws IOException
+        {
+        }
+    }
+}
diff --git a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/AsyncProxyServletLoadTest.java b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/AsyncProxyServletLoadTest.java
new file mode 100644
index 0000000..418cb6c
--- /dev/null
+++ b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/AsyncProxyServletLoadTest.java
@@ -0,0 +1,236 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.proxy;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Random;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.HttpProxy;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.util.BytesContentProvider;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.HttpConnectionFactory;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+ at RunWith(Parameterized.class)
+public class AsyncProxyServletLoadTest
+{
+    @Parameterized.Parameters(name = "{0}")
+    public static Iterable<Object[]> data()
+    {
+        return Arrays.asList(new Object[][]{
+                {AsyncProxyServlet.class},
+                {AsyncMiddleManServlet.class}
+        });
+    }
+
+    private static final Logger LOG = Log.getLogger(AsyncProxyServletLoadTest.class);
+    private static final String PROXIED_HEADER = "X-Proxied";
+
+    private HttpClient client;
+    private Server proxy;
+    private ServerConnector proxyConnector;
+    private AbstractProxyServlet proxyServlet;
+    private Server server;
+    private ServerConnector serverConnector;
+
+    public AsyncProxyServletLoadTest(Class<?> proxyServletClass) throws Exception
+    {
+        proxyServlet = (AbstractProxyServlet)proxyServletClass.newInstance();
+    }
+
+    private void startServer(HttpServlet servlet) throws Exception
+    {
+        QueuedThreadPool serverPool = new QueuedThreadPool();
+        serverPool.setName("server");
+        server = new Server(serverPool);
+        serverConnector = new ServerConnector(server);
+        server.addConnector(serverConnector);
+
+        ServletContextHandler appCtx = new ServletContextHandler(server, "/", true, false);
+        ServletHolder appServletHolder = new ServletHolder(servlet);
+        appCtx.addServlet(appServletHolder, "/*");
+
+        server.start();
+    }
+
+    private void startProxy() throws Exception
+    {
+        QueuedThreadPool proxyPool = new QueuedThreadPool();
+        proxyPool.setName("proxy");
+        proxy = new Server(proxyPool);
+
+        HttpConfiguration configuration = new HttpConfiguration();
+        configuration.setSendDateHeader(false);
+        configuration.setSendServerVersion(false);
+        proxyConnector = new ServerConnector(proxy, new HttpConnectionFactory(configuration));
+        proxy.addConnector(proxyConnector);
+
+        ServletContextHandler proxyContext = new ServletContextHandler(proxy, "/", true, false);
+        ServletHolder proxyServletHolder = new ServletHolder(proxyServlet);
+        proxyContext.addServlet(proxyServletHolder, "/*");
+
+        proxy.start();
+    }
+
+    private void startClient() throws Exception
+    {
+        QueuedThreadPool clientPool = new QueuedThreadPool();
+        clientPool.setName("client");
+        HttpClient result = new HttpClient();
+        result.setExecutor(clientPool);
+        result.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", proxyConnector.getLocalPort()));
+        result.start();
+        client = result;
+    }
+
+    @After
+    public void dispose() throws Exception
+    {
+        client.stop();
+        proxy.stop();
+        server.stop();
+    }
+
+    @Test
+    public void test() throws Exception
+    {
+        startServer(new HttpServlet()
+        {
+            @Override
+            protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+            {
+                if (req.getHeader("Via") != null)
+                    resp.addHeader(PROXIED_HEADER, "true");
+                IO.copy(req.getInputStream(), resp.getOutputStream());
+            }
+        });
+        startProxy();
+        startClient();
+
+        // Number of clients to simulate
+        int clientCount = Runtime.getRuntime().availableProcessors();
+
+        // Latch for number of clients still active (used to terminate test)
+        final CountDownLatch activeClientLatch = new CountDownLatch(clientCount);
+
+        // Atomic Boolean to track that its OK to still continue looping.
+        // When this goes false, that means one of the client threads has
+        // encountered an error condition, and should allow all remaining
+        // client threads to finish cleanly.
+        final AtomicBoolean success = new AtomicBoolean(true);
+
+        int iterations = 1000;
+
+        // Start clients
+        for (int i = 0; i < clientCount; i++)
+        {
+            ClientLoop r = new ClientLoop(activeClientLatch, success, client, "localhost", serverConnector.getLocalPort(), iterations);
+            String name = "client-" + i;
+            Thread thread = new Thread(r, name);
+            thread.start();
+        }
+
+        Assert.assertTrue(activeClientLatch.await(clientCount * iterations * 10, TimeUnit.MILLISECONDS));
+        Assert.assertTrue(success.get());
+    }
+
+    private static class ClientLoop implements Runnable
+    {
+        private final CountDownLatch active;
+        private final AtomicBoolean success;
+        private final HttpClient client;
+        private final String host;
+        private final int port;
+        private int iterations;
+
+        public ClientLoop(CountDownLatch activeClientLatch, AtomicBoolean success, HttpClient client, String serverHost, int serverPort, int iterations)
+        {
+            this.active = activeClientLatch;
+            this.success = success;
+            this.client = client;
+            this.host = serverHost;
+            this.port = serverPort;
+            this.iterations = iterations;
+        }
+
+        @Override
+        public void run()
+        {
+            String threadName = Thread.currentThread().getName();
+            LOG.info("Starting thread {}", threadName);
+            try
+            {
+                while (success.get())
+                {
+                    --iterations;
+
+                    byte[] content = new byte[1024];
+                    new Random().nextBytes(content);
+                    ContentResponse response = client.newRequest(host, port).method(HttpMethod.POST).content(new BytesContentProvider(content))
+                            .timeout(5, TimeUnit.SECONDS).send();
+
+                    if (response.getStatus() != 200)
+                    {
+                        LOG.warn("Got response <{}>, expecting <{}>", response.getStatus(), 200);
+                        // allow all ClientLoops to finish
+                        success.set(false);
+                    }
+                    else
+                    {
+                        if (iterations == 0)
+                            break;
+                    }
+                }
+            }
+            catch (Throwable x)
+            {
+                LOG.warn("Error processing request", x);
+                success.set(false);
+            }
+            finally
+            {
+                LOG.info("Shutting down thread {}", threadName);
+                active.countDown();
+            }
+        }
+    }
+}
diff --git a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/BalancerServletTest.java b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/BalancerServletTest.java
new file mode 100644
index 0000000..4d29b13
--- /dev/null
+++ b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/BalancerServletTest.java
@@ -0,0 +1,201 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.proxy;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.session.AbstractSessionIdManager;
+import org.eclipse.jetty.server.session.HashSessionIdManager;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class BalancerServletTest
+{
+    private static final String CONTEXT_PATH = "/context";
+    private static final String SERVLET_PATH = "/mapping";
+
+    private boolean stickySessions;
+    private Server server1;
+    private Server server2;
+    private Server balancer;
+    private HttpClient client;
+
+    @Before
+    public void prepare() throws Exception
+    {
+        client = new HttpClient();
+        client.start();
+    }
+
+    @After
+    public void dispose() throws Exception
+    {
+        server1.stop();
+        server2.stop();
+        balancer.stop();
+        client.stop();
+    }
+
+    protected void startBalancer(Class<? extends HttpServlet> servletClass) throws Exception
+    {
+        server1 = createServer(new ServletHolder(servletClass), "node1");
+        server1.start();
+
+        server2 = createServer(new ServletHolder(servletClass), "node2");
+        server2.start();
+
+        ServletHolder balancerServletHolder = new ServletHolder(BalancerServlet.class);
+        balancerServletHolder.setInitParameter("stickySessions", String.valueOf(stickySessions));
+        balancerServletHolder.setInitParameter("proxyPassReverse", "true");
+        balancerServletHolder.setInitParameter("balancerMember." + "node1" + ".proxyTo", "http://localhost:" + getServerPort(server1));
+        balancerServletHolder.setInitParameter("balancerMember." + "node2" + ".proxyTo", "http://localhost:" + getServerPort(server2));
+
+        balancer = createServer(balancerServletHolder, null);
+        balancer.start();
+    }
+
+    private Server createServer(ServletHolder servletHolder, String nodeName)
+    {
+        Server server = new Server();
+        ServerConnector connector = new ServerConnector(server);
+        server.addConnector(connector);
+
+        ServletContextHandler context = new ServletContextHandler(server, CONTEXT_PATH, ServletContextHandler.SESSIONS);
+        context.addServlet(servletHolder, SERVLET_PATH + "/*");
+
+        if (nodeName != null)
+        {
+            AbstractSessionIdManager sessionIdManager = new HashSessionIdManager();
+            sessionIdManager.setWorkerName(nodeName);
+            server.setSessionIdManager(sessionIdManager);
+        }
+
+        return server;
+    }
+
+    private int getServerPort(Server server)
+    {
+        return server.getURI().getPort();
+    }
+
+    protected byte[] sendRequestToBalancer(String path) throws Exception
+    {
+        ContentResponse response = client.newRequest("localhost", getServerPort(balancer))
+                .path(CONTEXT_PATH + SERVLET_PATH + path)
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+        return response.getContent();
+    }
+
+    @Test
+    public void testRoundRobinBalancer() throws Exception
+    {
+        stickySessions = false;
+        startBalancer(CounterServlet.class);
+        for (int i = 0; i < 10; i++)
+        {
+            byte[] responseBytes = sendRequestToBalancer("/roundRobin");
+            String returnedCounter = readFirstLine(responseBytes);
+            // Counter should increment every other request
+            String expectedCounter = String.valueOf(i / 2);
+            Assert.assertEquals(expectedCounter, returnedCounter);
+        }
+    }
+
+    @Test
+    public void testStickySessionsBalancer() throws Exception
+    {
+        stickySessions = true;
+        startBalancer(CounterServlet.class);
+        for (int i = 0; i < 10; i++)
+        {
+            byte[] responseBytes = sendRequestToBalancer("/stickySessions");
+            String returnedCounter = readFirstLine(responseBytes);
+            // Counter should increment every request
+            String expectedCounter = String.valueOf(i);
+            Assert.assertEquals(expectedCounter, returnedCounter);
+        }
+    }
+
+    @Test
+    public void testProxyPassReverse() throws Exception
+    {
+        stickySessions = false;
+        startBalancer(RelocationServlet.class);
+        byte[] responseBytes = sendRequestToBalancer("/index.html");
+        String msg = readFirstLine(responseBytes);
+        Assert.assertEquals("success", msg);
+    }
+
+    private String readFirstLine(byte[] responseBytes) throws IOException
+    {
+        BufferedReader reader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(responseBytes)));
+        return reader.readLine();
+    }
+
+    public static final class CounterServlet extends HttpServlet
+    {
+        private final AtomicInteger counter = new AtomicInteger();
+
+        @Override
+        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+        {
+            // Force session creation
+            req.getSession();
+            resp.setContentType("text/plain");
+            resp.getWriter().print(counter.getAndIncrement());
+        }
+    }
+
+    public static final class RelocationServlet extends HttpServlet
+    {
+        @Override
+        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+        {
+            if (req.getRequestURI().endsWith("/index.html"))
+            {
+                resp.sendRedirect("http://localhost:" + req.getLocalPort() + req.getContextPath() + req.getServletPath() + "/other.html?secret=pipo+molo");
+            }
+            else
+            {
+                resp.setContentType("text/plain");
+                if ("pipo molo".equals(req.getParameter("secret")))
+                    resp.getWriter().println("success");
+            }
+        }
+    }
+}
diff --git a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ConnectHandlerSSLTest.java b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ConnectHandlerSSLTest.java
new file mode 100644
index 0000000..d1696d8
--- /dev/null
+++ b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ConnectHandlerSSLTest.java
@@ -0,0 +1,201 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.proxy;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.Socket;
+import java.nio.charset.StandardCharsets;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.SSLSocketFactory;
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.eclipse.jetty.toolchain.test.http.SimpleHttpResponse;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class ConnectHandlerSSLTest extends AbstractConnectHandlerTest
+{
+    private SslContextFactory sslContextFactory;
+
+    @Before
+    public void prepare() throws Exception
+    {
+        sslContextFactory = new SslContextFactory();
+        String keyStorePath = MavenTestingUtils.getTestResourceFile("keystore").getAbsolutePath();
+        sslContextFactory.setKeyStorePath(keyStorePath);
+        sslContextFactory.setKeyStorePassword("storepwd");
+        sslContextFactory.setKeyManagerPassword("keypwd");
+        server = new Server();
+        serverConnector = new ServerConnector(server, sslContextFactory);
+        server.addConnector(serverConnector);
+        server.setHandler(new ServerHandler());
+        server.start();
+        prepareProxy();
+    }
+
+    @Test
+    public void testGETRequest() throws Exception
+    {
+        String hostPort = "localhost:" + serverConnector.getLocalPort();
+        String request = "" +
+                "CONNECT " + hostPort + " HTTP/1.1\r\n" +
+                "Host: " + hostPort + "\r\n" +
+                "\r\n";
+        try (Socket socket = newSocket())
+        {
+            OutputStream output = socket.getOutputStream();
+            BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
+
+            output.write(request.getBytes(StandardCharsets.UTF_8));
+            output.flush();
+
+            // Expect 200 OK from the CONNECT request
+            SimpleHttpResponse response = readResponse(input);
+            Assert.assertEquals("200", response.getCode());
+
+            // Be sure the buffered input does not have anything buffered
+            Assert.assertFalse(input.ready());
+
+            // Upgrade the socket to SSL
+            try (SSLSocket sslSocket = wrapSocket(socket))
+            {
+                output = sslSocket.getOutputStream();
+                input = new BufferedReader(new InputStreamReader(sslSocket.getInputStream()));
+
+                request =
+                        "GET /echo HTTP/1.1\r\n" +
+                                "Host: " + hostPort + "\r\n" +
+                                "\r\n";
+                output.write(request.getBytes(StandardCharsets.UTF_8));
+                output.flush();
+
+                response = readResponse(input);
+                Assert.assertEquals("200", response.getCode());
+                Assert.assertEquals("GET /echo", response.getBody());
+            }
+        }
+    }
+
+    @Test
+    public void testPOSTRequests() throws Exception
+    {
+        String hostPort = "localhost:" + serverConnector.getLocalPort();
+        String request = "" +
+                "CONNECT " + hostPort + " HTTP/1.1\r\n" +
+                "Host: " + hostPort + "\r\n" +
+                "\r\n";
+        try (Socket socket = newSocket())
+        {
+            OutputStream output = socket.getOutputStream();
+            BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
+
+            output.write(request.getBytes(StandardCharsets.UTF_8));
+            output.flush();
+
+            // Expect 200 OK from the CONNECT request
+            SimpleHttpResponse response = readResponse(input);
+            Assert.assertEquals("200", response.getCode());
+
+            // Be sure the buffered input does not have anything buffered
+            Assert.assertFalse(input.ready());
+
+            // Upgrade the socket to SSL
+            try (SSLSocket sslSocket = wrapSocket(socket))
+            {
+                output = sslSocket.getOutputStream();
+                input = new BufferedReader(new InputStreamReader(sslSocket.getInputStream()));
+
+                for (int i = 0; i < 10; ++i)
+                {
+                    request = "" +
+                            "POST /echo?param=" + i + " HTTP/1.1\r\n" +
+                            "Host: " + hostPort + "\r\n" +
+                            "Content-Length: 5\r\n" +
+                            "\r\n" +
+                            "HELLO";
+                    output.write(request.getBytes(StandardCharsets.UTF_8));
+                    output.flush();
+
+                    response = readResponse(input);
+                    Assert.assertEquals("200", response.getCode());
+                    Assert.assertEquals("POST /echo?param=" + i + "\r\nHELLO", response.getBody());
+                }
+            }
+        }
+    }
+
+    private SSLSocket wrapSocket(Socket socket) throws Exception
+    {
+        SSLContext sslContext = sslContextFactory.getSslContext();
+        SSLSocketFactory socketFactory = sslContext.getSocketFactory();
+        SSLSocket sslSocket = (SSLSocket)socketFactory.createSocket(socket, socket.getInetAddress().getHostAddress(), socket.getPort(), true);
+        sslSocket.setUseClientMode(true);
+        sslSocket.startHandshake();
+        return sslSocket;
+    }
+
+    private static class ServerHandler extends AbstractHandler
+    {
+        public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException
+        {
+            request.setHandled(true);
+
+            String uri = httpRequest.getRequestURI();
+            if ("/echo".equals(uri))
+            {
+                StringBuilder builder = new StringBuilder();
+                builder.append(httpRequest.getMethod()).append(" ").append(uri);
+                if (httpRequest.getQueryString() != null)
+                    builder.append("?").append(httpRequest.getQueryString());
+
+                ByteArrayOutputStream baos = new ByteArrayOutputStream();
+                InputStream input = httpRequest.getInputStream();
+                int read;
+                while ((read = input.read()) >= 0)
+                    baos.write(read);
+                baos.close();
+
+                ServletOutputStream output = httpResponse.getOutputStream();
+                output.println(builder.toString());
+                output.write(baos.toByteArray());
+            }
+            else
+            {
+                throw new ServletException();
+            }
+        }
+    }
+}
diff --git a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ConnectHandlerTest.java b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ConnectHandlerTest.java
new file mode 100644
index 0000000..f601975
--- /dev/null
+++ b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ConnectHandlerTest.java
@@ -0,0 +1,782 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.proxy;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.UnknownHostException;
+import java.nio.charset.StandardCharsets;
+import java.util.Locale;
+import java.util.concurrent.ConcurrentMap;
+
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.toolchain.test.http.SimpleHttpResponse;
+import org.eclipse.jetty.util.B64Code;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class ConnectHandlerTest extends AbstractConnectHandlerTest
+{
+    @Before
+    public void prepare() throws Exception
+    {
+        server = new Server();
+        serverConnector = new ServerConnector(server);
+        server.addConnector(serverConnector);
+        server.setHandler(new ServerHandler());
+        server.start();
+        prepareProxy();
+    }
+
+    @Test
+    public void testCONNECT() throws Exception
+    {
+        String hostPort = "localhost:" + serverConnector.getLocalPort();
+        String request = "" +
+                "CONNECT " + hostPort + " HTTP/1.1\r\n" +
+                "Host: " + hostPort + "\r\n" +
+                "\r\n";
+        try (Socket socket = newSocket())
+        {
+            OutputStream output = socket.getOutputStream();
+            BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
+
+            output.write(request.getBytes(StandardCharsets.UTF_8));
+            output.flush();
+
+            // Expect 200 OK from the CONNECT request
+            SimpleHttpResponse response = readResponse(input);
+            Assert.assertEquals("200", response.getCode());
+        }
+    }
+
+    @Test
+    public void testCONNECTAndGET() throws Exception
+    {
+        String hostPort = "localhost:" + serverConnector.getLocalPort();
+        String request = "" +
+                "CONNECT " + hostPort + " HTTP/1.1\r\n" +
+                "Host: " + hostPort + "\r\n" +
+                "\r\n";
+        try (Socket socket = newSocket())
+        {
+            OutputStream output = socket.getOutputStream();
+            BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
+
+            output.write(request.getBytes(StandardCharsets.UTF_8));
+            output.flush();
+
+            // Expect 200 OK from the CONNECT request
+            SimpleHttpResponse response = readResponse(input);
+            Assert.assertEquals("200", response.getCode());
+
+            request = "" +
+                    "GET /echo" + " HTTP/1.1\r\n" +
+                    "Host: " + hostPort + "\r\n" +
+                    "\r\n";
+            output.write(request.getBytes(StandardCharsets.UTF_8));
+            output.flush();
+
+            response = readResponse(input);
+            Assert.assertEquals("200", response.getCode());
+            Assert.assertEquals("GET /echo", response.getBody());
+        }
+    }
+
+    @Test
+    public void testProxyWhiteList() throws Exception
+    {
+        int port = serverConnector.getLocalPort();
+        String hostPort = "127.0.0.1:" + port;
+        connectHandler.getWhiteListHosts().add(hostPort);
+
+        // Try with the wrong host
+        String request = "" +
+                "CONNECT localhost:" + port + " HTTP/1.1\r\n" +
+                "Host: localhost:" + port + "\r\n" +
+                "\r\n";
+        try (Socket socket = newSocket())
+        {
+            OutputStream output = socket.getOutputStream();
+            BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
+
+            output.write(request.getBytes(StandardCharsets.UTF_8));
+            output.flush();
+
+            // Expect 403 from the CONNECT request
+            SimpleHttpResponse response = readResponse(input);
+            Assert.assertEquals("403", response.getCode());
+
+            // Socket should be closed
+            Assert.assertEquals(-1, input.read());
+        }
+
+        // Try again with the right host
+        request = "" +
+                "CONNECT " + hostPort + " HTTP/1.1\r\n" +
+                "Host: " + hostPort + "\r\n" +
+                "\r\n";
+        try (Socket socket = newSocket())
+        {
+            OutputStream output = socket.getOutputStream();
+            BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
+
+            output.write(request.getBytes(StandardCharsets.UTF_8));
+            output.flush();
+
+            // Expect 200 from the CONNECT request
+            SimpleHttpResponse response = readResponse(input);
+            Assert.assertEquals("200", response.getCode());
+
+            request = "" +
+                    "GET /echo" + " HTTP/1.1\r\n" +
+                    "Host: " + hostPort + "\r\n" +
+                    "\r\n";
+            output.write(request.getBytes(StandardCharsets.UTF_8));
+            output.flush();
+
+            response = readResponse(input);
+            Assert.assertEquals("200", response.getCode());
+            Assert.assertEquals("GET /echo", response.getBody());
+        }
+    }
+
+    @Test
+    public void testProxyBlackList() throws Exception
+    {
+        int port = serverConnector.getLocalPort();
+        String hostPort = "localhost:" + port;
+        connectHandler.getBlackListHosts().add(hostPort);
+
+        // Try with the wrong host
+        String request = "" +
+                "CONNECT " + hostPort + " HTTP/1.1\r\n" +
+                "Host: " + hostPort + "\r\n" +
+                "\r\n";
+        try (Socket socket = newSocket())
+        {
+            OutputStream output = socket.getOutputStream();
+            BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
+
+            output.write(request.getBytes(StandardCharsets.UTF_8));
+            output.flush();
+
+            // Expect 403 from the CONNECT request
+            SimpleHttpResponse response = readResponse(input);
+            Assert.assertEquals("403", response.getCode());
+
+            // Socket should be closed
+            Assert.assertEquals(-1, input.read());
+        }
+
+        // Try again with the right host
+        request = "" +
+                "CONNECT 127.0.0.1:" + port + " HTTP/1.1\r\n" +
+                "Host: 127.0.0.1:" + port + "\r\n" +
+                "\r\n";
+        try (Socket socket = newSocket())
+        {
+            OutputStream output = socket.getOutputStream();
+            BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
+
+            output.write(request.getBytes(StandardCharsets.UTF_8));
+            output.flush();
+
+            // Expect 200 from the CONNECT request
+            SimpleHttpResponse response = readResponse(input);
+            Assert.assertEquals("200", response.getCode());
+
+            request = "" +
+                    "GET /echo" + " HTTP/1.1\r\n" +
+                    "Host: 127.0.0.1:" + port + "\r\n" +
+                    "\r\n";
+            output.write(request.getBytes(StandardCharsets.UTF_8));
+            output.flush();
+
+            response = readResponse(input);
+            Assert.assertEquals("200", response.getCode());
+            Assert.assertEquals("GET /echo", response.getBody());
+        }
+    }
+
+    @Test
+    public void testProxyAuthentication() throws Exception
+    {
+        disposeProxy();
+        connectHandler = new ConnectHandler()
+        {
+            @Override
+            protected boolean handleAuthentication(HttpServletRequest request, HttpServletResponse response, String address)
+            {
+                String proxyAuthorization = request.getHeader("Proxy-Authorization");
+                if (proxyAuthorization == null)
+                {
+                    response.setHeader("Proxy-Authenticate", "Basic realm=\"test\"");
+                    return false;
+                }
+                String b64 = proxyAuthorization.substring("Basic ".length());
+                String credentials = B64Code.decode(b64, StandardCharsets.UTF_8);
+                return "test:test".equals(credentials);
+            }
+        };
+        proxy.setHandler(connectHandler);
+        proxy.start();
+
+        int port = serverConnector.getLocalPort();
+        String hostPort = "localhost:" + port;
+
+        // Try without authentication
+        String request = "" +
+                "CONNECT " + hostPort + " HTTP/1.1\r\n" +
+                "Host: " + hostPort + "\r\n" +
+                "\r\n";
+        try (Socket socket = newSocket())
+        {
+            OutputStream output = socket.getOutputStream();
+            BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
+
+            output.write(request.getBytes(StandardCharsets.UTF_8));
+            output.flush();
+
+            // Expect 407 from the CONNECT request
+            SimpleHttpResponse response = readResponse(input);
+            Assert.assertEquals("407", response.getCode());
+            Assert.assertTrue(response.getHeaders().containsKey("Proxy-Authenticate".toLowerCase(Locale.ENGLISH)));
+
+            // Socket should be closed
+            Assert.assertEquals(-1, input.read());
+        }
+
+        // Try with authentication
+        String credentials = "Basic " + B64Code.encode("test:test");
+        request = "" +
+                "CONNECT " + hostPort + " HTTP/1.1\r\n" +
+                "Host: " + hostPort + "\r\n" +
+                "Proxy-Authorization: " + credentials + "\r\n" +
+                "\r\n";
+        try (Socket socket = newSocket())
+        {
+            OutputStream output = socket.getOutputStream();
+            BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
+
+            output.write(request.getBytes(StandardCharsets.UTF_8));
+            output.flush();
+
+            // Expect 200 from the CONNECT request
+            SimpleHttpResponse response = readResponse(input);
+            Assert.assertEquals("200", response.getCode());
+
+            request = "" +
+                    "GET /echo" + " HTTP/1.1\r\n" +
+                    "Host: " + hostPort + "\r\n" +
+                    "\r\n";
+            output.write(request.getBytes(StandardCharsets.UTF_8));
+            output.flush();
+
+            response = readResponse(input);
+            Assert.assertEquals("200", response.getCode());
+            Assert.assertEquals("GET /echo", response.getBody());
+        }
+    }
+
+    @Test
+    public void testCONNECTBadHostPort() throws Exception
+    {
+        String invalidHostname = "badHost.webtide.com";
+
+        try
+        {
+            InetAddress address = InetAddress.getByName(invalidHostname);
+            StringBuilder err = new StringBuilder();
+            err.append("DNS Hijacking detected: ");
+            err.append(invalidHostname).append(" should have not returned a valid IP address [");
+            err.append(address.getHostAddress()).append("].  ");
+            err.append("Fix your DNS provider to have this test pass.");
+            err.append("\nFor more info see https://en.wikipedia.org/wiki/DNS_hijacking");
+            Assert.assertNull(err.toString(), address);
+        }
+        catch (UnknownHostException e)
+        {
+            // expected path
+        }
+
+        String hostPort = String.format("%s:%d", invalidHostname, serverConnector.getLocalPort());
+        String request = "" +
+                "CONNECT " + hostPort + " HTTP/1.1\r\n" +
+                "Host: " + hostPort + "\r\n" +
+                "\r\n";
+        Socket socket = newSocket();
+        socket.setSoTimeout(30000);
+        try
+        {
+            OutputStream output = socket.getOutputStream();
+            BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
+
+            output.write(request.getBytes(StandardCharsets.UTF_8));
+            output.flush();
+
+            // Expect 500 OK from the CONNECT request
+            SimpleHttpResponse response = readResponse(input);
+            Assert.assertEquals("Response Code", "500", response.getCode());
+        }
+        finally
+        {
+            socket.close();
+        }
+    }
+
+    @Test
+    public void testCONNECT10AndGET() throws Exception
+    {
+        String hostPort = "localhost:" + serverConnector.getLocalPort();
+        String request = "" +
+                "CONNECT " + hostPort + " HTTP/1.0\r\n" +
+                "Host: " + hostPort + "\r\n" +
+                "\r\n";
+        try (Socket socket = newSocket())
+        {
+            OutputStream output = socket.getOutputStream();
+            BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
+
+            output.write(request.getBytes(StandardCharsets.UTF_8));
+            output.flush();
+
+            // Expect 200 OK from the CONNECT request
+            SimpleHttpResponse response = readResponse(input);
+            Assert.assertEquals("200", response.getCode());
+
+            request = "" +
+                    "GET /echo" + " HTTP/1.1\r\n" +
+                    "Host: " + hostPort + "\r\n" +
+                    "\r\n";
+            output.write(request.getBytes(StandardCharsets.UTF_8));
+            output.flush();
+
+            response = readResponse(input);
+            Assert.assertEquals("200", response.getCode());
+            Assert.assertEquals("GET /echo", response.getBody());
+        }
+    }
+
+    @Test
+    public void testCONNECTAndGETPipelined() throws Exception
+    {
+        String hostPort = "localhost:" + serverConnector.getLocalPort();
+        String request = "" +
+                "CONNECT " + hostPort + " HTTP/1.1\r\n" +
+                "Host: " + hostPort + "\r\n" +
+                "\r\n" +
+                "GET /echo" + " HTTP/1.1\r\n" +
+                "Host: " + hostPort + "\r\n" +
+                "\r\n";
+        try (Socket socket = newSocket())
+        {
+            OutputStream output = socket.getOutputStream();
+            BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
+
+            output.write(request.getBytes(StandardCharsets.UTF_8));
+            output.flush();
+
+            // Expect 200 OK from the CONNECT request
+            SimpleHttpResponse response = readResponse(input);
+            Assert.assertEquals("200", response.getCode());
+
+            // The pipelined request must have gone up to the server as is
+            response = readResponse(input);
+            Assert.assertEquals("200", response.getCode());
+            Assert.assertEquals("GET /echo", response.getBody());
+        }
+    }
+
+    @Test
+    public void testCONNECTAndMultipleGETs() throws Exception
+    {
+        String hostPort = "localhost:" + serverConnector.getLocalPort();
+        String request = "" +
+                "CONNECT " + hostPort + " HTTP/1.1\r\n" +
+                "Host: " + hostPort + "\r\n" +
+                "\r\n";
+        try (Socket socket = newSocket())
+        {
+            OutputStream output = socket.getOutputStream();
+            BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
+
+            output.write(request.getBytes(StandardCharsets.UTF_8));
+            output.flush();
+
+            // Expect 200 OK from the CONNECT request
+            SimpleHttpResponse response = readResponse(input);
+            Assert.assertEquals("200", response.getCode());
+
+            for (int i = 0; i < 10; ++i)
+            {
+                request = "" +
+                        "GET /echo" + " HTTP/1.1\r\n" +
+                        "Host: " + hostPort + "\r\n" +
+                        "\r\n";
+                output.write(request.getBytes(StandardCharsets.UTF_8));
+                output.flush();
+
+                response = readResponse(input);
+                Assert.assertEquals("200", response.getCode());
+                Assert.assertEquals("GET /echo", response.getBody());
+            }
+        }
+    }
+
+    @Test
+    public void testCONNECTAndGETServerStop() throws Exception
+    {
+        String hostPort = "localhost:" + serverConnector.getLocalPort();
+        String request = "" +
+                "CONNECT " + hostPort + " HTTP/1.1\r\n" +
+                "Host: " + hostPort + "\r\n" +
+                "\r\n";
+        try (Socket socket = newSocket())
+        {
+            OutputStream output = socket.getOutputStream();
+            BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
+
+            output.write(request.getBytes(StandardCharsets.UTF_8));
+            output.flush();
+
+            // Expect 200 OK from the CONNECT request
+            SimpleHttpResponse response = readResponse(input);
+            Assert.assertEquals("200", response.getCode());
+
+            request = "" +
+                    "GET /echo HTTP/1.1\r\n" +
+                    "Host: " + hostPort + "\r\n" +
+                    "\r\n";
+            output.write(request.getBytes(StandardCharsets.UTF_8));
+            output.flush();
+
+            response = readResponse(input);
+            Assert.assertEquals("200", response.getCode());
+            Assert.assertEquals("GET /echo", response.getBody());
+
+            // Idle server is shut down
+            disposeServer();
+
+            int read = input.read();
+            Assert.assertEquals(-1, read);
+        }
+    }
+
+    @Test
+    public void testCONNECTAndGETAndServerSideClose() throws Exception
+    {
+        String hostPort = "localhost:" + serverConnector.getLocalPort();
+        String request = "" +
+                "CONNECT " + hostPort + " HTTP/1.1\r\n" +
+                "Host: " + hostPort + "\r\n" +
+                "\r\n";
+        try (Socket socket = newSocket())
+        {
+            OutputStream output = socket.getOutputStream();
+            BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
+
+            output.write(request.getBytes(StandardCharsets.UTF_8));
+            output.flush();
+
+            // Expect 200 OK from the CONNECT request
+            SimpleHttpResponse response = readResponse(input);
+            Assert.assertEquals("200", response.getCode());
+
+            request = "" +
+                    "GET /close HTTP/1.1\r\n" +
+                    "Host: " + hostPort + "\r\n" +
+                    "\r\n";
+            output.write(request.getBytes(StandardCharsets.UTF_8));
+            output.flush();
+
+            int read = input.read();
+            Assert.assertEquals(-1, read);
+        }
+    }
+
+    @Test
+    public void testCONNECTAndPOSTAndGET() throws Exception
+    {
+        String hostPort = "localhost:" + serverConnector.getLocalPort();
+        String request = "" +
+                "CONNECT " + hostPort + " HTTP/1.1\r\n" +
+                "Host: " + hostPort + "\r\n" +
+                "\r\n";
+        try (Socket socket = newSocket())
+        {
+            OutputStream output = socket.getOutputStream();
+            BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
+
+            output.write(request.getBytes(StandardCharsets.UTF_8));
+            output.flush();
+
+            // Expect 200 OK from the CONNECT request
+            SimpleHttpResponse response = readResponse(input);
+            Assert.assertEquals("200", response.getCode());
+
+            request = "" +
+                    "POST /echo HTTP/1.1\r\n" +
+                    "Host: " + hostPort + "\r\n" +
+                    "Content-Length: 5\r\n" +
+                    "\r\n" +
+                    "HELLO";
+            output.write(request.getBytes(StandardCharsets.UTF_8));
+            output.flush();
+
+            response = readResponse(input);
+            Assert.assertEquals("200", response.getCode());
+            Assert.assertEquals("POST /echo\r\nHELLO", response.getBody());
+
+            request = "" +
+                    "GET /echo" + " HTTP/1.1\r\n" +
+                    "Host: " + hostPort + "\r\n" +
+                    "\r\n";
+            output.write(request.getBytes(StandardCharsets.UTF_8));
+            output.flush();
+
+            response = readResponse(input);
+            Assert.assertEquals("200", response.getCode());
+            Assert.assertEquals("GET /echo", response.getBody());
+        }
+    }
+
+    @Test
+    public void testCONNECTAndPOSTWithBigBody() throws Exception
+    {
+        String hostPort = "localhost:" + serverConnector.getLocalPort();
+
+        String request = "" +
+                "CONNECT " + hostPort + " HTTP/1.1\r\n" +
+                "Host: " + hostPort + "\r\n" +
+                "\r\n";
+        try (Socket socket = newSocket())
+        {
+            OutputStream output = socket.getOutputStream();
+            BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
+
+            output.write(request.getBytes(StandardCharsets.UTF_8));
+            output.flush();
+
+            // Expect 200 OK from the CONNECT request
+            SimpleHttpResponse response = readResponse(input);
+            Assert.assertEquals("200", response.getCode());
+
+            StringBuilder body = new StringBuilder();
+            String chunk = "0123456789ABCDEF";
+            for (int i = 0; i < 1024 * 1024; ++i)
+                body.append(chunk);
+
+            request = "" +
+                    "POST /echo HTTP/1.1\r\n" +
+                    "Host: " + hostPort + "\r\n" +
+                    "Content-Length: " + body.length() + "\r\n" +
+                    "\r\n" +
+                    body;
+            output.write(request.getBytes(StandardCharsets.UTF_8));
+            output.flush();
+
+            response = readResponse(input);
+            Assert.assertEquals("200", response.getCode());
+            Assert.assertEquals("POST /echo\r\n" + body, response.getBody());
+        }
+    }
+
+    @Test
+    public void testCONNECTAndPOSTWithContext() throws Exception
+    {
+        final String contextKey = "contextKey";
+        final String contextValue = "contextValue";
+
+        // Replace the default ProxyHandler with a subclass to test context information passing
+        disposeProxy();
+        proxy.setHandler(new ConnectHandler()
+        {
+            @Override
+            protected boolean handleAuthentication(HttpServletRequest request, HttpServletResponse response, String address)
+            {
+                request.setAttribute(contextKey, contextValue);
+                return super.handleAuthentication(request, response, address);
+            }
+
+            @Override
+            protected void prepareContext(HttpServletRequest request, ConcurrentMap<String, Object> context)
+            {
+                // Transfer data from the HTTP request to the connection context
+                Assert.assertEquals(contextValue, request.getAttribute(contextKey));
+                context.put(contextKey, request.getAttribute(contextKey));
+            }
+        });
+        proxy.start();
+
+        String hostPort = "localhost:" + serverConnector.getLocalPort();
+        String request = "" +
+                "CONNECT " + hostPort + " HTTP/1.1\r\n" +
+                "Host: " + hostPort + "\r\n" +
+                "\r\n";
+        try (Socket socket = newSocket())
+        {
+            OutputStream output = socket.getOutputStream();
+            BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
+
+            output.write(request.getBytes(StandardCharsets.UTF_8));
+            output.flush();
+
+            // Expect 200 OK from the CONNECT request
+            SimpleHttpResponse response = readResponse(input);
+            Assert.assertEquals("200", response.getCode());
+
+            String body = "0123456789ABCDEF";
+            request = "" +
+                    "POST /echo HTTP/1.1\r\n" +
+                    "Host: " + hostPort + "\r\n" +
+                    "Content-Length: " + body.length() + "\r\n" +
+                    "\r\n" +
+                    body;
+            output.write(request.getBytes(StandardCharsets.UTF_8));
+            output.flush();
+
+            response = readResponse(input);
+            Assert.assertEquals("200", response.getCode());
+            Assert.assertEquals("POST /echo\r\n" + body, response.getBody());
+        }
+    }
+
+    @Test
+    public void testCONNECTAndGETPipelinedAndOutputShutdown() throws Exception
+    {
+        String hostPort = "localhost:" + serverConnector.getLocalPort();
+        String request = "" +
+                "CONNECT " + hostPort + " HTTP/1.1\r\n" +
+                "Host: " + hostPort + "\r\n" +
+                "\r\n" +
+                "GET /echo" + " HTTP/1.1\r\n" +
+                "Host: " + hostPort + "\r\n" +
+                "\r\n";
+        try (Socket socket = newSocket())
+        {
+            OutputStream output = socket.getOutputStream();
+            BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
+
+            output.write(request.getBytes(StandardCharsets.UTF_8));
+            output.flush();
+            socket.shutdownOutput();
+
+            // Expect 200 OK from the CONNECT request
+            SimpleHttpResponse response = readResponse(input);
+            Assert.assertEquals("200", response.getCode());
+
+            // The pipelined request must have gone up to the server as is
+            response = readResponse(input);
+            Assert.assertEquals("200", response.getCode());
+            Assert.assertEquals("GET /echo", response.getBody());
+        }
+    }
+
+    @Test
+    public void testCONNECTAndGETAndOutputShutdown() throws Exception
+    {
+        String hostPort = "localhost:" + serverConnector.getLocalPort();
+        String request = "" +
+                "CONNECT " + hostPort + " HTTP/1.1\r\n" +
+                "Host: " + hostPort + "\r\n" +
+                "\r\n";
+        try (Socket socket = newSocket())
+        {
+            OutputStream output = socket.getOutputStream();
+            BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
+
+            output.write(request.getBytes(StandardCharsets.UTF_8));
+            output.flush();
+
+            // Expect 200 OK from the CONNECT request
+            SimpleHttpResponse response = readResponse(input);
+            Assert.assertEquals("200", response.getCode());
+
+            request = "" +
+                    "GET /echo" + " HTTP/1.1\r\n" +
+                    "Host: " + hostPort + "\r\n" +
+                    "\r\n";
+            output.write(request.getBytes(StandardCharsets.UTF_8));
+            output.flush();
+            socket.shutdownOutput();
+
+            // The pipelined request must have gone up to the server as is
+            response = readResponse(input);
+            Assert.assertEquals("200", response.getCode());
+            Assert.assertEquals("GET /echo", response.getBody());
+        }
+    }
+
+    private static class ServerHandler extends AbstractHandler
+    {
+        public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException
+        {
+            request.setHandled(true);
+
+            String uri = httpRequest.getRequestURI();
+            switch (uri)
+            {
+                case "/echo":
+                {
+                    StringBuilder builder = new StringBuilder();
+                    builder.append(httpRequest.getMethod()).append(" ").append(uri);
+                    if (httpRequest.getQueryString() != null)
+                        builder.append("?").append(httpRequest.getQueryString());
+
+                    ByteArrayOutputStream baos = new ByteArrayOutputStream();
+                    InputStream input = httpRequest.getInputStream();
+                    int read;
+                    while ((read = input.read()) >= 0)
+                        baos.write(read);
+                    baos.close();
+
+                    ServletOutputStream output = httpResponse.getOutputStream();
+                    output.println(builder.toString());
+                    output.write(baos.toByteArray());
+                    break;
+                }
+                case "/close":
+                {
+                    request.getHttpChannel().getEndPoint().close();
+                    break;
+                }
+                default:
+                {
+                    throw new ServletException();
+                }
+            }
+        }
+    }
+}
diff --git a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/EchoHttpServlet.java b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/EchoHttpServlet.java
new file mode 100644
index 0000000..e7c19ff
--- /dev/null
+++ b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/EchoHttpServlet.java
@@ -0,0 +1,36 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.proxy;
+
+import java.io.IOException;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.util.IO;
+
+public class EchoHttpServlet extends HttpServlet
+{
+    @Override
+    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+    {
+        IO.copy(request.getInputStream(), response.getOutputStream());
+    }
+}
diff --git a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/EmptyHttpServlet.java b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/EmptyHttpServlet.java
new file mode 100644
index 0000000..e3abbaa
--- /dev/null
+++ b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/EmptyHttpServlet.java
@@ -0,0 +1,33 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.proxy;
+
+import java.io.IOException;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+public class EmptyHttpServlet extends HttpServlet
+{
+    @Override
+    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+    {
+    }
+}
diff --git a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServer.java b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServer.java
new file mode 100644
index 0000000..d417c64
--- /dev/null
+++ b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServer.java
@@ -0,0 +1,51 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.proxy;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+
+public class ProxyServer
+{
+    public static void main(String[] args) throws Exception
+    {
+        Server server = new Server();
+        ServerConnector connector = new ServerConnector(server);
+        connector.setPort(8888);
+        server.addConnector(connector);
+
+        // Setup proxy handler to handle CONNECT methods
+        ConnectHandler proxy = new ConnectHandler();
+//        proxy.setWhite(new String[]{"mail.google.com"});
+//        proxy.addWhitelistHost("www.google.com");
+        server.setHandler(proxy);
+
+        // Setup proxy servlet
+        ServletContextHandler context = new ServletContextHandler(proxy, "/", ServletContextHandler.SESSIONS);
+        ServletHolder proxyServlet = new ServletHolder(ProxyServlet.class);
+//        proxyServlet.setInitParameter("whiteList", "google.com, www.eclipse.org, localhost");
+//        proxyServlet.setInitParameter("blackList", "google.com/calendar/*, www.eclipse.org/committers/");
+        context.addServlet(proxyServlet, "/*");
+
+        server.start();
+    }
+
+}
diff --git a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletFailureTest.java b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletFailureTest.java
new file mode 100644
index 0000000..15c4e79
--- /dev/null
+++ b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletFailureTest.java
@@ -0,0 +1,419 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.proxy;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.Socket;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.HttpProxy;
+import org.eclipse.jetty.client.api.ContentProvider;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.client.util.BytesContentProvider;
+import org.eclipse.jetty.client.util.DeferredContentProvider;
+import org.eclipse.jetty.server.HttpConnectionFactory;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jetty.toolchain.test.TestTracker;
+import org.eclipse.jetty.toolchain.test.http.SimpleHttpParser;
+import org.eclipse.jetty.toolchain.test.http.SimpleHttpResponse;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.StdErrLog;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+ at RunWith(Parameterized.class)
+public class ProxyServletFailureTest
+{
+    private static final String PROXIED_HEADER = "X-Proxied";
+
+    @Parameterized.Parameters
+    public static Iterable<Object[]> data()
+    {
+        return Arrays.asList(new Object[][]{
+                {ProxyServlet.class},
+                {AsyncProxyServlet.class}
+        });
+    }
+
+    @Rule
+    public final TestTracker tracker = new TestTracker();
+    private HttpClient client;
+    private Server proxy;
+    private ServerConnector proxyConnector;
+    private ProxyServlet proxyServlet;
+    private Server server;
+    private ServerConnector serverConnector;
+
+    public ProxyServletFailureTest(Class<?> proxyServletClass) throws Exception
+    {
+        this.proxyServlet = (ProxyServlet)proxyServletClass.newInstance();
+    }
+
+    private void prepareProxy() throws Exception
+    {
+        prepareProxy(new HashMap<String, String>());
+    }
+
+    private void prepareProxy(Map<String, String> initParams) throws Exception
+    {
+        QueuedThreadPool executor = new QueuedThreadPool();
+        executor.setName("proxy");
+        proxy = new Server(executor);
+        proxyConnector = new ServerConnector(proxy);
+        proxy.addConnector(proxyConnector);
+        proxyConnector.getConnectionFactory(HttpConnectionFactory.class).getHttpConfiguration().setDelayDispatchUntilContent(false);
+
+        ServletContextHandler proxyCtx = new ServletContextHandler(proxy, "/", true, false);
+        ServletHolder proxyServletHolder = new ServletHolder(proxyServlet);
+        proxyServletHolder.setInitParameters(initParams);
+        proxyCtx.addServlet(proxyServletHolder, "/*");
+
+        proxy.start();
+
+        client = prepareClient();
+    }
+
+    private HttpClient prepareClient() throws Exception
+    {
+        HttpClient result = new HttpClient();
+        QueuedThreadPool executor = new QueuedThreadPool();
+        executor.setName("client");
+        result.setExecutor(executor);
+        result.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", proxyConnector.getLocalPort()));
+        result.start();
+        return result;
+    }
+
+    private void prepareServer(HttpServlet servlet) throws Exception
+    {
+        QueuedThreadPool executor = new QueuedThreadPool();
+        executor.setName("server");
+        server = new Server(executor);
+        serverConnector = new ServerConnector(server);
+        server.addConnector(serverConnector);
+
+        ServletContextHandler appCtx = new ServletContextHandler(server, "/", true, false);
+        ServletHolder appServletHolder = new ServletHolder(servlet);
+        appCtx.addServlet(appServletHolder, "/*");
+
+        server.start();
+    }
+
+    @After
+    public void disposeProxy() throws Exception
+    {
+        client.stop();
+        proxy.stop();
+    }
+
+    @After
+    public void disposeServer() throws Exception
+    {
+        server.stop();
+    }
+
+    @Test
+    public void testClientRequestStallsHeadersProxyIdlesTimeout() throws Exception
+    {
+        prepareProxy();
+        int idleTimeout = 2000;
+        proxyConnector.setIdleTimeout(idleTimeout);
+
+        prepareServer(new EchoHttpServlet());
+
+        try (Socket socket = new Socket("localhost", proxyConnector.getLocalPort()))
+        {
+            String serverHostPort = "localhost:" + serverConnector.getLocalPort();
+            String request = "" +
+                    "GET http://" + serverHostPort + " HTTP/1.1\r\n" +
+                    "Host: " + serverHostPort + "\r\n";
+            // Don't sent the \r\n that would signal the end of the headers.
+            OutputStream output = socket.getOutputStream();
+            output.write(request.getBytes("UTF-8"));
+            output.flush();
+
+            // Wait for idle timeout to fire.
+
+            socket.setSoTimeout(2 * idleTimeout);
+            InputStream input = socket.getInputStream();
+            Assert.assertEquals(-1, input.read());
+        }
+    }
+
+    @Test
+    public void testClientRequestDoesNotSendContentProxyIdlesTimeout() throws Exception
+    {
+        prepareProxy();
+        int idleTimeout = 2000;
+        proxyConnector.setIdleTimeout(idleTimeout);
+
+        prepareServer(new EchoHttpServlet());
+
+        try (Socket socket = new Socket("localhost", proxyConnector.getLocalPort()))
+        {
+            String serverHostPort = "localhost:" + serverConnector.getLocalPort();
+            String request = "" +
+                    "GET http://" + serverHostPort + " HTTP/1.1\r\n" +
+                    "Host: " + serverHostPort + "\r\n" +
+                    "Content-Length: 1\r\n" +
+                    "\r\n";
+            OutputStream output = socket.getOutputStream();
+            output.write(request.getBytes("UTF-8"));
+            output.flush();
+
+            // Do not send the promised content, wait to idle timeout.
+
+            socket.setSoTimeout(2 * idleTimeout);
+            SimpleHttpParser parser = new SimpleHttpParser();
+            BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));
+            SimpleHttpResponse response = parser.readResponse(reader);
+            Assert.assertTrue(Integer.parseInt(response.getCode()) >= 500);
+            String connectionHeader = response.getHeaders().get("connection");
+            Assert.assertNotNull(connectionHeader);
+            Assert.assertTrue(connectionHeader.contains("close"));
+            Assert.assertEquals(-1, reader.read());
+        }
+    }
+
+    @Test
+    public void testClientRequestStallsContentProxyIdlesTimeout() throws Exception
+    {
+        prepareProxy();
+        int idleTimeout = 2000;
+        proxyConnector.setIdleTimeout(idleTimeout);
+
+        prepareServer(new EchoHttpServlet());
+
+        try (Socket socket = new Socket("localhost", proxyConnector.getLocalPort()))
+        {
+            String serverHostPort = "localhost:" + serverConnector.getLocalPort();
+            String request = "" +
+                    "GET http://" + serverHostPort + " HTTP/1.1\r\n" +
+                    "Host: " + serverHostPort + "\r\n" +
+                    "Content-Length: 2\r\n" +
+                    "\r\n" +
+                    "Z";
+            OutputStream output = socket.getOutputStream();
+            output.write(request.getBytes("UTF-8"));
+            output.flush();
+
+            // Do not send all the promised content, wait to idle timeout.
+
+            socket.setSoTimeout(2 * idleTimeout);
+            SimpleHttpParser parser = new SimpleHttpParser();
+            BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));
+            SimpleHttpResponse response = parser.readResponse(reader);
+            Assert.assertTrue(Integer.parseInt(response.getCode()) >= 500);
+            String connectionHeader = response.getHeaders().get("connection");
+            Assert.assertNotNull(connectionHeader);
+            Assert.assertTrue(connectionHeader.contains("close"));
+            Assert.assertEquals(-1, reader.read());
+        }
+    }
+
+    @Test
+    public void testProxyRequestStallsContentServerIdlesTimeout() throws Exception
+    {
+        final byte[] content = new byte[]{'C', '0', 'F', 'F', 'E', 'E'};
+        if (proxyServlet instanceof AsyncProxyServlet)
+        {
+            proxyServlet = new AsyncProxyServlet()
+            {
+                @Override
+                protected ContentProvider proxyRequestContent(Request proxyRequest, HttpServletRequest request) throws IOException
+                {
+                    return new DeferredContentProvider()
+                    {
+                        @Override
+                        public boolean offer(ByteBuffer buffer, Callback callback)
+                        {
+                            // Ignore all content to trigger the test condition.
+                            return true;
+                        }
+                    };
+                }
+            };
+        }
+        else
+        {
+            proxyServlet = new ProxyServlet()
+            {
+                @Override
+                protected ContentProvider proxyRequestContent(Request proxyRequest, HttpServletRequest request) throws IOException
+                {
+                    return new BytesContentProvider(content)
+                    {
+                        @Override
+                        public long getLength()
+                        {
+                            // Increase the content length to trigger the test condition.
+                            return content.length + 1;
+                        }
+                    };
+                }
+            };
+        }
+
+        prepareProxy();
+        prepareServer(new EchoHttpServlet());
+        long idleTimeout = 1000;
+        serverConnector.setIdleTimeout(idleTimeout);
+
+        ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
+                .content(new BytesContentProvider(content))
+                .send();
+
+        Assert.assertEquals(500, response.getStatus());
+    }
+
+    @Test(expected = TimeoutException.class)
+    public void testClientRequestExpires() throws Exception
+    {
+        prepareProxy();
+        final long timeout = 1000;
+        proxyServlet.setTimeout(3 * timeout);
+        prepareServer(new HttpServlet()
+        {
+            @Override
+            protected void doGet(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException
+            {
+                try
+                {
+                    TimeUnit.MILLISECONDS.sleep(2 * timeout);
+                }
+                catch (InterruptedException x)
+                {
+                    throw new ServletException(x);
+                }
+            }
+        });
+
+        client.newRequest("localhost", serverConnector.getLocalPort())
+                .timeout(timeout, TimeUnit.MILLISECONDS)
+                .send();
+        Assert.fail();
+    }
+
+    @Test
+    public void testProxyRequestExpired() throws Exception
+    {
+        prepareProxy();
+        final long timeout = 1000;
+        proxyServlet.setTimeout(timeout);
+        prepareServer(new HttpServlet()
+        {
+            @Override
+            protected void doGet(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException
+            {
+                if (request.getHeader("Via") != null)
+                    response.addHeader(PROXIED_HEADER, "true");
+                try
+                {
+                    TimeUnit.MILLISECONDS.sleep(2 * timeout);
+                }
+                catch (InterruptedException x)
+                {
+                    throw new ServletException(x);
+                }
+            }
+        });
+
+        Response response = client.newRequest("localhost", serverConnector.getLocalPort())
+                .timeout(3 * timeout, TimeUnit.MILLISECONDS)
+                .send();
+        Assert.assertEquals(504, response.getStatus());
+        Assert.assertFalse(response.getHeaders().containsKey(PROXIED_HEADER));
+    }
+
+    @Test
+    public void testServerDown() throws Exception
+    {
+        prepareProxy();
+        prepareServer(new EmptyHttpServlet());
+
+        // Shutdown the server
+        int serverPort = serverConnector.getLocalPort();
+        server.stop();
+
+        ContentResponse response = client.newRequest("localhost", serverPort)
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        Assert.assertEquals(502, response.getStatus());
+    }
+
+    @Test
+    public void testServerException() throws Exception
+    {
+        ((StdErrLog)Log.getLogger(ServletHandler.class)).setHideStacks(true);
+        try
+        {
+            prepareProxy();
+            prepareServer(new HttpServlet()
+            {
+                @Override
+                protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+                {
+                    throw new ServletException("Expected Test Exception");
+                }
+            });
+
+            ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
+                    .timeout(5, TimeUnit.SECONDS)
+                    .send();
+
+            Assert.assertEquals(500, response.getStatus());
+        }
+        finally
+        {
+            ((StdErrLog)Log.getLogger(ServletHandler.class)).setHideStacks(false);
+        }
+    }
+
+    private interface Function<T, R>
+    {
+        public R apply(T arg);
+    }
+}
diff --git a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletTest.java b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletTest.java
new file mode 100644
index 0000000..f4f36d6
--- /dev/null
+++ b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletTest.java
@@ -0,0 +1,1234 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.proxy;
+
+import java.io.ByteArrayOutputStream;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InterruptedIOException;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.net.ConnectException;
+import java.net.HttpCookie;
+import java.nio.ByteBuffer;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.zip.GZIPOutputStream;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.AsyncEvent;
+import javax.servlet.AsyncListener;
+import javax.servlet.DispatcherType;
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.HttpContentResponse;
+import org.eclipse.jetty.client.HttpProxy;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.client.api.Result;
+import org.eclipse.jetty.client.http.HttpDestinationOverHTTP;
+import org.eclipse.jetty.client.util.BufferingResponseListener;
+import org.eclipse.jetty.client.util.BytesContentProvider;
+import org.eclipse.jetty.client.util.InputStreamResponseListener;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.HttpConnectionFactory;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.servlet.FilterHolder;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.eclipse.jetty.toolchain.test.TestTracker;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.hamcrest.Matchers;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+ at RunWith(Parameterized.class)
+public class ProxyServletTest
+{
+    private static final String PROXIED_HEADER = "X-Proxied";
+
+    @Parameterized.Parameters(name = "{0}")
+    public static Iterable<Object[]> data()
+    {
+        return Arrays.asList(new Object[][]{
+                {ProxyServlet.class},
+                {AsyncProxyServlet.class},
+                {AsyncMiddleManServlet.class}
+        });
+    }
+
+    @Rule
+    public final TestTracker tracker = new TestTracker();
+    private HttpClient client;
+    private Server proxy;
+    private ServerConnector proxyConnector;
+    private ServletContextHandler proxyContext;
+    private AbstractProxyServlet proxyServlet;
+    private Server server;
+    private ServerConnector serverConnector;
+
+    public ProxyServletTest(Class<?> proxyServletClass) throws Exception
+    {
+        this.proxyServlet = (AbstractProxyServlet)proxyServletClass.newInstance();
+    }
+
+    private void startServer(HttpServlet servlet) throws Exception
+    {
+        QueuedThreadPool serverPool = new QueuedThreadPool();
+        serverPool.setName("server");
+        server = new Server(serverPool);
+        serverConnector = new ServerConnector(server);
+        server.addConnector(serverConnector);
+
+        ServletContextHandler appCtx = new ServletContextHandler(server, "/", true, false);
+        ServletHolder appServletHolder = new ServletHolder(servlet);
+        appCtx.addServlet(appServletHolder, "/*");
+
+        server.start();
+    }
+
+    private void startProxy() throws Exception
+    {
+        startProxy(new HashMap<String, String>());
+    }
+
+    private void startProxy(Map<String, String> initParams) throws Exception
+    {
+        QueuedThreadPool proxyPool = new QueuedThreadPool();
+        proxyPool.setName("proxy");
+        proxy = new Server(proxyPool);
+
+        HttpConfiguration configuration = new HttpConfiguration();
+        configuration.setSendDateHeader(false);
+        configuration.setSendServerVersion(false);
+        String value = initParams.get("outputBufferSize");
+        if (value != null)
+            configuration.setOutputBufferSize(Integer.valueOf(value));
+        proxyConnector = new ServerConnector(proxy, new HttpConnectionFactory(configuration));
+        proxy.addConnector(proxyConnector);
+
+        proxyContext = new ServletContextHandler(proxy, "/", true, false);
+        ServletHolder proxyServletHolder = new ServletHolder(proxyServlet);
+        proxyServletHolder.setInitParameters(initParams);
+        proxyContext.addServlet(proxyServletHolder, "/*");
+
+        proxy.start();
+    }
+
+    private void startClient() throws Exception
+    {
+        client = prepareClient();
+    }
+
+    private HttpClient prepareClient() throws Exception
+    {
+        QueuedThreadPool clientPool = new QueuedThreadPool();
+        clientPool.setName("client");
+        HttpClient result = new HttpClient();
+        result.setExecutor(clientPool);
+        result.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", proxyConnector.getLocalPort()));
+        result.start();
+        return result;
+    }
+
+    @After
+    public void dispose() throws Exception
+    {
+        client.stop();
+        proxy.stop();
+        server.stop();
+    }
+
+    @Test
+    public void testProxyDown() throws Exception
+    {
+        startServer(new EmptyHttpServlet());
+        startProxy();
+        startClient();
+        // Shutdown the proxy
+        proxy.stop();
+
+        try
+        {
+            client.newRequest("localhost", serverConnector.getLocalPort())
+                    .timeout(5, TimeUnit.SECONDS)
+                    .send();
+            Assert.fail();
+        }
+        catch (ExecutionException x)
+        {
+            Assert.assertThat(x.getCause(), Matchers.instanceOf(ConnectException.class));
+        }
+    }
+
+    @Test
+    public void testProxyWithoutContent() throws Exception
+    {
+        startServer(new HttpServlet()
+        {
+            @Override
+            protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+            {
+                if (req.getHeader("Via") != null)
+                    resp.addHeader(PROXIED_HEADER, "true");
+            }
+        });
+        startProxy();
+        startClient();
+
+        ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        Assert.assertEquals("OK", response.getReason());
+        Assert.assertEquals(200, response.getStatus());
+        Assert.assertTrue(response.getHeaders().containsKey(PROXIED_HEADER));
+    }
+
+    @Test
+    public void testProxyWithResponseContent() throws Exception
+    {
+        final byte[] content = new byte[1024];
+        new Random().nextBytes(content);
+        startServer(new HttpServlet()
+        {
+            @Override
+            protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+            {
+                if (req.getHeader("Via") != null)
+                    resp.addHeader(PROXIED_HEADER, "true");
+                resp.getOutputStream().write(content);
+            }
+        });
+        startProxy();
+        startClient();
+
+        ContentResponse[] responses = new ContentResponse[10];
+        for (int i = 0; i < 10; ++i)
+        {
+            // Request is for the target server
+            responses[i] = client.newRequest("localhost", serverConnector.getLocalPort())
+                    .timeout(5, TimeUnit.SECONDS)
+                    .send();
+        }
+
+        for (int i = 0; i < 10; ++i)
+        {
+            Assert.assertEquals(200, responses[i].getStatus());
+            Assert.assertTrue(responses[i].getHeaders().containsKey(PROXIED_HEADER));
+            Assert.assertArrayEquals(content, responses[i].getContent());
+        }
+    }
+
+    @Test
+    public void testProxyWithRequestContentAndResponseContent() throws Exception
+    {
+        startServer(new HttpServlet()
+        {
+            @Override
+            protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+            {
+                if (req.getHeader("Via") != null)
+                    resp.addHeader(PROXIED_HEADER, "true");
+                IO.copy(req.getInputStream(), resp.getOutputStream());
+            }
+        });
+        startProxy();
+        startClient();
+
+        byte[] content = new byte[1024];
+        new Random().nextBytes(content);
+        ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
+                .method(HttpMethod.POST)
+                .content(new BytesContentProvider(content))
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        Assert.assertEquals(200, response.getStatus());
+        Assert.assertTrue(response.getHeaders().containsKey(PROXIED_HEADER));
+        Assert.assertArrayEquals(content, response.getContent());
+    }
+
+    @Test
+    public void testProxyWithBigRequestContentIgnored() throws Exception
+    {
+        startServer(new HttpServlet()
+        {
+            @Override
+            protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+            {
+                try
+                {
+                    // Give some time to the proxy to
+                    // upload the content to the server.
+                    Thread.sleep(1000);
+
+                    if (req.getHeader("Via") != null)
+                        resp.addHeader(PROXIED_HEADER, "true");
+                }
+                catch (InterruptedException x)
+                {
+                    throw new InterruptedIOException();
+                }
+            }
+        });
+        startProxy();
+        startClient();
+
+        byte[] content = new byte[128 * 1024];
+        ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
+                .method(HttpMethod.POST)
+                .content(new BytesContentProvider(content))
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        Assert.assertEquals(200, response.getStatus());
+        Assert.assertTrue(response.getHeaders().containsKey(PROXIED_HEADER));
+    }
+
+    @Test
+    public void testProxyWithBigRequestContentConsumed() throws Exception
+    {
+        final byte[] content = new byte[128 * 1024];
+        new Random().nextBytes(content);
+        startServer(new HttpServlet()
+        {
+            @Override
+            protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+            {
+                if (req.getHeader("Via") != null)
+                    resp.addHeader(PROXIED_HEADER, "true");
+                InputStream input = req.getInputStream();
+                int index = 0;
+                while (true)
+                {
+                    int value = input.read();
+                    if (value < 0)
+                        break;
+                    Assert.assertEquals("Content mismatch at index=" + index, content[index] & 0xFF, value);
+                    ++index;
+                }
+            }
+        });
+        startProxy();
+        startClient();
+
+        ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
+                .method(HttpMethod.POST)
+                .content(new BytesContentProvider(content))
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        Assert.assertEquals(200, response.getStatus());
+        Assert.assertTrue(response.getHeaders().containsKey(PROXIED_HEADER));
+    }
+
+    @Test
+    public void testProxyWithBigResponseContentWithSlowReader() throws Exception
+    {
+        // Create a 6 MiB file
+        final int length = 6 * 1024;
+        Path targetTestsDir = MavenTestingUtils.getTargetTestingDir().toPath();
+        Files.createDirectories(targetTestsDir);
+        final Path temp = Files.createTempFile(targetTestsDir, "test_", null);
+        byte[] kb = new byte[1024];
+        new Random().nextBytes(kb);
+        try (OutputStream output = Files.newOutputStream(temp, StandardOpenOption.CREATE))
+        {
+            for (int i = 0; i < length; ++i)
+                output.write(kb);
+        }
+        startServer(new HttpServlet()
+        {
+            @Override
+            protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+            {
+                try (InputStream input = Files.newInputStream(temp))
+                {
+                    IO.copy(input, response.getOutputStream());
+                }
+            }
+        });
+        startProxy();
+        startClient();
+
+        Request request = client.newRequest("localhost", serverConnector.getLocalPort()).path("/proxy/test");
+        final CountDownLatch latch = new CountDownLatch(1);
+        request.send(new BufferingResponseListener(2 * length * 1024)
+        {
+            @Override
+            public void onContent(Response response, ByteBuffer content)
+            {
+                try
+                {
+                    // Slow down the reader
+                    TimeUnit.MILLISECONDS.sleep(5);
+                    super.onContent(response, content);
+                }
+                catch (InterruptedException x)
+                {
+                    response.abort(x);
+                }
+            }
+
+            @Override
+            public void onComplete(Result result)
+            {
+                Assert.assertFalse(result.isFailed());
+                Assert.assertEquals(200, result.getResponse().getStatus());
+                Assert.assertEquals(length * 1024, getContent().length);
+                latch.countDown();
+            }
+        });
+        Assert.assertTrue(latch.await(30, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testProxyWithQueryString() throws Exception
+    {
+        startServer(new HttpServlet()
+        {
+            @Override
+            protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+            {
+                resp.getOutputStream().print(req.getQueryString());
+            }
+        });
+        startProxy();
+        startClient();
+
+        String query = "a=1&b=%E2%82%AC";
+        ContentResponse response = client.newRequest("http://localhost:" + serverConnector.getLocalPort() + "/?" + query)
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+        Assert.assertEquals(200, response.getStatus());
+        Assert.assertEquals(query, response.getContentAsString());
+    }
+
+    @Test
+    public void testProxyLongPoll() throws Exception
+    {
+        final long timeout = 1000;
+        startServer(new HttpServlet()
+        {
+            @Override
+            protected void doGet(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException
+            {
+                if (!request.isAsyncStarted())
+                {
+                    final AsyncContext asyncContext = request.startAsync();
+                    asyncContext.setTimeout(timeout);
+                    asyncContext.addListener(new AsyncListener()
+                    {
+                        @Override
+                        public void onComplete(AsyncEvent event) throws IOException
+                        {
+                        }
+
+                        @Override
+                        public void onTimeout(AsyncEvent event) throws IOException
+                        {
+                            if (request.getHeader("Via") != null)
+                                response.addHeader(PROXIED_HEADER, "true");
+                            asyncContext.complete();
+                        }
+
+                        @Override
+                        public void onError(AsyncEvent event) throws IOException
+                        {
+                        }
+
+                        @Override
+                        public void onStartAsync(AsyncEvent event) throws IOException
+                        {
+                        }
+                    });
+                }
+            }
+        });
+        startProxy();
+        startClient();
+
+        Response response = client.newRequest("localhost", serverConnector.getLocalPort())
+                .timeout(2 * timeout, TimeUnit.MILLISECONDS)
+                .send();
+        Assert.assertEquals(200, response.getStatus());
+        Assert.assertTrue(response.getHeaders().containsKey(PROXIED_HEADER));
+    }
+
+    @Test
+    public void testProxyXForwardedHostHeaderIsPresent() throws Exception
+    {
+        startServer(new HttpServlet()
+        {
+            @Override
+            protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+            {
+                PrintWriter writer = resp.getWriter();
+                writer.write(req.getHeader("X-Forwarded-Host"));
+                writer.flush();
+            }
+        });
+        startProxy();
+        startClient();
+
+        ContentResponse response = client.GET("http://localhost:" + serverConnector.getLocalPort());
+        Assert.assertThat("Response expected to contain content of X-Forwarded-Host Header from the request",
+                response.getContentAsString(),
+                Matchers.equalTo("localhost:" + serverConnector.getLocalPort()));
+    }
+
+    @Test
+    public void testProxyWhiteList() throws Exception
+    {
+        startServer(new EmptyHttpServlet());
+        startProxy();
+        startClient();
+        int port = serverConnector.getLocalPort();
+        proxyServlet.getWhiteListHosts().add("127.0.0.1:" + port);
+
+        // Try with the wrong host
+        ContentResponse response = client.newRequest("localhost", port)
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+        Assert.assertEquals(403, response.getStatus());
+
+        // Try again with the right host
+        response = client.newRequest("127.0.0.1", port)
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+        Assert.assertEquals(200, response.getStatus());
+    }
+
+    @Test
+    public void testProxyBlackList() throws Exception
+    {
+        startServer(new EmptyHttpServlet());
+        startProxy();
+        startClient();
+        int port = serverConnector.getLocalPort();
+        proxyServlet.getBlackListHosts().add("localhost:" + port);
+
+        // Try with the wrong host
+        ContentResponse response = client.newRequest("localhost", port)
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+        Assert.assertEquals(403, response.getStatus());
+
+        // Try again with the right host
+        response = client.newRequest("127.0.0.1", port)
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+        Assert.assertEquals(200, response.getStatus());
+    }
+
+    @Test
+    public void testClientExcludedHosts() throws Exception
+    {
+        startServer(new HttpServlet()
+        {
+            @Override
+            protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+            {
+                if (req.getHeader("Via") != null)
+                    resp.addHeader(PROXIED_HEADER, "true");
+            }
+        });
+        startProxy();
+        startClient();
+        int port = serverConnector.getLocalPort();
+        client.getProxyConfiguration().getProxies().get(0).getExcludedAddresses().add("127.0.0.1:" + port);
+
+        // Try with a proxied host
+        ContentResponse response = client.newRequest("localhost", port)
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+        Assert.assertEquals(200, response.getStatus());
+        Assert.assertTrue(response.getHeaders().containsKey(PROXIED_HEADER));
+
+        // Try again with an excluded host
+        response = client.newRequest("127.0.0.1", port)
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+        Assert.assertEquals(200, response.getStatus());
+        Assert.assertFalse(response.getHeaders().containsKey(PROXIED_HEADER));
+    }
+
+    @Test
+    public void testTransparentProxy() throws Exception
+    {
+        testTransparentProxyWithPrefix("/proxy");
+    }
+
+    @Test
+    public void testTransparentProxyWithRootContext() throws Exception
+    {
+        testTransparentProxyWithPrefix("/");
+    }
+
+    private void testTransparentProxyWithPrefix(String prefix) throws Exception
+    {
+        final String target = "/test";
+        startServer(new HttpServlet()
+        {
+            @Override
+            protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+            {
+                if (req.getHeader("Via") != null)
+                    resp.addHeader(PROXIED_HEADER, "true");
+                resp.setStatus(target.equals(req.getRequestURI()) ? 200 : 404);
+            }
+        });
+        String proxyTo = "http://localhost:" + serverConnector.getLocalPort();
+        proxyServlet = new ProxyServlet.Transparent();
+        Map<String, String> params = new HashMap<>();
+        params.put("proxyTo", proxyTo);
+        params.put("prefix", prefix);
+        startProxy(params);
+        startClient();
+
+        // Make the request to the proxy, it should transparently forward to the server
+        ContentResponse response = client.newRequest("localhost", proxyConnector.getLocalPort())
+                .path((prefix + target).replaceAll("//", "/"))
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+        Assert.assertEquals(200, response.getStatus());
+        Assert.assertTrue(response.getHeaders().containsKey(PROXIED_HEADER));
+    }
+
+    @Test
+    public void testTransparentProxyWithQuery() throws Exception
+    {
+        testTransparentProxyWithQuery("/foo", "/proxy", "/test");
+    }
+
+    @Test
+    public void testTransparentProxyEmptyContextWithQuery() throws Exception
+    {
+        testTransparentProxyWithQuery("", "/proxy", "/test");
+    }
+
+    @Test
+    public void testTransparentProxyEmptyTargetWithQuery() throws Exception
+    {
+        testTransparentProxyWithQuery("/bar", "/proxy", "");
+    }
+
+    @Test
+    public void testTransparentProxyEmptyContextEmptyTargetWithQuery() throws Exception
+    {
+        testTransparentProxyWithQuery("", "/proxy", "");
+    }
+
+    private void testTransparentProxyWithQuery(final String proxyToContext, String prefix, final String target) throws Exception
+    {
+        final String query = "a=1&b=2";
+        startServer(new HttpServlet()
+        {
+            @Override
+            protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+            {
+                if (req.getHeader("Via") != null)
+                    resp.addHeader(PROXIED_HEADER, "true");
+
+                String expectedURI = proxyToContext + target;
+                if (expectedURI.isEmpty())
+                    expectedURI = "/";
+                if (expectedURI.equals(req.getRequestURI()))
+                {
+                    if (query.equals(req.getQueryString()))
+                    {
+                        resp.setStatus(200);
+                        return;
+                    }
+                }
+                resp.setStatus(404);
+            }
+        });
+        String proxyTo = "http://localhost:" + serverConnector.getLocalPort() + proxyToContext;
+        proxyServlet = new ProxyServlet.Transparent();
+        Map<String, String> params = new HashMap<>();
+        params.put("proxyTo", proxyTo);
+        params.put("prefix", prefix);
+        startProxy(params);
+        startClient();
+
+        // Make the request to the proxy, it should transparently forward to the server
+        ContentResponse response = client.newRequest("localhost", proxyConnector.getLocalPort())
+                .path(prefix + target + "?" + query)
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+        Assert.assertEquals(200, response.getStatus());
+        Assert.assertTrue(response.getHeaders().containsKey(PROXIED_HEADER));
+    }
+
+    @Test
+    public void testTransparentProxyWithQueryWithSpaces() throws Exception
+    {
+        final String target = "/test";
+        final String query = "a=1&b=2&c=1234%205678&d=hello+world";
+        startServer(new HttpServlet()
+        {
+            @Override
+            protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+            {
+                if (req.getHeader("Via") != null)
+                    resp.addHeader(PROXIED_HEADER, "true");
+
+                if (target.equals(req.getRequestURI()))
+                {
+                    if (query.equals(req.getQueryString()))
+                    {
+                        resp.setStatus(200);
+                        return;
+                    }
+                }
+                resp.setStatus(404);
+            }
+        });
+        String proxyTo = "http://localhost:" + serverConnector.getLocalPort();
+        String prefix = "/proxy";
+        proxyServlet = new ProxyServlet.Transparent();
+        Map<String, String> params = new HashMap<>();
+        params.put("proxyTo", proxyTo);
+        params.put("prefix", prefix);
+        startProxy(params);
+        startClient();
+
+        // Make the request to the proxy, it should transparently forward to the server
+        ContentResponse response = client.newRequest("localhost", proxyConnector.getLocalPort())
+                .path(prefix + target + "?" + query)
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+        Assert.assertEquals(200, response.getStatus());
+        Assert.assertTrue(response.getHeaders().containsKey(PROXIED_HEADER));
+    }
+
+    @Test
+    public void testTransparentProxyWithoutPrefix() throws Exception
+    {
+        final String target = "/test";
+        startServer(new HttpServlet()
+        {
+            @Override
+            protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+            {
+                if (req.getHeader("Via") != null)
+                    resp.addHeader(PROXIED_HEADER, "true");
+                resp.setStatus(target.equals(req.getRequestURI()) ? 200 : 404);
+            }
+        });
+        final String proxyTo = "http://localhost:" + serverConnector.getLocalPort();
+        proxyServlet = new ProxyServlet.Transparent();
+        Map<String, String> initParams = new HashMap<>();
+        initParams.put("proxyTo", proxyTo);
+        startProxy(initParams);
+        startClient();
+
+        // Make the request to the proxy, it should transparently forward to the server
+        ContentResponse response = client.newRequest("localhost", proxyConnector.getLocalPort())
+                .path(target)
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+        Assert.assertEquals(200, response.getStatus());
+        Assert.assertTrue(response.getHeaders().containsKey(PROXIED_HEADER));
+    }
+
+    @Test
+    public void testCachingProxy() throws Exception
+    {
+        final byte[] content = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF};
+        startServer(new HttpServlet()
+        {
+            @Override
+            protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+            {
+                if (req.getHeader("Via") != null)
+                    resp.addHeader(PROXIED_HEADER, "true");
+                resp.getOutputStream().write(content);
+            }
+        });
+        // Don't do this at home: this example is not concurrent, not complete,
+        // it is only used for this test and to verify that ProxyServlet can be
+        // subclassed enough to write your own caching servlet
+        final String cacheHeader = "X-Cached";
+        proxyServlet = new ProxyServlet()
+        {
+            private Map<String, ContentResponse> cache = new HashMap<>();
+            private Map<String, ByteArrayOutputStream> temp = new HashMap<>();
+
+            @Override
+            protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+            {
+                ContentResponse cachedResponse = cache.get(request.getRequestURI());
+                if (cachedResponse != null)
+                {
+                    response.setStatus(cachedResponse.getStatus());
+                    // Should copy headers too, but keep it simple
+                    response.addHeader(cacheHeader, "true");
+                    response.getOutputStream().write(cachedResponse.getContent());
+                }
+                else
+                {
+                    super.service(request, response);
+                }
+            }
+
+            @Override
+            protected void onResponseContent(HttpServletRequest request, HttpServletResponse response, Response proxyResponse, byte[] buffer, int offset, int length, Callback callback)
+            {
+                // Accumulate the response content
+                ByteArrayOutputStream baos = temp.get(request.getRequestURI());
+                if (baos == null)
+                {
+                    baos = new ByteArrayOutputStream();
+                    temp.put(request.getRequestURI(), baos);
+                }
+                baos.write(buffer, offset, length);
+                super.onResponseContent(request, response, proxyResponse, buffer, offset, length, callback);
+            }
+
+            @Override
+            protected void onProxyResponseSuccess(HttpServletRequest request, HttpServletResponse response, Response proxyResponse)
+            {
+                byte[] content = temp.remove(request.getRequestURI()).toByteArray();
+                ContentResponse cached = new HttpContentResponse(proxyResponse, content, null, null);
+                cache.put(request.getRequestURI(), cached);
+                super.onProxyResponseSuccess(request, response, proxyResponse);
+            }
+        };
+        startProxy();
+        startClient();
+
+        // First request
+        ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+        Assert.assertEquals(200, response.getStatus());
+        Assert.assertTrue(response.getHeaders().containsKey(PROXIED_HEADER));
+        Assert.assertArrayEquals(content, response.getContent());
+
+        // Second request should be cached
+        response = client.newRequest("localhost", serverConnector.getLocalPort())
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+        Assert.assertEquals(200, response.getStatus());
+        Assert.assertTrue(response.getHeaders().containsKey(cacheHeader));
+        Assert.assertArrayEquals(content, response.getContent());
+    }
+
+    @Test
+    public void testRedirectsAreProxied() throws Exception
+    {
+        startServer(new HttpServlet()
+        {
+            @Override
+            protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+            {
+                if (req.getHeader("Via") != null)
+                    resp.addHeader(PROXIED_HEADER, "true");
+                resp.sendRedirect("/");
+            }
+        });
+        startProxy();
+        startClient();
+
+        client.setFollowRedirects(false);
+
+        ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+        Assert.assertEquals(302, response.getStatus());
+        Assert.assertTrue(response.getHeaders().containsKey(PROXIED_HEADER));
+    }
+
+    @Test
+    public void testGZIPContentIsProxied() throws Exception
+    {
+        final byte[] content = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+        startServer(new HttpServlet()
+        {
+            @Override
+            protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+            {
+                if (req.getHeader("Via") != null)
+                    resp.addHeader(PROXIED_HEADER, "true");
+
+                resp.addHeader("Content-Encoding", "gzip");
+                GZIPOutputStream gzipOutputStream = new GZIPOutputStream(resp.getOutputStream());
+                gzipOutputStream.write(content);
+                gzipOutputStream.close();
+            }
+        });
+        startProxy();
+        startClient();
+
+        ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+        Assert.assertEquals(200, response.getStatus());
+        Assert.assertTrue(response.getHeaders().containsKey(PROXIED_HEADER));
+        Assert.assertArrayEquals(content, response.getContent());
+    }
+
+    @Test(expected = TimeoutException.class)
+    public void testWrongContentLength() throws Exception
+    {
+        startServer(new HttpServlet()
+        {
+            @Override
+            protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+            {
+                byte[] message = "tooshort".getBytes("ascii");
+                resp.setContentType("text/plain;charset=ascii");
+                resp.setHeader("Content-Length", Long.toString(message.length + 1));
+                resp.getOutputStream().write(message);
+            }
+        });
+        startProxy();
+        startClient();
+
+        client.newRequest("localhost", serverConnector.getLocalPort())
+                .timeout(1, TimeUnit.SECONDS)
+                .send();
+
+        Assert.fail();
+    }
+
+    @Test
+    public void testCookiesFromDifferentClientsAreNotMixed() throws Exception
+    {
+        final String name = "biscuit";
+        startServer(new HttpServlet()
+        {
+            @Override
+            protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+            {
+                if (req.getHeader("Via") != null)
+                    resp.addHeader(PROXIED_HEADER, "true");
+
+                String value = req.getHeader(name);
+                if (value != null)
+                {
+                    Cookie cookie = new Cookie(name, value);
+                    cookie.setMaxAge(3600);
+                    resp.addCookie(cookie);
+                }
+                else
+                {
+                    Cookie[] cookies = req.getCookies();
+                    Assert.assertEquals(1, cookies.length);
+                }
+            }
+        });
+        startProxy();
+        startClient();
+
+        String value1 = "1";
+        ContentResponse response1 = client.newRequest("localhost", serverConnector.getLocalPort())
+                .header(name, value1)
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+        Assert.assertEquals(200, response1.getStatus());
+        Assert.assertTrue(response1.getHeaders().containsKey(PROXIED_HEADER));
+        List<HttpCookie> cookies = client.getCookieStore().getCookies();
+        Assert.assertEquals(1, cookies.size());
+        Assert.assertEquals(name, cookies.get(0).getName());
+        Assert.assertEquals(value1, cookies.get(0).getValue());
+
+        HttpClient client2 = prepareClient();
+        try
+        {
+            String value2 = "2";
+            ContentResponse response2 = client2.newRequest("localhost", serverConnector.getLocalPort())
+                    .header(name, value2)
+                    .timeout(5, TimeUnit.SECONDS)
+                    .send();
+            Assert.assertEquals(200, response2.getStatus());
+            Assert.assertTrue(response2.getHeaders().containsKey(PROXIED_HEADER));
+            cookies = client2.getCookieStore().getCookies();
+            Assert.assertEquals(1, cookies.size());
+            Assert.assertEquals(name, cookies.get(0).getName());
+            Assert.assertEquals(value2, cookies.get(0).getValue());
+
+            // Make a third request to be sure the proxy does not mix cookies
+            ContentResponse response3 = client.newRequest("localhost", serverConnector.getLocalPort())
+                    .timeout(5, TimeUnit.SECONDS)
+                    .send();
+            Assert.assertEquals(200, response3.getStatus());
+            Assert.assertTrue(response3.getHeaders().containsKey(PROXIED_HEADER));
+        }
+        finally
+        {
+            client2.stop();
+        }
+    }
+
+    @Test
+    public void testProxyRequestFailureInTheMiddleOfProxyingSmallContent() throws Exception
+    {
+        final CountDownLatch chunk1Latch = new CountDownLatch(1);
+        final int chunk1 = 'q';
+        final int chunk2 = 'w';
+        startServer(new HttpServlet()
+        {
+            @Override
+            protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+            {
+                ServletOutputStream output = response.getOutputStream();
+                output.write(chunk1);
+                response.flushBuffer();
+
+                // Wait for the client to receive this chunk.
+                await(chunk1Latch, 5000);
+
+                // Send second chunk, must not be received by proxy.
+                output.write(chunk2);
+            }
+
+            private boolean await(CountDownLatch latch, long ms) throws IOException
+            {
+                try
+                {
+                    return latch.await(ms, TimeUnit.MILLISECONDS);
+                }
+                catch (InterruptedException x)
+                {
+                    throw new InterruptedIOException();
+                }
+            }
+        });
+        final long proxyTimeout = 1000;
+        Map<String, String> proxyParams = new HashMap<>();
+        proxyParams.put("timeout", String.valueOf(proxyTimeout));
+        startProxy(proxyParams);
+        startClient();
+
+        InputStreamResponseListener listener = new InputStreamResponseListener();
+        int port = serverConnector.getLocalPort();
+        client.newRequest("localhost", port).send(listener);
+
+        // Make the proxy request fail; given the small content, the
+        // proxy-to-client response is not committed yet so it will be reset.
+        TimeUnit.MILLISECONDS.sleep(2 * proxyTimeout);
+
+        Response response = listener.get(5, TimeUnit.SECONDS);
+        Assert.assertEquals(504, response.getStatus());
+
+        // Make sure there is no content, as the proxy-to-client response has been reset.
+        InputStream input = listener.getInputStream();
+        Assert.assertEquals(-1, input.read());
+
+        chunk1Latch.countDown();
+
+        // Result succeeds because a 504 is a valid HTTP response.
+        Result result = listener.await(5, TimeUnit.SECONDS);
+        Assert.assertTrue(result.isSucceeded());
+
+        // Make sure the proxy does not receive chunk2.
+        Assert.assertEquals(-1, input.read());
+
+        HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination("http", "localhost", port);
+        Assert.assertEquals(0, destination.getConnectionPool().getIdleConnections().size());
+    }
+
+    @Test
+    public void testProxyRequestFailureInTheMiddleOfProxyingBigContent() throws Exception
+    {
+        int outputBufferSize = 1024;
+        final CountDownLatch chunk1Latch = new CountDownLatch(1);
+        final byte[] chunk1 = new byte[outputBufferSize];
+        new Random().nextBytes(chunk1);
+        final int chunk2 = 'w';
+        startServer(new HttpServlet()
+        {
+            @Override
+            protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+            {
+                ServletOutputStream output = response.getOutputStream();
+                output.write(chunk1);
+                response.flushBuffer();
+
+                // Wait for the client to receive this chunk.
+                await(chunk1Latch, 5000);
+
+                // Send second chunk, must not be received by proxy.
+                output.write(chunk2);
+            }
+
+            private boolean await(CountDownLatch latch, long ms) throws IOException
+            {
+                try
+                {
+                    return latch.await(ms, TimeUnit.MILLISECONDS);
+                }
+                catch (InterruptedException x)
+                {
+                    throw new InterruptedIOException();
+                }
+            }
+        });
+        final long proxyTimeout = 1000;
+        Map<String, String> proxyParams = new HashMap<>();
+        proxyParams.put("timeout", String.valueOf(proxyTimeout));
+        proxyParams.put("outputBufferSize", String.valueOf(outputBufferSize));
+        startProxy(proxyParams);
+        startClient();
+
+        InputStreamResponseListener listener = new InputStreamResponseListener();
+        int port = serverConnector.getLocalPort();
+        client.newRequest("localhost", port).send(listener);
+
+        Response response = listener.get(5, TimeUnit.SECONDS);
+        Assert.assertEquals(200, response.getStatus());
+
+        InputStream input = listener.getInputStream();
+        for (int i = 0; i < chunk1.length; ++i)
+            Assert.assertEquals(chunk1[i] & 0xFF, input.read());
+
+        TimeUnit.MILLISECONDS.sleep(2 * proxyTimeout);
+
+        chunk1Latch.countDown();
+
+        try
+        {
+            // Make sure the proxy does not receive chunk2.
+            input.read();
+            Assert.fail();
+        }
+        catch (EOFException x)
+        {
+            // Expected
+        }
+
+        HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination("http", "localhost", port);
+        Assert.assertEquals(0, destination.getConnectionPool().getIdleConnections().size());
+    }
+
+    @Test
+    public void testResponseHeadersAreNotRemoved() throws Exception
+    {
+        startServer(new EmptyHttpServlet());
+        startProxy();
+        proxyContext.stop();
+        final String headerName = "X-Test";
+        final String headerValue = "test-value";
+        proxyContext.addFilter(new FilterHolder(new Filter()
+        {
+            @Override
+            public void init(FilterConfig filterConfig) throws ServletException
+            {
+            }
+
+            @Override
+            public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
+            {
+                ((HttpServletResponse)response).addHeader(headerName, headerValue);
+                chain.doFilter(request, response);
+            }
+
+            @Override
+            public void destroy()
+            {
+            }
+        }), "/*", EnumSet.of(DispatcherType.REQUEST));
+        proxyContext.start();
+        startClient();
+
+        ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        Assert.assertEquals(200, response.getStatus());
+        Assert.assertEquals(headerValue, response.getHeaders().get(headerName));
+    }
+
+    @Test
+    public void testHeadersListedByConnectionHeaderAreRemoved() throws Exception
+    {
+        final Map<String, String> hopHeaders = new LinkedHashMap<>();
+        hopHeaders.put(HttpHeader.TE.asString(), "gzip");
+        hopHeaders.put(HttpHeader.CONNECTION.asString(), "Keep-Alive, Foo, Bar");
+        hopHeaders.put("Foo", "abc");
+        hopHeaders.put("Foo", "def");
+        hopHeaders.put(HttpHeader.KEEP_ALIVE.asString(), "timeout=30");
+        startServer(new HttpServlet()
+        {
+            @Override
+            protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+            {
+                List<String> names = Collections.list(request.getHeaderNames());
+                for (String name : names)
+                {
+                    if (hopHeaders.containsKey(name))
+                        throw new IOException("Hop header must not be proxied: " + name);
+                }
+            }
+        });
+        startProxy();
+        startClient();
+
+        Request request = client.newRequest("localhost", serverConnector.getLocalPort());
+        for (Map.Entry<String, String> entry : hopHeaders.entrySet())
+            request.header(entry.getKey(), entry.getValue());
+        ContentResponse response = request
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        Assert.assertEquals(200, response.getStatus());
+    }
+
+    // TODO: test proxy authentication
+}
diff --git a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyTunnellingTest.java b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyTunnellingTest.java
new file mode 100644
index 0000000..a747a0a
--- /dev/null
+++ b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyTunnellingTest.java
@@ -0,0 +1,434 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.proxy;
+
+import java.io.IOException;
+import java.net.ConnectException;
+import java.net.Socket;
+import java.net.URLEncoder;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.HttpProxy;
+import org.eclipse.jetty.client.api.Connection;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Destination;
+import org.eclipse.jetty.client.util.FutureResponseListener;
+import org.eclipse.jetty.client.util.StringContentProvider;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpScheme;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.HttpConnection;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.eclipse.jetty.toolchain.test.TestTracker;
+import org.eclipse.jetty.util.Promise;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.hamcrest.Matchers;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Assume;
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+public class ProxyTunnellingTest
+{
+    @Rule
+    public TestTracker tracker = new TestTracker();
+
+    private SslContextFactory sslContextFactory;
+    private Server server;
+    private ServerConnector serverConnector;
+    private Server proxy;
+    private ServerConnector proxyConnector;
+
+    protected int proxyPort()
+    {
+        return proxyConnector.getLocalPort();
+    }
+
+    protected void startSSLServer(Handler handler) throws Exception
+    {
+        sslContextFactory = new SslContextFactory();
+        String keyStorePath = MavenTestingUtils.getTestResourceFile("keystore").getAbsolutePath();
+        sslContextFactory.setKeyStorePath(keyStorePath);
+        sslContextFactory.setKeyStorePassword("storepwd");
+        sslContextFactory.setKeyManagerPassword("keypwd");
+        server = new Server();
+        serverConnector = new ServerConnector(server, sslContextFactory);
+        server.addConnector(serverConnector);
+        server.setHandler(handler);
+        server.start();
+    }
+
+    protected void startProxy() throws Exception
+    {
+        startProxy(new ConnectHandler());
+    }
+
+    protected void startProxy(ConnectHandler connectHandler) throws Exception
+    {
+        proxy = new Server();
+        proxyConnector = new ServerConnector(proxy);
+        proxy.addConnector(proxyConnector);
+        // Under Windows, it takes a while to detect that a connection
+        // attempt fails, so use an explicit timeout
+        connectHandler.setConnectTimeout(1000);
+        proxy.setHandler(connectHandler);
+        proxy.start();
+    }
+
+    @After
+    public void stop() throws Exception
+    {
+        stopProxy();
+        stopServer();
+    }
+
+    protected void stopServer() throws Exception
+    {
+        if (server != null)
+        {
+            server.stop();
+            server.join();
+        }
+    }
+
+    protected void stopProxy() throws Exception
+    {
+        if (proxy != null)
+        {
+            proxy.stop();
+            proxy.join();
+        }
+    }
+
+    @Test
+    public void testOneExchangeViaSSL() throws Exception
+    {
+        startSSLServer(new ServerHandler());
+        startProxy();
+
+        HttpClient httpClient = new HttpClient(sslContextFactory);
+        httpClient.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", proxyPort()));
+        httpClient.start();
+
+        try
+        {
+            // Use a numeric host to test the URI of the CONNECT request.
+            String host = "127.0.0.1";
+            String body = "BODY";
+            ContentResponse response = httpClient.newRequest(host, serverConnector.getLocalPort())
+                    .scheme(HttpScheme.HTTPS.asString())
+                    .method(HttpMethod.GET)
+                    .path("/echo?body=" + URLEncoder.encode(body, "UTF-8"))
+                    .send();
+
+            assertEquals(HttpStatus.OK_200, response.getStatus());
+            String content = response.getContentAsString();
+            assertEquals(body, content);
+        }
+        finally
+        {
+            httpClient.stop();
+        }
+    }
+
+    @Test
+    public void testTwoExchangesViaSSL() throws Exception
+    {
+        startSSLServer(new ServerHandler());
+        startProxy();
+
+        HttpClient httpClient = new HttpClient(sslContextFactory);
+        httpClient.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", proxyPort()));
+        httpClient.start();
+
+        try
+        {
+            String body = "BODY";
+            ContentResponse response1 = httpClient.newRequest("localhost", serverConnector.getLocalPort())
+                    .scheme(HttpScheme.HTTPS.asString())
+                    .method(HttpMethod.GET)
+                    .path("/echo?body=" + URLEncoder.encode(body, "UTF-8"))
+                    .send();
+
+            assertEquals(HttpStatus.OK_200, response1.getStatus());
+            String content = response1.getContentAsString();
+            assertEquals(body, content);
+
+            content = "body=" + body;
+            ContentResponse response2 = httpClient.newRequest("localhost", serverConnector.getLocalPort())
+                    .scheme(HttpScheme.HTTPS.asString())
+                    .method(HttpMethod.POST)
+                    .path("/echo")
+                    .header(HttpHeader.CONTENT_TYPE, MimeTypes.Type.FORM_ENCODED.asString())
+                    .header(HttpHeader.CONTENT_LENGTH, String.valueOf(content.length()))
+                    .content(new StringContentProvider(content))
+                    .send();
+
+            assertEquals(HttpStatus.OK_200, response2.getStatus());
+            content = response2.getContentAsString();
+            assertEquals(body, content);
+        }
+        finally
+        {
+            httpClient.stop();
+        }
+    }
+
+    @Test
+    public void testTwoConcurrentExchangesViaSSL() throws Exception
+    {
+        startSSLServer(new ServerHandler());
+        startProxy();
+
+        final HttpClient httpClient = new HttpClient(sslContextFactory);
+        httpClient.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", proxyPort()));
+        httpClient.start();
+
+        try
+        {
+            final AtomicReference<Connection> connection = new AtomicReference<>();
+            final CountDownLatch connectionLatch = new CountDownLatch(1);
+            String body1 = "BODY";
+            ContentResponse response1 = httpClient.newRequest("localhost", serverConnector.getLocalPort())
+                    .scheme(HttpScheme.HTTPS.asString())
+                    .method(HttpMethod.GET)
+                    .path("/echo?body=" + URLEncoder.encode(body1, "UTF-8"))
+                    .onRequestCommit(new org.eclipse.jetty.client.api.Request.CommitListener()
+                    {
+                        @Override
+                        public void onCommit(org.eclipse.jetty.client.api.Request request)
+                        {
+                            Destination destination = httpClient.getDestination(HttpScheme.HTTPS.asString(), "localhost", serverConnector.getLocalPort());
+                            destination.newConnection(new Promise.Adapter<Connection>()
+                            {
+                                @Override
+                                public void succeeded(Connection result)
+                                {
+                                    connection.set(result);
+                                    connectionLatch.countDown();
+                                }
+                            });
+                        }
+                    })
+                    .send();
+
+            assertEquals(HttpStatus.OK_200, response1.getStatus());
+            String content = response1.getContentAsString();
+            assertEquals(body1, content);
+
+            Assert.assertTrue(connectionLatch.await(5, TimeUnit.SECONDS));
+
+            String body2 = "body=" + body1;
+            org.eclipse.jetty.client.api.Request request2 = httpClient.newRequest("localhost", serverConnector.getLocalPort())
+                    .scheme(HttpScheme.HTTPS.asString())
+                    .method(HttpMethod.POST)
+                    .path("/echo")
+                    .header(HttpHeader.CONTENT_TYPE, MimeTypes.Type.FORM_ENCODED.asString())
+                    .header(HttpHeader.CONTENT_LENGTH, String.valueOf(body2.length()))
+                    .content(new StringContentProvider(body2));
+
+            // Make sure the second connection can send the exchange via the tunnel
+            FutureResponseListener listener2 = new FutureResponseListener(request2);
+            connection.get().send(request2, listener2);
+            ContentResponse response2 = listener2.get(5, TimeUnit.SECONDS);
+
+            assertEquals(HttpStatus.OK_200, response2.getStatus());
+            String content2 = response1.getContentAsString();
+            assertEquals(body1, content2);
+        }
+        finally
+        {
+            httpClient.stop();
+        }
+    }
+
+    @Test
+    public void testProxyDown() throws Exception
+    {
+        startSSLServer(new ServerHandler());
+        startProxy();
+        int proxyPort = proxyPort();
+        stopProxy();
+
+        HttpClient httpClient = new HttpClient(sslContextFactory);
+        httpClient.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", proxyPort));
+        httpClient.start();
+
+        try
+        {
+            String body = "BODY";
+            httpClient.newRequest("localhost", serverConnector.getLocalPort())
+                    .scheme(HttpScheme.HTTPS.asString())
+                    .method(HttpMethod.GET)
+                    .path("/echo?body=" + URLEncoder.encode(body, "UTF-8"))
+                    .send();
+            Assert.fail();
+        }
+        catch (ExecutionException x)
+        {
+            Assert.assertThat(x.getCause(), Matchers.instanceOf(ConnectException.class));
+        }
+        finally
+        {
+            httpClient.stop();
+        }
+    }
+
+    @Test
+    public void testServerDown() throws Exception
+    {
+        startSSLServer(new ServerHandler());
+        int serverPort = serverConnector.getLocalPort();
+        stopServer();
+        startProxy();
+
+        HttpClient httpClient = new HttpClient(sslContextFactory);
+        httpClient.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", proxyPort()));
+        httpClient.start();
+
+        try
+        {
+            String body = "BODY";
+            httpClient.newRequest("localhost", serverPort)
+                    .scheme(HttpScheme.HTTPS.asString())
+                    .method(HttpMethod.GET)
+                    .path("/echo?body=" + URLEncoder.encode(body, "UTF-8"))
+                    .send();
+            Assert.fail();
+        }
+        catch (ExecutionException x)
+        {
+            // Expected
+        }
+        finally
+        {
+            httpClient.stop();
+        }
+    }
+
+    @Test
+    public void testProxyClosesConnection() throws Exception
+    {
+        startSSLServer(new ServerHandler());
+        startProxy(new ConnectHandler()
+        {
+            @Override
+            protected void handleConnect(Request baseRequest, HttpServletRequest request, HttpServletResponse response, String serverAddress)
+            {
+                ((HttpConnection)baseRequest.getHttpChannel().getHttpTransport()).close();
+            }
+        });
+
+        HttpClient httpClient = new HttpClient(sslContextFactory);
+        httpClient.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", proxyPort()));
+        httpClient.start();
+
+        try
+        {
+            httpClient.newRequest("localhost", serverConnector.getLocalPort())
+                    .scheme(HttpScheme.HTTPS.asString())
+                    .send();
+            Assert.fail();
+        }
+        catch (ExecutionException x)
+        {
+            // Expected
+        }
+        finally
+        {
+            httpClient.stop();
+        }
+    }
+
+    @Test
+    @Ignore("External Proxy Server no longer stable enough for testing")
+    public void testExternalProxy() throws Exception
+    {
+        // Free proxy server obtained from http://hidemyass.com/proxy-list/
+        String proxyHost = "81.208.25.53";
+        int proxyPort = 3128;
+        try
+        {
+            new Socket(proxyHost, proxyPort).close();
+        }
+        catch (Throwable x)
+        {
+            Assume.assumeNoException(x);
+        }
+
+        SslContextFactory sslContextFactory = new SslContextFactory();
+        sslContextFactory.start();
+
+        HttpClient httpClient = new HttpClient(sslContextFactory);
+        httpClient.getProxyConfiguration().getProxies().add(new HttpProxy(proxyHost, proxyPort));
+        httpClient.start();
+
+        try
+        {
+            ContentResponse response = httpClient.newRequest("https://www.google.com")
+                    // Use a longer timeout, sometimes the proxy takes a while to answer
+                    .timeout(20, TimeUnit.SECONDS)
+                    .send();
+            assertEquals(HttpStatus.OK_200, response.getStatus());
+        }
+        finally
+        {
+            httpClient.stop();
+        }
+    }
+
+    private static class ServerHandler extends AbstractHandler
+    {
+        public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException
+        {
+            request.setHandled(true);
+
+            String uri = httpRequest.getRequestURI();
+            if ("/echo".equals(uri))
+            {
+                String body = httpRequest.getParameter("body");
+                ServletOutputStream output = httpResponse.getOutputStream();
+                output.print(body);
+            }
+            else
+            {
+                throw new ServletException();
+            }
+        }
+    }
+}
diff --git a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ReverseProxyTest.java b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ReverseProxyTest.java
new file mode 100644
index 0000000..0c4261c
--- /dev/null
+++ b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ReverseProxyTest.java
@@ -0,0 +1,153 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.proxy;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.HttpConnectionFactory;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jetty.toolchain.test.TestTracker;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class ReverseProxyTest
+{
+    @Rule
+    public final TestTracker tracker = new TestTracker();
+    private Server server;
+    private ServerConnector serverConnector;
+    private Server proxy;
+    private ServerConnector proxyConnector;
+    private HttpClient client;
+
+    private void startServer(HttpServlet servlet) throws Exception
+    {
+        server = new Server();
+
+        serverConnector = new ServerConnector(server);
+        server.addConnector(serverConnector);
+
+        ServletContextHandler appCtx = new ServletContextHandler(server, "/", true, false);
+        ServletHolder appServletHolder = new ServletHolder(servlet);
+        appCtx.addServlet(appServletHolder, "/*");
+
+        server.start();
+    }
+
+    private void startProxy(Map<String, String> params) throws Exception
+    {
+        proxy = new Server();
+
+        HttpConfiguration configuration = new HttpConfiguration();
+        configuration.setSendDateHeader(false);
+        configuration.setSendServerVersion(false);
+        proxyConnector = new ServerConnector(proxy, new HttpConnectionFactory(configuration));
+        proxy.addConnector(proxyConnector);
+
+        ServletContextHandler proxyContext = new ServletContextHandler(proxy, "/", true, false);
+        ServletHolder proxyServletHolder = new ServletHolder(new AsyncMiddleManServlet()
+        {
+            @Override
+            protected String rewriteTarget(HttpServletRequest clientRequest)
+            {
+                StringBuilder builder = new StringBuilder();
+                builder.append(clientRequest.getScheme()).append("://127.0.0.1:");
+                builder.append(serverConnector.getLocalPort());
+                builder.append(clientRequest.getRequestURI());
+                String query = clientRequest.getQueryString();
+                if (query != null)
+                    builder.append("?").append(query);
+                return builder.toString();
+            }
+        });
+        if (params != null)
+            proxyServletHolder.setInitParameters(params);
+
+        proxyContext.addServlet(proxyServletHolder, "/*");
+
+        proxy.start();
+    }
+
+    private void startClient() throws Exception
+    {
+        client = new HttpClient();
+        client.start();
+    }
+
+    @After
+    public void dispose() throws Exception
+    {
+        client.stop();
+        proxy.stop();
+        server.stop();
+    }
+
+    @Test
+    public void testHostHeaderUpdatedWhenSentToServer() throws Exception
+    {
+        startServer(new HttpServlet()
+        {
+            @Override
+            protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+            {
+                Assert.assertEquals("127.0.0.1", request.getServerName());
+                Assert.assertEquals(serverConnector.getLocalPort(), request.getServerPort());
+            }
+        });
+        startProxy(null);
+        startClient();
+
+        ContentResponse response = client.newRequest("localhost", proxyConnector.getLocalPort()).send();
+        Assert.assertEquals(200, response.getStatus());
+    }
+
+    @Test
+    public void testHostHeaderPreserved() throws Exception
+    {
+        startServer(new HttpServlet()
+        {
+            @Override
+            protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+            {
+                Assert.assertEquals("localhost", request.getServerName());
+                Assert.assertEquals(proxyConnector.getLocalPort(), request.getServerPort());
+            }
+        });
+        startProxy(new HashMap<String, String>() {{ put("preserveHost", "true"); }});
+        startClient();
+
+        ContentResponse response = client.newRequest("localhost", proxyConnector.getLocalPort()).send();
+        Assert.assertEquals(200, response.getStatus());
+    }
+}
diff --git a/jetty-proxy/src/test/resources/jetty-logging.properties b/jetty-proxy/src/test/resources/jetty-logging.properties
new file mode 100644
index 0000000..e0c669f
--- /dev/null
+++ b/jetty-proxy/src/test/resources/jetty-logging.properties
@@ -0,0 +1,4 @@
+org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
+#org.eclipse.jetty.LEVEL=DEBUG
+#org.eclipse.jetty.client.LEVEL=DEBUG
+#org.eclipse.jetty.proxy.LEVEL=DEBUG
diff --git a/jetty-client/src/test/resources/keystore b/jetty-proxy/src/test/resources/keystore
similarity index 100%
copy from jetty-client/src/test/resources/keystore
copy to jetty-proxy/src/test/resources/keystore
diff --git a/jetty-quickstart/README.txt b/jetty-quickstart/README.txt
new file mode 100644
index 0000000..43ad7f3
--- /dev/null
+++ b/jetty-quickstart/README.txt
@@ -0,0 +1,6 @@
+mvn exec:java -Dexec.classpathScope=test -Dexec.mainClass="org.eclipse.jetty.quickstart.StartBenchmarkWar"
+
+OR
+
+mvn exec:java -Dexec.classpathScope=test -Dexec.mainClass="org.eclipse.jetty.quickstart.PreconfigureBenchmarkWar"
+mvn exec:java -Dexec.classpathScope=test -Dexec.mainClass="org.eclipse.jetty.quickstart.QuickStartBenchmarkWar"
diff --git a/jetty-quickstart/pom.xml b/jetty-quickstart/pom.xml
new file mode 100644
index 0000000..b9fa34d
--- /dev/null
+++ b/jetty-quickstart/pom.xml
@@ -0,0 +1,104 @@
+<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">
+  <parent>
+    <groupId>org.eclipse.jetty</groupId>
+    <artifactId>jetty-project</artifactId>
+    <version>9.2.13.v20150730</version>
+  </parent>
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>org.eclipse.jetty</groupId>
+  <artifactId>jetty-quickstart</artifactId>
+  <name>Jetty :: Quick Start</name>
+  <description>Jetty Quick Start</description>
+  <url>http://www.eclipse.org/jetty</url>
+  <dependencies>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-webapp</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-jmx</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-plus</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-annotations</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>javax.transaction</groupId>
+      <artifactId>javax.transaction-api</artifactId>
+      <version>1.2</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.tests</groupId>
+      <artifactId>test-mock-resources</artifactId>
+      <version>${project.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.orbit</groupId>
+      <artifactId>javax.mail.glassfish</artifactId>
+      <version>1.4.1.v201005082020</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-servlets</artifactId>
+      <version>${project.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.websocket</groupId>
+      <artifactId>javax-websocket-server-impl</artifactId>
+      <version>${project.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.websocket</groupId>
+      <artifactId>websocket-server</artifactId>
+      <version>${project.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>apache-jsp</artifactId>
+      <version>${project.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>apache-jstl</artifactId>
+      <version>${project.version}</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-assembly-plugin</artifactId>
+        <executions>
+          <execution>
+            <phase>package</phase>
+            <goals>
+              <goal>single</goal>
+            </goals>
+            <configuration>
+              <descriptorRefs>
+                <descriptorRef>config</descriptorRef>
+              </descriptorRefs>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+</project>
diff --git a/jetty-quickstart/src/main/config/etc/example-quickstart.xml b/jetty-quickstart/src/main/config/etc/example-quickstart.xml
new file mode 100644
index 0000000..3f02e60
--- /dev/null
+++ b/jetty-quickstart/src/main/config/etc/example-quickstart.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0"  encoding="ISO-8859-1"?>
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
+
+<!-- An example context XML for a quickstart webapp
+A quick started webapp has all the jar scanning and fragment resolution done in a 
+Preconfigure phase, with all the discovered configuration encoded into a
+WEB-INF/quickstart-web.xml file.
+
+This allows very rapid and precise starting of a webapp, without the risk of accidental
+deployment of other discovered resources.  This is above and beyond what is available 
+with web.xml meta-data-complete, as it also prevents scanning for ServletContainer 
+initialisers and any annotations/classes they require.
+
+If autoPreconfigure is set to true, then the webapp will be preconfigured the first
+time it is run.
+ -->
+<Configure class="org.eclipse.jetty.quickstart.QuickStartWebApp">
+  <Set name="autoPreconfigure">true</Set>
+  <Set name="contextPath">/</Set>
+  <Set name="war"><Property name="jetty.webapps" default="."/>/application.war</Set>
+  <!--
+  <Call name="setInitParameter">
+    <Arg>org.eclipse.jetty.jsp.precompiled</Arg>
+    <Arg>true</Arg>
+  </Call>
+  -->
+</Configure>
diff --git a/jetty-quickstart/src/main/config/modules/quickstart.mod b/jetty-quickstart/src/main/config/modules/quickstart.mod
new file mode 100644
index 0000000..89db9fd
--- /dev/null
+++ b/jetty-quickstart/src/main/config/modules/quickstart.mod
@@ -0,0 +1,12 @@
+#
+# Jetty Quickstart module
+#
+
+[depend]
+server
+plus
+annotations
+
+
+[lib]
+lib/jetty-quickstart-${jetty.version}.jar
diff --git a/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/PreconfigureDescriptorProcessor.java b/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/PreconfigureDescriptorProcessor.java
new file mode 100644
index 0000000..98b3ed9
--- /dev/null
+++ b/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/PreconfigureDescriptorProcessor.java
@@ -0,0 +1,88 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.quickstart;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.webapp.Descriptor;
+import org.eclipse.jetty.webapp.IterativeDescriptorProcessor;
+import org.eclipse.jetty.webapp.WebAppContext;
+import org.eclipse.jetty.xml.XmlParser;
+
+/**
+ * Preconfigure DescriptorProcessor
+ *
+ * Saves literal XML snippets
+ *
+ */
+
+public class PreconfigureDescriptorProcessor extends IterativeDescriptorProcessor
+{
+    private static final Logger LOG = Log.getLogger(PreconfigureDescriptorProcessor.class);
+    
+    private final StringBuilder _buffer = new StringBuilder();
+    private final boolean _showOrigin;
+    private String _origin;
+
+    public PreconfigureDescriptorProcessor ()
+    {
+        _showOrigin=LOG.isDebugEnabled();
+        try
+        {
+            registerVisitor("env-entry", getClass().getMethod("saveSnippet", __signature));
+            registerVisitor("resource-ref", getClass().getMethod("saveSnippet", __signature));
+            registerVisitor("resource-env-ref", getClass().getMethod("saveSnippet", __signature));
+            registerVisitor("message-destination-ref", getClass().getMethod("saveSnippet", __signature));
+            registerVisitor("data-source", getClass().getMethod("saveSnippet", __signature));
+        }
+        catch (Exception e)
+        {
+            throw new IllegalStateException(e);
+        }
+    }
+
+    @Override
+    public void start(WebAppContext context, Descriptor descriptor)
+    {
+        LOG.debug("process {}",descriptor);
+        _origin=("  <!-- "+descriptor+" -->\n");
+    }
+
+
+    @Override
+    public void end(WebAppContext context,Descriptor descriptor)
+    {
+    }
+
+
+    public void saveSnippet (WebAppContext context, Descriptor descriptor, XmlParser.Node node)
+    throws Exception
+    {
+        LOG.debug("save {}",node.getTag());
+        if (_showOrigin)
+            _buffer.append(_origin);
+        _buffer.append("  ").append(node.toString()).append("\n");
+    }
+    
+    public String getXML()
+    {
+        return _buffer.toString();
+    }
+
+}
diff --git a/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/PreconfigureQuickStartWar.java b/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/PreconfigureQuickStartWar.java
new file mode 100644
index 0000000..134f423
--- /dev/null
+++ b/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/PreconfigureQuickStartWar.java
@@ -0,0 +1,129 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.quickstart;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.JarResource;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.xml.XmlConfiguration;
+
+public class PreconfigureQuickStartWar
+{
+    private static final Logger LOG = Log.getLogger(PreconfigureQuickStartWar.class);
+    static final boolean ORIGIN=LOG.isDebugEnabled();
+    
+
+    public static void main(String... args) throws Exception
+    {
+        Resource war = null;
+        Resource dir = null;
+        Resource xml = null;
+
+        switch (args.length)
+        {
+            case 0:
+                error("No WAR file or directory given");
+                break;
+
+            case 1:
+                dir = Resource.newResource(args[0]);
+
+            case 2:
+                war = Resource.newResource(args[0]);
+                if (war.isDirectory())
+                {
+                    dir = war;
+                    war = null;
+                    xml = Resource.newResource(args[1]);
+                }
+                else
+                {
+                    dir = Resource.newResource(args[1]);
+                }
+
+                break;
+
+            case 3:
+                war = Resource.newResource(args[0]);
+                dir = Resource.newResource(args[1]);
+                xml = Resource.newResource(args[2]);
+                break;
+
+            default:
+                error("Too many args");
+                break;
+        }
+
+        
+        preconfigure(war,dir,xml);
+    }
+
+    /**
+     * @param war The war (or directory) to preconfigure
+     * @param dir The directory to expand the war into (or null if war is a directory)
+     * @param xml A context XML to apply (or null if none)
+     * @throws Exception
+     */
+    public static void preconfigure(Resource war, Resource dir, Resource xml) throws Exception 
+    {
+        // Do we need to unpack a war?
+        if (war != null)
+        {
+            if (war.isDirectory())
+                error("war file is directory");
+
+            if (!dir.exists())
+                dir.getFile().mkdirs();
+            JarResource.newJarResource(war).copyTo(dir.getFile());
+        }
+        
+        final Server server = new Server();
+
+        QuickStartWebApp webapp = new QuickStartWebApp();
+
+        if (xml != null)
+        {
+            if (xml.isDirectory() || !xml.toString().toLowerCase().endsWith(".xml"))
+                error("Bad context.xml: "+xml);
+            XmlConfiguration xmlConfiguration = new XmlConfiguration(xml.getURL());
+            xmlConfiguration.configure(webapp);
+        }
+        webapp.setResourceBase(dir.getFile().getAbsolutePath());
+        webapp.setPreconfigure(true);
+        server.setHandler(webapp);
+        server.start();
+        server.stop();
+    }
+    
+    
+    
+
+    private static void error(String message)
+    {
+        System.err.println("ERROR: " + message);
+        System.err.println("Usage: java -jar PreconfigureQuickStartWar.jar <war-directory>");
+        System.err.println("       java -jar PreconfigureQuickStartWar.jar <war-directory> <context-xml-file>");
+        System.err.println("       java -jar PreconfigureQuickStartWar.jar <war-file> <target-war-directory>");
+        System.err.println("       java -jar PreconfigureQuickStartWar.jar <war-file> <target-war-directory> <context-xml-file>");
+        System.exit(1);
+    }
+
+}
diff --git a/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/QuickStartConfiguration.java b/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/QuickStartConfiguration.java
new file mode 100644
index 0000000..9643db5
--- /dev/null
+++ b/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/QuickStartConfiguration.java
@@ -0,0 +1,154 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.quickstart;
+
+import org.eclipse.jetty.annotations.AnnotationConfiguration;
+import org.eclipse.jetty.annotations.AnnotationDecorator;
+import org.eclipse.jetty.annotations.ServletContainerInitializersStarter;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.webapp.StandardDescriptorProcessor;
+import org.eclipse.jetty.webapp.WebAppClassLoader;
+import org.eclipse.jetty.webapp.WebAppContext;
+import org.eclipse.jetty.webapp.WebInfConfiguration;
+
+/**
+ * QuickStartConfiguration
+ * 
+ * Re-inflate a deployable webapp from a saved effective-web.xml
+ * which combines all pre-parsed web xml descriptors and annotations.
+ * 
+ */
+public class QuickStartConfiguration extends WebInfConfiguration
+{
+    private static final Logger LOG = Log.getLogger(QuickStartConfiguration.class);
+
+    /**
+     * @see org.eclipse.jetty.webapp.AbstractConfiguration#preConfigure(org.eclipse.jetty.webapp.WebAppContext)
+     */
+    @Override
+    public void preConfigure(WebAppContext context) throws Exception
+    {
+        //check that webapp is suitable for quick start - it is not a packed war
+        String war = context.getWar();
+        if (war == null || war.length()<=0)
+            throw new IllegalStateException ("No location for webapp");  
+        
+        //Make a temp directory for the webapp if one is not already set
+        resolveTempDirectory(context);
+
+        Resource webApp = context.newResource(war);
+
+        // Accept aliases for WAR files
+        if (webApp.getAlias() != null)
+        {
+            LOG.debug(webApp + " anti-aliased to " + webApp.getAlias());
+            webApp = context.newResource(webApp.getAlias());
+        }
+
+        // Is the WAR usable directly?
+        if (!webApp.exists() || !webApp.isDirectory() || webApp.toString().startsWith("jar:"))
+            throw new IllegalStateException("Webapp does not exist or is not unpacked");
+        
+        context.setBaseResource(webApp);
+
+        LOG.debug("webapp={}",webApp);
+
+        
+        //look for quickstart-web.xml in WEB-INF of webapp
+        Resource quickStartWebXml = getQuickStartWebXml(context);
+        LOG.debug("quickStartWebXml={}",quickStartWebXml);
+        
+        context.getMetaData().setWebXml(quickStartWebXml);
+    }
+
+    
+    /**
+     * Get the quickstart-web.xml file as a Resource.
+     * 
+     * @param context
+     * @return
+     * @throws Exception
+     */
+    public Resource getQuickStartWebXml (WebAppContext context) throws Exception
+    {
+        Resource webInf = context.getWebInf();
+        if (webInf == null || !webInf.exists())
+            throw new IllegalStateException("No WEB-INF");
+        LOG.debug("webinf={}",webInf);
+  
+        Resource quickStartWebXml = webInf.addPath("quickstart-web.xml");
+        if (!quickStartWebXml.exists())
+            throw new IllegalStateException ("No WEB-INF/quickstart-web.xml");
+        return quickStartWebXml;
+    }
+    
+    
+    
+    /**
+     * @see org.eclipse.jetty.webapp.AbstractConfiguration#configure(org.eclipse.jetty.webapp.WebAppContext)
+     */
+    @Override
+    public void configure(WebAppContext context) throws Exception
+    {
+        LOG.debug("configure {}",this);
+        if (context.isStarted())
+        {
+            LOG.warn("Cannot configure webapp after it is started");
+            return;
+        }
+        
+        //Temporary:  set up the classpath here. This should be handled by the QuickStartDescriptorProcessor
+        Resource webInf = context.getWebInf();
+
+        if (webInf != null && webInf.isDirectory() && context.getClassLoader() instanceof WebAppClassLoader)
+        {
+            // Look for classes directory
+            Resource classes= webInf.addPath("classes/");
+            if (classes.exists())
+                ((WebAppClassLoader)context.getClassLoader()).addClassPath(classes);
+
+            // Look for jars
+            Resource lib= webInf.addPath("lib/");
+            if (lib.exists() || lib.isDirectory())
+                ((WebAppClassLoader)context.getClassLoader()).addJars(lib);
+        }
+
+        //add the processor to handle normal web.xml content
+        context.getMetaData().addDescriptorProcessor(new StandardDescriptorProcessor());
+        
+        //add a processor to handle extended web.xml format
+        context.getMetaData().addDescriptorProcessor(new QuickStartDescriptorProcessor());
+        
+        //add a decorator that will find introspectable annotations
+        context.addDecorator(new AnnotationDecorator(context)); //this must be the last Decorator because they are run in reverse order!
+        
+        //add a context bean that will run ServletContainerInitializers as the context starts
+        ServletContainerInitializersStarter starter = (ServletContainerInitializersStarter)context.getAttribute(AnnotationConfiguration.CONTAINER_INITIALIZER_STARTER);
+        if (starter != null)
+            throw new IllegalStateException("ServletContainerInitializersStarter already exists");
+        starter = new ServletContainerInitializersStarter(context);
+        context.setAttribute(AnnotationConfiguration.CONTAINER_INITIALIZER_STARTER, starter);
+        context.addBean(starter, true);       
+
+        LOG.debug("configured {}",this);
+    }
+
+}
diff --git a/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/QuickStartDescriptorGenerator.java b/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/QuickStartDescriptorGenerator.java
new file mode 100644
index 0000000..ce2f97a
--- /dev/null
+++ b/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/QuickStartDescriptorGenerator.java
@@ -0,0 +1,688 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.quickstart;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EventListener;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import javax.servlet.DispatcherType;
+import javax.servlet.MultipartConfigElement;
+import javax.servlet.ServletContext;
+import javax.servlet.SessionCookieConfig;
+import javax.servlet.SessionTrackingMode;
+import javax.servlet.descriptor.JspPropertyGroupDescriptor;
+import javax.servlet.descriptor.TaglibDescriptor;
+
+import org.eclipse.jetty.annotations.AnnotationConfiguration;
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.plus.annotation.LifeCycleCallback;
+import org.eclipse.jetty.plus.annotation.LifeCycleCallbackCollection;
+import org.eclipse.jetty.security.ConstraintAware;
+import org.eclipse.jetty.security.ConstraintMapping;
+import org.eclipse.jetty.security.SecurityHandler;
+import org.eclipse.jetty.security.authentication.FormAuthenticator;
+import org.eclipse.jetty.servlet.ErrorPageErrorHandler;
+import org.eclipse.jetty.servlet.FilterHolder;
+import org.eclipse.jetty.servlet.FilterMapping;
+import org.eclipse.jetty.servlet.ServletHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jetty.servlet.ServletMapping;
+import org.eclipse.jetty.servlet.ServletContextHandler.JspConfig;
+import org.eclipse.jetty.util.QuotedStringTokenizer;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.security.Constraint;
+import org.eclipse.jetty.webapp.MetaData;
+import org.eclipse.jetty.webapp.MetaData.OriginInfo;
+import org.eclipse.jetty.webapp.MetaInfConfiguration;
+import org.eclipse.jetty.webapp.WebAppContext;
+import org.eclipse.jetty.xml.XmlAppendable;
+
+
+
+/**
+ * QuickStartDescriptorGenerator
+ *
+ * Generate an effective web.xml from a WebAppContext, including all components 
+ * from web.xml, web-fragment.xmls annotations etc.
+ */
+public class QuickStartDescriptorGenerator
+{
+    private static final Logger LOG = Log.getLogger(QuickStartDescriptorGenerator.class);
+    
+    public static final String DEFAULT_QUICKSTART_DESCRIPTOR_NAME = "quickstart-web.xml";
+    
+    protected WebAppContext _webApp;
+    protected String _extraXML;
+  
+    
+    
+    /**
+     * @param w the source WebAppContext
+     * @param extraXML any extra xml snippet to append
+     */
+    public QuickStartDescriptorGenerator (WebAppContext w,  String extraXML)
+    {
+        _webApp = w;
+        _extraXML = extraXML;
+    }
+    
+    
+    /**
+     * Perform the generation of the xml file
+     * @throws IOException 
+     * @throws FileNotFoundException 
+     * @throws Exception
+     */
+    public void generateQuickStartWebXml (OutputStream stream) throws FileNotFoundException, IOException 
+    {   
+        if (_webApp == null)
+            throw new IllegalStateException("No webapp for quickstart generation");
+        if (stream == null)
+            throw new IllegalStateException("No output for quickstart generation");
+        
+        _webApp.getMetaData().getOrigins();
+
+        if (_webApp.getBaseResource()==null)
+            throw new IllegalArgumentException("No base resource for "+this);
+
+        LOG.info("Quickstart generating");
+
+        XmlAppendable out = new XmlAppendable(stream,"UTF-8");
+
+        MetaData md = _webApp.getMetaData();
+
+        Map<String, String> webappAttr = new HashMap<>();
+        webappAttr.put("xmlns","http://xmlns.jcp.org/xml/ns/javaee");
+        webappAttr.put("xmlns:xsi","http://www.w3.org/2001/XMLSchema-instance");
+        webappAttr.put("xsi:schemaLocation","http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd");
+        webappAttr.put("metadata-complete","true");
+        webappAttr.put("version","3.1");
+
+        out.openTag("web-app",webappAttr);
+        if (_webApp.getDisplayName() != null)
+            out.tag("display-name",_webApp.getDisplayName());
+        
+        // Set some special context parameters
+
+        // The location of the war file on disk
+        String resourceBase = _webApp.getBaseResource().getFile().getCanonicalFile().getAbsoluteFile().toURI().toString();
+
+        // The library order
+        addContextParamFromAttribute(out,ServletContext.ORDERED_LIBS);
+        //the servlet container initializers
+        addContextParamFromAttribute(out,AnnotationConfiguration.CONTAINER_INITIALIZERS);
+        //the tlds discovered
+        addContextParamFromAttribute(out,MetaInfConfiguration.METAINF_TLDS,resourceBase);
+        //the META-INF/resources discovered
+        addContextParamFromAttribute(out,MetaInfConfiguration.METAINF_RESOURCES,resourceBase);
+
+
+        // init params
+        for (String p : _webApp.getInitParams().keySet())
+            out.openTag("context-param",origin(md,"context-param." + p))
+            .tag("param-name",p)
+            .tag("param-value",_webApp.getInitParameter(p))
+            .closeTag();
+
+        if (_webApp.getEventListeners() != null)
+            for (EventListener e : _webApp.getEventListeners())
+                out.openTag("listener",origin(md,e.getClass().getCanonicalName() + ".listener"))
+                .tag("listener-class",e.getClass().getCanonicalName())
+                .closeTag();
+
+        ServletHandler servlets = _webApp.getServletHandler();
+
+        if (servlets.getFilters() != null)
+        {
+            for (FilterHolder holder : servlets.getFilters())
+                outholder(out,md,holder);
+        }
+
+        if (servlets.getFilterMappings() != null)
+        {
+            for (FilterMapping mapping : servlets.getFilterMappings())
+            {
+                out.openTag("filter-mapping");
+                out.tag("filter-name",mapping.getFilterName());
+                if (mapping.getPathSpecs() != null)
+                    for (String s : mapping.getPathSpecs())
+                        out.tag("url-pattern",s);
+                if (mapping.getServletNames() != null)
+                    for (String n : mapping.getServletNames())
+                        out.tag("servlet-name",n);
+
+                if (!mapping.isDefaultDispatches())
+                {
+                    if (mapping.appliesTo(DispatcherType.REQUEST))
+                        out.tag("dispatcher","REQUEST");
+                    if (mapping.appliesTo(DispatcherType.ASYNC))
+                        out.tag("dispatcher","ASYNC");
+                    if (mapping.appliesTo(DispatcherType.ERROR))
+                        out.tag("dispatcher","ERROR");
+                    if (mapping.appliesTo(DispatcherType.FORWARD))
+                        out.tag("dispatcher","FORWARD");
+                    if (mapping.appliesTo(DispatcherType.INCLUDE))
+                        out.tag("dispatcher","INCLUDE");
+                }
+                out.closeTag();
+            }
+        }
+
+        if (servlets.getServlets() != null)
+        {
+            for (ServletHolder holder : servlets.getServlets())
+                outholder(out,md,holder);
+        }
+
+        if (servlets.getServletMappings() != null)
+        {
+            for (ServletMapping mapping : servlets.getServletMappings())
+            {
+                out.openTag("servlet-mapping",origin(md,mapping.getServletName() + ".servlet.mappings"));
+                out.tag("servlet-name",mapping.getServletName());
+                if (mapping.getPathSpecs() != null)
+                    for (String s : mapping.getPathSpecs())
+                        out.tag("url-pattern",s);
+                out.closeTag();
+            }
+        }
+
+        // Security elements
+        SecurityHandler security =_webApp. getSecurityHandler();
+
+        if (security!=null && (security.getRealmName()!=null || security.getAuthMethod()!=null))
+        {
+            out.openTag("login-config");
+            if (security.getAuthMethod()!=null)
+                out.tag("auth-method",origin(md,"auth-method"),security.getAuthMethod());
+            if (security.getRealmName()!=null)
+                out.tag("realm-name",origin(md,"realm-name"),security.getRealmName());
+
+
+            if (Constraint.__FORM_AUTH.equalsIgnoreCase(security.getAuthMethod()))
+            {
+                out.openTag("form-login-config");
+                out.tag("form-login-page",origin(md,"form-login-page"),security.getInitParameter(FormAuthenticator.__FORM_LOGIN_PAGE));
+                out.tag("form-error-page",origin(md,"form-error-page"),security.getInitParameter(FormAuthenticator.__FORM_ERROR_PAGE));
+                out.closeTag();
+            }
+
+            out.closeTag();
+        }
+
+        if (security instanceof ConstraintAware)
+        {
+            ConstraintAware ca = (ConstraintAware)security;
+            for (String r:ca.getRoles())
+                out.openTag("security-role")
+                .tag("role-name",r)
+                .closeTag();
+
+            for (ConstraintMapping m : ca.getConstraintMappings())
+            {
+                out.openTag("security-constraint");
+
+                out.openTag("web-resource-collection");
+                {
+                    if (m.getConstraint().getName()!=null)
+                        out.tag("web-resource-name",m.getConstraint().getName());
+                    if (m.getPathSpec()!=null)
+                        out.tag("url-pattern",origin(md,"constraint.url."+m.getPathSpec()),m.getPathSpec());
+                    if (m.getMethod()!=null)
+                        out.tag("http-method",m.getMethod());
+
+                    if (m.getMethodOmissions()!=null)
+                        for (String o:m.getMethodOmissions())
+                            out.tag("http-method-omission",o);
+
+                    out.closeTag();
+                }
+
+                if (m.getConstraint().getAuthenticate())
+                {
+                    String[] roles = m.getConstraint().getRoles();
+                    if (roles!=null && roles.length>0)
+                    {
+                        out.openTag("auth-constraint");
+                        if (m.getConstraint().getRoles()!=null)
+                            for (String r : m.getConstraint().getRoles())
+                                out.tag("role-name",r);
+                        out.closeTag();
+                    }
+                    else
+                        out.tag("auth-constraint");
+                }
+
+                switch (m.getConstraint().getDataConstraint())
+                {
+                    case Constraint.DC_NONE:
+                        out.openTag("user-data-constraint").tag("transport-guarantee","NONE").closeTag();
+                        break;
+
+                    case Constraint.DC_INTEGRAL:
+                        out.openTag("user-data-constraint").tag("transport-guarantee","INTEGRAL").closeTag();
+                        break;
+
+                    case Constraint.DC_CONFIDENTIAL:
+                        out.openTag("user-data-constraint").tag("transport-guarantee","CONFIDENTIAL").closeTag();
+                        break;
+
+                    default:
+                        break;
+
+                }
+
+                out.closeTag();
+
+            }
+        }
+
+        if (_webApp.getWelcomeFiles() != null)
+        {
+            out.openTag("welcome-file-list");
+            for (String welcomeFile:_webApp.getWelcomeFiles())
+            {
+                out.tag("welcome-file", welcomeFile);
+            }
+            out.closeTag();
+        }
+
+        Map<String,String> localeEncodings = _webApp.getLocaleEncodings();
+        if (localeEncodings != null && !localeEncodings.isEmpty())
+        {
+            out.openTag("locale-encoding-mapping-list");
+            for (Map.Entry<String, String> entry:localeEncodings.entrySet())
+            {
+                out.openTag("locale-encoding-mapping", origin(md,"locale-encoding."+entry.getKey()));
+                out.tag("locale", entry.getKey());
+                out.tag("encoding", entry.getValue());
+                out.closeTag();
+            }
+            out.closeTag();
+        }
+
+        //session-config
+        if (_webApp.getSessionHandler().getSessionManager() != null)
+        {
+            out.openTag("session-config");
+            int maxInactiveSec = _webApp.getSessionHandler().getSessionManager().getMaxInactiveInterval();
+            out.tag("session-timeout", (maxInactiveSec==0?"0":Integer.toString(maxInactiveSec/60)));
+
+
+            //cookie-config
+            SessionCookieConfig cookieConfig = _webApp.getSessionHandler().getSessionManager().getSessionCookieConfig();
+            if (cookieConfig != null)
+            {
+                out.openTag("cookie-config");
+                if (cookieConfig.getName() != null)
+                    out.tag("name", origin(md,"cookie-config.name"), cookieConfig.getName());
+
+                if (cookieConfig.getDomain() != null)
+                    out.tag("domain", origin(md, "cookie-config.domain"), cookieConfig.getDomain());
+
+                if (cookieConfig.getPath() != null)
+                    out.tag("path", origin(md, "cookie-config.path"), cookieConfig.getPath());
+
+                if (cookieConfig.getComment() != null)
+                    out.tag("comment", origin(md, "cookie-config.comment"), cookieConfig.getComment());
+
+                out.tag("http-only", origin(md, "cookie-config.http-only"), Boolean.toString(cookieConfig.isHttpOnly()));
+                out.tag("secure", origin(md, "cookie-config.secure"), Boolean.toString(cookieConfig.isSecure()));
+                out.tag("max-age", origin(md, "cookie-config.max-age"), Integer.toString(cookieConfig.getMaxAge()));
+                out.closeTag();
+            }
+            
+            // tracking-modes
+            Set<SessionTrackingMode> modes =_webApp. getSessionHandler().getSessionManager().getEffectiveSessionTrackingModes();
+            if (modes != null)
+            {
+                for (SessionTrackingMode mode:modes)
+                    out.tag("tracking-mode", mode.toString());
+            }
+            
+            out.closeTag();     
+        }
+
+        //error-pages
+        Map<String,String> errorPages = ((ErrorPageErrorHandler)_webApp.getErrorHandler()).getErrorPages();
+        if (errorPages != null)
+        {
+            for (Map.Entry<String, String> entry:errorPages.entrySet())
+            {
+                out.openTag("error-page", origin(md, "error."+entry.getKey()));
+                //a global or default error page has no code or exception               
+                if (!ErrorPageErrorHandler.GLOBAL_ERROR_PAGE.equals(entry.getKey()))
+                {
+                    if (entry.getKey().matches("\\d{3}"))
+                        out.tag("error-code", entry.getKey());
+                    else
+                        out.tag("exception-type", entry.getKey());
+                }
+                out.tag("location", entry.getValue());
+                out.closeTag();
+            }
+        }
+
+        //mime-types
+        MimeTypes mimeTypes = _webApp.getMimeTypes();
+        if (mimeTypes != null)
+        {
+            for (Map.Entry<String, String> entry:mimeTypes.getMimeMap().entrySet())
+            {
+                out.openTag("mime-mapping");
+                out.tag("extension", origin(md, "extension."+entry.getKey()), entry.getKey());
+                out.tag("mime-type", entry.getValue());
+                out.closeTag();
+            }
+        }
+
+        //jsp-config
+        JspConfig jspConfig = (JspConfig)_webApp.getServletContext().getJspConfigDescriptor();
+        if (jspConfig != null)
+        {
+            out.openTag("jsp-config");
+            Collection<TaglibDescriptor> tlds = jspConfig.getTaglibs();
+            if (tlds != null && !tlds.isEmpty())
+            {
+                for (TaglibDescriptor tld:tlds)
+                {
+                    out.openTag("taglib");
+                    out.tag("taglib-uri", tld.getTaglibURI());
+                    out.tag("taglib-location", tld.getTaglibLocation());
+                    out.closeTag();
+                }
+            }
+
+            Collection<JspPropertyGroupDescriptor> jspPropertyGroups = jspConfig.getJspPropertyGroups();
+            if (jspPropertyGroups != null && !jspPropertyGroups.isEmpty())
+            {
+                for (JspPropertyGroupDescriptor jspPropertyGroup:jspPropertyGroups)
+                {
+                    out.openTag("jsp-property-group");
+                    Collection<String> strings = jspPropertyGroup.getUrlPatterns();
+                    if (strings != null && !strings.isEmpty())
+                    {
+                        for (String urlPattern:strings)
+                            out.tag("url-pattern", urlPattern);
+                    }
+
+                    if (jspPropertyGroup.getElIgnored() != null)
+                        out.tag("el-ignored", jspPropertyGroup.getElIgnored());
+
+                    if (jspPropertyGroup.getPageEncoding() != null)
+                        out.tag("page-encoding", jspPropertyGroup.getPageEncoding());
+
+                    if (jspPropertyGroup.getScriptingInvalid() != null)
+                        out.tag("scripting-invalid", jspPropertyGroup.getScriptingInvalid());
+
+                    if (jspPropertyGroup.getIsXml() != null)
+                        out.tag("is-xml", jspPropertyGroup.getIsXml());
+
+                    if (jspPropertyGroup.getDeferredSyntaxAllowedAsLiteral() != null)
+                        out.tag("deferred-syntax-allowed-as-literal", jspPropertyGroup.getDeferredSyntaxAllowedAsLiteral());
+
+                    if (jspPropertyGroup.getTrimDirectiveWhitespaces() != null)
+                        out.tag("trim-directive-whitespaces", jspPropertyGroup.getTrimDirectiveWhitespaces());
+
+                    if (jspPropertyGroup.getDefaultContentType() != null)
+                        out.tag("default-content-type", jspPropertyGroup.getDefaultContentType());
+
+                    if (jspPropertyGroup.getBuffer() != null)
+                        out.tag("buffer", jspPropertyGroup.getBuffer());
+
+                    if (jspPropertyGroup.getErrorOnUndeclaredNamespace() != null)
+                        out.tag("error-on-undeclared-namespace", jspPropertyGroup.getErrorOnUndeclaredNamespace());
+
+                    strings = jspPropertyGroup.getIncludePreludes();
+                    if (strings != null && !strings.isEmpty())
+                    {
+                        for (String prelude:strings)
+                            out.tag("include-prelude", prelude);
+                    }
+
+                    strings = jspPropertyGroup.getIncludeCodas();
+                    if (strings != null && !strings.isEmpty())
+                    {
+                        for (String coda:strings)
+                            out.tag("include-coda", coda);
+                    }
+
+                    out.closeTag();
+                }
+            }
+
+            out.closeTag();
+        }
+
+        //lifecycle: post-construct, pre-destroy
+        LifeCycleCallbackCollection lifecycles = ((LifeCycleCallbackCollection)_webApp.getAttribute(LifeCycleCallbackCollection.LIFECYCLE_CALLBACK_COLLECTION));
+        if (lifecycles != null)
+        {
+            Collection<LifeCycleCallback> tmp = lifecycles.getPostConstructCallbacks();
+
+            for (LifeCycleCallback c:tmp)
+            {
+                out.openTag("post-construct");
+                out.tag("lifecycle-callback-class", c.getTargetClassName());
+                out.tag("lifecycle-callback-method", c.getMethodName());
+                out.closeTag();
+            }
+
+            tmp = lifecycles.getPreDestroyCallbacks();
+            for (LifeCycleCallback c:tmp)
+            {
+                out.openTag("pre-destroy");
+                out.tag("lifecycle-callback-class", c.getTargetClassName());
+                out.tag("lifecycle-callback-method", c.getMethodName());
+                out.closeTag();
+            }
+        }
+
+        out.literal(_extraXML);
+
+        out.closeTag();
+    }
+
+    /**
+     * Turn attribute into context-param to store.
+     * 
+     * @param out
+     * @param attribute
+     * @throws IOException
+     */
+    private void addContextParamFromAttribute(XmlAppendable out, String attribute) throws IOException
+    {
+        addContextParamFromAttribute(out,attribute,null);
+    }
+    
+    /**
+     * Turn context attribute into context-param to store.
+     * 
+     * @param out
+     * @param attribute
+     * @param resourceBase
+     * @throws IOException
+     */
+    private void addContextParamFromAttribute(XmlAppendable out, String attribute, String resourceBase) throws IOException
+    {
+        Object o = _webApp.getAttribute(attribute);
+        if (o == null)
+            return;
+                
+        Collection<?> c =  (o instanceof Collection)? (Collection<?>)o:Collections.singletonList(o);
+        StringBuilder v=new StringBuilder();
+        for (Object i:c)
+        {
+            if (i!=null)
+            {
+                if (v.length()>0)
+                    v.append(",\n    ");
+                else
+                    v.append("\n    ");
+                if (resourceBase==null)
+                    QuotedStringTokenizer.quote(v,i.toString());
+                else
+                    QuotedStringTokenizer.quote(v,i.toString().replace(resourceBase,"${WAR}/"));
+            }
+        }
+        out.openTag("context-param")
+        .tag("param-name",attribute)
+        .tagCDATA("param-value",v.toString())
+        .closeTag();        
+    }
+
+    /**
+     * Generate xml for a Holder (Filter/Servlet)
+     * 
+     * @param out
+     * @param md
+     * @param tag
+     * @param holder
+     * @throws IOException
+     */
+    private void outholder(XmlAppendable out, MetaData md, FilterHolder holder) throws IOException
+    {
+        if (LOG.isDebugEnabled())
+            out.openTag("filter",Collections.singletonMap("source",holder.getSource().toString()));
+        else
+            out.openTag("filter");
+        
+        String n = holder.getName();
+        out.tag("filter-name",n);
+
+        String ot = n + ".filter.";
+        
+        if (holder instanceof FilterHolder)
+        {
+            out.tag("filter-class",origin(md,ot + "filter-class"),holder.getClassName());
+            out.tag("async-supported",origin(md,ot + "async-supported"),holder.isAsyncSupported()?"true":"false");
+        }
+        
+        for (String p : holder.getInitParameters().keySet())
+        {
+            out.openTag("init-param",origin(md,ot + "init-param." + p))
+            .tag("param-name",p)
+            .tag("param-value",holder.getInitParameter(p))
+            .closeTag();
+        }
+
+        out.closeTag();
+    }
+
+    private void outholder(XmlAppendable out, MetaData md, ServletHolder holder) throws IOException
+    {
+        
+        if (LOG.isDebugEnabled())
+            out.openTag("servlet",Collections.singletonMap("source",holder.getSource().toString()));
+        else
+            out.openTag("servlet");
+        
+        String n = holder.getName();
+        out.tag("servlet-name",n);
+
+        String ot = n + ".servlet.";
+
+        ServletHolder s = (ServletHolder)holder;
+        if (s.getForcedPath() != null && s.getClassName() == null)
+            out.tag("jsp-file",s.getForcedPath());
+        else
+            out.tag("servlet-class",origin(md,ot + "servlet-class"),s.getClassName());
+
+        for (String p : holder.getInitParameters().keySet())
+        {
+            if ("jsp".equalsIgnoreCase(n) && "scratchdir".equalsIgnoreCase(p)) //don't preconfigure the temp dir for jsp output
+                continue;
+            out.openTag("init-param",origin(md,ot + "init-param." + p))
+            .tag("param-name",p)
+            .tag("param-value",holder.getInitParameter(p))
+            .closeTag();
+        }
+
+        if (s.getInitOrder() >= 0)
+            out.tag("load-on-startup",Integer.toString(s.getInitOrder()));
+
+        if (!s.isEnabled())
+            out.tag("enabled",origin(md,ot + "enabled"),"false");
+
+        out.tag("async-supported",origin(md,ot + "async-supported"),holder.isAsyncSupported()?"true":"false");
+
+        if (s.getRunAsRole() != null)
+            out.openTag("run-as",origin(md,ot + "run-as"))
+            .tag("role-name",s.getRunAsRole())
+            .closeTag();
+
+        Map<String,String> roles = s.getRoleRefMap();
+        if (roles!=null)
+        {
+            for (Map.Entry<String, String> e : roles.entrySet())
+            {
+                out.openTag("security-role-ref",origin(md,ot+"role-name."+e.getKey()))
+                .tag("role-name",e.getKey())
+                .tag("role-link",e.getValue())
+                .closeTag();
+            }
+        }
+
+        //multipart-config
+        MultipartConfigElement multipartConfig = ((ServletHolder.Registration)s.getRegistration()).getMultipartConfig();
+        if (multipartConfig != null)
+        {
+            out.openTag("multipart-config", origin(md, s.getName()+".servlet.multipart-config"));
+            if (multipartConfig.getLocation() != null)
+                out.tag("location", multipartConfig.getLocation());
+            out.tag("max-file-size", Long.toString(multipartConfig.getMaxFileSize()));
+            out.tag("max-request-size", Long.toString(multipartConfig.getMaxRequestSize()));
+            out.tag("file-size-threshold", Long.toString(multipartConfig.getFileSizeThreshold()));
+            out.closeTag();
+        }
+
+        out.closeTag();
+    }
+    
+    
+    /**
+     * Find the origin (web.xml, fragment, annotation etc) of a web artifact from MetaData.
+     * 
+     * @param md
+     * @param name
+     * @return
+     */
+    public Map<String, String> origin(MetaData md, String name)
+    {
+        if (!LOG.isDebugEnabled())
+            return Collections.emptyMap();
+        if (name == null)
+            return Collections.emptyMap();
+        OriginInfo origin = md.getOriginInfo(name);
+        if (LOG.isDebugEnabled()) LOG.debug("origin of "+name+" is "+origin);
+        if (origin == null)
+            return Collections.emptyMap();
+        return Collections.singletonMap("origin",origin.toString());
+    }
+     
+}
diff --git a/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/QuickStartDescriptorProcessor.java b/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/QuickStartDescriptorProcessor.java
new file mode 100644
index 0000000..43a0c2e
--- /dev/null
+++ b/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/QuickStartDescriptorProcessor.java
@@ -0,0 +1,218 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.quickstart;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+
+import javax.servlet.ServletContext;
+
+import org.eclipse.jetty.annotations.AnnotationConfiguration;
+import org.eclipse.jetty.annotations.ServletContainerInitializersStarter;
+import org.eclipse.jetty.plus.annotation.ContainerInitializer;
+import org.eclipse.jetty.util.QuotedStringTokenizer;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.util.resource.ResourceCollection;
+import org.eclipse.jetty.webapp.Descriptor;
+import org.eclipse.jetty.webapp.IterativeDescriptorProcessor;
+import org.eclipse.jetty.webapp.MetaInfConfiguration;
+import org.eclipse.jetty.webapp.WebAppContext;
+import org.eclipse.jetty.xml.XmlParser;
+
+/**
+ * QuickStartDescriptorProcessor
+ * 
+ * Handle  extended elements for quickstart-web.xml
+ */
+public class QuickStartDescriptorProcessor extends IterativeDescriptorProcessor
+{
+    /**
+     * 
+     */
+    public QuickStartDescriptorProcessor()
+    {
+        try
+        {
+            registerVisitor("context-param", this.getClass().getMethod("visitContextParam", __signature));
+        }    
+        catch (Exception e)
+        {
+            throw new IllegalStateException(e);
+        }
+    }
+
+    /**
+     * @see org.eclipse.jetty.webapp.IterativeDescriptorProcessor#start(org.eclipse.jetty.webapp.WebAppContext, org.eclipse.jetty.webapp.Descriptor)
+     */
+    @Override
+    public void start(WebAppContext context, Descriptor descriptor)
+    {
+    }
+
+    /**
+     * @see org.eclipse.jetty.webapp.IterativeDescriptorProcessor#end(org.eclipse.jetty.webapp.WebAppContext, org.eclipse.jetty.webapp.Descriptor)
+     */
+    @Override
+    public void end(WebAppContext context, Descriptor descriptor)
+    { 
+    }
+    
+
+    /**
+     * @param context
+     * @param descriptor
+     * @param node
+     */
+    public void visitContextParam (WebAppContext context, Descriptor descriptor, XmlParser.Node node)
+            throws Exception
+    {
+        String name = node.getString("param-name", false, true);
+        String value = node.getString("param-value", false, true);
+        List<String> values = new ArrayList<>();
+        
+        // extract values
+        switch(name)
+        {
+            case ServletContext.ORDERED_LIBS:
+            case AnnotationConfiguration.CONTAINER_INITIALIZERS:
+            case MetaInfConfiguration.METAINF_TLDS:
+            case MetaInfConfiguration.METAINF_RESOURCES:
+
+                context.removeAttribute(name);
+                
+                QuotedStringTokenizer tok = new QuotedStringTokenizer(value,",");
+                while(tok.hasMoreElements())
+                    values.add(tok.nextToken().trim());
+                
+                break;
+                
+            default:
+                values.add(value);
+        }
+
+        // handle values
+        switch(name)
+        {
+            case ServletContext.ORDERED_LIBS:
+            {
+                List<Object> libs = new ArrayList<>();
+                Object o=context.getAttribute(ServletContext.ORDERED_LIBS);
+                if (o instanceof Collection<?>)
+                    libs.addAll((Collection<?>)o);
+                libs.addAll(values);
+                if (libs.size()>0)
+                    context.setAttribute(ServletContext.ORDERED_LIBS,libs);
+                
+                break;
+            }
+                
+            case AnnotationConfiguration.CONTAINER_INITIALIZERS:
+            {
+                for (String i : values)
+                    visitContainerInitializer(context, new ContainerInitializer(Thread.currentThread().getContextClassLoader(), i));
+                break;
+            }
+            
+            case MetaInfConfiguration.METAINF_TLDS:
+            {
+                List<Object> tlds = new ArrayList<>();
+                String war=context.getBaseResource().getURI().toString();
+                Object o=context.getAttribute(MetaInfConfiguration.METAINF_TLDS);
+                if (o instanceof Collection<?>)
+                    tlds.addAll((Collection<?>)o);
+                for (String i : values)
+                {
+                    Resource r = Resource.newResource(i.replace("${WAR}/",war));
+                    if (r.exists())
+                        tlds.add(r.getURL());
+                    else
+                        throw new IllegalArgumentException("TLD not found: "+r);                    
+                }
+                
+                if (tlds.size()>0)
+                    context.setAttribute(MetaInfConfiguration.METAINF_TLDS,tlds);
+                break;
+            }
+            
+            case MetaInfConfiguration.METAINF_RESOURCES:
+            {
+                String war=context.getBaseResource().getURI().toString();
+                for (String i : values)
+                {
+                    Resource r = Resource.newResource(i.replace("${WAR}/",war));
+                    if (r.exists())
+                        visitMetaInfResource(context,r); 
+                    else
+                        throw new IllegalArgumentException("Resource not found: "+r);                    
+                }
+                break;
+            }
+                
+            default:
+                
+        }
+    }
+    
+
+    public void visitContainerInitializer (WebAppContext context, ContainerInitializer containerInitializer)
+    {
+        if (containerInitializer == null)
+            return;
+        
+        //add the ContainerInitializer to the list of container initializers
+        List<ContainerInitializer> containerInitializers = (List<ContainerInitializer>)context.getAttribute(AnnotationConfiguration.CONTAINER_INITIALIZERS);
+        if (containerInitializers == null)
+        {
+            containerInitializers = new ArrayList<ContainerInitializer>();
+            context.setAttribute(AnnotationConfiguration.CONTAINER_INITIALIZERS, containerInitializers);
+        }
+        
+        containerInitializers.add(containerInitializer);
+
+        //Ensure a bean is set up on the context that will invoke the ContainerInitializers as the context starts
+        ServletContainerInitializersStarter starter = (ServletContainerInitializersStarter)context.getAttribute(AnnotationConfiguration.CONTAINER_INITIALIZER_STARTER);
+        if (starter == null)
+        {
+            starter = new ServletContainerInitializersStarter(context);
+            context.setAttribute(AnnotationConfiguration.CONTAINER_INITIALIZER_STARTER, starter);
+            context.addBean(starter, true);
+        }
+    }
+    
+    
+    public void visitMetaInfResource (WebAppContext context, Resource dir)
+    {
+        Collection<Resource> metaInfResources =  (Collection<Resource>)context.getAttribute(MetaInfConfiguration.METAINF_RESOURCES);
+        if (metaInfResources == null)
+        {
+            metaInfResources = new HashSet<Resource>();
+            context.setAttribute(MetaInfConfiguration.METAINF_RESOURCES, metaInfResources);
+        }
+        metaInfResources.add(dir);
+        //also add to base resource of webapp
+        Resource[] collection=new Resource[metaInfResources.size()+1];
+        int i=0;
+        collection[i++]=context.getBaseResource();
+        for (Resource resource : metaInfResources)
+            collection[i++]=resource;
+        context.setBaseResource(new ResourceCollection(collection));
+    }
+}
diff --git a/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/QuickStartWebApp.java b/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/QuickStartWebApp.java
new file mode 100644
index 0000000..5f60843
--- /dev/null
+++ b/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/QuickStartWebApp.java
@@ -0,0 +1,182 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.quickstart;
+
+import java.io.FileOutputStream;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.JarResource;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.webapp.WebAppContext;
+
+/**
+ * QuickStartWar
+ *
+ */
+public class QuickStartWebApp extends WebAppContext
+{
+    private static final Logger LOG = Log.getLogger(QuickStartWebApp.class);
+    
+    
+    
+    public static final String[] __configurationClasses = new String[] 
+            {
+                org.eclipse.jetty.quickstart.QuickStartConfiguration.class.getCanonicalName(),
+                org.eclipse.jetty.plus.webapp.EnvConfiguration.class.getCanonicalName(),
+                org.eclipse.jetty.plus.webapp.PlusConfiguration.class.getCanonicalName(),
+                org.eclipse.jetty.webapp.JettyWebXmlConfiguration.class.getCanonicalName()
+            };
+    
+    
+    private boolean _preconfigure=false;
+    private boolean _autoPreconfigure=false;
+    private boolean _startWebapp=false;
+    private PreconfigureDescriptorProcessor _preconfigProcessor;
+    
+
+    public static final String[] __preconfigurationClasses = new String[]
+    { 
+        org.eclipse.jetty.webapp.WebInfConfiguration.class.getCanonicalName(), 
+        org.eclipse.jetty.webapp.WebXmlConfiguration.class.getCanonicalName(),
+        org.eclipse.jetty.webapp.MetaInfConfiguration.class.getCanonicalName(), 
+        org.eclipse.jetty.webapp.FragmentConfiguration.class.getCanonicalName(),
+        org.eclipse.jetty.plus.webapp.EnvConfiguration.class.getCanonicalName(), 
+        org.eclipse.jetty.plus.webapp.PlusConfiguration.class.getCanonicalName(),
+        org.eclipse.jetty.annotations.AnnotationConfiguration.class.getCanonicalName(),
+    };
+    
+    public QuickStartWebApp()
+    {
+        super();
+        setConfigurationClasses(__preconfigurationClasses);
+        setAttribute("org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern",".*\\.jar");
+    }
+
+    public boolean isPreconfigure()
+    {
+        return _preconfigure;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Preconfigure webapp
+     * @param preconfigure  If true, then starting the webapp will generate 
+     * the WEB-INF/quickstart-web.xml rather than start the webapp.
+     */
+    public void setPreconfigure(boolean preconfigure)
+    {
+        _preconfigure = preconfigure;
+    }
+
+    public boolean isAutoPreconfigure()
+    {
+        return _autoPreconfigure;
+    }
+    
+    public void setAutoPreconfigure(boolean autoPrecompile)
+    {
+        _autoPreconfigure = autoPrecompile;
+    }
+    
+    @Override
+    protected void startWebapp() throws Exception
+    {
+        if (isPreconfigure())
+            generateQuickstartWebXml(_preconfigProcessor.getXML());
+        
+        if (_startWebapp)
+            super.startWebapp();
+    }
+    
+    @Override
+    protected void doStart() throws Exception
+    {
+        // unpack and Adjust paths.
+        Resource war = null;
+        Resource dir = null;
+
+        Resource base = getBaseResource();
+        if (base==null)
+            base=Resource.newResource(getWar());
+
+        if (base.isDirectory())
+            dir=base;
+        else if (base.toString().toLowerCase().endsWith(".war"))
+        {
+            war=base;
+            String w=war.toString();
+            dir=Resource.newResource(w.substring(0,w.length()-4));
+
+            if (!dir.exists())
+            {                       
+                LOG.info("Quickstart Extract " + war + " to " + dir);
+                dir.getFile().mkdirs();
+                JarResource.newJarResource(war).copyTo(dir.getFile());
+            }
+
+            setWar(null);
+            setBaseResource(dir);
+        }
+        else 
+            throw new IllegalArgumentException();
+
+
+        Resource qswebxml=dir.addPath("/WEB-INF/quickstart-web.xml");
+        
+        if (isPreconfigure())
+        {
+            _preconfigProcessor = new PreconfigureDescriptorProcessor();
+            getMetaData().addDescriptorProcessor(_preconfigProcessor);
+            _startWebapp=false;
+        }
+        else if (qswebxml.exists())
+        {
+            setConfigurationClasses(__configurationClasses);
+            _startWebapp=true;
+        }
+        else if (_autoPreconfigure)
+        {   
+            LOG.info("Quickstart preconfigure: {}(war={},dir={})",this,war,dir);
+
+            _preconfigProcessor = new PreconfigureDescriptorProcessor();    
+            getMetaData().addDescriptorProcessor(_preconfigProcessor);
+            setPreconfigure(true);
+            _startWebapp=true;
+        }
+        else
+            _startWebapp=true;
+            
+        super.doStart();
+    }
+
+
+    public void generateQuickstartWebXml(String extraXML) throws Exception
+    {
+        Resource descriptor = getWebInf().addPath(QuickStartDescriptorGenerator.DEFAULT_QUICKSTART_DESCRIPTOR_NAME);
+        if (!descriptor.exists())
+            descriptor.getFile().createNewFile();
+        QuickStartDescriptorGenerator generator = new QuickStartDescriptorGenerator(this, extraXML);
+        try (FileOutputStream fos = new FileOutputStream(descriptor.getFile()))
+        {
+            generator.generateQuickStartWebXml(fos);
+        }
+    }
+
+  
+}
diff --git a/jetty-rewrite/pom.xml b/jetty-rewrite/pom.xml
index e05b93d..2236505 100644
--- a/jetty-rewrite/pom.xml
+++ b/jetty-rewrite/pom.xml
@@ -2,7 +2,7 @@
   <parent>
     <groupId>org.eclipse.jetty</groupId>
     <artifactId>jetty-project</artifactId>
-    <version>8.1.17.v20150415</version>
+    <version>9.2.13.v20150730</version>
   </parent>
   <modelVersion>4.0.0</modelVersion>
   <artifactId>jetty-rewrite</artifactId>
@@ -25,7 +25,7 @@
             </goals>
             <configuration>
               <instructions>
-                <Import-Package>javax.servlet.*;version="2.6.0",*</Import-Package>
+                <Import-Package>javax.servlet.*;version="[2.6.0,3.2)",*</Import-Package>
               </instructions>
             </configuration>
            </execution>
@@ -71,8 +71,8 @@
   </build>
   <dependencies>
     <dependency>
-      <groupId>junit</groupId>
-      <artifactId>junit</artifactId>
+      <groupId>org.eclipse.jetty.toolchain</groupId>
+      <artifactId>jetty-test-helper</artifactId>
       <scope>test</scope>
     </dependency>
     <dependency>
@@ -86,14 +86,8 @@
       <version>${project.version}</version>
     </dependency>
     <dependency>
-      <groupId>org.eclipse.jetty.orbit</groupId>
-      <artifactId>javax.servlet</artifactId>
-    </dependency>
-    <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>test-jetty-servlet</artifactId>
-      <version>${project.version}</version>
-      <scope>test</scope>
+      <groupId>javax.servlet</groupId>
+      <artifactId>javax.servlet-api</artifactId>
     </dependency>
   </dependencies>
 </project>
diff --git a/jetty-rewrite/src/main/config/etc/jetty-rewrite.xml b/jetty-rewrite/src/main/config/etc/jetty-rewrite.xml
index 1d208f8..706a932 100644
--- a/jetty-rewrite/src/main/config/etc/jetty-rewrite.xml
+++ b/jetty-rewrite/src/main/config/etc/jetty-rewrite.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0"?>
-<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
 
 <!-- =============================================================== -->
 <!-- Mixin the RewriteHandler                                        -->
@@ -9,109 +9,33 @@
 <Configure id="Server" class="org.eclipse.jetty.server.Server">
 
     <!-- =========================================================== -->
-    <!-- configure rewrite handler                                   --> 
+    <!-- configure rewrite handler                                   -->
     <!-- =========================================================== -->
     <Get id="oldhandler" name="handler"/>
 
     <Set name="handler">
      <New id="Rewrite" class="org.eclipse.jetty.rewrite.handler.RewriteHandler">
-      <Set name="handler"><Ref id="oldhandler"/></Set>
-      <Set name="rewriteRequestURI">true</Set>
-      <Set name="rewritePathInfo">false</Set>
-      <Set name="originalPathAttribute">requestedPath</Set>
-
-      <!-- Add rule to protect against IE ssl bug -->
-      <Call name="addRule">
-        <Arg>
-          <New class="org.eclipse.jetty.rewrite.handler.MsieSslRule"/>
-        </Arg>
-      </Call>
-
-      <!-- protect favicon handling -->
-      <Call name="addRule">
-        <Arg>
-          <New class="org.eclipse.jetty.rewrite.handler.HeaderPatternRule">
-            <Set name="pattern">/favicon.ico</Set>
-            <Set name="name">Cache-Control</Set>
-            <Set name="value">Max-Age=3600,public</Set>
-            <Set name="terminating">true</Set>
-          </New>
-        </Arg>
-      </Call>
-
-      <!-- redirect from the welcome page to a specific page -->
-      <Call name="addRule">
-        <Arg>
-          <New class="org.eclipse.jetty.rewrite.handler.RewritePatternRule">
-            <Set name="pattern">/rewrite/</Set>
-            <Set name="replacement">/rewrite/info.html</Set>
-          </New>
-        </Arg>
-      </Call>
-
-      <!-- replace the entire request URI -->
-      <Call name="addRule">
-        <Arg>
-          <New class="org.eclipse.jetty.rewrite.handler.RewritePatternRule">
-            <Set name="pattern">/some/old/context</Set>
-            <Set name="replacement">/rewritten/newcontext</Set>
-          </New>
-        </Arg>
-      </Call>
-
-      <!-- replace the beginning of the request URI -->
-      <Call name="addRule">
-        <Arg>
-          <New class="org.eclipse.jetty.rewrite.handler.RewritePatternRule">
-            <Set name="pattern">/rewrite/for/*</Set>
-            <Set name="replacement">/rewritten/</Set>
-          </New>
-        </Arg>
-      </Call>
-      
-      <!-- reverse the order of the path sections -->
-      <Call name="addRule">
-        <Arg>
-          <New class="org.eclipse.jetty.rewrite.handler.RewriteRegexRule">
-            <Set name="regex">(.*?)/reverse/([^/]*)/(.*)</Set>
-            <Set name="replacement">$1/reverse/$3/$2</Set>
-          </New>
-        </Arg>
-      </Call>
-
-      <!-- add a cookie to each path visited -->
-      <Call name="addRule">
-        <Arg>
-          <New class="org.eclipse.jetty.rewrite.handler.CookiePatternRule">
-            <Set name="pattern">/*</Set>
-            <Set name="name">visited</Set>
-            <Set name="value">yes</Set>
-          </New>
-        </Arg>
-      </Call>
-      
-      <!--  actual redirect, instead of internal rewrite -->
-      <Call name="addRule">
-        <Arg>
-          <New class="org.eclipse.jetty.rewrite.handler.RedirectPatternRule">
-            <Set name="pattern">/redirect/*</Set>
-            <Set name="location">/redirected</Set>
-          </New>
-        </Arg>
-      </Call>
-
-      <!-- add a response rule -->
-      <Call name="addRule">
-        <Arg>
-           <New class="org.eclipse.jetty.rewrite.handler.ResponsePatternRule">
-             <Set name="pattern">/400Error</Set>
-             <Set name="code">400</Set>
-             <Set name="reason">ResponsePatternRule Demo</Set>
-          </New>
-        </Arg>
-      </Call>
-
+      <Set name="handler"><Ref refid="oldhandler"/></Set>
+      <Set name="rewriteRequestURI"><Property name="rewrite.rewriteRequestURI" default="true"/></Set>
+      <Set name="rewritePathInfo"><Property name="rewrite.rewritePathInfo" default="false"/></Set>
+      <Set name="originalPathAttribute"><Property name="rewrite.originalPathAttribute" default="requestedPath"/></Set>
      </New>
     </Set>
-    
+
+    <!-- example rule -->
+    <!--
+    <Call name="addRule">
+      <Arg>
+	<New class="org.eclipse.jetty.rewrite.handler.HeaderPatternRule">
+	  <Set name="pattern">/favicon.ico</Set>
+	  <Set name="name">Cache-Control</Set>
+	  <Set name="value">Max-Age=3600,public</Set>
+	  <Set name="terminating">true</Set>
+	</New>
+      </Arg>
+    </Call>
+    -->
+
+    <!-- for example rules see jetty-demo.xml -->
+
 </Configure>
diff --git a/jetty-rewrite/src/main/config/modules/rewrite.mod b/jetty-rewrite/src/main/config/modules/rewrite.mod
new file mode 100644
index 0000000..d2e00c8
--- /dev/null
+++ b/jetty-rewrite/src/main/config/modules/rewrite.mod
@@ -0,0 +1,12 @@
+#
+# Jetty Rewrite module
+#
+
+[depend]
+server
+
+[lib]
+lib/jetty-rewrite-${jetty.version}.jar
+
+[xml]
+etc/jetty-rewrite.xml
diff --git a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/CompactPathRule.java b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/CompactPathRule.java
new file mode 100644
index 0000000..7ac3069
--- /dev/null
+++ b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/CompactPathRule.java
@@ -0,0 +1,56 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.rewrite.handler;
+
+import java.io.IOException;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.util.URIUtil;
+
+/**
+ * Rewrite the URI by compacting to remove // 
+ */
+public class CompactPathRule extends Rule implements Rule.ApplyURI
+{
+    public CompactPathRule()
+    {
+        _handling = false;
+        _terminating = false;
+    }
+
+    @Override
+    public void applyURI(Request request, String oldURI, String newURI) throws IOException
+    {
+        String uri = request.getRequestURI();
+        if (uri.startsWith("/"))
+            uri = URIUtil.compactPath(uri);
+        request.setRequestURI(uri);
+    }
+
+    @Override
+    public String matchAndApply(String target, HttpServletRequest request, HttpServletResponse response) throws IOException
+    {
+        if (target.startsWith("/"))
+            return URIUtil.compactPath(target);
+        return target;
+    }
+}
diff --git a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/CookiePatternRule.java b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/CookiePatternRule.java
index 4d0855d..62d3906 100644
--- a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/CookiePatternRule.java
+++ b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/CookiePatternRule.java
@@ -70,8 +70,21 @@ public class CookiePatternRule extends PatternRule
      * (non-Javadoc)
      * @see org.eclipse.jetty.server.server.handler.rules.RuleBase#apply(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
      */
+    @Override
     public String apply(String target, HttpServletRequest request, HttpServletResponse response) throws IOException
     {
+        // Check that cookie is not already set
+        Cookie[] cookies = request.getCookies();
+        if (cookies!=null)
+        {
+            for (Cookie cookie:cookies)
+            {
+                if (_name.equals(cookie.getName()) && _value.equals(cookie.getValue()))
+                    return target;
+            }
+        }
+        
+        // set it
         response.addCookie(new Cookie(_name, _value));
         return target;
     }
@@ -80,6 +93,7 @@ public class CookiePatternRule extends PatternRule
     /**
      * Returns the cookie contents.
      */
+    @Override
     public String toString()
     {
         return super.toString()+"["+_name+","+_value + "]";
diff --git a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/ForwardedSchemeHeaderRule.java b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/ForwardedSchemeHeaderRule.java
index 5c94194..01dc6ce 100644
--- a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/ForwardedSchemeHeaderRule.java
+++ b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/ForwardedSchemeHeaderRule.java
@@ -50,6 +50,7 @@ public class ForwardedSchemeHeaderRule extends HeaderRule {
     }
     
     /* ------------------------------------------------------------ */
+    @Override
     protected String apply(String target, String value, HttpServletRequest request, HttpServletResponse response) 
     {
         ((Request) request).setScheme(_scheme);
diff --git a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/HeaderPatternRule.java b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/HeaderPatternRule.java
index c0343b0..d16ec54 100644
--- a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/HeaderPatternRule.java
+++ b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/HeaderPatternRule.java
@@ -80,6 +80,7 @@ public class HeaderPatternRule extends PatternRule
      * 
      *@see org.eclipse.jetty.rewrite.handler.Rule#matchAndApply(String, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
      */
+    @Override
     public String apply(String target, HttpServletRequest request, HttpServletResponse response) throws IOException
     {
         // process header
@@ -125,6 +126,7 @@ public class HeaderPatternRule extends PatternRule
     /**
      * Returns the header contents.
      */
+    @Override
     public String toString()
     {
         return super.toString()+"["+_name+","+_value+"]";
diff --git a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/HeaderRegexRule.java b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/HeaderRegexRule.java
new file mode 100644
index 0000000..8023070
--- /dev/null
+++ b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/HeaderRegexRule.java
@@ -0,0 +1,128 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.rewrite.handler;
+
+import java.io.IOException;
+import java.util.regex.Matcher;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+
+/* ------------------------------------------------------------ */
+/** Rule to add a header based on a Regex match
+ */
+public class HeaderRegexRule extends RegexRule
+{
+
+    private String _name;
+    private String _value;
+    private boolean _add=false;
+
+    /* ------------------------------------------------------------ */
+    public HeaderRegexRule()
+    {
+        _handling = false;
+        _terminating = false;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Sets the header name.
+     * 
+     * @param name name of the header field
+     */
+    public void setName(String name)
+    {
+        _name = name;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Sets the header value. The value can be either a <code>String</code> or <code>int</code> value.
+     * 
+     * @param value of the header field
+     */
+    public void setValue(String value)
+    {
+        _value = value;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Sets the Add flag. 
+     * @param add If true, the header is added to the response, otherwise the header it is set on the response.
+     */
+    public void setAdd(boolean add)
+    {
+        _add = add;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected String apply(String target, HttpServletRequest request, HttpServletResponse response, Matcher matcher)
+            throws IOException 
+    {
+        // process header
+        if (_add)
+            response.addHeader(_name, _value);
+        else
+            response.setHeader(_name, _value); 
+        return target;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns the header name.
+     * @return the header name.
+     */
+    public String getName()
+    {
+        return _name;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns the header value.
+     * @return the header value.
+     */
+    public String getValue()
+    {
+        return _value;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns the add flag value.
+     */
+    public boolean isAdd()
+    {
+        return _add;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns the header contents.
+     */
+    @Override
+    public String toString()
+    {
+        return super.toString()+"["+_name+","+_value+"]";
+    }
+}
diff --git a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/HeaderRule.java b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/HeaderRule.java
index de6e2e6..5ad1c81 100644
--- a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/HeaderRule.java
+++ b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/HeaderRule.java
@@ -99,6 +99,7 @@ public abstract class HeaderRule extends Rule
     protected abstract String apply(String target, String value, HttpServletRequest request, HttpServletResponse response) throws IOException;
 
     /* ------------------------------------------------------------ */
+    @Override
     public String toString()
     {
         return super.toString() + "[" + _header + ":" + _headerValue + "]";
diff --git a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/MsieSslRule.java b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/MsieSslRule.java
index 257c2b4..0e2e0bc 100644
--- a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/MsieSslRule.java
+++ b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/MsieSslRule.java
@@ -23,9 +23,10 @@ import java.io.IOException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import org.eclipse.jetty.http.HttpHeaderValues;
-import org.eclipse.jetty.http.HttpHeaders;
-import org.eclipse.jetty.util.StringMap;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpHeaderValue;
+import org.eclipse.jetty.util.ArrayTernaryTrie;
+import org.eclipse.jetty.util.Trie;
 
 /**
  * MSIE (Microsoft Internet Explorer) SSL Rule.
@@ -38,7 +39,7 @@ public class MsieSslRule extends Rule
 {
     private static final int IEv5 = '5';
     private static final int IEv6 = '6';
-    private static StringMap __IE6_BadOS = new StringMap();
+    private static Trie<Boolean> __IE6_BadOS = new ArrayTernaryTrie<>();
     {
         __IE6_BadOS.put("NT 5.01", Boolean.TRUE);
         __IE6_BadOS.put("NT 5.0",Boolean.TRUE);
@@ -55,11 +56,12 @@ public class MsieSslRule extends Rule
         _terminating = false;
     }
     
+    @Override
     public String matchAndApply(String target, HttpServletRequest request, HttpServletResponse response) throws IOException
     {
         if (request.isSecure())
         {
-            String user_agent = request.getHeader(HttpHeaders.USER_AGENT);
+            String user_agent = request.getHeader(HttpHeader.USER_AGENT.asString());
             
             if (user_agent!=null)
             {
@@ -71,7 +73,7 @@ public class MsieSslRule extends Rule
                     
                     if ( ieVersion<=IEv5)
                     {
-                        response.setHeader(HttpHeaders.CONNECTION, HttpHeaderValues.CLOSE);
+                        response.setHeader(HttpHeader.CONNECTION.asString(), HttpHeaderValue.CLOSE.asString());
                         return target;
                     }
 
@@ -81,9 +83,9 @@ public class MsieSslRule extends Rule
                         if (windows>0)
                         {
                             int end=user_agent.indexOf(')',windows+8);
-                            if(end<0 || __IE6_BadOS.getEntry(user_agent,windows+8,end-windows-8)!=null)
+                            if(end<0 || __IE6_BadOS.get(user_agent,windows+8,end-windows-8)!=null)
                             {
-                                response.setHeader(HttpHeaders.CONNECTION, HttpHeaderValues.CLOSE);
+                                response.setHeader(HttpHeader.CONNECTION.asString(), HttpHeaderValue.CLOSE.asString());
                                 return target;
                             }
                         }
diff --git a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/PatternRule.java b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/PatternRule.java
index a12067b..637c669 100644
--- a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/PatternRule.java
+++ b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/PatternRule.java
@@ -55,6 +55,7 @@ public abstract class PatternRule extends Rule
     /* (non-Javadoc)
      * @see org.eclipse.jetty.server.server.handler.rules.RuleBase#matchAndApply(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
      */
+    @Override
     public String matchAndApply(String target, HttpServletRequest request, HttpServletResponse response) throws IOException
     {
         if (PathMap.match(_pattern, target))
@@ -77,6 +78,7 @@ public abstract class PatternRule extends Rule
     /**
      * Returns the rule pattern.
      */
+    @Override
     public String toString()
     {
         return super.toString()+"["+_pattern+"]";                
diff --git a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/ProxyRule.java b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/ProxyRule.java
deleted file mode 100644
index 44c76d3..0000000
--- a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/ProxyRule.java
+++ /dev/null
@@ -1,503 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.rewrite.handler;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.MalformedURLException;
-import java.util.Enumeration;
-import java.util.HashSet;
-import java.util.Locale;
-
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jetty.client.HttpClient;
-import org.eclipse.jetty.client.HttpExchange;
-import org.eclipse.jetty.http.HttpHeaderValues;
-import org.eclipse.jetty.http.HttpHeaders;
-import org.eclipse.jetty.http.HttpURI;
-import org.eclipse.jetty.http.PathMap;
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.EofException;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.util.thread.QueuedThreadPool;
-
-/**
- * This rule allows the user to configure a particular rewrite rule that will proxy out
- * to a configured location.  This rule uses the jetty http client.
- * 
- * Rule rule = new ProxyRule();
- * rule.setPattern("/foo/*");
- * rule.setProxyTo("http://url.com");
- * 
- * see api for other configuration options which influence the configuration of the jetty 
- * client instance
- * 
- */
-public class ProxyRule extends PatternRule
-{
-    private static final Logger _log = Log.getLogger(ProxyRule.class);
-
-    private HttpClient _client;
-    private String _hostHeader;
-    private String _proxyTo;
-    
-    private int _connectorType = HttpClient.CONNECTOR_SELECT_CHANNEL;
-    private String _maxThreads;
-    private String _maxConnections;
-    private String _timeout;
-    private String _idleTimeout;
-    private String _requestHeaderSize;
-    private String _requestBufferSize;
-    private String _responseHeaderSize;
-    private String _responseBufferSize;
-
-    private HashSet<String> _DontProxyHeaders = new HashSet<String>();
-    {
-        _DontProxyHeaders.add("proxy-connection");
-        _DontProxyHeaders.add("connection");
-        _DontProxyHeaders.add("keep-alive");
-        _DontProxyHeaders.add("transfer-encoding");
-        _DontProxyHeaders.add("te");
-        _DontProxyHeaders.add("trailer");
-        _DontProxyHeaders.add("proxy-authorization");
-        _DontProxyHeaders.add("proxy-authenticate");
-        _DontProxyHeaders.add("upgrade");
-    }
-
-    /* ------------------------------------------------------------ */
-    public ProxyRule()
-    {
-        _handling = true;
-        _terminating = true;
-    }
-
-    /* ------------------------------------------------------------ */
-    private void initializeClient() throws Exception
-    {
-        _client = new HttpClient();
-        _client.setConnectorType(_connectorType);
-        
-        if ( _maxThreads != null )
-        {
-            _client.setThreadPool(new QueuedThreadPool(Integer.parseInt(_maxThreads)));
-        }
-        else
-        {
-            _client.setThreadPool(new QueuedThreadPool());
-        }
-        
-        if ( _maxConnections != null )
-        {
-            _client.setMaxConnectionsPerAddress(Integer.parseInt(_maxConnections));
-        }
-        
-        if ( _timeout != null )
-        {
-            _client.setTimeout(Long.parseLong(_timeout));
-        }
-        
-        if ( _idleTimeout != null )
-        {
-            _client.setIdleTimeout(Long.parseLong(_idleTimeout));
-        }
-        
-        if ( _requestBufferSize != null )
-        {
-            _client.setRequestBufferSize(Integer.parseInt(_requestBufferSize));
-        }
-        
-        if ( _requestHeaderSize != null )
-        {
-            _client.setRequestHeaderSize(Integer.parseInt(_requestHeaderSize));
-        }
-        
-        if ( _responseBufferSize != null )
-        {
-            _client.setResponseBufferSize(Integer.parseInt(_responseBufferSize));
-        }
-        
-        if ( _responseHeaderSize != null )
-        {
-            _client.setResponseHeaderSize(Integer.parseInt(_responseHeaderSize));
-        }                 
-        
-        _client.start();
-    }
-
-    /* ------------------------------------------------------------ */
-    private HttpURI proxyHttpURI(String uri) throws MalformedURLException
-    {
-        return new HttpURI(_proxyTo + uri);
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    protected String apply(String target, HttpServletRequest request, final HttpServletResponse response) throws IOException
-    {
-        synchronized (this)
-        {
-            if (_client == null)
-            {
-                try
-                {
-                    initializeClient();
-                }
-                catch (Exception e)
-                {
-                    throw new IOException("Unable to proxy: " + e.getMessage());
-                }
-            }
-        }
-
-        final int debug = _log.isDebugEnabled()?request.hashCode():0;
-
-        final InputStream in = request.getInputStream();
-        final OutputStream out = response.getOutputStream();
-
-        HttpURI url = createUrl(request,debug);
-
-        if (url == null)
-        {
-            response.sendError(HttpServletResponse.SC_FORBIDDEN);
-            return target;
-        }
-
-        HttpExchange exchange = new HttpExchange()
-        {
-            @Override
-            protected void onRequestCommitted() throws IOException
-            {
-            }
-
-            @Override
-            protected void onRequestComplete() throws IOException
-            {
-            }
-
-            @Override
-            protected void onResponseComplete() throws IOException
-            {
-                if (debug != 0)
-                    _log.debug(debug + " complete");
-            }
-
-            @Override
-            protected void onResponseContent(Buffer content) throws IOException
-            {
-                if (debug != 0)
-                    _log.debug(debug + " content" + content.length());
-                content.writeTo(out);
-            }
-
-            @Override
-            protected void onResponseHeaderComplete() throws IOException
-            {
-            }
-
-            @SuppressWarnings("deprecation")
-            @Override
-            protected void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException
-            {
-                if (debug != 0)
-                    _log.debug(debug + " " + version + " " + status + " " + reason);
-
-                if (reason != null && reason.length() > 0)
-                    response.setStatus(status,reason.toString());
-                else
-                    response.setStatus(status);
-            }
-
-            @Override
-            protected void onResponseHeader(Buffer name, Buffer value) throws IOException
-            {
-                String s = name.toString().toLowerCase(Locale.ENGLISH);
-                if (!_DontProxyHeaders.contains(s) || (HttpHeaders.CONNECTION_BUFFER.equals(name) && HttpHeaderValues.CLOSE_BUFFER.equals(value)))
-                {
-                    if (debug != 0)
-                        _log.debug(debug + " " + name + ": " + value);
-
-                    response.addHeader(name.toString(),value.toString());
-                }
-                else if (debug != 0)
-                    _log.debug(debug + " " + name + "! " + value);
-            }
-
-            @Override
-            protected void onConnectionFailed(Throwable ex)
-            {
-                _log.warn(ex.toString());
-                _log.debug(ex);
-                if (!response.isCommitted())
-                {
-                    response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
-                }
-            }
-
-            @Override
-            protected void onException(Throwable ex)
-            {
-                if (ex instanceof EofException)
-                {
-                    _log.ignore(ex);
-                    return;
-                }
-                _log.warn(ex.toString());
-                _log.debug(ex);
-                if (!response.isCommitted())
-                {
-                    response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
-                }
-            }
-
-            @Override
-            protected void onExpire()
-            {
-                if (!response.isCommitted())
-                {
-                    response.setStatus(HttpServletResponse.SC_GATEWAY_TIMEOUT);
-                }
-            }
-
-        };
-
-        exchange.setMethod(request.getMethod());
-        exchange.setURL(url.toString());
-        exchange.setVersion(request.getProtocol());
-
-        if (debug != 0)
-        {
-            _log.debug("{} {} {} {}", debug ,request.getMethod(), url, request.getProtocol());
-        }
-        
-        boolean hasContent = createHeaders(request,debug,exchange);
-
-        if (hasContent)
-        {
-            exchange.setRequestContentSource(in);
-        }
-        
-        /*
-         * we need to set the timeout on the exchange to take into account the timeout of the HttpClient and the HttpExchange
-         */
-        long ctimeout = (_client.getTimeout() > exchange.getTimeout())?_client.getTimeout():exchange.getTimeout();
-        exchange.setTimeout(ctimeout);
-
-        _client.send(exchange);
-        
-        try
-        {
-            exchange.waitForDone();
-        }
-        catch (InterruptedException e)
-        {
-            _log.info("Exception while waiting for response on proxied request", e);
-        }
-        return target;
-    }
-
-    /* ------------------------------------------------------------ */
-    private HttpURI createUrl(HttpServletRequest request, final int debug) throws MalformedURLException
-    {
-        String uri = request.getRequestURI();
-        
-        if (request.getQueryString() != null)
-        {
-            uri += "?" + request.getQueryString();
-        }
-        
-        uri = PathMap.pathInfo(_pattern,uri);
-        
-        if(uri==null)
-        {
-            uri = "/";
-        }
-        
-        HttpURI url = proxyHttpURI(uri);
-
-        if (debug != 0)
-        {
-            _log.debug(debug + " proxy " + uri + "-->" + url);
-        }
-        
-        return url;
-    }
-
-    /* ------------------------------------------------------------ */
-    private boolean createHeaders(final HttpServletRequest request, final int debug, HttpExchange exchange)
-    {
-        // check connection header
-        String connectionHdr = request.getHeader("Connection");
-        if (connectionHdr != null)
-        {
-            connectionHdr = connectionHdr.toLowerCase(Locale.ENGLISH);
-            if (connectionHdr.indexOf("keep-alive") < 0 && connectionHdr.indexOf("close") < 0)
-            {
-                connectionHdr = null;
-            }
-        }
-
-        // force host
-        if (_hostHeader != null)
-        {
-            exchange.setRequestHeader("Host",_hostHeader);
-        }
-
-        // copy headers
-        boolean xForwardedFor = false;
-        boolean hasContent = false;
-        long contentLength = -1;
-        Enumeration<?> enm = request.getHeaderNames();
-        while (enm.hasMoreElements())
-        {
-            // TODO could be better than this!
-            String hdr = (String)enm.nextElement();
-            String lhdr = hdr.toLowerCase(Locale.ENGLISH);
-
-            if (_DontProxyHeaders.contains(lhdr))
-                continue;
-            if (connectionHdr != null && connectionHdr.indexOf(lhdr) >= 0)
-                continue;
-            if (_hostHeader != null && "host".equals(lhdr))
-                continue;
-
-            if ("content-type".equals(lhdr))
-                hasContent = true;
-            else if ("content-length".equals(lhdr))
-            {
-                contentLength = request.getContentLength();
-                exchange.setRequestHeader(HttpHeaders.CONTENT_LENGTH,Long.toString(contentLength));
-                if (contentLength > 0)
-                    hasContent = true;
-            }
-            else if ("x-forwarded-for".equals(lhdr))
-                xForwardedFor = true;
-
-            Enumeration<?> vals = request.getHeaders(hdr);
-            while (vals.hasMoreElements())
-            {
-                String val = (String)vals.nextElement();
-                if (val != null)
-                {
-                    if (debug != 0)
-                        _log.debug("{} {} {}",debug,hdr,val);
-
-                    exchange.setRequestHeader(hdr,val);
-                }
-            }
-        }
-
-        // Proxy headers
-        exchange.setRequestHeader("Via","1.1 (jetty)");
-        if (!xForwardedFor)
-        {
-            exchange.addRequestHeader("X-Forwarded-For",request.getRemoteAddr());
-            exchange.addRequestHeader("X-Forwarded-Proto",request.getScheme());
-            exchange.addRequestHeader("X-Forwarded-Host",request.getServerName());
-            exchange.addRequestHeader("X-Forwarded-Server",request.getLocalName());
-        }
-        return hasContent;
-    }
-
-    /* ------------------------------------------------------------ */
-    public void setProxyTo(String proxyTo)
-    {
-        this._proxyTo = proxyTo;
-    }
-    
-    /* ------------------------------------------------------------ */
-    public void setMaxThreads(String maxThreads)
-    {
-        this._maxThreads = maxThreads;
-    }
-    
-    /* ------------------------------------------------------------ */
-    public void setMaxConnections(String maxConnections)
-    {
-        _maxConnections = maxConnections;
-    }
-    
-    /* ------------------------------------------------------------ */
-    public void setTimeout(String timeout)
-    {
-        _timeout = timeout;
-    }
-    
-    /* ------------------------------------------------------------ */
-    public void setIdleTimeout(String idleTimeout)
-    {
-        _idleTimeout = idleTimeout;
-    }
-    
-    /* ------------------------------------------------------------ */
-    public void setRequestHeaderSize(String requestHeaderSize)
-    {
-        _requestHeaderSize = requestHeaderSize;
-    }
-    
-    /* ------------------------------------------------------------ */
-    public void setRequestBufferSize(String requestBufferSize)
-    {
-        _requestBufferSize = requestBufferSize;
-    }
-    
-    /* ------------------------------------------------------------ */
-    public void setResponseHeaderSize(String responseHeaderSize)
-    {
-        _responseHeaderSize = responseHeaderSize;
-    }
-    
-    /* ------------------------------------------------------------ */
-    public void setResponseBufferSize(String responseBufferSize)
-    {
-        _responseBufferSize = responseBufferSize;
-    }
-    
-    /* ------------------------------------------------------------ */
-    public void addDontProxyHeaders(String dontProxyHeader)
-    {
-        _DontProxyHeaders.add(dontProxyHeader);
-    }
-    
-    /* ------------------------------------------------------------ */
-    /**
-     *   CONNECTOR_SOCKET = 0;
-     *   CONNECTOR_SELECT_CHANNEL = 2; (default)
-     * 
-     * @param connectorType
-     */
-    public void setConnectorType( int connectorType )
-    {
-        _connectorType = connectorType;
-    }
-
-    public String getHostHeader()
-    {
-        return _hostHeader;
-    }
-
-    public void setHostHeader(String hostHeader)
-    {
-        _hostHeader = hostHeader;
-    }
-    
-}
diff --git a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RedirectPatternRule.java b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RedirectPatternRule.java
index 2c38338..de434a0 100644
--- a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RedirectPatternRule.java
+++ b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RedirectPatternRule.java
@@ -53,6 +53,7 @@ public class RedirectPatternRule extends PatternRule
      * (non-Javadoc)
      * @see org.eclipse.jetty.server.server.handler.rules.RuleBase#apply(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
      */
+    @Override
     public String apply(String target, HttpServletRequest request, HttpServletResponse response) throws IOException
     {
         response.sendRedirect(response.encodeRedirectURL(_location));
@@ -63,6 +64,7 @@ public class RedirectPatternRule extends PatternRule
     /**
      * Returns the redirect location.
      */
+    @Override
     public String toString()
     {
         return super.toString()+"["+_location+"]";
diff --git a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RegexRule.java b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RegexRule.java
index 083598a..34abbee 100644
--- a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RegexRule.java
+++ b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RegexRule.java
@@ -55,6 +55,7 @@ public abstract class RegexRule extends Rule
     
 
     /* ------------------------------------------------------------ */
+    @Override
     public String matchAndApply(String target, HttpServletRequest request, HttpServletResponse response) throws IOException
     {
         Matcher matcher=_regex.matcher(target);
@@ -82,6 +83,7 @@ public abstract class RegexRule extends Rule
     /**
      * Returns the regular expression string.
      */
+    @Override
     public String toString()
     {
         return super.toString()+"["+_regex+"]";
diff --git a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/ResponsePatternRule.java b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/ResponsePatternRule.java
index bc3cf5f..01d7c7b 100644
--- a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/ResponsePatternRule.java
+++ b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/ResponsePatternRule.java
@@ -65,6 +65,7 @@ public class ResponsePatternRule extends PatternRule
      * (non-Javadoc)
      * @see org.eclipse.jetty.server.server.handler.rules.RuleBase#apply(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
      */
+    @Override
     public String apply(String target, HttpServletRequest request, HttpServletResponse response) throws IOException
     {
         int code = Integer.parseInt(_code);
@@ -85,6 +86,7 @@ public class ResponsePatternRule extends PatternRule
     /**
      * Returns the code and reason string.
      */
+    @Override
     public String toString()
     {
         return super.toString()+"["+_code+","+_reason+"]";
diff --git a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RewriteHandler.java b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RewriteHandler.java
index b933f6e..495aea7 100644
--- a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RewriteHandler.java
+++ b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RewriteHandler.java
@@ -19,7 +19,10 @@
 package org.eclipse.jetty.rewrite.handler;
 
 import java.io.IOException;
+import java.util.Arrays;
+import java.util.EnumSet;
 
+import javax.servlet.DispatcherType;
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
@@ -50,7 +53,6 @@ import org.eclipse.jetty.server.handler.HandlerWrapper;
  * <li> {@link ResponsePatternRule} - sets the status/error codes. </li>
  * <li> {@link RewritePatternRule} - rewrites the requested URI. </li>
  * <li> {@link RewriteRegexRule} - rewrites the requested URI using regular expression for pattern matching. </li>
- * <li> {@link ProxyRule} - proxies the requested URI to the host defined in proxyTo. </li>
  * <li> {@link MsieSslRule} - disables the keep alive on SSL for IE5 and IE6. </li>
  * <li> {@link LegacyRule} - the old version of rewrite. </li>
  * <li> {@link ForwardedSchemeHeaderRule} - set the scheme according to the headers present. </li>
@@ -72,13 +74,6 @@ import org.eclipse.jetty.server.handler.HandlerWrapper;
  *           </Item>
  * 
  *           <Item>
- *             <New id="rewrite" class="org.eclipse.jetty.rewrite.handler.ProxyRule">
- *               <Set name="pattern">/*</Set>
- *               <Set name="proxyTo">http://webtide.com:8080</Set>
- *             </New>
- *           </Item>
- * 
- *           <Item>
  *             <New id="response" class="org.eclipse.jetty.rewrite.handler.ResponsePatternRule">
  *               <Set name="pattern">/session/</Set>
  *               <Set name="code">400</Set>
@@ -178,8 +173,8 @@ import org.eclipse.jetty.server.handler.HandlerWrapper;
  */
 public class RewriteHandler extends HandlerWrapper
 {
-
     private RuleContainer _rules;
+    private EnumSet<DispatcherType> _dispatchTypes = EnumSet.of(DispatcherType.REQUEST);
 
     /* ------------------------------------------------------------ */
     public RewriteHandler()
@@ -300,6 +295,23 @@ public class RewriteHandler extends HandlerWrapper
         _rules.setOriginalPathAttribute(originalPathAttribute);
     }
 
+    /* ------------------------------------------------------------ */
+    public EnumSet<DispatcherType> getDispatcherTypes()
+    {
+        return _dispatchTypes;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public void setDispatcherTypes(EnumSet<DispatcherType> types)
+    {
+        _dispatchTypes=EnumSet.copyOf(types);
+    }
+    
+    /* ------------------------------------------------------------ */
+    public void setDispatcherTypes(DispatcherType... types)
+    {
+        _dispatchTypes=EnumSet.copyOf(Arrays.asList(types));
+    }
 
     /* ------------------------------------------------------------ */
     /* (non-Javadoc)
@@ -310,8 +322,11 @@ public class RewriteHandler extends HandlerWrapper
     {
         if (isStarted())
         {
-            String returned = _rules.matchAndApply(target, request, response);
-            target = (returned == null) ? target : returned;
+            if (_dispatchTypes.contains(baseRequest.getDispatcherType()))
+            {
+                String returned = _rules.matchAndApply(target, request, response);
+                target = (returned == null) ? target : returned;
+            }
 
             if (!baseRequest.isHandled())
                 super.handle(target, baseRequest, request, response);
diff --git a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RewritePatternRule.java b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RewritePatternRule.java
index 02be3c0..80d6186 100644
--- a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RewritePatternRule.java
+++ b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RewritePatternRule.java
@@ -23,16 +23,18 @@ import java.io.IOException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
+import org.eclipse.jetty.http.HttpURI;
 import org.eclipse.jetty.http.PathMap;
 import org.eclipse.jetty.server.Request;
 import org.eclipse.jetty.util.URIUtil;
 
 /**
- * Rewrite the URI by replacing the matched {@link PathMap} path with a fixed string. 
+ * Rewrite the URI by replacing the matched {@link PathMap} path with a fixed string.
  */
 public class RewritePatternRule extends PatternRule implements Rule.ApplyURI
 {
     private String _replacement;
+    private String _query;
 
     /* ------------------------------------------------------------ */
     public RewritePatternRule()
@@ -44,36 +46,69 @@ public class RewritePatternRule extends PatternRule implements Rule.ApplyURI
     /* ------------------------------------------------------------ */
     /**
      * Whenever a match is found, it replaces with this value.
-     * 
-     * @param value the replacement string.
+     *
+     * @param replacement the replacement string.
      */
-    public void setReplacement(String value)
+    public void setReplacement(String replacement)
     {
-        _replacement = value;
+        String[] split = replacement.split("\\?", 2);
+        _replacement = split[0];
+        _query = split.length == 2 ? split[1] : null;
     }
 
     /* ------------------------------------------------------------ */
     /*
      * (non-Javadoc)
-     * @see org.eclipse.jetty.server.handler.rules.RuleBase#apply(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
+     *
+     * @see org.eclipse.jetty.server.handler.rules.RuleBase#apply(javax.servlet.http.HttpServletRequest,
+     * javax.servlet.http.HttpServletResponse)
      */
+    @Override
     public String apply(String target, HttpServletRequest request, HttpServletResponse response) throws IOException
     {
-        target = URIUtil.addPaths(_replacement, PathMap.pathInfo(_pattern,target));   
+        target = URIUtil.addPaths(_replacement, PathMap.pathInfo(_pattern, target));
         return target;
     }
 
     /* ------------------------------------------------------------ */
-    public void applyURI(Request request, String oldTarget, String newTarget) throws IOException 
+    /**
+     * This method will add _query to the requests's queryString and also combine it with existing queryStrings in
+     * the request. However it won't take care for duplicate. E.g. if request.getQueryString contains a parameter
+     * "param1 = true" and _query will contain "param1=false" the result will be param1=true&param1=false.
+     * To cover this use case some more complex pattern matching is necessary. We can implement this if there's use
+     * cases.
+     *
+     * @param request
+     * @param oldURI
+     * @param newURI
+     * @throws IOException
+     */
+    @Override
+    public void applyURI(Request request, String oldURI, String newURI) throws IOException
     {
-        String uri = URIUtil.addPaths(_replacement, PathMap.pathInfo(_pattern,request.getRequestURI()));
-        request.setRequestURI(uri);
+        if (_query == null)
+        {
+            request.setRequestURI(newURI);
+        }
+        else
+        {
+            String queryString = request.getQueryString();
+            if (queryString != null)
+                queryString = queryString + "&" + _query;
+            else
+                queryString = _query;
+            HttpURI uri = new HttpURI(newURI + "?" + queryString);
+            request.setUri(uri);
+            request.setRequestURI(newURI);
+            request.setQueryString(queryString);
+        }
     }
 
     /* ------------------------------------------------------------ */
     /**
      * Returns the replacement string.
      */
+    @Override
     public String toString()
     {
         return super.toString()+"["+_replacement+"]";
diff --git a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RewriteRegexRule.java b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RewriteRegexRule.java
index 0a56f59..30296bd 100644
--- a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RewriteRegexRule.java
+++ b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RewriteRegexRule.java
@@ -67,6 +67,7 @@ public class RewriteRegexRule extends RegexRule  implements Rule.ApplyURI
     /* (non-Javadoc)
      * @see org.eclipse.jetty.server.handler.rules.RegexRule#apply(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, java.util.regex.Matcher)
      */
+    @Override
     public String apply(String target, HttpServletRequest request, HttpServletResponse response, Matcher matcher) throws IOException
     {
         target=_replacement;
@@ -94,11 +95,12 @@ public class RewriteRegexRule extends RegexRule  implements Rule.ApplyURI
     }
 
     /* ------------------------------------------------------------ */
-    public void applyURI(Request request, String oldTarget, String newTarget) throws IOException
+    @Override
+    public void applyURI(Request request, String oldURI, String newURI) throws IOException
     {
         if (_query==null)
         {
-            request.setRequestURI(newTarget);
+            request.setRequestURI(newURI);
         }
         else
         {
@@ -106,9 +108,9 @@ public class RewriteRegexRule extends RegexRule  implements Rule.ApplyURI
             
             if (!_queryGroup && request.getQueryString()!=null)
                 query=request.getQueryString()+"&"+query;
-            HttpURI uri=new HttpURI(newTarget+"?"+query);
+            HttpURI uri=new HttpURI(newURI+"?"+query);
             request.setUri(uri);
-            request.setRequestURI(newTarget);
+            request.setRequestURI(newURI);
             request.setQueryString(query);
         }
     }
@@ -117,6 +119,7 @@ public class RewriteRegexRule extends RegexRule  implements Rule.ApplyURI
     /**
      * Returns the replacement string.
      */
+    @Override
     public String toString()
     {
         return super.toString()+"["+_replacement+"]";
diff --git a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/Rule.java b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/Rule.java
index 959153b..d3e5ffc 100644
--- a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/Rule.java
+++ b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/Rule.java
@@ -35,7 +35,7 @@ public abstract class Rule
      */
     public interface ApplyURI
     {
-        void applyURI(Request request, String oldTarget, String newTarget) throws IOException;   
+        void applyURI(Request request, String oldURI, String newURI) throws IOException;   
     }
     
     protected boolean _terminating;
@@ -49,7 +49,7 @@ public abstract class Rule
      * @param response
      * 
      * @return The new target if the rule has matched, else null
-     * @throws IOException TODO
+     * @throws IOException
      */
     public abstract String matchAndApply(String target, HttpServletRequest request, HttpServletResponse response) throws IOException;   
     
@@ -97,6 +97,7 @@ public abstract class Rule
     /**
      * Returns the handling and terminating flag values.
      */
+    @Override
     public String toString()
     {
         return this.getClass().getName()+(_handling?"[H":"[h")+(_terminating?"T]":"t]");
diff --git a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RuleContainer.java b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RuleContainer.java
index 34ae2b7..677a440 100644
--- a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RuleContainer.java
+++ b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RuleContainer.java
@@ -23,9 +23,10 @@ import java.io.IOException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import org.eclipse.jetty.server.AbstractHttpConnection;
+import org.eclipse.jetty.server.HttpChannel;
 import org.eclipse.jetty.server.Request;
-import org.eclipse.jetty.util.LazyList;
+import org.eclipse.jetty.util.ArrayUtil;
+import org.eclipse.jetty.util.URIUtil;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
 
@@ -110,7 +111,7 @@ public class RuleContainer extends Rule
      */
     public void addRule(Rule rule)
     {
-        _rules = (Rule[])LazyList.addToArray(_rules,rule,Rule.class);
+        _rules = ArrayUtil.addToArray(_rules,rule,Rule.class);
     }
    
 
@@ -211,10 +212,11 @@ public class RuleContainer extends Rule
 
                 if (_rewriteRequestURI)
                 {
+                    String encoded=URIUtil.encodePath(applied);
                     if (rule instanceof Rule.ApplyURI)
-                        ((Rule.ApplyURI)rule).applyURI((Request)request, target, applied);
+                        ((Rule.ApplyURI)rule).applyURI((Request)request,((Request)request).getRequestURI(), encoded);
                     else
-                        ((Request)request).setRequestURI(applied);
+                        ((Request)request).setRequestURI(encoded);
                 }
 
                 if (_rewritePathInfo)
@@ -225,7 +227,7 @@ public class RuleContainer extends Rule
                 if (rule.isHandling())
                 {
                     LOG.debug("handling {}",rule);
-                    (request instanceof Request?(Request)request:AbstractHttpConnection.getCurrentConnection().getRequest()).setHandled(true);
+                    (request instanceof Request?(Request)request:HttpChannel.getCurrentHttpChannel().getRequest()).setHandled(true);
                 }
 
                 if (rule.isTerminating())
diff --git a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/VirtualHostRuleContainer.java b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/VirtualHostRuleContainer.java
index ed9b0a0..bbdbdd6 100644
--- a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/VirtualHostRuleContainer.java
+++ b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/VirtualHostRuleContainer.java
@@ -23,7 +23,7 @@ import java.io.IOException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import org.eclipse.jetty.util.LazyList;
+import org.eclipse.jetty.util.ArrayUtil;
 
 /**
  * Groups rules that apply only to a specific virtual host
@@ -72,7 +72,7 @@ public class VirtualHostRuleContainer extends RuleContainer
      */
     public void addVirtualHost(String virtualHost)
     {
-        _virtualHosts = (String[])LazyList.addToArray(_virtualHosts,virtualHost,String.class);
+        _virtualHosts = ArrayUtil.addToArray(_virtualHosts,virtualHost,String.class);
     }
 
     /**
diff --git a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/package-info.java b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/package-info.java
new file mode 100644
index 0000000..acc1f04
--- /dev/null
+++ b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Rewrite : Rewrite Handler and Rules for Jetty
+ */
+package org.eclipse.jetty.rewrite.handler;
+
diff --git a/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/AbstractRuleTestCase.java b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/AbstractRuleTestCase.java
index 568b96a..900d46d 100644
--- a/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/AbstractRuleTestCase.java
+++ b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/AbstractRuleTestCase.java
@@ -18,24 +18,30 @@
 
 package org.eclipse.jetty.rewrite.handler;
 
-import org.eclipse.jetty.io.bio.StringEndPoint;
-import org.eclipse.jetty.server.BlockingHttpConnection;
+import java.io.IOException;
+import java.util.concurrent.CountDownLatch;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
 import org.eclipse.jetty.server.Connector;
-import org.eclipse.jetty.server.AbstractHttpConnection;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.HttpConnectionFactory;
 import org.eclipse.jetty.server.LocalConnector;
 import org.eclipse.jetty.server.Request;
 import org.eclipse.jetty.server.Response;
 import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.handler.AbstractHandler;
 import org.junit.After;
 
 public abstract class AbstractRuleTestCase
 {
     protected Server _server = new Server();
     protected LocalConnector _connector;
-    protected StringEndPoint _endpoint = new StringEndPoint();
-    protected AbstractHttpConnection _connection;
-    protected Request _request;
-    protected Response _response;
+    protected volatile Request _request;
+    protected volatile Response _response;
+    protected volatile CountDownLatch _latch;
     protected boolean _isSecure = false;
 
     @After
@@ -46,31 +52,65 @@ public abstract class AbstractRuleTestCase
 
     protected void start(final boolean isSecure) throws Exception
     {
-        _connector = new LocalConnector()
+        _connector = new LocalConnector(_server);
+        _connector.getConnectionFactory(HttpConnectionFactory.class).getHttpConfiguration().addCustomizer(new HttpConfiguration.Customizer()
         {
-            public boolean isConfidential(Request request)
+            @Override
+            public void customize(Connector connector, HttpConfiguration channelConfig, Request request)
             {
-                return isSecure;
+                request.setSecure(isSecure);
             }
-        };
+        });
         _server.setConnectors(new Connector[]{_connector});
+        
+
+        _server.setHandler(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                _request=baseRequest;
+                _response=_request.getResponse();
+                try
+                {
+                    _latch.await();
+                }
+                catch (InterruptedException e)
+                {
+                    throw new ServletException(e);
+                }
+            }
+        });
+
         _server.start();
-        reset();
+
+        _latch=new CountDownLatch(1);
+        _connector.executeRequest("GET / HTTP/1.0\nCookie: set=already\n\n");
+        
+        while (_response==null)
+            Thread.sleep(1);
     }
 
+    protected void reset()
+    {
+        if (_latch!=null)
+            _latch.countDown();
+        _request = null;
+        _response = null;
+        _latch=new CountDownLatch(1);
+        _connector.executeRequest("GET / HTTP/1.0\nCookie: set=already\n\n");
+        
+        while (_response==null)
+            Thread.yield();
+    }
+    
     protected void stop() throws Exception
     {
+        _latch.countDown();
         _server.stop();
         _server.join();
         _request = null;
         _response = null;
     }
 
-    protected void reset()
-    {
-        _connection = new BlockingHttpConnection(_connector, _endpoint, _server);
-        _request = new Request(_connection);
-        _response = new Response(_connection);
-        _request.setRequestURI("/test/");
-    }
 }
diff --git a/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/CookiePatternRuleTest.java b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/CookiePatternRuleTest.java
index aa80f47..e261a49 100644
--- a/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/CookiePatternRuleTest.java
+++ b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/CookiePatternRuleTest.java
@@ -18,16 +18,16 @@
 
 package org.eclipse.jetty.rewrite.handler;
 
+import static org.junit.Assert.assertEquals;
+
 import java.io.IOException;
 import java.util.Enumeration;
 
 import org.eclipse.jetty.http.HttpFields;
-import org.eclipse.jetty.http.HttpHeaders;
+import org.eclipse.jetty.http.HttpHeader;
 import org.junit.Before;
 import org.junit.Test;
 
-import static org.junit.Assert.assertEquals;
-
 public class CookiePatternRuleTest extends AbstractRuleTestCase
 {
     @Before
@@ -39,51 +39,42 @@ public class CookiePatternRuleTest extends AbstractRuleTestCase
     @Test
     public void testSingleCookie() throws IOException
     {
-        String[][] cookie = {
-                {"cookie", "value"}
-        };
-        assertCookies(cookie);
+        String[] cookie = {"cookie", "value"};
+        assertCookies(cookie,true);
     }
-
+    
     @Test
-    public void testMultipleCookies() throws IOException
+    public void testSetAlready() throws IOException
     {
-        String[][] cookies = {
-                {"cookie", "value"},
-                {"name", "wolfgangpuck"},
-                {"age", "28"}
-        };
-        assertCookies(cookies);
+        String[] cookie = {"set", "already"};
+        assertCookies(cookie,false);
     }
 
-    private void assertCookies(String[][] cookies) throws IOException
+    private void assertCookies(String[] cookie,boolean setExpected) throws IOException
     {
-        for (String[] cookie : cookies)
-        {
             // set cookie pattern
             CookiePatternRule rule = new CookiePatternRule();
             rule.setPattern("*");
             rule.setName(cookie[0]);
             rule.setValue(cookie[1]);
 
-            System.out.println(rule.toString());
+            // System.out.println(rule.toString());
 
             // apply cookie pattern
             rule.apply(_request.getRequestURI(), _request, _response);
 
             // verify
             HttpFields httpFields = _response.getHttpFields();
-            Enumeration e = httpFields.getValues(HttpHeaders.SET_COOKIE_BUFFER);
-            int index = 0;
+            Enumeration<String> e = httpFields.getValues(HttpHeader.SET_COOKIE.asString());
+            boolean set = false;
             while (e.hasMoreElements())
             {
-                String[] result = ((String)e.nextElement()).split("=");
-                assertEquals(cookies[index][0], result[0]);
-                assertEquals(cookies[index][1], result[1]);
-
-                // +1 cookies index
-                index++;
+                String[] result = (e.nextElement()).split("=");
+                assertEquals(cookie[0], result[0]);
+                assertEquals(cookie[1], result[1]);
+                set=true;
             }
-        }
+            
+            assertEquals(setExpected,set);
     }
 }
diff --git a/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/ForwardedSchemeHeaderRuleTest.java b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/ForwardedSchemeHeaderRuleTest.java
index 8f0e95b..b892ff5 100644
--- a/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/ForwardedSchemeHeaderRuleTest.java
+++ b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/ForwardedSchemeHeaderRuleTest.java
@@ -18,12 +18,12 @@
 
 package org.eclipse.jetty.rewrite.handler;
 
+import static org.junit.Assert.assertEquals;
+
 import org.eclipse.jetty.http.HttpFields;
 import org.junit.Before;
 import org.junit.Test;
 
-import static org.junit.Assert.assertEquals;
-
 public class ForwardedSchemeHeaderRuleTest extends AbstractRuleTestCase
 {
     private ForwardedSchemeHeaderRule _rule;
@@ -34,7 +34,7 @@ public class ForwardedSchemeHeaderRuleTest extends AbstractRuleTestCase
     {
         start(false);
         _rule = new ForwardedSchemeHeaderRule();
-        _requestHeaderFields = _connection.getRequestFields();
+        _requestHeaderFields = _request.getHttpFields();
         _request.setScheme(null);
     }
 
diff --git a/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/HeaderPatternRuleTest.java b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/HeaderPatternRuleTest.java
index 9efb7cf..70d4ebf 100644
--- a/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/HeaderPatternRuleTest.java
+++ b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/HeaderPatternRuleTest.java
@@ -18,14 +18,14 @@
 
 package org.eclipse.jetty.rewrite.handler;
 
+import static org.junit.Assert.assertEquals;
+
 import java.io.IOException;
 import java.util.Iterator;
 
 import org.junit.Before;
 import org.junit.Test;
 
-import static org.junit.Assert.assertEquals;
-
 public class HeaderPatternRuleTest extends AbstractRuleTestCase
 {
     private HeaderPatternRule _rule;
diff --git a/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/HeaderRegexRuleTest.java b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/HeaderRegexRuleTest.java
new file mode 100644
index 0000000..aaf22d7
--- /dev/null
+++ b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/HeaderRegexRuleTest.java
@@ -0,0 +1,131 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.rewrite.handler;
+
+import org.junit.Before;
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+
+import java.io.IOException;
+import java.util.Iterator;
+
+public class HeaderRegexRuleTest extends AbstractRuleTestCase
+{
+
+    private HeaderRegexRule _rule;
+
+    @Before
+    public void init() throws Exception
+    {
+        start(false);
+        _rule = new HeaderRegexRule();
+        _rule.setRegex("\\*");
+    }
+
+    @Test
+    public void testHeaderWithTextValues() throws IOException
+    {
+        // different keys
+        String headers[][] =
+        {
+        { "hnum#1", "test1" },
+        { "hnum#2", "2test2" },
+        { "hnum#3", "test3" } };
+        assertHeaders(headers);
+    }
+
+    @Test
+    public void testHeaderWithNumberValues() throws IOException
+    {
+        String headers[][] =
+        {
+        { "hello", "1" },
+        { "hello", "-1" },
+        { "hello", "100" },
+        { "hello", "100" },
+        { "hello", "100" },
+        { "hello", "100" },
+        { "hello", "100" },
+        { "hello1", "200" } };
+        assertHeaders(headers);
+    }
+
+    @Test
+    public void testHeaderOverwriteValues() throws IOException
+    {
+        String headers[][] =
+        {
+        { "size", "100" },
+        { "size", "200" },
+        { "size", "300" },
+        { "size", "400" },
+        { "size", "500" },
+        { "title", "abc" },
+        { "title", "bac" },
+        { "title", "cba" },
+        { "title1", "abba" },
+        { "title1", "abba1" },
+        { "title1", "abba" },
+        { "title1", "abba1" } };
+        assertHeaders(headers);
+        Iterator<String> e = _response.getHeaders("size").iterator();
+        int count = 0;
+        while (e.hasNext())
+        {
+            e.next();
+            count++;
+        }
+        assertEquals(1,count);
+        assertEquals("500",_response.getHeader("size"));
+        assertEquals("cba",_response.getHeader("title"));
+        assertEquals("abba1",_response.getHeader("title1"));
+    }
+
+    @Test
+    public void testMatch() throws Exception
+    {
+        _rule.setRegex("/my/dir/file/(.*)$");
+        _rule.setName("cache-control");
+        _rule.setValue("no-store");
+        _rule.matchAndApply("/my/dir/file/",_request,_response);
+        assertEquals("no-store",_response.getHeader("cache-control"));
+    }
+
+    @Test
+    public void testNotMatch() throws Exception
+    {
+        reset();
+        _rule.setRegex("/my/dir/file/(.*)$");
+        _rule.setName("cache-control");
+        _rule.setValue("no-store");
+        _rule.matchAndApply("/my/dir/file_not_match/",_request,_response);
+        assertEquals(null,_response.getHeader("cache-control"));
+    }
+
+    private void assertHeaders(String headers[][]) throws IOException
+    {
+        for (String[] header : headers)
+        {
+            _rule.setName(header[0]);
+            _rule.setValue(header[1]);
+            _rule.apply(null,_request,_response,null);
+            assertEquals(header[1],_response.getHeader(header[0]));
+        }
+    }
+}
diff --git a/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/LegacyRuleTest.java b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/LegacyRuleTest.java
index 74d43bc..9cff2d9 100644
--- a/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/LegacyRuleTest.java
+++ b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/LegacyRuleTest.java
@@ -18,12 +18,12 @@
 
 package org.eclipse.jetty.rewrite.handler;
 
+import static org.junit.Assert.assertEquals;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 
-import static org.junit.Assert.assertEquals;
-
 public class LegacyRuleTest extends AbstractRuleTestCase
 {
     private String[][] _tests=
diff --git a/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/MsieSslRuleTest.java b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/MsieSslRuleTest.java
index 6e71949..1624925 100644
--- a/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/MsieSslRuleTest.java
+++ b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/MsieSslRuleTest.java
@@ -18,14 +18,14 @@
 
 package org.eclipse.jetty.rewrite.handler;
 
+import static org.junit.Assert.assertEquals;
+
 import org.eclipse.jetty.http.HttpFields;
-import org.eclipse.jetty.http.HttpHeaderValues;
-import org.eclipse.jetty.http.HttpHeaders;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpHeaderValue;
 import org.junit.Before;
 import org.junit.Test;
 
-import static org.junit.Assert.assertEquals;
-
 public class MsieSslRuleTest extends AbstractRuleTestCase
 {
     private MsieSslRule _rule;
@@ -41,186 +41,186 @@ public class MsieSslRuleTest extends AbstractRuleTestCase
     @Test
     public void testWin2kWithIE5() throws Exception
     {
-        HttpFields fields = _connection.getRequestFields();
+        HttpFields fields = _request.getHttpFields();
         fields.add("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT 5.0)");
 
         String result = _rule.matchAndApply(_request.getRequestURI(), _request, _response);
 
         assertEquals(_request.getRequestURI(), result);
-        assertEquals(HttpHeaderValues.CLOSE, _response.getHeader(HttpHeaders.CONNECTION));
+        assertEquals(HttpHeaderValue.CLOSE.asString(), _response.getHeader(HttpHeader.CONNECTION.asString()));
 
 
         fields.add("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)");
         result = _rule.matchAndApply(_request.getRequestURI(), _request, _response);
         assertEquals(_request.getRequestURI(), result);
-        assertEquals(HttpHeaderValues.CLOSE, _response.getHeader(HttpHeaders.CONNECTION));
+        assertEquals(HttpHeaderValue.CLOSE.asString(), _response.getHeader(HttpHeader.CONNECTION.asString()));
 
         fields.add("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.0)");
         result = _rule.matchAndApply(_request.getRequestURI(), _request, _response);
         assertEquals(_request.getRequestURI(), result);
-        assertEquals(HttpHeaderValues.CLOSE, _response.getHeader(HttpHeaders.CONNECTION));
+        assertEquals(HttpHeaderValue.CLOSE.asString(), _response.getHeader(HttpHeader.CONNECTION.asString()));
     }
 
     @Test
     public void testWin2kWithIE6() throws Exception
     {
-        HttpFields fields = _connection.getRequestFields();
+        HttpFields fields = _request.getHttpFields();
         fields.add("User-Agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)");
 
         String result = _rule.matchAndApply(_request.getRequestURI(), _request, _response);
 
         assertEquals(_request.getRequestURI(), result);
-        assertEquals(HttpHeaderValues.CLOSE, _response.getHeader(HttpHeaders.CONNECTION));
+        assertEquals(HttpHeaderValue.CLOSE.asString(), _response.getHeader(HttpHeader.CONNECTION.asString()));
     }
 
     @Test
     public void testWin2kWithIE7() throws Exception
     {
-        HttpFields fields = _connection.getRequestFields();
+        HttpFields fields = _request.getHttpFields();
         fields.add("User-Agent", "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.0)");
 
         String result = _rule.matchAndApply(_request.getRequestURI(), _request, _response);
 
         assertEquals(null, result);
-        assertEquals(null, _response.getHeader(HttpHeaders.CONNECTION));
+        assertEquals(null, _response.getHeader(HttpHeader.CONNECTION.asString()));
     }
 
     @Test
     public void testWin2kSP1WithIE5() throws Exception
     {
-        HttpFields fields = _connection.getRequestFields();
+        HttpFields fields = _request.getHttpFields();
         fields.add("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT 5.01)");
 
         String result = _rule.matchAndApply(_request.getRequestURI(), _request, _response);
 
         assertEquals(_request.getRequestURI(), result);
-        assertEquals(HttpHeaderValues.CLOSE, _response.getHeader(HttpHeaders.CONNECTION));
+        assertEquals(HttpHeaderValue.CLOSE.asString(), _response.getHeader(HttpHeader.CONNECTION.asString()));
 
         fields.add("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.01)");
         result = _rule.matchAndApply(_request.getRequestURI(), _request, _response);
         assertEquals(_request.getRequestURI(), result);
-        assertEquals(HttpHeaderValues.CLOSE, _response.getHeader(HttpHeaders.CONNECTION));
+        assertEquals(HttpHeaderValue.CLOSE.asString(), _response.getHeader(HttpHeader.CONNECTION.asString()));
 
         fields.add("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.01)");
         result = _rule.matchAndApply(_request.getRequestURI(), _request, _response);
         assertEquals(_request.getRequestURI(), result);
-        assertEquals(HttpHeaderValues.CLOSE, _response.getHeader(HttpHeaders.CONNECTION));
+        assertEquals(HttpHeaderValue.CLOSE.asString(), _response.getHeader(HttpHeader.CONNECTION.asString()));
     }
 
     @Test
     public void testWin2kSP1WithIE6() throws Exception
     {
-        HttpFields fields = _connection.getRequestFields();
+        HttpFields fields = _request.getHttpFields();
         fields.add("User-Agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.01)");
 
         String result = _rule.matchAndApply(_request.getRequestURI(), _request, _response);
 
         assertEquals(_request.getRequestURI(), result);
-        assertEquals(HttpHeaderValues.CLOSE, _response.getHeader(HttpHeaders.CONNECTION));
+        assertEquals(HttpHeaderValue.CLOSE.asString(), _response.getHeader(HttpHeader.CONNECTION.asString()));
     }
 
     @Test
     public void testWin2kSP1WithIE7() throws Exception
     {
-        HttpFields fields = _connection.getRequestFields();
+        HttpFields fields = _request.getHttpFields();
         fields.add("User-Agent", "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.01)");
 
         String result = _rule.matchAndApply(_request.getRequestURI(), _request, _response);
 
         assertEquals(null, result);
-        assertEquals(null, _response.getHeader(HttpHeaders.CONNECTION));
+        assertEquals(null, _response.getHeader(HttpHeader.CONNECTION.asString()));
     }
 
     @Test
     public void testWinXpWithIE5() throws Exception
     {
-        HttpFields fields = _connection.getRequestFields();
+        HttpFields fields = _request.getHttpFields();
         fields.add("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT 5.1)");
 
         String result = _rule.matchAndApply(_request.getRequestURI(), _request, _response);
 
         assertEquals(_request.getRequestURI(), result);
-        assertEquals(HttpHeaderValues.CLOSE, _response.getHeader(HttpHeaders.CONNECTION));
+        assertEquals(HttpHeaderValue.CLOSE.asString(), _response.getHeader(HttpHeader.CONNECTION.asString()));
 
         fields.add("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.1)");
         result = _rule.matchAndApply(_request.getRequestURI(), _request, _response);
         assertEquals(_request.getRequestURI(), result);
-        assertEquals(HttpHeaderValues.CLOSE, _response.getHeader(HttpHeaders.CONNECTION));
+        assertEquals(HttpHeaderValue.CLOSE.asString(), _response.getHeader(HttpHeader.CONNECTION.asString()));
 
         fields.add("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.1)");
         result = _rule.matchAndApply(_request.getRequestURI(), _request, _response);
         assertEquals(_request.getRequestURI(), result);
-        assertEquals(HttpHeaderValues.CLOSE, _response.getHeader(HttpHeaders.CONNECTION));
+        assertEquals(HttpHeaderValue.CLOSE.asString(), _response.getHeader(HttpHeader.CONNECTION.asString()));
     }
 
     @Test
     public void testWinXpWithIE6() throws Exception
     {
-        HttpFields fields = _connection.getRequestFields();
+        HttpFields fields = _request.getHttpFields();
         fields.add("User-Agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)");
 
         String result = _rule.matchAndApply(_request.getRequestURI(), _request, _response);
 
         assertEquals(null, result);
-        assertEquals(null, _response.getHeader(HttpHeaders.CONNECTION));
+        assertEquals(null, _response.getHeader(HttpHeader.CONNECTION.asString()));
     }
 
     @Test
     public void testWinXpWithIE7() throws Exception
     {
-        HttpFields fields = _connection.getRequestFields();
+        HttpFields fields = _request.getHttpFields();
         fields.add("User-Agent", "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)");
 
         String result = _rule.matchAndApply(_request.getRequestURI(), _request, _response);
 
         assertEquals(null, result);
-        assertEquals(null, _response.getHeader(HttpHeaders.CONNECTION));
+        assertEquals(null, _response.getHeader(HttpHeader.CONNECTION.asString()));
     }
 
     @Test
     public void testWinVistaWithIE5() throws Exception
     {
-        HttpFields fields = _connection.getRequestFields();
+        HttpFields fields = _request.getHttpFields();
         fields.add("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT 6.0)");
 
         String result = _rule.matchAndApply(_request.getRequestURI(), _request, _response);
 
         assertEquals(_request.getRequestURI(), result);
-        assertEquals(HttpHeaderValues.CLOSE, _response.getHeader(HttpHeaders.CONNECTION));
+        assertEquals(HttpHeaderValue.CLOSE.asString(), _response.getHeader(HttpHeader.CONNECTION.asString()));
 
         fields.add("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 6.0)");
         result = _rule.matchAndApply(_request.getRequestURI(), _request, _response);
         assertEquals(_request.getRequestURI(), result);
-        assertEquals(HttpHeaderValues.CLOSE, _response.getHeader(HttpHeaders.CONNECTION));
+        assertEquals(HttpHeaderValue.CLOSE.asString(), _response.getHeader(HttpHeader.CONNECTION.asString()));
 
         fields.add("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 6.0)");
         result = _rule.matchAndApply(_request.getRequestURI(), _request, _response);
         assertEquals(_request.getRequestURI(), result);
-        assertEquals(HttpHeaderValues.CLOSE, _response.getHeader(HttpHeaders.CONNECTION));
+        assertEquals(HttpHeaderValue.CLOSE.asString(), _response.getHeader(HttpHeader.CONNECTION.asString()));
     }
 
     @Test
     public void testWinVistaWithIE6() throws Exception
     {
-        HttpFields fields = _connection.getRequestFields();
+        HttpFields fields = _request.getHttpFields();
         fields.add("User-Agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 6.0)");
 
         String result = _rule.matchAndApply(_request.getRequestURI(), _request, _response);
 
         assertEquals(null, result);
-        assertEquals(null, _response.getHeader(HttpHeaders.CONNECTION));
+        assertEquals(null, _response.getHeader(HttpHeader.CONNECTION.asString()));
     }
 
     @Test
     public void testWinVistaWithIE7() throws Exception
     {
-        HttpFields fields = _connection.getRequestFields();
+        HttpFields fields = _request.getHttpFields();
         fields.add("User-Agent", "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0)");
 
         String result = _rule.matchAndApply(_request.getRequestURI(), _request, _response);
 
         assertEquals(null, result);
-        assertEquals(null, _response.getHeader(HttpHeaders.CONNECTION));
+        assertEquals(null, _response.getHeader(HttpHeader.CONNECTION.asString()));
     }
 
     @Test
@@ -230,12 +230,12 @@ public class MsieSslRuleTest extends AbstractRuleTestCase
         super.stop();
         super.start(false);
 
-        HttpFields fields = _connection.getRequestFields();
+        HttpFields fields = _request.getHttpFields();
         fields.add("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT 5.0)");
 
         String result = _rule.matchAndApply(_request.getRequestURI(), _request, _response);
 
         assertEquals(null, result);
-        assertEquals(null, _response.getHeader(HttpHeaders.CONNECTION));
+        assertEquals(null, _response.getHeader(HttpHeader.CONNECTION.asString()));
     }
 }
diff --git a/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/PatternRuleTest.java b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/PatternRuleTest.java
index de6e4f6..533f429 100644
--- a/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/PatternRuleTest.java
+++ b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/PatternRuleTest.java
@@ -18,7 +18,10 @@
 
 package org.eclipse.jetty.rewrite.handler;
 
+import static org.junit.Assert.assertEquals;
+
 import java.io.IOException;
+
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
@@ -27,8 +30,6 @@ import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 
-import static org.junit.Assert.assertEquals;
-
 public class PatternRuleTest
 {
     private PatternRule _rule;
@@ -132,8 +133,9 @@ public class PatternRuleTest
     {
         _rule.setPattern(matchCase[0]);
         final String uri=matchCase[1];
+        
         String result = _rule.matchAndApply(uri,
-        new Request()
+        new Request(null,null)
         {
             {
                 setRequestURI(uri);
diff --git a/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/ProxyRuleTest.java b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/ProxyRuleTest.java
deleted file mode 100644
index fae9d9c..0000000
--- a/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/ProxyRuleTest.java
+++ /dev/null
@@ -1,136 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.rewrite.handler;
-
-import java.io.IOException;
-import java.net.URLEncoder;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jetty.client.ContentExchange;
-import org.eclipse.jetty.client.HttpClient;
-import org.eclipse.jetty.client.HttpExchange;
-import org.eclipse.jetty.http.HttpMethods;
-import org.eclipse.jetty.server.Connector;
-import org.eclipse.jetty.server.Request;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.handler.AbstractHandler;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.Test;
-
-import static org.junit.Assert.assertEquals;
-
-public class ProxyRuleTest
-{
-    private static ProxyRule _rule;
-    private static RewriteHandler _handler;
-    private static Server _proxyServer = new Server();
-    private static Connector _proxyServerConnector = new SelectChannelConnector();
-    private static Server _targetServer = new Server();
-    private static Connector _targetServerConnector = new SelectChannelConnector();
-    private static HttpClient _httpClient = new HttpClient();
-
-    @BeforeClass
-    public static void setupOnce() throws Exception
-    {
-        _targetServer.addConnector(_targetServerConnector);
-        _targetServer.setHandler(new AbstractHandler()
-        {
-            public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
-            {
-                baseRequest.setHandled(true);
-                String responseString = "uri: " + request.getRequestURI() + " some content";
-                response.getOutputStream().write(responseString.getBytes());
-                response.setStatus(201);
-            }
-        });
-        _targetServer.start();
-
-        _rule = new ProxyRule();
-        _rule.setPattern("/foo/*");
-        _rule.setProxyTo("http://localhost:" + _targetServerConnector.getLocalPort());
-        _handler = new RewriteHandler();
-        _handler.setRewriteRequestURI(true);
-        _handler.setRules(new Rule[] { _rule });
-
-        _proxyServer.addConnector(_proxyServerConnector);
-        _proxyServer.setHandler(_handler);
-        _proxyServer.start();
-
-        _httpClient.start();
-    }
-
-    @AfterClass
-    public static void destroy() throws Exception
-    {
-        _httpClient.stop();
-        _proxyServer.stop();
-        _targetServer.stop();
-        _rule = null;
-    }
-
-    @Test
-    public void testProxy() throws Exception
-    {
-
-        ContentExchange exchange = new ContentExchange(true);
-        exchange.setMethod(HttpMethods.GET);
-        String body = "BODY";
-        String url = "http://localhost:" + _proxyServerConnector.getLocalPort() + "/foo?body=" + URLEncoder.encode(body,"UTF-8");
-        exchange.setURL(url);
-
-        _httpClient.send(exchange);
-        assertEquals(HttpExchange.STATUS_COMPLETED,exchange.waitForDone());
-        assertEquals("uri: / some content",exchange.getResponseContent());
-        assertEquals(201,exchange.getResponseStatus());
-    }
-
-    @Test
-    public void testProxyWithDeeperPath() throws Exception
-    {
-
-        ContentExchange exchange = new ContentExchange(true);
-        exchange.setMethod(HttpMethods.GET);
-        String body = "BODY";
-        String url = "http://localhost:" + _proxyServerConnector.getLocalPort() + "/foo/bar/foobar?body=" + URLEncoder.encode(body,"UTF-8");
-        exchange.setURL(url);
-
-        _httpClient.send(exchange);
-        assertEquals(HttpExchange.STATUS_COMPLETED,exchange.waitForDone());
-        assertEquals("uri: /bar/foobar some content",exchange.getResponseContent());
-        assertEquals(201,exchange.getResponseStatus());
-    }
-
-    @Test
-    public void testProxyNoMatch() throws Exception
-    {
-        ContentExchange exchange = new ContentExchange(true);
-        exchange.setMethod(HttpMethods.GET);
-        String body = "BODY";
-        String url = "http://localhost:" + _proxyServerConnector.getLocalPort() + "/foobar?body=" + URLEncoder.encode(body,"UTF-8");
-        exchange.setURL(url);
-
-        _httpClient.send(exchange);
-        assertEquals(HttpExchange.STATUS_COMPLETED,exchange.waitForDone());
-        assertEquals(404,exchange.getResponseStatus());
-    }
-}
diff --git a/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RedirectPatternRuleTest.java b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RedirectPatternRuleTest.java
index caf64bd..b4a5e45 100644
--- a/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RedirectPatternRuleTest.java
+++ b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RedirectPatternRuleTest.java
@@ -18,15 +18,15 @@
 
 package org.eclipse.jetty.rewrite.handler;
 
+import static org.junit.Assert.assertEquals;
+
 import java.io.IOException;
 
-import org.eclipse.jetty.http.HttpHeaders;
+import org.eclipse.jetty.http.HttpHeader;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 
-import static org.junit.Assert.assertEquals;
-
 public class RedirectPatternRuleTest extends AbstractRuleTestCase
 {
     private RedirectPatternRule _rule;
@@ -51,6 +51,6 @@ public class RedirectPatternRuleTest extends AbstractRuleTestCase
         String location = "http://eclipse.com";
         _rule.setLocation(location);
         _rule.apply(null, _request, _response);
-        assertEquals(location, _response.getHeader(HttpHeaders.LOCATION));
+        assertEquals(location, _response.getHeader(HttpHeader.LOCATION.asString()));
     }
 }
diff --git a/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RedirectRegexRuleTest.java b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RedirectRegexRuleTest.java
index 46a8f30..f18839a 100644
--- a/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RedirectRegexRuleTest.java
+++ b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RedirectRegexRuleTest.java
@@ -18,15 +18,15 @@
 
 package org.eclipse.jetty.rewrite.handler;
 
+import static org.junit.Assert.assertEquals;
+
 import java.io.IOException;
 
-import org.eclipse.jetty.http.HttpHeaders;
+import org.eclipse.jetty.http.HttpHeader;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 
-import static org.junit.Assert.assertEquals;
-
 public class RedirectRegexRuleTest extends AbstractRuleTestCase
 {
     private RedirectRegexRule _rule;
@@ -52,7 +52,7 @@ public class RedirectRegexRuleTest extends AbstractRuleTestCase
 
         // Resource is dir
         _rule.matchAndApply("/my/dir/file/", _request, _response);
-        assertEquals("http://www.mortbay.org/", _response.getHeader(HttpHeaders.LOCATION));
+        assertEquals("http://www.mortbay.org/", _response.getHeader(HttpHeader.LOCATION.asString()));
     }
 
     @Test
@@ -63,7 +63,7 @@ public class RedirectRegexRuleTest extends AbstractRuleTestCase
 
         // Resource is an image
         _rule.matchAndApply("/my/dir/file/image.png", _request, _response);
-        assertEquals("http://www.mortbay.org/image.png", _response.getHeader(HttpHeaders.LOCATION));
+        assertEquals("http://www.mortbay.org/image.png", _response.getHeader(HttpHeader.LOCATION.asString()));
     }
 
     @Test
@@ -74,6 +74,6 @@ public class RedirectRegexRuleTest extends AbstractRuleTestCase
 
         // Resource is api with parameters
         _rule.matchAndApply("/my/dir/file/api/rest/foo?id=100&sort=date", _request, _response);
-        assertEquals("http://www.mortbay.org/api/rest/foo?id=100&sort=date", _response.getHeader(HttpHeaders.LOCATION));
+        assertEquals("http://www.mortbay.org/api/rest/foo?id=100&sort=date", _response.getHeader(HttpHeader.LOCATION.asString()));
     }
 }
diff --git a/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RegexRuleTest.java b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RegexRuleTest.java
index b348c38..4b49081 100644
--- a/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RegexRuleTest.java
+++ b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RegexRuleTest.java
@@ -18,8 +18,11 @@
 
 package org.eclipse.jetty.rewrite.handler;
 
+import static org.junit.Assert.assertEquals;
+
 import java.io.IOException;
 import java.util.regex.Matcher;
+
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
@@ -28,8 +31,6 @@ import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 
-import static org.junit.Assert.assertEquals;
-
 public class RegexRuleTest
 {
     private RegexRule _rule;
@@ -96,8 +97,9 @@ public class RegexRuleTest
         _rule.setRegex(matchCase[0]);
         final String uri=matchCase[1];
         String result = _rule.matchAndApply(uri,
-        new Request()
+        new Request(null,null)
         {
+            @Override
             public String getRequestURI()
             {
                 return uri;
diff --git a/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/ResponsePatternRuleTest.java b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/ResponsePatternRuleTest.java
index 33f5bf8..4b058e1 100644
--- a/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/ResponsePatternRuleTest.java
+++ b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/ResponsePatternRuleTest.java
@@ -18,13 +18,13 @@
 
 package org.eclipse.jetty.rewrite.handler;
 
+import static org.junit.Assert.assertEquals;
+
 import java.io.IOException;
 
 import org.junit.Before;
 import org.junit.Test;
 
-import static org.junit.Assert.assertEquals;
-
 public class ResponsePatternRuleTest extends AbstractRuleTestCase
 {
     private ResponsePatternRule _rule;
diff --git a/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RewriteHandlerTest.java b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RewriteHandlerTest.java
index 0df4a81..684e4d9 100644
--- a/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RewriteHandlerTest.java
+++ b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RewriteHandlerTest.java
@@ -18,7 +18,11 @@
 
 package org.eclipse.jetty.rewrite.handler;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
 import java.io.IOException;
+
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
@@ -28,9 +32,6 @@ import org.eclipse.jetty.server.handler.AbstractHandler;
 import org.junit.Before;
 import org.junit.Test;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
 public class RewriteHandlerTest extends AbstractRuleTestCase
 {
     private RewriteHandler _handler;
@@ -43,9 +44,10 @@ public class RewriteHandlerTest extends AbstractRuleTestCase
     public void init() throws Exception
     {
         _handler=new RewriteHandler();
-        _server.setHandler(_handler);
-        _handler.setHandler(new AbstractHandler(){
-
+        _handler.setServer(_server);
+        _handler.setHandler(new AbstractHandler()
+        {
+            @Override
             public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
             {
                 response.setStatus(201);
@@ -55,6 +57,7 @@ public class RewriteHandlerTest extends AbstractRuleTestCase
             }
 
         });
+        _handler.start();
 
         _rule1 = new RewritePatternRule();
         _rule1.setPattern("/aaa/*");
@@ -98,6 +101,7 @@ public class RewriteHandlerTest extends AbstractRuleTestCase
         _handler.setRewritePathInfo(false);
         _request.setRequestURI("/foo/bar");
         _request.setPathInfo("/foo/bar");
+        
         _handler.handle("/foo/bar",_request,_request, _response);
         assertEquals(201,_response.getStatus());
         assertEquals("/foo/bar",_request.getAttribute("target"));
@@ -162,6 +166,7 @@ public class RewriteHandlerTest extends AbstractRuleTestCase
     }
 
 
+    @Test
     public void testEncodedPattern() throws Exception
     {
         _response.setStatus(200);
@@ -169,15 +174,18 @@ public class RewriteHandlerTest extends AbstractRuleTestCase
         _handler.setOriginalPathAttribute("/before");
         _handler.setRewriteRequestURI(true);
         _handler.setRewritePathInfo(false);
-        _request.setRequestURI("/ccc/x%2Fy");
-        _request.setPathInfo("/ccc/x/y");
-        _handler.handle("/ccc/x/y",_request,_request, _response);
+        _request.setRequestURI("/ccc/x%20y");
+        _request.setPathInfo("/ccc/x y");
+        _handler.handle("/ccc/x y",_request,_request, _response);
         assertEquals(201,_response.getStatus());
-        assertEquals("/ddd/x/y",_request.getAttribute("target"));
-        assertEquals("/ddd/x%2Fy",_request.getAttribute("URI"));
-        assertEquals("/ccc/x/y",_request.getAttribute("info"));
+        assertEquals("/ddd/x y",_request.getAttribute("target"));
+        assertEquals("/ddd/x%20y",_request.getAttribute("URI"));
+        assertEquals("/ccc/x y",_request.getAttribute("info"));
 
     }
+    
+
+    @Test
     public void testEncodedRegex() throws Exception
     {
         _response.setStatus(200);
@@ -185,13 +193,13 @@ public class RewriteHandlerTest extends AbstractRuleTestCase
         _handler.setOriginalPathAttribute("/before");
         _handler.setRewriteRequestURI(true);
         _handler.setRewritePathInfo(false);
-        _request.setRequestURI("/xxx/x%2Fy");
-        _request.setPathInfo("/xxx/x/y");
-        _handler.handle("/xxx/x/y",_request,_request, _response);
+        _request.setRequestURI("/xxx/x%20y");
+        _request.setPathInfo("/xxx/x y");
+        _handler.handle("/xxx/x y",_request,_request, _response);
         assertEquals(201,_response.getStatus());
-        assertEquals("/x/y/zzz",_request.getAttribute("target"));
-        assertEquals("/x%2Fy/zzz",_request.getAttribute("URI"));
-        assertEquals("/xxx/x/y",_request.getAttribute("info"));
+        assertEquals("/x y/zzz",_request.getAttribute("target"));
+        assertEquals("/x%20y/zzz",_request.getAttribute("URI"));
+        assertEquals("/xxx/x y",_request.getAttribute("info"));
 
     }
 }
diff --git a/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RewritePatternRuleTest.java b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RewritePatternRuleTest.java
index 7f4789b..b246e6d 100644
--- a/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RewritePatternRuleTest.java
+++ b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RewritePatternRuleTest.java
@@ -18,23 +18,26 @@
 
 package org.eclipse.jetty.rewrite.handler;
 
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
 import java.io.IOException;
 
+import org.eclipse.jetty.http.HttpURI;
 import org.junit.Before;
 import org.junit.Test;
 
-import static org.junit.Assert.assertEquals;
-
 public class RewritePatternRuleTest extends AbstractRuleTestCase
 {
-    private String[][] _tests=
-    {
-            {"/foo/bar","/","/replace"},
-            {"/foo/bar","/*","/replace/foo/bar"},
-            {"/foo/bar","/foo/*","/replace/bar"},
-            {"/foo/bar","/foo/bar","/replace"},
-            {"/foo/bar.txt","*.txt","/replace"},
-    };
+    private String[][] _tests =
+            {
+                    {"/foo/bar", "/", "/replace"},
+                    {"/foo/bar", "/*", "/replace/foo/bar"},
+                    {"/foo/bar", "/foo/*", "/replace/bar"},
+                    {"/foo/bar", "/foo/bar", "/replace"},
+                    {"/foo/bar.txt", "*.txt", "/replace"},
+                    {"/foo/bar/%20x", "/foo/*", "/replace/bar/%20x"},
+            };
     private RewritePatternRule _rule;
 
     @Before
@@ -46,13 +49,82 @@ public class RewritePatternRuleTest extends AbstractRuleTestCase
     }
 
     @Test
-    public void testRequestUriEnabled() throws IOException
+    public void testMatchAndApplyAndApplyURI() throws IOException
     {
         for (String[] test : _tests)
         {
             _rule.setPattern(test[1]);
             String result = _rule.matchAndApply(test[0], _request, _response);
-            assertEquals(test[1], test[2], result);
+            assertThat(test[1], test[2], is(result));
+
+            _rule.applyURI(_request, null, result);
+            assertThat(_request.getRequestURI(), is(test[2]));
         }
     }
+
+    @Test
+    public void testReplacementWithQueryString() throws IOException
+    {
+        String replacement = "/replace?given=param";
+        String[] split = replacement.split("\\?", 2);
+        String path = split[0];
+        String queryString = split[1];
+
+        RewritePatternRule rewritePatternRule = new RewritePatternRule();
+        rewritePatternRule.setPattern("/old/context");
+        rewritePatternRule.setReplacement(replacement);
+
+        String result = rewritePatternRule.matchAndApply("/old/context", _request, _response);
+        assertThat(result, is(path));
+
+        rewritePatternRule.applyURI(_request, null, result);
+        assertThat("queryString matches expected", _request.getQueryString(), is(queryString));
+        assertThat("request URI matches expected", _request.getRequestURI(), is(path));
+
+    }
+
+    @Test
+    public void testRequestWithQueryString() throws IOException
+    {
+        String replacement = "/replace";
+        String queryString = "request=parameter";
+        _request.setUri(new HttpURI("/old/context"));
+        _request.setQueryString(queryString);
+
+        RewritePatternRule rewritePatternRule = new RewritePatternRule();
+        rewritePatternRule.setPattern("/old/context");
+        rewritePatternRule.setReplacement(replacement);
+
+        String result = rewritePatternRule.matchAndApply("/old/context", _request, _response);
+        assertThat("result matches expected", result, is(replacement));
+
+        rewritePatternRule.applyURI(_request, null, result);
+        assertThat("queryString matches expected", _request.getQueryString(), is(queryString));
+        assertThat("request URI matches expected", _request.getRequestURI(), is(replacement));
+    }
+
+    @Test
+    public void testRequestAndReplacementWithQueryString() throws IOException
+    {
+        String requestQueryString = "request=parameter";
+        String replacement = "/replace?given=param";
+        String[] split = replacement.split("\\?", 2);
+        String path = split[0];
+        String queryString = split[1];
+        _request.setUri(new HttpURI("/old/context"));
+        _request.setQueryString(requestQueryString);
+
+        RewritePatternRule rewritePatternRule = new RewritePatternRule();
+        rewritePatternRule.setPattern("/old/context");
+        rewritePatternRule.setReplacement(replacement);
+
+        String result = rewritePatternRule.matchAndApply("/old/context", _request, _response);
+        assertThat(result, is(path));
+
+        rewritePatternRule.applyURI(_request, null, result);
+        assertThat("queryString matches expected", _request.getQueryString(),
+                is(requestQueryString + "&" + queryString));
+        assertThat("request URI matches expected", _request.getRequestURI(), is(path));
+    }
+
 }
diff --git a/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RewriteRegexRuleTest.java b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RewriteRegexRuleTest.java
index 9d5bb00..11cf1fe 100644
--- a/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RewriteRegexRuleTest.java
+++ b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RewriteRegexRuleTest.java
@@ -18,17 +18,18 @@
 
 package org.eclipse.jetty.rewrite.handler;
 
+import static org.junit.Assert.assertEquals;
+
 import java.io.IOException;
+import java.nio.charset.StandardCharsets;
 
 import org.eclipse.jetty.http.HttpURI;
 import org.eclipse.jetty.util.MultiMap;
-import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.URIUtil;
 import org.eclipse.jetty.util.UrlEncoded;
 import org.junit.Before;
 import org.junit.Test;
 
-import static org.junit.Assert.assertEquals;
-
 public class RewriteRegexRuleTest extends AbstractRuleTestCase
 {
     private String[][] _tests=
@@ -37,6 +38,7 @@ public class RewriteRegexRuleTest extends AbstractRuleTestCase
             {"/foo1/bar","n=v",".*","/replace","/replace","n=v"},
             {"/foo2/bar",null,"/xxx.*","/replace",null,null},
             {"/foo3/bar",null,"/(.*)/(.*)","/$2/$1/xxx","/bar/foo3/xxx",null},
+            {"/f%20o3/bar",null,"/(.*)/(.*)","/$2/$1/xxx","/bar/f%20o3/xxx",null},
             {"/foo4/bar",null,"/(.*)/(.*)","/test?p2=$2&p1=$1","/test","p2=bar&p1=foo4"},
             {"/foo5/bar","n=v","/(.*)/(.*)","/test?p2=$2&p1=$1","/test","n=v&p2=bar&p1=foo5"},
             {"/foo6/bar",null,"/(.*)/(.*)","/foo6/bar?p2=$2&p1=$1","/foo6/bar","p2=bar&p1=foo6"},
@@ -87,7 +89,7 @@ public class RewriteRegexRuleTest extends AbstractRuleTestCase
             if (test[5]!=null)
             {
                 MultiMap<String> params=new MultiMap<String>();
-                UrlEncoded.decodeTo(test[5],params,StringUtil.__UTF8);
+                UrlEncoded.decodeTo(test[5],params, StandardCharsets.UTF_8,-1);
                                
                 for (String n:params.keySet())
                     assertEquals(params.getString(n),_request.getParameter(n));
@@ -112,8 +114,8 @@ public class RewriteRegexRuleTest extends AbstractRuleTestCase
             _request.setQueryString(test[1]);
             _request.getAttributes().clearAttributes();
             
-            String result = container.apply(test[0],_request,_response);
-            assertEquals(t,test[4]==null?test[0]:test[4], result);
+            String result = container.apply(URIUtil.decodePath(test[0]),_request,_response);
+            assertEquals(t,URIUtil.decodePath(test[4]==null?test[0]:test[4]), result);
             assertEquals(t,test[4]==null?test[0]:test[4], _request.getRequestURI());
             assertEquals(t,test[5], _request.getQueryString());
         }
diff --git a/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/ValidUrlRuleTest.java b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/ValidUrlRuleTest.java
index 9925f1d..0a612e3 100644
--- a/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/ValidUrlRuleTest.java
+++ b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/ValidUrlRuleTest.java
@@ -22,6 +22,7 @@ import static org.junit.Assert.assertEquals;
 import junit.framework.Assert;
 
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 
 @SuppressWarnings("unused")
@@ -42,7 +43,7 @@ public class ValidUrlRuleTest extends AbstractRuleTestCase
         _rule.setCode("404");
         _request.setRequestURI("/valid/uri.html");
         
-        String result = _rule.matchAndApply(_request.getRequestURI(), _request, _response);
+        _rule.matchAndApply(_request.getRequestURI(), _request, _response);
 
         assertEquals(200,_response.getStatus());
     }
@@ -84,6 +85,7 @@ public class ValidUrlRuleTest extends AbstractRuleTestCase
         assertEquals("foo",_response.getReason());
     }
     
+    @Ignore("Not working in jetty-9")
     @Test
     public void testInvalidShamrock() throws Exception
     {
@@ -97,6 +99,7 @@ public class ValidUrlRuleTest extends AbstractRuleTestCase
         assertEquals("foo",_response.getReason());
     }
 
+    @Ignore("Not working in jetty-9")
     @Test
     public void testValidShamrock() throws Exception
     {
diff --git a/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/VirtualHostRuleContainerTest.java b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/VirtualHostRuleContainerTest.java
index 9da7294..8d692ae 100644
--- a/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/VirtualHostRuleContainerTest.java
+++ b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/VirtualHostRuleContainerTest.java
@@ -18,11 +18,11 @@
 
 package org.eclipse.jetty.rewrite.handler;
 
+import static org.junit.Assert.assertEquals;
+
 import org.junit.Before;
 import org.junit.Test;
 
-import static org.junit.Assert.assertEquals;
-
 public class VirtualHostRuleContainerTest extends AbstractRuleTestCase
 {
     private RewriteHandler _handler;
@@ -48,10 +48,11 @@ public class VirtualHostRuleContainerTest extends AbstractRuleTestCase
         _fooContainerRule.setVirtualHosts(new String[] {"foo.com"});
         _fooContainerRule.setRules(new Rule[] { _fooRule });
 
-        _server.setHandler(_handler);
-
         start(false);
         _request.setRequestURI("/cheese/bar");
+        
+        _handler.setServer(_server);
+        _handler.start();
     }
 
     @Test
@@ -193,6 +194,6 @@ public class VirtualHostRuleContainerTest extends AbstractRuleTestCase
 
     private void handleRequest() throws Exception
     {
-        _server.handle("/cheese/bar", _request, _request, _response);
+        _handler.handle("/cheese/bar", _request, _request, _response);
     }
 }
diff --git a/jetty-rewrite/src/test/resources/org.mortbay.jetty.rewrite.handler/jetty-rewrite.xml b/jetty-rewrite/src/test/resources/org.mortbay.jetty.rewrite.handler/jetty-rewrite.xml
index e97f217..b54d24f 100644
--- a/jetty-rewrite/src/test/resources/org.mortbay.jetty.rewrite.handler/jetty-rewrite.xml
+++ b/jetty-rewrite/src/test/resources/org.mortbay.jetty.rewrite.handler/jetty-rewrite.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0"?>
-<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
+<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
 
 <!-- =============================================================== -->
 <!-- Configure the Jetty Server                                      -->
@@ -35,18 +35,16 @@
 
     <!-- Use this connector for many frequently idle connections
          and for threadless continuations.
-    -->    
+    -->
     <Call name="addConnector">
       <Arg>
-          <New class="org.eclipse.jetty.server.nio.SelectChannelConnector">
+          <New class="org.eclipse.jetty.server.ServerConnector">
             <Set name="host"><SystemProperty name="jetty.host" /></Set>
             <Set name="port"><SystemProperty name="jetty.port" default="8080"/></Set>
-            <Set name="maxIdleTime">30000</Set>
+            <Set name="idleTimeout">30000</Set>
             <Set name="Acceptors">2</Set>
             <Set name="statsOn">false</Set>
             <Set name="confidentialPort">8443</Set>
-      <Set name="lowResourcesConnections">10000</Set>
-      <Set name="lowResourcesMaxIdleTime">5000</Set>
           </New>
       </Arg>
     </Call>
@@ -56,7 +54,7 @@
     <!-- see jetty-ssl.xml to add an ssl connector. use                  -->
     <!-- java -jar start.jar etc/jetty-ssl.xml                           -->
     <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
-    
+
     <!-- =========================================================== -->
     <!-- Set up global session ID manager                            -->
     <!-- =========================================================== -->
@@ -69,10 +67,10 @@
     -->
 
     <!-- =========================================================== -->
-    <!-- Set handler Collection Structure                            --> 
+    <!-- Set handler Collection Structure                            -->
     <!-- =========================================================== -->
     <Set name="handler">
-    
+
       <!-- ==========================================================  -->
       <!-- RewriteHandler Sample Configuration                         -->
       <!-- ==========================================================  -->
@@ -80,7 +78,7 @@
 
         <Set name="originalPathAttribute">requestedPath</Set>
         <Set name="rewriteRequestURI">true</Set>
-        
+
         <Set name="rules">
           <Array type="org.eclipse.jetty.rewrite.handler.Rule">
 
@@ -90,7 +88,7 @@
                 <Set name="replacement">/test</Set>
               </New>
             </Item>
-            
+
             <Item>
               <New id="response" class="org.eclipse.jetty.rewrite.handler.ResponsePatternRule">
                 <Set name="pattern">/session/</Set>
@@ -128,7 +126,7 @@
                 <Set name="replacement">/demo</Set>
               </New>
             </Item>
-            
+
             <Item>
               <New id="forwardedHttps" class="org.eclipse.jetty.rewrite.handler.ForwardedSchemeHeaderRule">
                 <Set name="header">X-Forwarded-Scheme</Set>
@@ -158,10 +156,10 @@
                     </New>
                   </Arg>
                 </Call>
-     
+
              </New>
            </Item>
- 
+
           </Array>
         </Set>
 
@@ -184,7 +182,7 @@
         </Set>
       </New>
     </Set>
-    
+
     <!-- =========================================================== -->
     <!-- Configure the context deployer                              -->
     <!-- A context deployer will deploy contexts described in        -->
@@ -196,10 +194,10 @@
     <!-- in the $JETTY_HOME/contexts directory                       -->
     <!--                                                             -->
     <!-- =========================================================== -->
-    <Call name="addLifeCycle">
+    <Call name="addBean">
       <Arg>
         <New class="org.eclipse.jetty.server.deployer.ContextDeployer">
-          <Set name="contexts"><Ref id="Contexts"/></Set>
+          <Set name="contexts"><Ref refid="Contexts"/></Set>
           <Set name="configurationDir"><SystemProperty name="jetty.home" default="."/>/contexts</Set>
           <Set name="scanInterval">1</Set>
         </New>
@@ -219,10 +217,10 @@
     <!-- Normally only one type of deployer need be used.            -->
     <!--                                                             -->
     <!-- =========================================================== -->
-    <Call name="addLifeCycle">
+    <Call name="addBean">
       <Arg>
         <New class="org.eclipse.jetty.server.deployer.WebAppDeployer">
-          <Set name="contexts"><Ref id="Contexts"/></Set>
+          <Set name="contexts"><Ref refid="Contexts"/></Set>
           <Set name="webAppDir"><SystemProperty name="jetty.home" default="."/>/webapps</Set>
     <Set name="parentLoaderPriority">false</Set>
     <Set name="extract">true</Set>
@@ -258,7 +256,7 @@
     <!-- contexts configuration (see $(jetty.home)/contexts/test.xml -->
     <!-- for an example).                                            -->
     <!-- =========================================================== -->
-    <Ref id="RequestLog">
+    <Ref refid="RequestLog">
       <Set name="requestLog">
         <New id="RequestLogImpl" class="org.eclipse.jetty.server.NCSARequestLog">
           <Set name="filename"><SystemProperty name="jetty.logs" default="./logs"/>/yyyy_mm_dd.request.log</Set>
@@ -276,8 +274,6 @@
     <!-- extra options                                               -->
     <!-- =========================================================== -->
     <Set name="stopAtShutdown">true</Set>
-    <Set name="sendServerVersion">true</Set>
-    <Set name="sendDateHeader">true</Set>
-    <Set name="gracefulShutdown">1000</Set>
+    <Set name="stopTimeout">1000</Set>
 
 </Configure>
diff --git a/jetty-rhttp/README.TXT b/jetty-rhttp/README.TXT
new file mode 100644
index 0000000..46ca4e7
--- /dev/null
+++ b/jetty-rhttp/README.TXT
@@ -0,0 +1,33 @@
+Reverse HTTP
+
+The HTTP server paradigm is a valuable abstraction for browsing and accessing data and applications in a RESTful fashion from thin clients or
+other applications.  However, when it comes to mobile devices, the server paradigm is often not available because those devices exist on
+restricted networks that do not allow inbound connections.    These devices (eg. phones, tablets, industrial controllers, etc.) often have
+signficant content (eg. photos, video, music, contacts, etc.) and services (eg. GPS, phone, modem, camera, sound) that are worthwile to access
+remotely and often the HTTP server model is very applicable.
+
+The Jetty reverse HTTP module provides a gateway that efficiently allows HTTP connectivety to servers running in outbound-only networks.  There are two key components:
+
+The reverse HTTP connector is a jetty connector (like the HTTP, SSL, AJP connectors) that accepts HTTP requests for the Jetty server instance.  However, the reverse HTTP connector does not accept inbound TCP/IP connections.  Instead it makes an outbound HTTP connection to the reverse HTTP gateway and uses a long polling mechanism to efficiently and asynchronously fetch requests and send responses.
+
+The reverse HTTP gateway is a jetty server that accepts inbound connections from one or more Reverse HTTP connectors and makes them available as normal HTTP targets.
+
+To demonstrate this from a source release, first run a gateway instance:
+
+    cd jetty-reverse-http/reverse-http-gateway
+    mvn exec:java
+
+In another window, you can run 3 test servers with reverse connectors with:
+
+    cd jetty-reverse-http/reverse-http-connector
+    mvn exec:java
+
+
+The three servers are using context path ID's at the gateway (virtual host and cookie based mappings can also be done), so you can access the
+three servers via the gateway at:
+
+    http://localhost:8080/gw/A
+    http://localhost:8080/gw/B
+    http://localhost:8080/gw/C
+
+
diff --git a/jetty-rhttp/jetty-rhttp-client/pom.xml b/jetty-rhttp/jetty-rhttp-client/pom.xml
new file mode 100644
index 0000000..14dc5b3
--- /dev/null
+++ b/jetty-rhttp/jetty-rhttp-client/pom.xml
@@ -0,0 +1,95 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>org.eclipse.jetty.rhttp</groupId>
+        <artifactId>jetty-rhttp-project</artifactId>
+        <version>9.0.0-SNAPSHOT</version>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>reverse-http-client</artifactId>
+    <packaging>jar</packaging>
+    <name>Jetty :: Reverse HTTP :: Client</name>
+
+    <properties>
+        <bundle-symbolic-name>${project.groupId}.rhttp.client</bundle-symbolic-name>
+    </properties>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>manifest</goal>
+                        </goals>
+                        <configuration>
+                            <instructions>
+                                 <Import-Package>*</Import-Package>
+                            </instructions>
+                          </configuration>
+                       </execution>
+                  </executions>
+            </plugin>
+            <plugin>
+              <!--
+              Required for OSGI
+              -->
+              <groupId>org.apache.maven.plugins</groupId>
+              <artifactId>maven-jar-plugin</artifactId>
+              <configuration>
+                  <archive>
+                      <manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
+                  </archive>
+              </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-util</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-io</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-client</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-http</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpclient</artifactId>
+            <version>4.0</version>
+        </dependency>
+        <dependency>
+            <groupId>net.jcip</groupId>
+            <artifactId>jcip-annotations</artifactId>
+            <version>1.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+          <groupId>org.eclipse.jetty.toolchain</groupId>
+          <artifactId>jetty-test-helper</artifactId>
+          <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-server</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+</project>
diff --git a/jetty-rhttp/jetty-rhttp-client/src/main/java/org/eclipse/jetty/rhttp/client/AbstractClient.java b/jetty-rhttp/jetty-rhttp-client/src/main/java/org/eclipse/jetty/rhttp/client/AbstractClient.java
new file mode 100644
index 0000000..cecc330
--- /dev/null
+++ b/jetty-rhttp/jetty-rhttp-client/src/main/java/org/eclipse/jetty/rhttp/client/AbstractClient.java
@@ -0,0 +1,270 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.rhttp.client;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * @version $Revision$ $Date$
+ */
+public abstract class AbstractClient extends AbstractLifeCycle implements RHTTPClient
+{
+    private final Logger logger = Log.getLogger("org.mortbay.jetty.rhttp.client");
+    private final List<RHTTPListener> listeners = new CopyOnWriteArrayList<RHTTPListener>();
+    private final List<ClientListener> clientListeners = new CopyOnWriteArrayList<ClientListener>();
+    private final String targetId;
+    private volatile Status status = Status.DISCONNECTED;
+
+    public AbstractClient(String targetId)
+    {
+        this.targetId = targetId;
+    }
+
+    public String getGatewayURI()
+    {
+        return "http://"+getHost()+":"+getPort()+getPath();
+    }
+
+    public String getTargetId()
+    {
+        return targetId;
+    }
+
+    public Logger getLogger()
+    {
+        return logger;
+    }
+
+    public void addListener(RHTTPListener listener)
+    {
+        listeners.add(listener);
+    }
+
+    public void removeListener(RHTTPListener listener)
+    {
+        listeners.remove(listener);
+    }
+
+    public void addClientListener(ClientListener listener)
+    {
+        clientListeners.add(listener);
+    }
+
+    public void removeClientListener(ClientListener listener)
+    {
+        clientListeners.remove(listener);
+    }
+
+    protected void notifyRequests(List<RHTTPRequest> requests)
+    {
+        for (RHTTPRequest request : requests)
+        {
+            for (RHTTPListener listener : listeners)
+            {
+                try
+                {
+                    listener.onRequest(request);
+                }
+                catch (Throwable x)
+                {
+                    logger.warn("Listener " + listener + " threw", x);
+                    try
+                    {
+                        deliver(newExceptionResponse(request.getId(), x));
+                    }
+                    catch (IOException xx)
+                    {
+                        logger.debug("Could not deliver exception response", xx);
+                    }
+                }
+            }
+        }
+    }
+
+    protected RHTTPResponse newExceptionResponse(int requestId, Throwable x)
+    {
+        try
+        {
+            int statusCode = 500;
+            String statusMessage = "Internal Server Error";
+            Map<String, String> headers = new HashMap<String, String>();
+            byte[] body = x.toString().getBytes("UTF-8");
+            return new RHTTPResponse(requestId, statusCode, statusMessage, headers, body);
+        }
+        catch (UnsupportedEncodingException xx)
+        {
+            throw new AssertionError(xx);
+        }
+    }
+
+    protected void notifyConnectRequired()
+    {
+        for (ClientListener listener : clientListeners)
+        {
+            try
+            {
+                listener.connectRequired();
+            }
+            catch (Throwable x)
+            {
+                logger.warn("ClientListener " + listener + " threw", x);
+            }
+        }
+    }
+
+    protected void notifyConnectException()
+    {
+        for (ClientListener listener : clientListeners)
+        {
+            try
+            {
+                listener.connectException();
+            }
+            catch (Throwable x)
+            {
+                logger.warn("ClientListener " + listener + " threw", x);
+            }
+        }
+    }
+
+    protected void notifyConnectClosed()
+    {
+        for (ClientListener listener : clientListeners)
+        {
+            try
+            {
+                listener.connectClosed();
+            }
+            catch (Throwable xx)
+            {
+                logger.warn("ClientListener " + listener + " threw", xx);
+            }
+        }
+    }
+
+    protected void notifyDeliverException(RHTTPResponse response)
+    {
+        for (ClientListener listener : clientListeners)
+        {
+            try
+            {
+                listener.deliverException(response);
+            }
+            catch (Throwable x)
+            {
+                logger.warn("ClientListener " + listener + " threw", x);
+            }
+        }
+    }
+
+    protected String urlEncode(String value)
+    {
+        try
+        {
+            return URLEncoder.encode(value, "UTF-8");
+        }
+        catch (UnsupportedEncodingException x)
+        {
+            getLogger().debug("", x);
+            return null;
+        }
+    }
+
+    protected boolean isConnected()
+    {
+        return status == Status.CONNECTED;
+    }
+
+    protected boolean isDisconnecting()
+    {
+        return status == Status.DISCONNECTING;
+    }
+
+    protected boolean isDisconnected()
+    {
+        return status == Status.DISCONNECTED;
+    }
+
+    public void connect() throws IOException
+    {
+        if (isDisconnected())
+            status = Status.CONNECTING;
+
+        syncHandshake();
+        this.status = Status.CONNECTED;
+
+        asyncConnect();
+    }
+
+    public void disconnect() throws IOException
+    {
+        if (isConnected())
+        {
+            status = Status.DISCONNECTING;
+            try
+            {
+                syncDisconnect();
+            }
+            finally
+            {
+                status = Status.DISCONNECTED;
+            }
+        }
+    }
+
+    public void deliver(RHTTPResponse response) throws IOException
+    {
+        asyncDeliver(response);
+    }
+
+    protected abstract void syncHandshake() throws IOException;
+
+    protected abstract void asyncConnect();
+
+    protected abstract void syncDisconnect() throws IOException;
+
+    protected abstract void asyncDeliver(RHTTPResponse response);
+
+    protected void connectComplete(byte[] responseContent) throws IOException
+    {
+        List<RHTTPRequest> requests = RHTTPRequest.fromFrameBytes(responseContent);
+        getLogger().debug("Client {} connect returned from gateway, requests {}", getTargetId(), requests);
+
+        // Requests are arrived, reconnect while we process them
+        if (!isDisconnecting() && !isDisconnected())
+            asyncConnect();
+
+        notifyRequests(requests);
+    }
+
+    protected enum Status
+    {
+        CONNECTING, CONNECTED, DISCONNECTING, DISCONNECTED
+    }
+}
diff --git a/jetty-rhttp/jetty-rhttp-client/src/main/java/org/eclipse/jetty/rhttp/client/ApacheClient.java b/jetty-rhttp/jetty-rhttp-client/src/main/java/org/eclipse/jetty/rhttp/client/ApacheClient.java
new file mode 100644
index 0000000..b21d1cd
--- /dev/null
+++ b/jetty-rhttp/jetty-rhttp-client/src/main/java/org/eclipse/jetty/rhttp/client/ApacheClient.java
@@ -0,0 +1,156 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.rhttp.client;
+
+import java.io.IOException;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpHost;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.NoHttpResponseException;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.ByteArrayEntity;
+import org.apache.http.util.EntityUtils;
+
+/**
+ * Implementation of {@link RHTTPClient} that uses Apache's HttpClient.
+ *
+ * @version $Revision$ $Date$
+ */
+public class ApacheClient extends AbstractClient
+{
+    private final HttpClient httpClient;
+    private final String gatewayPath;
+
+    public ApacheClient(HttpClient httpClient, String gatewayPath, String targetId)
+    {
+        super(targetId);
+        this.httpClient = httpClient;
+        this.gatewayPath = gatewayPath;
+    }
+
+    public String getHost()
+    {
+        return ((HttpHost)httpClient.getParams().getParameter("http.default-host")).getHostName();
+    }
+
+    public int getPort()
+    {
+        return ((HttpHost)httpClient.getParams().getParameter("http.default-host")).getPort();
+    }
+    
+    public String getPath()
+    {
+        return gatewayPath;
+    }
+
+    protected void syncHandshake() throws IOException
+    {
+        HttpPost handshake = new HttpPost(gatewayPath + "/" + urlEncode(getTargetId()) + "/handshake");
+        HttpResponse response = httpClient.execute(handshake);
+        int statusCode = response.getStatusLine().getStatusCode();
+        HttpEntity entity = response.getEntity();
+        if (entity != null)
+            entity.consumeContent();
+        if (statusCode != HttpStatus.SC_OK)
+            throw new IOException("Handshake failed");
+        getLogger().debug("Client {} handshake returned from gateway", getTargetId(), null);
+    }
+
+    protected void asyncConnect()
+    {
+        new Thread()
+        {
+            @Override
+            public void run()
+            {
+                try
+                {
+                    HttpPost connect = new HttpPost(gatewayPath + "/" + urlEncode(getTargetId()) + "/connect");
+                    getLogger().debug("Client {} connect sent to gateway", getTargetId(), null);
+                    HttpResponse response = httpClient.execute(connect);
+                    int statusCode = response.getStatusLine().getStatusCode();
+                    HttpEntity entity = response.getEntity();
+                    byte[] responseContent = EntityUtils.toByteArray(entity);
+                    if (statusCode == HttpStatus.SC_OK)
+                        connectComplete(responseContent);
+                    else if (statusCode == HttpStatus.SC_UNAUTHORIZED)
+                        notifyConnectRequired();
+                    else
+                        notifyConnectException();
+                }
+                catch (NoHttpResponseException x)
+                {
+                    notifyConnectClosed();
+                }
+                catch (IOException x)
+                {
+                    getLogger().debug("", x);
+                    notifyConnectException();
+                }
+            }
+        }.start();
+    }
+
+    protected void syncDisconnect() throws IOException
+    {
+        HttpPost disconnect = new HttpPost(gatewayPath + "/" + urlEncode(getTargetId()) + "/disconnect");
+        HttpResponse response = httpClient.execute(disconnect);
+        int statusCode = response.getStatusLine().getStatusCode();
+        HttpEntity entity = response.getEntity();
+        if (entity != null)
+            entity.consumeContent();
+        if (statusCode != HttpStatus.SC_OK)
+            throw new IOException("Disconnect failed");
+        getLogger().debug("Client {} disconnect returned from gateway", getTargetId(), null);
+    }
+
+    protected void asyncDeliver(final RHTTPResponse response)
+    {
+        new Thread()
+        {
+            @Override
+            public void run()
+            {
+                try
+                {
+                    HttpPost deliver = new HttpPost(gatewayPath + "/" + urlEncode(getTargetId()) + "/deliver");
+                    deliver.setEntity(new ByteArrayEntity(response.getFrameBytes()));
+                    getLogger().debug("Client {} deliver sent to gateway, response {}", getTargetId(), response);
+                    HttpResponse httpResponse = httpClient.execute(deliver);
+                    int statusCode = httpResponse.getStatusLine().getStatusCode();
+                    HttpEntity entity = httpResponse.getEntity();
+                    if (entity != null)
+                        entity.consumeContent();
+                    if (statusCode == HttpStatus.SC_UNAUTHORIZED)
+                        notifyConnectRequired();
+                    else if (statusCode != HttpStatus.SC_OK)
+                        notifyDeliverException(response);
+                }
+                catch (IOException x)
+                {
+                    getLogger().debug("", x);
+                    notifyDeliverException(response);
+                }
+            }
+        }.start();
+    }
+}
diff --git a/jetty-rhttp/jetty-rhttp-client/src/main/java/org/eclipse/jetty/rhttp/client/ClientListener.java b/jetty-rhttp/jetty-rhttp-client/src/main/java/org/eclipse/jetty/rhttp/client/ClientListener.java
new file mode 100644
index 0000000..dcb2940
--- /dev/null
+++ b/jetty-rhttp/jetty-rhttp-client/src/main/java/org/eclipse/jetty/rhttp/client/ClientListener.java
@@ -0,0 +1,67 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.rhttp.client;
+
+/**
+ * A listener for network-related events happening on the gateway client.
+ *
+ * @version $Revision$ $Date$
+ */
+public interface ClientListener
+{
+    /**
+     * Called when the client detects that the server requested a new connect.
+     */
+    public void connectRequired();
+
+    /**
+     * Called when the client detects that the connection has been closed by the server.
+     */
+    public void connectClosed();
+
+    /**
+     * Called when the client detects a generic exception while trying to connect to the server.
+     */
+    public void connectException();
+
+    /**
+     * Called when the client detects a generic exception while tryint to deliver to the server.
+     * @param response the Response object that should have been sent to the server
+     */
+    public void deliverException(RHTTPResponse response);
+
+    public static class Adapter implements ClientListener
+    {
+        public void connectRequired()
+        {
+        }
+
+        public void connectClosed()
+        {
+        }
+
+        public void connectException()
+        {
+        }
+
+        public void deliverException(RHTTPResponse response)
+        {
+        }
+    }
+}
diff --git a/jetty-rhttp/jetty-rhttp-client/src/main/java/org/eclipse/jetty/rhttp/client/JettyClient.java b/jetty-rhttp/jetty-rhttp-client/src/main/java/org/eclipse/jetty/rhttp/client/JettyClient.java
new file mode 100644
index 0000000..637b8fb
--- /dev/null
+++ b/jetty-rhttp/jetty-rhttp-client/src/main/java/org/eclipse/jetty/rhttp/client/JettyClient.java
@@ -0,0 +1,306 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.rhttp.client;
+
+import java.io.ByteArrayOutputStream;
+import java.io.EOFException;
+import java.io.IOException;
+
+import org.eclipse.jetty.client.Address;
+import org.eclipse.jetty.client.ContentExchange;
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.HttpExchange;
+import org.eclipse.jetty.http.HttpMethods;
+import org.eclipse.jetty.http.HttpURI;
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.ByteArrayBuffer;
+import org.eclipse.jetty.io.EofException;
+
+/**
+ * Implementation of {@link RHTTPClient} that uses Jetty's HttpClient.
+ *
+ * @version $Revision$ $Date$
+ */
+public class JettyClient extends AbstractClient
+{
+    private final HttpClient httpClient;
+    private final Address gatewayAddress;
+    private final String gatewayPath;
+
+    public JettyClient(HttpClient httpClient, Address gatewayAddress, String gatewayPath, String targetId)
+    {
+        super(targetId);
+        this.httpClient = httpClient;
+        this.gatewayAddress = gatewayAddress;
+        this.gatewayPath = gatewayPath;
+    }
+    
+    public JettyClient(HttpClient httpClient, String gatewayURI, String targetId)
+    {
+        super(targetId);
+        
+        HttpURI uri = new HttpURI(gatewayURI);
+        
+        this.httpClient = httpClient;
+        this.gatewayAddress = new Address(uri.getHost(),uri.getPort());
+        this.gatewayPath = uri.getPath();
+    }
+    
+    public String getHost()
+    {
+        return gatewayAddress.getHost();
+    }
+
+    public int getPort()
+    {
+        return gatewayAddress.getPort();
+    }
+
+    public String getPath()
+    {
+        return gatewayPath;
+    }
+    
+    @Override
+    protected void doStart() throws Exception
+    {
+        httpClient.start();
+        super.doStart();
+    }
+
+    @Override
+    protected void doStop() throws Exception
+    {
+        super.doStop();
+        httpClient.stop();
+    }
+
+    protected void syncHandshake() throws IOException
+    {
+        HandshakeExchange exchange = new HandshakeExchange();
+        exchange.setMethod(HttpMethods.POST);
+        exchange.setAddress(gatewayAddress);
+        exchange.setURI(gatewayPath + "/" + urlEncode(getTargetId()) + "/handshake");
+        httpClient.send(exchange);
+        getLogger().debug("Client {} handshake sent to gateway", getTargetId(), null);
+
+        try
+        {
+            int exchangeStatus = exchange.waitForDone();
+            if (exchangeStatus != HttpExchange.STATUS_COMPLETED)
+                throw new IOException("Handshake failed");
+            if (exchange.getResponseStatus() != 200)
+                throw new IOException("Handshake failed");
+            getLogger().debug("Client {} handshake returned from gateway", getTargetId(), null);
+        }
+        catch (InterruptedException x)
+        {
+            Thread.currentThread().interrupt();
+            throw newIOException(x);
+        }
+    }
+
+    private IOException newIOException(Throwable x)
+    {
+        return (IOException)new IOException().initCause(x);
+    }
+
+    protected void asyncConnect()
+    {
+        try
+        {
+            ConnectExchange exchange = new ConnectExchange();
+            exchange.setMethod(HttpMethods.POST);
+            exchange.setAddress(gatewayAddress);
+            exchange.setURI(gatewayPath + "/" + urlEncode(getTargetId()) + "/connect");
+            httpClient.send(exchange);
+            getLogger().debug("Client {} connect sent to gateway", getTargetId(), null);
+        }
+        catch (IOException x)
+        {
+            getLogger().debug("Could not send exchange", x);
+            throw new RuntimeException(x);
+        }
+    }
+
+    protected void syncDisconnect() throws IOException
+    {
+        DisconnectExchange exchange = new DisconnectExchange();
+        exchange.setMethod(HttpMethods.POST);
+        exchange.setAddress(gatewayAddress);
+        exchange.setURI(gatewayPath + "/" + urlEncode(getTargetId()) + "/disconnect");
+        httpClient.send(exchange);
+        getLogger().debug("Client {} disconnect sent to gateway", getTargetId(), null);
+        try
+        {
+            int status = exchange.waitForDone();
+            if (status != HttpExchange.STATUS_COMPLETED)
+                throw new IOException("Disconnect failed");
+            if (exchange.getResponseStatus() != 200)
+                throw new IOException("Disconnect failed");
+            getLogger().debug("Client {} disconnect returned from gateway", getTargetId(), null);
+        }
+        catch (InterruptedException x)
+        {
+            Thread.currentThread().interrupt();
+            throw newIOException(x);
+        }
+    }
+
+    protected void asyncDeliver(RHTTPResponse response)
+    {
+        try
+        {
+            DeliverExchange exchange = new DeliverExchange(response);
+            exchange.setMethod(HttpMethods.POST);
+            exchange.setAddress(gatewayAddress);
+            exchange.setURI(gatewayPath + "/" + urlEncode(getTargetId()) + "/deliver");
+            exchange.setRequestContent(new ByteArrayBuffer(response.getFrameBytes()));
+            httpClient.send(exchange);
+            getLogger().debug("Client {} deliver sent to gateway, response {}", getTargetId(), response);
+        }
+        catch (IOException x)
+        {
+            getLogger().debug("Could not send exchange", x);
+            throw new RuntimeException(x);
+        }
+    }
+
+    protected class HandshakeExchange extends ContentExchange
+    {
+        protected HandshakeExchange()
+        {
+            super(true);
+        }
+        
+        @Override
+        protected void onConnectionFailed(Throwable x)
+        {
+            getLogger().warn(x.toString());
+            getLogger().debug(x);
+        }
+    }
+
+    protected class ConnectExchange extends ContentExchange
+    {
+        private final ByteArrayOutputStream content = new ByteArrayOutputStream();
+
+        protected ConnectExchange()
+        {
+            super(true);
+        }
+
+        @Override
+        protected void onResponseContent(Buffer buffer) throws IOException
+        {
+            buffer.writeTo(content);
+        }
+
+        @Override
+        protected void onResponseComplete()
+        {
+            int responseStatus = getResponseStatus();
+            if (responseStatus == 200)
+            {
+                try
+                {
+                    connectComplete(content.toByteArray());
+                }
+                catch (IOException x)
+                {
+                    onException(x);
+                }
+            }
+            else if (responseStatus == 401)
+            {
+                notifyConnectRequired();
+            }
+            else
+            {
+                notifyConnectException();
+            }
+        }
+
+        @Override
+        protected void onException(Throwable x)
+        {
+            getLogger().debug(x);
+            if (x instanceof EofException || x instanceof EOFException)
+            {
+                notifyConnectClosed();
+            }
+            else
+            {
+                notifyConnectException();
+            }
+        }
+        
+        @Override
+        protected void onConnectionFailed(Throwable x)
+        {
+            getLogger().debug(x);
+        }
+    }
+
+    protected class DisconnectExchange extends ContentExchange
+    {
+        protected DisconnectExchange()
+        {
+            super(true);
+        }
+    }
+
+    protected class DeliverExchange extends ContentExchange
+    {
+        private final RHTTPResponse response;
+
+        protected DeliverExchange(RHTTPResponse response)
+        {
+            super(true);
+            this.response = response;
+        }
+
+        @Override
+        protected void onResponseComplete() throws IOException
+        {
+            int responseStatus = getResponseStatus();
+            if (responseStatus == 401)
+            {
+                notifyConnectRequired();
+            }
+            else if (responseStatus != 200)
+            {
+                notifyDeliverException(response);
+            }
+        }
+
+        @Override
+        protected void onException(Throwable x)
+        {
+            getLogger().debug(x);
+            notifyDeliverException(response);
+        }
+
+        @Override
+        protected void onConnectionFailed(Throwable x)
+        {
+            getLogger().debug(x);
+        }
+    }
+}
diff --git a/jetty-rhttp/jetty-rhttp-client/src/main/java/org/eclipse/jetty/rhttp/client/RHTTPClient.java b/jetty-rhttp/jetty-rhttp-client/src/main/java/org/eclipse/jetty/rhttp/client/RHTTPClient.java
new file mode 100644
index 0000000..2888bee
--- /dev/null
+++ b/jetty-rhttp/jetty-rhttp-client/src/main/java/org/eclipse/jetty/rhttp/client/RHTTPClient.java
@@ -0,0 +1,133 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.rhttp.client;
+
+import java.io.IOException;
+
+/**
+ * <p><tt>RHTTPClient</tt> represent a client of the gateway server.</p>
+ * <p>A <tt>Client</tt> has a server side counterpart with which communicates
+ * using a comet protocol.<br /> The <tt>Client</tt>, its server-side
+ * counterpart and the comet protocol form the <em>Half-Object plus Protocol</em>
+ * pattern.</p>
+ * <p>A <tt>Client</tt> must first connect to the gateway server, to let the gateway
+ * server know its targetId, an identifier that uniquely distinguish this
+ * <tt>Client</tt> from other <tt>Client</tt>s.</p>
+ * <p>Once connected, the gateway server will use a comet procotol to notify the
+ * <tt>Client</tt> of server-side events, and the <tt>Client</tt> can send
+ * information to the gateway server to notify it of client-side events.</p>
+ * <p>Server-side event are notified to {@link RHTTPListener}s, while relevant
+ * network events are communicated to {@link ClientListener}s.</p>
+ *
+ * @version $Revision$ $Date$
+ */
+public interface RHTTPClient
+{
+    /**
+     * @return The gateway uri, typically "http://gatewayhost:gatewayport/gatewaypath".
+     */
+    public String getGatewayURI();
+    
+    /**
+     * @return The gateway host
+     */
+    public String getHost();
+    
+    /**
+     * @return The gateway port
+     */
+    public int getPort();
+    
+    /**
+     * @return The gateway path
+     */
+    public String getPath();
+    
+    /**
+     * @return the targetId that uniquely identifies this client.
+     */
+    public String getTargetId();
+
+    /**
+     * <p>Connects to the gateway server, establishing the long poll communication
+     * with the gateway server to be notified of server-side events.</p>
+     * <p>The connect is performed in two steps:
+     * <ul>
+     * <li>first, a connect message is sent to the gateway server; the gateway server
+     * will notice this is a first connect message and reply immediately with
+     * an empty response</li>
+     * <li>second, another connect message is sent to the gateway server which interprets
+     * it as a long poll request</li>
+     * </ul>
+     * The long poll request may return either because one or more server-side events
+     * happened, or because it expired. </p>
+     * <p>Any connect message after the first is treated as a long poll request.</p>
+     *
+     * @throws IOException if it is not possible to connect to the gateway server
+     * @see #disconnect()
+     */
+    public void connect() throws IOException;
+
+    /**
+     * <p>Disconnects from the gateway server.</p>
+     * <p>Just after the disconnect request is processed by to the gateway server, it will
+     * return the currently outstanding long poll request.</p>
+     * <p>If this client is not connected, it does nothing</p>
+     *
+     * @throws IOException if it is not possible to contact the gateway server to disconnect
+     * @see #connect()
+     */
+    public void disconnect() throws IOException;
+
+    /**
+     * <p>Sends a response to the gateway server.</p>
+     *
+     * @param response the response to send
+     * @throws IOException if it is not possible to contact the gateway server
+     */
+    public void deliver(RHTTPResponse response) throws IOException;
+
+    /**
+     * <p>Adds the given listener to this client.</p>
+     * @param listener the listener to add
+     * @see #removeListener(RHTTPListener)
+     */
+    public void addListener(RHTTPListener listener);
+
+    /**
+     * <p>Removes the given listener from this client.</p>
+     * @param listener the listener to remove
+     * @see #addListener(RHTTPListener)
+     */
+    public void removeListener(RHTTPListener listener);
+
+    /**
+     * <p>Adds the given client listener to this client.</p>
+     * @param listener the client listener to add
+     * @see #removeClientListener(ClientListener)
+     */
+    public void addClientListener(ClientListener listener);
+
+    /**
+     * <p>Removes the given client listener from this client.</p>
+     * @param listener the client listener to remove
+     * @see #addClientListener(ClientListener)
+     */
+    public void removeClientListener(ClientListener listener);
+}
diff --git a/jetty-rhttp/jetty-rhttp-client/src/main/java/org/eclipse/jetty/rhttp/client/RHTTPListener.java b/jetty-rhttp/jetty-rhttp-client/src/main/java/org/eclipse/jetty/rhttp/client/RHTTPListener.java
new file mode 100644
index 0000000..5c4aa84
--- /dev/null
+++ b/jetty-rhttp/jetty-rhttp-client/src/main/java/org/eclipse/jetty/rhttp/client/RHTTPListener.java
@@ -0,0 +1,36 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.rhttp.client;
+
+/**
+ * <p>Implementations of this class listen for requests arriving from the gateway server
+ * and notified by {@link RHTTPClient}.</p>
+ *
+ * @version $Revision$ $Date$
+ */
+public interface RHTTPListener
+{
+    /**
+     * Callback method called by {@link RHTTPClient} to inform that the gateway server
+     * sent a request to the gateway client.
+     * @param request the request sent by the gateway server.
+     * @throws Exception allowed to be thrown by implementations
+     */
+    public void onRequest(RHTTPRequest request) throws Exception;
+}
diff --git a/jetty-rhttp/jetty-rhttp-client/src/main/java/org/eclipse/jetty/rhttp/client/RHTTPRequest.java b/jetty-rhttp/jetty-rhttp-client/src/main/java/org/eclipse/jetty/rhttp/client/RHTTPRequest.java
new file mode 100644
index 0000000..c77f06a
--- /dev/null
+++ b/jetty-rhttp/jetty-rhttp-client/src/main/java/org/eclipse/jetty/rhttp/client/RHTTPRequest.java
@@ -0,0 +1,266 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.rhttp.client;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.jetty.http.HttpParser;
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.ByteArrayBuffer;
+
+/**
+ * <p>Represents the external request information that is carried over the comet protocol.</p>
+ * <p>Instances of this class are converted into an opaque byte array of the form:</p>
+ * <pre>
+ * <request-id> SPACE <request-length> CRLF
+ * <external-request>
+ * </pre>
+ * <p>The byte array form is carried as body of a normal HTTP response returned by the gateway server
+ * to the gateway client.</p>
+ * @see RHTTPResponse
+ * @version $Revision$ $Date$
+ */
+public class RHTTPRequest
+{
+    private static final String CRLF = "\r\n";
+    private static final byte[] CRLF_BYTES = CRLF.getBytes();
+
+    private final int id;
+    private final byte[] requestBytes;
+    private final byte[] frameBytes;
+    private volatile String method;
+    private volatile String uri;
+    private volatile Map<String, String> headers;
+    private volatile byte[] body;
+
+    public static List<RHTTPRequest> fromFrameBytes(byte[] bytes)
+    {
+        List<RHTTPRequest> result = new ArrayList<RHTTPRequest>();
+        int start = 0;
+        while (start < bytes.length)
+        {
+            // Scan until we find the space
+            int end = start;
+            while (bytes[end] != ' ') ++end;
+            int requestId = Integer.parseInt(new String(bytes, start, end - start));
+            start = end + 1;
+
+            // Scan until end of line
+            while (bytes[end] != '\n') ++end;
+            int length = Integer.parseInt(new String(bytes, start, end - start - 1));
+            start = end + 1;
+
+            byte[] requestBytes = new byte[length];
+            System.arraycopy(bytes, start, requestBytes, 0, length);
+            RHTTPRequest request = fromRequestBytes(requestId, requestBytes);
+            result.add(request);
+            start += length;
+        }
+        return result;
+    }
+
+    public static RHTTPRequest fromRequestBytes(int requestId, byte[] requestBytes)
+    {
+        return new RHTTPRequest(requestId, requestBytes);
+    }
+
+    public RHTTPRequest(int id, String method, String uri, Map<String, String> headers, byte[] body)
+    {
+        this.id = id;
+        this.method = method;
+        this.uri = uri;
+        this.headers = headers;
+        this.body = body;
+        this.requestBytes = toRequestBytes();
+        this.frameBytes = toFrameBytes(requestBytes);
+    }
+
+    private RHTTPRequest(int id, byte[] requestBytes)
+    {
+        this.id = id;
+        this.requestBytes = requestBytes;
+        this.frameBytes = toFrameBytes(requestBytes);
+        // Other fields are lazily initialized
+    }
+
+    private void initialize()
+    {
+        try
+        {
+            final ByteArrayOutputStream body = new ByteArrayOutputStream();
+            HttpParser parser = new HttpParser(new ByteArrayBuffer(requestBytes), new HttpParser.EventHandler()
+            {
+                @Override
+                public void startRequest(Buffer method, Buffer uri, Buffer httpVersion) throws IOException
+                {
+                    RHTTPRequest.this.method = method.toString("UTF-8");
+                    RHTTPRequest.this.uri = uri.toString("UTF-8");
+                    RHTTPRequest.this.headers = new LinkedHashMap<String, String>();
+                }
+
+                @Override
+                public void startResponse(Buffer httpVersion, int statusCode, Buffer statusMessage) throws IOException
+                {
+                }
+
+                @Override
+                public void parsedHeader(Buffer name, Buffer value) throws IOException
+                {
+                    RHTTPRequest.this.headers.put(name.toString("UTF-8"), value.toString("UTF-8"));
+                }
+
+                @Override
+                public void content(Buffer content) throws IOException
+                {
+                    content.writeTo(body);
+                }
+            });
+            parser.parse();
+            this.body = body.toByteArray();
+        }
+        catch (IOException x)
+        {
+            // Cannot happen: we're parsing from a byte[], not from an I/O stream
+            throw new AssertionError(x);
+        }
+    }
+
+    public int getId()
+    {
+        return id;
+    }
+
+    public byte[] getRequestBytes()
+    {
+        return requestBytes;
+    }
+
+    public byte[] getFrameBytes()
+    {
+        return frameBytes;
+    }
+
+    public String getMethod()
+    {
+        if (method == null)
+            initialize();
+        return method;
+    }
+
+    public String getURI()
+    {
+        if (uri == null)
+            initialize();
+        return uri;
+    }
+
+    public Map<String, String> getHeaders()
+    {
+        if (headers == null)
+            initialize();
+        return headers;
+    }
+
+    public byte[] getBody()
+    {
+        if (body == null)
+            initialize();
+        return body;
+    }
+
+    private byte[] toRequestBytes()
+    {
+        try
+        {
+            ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+            bytes.write(method.getBytes("UTF-8"));
+            bytes.write(' ');
+            bytes.write(uri.getBytes("UTF-8"));
+            bytes.write(' ');
+            bytes.write("HTTP/1.1".getBytes("UTF-8"));
+            bytes.write(CRLF_BYTES);
+            for (Map.Entry<String, String> entry : headers.entrySet())
+            {
+                bytes.write(entry.getKey().getBytes("UTF-8"));
+                bytes.write(':');
+                bytes.write(' ');
+                bytes.write(entry.getValue().getBytes("UTF-8"));
+                bytes.write(CRLF_BYTES);
+            }
+            bytes.write(CRLF_BYTES);
+            bytes.write(body);
+            bytes.close();
+            return bytes.toByteArray();
+        }
+        catch (IOException x)
+        {
+            throw new AssertionError(x);
+        }
+    }
+
+    private byte[] toFrameBytes(byte[] requestBytes)
+    {
+        try
+        {
+            ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+            bytes.write(String.valueOf(id).getBytes("UTF-8"));
+            bytes.write(' ');
+            bytes.write(String.valueOf(requestBytes.length).getBytes("UTF-8"));
+            bytes.write(CRLF_BYTES);
+            bytes.write(requestBytes);
+            bytes.close();
+            return bytes.toByteArray();
+        }
+        catch (IOException x)
+        {
+            throw new AssertionError(x);
+        }
+    }
+
+    @Override
+    public String toString()
+    {
+        // Use fields to avoid initialization
+        StringBuilder builder = new StringBuilder();
+        builder.append(id).append(" ");
+        builder.append(method).append(" ");
+        builder.append(uri).append(" ");
+        builder.append(requestBytes.length).append("/");
+        builder.append(frameBytes.length);
+        return builder.toString();
+    }
+
+    public String toLongString()
+    {
+        // Use getters to trigger initialization
+        StringBuilder builder = new StringBuilder();
+        builder.append(id).append(" ");
+        builder.append(getMethod()).append(" ");
+        builder.append(getURI()).append(CRLF);
+        for (Map.Entry<String, String> header : getHeaders().entrySet())
+            builder.append(header.getKey()).append(": ").append(header.getValue()).append(CRLF);
+        builder.append(getBody().length).append(" body bytes").append(CRLF);
+        return builder.toString();
+    }
+}
diff --git a/jetty-rhttp/jetty-rhttp-client/src/main/java/org/eclipse/jetty/rhttp/client/RHTTPResponse.java b/jetty-rhttp/jetty-rhttp-client/src/main/java/org/eclipse/jetty/rhttp/client/RHTTPResponse.java
new file mode 100644
index 0000000..f9dd30e
--- /dev/null
+++ b/jetty-rhttp/jetty-rhttp-client/src/main/java/org/eclipse/jetty/rhttp/client/RHTTPResponse.java
@@ -0,0 +1,256 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.rhttp.client;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.eclipse.jetty.http.HttpParser;
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.ByteArrayBuffer;
+
+/**
+ * <p>Represents the resource provider response information that is carried over the comet protocol.</p>
+ * <p>Instances of this class are converted into an opaque byte array of the form:</p>
+ * <pre>
+ * <request-id> SPACE <response-length> CRLF
+ * <resource-response>
+ * </pre>
+ * <p>The byte array form is carried as body of a normal HTTP request made by the gateway client to
+ * the gateway server.</p>
+ * @see RHTTPRequest
+ * @version $Revision$ $Date$
+ */
+public class RHTTPResponse
+{
+    private static final String CRLF = "\r\n";
+    private static final byte[] CRLF_BYTES = CRLF.getBytes();
+
+    private final int id;
+    private final byte[] responseBytes;
+    private final byte[] frameBytes;
+    private volatile int code;
+    private volatile String message;
+    private volatile Map<String, String> headers;
+    private volatile byte[] body;
+
+    public static RHTTPResponse fromFrameBytes(byte[] bytes)
+    {
+        int start = 0;
+        // Scan until we find the space
+        int end = start;
+        while (bytes[end] != ' ') ++end;
+        int responseId = Integer.parseInt(new String(bytes, start, end - start));
+        start = end + 1;
+
+        // Scan until end of line
+        while (bytes[end] != '\n') ++end;
+        int length = Integer.parseInt(new String(bytes, start, end - start - 1));
+        start = end + 1;
+
+        byte[] responseBytes = new byte[length];
+        System.arraycopy(bytes, start, responseBytes, 0, length);
+        return fromResponseBytes(responseId, responseBytes);
+    }
+
+    public static RHTTPResponse fromResponseBytes(int id, byte[] responseBytes)
+    {
+        return new RHTTPResponse(id, responseBytes);
+    }
+
+    public RHTTPResponse(int id, int code, String message, Map<String, String> headers, byte[] body)
+    {
+        this.id = id;
+        this.code = code;
+        this.message = message;
+        this.headers = headers;
+        this.body = body;
+        this.responseBytes = toResponseBytes();
+        this.frameBytes = toFrameBytes(responseBytes);
+    }
+
+    private RHTTPResponse(int id, byte[] responseBytes)
+    {
+        this.id = id;
+        this.responseBytes = responseBytes;
+        this.frameBytes = toFrameBytes(responseBytes);
+        // Other fields are lazily initialized
+    }
+
+    private void initialize()
+    {
+        try
+        {
+            final ByteArrayOutputStream body = new ByteArrayOutputStream();
+            HttpParser parser = new HttpParser(new ByteArrayBuffer(responseBytes), new HttpParser.EventHandler()
+            {
+                @Override
+                public void startRequest(Buffer method, Buffer uri, Buffer httpVersion) throws IOException
+                {
+                }
+
+                @Override
+                public void startResponse(Buffer httpVersion, int statusCode, Buffer statusMessage) throws IOException
+                {
+                    RHTTPResponse.this.code = statusCode;
+                    RHTTPResponse.this.message = statusMessage.toString("UTF-8");
+                    RHTTPResponse.this.headers = new LinkedHashMap<String, String>();
+                }
+
+                @Override
+                public void parsedHeader(Buffer name, Buffer value) throws IOException
+                {
+                    RHTTPResponse.this.headers.put(name.toString("UTF-8"), value.toString("UTF-8"));
+                }
+
+                @Override
+                public void content(Buffer content) throws IOException
+                {
+                    content.writeTo(body);
+                }
+            });
+            parser.parse();
+            this.body = body.toByteArray();
+        }
+        catch (IOException x)
+        {
+            // Cannot happen: we're parsing from a byte[], not from an I/O stream
+            throw new AssertionError(x);
+        }
+    }
+
+    public int getId()
+    {
+        return id;
+    }
+
+    public byte[] getResponseBytes()
+    {
+        return responseBytes;
+    }
+
+    public byte[] getFrameBytes()
+    {
+        return frameBytes;
+    }
+
+    public int getStatusCode()
+    {
+        if (code == 0)
+            initialize();
+        return code;
+    }
+
+    public String getStatusMessage()
+    {
+        if (message == null)
+            initialize();
+        return message;
+    }
+
+    public Map<String, String> getHeaders()
+    {
+        if (headers == null)
+            initialize();
+        return headers;
+    }
+
+    public byte[] getBody()
+    {
+        if (body == null)
+            initialize();
+        return body;
+    }
+
+    private byte[] toResponseBytes()
+    {
+        try
+        {
+            ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+            bytes.write("HTTP/1.1".getBytes("UTF-8"));
+            bytes.write(' ');
+            bytes.write(String.valueOf(code).getBytes("UTF-8"));
+            bytes.write(' ');
+            bytes.write(message.getBytes("UTF-8"));
+            bytes.write(CRLF_BYTES);
+            for (Map.Entry<String, String> entry : headers.entrySet())
+            {
+                bytes.write(entry.getKey().getBytes("UTF-8"));
+                bytes.write(':');
+                bytes.write(' ');
+                bytes.write(entry.getValue().getBytes("UTF-8"));
+                bytes.write(CRLF_BYTES);
+            }
+            bytes.write(CRLF_BYTES);
+            bytes.write(body);
+            bytes.close();
+            return bytes.toByteArray();
+        }
+        catch (IOException x)
+        {
+            throw new AssertionError(x);
+        }
+    }
+
+    private byte[] toFrameBytes(byte[] responseBytes)
+    {
+        try
+        {
+            ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+            bytes.write(String.valueOf(id).getBytes("UTF-8"));
+            bytes.write(' ');
+            bytes.write(String.valueOf(responseBytes.length).getBytes("UTF-8"));
+            bytes.write(CRLF_BYTES);
+            bytes.write(responseBytes);
+            return bytes.toByteArray();
+        }
+        catch (IOException x)
+        {
+            throw new AssertionError(x);
+        }
+    }
+
+    @Override
+    public String toString()
+    {
+        // Use fields to avoid initialization
+        StringBuilder builder = new StringBuilder();
+        builder.append(id).append(" ");
+        builder.append(code).append(" ");
+        builder.append(message).append(" ");
+        builder.append(responseBytes.length).append("/");
+        builder.append(frameBytes.length);
+        return builder.toString();
+    }
+
+    public String toLongString()
+    {
+        // Use getters to trigger initialization
+        StringBuilder builder = new StringBuilder();
+        builder.append(id).append(" ");
+        builder.append(getStatusCode()).append(" ");
+        builder.append(getStatusMessage()).append(CRLF);
+        for (Map.Entry<String, String> header : getHeaders().entrySet())
+            builder.append(header.getKey()).append(": ").append(header.getValue()).append(CRLF);
+        builder.append(getBody().length).append(" body bytes").append(CRLF);
+        return builder.toString();
+    }
+}
diff --git a/jetty-rhttp/jetty-rhttp-client/src/main/java/org/eclipse/jetty/rhttp/client/RetryingApacheClient.java b/jetty-rhttp/jetty-rhttp-client/src/main/java/org/eclipse/jetty/rhttp/client/RetryingApacheClient.java
new file mode 100644
index 0000000..45f8d8e
--- /dev/null
+++ b/jetty-rhttp/jetty-rhttp-client/src/main/java/org/eclipse/jetty/rhttp/client/RetryingApacheClient.java
@@ -0,0 +1,112 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.rhttp.client;
+
+import java.io.IOException;
+
+import org.apache.http.client.HttpClient;
+
+/**
+ * @version $Revision$ $Date$
+ */
+public class RetryingApacheClient extends ApacheClient
+{
+    public RetryingApacheClient(HttpClient httpClient, String gatewayURI, String targetId)
+    {
+        super(httpClient, gatewayURI, targetId);
+        addClientListener(new RetryClientListener());
+    }
+
+    @Override
+    protected void syncHandshake() throws IOException
+    {
+        while (true)
+        {
+            try
+            {
+                super.syncHandshake();
+                break;
+            }
+            catch (IOException x)
+            {
+                getLogger().debug("Handshake failed, backing off and retrying");
+                try
+                {
+                    Thread.sleep(1000);
+                }
+                catch (InterruptedException xx)
+                {
+                    throw (IOException)new IOException().initCause(xx);
+                }
+            }
+        }
+    }
+
+    private class RetryClientListener implements ClientListener
+    {
+        public void connectRequired()
+        {
+            getLogger().debug("Connect requested by server");
+            try
+            {
+                connect();
+            }
+            catch (IOException x)
+            {
+                // The connect() method is retried, so if it fails, it's a hard failure
+                getLogger().debug("Connect failed after server required connect, giving up");
+            }
+        }
+
+        public void connectClosed()
+        {
+            connectException();
+        }
+
+        public void connectException()
+        {
+            getLogger().debug("Connect failed, backing off and retrying");
+            try
+            {
+                Thread.sleep(1000);
+                asyncConnect();
+            }
+            catch (InterruptedException x)
+            {
+                // Ignore and stop retrying
+                Thread.currentThread().interrupt();
+            }
+        }
+
+        public void deliverException(RHTTPResponse response)
+        {
+            getLogger().debug("Deliver failed, backing off and retrying");
+            try
+            {
+                Thread.sleep(1000);
+                asyncDeliver(response);
+            }
+            catch (InterruptedException x)
+            {
+                // Ignore and stop retrying
+                Thread.currentThread().interrupt();
+            }
+        }
+    }
+}
diff --git a/jetty-rhttp/jetty-rhttp-client/src/test/java/org/eclipse/jetty/rhttp/client/ApacheClientTest.java b/jetty-rhttp/jetty-rhttp-client/src/test/java/org/eclipse/jetty/rhttp/client/ApacheClientTest.java
new file mode 100644
index 0000000..dc4a1c4
--- /dev/null
+++ b/jetty-rhttp/jetty-rhttp-client/src/test/java/org/eclipse/jetty/rhttp/client/ApacheClientTest.java
@@ -0,0 +1,75 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.rhttp.client;
+
+import java.io.IOException;
+
+import org.apache.http.HttpHost;
+import org.apache.http.client.HttpRequestRetryHandler;
+import org.apache.http.conn.ClientConnectionManager;
+import org.apache.http.conn.scheme.PlainSocketFactory;
+import org.apache.http.conn.scheme.Scheme;
+import org.apache.http.conn.scheme.SchemeRegistry;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
+import org.apache.http.params.BasicHttpParams;
+import org.apache.http.params.HttpParams;
+import org.apache.http.protocol.HttpContext;
+import org.eclipse.jetty.rhttp.client.ApacheClient;
+import org.eclipse.jetty.rhttp.client.RHTTPClient;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.StdErrLog;
+
+
+/**
+ * @version $Revision$ $Date$
+ */
+public class ApacheClientTest extends ClientTest
+{
+    {
+        ((StdErrLog)Log.getLog()).setHideStacks(!Log.getLog().isDebugEnabled());
+    }
+    
+    private ClientConnectionManager connectionManager;
+
+    protected RHTTPClient createClient(int port, String targetId) throws Exception
+    {
+        SchemeRegistry schemeRegistry = new SchemeRegistry();
+        schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), port));
+        connectionManager = new ThreadSafeClientConnManager(new BasicHttpParams(), schemeRegistry);
+        HttpParams httpParams = new BasicHttpParams();
+        httpParams.setParameter("http.default-host", new HttpHost("localhost", port));
+        DefaultHttpClient httpClient = new DefaultHttpClient(connectionManager, httpParams);
+        httpClient.setHttpRequestRetryHandler(new NoRetryHandler());
+        return new ApacheClient(httpClient, "", targetId);
+    }
+
+    protected void destroyClient(RHTTPClient client) throws Exception
+    {
+        connectionManager.shutdown();
+    }
+
+    private class NoRetryHandler implements HttpRequestRetryHandler
+    {
+        public boolean retryRequest(IOException x, int failedAttempts, HttpContext httpContext)
+        {
+            return false;
+        }
+    }
+}
diff --git a/jetty-rhttp/jetty-rhttp-client/src/test/java/org/eclipse/jetty/rhttp/client/ClientTest.java b/jetty-rhttp/jetty-rhttp-client/src/test/java/org/eclipse/jetty/rhttp/client/ClientTest.java
new file mode 100644
index 0000000..5412ac7
--- /dev/null
+++ b/jetty-rhttp/jetty-rhttp-client/src/test/java/org/eclipse/jetty/rhttp/client/ClientTest.java
@@ -0,0 +1,299 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.rhttp.client;
+
+import java.io.IOException;
+import java.util.LinkedHashMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import junit.framework.TestCase;
+
+import org.eclipse.jetty.rhttp.client.ClientListener;
+import org.eclipse.jetty.rhttp.client.RHTTPClient;
+import org.eclipse.jetty.rhttp.client.RHTTPResponse;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.bio.SocketConnector;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.server.nio.SelectChannelConnector;
+import org.eclipse.jetty.util.log.Log;
+
+
+/**
+ * @version $Revision$ $Date$
+ */
+public abstract class ClientTest extends TestCase
+{
+    protected abstract RHTTPClient createClient(int port, String targetId) throws Exception;
+
+    protected abstract void destroyClient(RHTTPClient client) throws Exception;
+
+    public void testConnectNoServer() throws Exception
+    {
+        RHTTPClient client = createClient(8080, "test1");
+        try
+        {
+            client.connect();
+            fail();
+        }
+        catch (IOException x)
+        {
+        }
+        finally
+        {
+            destroyClient(client);
+        }
+    }
+
+    public void testServerExceptionOnHandshake() throws Exception
+    {
+        final CountDownLatch serverLatch = new CountDownLatch(1);
+
+        Server server = new Server();
+        Connector connector = new SelectChannelConnector();
+        server.addConnector(connector);
+        server.setHandler(new AbstractHandler()
+        {
+            public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException
+            {
+                request.setHandled(true);
+                if (target.endsWith("/handshake"))
+                {
+                    serverLatch.countDown();
+                    throw new TestException();
+                }
+            }
+        });
+        server.start();
+        try
+        {
+            RHTTPClient client = createClient(connector.getLocalPort(), "test2");
+            try
+            {
+                try
+                {
+                    client.connect();
+                    fail();
+                }
+                catch (IOException x)
+                {
+                }
+
+                assertTrue(serverLatch.await(1000, TimeUnit.MILLISECONDS));
+            }
+            finally
+            {
+                destroyClient(client);
+            }
+        }
+        finally
+        {
+            server.stop();
+        }
+    }
+
+    public void testServerExceptionOnConnect() throws Exception
+    {
+        final CountDownLatch serverLatch = new CountDownLatch(1);
+
+        Server server = new Server();
+        Connector connector = new SelectChannelConnector();
+        server.addConnector(connector);
+        server.setHandler(new AbstractHandler()
+        {
+            public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException
+            {
+                request.setHandled(true);
+                if (target.endsWith("/connect"))
+                {
+                    serverLatch.countDown();
+                    throw new TestException();
+                }
+            }
+        });
+        server.start();
+        try
+        {
+            RHTTPClient client = createClient(connector.getLocalPort(), "test3");
+            try
+            {
+                final CountDownLatch connectLatch = new CountDownLatch(1);
+                client.addClientListener(new ClientListener.Adapter()
+                {
+                    @Override
+                    public void connectException()
+                    {
+                        connectLatch.countDown();
+                    }
+                });
+                client.connect();
+
+                assertTrue(serverLatch.await(1000, TimeUnit.MILLISECONDS));
+                assertTrue(connectLatch.await(1000, TimeUnit.MILLISECONDS));
+            }
+            finally
+            {
+                destroyClient(client);
+            }
+        }
+        finally
+        {
+            server.stop();
+        }
+    }
+
+    public void testServerExceptionOnDeliver() throws Exception
+    {
+        final CountDownLatch serverLatch = new CountDownLatch(1);
+
+        Server server = new Server();
+        Connector connector = new SelectChannelConnector();
+        server.addConnector(connector);
+        server.setHandler(new AbstractHandler()
+        {
+            public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException
+            {
+                request.setHandled(true);
+                if (target.endsWith("/connect"))
+                {
+                    serverLatch.countDown();
+                    try
+                    {
+                        // Simulate a long poll timeout
+                        Thread.sleep(10000);
+                    }
+                    catch (InterruptedException x)
+                    {
+                        Thread.currentThread().interrupt();
+                    }
+                }
+                else if (target.endsWith("/deliver"))
+                {
+                    // Throw an exception on deliver
+                    throw new TestException();
+                }
+            }
+        });
+        server.start();
+        try
+        {
+            RHTTPClient client = createClient(connector.getLocalPort(), "test4");
+            try
+            {
+                final CountDownLatch deliverLatch = new CountDownLatch(1);
+                client.addClientListener(new ClientListener.Adapter()
+                {
+                    @Override
+                    public void deliverException(RHTTPResponse response)
+                    {
+                        deliverLatch.countDown();
+                    }
+                });
+                client.connect();
+
+                assertTrue(serverLatch.await(1000, TimeUnit.MILLISECONDS));
+
+                client.deliver(new RHTTPResponse(1, 200, "OK", new LinkedHashMap<String, String>(), new byte[0]));
+
+                assertTrue(deliverLatch.await(1000, TimeUnit.MILLISECONDS));
+            }
+            finally
+            {
+                destroyClient(client);
+            }
+        }
+        finally
+        {
+            server.stop();
+        }
+    }
+
+    public void testServerShutdownAfterConnect() throws Exception
+    {
+        final CountDownLatch connectLatch = new CountDownLatch(1);
+        final CountDownLatch stopLatch = new CountDownLatch(1);
+
+        Server server = new Server();
+        Connector connector = new SocketConnector();
+        server.addConnector(connector);
+        server.setHandler(new AbstractHandler()
+        {
+            public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException
+            {
+                request.setHandled(true);
+                if (target.endsWith("/connect"))
+                {
+                    connectLatch.countDown();
+                    try
+                    {
+                        Thread.sleep(10000);
+                    }
+                    catch (InterruptedException e)
+                    {
+                        stopLatch.countDown();
+                    }
+                }
+            }
+        });
+        server.start();
+        try
+        {
+            RHTTPClient client = createClient(connector.getLocalPort(), "test5");
+            try
+            {
+                final CountDownLatch serverLatch = new CountDownLatch(1);
+                client.addClientListener(new ClientListener.Adapter()
+                {
+                    @Override
+                    public void connectClosed()
+                    {
+                        serverLatch.countDown();
+                    }
+                });
+                client.connect();
+
+                assertTrue(connectLatch.await(2000, TimeUnit.MILLISECONDS));
+
+                server.stop();
+                assertTrue(stopLatch.await(2000, TimeUnit.MILLISECONDS));
+
+                assertTrue(serverLatch.await(2000, TimeUnit.MILLISECONDS));
+            }
+            finally
+            {
+                destroyClient(client);
+            }
+        }
+        finally
+        {
+            server.stop();
+        }
+    }
+    
+    public static class TestException extends NullPointerException
+    {
+        
+    }
+}
diff --git a/jetty-rhttp/jetty-rhttp-client/src/test/java/org/eclipse/jetty/rhttp/client/JettyClientTest.java b/jetty-rhttp/jetty-rhttp-client/src/test/java/org/eclipse/jetty/rhttp/client/JettyClientTest.java
new file mode 100644
index 0000000..761f000
--- /dev/null
+++ b/jetty-rhttp/jetty-rhttp-client/src/test/java/org/eclipse/jetty/rhttp/client/JettyClientTest.java
@@ -0,0 +1,52 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.rhttp.client;
+
+import org.eclipse.jetty.client.Address;
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.rhttp.client.JettyClient;
+import org.eclipse.jetty.rhttp.client.RHTTPClient;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.StdErrLog;
+
+
+/**
+ * @version $Revision$ $Date$
+ */
+public class JettyClientTest extends ClientTest
+{
+    {
+        ((StdErrLog)Log.getLog()).setHideStacks(!Log.getLog().isDebugEnabled());
+    }
+    
+    private HttpClient httpClient;
+
+    protected RHTTPClient createClient(int port, String targetId) throws Exception
+    {
+        ((StdErrLog)Log.getLog()).setSource(true);
+        httpClient = new HttpClient();
+        httpClient.start();
+        return new JettyClient(httpClient, new Address("localhost", port), "", targetId);
+    }
+
+    protected void destroyClient(RHTTPClient client) throws Exception
+    {
+        httpClient.stop();
+    }
+}
diff --git a/jetty-rhttp/jetty-rhttp-client/src/test/java/org/eclipse/jetty/rhttp/client/RequestTest.java b/jetty-rhttp/jetty-rhttp-client/src/test/java/org/eclipse/jetty/rhttp/client/RequestTest.java
new file mode 100644
index 0000000..8fbf735
--- /dev/null
+++ b/jetty-rhttp/jetty-rhttp-client/src/test/java/org/eclipse/jetty/rhttp/client/RequestTest.java
@@ -0,0 +1,85 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.rhttp.client;
+
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.jetty.rhttp.client.RHTTPRequest;
+
+import junit.framework.TestCase;
+
+/**
+ * @version $Revision$ $Date$
+ */
+public class RequestTest extends TestCase
+{
+    public void testRequestConversions() throws Exception
+    {
+        int id = 1;
+        String method = "GET";
+        String uri = "/test";
+        Map<String, String> headers = new LinkedHashMap<String, String>();
+        headers.put("X", "X");
+        headers.put("Y", "Y");
+        headers.put("Z", "Z");
+        byte[] body = "BODY".getBytes("UTF-8");
+        headers.put("Content-Length", String.valueOf(body.length));
+        RHTTPRequest request1 = new RHTTPRequest(id, method, uri, headers, body);
+        byte[] requestBytes1 = request1.getRequestBytes();
+        RHTTPRequest request2 = RHTTPRequest.fromRequestBytes(id, requestBytes1);
+        assertEquals(id, request2.getId());
+        assertEquals(method, request2.getMethod());
+        assertEquals(uri, request2.getURI());
+        assertEquals(headers, request2.getHeaders());
+        assertTrue(Arrays.equals(request2.getBody(), body));
+
+        byte[] requestBytes2 = request2.getRequestBytes();
+        assertTrue(Arrays.equals(requestBytes1, requestBytes2));
+    }
+
+    public void testFrameConversions() throws Exception
+    {
+        int id = 1;
+        String method = "GET";
+        String uri = "/test";
+        Map<String, String> headers = new LinkedHashMap<String, String>();
+        headers.put("X", "X");
+        headers.put("Y", "Y");
+        headers.put("Z", "Z");
+        byte[] body = "BODY".getBytes("UTF-8");
+        headers.put("Content-Length", String.valueOf(body.length));
+        RHTTPRequest request1 = new RHTTPRequest(id, method, uri, headers, body);
+        byte[] frameBytes1 = request1.getFrameBytes();
+        List<RHTTPRequest> requests = RHTTPRequest.fromFrameBytes(frameBytes1);
+        assertNotNull(requests);
+        assertEquals(1, requests.size());
+        RHTTPRequest request2 = requests.get(0);
+        assertEquals(id, request2.getId());
+        assertEquals(method, request2.getMethod());
+        assertEquals(uri, request2.getURI());
+        assertEquals(headers, request2.getHeaders());
+        assertTrue(Arrays.equals(request2.getBody(), body));
+
+        byte[] frameBytes2 = request2.getFrameBytes();
+        assertTrue(Arrays.equals(frameBytes1, frameBytes2));
+    }
+}
diff --git a/jetty-rhttp/jetty-rhttp-client/src/test/java/org/eclipse/jetty/rhttp/client/ResponseTest.java b/jetty-rhttp/jetty-rhttp-client/src/test/java/org/eclipse/jetty/rhttp/client/ResponseTest.java
new file mode 100644
index 0000000..c2e90f9
--- /dev/null
+++ b/jetty-rhttp/jetty-rhttp-client/src/test/java/org/eclipse/jetty/rhttp/client/ResponseTest.java
@@ -0,0 +1,85 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.rhttp.client;
+
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.eclipse.jetty.rhttp.client.RHTTPResponse;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.StdErrLog;
+
+import junit.framework.TestCase;
+
+/**
+ * @version $Revision$ $Date$
+ */
+public class ResponseTest extends TestCase
+{
+    {
+        ((StdErrLog)Log.getLog()).setHideStacks(!Log.getLog().isDebugEnabled());
+    }
+    
+    public void testResponseConversions() throws Exception
+    {
+        int id = 1;
+        int statusCode = 200;
+        String statusMessage = "OK";
+        Map<String, String> headers = new LinkedHashMap<String, String>();
+        headers.put("X", "X");
+        headers.put("Y", "Y");
+        headers.put("Z", "Z");
+        byte[] body = "BODY".getBytes("UTF-8");
+        RHTTPResponse response1 = new RHTTPResponse(id, statusCode, statusMessage, headers, body);
+        byte[] responseBytes1 = response1.getResponseBytes();
+        RHTTPResponse response2 = RHTTPResponse.fromResponseBytes(id, responseBytes1);
+        assertEquals(id, response2.getId());
+        assertEquals(statusCode, response2.getStatusCode());
+        assertEquals(statusMessage, response2.getStatusMessage());
+        assertEquals(headers, response2.getHeaders());
+        assertTrue(Arrays.equals(response2.getBody(), body));
+
+        byte[] responseBytes2 = response2.getResponseBytes();
+        assertTrue(Arrays.equals(responseBytes1, responseBytes2));
+    }
+
+    public void testFrameConversions() throws Exception
+    {
+        int id = 1;
+        int statusCode = 200;
+        String statusMessage = "OK";
+        Map<String, String> headers = new LinkedHashMap<String, String>();
+        headers.put("X", "X");
+        headers.put("Y", "Y");
+        headers.put("Z", "Z");
+        byte[] body = "BODY".getBytes("UTF-8");
+        RHTTPResponse response1 = new RHTTPResponse(id, statusCode, statusMessage, headers, body);
+        byte[] frameBytes1 = response1.getFrameBytes();
+        RHTTPResponse response2 = RHTTPResponse.fromFrameBytes(frameBytes1);
+        assertEquals(id, response2.getId());
+        assertEquals(statusCode, response2.getStatusCode());
+        assertEquals(response2.getStatusMessage(), statusMessage);
+        assertEquals(headers, response2.getHeaders());
+        assertTrue(Arrays.equals(response2.getBody(), body));
+
+        byte[] frameBytes2 = response2.getFrameBytes();
+        assertTrue(Arrays.equals(frameBytes1, frameBytes2));
+    }
+}
diff --git a/jetty-rhttp/jetty-rhttp-connector/pom.xml b/jetty-rhttp/jetty-rhttp-connector/pom.xml
new file mode 100644
index 0000000..b248c48
--- /dev/null
+++ b/jetty-rhttp/jetty-rhttp-connector/pom.xml
@@ -0,0 +1,96 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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/xsd/maven-4.0.0.xsd">
+  <parent>
+    <groupId>org.eclipse.jetty.rhttp</groupId>
+    <artifactId>jetty-rhttp-project</artifactId>
+    <version>9.0.0-SNAPSHOT</version>
+  </parent>
+
+  <modelVersion>4.0.0</modelVersion>
+  <artifactId>reverse-http-connector</artifactId>
+  <packaging>jar</packaging>
+  <name>Jetty :: Reverse HTTP :: Connector</name>
+
+  <properties>
+      <bundle-symbolic-name>${project.groupId}.rhttp.connector</bundle-symbolic-name>
+  </properties>
+
+  <build>
+      <plugins>
+          <plugin>
+              <groupId>org.codehaus.mojo</groupId>
+              <artifactId>exec-maven-plugin</artifactId>
+              <configuration>
+                <classpathScope>test</classpathScope>
+                <mainClass>org.eclipse.jetty.rhttp.connector.TestReverseServer</mainClass>
+              </configuration>
+          </plugin>
+          <plugin>
+              <groupId>org.apache.felix</groupId>
+              <artifactId>maven-bundle-plugin</artifactId>
+              <extensions>true</extensions>
+              <executions>
+                  <execution>
+                      <goals>
+                          <goal>manifest</goal>
+                      </goals>
+                      <configuration>
+                          <instructions>
+                               <Import-Package>*</Import-Package>
+                          </instructions>
+                        </configuration>
+                     </execution>
+                </executions>
+            </plugin>
+            <plugin>
+              <!--
+              Required for OSGI
+              -->
+              <groupId>org.apache.maven.plugins</groupId>
+              <artifactId>maven-jar-plugin</artifactId>
+              <configuration>
+                  <archive>
+                      <manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
+                  </archive>
+              </configuration>
+            </plugin>
+      </plugins>
+  </build>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>reverse-http-client</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-util</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-io</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-server</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>example-jetty-embedded</artifactId>
+      <version>${project.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.toolchain</groupId>
+      <artifactId>jetty-test-helper</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>javax.servlet</groupId>
+      <artifactId>javax.servlet-api</artifactId>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+</project>
diff --git a/jetty-rhttp/jetty-rhttp-connector/src/main/config/etc/jetty-rhttp.xml b/jetty-rhttp/jetty-rhttp-connector/src/main/config/etc/jetty-rhttp.xml
new file mode 100644
index 0000000..2141794
--- /dev/null
+++ b/jetty-rhttp/jetty-rhttp-connector/src/main/config/etc/jetty-rhttp.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0"?>
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
+
+<!-- =============================================================== -->
+<!-- Configure the Jetty Reverse HTTP Connector                      -->
+<!-- =============================================================== -->
+<Configure id="Server" class="org.eclipse.jetty.server.Server">
+
+  <Call name="addConnector">
+    <Arg>
+      <New class="org.eclipse.jetty.rhttp.connector.ReverseHTTPConnector">
+        <New class="org.eclipse.jetty.rhttp.client.JettyClient">
+	  <Arg>
+	    <New class="HttpClient">
+	    </New>
+	  </Arg>
+	  <Arg>http://localhost:8888/</Arg>
+	  <Arg>nodeA</Arg>
+        </New>
+      </New>
+    </Arg>
+  </Call>
+</Configure>
diff --git a/jetty-rhttp/jetty-rhttp-connector/src/main/java/org/eclipse/jetty/rhttp/connector/ReverseHTTPConnector.java b/jetty-rhttp/jetty-rhttp-connector/src/main/java/org/eclipse/jetty/rhttp/connector/ReverseHTTPConnector.java
new file mode 100644
index 0000000..895ba43
--- /dev/null
+++ b/jetty-rhttp/jetty-rhttp-connector/src/main/java/org/eclipse/jetty/rhttp/connector/ReverseHTTPConnector.java
@@ -0,0 +1,170 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.rhttp.connector;
+
+import java.io.IOException;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+
+import org.eclipse.jetty.io.ByteArrayEndPoint;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.rhttp.client.RHTTPClient;
+import org.eclipse.jetty.rhttp.client.RHTTPListener;
+import org.eclipse.jetty.rhttp.client.RHTTPRequest;
+import org.eclipse.jetty.rhttp.client.RHTTPResponse;
+import org.eclipse.jetty.server.AbstractConnector;
+import org.eclipse.jetty.server.AbstractHttpConnection;
+import org.eclipse.jetty.server.BlockingHttpConnection;
+import org.eclipse.jetty.util.component.LifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * An implementation of a Jetty connector that uses a {@link RHTTPClient} connected
+ * to a gateway server to receive requests, feed them to the Jetty server, and
+ * forward responses from the Jetty server to the gateway server.
+ *
+ * @version $Revision$ $Date$
+ */
+public class ReverseHTTPConnector extends AbstractConnector implements RHTTPListener
+{
+    private static final Logger LOG = Log.getLogger(ReverseHTTPConnector.class);
+
+    private final BlockingQueue<RHTTPRequest> requests = new LinkedBlockingQueue<RHTTPRequest>();
+    private final RHTTPClient client;
+
+    public ReverseHTTPConnector(RHTTPClient client)
+    {
+        this.client = client;
+        super.setHost(client.getHost());
+        super.setPort(client.getPort());
+    }
+
+    @Override
+    public void setHost(String host)
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void setPort(int port)
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    protected void doStart() throws Exception
+    {
+        if (client instanceof LifeCycle)
+            ((LifeCycle)client).start();
+        super.doStart();
+        client.connect();
+    }
+
+    @Override
+    protected void doStop() throws Exception
+    {
+        client.disconnect();
+        super.doStop();
+        if (client instanceof LifeCycle)
+            ((LifeCycle)client).stop();
+    }
+
+    public void open()
+    {
+        client.addListener(this);
+    }
+
+    public void close()
+    {
+        client.removeListener(this);
+    }
+
+    public int getLocalPort()
+    {
+        return -1;
+    }
+
+    public Object getConnection()
+    {
+        return this;
+    }
+
+    @Override
+    protected void accept(int acceptorId) throws IOException, InterruptedException
+    {
+        RHTTPRequest request = requests.take();
+        IncomingRequest incomingRequest = new IncomingRequest(request);
+        getThreadPool().dispatch(incomingRequest);
+    }
+
+    @Override
+    public void persist(EndPoint endpoint) throws IOException
+    {
+        // Signals that the connection should not be closed
+        // Do nothing in this case, as we run from memory
+    }
+
+    public void onRequest(RHTTPRequest request) throws Exception
+    {
+        requests.add(request);
+    }
+
+    private class IncomingRequest implements Runnable
+    {
+        private final RHTTPRequest request;
+
+        private IncomingRequest(RHTTPRequest request)
+        {
+            this.request = request;
+        }
+
+        public void run()
+        {
+            byte[] requestBytes = request.getRequestBytes();
+
+            ByteArrayEndPoint endPoint = new ByteArrayEndPoint(requestBytes, 1024);
+            endPoint.setGrowOutput(true);
+
+            AbstractHttpConnection connection = new BlockingHttpConnection(ReverseHTTPConnector.this, endPoint, getServer());
+            
+            connectionOpened(connection);
+            try
+            {
+                // Loop over the whole content, since handle() only
+                // reads up to the connection buffer's capacities
+                while (endPoint.getIn().length() > 0)
+                    connection.handle();
+
+                byte[] responseBytes = endPoint.getOut().asArray();
+                RHTTPResponse response = RHTTPResponse.fromResponseBytes(request.getId(), responseBytes);
+                client.deliver(response);
+            }
+            catch (Exception x)
+            {
+                LOG.debug(x);
+            }
+            finally
+            {
+                connectionClosed(connection);
+            }
+        }
+    }
+}
diff --git a/jetty-rhttp/jetty-rhttp-connector/src/test/java/org/eclipse/jetty/rhttp/connector/ReverseHTTPConnectorTest.java b/jetty-rhttp/jetty-rhttp-connector/src/test/java/org/eclipse/jetty/rhttp/connector/ReverseHTTPConnectorTest.java
new file mode 100644
index 0000000..9944663
--- /dev/null
+++ b/jetty-rhttp/jetty-rhttp-connector/src/test/java/org/eclipse/jetty/rhttp/connector/ReverseHTTPConnectorTest.java
@@ -0,0 +1,187 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.rhttp.connector;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import junit.framework.TestCase;
+
+import org.eclipse.jetty.rhttp.client.ClientListener;
+import org.eclipse.jetty.rhttp.client.RHTTPClient;
+import org.eclipse.jetty.rhttp.client.RHTTPListener;
+import org.eclipse.jetty.rhttp.client.RHTTPRequest;
+import org.eclipse.jetty.rhttp.client.RHTTPResponse;
+import org.eclipse.jetty.rhttp.connector.ReverseHTTPConnector;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+
+/**
+ * @version $Revision$ $Date$
+ */
+public class ReverseHTTPConnectorTest extends TestCase
+{
+    public void testGatewayConnectorWithoutRequestBody() throws Exception
+    {
+        testGatewayConnector(false);
+    }
+
+    public void testGatewayConnectorWithRequestBody() throws Exception
+    {
+        testGatewayConnector(true);
+    }
+
+    private void testGatewayConnector(boolean withRequestBody) throws Exception
+    {
+        Server server = new Server();
+        final CountDownLatch handlerLatch = new CountDownLatch(1);
+        CountDownLatch clientLatch = new CountDownLatch(1);
+        AtomicReference<RHTTPResponse> responseRef = new AtomicReference<RHTTPResponse>();
+        ReverseHTTPConnector connector = new ReverseHTTPConnector(new TestClient(clientLatch, responseRef));
+        server.addConnector(connector);
+        final String method = "POST";
+        final String uri = "/test";
+        final byte[] requestBody = withRequestBody ? "REQUEST-BODY".getBytes("UTF-8") : new byte[0];
+        final int statusCode = HttpServletResponse.SC_CREATED;
+        final String headerName = "foo";
+        final String headerValue = "bar";
+        final byte[] responseBody = "RESPONSE-BODY".getBytes("UTF-8");
+        server.setHandler(new AbstractHandler()
+        {
+            public void handle(String pathInfo, org.eclipse.jetty.server.Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException
+            {
+                assertEquals(method, httpRequest.getMethod());
+                assertEquals(uri, httpRequest.getRequestURI());
+                assertEquals(headerValue, httpRequest.getHeader(headerName));
+                ByteArrayOutputStream baos = new ByteArrayOutputStream();
+                InputStream input = httpRequest.getInputStream();
+                int read;
+                while ((read = input.read()) >= 0)
+                    baos.write(read);
+                baos.close();
+                assertTrue(Arrays.equals(requestBody, baos.toByteArray()));
+
+                httpResponse.setStatus(statusCode);
+                httpResponse.setHeader(headerName, headerValue);
+                OutputStream output = httpResponse.getOutputStream();
+                output.write(responseBody);
+                output.flush();
+                request.setHandled(true);
+                handlerLatch.countDown();
+            }
+        });
+        server.start();
+
+        HashMap<String, String> headers = new HashMap<String, String>();
+        headers.put("Host", "localhost");
+        headers.put(headerName, headerValue);
+        headers.put("Content-Length", String.valueOf(requestBody.length));
+        RHTTPRequest request = new RHTTPRequest(1, method, uri, headers, requestBody);
+        request = RHTTPRequest.fromRequestBytes(request.getId(), request.getRequestBytes());
+        connector.onRequest(request);
+
+        assertTrue(handlerLatch.await(1000, TimeUnit.MILLISECONDS));
+        assertTrue(clientLatch.await(1000, TimeUnit.MILLISECONDS));
+        RHTTPResponse response = responseRef.get();
+        assertEquals(request.getId(), response.getId());
+        assertEquals(statusCode, response.getStatusCode());
+        assertEquals(headerValue, response.getHeaders().get(headerName));
+        assertTrue(Arrays.equals(response.getBody(), responseBody));
+    }
+
+    private class TestClient implements RHTTPClient
+    {
+        private final CountDownLatch latch;
+        private final AtomicReference<RHTTPResponse> responseRef;
+
+        private TestClient(CountDownLatch latch, AtomicReference<RHTTPResponse> response)
+        {
+            this.latch = latch;
+            this.responseRef = response;
+        }
+
+        public String getTargetId()
+        {
+            return null;
+        }
+
+        public void connect() throws IOException
+        {
+        }
+
+        public void disconnect() throws IOException
+        {
+        }
+
+        public void deliver(RHTTPResponse response) throws IOException
+        {
+            responseRef.set(response);
+            latch.countDown();
+        }
+
+        public void addListener(RHTTPListener listener)
+        {
+        }
+
+        public void removeListener(RHTTPListener listener)
+        {
+        }
+
+        public void addClientListener(ClientListener listener)
+        {
+        }
+
+        public void removeClientListener(ClientListener listener)
+        {
+        }
+
+        public String getHost()
+        {
+            return null;
+        }
+
+        public int getPort()
+        {
+            return 0;
+        }
+
+        public String getGatewayURI()
+        {
+            // TODO Auto-generated method stub
+            return null;
+        }
+
+        public String getPath()
+        {
+            // TODO Auto-generated method stub
+            return null;
+        }
+    }
+}
diff --git a/jetty-rhttp/jetty-rhttp-connector/src/test/java/org/eclipse/jetty/rhttp/connector/TestReverseServer.java b/jetty-rhttp/jetty-rhttp-connector/src/test/java/org/eclipse/jetty/rhttp/connector/TestReverseServer.java
new file mode 100644
index 0000000..8ff8107
--- /dev/null
+++ b/jetty-rhttp/jetty-rhttp-connector/src/test/java/org/eclipse/jetty/rhttp/connector/TestReverseServer.java
@@ -0,0 +1,58 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.rhttp.connector;
+
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.embedded.HelloHandler;
+import org.eclipse.jetty.rhttp.client.JettyClient;
+import org.eclipse.jetty.rhttp.client.RHTTPClient;
+import org.eclipse.jetty.rhttp.connector.ReverseHTTPConnector;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.util.log.Log;
+
+/**
+ * A Test content server that uses a {@link ReverseHTTPConnector}.
+ * The main of this class starts 3 TestReversionServers with IDs A, B and C.
+ */
+public class TestReverseServer extends Server
+{
+    TestReverseServer(String targetId)
+    {
+        setHandler(new HelloHandler("Hello "+targetId,"Hi from "+targetId));
+        
+        HttpClient httpClient = new HttpClient();
+        RHTTPClient client = new JettyClient(httpClient,"http://localhost:8080/__rhttp",targetId);
+        ReverseHTTPConnector connector = new ReverseHTTPConnector(client);
+        
+        addConnector(connector);
+    }
+    
+    public static void main(String... args) throws Exception
+    {
+        Log.getLogger("org.mortbay.jetty.rhttp.client").setDebugEnabled(true);
+        
+        TestReverseServer[] node = new TestReverseServer[] { new TestReverseServer("A"),new TestReverseServer("B"),new TestReverseServer("C") };
+        
+        for (TestReverseServer s : node)
+            s.start();
+
+        for (TestReverseServer s : node)
+            s.join();
+    }
+}
diff --git a/jetty-rhttp/jetty-rhttp-gateway/pom.xml b/jetty-rhttp/jetty-rhttp-gateway/pom.xml
new file mode 100644
index 0000000..054bbd5
--- /dev/null
+++ b/jetty-rhttp/jetty-rhttp-gateway/pom.xml
@@ -0,0 +1,99 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>org.eclipse.jetty.rhttp</groupId>
+        <artifactId>jetty-rhttp-project</artifactId>
+        <version>9.0.0-SNAPSHOT</version>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>reverse-http-gateway</artifactId>
+    <packaging>jar</packaging>
+    <name>Jetty :: Reverse HTTP :: Gateway</name>
+
+    <properties>
+        <bundle-symbolic-name>${project.groupId}.rhttp.gateway</bundle-symbolic-name>
+    </properties>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>exec-maven-plugin</artifactId>
+                <configuration>
+                  <mainClass>org.mortbay.jetty.rhttp.gateway.Main</mainClass>
+                  <arguments>
+                  </arguments>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>manifest</goal>
+                        </goals>
+                        <configuration>
+                            <instructions>
+                                 <Import-Package>*</Import-Package>
+                            </instructions>
+                          </configuration>
+                       </execution>
+                  </executions>
+            </plugin>
+            <plugin>
+              <!--
+              Required for OSGI
+              -->
+              <groupId>org.apache.maven.plugins</groupId>
+              <artifactId>maven-jar-plugin</artifactId>
+              <configuration>
+                  <archive>
+                      <manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
+                  </archive>
+              </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>reverse-http-client</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>javax.servlet-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-io</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-continuation</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-server</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-servlet</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-client</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty.toolchain</groupId>
+            <artifactId>jetty-test-helper</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/jetty-rhttp/jetty-rhttp-gateway/src/main/java/org/eclipse/jetty/rhttp/gateway/ClientDelegate.java b/jetty-rhttp/jetty-rhttp-gateway/src/main/java/org/eclipse/jetty/rhttp/gateway/ClientDelegate.java
new file mode 100644
index 0000000..192bf1f
--- /dev/null
+++ b/jetty-rhttp/jetty-rhttp-gateway/src/main/java/org/eclipse/jetty/rhttp/gateway/ClientDelegate.java
@@ -0,0 +1,92 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.rhttp.gateway;
+
+import java.io.IOException;
+import java.util.List;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.eclipse.jetty.rhttp.client.RHTTPRequest;
+
+
+/**
+ * <p>A <tt>ClientDelegate</tt> is the server-side counterpart of a gateway client.</p>
+ * <p>The gateway client, the comet protocol and the <tt>ClientDelegate</tt> form the
+ * <em>Half-Object plus Protocol</em> pattern that is used between the gateway server
+ * and the gateway client.</p>
+ * <p><tt>ClientDelegate</tt> offers a server-side API on top of the comet communication.<br />
+ * The API allows to enqueue server-side events to the gateway client, allows to
+ * flush them to the gateway client, and allows to close and dispose server-side
+ * resources when the gateway client disconnects.</p>
+ *
+ * @version $Revision$ $Date$
+ */
+public interface ClientDelegate
+{
+    /**
+     * @return the targetId that uniquely identifies this client delegate.
+     */
+    public String getTargetId();
+
+    /**
+     * <p>Enqueues the given request to the delivery queue so that it will be sent to the
+     * gateway client on the first flush occasion.</p>
+     * <p>Requests may fail to be queued, for example because the gateway client disconnected
+     * concurrently.</p>
+     *
+     * @param request the request to add to the delivery queue
+     * @return whether the request has been queued or not
+     * @see #process(HttpServletRequest)
+     */
+    public boolean enqueue(RHTTPRequest request);
+
+    /**
+     * <p>Flushes the requests that have been {@link #enqueue(RHTTPRequest) enqueued}.</p>
+     * <p>If no requests have been enqueued, then this method may suspend the current request for
+     * the long poll timeout. <br />
+     * The request is suspended only if all these conditions holds true:
+     * <ul>
+     * <li>it is not the first time that this method is called for this client delegate</li>
+     * <li>no requests have been enqueued</li>
+     * <li>this client delegate is not closed</li>
+     * <li>the previous call to this method did not suspend the request</li>
+     * </ul>
+     * In all other cases, a response if sent to the gateway client, possibly containing no requests.
+     *
+     * @param httpRequest the HTTP request for the long poll request from the gateway client
+     * @return the list of requests to send to the gateway client, or null if no response should be sent
+     * to the gateway client
+     * @throws IOException in case of I/O exception while flushing content to the gateway client
+     * @see #enqueue(RHTTPRequest)
+     */
+    public List<RHTTPRequest> process(HttpServletRequest httpRequest) throws IOException;
+
+    /**
+     * <p>Closes this client delegate, in response to a gateway client request to disconnect.</p>
+     * @see #isClosed()
+     */
+    public void close();
+
+    /**
+     * @return whether this delegate client is closed
+     * @see #close()
+     */
+    public boolean isClosed();
+}
diff --git a/jetty-rhttp/jetty-rhttp-gateway/src/main/java/org/eclipse/jetty/rhttp/gateway/ConnectorServlet.java b/jetty-rhttp/jetty-rhttp-gateway/src/main/java/org/eclipse/jetty/rhttp/gateway/ConnectorServlet.java
new file mode 100644
index 0000000..d477375
--- /dev/null
+++ b/jetty-rhttp/jetty-rhttp-gateway/src/main/java/org/eclipse/jetty/rhttp/gateway/ConnectorServlet.java
@@ -0,0 +1,224 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.rhttp.gateway;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.rhttp.client.RHTTPRequest;
+import org.eclipse.jetty.rhttp.client.RHTTPResponse;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * The servlet that handles the communication with the gateway clients.
+ * @version $Revision$ $Date$
+ */
+public class ConnectorServlet extends HttpServlet
+{
+    private final Logger logger = Log.getLogger(getClass().toString());
+    private final TargetIdRetriever targetIdRetriever = new StandardTargetIdRetriever();
+    private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
+    private final ConcurrentMap<String, Future<?>> expirations = new ConcurrentHashMap<String, Future<?>>();
+    private final Gateway gateway;
+    private long clientTimeout=15000;
+
+    public ConnectorServlet(Gateway gateway)
+    {
+        this.gateway = gateway;
+    }
+
+    @Override
+    public void init() throws ServletException 
+    {
+        String t = getInitParameter("clientTimeout");
+        if (t!=null && !"".equals(t))
+            clientTimeout=Long.parseLong(t);
+    }
+
+    @Override
+    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+    {
+        String targetId = targetIdRetriever.retrieveTargetId(request);
+
+        String uri = request.getRequestURI();
+        String path = uri.substring(request.getServletPath().length());
+        String[] segments = path.split("/");
+        if (segments.length < 3)
+            throw new ServletException("Invalid request to " + getClass().getSimpleName() + ": " + uri);
+
+        String action = segments[2];
+        if ("handshake".equals(action))
+            serviceHandshake(targetId, request, response);
+        else if ("connect".equals(action))
+            serviceConnect(targetId, request, response);
+        else if ("deliver".equals(action))
+            serviceDeliver(targetId, request, response);
+        else if ("disconnect".equals(action))
+            serviceDisconnect(targetId, request, response);
+        else
+            throw new ServletException("Invalid request to " + getClass().getSimpleName() + ": " + uri);
+    }
+
+    private void serviceHandshake(String targetId, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException
+    {
+        ClientDelegate client = gateway.getClientDelegate(targetId);
+        if (client != null)
+            throw new IOException("Client with targetId " + targetId + " is already connected");
+
+        client = gateway.newClientDelegate(targetId);
+        ClientDelegate existing = gateway.addClientDelegate(targetId, client);
+        if (existing != null)
+            throw new IOException("Client with targetId " + targetId + " is already connected");
+
+        flush(client, httpRequest, httpResponse);
+    }
+
+    private void flush(ClientDelegate client, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException
+    {
+        List<RHTTPRequest> requests = client.process(httpRequest);
+        if (requests != null)
+        {
+            // Schedule before sending the requests, to avoid that the remote client
+            // reconnects before we have scheduled the expiration timeout.
+            if (!client.isClosed())
+                schedule(client);
+
+            ServletOutputStream output = httpResponse.getOutputStream();
+            for (RHTTPRequest request : requests)
+                output.write(request.getFrameBytes());
+            // I could count the framed bytes of all requests and set a Content-Length header,
+            // but the implementation of ServletOutputStream takes care of everything:
+            // if the request was HTTP/1.1, then flushing result in a chunked response, but the
+            // client know how to handle it; if the request was HTTP/1.0, then no chunking.
+            // To avoid chunking in HTTP/1.1 I must set the Content-Length header.
+            output.flush();
+            logger.debug("Delivered to device {} requests {} ", client.getTargetId(), requests);
+        }
+    }
+
+    private void schedule(ClientDelegate client)
+    {
+        Future<?> task = scheduler.schedule(new ClientExpirationTask(client), clientTimeout, TimeUnit.MILLISECONDS);
+        Future<?> existing = expirations.put(client.getTargetId(), task);
+        assert existing == null;
+    }
+
+    private void unschedule(String targetId)
+    {
+        Future<?> task = expirations.remove(targetId);
+        if (task != null)
+            task.cancel(false);
+    }
+
+    private void serviceConnect(String targetId, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException
+    {
+        unschedule(targetId);
+
+        ClientDelegate client = gateway.getClientDelegate(targetId);
+        if (client == null)
+        {
+            // Expired client tries to connect without handshake
+            httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED);
+            return;
+        }
+
+        flush(client, httpRequest, httpResponse);
+
+        if (client.isClosed())
+            gateway.removeClientDelegate(targetId);
+    }
+
+    private void expireConnect(ClientDelegate client, long time)
+    {
+        String targetId = client.getTargetId();
+        logger.info("Client with targetId {} missing, last seen {} ms ago, closing it", targetId, System.currentTimeMillis() - time);
+        client.close();
+        // If the client expired, means that it did not connect,
+        // so there no request to resume, and we cleanup here
+        // (while normally this cleanup is done in serviceConnect())
+        unschedule(targetId);
+        gateway.removeClientDelegate(targetId);
+    }
+
+    private void serviceDeliver(String targetId, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws ServletException, IOException
+    {
+        if (gateway.getClientDelegate(targetId) == null)
+        {
+            // Expired client tries to deliver without handshake
+            httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED);
+            return;
+        }
+
+        byte[] body = Utils.read(httpRequest.getInputStream());
+
+        RHTTPResponse response = RHTTPResponse.fromFrameBytes(body);
+
+        ExternalRequest externalRequest = gateway.removeExternalRequest(response.getId());
+        if (externalRequest != null)
+        {
+            externalRequest.respond(response);
+            logger.debug("Deliver request from device {}, gateway request {}, response {}", new Object[] {targetId, externalRequest, response});
+        }
+        else
+        {
+            // We can arrive here for a race with the continuation expiration, which expired just before
+            // the gateway client responded with a valid response; log this case ignore it.
+            logger.debug("Deliver request from device {}, missing gateway request, response {}", targetId, response);
+        }
+    }
+
+    private void serviceDisconnect(String targetId, HttpServletRequest request, HttpServletResponse response)
+    {
+        // Do not remove the ClientDelegate from the gateway here,
+        // since closing the ClientDelegate will resume the connect request
+        // and we remove the ClientDelegate from the gateway there
+        ClientDelegate client = gateway.getClientDelegate(targetId);
+        if (client != null)
+            client.close();
+    }
+
+    private class ClientExpirationTask implements Runnable
+    {
+        private final long time = System.currentTimeMillis();
+        private final ClientDelegate client;
+
+        public ClientExpirationTask(ClientDelegate client)
+        {
+            this.client = client;
+        }
+
+        public void run()
+        {
+            expireConnect(client, time);
+        }
+    }
+}
diff --git a/jetty-rhttp/jetty-rhttp-gateway/src/main/java/org/eclipse/jetty/rhttp/gateway/ExternalRequest.java b/jetty-rhttp/jetty-rhttp-gateway/src/main/java/org/eclipse/jetty/rhttp/gateway/ExternalRequest.java
new file mode 100644
index 0000000..3142795
--- /dev/null
+++ b/jetty-rhttp/jetty-rhttp-gateway/src/main/java/org/eclipse/jetty/rhttp/gateway/ExternalRequest.java
@@ -0,0 +1,54 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.rhttp.gateway;
+
+import java.io.IOException;
+
+import org.eclipse.jetty.rhttp.client.RHTTPRequest;
+import org.eclipse.jetty.rhttp.client.RHTTPResponse;
+
+
+/**
+ * <p><tt>ExternalRequest</tt> represent an external request made to the gateway server.</p>
+ * <p><tt>ExternalRequest</tt>s that arrive to the gateway server are suspended, waiting
+ * for a response from the corresponding gateway client.</p>
+ *
+ * @version $Revision$ $Date$
+ */
+public interface ExternalRequest
+{
+    /**
+     * <p>Suspends this <tt>ExternalRequest</tt> waiting for a response from the gateway client.</p>
+     * @return true if the <tt>ExternalRequest</tt> has been suspended, false if the
+     * <tt>ExternalRequest</tt> has already been responded.
+     */
+    public boolean suspend();
+
+    /**
+     * <p>Responds to the original external request with the response arrived from the gateway client.</p>
+     * @param response the response arrived from the gateway client
+     * @throws IOException if responding to the original external request fails
+     */
+    public void respond(RHTTPResponse response) throws IOException;
+
+    /**
+     * @return the request to be sent to the gateway client
+     */
+    public RHTTPRequest getRequest();
+}
diff --git a/jetty-rhttp/jetty-rhttp-gateway/src/main/java/org/eclipse/jetty/rhttp/gateway/ExternalServlet.java b/jetty-rhttp/jetty-rhttp-gateway/src/main/java/org/eclipse/jetty/rhttp/gateway/ExternalServlet.java
new file mode 100644
index 0000000..6bda723
--- /dev/null
+++ b/jetty-rhttp/jetty-rhttp-gateway/src/main/java/org/eclipse/jetty/rhttp/gateway/ExternalServlet.java
@@ -0,0 +1,88 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.rhttp.gateway;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.rhttp.client.RHTTPRequest;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * The servlet that handles external requests.
+ *
+ * @version $Revision$ $Date$
+ */
+public class ExternalServlet extends HttpServlet
+{
+    private final Logger logger = Log.getLogger(getClass().toString());
+    private final Gateway gateway;
+    private TargetIdRetriever targetIdRetriever;
+
+    public ExternalServlet(Gateway gateway, TargetIdRetriever targetIdRetriever)
+    {
+        this.gateway = gateway;
+        this.targetIdRetriever = targetIdRetriever;
+    }
+
+    public TargetIdRetriever getTargetIdRetriever()
+    {
+        return targetIdRetriever;
+    }
+
+    public void setTargetIdRetriever(TargetIdRetriever targetIdRetriever)
+    {
+        this.targetIdRetriever = targetIdRetriever;
+    }
+
+    @Override
+    protected void service(HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws ServletException, IOException
+    {
+        logger.debug("External http request: {}", httpRequest.getRequestURL());
+
+        String targetId = targetIdRetriever.retrieveTargetId(httpRequest);
+        if (targetId == null)
+            throw new ServletException("Invalid request to " + getClass().getSimpleName() + ": " + httpRequest.getRequestURI());
+
+        ClientDelegate client = gateway.getClientDelegate(targetId);
+        if (client == null) throw new ServletException("Client with targetId " + targetId + " is not connected");
+
+        ExternalRequest externalRequest = gateway.newExternalRequest(httpRequest, httpResponse);
+        RHTTPRequest request = externalRequest.getRequest();
+        ExternalRequest existing = gateway.addExternalRequest(request.getId(), externalRequest);
+        assert existing == null;
+        logger.debug("External request {} for device {}", request, targetId);
+
+        boolean delivered = client.enqueue(request);
+        if (delivered)
+        {
+            externalRequest.suspend();
+        }
+        else
+        {
+            // TODO: improve this: we can temporarly queue this request elsewhere and wait for the client to reconnect ?
+            throw new ServletException("Could not enqueue request to client with targetId " + targetId);
+        }
+    }
+}
diff --git a/jetty-rhttp/jetty-rhttp-gateway/src/main/java/org/eclipse/jetty/rhttp/gateway/Gateway.java b/jetty-rhttp/jetty-rhttp-gateway/src/main/java/org/eclipse/jetty/rhttp/gateway/Gateway.java
new file mode 100644
index 0000000..83bd22c
--- /dev/null
+++ b/jetty-rhttp/jetty-rhttp-gateway/src/main/java/org/eclipse/jetty/rhttp/gateway/Gateway.java
@@ -0,0 +1,99 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.rhttp.gateway;
+
+import java.io.IOException;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * <p>Gateway instances are responsible of holding the state of the gateway server.</p>
+ * <p>The state is composed by:
+ * <ul>
+ * <li>{@link ExternalRequest external requests} that are suspended waiting for the response</li>
+ * <li>{@link ClientDelegate gateway clients} that are connected with the gateway server</li>
+ * </ul></p>
+ * <p>Instances of this class are created by the {@link GatewayServer}.</p>
+ *
+ * @version $Revision$ $Date$
+ */
+public interface Gateway
+{
+    /**
+     * <p>Returns the {@link ClientDelegate} with the given targetId.<br />
+     * If there is no such ClientDelegate returns null.</p>
+     *
+     * @param targetId the targetId of the ClientDelegate to return
+     * @return the ClientDelegate associated with the given targetId
+     */
+    public ClientDelegate getClientDelegate(String targetId);
+
+    /**
+     * <p>Creates and configures a new {@link ClientDelegate} with the given targetId.</p>
+     * @param targetId the targetId of the ClientDelegate to create
+     * @return a newly created ClientDelegate
+     * @see #addClientDelegate(String, ClientDelegate)
+     */
+    public ClientDelegate newClientDelegate(String targetId);
+
+    /**
+     * <p>Maps the given ClientDelegate to the given targetId.</p>
+     * @param targetId the targetId of the given ClientDelegate
+     * @param client the ClientDelegate to map
+     * @return the previously existing ClientDelegate mapped to the same targetId
+     * @see #removeClientDelegate(String)
+     */
+    public ClientDelegate addClientDelegate(String targetId, ClientDelegate client);
+
+    /**
+     * <p>Removes the {@link ClientDelegate} associated with the given targetId.</p>
+     * @param targetId the targetId of the ClientDelegate to remove
+     * @return the removed ClientDelegate, or null if no ClientDelegate was removed
+     * @see #addClientDelegate(String, ClientDelegate)
+     */
+    public ClientDelegate removeClientDelegate(String targetId);
+
+    /**
+     * <p>Creates a new {@link ExternalRequest} from the given HTTP request and HTTP response.</p>
+     * @param httpRequest the HTTP request of the external request
+     * @param httpResponse the HTTP response of the external request
+     * @return a newly created ExternalRequest
+     * @throws IOException in case of failures creating the ExternalRequest
+     * @see #addExternalRequest(int, ExternalRequest)
+     */
+    public ExternalRequest newExternalRequest(HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException;
+
+    /**
+     * Maps the given ExternalRequest with the given requestId into the gateway state.
+     * @param requestId the id of the ExternalRequest
+     * @param externalRequest the ExternalRequest to map
+     * @return the previously existing ExternalRequest mapped to the same requestId
+     * @see #removeExternalRequest(int)
+     */
+    public ExternalRequest addExternalRequest(int requestId, ExternalRequest externalRequest);
+
+    /**
+     * Removes the ExternalRequest mapped to the given requestId from the gateway state.
+     * @param requestId the id of the ExternalRequest
+     * @return the removed ExternalRequest
+     * @see #addExternalRequest(int, ExternalRequest)
+     */
+    public ExternalRequest removeExternalRequest(int requestId);
+}
diff --git a/jetty-rhttp/jetty-rhttp-gateway/src/main/java/org/eclipse/jetty/rhttp/gateway/GatewayProxyServer.java b/jetty-rhttp/jetty-rhttp-gateway/src/main/java/org/eclipse/jetty/rhttp/gateway/GatewayProxyServer.java
new file mode 100644
index 0000000..d446f69
--- /dev/null
+++ b/jetty-rhttp/jetty-rhttp-gateway/src/main/java/org/eclipse/jetty/rhttp/gateway/GatewayProxyServer.java
@@ -0,0 +1,228 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.rhttp.gateway;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.client.Address;
+import org.eclipse.jetty.client.ContentExchange;
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.HttpExchange;
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.ByteArrayBuffer;
+import org.eclipse.jetty.rhttp.client.JettyClient;
+import org.eclipse.jetty.rhttp.client.RHTTPClient;
+import org.eclipse.jetty.rhttp.client.RHTTPListener;
+import org.eclipse.jetty.rhttp.client.RHTTPRequest;
+import org.eclipse.jetty.rhttp.client.RHTTPResponse;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.nio.SelectChannelConnector;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * <p>This class combines a gateway server and a gateway client to obtain the functionality of a simple proxy server.</p>
+ * <p>This gateway proxy server starts on port 8080 and can be set as http proxy in browsers such as Firefox, and used
+ * to browse the internet.</p>
+ * <p>Its functionality is limited (for example, it only supports http, and not https).</p>
+ * @version $Revision$ $Date$
+ */
+public class GatewayProxyServer
+{
+    private static final Logger logger = Log.getLogger(GatewayProxyServer.class.toString());
+
+    public static void main(String[] args) throws Exception
+    {
+        GatewayServer server = new GatewayServer();
+
+        Connector plainConnector = new SelectChannelConnector();
+        plainConnector.setPort(8080);
+        server.addConnector(plainConnector);
+
+        ((StandardGateway)server.getGateway()).setExternalTimeout(180000);
+        ((StandardGateway)server.getGateway()).setGatewayTimeout(20000);
+        server.setTargetIdRetriever(new ProxyTargetIdRetriever());
+        server.start();
+
+        HttpClient httpClient = new HttpClient();
+        httpClient.setConnectorType(HttpClient.CONNECTOR_SOCKET);
+        httpClient.start();
+
+        RHTTPClient client = new JettyClient(httpClient, new Address("localhost", plainConnector.getPort()), server.getContext().getContextPath() + "/gw", "proxy");
+        client.addListener(new ProxyListener(httpClient, client));
+        client.connect();
+
+        Runtime.getRuntime().addShutdownHook(new Shutdown(server, httpClient, client));
+        logger.info("{} started", GatewayProxyServer.class.getSimpleName());
+    }
+
+    private static class Shutdown extends Thread
+    {
+        private final GatewayServer server;
+        private final HttpClient httpClient;
+        private final RHTTPClient client;
+
+        public Shutdown(GatewayServer server, HttpClient httpClient, RHTTPClient client)
+        {
+            this.server = server;
+            this.httpClient = httpClient;
+            this.client = client;
+        }
+
+        @Override
+        public void run()
+        {
+            try
+            {
+                client.disconnect();
+                httpClient.stop();
+                server.stop();
+                logger.info("{} stopped", GatewayProxyServer.class.getSimpleName());
+            }
+            catch (Exception x)
+            {
+                logger.debug("Exception while stopping " + GatewayProxyServer.class.getSimpleName(), x);
+            }
+        }
+    }
+
+    private static class ProxyListener implements RHTTPListener
+    {
+        private final HttpClient httpClient;
+        private final RHTTPClient client;
+
+        private ProxyListener(HttpClient httpClient, RHTTPClient client)
+        {
+            this.httpClient = httpClient;
+            this.client = client;
+        }
+
+        public void onRequest(RHTTPRequest request) throws Exception
+        {
+            ProxyExchange exchange = new ProxyExchange();
+            Address address = Address.from(request.getHeaders().get("Host"));
+            if (address.getPort() == 0) address = new Address(address.getHost(), 80);
+            exchange.setAddress(address);
+            exchange.setMethod(request.getMethod());
+            exchange.setURI(request.getURI());
+            for (Map.Entry<String, String> header : request.getHeaders().entrySet())
+                exchange.setRequestHeader(header.getKey(), header.getValue());
+            exchange.setRequestContent(new ByteArrayBuffer(request.getBody()));
+            int status = syncSend(exchange);
+            if (status == HttpExchange.STATUS_COMPLETED)
+            {
+                int statusCode = exchange.getResponseStatus();
+                String statusMessage = exchange.getResponseMessage();
+                Map<String, String> responseHeaders = exchange.getResponseHeaders();
+                byte[] responseBody = exchange.getResponseBody();
+                RHTTPResponse response = new RHTTPResponse(request.getId(), statusCode, statusMessage, responseHeaders, responseBody);
+                client.deliver(response);
+            }
+            else
+            {
+                int statusCode = HttpServletResponse.SC_SERVICE_UNAVAILABLE;
+                String statusMessage = "Gateway error";
+                HashMap<String, String> responseHeaders = new HashMap<String, String>();
+                responseHeaders.put("Connection", "close");
+                byte[] responseBody = new byte[0];
+                RHTTPResponse response = new RHTTPResponse(request.getId(), statusCode, statusMessage, responseHeaders, responseBody);
+                client.deliver(response);
+            }
+        }
+
+        private int syncSend(ProxyExchange exchange) throws Exception
+        {
+            long start = System.nanoTime();
+            httpClient.send(exchange);
+            int status = exchange.waitForDone();
+            long end = System.nanoTime();
+            long millis = TimeUnit.NANOSECONDS.toMillis(end - start);
+            long micros = TimeUnit.NANOSECONDS.toMicros(end - start - TimeUnit.MILLISECONDS.toNanos(millis));
+            logger.debug("Proxied request took {}.{} ms", millis, micros);
+            return status;
+        }
+    }
+
+    private static class ProxyExchange extends ContentExchange
+    {
+        private String responseMessage;
+        private Map<String, String> responseHeaders = new HashMap<String, String>();
+        private ByteArrayOutputStream responseBody = new ByteArrayOutputStream();
+
+        private ProxyExchange()
+        {
+            super(true);
+        }
+
+        public String getResponseMessage()
+        {
+            return responseMessage;
+        }
+
+        public Map<String, String> getResponseHeaders()
+        {
+            return responseHeaders;
+        }
+
+        public byte[] getResponseBody()
+        {
+            return responseBody.toByteArray();
+        }
+
+        @Override
+        protected void onResponseStatus(Buffer version, int code, Buffer message) throws IOException
+        {
+            super.onResponseStatus(version, code, message);
+            this.responseMessage = message.toString("UTF-8");
+        }
+
+        @Override
+        protected void onResponseHeader(Buffer nameBuffer, Buffer valueBuffer) throws IOException
+        {
+            super.onResponseHeader(nameBuffer, valueBuffer);
+            String name = nameBuffer.toString("UTF-8");
+            String value = valueBuffer.toString("UTF-8");
+            // Skip chunked header, since we read the whole body and will not re-chunk it
+            if (!name.equalsIgnoreCase("Transfer-Encoding") || !value.equalsIgnoreCase("chunked"))
+                responseHeaders.put(name, value);
+        }
+
+        @Override
+        protected void onResponseContent(Buffer buffer) throws IOException
+        {
+            responseBody.write(buffer.asArray());
+            super.onResponseContent(buffer);
+        }
+    }
+
+    public static class ProxyTargetIdRetriever implements TargetIdRetriever
+    {
+        public String retrieveTargetId(HttpServletRequest httpRequest)
+        {
+            return "proxy";
+        }
+    }
+}
diff --git a/jetty-rhttp/jetty-rhttp-gateway/src/main/java/org/eclipse/jetty/rhttp/gateway/GatewayServer.java b/jetty-rhttp/jetty-rhttp-gateway/src/main/java/org/eclipse/jetty/rhttp/gateway/GatewayServer.java
new file mode 100644
index 0000000..5c86cd4
--- /dev/null
+++ b/jetty-rhttp/jetty-rhttp-gateway/src/main/java/org/eclipse/jetty/rhttp/gateway/GatewayServer.java
@@ -0,0 +1,171 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.rhttp.gateway;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.rhttp.client.RHTTPClient;
+import org.eclipse.jetty.rhttp.client.RHTTPRequest;
+import org.eclipse.jetty.rhttp.client.RHTTPResponse;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.handler.HandlerCollection;
+import org.eclipse.jetty.servlet.DefaultServlet;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * <p>The gateway server is a server component that acts as intermediary between
+ * <em>external clients</em> which perform requests for resources, and the
+ * <em>resource providers</em>.</p>
+ * <p>The particularity of the gateway server is that the resource providers
+ * connect to the gateway using a comet protocol. <br />
+ * The comet procotol functionality is implemented by a gateway client. <br />
+ * This is quite different from a normal proxy server where it is the proxy that
+ * connects to the resource providers.</p>
+ * <p>Schematically, this is how the gateway server works:</p>
+ * <pre>
+ * External Client       Gateway Server         Gateway Client         Resource Provider
+ *                              |                      |
+ *                              | <-- comet req. 1 --- |
+ *        | --- ext. req. 1 --> |                      |
+ *        |                     | --- comet res. 1 --> |
+ *        |                     | <-- comet req. 2 --- |
+ *        |                                            | --- ext. req. 1 --> |
+ *                                                                           |
+ *        |                                            | <-- ext. res. 1 --- |
+ *        |                     | <-- ext.  res. 1 --- |
+ *        | <-- ext. res. 1 --- |
+ *
+ *        | --- ext. req. 2 --> |
+ *        |                     | --- comet res. 2 --> |
+ *        .                     .                      .
+ * </pre>
+ * <p>The gateway server is made of two servlets:
+ * <ul>
+ * <li>the external servlet, that handles external requests</li>
+ * <li>the gateway servlet, that handles the communication with the gateway client</li>
+ * </ul>
+ * </p>
+ * <p>External requests are suspended using Jetty continuations until a response for
+ * that request arrives from the resource provider, or a
+ * {@link #getExternalTimeout() configurable timeout} expires. <br />
+ * Comet requests made by the gateway client also expires after a (different)
+ * {@link #getGatewayTimeout() configurable timeout}.</p>
+ * <p>External requests are packed into {@link RHTTPRequest} objects, converted into an
+ * opaque byte array and sent as the body of the comet reponse to the gateway
+ * {@link RHTTPClient}.</p>
+ * <p>The gateway client uses a notification mechanism to alert listeners interested
+ * in external requests that have been forwarded through the gateway. It is up to the
+ * listeners to connect to the resource provider however they like.</p>
+ * <p>When the gateway client receives a response from the resource provider, it packs
+ * the response into a {@link RHTTPResponse} object, converts it into an opaque byte array
+ * and sends it as the body of a normal HTTP request to the gateway server.</p>
+ * <p>It is possible to connect more than one gateway client to a gateway server; each
+ * gateway client is identified by a unique <em>targetId</em>. <br />
+ * External requests must specify a targetId that allows the gateway server to forward
+ * the requests to the specific gateway client; how the targetId is retrieved from an
+ * external request is handled by {@link TargetIdRetriever} implementations.</p>
+ *
+ * @version $Revision$ $Date$
+ */
+public class GatewayServer extends Server
+{
+    public final static String DFT_EXT_PATH="/gw";
+    public final static String DFT_CONNECT_PATH="/__rhttp";
+    private final Logger logger = Log.getLogger(getClass().toString());
+    private final Gateway gateway;
+    private final ServletHolder externalServletHolder;
+    private final ServletHolder connectorServletHolder;
+    private final ServletContextHandler context;
+    
+    public GatewayServer()
+    {
+        this("",DFT_EXT_PATH,DFT_CONNECT_PATH,new StandardTargetIdRetriever());
+    }
+
+    public GatewayServer(String contextPath, String externalServletPath,String gatewayServletPath, TargetIdRetriever targetIdRetriever)
+    {
+        HandlerCollection handlers = new HandlerCollection();
+        setHandler(handlers);
+        context = new ServletContextHandler(handlers, contextPath, ServletContextHandler.SESSIONS);
+        
+        // Setup the gateway
+        gateway = createGateway();
+        
+        // Setup external servlet
+        ExternalServlet externalServlet = new ExternalServlet(gateway, targetIdRetriever);
+        externalServletHolder = new ServletHolder(externalServlet);
+        context.addServlet(externalServletHolder, externalServletPath + "/*");
+        logger.debug("External servlet mapped to {}/*", externalServletPath);
+
+        // Setup gateway servlet
+        ConnectorServlet gatewayServlet = new ConnectorServlet(gateway);
+        connectorServletHolder = new ServletHolder(gatewayServlet);
+        connectorServletHolder.setInitParameter("clientTimeout", "15000");
+        context.addServlet(connectorServletHolder, gatewayServletPath + "/*");
+        logger.debug("Gateway servlet mapped to {}/*", gatewayServletPath);
+    }
+
+    /**
+     * Creates and configures a {@link Gateway} object.
+     * @return the newly created and configured Gateway object.
+     */
+    protected Gateway createGateway()
+    {
+        StandardGateway gateway = new StandardGateway();
+        return gateway;
+    }
+    
+    public ServletContextHandler getContext()
+    {
+        return context;
+    }
+    
+    public Gateway getGateway()
+    {
+        return gateway;
+    }
+    
+    public ServletHolder getExternalServlet()
+    {
+        return externalServletHolder;
+    }
+    
+    public ServletHolder getConnectorServlet()
+    {
+        return connectorServletHolder;
+    }
+
+    public void setTargetIdRetriever(TargetIdRetriever retriever)
+    {
+        ((ExternalServlet)externalServletHolder.getServletInstance()).setTargetIdRetriever(retriever);
+    }
+
+    public TargetIdRetriever getTargetIdRetriever()
+    {
+        return ((ExternalServlet)externalServletHolder.getServletInstance()).getTargetIdRetriever();
+    }
+    
+}
diff --git a/jetty-rhttp/jetty-rhttp-gateway/src/main/java/org/eclipse/jetty/rhttp/gateway/HostTargetIdRetriever.java b/jetty-rhttp/jetty-rhttp-gateway/src/main/java/org/eclipse/jetty/rhttp/gateway/HostTargetIdRetriever.java
new file mode 100644
index 0000000..7c80795
--- /dev/null
+++ b/jetty-rhttp/jetty-rhttp-gateway/src/main/java/org/eclipse/jetty/rhttp/gateway/HostTargetIdRetriever.java
@@ -0,0 +1,54 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.rhttp.gateway;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * @version $Revision$ $Date$
+ */
+public class HostTargetIdRetriever implements TargetIdRetriever
+{
+    private final String suffix;
+
+    public HostTargetIdRetriever(String suffix)
+    {
+        this.suffix = suffix;
+    }
+
+    public String retrieveTargetId(HttpServletRequest httpRequest)
+    {
+        String host = httpRequest.getHeader("Host");
+        if (host != null)
+        {
+            // Strip the port
+            int colon = host.indexOf(':');
+            if (colon > 0)
+            {
+                host = host.substring(0, colon);
+            }
+
+            if (suffix != null && host.endsWith(suffix))
+            {
+                return host.substring(0, host.length() - suffix.length());
+            }
+        }
+        return host;
+    }
+}
diff --git a/jetty-rhttp/jetty-rhttp-gateway/src/main/java/org/eclipse/jetty/rhttp/gateway/Main.java b/jetty-rhttp/jetty-rhttp-gateway/src/main/java/org/eclipse/jetty/rhttp/gateway/Main.java
new file mode 100644
index 0000000..cddc8b6
--- /dev/null
+++ b/jetty-rhttp/jetty-rhttp-gateway/src/main/java/org/eclipse/jetty/rhttp/gateway/Main.java
@@ -0,0 +1,128 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.rhttp.gateway;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.nio.SelectChannelConnector;
+import org.eclipse.jetty.servlet.DefaultServlet;
+import org.eclipse.jetty.servlet.ServletHolder;
+
+/**
+ * <p>Main class that starts the gateway server.</p>
+ * <p>This class supports the following arguments:</p>
+ * <ul>
+ * <li>--port=<port> specifies the port on which the gateway server listens to, by default 8080</li>
+ * <li>--retriever=<retriever> specifies the
+ * {@link GatewayServer#setTargetIdRetriever(TargetIdRetriever) target id retriever}</li>
+ * <li>--resources=<resources file path> specifies the resource file path for the gateway</li>
+ * </ul>
+ * <p>Examples</p>
+ * <p> <tt>java --port=8080</tt> </p>
+ * <p> <tt>java --port=8080 --resources=/tmp/gateway-resources</tt> </p>
+ * <p> <tt>java --port=8080 --retriever=standard</tt> </p>
+ * <p> <tt>java --port=8080 --retriever=host,.rhttp.example.com</tt> </p>
+ * <p>The latter example specifies the {@link HostTargetIdRetriever} with a suffix of <tt>.rhttp.example.com</tt></p>
+ *
+ * @see GatewayServer
+ * @version $Revision$ $Date$
+ */
+public class Main
+{
+    private static final String PORT_ARG = "port";
+    private static final String RESOURCES_ARG = "resources";
+    private static final String RETRIEVER_ARG = "retriever";
+
+    public static void main(String[] args) throws Exception
+    {
+        Map<String, Object> arguments = parse(args);
+
+        int port = 8080;
+        if (arguments.containsKey(PORT_ARG))
+            port = (Integer)arguments.get(PORT_ARG);
+
+        String resources = null;
+        if (arguments.containsKey(RESOURCES_ARG))
+            resources = (String)arguments.get(RESOURCES_ARG);
+
+        TargetIdRetriever retriever = null;
+        if (arguments.containsKey(RETRIEVER_ARG))
+            retriever = (TargetIdRetriever)arguments.get(RETRIEVER_ARG);
+
+        GatewayServer server = new GatewayServer();
+
+        Connector connector = new SelectChannelConnector();
+        connector.setPort(port);
+        server.addConnector(connector);
+
+        if (resources != null)
+        {
+            server.getContext().setResourceBase(resources);
+            ServletHolder resourcesServletHolder = server.getContext().addServlet(DefaultServlet.class,"__r/*");
+            resourcesServletHolder.setInitParameter("dirAllowed", "true");
+        }
+
+        if (retriever != null)
+            server.setTargetIdRetriever(retriever);
+
+        server.start();
+    }
+
+    private static Map<String, Object> parse(String[] args)
+    {
+        Map<String, Object> result = new HashMap<String, Object>();
+
+        Pattern pattern = Pattern.compile("--([^=]+)=(.+)");
+        for (String arg : args)
+        {
+            Matcher matcher = pattern.matcher(arg);
+            if (matcher.matches())
+            {
+                String argName = matcher.group(1);
+                if (PORT_ARG.equals(argName))
+                {
+                    result.put(PORT_ARG, Integer.parseInt(matcher.group(2)));
+                }
+                else if (RESOURCES_ARG.equals(argName))
+                {
+                    String argValue = matcher.group(2);
+                    result.put(RESOURCES_ARG, argValue);
+                }
+                else if (RETRIEVER_ARG.equals(argName))
+                {
+                    String argValue = matcher.group(2);
+                    if (argValue.startsWith("host,"))
+                    {
+                        String[] typeAndSuffix = argValue.split(",");
+                        if (typeAndSuffix.length != 2)
+                            throw new IllegalArgumentException("Invalid option " + arg + ", must be of the form --" + RETRIEVER_ARG + "=host,suffix");
+
+                        result.put(RETRIEVER_ARG, new HostTargetIdRetriever(typeAndSuffix[1]));
+                    }
+                }
+            }
+        }
+
+        return result;
+    }
+}
diff --git a/jetty-rhttp/jetty-rhttp-gateway/src/main/java/org/eclipse/jetty/rhttp/gateway/StandardClientDelegate.java b/jetty-rhttp/jetty-rhttp-gateway/src/main/java/org/eclipse/jetty/rhttp/gateway/StandardClientDelegate.java
new file mode 100644
index 0000000..f8749fb
--- /dev/null
+++ b/jetty-rhttp/jetty-rhttp-gateway/src/main/java/org/eclipse/jetty/rhttp/gateway/StandardClientDelegate.java
@@ -0,0 +1,172 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.rhttp.gateway;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.eclipse.jetty.continuation.Continuation;
+import org.eclipse.jetty.continuation.ContinuationSupport;
+import org.eclipse.jetty.rhttp.client.RHTTPRequest;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * <p>Default implementation of {@link ClientDelegate}.</p>
+ *
+ * @version $Revision$ $Date$
+ */
+public class StandardClientDelegate implements ClientDelegate
+{
+    private final Logger logger = Log.getLogger(getClass().toString());
+    private final Object lock = new Object();
+    private final List<RHTTPRequest> requests = new ArrayList<RHTTPRequest>();
+    private final String targetId;
+    private volatile boolean firstFlush = true;
+    private volatile long timeout;
+    private volatile boolean closed;
+    private Continuation continuation;
+
+    public StandardClientDelegate(String targetId)
+    {
+        this.targetId = targetId;
+    }
+
+    public String getTargetId()
+    {
+        return targetId;
+    }
+
+    public long getTimeout()
+    {
+        return timeout;
+    }
+
+    public void setTimeout(long timeout)
+    {
+        this.timeout = timeout;
+    }
+
+    public boolean enqueue(RHTTPRequest request)
+    {
+        if (isClosed())
+            return false;
+
+        synchronized (lock)
+        {
+            requests.add(request);
+            resume();
+        }
+
+        return true;
+    }
+
+    private void resume()
+    {
+        synchronized (lock)
+        {
+            // Continuation may be null in several cases:
+            // 1. there always is something to deliver so we never suspend
+            // 2. concurrent calls to add() and close()
+            // 3. concurrent close() with a long poll that expired
+            // 4. concurrent close() with a long poll that resumed
+            if (continuation != null)
+            {
+                continuation.resume();
+                // Null the continuation, as there is no point is resuming multiple times
+                continuation = null;
+            }
+        }
+    }
+
+    public List<RHTTPRequest> process(HttpServletRequest httpRequest) throws IOException
+    {
+        // We want to respond in the following cases:
+        // 1. It's the first time we process: the client will wait for a response before issuing another connect.
+        // 2. The client disconnected, so we want to return from this connect before it times out.
+        // 3. We've been woken up because there are responses to send.
+        // 4. The continuation was suspended but timed out.
+        //    The timeout case is different from a non-first connect, in that we want to return
+        //    a (most of the times empty) response and we do not want to wait again.
+        // The order of these if statements is important, as the continuation timed out only if
+        // the client is not closed and there are no responses to send
+        List<RHTTPRequest> result = Collections.emptyList();
+        if (firstFlush)
+        {
+            firstFlush = false;
+            logger.debug("Connect request (first) from device {}, delivering requests {}", targetId, result);
+        }
+        else
+        {
+            // Synchronization is crucial here, since we don't want to suspend if there is something to deliver
+            synchronized (lock)
+            {
+                int size = requests.size();
+                if (size > 0)
+                {
+                    assert continuation == null;
+                    result = new ArrayList<RHTTPRequest>(size);
+                    result.addAll(requests);
+                    requests.clear();
+                    logger.debug("Connect request (resumed) from device {}, delivering requests {}", targetId, result);
+                }
+                else
+                {
+                    if (continuation != null)
+                    {
+                        continuation = null;
+                        logger.debug("Connect request (expired) from device {}, delivering requests {}", targetId, result);
+                    }
+                    else
+                    {
+                        if (isClosed())
+                        {
+                            logger.debug("Connect request (closed) from device {}, delivering requests {}", targetId, result);
+                        }
+                        else
+                        {
+                            // Here we need to suspend
+                            continuation = ContinuationSupport.getContinuation(httpRequest);
+                            continuation.setTimeout(getTimeout());
+                            continuation.suspend();
+                            result = null;
+                            logger.debug("Connect request (suspended) from device {}", targetId);
+                        }
+                    }
+                }
+            }
+        }
+        return result;
+    }
+
+    public void close()
+    {
+        closed = true;
+        resume();
+    }
+
+    public boolean isClosed()
+    {
+        return closed;
+    }
+}
diff --git a/jetty-rhttp/jetty-rhttp-gateway/src/main/java/org/eclipse/jetty/rhttp/gateway/StandardExternalRequest.java b/jetty-rhttp/jetty-rhttp-gateway/src/main/java/org/eclipse/jetty/rhttp/gateway/StandardExternalRequest.java
new file mode 100644
index 0000000..df12111
--- /dev/null
+++ b/jetty-rhttp/jetty-rhttp-gateway/src/main/java/org/eclipse/jetty/rhttp/gateway/StandardExternalRequest.java
@@ -0,0 +1,189 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.rhttp.gateway;
+
+import java.io.IOException;
+import java.util.Map;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.continuation.Continuation;
+import org.eclipse.jetty.continuation.ContinuationListener;
+import org.eclipse.jetty.continuation.ContinuationSupport;
+import org.eclipse.jetty.rhttp.client.RHTTPRequest;
+import org.eclipse.jetty.rhttp.client.RHTTPResponse;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * Default implementation of {@link ExternalRequest}.
+ *
+ * @version $Revision$ $Date$
+ */
+public class StandardExternalRequest implements ExternalRequest
+{
+    private final Logger logger = Log.getLogger(getClass().toString());
+    private final RHTTPRequest request;
+    private final HttpServletRequest httpRequest;
+    private final HttpServletResponse httpResponse;
+    private final Gateway gateway;
+    private final Object lock = new Object();
+    private volatile long timeout;
+    private Continuation continuation;
+    private boolean responded;
+
+    public StandardExternalRequest(RHTTPRequest request, HttpServletRequest httpRequest, HttpServletResponse httpResponse, Gateway gateway)
+    {
+        this.request = request;
+        this.httpRequest = httpRequest;
+        this.httpResponse = httpResponse;
+        this.gateway = gateway;
+    }
+
+    public long getTimeout()
+    {
+        return timeout;
+    }
+
+    public void setTimeout(long timeout)
+    {
+        this.timeout = timeout;
+    }
+
+    public boolean suspend()
+    {
+        synchronized (lock)
+        {
+            // We suspend only if we have no responded yet
+            if (!responded)
+            {
+                assert continuation == null;
+                continuation = ContinuationSupport.getContinuation(httpRequest);
+                continuation.setTimeout(getTimeout());
+                continuation.addContinuationListener(new TimeoutListener());
+                continuation.suspend(httpResponse);
+                logger.debug("Request {} suspended", getRequest());
+            }
+            else
+            {
+                logger.debug("Request {} already responded", getRequest());
+            }
+            return !responded;
+        }
+    }
+
+    public void respond(RHTTPResponse response) throws IOException
+    {
+        responseCompleted(response);
+    }
+
+    private void responseCompleted(RHTTPResponse response) throws IOException
+    {
+        synchronized (lock)
+        {
+            // Could be that we complete exactly when the response is being expired
+            if (!responded)
+            {
+                httpResponse.setStatus(response.getStatusCode());
+
+                for (Map.Entry<String, String> header : response.getHeaders().entrySet())
+                    httpResponse.setHeader(header.getKey(), header.getValue());
+
+                ServletOutputStream output = httpResponse.getOutputStream();
+                output.write(response.getBody());
+                output.flush();
+
+                // It may happen that the continuation is null,
+                // because the response arrived before we had the chance to suspend
+                if (continuation != null)
+                {
+                    continuation.complete();
+                    continuation = null;
+                }
+
+                // Mark as responded, so we know we don't have to suspend
+                // or respond with an expired response
+                responded = true;
+
+                if (logger.isDebugEnabled())
+                {
+                    String eol = System.getProperty("line.separator");
+                    logger.debug("Request {} responded {}{}{}{}{}", new Object[]{request, response, eol, request.toLongString(), eol, response.toLongString()});
+                }
+            }
+        }
+    }
+
+    private void responseExpired() throws IOException
+    {
+        synchronized (lock)
+        {
+            // Could be that we expired exactly when the response is being completed
+            if (!responded)
+            {
+                httpResponse.sendError(HttpServletResponse.SC_GATEWAY_TIMEOUT, "Gateway Time-out");
+
+                continuation.complete();
+                continuation = null;
+
+                // Mark as responded, so we know we don't have to respond with a completed response
+                responded = true;
+
+                logger.debug("Request {} expired", getRequest());
+            }
+        }
+    }
+
+    public RHTTPRequest getRequest()
+    {
+        return request;
+    }
+
+    @Override
+    public String toString()
+    {
+        return request.toString();
+    }
+
+    private class TimeoutListener implements ContinuationListener
+    {
+        public void onComplete(Continuation continuation)
+        {
+        }
+
+        public void onTimeout(Continuation continuation)
+        {
+            ExternalRequest externalRequest = gateway.removeExternalRequest(getRequest().getId());
+            // The gateway request can be null for a race with delivery
+            if (externalRequest != null)
+            {
+                try
+                {
+                    responseExpired();
+                }
+                catch (Exception x)
+                {
+                    logger.warn("Request " + getRequest() + " expired but failed", x);
+                }
+            }
+        }
+    }
+}
diff --git a/jetty-rhttp/jetty-rhttp-gateway/src/main/java/org/eclipse/jetty/rhttp/gateway/StandardGateway.java b/jetty-rhttp/jetty-rhttp-gateway/src/main/java/org/eclipse/jetty/rhttp/gateway/StandardGateway.java
new file mode 100644
index 0000000..1e39889
--- /dev/null
+++ b/jetty-rhttp/jetty-rhttp-gateway/src/main/java/org/eclipse/jetty/rhttp/gateway/StandardGateway.java
@@ -0,0 +1,131 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.rhttp.gateway;
+
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.rhttp.client.RHTTPRequest;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * Default implementation of {@link Gateway}.
+ *
+ * @version $Revision$ $Date$
+ */
+public class StandardGateway implements Gateway
+{
+    private final Logger logger = Log.getLogger(getClass().toString());
+    private final ConcurrentMap<String, ClientDelegate> clients = new ConcurrentHashMap<String, ClientDelegate>();
+    private final ConcurrentMap<Integer, ExternalRequest> requests = new ConcurrentHashMap<Integer, ExternalRequest>();
+    private final AtomicInteger requestIds = new AtomicInteger();
+    private volatile long gatewayTimeout=20000;
+    private volatile long externalTimeout=60000;
+
+    public long getGatewayTimeout()
+    {
+        return gatewayTimeout;
+    }
+
+    public void setGatewayTimeout(long timeout)
+    {
+        this.gatewayTimeout = timeout;
+    }
+
+    public long getExternalTimeout()
+    {
+        return externalTimeout;
+    }
+
+    public void setExternalTimeout(long externalTimeout)
+    {
+        this.externalTimeout = externalTimeout;
+    }
+
+    public ClientDelegate getClientDelegate(String targetId)
+    {
+        return clients.get(targetId);
+    }
+
+    public ClientDelegate newClientDelegate(String targetId)
+    {
+        StandardClientDelegate client = new StandardClientDelegate(targetId);
+        client.setTimeout(getGatewayTimeout());
+        return client;
+    }
+
+    public ClientDelegate addClientDelegate(String targetId, ClientDelegate client)
+    {
+        return clients.putIfAbsent(targetId, client);
+    }
+
+    public ClientDelegate removeClientDelegate(String targetId)
+    {
+        return clients.remove(targetId);
+    }
+
+    public ExternalRequest newExternalRequest(HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException
+    {
+        int requestId = requestIds.incrementAndGet();
+        RHTTPRequest request = convertHttpRequest(requestId, httpRequest);
+        StandardExternalRequest gatewayRequest = new StandardExternalRequest(request, httpRequest, httpResponse, this);
+        gatewayRequest.setTimeout(getExternalTimeout());
+        return gatewayRequest;
+    }
+
+    protected RHTTPRequest convertHttpRequest(int requestId, HttpServletRequest httpRequest) throws IOException
+    {
+        Map<String, String> headers = new HashMap<String, String>();
+        for (Enumeration headerNames = httpRequest.getHeaderNames(); headerNames.hasMoreElements();)
+        {
+            String name = (String)headerNames.nextElement();
+            // TODO: improve by supporting getHeaders(name)
+            String value = httpRequest.getHeader(name);
+            headers.put(name, value);
+        }
+
+        byte[] body = Utils.read(httpRequest.getInputStream());
+        return new RHTTPRequest(requestId, httpRequest.getMethod(), httpRequest.getRequestURI(), headers, body);
+    }
+
+    public ExternalRequest addExternalRequest(int requestId, ExternalRequest externalRequest)
+    {
+        ExternalRequest existing = requests.putIfAbsent(requestId, externalRequest);
+        if (existing == null)
+            logger.debug("Added external request {}/{} - {}", new Object[]{requestId, requests.size(), externalRequest});
+        return existing;
+    }
+
+    public ExternalRequest removeExternalRequest(int requestId)
+    {
+        ExternalRequest externalRequest = requests.remove(requestId);
+        if (externalRequest != null)
+            logger.debug("Removed external request {}/{} - {}", new Object[]{requestId, requests.size(), externalRequest});
+        return externalRequest;
+    }
+}
diff --git a/jetty-rhttp/jetty-rhttp-gateway/src/main/java/org/eclipse/jetty/rhttp/gateway/StandardTargetIdRetriever.java b/jetty-rhttp/jetty-rhttp-gateway/src/main/java/org/eclipse/jetty/rhttp/gateway/StandardTargetIdRetriever.java
new file mode 100644
index 0000000..c1d4a62
--- /dev/null
+++ b/jetty-rhttp/jetty-rhttp-gateway/src/main/java/org/eclipse/jetty/rhttp/gateway/StandardTargetIdRetriever.java
@@ -0,0 +1,40 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.rhttp.gateway;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * <p>This implementation retrieves the targetId from the request URI following this pattern:</p>
+ * <pre>
+ * /contextPath/servletPath/<targetId>/other/paths
+ * </pre>
+ * @version $Revision$ $Date$
+ */
+public class StandardTargetIdRetriever implements TargetIdRetriever
+{
+    public String retrieveTargetId(HttpServletRequest httpRequest)
+    {
+        String uri = httpRequest.getRequestURI();
+        String path = uri.substring(httpRequest.getServletPath().length());
+        String[] segments = path.split("/");
+        if (segments.length < 2) return null;
+        return segments[1];
+    }
+}
diff --git a/jetty-rhttp/jetty-rhttp-gateway/src/main/java/org/eclipse/jetty/rhttp/gateway/TargetIdRetriever.java b/jetty-rhttp/jetty-rhttp-gateway/src/main/java/org/eclipse/jetty/rhttp/gateway/TargetIdRetriever.java
new file mode 100644
index 0000000..53a36bd
--- /dev/null
+++ b/jetty-rhttp/jetty-rhttp-gateway/src/main/java/org/eclipse/jetty/rhttp/gateway/TargetIdRetriever.java
@@ -0,0 +1,40 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.rhttp.gateway;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * <p>Implementations should retrieve a <em>targetId</em> from an external request.</p>
+ * <p>Implementations of this class may return a fixed value, or inspect the request
+ * looking for URL patterns (e.g. "/<targetId>/resource.jsp"), or looking for request
+ * parameters (e.g. "/resource.jsp?targetId=<targetId>), or looking for virtual host
+ * naming patterns (e.g. "http://<targetId>.host.com/resource.jsp"), etc.</p>
+ *
+ * @version $Revision$ $Date$
+ */
+public interface TargetIdRetriever
+{
+    /**
+     * Extracts and returns the targetId.
+     * @param httpRequest the external request from where the targetId could be extracted
+     * @return the extracted targetId
+     */
+    public String retrieveTargetId(HttpServletRequest httpRequest);
+}
diff --git a/jetty-rhttp/jetty-rhttp-gateway/src/main/java/org/eclipse/jetty/rhttp/gateway/Utils.java b/jetty-rhttp/jetty-rhttp-gateway/src/main/java/org/eclipse/jetty/rhttp/gateway/Utils.java
new file mode 100644
index 0000000..325232a
--- /dev/null
+++ b/jetty-rhttp/jetty-rhttp-gateway/src/main/java/org/eclipse/jetty/rhttp/gateway/Utils.java
@@ -0,0 +1,40 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.rhttp.gateway;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * @version $Revision$ $Date$
+ */
+class Utils
+{
+    static byte[] read(InputStream input) throws IOException
+    {
+        ByteArrayOutputStream body = new ByteArrayOutputStream();
+        byte[] buffer = new byte[1024];
+        int read;
+        while ((read = input.read(buffer)) >= 0)
+            body.write(buffer, 0, read);
+        body.close();
+        return body.toByteArray();
+    }
+}
diff --git a/jetty-rhttp/jetty-rhttp-gateway/src/test/java/org/eclipse/jetty/rhttp/gateway/ClientTimeoutTest.java b/jetty-rhttp/jetty-rhttp-gateway/src/test/java/org/eclipse/jetty/rhttp/gateway/ClientTimeoutTest.java
new file mode 100644
index 0000000..ad87f82
--- /dev/null
+++ b/jetty-rhttp/jetty-rhttp-gateway/src/test/java/org/eclipse/jetty/rhttp/gateway/ClientTimeoutTest.java
@@ -0,0 +1,115 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.rhttp.gateway;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import junit.framework.TestCase;
+
+import org.eclipse.jetty.client.Address;
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.rhttp.client.ClientListener;
+import org.eclipse.jetty.rhttp.client.JettyClient;
+import org.eclipse.jetty.rhttp.client.RHTTPClient;
+import org.eclipse.jetty.rhttp.gateway.GatewayServer;
+import org.eclipse.jetty.rhttp.gateway.StandardGateway;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.nio.SelectChannelConnector;
+
+
+/**
+ * @version $Revision$ $Date$
+ */
+public class ClientTimeoutTest extends TestCase
+{
+    public void testClientTimeout() throws Exception
+    {
+        GatewayServer server = new GatewayServer();
+        Connector connector = new SelectChannelConnector();
+        server.addConnector(connector);
+        final long clientTimeout = 2000L;
+        server.getConnectorServlet().setInitParameter("clientTimeout",""+clientTimeout);
+        final long gatewayTimeout = 4000L;
+        ((StandardGateway)server.getGateway()).setGatewayTimeout(gatewayTimeout);
+        server.start();
+        try
+        {
+            Address address = new Address("localhost", connector.getLocalPort());
+
+            HttpClient httpClient = new HttpClient();
+            httpClient.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);
+            httpClient.start();
+            try
+            {
+                String targetId = "1";
+                final RHTTPClient client = new JettyClient(httpClient, address, server.getContext().getContextPath()+GatewayServer.DFT_CONNECT_PATH, targetId)
+                {
+                    private final AtomicInteger connects = new AtomicInteger();
+
+                    @Override
+                    protected void asyncConnect()
+                    {
+                        if (connects.incrementAndGet() == 2)
+                        {
+                            try
+                            {
+                                // Wait here instead of connecting, so that the client expires on the server
+                                Thread.sleep(clientTimeout * 2);
+                            }
+                            catch (InterruptedException x)
+                            {
+                                throw new RuntimeException(x);
+                            }
+                        }
+                        super.asyncConnect();
+                    }
+                };
+
+                final CountDownLatch connectLatch = new CountDownLatch(1);
+                client.addClientListener(new ClientListener.Adapter()
+                {
+                    @Override
+                    public void connectRequired()
+                    {
+                        connectLatch.countDown();
+                    }
+                });
+                client.connect();
+                try
+                {
+                    assertTrue(connectLatch.await(gatewayTimeout + clientTimeout * 3, TimeUnit.MILLISECONDS));
+                }
+                finally
+                {
+                    client.disconnect();
+                }
+            }
+            finally
+            {
+                httpClient.stop();
+            }
+        }
+        finally
+        {
+            server.stop();
+        }
+    }
+}
diff --git a/jetty-rhttp/jetty-rhttp-gateway/src/test/java/org/eclipse/jetty/rhttp/gateway/DisconnectClientTest.java b/jetty-rhttp/jetty-rhttp-gateway/src/test/java/org/eclipse/jetty/rhttp/gateway/DisconnectClientTest.java
new file mode 100644
index 0000000..89f4281
--- /dev/null
+++ b/jetty-rhttp/jetty-rhttp-gateway/src/test/java/org/eclipse/jetty/rhttp/gateway/DisconnectClientTest.java
@@ -0,0 +1,93 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.rhttp.gateway;
+
+import java.io.IOException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import junit.framework.TestCase;
+
+import org.eclipse.jetty.client.Address;
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.rhttp.client.JettyClient;
+import org.eclipse.jetty.rhttp.client.RHTTPClient;
+import org.eclipse.jetty.rhttp.gateway.GatewayServer;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.nio.SelectChannelConnector;
+
+
+/**
+ * @version $Revision$ $Date$
+ */
+public class DisconnectClientTest extends TestCase
+{
+    public void testDifferentClientDisconnects() throws Exception
+    {
+        GatewayServer server = new GatewayServer();
+        Connector connector = new SelectChannelConnector();
+        server.addConnector(connector);
+        server.start();
+        try
+        {
+            Address address = new Address("localhost", connector.getLocalPort());
+
+            HttpClient httpClient = new HttpClient();
+            httpClient.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);
+            httpClient.start();
+            try
+            {
+                final CountDownLatch latch = new CountDownLatch(1);
+                String targetId = "1";
+                final RHTTPClient client1 = new JettyClient(httpClient, address, server.getContext().getContextPath()+GatewayServer.DFT_CONNECT_PATH, targetId)
+                {
+                    @Override
+                    protected void connectComplete(byte[] responseContent) throws IOException
+                    {
+                        // If the other client can disconnect this one, this method is called soon after it disconnected
+                        latch.countDown();
+                        super.connectComplete(responseContent);
+                    }
+                };
+                client1.connect();
+                try
+                {
+                    final RHTTPClient client2 = new JettyClient(httpClient, address, server.getContext().getContextPath()+GatewayServer.DFT_CONNECT_PATH, targetId);
+                    // Disconnect client 2, this should not disconnect client1
+                    client2.disconnect();
+
+                    // We want the await() to expire, it means it has not disconnected
+                    assertFalse(latch.await(1000, TimeUnit.MILLISECONDS));
+                }
+                finally
+                {
+                    client1.disconnect();
+                }
+            }
+            finally
+            {
+                httpClient.stop();
+            }
+        }
+        finally
+        {
+            server.stop();
+        }
+    }
+}
diff --git a/jetty-rhttp/jetty-rhttp-gateway/src/test/java/org/eclipse/jetty/rhttp/gateway/DuplicateClientTest.java b/jetty-rhttp/jetty-rhttp-gateway/src/test/java/org/eclipse/jetty/rhttp/gateway/DuplicateClientTest.java
new file mode 100644
index 0000000..f8821ae
--- /dev/null
+++ b/jetty-rhttp/jetty-rhttp-gateway/src/test/java/org/eclipse/jetty/rhttp/gateway/DuplicateClientTest.java
@@ -0,0 +1,84 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.rhttp.gateway;
+
+import java.io.IOException;
+
+import junit.framework.TestCase;
+
+import org.eclipse.jetty.client.Address;
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.rhttp.client.JettyClient;
+import org.eclipse.jetty.rhttp.client.RHTTPClient;
+import org.eclipse.jetty.rhttp.gateway.GatewayServer;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.nio.SelectChannelConnector;
+
+
+/**
+ * @version $Revision$ $Date$
+ */
+public class DuplicateClientTest extends TestCase
+{
+    public void testDuplicateClient() throws Exception
+    {
+        GatewayServer server = new GatewayServer();
+        Connector connector = new SelectChannelConnector();
+        server.addConnector(connector);
+        server.start();
+        try
+        {
+            Address address = new Address("localhost", connector.getLocalPort());
+
+            HttpClient httpClient = new HttpClient();
+            httpClient.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);
+            httpClient.start();
+            try
+            {
+                String targetId = "1";
+                final RHTTPClient client1 = new JettyClient(httpClient, address, server.getContext().getContextPath()+GatewayServer.DFT_CONNECT_PATH, targetId);
+                client1.connect();
+                try
+                {
+                    final RHTTPClient client2 = new JettyClient(httpClient, address, server.getContext().getContextPath()+GatewayServer.DFT_CONNECT_PATH, targetId);
+                    try
+                    {
+                        client2.connect();
+                        fail();
+                    }
+                    catch (IOException x)
+                    {
+                    }
+                }
+                finally
+                {
+                    client1.disconnect();
+                }
+            }
+            finally
+            {
+                httpClient.stop();
+            }
+        }
+        finally
+        {
+            server.stop();
+        }
+    }
+}
diff --git a/jetty-rhttp/jetty-rhttp-gateway/src/test/java/org/eclipse/jetty/rhttp/gateway/ExternalRequestNotSuspendedTest.java b/jetty-rhttp/jetty-rhttp-gateway/src/test/java/org/eclipse/jetty/rhttp/gateway/ExternalRequestNotSuspendedTest.java
new file mode 100644
index 0000000..631c357
--- /dev/null
+++ b/jetty-rhttp/jetty-rhttp-gateway/src/test/java/org/eclipse/jetty/rhttp/gateway/ExternalRequestNotSuspendedTest.java
@@ -0,0 +1,186 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.rhttp.gateway;
+
+import java.io.IOException;
+import java.net.URLEncoder;
+import java.util.HashMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import junit.framework.TestCase;
+
+import org.eclipse.jetty.client.Address;
+import org.eclipse.jetty.client.ContentExchange;
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.HttpExchange;
+import org.eclipse.jetty.http.HttpMethods;
+import org.eclipse.jetty.io.ByteArrayBuffer;
+import org.eclipse.jetty.rhttp.client.JettyClient;
+import org.eclipse.jetty.rhttp.client.RHTTPClient;
+import org.eclipse.jetty.rhttp.client.RHTTPListener;
+import org.eclipse.jetty.rhttp.client.RHTTPRequest;
+import org.eclipse.jetty.rhttp.client.RHTTPResponse;
+import org.eclipse.jetty.rhttp.gateway.ExternalRequest;
+import org.eclipse.jetty.rhttp.gateway.Gateway;
+import org.eclipse.jetty.rhttp.gateway.GatewayServer;
+import org.eclipse.jetty.rhttp.gateway.StandardGateway;
+import org.eclipse.jetty.server.nio.SelectChannelConnector;
+
+
+/**
+ * @version $Revision$ $Date$
+ */
+public class ExternalRequestNotSuspendedTest extends TestCase
+{
+    public void testExternalRequestNotSuspended() throws Exception
+    {
+        final CountDownLatch respondLatch = new CountDownLatch(1);
+        final CountDownLatch suspendLatch = new CountDownLatch(1);
+        final AtomicBoolean suspended = new AtomicBoolean(true);
+        GatewayServer server = new GatewayServer()
+        {
+            @Override
+            protected Gateway createGateway()
+            {
+                StandardGateway gateway = new StandardGateway()
+                {
+                    @Override
+                    public ExternalRequest newExternalRequest(HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException
+                    {
+                        return new SlowToSuspendExternalRequest(super.newExternalRequest(httpRequest, httpResponse), respondLatch, suspendLatch, suspended);
+                    }
+                };
+                return gateway;
+            }
+        };
+        SelectChannelConnector connector = new SelectChannelConnector();
+        server.addConnector(connector);
+        server.start();
+        try
+        {
+            Address address = new Address("localhost", connector.getLocalPort());
+
+            HttpClient httpClient = new HttpClient();
+            httpClient.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);
+            httpClient.start();
+            try
+            {
+                String targetId = "1";
+                final RHTTPClient client = new JettyClient(httpClient, address, server.getContext().getContextPath()+GatewayServer.DFT_CONNECT_PATH, targetId);
+                final AtomicReference<Exception> exception = new AtomicReference<Exception>();
+                client.addListener(new RHTTPListener()
+                {
+                    public void onRequest(RHTTPRequest request)
+                    {
+                        try
+                        {
+                            RHTTPResponse response = new RHTTPResponse(request.getId(), 200, "OK", new HashMap<String, String>(), request.getBody());
+                            client.deliver(response);
+                        }
+                        catch (Exception x)
+                        {
+                            exception.set(x);
+                        }
+                    }
+                });
+
+                client.connect();
+                try
+                {
+                    // Make a request to the gateway and check response
+                    ContentExchange exchange = new ContentExchange(true);
+                    exchange.setMethod(HttpMethods.POST);
+                    exchange.setAddress(address);
+                    exchange.setURI(server.getContext().getContextPath()+GatewayServer.DFT_EXT_PATH + "/" + URLEncoder.encode(targetId, "UTF-8"));
+                    String requestContent = "body";
+                    exchange.setRequestContent(new ByteArrayBuffer(requestContent.getBytes("UTF-8")));
+                    httpClient.send(exchange);
+
+                    int status = exchange.waitForDone();
+                    assertEquals(HttpExchange.STATUS_COMPLETED, status);
+                    assertEquals(HttpServletResponse.SC_OK, exchange.getResponseStatus());
+                    assertNull(exception.get());
+
+                    suspendLatch.await();
+                    assertFalse(suspended.get());
+                }
+                finally
+                {
+                    client.disconnect();
+                }
+            }
+            finally
+            {
+                httpClient.stop();
+            }
+        }
+        finally
+        {
+            server.stop();
+        }
+    }
+
+    private class SlowToSuspendExternalRequest implements ExternalRequest
+    {
+        private final ExternalRequest delegate;
+        private final CountDownLatch respondLatch;
+        private final CountDownLatch suspendLatch;
+        private final AtomicBoolean suspended;
+
+        private SlowToSuspendExternalRequest(ExternalRequest delegate, CountDownLatch respondLatch, CountDownLatch suspendLatch, AtomicBoolean suspended)
+        {
+            this.delegate = delegate;
+            this.respondLatch = respondLatch;
+            this.suspendLatch = suspendLatch;
+            this.suspended = suspended;
+        }
+
+        public boolean suspend()
+        {
+            try
+            {
+                respondLatch.await();
+                boolean result = delegate.suspend();
+                suspended.set(result);
+                suspendLatch.countDown();
+                return result;
+            }
+            catch (InterruptedException x)
+            {
+                throw new AssertionError(x);
+            }
+        }
+
+        public void respond(RHTTPResponse response) throws IOException
+        {
+            delegate.respond(response);
+            respondLatch.countDown();
+        }
+
+        public RHTTPRequest getRequest()
+        {
+            return delegate.getRequest();
+        }
+    }
+}
diff --git a/jetty-rhttp/jetty-rhttp-gateway/src/test/java/org/eclipse/jetty/rhttp/gateway/ExternalTimeoutTest.java b/jetty-rhttp/jetty-rhttp-gateway/src/test/java/org/eclipse/jetty/rhttp/gateway/ExternalTimeoutTest.java
new file mode 100644
index 0000000..fba9334
--- /dev/null
+++ b/jetty-rhttp/jetty-rhttp-gateway/src/test/java/org/eclipse/jetty/rhttp/gateway/ExternalTimeoutTest.java
@@ -0,0 +1,126 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.rhttp.gateway;
+
+import java.net.URLEncoder;
+import java.util.HashMap;
+import java.util.concurrent.atomic.AtomicReference;
+
+import javax.servlet.http.HttpServletResponse;
+
+import junit.framework.TestCase;
+
+import org.eclipse.jetty.client.Address;
+import org.eclipse.jetty.client.ContentExchange;
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.HttpExchange;
+import org.eclipse.jetty.http.HttpMethods;
+import org.eclipse.jetty.io.ByteArrayBuffer;
+import org.eclipse.jetty.rhttp.client.JettyClient;
+import org.eclipse.jetty.rhttp.client.RHTTPClient;
+import org.eclipse.jetty.rhttp.client.RHTTPListener;
+import org.eclipse.jetty.rhttp.client.RHTTPRequest;
+import org.eclipse.jetty.rhttp.client.RHTTPResponse;
+import org.eclipse.jetty.rhttp.gateway.GatewayServer;
+import org.eclipse.jetty.rhttp.gateway.StandardGateway;
+import org.eclipse.jetty.server.nio.SelectChannelConnector;
+
+
+/**
+ * @version $Revision$ $Date$
+ */
+public class ExternalTimeoutTest extends TestCase
+{
+    public void testExternalTimeout() throws Exception
+    {
+        GatewayServer server = new GatewayServer();
+        SelectChannelConnector connector = new SelectChannelConnector();
+        server.addConnector(connector);
+        final long externalTimeout = 5000L;
+        ((StandardGateway)server.getGateway()).setExternalTimeout(externalTimeout);
+        server.start();
+        try
+        {
+            Address address = new Address("localhost", connector.getLocalPort());
+
+            HttpClient httpClient = new HttpClient();
+            httpClient.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);
+            httpClient.start();
+            try
+            {
+                String targetId = "1";
+                final RHTTPClient client = new JettyClient(httpClient, address, server.getContext().getContextPath()+GatewayServer.DFT_CONNECT_PATH, targetId);
+                final AtomicReference<Integer> requestId = new AtomicReference<Integer>();
+                final AtomicReference<Exception> exceptionRef = new AtomicReference<Exception>();
+                client.addListener(new RHTTPListener()
+                {
+                    public void onRequest(RHTTPRequest request)
+                    {
+                        try
+                        {
+                            // Save the request id
+                            requestId.set(request.getId());
+
+                            // Wait until external timeout expires
+                            Thread.sleep(externalTimeout + 1000L);
+                            RHTTPResponse response = new RHTTPResponse(request.getId(), 200, "OK", new HashMap<String, String>(), request.getBody());
+                            client.deliver(response);
+                        }
+                        catch (Exception x)
+                        {
+                            exceptionRef.set(x);
+                        }
+                    }
+                });
+
+                client.connect();
+                try
+                {
+                    // Make a request to the gateway and check response
+                    ContentExchange exchange = new ContentExchange(true);
+                    exchange.setMethod(HttpMethods.POST);
+                    exchange.setAddress(address);
+                    exchange.setURI(server.getContext().getContextPath()+GatewayServer.DFT_EXT_PATH + "/" + URLEncoder.encode(targetId, "UTF-8"));
+                    String requestContent = "body";
+                    exchange.setRequestContent(new ByteArrayBuffer(requestContent.getBytes("UTF-8")));
+                    httpClient.send(exchange);
+
+                    int status = exchange.waitForDone();
+                    assertEquals(HttpExchange.STATUS_COMPLETED, status);
+                    assertEquals(HttpServletResponse.SC_GATEWAY_TIMEOUT, exchange.getResponseStatus());
+                    assertNull(exceptionRef.get());
+
+                    assertNull(server.getGateway().removeExternalRequest(requestId.get()));
+                }
+                finally
+                {
+                    client.disconnect();
+                }
+            }
+            finally
+            {
+                httpClient.stop();
+            }
+        }
+        finally
+        {
+            server.stop();
+        }
+    }
+}
diff --git a/jetty-rhttp/jetty-rhttp-gateway/src/test/java/org/eclipse/jetty/rhttp/gateway/GatewayEchoServer.java b/jetty-rhttp/jetty-rhttp-gateway/src/test/java/org/eclipse/jetty/rhttp/gateway/GatewayEchoServer.java
new file mode 100644
index 0000000..769eb3f
--- /dev/null
+++ b/jetty-rhttp/jetty-rhttp-gateway/src/test/java/org/eclipse/jetty/rhttp/gateway/GatewayEchoServer.java
@@ -0,0 +1,109 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.rhttp.gateway;
+
+import java.util.HashMap;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.eclipse.jetty.client.Address;
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.rhttp.client.JettyClient;
+import org.eclipse.jetty.rhttp.client.RHTTPClient;
+import org.eclipse.jetty.rhttp.client.RHTTPListener;
+import org.eclipse.jetty.rhttp.client.RHTTPRequest;
+import org.eclipse.jetty.rhttp.client.RHTTPResponse;
+import org.eclipse.jetty.rhttp.gateway.GatewayServer;
+import org.eclipse.jetty.rhttp.gateway.TargetIdRetriever;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.nio.SelectChannelConnector;
+
+
+/**
+ * @version $Revision$ $Date$
+ */
+public class GatewayEchoServer
+{
+    private volatile GatewayServer server;
+    private volatile Address address;
+    private volatile String uri;
+    private volatile HttpClient httpClient;
+    private volatile RHTTPClient client;
+
+    public void start() throws Exception
+    {
+        server = new GatewayServer();
+        Connector connector = new SelectChannelConnector();
+        server.addConnector(connector);
+        server.setTargetIdRetriever(new EchoTargetIdRetriever());
+        server.start();
+        server.dumpStdErr();
+        address = new Address("localhost", connector.getLocalPort());
+        uri = server.getContext().getContextPath()+GatewayServer.DFT_EXT_PATH;
+
+        httpClient = new HttpClient();
+        httpClient.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);
+        httpClient.start();
+
+        client = new JettyClient(httpClient, new Address("localhost", connector.getLocalPort()), server.getContext().getContextPath()+GatewayServer.DFT_CONNECT_PATH, "echo");
+        client.addListener(new EchoListener(client));
+        client.connect();
+    }
+
+    public void stop() throws Exception
+    {
+        client.disconnect();
+        httpClient.stop();
+        server.stop();
+    }
+
+    public Address getAddress()
+    {
+        return address;
+    }
+
+    public String getURI()
+    {
+        return uri;
+    }
+
+    public static class EchoTargetIdRetriever implements TargetIdRetriever
+    {
+        public String retrieveTargetId(HttpServletRequest httpRequest)
+        {
+            return "echo";
+        }
+    }
+
+    private static class EchoListener implements RHTTPListener
+    {
+        private final RHTTPClient client;
+
+        public EchoListener(RHTTPClient client)
+        {
+            this.client = client;
+        }
+
+        public void onRequest(RHTTPRequest request) throws Exception
+        {
+            RHTTPResponse response = new RHTTPResponse(request.getId(), 200, "OK", new HashMap<String, String>(), request.getBody());
+            client.deliver(response);
+        }
+    }
+}
diff --git a/jetty-rhttp/jetty-rhttp-gateway/src/test/java/org/eclipse/jetty/rhttp/gateway/GatewayEchoTest.java b/jetty-rhttp/jetty-rhttp-gateway/src/test/java/org/eclipse/jetty/rhttp/gateway/GatewayEchoTest.java
new file mode 100644
index 0000000..103c1aa
--- /dev/null
+++ b/jetty-rhttp/jetty-rhttp-gateway/src/test/java/org/eclipse/jetty/rhttp/gateway/GatewayEchoTest.java
@@ -0,0 +1,77 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.rhttp.gateway;
+
+import javax.servlet.http.HttpServletResponse;
+
+import junit.framework.TestCase;
+
+import org.eclipse.jetty.client.ContentExchange;
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.HttpExchange;
+import org.eclipse.jetty.http.HttpMethods;
+import org.eclipse.jetty.io.ByteArrayBuffer;
+
+/**
+ * @version $Revision$ $Date$
+ */
+public class GatewayEchoTest extends TestCase
+{
+    /**
+     * Tests that the basic functionality of the gateway works,
+     * by issuing a request and by replying with the same body.
+     *
+     * @throws Exception in case of test exceptions
+     */
+    public void testEcho() throws Exception
+    {
+        GatewayEchoServer server = new GatewayEchoServer();
+        server.start();
+        try
+        {
+            HttpClient httpClient = new HttpClient();
+            httpClient.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);
+            httpClient.start();
+            try
+            {
+                // Make a request to the gateway and check response
+                ContentExchange exchange = new ContentExchange(true);
+                exchange.setMethod(HttpMethods.POST);
+                exchange.setAddress(server.getAddress());
+                exchange.setURI(server.getURI() + "/");
+                String requestBody = "body";
+                exchange.setRequestContent(new ByteArrayBuffer(requestBody.getBytes("UTF-8")));
+                httpClient.send(exchange);
+                int status = exchange.waitForDone();
+                assertEquals(HttpExchange.STATUS_COMPLETED, status);
+                assertEquals(HttpServletResponse.SC_OK, exchange.getResponseStatus());
+                String responseContent = exchange.getResponseContent();
+                assertEquals(responseContent, requestBody);
+            }
+            finally
+            {
+                httpClient.stop();
+            }
+        }
+        finally
+        {
+            server.stop();
+        }
+    }
+}
diff --git a/jetty-rhttp/jetty-rhttp-gateway/src/test/java/org/eclipse/jetty/rhttp/gateway/GatewayLoadTest.java b/jetty-rhttp/jetty-rhttp-gateway/src/test/java/org/eclipse/jetty/rhttp/gateway/GatewayLoadTest.java
new file mode 100644
index 0000000..c552675
--- /dev/null
+++ b/jetty-rhttp/jetty-rhttp-gateway/src/test/java/org/eclipse/jetty/rhttp/gateway/GatewayLoadTest.java
@@ -0,0 +1,202 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.rhttp.gateway;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+
+import javax.servlet.http.HttpServletResponse;
+
+import junit.framework.TestCase;
+
+import org.eclipse.jetty.client.ContentExchange;
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.http.HttpMethods;
+import org.eclipse.jetty.io.ByteArrayBuffer;
+
+/**
+ * @version $Revision$ $Date$
+ */
+public class GatewayLoadTest extends TestCase
+{
+    private final ConcurrentMap<Long, AtomicLong> latencies = new ConcurrentHashMap<Long, AtomicLong>();
+    private final AtomicLong responses = new AtomicLong(0L);
+    private final AtomicLong failures = new AtomicLong(0L);
+    private final AtomicLong minLatency = new AtomicLong(Long.MAX_VALUE);
+    private final AtomicLong maxLatency = new AtomicLong(0L);
+    private final AtomicLong totLatency = new AtomicLong(0L);
+
+    public void testEcho() throws Exception
+    {
+        GatewayEchoServer server = new GatewayEchoServer();
+        server.start();
+
+        HttpClient httpClient = new HttpClient();
+        httpClient.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);
+        httpClient.start();
+
+        String uri = server.getURI() + "/";
+
+        char[] chars = new char[1024];
+        Arrays.fill(chars, 'x');
+        String requestBody = new String(chars);
+
+        int count = 1000;
+        CountDownLatch latch = new CountDownLatch(count);
+        for (int i = 0; i < count; ++i)
+        {
+            GatewayLoadTestExchange exchange = new GatewayLoadTestExchange(latch);
+            exchange.setMethod(HttpMethods.POST);
+            exchange.setAddress(server.getAddress());
+            exchange.setURI(uri + i);
+            exchange.setRequestContent(new ByteArrayBuffer(requestBody.getBytes("UTF-8")));
+            exchange.setStartNanos(System.nanoTime());
+            httpClient.send(exchange);
+            Thread.sleep(5);
+        }
+        latch.await();
+        printLatencies(count);
+        assertEquals(count, responses.get() + failures.get());
+    }
+
+    private void updateLatencies(long start, long end)
+    {
+        long latency = end - start;
+
+        // Update the latencies using a non-blocking algorithm
+        long oldMinLatency = minLatency.get();
+        while (latency < oldMinLatency)
+        {
+            if (minLatency.compareAndSet(oldMinLatency, latency)) break;
+            oldMinLatency = minLatency.get();
+        }
+        long oldMaxLatency = maxLatency.get();
+        while (latency > oldMaxLatency)
+        {
+            if (maxLatency.compareAndSet(oldMaxLatency, latency)) break;
+            oldMaxLatency = maxLatency.get();
+        }
+        totLatency.addAndGet(latency);
+
+        latencies.putIfAbsent(latency, new AtomicLong(0L));
+        latencies.get(latency).incrementAndGet();
+    }
+
+    public void printLatencies(long expectedCount)
+    {
+        if (latencies.size() > 1)
+        {
+            long maxLatencyBucketFrequency = 0L;
+            long[] latencyBucketFrequencies = new long[20];
+            long latencyRange = maxLatency.get() - minLatency.get();
+            for (Iterator<Map.Entry<Long, AtomicLong>> entries = latencies.entrySet().iterator(); entries.hasNext();)
+            {
+                Map.Entry<Long, AtomicLong> entry = entries.next();
+                long latency = entry.getKey();
+                Long bucketIndex = (latency - minLatency.get()) * latencyBucketFrequencies.length / latencyRange;
+                int index = bucketIndex.intValue() == latencyBucketFrequencies.length ? latencyBucketFrequencies.length - 1 : bucketIndex.intValue();
+                long value = entry.getValue().get();
+                latencyBucketFrequencies[index] += value;
+                if (latencyBucketFrequencies[index] > maxLatencyBucketFrequency) maxLatencyBucketFrequency = latencyBucketFrequencies[index];
+                entries.remove();
+            }
+
+            System.out.println("Messages - Latency Distribution Curve (X axis: Frequency, Y axis: Latency):");
+            for (int i = 0; i < latencyBucketFrequencies.length; i++)
+            {
+                long latencyBucketFrequency = latencyBucketFrequencies[i];
+                int value = Math.round(latencyBucketFrequency * (float) latencyBucketFrequencies.length / maxLatencyBucketFrequency);
+                if (value == latencyBucketFrequencies.length) value = value - 1;
+                for (int j = 0; j < value; ++j) System.out.print(" ");
+                System.out.print("@");
+                for (int j = value + 1; j < latencyBucketFrequencies.length; ++j) System.out.print(" ");
+                System.out.print("  _  ");
+                System.out.print(TimeUnit.NANOSECONDS.toMillis((latencyRange * (i + 1) / latencyBucketFrequencies.length) + minLatency.get()));
+                System.out.print(" (" + latencyBucketFrequency + ")");
+                System.out.println(" ms");
+            }
+        }
+
+        long responseCount = responses.get();
+        System.out.print("Messages success/failed/expected = ");
+        System.out.print(responseCount);
+        System.out.print("/");
+        System.out.print(failures.get());
+        System.out.print("/");
+        System.out.print(expectedCount);
+        System.out.print(" - Latency min/ave/max = ");
+        System.out.print(TimeUnit.NANOSECONDS.toMillis(minLatency.get()) + "/");
+        System.out.print(responseCount == 0 ? "-/" : TimeUnit.NANOSECONDS.toMillis(totLatency.get() / responseCount) + "/");
+        System.out.println(TimeUnit.NANOSECONDS.toMillis(maxLatency.get()) + " ms");
+    }
+
+    private class GatewayLoadTestExchange extends ContentExchange
+    {
+        private final CountDownLatch latch;
+        private volatile long start;
+
+        private GatewayLoadTestExchange(CountDownLatch latch)
+        {
+            super(true);
+            this.latch = latch;
+        }
+
+        @Override
+        protected void onResponseComplete() throws IOException
+        {
+            if (getResponseStatus() == HttpServletResponse.SC_OK)
+            {
+                long end = System.nanoTime();
+                responses.incrementAndGet();
+                updateLatencies(start, end);
+            }
+            else
+            {
+                failures.incrementAndGet();
+            }
+            latch.countDown();
+        }
+
+        @Override
+        protected void onException(Throwable throwable)
+        {
+            failures.incrementAndGet();
+            latch.countDown();
+        }
+
+        @Override
+        protected void onExpire()
+        {
+            failures.incrementAndGet();
+            latch.countDown();
+        }
+
+        public void setStartNanos(long value)
+        {
+            start = value;
+        }
+    }
+}
diff --git a/jetty-rhttp/jetty-rhttp-gateway/src/test/java/org/eclipse/jetty/rhttp/gateway/GatewayTimeoutTest.java b/jetty-rhttp/jetty-rhttp-gateway/src/test/java/org/eclipse/jetty/rhttp/gateway/GatewayTimeoutTest.java
new file mode 100644
index 0000000..504e4f4
--- /dev/null
+++ b/jetty-rhttp/jetty-rhttp-gateway/src/test/java/org/eclipse/jetty/rhttp/gateway/GatewayTimeoutTest.java
@@ -0,0 +1,130 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.rhttp.gateway;
+
+import java.net.URLEncoder;
+import java.util.HashMap;
+import java.util.concurrent.atomic.AtomicReference;
+
+import javax.servlet.http.HttpServletResponse;
+
+import junit.framework.TestCase;
+
+import org.eclipse.jetty.client.Address;
+import org.eclipse.jetty.client.ContentExchange;
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.HttpExchange;
+import org.eclipse.jetty.http.HttpMethods;
+import org.eclipse.jetty.io.ByteArrayBuffer;
+import org.eclipse.jetty.rhttp.client.JettyClient;
+import org.eclipse.jetty.rhttp.client.RHTTPClient;
+import org.eclipse.jetty.rhttp.client.RHTTPListener;
+import org.eclipse.jetty.rhttp.client.RHTTPRequest;
+import org.eclipse.jetty.rhttp.client.RHTTPResponse;
+import org.eclipse.jetty.rhttp.gateway.GatewayServer;
+import org.eclipse.jetty.rhttp.gateway.StandardGateway;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.nio.SelectChannelConnector;
+
+
+/**
+ * @version $Revision$ $Date$
+ */
+public class GatewayTimeoutTest extends TestCase
+{
+    /**
+     * Tests a forwarded request that lasts longer than the gateway timeout.
+     * The gateway client will perform 2 long polls before the forwarded request's response is returned.
+     *
+     * @throws Exception in case of test exceptions
+     */
+    public void testGatewayTimeout() throws Exception
+    {
+        GatewayServer server = new GatewayServer();
+        Connector connector = new SelectChannelConnector();
+        server.addConnector(connector);
+        final long gatewayTimeout = 5000L;
+        ((StandardGateway)server.getGateway()).setGatewayTimeout(gatewayTimeout);
+        final long externalTimeout = gatewayTimeout * 2;
+        ((StandardGateway)server.getGateway()).setExternalTimeout(externalTimeout);
+        server.start();
+        try
+        {
+            Address address = new Address("localhost", connector.getLocalPort());
+
+            HttpClient httpClient = new HttpClient();
+            httpClient.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);
+            httpClient.start();
+            try
+            {
+                String targetId = "1";
+                final RHTTPClient client = new JettyClient(httpClient, address, server.getContext().getContextPath()+GatewayServer.DFT_CONNECT_PATH, targetId);
+                final AtomicReference<Exception> exceptionRef = new AtomicReference<Exception>();
+                client.addListener(new RHTTPListener()
+                {
+                    public void onRequest(RHTTPRequest request)
+                    {
+                        try
+                        {
+                            // Wait until gateway timeout expires
+                            Thread.sleep(gatewayTimeout + 1000L);
+                            RHTTPResponse response = new RHTTPResponse(request.getId(), 200, "OK", new HashMap<String, String>(), request.getBody());
+                            client.deliver(response);
+                        }
+                        catch (Exception x)
+                        {
+                            exceptionRef.set(x);
+                        }
+                    }
+                });
+                client.connect();
+                try
+                {
+                    // Make a request to the gateway and check response
+                    ContentExchange exchange = new ContentExchange(true);
+                    exchange.setMethod(HttpMethods.POST);
+                    exchange.setAddress(address);
+                    exchange.setURI(server.getContext().getContextPath()+GatewayServer.DFT_EXT_PATH + "/" + URLEncoder.encode(targetId, "UTF-8"));
+                    String requestContent = "body";
+                    exchange.setRequestContent(new ByteArrayBuffer(requestContent.getBytes("UTF-8")));
+                    httpClient.send(exchange);
+
+                    int status = exchange.waitForDone();
+                    assertEquals(HttpExchange.STATUS_COMPLETED, status);
+                    assertEquals(HttpServletResponse.SC_OK, exchange.getResponseStatus());
+                    assertNull(exceptionRef.get());
+                    String responseContent = exchange.getResponseContent();
+                    assertEquals(responseContent, requestContent);
+                }
+                finally
+                {
+                    client.disconnect();
+                }
+            }
+            finally
+            {
+                httpClient.stop();
+            }
+        }
+        finally
+        {
+            server.stop();
+        }
+    }
+}
diff --git a/jetty-rhttp/jetty-rhttp-gateway/src/test/java/org/eclipse/jetty/rhttp/gateway/HandshakeClientTest.java b/jetty-rhttp/jetty-rhttp-gateway/src/test/java/org/eclipse/jetty/rhttp/gateway/HandshakeClientTest.java
new file mode 100644
index 0000000..b2e4ca8
--- /dev/null
+++ b/jetty-rhttp/jetty-rhttp-gateway/src/test/java/org/eclipse/jetty/rhttp/gateway/HandshakeClientTest.java
@@ -0,0 +1,75 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.rhttp.gateway;
+
+import junit.framework.TestCase;
+
+import org.eclipse.jetty.client.Address;
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.rhttp.client.JettyClient;
+import org.eclipse.jetty.rhttp.client.RHTTPClient;
+import org.eclipse.jetty.rhttp.gateway.GatewayServer;
+import org.eclipse.jetty.rhttp.gateway.StandardGateway;
+import org.eclipse.jetty.server.nio.SelectChannelConnector;
+
+
+/**
+ * @version $Revision$ $Date$
+ */
+public class HandshakeClientTest extends TestCase
+{
+    public void testConnectReturnsImmediately() throws Exception
+    {
+        GatewayServer server = new GatewayServer();
+        SelectChannelConnector connector = new SelectChannelConnector();
+        server.addConnector(connector);
+        long gwt=5000L;
+        ((StandardGateway)server.getGateway()).setGatewayTimeout(gwt);
+        server.start();
+        try
+        {
+            HttpClient httpClient = new HttpClient();
+            httpClient.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);
+            httpClient.start();
+            try
+            {
+                RHTTPClient client = new JettyClient(httpClient, new Address("localhost", connector.getLocalPort()), server.getContext().getContextPath()+GatewayServer.DFT_CONNECT_PATH, "test1");
+                long start = System.currentTimeMillis();
+                client.connect();
+                try
+                {
+                    long end = System.currentTimeMillis();
+                    assertTrue(end - start < gwt / 2);
+                }
+                finally
+                {
+                    client.disconnect();
+                }
+            }
+            finally
+            {
+                httpClient.stop();
+            }
+        }
+        finally
+        {
+            server.stop();
+        }
+    }
+}
diff --git a/jetty-rhttp/jetty-rhttp-gateway/src/test/java/org/eclipse/jetty/rhttp/gateway/HostTargetIdRetrieverTest.java b/jetty-rhttp/jetty-rhttp-gateway/src/test/java/org/eclipse/jetty/rhttp/gateway/HostTargetIdRetrieverTest.java
new file mode 100644
index 0000000..8da0206
--- /dev/null
+++ b/jetty-rhttp/jetty-rhttp-gateway/src/test/java/org/eclipse/jetty/rhttp/gateway/HostTargetIdRetrieverTest.java
@@ -0,0 +1,107 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.rhttp.gateway;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.eclipse.jetty.rhttp.gateway.HostTargetIdRetriever;
+
+import junit.framework.TestCase;
+
+/**
+ * @version $Revision$ $Date$
+ */
+public class HostTargetIdRetrieverTest extends TestCase
+{
+    public void testHostTargetIdRetrieverNoSuffix()
+    {
+        String host = "test";
+        Class<HttpServletRequest> klass = HttpServletRequest.class;
+        HttpServletRequest request = (HttpServletRequest)Proxy.newProxyInstance(klass.getClassLoader(), new Class<?>[]{klass}, new Request(host));
+
+        HostTargetIdRetriever retriever = new HostTargetIdRetriever(null);
+        String result = retriever.retrieveTargetId(request);
+
+        assertEquals(host, result);
+    }
+
+    public void testHostTargetIdRetrieverWithSuffix()
+    {
+        String suffix = ".rhttp.example.com";
+        String host = "test";
+        Class<HttpServletRequest> klass = HttpServletRequest.class;
+        HttpServletRequest request = (HttpServletRequest)Proxy.newProxyInstance(klass.getClassLoader(), new Class<?>[]{klass}, new Request(host + suffix));
+
+        HostTargetIdRetriever retriever = new HostTargetIdRetriever(suffix);
+        String result = retriever.retrieveTargetId(request);
+
+        assertEquals(host, result);
+    }
+
+    public void testHostTargetIdRetrieverWithSuffixAndPort()
+    {
+        String suffix = ".rhttp.example.com";
+        String host = "test";
+        Class<HttpServletRequest> klass = HttpServletRequest.class;
+        HttpServletRequest request = (HttpServletRequest)Proxy.newProxyInstance(klass.getClassLoader(), new Class<?>[]{klass}, new Request(host + suffix + ":8080"));
+
+        HostTargetIdRetriever retriever = new HostTargetIdRetriever(suffix);
+        String result = retriever.retrieveTargetId(request);
+
+        assertEquals(host, result);
+    }
+
+    public void testHostTargetIdRetrieverNullHost()
+    {
+        Class<HttpServletRequest> klass = HttpServletRequest.class;
+        HttpServletRequest request = (HttpServletRequest)Proxy.newProxyInstance(klass.getClassLoader(), new Class<?>[]{klass}, new Request(null));
+
+        HostTargetIdRetriever retriever = new HostTargetIdRetriever(".rhttp.example.com");
+        String result = retriever.retrieveTargetId(request);
+
+        assertNull(result);
+    }
+
+    private static class Request implements InvocationHandler
+    {
+        private final String host;
+
+        private Request(String host)
+        {
+            this.host = host;
+        }
+
+        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
+        {
+            if ("getHeader".equals(method.getName()))
+            {
+                if (args.length == 1 && "Host".equals(args[0]))
+                {
+                    return host;
+                }
+            }
+            return null;
+        }
+    }
+
+}
diff --git a/jetty-rhttp/jetty-rhttp-gateway/src/test/resources/log4j.properties b/jetty-rhttp/jetty-rhttp-gateway/src/test/resources/log4j.properties
new file mode 100644
index 0000000..8e6eddb
--- /dev/null
+++ b/jetty-rhttp/jetty-rhttp-gateway/src/test/resources/log4j.properties
@@ -0,0 +1,13 @@
+# LOG4J levels: OFF, FATAL, ERROR, WARN, INFO, DEBUG, ALL
+#
+log4j.rootLogger=ALL,CONSOLE
+
+log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
+#log4j.appender.CONSOLE.threshold=INFO
+log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
+#log4j.appender.CONSOLE.layout.ConversionPattern=%d %t [%5p][%c{1}] %m%n
+log4j.appender.CONSOLE.layout.ConversionPattern=%d [%5p][%c] %m%n
+
+# Level tuning
+log4j.logger.org.eclipse.jetty=INFO
+log4j.logger.org.mortbay.jetty.rhttp=INFO
diff --git a/jetty-rhttp/jetty-rhttp-loadtest/pom.xml b/jetty-rhttp/jetty-rhttp-loadtest/pom.xml
new file mode 100644
index 0000000..bd36d4e
--- /dev/null
+++ b/jetty-rhttp/jetty-rhttp-loadtest/pom.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>org.eclipse.jetty.rhttp</groupId>
+        <artifactId>jetty-rhttp-project</artifactId>
+        <version>9.0.0-SNAPSHOT</version>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>reverse-http-loadtest</artifactId>
+    <packaging>jar</packaging>
+    <name>Jetty :: Reverse HTTP :: Load Test</name>
+
+    <profiles>
+        <profile>
+            <id>loader</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.codehaus.mojo</groupId>
+                        <artifactId>exec-maven-plugin</artifactId>
+                        <configuration>
+                            <mainClass>org.eclipse.jetty.rhttp.loadtest.Loader</mainClass>
+                            <classpathScope>runtime</classpathScope>
+                        </configuration>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+        <profile>
+            <id>server</id>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.codehaus.mojo</groupId>
+                        <artifactId>exec-maven-plugin</artifactId>
+                        <configuration>
+                            <mainClass>org.eclipse.jetty.rhttp.loadtest.Server</mainClass>
+                            <classpathScope>runtime</classpathScope>
+                        </configuration>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+    </profiles>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>reverse-http-gateway</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+    </dependencies>
+
+</project>
diff --git a/jetty-rhttp/jetty-rhttp-loadtest/src/main/java/org/eclipse/jetty/rhttp/loadtest/Loader.java b/jetty-rhttp/jetty-rhttp-loadtest/src/main/java/org/eclipse/jetty/rhttp/loadtest/Loader.java
new file mode 100644
index 0000000..50dbd28
--- /dev/null
+++ b/jetty-rhttp/jetty-rhttp-loadtest/src/main/java/org/eclipse/jetty/rhttp/loadtest/Loader.java
@@ -0,0 +1,429 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.rhttp.loadtest;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.eclipse.jetty.client.Address;
+import org.eclipse.jetty.client.ContentExchange;
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.io.ByteArrayBuffer;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.mortbay.jetty.rhttp.client.RHTTPClient;
+import org.mortbay.jetty.rhttp.client.JettyClient;
+import org.mortbay.jetty.rhttp.client.RHTTPListener;
+import org.mortbay.jetty.rhttp.client.RHTTPRequest;
+import org.mortbay.jetty.rhttp.client.RHTTPResponse;
+
+/**
+ * @version $Revision$ $Date$
+ */
+public class Loader
+{
+    private final List<RHTTPClient> clients = new ArrayList<RHTTPClient>();
+    private final AtomicLong start = new AtomicLong();
+    private final AtomicLong end = new AtomicLong();
+    private final AtomicLong responses = new AtomicLong();
+    private final AtomicLong failures = new AtomicLong();
+    private final AtomicLong minLatency = new AtomicLong();
+    private final AtomicLong maxLatency = new AtomicLong();
+    private final AtomicLong totLatency = new AtomicLong();
+    private final ConcurrentMap<Long, AtomicLong> latencies = new ConcurrentHashMap<Long, AtomicLong>();
+    private final String nodeName;
+
+    public static void main(String[] args) throws Exception
+    {
+        String nodeName = "";
+        if (args.length > 0)
+            nodeName = args[0];
+
+        Loader loader = new Loader(nodeName);
+        loader.run();
+    }
+
+    public Loader(String nodeName)
+    {
+        this.nodeName = nodeName;
+    }
+
+    private void run() throws Exception
+    {
+        HttpClient httpClient = new HttpClient();
+        httpClient.setMaxConnectionsPerAddress(40000);
+        QueuedThreadPool threadPool = new QueuedThreadPool();
+        threadPool.setMaxThreads(500);
+        threadPool.setDaemon(true);
+        httpClient.setThreadPool(threadPool);
+        httpClient.setIdleTimeout(5000);
+        httpClient.start();
+
+        Random random = new Random();
+
+        BufferedReader console = new BufferedReader(new InputStreamReader(System.in));
+
+        System.err.print("server [localhost]: ");
+        String value = console.readLine().trim();
+        if (value.length() == 0)
+            value = "localhost";
+        String host = value;
+
+        System.err.print("port [8080]: ");
+        value = console.readLine().trim();
+        if (value.length() == 0)
+            value = "8080";
+        int port = Integer.parseInt(value);
+
+        System.err.print("context []: ");
+        value = console.readLine().trim();
+        if (value.length() == 0)
+            value = "";
+        String context = value;
+
+        System.err.print("external path [/]: ");
+        value = console.readLine().trim();
+        if (value.length() == 0)
+            value = "/";
+        String externalPath = value;
+
+        System.err.print("gateway path [/__gateway]: ");
+        value = console.readLine().trim();
+        if (value.length() == 0)
+            value = "/__gateway";
+        String gatewayPath = value;
+
+        int clients = 100;
+        int batchCount = 1000;
+        int batchSize = 5;
+        long batchPause = 5;
+        int requestSize = 50;
+
+        while (true)
+        {
+            System.err.println("-----");
+
+            System.err.print("clients [" + clients + "]: ");
+            value = console.readLine();
+            if (value == null)
+                break;
+            value = value.trim();
+            if (value.length() == 0)
+                value = "" + clients;
+            clients = Integer.parseInt(value);
+
+            System.err.println("Waiting for clients to be ready...");
+
+            Address gatewayAddress = new Address(host, port);
+            String gatewayURI = context + gatewayPath;
+
+            // Create or remove the necessary clients
+            int currentClients = this.clients.size();
+            if (currentClients < clients)
+            {
+                for (int i = 0; i < clients - currentClients; ++i)
+                {
+                    final RHTTPClient client = new JettyClient(httpClient, gatewayAddress, gatewayURI, nodeName + (currentClients + i));
+                    client.addListener(new EchoListener(client));
+                    client.connect();
+                    this.clients.add(client);
+
+                    // Give some time to the server to accept connections and
+                    // reply to handshakes and connects
+                    if (i % 10 == 0)
+                    {
+                        Thread.sleep(100);
+                    }
+                }
+            }
+            else if (currentClients > clients)
+            {
+                for (int i = 0; i < currentClients - clients; ++i)
+                {
+                    RHTTPClient client = this.clients.remove(currentClients - i - 1);
+                    client.disconnect();
+                }
+            }
+
+            System.err.println("Clients ready");
+
+            currentClients = this.clients.size();
+            if (currentClients > 0)
+            {
+                System.err.print("batch count [" + batchCount + "]: ");
+                value = console.readLine().trim();
+                if (value.length() == 0)
+                    value = "" + batchCount;
+                batchCount = Integer.parseInt(value);
+
+                System.err.print("batch size [" + batchSize + "]: ");
+                value = console.readLine().trim();
+                if (value.length() == 0)
+                    value = "" + batchSize;
+                batchSize = Integer.parseInt(value);
+
+                System.err.print("batch pause [" + batchPause + "]: ");
+                value = console.readLine().trim();
+                if (value.length() == 0)
+                    value = "" + batchPause;
+                batchPause = Long.parseLong(value);
+
+                System.err.print("request size [" + requestSize + "]: ");
+                value = console.readLine().trim();
+                if (value.length() == 0)
+                    value = "" + requestSize;
+                requestSize = Integer.parseInt(value);
+                String requestBody = "";
+                for (int i = 0; i < requestSize; i++)
+                    requestBody += "x";
+
+                String externalURL = "http://" + host + ":" + port + context + externalPath;
+                if (!externalURL.endsWith("/"))
+                    externalURL += "/";
+
+                reset();
+
+                long start = System.nanoTime();
+                long expected = 0;
+                for (int i = 0; i < batchCount; ++i)
+                {
+                    for (int j = 0; j < batchSize; ++j)
+                    {
+                        int clientIndex = random.nextInt(this.clients.size());
+                        RHTTPClient client = this.clients.get(clientIndex);
+                        String targetId = client.getTargetId();
+                        String url = externalURL + targetId;
+
+                        ExternalExchange exchange = new ExternalExchange();
+                        exchange.setMethod("GET");
+                        exchange.setURL(url);
+                        exchange.setRequestContent(new ByteArrayBuffer(requestBody, "UTF-8"));
+                        exchange.send(httpClient);
+                        ++expected;
+                    }
+
+                    if (batchPause > 0)
+                        Thread.sleep(batchPause);
+                }
+                long end = System.nanoTime();
+                long elapsedNanos = end - start;
+                if (elapsedNanos > 0)
+                {
+                    System.err.print("Messages - Elapsed | Rate = ");
+                    System.err.print(TimeUnit.NANOSECONDS.toMillis(elapsedNanos));
+                    System.err.print(" ms | ");
+                    System.err.print(expected * 1000 * 1000 * 1000 / elapsedNanos);
+                    System.err.println(" requests/s ");
+                }
+
+                waitForResponses(expected);
+                printReport(expected);
+            }
+        }
+    }
+
+    private void reset()
+    {
+        start.set(0L);
+        end.set(0L);
+        responses.set(0L);
+        failures.set(0L);
+        minLatency.set(Long.MAX_VALUE);
+        maxLatency.set(0L);
+        totLatency.set(0L);
+    }
+
+    private void updateLatencies(long start, long end)
+    {
+        long latency = end - start;
+
+        // Update the latencies using a non-blocking algorithm
+        long oldMinLatency = minLatency.get();
+        while (latency < oldMinLatency)
+        {
+            if (minLatency.compareAndSet(oldMinLatency, latency)) break;
+            oldMinLatency = minLatency.get();
+        }
+        long oldMaxLatency = maxLatency.get();
+        while (latency > oldMaxLatency)
+        {
+            if (maxLatency.compareAndSet(oldMaxLatency, latency)) break;
+            oldMaxLatency = maxLatency.get();
+        }
+        totLatency.addAndGet(latency);
+
+        latencies.putIfAbsent(latency, new AtomicLong(0L));
+        latencies.get(latency).incrementAndGet();
+    }
+
+    private boolean waitForResponses(long expected) throws InterruptedException
+    {
+        long arrived = responses.get() + failures.get();
+        long lastArrived = 0;
+        int maxRetries = 20;
+        int retries = maxRetries;
+        while (arrived < expected)
+        {
+            System.err.println("Waiting for responses to arrive " + arrived + "/" + expected);
+            Thread.sleep(500);
+            if (lastArrived == arrived)
+            {
+                --retries;
+                if (retries == 0) break;
+            }
+            else
+            {
+                lastArrived = arrived;
+                retries = maxRetries;
+            }
+            arrived = responses.get() + failures.get();
+        }
+        if (arrived < expected)
+        {
+            System.err.println("Interrupting wait for responses " + arrived + "/" + expected);
+            return false;
+        }
+        else
+        {
+            System.err.println("All responses arrived " + arrived + "/" + expected);
+            return true;
+        }
+    }
+
+    public void printReport(long expectedCount)
+    {
+        long responseCount = responses.get() + failures.get();
+        System.err.print("Messages - Success/Failures/Expected = ");
+        System.err.print(responses.get());
+        System.err.print("/");
+        System.err.print(failures.get());
+        System.err.print("/");
+        System.err.println(expectedCount);
+
+        long elapsedNanos = end.get() - start.get();
+        if (elapsedNanos > 0)
+        {
+            System.err.print("Messages - Elapsed | Rate = ");
+            System.err.print(TimeUnit.NANOSECONDS.toMillis(elapsedNanos));
+            System.err.print(" ms | ");
+            System.err.print(responseCount * 1000 * 1000 * 1000 / elapsedNanos);
+            System.err.println(" responses/s ");
+        }
+
+        if (latencies.size() > 1)
+        {
+            long maxLatencyBucketFrequency = 0L;
+            long[] latencyBucketFrequencies = new long[20];
+            long latencyRange = maxLatency.get() - minLatency.get();
+            for (Iterator<Map.Entry<Long, AtomicLong>> entries = latencies.entrySet().iterator(); entries.hasNext();)
+            {
+                Map.Entry<Long, AtomicLong> entry = entries.next();
+                long latency = entry.getKey();
+                Long bucketIndex = (latency - minLatency.get()) * latencyBucketFrequencies.length / latencyRange;
+                int index = bucketIndex.intValue() == latencyBucketFrequencies.length ? latencyBucketFrequencies.length - 1 : bucketIndex.intValue();
+                long value = entry.getValue().get();
+                latencyBucketFrequencies[index] += value;
+                if (latencyBucketFrequencies[index] > maxLatencyBucketFrequency) maxLatencyBucketFrequency = latencyBucketFrequencies[index];
+                entries.remove();
+            }
+
+            System.err.println("Messages - Latency Distribution Curve (X axis: Frequency, Y axis: Latency):");
+            for (int i = 0; i < latencyBucketFrequencies.length; i++)
+            {
+                long latencyBucketFrequency = latencyBucketFrequencies[i];
+                int value = Math.round(latencyBucketFrequency * (float) latencyBucketFrequencies.length / maxLatencyBucketFrequency);
+                if (value == latencyBucketFrequencies.length) value = value - 1;
+                for (int j = 0; j < value; ++j) System.err.print(" ");
+                System.err.print("@");
+                for (int j = value + 1; j < latencyBucketFrequencies.length; ++j) System.err.print(" ");
+                System.err.print("  _  ");
+                System.err.print(TimeUnit.NANOSECONDS.toMillis((latencyRange * (i + 1) / latencyBucketFrequencies.length) + minLatency.get()));
+                System.err.println(" ms (" + latencyBucketFrequency + ")");
+            }
+        }
+
+        System.err.print("Messages - Latency Min/Ave/Max = ");
+        System.err.print(TimeUnit.NANOSECONDS.toMillis(minLatency.get()) + "/");
+        System.err.print(responseCount == 0 ? "-/" : TimeUnit.NANOSECONDS.toMillis(totLatency.get() / responseCount) + "/");
+        System.err.println(TimeUnit.NANOSECONDS.toMillis(maxLatency.get()) + " ms");
+    }
+
+    private class ExternalExchange extends ContentExchange
+    {
+        private volatile long sendTime;
+
+        private ExternalExchange()
+        {
+            super(true);
+        }
+
+        private void send(HttpClient httpClient) throws IOException
+        {
+            this.sendTime = System.nanoTime();
+            httpClient.send(this);
+        }
+
+        @Override
+        protected void onResponseComplete() throws IOException
+        {
+            if (getResponseStatus() == 200)
+                responses.incrementAndGet();
+            else
+                failures.incrementAndGet();
+
+            long arrivalTime = System.nanoTime();
+            if (start.get() == 0L)
+                start.set(arrivalTime);
+            end.set(arrivalTime);
+            updateLatencies(sendTime, arrivalTime);
+        }
+
+        @Override
+        protected void onException(Throwable x)
+        {
+            failures.incrementAndGet();
+        }
+    }
+
+    private static class EchoListener implements RHTTPListener
+    {
+        private final RHTTPClient client;
+
+        public EchoListener(RHTTPClient client)
+        {
+            this.client = client;
+        }
+
+        public void onRequest(RHTTPRequest request) throws Exception
+        {
+            RHTTPResponse response = new RHTTPResponse(request.getId(), 200, "OK", new HashMap<String, String>(), request.getBody());
+            client.deliver(response);
+        }
+    }
+}
diff --git a/jetty-rhttp/jetty-rhttp-loadtest/src/main/java/org/eclipse/jetty/rhttp/loadtest/Server.java b/jetty-rhttp/jetty-rhttp-loadtest/src/main/java/org/eclipse/jetty/rhttp/loadtest/Server.java
new file mode 100644
index 0000000..57016cf
--- /dev/null
+++ b/jetty-rhttp/jetty-rhttp-loadtest/src/main/java/org/eclipse/jetty/rhttp/loadtest/Server.java
@@ -0,0 +1,69 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.rhttp.loadtest;
+
+import org.eclipse.jetty.rhttp.gateway.GatewayServer;
+import org.eclipse.jetty.rhttp.gateway.StandardTargetIdRetriever;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.nio.SelectChannelConnector;
+
+/**
+ * @version $Revision$ $Date$
+ */
+public class Server
+{
+    public static void main(String[] args) throws Exception
+    {
+        int port = 8080;
+        if (args.length > 0)
+            port = Integer.parseInt(args[0]);
+
+        GatewayServer server = new GatewayServer();
+        Connector connector = new SelectChannelConnector();
+        connector.setLowResourceMaxIdleTime(connector.getMaxIdleTime());
+        connector.setPort(port);
+        server.addConnector(connector);
+        server.setTargetIdRetriever(new StandardTargetIdRetriever());
+        server.start();
+        server.getServer().dumpStdErr();
+        Runtime.getRuntime().addShutdownHook(new Shutdown(server));
+    }
+
+    private static class Shutdown extends Thread
+    {
+        private final GatewayServer server;
+
+        public Shutdown(GatewayServer server)
+        {
+            this.server = server;
+        }
+
+        @Override
+        public void run()
+        {
+            try
+            {
+                server.stop();
+            }
+            catch (Exception ignored)
+            {
+            }
+        }
+    }
+}
diff --git a/jetty-rhttp/jetty-rhttp-loadtest/src/main/resources/log4j.properties b/jetty-rhttp/jetty-rhttp-loadtest/src/main/resources/log4j.properties
new file mode 100644
index 0000000..8e6eddb
--- /dev/null
+++ b/jetty-rhttp/jetty-rhttp-loadtest/src/main/resources/log4j.properties
@@ -0,0 +1,13 @@
+# LOG4J levels: OFF, FATAL, ERROR, WARN, INFO, DEBUG, ALL
+#
+log4j.rootLogger=ALL,CONSOLE
+
+log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
+#log4j.appender.CONSOLE.threshold=INFO
+log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
+#log4j.appender.CONSOLE.layout.ConversionPattern=%d %t [%5p][%c{1}] %m%n
+log4j.appender.CONSOLE.layout.ConversionPattern=%d [%5p][%c] %m%n
+
+# Level tuning
+log4j.logger.org.eclipse.jetty=INFO
+log4j.logger.org.mortbay.jetty.rhttp=INFO
diff --git a/jetty-rhttp/pom.xml b/jetty-rhttp/pom.xml
new file mode 100644
index 0000000..0284a0f
--- /dev/null
+++ b/jetty-rhttp/pom.xml
@@ -0,0 +1,107 @@
+<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">
+    <parent>
+        <groupId>org.eclipse.jetty</groupId>
+        <artifactId>jetty-project</artifactId>
+        <version>9.0.0-SNAPSHOT</version>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>org.eclipse.jetty.rhttp</groupId>
+    <artifactId>jetty-rhttp-project</artifactId>
+    <packaging>pom</packaging>
+    <name>Jetty :: Reverse HTTP</name>
+
+    <properties>
+    </properties>
+
+    <modules>
+        <module>reverse-http-client</module>
+        <module>reverse-http-connector</module>
+        <module>reverse-http-gateway</module>
+        <module>reverse-http-loadtest</module>
+    </modules>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.sonatype.maven.plugin</groupId>
+                <artifactId>emma-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <phase>process-classes</phase>
+                        <goals>
+                            <goal>instrument</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <configuration>
+                    <classesDirectory>${project.build.directory}/generated-classes/emma/classes</classesDirectory>
+                    <systemProperties>
+                        <property>
+                            <name>emma.coverage.out.file</name>
+                            <value>${project.build.directory}/coverage.ec</value>
+                        </property>
+                    </systemProperties>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.sonatype.maven.plugin</groupId>
+                <artifactId>emma4it-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>report</id>
+                        <phase>post-integration-test</phase>
+                        <goals>
+                            <goal>report</goal>
+                        </goals>
+                        <configuration>
+                            <sourceSets>
+                                <sourceSet>
+                                    <directory>${project.build.sourceDirectory}</directory>
+                                </sourceSet>
+                            </sourceSets>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>javax.servlet</groupId>
+                <artifactId>servlet-api</artifactId>
+            </dependency>
+            <dependency>
+                <groupId>org.eclipse.jetty</groupId>
+                <artifactId>jetty-server</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.eclipse.jetty</groupId>
+                <artifactId>jetty-client</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.eclipse.jetty</groupId>
+                <artifactId>jetty-io</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.eclipse.jetty</groupId>
+                <artifactId>jetty-util</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.eclipse.jetty.toolchain</groupId>
+                <artifactId>jetty-test-helper</artifactId>
+                <scope>test</scope>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+</project>
diff --git a/jetty-runner/pom.xml b/jetty-runner/pom.xml
new file mode 100644
index 0000000..03ed06f
--- /dev/null
+++ b/jetty-runner/pom.xml
@@ -0,0 +1,101 @@
+<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">
+  <parent>
+    <groupId>org.eclipse.jetty</groupId>
+    <artifactId>jetty-project</artifactId>
+    <version>9.2.13.v20150730</version>
+  </parent>
+  <modelVersion>4.0.0</modelVersion>
+  <artifactId>jetty-runner</artifactId>
+  <name>Jetty :: Runner</name>
+
+ <properties>
+    <assembly-directory>target/distribution</assembly-directory>
+  </properties>
+  <url>http://www.eclipse.org/jetty</url>
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-dependency-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>unpack-dependencies</id>
+            <phase>package</phase>
+            <goals>
+              <goal>unpack-dependencies</goal>
+            </goals>
+            <configuration>
+              <includes>**</includes>
+              <excludes>**/MANIFEST.MF,META-INF/*.RSA,META-INF/*.DSA,META-INF/*.SF</excludes>
+              <outputDirectory>${project.build.directory}/classes</outputDirectory>
+              <overWriteReleases>false</overWriteReleases>
+              <overWriteSnapshots>true</overWriteSnapshots>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins
+        </groupId>
+        <artifactId>maven-jar-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>package</id>
+            <phase>package</phase>
+            <goals>
+              <goal>jar</goal>
+            </goals>
+            <configuration>
+              <archive>
+                <manifest>
+                  <mainClass>org.eclipse.jetty.runner.Runner</mainClass>
+                </manifest>
+                <manifestEntries>
+                  <mode>development</mode>
+                  <url>http://eclipse.org/jetty</url>
+                  <Built-By>${user.name}</Built-By>
+                  <package>org.eclipse.jetty.runner</package>
+                  <Bundle-Name>Jetty Runner</Bundle-Name>
+                  <Bundle-Vendor>Mort Bay Consulting</Bundle-Vendor>
+                </manifestEntries>
+              </archive>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-plus</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-annotations</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-jaas</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.websocket</groupId>
+      <artifactId>websocket-server</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-jndi</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+      <dependency>
+        <groupId>org.eclipse.jetty</groupId>
+        <artifactId>jetty-jsp</artifactId>
+        <version>${project.version}</version>
+      </dependency>
+  </dependencies>
+</project>
diff --git a/jetty-runner/src/main/java/org/eclipse/jetty/runner/Runner.java b/jetty-runner/src/main/java/org/eclipse/jetty/runner/Runner.java
new file mode 100644
index 0000000..f596e61
--- /dev/null
+++ b/jetty-runner/src/main/java/org/eclipse/jetty/runner/Runner.java
@@ -0,0 +1,566 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.runner;
+
+import org.eclipse.jetty.security.ConstraintMapping;
+import org.eclipse.jetty.security.ConstraintSecurityHandler;
+import org.eclipse.jetty.security.HashLoginService;
+import org.eclipse.jetty.security.authentication.BasicAuthenticator;
+import org.eclipse.jetty.server.*;
+import org.eclipse.jetty.server.handler.*;
+import org.eclipse.jetty.server.session.SessionHandler;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jetty.servlet.StatisticsServlet;
+import org.eclipse.jetty.util.RolloverFileOutputStream;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.util.security.Constraint;
+import org.eclipse.jetty.webapp.WebAppContext;
+import org.eclipse.jetty.xml.XmlConfiguration;
+
+import java.io.IOException;
+import java.io.PrintStream;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+
+
+/**
+ * Runner
+ *
+ * Combine jetty classes into a single executable jar and run webapps based on the args to it.
+ *
+ */
+public class Runner
+{
+    private static final Logger LOG = Log.getLogger(Runner.class);
+
+    public static final String[] __plusConfigurationClasses = new String[] {
+            org.eclipse.jetty.webapp.WebInfConfiguration.class.getCanonicalName(),
+            org.eclipse.jetty.webapp.WebXmlConfiguration.class.getCanonicalName(),
+            org.eclipse.jetty.webapp.MetaInfConfiguration.class.getCanonicalName(),
+            org.eclipse.jetty.webapp.FragmentConfiguration.class.getCanonicalName(),
+            org.eclipse.jetty.plus.webapp.EnvConfiguration.class.getCanonicalName(),
+            org.eclipse.jetty.plus.webapp.PlusConfiguration.class.getCanonicalName(),
+            org.eclipse.jetty.annotations.AnnotationConfiguration.class.getCanonicalName(),
+            org.eclipse.jetty.webapp.JettyWebXmlConfiguration.class.getCanonicalName()
+            };
+    public static final String __containerIncludeJarPattern =  ".*/jetty-runner-[^/]*\\.jar$";
+    public static final String __defaultContextPath = "/";
+    public static final int __defaultPort = 8080;
+
+    protected Server _server;
+    protected URLClassLoader _classLoader;
+    protected Classpath _classpath = new Classpath();
+    protected ContextHandlerCollection _contexts;
+    protected RequestLogHandler _logHandler;
+    protected String _logFile;
+    protected ArrayList<String> _configFiles;
+    protected boolean _enableStats=false;
+    protected String _statsPropFile;
+
+
+
+    /**
+     * Classpath
+     *
+     *
+     */
+    public class Classpath
+    {
+        private  List<URL> _classpath = new ArrayList<>();
+
+        public void addJars (Resource lib) throws IOException
+        {
+            if (lib == null || !lib.exists())
+                throw new IllegalStateException ("No such lib: "+lib);
+
+            String[] list = lib.list();
+            if (list==null)
+                return;
+
+            for (String path : list)
+            {
+                if (".".equals(path) || "..".equals(path))
+                    continue;
+
+                try(Resource item = lib.addPath(path))
+                {
+                    if (item.isDirectory())
+                        addJars(item);
+                    else
+                    {
+                        if (path.toLowerCase().endsWith(".jar") ||
+                            path.toLowerCase().endsWith(".zip"))
+                        {
+                            URL url = item.getURL();
+                            _classpath.add(url);
+                        }
+                    }
+                }
+            }
+        }
+
+
+        public void addPath (Resource path)
+        {
+            if (path == null || !path.exists())
+                throw new IllegalStateException ("No such path: "+path);
+            _classpath.add(path.getURL());
+        }
+
+
+        public URL[] asArray ()
+        {
+            return _classpath.toArray(new URL[_classpath.size()]);
+        }
+    }
+
+
+
+
+    /**
+     *
+     */
+    public Runner()
+    {
+
+    }
+
+
+    /**
+     * Generate helpful usage message and exit
+     *
+     * @param error
+     */
+    public void usage(String error)
+    {
+        if (error!=null)
+            System.err.println("ERROR: "+error);
+        System.err.println("Usage: java [-Djetty.home=dir] -jar jetty-runner.jar [--help|--version] [ server opts] [[ context opts] context ...] ");
+        System.err.println("Server opts:");
+        System.err.println(" --version                           - display version and exit");
+        System.err.println(" --log file                          - request log filename (with optional 'yyyy_mm_dd' wildcard");
+        System.err.println(" --out file                          - info/warn/debug log filename (with optional 'yyyy_mm_dd' wildcard");
+        System.err.println(" --host name|ip                      - interface to listen on (default is all interfaces)");
+        System.err.println(" --port n                            - port to listen on (default 8080)");
+        System.err.println(" --stop-port n                       - port to listen for stop command");
+        System.err.println(" --stop-key n                        - security string for stop command (required if --stop-port is present)");
+        System.err.println(" [--jar file]*n                      - each tuple specifies an extra jar to be added to the classloader");
+        System.err.println(" [--lib dir]*n                       - each tuple specifies an extra directory of jars to be added to the classloader");
+        System.err.println(" [--classes dir]*n                   - each tuple specifies an extra directory of classes to be added to the classloader");
+        System.err.println(" --stats [unsecure|realm.properties] - enable stats gathering servlet context");
+        System.err.println(" [--config file]*n                   - each tuple specifies the name of a jetty xml config file to apply (in the order defined)");
+        System.err.println("Context opts:");
+        System.err.println(" [[--path /path] context]*n          - WAR file, web app dir or context xml file, optionally with a context path");
+        System.exit(1);
+    }
+
+
+    /**
+     * Generate version message and exit
+     */
+    public void version ()
+    {
+        System.err.println("org.eclipse.jetty.runner.Runner: "+Server.getVersion());
+        System.exit(1);
+    }
+
+
+
+    /**
+     * Configure a jetty instance and deploy the webapps presented as args
+     *
+     * @param args
+     * @throws Exception
+     */
+    public void configure(String[] args) throws Exception
+    {
+        // handle classpath bits first so we can initialize the log mechanism.
+        for (int i=0;i<args.length;i++)
+        {
+            if ("--lib".equals(args[i]))
+            {
+                try(Resource lib = Resource.newResource(args[++i]))
+                {
+                    if (!lib.exists() || !lib.isDirectory())
+                        usage("No such lib directory "+lib);
+                    _classpath.addJars(lib);
+                }
+            }
+            else if ("--jar".equals(args[i]))
+            {
+                try(Resource jar = Resource.newResource(args[++i]))
+                {
+                    if (!jar.exists() || jar.isDirectory())
+                        usage("No such jar "+jar);
+                    _classpath.addPath(jar);
+                }
+            }
+            else if ("--classes".equals(args[i]))
+            {
+                try(Resource classes = Resource.newResource(args[++i]))
+                {
+                    if (!classes.exists() || !classes.isDirectory())
+                        usage("No such classes directory "+classes);
+                    _classpath.addPath(classes);
+                }
+            }
+            else if (args[i].startsWith("--"))
+                i++;
+        }
+
+        initClassLoader();
+
+        LOG.info("Runner");
+        LOG.debug("Runner classpath {}",_classpath);
+
+        String contextPath = __defaultContextPath;
+        boolean contextPathSet = false;
+        int port = __defaultPort;
+        String host = null;
+        int stopPort = 0;
+        String stopKey = null;
+
+        boolean runnerServerInitialized = false;
+
+        for (int i=0;i<args.length;i++)
+        {
+            switch (args[i]) 
+            {
+                case "--port":
+                    port = Integer.parseInt(args[++i]);
+                    break;
+                case "--host":
+                    host = args[++i];
+                    break;
+                case "--stop-port":
+                    stopPort = Integer.parseInt(args[++i]);
+                    break;
+                case "--stop-key":
+                    stopKey = args[++i];
+                    break;
+                case "--log":
+                    _logFile = args[++i];
+                    break;
+                case "--out":
+                    String outFile = args[++i];
+                    PrintStream out = new PrintStream(new RolloverFileOutputStream(outFile, true, -1));
+                    LOG.info("Redirecting stderr/stdout to " + outFile);
+                    System.setErr(out);
+                    System.setOut(out);
+                    break;
+                case "--path":
+                    contextPath = args[++i];
+                    contextPathSet = true;
+                    break;
+                case "--config":
+                    if (_configFiles == null)
+                        _configFiles = new ArrayList<>();
+                    _configFiles.add(args[++i]);
+                    break;
+                case "--lib":
+                    ++i;//skip
+
+                    break;
+                case "--jar":
+                    ++i; //skip
+
+                    break;
+                case "--classes":
+                    ++i;//skip
+
+                    break;
+                case "--stats":
+                    _enableStats = true;
+                    _statsPropFile = args[++i];
+                    _statsPropFile = ("unsecure".equalsIgnoreCase(_statsPropFile) ? null : _statsPropFile);
+                    break;
+                default:
+// process contexts
+
+                    if (!runnerServerInitialized) // log handlers not registered, server maybe not created, etc
+                    {
+                        if (_server == null) // server not initialized yet
+                        {
+                            // build the server
+                            _server = new Server();
+                        }
+
+                        //apply jetty config files if there are any
+                        if (_configFiles != null) 
+                        {
+                            for (String cfg : _configFiles) 
+                            {
+                                try (Resource resource = Resource.newResource(cfg)) {
+                                    XmlConfiguration xmlConfiguration = new XmlConfiguration(resource.getURL());
+                                    xmlConfiguration.configure(_server);
+                                }
+                            }
+                        }
+
+                        //check that everything got configured, and if not, make the handlers
+                        HandlerCollection handlers = (HandlerCollection) _server.getChildHandlerByClass(HandlerCollection.class);
+                        if (handlers == null) 
+                        {
+                            handlers = new HandlerCollection();
+                            _server.setHandler(handlers);
+                        }
+
+                        //check if contexts already configured
+                        _contexts = (ContextHandlerCollection) handlers.getChildHandlerByClass(ContextHandlerCollection.class);
+                        if (_contexts == null) 
+                        {
+                            _contexts = new ContextHandlerCollection();
+                            prependHandler(_contexts, handlers);
+                        }
+
+
+                        if (_enableStats) 
+                        {
+                            //if no stats handler already configured
+                            if (handlers.getChildHandlerByClass(StatisticsHandler.class) == null) {
+                                StatisticsHandler statsHandler = new StatisticsHandler();
+
+
+                                Handler oldHandler = _server.getHandler();
+                                statsHandler.setHandler(oldHandler);
+                                _server.setHandler(statsHandler);
+
+
+                                ServletContextHandler statsContext = new ServletContextHandler(_contexts, "/stats");
+                                statsContext.addServlet(new ServletHolder(new StatisticsServlet()), "/");
+                                statsContext.setSessionHandler(new SessionHandler());
+                                if (_statsPropFile != null) 
+                                {
+                                    HashLoginService loginService = new HashLoginService("StatsRealm", _statsPropFile);
+                                    Constraint constraint = new Constraint();
+                                    constraint.setName("Admin Only");
+                                    constraint.setRoles(new String[]{"admin"});
+                                    constraint.setAuthenticate(true);
+
+                                    ConstraintMapping cm = new ConstraintMapping();
+                                    cm.setConstraint(constraint);
+                                    cm.setPathSpec("/*");
+
+                                    ConstraintSecurityHandler securityHandler = new ConstraintSecurityHandler();
+                                    securityHandler.setLoginService(loginService);
+                                    securityHandler.setConstraintMappings(Collections.singletonList(cm));
+                                    securityHandler.setAuthenticator(new BasicAuthenticator());
+                                    statsContext.setSecurityHandler(securityHandler);
+                                }
+                            }
+                        }
+
+                        //ensure a DefaultHandler is present
+                        if (handlers.getChildHandlerByClass(DefaultHandler.class) == null) 
+                        {
+                            handlers.addHandler(new DefaultHandler());
+                        }
+
+                        //ensure a log handler is present
+                        _logHandler = (RequestLogHandler) handlers.getChildHandlerByClass(RequestLogHandler.class);
+                        if (_logHandler == null) 
+                        {
+                            _logHandler = new RequestLogHandler();
+                            handlers.addHandler(_logHandler);
+                        }
+
+
+                        //check a connector is configured to listen on
+                        Connector[] connectors = _server.getConnectors();
+                        if (connectors == null || connectors.length == 0) 
+                        {
+                            ServerConnector connector = new ServerConnector(_server);
+                            connector.setPort(port);
+                            if (host != null)
+                                connector.setHost(host);
+                            _server.addConnector(connector);
+                            if (_enableStats)
+                                connector.addBean(new ConnectorStatistics());
+                        } 
+                        else 
+                        {
+                            if (_enableStats) 
+                            {
+                                for (Connector connector : connectors) 
+                                {
+                                    ((AbstractConnector) connector).addBean(new ConnectorStatistics());
+                                }
+                            }
+                        }
+
+                        runnerServerInitialized = true;
+                    }
+
+                    // Create a context
+                    try (Resource ctx = Resource.newResource(args[i])) 
+                    {
+                        if (!ctx.exists())
+                            usage("Context '" + ctx + "' does not exist");
+
+                        if (contextPathSet && !(contextPath.startsWith("/")))
+                            contextPath = "/" + contextPath;
+
+                        // Configure the context
+                        if (!ctx.isDirectory() && ctx.toString().toLowerCase().endsWith(".xml")) 
+                        {
+                            // It is a context config file
+                            XmlConfiguration xmlConfiguration = new XmlConfiguration(ctx.getURL());
+                            xmlConfiguration.getIdMap().put("Server", _server);
+                            ContextHandler handler = (ContextHandler) xmlConfiguration.configure();
+                            if (contextPathSet)
+                                handler.setContextPath(contextPath);
+                            _contexts.addHandler(handler);
+                            handler.setAttribute("org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern", __containerIncludeJarPattern);
+                        }
+                        else 
+                        {
+                            // assume it is a WAR file
+                            WebAppContext webapp = new WebAppContext(_contexts, ctx.toString(), contextPath);
+                            webapp.setConfigurationClasses(__plusConfigurationClasses);
+                            webapp.setAttribute("org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern",
+                                    __containerIncludeJarPattern);
+                        }
+                    }
+                    //reset
+                    contextPathSet = false;
+                    contextPath = __defaultContextPath;
+                    break;
+            }
+        }
+
+        if (_server==null)
+            usage("No Contexts defined");
+        _server.setStopAtShutdown(true);
+
+        switch ((stopPort > 0 ? 1 : 0) + (stopKey != null ? 2 : 0))
+        {
+            case 1:
+                usage("Must specify --stop-key when --stop-port is specified");
+                break;
+
+            case 2:
+                usage("Must specify --stop-port when --stop-key is specified");
+                break;
+
+            case 3:
+                ShutdownMonitor monitor = ShutdownMonitor.getInstance();
+                monitor.setPort(stopPort);
+                monitor.setKey(stopKey);
+                monitor.setExitVm(true);
+                break;
+        }
+
+        if (_logFile!=null)
+        {
+            NCSARequestLog requestLog = new NCSARequestLog(_logFile);
+            requestLog.setExtended(false);
+            _logHandler.setRequestLog(requestLog);
+        }
+    }
+
+
+    /**
+     * @param handler
+     * @param handlers
+     */
+    protected void prependHandler (Handler handler, HandlerCollection handlers)
+    {
+        if (handler == null || handlers == null)
+            return;
+
+       Handler[] existing = handlers.getChildHandlers();
+       Handler[] children = new Handler[existing.length + 1];
+       children[0] = handler;
+       System.arraycopy(existing, 0, children, 1, existing.length);
+       handlers.setHandlers(children);
+    }
+
+
+
+
+    /**
+     * @throws Exception
+     */
+    public void run() throws Exception
+    {
+        _server.start();
+        _server.join();
+    }
+
+
+    /**
+     * Establish a classloader with custom paths (if any)
+     */
+    protected void initClassLoader()
+    {
+        URL[] paths = _classpath.asArray();
+
+        if (_classLoader==null && paths !=null && paths.length > 0)
+        {
+            ClassLoader context=Thread.currentThread().getContextClassLoader();
+
+            if (context==null)
+                _classLoader=new URLClassLoader(paths);
+            else
+                _classLoader=new URLClassLoader(paths, context);
+
+            Thread.currentThread().setContextClassLoader(_classLoader);
+        }
+    }
+
+
+
+
+    /**
+     * @param args
+     */
+    public static void main(String[] args)
+    {
+        Runner runner = new Runner();
+
+        try
+        {
+            if (args.length>0&&args[0].equalsIgnoreCase("--help"))
+            {
+                runner.usage(null);
+            }
+            else if (args.length>0&&args[0].equalsIgnoreCase("--version"))
+            {
+                runner.version();
+            }
+            else
+            {
+                runner.configure(args);
+                runner.run();
+            }
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+            runner.usage(null);
+        }
+    }
+}
diff --git a/jetty-runner/src/main/java/org/eclipse/jetty/runner/package-info.java b/jetty-runner/src/main/java/org/eclipse/jetty/runner/package-info.java
new file mode 100644
index 0000000..01ddadb
--- /dev/null
+++ b/jetty-runner/src/main/java/org/eclipse/jetty/runner/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Runner : Embedded Jetty Tool for running webapps directly
+ */
+package org.eclipse.jetty.runner;
+
diff --git a/jetty-security/pom.xml b/jetty-security/pom.xml
index 3445cf7..014de45 100644
--- a/jetty-security/pom.xml
+++ b/jetty-security/pom.xml
@@ -2,7 +2,7 @@
   <parent>
     <groupId>org.eclipse.jetty</groupId>
     <artifactId>jetty-project</artifactId>
-    <version>8.1.17.v20150415</version>
+    <version>9.2.13.v20150730</version>
   </parent>
   <modelVersion>4.0.0</modelVersion>
   <artifactId>jetty-security</artifactId>
@@ -25,7 +25,7 @@
             </goals>
             <configuration>
               <instructions>
-                <Import-Package>javax.servlet.*;version="2.6.0",javax.security.cert,*</Import-Package>
+                <Import-Package>javax.servlet.*;version="[2.6.0,3.2)",javax.security.cert,*</Import-Package>
               </instructions>
             </configuration>
 
diff --git a/jetty-security/src/main/config/modules/security.mod b/jetty-security/src/main/config/modules/security.mod
new file mode 100644
index 0000000..ba31632
--- /dev/null
+++ b/jetty-security/src/main/config/modules/security.mod
@@ -0,0 +1,9 @@
+#
+# Jetty Security Module
+#
+
+[depend]
+server
+
+[lib]
+lib/jetty-security-${jetty.version}.jar
diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/AbstractUserAuthentication.java b/jetty-security/src/main/java/org/eclipse/jetty/security/AbstractUserAuthentication.java
new file mode 100644
index 0000000..b7b6752
--- /dev/null
+++ b/jetty-security/src/main/java/org/eclipse/jetty/security/AbstractUserAuthentication.java
@@ -0,0 +1,98 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import java.io.Serializable;
+import java.util.Set;
+
+import org.eclipse.jetty.server.Authentication.User;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.server.UserIdentity.Scope;
+
+/**
+ * AbstractUserAuthentication
+ *
+ *
+ * Base class for representing an authenticated user.
+ */
+public abstract class AbstractUserAuthentication implements User, Serializable
+{
+    private static final long serialVersionUID = -6290411814232723403L;
+    protected String _method;
+    protected transient UserIdentity _userIdentity;
+    
+    
+    
+    public AbstractUserAuthentication(String method, UserIdentity userIdentity)
+    {
+        _method = method;
+        _userIdentity = userIdentity;
+    }
+    
+
+    @Override
+    public String getAuthMethod()
+    {
+        return _method;
+    }
+
+    @Override
+    public UserIdentity getUserIdentity()
+    {
+        return _userIdentity;
+    }
+
+    @Override
+    public boolean isUserInRole(Scope scope, String role)
+    {
+        String roleToTest = null;
+        if (scope!=null && scope.getRoleRefMap()!=null)
+            roleToTest=scope.getRoleRefMap().get(role);
+        if (roleToTest==null)
+            roleToTest=role;
+        //Servlet Spec 3.1 pg 125 if testing special role **
+        if ("**".equals(roleToTest.trim()))
+        {
+            //if ** is NOT a declared role name, the we return true 
+            //as the user is authenticated. If ** HAS been declared as a
+            //role name, then we have to check if the user has that role
+            if (!declaredRolesContains("**"))
+                return true;
+            else
+                return _userIdentity.isUserInRole(role, scope);
+        }
+      
+        return _userIdentity.isUserInRole(role, scope);
+    }
+
+    public boolean declaredRolesContains(String roleName)
+    {
+        SecurityHandler security=SecurityHandler.getCurrentSecurityHandler();
+        if (security==null)
+            return false;
+        
+        if (security instanceof ConstraintAware)
+        {
+            Set<String> declaredRoles = ((ConstraintAware)security).getRoles();
+            return (declaredRoles != null) && declaredRoles.contains(roleName);
+        }
+        
+        return false;
+    }
+}
diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/Authenticator.java b/jetty-security/src/main/java/org/eclipse/jetty/security/Authenticator.java
index 7f3ba3b..423fcad 100644
--- a/jetty-security/src/main/java/org/eclipse/jetty/security/Authenticator.java
+++ b/jetty-security/src/main/java/org/eclipse/jetty/security/Authenticator.java
@@ -32,10 +32,10 @@ import org.eclipse.jetty.server.Server;
  * Authenticator Interface
  * <p>
  * An Authenticator is responsible for checking requests and sending
- * response challenges in order to authenticate a request. 
+ * response challenges in order to authenticate a request.
  * Various types of {@link Authentication} are returned in order to
  * signal the next step in authentication.
- * 
+ *
  * @version $Rev: 4793 $ $Date: 2009-03-19 00:00:01 +0100 (Thu, 19 Mar 2009) $
  */
 public interface Authenticator
@@ -46,27 +46,43 @@ public interface Authenticator
      * @param configuration
      */
     void setConfiguration(AuthConfiguration configuration);
-    
+
     /* ------------------------------------------------------------ */
     /**
      * @return The name of the authentication method
      */
     String getAuthMethod();
     
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Called prior to validateRequest. The authenticator can
+     * manipulate the request to update it with information that
+     * can be inspected prior to validateRequest being called.
+     * The primary purpose of this method is to satisfy the Servlet
+     * Spec 3.1 section 13.6.3 on handling Form authentication
+     * where the http method of the original request causing authentication
+     * is not the same as the http method resulting from the redirect
+     * after authentication.
+     * @param request
+     */
+    void prepareRequest(ServletRequest request);
+    
+
     /* ------------------------------------------------------------ */
-    /** Validate a response
+    /** Validate a request
      * @param request The request
      * @param response The response
      * @param mandatory True if authentication is mandatory.
-     * @return An Authentication.  If Authentication is successful, this will be a {@link org.eclipse.jetty.server.Authentication.User}. If a response has 
+     * @return An Authentication.  If Authentication is successful, this will be a {@link org.eclipse.jetty.server.Authentication.User}. If a response has
      * been sent by the Authenticator (which can be done for both successful and unsuccessful authentications), then the result will
-     * implement {@link org.eclipse.jetty.server.Authentication.ResponseSent}.  If Authentication is not manditory, then a 
+     * implement {@link org.eclipse.jetty.server.Authentication.ResponseSent}.  If Authentication is not manditory, then a
      * {@link org.eclipse.jetty.server.Authentication.Deferred} may be returned.
-     * 
+     *
      * @throws ServerAuthException
      */
     Authentication validateRequest(ServletRequest request, ServletResponse response, boolean mandatory) throws ServerAuthException;
-    
+
     /* ------------------------------------------------------------ */
     /**
      * @param request
@@ -77,33 +93,33 @@ public interface Authenticator
      * @throws ServerAuthException
      */
     boolean secureResponse(ServletRequest request, ServletResponse response, boolean mandatory, User validatedUser) throws ServerAuthException;
-    
-    
+
+
     /* ------------------------------------------------------------ */
     /* ------------------------------------------------------------ */
     /* ------------------------------------------------------------ */
-    /** 
+    /**
      * Authenticator Configuration
      */
     interface AuthConfiguration
     {
         String getAuthMethod();
         String getRealmName();
-        
+
         /** Get a SecurityHandler init parameter
          * @see SecurityHandler#getInitParameter(String)
          * @param param parameter name
          * @return Parameter value or null
          */
         String getInitParameter(String param);
-        
+
         /* ------------------------------------------------------------ */
         /** Get a SecurityHandler init parameter names
          * @see SecurityHandler#getInitParameterNames()
          * @return Set of parameter names
          */
         Set<String> getInitParameterNames();
-        
+
         LoginService getLoginService();
         IdentityService getIdentityService();
         boolean isSessionRenewedOnAuthentication();
@@ -112,7 +128,7 @@ public interface Authenticator
     /* ------------------------------------------------------------ */
     /* ------------------------------------------------------------ */
     /* ------------------------------------------------------------ */
-    /** 
+    /**
      * Authenticator Factory
      */
     interface Factory
diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/ConstraintAware.java b/jetty-security/src/main/java/org/eclipse/jetty/security/ConstraintAware.java
index f9e3516..a93166c 100644
--- a/jetty-security/src/main/java/org/eclipse/jetty/security/ConstraintAware.java
+++ b/jetty-security/src/main/java/org/eclipse/jetty/security/ConstraintAware.java
@@ -51,4 +51,20 @@ public interface ConstraintAware
      * @param role
      */
     void addRole(String role);
+    
+    /**
+     * See Servlet Spec 31, sec 13.8.4, pg 145
+     * When true, requests with http methods not explicitly covered either by inclusion or omissions
+     * in constraints, will have access denied.
+     * @param deny
+     */
+    void setDenyUncoveredHttpMethods(boolean deny);
+    
+    boolean isDenyUncoveredHttpMethods();
+    
+    /**
+     * See Servlet Spec 31, sec 13.8.4, pg 145
+     * Container must check if there are urls with uncovered http methods
+     */
+    boolean checkPathsWithUncoveredHttpMethods();
 }
diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/ConstraintSecurityHandler.java b/jetty-security/src/main/java/org/eclipse/jetty/security/ConstraintSecurityHandler.java
index fb43e64..28b2f59 100644
--- a/jetty-security/src/main/java/org/eclipse/jetty/security/ConstraintSecurityHandler.java
+++ b/jetty-security/src/main/java/org/eclipse/jetty/security/ConstraintSecurityHandler.java
@@ -23,6 +23,7 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
@@ -31,44 +32,47 @@ import java.util.Set;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.CopyOnWriteArraySet;
 
-import org.eclipse.jetty.http.HttpSchemes;
 import javax.servlet.HttpConstraintElement;
 import javax.servlet.HttpMethodConstraintElement;
 import javax.servlet.ServletSecurityElement;
 import javax.servlet.annotation.ServletSecurity.EmptyRoleSemantic;
 import javax.servlet.annotation.ServletSecurity.TransportGuarantee;
 
+import org.eclipse.jetty.http.HttpStatus;
 import org.eclipse.jetty.http.PathMap;
-import org.eclipse.jetty.server.AbstractHttpConnection;
-import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.HttpChannel;
+import org.eclipse.jetty.server.HttpConfiguration;
 import org.eclipse.jetty.server.Request;
 import org.eclipse.jetty.server.Response;
 import org.eclipse.jetty.server.UserIdentity;
-import org.eclipse.jetty.util.StringMap;
-import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
 import org.eclipse.jetty.util.security.Constraint;
 
 /* ------------------------------------------------------------ */
 /**
+ * ConstraintSecurityHandler
+ * 
  * Handler to enforce SecurityConstraints. This implementation is servlet spec
- * 3.0 compliant and precomputes the constraint combinations for runtime
+ * 3.1 compliant and pre-computes the constraint combinations for runtime
  * efficiency.
  *
  */
 public class ConstraintSecurityHandler extends SecurityHandler implements ConstraintAware
 {
-    private static final String OMISSION_SUFFIX = ".omission";
-    
-    private final List<ConstraintMapping> _constraintMappings= new CopyOnWriteArrayList<ConstraintMapping>();
-    private final Set<String> _roles = new CopyOnWriteArraySet<String>();
-    private final PathMap _constraintMap = new PathMap();
-    private boolean _strict = true;
-    
+    private static final Logger LOG = Log.getLogger(SecurityHandler.class); //use same as SecurityHandler
     
+    private static final String OMISSION_SUFFIX = ".omission";
+    private static final String ALL_METHODS = "*";
+    private final List<ConstraintMapping> _constraintMappings= new CopyOnWriteArrayList<>();
+    private final Set<String> _roles = new CopyOnWriteArraySet<>();
+    private final PathMap<Map<String, RoleInfo>> _constraintMap = new PathMap<>();
+    private boolean _denyUncoveredMethods = false;
+
+
     /* ------------------------------------------------------------ */
-    /**
-     * @return
-     */
     public static Constraint createConstraint()
     {
         return new Constraint();
@@ -77,7 +81,6 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr
     /* ------------------------------------------------------------ */
     /**
      * @param constraint
-     * @return
      */
     public static Constraint createConstraint(Constraint constraint)
     {
@@ -99,7 +102,6 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr
      * @param authenticate
      * @param roles
      * @param dataConstraint
-     * @return
      */
     public static Constraint createConstraint (String name, boolean authenticate, String[] roles, int dataConstraint)
     {
@@ -117,7 +119,6 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr
     /**
      * @param name
      * @param element
-     * @return
      */
     public static Constraint createConstraint (String name, HttpConstraintElement element)
     {
@@ -131,7 +132,6 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr
      * @param rolesAllowed
      * @param permitOrDeny
      * @param transport
-     * @return
      */
     public static Constraint createConstraint (String name, String[] rolesAllowed, EmptyRoleSemantic permitOrDeny, TransportGuarantee transport)
     {
@@ -171,7 +171,6 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr
     /**
      * @param pathSpec
      * @param constraintMappings
-     * @return
      */
     public static List<ConstraintMapping> getConstraintMappingsForPath(String pathSpec, List<ConstraintMapping> constraintMappings)
     {
@@ -196,7 +195,6 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr
      * 
      * @param pathSpec
      * @param constraintMappings a new list minus the matching constraints
-     * @return
      */
     public static List<ConstraintMapping> removeConstraintMappingsForPath(String pathSpec, List<ConstraintMapping> constraintMappings)
     {
@@ -230,86 +228,69 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr
         List<ConstraintMapping> mappings = new ArrayList<ConstraintMapping>();
 
         //Create a constraint that will describe the default case (ie if not overridden by specific HttpMethodConstraints)
-        Constraint constraint = ConstraintSecurityHandler.createConstraint(name, securityElement);
-
-        //Create a mapping for the pathSpec for the default case
-        ConstraintMapping defaultMapping = new ConstraintMapping();
-        defaultMapping.setPathSpec(pathSpec);
-        defaultMapping.setConstraint(constraint);  
-        mappings.add(defaultMapping);
+        Constraint httpConstraint = null;
+        ConstraintMapping httpConstraintMapping = null;
+        
+        if (securityElement.getEmptyRoleSemantic() != EmptyRoleSemantic.PERMIT ||
+            securityElement.getRolesAllowed().length != 0 ||
+            securityElement.getTransportGuarantee() != TransportGuarantee.NONE)
+        {
+            httpConstraint = ConstraintSecurityHandler.createConstraint(name, securityElement);
 
+            //Create a mapping for the pathSpec for the default case
+            httpConstraintMapping = new ConstraintMapping();
+            httpConstraintMapping.setPathSpec(pathSpec);
+            httpConstraintMapping.setConstraint(httpConstraint); 
+            mappings.add(httpConstraintMapping);
+        }
+        
 
         //See Spec 13.4.1.2 p127
         List<String> methodOmissions = new ArrayList<String>();
         
         //make constraint mappings for this url for each of the HttpMethodConstraintElements
-        Collection<HttpMethodConstraintElement> methodConstraints = securityElement.getHttpMethodConstraints();
-        if (methodConstraints != null)
+        Collection<HttpMethodConstraintElement> methodConstraintElements = securityElement.getHttpMethodConstraints();
+        if (methodConstraintElements != null)
         {
-            for (HttpMethodConstraintElement methodConstraint:methodConstraints)
+            for (HttpMethodConstraintElement methodConstraintElement:methodConstraintElements)
             {
                 //Make a Constraint that captures the <auth-constraint> and <user-data-constraint> elements supplied for the HttpMethodConstraintElement
-                Constraint mconstraint = ConstraintSecurityHandler.createConstraint(name, methodConstraint);
+                Constraint methodConstraint = ConstraintSecurityHandler.createConstraint(name, methodConstraintElement);
                 ConstraintMapping mapping = new ConstraintMapping();
-                mapping.setConstraint(mconstraint);
+                mapping.setConstraint(methodConstraint);
                 mapping.setPathSpec(pathSpec);
-                if (methodConstraint.getMethodName() != null)
+                if (methodConstraintElement.getMethodName() != null)
                 {
-                    mapping.setMethod(methodConstraint.getMethodName());
+                    mapping.setMethod(methodConstraintElement.getMethodName());
                     //See spec 13.4.1.2 p127 - add an omission for every method name to the default constraint
-                    methodOmissions.add(methodConstraint.getMethodName());
+                    methodOmissions.add(methodConstraintElement.getMethodName());
                 }
                 mappings.add(mapping);
             }
         }
         //See spec 13.4.1.2 p127 - add an omission for every method name to the default constraint
-        if (methodOmissions.size() > 0)
-            defaultMapping.setMethodOmissions(methodOmissions.toArray(new String[methodOmissions.size()]));
-
+        //UNLESS the default constraint contains all default values. In that case, we won't add it. See Servlet Spec 3.1 pg 129
+        if (methodOmissions.size() > 0  && httpConstraintMapping != null)
+            httpConstraintMapping.setMethodOmissions(methodOmissions.toArray(new String[methodOmissions.size()]));
+     
         return mappings;
     }
     
     
-    /* ------------------------------------------------------------ */
-    /** Get the strict mode.
-     * @return true if the security handler is running in strict mode.
-     */
-    public boolean isStrict()
-    {
-        return _strict;
-    }
 
-    /* ------------------------------------------------------------ */
-    /** Set the strict mode of the security handler.
-     * <p>
-     * When in strict mode (the default), the full servlet specification
-     * will be implemented.
-     * If not in strict mode, some additional flexibility in configuration
-     * is allowed:<ul>
-     * <li>All users do not need to have a role defined in the deployment descriptor
-     * <li>The * role in a constraint applies to ANY role rather than all roles defined in
-     * the deployment descriptor.
-     * </ul>
-     *
-     * @param strict the strict to set
-     * @see #setRoles(Set)
-     * @see #setConstraintMappings(List, Set)
-     */
-    public void setStrict(boolean strict)
-    {
-        _strict = strict;
-    }
 
     /* ------------------------------------------------------------ */
     /**
      * @return Returns the constraintMappings.
      */
+    @Override
     public List<ConstraintMapping> getConstraintMappings()
     {
         return _constraintMappings;
     }
 
     /* ------------------------------------------------------------ */
+    @Override
     public Set<String> getRoles()
     {
         return _roles;
@@ -351,6 +332,7 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr
      *            The constraintMappings to set.
      * @param roles The known roles (or null to determine them from the mappings)
      */
+    @Override
     public void setConstraintMappings(List<ConstraintMapping> constraintMappings, Set<String> roles)
     {
         _constraintMappings.clear();
@@ -358,14 +340,14 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr
 
         if (roles==null)
         {
-            roles = new HashSet<String>();
+            roles = new HashSet<>();
             for (ConstraintMapping cm : constraintMappings)
             {
                 String[] cmr = cm.getConstraint().getRoles();
                 if (cmr!=null)
                 {
                     for (String r : cmr)
-                        if (!"*".equals(r))
+                        if (!ALL_METHODS.equals(r))
                             roles.add(r);
                 }
             }
@@ -386,7 +368,6 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr
      * Set the known roles.
      * This may be overridden by a subsequent call to {@link #setConstraintMappings(ConstraintMapping[])} or
      * {@link #setConstraintMappings(List, Set)}.
-     * @see #setStrict(boolean)
      * @param roles The known roles (or null to determine them from the mappings)
      */
     public void setRoles(Set<String> roles)
@@ -401,12 +382,21 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr
     /**
      * @see org.eclipse.jetty.security.ConstraintAware#addConstraintMapping(org.eclipse.jetty.security.ConstraintMapping)
      */
+    @Override
     public void addConstraintMapping(ConstraintMapping mapping)
     {
         _constraintMappings.add(mapping);
         if (mapping.getConstraint()!=null && mapping.getConstraint().getRoles()!=null)
+        {
+            //allow for lazy role naming: if a role is named in a security constraint, try and
+            //add it to the list of declared roles (ie as if it was declared with a security-role
             for (String role :  mapping.getConstraint().getRoles())
+            {
+                if ("*".equals(role) || "**".equals(role))
+                    continue;
                 addRole(role);
+            }
+        }
 
         if (isStarted())
         {
@@ -418,13 +408,15 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr
     /**
      * @see org.eclipse.jetty.security.ConstraintAware#addRole(java.lang.String)
      */
+    @Override
     public void addRole(String role)
     {
+        //add to list of declared roles
         boolean modified = _roles.add(role);
-        if (isStarted() && modified && _strict)
+        if (isStarted() && modified)
         {
             // Add the new role to currently defined any role role infos
-            for (Map<String,RoleInfo> map : (Collection<Map<String,RoleInfo>>)_constraintMap.values())
+            for (Map<String,RoleInfo> map : _constraintMap.values())
             {
                 for (RoleInfo info : map.values())
                 {
@@ -450,18 +442,20 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr
                 processConstraintMapping(mapping);
             }
         }
+        
+        //Servlet Spec 3.1 pg 147 sec 13.8.4.2 log paths for which there are uncovered http methods
+        checkPathsWithUncoveredHttpMethods();        
+       
         super.doStart();
     }
-    
+
     
     /* ------------------------------------------------------------ */
     @Override
     protected void doStop() throws Exception
     {
-        _constraintMap.clear();
-        _constraintMappings.clear();
-        _roles.clear();
         super.doStop();
+        _constraintMap.clear();
     }
     
     
@@ -474,24 +468,25 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr
      */
     protected void processConstraintMapping(ConstraintMapping mapping)
     {
-        Map<String, RoleInfo> mappings = (Map<String, RoleInfo>)_constraintMap.get(mapping.getPathSpec());
+        Map<String, RoleInfo> mappings = _constraintMap.get(mapping.getPathSpec());
         if (mappings == null)
         {
-            mappings = new StringMap();
+            mappings = new HashMap<String,RoleInfo>();
             _constraintMap.put(mapping.getPathSpec(),mappings);
         }
-        RoleInfo allMethodsRoleInfo = mappings.get(null);
+        RoleInfo allMethodsRoleInfo = mappings.get(ALL_METHODS);
         if (allMethodsRoleInfo != null && allMethodsRoleInfo.isForbidden())
             return;
-       
+
         if (mapping.getMethodOmissions() != null && mapping.getMethodOmissions().length > 0)
         {
-           
             processConstraintMappingWithMethodOmissions(mapping, mappings);
             return;
         }
 
-        String httpMethod = mapping.getMethod();       
+        String httpMethod = mapping.getMethod();
+        if (httpMethod==null)
+            httpMethod=ALL_METHODS;
         RoleInfo roleInfo = mappings.get(httpMethod);
         if (roleInfo == null)
         {
@@ -510,61 +505,42 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr
         
         if (roleInfo.isForbidden())
         {
-            if (httpMethod == null)
+            if (httpMethod.equals(ALL_METHODS))
             {
                 mappings.clear();
-                mappings.put(null,roleInfo);
-            }
-        }
-        else
-        {
-            //combine with any entry that covers all methods
-            if (httpMethod == null)
-            {
-                for (Map.Entry<String, RoleInfo> entry : mappings.entrySet())
-                {
-                    if (entry.getKey() != null)
-                    {
-                        RoleInfo specific = entry.getValue();
-                        specific.combine(roleInfo);
-                    }
-                }
+                mappings.put(ALL_METHODS,roleInfo);
             }
         }
     }
 
     /* ------------------------------------------------------------ */
     /** Constraints that name method omissions are dealt with differently.
-     * We create an entry in the mappings with key "method.omission". This entry
+     * We create an entry in the mappings with key "<method>.omission". This entry
      * is only ever combined with other omissions for the same method to produce a
      * consolidated RoleInfo. Then, when we wish to find the relevant constraints for
      *  a given Request (in prepareConstraintInfo()), we consult 3 types of entries in 
      * the mappings: an entry that names the method of the Request specifically, an
      * entry that names constraints that apply to all methods, entries of the form
-     * method.omission, where the method of the Request is not named in the omission.
+     * <method>.omission, where the method of the Request is not named in the omission.
      * @param mapping
      * @param mappings
      */
     protected void processConstraintMappingWithMethodOmissions (ConstraintMapping mapping, Map<String, RoleInfo> mappings)
     {
         String[] omissions = mapping.getMethodOmissions();
-
-        for (String omission:omissions)
+        StringBuilder sb = new StringBuilder();
+        for (int i=0; i<omissions.length; i++)
         {
-            //for each method omission, see if there is already a RoleInfo for it in mappings
-            RoleInfo ri = mappings.get(omission+OMISSION_SUFFIX);
-            if (ri == null)
-            {
-                //if not, make one
-                ri = new RoleInfo();
-                mappings.put(omission+OMISSION_SUFFIX, ri);
-            }
-
-            //initialize RoleInfo or combine from ConstraintMapping
-            configureRoleInfo(ri, mapping);
+            if (i > 0)
+                sb.append(".");
+            sb.append(omissions[i]);
         }
+        sb.append(OMISSION_SUFFIX);
+        RoleInfo ri = new RoleInfo();
+        mappings.put(sb.toString(), ri);
+        configureRoleInfo(ri, mapping);
     }
-    
+
     
     /* ------------------------------------------------------------ */
     /**
@@ -573,7 +549,7 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr
      * @param mapping
      */
     protected void configureRoleInfo (RoleInfo ri, ConstraintMapping mapping)
-    {
+    { 
         Constraint constraint = mapping.getConstraint();
         boolean forbidden = constraint.isForbidden();
         ri.setForbidden(forbidden);
@@ -582,7 +558,6 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr
         //which we need in order to do combining of omissions in prepareConstraintInfo
         UserDataConstraint userDataConstraint = UserDataConstraint.get(mapping.getConstraint().getDataConstraint());
         ri.setUserDataConstraint(userDataConstraint);
-        
 
         //if forbidden, no point setting up roles
         if (!ri.isForbidden())
@@ -590,35 +565,38 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr
             //add in the roles
             boolean checked = mapping.getConstraint().getAuthenticate();
             ri.setChecked(checked);
+
             if (ri.isChecked())
             {
                 if (mapping.getConstraint().isAnyRole())
                 {
-                    if (_strict)
-                    {
-                        // * means "all defined roles"
-                        for (String role : _roles)
-                            ri.addRole(role);
-                    }
-                    else
-                        // * means any role
-                        ri.setAnyRole(true);
+                    // * means matches any defined role
+                    for (String role : _roles)
+                        ri.addRole(role);
+                    ri.setAnyRole(true);
                 }
-                else
+                else if (mapping.getConstraint().isAnyAuth())
                 {
+                    //being authenticated is sufficient, not necessary to check roles
+                    ri.setAnyAuth(true);
+                }
+                else
+                {   
+                    //user must be in one of the named roles
                     String[] newRoles = mapping.getConstraint().getRoles();
-                    for (String role : newRoles)
-                    {
-                        if (_strict &&!_roles.contains(role))
-                            throw new IllegalArgumentException("Attempt to use undeclared role: " + role + ", known roles: " + _roles);
+                     for (String role : newRoles)
+                     {
+                         //check role has been defined
+                         if (!_roles.contains(role))
+                             throw new IllegalArgumentException("Attempt to use undeclared role: " + role + ", known roles: " + _roles);
                         ri.addRole(role);
-                    }
-                }
-            }
-        }
-    }
+                     }
+                 }
+             }
+         }
+     }
+
    
-    
     /* ------------------------------------------------------------ */
     /** 
      * Find constraints that apply to the given path.
@@ -626,15 +604,16 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr
      * represents a merged set of user data constraints, roles etc -:
      * <ol>
      * <li>A mapping of an exact method name </li>
-     * <li>A mapping will null key that matches every method name</li>
-     * <li>Mappings with keys of the form "method.omission" that indicates it will match every method name EXCEPT that given</li>
+     * <li>A mapping with key * that matches every method name</li>
+     * <li>Mappings with keys of the form "<method>.<method>.<method>.omission" that indicates it will match every method name EXCEPT those given</li>
      * </ol>
      * 
      * @see org.eclipse.jetty.security.SecurityHandler#prepareConstraintInfo(java.lang.String, org.eclipse.jetty.server.Request)
      */
-    protected Object prepareConstraintInfo(String pathInContext, Request request)
+    @Override
+    protected RoleInfo prepareConstraintInfo(String pathInContext, Request request)
     {
-        Map<String, RoleInfo> mappings = (Map<String, RoleInfo>)_constraintMap.match(pathInContext);
+        Map<String, RoleInfo> mappings = _constraintMap.match(pathInContext);
 
         if (mappings != null)
         {
@@ -646,7 +625,7 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr
                 List<RoleInfo> applicableConstraints = new ArrayList<RoleInfo>();
 
                 //Get info for constraint that matches all methods if it exists
-                RoleInfo all = mappings.get(null);
+                RoleInfo all = mappings.get(ALL_METHODS);
                 if (all != null)
                     applicableConstraints.add(all);
           
@@ -655,11 +634,16 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr
                 //(ie matches because target method is not omitted, hence considered covered by the constraint)
                 for (Entry<String, RoleInfo> entry: mappings.entrySet())
                 {
-                    if (entry.getKey() != null && entry.getKey().contains(OMISSION_SUFFIX) && !(httpMethod+OMISSION_SUFFIX).equals(entry.getKey()))
+                    if (entry.getKey() != null && entry.getKey().endsWith(OMISSION_SUFFIX) && ! entry.getKey().contains(httpMethod))
                         applicableConstraints.add(entry.getValue());
                 }
                 
-                if (applicableConstraints.size() == 1)
+                if (applicableConstraints.size() == 0 && isDenyUncoveredHttpMethods())
+                {
+                    roleInfo = new RoleInfo();
+                    roleInfo.setForbidden(true);
+                }
+                else if (applicableConstraints.size() == 1)
                     roleInfo = applicableConstraints.get(0);
                 else
                 {
@@ -671,75 +655,44 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr
                 }
 
             }
+           
             return roleInfo;
         }
+
         return null;
     }
-    
-    
-    /* ------------------------------------------------------------ */
-    /** 
-     * @see org.eclipse.jetty.security.SecurityHandler#checkUserDataPermissions(java.lang.String, org.eclipse.jetty.server.Request, org.eclipse.jetty.server.Response, java.lang.Object)
-     */
-    protected boolean checkUserDataPermissions(String pathInContext, Request request, Response response, Object constraintInfo) throws IOException
+
+    @Override
+    protected boolean checkUserDataPermissions(String pathInContext, Request request, Response response, RoleInfo roleInfo) throws IOException
     {
-        if (constraintInfo == null)
+        if (roleInfo == null)
             return true;
 
-        RoleInfo roleInfo = (RoleInfo)constraintInfo;
         if (roleInfo.isForbidden())
             return false;
 
-
         UserDataConstraint dataConstraint = roleInfo.getUserDataConstraint();
         if (dataConstraint == null || dataConstraint == UserDataConstraint.None)
-        {
             return true;
-        }
-        AbstractHttpConnection connection = AbstractHttpConnection.getCurrentConnection();
-        Connector connector = connection.getConnector();
 
-        if (dataConstraint == UserDataConstraint.Integral)
-        {
-            if (connector.isIntegral(request))
-                return true;
-            if (connector.getIntegralPort() > 0)
-            {
-                String scheme=connector.getIntegralScheme();
-                int port=connector.getIntegralPort();
-                String url = (HttpSchemes.HTTPS.equalsIgnoreCase(scheme) && port==443)
-                    ? "https://"+request.getServerName()+request.getRequestURI()
-                    : scheme + "://" + request.getServerName() + ":" + port + request.getRequestURI();
-                if (request.getQueryString() != null)
-                    url += "?" + request.getQueryString();
-                response.setContentLength(0);
-                response.sendRedirect(url);
-            }
-            else
-                response.sendError(Response.SC_FORBIDDEN,"!Integral");
+        HttpConfiguration httpConfig = HttpChannel.getCurrentHttpChannel().getHttpConfiguration();
 
-            request.setHandled(true);
-            return false;
-        }
-        else if (dataConstraint == UserDataConstraint.Confidential)
+        if (dataConstraint == UserDataConstraint.Confidential || dataConstraint == UserDataConstraint.Integral)
         {
-            if (connector.isConfidential(request))
+            if (request.isSecure())
                 return true;
 
-            if (connector.getConfidentialPort() > 0)
+            if (httpConfig.getSecurePort() > 0)
             {
-                String scheme=connector.getConfidentialScheme();
-                int port=connector.getConfidentialPort();
-                String url = (HttpSchemes.HTTPS.equalsIgnoreCase(scheme) && port==443)
-                    ? "https://"+request.getServerName()+request.getRequestURI()
-                    : scheme + "://" + request.getServerName() + ":" + port + request.getRequestURI();                    
-                if (request.getQueryString() != null)
-                    url += "?" + request.getQueryString();
+                String scheme = httpConfig.getSecureScheme();
+                int port = httpConfig.getSecurePort();
+                
+                String url = URIUtil.newURI(scheme, request.getServerName(), port,request.getRequestURI(),request.getQueryString());
                 response.setContentLength(0);
                 response.sendRedirect(url);
             }
             else
-                response.sendError(Response.SC_FORBIDDEN,"!Confidential");
+                response.sendError(HttpStatus.FORBIDDEN_403,"!Secure");
 
             request.setHandled(true);
             return false;
@@ -750,18 +703,11 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr
         }
 
     }
-    
-    /* ------------------------------------------------------------ */
-    /** 
-     * @see org.eclipse.jetty.security.SecurityHandler#isAuthMandatory(org.eclipse.jetty.server.Request, org.eclipse.jetty.server.Response, java.lang.Object)
-     */
+
+    @Override
     protected boolean isAuthMandatory(Request baseRequest, Response base_response, Object constraintInfo)
     {
-        if (constraintInfo == null)
-        {
-            return false;
-        }
-        return ((RoleInfo)constraintInfo).isChecked();
+        return constraintInfo != null && ((RoleInfo)constraintInfo).isChecked();
     }
     
     
@@ -784,14 +730,35 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr
             return true;
         }
 
-        if (roleInfo.isAnyRole() && request.getAuthType()!=null)
+        //handle ** role constraint
+        if (roleInfo.isAnyAuth() &&  request.getUserPrincipal() != null)
+        {
             return true;
-
+        }
+        
+        //check if user is any of the allowed roles
+        boolean isUserInRole = false;
         for (String role : roleInfo.getRoles())
         {
             if (userIdentity.isUserInRole(role, null))
-                return true;
+            {
+                isUserInRole = true;
+                break;
+            }
         }
+        
+        //handle * role constraint
+        if (roleInfo.isAnyRole() && request.getUserPrincipal() != null && isUserInRole)
+        {
+            return true;
+        }
+
+        //normal role check
+        if (isUserInRole)
+        {
+            return true;
+        }
+       
         return false;
     }
 
@@ -799,15 +766,146 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr
     @Override
     public void dump(Appendable out,String indent) throws IOException
     {
-        dumpThis(out);
-        dump(out,indent,
+        // TODO these should all be beans
+        dumpBeans(out,indent,
                 Collections.singleton(getLoginService()),
                 Collections.singleton(getIdentityService()),
                 Collections.singleton(getAuthenticator()),
                 Collections.singleton(_roles),
-                _constraintMap.entrySet(),
-                getBeans(),
-                TypeUtil.asList(getHandlers()));
+                _constraintMap.entrySet());
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** 
+     * @see org.eclipse.jetty.security.ConstraintAware#setDenyUncoveredHttpMethods(boolean)
+     */
+    @Override
+    public void setDenyUncoveredHttpMethods(boolean deny)
+    {
+        _denyUncoveredMethods = deny;
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean isDenyUncoveredHttpMethods()
+    {
+        return _denyUncoveredMethods;
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Servlet spec 3.1 pg. 147.
+     */
+    @Override
+    public boolean checkPathsWithUncoveredHttpMethods()
+    {
+        Set<String> paths = getPathsWithUncoveredHttpMethods();
+        if (paths != null && !paths.isEmpty())
+        {
+            for (String p:paths)
+                LOG.warn("{} has uncovered http methods for path: {}",ContextHandler.getCurrentContext(), p);
+            if (LOG.isDebugEnabled())
+                LOG.debug(new Throwable());
+            return true;
+        }
+        return false; 
     }
+    
 
+    /* ------------------------------------------------------------ */
+    /**
+     * Servlet spec 3.1 pg. 147.
+     * The container must check all the combined security constraint
+     * information and log any methods that are not protected and the
+     * urls at which they are not protected
+     * 
+     * @return list of paths for which there are uncovered methods
+     */
+    public Set<String> getPathsWithUncoveredHttpMethods ()
+    {
+        //if automatically denying uncovered methods, there are no uncovered methods
+        if (_denyUncoveredMethods)
+            return Collections.emptySet();
+        
+        Set<String> uncoveredPaths = new HashSet<String>();
+        
+        for (String path:_constraintMap.keySet())
+        {
+            Map<String, RoleInfo> methodMappings = _constraintMap.get(path);
+            //Each key is either:
+            // : an exact method name
+            // : * which means that the constraint applies to every method
+            // : a name of the form <method>.<method>.<method>.omission, which means it applies to every method EXCEPT those named
+            if (methodMappings.get(ALL_METHODS) != null)
+                continue; //can't be any uncovered methods for this url path
+          
+            boolean hasOmissions = omissionsExist(path, methodMappings);
+            
+            for (String method:methodMappings.keySet())
+            {
+                if (method.endsWith(OMISSION_SUFFIX))
+                {
+                    Set<String> omittedMethods = getOmittedMethods(method);
+                    for (String m:omittedMethods)
+                    {
+                        if (!methodMappings.containsKey(m))
+                            uncoveredPaths.add(path);
+                    }
+                }
+                else
+                {
+                    //an exact method name
+                    if (!hasOmissions)
+                        //a http-method does not have http-method-omission to cover the other method names
+                        uncoveredPaths.add(path);
+                }
+                
+            }
+        }
+        return uncoveredPaths;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Check if any http method omissions exist in the list of method
+     * to auth info mappings.
+     * 
+     * @param path
+     * @param methodMappings
+     * @return
+     */
+    protected boolean omissionsExist (String path, Map<String, RoleInfo> methodMappings)
+    {
+        if (methodMappings == null)
+            return false;
+        boolean hasOmissions = false;
+        for (String m:methodMappings.keySet())
+        {
+            if (m.endsWith(OMISSION_SUFFIX))
+                hasOmissions = true;
+        }
+        return hasOmissions;
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Given a string of the form <method>.<method>.omission
+     * split out the individual method names.
+     * 
+     * @param omission
+     * @return
+     */
+    protected Set<String> getOmittedMethods (String omission)
+    {
+        if (omission == null || !omission.endsWith(OMISSION_SUFFIX))
+            return Collections.emptySet();
+        
+        String[] strings = omission.split("\\.");
+        Set<String> methods = new HashSet<String>();
+        for (int i=0;i<strings.length-1;i++)
+            methods.add(strings[i]);
+        return methods;
+    }
 }
diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/CrossContextPsuedoSession.java b/jetty-security/src/main/java/org/eclipse/jetty/security/CrossContextPsuedoSession.java
index ce74715..711a8e8 100644
--- a/jetty-security/src/main/java/org/eclipse/jetty/security/CrossContextPsuedoSession.java
+++ b/jetty-security/src/main/java/org/eclipse/jetty/security/CrossContextPsuedoSession.java
@@ -23,6 +23,7 @@ import javax.servlet.http.HttpServletResponse;
 
 /**
  * @version $Rev: 4466 $ $Date: 2009-02-10 23:42:54 +0100 (Tue, 10 Feb 2009) $
+ * @deprecated
  */
 public interface CrossContextPsuedoSession<T>
 {
diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/DefaultIdentityService.java b/jetty-security/src/main/java/org/eclipse/jetty/security/DefaultIdentityService.java
index e8f9f37..eb3f3a9 100644
--- a/jetty-security/src/main/java/org/eclipse/jetty/security/DefaultIdentityService.java
+++ b/jetty-security/src/main/java/org/eclipse/jetty/security/DefaultIdentityService.java
@@ -31,7 +31,7 @@ import org.eclipse.jetty.server.UserIdentity;
  * This service handles only role reference maps passed in an
  * associated {@link org.eclipse.jetty.server.UserIdentity.Scope}.  If there are roles
  * refs present, then associate will wrap the UserIdentity with one
- * that uses the role references in the 
+ * that uses the role references in the
  * {@link org.eclipse.jetty.server.UserIdentity#isUserInRole(String, org.eclipse.jetty.server.UserIdentity.Scope)}
  * implementation. All other operations are effectively noops.
  *
@@ -42,10 +42,10 @@ public class DefaultIdentityService implements IdentityService
     public DefaultIdentityService()
     {
     }
-    
+
     /* ------------------------------------------------------------ */
-    /** 
-     * If there are roles refs present in the scope, then wrap the UserIdentity 
+    /**
+     * If there are roles refs present in the scope, then wrap the UserIdentity
      * with one that uses the role references in the {@link UserIdentity#isUserInRole(String, org.eclipse.jetty.server.UserIdentity.Scope)}
      */
     public Object associate(UserIdentity user)
@@ -54,7 +54,7 @@ public class DefaultIdentityService implements IdentityService
     }
 
     /* ------------------------------------------------------------ */
-    public void disassociate(Object previous) 
+    public void disassociate(Object previous)
     {
     }
 
@@ -86,5 +86,5 @@ public class DefaultIdentityService implements IdentityService
     {
         return new DefaultUserIdentity(subject,userPrincipal,roles);
     }
-    
+
 }
diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/DefaultUserIdentity.java b/jetty-security/src/main/java/org/eclipse/jetty/security/DefaultUserIdentity.java
index e6b8d71..46817ae 100644
--- a/jetty-security/src/main/java/org/eclipse/jetty/security/DefaultUserIdentity.java
+++ b/jetty-security/src/main/java/org/eclipse/jetty/security/DefaultUserIdentity.java
@@ -31,11 +31,11 @@ import org.eclipse.jetty.server.UserIdentity;
  *
  */
 public class DefaultUserIdentity implements UserIdentity
-{    
+{
     private final Subject _subject;
     private final Principal _userPrincipal;
     private final String[] _roles;
-    
+
     public DefaultUserIdentity(Subject subject, Principal userPrincipal, String[] roles)
     {
         _subject=subject;
@@ -55,11 +55,20 @@ public class DefaultUserIdentity implements UserIdentity
 
     public boolean isUserInRole(String role, Scope scope)
     {
+        //Servlet Spec 3.1, pg 125
+        if ("*".equals(role))
+            return false;
+        
+        String roleToTest = null;
         if (scope!=null && scope.getRoleRefMap()!=null)
-            role=scope.getRoleRefMap().get(role);
+            roleToTest=scope.getRoleRefMap().get(role);
 
+        //Servlet Spec 3.1, pg 125
+        if (roleToTest == null)
+            roleToTest = role;
+       
         for (String r :_roles)
-            if (r.equals(role))
+            if (r.equals(roleToTest))
                 return true;
         return false;
     }
diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/HashCrossContextPsuedoSession.java b/jetty-security/src/main/java/org/eclipse/jetty/security/HashCrossContextPsuedoSession.java
index 64ab174..61d8e33 100644
--- a/jetty-security/src/main/java/org/eclipse/jetty/security/HashCrossContextPsuedoSession.java
+++ b/jetty-security/src/main/java/org/eclipse/jetty/security/HashCrossContextPsuedoSession.java
@@ -29,6 +29,7 @@ import javax.servlet.http.HttpServletResponse;
 
 /**
  * @version $Rev: 4660 $ $Date: 2009-02-25 17:29:53 +0100 (Wed, 25 Feb 2009) $
+ * @deprecated
  */
 public class HashCrossContextPsuedoSession<T> implements CrossContextPsuedoSession<T>
 {
@@ -48,7 +49,11 @@ public class HashCrossContextPsuedoSession<T> implements CrossContextPsuedoSessi
 
     public T fetch(HttpServletRequest request)
     {
-        for (Cookie cookie : request.getCookies())
+        Cookie[] cookies = request.getCookies();
+        if (cookies == null)
+            return null;
+        
+        for (Cookie cookie : cookies)
         {
             if (_cookieName.equals(cookie.getName()))
             {
diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/HashLoginService.java b/jetty-security/src/main/java/org/eclipse/jetty/security/HashLoginService.java
index 7561e3b..ff6ed15 100644
--- a/jetty-security/src/main/java/org/eclipse/jetty/security/HashLoginService.java
+++ b/jetty-security/src/main/java/org/eclipse/jetty/security/HashLoginService.java
@@ -133,6 +133,7 @@ public class HashLoginService extends MappedLoginService implements UserListener
     /**
      * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart()
      */
+    @Override
     protected void doStart() throws Exception
     {
         super.doStart();
@@ -154,6 +155,7 @@ public class HashLoginService extends MappedLoginService implements UserListener
     /**
      * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStop()
      */
+    @Override
     protected void doStop() throws Exception
     {
         super.doStop();
@@ -163,6 +165,7 @@ public class HashLoginService extends MappedLoginService implements UserListener
     }
     
     /* ------------------------------------------------------------ */
+    @Override
     public void update(String userName, Credential credential, String[] roleArray)
     {
         if (LOG.isDebugEnabled())
@@ -171,6 +174,7 @@ public class HashLoginService extends MappedLoginService implements UserListener
     }
 
     /* ------------------------------------------------------------ */
+    @Override
     public void remove(String userName)
     {
         if (LOG.isDebugEnabled())
diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/IdentityService.java b/jetty-security/src/main/java/org/eclipse/jetty/security/IdentityService.java
index 2a7beb8..f93f095 100644
--- a/jetty-security/src/main/java/org/eclipse/jetty/security/IdentityService.java
+++ b/jetty-security/src/main/java/org/eclipse/jetty/security/IdentityService.java
@@ -22,32 +22,31 @@ import java.security.Principal;
 
 import javax.security.auth.Subject;
 
-import org.eclipse.jetty.server.Request;
 import org.eclipse.jetty.server.UserIdentity;
 
 /* ------------------------------------------------------------ */
 /**
  * Associates UserIdentities from with threads and UserIdentity.Contexts.
- * 
+ *
  */
 public interface IdentityService
 {
-    final static String[] NO_ROLES = new String[]{}; 
-    
+    final static String[] NO_ROLES = new String[]{};
+
     /* ------------------------------------------------------------ */
     /**
      * Associate a user identity with the current thread.
-     * This is called with as a thread enters the 
+     * This is called with as a thread enters the
      * {@link SecurityHandler#handle(String, Request, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)}
      * method and then again with a null argument as that call exits.
      * @param user The current user or null for no user to associated.
      * @return an object representing the previous associated state
      */
     Object associate(UserIdentity user);
-    
+
     /* ------------------------------------------------------------ */
-    /** 
-     * Disassociate the user identity from the current thread 
+    /**
+     * Disassociate the user identity from the current thread
      * and restore previous identity.
      * @param previous The opaque object returned from a call to {@link IdentityService#associate(UserIdentity)}
      */
@@ -61,7 +60,7 @@ public interface IdentityService
      * @return The previous runAsToken or null.
      */
     Object setRunAs(UserIdentity user, RunAsToken token);
-    
+
     /* ------------------------------------------------------------ */
     /**
      * Disassociate the current runAsToken from the thread
@@ -74,7 +73,7 @@ public interface IdentityService
     /**
      * Create a new UserIdentity for use with this identity service.
      * The UserIdentity should be immutable and able to be cached.
-     * 
+     *
      * @param subject Subject to include in UserIdentity
      * @param userPrincipal Principal to include in UserIdentity.  This will be returned from getUserPrincipal calls
      * @param roles set of roles to include in UserIdentity.
diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/JDBCLoginService.java b/jetty-security/src/main/java/org/eclipse/jetty/security/JDBCLoginService.java
index 20f5e79..d0cd990 100644
--- a/jetty-security/src/main/java/org/eclipse/jetty/security/JDBCLoginService.java
+++ b/jetty-security/src/main/java/org/eclipse/jetty/security/JDBCLoginService.java
@@ -19,6 +19,7 @@
 package org.eclipse.jetty.security;
 
 import java.io.IOException;
+import java.io.InputStream;
 import java.sql.Connection;
 import java.sql.DriverManager;
 import java.sql.PreparedStatement;
@@ -37,8 +38,7 @@ import org.eclipse.jetty.util.security.Credential;
 
 /* ------------------------------------------------------------ */
 /**
- * HashMapped User Realm with JDBC as data source. JDBCLoginService extends
- * HashULoginService and adds a method to fetch user information from database.
+ * HashMapped User Realm with JDBC as data source. 
  * The login() method checks the inherited Map for the user. If the user is not
  * found, it will fetch details from the database and populate the inherited
  * Map. It then calls the superclass login() method to perform the actual
@@ -61,19 +61,19 @@ public class JDBCLoginService extends MappedLoginService
 {
     private static final Logger LOG = Log.getLogger(JDBCLoginService.class);
 
-    private String _config;
-    private String _jdbcDriver;
-    private String _url;
-    private String _userName;
-    private String _password;
-    private String _userTableKey;
-    private String _userTablePasswordField;
-    private String _roleTableRoleField;
-    private int _cacheTime;
-    private long _lastHashPurge;
-    private Connection _con;
-    private String _userSql;
-    private String _roleSql;
+    protected String _config;
+    protected String _jdbcDriver;
+    protected String _url;
+    protected String _userName;
+    protected String _password;
+    protected String _userTableKey;
+    protected String _userTablePasswordField;
+    protected String _roleTableRoleField;
+    protected int _cacheTime;
+    protected long _lastHashPurge;
+    protected Connection _con;
+    protected String _userSql;
+    protected String _roleSql;
 
 
     /* ------------------------------------------------------------ */
@@ -116,8 +116,10 @@ public class JDBCLoginService extends MappedLoginService
     {
         Properties properties = new Properties();
         Resource resource = Resource.newResource(_config);
-        properties.load(resource.getInputStream());
-
+        try (InputStream in = resource.getInputStream())
+        {
+            properties.load(in);
+        }
         _jdbcDriver = properties.getProperty("jdbcdriver");
         _url = properties.getProperty("url");
         _userName = properties.getProperty("username");
@@ -238,25 +240,29 @@ public class JDBCLoginService extends MappedLoginService
             if (null == _con) 
                 throw new SQLException("Can't connect to database");
 
-            PreparedStatement stat = _con.prepareStatement(_userSql);
-            stat.setObject(1, username);
-            ResultSet rs = stat.executeQuery();
-
-            if (rs.next())
+            try (PreparedStatement stat1 = _con.prepareStatement(_userSql))
             {
-                int key = rs.getInt(_userTableKey);
-                String credentials = rs.getString(_userTablePasswordField);
-                stat.close();
-
-                stat = _con.prepareStatement(_roleSql);
-                stat.setInt(1, key);
-                rs = stat.executeQuery();
-                List<String> roles = new ArrayList<String>();
-                while (rs.next())
-                    roles.add(rs.getString(_roleTableRoleField));
+                stat1.setObject(1, username);
+                try (ResultSet rs1 = stat1.executeQuery())
+                {
+                    if (rs1.next())
+                    {
+                        int key = rs1.getInt(_userTableKey);
+                        String credentials = rs1.getString(_userTablePasswordField);
+                        List<String> roles = new ArrayList<String>();
 
-                stat.close();
-                return putUser(username, Credential.getCredential(credentials),roles.toArray(new String[roles.size()]));
+                        try (PreparedStatement stat2 = _con.prepareStatement(_roleSql))
+                        {
+                            stat2.setInt(1, key);
+                            try (ResultSet rs2 = stat2.executeQuery())
+                            {
+                                while (rs2.next())
+                                    roles.add(rs2.getString(_roleTableRoleField));
+                            }
+                        }
+                        return putUser(username, credentials, roles.toArray(new String[roles.size()]));
+                    }
+                }
             }
         }
         catch (SQLException e)
@@ -266,6 +272,13 @@ public class JDBCLoginService extends MappedLoginService
         }
         return null;
     }
+    
+    /* ------------------------------------------------------------ */
+    protected UserIdentity putUser (String username, String credentials, String[] roles)
+    {
+        return putUser(username, Credential.getCredential(credentials),roles);
+    }
+    
 
     /**
      * Close an existing connection
diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/MappedLoginService.java b/jetty-security/src/main/java/org/eclipse/jetty/security/MappedLoginService.java
index 62314c0..752fd26 100644
--- a/jetty-security/src/main/java/org/eclipse/jetty/security/MappedLoginService.java
+++ b/jetty-security/src/main/java/org/eclipse/jetty/security/MappedLoginService.java
@@ -40,7 +40,7 @@ import org.eclipse.jetty.util.security.Credential;
 /**
  * A login service that keeps UserIdentities in a concurrent map
  * either as the source or a cache of the users.
- * 
+ *
  */
 public abstract class MappedLoginService extends AbstractLifeCycle implements LoginService
 {
@@ -54,7 +54,7 @@ public abstract class MappedLoginService extends AbstractLifeCycle implements Lo
     protected MappedLoginService()
     {
     }
-    
+
     /* ------------------------------------------------------------ */
     /** Get the name.
      * @return the name
@@ -63,7 +63,7 @@ public abstract class MappedLoginService extends AbstractLifeCycle implements Lo
     {
         return _name;
     }
-    
+
     /* ------------------------------------------------------------ */
     /** Get the identityService.
      * @return the identityService
@@ -72,7 +72,7 @@ public abstract class MappedLoginService extends AbstractLifeCycle implements Lo
     {
         return _identityService;
     }
-    
+
     /* ------------------------------------------------------------ */
     /** Get the users.
      * @return the users
@@ -81,7 +81,7 @@ public abstract class MappedLoginService extends AbstractLifeCycle implements Lo
     {
         return _users;
     }
-    
+
     /* ------------------------------------------------------------ */
     /** Set the identityService.
      * @param identityService the identityService to set
@@ -136,17 +136,17 @@ public abstract class MappedLoginService extends AbstractLifeCycle implements Lo
 
     /* ------------------------------------------------------------ */
     public void logout(UserIdentity identity)
-    {   
+    {
         LOG.debug("logout {}",identity);
     }
-    
+
     /* ------------------------------------------------------------ */
     @Override
     public String toString()
     {
         return this.getClass().getSimpleName()+"["+_name+"]";
     }
-    
+
     /* ------------------------------------------------------------ */
     /** Put user into realm.
      * Called by implementations to put the user data loaded from
@@ -163,7 +163,7 @@ public abstract class MappedLoginService extends AbstractLifeCycle implements Lo
         else
         {
             Credential credential = (info instanceof Credential)?(Credential)info:Credential.getCredential(info.toString());
-            
+
             Principal userPrincipal = new KnownUser(userName,credential);
             Subject subject = new Subject();
             subject.getPrincipals().add(userPrincipal);
@@ -171,11 +171,11 @@ public abstract class MappedLoginService extends AbstractLifeCycle implements Lo
             subject.setReadOnly();
             identity=_identityService.newUserIdentity(subject,userPrincipal,IdentityService.NO_ROLES);
         }
-        
+
         _users.put(userName,identity);
         return identity;
     }
-    
+
     /* ------------------------------------------------------------ */
     /** Put user into realm.
      * @param userName The user to add
@@ -189,7 +189,7 @@ public abstract class MappedLoginService extends AbstractLifeCycle implements Lo
         Subject subject = new Subject();
         subject.getPrincipals().add(userPrincipal);
         subject.getPrivateCredentials().add(credential);
-        
+
         if (roles!=null)
             for (String role : roles)
                 subject.getPrincipals().add(new RolePrincipal(role));
@@ -198,13 +198,13 @@ public abstract class MappedLoginService extends AbstractLifeCycle implements Lo
         UserIdentity identity=_identityService.newUserIdentity(subject,userPrincipal,roles);
         _users.put(userName,identity);
         return identity;
-    } 
-    
+    }
+
     /* ------------------------------------------------------------ */
     public void removeUser(String username)
     {
         _users.remove(username);
-    }   
+    }
 
     /* ------------------------------------------------------------ */
     /**
@@ -212,11 +212,14 @@ public abstract class MappedLoginService extends AbstractLifeCycle implements Lo
      */
     public UserIdentity login(String username, Object credentials)
     {
-        UserIdentity user = _users.get(username);
+        if (username == null)
+            return null;
         
+        UserIdentity user = _users.get(username);
+
         if (user==null)
             user = loadUser(username);
-        
+
         if (user!=null)
         {
             UserPrincipal principal = (UserPrincipal)user.getUserPrincipal();
@@ -231,16 +234,16 @@ public abstract class MappedLoginService extends AbstractLifeCycle implements Lo
     {
         if (_users.containsKey(user.getUserPrincipal().getName()))
             return true;
-        
+
         if (loadUser(user.getUserPrincipal().getName())!=null)
             return true;
-                
+
         return false;
     }
 
     /* ------------------------------------------------------------ */
     protected abstract UserIdentity loadUser(String username);
-    
+
     /* ------------------------------------------------------------ */
     protected abstract void loadUsers() throws IOException;
 
@@ -253,7 +256,7 @@ public abstract class MappedLoginService extends AbstractLifeCycle implements Lo
         boolean authenticate(Object credentials);
         public boolean isAuthenticated();
     }
-    
+
     /* ------------------------------------------------------------ */
     /* ------------------------------------------------------------ */
     /* ------------------------------------------------------------ */
@@ -287,14 +290,14 @@ public abstract class MappedLoginService extends AbstractLifeCycle implements Lo
         {
             return "Anonymous";
         }
-        
+
         public boolean authenticate(Object credentials)
         {
             return false;
         }
-        
+
     }
-    
+
     /* ------------------------------------------------------------ */
     /* ------------------------------------------------------------ */
     /* ------------------------------------------------------------ */
@@ -303,7 +306,7 @@ public abstract class MappedLoginService extends AbstractLifeCycle implements Lo
         private static final long serialVersionUID = -6226920753748399662L;
         private final String _name;
         private final Credential _credential;
-        
+
         /* -------------------------------------------------------- */
         public KnownUser(String name,Credential credential)
         {
@@ -316,13 +319,13 @@ public abstract class MappedLoginService extends AbstractLifeCycle implements Lo
         {
             return _credential!=null && _credential.check(credentials);
         }
-        
+
         /* ------------------------------------------------------------ */
         public String getName()
         {
             return _name;
         }
-        
+
         /* -------------------------------------------------------- */
         public boolean isAuthenticated()
         {
diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/PropertyUserStore.java b/jetty-security/src/main/java/org/eclipse/jetty/security/PropertyUserStore.java
index 081b99b..b4937d3 100644
--- a/jetty-security/src/main/java/org/eclipse/jetty/security/PropertyUserStore.java
+++ b/jetty-security/src/main/java/org/eclipse/jetty/security/PropertyUserStore.java
@@ -46,16 +46,16 @@ import org.eclipse.jetty.util.security.Credential;
 
 /**
  * PropertyUserStore
- * 
+ *
  * This class monitors a property file of the format mentioned below and notifies registered listeners of the changes to the the given file.
- * 
+ *
  * <PRE>
  *  username: password [,rolename ...]
  * </PRE>
- * 
+ *
  * Passwords may be clear text, obfuscated or checksummed. The class com.eclipse.Util.Password should be used to generate obfuscated passwords or password
  * checksums.
- * 
+ *
  * If DIGEST Authentication is used, the password must be in a recoverable format, either plain text or OBF:.
  */
 public class PropertyUserStore extends AbstractLifeCycle
@@ -84,7 +84,7 @@ public class PropertyUserStore extends AbstractLifeCycle
     {
         _config = config;
     }
-    
+
     /* ------------------------------------------------------------ */
         public UserIdentity getUserIdentity(String userName)
         {
@@ -157,7 +157,7 @@ public class PropertyUserStore extends AbstractLifeCycle
                 }
                 known.add(username);
                 Credential credential = Credential.getCredential(credentials);
-                
+
                 Principal userPrincipal = new KnownUser(username,credential);
                 Subject subject = new Subject();
                 subject.getPrincipals().add(userPrincipal);
@@ -170,9 +170,9 @@ public class PropertyUserStore extends AbstractLifeCycle
                         subject.getPrincipals().add(new RolePrincipal(role));
                     }
                 }
-                
+
                 subject.setReadOnly();
-                
+
                 _knownUserIdentities.put(username,_identityService.newUserIdentity(subject,userPrincipal,roleArray));
                 notifyUpdate(username,credential,roleArray);
             }
@@ -216,8 +216,8 @@ public class PropertyUserStore extends AbstractLifeCycle
     /**
      * Depending on the value of the refresh interval, this method will either start up a scanner thread that will monitor the properties file for changes after
      * it has initially loaded it. Otherwise the users will be loaded and there will be no active monitoring thread so changes will not be detected.
-     * 
-     * 
+     *
+     *
      * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart()
      */
     protected void doStart() throws Exception
@@ -300,7 +300,7 @@ public class PropertyUserStore extends AbstractLifeCycle
 
     /**
      * Notifies the registered listeners of potential updates to a user
-     * 
+     *
      * @param username
      * @param credential
      * @param roleArray
@@ -318,7 +318,7 @@ public class PropertyUserStore extends AbstractLifeCycle
 
     /**
      * notifies the registered listeners that a user has been removed.
-     * 
+     *
      * @param username
      */
     private void notifyRemove(String username)
diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/RoleInfo.java b/jetty-security/src/main/java/org/eclipse/jetty/security/RoleInfo.java
index 8357290..1002103 100644
--- a/jetty-security/src/main/java/org/eclipse/jetty/security/RoleInfo.java
+++ b/jetty-security/src/main/java/org/eclipse/jetty/security/RoleInfo.java
@@ -22,6 +22,7 @@ import java.util.Set;
 import java.util.concurrent.CopyOnWriteArraySet;
 
 /**
+ * RoleInfo
  * 
  * Badly named class that holds the role and user data constraint info for a
  * path/http method combination, extracted and combined from security
@@ -31,11 +32,15 @@ import java.util.concurrent.CopyOnWriteArraySet;
  */
 public class RoleInfo
 {
+    private boolean _isAnyAuth;
     private boolean _isAnyRole;
     private boolean _checked;
     private boolean _forbidden;
     private UserDataConstraint _userDataConstraint;
 
+    /**
+     * List of permitted roles
+     */
     private final Set<String> _roles = new CopyOnWriteArraySet<String>();
 
     public RoleInfo()
@@ -55,6 +60,7 @@ public class RoleInfo
             _forbidden=false;
             _roles.clear();
             _isAnyRole=false;
+            _isAnyAuth=false;
         }
     }
 
@@ -71,6 +77,7 @@ public class RoleInfo
             _checked = true;
             _userDataConstraint = null;
             _isAnyRole=false;
+            _isAnyAuth=false;
             _roles.clear();
         }
     }
@@ -84,10 +91,19 @@ public class RoleInfo
     {
         this._isAnyRole=anyRole;
         if (anyRole)
-        {
             _checked = true;
-            _roles.clear();
-        }
+    }
+    
+    public boolean isAnyAuth ()
+    {
+        return _isAnyAuth;
+    }
+    
+    public void setAnyAuth(boolean anyAuth)
+    {
+        this._isAnyAuth=anyAuth;
+        if (anyAuth)
+            _checked = true;
     }
 
     public UserDataConstraint getUserDataConstraint()
@@ -100,6 +116,7 @@ public class RoleInfo
         if (userDataConstraint == null) throw new NullPointerException("Null UserDataConstraint");
         if (this._userDataConstraint == null)
         {
+           
             this._userDataConstraint = userDataConstraint;
         }
         else
@@ -126,6 +143,8 @@ public class RoleInfo
             setChecked(true);
         else if (other._isAnyRole)
             setAnyRole(true);
+        else if (other._isAnyAuth)
+            setAnyAuth(true);
         else if (!_isAnyRole)
         {
             for (String r : other._roles)
@@ -138,6 +157,6 @@ public class RoleInfo
     @Override
     public String toString()
     {
-        return "{RoleInfo"+(_forbidden?",F":"")+(_checked?",C":"")+(_isAnyRole?",*":_roles)+"}";
+        return "{RoleInfo"+(_forbidden?",F":"")+(_checked?",C":"")+(_isAnyRole?",*":_roles)+(_userDataConstraint!=null?","+_userDataConstraint:"")+"}";
     }
 }
diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/SecurityHandler.java b/jetty-security/src/main/java/org/eclipse/jetty/security/SecurityHandler.java
index 2b4e70b..8462f7e 100644
--- a/jetty-security/src/main/java/org/eclipse/jetty/security/SecurityHandler.java
+++ b/jetty-security/src/main/java/org/eclipse/jetty/security/SecurityHandler.java
@@ -20,9 +20,9 @@ package org.eclipse.jetty.security;
 
 import java.io.IOException;
 import java.security.Principal;
+import java.util.Collection;
 import java.util.Enumeration;
 import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
@@ -33,17 +33,16 @@ import javax.servlet.http.HttpSessionEvent;
 import javax.servlet.http.HttpSessionListener;
 
 import org.eclipse.jetty.security.authentication.DeferredAuthentication;
-import org.eclipse.jetty.server.AbstractHttpConnection;
 import org.eclipse.jetty.server.Authentication;
 import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.HttpChannel;
 import org.eclipse.jetty.server.Request;
 import org.eclipse.jetty.server.Response;
 import org.eclipse.jetty.server.UserIdentity;
 import org.eclipse.jetty.server.handler.ContextHandler;
 import org.eclipse.jetty.server.handler.ContextHandler.Context;
 import org.eclipse.jetty.server.handler.HandlerWrapper;
-import org.eclipse.jetty.server.session.AbstractSessionManager;
-import org.eclipse.jetty.util.component.LifeCycle;
+import org.eclipse.jetty.server.session.AbstractSession;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
 
@@ -55,11 +54,11 @@ import org.eclipse.jetty.util.log.Logger;
  * or will be create during {@link #start()} with a call to
  * either the default or set AuthenticatorFactory.
  * <p>
- * SecurityHandler has a set of initparameters that are used by the 
+ * SecurityHandler has a set of initparameters that are used by the
  * Authentication.Configuration. At startup, any context init parameters
- * that start with "org.eclipse.jetty.security." that do not have 
- * values in the SecurityHandler init parameters, are copied.  
- * 
+ * that start with "org.eclipse.jetty.security." that do not have
+ * values in the SecurityHandler init parameters, are copied.
+ *
  */
 public abstract class SecurityHandler extends HandlerWrapper implements Authenticator.AuthConfiguration
 {
@@ -73,19 +72,20 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti
     private String _authMethod;
     private final Map<String,String> _initParameters=new HashMap<String,String>();
     private LoginService _loginService;
-    private boolean _loginServiceShared;
     private IdentityService _identityService;
     private boolean _renewSession=true;
 
     /* ------------------------------------------------------------ */
     protected SecurityHandler()
     {
+        addBean(_authenticatorFactory);
     }
-    
+
     /* ------------------------------------------------------------ */
     /** Get the identityService.
      * @return the identityService
      */
+    @Override
     public IdentityService getIdentityService()
     {
         return _identityService;
@@ -99,6 +99,7 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti
     {
         if (isStarted())
             throw new IllegalStateException("Started");
+        updateBean(_identityService,identityService);
         _identityService = identityService;
     }
 
@@ -106,6 +107,7 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti
     /** Get the loginService.
      * @return the loginService
      */
+    @Override
     public LoginService getLoginService()
     {
         return _loginService;
@@ -119,8 +121,8 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti
     {
         if (isStarted())
             throw new IllegalStateException("Started");
+        updateBean(_loginService,loginService);
         _loginService = loginService;
-        _loginServiceShared=false;
     }
 
 
@@ -139,7 +141,10 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti
     {
         if (isStarted())
             throw new IllegalStateException("Started");
+        updateBean(_authenticator,authenticator);
         _authenticator = authenticator;
+        if (_authenticator!=null)
+            _authMethod=_authenticator.getAuthMethod();
     }
 
     /* ------------------------------------------------------------ */
@@ -160,6 +165,7 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti
     {
         if (isRunning())
             throw new IllegalStateException("running");
+        updateBean(_authenticatorFactory,authenticatorFactory);
         _authenticatorFactory = authenticatorFactory;
     }
 
@@ -167,6 +173,7 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti
     /**
      * @return the realmName
      */
+    @Override
     public String getRealmName()
     {
         return _realmName;
@@ -188,6 +195,7 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti
     /**
      * @return the authMethod
      */
+    @Override
     public String getAuthMethod()
     {
         return _authMethod;
@@ -204,7 +212,7 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti
             throw new IllegalStateException("running");
         _authMethod = authMethod;
     }
-    
+
     /* ------------------------------------------------------------ */
     /**
      * @return True if forwards to welcome files are authenticated
@@ -228,17 +236,19 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti
     }
 
     /* ------------------------------------------------------------ */
+    @Override
     public String getInitParameter(String key)
     {
         return _initParameters.get(key);
     }
-    
+
     /* ------------------------------------------------------------ */
+    @Override
     public Set<String> getInitParameterNames()
     {
         return _initParameters.keySet();
     }
-    
+
     /* ------------------------------------------------------------ */
     /** Set an initialization parameter.
      * @param key
@@ -252,32 +262,36 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti
             throw new IllegalStateException("running");
         return _initParameters.put(key,value);
     }
-    
+
     /* ------------------------------------------------------------ */
-    protected LoginService findLoginService()
+    protected LoginService findLoginService() throws Exception
     {
-        List<LoginService> list = getServer().getBeans(LoginService.class);
-        
+        Collection<LoginService> list = getServer().getBeans(LoginService.class);
+        LoginService service = null;
         String realm=getRealmName();
         if (realm!=null)
         {
-            for (LoginService service : list)
-                if (service.getName()!=null && service.getName().equals(realm))
-                    return service;
+            for (LoginService s : list)
+                if (s.getName()!=null && s.getName().equals(realm))
+                {
+                    service=s;
+                    break;
+                }
         }
         else if (list.size()==1)
-            return list.get(0);
-        return null;
+            service =  list.iterator().next();
+        
+        return service;
     }
-    
+
     /* ------------------------------------------------------------ */
     protected IdentityService findIdentityService()
     {
         return getServer().getBean(IdentityService.class);
     }
-    
+
     /* ------------------------------------------------------------ */
-    /** 
+    /**
      */
     @Override
     protected void doStart()
@@ -299,53 +313,61 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti
             //register a session listener to handle securing sessions when authentication is performed
             context.getContextHandler().addEventListener(new HttpSessionListener()
             {
-                
+                @Override
                 public void sessionDestroyed(HttpSessionEvent se)
                 {
-                   
                 }
-                
+
+                @Override
                 public void sessionCreated(HttpSessionEvent se)
-                {
+                {                    
                     //if current request is authenticated, then as we have just created the session, mark it as secure, as it has not yet been returned to a user
-                    AbstractHttpConnection connection = AbstractHttpConnection.getCurrentConnection();
-                    if (connection == null)
+                    HttpChannel<?> channel = HttpChannel.getCurrentHttpChannel();              
+                    
+                    if (channel == null)
                         return;
-                    Request request = connection.getRequest();
+                    Request request = channel.getRequest();
                     if (request == null)
                         return;
                     
                     if (request.isSecure())
                     {
-                        se.getSession().setAttribute(AbstractSessionManager.SESSION_KNOWN_ONLY_TO_AUTHENTICATED, Boolean.TRUE);
+                        se.getSession().setAttribute(AbstractSession.SESSION_KNOWN_ONLY_TO_AUTHENTICATED, Boolean.TRUE);
                     }
                 }
             });
         }
-        
+
         // complicated resolution of login and identity service to handle
         // many different ways these can be constructed and injected.
-        
+
         if (_loginService==null)
         {
-            _loginService=findLoginService();
+            setLoginService(findLoginService());
             if (_loginService!=null)
-                _loginServiceShared=true;
+                unmanage(_loginService);
         }
         
         if (_identityService==null)
         {
-           
             if (_loginService!=null)
-                _identityService=_loginService.getIdentityService();
+                setIdentityService(_loginService.getIdentityService());
 
             if (_identityService==null)
-                _identityService=findIdentityService();
-            
-            if (_identityService==null && _realmName!=null)
-                _identityService=new DefaultIdentityService();
+                setIdentityService(findIdentityService());
+
+            if (_identityService==null)
+            {
+                if (_realmName!=null)
+                { 
+                    setIdentityService(new DefaultIdentityService());
+                    manage(_identityService);
+                }
+            }
+            else
+                unmanage(_identityService);
         }
-        
+
         if (_loginService!=null)
         {
             if (_loginService.getIdentityService()==null)
@@ -354,46 +376,39 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti
                 throw new IllegalStateException("LoginService has different IdentityService to "+this);
         }
 
-        if (!_loginServiceShared && _loginService instanceof LifeCycle)
-            ((LifeCycle)_loginService).start();        
-        
-        if (_authenticator==null && _authenticatorFactory!=null && _identityService!=null)
-        {
-            _authenticator=_authenticatorFactory.getAuthenticator(getServer(),ContextHandler.getCurrentContext(),this, _identityService, _loginService);
-            if (_authenticator!=null)
-                _authMethod=_authenticator.getAuthMethod();
-        }
+        Authenticator.Factory authenticatorFactory = getAuthenticatorFactory();
+        if (_authenticator==null && authenticatorFactory!=null && _identityService!=null)
+            setAuthenticator(authenticatorFactory.getAuthenticator(getServer(),ContextHandler.getCurrentContext(),this, _identityService, _loginService));
 
-        if (_authenticator==null)
-        {
-            if (_realmName!=null)
-            {
-                LOG.warn("No ServerAuthentication for "+this);
-                throw new IllegalStateException("No ServerAuthentication");
-            }
-        }
-        else
-        {
+        if (_authenticator!=null)
             _authenticator.setConfiguration(this);
-            if (_authenticator instanceof LifeCycle)
-                ((LifeCycle)_authenticator).start();
+        else if (_realmName!=null)
+        {
+            LOG.warn("No Authenticator for "+this);
+            throw new IllegalStateException("No Authenticator");
         }
 
         super.doStart();
     }
 
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.server.handler.HandlerWrapper#doStop()
-     */
     @Override
+    /* ------------------------------------------------------------ */
     protected void doStop() throws Exception
     {
-        super.doStop();
+        //if we discovered the services (rather than had them explicitly configured), remove them.
+        if (!isManaged(_identityService))
+        {
+            removeBean(_identityService);
+            _identityService = null;   
+        }
         
-        if (!_loginServiceShared && _loginService instanceof LifeCycle)
-            ((LifeCycle)_loginService).stop();
+        if (!isManaged(_loginService))
+        {
+            removeBean(_loginService);
+            _loginService=null;
+        }
         
+        super.doStop();
     }
 
     /* ------------------------------------------------------------ */
@@ -405,7 +420,7 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti
             case ASYNC:
                 return true;
             case FORWARD:
-                if (_checkWelcomeFiles && request.getAttribute("org.eclipse.jetty.server.welcome") != null)
+                if (isCheckWelcomeFiles() && request.getAttribute("org.eclipse.jetty.server.welcome") != null)
                 {
                     request.removeAttribute("org.eclipse.jetty.server.welcome");
                     return true;
@@ -415,16 +430,17 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti
                 return false;
         }
     }
-    
+
     /* ------------------------------------------------------------ */
     /**
      * @see org.eclipse.jetty.security.Authenticator.AuthConfiguration#isSessionRenewedOnAuthentication()
      */
+    @Override
     public boolean isSessionRenewedOnAuthentication()
     {
         return _renewSession;
     }
-    
+
     /* ------------------------------------------------------------ */
     /** Set renew the session on Authentication.
      * <p>
@@ -435,7 +451,7 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti
     {
         _renewSession=renew;
     }
-    
+
     /* ------------------------------------------------------------ */
     /*
      * @see org.eclipse.jetty.server.Handler#handle(java.lang.String,
@@ -447,42 +463,46 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti
     {
         final Response base_response = baseRequest.getResponse();
         final Handler handler=getHandler();
-        
+
         if (handler==null)
             return;
 
         final Authenticator authenticator = _authenticator;
-        
+
         if (checkSecurity(baseRequest))
         {
-            Object constraintInfo = prepareConstraintInfo(pathInContext, baseRequest);
+            //See Servlet Spec 3.1 sec 13.6.3
+            if (authenticator != null)
+                authenticator.prepareRequest(baseRequest);
             
+            RoleInfo roleInfo = prepareConstraintInfo(pathInContext, baseRequest);
+
             // Check data constraints
-            if (!checkUserDataPermissions(pathInContext, baseRequest, base_response, constraintInfo))
+            if (!checkUserDataPermissions(pathInContext, baseRequest, base_response, roleInfo))
             {
                 if (!baseRequest.isHandled())
                 {
-                    response.sendError(Response.SC_FORBIDDEN);
+                    response.sendError(HttpServletResponse.SC_FORBIDDEN);
                     baseRequest.setHandled(true);
                 }
                 return;
             }
 
             // is Auth mandatory?
-            boolean isAuthMandatory = 
-                isAuthMandatory(baseRequest, base_response, constraintInfo);
+            boolean isAuthMandatory =
+                isAuthMandatory(baseRequest, base_response, roleInfo);
 
             if (isAuthMandatory && authenticator==null)
             {
-                LOG.warn("No authenticator for: "+constraintInfo);
+                LOG.warn("No authenticator for: "+roleInfo);
                 if (!baseRequest.isHandled())
                 {
-                    response.sendError(Response.SC_FORBIDDEN);
+                    response.sendError(HttpServletResponse.SC_FORBIDDEN);
                     baseRequest.setHandled(true);
                 }
                 return;
             }
-            
+
             // check authentication
             Object previousIdentity = null;
             try
@@ -510,15 +530,15 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti
 
                     if (isAuthMandatory)
                     {
-                        boolean authorized=checkWebResourcePermissions(pathInContext, baseRequest, base_response, constraintInfo, userAuth.getUserIdentity());
+                        boolean authorized=checkWebResourcePermissions(pathInContext, baseRequest, base_response, roleInfo, userAuth.getUserIdentity());
                         if (!authorized)
                         {
-                            response.sendError(Response.SC_FORBIDDEN, "!role");
+                            response.sendError(HttpServletResponse.SC_FORBIDDEN, "!role");
                             baseRequest.setHandled(true);
                             return;
                         }
                     }
-                         
+
                     handler.handle(pathInContext, baseRequest, request, response);
                     if (authenticator!=null)
                         authenticator.secureResponse(request, response, isAuthMandatory, userAuth);
@@ -563,7 +583,7 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti
             {
                 // jaspi 3.8.3 send HTTP 500 internal server error, with message
                 // from AuthException
-                response.sendError(Response.SC_INTERNAL_SERVER_ERROR, e.getMessage());
+                response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
             }
             finally
             {
@@ -582,9 +602,8 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti
         Context context = ContextHandler.getCurrentContext();
         if (context==null)
             return null;
-        
-        SecurityHandler security = context.getContextHandler().getChildHandlerByClass(SecurityHandler.class);
-        return security;
+
+        return context.getContextHandler().getChildHandlerByClass(SecurityHandler.class);
     }
 
     /* ------------------------------------------------------------ */
@@ -596,7 +615,7 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti
         {
             login_service.logout(user.getUserIdentity());
         }
-        
+
         IdentityService identity_service=getIdentityService();
         if (identity_service!=null)
         {
@@ -605,12 +624,12 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti
             identity_service.disassociate(previous);
         }
     }
-    
+
     /* ------------------------------------------------------------ */
-    protected abstract Object prepareConstraintInfo(String pathInContext, Request request);
+    protected abstract RoleInfo prepareConstraintInfo(String pathInContext, Request request);
 
     /* ------------------------------------------------------------ */
-    protected abstract boolean checkUserDataPermissions(String pathInContext, Request request, Response response, Object constraintInfo) throws IOException;
+    protected abstract boolean checkUserDataPermissions(String pathInContext, Request request, Response response, RoleInfo constraintInfo) throws IOException;
 
     /* ------------------------------------------------------------ */
     protected abstract boolean isAuthMandatory(Request baseRequest, Response base_response, Object constraintInfo);
@@ -619,11 +638,12 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti
     protected abstract boolean checkWebResourcePermissions(String pathInContext, Request request, Response response, Object constraintInfo,
                                                            UserIdentity userIdentity) throws IOException;
 
-    
+
     /* ------------------------------------------------------------ */
     /* ------------------------------------------------------------ */
     public class NotChecked implements Principal
     {
+        @Override
         public String getName()
         {
             return null;
@@ -641,11 +661,12 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti
         }
     }
 
-    
+
     /* ------------------------------------------------------------ */
     /* ------------------------------------------------------------ */
-    public static Principal __NO_USER = new Principal()
+    public static final Principal __NO_USER = new Principal()
     {
+        @Override
         public String getName()
         {
             return null;
@@ -657,7 +678,7 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti
             return "No User";
         }
     };
-    
+
     /* ------------------------------------------------------------ */
     /* ------------------------------------------------------------ */
     /**
@@ -668,8 +689,9 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti
      * FormAuthenticator to allow access to logon and error pages within an
      * authenticated URI tree.
      */
-    public static Principal __NOBODY = new Principal()
+    public static final Principal __NOBODY = new Principal()
     {
+        @Override
         public String getName()
         {
             return "Nobody";
diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/SpnegoLoginService.java b/jetty-security/src/main/java/org/eclipse/jetty/security/SpnegoLoginService.java
index a788eaf..cc59b47 100644
--- a/jetty-security/src/main/java/org/eclipse/jetty/security/SpnegoLoginService.java
+++ b/jetty-security/src/main/java/org/eclipse/jetty/security/SpnegoLoginService.java
@@ -23,11 +23,11 @@ import java.util.Properties;
 import javax.security.auth.Subject;
 
 import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.util.B64Code;
 import org.eclipse.jetty.util.component.AbstractLifeCycle;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
 import org.eclipse.jetty.util.resource.Resource;
-import org.eclipse.jetty.util.security.B64Code;
 import org.ietf.jgss.GSSContext;
 import org.ietf.jgss.GSSCredential;
 import org.ietf.jgss.GSSException;
@@ -42,25 +42,26 @@ public class SpnegoLoginService extends AbstractLifeCycle implements LoginServic
     protected IdentityService _identityService;// = new LdapIdentityService();
     protected String _name;
     private String _config;
-    
+
     private String _targetName;
 
     public SpnegoLoginService()
     {
-        
+
     }
-    
+
     public SpnegoLoginService( String name )
     {
         setName(name);
     }
-    
+
     public SpnegoLoginService( String name, String config )
     {
         setName(name);
         setConfig(config);
     }
-    
+
+    @Override
     public String getName()
     {
         return _name;
@@ -72,50 +73,51 @@ public class SpnegoLoginService extends AbstractLifeCycle implements LoginServic
         {
             throw new IllegalStateException("Running");
         }
-        
+
         _name = name;
     }
-    
+
     public String getConfig()
     {
         return _config;
     }
-    
+
     public void setConfig( String config )
     {
         if (isRunning())
         {
             throw new IllegalStateException("Running");
         }
-        
+
         _config = config;
     }
-    
-    
-    
+
+
+
     @Override
     protected void doStart() throws Exception
     {
         Properties properties = new Properties();
         Resource resource = Resource.newResource(_config);
         properties.load(resource.getInputStream());
-        
+
         _targetName = properties.getProperty("targetName");
-        
+
         LOG.debug("Target Name {}", _targetName);
-        
+
         super.doStart();
     }
 
     /**
      * username will be null since the credentials will contain all the relevant info
      */
+    @Override
     public UserIdentity login(String username, Object credentials)
     {
         String encodedAuthToken = (String)credentials;
-        
+
         byte[] authToken = B64Code.decode(encodedAuthToken);
-        
+
         GSSManager manager = GSSManager.getInstance();
         try
         {
@@ -138,7 +140,7 @@ public class SpnegoLoginService extends AbstractLifeCycle implements LoginServic
                 {
                     String clientName = gContext.getSrcName().toString();
                     String role = clientName.substring(clientName.indexOf('@') + 1);
-                    
+
                     LOG.debug("SpnegoUserRealm: established a security context");
                     LOG.debug("Client Principal is: " + gContext.getSrcName());
                     LOG.debug("Server Principal is: " + gContext.getTargName());
@@ -148,7 +150,7 @@ public class SpnegoLoginService extends AbstractLifeCycle implements LoginServic
 
                     Subject subject = new Subject();
                     subject.getPrincipals().add(user);
-                    
+
                     return _identityService.newUserIdentity(subject,user, new String[]{role});
                 }
             }
@@ -162,24 +164,28 @@ public class SpnegoLoginService extends AbstractLifeCycle implements LoginServic
         return null;
     }
 
+    @Override
     public boolean validate(UserIdentity user)
     {
         return false;
     }
 
+    @Override
     public IdentityService getIdentityService()
     {
         return _identityService;
     }
 
+    @Override
     public void setIdentityService(IdentityService service)
     {
         _identityService = service;
     }
 
-	public void logout(UserIdentity user) {
-		// TODO Auto-generated method stub
-		
-	}
+    @Override
+    public void logout(UserIdentity user) 
+    {
+        // TODO Auto-generated method stub
+    }
 
 }
diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/SpnegoUserIdentity.java b/jetty-security/src/main/java/org/eclipse/jetty/security/SpnegoUserIdentity.java
index b45da5a..8405c13 100644
--- a/jetty-security/src/main/java/org/eclipse/jetty/security/SpnegoUserIdentity.java
+++ b/jetty-security/src/main/java/org/eclipse/jetty/security/SpnegoUserIdentity.java
@@ -30,15 +30,15 @@ public class SpnegoUserIdentity implements UserIdentity
     private Subject _subject;
     private Principal _principal;
     private List<String> _roles;
-    
+
     public SpnegoUserIdentity( Subject subject, Principal principal, List<String> roles )
     {
         _subject = subject;
         _principal = principal;
         _roles = roles;
     }
-    
-    
+
+
     public Subject getSubject()
     {
         return _subject;
diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/SpnegoUserPrincipal.java b/jetty-security/src/main/java/org/eclipse/jetty/security/SpnegoUserPrincipal.java
index fefef21..4dafa65 100644
--- a/jetty-security/src/main/java/org/eclipse/jetty/security/SpnegoUserPrincipal.java
+++ b/jetty-security/src/main/java/org/eclipse/jetty/security/SpnegoUserPrincipal.java
@@ -20,26 +20,26 @@ package org.eclipse.jetty.security;
 
 import java.security.Principal;
 
-import org.eclipse.jetty.util.security.B64Code;
+import org.eclipse.jetty.util.B64Code;
 
 public class SpnegoUserPrincipal implements Principal
 {
     private final String _name;
     private byte[] _token;
     private String _encodedToken;
-    
+
     public SpnegoUserPrincipal( String name, String encodedToken )
     {
         _name = name;
         _encodedToken = encodedToken;
     }
-    
+
     public SpnegoUserPrincipal( String name, byte[] token )
     {
         _name = name;
         _token = token;
     }
-    
+
     public String getName()
     {
         return _name;
@@ -53,7 +53,7 @@ public class SpnegoUserPrincipal implements Principal
         }
         return _token;
     }
-    
+
     public String getEncodedToken()
     {
         if ( _encodedToken == null )
@@ -61,5 +61,5 @@ public class SpnegoUserPrincipal implements Principal
             _encodedToken = new String(B64Code.encode(_token,true));
         }
         return _encodedToken;
-    }   
+    }
 }
diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/UserAuthentication.java b/jetty-security/src/main/java/org/eclipse/jetty/security/UserAuthentication.java
index 38d33b3..60dee88 100644
--- a/jetty-security/src/main/java/org/eclipse/jetty/security/UserAuthentication.java
+++ b/jetty-security/src/main/java/org/eclipse/jetty/security/UserAuthentication.java
@@ -18,39 +18,20 @@
 
 package org.eclipse.jetty.security;
 
-import org.eclipse.jetty.server.Authentication;
 import org.eclipse.jetty.server.UserIdentity;
-import org.eclipse.jetty.server.UserIdentity.Scope;
 
 
 /**
  * @version $Rev: 4793 $ $Date: 2009-03-19 00:00:01 +0100 (Thu, 19 Mar 2009) $
  */
-public class UserAuthentication implements Authentication.User
+public class UserAuthentication extends AbstractUserAuthentication
 {
-    private final String _method;
-    private final UserIdentity _userIdentity;
-
+   
     public UserAuthentication(String method, UserIdentity userIdentity)
     {
-        _method = method;
-        _userIdentity = userIdentity;
-    }
-    
-    public String getAuthMethod()
-    {
-        return _method;
+        super(method, userIdentity);
     }
 
-    public UserIdentity getUserIdentity()
-    {
-        return _userIdentity;
-    }
-
-    public boolean isUserInRole(Scope scope, String role)
-    {
-        return _userIdentity.isUserInRole(role, scope);
-    }
     
     @Override
     public String toString()
diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/BasicAuthenticator.java b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/BasicAuthenticator.java
index 902651e..c05cd97 100644
--- a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/BasicAuthenticator.java
+++ b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/BasicAuthenticator.java
@@ -19,36 +19,37 @@
 package org.eclipse.jetty.security.authentication;
 
 import java.io.IOException;
+import java.nio.charset.StandardCharsets;
 
 import javax.servlet.ServletRequest;
 import javax.servlet.ServletResponse;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import org.eclipse.jetty.http.HttpHeaders;
+import org.eclipse.jetty.http.HttpHeader;
 import org.eclipse.jetty.security.ServerAuthException;
 import org.eclipse.jetty.security.UserAuthentication;
 import org.eclipse.jetty.server.Authentication;
 import org.eclipse.jetty.server.Authentication.User;
 import org.eclipse.jetty.server.UserIdentity;
 import org.eclipse.jetty.util.B64Code;
-import org.eclipse.jetty.util.StringUtil;
 import org.eclipse.jetty.util.security.Constraint;
 
 /**
  * @version $Rev: 4793 $ $Date: 2009-03-19 00:00:01 +0100 (Thu, 19 Mar 2009) $
  */
-public class BasicAuthenticator extends LoginAuthenticator 
-{   
+public class BasicAuthenticator extends LoginAuthenticator
+{
     /* ------------------------------------------------------------ */
     public BasicAuthenticator()
     {
     }
-    
+
     /* ------------------------------------------------------------ */
     /**
      * @see org.eclipse.jetty.security.Authenticator#getAuthMethod()
      */
+    @Override
     public String getAuthMethod()
     {
         return Constraint.__BASIC_AUTH;
@@ -60,11 +61,12 @@ public class BasicAuthenticator extends LoginAuthenticator
     /**
      * @see org.eclipse.jetty.security.Authenticator#validateRequest(javax.servlet.ServletRequest, javax.servlet.ServletResponse, boolean)
      */
+    @Override
     public Authentication validateRequest(ServletRequest req, ServletResponse res, boolean mandatory) throws ServerAuthException
     {
         HttpServletRequest request = (HttpServletRequest)req;
         HttpServletResponse response = (HttpServletResponse)res;
-        String credentials = request.getHeader(HttpHeaders.AUTHORIZATION);
+        String credentials = request.getHeader(HttpHeader.AUTHORIZATION.asString());
 
         try
         {
@@ -72,7 +74,7 @@ public class BasicAuthenticator extends LoginAuthenticator
                 return new DeferredAuthentication(this);
 
             if (credentials != null)
-            {                 
+            {
                 int space=credentials.indexOf(' ');
                 if (space>0)
                 {
@@ -80,7 +82,7 @@ public class BasicAuthenticator extends LoginAuthenticator
                     if ("basic".equalsIgnoreCase(method))
                     {
                         credentials = credentials.substring(space+1);
-                        credentials = B64Code.decode(credentials,StringUtil.__ISO_8859_1);
+                        credentials = B64Code.decode(credentials, StandardCharsets.ISO_8859_1);
                         int i = credentials.indexOf(':');
                         if (i>0)
                         {
@@ -99,8 +101,8 @@ public class BasicAuthenticator extends LoginAuthenticator
 
             if (DeferredAuthentication.isDeferred(response))
                 return Authentication.UNAUTHENTICATED;
-            
-            response.setHeader(HttpHeaders.WWW_AUTHENTICATE, "basic realm=\"" + _loginService.getName() + '"');
+
+            response.setHeader(HttpHeader.WWW_AUTHENTICATE.asString(), "basic realm=\"" + _loginService.getName() + '"');
             response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
             return Authentication.SEND_CONTINUE;
         }
@@ -110,6 +112,7 @@ public class BasicAuthenticator extends LoginAuthenticator
         }
     }
 
+    @Override
     public boolean secureResponse(ServletRequest req, ServletResponse res, boolean mandatory, User validatedUser) throws ServerAuthException
     {
         return true;
diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/ClientCertAuthenticator.java b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/ClientCertAuthenticator.java
index cae728f..a7e74f3 100644
--- a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/ClientCertAuthenticator.java
+++ b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/ClientCertAuthenticator.java
@@ -70,23 +70,25 @@ public class ClientCertAuthenticator extends LoginAuthenticator
     private boolean _enableOCSP = false;
     /** Location of OCSP Responder */
     private String _ocspResponderURL;
-    
+
     public ClientCertAuthenticator()
     {
         super();
     }
 
+    @Override
     public String getAuthMethod()
     {
         return Constraint.__CERT_AUTH;
     }
-    
+
     
 
     /**
      * @return Authentication for request
      * @throws ServerAuthException
      */
+    @Override
     public Authentication validateRequest(ServletRequest req, ServletResponse res, boolean mandatory) throws ServerAuthException
     {
         if (!mandatory)
@@ -101,7 +103,7 @@ public class ClientCertAuthenticator extends LoginAuthenticator
             // Need certificates.
             if (certs != null && certs.length > 0)
             {
-                
+
                 if (_validateCerts)
                 {
                     KeyStore trustStore = getKeyStore(null,
@@ -111,7 +113,7 @@ public class ClientCertAuthenticator extends LoginAuthenticator
                     CertificateValidator validator = new CertificateValidator(trustStore, crls);
                     validator.validate(certs);
                 }
-                
+
                 for (X509Certificate cert: certs)
                 {
                     if (cert==null)
@@ -136,7 +138,7 @@ public class ClientCertAuthenticator extends LoginAuthenticator
                 response.sendError(HttpServletResponse.SC_FORBIDDEN);
                 return Authentication.SEND_FAILURE;
             }
-            
+
             return Authentication.UNAUTHENTICATED;
         }
         catch (Exception e)
@@ -183,6 +185,7 @@ public class ClientCertAuthenticator extends LoginAuthenticator
         return CertificateUtils.loadCRL(crlPath);
     }
 
+    @Override
     public boolean secureResponse(ServletRequest req, ServletResponse res, boolean mandatory, User validatedUser) throws ServerAuthException
     {
         return true;
@@ -311,9 +314,9 @@ public class ClientCertAuthenticator extends LoginAuthenticator
     {
         _maxCertPathLength = maxCertPathLength;
     }
-    
+
     /* ------------------------------------------------------------ */
-    /** 
+    /**
      * @return true if CRL Distribution Points support is enabled
      */
     public boolean isEnableCRLDP()
@@ -331,7 +334,7 @@ public class ClientCertAuthenticator extends LoginAuthenticator
     }
 
     /* ------------------------------------------------------------ */
-    /** 
+    /**
      * @return true if On-Line Certificate Status Protocol support is enabled
      */
     public boolean isEnableOCSP()
@@ -349,7 +352,7 @@ public class ClientCertAuthenticator extends LoginAuthenticator
     }
 
     /* ------------------------------------------------------------ */
-    /** 
+    /**
      * @return Location of the OCSP Responder
      */
     public String getOcspResponderURL()
diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/DeferredAuthentication.java b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/DeferredAuthentication.java
index 9be8f56..53276ac 100644
--- a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/DeferredAuthentication.java
+++ b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/DeferredAuthentication.java
@@ -28,11 +28,10 @@ import java.util.Locale;
 import javax.servlet.ServletOutputStream;
 import javax.servlet.ServletRequest;
 import javax.servlet.ServletResponse;
+import javax.servlet.WriteListener;
 import javax.servlet.http.Cookie;
-import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import org.eclipse.jetty.security.Authenticator;
 import org.eclipse.jetty.security.IdentityService;
 import org.eclipse.jetty.security.LoginService;
 import org.eclipse.jetty.security.ServerAuthException;
@@ -61,12 +60,13 @@ public class DeferredAuthentication implements Authentication.Deferred
     /**
      * @see org.eclipse.jetty.server.Authentication.Deferred#authenticate(ServletRequest)
      */
+    @Override
     public Authentication authenticate(ServletRequest request)
     {
         try
         {
             Authentication authentication = _authenticator.validateRequest(request,__deferredResponse,true);
-            
+
             if (authentication!=null && (authentication instanceof Authentication.User) && !(authentication instanceof Authentication.ResponseSent))
             {
                 LoginService login_service= _authenticator.getLoginService();
@@ -85,11 +85,12 @@ public class DeferredAuthentication implements Authentication.Deferred
 
         return this;
     }
-    
+
     /* ------------------------------------------------------------ */
     /**
      * @see org.eclipse.jetty.server.Authentication.Deferred#authenticate(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
      */
+    @Override
     public Authentication authenticate(ServletRequest request, ServletResponse response)
     {
         try
@@ -111,10 +112,14 @@ public class DeferredAuthentication implements Authentication.Deferred
 
     /* ------------------------------------------------------------ */
     /**
-     * @see org.eclipse.jetty.server.Authentication.Deferred#login(java.lang.String, java.lang.String)
+     * @see org.eclipse.jetty.server.Authentication.Deferred#login(String, Object, ServletRequest)
      */
+    @Override
     public Authentication login(String username, Object password, ServletRequest request)
     {
+        if (username == null)
+            return null;
+        
         UserIdentity identity = _authenticator.login(username, password, request);
         if (identity != null)
         {
@@ -142,174 +147,213 @@ public class DeferredAuthentication implements Authentication.Deferred
     {
         return response==__deferredResponse;
     }
-    
+
     /* ------------------------------------------------------------ */
     /* ------------------------------------------------------------ */
     /* ------------------------------------------------------------ */
     final static HttpServletResponse __deferredResponse = new HttpServletResponse()
     {
+        @Override
         public void addCookie(Cookie cookie)
         {
         }
 
+        @Override
         public void addDateHeader(String name, long date)
         {
         }
 
+        @Override
         public void addHeader(String name, String value)
         {
         }
 
+        @Override
         public void addIntHeader(String name, int value)
         {
         }
 
+        @Override
         public boolean containsHeader(String name)
         {
             return false;
         }
 
+        @Override
         public String encodeRedirectURL(String url)
         {
             return null;
         }
 
+        @Override
         public String encodeRedirectUrl(String url)
         {
             return null;
         }
 
+        @Override
         public String encodeURL(String url)
         {
             return null;
         }
 
+        @Override
         public String encodeUrl(String url)
         {
             return null;
         }
 
+        @Override
         public void sendError(int sc) throws IOException
         {
         }
 
+        @Override
         public void sendError(int sc, String msg) throws IOException
         {
         }
 
+        @Override
         public void sendRedirect(String location) throws IOException
         {
         }
 
+        @Override
         public void setDateHeader(String name, long date)
         {
         }
 
+        @Override
         public void setHeader(String name, String value)
         {
         }
 
+        @Override
         public void setIntHeader(String name, int value)
         {
         }
 
+        @Override
         public void setStatus(int sc)
         {
         }
 
+        @Override
         public void setStatus(int sc, String sm)
         {
         }
 
+        @Override
         public void flushBuffer() throws IOException
         {
         }
 
+        @Override
         public int getBufferSize()
         {
             return 1024;
         }
 
+        @Override
         public String getCharacterEncoding()
         {
             return null;
         }
 
+        @Override
         public String getContentType()
         {
             return null;
         }
 
+        @Override
         public Locale getLocale()
         {
             return null;
         }
 
+        @Override
         public ServletOutputStream getOutputStream() throws IOException
         {
             return __nullOut;
         }
 
+        @Override
         public PrintWriter getWriter() throws IOException
         {
             return IO.getNullPrintWriter();
         }
 
+        @Override
         public boolean isCommitted()
         {
             return true;
         }
 
+        @Override
         public void reset()
         {
         }
 
+        @Override
         public void resetBuffer()
         {
         }
 
+        @Override
         public void setBufferSize(int size)
         {
         }
 
+        @Override
         public void setCharacterEncoding(String charset)
         {
         }
 
+        @Override
         public void setContentLength(int len)
         {
         }
+        
+        public void setContentLengthLong(long len)
+        {
+           
+        }
 
+        @Override
         public void setContentType(String type)
         {
         }
 
+        @Override
         public void setLocale(Locale loc)
         {
         }
 
-	public Collection<String> getHeaderNames()
-	{
-	    return Collections.emptyList();
-	}
+        @Override
+        public Collection<String> getHeaderNames()
+        {
+            return Collections.emptyList();
+        }
 
-	@Override
-	public String getHeader(String arg0)
-	{
-	    return null;
-	}
+        @Override
+        public String getHeader(String arg0)
+        {
+            return null;
+        }
 
-	@Override
-	public Collection<String> getHeaders(String arg0)
-	{
+        @Override
+        public Collection<String> getHeaders(String arg0)
+        {
             return Collections.emptyList();
-	}
+        }
+
+        @Override
+        public int getStatus()
+        {
+            return 0;
+        }
 
-	@Override
-	public int getStatus()
-	{
-	    return 0;
-	}
 
     };
 
@@ -318,18 +362,34 @@ public class DeferredAuthentication implements Authentication.Deferred
     /* ------------------------------------------------------------ */
     private static ServletOutputStream __nullOut = new ServletOutputStream()
     {
+        @Override
         public void write(int b) throws IOException
         {
         }
-
+        
+        @Override
         public void print(String s) throws IOException
         {
         }
-
+        
+        @Override
         public void println(String s) throws IOException
         {
         }
+
+     
+        @Override
+        public void setWriteListener(WriteListener writeListener)
+        {
+            
+        }
+
+        @Override
+        public boolean isReady()
+        {
+            return false;
+        }
     };
 
-    
+
 }
diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/DigestAuthenticator.java b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/DigestAuthenticator.java
index ea1bc0c..6b7f13b 100644
--- a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/DigestAuthenticator.java
+++ b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/DigestAuthenticator.java
@@ -19,6 +19,7 @@
 package org.eclipse.jetty.security.authentication;
 
 import java.io.IOException;
+import java.nio.charset.StandardCharsets;
 import java.security.MessageDigest;
 import java.security.SecureRandom;
 import java.util.BitSet;
@@ -26,15 +27,13 @@ import java.util.Queue;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.atomic.AtomicInteger;
 
 import javax.servlet.ServletRequest;
 import javax.servlet.ServletResponse;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import org.eclipse.jetty.http.HttpHeaders;
-import org.eclipse.jetty.security.SecurityHandler;
+import org.eclipse.jetty.http.HttpHeader;
 import org.eclipse.jetty.security.ServerAuthException;
 import org.eclipse.jetty.security.UserAuthentication;
 import org.eclipse.jetty.server.Authentication;
@@ -43,7 +42,6 @@ import org.eclipse.jetty.server.Request;
 import org.eclipse.jetty.server.UserIdentity;
 import org.eclipse.jetty.util.B64Code;
 import org.eclipse.jetty.util.QuotedStringTokenizer;
-import org.eclipse.jetty.util.StringUtil;
 import org.eclipse.jetty.util.TypeUtil;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
@@ -52,9 +50,10 @@ import org.eclipse.jetty.util.security.Credential;
 
 /**
  * @version $Rev: 4793 $ $Date: 2009-03-19 00:00:01 +0100 (Thu, 19 Mar 2009) $
- * 
- * The nonce max age in ms can be set with the {@link SecurityHandler#setInitParameter(String, String)} 
- * using the name "maxNonceAge"
+ *
+ * The nonce max age in ms can be set with the {@link SecurityHandler#setInitParameter(String, String)}
+ * using the name "maxNonceAge".  The nonce max count can be set with {@link SecurityHandler#setInitParameter(String, String)}
+ * using the name "maxNonceCount".  When the age or count is exceeded, the nonce is considered stale.
  */
 public class DigestAuthenticator extends LoginAuthenticator
 {
@@ -104,15 +103,19 @@ public class DigestAuthenticator extends LoginAuthenticator
     public void setConfiguration(AuthConfiguration configuration)
     {
         super.setConfiguration(configuration);
-        
+
         String mna=configuration.getInitParameter("maxNonceAge");
         if (mna!=null)
         {
             _maxNonceAgeMs=Long.valueOf(mna);
         }
+        String mnc=configuration.getInitParameter("maxNonceCount");
+        if (mnc!=null)
+        {
+            _maxNC=Integer.valueOf(mnc);
+        }
     }
 
-   
     /* ------------------------------------------------------------ */
     public int getMaxNonceCount()
     {
@@ -124,26 +127,28 @@ public class DigestAuthenticator extends LoginAuthenticator
     {
         _maxNC = maxNC;
     }
-    
+
     /* ------------------------------------------------------------ */
-    public void setMaxNonceAge(long maxNonceAgeInMillis)
+    public long getMaxNonceAge()
     {
-        _maxNonceAgeMs = maxNonceAgeInMillis;
+        return _maxNonceAgeMs;
     }
 
     /* ------------------------------------------------------------ */
-    public long getMaxNonceAge()
+    public synchronized void setMaxNonceAge(long maxNonceAgeInMillis)
     {
-        return _maxNonceAgeMs;
+        _maxNonceAgeMs = maxNonceAgeInMillis;
     }
 
     /* ------------------------------------------------------------ */
+    @Override
     public String getAuthMethod()
     {
         return Constraint.__DIGEST_AUTH;
     }
 
     /* ------------------------------------------------------------ */
+    @Override
     public boolean secureResponse(ServletRequest req, ServletResponse res, boolean mandatory, User validatedUser) throws ServerAuthException
     {
         return true;
@@ -152,6 +157,7 @@ public class DigestAuthenticator extends LoginAuthenticator
 
 
     /* ------------------------------------------------------------ */
+    @Override
     public Authentication validateRequest(ServletRequest req, ServletResponse res, boolean mandatory) throws ServerAuthException
     {
         if (!mandatory)
@@ -159,14 +165,14 @@ public class DigestAuthenticator extends LoginAuthenticator
 
         HttpServletRequest request = (HttpServletRequest)req;
         HttpServletResponse response = (HttpServletResponse)res;
-        String credentials = request.getHeader(HttpHeaders.AUTHORIZATION);
+        String credentials = request.getHeader(HttpHeader.AUTHORIZATION.asString());
 
         try
         {
             boolean stale = false;
             if (credentials != null)
             {
-                if (LOG.isDebugEnabled()) 
+                if (LOG.isDebugEnabled())
                     LOG.debug("Credentials: " + credentials);
                 QuotedStringTokenizer tokenizer = new QuotedStringTokenizer(credentials, "=, ", true, false);
                 final Digest digest = new Digest(request.getMethod());
@@ -208,7 +214,7 @@ public class DigestAuthenticator extends LoginAuthenticator
                                     digest.qop = tok;
                                 else if ("uri".equalsIgnoreCase(name))
                                     digest.uri = tok;
-                                else if ("response".equalsIgnoreCase(name)) 
+                                else if ("response".equalsIgnoreCase(name))
                                     digest.response = tok;
                                 name=null;
                             }
@@ -226,7 +232,7 @@ public class DigestAuthenticator extends LoginAuthenticator
                         return new UserAuthentication(getAuthMethod(),user);
                     }
                 }
-                else if (n == 0) 
+                else if (n == 0)
                     stale = true;
 
             }
@@ -234,9 +240,9 @@ public class DigestAuthenticator extends LoginAuthenticator
             if (!DeferredAuthentication.isDeferred(response))
             {
                 String domain = request.getContextPath();
-                if (domain == null) 
+                if (domain == null)
                     domain = "/";
-                response.setHeader(HttpHeaders.WWW_AUTHENTICATE, "Digest realm=\"" + _loginService.getName()
+                response.setHeader(HttpHeader.WWW_AUTHENTICATE.asString(), "Digest realm=\"" + _loginService.getName()
                         + "\", domain=\""
                         + domain
                         + "\", nonce=\""
@@ -261,7 +267,7 @@ public class DigestAuthenticator extends LoginAuthenticator
     public String newNonce(Request request)
     {
         Nonce nonce;
-        
+
         do
         {
             byte[] nounce = new byte[24];
@@ -271,7 +277,7 @@ public class DigestAuthenticator extends LoginAuthenticator
         }
         while (_nonceMap.putIfAbsent(nonce._nonce,nonce)!=null);
         _nonceQueue.add(nonce);
-               
+
         return nonce._nonce;
     }
 
@@ -292,19 +298,21 @@ public class DigestAuthenticator extends LoginAuthenticator
             _nonceMap.remove(nonce._nonce);
             nonce=_nonceQueue.peek();
         }
-        
+
         // Now check the requested nonce
         try
         {
             nonce = _nonceMap.get(digest.nonce);
             if (nonce==null)
                 return 0;
-         
+
             long count = Long.parseLong(digest.nc,16);
             if (count>=_maxNC)
                 return 0;
+            
             if (nonce.seen((int)count))
                 return -1;
+
             return 1;
         }
         catch (Exception e)
@@ -358,18 +366,18 @@ public class DigestAuthenticator extends LoginAuthenticator
                 else
                 {
                     // calc A1 digest
-                    md.update(username.getBytes(StringUtil.__ISO_8859_1));
+                    md.update(username.getBytes(StandardCharsets.ISO_8859_1));
                     md.update((byte) ':');
-                    md.update(realm.getBytes(StringUtil.__ISO_8859_1));
+                    md.update(realm.getBytes(StandardCharsets.ISO_8859_1));
                     md.update((byte) ':');
-                    md.update(password.getBytes(StringUtil.__ISO_8859_1));
+                    md.update(password.getBytes(StandardCharsets.ISO_8859_1));
                     ha1 = md.digest();
                 }
                 // calc A2 digest
                 md.reset();
-                md.update(method.getBytes(StringUtil.__ISO_8859_1));
+                md.update(method.getBytes(StandardCharsets.ISO_8859_1));
                 md.update((byte) ':');
-                md.update(uri.getBytes(StringUtil.__ISO_8859_1));
+                md.update(uri.getBytes(StandardCharsets.ISO_8859_1));
                 byte[] ha2 = md.digest();
 
                 // calc digest
@@ -379,17 +387,17 @@ public class DigestAuthenticator extends LoginAuthenticator
                 // request-digest = <"> < KD ( H(A1), unq(nonce-value) ":" H(A2)
                 // ) > <">
 
-                md.update(TypeUtil.toString(ha1, 16).getBytes(StringUtil.__ISO_8859_1));
+                md.update(TypeUtil.toString(ha1, 16).getBytes(StandardCharsets.ISO_8859_1));
                 md.update((byte) ':');
-                md.update(nonce.getBytes(StringUtil.__ISO_8859_1));
+                md.update(nonce.getBytes(StandardCharsets.ISO_8859_1));
                 md.update((byte) ':');
-                md.update(nc.getBytes(StringUtil.__ISO_8859_1));
+                md.update(nc.getBytes(StandardCharsets.ISO_8859_1));
                 md.update((byte) ':');
-                md.update(cnonce.getBytes(StringUtil.__ISO_8859_1));
+                md.update(cnonce.getBytes(StandardCharsets.ISO_8859_1));
                 md.update((byte) ':');
-                md.update(qop.getBytes(StringUtil.__ISO_8859_1));
+                md.update(qop.getBytes(StandardCharsets.ISO_8859_1));
                 md.update((byte) ':');
-                md.update(TypeUtil.toString(ha2, 16).getBytes(StringUtil.__ISO_8859_1));
+                md.update(TypeUtil.toString(ha2, 16).getBytes(StandardCharsets.ISO_8859_1));
                 byte[] digest = md.digest();
 
                 // check digest
diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/FormAuthenticator.java b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/FormAuthenticator.java
index 9c8a771..302f651 100644
--- a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/FormAuthenticator.java
+++ b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/FormAuthenticator.java
@@ -22,7 +22,6 @@ import java.io.IOException;
 import java.util.Collections;
 import java.util.Enumeration;
 import java.util.Locale;
-
 import javax.servlet.RequestDispatcher;
 import javax.servlet.ServletException;
 import javax.servlet.ServletRequest;
@@ -33,15 +32,18 @@ import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpServletResponseWrapper;
 import javax.servlet.http.HttpSession;
 
-import org.eclipse.jetty.http.HttpHeaders;
-import org.eclipse.jetty.http.HttpMethods;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpHeaderValue;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpVersion;
 import org.eclipse.jetty.http.MimeTypes;
 import org.eclipse.jetty.security.ServerAuthException;
 import org.eclipse.jetty.security.UserAuthentication;
-import org.eclipse.jetty.server.AbstractHttpConnection;
 import org.eclipse.jetty.server.Authentication;
 import org.eclipse.jetty.server.Authentication.User;
+import org.eclipse.jetty.server.HttpChannel;
 import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
 import org.eclipse.jetty.server.UserIdentity;
 import org.eclipse.jetty.util.MultiMap;
 import org.eclipse.jetty.util.StringUtil;
@@ -52,18 +54,18 @@ import org.eclipse.jetty.util.security.Constraint;
 
 /**
  * FORM Authenticator.
- * 
+ *
  * <p>This authenticator implements form authentication will use dispatchers to
  * the login page if the {@link #__FORM_DISPATCH} init parameter is set to true.
  * Otherwise it will redirect.</p>
- * 
+ *
  * <p>The form authenticator redirects unauthenticated requests to a log page
  * which should use a form to gather username/password from the user and send them
- * to the /j_security_check URI within the context.  FormAuthentication uses 
+ * to the /j_security_check URI within the context.  FormAuthentication uses
  * {@link SessionAuthentication} to wrap Authentication results so that they
  * are  associated with the session.</p>
- *  
- * 
+ *
+ *
  */
 public class FormAuthenticator extends LoginAuthenticator
 {
@@ -74,6 +76,7 @@ public class FormAuthenticator extends LoginAuthenticator
     public final static String __FORM_DISPATCH="org.eclipse.jetty.security.dispatch";
     public final static String __J_URI = "org.eclipse.jetty.security.form_URI";
     public final static String __J_POST = "org.eclipse.jetty.security.form_POST";
+    public final static String __J_METHOD = "org.eclipse.jetty.security.form_METHOD";
     public final static String __J_SECURITY_CHECK = "/j_security_check";
     public final static String __J_USERNAME = "j_username";
     public final static String __J_PASSWORD = "j_password";
@@ -99,7 +102,7 @@ public class FormAuthenticator extends LoginAuthenticator
             setErrorPage(error);
         _dispatch=dispatch;
     }
-    
+
     /* ------------------------------------------------------------ */
     /**
      * If true, uris that cause a redirect to a login page will always
@@ -112,14 +115,14 @@ public class FormAuthenticator extends LoginAuthenticator
     {
         _alwaysSaveUri = alwaysSave;
     }
-    
-    
+
+
     /* ------------------------------------------------------------ */
     public boolean getAlwaysSaveUri ()
     {
         return _alwaysSaveUri;
     }
-    
+
     /* ------------------------------------------------------------ */
     /**
      * @see org.eclipse.jetty.security.authentication.LoginAuthenticator#setConfiguration(org.eclipse.jetty.security.Authenticator.AuthConfiguration)
@@ -139,6 +142,7 @@ public class FormAuthenticator extends LoginAuthenticator
     }
 
     /* ------------------------------------------------------------ */
+    @Override
     public String getAuthMethod()
     {
         return Constraint.__FORM_AUTH;
@@ -154,7 +158,7 @@ public class FormAuthenticator extends LoginAuthenticator
         }
         _formLoginPage = path;
         _formLoginPath = path;
-        if (_formLoginPath.indexOf('?') > 0) 
+        if (_formLoginPath.indexOf('?') > 0)
             _formLoginPath = _formLoginPath.substring(0, _formLoginPath.indexOf('?'));
     }
 
@@ -176,7 +180,7 @@ public class FormAuthenticator extends LoginAuthenticator
             _formErrorPage = path;
             _formErrorPath = path;
 
-            if (_formErrorPath.indexOf('?') > 0) 
+            if (_formErrorPath.indexOf('?') > 0)
                 _formErrorPath = _formErrorPath.substring(0, _formErrorPath.indexOf('?'));
         }
     }
@@ -196,10 +200,50 @@ public class FormAuthenticator extends LoginAuthenticator
         }
         return user;
     }
+    
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public void prepareRequest(ServletRequest request)
+    {
+        //if this is a request resulting from a redirect after auth is complete
+        //(ie its from a redirect to the original request uri) then due to 
+        //browser handling of 302 redirects, the method may not be the same as
+        //that of the original request. Replace the method and original post
+        //params (if it was a post).
+        //
+        //See Servlet Spec 3.1 sec 13.6.3
+        HttpServletRequest httpRequest = (HttpServletRequest)request;
+        HttpSession session = httpRequest.getSession(false);
+        if (session == null || session.getAttribute(SessionAuthentication.__J_AUTHENTICATED) == null)
+            return; //not authenticated yet
+        
+        String juri = (String)session.getAttribute(__J_URI);
+        if (juri == null || juri.length() == 0)
+            return; //no original uri saved
+        
+        String method = (String)session.getAttribute(__J_METHOD);
+        if (method == null || method.length() == 0)
+            return; //didn't save original request method
+       
+        StringBuffer buf = httpRequest.getRequestURL();
+        if (httpRequest.getQueryString() != null)
+            buf.append("?").append(httpRequest.getQueryString());
+        
+        if (!juri.equals(buf.toString()))
+            return; //this request is not for the same url as the original
+        
+        //restore the original request's method on this request
+        if (LOG.isDebugEnabled()) LOG.debug("Restoring original method {} for {} with method {}", method, juri,httpRequest.getMethod());
+        Request base_request = HttpChannel.getCurrentHttpChannel().getRequest();
+        HttpMethod m = HttpMethod.fromString(method);
+        base_request.setMethod(m,m.asString());
+    }
 
     /* ------------------------------------------------------------ */
+    @Override
     public Authentication validateRequest(ServletRequest req, ServletResponse res, boolean mandatory) throws ServerAuthException
-    {   
+    {
         HttpServletRequest request = (HttpServletRequest)req;
         HttpServletResponse response = (HttpServletResponse)res;
         String uri = request.getRequestURI();
@@ -214,7 +258,7 @@ public class FormAuthenticator extends LoginAuthenticator
             return new DeferredAuthentication(this);
 
         HttpSession session = request.getSession(true);
-            
+
         try
         {
             // Handle a request for authentication.
@@ -222,13 +266,15 @@ public class FormAuthenticator extends LoginAuthenticator
             {
                 final String username = request.getParameter(__J_USERNAME);
                 final String password = request.getParameter(__J_PASSWORD);
-                
+
                 UserIdentity user = login(username, password, request);
+                LOG.debug("jsecuritycheck {} {}",username,user);
                 session = request.getSession(true);
                 if (user!=null)
                 {                    
                     // Redirect to original request
                     String nuri;
+                    FormAuthentication form_auth;
                     synchronized(session)
                     {
                         nuri = (String) session.getAttribute(__J_URI);
@@ -236,78 +282,92 @@ public class FormAuthenticator extends LoginAuthenticator
                         if (nuri == null || nuri.length() == 0)
                         {
                             nuri = request.getContextPath();
-                            if (nuri.length() == 0) 
+                            if (nuri.length() == 0)
                                 nuri = URIUtil.SLASH;
                         }
+                        form_auth = new FormAuthentication(getAuthMethod(),user);
                     }
-                    response.setContentLength(0);   
-                    response.sendRedirect(response.encodeRedirectURL(nuri));
-                    
-                    return new FormAuthentication(getAuthMethod(),user);
+                    LOG.debug("authenticated {}->{}",form_auth,nuri);
+
+                    response.setContentLength(0);
+                    Response base_response = HttpChannel.getCurrentHttpChannel().getResponse();
+                    Request base_request = HttpChannel.getCurrentHttpChannel().getRequest();
+                    int redirectCode = (base_request.getHttpVersion().getVersion() < HttpVersion.HTTP_1_1.getVersion() ? HttpServletResponse.SC_MOVED_TEMPORARILY : HttpServletResponse.SC_SEE_OTHER);
+                    base_response.sendRedirect(redirectCode, response.encodeRedirectURL(nuri));
+                    return form_auth;
                 }
-                
+
                 // not authenticated
-                if (LOG.isDebugEnabled()) 
+                if (LOG.isDebugEnabled())
                     LOG.debug("Form authentication FAILED for " + StringUtil.printable(username));
                 if (_formErrorPage == null)
                 {
-                    if (response != null) 
+                    LOG.debug("auth failed {}->403",username);
+                    if (response != null)
                         response.sendError(HttpServletResponse.SC_FORBIDDEN);
                 }
                 else if (_dispatch)
                 {
+                    LOG.debug("auth failed {}=={}",username,_formErrorPage);
                     RequestDispatcher dispatcher = request.getRequestDispatcher(_formErrorPage);
-                    response.setHeader(HttpHeaders.CACHE_CONTROL,"No-cache");
-                    response.setDateHeader(HttpHeaders.EXPIRES,1);
+                    response.setHeader(HttpHeader.CACHE_CONTROL.asString(),HttpHeaderValue.NO_CACHE.asString());
+                    response.setDateHeader(HttpHeader.EXPIRES.asString(),1);
                     dispatcher.forward(new FormRequest(request), new FormResponse(response));
                 }
                 else
                 {
-                    response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(request.getContextPath(),_formErrorPage)));
+                    LOG.debug("auth failed {}->{}",username,_formErrorPage);
+                    Response base_response = HttpChannel.getCurrentHttpChannel().getResponse();
+                    Request base_request = HttpChannel.getCurrentHttpChannel().getRequest();
+                    int redirectCode = (base_request.getHttpVersion().getVersion() < HttpVersion.HTTP_1_1.getVersion() ? HttpServletResponse.SC_MOVED_TEMPORARILY : HttpServletResponse.SC_SEE_OTHER);
+                    base_response.sendRedirect(redirectCode, response.encodeRedirectURL(URIUtil.addPaths(request.getContextPath(),_formErrorPage)));
                 }
-                
+
                 return Authentication.SEND_FAILURE;
             }
-            
+
             // Look for cached authentication
             Authentication authentication = (Authentication) session.getAttribute(SessionAuthentication.__J_AUTHENTICATED);
-            if (authentication != null) 
+            if (authentication != null)
             {
                 // Has authentication been revoked?
-                if (authentication instanceof Authentication.User && 
+                if (authentication instanceof Authentication.User &&
                     _loginService!=null &&
                     !_loginService.validate(((Authentication.User)authentication).getUserIdentity()))
                 {
-                
+                    LOG.debug("auth revoked {}",authentication);
                     session.removeAttribute(SessionAuthentication.__J_AUTHENTICATED);
                 }
                 else
                 {
-                    String j_uri=(String)session.getAttribute(__J_URI);
-                    if (j_uri!=null)
+                    synchronized (session)
                     {
-                        MultiMap<String> j_post = (MultiMap<String>)session.getAttribute(__J_POST);
-                        if (j_post!=null)
+                        String j_uri=(String)session.getAttribute(__J_URI);
+                        if (j_uri!=null)
                         {
+                            //check if the request is for the same url as the original and restore
+                            //params if it was a post
+                            LOG.debug("auth retry {}->{}",authentication,j_uri);
                             StringBuffer buf = request.getRequestURL();
                             if (request.getQueryString() != null)
                                 buf.append("?").append(request.getQueryString());
 
                             if (j_uri.equals(buf.toString()))
                             {
-                                // This is a retry of an original POST request
-                                // so restore method and parameters
-
-                                session.removeAttribute(__J_POST);                        
-                                Request base_request = (req instanceof Request)?(Request)req:AbstractHttpConnection.getCurrentConnection().getRequest();
-                                base_request.setMethod(HttpMethods.POST);
-                                base_request.setParameters(j_post);
+                                MultiMap<String> j_post = (MultiMap<String>)session.getAttribute(__J_POST);
+                                if (j_post!=null)
+                                {
+                                    LOG.debug("auth rePOST {}->{}",authentication,j_uri);
+                                    Request base_request = HttpChannel.getCurrentHttpChannel().getRequest();
+                                    base_request.setContentParameters(j_post);
+                                }
+                                session.removeAttribute(__J_URI);
+                                session.removeAttribute(__J_METHOD);
+                                session.removeAttribute(__J_POST);
                             }
                         }
-                        else
-                            session.removeAttribute(__J_URI);
-                            
                     }
+                    LOG.debug("auth {}",authentication);
                     return authentication;
                 }
             }
@@ -324,52 +384,53 @@ public class FormAuthenticator extends LoginAuthenticator
             {
                 // But only if it is not set already, or we save every uri that leads to a login form redirect
                 if (session.getAttribute(__J_URI)==null || _alwaysSaveUri)
-                {  
+                {
                     StringBuffer buf = request.getRequestURL();
                     if (request.getQueryString() != null)
                         buf.append("?").append(request.getQueryString());
                     session.setAttribute(__J_URI, buf.toString());
-                    
-                    if (MimeTypes.FORM_ENCODED.equalsIgnoreCase(req.getContentType()) && HttpMethods.POST.equals(request.getMethod()))
+                    session.setAttribute(__J_METHOD, request.getMethod());
+
+                    if (MimeTypes.Type.FORM_ENCODED.is(req.getContentType()) && HttpMethod.POST.is(request.getMethod()))
                     {
-                        Request base_request = (req instanceof Request)?(Request)req:AbstractHttpConnection.getCurrentConnection().getRequest();
-                        base_request.extractParameters();                        
-                        session.setAttribute(__J_POST, new MultiMap<String>(base_request.getParameters()));
+                        Request base_request = (req instanceof Request)?(Request)req:HttpChannel.getCurrentHttpChannel().getRequest();
+                        MultiMap<String> formParameters = new MultiMap<>();
+                        base_request.extractFormParameters(formParameters);
+                        session.setAttribute(__J_POST, formParameters);
                     }
                 }
             }
-            
+
             // send the the challenge
             if (_dispatch)
             {
+                LOG.debug("challenge {}=={}",session.getId(),_formLoginPage);
                 RequestDispatcher dispatcher = request.getRequestDispatcher(_formLoginPage);
-                response.setHeader(HttpHeaders.CACHE_CONTROL,"No-cache");
-                response.setDateHeader(HttpHeaders.EXPIRES,1);
+                response.setHeader(HttpHeader.CACHE_CONTROL.asString(),HttpHeaderValue.NO_CACHE.asString());
+                response.setDateHeader(HttpHeader.EXPIRES.asString(),1);
                 dispatcher.forward(new FormRequest(request), new FormResponse(response));
             }
             else
             {
-                response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(request.getContextPath(),_formLoginPage)));
+                LOG.debug("challenge {}->{}",session.getId(),_formLoginPage);
+                Response base_response = HttpChannel.getCurrentHttpChannel().getResponse();
+                Request base_request = HttpChannel.getCurrentHttpChannel().getRequest();
+                int redirectCode = (base_request.getHttpVersion().getVersion() < HttpVersion.HTTP_1_1.getVersion() ? HttpServletResponse.SC_MOVED_TEMPORARILY : HttpServletResponse.SC_SEE_OTHER);
+                base_response.sendRedirect(redirectCode, response.encodeRedirectURL(URIUtil.addPaths(request.getContextPath(),_formLoginPage)));
             }
             return Authentication.SEND_CONTINUE;
-            
-         
-        }
-        catch (IOException e)
-        {
-            throw new ServerAuthException(e);
         }
-        catch (ServletException e)
+        catch (IOException | ServletException e)
         {
             throw new ServerAuthException(e);
         }
     }
-    
+
     /* ------------------------------------------------------------ */
     public boolean isJSecurityCheck(String uri)
     {
         int jsc = uri.indexOf(__J_SECURITY_CHECK);
-        
+
         if (jsc<0)
             return false;
         int e=jsc+__J_SECURITY_CHECK.length();
@@ -378,14 +439,15 @@ public class FormAuthenticator extends LoginAuthenticator
         char c = uri.charAt(e);
         return c==';'||c=='#'||c=='/'||c=='?';
     }
-    
+
     /* ------------------------------------------------------------ */
     public boolean isLoginOrErrorPage(String pathInContext)
     {
         return pathInContext != null && (pathInContext.equals(_formErrorPath) || pathInContext.equals(_formLoginPath));
     }
-    
+
     /* ------------------------------------------------------------ */
+    @Override
     public boolean secureResponse(ServletRequest req, ServletResponse res, boolean mandatory, User validatedUser) throws ServerAuthException
     {
         return true;
@@ -407,7 +469,7 @@ public class FormAuthenticator extends LoginAuthenticator
                 return -1;
             return super.getDateHeader(name);
         }
-        
+
         @Override
         public String getHeader(String name)
         {
@@ -417,16 +479,16 @@ public class FormAuthenticator extends LoginAuthenticator
         }
 
         @Override
-        public Enumeration getHeaderNames()
+        public Enumeration<String> getHeaderNames()
         {
             return Collections.enumeration(Collections.list(super.getHeaderNames()));
         }
 
         @Override
-        public Enumeration getHeaders(String name)
+        public Enumeration<String> getHeaders(String name)
         {
             if (name.toLowerCase(Locale.ENGLISH).startsWith("if-"))
-                return Collections.enumeration(Collections.EMPTY_LIST);
+                return Collections.<String>enumeration(Collections.<String>emptyList());
             return super.getHeaders(name);
         }
     }
@@ -460,30 +522,30 @@ public class FormAuthenticator extends LoginAuthenticator
             if (notIgnored(name))
                 super.setDateHeader(name,date);
         }
-        
+
         @Override
         public void setHeader(String name, String value)
         {
             if (notIgnored(name))
                 super.setHeader(name,value);
         }
-        
+
         private boolean notIgnored(String name)
         {
-            if (HttpHeaders.CACHE_CONTROL.equalsIgnoreCase(name) ||
-                HttpHeaders.PRAGMA.equalsIgnoreCase(name) ||
-                HttpHeaders.ETAG.equalsIgnoreCase(name) ||
-                HttpHeaders.EXPIRES.equalsIgnoreCase(name) ||
-                HttpHeaders.LAST_MODIFIED.equalsIgnoreCase(name) ||
-                HttpHeaders.AGE.equalsIgnoreCase(name))
+            if (HttpHeader.CACHE_CONTROL.is(name) ||
+                HttpHeader.PRAGMA.is(name) ||
+                HttpHeader.ETAG.is(name) ||
+                HttpHeader.EXPIRES.is(name) ||
+                HttpHeader.LAST_MODIFIED.is(name) ||
+                HttpHeader.AGE.is(name))
                 return false;
             return true;
         }
     }
-    
+
     /* ------------------------------------------------------------ */
     /** This Authentication represents a just completed Form authentication.
-     * Subsequent requests from the same user are authenticated by the presents 
+     * Subsequent requests from the same user are authenticated by the presents
      * of a {@link SessionAuthentication} instance in their session.
      */
     public static class FormAuthentication extends UserAuthentication implements Authentication.ResponseSent
@@ -492,7 +554,7 @@ public class FormAuthenticator extends LoginAuthenticator
         {
             super(method,userIdentity);
         }
-        
+
         @Override
         public String toString()
         {
diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/LoginAuthenticator.java b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/LoginAuthenticator.java
index c18a630..0f2e097 100644
--- a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/LoginAuthenticator.java
+++ b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/LoginAuthenticator.java
@@ -26,20 +26,34 @@ import javax.servlet.http.HttpSession;
 import org.eclipse.jetty.security.Authenticator;
 import org.eclipse.jetty.security.IdentityService;
 import org.eclipse.jetty.security.LoginService;
-import org.eclipse.jetty.server.Authentication;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
 import org.eclipse.jetty.server.UserIdentity;
-import org.eclipse.jetty.server.session.AbstractSessionManager;
+import org.eclipse.jetty.server.session.AbstractSession;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
 
 public abstract class LoginAuthenticator implements Authenticator
 {
+    private static final Logger LOG = Log.getLogger(LoginAuthenticator.class);
+
     protected LoginService _loginService;
     protected IdentityService _identityService;
     private boolean _renewSession;
-
+    
+    
+    /* ------------------------------------------------------------ */
     protected LoginAuthenticator()
     {
     }
 
+    /* ------------------------------------------------------------ */
+    @Override
+    public void prepareRequest(ServletRequest request)
+    {
+        //empty implementation as the default
+    }
+
 
     /* ------------------------------------------------------------ */
     public UserIdentity login(String username, Object password, ServletRequest request)
@@ -47,13 +61,14 @@ public abstract class LoginAuthenticator implements Authenticator
         UserIdentity user = _loginService.login(username,password);
         if (user!=null)
         {
-            renewSession((HttpServletRequest)request, null);
+            renewSession((HttpServletRequest)request, (request instanceof Request? ((Request)request).getResponse() : null));
             return user;
         }
         return null;
     }
 
-
+    /* ------------------------------------------------------------ */
+    @Override
     public void setConfiguration(AuthConfiguration configuration)
     {
         _loginService=configuration.getLoginService();
@@ -65,11 +80,15 @@ public abstract class LoginAuthenticator implements Authenticator
         _renewSession=configuration.isSessionRenewedOnAuthentication();
     }
     
+    
+    /* ------------------------------------------------------------ */
     public LoginService getLoginService()
     {
         return _loginService;
     }
     
+    
+    /* ------------------------------------------------------------ */
     /** Change the session id.
      * The session is changed to a new instance with a new ID if and only if:<ul>
      * <li>A session exists.
@@ -83,14 +102,30 @@ public abstract class LoginAuthenticator implements Authenticator
     protected HttpSession renewSession(HttpServletRequest request, HttpServletResponse response)
     {
         HttpSession httpSession = request.getSession(false);
-       
-        //if we should renew sessions, and there is an existing session that may have been seen by non-authenticated users
-        //(indicated by SESSION_SECURED not being set on the session) then we should change id
-        if (_renewSession && httpSession!=null && httpSession.getAttribute(AbstractSessionManager.SESSION_KNOWN_ONLY_TO_AUTHENTICATED)!=Boolean.TRUE)
+
+        if (_renewSession && httpSession!=null)
         {
-            synchronized (this)
+            synchronized (httpSession)
             {
-                httpSession = AbstractSessionManager.renewSession(request, httpSession,true);
+                //if we should renew sessions, and there is an existing session that may have been seen by non-authenticated users
+                //(indicated by SESSION_SECURED not being set on the session) then we should change id
+                if (httpSession.getAttribute(AbstractSession.SESSION_KNOWN_ONLY_TO_AUTHENTICATED)!=Boolean.TRUE)
+                {
+                    if (httpSession instanceof AbstractSession)
+                    {
+                        AbstractSession abstractSession = (AbstractSession)httpSession;
+                        String oldId = abstractSession.getId();
+                        abstractSession.renewId(request);
+                        abstractSession.setAttribute(AbstractSession.SESSION_KNOWN_ONLY_TO_AUTHENTICATED, Boolean.TRUE);
+                        if (abstractSession.isIdChanged() && response != null && (response instanceof Response))
+                            ((Response)response).addCookie(abstractSession.getSessionManager().getSessionCookie(abstractSession, request.getContextPath(), request.isSecure()));
+                        LOG.debug("renew {}->{}",oldId,abstractSession.getId());
+                    }
+                    else
+                        LOG.warn("Unable to renew session "+httpSession);
+                    
+                    return httpSession;
+                }
             }
         }
         return httpSession;
diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/LoginCallback.java b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/LoginCallback.java
index 3093518..3ceaa4b 100644
--- a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/LoginCallback.java
+++ b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/LoginCallback.java
@@ -26,7 +26,7 @@ import javax.security.auth.Subject;
 /**
  * This is similar to the jaspi PasswordValidationCallback but includes user
  * principal and group info as well.
- * 
+ *
  * @version $Rev: 4792 $ $Date: 2009-03-18 22:55:52 +0100 (Wed, 18 Mar 2009) $
  */
 public interface LoginCallback
@@ -36,7 +36,7 @@ public interface LoginCallback
     public String getUserName();
 
     public Object getCredential();
- 
+
     public boolean isSuccess();
 
     public void setSuccess(boolean success);
@@ -46,10 +46,10 @@ public interface LoginCallback
     public void setUserPrincipal(Principal userPrincipal);
 
     public String[] getRoles();
-    
+
     public void setRoles(String[] roles);
-  
+
     public void clearPassword();
-   
+
 
 }
diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/LoginCallbackImpl.java b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/LoginCallbackImpl.java
index 91375f4..ad9133c 100644
--- a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/LoginCallbackImpl.java
+++ b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/LoginCallbackImpl.java
@@ -27,7 +27,7 @@ import org.eclipse.jetty.security.IdentityService;
 /**
  * This is similar to the jaspi PasswordValidationCallback but includes user
  * principal and group info as well.
- * 
+ *
  * @version $Rev: 4793 $ $Date: 2009-03-19 00:00:01 +0100 (Thu, 19 Mar 2009) $
  */
 public class LoginCallbackImpl implements LoginCallback
diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/SessionAuthentication.java b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/SessionAuthentication.java
index 8a97f6f..ddc1732 100644
--- a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/SessionAuthentication.java
+++ b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/SessionAuthentication.java
@@ -29,71 +29,52 @@ import javax.servlet.http.HttpSessionBindingEvent;
 import javax.servlet.http.HttpSessionBindingListener;
 import javax.servlet.http.HttpSessionEvent;
 
+import org.eclipse.jetty.security.AbstractUserAuthentication;
 import org.eclipse.jetty.security.LoginService;
 import org.eclipse.jetty.security.SecurityHandler;
-import org.eclipse.jetty.server.Authentication;
 import org.eclipse.jetty.server.UserIdentity;
-import org.eclipse.jetty.server.UserIdentity.Scope;
-import org.eclipse.jetty.server.session.AbstractSessionManager;
+import org.eclipse.jetty.server.session.AbstractSession;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
 
-public class SessionAuthentication implements Authentication.User, Serializable, HttpSessionActivationListener, HttpSessionBindingListener
+public class SessionAuthentication extends AbstractUserAuthentication implements Serializable, HttpSessionActivationListener, HttpSessionBindingListener
 {
     private static final Logger LOG = Log.getLogger(SessionAuthentication.class);
 
     private static final long serialVersionUID = -4643200685888258706L;
 
-    
+
 
     public final static String __J_AUTHENTICATED="org.eclipse.jetty.security.UserIdentity";
 
-    private final String _method;
     private final String _name;
     private final Object _credentials;
-    
-    private transient UserIdentity _userIdentity;
     private transient HttpSession _session;
-    
+
     public SessionAuthentication(String method, UserIdentity userIdentity, Object credentials)
     {
-        _method = method;
-        _userIdentity = userIdentity;
-        _name=_userIdentity.getUserPrincipal().getName();
+        super(method, userIdentity);
+        _name=userIdentity.getUserPrincipal().getName();
         _credentials=credentials;
     }
 
-    public String getAuthMethod()
-    {
-        return _method;
-    }
-
-    public UserIdentity getUserIdentity()
-    {
-        return _userIdentity;
-    }
-
-    public boolean isUserInRole(Scope scope, String role)
-    {
-        return _userIdentity.isUserInRole(role, scope);
-    }
 
-    private void readObject(ObjectInputStream stream) 
-        throws IOException, ClassNotFoundException 
+    private void readObject(ObjectInputStream stream)
+        throws IOException, ClassNotFoundException
     {
         stream.defaultReadObject();
-        
+
         SecurityHandler security=SecurityHandler.getCurrentSecurityHandler();
         if (security==null)
             throw new IllegalStateException("!SecurityHandler");
         LoginService login_service=security.getLoginService();
         if (login_service==null)
             throw new IllegalStateException("!LoginService");
-        
+
         _userIdentity=login_service.login(_name,_credentials);
         LOG.debug("Deserialized and relogged in {}",this);
     }
-    
+
     public void logout()
     {
         if (_session!=null && _session.getAttribute(__J_AUTHENTICATED)!=null)
@@ -101,27 +82,29 @@ public class SessionAuthentication implements Authentication.User, Serializable,
 
         doLogout();
     }
-    
+
     private void doLogout()
     {
         SecurityHandler security=SecurityHandler.getCurrentSecurityHandler();
         if (security!=null)
             security.logout(this);
         if (_session!=null)
-            _session.removeAttribute(AbstractSessionManager.SESSION_KNOWN_ONLY_TO_AUTHENTICATED);
+            _session.removeAttribute(AbstractSession.SESSION_KNOWN_ONLY_TO_AUTHENTICATED);
     }
-        
+
     @Override
     public String toString()
     {
-        return "Session"+super.toString();
+        return String.format("%s@%x{%s,%s}",this.getClass().getSimpleName(),hashCode(),_session==null?"-":_session.getId(),_userIdentity);
     }
 
+    @Override
     public void sessionWillPassivate(HttpSessionEvent se)
     {
        
     }
 
+    @Override
     public void sessionDidActivate(HttpSessionEvent se)
     {
         if (_session==null)
@@ -130,6 +113,7 @@ public class SessionAuthentication implements Authentication.User, Serializable,
         }
     }
 
+    @Override
     public void valueBound(HttpSessionBindingEvent event)
     {
         if (_session==null)
@@ -138,9 +122,10 @@ public class SessionAuthentication implements Authentication.User, Serializable,
         }
     }
 
+    @Override
     public void valueUnbound(HttpSessionBindingEvent event)
     {
         doLogout();
     }
-    
+
 }
diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/SpnegoAuthenticator.java b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/SpnegoAuthenticator.java
index 15f7825..3f0fbe6 100644
--- a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/SpnegoAuthenticator.java
+++ b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/SpnegoAuthenticator.java
@@ -25,7 +25,7 @@ import javax.servlet.ServletResponse;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import org.eclipse.jetty.http.HttpHeaders;
+import org.eclipse.jetty.http.HttpHeader;
 import org.eclipse.jetty.security.ServerAuthException;
 import org.eclipse.jetty.security.UserAuthentication;
 import org.eclipse.jetty.server.Authentication;
@@ -38,77 +38,76 @@ import org.eclipse.jetty.util.security.Constraint;
 public class SpnegoAuthenticator extends LoginAuthenticator
 {
     private static final Logger LOG = Log.getLogger(SpnegoAuthenticator.class);
-    
     private String _authMethod = Constraint.__SPNEGO_AUTH;
-    
+
     public SpnegoAuthenticator()
     {
-    	
     }
-    
+
     /**
      * Allow for a custom authMethod value to be set for instances where SPENGO may not be appropriate
      * @param authMethod
      */
     public SpnegoAuthenticator( String authMethod )
     {
-    	_authMethod = authMethod;
+        _authMethod = authMethod;
     }
-    
+
+    @Override
     public String getAuthMethod()
     {
         return _authMethod;
     }
 
-
-
+    @Override
     public Authentication validateRequest(ServletRequest request, ServletResponse response, boolean mandatory) throws ServerAuthException
-    {        
+    {
         HttpServletRequest req = (HttpServletRequest)request;
         HttpServletResponse res = (HttpServletResponse)response;
-        
-        String header = req.getHeader(HttpHeaders.AUTHORIZATION);
+
+        String header = req.getHeader(HttpHeader.AUTHORIZATION.asString());
 
         if (!mandatory)
         {
             return new DeferredAuthentication(this);
         }
-        
+
         // check to see if we have authorization headers required to continue
         if ( header == null )
         {
             try
             {
-            	 if (DeferredAuthentication.isDeferred(res))
-            	 {
+                 if (DeferredAuthentication.isDeferred(res))
+                 {
                      return Authentication.UNAUTHENTICATED;
-            	 }
-            	 
+                 }
+
                 LOG.debug("SpengoAuthenticator: sending challenge");
-                res.setHeader(HttpHeaders.WWW_AUTHENTICATE, HttpHeaders.NEGOTIATE);
+                res.setHeader(HttpHeader.WWW_AUTHENTICATE.asString(), HttpHeader.NEGOTIATE.asString());
                 res.sendError(HttpServletResponse.SC_UNAUTHORIZED);
                 return Authentication.SEND_CONTINUE;
-            } 
+            }
             catch (IOException ioe)
             {
                 throw new ServerAuthException(ioe);
-            }       
+            }
         }
-        else if (header != null && header.startsWith(HttpHeaders.NEGOTIATE))
+        else if (header != null && header.startsWith(HttpHeader.NEGOTIATE.asString()))
         {
             String spnegoToken = header.substring(10);
-            
+
             UserIdentity user = login(null,spnegoToken, request);
-            
+
             if ( user != null )
             {
                 return new UserAuthentication(getAuthMethod(),user);
             }
         }
-        
+
         return Authentication.UNAUTHENTICATED;
     }
 
+    @Override
     public boolean secureResponse(ServletRequest request, ServletResponse response, boolean mandatory, User validatedUser) throws ServerAuthException
     {
         return true;
diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/package-info.java b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/package-info.java
new file mode 100644
index 0000000..a5591fe
--- /dev/null
+++ b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Security : Authenticators and Callbacks
+ */
+package org.eclipse.jetty.security.authentication;
+
diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/package-info.java b/jetty-security/src/main/java/org/eclipse/jetty/security/package-info.java
new file mode 100644
index 0000000..38972ad
--- /dev/null
+++ b/jetty-security/src/main/java/org/eclipse/jetty/security/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Security : Modular Support for Security in Jetty
+ */
+package org.eclipse.jetty.security;
+
diff --git a/jetty-security/src/test/java/org/eclipse/jetty/security/AliasedConstraintTest.java b/jetty-security/src/test/java/org/eclipse/jetty/security/AliasedConstraintTest.java
index 11aebb3..fffc6cd 100644
--- a/jetty-security/src/test/java/org/eclipse/jetty/security/AliasedConstraintTest.java
+++ b/jetty-security/src/test/java/org/eclipse/jetty/security/AliasedConstraintTest.java
@@ -1,174 +1,174 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.security;
-
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-import org.eclipse.jetty.http.HttpStatus;
-import org.eclipse.jetty.server.Connector;
-import org.eclipse.jetty.server.LocalConnector;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.handler.ContextHandler;
-import org.eclipse.jetty.server.handler.ResourceHandler;
-import org.eclipse.jetty.server.session.SessionHandler;
-import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
-import org.eclipse.jetty.util.security.Constraint;
-import org.eclipse.jetty.util.security.Password;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameter;
-import org.junit.runners.Parameterized.Parameters;
-
-/**
- * Some requests for static data that is served by ResourceHandler, but some is secured.
- * <p>
- * This is mainly here to test security bypass techniques using aliased names that should be caught.
- */
- at RunWith(Parameterized.class)
-public class AliasedConstraintTest
-{
-    private static final String TEST_REALM = "TestRealm";
-    private static Server server;
-    private static LocalConnector connector;
-    private static ConstraintSecurityHandler security;
-
-    @BeforeClass
-    public static void startServer() throws Exception
-    {
-        server = new Server();
-        connector = new LocalConnector();
-        server.setConnectors(new Connector[] { connector });
-
-        ContextHandler context = new ContextHandler();
-        SessionHandler session = new SessionHandler();
-
-        HashLoginService loginService = new HashLoginService(TEST_REALM);
-        loginService.putUser("user0",new Password("password"),new String[] {});
-        loginService.putUser("user",new Password("password"),new String[] { "user" });
-        loginService.putUser("user2",new Password("password"),new String[] { "user" });
-        loginService.putUser("admin",new Password("password"),new String[] { "user", "administrator" });
-        loginService.putUser("user3",new Password("password"),new String[] { "foo" });
-
-        context.setContextPath("/ctx");
-        server.setHandler(context);
-        context.setHandler(session);
-
-        server.addBean(loginService);
-
-        security = new ConstraintSecurityHandler();
-        session.setHandler(security);
-        ResourceHandler handler = new ResourceHandler();
-        String resourceBase = MavenTestingUtils.getTestResourceDir("docroot").getAbsolutePath();
-        handler.setResourceBase(resourceBase);
-        security.setHandler(handler);
-
-        List<ConstraintMapping> constraints = new ArrayList<ConstraintMapping>();
-
-        Constraint constraint0 = new Constraint();
-        constraint0.setAuthenticate(true);
-        constraint0.setName("forbid");
-        ConstraintMapping mapping0 = new ConstraintMapping();
-        mapping0.setPathSpec("/forbid/*");
-        mapping0.setConstraint(constraint0);
-        constraints.add(mapping0);
-
-        Set<String> knownRoles = new HashSet<String>();
-        knownRoles.add("user");
-        knownRoles.add("administrator");
-
-        security.setConstraintMappings(constraints,knownRoles);
-        server.start();
-    }
-
-    @AfterClass
-    public static void stopServer() throws Exception
-    {
-        server.stop();
-    }
-
-    @Parameters(name = "{0}: {1}")
-    public static Collection<Object[]> data()
-    {
-        List<Object[]> data = new ArrayList<Object[]>();
-
-        final String OPENCONTENT = "this is open content";
-
-        data.add(new Object[] { "/ctx/all/index.txt", HttpStatus.OK_200, OPENCONTENT });
-        data.add(new Object[] { "/ctx/ALL/index.txt", HttpStatus.NOT_FOUND_404, null });
-        data.add(new Object[] { "/ctx/ALL/Fred/../index.txt", HttpStatus.NOT_FOUND_404, null });
-        data.add(new Object[] { "/ctx/../bar/../ctx/all/index.txt", HttpStatus.OK_200, OPENCONTENT });
-        data.add(new Object[] { "/ctx/forbid/index.txt", HttpStatus.FORBIDDEN_403, null });
-        data.add(new Object[] { "/ctx/all/../forbid/index.txt", HttpStatus.FORBIDDEN_403, null });
-        data.add(new Object[] { "/ctx/FoRbId/index.txt", HttpStatus.NOT_FOUND_404, null });
-
-        return data;
-    }
-
-    @Parameter(value = 0)
-    public String uri;
-
-    @Parameter(value = 1)
-    public int expectedStatusCode;
-
-    @Parameter(value = 2)
-    public String expectedContent;
-
-    @Test
-    public void testAccess() throws Exception
-    {
-        StringBuilder request = new StringBuilder();
-        request.append("GET ").append(uri).append(" HTTP/1.1\r\n");
-        request.append("Host: localhost\r\n");
-        request.append("Connection: close\r\n");
-        request.append("\r\n");
-
-        String response = connector.getResponses(request.toString());
-
-        switch (expectedStatusCode)
-        {
-            case 200:
-                assertThat(response,startsWith("HTTP/1.1 200 OK"));
-                break;
-            case 403:
-                assertThat(response,startsWith("HTTP/1.1 403 Forbidden"));
-                break;
-            case 404:
-                assertThat(response,startsWith("HTTP/1.1 404 Not Found"));
-                break;
-            default:
-                fail("Write a handler for response status code: " + expectedStatusCode);
-                break;
-        }
-
-        if (expectedContent != null)
-        {
-            assertThat(response,containsString("this is open content"));
-        }
-    }
-}
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.LocalConnector;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.ResourceHandler;
+import org.eclipse.jetty.server.session.SessionHandler;
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.eclipse.jetty.util.security.Constraint;
+import org.eclipse.jetty.util.security.Password;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Some requests for static data that is served by ResourceHandler, but some is secured.
+ * <p>
+ * This is mainly here to test security bypass techniques using aliased names that should be caught.
+ */
+ at RunWith(Parameterized.class)
+public class AliasedConstraintTest
+{
+    private static final String TEST_REALM = "TestRealm";
+    private static Server server;
+    private static LocalConnector connector;
+    private static ConstraintSecurityHandler security;
+
+    @BeforeClass
+    public static void startServer() throws Exception
+    {
+        server = new Server();
+        connector = new LocalConnector(server);
+        server.setConnectors(new Connector[] { connector });
+
+        ContextHandler context = new ContextHandler();
+        SessionHandler session = new SessionHandler();
+
+        HashLoginService loginService = new HashLoginService(TEST_REALM);
+        loginService.putUser("user0",new Password("password"),new String[] {});
+        loginService.putUser("user",new Password("password"),new String[] { "user" });
+        loginService.putUser("user2",new Password("password"),new String[] { "user" });
+        loginService.putUser("admin",new Password("password"),new String[] { "user", "administrator" });
+        loginService.putUser("user3",new Password("password"),new String[] { "foo" });
+
+        context.setContextPath("/ctx");
+        context.setResourceBase(MavenTestingUtils.getTestResourceDir("docroot").getAbsolutePath());
+        server.setHandler(context);
+        context.setHandler(session);
+        // context.addAliasCheck(new AllowSymLinkAliasChecker());
+
+        server.addBean(loginService);
+
+        security = new ConstraintSecurityHandler();
+        session.setHandler(security);
+        ResourceHandler handler = new ResourceHandler();
+        security.setHandler(handler);
+
+        List<ConstraintMapping> constraints = new ArrayList<>();
+
+        Constraint constraint0 = new Constraint();
+        constraint0.setAuthenticate(true);
+        constraint0.setName("forbid");
+        ConstraintMapping mapping0 = new ConstraintMapping();
+        mapping0.setPathSpec("/forbid/*");
+        mapping0.setConstraint(constraint0);
+        constraints.add(mapping0);
+
+        Set<String> knownRoles = new HashSet<>();
+        knownRoles.add("user");
+        knownRoles.add("administrator");
+
+        security.setConstraintMappings(constraints,knownRoles);
+        server.start();
+    }
+
+    @AfterClass
+    public static void stopServer() throws Exception
+    {
+        server.stop();
+    }
+
+    @Parameters(name = "{0}: {1}")
+    public static Collection<Object[]> data()
+    {
+        List<Object[]> data = new ArrayList<>();
+
+        final String OPENCONTENT = "this is open content";
+
+        data.add(new Object[] { "/ctx/all/index.txt", HttpStatus.OK_200, OPENCONTENT });
+        data.add(new Object[] { "/ctx/ALL/index.txt", HttpStatus.NOT_FOUND_404, null });
+        data.add(new Object[] { "/ctx/ALL/Fred/../index.txt", HttpStatus.NOT_FOUND_404, null });
+        data.add(new Object[] { "/ctx/../bar/../ctx/all/index.txt", HttpStatus.OK_200, OPENCONTENT });
+        data.add(new Object[] { "/ctx/forbid/index.txt", HttpStatus.FORBIDDEN_403, null });
+        data.add(new Object[] { "/ctx/all/../forbid/index.txt", HttpStatus.FORBIDDEN_403, null });
+        data.add(new Object[] { "/ctx/FoRbId/index.txt", HttpStatus.NOT_FOUND_404, null });
+
+        return data;
+    }
+
+    @Parameter(value = 0)
+    public String uri;
+
+    @Parameter(value = 1)
+    public int expectedStatusCode;
+
+    @Parameter(value = 2)
+    public String expectedContent;
+
+    @Test
+    public void testAccess() throws Exception
+    {
+        StringBuilder request = new StringBuilder();
+        request.append("GET ").append(uri).append(" HTTP/1.1\r\n");
+        request.append("Host: localhost\r\n");
+        request.append("Connection: close\r\n");
+        request.append("\r\n");
+
+        String response = connector.getResponses(request.toString());
+
+        switch (expectedStatusCode)
+        {
+            case 200:
+                assertThat(response,startsWith("HTTP/1.1 200 OK"));
+                break;
+            case 403:
+                assertThat(response,startsWith("HTTP/1.1 403 Forbidden"));
+                break;
+            case 404:
+                assertThat(response,startsWith("HTTP/1.1 404 Not Found"));
+                break;
+            default:
+                fail("Write a handler for response status code: " + expectedStatusCode);
+                break;
+        }
+
+        if (expectedContent != null)
+        {
+            assertThat(response,containsString("this is open content"));
+        }
+    }
+}
diff --git a/jetty-security/src/test/java/org/eclipse/jetty/security/ConstraintTest.java b/jetty-security/src/test/java/org/eclipse/jetty/security/ConstraintTest.java
index b65f95a..5716445 100644
--- a/jetty-security/src/test/java/org/eclipse/jetty/security/ConstraintTest.java
+++ b/jetty-security/src/test/java/org/eclipse/jetty/security/ConstraintTest.java
@@ -18,22 +18,26 @@
 
 package org.eclipse.jetty.security;
 
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
 import java.io.IOException;
-import java.util.ArrayList;
+import java.nio.charset.StandardCharsets;
 import java.security.MessageDigest;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.TimeUnit;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
-
+import javax.servlet.HttpConstraintElement;
+import javax.servlet.HttpMethodConstraintElement;
 import javax.servlet.ServletException;
+import javax.servlet.ServletSecurityElement;
+import javax.servlet.annotation.ServletSecurity.EmptyRoleSemantic;
+import javax.servlet.annotation.ServletSecurity.TransportGuarantee;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
@@ -41,6 +45,8 @@ import org.eclipse.jetty.security.authentication.BasicAuthenticator;
 import org.eclipse.jetty.security.authentication.DigestAuthenticator;
 import org.eclipse.jetty.security.authentication.FormAuthenticator;
 import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.HttpConnectionFactory;
 import org.eclipse.jetty.server.LocalConnector;
 import org.eclipse.jetty.server.Request;
 import org.eclipse.jetty.server.Server;
@@ -50,39 +56,37 @@ import org.eclipse.jetty.server.handler.ContextHandler;
 import org.eclipse.jetty.server.handler.HandlerWrapper;
 import org.eclipse.jetty.server.session.SessionHandler;
 import org.eclipse.jetty.util.B64Code;
-import org.eclipse.jetty.util.StringUtil;
 import org.eclipse.jetty.util.TypeUtil;
 import org.eclipse.jetty.util.security.Constraint;
-import org.eclipse.jetty.util.security.Credential;
 import org.eclipse.jetty.util.security.Password;
+import org.hamcrest.Matchers;
 import org.junit.After;
+import org.junit.Assert;
 import org.junit.Before;
-import org.junit.BeforeClass;
 import org.junit.Test;
 
-/**
- * @version $Revision: 1441 $ $Date: 2010-04-02 12:28:17 +0200 (Fri, 02 Apr 2010) $
- */
 public class ConstraintTest
 {
     private static final String TEST_REALM = "TestRealm";
-    private static Server _server;
-    private static LocalConnector _connector;
-    private static SessionHandler _session;
+    private Server _server;
+    private LocalConnector _connector;
     private ConstraintSecurityHandler _security;
+    private HttpConfiguration _config;
 
-    @BeforeClass
-    public static void startServer()
+    @Before
+    public void startServer()
     {
         _server = new Server();
-        _connector = new LocalConnector();
+        _connector = new LocalConnector(_server);
+        _config=_connector.getConnectionFactory(HttpConnectionFactory.class).getHttpConfiguration();
         _server.setConnectors(new Connector[]{_connector});
 
         ContextHandler _context = new ContextHandler();
-        _session = new SessionHandler();
+        SessionHandler _session = new SessionHandler();
 
         HashLoginService _loginService = new HashLoginService(TEST_REALM);
-        _loginService.putUser("user",new Password("password"));
+        _loginService.putUser("user0", new Password("password"), new String[]{});
+        _loginService.putUser("user",new Password("password"), new String[] {"user"});
         _loginService.putUser("user2",new Password("password"), new String[] {"user"});
         _loginService.putUser("admin",new Password("password"), new String[] {"user","administrator"});
         _loginService.putUser("user3", new Password("password"), new String[] {"foo"});
@@ -93,16 +97,32 @@ public class ConstraintTest
         _context.setHandler(_session);
 
         _server.addBean(_loginService);
-    }
 
-    @Before
-    public void setupSecurity()
-    {
         _security = new ConstraintSecurityHandler();
         _session.setHandler(_security);
         RequestHandler _handler = new RequestHandler();
         _security.setHandler(_handler);
 
+        _security.setConstraintMappings(getConstraintMappings(), getKnownRoles());
+    }
+
+    @After
+    public void stopServer() throws Exception
+    {
+        _server.stop();
+    }
+
+    public Set<String> getKnownRoles()
+    {
+        Set<String> knownRoles=new HashSet<>();
+        knownRoles.add("user");
+        knownRoles.add("administrator");
+
+        return knownRoles;
+    }
+
+    private List<ConstraintMapping> getConstraintMappings()
+    {
         Constraint constraint0 = new Constraint();
         constraint0.setAuthenticate(true);
         constraint0.setName("forbid");
@@ -158,61 +178,270 @@ public class ConstraintTest
         mapping6.setPathSpec("/data/*");
         mapping6.setConstraint(constraint6);
         
-        Set<String> knownRoles=new HashSet<String>();
-        knownRoles.add("user");
-        knownRoles.add("administrator");
-
-        _security.setConstraintMappings(Arrays.asList(new ConstraintMapping[]
-                {
-                        mapping0, mapping1, mapping2, mapping3, mapping4, mapping5,mapping6
-                }), knownRoles);
+        Constraint constraint7 = new Constraint();
+        constraint7.setAuthenticate(true);
+        constraint7.setName("** constraint");
+        constraint7.setRoles(new String[]{Constraint.ANY_AUTH,"user"}); //the "user" role is superfluous once ** has been defined
+        ConstraintMapping mapping7 = new ConstraintMapping();
+        mapping7.setPathSpec("/starstar/*");
+        mapping7.setConstraint(constraint7);
+       
+        
+        return Arrays.asList(mapping0, mapping1, mapping2, mapping3, mapping4, mapping5, mapping6, mapping7);
     }
 
-    @After
-    public void stopServer() throws Exception
+    @Test
+    public void testConstraints() throws Exception
     {
-        if (_server.isRunning())
-        {
-            _server.stop();
-            _server.join();
-        }
+        List<ConstraintMapping> mappings = new ArrayList<>(_security.getConstraintMappings());
+
+        Assert.assertTrue(mappings.get(0).getConstraint().isForbidden());
+        Assert.assertFalse(mappings.get(1).getConstraint().isForbidden());
+        Assert.assertFalse(mappings.get(2).getConstraint().isForbidden());
+        Assert.assertFalse(mappings.get(3).getConstraint().isForbidden());
+
+        Assert.assertFalse(mappings.get(0).getConstraint().isAnyRole());
+        Assert.assertTrue(mappings.get(1).getConstraint().isAnyRole());
+        Assert.assertFalse(mappings.get(2).getConstraint().isAnyRole());
+        Assert.assertFalse(mappings.get(3).getConstraint().isAnyRole());
+
+        Assert.assertFalse(mappings.get(0).getConstraint().hasRole("administrator"));
+        Assert.assertTrue(mappings.get(1).getConstraint().hasRole("administrator"));
+        Assert.assertTrue(mappings.get(2).getConstraint().hasRole("administrator"));
+        Assert.assertFalse(mappings.get(3).getConstraint().hasRole("administrator"));
+
+        Assert.assertTrue(mappings.get(0).getConstraint().getAuthenticate());
+        Assert.assertTrue(mappings.get(1).getConstraint().getAuthenticate());
+        Assert.assertTrue(mappings.get(2).getConstraint().getAuthenticate());
+        Assert.assertFalse(mappings.get(3).getConstraint().getAuthenticate());
     }
+    
 
+    /**
+     * Equivalent of Servlet Spec 3.1 pg 132, sec 13.4.1.1, Example 13-1
+     * @ServletSecurity
+     * @throws Exception
+     */
     @Test
-    public void testConstraints() throws Exception
+    public void testSecurityElementExample13_1() throws Exception
+    {
+        ServletSecurityElement element = new ServletSecurityElement();
+        List<ConstraintMapping> mappings = ConstraintSecurityHandler.createConstraintsWithMappingsForPath("foo", "/foo/*", element);
+        Assert.assertTrue(mappings.isEmpty());
+    }
+    
+   
+    /**
+     * Equivalent of Servlet Spec 3.1 pg 132, sec 13.4.1.1, Example 13-2
+     * @ServletSecurity(@HttpConstraint(transportGuarantee = TransportGuarantee.CONFIDENTIAL))
+     * 
+     * @throws Exception
+     */
+    @Test
+    public void testSecurityElementExample13_2() throws Exception
+    {
+        HttpConstraintElement httpConstraintElement = new HttpConstraintElement(TransportGuarantee.CONFIDENTIAL);
+        ServletSecurityElement element = new ServletSecurityElement(httpConstraintElement);
+        List<ConstraintMapping> mappings = ConstraintSecurityHandler.createConstraintsWithMappingsForPath("foo", "/foo/*", element);
+        Assert.assertTrue(!mappings.isEmpty());
+        Assert.assertEquals(1, mappings.size());
+        ConstraintMapping mapping = mappings.get(0);
+        Assert.assertEquals(2, mapping.getConstraint().getDataConstraint());
+    }
+    
+    /**
+     * Equivalent of Servlet Spec 3.1 pg 132, sec 13.4.1.1, Example 13-3
+     * @ServletSecurity(@HttpConstraint(EmptyRoleSemantic.DENY))
+     * @throws Exception
+     */
+    @Test
+    public void testSecurityElementExample13_3() throws Exception
+    {
+        HttpConstraintElement httpConstraintElement = new HttpConstraintElement(EmptyRoleSemantic.DENY);
+        ServletSecurityElement element = new ServletSecurityElement(httpConstraintElement);
+        List<ConstraintMapping> mappings = ConstraintSecurityHandler.createConstraintsWithMappingsForPath("foo", "/foo/*", element);
+        Assert.assertTrue(!mappings.isEmpty());
+        Assert.assertEquals(1, mappings.size());
+        ConstraintMapping mapping = mappings.get(0);
+        Assert.assertTrue(mapping.getConstraint().isForbidden());
+    }
+    
+    /**
+     * Equivalent of Servlet Spec 3.1 pg 132, sec 13.4.1.1, Example 13-4
+     * @ServletSecurity(@HttpConstraint(rolesAllowed = "R1"))
+     * @throws Exception
+     */
+    @Test
+    public void testSecurityElementExample13_4() throws Exception
+    {
+        HttpConstraintElement httpConstraintElement = new HttpConstraintElement(TransportGuarantee.NONE, "R1");
+        ServletSecurityElement element = new ServletSecurityElement(httpConstraintElement);
+        List<ConstraintMapping> mappings = ConstraintSecurityHandler.createConstraintsWithMappingsForPath("foo", "/foo/*", element);
+        Assert.assertTrue(!mappings.isEmpty());
+        Assert.assertEquals(1, mappings.size());
+        ConstraintMapping mapping = mappings.get(0);
+        Assert.assertTrue(mapping.getConstraint().getAuthenticate());
+        Assert.assertTrue(mapping.getConstraint().getRoles() != null);
+        Assert.assertEquals(1, mapping.getConstraint().getRoles().length);
+        Assert.assertEquals("R1", mapping.getConstraint().getRoles()[0]);
+        Assert.assertEquals(0, mapping.getConstraint().getDataConstraint());
+    }
+    
+    /**
+     * Equivalent of Servlet Spec 3.1 pg 132, sec 13.4.1.1, Example 13-5
+     * @ServletSecurity((httpMethodConstraints = {
+     * @HttpMethodConstraint(value = "GET", rolesAllowed = "R1"),
+     * @HttpMethodConstraint(value = "POST", rolesAllowed = "R1",
+     *         transportGuarantee = TransportGuarantee.CONFIDENTIAL)})
+     * @throws Exception
+     */ 
+    @Test
+    public void testSecurityElementExample13_5() throws Exception
+    {
+        List<HttpMethodConstraintElement> methodElements = new ArrayList<HttpMethodConstraintElement>();
+        methodElements.add(new HttpMethodConstraintElement("GET", new HttpConstraintElement(TransportGuarantee.NONE, "R1")));
+        methodElements.add(new HttpMethodConstraintElement("POST", new HttpConstraintElement(TransportGuarantee.CONFIDENTIAL, "R1")));
+        ServletSecurityElement element = new ServletSecurityElement(methodElements);
+        List<ConstraintMapping> mappings = ConstraintSecurityHandler.createConstraintsWithMappingsForPath("foo", "/foo/*", element);
+        Assert.assertTrue(!mappings.isEmpty());
+        Assert.assertEquals(2, mappings.size());
+        Assert.assertEquals("GET", mappings.get(0).getMethod());
+        Assert.assertEquals("R1", mappings.get(0).getConstraint().getRoles()[0]);
+        Assert.assertTrue(mappings.get(0).getMethodOmissions() == null);
+        Assert.assertEquals(0, mappings.get(0).getConstraint().getDataConstraint());
+        Assert.assertEquals("POST", mappings.get(1).getMethod());
+        Assert.assertEquals("R1", mappings.get(1).getConstraint().getRoles()[0]);
+        Assert.assertEquals(2, mappings.get(1).getConstraint().getDataConstraint());
+        Assert.assertTrue(mappings.get(1).getMethodOmissions() == null);
+    }
+    
+    /**
+     * Equivalent of Servlet Spec 3.1 pg 132, sec 13.4.1.1, Example 13-6
+     * @ServletSecurity(value = @HttpConstraint(rolesAllowed = "R1"), httpMethodConstraints = @HttpMethodConstraint("GET"))
+     * @throws Exception
+     */
+    @Test
+    public void testSecurityElementExample13_6 () throws Exception
     {
-        ConstraintMapping[] mappings =_security.getConstraintMappings().toArray(new ConstraintMapping[0]);
-
-        assertTrue (mappings[0].getConstraint().isForbidden());
-        assertFalse(mappings[1].getConstraint().isForbidden());
-        assertFalse(mappings[2].getConstraint().isForbidden());
-        assertFalse(mappings[3].getConstraint().isForbidden());
-
-        assertFalse(mappings[0].getConstraint().isAnyRole());
-        assertTrue (mappings[1].getConstraint().isAnyRole());
-        assertFalse(mappings[2].getConstraint().isAnyRole());
-        assertFalse(mappings[3].getConstraint().isAnyRole());
-
-        assertFalse(mappings[0].getConstraint().hasRole("administrator"));
-        assertTrue (mappings[1].getConstraint().hasRole("administrator"));
-        assertTrue (mappings[2].getConstraint().hasRole("administrator"));
-        assertFalse(mappings[3].getConstraint().hasRole("administrator"));
-
-        assertTrue (mappings[0].getConstraint().getAuthenticate());
-        assertTrue (mappings[1].getConstraint().getAuthenticate());
-        assertTrue (mappings[2].getConstraint().getAuthenticate());
-        assertFalse(mappings[3].getConstraint().getAuthenticate());
+        List<HttpMethodConstraintElement> methodElements = new ArrayList<HttpMethodConstraintElement>();
+        methodElements.add(new HttpMethodConstraintElement("GET"));
+        ServletSecurityElement element = new ServletSecurityElement(new HttpConstraintElement(TransportGuarantee.NONE, "R1"), methodElements);
+        List<ConstraintMapping> mappings = ConstraintSecurityHandler.createConstraintsWithMappingsForPath("foo", "/foo/*", element);
+        Assert.assertTrue(!mappings.isEmpty());
+        Assert.assertEquals(2, mappings.size());
+        Assert.assertTrue(mappings.get(0).getMethodOmissions() != null);
+        Assert.assertEquals("GET", mappings.get(0).getMethodOmissions()[0]);
+        Assert.assertTrue(mappings.get(0).getConstraint().getAuthenticate());
+        Assert.assertEquals("R1", mappings.get(0).getConstraint().getRoles()[0]);
+        Assert.assertEquals("GET", mappings.get(1).getMethod());
+        Assert.assertTrue(mappings.get(1).getMethodOmissions() == null);
+        Assert.assertEquals(0, mappings.get(1).getConstraint().getDataConstraint());
+        Assert.assertFalse(mappings.get(1).getConstraint().getAuthenticate());
+    }
+    
+    /**
+     * Equivalent of Servlet Spec 3.1 pg 132, sec 13.4.1.1, Example 13-7
+     * @ServletSecurity(value = @HttpConstraint(rolesAllowed = "R1"), 
+     *                  httpMethodConstraints = @HttpMethodConstraint(value="TRACE",
+     *                  emptyRoleSemantic = EmptyRoleSemantic.DENY))
+     * @throws Exception
+     */
+    @Test
+    public void testSecurityElementExample13_7() throws Exception
+    {
+        List<HttpMethodConstraintElement> methodElements = new ArrayList<HttpMethodConstraintElement>();
+        methodElements.add(new HttpMethodConstraintElement("TRACE", new HttpConstraintElement(EmptyRoleSemantic.DENY)));
+        ServletSecurityElement element = new ServletSecurityElement(new HttpConstraintElement(TransportGuarantee.NONE, "R1"), methodElements);
+        List<ConstraintMapping> mappings = ConstraintSecurityHandler.createConstraintsWithMappingsForPath("foo", "/foo/*", element);
+        Assert.assertTrue(!mappings.isEmpty());
+        Assert.assertEquals(2, mappings.size());
+        Assert.assertTrue(mappings.get(0).getMethodOmissions() != null);
+        Assert.assertEquals("TRACE", mappings.get(0).getMethodOmissions()[0]);
+        Assert.assertTrue(mappings.get(0).getConstraint().getAuthenticate());
+        Assert.assertEquals("R1", mappings.get(0).getConstraint().getRoles()[0]);
+        Assert.assertEquals("TRACE", mappings.get(1).getMethod());
+        Assert.assertTrue(mappings.get(1).getMethodOmissions() == null);
+        Assert.assertEquals(0, mappings.get(1).getConstraint().getDataConstraint());
+        Assert.assertTrue(mappings.get(1).getConstraint().isForbidden());
+    }
+    
+    @Test
+    public void testUncoveredHttpMethodDetection() throws Exception
+    {
+        //Test no methods named
+        Constraint constraint1 = new Constraint();
+        constraint1.setAuthenticate(true);
+        constraint1.setName("** constraint");
+        constraint1.setRoles(new String[]{Constraint.ANY_AUTH,"user"}); //No methods named, no uncovered methods
+        ConstraintMapping mapping1 = new ConstraintMapping();
+        mapping1.setPathSpec("/starstar/*");
+        mapping1.setConstraint(constraint1);
+        
+        _security.setConstraintMappings(Collections.singletonList(mapping1));
+        _security.setAuthenticator(new BasicAuthenticator());
+        _server.start();
+
+        Set<String> uncoveredPaths = _security.getPathsWithUncoveredHttpMethods();
+        Assert.assertTrue(uncoveredPaths.isEmpty()); //no uncovered methods
+  
+        //Test only an explicitly named method, no omissions to cover other methods
+        Constraint constraint2 = new Constraint();
+        constraint2.setAuthenticate(true);
+        constraint2.setName("user constraint");
+        constraint2.setRoles(new String[]{"user"});
+        ConstraintMapping mapping2 = new ConstraintMapping();
+        mapping2.setPathSpec("/user/*");
+        mapping2.setMethod("GET");
+        mapping2.setConstraint(constraint2);
+        
+        _security.addConstraintMapping(mapping2);
+        uncoveredPaths = _security.getPathsWithUncoveredHttpMethods();
+        Assert.assertNotNull(uncoveredPaths);
+        Assert.assertEquals(1, uncoveredPaths.size());
+        Assert.assertTrue(uncoveredPaths.contains("/user/*"));
+        
+        //Test an explicitly named method with a http-method-omission to cover all other methods
+        Constraint constraint2a = new Constraint();
+        constraint2a.setAuthenticate(true);
+        constraint2a.setName("forbid constraint");
+        ConstraintMapping mapping2a = new ConstraintMapping();
+        mapping2a.setPathSpec("/user/*");
+        mapping2a.setMethodOmissions(new String[]{"GET"});
+        mapping2a.setConstraint(constraint2a);
+        
+        _security.addConstraintMapping(mapping2a);
+        uncoveredPaths = _security.getPathsWithUncoveredHttpMethods();
+        Assert.assertNotNull(uncoveredPaths);
+        Assert.assertEquals(0, uncoveredPaths.size());
+        
+        //Test a http-method-omission only
+        Constraint constraint3 = new Constraint();
+        constraint3.setAuthenticate(true);
+        constraint3.setName("omit constraint");
+        ConstraintMapping mapping3 = new ConstraintMapping();
+        mapping3.setPathSpec("/omit/*");
+        mapping3.setMethodOmissions(new String[]{"GET", "POST"});
+        mapping3.setConstraint(constraint3);
+        
+        _security.addConstraintMapping(mapping3);
+        uncoveredPaths = _security.getPathsWithUncoveredHttpMethods();
+        Assert.assertNotNull(uncoveredPaths);
+        Assert.assertTrue(uncoveredPaths.contains("/omit/*"));
+        
+        _security.setDenyUncoveredHttpMethods(true);
+        uncoveredPaths = _security.getPathsWithUncoveredHttpMethods();
+        Assert.assertNotNull(uncoveredPaths);
+        Assert.assertEquals(0, uncoveredPaths.size());
     }
 
     @Test
     public void testBasic() throws Exception
     {
-        
-        List<ConstraintMapping> list = new ArrayList<ConstraintMapping>(_security.getConstraintMappings());
+        List<ConstraintMapping> list = new ArrayList<>(_security.getConstraintMappings());
         
         Constraint constraint6 = new Constraint();
         constraint6.setAuthenticate(true);
-        constraint6.setName("omit POST and GET");
+        constraint6.setName("omit HEAD and GET");
         constraint6.setRoles(new String[]{"user"});
         ConstraintMapping mapping6 = new ConstraintMapping();
         mapping6.setPathSpec("/omit/*");
@@ -239,7 +468,7 @@ public class ConstraintTest
         mapping8.setConstraint(constraint8);//requests for all methods must be in role "foo"
         list.add(mapping8);
         
-        Set<String> knownRoles=new HashSet<String>();
+        Set<String> knownRoles=new HashSet<>();
         knownRoles.add("user");
         knownRoles.add("administrator");
         knownRoles.add("foo");
@@ -248,82 +477,80 @@ public class ConstraintTest
         
         
         _security.setAuthenticator(new BasicAuthenticator());
-        _security.setStrict(false);
         _server.start();
 
         String response;
         response = _connector.getResponses("GET /ctx/noauth/info HTTP/1.0\r\n\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 200 OK"));
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 200 OK"));
    
         response = _connector.getResponses("GET /ctx/forbid/info HTTP/1.0\r\n\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 403 Forbidden"));
-        
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 403 Forbidden"));
         response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 401 Unauthorized"));
-        assertTrue(response.indexOf("WWW-Authenticate: basic realm=\"TestRealm\"") > 0);
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 401 Unauthorized"));
+        Assert.assertThat(response, Matchers.containsString("WWW-Authenticate: basic realm=\"TestRealm\""));
 
         response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n" +
                 "Authorization: Basic " + B64Code.encode("user:wrong") + "\r\n" +
                 "\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 401 Unauthorized"));
-        assertTrue(response.indexOf("WWW-Authenticate: basic realm=\"TestRealm\"") > 0);
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 401 Unauthorized"));
+        Assert.assertThat(response, Matchers.containsString("WWW-Authenticate: basic realm=\"TestRealm\""));
 
         response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n" +
                 "Authorization: Basic " + B64Code.encode("user:password") + "\r\n" +
                 "\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 200 OK"));
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 200 OK"));
         
         // test admin
         response = _connector.getResponses("GET /ctx/admin/info HTTP/1.0\r\n\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 401 Unauthorized"));
-        assertTrue(response.indexOf("WWW-Authenticate: basic realm=\"TestRealm\"") > 0);
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 401 Unauthorized"));
+        Assert.assertThat(response, Matchers.containsString("WWW-Authenticate: basic realm=\"TestRealm\""));
 
         response = _connector.getResponses("GET /ctx/admin/info HTTP/1.0\r\n" +
                 "Authorization: Basic " + B64Code.encode("admin:wrong") + "\r\n" +
                 "\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 401 Unauthorized"));
-        assertTrue(response.indexOf("WWW-Authenticate: basic realm=\"TestRealm\"") > 0);
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 401 Unauthorized"));
+        Assert.assertThat(response, Matchers.containsString("WWW-Authenticate: basic realm=\"TestRealm\""));
 
         response = _connector.getResponses("GET /ctx/admin/info HTTP/1.0\r\n" +
                 "Authorization: Basic " + B64Code.encode("user:password") + "\r\n" +
                 "\r\n");
 
-        assertTrue(response.startsWith("HTTP/1.1 403 "));
-        assertTrue(response.indexOf("!role") > 0);
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 403 "));
+        Assert.assertThat(response, Matchers.containsString("!role"));
 
         response = _connector.getResponses("GET /ctx/admin/info HTTP/1.0\r\n" +
                 "Authorization: Basic " + B64Code.encode("admin:password") + "\r\n" +
                 "\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 200 OK"));
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 200 OK"));
 
         response = _connector.getResponses("GET /ctx/admin/relax/info HTTP/1.0\r\n\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 200 OK"));
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 200 OK"));
         
         //check GET is in role administrator 
         response = _connector.getResponses("GET /ctx/omit/x HTTP/1.0\r\n" +
                                            "Authorization: Basic " + B64Code.encode("admin:password") + "\r\n" +
                                            "\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 200 OK"));
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 200 OK"));
         
         //check POST is in role user
         response = _connector.getResponses("POST /ctx/omit/x HTTP/1.0\r\n" +
                                            "Authorization: Basic " + B64Code.encode("user2:password") + "\r\n" +
                                            "\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 200 OK"));
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 200 OK"));
         
         //check POST can be in role foo too      
         response = _connector.getResponses("POST /ctx/omit/x HTTP/1.0\r\n" +
                                            "Authorization: Basic " + B64Code.encode("user3:password") + "\r\n" +
                                            "\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 200 OK"));
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 200 OK"));
         
         //check HEAD cannot be in role user
         response = _connector.getResponses("HEAD /ctx/omit/x HTTP/1.0\r\n" +
                                            "Authorization: Basic " + B64Code.encode("user2:password") + "\r\n" +
                                            "\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 200 OK"));
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 403 "));
     }
-    
+
     
     private static String CNONCE="1234567890";
     private String digest(String nonce, String username,String password,String uri,String nc) throws Exception
@@ -331,17 +558,17 @@ public class ConstraintTest
         MessageDigest md = MessageDigest.getInstance("MD5");
         byte[] ha1;
         // calc A1 digest
-        md.update(username.getBytes(StringUtil.__ISO_8859_1));
+        md.update(username.getBytes(StandardCharsets.ISO_8859_1));
         md.update((byte) ':');
-        md.update("TestRealm".getBytes(StringUtil.__ISO_8859_1));
+        md.update("TestRealm".getBytes(StandardCharsets.ISO_8859_1));
         md.update((byte) ':');
-        md.update(password.getBytes(StringUtil.__ISO_8859_1));
+        md.update(password.getBytes(StandardCharsets.ISO_8859_1));
         ha1 = md.digest();
         // calc A2 digest
         md.reset();
-        md.update("GET".getBytes(StringUtil.__ISO_8859_1));
+        md.update("GET".getBytes(StandardCharsets.ISO_8859_1));
         md.update((byte) ':');
-        md.update(uri.getBytes(StringUtil.__ISO_8859_1));
+        md.update(uri.getBytes(StandardCharsets.ISO_8859_1));
         byte[] ha2 = md.digest();
 
         // calc digest
@@ -351,17 +578,17 @@ public class ConstraintTest
         // request-digest = <"> < KD ( H(A1), unq(nonce-value) ":" H(A2)
         // ) > <">
 
-        md.update(TypeUtil.toString(ha1, 16).getBytes(StringUtil.__ISO_8859_1));
+        md.update(TypeUtil.toString(ha1, 16).getBytes(StandardCharsets.ISO_8859_1));
         md.update((byte) ':');
-        md.update(nonce.getBytes(StringUtil.__ISO_8859_1));
+        md.update(nonce.getBytes(StandardCharsets.ISO_8859_1));
         md.update((byte) ':');
-        md.update(nc.getBytes(StringUtil.__ISO_8859_1));
+        md.update(nc.getBytes(StandardCharsets.ISO_8859_1));
         md.update((byte) ':');
-        md.update(CNONCE.getBytes(StringUtil.__ISO_8859_1));
+        md.update(CNONCE.getBytes(StandardCharsets.ISO_8859_1));
         md.update((byte) ':');
-        md.update("auth".getBytes(StringUtil.__ISO_8859_1));
+        md.update("auth".getBytes(StandardCharsets.ISO_8859_1));
         md.update((byte) ':');
-        md.update(TypeUtil.toString(ha2, 16).getBytes(StringUtil.__ISO_8859_1));
+        md.update(TypeUtil.toString(ha2, 16).getBytes(StandardCharsets.ISO_8859_1));
         byte[] digest = md.digest();
 
         // check digest
@@ -371,23 +598,24 @@ public class ConstraintTest
     @Test
     public void testDigest() throws Exception
     {
-        _security.setAuthenticator(new DigestAuthenticator());
-        _security.setStrict(false);
+        DigestAuthenticator authenticator = new DigestAuthenticator();
+        authenticator.setMaxNonceCount(5);
+        _security.setAuthenticator(authenticator);
         _server.start();
 
         String response;
         response = _connector.getResponses("GET /ctx/noauth/info HTTP/1.0\r\n\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 200 OK"));
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 200 OK"));
    
         response = _connector.getResponses("GET /ctx/forbid/info HTTP/1.0\r\n\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 403 Forbidden"));
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 403 Forbidden"));
         response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 401 Unauthorized"));
-        assertTrue(response.contains("WWW-Authenticate: Digest realm=\"TestRealm\""));
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 401 Unauthorized"));
+        Assert.assertThat(response, Matchers.containsString("WWW-Authenticate: Digest realm=\"TestRealm\""));
 
         Pattern nonceP = Pattern.compile("nonce=\"([^\"]*)\",");
         Matcher matcher = nonceP.matcher(response);
-        assertTrue(matcher.find());
+        Assert.assertTrue(matcher.find());
         String nonce=matcher.group(1);
         
         
@@ -399,7 +627,7 @@ public class ConstraintTest
             "nonce=\""+nonce+"\", "+
             "response=\""+digest+"\"\r\n"+
             "\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 401 Unauthorized"));
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 401 Unauthorized"));
         
         // right password
         digest= digest(nonce,"user","password","/ctx/auth/info","2");
@@ -409,7 +637,7 @@ public class ConstraintTest
             "nonce=\""+nonce+"\", "+
             "response=\""+digest+"\"\r\n"+
             "\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 200 OK"));
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 200 OK"));
         
 
         // once only
@@ -420,7 +648,7 @@ public class ConstraintTest
             "nonce=\""+nonce+"\", "+
             "response=\""+digest+"\"\r\n"+
             "\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 401 Unauthorized"));
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 401 Unauthorized"));
 
         // increasing
         digest= digest(nonce,"user","password","/ctx/auth/info","4");
@@ -430,7 +658,7 @@ public class ConstraintTest
             "nonce=\""+nonce+"\", "+
             "response=\""+digest+"\"\r\n"+
             "\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 200 OK"));
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 200 OK"));
         
         // out of order
         digest= digest(nonce,"user","password","/ctx/auth/info","3");
@@ -440,30 +668,39 @@ public class ConstraintTest
             "nonce=\""+nonce+"\", "+
             "response=\""+digest+"\"\r\n"+
             "\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 200 OK"));
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 200 OK"));
         
+        // stale
+        digest= digest(nonce,"user","password","/ctx/auth/info","5");
+        response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n" +
+            "Authorization: Digest username=\"user\", qop=auth, cnonce=\"1234567890\", uri=\"/ctx/auth/info\", realm=\"TestRealm\", "+
+            "nc=5, "+
+            "nonce=\""+nonce+"\", "+
+            "response=\""+digest+"\"\r\n"+
+            "\r\n");
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 401 Unauthorized"));
+        Assert.assertThat(response, Matchers.containsString("stale=true"));
     }
 
-    
+
     @Test
     public void testFormDispatch() throws Exception
     {
         _security.setAuthenticator(new FormAuthenticator("/testLoginPage","/testErrorPage",true));
-        _security.setStrict(false);
         _server.start();
 
         String response;
 
         response = _connector.getResponses("GET /ctx/noauth/info HTTP/1.0\r\n\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 200 OK"));
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 200 OK"));
 
         response = _connector.getResponses("GET /ctx/forbid/info HTTP/1.0\r\n\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 403 Forbidden"));
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 403 Forbidden"));
 
         response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n\r\n");
-        assertTrue(response.indexOf("Cache-Control: no-cache") > 0);
-        assertTrue(response.indexOf("Expires") > 0);
-        assertTrue(response.indexOf("URI=/ctx/testLoginPage") > 0);
+        Assert.assertThat(response, Matchers.containsString("Cache-Control: no-cache"));
+        Assert.assertThat(response, Matchers.containsString("Expires"));
+        Assert.assertThat(response, Matchers.containsString("URI=/ctx/testLoginPage"));
 
         String session = response.substring(response.indexOf("JSESSIONID=") + 11, response.indexOf(";Path=/ctx"));
 
@@ -473,7 +710,7 @@ public class ConstraintTest
                 "Content-Length: 31\r\n" +
                 "\r\n" +
         "j_username=user&j_password=wrong\r\n");
-        assertTrue(response.indexOf("testErrorPage") > 0);
+        Assert.assertThat(response, Matchers.containsString("testErrorPage"));
 
         response = _connector.getResponses("POST /ctx/j_security_check HTTP/1.0\r\n" +
                 "Cookie: JSESSIONID=" + session + "\r\n" +
@@ -481,109 +718,109 @@ public class ConstraintTest
                 "Content-Length: 35\r\n" +
                 "\r\n" +
                 "j_username=user&j_password=password\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 302 "));
-        assertTrue(response.indexOf("Location") > 0);
-        assertTrue(response.indexOf("/ctx/auth/info") > 0);
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 302 "));
+        Assert.assertThat(response, Matchers.containsString("Location"));
+        Assert.assertThat(response, Matchers.containsString("Location"));
+        Assert.assertThat(response, Matchers.containsString("/ctx/auth/info"));
         session = response.substring(response.indexOf("JSESSIONID=") + 11, response.indexOf(";Path=/ctx"));
 
         response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n" +
                 "Cookie: JSESSIONID=" + session + "\r\n" +
                 "\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 200 OK"));
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 200 OK"));
+
 
         response = _connector.getResponses("GET /ctx/admin/info HTTP/1.0\r\n" +
                 "Cookie: JSESSIONID=" + session + "\r\n" +
                 "\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 403"));
-        assertTrue(response.indexOf("!role") > 0);
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 403"));
+        Assert.assertThat(response, Matchers.containsString("!role"));
     }
 
     @Test
     public void testFormRedirect() throws Exception
     {
         _security.setAuthenticator(new FormAuthenticator("/testLoginPage","/testErrorPage",false));
-        _security.setStrict(false);
         _server.start();
 
         String response;
 
         response = _connector.getResponses("GET /ctx/noauth/info HTTP/1.0\r\n\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 200 OK"));
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 200 OK"));
 
         response = _connector.getResponses("GET /ctx/forbid/info HTTP/1.0\r\n\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 403 Forbidden"));
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 403 Forbidden"));
 
         response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n\r\n");
-        assertTrue(response.indexOf(" 302 Found") > 0);
-        assertTrue(response.indexOf("/ctx/testLoginPage") > 0);
+        Assert.assertThat(response, Matchers.containsString(" 302 Found"));
+        Assert.assertThat(response, Matchers.containsString("/ctx/testLoginPage"));
         String session = response.substring(response.indexOf("JSESSIONID=") + 11, response.indexOf(";Path=/ctx"));
 
         response = _connector.getResponses("GET /ctx/testLoginPage HTTP/1.0\r\n"+
                 "Cookie: JSESSIONID=" + session + "\r\n" +
                 "\r\n");
-        assertTrue(response.indexOf(" 200 OK") > 0);
-        assertTrue(response.indexOf("URI=/ctx/testLoginPage") > 0);
+        Assert.assertThat(response, Matchers.containsString(" 200 OK"));
+        Assert.assertThat(response, Matchers.containsString("URI=/ctx/testLoginPage"));
 
         response = _connector.getResponses("POST /ctx/j_security_check HTTP/1.0\r\n" +
                 "Cookie: JSESSIONID=" + session + "\r\n" +
                 "Content-Type: application/x-www-form-urlencoded\r\n" +
-                "Content-Length: 31\r\n" +
+                "Content-Length: 32\r\n" +
                 "\r\n" +
-        "j_username=user&j_password=wrong\r\n");
-        assertTrue(response.indexOf("Location") > 0);
+                "j_username=user&j_password=wrong");
+        Assert.assertThat(response, Matchers.containsString("Location"));
 
         response = _connector.getResponses("POST /ctx/j_security_check HTTP/1.0\r\n" +
                 "Cookie: JSESSIONID=" + session + "\r\n" +
                 "Content-Type: application/x-www-form-urlencoded\r\n" +
                 "Content-Length: 35\r\n" +
                 "\r\n" +
-                "j_username=user&j_password=password\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 302 "));
-        assertTrue(response.indexOf("Location") > 0);
-        assertTrue(response.indexOf("/ctx/auth/info") > 0);
+                "j_username=user&j_password=password");
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 302 "));
+        Assert.assertThat(response, Matchers.containsString("Location"));
+        Assert.assertThat(response, Matchers.containsString("/ctx/auth/info"));
         session = response.substring(response.indexOf("JSESSIONID=") + 11, response.indexOf(";Path=/ctx"));
 
         response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n" +
                 "Cookie: JSESSIONID=" + session + "\r\n" +
                 "\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 200 OK"));
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 200 OK"));
 
         response = _connector.getResponses("GET /ctx/admin/info HTTP/1.0\r\n" +
                 "Cookie: JSESSIONID=" + session + "\r\n" +
                 "\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 403"));
-        assertTrue(response.indexOf("!role") > 0);
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 403"));
+        Assert.assertThat(response, Matchers.containsString("!role"));
     }
 
     @Test
     public void testFormPostRedirect() throws Exception
     {
         _security.setAuthenticator(new FormAuthenticator("/testLoginPage","/testErrorPage",false));
-        _security.setStrict(false);
         _server.start();
 
         String response;
 
         response = _connector.getResponses("GET /ctx/noauth/info HTTP/1.0\r\n\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 200 OK"));
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 200 OK"));
 
         response = _connector.getResponses("GET /ctx/forbid/info HTTP/1.0\r\n\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 403 Forbidden"));
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 403 Forbidden"));
 
         response = _connector.getResponses("POST /ctx/auth/info HTTP/1.0\r\n"+
                 "Content-Type: application/x-www-form-urlencoded\r\n" +
                 "Content-Length: 27\r\n" +
                 "\r\n" +
                 "test_parameter=test_value\r\n");
-        assertTrue(response.indexOf(" 302 Found") > 0);
-        assertTrue(response.indexOf("/ctx/testLoginPage") > 0);
+        Assert.assertThat(response, Matchers.containsString(" 302 Found"));
+        Assert.assertThat(response, Matchers.containsString("/ctx/testLoginPage"));
         String session = response.substring(response.indexOf("JSESSIONID=") + 11, response.indexOf(";Path=/ctx"));
 
         response = _connector.getResponses("GET /ctx/testLoginPage HTTP/1.0\r\n"+
                 "Cookie: JSESSIONID=" + session + "\r\n" +
                 "\r\n");
-        assertTrue(response.indexOf(" 200 OK") > 0);
-        assertTrue(response.indexOf("URI=/ctx/testLoginPage") > 0);
+        Assert.assertThat(response, Matchers.containsString(" 200 OK"));
+        Assert.assertThat(response, Matchers.containsString("URI=/ctx/testLoginPage"));
 
         response = _connector.getResponses("POST /ctx/j_security_check HTTP/1.0\r\n" +
                 "Cookie: JSESSIONID=" + session + "\r\n" +
@@ -591,7 +828,8 @@ public class ConstraintTest
                 "Content-Length: 31\r\n" +
                 "\r\n" +
         "j_username=user&j_password=wrong\r\n");
-        assertTrue(response.indexOf("Location") > 0);
+        
+        Assert.assertThat(response, Matchers.containsString("Location"));
 
         response = _connector.getResponses("POST /ctx/j_security_check HTTP/1.0\r\n" +
                 "Cookie: JSESSIONID=" + session + "\r\n" +
@@ -599,83 +837,82 @@ public class ConstraintTest
                 "Content-Length: 35\r\n" +
                 "\r\n" +
                 "j_username=user&j_password=password\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 302 "));
-        assertTrue(response.indexOf("Location") > 0);
-        assertTrue(response.indexOf("/ctx/auth/info") > 0);
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 302 "));
+        Assert.assertThat(response, Matchers.containsString("Location"));
+        Assert.assertThat(response, Matchers.containsString("/ctx/auth/info"));
         session = response.substring(response.indexOf("JSESSIONID=") + 11, response.indexOf(";Path=/ctx"));
 
         // sneak in other request
         response = _connector.getResponses("GET /ctx/auth/other HTTP/1.0\r\n" +
                 "Cookie: JSESSIONID=" + session + "\r\n" +
                 "\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 200 OK"));
-        assertTrue(!response.contains("test_value"));
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 200 OK"));
+        Assert.assertTrue(!response.contains("test_value"));
 
         // retry post as GET
         response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n" +
                 "Cookie: JSESSIONID=" + session + "\r\n" +
                 "\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 200 OK"));
-        assertTrue(response.contains("test_value"));
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 200 OK"));
+        Assert.assertTrue(response.contains("test_value"));
 
         response = _connector.getResponses("GET /ctx/admin/info HTTP/1.0\r\n" +
                 "Cookie: JSESSIONID=" + session + "\r\n" +
                 "\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 403"));
-        assertTrue(response.indexOf("!role") > 0);
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 403"));
+        Assert.assertThat(response, Matchers.containsString("!role"));
     }
-    
+
     @Test
     public void testFormNoCookies() throws Exception
     {
         _security.setAuthenticator(new FormAuthenticator("/testLoginPage","/testErrorPage",false));
-        _security.setStrict(false);
         _server.start();
 
         String response;
 
         response = _connector.getResponses("GET /ctx/noauth/info HTTP/1.0\r\n\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 200 OK"));
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 200 OK"));
 
         response = _connector.getResponses("GET /ctx/forbid/info HTTP/1.0\r\n\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 403 Forbidden"));
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 403 Forbidden"));
 
         response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n\r\n");
-        assertTrue(response.indexOf(" 302 Found") > 0);
-        assertTrue(response.indexOf("/ctx/testLoginPage") > 0);
+        Assert.assertThat(response, Matchers.containsString(" 302 Found"));
+        Assert.assertThat(response, Matchers.containsString("/ctx/testLoginPage"));
         int jsession=response.indexOf(";jsessionid=");
         String session = response.substring(jsession + 12, response.indexOf("\r\n",jsession));
 
         response = _connector.getResponses("GET /ctx/testLoginPage;jsessionid="+session+";other HTTP/1.0\r\n"+
                 "\r\n");
-        assertTrue(response.indexOf(" 200 OK") > 0);
-        assertTrue(response.indexOf("URI=/ctx/testLoginPage") > 0);
+        Assert.assertThat(response, Matchers.containsString(" 200 OK"));
+        Assert.assertThat(response, Matchers.containsString("URI=/ctx/testLoginPage"));
 
         response = _connector.getResponses("POST /ctx/j_security_check;jsessionid="+session+";other HTTP/1.0\r\n" +
                 "Content-Type: application/x-www-form-urlencoded\r\n" +
                 "Content-Length: 31\r\n" +
                 "\r\n" +
         "j_username=user&j_password=wrong\r\n");
-        assertTrue(response.indexOf("Location") > 0);
+        Assert.assertThat(response, Matchers.containsString("Location"));
 
         response = _connector.getResponses("POST /ctx/j_security_check;jsessionid="+session+";other HTTP/1.0\r\n" +
                 "Content-Type: application/x-www-form-urlencoded\r\n" +
                 "Content-Length: 35\r\n" +
                 "\r\n" +
                 "j_username=user&j_password=password\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 302 "));
-        assertTrue(response.indexOf("Location") > 0);
-        assertTrue(response.indexOf("/ctx/auth/info") > 0);
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 302 "));
+        Assert.assertThat(response, Matchers.containsString("Location"));
+        Assert.assertThat(response, Matchers.containsString("/ctx/auth/info"));
         session = response.substring(response.indexOf("JSESSIONID=") + 11, response.indexOf(";Path=/ctx"));
 
         response = _connector.getResponses("GET /ctx/auth/info;jsessionid="+session+";other HTTP/1.0\r\n" +
                 "\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 200 OK"));
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 200 OK"));
 
         response = _connector.getResponses("GET /ctx/admin/info;jsessionid="+session+";other HTTP/1.0\r\n" +
                 "\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 403"));
-        assertTrue(response.indexOf("!role") > 0);
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 403"));
+        Assert.assertThat(response, Matchers.containsString("!role"));
     }
 
     @Test
@@ -686,58 +923,58 @@ public class ConstraintTest
 
         String response;
         response = _connector.getResponses("GET /ctx/noauth/info HTTP/1.0\r\n\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 200 OK"));
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 200 OK"));
 
         response = _connector.getResponses("GET /ctx/forbid/info HTTP/1.0\r\n\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 403 Forbidden"));
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 403 Forbidden"));
 
         response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 401 Unauthorized"));
-        assertTrue(response.indexOf("WWW-Authenticate: basic realm=\"TestRealm\"") > 0);
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 401 Unauthorized"));
+        Assert.assertThat(response, Matchers.containsString("WWW-Authenticate: basic realm=\"TestRealm\""));
 
         response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n" +
                 "Authorization: Basic " + B64Code.encode("user:wrong") + "\r\n" +
                 "\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 401 Unauthorized"));
-        assertTrue(response.indexOf("WWW-Authenticate: basic realm=\"TestRealm\"") > 0);
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 401 Unauthorized"));
+        Assert.assertThat(response, Matchers.containsString("WWW-Authenticate: basic realm=\"TestRealm\""));
 
         response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n" +
-                "Authorization: Basic " + B64Code.encode("user:password") + "\r\n" +
+                "Authorization: Basic " + B64Code.encode("user3:password") + "\r\n" +
                 "\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 403"));
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 403"));
 
         response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n" +
                 "Authorization: Basic " + B64Code.encode("user2:password") + "\r\n" +
                 "\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 200 OK"));
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 200 OK"));
 
 
         // test admin
         response = _connector.getResponses("GET /ctx/admin/info HTTP/1.0\r\n\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 401 Unauthorized"));
-        assertTrue(response.indexOf("WWW-Authenticate: basic realm=\"TestRealm\"") > 0);
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 401 Unauthorized"));
+        Assert.assertThat(response, Matchers.containsString("WWW-Authenticate: basic realm=\"TestRealm\""));
 
         response = _connector.getResponses("GET /ctx/admin/info HTTP/1.0\r\n" +
                 "Authorization: Basic " + B64Code.encode("admin:wrong") + "\r\n" +
                 "\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 401 Unauthorized"));
-        assertTrue(response.indexOf("WWW-Authenticate: basic realm=\"TestRealm\"") > 0);
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 401 Unauthorized"));
+        Assert.assertThat(response, Matchers.containsString("WWW-Authenticate: basic realm=\"TestRealm\""));
 
         response = _connector.getResponses("GET /ctx/admin/info HTTP/1.0\r\n" +
                 "Authorization: Basic " + B64Code.encode("user:password") + "\r\n" +
                 "\r\n");
 
-        assertTrue(response.startsWith("HTTP/1.1 403 "));
-        assertTrue(response.indexOf("!role") > 0);
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 403 "));
+        Assert.assertThat(response, Matchers.containsString("!role"));
 
         response = _connector.getResponses("GET /ctx/admin/info HTTP/1.0\r\n" +
                 "Authorization: Basic " + B64Code.encode("admin:password") + "\r\n" +
                 "\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 200 OK"));
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 200 OK"));
 
 
         response = _connector.getResponses("GET /ctx/admin/relax/info HTTP/1.0\r\n\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 200 OK"));
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 200 OK"));
     }
 
     @Test
@@ -750,17 +987,17 @@ public class ConstraintTest
         String response;
 
         response = _connector.getResponses("GET /ctx/noauth/info HTTP/1.0\r\n\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 200 OK"));
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 200 OK"));
 
         response = _connector.getResponses("GET /ctx/forbid/info HTTP/1.0\r\n\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 403 Forbidden"));
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 403 Forbidden"));
 
         response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n\r\n");
-        // assertTrue(response.indexOf(" 302 Found") > 0);
-        // assertTrue(response.indexOf("/ctx/testLoginPage") > 0);
-        assertTrue(response.indexOf("Cache-Control: no-cache") > 0);
-        assertTrue(response.indexOf("Expires") > 0);
-        assertTrue(response.indexOf("URI=/ctx/testLoginPage") > 0);
+        // assertThat(response,containsString(" 302 Found"));
+        // assertThat(response,containsString("/ctx/testLoginPage"));
+        Assert.assertThat(response, Matchers.containsString("Cache-Control: no-cache"));
+        Assert.assertThat(response, Matchers.containsString("Expires"));
+        Assert.assertThat(response, Matchers.containsString("URI=/ctx/testLoginPage"));
 
         String session = response.substring(response.indexOf("JSESSIONID=") + 11, response.indexOf(";Path=/ctx"));
 
@@ -770,38 +1007,38 @@ public class ConstraintTest
                 "Content-Length: 31\r\n" +
                 "\r\n" +
                 "j_username=user&j_password=wrong\r\n");
-        // assertTrue(response.indexOf("Location") > 0);
-        assertTrue(response.indexOf("testErrorPage") > 0);
+        // assertThat(response,containsString("Location"));
+        Assert.assertThat(response, Matchers.containsString("testErrorPage"));
 
         response = _connector.getResponses("POST /ctx/j_security_check HTTP/1.0\r\n" +
                 "Cookie: JSESSIONID=" + session + "\r\n" +
                 "Content-Type: application/x-www-form-urlencoded\r\n" +
-                "Content-Length: 35\r\n" +
+                "Content-Length: 36\r\n" +
                 "\r\n" +
-                "j_username=user&j_password=password\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 302 "));
-        assertTrue(response.indexOf("Location") > 0);
-        assertTrue(response.indexOf("/ctx/auth/info") > 0);
+                "j_username=user0&j_password=password\r\n");
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 302 "));
+        Assert.assertThat(response, Matchers.containsString("Location"));
+        Assert.assertThat(response, Matchers.containsString("/ctx/auth/info"));
         session = response.substring(response.indexOf("JSESSIONID=") + 11, response.indexOf(";Path=/ctx"));
 
         response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n" +
                 "Cookie: JSESSIONID=" + session + "\r\n" +
                 "\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 403"));
-        assertTrue(response.indexOf("!role") > 0);
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 403"));
+        Assert.assertThat(response, Matchers.containsString("!role"));
 
         response = _connector.getResponses("GET /ctx/admin/info HTTP/1.0\r\n" +
                 "Cookie: JSESSIONID=" + session + "\r\n" +
                 "\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 403"));
-        assertTrue(response.indexOf("!role") > 0);
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 403"));
+        Assert.assertThat(response, Matchers.containsString("!role"));
 
 
 
         // log in again as user2
         response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n\r\n");
-//        assertTrue(response.startsWith("HTTP/1.1 302 "));
-//        assertTrue(response.indexOf("testLoginPage") > 0);
+//        assertThat(response,startsWith("HTTP/1.1 302 "));
+//        assertThat(response,containsString("testLoginPage"));
         session = response.substring(response.indexOf("JSESSIONID=") + 11, response.indexOf(";Path=/ctx"));
 
         response = _connector.getResponses("POST /ctx/j_security_check HTTP/1.0\r\n" +
@@ -810,28 +1047,28 @@ public class ConstraintTest
                 "Content-Length: 36\r\n" +
                 "\r\n" +
                 "j_username=user2&j_password=password\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 302 "));
-        assertTrue(response.indexOf("Location") > 0);
-        assertTrue(response.indexOf("/ctx/auth/info") > 0);
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 302 "));
+        Assert.assertThat(response, Matchers.containsString("Location"));
+        Assert.assertThat(response, Matchers.containsString("/ctx/auth/info"));
         session = response.substring(response.indexOf("JSESSIONID=") + 11, response.indexOf(";Path=/ctx"));
 
         response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n" +
                 "Cookie: JSESSIONID=" + session + "\r\n" +
                 "\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 200 OK"));
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 200 OK"));
 
         response = _connector.getResponses("GET /ctx/admin/info HTTP/1.0\r\n" +
                 "Cookie: JSESSIONID=" + session + "\r\n" +
                 "\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 403"));
-        assertTrue(response.indexOf("!role") > 0);
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 403"));
+        Assert.assertThat(response, Matchers.containsString("!role"));
 
 
 
         // log in again as admin
         response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n\r\n");
-//        assertTrue(response.startsWith("HTTP/1.1 302 "));
-//        assertTrue(response.indexOf("testLoginPage") > 0);
+//        assertThat(response,startsWith("HTTP/1.1 302 "));
+//        assertThat(response,containsString("testLoginPage"));
         session = response.substring(response.indexOf("JSESSIONID=") + 11, response.indexOf(";Path=/ctx"));
 
         response = _connector.getResponses("POST /ctx/j_security_check HTTP/1.0\r\n" +
@@ -840,20 +1077,20 @@ public class ConstraintTest
                 "Content-Length: 36\r\n" +
                 "\r\n" +
                 "j_username=admin&j_password=password\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 302 "));
-        assertTrue(response.indexOf("Location") > 0);
-        assertTrue(response.indexOf("/ctx/auth/info") > 0);
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 302 "));
+        Assert.assertThat(response, Matchers.containsString("Location"));
+        Assert.assertThat(response, Matchers.containsString("/ctx/auth/info"));
         session = response.substring(response.indexOf("JSESSIONID=") + 11, response.indexOf(";Path=/ctx"));
 
         response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n" +
                 "Cookie: JSESSIONID=" + session + "\r\n" +
                 "\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 200 OK"));
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 200 OK"));
 
         response = _connector.getResponses("GET /ctx/admin/info HTTP/1.0\r\n" +
                 "Cookie: JSESSIONID=" + session + "\r\n" +
                 "\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 200 OK"));
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 200 OK"));
     }
 
     @Test
@@ -865,14 +1102,14 @@ public class ConstraintTest
         String response;
 
         response = _connector.getResponses("GET /ctx/noauth/info HTTP/1.0\r\n\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 200 OK"));
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 200 OK"));
 
         response = _connector.getResponses("GET /ctx/forbid/info HTTP/1.0\r\n\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 403 Forbidden"));
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 403 Forbidden"));
 
         response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\nHost:wibble.com:8888\r\n\r\n");
-        assertTrue(response.indexOf(" 302 Found") > 0);
-        assertTrue(response.indexOf("http://wibble.com:8888/ctx/testLoginPage") > 0);
+        Assert.assertThat(response, Matchers.containsString(" 302 Found"));
+        Assert.assertThat(response, Matchers.containsString("http://wibble.com:8888/ctx/testLoginPage"));
 
         String session = response.substring(response.indexOf("JSESSIONID=") + 11, response.indexOf(";Path=/ctx"));
 
@@ -882,37 +1119,37 @@ public class ConstraintTest
                 "Content-Length: 31\r\n" +
                 "\r\n" +
                 "j_username=user&j_password=wrong\r\n");
-        assertTrue(response.indexOf("Location") > 0);
+        Assert.assertThat(response, Matchers.containsString("Location"));
 
         response = _connector.getResponses("POST /ctx/j_security_check HTTP/1.0\r\n" +
                 "Cookie: JSESSIONID=" + session + "\r\n" +
                 "Content-Type: application/x-www-form-urlencoded\r\n" +
-                "Content-Length: 35\r\n" +
+                "Content-Length: 36\r\n" +
                 "\r\n" +
-                "j_username=user&j_password=password\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 302 "));
-        assertTrue(response.indexOf("Location") > 0);
-        assertTrue(response.indexOf("/ctx/auth/info") > 0);
+                "j_username=user3&j_password=password\r\n");
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 302 "));
+        Assert.assertThat(response, Matchers.containsString("Location"));
+        Assert.assertThat(response, Matchers.containsString("/ctx/auth/info"));
         session = response.substring(response.indexOf("JSESSIONID=") + 11, response.indexOf(";Path=/ctx"));
 
         response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n" +
                 "Cookie: JSESSIONID=" + session + "\r\n" +
                 "\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 403"));
-        assertTrue(response.indexOf("!role") > 0);
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 403"));
+        Assert.assertThat(response, Matchers.containsString("!role"));
 
         response = _connector.getResponses("GET /ctx/admin/info HTTP/1.0\r\n" +
                 "Cookie: JSESSIONID=" + session + "\r\n" +
                 "\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 403"));
-        assertTrue(response.indexOf("!role") > 0);
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 403"));
+        Assert.assertThat(response, Matchers.containsString("!role"));
 
 
 
         // log in again as user2
         response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 302 "));
-        assertTrue(response.indexOf("testLoginPage") > 0);
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 302 "));
+        Assert.assertThat(response, Matchers.containsString("testLoginPage"));
         session = response.substring(response.indexOf("JSESSIONID=") + 11, response.indexOf(";Path=/ctx"));
 
         response = _connector.getResponses("POST /ctx/j_security_check HTTP/1.0\r\n" +
@@ -921,29 +1158,52 @@ public class ConstraintTest
                 "Content-Length: 36\r\n" +
                 "\r\n" +
                 "j_username=user2&j_password=password\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 302 "));
-        assertTrue(response.indexOf("Location") > 0);
-        assertTrue(response.indexOf("/ctx/auth/info") > 0);
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 302 "));
+        Assert.assertThat(response, Matchers.containsString("Location"));
+        Assert.assertThat(response, Matchers.containsString("/ctx/auth/info"));
         session = response.substring(response.indexOf("JSESSIONID=") + 11, response.indexOf(";Path=/ctx"));
 
 
         response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n" +
                 "Cookie: JSESSIONID=" + session + "\r\n" +
                 "\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 200 OK"));
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 200 OK"));
 
+        //check user2 does not have right role to access /admin/*
         response = _connector.getResponses("GET /ctx/admin/info HTTP/1.0\r\n" +
                 "Cookie: JSESSIONID=" + session + "\r\n" +
                 "\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 403"));
-        assertTrue(response.indexOf("!role") > 0);
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 403"));
+        Assert.assertThat(response, Matchers.containsString("!role"));
+        
+        //log in as user3, who doesn't have a valid role, but we are checking a constraint
+        //of ** which just means they have to be authenticated
+        response = _connector.getResponses("GET /ctx/starstar/info HTTP/1.0\r\n\r\n");
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 302 "));
+        Assert.assertThat(response, Matchers.containsString("testLoginPage"));
+        session = response.substring(response.indexOf("JSESSIONID=") + 11, response.indexOf(";Path=/ctx"));
+
+        response = _connector.getResponses("POST /ctx/j_security_check HTTP/1.0\r\n" +
+                "Cookie: JSESSIONID=" + session + "\r\n" +
+                "Content-Type: application/x-www-form-urlencoded\r\n" +
+                "Content-Length: 36\r\n" +
+                "\r\n" +
+                "j_username=user3&j_password=password\r\n");
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 302 "));
+        Assert.assertThat(response, Matchers.containsString("Location"));
+        Assert.assertThat(response, Matchers.containsString("/ctx/starstar/info"));
+        session = response.substring(response.indexOf("JSESSIONID=") + 11, response.indexOf(";Path=/ctx"));
 
+        response = _connector.getResponses("GET /ctx/starstar/info HTTP/1.0\r\n" +
+                "Cookie: JSESSIONID=" + session + "\r\n" +
+                "\r\n");
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 200 OK"));
 
 
         // log in again as admin
         response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n\r\n");
-//        assertTrue(response.startsWith("HTTP/1.1 302 "));
-//        assertTrue(response.indexOf("testLoginPage") > 0);
+//        assertThat(response,startsWith("HTTP/1.1 302 "));
+//        assertThat(response,containsString("testLoginPage"));
         session = response.substring(response.indexOf("JSESSIONID=") + 11, response.indexOf(";Path=/ctx"));
 
         response = _connector.getResponses("POST /ctx/j_security_check HTTP/1.0\r\n" +
@@ -952,20 +1212,20 @@ public class ConstraintTest
                 "Content-Length: 36\r\n" +
                 "\r\n" +
                 "j_username=admin&j_password=password\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 302 "));
-        assertTrue(response.indexOf("Location") > 0);
-        assertTrue(response.indexOf("/ctx/auth/info") > 0);
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 302 "));
+        Assert.assertThat(response, Matchers.containsString("Location"));
+        Assert.assertThat(response, Matchers.containsString("/ctx/auth/info"));
         session = response.substring(response.indexOf("JSESSIONID=") + 11, response.indexOf(";Path=/ctx"));
 
         response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n" +
                 "Cookie: JSESSIONID=" + session + "\r\n" +
                 "\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 200 OK"));
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 200 OK"));
 
         response = _connector.getResponses("GET /ctx/admin/info HTTP/1.0\r\n" +
                 "Cookie: JSESSIONID=" + session + "\r\n" +
                 "\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 200 OK"));
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 200 OK"));
     }
 
 
@@ -979,35 +1239,35 @@ public class ConstraintTest
         String response;
 
         response = _connector.getResponses("GET /ctx/data/info HTTP/1.0\r\n\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 403"));
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 403"));
         
-        _connector.setConfidentialPort(8443);
-        _connector.setConfidentialScheme("https");
+        _config.setSecurePort(8443);
+        _config.setSecureScheme("https");
 
         response = _connector.getResponses("GET /ctx/data/info HTTP/1.0\r\n\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 302 Found"));
-        assertTrue(response.indexOf("Location") > 0);
-        assertTrue(response.indexOf(":8443/ctx/data/info") > 0);
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 302 Found"));
+        Assert.assertTrue(response.indexOf("Location") > 0);
+        Assert.assertTrue(response.indexOf(":8443/ctx/data/info") > 0);
 
-        _connector.setConfidentialPort(443);
+        _config.setSecurePort(443);
         response = _connector.getResponses("GET /ctx/data/info HTTP/1.0\r\n\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 302 Found"));
-        assertTrue(response.indexOf("Location") > 0);
-        assertTrue(response.indexOf(":443/ctx/data/info") < 0);
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 302 Found"));
+        Assert.assertTrue(response.indexOf("Location") > 0);
+        Assert.assertTrue(!response.contains(":443/ctx/data/info"));
 
-        _connector.setConfidentialPort(8443);
+        _config.setSecurePort(8443);
         response = _connector.getResponses("GET /ctx/data/info HTTP/1.0\r\nHost: wobble.com\r\n\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 302 Found"));
-        assertTrue(response.indexOf("Location") > 0);
-        assertTrue(response.indexOf("https://wobble.com:8443/ctx/data/info") > 0);
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 302 Found"));
+        Assert.assertTrue(response.indexOf("Location") > 0);
+        Assert.assertTrue(response.indexOf("https://wobble.com:8443/ctx/data/info") > 0);
 
-        _connector.setConfidentialPort(443);
+        _config.setSecurePort(443);
         response = _connector.getResponses("GET /ctx/data/info HTTP/1.0\r\nHost: wobble.com\r\n\r\n");
         System.err.println(response);
-        assertTrue(response.startsWith("HTTP/1.1 302 Found"));
-        assertTrue(response.indexOf("Location") > 0);
-        assertTrue(response.indexOf(":443") < 0);
-        assertTrue(response.indexOf("https://wobble.com/ctx/data/info") > 0);
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 302 Found"));
+        Assert.assertTrue(response.indexOf("Location") > 0);
+        Assert.assertTrue(!response.contains(":443"));
+        Assert.assertTrue(response.indexOf("https://wobble.com/ctx/data/info") > 0);
     }
     
     @Test
@@ -1016,21 +1276,22 @@ public class ConstraintTest
         RoleCheckHandler check=new RoleCheckHandler();
         _security.setHandler(check);
         _security.setAuthenticator(new BasicAuthenticator());
-        _security.setStrict(false);
+
         _server.start();
 
         String response;
-        response = _connector.getResponses("GET /ctx/noauth/info HTTP/1.0\r\n\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 200 OK"));
+        response = _connector.getResponses("GET /ctx/noauth/info HTTP/1.0\r\n\r\n", 100000, TimeUnit.MILLISECONDS);
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 200 OK"));
 
         response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n" +
                 "Authorization: Basic " + B64Code.encode("user2:password") + "\r\n" +
-                "\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 500 "));
+                "\r\n", 100000, TimeUnit.MILLISECONDS);
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 500 "));
 
         _server.stop();
 
         RoleRefHandler roleref = new RoleRefHandler();
+        roleref.setHandler(_security.getHandler());
         _security.setHandler(roleref);
         roleref.setHandler(check);
 
@@ -1038,56 +1299,55 @@ public class ConstraintTest
 
         response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n" +
                 "Authorization: Basic " + B64Code.encode("user2:password") + "\r\n" +
-                "\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 200 OK"));
+                "\r\n", 100000, TimeUnit.MILLISECONDS);
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 200 OK"));
     }
 
     @Test
     public void testDeferredBasic() throws Exception
     {
         _security.setAuthenticator(new BasicAuthenticator());
-        _security.setStrict(false);
         _server.start();
 
         String response;
 
         response = _connector.getResponses("GET /ctx/noauth/info HTTP/1.0\r\n"+
             "\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 200 OK"));
-        assertTrue(response.indexOf("user=null") > 0);
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 200 OK"));
+        Assert.assertThat(response, Matchers.containsString("user=null"));
 
         response = _connector.getResponses("GET /ctx/noauth/info HTTP/1.0\r\n"+
                 "Authorization: Basic " + B64Code.encode("admin:wrong") + "\r\n" +
             "\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 200 OK"));
-        assertTrue(response.indexOf("user=null") > 0);
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 200 OK"));
+        Assert.assertThat(response, Matchers.containsString("user=null"));
 
         response = _connector.getResponses("GET /ctx/noauth/info HTTP/1.0\r\n"+
                 "Authorization: Basic " + B64Code.encode("admin:password") + "\r\n" +
             "\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 200 OK"));
-        assertTrue(response.indexOf("user=admin") > 0);
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 200 OK"));
+        Assert.assertThat(response, Matchers.containsString("user=admin"));
     }
 
     @Test
     public void testRelaxedMethod() throws Exception
     {
         _security.setAuthenticator(new BasicAuthenticator());
-        _security.setStrict(false);
         _server.start();
 
         String response;
         response = _connector.getResponses("GET /ctx/forbid/somethig HTTP/1.0\r\n\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 403 "));
-        
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 403 "));
+
         response = _connector.getResponses("POST /ctx/forbid/post HTTP/1.0\r\n\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 200 "));
-        
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 200 "));
+
         response = _connector.getResponses("GET /ctx/forbid/post HTTP/1.0\r\n\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 200 "));  // This is so stupid, but it is the S P E C
+        Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 200 "));  // This is so stupid, but it is the S P E C
     }
     private class RequestHandler extends AbstractHandler
     {
+        @Override
         public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response ) throws IOException, ServletException
         {
             baseRequest.setHandled(true);
@@ -1119,19 +1379,22 @@ public class ConstraintTest
 
             UserIdentity.Scope scope = new UserIdentity.Scope()
             {
+                @Override
                 public String getContextPath()
                 {
                     return "/";
                 }
 
+                @Override
                 public String getName()
                 {
                     return "someServlet";
                 }
 
+                @Override
                 public Map<String, String> getRoleRefMap()
                 {
-                    Map<String, String> map = new HashMap<String, String>();
+                    Map<String, String> map = new HashMap<>();
                     map.put("untranslated", "user");
                     return map;
                 }
@@ -1152,6 +1415,7 @@ public class ConstraintTest
 
     private class RoleCheckHandler extends AbstractHandler
     {
+        @Override
         public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response ) throws IOException, ServletException
         {
             ((Request) request).setHandled(true);
diff --git a/jetty-security/src/test/java/org/eclipse/jetty/security/DataConstraintsTest.java b/jetty-security/src/test/java/org/eclipse/jetty/security/DataConstraintsTest.java
index 518cbb5..aec891a 100644
--- a/jetty-security/src/test/java/org/eclipse/jetty/security/DataConstraintsTest.java
+++ b/jetty-security/src/test/java/org/eclipse/jetty/security/DataConstraintsTest.java
@@ -18,21 +18,18 @@
 
 package org.eclipse.jetty.security;
 
-import static org.junit.Assert.assertThat;
-import static org.junit.matchers.JUnitMatchers.containsString;
-
 import java.io.IOException;
 import java.util.Arrays;
-
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import org.eclipse.jetty.http.HttpMethods;
-import org.eclipse.jetty.http.HttpSchemes;
-import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpScheme;
 import org.eclipse.jetty.security.authentication.BasicAuthenticator;
 import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.HttpConnectionFactory;
 import org.eclipse.jetty.server.LocalConnector;
 import org.eclipse.jetty.server.Request;
 import org.eclipse.jetty.server.Server;
@@ -41,13 +38,12 @@ import org.eclipse.jetty.server.handler.AbstractHandler;
 import org.eclipse.jetty.server.handler.ContextHandler;
 import org.eclipse.jetty.server.session.SessionHandler;
 import org.eclipse.jetty.util.security.Constraint;
+import org.hamcrest.Matchers;
 import org.junit.After;
+import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 
-/**
- * @version $Revision: 1441 $ $Date: 2010-04-02 12:28:17 +0200 (Fri, 02 Apr 2010) $
- */
 public class DataConstraintsTest
 {
     private Server _server;
@@ -60,33 +56,25 @@ public class DataConstraintsTest
     public  void startServer()
     {
         _server = new Server();
-        _connector = new LocalConnector();
-        _connector.setMaxIdleTime(300000);
-        _connector.setIntegralPort(9998);
-        _connector.setIntegralScheme("FTP");
-        _connector.setConfidentialPort(9999);
-        _connector.setConfidentialScheme("SPDY");
-        _connectorS = new LocalConnector()
+        
+        HttpConnectionFactory http = new HttpConnectionFactory();
+        http.getHttpConfiguration().setSecurePort(9999);
+        http.getHttpConfiguration().setSecureScheme("BWTP");
+        _connector = new LocalConnector(_server,http);
+        _connector.setIdleTimeout(300000);
+
+        HttpConnectionFactory https = new HttpConnectionFactory();
+        https.getHttpConfiguration().addCustomizer(new HttpConfiguration.Customizer()
         {
             @Override
-            public void customize(EndPoint endpoint, Request request) throws IOException
-            {
-                super.customize(endpoint,request);
-                request.setScheme(HttpSchemes.HTTPS);
-            }
-
-            @Override
-            public boolean isIntegral(Request request)
-            {
-                return true;
-            }
-
-            @Override
-            public boolean isConfidential(Request request)
+            public void customize(Connector connector, HttpConfiguration channelConfig, Request request)
             {
-                return true;
+                request.setScheme(HttpScheme.HTTPS.asString());
+                request.setSecure(true);
             }
-        };
+        });
+        
+        _connectorS = new LocalConnector(_server,https);
         _server.setConnectors(new Connector[]{_connector,_connectorS});
 
         ContextHandler _context = new ContextHandler();
@@ -101,6 +89,7 @@ public class DataConstraintsTest
 
         _security.setHandler(new AbstractHandler()
         {
+            @Override
             public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
             {
                 baseRequest.setHandled(true);
@@ -140,15 +129,15 @@ public class DataConstraintsTest
 
         String response;
         response = _connector.getResponses("GET /ctx/some/thing HTTP/1.0\r\n\r\n");
-        assertThat(response, containsString("HTTP/1.1 404 Not Found"));
+        Assert.assertThat(response, Matchers.containsString("HTTP/1.1 404 Not Found"));
 
         response = _connector.getResponses("GET /ctx/integral/info HTTP/1.0\r\n\r\n");
-        assertThat(response, containsString("HTTP/1.1 302 Found"));
-        assertThat(response, containsString("Location: FTP://"));
-        assertThat(response, containsString(":9998"));
+        Assert.assertThat(response, Matchers.containsString("HTTP/1.1 302 Found"));
+        Assert.assertThat(response, Matchers.containsString("Location: BWTP://"));
+        Assert.assertThat(response, Matchers.containsString(":9999"));
 
         response = _connectorS.getResponses("GET /ctx/integral/info HTTP/1.0\r\n\r\n");
-        assertThat(response, containsString("HTTP/1.1 404 Not Found"));
+        Assert.assertThat(response, Matchers.containsString("HTTP/1.1 404 Not Found"));
 
     }
 
@@ -172,15 +161,15 @@ public class DataConstraintsTest
 
         String response;
         response = _connector.getResponses("GET /ctx/some/thing HTTP/1.0\r\n\r\n");
-        assertThat(response, containsString("HTTP/1.1 404 Not Found"));
+        Assert.assertThat(response, Matchers.containsString("HTTP/1.1 404 Not Found"));
 
         response = _connector.getResponses("GET /ctx/confid/info HTTP/1.0\r\n\r\n");
-        assertThat(response, containsString("HTTP/1.1 302 Found"));
-        assertThat(response, containsString("Location: SPDY://"));
-        assertThat(response, containsString(":9999"));
+        Assert.assertThat(response, Matchers.containsString("HTTP/1.1 302 Found"));
+        Assert.assertThat(response, Matchers.containsString("Location: BWTP://"));
+        Assert.assertThat(response, Matchers.containsString(":9999"));
 
         response = _connectorS.getResponses("GET /ctx/confid/info HTTP/1.0\r\n\r\n");
-        assertThat(response, containsString("HTTP/1.1 404 Not Found"));
+        Assert.assertThat(response, Matchers.containsString("HTTP/1.1 404 Not Found"));
 
     }
 
@@ -204,10 +193,10 @@ public class DataConstraintsTest
         String response;
 
         response = _connector.getResponses("GET /ctx/confid/info HTTP/1.0\r\n\r\n");
-        assertThat(response, containsString("HTTP/1.1 302 Found"));
+        Assert.assertThat(response, Matchers.containsString("HTTP/1.1 302 Found"));
 
         response = _connectorS.getResponses("GET /ctx/confid/info HTTP/1.0\r\n\r\n");
-        assertThat(response, containsString("HTTP/1.1 404 Not Found"));
+        Assert.assertThat(response, Matchers.containsString("HTTP/1.1 404 Not Found"));
 
     }
 
@@ -219,7 +208,7 @@ public class DataConstraintsTest
         constraint0.setDataConstraint(Constraint.DC_CONFIDENTIAL);
         ConstraintMapping mapping0 = new ConstraintMapping();
         mapping0.setPathSpec("/confid/*");
-        mapping0.setMethod(HttpMethods.POST);
+        mapping0.setMethod(HttpMethod.POST.asString());
         mapping0.setConstraint(constraint0);
 
         _security.setConstraintMappings(Arrays.asList(new ConstraintMapping[]
@@ -232,16 +221,16 @@ public class DataConstraintsTest
         String response;
 
         response = _connector.getResponses("GET /ctx/confid/info HTTP/1.0\r\n\r\n");
-        assertThat(response, containsString("HTTP/1.1 404 Not Found"));
+        Assert.assertThat(response, Matchers.containsString("HTTP/1.1 404 Not Found"));
 
         response = _connectorS.getResponses("GET /ctx/confid/info HTTP/1.0\r\n\r\n");
-        assertThat(response, containsString("HTTP/1.1 404 Not Found"));
+        Assert.assertThat(response, Matchers.containsString("HTTP/1.1 404 Not Found"));
 
         response = _connector.getResponses("POST /ctx/confid/info HTTP/1.0\r\n\r\n");
-        assertThat(response, containsString("HTTP/1.1 302 Found"));
+        Assert.assertThat(response, Matchers.containsString("HTTP/1.1 302 Found"));
 
         response = _connectorS.getResponses("POST /ctx/confid/info HTTP/1.0\r\n\r\n");
-        assertThat(response, containsString("HTTP/1.1 404 Not Found"));
+        Assert.assertThat(response, Matchers.containsString("HTTP/1.1 404 Not Found"));
 
     }
     @Test
@@ -253,7 +242,7 @@ public class DataConstraintsTest
         constraint0.setDataConstraint(Constraint.DC_CONFIDENTIAL);
         ConstraintMapping mapping0 = new ConstraintMapping();
         mapping0.setPathSpec("/confid/*");
-        mapping0.setMethod(HttpMethods.POST);
+        mapping0.setMethod(HttpMethod.POST.asString());
         mapping0.setConstraint(constraint0);
 
         _security.setConstraintMappings(Arrays.asList(new ConstraintMapping[]
@@ -266,16 +255,16 @@ public class DataConstraintsTest
         String response;
 
         response = _connector.getResponses("GET /ctx/confid/info HTTP/1.0\r\n\r\n");
-        assertThat(response, containsString("HTTP/1.1 404 Not Found"));
+        Assert.assertThat(response, Matchers.containsString("HTTP/1.1 404 Not Found"));
 
         response = _connectorS.getResponses("GET /ctx/confid/info HTTP/1.0\r\n\r\n");
-        assertThat(response, containsString("HTTP/1.1 404 Not Found"));
+        Assert.assertThat(response, Matchers.containsString("HTTP/1.1 404 Not Found"));
 
         response = _connector.getResponses("POST /ctx/confid/info HTTP/1.0\r\n\r\n");
-        assertThat(response, containsString("HTTP/1.1 302 Found"));
+        Assert.assertThat(response, Matchers.containsString("HTTP/1.1 302 Found"));
 
         response = _connectorS.getResponses("POST /ctx/confid/info HTTP/1.0\r\n\r\n");
-        assertThat(response, containsString("HTTP/1.1 404 Not Found"));
+        Assert.assertThat(response, Matchers.containsString("HTTP/1.1 404 Not Found"));
 
     }
 
@@ -289,7 +278,7 @@ public class DataConstraintsTest
         constraint0.setDataConstraint(Constraint.DC_CONFIDENTIAL);
         ConstraintMapping mapping0 = new ConstraintMapping();
         mapping0.setPathSpec("/confid/*");
-        mapping0.setMethod(HttpMethods.POST);
+        mapping0.setMethod(HttpMethod.POST.asString());
         mapping0.setConstraint(constraint0);
 
         _security.setConstraintMappings(Arrays.asList(new ConstraintMapping[]
@@ -305,25 +294,25 @@ public class DataConstraintsTest
         String response;
 
         response = _connector.getResponses("GET /ctx/confid/info HTTP/1.0\r\n\r\n");
-        assertThat(response, containsString("HTTP/1.1 404 Not Found"));
+        Assert.assertThat(response, Matchers.containsString("HTTP/1.1 404 Not Found"));
 
         response = _connectorS.getResponses("GET /ctx/confid/info HTTP/1.0\r\n\r\n");
-        assertThat(response, containsString("HTTP/1.1 404 Not Found"));
+        Assert.assertThat(response, Matchers.containsString("HTTP/1.1 404 Not Found"));
 
         response = _connector.getResponses("POST /ctx/confid/info HTTP/1.0\r\n\r\n");
-        assertThat(response, containsString("HTTP/1.1 302 Found"));
+        Assert.assertThat(response, Matchers.containsString("HTTP/1.1 302 Found"));
 
         response = _connectorS.getResponses("POST /ctx/confid/info HTTP/1.0\r\n\r\n");
-        assertThat(response, containsString("HTTP/1.1 401 Unauthorized"));
+        Assert.assertThat(response, Matchers.containsString("HTTP/1.1 401 Unauthorized"));
 
         response = _connector.getResponses("GET /ctx/confid/info HTTP/1.0\r\nAuthorization: Basic YWRtaW46cGFzc3dvcmQ=\r\n\r\n");
-        assertThat(response, containsString("HTTP/1.1 404 Not Found"));
+        Assert.assertThat(response, Matchers.containsString("HTTP/1.1 404 Not Found"));
 
         response = _connector.getResponses("POST /ctx/confid/info HTTP/1.0\r\nAuthorization: Basic YWRtaW46cGFzc3dvcmQ=\r\n\r\n");
-        assertThat(response, containsString("HTTP/1.1 302 Found"));
+        Assert.assertThat(response, Matchers.containsString("HTTP/1.1 302 Found"));
 
         response = _connectorS.getResponses("POST /ctx/confid/info HTTP/1.0\r\nAuthorization: Basic YWRtaW46cGFzc3dvcmQ=\r\n\r\n");
-        assertThat(response, containsString("HTTP/1.1 404 Not Found"));
+        Assert.assertThat(response, Matchers.containsString("HTTP/1.1 404 Not Found"));
 
     }
 
@@ -347,16 +336,16 @@ public class DataConstraintsTest
         String response;
 
         response = _connector.getResponses("GET /ctx/restricted/info HTTP/1.0\r\n\r\n");
-        assertThat(response, containsString("HTTP/1.1 403 Forbidden"));
+        Assert.assertThat(response, Matchers.containsString("HTTP/1.1 403 Forbidden"));
 
         response = _connectorS.getResponses("GET /ctx/restricted/info HTTP/1.0\r\n\r\n");
-        assertThat(response, containsString("HTTP/1.1 403 Forbidden"));
+        Assert.assertThat(response, Matchers.containsString("HTTP/1.1 403 Forbidden"));
 
-        response = _connector.getResponses("GET /ctx/restricted/info HTTP/1.0\r\n Authorization: Basic YWRtaW46cGFzc3dvcmQ=\r\n\r\n");
-        assertThat(response, containsString("HTTP/1.1 403 Forbidden"));
+        response = _connector.getResponses("GET /ctx/restricted/info HTTP/1.0\r\nAuthorization: Basic YWRtaW46cGFzc3dvcmQ=\r\n\r\n");
+        Assert.assertThat(response, Matchers.containsString("HTTP/1.1 403 Forbidden"));
 
-        response = _connectorS.getResponses("GET /ctx/restricted/info HTTP/1.0\r\n Authorization: Basic YWRtaW46cGFzc3dvcmQ=\r\n\r\n");
-        assertThat(response, containsString("HTTP/1.1 403 Forbidden"));
+        response = _connectorS.getResponses("GET /ctx/restricted/info HTTP/1.0\r\nAuthorization: Basic YWRtaW46cGFzc3dvcmQ=\r\n\r\n");
+        Assert.assertThat(response, Matchers.containsString("HTTP/1.1 403 Forbidden"));
 
     }
 
@@ -381,16 +370,16 @@ public class DataConstraintsTest
         String response;
 
         response = _connector.getResponses("GET /ctx/restricted/info HTTP/1.0\r\n\r\n");
-        assertThat(response, containsString("HTTP/1.1 403 Forbidden"));
+        Assert.assertThat(response, Matchers.containsString("HTTP/1.1 403 Forbidden"));
 
         response = _connectorS.getResponses("GET /ctx/restricted/info HTTP/1.0\r\n\r\n");
-        assertThat(response, containsString("HTTP/1.1 403 Forbidden"));
+        Assert.assertThat(response, Matchers.containsString("HTTP/1.1 403 Forbidden"));
 
         response = _connector.getResponses("GET /ctx/restricted/info HTTP/1.0\r\n Authorization: Basic YWRtaW46cGFzc3dvcmQ=\r\n\r\n");
-        assertThat(response, containsString("HTTP/1.1 403 Forbidden"));
+        Assert.assertThat(response, Matchers.containsString("HTTP/1.1 403 Forbidden"));
 
         response = _connectorS.getResponses("GET /ctx/restricted/info HTTP/1.0\r\n Authorization: Basic YWRtaW46cGFzc3dvcmQ=\r\n\r\n");
-        assertThat(response, containsString("HTTP/1.1 403 Forbidden"));
+        Assert.assertThat(response, Matchers.containsString("HTTP/1.1 403 Forbidden"));
 
     }
 
@@ -419,16 +408,16 @@ public class DataConstraintsTest
         String response;
 
         response = _connector.getResponses("GET /ctx/restricted/info HTTP/1.0\r\n\r\n");
-        assertThat(response, containsString("HTTP/1.1 401 Unauthorized"));
+        Assert.assertThat(response, Matchers.containsString("HTTP/1.1 401 Unauthorized"));
 
         response = _connectorS.getResponses("GET /ctx/restricted/info HTTP/1.0\r\n\r\n");
-        assertThat(response, containsString("HTTP/1.1 401 Unauthorized"));
+        Assert.assertThat(response, Matchers.containsString("HTTP/1.1 401 Unauthorized"));
 
         response = _connector.getResponses("GET /ctx/restricted/info HTTP/1.0\nAuthorization: Basic YWRtaW46cGFzc3dvcmQ=\n\n");
-        assertThat(response, containsString("HTTP/1.1 404 Not Found"));
+        Assert.assertThat(response, Matchers.containsString("HTTP/1.1 404 Not Found"));
 
         response = _connectorS.getResponses("GET /ctx/restricted/info HTTP/1.0\nAuthorization: Basic YWRtaW46cGFzc3dvcmQ=\n\n");
-        assertThat(response, containsString("HTTP/1.1 404 Not Found"));
+        Assert.assertThat(response, Matchers.containsString("HTTP/1.1 404 Not Found"));
 
     }
 
@@ -439,11 +428,14 @@ public class DataConstraintsTest
         {
             this.identityService = identityService;
         }
+        
+        @Override
         public String getName()
         {
             return "name";
         }
 
+        @Override
         public UserIdentity login(String username, Object credentials)
         {
             if("admin".equals(username) && "password".equals(credentials))
@@ -451,20 +443,24 @@ public class DataConstraintsTest
             return null;
         }
 
+        @Override
         public boolean validate(UserIdentity user)
         {
             return false;
         }
 
+        @Override
         public IdentityService getIdentityService()
         {
             return identityService;
         }
 
+        @Override
         public void setIdentityService(IdentityService service)
         {
         }
 
+        @Override
         public void logout(UserIdentity user)
         {
         }
diff --git a/jetty-security/src/test/java/org/eclipse/jetty/security/PropertyUserStoreTest.java b/jetty-security/src/test/java/org/eclipse/jetty/security/PropertyUserStoreTest.java
index ea60f72..e57c9ec 100644
--- a/jetty-security/src/test/java/org/eclipse/jetty/security/PropertyUserStoreTest.java
+++ b/jetty-security/src/test/java/org/eclipse/jetty/security/PropertyUserStoreTest.java
@@ -21,14 +21,14 @@ package org.eclipse.jetty.security;
 import java.io.BufferedWriter;
 import java.io.File;
 import java.io.FileWriter;
+import java.io.Writer;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicInteger;
 
-import junit.framework.Assert;
-
 import org.eclipse.jetty.util.security.Credential;
 import org.junit.After;
+import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -56,19 +56,21 @@ public class PropertyUserStoreTest
 
     private void writeInitialUsers(String testFile) throws Exception
     {
-        BufferedWriter writer = new BufferedWriter(new FileWriter(testFile));
-        writer.append("tom: tom, roleA\n");
-        writer.append("dick: dick, roleB\n");
-        writer.append("harry: harry, roleA, roleB\n");
-        writer.close();
+        try (Writer writer = new BufferedWriter(new FileWriter(testFile)))
+        {
+            writer.append("tom: tom, roleA\n");
+            writer.append("dick: dick, roleB\n");
+            writer.append("harry: harry, roleA, roleB\n");
+        }
     }
 
     private void writeAdditionalUser(String testFile) throws Exception
     {
         Thread.sleep(1001);
-        BufferedWriter writer = new BufferedWriter(new FileWriter(testFile,true));
-        writer.append("skip: skip, roleA\n");
-        writer.close();
+        try (Writer writer = new BufferedWriter(new FileWriter(testFile,true)))
+        {
+            writer.append("skip: skip, roleA\n");
+        }
     }
 
     @Test
@@ -141,7 +143,7 @@ public class PropertyUserStoreTest
         {
             Thread.sleep(10);
         }
-        
+
         Assert.assertNotNull("Failed to retrieve UserIdentity from PropertyUserStore directly", store.getUserIdentity("skip"));
         Assert.assertEquals(4,userCount.get());
 
diff --git a/jetty-security/src/test/java/org/eclipse/jetty/security/SpecExampleConstraintTest.java b/jetty-security/src/test/java/org/eclipse/jetty/security/SpecExampleConstraintTest.java
index 7e58793..007d5ed 100644
--- a/jetty-security/src/test/java/org/eclipse/jetty/security/SpecExampleConstraintTest.java
+++ b/jetty-security/src/test/java/org/eclipse/jetty/security/SpecExampleConstraintTest.java
@@ -18,6 +18,9 @@
 
 package org.eclipse.jetty.security;
 
+import static org.hamcrest.Matchers.startsWith;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
 
 import java.io.IOException;
@@ -60,7 +63,7 @@ public class SpecExampleConstraintTest
     public static void startServer()
     {
         _server = new Server();
-        _connector = new LocalConnector();
+        _connector = new LocalConnector(_server);
         _server.setConnectors(new Connector[]{_connector});
 
         ContextHandler _context = new ContextHandler();
@@ -230,6 +233,45 @@ public class SpecExampleConstraintTest
         }
     }
 
+    @Test
+    public void testUncoveredHttpMethodDetection() throws Exception
+    {
+        _security.setAuthenticator(new BasicAuthenticator());
+        _server.start();
+
+       Set<String> paths =  _security.getPathsWithUncoveredHttpMethods();
+       assertEquals(1, paths.size());
+       assertEquals("/*", paths.iterator().next());
+    }
+    
+    @Test
+    public void testUncoveredHttpMethodsDenied() throws Exception
+    {
+        try
+        {
+            _security.setDenyUncoveredHttpMethods(false);
+            _security.setAuthenticator(new BasicAuthenticator());
+            _server.start();
+            
+            //There are uncovered methods for GET/POST at url /*
+            //without deny-uncovered-http-methods they should be accessible
+            String response;
+            response = _connector.getResponses("GET /ctx/index.html HTTP/1.0\r\n\r\n");
+            assertThat(response,startsWith("HTTP/1.1 200 OK"));   
+            
+            //set deny-uncovered-http-methods true
+            _security.setDenyUncoveredHttpMethods(true);
+            
+            //check they cannot be accessed
+            response = _connector.getResponses("GET /ctx/index.html HTTP/1.0\r\n\r\n");
+            assertTrue(response.startsWith("HTTP/1.1 403 Forbidden"));
+        }
+        finally
+        {
+            _security.setDenyUncoveredHttpMethods(false);
+        }
+        
+    }
   
 
     @Test
@@ -237,7 +279,6 @@ public class SpecExampleConstraintTest
     {
         
         _security.setAuthenticator(new BasicAuthenticator());
-        _security.setStrict(false);
         _server.start();
 
         String response;
@@ -258,42 +299,42 @@ public class SpecExampleConstraintTest
         response = _connector.getResponses("HEAD /ctx/index.html HTTP/1.0\r\n" +
                 "Authorization: Basic " + B64Code.encode("harry:password") + "\r\n" +
                 "\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 403 Forbidden"));
+        assertThat(response,startsWith("HTTP/1.1 403 Forbidden"));
 
         response = _connector.getResponses("HEAD /ctx/acme/wholesale/index.html HTTP/1.0\r\n" +
                                            "Authorization: Basic " + B64Code.encode("harry:password") + "\r\n" +
                                            "\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 403 Forbidden"));
+        assertThat(response,startsWith("HTTP/1.1 403 Forbidden"));
         
         response = _connector.getResponses("HEAD /ctx/acme/retail/index.html HTTP/1.0\r\n" +
                                            "Authorization: Basic " + B64Code.encode("harry:password") + "\r\n" +
                                            "\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 403 Forbidden"));
+        assertThat(response,startsWith("HTTP/1.1 403 Forbidden"));
         
         //a user in role CONTRACTOR can do a GET
         response = _connector.getResponses("GET /ctx/acme/wholesale/index.html HTTP/1.0\r\n" +
                                            "Authorization: Basic " + B64Code.encode("chris:password") + "\r\n" +
                                            "\r\n");
-        
-        assertTrue(response.startsWith("HTTP/1.1 200 OK"));
+
+        assertThat(response,startsWith("HTTP/1.1 200 OK"));
         
         //a user in role CONTRACTOR can only do a post if confidential
         response = _connector.getResponses("POST /ctx/acme/wholesale/index.html HTTP/1.0\r\n" +
                                            "Authorization: Basic " + B64Code.encode("chris:password") + "\r\n" +
                                            "\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 403 !Confidential"));
-        
+        assertThat(response,startsWith("HTTP/1.1 403 !"));
         
         //a user in role HOMEOWNER can do a GET
         response = _connector.getResponses("GET /ctx/acme/retail/index.html HTTP/1.0\r\n" +
                                            "Authorization: Basic " + B64Code.encode("harry:password") + "\r\n" +
                                            "\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 200 OK"));   
+        assertThat(response,startsWith("HTTP/1.1 200 OK"));   
     }
     
   
     private class RequestHandler extends AbstractHandler
     {
+        @Override
         public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response ) throws IOException, ServletException
         {
             baseRequest.setHandled(true);
diff --git a/jetty-server/pom.xml b/jetty-server/pom.xml
index e388573..0b0dbf3 100644
--- a/jetty-server/pom.xml
+++ b/jetty-server/pom.xml
@@ -2,7 +2,7 @@
   <parent>
     <groupId>org.eclipse.jetty</groupId>
     <artifactId>jetty-project</artifactId>
-    <version>8.1.17.v20150415</version>
+    <version>9.2.13.v20150730</version>
   </parent>
   <modelVersion>4.0.0</modelVersion>
   <artifactId>jetty-server</artifactId>
@@ -26,7 +26,8 @@
             </goals>
             <configuration>
               <instructions>
-                <Import-Package>javax.servlet.*;version="2.6.0",org.eclipse.jetty.jmx.*;version="8.0";resolution:=optional,*</Import-Package>
+                <Import-Package>javax.servlet.*;version="[2.6.0,3.2)",org.eclipse.jetty.jmx.*;version="9.1";resolution:=optional,*</Import-Package>
+                <_nouses>true</_nouses>
               </instructions>
             </configuration>
           </execution>
@@ -88,21 +89,31 @@
       <scope>test</scope>
     </dependency>
     <dependency>
+      <groupId>javax.servlet</groupId>
+      <artifactId>javax.servlet-api</artifactId>
+<!--
       <groupId>org.eclipse.jetty.orbit</groupId>
       <artifactId>javax.servlet</artifactId>
+-->
     </dependency>
     <dependency>
       <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-continuation</artifactId>
+      <artifactId>jetty-http</artifactId>
       <version>${project.version}</version>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-http</artifactId>
+      <artifactId>jetty-io</artifactId>
       <version>${project.version}</version>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-xml</artifactId>
+      <version>${project.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
       <artifactId>jetty-jmx</artifactId>
       <version>${project.version}</version>
       <optional>true</optional>
diff --git a/jetty-server/src/main/config/etc/home-base-warning.xml b/jetty-server/src/main/config/etc/home-base-warning.xml
new file mode 100644
index 0000000..b83f36f
--- /dev/null
+++ b/jetty-server/src/main/config/etc/home-base-warning.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0"?>
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
+
+<!-- ============================================================= -->
+<!-- Display a Warning Message if {jetty.home} == {jetty.base}     -->
+<!-- ============================================================= -->
+<Configure id="homeBaseWarning" class="org.eclipse.jetty.server.HomeBaseWarning">
+</Configure>
\ No newline at end of file
diff --git a/jetty-server/src/main/config/etc/jetty-bio-ssl.xml b/jetty-server/src/main/config/etc/jetty-bio-ssl.xml
deleted file mode 100644
index 3386b7b..0000000
--- a/jetty-server/src/main/config/etc/jetty-bio-ssl.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0"?>
-<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
-
-<!-- =============================================================== -->
-<!-- Configure SSL for the Jetty Server                              -->
-<!-- this configuration file should be used in combination with      -->
-<!-- other configuration files.  e.g.                                -->
-<!--    java -jar start.jar etc/jetty-ssl.xml                        -->
-<!-- =============================================================== -->
-<Configure id="Server" class="org.eclipse.jetty.server.Server">
-
-  <Call name="addConnector">
-    <Arg>
-      <New class="org.eclipse.jetty.server.ssl.SslSocketConnector">
-	<Set name="Port">9443</Set>
-	<Set name="maxIdleTime">30000</Set>
-	<Set name="Keystore"><Property name="jetty.home" default="." />/etc/keystore</Set>
-	<Set name="Password">OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4</Set>
-	<Set name="KeyPassword">OBF:1u2u1wml1z7s1z7a1wnl1u2g</Set>
-        <Set name="truststore"><Property name="jetty.home" default="." />/etc/keystore</Set>
-        <Set name="trustPassword">OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4</Set>
-      </New>
-    </Arg>
-  </Call>
-</Configure>
diff --git a/jetty-server/src/main/config/etc/jetty-bio.xml b/jetty-server/src/main/config/etc/jetty-bio.xml
deleted file mode 100644
index 66950ee..0000000
--- a/jetty-server/src/main/config/etc/jetty-bio.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0"?>
-<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
-
-<!-- =============================================================== -->
-<!-- Mixin configuration for Block socket connector                  -->
-<!--                                                                 -->
-<!-- =============================================================== -->
-
-
-<Configure id="Server" class="org.eclipse.jetty.server.Server">
-
-    <!-- Use this connector if NIO is not available.  -->
-    <Call name="addConnector">
-      <Arg>
-          <New class="org.eclipse.jetty.server.bio.SocketConnector">
-            <Set name="port"><Property name="jetty.bio.port" default="8081"/></Set>
-            <Set name="maxIdleTime">50000</Set>
-            <Set name="lowResourceMaxIdleTime">1500</Set>
-          </New>
-      </Arg>
-    </Call>
-
-</Configure>
diff --git a/jetty-server/src/main/config/etc/jetty-debug.xml b/jetty-server/src/main/config/etc/jetty-debug.xml
index 6d66953..52b4bdb 100644
--- a/jetty-server/src/main/config/etc/jetty-debug.xml
+++ b/jetty-server/src/main/config/etc/jetty-debug.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0"?>
-<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
 
 <!-- =============================================================== -->
 <!-- Mixin the DebugHandler                                          -->
@@ -10,7 +10,7 @@
     <Get id="oldhandler" name="handler"/>
     <Set name="handler">
       <New id="DebugHandler" class="org.eclipse.jetty.server.handler.DebugHandler">
-        <Set name="handler"><Ref id="oldhandler"/></Set>
+        <Set name="handler"><Ref refid="oldhandler"/></Set>
 	<Set name="outputStream">
 	  <New class="org.eclipse.jetty.util.RolloverFileOutputStream">
 	    <Arg type="String"><Property name="jetty.logs" default="./logs"/>/yyyy_mm_dd.debug.log</Arg>
diff --git a/jetty-server/src/main/config/etc/jetty-fileserver.xml b/jetty-server/src/main/config/etc/jetty-fileserver.xml
deleted file mode 100644
index de15b38..0000000
--- a/jetty-server/src/main/config/etc/jetty-fileserver.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?xml version="1.0"?>
-<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
-
-
-<Configure id="FileServer" class="org.eclipse.jetty.server.Server">
-
-    <Call name="addConnector">
-      <Arg>
-          <New class="org.eclipse.jetty.server.nio.SelectChannelConnector">
-            <Set name="port">8080</Set>
-          </New>
-      </Arg>
-    </Call>
-
-    <Set name="handler">
-      <New class="org.eclipse.jetty.server.handler.HandlerList">
-        <Set name="handlers">
-	  <Array type="org.eclipse.jetty.server.Handler">
-	    <Item>
-	      <New class="org.eclipse.jetty.server.handler.ResourceHandler">
-	        <Set name="directoriesListed">true</Set>
-		<Set name="welcomeFiles">
-		  <Array type="String"><Item>index.html</Item></Array>
-		</Set>
-	        <Set name="resourceBase">.</Set>
-	      </New>
-	    </Item>
-	    <Item>
-	      <New class="org.eclipse.jetty.server.handler.DefaultHandler">
-	      </New>
-	    </Item>
-	  </Array>
-        </Set>
-      </New>
-    </Set>
-    
-</Configure>
diff --git a/jetty-server/src/main/config/etc/jetty-http.xml b/jetty-server/src/main/config/etc/jetty-http.xml
new file mode 100644
index 0000000..1459ba8
--- /dev/null
+++ b/jetty-server/src/main/config/etc/jetty-http.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0"?>
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
+
+<!-- ============================================================= -->
+<!-- Configure the Jetty Server instance with an ID "Server"       -->
+<!-- by adding a HTTP connector.                                   -->
+<!-- This configuration must be used in conjunction with jetty.xml -->
+<!-- ============================================================= -->
+<Configure id="Server" class="org.eclipse.jetty.server.Server">
+
+  <!-- =========================================================== -->
+  <!-- Add a HTTP Connector.                                       -->
+  <!-- Configure an o.e.j.server.ServerConnector with a single     -->
+  <!-- HttpConnectionFactory instance using the common httpConfig  -->
+  <!-- instance defined in jetty.xml                               -->
+  <!--                                                             -->
+  <!-- Consult the javadoc of o.e.j.server.ServerConnector and     -->
+  <!-- o.e.j.server.HttpConnectionFactory for all configuration    -->
+  <!-- that may be set here.                                       -->
+  <!-- =========================================================== -->
+  <Call name="addConnector">
+    <Arg>
+      <New class="org.eclipse.jetty.server.ServerConnector">
+        <Arg name="server"><Ref refid="Server" /></Arg>
+        <Arg name="acceptors" type="int"><Property name="http.acceptors" default="-1"/></Arg>
+        <Arg name="selectors" type="int"><Property name="http.selectors" default="-1"/></Arg>
+        <Arg name="factories">
+          <Array type="org.eclipse.jetty.server.ConnectionFactory">
+            <Item>
+              <New class="org.eclipse.jetty.server.HttpConnectionFactory">
+                <Arg name="config"><Ref refid="httpConfig" /></Arg>
+              </New>
+            </Item>
+          </Array>
+        </Arg>
+        <Set name="host"><Property name="jetty.host" /></Set>
+        <Set name="port"><Property name="jetty.port" default="80" /></Set>
+        <Set name="idleTimeout"><Property name="http.timeout" default="30000"/></Set>
+        <Set name="soLingerTime"><Property name="http.soLingerTime" default="-1"/></Set>
+        <Set name="acceptorPriorityDelta"><Property name="http.acceptorPriorityDelta" default="0"/></Set>
+        <Set name="selectorPriorityDelta"><Property name="http.selectorPriorityDelta" default="0"/></Set>
+        <Set name="acceptQueueSize"><Property name="http.acceptQueueSize" default="0"/></Set>
+      </New>
+    </Arg>
+  </Call>
+
+</Configure>
diff --git a/jetty-server/src/main/config/etc/jetty-https.xml b/jetty-server/src/main/config/etc/jetty-https.xml
new file mode 100644
index 0000000..5eab5c5
--- /dev/null
+++ b/jetty-server/src/main/config/etc/jetty-https.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0"?>
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
+
+<!-- ============================================================= -->
+<!-- Configure a HTTPS connector.                                  -->
+<!-- This configuration must be used in conjunction with jetty.xml -->
+<!-- and jetty-ssl.xml.                                            -->
+<!-- ============================================================= -->
+<Configure id="Server" class="org.eclipse.jetty.server.Server">
+
+  <!-- =========================================================== -->
+  <!-- Add a HTTPS Connector.                                      -->
+  <!-- Configure an o.e.j.server.ServerConnector with connection   -->
+  <!-- factories for TLS (aka SSL) and HTTP to provide HTTPS.      -->
+  <!-- All accepted TLS connections are wired to a HTTP connection.-->
+  <!--                                                             -->
+  <!-- Consult the javadoc of o.e.j.server.ServerConnector,        -->
+  <!-- o.e.j.server.SslConnectionFactory and                       -->
+  <!-- o.e.j.server.HttpConnectionFactory for all configuration    -->
+  <!-- that may be set here.                                       -->
+  <!-- =========================================================== -->
+  <Call id="httpsConnector" name="addConnector">
+    <Arg>
+      <New class="org.eclipse.jetty.server.ServerConnector">
+        <Arg name="server"><Ref refid="Server" /></Arg>
+        <Arg name="acceptors" type="int"><Property name="ssl.acceptors" default="-1"/></Arg>
+        <Arg name="selectors" type="int"><Property name="ssl.selectors" default="-1"/></Arg>
+        <Arg name="factories">
+          <Array type="org.eclipse.jetty.server.ConnectionFactory">
+            <Item>
+              <New class="org.eclipse.jetty.server.SslConnectionFactory">
+                <Arg name="next">http/1.1</Arg>
+                <Arg name="sslContextFactory"><Ref refid="sslContextFactory"/></Arg>
+              </New>
+            </Item>
+            <Item>
+              <New class="org.eclipse.jetty.server.HttpConnectionFactory">
+                <Arg name="config"><Ref refid="sslHttpConfig"/></Arg>
+              </New>
+            </Item>
+          </Array>
+        </Arg>
+        <Set name="host"><Property name="jetty.host" /></Set>
+        <Set name="port"><Property name="https.port" default="443" /></Set>
+        <Set name="idleTimeout"><Property name="https.timeout" default="30000"/></Set>
+        <Set name="soLingerTime"><Property name="https.soLingerTime" default="-1"/></Set>
+        <Set name="acceptorPriorityDelta"><Property name="ssl.acceptorPriorityDelta" default="0"/></Set>
+        <Set name="selectorPriorityDelta"><Property name="ssl.selectorPriorityDelta" default="0"/></Set>
+        <Set name="acceptQueueSize"><Property name="https.acceptQueueSize" default="0"/></Set>
+      </New>
+    </Arg>
+  </Call>
+</Configure>
diff --git a/jetty-server/src/main/config/etc/jetty-ipaccess.xml b/jetty-server/src/main/config/etc/jetty-ipaccess.xml
index d5fb5f8..a44aa1d 100644
--- a/jetty-server/src/main/config/etc/jetty-ipaccess.xml
+++ b/jetty-server/src/main/config/etc/jetty-ipaccess.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0"?>
-<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
 
 <!-- =============================================================== -->
 <!-- Mixin the Statistics Handler                                    -->
@@ -12,20 +12,20 @@
 
     <Set name="handler">
      <New id="IPAccessHandler" class="org.eclipse.jetty.server.handler.IPAccessHandler">
-      <Set name="handler"><Ref id="oldhandler"/></Set>
+      <Set name="handler"><Ref refid="oldhandler"/></Set>
       <Set name="white">
         <Array type="String">
-	  <Item>127.0.0.1</Item>
-	  <Item>127.0.0.2/*.html</Item>
-	</Array>
+	      <Item>127.0.0.1</Item>
+	      <Item>127.0.0.2/*.html</Item>
+	    </Array>
       </Set>
       <Set name="black">
         <Array type="String">
-	  <Item>127.0.0.1/blacklisted</Item>
-	  <Item>127.0.0.2/black.html</Item>
-	</Array>
+	      <Item>127.0.0.1/blacklisted</Item>
+	      <Item>127.0.0.2/black.html</Item>
+	    </Array>
       </Set>
+      <Set name="whiteListByPath">false</Set>
      </New>
     </Set>
-    
 </Configure>
diff --git a/jetty-server/src/main/config/etc/jetty-lowresources.xml b/jetty-server/src/main/config/etc/jetty-lowresources.xml
new file mode 100644
index 0000000..060919a
--- /dev/null
+++ b/jetty-server/src/main/config/etc/jetty-lowresources.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0"?>
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
+
+<!-- =============================================================== -->
+<!-- Mixin the Low Resources Monitor                                 -->
+<!-- =============================================================== -->
+
+<Configure id="Server" class="org.eclipse.jetty.server.Server">
+  <Call name="addBean">
+    <Arg>
+      <New class="org.eclipse.jetty.server.LowResourceMonitor">
+        <Arg name="server"><Ref refid='Server'/></Arg>
+        <Set name="period"><Property name="lowresources.period" default="1000"/></Set>
+        <Set name="lowResourcesIdleTimeout"><Property name="lowresources.lowResourcesIdleTimeout" default="200"/></Set>
+        <Set name="monitorThreads"><Property name="lowresources.monitorThreads" default="true"/></Set>
+        <Set name="maxConnections"><Property name="lowresources.maxConnections" default="0"/></Set>
+        <Set name="maxMemory"><Property name="lowresources.maxMemory" default="0"/></Set>
+        <Set name="maxLowResourcesTime"><Property name="lowresources.maxLowResourcesTime" default="5000"/></Set>
+      </New>
+    </Arg>
+  </Call>
+</Configure>
diff --git a/jetty-server/src/main/config/etc/jetty-proxy.xml b/jetty-server/src/main/config/etc/jetty-proxy.xml
deleted file mode 100644
index 5bc46c3..0000000
--- a/jetty-server/src/main/config/etc/jetty-proxy.xml
+++ /dev/null
@@ -1,64 +0,0 @@
-<?xml version="1.0"?>
-<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
-
-<!-- =============================================================== -->
-<!-- Configure the Jetty Server                                      -->
-<!--                                                                 -->
-<!-- Documentation of this file format can be found at:              -->
-<!-- http://docs.codehaus.org/display/JETTY/jetty.xml                -->
-<!--                                                                 -->
-<!-- =============================================================== -->
-
-
-<Configure id="Proxy" class="org.eclipse.jetty.server.Server">
-
-    <!-- =========================================================== -->
-    <!-- Server Thread Pool                                          -->
-    <!-- =========================================================== -->
-    <Set name="ThreadPool">
-      <!-- Default queued blocking threadpool 
-      -->
-      <New class="org.eclipse.jetty.util.thread.QueuedThreadPool">
-        <Set name="minThreads">10</Set>
-        <Set name="maxThreads">50</Set>
-      </New>
-    </Set>
-
-
-    <!-- =========================================================== -->
-    <!-- Set connectors                                              -->
-    <!-- =========================================================== -->
-
-    <Call name="addConnector">
-      <Arg>
-          <New class="org.eclipse.jetty.server.nio.SelectChannelConnector">
-            <Set name="host"><Property name="jetty.host" /></Set>
-            <Set name="port"><Property name="jetty.port" default="8888"/></Set>
-            <Set name="maxIdleTime">300000</Set>
-            <Set name="Acceptors">2</Set>
-            <Set name="statsOn">false</Set>
-	    <Set name="lowResourcesConnections">20000</Set>
-	    <Set name="lowResourcesMaxIdleTime">5000</Set>
-          </New>
-      </Arg>
-    </Call>
-
-    <!-- =========================================================== -->
-    <Set name="handler">
-      <New id="Servlets" class="org.eclipse.jetty.servlet.ServletHandler">
-        <Call name="addServletWithMapping">
-	  <Arg>org.eclipse.jetty.servlets.ProxyServlet</Arg>
-	  <Arg>/</Arg>
-	</Call>
-      </New>
-    </Set>
-    
-    <!-- =========================================================== -->
-    <!-- extra options                                               -->
-    <!-- =========================================================== -->
-    <Set name="stopAtShutdown">true</Set>
-    <Set name="sendServerVersion">true</Set>
-    <Set name="sendDateHeader">true</Set>
-    <Set name="gracefulShutdown">1000</Set>
-
-</Configure>
diff --git a/jetty-server/src/main/config/etc/jetty-requestlog.xml b/jetty-server/src/main/config/etc/jetty-requestlog.xml
index 45d3aab..135885e 100644
--- a/jetty-server/src/main/config/etc/jetty-requestlog.xml
+++ b/jetty-server/src/main/config/etc/jetty-requestlog.xml
@@ -1,34 +1,32 @@
 <?xml version="1.0"?>
-<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
 
 <!-- =============================================================== -->
 <!-- Configure the Jetty Request Log                                 -->
 <!-- =============================================================== -->
-
 <Configure id="Server" class="org.eclipse.jetty.server.Server">
 
-    <!-- =========================================================== -->
-    <!-- Configure Request Log                                       -->
-    <!-- =========================================================== -->
-    <Ref id="Handlers">
-      <Call name="addHandler">
-        <Arg>
-          <New id="RequestLog" class="org.eclipse.jetty.server.handler.RequestLogHandler">
-	    <Set name="requestLog">
-	      <!-- Use AsyncNCSARequestLog for improved request latency -->
-	      <New id="RequestLogImpl" class="org.eclipse.jetty.server.NCSARequestLog">
-		<Set name="filename"><Property name="jetty.logs" default="./logs"/>/yyyy_mm_dd.request.log</Set>
-		<Set name="filenameDateFormat">yyyy_MM_dd</Set>
-		<Set name="retainDays">90</Set>
-		<Set name="append">true</Set>
-		<Set name="extended">false</Set>
-		<Set name="logCookies">false</Set>
-		<Set name="LogTimeZone">GMT</Set>
-	      </New>
-	    </Set>
-	  </New>
-        </Arg>
-      </Call>
-    </Ref>
+  <!-- =========================================================== -->
+  <!-- Configure Request Log -->
+  <!-- =========================================================== -->
+  <Ref refid="Handlers">
+    <Call name="addHandler">
+      <Arg>
+        <New id="RequestLog" class="org.eclipse.jetty.server.handler.RequestLogHandler">
+          <Set name="requestLog">
+            <New id="RequestLogImpl" class="org.eclipse.jetty.server.AsyncNCSARequestLog">
+              <Set name="filename"><Property name="jetty.base" default="." /><Property name="requestlog.filename" default="/logs/yyyy_mm_dd.request.log"/></Set>
+              <Set name="filenameDateFormat"><Property name="requestlog.filenameDateFormat" default="yyyy_MM_dd"/></Set>
+              <Set name="retainDays"><Property name="requestlog.retain" default="90"/></Set>
+              <Set name="append"><Property name="requestlog.append" default="false"/></Set>
+              <Set name="extended"><Property name="requestlog.extended" default="false"/></Set>
+              <Set name="logCookies"><Property name="requestlog.cookies" default="false"/></Set>
+              <Set name="LogTimeZone"><Property name="requestlog.timezone" default="GMT"/></Set>
+            </New>
+          </Set>
+        </New>
+      </Arg>
+    </Call>
+  </Ref>
 
 </Configure>
diff --git a/jetty-server/src/main/config/etc/jetty-ssl.xml b/jetty-server/src/main/config/etc/jetty-ssl.xml
index 4c0c9ff..4ac2d3e 100644
--- a/jetty-server/src/main/config/etc/jetty-ssl.xml
+++ b/jetty-server/src/main/config/etc/jetty-ssl.xml
@@ -1,35 +1,43 @@
 <?xml version="1.0"?>
-<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
 
-<!-- =============================================================== -->
-<!-- Configure SSL for the Jetty Server                              -->
-<!-- this configuration file should be used in combination with      -->
-<!-- other configuration files.  e.g.                                -->
-<!--    java -jar start.jar etc/jetty-ssl.xml                        -->
-<!--                                                                 -->
-<!--  alternately, add to the start.ini for easier usage             -->
-<!-- =============================================================== -->
-<Configure id="Server" class="org.eclipse.jetty.server.Server">
+<!-- ============================================================= -->
+<!-- Configure a TLS (SSL) Context Factory                         -->
+<!-- This configuration must be used in conjunction with jetty.xml -->
+<!-- and either jetty-https.xml or jetty-spdy.xml (but not both)   -->
+<!-- ============================================================= -->
+<Configure id="sslContextFactory" class="org.eclipse.jetty.util.ssl.SslContextFactory">
+  <Set name="KeyStorePath"><Property name="jetty.base" default="." />/<Property name="jetty.keystore" default="etc/keystore"/></Set>
+  <Set name="KeyStorePassword"><Property name="jetty.keystore.password" default="OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4"/></Set>
+  <Set name="KeyManagerPassword"><Property name="jetty.keymanager.password" default="OBF:1u2u1wml1z7s1z7a1wnl1u2g"/></Set>
+  <Set name="TrustStorePath"><Property name="jetty.base" default="." />/<Property name="jetty.truststore" default="etc/keystore"/></Set>
+  <Set name="TrustStorePassword"><Property name="jetty.truststore.password" default="OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4"/></Set>
+  <Set name="EndpointIdentificationAlgorithm"></Set>
+  <Set name="NeedClientAuth"><Property name="jetty.ssl.needClientAuth" default="false"/></Set>
+  <Set name="WantClientAuth"><Property name="jetty.ssl.wantClientAuth" default="false"/></Set>
+  <Set name="ExcludeCipherSuites">
+    <Array type="String">
+      <Item>SSL_RSA_WITH_DES_CBC_SHA</Item>
+      <Item>SSL_DHE_RSA_WITH_DES_CBC_SHA</Item>
+      <Item>SSL_DHE_DSS_WITH_DES_CBC_SHA</Item>
+      <Item>SSL_RSA_EXPORT_WITH_RC4_40_MD5</Item>
+      <Item>SSL_RSA_EXPORT_WITH_DES40_CBC_SHA</Item>
+      <Item>SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA</Item>
+      <Item>SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA</Item>
+    </Array>
+  </Set>
 
-  <!-- if NIO is not available, use org.eclipse.jetty.server.ssl.SslSocketConnector -->
-  
-  <New id="sslContextFactory" class="org.eclipse.jetty.http.ssl.SslContextFactory">
-    <Set name="KeyStore"><Property name="jetty.home" default="." />/etc/keystore</Set>
-    <Set name="KeyStorePassword">OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4</Set>
-    <Set name="KeyManagerPassword">OBF:1u2u1wml1z7s1z7a1wnl1u2g</Set>
-    <Set name="TrustStore"><Property name="jetty.home" default="." />/etc/keystore</Set>
-    <Set name="TrustStorePassword">OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4</Set>
+  <!-- =========================================================== -->
+  <!-- Create a TLS specific HttpConfiguration based on the        -->
+  <!-- common HttpConfiguration defined in jetty.xml               -->
+  <!-- Add a SecureRequestCustomizer to extract certificate and    -->
+  <!-- session information                                         -->
+  <!-- =========================================================== -->
+  <New id="sslHttpConfig" class="org.eclipse.jetty.server.HttpConfiguration">
+    <Arg><Ref refid="httpConfig"/></Arg>
+    <Call name="addCustomizer">
+      <Arg><New class="org.eclipse.jetty.server.SecureRequestCustomizer"/></Arg>
+    </Call>
   </New>
 
-  <Call name="addConnector">
-    <Arg>
-      <New class="org.eclipse.jetty.server.ssl.SslSelectChannelConnector">
-        <Arg><Ref id="sslContextFactory" /></Arg>
-        <Set name="Port">8443</Set>
-        <Set name="maxIdleTime">30000</Set>
-        <Set name="Acceptors">2</Set>
-        <Set name="AcceptQueueSize">100</Set>
-      </New>
-    </Arg>
-  </Call>
 </Configure>
diff --git a/jetty-server/src/main/config/etc/jetty-stats.xml b/jetty-server/src/main/config/etc/jetty-stats.xml
index e0a0c7f..2e7a57c 100644
--- a/jetty-server/src/main/config/etc/jetty-stats.xml
+++ b/jetty-server/src/main/config/etc/jetty-stats.xml
@@ -1,19 +1,18 @@
 <?xml version="1.0"?>
-<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
 
 <!-- =============================================================== -->
 <!-- Mixin the Statistics Handler                                    -->
 <!-- =============================================================== -->
 
-
 <Configure id="Server" class="org.eclipse.jetty.server.Server">
-
-    <Get id="oldhandler" name="handler"/>
-
-    <Set name="handler">
-     <New id="StatsHandler" class="org.eclipse.jetty.server.handler.StatisticsHandler">
-      <Set name="handler"><Ref id="oldhandler"/></Set>
-     </New>
-    </Set>
-    
+  <Get id="oldhandler" name="handler" />
+  <Set name="handler">
+    <New id="StatsHandler" class="org.eclipse.jetty.server.handler.StatisticsHandler">
+      <Set name="handler"><Ref refid="oldhandler" /></Set>
+    </New>
+  </Set>
+  <Call class="org.eclipse.jetty.server.ConnectorStatistics" name="addToAllConnectors">
+    <Arg><Ref refid="Server"/></Arg>
+  </Call>
 </Configure>
diff --git a/jetty-server/src/main/config/etc/jetty-xinetd.xml b/jetty-server/src/main/config/etc/jetty-xinetd.xml
index c2fbaa2..a4750de 100644
--- a/jetty-server/src/main/config/etc/jetty-xinetd.xml
+++ b/jetty-server/src/main/config/etc/jetty-xinetd.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0"?>
-<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
 
 <!-- =============================================================== -->
 <!-- Configuration for starting up Jetty using inetd/xinetd          -->
@@ -16,8 +16,8 @@ service jetty
     disable     = no
 
     id          = jetty
-    type        = UNLISTED     
-    wait        = yes          
+    type        = UNLISTED
+    wait        = yes
     socket_type = stream
 
     # change this
@@ -39,16 +39,14 @@ service jetty
           <New class="org.eclipse.jetty.server.nio.InheritedChannelConnector">
 
 
-            <!-- Optional. Fallback in case System.inheritedChannel() does not give a ServerSocketChannel 
+            <!-- Optional. Fallback in case System.inheritedChannel() does not give a ServerSocketChannel
             <Set name="port"><Property name="jetty.service.port" default="8082"/></Set>
             -->
 
             <!-- sane defaults -->
-            <Set name="maxIdleTime">300000</Set>
-            <Set name="Acceptors">2</Set>
-            <Set name="statsOn">false</Set>
-      	    <Set name="lowResourcesConnections">20000</Set>
-	        <Set name="lowResourcesMaxIdleTime">5000</Set>
+            <Set name="idleTimeout"><Property name="jetty.xinetd.idleTimeout" default="300000"/></Set>
+            <Set name="Acceptors"><Property name="jetty.xinetd.acceptors" default="2"/></Set>
+            <Set name="statsOn"><Property name="jetty.xinetd.statsOn" default="false"/></Set>
           </New>
       </Arg>
     </Call>
diff --git a/jetty-server/src/main/config/etc/jetty.xml b/jetty-server/src/main/config/etc/jetty.xml
index 153514d..d8708ef 100644
--- a/jetty-server/src/main/config/etc/jetty.xml
+++ b/jetty-server/src/main/config/etc/jetty.xml
@@ -1,55 +1,110 @@
 <?xml version="1.0"?>
-<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
 
 <!-- =============================================================== -->
-<!-- Configure the Jetty Server                                      -->
-<!--                                                                 -->
 <!-- Documentation of this file format can be found at:              -->
 <!-- http://wiki.eclipse.org/Jetty/Reference/jetty.xml_syntax        -->
 <!--                                                                 -->
 <!-- Additional configuration files are available in $JETTY_HOME/etc -->
-<!-- and can be mixed in.  For example:                              -->
-<!--   java -jar start.jar etc/jetty-ssl.xml                         -->
+<!-- and can be mixed in. See start.ini file for the default         -->
+<!-- configuration files.                                            -->
 <!--                                                                 -->
-<!-- See start.ini file for the default configuraton files           -->
+<!-- For a description of the configuration mechanism, see the       -->
+<!-- output of:                                                      -->
+<!--   java -jar start.jar -?                                        -->
 <!-- =============================================================== -->
 
-
+<!-- =============================================================== -->
+<!-- Configure a Jetty Server instance with an ID "Server"           -->
+<!-- Other configuration files may also configure the "Server"       -->
+<!-- ID, in which case they are adding configuration to the same     -->
+<!-- instance.  If other configuration have a different ID, they     -->
+<!-- will create and configure another instance of Jetty.            -->
+<!-- Consult the javadoc of o.e.j.server.Server for all              -->
+<!-- configuration that may be set here.                             -->
+<!-- =============================================================== -->
 <Configure id="Server" class="org.eclipse.jetty.server.Server">
 
     <!-- =========================================================== -->
-    <!-- Server Thread Pool                                          -->
+    <!-- Configure the Server Thread Pool.                           -->
+    <!-- The server holds a common thread pool which is used by      -->
+    <!-- default as the executor used by all connectors and servlet  -->
+    <!-- dispatches.                                                 -->
+    <!--                                                             -->
+    <!-- Configuring a fixed thread pool is vital to controlling the -->
+    <!-- maximal memory footprint of the server and is a key tuning  -->
+    <!-- parameter for tuning.  In an application that rarely blocks -->
+    <!-- then maximal threads may be close to the number of 5*CPUs.  -->
+    <!-- In an application that frequently blocks, then maximal      -->
+    <!-- threads should be set as high as possible given the memory  -->
+    <!-- available.                                                  -->
+    <!--                                                             -->
+    <!-- Consult the javadoc of o.e.j.util.thread.QueuedThreadPool   -->
+    <!-- for all configuration that may be set here.                 -->
     <!-- =========================================================== -->
-    <Set name="ThreadPool">
-      <!-- Default queued blocking threadpool -->
-      <New class="org.eclipse.jetty.util.thread.QueuedThreadPool">
-        <Set name="minThreads">10</Set>
-        <Set name="maxThreads">200</Set>
-        <Set name="detailedDump">false</Set>
-      </New>
-    </Set>
+    <!-- uncomment to change type of threadpool
+    <Arg name="threadpool"><New id="threadpool" class="org.eclipse.jetty.util.thread.QueuedThreadPool"/></Arg>
+    -->
+    <Get name="ThreadPool">
+      <Set name="minThreads" type="int"><Property name="threads.min" default="10"/></Set>
+      <Set name="maxThreads" type="int"><Property name="threads.max" default="200"/></Set>
+      <Set name="idleTimeout" type="int"><Property name="threads.timeout" default="60000"/></Set>
+      <Set name="detailedDump">false</Set>
+    </Get>
 
     <!-- =========================================================== -->
-    <!-- Set connectors                                              -->
+    <!-- Add shared Scheduler instance                               -->
     <!-- =========================================================== -->
-
-    <Call name="addConnector">
+    <Call name="addBean">
       <Arg>
-          <New class="org.eclipse.jetty.server.nio.SelectChannelConnector">
-            <Set name="host"><Property name="jetty.host" /></Set>
-            <Set name="port"><Property name="jetty.port" default="8080"/></Set>
-            <Set name="maxIdleTime">300000</Set>
-            <Set name="Acceptors">2</Set>
-            <Set name="statsOn">false</Set>
-            <Set name="confidentialPort">8443</Set>
-	    <Set name="lowResourcesConnections">20000</Set>
-	    <Set name="lowResourcesMaxIdleTime">5000</Set>
-          </New>
+        <New class="org.eclipse.jetty.util.thread.ScheduledExecutorScheduler"/>
       </Arg>
     </Call>
 
     <!-- =========================================================== -->
-    <!-- Set handler Collection Structure                            --> 
+    <!-- Http Configuration.                                         -->
+    <!-- This is a common configuration instance used by all         -->
+    <!-- connectors that can carry HTTP semantics (HTTP, HTTPS, SPDY)-->
+    <!-- It configures the non wire protocol aspects of the HTTP     -->
+    <!-- semantic.                                                   -->
+    <!--                                                             -->
+    <!-- This configuration is only defined here and is used by      -->
+    <!-- reference from the jetty-http.xml, jetty-https.xml and      -->
+    <!-- jetty-spdy.xml configuration files which instantiate the    -->
+    <!-- connectors.                                                 -->
+    <!--                                                             -->
+    <!-- Consult the javadoc of o.e.j.server.HttpConfiguration       -->
+    <!-- for all configuration that may be set here.                 -->
+    <!-- =========================================================== -->
+    <New id="httpConfig" class="org.eclipse.jetty.server.HttpConfiguration">
+      <Set name="secureScheme">https</Set>
+      <Set name="securePort"><Property name="jetty.secure.port" default="8443" /></Set>
+      <Set name="outputBufferSize"><Property name="jetty.output.buffer.size" default="32768" /></Set>
+      <Set name="outputAggregationSize"><Property name="jetty.output.aggregation.size" default="8192" /></Set>
+      <Set name="requestHeaderSize"><Property name="jetty.request.header.size" default="8192" /></Set>
+      <Set name="responseHeaderSize"><Property name="jetty.response.header.size" default="8192" /></Set>
+      <Set name="sendServerVersion"><Property name="jetty.send.server.version" default="true" /></Set>
+      <Set name="sendDateHeader"><Property name="jetty.send.date.header" default="false" /></Set>
+      <Set name="headerCacheSize">512</Set>
+      <Set name="delayDispatchUntilContent"><Property name="jetty.delayDispatchUntilContent" default="false"/></Set>
+      <!-- Uncomment to enable handling of X-Forwarded- style headers
+      <Call name="addCustomizer">
+        <Arg><New class="org.eclipse.jetty.server.ForwardedRequestCustomizer"/></Arg>
+      </Call>
+      -->
+    </New>
+
+
+    <!-- =========================================================== -->
+    <!-- Set the default handler structure for the Server            -->
+    <!-- A handler collection is used to pass received requests to   -->
+    <!-- both the ContextHandlerCollection, which selects the next   -->
+    <!-- handler by context path and virtual host, and the           -->
+    <!-- DefaultHandler, which handles any requests not handled by   -->
+    <!-- the context handlers.                                       -->
+    <!-- Other handlers may be added to the "Handlers" collection,   -->
+    <!-- for example the jetty-requestlog.xml file adds the          -->
+    <!-- RequestLogHandler after the default handler                 -->
     <!-- =========================================================== -->
     <Set name="handler">
       <New id="Handlers" class="org.eclipse.jetty.server.handler.HandlerCollection">
@@ -67,13 +122,11 @@
     </Set>
 
     <!-- =========================================================== -->
-    <!-- extra options                                               -->
+    <!-- extra server options                                        -->
     <!-- =========================================================== -->
     <Set name="stopAtShutdown">true</Set>
-    <Set name="sendServerVersion">true</Set>
-    <Set name="sendDateHeader">true</Set>
-    <Set name="gracefulShutdown">1000</Set>
-    <Set name="dumpAfterStart">false</Set>
-    <Set name="dumpBeforeStop">false</Set>
+    <Set name="stopTimeout">5000</Set>
+    <Set name="dumpAfterStart"><Property name="jetty.dump.start" default="false"/></Set>
+    <Set name="dumpBeforeStop"><Property name="jetty.dump.stop" default="false"/></Set>
 
 </Configure>
diff --git a/jetty-server/src/main/config/etc/keystore.pkf b/jetty-server/src/main/config/etc/keystore.pkf
new file mode 100644
index 0000000..443818e
--- /dev/null
+++ b/jetty-server/src/main/config/etc/keystore.pkf
@@ -0,0 +1,20 @@
+Bag Attributes
+    friendlyName: jetty
+    localKeyID: 54 69 6D 65 20 31 34 32 33 31 39 38 30 39 33 31 31 35 
+Key Attributes: <No Attributes>
+-----BEGIN PRIVATE KEY-----
+MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAIPh4Q0t4xklXTzX
+N2VAb47r5n7idAupp4CTNEhhT6lS70iA+A8i4+0lSEHWAogvd9jl3H7SvScr30QM
+4ieC0JCGSOwGc8f+yqKrO56PPd5OuqW380BJ0r74jJczU9CcsuavHD7e6mRLUnmj
+xM20NSxrcicMiPUHY1mJZtN9swtxAgMBAAECgYADS9P6Jll0uXBZIu/pgfDH27GJ
+HlPULstW9VbrMDNzgfUlFMQebLrRpIrnyleJ29Xc//HA4beEkR4lb0T/w88+pEkt
+7fhYeqRLPIfpDOgzloynnsoPcd8f/PypbimQrNLmBiG1178nVcy4Yoh5lYVIJwtU
+3VriqDlvAfTLrrx8AQJBAMLWuh27Hb8xs3LRg4UD7hcv8tJejstm08Y+czRz7cO0
+RENa3aDjGFSegc+IUfdez7BP8uDw+PwE+jybmTvaliECQQCtR/anCY1WS28/bKvy
+lmIwoI15eraBdVFkN0Hfxh+9PfR3rMD5uyvukT5GgTtY/XxADyafSTaipDJiZHJI
+EitRAkBjeCBYYVjUbVlBuvi8Bb+dktsSzzdzXDGtueAy3SR7jyJyiIcxRf775Fg9
+TUkbUwoQ5yAF+sACWcAvBPj796JBAkAEZEeHEkHnxv+pztpIyrDwZJFRW9/WRh/q
+90+PGVlilXhltBYr/idt43Z9mPblGX+VrAyhitx8oMa6IauX0gYRAkEAgnyVeXrD
+jDLUZRA3P8Gu27k1k6GjbTYiUz3HKCz2/6+MZ2MK2qqwafgqocji029Q6dHdPD7a
+4QnRlvraUnyQLA==
+-----END PRIVATE KEY-----
diff --git a/jetty-server/src/main/config/modules/continuation.mod b/jetty-server/src/main/config/modules/continuation.mod
new file mode 100644
index 0000000..231c09d
--- /dev/null
+++ b/jetty-server/src/main/config/modules/continuation.mod
@@ -0,0 +1,6 @@
+#
+# Classic Jetty Continuation Support Module
+#
+
+[lib]
+lib/jetty-continuation-${jetty.version}.jar
diff --git a/jetty-server/src/main/config/modules/debug.mod b/jetty-server/src/main/config/modules/debug.mod
new file mode 100644
index 0000000..f740ea2
--- /dev/null
+++ b/jetty-server/src/main/config/modules/debug.mod
@@ -0,0 +1,9 @@
+#
+# Debug module
+#
+
+[depend]
+server
+
+[xml]
+etc/jetty-debug.xml
diff --git a/jetty-server/src/main/config/modules/ext.mod b/jetty-server/src/main/config/modules/ext.mod
new file mode 100644
index 0000000..56b10f7
--- /dev/null
+++ b/jetty-server/src/main/config/modules/ext.mod
@@ -0,0 +1,11 @@
+#
+# Module to add all lib/ext/**.jar files to classpath
+#
+
+[lib]
+lib/ext/**.jar
+
+[files]
+lib/
+lib/ext/
+
diff --git a/jetty-server/src/main/config/modules/home-base-warning.mod b/jetty-server/src/main/config/modules/home-base-warning.mod
new file mode 100644
index 0000000..28e5757
--- /dev/null
+++ b/jetty-server/src/main/config/modules/home-base-warning.mod
@@ -0,0 +1,7 @@
+#
+# Home and Base Warning
+#
+
+[xml]
+etc/home-base-warning.xml
+
diff --git a/jetty-server/src/main/config/modules/http.mod b/jetty-server/src/main/config/modules/http.mod
new file mode 100644
index 0000000..dc34bc3
--- /dev/null
+++ b/jetty-server/src/main/config/modules/http.mod
@@ -0,0 +1,27 @@
+#
+# Jetty HTTP Connector
+#
+
+[depend]
+server
+
+[xml]
+etc/jetty-http.xml
+
+[ini-template]
+### HTTP Connector Configuration
+
+## HTTP port to listen on
+jetty.port=8080
+
+## HTTP idle timeout in milliseconds
+http.timeout=30000
+
+## HTTP Socket.soLingerTime in seconds. (-1 to disable)
+# http.soLingerTime=-1
+
+## Parameters to control the number and priority of acceptors and selectors
+# http.selectors=1
+# http.acceptors=1
+# http.selectorPriorityDelta=0
+# http.acceptorPriorityDelta=0
diff --git a/jetty-server/src/main/config/modules/https.mod b/jetty-server/src/main/config/modules/https.mod
new file mode 100644
index 0000000..bd1b718
--- /dev/null
+++ b/jetty-server/src/main/config/modules/https.mod
@@ -0,0 +1,19 @@
+#
+# Jetty HTTPS Connector
+#
+
+[depend]
+ssl
+
+[xml]
+etc/jetty-https.xml
+
+[ini-template]
+## HTTPS Configuration
+# HTTP port to listen on
+https.port=8443
+# HTTPS idle timeout in milliseconds
+https.timeout=30000
+# HTTPS Socket.soLingerTime in seconds. (-1 to disable)
+# https.soLingerTime=-1
+
diff --git a/jetty-server/src/main/config/modules/ipaccess.mod b/jetty-server/src/main/config/modules/ipaccess.mod
new file mode 100644
index 0000000..956ea0f
--- /dev/null
+++ b/jetty-server/src/main/config/modules/ipaccess.mod
@@ -0,0 +1,9 @@
+#
+# IPAccess module
+#
+
+[depend]
+server
+
+[xml]
+etc/jetty-ipaccess.xml
diff --git a/jetty-server/src/main/config/modules/jvm.mod b/jetty-server/src/main/config/modules/jvm.mod
new file mode 100644
index 0000000..195521c
--- /dev/null
+++ b/jetty-server/src/main/config/modules/jvm.mod
@@ -0,0 +1,23 @@
+[ini-template]
+## JVM Configuration
+## If JVM args are include in an ini file then --exec is needed
+## to start a new JVM from start.jar with the extra args.
+##
+## If you wish to avoid an extra JVM running, place JVM args
+## on the normal command line and do not use --exec
+# --exec
+# -Xmx2000m
+# -Xmn512m
+# -XX:+UseConcMarkSweepGC
+# -XX:ParallelCMSThreads=2
+# -XX:+CMSClassUnloadingEnabled
+# -XX:+UseCMSCompactAtFullCollection
+# -XX:CMSInitiatingOccupancyFraction=80
+# -verbose:gc
+# -XX:+PrintGCDateStamps
+# -XX:+PrintGCTimeStamps
+# -XX:+PrintGCDetails
+# -XX:+PrintTenuringDistribution
+# -XX:+PrintCommandLineFlags
+# -XX:+DisableExplicitGC
+# -Dorg.apache.jasper.compiler.disablejsr199=true
diff --git a/jetty-server/src/main/config/modules/lowresources.mod b/jetty-server/src/main/config/modules/lowresources.mod
new file mode 100644
index 0000000..99112d5
--- /dev/null
+++ b/jetty-server/src/main/config/modules/lowresources.mod
@@ -0,0 +1,18 @@
+#
+# Low Resources module
+#
+
+[depend]
+server
+
+[xml]
+etc/jetty-lowresources.xml
+
+[ini-template]
+## Low Resources Configuration
+# lowresources.period=1050
+# lowresources.lowResourcesIdleTimeout=200
+# lowresources.monitorThreads=true
+# lowresources.maxConnections=0
+# lowresources.maxMemory=0
+# lowresources.maxLowResourcesTime=5000
diff --git a/jetty-server/src/main/config/modules/requestlog.mod b/jetty-server/src/main/config/modules/requestlog.mod
new file mode 100644
index 0000000..f5e0614
--- /dev/null
+++ b/jetty-server/src/main/config/modules/requestlog.mod
@@ -0,0 +1,30 @@
+#
+# Request Log module
+#
+
+[depend]
+server
+
+[xml]
+etc/jetty-requestlog.xml
+
+[files]
+logs/
+
+[ini-template]
+## Request Log Configuration
+# Filename for Request Log output (relative to jetty.base)
+# requestlog.filename=/logs/yyyy_mm_dd.request.log
+# Date format for rollovered files (uses SimpleDateFormat syntax)
+# requestlog.filenameDateFormat=yyyy_MM_dd
+# How many days to retain the logs
+# requestlog.retain=90
+# If an existing log with the same name is found, just append to it
+# requestlog.append=true
+# Use the extended log output
+# requestlog.extended=true
+# Log http cookie information as well
+# requestlog.cookies=true
+# Set the log output timezone
+# requestlog.timezone=GMT
+
diff --git a/jetty-server/src/main/config/modules/resources.mod b/jetty-server/src/main/config/modules/resources.mod
new file mode 100644
index 0000000..8647d81
--- /dev/null
+++ b/jetty-server/src/main/config/modules/resources.mod
@@ -0,0 +1,10 @@
+#
+# Module to add resources directory to classpath
+#
+
+[lib]
+resources/
+
+[files]
+resources/
+
diff --git a/jetty-server/src/main/config/modules/server.mod b/jetty-server/src/main/config/modules/server.mod
new file mode 100644
index 0000000..b3f87de
--- /dev/null
+++ b/jetty-server/src/main/config/modules/server.mod
@@ -0,0 +1,49 @@
+#
+# Base Server Module
+#
+
+[optional]
+jvm
+ext
+resources
+
+[lib]
+lib/servlet-api-3.1.jar
+lib/jetty-schemas-3.1.jar
+lib/jetty-http-${jetty.version}.jar
+lib/jetty-server-${jetty.version}.jar
+lib/jetty-xml-${jetty.version}.jar
+lib/jetty-util-${jetty.version}.jar
+lib/jetty-io-${jetty.version}.jar
+
+[xml]
+etc/jetty.xml
+
+[ini-template]
+##
+## Server Threading Configuration
+##
+# minimum number of threads
+threads.min=10
+# maximum number of threads
+threads.max=200
+# thread idle timeout in milliseconds
+threads.timeout=60000
+# buffer size for output
+jetty.output.buffer.size=32768
+# request header buffer size
+jetty.request.header.size=8192
+# response header buffer size
+jetty.response.header.size=8192
+# should jetty send the server version header?
+jetty.send.server.version=true
+# should jetty send the date header?
+jetty.send.date.header=false
+# What host to listen on (leave commented to listen on all interfaces)
+#jetty.host=myhost.com
+# Dump the state of the Jetty server, components, and webapps after startup
+jetty.dump.start=false
+# Dump the state of the Jetty server, before stop
+jetty.dump.stop=false
+# Enable delayed dispatch optimisation
+jetty.delayDispatchUntilContent=false
diff --git a/jetty-server/src/main/config/modules/ssl.mod b/jetty-server/src/main/config/modules/ssl.mod
new file mode 100644
index 0000000..ef47192
--- /dev/null
+++ b/jetty-server/src/main/config/modules/ssl.mod
@@ -0,0 +1,40 @@
+#
+# SSL Keystore module
+#
+
+[depend]
+server
+
+[xml]
+etc/jetty-ssl.xml
+
+[files]
+http://git.eclipse.org/c/jetty/org.eclipse.jetty.project.git/plain/jetty-server/src/main/config/etc/keystore|etc/keystore
+
+[ini-template]
+### SSL Keystore Configuration
+# define the port to use for secure redirection
+jetty.secure.port=8443
+
+## Setup a demonstration keystore and truststore
+jetty.keystore=etc/keystore
+jetty.truststore=etc/keystore
+
+## Set the demonstration passwords.
+## Note that OBF passwords are not secure, just protected from casual observation
+## See http://www.eclipse.org/jetty/documentation/current/configuring-security-secure-passwords.html
+jetty.keystore.password=OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4
+jetty.keymanager.password=OBF:1u2u1wml1z7s1z7a1wnl1u2g
+jetty.truststore.password=OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4
+
+### Set the client auth behavior
+## Set to true if client certificate authentication is required
+# jetty.ssl.needClientAuth=true
+## Set to true if client certificate authentication is desired
+# jetty.ssl.wantClientAuth=true
+
+## Parameters to control the number and priority of acceptors and selectors
+# ssl.selectors=1
+# ssl.acceptors=1
+# ssl.selectorPriorityDelta=0
+# ssl.acceptorPriorityDelta=0
diff --git a/jetty-server/src/main/config/modules/stats.mod b/jetty-server/src/main/config/modules/stats.mod
new file mode 100644
index 0000000..0922469
--- /dev/null
+++ b/jetty-server/src/main/config/modules/stats.mod
@@ -0,0 +1,9 @@
+#
+# Stats module
+#
+
+[depend]
+server
+
+[xml]
+etc/jetty-stats.xml
diff --git a/jetty-server/src/main/config/modules/xinetd.mod b/jetty-server/src/main/config/modules/xinetd.mod
new file mode 100644
index 0000000..e53618e
--- /dev/null
+++ b/jetty-server/src/main/config/modules/xinetd.mod
@@ -0,0 +1,17 @@
+#
+# Xinetd module
+#
+
+[depend]
+server
+
+[xml]
+etc/jetty-xinetd.xml
+
+[ini-template]
+## Xinetd Configuration
+## See ${jetty.home}/etc/jetty-xinetd.xml for example service entry
+jetty.xinetd.idleTimeout=300000
+jetty.xinetd.acceptors=2
+jetty.xinetd.statsOn=false
+
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnectionFactory.java b/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnectionFactory.java
new file mode 100644
index 0000000..50a6926
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnectionFactory.java
@@ -0,0 +1,92 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import org.eclipse.jetty.io.AbstractConnection;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.util.ArrayUtil;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+
+public abstract class AbstractConnectionFactory extends ContainerLifeCycle implements ConnectionFactory
+{
+    private final String _protocol;
+    private int _inputbufferSize = 8192;
+
+    protected AbstractConnectionFactory(String protocol)
+    {
+        _protocol=protocol;
+    }
+
+    @Override
+    public String getProtocol()
+    {
+        return _protocol;
+    }
+
+    public int getInputBufferSize()
+    {
+        return _inputbufferSize;
+    }
+
+    public void setInputBufferSize(int size)
+    {
+        _inputbufferSize=size;
+    }
+
+    protected AbstractConnection configure(AbstractConnection connection, Connector connector, EndPoint endPoint)
+    {
+        connection.setInputBufferSize(getInputBufferSize());
+
+        if (connector instanceof ContainerLifeCycle)
+        {
+            ContainerLifeCycle aggregate = (ContainerLifeCycle)connector;
+            for (Connection.Listener listener : aggregate.getBeans(Connection.Listener.class))
+                connection.addListener(listener);
+        }
+        return connection;
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s@%x{%s}",this.getClass().getSimpleName(),hashCode(),getProtocol());
+    }
+
+    public static ConnectionFactory[] getFactories(SslContextFactory sslContextFactory, ConnectionFactory... factories)
+    {
+        factories=ArrayUtil.removeNulls(factories);
+
+        if (sslContextFactory==null)
+            return factories;
+
+        for (ConnectionFactory factory : factories)
+        {
+            if (factory instanceof HttpConfiguration.ConnectionFactory)
+            {
+                HttpConfiguration config = ((HttpConfiguration.ConnectionFactory)factory).getHttpConfiguration();
+                if (config.getCustomizer(SecureRequestCustomizer.class)==null)
+                    config.addCustomizer(new SecureRequestCustomizer());
+            }
+        }
+        return ArrayUtil.prependToArray(new SslConnectionFactory(sslContextFactory,factories[0].getProtocol()),factories,ConnectionFactory.class);
+
+    }
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java
index 3791c9e..97d2df3 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java
@@ -19,1204 +19,591 @@
 package org.eclipse.jetty.server;
 
 import java.io.IOException;
-import java.net.InetAddress;
-import java.net.Socket;
-import java.net.UnknownHostException;
-import java.util.concurrent.atomic.AtomicLong;
-
-import javax.servlet.ServletRequest;
-
-import org.eclipse.jetty.http.HttpBuffers;
-import org.eclipse.jetty.http.HttpBuffersImpl;
-import org.eclipse.jetty.http.HttpFields;
-import org.eclipse.jetty.http.HttpHeaders;
-import org.eclipse.jetty.http.HttpSchemes;
-import org.eclipse.jetty.io.Buffers;
-import org.eclipse.jetty.io.Buffers.Type;
-import org.eclipse.jetty.io.Connection;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.io.ArrayByteBufferPool;
+import org.eclipse.jetty.io.ByteBufferPool;
 import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.io.EofException;
-import org.eclipse.jetty.util.component.AggregateLifeCycle;
+import org.eclipse.jetty.util.FutureCallback;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
 import org.eclipse.jetty.util.component.Dumpable;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.util.statistic.CounterStatistic;
-import org.eclipse.jetty.util.statistic.SampleStatistic;
-import org.eclipse.jetty.util.thread.ThreadPool;
+import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
+import org.eclipse.jetty.util.thread.Scheduler;
 
 /**
- * Abstract Connector implementation. This abstract implementation of the Connector interface provides:
+ * <p>An abstract implementation of {@link Connector} that provides a {@link ConnectionFactory} mechanism
+ * for creating {@link Connection} instances for various protocols (HTTP, SSL, SPDY, etc).</p>
+ *
+ * <h2>Connector Services</h2>
+ * The abstract connector manages the dependent services needed by all specific connector instances:
  * <ul>
- * <li>AbstractLifeCycle implementation</li>
- * <li>Implementations for connector getters and setters</li>
- * <li>Buffer management</li>
- * <li>Socket configuration</li>
- * <li>Base acceptor thread</li>
- * <li>Optional reverse proxy headers checking</li>
+ * <li>The {@link Executor} service is used to run all active tasks needed by this connector such as accepting connections
+ * or handle HTTP requests. The default is to use the {@link Server#getThreadPool()} as an executor.
+ * </li>
+ * <li>The {@link Scheduler} service is used to monitor the idle timeouts of all connections and is also made available
+ * to the connections to time such things as asynchronous request timeouts.  The default is to use a new
+ * {@link ScheduledExecutorScheduler} instance.
+ * </li>
+ * <li>The {@link ByteBufferPool} service is made available to all connections to be used to acquire and release
+ * {@link ByteBuffer} instances from a pool.  The default is to use a new {@link ArrayByteBufferPool} instance.
+ * </li>
  * </ul>
+ * These services are managed as aggregate beans by the {@link ContainerLifeCycle} super class and
+ * may either be managed or unmanaged beans.
+ *
+ * <h2>Connection Factories</h2>
+ * The connector keeps a collection of {@link ConnectionFactory} instances, each of which are known by their
+ * protocol name.  The protocol name may be a real protocol (eg http/1.1 or spdy/3) or it may be a private name
+ * that represents a special connection factory. For example, the name "SSL-http/1.1" is used for
+ * an {@link SslConnectionFactory} that has been instantiated with the {@link HttpConnectionFactory} as it's
+ * next protocol.
+ *
+ * <h4>Configuring Connection Factories</h4>
+ * The collection of available {@link ConnectionFactory} may be constructor injected or modified with the
+ * methods {@link #addConnectionFactory(ConnectionFactory)}, {@link #removeConnectionFactory(String)} and
+ * {@link #setConnectionFactories(Collection)}.  Only a single {@link ConnectionFactory} instance may be configured
+ * per protocol name, so if two factories with the same {@link ConnectionFactory#getProtocol()} are set, then
+ * the second will replace the first.
+ * <p>
+ * The protocol factory used for newly accepted connections is specified by
+ * the method {@link #setDefaultProtocol(String)} or defaults to the protocol of the first configured factory.
+ * <p>
+ * Each Connection factory type is responsible for the configuration of the protocols that it accepts. Thus to
+ * configure the HTTP protocol, you pass a {@link HttpConfiguration} instance to the {@link HttpConnectionFactory}
+ * (or the SPDY factories that can also provide HTTP Semantics).  Similarly the {@link SslConnectionFactory} is
+ * configured by passing it a {@link SslContextFactory} and a next protocol name.
+ *
+ * <h4>Connection Factory Operation</h4>
+ * {@link ConnectionFactory}s may simply create a {@link Connection} instance to support a specific
+ * protocol.  For example, the {@link HttpConnectionFactory} will create a {@link HttpConnection} instance
+ * that can handle http/1.1, http/1.0 and http/0.9.
+ * <p>
+ * {@link ConnectionFactory}s may also create a chain of {@link Connection} instances, using other {@link ConnectionFactory} instances.
+ * For example, the {@link SslConnectionFactory} is configured with a next protocol name, so that once it has accepted
+ * a connection and created an {@link SslConnection}, it then used the next {@link ConnectionFactory} from the
+ * connector using the {@link #getConnectionFactory(String)} method, to create a {@link Connection} instance that
+ * will handle the unecrypted bytes from the {@link SslConnection}.   If the next protocol is "http/1.1", then the
+ * {@link SslConnectionFactory} will have a protocol name of "SSL-http/1.1" and lookup "http/1.1" for the protocol
+ * to run over the SSL connection.
+ * <p>
+ * {@link ConnectionFactory}s may also create temporary {@link Connection} instances that will exchange bytes
+ * over the connection to determine what is the next protocol to use.  For example the NPN protocol is an extension
+ * of SSL to allow a protocol to be specified during the SSL handshake. NPN is used by the SPDY protocol to
+ * negotiate the version of SPDY or HTTP that the client and server will speak.  Thus to accept a SPDY connection, the
+ * connector will be configured with {@link ConnectionFactory}s for "SSL-NPN", "NPN", "spdy/3", "spdy/2", "http/1.1"
+ * with the default protocol being "SSL-NPN".  Thus a newly accepted connection uses "SSL-NPN", which specifies a
+ * SSLConnectionFactory with "NPN" as the next protocol.  Thus an SslConnection instance is created chained to an NPNConnection
+ * instance.  The NPN connection then negotiates with the client to determined the next protocol, which could be
+ * "spdy/3", "spdy/2" or the default of "http/1.1".  Once the next protocol is determined, the NPN connection
+ * calls {@link #getConnectionFactory(String)} to create a connection instance that will replace the NPN connection as
+ * the connection chained to the SSLConnection.
+ * <p>
+ * <h2>Acceptors</h2>
+ * The connector will execute a number of acceptor tasks to the {@link Exception} service passed to the constructor.
+ * The acceptor tasks run in a loop while the connector is running and repeatedly call the abstract {@link #accept(int)} method.
+ * The implementation of the accept method must:
+ * <nl>
+ * <li>block waiting for new connections
+ * <li>accept the connection (eg socket accept)
+ * <li>perform any configuration of the connection (eg. socket linger times)
+ * <li>call the {@link #getDefaultConnectionFactory()} {@link ConnectionFactory#newConnection(Connector, org.eclipse.jetty.io.EndPoint)}
+ * method to create a new Connection instance.
+ * </nl>
+ * The default number of acceptor tasks is the minimum of 1 and half the number of available CPUs. Having more acceptors may reduce
+ * the latency for servers that see a high rate of new connections (eg HTTP/1.0 without keep-alive).  Typically the default is
+ * sufficient for modern persistent protocols (HTTP/1.1, SPDY etc.)
  */
-public abstract class AbstractConnector extends AggregateLifeCycle implements HttpBuffers, Connector, Dumpable
+ at ManagedObject("Abstract implementation of the Connector Interface")
+public abstract class AbstractConnector extends ContainerLifeCycle implements Connector, Dumpable
 {
-    private static final Logger LOG = Log.getLogger(AbstractConnector.class);
-
+    protected final Logger LOG = Log.getLogger(getClass());
+    // Order is important on server side, so we use a LinkedHashMap
+    private final Map<String, ConnectionFactory> _factories = new LinkedHashMap<>();
+    private final Server _server;
+    private final Executor _executor;
+    private final Scheduler _scheduler;
+    private final ByteBufferPool _byteBufferPool;
+    private final Thread[] _acceptors;
+    private final Set<EndPoint> _endpoints = Collections.newSetFromMap(new ConcurrentHashMap<EndPoint, Boolean>());
+    private final Set<EndPoint> _immutableEndPoints = Collections.unmodifiableSet(_endpoints);
+    private volatile CountDownLatch _stopping;
+    private long _idleTimeout = 30000;
+    private String _defaultProtocol;
+    private ConnectionFactory _defaultConnectionFactory;
     private String _name;
+    private int _acceptorPriorityDelta;
 
-    private Server _server;
-    private ThreadPool _threadPool;
-    private String _host;
-    private int _port = 0;
-    private String _integralScheme = HttpSchemes.HTTPS;
-    private int _integralPort = 0;
-    private String _confidentialScheme = HttpSchemes.HTTPS;
-    private int _confidentialPort = 0;
-    private int _acceptQueueSize = 0;
-    private int _acceptors = 1;
-    private int _acceptorPriorityOffset = 0;
-    private boolean _useDNS;
-    private boolean _forwarded;
-    private String _hostHeader;
-
-    private String _forwardedHostHeader = HttpHeaders.X_FORWARDED_HOST;
-    private String _forwardedServerHeader = HttpHeaders.X_FORWARDED_SERVER;
-    private String _forwardedForHeader = HttpHeaders.X_FORWARDED_FOR;
-    private String _forwardedProtoHeader = HttpHeaders.X_FORWARDED_PROTO;
-    private String _forwardedCipherSuiteHeader;
-    private String _forwardedSslSessionIdHeader;
-    private boolean _reuseAddress = true;
-
-    protected int _maxIdleTime = 200000;
-    protected int _lowResourceMaxIdleTime = -1;
-    protected int _soLingerTime = -1;
-
-    private transient Thread[] _acceptorThreads;
-
-    private final AtomicLong _statsStartedAt = new AtomicLong(-1L);
-
-    /** connections to server */
-    private final CounterStatistic _connectionStats = new CounterStatistic();
-    /** requests per connection */
-    private final SampleStatistic _requestStats = new SampleStatistic();
-    /** duration of a connection */
-    private final SampleStatistic _connectionDurationStats = new SampleStatistic();
 
-    protected final HttpBuffersImpl _buffers = new HttpBuffersImpl();
-
-    /* ------------------------------------------------------------ */
     /**
+     * @param server The server this connector will be added to. Must not be null.
+     * @param executor An executor for this connector or null to use the servers executor
+     * @param scheduler A scheduler for this connector or null to either a {@link Scheduler} set as a server bean or if none set, then a new {@link ScheduledExecutorScheduler} instance.
+     * @param pool A buffer pool for this connector or null to either a {@link ByteBufferPool} set as a server bean or none set, the new  {@link ArrayByteBufferPool} instance.
+     * @param acceptors the number of acceptor threads to use, or -1 for a default value. If 0, then no acceptor threads will be launched and some other mechanism will need to be used to accept new connections.
+     * @param factories The Connection Factories to use.
      */
-    public AbstractConnector()
-    {
-        addBean(_buffers);
-    }
-
-    /* ------------------------------------------------------------ */
-    /*
-     */
-    public Server getServer()
+    public AbstractConnector(
+            Server server,
+            Executor executor,
+            Scheduler scheduler,
+            ByteBufferPool pool,
+            int acceptors,
+            ConnectionFactory... factories)
     {
-        return _server;
-    }
+        _server=server;
+        _executor=executor!=null?executor:_server.getThreadPool();
+        if (scheduler==null)
+            scheduler=_server.getBean(Scheduler.class);
+        _scheduler=scheduler!=null?scheduler:new ScheduledExecutorScheduler();
+        if (pool==null)
+            pool=_server.getBean(ByteBufferPool.class);
+        _byteBufferPool = pool!=null?pool:new ArrayByteBufferPool();
 
-    /* ------------------------------------------------------------ */
-    public void setServer(Server server)
-    {
-        _server = server;
-    }
+        addBean(_server,false);
+        addBean(_executor);
+        if (executor==null)
+            unmanage(_executor); // inherited from server
+        addBean(_scheduler);
+        addBean(_byteBufferPool);
 
-    /* ------------------------------------------------------------ */
-    public ThreadPool getThreadPool()
-    {
-        return _threadPool;
-    }
+        for (ConnectionFactory factory:factories)
+            addConnectionFactory(factory);
 
-    /* ------------------------------------------------------------ */
-    /** Set the ThreadPool.
-     * The threadpool passed is added via {@link #addBean(Object)} so that 
-     * it's lifecycle may be managed as a {@link AggregateLifeCycle}.
-     * @param pool the threadPool to set
-     */
-    public void setThreadPool(ThreadPool pool)
-    {
-        removeBean(_threadPool);
-        _threadPool = pool;
-        addBean(_threadPool);
+        int cores = Runtime.getRuntime().availableProcessors();
+        if (acceptors < 0)
+            acceptors=Math.max(1, Math.min(4,cores/8));        
+        if (acceptors > cores)
+            LOG.warn("Acceptors should be <= availableProcessors: " + this);
+        _acceptors = new Thread[acceptors];
     }
 
-    /* ------------------------------------------------------------ */
-    /**
-     */
-    public void setHost(String host)
-    {
-        _host = host;
-    }
 
-    /* ------------------------------------------------------------ */
-    /*
-     */
-    public String getHost()
+    @Override
+    public Server getServer()
     {
-        return _host;
+        return _server;
     }
 
-    /* ------------------------------------------------------------ */
-    public void setPort(int port)
+    @Override
+    public Executor getExecutor()
     {
-        _port = port;
+        return _executor;
     }
 
-    /* ------------------------------------------------------------ */
-    public int getPort()
+    @Override
+    public ByteBufferPool getByteBufferPool()
     {
-        return _port;
+        return _byteBufferPool;
     }
 
-    /* ------------------------------------------------------------ */
-    /**
-     * @return Returns the maxIdleTime.
-     */
-    public int getMaxIdleTime()
+    @Override
+    @ManagedAttribute("Idle timeout")
+    public long getIdleTimeout()
     {
-        return _maxIdleTime;
+        return _idleTimeout;
     }
 
-    /* ------------------------------------------------------------ */
     /**
-     * Set the maximum Idle time for a connection, which roughly translates to the {@link Socket#setSoTimeout(int)} call, although with NIO implementations
-     * other mechanisms may be used to implement the timeout. The max idle time is applied:
+     * <p>Sets the maximum Idle time for a connection, which roughly translates to the {@link Socket#setSoTimeout(int)}
+     * call, although with NIO implementations other mechanisms may be used to implement the timeout.</p>
+     * <p>The max idle time is applied:</p>
      * <ul>
-     * <li>When waiting for a new request to be received on a connection</li>
-     * <li>When reading the headers and content of a request</li>
-     * <li>When writing the headers and content of a response</li>
+     * <li>When waiting for a new message to be received on a connection</li>
+     * <li>When waiting for a new message to be sent on a connection</li>
      * </ul>
-     * Jetty interprets this value as the maximum time between some progress being made on the connection. So if a single byte is read or written, then the
-     * timeout (if implemented by jetty) is reset. However, in many instances, the reading/writing is delegated to the JVM, and the semantic is more strictly
-     * enforced as the maximum time a single read/write operation can take. Note, that as Jetty supports writes of memory mapped file buffers, then a write may
-     * take many 10s of seconds for large content written to a slow device.
-     * <p>
-     * Previously, Jetty supported separate idle timeouts and IO operation timeouts, however the expense of changing the value of soTimeout was significant, so
-     * these timeouts were merged. With the advent of NIO, it may be possible to again differentiate these values (if there is demand).
+     * <p>This value is interpreted as the maximum time between some progress being made on the connection.
+     * So if a single byte is read or written, then the timeout is reset.</p>
      *
-     * @param maxIdleTime
-     *            The maxIdleTime to set.
+     * @param idleTimeout the idle timeout
      */
-    public void setMaxIdleTime(int maxIdleTime)
+    public void setIdleTimeout(long idleTimeout)
     {
-        _maxIdleTime = maxIdleTime;
+        _idleTimeout = idleTimeout;
     }
 
-    /* ------------------------------------------------------------ */
     /**
-     * @return Returns the maxIdleTime when resources are low.
+     * @return Returns the number of acceptor threads.
      */
-    public int getLowResourcesMaxIdleTime()
+    @ManagedAttribute("number of acceptor threads")
+    public int getAcceptors()
     {
-        return _lowResourceMaxIdleTime;
+        return _acceptors.length;
     }
 
-    /* ------------------------------------------------------------ */
-    /**
-     * @param maxIdleTime
-     *            The maxIdleTime to set when resources are low.
-     */
-    public void setLowResourcesMaxIdleTime(int maxIdleTime)
+    @Override
+    protected void doStart() throws Exception
     {
-        _lowResourceMaxIdleTime = maxIdleTime;
-    }
+        _defaultConnectionFactory = getConnectionFactory(_defaultProtocol);
+        if(_defaultConnectionFactory==null)
+            throw new IllegalStateException("No protocol factory for default protocol: "+_defaultProtocol);
 
-    /* ------------------------------------------------------------ */
-    /**
-     * @return Returns the maxIdleTime when resources are low.
-     * @deprecated
-     */
-    @Deprecated
-    public final int getLowResourceMaxIdleTime()
-    {
-        return getLowResourcesMaxIdleTime();
-    }
+        super.doStart();
 
-    /* ------------------------------------------------------------ */
-    /**
-     * @param maxIdleTime
-     *            The maxIdleTime to set when resources are low.
-     * @deprecated
-     */
-    @Deprecated
-    public final void setLowResourceMaxIdleTime(int maxIdleTime)
-    {
-        setLowResourcesMaxIdleTime(maxIdleTime);
+        _stopping=new CountDownLatch(_acceptors.length);
+        for (int i = 0; i < _acceptors.length; i++)
+        {
+            Acceptor a = new Acceptor(i);
+            addBean(a);
+            getExecutor().execute(a);
+        }
+
+        LOG.info("Started {}", this);
     }
 
-    /* ------------------------------------------------------------ */
-    /**
-     * @return Returns the soLingerTime.
-     */
-    public int getSoLingerTime()
+
+    protected void interruptAcceptors()
     {
-        return _soLingerTime;
+        synchronized (this)
+        {
+            for (Thread thread : _acceptors)
+            {
+                if (thread != null)
+                    thread.interrupt();
+            }
+        }
     }
 
-    /* ------------------------------------------------------------ */
-    /**
-     * @return Returns the acceptQueueSize.
-     */
-    public int getAcceptQueueSize()
+    @Override
+    public Future<Void> shutdown()
     {
-        return _acceptQueueSize;
+        return new FutureCallback(true);
     }
 
-    /* ------------------------------------------------------------ */
-    /**
-     * @param acceptQueueSize
-     *            The acceptQueueSize to set.
-     */
-    public void setAcceptQueueSize(int acceptQueueSize)
+    @Override
+    protected void doStop() throws Exception
     {
-        _acceptQueueSize = acceptQueueSize;
+        // Tell the acceptors we are stopping
+        interruptAcceptors();
+
+        // If we have a stop timeout
+        long stopTimeout = getStopTimeout();
+        CountDownLatch stopping=_stopping;
+        if (stopTimeout > 0 && stopping!=null)
+            stopping.await(stopTimeout,TimeUnit.MILLISECONDS);
+        _stopping=null;
+
+        super.doStop();
+        
+        for (Acceptor a : getBeans(Acceptor.class))
+            removeBean(a);
+
+        LOG.info("Stopped {}", this);
     }
 
-    /* ------------------------------------------------------------ */
-    /**
-     * @return Returns the number of acceptor threads.
-     */
-    public int getAcceptors()
+    public void join() throws InterruptedException
     {
-        return _acceptors;
+        join(0);
     }
 
-    /* ------------------------------------------------------------ */
-    /**
-     * @param acceptors
-     *            The number of acceptor threads to set.
-     */
-    public void setAcceptors(int acceptors)
+    public void join(long timeout) throws InterruptedException
     {
-        if (acceptors > 2 * Runtime.getRuntime().availableProcessors())
-            LOG.warn("Acceptors should be <=2*availableProcessors: " + this);
-        _acceptors = acceptors;
+        synchronized (this)
+        {
+            for (Thread thread : _acceptors)
+                if (thread != null)
+                    thread.join(timeout);
+        }
     }
 
+    protected abstract void accept(int acceptorID) throws IOException, InterruptedException;
+
+
     /* ------------------------------------------------------------ */
     /**
-     * @param soLingerTime
-     *            The soLingerTime to set or -1 to disable.
+     * @return Is the connector accepting new connections
      */
-    public void setSoLingerTime(int soLingerTime)
+    protected boolean isAccepting()
     {
-        _soLingerTime = soLingerTime;
+        return isRunning();
     }
 
-    /* ------------------------------------------------------------ */
     @Override
-    protected void doStart() throws Exception
+    public ConnectionFactory getConnectionFactory(String protocol)
     {
-        if (_server == null)
-            throw new IllegalStateException("No server");
-
-        // open listener port
-        open();
-
-        if (_threadPool == null)
+        synchronized (_factories)
         {
-            _threadPool = _server.getThreadPool();
-            addBean(_threadPool,false);
+            return _factories.get(protocol.toLowerCase(Locale.ENGLISH));
         }
+    }
 
-        super.doStart();
-
-        // Start selector thread
-        synchronized (this)
+    @Override
+    public <T> T getConnectionFactory(Class<T> factoryType)
+    {
+        synchronized (_factories)
         {
-            _acceptorThreads = new Thread[getAcceptors()];
-
-            for (int i = 0; i < _acceptorThreads.length; i++)
-                if (!_threadPool.dispatch(new Acceptor(i)))
-                    throw new IllegalStateException("!accepting");
-            if (_threadPool.isLowOnThreads())
-                LOG.warn("insufficient threads configured for {}",this);
+            for (ConnectionFactory f : _factories.values())
+                if (factoryType.isAssignableFrom(f.getClass()))
+                    return (T)f;
+            return null;
         }
-
-        LOG.info("Started {}",this);
     }
 
-    /* ------------------------------------------------------------ */
-    @Override
-    protected void doStop() throws Exception
+    public void addConnectionFactory(ConnectionFactory factory)
     {
-        try
+        synchronized (_factories)
         {
-            close();
+            ConnectionFactory old=_factories.remove(factory.getProtocol());
+            if (old!=null)
+                removeBean(old);
+            _factories.put(factory.getProtocol().toLowerCase(Locale.ENGLISH), factory);
+            addBean(factory);
+            if (_defaultProtocol==null)
+                _defaultProtocol=factory.getProtocol();
         }
-        catch (IOException e)
+    }
+
+    public ConnectionFactory removeConnectionFactory(String protocol)
+    {
+        synchronized (_factories)
         {
-            LOG.warn(e);
+            ConnectionFactory factory= _factories.remove(protocol.toLowerCase(Locale.ENGLISH));
+            removeBean(factory);
+            return factory;
         }
+    }
 
-        super.doStop();
-
-        Thread[] acceptors;
-        synchronized (this)
+    @Override
+    public Collection<ConnectionFactory> getConnectionFactories()
+    {
+        synchronized (_factories)
         {
-            acceptors = _acceptorThreads;
-            _acceptorThreads = null;
+            return _factories.values();
         }
-        if (acceptors != null)
+    }
+
+    public void setConnectionFactories(Collection<ConnectionFactory> factories)
+    {
+        synchronized (_factories)
         {
-            for (Thread thread : acceptors)
-            {
-                if (thread != null)
-                    thread.interrupt();
-            }
+            List<ConnectionFactory> existing = new ArrayList<>(_factories.values());
+            for (ConnectionFactory factory: existing)
+                removeConnectionFactory(factory.getProtocol());
+            for (ConnectionFactory factory: factories)
+                if (factory!=null)
+                    addConnectionFactory(factory);
         }
     }
 
+    @ManagedAttribute("The priority delta to apply to acceptor threads")
+    public int getAcceptorPriorityDelta()
+    {
+        return _acceptorPriorityDelta;
+    }
+
     /* ------------------------------------------------------------ */
-    public void join() throws InterruptedException
+    /** Set the acceptor thread priority delta.
+     * <p>This allows the acceptor thread to run at a different priority.
+     * Typically this would be used to lower the priority to give preference 
+     * to handling previously accepted connections rather than accepting 
+     * new connections</p>
+     * @param acceptorPriorityDelta
+     */
+    public void setAcceptorPriorityDelta(int acceptorPriorityDelta)
     {
-        Thread[] threads;
-        synchronized(this)
+        int old=_acceptorPriorityDelta;
+        _acceptorPriorityDelta = acceptorPriorityDelta;
+        if (old!=acceptorPriorityDelta && isStarted())
         {
-            threads=_acceptorThreads;
+            for (Thread thread : _acceptors)
+                thread.setPriority(Math.max(Thread.MIN_PRIORITY,Math.min(Thread.MAX_PRIORITY,thread.getPriority()-old+acceptorPriorityDelta)));
         }
-        if (threads != null)
-            for (Thread thread : threads)
-                if (thread != null)
-                    thread.join();
     }
 
-    /* ------------------------------------------------------------ */
-    protected void configure(Socket socket) throws IOException
+    @Override
+    @ManagedAttribute("Protocols supported by this connector")
+    public List<String> getProtocols()
     {
-        try
+        synchronized (_factories)
         {
-            socket.setTcpNoDelay(true);
-            if (_soLingerTime >= 0)
-                socket.setSoLinger(true,_soLingerTime / 1000);
-            else
-                socket.setSoLinger(false,0);
+            return new ArrayList<>(_factories.keySet());
         }
-        catch (Exception e)
+    }
+
+    public void clearConnectionFactories()
+    {
+        synchronized (_factories)
         {
-            LOG.ignore(e);
+            _factories.clear();
         }
     }
 
-    /* ------------------------------------------------------------ */
-    public void customize(EndPoint endpoint, Request request) throws IOException
+    @ManagedAttribute("This connector's default protocol")
+    public String getDefaultProtocol()
     {
-        if (isForwarded())
-            checkForwardedHeaders(endpoint,request);
+        return _defaultProtocol;
     }
 
-    /* ------------------------------------------------------------ */
-    protected void checkForwardedHeaders(EndPoint endpoint, Request request) throws IOException
+    public void setDefaultProtocol(String defaultProtocol)
     {
-        HttpFields httpFields = request.getConnection().getRequestFields();
+        _defaultProtocol = defaultProtocol.toLowerCase(Locale.ENGLISH);
+        if (isRunning())
+            _defaultConnectionFactory=getConnectionFactory(_defaultProtocol);
+    }
 
-        // Do SSL first
-        if (getForwardedCipherSuiteHeader()!=null)
-        {
-            String cipher_suite=httpFields.getStringField(getForwardedCipherSuiteHeader());
-            if (cipher_suite!=null)
-                request.setAttribute("javax.servlet.request.cipher_suite",cipher_suite);
-        }
-        if (getForwardedSslSessionIdHeader()!=null)
-        {
-            String ssl_session_id=httpFields.getStringField(getForwardedSslSessionIdHeader());
-            if(ssl_session_id!=null)
-            {
-                request.setAttribute("javax.servlet.request.ssl_session_id", ssl_session_id);
-                request.setScheme(HttpSchemes.HTTPS);
-            }
-        }
+    @Override
+    public ConnectionFactory getDefaultConnectionFactory()
+    {
+        if (isStarted())
+            return _defaultConnectionFactory;
+        return getConnectionFactory(_defaultProtocol);
+    }
 
-        // Retrieving headers from the request
-        String forwardedHost = getLeftMostFieldValue(httpFields,getForwardedHostHeader());
-        String forwardedServer = getLeftMostFieldValue(httpFields,getForwardedServerHeader());
-        String forwardedFor = getLeftMostFieldValue(httpFields,getForwardedForHeader());
-        String forwardedProto = getLeftMostFieldValue(httpFields,getForwardedProtoHeader());
+    private class Acceptor implements Runnable
+    {
+        private final int _acceptor;
+        private String _name;
 
-        if (_hostHeader != null)
-        {
-            // Update host header
-            httpFields.put(HttpHeaders.HOST_BUFFER,_hostHeader);
-            request.setServerName(null);
-            request.setServerPort(-1);
-            request.getServerName();
-        }
-        else if (forwardedHost != null)
-        {
-            // Update host header
-            httpFields.put(HttpHeaders.HOST_BUFFER,forwardedHost);
-            request.setServerName(null);
-            request.setServerPort(-1);
-            request.getServerName();
-        }
-        else if (forwardedServer != null)
+        private Acceptor(int id)
         {
-            // Use provided server name
-            request.setServerName(forwardedServer);
+            _acceptor = id;
         }
 
-        if (forwardedFor != null)
+        @Override
+        public void run()
         {
-            request.setRemoteAddr(forwardedFor);
-            InetAddress inetAddress = null;
+            final Thread thread = Thread.currentThread();
+            String name=thread.getName();
+            _name=String.format("%s-acceptor-%d@%x-%s",name,_acceptor,hashCode(),AbstractConnector.this.toString());
+            thread.setName(_name);
+            
+            int priority=thread.getPriority();
+            if (_acceptorPriorityDelta!=0)
+                thread.setPriority(Math.max(Thread.MIN_PRIORITY,Math.min(Thread.MAX_PRIORITY,priority+_acceptorPriorityDelta)));
+
+            synchronized (AbstractConnector.this)
+            {
+                _acceptors[_acceptor] = thread;
+            }
 
-            if (_useDNS)
+            try
             {
-                try
+                while (isAccepting())
                 {
-                    inetAddress = InetAddress.getByName(forwardedFor);
+                    try
+                    {
+                        accept(_acceptor);
+                    }
+                    catch (Throwable e)
+                    {
+                        if (isAccepting())
+                            LOG.warn(e);
+                        else
+                            LOG.ignore(e);
+                    }
                 }
-                catch (UnknownHostException e)
+            }
+            finally
+            {
+                thread.setName(name);
+                if (_acceptorPriorityDelta!=0)
+                    thread.setPriority(priority);
+
+                synchronized (AbstractConnector.this)
                 {
-                    LOG.ignore(e);
+                    _acceptors[_acceptor] = null;
                 }
+                CountDownLatch stopping=_stopping;
+                if (stopping!=null)
+                    stopping.countDown();
             }
-
-            request.setRemoteHost(inetAddress == null?forwardedFor:inetAddress.getHostName());
-        }
-
-        if (forwardedProto != null)
-        {
-            request.setScheme(forwardedProto);
         }
-    }
-
-    /* ------------------------------------------------------------ */
-    protected String getLeftMostFieldValue(HttpFields fields, String header)
-    {
-        if (header == null)
-            return null;
-
-        String headerValue = fields.getStringField(header);
-
-        if (headerValue == null)
-            return null;
-
-        int commaIndex = headerValue.indexOf(',');
-
-        if (commaIndex == -1)
+        
+        @Override
+        public String toString()
         {
-            // Single value
-            return headerValue;
+            String name=_name;
+            if (name==null)
+                return String.format("acceptor-%d@%x", _acceptor, hashCode());
+            return name;
         }
-
-        // The left-most value is the farthest downstream client
-        return headerValue.substring(0,commaIndex);
-    }
-
-    /* ------------------------------------------------------------ */
-    public void persist(EndPoint endpoint) throws IOException
-    {
+        
     }
 
-    /* ------------------------------------------------------------ */
-    /*
-     * @see org.eclipse.jetty.server.Connector#getConfidentialPort()
-     */
-    public int getConfidentialPort()
-    {
-        return _confidentialPort;
-    }
 
-    /* ------------------------------------------------------------ */
-    /* ------------------------------------------------------------ */
-    /*
-     * @see org.eclipse.jetty.server.Connector#getConfidentialScheme()
-     */
-    public String getConfidentialScheme()
-    {
-        return _confidentialScheme;
-    }
 
-    /* ------------------------------------------------------------ */
-    /*
-     * @see org.eclipse.jetty.server.Connector#isConfidential(org.eclipse.jetty.server .Request)
-     */
-    public boolean isIntegral(Request request)
-    {
-        return false;
-    }
 
-    /* ------------------------------------------------------------ */
-    /*
-     * @see org.eclipse.jetty.server.Connector#getConfidentialPort()
-     */
-    public int getIntegralPort()
-    {
-        return _integralPort;
-    }
+//    protected void connectionOpened(Connection connection)
+//    {
+//        _stats.connectionOpened();
+//        connection.onOpen();
+//    }
+//
+//    protected void connectionClosed(Connection connection)
+//    {
+//        connection.onClose();
+//        long duration = System.currentTimeMillis() - connection.getEndPoint().getCreatedTimeStamp();
+//        _stats.connectionClosed(duration, connection.getMessagesIn(), connection.getMessagesOut());
+//    }
+//
+//    public void connectionUpgraded(Connection oldConnection, Connection newConnection)
+//    {
+//        oldConnection.onClose();
+//        _stats.connectionUpgraded(oldConnection.getMessagesIn(), oldConnection.getMessagesOut());
+//        newConnection.onOpen();
+//    }
 
-    /* ------------------------------------------------------------ */
-    /*
-     * @see org.eclipse.jetty.server.Connector#getIntegralScheme()
-     */
-    public String getIntegralScheme()
+    @Override
+    public Collection<EndPoint> getConnectedEndPoints()
     {
-        return _integralScheme;
+        return _immutableEndPoints;
     }
 
-    /* ------------------------------------------------------------ */
-    /*
-     * @see org.eclipse.jetty.server.Connector#isConfidential(org.eclipse.jetty.server.Request)
-     */
-    public boolean isConfidential(Request request)
+    protected void onEndPointOpened(EndPoint endp)
     {
-        return _forwarded && request.getScheme().equalsIgnoreCase(HttpSchemes.HTTPS);
+        _endpoints.add(endp);
     }
 
-    /* ------------------------------------------------------------ */
-    /**
-     * @param confidentialPort
-     *            The confidentialPort to set.
-     */
-    public void setConfidentialPort(int confidentialPort)
+    protected void onEndPointClosed(EndPoint endp)
     {
-        _confidentialPort = confidentialPort;
+        _endpoints.remove(endp);
     }
 
-    /* ------------------------------------------------------------ */
-    /**
-     * @param confidentialScheme
-     *            The confidentialScheme to set.
-     */
-    public void setConfidentialScheme(String confidentialScheme)
+    @Override
+    public Scheduler getScheduler()
     {
-        _confidentialScheme = confidentialScheme;
+        return _scheduler;
     }
 
-    /* ------------------------------------------------------------ */
-    /**
-     * @param integralPort
-     *            The integralPort to set.
-     */
-    public void setIntegralPort(int integralPort)
+    @Override
+    public String getName()
     {
-        _integralPort = integralPort;
+        return _name;
     }
-
+    
     /* ------------------------------------------------------------ */
     /**
-     * @param integralScheme
-     *            The integralScheme to set.
+     * Set a connector name.   A context may be configured with
+     * virtual hosts in the form "@contextname" and will only serve
+     * requests from the named connector,
+     * @param name A connector name.
      */
-    public void setIntegralScheme(String integralScheme)
+    public void setName(String name)
     {
-        _integralScheme = integralScheme;
+        _name=name;
     }
-
-    /* ------------------------------------------------------------ */
-    protected abstract void accept(int acceptorID) throws IOException, InterruptedException;
-
-    /* ------------------------------------------------------------ */
-    public void stopAccept(int acceptorID) throws Exception
+    
+    @Override
+    public String toString()
     {
-    }
-
-    /* ------------------------------------------------------------ */
-    public boolean getResolveNames()
-    {
-        return _useDNS;
-    }
-
-    /* ------------------------------------------------------------ */
-    public void setResolveNames(boolean resolve)
-    {
-        _useDNS = resolve;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Is reverse proxy handling on?
-     *
-     * @return true if this connector is checking the x-forwarded-for/host/server headers
-     */
-    public boolean isForwarded()
-    {
-        return _forwarded;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Set reverse proxy handling. If set to true, then the X-Forwarded headers (or the headers set in their place) are looked for to set the request protocol,
-     * host, server and client ip.
-     *
-     * @param check
-     *            true if this connector is checking the x-forwarded-for/host/server headers
-     * @see #setForwardedForHeader(String)
-     * @see #setForwardedHostHeader(String)
-     * @see #setForwardedProtoHeader(String)
-     * @see #setForwardedServerHeader(String)
-     */
-    public void setForwarded(boolean check)
-    {
-        if (check)
-            LOG.debug("{} is forwarded",this);
-        _forwarded = check;
-    }
-
-    /* ------------------------------------------------------------ */
-    public String getHostHeader()
-    {
-        return _hostHeader;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Set a forced valued for the host header to control what is returned by {@link ServletRequest#getServerName()} and {@link ServletRequest#getServerPort()}.
-     * This value is only used if {@link #isForwarded()} is true.
-     *
-     * @param hostHeader
-     *            The value of the host header to force.
-     */
-    public void setHostHeader(String hostHeader)
-    {
-        _hostHeader = hostHeader;
-    }
-
-    /* ------------------------------------------------------------ */
-    /*
-     *
-     * @see #setForwarded(boolean)
-     */
-    public String getForwardedHostHeader()
-    {
-        return _forwardedHostHeader;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param forwardedHostHeader
-     *            The header name for forwarded hosts (default x-forwarded-host)
-     * @see #setForwarded(boolean)
-     */
-    public void setForwardedHostHeader(String forwardedHostHeader)
-    {
-        _forwardedHostHeader = forwardedHostHeader;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return the header name for forwarded server.
-     * @see #setForwarded(boolean)
-     */
-    public String getForwardedServerHeader()
-    {
-        return _forwardedServerHeader;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param forwardedServerHeader
-     *            The header name for forwarded server (default x-forwarded-server)
-     * @see #setForwarded(boolean)
-     */
-    public void setForwardedServerHeader(String forwardedServerHeader)
-    {
-        _forwardedServerHeader = forwardedServerHeader;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see #setForwarded(boolean)
-     */
-    public String getForwardedForHeader()
-    {
-        return _forwardedForHeader;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param forwardedRemoteAddressHeader
-     *            The header name for forwarded for (default x-forwarded-for)
-     * @see #setForwarded(boolean)
-     */
-    public void setForwardedForHeader(String forwardedRemoteAddressHeader)
-    {
-        _forwardedForHeader = forwardedRemoteAddressHeader;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Get the forwardedProtoHeader.
-     *
-     * @return the forwardedProtoHeader (default X-Forwarded-For)
-     * @see #setForwarded(boolean)
-     */
-    public String getForwardedProtoHeader()
-    {
-        return _forwardedProtoHeader;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Set the forwardedProtoHeader.
-     *
-     * @param forwardedProtoHeader
-     *            the forwardedProtoHeader to set (default X-Forwarded-For)
-     * @see #setForwarded(boolean)
-     */
-    public void setForwardedProtoHeader(String forwardedProtoHeader)
-    {
-        _forwardedProtoHeader = forwardedProtoHeader;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return The header name holding a forwarded cipher suite (default null)
-     */
-    public String getForwardedCipherSuiteHeader()
-    {
-        return _forwardedCipherSuiteHeader;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param forwardedCipherSuite
-     *            The header name holding a forwarded cipher suite (default null)
-     */
-    public void setForwardedCipherSuiteHeader(String forwardedCipherSuite)
-    {
-        _forwardedCipherSuiteHeader = forwardedCipherSuite;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return The header name holding a forwarded SSL Session ID (default null)
-     */
-    public String getForwardedSslSessionIdHeader()
-    {
-        return _forwardedSslSessionIdHeader;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param forwardedSslSessionId
-     *            The header name holding a forwarded SSL Session ID (default null)
-     */
-    public void setForwardedSslSessionIdHeader(String forwardedSslSessionId)
-    {
-        _forwardedSslSessionIdHeader = forwardedSslSessionId;
-    }
-
-    public int getRequestBufferSize()
-    {
-        return _buffers.getRequestBufferSize();
-    }
-
-    public void setRequestBufferSize(int requestBufferSize)
-    {
-        _buffers.setRequestBufferSize(requestBufferSize);
-    }
-
-    public int getRequestHeaderSize()
-    {
-        return _buffers.getRequestHeaderSize();
-    }
-
-    public void setRequestHeaderSize(int requestHeaderSize)
-    {
-        _buffers.setRequestHeaderSize(requestHeaderSize);
-    }
-
-    public int getResponseBufferSize()
-    {
-        return _buffers.getResponseBufferSize();
-    }
-
-    public void setResponseBufferSize(int responseBufferSize)
-    {
-        _buffers.setResponseBufferSize(responseBufferSize);
-    }
-
-    public int getResponseHeaderSize()
-    {
-        return _buffers.getResponseHeaderSize();
-    }
-
-    public void setResponseHeaderSize(int responseHeaderSize)
-    {
-        _buffers.setResponseHeaderSize(responseHeaderSize);
-    }
-
-    public Type getRequestBufferType()
-    {
-        return _buffers.getRequestBufferType();
-    }
-
-    public Type getRequestHeaderType()
-    {
-        return _buffers.getRequestHeaderType();
-    }
-
-    public Type getResponseBufferType()
-    {
-        return _buffers.getResponseBufferType();
-    }
-
-    public Type getResponseHeaderType()
-    {
-        return _buffers.getResponseHeaderType();
-    }
-
-    public void setRequestBuffers(Buffers requestBuffers)
-    {
-        _buffers.setRequestBuffers(requestBuffers);
-    }
-
-    public void setResponseBuffers(Buffers responseBuffers)
-    {
-        _buffers.setResponseBuffers(responseBuffers);
-    }
-
-    public Buffers getRequestBuffers()
-    {
-        return _buffers.getRequestBuffers();
-    }
-
-    public Buffers getResponseBuffers()
-    {
-        return _buffers.getResponseBuffers();
-    }
-
-    public void setMaxBuffers(int maxBuffers)
-    {
-        _buffers.setMaxBuffers(maxBuffers);
-    }
-
-    public int getMaxBuffers()
-    {
-        return _buffers.getMaxBuffers();
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public String toString()
-    {
-        return String.format("%s@%s:%d",
-                getClass().getSimpleName(),
-                getHost()==null?"0.0.0.0":getHost(),
-                getLocalPort()<=0?getPort():getLocalPort());
-    }
-
-    /* ------------------------------------------------------------ */
-    /* ------------------------------------------------------------ */
-    /* ------------------------------------------------------------ */
-    private class Acceptor implements Runnable
-    {
-        int _acceptor = 0;
-
-        Acceptor(int id)
-        {
-            _acceptor = id;
-        }
-
-        /* ------------------------------------------------------------ */
-        public void run()
-        {
-            Thread current = Thread.currentThread();
-            String name;
-            synchronized (AbstractConnector.this)
-            {
-                if (_acceptorThreads == null)
-                    return;
-
-                _acceptorThreads[_acceptor] = current;
-                name = _acceptorThreads[_acceptor].getName();
-                current.setName(name + " Acceptor" + _acceptor + " " + AbstractConnector.this);
-            }
-            int old_priority = current.getPriority();
-
-            try
-            {
-                current.setPriority(old_priority - _acceptorPriorityOffset);
-                while (isRunning() && getConnection() != null)
-                {
-                    try
-                    {
-                        accept(_acceptor);
-                    }
-                    catch (EofException e)
-                    {
-                        LOG.ignore(e);
-                    }
-                    catch (IOException e)
-                    {
-                        LOG.ignore(e);
-                    }
-                    catch (InterruptedException x)
-                    {
-                        // Connector has been stopped
-                        LOG.ignore(x);
-                    }
-                    catch (Throwable e)
-                    {
-                        LOG.warn(e);
-                    }
-                }
-            }
-            finally
-            {
-                current.setPriority(old_priority);
-                current.setName(name);
-
-                synchronized (AbstractConnector.this)
-                {
-                    if (_acceptorThreads != null)
-                        _acceptorThreads[_acceptor] = null;
-                }
-            }
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    public String getName()
-    {
-        if (_name == null)
-            _name = (getHost() == null?"0.0.0.0":getHost()) + ":" + (getLocalPort() <= 0?getPort():getLocalPort());
-        return _name;
-    }
-
-    /* ------------------------------------------------------------ */
-    public void setName(String name)
-    {
-        _name = name;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return Get the number of requests handled by this connector since last call of statsReset(). If setStatsOn(false) then this is undefined.
-     */
-    public int getRequests()
-    {
-        return (int)_requestStats.getTotal();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return Returns the connectionsDurationTotal.
-     */
-    public long getConnectionsDurationTotal()
-    {
-        return _connectionDurationStats.getTotal();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return Number of connections accepted by the server since statsReset() called. Undefined if setStatsOn(false).
-     */
-    public int getConnections()
-    {
-        return (int)_connectionStats.getTotal();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return Number of connections currently open that were opened since statsReset() called. Undefined if setStatsOn(false).
-     */
-    public int getConnectionsOpen()
-    {
-        return (int)_connectionStats.getCurrent();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return Maximum number of connections opened simultaneously since statsReset() called. Undefined if setStatsOn(false).
-     */
-    public int getConnectionsOpenMax()
-    {
-        return (int)_connectionStats.getMax();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return Mean duration in milliseconds of open connections since statsReset() called. Undefined if setStatsOn(false).
-     */
-    public double getConnectionsDurationMean()
-    {
-        return _connectionDurationStats.getMean();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return Maximum duration in milliseconds of an open connection since statsReset() called. Undefined if setStatsOn(false).
-     */
-    public long getConnectionsDurationMax()
-    {
-        return _connectionDurationStats.getMax();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return Standard deviation of duration in milliseconds of open connections since statsReset() called. Undefined if setStatsOn(false).
-     */
-    public double getConnectionsDurationStdDev()
-    {
-        return _connectionDurationStats.getStdDev();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return Mean number of requests per connection since statsReset() called. Undefined if setStatsOn(false).
-     */
-    public double getConnectionsRequestsMean()
-    {
-        return _requestStats.getMean();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return Maximum number of requests per connection since statsReset() called. Undefined if setStatsOn(false).
-     */
-    public int getConnectionsRequestsMax()
-    {
-        return (int)_requestStats.getMax();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return Standard deviation of number of requests per connection since statsReset() called. Undefined if setStatsOn(false).
-     */
-    public double getConnectionsRequestsStdDev()
-    {
-        return _requestStats.getStdDev();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Reset statistics.
-     */
-    public void statsReset()
-    {
-        updateNotEqual(_statsStartedAt,-1,System.currentTimeMillis());
-
-        _requestStats.reset();
-        _connectionStats.reset();
-        _connectionDurationStats.reset();
-    }
-
-    /* ------------------------------------------------------------ */
-    public void setStatsOn(boolean on)
-    {
-        if (on && _statsStartedAt.get() != -1)
-            return;
-
-        if (LOG.isDebugEnabled())
-            LOG.debug("Statistics on = " + on + " for " + this);
-
-        statsReset();
-        _statsStartedAt.set(on?System.currentTimeMillis():-1);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return True if statistics collection is turned on.
-     */
-    public boolean getStatsOn()
-    {
-        return _statsStartedAt.get() != -1;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return Timestamp stats were started at.
-     */
-    public long getStatsOnMs()
-    {
-        long start = _statsStartedAt.get();
-
-        return (start != -1)?(System.currentTimeMillis() - start):0;
-    }
-
-    /* ------------------------------------------------------------ */
-    protected void connectionOpened(Connection connection)
-    {
-        if (_statsStartedAt.get() == -1)
-            return;
-
-        _connectionStats.increment();
-    }
-
-    /* ------------------------------------------------------------ */
-    protected void connectionUpgraded(Connection oldConnection, Connection newConnection)
-    {
-        _requestStats.set((oldConnection instanceof AbstractHttpConnection)?((AbstractHttpConnection)oldConnection).getRequests():0);
-    }
-
-    /* ------------------------------------------------------------ */
-    protected void connectionClosed(Connection connection)
-    {
-        connection.onClose();
-
-        if (_statsStartedAt.get() == -1)
-            return;
-
-        long duration = System.currentTimeMillis() - connection.getTimeStamp();
-        int requests = (connection instanceof AbstractHttpConnection)?((AbstractHttpConnection)connection).getRequests():0;
-        _requestStats.set(requests);
-        _connectionStats.decrement();
-        _connectionDurationStats.set(duration);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return the acceptorPriority
-     */
-    public int getAcceptorPriorityOffset()
-    {
-        return _acceptorPriorityOffset;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Set the priority offset of the acceptor threads. The priority is adjusted by this amount (default 0) to either favour the acceptance of new threads and
-     * newly active connections or to favour the handling of already dispatched connections.
-     *
-     * @param offset
-     *            the amount to alter the priority of the acceptor threads.
-     */
-    public void setAcceptorPriorityOffset(int offset)
-    {
-        _acceptorPriorityOffset = offset;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return True if the the server socket will be opened in SO_REUSEADDR mode.
-     */
-    public boolean getReuseAddress()
-    {
-        return _reuseAddress;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param reuseAddress
-     *            True if the the server socket will be opened in SO_REUSEADDR mode.
-     */
-    public void setReuseAddress(boolean reuseAddress)
-    {
-        _reuseAddress = reuseAddress;
-    }
-
-    /* ------------------------------------------------------------ */
-    public boolean isLowResources()
-    {
-        if (_threadPool != null)
-            return _threadPool.isLowOnThreads();
-        return _server.getThreadPool().isLowOnThreads();
-    }
-
-    /* ------------------------------------------------------------ */
-    private void updateNotEqual(AtomicLong valueHolder, long compare, long value)
-    {
-        long oldValue = valueHolder.get();
-        while (compare != oldValue)
-        {
-            if (valueHolder.compareAndSet(oldValue,value))
-                break;
-            oldValue = valueHolder.get();
-        }
+        return String.format("%s@%x{%s}",
+                _name==null?getClass().getSimpleName():_name,
+                hashCode(),
+                getDefaultProtocol());
     }
 }
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractHttpConnection.java b/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractHttpConnection.java
deleted file mode 100644
index 85fd983..0000000
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractHttpConnection.java
+++ /dev/null
@@ -1,1273 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.server;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.PrintWriter;
-
-import javax.servlet.DispatcherType;
-import javax.servlet.RequestDispatcher;
-import javax.servlet.ServletInputStream;
-import javax.servlet.ServletOutputStream;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jetty.continuation.ContinuationThrowable;
-import org.eclipse.jetty.http.EncodedHttpURI;
-import org.eclipse.jetty.http.Generator;
-import org.eclipse.jetty.http.HttpBuffers;
-import org.eclipse.jetty.http.HttpContent;
-import org.eclipse.jetty.http.HttpException;
-import org.eclipse.jetty.http.HttpFields;
-import org.eclipse.jetty.http.HttpGenerator;
-import org.eclipse.jetty.http.HttpHeaderValues;
-import org.eclipse.jetty.http.HttpHeaders;
-import org.eclipse.jetty.http.HttpMethods;
-import org.eclipse.jetty.http.HttpParser;
-import org.eclipse.jetty.http.HttpStatus;
-import org.eclipse.jetty.http.HttpURI;
-import org.eclipse.jetty.http.HttpVersions;
-import org.eclipse.jetty.http.MimeTypes;
-import org.eclipse.jetty.http.Parser;
-import org.eclipse.jetty.http.PathMap;
-import org.eclipse.jetty.io.AbstractConnection;
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.BufferCache.CachedBuffer;
-import org.eclipse.jetty.io.Buffers;
-import org.eclipse.jetty.io.Connection;
-import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.io.EofException;
-import org.eclipse.jetty.io.RuntimeIOException;
-import org.eclipse.jetty.io.UncheckedPrintWriter;
-import org.eclipse.jetty.server.handler.ErrorHandler;
-import org.eclipse.jetty.server.nio.NIOConnector;
-import org.eclipse.jetty.server.ssl.SslConnector;
-import org.eclipse.jetty.util.QuotedStringTokenizer;
-import org.eclipse.jetty.util.StringUtil;
-import org.eclipse.jetty.util.URIUtil;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.util.resource.Resource;
-
-/**
- * <p>A HttpConnection represents the connection of a HTTP client to the server
- * and is created by an instance of a {@link Connector}. It's prime function is
- * to associate {@link Request} and {@link Response} instances with a {@link EndPoint}.
- * </p>
- * <p>
- * A connection is also the prime mechanism used by jetty to recycle objects without
- * pooling.  The {@link Request},  {@link Response}, {@link HttpParser}, {@link HttpGenerator}
- * and {@link HttpFields} instances are all recycled for the duraction of
- * a connection. Where appropriate, allocated buffers are also kept associated
- * with the connection via the parser and/or generator.
- * </p>
- * <p>
- * The connection state is held by 3 separate state machines: The request state, the
- * response state and the continuation state.  All three state machines must be driven
- * to completion for every request, and all three can complete in any order.
- * </p>
- * <p>
- * The HttpConnection support protocol upgrade.  If on completion of a request, the
- * response code is 101 (switch protocols), then the org.eclipse.jetty.io.Connection
- * request attribute is checked to see if there is a new Connection instance. If so,
- * the new connection is returned from {@link #handle()} and is used for future
- * handling of the underlying connection.   Note that for switching protocols that
- * don't use 101 responses (eg CONNECT), the response should be sent and then the
- * status code changed to 101 before returning from the handler.  Implementors
- * of new Connection types should be careful to extract any buffered data from
- * (HttpParser)http.getParser()).getHeaderBuffer() and
- * (HttpParser)http.getParser()).getBodyBuffer() to initialise their new connection.
- * </p>
- *
- */
-public abstract class AbstractHttpConnection  extends AbstractConnection
-{
-    private static final Logger LOG = Log.getLogger(AbstractHttpConnection.class);
-
-    private static final int UNKNOWN = -2;
-    private static final ThreadLocal<AbstractHttpConnection> __currentConnection = new ThreadLocal<AbstractHttpConnection>();
-
-    private int _requests;
-
-    protected final Connector _connector;
-    protected final Server _server;
-    protected final HttpURI _uri;
-
-    protected final Parser _parser;
-    protected final HttpFields _requestFields;
-    protected final Request _request;
-    protected volatile ServletInputStream _in;
-
-    protected final Generator _generator;
-    protected final HttpFields _responseFields;
-    protected final Response _response;
-    protected volatile Output _out;
-    protected volatile OutputWriter _writer;
-    protected volatile PrintWriter _printWriter;
-
-    int _include;
-
-    private Object _associatedObject; // associated object
-
-    private int _version = UNKNOWN;
-
-    private String _charset;
-    private boolean _expect = false;
-    private boolean _expect100Continue = false;
-    private boolean _expect102Processing = false;
-    private boolean _head = false;
-    private boolean _host = false;
-    private boolean _delayedHandling=false;
-    private boolean _earlyEOF = false;
-
-    /* ------------------------------------------------------------ */
-    public static AbstractHttpConnection getCurrentConnection()
-    {
-        return __currentConnection.get();
-    }
-
-    /* ------------------------------------------------------------ */
-    protected static void setCurrentConnection(AbstractHttpConnection connection)
-    {
-        __currentConnection.set(connection);
-    }
-
-    /* ------------------------------------------------------------ */
-    public AbstractHttpConnection(Connector connector, EndPoint endpoint, Server server)
-    {
-        super(endpoint);
-        _uri = StringUtil.__UTF8.equals(URIUtil.__CHARSET)?new HttpURI():new EncodedHttpURI(URIUtil.__CHARSET);
-        _connector = connector;
-        HttpBuffers ab = (HttpBuffers)_connector;
-        _parser = newHttpParser(ab.getRequestBuffers(), endpoint, new RequestHandler());
-        _requestFields = new HttpFields();
-        _responseFields = new HttpFields();
-        _request = new Request(this);
-        _response = new Response(this);
-        _generator = newHttpGenerator(ab.getResponseBuffers(), endpoint);
-        _generator.setSendServerVersion(server.getSendServerVersion());
-        _server = server;
-    }
-
-    /* ------------------------------------------------------------ */
-    protected AbstractHttpConnection(Connector connector, EndPoint endpoint, Server server,
-            Parser parser, Generator generator, Request request)
-    {
-        super(endpoint);
-
-        _uri = URIUtil.__CHARSET.equals(StringUtil.__UTF8)?new HttpURI():new EncodedHttpURI(URIUtil.__CHARSET);
-        _connector = connector;
-        _parser = parser;
-        _requestFields = new HttpFields();
-        _responseFields = new HttpFields();
-        _request = request;
-        _response = new Response(this);
-        _generator = generator;
-        _generator.setSendServerVersion(server.getSendServerVersion());
-        _server = server;
-    }
-
-    protected HttpParser newHttpParser(Buffers requestBuffers, EndPoint endpoint, HttpParser.EventHandler requestHandler)
-    {
-        return new HttpParser(requestBuffers, endpoint, requestHandler);
-    }
-
-    protected HttpGenerator newHttpGenerator(Buffers responseBuffers, EndPoint endPoint)
-    {
-        return new HttpGenerator(responseBuffers, endPoint);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return the parser used by this connection
-     */
-    public Parser getParser()
-    {
-        return _parser;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return the number of requests handled by this connection
-     */
-    public int getRequests()
-    {
-        return _requests;
-    }
-
-    /* ------------------------------------------------------------ */
-    public Server getServer()
-    {
-        return _server;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return Returns the associatedObject.
-     */
-    public Object getAssociatedObject()
-    {
-        return _associatedObject;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param associatedObject The associatedObject to set.
-     */
-    public void setAssociatedObject(Object associatedObject)
-    {
-        _associatedObject = associatedObject;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return Returns the connector.
-     */
-    public Connector getConnector()
-    {
-        return _connector;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return Returns the requestFields.
-     */
-    public HttpFields getRequestFields()
-    {
-        return _requestFields;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return Returns the responseFields.
-     */
-    public HttpFields getResponseFields()
-    {
-        return _responseFields;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Find out if the request supports CONFIDENTIAL security.
-     * @param request the incoming HTTP request
-     * @return the result of calling {@link Connector#isConfidential(Request)}, or false
-     * if there is no connector
-     */
-    public boolean isConfidential(Request request)
-    {
-        return _connector != null && _connector.isConfidential(request);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Find out if the request supports INTEGRAL security.
-     * @param request the incoming HTTP request
-     * @return the result of calling {@link Connector#isIntegral(Request)}, or false
-     * if there is no connector
-     */
-    public boolean isIntegral(Request request)
-    {
-        return _connector != null && _connector.isIntegral(request);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return <code>false</code> (this method is not yet implemented)
-     */
-    public boolean getResolveNames()
-    {
-        return _connector.getResolveNames();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return Returns the request.
-     */
-    public Request getRequest()
-    {
-        return _request;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return Returns the response.
-     */
-    public Response getResponse()
-    {
-        return _response;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Get the inputStream from the connection.
-     * <p>
-     * If the associated response has the Expect header set to 100 Continue,
-     * then accessing the input stream indicates that the handler/servlet
-     * is ready for the request body and thus a 100 Continue response is sent.
-     *
-     * @return The input stream for this connection.
-     * The stream will be created if it does not already exist.
-     * @throws IOException if the input stream cannot be retrieved
-     */
-    public ServletInputStream getInputStream() throws IOException
-    {
-        // If the client is expecting 100 CONTINUE, then send it now.
-        if (_expect100Continue)
-        {
-            // is content missing?
-            if (((HttpParser)_parser).getHeaderBuffer()==null || ((HttpParser)_parser).getHeaderBuffer().length()<2)
-            {
-                if (_generator.isCommitted())
-                    throw new IllegalStateException("Committed before 100 Continues");
-
-                ((HttpGenerator)_generator).send1xx(HttpStatus.CONTINUE_100);
-            }
-            _expect100Continue=false;
-        }
-
-        if (_in == null)
-            _in = new HttpInput(AbstractHttpConnection.this);
-        return _in;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return The output stream for this connection. The stream will be created if it does not already exist.
-     */
-    public ServletOutputStream getOutputStream()
-    {
-        if (_out == null)
-            _out = new Output();
-        return _out;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param encoding the PrintWriter encoding
-     * @return A {@link PrintWriter} wrapping the {@link #getOutputStream output stream}. The writer is created if it
-     *    does not already exist.
-     */
-    public PrintWriter getPrintWriter(String encoding)
-    {
-        getOutputStream();
-        if (_writer==null)
-        {
-            _writer=new OutputWriter();
-            if (_server.isUncheckedPrintWriter())
-                _printWriter=new UncheckedPrintWriter(_writer);
-            else
-                _printWriter = new PrintWriter(_writer)
-                {
-                    public void close()
-                    {
-                        synchronized (lock)
-                        {
-                            try
-                            {
-                                out.close();
-                            }
-                            catch (IOException e)
-                            {
-                                setError();
-                            }
-                        }
-                    }
-                };
-        }
-        _writer.setCharacterEncoding(encoding);
-        return _printWriter;
-    }
-
-    /* ------------------------------------------------------------ */
-    public boolean isResponseCommitted()
-    {
-        return _generator.isCommitted();
-    }
-
-    /* ------------------------------------------------------------ */
-    public boolean isEarlyEOF()
-    {
-        return _earlyEOF;
-    }
-
-    /* ------------------------------------------------------------ */
-    public void reset()
-    {
-        _parser.reset();
-        _parser.returnBuffers(); // TODO maybe only on unhandle
-        _requestFields.clear();
-        _request.recycle();
-        _generator.reset();
-        _generator.returnBuffers();// TODO maybe only on unhandle
-        _responseFields.clear();
-        _response.recycle();
-        _uri.clear();
-        _writer=null;
-        _earlyEOF = false;
-    }
-
-    /* ------------------------------------------------------------ */
-    protected void handleRequest() throws IOException
-    {
-        boolean error = false;
-
-        String threadName=null;
-        Throwable async_exception=null;
-        try
-        {
-            if (LOG.isDebugEnabled())
-            {
-                threadName=Thread.currentThread().getName();
-                Thread.currentThread().setName(threadName+" - "+_uri);
-            }
-
-
-            // Loop here to handle async request redispatches.
-            // The loop is controlled by the call to async.unhandle in the
-            // finally block below.  If call is from a non-blocking connector,
-            // then the unhandle will return false only if an async dispatch has
-            // already happened when unhandle is called.   For a blocking connector,
-            // the wait for the asynchronous dispatch or timeout actually happens
-            // within the call to unhandle().
-
-            final Server server=_server;
-            boolean was_continuation=_request._async.isContinuation();
-            boolean handling=_request._async.handling() && server!=null && server.isRunning();
-            while (handling)
-            {
-                _request.setHandled(false);
-
-                String info=null;
-                try
-                {
-                    _uri.getPort();
-                    String path = null;
-
-                    try
-                    {
-                        path = _uri.getDecodedPath();
-                    }
-                    catch (Exception e)
-                    {
-                        LOG.warn("Failed UTF-8 decode for request path, trying ISO-8859-1");
-                        LOG.ignore(e);
-                        path = _uri.getDecodedPath(StringUtil.__ISO_8859_1);
-                    }
-
-                    info=URIUtil.canonicalPath(path);
-                    if (info==null && !_request.getMethod().equals(HttpMethods.CONNECT))
-                    {
-                        if (path==null && _uri.getScheme()!=null && _uri.getHost()!=null)
-                        {
-                            info="/";
-                            _request.setRequestURI("");
-                        }
-                        else
-                            throw new HttpException(400);
-                    }
-                    _request.setPathInfo(info);
-
-                    if (_out!=null)
-                        _out.reopen();
-
-                    if (_request._async.isInitial())
-                    {
-                        _request.setDispatcherType(DispatcherType.REQUEST);
-                        _connector.customize(_endp, _request);
-                        server.handle(this);
-                    }
-                    else
-                    {
-                        if (_request._async.isExpired()&&!was_continuation)
-                        {
-                            async_exception = (Throwable)_request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
-                            _response.setStatus(500,async_exception==null?"Async Timeout":"Async Exception");
-                            _request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE,new Integer(500));
-                            _request.setAttribute(RequestDispatcher.ERROR_MESSAGE, _response.getReason());
-                            _request.setDispatcherType(DispatcherType.ERROR);
-                            
-                            ErrorHandler eh = _request._async.getContextHandler().getErrorHandler();
-                            if (eh instanceof ErrorHandler.ErrorPageMapper)
-                            {
-                                String error_page=((ErrorHandler.ErrorPageMapper)eh).getErrorPage((HttpServletRequest)_request._async.getRequest());
-                                if (error_page!=null)
-                                { 
-                                    AsyncContinuation.AsyncEventState state = _request._async.getAsyncEventState();
-                                    state.setPath(error_page);
-                                }
-                            }
-                        }
-                        else
-                            _request.setDispatcherType(DispatcherType.ASYNC);
-                        server.handleAsync(this);
-                    }
-                }
-                catch (ContinuationThrowable e)
-                {
-                    LOG.ignore(e);
-                }
-                catch (EofException e)
-                {
-                    async_exception=e;
-                    LOG.debug(e);
-                    error=true;
-                    _request.setHandled(true);
-                    if (!_response.isCommitted())
-                        _generator.sendError(500, null, null, true);
-                }
-                catch (RuntimeIOException e)
-                {
-                    async_exception=e;
-                    LOG.debug(e);
-                    error=true;
-                    _request.setHandled(true);
-                }
-                catch (HttpException e)
-                {
-                    LOG.debug(e);
-                    error=true;
-                    _request.setHandled(true);
-                    _response.sendError(e.getStatus(), e.getReason());
-                }
-                catch (Throwable e)
-                {
-                    async_exception=e;
-                    LOG.warn(String.valueOf(_uri),e);
-                    error=true;
-                    _request.setHandled(true);
-                    _generator.sendError(info==null?400:500, null, null, true);
-                    
-                }
-                finally
-                {
-                    // Complete async requests 
-                    if (error && _request.isAsyncStarted())
-                        _request.getAsyncContinuation().errorComplete();
-                        
-                    was_continuation=_request._async.isContinuation();
-                    handling = !_request._async.unhandle() && server.isRunning() && _server!=null;
-                }
-            }
-        }
-        finally
-        {
-            if (threadName!=null)
-                Thread.currentThread().setName(threadName);
-
-            if (_request._async.isUncompleted())
-            {
-                
-                _request._async.doComplete(async_exception);
-
-                if (_expect100Continue)
-                {
-                    LOG.debug("100 continues not sent");
-                    // We didn't send 100 continues, but the latest interpretation
-                    // of the spec (see httpbis) is that the client will either
-                    // send the body anyway, or close.  So we no longer need to
-                    // do anything special here other than make the connection not persistent
-                    _expect100Continue = false;
-                    if (!_response.isCommitted())
-                        _generator.setPersistent(false);
-                }
-
-                if(_endp.isOpen())
-                {
-                    if (error)
-                    {
-                        _endp.shutdownOutput();
-                        _generator.setPersistent(false);
-                        if (!_generator.isComplete())
-                            _response.complete();
-                    }
-                    else
-                    {
-                        if (!_response.isCommitted() && !_request.isHandled())
-                            _response.sendError(HttpServletResponse.SC_NOT_FOUND);
-                        _response.complete();
-                        if (_generator.isPersistent())
-                            _connector.persist(_endp);
-                    }
-                }
-                else
-                {
-                    _response.complete();
-                }
-
-                _request.setHandled(true);
-            }
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    public abstract Connection handle() throws IOException;
-
-    /* ------------------------------------------------------------ */
-    public void commitResponse(boolean last) throws IOException
-    {
-        if (!_generator.isCommitted())
-        {
-            _generator.setResponse(_response.getStatus(), _response.getReason());
-            try
-            {
-                // If the client was expecting 100 continues, but we sent something
-                // else, then we need to close the connection
-                if (_expect100Continue && _response.getStatus()!=100)
-                    _generator.setPersistent(false);
-                _generator.completeHeader(_responseFields, last);
-            }
-            catch(RuntimeException e)
-            {
-                LOG.warn("header full: " + e);
-
-                _response.reset();
-                _generator.reset();
-                _generator.setResponse(HttpStatus.INTERNAL_SERVER_ERROR_500,null);
-                _generator.completeHeader(_responseFields,Generator.LAST);
-                _generator.complete();
-                throw new HttpException(HttpStatus.INTERNAL_SERVER_ERROR_500);
-            }
-
-        }
-        if (last)
-            _generator.complete();
-    }
-
-    /* ------------------------------------------------------------ */
-    public void completeResponse() throws IOException
-    {
-        if (!_generator.isCommitted())
-        {
-            _generator.setResponse(_response.getStatus(), _response.getReason());
-            try
-            {
-                _generator.completeHeader(_responseFields, Generator.LAST);
-            }
-            catch(RuntimeException e)
-            {
-                LOG.warn("header full: "+e);
-                LOG.debug(e);
-
-                _response.reset();
-                _generator.reset();
-                _generator.setResponse(HttpStatus.INTERNAL_SERVER_ERROR_500,null);
-                _generator.completeHeader(_responseFields,Generator.LAST);
-                _generator.complete();
-                throw new HttpException(HttpStatus.INTERNAL_SERVER_ERROR_500);
-            }
-        }
-
-        _generator.complete();
-    }
-
-    /* ------------------------------------------------------------ */
-    public void flushResponse() throws IOException
-    {
-        try
-        {
-            commitResponse(Generator.MORE);
-            _generator.flushBuffer();
-        }
-        catch(IOException e)
-        {
-            throw (e instanceof EofException) ? e:new EofException(e);
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    public Generator getGenerator()
-    {
-        return _generator;
-    }
-
-    /* ------------------------------------------------------------ */
-    public boolean isIncluding()
-    {
-        return _include>0;
-    }
-
-    /* ------------------------------------------------------------ */
-    public void include()
-    {
-        _include++;
-    }
-
-    /* ------------------------------------------------------------ */
-    public void included()
-    {
-        _include--;
-        if (_out!=null)
-            _out.reopen();
-    }
-
-    /* ------------------------------------------------------------ */
-    public boolean isIdle()
-    {
-        return _generator.isIdle() && (_parser.isIdle() || _delayedHandling);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.io.Connection#isSuspended()
-     */
-    public boolean isSuspended()
-    {
-        return _request.getAsyncContinuation().isSuspended();
-    }
-
-    /* ------------------------------------------------------------ */
-    public void onClose()
-    {
-        LOG.debug("closed {}",this);
-    }
-
-    /* ------------------------------------------------------------ */
-    public boolean isExpecting100Continues()
-    {
-        return _expect100Continue;
-    }
-
-    /* ------------------------------------------------------------ */
-    public boolean isExpecting102Processing()
-    {
-        return _expect102Processing;
-    }
-
-    /* ------------------------------------------------------------ */
-    public int getMaxIdleTime()
-    {
-        if (_connector.isLowResources() && _endp.getMaxIdleTime()==_connector.getMaxIdleTime())
-            return _connector.getLowResourceMaxIdleTime();
-        if (_endp.getMaxIdleTime()>0)
-            return _endp.getMaxIdleTime();
-        return _connector.getMaxIdleTime();
-    }
-
-    /* ------------------------------------------------------------ */
-    public String toString()
-    {
-        return String.format("%s,g=%s,p=%s,r=%d",
-                super.toString(),
-                _generator,
-                _parser,
-                _requests);
-    }
-
-    /* ------------------------------------------------------------ */
-    protected void startRequest(Buffer method, Buffer uri, Buffer version) throws IOException
-    {
-        uri=uri.asImmutableBuffer();
-
-        _host = false;
-        _expect = false;
-        _expect100Continue=false;
-        _expect102Processing=false;
-        _delayedHandling=false;
-        _charset=null;
-
-        if(_request.getTimeStamp()==0)
-            _request.setTimeStamp(System.currentTimeMillis());
-        _request.setMethod(method.toString());
-
-        try
-        {
-            _head=false;
-            switch (HttpMethods.CACHE.getOrdinal(method))
-            {
-              case HttpMethods.CONNECT_ORDINAL:
-                  _uri.parseConnect(uri.array(), uri.getIndex(), uri.length());
-                  break;
-
-              case HttpMethods.HEAD_ORDINAL:
-                  _head=true;
-                  _uri.parse(uri.array(), uri.getIndex(), uri.length());
-                  break;
-
-              default:
-                  _uri.parse(uri.array(), uri.getIndex(), uri.length());
-            }
-
-            _request.setUri(_uri);
-
-            if (version==null)
-            {
-                _request.setProtocol(HttpVersions.HTTP_0_9);
-                _version=HttpVersions.HTTP_0_9_ORDINAL;
-            }
-            else
-            {
-                version= HttpVersions.CACHE.get(version);
-                if (version==null)
-                    throw new HttpException(HttpStatus.BAD_REQUEST_400,null);
-                _version = HttpVersions.CACHE.getOrdinal(version);
-                if (_version <= 0) _version = HttpVersions.HTTP_1_0_ORDINAL;
-                _request.setProtocol(version.toString());
-            }
-        }
-        catch (Exception e)
-        {
-            LOG.debug(e);
-            if (e instanceof HttpException)
-                throw (HttpException)e;
-            throw new HttpException(HttpStatus.BAD_REQUEST_400,null,e);
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    protected void parsedHeader(Buffer name, Buffer value) throws IOException
-    {
-        int ho = HttpHeaders.CACHE.getOrdinal(name);
-        switch (ho)
-        {
-            case HttpHeaders.HOST_ORDINAL:
-                // TODO check if host matched a host in the URI.
-                _host = true;
-                break;
-
-            case HttpHeaders.EXPECT_ORDINAL:
-                if (_version>=HttpVersions.HTTP_1_1_ORDINAL)
-                {
-                    value = HttpHeaderValues.CACHE.lookup(value);
-                    switch(HttpHeaderValues.CACHE.getOrdinal(value))
-                    {
-                        case HttpHeaderValues.CONTINUE_ORDINAL:
-                            _expect100Continue=_generator instanceof HttpGenerator;
-                            break;
-
-                        case HttpHeaderValues.PROCESSING_ORDINAL:
-                            _expect102Processing=_generator instanceof HttpGenerator;
-                            break;
-
-                        default:
-                            String[] values = value.toString().split(",");
-                            for  (int i=0;values!=null && i<values.length;i++)
-                            {
-                                CachedBuffer cb=HttpHeaderValues.CACHE.get(values[i].trim());
-                                if (cb==null)
-                                    _expect=true;
-                                else
-                                {
-                                    switch(cb.getOrdinal())
-                                    {
-                                        case HttpHeaderValues.CONTINUE_ORDINAL:
-                                            _expect100Continue=_generator instanceof HttpGenerator;
-                                            break;
-                                        case HttpHeaderValues.PROCESSING_ORDINAL:
-                                            _expect102Processing=_generator instanceof HttpGenerator;
-                                            break;
-                                        default:
-                                            _expect=true;
-                                    }
-                                }
-                            }
-                    }
-                }
-                break;
-
-            case HttpHeaders.ACCEPT_ENCODING_ORDINAL:
-            case HttpHeaders.USER_AGENT_ORDINAL:
-                value = HttpHeaderValues.CACHE.lookup(value);
-                break;
-
-            case HttpHeaders.CONTENT_TYPE_ORDINAL:
-                value = MimeTypes.CACHE.lookup(value);
-                _charset=MimeTypes.getCharsetFromContentType(value);
-                break;
-        }
-
-        _requestFields.add(name, value);
-    }
-
-    /* ------------------------------------------------------------ */
-    protected void headerComplete() throws IOException
-    {
-        // Handle idle race
-        if (_endp.isOutputShutdown())
-        {
-            _endp.close();
-            return;
-        }
-        
-        _requests++;
-        _generator.setVersion(_version);
-        switch (_version)
-        {
-            case HttpVersions.HTTP_0_9_ORDINAL:
-                break;
-            case HttpVersions.HTTP_1_0_ORDINAL:
-                _generator.setHead(_head);
-                if (_parser.isPersistent())
-                {
-                    _responseFields.add(HttpHeaders.CONNECTION_BUFFER, HttpHeaderValues.KEEP_ALIVE_BUFFER);
-                    _generator.setPersistent(true);
-                }
-                else if (HttpMethods.CONNECT.equals(_request.getMethod()))
-                {
-                    _generator.setPersistent(true);
-                    _parser.setPersistent(true);
-                }
-
-                if (_server.getSendDateHeader())
-                    _generator.setDate(_request.getTimeStampBuffer());
-                break;
-
-            case HttpVersions.HTTP_1_1_ORDINAL:
-                _generator.setHead(_head);
-
-                if (!_parser.isPersistent())
-                {
-                    _responseFields.add(HttpHeaders.CONNECTION_BUFFER,HttpHeaderValues.CLOSE_BUFFER);
-                    _generator.setPersistent(false);
-                }
-                if (_server.getSendDateHeader())
-                    _generator.setDate(_request.getTimeStampBuffer());
-
-                if (!_host)
-                {
-                    LOG.debug("!host {}",this);
-                    _generator.setResponse(HttpStatus.BAD_REQUEST_400, null);
-                    _responseFields.put(HttpHeaders.CONNECTION_BUFFER, HttpHeaderValues.CLOSE_BUFFER);
-                    _generator.completeHeader(_responseFields, true);
-                    _generator.complete();
-                    return;
-                }
-
-                if (_expect)
-                {
-                    LOG.debug("!expectation {}",this);
-                    _generator.setResponse(HttpStatus.EXPECTATION_FAILED_417, null);
-                    _responseFields.put(HttpHeaders.CONNECTION_BUFFER, HttpHeaderValues.CLOSE_BUFFER);
-                    _generator.completeHeader(_responseFields, true);
-                    _generator.complete();
-                    return;
-                }
-
-                break;
-            default:
-        }
-
-        if(_charset!=null)
-            _request.setCharacterEncodingUnchecked(_charset);
-
-        // Either handle now or wait for first content
-        if ((((HttpParser)_parser).getContentLength()<=0 && !((HttpParser)_parser).isChunking())||_expect100Continue)
-            handleRequest();
-        else
-            _delayedHandling=true;
-    }
-
-    /* ------------------------------------------------------------ */
-    protected void content(Buffer buffer) throws IOException
-    {
-        if (_delayedHandling)
-        {
-            _delayedHandling=false;
-            handleRequest();
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    public void messageComplete(long contentLength) throws IOException
-    {
-        if (_delayedHandling)
-        {
-            _delayedHandling=false;
-            handleRequest();
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    public void earlyEOF()
-    {
-        _earlyEOF = true;
-    }
-
-    /* ------------------------------------------------------------ */
-    /* ------------------------------------------------------------ */
-    /* ------------------------------------------------------------ */
-    private class RequestHandler extends HttpParser.EventHandler
-    {
-        /*
-         *
-         * @see org.eclipse.jetty.server.server.HttpParser.EventHandler#startRequest(org.eclipse.io.Buffer,
-         *      org.eclipse.io.Buffer, org.eclipse.io.Buffer)
-         */
-        @Override
-        public void startRequest(Buffer method, Buffer uri, Buffer version) throws IOException
-        {
-            AbstractHttpConnection.this.startRequest(method, uri, version);
-        }
-
-        /*
-         * @see org.eclipse.jetty.server.server.HttpParser.EventHandler#parsedHeaderValue(org.eclipse.io.Buffer)
-         */
-        @Override
-        public void parsedHeader(Buffer name, Buffer value) throws IOException
-        {
-            AbstractHttpConnection.this.parsedHeader(name, value);
-        }
-
-        /*
-         * @see org.eclipse.jetty.server.server.HttpParser.EventHandler#headerComplete()
-         */
-        @Override
-        public void headerComplete() throws IOException
-        {
-            AbstractHttpConnection.this.headerComplete();
-        }
-
-        /* ------------------------------------------------------------ */
-        /*
-         * @see org.eclipse.jetty.server.server.HttpParser.EventHandler#content(int, org.eclipse.io.Buffer)
-         */
-        @Override
-        public void content(Buffer ref) throws IOException
-        {
-            AbstractHttpConnection.this.content(ref);
-        }
-
-        /* ------------------------------------------------------------ */
-        /*
-         * (non-Javadoc)
-         *
-         * @see org.eclipse.jetty.server.server.HttpParser.EventHandler#messageComplete(int)
-         */
-        @Override
-        public void messageComplete(long contentLength) throws IOException
-        {
-            AbstractHttpConnection.this.messageComplete(contentLength);
-        }
-
-        /* ------------------------------------------------------------ */
-        /*
-         * (non-Javadoc)
-         *
-         * @see org.eclipse.jetty.server.server.HttpParser.EventHandler#startResponse(org.eclipse.io.Buffer, int,
-         *      org.eclipse.io.Buffer)
-         */
-        @Override
-        public void startResponse(Buffer version, int status, Buffer reason)
-        {
-            if (LOG.isDebugEnabled())
-                LOG.debug("Bad request!: "+version+" "+status+" "+reason);
-        }
-
-        /* ------------------------------------------------------------ */
-        /*
-         * (non-Javadoc)
-         *
-         * @see org.eclipse.jetty.server.server.HttpParser.EventHandler#earlyEOF()
-         */
-        @Override
-        public void earlyEOF()
-        {
-            AbstractHttpConnection.this.earlyEOF();
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    /* ------------------------------------------------------------ */
-    /* ------------------------------------------------------------ */
-    public class Output extends HttpOutput
-    {
-        Output()
-        {
-            super(AbstractHttpConnection.this);
-        }
-
-        /* ------------------------------------------------------------ */
-        /*
-         * @see java.io.OutputStream#close()
-         */
-        @Override
-        public void close() throws IOException
-        {
-            if (isClosed())
-                return;
-
-            if (!isIncluding() && !super._generator.isCommitted())
-                commitResponse(Generator.LAST);
-            else
-                flushResponse();
-
-            super.close();
-        }
-
-
-        /* ------------------------------------------------------------ */
-        /*
-         * @see java.io.OutputStream#flush()
-         */
-        @Override
-        public void flush() throws IOException
-        {
-            if (!super._generator.isCommitted())
-                commitResponse(Generator.MORE);
-            super.flush();
-        }
-
-        /* ------------------------------------------------------------ */
-        /*
-         * @see javax.servlet.ServletOutputStream#print(java.lang.String)
-         */
-        @Override
-        public void print(String s) throws IOException
-        {
-            if (isClosed())
-                throw new IOException("Closed");
-            PrintWriter writer=getPrintWriter(null);
-            writer.print(s);
-        }
-
-        /* ------------------------------------------------------------ */
-        public void sendResponse(Buffer response) throws IOException
-        {
-            ((HttpGenerator)super._generator).sendResponse(response);
-        }
-
-        /* ------------------------------------------------------------ */
-        public void sendContent(Object content) throws IOException
-        {
-            Resource resource=null;
-
-            if (isClosed())
-                throw new IOException("Closed");
-
-            if (super._generator.isWritten())
-                throw new IllegalStateException("!empty");
-
-            // Convert HTTP content to content
-            if (content instanceof HttpContent)
-            {
-                HttpContent httpContent = (HttpContent) content;
-                Buffer contentType = httpContent.getContentType();
-                if (contentType != null && !_responseFields.containsKey(HttpHeaders.CONTENT_TYPE_BUFFER))
-                {
-                    String enc = _response.getSetCharacterEncoding();
-                    if(enc==null)
-                        _responseFields.add(HttpHeaders.CONTENT_TYPE_BUFFER, contentType);
-                    else
-                    {
-                        if(contentType instanceof CachedBuffer)
-                        {
-                            CachedBuffer content_type = ((CachedBuffer)contentType).getAssociate(enc);
-                            if(content_type!=null)
-                                _responseFields.put(HttpHeaders.CONTENT_TYPE_BUFFER, content_type);
-                            else
-                            {
-                                _responseFields.put(HttpHeaders.CONTENT_TYPE_BUFFER,
-                                        contentType+";charset="+QuotedStringTokenizer.quoteIfNeeded(enc,";= "));
-                            }
-                        }
-                        else
-                        {
-                            _responseFields.put(HttpHeaders.CONTENT_TYPE_BUFFER,
-                                    contentType+";charset="+QuotedStringTokenizer.quoteIfNeeded(enc,";= "));
-                        }
-                    }
-                }
-                if (httpContent.getContentLength() > 0)
-                    _responseFields.putLongField(HttpHeaders.CONTENT_LENGTH_BUFFER, httpContent.getContentLength());
-                Buffer lm = httpContent.getLastModified();
-                long lml=httpContent.getResource().lastModified();
-                if (lm != null)
-                {
-                    _responseFields.put(HttpHeaders.LAST_MODIFIED_BUFFER, lm);
-                }
-                else if (httpContent.getResource()!=null)
-                {
-                    if (lml!=-1)
-                        _responseFields.putDateField(HttpHeaders.LAST_MODIFIED_BUFFER, lml);
-                }
-                
-                Buffer etag=httpContent.getETag();
-                if (etag!=null)
-                    _responseFields.put(HttpHeaders.ETAG_BUFFER,etag);
-
-                
-                boolean direct=_connector instanceof NIOConnector && ((NIOConnector)_connector).getUseDirectBuffers() && !(_connector instanceof SslConnector);
-                content = direct?httpContent.getDirectBuffer():httpContent.getIndirectBuffer();
-                if (content==null)
-                    content=httpContent.getInputStream();
-            }
-            else if (content instanceof Resource)
-            {
-                resource=(Resource)content;
-                _responseFields.putDateField(HttpHeaders.LAST_MODIFIED_BUFFER, resource.lastModified());
-                content=resource.getInputStream();
-            }
-
-            // Process content.
-            if (content instanceof Buffer)
-            {
-                super._generator.addContent((Buffer) content, Generator.LAST);
-                commitResponse(Generator.LAST);
-            }
-            else if (content instanceof InputStream)
-            {
-                InputStream in = (InputStream)content;
-
-                try
-                {
-                    int max = super._generator.prepareUncheckedAddContent();
-                    Buffer buffer = super._generator.getUncheckedBuffer();
-
-                    int len=buffer.readFrom(in,max);
-
-                    while (len>=0 && !_endp.isOutputShutdown())
-                    {
-                        super._generator.completeUncheckedAddContent();
-                        _out.flush();
-
-                        max = super._generator.prepareUncheckedAddContent();
-                        buffer = super._generator.getUncheckedBuffer();
-                        len=buffer.readFrom(in,max);
-                    }
-                    super._generator.completeUncheckedAddContent();
-                    _out.flush();
-                }
-                finally
-                {
-                    if (resource!=null)
-                        resource.release();
-                    else
-                        in.close();
-                }
-            }
-            else
-                throw new IllegalArgumentException("unknown content type?");
-
-
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    /* ------------------------------------------------------------ */
-    /* ------------------------------------------------------------ */
-    public class OutputWriter extends HttpWriter
-    {
-        OutputWriter()
-        {
-            super(AbstractHttpConnection.this._out);
-        }
-    }
-
-
-}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractNCSARequestLog.java b/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractNCSARequestLog.java
new file mode 100644
index 0000000..a9ac0d6
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractNCSARequestLog.java
@@ -0,0 +1,482 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.util.Locale;
+
+import javax.servlet.http.Cookie;
+
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.PathMap;
+import org.eclipse.jetty.util.DateCache;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * Base implementation of the {@link RequestLog} outputs logs in the pseudo-standard NCSA common log format.
+ * Configuration options allow a choice between the standard Common Log Format (as used in the 3 log format) and the
+ * Combined Log Format (single log format). This log format can be output by most web servers, and almost all web log
+ * analysis software can understand these formats.
+ */
+public abstract class AbstractNCSARequestLog extends AbstractLifeCycle implements RequestLog
+{
+    protected static final Logger LOG = Log.getLogger(AbstractNCSARequestLog.class);
+
+    private static ThreadLocal<StringBuilder> _buffers = new ThreadLocal<StringBuilder>()
+    {
+        @Override
+        protected StringBuilder initialValue()
+        {
+            return new StringBuilder(256);
+        }
+    };
+
+
+    private String[] _ignorePaths;
+    private boolean _extended;
+    private transient PathMap<String> _ignorePathMap;
+    private boolean _logLatency = false;
+    private boolean _logCookies = false;
+    private boolean _logServer = false;
+    private boolean _preferProxiedForAddress;
+    private transient DateCache _logDateCache;
+    private String _logDateFormat = "dd/MMM/yyyy:HH:mm:ss Z";
+    private Locale _logLocale = Locale.getDefault();
+    private String _logTimeZone = "GMT";
+
+    /* ------------------------------------------------------------ */
+
+    /**
+     * Is logging enabled
+     */
+    protected abstract boolean isEnabled();
+    
+    /* ------------------------------------------------------------ */
+
+    /**
+     * Write requestEntry out. (to disk or slf4j log)
+     */
+    public abstract void write(String requestEntry) throws IOException;
+
+    /* ------------------------------------------------------------ */
+
+    /**
+     * Writes the request and response information to the output stream.
+     *
+     * @see org.eclipse.jetty.server.RequestLog#log(org.eclipse.jetty.server.Request,
+     *      org.eclipse.jetty.server.Response)
+     */
+    @Override
+    public void log(Request request, Response response)
+    {
+        try
+        {
+            if (_ignorePathMap != null && _ignorePathMap.getMatch(request.getRequestURI()) != null)
+                return;
+
+            if (!isEnabled())
+                return;
+
+            StringBuilder buf = _buffers.get();
+            buf.setLength(0);
+
+            if (_logServer)
+            {
+                buf.append(request.getServerName());
+                buf.append(' ');
+            }
+
+            String addr = null;
+            if (_preferProxiedForAddress)
+            {
+                addr = request.getHeader(HttpHeader.X_FORWARDED_FOR.toString());
+            }
+
+            if (addr == null)
+                addr = request.getRemoteAddr();
+
+            buf.append(addr);
+            buf.append(" - ");
+            Authentication authentication = request.getAuthentication();
+            if (authentication instanceof Authentication.User)
+                buf.append(((Authentication.User)authentication).getUserIdentity().getUserPrincipal().getName());
+            else
+                buf.append("-");
+
+            buf.append(" [");
+            if (_logDateCache != null)
+                buf.append(_logDateCache.format(request.getTimeStamp()));
+            else
+                buf.append(request.getTimeStamp());
+
+            buf.append("] \"");
+            buf.append(request.getMethod());
+            buf.append(' ');
+            buf.append(request.getUri().toString());
+            buf.append(' ');
+            buf.append(request.getProtocol());
+            buf.append("\" ");
+
+            int status = response.getStatus();
+            if (status <= 0)
+                status = 404;
+            buf.append((char)('0' + ((status / 100) % 10)));
+            buf.append((char)('0' + ((status / 10) % 10)));
+            buf.append((char)('0' + (status % 10)));
+
+            long responseLength = response.getLongContentLength();
+            if (responseLength >= 0)
+            {
+                buf.append(' ');
+                if (responseLength > 99999)
+                    buf.append(responseLength);
+                else
+                {
+                    if (responseLength > 9999)
+                        buf.append((char)('0' + ((responseLength / 10000) % 10)));
+                    if (responseLength > 999)
+                        buf.append((char)('0' + ((responseLength / 1000) % 10)));
+                    if (responseLength > 99)
+                        buf.append((char)('0' + ((responseLength / 100) % 10)));
+                    if (responseLength > 9)
+                        buf.append((char)('0' + ((responseLength / 10) % 10)));
+                    buf.append((char)('0' + (responseLength) % 10));
+                }
+                buf.append(' ');
+            }
+            else
+                buf.append(" - ");
+
+
+            if (_extended)
+                logExtended(request, response, buf);
+
+            if (_logCookies)
+            {
+                Cookie[] cookies = request.getCookies();
+                if (cookies == null || cookies.length == 0)
+                    buf.append(" -");
+                else
+                {
+                    buf.append(" \"");
+                    for (int i = 0; i < cookies.length; i++)
+                    {
+                        if (i != 0)
+                            buf.append(';');
+                        buf.append(cookies[i].getName());
+                        buf.append('=');
+                        buf.append(cookies[i].getValue());
+                    }
+                    buf.append('\"');
+                }
+            }
+
+            if (_logLatency)
+            {
+                long now = System.currentTimeMillis();
+
+                if (_logLatency)
+                {
+                    buf.append(' ');
+                    buf.append(now - request.getTimeStamp());
+                }
+            }
+
+            String log = buf.toString();
+            write(log);
+        }
+        catch (IOException e)
+        {
+            LOG.warn(e);
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+
+    /**
+     * Writes extended request and response information to the output stream.
+     *
+     * @param request  request object
+     * @param response response object
+     * @param b        StringBuilder to write to
+     * @throws IOException
+     */
+    protected void logExtended(Request request,
+                               Response response,
+                               StringBuilder b) throws IOException
+    {
+        String referer = request.getHeader(HttpHeader.REFERER.toString());
+        if (referer == null)
+            b.append("\"-\" ");
+        else
+        {
+            b.append('"');
+            b.append(referer);
+            b.append("\" ");
+        }
+
+        String agent = request.getHeader(HttpHeader.USER_AGENT.toString());
+        if (agent == null)
+            b.append("\"-\" ");
+        else
+        {
+            b.append('"');
+            b.append(agent);
+            b.append('"');
+        }
+    }
+
+
+    /**
+     * Set request paths that will not be logged.
+     *
+     * @param ignorePaths array of request paths
+     */
+    public void setIgnorePaths(String[] ignorePaths)
+    {
+        _ignorePaths = ignorePaths;
+    }
+
+    /**
+     * Retrieve the request paths that will not be logged.
+     *
+     * @return array of request paths
+     */
+    public String[] getIgnorePaths()
+    {
+        return _ignorePaths;
+    }
+
+    /**
+     * Controls logging of the request cookies.
+     *
+     * @param logCookies true - values of request cookies will be logged, false - values of request cookies will not be
+     *                   logged
+     */
+    public void setLogCookies(boolean logCookies)
+    {
+        _logCookies = logCookies;
+    }
+
+    /**
+     * Retrieve log cookies flag
+     *
+     * @return value of the flag
+     */
+    public boolean getLogCookies()
+    {
+        return _logCookies;
+    }
+
+    /**
+     * Controls logging of the request hostname.
+     *
+     * @param logServer true - request hostname will be logged, false - request hostname will not be logged
+     */
+    public void setLogServer(boolean logServer)
+    {
+        _logServer = logServer;
+    }
+
+    /**
+     * Retrieve log hostname flag.
+     *
+     * @return value of the flag
+     */
+    public boolean getLogServer()
+    {
+        return _logServer;
+    }
+
+    /**
+     * Controls logging of request processing time.
+     *
+     * @param logLatency true - request processing time will be logged false - request processing time will not be
+     *                   logged
+     */
+    public void setLogLatency(boolean logLatency)
+    {
+        _logLatency = logLatency;
+    }
+
+    /**
+     * Retrieve log request processing time flag.
+     *
+     * @return value of the flag
+     */
+    public boolean getLogLatency()
+    {
+        return _logLatency;
+    }
+
+    /**
+     * @deprecated use {@link StatisticsHandler}
+     */
+    public void setLogDispatch(boolean value)
+    {
+    }
+
+    /**
+     * @deprecated use {@link StatisticsHandler}
+     */
+    public boolean isLogDispatch()
+    {
+        return false;
+    }
+
+    /**
+     * Controls whether the actual IP address of the connection or the IP address from the X-Forwarded-For header will
+     * be logged.
+     *
+     * @param preferProxiedForAddress true - IP address from header will be logged, false - IP address from the
+     *                                connection will be logged
+     */
+    public void setPreferProxiedForAddress(boolean preferProxiedForAddress)
+    {
+        _preferProxiedForAddress = preferProxiedForAddress;
+    }
+
+    /**
+     * Retrieved log X-Forwarded-For IP address flag.
+     *
+     * @return value of the flag
+     */
+    public boolean getPreferProxiedForAddress()
+    {
+        return _preferProxiedForAddress;
+    }
+
+    /**
+     * Set the extended request log format flag.
+     *
+     * @param extended true - log the extended request information, false - do not log the extended request information
+     */
+    public void setExtended(boolean extended)
+    {
+        _extended = extended;
+    }
+
+    /**
+     * Retrieve the extended request log format flag.
+     *
+     * @return value of the flag
+     */
+    @ManagedAttribute("use extended NCSA format")
+    public boolean isExtended()
+    {
+        return _extended;
+    }
+
+    /**
+     * Set up request logging and open log file.
+     *
+     * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart()
+     */
+    @Override
+    protected synchronized void doStart() throws Exception
+    {
+        if (_logDateFormat != null)
+        {
+            _logDateCache = new DateCache(_logDateFormat, _logLocale ,_logTimeZone);
+        }
+
+        if (_ignorePaths != null && _ignorePaths.length > 0)
+        {
+            _ignorePathMap = new PathMap<>();
+            for (int i = 0; i < _ignorePaths.length; i++)
+                _ignorePathMap.put(_ignorePaths[i], _ignorePaths[i]);
+        }
+        else
+            _ignorePathMap = null;
+
+        super.doStart();
+    }
+
+    @Override
+    protected void doStop() throws Exception
+    {
+        _logDateCache = null;
+        super.doStop();
+    }
+
+    /**
+     * Set the timestamp format for request log entries in the file. If this is not set, the pre-formated request
+     * timestamp is used.
+     *
+     * @param format timestamp format string
+     */
+    public void setLogDateFormat(String format)
+    {
+        _logDateFormat = format;
+    }
+
+    /**
+     * Retrieve the timestamp format string for request log entries.
+     *
+     * @return timestamp format string.
+     */
+    public String getLogDateFormat()
+    {
+        return _logDateFormat;
+    }
+
+    /**
+     * Set the locale of the request log.
+     *
+     * @param logLocale locale object
+     */
+    public void setLogLocale(Locale logLocale)
+    {
+        _logLocale = logLocale;
+    }
+
+    /**
+     * Retrieve the locale of the request log.
+     *
+     * @return locale object
+     */
+    public Locale getLogLocale()
+    {
+        return _logLocale;
+    }
+
+    /**
+     * Set the timezone of the request log.
+     *
+     * @param tz timezone string
+     */
+    public void setLogTimeZone(String tz)
+    {
+        _logTimeZone = tz;
+    }
+
+    /**
+     * Retrieve the timezone of the request log.
+     *
+     * @return timezone string
+     */
+    @ManagedAttribute("the timezone")
+    public String getLogTimeZone()
+    {
+        return _logTimeZone;
+    }
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractNetworkConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractNetworkConnector.java
new file mode 100644
index 0000000..e82a62b
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractNetworkConnector.java
@@ -0,0 +1,125 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Future;
+
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+/**
+ * An abstract Network Connector.
+ * <p>
+ * Extends the {@link AbstractConnector} support for the {@link NetworkConnector} interface.
+ */
+ at ManagedObject("AbstractNetworkConnector")
+public abstract class AbstractNetworkConnector extends AbstractConnector implements NetworkConnector
+{
+
+    private volatile String _host;
+    private volatile int _port = 0;
+
+    public AbstractNetworkConnector(Server server, Executor executor, Scheduler scheduler, ByteBufferPool pool, int acceptors, ConnectionFactory... factories)
+    {
+        super(server,executor,scheduler,pool,acceptors,factories);
+    }
+
+    public void setHost(String host)
+    {
+        _host = host;
+    }
+
+    @Override
+    @ManagedAttribute("The network interface this connector binds to as an IP address or a hostname.  If null or 0.0.0.0, then bind to all interfaces.")
+    public String getHost()
+    {
+        return _host;
+    }
+
+    public void setPort(int port)
+    {
+        _port = port;
+    }
+
+    @Override
+    @ManagedAttribute("Port this connector listens on. If set the 0 a random port is assigned which may be obtained with getLocalPort()")
+    public int getPort()
+    {
+        return _port;
+    }
+
+    @Override
+    public int getLocalPort()
+    {
+        return -1;
+    }
+
+    @Override
+    protected void doStart() throws Exception
+    {
+        open();
+        super.doStart();
+    }
+
+    @Override
+    protected void doStop() throws Exception
+    {
+        close();
+        super.doStop();
+    }
+
+    @Override
+    public void open() throws IOException
+    {
+    }
+
+    @Override
+    public void close()
+    {
+        // Interrupting is often sufficient to close the channel
+        interruptAcceptors();
+    }
+    
+
+    @Override
+    public Future<Void> shutdown()
+    {
+        close();
+        return super.shutdown();
+    }
+
+    @Override
+    protected boolean isAccepting()
+    {
+        return super.isAccepting() && isOpen();
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s{%s:%d}",
+                super.toString(),
+                getHost() == null ? "0.0.0.0" : getHost(),
+                getLocalPort() <= 0 ? getPort() : getLocalPort());
+    }
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncContextEvent.java b/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncContextEvent.java
new file mode 100644
index 0000000..d2ec0e0
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncContextEvent.java
@@ -0,0 +1,164 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+
+import javax.servlet.AsyncContext;
+import javax.servlet.AsyncEvent;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+import org.eclipse.jetty.server.handler.ContextHandler.Context;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+public class AsyncContextEvent extends AsyncEvent implements Runnable
+{
+    final private Context _context;
+    final private AsyncContextState _asyncContext;
+    private volatile HttpChannelState _state;
+    private ServletContext _dispatchContext;
+    private String _dispatchPath;
+    private volatile Scheduler.Task _timeoutTask;
+    private Throwable _throwable;
+
+    public AsyncContextEvent(Context context,AsyncContextState asyncContext, HttpChannelState state, Request baseRequest, ServletRequest request, ServletResponse response)
+    {
+        super(null,request,response,null);
+        _context=context;
+        _asyncContext=asyncContext;
+        _state=state;
+
+        // If we haven't been async dispatched before
+        if (baseRequest.getAttribute(AsyncContext.ASYNC_REQUEST_URI)==null)
+        {
+            // We are setting these attributes during startAsync, when the spec implies that
+            // they are only available after a call to AsyncContext.dispatch(...);
+
+            // have we been forwarded before?
+            String uri=(String)baseRequest.getAttribute(RequestDispatcher.FORWARD_REQUEST_URI);
+            if (uri!=null)
+            {
+                baseRequest.setAttribute(AsyncContext.ASYNC_REQUEST_URI,uri);
+                baseRequest.setAttribute(AsyncContext.ASYNC_CONTEXT_PATH,baseRequest.getAttribute(RequestDispatcher.FORWARD_CONTEXT_PATH));
+                baseRequest.setAttribute(AsyncContext.ASYNC_SERVLET_PATH,baseRequest.getAttribute(RequestDispatcher.FORWARD_SERVLET_PATH));
+                baseRequest.setAttribute(AsyncContext.ASYNC_PATH_INFO,baseRequest.getAttribute(RequestDispatcher.FORWARD_PATH_INFO));
+                baseRequest.setAttribute(AsyncContext.ASYNC_QUERY_STRING,baseRequest.getAttribute(RequestDispatcher.FORWARD_QUERY_STRING));
+            }
+            else
+            {
+                baseRequest.setAttribute(AsyncContext.ASYNC_REQUEST_URI,baseRequest.getRequestURI());
+                baseRequest.setAttribute(AsyncContext.ASYNC_CONTEXT_PATH,baseRequest.getContextPath());
+                baseRequest.setAttribute(AsyncContext.ASYNC_SERVLET_PATH,baseRequest.getServletPath());
+                baseRequest.setAttribute(AsyncContext.ASYNC_PATH_INFO,baseRequest.getPathInfo());
+                baseRequest.setAttribute(AsyncContext.ASYNC_QUERY_STRING,baseRequest.getQueryString());
+            }
+        }
+    }
+
+    public ServletContext getSuspendedContext()
+    {
+        return _context;
+    }
+    
+    public Context getContext()
+    {
+        return _context;
+    }
+
+    public ServletContext getDispatchContext()
+    {
+        return _dispatchContext;
+    }
+
+    public ServletContext getServletContext()
+    {
+        return _dispatchContext==null?_context:_dispatchContext;
+    }
+
+    /**
+     * @return The path in the context
+     */
+    public String getPath()
+    {
+        return _dispatchPath;
+    }
+    
+    public void setTimeoutTask(Scheduler.Task task)
+    {
+        _timeoutTask = task;
+    }
+    
+    public void cancelTimeoutTask()
+    {
+        Scheduler.Task task=_timeoutTask;
+        _timeoutTask=null;
+        if (task!=null)
+            task.cancel();
+    }
+
+    @Override
+    public AsyncContext getAsyncContext()
+    {
+        return _asyncContext;
+    }
+    
+    @Override
+    public Throwable getThrowable()
+    {
+        return _throwable;
+    }
+    
+    public void setThrowable(Throwable throwable)
+    {
+        _throwable=throwable;
+    }
+
+    public void setDispatchContext(ServletContext context)
+    {
+        _dispatchContext=context;
+    }
+    
+    public void setDispatchPath(String path)
+    {
+        _dispatchPath=path;
+    }
+    
+    public void completed()
+    {
+        _timeoutTask=null;
+        _asyncContext.reset();
+    }
+
+    public HttpChannelState getHttpChannelState()
+    {
+        return _state;
+    }
+
+    @Override
+    public void run()
+    {
+        Scheduler.Task task=_timeoutTask;
+        _timeoutTask=null;
+        if (task!=null)
+            _state.expired();
+    }
+
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncContextState.java b/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncContextState.java
new file mode 100644
index 0000000..b8b4eeb
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncContextState.java
@@ -0,0 +1,185 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.AsyncEvent;
+import javax.servlet.AsyncListener;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+import org.eclipse.jetty.server.handler.ContextHandler;
+
+
+public class AsyncContextState implements AsyncContext
+{
+    volatile HttpChannelState _state;
+
+    public AsyncContextState(HttpChannelState state)
+    {
+        _state=state;
+    }
+    
+    HttpChannelState state()
+    {
+        HttpChannelState state=_state;
+        if (state==null)
+            throw new IllegalStateException("AsyncContext completed");
+        return state;
+    }
+
+    @Override
+    public void addListener(final AsyncListener listener, final ServletRequest request, final ServletResponse response)
+    {
+        AsyncListener wrap = new AsyncListener()
+        {
+            @Override
+            public void onTimeout(AsyncEvent event) throws IOException
+            {
+                listener.onTimeout(new AsyncEvent(event.getAsyncContext(),request,response,event.getThrowable()));
+            }
+            
+            @Override
+            public void onStartAsync(AsyncEvent event) throws IOException
+            {
+                listener.onStartAsync(new AsyncEvent(event.getAsyncContext(),request,response,event.getThrowable()));
+            }
+            
+            @Override
+            public void onError(AsyncEvent event) throws IOException
+            {
+                listener.onComplete(new AsyncEvent(event.getAsyncContext(),request,response,event.getThrowable()));
+            }
+            
+            @Override
+            public void onComplete(AsyncEvent event) throws IOException
+            {                
+                listener.onComplete(new AsyncEvent(event.getAsyncContext(),request,response,event.getThrowable()));
+            }
+        };
+        state().addListener(wrap);
+    }
+
+    @Override
+    public void addListener(AsyncListener listener)
+    {
+        state().addListener(listener);
+    }
+
+    @Override
+    public void complete()
+    {
+        state().complete();
+    }
+
+    @Override
+    public <T extends AsyncListener> T createListener(Class<T> clazz) throws ServletException
+    {    
+        ContextHandler contextHandler = state().getContextHandler();
+        if (contextHandler != null)
+            return contextHandler.getServletContext().createListener(clazz);
+        try
+        {
+            return clazz.newInstance();
+        }
+        catch (Exception e)
+        {
+            throw new ServletException(e);
+        }
+    }
+
+    @Override
+    public void dispatch()
+    {
+        state().dispatch(null,null);
+    }
+
+    @Override
+    public void dispatch(String path)
+    {
+        state().dispatch(null,path);
+    }
+    
+    @Override
+    public void dispatch(ServletContext context, String path)
+    {
+        state().dispatch(context,path);
+    }
+
+    @Override
+    public ServletRequest getRequest()
+    {
+        return state().getAsyncContextEvent().getSuppliedRequest();
+    }
+
+    @Override
+    public ServletResponse getResponse()
+    {
+        return state().getAsyncContextEvent().getSuppliedResponse();
+    }
+
+    @Override
+    public long getTimeout()
+    {
+        return state().getTimeout();
+    }
+
+    @Override
+    public boolean hasOriginalRequestAndResponse()
+    {
+        HttpChannel<?> channel=state().getHttpChannel();
+        return channel.getRequest()==getRequest() && channel.getResponse()==getResponse();
+    }
+
+    @Override
+    public void setTimeout(long arg0)
+    {
+        state().setTimeout(arg0);
+    }
+
+    @Override
+    public void start(final Runnable task)
+    {
+        state().getHttpChannel().execute(new Runnable()
+        {
+            @Override
+            public void run()
+            {
+                state().getAsyncContextEvent().getContext().getContextHandler().handle(task);
+            }
+        });
+    }
+
+    public void reset()
+    {
+        _state=null;
+    }
+
+    public HttpChannelState getHttpChannelState()
+    {
+        return state();
+    }
+
+    
+
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncContinuation.java b/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncContinuation.java
deleted file mode 100644
index ffebbb1..0000000
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncContinuation.java
+++ /dev/null
@@ -1,1160 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.server;
-
-import javax.servlet.AsyncContext;
-import javax.servlet.AsyncEvent;
-import javax.servlet.AsyncListener;
-import javax.servlet.RequestDispatcher;
-import javax.servlet.ServletException;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.servlet.ServletContext;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
-import javax.servlet.http.HttpServletRequest;
-
-import org.eclipse.jetty.continuation.Continuation;
-import org.eclipse.jetty.continuation.ContinuationThrowable;
-import org.eclipse.jetty.continuation.ContinuationListener;
-import org.eclipse.jetty.io.AsyncEndPoint;
-import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.server.handler.ContextHandler;
-import org.eclipse.jetty.server.handler.ContextHandler.Context;
-import org.eclipse.jetty.util.URIUtil;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.util.thread.Timeout;
-
-/* ------------------------------------------------------------ */
-/** Implementation of Continuation and AsyncContext interfaces
- * 
- */
-public class AsyncContinuation implements AsyncContext, Continuation
-{
-    private static final Logger LOG = Log.getLogger(AsyncContinuation.class);
-
-    private final static long DEFAULT_TIMEOUT=30000L;
-    
-    private final static ContinuationThrowable __exception = new ContinuationThrowable();
-    
-    // STATES:
-    //               handling()    suspend()     unhandle()    resume()       complete()  doComplete()
-    //                             startAsync()                dispatch()   
-    // IDLE          DISPATCHED      
-    // DISPATCHED                  ASYNCSTARTED  UNCOMPLETED
-    // ASYNCSTARTED                              ASYNCWAIT     REDISPATCHING  COMPLETING
-    // REDISPATCHING                             REDISPATCHED  
-    // ASYNCWAIT                                               REDISPATCH     COMPLETING
-    // REDISPATCH    REDISPATCHED
-    // REDISPATCHED                ASYNCSTARTED  UNCOMPLETED
-    // COMPLETING    UNCOMPLETED                 UNCOMPLETED
-    // UNCOMPLETED                                                                        COMPLETED
-    // COMPLETED
-    private static final int __IDLE=0;         // Idle request
-    private static final int __DISPATCHED=1;   // Request dispatched to filter/servlet
-    private static final int __ASYNCSTARTED=2; // Suspend called, but not yet returned to container
-    private static final int __REDISPATCHING=3;// resumed while dispatched
-    private static final int __ASYNCWAIT=4;    // Suspended and parked
-    private static final int __REDISPATCH=5;   // Has been scheduled
-    private static final int __REDISPATCHED=6; // Request redispatched to filter/servlet
-    private static final int __COMPLETING=7;   // complete while dispatched
-    private static final int __UNCOMPLETED=8;  // Request is completable
-    private static final int __COMPLETED=9;    // Request is complete
-    
-    /* ------------------------------------------------------------ */
-    protected AbstractHttpConnection _connection;
-    private List<AsyncListener> _lastAsyncListeners;
-    private List<AsyncListener> _asyncListeners;
-    private List<ContinuationListener> _continuationListeners;
-
-    /* ------------------------------------------------------------ */
-    private int _state;
-    private boolean _initial;
-    private boolean _resumed;
-    private boolean _expired;
-    private volatile boolean _responseWrapped;
-    private long _timeoutMs=DEFAULT_TIMEOUT;
-    private AsyncEventState _event;
-    private volatile long _expireAt;    
-    private volatile boolean _continuation;
-    
-    /* ------------------------------------------------------------ */
-    protected AsyncContinuation()
-    {
-        _state=__IDLE;
-        _initial=true;
-    }
-
-    /* ------------------------------------------------------------ */
-    protected void setConnection(final AbstractHttpConnection connection)
-    {
-        synchronized(this)
-        {
-            _connection=connection;
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    public void addListener(AsyncListener listener)
-    {
-        synchronized(this)
-        {
-            if (_asyncListeners==null)
-                _asyncListeners=new ArrayList<AsyncListener>();
-            _asyncListeners.add(listener);
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    public void addListener(AsyncListener listener,ServletRequest request, ServletResponse response)
-    {
-        synchronized(this)
-        {
-            // TODO handle the request/response ???
-            if (_asyncListeners==null)
-                _asyncListeners=new ArrayList<AsyncListener>();
-            _asyncListeners.add(listener);
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    public void addContinuationListener(ContinuationListener listener)
-    {
-        synchronized(this)
-        {
-            if (_continuationListeners==null)
-                _continuationListeners=new ArrayList<ContinuationListener>();
-            _continuationListeners.add(listener);
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    public void setTimeout(long ms)
-    {
-        synchronized(this)
-        {
-            _timeoutMs=ms;
-        }
-    } 
-
-    /* ------------------------------------------------------------ */
-    public long getTimeout()
-    {
-        synchronized(this)
-        {
-            return _timeoutMs;
-        }
-    } 
-
-    /* ------------------------------------------------------------ */
-    public AsyncEventState getAsyncEventState()
-    {
-        synchronized(this)
-        {
-            return _event;
-        }
-    } 
-   
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.continuation.Continuation#keepWrappers()
-     */
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.continuation.Continuation#isResponseWrapped()
-     */
-    public boolean isResponseWrapped()
-    {
-        return _responseWrapped;
-    }
-
-    /* ------------------------------------------------------------ */
-    /* (non-Javadoc)
-     * @see javax.servlet.ServletRequest#isInitial()
-     */
-    public boolean isInitial()
-    {
-        synchronized(this)
-        {
-            return _initial;
-        }
-    }
-    
-    public boolean isContinuation()
-    {
-        return _continuation;
-    }
-    
-    /* ------------------------------------------------------------ */
-    /* (non-Javadoc)
-     * @see javax.servlet.ServletRequest#isSuspended()
-     */
-    public boolean isSuspended()
-    {
-        synchronized(this)
-        {
-            switch(_state)
-            {
-                case __ASYNCSTARTED:
-                case __REDISPATCHING:
-                case __COMPLETING:
-                case __ASYNCWAIT:
-                    return true;
-                    
-                default:
-                    return false;   
-            }
-        }
-    }
-    
-    /* ------------------------------------------------------------ */
-    public boolean isSuspending()
-    {
-        synchronized(this)
-        {
-            switch(_state)
-            {
-                case __ASYNCSTARTED:
-                case __ASYNCWAIT:
-                    return true;
-                    
-                default:
-                    return false;   
-            }
-        }
-    }
-    
-    /* ------------------------------------------------------------ */
-    public boolean isDispatchable()
-    {
-        synchronized(this)
-        {
-            switch(_state)
-            {
-                case __REDISPATCH:
-                case __REDISPATCHED:
-                case __REDISPATCHING:
-                case __COMPLETING:
-                    return true;
-                    
-                default:
-                    return false;   
-            }
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public String toString()
-    {
-        synchronized (this)
-        {
-            return super.toString()+"@"+getStatusString();
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    public String getStatusString()
-    {
-        synchronized (this)
-        {
-            return
-            ((_state==__IDLE)?"IDLE":
-                (_state==__DISPATCHED)?"DISPATCHED":
-                    (_state==__ASYNCSTARTED)?"ASYNCSTARTED":
-                        (_state==__ASYNCWAIT)?"ASYNCWAIT":
-                            (_state==__REDISPATCHING)?"REDISPATCHING":
-                                (_state==__REDISPATCH)?"REDISPATCH":
-                                    (_state==__REDISPATCHED)?"REDISPATCHED":
-                                        (_state==__COMPLETING)?"COMPLETING":
-                                            (_state==__UNCOMPLETED)?"UNCOMPLETED":
-                                                (_state==__COMPLETED)?"COMPLETE":
-                                                    ("UNKNOWN?"+_state))+
-            (_initial?",initial":"")+
-            (_resumed?",resumed":"")+
-            (_expired?",expired":"");
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return false if the handling of the request should not proceed
-     */
-    protected boolean handling()
-    {
-        synchronized (this)
-        {
-            _continuation=false;
-            
-            switch(_state)
-            {
-                case __IDLE:
-                    _initial=true;
-                    _state=__DISPATCHED;
-                    if (_lastAsyncListeners!=null)
-                        _lastAsyncListeners.clear();
-                    if (_asyncListeners!=null)
-                        _asyncListeners.clear();
-                    else
-                    {
-                        _asyncListeners=_lastAsyncListeners;
-                        _lastAsyncListeners=null;
-                    }
-                    return true;
-                    
-                case __COMPLETING:
-                    _state=__UNCOMPLETED;
-                    return false;
-
-                case __ASYNCWAIT:
-                    return false;
-                    
-                case __REDISPATCH:
-                    _state=__REDISPATCHED;
-                    return true;
-
-                default:
-                    throw new IllegalStateException(this.getStatusString());
-            }
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    /* (non-Javadoc)
-     * @see javax.servlet.ServletRequest#suspend(long)
-     */
-    private void doSuspend(final ServletContext context,
-            final ServletRequest request,
-            final ServletResponse response)
-    {
-        synchronized (this)
-        {
-            switch(_state)
-            {
-                case __DISPATCHED:
-                case __REDISPATCHED:
-                    _resumed=false;
-                    _expired=false;
-
-                    if (_event==null || request!=_event.getSuppliedRequest() || response != _event.getSuppliedResponse() || context != _event.getServletContext())
-                        _event=new AsyncEventState(context,request,response);
-                    else
-                    {
-                        _event._dispatchContext=null;
-                        _event._pathInContext=null;
-                    }
-                    _state=__ASYNCSTARTED;
-                    List<AsyncListener> recycle=_lastAsyncListeners;
-                    _lastAsyncListeners=_asyncListeners;
-                    _asyncListeners=recycle;
-                    if (_asyncListeners!=null)
-                        _asyncListeners.clear();
-                    break;
-
-                default:
-                    throw new IllegalStateException(this.getStatusString());
-            }
-        }
-        
-        if (_lastAsyncListeners!=null)
-        {
-            for (AsyncListener listener : _lastAsyncListeners)
-            {
-                try
-                {
-                    listener.onStartAsync(_event);
-                }
-                catch(Exception e)
-                {
-                    LOG.warn(e);
-                }
-            }
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Signal that the HttpConnection has finished handling the request.
-     * For blocking connectors, this call may block if the request has
-     * been suspended (startAsync called).
-     * @return true if handling is complete, false if the request should 
-     * be handled again (eg because of a resume that happened before unhandle was called)
-     */
-    protected boolean unhandle()
-    {
-        synchronized (this)
-        {
-            switch(_state)
-            {
-                case __REDISPATCHED:
-                case __DISPATCHED:
-                    _state=__UNCOMPLETED;
-                    return true;
-
-                case __IDLE:
-                    throw new IllegalStateException(this.getStatusString());
-
-                case __ASYNCSTARTED:
-                    _initial=false;
-                    _state=__ASYNCWAIT;
-                    scheduleTimeout(); // could block and change state.
-                    if (_state==__ASYNCWAIT)
-                        return true;
-                    else if (_state==__COMPLETING)
-                    {
-                        _state=__UNCOMPLETED;
-                        return true;
-                    }         
-                    _initial=false;
-                    _state=__REDISPATCHED;
-                    return false; 
-
-                case __REDISPATCHING:
-                    _initial=false;
-                    _state=__REDISPATCHED;
-                    return false; 
-
-                case __COMPLETING:
-                    _initial=false;
-                    _state=__UNCOMPLETED;
-                    return true;
-
-                default:
-                    throw new IllegalStateException(this.getStatusString());
-            }
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    public void dispatch()
-    {
-        boolean dispatch=false;
-        synchronized (this)
-        {
-            switch(_state)
-            {
-                case __ASYNCSTARTED:
-                    _state=__REDISPATCHING;
-                    _resumed=true;
-                    return;
-
-                case __ASYNCWAIT:
-                    dispatch=!_expired;
-                    _state=__REDISPATCH;
-                    _resumed=true;
-                    break;
-                    
-                case __REDISPATCH:
-                    return;
-                    
-                default:
-                    throw new IllegalStateException(this.getStatusString());
-            }
-        }
-        
-        if (dispatch)
-        {
-            cancelTimeout();
-            scheduleDispatch();
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    protected void expired()
-    {
-        final List<ContinuationListener> cListeners;
-        final List<AsyncListener> aListeners;
-        synchronized (this)
-        {
-            switch(_state)
-            {
-                case __ASYNCSTARTED:
-                case __ASYNCWAIT:
-                    cListeners=_continuationListeners;
-                    aListeners=_asyncListeners;
-                    break;
-                default:
-                    cListeners=null;
-                    aListeners=null;
-                    return;
-            }
-            _expired=true;
-        }
-        
-        if (aListeners!=null)
-        {
-            for (AsyncListener listener : aListeners)
-            {
-                try
-                {
-                    listener.onTimeout(_event);
-                }
-                catch(Exception e)
-                {
-                    LOG.debug(e);
-                    _connection.getRequest().setAttribute(RequestDispatcher.ERROR_EXCEPTION,e);
-                    break;
-                }
-            }
-        }
-        if (cListeners!=null)
-        {
-            for (ContinuationListener listener : cListeners)
-            {
-                try
-                {
-                    listener.onTimeout(this);
-                }
-                catch(Exception e)
-                {
-                    LOG.warn(e);
-                }
-            }
-        }
-        
-        synchronized (this)
-        {
-            switch(_state)
-            {
-                case __ASYNCSTARTED:
-                case __ASYNCWAIT:
-                    dispatch();
-                    break;
-                    
-                default:
-                    if (!_continuation)
-                        _expired=false;
-            }
-        }
-
-        scheduleDispatch();
-    }
-    
-    /* ------------------------------------------------------------ */
-    /* (non-Javadoc)
-     * @see javax.servlet.ServletRequest#complete()
-     */
-    public void complete()
-    {
-        // just like resume, except don't set _resumed=true;
-        boolean dispatch=false;
-        synchronized (this)
-        {
-            switch(_state)
-            {
-                case __DISPATCHED:
-                case __REDISPATCHED:
-                    throw new IllegalStateException(this.getStatusString());
-
-                case __ASYNCSTARTED:
-                    _state=__COMPLETING;
-                    return;
-                    
-                case __ASYNCWAIT:
-                    _state=__COMPLETING;
-                    dispatch=!_expired;
-                    break;
-                    
-                default:
-                    throw new IllegalStateException(this.getStatusString());
-            }
-        }
-        
-        if (dispatch)
-        {
-            cancelTimeout();
-            scheduleDispatch();
-        }
-    }
-    
-    /* ------------------------------------------------------------ */
-    /* (non-Javadoc)
-     * @see javax.servlet.ServletRequest#complete()
-     */
-    public void errorComplete()
-    {
-        // just like complete except can overrule a prior dispatch call;
-        synchronized (this)
-        {
-            switch(_state)
-            {
-                case __REDISPATCHING:
-                case __ASYNCSTARTED:
-                    _state=__COMPLETING;
-                    _resumed=false;
-                    return;
-                    
-                case __COMPLETING:
-                    return;
-                    
-                default:
-                    throw new IllegalStateException(this.getStatusString());
-            }
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public <T extends AsyncListener> T createListener(Class<T> clazz) throws ServletException 
-    {
-        try
-        {
-            // TODO inject
-            return clazz.newInstance();
-        }
-        catch(Exception e)
-        {
-            throw new ServletException(e);
-        }
-    }
-
-
-    /* ------------------------------------------------------------ */
-    /* (non-Javadoc)
-     * @see javax.servlet.ServletRequest#complete()
-     */
-    protected void doComplete(Throwable ex)
-    {
-        final List<ContinuationListener> cListeners;
-        final List<AsyncListener> aListeners;
-        synchronized (this)
-        {
-            switch(_state)
-            {
-                case __UNCOMPLETED:
-                    _state=__COMPLETED;
-                    cListeners=_continuationListeners;
-                    aListeners=_asyncListeners;
-                    break;
-                    
-                default:
-                    cListeners=null;
-                    aListeners=null;
-                    throw new IllegalStateException(this.getStatusString());
-            }
-        }
-        
-        if (aListeners!=null)
-        {
-            for (AsyncListener listener : aListeners)
-            {
-                try
-                {
-                    if (ex!=null)
-                    {
-                        _event.getSuppliedRequest().setAttribute(RequestDispatcher.ERROR_EXCEPTION,ex);
-                        _event.getSuppliedRequest().setAttribute(RequestDispatcher.ERROR_MESSAGE,ex.getMessage());
-                        listener.onError(_event);
-                    }
-                    else
-                        listener.onComplete(_event);
-                }
-                catch(Exception e)
-                {
-                    LOG.warn(e);
-                }
-            }
-        }
-        if (cListeners!=null)
-        {
-            for (ContinuationListener listener : cListeners)
-            {
-                try
-                {
-                    listener.onComplete(this);
-                }
-                catch(Exception e)
-                {
-                    LOG.warn(e);
-                }
-            }
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    protected void recycle()
-    {
-        synchronized (this)
-        {
-            switch(_state)
-            {
-                case __DISPATCHED:
-                case __REDISPATCHED:
-                    throw new IllegalStateException(getStatusString());
-                default:
-                    _state=__IDLE;
-            }
-            _initial = true;
-            _resumed=false;
-            _expired=false;
-            _responseWrapped=false;
-            cancelTimeout();
-            _timeoutMs=DEFAULT_TIMEOUT;
-            _continuationListeners=null;
-        }
-    }    
-    
-    /* ------------------------------------------------------------ */
-    public void cancel()
-    {
-        synchronized (this)
-        {
-            cancelTimeout();
-            _continuationListeners=null;
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    protected void scheduleDispatch()
-    {
-        EndPoint endp=_connection.getEndPoint();
-        if (!endp.isBlocking())
-        {
-            ((AsyncEndPoint)endp).asyncDispatch();
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    protected void scheduleTimeout()
-    {
-        EndPoint endp=_connection.getEndPoint();
-        if (_timeoutMs>0)
-        {
-            if (endp.isBlocking())
-            {
-                synchronized(this)
-                {
-                    _expireAt = System.currentTimeMillis()+_timeoutMs;
-                    long wait=_timeoutMs;
-                    while (_expireAt>0 && wait>0 && _connection.getServer().isRunning())
-                    {
-                        try
-                        {
-                            this.wait(wait);
-                        }
-                        catch (InterruptedException e)
-                        {
-                            LOG.ignore(e);
-                        }
-                        wait=_expireAt-System.currentTimeMillis();
-                    }
-
-                    if (_expireAt>0 && wait<=0 && _connection.getServer().isRunning())
-                    {
-                        expired();
-                    }
-                }            
-            }
-            else
-            {
-                ((AsyncEndPoint)endp).scheduleTimeout(_event._timeout,_timeoutMs);
-            }
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    protected void cancelTimeout()
-    {
-        EndPoint endp=_connection.getEndPoint();
-        if (endp.isBlocking())
-        {
-            synchronized(this)
-            {
-                _expireAt=0;
-                this.notifyAll();
-            }
-        }
-        else 
-        {
-            final AsyncEventState event=_event;
-            if (event!=null)
-            {
-                ((AsyncEndPoint)endp).cancelTimeout(event._timeout);
-            }
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    public boolean isCompleting()
-    {
-        synchronized (this)
-        {
-            return _state==__COMPLETING;
-        }
-    }
-    
-    /* ------------------------------------------------------------ */
-    boolean isUncompleted()
-    {
-        synchronized (this)
-        {
-            return _state==__UNCOMPLETED;
-        }
-    } 
-    
-    /* ------------------------------------------------------------ */
-    public boolean isComplete()
-    {
-        synchronized (this)
-        {
-            return _state==__COMPLETED;
-        }
-    }
-
-
-    /* ------------------------------------------------------------ */
-    public boolean isAsyncStarted()
-    {
-        synchronized (this)
-        {
-            switch(_state)
-            {
-                case __ASYNCSTARTED:
-                case __REDISPATCHING:
-                case __REDISPATCH:
-                case __ASYNCWAIT:
-                    return true;
-
-                default:
-                    return false;
-            }
-        }
-    }
-
-
-    /* ------------------------------------------------------------ */
-    public boolean isAsync()
-    {
-        synchronized (this)
-        {
-            switch(_state)
-            {
-                case __IDLE:
-                case __DISPATCHED:
-                case __UNCOMPLETED:
-                case __COMPLETED:
-                    return false;
-
-                default:
-                    return true;
-            }
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    public void dispatch(ServletContext context, String path)
-    {
-        _event._dispatchContext=context;
-        _event.setPath(path);
-        dispatch();
-    }
-
-    /* ------------------------------------------------------------ */
-    public void dispatch(String path)
-    {
-        _event.setPath(path);
-        dispatch();
-    }
-
-    /* ------------------------------------------------------------ */
-    public Request getBaseRequest()
-    {
-        return _connection.getRequest();
-    }
-    
-    /* ------------------------------------------------------------ */
-    public ServletRequest getRequest()
-    {
-        if (_event!=null)
-            return _event.getSuppliedRequest();
-        return _connection.getRequest();
-    }
-
-    /* ------------------------------------------------------------ */
-    public ServletResponse getResponse()
-    {
-        if (_responseWrapped && _event!=null && _event.getSuppliedResponse()!=null)
-            return _event.getSuppliedResponse();
-        return _connection.getResponse();
-    }
-
-    /* ------------------------------------------------------------ */
-    public void start(final Runnable run)
-    {
-        final AsyncEventState event=_event;
-        if (event!=null)
-        {
-            _connection.getServer().getThreadPool().dispatch(new Runnable()
-            {
-                public void run()
-                {
-                    ((Context)event.getServletContext()).getContextHandler().handle(run);
-                }
-            });
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    public boolean hasOriginalRequestAndResponse()
-    {
-        synchronized (this)
-        {
-            return (_event!=null && _event.getSuppliedRequest()==_connection._request && _event.getSuppliedResponse()==_connection._response);
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    public ContextHandler getContextHandler()
-    {
-        final AsyncEventState event=_event;
-        if (event!=null)
-            return ((Context)event.getServletContext()).getContextHandler();
-        return null;
-    }
-
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see Continuation#isResumed()
-     */
-    public boolean isResumed()
-    {
-        synchronized (this)
-        {
-            return _resumed;
-        }
-    }
-    /* ------------------------------------------------------------ */
-    /**
-     * @see Continuation#isExpired()
-     */
-    public boolean isExpired()
-    {
-        synchronized (this)
-        {
-            return _expired;
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see Continuation#resume()
-     */
-    public void resume()
-    {
-        dispatch();
-    }
-    
-
-
-    /* ------------------------------------------------------------ */
-    protected void startAsync(final ServletContext context,
-            final ServletRequest request,
-            final ServletResponse response)
-    {
-        synchronized (this)
-        {
-            _responseWrapped=!(response instanceof Response);
-            doSuspend(context,request,response);
-            if (request instanceof HttpServletRequest)
-            {
-                _event._pathInContext = URIUtil.addPaths(((HttpServletRequest)request).getServletPath(),((HttpServletRequest)request).getPathInfo());
-            }
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    protected void startAsync()
-    {
-        _responseWrapped=false;
-        _continuation=false;
-        doSuspend(_connection.getRequest().getServletContext(),_connection.getRequest(),_connection.getResponse());  
-    }
-
-    
-    /* ------------------------------------------------------------ */
-    /**
-     * @see Continuation#suspend()
-     */
-    public void suspend(ServletResponse response)
-    {
-        _continuation=true;
-        _responseWrapped=!(response instanceof Response);
-        doSuspend(_connection.getRequest().getServletContext(),_connection.getRequest(),response); 
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see Continuation#suspend()
-     */
-    public void suspend()
-    {
-        _responseWrapped=false;
-        _continuation=true;
-        doSuspend(_connection.getRequest().getServletContext(),_connection.getRequest(),_connection.getResponse());       
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.continuation.Continuation#getServletResponse()
-     */
-    public ServletResponse getServletResponse()
-    {
-        if (_responseWrapped && _event!=null && _event.getSuppliedResponse()!=null)
-            return _event.getSuppliedResponse();
-        return _connection.getResponse();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.continuation.Continuation#getAttribute(java.lang.String)
-     */
-    public Object getAttribute(String name)
-    {
-        return _connection.getRequest().getAttribute(name);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.continuation.Continuation#removeAttribute(java.lang.String)
-     */
-    public void removeAttribute(String name)
-    {
-        _connection.getRequest().removeAttribute(name);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.continuation.Continuation#setAttribute(java.lang.String, java.lang.Object)
-     */
-    public void setAttribute(String name, Object attribute)
-    {
-        _connection.getRequest().setAttribute(name,attribute);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.continuation.Continuation#undispatch()
-     */
-    public void undispatch()
-    {
-        if (isSuspended())
-        {
-            if (LOG.isDebugEnabled())
-                throw new ContinuationThrowable();
-            else
-                throw __exception;
-        }
-        throw new IllegalStateException("!suspended");
-    }
-
-    /* ------------------------------------------------------------ */
-    /* ------------------------------------------------------------ */
-    public class AsyncTimeout extends Timeout.Task implements Runnable
-    {
-            @Override
-            public void expired()
-            {
-                AsyncContinuation.this.expired();
-            }
-
-            @Override
-            public void run()
-            {
-                AsyncContinuation.this.expired();
-            }
-    }
-
-    /* ------------------------------------------------------------ */
-    /* ------------------------------------------------------------ */
-    public class AsyncEventState extends AsyncEvent
-    {
-        private final ServletContext _suspendedContext;
-        private ServletContext _dispatchContext;
-        private String _pathInContext;
-        private Timeout.Task _timeout=  new AsyncTimeout();
-        
-        public AsyncEventState(ServletContext context, ServletRequest request, ServletResponse response)
-        {
-            super(AsyncContinuation.this, request,response);
-            _suspendedContext=context;
-            // Get the base request So we can remember the initial paths
-            Request r=_connection.getRequest();
- 
-            // If we haven't been async dispatched before
-            if (r.getAttribute(AsyncContext.ASYNC_REQUEST_URI)==null)
-            {
-                // We are setting these attributes during startAsync, when the spec implies that 
-                // they are only available after a call to AsyncContext.dispatch(...);
-                
-                // have we been forwarded before?
-                String uri=(String)r.getAttribute(RequestDispatcher.FORWARD_REQUEST_URI);
-                if (uri!=null)
-                {
-                    r.setAttribute(AsyncContext.ASYNC_REQUEST_URI,uri);
-                    r.setAttribute(AsyncContext.ASYNC_CONTEXT_PATH,r.getAttribute(RequestDispatcher.FORWARD_CONTEXT_PATH));
-                    r.setAttribute(AsyncContext.ASYNC_SERVLET_PATH,r.getAttribute(RequestDispatcher.FORWARD_SERVLET_PATH));
-                    r.setAttribute(AsyncContext.ASYNC_PATH_INFO,r.getAttribute(RequestDispatcher.FORWARD_PATH_INFO));
-                    r.setAttribute(AsyncContext.ASYNC_QUERY_STRING,r.getAttribute(RequestDispatcher.FORWARD_QUERY_STRING));
-                }
-                else
-                {
-                    r.setAttribute(AsyncContext.ASYNC_REQUEST_URI,r.getRequestURI());
-                    r.setAttribute(AsyncContext.ASYNC_CONTEXT_PATH,r.getContextPath());
-                    r.setAttribute(AsyncContext.ASYNC_SERVLET_PATH,r.getServletPath());
-                    r.setAttribute(AsyncContext.ASYNC_PATH_INFO,r.getPathInfo());
-                    r.setAttribute(AsyncContext.ASYNC_QUERY_STRING,r.getQueryString());
-                }
-            }
-        }
-        
-        public ServletContext getSuspendedContext()
-        {
-            return _suspendedContext;
-        }
-        
-        public ServletContext getDispatchContext()
-        {
-            return _dispatchContext;
-        }
-        
-        public ServletContext getServletContext()
-        {
-            return _dispatchContext==null?_suspendedContext:_dispatchContext;
-        }
-        
-        public void setPath(String path)
-        {
-            _pathInContext=path;
-        }
-        
-        /* ------------------------------------------------------------ */
-        /**
-         * @return The path in the context
-         */
-        public String getPath()
-        {
-            return _pathInContext;
-        }
-    }
-}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncHttpConnection.java b/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncHttpConnection.java
deleted file mode 100644
index 5fcbba6..0000000
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncHttpConnection.java
+++ /dev/null
@@ -1,220 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.server;
-
-import java.io.IOException;
-
-import org.eclipse.jetty.http.HttpException;
-import org.eclipse.jetty.http.HttpStatus;
-import org.eclipse.jetty.io.AsyncEndPoint;
-import org.eclipse.jetty.io.Connection;
-import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.io.nio.AsyncConnection;
-import org.eclipse.jetty.io.nio.SelectChannelEndPoint;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-
-
-/* ------------------------------------------------------------ */
-/** Asychronous Server HTTP connection
- *
- */
-public class AsyncHttpConnection extends AbstractHttpConnection implements AsyncConnection
-{
-    private final static int NO_PROGRESS_INFO = Integer.getInteger("org.mortbay.jetty.NO_PROGRESS_INFO",100);
-    private final static int NO_PROGRESS_CLOSE = Integer.getInteger("org.mortbay.jetty.NO_PROGRESS_CLOSE",200);
-
-    private static final Logger LOG = Log.getLogger(AsyncHttpConnection.class);
-    private int _total_no_progress;
-    private final AsyncEndPoint _asyncEndp;
-    private boolean _readInterested = true;
-
-    public AsyncHttpConnection(Connector connector, EndPoint endpoint, Server server)
-    {
-        super(connector,endpoint,server);
-        _asyncEndp=(AsyncEndPoint)endpoint;
-    }
-
-    @Override
-    public Connection handle() throws IOException
-    {
-        Connection connection = this;
-        boolean some_progress=false;
-        boolean progress=true;
-
-        try
-        {
-            setCurrentConnection(this);
-
-            // don't check for idle while dispatched (unless blocking IO is done).
-            _asyncEndp.setCheckForIdle(false);
-
-
-            // While progress and the connection has not changed
-            while (progress && connection==this)
-            {
-                progress=false;
-                try
-                {
-                    // Handle resumed request
-                    if (_request._async.isAsync())
-                    {
-                       if (_request._async.isDispatchable())
-                           handleRequest();
-                    }
-                    // else Parse more input
-                    else if (!_parser.isComplete() && _parser.parseAvailable())
-                        progress=true;
-
-                    // Generate more output
-                    if (_generator.isCommitted() && !_generator.isComplete() && !_endp.isOutputShutdown() && !_request.getAsyncContinuation().isAsyncStarted())
-                        if (_generator.flushBuffer()>0)
-                            progress=true;
-
-                    // Flush output
-                    _endp.flush();
-
-                    // Has any IO been done by the endpoint itself since last loop
-                    if (_asyncEndp.hasProgressed())
-                        progress=true;
-                }
-                catch (HttpException e)
-                {
-                    if (LOG.isDebugEnabled())
-                    {
-                        LOG.debug("uri="+_uri);
-                        LOG.debug("fields="+_requestFields);
-                        LOG.debug(e);
-                    }
-                    progress=true;
-                    _generator.sendError(e.getStatus(), e.getReason(), null, true);
-                }
-                finally
-                {
-                    some_progress|=progress;
-                    //  Is this request/response round complete and are fully flushed?
-                    boolean parserComplete = _parser.isComplete();
-                    boolean generatorComplete = _generator.isComplete();
-                    boolean complete = parserComplete && generatorComplete;
-                    if (parserComplete)
-                    {
-                        if (generatorComplete)
-                        {
-                            // Reset the parser/generator
-                            progress=true;
-
-                            // look for a switched connection instance?
-                            if (_response.getStatus()==HttpStatus.SWITCHING_PROTOCOLS_101)
-                            {
-                                Connection switched=(Connection)_request.getAttribute("org.eclipse.jetty.io.Connection");
-                                if (switched!=null)
-                                    connection=switched;
-                            }
-
-                            reset();
-
-                            // TODO Is this still required?
-                            if (!_generator.isPersistent() && !_endp.isOutputShutdown())
-                            {
-                                LOG.warn("Safety net oshut!!!  IF YOU SEE THIS, PLEASE RAISE BUGZILLA");
-                                _endp.shutdownOutput();
-                            }
-                        }
-                        else
-                        {
-                            // We have finished parsing, but not generating so
-                            // we must not be interested in reading until we
-                            // have finished generating and we reset the generator
-                            _readInterested = false;
-                            LOG.debug("Disabled read interest while writing response {}", _endp);
-                        }
-                    }
-
-                    if (!complete && _request.getAsyncContinuation().isAsyncStarted())
-                    {
-                        // The request is suspended, so even though progress has been made,
-                        // exit the while loop by setting progress to false
-                        LOG.debug("suspended {}",this);
-                        progress=false;
-                    }
-                }
-            }
-        }
-        finally
-        {
-            setCurrentConnection(null);
-
-            // If we are not suspended
-            if (!_request.getAsyncContinuation().isAsyncStarted())
-            {
-                // return buffers
-                _parser.returnBuffers();
-                _generator.returnBuffers();
-
-                // reenable idle checking unless request is suspended
-                _asyncEndp.setCheckForIdle(true);
-            }
-
-            // Safety net to catch spinning
-            if (some_progress)
-                _total_no_progress=0;
-            else
-            {
-                _total_no_progress++;
-                if (NO_PROGRESS_INFO>0 && _total_no_progress%NO_PROGRESS_INFO==0 && (NO_PROGRESS_CLOSE<=0 || _total_no_progress< NO_PROGRESS_CLOSE))
-                    LOG.info("EndPoint making no progress: "+_total_no_progress+" "+_endp+" "+this);
-                if (NO_PROGRESS_CLOSE>0 && _total_no_progress==NO_PROGRESS_CLOSE)
-                {
-                    LOG.warn("Closing EndPoint making no progress: "+_total_no_progress+" "+_endp+" "+this);
-                    if (_endp instanceof SelectChannelEndPoint)
-                        ((SelectChannelEndPoint)_endp).getChannel().close();
-                }
-            }
-        }
-        return connection;
-    }
-
-    public void onInputShutdown() throws IOException
-    {
-        // If we don't have a committed response and we are not suspended
-        if (_generator.isIdle() && !_request.getAsyncContinuation().isSuspended())
-        {
-            // then no more can happen, so close.
-            _endp.close();
-        }
-
-        // Make idle parser seek EOF
-        if (_parser.isIdle())
-            _parser.setPersistent(false);
-    }
-
-    @Override
-    public void reset()
-    {
-        _readInterested = true;
-        LOG.debug("Enabled read interest {}", _endp);
-        super.reset();
-    }
-
-    @Override
-    public boolean isSuspended()
-    {
-        return !_readInterested || super.isSuspended();
-    }
-}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncNCSARequestLog.java b/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncNCSARequestLog.java
index 5289c20..19ec50b 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncNCSARequestLog.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncNCSARequestLog.java
@@ -18,15 +18,14 @@
 
 package org.eclipse.jetty.server;
 
-import org.eclipse.jetty.util.BlockingArrayQueue;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-
 import java.io.IOException;
 import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
 
+import org.eclipse.jetty.util.BlockingArrayQueue;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
 
 /* ------------------------------------------------------------ */
 /**
@@ -43,7 +42,7 @@ public class AsyncNCSARequestLog extends NCSARequestLog
     {
         this(null,null);
     }
-    
+
     public AsyncNCSARequestLog(BlockingQueue<String> queue)
     {
         this(null,queue);
@@ -53,12 +52,12 @@ public class AsyncNCSARequestLog extends NCSARequestLog
     {
         this(filename,null);
     }
-    
+
     public AsyncNCSARequestLog(String filename,BlockingQueue<String> queue)
     {
         super(filename);
         if (queue==null)
-            queue=new BlockingArrayQueue<String>(1024);
+            queue=new BlockingArrayQueue<>(1024);
         _queue=queue;
     }
 
@@ -68,7 +67,7 @@ public class AsyncNCSARequestLog extends NCSARequestLog
         {
             setName("AsyncNCSARequestLog@"+Integer.toString(AsyncNCSARequestLog.this.hashCode(),16));
         }
-        
+
         @Override
         public void run()
         {
@@ -79,7 +78,7 @@ public class AsyncNCSARequestLog extends NCSARequestLog
                     String log = _queue.poll(10,TimeUnit.SECONDS);
                     if (log!=null)
                         AsyncNCSARequestLog.super.write(log);
-                    
+
                     while(!_queue.isEmpty())
                     {
                         log=_queue.poll();
@@ -117,7 +116,7 @@ public class AsyncNCSARequestLog extends NCSARequestLog
     }
 
     @Override
-    protected void write(String log) throws IOException
+    public void write(String log) throws IOException
     {
         if (!_queue.offer(log))
         {
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Authentication.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Authentication.java
index b8278ca..66133eb 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/Authentication.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Authentication.java
@@ -36,6 +36,15 @@ import javax.servlet.http.HttpServletResponse;
 public interface Authentication
 {
     /* ------------------------------------------------------------ */
+    public static class Failed extends QuietServletException
+    {
+       public Failed(String message)
+       {
+           super(message);
+       }
+    }
+    
+    /* ------------------------------------------------------------ */
     /** A successful Authentication with User information.
      */
     public interface User extends Authentication
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/BlockingHttpConnection.java b/jetty-server/src/main/java/org/eclipse/jetty/server/BlockingHttpConnection.java
deleted file mode 100644
index 7cd1204..0000000
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/BlockingHttpConnection.java
+++ /dev/null
@@ -1,138 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.server;
-
-import java.io.IOException;
-
-import org.eclipse.jetty.http.Generator;
-import org.eclipse.jetty.http.HttpException;
-import org.eclipse.jetty.http.HttpStatus;
-import org.eclipse.jetty.http.Parser;
-import org.eclipse.jetty.io.Connection;
-import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-
-
-/* ------------------------------------------------------------ */
-/** Blocking Server HTTP Connection
- */
-public class BlockingHttpConnection extends AbstractHttpConnection
-{
-    private static final Logger LOG = Log.getLogger(BlockingHttpConnection.class);
-
-    public BlockingHttpConnection(Connector connector, EndPoint endpoint, Server server)
-    {
-        super(connector,endpoint,server);
-    }
-
-    public BlockingHttpConnection(Connector connector, EndPoint endpoint, Server server, Parser parser, Generator generator, Request request)
-    {
-        super(connector,endpoint,server,parser,generator,request);
-    }
-
-    @Override
-    protected void handleRequest() throws IOException
-    {
-        super.handleRequest();
-    }
-
-    public Connection handle() throws IOException
-    {
-        Connection connection = this;
-
-        try
-        {
-            setCurrentConnection(this);
-
-            // do while the endpoint is open
-            // AND the connection has not changed
-            while (_endp.isOpen() && connection==this)
-            {
-                try
-                {
-                    // If we are not ended then parse available
-                    if (!_parser.isComplete() && !_endp.isInputShutdown())
-                        _parser.parseAvailable();
-
-                    // Do we have more generating to do?
-                    // Loop here because some writes may take multiple steps and
-                    // we need to flush them all before potentially blocking in the
-                    // next loop.
-                    if (_generator.isCommitted() && !_generator.isComplete() && !_endp.isOutputShutdown())
-                        _generator.flushBuffer();
-
-                    // Flush buffers
-                    _endp.flush();
-                }
-                catch (HttpException e)
-                {
-                    if (LOG.isDebugEnabled())
-                    {
-                        LOG.debug("uri="+_uri);
-                        LOG.debug("fields="+_requestFields);
-                        LOG.debug(e);
-                    }
-                    _generator.sendError(e.getStatus(), e.getReason(), null, true);
-                    _parser.reset();
-                    _endp.shutdownOutput();
-                }
-                finally
-                {
-                    //  Is this request/response round complete and are fully flushed?
-                    if (_parser.isComplete() && _generator.isComplete())
-                    {
-                        // Reset the parser/generator
-                        reset();
-
-                        // look for a switched connection instance?
-                        if (_response.getStatus()==HttpStatus.SWITCHING_PROTOCOLS_101)
-                        {
-                            Connection switched=(Connection)_request.getAttribute("org.eclipse.jetty.io.Connection");
-                            if (switched!=null)
-                                connection=switched;
-                        }
-
-                        // TODO Is this required?
-                        if (!_generator.isPersistent() && !_endp.isOutputShutdown())
-                        {
-                            LOG.warn("Safety net oshut!!! Please open a bugzilla");
-                            _endp.shutdownOutput();
-                        }
-                    }
-                    
-                    // If we don't have a committed response and we are not suspended
-                    if (_endp.isInputShutdown() && _generator.isIdle() && !_request.getAsyncContinuation().isSuspended())
-                    {
-                        // then no more can happen, so close.
-                        _endp.close();
-                    }
-                }
-            }
-
-            return connection;
-        }
-        finally
-        {
-            setCurrentConnection(null);
-            _parser.returnBuffers();
-            _generator.returnBuffers();
-        }
-    }
-}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ByteBufferQueuedHttpInput.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ByteBufferQueuedHttpInput.java
new file mode 100644
index 0000000..c900124
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ByteBufferQueuedHttpInput.java
@@ -0,0 +1,53 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.nio.ByteBuffer;
+
+/**
+ * <p>An implementation of HttpInput using {@link ByteBuffer} as items.</p>
+ */
+public class ByteBufferQueuedHttpInput extends QueuedHttpInput<ByteBuffer>
+{
+    @Override
+    protected int remaining(ByteBuffer item)
+    {
+        return item.remaining();
+    }
+
+    @Override
+    protected int get(ByteBuffer item, byte[] buffer, int offset, int length)
+    {
+        int l = Math.min(item.remaining(), length);
+        item.get(buffer, offset, l);
+        return l;
+    }
+    
+    @Override
+    protected void consume(ByteBuffer item, int length)
+    {
+        item.position(item.position()+length);
+    }
+
+    @Override
+    protected void onContentConsumed(ByteBuffer item)
+    {
+    }
+
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ClassLoaderDump.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ClassLoaderDump.java
new file mode 100644
index 0000000..392d841
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ClassLoaderDump.java
@@ -0,0 +1,66 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.net.URLClassLoader;
+import java.util.Collections;
+
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.component.Dumpable;
+
+public class ClassLoaderDump implements Dumpable
+{
+    final ClassLoader _loader;
+
+    public ClassLoaderDump(ClassLoader loader)
+    {
+        _loader = loader;
+    }
+
+    @Override
+    public String dump()
+    {
+        return ContainerLifeCycle.dump(this);
+    }
+
+    @Override
+    public void dump(Appendable out, String indent) throws IOException
+    {
+        if (_loader==null)
+            out.append("No ClassLoader\n");
+        else
+        {
+            out.append(String.valueOf(_loader)).append("\n");
+
+            Object parent = _loader.getParent();
+            if (parent != null)
+            {
+                if (!(parent instanceof Dumpable))
+                    parent = new ClassLoaderDump((ClassLoader)parent);
+
+                if (_loader instanceof URLClassLoader)
+                    ContainerLifeCycle.dump(out,indent,TypeUtil.asList(((URLClassLoader)_loader).getURLs()),Collections.singleton(parent));
+                else
+                    ContainerLifeCycle.dump(out,indent,Collections.singleton(parent));
+            }
+        }
+    }
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ConnectionFactory.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ConnectionFactory.java
new file mode 100644
index 0000000..0b88d9d
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ConnectionFactory.java
@@ -0,0 +1,57 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+
+/**
+ * <p>A Factory to create {@link Connection} instances for {@link Connector}s.</p>
+ * <p>A Connection factory is responsible for instantiating and configuring a {@link Connection} instance
+ * to handle an {@link EndPoint} accepted by a {@link Connector}.</p>
+ * <p>
+ * A ConnectionFactory has a protocol name that represents the protocol of the Connections
+ * created.  Example of protocol names include:<dl>
+ * <dt>http</dt><dd>Creates a HTTP connection that can handle multiple versions of HTTP from 0.9 to 1.1</dd>
+ * <dt>spdy/2</dt><dd>Creates a HTTP connection that handles a specific version of the SPDY protocol</dd>
+ * <dt>SSL-XYZ</dt><dd>Create an SSL connection chained to a connection obtained from a connection factory 
+ * with a protocol "XYZ".</dd>
+ * <dt>SSL-http</dt><dd>Create an SSL connection chained to a HTTP connection (aka https)</dd>
+ * <dt>SSL-npn</dt><dd>Create an SSL connection chained to a NPN connection, that uses a negotiation with
+ * the client to determine the next protocol.</dd>
+ * </dl>
+ */
+public interface ConnectionFactory
+{
+    /* ------------------------------------------------------------ */
+    /**
+     * @return A string representing the protocol name.
+     */
+    public String getProtocol();
+    
+    /**
+     * <p>Creates a new {@link Connection} with the given parameters</p>
+     * @param connector The {@link Connector} creating this connection
+     * @param endPoint the {@link EndPoint} associated with the connection
+     * @return a new {@link Connection}
+     */
+    public Connection newConnection(Connector connector, EndPoint endPoint);
+    
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Connector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Connector.java
index aa7c4d2..4588558 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/Connector.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Connector.java
@@ -18,370 +18,87 @@
 
 package org.eclipse.jetty.server;
 
-import java.io.IOException;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.Executor;
 
-import org.eclipse.jetty.io.Buffers;
+import org.eclipse.jetty.io.ByteBufferPool;
 import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.component.Graceful;
 import org.eclipse.jetty.util.component.LifeCycle;
-import org.eclipse.jetty.util.thread.ThreadPool;
+import org.eclipse.jetty.util.thread.Scheduler;
 
-/** HTTP Connector.
- * Implementations of this interface provide connectors for the HTTP protocol.
- * A connector receives requests (normally from a socket) and calls the 
- * handle method of the Handler object.  These operations are performed using
- * threads from the ThreadPool set on the connector.
- * 
- * When a connector is registered with an instance of Server, then the server
- * will set itself as both the ThreadPool and the Handler.  Note that a connector
- * can be used without a Server if a thread pool and handler are directly provided.
- * 
- * 
- * 
- */
 /**
- * @author gregw
- *
+ * <p>A {@link Connector} accept connections and data from remote peers,
+ * and allows applications to send data to remote peers, by setting up
+ * the machinery needed to handle such tasks.</p>
  */
-public interface Connector extends LifeCycle
-{ 
-    /* ------------------------------------------------------------ */
-    /**
-     * @return the name of the connector. Defaults to the HostName:port
-     */
-    String getName();
-    
-    /* ------------------------------------------------------------ */
+ at ManagedObject("Connector Interface")
+public interface Connector extends LifeCycle, Graceful
+{
     /**
-     * Opens the connector 
-     * @throws IOException
+     * @return the {@link Server} instance associated with this {@link Connector}
      */
-    void open() throws IOException;
-
-    /* ------------------------------------------------------------ */
-    void close() throws IOException;
+    public Server getServer();
 
-    /* ------------------------------------------------------------ */
-    void setServer(Server server);
-    
-    /* ------------------------------------------------------------ */
-    Server getServer();
-
-    /* ------------------------------------------------------------ */
     /**
-     * @return Returns the request header buffer size in bytes.
+     * @return the {@link Executor} used to submit tasks
      */
-    int getRequestHeaderSize();
-    
-    /* ------------------------------------------------------------ */
-    /**
-     * Set the size of the buffer to be used for request headers.
-     * @param size The size in bytes.
-     */
-    void setRequestHeaderSize(int size);
+    public Executor getExecutor();
 
-    /* ------------------------------------------------------------ */
     /**
-     * @return Returns the response header buffer size in bytes.
+     * @return the {@link Scheduler} used to schedule tasks
      */
-    int getResponseHeaderSize();
-    
-    /* ------------------------------------------------------------ */
-    /**
-     * Set the size of the buffer to be used for request headers.
-     * @param size The size in bytes.
-     */
-    void setResponseHeaderSize(int size);
-    
+    public Scheduler getScheduler();
 
-    /* ------------------------------------------------------------ */
     /**
-     * @return factory for request buffers
+     * @return the {@link ByteBufferPool} to acquire buffers from and release buffers to
      */
-    Buffers getRequestBuffers();
+    public ByteBufferPool getByteBufferPool();
 
-    /* ------------------------------------------------------------ */
-    /**
-     * @return factory for response buffers
-     */
-    Buffers getResponseBuffers();
-    
-    
-    /* ------------------------------------------------------------ */
     /**
-     * @return Returns the requestBufferSize.
+     * @return the {@link ConnectionFactory} associated with the protocol name
      */
-    int getRequestBufferSize();
+    public ConnectionFactory getConnectionFactory(String nextProtocol);
     
-    /* ------------------------------------------------------------ */
-    /**
-     * Set the size of the content buffer for receiving requests. 
-     * These buffers are only used for active connections that have
-     * requests with bodies that will not fit within the header buffer.
-     * @param requestBufferSize The requestBufferSize to set.
-     */
-    void setRequestBufferSize(int requestBufferSize);
-    
-    /* ------------------------------------------------------------ */
-    /**
-     * @return Returns the responseBufferSize.
-     */
-    int getResponseBufferSize();
-    
-    /* ------------------------------------------------------------ */
-    /**
-     * Set the size of the content buffer for sending responses. 
-     * These buffers are only used for active connections that are sending 
-     * responses with bodies that will not fit within the header buffer.
-     * @param responseBufferSize The responseBufferSize to set.
-     */
-    void setResponseBufferSize(int responseBufferSize);
-    
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return The port to use when redirecting a request if a data constraint of integral is 
-     * required. See {@link org.eclipse.jetty.util.security.Constraint#getDataConstraint()}
-     */
-    int getIntegralPort();
 
-    /* ------------------------------------------------------------ */
-    /**
-     * @return The schema to use when redirecting a request if a data constraint of integral is 
-     * required. See {@link org.eclipse.jetty.util.security.Constraint#getDataConstraint()}
-     */
-    String getIntegralScheme();
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param request A request
-     * @return true if the request is integral. This normally means the https schema has been used.
-     */
-    boolean isIntegral(Request request);
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return The port to use when redirecting a request if a data constraint of confidential is 
-     * required. See {@link org.eclipse.jetty.util.security.Constraint#getDataConstraint()}
-     */
-    int getConfidentialPort();
+    public <T> T getConnectionFactory(Class<T> factoryType);
     
-
-    /* ------------------------------------------------------------ */
     /**
-     * @return The schema to use when redirecting a request if a data constraint of confidential is 
-     * required. See {@link org.eclipse.jetty.util.security.Constraint#getDataConstraint()}
+     * @return the default {@link ConnectionFactory} associated with the default protocol name
      */
-    String getConfidentialScheme();
+    public ConnectionFactory getDefaultConnectionFactory();
     
-    /* ------------------------------------------------------------ */
-    /**
-     * @param request A request
-     * @return true if the request is confidential. This normally means the https schema has been used.
-     */
-    boolean isConfidential(Request request);
-
-    /* ------------------------------------------------------------ */
-    /** Customize a request for an endpoint.
-     * Called on every request to allow customization of the request for
-     * the particular endpoint (eg security properties from a SSL connection).
-     * @param endpoint
-     * @param request
-     * @throws IOException
-     */
-    void customize(EndPoint endpoint, Request request) throws IOException;
-
-    /* ------------------------------------------------------------ */
-    /** Persist an endpoint.
-     * Called after every request if the connection is to remain open.
-     * @param endpoint
-     * @throws IOException
-     */
-    void persist(EndPoint endpoint) throws IOException;
+    public Collection<ConnectionFactory> getConnectionFactories();
     
-    /* ------------------------------------------------------------ */
-    /**
-     * @return The hostname representing the interface to which 
-     * this connector will bind, or null for all interfaces.
-     */
-    String getHost();
+    public List<String> getProtocols();
     
-    /* ------------------------------------------------------------ */
     /**
-     * Set the hostname of the interface to bind to.
-     * @param hostname The hostname representing the interface to which 
-     * this connector will bind, or null for all interfaces.
+     * @return the max idle timeout for connections in milliseconds
      */
-    void setHost(String hostname);
+    @ManagedAttribute("maximum time a connection can be idle before being closed (in ms)")
+    public long getIdleTimeout();
 
-    /* ------------------------------------------------------------ */
-    /**
-     * @param port The port to listen of for connections or 0 if any available
-     * port may be used.
-     */
-    void setPort(int port);
-    
-    /* ------------------------------------------------------------ */
-    /**
-     * @return The configured port for the connector or 0 if any available
-     * port may be used.
-     */
-    int getPort();
-    
-    /* ------------------------------------------------------------ */
-    /**
-     * @return The actual port the connector is listening on or
-     * -1 if it has not been opened, or -2 if it has been closed.
-     */
-    int getLocalPort();
-    
-    /* ------------------------------------------------------------ */
-    /**
-     * @return Max Idle time for connections in milliseconds
-     */
-    int getMaxIdleTime();
-    
-    /**
-     * @param ms Max Idle time for connections in milliseconds
-     */
-    void setMaxIdleTime(int ms);
-    
-    /* ------------------------------------------------------------ */
-    int getLowResourceMaxIdleTime();
-    void setLowResourceMaxIdleTime(int ms);
-    
-    /* ------------------------------------------------------------ */
     /**
      * @return the underlying socket, channel, buffer etc. for the connector.
      */
-    Object getConnection();
-    
-    
-    /* ------------------------------------------------------------ */
-    /**
-     * @return true if names resolution should be done.
-     */
-    boolean getResolveNames();
-    
+    public Object getTransport();
     
-
-    /* ------------------------------------------------------------ */
     /**
-     * @return Get the number of requests handled by this connector
-     * since last call of statsReset(). If setStatsOn(false) then this
-     * is undefined.
-     */
-    public int getRequests();
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return Returns the connectionsDurationTotal.
-     */
-    public long getConnectionsDurationTotal();
-
-    /* ------------------------------------------------------------ */
-    /** 
-     * @return Number of connections accepted by the server since
-     * statsReset() called. Undefined if setStatsOn(false).
+     * @return immutable collection of connected endpoints
      */
-    public int getConnections() ;
+    public Collection<EndPoint> getConnectedEndPoints();
 
-    /* ------------------------------------------------------------ */
-    /** 
-     * @return Number of connections currently open that were opened
-     * since statsReset() called. Undefined if setStatsOn(false).
-     */
-    public int getConnectionsOpen() ;
-
-    /* ------------------------------------------------------------ */
-    /** 
-     * @return Maximum number of connections opened simultaneously
-     * since statsReset() called. Undefined if setStatsOn(false).
-     */
-    public int getConnectionsOpenMax() ;
-
-    /* ------------------------------------------------------------ */
-    /** 
-     * @return Maximum duration in milliseconds of an open connection
-     * since statsReset() called. Undefined if setStatsOn(false).
-     */
-    public long getConnectionsDurationMax();
-
-    /* ------------------------------------------------------------ */
-    /** 
-     * @return Mean duration in milliseconds of open connections
-     * since statsReset() called. Undefined if setStatsOn(false).
-     */
-    public double getConnectionsDurationMean() ;
-
-    /* ------------------------------------------------------------ */
-    /** 
-     * @return Standard deviation of duration in milliseconds of
-     * open connections since statsReset() called. Undefined if
-     * setStatsOn(false).
-     */
-    public double getConnectionsDurationStdDev() ;
-
-    /* ------------------------------------------------------------ */
-    /** 
-     * @return Mean number of requests per connection
-     * since statsReset() called. Undefined if setStatsOn(false).
-     */
-    public double getConnectionsRequestsMean() ;
-
-    /* ------------------------------------------------------------ */
-    /** 
-     * @return Standard Deviation of number of requests per connection
-     * since statsReset() called. Undefined if setStatsOn(false).
-     */
-    public double getConnectionsRequestsStdDev() ;
-
-    /* ------------------------------------------------------------ */
-    /** 
-     * @return Maximum number of requests per connection
-     * since statsReset() called. Undefined if setStatsOn(false).
-     */
-    public int getConnectionsRequestsMax();
-
-    /* ------------------------------------------------------------ */
-    /** Reset statistics.
-     */
-    public void statsReset();
-    
-    /* ------------------------------------------------------------ */
-    public void setStatsOn(boolean on);
     
     /* ------------------------------------------------------------ */
-    /** 
-     * @return True if statistics collection is turned on.
-     */
-    public boolean getStatsOn();
-    
-    /* ------------------------------------------------------------ */
-    /** 
-     * @return Timestamp stats were started at.
-     */
-    public long getStatsOnMs();
-    
-
-    /* ------------------------------------------------------------ */
-    /** Check if low on resources.
-     * For most connectors, low resources is measured by calling 
-     * {@link ThreadPool#isLowOnThreads()} on the connector threadpool
-     * or the server threadpool if there is no connector threadpool.
-     * <p>
-     * For blocking connectors, low resources is used to trigger
-     * usage of {@link #getLowResourceMaxIdleTime()} for the timeout
-     * of an idle connection.
-     * <p>
-     * for non-blocking connectors, the number of connections is
-     * used instead of this method, to select the timeout of an 
-     * idle connection.
-     * <p>
-     * For all connectors, low resources is used to trigger the 
-     * usage of {@link #getLowResourceMaxIdleTime()} for read and 
-     * write operations.
-     * 
-     * @return true if this connector is low on resources.
+    /**
+     * Get the connector name if set.
+     * <p>A {@link ContextHandler} may be configured with
+     * virtual hosts in the form "@connectorName" and will only serve
+     * requests from the named connector.
+     * @return The connector name or null.
      */
-    public boolean isLowResources();
+    public String getName();
 }
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ConnectorStatistics.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ConnectorStatistics.java
new file mode 100644
index 0000000..cff1911
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ConnectorStatistics.java
@@ -0,0 +1,308 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.annotation.ManagedOperation;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.component.Container;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.component.Dumpable;
+import org.eclipse.jetty.util.statistic.CounterStatistic;
+import org.eclipse.jetty.util.statistic.SampleStatistic;
+
+
+/* ------------------------------------------------------------ */
+/** A Connector.Listener that gathers Connector and Connections Statistics.
+ * Adding an instance of this class as with {@link AbstractConnector#addBean(Object)} 
+ * will register the listener with all connections accepted by that connector.
+ */
+ at ManagedObject("Connector Statistics")
+public class ConnectorStatistics extends AbstractLifeCycle implements Dumpable, Connection.Listener
+{
+    private final static Sample ZERO=new Sample();
+    private final AtomicLong _startMillis = new AtomicLong(-1L);
+    private final CounterStatistic _connectionStats = new CounterStatistic();
+    private final SampleStatistic _messagesIn = new SampleStatistic();
+    private final SampleStatistic _messagesOut = new SampleStatistic();
+    private final SampleStatistic _connectionDurationStats = new SampleStatistic();
+    private final ConcurrentMap<Connection, Sample> _samples = new ConcurrentHashMap<>();
+    private final AtomicInteger _closedIn = new AtomicInteger();
+    private final AtomicInteger _closedOut = new AtomicInteger();
+    private AtomicLong _nanoStamp=new AtomicLong();
+    private volatile int _messagesInPerSecond;
+    private volatile int _messagesOutPerSecond;
+
+    @Override
+    public void onOpened(Connection connection)
+    {
+        if (isStarted())
+        {
+            _connectionStats.increment();
+            _samples.put(connection,ZERO);
+        }
+    }
+
+    @Override
+    public void onClosed(Connection connection)
+    {
+        if (isStarted())
+        {
+            int msgsIn=connection.getMessagesIn();
+            int msgsOut=connection.getMessagesOut();
+            _messagesIn.set(msgsIn);
+            _messagesOut.set(msgsOut);
+            _connectionStats.decrement();
+            _connectionDurationStats.set(System.currentTimeMillis()-connection.getCreatedTimeStamp());
+
+            Sample sample=_samples.remove(connection);
+            if (sample!=null)
+            {
+                _closedIn.addAndGet(msgsIn-sample._messagesIn);
+                _closedOut.addAndGet(msgsOut-sample._messagesOut);
+            }
+        }
+    }
+
+    @ManagedAttribute("Total number of bytes received by this connector")
+    public int getBytesIn()
+    {
+        // TODO
+        return -1;
+    }
+
+    @ManagedAttribute("Total number of bytes sent by this connector")
+    public int getBytesOut()
+    {
+        // TODO
+        return -1;
+    }
+
+    @ManagedAttribute("Total number of connections seen by this connector")
+    public int getConnections()
+    {
+        return (int)_connectionStats.getTotal();
+    }
+
+    @ManagedAttribute("Connection duration maximum in ms")
+    public long getConnectionDurationMax()
+    {
+        return _connectionDurationStats.getMax();
+    }
+
+    @ManagedAttribute("Connection duration mean in ms")
+    public double getConnectionDurationMean()
+    {
+        return _connectionDurationStats.getMean();
+    }
+
+    @ManagedAttribute("Connection duration standard deviation")
+    public double getConnectionDurationStdDev()
+    {
+        return _connectionDurationStats.getStdDev();
+    }
+
+    @ManagedAttribute("Messages In for all connections")
+    public int getMessagesIn()
+    {
+        return (int)_messagesIn.getTotal();
+    }
+
+    @ManagedAttribute("Messages In per connection maximum")
+    public int getMessagesInPerConnectionMax()
+    {
+        return (int)_messagesIn.getMax();
+    }
+
+    @ManagedAttribute("Messages In per connection mean")
+    public double getMessagesInPerConnectionMean()
+    {
+        return _messagesIn.getMean();
+    }
+
+    @ManagedAttribute("Messages In per connection standard deviation")
+    public double getMessagesInPerConnectionStdDev()
+    {
+        return _messagesIn.getStdDev();
+    }
+
+    @ManagedAttribute("Connections open")
+    public int getConnectionsOpen()
+    {
+        return (int)_connectionStats.getCurrent();
+    }
+
+    @ManagedAttribute("Connections open maximum")
+    public int getConnectionsOpenMax()
+    {
+        return (int)_connectionStats.getMax();
+    }
+
+    @ManagedAttribute("Messages Out for all connections")
+    public int getMessagesOut()
+    {
+        return (int)_messagesIn.getTotal();
+    }
+
+    @ManagedAttribute("Messages In per connection maximum")
+    public int getMessagesOutPerConnectionMax()
+    {
+        return (int)_messagesIn.getMax();
+    }
+
+    @ManagedAttribute("Messages In per connection mean")
+    public double getMessagesOutPerConnectionMean()
+    {
+        return _messagesIn.getMean();
+    }
+
+    @ManagedAttribute("Messages In per connection standard deviation")
+    public double getMessagesOutPerConnectionStdDev()
+    {
+        return _messagesIn.getStdDev();
+    }
+
+    @ManagedAttribute("Connection statistics started ms since epoch")
+    public long getStartedMillis()
+    {
+        long start = _startMillis.get();
+        return start < 0 ? 0 : System.currentTimeMillis() - start;
+    }
+
+    @ManagedAttribute("Messages in per second calculated over period since last called")
+    public int getMessagesInPerSecond()
+    {
+        update();
+        return _messagesInPerSecond;
+    }
+
+    @ManagedAttribute("Messages out per second calculated over period since last called")
+    public int getMessagesOutPerSecond()
+    {
+        update();
+        return _messagesOutPerSecond;
+    }
+
+    @Override
+    public void doStart()
+    {
+        reset();
+    }
+
+    @Override
+    public void doStop()
+    {
+        _samples.clear();
+    }
+
+    @ManagedOperation("Reset the statistics")
+    public void reset()
+    {
+        _startMillis.set(System.currentTimeMillis());
+        _messagesIn.reset();
+        _messagesOut.reset();
+        _connectionStats.reset();
+        _connectionDurationStats.reset();
+        _samples.clear();
+    }
+
+    @Override
+    @ManagedOperation("dump thread state")
+    public String dump()
+    {
+        return ContainerLifeCycle.dump(this);
+    }
+
+    @Override
+    public void dump(Appendable out, String indent) throws IOException
+    {
+        ContainerLifeCycle.dumpObject(out,this);
+        ContainerLifeCycle.dump(out,indent,Arrays.asList(new String[]{"connections="+_connectionStats,"duration="+_connectionDurationStats,"in="+_messagesIn,"out="+_messagesOut}));
+    }
+    
+    public static void addToAllConnectors(Server server)
+    {
+        for (Connector connector : server.getConnectors())
+        {
+            if (connector instanceof Container)
+             ((Container)connector).addBean(new ConnectorStatistics());
+        }
+    }  
+    
+    private static final long SECOND_NANOS=TimeUnit.SECONDS.toNanos(1);
+    private synchronized void update()
+    {
+        long now=System.nanoTime();
+        long then=_nanoStamp.get();
+        long duration=now-then;
+                
+        if (duration>SECOND_NANOS/2)
+        {
+            if (_nanoStamp.compareAndSet(then,now))
+            {
+                long msgsIn=_closedIn.getAndSet(0);
+                long msgsOut=_closedOut.getAndSet(0);
+
+                for (Map.Entry<Connection, Sample> entry : _samples.entrySet())
+                {
+                    Connection connection=entry.getKey();
+                    Sample sample = entry.getValue();
+                    Sample next = new Sample(connection);
+                    if (_samples.replace(connection,sample,next))
+                    {
+                        msgsIn+=next._messagesIn-sample._messagesIn;
+                        msgsOut+=next._messagesOut-sample._messagesOut;
+                    }
+                }
+                
+                _messagesInPerSecond=(int)(msgsIn*SECOND_NANOS/duration);
+                _messagesOutPerSecond=(int)(msgsOut*SECOND_NANOS/duration);
+            }
+        }
+    }
+    
+    private static class Sample
+    {
+        Sample()
+        {
+            _messagesIn=0;
+            _messagesOut=0;
+        }
+        
+        Sample(Connection connection)
+        {
+            _messagesIn=connection.getMessagesIn();
+            _messagesOut=connection.getMessagesOut();
+        }
+        
+        final int _messagesIn;
+        final int _messagesOut;
+    }
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/CookieCutter.java b/jetty-server/src/main/java/org/eclipse/jetty/server/CookieCutter.java
index c8a1611..e75fc98 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/CookieCutter.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/CookieCutter.java
@@ -17,11 +17,12 @@
 //
 
 package org.eclipse.jetty.server;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Locale;
 
 import javax.servlet.http.Cookie;
 
-import org.eclipse.jetty.util.LazyList;
 import org.eclipse.jetty.util.QuotedStringTokenizer;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
@@ -41,10 +42,9 @@ public class CookieCutter
 {
     private static final Logger LOG = Log.getLogger(CookieCutter.class);
 
-
     private Cookie[] _cookies;
     private Cookie[] _lastCookies;
-    Object _lazyFields;
+    private final List<String> _fieldList = new ArrayList<>();
     int _fields;
     
     public CookieCutter()
@@ -56,9 +56,7 @@ public class CookieCutter
         if (_cookies!=null)
             return _cookies;
         
-        if (_lastCookies!=null &&
-            _lazyFields!=null &&
-            _fields==LazyList.size(_lazyFields))
+        if (_lastCookies!=null && _fields==_fieldList.size())
             _cookies=_lastCookies;
         else
             parseFields();
@@ -70,7 +68,7 @@ public class CookieCutter
     {
         _cookies=cookies;
         _lastCookies=null;
-        _lazyFields=null;
+        _fieldList.clear();
         _fields=0;
     }
     
@@ -88,20 +86,20 @@ public class CookieCutter
         if (f.length()==0)
             return;
             
-        if (LazyList.size(_lazyFields)>_fields)
+        if (_fieldList.size()>_fields)
         {
-            if (f.equals(LazyList.get(_lazyFields,_fields)))
+            if (f.equals(_fieldList.get(_fields)))
             {
                 _fields++;
                 return;
             }
             
-            while (LazyList.size(_lazyFields)>_fields)
-                _lazyFields=LazyList.remove(_lazyFields,_fields);
+            while (_fieldList.size()>_fields)
+                _fieldList.remove(_fields);
         }
         _cookies=null;
         _lastCookies=null;
-        _lazyFields=LazyList.add(_lazyFields,_fields++,f);
+        _fieldList.add(_fields++,f);
     }
     
     
@@ -110,19 +108,17 @@ public class CookieCutter
         _lastCookies=null;
         _cookies=null;
         
-        Object cookies = null;
+        List<Cookie> cookies = new ArrayList<>();
 
         int version = 0;
 
         // delete excess fields
-        while (LazyList.size(_lazyFields)>_fields)
-            _lazyFields=LazyList.remove(_lazyFields,_fields);
+        while (_fieldList.size()>_fields)
+            _fieldList.remove(_fields);
         
         // For each cookie field
-        for (int f=0;f<_fields;f++)
+        for (String hdr : _fieldList)
         {
-            String hdr = LazyList.get(_lazyFields,f);
-            
             // Parse the header
             String name = null;
             String value = null;
@@ -200,7 +196,6 @@ public class CookieCutter
                                 continue;
 
                             case ';':
-                            // case ',':
                                 if (tokenstart>=0)
                                     value = hdr.substring(tokenstart, tokenend+1);
                                 else
@@ -246,7 +241,6 @@ public class CookieCutter
                                 continue;
 
                             case ';':
-                            // case ',':
                                 if (tokenstart>=0)
                                 {
                                     name = hdr.substring(tokenstart, tokenend+1);
@@ -280,7 +274,6 @@ public class CookieCutter
                 // If after processing the current character we have a value and a name, then it is a cookie
                 if (value!=null && name!=null)
                 {
-                    // TODO handle unquoting during parsing!  But quoting is uncommon
                     name=QuotedStringTokenizer.unquoteOnly(name);
                     value=QuotedStringTokenizer.unquoteOnly(value);
                     
@@ -314,7 +307,7 @@ public class CookieCutter
                             cookie = new Cookie(name, value);
                             if (version > 0)
                                 cookie.setVersion(version);
-                            cookies = LazyList.add(cookies, cookie);
+                            cookies.add(cookie);
                         }
                     }
                     catch (Exception e)
@@ -328,7 +321,7 @@ public class CookieCutter
             }
         }
 
-        _cookies = (Cookie[]) LazyList.toArray(cookies,Cookie.class);
+        _cookies = (Cookie[]) cookies.toArray(new Cookie[cookies.size()]);
         _lastCookies=_cookies;
     }
     
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Dispatcher.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Dispatcher.java
index 85a13f2..6b7e205 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/Dispatcher.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Dispatcher.java
@@ -22,9 +22,6 @@ import java.io.IOException;
 import java.util.Collections;
 import java.util.Enumeration;
 import java.util.HashSet;
-import java.util.Iterator;
-import java.util.Map;
-
 import javax.servlet.DispatcherType;
 import javax.servlet.RequestDispatcher;
 import javax.servlet.ServletException;
@@ -35,15 +32,8 @@ import javax.servlet.http.HttpServletResponse;
 
 import org.eclipse.jetty.server.handler.ContextHandler;
 import org.eclipse.jetty.util.Attributes;
-import org.eclipse.jetty.util.LazyList;
 import org.eclipse.jetty.util.MultiMap;
-import org.eclipse.jetty.util.UrlEncoded;
 
-/* ------------------------------------------------------------ */
-/** Servlet RequestDispatcher.
- * 
- * 
- */
 public class Dispatcher implements RequestDispatcher
 {
     /** Dispatch include attribute names */
@@ -52,201 +42,124 @@ public class Dispatcher implements RequestDispatcher
     /** Dispatch include attribute names */
     public final static String __FORWARD_PREFIX="javax.servlet.forward.";
 
-    /** JSP attributes */
-    public final static String __JSP_FILE="org.apache.catalina.jsp_file";
-
-    /* ------------------------------------------------------------ */
     private final ContextHandler _contextHandler;
     private final String _uri;
     private final String _path;
-    private final String _dQuery;
+    private final String _query;
     private final String _named;
-    
-    /* ------------------------------------------------------------ */
-    /**
-     * @param contextHandler
-     * @param uri
-     * @param pathInContext
-     * @param query
-     */
+
     public Dispatcher(ContextHandler contextHandler, String uri, String pathInContext, String query)
     {
         _contextHandler=contextHandler;
         _uri=uri;
         _path=pathInContext;
-        _dQuery=query;
+        _query=query;
         _named=null;
     }
 
-
-    /* ------------------------------------------------------------ */
-    /** Constructor. 
-     * @param contextHandler
-     * @param name
-     */
-    public Dispatcher(ContextHandler contextHandler,String name)
-        throws IllegalStateException
+    public Dispatcher(ContextHandler contextHandler, String name) throws IllegalStateException
     {
         _contextHandler=contextHandler;
         _named=name;
         _uri=null;
         _path=null;
-        _dQuery=null;
+        _query=null;
     }
-    
-    /* ------------------------------------------------------------ */
-    /* 
-     * @see javax.servlet.RequestDispatcher#forward(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
-     */
+
+    @Override
     public void forward(ServletRequest request, ServletResponse response) throws ServletException, IOException
     {
         forward(request, response, DispatcherType.FORWARD);
     }
-    
-    /* ------------------------------------------------------------ */
-    /* 
-     * @see javax.servlet.RequestDispatcher#forward(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
-     */
+
     public void error(ServletRequest request, ServletResponse response) throws ServletException, IOException
     {
         forward(request, response, DispatcherType.ERROR);
     }
-    
-    /* ------------------------------------------------------------ */
-    /* 
-     * @see javax.servlet.RequestDispatcher#include(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
-     */
+
+    @Override
     public void include(ServletRequest request, ServletResponse response) throws ServletException, IOException
     {
-        Request baseRequest=(request instanceof Request)?((Request)request):AbstractHttpConnection.getCurrentConnection().getRequest();
-      
-        
+        Request baseRequest=(request instanceof Request)?((Request)request):HttpChannel.getCurrentHttpChannel().getRequest();
+
         if (!(request instanceof HttpServletRequest))
             request = new ServletRequestHttpWrapper(request);
         if (!(response instanceof HttpServletResponse))
             response = new ServletResponseHttpWrapper(response);
-            
-        
-        // TODO - allow stream or writer????
-        
+
         final DispatcherType old_type = baseRequest.getDispatcherType();
         final Attributes old_attr=baseRequest.getAttributes();
-        MultiMap old_params=baseRequest.getParameters();
+        final MultiMap<String> old_query_params=baseRequest.getQueryParameters();
         try
         {
             baseRequest.setDispatcherType(DispatcherType.INCLUDE);
-            baseRequest.getConnection().include();
+            baseRequest.getResponse().include();
             if (_named!=null)
+            {
                 _contextHandler.handle(_named,baseRequest, (HttpServletRequest)request, (HttpServletResponse)response);
-            else 
+            }
+            else
             {
-                String query=_dQuery;
-                
-                if (query!=null)
-                {
-                    // force parameter extraction
-                    if (old_params==null)
-                    {
-                        baseRequest.extractParameters();
-                        old_params=baseRequest.getParameters();
-                    }
-                    
-                    MultiMap parameters=new MultiMap();
-                    UrlEncoded.decodeTo(query,parameters,baseRequest.getCharacterEncoding());
-                    
-                    if (old_params!=null && old_params.size()>0)
-                    {
-                        // Merge parameters.
-                        Iterator iter = old_params.entrySet().iterator();
-                        while (iter.hasNext())
-                        {
-                            Map.Entry entry = (Map.Entry)iter.next();
-                            String name=(String)entry.getKey();
-                            Object values=entry.getValue();
-                            for (int i=0;i<LazyList.size(values);i++)
-                                parameters.add(name, LazyList.get(values, i));
-                        }
-                    }
-                    baseRequest.setParameters(parameters);
-                }
-                
-                IncludeAttributes attr = new IncludeAttributes(old_attr); 
-                
+                IncludeAttributes attr = new IncludeAttributes(old_attr);
+
                 attr._requestURI=_uri;
                 attr._contextPath=_contextHandler.getContextPath();
                 attr._servletPath=null; // set by ServletHandler
                 attr._pathInfo=_path;
-                attr._query=query;
-                
+                attr._query=_query;
+
+                if (_query!=null)
+                    baseRequest.mergeQueryParameters(_query, false);
                 baseRequest.setAttributes(attr);
-                
-                _contextHandler.handle(_path,baseRequest, (HttpServletRequest)request, (HttpServletResponse)response);
+
+                _contextHandler.handle(_path, baseRequest, (HttpServletRequest)request, (HttpServletResponse)response);
             }
         }
         finally
         {
             baseRequest.setAttributes(old_attr);
-            baseRequest.getConnection().included();
-            baseRequest.setParameters(old_params);
+            baseRequest.getResponse().included();
+            baseRequest.setQueryParameters(old_query_params);
+            baseRequest.resetParameters();
             baseRequest.setDispatcherType(old_type);
         }
     }
 
-    
-    /* ------------------------------------------------------------ */
-    /* 
-     * @see javax.servlet.RequestDispatcher#forward(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
-     */
     protected void forward(ServletRequest request, ServletResponse response, DispatcherType dispatch) throws ServletException, IOException
     {
-        Request baseRequest=(request instanceof Request)?((Request)request):AbstractHttpConnection.getCurrentConnection().getRequest();
+        Request baseRequest=(request instanceof Request)?((Request)request):HttpChannel.getCurrentHttpChannel().getRequest();
         Response base_response=baseRequest.getResponse();
-        response.resetBuffer();
-        base_response.fwdReset();
-       
+        base_response.resetForForward();
 
         if (!(request instanceof HttpServletRequest))
             request = new ServletRequestHttpWrapper(request);
         if (!(response instanceof HttpServletResponse))
             response = new ServletResponseHttpWrapper(response);
-        
+
         final boolean old_handled=baseRequest.isHandled();
         final String old_uri=baseRequest.getRequestURI();
         final String old_context_path=baseRequest.getContextPath();
         final String old_servlet_path=baseRequest.getServletPath();
         final String old_path_info=baseRequest.getPathInfo();
         final String old_query=baseRequest.getQueryString();
+        final MultiMap<String> old_query_params=baseRequest.getQueryParameters();
         final Attributes old_attr=baseRequest.getAttributes();
         final DispatcherType old_type=baseRequest.getDispatcherType();
-        MultiMap<String> old_params=baseRequest.getParameters();
-        
+
         try
         {
             baseRequest.setHandled(false);
             baseRequest.setDispatcherType(dispatch);
-            
+
             if (_named!=null)
-                _contextHandler.handle(_named,baseRequest, (HttpServletRequest)request, (HttpServletResponse)response);
-            else 
             {
-                
-                // process any query string from the dispatch URL
-                String query=_dQuery;
-                if (query!=null)
-                {
-                    // force parameter extraction
-                    if (old_params==null)
-                    {
-                        baseRequest.extractParameters();
-                        old_params=baseRequest.getParameters();
-                    }
-                    
-                    baseRequest.mergeQueryString(query);
-                }
-                
-                ForwardAttributes attr = new ForwardAttributes(old_attr); 
-                
-                //If we have already been forwarded previously, then keep using the established 
+                _contextHandler.handle(_named, baseRequest, (HttpServletRequest)request, (HttpServletResponse)response);
+            }
+            else
+            {
+                ForwardAttributes attr = new ForwardAttributes(old_attr);
+
+                //If we have already been forwarded previously, then keep using the established
                 //original value. Otherwise, this is the first forward and we need to establish the values.
                 //Note: the established value on the original request for pathInfo and
                 //for queryString is allowed to be null, but cannot be null for the other values.
@@ -265,17 +178,19 @@ public class Dispatcher implements RequestDispatcher
                     attr._requestURI=old_uri;
                     attr._contextPath=old_context_path;
                     attr._servletPath=old_servlet_path;
-                }     
-                
+                }
+
                 baseRequest.setRequestURI(_uri);
                 baseRequest.setContextPath(_contextHandler.getContextPath());
                 baseRequest.setServletPath(null);
                 baseRequest.setPathInfo(_uri);
+                if (_query!=null)
+                    baseRequest.mergeQueryParameters(_query, true);
                 baseRequest.setAttributes(attr);
-                
-                _contextHandler.handle(_path,baseRequest, (HttpServletRequest)request, (HttpServletResponse)response);
-                
-                if (!baseRequest.getAsyncContinuation().isAsyncStarted())
+
+                _contextHandler.handle(_path, baseRequest, (HttpServletRequest)request, (HttpServletResponse)response);
+
+                if (!baseRequest.getHttpChannelState().isAsync())
                     commitResponse(response,baseRequest);
             }
         }
@@ -286,15 +201,14 @@ public class Dispatcher implements RequestDispatcher
             baseRequest.setContextPath(old_context_path);
             baseRequest.setServletPath(old_servlet_path);
             baseRequest.setPathInfo(old_path_info);
-            baseRequest.setAttributes(old_attr);
-            baseRequest.setParameters(old_params);
             baseRequest.setQueryString(old_query);
+            baseRequest.setQueryParameters(old_query_params);
+            baseRequest.resetParameters();
+            baseRequest.setAttributes(old_attr);
             baseRequest.setDispatcherType(old_type);
         }
     }
 
-
-    /* ------------------------------------------------------------ */
     private void commitResponse(ServletResponse response, Request baseRequest) throws IOException
     {
         if (baseRequest.getResponse().isWriting())
@@ -321,61 +235,58 @@ public class Dispatcher implements RequestDispatcher
         }
     }
 
-
-    /* ------------------------------------------------------------ */
-    /* ------------------------------------------------------------ */
-    /* ------------------------------------------------------------ */
     private class ForwardAttributes implements Attributes
     {
         final Attributes _attr;
-        
+
         String _requestURI;
         String _contextPath;
         String _servletPath;
         String _pathInfo;
         String _query;
-        
+
         ForwardAttributes(Attributes attributes)
         {
             _attr=attributes;
         }
-        
+
         /* ------------------------------------------------------------ */
+        @Override
         public Object getAttribute(String key)
         {
             if (Dispatcher.this._named==null)
             {
-                if (key.equals(FORWARD_PATH_INFO))    
+                if (key.equals(FORWARD_PATH_INFO))
                     return _pathInfo;
-                if (key.equals(FORWARD_REQUEST_URI))  
+                if (key.equals(FORWARD_REQUEST_URI))
                     return _requestURI;
-                if (key.equals(FORWARD_SERVLET_PATH)) 
+                if (key.equals(FORWARD_SERVLET_PATH))
                     return _servletPath;
-                if (key.equals(FORWARD_CONTEXT_PATH)) 
+                if (key.equals(FORWARD_CONTEXT_PATH))
                     return _contextPath;
-                if (key.equals(FORWARD_QUERY_STRING)) 
+                if (key.equals(FORWARD_QUERY_STRING))
                     return _query;
             }
-            
+
             if (key.startsWith(__INCLUDE_PREFIX))
                 return null;
-            
+
             return _attr.getAttribute(key);
         }
-        
-        /* ------------------------------------------------------------ */
-        public Enumeration getAttributeNames()
+
+        @Override
+        public Enumeration<String> getAttributeNames()
         {
-            HashSet set=new HashSet();
-            Enumeration e=_attr.getAttributeNames();
+            HashSet<String> set=new HashSet<>();
+            Enumeration<String> e=_attr.getAttributeNames();
             while(e.hasMoreElements())
             {
-                String name=(String)e.nextElement();
+                String name=e.nextElement();
                 if (!name.startsWith(__INCLUDE_PREFIX) &&
                     !name.startsWith(__FORWARD_PREFIX))
                     set.add(name);
             }
-            
+
             if (_named==null)
             {
                 if (_pathInfo!=null)
@@ -393,73 +304,69 @@ public class Dispatcher implements RequestDispatcher
 
             return Collections.enumeration(set);
         }
-        
-        /* ------------------------------------------------------------ */
+
+        @Override
         public void setAttribute(String key, Object value)
         {
             if (_named==null && key.startsWith("javax.servlet."))
             {
-                if (key.equals(FORWARD_PATH_INFO))         
+                if (key.equals(FORWARD_PATH_INFO))
                     _pathInfo=(String)value;
-                else if (key.equals(FORWARD_REQUEST_URI))  
+                else if (key.equals(FORWARD_REQUEST_URI))
                     _requestURI=(String)value;
-                else if (key.equals(FORWARD_SERVLET_PATH)) 
+                else if (key.equals(FORWARD_SERVLET_PATH))
                     _servletPath=(String)value;
-                else if (key.equals(FORWARD_CONTEXT_PATH)) 
+                else if (key.equals(FORWARD_CONTEXT_PATH))
                     _contextPath=(String)value;
-                else if (key.equals(FORWARD_QUERY_STRING)) 
+                else if (key.equals(FORWARD_QUERY_STRING))
                     _query=(String)value;
-                
+
                 else if (value==null)
                     _attr.removeAttribute(key);
                 else
-                    _attr.setAttribute(key,value); 
+                    _attr.setAttribute(key,value);
             }
             else if (value==null)
                 _attr.removeAttribute(key);
             else
                 _attr.setAttribute(key,value);
         }
-        
-        /* ------------------------------------------------------------ */
+
         @Override
-        public String toString() 
+        public String toString()
         {
             return "FORWARD+"+_attr.toString();
         }
 
-        /* ------------------------------------------------------------ */
+        @Override
         public void clearAttributes()
         {
             throw new IllegalStateException();
         }
 
-        /* ------------------------------------------------------------ */
+        @Override
         public void removeAttribute(String name)
         {
             setAttribute(name,null);
         }
     }
 
-    /* ------------------------------------------------------------ */
     private class IncludeAttributes implements Attributes
     {
         final Attributes _attr;
-        
+
         String _requestURI;
         String _contextPath;
         String _servletPath;
         String _pathInfo;
         String _query;
-        
+
         IncludeAttributes(Attributes attributes)
         {
             _attr=attributes;
         }
-        
-        /* ------------------------------------------------------------ */
-        /* ------------------------------------------------------------ */
-        /* ------------------------------------------------------------ */
+
+        @Override
         public Object getAttribute(String key)
         {
             if (Dispatcher.this._named==null)
@@ -470,25 +377,25 @@ public class Dispatcher implements RequestDispatcher
                 if (key.equals(INCLUDE_QUERY_STRING)) return _query;
                 if (key.equals(INCLUDE_REQUEST_URI))  return _requestURI;
             }
-            else if (key.startsWith(__INCLUDE_PREFIX)) 
+            else if (key.startsWith(__INCLUDE_PREFIX))
                     return null;
-            
-            
+
+
             return _attr.getAttribute(key);
         }
-        
-        /* ------------------------------------------------------------ */
-        public Enumeration getAttributeNames()
+
+        @Override
+        public Enumeration<String> getAttributeNames()
         {
-            HashSet set=new HashSet();
-            Enumeration e=_attr.getAttributeNames();
+            HashSet<String> set=new HashSet<>();
+            Enumeration<String> e=_attr.getAttributeNames();
             while(e.hasMoreElements())
             {
-                String name=(String)e.nextElement();
+                String name=e.nextElement();
                 if (!name.startsWith(__INCLUDE_PREFIX))
                     set.add(name);
             }
-            
+
             if (_named==null)
             {
                 if (_pathInfo!=null)
@@ -503,11 +410,11 @@ public class Dispatcher implements RequestDispatcher
                 else
                     set.remove(INCLUDE_QUERY_STRING);
             }
-            
+
             return Collections.enumeration(set);
         }
-        
-        /* ------------------------------------------------------------ */
+
+        @Override
         public void setAttribute(String key, Object value)
         {
             if (_named==null && key.startsWith("javax.servlet."))
@@ -520,28 +427,27 @@ public class Dispatcher implements RequestDispatcher
                 else if (value==null)
                     _attr.removeAttribute(key);
                 else
-                    _attr.setAttribute(key,value); 
+                    _attr.setAttribute(key,value);
             }
             else if (value==null)
                 _attr.removeAttribute(key);
             else
                 _attr.setAttribute(key,value);
         }
-        
-        /* ------------------------------------------------------------ */
+
         @Override
-        public String toString() 
+        public String toString()
         {
             return "INCLUDE+"+_attr.toString();
         }
 
-        /* ------------------------------------------------------------ */
+        @Override
         public void clearAttributes()
         {
             throw new IllegalStateException();
         }
 
-        /* ------------------------------------------------------------ */
+        @Override
         public void removeAttribute(String name)
         {
             setAttribute(name,null);
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/EncodingHttpWriter.java b/jetty-server/src/main/java/org/eclipse/jetty/server/EncodingHttpWriter.java
new file mode 100644
index 0000000..8320e7d
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/EncodingHttpWriter.java
@@ -0,0 +1,70 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.UnsupportedEncodingException;
+import java.io.Writer;
+
+/**
+ *
+ */
+public class EncodingHttpWriter extends HttpWriter
+{
+    final Writer _converter;
+
+    /* ------------------------------------------------------------ */
+    public EncodingHttpWriter(HttpOutput out, String encoding)
+    {
+        super(out);
+        try
+        {
+            _converter = new OutputStreamWriter(_bytes, encoding);
+        }
+        catch (UnsupportedEncodingException e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void write (char[] s,int offset, int length) throws IOException
+    {
+        HttpOutput out = _out;
+        if (length==0 && out.isAllContentWritten())
+        {
+            out.close();
+            return;
+        }
+            
+        while (length > 0)
+        {
+            _bytes.reset();
+            int chars = length>MAX_OUTPUT_CHARS?MAX_OUTPUT_CHARS:length;
+
+            _converter.write(s, offset, chars);
+            _converter.flush();
+            _bytes.writeTo(out);
+            length-=chars;
+            offset+=chars;
+        }
+    }
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ForwardedRequestCustomizer.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ForwardedRequestCustomizer.java
new file mode 100644
index 0000000..46c46ac
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ForwardedRequestCustomizer.java
@@ -0,0 +1,289 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.net.InetSocketAddress;
+
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpScheme;
+import org.eclipse.jetty.server.HttpConfiguration.Customizer;
+
+
+/* ------------------------------------------------------------ */
+/** Customize Requests for Proxy Forwarding.
+ * <p>
+ * This customizer looks at at HTTP request for headers that indicate
+ * it has been forwarded by one or more proxies.  Specifically handled are:
+ * <ul>
+ * <li>X-Forwarded-Host</li>
+ * <li>X-Forwarded-Server</li>
+ * <li>X-Forwarded-For</li>
+ * <li>X-Forwarded-Proto</li>
+ * </ul>
+ * <p>If these headers are present, then the {@link Request} object is updated
+ * so that the proxy is not seen as the other end point of the connection on which
+ * the request came</p>
+ * <p>Headers can also be defined so that forwarded SSL Session IDs and Cipher
+ * suites may be customised</p> 
+ * @see <a href="http://en.wikipedia.org/wiki/X-Forwarded-For">Wikipedia: X-Forwarded-For</a>
+ */
+public class ForwardedRequestCustomizer implements Customizer
+{
+    private String _hostHeader;
+    private String _forwardedHostHeader = HttpHeader.X_FORWARDED_HOST.toString();
+    private String _forwardedServerHeader = HttpHeader.X_FORWARDED_SERVER.toString();
+    private String _forwardedForHeader = HttpHeader.X_FORWARDED_FOR.toString();
+    private String _forwardedProtoHeader = HttpHeader.X_FORWARDED_PROTO.toString();
+    private String _forwardedCipherSuiteHeader;
+    private String _forwardedSslSessionIdHeader;
+    
+
+    /* ------------------------------------------------------------ */
+    public String getHostHeader()
+    {
+        return _hostHeader;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set a forced valued for the host header to control what is returned by {@link ServletRequest#getServerName()} and {@link ServletRequest#getServerPort()}.
+     *
+     * @param hostHeader
+     *            The value of the host header to force.
+     */
+    public void setHostHeader(String hostHeader)
+    {
+        _hostHeader = hostHeader;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     *
+     * @see #setForwarded(boolean)
+     */
+    public String getForwardedHostHeader()
+    {
+        return _forwardedHostHeader;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param forwardedHostHeader
+     *            The header name for forwarded hosts (default x-forwarded-host)
+     */
+    public void setForwardedHostHeader(String forwardedHostHeader)
+    {
+        _forwardedHostHeader = forwardedHostHeader;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the header name for forwarded server.
+     */
+    public String getForwardedServerHeader()
+    {
+        return _forwardedServerHeader;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param forwardedServerHeader
+     *            The header name for forwarded server (default x-forwarded-server)
+     */
+    public void setForwardedServerHeader(String forwardedServerHeader)
+    {
+        _forwardedServerHeader = forwardedServerHeader;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the forwarded for header
+     */
+    public String getForwardedForHeader()
+    {
+        return _forwardedForHeader;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param forwardedRemoteAddressHeader
+     *            The header name for forwarded for (default x-forwarded-for)
+     */
+    public void setForwardedForHeader(String forwardedRemoteAddressHeader)
+    {
+        _forwardedForHeader = forwardedRemoteAddressHeader;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Get the forwardedProtoHeader.
+     *
+     * @return the forwardedProtoHeader (default X-Forwarded-For)
+     */
+    public String getForwardedProtoHeader()
+    {
+        return _forwardedProtoHeader;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the forwardedProtoHeader.
+     *
+     * @param forwardedProtoHeader
+     *            the forwardedProtoHeader to set (default X-Forwarded-For)
+     */
+    public void setForwardedProtoHeader(String forwardedProtoHeader)
+    {
+        _forwardedProtoHeader = forwardedProtoHeader;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The header name holding a forwarded cipher suite (default null)
+     */
+    public String getForwardedCipherSuiteHeader()
+    {
+        return _forwardedCipherSuiteHeader;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param forwardedCipherSuite
+     *            The header name holding a forwarded cipher suite (default null)
+     */
+    public void setForwardedCipherSuiteHeader(String forwardedCipherSuite)
+    {
+        _forwardedCipherSuiteHeader = forwardedCipherSuite;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The header name holding a forwarded SSL Session ID (default null)
+     */
+    public String getForwardedSslSessionIdHeader()
+    {
+        return _forwardedSslSessionIdHeader;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param forwardedSslSessionId
+     *            The header name holding a forwarded SSL Session ID (default null)
+     */
+    public void setForwardedSslSessionIdHeader(String forwardedSslSessionId)
+    {
+        _forwardedSslSessionIdHeader = forwardedSslSessionId;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void customize(Connector connector, HttpConfiguration config, Request request)
+    {
+        HttpFields httpFields = request.getHttpFields();
+
+        // Do SSL first
+        if (getForwardedCipherSuiteHeader()!=null)
+        {
+            String cipher_suite=httpFields.getStringField(getForwardedCipherSuiteHeader());
+            if (cipher_suite!=null)
+                request.setAttribute("javax.servlet.request.cipher_suite",cipher_suite);
+        }
+        if (getForwardedSslSessionIdHeader()!=null)
+        {
+            String ssl_session_id=httpFields.getStringField(getForwardedSslSessionIdHeader());
+            if(ssl_session_id!=null)
+            {
+                request.setAttribute("javax.servlet.request.ssl_session_id", ssl_session_id);
+                request.setScheme(HttpScheme.HTTPS.asString());
+            }
+        }
+
+        // Retrieving headers from the request
+        String forwardedHost = getLeftMostFieldValue(httpFields,getForwardedHostHeader());
+        String forwardedServer = getLeftMostFieldValue(httpFields,getForwardedServerHeader());
+        String forwardedFor = getLeftMostFieldValue(httpFields,getForwardedForHeader());
+        String forwardedProto = getLeftMostFieldValue(httpFields,getForwardedProtoHeader());
+
+        if (_hostHeader != null)
+        {
+            // Update host header
+            httpFields.put(HttpHeader.HOST.toString(),_hostHeader);
+            request.setServerName(null);
+            request.setServerPort(-1);
+            request.getServerName();
+        }
+        else if (forwardedHost != null)
+        {
+            // Update host header
+            httpFields.put(HttpHeader.HOST.toString(),forwardedHost);
+            request.setServerName(null);
+            request.setServerPort(-1);
+            request.getServerName();
+        }
+        else if (forwardedServer != null)
+        {
+            // Use provided server name
+            request.setServerName(forwardedServer);
+        }
+
+        if (forwardedFor != null)
+        {
+            request.setRemoteAddr(InetSocketAddress.createUnresolved(forwardedFor,request.getRemotePort()));
+        }
+
+        if (forwardedProto != null)
+        {
+            request.setScheme(forwardedProto);
+            if (forwardedProto.equals(config.getSecureScheme()))
+                request.setSecure(true);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    protected String getLeftMostFieldValue(HttpFields fields, String header)
+    {
+        if (header == null)
+            return null;
+
+        String headerValue = fields.getStringField(header);
+
+        if (headerValue == null)
+            return null;
+
+        int commaIndex = headerValue.indexOf(',');
+
+        if (commaIndex == -1)
+        {
+            // Single value
+            return headerValue;
+        }
+
+        // The left-most value is the farthest downstream client
+        return headerValue.substring(0,commaIndex);
+    }
+
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        return String.format("%s@%x",this.getClass().getSimpleName(),hashCode());
+    }
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Handler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Handler.java
index 9d5bcc9..5db8e2b 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/Handler.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Handler.java
@@ -24,27 +24,29 @@ import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import org.eclipse.jetty.server.handler.HandlerCollection;
-import org.eclipse.jetty.server.handler.HandlerWrapper;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.annotation.ManagedOperation;
 import org.eclipse.jetty.util.component.Destroyable;
 import org.eclipse.jetty.util.component.LifeCycle;
 
 /* ------------------------------------------------------------ */
 /** A Jetty Server Handler.
- * 
+ *
  * A Handler instance is required by a {@link Server} to handle incoming
  * HTTP requests.  A Handler may: <ul>
  * <li>Completely generate the HTTP Response</li>
  * <li>Examine/modify the request and call another Handler (see {@link HandlerWrapper}).
  * <li>Pass the request to one or more other Handlers (see {@link HandlerCollection}).
  * </ul>
- * 
- * Handlers are passed the servlet API request and response object, but are 
- * not Servlets.  The servlet container is implemented by handlers for 
- * context, security, session and servlet that modify the request object 
+ *
+ * Handlers are passed the servlet API request and response object, but are
+ * not Servlets.  The servlet container is implemented by handlers for
+ * context, security, session and servlet that modify the request object
  * before passing it to the next stage of handling.
- * 
+ *
  */
+ at ManagedObject("Jetty Handler")
 public interface Handler extends LifeCycle, Destroyable
 {
     /* ------------------------------------------------------------ */
@@ -52,21 +54,24 @@ public interface Handler extends LifeCycle, Destroyable
      * @param target The target of the request - either a URI or a name.
      * @param baseRequest The original unwrapped request object.
      * @param request The request either as the {@link Request}
-     * object or a wrapper of that request. The {@link AbstractHttpConnection#getCurrentConnection()} 
+     * object or a wrapper of that request. The {@link HttpChannel#getCurrentHttpChannel()}
      * method can be used access the Request object if required.
      * @param response The response as the {@link Response}
-     * object or a wrapper of that request. The {@link AbstractHttpConnection#getCurrentConnection()} 
+     * object or a wrapper of that request. The {@link HttpChannel#getCurrentHttpChannel()}
      * method can be used access the Response object if required.
      * @throws IOException
      * @throws ServletException
      */
     public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
         throws IOException, ServletException;
-    
+
     public void setServer(Server server);
+
+    @ManagedAttribute(value="the jetty server for this handler", readonly=true)
     public Server getServer();
-    
+
+    @ManagedOperation(value="destroy associated resources", impact="ACTION")
     public void destroy();
-    
+
 }
 
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HandlerContainer.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HandlerContainer.java
index 3797a07..dfe4c71 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/HandlerContainer.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HandlerContainer.java
@@ -18,6 +18,8 @@
 
 package org.eclipse.jetty.server;
 
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
 import org.eclipse.jetty.util.component.LifeCycle;
 
 /**
@@ -27,18 +29,21 @@ import org.eclipse.jetty.util.component.LifeCycle;
  * or many (see {@link org.eclipse.jetty.server.handler.HandlerList} or {@link org.eclipse.jetty.server.handler.HandlerCollection}. 
  *
  */
+ at ManagedObject("Handler of Multiple Handlers")
 public interface HandlerContainer extends LifeCycle
 {
     /* ------------------------------------------------------------ */
     /**
      * @return array of handlers directly contained by this handler.
      */
+    @ManagedAttribute("handlers in this container")
     public Handler[] getHandlers();
     
     /* ------------------------------------------------------------ */
     /**
      * @return array of all handlers contained by this handler and it's children
      */
+    @ManagedAttribute("all contained handlers")
     public Handler[] getChildHandlers();
     
     /* ------------------------------------------------------------ */
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HomeBaseWarning.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HomeBaseWarning.java
new file mode 100644
index 0000000..1aa4144
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HomeBaseWarning.java
@@ -0,0 +1,75 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * Display an optional Warning Message if the {jetty.home} and {jetty.base} are the same directory.
+ * <p>
+ * This is to warn about not recommended approach to setting up the Jetty Distribution.
+ */
+public class HomeBaseWarning
+{
+    private static final Logger LOG = Log.getLogger(HomeBaseWarning.class);
+
+    public HomeBaseWarning()
+    {
+        boolean showWarn = false;
+        
+        String home = System.getProperty("jetty.home");
+        String base = System.getProperty("jetty.base");
+
+        if (StringUtil.isBlank(base))
+        {
+            // no base defined? then we are likely running
+            // via direct command line.
+            return;
+        }
+
+        Path homePath = new File(home).toPath();
+        Path basePath = new File(base).toPath();
+
+        try
+        {
+            showWarn = Files.isSameFile(homePath,basePath);
+        }
+        catch (IOException e)
+        {
+            LOG.ignore(e);
+            // Can't definitively determine this state
+            return;
+        }
+
+        if (showWarn)
+        {
+            StringBuilder warn = new StringBuilder();
+            warn.append("This instance of Jetty is not running from a separate {jetty.base} directory");
+            warn.append(", this is not recommended.  See documentation at http://www.eclipse.org/jetty/documentation/current/startup.html");
+            LOG.warn("{}",warn.toString());
+        }
+    }
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HostHeaderCustomizer.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HostHeaderCustomizer.java
new file mode 100644
index 0000000..f4f5e7d
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HostHeaderCustomizer.java
@@ -0,0 +1,71 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.util.Objects;
+
+/**
+ * Customizes requests that lack the {@code Host} header (for example, HTTP 1.0 requests).
+ * <p />
+ * In case of HTTP 1.0 requests that lack the {@code Host} header, the application may issue
+ * a redirect, and the {@code Location} header is usually constructed from the {@code Host}
+ * header; if the {@code Host} header is missing, the server may query the connector for its
+ * IP address in order to construct the {@code Location} header, and thus leak to clients
+ * internal IP addresses.
+ * <p />
+ * This {@link HttpConfiguration.Customizer} is configured with a {@code serverName} and
+ * optionally a {@code serverPort}.
+ * If the {@code Host} header is absent, the configured {@code serverName} will be set on
+ * the request so that {@link HttpServletRequest#getServerName()} will return that value,
+ * and likewise for {@code serverPort} and {@link HttpServletRequest#getServerPort()}.
+ */
+public class HostHeaderCustomizer implements HttpConfiguration.Customizer
+{
+    private final String serverName;
+    private final int serverPort;
+
+    /**
+     * @param serverName the {@code serverName} to set on the request (the {@code serverPort} will not be set)
+     */
+    public HostHeaderCustomizer(String serverName)
+    {
+        this(serverName, 0);
+    }
+
+    /**
+     * @param serverName the {@code serverName} to set on the request
+     * @param serverPort the {@code serverPort} to set on the request
+     */
+    public HostHeaderCustomizer(String serverName, int serverPort)
+    {
+        this.serverName = Objects.requireNonNull(serverName);
+        this.serverPort = serverPort;
+    }
+
+    @Override
+    public void customize(Connector connector, HttpConfiguration channelConfig, Request request)
+    {
+        if (request.getHeader("Host") == null)
+        {
+            request.setServerName(serverName);
+            if (serverPort > 0)
+                request.setServerPort(serverPort);
+        }
+    }
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java
new file mode 100644
index 0000000..f49907e
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java
@@ -0,0 +1,898 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.channels.ClosedChannelException;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import javax.servlet.DispatcherType;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.http.HttpServletRequest;
+
+import org.eclipse.jetty.http.HttpField;
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpGenerator;
+import org.eclipse.jetty.http.HttpGenerator.ResponseInfo;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpHeaderValue;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpParser;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.HttpURI;
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.ChannelEndPoint;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.EofException;
+import org.eclipse.jetty.server.HttpChannelState.Action;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.ErrorHandler;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.SharedBlockingCallback.Blocker;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+
+/* ------------------------------------------------------------ */
+/** HttpChannel.
+ * Represents a single endpoint for HTTP semantic processing.
+ * The HttpChannel is both a HttpParser.RequestHandler, where it passively receives events from
+ * an incoming HTTP request, and a Runnable, where it actively takes control of the request/response
+ * life cycle and calls the application (perhaps suspending and resuming with multiple calls to run).
+ * The HttpChannel signals the switch from passive mode to active mode by returning true to one of the
+ * HttpParser.RequestHandler callbacks.   The completion of the active phase is signalled by a call to
+ * HttpTransport.completed().
+ *
+ */
+public class HttpChannel<T> implements HttpParser.RequestHandler<T>, Runnable, HttpParser.ProxyHandler
+{
+    private static final Logger LOG = Log.getLogger(HttpChannel.class);
+    private static final ThreadLocal<HttpChannel<?>> __currentChannel = new ThreadLocal<>();
+
+    /* ------------------------------------------------------------ */
+    /** Get the current channel that this thread is dispatched to.
+     * @see Request#getAttribute(String) for a more general way to access the HttpChannel
+     * @return the current HttpChannel or null
+     */
+    public static HttpChannel<?> getCurrentHttpChannel()
+    {
+        return __currentChannel.get();
+    }
+
+    protected static HttpChannel<?> setCurrentHttpChannel(HttpChannel<?> channel)
+    {
+        HttpChannel<?> last=__currentChannel.get();
+        __currentChannel.set(channel);
+        return last;
+    }
+
+    private final AtomicBoolean _committed = new AtomicBoolean();
+    private final AtomicInteger _requests = new AtomicInteger();
+    private final Connector _connector;
+    private final HttpConfiguration _configuration;
+    private final EndPoint _endPoint;
+    private final HttpTransport _transport;
+    private final HttpURI _uri;
+    private final HttpChannelState _state;
+    private final Request _request;
+    private final Response _response;
+    private HttpVersion _version = HttpVersion.HTTP_1_1;
+    private boolean _expect = false;
+    private boolean _expect100Continue = false;
+    private boolean _expect102Processing = false;
+
+    public HttpChannel(Connector connector, HttpConfiguration configuration, EndPoint endPoint, HttpTransport transport, HttpInput<T> input)
+    {
+        _connector = connector;
+        _configuration = configuration;
+        _endPoint = endPoint;
+        _transport = transport;
+
+        _uri = new HttpURI(URIUtil.__CHARSET);
+        _state = new HttpChannelState(this);
+        input.init(_state);
+        _request = new Request(this, input);
+        _response = new Response(this, new HttpOutput(this));
+        
+        if (LOG.isDebugEnabled())
+            LOG.debug("new {} -> {},{},{}",this,_endPoint,_endPoint.getConnection(),_state);
+    }
+
+    public HttpChannelState getState()
+    {
+        return _state;
+    }
+
+    public HttpVersion getHttpVersion()
+    {
+        return _version;
+    }
+    /**
+     * @return the number of requests handled by this connection
+     */
+    public int getRequests()
+    {
+        return _requests.get();
+    }
+
+    public Connector getConnector()
+    {
+        return _connector;
+    }
+
+    public HttpTransport getHttpTransport()
+    {
+        return _transport;
+    }
+
+    /**
+     * Get the idle timeout.
+     * <p>This is implemented as a call to {@link EndPoint#getIdleTimeout()}, but may be
+     * overridden by channels that have timeouts different from their connections.
+     */
+    public long getIdleTimeout()
+    {
+        return _endPoint.getIdleTimeout();
+    }
+
+    /**
+     * Set the idle timeout.
+     * <p>This is implemented as a call to {@link EndPoint#setIdleTimeout(long), but may be
+     * overridden by channels that have timeouts different from their connections.
+     */
+    public void setIdleTimeout(long timeoutMs)
+    {
+        _endPoint.setIdleTimeout(timeoutMs);
+    }
+    
+    public ByteBufferPool getByteBufferPool()
+    {
+        return _connector.getByteBufferPool();
+    }
+
+    public HttpConfiguration getHttpConfiguration()
+    {
+        return _configuration;
+    }
+
+    public Server getServer()
+    {
+        return _connector.getServer();
+    }
+
+    public Request getRequest()
+    {
+        return _request;
+    }
+
+    public Response getResponse()
+    {
+        return _response;
+    }
+
+    public EndPoint getEndPoint()
+    {
+        return _endPoint;
+    }
+
+    public InetSocketAddress getLocalAddress()
+    {
+        return _endPoint.getLocalAddress();
+    }
+
+    public InetSocketAddress getRemoteAddress()
+    {
+        return _endPoint.getRemoteAddress();
+    }
+
+    @Override
+    public int getHeaderCacheSize()
+    {
+        return _configuration.getHeaderCacheSize();
+    }
+
+    /**
+     * If the associated response has the Expect header set to 100 Continue,
+     * then accessing the input stream indicates that the handler/servlet
+     * is ready for the request body and thus a 100 Continue response is sent.
+     *
+     * @throws IOException if the InputStream cannot be created
+     */
+    public void continue100(int available) throws IOException
+    {
+        // If the client is expecting 100 CONTINUE, then send it now.
+        // TODO: consider using an AtomicBoolean ?
+        if (isExpecting100Continue())
+        {
+            _expect100Continue = false;
+
+            // is content missing?
+            if (available == 0)
+            {
+                if (_response.isCommitted())
+                    throw new IOException("Committed before 100 Continues");
+
+                // TODO: break this dependency with HttpGenerator
+                boolean committed = sendResponse(HttpGenerator.CONTINUE_100_INFO, null, false);
+                if (!committed)
+                    throw new IOException("Concurrent commit while trying to send 100-Continue");
+            }
+        }
+    }
+
+    public void reset()
+    {
+        _committed.set(false);
+        _expect = false;
+        _expect100Continue = false;
+        _expect102Processing = false;
+        _request.recycle();
+        _response.recycle();
+        _uri.clear();
+    }
+
+    @Override
+    public void run()
+    {
+        handle();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return True if the channel is ready to continue handling (ie it is not suspended)
+     */
+    public boolean handle()
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("{} handle enter", this);
+
+        final HttpChannel<?>last = setCurrentHttpChannel(this);
+
+        String threadName = null;
+        if (LOG.isDebugEnabled())
+        {
+            threadName = Thread.currentThread().getName();
+            Thread.currentThread().setName(threadName + " - " + _uri);
+        }
+
+        HttpChannelState.Action action = _state.handling();
+        try
+        {
+            // Loop here to handle async request redispatches.
+            // The loop is controlled by the call to async.unhandle in the
+            // finally block below.  Unhandle will return false only if an async dispatch has
+            // already happened when unhandle is called.
+            loop: while (action.ordinal()<HttpChannelState.Action.WAIT.ordinal() && getServer().isRunning())
+            {
+                boolean error=false;
+                try
+                {
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("{} action {}",this,action);
+
+                    switch(action)
+                    {
+                        case REQUEST_DISPATCH:
+                            _request.setHandled(false);
+                            _response.getHttpOutput().reopen();
+                            _request.setDispatcherType(DispatcherType.REQUEST);
+
+                            List<HttpConfiguration.Customizer> customizers = _configuration.getCustomizers();
+                            if (!customizers.isEmpty())
+                            {
+                                for (HttpConfiguration.Customizer customizer : customizers)
+                                    customizer.customize(getConnector(), _configuration, _request);
+                            }
+                            getServer().handle(this);
+                            break;
+
+                        case ASYNC_DISPATCH:
+                            _request.setHandled(false);
+                            _response.getHttpOutput().reopen();
+                            _request.setDispatcherType(DispatcherType.ASYNC);
+                            getServer().handleAsync(this);
+                            break;
+
+                        case ASYNC_EXPIRED:
+                            _request.setHandled(false);
+                            _response.getHttpOutput().reopen();
+                            _request.setDispatcherType(DispatcherType.ERROR);
+
+                            Throwable ex=_state.getAsyncContextEvent().getThrowable();
+                            String reason="Async Timeout";
+                            if (ex!=null)
+                            {
+                                reason="Async Exception";
+                                _request.setAttribute(RequestDispatcher.ERROR_EXCEPTION,ex);
+                            }
+                            _request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE,new Integer(500));
+                            _request.setAttribute(RequestDispatcher.ERROR_MESSAGE,reason);
+                            _request.setAttribute(RequestDispatcher.ERROR_REQUEST_URI,_request.getRequestURI());
+
+                            _response.setStatusWithReason(500,reason);
+
+                            
+                            ErrorHandler eh = ErrorHandler.getErrorHandler(getServer(),_state.getContextHandler());                                
+                            if (eh instanceof ErrorHandler.ErrorPageMapper)
+                            {
+                                String error_page=((ErrorHandler.ErrorPageMapper)eh).getErrorPage((HttpServletRequest)_state.getAsyncContextEvent().getSuppliedRequest());
+                                if (error_page!=null)
+                                    _state.getAsyncContextEvent().setDispatchPath(error_page);
+                            }
+
+                            getServer().handleAsync(this);
+                            break;
+
+                        case READ_CALLBACK:
+                        {
+                            ContextHandler handler=_state.getContextHandler();
+                            if (handler!=null)
+                                handler.handle(_request.getHttpInput());
+                            else
+                                _request.getHttpInput().run();
+                            break;
+                        }
+
+                        case WRITE_CALLBACK:
+                        {
+                            ContextHandler handler=_state.getContextHandler();
+
+                            if (handler!=null)
+                                handler.handle(_response.getHttpOutput());
+                            else
+                                _response.getHttpOutput().run();
+                            break;
+                        }   
+
+                        default:
+                            break loop;
+
+                    }
+                }
+                catch (Error e)
+                {
+                    if ("ContinuationThrowable".equals(e.getClass().getSimpleName()))
+                        LOG.ignore(e);
+                    else
+                    {
+                        error=true;
+                        LOG.warn(String.valueOf(_uri), e);
+                        _state.error(e);
+                        _request.setHandled(true);
+                        handleException(e);
+                    }
+                }
+                catch (Exception e)
+                {
+                    error=true;
+                    if (e instanceof EofException)
+                        LOG.debug(e);
+                    else
+                        LOG.warn(String.valueOf(_uri), e);
+                    _state.error(e);
+                    _request.setHandled(true);
+                    handleException(e);
+                }
+                finally
+                {
+                    if (error && _state.isAsyncStarted())
+                        _state.errorComplete();
+                    action = _state.unhandle();
+                }
+            }
+
+            if (action==Action.COMPLETE)
+            {
+                try
+                {
+                    _state.completed();
+
+                    if (!_response.isCommitted() && !_request.isHandled())
+                    {
+                        _response.sendError(404);
+                    }
+                    else
+                    {
+                        // Complete generating the response
+                        _response.closeOutput();
+                    }
+                }
+                catch(EofException|ClosedChannelException e)
+                {
+                    LOG.debug(e);
+                }
+                catch(Exception e)
+                {
+                    LOG.warn("complete failed",e);
+                }
+                finally
+                {
+                    _request.setHandled(true);
+                    _transport.completed();
+                }
+            }
+        }
+        finally
+        {
+            setCurrentHttpChannel(last);
+            if (threadName != null && LOG.isDebugEnabled())
+                Thread.currentThread().setName(threadName);
+        }
+
+        if (LOG.isDebugEnabled())
+            LOG.debug("{} handle exit, result {}", this, action);
+
+        return action!=Action.WAIT;
+    }
+
+    /**
+     * <p>Sends an error 500, performing a special logic to detect whether the request is suspended,
+     * to avoid concurrent writes from the application.</p>
+     * <p>It may happen that the application suspends, and then throws an exception, while an application
+     * spawned thread writes the response content; in such case, we attempt to commit the error directly
+     * bypassing the {@link ErrorHandler} mechanisms and the response OutputStream.</p>
+     *
+     * @param x the Throwable that caused the problem
+     */
+    protected void handleException(Throwable x)
+    {
+        try
+        {
+            _request.setAttribute(RequestDispatcher.ERROR_EXCEPTION,x);
+            _request.setAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE,x.getClass());
+            if (_state.isSuspended())
+            {
+                HttpFields fields = new HttpFields();
+                fields.add(HttpHeader.CONNECTION,HttpHeaderValue.CLOSE);
+                ResponseInfo info = new ResponseInfo(_request.getHttpVersion(), fields, 0, HttpStatus.INTERNAL_SERVER_ERROR_500, null, _request.isHead());
+                boolean committed = sendResponse(info, null, true);
+                if (!committed)
+                    LOG.warn("Could not send response error 500: "+x);
+                _request.getAsyncContext().complete();
+            }
+            else if (isCommitted())
+            {
+                abort();
+                if (!(x instanceof EofException))
+                    LOG.warn("Could not send response error 500: "+x);
+            }
+            else
+            {
+                _response.setHeader(HttpHeader.CONNECTION.asString(),HttpHeaderValue.CLOSE.asString());
+                _response.sendError(500, x.getMessage());
+            }
+        }
+        catch (IOException e)
+        {
+            // We tried our best, just log
+            LOG.debug("Could not commit response error 500", e);
+        }
+    }
+
+    public boolean isExpecting100Continue()
+    {
+        return _expect100Continue;
+    }
+
+    public boolean isExpecting102Processing()
+    {
+        return _expect102Processing;
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s@%x{r=%s,c=%b,a=%s,uri=%s}",
+                getClass().getSimpleName(),
+                hashCode(),
+                _requests,
+                _committed.get(),
+                _state.getState(),
+                _state.getState()==HttpChannelState.State.IDLE?"-":_request.getRequestURI()
+            );
+    }
+
+    @Override
+    public void proxied(String protocol, String sAddr, String dAddr, int sPort, int dPort)
+    {
+        _request.setAttribute("PROXY", protocol);
+        _request.setServerName(sAddr);
+        _request.setServerPort(dPort);
+        _request.setRemoteAddr(InetSocketAddress.createUnresolved(sAddr,sPort));
+    }
+    
+    @Override
+    public boolean startRequest(HttpMethod httpMethod, String method, ByteBuffer uri, HttpVersion version)
+    {
+        _expect = false;
+        _expect100Continue = false;
+        _expect102Processing = false;
+
+        _request.setTimeStamp(System.currentTimeMillis());
+        _request.setMethod(httpMethod, method);
+
+        if (httpMethod == HttpMethod.CONNECT)
+            _uri.parseConnect(uri.array(),uri.arrayOffset()+uri.position(),uri.remaining());
+        else
+            _uri.parse(uri.array(),uri.arrayOffset()+uri.position(),uri.remaining());
+        _request.setUri(_uri);
+
+        String path;
+        try
+        {
+            path = _uri.getDecodedPath();
+        }
+        catch (Exception e)
+        {
+            LOG.warn("Failed UTF-8 decode for request path, trying ISO-8859-1");
+            LOG.ignore(e);
+            path = _uri.getDecodedPath(StandardCharsets.ISO_8859_1);
+        }
+        
+        String info = URIUtil.canonicalPath(path);
+
+        if (info == null)
+        {
+            if( path==null && _uri.getScheme()!=null &&_uri.getHost()!=null)
+            {
+                info = "/";
+                _request.setRequestURI("");
+            }
+            else
+            {
+                badMessage(400,null);
+                return true;
+            }
+        }
+        _request.setPathInfo(info);
+        _version = version == null ? HttpVersion.HTTP_0_9 : version;
+        _request.setHttpVersion(_version);
+
+        return false;
+    }
+
+    @Override
+    public boolean parsedHeader(HttpField field)
+    {
+        HttpHeader header=field.getHeader();
+        String value=field.getValue();
+        if (value == null)
+            value = "";
+        if (header != null)
+        {
+            switch (header)
+            {
+                case EXPECT:
+                    if (_version.getVersion()>=HttpVersion.HTTP_1_1.getVersion())
+                    {
+                        HttpHeaderValue expect = HttpHeaderValue.CACHE.get(value);
+                        switch (expect == null ? HttpHeaderValue.UNKNOWN : expect)
+                        {
+                            case CONTINUE:
+                                _expect100Continue = true;
+                                break;
+
+                            case PROCESSING:
+                                _expect102Processing = true;
+                                break;
+
+                            default:
+                                String[] values = value.split(",");
+                                for (int i = 0; i < values.length; i++)
+                                {
+                                    expect = HttpHeaderValue.CACHE.get(values[i].trim());
+                                    if (expect == null)
+                                        _expect = true;
+                                    else
+                                    {
+                                        switch (expect)
+                                        {
+                                            case CONTINUE:
+                                                _expect100Continue = true;
+                                                break;
+                                            case PROCESSING:
+                                                _expect102Processing = true;
+                                                break;
+                                            default:
+                                                _expect = true;
+                                        }
+                                    }
+                                }
+                        }
+                    }
+                    break;
+
+                case CONTENT_TYPE:
+                    MimeTypes.Type mime = MimeTypes.CACHE.get(value);
+                    String charset = (mime == null || mime.getCharset() == null) ? MimeTypes.getCharsetFromContentType(value) : mime.getCharset().toString();
+                    if (charset != null)
+                        _request.setCharacterEncodingUnchecked(charset);
+                    break;
+                default:
+            }
+        }
+
+        if (field.getName()!=null)
+            _request.getHttpFields().add(field);
+        return false;
+    }
+
+    @Override
+    public boolean parsedHostHeader(String host, int port)
+    {
+        if (_uri.getHost()==null)
+        {
+            _request.setServerName(host);
+            _request.setServerPort(port);
+        }
+        return false;
+    }
+
+    @Override
+    public boolean headerComplete()
+    {
+        _requests.incrementAndGet();
+        HttpFields fields = _response.getHttpFields();
+        switch (_version)
+        {
+            case HTTP_0_9:
+                break;
+
+            case HTTP_1_0:
+                if (_configuration.getSendDateHeader() && !fields.contains(HttpHeader.DATE))
+                    _response.getHttpFields().add(_connector.getServer().getDateField());
+                break;
+
+            case HTTP_1_1:
+                if (_configuration.getSendDateHeader() && !fields.contains(HttpHeader.DATE))
+                    _response.getHttpFields().add(_connector.getServer().getDateField());
+
+                if (_expect)
+                {
+                    badMessage(HttpStatus.EXPECTATION_FAILED_417,null);
+                    return true;
+                }
+
+                break;
+
+            default:
+                throw new IllegalStateException();
+        }
+
+        return true;
+    }
+
+    @Override
+    public boolean content(T item)
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("{} content {}", this, item);
+        @SuppressWarnings("unchecked")
+        HttpInput<T> input = (HttpInput<T>)_request.getHttpInput();
+        input.content(item);
+
+        return false;
+    }
+
+    @Override
+    public boolean messageComplete()
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("{} messageComplete", this);
+        _request.getHttpInput().messageComplete();
+        return true;
+    }
+
+    @Override
+    public void earlyEOF()
+    {
+        _request.getHttpInput().earlyEOF();
+    }
+
+    @Override
+    public void badMessage(int status, String reason)
+    {
+        if (status < 400 || status > 599)
+            status = HttpStatus.BAD_REQUEST_400;
+
+        try
+        {
+            if (_state.handling()==Action.REQUEST_DISPATCH)
+            {
+                ByteBuffer content=null;
+                HttpFields fields=new HttpFields();
+
+                ErrorHandler handler=getServer().getBean(ErrorHandler.class);
+                if (handler!=null)
+                    content=handler.badMessageError(status,reason,fields);
+
+                sendResponse(new ResponseInfo(HttpVersion.HTTP_1_1,fields,0,status,reason,false),content ,true);
+            }
+        }
+        catch (IOException e)
+        {
+            LOG.debug(e);
+        }
+        finally
+        {
+            if (_state.unhandle()==Action.COMPLETE)
+                _state.completed();
+            else 
+                throw new IllegalStateException();
+        }
+    }
+
+    protected boolean sendResponse(ResponseInfo info, ByteBuffer content, boolean complete, final Callback callback)
+    {
+        boolean committing = _committed.compareAndSet(false, true);
+        if (committing)
+        {
+            // We need an info to commit
+            if (info==null)
+                info = _response.newResponseInfo();
+
+            // wrap callback to process 100 responses
+            final int status=info.getStatus();
+            final Callback committed = (status<200&&status>=100)?new Commit100Callback(callback):new CommitCallback(callback);
+
+            // committing write
+            _transport.send(info, content, complete, committed);
+        }
+        else if (info==null)
+        {
+            // This is a normal write
+            _transport.send(content, complete, callback);
+        }
+        else
+        {
+            callback.failed(new IllegalStateException("committed"));
+        }
+        return committing;
+    }
+
+    protected boolean sendResponse(ResponseInfo info, ByteBuffer content, boolean complete) throws IOException
+    {
+        try(Blocker blocker = _response.getHttpOutput().acquireWriteBlockingCallback())
+        {
+            boolean committing = sendResponse(info,content,complete,blocker);
+            blocker.block();
+            return committing;
+        }
+    }
+
+    public boolean isCommitted()
+    {
+        return _committed.get();
+    }
+
+    /**
+     * <p>Non-Blocking write, committing the response if needed.</p>
+     *
+     * @param content  the content buffer to write
+     * @param complete whether the content is complete for the response
+     * @param callback Callback when complete or failed
+     */
+    protected void write(ByteBuffer content, boolean complete, Callback callback)
+    {
+        sendResponse(null,content,complete,callback);
+    }
+
+    protected void execute(Runnable task)
+    {
+        _connector.getExecutor().execute(task);
+    }
+
+    public Scheduler getScheduler()
+    {
+        return _connector.getScheduler();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return true if the HttpChannel can efficiently use direct buffer (typically this means it is not over SSL or a multiplexed protocol)
+     */
+    public boolean useDirectBuffers()
+    {
+        return getEndPoint() instanceof ChannelEndPoint;
+    }
+
+    /**
+     * If a write or similar to this channel fails this method should be called. The standard implementation
+     * is to call {@link HttpTransport#abort()}
+     */
+    public void abort()
+    {
+        _transport.abort();
+    }
+
+    private class CommitCallback implements Callback
+    {
+        private final Callback _callback;
+
+        private CommitCallback(Callback callback)
+        {
+            _callback = callback;
+        }
+
+        @Override
+        public void succeeded()
+        {
+            _callback.succeeded();
+        }
+
+        @Override
+        public void failed(final Throwable x)
+        {
+            if (x instanceof EofException || x instanceof ClosedChannelException)
+            {
+                LOG.debug(x);
+                _callback.failed(x);
+                _response.getHttpOutput().closed();
+            }
+            else
+            {
+                LOG.warn("Commit failed",x);
+                _transport.send(HttpGenerator.RESPONSE_500_INFO,null,true,new Callback()
+                {
+                    @Override
+                    public void succeeded()
+                    {
+                        _callback.failed(x);
+                        _response.getHttpOutput().closed();
+                    }
+
+                    @Override
+                    public void failed(Throwable th)
+                    {
+                        LOG.ignore(th);
+                        _callback.failed(x);
+                        _response.getHttpOutput().closed();
+                    }
+                });
+            }
+        }
+    }
+
+    private class Commit100Callback extends CommitCallback
+    {
+        private Commit100Callback(Callback callback)
+        {
+            super(callback);
+        }
+
+        @Override
+        public void succeeded()
+        {
+            if (_committed.compareAndSet(true, false))
+                super.succeeded();
+            else
+                super.failed(new IllegalStateException());
+        }
+
+    }
+
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java
new file mode 100644
index 0000000..078a710
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java
@@ -0,0 +1,742 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import javax.servlet.AsyncListener;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletResponse;
+
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.ContextHandler.Context;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+/**
+ * Implementation of AsyncContext interface that holds the state of request-response cycle.
+ */
+public class HttpChannelState
+{
+    private static final Logger LOG = Log.getLogger(HttpChannelState.class);
+
+    private final static long DEFAULT_TIMEOUT=Long.getLong("org.eclipse.jetty.server.HttpChannelState.DEFAULT_TIMEOUT",30000L);
+
+    /** The dispatched state of the HttpChannel, used to control the overall livecycle
+     */
+    public enum State
+    {
+        IDLE,             // Idle request
+        DISPATCHED,       // Request dispatched to filter/servlet
+        ASYNC_WAIT,       // Suspended and parked
+        ASYNC_WOKEN,      // A thread has been dispatch to handle from ASYNCWAIT
+        ASYNC_IO,         // Has been dispatched for async IO
+        COMPLETING,       // Request is completable
+        COMPLETED,        // Request is complete
+        UPGRADED          // Request upgraded the connection
+    }
+
+    /**
+     * The actions to take as the channel moves from state to state.
+     */
+    public enum Action
+    {
+        REQUEST_DISPATCH, // handle a normal request dispatch  
+        ASYNC_DISPATCH,   // handle an async request dispatch
+        ASYNC_EXPIRED,    // handle an async timeout
+        WRITE_CALLBACK,   // handle an IO write callback
+        READ_CALLBACK,    // handle an IO read callback
+        WAIT,             // Wait for further events 
+        COMPLETE          // Complete the channel
+    }
+    
+    /**
+     * The state of the servlet async API.  This can lead or follow the 
+     * channel dispatch state and also includes reasons such as expired,
+     * dispatched or completed.
+     */
+    public enum Async
+    {
+        STARTED,
+        DISPATCH,
+        COMPLETE,
+        EXPIRING,
+        EXPIRED
+    }
+
+    private final boolean DEBUG=LOG.isDebugEnabled();
+    private final HttpChannel<?> _channel;
+
+    private List<AsyncListener> _asyncListeners;
+    private State _state;
+    private Async _async;
+    private boolean _initial;
+    private boolean _asyncRead;
+    private boolean _asyncWrite;
+    private long _timeoutMs=DEFAULT_TIMEOUT;
+    private AsyncContextEvent _event;
+
+    protected HttpChannelState(HttpChannel<?> channel)
+    {
+        _channel=channel;
+        _state=State.IDLE;
+        _async=null;
+        _initial=true;
+    }
+
+    public State getState()
+    {
+        synchronized(this)
+        {
+            return _state;
+        }
+    }
+
+    public void addListener(AsyncListener listener)
+    {
+        synchronized(this)
+        {
+            if (_asyncListeners==null)
+                _asyncListeners=new ArrayList<>();
+            _asyncListeners.add(listener);
+        }
+    }
+
+    public void setTimeout(long ms)
+    {
+        synchronized(this)
+        {
+            _timeoutMs=ms;
+        }
+    }
+
+    public long getTimeout()
+    {
+        synchronized(this)
+        {
+            return _timeoutMs;
+        }
+    }
+
+    public AsyncContextEvent getAsyncContextEvent()
+    {
+        synchronized(this)
+        {
+            return _event;
+        }
+    }
+
+    @Override
+    public String toString()
+    {
+        synchronized (this)
+        {
+            return String.format("%s@%x{s=%s i=%b a=%s}",getClass().getSimpleName(),hashCode(),_state,_initial,_async);
+        }
+    }
+
+    public String getStatusString()
+    {
+        synchronized (this)
+        {
+            return String.format("s=%s i=%b a=%s",_state,_initial,_async);
+        }
+    }
+
+    /**
+     * @return Next handling of the request should proceed
+     */
+    protected Action handling()
+    {
+        synchronized (this)
+        {
+            if(DEBUG)
+                LOG.debug("{} handling {}",this,_state);
+            switch(_state)
+            {
+                case IDLE:
+                    _initial=true;
+                    _state=State.DISPATCHED;
+                    return Action.REQUEST_DISPATCH;
+
+                case COMPLETING:
+                    return Action.COMPLETE;
+
+                case COMPLETED:
+                    return Action.WAIT;
+
+                case ASYNC_WOKEN:
+                    if (_asyncRead)
+                    {
+                        _state=State.ASYNC_IO;
+                        _asyncRead=false;
+                        return Action.READ_CALLBACK;
+                    }
+                    if (_asyncWrite)
+                    {
+                        _state=State.ASYNC_IO;
+                        _asyncWrite=false;
+                        return Action.WRITE_CALLBACK;
+                    }
+                    
+                    if (_async!=null)
+                    {
+                        Async async=_async;
+                        switch(async)
+                        {
+                            case COMPLETE:
+                                _state=State.COMPLETING;
+                                return Action.COMPLETE;
+                            case DISPATCH:
+                                _state=State.DISPATCHED;
+                                _async=null;
+                                return Action.ASYNC_DISPATCH;
+                            case EXPIRING:
+                                break;
+                            case EXPIRED:
+                                _state=State.DISPATCHED;
+                                _async=null;
+                                return Action.ASYNC_EXPIRED;
+                            case STARTED:
+                                // TODO
+                                if (DEBUG)
+                                    LOG.debug("TODO Fix this double dispatch",new IllegalStateException(this
+                                            .getStatusString()));
+                                return Action.WAIT;
+                        }
+                    }
+                    
+                    return Action.WAIT;
+
+                default:
+                    throw new IllegalStateException(this.getStatusString());
+            }
+        }
+    }
+
+    public void startAsync(AsyncContextEvent event)
+    {
+        final List<AsyncListener> lastAsyncListeners;
+        
+        synchronized (this)
+        {
+            if (_state!=State.DISPATCHED || _async!=null)
+                throw new IllegalStateException(this.getStatusString());
+            
+            _async=Async.STARTED;
+            _event=event;
+            lastAsyncListeners=_asyncListeners;
+            _asyncListeners=null;
+        }
+
+        if (lastAsyncListeners!=null)
+        {
+            for (AsyncListener listener : lastAsyncListeners)
+            {
+                try
+                {
+                    listener.onStartAsync(event);
+                }
+                catch(Exception e)
+                {
+                    LOG.warn(e);
+                }
+            }
+        }
+    }
+
+    protected void error(Throwable th)
+    {
+        synchronized (this)
+        {
+            if (_event!=null)
+                _event.setThrowable(th);
+        }
+    }
+
+    /**
+     * Signal that the HttpConnection has finished handling the request.
+     * For blocking connectors, this call may block if the request has
+     * been suspended (startAsync called).
+     * @return next actions
+     * be handled again (eg because of a resume that happened before unhandle was called)
+     */
+    protected Action unhandle()
+    {
+        synchronized (this)
+        {
+            if(DEBUG)
+                LOG.debug("{} unhandle {}",this,_state);
+            
+            switch(_state)
+            {
+                case DISPATCHED:
+                case ASYNC_IO:
+                    break;
+                default:
+                    throw new IllegalStateException(this.getStatusString());
+            }
+
+            if (_asyncRead)
+            {
+                _state=State.ASYNC_IO;
+                _asyncRead=false;
+                return Action.READ_CALLBACK;
+            }
+            
+            if (_asyncWrite)
+            {
+                _asyncWrite=false;
+                _state=State.ASYNC_IO;
+                return Action.WRITE_CALLBACK;
+            }
+
+            if (_async!=null)
+            {
+                _initial=false;
+                switch(_async)
+                {
+                    case COMPLETE:
+                        _state=State.COMPLETING;
+                        _async=null;
+                        return Action.COMPLETE;
+                    case DISPATCH:
+                        _state=State.DISPATCHED;
+                        _async=null;
+                        return Action.ASYNC_DISPATCH;
+                    case EXPIRED:
+                        _state=State.DISPATCHED;
+                        _async=null;
+                        return Action.ASYNC_EXPIRED;
+                    case EXPIRING:
+                    case STARTED:
+                        scheduleTimeout();
+                        _state=State.ASYNC_WAIT;
+                        return Action.WAIT;
+                }
+            }
+            
+            _state=State.COMPLETING;
+            return Action.COMPLETE;
+        }
+    }
+
+    public void dispatch(ServletContext context, String path)
+    {
+        boolean dispatch;
+        synchronized (this)
+        {
+            if (_async!=Async.STARTED && _async!=Async.EXPIRING)
+                throw new IllegalStateException("AsyncContext#dispath "+this.getStatusString());
+            _async=Async.DISPATCH;
+            
+            if (context!=null)
+                _event.setDispatchContext(context);
+            if (path!=null)
+                _event.setDispatchPath(path);
+           
+            switch(_state)
+            {
+                case DISPATCHED:
+                case ASYNC_IO:
+                    dispatch=false;
+                    break;
+                case ASYNC_WAIT:
+                    _state=State.ASYNC_WOKEN;
+                    dispatch=true;
+                    break;
+                case ASYNC_WOKEN:
+                    dispatch=false;
+                    break;
+                default:
+                    LOG.warn("async dispatched when complete {}",this);
+                    dispatch=false;
+                    break;
+            }
+        }
+
+        cancelTimeout();
+        if (dispatch)
+            scheduleDispatch();
+    }
+
+    protected void expired()
+    {
+        final List<AsyncListener> aListeners;
+        AsyncContextEvent event;
+        synchronized (this)
+        {
+            if (_async!=Async.STARTED)
+                return;
+            _async=Async.EXPIRING;
+            event=_event;
+            aListeners=_asyncListeners;
+        }
+
+        if (aListeners!=null)
+        {
+            for (AsyncListener listener : aListeners)
+            {
+                try
+                {
+                    listener.onTimeout(event);
+                }
+                catch(Exception e)
+                {
+                    LOG.debug(e);
+                    event.setThrowable(e);
+                    _channel.getRequest().setAttribute(RequestDispatcher.ERROR_EXCEPTION,e);
+                    break;
+                }
+            }
+        }
+        
+        boolean dispatch=false;
+        synchronized (this)
+        {
+            if (_async==Async.EXPIRING)
+            {
+                _async=Async.EXPIRED;
+                if (_state==State.ASYNC_WAIT)
+                {
+                    _state=State.ASYNC_WOKEN;
+                    dispatch=true;
+                }
+            }
+        }
+
+        if (dispatch)
+            scheduleDispatch();
+    }
+
+    public void complete()
+    {
+        // just like resume, except don't set _dispatched=true;
+        boolean handle=false;
+        synchronized (this)
+        {
+            if (_async!=Async.STARTED && _async!=Async.EXPIRING)
+                throw new IllegalStateException(this.getStatusString());
+            _async=Async.COMPLETE;
+            if (_state==State.ASYNC_WAIT)
+            {
+                handle=true;
+                _state=State.ASYNC_WOKEN;
+            }
+        }
+
+        cancelTimeout();
+        if (handle)
+        {
+            ContextHandler handler=getContextHandler();
+            if (handler!=null)
+                handler.handle(_channel);
+            else
+                _channel.handle();
+        }
+    }
+
+    public void errorComplete()
+    {
+        synchronized (this)
+        {
+            _async=Async.COMPLETE;
+            _event.setDispatchContext(null);
+            _event.setDispatchPath(null);
+        }
+
+        cancelTimeout();
+    }
+
+    protected void completed()
+    {
+        final List<AsyncListener> aListeners;
+        final AsyncContextEvent event;
+        synchronized (this)
+        {
+            switch(_state)
+            {
+                case COMPLETING:
+                    _state=State.COMPLETED;
+                    aListeners=_asyncListeners;
+                    event=_event;
+                    break;
+
+                default:
+                    throw new IllegalStateException(this.getStatusString());
+            }
+        }
+
+        if (event!=null)
+        {
+            if (aListeners!=null)
+            {
+                if (event.getThrowable()!=null)
+                {
+                    event.getSuppliedRequest().setAttribute(RequestDispatcher.ERROR_EXCEPTION,event.getThrowable());
+                    event.getSuppliedRequest().setAttribute(RequestDispatcher.ERROR_MESSAGE,event.getThrowable().getMessage());
+                }
+
+                for (AsyncListener listener : aListeners)
+                {
+                    try
+                    {
+                        if (event.getThrowable()!=null)
+                            listener.onError(event);
+                        else
+                            listener.onComplete(event);
+                    }
+                    catch(Exception e)
+                    {
+                        LOG.warn(e);
+                    }
+                }
+            }
+
+            event.completed();
+        }
+    }
+
+    protected void recycle()
+    {
+        synchronized (this)
+        {
+            switch(_state)
+            {
+                case DISPATCHED:
+                case ASYNC_IO:
+                    throw new IllegalStateException(getStatusString());
+                case UPGRADED:
+                    return;
+                default:
+                    break;
+            }
+            _asyncListeners=null;
+            _state=State.IDLE;
+            _async=null;
+            _initial=true;
+            _asyncRead=false;
+            _asyncWrite=false;
+            _timeoutMs=DEFAULT_TIMEOUT;
+            cancelTimeout();
+            _event=null;
+        }
+    }
+    
+    public void upgrade()
+    {
+        synchronized (this)
+        {
+            switch(_state)
+            {
+                case IDLE:
+                case COMPLETED:
+                    break;
+                default:
+                    throw new IllegalStateException(getStatusString());
+            }
+            _asyncListeners=null;
+            _state=State.UPGRADED;
+            _async=null;
+            _initial=true;
+            _asyncRead=false;
+            _asyncWrite=false;
+            _timeoutMs=DEFAULT_TIMEOUT;
+            cancelTimeout();
+            _event=null;
+        }
+    }
+
+
+    protected void scheduleDispatch()
+    {
+        _channel.execute(_channel);
+    }
+
+    protected void scheduleTimeout()
+    {
+        Scheduler scheduler = _channel.getScheduler();
+        if (scheduler!=null && _timeoutMs>0)
+            _event.setTimeoutTask(scheduler.schedule(_event,_timeoutMs,TimeUnit.MILLISECONDS));
+    }
+
+    protected void cancelTimeout()
+    {
+        final AsyncContextEvent event;
+        synchronized (this)
+        { 
+            event=_event;
+        }
+        if (event!=null)
+            event.cancelTimeoutTask();
+    }
+
+    public boolean isExpired()
+    {
+        synchronized (this)
+        {
+            return _async==Async.EXPIRED;
+        }
+    }
+
+    public boolean isInitial()
+    {
+        synchronized(this)
+        {
+            return _initial;
+        }
+    }
+
+    public boolean isSuspended()
+    {
+        synchronized(this)
+        {
+            return _state==State.ASYNC_WAIT || _state==State.DISPATCHED && _async==Async.STARTED;
+        }
+    }
+
+    boolean isCompleting()
+    {
+        synchronized (this)
+        {
+            return _state==State.COMPLETING;
+        }
+    }
+
+    boolean isCompleted()
+    {
+        synchronized (this)
+        {
+            return _state == State.COMPLETED;
+        }
+    }
+
+    public boolean isAsyncStarted()
+    {
+        synchronized (this)
+        {    
+            if (_state==State.DISPATCHED)
+                return _async!=null;
+            return _async==Async.STARTED || _async==Async.EXPIRING;
+        }
+    }
+
+    public boolean isAsync()
+    {
+        synchronized (this)
+        {
+            return !_initial || _async!=null;
+        }
+    }
+
+    public Request getBaseRequest()
+    {
+        return _channel.getRequest();
+    }
+
+    public HttpChannel<?> getHttpChannel()
+    {
+        return _channel;
+    }
+
+    public ContextHandler getContextHandler()
+    {
+        final AsyncContextEvent event;
+        synchronized (this)
+        { 
+            event=_event;
+        }
+       
+        if (event!=null)
+        {
+            Context context=((Context)event.getServletContext());
+            if (context!=null)
+                return context.getContextHandler();
+        }
+        return null;
+    }
+
+    public ServletResponse getServletResponse()
+    {
+        final AsyncContextEvent event;
+        synchronized (this)
+        { 
+            event=_event;
+        }
+        if (event!=null && event.getSuppliedResponse()!=null)
+            return event.getSuppliedResponse();
+        return _channel.getResponse();
+    }
+
+    public Object getAttribute(String name)
+    {
+        return _channel.getRequest().getAttribute(name);
+    }
+
+    public void removeAttribute(String name)
+    {
+        _channel.getRequest().removeAttribute(name);
+    }
+
+    public void setAttribute(String name, Object attribute)
+    {
+        _channel.getRequest().setAttribute(name,attribute);
+    }
+
+    public void onReadPossible()
+    {
+        boolean handle=false;
+
+        synchronized (this)
+        {
+            _asyncRead=true;
+            if (_state==State.ASYNC_WAIT)
+            {
+                _state=State.ASYNC_WOKEN;
+                handle=true;
+            }
+        }
+
+        if (handle)
+            _channel.execute(_channel);
+    }
+    
+    public void onWritePossible()
+    {
+        boolean handle=false;
+
+        synchronized (this)
+        {
+            _asyncWrite=true;
+            if (_state==State.ASYNC_WAIT)
+            {
+                _state=State.ASYNC_WOKEN;
+                handle=true;
+            }
+        }
+
+        if (handle)
+            _channel.execute(_channel);
+    }
+    
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConfiguration.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConfiguration.java
new file mode 100644
index 0000000..d9c833f
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConfiguration.java
@@ -0,0 +1,342 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.eclipse.jetty.http.HttpScheme;
+import org.eclipse.jetty.util.Jetty;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+
+
+/* ------------------------------------------------------------ */
+/** HTTP Configuration.
+ * <p>This class is a holder of HTTP configuration for use by the 
+ * {@link HttpChannel} class.  Typically a HTTPConfiguration instance
+ * is instantiated and passed to a {@link ConnectionFactory} that can 
+ * create HTTP channels (eg HTTP, AJP or SPDY).</p>
+ * <p>The configuration held by this class is not for the wire protocol,
+ * but for the interpretation and handling of HTTP requests that could
+ * be transported by a variety of protocols.
+ * </p>
+ */
+ at ManagedObject("HTTP Configuration")
+public class HttpConfiguration
+{
+    public static final String SERVER_VERSION = "Jetty(" + Jetty.VERSION + ")";
+
+    private List<Customizer> _customizers=new CopyOnWriteArrayList<>();
+    private int _outputBufferSize=32*1024;
+    private int _outputAggregationSize=_outputBufferSize/4;
+    private int _requestHeaderSize=8*1024;
+    private int _responseHeaderSize=8*1024;
+    private int _headerCacheSize=512;
+    private int _securePort;
+    private String _secureScheme = HttpScheme.HTTPS.asString();
+    private boolean _sendServerVersion = true;
+    private boolean _sendXPoweredBy = false;
+    private boolean _sendDateHeader = true;
+    private boolean _delayDispatchUntilContent = false;
+
+    /* ------------------------------------------------------------ */
+    /** 
+     * <p>An interface that allows a request object to be customized 
+     * for a particular HTTP connector configuration.  Unlike Filters, customizer are
+     * applied before the request is submitted for processing and can be specific to the 
+     * connector on which the request was received.
+     * 
+     * <p>Typically Customizers perform tasks such as: <ul>
+     *  <li>process header fields that may be injected by a proxy or load balancer.
+     *  <li>setup attributes that may come from the connection/connector such as SSL Session IDs
+     *  <li>Allow a request to be marked as secure or authenticated if those have been offloaded
+     *  and communicated by header, cookie or other out-of-band mechanism
+     *  <li>Set request attributes/fields that are determined by the connector on which the
+     *  request was received
+     *  </ul>
+     */
+    public interface Customizer
+    {
+        public void customize(Connector connector, HttpConfiguration channelConfig, Request request);
+    }
+    
+    public interface ConnectionFactory
+    {
+        HttpConfiguration getHttpConfiguration();
+    }
+    
+    public HttpConfiguration()
+    {
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Create a configuration from another.
+     * @param config The configuration to copy.
+     */
+    public HttpConfiguration(HttpConfiguration config)
+    {
+        _customizers.addAll(config._customizers);
+        _outputBufferSize=config._outputBufferSize;
+        _outputAggregationSize=config._outputAggregationSize;
+        _requestHeaderSize=config._requestHeaderSize;
+        _responseHeaderSize=config._responseHeaderSize;
+        _securePort=config._securePort;
+        _secureScheme=config._secureScheme;
+        _sendDateHeader=config._sendDateHeader;
+        _sendServerVersion=config._sendServerVersion;
+        _headerCacheSize=config._headerCacheSize;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** 
+     * <p>Add a {@link Customizer} that is invoked for every 
+     * request received.</p>
+     * <p>Customiser are often used to interpret optional headers (eg {@link ForwardedRequestCustomizer}) or 
+     * optional protocol semantics (eg {@link SecureRequestCustomizer}). 
+     * @param customizer A request customizer
+     */
+    public void addCustomizer(Customizer customizer)
+    {
+        _customizers.add(customizer);
+    }
+    
+    /* ------------------------------------------------------------ */
+    public List<Customizer> getCustomizers()
+    {
+        return _customizers;
+    }
+
+    /* ------------------------------------------------------------ */
+    public <T> T getCustomizer(Class<T> type)
+    {
+        for (Customizer c : _customizers)
+            if (type.isAssignableFrom(c.getClass()))
+                return (T)c;
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    @ManagedAttribute("The size in bytes of the output buffer used to aggregate HTTP output")
+    public int getOutputBufferSize()
+    {
+        return _outputBufferSize;
+    }
+
+    /* ------------------------------------------------------------ */
+    @ManagedAttribute("The maximum size in bytes for HTTP output to be aggregated")
+    public int getOutputAggregationSize()
+    {
+        return _outputAggregationSize;
+    }
+
+    /* ------------------------------------------------------------ */
+    @ManagedAttribute("The maximum allowed size in bytes for a HTTP request header")
+    public int getRequestHeaderSize()
+    {
+        return _requestHeaderSize;
+    }
+
+    /* ------------------------------------------------------------ */
+    @ManagedAttribute("The maximum allowed size in bytes for a HTTP response header")
+    public int getResponseHeaderSize()
+    {
+        return _responseHeaderSize;
+    }
+
+    /* ------------------------------------------------------------ */
+    @ManagedAttribute("The maximum allowed size in bytes for a HTTP header field cache")
+    public int getHeaderCacheSize()
+    {
+        return _headerCacheSize;
+    }
+
+    /* ------------------------------------------------------------ */
+    @ManagedAttribute("The port to which Integral or Confidential security constraints are redirected")
+    public int getSecurePort()
+    {
+        return _securePort;
+    }
+
+    /* ------------------------------------------------------------ */
+    @ManagedAttribute("The scheme with which Integral or Confidential security constraints are redirected")
+    public String getSecureScheme()
+    {
+        return _secureScheme;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setSendServerVersion (boolean sendServerVersion)
+    {
+        _sendServerVersion = sendServerVersion;
+    }
+
+    /* ------------------------------------------------------------ */
+    @ManagedAttribute("if true, send the Server header in responses")
+    public boolean getSendServerVersion()
+    {
+        return _sendServerVersion;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setSendXPoweredBy (boolean sendXPoweredBy)
+    {
+        _sendXPoweredBy=sendXPoweredBy;
+    }
+
+    /* ------------------------------------------------------------ */
+    @ManagedAttribute("if true, send the X-Powered-By header in responses")
+    public boolean getSendXPoweredBy()
+    {
+        return _sendXPoweredBy;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setSendDateHeader(boolean sendDateHeader)
+    {
+        _sendDateHeader = sendDateHeader;
+    }
+
+    /* ------------------------------------------------------------ */
+    @ManagedAttribute("if true, include the date in HTTP headers")
+    public boolean getSendDateHeader()
+    {
+        return _sendDateHeader;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param delay if true, delay the application dispatch until content is available
+     */
+    public void setDelayDispatchUntilContent(boolean delay)
+    {
+        _delayDispatchUntilContent = delay;
+    }
+
+    /* ------------------------------------------------------------ */
+    @ManagedAttribute("if true, delay the application dispatch until content is available")
+    public boolean isDelayDispatchUntilContent()
+    {
+        return _delayDispatchUntilContent;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * <p>Set the {@link Customizer}s that are invoked for every 
+     * request received.</p>
+     * <p>Customizers are often used to interpret optional headers (eg {@link ForwardedRequestCustomizer}) or
+     * optional protocol semantics (eg {@link SecureRequestCustomizer}). 
+     * @param customizers the list of customizers
+     */
+    public void setCustomizers(List<Customizer> customizers)
+    {
+        _customizers.clear();
+        _customizers.addAll(customizers);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the size of the buffer into which response content is aggregated
+     * before being sent to the client.  A larger buffer can improve performance by allowing
+     * a content producer to run without blocking, however larger buffers consume more memory and
+     * may induce some latency before a client starts processing the content.
+     * @param outputBufferSize buffer size in bytes.
+     */
+    public void setOutputBufferSize(int outputBufferSize)
+    {
+        _outputBufferSize = outputBufferSize;
+        setOutputAggregationSize(outputBufferSize / 4);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the max size of the response content write that is copied into the aggregate buffer.
+     * Writes that are smaller of this size are copied into the aggregate buffer, while
+     * writes that are larger of this size will cause the aggregate buffer to be flushed
+     * and the write to be executed without being copied.
+     * @param outputAggregationSize the max write size that is aggregated
+     */
+    public void setOutputAggregationSize(int outputAggregationSize)
+    {
+        _outputAggregationSize = outputAggregationSize;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the maximum size of a request header.
+     * <p>Larger headers will allow for more and/or larger cookies plus larger form content encoded 
+     * in a URL. However, larger headers consume more memory and can make a server more vulnerable to denial of service
+     * attacks.</p>
+     * @param requestHeaderSize Max header size in bytes
+     */
+    public void setRequestHeaderSize(int requestHeaderSize)
+    {
+        _requestHeaderSize = requestHeaderSize;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the maximum size of a response header.
+     * 
+     * <p>Larger headers will allow for more and/or larger cookies and longer HTTP headers (eg for redirection). 
+     * However, larger headers will also consume more memory.</p>
+     * @param responseHeaderSize Response header size in bytes.
+     */
+    public void setResponseHeaderSize(int responseHeaderSize)
+    {
+        _responseHeaderSize = responseHeaderSize;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the header field cache size.
+     * @param headerCacheSize The size in bytes of the header field cache.
+     */
+    public void setHeaderCacheSize(int headerCacheSize)
+    {
+        _headerCacheSize = headerCacheSize;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the TCP/IP port used for CONFIDENTIAL and INTEGRAL redirections.
+     * @param securePort the secure port to redirect to.
+     */
+    public void setSecurePort(int securePort)
+    {
+        _securePort = securePort;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the  URI scheme used for CONFIDENTIAL and INTEGRAL redirections.
+     * @param secureScheme A scheme string like "https"
+     */
+    public void setSecureScheme(String secureScheme)
+    {
+        _secureScheme = secureScheme;
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s@%x{%d/%d,%d/%d,%s://:%d,%s}",
+                this.getClass().getSimpleName(),
+                hashCode(),
+                _outputBufferSize, _outputAggregationSize,
+                _requestHeaderSize,_responseHeaderSize,
+                _secureScheme,_securePort,
+                _customizers);
+    }
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java
new file mode 100644
index 0000000..a4ae349
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java
@@ -0,0 +1,761 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.WritePendingException;
+import java.util.concurrent.RejectedExecutionException;
+
+import org.eclipse.jetty.http.HttpGenerator;
+import org.eclipse.jetty.http.HttpGenerator.ResponseInfo;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpHeaderValue;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpParser;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.io.AbstractConnection;
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.EofException;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.IteratingCallback;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * <p>A {@link Connection} that handles the HTTP protocol.</p>
+ */
+public class HttpConnection extends AbstractConnection implements Runnable, HttpTransport, Connection.UpgradeFrom
+{
+    public static final String UPGRADE_CONNECTION_ATTRIBUTE = "org.eclipse.jetty.server.HttpConnection.UPGRADE";
+    private static final boolean REQUEST_BUFFER_DIRECT=false;
+    private static final boolean HEADER_BUFFER_DIRECT=false;
+    private static final boolean CHUNK_BUFFER_DIRECT=false;
+    private static final Logger LOG = Log.getLogger(HttpConnection.class);
+    private static final ThreadLocal<HttpConnection> __currentConnection = new ThreadLocal<>();
+
+    private final HttpConfiguration _config;
+    private final Connector _connector;
+    private final ByteBufferPool _bufferPool;
+    private final HttpGenerator _generator;
+    private final HttpChannelOverHttp _channel;
+    private final HttpParser _parser;
+    private volatile ByteBuffer _requestBuffer = null;
+    private volatile ByteBuffer _chunk = null;
+    private final SendCallback _sendCallback = new SendCallback();
+
+
+    /* ------------------------------------------------------------ */
+    /** Get the current connection that this thread is dispatched to.
+     * Note that a thread may be processing a request asynchronously and 
+     * thus not be dispatched to the connection.  
+     * @see Request#getAttribute(String) for a more general way to access the HttpConnection
+     * @return the current HttpConnection or null
+     */
+    public static HttpConnection getCurrentConnection()
+    {
+        return __currentConnection.get();
+    }
+
+    protected static HttpConnection setCurrentConnection(HttpConnection connection)
+    {
+        HttpConnection last=__currentConnection.get();
+        __currentConnection.set(connection);
+        return last;
+    }
+
+    public HttpConfiguration getHttpConfiguration()
+    {
+        return _config;
+    }
+
+    public HttpConnection(HttpConfiguration config, Connector connector, EndPoint endPoint)
+    {
+        // Tell AbstractConnector executeOnFillable==true because we want the same thread that
+        // does the HTTP parsing to handle the request so its cache is hot
+        super(endPoint, connector.getExecutor(),true);
+
+        _config = config;
+        _connector = connector;
+        _bufferPool = _connector.getByteBufferPool();
+        _generator = newHttpGenerator();
+        HttpInput<ByteBuffer> input = newHttpInput();
+        _channel = newHttpChannel(input);
+        _parser = newHttpParser();
+        if (LOG.isDebugEnabled())
+            LOG.debug("New HTTP Connection {}", this);
+    }
+
+    protected HttpGenerator newHttpGenerator()
+    {
+        return new HttpGenerator(_config.getSendServerVersion(),_config.getSendXPoweredBy());
+    }
+    
+    protected HttpInput<ByteBuffer> newHttpInput()
+    {
+        return new HttpInputOverHTTP(this);
+    }
+    
+    protected HttpChannelOverHttp newHttpChannel(HttpInput<ByteBuffer> httpInput)
+    {
+        return new HttpChannelOverHttp(_connector, _config, getEndPoint(), this, httpInput);
+    }
+    
+    protected HttpParser newHttpParser()
+    {
+        return new HttpParser(newRequestHandler(), getHttpConfiguration().getRequestHeaderSize());
+    }
+
+    protected HttpParser.RequestHandler<ByteBuffer> newRequestHandler()
+    {
+        return _channel;
+    }
+
+    public Server getServer()
+    {
+        return _connector.getServer();
+    }
+
+    public Connector getConnector()
+    {
+        return _connector;
+    }
+
+    public HttpChannel<?> getHttpChannel()
+    {
+        return _channel;
+    }
+
+    public HttpParser getParser()
+    {
+        return _parser;
+    }
+
+    @Override
+    public int getMessagesIn()
+    {
+        return getHttpChannel().getRequests();
+    }
+
+    @Override
+    public int getMessagesOut()
+    {
+        return getHttpChannel().getRequests();
+    }
+
+    @Override
+    public ByteBuffer onUpgradeFrom()
+    {
+        if (BufferUtil.hasContent(_requestBuffer))
+        {
+            ByteBuffer buffer = _requestBuffer;
+            _requestBuffer=null;
+            return buffer;
+        }
+        return null;
+    }
+
+    void releaseRequestBuffer()
+    {
+        if (_requestBuffer != null && !_requestBuffer.hasRemaining())
+        {
+            ByteBuffer buffer=_requestBuffer;
+            _requestBuffer=null;
+            _bufferPool.release(buffer);
+        }
+    }
+    
+    public ByteBuffer getRequestBuffer()
+    {
+        if (_requestBuffer == null)
+            _requestBuffer = _bufferPool.acquire(getInputBufferSize(), REQUEST_BUFFER_DIRECT);
+        return _requestBuffer;
+    }
+
+    /**
+     * <p>Parses and handles HTTP messages.</p>
+     * <p>This method is called when this {@link Connection} is ready to read bytes from the {@link EndPoint}.
+     * However, it can also be called if there is unconsumed data in the _requestBuffer, as a result of
+     * resuming a suspended request when there is a pipelined request already read into the buffer.</p>
+     * <p>This method fills bytes and parses them until either: EOF is filled; 0 bytes are filled;
+     * the HttpChannel finishes handling; or the connection has changed.</p>
+     */
+    @Override
+    public void onFillable()
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("{} onFillable {}", this, _channel.getState());
+
+        final HttpConnection last=setCurrentConnection(this);
+        int filled=Integer.MAX_VALUE;
+        boolean suspended=false;
+        try
+        {
+            // while not suspended and not upgraded
+            while (!suspended && getEndPoint().getConnection()==this)
+            {
+                // Do we need some data to parse
+                if (BufferUtil.isEmpty(_requestBuffer))
+                {
+                    // If the previous iteration filled 0 bytes or saw a close, then break here 
+                    if (filled<=0)
+                        break;
+                        
+                    // Can we fill?
+                    if(getEndPoint().isInputShutdown())
+                    {
+                        // No pretend we read -1
+                        filled=-1;
+                        _parser.atEOF();
+                    }
+                    else
+                    {
+                        // Get a buffer
+                        // We are not in a race here for the request buffer as we have not yet received a request,
+                        // so there are not an possible legal threads calling #parseContent or #completed.
+                        _requestBuffer = getRequestBuffer();
+
+                        // fill
+                        filled = getEndPoint().fill(_requestBuffer);
+                        if (filled==0) // Do a retry on fill 0 (optimization for SSL connections)
+                            filled = getEndPoint().fill(_requestBuffer);
+                        
+                        // tell parser
+                        if (filled < 0)
+                            _parser.atEOF();
+                    }
+                }
+                
+                // Parse the buffer
+                if (_parser.parseNext(_requestBuffer==null?BufferUtil.EMPTY_BUFFER:_requestBuffer))
+                {
+                    // The parser returned true, which indicates the channel is ready to handle a request.
+                    // Call the channel and this will either handle the request/response to completion OR,
+                    // if the request suspends, the request/response will be incomplete so the outer loop will exit.
+                    // Not that onFillable no longer manipulates the request buffer from this point and that is
+                    // left to threads calling #completed or #parseContent (which may be this thread inside handle())
+                    suspended = !_channel.handle();
+                }
+                else
+                {
+                    // We parsed what we could, recycle the request buffer
+                    // We are not in a race here for the request buffer as we have not yet received a request,
+                    // so there are not an possible legal threads calling #parseContent or #completed.
+                    releaseRequestBuffer();
+                }
+            }
+        }
+        catch (EofException e)
+        {
+            LOG.debug(e);
+        }
+        catch (Exception e)
+        {
+            if (_parser.isIdle())
+                LOG.debug(e);
+            else
+                LOG.warn(this.toString(), e);
+            close();
+        }
+        finally
+        {                        
+            setCurrentConnection(last);
+            if (!suspended && getEndPoint().isOpen() && getEndPoint().getConnection()==this)
+            {
+                fillInterested();
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Fill and parse data looking for content
+     * @throws IOException
+     */
+    protected void parseContent() throws IOException
+    {
+        // Not in a race here for the request buffer with #onFillable because an async consumer of
+        // content would only be started after onFillable has given up control.
+        // In a little bit of a race with #completed, but then not sure if it is legal to be doing 
+        // async calls to IO and have a completed call at the same time.
+        ByteBuffer requestBuffer = getRequestBuffer();
+
+        while (_parser.inContentState())
+        {
+            // Can the parser progress (even with an empty buffer)
+            boolean parsed = _parser.parseNext(requestBuffer==null?BufferUtil.EMPTY_BUFFER:requestBuffer);
+
+            // No, we can we try reading some content?
+            if (BufferUtil.isEmpty(requestBuffer) && getEndPoint().isInputShutdown())
+            {
+                _parser.atEOF();
+                if (parsed)
+                    break;
+                continue;
+            }
+
+            if (parsed)
+                break;
+            
+            // OK lets read some data
+            int filled=getEndPoint().fill(requestBuffer);
+            if (LOG.isDebugEnabled()) // Avoid boxing of variable 'filled'
+                LOG.debug("{} filled {}",this,filled);
+            if (filled<=0)
+            {
+                if (filled<0)
+                {
+                    _parser.atEOF();
+                    continue;
+                }
+                break;
+            }
+        }
+    }
+    
+    @Override
+    public void completed()
+    {
+        // Handle connection upgrades
+        if (_channel.getResponse().getStatus() == HttpStatus.SWITCHING_PROTOCOLS_101)
+        {
+            Connection connection = (Connection)_channel.getRequest().getAttribute(UPGRADE_CONNECTION_ATTRIBUTE);
+            if (connection != null)
+            {
+                _channel.getState().upgrade();
+                getEndPoint().upgrade(connection);
+                _channel.reset();
+                _parser.reset();
+                _generator.reset();
+                releaseRequestBuffer();
+                return;
+            }
+        }
+        
+        // Finish consuming the request
+        // If we are still expecting
+        if (_channel.isExpecting100Continue())
+        {
+            // close to seek EOF
+            _parser.close();
+        }
+        else if (_parser.inContentState() && _generator.isPersistent())
+        {
+            // If we are async, then we have problems to complete neatly
+            if (_channel.getRequest().getHttpInput().isAsync())
+            {
+                if (LOG.isDebugEnabled())
+                    LOG.debug("unconsumed async input {}", this);
+                _channel.abort();
+            }
+            else
+            {
+                if (LOG.isDebugEnabled())
+                    LOG.debug("unconsumed input {}", this);
+                // Complete reading the request
+                if (!_channel.getRequest().getHttpInput().consumeAll())
+                    _channel.abort();
+            }
+        }
+
+        // Reset the channel, parsers and generator
+        _channel.reset();
+        if (_generator.isPersistent() && !_parser.isClosed())
+            _parser.reset();
+        else
+            _parser.close();
+        
+        // Not in a race here with onFillable, because it has given up control before calling handle.
+        // in a slight race with #completed, but not sure what to do with that anyway.
+        releaseRequestBuffer();
+        if (_chunk!=null)
+            _bufferPool.release(_chunk);
+        _chunk=null;
+        _generator.reset();
+
+        // if we are not called from the onfillable thread, schedule completion
+        if (getCurrentConnection()!=this)
+        {
+            // If we are looking for the next request
+            if (_parser.isStart())
+            {
+                // if the buffer is empty
+                if (BufferUtil.isEmpty(_requestBuffer))
+                {
+                    // look for more data
+                    fillInterested();
+                }
+                // else if we are still running
+                else if (getConnector().isRunning())
+                {
+                    // Dispatched to handle a pipelined request
+                    try
+                    {
+                        getExecutor().execute(this);
+                    }
+                    catch (RejectedExecutionException e)
+                    {
+                        if (getConnector().isRunning())
+                            LOG.warn(e);
+                        else
+                            LOG.ignore(e);
+                        getEndPoint().close();
+                    }
+                }
+                else
+                {
+                    getEndPoint().close();
+                }
+            }
+            // else the parser must be closed, so seek the EOF if we are still open 
+            else if (getEndPoint().isOpen())
+                fillInterested();
+        }
+    }
+
+    @Override
+    protected void onFillInterestedFailed(Throwable cause)
+    {
+        _parser.close();
+        super.onFillInterestedFailed(cause);
+    }
+
+    @Override
+    public void onOpen()
+    {
+        super.onOpen();
+        fillInterested();
+    }
+
+    @Override
+    public void onClose()
+    {
+        _sendCallback.close();
+        super.onClose();
+    }
+
+    @Override
+    public void run()
+    {
+        onFillable();
+    }
+
+    @Override
+    public void send(ResponseInfo info, ByteBuffer content, boolean lastContent, Callback callback)
+    {
+        // If we are still expecting a 100 continues when we commit
+        if (info!=null && _channel.isExpecting100Continue())
+            // then we can't be persistent
+            _generator.setPersistent(false);
+        
+        if(_sendCallback.reset(info,content,lastContent,callback))
+            _sendCallback.iterate();
+    }
+
+    @Override
+    public void send(ByteBuffer content, boolean lastContent, Callback callback)
+    {
+        if (!lastContent && BufferUtil.isEmpty(content))
+            callback.succeeded();
+        else if (_sendCallback.reset(null,content,lastContent,callback))
+            _sendCallback.iterate();
+    }
+    
+    protected class HttpChannelOverHttp extends HttpChannel<ByteBuffer>
+    {        
+        public HttpChannelOverHttp(Connector connector, HttpConfiguration config, EndPoint endPoint, HttpTransport transport, HttpInput<ByteBuffer> input)
+        {
+            super(connector,config,endPoint,transport,input);
+        }
+        
+        @Override
+        public void earlyEOF()
+        {
+            // If we have no request yet, just close
+            if (getRequest().getMethod()==null)
+                close();
+            else
+                super.earlyEOF();
+        }
+
+        @Override
+        public boolean content(ByteBuffer item)
+        {
+            super.content(item);
+            return true;
+        }
+
+        @Override
+        public void badMessage(int status, String reason)
+        {
+            _generator.setPersistent(false);
+            super.badMessage(status,reason);
+        }
+
+        @Override
+        public boolean headerComplete()
+        {
+            boolean persistent;
+            HttpVersion version = getHttpVersion();
+
+            switch (version)
+            {
+                case HTTP_0_9:
+                {
+                    persistent = false;
+                    break;
+                }
+                case HTTP_1_0:
+                {
+                    persistent = getRequest().getHttpFields().contains(HttpHeader.CONNECTION, HttpHeaderValue.KEEP_ALIVE.asString());
+                    if (!persistent)
+                        persistent = HttpMethod.CONNECT.is(getRequest().getMethod());
+                    if (persistent)
+                        getResponse().getHttpFields().add(HttpHeader.CONNECTION, HttpHeaderValue.KEEP_ALIVE);
+                    break;
+                }
+                case HTTP_1_1:
+                {
+                    persistent = !getRequest().getHttpFields().contains(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString());
+                    if (!persistent)
+                        persistent = HttpMethod.CONNECT.is(getRequest().getMethod());
+                    if (!persistent)
+                        getResponse().getHttpFields().add(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE);
+                    break;
+                }
+                case HTTP_2:
+                {
+                    persistent=false;
+                    badMessage(400,null);
+                    return true;
+                }
+                default:
+                {
+                    throw new IllegalStateException();
+                }
+            }
+
+            if (!persistent)
+                _generator.setPersistent(false);
+
+            if (!super.headerComplete())
+                return false;
+            
+            // Should we delay dispatch until we have some content?
+            // We should not delay if there is no content expect or client is expecting 100 or the response is already committed or the request buffer already has something in it to parse
+            if (getHttpConfiguration().isDelayDispatchUntilContent() && _parser.getContentLength() > 0 &&
+                    !isExpecting100Continue() && !isCommitted() && BufferUtil.isEmpty(_requestBuffer))
+                return false;
+
+            return true;
+        }
+
+        @Override
+        protected void handleException(Throwable x)
+        {
+            _generator.setPersistent(false);
+            super.handleException(x);
+        }
+
+        @Override
+        public void abort()
+        {
+            super.abort();
+            _generator.setPersistent(false);
+        }
+
+        @Override
+        public boolean messageComplete()
+        {
+            super.messageComplete();
+            return false;
+        }
+    }
+
+    private class SendCallback extends IteratingCallback
+    {
+        private ResponseInfo _info;
+        private ByteBuffer _content;
+        private boolean _lastContent;
+        private Callback _callback;
+        private ByteBuffer _header;
+        private boolean _shutdownOut;
+
+        private SendCallback()
+        {
+            super(true);
+        }
+
+        private boolean reset(ResponseInfo info, ByteBuffer content, boolean last, Callback callback)
+        {
+            if (reset())
+            {
+                _info = info;
+                _content = content;
+                _lastContent = last;
+                _callback = callback;
+                _header = null;
+                _shutdownOut = false;
+                return true;
+            }
+            
+            if (isClosed())
+                callback.failed(new EofException());
+            else
+                callback.failed(new WritePendingException());
+            return false;
+        }
+
+        @Override
+        public Action process() throws Exception
+        {
+            if (_callback==null)
+                throw new IllegalStateException();
+            
+            ByteBuffer chunk = _chunk;
+            while (true)
+            {
+                HttpGenerator.Result result = _generator.generateResponse(_info, _header, chunk, _content, _lastContent);
+                if (LOG.isDebugEnabled())
+                    LOG.debug("{} generate: {} ({},{},{})@{}",
+                        this,
+                        result,
+                        BufferUtil.toSummaryString(_header),
+                        BufferUtil.toSummaryString(_content),
+                        _lastContent,
+                        _generator.getState());
+
+                switch (result)
+                {
+                    case NEED_HEADER:
+                    {
+                        _header = _bufferPool.acquire(_config.getResponseHeaderSize(), HEADER_BUFFER_DIRECT);    
+                        continue;
+                    }
+                    case NEED_CHUNK:
+                    {
+                        chunk = _chunk = _bufferPool.acquire(HttpGenerator.CHUNK_SIZE, CHUNK_BUFFER_DIRECT);
+                        continue;
+                    }
+                    case FLUSH:
+                    {
+                        // Don't write the chunk or the content if this is a HEAD response, or any other type of response that should have no content
+                        if (_channel.getRequest().isHead() || _generator.isNoContent())
+                        {
+                            BufferUtil.clear(chunk);
+                            BufferUtil.clear(_content);
+                        }
+
+                        // If we have a header
+                        if (BufferUtil.hasContent(_header))
+                        {
+                            if (BufferUtil.hasContent(_content))
+                            {
+                                if (BufferUtil.hasContent(chunk))
+                                    getEndPoint().write(this, _header, chunk, _content);
+                                else
+                                    getEndPoint().write(this, _header, _content);
+                            }
+                            else
+                                getEndPoint().write(this, _header);
+                        }
+                        else if (BufferUtil.hasContent(chunk))
+                        {
+                            if (BufferUtil.hasContent(_content))
+                                getEndPoint().write(this, chunk, _content);
+                            else
+                                getEndPoint().write(this, chunk);
+                        }
+                        else if (BufferUtil.hasContent(_content))
+                        {
+                            getEndPoint().write(this, _content);
+                        }
+                        else
+                        {
+                            succeeded(); // nothing to write
+                        }
+                        return Action.SCHEDULED;
+                    }
+                    case SHUTDOWN_OUT:
+                    {
+                        _shutdownOut=true;
+                        continue;
+                    }
+                    case DONE:
+                    {
+                        return Action.SUCCEEDED;
+                    }
+                    case CONTINUE:
+                    {
+                        break;
+                    }
+                    default:
+                    {
+                        throw new IllegalStateException("generateResponse="+result);
+                    }
+                }
+            }
+        }
+
+        private void releaseHeader()
+        {
+            ByteBuffer h=_header;
+            _header=null;
+            if (h!=null)
+                _bufferPool.release(h);
+        }
+        
+        @Override
+        protected void onCompleteSuccess()
+        {
+            releaseHeader();
+            _callback.succeeded();
+            if (_shutdownOut)
+                getEndPoint().shutdownOutput();
+        }
+
+        @Override
+        public void onCompleteFailure(final Throwable x)
+        {
+            releaseHeader();
+            failedCallback(_callback,x);
+            if (_shutdownOut)
+                getEndPoint().shutdownOutput();
+        }
+        
+        @Override
+        public String toString()
+        {
+            return String.format("%s[i=%s,cb=%s]",super.toString(),_info,_callback);
+        }
+    }
+
+
+    @Override
+    public void abort()
+    {
+        // Do a direct close of the output, as this may indicate to a client that the 
+        // response is bad either with RST or by abnormal completion of chunked response.
+        getEndPoint().close();
+    }
+
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnectionFactory.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnectionFactory.java
new file mode 100644
index 0000000..48872a2
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnectionFactory.java
@@ -0,0 +1,64 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.server;
+
+
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.util.annotation.Name;
+
+
+/* ------------------------------------------------------------ */
+/** A Connection Factory for HTTP Connections.
+ * <p>Accepts connections either directly or via SSL and/or NPN chained connection factories.  The accepted 
+ * {@link HttpConnection}s are configured by a {@link HttpConfiguration} instance that is either created by
+ * default or passed in to the constructor.
+ */
+public class HttpConnectionFactory extends AbstractConnectionFactory implements HttpConfiguration.ConnectionFactory
+{
+    private final HttpConfiguration _config;
+
+    public HttpConnectionFactory()
+    {
+        this(new HttpConfiguration());
+        setInputBufferSize(16384);
+    }
+
+    public HttpConnectionFactory(@Name("config") HttpConfiguration config)
+    {
+        super(HttpVersion.HTTP_1_1.toString());
+        _config=config;
+        addBean(_config);
+    }
+
+    @Override
+    public HttpConfiguration getHttpConfiguration()
+    {
+        return _config;
+    }
+
+    @Override
+    public Connection newConnection(Connector connector, EndPoint endPoint)
+    {
+        return configure(new HttpConnection(_config, connector, endPoint), connector, endPoint);
+    }
+
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java
index 3ac48ad..315024a 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java
@@ -19,57 +19,518 @@
 package org.eclipse.jetty.server;
 
 import java.io.IOException;
-
+import java.util.Objects;
+import javax.servlet.ReadListener;
 import javax.servlet.ServletInputStream;
 
-import org.eclipse.jetty.http.HttpParser;
-import org.eclipse.jetty.io.Buffer;
 import org.eclipse.jetty.io.EofException;
+import org.eclipse.jetty.io.RuntimeIOException;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
 
-public class HttpInput extends ServletInputStream
+/**
+ * {@link HttpInput} provides an implementation of {@link ServletInputStream} for {@link HttpChannel}.
+ * <p/>
+ * Content may arrive in patterns such as [content(), content(), messageComplete()] so that this class
+ * maintains two states: the content state that tells whether there is content to consume and the EOF
+ * state that tells whether an EOF has arrived.
+ * Only once the content has been consumed the content state is moved to the EOF state.
+ */
+public abstract class HttpInput<T> extends ServletInputStream implements Runnable
 {
-    protected final AbstractHttpConnection _connection;
-    protected final HttpParser _parser;
+    private final static Logger LOG = Log.getLogger(HttpInput.class);
+
+    private final byte[] _oneByteBuffer = new byte[1];
+    private final Object _lock;
+    private HttpChannelState _channelState;
+    private ReadListener _listener;
+    private Throwable _onError;
+    private boolean _notReady;
+    private State _contentState = STREAM;
+    private State _eofState;
+    private long _contentRead;
 
-    /* ------------------------------------------------------------ */
-    public HttpInput(AbstractHttpConnection connection)
+    protected HttpInput()
     {
-        _connection=connection;
-        _parser=(HttpParser)connection.getParser();
+        this(null);
     }
-    
-    /* ------------------------------------------------------------ */
-    /*
-     * @see java.io.InputStream#read()
-     */
+
+    protected HttpInput(Object lock)
+    {
+        _lock = lock == null ? this : lock;
+    }
+
+    public void init(HttpChannelState state)
+    {
+        synchronized (lock())
+        {
+            _channelState = state;
+        }
+    }
+
+    public final Object lock()
+    {
+        return _lock;
+    }
+
+    public void recycle()
+    {
+        synchronized (lock())
+        {
+            _listener = null;
+            _onError = null;
+            _notReady = false;
+            _contentState = STREAM;
+            _eofState = null;
+            _contentRead = 0;
+        }
+    }
+
+    @Override
+    public int available()
+    {
+        try
+        {
+            synchronized (lock())
+            {
+                T item = getNextContent();
+                return item == null ? 0 : remaining(item);
+            }
+        }
+        catch (IOException e)
+        {
+            throw new RuntimeIOException(e);
+        }
+    }
+
     @Override
     public int read() throws IOException
     {
-        byte[] bytes = new byte[1];
-        int read = read(bytes, 0, 1);
-        return read < 0 ? -1 : 0xff & bytes[0];
+        int read = read(_oneByteBuffer, 0, 1);
+        return read < 0 ? -1 : _oneByteBuffer[0] & 0xFF;
+    }
+
+    @Override
+    public int read(byte[] b, int off, int len) throws IOException
+    {
+        synchronized (lock())
+        {
+            T item = getNextContent();
+            if (item == null)
+            {
+                _contentState.waitForContent(this);
+                item = getNextContent();
+                if (item == null)
+                    return _contentState.noContent();
+            }
+            int l = get(item, b, off, len);
+            _contentRead += l;
+            return l;
+        }
+    }
+
+    /**
+     * A convenience method to call nextContent and to check the return value, which if null then the
+     * a check is made for EOF and the state changed accordingly.
+     *
+     * @return Content or null if none available.
+     * @throws IOException
+     * @see #nextContent()
+     */
+    protected T getNextContent() throws IOException
+    {
+        T content = nextContent();
+        if (content == null)
+        {
+            synchronized (lock())
+            {
+                if (_eofState != null)
+                {
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("{} eof {}", this, _eofState);
+                    _contentState = _eofState;
+                }
+            }
+        }
+        return content;
+    }
+
+    /**
+     * Access the next content to be consumed from.   Returning the next item does not consume it
+     * and it may be returned multiple times until it is consumed.
+     * <p/>
+     * Calls to {@link #get(Object, byte[], int, int)}
+     * or {@link #consume(Object, int)} are required to consume data from the content.
+     *
+     * @return the content or null if none available.
+     * @throws IOException if retrieving the content fails
+     */
+    protected abstract T nextContent() throws IOException;
+
+    /**
+     * @param item the content
+     * @return how many bytes remain in the given content
+     */
+    protected abstract int remaining(T item);
+
+    /**
+     * Copies the given content into the given byte buffer.
+     *
+     * @param item   the content to copy from
+     * @param buffer the buffer to copy into
+     * @param offset the buffer offset to start copying from
+     * @param length the space available in the buffer
+     * @return the number of bytes actually copied
+     */
+    protected abstract int get(T item, byte[] buffer, int offset, int length);
+
+    /**
+     * Consumes the given content.
+     *
+     * @param item   the content to consume
+     * @param length the number of bytes to consume
+     */
+    protected abstract void consume(T item, int length);
+
+    /**
+     * Blocks until some content or some end-of-file event arrives.
+     *
+     * @throws IOException if the wait is interrupted
+     */
+    protected abstract void blockForContent() throws IOException;
+
+    /**
+     * Adds some content to this input stream.
+     *
+     * @param item the content to add
+     */
+    public abstract void content(T item);
+
+    protected boolean onAsyncRead()
+    {
+        synchronized (lock())
+        {
+            if (_listener == null)
+                return false;
+        }
+        _channelState.onReadPossible();
+        return true;
+    }
+
+    public long getContentRead()
+    {
+        synchronized (lock())
+        {
+            return _contentRead;
+        }
+    }
+
+    /**
+     * This method should be called to signal that an EOF has been
+     * detected before all the expected content arrived.
+     * <p/>
+     * Typically this will result in an EOFException being thrown
+     * from a subsequent read rather than a -1 return.
+     */
+    public void earlyEOF()
+    {
+        synchronized (lock())
+        {
+            if (!isEOF())
+            {
+                if (LOG.isDebugEnabled())
+                    LOG.debug("{} early EOF", this);
+                _eofState = EARLY_EOF;
+                if (_listener == null)
+                    return;
+            }
+        }
+        _channelState.onReadPossible();
+    }
+
+
+    public boolean isEarlyEOF()
+    {
+        synchronized (lock())
+        {
+            return _contentState==EARLY_EOF;
+        }
+    }
+    
+    /**
+     * This method should be called to signal that all the expected
+     * content arrived.
+     */
+    public void messageComplete()
+    {
+        synchronized (lock())
+        {
+            if (!isEOF())
+            {
+                if (LOG.isDebugEnabled())
+                    LOG.debug("{} EOF", this);
+                _eofState = EOF;
+                if (_listener == null)
+                    return;
+            }
+        }
+        _channelState.onReadPossible();
+    }
+
+    public boolean consumeAll()
+    {
+        synchronized (lock())
+        {
+            // Don't bother reading if we already know there was an error.
+            if (_onError != null)
+                return false;
+
+            try
+            {
+                while (!isFinished())
+                {
+                    T item = getNextContent();
+                    if (item == null)
+                        _contentState.waitForContent(this);
+                    else
+                        consume(item, remaining(item));
+                }
+                return true;
+            }
+            catch (IOException e)
+            {
+                LOG.debug(e);
+                return false;
+            }
+        }
+    }
+
+    public boolean isAsync()
+    {
+        synchronized (lock())
+        {
+            return _contentState==ASYNC;
+        }
     }
     
-    /* ------------------------------------------------------------ */
-    /* 
-     * @see java.io.InputStream#read(byte[], int, int)
+    /**
+     * @return whether an EOF has been detected, even though there may be content to consume.
      */
+    public boolean isEOF()
+    {
+        synchronized (lock())
+        {
+            return _eofState != null && _eofState.isEOF();
+        }
+    }
+
     @Override
-    public int read(byte[] b, int off, int len) throws IOException
+    public boolean isFinished()
+    {
+        synchronized (lock())
+        {
+            return _contentState.isEOF();
+        }
+    }
+    
+
+    @Override
+    public boolean isReady()
+    {
+        boolean finished;
+        synchronized (lock())
+        {
+            if (_contentState.isEOF())
+                return true;
+            if (_listener == null )
+                return true;
+            if (available() > 0)
+                return true;
+            if (_notReady)
+                return false;
+            _notReady = true;
+            finished = isFinished();
+        }
+        if (finished)
+            _channelState.onReadPossible();
+        else
+            unready();
+        return false;
+    }
+
+    protected void unready()
+    {
+    }
+
+    @Override
+    public void setReadListener(ReadListener readListener)
+    {
+        try
+        {
+            readListener = Objects.requireNonNull(readListener);
+            boolean content;
+            synchronized (lock())
+            {
+                if (_contentState != STREAM)
+                    throw new IllegalStateException("state=" + _contentState);
+                _contentState = ASYNC;
+                _listener = readListener;
+                _notReady = true;
+                
+                content = getNextContent()!=null || isEOF();
+                
+            }
+            if (content)
+                _channelState.onReadPossible();
+            else
+                unready();
+        }
+        catch(IOException e)
+        {
+            throw new RuntimeIOException(e);
+        }
+    }
+
+    public void failed(Throwable x)
     {
-        int l=-1;
-        Buffer content=_parser.blockForContent(_connection.getMaxIdleTime());
-        if (content!=null)
-            l= content.get(b, off, len);
-        else if (_connection.isEarlyEOF())
-            throw new EofException("early EOF");
-        return l;
+        synchronized (lock())
+        {
+            if (_onError != null)
+                LOG.warn(x);
+            else
+                _onError = x;
+        }
     }
 
-    /* ------------------------------------------------------------ */
     @Override
-    public int available() throws IOException
+    public void run()
+    {
+        final Throwable error;
+        final ReadListener listener;
+        boolean available = false;
+        final boolean eof;
+
+        synchronized (lock())
+        {
+            if (!_notReady || _listener == null)
+                return;
+
+            error = _onError;
+            listener = _listener;
+
+            try
+            {
+                T item = getNextContent();
+                available = item != null && remaining(item) > 0;
+            }
+            catch (Exception e)
+            {
+                failed(e);
+            }
+
+            eof = !available && isFinished();
+            _notReady = !available && !eof;
+        }
+
+        try
+        {
+            if (error != null)
+                listener.onError(error);
+            else if (available)
+                listener.onDataAvailable();
+            else if (eof)
+                listener.onAllDataRead();
+            else
+                unready();
+        }
+        catch (Throwable e)
+        {
+            LOG.warn(e.toString());
+            LOG.debug(e);
+            listener.onError(e);
+        }
+    }
+
+    protected static abstract class State
     {
-        return _parser.available();
+        public void waitForContent(HttpInput<?> in) throws IOException
+        {
+        }
+
+        public int noContent() throws IOException
+        {
+            return -1;
+        }
+
+        public boolean isEOF()
+        {
+            return false;
+        }
     }
+
+    protected static final State STREAM = new State()
+    {
+        @Override
+        public void waitForContent(HttpInput<?> input) throws IOException
+        {
+            input.blockForContent();
+        }
+
+        @Override
+        public String toString()
+        {
+            return "STREAM";
+        }
+    };
+
+    protected static final State ASYNC = new State()
+    {
+        @Override
+        public int noContent() throws IOException
+        {
+            return 0;
+        }
+
+        @Override
+        public String toString()
+        {
+            return "ASYNC";
+        }
+    };
+
+    protected static final State EARLY_EOF = new State()
+    {
+        @Override
+        public int noContent() throws IOException
+        {
+            throw new EofException("Early EOF");
+        }
+
+        @Override
+        public boolean isEOF()
+        {
+            return true;
+        }
+
+        @Override
+        public String toString()
+        {
+            return "EARLY_EOF";
+        }
+    };
+
+    protected static final State EOF = new State()
+    {
+        @Override
+        public boolean isEOF()
+        {
+            return true;
+        }
+
+        @Override
+        public String toString()
+        {
+            return "EOF";
+        }
+    };
 }
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInputOverHTTP.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInputOverHTTP.java
new file mode 100644
index 0000000..115b9af
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInputOverHTTP.java
@@ -0,0 +1,146 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.SharedBlockingCallback;
+import org.eclipse.jetty.util.SharedBlockingCallback.Blocker;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public class HttpInputOverHTTP extends HttpInput<ByteBuffer> implements Callback
+{
+    private static final Logger LOG = Log.getLogger(HttpInputOverHTTP.class);
+    private final SharedBlockingCallback _readBlocker = new SharedBlockingCallback();
+    private final HttpConnection _httpConnection;
+    private ByteBuffer _content;
+
+    /**
+     * @param httpConnection
+     */
+    public HttpInputOverHTTP(HttpConnection httpConnection)
+    {
+        _httpConnection = httpConnection;
+    }
+
+    @Override
+    public void recycle()
+    {
+        synchronized (lock())
+        {
+            super.recycle();
+            _content=null;
+        }
+    }
+
+    @Override
+    protected void blockForContent() throws IOException
+    {
+        while(true)
+        {
+            try (Blocker blocker=_readBlocker.acquire())
+            {            
+                _httpConnection.fillInterested(blocker);
+                if (LOG.isDebugEnabled())
+                    LOG.debug("{} block readable on {}",this,blocker);
+                blocker.block();
+            }
+
+            Object content=getNextContent();
+            if (content!=null || isFinished())
+                break;
+        }
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s@%x",getClass().getSimpleName(),hashCode());
+    }
+
+    @Override
+    protected ByteBuffer nextContent() throws IOException
+    {
+        // If we have some content available, return it
+        if (BufferUtil.hasContent(_content))
+            return _content;
+
+        // No - then we are going to need to parse some more content
+        _content=null;
+        _httpConnection.parseContent();
+        
+        // If we have some content available, return it
+        if (BufferUtil.hasContent(_content))
+            return _content;
+
+        return null;
+
+    }
+
+    @Override
+    protected int remaining(ByteBuffer item)
+    {
+        return item.remaining();
+    }
+
+    @Override
+    protected int get(ByteBuffer item, byte[] buffer, int offset, int length)
+    {
+        int l = Math.min(item.remaining(), length);
+        item.get(buffer, offset, l);
+        return l;
+    }
+
+    @Override
+    protected void consume(ByteBuffer item, int length)
+    {
+        item.position(item.position()+length);
+    }
+
+    @Override
+    public void content(ByteBuffer item)
+    {
+        if (BufferUtil.hasContent(_content))
+            throw new IllegalStateException();
+        _content=item;
+    }
+
+    @Override
+    protected void unready()
+    {
+        _httpConnection.fillInterested(this);
+    }
+
+    @Override
+    public void succeeded()
+    {
+        _httpConnection.getHttpChannel().getState().onReadPossible();
+    }
+
+    @Override
+    public void failed(Throwable x)
+    {
+        super.failed(x);
+        _httpConnection.getHttpChannel().getState().onReadPossible();
+    }
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java
index aa1cd18..ddae39c 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java
@@ -19,165 +19,1116 @@
 package org.eclipse.jetty.server;
 
 import java.io.IOException;
-import java.io.Writer;
-
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.WritePendingException;
+import java.util.concurrent.atomic.AtomicReference;
+import javax.servlet.RequestDispatcher;
 import javax.servlet.ServletOutputStream;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.WriteListener;
 
-import org.eclipse.jetty.http.AbstractGenerator;
-import org.eclipse.jetty.http.Generator;
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.ByteArrayBuffer;
+import org.eclipse.jetty.http.HttpContent;
 import org.eclipse.jetty.io.EofException;
-import org.eclipse.jetty.util.ByteArrayOutputStream2;
-
-/** Output.
- * 
- * <p>
- * Implements  {@link javax.servlet.ServletOutputStream} from the <code>javax.servlet</code> package.   
- * </p>
- * A {@link ServletOutputStream} implementation that writes content
- * to a {@link AbstractGenerator}.   The class is designed to be reused
- * and can be reopened after a close.
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.IteratingCallback;
+import org.eclipse.jetty.util.IteratingNestedCallback;
+import org.eclipse.jetty.util.SharedBlockingCallback;
+import org.eclipse.jetty.util.SharedBlockingCallback.Blocker;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * <p>{@link HttpOutput} implements {@link ServletOutputStream}
+ * as required by the Servlet specification.</p>
+ * <p>{@link HttpOutput} buffers content written by the application until a
+ * further write will overflow the buffer, at which point it triggers a commit
+ * of the response.</p>
+ * <p>{@link HttpOutput} can be closed and reopened, to allow requests included
+ * via {@link RequestDispatcher#include(ServletRequest, ServletResponse)} to
+ * close the stream, to be reopened after the inclusion ends.</p>
  */
-public class HttpOutput extends ServletOutputStream 
+public class HttpOutput extends ServletOutputStream implements Runnable
 {
-    protected final AbstractHttpConnection _connection;
-    protected final AbstractGenerator _generator;
-    private boolean _closed;
-    private ByteArrayBuffer _onebyte;
+    private static Logger LOG = Log.getLogger(HttpOutput.class);
+    private final HttpChannel<?> _channel;
+    private final SharedBlockingCallback _writeblock=new SharedBlockingCallback()
+    {
+        @Override
+        protected long getIdleTimeout()
+        {
+            return _channel.getIdleTimeout();
+        }
+    };
+    private long _written;
+    private ByteBuffer _aggregate;
+    private int _bufferSize;
+    private int _commitSize;
+    private WriteListener _writeListener;
+    private volatile Throwable _onError;
+
+    /*
+    ACTION             OPEN       ASYNC      READY      PENDING       UNREADY       CLOSED
+    -----------------------------------------------------------------------------------------------------
+    setWriteListener() READY->owp ise        ise        ise           ise           ise
+    write()            OPEN       ise        PENDING    wpe           wpe           eof
+    flush()            OPEN       ise        PENDING    wpe           wpe           eof
+    close()            CLOSED     CLOSED     CLOSED     CLOSED        wpe           CLOSED
+    isReady()          OPEN:true  READY:true READY:true UNREADY:false UNREADY:false CLOSED:true
+    write completed    -          -          -          ASYNC         READY->owp    -
     
-    // These are held here for reuse by Writer
-    String _characterEncoding;
-    Writer _converter;
-    char[] _chars;
-    ByteArrayOutputStream2 _bytes;
+    */
+    enum OutputState { OPEN, ASYNC, READY, PENDING, UNREADY, ERROR, CLOSED }
+    private final AtomicReference<OutputState> _state=new AtomicReference<>(OutputState.OPEN);
 
-    /* ------------------------------------------------------------ */
-    public HttpOutput(AbstractHttpConnection connection)
+    public HttpOutput(HttpChannel<?> channel)
     {
-        _connection=connection;
-        _generator=(AbstractGenerator)connection.getGenerator();
+        _channel = channel;
+        HttpConfiguration config = channel.getHttpConfiguration();
+        _bufferSize = config.getOutputBufferSize();
+        _commitSize = config.getOutputAggregationSize();
+        if (_commitSize>_bufferSize)
+        {
+            LOG.warn("OutputAggregationSize {} exceeds bufferSize {}",_commitSize,_bufferSize);
+            _commitSize=_bufferSize;
+        }
     }
-
-    /* ------------------------------------------------------------ */
-    public int getMaxIdleTime()
+    
+    public HttpChannel<?> getHttpChannel()
     {
-        return _connection.getMaxIdleTime();
+        return _channel;
     }
     
-    /* ------------------------------------------------------------ */
     public boolean isWritten()
     {
-        return _generator.getContentWritten()>0;
+        return _written > 0;
     }
-    
-    /* ------------------------------------------------------------ */
-    /*
-     * @see java.io.OutputStream#close()
-     */
-    @Override
-    public void close() throws IOException
+
+    public long getWritten()
+    {
+        return _written;
+    }
+
+    public void reset()
+    {
+        _written = 0;
+        reopen();
+    }
+
+    public void reopen()
     {
-        _closed=true;
+        _state.set(OutputState.OPEN);
+    }
+
+    public boolean isAllContentWritten()
+    {
+        return _channel.getResponse().isAllContentWritten(_written);
+    }
+
+    protected Blocker acquireWriteBlockingCallback() throws IOException
+    {
+        return _writeblock.acquire();
     }
     
-    /* ------------------------------------------------------------ */
-    public boolean isClosed()
+    protected void write(ByteBuffer content, boolean complete) throws IOException
     {
-        return _closed;
+        try (Blocker blocker=_writeblock.acquire())
+        {        
+            write(content,complete,blocker);
+            blocker.block();
+        }
     }
     
-    /* ------------------------------------------------------------ */
-    public void reopen()
+    protected void write(ByteBuffer content, boolean complete, Callback callback)
     {
-        _closed=false;
+        _channel.write(content,complete,callback);
     }
     
-    /* ------------------------------------------------------------ */
+    @Override
+    public void close()
+    {
+        loop: while(true)
+        {
+            OutputState state=_state.get();
+            switch (state)
+            {
+                case CLOSED:
+                    break loop;
+                    
+                case UNREADY:
+                    if (_state.compareAndSet(state,OutputState.ERROR))
+                        _writeListener.onError(_onError==null?new EofException("Async close"):_onError);
+                    continue;
+                    
+                default:
+                    if (_state.compareAndSet(state,OutputState.CLOSED))
+                    {
+                        try
+                        {
+                            write(BufferUtil.hasContent(_aggregate)?_aggregate:BufferUtil.EMPTY_BUFFER,!_channel.getResponse().isIncluding());
+                        }
+                        catch(IOException e)
+                        {
+                            LOG.debug(e);
+                            _channel.abort();
+                        }
+                        releaseBuffer();
+                        return;
+                    }
+            }
+        }
+    }
+
+    /* Called to indicated that the output is already closed (write with last==true performed) and the state needs to be updated to match */
+    void closed()
+    {
+        loop: while(true)
+        {
+            OutputState state=_state.get();
+            switch (state)
+            {
+                case CLOSED:
+                    break loop;
+                    
+                case UNREADY:
+                    if (_state.compareAndSet(state,OutputState.ERROR))
+                        _writeListener.onError(_onError==null?new EofException("Async closed"):_onError);
+                    continue;
+                    
+                default:
+                    if (_state.compareAndSet(state,OutputState.CLOSED))
+                    {
+                        try
+                        {
+                            _channel.getResponse().closeOutput();
+                        }
+                        catch(IOException e)
+                        {
+                            LOG.debug(e);
+                            _channel.abort();
+                        }
+                        releaseBuffer();
+                        return;
+                    }
+            }
+        }
+    }
+
+    private void releaseBuffer()
+    {
+        if (_aggregate != null)
+        {
+            _channel.getConnector().getByteBufferPool().release(_aggregate);
+            _aggregate = null;
+        }
+    }
+
+    public boolean isClosed()
+    {
+        return _state.get()==OutputState.CLOSED;
+    }
+
     @Override
     public void flush() throws IOException
     {
-        _generator.flush(getMaxIdleTime());
+        while(true)
+        {
+            switch(_state.get())
+            {
+                case OPEN:
+                    write(BufferUtil.hasContent(_aggregate)?_aggregate:BufferUtil.EMPTY_BUFFER, false);
+                    return;
+
+                case ASYNC:
+                    throw new IllegalStateException("isReady() not called");
+
+                case READY:
+                    if (!_state.compareAndSet(OutputState.READY, OutputState.PENDING))
+                        continue;
+                    new AsyncFlush().iterate();
+                    return;
+
+                case PENDING:
+                case UNREADY:
+                    throw new WritePendingException();
+
+                case ERROR:
+                    throw new EofException(_onError);
+                    
+                case CLOSED:
+                    return;
+            }
+            break;
+        }
     }
 
-    /* ------------------------------------------------------------ */
+
     @Override
     public void write(byte[] b, int off, int len) throws IOException
     {
-        write(new ByteArrayBuffer(b,off,len));
+        _written+=len;
+        boolean complete=_channel.getResponse().isAllContentWritten(_written);
+
+        // Async or Blocking ?
+        while(true)
+        {
+            switch(_state.get())
+            {
+                case OPEN:
+                    // process blocking below
+                    break;
+
+                case ASYNC:
+                    throw new IllegalStateException("isReady() not called");
+
+                case READY:
+                    if (!_state.compareAndSet(OutputState.READY, OutputState.PENDING))
+                        continue;
+
+                    // Should we aggregate?
+                    if (!complete && len<=_commitSize)
+                    {
+                        if (_aggregate == null)
+                            _aggregate = _channel.getByteBufferPool().acquire(getBufferSize(), false);
+
+                        // YES - fill the aggregate with content from the buffer
+                        int filled = BufferUtil.fill(_aggregate, b, off, len);
+
+                        // return if we are not complete, not full and filled all the content
+                        if (filled==len && !BufferUtil.isFull(_aggregate))
+                        {
+                            if (!_state.compareAndSet(OutputState.PENDING, OutputState.ASYNC))
+                                throw new IllegalStateException();
+                            return;
+                        }
+
+                        // adjust offset/length
+                        off+=filled;
+                        len-=filled;
+                    }
+
+                    // Do the asynchronous writing from the callback
+                    new AsyncWrite(b,off,len,complete).iterate();
+                    return;
+
+                case PENDING:
+                case UNREADY:
+                    throw new WritePendingException();
+
+                case ERROR:
+                    throw new EofException(_onError);
+                    
+                case CLOSED:
+                    throw new EofException("Closed");
+            }
+            break;
+        }
+
+
+        // handle blocking write
+
+        // Should we aggregate?
+        int capacity = getBufferSize();
+        if (!complete && len<=_commitSize)
+        {
+            if (_aggregate == null)
+                _aggregate = _channel.getByteBufferPool().acquire(capacity, false);
+
+            // YES - fill the aggregate with content from the buffer
+            int filled = BufferUtil.fill(_aggregate, b, off, len);
+
+            // return if we are not complete, not full and filled all the content
+            if (filled==len && !BufferUtil.isFull(_aggregate))
+                return;
+
+            // adjust offset/length
+            off+=filled;
+            len-=filled;
+        }
+
+        // flush any content from the aggregate
+        if (BufferUtil.hasContent(_aggregate))
+        {
+            write(_aggregate, complete && len==0);
+
+            // should we fill aggregate again from the buffer?
+            if (len>0 && !complete && len<=_commitSize && len<=BufferUtil.space(_aggregate))
+            {
+                BufferUtil.append(_aggregate, b, off, len);
+                return;
+            }
+        }
+
+        // write any remaining content in the buffer directly
+        if (len>0)
+        {
+            ByteBuffer wrap = ByteBuffer.wrap(b, off, len);
+            ByteBuffer view = wrap.duplicate();
+
+            // write a buffer capacity at a time to avoid JVM pooling large direct buffers
+            // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6210541
+            while (len>getBufferSize())
+            {
+                int p=view.position();
+                int l=p+getBufferSize();
+                view.limit(p+getBufferSize());
+                write(view,false);
+                len-=getBufferSize();
+                view.limit(l+Math.min(len,getBufferSize()));
+                view.position(l);
+            }
+            write(view,complete);
+        }
+        else if (complete)
+            write(BufferUtil.EMPTY_BUFFER,complete);
+
+        if (complete)
+            closed();
+
+    }
+
+    public void write(ByteBuffer buffer) throws IOException
+    {
+        _written+=buffer.remaining();
+        boolean complete=_channel.getResponse().isAllContentWritten(_written);
+
+        // Async or Blocking ?
+        while(true)
+        {
+            switch(_state.get())
+            {
+                case OPEN:
+                    // process blocking below
+                    break;
+
+                case ASYNC:
+                    throw new IllegalStateException("isReady() not called");
+
+                case READY:
+                    if (!_state.compareAndSet(OutputState.READY, OutputState.PENDING))
+                        continue;
+
+                    // Do the asynchronous writing from the callback
+                    new AsyncWrite(buffer,complete).iterate();
+                    return;
+
+                case PENDING:
+                case UNREADY:
+                    throw new WritePendingException();
+
+                case ERROR:
+                    throw new EofException(_onError);
+                    
+                case CLOSED:
+                    throw new EofException("Closed");
+            }
+            break;
+        }
+
+
+        // handle blocking write
+        int len=BufferUtil.length(buffer);
+
+        // flush any content from the aggregate
+        if (BufferUtil.hasContent(_aggregate))
+            write(_aggregate, complete && len==0);
+
+        // write any remaining content in the buffer directly
+        if (len>0)
+            write(buffer, complete);
+        else if (complete)
+            write(BufferUtil.EMPTY_BUFFER,complete);
+
+        if (complete)
+            closed();
+    }
+
+    @Override
+    public void write(int b) throws IOException
+    {
+        _written+=1;
+        boolean complete=_channel.getResponse().isAllContentWritten(_written);
+
+        // Async or Blocking ?
+        while(true)
+        {
+            switch(_state.get())
+            {
+                case OPEN:
+                    if (_aggregate == null)
+                        _aggregate = _channel.getByteBufferPool().acquire(getBufferSize(), false);
+                    BufferUtil.append(_aggregate, (byte)b);
+
+                    // Check if all written or full
+                    if (complete || BufferUtil.isFull(_aggregate))
+                    {
+                        try(Blocker blocker=_writeblock.acquire())
+                        {
+                            write(_aggregate, complete, blocker);
+                            blocker.block();
+                        }
+                        if (complete)
+                            closed();
+                    }
+                    break;
+
+                case ASYNC:
+                    throw new IllegalStateException("isReady() not called");
+
+                case READY:
+                    if (!_state.compareAndSet(OutputState.READY, OutputState.PENDING))
+                        continue;
+
+                    if (_aggregate == null)
+                        _aggregate = _channel.getByteBufferPool().acquire(getBufferSize(), false);
+                    BufferUtil.append(_aggregate, (byte)b);
+
+                    // Check if all written or full
+                    if (!complete && !BufferUtil.isFull(_aggregate))
+                    {
+                        if (!_state.compareAndSet(OutputState.PENDING, OutputState.ASYNC))
+                            throw new IllegalStateException();
+                        return;
+                    }
+
+                    // Do the asynchronous writing from the callback
+                    new AsyncFlush().iterate();
+                    return;
+
+                case PENDING:
+                case UNREADY:
+                    throw new WritePendingException();
+
+                case ERROR:
+                    throw new EofException(_onError);
+                    
+                case CLOSED:
+                    throw new EofException("Closed");
+            }
+            break;
+        }
+    }
+
+    @Override
+    public void print(String s) throws IOException
+    {
+        if (isClosed())
+            throw new IOException("Closed");
+
+        write(s.getBytes(_channel.getResponse().getCharacterEncoding()));
     }
 
     /* ------------------------------------------------------------ */
-    /*
-     * @see java.io.OutputStream#write(byte[])
+    /** Blocking send of content.
+     * @param content The content to send.
+     * @throws IOException
      */
-    @Override
-    public void write(byte[] b) throws IOException
+    public void sendContent(ByteBuffer content) throws IOException
     {
-        write(new ByteArrayBuffer(b));
+        try(Blocker blocker=_writeblock.acquire())
+        {
+            write(content,true,blocker);
+            blocker.block();
+        }
     }
 
-    
     /* ------------------------------------------------------------ */
-    /*
-     * @see java.io.OutputStream#write(int)
+    /** Blocking send of content.
+     * @param in The content to send
+     * @throws IOException
      */
+    public void sendContent(InputStream in) throws IOException
+    {
+        try(Blocker blocker=_writeblock.acquire())
+        {
+            new InputStreamWritingCB(in,blocker).iterate();
+            blocker.block();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Blocking send of content.
+     * @param in The content to send
+     * @throws IOException
+     */
+    public void sendContent(ReadableByteChannel in) throws IOException
+    {
+        try(Blocker blocker=_writeblock.acquire())
+        {
+            new ReadableByteChannelWritingCB(in,blocker).iterate();
+            blocker.block();
+        }
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /** Blocking send of content.
+     * @param content The content to send
+     * @throws IOException
+     */
+    public void sendContent(HttpContent content) throws IOException
+    {
+        try(Blocker blocker=_writeblock.acquire())
+        {
+            sendContent(content,blocker);
+            blocker.block();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Asynchronous send of content.
+     * @param content The content to send
+     * @param callback The callback to use to notify success or failure
+     */
+    public void sendContent(ByteBuffer content, final Callback callback)
+    {
+        write(content,true,new Callback()
+        {
+            @Override
+            public void succeeded()
+            {
+                closed();
+                callback.succeeded();
+            }
+
+            @Override
+            public void failed(Throwable x)
+            {
+                callback.failed(x);
+            }
+        });
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Asynchronous send of content.
+     * @param in The content to send as a stream.  The stream will be closed
+     * after reading all content.
+     * @param callback The callback to use to notify success or failure
+     */
+    public void sendContent(InputStream in, Callback callback)
+    {
+        new InputStreamWritingCB(in,callback).iterate();
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Asynchronous send of content.
+     * @param in The content to send as a channel.  The channel will be closed
+     * after reading all content.
+     * @param callback The callback to use to notify success or failure
+     */
+    public void sendContent(ReadableByteChannel in, Callback callback)
+    {
+        new ReadableByteChannelWritingCB(in,callback).iterate();
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Asynchronous send of content.
+     * @param httpContent The content to send
+     * @param callback The callback to use to notify success or failure
+     */
+    public void sendContent(HttpContent httpContent, Callback callback)
+    {
+        if (BufferUtil.hasContent(_aggregate))
+        {
+            callback.failed(new IOException("cannot sendContent() after write()"));
+            return;
+        }
+        if (_channel.isCommitted())
+        {
+            callback.failed(new IOException("committed"));
+            return;
+        }
+
+        while (true)
+        {
+            switch(_state.get())
+            {
+                case OPEN:
+                    if (!_state.compareAndSet(OutputState.OPEN, OutputState.PENDING))
+                        continue;
+                    break;
+                case ERROR:
+                    callback.failed(new EofException(_onError));
+                    return;
+                    
+                case CLOSED:
+                    callback.failed(new EofException("Closed"));
+                    return;
+                default:
+                    throw new IllegalStateException();
+            }
+            break;
+        }
+        ByteBuffer buffer= _channel.useDirectBuffers()?httpContent.getDirectBuffer():null;
+        if (buffer == null)
+            buffer = httpContent.getIndirectBuffer();
+
+        if (buffer!=null)
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("sendContent({}=={},{},direct={})",httpContent,BufferUtil.toDetailString(buffer),callback,_channel.useDirectBuffers());
+            
+            sendContent(buffer,callback);
+            return;
+        }
+
+        try
+        {
+            ReadableByteChannel rbc=httpContent.getReadableByteChannel();
+            if (rbc!=null)
+            {
+                if (LOG.isDebugEnabled())
+                    LOG.debug("sendContent({}=={},{},direct={})",httpContent,rbc,callback,_channel.useDirectBuffers());
+                // Close of the rbc is done by the async sendContent
+                sendContent(rbc,callback);
+                return;
+            }
+
+            InputStream in = httpContent.getInputStream();
+            if ( in!=null )
+            {
+                if (LOG.isDebugEnabled())
+                    LOG.debug("sendContent({}=={},{},direct={})",httpContent,in,callback,_channel.useDirectBuffers());
+                sendContent(in,callback);
+                return;
+            }
+        }
+        catch(Throwable th)
+        {
+            callback.failed(th);
+            return;
+        }
+
+        callback.failed(new IllegalArgumentException("unknown content for "+httpContent));
+    }
+
+    public int getBufferSize()
+    {
+        return _bufferSize;
+    }
+
+    public void setBufferSize(int size)
+    {
+        _bufferSize = size;
+        _commitSize = size;
+    }
+
+    public void resetBuffer()
+    {
+        if (BufferUtil.hasContent(_aggregate))
+            BufferUtil.clear(_aggregate);
+    }
+
     @Override
-    public void write(int b) throws IOException
+    public void setWriteListener(WriteListener writeListener)
     {
-        if (_onebyte==null)
-            _onebyte=new ByteArrayBuffer(1);
+        if (!_channel.getState().isAsync())
+            throw new IllegalStateException("!ASYNC");
+
+        if (_state.compareAndSet(OutputState.OPEN, OutputState.READY))
+        {
+            _writeListener = writeListener;
+            _channel.getState().onWritePossible();
+        }
         else
-            _onebyte.clear();
-        _onebyte.put((byte)b);
-        write(_onebyte);
+            throw new IllegalStateException();
     }
 
-    /* ------------------------------------------------------------ */
-    private void write(Buffer buffer) throws IOException
+    /**
+     * @see javax.servlet.ServletOutputStream#isReady()
+     */
+    @Override
+    public boolean isReady()
     {
-        if (_closed)
-            throw new IOException("Closed");
-        if (!_generator.isOpen())
-            throw new EofException();
-        
-        // Block until we can add _content.
-        while (_generator.isBufferFull())
+        while (true)
+        {
+            switch(_state.get())
+            {
+                case OPEN:
+                    return true;
+                case ASYNC:
+                    if (!_state.compareAndSet(OutputState.ASYNC, OutputState.READY))
+                        continue;
+                    return true;
+                case READY:
+                    return true;
+                case PENDING:
+                    if (!_state.compareAndSet(OutputState.PENDING, OutputState.UNREADY))
+                        continue;
+                    return false;
+                case UNREADY:
+                    return false;
+
+                case ERROR:
+                    return true;
+                    
+                case CLOSED:
+                    return true;
+            }
+        }
+    }
+
+    @Override
+    public void run()
+    {
+        loop: while (true)
         {
-            _generator.blockForOutput(getMaxIdleTime());
-            if (_closed)
-                throw new IOException("Closed");
-            if (!_generator.isOpen())
-                throw new EofException();
+            OutputState state = _state.get();
+
+            if(_onError!=null)
+            {
+                switch(state)
+                {
+                    case CLOSED:
+                    case ERROR:
+                        _onError=null;
+                        break loop;
+
+                    default:
+                        if (_state.compareAndSet(state, OutputState.ERROR))
+                        {
+                            Throwable th=_onError;
+                            _onError=null;
+                            if (LOG.isDebugEnabled())
+                                LOG.debug("onError",th);
+                            _writeListener.onError(th);
+                            close();
+
+                            break loop;
+                        }
+
+                }
+                continue;
+            }
+            
+            switch(_state.get())
+            {
+                case CLOSED:
+                    // even though a write is not possible, because a close has 
+                    // occurred, we need to call onWritePossible to tell async
+                    // producer that the last write completed.
+                    // so fall through
+                case READY:
+                    try
+                    {
+                        _writeListener.onWritePossible();
+                        break loop;
+                    }
+                    catch (Throwable e)
+                    {
+                        _onError=e;
+                    }
+                    break;
+                    
+                default:
+                    _onError=new IllegalStateException("state="+_state.get());
+            }
+        }
+    }
+    
+    @Override
+    public String toString()
+    {
+        return String.format("%s@%x{%s}",this.getClass().getSimpleName(),hashCode(),_state.get());
+    }
+    
+    private abstract class AsyncICB extends IteratingCallback
+    {
+        @Override
+        protected void onCompleteSuccess()
+        {
+            while(true)
+            {
+                OutputState last=_state.get();
+                switch(last)
+                {
+                    case PENDING:
+                        if (!_state.compareAndSet(OutputState.PENDING, OutputState.ASYNC))
+                            continue;
+                        break;
+
+                    case UNREADY:
+                        if (!_state.compareAndSet(OutputState.UNREADY, OutputState.READY))
+                            continue;
+                        _channel.getState().onWritePossible();
+                        break;
+
+                    case CLOSED:
+                        break;
+
+                    default:
+                        throw new IllegalStateException();
+                }
+                break;
+            }
+        }
+
+        @Override
+        public void onCompleteFailure(Throwable e)
+        {
+            _onError=e==null?new IOException():e;
+            _channel.getState().onWritePossible();
         }
+    }
+    
+    
+    private class AsyncFlush extends AsyncICB
+    {
+        protected volatile boolean _flushed;
+
+        public AsyncFlush()
+        {
+        }
+
+        @Override
+        protected Action process()
+        {
+            if (BufferUtil.hasContent(_aggregate))
+            {
+                _flushed=true;
+                write(_aggregate, false, this);
+                return Action.SCHEDULED;
+            }
+
+            if (!_flushed)
+            {
+                _flushed=true;
+                write(BufferUtil.EMPTY_BUFFER,false,this);
+                return Action.SCHEDULED;
+            }
 
-        // Add the _content
-        _generator.addContent(buffer, Generator.MORE);
+            return Action.SUCCEEDED;
+        }
+    }
+
+
+
+    private class AsyncWrite extends AsyncICB
+    {
+        private final ByteBuffer _buffer;
+        private final ByteBuffer _slice;
+        private final boolean _complete;
+        private final int _len;
+        protected volatile boolean _completed;
+
+        public AsyncWrite(byte[] b, int off, int len, boolean complete)
+        {
+            _buffer=ByteBuffer.wrap(b, off, len);
+            _len=len;
+            // always use a view for large byte arrays to avoid JVM pooling large direct buffers
+            _slice=_len<getBufferSize()?null:_buffer.duplicate();
+            _complete=complete;
+        }
 
-        // Have to flush and complete headers?
+        public AsyncWrite(ByteBuffer buffer, boolean complete)
+        {
+            _buffer=buffer;
+            _len=buffer.remaining();
+            // Use a slice buffer for large indirect to avoid JVM pooling large direct buffers
+            _slice=_buffer.isDirect()||_len<getBufferSize()?null:_buffer.duplicate();
+            _complete=complete;
+        }
+
+        @Override
+        protected Action process()
+        {
+            // flush any content from the aggregate
+            if (BufferUtil.hasContent(_aggregate))
+            {
+                _completed=_len==0;
+                write(_aggregate, _complete && _completed, this);
+                return Action.SCHEDULED;
+            }
+
+            // Can we just aggregate the remainder?
+            if (!_complete && _len<BufferUtil.space(_aggregate) && _len<_commitSize)
+            {
+                int position = BufferUtil.flipToFill(_aggregate);
+                BufferUtil.put(_buffer,_aggregate);
+                BufferUtil.flipToFlush(_aggregate, position);
+                return Action.SUCCEEDED;
+            }
+            
+            // Is there data left to write?
+            if (_buffer.hasRemaining())
+            {
+                // if there is no slice, just write it
+                if (_slice==null)
+                {
+                    _completed=true;
+                    write(_buffer, _complete, this);
+                    return Action.SCHEDULED;
+                }
+                
+                // otherwise take a slice
+                int p=_buffer.position();
+                int l=Math.min(getBufferSize(),_buffer.remaining());
+                int pl=p+l;
+                _slice.limit(pl);
+                _buffer.position(pl);
+                _slice.position(p);
+                _completed=!_buffer.hasRemaining();
+                write(_slice, _complete && _completed, this);
+                return Action.SCHEDULED;
+            }
+            
+            // all content written, but if we have not yet signal completion, we
+            // need to do so
+            if (_complete && !_completed)
+            {
+                _completed=true;
+                write(BufferUtil.EMPTY_BUFFER, _complete, this);
+                return Action.SCHEDULED;
+            }
+
+            return Action.SUCCEEDED;
+        }
+
+        @Override
+        protected void onCompleteSuccess()
+        {
+            super.onCompleteSuccess();
+            if (_complete)
+                closed();
+        }
         
-        if (_generator.isAllContentWritten())
+        
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /** An iterating callback that will take content from an
+     * InputStream and write it to the associated {@link HttpChannel}.
+     * A non direct buffer of size {@link HttpOutput#getBufferSize()} is used.
+     * This callback is passed to the {@link HttpChannel#write(ByteBuffer, boolean, Callback)} to
+     * be notified as each buffer is written and only once all the input is consumed will the
+     * wrapped {@link Callback#succeeded()} method be called.
+     */
+    private class InputStreamWritingCB extends IteratingNestedCallback
+    {
+        private final InputStream _in;
+        private final ByteBuffer _buffer;
+        private boolean _eof;
+
+        public InputStreamWritingCB(InputStream in, Callback callback)
         {
-            flush();
-            close();
-        } 
-        else if (_generator.isBufferFull())
-            _connection.commitResponse(Generator.MORE);
+            super(callback);
+            _in=in;
+            _buffer = _channel.getByteBufferPool().acquire(getBufferSize(), false);
+        }
 
-        // Block until our buffer is free
-        while (buffer.length() > 0 && _generator.isOpen())
+        @Override
+        protected Action process() throws Exception
         {
-            _generator.blockForOutput(getMaxIdleTime());
+            // Only return if EOF has previously been read and thus
+            // a write done with EOF=true
+            if (_eof)
+            {
+                // Handle EOF
+                _in.close();
+                closed();
+                _channel.getByteBufferPool().release(_buffer);
+                return Action.SUCCEEDED;
+            }
+            
+            // Read until buffer full or EOF
+            int len=0;
+            while (len<_buffer.capacity() && !_eof)
+            {
+                int r=_in.read(_buffer.array(),_buffer.arrayOffset()+len,_buffer.capacity()-len);
+                if (r<0)
+                    _eof=true;
+                else
+                    len+=r;
+            }
+
+            // write what we have
+            _buffer.position(0);
+            _buffer.limit(len);
+            write(_buffer,_eof,this);
+            return Action.SCHEDULED;
         }
+
+        @Override
+        public void onCompleteFailure(Throwable x)
+        {
+            super.onCompleteFailure(x);
+            _channel.getByteBufferPool().release(_buffer);
+            try
+            {
+                _in.close();
+            }
+            catch (IOException e)
+            {
+                LOG.ignore(e);
+            }
+        }
+
     }
 
     /* ------------------------------------------------------------ */
-    /* 
-     * @see javax.servlet.ServletOutputStream#print(java.lang.String)
+    /** An iterating callback that will take content from a
+     * ReadableByteChannel and write it to the {@link HttpChannel}.
+     * A {@link ByteBuffer} of size {@link HttpOutput#getBufferSize()} is used that will be direct if
+     * {@link HttpChannel#useDirectBuffers()} is true.
+     * This callback is passed to the {@link HttpChannel#write(ByteBuffer, boolean, Callback)} to
+     * be notified as each buffer is written and only once all the input is consumed will the
+     * wrapped {@link Callback#succeeded()} method be called.
      */
-    @Override
-    public void print(String s) throws IOException
+    private class ReadableByteChannelWritingCB extends IteratingNestedCallback
     {
-        write(s.getBytes());
+        private final ReadableByteChannel _in;
+        private final ByteBuffer _buffer;
+        private boolean _eof;
+
+        public ReadableByteChannelWritingCB(ReadableByteChannel in, Callback callback)
+        {
+            super(callback);
+            _in=in;
+            _buffer = _channel.getByteBufferPool().acquire(getBufferSize(), _channel.useDirectBuffers());
+        }
+
+        @Override
+        protected Action process() throws Exception
+        {
+            // Only return if EOF has previously been read and thus
+            // a write done with EOF=true
+            if (_eof)
+            {
+                _in.close();
+                closed();
+                _channel.getByteBufferPool().release(_buffer);
+                return Action.SUCCEEDED;
+            }
+            
+            // Read from stream until buffer full or EOF
+            _buffer.clear();
+            while (_buffer.hasRemaining() && !_eof)
+              _eof = (_in.read(_buffer)) <  0;
+
+            // write what we have
+            _buffer.flip();
+            write(_buffer,_eof,this);
+
+            return Action.SCHEDULED;
+        }
+
+        @Override
+        public void onCompleteFailure(Throwable x)
+        {
+            super.onCompleteFailure(x);
+            _channel.getByteBufferPool().release(_buffer);
+            try
+            {
+                _in.close();
+            }
+            catch (IOException e)
+            {
+                LOG.ignore(e);
+            }
+        }
     }
 }
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpTransport.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpTransport.java
new file mode 100644
index 0000000..ed2b808
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpTransport.java
@@ -0,0 +1,40 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.http.HttpGenerator;
+import org.eclipse.jetty.util.Callback;
+
+public interface HttpTransport
+{    
+    void send(HttpGenerator.ResponseInfo info, ByteBuffer content, boolean lastContent, Callback callback);
+
+    void send(ByteBuffer content, boolean lastContent, Callback callback);
+    
+    void completed();
+    
+    /* ------------------------------------------------------------ */
+    /** Abort transport.
+     * This is called when an error response needs to be sent, but the response is already committed.
+     * Abort to should terminate the transport in a way that can indicate abnormal response to the client. 
+     */
+    void abort();
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpWriter.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpWriter.java
index 44968e1..e669b66 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpWriter.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpWriter.java
@@ -19,63 +19,27 @@
 package org.eclipse.jetty.server;
 
 import java.io.IOException;
-import java.io.OutputStreamWriter;
 import java.io.Writer;
 
-import org.eclipse.jetty.http.AbstractGenerator;
 import org.eclipse.jetty.util.ByteArrayOutputStream2;
-import org.eclipse.jetty.util.StringUtil;
 
-/** OutputWriter.
- * A writer that can wrap a {@link HttpOutput} stream and provide
- * character encodings.
- *
- * The UTF-8 encoding is done by this class and no additional 
- * buffers or Writers are used.
- * The UTF-8 code was inspired by http://javolution.org
+/**
+ * 
  */
-public class HttpWriter extends Writer
+public abstract class HttpWriter extends Writer
 {
     public static final int MAX_OUTPUT_CHARS = 512; 
     
-    private static final int WRITE_CONV = 0;
-    private static final int WRITE_ISO1 = 1;
-    private static final int WRITE_UTF8 = 2;
-    
     final HttpOutput _out;
-    final AbstractGenerator _generator;
-    int _writeMode;
-    int _surrogate;
+    final ByteArrayOutputStream2 _bytes;
+    final char[] _chars;
 
     /* ------------------------------------------------------------ */
     public HttpWriter(HttpOutput out)
     {
         _out=out;
-        _generator=_out._generator;
-        _surrogate=0; // AS lastUTF16CodePoint
-    }
-
-    /* ------------------------------------------------------------ */
-    public void setCharacterEncoding(String encoding)
-    {
-        if (encoding == null || StringUtil.__ISO_8859_1.equalsIgnoreCase(encoding))
-        {
-            _writeMode = WRITE_ISO1;
-        }
-        else if (StringUtil.__UTF8.equalsIgnoreCase(encoding))
-        {
-            _writeMode = WRITE_UTF8;
-        }
-        else
-        {
-            _writeMode = WRITE_CONV;
-            if (_out._characterEncoding == null || !_out._characterEncoding.equalsIgnoreCase(encoding))
-                _out._converter = null; // Set lazily in getConverter()
-        }
-        
-        _out._characterEncoding = encoding;
-        if (_out._bytes==null)
-            _out._bytes = new ByteArrayOutputStream2(MAX_OUTPUT_CHARS);
+        _chars=new char[MAX_OUTPUT_CHARS];
+        _bytes = new ByteArrayOutputStream2(MAX_OUTPUT_CHARS);   
     }
 
     /* ------------------------------------------------------------ */
@@ -103,200 +67,14 @@ public class HttpWriter extends Writer
             length -= MAX_OUTPUT_CHARS;
         }
 
-        if (_out._chars==null)
-        {
-            _out._chars = new char[MAX_OUTPUT_CHARS]; 
-        }
-        char[] chars = _out._chars;
-        s.getChars(offset, offset + length, chars, 0);
-        write(chars, 0, length);
+        s.getChars(offset, offset + length, _chars, 0);
+        write(_chars, 0, length);
     }
 
     /* ------------------------------------------------------------ */
     @Override
     public void write (char[] s,int offset, int length) throws IOException
-    {              
-        HttpOutput out = _out; 
-        
-        while (length > 0)
-        {  
-            out._bytes.reset();
-            int chars = length>MAX_OUTPUT_CHARS?MAX_OUTPUT_CHARS:length;
-            
-            switch (_writeMode)
-            {
-                case WRITE_CONV:
-                {
-                    Writer converter=getConverter();
-                    converter.write(s, offset, chars);
-                    converter.flush();
-                }
-                break;
-
-                case WRITE_ISO1:
-                {
-                    byte[] buffer=out._bytes.getBuf();
-                    int bytes=out._bytes.getCount();
-                    
-                    if (chars>buffer.length-bytes)
-                        chars=buffer.length-bytes;
-
-                    for (int i = 0; i < chars; i++)
-                    {
-                        int c = s[offset+i];
-                        buffer[bytes++]=(byte)(c<256?c:'?'); // ISO-1 and UTF-8 match for 0 - 255
-                    }
-                    if (bytes>=0)
-                        out._bytes.setCount(bytes);
-
-                    break;
-                }
-
-                case WRITE_UTF8:
-                {
-                    byte[] buffer=out._bytes.getBuf();
-                    int bytes=out._bytes.getCount();
-
-                    if (bytes+chars>buffer.length)
-                        chars=buffer.length-bytes;
-
-                    for (int i = 0; i < chars; i++)
-                    {
-                        int code = s[offset+i];
-
-                        // Do we already have a surrogate?
-                        if(_surrogate==0)
-                        {
-                            // No - is this char code a surrogate?
-                            if(Character.isHighSurrogate((char)code))
-                            {
-                                _surrogate=code; // UCS-?
-                                continue;
-                            }                            
-                        }
-                        // else handle a low surrogate
-                        else if(Character.isLowSurrogate((char)code))
-                        {
-                            code = Character.toCodePoint((char)_surrogate, (char)code); // UCS-4
-                        }
-                        // else UCS-2
-                        else
-                        {
-                            code=_surrogate; // UCS-2
-                            _surrogate=0; // USED
-                            i--;
-                        }
-
-                        if ((code & 0xffffff80) == 0) 
-                        {
-                            // 1b
-                            if (bytes>=buffer.length)
-                            {
-                                chars=i;
-                                break;
-                            }
-                            buffer[bytes++]=(byte)(code);
-                        }
-                        else
-                        {
-                            if((code&0xfffff800)==0)
-                            {
-                                // 2b
-                                if (bytes+2>buffer.length)
-                                {
-                                    chars=i;
-                                    break;
-                                }
-                                buffer[bytes++]=(byte)(0xc0|(code>>6));
-                                buffer[bytes++]=(byte)(0x80|(code&0x3f));
-                            }
-                            else if((code&0xffff0000)==0)
-                            {
-                                // 3b
-                                if (bytes+3>buffer.length)
-                                {
-                                    chars=i;
-                                    break;
-                                }
-                                buffer[bytes++]=(byte)(0xe0|(code>>12));
-                                buffer[bytes++]=(byte)(0x80|((code>>6)&0x3f));
-                                buffer[bytes++]=(byte)(0x80|(code&0x3f));
-                            }
-                            else if((code&0xff200000)==0)
-                            {
-                                // 4b
-                                if (bytes+4>buffer.length)
-                                {
-                                    chars=i;
-                                    break;
-                                }
-                                buffer[bytes++]=(byte)(0xf0|(code>>18));
-                                buffer[bytes++]=(byte)(0x80|((code>>12)&0x3f));
-                                buffer[bytes++]=(byte)(0x80|((code>>6)&0x3f));
-                                buffer[bytes++]=(byte)(0x80|(code&0x3f));
-                            }
-                            else if((code&0xf4000000)==0)
-                            {
-                                // 5b
-                                if (bytes+5>buffer.length)
-                                {
-                                    chars=i;
-                                    break;
-                                }
-                                buffer[bytes++]=(byte)(0xf8|(code>>24));
-                                buffer[bytes++]=(byte)(0x80|((code>>18)&0x3f));
-                                buffer[bytes++]=(byte)(0x80|((code>>12)&0x3f));
-                                buffer[bytes++]=(byte)(0x80|((code>>6)&0x3f));
-                                buffer[bytes++]=(byte)(0x80|(code&0x3f));
-                            }
-                            else if((code&0x80000000)==0)
-                            {
-                                // 6b
-                                if (bytes+6>buffer.length)
-                                {
-                                    chars=i;
-                                    break;
-                                }
-                                buffer[bytes++]=(byte)(0xfc|(code>>30));
-                                buffer[bytes++]=(byte)(0x80|((code>>24)&0x3f));
-                                buffer[bytes++]=(byte)(0x80|((code>>18)&0x3f));
-                                buffer[bytes++]=(byte)(0x80|((code>>12)&0x3f));
-                                buffer[bytes++]=(byte)(0x80|((code>>6)&0x3f));
-                                buffer[bytes++]=(byte)(0x80|(code&0x3f));
-                            }
-                            else
-                            {
-                                buffer[bytes++]=(byte)('?');
-                            } 
-
-                            _surrogate=0; // USED
-
-                            if (bytes==buffer.length)
-                            {
-                                chars=i+1;
-                                break;
-                            }
-                        }
-                    }
-                    out._bytes.setCount(bytes);
-                    break;
-                }
-                default:
-                    throw new IllegalStateException();
-            }
-            
-            out._bytes.writeTo(out);
-            length-=chars;
-            offset+=chars;
-        }
+    {         
+        throw new AbstractMethodError();
     }
-    
-    
-    /* ------------------------------------------------------------ */
-    private Writer getConverter() throws IOException
-    {
-        if (_out._converter == null)
-            _out._converter = new OutputStreamWriter(_out._bytes, _out._characterEncoding);
-        return _out._converter;
-    }   
 }
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/InclusiveByteRange.java b/jetty-server/src/main/java/org/eclipse/jetty/server/InclusiveByteRange.java
index 8e48a35..71b5fd5 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/InclusiveByteRange.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/InclusiveByteRange.java
@@ -44,6 +44,9 @@ import org.eclipse.jetty.util.log.Logger;
  * </PRE>
  * 
  * Based on RFC2616 3.12, 14.16, 14.35.1, 14.35.2
+ * <p>
+ * And yes the spec does strangely say that while 10-20, is bytes 10 to 20 and 10- is bytes 10 until the end that -20 IS NOT bytes 0-20, but the last 20 bytes of the content.
+ * 
  * @version $version$
  * 
  */
@@ -78,7 +81,7 @@ public class InclusiveByteRange
      * @param size Size of the resource.
      * @return LazyList of satisfiable ranges
      */
-    public static List satisfiableRanges(Enumeration headers, long size)
+    public static List<InclusiveByteRange> satisfiableRanges(Enumeration<String> headers, long size)
     {
         Object satRanges=null;
         
@@ -86,7 +89,7 @@ public class InclusiveByteRange
     headers:
         while (headers.hasMoreElements())
         {
-            String header = (String) headers.nextElement();
+            String header = headers.nextElement();
             StringTokenizer tok = new StringTokenizer(header,"=,",false);
             String t=null;
             try
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Iso88591HttpWriter.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Iso88591HttpWriter.java
new file mode 100644
index 0000000..e0d7183
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Iso88591HttpWriter.java
@@ -0,0 +1,75 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+
+/**
+ */
+public class Iso88591HttpWriter extends HttpWriter
+{
+    /* ------------------------------------------------------------ */
+    public Iso88591HttpWriter(HttpOutput out)
+    {
+        super(out);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void write (char[] s,int offset, int length) throws IOException
+    {
+        HttpOutput out = _out;
+        if (length==0 && out.isAllContentWritten())
+        {
+            close();
+            return;
+        }
+
+        if (length==1)
+        {
+            int c=s[offset];
+            out.write(c<256?c:'?');
+            return;
+        }
+        
+        while (length > 0)
+        {
+            _bytes.reset();
+            int chars = length>MAX_OUTPUT_CHARS?MAX_OUTPUT_CHARS:length;
+
+            byte[] buffer=_bytes.getBuf();
+            int bytes=_bytes.getCount();
+
+            if (chars>buffer.length-bytes)
+                chars=buffer.length-bytes;
+
+            for (int i = 0; i < chars; i++)
+            {
+                int c = s[offset+i];
+                buffer[bytes++]=(byte)(c<256?c:'?');
+            }
+            if (bytes>=0)
+                _bytes.setCount(bytes);
+
+            _bytes.writeTo(out);
+            length-=chars;
+            offset+=chars;
+        }
+    }
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/LocalConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/LocalConnector.java
index 3bc64d4..ff2e14a 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/LocalConnector.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/LocalConnector.java
@@ -19,158 +19,254 @@
 package org.eclipse.jetty.server;
 
 import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
 
-import org.eclipse.jetty.io.ByteArrayBuffer;
 import org.eclipse.jetty.io.ByteArrayEndPoint;
+import org.eclipse.jetty.io.ByteBufferPool;
 import org.eclipse.jetty.io.Connection;
-import org.eclipse.jetty.util.StringUtil;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.eclipse.jetty.util.thread.Scheduler;
 
 public class LocalConnector extends AbstractConnector
 {
-    private static final Logger LOG = Log.getLogger(LocalConnector.class);
-    private final BlockingQueue<Request> _requests = new LinkedBlockingQueue<Request>();
-    
-    public LocalConnector()
+    private final BlockingQueue<LocalEndPoint> _connects = new LinkedBlockingQueue<>();
+
+
+    public LocalConnector(Server server, Executor executor, Scheduler scheduler, ByteBufferPool pool, int acceptors, ConnectionFactory... factories)
     {
-        setMaxIdleTime(30000);
+        super(server,executor,scheduler,pool,acceptors,factories);
+        setIdleTimeout(30000);
     }
 
-    public Object getConnection()
+    public LocalConnector(Server server)
     {
-        return this;
+        this(server, null, null, null, -1, new HttpConnectionFactory());
     }
 
-    public String getResponses(String requests) throws Exception
+    public LocalConnector(Server server, SslContextFactory sslContextFactory)
     {
-        return getResponses(requests, false);
+        this(server, null, null, null, -1,AbstractConnectionFactory.getFactories(sslContextFactory,new HttpConnectionFactory()));
     }
 
-    public String getResponses(String requests, boolean keepOpen) throws Exception
+    public LocalConnector(Server server, ConnectionFactory connectionFactory)
     {
-        ByteArrayBuffer result = getResponses(new ByteArrayBuffer(requests, StringUtil.__ISO_8859_1), keepOpen);
-        return result==null?null:result.toString(StringUtil.__ISO_8859_1);
+        this(server, null, null, null, -1, connectionFactory);
     }
 
-    public ByteArrayBuffer getResponses(ByteArrayBuffer requestsBuffer, boolean keepOpen) throws Exception
+    public LocalConnector(Server server, ConnectionFactory connectionFactory, SslContextFactory sslContextFactory)
     {
-        CountDownLatch latch = new CountDownLatch(1);
-        Request request = new Request(requestsBuffer, keepOpen, latch);
-        _requests.add(request);
-        latch.await(getMaxIdleTime(),TimeUnit.MILLISECONDS);
-        return request.getResponsesBuffer();
+        this(server, null, null, null, -1, AbstractConnectionFactory.getFactories(sslContextFactory,connectionFactory));
     }
 
     @Override
-    protected void accept(int acceptorID) throws IOException, InterruptedException
+    public Object getTransport()
+    {
+        return this;
+    }
+
+    /** Sends requests and get responses based on thread activity.
+     * Returns all the responses received once the thread activity has
+     * returned to the level it was before the requests.
+     * <p>
+     * This methods waits until the connection is closed or
+     * is idle for 1s before returning the responses.
+     * @param requests the requests
+     * @return the responses
+     * @throws Exception if the requests fail
+     */
+    public String getResponses(String requests) throws Exception
     {
-        Request request = _requests.take();
-        getThreadPool().dispatch(request);
+        return getResponses(requests, 5, TimeUnit.SECONDS);
     }
 
-    public void open() throws IOException
+    /** Sends requests and get responses based on thread activity.
+     * Returns all the responses received once the thread activity has
+     * returned to the level it was before the requests.
+     * <p>
+     * This methods waits until the connection is closed or
+     * an idle period before returning the responses.
+     * @param requests the requests
+     * @param idleFor The time the response stream must be idle for before returning
+     * @param units The units of idleFor
+     * @return the responses
+     * @throws Exception if the requests fail
+     */
+    public String getResponses(String requests,long idleFor,TimeUnit units) throws Exception
     {
+        ByteBuffer result = getResponses(BufferUtil.toBuffer(requests,StandardCharsets.UTF_8),idleFor,units);
+        return result==null?null:BufferUtil.toString(result,StandardCharsets.UTF_8);
     }
 
-    public void close() throws IOException
+    /** Sends requests and get's responses based on thread activity.
+     * Returns all the responses received once the thread activity has
+     * returned to the level it was before the requests.
+     * <p>
+     * This methods waits until the connection is closed or
+     * is idle for 1s before returning the responses.
+     * @param requestsBuffer the requests
+     * @return the responses
+     * @throws Exception if the requests fail
+     */
+    public ByteBuffer getResponses(ByteBuffer requestsBuffer) throws Exception
     {
+        return getResponses(requestsBuffer, 5, TimeUnit.SECONDS);
     }
 
-    public int getLocalPort()
+    /** Sends requests and get's responses based on thread activity.
+     * Returns all the responses received once the thread activity has
+     * returned to the level it was before the requests.
+     * <p>
+     * This methods waits until the connection is closed or
+     * an idle period before returning the responses.
+     * @param requestsBuffer the requests
+     * @param idleFor The time the response stream must be idle for before returning
+     * @param units The units of idleFor
+     * @return the responses
+     * @throws Exception if the requests fail
+     */
+    public ByteBuffer getResponses(ByteBuffer requestsBuffer,long idleFor,TimeUnit units) throws Exception
     {
-        return -1;
+        if (LOG.isDebugEnabled())
+            LOG.debug("requests {}", BufferUtil.toUTF8String(requestsBuffer));
+        LocalEndPoint endp = executeRequest(requestsBuffer);
+        endp.waitUntilClosedOrIdleFor(idleFor,units);
+        ByteBuffer responses = endp.takeOutput();
+        if (endp.isOutputShutdown())
+            endp.close();
+        if (LOG.isDebugEnabled())
+            LOG.debug("responses {}", BufferUtil.toUTF8String(responses));
+        return responses;
     }
 
-    public void executeRequest(String rawRequest) throws IOException
+    /**
+     * Execute a request and return the EndPoint through which
+     * responses can be received.
+     * @param rawRequest the request
+     * @return the local endpoint
+     */
+    public LocalEndPoint executeRequest(String rawRequest)
     {
-        Request request = new Request(new ByteArrayBuffer(rawRequest, "UTF-8"), true, null);
-        _requests.add(request);
+        return executeRequest(BufferUtil.toBuffer(rawRequest, StandardCharsets.UTF_8));
     }
 
-    private class Request implements Runnable
+    private LocalEndPoint executeRequest(ByteBuffer rawRequest)
     {
-        private final ByteArrayBuffer _requestsBuffer;
-        private final boolean _keepOpen;
-        private final CountDownLatch _latch;
-        private volatile ByteArrayBuffer _responsesBuffer;
+        if (!isStarted())
+            throw new IllegalStateException("!STARTED");
+        LocalEndPoint endp = new LocalEndPoint();
+        endp.setInput(rawRequest);
+        _connects.add(endp);
+        return endp;
+    }
 
-        private Request(ByteArrayBuffer requestsBuffer, boolean keepOpen, CountDownLatch latch)
+    @Override
+    protected void accept(int acceptorID) throws IOException, InterruptedException
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("accepting {}", acceptorID);
+        LocalEndPoint endPoint = _connects.take();
+        endPoint.onOpen();
+        onEndPointOpened(endPoint);
+
+        Connection connection = getDefaultConnectionFactory().newConnection(this, endPoint);
+        endPoint.setConnection(connection);
+
+        connection.onOpen();
+    }
+
+    public class LocalEndPoint extends ByteArrayEndPoint
+    {
+        private final CountDownLatch _closed = new CountDownLatch(1);
+
+        public LocalEndPoint()
         {
-            _requestsBuffer = requestsBuffer;
-            _keepOpen = keepOpen;
-            _latch = latch;
+            super(getScheduler(), LocalConnector.this.getIdleTimeout());
+            setGrowOutput(true);
         }
 
-        public void run()
+        public void addInput(String s)
         {
-            try
+            // TODO this is a busy wait
+            while(getIn()==null || BufferUtil.hasContent(getIn()))
+                Thread.yield();
+            setInput(BufferUtil.toBuffer(s, StandardCharsets.UTF_8));
+        }
+
+        @Override
+        public void close()
+        {
+            boolean wasOpen=isOpen();
+            super.close();
+            if (wasOpen)
             {
-                ByteArrayEndPoint endPoint = new ByteArrayEndPoint(_requestsBuffer.asArray(), 1024)
-                {
-                    @Override
-                    public void setConnection(Connection connection)
-                    {
-                        if (getConnection()!=null && connection!=getConnection())
-                            connectionUpgraded(getConnection(),connection);
-                        super.setConnection(connection);
-                    }
-                };
+//                connectionClosed(getConnection());
+                getConnection().onClose();
+                onClose();
+            }
+        }
 
-                endPoint.setGrowOutput(true);
-                AbstractHttpConnection connection = new BlockingHttpConnection(LocalConnector.this, endPoint, getServer());
-                endPoint.setConnection(connection);
-                connectionOpened(connection);
+        @Override
+        public void onClose()
+        {
+            LocalConnector.this.onEndPointClosed(this);
+            super.onClose();
+            _closed.countDown();
+        }
 
-                boolean leaveOpen = _keepOpen;
+        @Override
+        public void shutdownOutput()
+        {
+            super.shutdownOutput();
+            close();
+        }
+
+        public void waitUntilClosed()
+        {
+            while (isOpen())
+            {
                 try
                 {
-                    while (endPoint.getIn().length() > 0 && endPoint.isOpen())
-                    {
-                        while (true)
-                        {
-                            final Connection con = endPoint.getConnection();
-                            final Connection next = con.handle();
-                            if (next!=con)
-                            {  
-                                endPoint.setConnection(next);
-                                continue;
-                            }
-                            break;
-                        }
-                    }
+                    if (!_closed.await(10,TimeUnit.SECONDS))
+                        break;
                 }
-                catch (IOException x)
+                catch(Exception e)
                 {
-                    LOG.debug(x);
-                    leaveOpen = false;
+                    LOG.warn(e);
                 }
-                catch (Exception x)
+            }
+        }
+
+        public void waitUntilClosedOrIdleFor(long idleFor,TimeUnit units)
+        {
+            Thread.yield();
+            int size=getOutput().remaining();
+            while (isOpen())
+            {
+                try
                 {
-                    LOG.warn(x);
-                    leaveOpen = false;
+                    if (!_closed.await(idleFor,units))
+                    {
+                        if (size==getOutput().remaining())
+                        {
+                            if (LOG.isDebugEnabled())
+                                LOG.debug("idle for {} {}",idleFor,units);
+                            return;
+                        }
+                        size=getOutput().remaining();
+                    }
                 }
-                finally
+                catch(Exception e)
                 {
-                    if (!leaveOpen)
-                        connectionClosed(connection);
-                    _responsesBuffer = endPoint.getOut();
+                    LOG.warn(e);
                 }
             }
-            finally
-            {
-                if (_latch != null)
-                    _latch.countDown();
-            }
-        }
-
-        public ByteArrayBuffer getResponsesBuffer()
-        {
-            return _responsesBuffer;
         }
     }
 }
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/LowResourceMonitor.java b/jetty-server/src/main/java/org/eclipse/jetty/server/LowResourceMonitor.java
new file mode 100644
index 0000000..028e0a0
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/LowResourceMonitor.java
@@ -0,0 +1,350 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.annotation.Name;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
+import org.eclipse.jetty.util.thread.Scheduler;
+import org.eclipse.jetty.util.thread.ThreadPool;
+
+
+/* ------------------------------------------------------------ */
+/** A monitor for low resources
+ * <p>An instance of this class will monitor all the connectors of a server (or a set of connectors
+ * configured with {@link #setMonitoredConnectors(Collection)}) for a low resources state.
+ * Low resources can be detected by:<ul>
+ * <li>{@link ThreadPool#isLowOnThreads()} if {@link Connector#getExecutor()} is
+ * an instance of {@link ThreadPool} and {@link #setMonitorThreads(boolean)} is true.<li>
+ * <li>If {@link #setMaxMemory(long)} is non zero then low resources is detected if the JVMs
+ * {@link Runtime} instance has {@link Runtime#totalMemory()} minus {@link Runtime#freeMemory()}
+ * greater than {@link #getMaxMemory()}</li>
+ * <li>If {@link #setMaxConnections(int)} is non zero then low resources is dected if the total number
+ * of connections exceeds {@link #getMaxConnections()}</li>
+ * </ul>
+ * </p>
+ * <p>Once low resources state is detected, the cause is logged and all existing connections returned
+ * by {@link Connector#getConnectedEndPoints()} have {@link EndPoint#setIdleTimeout(long)} set
+ * to {@link #getLowResourcesIdleTimeout()}.  New connections are not affected, however if the low
+ * resources state persists for more than {@link #getMaxLowResourcesTime()}, then the
+ * {@link #getLowResourcesIdleTimeout()} to all connections again.  Once the low resources state is
+ * cleared, the idle timeout is reset to the connector default given by {@link Connector#getIdleTimeout()}.
+ * </p>
+ */
+ at ManagedObject ("Monitor for low resource conditions and activate a low resource mode if detected")
+public class LowResourceMonitor extends AbstractLifeCycle
+{
+    private static final Logger LOG = Log.getLogger(LowResourceMonitor.class);
+    private final Server _server;
+    private Scheduler _scheduler;
+    private Connector[] _monitoredConnectors;
+    private int _period=1000;
+    private int _maxConnections;
+    private long _maxMemory;
+    private int _lowResourcesIdleTimeout=1000;
+    private int _maxLowResourcesTime=0;
+    private boolean _monitorThreads=true;
+    private final AtomicBoolean _low = new AtomicBoolean();
+    private String _cause;
+    private String _reasons;
+    private long _lowStarted;
+
+
+    private final Runnable _monitor = new Runnable()
+    {
+        @Override
+        public void run()
+        {
+            if (isRunning())
+            {
+                monitor();
+                _scheduler.schedule(_monitor,_period,TimeUnit.MILLISECONDS);
+            }
+        }
+    };
+
+    public LowResourceMonitor(@Name("server") Server server)
+    {
+        _server=server;
+    }
+
+    @ManagedAttribute("Are the monitored connectors low on resources?")
+    public boolean isLowOnResources()
+    {
+        return _low.get();
+    }
+
+    @ManagedAttribute("The reason(s) the monitored connectors are low on resources")
+    public String getLowResourcesReasons()
+    {
+        return _reasons;
+    }
+
+    @ManagedAttribute("Get the timestamp in ms since epoch that low resources state started")
+    public long getLowResourcesStarted()
+    {
+        return _lowStarted;
+    }
+
+    @ManagedAttribute("The monitored connectors. If null then all server connectors are monitored")
+    public Collection<Connector> getMonitoredConnectors()
+    {
+        if (_monitoredConnectors==null)
+            return Collections.emptyList();
+        return Arrays.asList(_monitoredConnectors);
+    }
+
+    /**
+     * @param monitoredConnectors The collections of Connectors that should be monitored for low resources.
+     */
+    public void setMonitoredConnectors(Collection<Connector> monitoredConnectors)
+    {
+        if (monitoredConnectors==null || monitoredConnectors.size()==0)
+            _monitoredConnectors=null;
+        else
+            _monitoredConnectors = monitoredConnectors.toArray(new Connector[monitoredConnectors.size()]);
+    }
+
+    @ManagedAttribute("The monitor period in ms")
+    public int getPeriod()
+    {
+        return _period;
+    }
+
+    /**
+     * @param periodMS The period in ms to monitor for low resources
+     */
+    public void setPeriod(int periodMS)
+    {
+        _period = periodMS;
+    }
+
+    @ManagedAttribute("True if low available threads status is monitored")
+    public boolean getMonitorThreads()
+    {
+        return _monitorThreads;
+    }
+
+    /**
+     * @param monitorThreads If true, check connectors executors to see if they are
+     * {@link ThreadPool} instances that are low on threads.
+     */
+    public void setMonitorThreads(boolean monitorThreads)
+    {
+        _monitorThreads = monitorThreads;
+    }
+
+    @ManagedAttribute("The maximum connections allowed for the monitored connectors before low resource handling is activated")
+    public int getMaxConnections()
+    {
+        return _maxConnections;
+    }
+
+    /**
+     * @param maxConnections The maximum connections before low resources state is triggered
+     */
+    public void setMaxConnections(int maxConnections)
+    {
+        _maxConnections = maxConnections;
+    }
+
+    @ManagedAttribute("The maximum memory (in bytes) that can be used before low resources is triggered.  Memory used is calculated as (totalMemory-freeMemory).")
+    public long getMaxMemory()
+    {
+        return _maxMemory;
+    }
+
+    /**
+     * @param maxMemoryBytes The maximum memory in bytes in use before low resources is triggered.
+     */
+    public void setMaxMemory(long maxMemoryBytes)
+    {
+        _maxMemory = maxMemoryBytes;
+    }
+
+    @ManagedAttribute("The idletimeout in ms to apply to all existing connections when low resources is detected")
+    public int getLowResourcesIdleTimeout()
+    {
+        return _lowResourcesIdleTimeout;
+    }
+
+    /**
+     * @param lowResourcesIdleTimeoutMS The timeout in ms to apply to EndPoints when in the low resources state.
+     */
+    public void setLowResourcesIdleTimeout(int lowResourcesIdleTimeoutMS)
+    {
+        _lowResourcesIdleTimeout = lowResourcesIdleTimeoutMS;
+    }
+
+    @ManagedAttribute("The maximum time in ms that low resources condition can persist before lowResourcesIdleTimeout is applied to new connections as well as existing connections")
+    public int getMaxLowResourcesTime()
+    {
+        return _maxLowResourcesTime;
+    }
+
+    /**
+     * @param maxLowResourcesTimeMS The time in milliseconds that a low resource state can persist before the low resource idle timeout is reapplied to all connections
+     */
+    public void setMaxLowResourcesTime(int maxLowResourcesTimeMS)
+    {
+        _maxLowResourcesTime = maxLowResourcesTimeMS;
+    }
+
+    @Override
+    protected void doStart() throws Exception
+    {
+        _scheduler = _server.getBean(Scheduler.class);
+
+        if (_scheduler==null)
+        {
+            _scheduler=new LRMScheduler();
+            _scheduler.start();
+        }
+        super.doStart();
+
+        _scheduler.schedule(_monitor,_period,TimeUnit.MILLISECONDS);
+    }
+
+    @Override
+    protected void doStop() throws Exception
+    {
+        if (_scheduler instanceof LRMScheduler)
+            _scheduler.stop();
+        super.doStop();
+    }
+
+    protected Connector[] getMonitoredOrServerConnectors()
+    {
+        if (_monitoredConnectors!=null && _monitoredConnectors.length>0)
+            return _monitoredConnectors;
+        return _server.getConnectors();
+    }
+
+    protected void monitor()
+    {
+        String reasons=null;
+        String cause="";
+        int connections=0;
+
+        for(Connector connector : getMonitoredOrServerConnectors())
+        {
+            connections+=connector.getConnectedEndPoints().size();
+
+            Executor executor = connector.getExecutor();
+            if (executor instanceof ThreadPool)
+            {
+                ThreadPool threadpool=(ThreadPool) executor;
+                if (_monitorThreads && threadpool.isLowOnThreads())
+                {
+                    reasons=low(reasons,"Low on threads: "+threadpool);
+                    cause+="T";
+                }
+            }
+        }
+
+        if (_maxConnections>0 && connections>_maxConnections)
+        {
+            reasons=low(reasons,"Max Connections exceeded: "+connections+">"+_maxConnections);
+            cause+="C";
+        }
+
+        long memory=Runtime.getRuntime().totalMemory()-Runtime.getRuntime().freeMemory();
+        if (_maxMemory>0 && memory>_maxMemory)
+        {
+            reasons=low(reasons,"Max memory exceeded: "+memory+">"+_maxMemory);
+            cause+="M";
+        }
+
+
+        if (reasons!=null)
+        {
+            // Log the reasons if there is any change in the cause
+            if (!cause.equals(_cause))
+            {
+                LOG.warn("Low Resources: {}",reasons);
+                _cause=cause;
+            }
+
+            // Enter low resources state?
+            if (_low.compareAndSet(false,true))
+            {
+                _reasons=reasons;
+                _lowStarted=System.currentTimeMillis();
+                setLowResources();
+            }
+
+            // Too long in low resources state?
+            if (_maxLowResourcesTime>0 && (System.currentTimeMillis()-_lowStarted)>_maxLowResourcesTime)
+                setLowResources();
+        }
+        else
+        {
+            if (_low.compareAndSet(true,false))
+            {
+                LOG.info("Low Resources cleared");
+                _reasons=null;
+                _lowStarted=0;
+                _cause=null;
+                clearLowResources();
+            }
+        }
+    }
+
+    protected void setLowResources()
+    {
+        for(Connector connector : getMonitoredOrServerConnectors())
+        {
+            for (EndPoint endPoint : connector.getConnectedEndPoints())
+                endPoint.setIdleTimeout(_lowResourcesIdleTimeout);
+        }
+    }
+
+    protected void clearLowResources()
+    {
+        for(Connector connector : getMonitoredOrServerConnectors())
+        {
+            for (EndPoint endPoint : connector.getConnectedEndPoints())
+                endPoint.setIdleTimeout(connector.getIdleTimeout());
+        }
+    }
+
+    private String low(String reasons, String newReason)
+    {
+        if (reasons==null)
+            return newReason;
+        return reasons+", "+newReason;
+    }
+
+
+    private static class LRMScheduler extends ScheduledExecutorScheduler
+    {
+    }
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/NCSARequestLog.java b/jetty-server/src/main/java/org/eclipse/jetty/server/NCSARequestLog.java
index 335f709..c8ba015 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/NCSARequestLog.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/NCSARequestLog.java
@@ -22,19 +22,12 @@ import java.io.IOException;
 import java.io.OutputStream;
 import java.io.OutputStreamWriter;
 import java.io.Writer;
-import java.util.Locale;
 import java.util.TimeZone;
 
-import javax.servlet.http.Cookie;
-
-import org.eclipse.jetty.http.HttpHeaders;
-import org.eclipse.jetty.http.PathMap;
-import org.eclipse.jetty.util.DateCache;
 import org.eclipse.jetty.util.RolloverFileOutputStream;
 import org.eclipse.jetty.util.StringUtil;
-import org.eclipse.jetty.util.component.AbstractLifeCycle;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
 
 /**
  * This {@link RequestLog} implementation outputs logs in the pseudo-standard
@@ -43,45 +36,17 @@ import org.eclipse.jetty.util.log.Logger;
  * Format (single log format). This log format can be output by most web
  * servers, and almost all web log analysis software can understand these
  * formats.
- *
- * @org.apache.xbean.XBean element="ncsaLog"
- */
-
-/* ------------------------------------------------------------ */
-/**
  */
-public class NCSARequestLog extends AbstractLifeCycle implements RequestLog
+ at ManagedObject("NCSA standard format request log")
+public class NCSARequestLog extends AbstractNCSARequestLog implements RequestLog
 {
-    private static final Logger LOG = Log.getLogger(NCSARequestLog.class);
-    private static ThreadLocal<StringBuilder> _buffers = new ThreadLocal<StringBuilder>()
-            {
-                @Override
-                protected StringBuilder initialValue()
-                {
-                    return new StringBuilder(256);
-                }
-            };
-
     private String _filename;
-    private boolean _extended;
     private boolean _append;
     private int _retainDays;
     private boolean _closeOut;
-    private boolean _preferProxiedForAddress;
-    private String _logDateFormat = "dd/MMM/yyyy:HH:mm:ss Z";
     private String _filenameDateFormat = null;
-    private Locale _logLocale = Locale.getDefault();
-    private String _logTimeZone = "GMT";
-    private String[] _ignorePaths;
-    private boolean _logLatency = false;
-    private boolean _logCookies = false;
-    private boolean _logServer = false;
-    private boolean _logDispatch = false;
-
     private transient OutputStream _out;
     private transient OutputStream _fileOut;
-    private transient DateCache _logDateCache;
-    private transient PathMap _ignorePathMap;
     private transient Writer _writer;
 
     /* ------------------------------------------------------------ */
@@ -90,7 +55,7 @@ public class NCSARequestLog extends AbstractLifeCycle implements RequestLog
      */
     public NCSARequestLog()
     {
-        _extended = true;
+        setExtended(true);
         _append = true;
         _retainDays = 31;
     }
@@ -98,14 +63,14 @@ public class NCSARequestLog extends AbstractLifeCycle implements RequestLog
     /* ------------------------------------------------------------ */
     /**
      * Create request log object with specified output file name.
-     * 
+     *
      * @param filename the file name for the request log.
      *                 This may be in the format expected
      *                 by {@link RolloverFileOutputStream}
      */
     public NCSARequestLog(String filename)
     {
-        _extended = true;
+        setExtended(true);
         _append = true;
         _retainDays = 31;
         setFilename(filename);
@@ -116,9 +81,9 @@ public class NCSARequestLog extends AbstractLifeCycle implements RequestLog
      * Set the output file name of the request log.
      * The file name may be in the format expected by
      * {@link RolloverFileOutputStream}.
-     * 
+     *
      * @param filename file name of the request log
-     *                
+     *
      */
     public void setFilename(String filename)
     {
@@ -134,20 +99,21 @@ public class NCSARequestLog extends AbstractLifeCycle implements RequestLog
     /* ------------------------------------------------------------ */
     /**
      * Retrieve the output file name of the request log.
-     * 
+     *
      * @return file name of the request log
      */
+    @ManagedAttribute("file of log")
     public String getFilename()
     {
         return _filename;
     }
-
+    
     /* ------------------------------------------------------------ */
     /**
      * Retrieve the file name of the request log with the expanded
      * date wildcard if the output is written to the disk using
      * {@link RolloverFileOutputStream}.
-     * 
+     *
      * @return file name of the request log, or null if not applicable
      */
     public String getDatedFilename()
@@ -158,76 +124,16 @@ public class NCSARequestLog extends AbstractLifeCycle implements RequestLog
     }
 
     /* ------------------------------------------------------------ */
-    /**
-     * Set the timestamp format for request log entries in the file.
-     * If this is not set, the pre-formated request timestamp is used.
-     * 
-     * @param format timestamp format string 
-     */
-    public void setLogDateFormat(String format)
-    {
-        _logDateFormat = format;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Retrieve the timestamp format string for request log entries.
-     * 
-     * @return timestamp format string.
-     */
-    public String getLogDateFormat()
-    {
-        return _logDateFormat;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Set the locale of the request log.
-     * 
-     * @param logLocale locale object
-     */
-    public void setLogLocale(Locale logLocale)
-    {
-        _logLocale = logLocale;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Retrieve the locale of the request log.
-     * 
-     * @return locale object
-     */
-    public Locale getLogLocale()
-    {
-        return _logLocale;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Set the timezone of the request log.
-     * 
-     * @param tz timezone string
-     */
-    public void setLogTimeZone(String tz)
-    {
-        _logTimeZone = tz;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Retrieve the timezone of the request log.
-     * 
-     * @return timezone string
-     */
-    public String getLogTimeZone()
+    @Override
+    protected boolean isEnabled()
     {
-        return _logTimeZone;
+        return (_fileOut != null);
     }
 
     /* ------------------------------------------------------------ */
     /**
      * Set the number of days before rotated log files are deleted.
-     * 
+     *
      * @param retainDays number of days to keep a log file
      */
     public void setRetainDays(int retainDays)
@@ -238,9 +144,10 @@ public class NCSARequestLog extends AbstractLifeCycle implements RequestLog
     /* ------------------------------------------------------------ */
     /**
      * Retrieve the number of days before rotated log files are deleted.
-     * 
+     *
      * @return number of days to keep a log file
      */
+    @ManagedAttribute("number of days that log files are kept")
     public int getRetainDays()
     {
         return _retainDays;
@@ -248,31 +155,8 @@ public class NCSARequestLog extends AbstractLifeCycle implements RequestLog
 
     /* ------------------------------------------------------------ */
     /**
-     * Set the extended request log format flag.
-     * 
-     * @param extended true - log the extended request information,
-     *                 false - do not log the extended request information
-     */
-    public void setExtended(boolean extended)
-    {
-        _extended = extended;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Retrieve the extended request log format flag.
-     * 
-     * @return value of the flag
-     */
-    public boolean isExtended()
-    {
-        return _extended;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
      * Set append to log flag.
-     * 
+     *
      * @param append true - request log file will be appended after restart,
      *               false - request log file will be overwritten after restart
      */
@@ -284,9 +168,10 @@ public class NCSARequestLog extends AbstractLifeCycle implements RequestLog
     /* ------------------------------------------------------------ */
     /**
      * Retrieve append to log flag.
-     * 
+     *
      * @return value of the flag
      */
+    @ManagedAttribute("existing log files are appends to the new one")
     public boolean isAppend()
     {
         return _append;
@@ -294,124 +179,9 @@ public class NCSARequestLog extends AbstractLifeCycle implements RequestLog
 
     /* ------------------------------------------------------------ */
     /**
-     * Set request paths that will not be logged.
-     * 
-     * @param ignorePaths array of request paths
-     */
-    public void setIgnorePaths(String[] ignorePaths)
-    {
-        _ignorePaths = ignorePaths;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Retrieve the request paths that will not be logged.
-     * 
-     * @return array of request paths
-     */
-    public String[] getIgnorePaths()
-    {
-        return _ignorePaths;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Controls logging of the request cookies.
-     * 
-     * @param logCookies true - values of request cookies will be logged,
-     *                   false - values of request cookies will not be logged
-     */
-    public void setLogCookies(boolean logCookies)
-    {
-        _logCookies = logCookies;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Retrieve log cookies flag
-     * 
-     * @return value of the flag
-     */
-    public boolean getLogCookies()
-    {
-        return _logCookies;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Controls logging of the request hostname.
-     * 
-     * @param logServer true - request hostname will be logged,
-     *                  false - request hostname will not be logged
-     */
-    public void setLogServer(boolean logServer)
-    {
-        _logServer = logServer;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Retrieve log hostname flag.
-     * 
-     * @return value of the flag
-     */
-    public boolean getLogServer()
-    {
-        return _logServer;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Controls logging of request processing time.
-     * 
-     * @param logLatency true - request processing time will be logged
-     *                   false - request processing time will not be logged
-     */
-    public void setLogLatency(boolean logLatency)
-    {
-        _logLatency = logLatency;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Retrieve log request processing time flag.
-     * 
-     * @return value of the flag
-     */
-    public boolean getLogLatency()
-    {
-        return _logLatency;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Controls whether the actual IP address of the connection or
-     * the IP address from the X-Forwarded-For header will be logged.
-     * 
-     * @param preferProxiedForAddress true - IP address from header will be logged,
-     *                                false - IP address from the connection will be logged
-     */
-    public void setPreferProxiedForAddress(boolean preferProxiedForAddress)
-    {
-        _preferProxiedForAddress = preferProxiedForAddress;
-    }
-    
-    /* ------------------------------------------------------------ */
-    /**
-     * Retrieved log X-Forwarded-For IP address flag.
-     * 
-     * @return value of the flag
-     */
-    public boolean getPreferProxiedForAddress()
-    {
-        return _preferProxiedForAddress;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
      * Set the log file name date format.
      * @see RolloverFileOutputStream#RolloverFileOutputStream(String, boolean, int, TimeZone, String, String)
-     * 
+     *
      * @param logFileDateFormat format string that is passed to {@link RolloverFileOutputStream}
      */
     public void setFilenameDateFormat(String logFileDateFormat)
@@ -422,7 +192,7 @@ public class NCSARequestLog extends AbstractLifeCycle implements RequestLog
     /* ------------------------------------------------------------ */
     /**
      * Retrieve the file name date format string.
-     * 
+     *
      * @return the log File Date Format
      */
     public String getFilenameDateFormat()
@@ -431,236 +201,31 @@ public class NCSARequestLog extends AbstractLifeCycle implements RequestLog
     }
 
     /* ------------------------------------------------------------ */
-    /** 
-     * Controls logging of the request dispatch time
-     * 
-     * @param value true - request dispatch time will be logged
-     *              false - request dispatch time will not be logged
-     */
-    public void setLogDispatch(boolean value)
-    {
-        _logDispatch = value;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Retrieve request dispatch time logging flag
-     * 
-     * @return value of the flag
-     */
-    public boolean isLogDispatch()
-    {
-        return _logDispatch;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Writes the request and response information to the output stream.
-     * 
-     * @see org.eclipse.jetty.server.RequestLog#log(org.eclipse.jetty.server.Request, org.eclipse.jetty.server.Response)
-     */
-    public void log(Request request, Response response)
-    {
-        try
-        {
-            if (_ignorePathMap != null && _ignorePathMap.getMatch(request.getRequestURI()) != null)
-                return;
-
-            if (_fileOut == null)
-                return;
-
-            StringBuilder buf= _buffers.get();
-            buf.setLength(0);
-
-            if (_logServer)
-            {
-                buf.append(request.getServerName());
-                buf.append(' ');
-            }
-
-            String addr = null;
-            if (_preferProxiedForAddress)
-            {
-                addr = request.getHeader(HttpHeaders.X_FORWARDED_FOR);
-            }
-
-            if (addr == null)
-                addr = request.getRemoteAddr();
-
-            buf.append(addr);
-            buf.append(" - ");
-            Authentication authentication=request.getAuthentication();
-            if (authentication instanceof Authentication.User)
-                buf.append(((Authentication.User)authentication).getUserIdentity().getUserPrincipal().getName());
-            else
-                buf.append(" - ");
-
-            buf.append(" [");
-            if (_logDateCache != null)
-                buf.append(_logDateCache.format(request.getTimeStamp()));
-            else
-                buf.append(request.getTimeStampBuffer().toString());
-
-            buf.append("] \"");
-            buf.append(request.getMethod());
-            buf.append(' ');
-            buf.append(request.getUri().toString());
-            buf.append(' ');
-            buf.append(request.getProtocol());
-            buf.append("\" ");
-            if (request.getAsyncContinuation().isInitial())
-            {
-                int status = response.getStatus();
-                if (status <= 0)
-                    status = 404;
-                buf.append((char)('0' + ((status / 100) % 10)));
-                buf.append((char)('0' + ((status / 10) % 10)));
-                buf.append((char)('0' + (status % 10)));
-            }
-            else
-                buf.append("Async");
-
-            long responseLength = response.getContentCount();
-            if (responseLength >= 0)
-            {
-                buf.append(' ');
-                if (responseLength > 99999)
-                    buf.append(responseLength);
-                else
-                {
-                    if (responseLength > 9999)
-                        buf.append((char)('0' + ((responseLength / 10000) % 10)));
-                    if (responseLength > 999)
-                        buf.append((char)('0' + ((responseLength / 1000) % 10)));
-                    if (responseLength > 99)
-                        buf.append((char)('0' + ((responseLength / 100) % 10)));
-                    if (responseLength > 9)
-                        buf.append((char)('0' + ((responseLength / 10) % 10)));
-                    buf.append((char)('0' + (responseLength) % 10));
-                }
-                buf.append(' ');
-            }
-            else
-                buf.append(" - ");
-
-            
-            if (_extended)
-                logExtended(request, response, buf);
-
-            if (_logCookies)
-            {
-                Cookie[] cookies = request.getCookies();
-                if (cookies == null || cookies.length == 0)
-                    buf.append(" -");
-                else
-                {
-                    buf.append(" \"");
-                    for (int i = 0; i < cookies.length; i++)
-                    {
-                        if (i != 0)
-                            buf.append(';');
-                        buf.append(cookies[i].getName());
-                        buf.append('=');
-                        buf.append(cookies[i].getValue());
-                    }
-                    buf.append('\"');
-                }
-            }
-
-            if (_logDispatch || _logLatency)
-            {
-                long now = System.currentTimeMillis();
-
-                if (_logDispatch)
-                {   
-                    long d = request.getDispatchTime();
-                    buf.append(' ');
-                    buf.append(now - (d==0 ? request.getTimeStamp():d));
-                }
-
-                if (_logLatency)
-                {
-                    buf.append(' ');
-                    buf.append(now - request.getTimeStamp());
-                }
-            }
-
-            buf.append(StringUtil.__LINE_SEPARATOR);
-            
-            String log = buf.toString();
-            write(log);
-        }
-        catch (IOException e)
-        {
-            LOG.warn(e);
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    protected void write(String log) throws IOException 
+    @Override
+    public void write(String requestEntry) throws IOException
     {
         synchronized(this)
         {
             if (_writer==null)
                 return;
-            _writer.write(log);
+            _writer.write(requestEntry);
+            _writer.write(StringUtil.__LINE_SEPARATOR);
             _writer.flush();
         }
     }
-
     
     /* ------------------------------------------------------------ */
     /**
-     * Writes extended request and response information to the output stream.
-     * 
-     * @param request request object
-     * @param response response object
-     * @param b StringBuilder to write to
-     * @throws IOException
-     */
-    protected void logExtended(Request request,
-                               Response response,
-                               StringBuilder b) throws IOException
-    {
-        String referer = request.getHeader(HttpHeaders.REFERER);
-        if (referer == null)
-            b.append("\"-\" ");
-        else
-        {
-            b.append('"');
-            b.append(referer);
-            b.append("\" ");
-        }
-
-        String agent = request.getHeader(HttpHeaders.USER_AGENT);
-        if (agent == null)
-            b.append("\"-\" ");
-        else
-        {
-            b.append('"');
-            b.append(agent);
-            b.append('"');
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
      * Set up request logging and open log file.
-     * 
+     *
      * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart()
      */
     @Override
     protected synchronized void doStart() throws Exception
     {
-        if (_logDateFormat != null)
-        {
-            _logDateCache = new DateCache(_logDateFormat,_logLocale);
-            _logDateCache.setTimeZoneID(_logTimeZone);
-        }
-
         if (_filename != null)
         {
-            _fileOut = new RolloverFileOutputStream(_filename,_append,_retainDays,TimeZone.getTimeZone(_logTimeZone),_filenameDateFormat,null);
+            _fileOut = new RolloverFileOutputStream(_filename,_append,_retainDays,TimeZone.getTimeZone(getLogTimeZone()),_filenameDateFormat,null);
             _closeOut = true;
             LOG.info("Opened " + getDatedFilename());
         }
@@ -669,15 +234,6 @@ public class NCSARequestLog extends AbstractLifeCycle implements RequestLog
 
         _out = _fileOut;
 
-        if (_ignorePaths != null && _ignorePaths.length > 0)
-        {
-            _ignorePathMap = new PathMap();
-            for (int i = 0; i < _ignorePaths.length; i++)
-                _ignorePathMap.put(_ignorePaths[i],_ignorePaths[i]);
-        }
-        else
-            _ignorePathMap = null;
-
         synchronized(this)
         {
             _writer = new OutputStreamWriter(_out);
@@ -688,7 +244,7 @@ public class NCSARequestLog extends AbstractLifeCycle implements RequestLog
     /* ------------------------------------------------------------ */
     /**
      * Close the log file and perform cleanup.
-     * 
+     *
      * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStop()
      */
     @Override
@@ -719,7 +275,6 @@ public class NCSARequestLog extends AbstractLifeCycle implements RequestLog
             _out = null;
             _fileOut = null;
             _closeOut = false;
-            _logDateCache = null;
             _writer = null;
         }
     }
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/NegotiatingServerConnection.java b/jetty-server/src/main/java/org/eclipse/jetty/server/NegotiatingServerConnection.java
new file mode 100644
index 0000000..9757ac8
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/NegotiatingServerConnection.java
@@ -0,0 +1,162 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.List;
+
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLEngineResult;
+
+import org.eclipse.jetty.io.AbstractConnection;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public abstract class NegotiatingServerConnection extends AbstractConnection
+{
+    private static final Logger LOG = Log.getLogger(NegotiatingServerConnection.class);
+
+    private final Connector connector;
+    private final SSLEngine engine;
+    private final List<String> protocols;
+    private final String defaultProtocol;
+    private String protocol; // No need to be volatile: it is modified and read by the same thread
+
+    protected NegotiatingServerConnection(Connector connector, EndPoint endPoint, SSLEngine engine, List<String> protocols, String defaultProtocol)
+    {
+        super(endPoint, connector.getExecutor());
+        this.connector = connector;
+        this.protocols = protocols;
+        this.defaultProtocol = defaultProtocol;
+        this.engine = engine;
+    }
+
+    protected List<String> getProtocols()
+    {
+        return protocols;
+    }
+
+    protected String getDefaultProtocol()
+    {
+        return defaultProtocol;
+    }
+
+    protected SSLEngine getSSLEngine()
+    {
+        return engine;
+    }
+
+    protected String getProtocol()
+    {
+        return protocol;
+    }
+
+    protected void setProtocol(String protocol)
+    {
+        this.protocol = protocol;
+    }
+
+    @Override
+    public void onOpen()
+    {
+        super.onOpen();
+        fillInterested();
+    }
+
+    @Override
+    public void onFillable()
+    {
+        int filled = fill();
+
+        if (filled == 0)
+        {
+            if (protocol == null)
+            {
+                if (engine.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING)
+                {
+                    // Here the SSL handshake is finished, but the protocol has not been negotiated.
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("{} could not negotiate protocol, SSLEngine: {}", this, engine);
+                    close();
+                }
+                else
+                {
+                    // Here the SSL handshake is not finished yet but we filled 0 bytes,
+                    // so we need to read more.
+                    fillInterested();
+                }
+            }
+            else
+            {
+                ConnectionFactory connectionFactory = connector.getConnectionFactory(protocol);
+                if (connectionFactory == null)
+                {
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("{} application selected protocol '{}', but no correspondent {} has been configured",
+                            this, protocol, ConnectionFactory.class.getName());
+                    close();
+                }
+                else
+                {
+                    EndPoint endPoint = getEndPoint();
+                    Connection newConnection = connectionFactory.newConnection(connector, endPoint);
+                    endPoint.upgrade(newConnection);
+                }
+            }
+        }
+        else if (filled < 0)
+        {
+            // Something went bad, we need to close.
+            if (LOG.isDebugEnabled())
+                LOG.debug("{} closing on client close", this);
+            close();
+        }
+        else
+        {
+            // Must never happen, since we fill using an empty buffer
+            throw new IllegalStateException();
+        }
+    }
+
+    private int fill()
+    {
+        try
+        {
+            return getEndPoint().fill(BufferUtil.EMPTY_BUFFER);
+        }
+        catch (IOException x)
+        {
+            LOG.debug(x);
+            close();
+            return -1;
+        }
+    }
+
+    @Override
+    public void close()
+    {
+        // Gentler close for SSL.
+        getEndPoint().shutdownOutput();
+        super.close();
+    }
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/NegotiatingServerConnectionFactory.java b/jetty-server/src/main/java/org/eclipse/jetty/server/NegotiatingServerConnectionFactory.java
new file mode 100644
index 0000000..6812fea
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/NegotiatingServerConnectionFactory.java
@@ -0,0 +1,103 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import javax.net.ssl.SSLEngine;
+
+import org.eclipse.jetty.io.AbstractConnection;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.ssl.SslConnection;
+import org.eclipse.jetty.server.AbstractConnectionFactory;
+import org.eclipse.jetty.server.Connector;
+
+public abstract class NegotiatingServerConnectionFactory extends AbstractConnectionFactory
+{
+    private final List<String> protocols;
+    private String defaultProtocol;
+
+    public NegotiatingServerConnectionFactory(String protocol, String... protocols)
+    {
+        super(protocol);
+        this.protocols = Arrays.asList(protocols);
+    }
+
+    public String getDefaultProtocol()
+    {
+        return defaultProtocol;
+    }
+
+    public void setDefaultProtocol(String defaultProtocol)
+    {
+        this.defaultProtocol = defaultProtocol;
+    }
+
+    public List<String> getProtocols()
+    {
+        return protocols;
+    }
+
+    @Override
+    public Connection newConnection(Connector connector, EndPoint endPoint)
+    {
+        List<String> protocols = this.protocols;
+        if (protocols.isEmpty())
+        {
+            protocols = connector.getProtocols();
+            Iterator<String> i = protocols.iterator();
+            while (i.hasNext())
+            {
+                String protocol = i.next();
+                String prefix = "ssl-";
+                if (protocol.regionMatches(true, 0, prefix, 0, prefix.length()) || protocol.equalsIgnoreCase("alpn"))
+                {
+                    i.remove();
+                }
+            }
+        }
+
+        String dft = defaultProtocol;
+        if (dft == null && !protocols.isEmpty())
+            dft = protocols.get(0);
+
+        SSLEngine engine = null;
+        EndPoint ep = endPoint;
+        while (engine == null && ep != null)
+        {
+            // TODO make more generic
+            if (ep instanceof SslConnection.DecryptedEndPoint)
+                engine = ((SslConnection.DecryptedEndPoint)ep).getSslConnection().getSSLEngine();
+            else
+                ep = null;
+        }
+
+        return configure(newServerConnection(connector, endPoint, engine, protocols, dft), connector, endPoint);
+    }
+
+    protected abstract AbstractConnection newServerConnection(Connector connector, EndPoint endPoint, SSLEngine engine, List<String> protocols, String defaultProtocol);
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s@%x{%s,%s,%s}", getClass().getSimpleName(), hashCode(), getProtocol(), getDefaultProtocol(), getProtocols());
+    }
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/NetworkConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/NetworkConnector.java
new file mode 100644
index 0000000..96c734b
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/NetworkConnector.java
@@ -0,0 +1,72 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.Closeable;
+import java.io.IOException;
+
+/**
+ * <p>A {@link Connector} for TCP/IP network connectors</p>
+ */
+public interface NetworkConnector extends Connector, Closeable
+{
+    /**
+     * <p>Performs the activities needed to open the network communication
+     * (for example, to start accepting incoming network connections).</p>
+     *
+     * @throws IOException if this connector cannot be opened
+     * @see #close()
+     */
+    void open() throws IOException;
+
+    /**
+     * <p>Performs the activities needed to close the network communication
+     * (for example, to stop accepting network connections).</p>
+     * Once a connector has been closed, it cannot be opened again without first
+     * calling {@link #stop()} and it will not be active again until a subsequent call to {@link #start()}
+     */
+    @Override
+    void close();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * A Connector may be opened and not started (to reserve a port)
+     * or closed and running (to allow graceful shutdown of existing connections)
+     * @return True if the connector is Open.
+     */
+    boolean isOpen();
+    
+    /**
+     * @return The hostname representing the interface to which
+     * this connector will bind, or null for all interfaces.
+     */
+    String getHost();
+
+    /**
+     * @return The configured port for the connector or 0 if any available
+     * port may be used.
+     */
+    int getPort();
+
+    /**
+     * @return The actual port the connector is listening on, or
+     * -1 if it has not been opened, or -2 if it has been closed.
+     */
+    int getLocalPort();
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/NetworkTrafficServerConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/NetworkTrafficServerConnector.java
new file mode 100644
index 0000000..d6f3632
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/NetworkTrafficServerConnector.java
@@ -0,0 +1,92 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.SocketChannel;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Executor;
+
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.NetworkTrafficListener;
+import org.eclipse.jetty.io.NetworkTrafficSelectChannelEndPoint;
+import org.eclipse.jetty.io.SelectChannelEndPoint;
+import org.eclipse.jetty.io.SelectorManager;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+/**
+ * <p>A specialized version of {@link ServerConnector} that supports {@link NetworkTrafficListener}s.</p>
+ * <p>{@link NetworkTrafficListener}s can be added and removed dynamically before and after this connector has
+ * been started without causing {@link java.util.ConcurrentModificationException}s.</p>
+ */
+public class NetworkTrafficServerConnector extends ServerConnector
+{
+    private final List<NetworkTrafficListener> listeners = new CopyOnWriteArrayList<>();
+
+    public NetworkTrafficServerConnector(Server server)
+    {
+        this(server, null, null, null, 0, 0, new HttpConnectionFactory());
+    }
+
+    public NetworkTrafficServerConnector(Server server, ConnectionFactory connectionFactory, SslContextFactory sslContextFactory)
+    {
+        super(server, sslContextFactory, connectionFactory);
+    }
+
+    public NetworkTrafficServerConnector(Server server, ConnectionFactory connectionFactory)
+    {
+        super(server, connectionFactory);
+    }
+
+    public NetworkTrafficServerConnector(Server server, Executor executor, Scheduler scheduler, ByteBufferPool pool, int acceptors, int selectors, ConnectionFactory... factories)
+    {
+        super(server, executor, scheduler, pool, acceptors, selectors, factories);
+    }
+
+    public NetworkTrafficServerConnector(Server server, SslContextFactory sslContextFactory)
+    {
+        super(server, sslContextFactory);
+    }
+
+    /**
+     * @param listener the listener to add
+     */
+    public void addNetworkTrafficListener(NetworkTrafficListener listener)
+    {
+        listeners.add(listener);
+    }
+
+    /**
+     * @param listener the listener to remove
+     */
+    public void removeNetworkTrafficListener(NetworkTrafficListener listener)
+    {
+        listeners.remove(listener);
+    }
+
+    @Override
+    protected SelectChannelEndPoint newEndPoint(SocketChannel channel, SelectorManager.ManagedSelector selectSet, SelectionKey key) throws IOException
+    {
+        NetworkTrafficSelectChannelEndPoint endPoint = new NetworkTrafficSelectChannelEndPoint(channel, selectSet, key, getScheduler(), getIdleTimeout(), listeners);
+        return endPoint;
+    }
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/QueuedHttpInput.java b/jetty-server/src/main/java/org/eclipse/jetty/server/QueuedHttpInput.java
new file mode 100644
index 0000000..e3bf2a1
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/QueuedHttpInput.java
@@ -0,0 +1,146 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.io.InterruptedIOException;
+
+import org.eclipse.jetty.util.ArrayQueue;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * {@link QueuedHttpInput} holds a queue of items passed to it by calls to {@link #content(Object)}.
+ * <p/>
+ * {@link QueuedHttpInput} stores the items directly; if the items contain byte buffers, it does not copy them
+ * but simply holds references to the item, thus the caller must organize for those buffers to valid while
+ * held by this class.
+ * <p/>
+ * To assist the caller, subclasses may override methods {@link #onAsyncRead()}, {@link #onContentConsumed(Object)}
+ * that can be implemented so that the caller will know when buffers are queued and consumed.
+ */
+public abstract class QueuedHttpInput<T> extends HttpInput<T>
+{
+    private final static Logger LOG = Log.getLogger(QueuedHttpInput.class);
+
+    private final ArrayQueue<T> _inputQ = new ArrayQueue<>(lock());
+
+    public QueuedHttpInput()
+    {
+    }
+
+    public void content(T item)
+    {
+        // The buffer is not copied here.  This relies on the caller not recycling the buffer
+        // until the it is consumed.  The onContentConsumed and onAllContentConsumed() callbacks are
+        // the signals to the caller that the buffers can be recycled.
+
+        synchronized (lock())
+        {
+            boolean wasEmpty = _inputQ.isEmpty();
+            _inputQ.add(item);
+            if (LOG.isDebugEnabled())
+                LOG.debug("{} queued {}", this, item);
+            if (wasEmpty)
+            {
+                if (!onAsyncRead())
+                    lock().notify();
+            }
+        }
+    }
+
+    public void recycle()
+    {
+        synchronized (lock())
+        {
+            T item = _inputQ.pollUnsafe();
+            while (item != null)
+            {
+                onContentConsumed(item);
+                item = _inputQ.pollUnsafe();
+            }
+            super.recycle();
+        }
+    }
+
+    @Override
+    protected T nextContent()
+    {
+        synchronized (lock())
+        {
+            // Items are removed only when they are fully consumed.
+            T item = _inputQ.peekUnsafe();
+            // Skip consumed items at the head of the queue.
+            while (item != null && remaining(item) == 0)
+            {
+                _inputQ.pollUnsafe();
+                onContentConsumed(item);
+                if (LOG.isDebugEnabled())
+                    LOG.debug("{} consumed {}", this, item);
+                item = _inputQ.peekUnsafe();
+            }
+            return item;
+        }
+    }
+
+    protected void blockForContent() throws IOException
+    {
+        synchronized (lock())
+        {
+            while (_inputQ.isEmpty() && !isFinished() && !isEOF())
+            {
+                try
+                {
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("{} waiting for content", this);
+                    lock().wait();
+                }
+                catch (InterruptedException e)
+                {
+                    throw (IOException)new InterruptedIOException().initCause(e);
+                }
+            }
+        }
+    }
+
+    /**
+     * Callback that signals that the given content has been consumed.
+     *
+     * @param item the consumed content
+     */
+    protected abstract void onContentConsumed(T item);
+
+    public void earlyEOF()
+    {
+        synchronized (lock())
+        {
+            super.earlyEOF();
+            lock().notify();
+        }
+    }
+
+    public void messageComplete()
+    {
+        synchronized (lock())
+        {
+            super.messageComplete();
+            lock().notify();
+        }
+    }
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/QuietServletException.java b/jetty-server/src/main/java/org/eclipse/jetty/server/QuietServletException.java
new file mode 100644
index 0000000..e7adce2
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/QuietServletException.java
@@ -0,0 +1,53 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import javax.servlet.ServletException;
+
+
+/* ------------------------------------------------------------ */
+/** A ServletException that is logged less verbosely than
+ * a normal ServletException.
+ * <p>
+ * Used for container generated exceptions that need only a message rather
+ * than a stack trace.
+ * </p>
+ */
+public class QuietServletException extends ServletException
+{
+    public QuietServletException()
+    {
+        super();
+    }
+
+    public QuietServletException(String message, Throwable rootCause)
+    {
+        super(message,rootCause);
+    }
+
+    public QuietServletException(String message)
+    {
+        super(message);
+    }
+
+    public QuietServletException(Throwable rootCause)
+    {
+        super(rootCause);
+    }
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java
index 37d87b8..811e111 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java
@@ -26,21 +26,21 @@ import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.UnsupportedEncodingException;
 import java.net.InetAddress;
-import java.nio.ByteBuffer;
+import java.net.InetSocketAddress;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.nio.charset.UnsupportedCharsetException;
 import java.security.Principal;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Enumeration;
 import java.util.EventListener;
 import java.util.HashMap;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
-import java.util.Map.Entry;
-
 import javax.servlet.AsyncContext;
-import javax.servlet.AsyncEvent;
 import javax.servlet.AsyncListener;
 import javax.servlet.DispatcherType;
 import javax.servlet.MultipartConfigElement;
@@ -58,35 +58,26 @@ import javax.servlet.http.Cookie;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpSession;
+import javax.servlet.http.HttpUpgradeHandler;
 import javax.servlet.http.Part;
 
-import org.eclipse.jetty.continuation.Continuation;
-import org.eclipse.jetty.continuation.ContinuationListener;
 import org.eclipse.jetty.http.HttpCookie;
 import org.eclipse.jetty.http.HttpFields;
-import org.eclipse.jetty.http.HttpHeaders;
-import org.eclipse.jetty.http.HttpMethods;
-import org.eclipse.jetty.http.HttpParser;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpMethod;
 import org.eclipse.jetty.http.HttpStatus;
 import org.eclipse.jetty.http.HttpURI;
-import org.eclipse.jetty.http.HttpVersions;
+import org.eclipse.jetty.http.HttpVersion;
 import org.eclipse.jetty.http.MimeTypes;
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.BufferUtil;
-import org.eclipse.jetty.io.ByteArrayBuffer;
-import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.io.nio.DirectNIOBuffer;
-import org.eclipse.jetty.io.nio.IndirectNIOBuffer;
-import org.eclipse.jetty.io.nio.NIOBuffer;
 import org.eclipse.jetty.server.handler.ContextHandler;
 import org.eclipse.jetty.server.handler.ContextHandler.Context;
+import org.eclipse.jetty.server.session.AbstractSession;
 import org.eclipse.jetty.util.Attributes;
 import org.eclipse.jetty.util.AttributesMap;
 import org.eclipse.jetty.util.IO;
-import org.eclipse.jetty.util.LazyList;
 import org.eclipse.jetty.util.MultiException;
 import org.eclipse.jetty.util.MultiMap;
-import org.eclipse.jetty.util.MultiPartInputStream;
+import org.eclipse.jetty.util.MultiPartInputStreamParser;
 import org.eclipse.jetty.util.StringUtil;
 import org.eclipse.jetty.util.URIUtil;
 import org.eclipse.jetty.util.UrlEncoded;
@@ -114,7 +105,7 @@ import org.eclipse.jetty.util.log.Logger;
  * and the pathInfo matched against the servlet URL patterns and {@link Request#setServletPath(String)} called as a result.</li>
  * </ul>
  *
- * A request instance is created for each {@link AbstractHttpConnection} accepted by the server and recycled for each HTTP request received via that connection.
+ * A request instance is created for each connection accepted by the server and recycled for each HTTP request received via that connection.
  * An effort is made to avoid reparsing headers and cookies that are likely to be the same for requests from the same connection.
  *
  * <p>
@@ -127,23 +118,26 @@ import org.eclipse.jetty.util.log.Logger;
  */
 public class Request implements HttpServletRequest
 {
-    public static final String __MULTIPART_CONFIG_ELEMENT = "org.eclipse.multipartConfig";
-    public static final String __MULTIPART_INPUT_STREAM = "org.eclipse.multiPartInputStream";
-    public static final String __MULTIPART_CONTEXT = "org.eclipse.multiPartContext";
-    private static final Logger LOG = Log.getLogger(Request.class);
+    public static final String __MULTIPART_CONFIG_ELEMENT = "org.eclipse.jetty.multipartConfig";
+    public static final String __MULTIPART_INPUT_STREAM = "org.eclipse.jetty.multiPartInputStream";
+    public static final String __MULTIPART_CONTEXT = "org.eclipse.jetty.multiPartContext";
 
-    private static final String __ASYNC_FWD = "org.eclipse.asyncfwd";
-    private static final Collection __defaultLocale = Collections.singleton(Locale.getDefault());
+    private static final Logger LOG = Log.getLogger(Request.class);
+    private static final Collection<Locale> __defaultLocale = Collections.singleton(Locale.getDefault());
     private static final int __NONE = 0, _STREAM = 1, __READER = 2;
 
+    private final HttpChannel<?> _channel;
+    private final HttpFields _fields=new HttpFields();
+    private final List<ServletRequestAttributeListener>  _requestAttributeListeners=new ArrayList<>();
+    private final HttpInput<?> _input;
+    
     public static class MultiPartCleanerListener implements ServletRequestListener
     {
-
         @Override
         public void requestDestroyed(ServletRequestEvent sre)
         {
             //Clean up any tmp files created by MultiPartInputStream
-            MultiPartInputStream mpis = (MultiPartInputStream)sre.getServletRequest().getAttribute(__MULTIPART_INPUT_STREAM);
+            MultiPartInputStreamParser mpis = (MultiPartInputStreamParser)sre.getServletRequest().getAttribute(__MULTIPART_INPUT_STREAM);
             if (mpis != null)
             {
                 ContextHandler.Context context = (ContextHandler.Context)sre.getServletRequest().getAttribute(__MULTIPART_CONTEXT);
@@ -172,46 +166,36 @@ public class Request implements HttpServletRequest
     }
     
     
-    /* ------------------------------------------------------------ */
-    public static Request getRequest(HttpServletRequest request)
-    {
-        if (request instanceof Request)
-            return (Request)request;
 
-        return AbstractHttpConnection.getCurrentConnection().getRequest();
-    }
-    protected final AsyncContinuation _async = new AsyncContinuation();
+    private boolean _secure;
     private boolean _asyncSupported = true;
+    private boolean _newContext;
+    private boolean _cookiesExtracted = false;
+    private boolean _handled = false;
+    private boolean _paramsExtracted;
+    private boolean _requestedSessionIdFromCookie = false;
     private volatile Attributes _attributes;
     private Authentication _authentication;
-    private MultiMap<String> _baseParameters;
     private String _characterEncoding;
-    protected AbstractHttpConnection _connection;
     private ContextHandler.Context _context;
-    private boolean _newContext;
     private String _contextPath;
     private CookieCutter _cookies;
-    private boolean _cookiesExtracted = false;
     private DispatcherType _dispatcherType;
-    private boolean _dns = false;
-    private EndPoint _endp;
-    private boolean _handled = false;
     private int _inputState = __NONE;
-    private String _method;
+    private HttpMethod _httpMethod;
+    private String _httpMethodString;
+    private MultiMap<String> _queryParameters;
+    private MultiMap<String> _contentParameters;
     private MultiMap<String> _parameters;
-    private boolean _paramsExtracted;
     private String _pathInfo;
     private int _port;
-    private String _protocol = HttpVersions.HTTP_1_1;
+    private HttpVersion _httpVersion = HttpVersion.HTTP_1_1;
     private String _queryEncoding;
     private String _queryString;
     private BufferedReader _reader;
     private String _readerEncoding;
-    private String _remoteAddr;
-    private String _remoteHost;
-    private Object _requestAttributeListeners;
+    private InetSocketAddress _remote;
     private String _requestedSessionId;
-    private boolean _requestedSessionIdFromCookie = false;
     private String _requestURI;
     private Map<Object, HttpSession> _savedNewSessions;
     private String _scheme = URIUtil.HTTP;
@@ -221,236 +205,248 @@ public class Request implements HttpServletRequest
     private HttpSession _session;
     private SessionManager _sessionManager;
     private long _timeStamp;
-    private long _dispatchTime;
-
-    private Buffer _timeStampBuffer;
     private HttpURI _uri;
+    private MultiPartInputStreamParser _multiPartInputStream; //if the request is a multi-part mime
+    private AsyncContextState _async;
     
-    private MultiPartInputStream _multiPartInputStream; //if the request is a multi-part mime
-    
     /* ------------------------------------------------------------ */
-    public Request()
+    public Request(HttpChannel<?> channel, HttpInput<?> input)
+    {
+        _channel = channel;
+        _input = input;
+    }
+
+    /* ------------------------------------------------------------ */
+    public HttpFields getHttpFields()
     {
+        return _fields;
     }
 
     /* ------------------------------------------------------------ */
-    public Request(AbstractHttpConnection connection)
+    public HttpInput<?> getHttpInput()
     {
-        setConnection(connection);
+        return _input;
     }
 
     /* ------------------------------------------------------------ */
     public void addEventListener(final EventListener listener)
     {
         if (listener instanceof ServletRequestAttributeListener)
-            _requestAttributeListeners = LazyList.add(_requestAttributeListeners,listener);
-        if (listener instanceof ContinuationListener)
-            throw new IllegalArgumentException(listener.getClass().toString());
+            _requestAttributeListeners.add((ServletRequestAttributeListener)listener);
         if (listener instanceof AsyncListener)
             throw new IllegalArgumentException(listener.getClass().toString());
     }
 
-    /* ------------------------------------------------------------ */
-    /**
-     * Extract Parameters from query string and/or form _content.
-     */
     public void extractParameters()
     {
-        if (_baseParameters == null)
-            _baseParameters = new MultiMap(16);
-
         if (_paramsExtracted)
-        {
-            if (_parameters == null)
-                _parameters = _baseParameters;
             return;
-        }
 
         _paramsExtracted = true;
 
-        try
+        // Extract query string parameters; these may be replaced by a forward()
+        // and may have already been extracted by mergeQueryParameters().
+        if (_queryParameters == null)
+            _queryParameters = extractQueryParameters();
+
+        // Extract content parameters; these cannot be replaced by a forward()
+        // once extracted and may have already been extracted by getParts() or
+        // by a processing happening after a form-based authentication.
+        if (_contentParameters == null)
+            _contentParameters = extractContentParameters();
+
+        _parameters = restoreParameters();
+    }
+
+    private MultiMap<String> extractQueryParameters()
+    {
+        MultiMap<String> result = new MultiMap<>();
+        if (_uri != null && _uri.hasQuery())
         {
-            // Handle query string
-            if (_uri != null && _uri.hasQuery())
+            if (_queryEncoding == null)
             {
-                if (_queryEncoding == null)
-                    _uri.decodeQueryTo(_baseParameters);
-                else
+                _uri.decodeQueryTo(result);
+            }
+            else
+            {
+                try
                 {
-                    try
-                    {
-                        _uri.decodeQueryTo(_baseParameters,_queryEncoding);
-                    }
-                    catch (UnsupportedEncodingException e)
-                    {
-                        if (LOG.isDebugEnabled())
-                            LOG.warn(e);
-                        else
-                            LOG.warn(e.toString());
-                    }
+                    _uri.decodeQueryTo(result, _queryEncoding);
+                }
+                catch (UnsupportedEncodingException e)
+                {
+                    if (LOG.isDebugEnabled())
+                        LOG.warn(e);
+                    else
+                        LOG.warn(e.toString());
                 }
             }
+        }
+        return result;
+    }
 
-            // handle any _content.
-            String encoding = getCharacterEncoding();
-            String content_type = getContentType();
-            if (content_type != null && content_type.length() > 0)
-            {
-                content_type = HttpFields.valueParameters(content_type,null);
+    private MultiMap<String> extractContentParameters()
+    {
+        MultiMap<String> result = new MultiMap<>();
 
-                if (MimeTypes.FORM_ENCODED.equalsIgnoreCase(content_type) && _inputState == __NONE
-                        && (HttpMethods.POST.equals(getMethod()) || HttpMethods.PUT.equals(getMethod())))
+        String contentType = getContentType();
+        if (contentType != null && !contentType.isEmpty())
+        {
+            contentType = HttpFields.valueParameters(contentType, null);
+            int contentLength = getContentLength();
+            if (contentLength != 0)
+            {
+                if (MimeTypes.Type.FORM_ENCODED.is(contentType) && _inputState == __NONE &&
+                        (HttpMethod.POST.is(getMethod()) || HttpMethod.PUT.is(getMethod())))
                 {
-                    int content_length = getContentLength();
-                    if (content_length != 0)
-                    {
-                        try
-                        {
-                            int maxFormContentSize = -1;
-                            int maxFormKeys = -1;
-
-                            if (_context != null)
-                            {
-                                maxFormContentSize = _context.getContextHandler().getMaxFormContentSize();
-                                maxFormKeys = _context.getContextHandler().getMaxFormKeys();
-                            }
-                            
-                            if (maxFormContentSize < 0)
-                            {
-                                Object obj = _connection.getConnector().getServer().getAttribute("org.eclipse.jetty.server.Request.maxFormContentSize");
-                                if (obj == null)
-                                    maxFormContentSize = 200000;
-                                else if (obj instanceof Number)
-                                {                      
-                                    Number size = (Number)obj;
-                                    maxFormContentSize = size.intValue();
-                                }
-                                else if (obj instanceof String)
-                                {
-                                    maxFormContentSize = Integer.valueOf((String)obj);
-                                }
-                            }
-                            
-                            if (maxFormKeys < 0)
-                            {
-                                Object obj = _connection.getConnector().getServer().getAttribute("org.eclipse.jetty.server.Request.maxFormKeys");
-                                if (obj == null)
-                                    maxFormKeys = 1000;
-                                else if (obj instanceof Number)
-                                {
-                                    Number keys = (Number)obj;
-                                    maxFormKeys = keys.intValue();
-                                }
-                                else if (obj instanceof String)
-                                {
-                                    maxFormKeys = Integer.valueOf((String)obj);
-                                }
-                            }
-
-                            if (content_length > maxFormContentSize && maxFormContentSize > 0)
-                            {
-                                throw new IllegalStateException("Form too large " + content_length + ">" + maxFormContentSize);
-                            }
-                            InputStream in = getInputStream();
-
-                            // Add form params to query params
-                            UrlEncoded.decodeTo(in,_baseParameters,encoding,content_length < 0?maxFormContentSize:-1,maxFormKeys);
-                        }
-                        catch (IOException e)
-                        {
-                            if (LOG.isDebugEnabled())
-                                LOG.warn(e);
-                            else
-                                LOG.warn(e.toString());
-                        }
-                    }
+                    extractFormParameters(result);
+                }
+                else if (contentType.startsWith("multipart/form-data") &&
+                        getAttribute(__MULTIPART_CONFIG_ELEMENT) != null &&
+                        _multiPartInputStream == null)
+                {
+                    extractMultipartParameters(result);
                 }
-              
             }
+        }
+
+        return result;
+    }
+
+    public void extractFormParameters(MultiMap<String> params)
+    {
+        try
+        {
+            int maxFormContentSize = -1;
+            int maxFormKeys = -1;
 
-            if (_parameters == null)
-                _parameters = _baseParameters;
-            else if (_parameters != _baseParameters)
+            if (_context != null)
             {
-                // Merge parameters (needed if parameters extracted after a forward).
-                Iterator iter = _baseParameters.entrySet().iterator();
-                while (iter.hasNext())
-                {
-                    Map.Entry entry = (Map.Entry)iter.next();
-                    String name = (String)entry.getKey();
-                    Object values = entry.getValue();
-                    for (int i = 0; i < LazyList.size(values); i++)
-                        _parameters.add(name,LazyList.get(values,i));
-                }
+                maxFormContentSize = _context.getContextHandler().getMaxFormContentSize();
+                maxFormKeys = _context.getContextHandler().getMaxFormKeys();
             }
 
-            if (content_type != null && content_type.length()>0 && content_type.startsWith("multipart/form-data") && getAttribute(__MULTIPART_CONFIG_ELEMENT)!=null)
+            if (maxFormContentSize < 0)
             {
-                try
+                Object obj = _channel.getServer().getAttribute("org.eclipse.jetty.server.Request.maxFormContentSize");
+                if (obj == null)
+                    maxFormContentSize = 200000;
+                else if (obj instanceof Number)
                 {
-                    getParts();
+                    Number size = (Number)obj;
+                    maxFormContentSize = size.intValue();
                 }
-                catch (IOException e)
+                else if (obj instanceof String)
                 {
-                    if (LOG.isDebugEnabled())
-                        LOG.warn(e);
-                    else
-                        LOG.warn(e.toString());
+                    maxFormContentSize = Integer.valueOf((String)obj);
                 }
-                catch (ServletException e)
+            }
+
+            if (maxFormKeys < 0)
+            {
+                Object obj = _channel.getServer().getAttribute("org.eclipse.jetty.server.Request.maxFormKeys");
+                if (obj == null)
+                    maxFormKeys = 1000;
+                else if (obj instanceof Number)
                 {
-                    if (LOG.isDebugEnabled())
-                        LOG.warn(e);
-                    else
-                        LOG.warn(e.toString());
+                    Number keys = (Number)obj;
+                    maxFormKeys = keys.intValue();
+                }
+                else if (obj instanceof String)
+                {
+                    maxFormKeys = Integer.valueOf((String)obj);
                 }
             }
+
+            int contentLength = getContentLength();
+            if (contentLength > maxFormContentSize && maxFormContentSize > 0)
+            {
+                throw new IllegalStateException("Form too large: " + contentLength + " > " + maxFormContentSize);
+            }
+            InputStream in = getInputStream();
+            if (_input.isAsync())
+                throw new IllegalStateException("Cannot extract parameters with async IO");
+
+            UrlEncoded.decodeTo(in,params,getCharacterEncoding(),contentLength<0?maxFormContentSize:-1,maxFormKeys);
         }
-        finally
+        catch (IOException e)
         {
-            // ensure params always set (even if empty) after extraction
-            if (_parameters == null)
-                _parameters = _baseParameters;
+            if (LOG.isDebugEnabled())
+                LOG.warn(e);
+            else
+                LOG.warn(e.toString());
+        }
+    }
+
+    private void extractMultipartParameters(MultiMap<String> result)
+    {
+        try
+        {
+            getParts(result);
+        }
+        catch (IOException | ServletException e)
+        {
+            LOG.warn(e);
+            throw new RuntimeException(e);
         }
     }
 
     /* ------------------------------------------------------------ */
+    @Override
     public AsyncContext getAsyncContext()
     {
-        if (_async.isInitial() && !_async.isAsyncStarted())
-            throw new IllegalStateException(_async.getStatusString());
+        HttpChannelState state = getHttpChannelState();
+        if (_async==null || !state.isAsyncStarted())
+            throw new IllegalStateException(state.getStatusString());
+        
         return _async;
     }
 
     /* ------------------------------------------------------------ */
-    public AsyncContinuation getAsyncContinuation()
+    public HttpChannelState getHttpChannelState()
     {
-        return _async;
+        return _channel.getState();
     }
-    
+
     /* ------------------------------------------------------------ */
-    /*
+    /**
+     * Get Request Attribute.
+     * <p>Also supports jetty specific attributes to gain access to Jetty APIs:
+     * <dl>
+     * <dt>org.eclipse.jetty.server.Server</dt><dd>The Jetty Server instance</dd>
+     * <dt>org.eclipse.jetty.server.HttpChannel</dt><dd>The HttpChannel for this request</dd>
+     * <dt>org.eclipse.jetty.server.HttpConnection</dt><dd>The HttpConnection or null if another transport is used</dd>
+     * </dl>
+     * While these attributes may look like security problems, they are exposing nothing that is not already
+     * available via reflection from a Request instance.
+     * </p>
      * @see javax.servlet.ServletRequest#getAttribute(java.lang.String)
      */
+    @Override
     public Object getAttribute(String name)
     {
-        if ("org.eclipse.jetty.io.EndPoint.maxIdleTime".equalsIgnoreCase(name))
-            return new Long(getConnection().getEndPoint().getMaxIdleTime());
-
-        Object attr = (_attributes == null)?null:_attributes.getAttribute(name);
-        if (attr == null && Continuation.ATTRIBUTE.equals(name))
-            return _async;
-        return attr;
+        if (name.startsWith("org.eclipse.jetty"))
+        {
+            if ("org.eclipse.jetty.server.Server".equals(name))
+                return _channel.getServer();
+            if ("org.eclipse.jetty.server.HttpChannel".equals(name))
+                return _channel;
+            if ("org.eclipse.jetty.server.HttpConnection".equals(name) &&
+                _channel.getHttpTransport() instanceof HttpConnection)
+                return _channel.getHttpTransport();
+        }
+        return (_attributes == null)?null:_attributes.getAttribute(name);
     }
 
     /* ------------------------------------------------------------ */
     /*
      * @see javax.servlet.ServletRequest#getAttributeNames()
      */
-    public Enumeration getAttributeNames()
+    @Override
+    public Enumeration<String> getAttributeNames()
     {
         if (_attributes == null)
-            return Collections.enumeration(Collections.EMPTY_LIST);
+            return Collections.enumeration(Collections.<String>emptyList());
 
         return AttributesMap.getAttributeNamesCopy(_attributes);
     }
@@ -480,11 +476,12 @@ public class Request implements HttpServletRequest
     /*
      * @see javax.servlet.http.HttpServletRequest#getAuthType()
      */
+    @Override
     public String getAuthType()
     {
         if (_authentication instanceof Authentication.Deferred)
             setAuthentication(((Authentication.Deferred)_authentication).authenticate(this));
-        
+
         if (_authentication instanceof Authentication.User)
             return ((Authentication.User)_authentication).getAuthMethod();
         return null;
@@ -494,6 +491,7 @@ public class Request implements HttpServletRequest
     /*
      * @see javax.servlet.ServletRequest#getCharacterEncoding()
      */
+    @Override
     public String getCharacterEncoding()
     {
         return _characterEncoding;
@@ -503,35 +501,45 @@ public class Request implements HttpServletRequest
     /**
      * @return Returns the connection.
      */
-    public AbstractHttpConnection getConnection()
+    public HttpChannel<?> getHttpChannel()
     {
-        return _connection;
+        return _channel;
     }
 
     /* ------------------------------------------------------------ */
     /*
      * @see javax.servlet.ServletRequest#getContentLength()
      */
+    @Override
     public int getContentLength()
     {
-        return (int)_connection.getRequestFields().getLongField(HttpHeaders.CONTENT_LENGTH_BUFFER);
+        return (int)_fields.getLongField(HttpHeader.CONTENT_LENGTH.toString());
     }
 
-    public long getContentRead()
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest.getContentLengthLong()
+     */
+    @Override
+    public long getContentLengthLong()
     {
-        if (_connection == null || _connection.getParser() == null)
-            return -1;
-
-        return ((HttpParser)_connection.getParser()).getContentRead();
+        return _fields.getLongField(HttpHeader.CONTENT_LENGTH.toString());
     }
 
     /* ------------------------------------------------------------ */
+    public long getContentRead()
+    {
+        return _input.getContentRead();
+    }
+    
+    /* ------------------------------------------------------------ */
     /*
      * @see javax.servlet.ServletRequest#getContentType()
      */
+    @Override
     public String getContentType()
     {
-        return _connection.getRequestFields().getStringField(HttpHeaders.CONTENT_TYPE_BUFFER);
+        return _fields.getStringField(HttpHeader.CONTENT_TYPE);
     }
 
     /* ------------------------------------------------------------ */
@@ -547,6 +555,7 @@ public class Request implements HttpServletRequest
     /*
      * @see javax.servlet.http.HttpServletRequest#getContextPath()
      */
+    @Override
     public String getContextPath()
     {
         return _contextPath;
@@ -556,14 +565,20 @@ public class Request implements HttpServletRequest
     /*
      * @see javax.servlet.http.HttpServletRequest#getCookies()
      */
+    @Override
     public Cookie[] getCookies()
     {
         if (_cookiesExtracted)
-            return _cookies == null?null:_cookies.getCookies();
+        {
+            if (_cookies == null || _cookies.getCookies().length == 0)
+                return null;
+            
+            return _cookies.getCookies();
+        }
 
         _cookiesExtracted = true;
 
-        Enumeration enm = _connection.getRequestFields().getValues(HttpHeaders.COOKIE_BUFFER);
+        Enumeration<?> enm = _fields.getValues(HttpHeader.COOKIE.toString());
 
         // Handle no cookies
         if (enm != null)
@@ -578,19 +593,25 @@ public class Request implements HttpServletRequest
             }
         }
 
-        return _cookies == null?null:_cookies.getCookies();
+        //Javadoc for Request.getCookies() stipulates null for no cookies
+        if (_cookies == null || _cookies.getCookies().length == 0)
+            return null;
+        
+        return _cookies.getCookies();
     }
 
     /* ------------------------------------------------------------ */
     /*
      * @see javax.servlet.http.HttpServletRequest#getDateHeader(java.lang.String)
      */
+    @Override
     public long getDateHeader(String name)
     {
-        return _connection.getRequestFields().getDateField(name);
+        return _fields.getDateField(name);
     }
 
     /* ------------------------------------------------------------ */
+    @Override
     public DispatcherType getDispatcherType()
     {
         return _dispatcherType;
@@ -600,29 +621,32 @@ public class Request implements HttpServletRequest
     /*
      * @see javax.servlet.http.HttpServletRequest#getHeader(java.lang.String)
      */
+    @Override
     public String getHeader(String name)
     {
-        return _connection.getRequestFields().getStringField(name);
+        return _fields.getStringField(name);
     }
 
     /* ------------------------------------------------------------ */
     /*
      * @see javax.servlet.http.HttpServletRequest#getHeaderNames()
      */
-    public Enumeration getHeaderNames()
+    @Override
+    public Enumeration<String> getHeaderNames()
     {
-        return _connection.getRequestFields().getFieldNames();
+        return _fields.getFieldNames();
     }
 
     /* ------------------------------------------------------------ */
     /*
      * @see javax.servlet.http.HttpServletRequest#getHeaders(java.lang.String)
      */
-    public Enumeration getHeaders(String name)
+    @Override
+    public Enumeration<String> getHeaders(String name)
     {
-        Enumeration e = _connection.getRequestFields().getValues(name);
+        Enumeration<String> e = _fields.getValues(name);
         if (e == null)
-            return Collections.enumeration(Collections.EMPTY_LIST);
+            return Collections.enumeration(Collections.<String>emptyList());
         return e;
     }
 
@@ -639,46 +663,45 @@ public class Request implements HttpServletRequest
     /*
      * @see javax.servlet.ServletRequest#getInputStream()
      */
+    @Override
     public ServletInputStream getInputStream() throws IOException
     {
         if (_inputState != __NONE && _inputState != _STREAM)
             throw new IllegalStateException("READER");
         _inputState = _STREAM;
-        return _connection.getInputStream();
+
+        if (_channel.isExpecting100Continue())
+            _channel.continue100(_input.available());
+
+        return _input;
     }
 
     /* ------------------------------------------------------------ */
     /*
      * @see javax.servlet.http.HttpServletRequest#getIntHeader(java.lang.String)
      */
+    @Override
     public int getIntHeader(String name)
     {
-        return (int)_connection.getRequestFields().getLongField(name);
+        return (int)_fields.getLongField(name);
     }
 
-    /* ------------------------------------------------------------ */
-    /*
-     * @see javax.servlet.ServletRequest#getLocalAddr()
-     */
-    public String getLocalAddr()
-    {
-        return _endp == null?null:_endp.getLocalAddr();
-    }
 
     /* ------------------------------------------------------------ */
     /*
      * @see javax.servlet.ServletRequest#getLocale()
      */
+    @Override
     public Locale getLocale()
     {
-        Enumeration enm = _connection.getRequestFields().getValues(HttpHeaders.ACCEPT_LANGUAGE,HttpFields.__separators);
+        Enumeration<String> enm = _fields.getValues(HttpHeader.ACCEPT_LANGUAGE.toString(),HttpFields.__separators);
 
         // handle no locale
         if (enm == null || !enm.hasMoreElements())
             return Locale.getDefault();
 
         // sort the list in quality order
-        List acceptLanguage = HttpFields.qualityList(enm);
+        List<?> acceptLanguage = HttpFields.qualityList(enm);
         if (acceptLanguage.size() == 0)
             return Locale.getDefault();
 
@@ -705,101 +728,117 @@ public class Request implements HttpServletRequest
     /*
      * @see javax.servlet.ServletRequest#getLocales()
      */
-    public Enumeration getLocales()
+    @Override
+    public Enumeration<Locale> getLocales()
     {
 
-        Enumeration enm = _connection.getRequestFields().getValues(HttpHeaders.ACCEPT_LANGUAGE,HttpFields.__separators);
+        Enumeration<String> enm = _fields.getValues(HttpHeader.ACCEPT_LANGUAGE.toString(),HttpFields.__separators);
 
         // handle no locale
         if (enm == null || !enm.hasMoreElements())
             return Collections.enumeration(__defaultLocale);
 
         // sort the list in quality order
-        List acceptLanguage = HttpFields.qualityList(enm);
+        List<String> acceptLanguage = HttpFields.qualityList(enm);
 
         if (acceptLanguage.size() == 0)
             return Collections.enumeration(__defaultLocale);
 
-        Object langs = null;
-        int size = acceptLanguage.size();
+        List<Locale> langs = new ArrayList<>();
 
         // convert to locals
-        for (int i = 0; i < size; i++)
+        for (String language : acceptLanguage)
         {
-            String language = (String)acceptLanguage.get(i);
-            language = HttpFields.valueParameters(language,null);
+            language = HttpFields.valueParameters(language, null);
             String country = "";
             int dash = language.indexOf('-');
             if (dash > -1)
             {
                 country = language.substring(dash + 1).trim();
-                language = language.substring(0,dash).trim();
+                language = language.substring(0, dash).trim();
             }
-            langs = LazyList.ensureSize(langs,size);
-            langs = LazyList.add(langs,new Locale(language,country));
+            langs.add(new Locale(language, country));
         }
 
-        if (LazyList.size(langs) == 0)
+        if (langs.size() == 0)
             return Collections.enumeration(__defaultLocale);
 
-        return Collections.enumeration(LazyList.getList(langs));
+        return Collections.enumeration(langs);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getLocalAddr()
+     */
+    @Override
+    public String getLocalAddr()
+    {
+        InetSocketAddress local=_channel.getLocalAddress();
+        if (local==null)
+            return "";
+        InetAddress address = local.getAddress();
+        if (address==null)
+            return local.getHostString();
+        return address.getHostAddress();
     }
 
     /* ------------------------------------------------------------ */
     /*
      * @see javax.servlet.ServletRequest#getLocalName()
      */
+    @Override
     public String getLocalName()
     {
-        if (_endp == null)
-            return null;
-        if (_dns)
-            return _endp.getLocalHost();
-
-        String local = _endp.getLocalAddr();
-        if (local != null && local.indexOf(':') >= 0)
-            local = "[" + local + "]";
-        return local;
+        InetSocketAddress local=_channel.getLocalAddress();
+        return local.getHostString();
     }
 
     /* ------------------------------------------------------------ */
     /*
      * @see javax.servlet.ServletRequest#getLocalPort()
      */
+    @Override
     public int getLocalPort()
     {
-        return _endp == null?0:_endp.getLocalPort();
+        InetSocketAddress local=_channel.getLocalAddress();
+        return local.getPort();
     }
 
     /* ------------------------------------------------------------ */
     /*
      * @see javax.servlet.http.HttpServletRequest#getMethod()
      */
+    @Override
     public String getMethod()
     {
-        return _method;
+        return _httpMethodString;
     }
 
     /* ------------------------------------------------------------ */
     /*
      * @see javax.servlet.ServletRequest#getParameter(java.lang.String)
      */
+    @Override
     public String getParameter(String name)
     {
         if (!_paramsExtracted)
             extractParameters();
-        return (String)_parameters.getValue(name,0);
+        if (_parameters == null)
+            _parameters = restoreParameters();
+        return _parameters.getValue(name,0);
     }
 
     /* ------------------------------------------------------------ */
     /*
      * @see javax.servlet.ServletRequest#getParameterMap()
      */
-    public Map getParameterMap()
+    @Override
+    public Map<String, String[]> getParameterMap()
     {
         if (!_paramsExtracted)
             extractParameters();
-
+        if (_parameters == null)
+            _parameters = restoreParameters();
         return Collections.unmodifiableMap(_parameters.toStringArrayMap());
     }
 
@@ -807,40 +846,68 @@ public class Request implements HttpServletRequest
     /*
      * @see javax.servlet.ServletRequest#getParameterNames()
      */
-    public Enumeration getParameterNames()
+    @Override
+    public Enumeration<String> getParameterNames()
     {
         if (!_paramsExtracted)
             extractParameters();
+        if (_parameters == null)
+            _parameters = restoreParameters();
         return Collections.enumeration(_parameters.keySet());
     }
 
     /* ------------------------------------------------------------ */
-    /**
-     * @return Returns the parameters.
-     */
-    public MultiMap<String> getParameters()
-    {
-        return _parameters;
-    }
-
-    /* ------------------------------------------------------------ */
     /*
      * @see javax.servlet.ServletRequest#getParameterValues(java.lang.String)
      */
+    @Override
     public String[] getParameterValues(String name)
     {
         if (!_paramsExtracted)
             extractParameters();
-        List<Object> vals = _parameters.getValues(name);
+        if (_parameters == null)
+            _parameters = restoreParameters();
+        List<String> vals = _parameters.getValues(name);
         if (vals == null)
             return null;
         return vals.toArray(new String[vals.size()]);
     }
 
+    private MultiMap<String> restoreParameters()
+    {
+        MultiMap<String> result = new MultiMap<>();
+        if (_queryParameters == null)
+            _queryParameters = extractQueryParameters();
+        result.addAllValues(_queryParameters);
+        result.addAllValues(_contentParameters);
+        return result;
+    }
+
+    public MultiMap<String> getQueryParameters()
+    {
+        return _queryParameters;
+    }
+
+    public void setQueryParameters(MultiMap<String> queryParameters)
+    {
+        _queryParameters = queryParameters;
+    }
+
+    public void setContentParameters(MultiMap<String> contentParameters)
+    {
+        _contentParameters = contentParameters;
+    }
+
+    public void resetParameters()
+    {
+        _parameters = null;
+    }
+
     /* ------------------------------------------------------------ */
     /*
      * @see javax.servlet.http.HttpServletRequest#getPathInfo()
      */
+    @Override
     public String getPathInfo()
     {
         return _pathInfo;
@@ -850,6 +917,7 @@ public class Request implements HttpServletRequest
     /*
      * @see javax.servlet.http.HttpServletRequest#getPathTranslated()
      */
+    @Override
     public String getPathTranslated()
     {
         if (_pathInfo == null || _context == null)
@@ -861,9 +929,19 @@ public class Request implements HttpServletRequest
     /*
      * @see javax.servlet.ServletRequest#getProtocol()
      */
+    @Override
     public String getProtocol()
     {
-        return _protocol;
+        return _httpVersion.toString();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getProtocol()
+     */
+    public HttpVersion getHttpVersion()
+    {
+        return _httpVersion;
     }
 
     /* ------------------------------------------------------------ */
@@ -876,6 +954,7 @@ public class Request implements HttpServletRequest
     /*
      * @see javax.servlet.http.HttpServletRequest#getQueryString()
      */
+    @Override
     public String getQueryString()
     {
         if (_queryString == null && _uri != null)
@@ -892,6 +971,7 @@ public class Request implements HttpServletRequest
     /*
      * @see javax.servlet.ServletRequest#getReader()
      */
+    @Override
     public BufferedReader getReader() throws IOException
     {
         if (_inputState != __NONE && _inputState != __READER)
@@ -925,6 +1005,7 @@ public class Request implements HttpServletRequest
     /*
      * @see javax.servlet.ServletRequest#getRealPath(java.lang.String)
      */
+    @Override
     public String getRealPath(String path)
     {
         if (_context == null)
@@ -933,46 +1014,73 @@ public class Request implements HttpServletRequest
     }
 
     /* ------------------------------------------------------------ */
+    /**
+     * Access the underlying Remote {@link InetSocketAddress} for this request.
+     * 
+     * @return the remote {@link InetSocketAddress} for this request, or null if the request has no remote (see {@link ServletRequest#getRemoteAddr()} for
+     *         conditions that result in no remote address)
+     */
+    public InetSocketAddress getRemoteInetSocketAddress()
+    {
+        InetSocketAddress remote = _remote;
+        if (remote == null)
+            remote = _channel.getRemoteAddress();
+
+        return remote;
+    }
+
+    /* ------------------------------------------------------------ */
     /*
      * @see javax.servlet.ServletRequest#getRemoteAddr()
      */
+    @Override
     public String getRemoteAddr()
     {
-        if (_remoteAddr != null)
-            return _remoteAddr;
-        return _endp == null?null:_endp.getRemoteAddr();
+        InetSocketAddress remote=_remote;
+        if (remote==null)
+            remote=_channel.getRemoteAddress();
+        
+        if (remote==null)
+            return "";
+        
+        InetAddress address = remote.getAddress();
+        if (address==null)
+            return remote.getHostString();
+        
+        return address.getHostAddress();
     }
 
     /* ------------------------------------------------------------ */
     /*
      * @see javax.servlet.ServletRequest#getRemoteHost()
      */
+    @Override
     public String getRemoteHost()
     {
-        if (_dns)
-        {
-            if (_remoteHost != null)
-            {
-                return _remoteHost;
-            }
-            return _endp == null?null:_endp.getRemoteHost();
-        }
-        return getRemoteAddr();
+        InetSocketAddress remote=_remote;
+        if (remote==null)
+            remote=_channel.getRemoteAddress();
+        return remote==null?"":remote.getHostString();
     }
 
     /* ------------------------------------------------------------ */
     /*
      * @see javax.servlet.ServletRequest#getRemotePort()
      */
+    @Override
     public int getRemotePort()
     {
-        return _endp == null?0:_endp.getRemotePort();
+        InetSocketAddress remote=_remote;
+        if (remote==null)
+            remote=_channel.getRemoteAddress();
+        return remote==null?0:remote.getPort();
     }
 
     /* ------------------------------------------------------------ */
     /*
      * @see javax.servlet.http.HttpServletRequest#getRemoteUser()
      */
+    @Override
     public String getRemoteUser()
     {
         Principal p = getUserPrincipal();
@@ -985,6 +1093,7 @@ public class Request implements HttpServletRequest
     /*
      * @see javax.servlet.ServletRequest#getRequestDispatcher(java.lang.String)
      */
+    @Override
     public RequestDispatcher getRequestDispatcher(String path)
     {
         if (path == null || _context == null)
@@ -1009,6 +1118,7 @@ public class Request implements HttpServletRequest
     /*
      * @see javax.servlet.http.HttpServletRequest#getRequestedSessionId()
      */
+    @Override
     public String getRequestedSessionId()
     {
         return _requestedSessionId;
@@ -1018,6 +1128,7 @@ public class Request implements HttpServletRequest
     /*
      * @see javax.servlet.http.HttpServletRequest#getRequestURI()
      */
+    @Override
     public String getRequestURI()
     {
         if (_requestURI == null && _uri != null)
@@ -1029,32 +1140,19 @@ public class Request implements HttpServletRequest
     /*
      * @see javax.servlet.http.HttpServletRequest#getRequestURL()
      */
+    @Override
     public StringBuffer getRequestURL()
     {
-        final StringBuffer url = new StringBuffer(48);
-        synchronized (url)
-        {
-            String scheme = getScheme();
-            int port = getServerPort();
-
-            url.append(scheme);
-            url.append("://");
-            url.append(getServerName());
-            if (_port > 0 && ((scheme.equalsIgnoreCase(URIUtil.HTTP) && port != 80) || (scheme.equalsIgnoreCase(URIUtil.HTTPS) && port != 443)))
-            {
-                url.append(':');
-                url.append(_port);
-            }
-
-            url.append(getRequestURI());
-            return url;
-        }
+        final StringBuffer url = new StringBuffer(128);
+        URIUtil.appendSchemeHostPort(url,getScheme(),getServerName(),getServerPort());
+        url.append(getRequestURI());
+        return url;
     }
 
     /* ------------------------------------------------------------ */
     public Response getResponse()
     {
-        return _connection._response;
+        return _channel.getResponse();
     }
 
     /* ------------------------------------------------------------ */
@@ -1070,19 +1168,8 @@ public class Request implements HttpServletRequest
      */
     public StringBuilder getRootURL()
     {
-        StringBuilder url = new StringBuilder(48);
-        String scheme = getScheme();
-        int port = getServerPort();
-
-        url.append(scheme);
-        url.append("://");
-        url.append(getServerName());
-
-        if (port > 0 && ((scheme.equalsIgnoreCase("http") && port != 80) || (scheme.equalsIgnoreCase("https") && port != 443)))
-        {
-            url.append(':');
-            url.append(port);
-        }
+        StringBuilder url = new StringBuilder(128);
+        URIUtil.appendSchemeHostPort(url,getScheme(),getServerName(),getServerPort());
         return url;
     }
 
@@ -1090,6 +1177,7 @@ public class Request implements HttpServletRequest
     /*
      * @see javax.servlet.ServletRequest#getScheme()
      */
+    @Override
     public String getScheme()
     {
         return _scheme;
@@ -1099,6 +1187,7 @@ public class Request implements HttpServletRequest
     /*
      * @see javax.servlet.ServletRequest#getServerName()
      */
+    @Override
     public String getServerName()
     {
         // Return already determined host
@@ -1110,54 +1199,64 @@ public class Request implements HttpServletRequest
 
         // Return host from absolute URI
         _serverName = _uri.getHost();
-        _port = _uri.getPort();
         if (_serverName != null)
+        {
+            _port = _uri.getPort();
             return _serverName;
+        }
 
         // Return host from header field
-        Buffer hostPort = _connection.getRequestFields().get(HttpHeaders.HOST_BUFFER);
+        String hostPort = _fields.getStringField(HttpHeader.HOST);
+        
+        _port=0;
         if (hostPort != null)
         {
-            loop: for (int i = hostPort.putIndex(); i-- > hostPort.getIndex();)
+            int len=hostPort.length();
+            loop: for (int i = len; i-- > 0;)
             {
-                char ch = (char)(0xff & hostPort.peek(i));
-                switch (ch)
+                char c2 = (char)(0xff & hostPort.charAt(i));
+                switch (c2)
                 {
                     case ']':
                         break loop;
 
                     case ':':
-                        _serverName = BufferUtil.to8859_1_String(hostPort.peek(hostPort.getIndex(),i - hostPort.getIndex()));
                         try
                         {
-                            _port = BufferUtil.toInt(hostPort.peek(i + 1,hostPort.putIndex() - i - 1));
+                            len=i;
+                            _port = StringUtil.toInt(hostPort.substring(i+1));
                         }
                         catch (NumberFormatException e)
                         {
-                            try
-                            {
-                                if (_connection != null)
-                                    _connection._generator.sendError(HttpStatus.BAD_REQUEST_400,"Bad Host header",null,true);
-                            }
-                            catch (IOException e1)
-                            {
-                                throw new RuntimeException(e1);
-                            }
+                            LOG.warn(e);
+                            _serverName=hostPort;
+                            _port=0;
+                            return _serverName;
                         }
-                        return _serverName;
+                        break loop;
                 }
             }
-            if (_serverName == null || _port < 0)
+            if (hostPort.charAt(0)=='[')
             {
-                _serverName = BufferUtil.to8859_1_String(hostPort);
-                _port = 0;
+                if (hostPort.charAt(len-1)!=']') 
+                {
+                    LOG.warn("Bad IPv6 "+hostPort);
+                    _serverName=hostPort;
+                    _port=0;
+                    return _serverName;
+                }
+                _serverName = hostPort.substring(0,len);
             }
+            else if (len==hostPort.length())
+                _serverName=hostPort;
+            else
+                _serverName = hostPort.substring(0,len);
 
             return _serverName;
         }
 
         // Return host from connection
-        if (_connection != null)
+        if (_channel != null)
         {
             _serverName = getLocalName();
             _port = getLocalPort();
@@ -1181,6 +1280,7 @@ public class Request implements HttpServletRequest
     /*
      * @see javax.servlet.ServletRequest#getServerPort()
      */
+    @Override
     public int getServerPort()
     {
         if (_port <= 0)
@@ -1193,7 +1293,10 @@ public class Request implements HttpServletRequest
                 if (_serverName != null && _uri != null)
                     _port = _uri.getPort();
                 else
-                    _port = _endp == null?0:_endp.getLocalPort();
+                {
+                    InetSocketAddress local = _channel.getLocalAddress();
+                    _port = local == null?0:local.getPort();
+                }
             }
         }
 
@@ -1207,6 +1310,7 @@ public class Request implements HttpServletRequest
     }
 
     /* ------------------------------------------------------------ */
+    @Override
     public ServletContext getServletContext()
     {
         return _context;
@@ -1226,6 +1330,7 @@ public class Request implements HttpServletRequest
     /*
      * @see javax.servlet.http.HttpServletRequest#getServletPath()
      */
+    @Override
     public String getServletPath()
     {
         if (_servletPath == null)
@@ -1236,13 +1341,37 @@ public class Request implements HttpServletRequest
     /* ------------------------------------------------------------ */
     public ServletResponse getServletResponse()
     {
-        return _connection.getResponse();
+        return _channel.getResponse();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * Add @override when 3.1 api is available
+     */
+    public String changeSessionId()
+    {
+        HttpSession session = getSession(false);
+        if (session == null)
+            throw new IllegalStateException("No session");
+
+        if (session instanceof AbstractSession)
+        {
+            AbstractSession abstractSession =  ((AbstractSession)session);
+            abstractSession.renewId(this);
+            if (getRemoteUser() != null)
+                abstractSession.setAttribute(AbstractSession.SESSION_KNOWN_ONLY_TO_AUTHENTICATED, Boolean.TRUE);
+            if (abstractSession.isIdChanged())
+                _channel.getResponse().addCookie(_sessionManager.getSessionCookie(abstractSession, getContextPath(), isSecure()));
+        }
+
+        return session.getId();
     }
 
     /* ------------------------------------------------------------ */
     /*
      * @see javax.servlet.http.HttpServletRequest#getSession()
      */
+    @Override
     public HttpSession getSession()
     {
         return getSession(true);
@@ -1252,6 +1381,7 @@ public class Request implements HttpServletRequest
     /*
      * @see javax.servlet.http.HttpServletRequest#getSession(boolean)
      */
+    @Override
     public HttpSession getSession(boolean create)
     {
         if (_session != null)
@@ -1264,6 +1394,9 @@ public class Request implements HttpServletRequest
 
         if (!create)
             return null;
+        
+        if (getResponse().isCommitted())
+            throw new IllegalStateException("Response is committed");
 
         if (_sessionManager == null)
             throw new IllegalStateException("No SessionManager");
@@ -1271,7 +1404,7 @@ public class Request implements HttpServletRequest
         _session = _sessionManager.newHttpSession(this);
         HttpCookie cookie = _sessionManager.getSessionCookie(_session,getContextPath(),isSecure());
         if (cookie != null)
-            _connection.getResponse().addCookie(cookie);
+            _channel.getResponse().addCookie(cookie);
 
         return _session;
     }
@@ -1298,19 +1431,6 @@ public class Request implements HttpServletRequest
 
     /* ------------------------------------------------------------ */
     /**
-     * Get Request TimeStamp
-     *
-     * @return The time that the request was received.
-     */
-    public Buffer getTimeStampBuffer()
-    {
-        if (_timeStampBuffer == null && _timeStamp > 0)
-            _timeStampBuffer = HttpFields.__dateCache.formatBuffer(_timeStamp);
-        return _timeStampBuffer;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
      * @return Returns the uri.
      */
     public HttpURI getUri()
@@ -1351,6 +1471,7 @@ public class Request implements HttpServletRequest
     /*
      * @see javax.servlet.http.HttpServletRequest#getUserPrincipal()
      */
+    @Override
     public Principal getUserPrincipal()
     {
         if (_authentication instanceof Authentication.Deferred)
@@ -1365,16 +1486,6 @@ public class Request implements HttpServletRequest
         return null;
     }
 
-    /* ------------------------------------------------------------ */
-    /**
-     * Get timestamp of the request dispatch
-     *
-     * @return timestamp
-     */
-    public long getDispatchTime()
-    {
-        return _dispatchTime;
-    }
 
     /* ------------------------------------------------------------ */
     public boolean isHandled()
@@ -1382,13 +1493,15 @@ public class Request implements HttpServletRequest
         return _handled;
     }
 
+    @Override
     public boolean isAsyncStarted()
     {
-       return _async.isAsyncStarted();
+       return getHttpChannelState().isAsyncStarted();
     }
 
 
     /* ------------------------------------------------------------ */
+    @Override
     public boolean isAsyncSupported()
     {
         return _asyncSupported;
@@ -1398,6 +1511,7 @@ public class Request implements HttpServletRequest
     /*
      * @see javax.servlet.http.HttpServletRequest#isRequestedSessionIdFromCookie()
      */
+    @Override
     public boolean isRequestedSessionIdFromCookie()
     {
         return _requestedSessionId != null && _requestedSessionIdFromCookie;
@@ -1407,6 +1521,7 @@ public class Request implements HttpServletRequest
     /*
      * @see javax.servlet.http.HttpServletRequest#isRequestedSessionIdFromUrl()
      */
+    @Override
     public boolean isRequestedSessionIdFromUrl()
     {
         return _requestedSessionId != null && !_requestedSessionIdFromCookie;
@@ -1416,6 +1531,7 @@ public class Request implements HttpServletRequest
     /*
      * @see javax.servlet.http.HttpServletRequest#isRequestedSessionIdFromURL()
      */
+    @Override
     public boolean isRequestedSessionIdFromURL()
     {
         return _requestedSessionId != null && !_requestedSessionIdFromCookie;
@@ -1425,6 +1541,7 @@ public class Request implements HttpServletRequest
     /*
      * @see javax.servlet.http.HttpServletRequest#isRequestedSessionIdValid()
      */
+    @Override
     public boolean isRequestedSessionIdValid()
     {
         if (_requestedSessionId == null)
@@ -1438,15 +1555,23 @@ public class Request implements HttpServletRequest
     /*
      * @see javax.servlet.ServletRequest#isSecure()
      */
+    @Override
     public boolean isSecure()
     {
-        return _connection.isConfidential(this);
+        return _secure;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setSecure(boolean secure)
+    {
+        _secure=secure;
     }
 
     /* ------------------------------------------------------------ */
     /*
      * @see javax.servlet.http.HttpServletRequest#isUserInRole(java.lang.String)
      */
+    @Override
     public boolean isUserInRole(String role)
     {
         if (_authentication instanceof Authentication.Deferred)
@@ -1468,6 +1593,9 @@ public class Request implements HttpServletRequest
     /* ------------------------------------------------------------ */
     protected void recycle()
     {
+        if (_context != null)
+            throw new IllegalStateException("Request in context!");
+        
         if (_inputState == __READER)
         {
             try
@@ -1483,12 +1611,14 @@ public class Request implements HttpServletRequest
             }
         }
 
+        _dispatcherType=null;
         setAuthentication(Authentication.NOT_CHECKED);
-        _async.recycle();
+        getHttpChannelState().recycle();
+        if (_async!=null)
+            _async.reset();
+        _async=null;
         _asyncSupported = true;
         _handled = false;
-        if (_context != null)
-            throw new IllegalStateException("Request in context!");
         if (_attributes != null)
             _attributes.clearAttributes();
         _characterEncoding = null;
@@ -1497,15 +1627,18 @@ public class Request implements HttpServletRequest
             _cookies.reset();
         _cookiesExtracted = false;
         _context = null;
+        _newContext=false;
         _serverName = null;
-        _method = null;
+        _httpMethod=null;
+        _httpMethodString = null;
         _pathInfo = null;
         _port = 0;
-        _protocol = HttpVersions.HTTP_1_1;
+        _httpVersion = HttpVersion.HTTP_1_1;
         _queryEncoding = null;
         _queryString = null;
         _requestedSessionId = null;
         _requestedSessionIdFromCookie = false;
+        _secure=false;
         _session = null;
         _sessionManager = null;
         _requestURI = null;
@@ -1513,10 +1646,9 @@ public class Request implements HttpServletRequest
         _scheme = URIUtil.HTTP;
         _servletPath = null;
         _timeStamp = 0;
-        _timeStampBuffer = null;
         _uri = null;
-        if (_baseParameters != null)
-            _baseParameters.clear();
+        _queryParameters = null;
+        _contentParameters = null;
         _parameters = null;
         _paramsExtracted = false;
         _inputState = __NONE;
@@ -1525,12 +1657,16 @@ public class Request implements HttpServletRequest
             _savedNewSessions.clear();
         _savedNewSessions=null;
         _multiPartInputStream = null;
+        _remote=null;
+        _fields.clear();
+        _input.recycle();
     }
 
     /* ------------------------------------------------------------ */
     /*
      * @see javax.servlet.ServletRequest#removeAttribute(java.lang.String)
      */
+    @Override
     public void removeAttribute(String name)
     {
         Object old_value = _attributes == null?null:_attributes.getAttribute(name);
@@ -1538,36 +1674,25 @@ public class Request implements HttpServletRequest
         if (_attributes != null)
             _attributes.removeAttribute(name);
 
-        if (old_value != null)
+        if (old_value != null && !_requestAttributeListeners.isEmpty())
         {
-            if (_requestAttributeListeners != null)
-            {
-                final ServletRequestAttributeEvent event = new ServletRequestAttributeEvent(_context,this,name,old_value);
-                final int size = LazyList.size(_requestAttributeListeners);
-                for (int i = 0; i < size; i++)
-                {
-                    final EventListener listener = (ServletRequestAttributeListener)LazyList.get(_requestAttributeListeners,i);
-                    if (listener instanceof ServletRequestAttributeListener)
-                    {
-                        final ServletRequestAttributeListener l = (ServletRequestAttributeListener)listener;
-                        l.attributeRemoved(event);
-                    }
-                }
-            }
+            final ServletRequestAttributeEvent event = new ServletRequestAttributeEvent(_context,this,name,old_value);
+            for (ServletRequestAttributeListener listener : _requestAttributeListeners)
+                listener.attributeRemoved(event);
         }
     }
 
     /* ------------------------------------------------------------ */
     public void removeEventListener(final EventListener listener)
     {
-        _requestAttributeListeners = LazyList.remove(_requestAttributeListeners,listener);
+        _requestAttributeListeners.remove(listener);
     }
 
     /* ------------------------------------------------------------ */
     public void saveNewSession(Object key, HttpSession session)
     {
         if (_savedNewSessions == null)
-            _savedNewSessions = new HashMap<Object, HttpSession>();
+            _savedNewSessions = new HashMap<>();
         _savedNewSessions.put(key,session);
     }
 
@@ -1580,82 +1705,35 @@ public class Request implements HttpServletRequest
     /* ------------------------------------------------------------ */
     /*
      * Set a request attribute. if the attribute name is "org.eclipse.jetty.server.server.Request.queryEncoding" then the value is also passed in a call to
-     * {@link #setQueryEncoding}. <p> if the attribute name is "org.eclipse.jetty.server.server.ResponseBuffer", then the response buffer is flushed with @{link
-     * #flushResponseBuffer} <p> if the attribute name is "org.eclipse.jetty.io.EndPoint.maxIdleTime", then the value is passed to the associated {@link
-     * EndPoint#setMaxIdleTime}.
+     * {@link #setQueryEncoding}.
      *
      * @see javax.servlet.ServletRequest#setAttribute(java.lang.String, java.lang.Object)
      */
+    @Override
     public void setAttribute(String name, Object value)
     {
         Object old_value = _attributes == null?null:_attributes.getAttribute(name);
 
-        if (name.startsWith("org.eclipse.jetty."))
-        {
-            if ("org.eclipse.jetty.server.Request.queryEncoding".equals(name))
-                setQueryEncoding(value == null?null:value.toString());
-            else if ("org.eclipse.jetty.server.sendContent".equals(name))
-            {
-                try
-                {
-                    ((AbstractHttpConnection.Output)getServletResponse().getOutputStream()).sendContent(value);
-                }
-                catch (IOException e)
-                {
-                    throw new RuntimeException(e);
-                }
-            }
-            else if ("org.eclipse.jetty.server.ResponseBuffer".equals(name))
-            {
-                try
-                {
-                    final ByteBuffer byteBuffer = (ByteBuffer)value;
-                    synchronized (byteBuffer)
-                    {
-                        NIOBuffer buffer = byteBuffer.isDirect()?new DirectNIOBuffer(byteBuffer,true):new IndirectNIOBuffer(byteBuffer,true);
-                        ((AbstractHttpConnection.Output)getServletResponse().getOutputStream()).sendResponse(buffer);
-                    }
-                }
-                catch (IOException e)
-                {
-                    throw new RuntimeException(e);
-                }
-            }
-            else if ("org.eclipse.jetty.io.EndPoint.maxIdleTime".equalsIgnoreCase(name))
-            {
-                try
-                {
-                    getConnection().getEndPoint().setMaxIdleTime(Integer.valueOf(value.toString()));
-                }
-                catch (IOException e)
-                {
-                    throw new RuntimeException(e);
-                }
-            }
-        }
-
+        if ("org.eclipse.jetty.server.Request.queryEncoding".equals(name))
+            setQueryEncoding(value == null?null:value.toString());
+        else if ("org.eclipse.jetty.server.sendContent".equals(name))
+            LOG.warn("Deprecated: org.eclipse.jetty.server.sendContent");
+        
         if (_attributes == null)
             _attributes = new AttributesMap();
         _attributes.setAttribute(name,value);
 
-        if (_requestAttributeListeners != null)
+        if (!_requestAttributeListeners.isEmpty())
         {
             final ServletRequestAttributeEvent event = new ServletRequestAttributeEvent(_context,this,name,old_value == null?value:old_value);
-            final int size = LazyList.size(_requestAttributeListeners);
-            for (int i = 0; i < size; i++)
+            for (ServletRequestAttributeListener l : _requestAttributeListeners)
             {
-                final EventListener listener = (ServletRequestAttributeListener)LazyList.get(_requestAttributeListeners,i);
-                if (listener instanceof ServletRequestAttributeListener)
-                {
-                    final ServletRequestAttributeListener l = (ServletRequestAttributeListener)listener;
-
-                    if (old_value == null)
-                        l.attributeAdded(event);
-                    else if (value == null)
-                        l.attributeRemoved(event);
-                    else
-                        l.attributeReplaced(event);
-                }
+                if (old_value == null)
+                    l.attributeAdded(event);
+                else if (value == null)
+                    l.attributeRemoved(event);
+                else
+                    l.attributeReplaced(event);
             }
         }
     }
@@ -1686,6 +1764,7 @@ public class Request implements HttpServletRequest
     /*
      * @see javax.servlet.ServletRequest#setCharacterEncoding(java.lang.String)
      */
+    @Override
     public void setCharacterEncoding(String encoding) throws UnsupportedEncodingException
     {
         if (_inputState != __NONE)
@@ -1695,8 +1774,16 @@ public class Request implements HttpServletRequest
 
         // check encoding is supported
         if (!StringUtil.isUTF8(encoding))
-            // noinspection ResultOfMethodCallIgnored
-            "".getBytes(encoding);
+        {
+            try
+            {
+                Charset.forName(encoding);
+            }
+            catch (UnsupportedCharsetException e)
+            {
+                throw new UnsupportedEncodingException(e.getMessage());
+            }
+        }
     }
 
     /* ------------------------------------------------------------ */
@@ -1709,22 +1796,12 @@ public class Request implements HttpServletRequest
     }
 
     /* ------------------------------------------------------------ */
-    // final so we can safely call this from constructor
-    protected final void setConnection(AbstractHttpConnection connection)
-    {
-        _connection = connection;
-        _async.setConnection(connection);
-        _endp = connection.getEndPoint();
-        _dns = connection.getResolveNames();
-    }
-
-    /* ------------------------------------------------------------ */
     /*
      * @see javax.servlet.ServletRequest#getContentType()
      */
     public void setContentType(String contentType)
     {
-        _connection.getRequestFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,contentType);
+        _fields.put(HttpHeader.CONTENT_TYPE,contentType);
 
     }
 
@@ -1793,21 +1870,16 @@ public class Request implements HttpServletRequest
      * @param method
      *            The method to set.
      */
-    public void setMethod(String method)
+    public void setMethod(HttpMethod httpMethod, String method)
     {
-        _method = method;
+        _httpMethod=httpMethod;
+        _httpMethodString = method;
     }
 
     /* ------------------------------------------------------------ */
-    /**
-     * @param parameters
-     *            The parameters to set.
-     */
-    public void setParameters(MultiMap<String> parameters)
+    public boolean isHead()
     {
-        _parameters = (parameters == null)?_baseParameters:parameters;
-        if (_paramsExtracted && _parameters == null)
-            throw new IllegalStateException();
+        return HttpMethod.HEAD==_httpMethod;
     }
 
     /* ------------------------------------------------------------ */
@@ -1822,18 +1894,18 @@ public class Request implements HttpServletRequest
 
     /* ------------------------------------------------------------ */
     /**
-     * @param protocol
+     * @param version
      *            The protocol to set.
      */
-    public void setProtocol(String protocol)
+    public void setHttpVersion(HttpVersion version)
     {
-        _protocol = protocol;
+        _httpVersion = version;
     }
 
     /* ------------------------------------------------------------ */
     /**
      * Set the character encoding used for the query string. This call will effect the return of getQueryString and getParamaters. It must be called before any
-     * geParameter methods.
+     * getParameter methods.
      *
      * The request attribute "org.eclipse.jetty.server.server.Request.queryEncoding" may be set as an alternate method of calling setQueryEncoding.
      *
@@ -1861,21 +1933,11 @@ public class Request implements HttpServletRequest
      * @param addr
      *            The address to set.
      */
-    public void setRemoteAddr(String addr)
-    {
-        _remoteAddr = addr;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param host
-     *            The host to set.
-     */
-    public void setRemoteHost(String host)
+    public void setRemoteAddr(InetSocketAddress addr)
     {
-        _remoteHost = host;
+        _remote = addr;
     }
-
+    
     /* ------------------------------------------------------------ */
     /**
      * @param requestedSessionId
@@ -1989,32 +2051,32 @@ public class Request implements HttpServletRequest
     }
 
     /* ------------------------------------------------------------ */
-    /**
-     * Set timetstamp of request dispatch
-     *
-     * @param value
-     *            timestamp
-     */
-    public void setDispatchTime(long value)
-    {
-        _dispatchTime = value;
-    }
-
-    /* ------------------------------------------------------------ */
+    @Override
     public AsyncContext startAsync() throws IllegalStateException
     {
         if (!_asyncSupported)
             throw new IllegalStateException("!asyncSupported");
-        _async.startAsync();
+        HttpChannelState state = getHttpChannelState();
+        if (_async==null)
+            _async=new AsyncContextState(state);
+        AsyncContextEvent event = new AsyncContextEvent(_context,_async,state,this,this,getResponse());
+        state.startAsync(event);
         return _async;
     }
 
     /* ------------------------------------------------------------ */
+    @Override
     public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) throws IllegalStateException
     {
         if (!_asyncSupported)
             throw new IllegalStateException("!asyncSupported");
-        _async.startAsync(_context,servletRequest,servletResponse);
+        HttpChannelState state = getHttpChannelState();
+        if (_async==null)
+            _async=new AsyncContextState(state);
+        AsyncContextEvent event = new AsyncContextEvent(_context,_async,state,this,servletRequest,servletResponse);
+        event.setDispatchContext(getServletContext());
+        event.setDispatchPath(URIUtil.addPaths(getServletPath(),getPathInfo()));
+        state.startAsync(event);
         return _async;
     }
 
@@ -2026,33 +2088,41 @@ public class Request implements HttpServletRequest
     }
 
     /* ------------------------------------------------------------ */
+    @Override
     public boolean authenticate(HttpServletResponse response) throws IOException, ServletException
     {
         if (_authentication instanceof Authentication.Deferred)
         {
             setAuthentication(((Authentication.Deferred)_authentication).authenticate(this,response));
-            return !(_authentication instanceof Authentication.ResponseSent);        
+            return !(_authentication instanceof Authentication.ResponseSent);
         }
         response.sendError(HttpStatus.UNAUTHORIZED_401);
         return false;
     }
 
     /* ------------------------------------------------------------ */
+    @Override
     public Part getPart(String name) throws IOException, ServletException
-    {                
+    {
         getParts();
+
         return _multiPartInputStream.getPart(name);
     }
 
     /* ------------------------------------------------------------ */
+    @Override
     public Collection<Part> getParts() throws IOException, ServletException
     {
         if (getContentType() == null || !getContentType().startsWith("multipart/form-data"))
             throw new ServletException("Content-Type != multipart/form-data");
-        
+        return getParts(null);
+    }
+
+    private Collection<Part> getParts(MultiMap<String> params) throws IOException, ServletException
+    {
         if (_multiPartInputStream == null)
-            _multiPartInputStream = (MultiPartInputStream)getAttribute(__MULTIPART_INPUT_STREAM);
-        
+            _multiPartInputStream = (MultiPartInputStreamParser)getAttribute(__MULTIPART_INPUT_STREAM);
+
         if (_multiPartInputStream == null)
         {
             MultipartConfigElement config = (MultipartConfigElement)getAttribute(__MULTIPART_CONFIG_ELEMENT);
@@ -2060,38 +2130,35 @@ public class Request implements HttpServletRequest
             if (config == null)
                 throw new IllegalStateException("No multipart config for servlet");
             
-            _multiPartInputStream = new MultiPartInputStream(getInputStream(), 
+            _multiPartInputStream = new MultiPartInputStreamParser(getInputStream(),
                                                              getContentType(), config, 
                                                              (_context != null?(File)_context.getAttribute("javax.servlet.context.tempdir"):null));
             
             setAttribute(__MULTIPART_INPUT_STREAM, _multiPartInputStream);
             setAttribute(__MULTIPART_CONTEXT, _context);
-            Collection<Part> parts = _multiPartInputStream.getParts(); //causes parsing 
+            Collection<Part> parts = _multiPartInputStream.getParts(); //causes parsing
+            ByteArrayOutputStream os = null;
             for (Part p:parts)
             {
-                MultiPartInputStream.MultiPart mp = (MultiPartInputStream.MultiPart)p;
+                MultiPartInputStreamParser.MultiPart mp = (MultiPartInputStreamParser.MultiPart)p;
                 if (mp.getContentDispositionFilename() == null)
                 {
-                    //Servlet Spec 3.0 pg 23, parts without filenames must be put into init params
+                    // Servlet Spec 3.0 pg 23, parts without filename must be put into params.
                     String charset = null;
                     if (mp.getContentType() != null)
-                        charset = MimeTypes.getCharsetFromContentType(new ByteArrayBuffer(mp.getContentType()));
+                        charset = MimeTypes.getCharsetFromContentType(mp.getContentType());
 
-                    ByteArrayOutputStream os = null;
-                    InputStream is = mp.getInputStream(); //get the bytes regardless of being in memory or in temp file
-                    try
+                    try (InputStream is = mp.getInputStream())
                     {
-                        os = new ByteArrayOutputStream();
+                        if (os == null)
+                            os = new ByteArrayOutputStream();
                         IO.copy(is, os);
-                        String content=new String(os.toByteArray(),charset==null?StringUtil.__UTF8:charset);   
-                        getParameter(""); //cause params to be evaluated
-                        getParameters().add(mp.getName(), content);
-                    }
-                    finally
-                    {
-                        IO.close(os);
-                        IO.close(is);
+                        String content=new String(os.toByteArray(),charset==null?StandardCharsets.UTF_8:Charset.forName(charset));
+                        if (_contentParameters == null)
+                            _contentParameters = params == null ? new MultiMap<String>() : params;
+                        _contentParameters.add(mp.getName(), content);
                     }
+                    os.reset();
                 }
             }
         }
@@ -2100,105 +2167,94 @@ public class Request implements HttpServletRequest
     }
 
     /* ------------------------------------------------------------ */
+    @Override
     public void login(String username, String password) throws ServletException
     {
-        if (_authentication instanceof Authentication.Deferred) 
+        if (_authentication instanceof Authentication.Deferred)
         {
             _authentication=((Authentication.Deferred)_authentication).login(username,password,this);
             if (_authentication == null)
-                throw new ServletException();
-        } 
-        else 
+                throw new Authentication.Failed("Authentication failed for username '"+username+"'");
+        }
+        else
         {
-            throw new ServletException("Authenticated as "+_authentication);
+            throw new Authentication.Failed("Authenticated failed for username '"+username+"'. Already authenticated as "+_authentication);
         }
     }
 
     /* ------------------------------------------------------------ */
+    @Override
     public void logout() throws ServletException
     {
         if (_authentication instanceof Authentication.User)
             ((Authentication.User)_authentication).logout();
         _authentication=Authentication.UNAUTHENTICATED;
     }
-    
-    /* ------------------------------------------------------------ */
-    /**
-     * Merge in a new query string. The query string is merged with the existing parameters and {@link #setParameters(MultiMap)} and
-     * {@link #setQueryString(String)} are called with the result. The merge is according to the rules of the servlet dispatch forward method.
-     *
-     * @param query
-     *            The query string to merge into the request.
-     */
-    public void mergeQueryString(String query)
-    {
-        // extract parameters from dispatch query
-        MultiMap<String> parameters = new MultiMap<String>();
-        UrlEncoded.decodeTo(query,parameters, StringUtil.__UTF8); //have to assume UTF-8 because we can't know otherwise
 
-        boolean merge_old_query = false;
-
-        // Have we evaluated parameters
-        if (!_paramsExtracted)
-            extractParameters();
+    public void mergeQueryParameters(String newQuery, boolean updateQueryString)
+    {
+        MultiMap<String> newQueryParams = new MultiMap<>();
+        // Have to assume ENCODING because we can't know otherwise.
+        UrlEncoded.decodeTo(newQuery, newQueryParams, UrlEncoded.ENCODING, -1);
 
-        // Are there any existing parameters?
-        if (_parameters != null && _parameters.size() > 0)
+        MultiMap<String> oldQueryParams = _queryParameters;
+        if (oldQueryParams == null && _queryString != null)
         {
-            // Merge parameters; new parameters of the same name take precedence.
-            Iterator<Entry<String, Object>> iter = _parameters.entrySet().iterator();
-            while (iter.hasNext())
-            {
-                Map.Entry<String, Object> entry = iter.next();
-                String name = entry.getKey();
-
-                // If the names match, we will need to remake the query string
-                if (parameters.containsKey(name))
-                    merge_old_query = true;
-
-                // Add the old values to the new parameter map
-                Object values = entry.getValue();
-                for (int i = 0; i < LazyList.size(values); i++)
-                    parameters.add(name,LazyList.get(values,i));
-            }
+            oldQueryParams = new MultiMap<>();
+            UrlEncoded.decodeTo(_queryString, oldQueryParams, getQueryEncoding(), -1);
         }
 
-        if (_queryString != null && _queryString.length() > 0)
+        MultiMap<String> mergedQueryParams = newQueryParams;
+        if (oldQueryParams != null)
         {
-            if (merge_old_query)
-            {
-                StringBuilder overridden_query_string = new StringBuilder();
-                MultiMap<String> overridden_old_query = new MultiMap<String>();
-                UrlEncoded.decodeTo(_queryString,overridden_old_query,getQueryEncoding());//decode using any queryencoding set for the request
-                
-                
-                MultiMap<String> overridden_new_query = new MultiMap<String>();
-                UrlEncoded.decodeTo(query,overridden_new_query,StringUtil.__UTF8); //have to assume utf8 as we cannot know otherwise
-
-                Iterator<Entry<String, Object>> iter = overridden_old_query.entrySet().iterator();
-                while (iter.hasNext())
-                {
-                    Map.Entry<String, Object> entry = iter.next();
-                    String name = entry.getKey();
-                    if (!overridden_new_query.containsKey(name))
-                    {
-                        Object values = entry.getValue();
-                        for (int i = 0; i < LazyList.size(values); i++)
-                        {
-                            overridden_query_string.append("&").append(name).append("=").append(LazyList.get(values,i));
-                        }
-                    }
-                }
+            // Parameters values are accumulated.
+            mergedQueryParams = new MultiMap<>(newQueryParams);
+            mergedQueryParams.addAllValues(oldQueryParams);
+        }
 
-                query = query + overridden_query_string;
-            }
-            else
+        setQueryParameters(mergedQueryParams);
+        resetParameters();
+
+        if (updateQueryString)
+        {
+            // Build the new merged query string, parameters in the
+            // new query string hide parameters in the old query string.
+            StringBuilder mergedQuery = new StringBuilder(newQuery);
+            for (Map.Entry<String, List<String>> entry : mergedQueryParams.entrySet())
             {
-                query = query + "&" + _queryString;
+                if (newQueryParams.containsKey(entry.getKey()))
+                    continue;
+                for (String value : entry.getValue())
+                    mergedQuery.append("&").append(entry.getKey()).append("=").append(value);
             }
+
+            setQueryString(mergedQuery.toString());
         }
+    }
 
-        setParameters(parameters);
-        setQueryString(query);
+    /**
+     * @see javax.servlet.http.HttpServletRequest#upgrade(java.lang.Class)
+     */
+    @Override
+    public <T extends HttpUpgradeHandler> T upgrade(Class<T> handlerClass) throws IOException, ServletException
+    {
+        if (getContext() == null)
+            throw new ServletException ("Unable to instantiate "+handlerClass);
+
+        try
+        {
+            //Instantiate an instance and inject it
+            T h = getContext().createInstance(handlerClass);
+            
+            //TODO handle the rest of the upgrade process
+            
+            return h;
+        }
+        catch (Exception e)
+        {
+            if (e instanceof ServletException)
+                throw (ServletException)e;
+            throw new ServletException(e);
+        }
     }
 }
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceCache.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceCache.java
index 34afc71..0d7dbde 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceCache.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceCache.java
@@ -21,6 +21,8 @@ package org.eclipse.jetty.server;
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.channels.ReadableByteChannel;
 import java.util.Comparator;
 import java.util.SortedSet;
 import java.util.TreeSet;
@@ -29,15 +31,10 @@ import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicReference;
 
+import org.eclipse.jetty.http.DateGenerator;
 import org.eclipse.jetty.http.HttpContent;
-import org.eclipse.jetty.http.HttpContent.ResourceAsHttpContent;
-import org.eclipse.jetty.http.HttpFields;
 import org.eclipse.jetty.http.MimeTypes;
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.ByteArrayBuffer;
-import org.eclipse.jetty.io.View;
-import org.eclipse.jetty.io.nio.DirectNIOBuffer;
-import org.eclipse.jetty.io.nio.IndirectNIOBuffer;
+import org.eclipse.jetty.util.BufferUtil;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
 import org.eclipse.jetty.util.resource.Resource;
@@ -58,12 +55,12 @@ public class ResourceCache
     private final ResourceFactory _factory;
     private final ResourceCache _parent;
     private final MimeTypes _mimeTypes;
-    private final boolean _etags;
-
-    private boolean  _useFileMappedBuffer=true;
-    private int _maxCachedFileSize =4*1024*1024;
+    private final boolean _etagSupported;
+    private final boolean  _useFileMappedBuffer;
+    
+    private int _maxCachedFileSize =128*1024*1024;
     private int _maxCachedFiles=2048;
-    private int _maxCacheSize =32*1024*1024;
+    private int _maxCacheSize =256*1024*1024;
     
     /* ------------------------------------------------------------ */
     /** Constructor.
@@ -77,8 +74,8 @@ public class ResourceCache
         _cachedFiles=new AtomicInteger();
         _mimeTypes=mimeTypes;
         _parent=parent;
-        _etags=etags;
         _useFileMappedBuffer=useFileMappedBuffer;
+        _etagSupported=etags;
     }
 
     /* ------------------------------------------------------------ */
@@ -145,12 +142,6 @@ public class ResourceCache
     }
 
     /* ------------------------------------------------------------ */
-    public void setUseFileMappedBuffer(boolean useFileMappedBuffer)
-    {
-        _useFileMappedBuffer = useFileMappedBuffer;
-    }
-
-    /* ------------------------------------------------------------ */
     public void flushCache()
     {
         if (_cache!=null)
@@ -245,7 +236,7 @@ public class ResourceCache
             return content;
         }
         
-        return new HttpContent.ResourceAsHttpContent(resource,_mimeTypes.getMimeByExtension(resource.toString()),getMaxCachedFileSize(),_etags);
+        return new HttpContent.ResourceAsHttpContent(resource,_mimeTypes.getMimeByExtension(resource.toString()),getMaxCachedFileSize(),_etagSupported);
         
     }
     
@@ -288,23 +279,13 @@ public class ResourceCache
     }
     
     /* ------------------------------------------------------------ */
-    protected Buffer getIndirectBuffer(Resource resource)
+    protected ByteBuffer getIndirectBuffer(Resource resource)
     {
         try
         {
-            int len=(int)resource.length();
-            if (len<0)
-            {
-                LOG.warn("invalid resource: "+String.valueOf(resource)+" "+len);
-                return null;
-            }
-            Buffer buffer = new IndirectNIOBuffer(len);
-            InputStream is = resource.getInputStream();
-            buffer.readFrom(is,len);
-            is.close();
-            return buffer;
+            return BufferUtil.toBuffer(resource,true);
         }
-        catch(IOException e)
+        catch(IOException|IllegalArgumentException e)
         {
             LOG.warn(e);
             return null;
@@ -312,26 +293,16 @@ public class ResourceCache
     }
 
     /* ------------------------------------------------------------ */
-    protected Buffer getDirectBuffer(Resource resource)
+    protected ByteBuffer getDirectBuffer(Resource resource)
     {
         try
         {
-            if (_useFileMappedBuffer && resource.getFile()!=null) 
-                return new DirectNIOBuffer(resource.getFile());
-
-            int len=(int)resource.length();
-            if (len<0)
-            {
-                LOG.warn("invalid resource: "+String.valueOf(resource)+" "+len);
-                return null;
-            }
-            Buffer buffer = new DirectNIOBuffer(len);
-            InputStream is = resource.getInputStream();
-            buffer.readFrom(is,len);
-            is.close();
-            return buffer;
+            if (_useFileMappedBuffer && resource.getFile()!=null && resource.length()<Integer.MAX_VALUE) 
+                return BufferUtil.toMappedBuffer(resource.getFile());
+            
+            return BufferUtil.toBuffer(resource,true);
         }
-        catch(IOException e)
+        catch(IOException|IllegalArgumentException e)
         {
             LOG.warn(e);
             return null;
@@ -355,13 +326,13 @@ public class ResourceCache
         final int _length;
         final String _key;
         final long _lastModified;
-        final Buffer _lastModifiedBytes;
-        final Buffer _contentType;
-        final Buffer _etagBuffer;
+        final ByteBuffer _lastModifiedBytes;
+        final ByteBuffer _contentType;
+        final String _etag;
         
         volatile long _lastAccessed;
-        AtomicReference<Buffer> _indirectBuffer=new AtomicReference<Buffer>();
-        AtomicReference<Buffer> _directBuffer=new AtomicReference<Buffer>();
+        AtomicReference<ByteBuffer> _indirectBuffer=new AtomicReference<ByteBuffer>();
+        AtomicReference<ByteBuffer> _directBuffer=new AtomicReference<ByteBuffer>();
 
         /* ------------------------------------------------------------ */
         Content(String pathInContext,Resource resource)
@@ -369,17 +340,18 @@ public class ResourceCache
             _key=pathInContext;
             _resource=resource;
 
-            _contentType=_mimeTypes.getMimeByExtension(_resource.toString());
+            String mimeType = _mimeTypes.getMimeByExtension(_resource.toString());
+            _contentType=(mimeType==null?null:BufferUtil.toBuffer(mimeType));
             boolean exists=resource.exists();
             _lastModified=exists?resource.lastModified():-1;
-            _lastModifiedBytes=_lastModified<0?null:new ByteArrayBuffer(HttpFields.formatDate(_lastModified));
+            _lastModifiedBytes=_lastModified<0?null:BufferUtil.toBuffer(DateGenerator.formatDate(_lastModified));
             
             _length=exists?(int)resource.length():0;
             _cachedSize.addAndGet(_length);
             _cachedFiles.incrementAndGet();
             _lastAccessed=System.currentTimeMillis();
             
-            _etagBuffer=_etags?new ByteArrayBuffer(resource.getWeakETag()):null;
+            _etag=ResourceCache.this._etagSupported?resource.getWeakETag():null;
         }
 
 
@@ -402,15 +374,17 @@ public class ResourceCache
         }
 
         /* ------------------------------------------------------------ */
+        @Override
         public Resource getResource()
         {
             return _resource;
         }
 
         /* ------------------------------------------------------------ */
-        public Buffer getETag()
+        @Override
+        public String getETag()
         {
-            return _etagBuffer;
+            return _etag;
         }
         
         /* ------------------------------------------------------------ */
@@ -433,34 +407,38 @@ public class ResourceCache
             // Invalidate it
             _cachedSize.addAndGet(-_length);
             _cachedFiles.decrementAndGet();
-            _resource.release(); 
+            _resource.close(); 
         }
 
         /* ------------------------------------------------------------ */
-        public Buffer getLastModified()
+        @Override
+        public String getLastModified()
         {
-            return _lastModifiedBytes;
+            return BufferUtil.toString(_lastModifiedBytes);
         }
 
         /* ------------------------------------------------------------ */
-        public Buffer getContentType()
+        @Override
+        public String getContentType()
         {
-            return _contentType;
+            return BufferUtil.toString(_contentType);
         }
 
         /* ------------------------------------------------------------ */
+        @Override
         public void release()
         {
             // don't release while cached. Release when invalidated.
         }
 
         /* ------------------------------------------------------------ */
-        public Buffer getIndirectBuffer()
+        @Override
+        public ByteBuffer getIndirectBuffer()
         {
-            Buffer buffer = _indirectBuffer.get();
+            ByteBuffer buffer = _indirectBuffer.get();
             if (buffer==null)
             {
-                Buffer buffer2=ResourceCache.this.getIndirectBuffer(_resource);
+                ByteBuffer buffer2=ResourceCache.this.getIndirectBuffer(_resource);
                 
                 if (buffer2==null)
                     LOG.warn("Could not load "+this);
@@ -471,17 +449,18 @@ public class ResourceCache
             }
             if (buffer==null)
                 return null;
-            return new View(buffer);
+            return buffer.slice();
         }
         
 
         /* ------------------------------------------------------------ */
-        public Buffer getDirectBuffer()
+        @Override
+        public ByteBuffer getDirectBuffer()
         {
-            Buffer buffer = _directBuffer.get();
+            ByteBuffer buffer = _directBuffer.get();
             if (buffer==null)
             {
-                Buffer buffer2=ResourceCache.this.getDirectBuffer(_resource);
+                ByteBuffer buffer2=ResourceCache.this.getDirectBuffer(_resource);
 
                 if (buffer2==null)
                     LOG.warn("Could not load "+this);
@@ -492,31 +471,40 @@ public class ResourceCache
             }
             if (buffer==null)
                 return null;
-                        
-            return new View(buffer);
+            return buffer.asReadOnlyBuffer();
         }
         
         /* ------------------------------------------------------------ */
+        @Override
         public long getContentLength()
         {
             return _length;
         }
 
         /* ------------------------------------------------------------ */
+        @Override
         public InputStream getInputStream() throws IOException
         {
-            Buffer indirect = getIndirectBuffer();
-            if (indirect!=null && indirect.array()!=null)
-                return new ByteArrayInputStream(indirect.array(),indirect.getIndex(),indirect.length());
+            ByteBuffer indirect = getIndirectBuffer();
+            if (indirect!=null && indirect.hasArray())
+                return new ByteArrayInputStream(indirect.array(),indirect.arrayOffset()+indirect.position(),indirect.remaining());
            
             return _resource.getInputStream();
         }   
+        
+        /* ------------------------------------------------------------ */
+        @Override
+        public ReadableByteChannel getReadableByteChannel() throws IOException
+        {
+            return _resource.getReadableByteChannel();
+        }
+
 
         /* ------------------------------------------------------------ */
         @Override
         public String toString()
         {
-            return String.format("%s %s %d %s %s",_resource,_resource.exists(),_resource.lastModified(),_contentType,_lastModifiedBytes);
+            return String.format("CachedContent@%x{r=%s,e=%b,lm=%s,ct=%s}",hashCode(),_resource,_resource.exists(),BufferUtil.toString(_lastModifiedBytes),_contentType);
         }   
     }
 }
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java
index 7637147..93cc88b 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java
@@ -18,34 +18,40 @@
 
 package org.eclipse.jetty.server;
 
+import static org.eclipse.jetty.util.QuotedStringTokenizer.isQuoted;
+
 import java.io.IOException;
 import java.io.PrintWriter;
-import java.util.Collection;
+import java.nio.channels.IllegalSelectorException;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
-import java.util.EnumSet;
 import java.util.Enumeration;
+import java.util.Iterator;
 import java.util.Locale;
+import java.util.concurrent.atomic.AtomicInteger;
 
 import javax.servlet.RequestDispatcher;
 import javax.servlet.ServletOutputStream;
-import javax.servlet.SessionTrackingMode;
 import javax.servlet.http.Cookie;
 import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpSession;
 
+import org.eclipse.jetty.http.DateGenerator;
+import org.eclipse.jetty.http.HttpContent;
 import org.eclipse.jetty.http.HttpCookie;
+import org.eclipse.jetty.http.HttpField;
 import org.eclipse.jetty.http.HttpFields;
 import org.eclipse.jetty.http.HttpGenerator;
-import org.eclipse.jetty.http.HttpHeaderValues;
-import org.eclipse.jetty.http.HttpHeaders;
-import org.eclipse.jetty.http.HttpSchemes;
+import org.eclipse.jetty.http.HttpGenerator.ResponseInfo;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpHeaderValue;
+import org.eclipse.jetty.http.HttpScheme;
 import org.eclipse.jetty.http.HttpStatus;
 import org.eclipse.jetty.http.HttpURI;
-import org.eclipse.jetty.http.HttpVersions;
+import org.eclipse.jetty.http.HttpVersion;
 import org.eclipse.jetty.http.MimeTypes;
-import org.eclipse.jetty.io.BufferCache.CachedBuffer;
-import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.io.RuntimeIOException;
 import org.eclipse.jetty.server.handler.ErrorHandler;
 import org.eclipse.jetty.util.ByteArrayISO8859Writer;
 import org.eclipse.jetty.util.QuotedStringTokenizer;
@@ -54,20 +60,40 @@ import org.eclipse.jetty.util.URIUtil;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
 
-/** Response.
- * <p>
- * Implements {@link javax.servlet.http.HttpServletResponse} from the <code>javax.servlet.http</code> package.
- * </p>
+/**
+ * <p>{@link Response} provides the implementation for {@link HttpServletResponse}.</p>
  */
 public class Response implements HttpServletResponse
 {
-    private static final Logger LOG = Log.getLogger(Response.class);
+    private static final Logger LOG = Log.getLogger(Response.class);    
+    private static final String __COOKIE_DELIM="\",;\\ \t";
+    private final static String __01Jan1970_COOKIE = DateGenerator.formatCookieDate(0).trim();
+    private final static int __MIN_BUFFER_SIZE = 1;
+    
 
+    // Cookie building buffer. Reduce garbage for cookie using applications
+    private static final ThreadLocal<StringBuilder> __cookieBuilder = new ThreadLocal<StringBuilder>()
+    {
+       @Override
+       protected StringBuilder initialValue()
+       {
+          return new StringBuilder(128);
+       }
+    };
+
+    /* ------------------------------------------------------------ */
+    public static Response getResponse(HttpServletResponse response)
+    {
+        if (response instanceof Response)
+            return (Response)response;
+        return HttpChannel.getCurrentHttpChannel().getResponse();
+    }
     
-    public static final int
-        NONE=0,
-        STREAM=1,
-        WRITER=2;
+    
+    public enum OutputType
+    {
+        NONE, STREAM, WRITER
+    }
 
     /**
      * If a header name starts with this string,  the header (stripped of the prefix)
@@ -77,162 +103,375 @@ public class Response implements HttpServletResponse
     public final static String SET_INCLUDE_HEADER_PREFIX = "org.eclipse.jetty.server.include.";
 
     /**
-     * If this string is found within the comment of a cookie added with {@link #addCookie(Cookie)}, then the cookie 
+     * If this string is found within the comment of a cookie added with {@link #addCookie(Cookie)}, then the cookie
      * will be set as HTTP ONLY.
      */
-    public final static String HTTP_ONLY_COMMENT="__HTTP_ONLY__";
-    
-    
-    /* ------------------------------------------------------------ */
-    public static Response getResponse(HttpServletResponse response)
-    {
-        if (response instanceof Response)
-            return (Response)response;
+    public final static String HTTP_ONLY_COMMENT = "__HTTP_ONLY__";
 
-        return AbstractHttpConnection.getCurrentConnection().getResponse();
-    }
-    
-    private final AbstractHttpConnection _connection;
-    private int _status=SC_OK;
+    private final HttpChannel<?> _channel;
+    private final HttpFields _fields = new HttpFields();
+    private final AtomicInteger _include = new AtomicInteger();
+    private HttpOutput _out;
+    private int _status = HttpStatus.OK_200;
     private String _reason;
     private Locale _locale;
-    private String _mimeType;
-    private CachedBuffer _cachedMimeType;
+    private MimeTypes.Type _mimeType;
     private String _characterEncoding;
     private boolean _explicitEncoding;
     private String _contentType;
-    private volatile int _outputState;
-    private PrintWriter _writer;
+    private OutputType _outputType = OutputType.NONE;
+    private ResponseWriter _writer;
+    private long _contentLength = -1;
+    
 
-    /* ------------------------------------------------------------ */
-    /**
-     *
-     */
-    public Response(AbstractHttpConnection connection)
+    public Response(HttpChannel<?> channel, HttpOutput out)
     {
-        _connection=connection;
+        _channel = channel;
+        _out = out;
     }
 
+    protected HttpChannel<?> getHttpChannel()
+    {
+        return _channel;
+    }
 
-    /* ------------------------------------------------------------ */
-    /*
-     * @see javax.servlet.ServletResponse#reset()
-     */
     protected void recycle()
     {
-        _status=SC_OK;
-        _reason=null;
-        _locale=null;
-        _mimeType=null;
-        _cachedMimeType=null;
-        _characterEncoding=null;
+        _status = HttpStatus.OK_200;
+        _reason = null;
+        _locale = null;
+        _mimeType = null;
+        _characterEncoding = null;
+        _contentType = null;
+        _outputType = OutputType.NONE;
+        _contentLength = -1;
+        _out.reset();
+        _fields.clear();
         _explicitEncoding=false;
-        _contentType=null;
-        _writer=null;
-        _outputState=NONE;
     }
 
-    /* ------------------------------------------------------------ */
-    /*
-     * @see javax.servlet.http.HttpServletResponse#addCookie(javax.servlet.http.Cookie)
-     */
-    public void addCookie(HttpCookie cookie)
+    public void setHeaders(HttpContent httpContent)
+    {
+        Response response = _channel.getResponse();
+        String contentType = httpContent.getContentType();
+        if (contentType != null && !response.getHttpFields().containsKey(HttpHeader.CONTENT_TYPE.asString()))
+            setContentType(contentType);
+        
+        if (httpContent.getContentLength() > 0)
+            setLongContentLength(httpContent.getContentLength());
+
+        String lm = httpContent.getLastModified();
+        if (lm != null)
+            response.getHttpFields().put(HttpHeader.LAST_MODIFIED, lm);
+        else if (httpContent.getResource() != null)
+        {
+            long lml = httpContent.getResource().lastModified();
+            if (lml != -1)
+                response.getHttpFields().putDateField(HttpHeader.LAST_MODIFIED, lml);
+        }
+
+        String etag=httpContent.getETag();
+        if (etag!=null)
+            response.getHttpFields().put(HttpHeader.ETAG,etag);
+    }
+    
+    public HttpOutput getHttpOutput()
     {
-        _connection.getResponseFields().addSetCookie(cookie);
+        return _out;
     }
     
-    /* ------------------------------------------------------------ */
-    /*
-     * @see javax.servlet.http.HttpServletResponse#addCookie(javax.servlet.http.Cookie)
-     */
+    public void setHttpOutput(HttpOutput out)
+    {
+        _out=out;
+    }
+
+    public boolean isIncluding()
+    {
+        return _include.get() > 0;
+    }
+
+    public void include()
+    {
+        _include.incrementAndGet();
+    }
+
+    public void included()
+    {
+        _include.decrementAndGet();
+        if (_outputType == OutputType.WRITER)
+        {
+            _writer.reopen();
+        }
+        _out.reopen();
+    }
+
+    public void addCookie(HttpCookie cookie)
+    {
+        addSetCookie(
+                cookie.getName(),
+                cookie.getValue(),
+                cookie.getDomain(),
+                cookie.getPath(),
+                cookie.getMaxAge(),
+                cookie.getComment(),
+                cookie.isSecure(),
+                cookie.isHttpOnly(),
+                cookie.getVersion());;
+    }
+
+    @Override
     public void addCookie(Cookie cookie)
     {
-        String comment=cookie.getComment();
-        boolean http_only=false;
-        
-        if (comment!=null)
+        String comment = cookie.getComment();
+        boolean httpOnly = false;
+
+        if (comment != null)
         {
-            int i=comment.indexOf(HTTP_ONLY_COMMENT);
-            if (i>=0)
+            int i = comment.indexOf(HTTP_ONLY_COMMENT);
+            if (i >= 0)
             {
-                http_only=true;
-                comment=comment.replace(HTTP_ONLY_COMMENT,"").trim();
-                if (comment.length()==0)
-                    comment=null;
+                httpOnly = true;
+                comment = comment.replace(HTTP_ONLY_COMMENT, "").trim();
+                if (comment.length() == 0)
+                    comment = null;
             }
         }
-        _connection.getResponseFields().addSetCookie(cookie.getName(),
+        addSetCookie(cookie.getName(),
                 cookie.getValue(),
                 cookie.getDomain(),
                 cookie.getPath(),
                 cookie.getMaxAge(),
                 comment,
                 cookie.getSecure(),
-                http_only || cookie.isHttpOnly(),
+                httpOnly || cookie.isHttpOnly(),
                 cookie.getVersion());
     }
 
-    /* ------------------------------------------------------------ */
-    /*
-     * @see javax.servlet.http.HttpServletResponse#containsHeader(java.lang.String)
+
+    /**
+     * Format a set cookie value
+     *
+     * @param name the name
+     * @param value the value
+     * @param domain the domain
+     * @param path the path
+     * @param maxAge the maximum age
+     * @param comment the comment (only present on versions > 0)
+     * @param isSecure true if secure cookie
+     * @param isHttpOnly true if for http only
+     * @param version version of cookie logic to use (0 == default behavior)
      */
-    public boolean containsHeader(String name)
+    public void addSetCookie(
+            final String name,
+            final String value,
+            final String domain,
+            final String path,
+            final long maxAge,
+            final String comment,
+            final boolean isSecure,
+            final boolean isHttpOnly,
+            int version)
     {
-        return _connection.getResponseFields().containsKey(name);
+        // Check arguments
+        if (name == null || name.length() == 0)
+            throw new IllegalArgumentException("Bad cookie name");
+
+        // Format value and params
+        StringBuilder buf = __cookieBuilder.get();
+        buf.setLength(0);
+        
+        // Name is checked for legality by servlet spec, but can also be passed directly so check again for quoting
+        boolean quote_name=isQuoteNeededForCookie(name);
+        quoteOnlyOrAppend(buf,name,quote_name);
+        
+        buf.append('=');
+        
+        // Remember name= part to look for other matching set-cookie
+        String name_equals=buf.toString();
+
+        // Append the value
+        boolean quote_value=isQuoteNeededForCookie(value);
+        quoteOnlyOrAppend(buf,value,quote_value);
+
+        // Look for domain and path fields and check if they need to be quoted
+        boolean has_domain = domain!=null && domain.length()>0;
+        boolean quote_domain = has_domain && isQuoteNeededForCookie(domain);
+        boolean has_path = path!=null && path.length()>0;
+        boolean quote_path = has_path && isQuoteNeededForCookie(path);
+        
+        // Upgrade the version if we have a comment or we need to quote value/path/domain or if they were already quoted
+        if (version==0 && ( comment!=null || quote_name || quote_value || quote_domain || quote_path || isQuoted(name) || isQuoted(value) || isQuoted(path) || isQuoted(domain)))
+            version=1;
+
+        // Append version
+        if (version==1)
+            buf.append (";Version=1");
+        else if (version>1)
+            buf.append (";Version=").append(version);
+        
+        // Append path
+        if (has_path)
+        {
+            buf.append(";Path=");
+            quoteOnlyOrAppend(buf,path,quote_path);
+        }
+        
+        // Append domain
+        if (has_domain)
+        {
+            buf.append(";Domain=");
+            quoteOnlyOrAppend(buf,domain,quote_domain);
+        }
+
+        // Handle max-age and/or expires
+        if (maxAge >= 0)
+        {
+            // Always use expires
+            // This is required as some browser (M$ this means you!) don't handle max-age even with v1 cookies
+            buf.append(";Expires=");
+            if (maxAge == 0)
+                buf.append(__01Jan1970_COOKIE);
+            else
+                DateGenerator.formatCookieDate(buf, System.currentTimeMillis() + 1000L * maxAge);
+            
+            // for v1 cookies, also send max-age
+            if (version>=1)
+            {
+                buf.append(";Max-Age=");
+                buf.append(maxAge);
+            }
+        }
+
+        // add the other fields
+        if (isSecure)
+            buf.append(";Secure");
+        if (isHttpOnly)
+            buf.append(";HttpOnly");
+        if (comment != null)
+        {
+            buf.append(";Comment=");
+            quoteOnlyOrAppend(buf,comment,isQuoteNeededForCookie(comment));
+        }
+
+        // remove any existing set-cookie fields of same name
+        Iterator<HttpField> i=_fields.iterator();
+        while (i.hasNext())
+        {
+            HttpField field=i.next();
+            if (field.getHeader()==HttpHeader.SET_COOKIE)
+            {
+                String val = field.getValue();
+                if (val!=null && val.startsWith(name_equals))
+                {
+                    //existing cookie has same name, does it also match domain and path?
+                    if (((!has_domain && !val.contains("Domain")) || (has_domain && val.contains(domain))) &&
+                        ((!has_path && !val.contains("Path")) || (has_path && val.contains(path))))
+                    {
+                        i.remove();
+                    }
+                }
+            }
+        }
+        
+        // add the set cookie
+        _fields.add(HttpHeader.SET_COOKIE.toString(), buf.toString());
+
+        // Expire responses with set-cookie headers so they do not get cached.
+        _fields.put(HttpHeader.EXPIRES.toString(), DateGenerator.__01Jan1970);
     }
 
+
     /* ------------------------------------------------------------ */
-    /*
-     * @see javax.servlet.http.HttpServletResponse#encodeURL(java.lang.String)
+    /** Does a cookie value need to be quoted?
+     * @param s value string
+     * @return true if quoted;
+     * @throws IllegalArgumentException If there a control characters in the string
      */
+    private static boolean isQuoteNeededForCookie(String s)
+    {
+        if (s==null || s.length()==0)
+            return true;
+        
+        if (QuotedStringTokenizer.isQuoted(s))
+            return false;
+
+        for (int i=0;i<s.length();i++)
+        {
+            char c = s.charAt(i);
+            if (__COOKIE_DELIM.indexOf(c)>=0)
+                return true;
+            
+            if (c<0x20 || c>=0x7f)
+                throw new IllegalArgumentException("Illegal character in cookie value");
+        }
+
+        return false;
+    }
+    
+    
+    private static void quoteOnlyOrAppend(StringBuilder buf, String s, boolean quote)
+    {
+        if (quote)
+            QuotedStringTokenizer.quoteOnly(buf,s);
+        else
+            buf.append(s);
+    }
+    
+    @Override
+    public boolean containsHeader(String name)
+    {
+        return _fields.containsKey(name);
+    }
+
+    @Override
     public String encodeURL(String url)
     {
-        final Request request=_connection.getRequest();
+        final Request request = _channel.getRequest();
         SessionManager sessionManager = request.getSessionManager();
-        if (sessionManager==null)
+        if (sessionManager == null)
             return url;
-        
+
         HttpURI uri = null;
         if (sessionManager.isCheckingRemoteSessionIdEncoding() && URIUtil.hasScheme(url))
         {
             uri = new HttpURI(url);
             String path = uri.getPath();
-            path = (path == null?"":path);
-            int port=uri.getPort();
-            if (port<0) 
-                port = HttpSchemes.HTTPS.equalsIgnoreCase(uri.getScheme())?443:80;
+            path = (path == null ? "" : path);
+            int port = uri.getPort();
+            if (port < 0)
+                port = HttpScheme.HTTPS.asString().equalsIgnoreCase(uri.getScheme()) ? 443 : 80;
             if (!request.getServerName().equalsIgnoreCase(uri.getHost()) ||
-                request.getServerPort()!=port ||
-                !path.startsWith(request.getContextPath())) //TODO the root context path is "", with which every non null string starts
+                    request.getServerPort() != port ||
+                    !path.startsWith(request.getContextPath())) //TODO the root context path is "", with which every non null string starts
                 return url;
         }
-        
+
         String sessionURLPrefix = sessionManager.getSessionIdPathParameterNamePrefix();
-        if (sessionURLPrefix==null)
+        if (sessionURLPrefix == null)
             return url;
 
-        if (url==null)
+        if (url == null)
             return null;
-        
+
         // should not encode if cookies in evidence
         if ((sessionManager.isUsingCookies() && request.isRequestedSessionIdFromCookie()) || !sessionManager.isUsingURLs()) 
         {
-            int prefix=url.indexOf(sessionURLPrefix);
-            if (prefix!=-1)
+            int prefix = url.indexOf(sessionURLPrefix);
+            if (prefix != -1)
             {
-                int suffix=url.indexOf("?",prefix);
-                if (suffix<0)
-                    suffix=url.indexOf("#",prefix);
+                int suffix = url.indexOf("?", prefix);
+                if (suffix < 0)
+                    suffix = url.indexOf("#", prefix);
 
-                if (suffix<=prefix)
-                    return url.substring(0,prefix);
-                return url.substring(0,prefix)+url.substring(suffix);
+                if (suffix <= prefix)
+                    return url.substring(0, prefix);
+                return url.substring(0, prefix) + url.substring(suffix);
             }
             return url;
         }
 
         // get session;
-        HttpSession session=request.getSession(false);
+        HttpSession session = request.getSession(false);
 
         // no session
         if (session == null)
@@ -242,91 +481,105 @@ public class Response implements HttpServletResponse
         if (!sessionManager.isValid(session))
             return url;
 
-        String id=sessionManager.getNodeId(session);
+        String id = sessionManager.getNodeId(session);
 
         if (uri == null)
-                uri = new HttpURI(url);
-     
-        
+            uri = new HttpURI(url);
+
+
         // Already encoded
-        int prefix=url.indexOf(sessionURLPrefix);
-        if (prefix!=-1)
+        int prefix = url.indexOf(sessionURLPrefix);
+        if (prefix != -1)
         {
-            int suffix=url.indexOf("?",prefix);
-            if (suffix<0)
-                suffix=url.indexOf("#",prefix);
-
-            if (suffix<=prefix)
-                return url.substring(0,prefix+sessionURLPrefix.length())+id;
-            return url.substring(0,prefix+sessionURLPrefix.length())+id+
-                url.substring(suffix);
+            int suffix = url.indexOf("?", prefix);
+            if (suffix < 0)
+                suffix = url.indexOf("#", prefix);
+
+            if (suffix <= prefix)
+                return url.substring(0, prefix + sessionURLPrefix.length()) + id;
+            return url.substring(0, prefix + sessionURLPrefix.length()) + id +
+                    url.substring(suffix);
         }
 
         // edit the session
-        int suffix=url.indexOf('?');
-        if (suffix<0)
-            suffix=url.indexOf('#');
-        if (suffix<0) 
-        {          
-            return url+ 
-                   ((HttpSchemes.HTTPS.equalsIgnoreCase(uri.getScheme()) || HttpSchemes.HTTP.equalsIgnoreCase(uri.getScheme())) && uri.getPath()==null?"/":"") + //if no path, insert the root path
-                   sessionURLPrefix+id;
+        int suffix = url.indexOf('?');
+        if (suffix < 0)
+            suffix = url.indexOf('#');
+        if (suffix < 0)
+        {
+            return url +
+                    ((HttpScheme.HTTPS.is(uri.getScheme()) || HttpScheme.HTTP.is(uri.getScheme())) && uri.getPath() == null ? "/" : "") + //if no path, insert the root path
+                    sessionURLPrefix + id;
         }
-     
-        
-        return url.substring(0,suffix)+
-            ((HttpSchemes.HTTPS.equalsIgnoreCase(uri.getScheme()) || HttpSchemes.HTTP.equalsIgnoreCase(uri.getScheme())) && uri.getPath()==null?"/":"")+ //if no path so insert the root path
-            sessionURLPrefix+id+url.substring(suffix);
+
+
+        return url.substring(0, suffix) +
+                ((HttpScheme.HTTPS.is(uri.getScheme()) || HttpScheme.HTTP.is(uri.getScheme())) && uri.getPath() == null ? "/" : "") + //if no path so insert the root path
+                sessionURLPrefix + id + url.substring(suffix);
     }
 
-    /* ------------------------------------------------------------ */
-    /**
-     * @see javax.servlet.http.HttpServletResponse#encodeRedirectURL(java.lang.String)
-     */
+    @Override
     public String encodeRedirectURL(String url)
     {
         return encodeURL(url);
     }
 
-    /* ------------------------------------------------------------ */
+    @Override
     @Deprecated
     public String encodeUrl(String url)
     {
         return encodeURL(url);
     }
 
-    /* ------------------------------------------------------------ */
+    @Override
     @Deprecated
     public String encodeRedirectUrl(String url)
     {
         return encodeRedirectURL(url);
     }
 
-    /* ------------------------------------------------------------ */
-    /*
-     * @see javax.servlet.http.HttpServletResponse#sendError(int, java.lang.String)
-     */
+    @Override
+    public void sendError(int sc) throws IOException
+    {
+        sendError(sc, null);
+    }
+
+    @Override
     public void sendError(int code, String message) throws IOException
     {
-    	if (_connection.isIncluding())
-    		return;
+        if (isIncluding())
+            return;
+
+        switch(code)
+        {
+            case -1:
+                _channel.abort();
+                return;
+            case 102:
+                sendProcessing();
+                return;
+            default:
+        }
 
         if (isCommitted())
             LOG.warn("Committed before "+code+" "+message);
 
         resetBuffer();
         _characterEncoding=null;
-        setHeader(HttpHeaders.EXPIRES,null);
-        setHeader(HttpHeaders.LAST_MODIFIED,null);
-        setHeader(HttpHeaders.CACHE_CONTROL,null);
-        setHeader(HttpHeaders.CONTENT_TYPE,null);
-        setHeader(HttpHeaders.CONTENT_LENGTH,null);
-
-        _outputState=NONE;
-        setStatus(code,message);
-
+        setHeader(HttpHeader.EXPIRES,null);
+        setHeader(HttpHeader.LAST_MODIFIED,null);
+        setHeader(HttpHeader.CACHE_CONTROL,null);
+        setHeader(HttpHeader.CONTENT_TYPE,null);
+        setHeader(HttpHeader.CONTENT_LENGTH,null);
+
+        _outputType = OutputType.NONE;
+        setStatus(code);
+        _reason=message;
+
+        Request request = _channel.getRequest();
+        Throwable cause = (Throwable)request.getAttribute(Dispatcher.ERROR_EXCEPTION);
         if (message==null)
-            message=HttpStatus.getMessage(code);
+            message=cause==null?HttpStatus.getMessage(code):cause.toString();
 
         // If we are allowed to have a body
         if (code!=SC_NO_CONTENT &&
@@ -334,93 +587,65 @@ public class Response implements HttpServletResponse
             code!=SC_PARTIAL_CONTENT &&
             code>=SC_OK)
         {
-            Request request = _connection.getRequest();
-
-            ErrorHandler error_handler = null;
-            ContextHandler.Context context = request.getContext();
-            if (context!=null)
-                error_handler=context.getContextHandler().getErrorHandler();
-            if (error_handler==null)
-                error_handler = _connection.getConnector().getServer().getBean(ErrorHandler.class);
+            ErrorHandler error_handler = ErrorHandler.getErrorHandler(_channel.getServer(),request.getContext()==null?null:request.getContext().getContextHandler());
             if (error_handler!=null)
             {
                 request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE,new Integer(code));
                 request.setAttribute(RequestDispatcher.ERROR_MESSAGE, message);
                 request.setAttribute(RequestDispatcher.ERROR_REQUEST_URI, request.getRequestURI());
                 request.setAttribute(RequestDispatcher.ERROR_SERVLET_NAME,request.getServletName());
-                error_handler.handle(null,_connection.getRequest(),_connection.getRequest(),this );
+                error_handler.handle(null,_channel.getRequest(),_channel.getRequest(),this );
             }
             else
             {
-                setHeader(HttpHeaders.CACHE_CONTROL, "must-revalidate,no-cache,no-store");
-                setContentType(MimeTypes.TEXT_HTML_8859_1);
-                ByteArrayISO8859Writer writer= new ByteArrayISO8859Writer(2048);
-                if (message != null)
+                setHeader(HttpHeader.CACHE_CONTROL, "must-revalidate,no-cache,no-store");
+                setContentType(MimeTypes.Type.TEXT_HTML_8859_1.toString());
+                try (ByteArrayISO8859Writer writer= new ByteArrayISO8859Writer(2048);)
                 {
-                    message= StringUtil.replace(message, "&", "&");
-                    message= StringUtil.replace(message, "<", "<");
-                    message= StringUtil.replace(message, ">", ">");
-                }
-                String uri= request.getRequestURI();
-                if (uri!=null)
-                {
-                    uri= StringUtil.replace(uri, "&", "&");
-                    uri= StringUtil.replace(uri, "<", "<");
-                    uri= StringUtil.replace(uri, ">", ">");
+                    message=StringUtil.sanitizeXmlString(message);
+                    String uri= request.getRequestURI();
+                    uri=StringUtil.sanitizeXmlString(uri);
+
+                    writer.write("<html>\n<head>\n<meta http-equiv=\"Content-Type\" content=\"text/html;charset=ISO-8859-1\"/>\n");
+                    writer.write("<title>Error ");
+                    writer.write(Integer.toString(code));
+                    writer.write(' ');
+                    if (message==null)
+                        writer.write(message);
+                    writer.write("</title>\n</head>\n<body>\n<h2>HTTP ERROR: ");
+                    writer.write(Integer.toString(code));
+                    writer.write("</h2>\n<p>Problem accessing ");
+                    writer.write(uri);
+                    writer.write(". Reason:\n<pre>    ");
+                    writer.write(message);
+                    writer.write("</pre>");
+                    writer.write("</p>\n<hr /><i><small>Powered by Jetty://</small></i>");
+                    writer.write("\n</body>\n</html>\n");
+
+                    writer.flush();
+                    setContentLength(writer.size());
+                    try (ServletOutputStream outputStream = getOutputStream())
+                    {
+                        writer.writeTo(outputStream);
+                        writer.destroy();
+                    }
                 }
-
-                writer.write("<html>\n<head>\n<meta http-equiv=\"Content-Type\" content=\"text/html;charset=ISO-8859-1\"/>\n");
-                writer.write("<title>Error ");
-                writer.write(Integer.toString(code));
-                writer.write(' ');
-                if (message==null)
-                    message=HttpStatus.getMessage(code);
-                writer.write(message);
-                writer.write("</title>\n</head>\n<body>\n<h2>HTTP ERROR: ");
-                writer.write(Integer.toString(code));
-                writer.write("</h2>\n<p>Problem accessing ");
-                writer.write(uri);
-                writer.write(". Reason:\n<pre>    ");
-                writer.write(message);
-                writer.write("</pre>");
-                writer.write("</p>\n<hr /><i><small>Powered by Jetty://</small></i>");
-
-                for (int i= 0; i < 20; i++)
-                    writer.write("\n                                                ");
-                writer.write("\n</body>\n</html>\n");
-
-                writer.flush();
-                setContentLength(writer.size());
-                writer.writeTo(getOutputStream());
-                writer.destroy();
             }
         }
         else if (code!=SC_PARTIAL_CONTENT)
         {
-            _connection.getRequestFields().remove(HttpHeaders.CONTENT_TYPE_BUFFER);
-            _connection.getRequestFields().remove(HttpHeaders.CONTENT_LENGTH_BUFFER);
+            // TODO work out why this is required?
+            _channel.getRequest().getHttpFields().remove(HttpHeader.CONTENT_TYPE);
+            _channel.getRequest().getHttpFields().remove(HttpHeader.CONTENT_LENGTH);
             _characterEncoding=null;
             _mimeType=null;
-            _cachedMimeType=null;
         }
 
-        complete();
+        closeOutput();
     }
 
-    /* ------------------------------------------------------------ */
-    /*
-     * @see javax.servlet.http.HttpServletResponse#sendError(int)
-     */
-    public void sendError(int sc) throws IOException
-    {
-        if (sc==102)
-            sendProcessing();
-        else
-            sendError(sc,null);
-    }
-
-    /* ------------------------------------------------------------ */
-    /* Send a 102-Processing response.
+    /**
+     * Sends a 102-Processing response.
      * If the connection is a HTTP connection, the version is 1.1 and the
      * request has a Expect header starting with 102, then a 102 response is
      * sent. This indicates that the request still be processed and real response
@@ -429,34 +654,41 @@ public class Response implements HttpServletResponse
      */
     public void sendProcessing() throws IOException
     {
-        if (_connection.isExpecting102Processing() && !isCommitted())
-            ((HttpGenerator)_connection.getGenerator()).send1xx(HttpStatus.PROCESSING_102);
+        if (_channel.isExpecting102Processing() && !isCommitted())
+        {
+            _channel.sendResponse(HttpGenerator.PROGRESS_102_INFO, null, true);
+        }
     }
-
-    /* ------------------------------------------------------------ */
-    /*
-     * @see javax.servlet.http.HttpServletResponse#sendRedirect(java.lang.String)
+    
+    /**
+     * Sends a response with one of the 300 series redirection codes.
+     * @param code
+     * @param location
+     * @throws IOException
      */
-    public void sendRedirect(String location) throws IOException
+    public void sendRedirect(int code, String location) throws IOException
     {
-    	if (_connection.isIncluding())
-    		return;
+        if ((code < HttpServletResponse.SC_MULTIPLE_CHOICES) || (code >= HttpServletResponse.SC_BAD_REQUEST))
+            throw new IllegalArgumentException("Not a 3xx redirect code");
+        
+        if (isIncluding())
+            return;
 
-        if (location==null)
+        if (location == null)
             throw new IllegalArgumentException();
 
         if (!URIUtil.hasScheme(location))
         {
-            StringBuilder buf = _connection.getRequest().getRootURL();
+            StringBuilder buf = _channel.getRequest().getRootURL();
             if (location.startsWith("/"))
             {
-            	// absolute in context
+                // absolute in context
                 location=URIUtil.canonicalPath(location);
             }
             else
             {
-            	// relative to request
-                String path=_connection.getRequest().getRequestURI();
+                // relative to request
+                String path=_channel.getRequest().getRequestURI();
                 String parent=(path.endsWith("/"))?path:URIUtil.parentPath(path);
                 location=URIUtil.canonicalPath(URIUtil.addPaths(parent,location));
                 if (!location.startsWith("/"))
@@ -469,622 +701,518 @@ public class Response implements HttpServletResponse
             
             location=buf.toString();
         }
-        
+
         resetBuffer();
-        setHeader(HttpHeaders.LOCATION,location);
-        setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);
-        complete();
+        setHeader(HttpHeader.LOCATION, location);
+        setStatus(code);
+        closeOutput();
+    }
 
+    @Override
+    public void sendRedirect(String location) throws IOException
+    {
+        sendRedirect(HttpServletResponse.SC_MOVED_TEMPORARILY, location);
     }
 
-    /* ------------------------------------------------------------ */
-    /*
-     * @see javax.servlet.http.HttpServletResponse#setDateHeader(java.lang.String, long)
-     */
+    @Override
     public void setDateHeader(String name, long date)
     {
-        if (!_connection.isIncluding())
-            _connection.getResponseFields().putDateField(name, date);
+        if (!isIncluding())
+            _fields.putDateField(name, date);
     }
 
-    /* ------------------------------------------------------------ */
-    /*
-     * @see javax.servlet.http.HttpServletResponse#addDateHeader(java.lang.String, long)
-     */
+    @Override
     public void addDateHeader(String name, long date)
     {
-        if (!_connection.isIncluding())
-            _connection.getResponseFields().addDateField(name, date);
+        if (!isIncluding())
+            _fields.addDateField(name, date);
     }
 
-    /* ------------------------------------------------------------ */
-    /*
-     * @see javax.servlet.http.HttpServletResponse#setHeader(java.lang.String, java.lang.String)
-     */
+    public void setHeader(HttpHeader name, String value)
+    {
+        if (HttpHeader.CONTENT_TYPE == name)
+            setContentType(value);
+        else
+        {
+            if (isIncluding())
+                return;
+
+            _fields.put(name, value);
+
+            if (HttpHeader.CONTENT_LENGTH == name)
+            {
+                if (value == null)
+                    _contentLength = -1l;
+                else
+                    _contentLength = Long.parseLong(value);
+            }
+        }
+    }
+
+    @Override
     public void setHeader(String name, String value)
     {
-        if (HttpHeaders.CONTENT_TYPE.equalsIgnoreCase(name))
+        if (HttpHeader.CONTENT_TYPE.is(name))
             setContentType(value);
         else
         {
-            if (_connection.isIncluding())
+            if (isIncluding())
             {
                 if (name.startsWith(SET_INCLUDE_HEADER_PREFIX))
-                    name=name.substring(SET_INCLUDE_HEADER_PREFIX.length());
+                    name = name.substring(SET_INCLUDE_HEADER_PREFIX.length());
                 else
                     return;
             }
-            _connection.getResponseFields().put(name, value);
-            if (HttpHeaders.CONTENT_LENGTH.equalsIgnoreCase(name))
+            _fields.put(name, value);
+            if (HttpHeader.CONTENT_LENGTH.is(name))
             {
-                if (value==null)
-                    _connection._generator.setContentLength(-1);
+                if (value == null)
+                    _contentLength = -1l;
                 else
-                    _connection._generator.setContentLength(Long.parseLong(value));
+                    _contentLength = Long.parseLong(value);
             }
         }
     }
 
-
-    /* ------------------------------------------------------------ */
+    @Override
     public Collection<String> getHeaderNames()
     {
-        final HttpFields fields=_connection.getResponseFields();
+        final HttpFields fields = _fields;
         return fields.getFieldNamesCollection();
     }
-    
-    /* ------------------------------------------------------------ */
-    /*
-     */
+
+    @Override
     public String getHeader(String name)
     {
-        return _connection.getResponseFields().getStringField(name);
+        return _fields.getStringField(name);
     }
 
-    /* ------------------------------------------------------------ */
-    /*
-     */
+    @Override
     public Collection<String> getHeaders(String name)
     {
-        final HttpFields fields=_connection.getResponseFields();
-        Collection<String> i = fields.getValuesCollection(name);
-        if (i==null)
-            return Collections.EMPTY_LIST;
+        final HttpFields fields = _fields;
+        Collection<String> i = fields.getValuesList(name);
+        if (i == null)
+            return Collections.emptyList();
         return i;
     }
 
-    /* ------------------------------------------------------------ */
-    /*
-     * @see javax.servlet.http.HttpServletResponse#addHeader(java.lang.String, java.lang.String)
-     */
+    @Override
     public void addHeader(String name, String value)
     {
-
-        if (_connection.isIncluding())
+        if (isIncluding())
         {
             if (name.startsWith(SET_INCLUDE_HEADER_PREFIX))
-                name=name.substring(SET_INCLUDE_HEADER_PREFIX.length());
+                name = name.substring(SET_INCLUDE_HEADER_PREFIX.length());
             else
                 return;
         }
 
-        if (HttpHeaders.CONTENT_TYPE.equalsIgnoreCase(name))
+        if (HttpHeader.CONTENT_TYPE.is(name))
         {
             setContentType(value);
             return;
         }
         
-        _connection.getResponseFields().add(name, value);
-        if (HttpHeaders.CONTENT_LENGTH.equalsIgnoreCase(name))
-            _connection._generator.setContentLength(Long.parseLong(value));
+        if (HttpHeader.CONTENT_LENGTH.is(name))
+        {
+            setHeader(name,value);
+            return;
+        }
+        
+        _fields.add(name, value);
     }
 
-    /* ------------------------------------------------------------ */
-    /*
-     * @see javax.servlet.http.HttpServletResponse#setIntHeader(java.lang.String, int)
-     */
+    @Override
     public void setIntHeader(String name, int value)
     {
-        if (!_connection.isIncluding())
+        if (!isIncluding())
         {
-            _connection.getResponseFields().putLongField(name, value);
-            if (HttpHeaders.CONTENT_LENGTH.equalsIgnoreCase(name))
-                _connection._generator.setContentLength(value);
+            _fields.putLongField(name, value);
+            if (HttpHeader.CONTENT_LENGTH.is(name))
+                _contentLength = value;
         }
     }
 
-    /* ------------------------------------------------------------ */
-    /*
-     * @see javax.servlet.http.HttpServletResponse#addIntHeader(java.lang.String, int)
-     */
+    @Override
     public void addIntHeader(String name, int value)
     {
-        if (!_connection.isIncluding())
+        if (!isIncluding())
         {
-            _connection.getResponseFields().addLongField(name, value);
-            if (HttpHeaders.CONTENT_LENGTH.equalsIgnoreCase(name))
-                _connection._generator.setContentLength(value);
+            _fields.add(name, Integer.toString(value));
+            if (HttpHeader.CONTENT_LENGTH.is(name))
+                _contentLength = value;
         }
     }
 
-    /* ------------------------------------------------------------ */
-    /*
-     * @see javax.servlet.http.HttpServletResponse#setStatus(int)
-     */
+    @Override
     public void setStatus(int sc)
     {
-        setStatus(sc,null);
+        if (sc <= 0)
+            throw new IllegalArgumentException();
+        if (!isIncluding())
+        {
+            _status = sc;
+            _reason = null;
+        }
     }
 
-    /* ------------------------------------------------------------ */
-    /*
-     * @see javax.servlet.http.HttpServletResponse#setStatus(int, java.lang.String)
-     */
+    @Override
+    @Deprecated
     public void setStatus(int sc, String sm)
     {
-        if (sc<=0)
+        setStatusWithReason(sc,sm);
+    }
+    
+    public void setStatusWithReason(int sc, String sm)
+    {
+        if (sc <= 0)
             throw new IllegalArgumentException();
-        if (!_connection.isIncluding())
+        if (!isIncluding())
         {
-            _status=sc;
-            _reason=sm;
+            _status = sc;
+            _reason = sm;
         }
     }
 
-    /* ------------------------------------------------------------ */
-    /*
-     * @see javax.servlet.ServletResponse#getCharacterEncoding()
-     */
+    @Override
     public String getCharacterEncoding()
     {
-        if (_characterEncoding==null)
-            _characterEncoding=StringUtil.__ISO_8859_1;
-        return _characterEncoding;
-    }
-    
-    /* ------------------------------------------------------------ */
-    String getSetCharacterEncoding()
-    {
+        if (_characterEncoding == null)
+            _characterEncoding = StringUtil.__ISO_8859_1;
         return _characterEncoding;
     }
 
-    /* ------------------------------------------------------------ */
-    /*
-     * @see javax.servlet.ServletResponse#getContentType()
-     */
+    @Override
     public String getContentType()
     {
         return _contentType;
     }
 
-    /* ------------------------------------------------------------ */
-    /*
-     * @see javax.servlet.ServletResponse#getOutputStream()
-     */
+    @Override
     public ServletOutputStream getOutputStream() throws IOException
     {
-        if (_outputState!=NONE && _outputState!=STREAM)
+        if (_outputType == OutputType.WRITER)
             throw new IllegalStateException("WRITER");
-
-        ServletOutputStream out = _connection.getOutputStream();
-        _outputState=STREAM;
-        return out;
+        _outputType = OutputType.STREAM;
+        return _out;
     }
 
-    /* ------------------------------------------------------------ */
     public boolean isWriting()
     {
-        return _outputState==WRITER;
-    }
-
-    /* ------------------------------------------------------------ */
-    public boolean isOutputing()
-    {
-        return _outputState!=NONE;
+        return _outputType == OutputType.WRITER;
     }
 
-    /* ------------------------------------------------------------ */
-    /*
-     * @see javax.servlet.ServletResponse#getWriter()
-     */
+    @Override
     public PrintWriter getWriter() throws IOException
     {
-        if (_outputState!=NONE && _outputState!=WRITER)
+        if (_outputType == OutputType.STREAM)
             throw new IllegalStateException("STREAM");
 
-        /* if there is no writer yet */
-        if (_writer==null)
+        if (_outputType == OutputType.NONE)
         {
             /* get encoding from Content-Type header */
             String encoding = _characterEncoding;
-
-            if (encoding==null)
+            if (encoding == null)
             {
-                /* implementation of educated defaults */
-                if(_cachedMimeType != null)
-                    encoding = MimeTypes.getCharsetFromContentType(_cachedMimeType);
-
-                if (encoding==null)
-                    encoding = StringUtil.__ISO_8859_1;
-
-                setCharacterEncoding(encoding);
-            }
-
-            /* construct Writer using correct encoding */
-            _writer = _connection.getPrintWriter(encoding);
-        }
-        _outputState=WRITER;
-        return _writer;
-    }
-
-    /* ------------------------------------------------------------ */
-    /*
-     * @see javax.servlet.ServletResponse#setCharacterEncoding(java.lang.String)
-     */
-    public void setCharacterEncoding(String encoding)
-    {
-    	if (_connection.isIncluding())
-    		return;
-
-        if (this._outputState==0 && !isCommitted())
-        {
-            _explicitEncoding=true;
-
-            if (encoding==null)
-            {
-                // Clear any encoding.
-                if (_characterEncoding!=null)
+                if (_mimeType!=null && _mimeType.isCharsetAssumed())
+                    encoding=_mimeType.getCharset().toString();
+                else
                 {
-                    _characterEncoding=null;
-                    if (_cachedMimeType!=null)
-                        _contentType=_cachedMimeType.toString();
-                    else if (_mimeType!=null)
-                        _contentType=_mimeType;
-                    else
-                        _contentType=null;
-
-                    if (_contentType==null)
-                        _connection.getResponseFields().remove(HttpHeaders.CONTENT_TYPE_BUFFER);
-                    else
-                        _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
+                    encoding = MimeTypes.inferCharsetFromContentType(_contentType);
+                    if (encoding == null)
+                        encoding = StringUtil.__ISO_8859_1;
+                    setCharacterEncoding(encoding,false);
                 }
             }
+            
+            if (_writer != null && _writer.isFor(encoding))
+                _writer.reopen();
             else
             {
-                // No, so just add this one to the mimetype
-                _characterEncoding=encoding;
-                if (_contentType!=null)
-                {
-                    int i0=_contentType.indexOf(';');
-                    if (i0<0)
-                    {
-                        _contentType=null;
-                        if(_cachedMimeType!=null)
-                        {
-                            CachedBuffer content_type = _cachedMimeType.getAssociate(_characterEncoding);
-                            if (content_type!=null)
-                            {
-                                _contentType=content_type.toString();
-                                _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,content_type);
-                            }
-                        }
-
-                        if (_contentType==null)
-                        {
-                            _contentType = _mimeType+";charset="+QuotedStringTokenizer.quoteIfNeeded(_characterEncoding,";= ");
-                            _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
-                        }
-                    }
-                    else
-                    {
-                        int i1=_contentType.indexOf("charset=",i0);
-                        if (i1<0)
-                        {
-                            _contentType = _contentType+";charset="+QuotedStringTokenizer.quoteIfNeeded(_characterEncoding,";= ");
-                        }
-                        else
-                        {
-                            int i8=i1+8;
-                            int i2=_contentType.indexOf(" ",i8);
-                            if (i2<0)
-                                _contentType=_contentType.substring(0,i8)+QuotedStringTokenizer.quoteIfNeeded(_characterEncoding,";= ");
-                            else
-                                _contentType=_contentType.substring(0,i8)+QuotedStringTokenizer.quoteIfNeeded(_characterEncoding,";= ")+_contentType.substring(i2);
-                        }
-                        _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
-                    }
-                }
+                if (StringUtil.__ISO_8859_1.equalsIgnoreCase(encoding))
+                    _writer = new ResponseWriter(new Iso88591HttpWriter(_out),encoding);
+                else if (StringUtil.__UTF8.equalsIgnoreCase(encoding))
+                    _writer = new ResponseWriter(new Utf8HttpWriter(_out),encoding);
+                else
+                    _writer = new ResponseWriter(new EncodingHttpWriter(_out, encoding),encoding);
             }
+            
+            // Set the output type at the end, because setCharacterEncoding() checks for it
+            _outputType = OutputType.WRITER;
         }
+        return _writer;
     }
 
-    /* ------------------------------------------------------------ */
-    /*
-     * @see javax.servlet.ServletResponse#setContentLength(int)
-     */
+    @Override
     public void setContentLength(int len)
     {
         // Protect from setting after committed as default handling
         // of a servlet HEAD request ALWAYS sets _content length, even
         // if the getHandling committed the response!
-        if (isCommitted() || _connection.isIncluding())
+        if (isCommitted() || isIncluding())
             return;
-        _connection._generator.setContentLength(len);
-        if (len>0)
+
+        _contentLength = len;
+        if (_contentLength > 0)
         {
-            _connection.getResponseFields().putLongField(HttpHeaders.CONTENT_LENGTH, len);
-            if (_connection._generator.isAllContentWritten())
+            long written = _out.getWritten();
+            if (written > len)
+                throw new IllegalArgumentException("setContentLength(" + len + ") when already written " + written);
+            
+            _fields.putLongField(HttpHeader.CONTENT_LENGTH, len);
+            if (isAllContentWritten(written))
             {
-                if (_outputState==WRITER)
-                    _writer.close();
-                else if (_outputState==STREAM)
+                try
                 {
-                    try
-                    {
-                        getOutputStream().close();
-                    }
-                    catch(IOException e)
-                    {
-                        throw new RuntimeException(e);
-                    }
+                    closeOutput();
+                }
+                catch(IOException e)
+                {
+                    throw new RuntimeIOException(e);
                 }
             }
         }
+        else if (_contentLength==0)
+        {
+            long written = _out.getWritten();
+            if (written > 0)
+                throw new IllegalArgumentException("setContentLength(0) when already written " + written);
+            _fields.put(HttpHeader.CONTENT_LENGTH, "0");
+        }
+        else
+            _fields.remove(HttpHeader.CONTENT_LENGTH);
+    }
+    
+    public long getContentLength()
+    {
+        return _contentLength;
+    }
+
+    public boolean isAllContentWritten(long written)
+    {
+        return (_contentLength >= 0 && written >= _contentLength);
+    }
+
+    public void closeOutput() throws IOException
+    {
+        switch (_outputType)
+        {
+            case WRITER:
+                _writer.close();
+                if (!_out.isClosed())
+                    _out.close();
+                break;
+            case STREAM:
+                getOutputStream().close();
+                break;
+            default:
+                _out.close();
+        }
+    }
+
+    public long getLongContentLength()
+    {
+        return _contentLength;
     }
 
-    /* ------------------------------------------------------------ */
-    /*
-     * @see javax.servlet.ServletResponse#setContentLength(int)
-     */
     public void setLongContentLength(long len)
     {
         // Protect from setting after committed as default handling
         // of a servlet HEAD request ALWAYS sets _content length, even
         // if the getHandling committed the response!
-        if (isCommitted() || _connection.isIncluding())
-        	return;
-        _connection._generator.setContentLength(len);
-        _connection.getResponseFields().putLongField(HttpHeaders.CONTENT_LENGTH, len);
+        if (isCommitted() || isIncluding())
+            return;
+        _contentLength = len;
+        _fields.putLongField(HttpHeader.CONTENT_LENGTH.toString(), len);
+    }
+    
+    @Override
+    public void setContentLengthLong(long length)
+    {
+        setLongContentLength(length);
     }
 
-    /* ------------------------------------------------------------ */
-    /*
-     * @see javax.servlet.ServletResponse#setContentType(java.lang.String)
-     */
-    public void setContentType(String contentType)
+    @Override
+    public void setCharacterEncoding(String encoding)
     {
-        if (isCommitted() || _connection.isIncluding())
+        setCharacterEncoding(encoding,true);
+    }
+    
+    private void setCharacterEncoding(String encoding, boolean explicit)
+    {
+        if (isIncluding() || isWriting())
             return;
 
-        // Yes this method is horribly complex.... but there are lots of special cases and
-        // as this method is called on every request, it is worth trying to save string creation.
-        //
-
-        if (contentType==null)
+        if (_outputType == OutputType.NONE && !isCommitted())
         {
-            if (_locale==null)
-                _characterEncoding=null;
-            _mimeType=null;
-            _cachedMimeType=null;
-            _contentType=null;
-            _connection.getResponseFields().remove(HttpHeaders.CONTENT_TYPE_BUFFER);
-        }
-        else
-        {
-            // Look for encoding in contentType
-            int i0=contentType.indexOf(';');
-
-            if (i0>0)
+            if (encoding == null)
             {
-                // we have content type parameters
-
-                // Extract params off mimetype
-                _mimeType=contentType.substring(0,i0).trim();
-                _cachedMimeType=MimeTypes.CACHE.get(_mimeType);
-
-                // Look for charset
-                int i1=contentType.indexOf("charset=",i0+1);
-                if (i1>=0)
+                _explicitEncoding=false;
+                
+                // Clear any encoding.
+                if (_characterEncoding != null)
                 {
-                    _explicitEncoding=true;
-                    int i8=i1+8;
-                    int i2 = contentType.indexOf(' ',i8);
-
-                    if (_outputState==WRITER)
-                    {
-                        // strip the charset and ignore;
-                        if ((i1==i0+1 && i2<0) || (i1==i0+2 && i2<0 && contentType.charAt(i0+1)==' '))
-                        {
-                            if (_cachedMimeType!=null)
-                            {
-                                CachedBuffer content_type = _cachedMimeType.getAssociate(_characterEncoding);
-                                if (content_type!=null)
-                                {
-                                    _contentType=content_type.toString();
-                                    _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,content_type);
-                                }
-                                else
-                                {
-                                    _contentType=_mimeType+";charset="+_characterEncoding;
-                                    _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
-                                }
-                            }
-                            else
-                            {
-                                _contentType=_mimeType+";charset="+_characterEncoding;
-                                _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
-                            }
-                        }
-                        else if (i2<0)
-                        {
-                            _contentType=contentType.substring(0,i1)+";charset="+QuotedStringTokenizer.quoteIfNeeded(_characterEncoding,";= ");
-                            _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
-                        }
-                        else
-                        {
-                            _contentType=contentType.substring(0,i1)+contentType.substring(i2)+";charset="+QuotedStringTokenizer.quoteIfNeeded(_characterEncoding,";= ");
-                            _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
-                        }
-                    }
-                    else if ((i1==i0+1 && i2<0) || (i1==i0+2 && i2<0 && contentType.charAt(i0+1)==' '))
+                    _characterEncoding = null;
+                    
+                    if (_mimeType!=null)
                     {
-                        // The params are just the char encoding
-                        _cachedMimeType=MimeTypes.CACHE.get(_mimeType);
-                        _characterEncoding = QuotedStringTokenizer.unquote(contentType.substring(i8));
-
-                        if (_cachedMimeType!=null)
-                        {
-                            CachedBuffer content_type = _cachedMimeType.getAssociate(_characterEncoding);
-                            if (content_type!=null)
-                            {
-                                _contentType=content_type.toString();
-                                _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,content_type);
-                            }
-                            else
-                            {
-                                _contentType=contentType;
-                                _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
-                            }
-                        }
-                        else
-                        {
-                            _contentType=contentType;
-                            _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
-                        }
+                        _mimeType=_mimeType.getBaseType();
+                        _contentType=_mimeType.asString();
+                        _fields.put(_mimeType.getContentTypeField());
                     }
-                    else if (i2>0)
+                    else if (_contentType != null)
                     {
-                        _characterEncoding = QuotedStringTokenizer.unquote(contentType.substring(i8,i2));
-                        _contentType=contentType;
-                        _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
-                    }
-                    else
-                    {
-                        _characterEncoding = QuotedStringTokenizer.unquote(contentType.substring(i8));
-                        _contentType=contentType;
-                        _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
+                        _contentType = MimeTypes.getContentTypeWithoutCharset(_contentType);
+                        _fields.put(HttpHeader.CONTENT_TYPE, _contentType);
                     }
                 }
-                else // No encoding in the params.
-                {
-                    _cachedMimeType=null;
-                    _contentType=_characterEncoding==null?contentType:contentType+";charset="+QuotedStringTokenizer.quoteIfNeeded(_characterEncoding,";= ");
-                    _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
-                }
             }
-            else // No params at all
+            else
             {
-                _mimeType=contentType;
-                _cachedMimeType=MimeTypes.CACHE.get(_mimeType);
-
-                if (_characterEncoding!=null)
+                // No, so just add this one to the mimetype
+                _explicitEncoding = explicit;
+                _characterEncoding = HttpGenerator.__STRICT?encoding:StringUtil.normalizeCharset(encoding);
+                if (_mimeType!=null)
                 {
-                    if (_cachedMimeType!=null)
-                    {
-                        CachedBuffer content_type = _cachedMimeType.getAssociate(_characterEncoding);
-                        if (content_type!=null)
-                        {
-                            _contentType=content_type.toString();
-                            _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,content_type);
-                        }
-                        else
-                        {
-                            _contentType=_mimeType+";charset="+QuotedStringTokenizer.quoteIfNeeded(_characterEncoding,";= ");
-                            _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
-                        }
-                    }
+                    _contentType=_mimeType.getBaseType().asString()+ "; charset=" + _characterEncoding;
+                    _mimeType = MimeTypes.CACHE.get(_contentType);
+                    if (_mimeType==null || HttpGenerator.__STRICT)
+                        _fields.put(HttpHeader.CONTENT_TYPE, _contentType);
                     else
-                    {
-                        _contentType=contentType+";charset="+QuotedStringTokenizer.quoteIfNeeded(_characterEncoding,";= ");
-                        _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
-                    }
+                        _fields.put(_mimeType.getContentTypeField());
                 }
-                else if (_cachedMimeType!=null)
+                else if (_contentType != null)
                 {
-                    _contentType=_cachedMimeType.toString();
-                    _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_cachedMimeType);
+                    _contentType = MimeTypes.getContentTypeWithoutCharset(_contentType) + "; charset=" + _characterEncoding;
+                    _fields.put(HttpHeader.CONTENT_TYPE, _contentType);
                 }
-                else
+            }
+        }
+    }
+
+    @Override
+    public void setContentType(String contentType)
+    {
+        if (isCommitted() || isIncluding())
+            return;
+
+        if (contentType == null)
+        {
+            if (isWriting() && _characterEncoding != null)
+                throw new IllegalSelectorException();
+
+            if (_locale == null)
+                _characterEncoding = null;
+            _mimeType = null;
+            _contentType = null;
+            _fields.remove(HttpHeader.CONTENT_TYPE);
+        }
+        else
+        {
+            _contentType = contentType;
+            _mimeType = MimeTypes.CACHE.get(contentType);
+            
+            String charset;
+            if (_mimeType!=null && _mimeType.getCharset()!=null && !_mimeType.isCharsetAssumed())
+                charset=_mimeType.getCharset().toString();
+            else
+                charset = MimeTypes.getCharsetFromContentType(contentType);
+
+            if (charset == null)
+            {
+                if (_characterEncoding != null)
                 {
-                    _contentType=contentType;
-                    _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
+                    _contentType = contentType + "; charset=" + _characterEncoding;
+                    _mimeType = null;
                 }
             }
+            else if (isWriting() && !charset.equals(_characterEncoding))
+            {
+                // too late to change the character encoding;
+                _mimeType = null;
+                _contentType = MimeTypes.getContentTypeWithoutCharset(_contentType);
+                if (_characterEncoding != null)
+                    _contentType = _contentType + "; charset=" + _characterEncoding;
+            }
+            else
+            {
+                _characterEncoding = charset;
+                _explicitEncoding = true;
+            }
+
+            if (HttpGenerator.__STRICT || _mimeType==null)
+                _fields.put(HttpHeader.CONTENT_TYPE, _contentType);
+            else
+            {
+                _contentType=_mimeType.asString();
+                _fields.put(_mimeType.getContentTypeField());
+            }
         }
+        
     }
 
-    /* ------------------------------------------------------------ */
-    /*
-     * @see javax.servlet.ServletResponse#setBufferSize(int)
-     */
+    @Override
     public void setBufferSize(int size)
     {
-        if (isCommitted() || getContentCount()>0)
+        if (isCommitted() || getContentCount() > 0)
             throw new IllegalStateException("Committed or content written");
-        _connection.getGenerator().increaseContentBufferSize(size);
+        if (size <= 0)
+            size = __MIN_BUFFER_SIZE;
+        _out.setBufferSize(size);
     }
 
-    /* ------------------------------------------------------------ */
-    /*
-     * @see javax.servlet.ServletResponse#getBufferSize()
-     */
+    @Override
     public int getBufferSize()
     {
-        return _connection.getGenerator().getContentBufferSize();
+        return _out.getBufferSize();
     }
 
-    /* ------------------------------------------------------------ */
-    /*
-     * @see javax.servlet.ServletResponse#flushBuffer()
-     */
+    @Override
     public void flushBuffer() throws IOException
     {
-        _connection.flushResponse();
+        if (!_out.isClosed())
+            _out.flush();
     }
 
-    /* ------------------------------------------------------------ */
-    /*
-     * @see javax.servlet.ServletResponse#reset()
-     */
+    @Override
     public void reset()
     {
-        resetBuffer();
-        fwdReset();
-        _status=200;
-        _reason=null;
-        
-        HttpFields response_fields=_connection.getResponseFields();
-        
-        response_fields.clear();
-        String connection=_connection.getRequestFields().getStringField(HttpHeaders.CONNECTION_BUFFER);
-        if (connection!=null)
+        resetForForward();
+        _status = 200;
+        _reason = null;
+        _contentLength = -1;
+        _fields.clear();
+
+        String connection = _channel.getRequest().getHttpFields().getStringField(HttpHeader.CONNECTION);
+        if (connection != null)
         {
             String[] values = connection.split(",");
-            for  (int i=0;values!=null && i<values.length;i++)
+            for (int i = 0; values != null && i < values.length; i++)
             {
-                CachedBuffer cb = HttpHeaderValues.CACHE.get(values[0].trim());
+                HttpHeaderValue cb = HttpHeaderValue.CACHE.get(values[0].trim());
 
-                if (cb!=null)
+                if (cb != null)
                 {
-                    switch(cb.getOrdinal())
+                    switch (cb)
                     {
-                        case HttpHeaderValues.CLOSE_ORDINAL:
-                            response_fields.put(HttpHeaders.CONNECTION_BUFFER,HttpHeaderValues.CLOSE_BUFFER);
+                        case CLOSE:
+                            _fields.put(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.toString());
                             break;
 
-                        case HttpHeaderValues.KEEP_ALIVE_ORDINAL:
-                            if (HttpVersions.HTTP_1_0.equalsIgnoreCase(_connection.getRequest().getProtocol()))
-                                response_fields.put(HttpHeaders.CONNECTION_BUFFER,HttpHeaderValues.KEEP_ALIVE);
+                        case KEEP_ALIVE:
+                            if (HttpVersion.HTTP_1_0.is(_channel.getRequest().getProtocol()))
+                                _fields.put(HttpHeader.CONNECTION, HttpHeaderValue.KEEP_ALIVE.toString());
                             break;
-                        case HttpHeaderValues.TE_ORDINAL:
-                            response_fields.put(HttpHeaders.CONNECTION_BUFFER,HttpHeaderValues.TE);
+                        case TE:
+                            _fields.put(HttpHeader.CONNECTION, HttpHeaderValue.TE.toString());
                             break;
+                        default:
                     }
                 }
             }
         }
     }
-    
 
     public void reset(boolean preserveCookies)
     { 
@@ -1092,192 +1220,129 @@ public class Response implements HttpServletResponse
             reset();
         else
         {
-            HttpFields response_fields=_connection.getResponseFields();
-
             ArrayList<String> cookieValues = new ArrayList<String>(5);
-            Enumeration<String> vals = response_fields.getValues(HttpHeaders.SET_COOKIE);
+            Enumeration<String> vals = _fields.getValues(HttpHeader.SET_COOKIE.asString());
             while (vals.hasMoreElements())
-                cookieValues.add((String)vals.nextElement());
-
+                cookieValues.add(vals.nextElement());
             reset();
-
             for (String v:cookieValues)
-                response_fields.add(HttpHeaders.SET_COOKIE, v);
+                _fields.add(HttpHeader.SET_COOKIE, v);
         }
     }
-    
-    
-    
-    /* ------------------------------------------------------------ */
-    /*
-     * @see javax.servlet.ServletResponse#reset()
-     */
-    public void fwdReset()
+
+    public void resetForForward()
     {
         resetBuffer();
-
-        _writer=null;
-        _outputState=NONE;
+        _outputType = OutputType.NONE;
     }
 
-    /* ------------------------------------------------------------ */
-    /*
-     * @see javax.servlet.ServletResponse#resetBuffer()
-     */
+    @Override
     public void resetBuffer()
     {
         if (isCommitted())
             throw new IllegalStateException("Committed");
-        _connection.getGenerator().resetBuffer();
+
+        switch (_outputType)
+        {
+            case STREAM:
+            case WRITER:
+                _out.reset();
+                break;
+            default:
+        }
+
+        _out.resetBuffer();
     }
 
-    /* ------------------------------------------------------------ */
-    /*
-     * @see javax.servlet.ServletResponse#isCommitted()
-     */
-    public boolean isCommitted()
+    protected ResponseInfo newResponseInfo()
     {
-        return _connection.isResponseCommitted();
+        return new ResponseInfo(_channel.getRequest().getHttpVersion(), _fields, getLongContentLength(), getStatus(), getReason(), _channel.getRequest().isHead());
     }
 
+    @Override
+    public boolean isCommitted()
+    {
+        return _channel.isCommitted();
+    }
 
-    /* ------------------------------------------------------------ */
-    /*
-     * @see javax.servlet.ServletResponse#setLocale(java.util.Locale)
-     */
+    @Override
     public void setLocale(Locale locale)
     {
-        if (locale == null || isCommitted() ||_connection.isIncluding())
+        if (locale == null || isCommitted() || isIncluding())
             return;
 
         _locale = locale;
-        _connection.getResponseFields().put(HttpHeaders.CONTENT_LANGUAGE_BUFFER,locale.toString().replace('_','-'));
+        _fields.put(HttpHeader.CONTENT_LANGUAGE, locale.toString().replace('_', '-'));
 
-        if (_explicitEncoding || _outputState!=0 )
+        if (_outputType != OutputType.NONE)
             return;
 
-        if (_connection.getRequest().getContext()==null)
+        if (_channel.getRequest().getContext() == null)
             return;
 
-        String charset = _connection.getRequest().getContext().getContextHandler().getLocaleEncoding(locale);
-
-        if (charset!=null && charset.length()>0)
-        {
-            _characterEncoding=charset;
+        String charset = _channel.getRequest().getContext().getContextHandler().getLocaleEncoding(locale);
 
-            /* get current MIME type from Content-Type header */
-            String type=getContentType();
-            if (type!=null)
-            {
-                _characterEncoding=charset;
-                int semi=type.indexOf(';');
-                if (semi<0)
-                {
-                    _mimeType=type;
-                    _contentType= type += ";charset="+charset;
-                }
-                else
-                {
-                    _mimeType=type.substring(0,semi);
-                    _contentType= _mimeType += ";charset="+charset;
-                }
-
-                _cachedMimeType=MimeTypes.CACHE.get(_mimeType);
-                _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
-            }
-        }
+        if (charset != null && charset.length() > 0 && !_explicitEncoding)
+            setCharacterEncoding(charset,false);
     }
 
-    /* ------------------------------------------------------------ */
-    /*
-     * @see javax.servlet.ServletResponse#getLocale()
-     */
+    @Override
     public Locale getLocale()
     {
-        if (_locale==null)
+        if (_locale == null)
             return Locale.getDefault();
         return _locale;
     }
 
-    /* ------------------------------------------------------------ */
-    /**
-     * @return The HTTP status code that has been set for this request. This will be <code>200<code>
-     *    ({@link HttpServletResponse#SC_OK}), unless explicitly set through one of the <code>setStatus</code> methods.
-     */
+    @Override
     public int getStatus()
     {
         return _status;
     }
 
-    /* ------------------------------------------------------------ */
-    /**
-     * @return The reason associated with the current {@link #getStatus() status}. This will be <code>null</code>,
-     *    unless one of the <code>setStatus</code> methods have been called.
-     */
     public String getReason()
     {
         return _reason;
     }
 
-    /* ------------------------------------------------------------ */
-    /**
-     */
-    public void complete()
-        throws IOException
+    public HttpFields getHttpFields()
     {
-        _connection.completeResponse();
+        return _fields;
     }
 
-    /* ------------------------------------------------------------- */
-    /**
-     * @return the number of bytes actually written in response body
-     */
     public long getContentCount()
     {
-        if (_connection==null || _connection.getGenerator()==null)
-            return -1;
-        return _connection.getGenerator().getContentWritten();
+        return _out.getWritten();
     }
 
-    /* ------------------------------------------------------------ */
-    public HttpFields getHttpFields()
-    {
-        return _connection.getResponseFields();
-    }
-
-    /* ------------------------------------------------------------ */
     @Override
     public String toString()
     {
-        return "HTTP/1.1 "+_status+" "+ (_reason==null?"":_reason) +System.getProperty("line.separator")+
-        _connection.getResponseFields().toString();
+        return String.format("%s %d %s%n%s", _channel.getRequest().getHttpVersion(), _status, _reason == null ? "" : _reason, _fields);
     }
     
-    /* ------------------------------------------------------------ */
-    /* ------------------------------------------------------------ */
-    /* ------------------------------------------------------------ */
-    private static class NullOutput extends ServletOutputStream
-    {
-        @Override
-        public void write(int b) throws IOException
-        {
-        }
 
-        @Override
-        public void print(String s) throws IOException
+    private static class ResponseWriter extends PrintWriter
+    {
+        private final String _encoding;
+        private final HttpWriter _httpWriter;
+        
+        public ResponseWriter(HttpWriter httpWriter,String encoding)
         {
+            super(httpWriter);
+            _httpWriter=httpWriter;
+            _encoding=encoding;
         }
 
-        @Override
-        public void println(String s) throws IOException
+        public boolean isFor(String encoding)
         {
+            return _encoding.equalsIgnoreCase(encoding);
         }
-
-        @Override
-        public void write(byte[] b, int off, int len) throws IOException
+        
+        protected void reopen()
         {
+            super.clearError();
+            out=_httpWriter;
         }
-
     }
-
 }
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/SecureRequestCustomizer.java b/jetty-server/src/main/java/org/eclipse/jetty/server/SecureRequestCustomizer.java
new file mode 100644
index 0000000..2ebfbaf
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/SecureRequestCustomizer.java
@@ -0,0 +1,172 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.security.cert.X509Certificate;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLSession;
+import javax.servlet.ServletRequest;
+
+import org.eclipse.jetty.http.HttpScheme;
+import org.eclipse.jetty.io.ssl.SslConnection;
+import org.eclipse.jetty.io.ssl.SslConnection.DecryptedEndPoint;
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+
+
+/* ------------------------------------------------------------ */
+/** Customizer that extracts the attribute from an {@link SSLContext}
+ * and sets them on the request with {@link ServletRequest#setAttribute(String, Object)}
+ * according to Servlet Specification Requirements.
+ */
+public class SecureRequestCustomizer implements HttpConfiguration.Customizer
+{
+    private static final Logger LOG = Log.getLogger(SecureRequestCustomizer.class);
+    
+    /**
+     * The name of the SSLSession attribute that will contain any cached information.
+     */
+    public static final String CACHED_INFO_ATTR = CachedInfo.class.getName();
+
+    @Override
+    public void customize(Connector connector, HttpConfiguration channelConfig, Request request)
+    {
+        if (request.getHttpChannel().getEndPoint() instanceof DecryptedEndPoint)
+        {
+            request.setScheme(HttpScheme.HTTPS.asString());
+            request.setSecure(true);
+            SslConnection.DecryptedEndPoint ssl_endp = (DecryptedEndPoint)request.getHttpChannel().getEndPoint();
+            SslConnection sslConnection = ssl_endp.getSslConnection();
+            SSLEngine sslEngine=sslConnection.getSSLEngine();
+            customize(sslEngine,request);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * Customise the request attributes to be set for SSL requests. <br>
+     * The requirements of the Servlet specs are:
+     * <ul>
+     * <li> an attribute named "javax.servlet.request.ssl_session_id" of type
+     * String (since Servlet Spec 3.0).</li>
+     * <li> an attribute named "javax.servlet.request.cipher_suite" of type
+     * String.</li>
+     * <li> an attribute named "javax.servlet.request.key_size" of type Integer.</li>
+     * <li> an attribute named "javax.servlet.request.X509Certificate" of type
+     * java.security.cert.X509Certificate[]. This is an array of objects of type
+     * X509Certificate, the order of this array is defined as being in ascending
+     * order of trust. The first certificate in the chain is the one set by the
+     * client, the next is the one used to authenticate the first, and so on.
+     * </li>
+     * </ul>
+     *
+     * @param request
+     *                HttpRequest to be customised.
+     */
+    public void customize(SSLEngine sslEngine, Request request)
+    {
+        request.setScheme(HttpScheme.HTTPS.asString());
+        SSLSession sslSession = sslEngine.getSession();
+
+        try
+        {
+            String cipherSuite=sslSession.getCipherSuite();
+            Integer keySize;
+            X509Certificate[] certs;
+            String idStr;
+
+            CachedInfo cachedInfo=(CachedInfo)sslSession.getValue(CACHED_INFO_ATTR);
+            if (cachedInfo!=null)
+            {
+                keySize=cachedInfo.getKeySize();
+                certs=cachedInfo.getCerts();
+                idStr=cachedInfo.getIdStr();
+            }
+            else 
+            {
+                keySize=new Integer(SslContextFactory.deduceKeyLength(cipherSuite));
+                certs=SslContextFactory.getCertChain(sslSession);
+                byte[] bytes = sslSession.getId();
+                idStr = TypeUtil.toHexString(bytes);
+                cachedInfo=new CachedInfo(keySize,certs,idStr);
+                sslSession.putValue(CACHED_INFO_ATTR,cachedInfo);
+            }
+
+            if (certs!=null)
+                request.setAttribute("javax.servlet.request.X509Certificate",certs);
+
+            request.setAttribute("javax.servlet.request.cipher_suite",cipherSuite);
+            request.setAttribute("javax.servlet.request.key_size",keySize);
+            request.setAttribute("javax.servlet.request.ssl_session_id", idStr);
+        }
+        catch (Exception e)
+        {
+            LOG.warn(Log.EXCEPTION,e);
+        }
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s@%x",this.getClass().getSimpleName(),hashCode());
+    }
+    
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /**
+     * Simple bundle of information that is cached in the SSLSession. Stores the
+     * effective keySize and the client certificate chain.
+     */
+    private static class CachedInfo
+    {
+        private final X509Certificate[] _certs;
+        private final Integer _keySize;
+        private final String _idStr;
+
+        CachedInfo(Integer keySize, X509Certificate[] certs,String idStr)
+        {
+            this._keySize=keySize;
+            this._certs=certs;
+            this._idStr=idStr;
+        }
+
+        X509Certificate[] getCerts()
+        {
+            return _certs;
+        }
+
+        Integer getKeySize()
+        {
+            return _keySize;
+        }
+
+        String getIdStr()
+        {
+            return _idStr;
+        }
+    }
+
+
+
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Server.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Server.java
index 7c10f76..96d10b6 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/Server.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Server.java
@@ -19,32 +19,51 @@
 package org.eclipse.jetty.server;
 
 import java.io.IOException;
+import java.net.InetAddress;
 import java.net.InetSocketAddress;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
 import java.util.Enumeration;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
 
-import javax.servlet.AsyncContext;
+import javax.servlet.ServletContext;
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
+import org.eclipse.jetty.http.DateGenerator;
+import org.eclipse.jetty.http.HttpField;
 import org.eclipse.jetty.http.HttpGenerator;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpStatus;
 import org.eclipse.jetty.http.HttpURI;
+import org.eclipse.jetty.server.handler.ContextHandler;
 import org.eclipse.jetty.server.handler.HandlerWrapper;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
+import org.eclipse.jetty.server.handler.StatisticsHandler;
 import org.eclipse.jetty.util.Attributes;
 import org.eclipse.jetty.util.AttributesMap;
-import org.eclipse.jetty.util.LazyList;
+import org.eclipse.jetty.util.Jetty;
 import org.eclipse.jetty.util.MultiException;
-import org.eclipse.jetty.util.TypeUtil;
 import org.eclipse.jetty.util.URIUtil;
-import org.eclipse.jetty.util.component.Container;
-import org.eclipse.jetty.util.component.Destroyable;
+import org.eclipse.jetty.util.Uptime;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.annotation.Name;
+import org.eclipse.jetty.util.component.Graceful;
 import org.eclipse.jetty.util.component.LifeCycle;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
 import org.eclipse.jetty.util.thread.QueuedThreadPool;
 import org.eclipse.jetty.util.thread.ShutdownThread;
 import org.eclipse.jetty.util.thread.ThreadPool;
+import org.eclipse.jetty.util.thread.ThreadPool.SizedThreadPool;
 
 /* ------------------------------------------------------------ */
 /** Jetty HTTP Servlet Server.
@@ -52,85 +71,71 @@ import org.eclipse.jetty.util.thread.ThreadPool;
  * It aggregates Connectors (HTTP request receivers) and request Handlers.
  * The server is itself a handler and a ThreadPool.  Connectors use the ThreadPool methods
  * to run jobs that will eventually call the handle method.
- *
- *  @org.apache.xbean.XBean  description="Creates an embedded Jetty web server"
  */
+ at ManagedObject(value="Jetty HTTP Servlet server")
 public class Server extends HandlerWrapper implements Attributes
 {
     private static final Logger LOG = Log.getLogger(Server.class);
 
-    private static final String __version;
-    static
-    {
-        if (Server.class.getPackage()!=null &&
-            "Eclipse.org - Jetty".equals(Server.class.getPackage().getImplementationVendor()) &&
-             Server.class.getPackage().getImplementationVersion()!=null)
-            __version=Server.class.getPackage().getImplementationVersion();
-        else
-            __version=System.getProperty("jetty.version","8.y.z-SNAPSHOT");
-    }
-
-    private final Container _container=new Container();
     private final AttributesMap _attributes = new AttributesMap();
-    private ThreadPool _threadPool;
-    private Connector[] _connectors;
+    private final ThreadPool _threadPool;
+    private final List<Connector> _connectors = new CopyOnWriteArrayList<>();
     private SessionIdManager _sessionIdManager;
-    private boolean _sendServerVersion = true; //send Server: header
-    private boolean _sendDateHeader = false; //send Date: header
-    private int _graceful=0;
     private boolean _stopAtShutdown;
     private boolean _dumpAfterStart=false;
     private boolean _dumpBeforeStop=false;
-    private boolean _uncheckedPrintWriter=false;
-
-
+    
+    private volatile DateField _dateField;
+    
     /* ------------------------------------------------------------ */
     public Server()
     {
-        setServer(this);
+        this((ThreadPool)null);
     }
 
     /* ------------------------------------------------------------ */
     /** Convenience constructor
-     * Creates server and a {@link SelectChannelConnector} at the passed port.
+     * Creates server and a {@link ServerConnector} at the passed port.
+     * @param port The port of a network HTTP connector (or 0 for a randomly allocated port).
+     * @see NetworkConnector#getLocalPort()
      */
-    public Server(int port)
+    public Server(@Name("port")int port)
     {
-        setServer(this);
-
-        Connector connector=new SelectChannelConnector();
+        this((ThreadPool)null);
+        ServerConnector connector=new ServerConnector(this);
         connector.setPort(port);
         setConnectors(new Connector[]{connector});
     }
 
     /* ------------------------------------------------------------ */
     /** Convenience constructor
-     * Creates server and a {@link SelectChannelConnector} at the passed address.
+     * Creates server and a {@link ServerConnector} at the passed address.
      */
-    public Server(InetSocketAddress addr)
+    public Server(@Name("address")InetSocketAddress addr)
     {
-        setServer(this);
-
-        Connector connector=new SelectChannelConnector();
+        this((ThreadPool)null);
+        ServerConnector connector=new ServerConnector(this);
         connector.setHost(addr.getHostName());
         connector.setPort(addr.getPort());
         setConnectors(new Connector[]{connector});
     }
 
 
+
     /* ------------------------------------------------------------ */
-    public static String getVersion()
+    public Server(@Name("threadpool") ThreadPool pool)
     {
-        return __version;
+        _threadPool=pool!=null?pool:new QueuedThreadPool();
+        addBean(_threadPool);
+        setServer(this);
     }
 
+
     /* ------------------------------------------------------------ */
-    /**
-     * @return Returns the container.
-     */
-    public Container getContainer()
+    @ManagedAttribute("version of this server")
+    public static String getVersion()
     {
-        return _container;
+        return Jetty.VERSION;
     }
 
     /* ------------------------------------------------------------ */
@@ -138,8 +143,28 @@ public class Server extends HandlerWrapper implements Attributes
     {
         return _stopAtShutdown;
     }
+   
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Set a graceful stop time.
+     * The {@link StatisticsHandler} must be configured so that open connections can
+     * be tracked for a graceful shutdown.
+     * @see org.eclipse.jetty.util.component.ContainerLifeCycle#setStopTimeout(long)
+     */
+    @Override
+    public void setStopTimeout(long stopTimeout)
+    {
+        super.setStopTimeout(stopTimeout);
+    }
 
     /* ------------------------------------------------------------ */
+    /** Set stop server at shutdown behaviour.
+     * @param stop If true, this server instance will be explicitly stopped when the
+     * JVM is shutdown. Otherwise the JVM is stopped with the server running.
+     * @see Runtime#addShutdownHook(Thread)
+     * @see ShutdownThread
+     */
     public void setStopAtShutdown(boolean stop)
     {
         //if we now want to stop
@@ -147,15 +172,15 @@ public class Server extends HandlerWrapper implements Attributes
         {
             //and we weren't stopping before
             if (!_stopAtShutdown)
-            {  
+            {
                 //only register to stop if we're already started (otherwise we'll do it in doStart())
-                if (isStarted()) 
+                if (isStarted())
                     ShutdownThread.register(this);
             }
         }
         else
             ShutdownThread.deregister(this);
-        
+
         _stopAtShutdown=stop;
     }
 
@@ -163,25 +188,33 @@ public class Server extends HandlerWrapper implements Attributes
     /**
      * @return Returns the connectors.
      */
+    @ManagedAttribute(value="connectors for this server", readonly=true)
     public Connector[] getConnectors()
     {
-        return _connectors;
+        List<Connector> connectors = new ArrayList<>(_connectors);
+        return connectors.toArray(new Connector[connectors.size()]);
     }
 
     /* ------------------------------------------------------------ */
     public void addConnector(Connector connector)
     {
-        setConnectors((Connector[])LazyList.addToArray(getConnectors(), connector, Connector.class));
+        if (connector.getServer() != this)
+            throw new IllegalArgumentException("Connector " + connector +
+                    " cannot be shared among server " + connector.getServer() + " and server " + this);
+        if (_connectors.add(connector))
+            addBean(connector);
     }
 
     /* ------------------------------------------------------------ */
     /**
-     * Conveniance method which calls {@link #getConnectors()} and {@link #setConnectors(Connector[])} to
+     * Convenience method which calls {@link #getConnectors()} and {@link #setConnectors(Connector[])} to
      * remove a connector.
      * @param connector The connector to remove.
      */
-    public void removeConnector(Connector connector) {
-        setConnectors((Connector[])LazyList.removeFromArray (getConnectors(), connector));
+    public void removeConnector(Connector connector)
+    {
+        if (_connectors.remove(connector))
+            removeBean(connector);
     }
 
     /* ------------------------------------------------------------ */
@@ -191,42 +224,37 @@ public class Server extends HandlerWrapper implements Attributes
      */
     public void setConnectors(Connector[] connectors)
     {
-        if (connectors!=null)
+        if (connectors != null)
         {
-            for (int i=0;i<connectors.length;i++)
-                connectors[i].setServer(this);
+            for (Connector connector : connectors)
+            {
+                if (connector.getServer() != this)
+                    throw new IllegalArgumentException("Connector " + connector +
+                            " cannot be shared among server " + connector.getServer() + " and server " + this);
+            }
         }
 
-        _container.update(this, _connectors, connectors, "connector");
-        _connectors = connectors;
+        Connector[] oldConnectors = getConnectors();
+        updateBeans(oldConnectors, connectors);
+        _connectors.removeAll(Arrays.asList(oldConnectors));
+        if (connectors != null)
+            _connectors.addAll(Arrays.asList(connectors));
     }
 
     /* ------------------------------------------------------------ */
     /**
      * @return Returns the threadPool.
      */
+    @ManagedAttribute("the server thread pool")
     public ThreadPool getThreadPool()
     {
         return _threadPool;
     }
 
-    /* ------------------------------------------------------------ */
-    /**
-     * @param threadPool The threadPool to set.
-     */
-    public void setThreadPool(ThreadPool threadPool)
-    {
-        if (_threadPool!=null)
-            removeBean(_threadPool);
-        _container.update(this, _threadPool, threadPool, "threadpool",false);
-        _threadPool = threadPool;
-        if (_threadPool!=null)
-            addBean(_threadPool);
-    }
-
     /**
      * @return true if {@link #dumpStdErr()} is called after starting
      */
+    @ManagedAttribute("dump state to stderr after start")
     public boolean isDumpAfterStart()
     {
         return _dumpAfterStart;
@@ -243,6 +271,7 @@ public class Server extends HandlerWrapper implements Attributes
     /**
      * @return true if {@link #dumpStdErr()} is called before stopping
      */
+    @ManagedAttribute("dump state to stderr before stop")
     public boolean isDumpBeforeStop()
     {
         return _dumpBeforeStop;
@@ -256,27 +285,70 @@ public class Server extends HandlerWrapper implements Attributes
         _dumpBeforeStop = dumpBeforeStop;
     }
 
-
+    /* ------------------------------------------------------------ */
+    public HttpField getDateField()
+    {
+        long now=System.currentTimeMillis();
+        long seconds = now/1000;
+        DateField df = _dateField;
+        
+        if (df==null || df._seconds!=seconds)
+        {
+            synchronized (this) // Trade some contention for less garbage
+            {
+                df = _dateField;
+                if (df==null || df._seconds!=seconds)
+                {
+                    HttpField field=new HttpGenerator.CachedHttpField(HttpHeader.DATE,DateGenerator.formatDate(now));
+                    _dateField=new DateField(seconds,field);
+                    return field;
+                }
+            }
+        }
+        return df._dateField;
+    }
 
     /* ------------------------------------------------------------ */
     @Override
     protected void doStart() throws Exception
     {
+        //If the Server should be stopped when the jvm exits, register
+        //with the shutdown handler thread.
         if (getStopAtShutdown())
-        {
-            ShutdownThread.register(this);    
-        }
+            ShutdownThread.register(this);
+
+        //Register the Server with the handler thread for receiving
+        //remote stop commands
+        ShutdownMonitor.register(this);
         
+        //Start a thread waiting to receive "stop" commands.
         ShutdownMonitor.getInstance().start(); // initialize
 
-        LOG.info("jetty-"+__version);
-        HttpGenerator.setServerVersion(__version);
-        
+        LOG.info("jetty-" + getVersion());
+        HttpGenerator.setJettyVersion(HttpConfiguration.SERVER_VERSION);
         MultiException mex=new MultiException();
 
-        if (_threadPool==null)
-            setThreadPool(new QueuedThreadPool());
+        // check size of thread pool
+        SizedThreadPool pool = getBean(SizedThreadPool.class);
+        int max=pool==null?-1:pool.getMaxThreads();
+        int selectors=0;
+        int acceptors=0;
+        if (mex.size()==0)
+        {
+            for (Connector connector : _connectors)
+            {
+                if (connector instanceof AbstractConnector)
+                    acceptors+=((AbstractConnector)connector).getAcceptors();
+                    
+                if (connector instanceof ServerConnector)
+                    selectors+=((ServerConnector)connector).getSelectorManager().getSelectorCount();
+            }
+        }
 
+        int needed=1+selectors+acceptors;
+        if (max>0 && needed>max)
+            throw new IllegalStateException(String.format("Insufficient threads: max=%d < needed(acceptors=%d + selectors=%d + request=1)",max,acceptors,selectors));
+        
         try
         {
             super.doStart();
@@ -286,22 +358,33 @@ public class Server extends HandlerWrapper implements Attributes
             mex.add(e);
         }
 
-        if (_connectors!=null && mex.size()==0)
+        // start connectors last
+        for (Connector connector : _connectors)
         {
-            for (int i=0;i<_connectors.length;i++)
+            try
+            {   
+                connector.start();
+            }
+            catch(Throwable e)
             {
-                try{_connectors[i].start();}
-                catch(Throwable e)
-                {
-                    mex.add(e);
-                }
+                mex.add(e);
             }
         }
-
+        
         if (isDumpAfterStart())
             dumpStdErr();
 
         mex.ifExceptionThrow();
+
+        LOG.info(String.format("Started @%dms",Uptime.getUptime()));
+    }
+
+    @Override
+    protected void start(LifeCycle l) throws Exception
+    {
+        // start connectors last
+        if (!(l instanceof Connector))
+            super.start(l);
     }
 
     /* ------------------------------------------------------------ */
@@ -313,39 +396,80 @@ public class Server extends HandlerWrapper implements Attributes
 
         MultiException mex=new MultiException();
 
-        if (_graceful>0)
+        // list if graceful futures
+        List<Future<Void>> futures = new ArrayList<>();
+
+        // First close the network connectors to stop accepting new connections
+        for (Connector connector : _connectors)
+            futures.add(connector.shutdown());
+
+        // Then tell the contexts that we are shutting down
+        
+        Handler[] gracefuls = getChildHandlersByClass(Graceful.class);
+        for (Handler graceful : gracefuls)
+            futures.add(((Graceful)graceful).shutdown());
+
+        // Shall we gracefully wait for zero connections?
+        long stopTimeout = getStopTimeout();
+        if (stopTimeout>0)
         {
-            if (_connectors!=null)
+            long stop_by=System.currentTimeMillis()+stopTimeout;
+            if (LOG.isDebugEnabled())
+                LOG.debug("Graceful shutdown {} by ",this,new Date(stop_by));
+
+            // Wait for shutdowns
+            for (Future<Void> future: futures)
             {
-                for (int i=_connectors.length;i-->0;)
+                try
                 {
-                    LOG.info("Graceful shutdown {}",_connectors[i]);
-                    try{_connectors[i].close();}catch(Throwable e){mex.add(e);}
+                    if (!future.isDone())
+                        future.get(Math.max(1L,stop_by-System.currentTimeMillis()),TimeUnit.MILLISECONDS);
+                }
+                catch (Exception e)
+                {
+                    mex.add(e);
                 }
             }
+        }
+
+        // Cancel any shutdowns not done
+        for (Future<Void> future: futures)
+            if (!future.isDone())
+                future.cancel(true);
 
-            Handler[] contexts = getChildHandlersByClass(Graceful.class);
-            for (int c=0;c<contexts.length;c++)
+        // Now stop the connectors (this will close existing connections)
+        for (Connector connector : _connectors)
+        {
+            try
             {
-                Graceful context=(Graceful)contexts[c];
-                LOG.info("Graceful shutdown {}",context);
-                context.setShutdown(true);
+                connector.stop();
+            }
+            catch (Throwable e)
+            {
+                mex.add(e);
             }
-            Thread.sleep(_graceful);
         }
 
-        if (_connectors!=null)
+        // And finally stop everything else
+        try
         {
-            for (int i=_connectors.length;i-->0;)
-                try{_connectors[i].stop();}catch(Throwable e){mex.add(e);}
+            super.doStop();
+        }
+        catch (Throwable e)
+        {
+            mex.add(e);
         }
 
-        try {super.doStop(); } catch(Throwable e) { mex.add(e);}
+        if (getStopAtShutdown())
+            ShutdownThread.deregister(this);
+        
+        //Unregister the Server with the handler thread for receiving
+        //remote stop commands as we are stopped already
+        ShutdownMonitor.deregister(this);
+        
 
         mex.ifExceptionThrow();
 
-        if (getStopAtShutdown())
-            ShutdownThread.deregister(this);
     }
 
     /* ------------------------------------------------------------ */
@@ -354,20 +478,35 @@ public class Server extends HandlerWrapper implements Attributes
      * or after the entire request has been received (for short requests of known length), or
      * on the dispatch of an async request.
      */
-    public void handle(AbstractHttpConnection connection) throws IOException, ServletException
+    public void handle(HttpChannel<?> connection) throws IOException, ServletException
     {
         final String target=connection.getRequest().getPathInfo();
         final Request request=connection.getRequest();
         final Response response=connection.getResponse();
 
         if (LOG.isDebugEnabled())
+            LOG.debug(request.getDispatcherType()+" "+request.getMethod()+" "+target+" on "+connection);
+
+        if (HttpMethod.OPTIONS.is(request.getMethod()) || "*".equals(target))
         {
-            LOG.debug("REQUEST "+target+" on "+connection);
-            handle(target, request, request, response);
-            LOG.debug("RESPONSE "+target+"  "+connection.getResponse().getStatus()+" handled="+request.isHandled());
+            if (!HttpMethod.OPTIONS.is(request.getMethod()))
+                response.sendError(HttpStatus.BAD_REQUEST_400);
+            handleOptions(request,response);
+            if (!request.isHandled())
+                handle(target, request, request, response);
         }
         else
             handle(target, request, request, response);
+
+        if (LOG.isDebugEnabled())
+            LOG.debug("RESPONSE "+target+"  "+connection.getResponse().getStatus()+" handled="+request.isHandled());
+    }
+
+    /* ------------------------------------------------------------ */
+    /* Handle Options request to server
+     */
+    protected void handleOptions(Request request,Response response) throws IOException
+    {
     }
 
     /* ------------------------------------------------------------ */
@@ -376,33 +515,33 @@ public class Server extends HandlerWrapper implements Attributes
      * or after the entire request has been received (for short requests of known length), or
      * on the dispatch of an async request.
      */
-    public void handleAsync(AbstractHttpConnection connection) throws IOException, ServletException
+    public void handleAsync(HttpChannel<?> connection) throws IOException, ServletException
     {
-        final AsyncContinuation async = connection.getRequest().getAsyncContinuation();
-        final AsyncContinuation.AsyncEventState state = async.getAsyncEventState();
+        final HttpChannelState state = connection.getRequest().getHttpChannelState();
+        final AsyncContextEvent event = state.getAsyncContextEvent();
 
         final Request baseRequest=connection.getRequest();
-        final String path=state.getPath();
-
+        final String path=event.getPath();
+        
         if (path!=null)
         {
             // this is a dispatch with a path
-            final String contextPath=state.getServletContext().getContextPath();
-            HttpURI uri = new HttpURI(URIUtil.addPaths(contextPath,path));
+            ServletContext context=event.getServletContext();
+            HttpURI uri = new HttpURI(URIUtil.addPaths(context==null?null:context.getContextPath(), path));            
             baseRequest.setUri(uri);
             baseRequest.setRequestURI(null);
-            baseRequest.setPathInfo(baseRequest.getRequestURI());
+            baseRequest.setPathInfo(uri.getDecodedPath());
             if (uri.getQuery()!=null)
-                baseRequest.mergeQueryString(uri.getQuery()); //we have to assume dispatch path and query are UTF8
+                baseRequest.mergeQueryParameters(uri.getQuery(), true); //we have to assume dispatch path and query are UTF8
         }
 
         final String target=baseRequest.getPathInfo();
-        final HttpServletRequest request=(HttpServletRequest)async.getRequest();
-        final HttpServletResponse response=(HttpServletResponse)async.getResponse();
+        final HttpServletRequest request=(HttpServletRequest)event.getSuppliedRequest();
+        final HttpServletResponse response=(HttpServletResponse)event.getSuppliedResponse();
 
         if (LOG.isDebugEnabled())
         {
-            LOG.debug("REQUEST "+target+" on "+connection);
+            LOG.debug(request.getDispatcherType()+" "+request.getMethod()+" "+target+" on "+connection);
             handle(target, baseRequest, request, response);
             LOG.debug("RESPONSE "+target+"  "+connection.getResponse().getStatus());
         }
@@ -411,7 +550,6 @@ public class Server extends HandlerWrapper implements Attributes
 
     }
 
-
     /* ------------------------------------------------------------ */
     public void join() throws InterruptedException
     {
@@ -419,7 +557,6 @@ public class Server extends HandlerWrapper implements Attributes
     }
 
     /* ------------------------------------------------------------ */
-    /* ------------------------------------------------------------ */
     /**
      * @return Returns the sessionIdManager.
      */
@@ -429,129 +566,25 @@ public class Server extends HandlerWrapper implements Attributes
     }
 
     /* ------------------------------------------------------------ */
-    /* ------------------------------------------------------------ */
     /**
      * @param sessionIdManager The sessionIdManager to set.
      */
     public void setSessionIdManager(SessionIdManager sessionIdManager)
     {
-        if (_sessionIdManager!=null)
-            removeBean(_sessionIdManager);
-        _container.update(this, _sessionIdManager, sessionIdManager, "sessionIdManager",false);
-        _sessionIdManager = sessionIdManager;
-        if (_sessionIdManager!=null)
-            addBean(_sessionIdManager);
-    }
-
-    /* ------------------------------------------------------------ */
-    public void setSendServerVersion (boolean sendServerVersion)
-    {
-        _sendServerVersion = sendServerVersion;
-    }
-
-    /* ------------------------------------------------------------ */
-    public boolean getSendServerVersion()
-    {
-        return _sendServerVersion;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param sendDateHeader
-     */
-    public void setSendDateHeader(boolean sendDateHeader)
-    {
-        _sendDateHeader = sendDateHeader;
-    }
-
-    /* ------------------------------------------------------------ */
-    public boolean getSendDateHeader()
-    {
-        return _sendDateHeader;
-    }
-
-    /* ------------------------------------------------------------ */
-    /** 
-     */
-    @Deprecated
-    public int getMaxCookieVersion()
-    {
-        return 1;
-    }
-
-    /* ------------------------------------------------------------ */
-    /** 
-     */
-    @Deprecated
-    public void setMaxCookieVersion(int maxCookieVersion)
-    {
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Add a LifeCycle object to be started/stopped
-     * along with the Server.
-     * @deprecated Use {@link #addBean(Object)}
-     * @param c
-     */
-    @Deprecated
-    public void addLifeCycle (LifeCycle c)
-    {
-        addBean(c);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Add an associated bean.
-     * The bean will be added to the servers {@link Container}
-     * and if it is a {@link LifeCycle} instance, it will be
-     * started/stopped along with the Server. Any beans that are also
-     * {@link Destroyable}, will be destroyed with the server.
-     * @param o the bean object to add
-     */
-    @Override
-    public boolean addBean(Object o)
-    {
-        if (super.addBean(o))
-        {
-            _container.addBean(o);
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * Remove a LifeCycle object to be started/stopped
-     * along with the Server
-     * @deprecated Use {@link #removeBean(Object)}
-     */
-    @Deprecated
-    public void removeLifeCycle (LifeCycle c)
-    {
-        removeBean(c);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Remove an associated bean.
-     */
-    @Override
-    public boolean removeBean (Object o)
-    {
-        if (super.removeBean(o))
-        {
-            _container.removeBean(o);
-            return true;
-        }
-        return false;
+        updateBean(_sessionIdManager,sessionIdManager);
+        _sessionIdManager=sessionIdManager;
     }
 
     /* ------------------------------------------------------------ */
     /*
      * @see org.eclipse.util.AttributesMap#clearAttributes()
      */
+    @Override
     public void clearAttributes()
     {
+        Enumeration<String> names = _attributes.getAttributeNames();
+        while (names.hasMoreElements())
+            removeBean(_attributes.getAttribute(names.nextElement()));
         _attributes.clearAttributes();
     }
 
@@ -559,6 +592,7 @@ public class Server extends HandlerWrapper implements Attributes
     /*
      * @see org.eclipse.util.AttributesMap#getAttribute(java.lang.String)
      */
+    @Override
     public Object getAttribute(String name)
     {
         return _attributes.getAttribute(name);
@@ -568,7 +602,8 @@ public class Server extends HandlerWrapper implements Attributes
     /*
      * @see org.eclipse.util.AttributesMap#getAttributeNames()
      */
-    public Enumeration getAttributeNames()
+    @Override
+    public Enumeration<String> getAttributeNames()
     {
         return AttributesMap.getAttributeNamesCopy(_attributes);
     }
@@ -577,8 +612,12 @@ public class Server extends HandlerWrapper implements Attributes
     /*
      * @see org.eclipse.util.AttributesMap#removeAttribute(java.lang.String)
      */
+    @Override
     public void removeAttribute(String name)
     {
+        Object bean=_attributes.getAttribute(name);
+        if (bean!=null)
+            removeBean(bean);
         _attributes.removeAttribute(name);
     }
 
@@ -586,33 +625,55 @@ public class Server extends HandlerWrapper implements Attributes
     /*
      * @see org.eclipse.util.AttributesMap#setAttribute(java.lang.String, java.lang.Object)
      */
+    @Override
     public void setAttribute(String name, Object attribute)
     {
+        addBean(attribute);
         _attributes.setAttribute(name, attribute);
     }
 
     /* ------------------------------------------------------------ */
     /**
-     * @return the graceful
+     * @return The URI of the first {@link NetworkConnector} and first {@link ContextHandler}, or null
      */
-    public int getGracefulShutdown()
+    @SuppressWarnings("resource")
+    public URI getURI()
     {
-        return _graceful;
-    }
+        NetworkConnector connector=null;
+        for (Connector c: _connectors)
+        {
+            if (c instanceof NetworkConnector)
+            {
+                connector=(NetworkConnector)c;
+                break;
+            }
+        }
 
-    /* ------------------------------------------------------------ */
-    /**
-     * Set graceful shutdown timeout.  If set, the internal <code>doStop()</code> method will not immediately stop the
-     * server. Instead, all {@link Connector}s will be closed so that new connections will not be accepted
-     * and all handlers that implement {@link Graceful} will be put into the shutdown mode so that no new requests
-     * will be accepted, but existing requests can complete.  The server will then wait the configured timeout
-     * before stopping.
-     * @param timeoutMS the milliseconds to wait for existing request to complete before stopping the server.
-     *
-     */
-    public void setGracefulShutdown(int timeoutMS)
-    {
-        _graceful=timeoutMS;
+        if (connector==null)
+            return null;
+
+        ContextHandler context = getChildHandlerByClass(ContextHandler.class);
+
+        try
+        {
+            String scheme=connector.getDefaultConnectionFactory().getProtocol().startsWith("SSL-")?"https":"http";
+
+            String host=connector.getHost();
+            if (context!=null && context.getVirtualHosts()!=null && context.getVirtualHosts().length>0)
+                host=context.getVirtualHosts()[0];
+            if (host==null)
+                host=InetAddress.getLocalHost().getHostAddress();
+
+            String path=context==null?null:context.getContextPath();
+            if (path==null)
+                path="/";
+            return new URI(scheme,null,host,connector.getLocalPort(),path,null,null);
+        }
+        catch(Exception e)
+        {
+            LOG.warn(e);
+            return null;
+        }
     }
 
     /* ------------------------------------------------------------ */
@@ -626,37 +687,27 @@ public class Server extends HandlerWrapper implements Attributes
     @Override
     public void dump(Appendable out,String indent) throws IOException
     {
-        dumpThis(out);
-        dump(out,indent,TypeUtil.asList(getHandlers()),getBeans(),TypeUtil.asList(_connectors));
-    }
-
-
-    /* ------------------------------------------------------------ */
-    public boolean isUncheckedPrintWriter()
-    {
-        return _uncheckedPrintWriter;
+        dumpBeans(out,indent,Collections.singleton(new ClassLoaderDump(this.getClass().getClassLoader())));
     }
 
     /* ------------------------------------------------------------ */
-    public void setUncheckedPrintWriter(boolean unchecked)
+    public static void main(String...args) throws Exception
     {
-        _uncheckedPrintWriter=unchecked;
+        System.err.println(getVersion());
     }
 
-
     /* ------------------------------------------------------------ */
-    /* A handler that can be gracefully shutdown.
-     * Called by doStop if a {@link #setGracefulShutdown} period is set.
-     * TODO move this somewhere better
-     */
-    public interface Graceful extends Handler
-    {
-        public void setShutdown(boolean shutdown);
-    }
-
     /* ------------------------------------------------------------ */
-    public static void main(String...args) throws Exception
+    private static class DateField
     {
-        System.err.println(getVersion());
+        final long _seconds;
+        final HttpField _dateField;
+        public DateField(long seconds, HttpField dateField)
+        {
+            super();
+            _seconds = seconds;
+            _dateField = dateField;
+        }
+        
     }
 }
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ServerConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ServerConnector.java
new file mode 100644
index 0000000..f05dbb7
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ServerConnector.java
@@ -0,0 +1,524 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.SocketException;
+import java.nio.channels.Channel;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.ServerSocketChannel;
+import java.nio.channels.SocketChannel;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Future;
+
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.SelectChannelEndPoint;
+import org.eclipse.jetty.io.SelectorManager;
+import org.eclipse.jetty.io.SelectorManager.ManagedSelector;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.annotation.Name;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+/**
+ * This {@link Connector} implementation is the primary connector for the
+ * Jetty server over TCP/IP.  By the use of various {@link ConnectionFactory} instances it is able
+ * to accept connections for HTTP, SPDY and WebSocket, either directly or over SSL.
+ * <p>
+ * The connector is a fully asynchronous NIO based implementation that by default will
+ * use all the commons services (eg {@link Executor}, {@link Scheduler})  of the
+ * passed {@link Server} instance, but all services may also be constructor injected
+ * into the connector so that it may operate with dedicated or otherwise shared services.
+ * <p>
+ * <h2>Connection Factories</h2>
+ * Various convenience constructors are provided to assist with common configurations of
+ * ConnectionFactories, whose generic use is described in {@link AbstractConnector}.
+ * If no connection factories are passed, then the connector will
+ * default to use a {@link HttpConnectionFactory}.  If an non null {@link SslContextFactory}
+ * instance is passed, then this used to instantiate a {@link SslConnectionFactory} which is
+ * prepended to the other passed or default factories.
+ * <p>
+ * <h2>Selectors</h2>
+ * The connector will use the {@link Executor} service to execute a number of Selector Tasks,
+ * which are implemented to each use a NIO {@link Selector} instance to asynchronously
+ * schedule a set of accepted connections.  It is the selector thread that will call the
+ * {@link Callback} instances passed in the {@link EndPoint#fillInterested(Callback)} or
+ * {@link EndPoint#write(Callback, java.nio.ByteBuffer...)} methods.  It is expected
+ * that these callbacks may do some non-blocking IO work, but will always dispatch to the
+ * {@link Executor} service any blocking, long running or application tasks.
+ * <p>
+ * The default number of selectors is equal to the number of processors available to the JVM,
+ * which should allow optimal performance even if all the connections used are performing
+ * significant non-blocking work in the callback tasks.
+ *
+ */
+ at ManagedObject("HTTP connector using NIO ByteChannels and Selectors")
+public class ServerConnector extends AbstractNetworkConnector
+{
+    private final SelectorManager _manager;
+    private volatile ServerSocketChannel _acceptChannel;
+    private volatile boolean _inheritChannel = false;
+    private volatile int _localPort = -1;
+    private volatile int _acceptQueueSize = 0;
+    private volatile boolean _reuseAddress = true;
+    private volatile int _lingerTime = -1;
+
+
+    /* ------------------------------------------------------------ */
+    /** HTTP Server Connection.
+     * <p>Construct a ServerConnector with a private instance of {@link HttpConnectionFactory} as the only factory.</p>
+     * @param server The {@link Server} this connector will accept connection for. 
+     */
+    public ServerConnector(
+        @Name("server") Server server)
+    {
+        this(server,null,null,null,-1,-1,new HttpConnectionFactory());
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** HTTP Server Connection.
+     * <p>Construct a ServerConnector with a private instance of {@link HttpConnectionFactory} as the only factory.</p>
+     * @param server The {@link Server} this connector will accept connection for. 
+     * @param acceptors 
+     *          the number of acceptor threads to use, or -1 for a default value. Acceptors accept new TCP/IP connections.  If 0, then 
+     *          the selector threads are used to accept connections.
+     * @param selectors
+     *          the number of selector threads, or <=0 for a default value. Selectors notice and schedule established connection that can make IO progress.
+     */
+    public ServerConnector(
+        @Name("server") Server server,
+        @Name("acceptors") int acceptors,
+        @Name("selectors") int selectors)
+    {
+        this(server,null,null,null,acceptors,selectors,new HttpConnectionFactory());
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** HTTP Server Connection.
+     * <p>Construct a ServerConnector with a private instance of {@link HttpConnectionFactory} as the only factory.</p>
+     * @param server The {@link Server} this connector will accept connection for. 
+     * @param acceptors 
+     *          the number of acceptor threads to use, or -1 for a default value. Acceptors accept new TCP/IP connections.  If 0, then 
+     *          the selector threads are used to accept connections.
+     * @param selectors
+     *          the number of selector threads, or <=0 for a default value. Selectors notice and schedule established connection that can make IO progress.
+     * @param factories Zero or more {@link ConnectionFactory} instances used to create and configure connections.
+     */
+    public ServerConnector(
+        @Name("server") Server server,
+        @Name("acceptors") int acceptors,
+        @Name("selectors") int selectors,
+        @Name("factories") ConnectionFactory... factories)
+    {
+        this(server,null,null,null,acceptors,selectors,factories);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Generic Server Connection with default configuration.
+     * <p>Construct a Server Connector with the passed Connection factories.</p>
+     * @param server The {@link Server} this connector will accept connection for. 
+     * @param factories Zero or more {@link ConnectionFactory} instances used to create and configure connections.
+     */
+    public ServerConnector(
+        @Name("server") Server server,
+        @Name("factories") ConnectionFactory... factories)
+    {
+        this(server,null,null,null,-1,-1,factories);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** HTTP Server Connection.
+     * <p>Construct a ServerConnector with a private instance of {@link HttpConnectionFactory} as the primary protocol</p>.
+     * @param server The {@link Server} this connector will accept connection for. 
+     * @param sslContextFactory If non null, then a {@link SslConnectionFactory} is instantiated and prepended to the 
+     * list of HTTP Connection Factory.
+     */
+    public ServerConnector(
+        @Name("server") Server server,
+        @Name("sslContextFactory") SslContextFactory sslContextFactory)
+    {
+        this(server,null,null,null,-1,-1,AbstractConnectionFactory.getFactories(sslContextFactory,new HttpConnectionFactory()));
+    }
+
+    /* ------------------------------------------------------------ */
+    /** HTTP Server Connection.
+     * <p>Construct a ServerConnector with a private instance of {@link HttpConnectionFactory} as the primary protocol</p>.
+     * @param server The {@link Server} this connector will accept connection for. 
+     * @param sslContextFactory If non null, then a {@link SslConnectionFactory} is instantiated and prepended to the 
+     * list of HTTP Connection Factory.
+     * @param acceptors 
+     *          the number of acceptor threads to use, or -1 for a default value. Acceptors accept new TCP/IP connections.  If 0, then 
+     *          the selector threads are used to accept connections.
+     * @param selectors
+     *          the number of selector threads, or <=0 for a default value. Selectors notice and schedule established connection that can make IO progress.
+     */
+    public ServerConnector(
+        @Name("server") Server server,
+        @Name("acceptors") int acceptors,
+        @Name("selectors") int selectors,
+        @Name("sslContextFactory") SslContextFactory sslContextFactory)
+    {
+        this(server,null,null,null,acceptors,selectors,AbstractConnectionFactory.getFactories(sslContextFactory,new HttpConnectionFactory()));
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Generic SSL Server Connection.
+     * @param server The {@link Server} this connector will accept connection for. 
+     * @param sslContextFactory If non null, then a {@link SslConnectionFactory} is instantiated and prepended to the 
+     * list of ConnectionFactories, with the first factory being the default protocol for the SslConnectionFactory.
+     * @param factories Zero or more {@link ConnectionFactory} instances used to create and configure connections.
+     */
+    public ServerConnector(
+        @Name("server") Server server,
+        @Name("sslContextFactory") SslContextFactory sslContextFactory,
+        @Name("factories") ConnectionFactory... factories)
+    {
+        this(server,null,null,null,-1,-1,AbstractConnectionFactory.getFactories(sslContextFactory,factories));
+    }
+
+    /** Generic Server Connection.
+     * @param server    
+     *          The server this connector will be accept connection for.  
+     * @param executor  
+     *          An executor used to run tasks for handling requests, acceptors and selectors.
+     *          If null then use the servers executor
+     * @param scheduler 
+     *          A scheduler used to schedule timeouts. If null then use the servers scheduler
+     * @param bufferPool
+     *          A ByteBuffer pool used to allocate buffers.  If null then create a private pool with default configuration.
+     * @param acceptors 
+     *          the number of acceptor threads to use, or -1 for a default value. Acceptors accept new TCP/IP connections.  If 0, then 
+     *          the selector threads are used to accept connections.
+     * @param selectors
+     *          the number of selector threads, or <=0 for a default value. Selectors notice and schedule established connection that can make IO progress.
+     * @param factories 
+     *          Zero or more {@link ConnectionFactory} instances used to create and configure connections.
+     */
+    public ServerConnector(
+        @Name("server") Server server,
+        @Name("executor") Executor executor,
+        @Name("scheduler") Scheduler scheduler,
+        @Name("bufferPool") ByteBufferPool bufferPool,
+        @Name("acceptors") int acceptors,
+        @Name("selectors") int selectors,
+        @Name("factories") ConnectionFactory... factories)
+    {
+        super(server,executor,scheduler,bufferPool,acceptors,factories);
+        _manager = new ServerConnectorManager(getExecutor(), getScheduler(), 
+            selectors>0?selectors:Math.max(1,Math.min(4,Runtime.getRuntime().availableProcessors()/2)));
+        addBean(_manager, true);
+    }
+
+    @Override
+    protected void doStart() throws Exception
+    {
+        super.doStart();
+
+        if (getAcceptors()==0)
+        {
+            _acceptChannel.configureBlocking(false);
+            _manager.acceptor(_acceptChannel);
+        }
+    }
+
+    @Override
+    public boolean isOpen()
+    {
+        ServerSocketChannel channel = _acceptChannel;
+        return channel!=null && channel.isOpen();
+    }
+
+
+    @ManagedAttribute("The priority delta to apply to selector threads")
+    public int getSelectorPriorityDelta()
+    {
+        return _manager.getSelectorPriorityDelta();
+    }
+
+    /**
+     * Sets the selector thread priority delta to the given amount.
+     * <p>This allows the selector threads to run at a different priority.
+     * Typically this would be used to lower the priority to give preference 
+     * to handling previously accepted connections rather than accepting
+     * new connections.</p>
+     *
+     * @param selectorPriorityDelta the amount to set the thread priority delta to
+     *                              (may be negative)
+     * @see Thread#getPriority()
+     */
+    public void setSelectorPriorityDelta(int selectorPriorityDelta)
+    {
+        _manager.setSelectorPriorityDelta(selectorPriorityDelta);
+    }
+    
+    /**
+     * @return whether this connector uses a channel inherited from the JVM.
+     * @see System#inheritedChannel()
+     */
+    public boolean isInheritChannel()
+    {
+        return _inheritChannel;
+    }
+
+    /**
+     * <p>Sets whether this connector uses a channel inherited from the JVM.</p>
+     * <p>If true, the connector first tries to inherit from a channel provided by the system.
+     * If there is no inherited channel available, or if the inherited channel is not usable,
+     * then it will fall back using {@link ServerSocketChannel}.</p>
+     * <p>Use it with xinetd/inetd, to launch an instance of Jetty on demand. The port
+     * used to access pages on the Jetty instance is the same as the port used to
+     * launch Jetty.</p>
+     *
+     * @param inheritChannel whether this connector uses a channel inherited from the JVM.
+     */
+    public void setInheritChannel(boolean inheritChannel)
+    {
+        _inheritChannel = inheritChannel;
+    }
+
+    @Override
+    public void open() throws IOException
+    {
+        if (_acceptChannel == null)
+        {
+            ServerSocketChannel serverChannel = null;
+            if (isInheritChannel())
+            {
+                Channel channel = System.inheritedChannel();
+                if (channel instanceof ServerSocketChannel)
+                    serverChannel = (ServerSocketChannel)channel;
+                else
+                    LOG.warn("Unable to use System.inheritedChannel() [{}]. Trying a new ServerSocketChannel at {}:{}", channel, getHost(), getPort());
+            }
+
+            if (serverChannel == null)
+            {
+                serverChannel = ServerSocketChannel.open();
+
+                InetSocketAddress bindAddress = getHost() == null ? new InetSocketAddress(getPort()) : new InetSocketAddress(getHost(), getPort());
+                serverChannel.socket().setReuseAddress(getReuseAddress());
+                serverChannel.socket().bind(bindAddress, getAcceptQueueSize());
+
+                _localPort = serverChannel.socket().getLocalPort();
+                if (_localPort <= 0)
+                    throw new IOException("Server channel not bound");
+
+                addBean(serverChannel);
+            }
+
+            serverChannel.configureBlocking(true);
+            addBean(serverChannel);
+
+            _acceptChannel = serverChannel;
+        }
+    }
+
+    @Override
+    public Future<Void> shutdown()
+    {
+        // TODO shutdown all the connections
+        return super.shutdown();
+    }
+
+    @Override
+    public void close()
+    {
+        ServerSocketChannel serverChannel = _acceptChannel;
+        _acceptChannel = null;
+
+        if (serverChannel != null)
+        {
+            removeBean(serverChannel);
+
+            // If the interrupt did not close it, we should close it
+            if (serverChannel.isOpen())
+            {
+                try
+                {
+                    serverChannel.close();
+                }
+                catch (IOException e)
+                {
+                    LOG.warn(e);
+                }
+            }
+        }
+        // super.close();
+        _localPort = -2;
+    }
+
+    @Override
+    public void accept(int acceptorID) throws IOException
+    {
+        ServerSocketChannel serverChannel = _acceptChannel;
+        if (serverChannel != null && serverChannel.isOpen())
+        {
+            SocketChannel channel = serverChannel.accept();
+            accepted(channel);
+        }
+    }
+    
+    private void accepted(SocketChannel channel) throws IOException
+    {
+        channel.configureBlocking(false);
+        Socket socket = channel.socket();
+        configure(socket);
+        _manager.accept(channel);
+    }
+
+    protected void configure(Socket socket)
+    {
+        try
+        {
+            socket.setTcpNoDelay(true);
+            if (_lingerTime >= 0)
+                socket.setSoLinger(true, _lingerTime / 1000);
+            else
+                socket.setSoLinger(false, 0);
+        }
+        catch (SocketException e)
+        {
+            LOG.ignore(e);
+        }
+    }
+
+    public SelectorManager getSelectorManager()
+    {
+        return _manager;
+    }
+
+    @Override
+    public Object getTransport()
+    {
+        return _acceptChannel;
+    }
+
+    @Override
+    @ManagedAttribute("local port")
+    public int getLocalPort()
+    {
+        return _localPort;
+    }
+
+    protected SelectChannelEndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey key) throws IOException
+    {
+        return new SelectChannelEndPoint(channel, selectSet, key, getScheduler(), getIdleTimeout());
+    }
+
+    /**
+     * @return the linger time
+     * @see Socket#getSoLinger()
+     */
+    @ManagedAttribute("TCP/IP solinger time or -1 to disable")
+    public int getSoLingerTime()
+    {
+        return _lingerTime;
+    }
+
+    /**
+     * @param lingerTime the linger time. Use -1 to disable.
+     * @see Socket#setSoLinger(boolean, int)
+     */
+    public void setSoLingerTime(int lingerTime)
+    {
+        _lingerTime = lingerTime;
+    }
+
+    /**
+     * @return the accept queue size
+     */
+    @ManagedAttribute("Accept Queue size")
+    public int getAcceptQueueSize()
+    {
+        return _acceptQueueSize;
+    }
+
+    /**
+     * @param acceptQueueSize the accept queue size (also known as accept backlog)
+     */
+    public void setAcceptQueueSize(int acceptQueueSize)
+    {
+        _acceptQueueSize = acceptQueueSize;
+    }
+
+    /**
+     * @return whether the server socket reuses addresses
+     * @see ServerSocket#getReuseAddress()
+     */
+    public boolean getReuseAddress()
+    {
+        return _reuseAddress;
+    }
+
+    /**
+     * @param reuseAddress whether the server socket reuses addresses
+     * @see ServerSocket#setReuseAddress(boolean)
+     */
+    public void setReuseAddress(boolean reuseAddress)
+    {
+        _reuseAddress = reuseAddress;
+    }
+
+    private final class ServerConnectorManager extends SelectorManager
+    {
+        private ServerConnectorManager(Executor executor, Scheduler scheduler, int selectors)
+        {
+            super(executor, scheduler, selectors);
+        }
+
+        @Override
+        protected void accepted(SocketChannel channel) throws IOException
+        {
+            ServerConnector.this.accepted(channel);
+        }
+
+        @Override
+        protected SelectChannelEndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey selectionKey) throws IOException
+        {
+            return ServerConnector.this.newEndPoint(channel, selectSet, selectionKey);
+        }
+
+        @Override
+        public Connection newConnection(SocketChannel channel, EndPoint endpoint, Object attachment) throws IOException
+        {
+            return getDefaultConnectionFactory().newConnection(ServerConnector.this, endpoint);
+        }
+
+        @Override
+        protected void endPointOpened(EndPoint endpoint)
+        {
+            super.endPointOpened(endpoint);
+            onEndPointOpened(endpoint);
+        }
+
+        @Override
+        protected void endPointClosed(EndPoint endpoint)
+        {
+            onEndPointClosed(endpoint);
+            super.endPointClosed(endpoint);
+        }
+        
+        
+    }
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ServletRequestHttpWrapper.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ServletRequestHttpWrapper.java
index 547c7dc..6371490 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/ServletRequestHttpWrapper.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ServletRequestHttpWrapper.java
@@ -31,10 +31,15 @@ import javax.servlet.http.Cookie;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpSession;
+import javax.servlet.http.HttpUpgradeHandler;
 import javax.servlet.http.Part;
 
+
 /* ------------------------------------------------------------ */
-/** Class to tunnel a ServletRequest via a HttpServletRequest
+/** 
+ * ServletRequestHttpWrapper
+ * 
+ * Class to tunnel a ServletRequest via a HttpServletRequest
  */
 public class ServletRequestHttpWrapper extends ServletRequestWrapper implements HttpServletRequest
 {
@@ -43,170 +48,221 @@ public class ServletRequestHttpWrapper extends ServletRequestWrapper implements
         super(request);
     }
 
+    @Override
     public String getAuthType()
     {
         return null;
     }
 
+    @Override
     public Cookie[] getCookies()
     {
         return null;
     }
 
+    @Override
     public long getDateHeader(String name)
     {
         return 0;
     }
 
+    @Override
     public String getHeader(String name)
     {
         return null;
     }
 
-    public Enumeration getHeaders(String name)
+    @Override
+    public Enumeration<String> getHeaders(String name)
     {
         return null;
     }
 
-    public Enumeration getHeaderNames()
+    @Override
+    public Enumeration<String> getHeaderNames()
     {
         return null;
     }
 
+    @Override
     public int getIntHeader(String name)
     {
         return 0;
     }
 
+    @Override
     public String getMethod()
     {
         return null;
     }
 
+    @Override
     public String getPathInfo()
     {
         return null;
     }
 
+    @Override
     public String getPathTranslated()
     {
         return null;
     }
 
+    @Override
     public String getContextPath()
     {
         return null;
     }
 
+    @Override
     public String getQueryString()
     {
         return null;
     }
 
+    @Override
     public String getRemoteUser()
     {
         return null;
     }
 
+    @Override
     public boolean isUserInRole(String role)
     {
         return false;
     }
 
+    @Override
     public Principal getUserPrincipal()
     {
         return null;
     }
 
+    @Override
     public String getRequestedSessionId()
     {
         return null;
     }
 
+    @Override
     public String getRequestURI()
     {
         return null;
     }
 
+    @Override
     public StringBuffer getRequestURL()
     {
         return null;
     }
 
+    @Override
     public String getServletPath()
     {
         return null;
     }
 
+    @Override
     public HttpSession getSession(boolean create)
     {
         return null;
     }
 
+    @Override
     public HttpSession getSession()
     {
         return null;
     }
 
+    @Override
     public boolean isRequestedSessionIdValid()
     {
         return false;
     }
 
+    @Override
     public boolean isRequestedSessionIdFromCookie()
     {
         return false;
     }
 
+    @Override
     public boolean isRequestedSessionIdFromURL()
     {
         return false;
     }
 
+    @Override
     public boolean isRequestedSessionIdFromUrl()
     {
         return false;
     }
 
-    /** 
+    /**
      * @see javax.servlet.http.HttpServletRequest#authenticate(javax.servlet.http.HttpServletResponse)
      */
+    @Override
     public boolean authenticate(HttpServletResponse response) throws IOException, ServletException
     {
         return false;
     }
 
-    /** 
+    /**
      * @see javax.servlet.http.HttpServletRequest#getPart(java.lang.String)
      */
+    @Override
     public Part getPart(String name) throws IOException, ServletException
     {
         return null;
     }
 
-    /** 
+    /**
      * @see javax.servlet.http.HttpServletRequest#getParts()
      */
+    @Override
     public Collection<Part> getParts() throws IOException, ServletException
     {
         return null;
     }
 
-    /** 
+    /**
      * @see javax.servlet.http.HttpServletRequest#login(java.lang.String, java.lang.String)
      */
+    @Override
     public void login(String username, String password) throws ServletException
     {
 
     }
 
-    /** 
+    /**
      * @see javax.servlet.http.HttpServletRequest#logout()
      */
+    @Override
     public void logout() throws ServletException
     {
-        
+
     }
 
-    
+
+    /** 
+     * @see javax.servlet.http.HttpServletRequest#changeSessionId()
+     */
+    @Override
+    public String changeSessionId()
+    {
+        // TODO 3.1 Auto-generated method stub
+        return null;
+    }
+
+    /** 
+     * @see javax.servlet.http.HttpServletRequest#upgrade(java.lang.Class)
+     */
+    @Override
+    public <T extends HttpUpgradeHandler> T upgrade(Class<T> handlerClass) throws IOException, ServletException
+    {
+        // TODO 3.1 Auto-generated method stub
+        return null;
+    }
+
+
 }
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ServletResponseHttpWrapper.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ServletResponseHttpWrapper.java
index 78264eb..decd020 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/ServletResponseHttpWrapper.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ServletResponseHttpWrapper.java
@@ -28,7 +28,10 @@ import javax.servlet.http.HttpServletResponse;
 
 
 /* ------------------------------------------------------------ */
-/** Wrapper to tunnel a ServletResponse via a HttpServletResponse
+/** 
+ * ServletResponseHttpWrapper
+ * 
+ * Wrapper to tunnel a ServletResponse via a HttpServletResponse
  */
 public class ServletResponseHttpWrapper extends ServletResponseWrapper implements HttpServletResponse
 {
@@ -38,7 +41,7 @@ public class ServletResponseHttpWrapper extends ServletResponseWrapper implement
     }
 
     public void addCookie(Cookie cookie)
-    {        
+    {
     }
 
     public boolean containsHeader(String name)
@@ -67,50 +70,50 @@ public class ServletResponseHttpWrapper extends ServletResponseWrapper implement
     }
 
     public void sendError(int sc, String msg) throws IOException
-    {        
+    {
     }
 
     public void sendError(int sc) throws IOException
-    {        
+    {
     }
 
     public void sendRedirect(String location) throws IOException
-    {        
+    {
     }
 
     public void setDateHeader(String name, long date)
-    {        
+    {
     }
 
     public void addDateHeader(String name, long date)
-    {        
+    {
     }
 
     public void setHeader(String name, String value)
-    {        
+    {
     }
 
     public void addHeader(String name, String value)
-    {        
+    {
     }
 
     public void setIntHeader(String name, int value)
-    {        
+    {
     }
 
     public void addIntHeader(String name, int value)
-    {        
+    {
     }
 
     public void setStatus(int sc)
-    {        
+    {
     }
 
     public void setStatus(int sc, String sm)
-    {        
+    {
     }
 
-    /** 
+    /**
      * @see javax.servlet.http.HttpServletResponse#getHeader(java.lang.String)
      */
     public String getHeader(String name)
@@ -118,7 +121,7 @@ public class ServletResponseHttpWrapper extends ServletResponseWrapper implement
         return null;
     }
 
-    /** 
+    /**
      * @see javax.servlet.http.HttpServletResponse#getHeaderNames()
      */
     public Collection<String> getHeaderNames()
@@ -126,7 +129,7 @@ public class ServletResponseHttpWrapper extends ServletResponseWrapper implement
         return null;
     }
 
-    /** 
+    /**
      * @see javax.servlet.http.HttpServletResponse#getHeaders(java.lang.String)
      */
     public Collection<String> getHeaders(String name)
@@ -134,7 +137,7 @@ public class ServletResponseHttpWrapper extends ServletResponseWrapper implement
         return null;
     }
 
-    /** 
+    /**
      * @see javax.servlet.http.HttpServletResponse#getStatus()
      */
     public int getStatus()
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/SessionIdManager.java b/jetty-server/src/main/java/org/eclipse/jetty/server/SessionIdManager.java
index 8142aff..cb0dabf 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/SessionIdManager.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/SessionIdManager.java
@@ -59,6 +59,8 @@ public interface SessionIdManager extends LifeCycle
      */
     public String newSessionId(HttpServletRequest request,long created);
     
+    
+    
     public String getWorkerName();
     
     
@@ -78,4 +80,15 @@ public interface SessionIdManager extends LifeCycle
      */
     public String getNodeId(String clusterId,HttpServletRequest request);
     
+    
+    /* ------------------------------------------------------------ */
+    /** Change the existing session id.
+    * 
+    * @param oldClusterId
+    * @param oldNodeId
+    * @param request
+    */
+    public void renewSessionId(String oldClusterId, String oldNodeId, HttpServletRequest request);    
+
+    
 }
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/SessionManager.java b/jetty-server/src/main/java/org/eclipse/jetty/server/SessionManager.java
index 29cbd3a..dccd943 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/SessionManager.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/SessionManager.java
@@ -23,7 +23,6 @@ import java.util.Set;
 
 import javax.servlet.SessionCookieConfig;
 import javax.servlet.SessionTrackingMode;
-import javax.servlet.http.Cookie;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpSession;
 
@@ -281,7 +280,7 @@ public interface SessionManager extends LifeCycle
      * @return whether the session management is handled via cookies.
      */
     public boolean isUsingCookies();
-    
+
     /**
      * @return whether the session management is handled via URLs.
      */
@@ -294,14 +293,24 @@ public interface SessionManager extends LifeCycle
     public void setSessionTrackingModes(Set<SessionTrackingMode> sessionTrackingModes);
 
     public SessionCookieConfig getSessionCookieConfig();
-    
+
     /**
      * @return True if absolute URLs are check for remoteness before being session encoded.
      */
     public boolean isCheckingRemoteSessionIdEncoding();
-    
+
     /**
      * @param remote True if absolute URLs are check for remoteness before being session encoded.
      */
     public void setCheckingRemoteSessionIdEncoding(boolean remote);
+    
+    /* ------------------------------------------------------------ */
+    /** Change the existing session id.
+    * 
+    * @param oldClusterId
+    * @param oldNodeId
+    * @param newClusterId
+    * @param newNodeId
+    */
+    public void renewSessionId(String oldClusterId, String oldNodeId, String newClusterId, String newNodeId);  
 }
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ShutdownMonitor.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ShutdownMonitor.java
index 07847b3..3b71a74 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/ShutdownMonitor.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ShutdownMonitor.java
@@ -23,18 +23,24 @@ import java.io.InputStreamReader;
 import java.io.LineNumberReader;
 import java.io.OutputStream;
 import java.net.InetAddress;
+import java.net.InetSocketAddress;
 import java.net.ServerSocket;
 import java.net.Socket;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
 import java.util.Properties;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
 
-import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.component.Destroyable;
+import org.eclipse.jetty.util.component.LifeCycle;
 import org.eclipse.jetty.util.thread.ShutdownThread;
 
 /**
  * Shutdown/Stop Monitor thread.
  * <p>
- * This thread listens on the port specified by the STOP.PORT system parameter (defaults to -1 for not listening) for request authenticated with the key given
- * by the STOP.KEY system parameter (defaults to "eclipse") for admin requests.
+ * This thread listens on the host/port specified by the STOP.HOST/STOP.PORT system parameter (defaults to 127.0.0.1/-1 for not listening) for 
+ * request authenticated with the key given by the STOP.KEY system parameter (defaults to "eclipse") for admin requests.
  * <p>
  * If the stop port is set to zero, then a random port is assigned and the port number is printed to stdout.
  * <p>
@@ -42,6 +48,8 @@ import org.eclipse.jetty.util.thread.ShutdownThread;
  */
 public class ShutdownMonitor 
 {
+    private final Set<LifeCycle> _lifeCycles = new CopyOnWriteArraySet<LifeCycle>();
+    
     // Implementation of safe lazy init, using Initialization on Demand Holder technique.
     static class Holder
     {
@@ -52,22 +60,40 @@ public class ShutdownMonitor
     {
         return Holder.instance;
     }
+    
+    /* ------------------------------------------------------------ */
+    public static synchronized void register(LifeCycle... lifeCycles)
+    {
+        getInstance()._lifeCycles.addAll(Arrays.asList(lifeCycles));
+    }
 
+   
+    /* ------------------------------------------------------------ */
+    public static synchronized void deregister(LifeCycle lifeCycle)
+    {
+        getInstance()._lifeCycles.remove(lifeCycle);
+    }
+    
+    /* ------------------------------------------------------------ */
+    public static synchronized boolean isRegistered(LifeCycle lifeCycle)
+    {
+        return getInstance()._lifeCycles.contains(lifeCycle);
+    }
+    
+    /* ------------------------------------------------------------ */
     /**
-     * ShutdownMonitorThread
+     * ShutdownMonitorRunnable
      *
      * Thread for listening to STOP.PORT for command to stop Jetty.
      * If ShowndownMonitor.exitVm is true, then Sytem.exit will also be
      * called after the stop.
      *
      */
-    public class ShutdownMonitorThread extends Thread
+    private class ShutdownMonitorRunnable implements Runnable
     {
-
-        public ShutdownMonitorThread ()
+        public ShutdownMonitorRunnable()
         {
-            setDaemon(true);
-            setName("ShutdownMonitor");
+            startListenSocket();
         }
         
         @Override
@@ -97,23 +123,38 @@ public class ShutdownMonitor
 
                     String cmd = lin.readLine();
                     debug("command=%s",cmd);
-                    if ("stop".equals(cmd))
+                    if ("stop".equalsIgnoreCase(cmd)) //historic, for backward compatibility
                     {
-                        // Graceful Shutdown
-                        debug("Issuing graceful shutdown..");
-                        ShutdownThread.getInstance().run();
+                        //Stop the lifecycles, only if they are registered with the ShutdownThread, only destroying if vm is exiting
+                        debug("Issuing stop...");
+                        
+                        for (LifeCycle l:_lifeCycles)
+                        {
+                            try
+                            {
+                                if (l.isStarted() && ShutdownThread.isRegistered(l))
+                                {
+                                    l.stop();
+                                }
+                                
+                                if ((l instanceof Destroyable) && exitVm)
+                                    ((Destroyable)l).destroy();
+                            }
+                            catch (Exception e)
+                            {
+                                debug(e);
+                            }
+                        }
+
+                        //Stop accepting any more commands
+                        stopInput(socket);
 
                         // Reply to client
                         debug("Informing client that we are stopped.");
-                        out.write("Stopped\r\n".getBytes(StringUtil.__UTF8));
-                        out.flush();
+                        informClient(out, "Stopped\r\n");
 
-                        // Shutdown Monitor
-                        debug("Shutting down monitor");
-                        close(socket);
-                        socket = null;
-                        close(serverSocket);
-                        serverSocket = null;
+                        //Stop the output and close the monitor socket
+                        stopOutput(socket);
 
                         if (exitVm)
                         {
@@ -122,11 +163,59 @@ public class ShutdownMonitor
                             System.exit(0);
                         }
                     }
-                    else if ("status".equals(cmd))
+                    else if ("forcestop".equalsIgnoreCase(cmd))
                     {
+                        debug("Issuing force stop...");
+                        
+                        //Ensure that objects are stopped, destroyed only if vm is forcibly exiting
+                        stopLifeCycles(exitVm);
+
+                        //Stop accepting any more commands
+                        stopInput(socket);
+
                         // Reply to client
-                        out.write("OK\r\n".getBytes(StringUtil.__UTF8));
-                        out.flush();
+                        debug("Informing client that we are stopped.");
+                        informClient(out, "Stopped\r\n");
+
+                        //Stop the output and close the monitor socket
+                        stopOutput(socket);
+                        
+                        //Honour any pre-setup config to stop the jvm when this command is given
+                        if (exitVm)
+                        {
+                            // Kill JVM
+                            debug("Killing JVM");
+                            System.exit(0);
+                        }
+                    }
+                    else if ("stopexit".equalsIgnoreCase(cmd))
+                    {
+                        debug("Issuing stop and exit...");
+                        //Make sure that objects registered with the shutdown thread will be stopped
+                        stopLifeCycles(true);
+                        
+                        //Stop accepting any more input
+                        stopInput(socket);
+
+                        // Reply to client
+                        debug("Informing client that we are stopped.");                       
+                        informClient(out, "Stopped\r\n");              
+
+                        //Stop the output and close the monitor socket
+                        stopOutput(socket);
+                        
+                        debug("Killing JVM");
+                        System.exit(0);
+                    }
+                    else if ("exit".equalsIgnoreCase(cmd))
+                    {
+                        debug("Killing JVM");
+                        System.exit(0);
+                    }
+                    else if ("status".equalsIgnoreCase(cmd))
+                    {
+                        // Reply to client
+                        informClient(out, "OK\r\n");
                     }
                 }
                 catch (Exception e)
@@ -142,26 +231,58 @@ public class ShutdownMonitor
             }
         }
         
-        public void start()
+        public void stopInput (Socket socket)
         {
-            if (isAlive())
-            {
-                System.err.printf("ShutdownMonitorThread already started");
-                return; // cannot start it again
-            }
+            //Stop accepting any more input
+            close(serverSocket);
+            serverSocket = null;
+            //Shutdown input from client
+            shutdownInput(socket);  
+        }
+        
+        public void stopOutput (Socket socket) throws IOException
+        {
+            socket.shutdownOutput();
+            close(socket);
+            socket = null;                        
+            debug("Shutting down monitor");
+            serverSocket = null;
+        }
+        
+        public void informClient (OutputStream out, String message) throws IOException
+        {
+            out.write(message.getBytes(StandardCharsets.UTF_8));
+            out.flush();
+        }
 
-            startListenSocket();
-            
-            if (serverSocket == null)
+        /**
+         * Stop the registered lifecycles, optionally
+         * calling destroy on them.
+         * 
+         * @param destroy
+         */
+        public void stopLifeCycles (boolean destroy)
+        {
+            for (LifeCycle l:_lifeCycles)
             {
-                return;
+                try
+                {
+                    if (l.isStarted())
+                    {
+                        l.stop();
+                    }
+                    
+                    if ((l instanceof Destroyable) && destroy)
+                        ((Destroyable)l).destroy();
+                }
+                catch (Exception e)
+                {
+                    debug(e);
+                }
             }
-            if (DEBUG)
-                System.err.println("Starting ShutdownMonitorThread");
-            super.start();
         }
-        
-        private void startListenSocket()
+
+        public void startListenSocket()
         {
             if (port < 0)
             {            
@@ -172,7 +293,9 @@ public class ShutdownMonitor
 
             try
             {
-                serverSocket = new ServerSocket(port,1,InetAddress.getByName("127.0.0.1"));
+                serverSocket = new ServerSocket();
+                serverSocket.setReuseAddress(true);
+                serverSocket.bind(new InetSocketAddress(InetAddress.getByName(host), port), 1);
                 if (port == 0)
                 {
                     // server assigned port in use
@@ -205,13 +328,12 @@ public class ShutdownMonitor
     }
     
     private boolean DEBUG;
+    private String host;
     private int port;
     private String key;
     private boolean exitVm;
     private ServerSocket serverSocket;
-    private ShutdownMonitorThread thread;
-    
-    
+    private Thread thread;
 
     /**
      * Create a ShutdownMonitor using configuration from the System properties.
@@ -228,6 +350,7 @@ public class ShutdownMonitor
         this.DEBUG = props.containsKey("DEBUG");
 
         // Use values passed thru via /jetty-start/
+        this.host = props.getProperty("STOP.HOST","127.0.0.1");
         this.port = Integer.parseInt(props.getProperty("STOP.PORT","-1"));
         this.key = props.getProperty("STOP.KEY",null);
         this.exitVm = true;
@@ -246,7 +369,7 @@ public class ShutdownMonitor
         }
         catch (IOException ignore)
         {
-            /* ignore */
+            debug(ignore);
         }
     }
 
@@ -263,10 +386,27 @@ public class ShutdownMonitor
         }
         catch (IOException ignore)
         {
-            /* ignore */
+            debug(ignore);
         }
     }
 
+    
+    private void shutdownInput(Socket socket)
+    {
+        if (socket == null)
+            return;
+        
+        try
+        {
+            socket.shutdownInput();
+        }   
+        catch (IOException ignore)
+        {
+            debug(ignore);
+        }
+    }
+    
+    
     private void debug(String format, Object... args)
     {
         if (DEBUG)
@@ -309,6 +449,9 @@ public class ShutdownMonitor
         this.DEBUG = flag;
     }
 
+    /**
+     * @param exitVm
+     */
     public void setExitVm(boolean exitVm)
     {
         synchronized (this)
@@ -347,16 +490,20 @@ public class ShutdownMonitor
 
     protected void start() throws Exception
     {
-        ShutdownMonitorThread t = null;
+        Thread t = null;
+        
         synchronized (this)
         {
             if (thread != null && thread.isAlive())
             {
-                System.err.printf("ShutdownMonitorThread already started");
+                if (DEBUG)
+                    System.err.printf("ShutdownMonitorThread already started");
                 return; // cannot start it again
             }
          
-            thread = new ShutdownMonitorThread();
+            thread = new Thread(new ShutdownMonitorRunnable());
+            thread.setDaemon(true);
+            thread.setName("ShutdownMonitor");
             t = thread;
         }
          
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Slf4jRequestLog.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Slf4jRequestLog.java
new file mode 100644
index 0000000..6d95b00
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Slf4jRequestLog.java
@@ -0,0 +1,69 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.log.Slf4jLog;
+
+/**
+ * Implementation of NCSARequestLog where output is sent as a SLF4J INFO Log message on the named logger "org.eclipse.jetty.server.RequestLog"
+ */
+ at ManagedObject("NCSA standard format request log to slf4j bridge")
+public class Slf4jRequestLog extends AbstractNCSARequestLog implements RequestLog
+{
+    private Slf4jLog logger;
+    private String loggerName;
+
+    public Slf4jRequestLog()
+    {
+        // Default logger name (can be set)
+        this.loggerName = "org.eclipse.jetty.server.RequestLog";
+    }
+
+    public void setLoggerName(String loggerName)
+    {
+        this.loggerName = loggerName;
+    }
+
+    public String getLoggerName()
+    {
+        return loggerName;
+    }
+
+    @Override
+    protected boolean isEnabled()
+    {
+        return logger != null;
+    }
+
+    @Override
+    public void write(String requestEntry) throws IOException
+    {
+        logger.info(requestEntry);
+    }
+
+    @Override
+    protected synchronized void doStart() throws Exception
+    {
+        logger = new Slf4jLog(loggerName);
+        super.doStart();
+    }
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/SslConnectionFactory.java b/jetty-server/src/main/java/org/eclipse/jetty/server/SslConnectionFactory.java
new file mode 100644
index 0000000..42abe64
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/SslConnectionFactory.java
@@ -0,0 +1,102 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.server;
+
+
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLSession;
+
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.ssl.SslConnection;
+import org.eclipse.jetty.util.annotation.Name;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+
+public class SslConnectionFactory extends AbstractConnectionFactory
+{
+    private final SslContextFactory _sslContextFactory;
+    private final String _nextProtocol;
+
+    public SslConnectionFactory()
+    {
+        this(HttpVersion.HTTP_1_1.asString());
+    }
+
+    public SslConnectionFactory(@Name("next") String nextProtocol)
+    {
+        this(null,nextProtocol);
+    }
+
+    public SslConnectionFactory(@Name("sslContextFactory") SslContextFactory factory, @Name("next") String nextProtocol)
+    {
+        super("SSL-"+nextProtocol);
+        _sslContextFactory=factory==null?new SslContextFactory():factory;
+        _nextProtocol=nextProtocol;
+        addBean(_sslContextFactory);
+    }
+
+    public SslContextFactory getSslContextFactory()
+    {
+        return _sslContextFactory;
+    }
+
+    @Override
+    protected void doStart() throws Exception
+    {
+        super.doStart();
+
+        SSLEngine engine = _sslContextFactory.newSSLEngine();
+        engine.setUseClientMode(false);
+        SSLSession session=engine.getSession();
+
+        if (session.getPacketBufferSize()>getInputBufferSize())
+            setInputBufferSize(session.getPacketBufferSize());
+    }
+
+    @Override
+    public Connection newConnection(Connector connector, EndPoint endPoint)
+    {
+        SSLEngine engine = _sslContextFactory.newSSLEngine(endPoint.getRemoteAddress());
+        engine.setUseClientMode(false);
+
+        SslConnection sslConnection = newSslConnection(connector, endPoint, engine);
+        sslConnection.setRenegotiationAllowed(_sslContextFactory.isRenegotiationAllowed());
+        configure(sslConnection, connector, endPoint);
+
+        ConnectionFactory next = connector.getConnectionFactory(_nextProtocol);
+        EndPoint decryptedEndPoint = sslConnection.getDecryptedEndPoint();
+        Connection connection = next.newConnection(connector, decryptedEndPoint);
+        decryptedEndPoint.setConnection(connection);
+
+        return sslConnection;
+    }
+
+    protected SslConnection newSslConnection(Connector connector, EndPoint endPoint, SSLEngine engine)
+    {
+        return new SslConnection(connector.getByteBufferPool(), connector.getExecutor(), endPoint, engine);
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s@%x{%s}",this.getClass().getSimpleName(),hashCode(),getProtocol());
+    }
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/UserIdentity.java b/jetty-server/src/main/java/org/eclipse/jetty/server/UserIdentity.java
index 3e74b86..48026fd 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/UserIdentity.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/UserIdentity.java
@@ -17,13 +17,14 @@
 //
 
 package org.eclipse.jetty.server;
+
 import java.security.Principal;
 import java.util.Map;
 
 import javax.security.auth.Subject;
 
 /* ------------------------------------------------------------ */
-/** User object that encapsulates user identity and operations such as run-as-role actions, 
+/** User object that encapsulates user identity and operations such as run-as-role actions,
  * checking isUserInRole and getUserPrincipal.
  *
  * Implementations of UserIdentity should be immutable so that they may be
@@ -46,20 +47,20 @@ public interface UserIdentity
 
     /* ------------------------------------------------------------ */
     /** Check if the user is in a role.
-     * This call is used to satisfy authorization calls from 
+     * This call is used to satisfy authorization calls from
      * container code which will be using translated role names.
      * @param role A role name.
      * @param scope
      * @return True if the user can act in that role.
      */
     boolean isUserInRole(String role, Scope scope);
-    
+
 
     /* ------------------------------------------------------------ */
     /**
      * A UserIdentity Scope.
-     * A scope is the environment in which a User Identity is to 
-     * be interpreted. Typically it is set by the target servlet of 
+     * A scope is the environment in which a User Identity is to
+     * be interpreted. Typically it is set by the target servlet of
      * a request.
      */
     interface Scope
@@ -69,13 +70,13 @@ public interface UserIdentity
          * @return The context path that the identity is being considered within
          */
         String getContextPath();
-        
+
         /* ------------------------------------------------------------ */
         /**
          * @return The name of the identity context. Typically this is the servlet name.
          */
         String getName();
-        
+
         /* ------------------------------------------------------------ */
         /**
          * @return A map of role reference names that converts from names used by application code
@@ -83,7 +84,7 @@ public interface UserIdentity
          */
         Map<String,String> getRoleRefMap();
     }
-    
+
     /* ------------------------------------------------------------ */
     public interface UnauthenticatedUserIdentity extends UserIdentity
     {
@@ -96,17 +97,17 @@ public interface UserIdentity
         {
             return null;
         }
-        
+
         public Principal getUserPrincipal()
         {
             return null;
         }
-        
+
         public boolean isUserInRole(String role, Scope scope)
         {
             return false;
         }
-        
+
         @Override
         public String toString()
         {
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Utf8HttpWriter.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Utf8HttpWriter.java
new file mode 100644
index 0000000..ae878d2
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Utf8HttpWriter.java
@@ -0,0 +1,188 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+
+/** OutputWriter.
+ * A writer that can wrap a {@link HttpOutput} stream and provide
+ * character encodings.
+ *
+ * The UTF-8 encoding is done by this class and no additional
+ * buffers or Writers are used.
+ * The UTF-8 code was inspired by http://javolution.org
+ */
+public class Utf8HttpWriter extends HttpWriter
+{
+    int _surrogate=0;
+
+    /* ------------------------------------------------------------ */
+    public Utf8HttpWriter(HttpOutput out)
+    {
+        super(out);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void write (char[] s,int offset, int length) throws IOException
+    {
+        HttpOutput out = _out;
+        if (length==0 && out.isAllContentWritten())
+        {
+            close();
+            return;
+        }
+        
+        while (length > 0)
+        {
+            _bytes.reset();
+            int chars = length>MAX_OUTPUT_CHARS?MAX_OUTPUT_CHARS:length;
+
+            byte[] buffer=_bytes.getBuf();
+            int bytes=_bytes.getCount();
+
+            if (bytes+chars>buffer.length)
+                chars=buffer.length-bytes;
+
+            for (int i = 0; i < chars; i++)
+            {
+                int code = s[offset+i];
+
+                // Do we already have a surrogate?
+                if(_surrogate==0)
+                {
+                    // No - is this char code a surrogate?
+                    if(Character.isHighSurrogate((char)code))
+                    {
+                        _surrogate=code; // UCS-?
+                        continue;
+                    }
+                }
+                // else handle a low surrogate
+                else if(Character.isLowSurrogate((char)code))
+                {
+                    code = Character.toCodePoint((char)_surrogate, (char)code); // UCS-4
+                }
+                // else UCS-2
+                else
+                {
+                    code=_surrogate; // UCS-2
+                    _surrogate=0; // USED
+                    i--;
+                }
+
+                if ((code & 0xffffff80) == 0)
+                {
+                    // 1b
+                    if (bytes>=buffer.length)
+                    {
+                        chars=i;
+                        break;
+                    }
+                    buffer[bytes++]=(byte)(code);
+                }
+                else
+                {
+                    if((code&0xfffff800)==0)
+                    {
+                        // 2b
+                        if (bytes+2>buffer.length)
+                        {
+                            chars=i;
+                            break;
+                        }
+                        buffer[bytes++]=(byte)(0xc0|(code>>6));
+                        buffer[bytes++]=(byte)(0x80|(code&0x3f));
+                    }
+                    else if((code&0xffff0000)==0)
+                    {
+                        // 3b
+                        if (bytes+3>buffer.length)
+                        {
+                            chars=i;
+                            break;
+                        }
+                        buffer[bytes++]=(byte)(0xe0|(code>>12));
+                        buffer[bytes++]=(byte)(0x80|((code>>6)&0x3f));
+                        buffer[bytes++]=(byte)(0x80|(code&0x3f));
+                    }
+                    else if((code&0xff200000)==0)
+                    {
+                        // 4b
+                        if (bytes+4>buffer.length)
+                        {
+                            chars=i;
+                            break;
+                        }
+                        buffer[bytes++]=(byte)(0xf0|(code>>18));
+                        buffer[bytes++]=(byte)(0x80|((code>>12)&0x3f));
+                        buffer[bytes++]=(byte)(0x80|((code>>6)&0x3f));
+                        buffer[bytes++]=(byte)(0x80|(code&0x3f));
+                    }
+                    else if((code&0xf4000000)==0)
+                    {
+                        // 5b
+                        if (bytes+5>buffer.length)
+                        {
+                            chars=i;
+                            break;
+                        }
+                        buffer[bytes++]=(byte)(0xf8|(code>>24));
+                        buffer[bytes++]=(byte)(0x80|((code>>18)&0x3f));
+                        buffer[bytes++]=(byte)(0x80|((code>>12)&0x3f));
+                        buffer[bytes++]=(byte)(0x80|((code>>6)&0x3f));
+                        buffer[bytes++]=(byte)(0x80|(code&0x3f));
+                    }
+                    else if((code&0x80000000)==0)
+                    {
+                        // 6b
+                        if (bytes+6>buffer.length)
+                        {
+                            chars=i;
+                            break;
+                        }
+                        buffer[bytes++]=(byte)(0xfc|(code>>30));
+                        buffer[bytes++]=(byte)(0x80|((code>>24)&0x3f));
+                        buffer[bytes++]=(byte)(0x80|((code>>18)&0x3f));
+                        buffer[bytes++]=(byte)(0x80|((code>>12)&0x3f));
+                        buffer[bytes++]=(byte)(0x80|((code>>6)&0x3f));
+                        buffer[bytes++]=(byte)(0x80|(code&0x3f));
+                    }
+                    else
+                    {
+                        buffer[bytes++]=(byte)('?');
+                    }
+
+                    _surrogate=0; // USED
+
+                    if (bytes==buffer.length)
+                    {
+                        chars=i+1;
+                        break;
+                    }
+                }
+            }
+            _bytes.setCount(bytes);
+
+            _bytes.writeTo(out);
+            length-=chars;
+            offset+=chars;
+        }
+    }
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/bio/SocketConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/bio/SocketConnector.java
deleted file mode 100644
index 7012bf4..0000000
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/bio/SocketConnector.java
+++ /dev/null
@@ -1,325 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.server.bio;
-
-import java.io.IOException;
-import java.net.InetAddress;
-import java.net.ServerSocket;
-import java.net.Socket;
-import java.net.SocketException;
-import java.util.HashSet;
-import java.util.Set;
-
-import org.eclipse.jetty.http.HttpException;
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.ConnectedEndPoint;
-import org.eclipse.jetty.io.Connection;
-import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.io.EofException;
-import org.eclipse.jetty.io.bio.SocketEndPoint;
-import org.eclipse.jetty.server.AbstractConnector;
-import org.eclipse.jetty.server.AbstractHttpConnection;
-import org.eclipse.jetty.server.BlockingHttpConnection;
-import org.eclipse.jetty.server.Request;
-import org.eclipse.jetty.util.component.AggregateLifeCycle;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-
-
-/* ------------------------------------------------------------------------------- */
-/**  Socket Connector.
- * This connector implements a traditional blocking IO and threading model.
- * Normal JRE sockets are used and a thread is allocated per connection.
- * Buffers are managed so that large buffers are only allocated to active connections.
- *
- * This Connector should only be used if NIO is not available.
- *
- * @org.apache.xbean.XBean element="bioConnector" description="Creates a BIO based socket connector"
- *
- *
- */
-public class SocketConnector extends AbstractConnector
-{
-    private static final Logger LOG = Log.getLogger(SocketConnector.class);
-
-    protected ServerSocket _serverSocket;
-    protected final Set<EndPoint> _connections;
-    protected volatile int _localPort=-1;
-
-    /* ------------------------------------------------------------ */
-    /** Constructor.
-     *
-     */
-    public SocketConnector()
-    {
-        _connections=new HashSet<EndPoint>();
-    }
-
-    /* ------------------------------------------------------------ */
-    public Object getConnection()
-    {
-        return _serverSocket;
-    }
-
-    /* ------------------------------------------------------------ */
-    public void open() throws IOException
-    {
-        // Create a new server socket and set to non blocking mode
-        if (_serverSocket==null || _serverSocket.isClosed())
-        _serverSocket= newServerSocket(getHost(),getPort(),getAcceptQueueSize());
-        _serverSocket.setReuseAddress(getReuseAddress());
-        _localPort=_serverSocket.getLocalPort();
-        if (_localPort<=0)
-            throw new IllegalStateException("port not allocated for "+this);
-
-    }
-
-    /* ------------------------------------------------------------ */
-    protected ServerSocket newServerSocket(String host, int port,int backlog) throws IOException
-    {
-        ServerSocket ss= host==null?
-            new ServerSocket(port,backlog):
-            new ServerSocket(port,backlog,InetAddress.getByName(host));
-
-        return ss;
-    }
-
-    /* ------------------------------------------------------------ */
-    public void close() throws IOException
-    {
-        if (_serverSocket!=null)
-            _serverSocket.close();
-        _serverSocket=null;
-        _localPort=-2;
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public void accept(int acceptorID)
-    	throws IOException, InterruptedException
-    {
-        Socket socket = _serverSocket.accept();
-        configure(socket);
-
-        ConnectorEndPoint connection=new ConnectorEndPoint(socket);
-        connection.dispatch();
-    }
-
-    /* ------------------------------------------------------------------------------- */
-    /**
-     * Allows subclass to override Conection if required.
-     */
-    protected Connection newConnection(EndPoint endpoint)
-    {
-        return new BlockingHttpConnection(this, endpoint, getServer());
-    }
-
-    /* ------------------------------------------------------------------------------- */
-    @Override
-    public void customize(EndPoint endpoint, Request request)
-        throws IOException
-    {
-        ConnectorEndPoint connection = (ConnectorEndPoint)endpoint;
-        int lrmit = isLowResources()?_lowResourceMaxIdleTime:_maxIdleTime;
-        connection.setMaxIdleTime(lrmit);
-
-        super.customize(endpoint, request);
-    }
-
-    /* ------------------------------------------------------------------------------- */
-    public int getLocalPort()
-    {
-        return _localPort;
-    }
-
-    /* ------------------------------------------------------------------------------- */
-    @Override
-    protected void doStart() throws Exception
-    {
-        _connections.clear();
-        super.doStart();
-    }
-
-    /* ------------------------------------------------------------------------------- */
-    @Override
-    protected void doStop() throws Exception
-    {
-        super.doStop();
-        Set<EndPoint> set = new HashSet<EndPoint>();
-        synchronized(_connections)
-        {
-            set.addAll(_connections);
-        }
-        for (EndPoint endPoint : set)
-        {
-            ConnectorEndPoint connection = (ConnectorEndPoint)endPoint;
-            connection.close();
-        }
-    }
-
-    @Override
-    public void dump(Appendable out, String indent) throws IOException
-    {
-        super.dump(out, indent);
-        Set<EndPoint> connections = new HashSet<EndPoint>();
-        synchronized (_connections)
-        {
-            connections.addAll(_connections);
-        }
-        AggregateLifeCycle.dump(out, indent, connections);
-    }
-
-    /* ------------------------------------------------------------------------------- */
-    /* ------------------------------------------------------------------------------- */
-    /* ------------------------------------------------------------------------------- */
-    protected class ConnectorEndPoint extends SocketEndPoint implements Runnable, ConnectedEndPoint
-    {
-        volatile Connection _connection;
-        protected final Socket _socket;
-
-        public ConnectorEndPoint(Socket socket) throws IOException
-        {
-            super(socket,_maxIdleTime);
-            _connection = newConnection(this);
-            _socket=socket;
-        }
-
-        public Connection getConnection()
-        {
-            return _connection;
-        }
-
-        public void setConnection(Connection connection)
-        {
-            if (_connection!=connection && _connection!=null)
-                connectionUpgraded(_connection,connection);
-            _connection=connection;
-        }
-
-        public void dispatch() throws IOException
-        {
-            if (getThreadPool()==null || !getThreadPool().dispatch(this))
-            {
-                LOG.warn("dispatch failed for {}",_connection);
-                close();
-            }
-        }
-
-        @Override
-        public int fill(Buffer buffer) throws IOException
-        {
-            int l = super.fill(buffer);
-            if (l<0)
-            {
-                if (!isInputShutdown())
-                    shutdownInput();
-                if (isOutputShutdown())
-                    close();
-            }
-            return l;
-        }
-
-        @Override
-        public void close() throws IOException
-        {
-            if (_connection instanceof AbstractHttpConnection)
-                ((AbstractHttpConnection)_connection).getRequest().getAsyncContinuation().cancel();
-            super.close();
-        }
-
-        public void run()
-        {
-            try
-            {
-                connectionOpened(_connection);
-                synchronized(_connections)
-                {
-                    _connections.add(this);
-                }
-
-                while (isStarted() && !isClosed())
-                {
-                    if (_connection.isIdle())
-                    {
-                        if (isLowResources())
-                            setMaxIdleTime(getLowResourcesMaxIdleTime());
-                    }
-
-                    _connection=_connection.handle();
-                }
-            }
-            catch (EofException e)
-            {
-                LOG.debug("EOF", e);
-                try{close();}
-                catch(IOException e2){LOG.ignore(e2);}
-            }
-            catch (SocketException e)
-            {
-                LOG.debug("EOF", e);
-                try{close();}
-                catch(IOException e2){LOG.ignore(e2);}
-            }
-            catch (HttpException e)
-            {
-                LOG.debug("BAD", e);
-                try{close();}
-                catch(IOException e2){LOG.ignore(e2);}
-            }
-            catch(Exception e)
-            {
-                LOG.warn("handle failed?",e);
-                try{close();}
-                catch(IOException e2){LOG.ignore(e2);}
-            }
-            finally
-            {
-                connectionClosed(_connection);
-                synchronized(_connections)
-                {
-                    _connections.remove(this);
-                }
-
-                // wait for client to close, but if not, close ourselves.
-                try
-                {
-                    if (!_socket.isClosed())
-                    {
-                        long timestamp=System.currentTimeMillis();
-                        int max_idle=getMaxIdleTime();
-
-                        _socket.setSoTimeout(getMaxIdleTime());
-                        int c=0;
-                        do
-                        {
-                            c = _socket.getInputStream().read();
-                        }
-                        while (c>=0 && (System.currentTimeMillis()-timestamp)<max_idle);
-                        if (!_socket.isClosed())
-                            _socket.close();
-                    }
-                }
-                catch(IOException e)
-                {
-                    LOG.ignore(e);
-                }
-            }
-        }
-    }
-}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/AbstractHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/AbstractHandler.java
index 4457203..d5fca99 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/AbstractHandler.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/AbstractHandler.java
@@ -23,17 +23,17 @@ import java.io.IOException;
 
 import org.eclipse.jetty.server.Handler;
 import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.util.component.AggregateLifeCycle;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
 
 
 /* ------------------------------------------------------------ */
 /** AbstractHandler.
- * 
- *
  */
-public abstract class AbstractHandler extends AggregateLifeCycle implements Handler
+ at ManagedObject("Jetty Handler")
+public abstract class AbstractHandler extends ContainerLifeCycle implements Handler
 {
     private static final Logger LOG = Log.getLogger(AbstractHandler.class);
 
@@ -54,7 +54,10 @@ public abstract class AbstractHandler extends AggregateLifeCycle implements Hand
     @Override
     protected void doStart() throws Exception
     {
-        LOG.debug("starting {}",this);
+        if (LOG.isDebugEnabled())
+            LOG.debug("starting {}",this);
+        if (_server==null)
+            LOG.warn("No Server set for {}",this);
         super.doStart();
     }
 
@@ -65,38 +68,40 @@ public abstract class AbstractHandler extends AggregateLifeCycle implements Hand
     @Override
     protected void doStop() throws Exception
     {
-        LOG.debug("stopping {}",this);
+        if (LOG.isDebugEnabled())
+            LOG.debug("stopping {}",this);
         super.doStop();
     }
 
     /* ------------------------------------------------------------ */
+    @Override
     public void setServer(Server server)
     {
-        Server old_server=_server;
-        if (old_server!=null && old_server!=server)
-            old_server.getContainer().removeBean(this);
+        if (_server==server)
+            return;
+        if (isStarted())
+            throw new IllegalStateException(STARTED);
         _server=server;
-        if (_server!=null && _server!=old_server)
-            _server.getContainer().addBean(this);
     }
 
     /* ------------------------------------------------------------ */
+    @Override
     public Server getServer()
     {
         return _server;
     }
 
     /* ------------------------------------------------------------ */
+    @Override
     public void destroy()
     {
         if (!isStopped())
             throw new IllegalStateException("!STOPPED");
         super.destroy();
-        if (_server!=null)
-            _server.getContainer().removeBean(this);
     }
 
     /* ------------------------------------------------------------ */
+    @Override
     public void dumpThis(Appendable out) throws IOException
     {
         out.append(toString()).append(" - ").append(getState()).append('\n');
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/AbstractHandlerContainer.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/AbstractHandlerContainer.java
index 43b086c..4524672 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/AbstractHandlerContainer.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/AbstractHandlerContainer.java
@@ -19,12 +19,12 @@
 package org.eclipse.jetty.server.handler;
 
 
-import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
 
 import org.eclipse.jetty.server.Handler;
 import org.eclipse.jetty.server.HandlerContainer;
-import org.eclipse.jetty.util.LazyList;
-import org.eclipse.jetty.util.TypeUtil;
 
 
 /* ------------------------------------------------------------ */
@@ -38,69 +38,73 @@ public abstract class AbstractHandlerContainer extends AbstractHandler implement
     public AbstractHandlerContainer()
     {
     }
-    
+
     /* ------------------------------------------------------------ */
+    @Override
     public Handler[] getChildHandlers()
     {
-        Object list = expandChildren(null,null);
-        return (Handler[])LazyList.toArray(list, Handler.class);
+        List<Handler> list=new ArrayList<>();
+        expandChildren(list,null);
+        return list.toArray(new Handler[list.size()]);
     }
-        
+
     /* ------------------------------------------------------------ */
+    @Override
     public Handler[] getChildHandlersByClass(Class<?> byclass)
     {
-        Object list = expandChildren(null,byclass);
-        return (Handler[])LazyList.toArray(list, byclass);
+        List<Handler> list=new ArrayList<>();
+        expandChildren(list,byclass);
+        return list.toArray(new Handler[list.size()]);
     }
-    
+
     /* ------------------------------------------------------------ */
+    @SuppressWarnings("unchecked")
+    @Override
     public <T extends Handler> T getChildHandlerByClass(Class<T> byclass)
     {
-        // TODO this can be more efficient?
-        Object list = expandChildren(null,byclass);
-        if (list==null)
+        List<Handler> list=new ArrayList<>();
+        expandChildren(list,byclass);
+        if (list.isEmpty())
             return null;
-        return (T)LazyList.get(list, 0);
+        return (T)list.get(0);
     }
-    
+
     /* ------------------------------------------------------------ */
-    protected Object expandChildren(Object list, Class<?> byClass)
+    protected void expandChildren(List<Handler> list, Class<?> byClass)
     {
-        return list;
     }
 
     /* ------------------------------------------------------------ */
-    protected Object expandHandler(Handler handler, Object list, Class<Handler> byClass)
+    protected void expandHandler(Handler handler, List<Handler> list, Class<?> byClass)
     {
         if (handler==null)
-            return list;
-        
+            return;
+
         if (byClass==null || byClass.isAssignableFrom(handler.getClass()))
-            list=LazyList.add(list, handler);
+            list.add(handler);
 
         if (handler instanceof AbstractHandlerContainer)
-            list=((AbstractHandlerContainer)handler).expandChildren(list, byClass);
+            ((AbstractHandlerContainer)handler).expandChildren(list, byClass);
         else if (handler instanceof HandlerContainer)
         {
             HandlerContainer container = (HandlerContainer)handler;
             Handler[] handlers=byClass==null?container.getChildHandlers():container.getChildHandlersByClass(byClass);
-            list=LazyList.addArray(list, handlers);
+            list.addAll(Arrays.asList(handlers));
         }
-        
-        return list;
     }
-    
+
     /* ------------------------------------------------------------ */
     public static <T extends HandlerContainer> T findContainerOf(HandlerContainer root,Class<T>type, Handler handler)
     {
         if (root==null || handler==null)
             return null;
-        
+
         Handler[] branches=root.getChildHandlersByClass(type);
         if (branches!=null)
         {
             for (Handler h:branches)
             {
+                @SuppressWarnings("unchecked")
                 T container = (T)h;
                 Handler[] candidates = container.getChildHandlersByClass(handler.getClass());
                 if (candidates!=null)
@@ -113,11 +117,4 @@ public abstract class AbstractHandlerContainer extends AbstractHandler implement
         }
         return null;
     }
-    
-    /* ------------------------------------------------------------ */
-    public void dump(Appendable out,String indent) throws IOException
-    {
-        dumpThis(out);
-        dump(out,indent,getBeans(),TypeUtil.asList(getHandlers()));
-    }
 }
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/AllowSymLinkAliasChecker.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/AllowSymLinkAliasChecker.java
new file mode 100644
index 0000000..0136ee4
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/AllowSymLinkAliasChecker.java
@@ -0,0 +1,97 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.handler;
+
+import java.io.File;
+import java.net.URI;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import org.eclipse.jetty.server.handler.ContextHandler.AliasCheck;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+
+
+/* ------------------------------------------------------------ */
+/** Symbolic Link AliasChecker.
+ * <p>An instance of this class can be registered with {@link ContextHandler#addAliasCheck(AliasCheck)}
+ * to check resources that are aliased to other locations.   The checker uses the 
+ * Java {@link Files#readSymbolicLink(Path)} and {@link Path#toRealPath(java.nio.file.LinkOption...)}
+ * APIs to check if a file is aliased with symbolic links.</p>
+ */
+public class AllowSymLinkAliasChecker implements AliasCheck
+{
+    private static final Logger LOG = Log.getLogger(AllowSymLinkAliasChecker.class);
+    
+    @Override
+    public boolean check(String path, Resource resource)
+    {
+        try
+        {
+            File file =resource.getFile();
+            if (file==null)
+                return false;
+            
+            // If the file exists
+            if (file.exists())
+            {
+                // we can use the real path method to check the symlinks resolve to the alias
+                URI real = file.toPath().toRealPath().toUri();
+                if (real.equals(resource.getAlias()))
+                {
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("Allow symlink {} --> {}",resource,real);
+                    return true;
+                }
+            }
+            else
+            {
+                // file does not exists, so we have to walk the path and links ourselves.
+                Path p = file.toPath().toAbsolutePath();
+                File d = p.getRoot().toFile();
+                for (Path e:p)
+                {
+                    d=new File(d,e.toString());
+                    
+                    while (d.exists() && Files.isSymbolicLink(d.toPath()))
+                    {
+                        Path link=Files.readSymbolicLink(d.toPath());
+                        if (!link.isAbsolute())
+                            link=link.resolve(d.toPath());
+                        d=link.toFile().getAbsoluteFile().getCanonicalFile();
+                    }
+                }
+                if (resource.getAlias().equals(d.toURI()))
+                {
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("Allow symlink {} --> {}",resource,d);
+                    return true;
+                }
+            }
+        }
+        catch(Exception e)
+        {
+            e.printStackTrace();
+            LOG.ignore(e);
+        }
+        return false;
+    }
+
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/AsyncDelayHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/AsyncDelayHandler.java
new file mode 100644
index 0000000..eccfc18
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/AsyncDelayHandler.java
@@ -0,0 +1,153 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.handler;
+
+import java.io.IOException;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.DispatcherType;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.Request;
+
+
+/* ------------------------------------------------------------ */
+/** A handler wrapper that provides the framework to asynchronously
+ * delay the handling of a request.  While it uses standard servlet 
+ * API for asynchronous servlets, it adjusts the dispatch type of the 
+ * request so that it does not appear to be asynchronous during the
+ * delayed dispatch.
+ * 
+ */
+public class AsyncDelayHandler extends HandlerWrapper
+{
+    public final static String AHW_ATTR = "o.e.j.s.h.AsyncHandlerWrapper";
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+    {
+        if (!isStarted() || _handler==null)
+            return;
+        
+        // Get the dispatcher types
+        DispatcherType ctype = baseRequest.getDispatcherType();
+        DispatcherType dtype = (DispatcherType)baseRequest.getAttribute(AHW_ATTR);
+        Object async_context_path=null;
+        Object async_path_info=null;
+        Object async_query_string=null;
+        Object async_request_uri=null;
+        Object async_servlet_path=null;
+        
+        // Is this request a restarted one?
+        boolean restart=false;
+        if (dtype!=null)
+        {
+            // fake the dispatch type to the original
+            baseRequest.setAttribute(AHW_ATTR,null);
+            baseRequest.setDispatcherType(dtype);
+            restart=true;
+            
+            async_context_path=baseRequest.getAttribute(AsyncContext.ASYNC_CONTEXT_PATH);
+            baseRequest.setAttribute(AsyncContext.ASYNC_CONTEXT_PATH,null);
+            async_path_info=baseRequest.getAttribute(AsyncContext.ASYNC_PATH_INFO);
+            baseRequest.setAttribute(AsyncContext.ASYNC_PATH_INFO,null);
+            async_query_string=baseRequest.getAttribute(AsyncContext.ASYNC_QUERY_STRING);
+            baseRequest.setAttribute(AsyncContext.ASYNC_QUERY_STRING,null);
+            async_request_uri=baseRequest.getAttribute(AsyncContext.ASYNC_REQUEST_URI);
+            baseRequest.setAttribute(AsyncContext.ASYNC_REQUEST_URI,null);
+            async_servlet_path=baseRequest.getAttribute(AsyncContext.ASYNC_SERVLET_PATH);
+            baseRequest.setAttribute(AsyncContext.ASYNC_SERVLET_PATH,null);
+        }
+        
+        // Should we handle this request now?
+        if (!startHandling(baseRequest,restart))
+        {
+            // No, so go async and remember dispatch type
+            AsyncContext context = baseRequest.startAsync();
+            baseRequest.setAttribute(AHW_ATTR,ctype);
+
+            delayHandling(baseRequest, context);
+            return;
+        }
+
+        // Handle the request
+        try
+        {
+            _handler.handle(target,baseRequest, request, response);
+        }
+        finally
+        {
+            if(restart)
+            {
+                // reset the request
+                baseRequest.setDispatcherType(ctype);
+                baseRequest.setAttribute(AsyncContext.ASYNC_CONTEXT_PATH,async_context_path);
+                baseRequest.setAttribute(AsyncContext.ASYNC_PATH_INFO,async_path_info);
+                baseRequest.setAttribute(AsyncContext.ASYNC_QUERY_STRING,async_query_string);
+                baseRequest.setAttribute(AsyncContext.ASYNC_REQUEST_URI,async_request_uri);
+                baseRequest.setAttribute(AsyncContext.ASYNC_SERVLET_PATH,async_servlet_path);
+            }
+            
+            // signal the request is leaving the handler
+            endHandling(baseRequest);
+        }
+        
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Called to indicate that a request has been presented for handling
+     * @param request The request to handle
+     * @param restart True if this request is being restarted after a delay
+     * @return True if the request should be handled now
+     */
+    protected boolean startHandling(Request request, boolean restart)
+    {
+        return true;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Called to indicate that a requests handling is being delayed/
+     * The implementation should arrange for context.dispatch() to be
+     * called when the request should be handled.  It may also set
+     * timeouts on the context. 
+     * 
+     * @param request The request to be delayed
+     * @param context The AsyncContext of the delayed request
+     */
+    protected void delayHandling(Request request,AsyncContext context)
+    {
+        context.dispatch();
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Called to indicated the handling of the request is ending.
+     * This is only the end of the current dispatch of the request and
+     * if the request is asynchronous, it may be handled again.
+     * @param request The request
+     */
+    protected void endHandling(Request request)
+    {
+        
+    }
+    
+    
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ConnectHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ConnectHandler.java
deleted file mode 100644
index 6fff57a..0000000
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ConnectHandler.java
+++ /dev/null
@@ -1,1025 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.server.handler;
-
-import java.io.IOException;
-import java.net.InetSocketAddress;
-import java.net.SocketException;
-import java.net.SocketTimeoutException;
-import java.nio.channels.ClosedChannelException;
-import java.nio.channels.SelectionKey;
-import java.nio.channels.SocketChannel;
-import java.util.Arrays;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jetty.http.HttpMethods;
-import org.eclipse.jetty.http.HttpParser;
-import org.eclipse.jetty.io.AsyncEndPoint;
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.ConnectedEndPoint;
-import org.eclipse.jetty.io.Connection;
-import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.io.nio.AsyncConnection;
-import org.eclipse.jetty.io.nio.IndirectNIOBuffer;
-import org.eclipse.jetty.io.nio.SelectChannelEndPoint;
-import org.eclipse.jetty.io.nio.SelectorManager;
-import org.eclipse.jetty.server.AbstractHttpConnection;
-import org.eclipse.jetty.server.Handler;
-import org.eclipse.jetty.server.Request;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.util.HostMap;
-import org.eclipse.jetty.util.TypeUtil;
-import org.eclipse.jetty.util.component.LifeCycle;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.util.thread.ThreadPool;
-
-/**
- * <p>Implementation of a tunneling proxy that supports HTTP CONNECT.</p>
- * <p>To work as CONNECT proxy, objects of this class must be instantiated using the no-arguments
- * constructor, since the remote server information will be present in the CONNECT URI.</p>
- */
-public class ConnectHandler extends HandlerWrapper
-{
-    private static final Logger LOG = Log.getLogger(ConnectHandler.class);
-    private final SelectorManager _selectorManager = new Manager();
-    private volatile int _connectTimeout = 5000;
-    private volatile int _writeTimeout = 30000;
-    private volatile ThreadPool _threadPool;
-    private volatile boolean _privateThreadPool;
-    private HostMap<String> _white = new HostMap<String>();
-    private HostMap<String> _black = new HostMap<String>();
-
-    public ConnectHandler()
-    {
-        this(null);
-    }
-
-    public ConnectHandler(String[] white, String[] black)
-    {
-        this(null, white, black);
-    }
-
-    public ConnectHandler(Handler handler)
-    {
-        setHandler(handler);
-    }
-
-    public ConnectHandler(Handler handler, String[] white, String[] black)
-    {
-        setHandler(handler);
-        set(white, _white);
-        set(black, _black);
-    }
-
-    /**
-     * @return the timeout, in milliseconds, to connect to the remote server
-     */
-    public int getConnectTimeout()
-    {
-        return _connectTimeout;
-    }
-
-    /**
-     * @param connectTimeout the timeout, in milliseconds, to connect to the remote server
-     */
-    public void setConnectTimeout(int connectTimeout)
-    {
-        _connectTimeout = connectTimeout;
-    }
-
-    /**
-     * @return the timeout, in milliseconds, to write data to a peer
-     */
-    public int getWriteTimeout()
-    {
-        return _writeTimeout;
-    }
-
-    /**
-     * @param writeTimeout the timeout, in milliseconds, to write data to a peer
-     */
-    public void setWriteTimeout(int writeTimeout)
-    {
-        _writeTimeout = writeTimeout;
-    }
-
-    @Override
-    public void setServer(Server server)
-    {
-        super.setServer(server);
-
-        server.getContainer().update(this, null, _selectorManager, "selectManager");
-
-        if (_privateThreadPool)
-            server.getContainer().update(this, null, _privateThreadPool, "threadpool", true);
-        else
-            _threadPool = server.getThreadPool();
-    }
-
-    /**
-     * @return the thread pool
-     */
-    public ThreadPool getThreadPool()
-    {
-        return _threadPool;
-    }
-
-    /**
-     * @param threadPool the thread pool
-     */
-    public void setThreadPool(ThreadPool threadPool)
-    {
-        if (getServer() != null)
-            getServer().getContainer().update(this, _privateThreadPool ? _threadPool : null, threadPool, "threadpool", true);
-        _privateThreadPool = threadPool != null;
-        _threadPool = threadPool;
-    }
-
-    @Override
-    protected void doStart() throws Exception
-    {
-        super.doStart();
-
-        if (_threadPool == null)
-        {
-            _threadPool = getServer().getThreadPool();
-            _privateThreadPool = false;
-        }
-        if (_threadPool instanceof LifeCycle && !((LifeCycle)_threadPool).isRunning())
-            ((LifeCycle)_threadPool).start();
-
-        _selectorManager.start();
-    }
-
-    @Override
-    protected void doStop() throws Exception
-    {
-        _selectorManager.stop();
-
-        ThreadPool threadPool = _threadPool;
-        if (_privateThreadPool && _threadPool != null && threadPool instanceof LifeCycle)
-            ((LifeCycle)threadPool).stop();
-
-        super.doStop();
-    }
-
-    @Override
-    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
-    {
-        if (HttpMethods.CONNECT.equalsIgnoreCase(request.getMethod()))
-        {
-            LOG.debug("CONNECT request for {}", request.getRequestURI());
-            try
-            {
-                handleConnect(baseRequest, request, response, request.getRequestURI());
-            }
-            catch(Exception e)
-            {
-                LOG.warn("ConnectHandler "+baseRequest.getUri()+" "+ e);
-                LOG.debug(e);
-            }
-        }
-        else
-        {
-            super.handle(target, baseRequest, request, response);
-        }
-    }
-
-    /**
-     * <p>Handles a CONNECT request.</p>
-     * <p>CONNECT requests may have authentication headers such as <code>Proxy-Authorization</code>
-     * that authenticate the client with the proxy.</p>
-     *
-     * @param baseRequest   Jetty-specific http request
-     * @param request       the http request
-     * @param response      the http response
-     * @param serverAddress the remote server address in the form {@code host:port}
-     * @throws ServletException if an application error occurs
-     * @throws IOException      if an I/O error occurs
-     */
-    protected void handleConnect(Request baseRequest, HttpServletRequest request, HttpServletResponse response, String serverAddress) throws ServletException, IOException
-    {
-        boolean proceed = handleAuthentication(request, response, serverAddress);
-        if (!proceed)
-            return;
-
-        String host = serverAddress;
-        int port = 80;
-        int colon = serverAddress.indexOf(':');
-        if (colon > 0)
-        {
-            host = serverAddress.substring(0, colon);
-            port = Integer.parseInt(serverAddress.substring(colon + 1));
-        }
-
-        if (!validateDestination(host))
-        {
-            LOG.info("ProxyHandler: Forbidden destination " + host);
-            response.setStatus(HttpServletResponse.SC_FORBIDDEN);
-            baseRequest.setHandled(true);
-            return;
-        }
-
-        SocketChannel channel;
-
-        try
-        {
-            channel = connectToServer(request,host,port);
-        }
-        catch (SocketException se)
-        {
-            LOG.info("ConnectHandler: SocketException " + se.getMessage());
-            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
-            baseRequest.setHandled(true);
-            return;
-        }
-        catch (SocketTimeoutException ste)
-        {
-            LOG.info("ConnectHandler: SocketTimeoutException" + ste.getMessage());
-            response.setStatus(HttpServletResponse.SC_GATEWAY_TIMEOUT);
-            baseRequest.setHandled(true);
-            return;
-        }
-        catch (IOException ioe)
-        {
-            LOG.info("ConnectHandler: IOException" + ioe.getMessage());
-            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
-            baseRequest.setHandled(true);
-            return;
-        }
-
-        // Transfer unread data from old connection to new connection
-        // We need to copy the data to avoid races:
-        // 1. when this unread data is written and the server replies before the clientToProxy
-        // connection is installed (it is only installed after returning from this method)
-        // 2. when the client sends data before this unread data has been written.
-        AbstractHttpConnection httpConnection = AbstractHttpConnection.getCurrentConnection();
-        Buffer headerBuffer = ((HttpParser)httpConnection.getParser()).getHeaderBuffer();
-        Buffer bodyBuffer = ((HttpParser)httpConnection.getParser()).getBodyBuffer();
-        int length = headerBuffer == null ? 0 : headerBuffer.length();
-        length += bodyBuffer == null ? 0 : bodyBuffer.length();
-        IndirectNIOBuffer buffer = null;
-        if (length > 0)
-        {
-            buffer = new IndirectNIOBuffer(length);
-            if (headerBuffer != null)
-            {
-                buffer.put(headerBuffer);
-                headerBuffer.clear();
-            }
-            if (bodyBuffer != null)
-            {
-                buffer.put(bodyBuffer);
-                bodyBuffer.clear();
-            }
-        }
-
-        ConcurrentMap<String, Object> context = new ConcurrentHashMap<String, Object>();
-        prepareContext(request, context);
-
-        ClientToProxyConnection clientToProxy = prepareConnections(context, channel, buffer);
-
-        // CONNECT expects a 200 response
-        response.setStatus(HttpServletResponse.SC_OK);
-
-        // Prevent close
-        baseRequest.getConnection().getGenerator().setPersistent(true);
-
-        // Close to force last flush it so that the client receives it
-        response.getOutputStream().close();
-
-        upgradeConnection(request, response, clientToProxy);
-    }
-
-    private ClientToProxyConnection prepareConnections(ConcurrentMap<String, Object> context, SocketChannel channel, Buffer buffer)
-    {
-        AbstractHttpConnection httpConnection = AbstractHttpConnection.getCurrentConnection();
-        ProxyToServerConnection proxyToServer = newProxyToServerConnection(context, buffer);
-        ClientToProxyConnection clientToProxy = newClientToProxyConnection(context, channel, httpConnection.getEndPoint(), httpConnection.getTimeStamp());
-        clientToProxy.setConnection(proxyToServer);
-        proxyToServer.setConnection(clientToProxy);
-        return clientToProxy;
-    }
-
-    /**
-     * <p>Handles the authentication before setting up the tunnel to the remote server.</p>
-     * <p>The default implementation returns true.</p>
-     *
-     * @param request  the HTTP request
-     * @param response the HTTP response
-     * @param address  the address of the remote server in the form {@code host:port}.
-     * @return true to allow to connect to the remote host, false otherwise
-     * @throws ServletException to report a server error to the caller
-     * @throws IOException      to report a server error to the caller
-     */
-    protected boolean handleAuthentication(HttpServletRequest request, HttpServletResponse response, String address) throws ServletException, IOException
-    {
-        return true;
-    }
-
-    protected ClientToProxyConnection newClientToProxyConnection(ConcurrentMap<String, Object> context, SocketChannel channel, EndPoint endPoint, long timeStamp)
-    {
-        return new ClientToProxyConnection(context, channel, endPoint, timeStamp);
-    }
-
-    protected ProxyToServerConnection newProxyToServerConnection(ConcurrentMap<String, Object> context, Buffer buffer)
-    {
-        return new ProxyToServerConnection(context, buffer);
-    }
-
-    // may return null
-    private SocketChannel connectToServer(HttpServletRequest request, String host, int port) throws IOException
-    {
-        SocketChannel channel = connect(request, host, port);      
-        channel.configureBlocking(false);
-        return channel;
-    }
-
-    /**
-     * <p>Establishes a connection to the remote server.</p>
-     *
-     * @param request the HTTP request that initiated the tunnel
-     * @param host    the host to connect to
-     * @param port    the port to connect to
-     * @return a {@link SocketChannel} connected to the remote server
-     * @throws IOException if the connection cannot be established
-     */
-    protected SocketChannel connect(HttpServletRequest request, String host, int port) throws IOException
-    {
-        SocketChannel channel = SocketChannel.open();
-
-        if (channel == null)
-        {
-            throw new IOException("unable to connect to " + host + ":" + port);
-        }
-
-        try
-        {
-            // Connect to remote server
-            LOG.debug("Establishing connection to {}:{}", host, port);
-            channel.socket().setTcpNoDelay(true);
-            channel.socket().connect(new InetSocketAddress(host, port), getConnectTimeout());
-            LOG.debug("Established connection to {}:{}", host, port);
-            return channel;
-        }
-        catch (IOException x)
-        {
-            LOG.debug("Failed to establish connection to " + host + ":" + port, x);
-            try
-            {
-                channel.close();
-            }
-            catch (IOException xx)
-            {
-                LOG.ignore(xx);
-            }
-            throw x;
-        }
-    }
-
-    protected void prepareContext(HttpServletRequest request, ConcurrentMap<String, Object> context)
-    {
-    }
-
-    private void upgradeConnection(HttpServletRequest request, HttpServletResponse response, Connection connection) throws IOException
-    {
-        // Set the new connection as request attribute and change the status to 101
-        // so that Jetty understands that it has to upgrade the connection
-        request.setAttribute("org.eclipse.jetty.io.Connection", connection);
-        response.setStatus(HttpServletResponse.SC_SWITCHING_PROTOCOLS);
-        LOG.debug("Upgraded connection to {}", connection);
-    }
-
-    private void register(SocketChannel channel, ProxyToServerConnection proxyToServer) throws IOException
-    {
-        _selectorManager.register(channel, proxyToServer);
-        proxyToServer.waitReady(_connectTimeout);
-    }
-
-    /**
-     * <p>Reads (with non-blocking semantic) into the given {@code buffer} from the given {@code endPoint}.</p>
-     *
-     * @param endPoint the endPoint to read from
-     * @param buffer   the buffer to read data into
-     * @param context  the context information related to the connection
-     * @return the number of bytes read (possibly 0 since the read is non-blocking)
-     *         or -1 if the channel has been closed remotely
-     * @throws IOException if the endPoint cannot be read
-     */
-    protected int read(EndPoint endPoint, Buffer buffer, ConcurrentMap<String, Object> context) throws IOException
-    {
-        return endPoint.fill(buffer);
-    }
-
-    /**
-     * <p>Writes (with blocking semantic) the given buffer of data onto the given endPoint.</p>
-     *
-     * @param endPoint the endPoint to write to
-     * @param buffer   the buffer to write
-     * @param context  the context information related to the connection
-     * @throws IOException if the buffer cannot be written
-     * @return the number of bytes written
-     */
-    protected int write(EndPoint endPoint, Buffer buffer, ConcurrentMap<String, Object> context) throws IOException
-    {
-        if (buffer == null)
-            return 0;
-
-        int length = buffer.length();
-        final StringBuilder debug = LOG.isDebugEnabled()?new StringBuilder():null;
-        int flushed = endPoint.flush(buffer);
-        if (debug!=null)
-            debug.append(flushed);
-        
-        // Loop until all written
-        while (buffer.length()>0 && !endPoint.isOutputShutdown())
-        {
-            if (!endPoint.isBlocking())
-            {
-                boolean ready = endPoint.blockWritable(getWriteTimeout());
-                if (!ready)
-                    throw new IOException("Write timeout");
-            }
-            flushed = endPoint.flush(buffer);
-            if (debug!=null)
-                debug.append("+").append(flushed);
-        }
-       
-        LOG.debug("Written {}/{} bytes {}", debug, length, endPoint);
-        buffer.compact();
-        return length;
-    }
-
-    private class Manager extends SelectorManager
-    {
-        @Override
-        protected SelectChannelEndPoint newEndPoint(SocketChannel channel, SelectSet selectSet, SelectionKey key) throws IOException
-        {
-            SelectChannelEndPoint endp = new SelectChannelEndPoint(channel, selectSet, key, channel.socket().getSoTimeout());
-            endp.setConnection(selectSet.getManager().newConnection(channel,endp, key.attachment()));
-            endp.setMaxIdleTime(_writeTimeout);
-            return endp;
-        }
-
-        @Override
-        public AsyncConnection newConnection(SocketChannel channel, AsyncEndPoint endpoint, Object attachment)
-        {
-            ProxyToServerConnection proxyToServer = (ProxyToServerConnection)attachment;
-            proxyToServer.setTimeStamp(System.currentTimeMillis());
-            proxyToServer.setEndPoint(endpoint);
-            return proxyToServer;
-        }
-
-        @Override
-        protected void endPointOpened(SelectChannelEndPoint endpoint)
-        {
-            ProxyToServerConnection proxyToServer = (ProxyToServerConnection)endpoint.getSelectionKey().attachment();
-            proxyToServer.ready();
-        }
-
-        @Override
-        public boolean dispatch(Runnable task)
-        {
-            return _threadPool.dispatch(task);
-        }
-
-        @Override
-        protected void endPointClosed(SelectChannelEndPoint endpoint)
-        {
-        }
-
-        @Override
-        protected void endPointUpgraded(ConnectedEndPoint endpoint, Connection oldConnection)
-        {
-        }
-    }
-
-    public class ProxyToServerConnection implements AsyncConnection
-    {
-        private final CountDownLatch _ready = new CountDownLatch(1);
-        private final Buffer _buffer = new IndirectNIOBuffer(4096);
-        private final ConcurrentMap<String, Object> _context;
-        private volatile Buffer _data;
-        private volatile ClientToProxyConnection _toClient;
-        private volatile long _timestamp;
-        private volatile AsyncEndPoint _endPoint;
-
-        public ProxyToServerConnection(ConcurrentMap<String, Object> context, Buffer data)
-        {
-            _context = context;
-            _data = data;
-        }
-
-        @Override
-        public String toString()
-        {
-            StringBuilder builder = new StringBuilder("ProxyToServer");
-            builder.append("(:").append(_endPoint.getLocalPort());
-            builder.append("<=>:").append(_endPoint.getRemotePort());
-            return builder.append(")").toString();
-        }
-
-        public Connection handle() throws IOException
-        {
-            LOG.debug("{}: begin reading from server", this);
-            try
-            {
-                writeData();
-
-                while (true)
-                {
-                    int read = read(_endPoint, _buffer, _context);
-
-                    if (read == -1)
-                    {
-                        LOG.debug("{}: server closed connection {}", this, _endPoint);
-
-                        if (_endPoint.isOutputShutdown() || !_endPoint.isOpen())
-                            closeClient();
-                        else
-                            _toClient.shutdownOutput();
-
-                        break;
-                    }
-
-                    if (read == 0)
-                        break;
-
-                    LOG.debug("{}: read from server {} bytes {}", this, read, _endPoint);
-                    int written = write(_toClient._endPoint, _buffer, _context);
-                    LOG.debug("{}: written to {} {} bytes", this, _toClient, written);
-                }
-                return this;
-            }
-            catch (ClosedChannelException x)
-            {
-                LOG.debug(x);
-                throw x;
-            }
-            catch (IOException x)
-            {
-                LOG.warn(this + ": unexpected exception", x);
-                close();
-                throw x;
-            }
-            catch (RuntimeException x)
-            {
-                LOG.warn(this + ": unexpected exception", x);
-                close();
-                throw x;
-            }
-            finally
-            {
-                LOG.debug("{}: end reading from server", this);
-            }
-        }
-
-        public void onInputShutdown() throws IOException
-        {
-        }
-
-        private void writeData() throws IOException
-        {
-            // This method is called from handle() and closeServer()
-            // which may happen concurrently (e.g. a client closing
-            // while reading from the server), so needs synchronization
-            synchronized (this)
-            {
-                if (_data != null)
-                {
-                    try
-                    {
-                        int written = write(_endPoint, _data, _context);
-                        LOG.debug("{}: written to server {} bytes", this, written);
-                    }
-                    finally
-                    {
-                        // Attempt once to write the data; if the write fails (for example
-                        // because the connection is already closed), clear the data and
-                        // give up to avoid to continue to write data to a closed connection
-                        _data = null;
-                    }
-                }
-            }
-        }
-
-        public void setConnection(ClientToProxyConnection connection)
-        {
-            _toClient = connection;
-        }
-
-        public long getTimeStamp()
-        {
-            return _timestamp;
-        }
-
-        public void setTimeStamp(long timestamp)
-        {
-            _timestamp = timestamp;
-        }
-
-        public void setEndPoint(AsyncEndPoint endpoint)
-        {
-            _endPoint = endpoint;
-        }
-
-        public boolean isIdle()
-        {
-            return false;
-        }
-
-        public boolean isSuspended()
-        {
-            return false;
-        }
-
-        public void onClose()
-        {
-        }
-
-        public void ready()
-        {
-            _ready.countDown();
-        }
-
-        public void waitReady(long timeout) throws IOException
-        {
-            try
-            {
-                _ready.await(timeout, TimeUnit.MILLISECONDS);
-            }
-            catch (final InterruptedException x)
-            {
-                throw new IOException()
-                {{
-                        initCause(x);
-                    }};
-            }
-        }
-
-        public void closeClient() throws IOException
-        {
-            _toClient.closeClient();
-        }
-
-        public void closeServer() throws IOException
-        {
-            _endPoint.close();
-        }
-
-        public void close()
-        {
-            try
-            {
-                closeClient();
-            }
-            catch (IOException x)
-            {
-                LOG.debug(this + ": unexpected exception closing the client", x);
-            }
-
-            try
-            {
-                closeServer();
-            }
-            catch (IOException x)
-            {
-                LOG.debug(this + ": unexpected exception closing the server", x);
-            }
-        }
-
-        public void shutdownOutput() throws IOException
-        {
-            writeData();
-            _endPoint.shutdownOutput();
-        }
-
-        public void onIdleExpired(long idleForMs)
-        {
-            try
-            {
-                LOG.debug("{} idle expired", this);
-                if (_endPoint.isOutputShutdown())
-                    close();
-                else
-                    shutdownOutput();
-            }
-            catch(Exception e)
-            {
-                LOG.debug(e);
-                close();
-            }
-        }
-    }
-
-    public class ClientToProxyConnection implements AsyncConnection
-    {
-        private final Buffer _buffer = new IndirectNIOBuffer(4096);
-        private final ConcurrentMap<String, Object> _context;
-        private final SocketChannel _channel;
-        private final EndPoint _endPoint;
-        private final long _timestamp;
-        private volatile ProxyToServerConnection _toServer;
-        private boolean _firstTime = true;
-
-        public ClientToProxyConnection(ConcurrentMap<String, Object> context, SocketChannel channel, EndPoint endPoint, long timestamp)
-        {
-            _context = context;
-            _channel = channel;
-            _endPoint = endPoint;
-            _timestamp = timestamp;
-        }
-
-        @Override
-        public String toString()
-        {
-            StringBuilder builder = new StringBuilder("ClientToProxy");
-            builder.append("(:").append(_endPoint.getLocalPort());
-            builder.append("<=>:").append(_endPoint.getRemotePort());
-            return builder.append(")").toString();
-        }
-
-        public Connection handle() throws IOException
-        {
-            LOG.debug("{}: begin reading from client", this);
-            try
-            {
-                if (_firstTime)
-                {
-                    _firstTime = false;
-                    register(_channel, _toServer);
-                    LOG.debug("{}: registered channel {} with connection {}", this, _channel, _toServer);
-                }
-
-                while (true)
-                {
-                    int read = read(_endPoint, _buffer, _context);
-
-                    if (read == -1)
-                    {
-                        LOG.debug("{}: client closed connection {}", this, _endPoint);
-
-                        if (_endPoint.isOutputShutdown() || !_endPoint.isOpen())
-                            closeServer();
-                        else
-                            _toServer.shutdownOutput();
-
-                        break;
-                    }
-
-                    if (read == 0)
-                        break;
-
-                    LOG.debug("{}: read from client {} bytes {}", this, read, _endPoint);
-                    int written = write(_toServer._endPoint, _buffer, _context);
-                    LOG.debug("{}: written to {} {} bytes", this, _toServer, written);
-                }
-                return this;
-            }
-            catch (ClosedChannelException x)
-            {
-                LOG.debug(x);
-                closeServer();
-                throw x;
-            }
-            catch (IOException x)
-            {
-                LOG.warn(this + ": unexpected exception", x);
-                close();
-                throw x;
-            }
-            catch (RuntimeException x)
-            {
-                LOG.warn(this + ": unexpected exception", x);
-                close();
-                throw x;
-            }
-            finally
-            {
-                LOG.debug("{}: end reading from client", this);
-            }
-        }
-
-        public void onInputShutdown() throws IOException
-        {
-        }
-
-        public long getTimeStamp()
-        {
-            return _timestamp;
-        }
-
-        public boolean isIdle()
-        {
-            return false;
-        }
-
-        public boolean isSuspended()
-        {
-            return false;
-        }
-
-        public void onClose()
-        {
-        }
-
-        public void setConnection(ProxyToServerConnection connection)
-        {
-            _toServer = connection;
-        }
-
-        public void closeClient() throws IOException
-        {
-            _endPoint.close();
-        }
-
-        public void closeServer() throws IOException
-        {
-            _toServer.closeServer();
-        }
-
-        public void close()
-        {
-            try
-            {
-                closeClient();
-            }
-            catch (IOException x)
-            {
-                LOG.debug(this + ": unexpected exception closing the client", x);
-            }
-
-            try
-            {
-                closeServer();
-            }
-            catch (IOException x)
-            {
-                LOG.debug(this + ": unexpected exception closing the server", x);
-            }
-        }
-
-        public void shutdownOutput() throws IOException
-        {
-            _endPoint.shutdownOutput();
-        }
-
-        public void onIdleExpired(long idleForMs)
-        {
-            try
-            {
-                LOG.debug("{} idle expired", this);
-                if (_endPoint.isOutputShutdown())
-                    close();
-                else
-                    shutdownOutput();
-            }
-            catch(Exception e)
-            {
-                LOG.debug(e);
-                close();
-            }
-        }
-    }
-
-    /**
-     * Add a whitelist entry to an existing handler configuration
-     *
-     * @param entry new whitelist entry
-     */
-    public void addWhite(String entry)
-    {
-        add(entry, _white);
-    }
-
-    /**
-     * Add a blacklist entry to an existing handler configuration
-     *
-     * @param entry new blacklist entry
-     */
-    public void addBlack(String entry)
-    {
-        add(entry, _black);
-    }
-
-    /**
-     * Re-initialize the whitelist of existing handler object
-     *
-     * @param entries array of whitelist entries
-     */
-    public void setWhite(String[] entries)
-    {
-        set(entries, _white);
-    }
-
-    /**
-     * Re-initialize the blacklist of existing handler object
-     *
-     * @param entries array of blacklist entries
-     */
-    public void setBlack(String[] entries)
-    {
-        set(entries, _black);
-    }
-
-    /**
-     * Helper method to process a list of new entries and replace
-     * the content of the specified host map
-     *
-     * @param entries new entries
-     * @param hostMap target host map
-     */
-    protected void set(String[] entries, HostMap<String> hostMap)
-    {
-        hostMap.clear();
-
-        if (entries != null && entries.length > 0)
-        {
-            for (String addrPath : entries)
-            {
-                add(addrPath, hostMap);
-            }
-        }
-    }
-
-    /**
-     * Helper method to process the new entry and add it to
-     * the specified host map.
-     *
-     * @param entry      new entry
-     * @param hostMap target host map
-     */
-    private void add(String entry, HostMap<String> hostMap)
-    {
-        if (entry != null && entry.length() > 0)
-        {
-            entry = entry.trim();
-            if (hostMap.get(entry) == null)
-            {
-                hostMap.put(entry, entry);
-            }
-        }
-    }
-
-    /**
-     * Check the request hostname against white- and blacklist.
-     *
-     * @param host hostname to check
-     * @return true if hostname is allowed to be proxied
-     */
-    public boolean validateDestination(String host)
-    {
-        if (_white.size() > 0)
-        {
-            Object whiteObj = _white.getLazyMatches(host);
-            if (whiteObj == null)
-            {
-                return false;
-            }
-        }
-
-        if (_black.size() > 0)
-        {
-            Object blackObj = _black.getLazyMatches(host);
-            if (blackObj != null)
-            {
-                return false;
-            }
-        }
-
-        return true;
-    }
-
-    @Override
-    public void dump(Appendable out, String indent) throws IOException
-    {
-        dumpThis(out);
-        if (_privateThreadPool)
-            dump(out, indent, Arrays.asList(_threadPool, _selectorManager), TypeUtil.asList(getHandlers()), getBeans());
-        else
-            dump(out, indent, Arrays.asList(_selectorManager), TypeUtil.asList(getHandlers()), getBeans());
-    }
-}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java
index 43ef8ce..375d883 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java
@@ -21,7 +21,9 @@ package org.eclipse.jetty.server.handler;
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
+import java.lang.reflect.Method;
 import java.net.MalformedURLException;
+import java.net.URI;
 import java.net.URL;
 import java.net.URLClassLoader;
 import java.security.AccessController;
@@ -38,8 +40,12 @@ import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Future;
 
 import javax.servlet.DispatcherType;
+import javax.servlet.Filter;
+import javax.servlet.FilterRegistration;
+import javax.servlet.FilterRegistration.Dynamic;
 import javax.servlet.RequestDispatcher;
 import javax.servlet.Servlet;
 import javax.servlet.ServletContext;
@@ -54,17 +60,12 @@ import javax.servlet.ServletRequestEvent;
 import javax.servlet.ServletRequestListener;
 import javax.servlet.SessionCookieConfig;
 import javax.servlet.SessionTrackingMode;
-import javax.servlet.Filter;
-import javax.servlet.FilterRegistration;
-import javax.servlet.FilterRegistration.Dynamic;
 import javax.servlet.descriptor.JspConfigDescriptor;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import org.eclipse.jetty.http.HttpException;
 import org.eclipse.jetty.http.MimeTypes;
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.server.AbstractHttpConnection;
+import org.eclipse.jetty.server.ClassLoaderDump;
 import org.eclipse.jetty.server.Dispatcher;
 import org.eclipse.jetty.server.Handler;
 import org.eclipse.jetty.server.HandlerContainer;
@@ -72,13 +73,13 @@ import org.eclipse.jetty.server.Request;
 import org.eclipse.jetty.server.Server;
 import org.eclipse.jetty.util.Attributes;
 import org.eclipse.jetty.util.AttributesMap;
-import org.eclipse.jetty.util.LazyList;
+import org.eclipse.jetty.util.FutureCallback;
 import org.eclipse.jetty.util.Loader;
 import org.eclipse.jetty.util.StringUtil;
-import org.eclipse.jetty.util.TypeUtil;
 import org.eclipse.jetty.util.URIUtil;
-import org.eclipse.jetty.util.component.AggregateLifeCycle;
-import org.eclipse.jetty.util.component.Dumpable;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.component.Graceful;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
 import org.eclipse.jetty.util.resource.Resource;
@@ -95,11 +96,27 @@ import org.eclipse.jetty.util.resource.Resource;
  * <p>
  * The maximum size of a form that can be processed by this context is controlled by the system properties org.eclipse.jetty.server.Request.maxFormKeys
  * and org.eclipse.jetty.server.Request.maxFormContentSize.  These can also be configured with {@link #setMaxFormContentSize(int)} and {@link #setMaxFormKeys(int)}
- * 
+ * <p>
+ * This servers executore is made available via a context attributed "org.eclipse.jetty.server.Executor".
+ *
  * @org.apache.xbean.XBean description="Creates a basic HTTP context"
  */
-public class ContextHandler extends ScopedHandler implements Attributes, Server.Graceful
+ at ManagedObject("URI Context")
+public class ContextHandler extends ScopedHandler implements Attributes, Graceful
 {
+    public final static int SERVLET_MAJOR_VERSION=3;
+    public final static int SERVLET_MINOR_VERSION=1;
+    public static final Class<?>[] SERVLET_LISTENER_TYPES = new Class[] {ServletContextListener.class,
+                                                                      ServletContextAttributeListener.class,
+                                                                      ServletRequestListener.class,
+                                                                      ServletRequestAttributeListener.class};
+
+    public static final int DEFAULT_LISTENER_TYPE_INDEX = 1;
+    public static final int EXTENDED_LISTENER_TYPE_INDEX = 0;
+
+
+    final private static String __unimplmented="Unimplemented - use org.eclipse.jetty.servlet.ServletContextHandler";
+
     private static final Logger LOG = Log.getLogger(ContextHandler.class);
 
     private static final ThreadLocal<Context> __context = new ThreadLocal<Context>();
@@ -122,43 +139,52 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
         return __context.get();
     }
 
-    protected Context _scontext;
+    /* ------------------------------------------------------------ */
+    public static ContextHandler getContextHandler(ServletContext context)
+    {
+        if(context instanceof ContextHandler.Context)
+            return ((ContextHandler.Context)context).getContextHandler();
+        Context c=  getCurrentContext();
+        if (c!=null)
+            return c.getContextHandler();
+        return null;
+    }
+
 
+    protected Context _scontext;
     private final AttributesMap _attributes;
-    private final AttributesMap _contextAttributes;
     private final Map<String, String> _initParams;
     private ClassLoader _classLoader;
     private String _contextPath = "/";
+
     private String _displayName;
+
     private Resource _baseResource;
     private MimeTypes _mimeTypes;
     private Map<String, String> _localeEncodingMap;
     private String[] _welcomeFiles;
     private ErrorHandler _errorHandler;
     private String[] _vhosts;
-    private Set<String> _connectors;
-    private EventListener[] _eventListeners;
+
     private Logger _logger;
     private boolean _allowNullPathInfo;
     private int _maxFormKeys = Integer.getInteger("org.eclipse.jetty.server.Request.maxFormKeys",-1).intValue();
     private int _maxFormContentSize = Integer.getInteger("org.eclipse.jetty.server.Request.maxFormContentSize",-1).intValue();
     private boolean _compactPath = false;
-    private boolean _aliasesAllowed = false;
 
-    private Object _contextListeners;
-    private Object _contextAttributeListeners;
-    private Object _requestListeners;
-    private Object _requestAttributeListeners;
-    private Object _durableListeners;
+    private final List<EventListener> _eventListeners=new CopyOnWriteArrayList<>();
+    private final List<EventListener> _programmaticListeners=new CopyOnWriteArrayList<>();
+    private final List<ServletContextListener> _contextListeners=new CopyOnWriteArrayList<>();
+    private final List<ServletContextAttributeListener> _contextAttributeListeners=new CopyOnWriteArrayList<>();
+    private final List<ServletRequestListener> _requestListeners=new CopyOnWriteArrayList<>();
+    private final List<ServletRequestAttributeListener> _requestAttributeListeners=new CopyOnWriteArrayList<>();
+    private final List<EventListener> _durableListeners = new CopyOnWriteArrayList<>();
     private Map<String, Object> _managedAttributes;
     private String[] _protectedTargets;
     private final CopyOnWriteArrayList<AliasCheck> _aliasChecks = new CopyOnWriteArrayList<ContextHandler.AliasCheck>();
 
-    private boolean _shutdown = false;
-    private boolean _available = true;
-    private volatile int _availability; // 0=STOPPED, 1=AVAILABLE, 2=SHUTDOWN, 3=UNAVAILABLE
-
-    private final static int __STOPPED = 0, __AVAILABLE = 1, __SHUTDOWN = 2, __UNAVAILABLE = 3;
+    public enum Availability { UNAVAILABLE,STARTING,AVAILABLE,SHUTDOWN,};
+    private volatile Availability _availability;
 
     /* ------------------------------------------------------------ */
     /**
@@ -169,7 +195,6 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
         super();
         _scontext = new Context();
         _attributes = new AttributesMap();
-        _contextAttributes = new AttributesMap();
         _initParams = new HashMap<String, String>();
         addAliasCheck(new ApproveNonExistentDirectoryAliases());
     }
@@ -183,7 +208,6 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
         super();
         _scontext = context;
         _attributes = new AttributesMap();
-        _contextAttributes = new AttributesMap();
         _initParams = new HashMap<String, String>();
         addAliasCheck(new ApproveNonExistentDirectoryAliases());
     }
@@ -216,9 +240,11 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
     @Override
     public void dump(Appendable out, String indent) throws IOException
     {
-        dumpThis(out);
-        dump(out,indent,Collections.singletonList(new CLDump(getClassLoader())),TypeUtil.asList(getHandlers()),getBeans(),_initParams.entrySet(),
-                _attributes.getAttributeEntrySet(),_contextAttributes.getAttributeEntrySet());
+        dumpBeans(out,indent,
+            Collections.singletonList(new ClassLoaderDump(getClassLoader())),
+            _initParams.entrySet(),
+            _attributes.getAttributeEntrySet(),
+            _scontext.getAttributeEntrySet());
     }
 
     /* ------------------------------------------------------------ */
@@ -231,6 +257,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
     /**
      * @return the allowNullPathInfo true if /context is not redirected to /context/
      */
+    @ManagedAttribute("Checks if the /context is not redirected to /context/")
     public boolean getAllowNullPathInfo()
     {
         return _allowNullPathInfo;
@@ -250,18 +277,9 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
     @Override
     public void setServer(Server server)
     {
+        super.setServer(server);
         if (_errorHandler != null)
-        {
-            Server old_server = getServer();
-            if (old_server != null && old_server != server)
-                old_server.getContainer().update(this,_errorHandler,null,"error",true);
-            super.setServer(server);
-            if (server != null && server != old_server)
-                server.getContainer().update(this,null,_errorHandler,"error",true);
             _errorHandler.setServer(server);
-        }
-        else
-            super.setServer(server);
     }
 
     /* ------------------------------------------------------------ */
@@ -272,7 +290,8 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
      *
      * @param vhosts
      *            Array of virtual hosts that this context responds to. A null host name or null/empty array means any hostname is acceptable. Host names may be
-     *            String representation of IP addresses. Host names may start with '*.' to wildcard one level of names.
+     *            String representation of IP addresses. Host names may start with '*.' to wildcard one level of names.  Hosts may start with '@', in which case they
+     *            will match the {@link Connector#getName()} for the request.
      */
     public void setVirtualHosts(String[] vhosts)
     {
@@ -293,7 +312,8 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
      *
      * @param virtualHosts
      *            Array of virtual hosts that this context responds to. A null host name or null/empty array means any hostname is acceptable. Host names may be
-     *            String representation of IP addresses. Host names may start with '*.' to wildcard one level of names.
+     *            String representation of IP addresses. Host names may start with '*.' to wildcard one level of names. Host names may start with '@', in which case they
+     *            will match the {@link Connector#getName()} for the request.
      */
     public void addVirtualHosts(String[] virtualHosts)
     {
@@ -376,44 +396,17 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
      * @return Array of virtual hosts that this context responds to. A null host name or empty array means any hostname is acceptable. Host names may be String
      *         representation of IP addresses. Host names may start with '*.' to wildcard one level of names.
      */
+    @ManagedAttribute(value="Virtual hosts accepted by the context", readonly=true)
     public String[] getVirtualHosts()
     {
         return _vhosts;
     }
 
     /* ------------------------------------------------------------ */
-    /**
-     * @return an array of connector names that this context will accept a request from.
-     */
-    public String[] getConnectorNames()
-    {
-        if (_connectors == null || _connectors.size() == 0)
-            return null;
-
-        return _connectors.toArray(new String[_connectors.size()]);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Set the names of accepted connectors.
-     *
-     * Names are either "host:port" or a specific configured name for a connector.
-     *
-     * @param connectors
-     *            If non null, an array of connector names that this context will accept a request from.
-     */
-    public void setConnectorNames(String[] connectors)
-    {
-        if (connectors == null || connectors.length == 0)
-            _connectors = null;
-        else
-            _connectors = new HashSet<String>(Arrays.asList(connectors));
-    }
-
-    /* ------------------------------------------------------------ */
     /*
      * @see javax.servlet.ServletContext#getAttribute(java.lang.String)
      */
+    @Override
     public Object getAttribute(String name)
     {
         return _attributes.getAttribute(name);
@@ -423,8 +416,8 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
     /*
      * @see javax.servlet.ServletContext#getAttributeNames()
      */
-    @SuppressWarnings("unchecked")
-    public Enumeration getAttributeNames()
+    @Override
+    public Enumeration<String> getAttributeNames()
     {
         return AttributesMap.getAttributeNamesCopy(_attributes);
     }
@@ -453,6 +446,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
      *
      * @return Returns the classLoader.
      */
+    @ManagedAttribute("The file classpath")
     public String getClassPath()
     {
         if (_classLoader == null || !(_classLoader instanceof URLClassLoader))
@@ -487,6 +481,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
     /**
      * @return Returns the _contextPath.
      */
+    @ManagedAttribute("True if URLs are compacted to replace the multiple '/'s with a single '/'")
     public String getContextPath()
     {
         return _contextPath;
@@ -513,8 +508,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
     /*
      * @see javax.servlet.ServletContext#getInitParameterNames()
      */
-    @SuppressWarnings("rawtypes")
-    public Enumeration getInitParameterNames()
+    public Enumeration<String> getInitParameterNames()
     {
         return Collections.enumeration(_initParams.keySet());
     }
@@ -523,6 +517,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
     /**
      * @return Returns the initParams.
      */
+    @ManagedAttribute("Initial Parameter map for the context")
     public Map<String, String> getInitParams()
     {
         return _initParams;
@@ -532,6 +527,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
     /*
      * @see javax.servlet.ServletContext#getServletContextName()
      */
+    @ManagedAttribute(value="Display name of the Context", readonly=true)
     public String getDisplayName()
     {
         return _displayName;
@@ -540,7 +536,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
     /* ------------------------------------------------------------ */
     public EventListener[] getEventListeners()
     {
-        return _eventListeners;
+        return _eventListeners.toArray(new EventListener[_eventListeners.size()]);
     }
 
     /* ------------------------------------------------------------ */
@@ -556,73 +552,104 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
      */
     public void setEventListeners(EventListener[] eventListeners)
     {
-        _contextListeners = null;
-        _contextAttributeListeners = null;
-        _requestListeners = null;
-        _requestAttributeListeners = null;
+        _contextListeners.clear();
+        _contextAttributeListeners.clear();
+        _requestListeners.clear();
+        _requestAttributeListeners.clear();
+        _eventListeners.clear();
+
+        if (eventListeners!=null)
+            for (EventListener listener : eventListeners)
+                addEventListener(listener);
+    }
 
-        _eventListeners = eventListeners;
+    /* ------------------------------------------------------------ */
+    /**
+     * Add a context event listeners.
+     *
+     * @see ServletContextListener
+     * @see ServletContextAttributeListener
+     * @see ServletRequestListener
+     * @see ServletRequestAttributeListener
+     */
+    public void addEventListener(EventListener listener)
+    {
+        _eventListeners.add(listener);
 
-        for (int i = 0; eventListeners != null && i < eventListeners.length; i++)
-        {
-            EventListener listener = _eventListeners[i];
+        if (!(isStarted() || isStarting()))
+            _durableListeners.add(listener);
 
-            if (listener instanceof ServletContextListener)
-                _contextListeners = LazyList.add(_contextListeners,listener);
+        if (listener instanceof ServletContextListener)
+            _contextListeners.add((ServletContextListener)listener);
 
-            if (listener instanceof ServletContextAttributeListener)
-                _contextAttributeListeners = LazyList.add(_contextAttributeListeners,listener);
+        if (listener instanceof ServletContextAttributeListener)
+            _contextAttributeListeners.add((ServletContextAttributeListener)listener);
 
-            if (listener instanceof ServletRequestListener)
-                _requestListeners = LazyList.add(_requestListeners,listener);
+        if (listener instanceof ServletRequestListener)
+            _requestListeners.add((ServletRequestListener)listener);
 
-            if (listener instanceof ServletRequestAttributeListener)
-                _requestAttributeListeners = LazyList.add(_requestAttributeListeners,listener);
-        }
+        if (listener instanceof ServletRequestAttributeListener)
+            _requestAttributeListeners.add((ServletRequestAttributeListener)listener);
     }
 
     /* ------------------------------------------------------------ */
     /**
-     * Add a context event listeners.
+     * Remove a context event listeners.
      *
      * @see ServletContextListener
      * @see ServletContextAttributeListener
      * @see ServletRequestListener
      * @see ServletRequestAttributeListener
      */
-    public void addEventListener(EventListener listener)
+    public void removeEventListener(EventListener listener)
     {
-        //Only listeners added before the context starts last through a stop/start cycle
-        if (!(isStarted() || isStarting()))
-            _durableListeners = LazyList.add(_durableListeners, listener);
-        
-        setEventListeners((EventListener[])LazyList.addToArray(getEventListeners(),listener,EventListener.class));
+        _eventListeners.remove(listener);
+
+        if (listener instanceof ServletContextListener)
+            _contextListeners.remove(listener);
+
+        if (listener instanceof ServletContextAttributeListener)
+            _contextAttributeListeners.remove(listener);
+
+        if (listener instanceof ServletRequestListener)
+            _requestListeners.remove(listener);
+
+        if (listener instanceof ServletRequestAttributeListener)
+            _requestAttributeListeners.remove(listener);
     }
-    
-   
+
+    /* ------------------------------------------------------------ */
     /**
-     * Apply any necessary restrictions on a programmatically added
-     * listener.
-     * 
-     * Superclasses should implement.
-     * 
+     * Apply any necessary restrictions on a programmatic added listener.
+     *
      * @param listener
      */
-    public void restrictEventListener (EventListener listener)
+    protected void addProgrammaticListener (EventListener listener)
     {
+        _programmaticListeners.add(listener);
+    }
+
+    /* ------------------------------------------------------------ */
+    protected boolean isProgrammaticListener(EventListener listener)
+    {
+        return _programmaticListeners.contains(listener);
     }
 
-    
+
 
     /* ------------------------------------------------------------ */
     /**
      * @return true if this context is accepting new requests
      */
+    @ManagedAttribute("true for graceful shutdown, which allows existing requests to complete")
     public boolean isShutdown()
     {
-        synchronized (this)
+        switch(_availability)
         {
-            return !_shutdown;
+            case SHUTDOWN:
+                return true;
+            default:
+                return false;
         }
     }
 
@@ -631,16 +658,12 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
      * Set shutdown status. This field allows for graceful shutdown of a context. A started context may be put into non accepting state so that existing
      * requests can complete, but no new requests are accepted.
      *
-     * @param shutdown
-     *            true if this context is (not?) accepting new requests
      */
-    public void setShutdown(boolean shutdown)
+    @Override
+    public Future<Void> shutdown()
     {
-        synchronized (this)
-        {
-            _shutdown = shutdown;
-            _availability = isRunning()?(_shutdown?__SHUTDOWN:_available?__AVAILABLE:__UNAVAILABLE):__STOPPED;
-        }
+        _availability = isRunning() ? Availability.SHUTDOWN : Availability.UNAVAILABLE;
+        return new FutureCallback(true);
     }
 
     /* ------------------------------------------------------------ */
@@ -649,10 +672,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
      */
     public boolean isAvailable()
     {
-        synchronized (this)
-        {
-            return _available;
-        }
+        return _availability==Availability.AVAILABLE;
     }
 
     /* ------------------------------------------------------------ */
@@ -663,8 +683,10 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
     {
         synchronized (this)
         {
-            _available = available;
-            _availability = isRunning()?(_shutdown?__SHUTDOWN:_available?__AVAILABLE:__UNAVAILABLE):__STOPPED;
+            if (available && isRunning())
+                _availability = Availability.AVAILABLE;
+            else if (!available || !isRunning())
+                _availability = Availability.UNAVAILABLE;
         }
     }
 
@@ -687,7 +709,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
     @Override
     protected void doStart() throws Exception
     {
-        _availability = __STOPPED;
+        _availability = Availability.STARTING;
 
         if (_contextPath == null)
             throw new IllegalStateException("Null contextPath");
@@ -697,6 +719,8 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
         Thread current_thread = null;
         Context old_context = null;
 
+        _attributes.setAttribute("org.eclipse.jetty.server.Executor",getServer().getThreadPool());
+        
         try
         {
             // Set the classloader
@@ -716,21 +740,16 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
             // defers the calling of super.doStart()
             startContext();
 
-            synchronized(this)
-            {
-                _availability = _shutdown?__SHUTDOWN:_available?__AVAILABLE:__UNAVAILABLE;
-            }
+            _availability = Availability.AVAILABLE;
+            LOG.info("Started {}", this);
         }
         finally
         {
             __context.set(old_context);
 
             // reset the classloader
-            if (_classLoader != null)
-            {
+            if (_classLoader != null && current_thread!=null)
                 current_thread.setContextClassLoader(old_classloader);
-            }
-
         }
     }
 
@@ -749,12 +768,14 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
             _managedAttributes = new HashMap<String, Object>();
             String[] attributes = managedAttributes.split(",");
             for (String attribute : attributes)
-                _managedAttributes.put(attribute,null);
+            {
+                _managedAttributes.put(attribute.trim(),null);
+            }
 
-            Enumeration e = _scontext.getAttributeNames();
+            Enumeration<String> e = _scontext.getAttributeNames();
             while (e.hasMoreElements())
             {
-                String name = (String)e.nextElement();
+                String name = e.nextElement();
                 Object value = _scontext.getAttribute(name);
                 checkManagedAttribute(name,value);
             }
@@ -762,32 +783,31 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
 
         super.doStart();
 
-        if (_errorHandler != null)
-            _errorHandler.start();
-
-        // Context listeners
-        if (_contextListeners != null)
+        // Call context listeners
+        if (!_contextListeners.isEmpty())
         {
             ServletContextEvent event = new ServletContextEvent(_scontext);
-            for (int i = 0; i < LazyList.size(_contextListeners); i++)
-            {
-                callContextInitialized(((ServletContextListener)LazyList.get(_contextListeners, i)), event);
-            }
+            for (ServletContextListener listener:_contextListeners)
+                callContextInitialized(listener, event);
         }
     }
 
     /* ------------------------------------------------------------ */
-    public void callContextInitialized (ServletContextListener l, ServletContextEvent e)
+    protected void callContextInitialized (ServletContextListener l, ServletContextEvent e)
     {
+        if (LOG.isDebugEnabled())
+            LOG.debug("contextInitialized: {}->{}",e,l);
         l.contextInitialized(e);
     }
 
     /* ------------------------------------------------------------ */
-    public void callContextDestroyed (ServletContextListener l, ServletContextEvent e)
+    protected void callContextDestroyed (ServletContextListener l, ServletContextEvent e)
     {
+        if (LOG.isDebugEnabled())
+            LOG.debug("contextDestroyed: {}->{}",e,l);
         l.contextDestroyed(e);
     }
-    
+
     /* ------------------------------------------------------------ */
     /*
      * @see org.eclipse.thread.AbstractLifeCycle#doStop()
@@ -795,7 +815,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
     @Override
     protected void doStop() throws Exception
     {
-        _availability = __STOPPED;
+        _availability = Availability.UNAVAILABLE;
 
         ClassLoader old_classloader = null;
         Thread current_thread = null;
@@ -815,116 +835,135 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
             super.doStop();
 
             // Context listeners
-            if (_contextListeners != null)
+            if (!_contextListeners.isEmpty())
             {
                 ServletContextEvent event = new ServletContextEvent(_scontext);
-                for (int i = LazyList.size(_contextListeners); i-- > 0;)
-                {
-                    ((ServletContextListener)LazyList.get(_contextListeners,i)).contextDestroyed(event);
-                }
+                for (int i = _contextListeners.size(); i-->0;)
+                    callContextDestroyed(_contextListeners.get(i),event);
             }
-            
-            //remove all non-durable listeners
-            setEventListeners((EventListener[])LazyList.toArray(_durableListeners, EventListener.class));
-            _durableListeners = null;
+
+            //retain only durable listeners
+            setEventListeners(_durableListeners.toArray(new EventListener[_durableListeners.size()]));
+            _durableListeners.clear();
 
             if (_errorHandler != null)
                 _errorHandler.stop();
 
-            Enumeration e = _scontext.getAttributeNames();
+            Enumeration<String> e = _scontext.getAttributeNames();
             while (e.hasMoreElements())
             {
-                String name = (String)e.nextElement();
+                String name = e.nextElement();
                 checkManagedAttribute(name,null);
             }
+
+            for (EventListener l : _programmaticListeners)
+                removeEventListener(l);
+            _programmaticListeners.clear();
         }
         finally
         {
-            LOG.info("stopped {}",this);
+            LOG.info("Stopped {}", this);
             __context.set(old_context);
             // reset the classloader
-            if (_classLoader != null)
+            if (_classLoader != null && current_thread!=null)
                 current_thread.setContextClassLoader(old_classloader);
         }
 
-        _contextAttributes.clearAttributes();
+        _scontext.clearAttributes();
     }
 
-    /* ------------------------------------------------------------ */
-    /*
-     * @see org.eclipse.jetty.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
-     */
-    public boolean checkContext(final String target, final Request baseRequest, final HttpServletResponse response) throws IOException, ServletException
+    public boolean checkVirtualHost(final Request baseRequest)
     {
-        DispatcherType dispatch = baseRequest.getDispatcherType();
-
-        switch (_availability)
-        {
-            case __STOPPED:
-            case __SHUTDOWN:
-                return false;
-            case __UNAVAILABLE:
-                baseRequest.setHandled(true);
-                response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
-                return false;
-            default:
-                if ((DispatcherType.REQUEST.equals(dispatch) && baseRequest.isHandled()))
-                    return false;
-        }
-
-        // Check the vhosts
         if (_vhosts != null && _vhosts.length > 0)
         {
             String vhost = normalizeHostname(baseRequest.getServerName());
 
             boolean match = false;
+            boolean connectorName = false;
+            boolean connectorMatch = false;
 
-            // TODO non-linear lookup
-            for (int i = 0; !match && i < _vhosts.length; i++)
+            for (String contextVhost:_vhosts)
             {
-                String contextVhost = _vhosts[i];
-                if (contextVhost == null)
+                if (contextVhost == null || contextVhost.length()==0)
                     continue;
-                if (contextVhost.startsWith("*."))
+                char c=contextVhost.charAt(0);
+                switch (c)
                 {
-                    // wildcard only at the beginning, and only for one additional subdomain level
-                    match = contextVhost.regionMatches(true,2,vhost,vhost.indexOf(".") + 1,contextVhost.length() - 2);
+                    case '*':
+                        if (contextVhost.startsWith("*."))
+                            // wildcard only at the beginning, and only for one additional subdomain level
+                            match = match || contextVhost.regionMatches(true,2,vhost,vhost.indexOf(".") + 1,contextVhost.length() - 2);
+                        break;
+                    case '@':
+                        connectorName=true;
+                        String name=baseRequest.getHttpChannel().getConnector().getName();
+                        boolean m=name!=null && contextVhost.length()==name.length()+1 && contextVhost.endsWith(name);
+                        match = match || m;
+                        connectorMatch = connectorMatch || m;
+                        break;
+                    default:
+                        match = match || contextVhost.equalsIgnoreCase(vhost);
                 }
-                else
-                    match = contextVhost.equalsIgnoreCase(vhost);
-            }
-            if (!match)
-                return false;
-        }
 
-        // Check the connector
-        if (_connectors != null && _connectors.size() > 0)
-        {
-            String connector = AbstractHttpConnection.getCurrentConnection().getConnector().getName();
-            if (connector == null || !_connectors.contains(connector))
+            }
+            if (!match || connectorName && !connectorMatch)
                 return false;
         }
-
+        return true;
+    }
+    
+    public boolean checkContextPath(String uri)
+    {
         // Are we not the root context?
         if (_contextPath.length() > 1)
         {
             // reject requests that are not for us
-            if (!target.startsWith(_contextPath))
+            if (!uri.startsWith(_contextPath))
                 return false;
-            if (target.length() > _contextPath.length() && target.charAt(_contextPath.length()) != '/')
+            if (uri.length() > _contextPath.length() && uri.charAt(_contextPath.length()) != '/')
                 return false;
+        }
+        return true;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.jetty.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
+     */
+    public boolean checkContext(final String target, final Request baseRequest, final HttpServletResponse response) throws IOException
+    {
+        DispatcherType dispatch = baseRequest.getDispatcherType();
 
-            // redirect null path infos
-            if (!_allowNullPathInfo && _contextPath.length() == target.length())
-            {
-                // context request must end with /
+        // Check the vhosts
+        if (!checkVirtualHost(baseRequest))
+            return false;
+
+        if (!checkContextPath(target))
+            return false;
+        
+        // Are we not the root context?
+        // redirect null path infos
+        if (!_allowNullPathInfo && _contextPath.length() == target.length() && _contextPath.length()>1)
+        {
+            // context request must end with /
+            baseRequest.setHandled(true);
+            if (baseRequest.getQueryString() != null)
+                response.sendRedirect(URIUtil.addPaths(baseRequest.getRequestURI(),URIUtil.SLASH) + "?" + baseRequest.getQueryString());
+            else
+                response.sendRedirect(URIUtil.addPaths(baseRequest.getRequestURI(),URIUtil.SLASH));
+            return false;
+        }
+
+        switch (_availability)
+        {
+            case SHUTDOWN:
+            case UNAVAILABLE:
                 baseRequest.setHandled(true);
-                if (baseRequest.getQueryString() != null)
-                    response.sendRedirect(URIUtil.addPaths(baseRequest.getRequestURI(),URIUtil.SLASH) + "?" + baseRequest.getQueryString());
-                else
-                    response.sendRedirect(URIUtil.addPaths(baseRequest.getRequestURI(),URIUtil.SLASH));
+                response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
                 return false;
-            }
+            default:
+                if ((DispatcherType.REQUEST.equals(dispatch) && baseRequest.isHandled()))
+                    return false;
         }
 
         return true;
@@ -957,7 +996,9 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
         if (old_context != _scontext)
         {
             // check the target.
-            if (DispatcherType.REQUEST.equals(dispatch) || DispatcherType.ASYNC.equals(dispatch) || (DispatcherType.ERROR.equals(dispatch) && baseRequest.getAsyncContinuation().isExpired()))
+            if (DispatcherType.REQUEST.equals(dispatch) ||
+                DispatcherType.ASYNC.equals(dispatch) ||
+                DispatcherType.ERROR.equals(dispatch) && baseRequest.getHttpChannelState().isAsync())
             {
                 if (_compactPath)
                     target = URIUtil.compactPath(target);
@@ -1029,7 +1070,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
             if (old_context != _scontext)
             {
                 // reset the classloader
-                if (_classLoader != null)
+                if (_classLoader != null && current_thread!=null)
                 {
                     current_thread.setContextClassLoader(old_classloader);
                 }
@@ -1059,24 +1100,24 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
             if (new_context)
             {
                 // Handle the REALLY SILLY request events!
-                if (_requestAttributeListeners != null)
-                {
-                    final int s = LazyList.size(_requestAttributeListeners);
-                    for (int i = 0; i < s; i++)
-                        baseRequest.addEventListener(((EventListener)LazyList.get(_requestAttributeListeners,i)));
-                }
+                if (!_requestAttributeListeners.isEmpty())
+                    for (ServletRequestAttributeListener l :_requestAttributeListeners)
+                        baseRequest.addEventListener(l);
 
-                if (_requestListeners != null)
+                if (!_requestListeners.isEmpty())
                 {
-                    final int s = LazyList.size(_requestListeners);
                     final ServletRequestEvent sre = new ServletRequestEvent(_scontext,request);
-                    for (int i = 0; i < s; i++)
-                        ((ServletRequestListener)LazyList.get(_requestListeners,i)).requestInitialized(sre);
+                    for (ServletRequestListener l : _requestListeners)
+                        l.requestInitialized(sre);
                 }
             }
 
             if (DispatcherType.REQUEST.equals(dispatch) && isProtectedTarget(target))
-                throw new HttpException(HttpServletResponse.SC_NOT_FOUND);
+            {
+                response.sendError(HttpServletResponse.SC_NOT_FOUND);
+                baseRequest.setHandled(true);
+                return;
+            }
 
             // start manual inline of nextHandle(target,baseRequest,request,response);
             // noinspection ConstantIfStatement
@@ -1088,28 +1129,22 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
                 _handler.handle(target,baseRequest,request,response);
             // end manual inline
         }
-        catch (HttpException e)
-        {
-            LOG.debug(e);
-            baseRequest.setHandled(true);
-            response.sendError(e.getStatus(),e.getReason());
-        }
         finally
         {
             // Handle more REALLY SILLY request events!
             if (new_context)
             {
-                if (_requestListeners != null)
+                if (!_requestListeners.isEmpty())
                 {
                     final ServletRequestEvent sre = new ServletRequestEvent(_scontext,request);
-                    for (int i = LazyList.size(_requestListeners); i-- > 0;)
-                        ((ServletRequestListener)LazyList.get(_requestListeners,i)).requestDestroyed(sre);
+                    for (int i=_requestListeners.size();i-->0;)
+                        _requestListeners.get(i).requestDestroyed(sre);
                 }
 
-                if (_requestAttributeListeners != null)
+                if (!_requestAttributeListeners.isEmpty())
                 {
-                    for (int i = LazyList.size(_requestAttributeListeners); i-- > 0;)
-                        baseRequest.removeEventListener(((EventListener)LazyList.get(_requestAttributeListeners,i)));
+                    for (int i=_requestAttributeListeners.size();i-->0;)
+                        baseRequest.removeEventListener(_requestAttributeListeners.get(i));
                 }
             }
         }
@@ -1142,7 +1177,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
         finally
         {
             __context.set(old_context);
-            if (old_classloader != null)
+            if (old_classloader != null && current_thread!=null)
             {
                 current_thread.setContextClassLoader(old_classloader);
             }
@@ -1152,27 +1187,41 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
     /* ------------------------------------------------------------ */
     /**
      * Check the target. Called by {@link #handle(String, Request, HttpServletRequest, HttpServletResponse)} when a target within a context is determined. If
-     * the target is protected, 404 is returned. 
+     * the target is protected, 404 is returned.
      */
     /* ------------------------------------------------------------ */
     public boolean isProtectedTarget(String target)
     {
         if (target == null || _protectedTargets == null)
             return false;
-        
+
         while (target.startsWith("//"))
             target=URIUtil.compactPath(target);
-        
-        boolean isProtected = false;
-        int i=0;
-        while (!isProtected && i<_protectedTargets.length)
+
+        for (int i=0; i<_protectedTargets.length; i++)
         {
-            isProtected = StringUtil.startsWithIgnoreCase(target, _protectedTargets[i++]);
+            String t=_protectedTargets[i];
+            if (StringUtil.startsWithIgnoreCase(target,t))
+            {
+                if (target.length()==t.length())
+                    return true;
+                
+                // Check that the target prefix really is a path segment, thus
+                // it can end with /, a query, a target or a parameter
+                char c=target.charAt(t.length());
+                if (c=='/'||c=='?'||c=='#'||c==';')
+                    return true;
+            }
         }
-        return isProtected;
+        return false;
     }
-    
-    
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param targets Array of URL prefix. Each prefix is in the form /path and will match
+     * either /path exactly or /path/anything
+     */
     public void setProtectedTargets (String[] targets)
     {
         if (targets == null)
@@ -1181,25 +1230,24 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
             return;
         }
         
-        _protectedTargets = new String[targets.length];
-        System.arraycopy(targets, 0, _protectedTargets, 0, targets.length);
+        _protectedTargets = Arrays.copyOf(targets, targets.length);
     }
-    
-    public String[] getProtectedTargets ()
+
+    /* ------------------------------------------------------------ */
+    public String[] getProtectedTargets()
     {
         if (_protectedTargets == null)
             return null;
-        
-        String[] tmp = new String[_protectedTargets.length];
-        System.arraycopy(_protectedTargets, 0, tmp, 0, _protectedTargets.length);
-        return tmp;
+
+        return Arrays.copyOf(_protectedTargets, _protectedTargets.length);
     }
-    
+
 
     /* ------------------------------------------------------------ */
     /*
      * @see javax.servlet.ServletContext#removeAttribute(java.lang.String)
      */
+    @Override
     public void removeAttribute(String name)
     {
         checkManagedAttribute(name,null);
@@ -1213,7 +1261,8 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
      *
      * @see javax.servlet.ServletContext#setAttribute(java.lang.String, java.lang.Object)
      */
-    public void setAttribute(String name, Object value)
+    @Override
+    public void setAttribute( String name, Object value)
     {
         checkManagedAttribute(name,value);
         _attributes.setAttribute(name,value);
@@ -1228,21 +1277,22 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
     {
         _attributes.clearAttributes();
         _attributes.addAll(attributes);
-        Enumeration e = _attributes.getAttributeNames();
+        Enumeration<String> e = _attributes.getAttributeNames();
         while (e.hasMoreElements())
         {
-            String name = (String)e.nextElement();
+            String name = e.nextElement();
             checkManagedAttribute(name,attributes.getAttribute(name));
         }
     }
 
     /* ------------------------------------------------------------ */
+    @Override
     public void clearAttributes()
     {
-        Enumeration e = _attributes.getAttributeNames();
+        Enumeration<String> e = _attributes.getAttributeNames();
         while (e.hasMoreElements())
         {
-            String name = (String)e.nextElement();
+            String name = e.nextElement();
             checkManagedAttribute(name,null);
         }
         _attributes.clearAttributes();
@@ -1261,7 +1311,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
     public void setManagedAttribute(String name, Object value)
     {
         Object old = _managedAttributes.put(name,value);
-        getServer().getContainer().update(this,old,value,name,true);
+        updateBean(old,value);
     }
 
     /* ------------------------------------------------------------ */
@@ -1281,8 +1331,26 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
      */
     public void setContextPath(String contextPath)
     {
-        if (contextPath != null && contextPath.length() > 1 && contextPath.endsWith("/"))
-            throw new IllegalArgumentException("ends with /");
+        if (contextPath == null)
+            throw new IllegalArgumentException("null contextPath");
+
+        if (contextPath.endsWith("/*"))
+        {
+            LOG.warn(this+" contextPath ends with /*");
+            contextPath=contextPath.substring(0,contextPath.length()-2);
+        }
+        else if (contextPath.length()>1 && contextPath.endsWith("/"))
+        {
+            LOG.warn(this+" contextPath ends with /");
+            contextPath=contextPath.substring(0,contextPath.length()-1);
+        }
+
+        if (contextPath.length()==0)
+        {
+            LOG.warn("Empty contextPath");
+            contextPath="/";
+        }
+
         _contextPath = contextPath;
 
         if (getServer() != null && (getServer().isStarting() || getServer().isStarted()))
@@ -1318,6 +1386,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
     /**
      * @return Returns the base resource as a string.
      */
+    @ManagedAttribute("document root for context")
     public String getResourceBase()
     {
         if (_baseResource == null)
@@ -1327,8 +1396,9 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
 
     /* ------------------------------------------------------------ */
     /**
-     * @param base
-     *            The resourceBase to set.
+     * Set the base resource for this context.
+     * @param base The resource used as the base for all static content of this context.
+     * @see #setResourceBase(String)
      */
     public void setBaseResource(Resource base)
     {
@@ -1336,9 +1406,11 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
     }
 
     /* ------------------------------------------------------------ */
-    /**
-     * @param resourceBase
-     *            The base resource as a string.
+    /** 
+     * Set the base resource for this context.
+     * @param resourceBase A string representing the base resource for the context. Any string accepted 
+     * by {@link Resource#newResource(String)} may be passed and the call is equivalent to 
+     * <code>setBaseResource(newResource(resourceBase));</code>
      */
     public void setResourceBase(String resourceBase)
     {
@@ -1356,25 +1428,6 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
 
     /* ------------------------------------------------------------ */
     /**
-     * @return True if aliases are allowed
-     */
-    public boolean isAliases()
-    {
-        return _aliasesAllowed;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param aliases
-     *            aliases are allowed
-     */
-    public void setAliases(boolean aliases)
-    {
-        _aliasesAllowed = aliases;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
      * @return Returns the mimeTypes.
      */
     public MimeTypes getMimeTypes()
@@ -1408,6 +1461,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
      * @see <a href="http://jcp.org/aboutJava/communityprocess/final/jsr154/index.html">The Servlet Specification</a>
      * @see #setWelcomeFiles
      */
+    @ManagedAttribute(value="Partial URIs of directory welcome files", readonly=true)
     public String[] getWelcomeFiles()
     {
         return _welcomeFiles;
@@ -1417,6 +1471,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
     /**
      * @return Returns the errorHandler.
      */
+    @ManagedAttribute("The error handler to use for the context")
     public ErrorHandler getErrorHandler()
     {
         return _errorHandler;
@@ -1431,12 +1486,12 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
     {
         if (errorHandler != null)
             errorHandler.setServer(getServer());
-        if (getServer() != null)
-            getServer().getContainer().update(this,_errorHandler,errorHandler,"errorHandler",true);
+        updateBean(_errorHandler,errorHandler);
         _errorHandler = errorHandler;
     }
 
     /* ------------------------------------------------------------ */
+    @ManagedAttribute("The maximum content size")
     public int getMaxFormContentSize()
     {
         return _maxFormContentSize;
@@ -1506,8 +1561,8 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
                     b.append(s.charAt(0)).append('.');
             }
         }
-        b.append(getClass().getSimpleName());
-        b.append('{').append(getContextPath()).append(',').append(getBaseResource());
+        b.append(getClass().getSimpleName()).append('@').append(Integer.toString(hashCode(),16));
+        b.append('{').append(getContextPath()).append(',').append(getBaseResource()).append(',').append(_availability);
 
         if (vhosts != null && vhosts.length > 0)
             b.append(',').append(vhosts[0]);
@@ -1563,6 +1618,19 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
             encoding = _localeEncodingMap.get(locale.getLanguage());
         return encoding;
     }
+    
+    /* ------------------------------------------------------------ */
+    /** 
+     * Get all of the locale encodings
+     * 
+     * @return a map of all the locale encodings: key is name of the locale and value is the char encoding
+     */
+    public Map<String,String> getLocaleEncodings()
+    {
+        if (_localeEncodingMap == null)
+            return null;
+        return Collections.unmodifiableMap(_localeEncodingMap);
+    }
 
     /* ------------------------------------------------------------ */
     /*
@@ -1593,10 +1661,15 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
     }
 
     /* ------------------------------------------------------------ */
+    /**
+     * @param path
+     * @param resource
+     * @return True if the alias is OK
+     */
     public boolean checkAlias(String path, Resource resource)
     {
         // Is the resource aliased?
-        if (!_aliasesAllowed && resource.getAlias() != null)
+        if (resource.getAlias() != null)
         {
             if (LOG.isDebugEnabled())
                 LOG.debug("Aliased resource: " + resource + "~=" + resource.getAlias());
@@ -1625,6 +1698,15 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
     {
         return Resource.newResource(url);
     }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Convert URL to Resource wrapper for {@link Resource#newResource(URL)} enables extensions to provide alternate resource implementations.
+     */
+    public Resource newResource(URI uri) throws IOException
+    {
+        return Resource.newResource(uri);
+    }
 
     /* ------------------------------------------------------------ */
     /**
@@ -1684,7 +1766,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
 
         return host;
     }
-    
+
     /* ------------------------------------------------------------ */
     /**
      * Add an AliasCheck instance to possibly permit aliased resources
@@ -1694,7 +1776,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
     {
         _aliasChecks.add(check);
     }
-    
+
     /* ------------------------------------------------------------ */
     /**
      * @return Mutable list of Alias checks
@@ -1703,7 +1785,17 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
     {
         return _aliasChecks;
     }
-
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param checks list of AliasCheck instances
+     */
+    public void setAliasChecks(List<AliasCheck> checks)
+    {
+        _aliasChecks.clear();
+        _aliasChecks.addAll(checks);
+    }
+
     /* ------------------------------------------------------------ */
     /**
      * Context.
@@ -1713,11 +1805,11 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
      *
      *
      */
-    public class Context implements ServletContext
+    public class Context extends NoContext
     {
-        protected int _majorVersion = 3;
-        protected int _minorVersion = 0;
         protected boolean _enabled = true; //whether or not the dynamic API is enabled for callers
+        protected boolean _extendedListenerTypes = false;
+
 
         /* ------------------------------------------------------------ */
         protected Context()
@@ -1727,7 +1819,6 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
         /* ------------------------------------------------------------ */
         public ContextHandler getContextHandler()
         {
-            // TODO reduce visibility of this method
             return ContextHandler.this;
         }
 
@@ -1807,7 +1898,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
                         matched_path = context_path;
                     }
 
-                    if (matched_path.equals(context_path))
+                    if (matched_path != null && matched_path.equals(context_path))
                         contexts.add(ch);
                 }
             }
@@ -1819,17 +1910,6 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
 
         /* ------------------------------------------------------------ */
         /*
-         * @see javax.servlet.ServletContext#getMajorVersion()
-         */
-        @Override
-        public int getMajorVersion()
-        {
-            return 3;
-        }
-      
-
-        /* ------------------------------------------------------------ */
-        /*
          * @see javax.servlet.ServletContext#getMimeType(java.lang.String)
          */
         @Override
@@ -1837,30 +1917,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
         {
             if (_mimeTypes == null)
                 return null;
-            Buffer mime = _mimeTypes.getMimeByExtension(file);
-            if (mime != null)
-                return mime.toString();
-            return null;
-        }
-
-        /* ------------------------------------------------------------ */
-        /*
-         * @see javax.servlet.ServletContext#getMinorVersion()
-         */
-        @Override
-        public int getMinorVersion()
-        {
-            return 0;
-        }
-
-        /* ------------------------------------------------------------ */
-        /*
-         * @see javax.servlet.ServletContext#getNamedDispatcher(java.lang.String)
-         */
-        @Override
-        public RequestDispatcher getNamedDispatcher(String name)
-        {
-            return null;
+            return _mimeTypes.getMimeByExtension(file);
         }
 
         /* ------------------------------------------------------------ */
@@ -1970,58 +2027,13 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
          * @see javax.servlet.ServletContext#getResourcePaths(java.lang.String)
          */
         @Override
-        public Set getResourcePaths(String path)
+        public Set<String> getResourcePaths(String path)
         {
             return ContextHandler.this.getResourcePaths(path);
         }
 
         /* ------------------------------------------------------------ */
         /*
-         * @see javax.servlet.ServletContext#getServerInfo()
-         */
-        @Override
-        public String getServerInfo()
-        {
-            return "jetty/" + Server.getVersion();
-        }
-
-        /* ------------------------------------------------------------ */
-        /*
-         * @see javax.servlet.ServletContext#getServlet(java.lang.String)
-         */
-        @Override
-        @Deprecated
-        public Servlet getServlet(String name) throws ServletException
-        {
-            return null;
-        }
-
-        /* ------------------------------------------------------------ */
-        /*
-         * @see javax.servlet.ServletContext#getServletNames()
-         */
-        @SuppressWarnings("unchecked")
-        @Override
-        @Deprecated
-        public Enumeration getServletNames()
-        {
-            return Collections.enumeration(Collections.EMPTY_LIST);
-        }
-
-        /* ------------------------------------------------------------ */
-        /*
-         * @see javax.servlet.ServletContext#getServlets()
-         */
-        @SuppressWarnings("unchecked")
-        @Override
-        @Deprecated
-        public Enumeration getServlets()
-        {
-            return Collections.enumeration(Collections.EMPTY_LIST);
-        }
-
-        /* ------------------------------------------------------------ */
-        /*
          * @see javax.servlet.ServletContext#log(java.lang.Exception, java.lang.String)
          */
         @Override
@@ -2064,9 +2076,8 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
         /*
          * @see javax.servlet.ServletContext#getInitParameterNames()
          */
-        @SuppressWarnings("unchecked")
         @Override
-        public Enumeration getInitParameterNames()
+        public Enumeration<String> getInitParameterNames()
         {
             return ContextHandler.this.getInitParameterNames();
         }
@@ -2079,8 +2090,8 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
         public synchronized Object getAttribute(String name)
         {
             Object o = ContextHandler.this.getAttribute(name);
-            if (o == null && _contextAttributes != null)
-                o = _contextAttributes.getAttribute(name);
+            if (o == null)
+                o = super.getAttribute(name);
             return o;
         }
 
@@ -2088,18 +2099,14 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
         /*
          * @see javax.servlet.ServletContext#getAttributeNames()
          */
-        @SuppressWarnings("unchecked")
         @Override
-        public synchronized Enumeration getAttributeNames()
+        public synchronized Enumeration<String> getAttributeNames()
         {
             HashSet<String> set = new HashSet<String>();
-            if (_contextAttributes != null)
-            {
-                Enumeration<String> e = _contextAttributes.getAttributeNames();
-                while (e.hasMoreElements())
-                    set.add(e.nextElement());
-            }
-            Enumeration<String> e = _attributes.getAttributeNames();
+            Enumeration<String> e = super.getAttributeNames();
+            while (e.hasMoreElements())
+                set.add(e.nextElement());
+            e = _attributes.getAttributeNames();
             while (e.hasMoreElements())
                 set.add(e.nextElement());
 
@@ -2114,21 +2121,19 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
         public synchronized void setAttribute(String name, Object value)
         {
             checkManagedAttribute(name,value);
-            Object old_value = _contextAttributes.getAttribute(name);
+            Object old_value = super.getAttribute(name);
 
             if (value == null)
-                _contextAttributes.removeAttribute(name);
+                super.removeAttribute(name);
             else
-                _contextAttributes.setAttribute(name,value);
+                super.setAttribute(name,value);
 
-            if (_contextAttributeListeners != null)
+            if (!_contextAttributeListeners.isEmpty())
             {
                 ServletContextAttributeEvent event = new ServletContextAttributeEvent(_scontext,name,old_value == null?value:old_value);
 
-                for (int i = 0; i < LazyList.size(_contextAttributeListeners); i++)
+                for (ServletContextAttributeListener l : _contextAttributeListeners)
                 {
-                    ServletContextAttributeListener l = (ServletContextAttributeListener)LazyList.get(_contextAttributeListeners,i);
-
                     if (old_value == null)
                         l.attributeAdded(event);
                     else if (value == null)
@@ -2148,24 +2153,14 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
         {
             checkManagedAttribute(name,null);
 
-            if (_contextAttributes == null)
+            Object old_value = super.getAttribute(name);
+            super.removeAttribute(name);
+            if (old_value != null &&!_contextAttributeListeners.isEmpty())
             {
-                // Set it on the handler
-                _attributes.removeAttribute(name);
-                return;
-            }
-
-            Object old_value = _contextAttributes.getAttribute(name);
-            _contextAttributes.removeAttribute(name);
-            if (old_value != null)
-            {
-                if (_contextAttributeListeners != null)
-                {
-                    ServletContextAttributeEvent event = new ServletContextAttributeEvent(_scontext,name,old_value);
+                ServletContextAttributeEvent event = new ServletContextAttributeEvent(_scontext,name,old_value);
 
-                    for (int i = 0; i < LazyList.size(_contextAttributeListeners); i++)
-                        ((ServletContextAttributeListener)LazyList.get(_contextAttributeListeners,i)).attributeRemoved(event);
-                }
+                for (ServletContextAttributeListener l : _contextAttributeListeners)
+                    l.attributeRemoved(event);
             }
         }
 
@@ -2209,8 +2204,331 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
             return true;
         }
 
+        @Override
+        public void addListener(String className)
+        {
+            if (!_enabled)
+                throw new UnsupportedOperationException();
+
+            try
+            {
+                @SuppressWarnings("unchecked")
+                Class<? extends EventListener> clazz = _classLoader==null?Loader.loadClass(ContextHandler.class,className):_classLoader.loadClass(className);
+                addListener(clazz);
+            }
+            catch (ClassNotFoundException e)
+            {
+                throw new IllegalArgumentException(e);
+            }
+        }
+
+        @Override
+        public <T extends EventListener> void addListener(T t)
+        {
+            if (!_enabled)
+                throw new UnsupportedOperationException();
+
+            checkListener(t.getClass());
+
+            ContextHandler.this.addEventListener(t);
+            ContextHandler.this.addProgrammaticListener(t);
+        }
+
+        @Override
+        public void addListener(Class<? extends EventListener> listenerClass)
+        {
+            if (!_enabled)
+                throw new UnsupportedOperationException();
+
+            try
+            {
+                EventListener e = createListener(listenerClass);
+                addListener(e);
+            }
+            catch (ServletException e)
+            {
+                throw new IllegalArgumentException(e);
+            }
+        }
+
+        @Override
+        public <T extends EventListener> T createListener(Class<T> clazz) throws ServletException
+        {
+            try
+            {
+                return createInstance(clazz);
+            }
+            catch (Exception e)
+            {
+                throw new ServletException(e);
+            }
+        }
+
+
+        public void checkListener (Class<? extends EventListener> listener) throws IllegalStateException
+        {
+            boolean ok = false;
+            int startIndex = (isExtendedListenerTypes()?EXTENDED_LISTENER_TYPE_INDEX:DEFAULT_LISTENER_TYPE_INDEX);
+            for (int i=startIndex;i<SERVLET_LISTENER_TYPES.length;i++)
+            {
+                if (SERVLET_LISTENER_TYPES[i].isAssignableFrom(listener))
+                {
+                    ok = true;
+                    break;
+                }
+            }
+            if (!ok)
+                throw new IllegalArgumentException("Inappropriate listener class "+listener.getName());
+        }
+
+        public void setExtendedListenerTypes (boolean extended)
+        {
+           _extendedListenerTypes = extended;
+        }
+
+       public boolean isExtendedListenerTypes()
+       {
+           return _extendedListenerTypes;
+       }
+
+
+       @Override
+       public ClassLoader getClassLoader()
+       {
+           if (!_enabled)
+               throw new UnsupportedOperationException();
+           
+           //no security manager just return the classloader
+           if (System.getSecurityManager() == null)
+               return _classLoader;
+           else
+           {
+               //check to see if the classloader of the caller is the same as the context
+               //classloader, or a parent of it
+               try
+               {
+                   Class<?> reflect = Loader.loadClass(getClass(), "sun.reflect.Reflection");
+                   Method getCallerClass = reflect.getMethod("getCallerClass", Integer.TYPE);
+                   Class<?> caller = (Class<?>)getCallerClass.invoke(null, 2);
+
+                   boolean ok = false;
+                   ClassLoader callerLoader = caller.getClassLoader();
+                   while (!ok && callerLoader != null)
+                   {
+                       if (callerLoader == _classLoader) 
+                           ok = true;
+                       else
+                           callerLoader = callerLoader.getParent();    
+                   }
+
+                   if (ok)
+                       return _classLoader;
+               }
+               catch (Exception e)      
+               {
+                   LOG.warn("Unable to check classloader of caller",e);
+               }
+              
+               AccessController.checkPermission(new RuntimePermission("getClassLoader"));
+               return _classLoader;
+           }
+        }
+
+        @Override
+        public JspConfigDescriptor getJspConfigDescriptor()
+        {
+            LOG.warn(__unimplmented);
+            return null;
+        }
+
+        public void setJspConfigDescriptor(JspConfigDescriptor d)
+        {
+
+        }
+
+        @Override
+        public void declareRoles(String... roleNames)
+        {
+            if (!isStarting())
+                throw new IllegalStateException ();
+            if (!_enabled)
+                throw new UnsupportedOperationException();
+        }
+
+        public void setEnabled(boolean enabled)
+        {
+            _enabled = enabled;
+        }
+
+        public boolean isEnabled()
+        {
+            return _enabled;
+        }
+
+
+
+        public <T> T createInstance (Class<T> clazz) throws Exception
+        {
+            T o = clazz.newInstance();
+            return o;
+        }
+    }
+
+
+    public static class NoContext extends AttributesMap implements ServletContext
+    {
+        private int _effectiveMajorVersion = SERVLET_MAJOR_VERSION;
+        private int _effectiveMinorVersion = SERVLET_MINOR_VERSION;
+
         /* ------------------------------------------------------------ */
-        final private static String __unimplmented="Unimplemented - use org.eclipse.jetty.servlet.ServletContextHandler";
+        public NoContext()
+        {
+        }
+
+        @Override
+        public ServletContext getContext(String uripath)
+        {
+            return null;
+        }
+
+        @Override
+        public int getMajorVersion()
+        {
+            return SERVLET_MAJOR_VERSION;
+        }
+
+        @Override
+        public String getMimeType(String file)
+        {
+            return null;
+        }
+
+        @Override
+        public int getMinorVersion()
+        {
+            return SERVLET_MINOR_VERSION;
+        }
+
+        @Override
+        public RequestDispatcher getNamedDispatcher(String name)
+        {
+            return null;
+        }
+
+        @Override
+        public RequestDispatcher getRequestDispatcher(String uriInContext)
+        {
+            return null;
+        }
+
+        @Override
+        public String getRealPath(String path)
+        {
+            return null;
+        }
+
+        @Override
+        public URL getResource(String path) throws MalformedURLException
+        {
+            return null;
+        }
+
+        @Override
+        public InputStream getResourceAsStream(String path)
+        {
+            return null;
+        }
+
+        @Override
+        public Set<String> getResourcePaths(String path)
+        {
+            return null;
+        }
+
+        @Override
+        public String getServerInfo()
+        {
+            // NOTE: DO NOT CHANGE
+            //   this is used by weld to detect Jetty
+            //   implementation version
+            // See: https://github.com/weld/core/blob/master/environments/servlet/core/src/main/java/org/jboss/weld/environment/jetty/JettyContainer.java
+            //   and its touch(ContainerContext) method
+            return "jetty/" + Server.getVersion();
+        }
+
+        @Override
+        @Deprecated
+        public Servlet getServlet(String name) throws ServletException
+        {
+            return null;
+        }
+
+        @SuppressWarnings("unchecked")
+        @Override
+        @Deprecated
+        public Enumeration<String> getServletNames()
+        {
+            return Collections.enumeration(Collections.EMPTY_LIST);
+        }
+
+        @SuppressWarnings("unchecked")
+        @Override
+        @Deprecated
+        public Enumeration<Servlet> getServlets()
+        {
+            return Collections.enumeration(Collections.EMPTY_LIST);
+        }
+
+        @Override
+        public void log(Exception exception, String msg)
+        {
+            LOG.warn(msg,exception);
+        }
+
+        @Override
+        public void log(String msg)
+        {
+            LOG.info(msg);
+        }
+
+        @Override
+        public void log(String message, Throwable throwable)
+        {
+            LOG.warn(message,throwable);
+        }
+
+        @Override
+        public String getInitParameter(String name)
+        {
+            return null;
+        }
+
+        @SuppressWarnings("unchecked")
+        @Override
+        public Enumeration<String> getInitParameterNames()
+        {
+            return Collections.enumeration(Collections.EMPTY_LIST);
+        }
+
+
+        @Override
+        public String getServletContextName()
+        {
+            return "No Context";
+        }
+
+        @Override
+        public String getContextPath()
+        {
+            return null;
+        }
+
+
+        @Override
+        public boolean setInitParameter(String name, String value)
+        {
+            return false;
+        }
 
         @Override
         public Dynamic addFilter(String filterName, Class<? extends Filter> filterClass)
@@ -2326,45 +2644,19 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
         @Override
         public void addListener(String className)
         {
-            if (!_enabled)
-                throw new UnsupportedOperationException();
-            
-            try
-            {
-                Class<? extends EventListener> clazz = _classLoader==null?Loader.loadClass(ContextHandler.class,className):_classLoader.loadClass(className);
-                addListener(clazz);
-            }
-            catch (ClassNotFoundException e)
-            {
-                throw new IllegalArgumentException(e);
-            }
+            LOG.warn(__unimplmented);
         }
 
         @Override
         public <T extends EventListener> void addListener(T t)
-        {            
-            if (!_enabled)
-                throw new UnsupportedOperationException();
-            ContextHandler.this.addEventListener(t);
-            ContextHandler.this.restrictEventListener(t);
+        {
+            LOG.warn(__unimplmented);
         }
 
         @Override
         public void addListener(Class<? extends EventListener> listenerClass)
-        {            
-            if (!_enabled)
-                throw new UnsupportedOperationException();
-
-            try
-            {
-                EventListener e = createListener(listenerClass);
-                ContextHandler.this.addEventListener(e);
-                ContextHandler.this.restrictEventListener(e);
-            }
-            catch (ServletException e)
-            {
-                throw new IllegalArgumentException(e);
-            }
+        {
+            LOG.warn(__unimplmented);
         }
 
         @Override
@@ -2388,31 +2680,31 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
         public ClassLoader getClassLoader()
         {
             AccessController.checkPermission(new RuntimePermission("getClassLoader"));
-            return _classLoader;
+            return ContextHandler.class.getClassLoader();
         }
 
         @Override
         public int getEffectiveMajorVersion()
         {
-            return _majorVersion;
+            return _effectiveMajorVersion;
         }
 
         @Override
         public int getEffectiveMinorVersion()
         {
-            return _minorVersion;
+            return _effectiveMinorVersion;
         }
 
         public void setEffectiveMajorVersion (int v)
         {
-            _majorVersion = v;
+            _effectiveMajorVersion = v;
         }
-        
+
         public void setEffectiveMinorVersion (int v)
         {
-            _minorVersion = v;
+            _effectiveMinorVersion = v;
         }
-        
+
         @Override
         public JspConfigDescriptor getJspConfigDescriptor()
         {
@@ -2420,70 +2712,24 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
             return null;
         }
 
-        public void setJspConfigDescriptor(JspConfigDescriptor d)
-        {
-            
-        }
-        
         @Override
         public void declareRoles(String... roleNames)
         {
-            if (!isStarting())
-                throw new IllegalStateException ();
-            if (!_enabled)
-                throw new UnsupportedOperationException();
-            
-            // TODO Auto-generated method stub
-            
-        }
-
-        public void setEnabled(boolean enabled)
-        {
-            _enabled = enabled;
+            LOG.warn(__unimplmented);
         }
 
-        public boolean isEnabled()
+        /**
+         * @see javax.servlet.ServletContext#getVirtualServerName()
+         */
+        @Override
+        public String getVirtualServerName()
         {
-            return _enabled;
+            // TODO 3.1 Auto-generated method stub
+            return null;
         }
     }
 
-    private static class CLDump implements Dumpable
-    {
-        final ClassLoader _loader;
-
-        CLDump(ClassLoader loader)
-        {
-            _loader = loader;
-        }
-
-        public String dump()
-        {
-            return AggregateLifeCycle.dump(this);
-        }
-
-        public void dump(Appendable out, String indent) throws IOException
-        {
-            out.append(String.valueOf(_loader)).append("\n");
-
-            if (_loader != null)
-            {
-                Object parent = _loader.getParent();
-                if (parent != null)
-                {
-                    if (!(parent instanceof Dumpable))
-                        parent = new CLDump((ClassLoader)parent);
 
-                    if (_loader instanceof URLClassLoader)
-                        AggregateLifeCycle.dump(out,indent,TypeUtil.asList(((URLClassLoader)_loader).getURLs()),Collections.singleton(parent));
-                    else
-                        AggregateLifeCycle.dump(out,indent,Collections.singleton(parent));
-                }
-            }
-        }
-    }
-    
-    
     /* ------------------------------------------------------------ */
     /** Interface to check aliases
      */
@@ -2497,8 +2743,20 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
          */
         boolean check(String path, Resource resource);
     }
-    
-    
+
+
+    /* ------------------------------------------------------------ */
+    /** Approve all aliases.
+     */
+    public static class ApproveAliases implements AliasCheck
+    {
+        @Override
+        public boolean check(String path, Resource resource)
+        {
+            return true;
+        }
+    }
+
     /* ------------------------------------------------------------ */
     /** Approve Aliases with same suffix.
      * Eg. a symbolic link from /foobar.html to /somewhere/wibble.html would be
@@ -2511,6 +2769,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
             LOG.warn("ApproveSameSuffixAlias is not safe for production");
         }
         
+        @Override
         public boolean check(String path, Resource resource)
         {
             int dot = path.lastIndexOf('.');
@@ -2520,8 +2779,8 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
             return resource.toString().endsWith(suffix);
         }
     }
-    
-    
+
+
     /* ------------------------------------------------------------ */
     /** Approve Aliases with a path prefix.
      * Eg. a symbolic link from /dirA/foobar.html to /dirB/foobar.html would be
@@ -2534,6 +2793,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
             LOG.warn("ApprovePathPrefixAliases is not safe for production");
         }
         
+        @Override
         public boolean check(String path, Resource resource)
         {
             int slash = path.lastIndexOf('/');
@@ -2546,11 +2806,12 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
     
     /* ------------------------------------------------------------ */
     /** Approve Aliases of a non existent directory.
-     * If a directory "/foobar/" does not exist, then the resource is 
+     * If a directory "/foobar/" does not exist, then the resource is
      * aliased to "/foobar".  Accept such aliases.
      */
     public static class ApproveNonExistentDirectoryAliases implements AliasCheck
     {
+        @Override
         public boolean check(String path, Resource resource)
         {
             if (resource.exists())
@@ -2561,9 +2822,11 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
             
             if (a.length()>r.length())
                 return a.startsWith(r) && a.length()==r.length()+1 && a.endsWith("/");
-            else
+            if (a.length()<r.length())
                 return r.startsWith(a) && r.length()==a.length()+1 && r.endsWith("/");
+            
+            return a.equals(r); 
         }
     }
-    
+
 }
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandlerCollection.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandlerCollection.java
index 5ad4131..78c8948 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandlerCollection.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandlerCollection.java
@@ -19,41 +19,52 @@
 package org.eclipse.jetty.server.handler;
 
 import java.io.IOException;
+import java.lang.reflect.Array;
+import java.util.Arrays;
 import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
 
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import org.eclipse.jetty.http.PathMap;
-import org.eclipse.jetty.server.AsyncContinuation;
 import org.eclipse.jetty.server.Handler;
 import org.eclipse.jetty.server.HandlerContainer;
+import org.eclipse.jetty.server.HttpChannelState;
 import org.eclipse.jetty.server.Request;
-import org.eclipse.jetty.util.LazyList;
+import org.eclipse.jetty.util.ArrayTernaryTrie;
+import org.eclipse.jetty.util.ArrayUtil;
+import org.eclipse.jetty.util.Trie;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.annotation.ManagedOperation;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
 
 /* ------------------------------------------------------------ */
 /** ContextHandlerCollection.
- * 
- * This {@link org.eclipse.jetty.server.handler.HandlerCollection} is creates a 
+ *
+ * This {@link org.eclipse.jetty.server.handler.HandlerCollection} is creates a
  * {@link org.eclipse.jetty.http.PathMap} to it's contained handlers based
  * on the context path and virtual hosts of any contained {@link org.eclipse.jetty.server.handler.ContextHandler}s.
  * The contexts do not need to be directly contained, only children of the contained handlers.
  * Multiple contexts may have the same context path and they are called in order until one
- * handles the request.  
- * 
- * @org.apache.xbean.XBean element="contexts"
+ * handles the request.
+ *
  */
+ at ManagedObject("Context Handler Collection")
 public class ContextHandlerCollection extends HandlerCollection
 {
     private static final Logger LOG = Log.getLogger(ContextHandlerCollection.class);
- 
-    private volatile PathMap _contextMap;
+
+    private final ConcurrentMap<ContextHandler,Handler> _contextBranches = new ConcurrentHashMap<>();
+    private volatile Trie<Map.Entry<String,Branch[]>> _pathBranches;
     private Class<? extends ContextHandler> _contextClass = ContextHandler.class;
-    
+
     /* ------------------------------------------------------------ */
     public ContextHandlerCollection()
     {
@@ -65,100 +76,80 @@ public class ContextHandlerCollection extends HandlerCollection
     /**
      * Remap the context paths.
      */
+    @ManagedOperation("update the mapping of context path to context")
     public void mapContexts()
     {
-        PathMap contextMap = new PathMap();
-        Handler[] branches = getHandlers();
+        _contextBranches.clear();
         
+        if (getHandlers()==null)
+        {
+            _pathBranches=new ArrayTernaryTrie<>(false,16);
+            return;
+        }
         
-        for (int b=0;branches!=null && b<branches.length;b++)
+        // Create map of contextPath to handler Branch
+        Map<String,Branch[]> map = new HashMap<>();
+        for (Handler handler:getHandlers())
         {
-            Handler[] handlers=null;
-            
-            if (branches[b] instanceof ContextHandler)
-            {
-                handlers = new Handler[]{ branches[b] };
-            }
-            else if (branches[b] instanceof HandlerContainer)
+            Branch branch=new Branch(handler);
+            for (String contextPath : branch.getContextPaths())
             {
-                handlers = ((HandlerContainer)branches[b]).getChildHandlersByClass(ContextHandler.class);
+                Branch[] branches=map.get(contextPath);
+                map.put(contextPath, ArrayUtil.addToArray(branches, branch, Branch.class));
             }
-            else 
-                continue;
             
-            for (int i=0;i<handlers.length;i++)
+            for (ContextHandler context : branch.getContextHandlers())
+                _contextBranches.putIfAbsent(context, branch.getHandler());
+        }
+        
+        // Sort the branches so those with virtual hosts are considered before those without
+        for (Map.Entry<String,Branch[]> entry: map.entrySet())
+        {
+            Branch[] branches=entry.getValue();
+            Branch[] sorted=new Branch[branches.length];
+            int i=0;
+            for (Branch branch:branches)
+                if (branch.hasVirtualHost())
+                    sorted[i++]=branch;
+            for (Branch branch:branches)
+                if (!branch.hasVirtualHost())
+                    sorted[i++]=branch;
+            entry.setValue(sorted);
+        }
+        
+        // Loop until we have a big enough trie to hold all the context paths
+        int capacity=512;
+        Trie<Map.Entry<String,Branch[]>> trie;
+        loop: while(true)
+        {
+            trie=new ArrayTernaryTrie<>(false,capacity);
+            for (Map.Entry<String,Branch[]> entry: map.entrySet())
             {
-                ContextHandler handler=(ContextHandler)handlers[i];
-
-                String contextPath=handler.getContextPath();
-
-                if (contextPath==null || contextPath.indexOf(',')>=0 || contextPath.startsWith("*"))
-                    throw new IllegalArgumentException ("Illegal context spec:"+contextPath);
-
-                if(!contextPath.startsWith("/"))
-                    contextPath='/'+contextPath;
-
-                if (contextPath.length()>1)
+                if (!trie.put(entry.getKey().substring(1),entry))
                 {
-                    if (contextPath.endsWith("/"))
-                        contextPath+="*";
-                    else if (!contextPath.endsWith("/*"))
-                        contextPath+="/*";
-                }
-
-                Object contexts=contextMap.get(contextPath);
-                String[] vhosts=handler.getVirtualHosts();
-
-                
-                if (vhosts!=null && vhosts.length>0)
-                {
-                    Map hosts;
-
-                    if (contexts instanceof Map)
-                        hosts=(Map)contexts;
-                    else
-                    {
-                        hosts=new HashMap(); 
-                        hosts.put("*",contexts);
-                        contextMap.put(contextPath, hosts);
-                    }
-
-                    for (int j=0;j<vhosts.length;j++)
-                    {
-                        String vhost=vhosts[j];
-                        contexts=hosts.get(vhost);
-                        contexts=LazyList.add(contexts,branches[b]);
-                        hosts.put(vhost,contexts);
-                    }
-                }
-                else if (contexts instanceof Map)
-                {
-                    Map hosts=(Map)contexts;
-                    contexts=hosts.get("*");
-                    contexts= LazyList.add(contexts, branches[b]);
-                    hosts.put("*",contexts);
-                }
-                else
-                {
-                    contexts= LazyList.add(contexts, branches[b]);
-                    contextMap.put(contextPath, contexts);
+                    capacity+=512;
+                    continue loop;
                 }
             }
+            break loop;
         }
-        _contextMap=contextMap;
-
+            
+        
+        if (LOG.isDebugEnabled())
+        {
+            for (String ctx : trie.keySet())
+                LOG.debug("{}->{}",ctx,Arrays.asList(trie.get(ctx).getValue()));
+        }
+        _pathBranches=trie;
     }
-    
 
-    
     /* ------------------------------------------------------------ */
-    /* 
+    /*
      * @see org.eclipse.jetty.server.server.handler.HandlerCollection#setHandlers(org.eclipse.jetty.server.server.Handler[])
      */
     @Override
     public void setHandlers(Handler[] handlers)
     {
-        _contextMap=null;
         super.setHandlers(handlers);
         if (isStarted())
             mapContexts();
@@ -171,10 +162,10 @@ public class ContextHandlerCollection extends HandlerCollection
         mapContexts();
         super.doStart();
     }
-    
+
 
     /* ------------------------------------------------------------ */
-    /* 
+    /*
      * @see org.eclipse.jetty.server.server.Handler#handle(java.lang.String, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
      */
     @Override
@@ -182,102 +173,72 @@ public class ContextHandlerCollection extends HandlerCollection
     {
         Handler[] handlers = getHandlers();
         if (handlers==null || handlers.length==0)
-	    return;
-	
-	AsyncContinuation async = baseRequest.getAsyncContinuation();
-	if (async.isAsync())
-	{
-	    ContextHandler context=async.getContextHandler();
-	    if (context!=null)
-	    {
-	        context.handle(target,baseRequest,request, response);
-	        return;
-	    }
-	}
-	
-	// data structure which maps a request to a context; first-best match wins
-	// { context path => 
-	//     { virtual host => context } 
-	// }
-	PathMap map = _contextMap;
-	if (map!=null && target!=null && target.startsWith("/"))
-	{
-	    // first, get all contexts matched by context path
-	    Object contexts = map.getLazyMatches(target);
+            return;
 
-            for (int i=0; i<LazyList.size(contexts); i++)
+        HttpChannelState async = baseRequest.getHttpChannelState();
+        if (async.isAsync())
+        {
+            ContextHandler context=async.getContextHandler();
+            if (context!=null)
             {
-                // then, match against the virtualhost of each context
-                Map.Entry entry = (Map.Entry)LazyList.get(contexts, i);
-                Object list = entry.getValue();
-
-                if (list instanceof Map)
-                {
-                    Map hosts = (Map)list;
-                    String host = normalizeHostname(request.getServerName());
-           
-                    // explicitly-defined virtual hosts, most specific
-                    list=hosts.get(host);
-                    for (int j=0; j<LazyList.size(list); j++)
-                    {
-                        Handler handler = (Handler)LazyList.get(list,j);
-                        handler.handle(target,baseRequest, request, response);
-                        if (baseRequest.isHandled())
-                            return;
-                    }
-                    
-                    // wildcard for one level of names 
-                    list=hosts.get("*."+host.substring(host.indexOf(".")+1));
-                    for (int j=0; j<LazyList.size(list); j++)
-                    {
-                        Handler handler = (Handler)LazyList.get(list,j);
-                        handler.handle(target,baseRequest, request, response);
-                        if (baseRequest.isHandled())
-                            return;
-                    }
-                    
-                    // no virtualhosts defined for the context, least specific
-                    // will handle any request that does not match to a specific virtual host above
-                    list=hosts.get("*");
-                    for (int j=0; j<LazyList.size(list); j++)
-                    {
-                        Handler handler = (Handler)LazyList.get(list,j);
-                        handler.handle(target,baseRequest, request, response);
-                        if (baseRequest.isHandled())
-                            return;
-                    }
-                }
+                Handler branch = _contextBranches.get(context);
+                
+                if (branch==null)
+                    context.handle(target,baseRequest,request, response);
                 else
+                    branch.handle(target, baseRequest, request, response);
+                return;
+            }
+        }
+        
+        // data structure which maps a request to a context; first-best match wins
+        // { context path => [ context ] }
+        // }
+        if (target.startsWith("/"))
+        {
+            int limit = target.length()-1;
+
+            while (limit>=0)
+            {
+                // Get best match
+                Map.Entry<String,Branch[]> branches = _pathBranches.getBest(target,1,limit);
+                
+                
+                if (branches==null)
+                    break;
+                
+                int l=branches.getKey().length();
+                if (l==1 || target.length()==l || target.charAt(l)=='/')
                 {
-                    for (int j=0; j<LazyList.size(list); j++)
+                    for (Branch branch : branches.getValue())
                     {
-                        Handler handler = (Handler)LazyList.get(list,j);
-                        handler.handle(target,baseRequest, request, response);
+                        branch.getHandler().handle(target,baseRequest, request, response);
                         if (baseRequest.isHandled())
                             return;
                     }
                 }
-	    }
-	}
-	else
-	{
+                
+                limit=l-2;
+            }
+        }
+        else
+        {
             // This may not work in all circumstances... but then I think it should never be called
-	    for (int i=0;i<handlers.length;i++)
-	    {
-		handlers[i].handle(target,baseRequest, request, response);
-		if ( baseRequest.isHandled())
-		    return;
-	    }
-	}
+            for (int i=0;i<handlers.length;i++)
+            {
+                handlers[i].handle(target,baseRequest, request, response);
+                if ( baseRequest.isHandled())
+                    return;
+            }
+        }
     }
-    
-    
+
     /* ------------------------------------------------------------ */
     /** Add a context handler.
      * @param contextPath  The context path to add
      * @return the ContextHandler just added
      */
-    public ContextHandler addContext(String contextPath,String resourceBase) 
+    public ContextHandler addContext(String contextPath,String resourceBase)
     {
         try
         {
@@ -300,7 +261,7 @@ public class ContextHandlerCollection extends HandlerCollection
     /**
      * @return The class to use to add new Contexts
      */
-    public Class getContextClass()
+    public Class<?> getContextClass()
     {
         return _contextClass;
     }
@@ -310,23 +271,71 @@ public class ContextHandlerCollection extends HandlerCollection
     /**
      * @param contextClass The class to use to add new Contexts
      */
-    public void setContextClass(Class contextClass)
+    public void setContextClass(Class<? extends ContextHandler> contextClass)
     {
         if (contextClass ==null || !(ContextHandler.class.isAssignableFrom(contextClass)))
             throw new IllegalArgumentException();
         _contextClass = contextClass;
     }
-    
+
+    /* ------------------------------------------------------------ */
     /* ------------------------------------------------------------ */
-    private String normalizeHostname( String host )
+    /* ------------------------------------------------------------ */
+    private final static class Branch
     {
-        if ( host == null )
-            return null;
+        private final Handler _handler;
+        private final ContextHandler[] _contexts;
         
-        if ( host.endsWith( "." ) )
-            return host.substring( 0, host.length() -1);
-      
-        return host;
+        Branch(Handler handler)
+        {
+            _handler=handler;
+
+            if (handler instanceof ContextHandler)
+            {
+                _contexts = new ContextHandler[]{(ContextHandler)handler};
+            }
+            else if (handler instanceof HandlerContainer)
+            {
+                Handler[] contexts=((HandlerContainer)handler).getChildHandlersByClass(ContextHandler.class);
+                _contexts = new ContextHandler[contexts.length];
+                System.arraycopy(contexts, 0, _contexts, 0, contexts.length);
+            }
+            else
+                _contexts = new ContextHandler[0];
+        }
+        
+        Set<String> getContextPaths()
+        {
+            Set<String> set = new HashSet<String>();
+            for (ContextHandler context:_contexts)
+                set.add(context.getContextPath());
+            return set;
+        }
+        
+        boolean hasVirtualHost()
+        {
+            for (ContextHandler context:_contexts)
+                if (context.getVirtualHosts()!=null && context.getVirtualHosts().length>0)
+                    return true;
+            return false;
+        }
+        
+        ContextHandler[] getContextHandlers()
+        {
+            return _contexts;
+        }
+        
+        Handler getHandler()
+        {
+            return _handler;
+        }
+        
+        @Override
+        public String toString()
+        {
+            return String.format("{%s,%s}",_handler,Arrays.asList(_contexts));
+        }
     }
-    
+
+
 }
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/DebugHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/DebugHandler.java
index a795932..e30f213 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/DebugHandler.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/DebugHandler.java
@@ -16,7 +16,7 @@
 //  ========================================================================
 //
 
-package org.eclipse.jetty.server.handler; 
+package org.eclipse.jetty.server.handler;
 
 import java.io.IOException;
 import java.io.OutputStream;
@@ -27,27 +27,30 @@ import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.server.AbstractConnector;
+import org.eclipse.jetty.server.Connector;
 import org.eclipse.jetty.server.Request;
 import org.eclipse.jetty.server.Response;
 import org.eclipse.jetty.util.DateCache;
 import org.eclipse.jetty.util.RolloverFileOutputStream;
 
 
-/** 
+/**
  * Debug Handler.
  * A lightweight debug handler that can be used in production code.
  * Details of the request and response are written to an output stream
  * and the current thread name is updated with information that will link
  * to the details in that output.
  */
-public class DebugHandler extends HandlerWrapper
+public class DebugHandler extends HandlerWrapper implements Connection.Listener
 {
     private DateCache _date=new DateCache("HH:mm:ss", Locale.US);
     private OutputStream _out;
     private PrintStream _print;
-    
+
     /* ------------------------------------------------------------ */
-    /* 
+    /*
      * @see org.eclipse.jetty.server.Handler#handle(java.lang.String, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
      */
     @Override
@@ -65,19 +68,16 @@ public class DebugHandler extends HandlerWrapper
             name=old_name+":"+baseRequest.getScheme()+"://"+baseRequest.getLocalAddr()+":"+baseRequest.getLocalPort()+baseRequest.getUri();
         else
             retry=true;
-        
+
         String ex=null;
         try
         {
-            final String d=_date.now();
-            final int ms=_date.lastMs();
-            
             if (retry)
-                _print.println(d+(ms>99?".":(ms>9?".0":".00"))+ms+":"+name+" RETRY");
+                print(name,"RESUME");
             else
-                _print.println(d+(ms>99?".":(ms>9?".0":".00"))+ms+":"+name+" "+baseRequest.getRemoteAddr()+" "+request.getMethod()+" "+baseRequest.getHeader("Cookie")+"; "+baseRequest.getHeader("User-Agent"));
+                print(name,"REQUEST "+baseRequest.getRemoteAddr()+" "+request.getMethod()+" "+baseRequest.getHeader("Cookie")+"; "+baseRequest.getHeader("User-Agent"));
             thread.setName(name);
-            
+
             getHandler().handle(target,baseRequest,request,response);
         }
         catch(IOException ioe)
@@ -103,20 +103,25 @@ public class DebugHandler extends HandlerWrapper
         finally
         {
             thread.setName(old_name);
-            final String d=_date.now();
-            final int ms=_date.lastMs();
-            suspend=baseRequest.getAsyncContinuation().isSuspended();
+            suspend=baseRequest.getHttpChannelState().isSuspended();
             if (suspend)
             {
                 request.setAttribute("org.eclipse.jetty.thread.name",name);
-                _print.println(d+(ms>99?".":(ms>9?".0":".00"))+ms+":"+name+" SUSPEND");
+                print(name,"SUSPEND");
             }
             else
-                _print.println(d+(ms>99?".":(ms>9?".0":".00"))+ms+":"+name+" "+base_response.getStatus()+
-		        (ex==null?"":("/"+ex))+
-		        " "+base_response.getContentType()+" "+base_response.getContentCount());
+                print(name,"RESPONSE "+base_response.getStatus()+(ex==null?"":("/"+ex))+" "+base_response.getContentType());
         }
     }
+    
+    private void print(String name,String message)
+    {
+        long now=System.currentTimeMillis();
+        final String d=_date.formatNow(now);
+        final int ms=(int)(now%1000);
+
+        _print.println(d+(ms>99?".":(ms>9?".0":".00"))+ms+":"+name+" "+message);
+    }
 
     /* (non-Javadoc)
      * @see org.eclipse.jetty.server.handler.HandlerWrapper#doStart()
@@ -127,6 +132,11 @@ public class DebugHandler extends HandlerWrapper
         if (_out==null)
             _out=new RolloverFileOutputStream("./logs/yyyy_mm_dd.debug.log",true);
         _print=new PrintStream(_out);
+        
+        for (Connector connector : getServer().getConnectors())
+            if (connector instanceof AbstractConnector)
+                ((AbstractConnector)connector).addBean(this,false);
+            
         super.doStart();
     }
 
@@ -138,6 +148,9 @@ public class DebugHandler extends HandlerWrapper
     {
         super.doStop();
         _print.close();
+        for (Connector connector : getServer().getConnectors())
+            if (connector instanceof AbstractConnector)
+                ((AbstractConnector)connector).removeBean(this);
     }
 
     /**
@@ -155,4 +168,17 @@ public class DebugHandler extends HandlerWrapper
     {
         _out = out;
     }
+    
+    @Override
+    public void onOpened(Connection connection)
+    {
+        print(Thread.currentThread().getName(),"OPENED "+connection.toString());
+    }
+
+    @Override
+    public void onClosed(Connection connection)
+    {
+        print(Thread.currentThread().getName(),"CLOSED "+connection.toString());
+    }
+
 }
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/DefaultHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/DefaultHandler.java
index 6e04af5..f6a7899 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/DefaultHandler.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/DefaultHandler.java
@@ -26,8 +26,8 @@ import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import org.eclipse.jetty.http.HttpHeaders;
-import org.eclipse.jetty.http.HttpMethods;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpMethod;
 import org.eclipse.jetty.http.MimeTypes;
 import org.eclipse.jetty.server.Handler;
 import org.eclipse.jetty.server.Request;
@@ -41,14 +41,13 @@ import org.eclipse.jetty.util.resource.Resource;
 
 /* ------------------------------------------------------------ */
 /** Default Handler.
- * 
+ *
  * This handle will deal with unhandled requests in the server.
- * For requests for favicon.ico, the Jetty icon is served. 
+ * For requests for favicon.ico, the Jetty icon is served.
  * For reqests to '/' a 404 with a list of known contexts is served.
  * For all other requests a normal 404 is served.
- * TODO Implement OPTIONS and TRACE methods for the server.
- * 
- * 
+ *
+ *
  * @org.apache.xbean.XBean
  */
 public class DefaultHandler extends AbstractHandler
@@ -59,7 +58,7 @@ public class DefaultHandler extends AbstractHandler
     byte[] _favicon;
     boolean _serveIcon=true;
     boolean _showContexts=true;
-    
+
     public DefaultHandler()
     {
         try
@@ -76,60 +75,58 @@ public class DefaultHandler extends AbstractHandler
             LOG.warn(e);
         }
     }
-    
+
     /* ------------------------------------------------------------ */
-    /* 
+    /*
      * @see org.eclipse.jetty.server.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
      */
+    @Override
     public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
-    {              
+    {
         if (response.isCommitted() || baseRequest.isHandled())
             return;
-        
+
         baseRequest.setHandled(true);
-        
+
         String method=request.getMethod();
 
         // little cheat for common request
-        if (_serveIcon && _favicon!=null && method.equals(HttpMethods.GET) && request.getRequestURI().equals("/favicon.ico"))
+        if (_serveIcon && _favicon!=null && HttpMethod.GET.is(method) && request.getRequestURI().equals("/favicon.ico"))
         {
-            if (request.getDateHeader(HttpHeaders.IF_MODIFIED_SINCE)==_faviconModified)
+            if (request.getDateHeader(HttpHeader.IF_MODIFIED_SINCE.toString())==_faviconModified)
                 response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
             else
             {
                 response.setStatus(HttpServletResponse.SC_OK);
                 response.setContentType("image/x-icon");
                 response.setContentLength(_favicon.length);
-                response.setDateHeader(HttpHeaders.LAST_MODIFIED, _faviconModified);
-                response.setHeader(HttpHeaders.CACHE_CONTROL,"max-age=360000,public");
+                response.setDateHeader(HttpHeader.LAST_MODIFIED.toString(), _faviconModified);
+                response.setHeader(HttpHeader.CACHE_CONTROL.toString(),"max-age=360000,public");
                 response.getOutputStream().write(_favicon);
             }
             return;
         }
-        
-        
-        if (!method.equals(HttpMethods.GET) || !request.getRequestURI().equals("/"))
+
+
+        if (!_showContexts || !HttpMethod.GET.is(method) || !request.getRequestURI().equals("/"))
         {
             response.sendError(HttpServletResponse.SC_NOT_FOUND);
-            return;   
+            return;
         }
 
         response.setStatus(HttpServletResponse.SC_NOT_FOUND);
-        response.setContentType(MimeTypes.TEXT_HTML);
-        
-        ByteArrayISO8859Writer writer = new ByteArrayISO8859Writer(1500);
-        
-        writer.write("<HTML>\n<HEAD>\n<TITLE>Error 404 - Not Found");
-        writer.write("</TITLE>\n<BODY>\n<H2>Error 404 - Not Found.</H2>\n");
-        writer.write("No context on this server matched or handled this request.<BR>");
-        
-        if (_showContexts)
+        response.setContentType(MimeTypes.Type.TEXT_HTML.toString());
+
+        try (ByteArrayISO8859Writer writer = new ByteArrayISO8859Writer(1500);)
         {
+            writer.write("<HTML>\n<HEAD>\n<TITLE>Error 404 - Not Found");
+            writer.write("</TITLE>\n<BODY>\n<H2>Error 404 - Not Found.</H2>\n");
+            writer.write("No context on this server matched or handled this request.<BR>");
             writer.write("Contexts known to this server are: <ul>");
-            
+
             Server server = getServer();
             Handler[] handlers = server==null?null:server.getChildHandlersByClass(ContextHandler.class);
-     
+
             for (int i=0;handlers!=null && i<handlers.length;i++)
             {
                 ContextHandler context = (ContextHandler)handlers[i];
@@ -164,17 +161,19 @@ public class DefaultHandler extends AbstractHandler
                     writer.write("</li>\n");
                 }
             }
-        }
-        
-        for (int i=0;i<10;i++)
-            writer.write("\n<!-- Padding for IE                  -->");
-        
-        writer.write("\n</BODY>\n</HTML>\n");
-        writer.flush();
-        response.setContentLength(writer.size());
-        OutputStream out=response.getOutputStream();
-        writer.writeTo(out);
-        out.close();
+
+            writer.write("</ul><hr>");
+            writer.write("<a href=\"http://eclipse.org/jetty\"><img border=0 src=\"/favicon.ico\"/></a> ");
+            writer.write("<a href=\"http://eclipse.org/jetty\">Powered by Jetty:// Java Web Server</a><hr/>\n");
+
+            writer.write("\n</BODY>\n</HTML>\n");
+            writer.flush();
+            response.setContentLength(writer.size());
+            try (OutputStream out=response.getOutputStream())
+            {
+                writer.writeTo(out);
+            }
+        } 
     }
 
     /* ------------------------------------------------------------ */
@@ -194,7 +193,7 @@ public class DefaultHandler extends AbstractHandler
     {
         _serveIcon = serveIcon;
     }
-    
+
     public boolean getShowContexts()
     {
         return _showContexts;
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ErrorHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ErrorHandler.java
index 386890d..9591e53 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ErrorHandler.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ErrorHandler.java
@@ -22,28 +22,34 @@ import java.io.IOException;
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.io.Writer;
+import java.nio.ByteBuffer;
 
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import org.eclipse.jetty.http.HttpHeaders;
-import org.eclipse.jetty.http.HttpMethods;
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpMethod;
 import org.eclipse.jetty.http.HttpStatus;
 import org.eclipse.jetty.http.MimeTypes;
-import org.eclipse.jetty.server.AbstractHttpConnection;
 import org.eclipse.jetty.server.Dispatcher;
 import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.util.BufferUtil;
 import org.eclipse.jetty.util.ByteArrayISO8859Writer;
+import org.eclipse.jetty.util.StringUtil;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
 
 /* ------------------------------------------------------------ */
 /** Handler for Error pages
- * An ErrorHandler is registered with {@link ContextHandler#setErrorHandler(ErrorHandler)} or 
- * {@link org.eclipse.jetty.server.Server#addBean(Object)}.   
- * It is called by the HttpResponse.sendError method to write a error page.
- * 
+ * An ErrorHandler is registered with {@link ContextHandler#setErrorHandler(ErrorHandler)} or
+ * {@link org.eclipse.jetty.server.Server#addBean(Object)}.
+ * It is called by the HttpResponse.sendError method to write a error page via {@link #handle(String, Request, HttpServletRequest, HttpServletResponse)}
+ * or via {@link #badMessageError(int, String, HttpFields)} for bad requests for which a dispatch cannot be done.
+ *
  */
 public class ErrorHandler extends AbstractHandler
 {    
@@ -53,18 +59,18 @@ public class ErrorHandler extends AbstractHandler
     boolean _showStacks=true;
     boolean _showMessageInTitle=true;
     String _cacheControl="must-revalidate,no-cache,no-store";
-    
+
     /* ------------------------------------------------------------ */
-    /* 
+    /*
      * @see org.eclipse.jetty.server.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
      */
+    @Override
     public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
     {
-        AbstractHttpConnection connection = AbstractHttpConnection.getCurrentConnection();
         String method = request.getMethod();
-        if(!method.equals(HttpMethods.GET) && !method.equals(HttpMethods.POST) && !method.equals(HttpMethods.HEAD))
+        if (!HttpMethod.GET.is(method) && !HttpMethod.POST.is(method) && !HttpMethod.HEAD.is(method))
         {
-            connection.getRequest().setHandled(true);
+            baseRequest.setHandled(true);
             return;
         }
         
@@ -97,12 +103,13 @@ public class ErrorHandler extends AbstractHandler
             }
         }
         
-        connection.getRequest().setHandled(true);
-        response.setContentType(MimeTypes.TEXT_HTML_8859_1);    
+        baseRequest.setHandled(true);
+        response.setContentType(MimeTypes.Type.TEXT_HTML_8859_1.asString());    
         if (_cacheControl!=null)
-            response.setHeader(HttpHeaders.CACHE_CONTROL, _cacheControl);
+            response.setHeader(HttpHeader.CACHE_CONTROL.asString(), _cacheControl);
         ByteArrayISO8859Writer writer= new ByteArrayISO8859Writer(4096);
-        handleErrorPage(request, writer, connection.getResponse().getStatus(), connection.getResponse().getReason());
+        String reason=(response instanceof Response)?((Response)response).getReason():null;
+        handleErrorPage(request, writer, response.getStatus(), reason);
         writer.flush();
         response.setContentLength(writer.size());
         writer.writeTo(response.getOutputStream());
@@ -115,7 +122,7 @@ public class ErrorHandler extends AbstractHandler
     {
         writeErrorPage(request, writer, code, message, _showStacks);
     }
-    
+
     /* ------------------------------------------------------------ */
     protected void writeErrorPage(HttpServletRequest request, Writer writer, int code, String message, boolean showStacks)
         throws IOException
@@ -134,7 +141,7 @@ public class ErrorHandler extends AbstractHandler
     protected void writeErrorPageHead(HttpServletRequest request, Writer writer, int code, String message)
         throws IOException
         {
-        writer.write("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=ISO-8859-1\"/>\n");
+        writer.write("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"/>\n");
         writer.write("<title>Error ");
         writer.write(Integer.toString(code));
 
@@ -143,7 +150,7 @@ public class ErrorHandler extends AbstractHandler
             writer.write(' ');
             write(writer,message);
         }
-        writer.write("</title>\n");    
+        writer.write("</title>\n");
     }
 
     /* ------------------------------------------------------------ */
@@ -151,13 +158,11 @@ public class ErrorHandler extends AbstractHandler
         throws IOException
     {
         String uri= request.getRequestURI();
-        
+
         writeErrorPageMessage(request,writer,code,message,uri);
         if (showStacks)
             writeErrorPageStacks(request,writer);
-        writer.write("<hr /><i><small>Powered by Jetty://</small></i>");
-        for (int i= 0; i < 20; i++)
-            writer.write("<br/>                                                \n");
+        writer.write("<hr><i><small>Powered by Jetty://</small></i><hr/>\n");
     }
 
     /* ------------------------------------------------------------ */
@@ -191,9 +196,28 @@ public class ErrorHandler extends AbstractHandler
             th =th.getCause();
         }
     }
-        
 
     /* ------------------------------------------------------------ */
+    /** Bad Message Error body
+     * <p>Generate a error response body to be sent for a bad message.
+     * In this case there is something wrong with the request, so either
+     * a request cannot be built, or it is not safe to build a request.
+     * This method allows for a simple error page body to be returned 
+     * and some response headers to be set.
+     * @param status The error code that will be sent
+     * @param reason The reason for the error code (may be null)
+     * @param fields The header fields that will be sent with the response.
+     * @return The content as a ByteBuffer, or null for no body.
+     */
+    public ByteBuffer badMessageError(int status, String reason, HttpFields fields)
+    {
+        if (reason==null)
+            reason=HttpStatus.getMessage(status);
+        fields.put(HttpHeader.CONTENT_TYPE,MimeTypes.Type.TEXT_HTML_8859_1.asString());
+        return BufferUtil.toBuffer("<h1>Bad Message " + status + "</h1><pre>reason: " + reason + "</pre>");
+    }    
+    
+    /* ------------------------------------------------------------ */
     /** Get the cacheControl.
      * @return the cacheControl header to set on error responses.
      */
@@ -228,7 +252,7 @@ public class ErrorHandler extends AbstractHandler
     {
         _showStacks = showStacks;
     }
-    
+
     /* ------------------------------------------------------------ */
     /**
      * @param showMessageInTitle if true, the error message appears in page title
@@ -237,8 +261,8 @@ public class ErrorHandler extends AbstractHandler
     {
         _showMessageInTitle = showMessageInTitle;
     }
-    
-    
+
+
     /* ------------------------------------------------------------ */
     public boolean getShowMessageInTitle()
     {
@@ -251,30 +275,8 @@ public class ErrorHandler extends AbstractHandler
     {
         if (string==null)
             return;
-        
-        for (int i=0;i<string.length();i++)
-        {
-            char c=string.charAt(i);
-            
-            switch(c)
-            {
-                case '&' :
-                    writer.write("&");
-                    break;
-                case '<' :
-                    writer.write("<");
-                    break;
-                case '>' :
-                    writer.write(">");
-                    break;
-                    
-                default:
-                    if (Character.isISOControl(c) && !Character.isWhitespace(c))
-                        writer.write('?');
-                    else 
-                        writer.write(c);
-            }          
-        }
+
+        writer.write(StringUtil.sanitizeXmlString(string));
     }
 
     /* ------------------------------------------------------------ */
@@ -282,4 +284,15 @@ public class ErrorHandler extends AbstractHandler
     {
         String getErrorPage(HttpServletRequest request);
     }
+
+    /* ------------------------------------------------------------ */
+    public static ErrorHandler getErrorHandler(Server server, ContextHandler context)
+    {
+        ErrorHandler error_handler=null;
+        if (context!=null)
+            error_handler=context.getErrorHandler();
+        if (error_handler==null && server!=null)
+            error_handler = server.getBean(ErrorHandler.class);
+        return error_handler;
+    }
 }
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/GzipHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/GzipHandler.java
deleted file mode 100644
index 538ffd4..0000000
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/GzipHandler.java
+++ /dev/null
@@ -1,356 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.server.handler;
-
-import java.io.IOException;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.io.PrintWriter;
-import java.io.UnsupportedEncodingException;
-import java.util.HashSet;
-import java.util.Set;
-import java.util.StringTokenizer;
-import java.util.zip.DeflaterOutputStream;
-import java.util.zip.GZIPOutputStream;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jetty.continuation.Continuation;
-import org.eclipse.jetty.continuation.ContinuationListener;
-import org.eclipse.jetty.continuation.ContinuationSupport;
-import org.eclipse.jetty.http.HttpMethods;
-import org.eclipse.jetty.http.gzip.CompressedResponseWrapper;
-import org.eclipse.jetty.http.gzip.AbstractCompressedStream;
-import org.eclipse.jetty.server.Request;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-
-/* ------------------------------------------------------------ */
-/**
- * GZIP Handler This handler will gzip the content of a response if:
- * <ul>
- * <li>The filter is mapped to a matching path</li>
- * <li>The response status code is >=200 and <300
- * <li>The content length is unknown or more than the <code>minGzipSize</code> initParameter or the minGzipSize is 0(default)</li>
- * <li>The content-type is in the comma separated list of mimeTypes set in the <code>mimeTypes</code> initParameter or if no mimeTypes are defined the
- * content-type is not "application/gzip"</li>
- * <li>No content-encoding is specified by the resource</li>
- * </ul>
- * 
- * <p>
- * Compressing the content can greatly improve the network bandwidth usage, but at a cost of memory and CPU cycles. If this handler is used for static content,
- * then use of efficient direct NIO may be prevented, thus use of the gzip mechanism of the <code>org.eclipse.jetty.servlet.DefaultServlet</code> is advised instead.
- * </p>
- */
-public class GzipHandler extends HandlerWrapper
-{
-    private static final Logger LOG = Log.getLogger(GzipHandler.class);
-
-    protected Set<String> _mimeTypes;
-    protected Set<String> _excluded;
-    protected int _bufferSize = 8192;
-    protected int _minGzipSize = 256;
-    protected String _vary = "Accept-Encoding, User-Agent";
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Instantiates a new gzip handler.
-     */
-    public GzipHandler()
-    {
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Get the mime types.
-     * 
-     * @return mime types to set
-     */
-    public Set<String> getMimeTypes()
-    {
-        return _mimeTypes;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Set the mime types.
-     * 
-     * @param mimeTypes
-     *            the mime types to set
-     */
-    public void setMimeTypes(Set<String> mimeTypes)
-    {
-        _mimeTypes = mimeTypes;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Set the mime types.
-     * 
-     * @param mimeTypes
-     *            the mime types to set
-     */
-    public void setMimeTypes(String mimeTypes)
-    {
-        if (mimeTypes != null)
-        {
-            _mimeTypes = new HashSet<String>();
-            StringTokenizer tok = new StringTokenizer(mimeTypes,",",false);
-            while (tok.hasMoreTokens())
-            {
-                _mimeTypes.add(tok.nextToken());
-            }
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Get the excluded user agents.
-     * 
-     * @return excluded user agents
-     */
-    public Set<String> getExcluded()
-    {
-        return _excluded;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Set the excluded user agents.
-     * 
-     * @param excluded
-     *            excluded user agents to set
-     */
-    public void setExcluded(Set<String> excluded)
-    {
-        _excluded = excluded;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Set the excluded user agents.
-     * 
-     * @param excluded
-     *            excluded user agents to set
-     */
-    public void setExcluded(String excluded)
-    {
-        if (excluded != null)
-        {
-            _excluded = new HashSet<String>();
-            StringTokenizer tok = new StringTokenizer(excluded,",",false);
-            while (tok.hasMoreTokens())
-                _excluded.add(tok.nextToken());
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return The value of the Vary header set if a response can be compressed.
-     */
-    public String getVary()
-    {
-        return _vary;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Set the value of the Vary header sent with responses that could be compressed.  
-     * <p>
-     * By default it is set to 'Accept-Encoding, User-Agent' since IE6 is excluded by 
-     * default from the excludedAgents. If user-agents are not to be excluded, then 
-     * this can be set to 'Accept-Encoding'.  Note also that shared caches may cache 
-     * many copies of a resource that is varied by User-Agent - one per variation of the 
-     * User-Agent, unless the cache does some normalization of the UA string.
-     * @param vary The value of the Vary header set if a response can be compressed.
-     */
-    public void setVary(String vary)
-    {
-        _vary = vary;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Get the buffer size.
-     * 
-     * @return the buffer size
-     */
-    public int getBufferSize()
-    {
-        return _bufferSize;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Set the buffer size.
-     * 
-     * @param bufferSize
-     *            buffer size to set
-     */
-    public void setBufferSize(int bufferSize)
-    {
-        _bufferSize = bufferSize;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Get the minimum reponse size.
-     * 
-     * @return minimum reponse size
-     */
-    public int getMinGzipSize()
-    {
-        return _minGzipSize;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Set the minimum reponse size.
-     * 
-     * @param minGzipSize
-     *            minimum reponse size
-     */
-    public void setMinGzipSize(int minGzipSize)
-    {
-        _minGzipSize = minGzipSize;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.server.handler.HandlerWrapper#handle(java.lang.String, org.eclipse.jetty.server.Request, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
-     */
-    @Override
-    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
-    {
-        if (_handler!=null && isStarted())
-        {
-            String ae = request.getHeader("accept-encoding");
-            if (ae != null && ae.indexOf("gzip")>=0 && !response.containsHeader("Content-Encoding")
-                    && !HttpMethods.HEAD.equalsIgnoreCase(request.getMethod()))
-            {
-                if (_excluded!=null)
-                {
-                    String ua = request.getHeader("User-Agent");
-                    if (_excluded.contains(ua))
-                    {
-                        _handler.handle(target,baseRequest, request, response);
-                        return;
-                    }
-                }
-
-                final CompressedResponseWrapper wrappedResponse = newGzipResponseWrapper(request,response);
-                
-                boolean exceptional=true;
-                try
-                {
-                    _handler.handle(target, baseRequest, request, wrappedResponse);
-                    exceptional=false;
-                }
-                finally
-                {
-                    Continuation continuation = ContinuationSupport.getContinuation(request);
-                    if (continuation.isSuspended() && continuation.isResponseWrapped())   
-                    {
-                        continuation.addContinuationListener(new ContinuationListener()
-                        {
-                            public void onComplete(Continuation continuation)
-                            {
-                                try
-                                {
-                                    wrappedResponse.finish();
-                                }
-                                catch(IOException e)
-                                {
-                                    LOG.warn(e);
-                                }
-                            }
-
-                            public void onTimeout(Continuation continuation)
-                            {}
-                        });
-                    }
-                    else if (exceptional && !response.isCommitted())
-                    {
-                        wrappedResponse.resetBuffer();
-                        wrappedResponse.noCompression();
-                    }
-                    else
-                        wrappedResponse.finish();
-                }
-            }
-            else
-            {
-                _handler.handle(target,baseRequest, request, response);
-            }
-        }
-    }
-
-    /**
-     * Allows derived implementations to replace ResponseWrapper implementation.
-     *
-     * @param request the request
-     * @param response the response
-     * @return the gzip response wrapper
-     */
-    protected CompressedResponseWrapper newGzipResponseWrapper(HttpServletRequest request, HttpServletResponse response)
-    {
-        return new CompressedResponseWrapper(request,response)
-        {
-            {
-                super.setMimeTypes(GzipHandler.this._mimeTypes);
-                super.setBufferSize(GzipHandler.this._bufferSize);
-                super.setMinCompressSize(GzipHandler.this._minGzipSize);
-            }
-            
-            @Override
-            protected AbstractCompressedStream newCompressedStream(HttpServletRequest request,HttpServletResponse response) throws IOException
-            {
-                return new AbstractCompressedStream("gzip",request,this,_vary)
-                {
-                    @Override
-                    protected DeflaterOutputStream createStream() throws IOException
-                    {
-                        return new GZIPOutputStream(_response.getOutputStream(),_bufferSize);
-                    }
-                };
-            }
-            
-            @Override
-            protected PrintWriter newWriter(OutputStream out,String encoding) throws UnsupportedEncodingException
-            {
-                return GzipHandler.this.newWriter(out,encoding);
-            }
-        };
-    }
-    
-    /**
-     * Allows derived implementations to replace PrintWriter implementation.
-     *
-     * @param out the out
-     * @param encoding the encoding
-     * @return the prints the writer
-     * @throws UnsupportedEncodingException
-     */
-    protected PrintWriter newWriter(OutputStream out,String encoding) throws UnsupportedEncodingException
-    {
-        return encoding==null?new PrintWriter(out):new PrintWriter(new OutputStreamWriter(out,encoding));
-    }
-}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerCollection.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerCollection.java
index 60a64cc..4b4aca7 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerCollection.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerCollection.java
@@ -19,7 +19,8 @@
 package org.eclipse.jetty.server.handler;
 
 import java.io.IOException;
-import java.util.concurrent.CountDownLatch;
+import java.util.Arrays;
+import java.util.List;
 
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
@@ -28,32 +29,33 @@ import javax.servlet.http.HttpServletResponse;
 import org.eclipse.jetty.server.Handler;
 import org.eclipse.jetty.server.Request;
 import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.util.LazyList;
+import org.eclipse.jetty.util.ArrayUtil;
 import org.eclipse.jetty.util.MultiException;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
 
 /* ------------------------------------------------------------ */
-/** A collection of handlers.  
+/** A collection of handlers.
  * <p>
- * The default implementations  calls all handlers in list order, 
+ * The default implementations  calls all handlers in list order,
  * regardless of the response status or exceptions. Derived implementation
- * may alter the order or the conditions of calling the contained 
+ * may alter the order or the conditions of calling the contained
  * handlers.
  * <p>
- * 
- * @org.apache.xbean.XBean
+ *
  */
+ at ManagedObject("Handler of multiple handlers")
 public class HandlerCollection extends AbstractHandlerContainer
 {
     private final boolean _mutableWhenRunning;
     private volatile Handler[] _handlers;
-    private boolean _parallelStart=false; 
 
     /* ------------------------------------------------------------ */
     public HandlerCollection()
     {
         _mutableWhenRunning=false;
     }
-    
+
     /* ------------------------------------------------------------ */
     public HandlerCollection(boolean mutableWhenRunning)
     {
@@ -64,89 +66,43 @@ public class HandlerCollection extends AbstractHandlerContainer
     /**
      * @return Returns the handlers.
      */
+    @Override
+    @ManagedAttribute(value="Wrapped handlers", readonly=true)
     public Handler[] getHandlers()
     {
         return _handlers;
     }
-    
+
     /* ------------------------------------------------------------ */
     /**
-     * 
      * @param handlers The handlers to set.
      */
     public void setHandlers(Handler[] handlers)
     {
         if (!_mutableWhenRunning && isStarted())
             throw new IllegalStateException(STARTED);
-        
-        Handler [] old_handlers = _handlers==null?null:_handlers.clone();
-        _handlers = handlers;
-        
-        Server server = getServer();
-        MultiException mex = new MultiException();
-        for (int i=0;handlers!=null && i<handlers.length;i++)
-        {
-            if (handlers[i].getServer()!=server)
-                handlers[i].setServer(server);
-        }
-
-        if (getServer()!=null)
-            getServer().getContainer().update(this, old_handlers, handlers, "handler");
-        
-        // stop old handlers
-        for (int i=0;old_handlers!=null && i<old_handlers.length;i++)
-        {
-            if (old_handlers[i]!=null)
-            {
-                try
-                {
-                    if (old_handlers[i].isStarted())
-                        old_handlers[i].stop();
-                }
-                catch (Throwable e)
-                {
-                    mex.add(e);
-                }
-            }
-        }
-                
-        mex.ifExceptionThrowRuntime();
-    }
-    
-
-    
-    /* ------------------------------------------------------------ */
-    /** Get the parrallelStart.
-     * @return true if the contained handlers are started in parallel.
-     */
-    public boolean isParallelStart()
-    {
-        return _parallelStart;
-    }
-
 
-
-    /* ------------------------------------------------------------ */
-    /** Set the parallelStart.
-     * @param parallelStart If true, contained handlers are started in parallel.
-     */
-    public void setParallelStart(boolean parallelStart)
-    {
-        this._parallelStart = parallelStart;
+        if (handlers!=null)
+            for (Handler handler:handlers)
+                if (handler.getServer()!=getServer())
+                    handler.setServer(getServer());
+        Handler[] old=_handlers;;
+        _handlers = handlers;
+        updateBeans(old, handlers);
     }
 
-
     /* ------------------------------------------------------------ */
     /**
      * @see Handler#handle(String, Request, HttpServletRequest, HttpServletResponse)
      */
-    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) 
+    @Override
+    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
         throws IOException, ServletException
     {
         if (_handlers!=null && isStarted())
         {
             MultiException mex=null;
-            
+
             for (int i=0;i<_handlers.length;i++)
             {
                 try
@@ -175,130 +131,47 @@ public class HandlerCollection extends AbstractHandlerContainer
                 else
                     throw new ServletException(mex);
             }
-            
-        }    
-    }
 
-    /* ------------------------------------------------------------ */
-    /* 
-     * @see org.eclipse.jetty.server.server.handler.AbstractHandler#doStart()
-     */
-    @Override
-    protected void doStart() throws Exception
-    {
-        final MultiException mex=new MultiException();
-        if (_handlers!=null)
-        {
-            if (_parallelStart)
-            {
-                final CountDownLatch latch = new CountDownLatch(_handlers.length);
-                final ClassLoader loader = Thread.currentThread().getContextClassLoader();
-                for (int i=0;i<_handlers.length;i++)
-                {
-                    final int h=i;
-                    getServer().getThreadPool().dispatch(
-                            new Runnable()
-                            {
-                                public void run()
-                                {
-                                    ClassLoader orig = Thread.currentThread().getContextClassLoader();
-                                    try
-                                    {
-                                        Thread.currentThread().setContextClassLoader(loader);
-                                        _handlers[h].start();
-                                    }
-                                    catch(Throwable e)
-                                    {
-                                        mex.add(e);
-                                    }
-                                    finally
-                                    {
-                                        Thread.currentThread().setContextClassLoader(orig);
-                                        latch.countDown();
-                                    }
-                                }
-                            }
-                    );
-                }
-                latch.await();
-            }
-            else
-            {
-                for (int i=0;i<_handlers.length;i++)
-                {
-                    try{_handlers[i].start();}
-                    catch(Throwable e){mex.add(e);}
-                }
-            }
         }
-        super.doStart();
-        mex.ifExceptionThrow();
     }
 
     /* ------------------------------------------------------------ */
-    /* 
-     * @see org.eclipse.jetty.server.server.handler.AbstractHandler#doStop()
-     */
-    @Override
-    protected void doStop() throws Exception
-    {
-        MultiException mex=new MultiException();
-        try { super.doStop(); } catch(Throwable e){mex.add(e);}
-        if (_handlers!=null)
-        {
-            for (int i=_handlers.length;i-->0;)
-                try{_handlers[i].stop();}catch(Throwable e){mex.add(e);}
-        }
-        mex.ifExceptionThrow();
-    }
-    
-    /* ------------------------------------------------------------ */
     @Override
     public void setServer(Server server)
     {
-        if (isStarted())
-            throw new IllegalStateException(STARTED);
-        
-        Server old_server=getServer();
-        
         super.setServer(server);
-
-        Handler[] h=getHandlers();
-        for (int i=0;h!=null && i<h.length;i++)
-            h[i].setServer(server);
-        
-        if (server!=null && server!=old_server)
-            server.getContainer().update(this, null,_handlers, "handler");
-        
+        Handler[] handlers=getHandlers();
+        if (handlers!=null)
+            for (Handler h : handlers)
+                h.setServer(server);
     }
 
     /* ------------------------------------------------------------ */
     /* Add a handler.
-     * This implementation adds the passed handler to the end of the existing collection of handlers. 
+     * This implementation adds the passed handler to the end of the existing collection of handlers.
      * @see org.eclipse.jetty.server.server.HandlerContainer#addHandler(org.eclipse.jetty.server.server.Handler)
      */
     public void addHandler(Handler handler)
     {
-        setHandlers((Handler[])LazyList.addToArray(getHandlers(), handler, Handler.class));
+        setHandlers(ArrayUtil.addToArray(getHandlers(), handler, Handler.class));
     }
-    
+
     /* ------------------------------------------------------------ */
     public void removeHandler(Handler handler)
     {
         Handler[] handlers = getHandlers();
-        
+
         if (handlers!=null && handlers.length>0 )
-            setHandlers((Handler[])LazyList.removeFromArray(handlers, handler));
+            setHandlers(ArrayUtil.removeFromArray(handlers, handler));
     }
 
     /* ------------------------------------------------------------ */
     @Override
-    protected Object expandChildren(Object list, Class byClass)
+    protected void expandChildren(List<Handler> list, Class<?> byClass)
     {
-        Handler[] handlers = getHandlers();
-        for (int i=0;handlers!=null && i<handlers.length;i++)
-            list=expandHandler(handlers[i], list, byClass);
-        return list;
+        if (getHandlers()!=null)
+            for (Handler h:getHandlers())
+                expandHandler(h, list, byClass);
     }
 
     /* ------------------------------------------------------------ */
@@ -313,4 +186,12 @@ public class HandlerCollection extends AbstractHandlerContainer
             child.destroy();
         super.destroy();
     }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        Handler[] handlers=getHandlers();
+        return super.toString()+(handlers==null?"[]":Arrays.asList(getHandlers()).toString());
+    }
 }
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerList.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerList.java
index 9291083..8f31de2 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerList.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerList.java
@@ -30,7 +30,7 @@ import org.eclipse.jetty.server.Request;
 /* ------------------------------------------------------------ */
 /** HandlerList.
  * This extension of {@link HandlerCollection} will call
- * each contained handler in turn until either an exception is thrown, the response 
+ * each contained handler in turn until either an exception is thrown, the response
  * is committed or a positive response status is set.
  */
 public class HandlerList extends HandlerCollection
@@ -40,11 +40,11 @@ public class HandlerList extends HandlerCollection
      * @see Handler#handle(String, Request, HttpServletRequest, HttpServletResponse)
      */
     @Override
-    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) 
+    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
         throws IOException, ServletException
     {
         Handler[] handlers = getHandlers();
-        
+
         if (handlers!=null && isStarted())
         {
             for (int i=0;i<handlers.length;i++)
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerWrapper.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerWrapper.java
index 7b0217c..e3f3fd5 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerWrapper.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerWrapper.java
@@ -19,6 +19,7 @@
 package org.eclipse.jetty.server.handler;
 
 import java.io.IOException;
+import java.util.List;
 
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
@@ -27,13 +28,15 @@ import javax.servlet.http.HttpServletResponse;
 import org.eclipse.jetty.server.Handler;
 import org.eclipse.jetty.server.Request;
 import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.util.component.LifeCycle;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
 
 /* ------------------------------------------------------------ */
 /** A <code>HandlerWrapper</code> acts as a {@link Handler} but delegates the {@link Handler#handle handle} method and
  * {@link LifeCycle life cycle} events to a delegate. This is primarily used to implement the <i>Decorator</i> pattern.
  *
  */
+ at ManagedObject("Handler wrapping another Handler")
 public class HandlerWrapper extends AbstractHandlerContainer
 {
     protected Handler _handler;
@@ -50,6 +53,7 @@ public class HandlerWrapper extends AbstractHandlerContainer
     /**
      * @return Returns the handlers.
      */
+    @ManagedAttribute(value="Wrapped Handler", readonly=true)
     public Handler getHandler()
     {
         return _handler;
@@ -59,6 +63,7 @@ public class HandlerWrapper extends AbstractHandlerContainer
     /**
      * @return Returns the handlers.
      */
+    @Override
     public Handler[] getHandlers()
     {
         if (_handler==null)
@@ -75,40 +80,16 @@ public class HandlerWrapper extends AbstractHandlerContainer
         if (isStarted())
             throw new IllegalStateException(STARTED);
 
-        Handler old_handler = _handler;
-        _handler = handler;
         if (handler!=null)
             handler.setServer(getServer());
         
-        if (getServer()!=null)
-            getServer().getContainer().update(this, old_handler, handler, "handler");
-    }
-
-    /* ------------------------------------------------------------ */
-    /*
-     * @see org.eclipse.thread.AbstractLifeCycle#doStart()
-     */
-    @Override
-    protected void doStart() throws Exception
-    {
-        if (_handler!=null)
-            _handler.start();
-        super.doStart();
+        Handler old=_handler;
+        _handler=handler;
+        updateBean(old,_handler);
     }
 
     /* ------------------------------------------------------------ */
-    /*
-     * @see org.eclipse.thread.AbstractLifeCycle#doStop()
-     */
     @Override
-    protected void doStop() throws Exception
-    {
-        if (_handler!=null)
-            _handler.stop();
-        super.doStop();
-    }
-
-    /* ------------------------------------------------------------ */
     public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
     {
         if (_handler!=null && isStarted())
@@ -122,46 +103,24 @@ public class HandlerWrapper extends AbstractHandlerContainer
     @Override
     public void setServer(Server server)
     {
-        Server old_server=getServer();
-        if (server==old_server)
+        if (server==getServer())
             return;
-
+        
         if (isStarted())
             throw new IllegalStateException(STARTED);
 
         super.setServer(server);
-
         Handler h=getHandler();
         if (h!=null)
             h.setServer(server);
-
-        if (server!=null && server!=old_server)
-            server.getContainer().update(this, null,_handler, "handler");
     }
 
 
     /* ------------------------------------------------------------ */
     @Override
-    protected Object expandChildren(Object list, Class byClass)
+    protected void expandChildren(List<Handler> list, Class<?> byClass)
     {
-        return expandHandler(_handler,list,byClass);
-    }
-
-    /* ------------------------------------------------------------ */
-    public <H extends Handler> H getNestedHandlerByClass(Class<H> byclass)
-    {
-        HandlerWrapper h=this;
-        while (h!=null)
-        {
-            if (byclass.isInstance(h))
-                return (H)h;
-            Handler w = h.getHandler();
-            if (w instanceof HandlerWrapper)
-                h=(HandlerWrapper)w;
-            else break;
-        }
-        return null;
-
+        expandHandler(_handler,list,byClass);
     }
 
     /* ------------------------------------------------------------ */
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HotSwapHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HotSwapHandler.java
index 4d2dd9c..68eaad1 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HotSwapHandler.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HotSwapHandler.java
@@ -19,6 +19,7 @@
 package org.eclipse.jetty.server.handler;
 
 import java.io.IOException;
+import java.util.List;
 
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
@@ -31,7 +32,7 @@ import org.eclipse.jetty.server.Server;
 /* ------------------------------------------------------------ */
 /**
  * A <code>HandlerContainer</code> that allows a hot swap of a wrapped handler.
- * 
+ *
  */
 public class HotSwapHandler extends AbstractHandlerContainer
 {
@@ -39,7 +40,7 @@ public class HotSwapHandler extends AbstractHandlerContainer
 
     /* ------------------------------------------------------------ */
     /**
-     * 
+     *
      */
     public HotSwapHandler()
     {
@@ -58,6 +59,7 @@ public class HotSwapHandler extends AbstractHandlerContainer
     /**
      * @return Returns the handlers.
      */
+    @Override
     public Handler[] getHandlers()
     {
         return new Handler[]
@@ -75,20 +77,10 @@ public class HotSwapHandler extends AbstractHandlerContainer
             throw new IllegalArgumentException("Parameter handler is null.");
         try
         {
-            Handler old_handler = _handler;
-            _handler = handler;
+            updateBean(_handler,handler);
+            _handler=handler;
             Server server = getServer();
             handler.setServer(server);
-            addBean(handler);
-
-            if (server != null)
-                server.getContainer().update(this,old_handler,handler,"handler");
-
-            // if there is an old handler and it was started, stop it
-            if (old_handler != null)
-            {
-                removeBean(old_handler);
-            }
 
         }
         catch (Exception e)
@@ -121,6 +113,7 @@ public class HotSwapHandler extends AbstractHandlerContainer
     /*
      * @see org.eclipse.jetty.server.server.EventHandler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
      */
+    @Override
     public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
     {
         if (_handler != null && isStarted())
@@ -133,10 +126,6 @@ public class HotSwapHandler extends AbstractHandlerContainer
     @Override
     public void setServer(Server server)
     {
-        Server old_server = getServer();
-        if (server == old_server)
-            return;
-
         if (isRunning())
             throw new IllegalStateException(RUNNING);
 
@@ -145,18 +134,13 @@ public class HotSwapHandler extends AbstractHandlerContainer
         Handler h = getHandler();
         if (h != null)
             h.setServer(server);
-
-        if (server != null && server != old_server)
-            server.getContainer().update(this,null,_handler,"handler");
     }
 
     /* ------------------------------------------------------------ */
-    @SuppressWarnings(
-    { "rawtypes", "unchecked" })
     @Override
-    protected Object expandChildren(Object list, Class byClass)
+    protected void expandChildren(List<Handler> list, Class<?> byClass)
     {
-        return expandHandler(_handler,list,byClass);
+        expandHandler(_handler,list,byClass);
     }
 
     /* ------------------------------------------------------------ */
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/IPAccessHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/IPAccessHandler.java
index 0be4d36..747b88d 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/IPAccessHandler.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/IPAccessHandler.java
@@ -19,8 +19,7 @@
 package org.eclipse.jetty.server.handler;
 
 import java.io.IOException;
-import java.util.Collections;
-import java.util.List;
+import java.net.InetSocketAddress;
 import java.util.Map;
 
 import javax.servlet.ServletException;
@@ -30,7 +29,7 @@ import javax.servlet.http.HttpServletResponse;
 import org.eclipse.jetty.http.HttpStatus;
 import org.eclipse.jetty.http.PathMap;
 import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.server.AbstractHttpConnection;
+import org.eclipse.jetty.server.HttpChannel;
 import org.eclipse.jetty.server.Request;
 import org.eclipse.jetty.util.IPAddressMap;
 import org.eclipse.jetty.util.log.Log;
@@ -43,7 +42,7 @@ import org.eclipse.jetty.util.log.Logger;
  * Controls access to the wrapped handler by the real remote IP. Control is provided
  * by white/black lists that include both internet addresses and URIs. This handler
  * uses the real internet address of the connection, not one reported in the forwarded
- * for headers, as this cannot be as easily forged. 
+ * for headers, as this cannot be as easily forged.
  * <p>
  * Typically, the black/white lists will be used in one of three modes:
  * <ul>
@@ -53,17 +52,23 @@ import org.eclipse.jetty.util.log.Logger;
  * entries, that are then further refined by several specific black list exceptions
  * </ul>
  * <p>
- * An empty white list is treated as match all. If there is at least one entry in
+ * By default an empty white list is treated as match all. If there is at least one entry in
  * the white list, then a request must match a white list entry. Black list entries
- * are always applied, so that even if an entry matches the white list, a black list 
+ * are always applied, so that even if an entry matches the white list, a black list
  * entry will override it.
  * <p>
- * Internet addresses may be specified as absolute address or as a combination of 
+ * <p>
+ * You can change white list policy setting whiteListByPath to true. In this mode a request will be white listed
+ * IF it has a matching URL in the white list, otherwise the black list applies, e.g. in default mode when
+ * whiteListByPath = false and wl = "127.0.0.1|/foo", /bar request from 127.0.0.1 will be blacklisted,
+ * if whiteListByPath=true then not.
+ * </p>
+ * Internet addresses may be specified as absolute address or as a combination of
  * four octet wildcard specifications (a.b.c.d) that are defined as follows.
  * </p>
  * <pre>
  * nnn - an absolute value (0-255)
- * mmm-nnn - an inclusive range of absolute values, 
+ * mmm-nnn - an inclusive range of absolute values,
  *           with following shorthand notations:
  *           nnn- => nnn-255
  *           -nnn => 0-nnn
@@ -72,13 +77,13 @@ import org.eclipse.jetty.util.log.Logger;
  * </pre>
  * <p>
  * Internet address specification is separated from the URI pattern using the "|" (pipe)
- * character. URI patterns follow the servlet specification for simple * prefix and 
+ * character. URI patterns follow the servlet specification for simple * prefix and
  * suffix wild cards (e.g. /, /foo, /foo/bar, /foo/bar/*, *.baz).
  * <p>
  * Earlier versions of the handler used internet address prefix wildcard specification
  * to define a range of the internet addresses (e.g. 127., 10.10., 172.16.1.).
- * They also used the first "/" character of the URI pattern to separate it from the 
- * internet address. Both of these features have been deprecated in the current version. 
+ * They also used the first "/" character of the URI pattern to separate it from the
+ * internet address. Both of these features have been deprecated in the current version.
  * <p>
  * Examples of the entry specifications are:
  * <ul>
@@ -94,15 +99,16 @@ import org.eclipse.jetty.util.log.Logger;
  * <p>
  * Earlier versions of the handler used internet address prefix wildcard specification
  * to define a range of the internet addresses (e.g. 127., 10.10., 172.16.1.).
- * They also used the first "/" character of the URI pattern to separate it from the 
- * internet address. Both of these features have been deprecated in the current version. 
+ * They also used the first "/" character of the URI pattern to separate it from the
+ * internet address. Both of these features have been deprecated in the current version.
  */
 public class IPAccessHandler extends HandlerWrapper
 {
     private static final Logger LOG = Log.getLogger(IPAccessHandler.class);
-
-    IPAddressMap<PathMap> _white = new IPAddressMap<PathMap>();
-    IPAddressMap<PathMap> _black = new IPAddressMap<PathMap>();
+    // true means nodefault match
+    PathMap<IPAddressMap<Boolean>> _white = new PathMap<IPAddressMap<Boolean>>(true);
+    PathMap<IPAddressMap<Boolean>> _black = new PathMap<IPAddressMap<Boolean>>(true);
+    boolean _whiteListByPath = false;
 
     /* ------------------------------------------------------------ */
     /**
@@ -112,86 +118,97 @@ public class IPAccessHandler extends HandlerWrapper
     {
         super();
     }
-    
+
     /* ------------------------------------------------------------ */
     /**
      * Creates new handler object and initializes white- and black-list
-     * 
+     *
      * @param white array of whitelist entries
      * @param black array of blacklist entries
      */
     public IPAccessHandler(String[] white, String []black)
     {
         super();
-        
+
         if (white != null && white.length > 0)
             setWhite(white);
         if (black != null && black.length > 0)
             setBlack(black);
     }
-    
+
     /* ------------------------------------------------------------ */
     /**
      * Add a whitelist entry to an existing handler configuration
-     * 
+     *
      * @param entry new whitelist entry
      */
     public void addWhite(String entry)
     {
         add(entry, _white);
     }
-    
+
     /* ------------------------------------------------------------ */
     /**
      * Add a blacklist entry to an existing handler configuration
-     * 
+     *
      * @param entry new blacklist entry
      */
     public void addBlack(String entry)
     {
         add(entry, _black);
     }
-    
+
     /* ------------------------------------------------------------ */
     /**
      * Re-initialize the whitelist of existing handler object
-     * 
+     *
      * @param entries array of whitelist entries
      */
     public void setWhite(String[] entries)
     {
         set(entries, _white);
     }
-    
+
     /* ------------------------------------------------------------ */
     /**
      * Re-initialize the blacklist of existing handler object
-     * 
+     *
      * @param entries array of blacklist entries
      */
     public void setBlack(String[] entries)
     {
         set(entries, _black);
     }
-    
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Re-initialize the mode of path matching
+     *
+     * @param whiteListByPath matching mode
+     */
+    public void setWhiteListByPath(boolean whiteListByPath)
+    {
+        this._whiteListByPath = whiteListByPath;
+    }
+
     /* ------------------------------------------------------------ */
     /**
      * Checks the incoming request against the whitelist and blacklist
-     * 
+     *
      * @see org.eclipse.jetty.server.handler.HandlerWrapper#handle(java.lang.String, org.eclipse.jetty.server.Request, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
      */
     @Override
     public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
     {
         // Get the real remote IP (not the one set by the forwarded headers (which may be forged))
-        AbstractHttpConnection connection = baseRequest.getConnection();
-        if (connection!=null)
+        HttpChannel<?> channel = baseRequest.getHttpChannel();
+        if (channel!=null)
         {
-            EndPoint endp=connection.getEndPoint();
+            EndPoint endp=channel.getEndPoint();
             if (endp!=null)
             {
-                String addr = endp.getRemoteAddr();
-                if (addr!=null && !isAddrUriAllowed(addr,baseRequest.getPathInfo()))
+                InetSocketAddress address = endp.getRemoteAddress();
+                if (address!=null && !isAddrUriAllowed(address.getHostString(),baseRequest.getPathInfo()))
                 {
                     response.sendError(HttpStatus.FORBIDDEN_403);
                     baseRequest.setHandled(true);
@@ -199,20 +216,20 @@ public class IPAccessHandler extends HandlerWrapper
                 }
             }
         }
-        
+
         getHandler().handle(target,baseRequest, request, response);
     }
-    
+
 
     /* ------------------------------------------------------------ */
     /**
-     * Helper method to parse the new entry and add it to 
+     * Helper method to parse the new entry and add it to
      * the specified address pattern map.
-     * 
+     *
      * @param entry new entry
      * @param patternMap target address pattern map
      */
-    protected void add(String entry, IPAddressMap<PathMap> patternMap)
+    protected void add(String entry, PathMap<IPAddressMap<Boolean>> patternMap)
     {
         if (entry != null && entry.length() > 0)
         {
@@ -227,24 +244,25 @@ public class IPAccessHandler extends HandlerWrapper
                 idx = entry.indexOf('/');
                 deprecated = (idx >= 0);
             }
-            
-            String addr = idx > 0 ? entry.substring(0,idx) : entry;        
+
+            String addr = idx > 0 ? entry.substring(0,idx) : entry;
             String path = idx > 0 ? entry.substring(idx) : "/*";
-            
+
             if (addr.endsWith("."))
                 deprecated = true;
             if (path!=null && (path.startsWith("|") || path.startsWith("/*.")))
                 path=path.substring(1);
-           
-            PathMap pathMap = patternMap.get(addr);
-            if (pathMap == null)
+
+            IPAddressMap<Boolean> addrMap = patternMap.get(path);
+            if (addrMap == null)
             {
-                pathMap = new PathMap(true);
-                patternMap.put(addr,pathMap);
+                addrMap = new IPAddressMap<Boolean>();
+                patternMap.put(path,addrMap);
             }
-            if (path != null && !"".equals(path))
-                pathMap.put(path,path);
-            
+            if (addr != null && !"".equals(addr))
+                // MUST NOT BE null
+                addrMap.put(addr, true);
+
             if (deprecated)
                 LOG.debug(toString() +" - deprecated specification syntax: "+entry);
         }
@@ -252,16 +270,16 @@ public class IPAccessHandler extends HandlerWrapper
 
     /* ------------------------------------------------------------ */
     /**
-     * Helper method to process a list of new entries and replace 
+     * Helper method to process a list of new entries and replace
      * the content of the specified address pattern map
-     * 
+     *
      * @param entries new entries
      * @param patternMap target address pattern map
      */
-    protected void set(String[] entries,  IPAddressMap<PathMap> patternMap)
+    protected void set(String[] entries,  PathMap<IPAddressMap<Boolean>> patternMap)
     {
         patternMap.clear();
-        
+
         if (entries != null && entries.length > 0)
         {
             for (String addrPath:entries)
@@ -270,11 +288,11 @@ public class IPAccessHandler extends HandlerWrapper
             }
         }
     }
-    
+
     /* ------------------------------------------------------------ */
     /**
      * Check if specified request is allowed by current IPAccess rules.
-     * 
+     *
      * @param addr internet address
      * @param path context path
      * @return true if request is allowed
@@ -285,98 +303,83 @@ public class IPAccessHandler extends HandlerWrapper
         if (_white.size()>0)
         {
             boolean match = false;
-            
-            Object whiteObj = _white.getLazyMatches(addr);
-            if (whiteObj != null) 
-            {
-                List whiteList = (whiteObj instanceof List) ? (List)whiteObj : Collections.singletonList(whiteObj);
+            boolean matchedByPath = false;
 
-                for (Object entry: whiteList)
+            for (Map.Entry<String,IPAddressMap<Boolean>> entry : _white.getMatches(path))
+            {
+                matchedByPath=true;
+                IPAddressMap<Boolean> addrMap = entry.getValue();
+                if ((addrMap!=null && (addrMap.size()==0 || addrMap.match(addr)!=null)))
                 {
-                    PathMap pathMap = ((Map.Entry<String,PathMap>)entry).getValue();
-                    if (match = (pathMap!=null && (pathMap.size()==0 || pathMap.match(path)!=null)))
-                        break;
+                    match=true;
+                    break;
                 }
             }
             
-            if (!match)
-                return false;
+            if (_whiteListByPath)
+            {
+                if (matchedByPath && !match)
+                    return false;
+            }
+            else
+            {
+                if (!match)
+                    return false;
+            }
         }
 
         if (_black.size() > 0)
         {
-            Object blackObj = _black.getLazyMatches(addr);
-            if (blackObj != null) 
+            for (Map.Entry<String,IPAddressMap<Boolean>> entry : _black.getMatches(path))
             {
-                List blackList = (blackObj instanceof List) ? (List)blackObj : Collections.singletonList(blackObj);
-    
-                for (Object entry: blackList)
-                {
-                    PathMap pathMap = ((Map.Entry<String,PathMap>)entry).getValue();
-                    if (pathMap!=null && (pathMap.size()==0 || pathMap.match(path)!=null))
-                        return false;
-                }
+                IPAddressMap<Boolean> addrMap = entry.getValue();
+                if (addrMap!=null && (addrMap.size()==0 || addrMap.match(addr)!=null))
+                    return false;
             }
+            
         }
-        
+
         return true;
     }
 
     /* ------------------------------------------------------------ */
     /**
-     * Dump the white- and black-list configurations when started
-     * 
-     * @see org.eclipse.jetty.server.handler.HandlerWrapper#doStart()
-     */
-    @Override
-    protected void doStart()
-        throws Exception
-    {
-        super.doStart();
-        
-        if (LOG.isDebugEnabled())
-        {
-            System.err.println(dump());
-        }
-    }
-    
-    /* ------------------------------------------------------------ */
-    /**
      * Dump the handler configuration
      */
+    @Override
     public String dump()
     {
         StringBuilder buf = new StringBuilder();
-        
+
         buf.append(toString());
         buf.append(" WHITELIST:\n");
         dump(buf, _white);
         buf.append(toString());
         buf.append(" BLACKLIST:\n");
         dump(buf, _black);
-        
+
         return buf.toString();
-    }    
-    
+    }
+
     /* ------------------------------------------------------------ */
     /**
      * Dump a pattern map into a StringBuilder buffer
-     * 
+     *
      * @param buf buffer
      * @param patternMap pattern map to dump
      */
-    protected void dump(StringBuilder buf, IPAddressMap<PathMap> patternMap)
+    protected void dump(StringBuilder buf, PathMap<IPAddressMap<Boolean>> patternMap)
     {
-        for (String addr: patternMap.keySet())
+        for (String path: patternMap.keySet())
         {
-            for (Object path: ((PathMap)patternMap.get(addr)).values())
+            for (String addr: patternMap.get(path).keySet())
             {
                 buf.append("# ");
                 buf.append(addr);
                 buf.append("|");
                 buf.append(path);
                 buf.append("\n");
-            }       
+            }
         }
     }
  }
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/IdleTimeoutHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/IdleTimeoutHandler.java
index 6cacd14..fb52e50 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/IdleTimeoutHandler.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/IdleTimeoutHandler.java
@@ -27,13 +27,13 @@ import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
 import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.server.AbstractHttpConnection;
+import org.eclipse.jetty.server.HttpChannel;
+import org.eclipse.jetty.server.HttpConnection;
 import org.eclipse.jetty.server.Request;
 
 /**
  * Handler to adjust the idle timeout of requests while dispatched.
- * 
- * <p>Can be applied in jetty.xml with
+ * Can be applied in jetty.xml with
  * <pre>
  *   <Get id='handler' name='Handler'/>
  *   <Set name='Handler'>
@@ -46,10 +46,9 @@ import org.eclipse.jetty.server.Request;
  */
 public class IdleTimeoutHandler extends HandlerWrapper
 {
-    private int _idleTimeoutMs = 1000;
+    private long _idleTimeoutMs = 1000;
     private boolean _applyToAsync = false;
     
-    
     public boolean isApplyToAsync()
     {
         return _applyToAsync;
@@ -72,26 +71,18 @@ public class IdleTimeoutHandler extends HandlerWrapper
     /**
      * @param idleTimeoutMs The idle timeout in MS to apply while dispatched or async
      */
-    public void setIdleTimeoutMs(int _idleTimeoutMs)
+    public void setIdleTimeoutMs(long idleTimeoutMs)
     {
-        this._idleTimeoutMs = _idleTimeoutMs;
+        this._idleTimeoutMs = idleTimeoutMs;
     }
     
    
     @Override
     public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
     {
-        AbstractHttpConnection connection = AbstractHttpConnection.getCurrentConnection();
-        final EndPoint endp = connection==null?null:connection.getEndPoint();
-        
-        final int idle_timeout;
-        if (endp==null)
-            idle_timeout=-1;
-        else
-        {
-            idle_timeout=endp.getMaxIdleTime();
-            endp.setMaxIdleTime(_idleTimeoutMs);
-        }
+        final HttpChannel<?> channel = baseRequest.getHttpChannel();
+        final long idle_timeout=baseRequest.getHttpChannel().getIdleTimeout();
+        channel.setIdleTimeout(_idleTimeoutMs);
         
         try
         {
@@ -99,8 +90,6 @@ public class IdleTimeoutHandler extends HandlerWrapper
         }
         finally
         {
-            if (endp!=null)
-            {
                 if (_applyToAsync && request.isAsyncStarted())
                 {
                     request.getAsyncContext().addListener(new AsyncListener()
@@ -118,21 +107,18 @@ public class IdleTimeoutHandler extends HandlerWrapper
                         @Override
                         public void onError(AsyncEvent event) throws IOException
                         {
-                            endp.setMaxIdleTime(idle_timeout);
+                            channel.setIdleTimeout(idle_timeout);
                         }
                         
                         @Override
                         public void onComplete(AsyncEvent event) throws IOException
                         {
-                            endp.setMaxIdleTime(idle_timeout);
+                            channel.setIdleTimeout(idle_timeout);
                         }
                     });
                 }
                 else 
-                {
-                    endp.setMaxIdleTime(idle_timeout);
-                }
-            }
+                    channel.setIdleTimeout(idle_timeout);
         }
     }
 }
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/MovedContextHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/MovedContextHandler.java
index 81716a5..fd0ca7b 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/MovedContextHandler.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/MovedContextHandler.java
@@ -24,7 +24,7 @@ import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import org.eclipse.jetty.http.HttpHeaders;
+import org.eclipse.jetty.http.HttpHeader;
 import org.eclipse.jetty.server.HandlerContainer;
 import org.eclipse.jetty.server.Request;
 import org.eclipse.jetty.util.URIUtil;
@@ -33,7 +33,7 @@ import org.eclipse.jetty.util.URIUtil;
 /** Moved ContextHandler.
  * This context can be used to replace a context that has changed
  * location.  Requests are redirected (either to a fixed URL or to a
- * new context base). 
+ * new context base).
  */
 public class MovedContextHandler extends ContextHandler
 {
@@ -50,7 +50,7 @@ public class MovedContextHandler extends ContextHandler
         setHandler(_redirector);
         setAllowNullPathInfo(true);
     }
-    
+
     public MovedContextHandler(HandlerContainer parent, String contextPath, String newContextURL)
     {
         super(parent,contextPath);
@@ -98,7 +98,7 @@ public class MovedContextHandler extends ContextHandler
     {
         _discardQuery = discardQuery;
     }
-    
+
     private class Redirector extends AbstractHandler
     {
         public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
@@ -109,7 +109,7 @@ public class MovedContextHandler extends ContextHandler
             String path=_newContextURL;
             if (!_discardPathInfo && request.getPathInfo()!=null)
                 path=URIUtil.addPaths(path, request.getPathInfo());
-            
+
             StringBuilder location = URIUtil.hasScheme(path)?new StringBuilder():baseRequest.getRootURL();
 
             location.append(path);
@@ -120,17 +120,17 @@ public class MovedContextHandler extends ContextHandler
                 q=q.replaceAll("\r\n?&=","!");
                 location.append(q);
             }
-            
-            response.setHeader(HttpHeaders.LOCATION,location.toString());
+
+            response.setHeader(HttpHeader.LOCATION.asString(),location.toString());
 
             if (_expires!=null)
-                response.setHeader(HttpHeaders.EXPIRES,_expires);
-            
+                response.setHeader(HttpHeader.EXPIRES.asString(),_expires);
+
             response.setStatus(_permanent?HttpServletResponse.SC_MOVED_PERMANENTLY:HttpServletResponse.SC_FOUND);
             response.setContentLength(0);
             baseRequest.setHandled(true);
         }
-        
+
     }
 
     /* ------------------------------------------------------------ */
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ProxyHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ProxyHandler.java
deleted file mode 100644
index 3b1f6c6..0000000
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ProxyHandler.java
+++ /dev/null
@@ -1,51 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.server.handler;
-
-import org.eclipse.jetty.server.Handler;
-
-
-/* ------------------------------------------------------------ */
-/** ProxyHandler.
- * <p>This class has been renamed to ConnectHandler, as it only implements
- * the CONNECT method (and a ProxyServlet must be used for full proxy handling).
- * @deprecated Use {@link ConnectHandler}
- */
-public class ProxyHandler extends ConnectHandler
-{
-    public ProxyHandler()
-    {
-        super();
-    }
-
-    public ProxyHandler(Handler handler, String[] white, String[] black)
-    {
-        super(handler,white,black);
-    }
-
-    public ProxyHandler(Handler handler)
-    {
-        super(handler);
-    }
-
-    public ProxyHandler(String[] white, String[] black)
-    {
-        super(white,black);
-    }
-}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/RequestLogHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/RequestLogHandler.java
index 91900a3..7c8f4b5 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/RequestLogHandler.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/RequestLogHandler.java
@@ -16,77 +16,98 @@
 //  ========================================================================
 //
 
-package org.eclipse.jetty.server.handler; 
+package org.eclipse.jetty.server.handler;
 
 import java.io.IOException;
 
+import javax.servlet.AsyncEvent;
+import javax.servlet.AsyncListener;
 import javax.servlet.DispatcherType;
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import org.eclipse.jetty.continuation.Continuation;
-import org.eclipse.jetty.continuation.ContinuationListener;
-import org.eclipse.jetty.server.AsyncContinuation;
+import org.eclipse.jetty.server.AsyncContextState;
 import org.eclipse.jetty.server.Request;
 import org.eclipse.jetty.server.RequestLog;
 import org.eclipse.jetty.server.Response;
-import org.eclipse.jetty.server.Server;
 import org.eclipse.jetty.util.component.AbstractLifeCycle;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
 
 
-/** 
+
+/**
  * RequestLogHandler.
  * This handler can be used to wrap an individual context for context logging.
- * 
+ *
+ *
  * @org.apache.xbean.XBean
  */
 public class RequestLogHandler extends HandlerWrapper
 {
     private static final Logger LOG = Log.getLogger(RequestLogHandler.class);
-
     private RequestLog _requestLog;
-    
+    private final AsyncListener _listener = new AsyncListener()
+    {
+        
+        @Override
+        public void onTimeout(AsyncEvent event) throws IOException
+        {
+            
+        }
+        
+        @Override
+        public void onStartAsync(AsyncEvent event) throws IOException
+        {
+            event.getAsyncContext().addListener(this);
+        }
+        
+        @Override
+        public void onError(AsyncEvent event) throws IOException
+        {
+            HttpServletResponse response = (HttpServletResponse)event.getAsyncContext().getResponse();
+            if (!response.isCommitted())
+                response.setStatus(500);
+            
+        }
+        
+        @Override
+        public void onComplete(AsyncEvent event) throws IOException
+        {
+            AsyncContextState context = (AsyncContextState)event.getAsyncContext();
+            Request request=context.getHttpChannelState().getBaseRequest();
+            Response response=request.getResponse();
+            _requestLog.log(request,response);
+        }
+    };
+
     /* ------------------------------------------------------------ */
-    /* 
+    /*
      * @see org.eclipse.jetty.server.server.Handler#handle(java.lang.String, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
      */
     @Override
-    public void handle(String target, final Request baseRequest, HttpServletRequest request, final HttpServletResponse response)
+    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
             throws IOException, ServletException
     {
-        AsyncContinuation continuation = baseRequest.getAsyncContinuation();
-        if (!continuation.isInitial())
-        {
-            baseRequest.setDispatchTime(System.currentTimeMillis());
-        }
-        
         try
         {
             super.handle(target, baseRequest, request, response);
         }
+        catch(Error|IOException|ServletException|RuntimeException e)
+        {
+            if (!response.isCommitted() && !baseRequest.getHttpChannelState().isAsync())
+                response.setStatus(500);
+            throw e;
+        }
         finally
         {
             if (_requestLog != null && baseRequest.getDispatcherType().equals(DispatcherType.REQUEST))
             {
-                if (continuation.isAsync())
+                if (baseRequest.getHttpChannelState().isAsync())
                 {
-                    if (continuation.isInitial())
-                        continuation.addContinuationListener(new ContinuationListener()
-                        {
-
-                            public void onTimeout(Continuation continuation)
-                            {
-
-                            }
-
-                            public void onComplete(Continuation continuation)
-                            {
-                                _requestLog.log(baseRequest, (Response)response);
-                            }
-                        });
+                    if (baseRequest.getHttpChannelState().isInitial())
+                        baseRequest.getAsyncContext().addListener(_listener);
                 }
                 else
                     _requestLog.log(baseRequest, (Response)response);
@@ -97,63 +118,17 @@ public class RequestLogHandler extends HandlerWrapper
     /* ------------------------------------------------------------ */
     public void setRequestLog(RequestLog requestLog)
     {
-        //are we changing the request log impl?
-        try
-        {
-            if (_requestLog != null)
-                _requestLog.stop();
-        }
-        catch (Exception e)
-        {
-            LOG.warn (e);
-        }
-        
-        if (getServer()!=null)
-            getServer().getContainer().update(this, _requestLog, requestLog, "logimpl",true);
-        
-        _requestLog = requestLog;
-        
-        //if we're already started, then start our request log
-        try
-        {
-            if (isStarted() && (_requestLog != null))
-                _requestLog.start();
-        }
-        catch (Exception e)
-        {
-            throw new RuntimeException (e);
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    /* 
-     * @see org.eclipse.jetty.server.server.handler.HandlerWrapper#setServer(org.eclipse.jetty.server.server.Server)
-     */
-    @Override
-    public void setServer(Server server)
-    {
-        if (_requestLog!=null)
-        {
-            if (getServer()!=null && getServer()!=server)
-                getServer().getContainer().update(this, _requestLog, null, "logimpl",true);
-            super.setServer(server);
-            if (server!=null && server!=getServer())
-                server.getContainer().update(this, null,_requestLog, "logimpl",true);
-        }
-        else
-            super.setServer(server);
+        updateBean(_requestLog,requestLog);
+        _requestLog=requestLog;
     }
 
     /* ------------------------------------------------------------ */
-    public RequestLog getRequestLog() 
+    public RequestLog getRequestLog()
     {
         return _requestLog;
     }
-
+    
     /* ------------------------------------------------------------ */
-    /* 
-     * @see org.eclipse.jetty.server.server.handler.HandlerWrapper#doStart()
-     */
     @Override
     protected void doStart() throws Exception
     {
@@ -163,18 +138,13 @@ public class RequestLogHandler extends HandlerWrapper
             _requestLog=new NullRequestLog();
         }
         super.doStart();
-        _requestLog.start();
     }
-
+    
     /* ------------------------------------------------------------ */
-    /* 
-     * @see org.eclipse.jetty.server.server.handler.HandlerWrapper#doStop()
-     */
     @Override
     protected void doStop() throws Exception
     {
         super.doStop();
-        _requestLog.stop();
         if (_requestLog instanceof NullRequestLog)
             _requestLog=null;
     }
@@ -184,9 +154,9 @@ public class RequestLogHandler extends HandlerWrapper
     /* ------------------------------------------------------------ */
     private static class NullRequestLog extends AbstractLifeCycle implements RequestLog
     {
+        @Override
         public void log(Request request, Response response)
         {            
         }
     }
-    
 }
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ResourceHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ResourceHandler.java
index e12545f..3e7e5a0 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ResourceHandler.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ResourceHandler.java
@@ -21,24 +21,27 @@ package org.eclipse.jetty.server.handler;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.net.MalformedURLException;
+import java.nio.ByteBuffer;
+import java.nio.channels.ReadableByteChannel;
 
+import javax.servlet.AsyncContext;
+import javax.servlet.RequestDispatcher;
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
 import org.eclipse.jetty.http.HttpFields;
-import org.eclipse.jetty.http.HttpHeaders;
-import org.eclipse.jetty.http.HttpMethods;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpMethod;
 import org.eclipse.jetty.http.HttpStatus;
 import org.eclipse.jetty.http.MimeTypes;
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.ByteArrayBuffer;
 import org.eclipse.jetty.io.WriterOutputStream;
-import org.eclipse.jetty.server.AbstractHttpConnection;
-import org.eclipse.jetty.server.Dispatcher;
+import org.eclipse.jetty.server.HttpOutput;
 import org.eclipse.jetty.server.Request;
 import org.eclipse.jetty.server.Response;
 import org.eclipse.jetty.server.handler.ContextHandler.Context;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
 import org.eclipse.jetty.util.URIUtil;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
@@ -66,15 +69,16 @@ public class ResourceHandler extends HandlerWrapper
     Resource _stylesheet;
     String[] _welcomeFiles={"index.html"};
     MimeTypes _mimeTypes = new MimeTypes();
-    ByteArrayBuffer _cacheControl;
-    boolean _aliases;
+    String _cacheControl;
     boolean _directory;
     boolean _etags;
+    int _minMemoryMappedContentLength=1024;
+    int _minAsyncContentLength=0;
 
     /* ------------------------------------------------------------ */
     public ResourceHandler()
     {
-    	
+
     }
 
     /* ------------------------------------------------------------ */
@@ -90,43 +94,65 @@ public class ResourceHandler extends HandlerWrapper
     }
 
     /* ------------------------------------------------------------ */
-    /**
-     * @return True if resource aliases are allowed.
+    /** Get the directory option.
+     * @return true if directories are listed.
      */
-    public boolean isAliases()
+    public boolean isDirectoriesListed()
     {
-        return _aliases;
+        return _directory;
     }
 
     /* ------------------------------------------------------------ */
-    /**
-     * Set if resource aliases (eg symlink, 8.3 names, case insensitivity) are allowed.
-     * Allowing aliases can significantly increase security vulnerabilities.
-     * If this handler is deployed inside a ContextHandler, then the
-     * {@link ContextHandler#isAliases()} takes precedent.
-     * @param aliases True if aliases are supported.
+    /** Set the directory.
+     * @param directory true if directories are listed.
      */
-    public void setAliases(boolean aliases)
+    public void setDirectoriesListed(boolean directory)
     {
-        _aliases = aliases;
+        _directory = directory;
     }
 
     /* ------------------------------------------------------------ */
-    /** Get the directory option.
-     * @return true if directories are listed.
+    /** Get minimum memory mapped file content length.
+     * @return the minimum size in bytes of a file resource that will
+     * be served using a memory mapped buffer, or -1 (default) for no memory mapped
+     * buffers.
      */
-    public boolean isDirectoriesListed()
+    public int getMinMemoryMappedContentLength()
     {
-        return _directory;
+        return _minMemoryMappedContentLength;
     }
 
     /* ------------------------------------------------------------ */
-    /** Set the directory.
-     * @param directory true if directories are listed.
+    /** Set minimum memory mapped file content length.
+     * @param minMemoryMappedFileSize the minimum size in bytes of a file resource that will
+     * be served using a memory mapped buffer, or -1 for no memory mapped
+     * buffers.
      */
-    public void setDirectoriesListed(boolean directory)
+    public void setMinMemoryMappedContentLength(int minMemoryMappedFileSize)
     {
-        _directory = directory;
+        _minMemoryMappedContentLength = minMemoryMappedFileSize;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Get the minimum content length for async handling.
+     * @return The minimum size in bytes of the content before asynchronous 
+     * handling is used, or -1 for no async handling or 0 (default) for using
+     * {@link HttpServletResponse#getBufferSize()} as the minimum length.
+     */
+    public int getMinAsyncContentLength()
+    {
+        return _minAsyncContentLength;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the minimum content length for async handling.
+     * @param minAsyncContentLength The minimum size in bytes of the content before asynchronous 
+     * handling is used, or -1 for no async handling or 0 for using
+     * {@link HttpServletResponse#getBufferSize()} as the minimum length.
+     */
+    public void setMinAsyncContentLength(int minAsyncContentLength)
+    {
+        _minAsyncContentLength = minAsyncContentLength;
     }
 
     /* ------------------------------------------------------------ */
@@ -155,12 +181,6 @@ public class ResourceHandler extends HandlerWrapper
         Context scontext = ContextHandler.getCurrentContext();
         _context = (scontext==null?null:scontext.getContextHandler());
 
-        if (_context!=null)
-            _aliases=_context.isAliases();
-
-        if (!_aliases && !FileResource.getCheckAliases())
-            throw new IllegalStateException("Alias checking disabled");
-
         super.doStart();
     }
 
@@ -213,35 +233,27 @@ public class ResourceHandler extends HandlerWrapper
             throw new IllegalArgumentException(resourceBase);
         }
     }
-    
+
     /* ------------------------------------------------------------ */
     /**
      * @return Returns the stylesheet as a Resource.
      */
     public Resource getStylesheet()
     {
-    	if(_stylesheet != null)
-    	{
-    	    return _stylesheet;
-    	}
-    	else
-    	{
-    	    if(_defaultStylesheet == null)
-    	    {
-    	        try
-    	        {
-    	            _defaultStylesheet =  Resource.newResource(this.getClass().getResource("/jetty-dir.css"));
-    	        }
-    	        catch(IOException e)
-    	        {
-    	            LOG.warn(e.toString());
-    	            LOG.debug(e);
-    	        }	 
-    	    }
-    	    return _defaultStylesheet;
-    	}
+        if(_stylesheet != null)
+        {
+            return _stylesheet;
+        }
+        else
+        {
+            if(_defaultStylesheet == null)
+            {
+                _defaultStylesheet =  Resource.newResource(this.getClass().getResource("/jetty-dir.css"));
+            }
+            return _defaultStylesheet;
+        }
     }
-    
+
     /* ------------------------------------------------------------ */
     /**
      * @param stylesheet The location of the stylesheet to be used as a String.
@@ -257,12 +269,12 @@ public class ResourceHandler extends HandlerWrapper
                 _stylesheet = null;
             }
         }
-    	catch(Exception e)
-    	{
-    		LOG.warn(e.toString());
+        catch(Exception e)
+        {
+            LOG.warn(e.toString());
             LOG.debug(e);
-            throw new IllegalArgumentException(stylesheet.toString());
-    	}
+            throw new IllegalArgumentException(stylesheet);
+        }
     }
 
     /* ------------------------------------------------------------ */
@@ -271,7 +283,7 @@ public class ResourceHandler extends HandlerWrapper
      */
     public String getCacheControl()
     {
-        return _cacheControl.toString();
+        return _cacheControl;
     }
 
     /* ------------------------------------------------------------ */
@@ -280,7 +292,7 @@ public class ResourceHandler extends HandlerWrapper
      */
     public void setCacheControl(String cacheControl)
     {
-        _cacheControl=cacheControl==null?null:new ByteArrayBuffer(cacheControl);
+        _cacheControl=cacheControl;
     }
 
     /* ------------------------------------------------------------ */
@@ -291,20 +303,29 @@ public class ResourceHandler extends HandlerWrapper
         if (path==null || !path.startsWith("/"))
             throw new MalformedURLException(path);
 
+        if (LOG.isDebugEnabled())
+            LOG.debug("{} getResource({})",_context==null?_baseResource:_context,_baseResource,path);
+        
         Resource base = _baseResource;
         if (base==null)
         {
             if (_context==null)
                 return null;
-            base=_context.getBaseResource();
-            if (base==null)
-                return null;
+            return _context.getResource(path);
         }
 
         try
         {
             path=URIUtil.canonicalPath(path);
-            return base.addPath(path);
+            Resource r = base.addPath(path);
+            
+            if (r!=null && r.getAlias()!=null && (_context==null || !_context.checkAlias(path, r)))
+            {
+                if (LOG.isDebugEnabled())
+                    LOG.debug("resource={} alias={}",r,r.getAlias());
+                return null;
+            }
+            return r;
         }
         catch(Exception e)
         {
@@ -319,12 +340,12 @@ public class ResourceHandler extends HandlerWrapper
     {
         String servletPath;
         String pathInfo;
-        Boolean included = request.getAttribute(Dispatcher.INCLUDE_REQUEST_URI) != null;
+        Boolean included = request.getAttribute(RequestDispatcher.INCLUDE_REQUEST_URI) != null;
         if (included != null && included.booleanValue())
         {
-            servletPath = (String)request.getAttribute(Dispatcher.INCLUDE_SERVLET_PATH);
-            pathInfo = (String)request.getAttribute(Dispatcher.INCLUDE_PATH_INFO);
- 
+            servletPath = (String)request.getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH);
+            pathInfo = (String)request.getAttribute(RequestDispatcher.INCLUDE_PATH_INFO);
+
             if (servletPath == null && pathInfo == null)
             {
                 servletPath = request.getServletPath();
@@ -336,7 +357,7 @@ public class ResourceHandler extends HandlerWrapper
             servletPath = request.getServletPath();
             pathInfo = request.getPathInfo();
         }
-        
+
         String pathInContext=URIUtil.addPaths(servletPath,pathInfo);
         return getResource(pathInContext);
     }
@@ -371,6 +392,7 @@ public class ResourceHandler extends HandlerWrapper
     /*
      * @see org.eclipse.jetty.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
      */
+    @Override
     public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
     {
         if (baseRequest.isHandled())
@@ -378,9 +400,9 @@ public class ResourceHandler extends HandlerWrapper
 
         boolean skipContentBody = false;
 
-        if(!HttpMethods.GET.equals(request.getMethod()))
+        if(!HttpMethod.GET.is(request.getMethod()))
         {
-            if(!HttpMethods.HEAD.equals(request.getMethod()))
+            if(!HttpMethod.HEAD.is(request.getMethod()))
             {
                 //try another handler
                 super.handle(target, baseRequest, request, response);
@@ -388,38 +410,46 @@ public class ResourceHandler extends HandlerWrapper
             }
             skipContentBody = true;
         }
-        
+
         Resource resource = getResource(request);
         
+        if (LOG.isDebugEnabled())
+        { 
+            if (resource==null)
+                LOG.debug("resource=null");
+            else
+                LOG.debug("resource={} alias={} exists={}",resource,resource.getAlias(),resource.exists());
+        }
+        
+        
+        // If resource is not found
         if (resource==null || !resource.exists())
         {
+            // inject the jetty-dir.css file if it matches
             if (target.endsWith("/jetty-dir.css"))
-            {	                
+            {
                 resource = getStylesheet();
                 if (resource==null)
                     return;
                 response.setContentType("text/css");
             }
-            else 
+            else
             {
                 //no resource - try other handlers
                 super.handle(target, baseRequest, request, response);
                 return;
             }
         }
-            
-        if (!_aliases && resource.getAlias()!=null)
-        {
-            LOG.info(resource+" aliased to "+resource.getAlias());
-            return;
-        }
 
         // We are going to serve something
         baseRequest.setHandled(true);
 
+        // handle directories
         if (resource.isDirectory())
         {
-            if (!request.getPathInfo().endsWith(URIUtil.SLASH))
+            String pathInfo = request.getPathInfo();
+            boolean endsWithSlash=(pathInfo==null?request.getServletPath():pathInfo).endsWith(URIUtil.SLASH);
+            if (!endsWithSlash)
             {
                 response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(request.getRequestURI(),URIUtil.SLASH)));
                 return;
@@ -436,26 +466,26 @@ public class ResourceHandler extends HandlerWrapper
             }
         }
 
-        // set some headers
+        // Handle ETAGS
         long last_modified=resource.lastModified();
         String etag=null;
         if (_etags)
         {
             // simple handling of only a single etag
-            String ifnm = request.getHeader(HttpHeaders.IF_NONE_MATCH);
+            String ifnm = request.getHeader(HttpHeader.IF_NONE_MATCH.asString());
             etag=resource.getWeakETag();
             if (ifnm!=null && resource!=null && ifnm.equals(etag))
             {
                 response.setStatus(HttpStatus.NOT_MODIFIED_304);
-                baseRequest.getResponse().getHttpFields().put(HttpHeaders.ETAG_BUFFER,etag);
+                baseRequest.getResponse().getHttpFields().put(HttpHeader.ETAG,etag);
                 return;
             }
         }
         
-        
+        // Handle if modified since 
         if (last_modified>0)
         {
-            long if_modified=request.getDateHeader(HttpHeaders.IF_MODIFIED_SINCE);
+            long if_modified=request.getDateHeader(HttpHeader.IF_MODIFIED_SINCE.asString());
             if (if_modified>0 && last_modified/1000<=if_modified/1000)
             {
                 response.setStatus(HttpStatus.NOT_MODIFIED_304);
@@ -463,33 +493,93 @@ public class ResourceHandler extends HandlerWrapper
             }
         }
 
-        Buffer mime=_mimeTypes.getMimeByExtension(resource.toString());
+        // set the headers
+        String mime=_mimeTypes.getMimeByExtension(resource.toString());
         if (mime==null)
             mime=_mimeTypes.getMimeByExtension(request.getPathInfo());
-
-        // set the headers
-        doResponseHeaders(response,resource,mime!=null?mime.toString():null);
-        response.setDateHeader(HttpHeaders.LAST_MODIFIED,last_modified);
+        doResponseHeaders(response,resource,mime);
         if (_etags)
-            baseRequest.getResponse().getHttpFields().put(HttpHeaders.ETAG_BUFFER,etag);
+            baseRequest.getResponse().getHttpFields().put(HttpHeader.ETAG,etag);
         
         if(skipContentBody)
             return;
+        
+        
         // Send the content
         OutputStream out =null;
         try {out = response.getOutputStream();}
         catch(IllegalStateException e) {out = new WriterOutputStream(response.getWriter());}
 
-        // See if a short direct method can be used?
-        if (out instanceof AbstractHttpConnection.Output)
-        {
-            // TODO file mapped buffers
-            ((AbstractHttpConnection.Output)out).sendContent(resource.getInputStream());
-        }
+        // Has the output been wrapped
+        if (!(out instanceof HttpOutput))
+            // Write content via wrapped output
+            resource.writeTo(out,0,resource.length());
         else
         {
-            // Write content normally
-            resource.writeTo(out,0,resource.length());
+            // select async by size
+            int min_async_size=_minAsyncContentLength==0?response.getBufferSize():_minAsyncContentLength;
+            
+            if (request.isAsyncSupported() && 
+                min_async_size>0 &&
+                resource.length()>=min_async_size)
+            {
+                final AsyncContext async = request.startAsync();
+                async.setTimeout(0);
+                Callback callback = new Callback()
+                {
+                    @Override
+                    public void succeeded()
+                    {
+                        async.complete();
+                    }
+
+                    @Override
+                    public void failed(Throwable x)
+                    {
+                        LOG.warn(x.toString());
+                        LOG.debug(x);
+                        async.complete();
+                    }   
+                };
+
+                // Can we use a memory mapped file?
+                if (_minMemoryMappedContentLength>0 && 
+                    resource.length()>_minMemoryMappedContentLength &&
+                    resource.length()<Integer.MAX_VALUE &&
+                    resource instanceof FileResource)
+                {
+                    ByteBuffer buffer = BufferUtil.toMappedBuffer(resource.getFile());
+                    ((HttpOutput)out).sendContent(buffer,callback);
+                }
+                else  // Do a blocking write of a channel (if available) or input stream
+                {
+                    // Close of the channel/inputstream is done by the async sendContent
+                    ReadableByteChannel channel= resource.getReadableByteChannel();
+                    if (channel!=null)
+                        ((HttpOutput)out).sendContent(channel,callback);
+                    else
+                        ((HttpOutput)out).sendContent(resource.getInputStream(),callback);
+                }
+            }
+            else
+            {
+                // Can we use a memory mapped file?
+                if (_minMemoryMappedContentLength>0 && 
+                    resource.length()>_minMemoryMappedContentLength &&
+                    resource instanceof FileResource)
+                {
+                    ByteBuffer buffer = BufferUtil.toMappedBuffer(resource.getFile());
+                    ((HttpOutput)out).sendContent(buffer);
+                }
+                else  // Do a blocking write of a channel (if available) or input stream
+                {
+                    ReadableByteChannel channel= resource.getReadableByteChannel();
+                    if (channel!=null)
+                        ((HttpOutput)out).sendContent(channel);
+                    else
+                        ((HttpOutput)out).sendContent(resource.getInputStream());
+                }
+            }
         }
     }
 
@@ -527,19 +617,20 @@ public class ResourceHandler extends HandlerWrapper
             HttpFields fields = ((Response)response).getHttpFields();
 
             if (length>0)
-                fields.putLongField(HttpHeaders.CONTENT_LENGTH_BUFFER,length);
+                ((Response)response).setLongContentLength(length);
 
             if (_cacheControl!=null)
-                fields.put(HttpHeaders.CACHE_CONTROL_BUFFER,_cacheControl);
+                fields.put(HttpHeader.CACHE_CONTROL,_cacheControl);
         }
         else
         {
-            if (length>0)
-                response.setHeader(HttpHeaders.CONTENT_LENGTH,Long.toString(length));
+            if (length>Integer.MAX_VALUE)
+                response.setHeader(HttpHeader.CONTENT_LENGTH.asString(),Long.toString(length));
+            else if (length>0)
+                response.setContentLength((int)length);
 
             if (_cacheControl!=null)
-                response.setHeader(HttpHeaders.CACHE_CONTROL,_cacheControl.toString());
+                response.setHeader(HttpHeader.CACHE_CONTROL.asString(),_cacheControl);
         }
-
     }
 }
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ScopedHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ScopedHandler.java
index ed277ef..dc1b88f 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ScopedHandler.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ScopedHandler.java
@@ -29,28 +29,32 @@ import org.eclipse.jetty.server.Request;
 
 /* ------------------------------------------------------------ */
 /** ScopedHandler.
- * 
+ *
  * A ScopedHandler is a HandlerWrapper where the wrapped handlers
- * each define a scope.   When {@link #handle(String, Request, HttpServletRequest, HttpServletResponse)}
- * is called on the first ScopedHandler in a chain of HandlerWrappers,
- * the {@link #doScope(String, Request, HttpServletRequest, HttpServletResponse)} method is 
- * called on all contained ScopedHandlers, before the 
- * {@link #doHandle(String, Request, HttpServletRequest, HttpServletResponse)} method 
- * is called on all contained handlers.
+ * each define a scope.   
  * 
- * <p>For example if Scoped handlers A, B & C were chained together, then 
- * the calling order would be:<pre>
+ * <p>When {@link #handle(String, Request, HttpServletRequest, HttpServletResponse)}
+ * is called on the first ScopedHandler in a chain of HandlerWrappers,
+ * the {@link #doScope(String, Request, HttpServletRequest, HttpServletResponse)} method is
+ * called on all contained ScopedHandlers, before the
+ * {@link #doHandle(String, Request, HttpServletRequest, HttpServletResponse)} method
+ * is called on all contained handlers.</p>
+ *
+ * <p>For example if Scoped handlers A, B & C were chained together, then
+ * the calling order would be:</p>
+ * <pre>
  * A.handle(...)
  *   A.doScope(...)
  *     B.doScope(...)
  *       C.doScope(...)
  *         A.doHandle(...)
  *           B.doHandle(...)
- *              C.doHandle(...)   
+ *              C.doHandle(...)
+ * </pre>
+ *
+ * <p>If non scoped handler X was in the chained A, B, X & C, then
+ * the calling order would be:</p>
  * <pre>
- * 
- * <p>If non scoped handler X was in the chained A, B, X & C, then 
- * the calling order would be:<pre>
  * A.handle(...)
  *   A.doScope(...)
  *     B.doScope(...)
@@ -59,10 +63,11 @@ import org.eclipse.jetty.server.Request;
  *           B.doHandle(...)
  *             X.handle(...)
  *               C.handle(...)
- *                 C.doHandle(...)   
+ *                 C.doHandle(...)
+ * </pre>
+ *
+ * <p>A typical usage pattern is:</p>
  * <pre>
- * 
- * <p>A typical usage pattern is:<pre>
  *     private static class MyHandler extends ScopedHandler
  *     {
  *         public void doScope(String target, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
@@ -77,7 +82,7 @@ import org.eclipse.jetty.server.Request;
  *                 tearDownMyScope();
  *             }
  *         }
- *         
+ *
  *         public void doHandle(String target, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
  *         {
  *             try
@@ -98,7 +103,7 @@ public abstract class ScopedHandler extends HandlerWrapper
     private static final ThreadLocal<ScopedHandler> __outerScope= new ThreadLocal<ScopedHandler>();
     protected ScopedHandler _outerScope;
     protected ScopedHandler _nextScope;
-    
+
     /* ------------------------------------------------------------ */
     /**
      * @see org.eclipse.jetty.server.handler.HandlerWrapper#doStart()
@@ -111,11 +116,11 @@ public abstract class ScopedHandler extends HandlerWrapper
             _outerScope=__outerScope.get();
             if (_outerScope==null)
                 __outerScope.set(this);
-            
+
             super.doStart();
-            
-            _nextScope= (ScopedHandler)getChildHandlerByClass(ScopedHandler.class);
-            
+
+            _nextScope= getChildHandlerByClass(ScopedHandler.class);
+
         }
         finally
         {
@@ -124,31 +129,33 @@ public abstract class ScopedHandler extends HandlerWrapper
         }
     }
 
-
     /* ------------------------------------------------------------ */
-    /* 
+    /*
      */
     @Override
     public final void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
     {
-        if (_outerScope==null)  
-            doScope(target,baseRequest,request, response);
-        else 
-            doHandle(target,baseRequest,request, response);
+        if (isStarted())
+        {
+            if (_outerScope==null)
+                doScope(target,baseRequest,request, response);
+            else
+                doHandle(target,baseRequest,request, response);
+        }
     }
-    
+
     /* ------------------------------------------------------------ */
-    /* 
+    /*
      * Scope the handler
      */
-    public abstract void doScope(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) 
+    public abstract void doScope(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
         throws IOException, ServletException;
-    
+
     /* ------------------------------------------------------------ */
-    /* 
+    /*
      * Scope the handler
      */
-    public final void nextScope(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) 
+    public final void nextScope(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
         throws IOException, ServletException
     {
         // this method has been manually inlined in several locations, but
@@ -158,19 +165,19 @@ public abstract class ScopedHandler extends HandlerWrapper
             _nextScope.doScope(target,baseRequest,request, response);
         else if (_outerScope!=null)
             _outerScope.doHandle(target,baseRequest,request, response);
-        else 
+        else
             doHandle(target,baseRequest,request, response);
     }
 
     /* ------------------------------------------------------------ */
-    /* 
+    /*
      * Do the handler work within the scope.
      */
-    public abstract void doHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) 
+    public abstract void doHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
         throws IOException, ServletException;
-    
+
     /* ------------------------------------------------------------ */
-    /* 
+    /*
      * Do the handler work within the scope.
      */
     public final void nextHandle(String target, final Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
@@ -183,11 +190,11 @@ public abstract class ScopedHandler extends HandlerWrapper
         else if (_handler!=null)
             _handler.handle(target,baseRequest, request, response);
     }
-    
+
     /* ------------------------------------------------------------ */
     protected boolean never()
     {
         return false;
     }
-    
+
 }
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/SecuredRedirectHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/SecuredRedirectHandler.java
new file mode 100644
index 0000000..57a6957
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/SecuredRedirectHandler.java
@@ -0,0 +1,76 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.handler;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.server.HttpChannel;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.HttpConnection;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.util.URIUtil;
+
+/**
+ * Secured Redirect Handler
+ * <p>
+ * Using information present in the {@link HttpConfiguration}, will attempt to redirect to the {@link HttpConfiguration#getSecureScheme()} and
+ * {@link HttpConfiguration#getSecurePort()} for any request that {@link HttpServletRequest#isSecure()} == false.
+ */
+public class SecuredRedirectHandler extends AbstractHandler
+{
+    @Override
+    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+    {
+        HttpChannel<?> channel = baseRequest.getHttpChannel();
+        if (baseRequest.isSecure() || (channel == null))
+        {
+            // nothing to do
+            return;
+        }
+
+        HttpConfiguration httpConfig = channel.getHttpConfiguration();
+        if (httpConfig == null)
+        {
+            // no config, show error
+            response.sendError(HttpStatus.FORBIDDEN_403,"No http configuration available");
+            return;
+        }
+
+        if (httpConfig.getSecurePort() > 0)
+        {
+            String scheme = httpConfig.getSecureScheme();
+            int port = httpConfig.getSecurePort();
+
+            String url = URIUtil.newURI(scheme,baseRequest.getServerName(),port,baseRequest.getRequestURI(),baseRequest.getQueryString());
+            response.setContentLength(0);
+            response.sendRedirect(url);
+        }
+        else
+        {
+            response.sendError(HttpStatus.FORBIDDEN_403,"Not Secure");
+        }
+        
+        baseRequest.setHandled(true);
+    }
+}
\ No newline at end of file
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ShutdownHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ShutdownHandler.java
index d196845..2c499dc 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ShutdownHandler.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ShutdownHandler.java
@@ -19,11 +19,17 @@
 package org.eclipse.jetty.server.handler;
 
 import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.InetSocketAddress;
+import java.net.SocketException;
+import java.net.URL;
 
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.NetworkConnector;
 import org.eclipse.jetty.server.Request;
 import org.eclipse.jetty.server.Server;
 import org.eclipse.jetty.util.log.Log;
@@ -31,8 +37,12 @@ import org.eclipse.jetty.util.log.Logger;
 
 /* ------------------------------------------------------------ */
 /**
- * A handler that shuts the server down on a valid request. Used to do "soft" restarts from Java. If _exitJvm ist set to true a hard System.exit() call is being
- * made.
+ * A handler that shuts the server down on a valid request. Used to do "soft" restarts from Java.
+ * If _exitJvm is set to true a hard System.exit() call is being made.
+ * If _sendShutdownAtStart is set to true, starting the server will try to shut down an existing server at the same port.
+ * If _sendShutdownAtStart is set to true, make a http call to
+ * "http://localhost:" + port + "/shutdown?token=" + shutdownCookie
+ * in order to shut down the server.
  *
  * This handler is a contribution from Johannes Brodwall: https://bugs.eclipse.org/bugs/show_bug.cgi?id=357687
  *
@@ -42,11 +52,11 @@ import org.eclipse.jetty.util.log.Logger;
     Server server = new Server(8080);
     HandlerList handlers = new HandlerList();
     handlers.setHandlers(new Handler[]
-    { someOtherHandler, new ShutdownHandler(server,"secret password") });
+    { someOtherHandler, new ShutdownHandler("secret password", false, true) });
     server.setHandler(handlers);
     server.start();
    </pre>
- * 
+ *
    <pre>
    public static void attemptShutdown(int port, String shutdownCookie) {
         try {
@@ -64,17 +74,14 @@ import org.eclipse.jetty.util.log.Logger;
     }
   </pre>
  */
-public class ShutdownHandler extends AbstractHandler
+public class ShutdownHandler extends HandlerWrapper
 {
     private static final Logger LOG = Log.getLogger(ShutdownHandler.class);
 
     private final String _shutdownToken;
-
-    private final Server _server;
-
+    private boolean _sendShutdownAtStart;
     private boolean _exitJvm = false;
-  
-    
+
 
     /**
      * Creates a listener that lets the server be shut down remotely (but only from localhost).
@@ -84,16 +91,88 @@ public class ShutdownHandler extends AbstractHandler
      * @param shutdownToken
      *            a secret password to avoid unauthorized shutdown attempts
      */
+    @Deprecated
     public ShutdownHandler(Server server, String shutdownToken)
     {
-        this._server = server;
+        this(shutdownToken);
+    }
+
+    public ShutdownHandler(String shutdownToken)
+    {
+        this(shutdownToken,false,false);
+    }
+    
+    /**
+     * @param shutdownToken
+     *            a secret password to avoid unauthorized shutdown attempts
+     * @param exitJVM If true, when the shutdown is executed, the handler class System.exit()
+     * @param sendShutdownAtStart If true, a shutdown is sent as a HTTP post
+     *            during startup, which will shutdown any previously running instances of
+     *            this server with an identically configured ShutdownHandler
+     */
+    public ShutdownHandler(String shutdownToken, boolean exitJVM, boolean sendShutdownAtStart)
+    {
         this._shutdownToken = shutdownToken;
+        setExitJvm(exitJVM);
+        setSendShutdownAtStart(sendShutdownAtStart);
     }
+    
+
+    public void sendShutdown() throws IOException
+    {
+        URL url = new URL(getServerUrl() + "/shutdown?token=" + _shutdownToken);
+        try
+        {
+            HttpURLConnection connection = (HttpURLConnection)url.openConnection();
+            connection.setRequestMethod("POST");
+            connection.getResponseCode();
+            LOG.info("Shutting down " + url + ": " + connection.getResponseCode() + " " + connection.getResponseMessage());
+        }
+        catch (SocketException e)
+        {
+            LOG.debug("Not running");
+            // Okay - the server is not running
+        }
+        catch (IOException e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @SuppressWarnings("resource")
+    private String getServerUrl()
+    {
+        NetworkConnector connector=null;
+        for (Connector c: getServer().getConnectors())
+        {
+            if (c instanceof NetworkConnector)
+            {
+                connector=(NetworkConnector)c;
+                break;
+            }
+        }
 
+        if (connector==null)
+            return "http://localhost";
+
+        return "http://localhost:" + connector.getPort();
+    }
+    
+    
+    @Override
+    protected void doStart() throws Exception
+    {
+        super.doStart();
+        if (_sendShutdownAtStart)
+            sendShutdown();
+    }
+    
+    @Override
     public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
     {
         if (!target.equals("/shutdown"))
         {
+            super.handle(target,baseRequest,request,response);
             return;
         }
 
@@ -104,26 +183,38 @@ public class ShutdownHandler extends AbstractHandler
         }
         if (!hasCorrectSecurityToken(request))
         {
-            LOG.warn("Unauthorized shutdown attempt from " + getRemoteAddr(request));
+            LOG.warn("Unauthorized tokenless shutdown attempt from " + request.getRemoteAddr());
             response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
             return;
         }
-        if (!requestFromLocalhost(request))
+        if (!requestFromLocalhost(baseRequest))
         {
-            LOG.warn("Unauthorized shutdown attempt from " + getRemoteAddr(request));
+            LOG.warn("Unauthorized non-loopback shutdown attempt from " + request.getRemoteAddr());
             response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
             return;
         }
 
-        LOG.info("Shutting down by request from " + getRemoteAddr(request));
-        
+        LOG.info("Shutting down by request from " + request.getRemoteAddr());
+        doShutdown(baseRequest, response);
+    }
+
+    protected void doShutdown(Request baseRequest, HttpServletResponse response) throws IOException {
+        for (Connector connector : getServer().getConnectors()) {
+            connector.shutdown();
+        }
+
+        response.sendError(200, "Connectors closed, commencing full shutdown");
+        baseRequest.setHandled(true);
+
+        final Server server=getServer();
         new Thread()
         {
+            @Override
             public void run ()
             {
                 try
                 {
-                    shutdownServer();
+                    shutdownServer(server);
                 }
                 catch (InterruptedException e)
                 {
@@ -137,25 +228,28 @@ public class ShutdownHandler extends AbstractHandler
         }.start();
     }
 
-    private boolean requestFromLocalhost(HttpServletRequest request)
+    private boolean requestFromLocalhost(Request request)
     {
-        return "127.0.0.1".equals(getRemoteAddr(request));
-    }
-
-    protected String getRemoteAddr(HttpServletRequest request)
-    {
-        return request.getRemoteAddr();
+        InetSocketAddress addr = request.getRemoteInetSocketAddress();
+        if (addr == null)
+        {
+            return false;
+        }
+        return addr.getAddress().isLoopbackAddress();
     }
 
     private boolean hasCorrectSecurityToken(HttpServletRequest request)
     {
-        return _shutdownToken.equals(request.getParameter("token"));
+        String tok = request.getParameter("token");
+        if (LOG.isDebugEnabled())
+            LOG.debug("Token: {}", tok);
+        return _shutdownToken.equals(tok);
     }
 
-    private void shutdownServer() throws Exception
+    private void shutdownServer(Server server) throws Exception
     {
-        _server.stop();
-        
+        server.stop();
+
         if (_exitJvm)
         {
             System.exit(0);
@@ -167,4 +261,24 @@ public class ShutdownHandler extends AbstractHandler
         this._exitJvm = exitJvm;
     }
 
+    public boolean isSendShutdownAtStart()
+    {
+        return _sendShutdownAtStart;
+    }
+
+    public void setSendShutdownAtStart(boolean sendShutdownAtStart)
+    {
+        _sendShutdownAtStart = sendShutdownAtStart;
+    }
+
+    public String getShutdownToken()
+    {
+        return _shutdownToken;
+    }
+
+    public boolean isExitJvm()
+    {
+        return _exitJvm;
+    }
+
 }
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/StatisticsHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/StatisticsHandler.java
index f0dcf33..4f4627e 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/StatisticsHandler.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/StatisticsHandler.java
@@ -19,34 +19,44 @@
 package org.eclipse.jetty.server.handler;
 
 import java.io.IOException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeoutException;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicReference;
 
+import javax.servlet.AsyncEvent;
+import javax.servlet.AsyncListener;
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import org.eclipse.jetty.continuation.Continuation;
-import org.eclipse.jetty.continuation.ContinuationListener;
-import org.eclipse.jetty.server.AsyncContinuation;
+import org.eclipse.jetty.server.AsyncContextEvent;
+import org.eclipse.jetty.server.HttpChannelState;
 import org.eclipse.jetty.server.Request;
 import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.util.FutureCallback;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.annotation.ManagedOperation;
+import org.eclipse.jetty.util.component.Graceful;
 import org.eclipse.jetty.util.statistic.CounterStatistic;
 import org.eclipse.jetty.util.statistic.SampleStatistic;
 
-public class StatisticsHandler extends HandlerWrapper
+ at ManagedObject("Request Statistics Gathering")
+public class StatisticsHandler extends HandlerWrapper implements Graceful
 {
     private final AtomicLong _statsStartedAt = new AtomicLong();
-    
+
     private final CounterStatistic _requestStats = new CounterStatistic();
     private final SampleStatistic _requestTimeStats = new SampleStatistic();
     private final CounterStatistic _dispatchedStats = new CounterStatistic();
     private final SampleStatistic _dispatchedTimeStats = new SampleStatistic();
-    private final CounterStatistic _suspendStats = new CounterStatistic();
+    private final CounterStatistic _asyncWaitStats = new CounterStatistic();
 
-    private final AtomicInteger _resumes = new AtomicInteger();
+    private final AtomicInteger _asyncDispatches = new AtomicInteger();
     private final AtomicInteger _expires = new AtomicInteger();
-    
+
     private final AtomicInteger _responses1xx = new AtomicInteger();
     private final AtomicInteger _responses2xx = new AtomicInteger();
     private final AtomicInteger _responses3xx = new AtomicInteger();
@@ -54,42 +64,67 @@ public class StatisticsHandler extends HandlerWrapper
     private final AtomicInteger _responses5xx = new AtomicInteger();
     private final AtomicLong _responsesTotalBytes = new AtomicLong();
 
-    private final ContinuationListener _onCompletion = new ContinuationListener()
+    private final AtomicReference<FutureCallback> _shutdown=new AtomicReference<>();
+    
+    private final AsyncListener _onCompletion = new AsyncListener()
     {
-        public void onComplete(Continuation continuation)
+        @Override
+        public void onTimeout(AsyncEvent event) throws IOException
+        {
+            _expires.incrementAndGet();
+        }
+        
+        @Override
+        public void onStartAsync(AsyncEvent event) throws IOException
+        {
+            event.getAsyncContext().addListener(this);
+        }
+        
+        @Override
+        public void onError(AsyncEvent event) throws IOException
         {
-            final Request request = ((AsyncContinuation)continuation).getBaseRequest();
+        }
+
+        @Override
+        public void onComplete(AsyncEvent event) throws IOException
+        {
+            HttpChannelState state = ((AsyncContextEvent)event).getHttpChannelState();
+
+            Request request = state.getBaseRequest();
             final long elapsed = System.currentTimeMillis()-request.getTimeStamp();
-            
-            _requestStats.decrement();
+
+            long d=_requestStats.decrement();
             _requestTimeStats.set(elapsed);
-            
+
             updateResponse(request);
-            
-            if (!continuation.isResumed())
-                _suspendStats.decrement();
-        }
 
-        public void onTimeout(Continuation continuation)
-        {
-            _expires.incrementAndGet();
+            _asyncWaitStats.decrement();
+            
+            // If we have no more dispatches, should we signal shutdown?
+            if (d==0)
+            {
+                FutureCallback shutdown = _shutdown.get();
+                if (shutdown!=null)
+                    shutdown.succeeded();
+            }   
         }
     };
-    
+
     /**
      * Resets the current request statistics.
      */
+    @ManagedOperation(value="resets statistics", impact="ACTION")
     public void statsReset()
     {
         _statsStartedAt.set(System.currentTimeMillis());
-        
+
         _requestStats.reset();
         _requestTimeStats.reset();
         _dispatchedStats.reset();
         _dispatchedTimeStats.reset();
-        _suspendStats.reset();
+        _asyncWaitStats.reset();
 
-        _resumes.set(0);
+        _asyncDispatches.set(0);
         _expires.set(0);
         _responses1xx.set(0);
         _responses2xx.set(0);
@@ -105,8 +140,8 @@ public class StatisticsHandler extends HandlerWrapper
         _dispatchedStats.increment();
 
         final long start;
-        AsyncContinuation continuation = request.getAsyncContinuation();
-        if (continuation.isInitial())
+        HttpChannelState state = request.getHttpChannelState();
+        if (state.isInitial())
         {
             // new request
             _requestStats.increment();
@@ -116,9 +151,7 @@ public class StatisticsHandler extends HandlerWrapper
         {
             // resumed request
             start = System.currentTimeMillis();
-            _suspendStats.decrement();
-            if (continuation.isResumed())
-                _resumes.incrementAndGet();
+            _asyncDispatches.incrementAndGet();
         }
 
         try
@@ -129,65 +162,94 @@ public class StatisticsHandler extends HandlerWrapper
         {
             final long now = System.currentTimeMillis();
             final long dispatched=now-start;
-            
+
             _dispatchedStats.decrement();
             _dispatchedTimeStats.set(dispatched);
-            
-            if (continuation.isSuspended())
+
+            if (state.isSuspended())
             {
-                if (continuation.isInitial())
-                    continuation.addContinuationListener(_onCompletion);
-                _suspendStats.increment();
+                if (state.isInitial())
+                {
+                    state.addListener(_onCompletion);
+                    _asyncWaitStats.increment();
+                }
             }
-            else if (continuation.isInitial())
+            else if (state.isInitial())
             {
-                _requestStats.decrement();
+                long d=_requestStats.decrement();
                 _requestTimeStats.set(dispatched);
                 updateResponse(request);
+                
+                // If we have no more dispatches, should we signal shutdown?
+                FutureCallback shutdown = _shutdown.get();
+                if (shutdown!=null)
+                {
+                    httpResponse.flushBuffer();
+                    if (d==0)
+                        shutdown.succeeded();
+                }   
             }
             // else onCompletion will handle it.
         }
     }
 
-    private void updateResponse(Request request)
+    protected void updateResponse(Request request)
     {
         Response response = request.getResponse();
-        switch (response.getStatus() / 100)
+        if (request.isHandled())
         {
-            case 1:
-                _responses1xx.incrementAndGet();
-                break;
-            case 2:
-                _responses2xx.incrementAndGet();
-                break;
-            case 3:
-                _responses3xx.incrementAndGet();
-                break;
-            case 4:
-                _responses4xx.incrementAndGet();
-                break;
-            case 5:
-                _responses5xx.incrementAndGet();
-                break;
-            default:
-                break;
+            switch (response.getStatus() / 100)
+            {
+                case 1:
+                    _responses1xx.incrementAndGet();
+                    break;
+                case 2:
+                    _responses2xx.incrementAndGet();
+                    break;
+                case 3:
+                    _responses3xx.incrementAndGet();
+                    break;
+                case 4:
+                    _responses4xx.incrementAndGet();
+                    break;
+                case 5:
+                    _responses5xx.incrementAndGet();
+                    break;
+                default:
+                    break;
+            }
         }
+        else
+            // will fall through to not found handler
+            _responses4xx.incrementAndGet();
         _responsesTotalBytes.addAndGet(response.getContentCount());
     }
 
     @Override
     protected void doStart() throws Exception
     {
+        _shutdown.set(null);
         super.doStart();
         statsReset();
     }
+    
+
+    @Override
+    protected void doStop() throws Exception
+    {
+        super.doStop();
+        FutureCallback shutdown = _shutdown.get();
+        if (shutdown!=null && !shutdown.isDone())
+            shutdown.failed(new TimeoutException());
+    }
 
     /**
      * @return the number of requests handled by this handler
      * since {@link #statsReset()} was last called, excluding
      * active requests
-     * @see #getResumes()
+     * @see #getAsyncDispatches()
      */
+    @ManagedAttribute("number of requests")
     public int getRequests()
     {
         return (int)_requestStats.getTotal();
@@ -197,6 +259,7 @@ public class StatisticsHandler extends HandlerWrapper
      * @return the number of requests currently active.
      * since {@link #statsReset()} was last called.
      */
+    @ManagedAttribute("number of requests currently active")
     public int getRequestsActive()
     {
         return (int)_requestStats.getCurrent();
@@ -206,6 +269,7 @@ public class StatisticsHandler extends HandlerWrapper
      * @return the maximum number of active requests
      * since {@link #statsReset()} was last called.
      */
+    @ManagedAttribute("maximum number of active requests")
     public int getRequestsActiveMax()
     {
         return (int)_requestStats.getMax();
@@ -215,6 +279,7 @@ public class StatisticsHandler extends HandlerWrapper
      * @return the maximum time (in milliseconds) of request handling
      * since {@link #statsReset()} was last called.
      */
+    @ManagedAttribute("maximum time spend handling requests (in ms)")
     public long getRequestTimeMax()
     {
         return _requestTimeStats.getMax();
@@ -224,6 +289,7 @@ public class StatisticsHandler extends HandlerWrapper
      * @return the total time (in milliseconds) of requests handling
      * since {@link #statsReset()} was last called.
      */
+    @ManagedAttribute("total time spend in all request handling (in ms)")
     public long getRequestTimeTotal()
     {
         return _requestTimeStats.getTotal();
@@ -235,6 +301,7 @@ public class StatisticsHandler extends HandlerWrapper
      * @see #getRequestTimeTotal()
      * @see #getRequests()
      */
+    @ManagedAttribute("mean time spent handling requests (in ms)")
     public double getRequestTimeMean()
     {
         return _requestTimeStats.getMean();
@@ -246,6 +313,7 @@ public class StatisticsHandler extends HandlerWrapper
      * @see #getRequestTimeTotal()
      * @see #getRequests()
      */
+    @ManagedAttribute("standard deviation for request handling (in ms)")
     public double getRequestTimeStdDev()
     {
         return _requestTimeStats.getStdDev();
@@ -256,6 +324,7 @@ public class StatisticsHandler extends HandlerWrapper
      * since {@link #statsReset()} was last called, excluding
      * active dispatches
      */
+    @ManagedAttribute("number of dispatches")
     public int getDispatched()
     {
         return (int)_dispatchedStats.getTotal();
@@ -266,6 +335,7 @@ public class StatisticsHandler extends HandlerWrapper
      * since {@link #statsReset()} was last called, including
      * resumed requests
      */
+    @ManagedAttribute("number of dispatches currently active")
     public int getDispatchedActive()
     {
         return (int)_dispatchedStats.getCurrent();
@@ -276,6 +346,7 @@ public class StatisticsHandler extends HandlerWrapper
      * since {@link #statsReset()} was last called, including
      * resumed requests
      */
+    @ManagedAttribute("maximum number of active dispatches being handled")
     public int getDispatchedActiveMax()
     {
         return (int)_dispatchedStats.getMax();
@@ -285,15 +356,17 @@ public class StatisticsHandler extends HandlerWrapper
      * @return the maximum time (in milliseconds) of request dispatch
      * since {@link #statsReset()} was last called.
      */
+    @ManagedAttribute("maximum time spend in dispatch handling")
     public long getDispatchedTimeMax()
     {
         return _dispatchedTimeStats.getMax();
     }
-    
+
     /**
      * @return the total time (in milliseconds) of requests handling
      * since {@link #statsReset()} was last called.
      */
+    @ManagedAttribute("total time spent in dispatch handling (in ms)")
     public long getDispatchedTimeTotal()
     {
         return _dispatchedTimeStats.getTotal();
@@ -305,64 +378,70 @@ public class StatisticsHandler extends HandlerWrapper
      * @see #getRequestTimeTotal()
      * @see #getRequests()
      */
+    @ManagedAttribute("mean time spent in dispatch handling (in ms)")
     public double getDispatchedTimeMean()
     {
         return _dispatchedTimeStats.getMean();
     }
-    
+
     /**
      * @return the standard deviation of time (in milliseconds) of request handling
      * since {@link #statsReset()} was last called.
      * @see #getRequestTimeTotal()
      * @see #getRequests()
      */
+    @ManagedAttribute("standard deviation for dispatch handling (in ms)")
     public double getDispatchedTimeStdDev()
     {
         return _dispatchedTimeStats.getStdDev();
     }
-    
+
     /**
      * @return the number of requests handled by this handler
      * since {@link #statsReset()} was last called, including
      * resumed requests
-     * @see #getResumes()
+     * @see #getAsyncDispatches()
      */
-    public int getSuspends()
+    @ManagedAttribute("total number of async requests")
+    public int getAsyncRequests()
     {
-        return (int)_suspendStats.getTotal();
+        return (int)_asyncWaitStats.getTotal();
     }
 
     /**
      * @return the number of requests currently suspended.
      * since {@link #statsReset()} was last called.
      */
-    public int getSuspendsActive()
+    @ManagedAttribute("currently waiting async requests")
+    public int getAsyncRequestsWaiting()
     {
-        return (int)_suspendStats.getCurrent();
+        return (int)_asyncWaitStats.getCurrent();
     }
 
     /**
      * @return the maximum number of current suspended requests
      * since {@link #statsReset()} was last called.
      */
-    public int getSuspendsActiveMax()
+    @ManagedAttribute("maximum number of waiting async requests")
+    public int getAsyncRequestsWaitingMax()
     {
-        return (int)_suspendStats.getMax();
+        return (int)_asyncWaitStats.getMax();
     }
-    
+
     /**
-     * @return the number of requests that have been resumed
-     * @see #getExpires()
+     * @return the number of requests that have been asynchronously dispatched
      */
-    public int getResumes()
+    @ManagedAttribute("number of requested that have been asynchronously dispatched")
+    public int getAsyncDispatches()
     {
-        return _resumes.get();
+        return _asyncDispatches.get();
     }
 
     /**
      * @return the number of requests that expired while suspended.
-     * @see #getResumes()
+     * @see #getAsyncDispatches()
      */
+    @ManagedAttribute("number of async requests requests that have expired")
     public int getExpires()
     {
         return _expires.get();
@@ -372,6 +451,7 @@ public class StatisticsHandler extends HandlerWrapper
      * @return the number of responses with a 1xx status returned by this context
      * since {@link #statsReset()} was last called.
      */
+    @ManagedAttribute("number of requests with 1xx response status")
     public int getResponses1xx()
     {
         return _responses1xx.get();
@@ -381,6 +461,7 @@ public class StatisticsHandler extends HandlerWrapper
      * @return the number of responses with a 2xx status returned by this context
      * since {@link #statsReset()} was last called.
      */
+    @ManagedAttribute("number of requests with 2xx response status")
     public int getResponses2xx()
     {
         return _responses2xx.get();
@@ -390,6 +471,7 @@ public class StatisticsHandler extends HandlerWrapper
      * @return the number of responses with a 3xx status returned by this context
      * since {@link #statsReset()} was last called.
      */
+    @ManagedAttribute("number of requests with 3xx response status")
     public int getResponses3xx()
     {
         return _responses3xx.get();
@@ -399,6 +481,7 @@ public class StatisticsHandler extends HandlerWrapper
      * @return the number of responses with a 4xx status returned by this context
      * since {@link #statsReset()} was last called.
      */
+    @ManagedAttribute("number of requests with 4xx response status")
     public int getResponses4xx()
     {
         return _responses4xx.get();
@@ -408,6 +491,7 @@ public class StatisticsHandler extends HandlerWrapper
      * @return the number of responses with a 5xx status returned by this context
      * since {@link #statsReset()} was last called.
      */
+    @ManagedAttribute("number of requests with 5xx response status")
     public int getResponses5xx()
     {
         return _responses5xx.get();
@@ -416,21 +500,23 @@ public class StatisticsHandler extends HandlerWrapper
     /**
      * @return the milliseconds since the statistics were started with {@link #statsReset()}.
      */
+    @ManagedAttribute("time in milliseconds stats have been collected for")
     public long getStatsOnMs()
     {
         return System.currentTimeMillis() - _statsStartedAt.get();
     }
-    
+
     /**
      * @return the total bytes of content sent in responses
      */
+    @ManagedAttribute("total number of bytes across all responses")
     public long getResponsesBytesTotal()
     {
         return _responsesTotalBytes.get();
     }
-    
+
     public String toStatsHTML()
-    {   
+    {
         StringBuilder sb = new StringBuilder();
 
         sb.append("<h1>Statistics:</h1>\n");
@@ -444,7 +530,7 @@ public class StatisticsHandler extends HandlerWrapper
         sb.append("Mean request time: ").append(getRequestTimeMean()).append("<br />\n");
         sb.append("Max request time: ").append(getRequestTimeMax()).append("<br />\n");
         sb.append("Request time standard deviation: ").append(getRequestTimeStdDev()).append("<br />\n");
-        
+
 
         sb.append("<h2>Dispatches:</h2>\n");
         sb.append("Total dispatched: ").append(getDispatched()).append("<br />\n");
@@ -456,10 +542,10 @@ public class StatisticsHandler extends HandlerWrapper
         sb.append("Dispatched time standard deviation: ").append(getDispatchedTimeStdDev()).append("<br />\n");
 
 
-        sb.append("Total requests suspended: ").append(getSuspends()).append("<br />\n");
+        sb.append("Total requests suspended: ").append(getAsyncRequests()).append("<br />\n");
         sb.append("Total requests expired: ").append(getExpires()).append("<br />\n");
-        sb.append("Total requests resumed: ").append(getResumes()).append("<br />\n");
-        
+        sb.append("Total requests resumed: ").append(getAsyncDispatches()).append("<br />\n");
+
         sb.append("<h2>Responses:</h2>\n");
         sb.append("1xx responses: ").append(getResponses1xx()).append("<br />\n");
         sb.append("2xx responses: ").append(getResponses2xx()).append("<br />\n");
@@ -471,4 +557,15 @@ public class StatisticsHandler extends HandlerWrapper
         return sb.toString();
 
     }
+
+    @Override
+    public Future<Void> shutdown()
+    {
+        FutureCallback shutdown=new FutureCallback(false);
+        _shutdown.compareAndSet(null,shutdown);
+        shutdown=_shutdown.get();
+        if (_dispatchedStats.getCurrent()==0)
+            shutdown.succeeded();
+        return shutdown;
+    }
 }
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/jmx/AbstractHandlerMBean.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/jmx/AbstractHandlerMBean.java
index f8037ec..bbe5080 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/jmx/AbstractHandlerMBean.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/jmx/AbstractHandlerMBean.java
@@ -47,7 +47,12 @@ public class AbstractHandlerMBean extends ObjectMBean
             String basis = null;
             if (_managed instanceof ContextHandler)
             {
-                return null;
+                ContextHandler handler = (ContextHandler)_managed;
+                String context = getContextName(handler);
+                if (context==null)
+                    context=handler.getDisplayName();
+                if (context!=null)
+                    return context;
             }
             else if (_managed instanceof AbstractHandler)
             {
@@ -68,26 +73,6 @@ public class AbstractHandlerMBean extends ObjectMBean
         }
         return super.getObjectContextBasis();
     }
-    
-    /* ------------------------------------------------------------ */
-    @Override
-    public String getObjectNameBasis()
-    {
-        if (_managed != null )
-        {
-            String name = null;
-            if (_managed instanceof ContextHandler)
-            {
-                ContextHandler context = (ContextHandler)_managed;
-                name = getContextName(context);
-            }
-            
-            if (name != null)
-                return name;
-        }
-        
-        return super.getObjectNameBasis();
-    }
 
     /* ------------------------------------------------------------ */
     protected String getContextName(ContextHandler context)
@@ -116,6 +101,9 @@ public class AbstractHandlerMBean extends ObjectMBean
             }
         }
         
+        if (context.getVirtualHosts()!=null && context.getVirtualHosts().length>0)
+            name='"'+name+"@"+context.getVirtualHosts()[0]+'"';
+        
         return name;
     }
 }
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/jmx/ContextHandlerMBean.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/jmx/ContextHandlerMBean.java
index 403e7d6..abcf2f5 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/jmx/ContextHandlerMBean.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/jmx/ContextHandlerMBean.java
@@ -24,7 +24,12 @@ import java.util.Map;
 
 import org.eclipse.jetty.server.handler.ContextHandler;
 import org.eclipse.jetty.util.Attributes;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.annotation.ManagedOperation;
+import org.eclipse.jetty.util.annotation.Name;
 
+ at ManagedObject("ContextHandler mbean wrapper")
 public class ContextHandlerMBean extends AbstractHandlerMBean
 {
     public ContextHandlerMBean(Object managedObject)
@@ -32,11 +37,12 @@ public class ContextHandlerMBean extends AbstractHandlerMBean
         super(managedObject);
     }
 
-    public Map getContextAttributes()
+    @ManagedAttribute("Map of context attributes")
+    public Map<String,Object> getContextAttributes()
     {
-        Map map = new HashMap();
+        Map<String,Object> map = new HashMap<String,Object>();
         Attributes attrs = ((ContextHandler)_managed).getAttributes();
-        Enumeration en = attrs.getAttributeNames();
+        Enumeration<String> en = attrs.getAttributeNames();
         while (en.hasMoreElements())
         {
             String name = (String)en.nextElement();
@@ -46,19 +52,22 @@ public class ContextHandlerMBean extends AbstractHandlerMBean
         return map;
     }
     
-    public void setContextAttribute(String name, Object value)
+    @ManagedOperation(value="Set context attribute", impact="ACTION")
+    public void setContextAttribute(@Name(value = "name", description="attribute name") String name, @Name(value = "value", description="attribute value") Object value)
     {
         Attributes attrs = ((ContextHandler)_managed).getAttributes();
         attrs.setAttribute(name,value);
     }
     
-    public void setContextAttribute(String name, String value)
+    @ManagedOperation(value="Set context attribute", impact="ACTION")
+    public void setContextAttribute(@Name(value = "name", description="attribute name") String name, @Name(value = "value", description="attribute value") String value)
     {
         Attributes attrs = ((ContextHandler)_managed).getAttributes();
         attrs.setAttribute(name,value);
     }
     
-    public void removeContextAttribute(String name)
+    @ManagedOperation(value="Remove context attribute", impact="ACTION")
+    public void removeContextAttribute(@Name(value = "name", description="attribute name") String name)
     {
         Attributes attrs = ((ContextHandler)_managed).getAttributes();
         attrs.removeAttribute(name);
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/jmx/package-info.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/jmx/package-info.java
new file mode 100644
index 0000000..9760c01
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/jmx/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Server : Handler JMX Integration
+ */
+package org.eclipse.jetty.server.handler.jmx;
+
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/package-info.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/package-info.java
new file mode 100644
index 0000000..34b1f5a
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Server : Core Handler API
+ */
+package org.eclipse.jetty.server.handler;
+
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/jmx/AbstractConnectorMBean.java b/jetty-server/src/main/java/org/eclipse/jetty/server/jmx/AbstractConnectorMBean.java
new file mode 100644
index 0000000..7224dd2
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/jmx/AbstractConnectorMBean.java
@@ -0,0 +1,41 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.jmx;
+
+import org.eclipse.jetty.jmx.ObjectMBean;
+import org.eclipse.jetty.server.AbstractConnector;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+
+ at ManagedObject("MBean Wrapper for Connectors")
+public class AbstractConnectorMBean extends ObjectMBean
+{
+    final AbstractConnector _connector;
+    public AbstractConnectorMBean(Object managedObject)
+    {
+        super(managedObject);
+        _connector=(AbstractConnector)managedObject;
+    }
+    @Override
+    public String getObjectContextBasis()
+    {
+        return String.format("%s@%x",_connector.getDefaultProtocol(),_connector.hashCode());
+    }
+
+
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/jmx/ServerMBean.java b/jetty-server/src/main/java/org/eclipse/jetty/server/jmx/ServerMBean.java
index 2f92636..97805ef 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/jmx/ServerMBean.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/jmx/ServerMBean.java
@@ -22,10 +22,13 @@ import org.eclipse.jetty.jmx.ObjectMBean;
 import org.eclipse.jetty.server.Handler;
 import org.eclipse.jetty.server.Server;
 import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
 
 /**
  *
  */
+ at ManagedObject("MBean Wrapper for Server")
 public class ServerMBean extends ObjectMBean
 {
     private final long startupTime;
@@ -38,11 +41,13 @@ public class ServerMBean extends ObjectMBean
         server = (Server)managedObject;
     }
 
+    @ManagedAttribute("contexts on this server")
     public Handler[] getContexts()
     {
         return server.getChildHandlersByClass(ContextHandler.class);
     }
 
+    @ManagedAttribute("the startup time since January 1st, 1970 (in ms)")
     public long getStartupTime()
     {
         return startupTime;
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/jmx/package-info.java b/jetty-server/src/main/java/org/eclipse/jetty/server/jmx/package-info.java
new file mode 100644
index 0000000..08858aa
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/jmx/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Server : Server JMX Integration
+ */
+package org.eclipse.jetty.server.jmx;
+
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/nio/AbstractNIOConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/nio/AbstractNIOConnector.java
deleted file mode 100644
index 1641deb..0000000
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/nio/AbstractNIOConnector.java
+++ /dev/null
@@ -1,53 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-/**
- *
- */
-package org.eclipse.jetty.server.nio;
-
-import org.eclipse.jetty.io.Buffers.Type;
-import org.eclipse.jetty.server.AbstractConnector;
-
-public abstract class AbstractNIOConnector extends AbstractConnector implements NIOConnector
-{
-    public AbstractNIOConnector()
-    {
-        _buffers.setRequestBufferType(Type.DIRECT);
-        _buffers.setRequestHeaderType(Type.INDIRECT);
-        _buffers.setResponseBufferType(Type.DIRECT);
-        _buffers.setResponseHeaderType(Type.INDIRECT);
-    }
-
-    /* ------------------------------------------------------------------------------- */
-    public boolean getUseDirectBuffers()
-    {
-        return getRequestBufferType()==Type.DIRECT;
-    }
-
-    /* ------------------------------------------------------------------------------- */
-    /**
-     * @param direct If True (the default), the connector can use NIO direct buffers.
-     * Some JVMs have memory management issues (bugs) with direct buffers.
-     */
-    public void setUseDirectBuffers(boolean direct)
-    {
-        _buffers.setRequestBufferType(direct?Type.DIRECT:Type.INDIRECT);
-        _buffers.setResponseBufferType(direct?Type.DIRECT:Type.INDIRECT);
-    }
-}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/nio/BlockingChannelConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/nio/BlockingChannelConnector.java
deleted file mode 100644
index 3f10177..0000000
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/nio/BlockingChannelConnector.java
+++ /dev/null
@@ -1,366 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.server.nio;
-
-import java.io.IOException;
-import java.net.InetSocketAddress;
-import java.net.Socket;
-import java.nio.channels.ByteChannel;
-import java.nio.channels.SelectionKey;
-import java.nio.channels.ServerSocketChannel;
-import java.nio.channels.SocketChannel;
-import java.util.Set;
-
-import org.eclipse.jetty.http.HttpException;
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.ConnectedEndPoint;
-import org.eclipse.jetty.io.Connection;
-import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.io.EofException;
-import org.eclipse.jetty.io.nio.ChannelEndPoint;
-import org.eclipse.jetty.server.BlockingHttpConnection;
-import org.eclipse.jetty.server.Request;
-import org.eclipse.jetty.util.ConcurrentHashSet;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-
-
-/* ------------------------------------------------------------------------------- */
-/**  Blocking NIO connector.
- * This connector uses efficient NIO buffers with a traditional blocking thread model.
- * Direct NIO buffers are used and a thread is allocated per connections.
- *
- * This connector is best used when there are a few very active connections.
- *
- * @org.apache.xbean.XBean element="blockingNioConnector" description="Creates a blocking NIO based socket connector"
- *
- *
- *
- */
-public class BlockingChannelConnector extends AbstractNIOConnector
-{
-    private static final Logger LOG = Log.getLogger(BlockingChannelConnector.class);
-
-    private transient ServerSocketChannel _acceptChannel;
-    private final Set<BlockingChannelEndPoint> _endpoints = new ConcurrentHashSet<BlockingChannelEndPoint>();
-
-
-    /* ------------------------------------------------------------ */
-    /** Constructor.
-     *
-     */
-    public BlockingChannelConnector()
-    {
-    }
-
-    /* ------------------------------------------------------------ */
-    public Object getConnection()
-    {
-        return _acceptChannel;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.server.AbstractConnector#doStart()
-     */
-    @Override
-    protected void doStart() throws Exception
-    {
-        super.doStart();
-        getThreadPool().dispatch(new Runnable()
-        {
-
-            public void run()
-            {
-                while (isRunning())
-                {
-                    try
-                    {
-                        Thread.sleep(400);
-                        long now=System.currentTimeMillis();
-                        for (BlockingChannelEndPoint endp : _endpoints)
-                        {
-                            endp.checkIdleTimestamp(now);
-                        }
-                    }
-                    catch(InterruptedException e)
-                    {
-                        LOG.ignore(e);
-                    }
-                    catch(Exception e)
-                    {
-                        LOG.warn(e);
-                    }
-                }
-            }
-
-        });
-
-    }
-
-
-    /* ------------------------------------------------------------ */
-    public void open() throws IOException
-    {
-        // Create a new server socket and set to non blocking mode
-        _acceptChannel= ServerSocketChannel.open();
-        _acceptChannel.configureBlocking(true);
-
-        // Bind the server socket to the local host and port
-        InetSocketAddress addr = getHost()==null?new InetSocketAddress(getPort()):new InetSocketAddress(getHost(),getPort());
-        _acceptChannel.socket().bind(addr,getAcceptQueueSize());
-    }
-
-    /* ------------------------------------------------------------ */
-    public void close() throws IOException
-    {
-        if (_acceptChannel != null)
-            _acceptChannel.close();
-        _acceptChannel=null;
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public void accept(int acceptorID)
-    	throws IOException, InterruptedException
-    {
-        SocketChannel channel = _acceptChannel.accept();
-        channel.configureBlocking(true);
-        Socket socket=channel.socket();
-        configure(socket);
-
-        BlockingChannelEndPoint connection=new BlockingChannelEndPoint(channel);
-        connection.dispatch();
-    }
-
-    /* ------------------------------------------------------------------------------- */
-    @Override
-    public void customize(EndPoint endpoint, Request request)
-        throws IOException
-    {
-        super.customize(endpoint, request);
-        endpoint.setMaxIdleTime(_maxIdleTime);
-        configure(((SocketChannel)endpoint.getTransport()).socket());
-    }
-
-
-    /* ------------------------------------------------------------------------------- */
-    public int getLocalPort()
-    {
-        if (_acceptChannel==null || !_acceptChannel.isOpen())
-            return -1;
-        return _acceptChannel.socket().getLocalPort();
-    }
-
-    /* ------------------------------------------------------------------------------- */
-    /* ------------------------------------------------------------------------------- */
-    /* ------------------------------------------------------------------------------- */
-    private class BlockingChannelEndPoint extends ChannelEndPoint implements Runnable, ConnectedEndPoint
-    {
-        private Connection _connection;
-        private int _timeout;
-        private volatile long _idleTimestamp;
-
-        BlockingChannelEndPoint(ByteChannel channel)
-            throws IOException
-        {
-            super(channel,BlockingChannelConnector.this._maxIdleTime);
-            _connection = new BlockingHttpConnection(BlockingChannelConnector.this,this,getServer());
-        }
-
-        /* ------------------------------------------------------------ */
-        /** Get the connection.
-         * @return the connection
-         */
-        public Connection getConnection()
-        {
-            return _connection;
-        }
-
-        /* ------------------------------------------------------------ */
-        public void setConnection(Connection connection)
-        {
-            _connection=connection;
-        }
-
-        /* ------------------------------------------------------------ */
-        public void checkIdleTimestamp(long now)
-        {
-            if (_idleTimestamp!=0 && _timeout>0 && now>(_idleTimestamp+_timeout))
-            {
-                idleExpired();
-            }
-        }
-
-        /* ------------------------------------------------------------ */
-        protected void idleExpired()
-        {
-            try
-            {
-                super.close();
-            }
-            catch (IOException e)
-            {
-                LOG.ignore(e);
-            }
-        }
-
-        /* ------------------------------------------------------------ */
-        void dispatch() throws IOException
-        {
-            if (!getThreadPool().dispatch(this))
-            {
-                LOG.warn("dispatch failed for  {}",_connection);
-                super.close();
-            }
-        }
-
-        /* ------------------------------------------------------------ */
-        /**
-         * @see org.eclipse.jetty.io.nio.ChannelEndPoint#fill(org.eclipse.jetty.io.Buffer)
-         */
-        @Override
-        public int fill(Buffer buffer) throws IOException
-        {
-            _idleTimestamp=System.currentTimeMillis();
-            return super.fill(buffer);
-        }
-
-        /* ------------------------------------------------------------ */
-        /**
-         * @see org.eclipse.jetty.io.nio.ChannelEndPoint#flush(org.eclipse.jetty.io.Buffer)
-         */
-        @Override
-        public int flush(Buffer buffer) throws IOException
-        {
-            _idleTimestamp=System.currentTimeMillis();
-            return super.flush(buffer);
-        }
-
-        /* ------------------------------------------------------------ */
-        /**
-         * @see org.eclipse.jetty.io.nio.ChannelEndPoint#flush(org.eclipse.jetty.io.Buffer, org.eclipse.jetty.io.Buffer, org.eclipse.jetty.io.Buffer)
-         */
-        @Override
-        public int flush(Buffer header, Buffer buffer, Buffer trailer) throws IOException
-        {
-            _idleTimestamp=System.currentTimeMillis();
-            return super.flush(header,buffer,trailer);
-        }
-
-        /* ------------------------------------------------------------ */
-        public void run()
-        {
-            try
-            {
-                _timeout=getMaxIdleTime();
-                connectionOpened(_connection);
-                _endpoints.add(this);
-
-                while (isOpen())
-                {
-                    _idleTimestamp=System.currentTimeMillis();
-                    if (_connection.isIdle())
-                    {
-                        if (getServer().getThreadPool().isLowOnThreads())
-                        {
-                            int lrmit = getLowResourcesMaxIdleTime();
-                            if (lrmit>=0 && _timeout!= lrmit)
-                            {
-                                _timeout=lrmit;
-                            }
-                        }
-                    }
-                    else
-                    {
-                        if (_timeout!=getMaxIdleTime())
-                        {
-                            _timeout=getMaxIdleTime();
-                        }
-                    }
-
-                    _connection = _connection.handle();
-
-                }
-            }
-            catch (EofException e)
-            {
-                LOG.debug("EOF", e);
-                try{BlockingChannelEndPoint.this.close();}
-                catch(IOException e2){LOG.ignore(e2);}
-            }
-            catch (HttpException e)
-            {
-                LOG.debug("BAD", e);
-                try{super.close();}
-                catch(IOException e2){LOG.ignore(e2);}
-            }
-            catch(Throwable e)
-            {
-                LOG.warn("handle failed",e);
-                try{super.close();}
-                catch(IOException e2){LOG.ignore(e2);}
-            }
-            finally
-            {
-                connectionClosed(_connection);
-                _endpoints.remove(this);
-
-                // wait for client to close, but if not, close ourselves.
-                try
-                {
-                    if (!_socket.isClosed())
-                    {
-                        long timestamp=System.currentTimeMillis();
-                        int max_idle=getMaxIdleTime();
-
-                        _socket.setSoTimeout(getMaxIdleTime());
-                        int c=0;
-                        do
-                        {
-                            c = _socket.getInputStream().read();
-                        }
-                        while (c>=0 && (System.currentTimeMillis()-timestamp)<max_idle);
-                        if (!_socket.isClosed())
-                            _socket.close();
-                    }
-                }
-                catch(IOException e)
-                {
-                    LOG.ignore(e);
-                }
-            }
-        }
-
-        /* ------------------------------------------------------------ */
-        @Override
-        public String toString()
-        {
-            return String.format("BCEP@%x{l(%s)<->r(%s),open=%b,ishut=%b,oshut=%b}-{%s}",
-                    hashCode(),
-                    _socket.getRemoteSocketAddress(),
-                    _socket.getLocalSocketAddress(),
-                    isOpen(),
-                    isInputShutdown(),
-                    isOutputShutdown(),
-                    _connection);
-        }
-
-    }
-}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/nio/InheritedChannelConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/nio/InheritedChannelConnector.java
deleted file mode 100644
index 8c214c3..0000000
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/nio/InheritedChannelConnector.java
+++ /dev/null
@@ -1,75 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.server.nio;
-
-import java.io.IOException;
-import java.nio.channels.Channel;
-import java.nio.channels.ServerSocketChannel;
-
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-
-/**
- * An implementation of the SelectChannelConnector which first tries to  
- * inherit from a channel provided by the system. If there is no inherited
- * channel available, or if the inherited channel provided not usable, then 
- * it will fall back upon normal ServerSocketChannel creation.
- * <p> 
- * Note that System.inheritedChannel() is only available from Java 1.5 onwards.
- * Trying to use this class under Java 1.4 will be the same as using a normal
- * SelectChannelConnector. 
- * <p> 
- * Use it with xinetd/inetd, to launch an instance of Jetty on demand. The port
- * used to access pages on the Jetty instance is the same as the port used to
- * launch Jetty. 
- * 
- * @author athena
- */
-public class InheritedChannelConnector extends SelectChannelConnector
-{
-    private static final Logger LOG = Log.getLogger(InheritedChannelConnector.class);
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public void open() throws IOException
-    {
-        synchronized(this)
-        {
-            try 
-            {
-                Channel channel = System.inheritedChannel();
-                if ( channel instanceof ServerSocketChannel )
-                    _acceptChannel = (ServerSocketChannel)channel;
-                else
-                    LOG.warn("Unable to use System.inheritedChannel() [" +channel+ "]. Trying a new ServerSocketChannel at " + getHost() + ":" + getPort());
-                
-                if ( _acceptChannel != null )
-                    _acceptChannel.configureBlocking(true);
-            }
-            catch(NoSuchMethodError e)
-            {
-                LOG.warn("Need at least Java 5 to use socket inherited from xinetd/inetd.");
-            }
-
-            if (_acceptChannel == null)
-                super.open();
-        }
-    }
-
-}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/nio/NIOConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/nio/NIOConnector.java
deleted file mode 100644
index 939d269..0000000
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/nio/NIOConnector.java
+++ /dev/null
@@ -1,31 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.server.nio; 
-
-/** 
- * NIOConnector.
- * A marker interface that indicates that NIOBuffers can be handled (efficiently) by this Connector.
- * 
- * 
- * 
- */
-public interface NIOConnector
-{
-    boolean getUseDirectBuffers();
-}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/nio/NetworkTrafficSelectChannelConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/nio/NetworkTrafficSelectChannelConnector.java
index 91fdf79..565355e 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/nio/NetworkTrafficSelectChannelConnector.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/nio/NetworkTrafficSelectChannelConnector.java
@@ -18,56 +18,43 @@
 
 package org.eclipse.jetty.server.nio;
 
-import java.io.IOException;
-import java.nio.channels.SelectionKey;
-import java.nio.channels.SocketChannel;
-import java.util.ConcurrentModificationException;
-import java.util.List;
-import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Executor;
 
-import org.eclipse.jetty.io.NetworkTrafficListener;
-import org.eclipse.jetty.io.nio.NetworkTrafficSelectChannelEndPoint;
-import org.eclipse.jetty.io.nio.SelectChannelEndPoint;
-import org.eclipse.jetty.io.nio.SelectorManager;
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.server.ConnectionFactory;
+import org.eclipse.jetty.server.NetworkTrafficServerConnector;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.eclipse.jetty.util.thread.Scheduler;
 
 /**
- * <p>A specialized version of {@link SelectChannelConnector} that supports {@link NetworkTrafficListener}s.</p>
- * <p>{@link NetworkTrafficListener}s can be added and removed dynamically before and after this connector has
- * been started without causing {@link ConcurrentModificationException}s.</p>
+ * @deprecated use {@link org.eclipse.jetty.server.NetworkTrafficServerConnector} instead.
  */
-public class NetworkTrafficSelectChannelConnector extends SelectChannelConnector
+ at Deprecated
+public class NetworkTrafficSelectChannelConnector extends NetworkTrafficServerConnector
 {
-    private final List<NetworkTrafficListener> listeners = new CopyOnWriteArrayList<NetworkTrafficListener>();
+    public NetworkTrafficSelectChannelConnector(Server server)
+    {
+        super(server);
+    }
 
-    /**
-     * @param listener the listener to add
-     */
-    public void addNetworkTrafficListener(NetworkTrafficListener listener)
+    public NetworkTrafficSelectChannelConnector(Server server, ConnectionFactory connectionFactory, SslContextFactory sslContextFactory)
     {
-        listeners.add(listener);
+        super(server, connectionFactory, sslContextFactory);
     }
 
-    /**
-     * @param listener the listener to remove
-     */
-    public void removeNetworkTrafficListener(NetworkTrafficListener listener)
+    public NetworkTrafficSelectChannelConnector(Server server, ConnectionFactory connectionFactory)
     {
-        listeners.remove(listener);
+        super(server, connectionFactory);
     }
 
-    @Override
-    protected SelectChannelEndPoint newEndPoint(SocketChannel channel, SelectorManager.SelectSet selectSet, SelectionKey key) throws IOException
+    public NetworkTrafficSelectChannelConnector(Server server, Executor executor, Scheduler scheduler, ByteBufferPool pool, int acceptors, int selectors, ConnectionFactory... factories)
     {
-        NetworkTrafficSelectChannelEndPoint endPoint = new NetworkTrafficSelectChannelEndPoint(channel, selectSet, key, _maxIdleTime, listeners);
-        endPoint.setConnection(selectSet.getManager().newConnection(channel,endPoint, key.attachment()));
-        endPoint.notifyOpened();
-        return endPoint;
+        super(server, executor, scheduler, pool, acceptors, selectors, factories);
     }
 
-    @Override
-    protected void endPointClosed(SelectChannelEndPoint endpoint)
+    public NetworkTrafficSelectChannelConnector(Server server, SslContextFactory sslContextFactory)
     {
-        super.endPointClosed(endpoint);
-        ((NetworkTrafficSelectChannelEndPoint)endpoint).notifyClosed();
+        super(server, sslContextFactory);
     }
 }
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/nio/SelectChannelConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/nio/SelectChannelConnector.java
deleted file mode 100644
index 34e4c17..0000000
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/nio/SelectChannelConnector.java
+++ /dev/null
@@ -1,334 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.server.nio;
-
-import java.io.IOException;
-import java.net.InetSocketAddress;
-import java.net.Socket;
-import java.nio.channels.SelectionKey;
-import java.nio.channels.ServerSocketChannel;
-import java.nio.channels.SocketChannel;
-
-import org.eclipse.jetty.continuation.Continuation;
-import org.eclipse.jetty.io.AsyncEndPoint;
-import org.eclipse.jetty.io.ConnectedEndPoint;
-import org.eclipse.jetty.io.Connection;
-import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.io.nio.AsyncConnection;
-import org.eclipse.jetty.io.nio.SelectChannelEndPoint;
-import org.eclipse.jetty.io.nio.SelectorManager;
-import org.eclipse.jetty.io.nio.SelectorManager.SelectSet;
-import org.eclipse.jetty.server.AsyncHttpConnection;
-import org.eclipse.jetty.server.Request;
-import org.eclipse.jetty.util.thread.ThreadPool;
-
-/* ------------------------------------------------------------------------------- */
-/**
- * Selecting NIO connector.
- * <p>
- * This connector uses efficient NIO buffers with a non blocking threading model. Direct NIO buffers
- * are used and threads are only allocated to connections with requests. Synchronization is used to
- * simulate blocking for the servlet API, and any unflushed content at the end of request handling
- * is written asynchronously.
- * </p>
- * <p>
- * This connector is best used when there are a many connections that have idle periods.
- * </p>
- * <p>
- * When used with {@link org.eclipse.jetty.continuation.Continuation}, threadless waits are supported.
- * If a filter or servlet returns after calling {@link Continuation#suspend()} or when a
- * runtime exception is thrown from a call to {@link Continuation#undispatch()}, Jetty will
- * will not send a response to the client. Instead the thread is released and the Continuation is
- * placed on the timer queue. If the Continuation timeout expires, or it's
- * resume method is called, then the request is again allocated a thread and the request is retried.
- * The limitation of this approach is that request content is not available on the retried request,
- * thus if possible it should be read after the continuation or saved as a request attribute or as the
- * associated object of the Continuation instance.
- * </p>
- *
- * @org.apache.xbean.XBean element="nioConnector" description="Creates an NIO based socket connector"
- */
-public class SelectChannelConnector extends AbstractNIOConnector
-{
-    protected ServerSocketChannel _acceptChannel;
-    private int _lowResourcesConnections;
-    private int _lowResourcesMaxIdleTime;
-    private int _localPort=-1;
-
-    private final SelectorManager _manager = new ConnectorSelectorManager();
-
-    /* ------------------------------------------------------------------------------- */
-    /**
-     * Constructor.
-     *
-     */
-    public SelectChannelConnector()
-    {
-        _manager.setMaxIdleTime(getMaxIdleTime());
-        addBean(_manager,true);
-        setAcceptors(Math.max(1,(Runtime.getRuntime().availableProcessors()+3)/4));
-    }
-    
-    @Override
-    public void setThreadPool(ThreadPool pool)
-    {
-        super.setThreadPool(pool);
-        // preserve start order
-        removeBean(_manager);
-        addBean(_manager,true);
-    }
-    
-    /* ------------------------------------------------------------ */
-    @Override
-    public void accept(int acceptorID) throws IOException
-    {
-        ServerSocketChannel server;
-        synchronized(this)
-        {
-            server = _acceptChannel;
-        }
-
-        if (server!=null && server.isOpen() && _manager.isStarted())
-        {
-            SocketChannel channel = server.accept();
-            channel.configureBlocking(false);
-            Socket socket = channel.socket();
-            configure(socket);
-            _manager.register(channel);
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    public void close() throws IOException
-    {
-        synchronized(this)
-        {
-            if (_acceptChannel != null)
-            {
-                removeBean(_acceptChannel);
-                if (_acceptChannel.isOpen())
-                    _acceptChannel.close();
-            }
-            _acceptChannel = null;
-            _localPort=-2;
-        }
-    }
-
-    /* ------------------------------------------------------------------------------- */
-    @Override
-    public void customize(EndPoint endpoint, Request request) throws IOException
-    {
-        request.setTimeStamp(System.currentTimeMillis());
-        endpoint.setMaxIdleTime(_maxIdleTime);
-        super.customize(endpoint, request);
-    }
-
-    /* ------------------------------------------------------------------------------- */
-    @Override
-    public void persist(EndPoint endpoint) throws IOException
-    {
-        AsyncEndPoint aEndp = ((AsyncEndPoint)endpoint);
-        aEndp.setCheckForIdle(true);
-        super.persist(endpoint);
-    }
-
-    /* ------------------------------------------------------------ */
-    public SelectorManager getSelectorManager()
-    {
-        return _manager;
-    }
-
-    /* ------------------------------------------------------------ */
-    public synchronized Object getConnection()
-    {
-        return _acceptChannel;
-    }
-
-    /* ------------------------------------------------------------------------------- */
-    public int getLocalPort()
-    {
-        synchronized(this)
-        {
-            return _localPort;
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    public void open() throws IOException
-    {
-        synchronized(this)
-        {
-            if (_acceptChannel == null)
-            {
-                // Create a new server socket
-                _acceptChannel = ServerSocketChannel.open();
-                // Set to blocking mode
-                _acceptChannel.configureBlocking(true);
-
-                // Bind the server socket to the local host and port
-                _acceptChannel.socket().setReuseAddress(getReuseAddress());
-                InetSocketAddress addr = getHost()==null?new InetSocketAddress(getPort()):new InetSocketAddress(getHost(),getPort());
-                _acceptChannel.socket().bind(addr,getAcceptQueueSize());
-
-                _localPort=_acceptChannel.socket().getLocalPort();
-                if (_localPort<=0)
-                    throw new IOException("Server channel not bound");
-
-                addBean(_acceptChannel);
-            }
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public void setMaxIdleTime(int maxIdleTime)
-    {
-        _manager.setMaxIdleTime(maxIdleTime);
-        super.setMaxIdleTime(maxIdleTime);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return the lowResourcesConnections
-     */
-    public int getLowResourcesConnections()
-    {
-        return _lowResourcesConnections;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Set the number of connections, which if exceeded places this manager in low resources state.
-     * This is not an exact measure as the connection count is averaged over the select sets.
-     * @param lowResourcesConnections the number of connections
-     * @see #setLowResourcesMaxIdleTime(int)
-     */
-    public void setLowResourcesConnections(int lowResourcesConnections)
-    {
-        _lowResourcesConnections=lowResourcesConnections;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return the lowResourcesMaxIdleTime
-     */
-    @Override
-    public int getLowResourcesMaxIdleTime()
-    {
-        return _lowResourcesMaxIdleTime;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Set the period in ms that a connection is allowed to be idle when this there are more
-     * than {@link #getLowResourcesConnections()} connections.  This allows the server to rapidly close idle connections
-     * in order to gracefully handle high load situations.
-     * @param lowResourcesMaxIdleTime the period in ms that a connection is allowed to be idle when resources are low.
-     * @see #setMaxIdleTime(int)
-     */
-    @Override
-    public void setLowResourcesMaxIdleTime(int lowResourcesMaxIdleTime)
-    {
-        _lowResourcesMaxIdleTime=lowResourcesMaxIdleTime;
-        super.setLowResourcesMaxIdleTime(lowResourcesMaxIdleTime);
-    }
-
-
-    /* ------------------------------------------------------------ */
-    /*
-     * @see org.eclipse.jetty.server.server.AbstractConnector#doStart()
-     */
-    @Override
-    protected void doStart() throws Exception
-    {
-        _manager.setSelectSets(getAcceptors());
-        _manager.setMaxIdleTime(getMaxIdleTime());
-        _manager.setLowResourcesConnections(getLowResourcesConnections());
-        _manager.setLowResourcesMaxIdleTime(getLowResourcesMaxIdleTime());
-
-        super.doStart();
-    }
-
-    /* ------------------------------------------------------------ */
-    protected SelectChannelEndPoint newEndPoint(SocketChannel channel, SelectSet selectSet, SelectionKey key) throws IOException
-    {
-        SelectChannelEndPoint endp= new SelectChannelEndPoint(channel,selectSet,key, SelectChannelConnector.this._maxIdleTime);
-        endp.setConnection(selectSet.getManager().newConnection(channel,endp, key.attachment()));
-        return endp;
-    }
-
-    /* ------------------------------------------------------------------------------- */
-    protected void endPointClosed(SelectChannelEndPoint endpoint)
-    {
-        connectionClosed(endpoint.getConnection());
-    }
-
-    /* ------------------------------------------------------------------------------- */
-    protected AsyncConnection newConnection(SocketChannel channel,final AsyncEndPoint endpoint)
-    {
-        return new AsyncHttpConnection(SelectChannelConnector.this,endpoint,getServer());
-    }
-
-
-    /* ------------------------------------------------------------ */
-    /* ------------------------------------------------------------ */
-    /* ------------------------------------------------------------ */
-    private final class ConnectorSelectorManager extends SelectorManager
-    {
-        @Override
-        public boolean dispatch(Runnable task)
-        {
-            ThreadPool pool=getThreadPool();
-            if (pool==null)
-                pool=getServer().getThreadPool();
-            return pool.dispatch(task);
-        }
-
-        @Override
-        protected void endPointClosed(final SelectChannelEndPoint endpoint)
-        {
-            SelectChannelConnector.this.endPointClosed(endpoint);
-        }
-
-        @Override
-        protected void endPointOpened(SelectChannelEndPoint endpoint)
-        {
-            // TODO handle max connections and low resources
-            connectionOpened(endpoint.getConnection());
-        }
-
-        @Override
-        protected void endPointUpgraded(ConnectedEndPoint endpoint, Connection oldConnection)
-        {
-            connectionUpgraded(oldConnection,endpoint.getConnection());
-        }
-
-        @Override
-        public AsyncConnection newConnection(SocketChannel channel,AsyncEndPoint endpoint, Object attachment)
-        {
-            return SelectChannelConnector.this.newConnection(channel,endpoint);
-        }
-
-        @Override
-        protected SelectChannelEndPoint newEndPoint(SocketChannel channel, SelectSet selectSet, SelectionKey sKey) throws IOException
-        {
-            return SelectChannelConnector.this.newEndPoint(channel,selectSet,sKey);
-        }
-    }
-}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/nio/package-info.java b/jetty-server/src/main/java/org/eclipse/jetty/server/nio/package-info.java
new file mode 100644
index 0000000..74b1e1c
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/nio/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Server : Core Server Connector
+ */
+package org.eclipse.jetty.server.nio;
+
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/package-info.java b/jetty-server/src/main/java/org/eclipse/jetty/server/package-info.java
new file mode 100644
index 0000000..c838252
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Server : Core Server API
+ */
+package org.eclipse.jetty.server;
+
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSession.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSession.java
index 7c96659..9ed8b82 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSession.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSession.java
@@ -19,15 +19,10 @@
 package org.eclipse.jetty.server.session;
 
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.HashSet;
 import java.util.Iterator;
-import java.util.List;
 import java.util.Map;
 import java.util.Set;
-
 import javax.servlet.ServletContext;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpSessionActivationListener;
@@ -36,6 +31,7 @@ import javax.servlet.http.HttpSessionBindingListener;
 import javax.servlet.http.HttpSessionContext;
 import javax.servlet.http.HttpSessionEvent;
 
+import org.eclipse.jetty.server.SessionManager;
 import org.eclipse.jetty.util.log.Logger;
 
 /**
@@ -43,17 +39,16 @@ import org.eclipse.jetty.util.log.Logger;
  * <p>
  * Implements {@link javax.servlet.http.HttpSession} from the <code>javax.servlet</code> package.
  * </p>
- * 
+ *
  */
 @SuppressWarnings("deprecation")
 public abstract class AbstractSession implements AbstractSessionManager.SessionIf
 {
     final static Logger LOG = SessionHandler.LOG;
-    
+    public final static String SESSION_KNOWN_ONLY_TO_AUTHENTICATED="org.eclipse.jetty.security.sessionKnownOnlytoAuthenticated";
+    private  String _clusterId; // ID without any node (ie "worker") id appended
+    private  String _nodeId;    // ID of session with node(ie "worker") id appended
     private final AbstractSessionManager _manager;
-    private final String _clusterId; // ID unique within cluster
-    private final String _nodeId;    // ID unique within node
-    private final Map<String,Object> _attributes=new HashMap<String, Object>();
     private boolean _idChanged;
     private final long _created;
     private long _cookieSet;
@@ -66,12 +61,12 @@ public abstract class AbstractSession implements AbstractSessionManager.SessionI
     private int _requests;
 
 
-    
+
     /* ------------------------------------------------------------- */
     protected AbstractSession(AbstractSessionManager abstractSessionManager, HttpServletRequest request)
     {
         _manager = abstractSessionManager;
-        
+
         _newSession=true;
         _created=System.currentTimeMillis();
         _clusterId=_manager._sessionIdManager.newSessionId(request,_created);
@@ -98,7 +93,7 @@ public abstract class AbstractSession implements AbstractSessionManager.SessionI
         if (LOG.isDebugEnabled())
             LOG.debug("new session "+_nodeId+" "+_clusterId);
     }
-    
+
     /* ------------------------------------------------------------- */
     /**
      * asserts that the session is valid
@@ -110,6 +105,19 @@ public abstract class AbstractSession implements AbstractSessionManager.SessionI
     }
     
     /* ------------------------------------------------------------- */
+    /** Check to see if session has expired as at the time given.
+     * @param time
+     * @return
+     */
+    protected boolean checkExpiry(long time)
+    {
+        if (_maxIdleMs>0 && _lastAccessed>0 && _lastAccessed + _maxIdleMs < time)
+            return true;
+        return false;
+    }
+
+    /* ------------------------------------------------------------- */
+    @Override
     public AbstractSession getSession()
     {
         return this;
@@ -123,47 +131,23 @@ public abstract class AbstractSession implements AbstractSessionManager.SessionI
             return _accessed;
         }
     }
+
+    /* ------------------------------------------------------------- */
+    public abstract Map<String,Object> getAttributeMap();
+
+
+ 
     
-    /* ------------------------------------------------------------ */
-    public Object getAttribute(String name)
-    {
-        synchronized (this)
-        {
-            checkValid();
-            return _attributes.get(name);
-        }
-    }
-    
-    /* ------------------------------------------------------------ */
-    public int getAttributes()
-    {
-        synchronized (this)
-        {
-            checkValid();
-            return _attributes.size();
-        }
-    }
 
     /* ------------------------------------------------------------ */
-    @SuppressWarnings({ "unchecked" })
-    public Enumeration<String> getAttributeNames()
-    {
-        synchronized (this)
-        {
-            checkValid();
-            List<String> names=_attributes==null?Collections.EMPTY_LIST:new ArrayList<String>(_attributes.keySet());
-            return Collections.enumeration(names);
-        }
-    }
-    
+    public abstract int getAttributes();
+  
+
+ 
+
     /* ------------------------------------------------------------ */
-    public Set<String> getNames()
-    {
-        synchronized (this)
-        { 
-            return new HashSet<String>(_attributes.keySet());
-        }
-    }
+    public abstract Set<String> getNames();
+  
 
     /* ------------------------------------------------------------- */
     public long getCookieSetTime()
@@ -172,12 +156,15 @@ public abstract class AbstractSession implements AbstractSessionManager.SessionI
     }
 
     /* ------------------------------------------------------------- */
+    @Override
     public long getCreationTime() throws IllegalStateException
     {
+        checkValid();
         return _created;
     }
 
     /* ------------------------------------------------------------ */
+    @Override
     public String getId() throws IllegalStateException
     {
         return _manager._nodeIdInSessionId?_nodeId:_clusterId;
@@ -196,6 +183,7 @@ public abstract class AbstractSession implements AbstractSessionManager.SessionI
     }
 
     /* ------------------------------------------------------------- */
+    @Override
     public long getLastAccessedTime() throws IllegalStateException
     {
         checkValid();
@@ -209,6 +197,7 @@ public abstract class AbstractSession implements AbstractSessionManager.SessionI
     }
 
     /* ------------------------------------------------------------- */
+    @Override
     public int getMaxInactiveInterval()
     {
         return (int)(_maxIdleMs/1000);
@@ -218,6 +207,7 @@ public abstract class AbstractSession implements AbstractSessionManager.SessionI
     /*
      * @see javax.servlet.http.HttpSession#getServletContext()
      */
+    @Override
     public ServletContext getServletContext()
     {
         return _manager._context;
@@ -225,6 +215,7 @@ public abstract class AbstractSession implements AbstractSessionManager.SessionI
 
     /* ------------------------------------------------------------- */
     @Deprecated
+    @Override
     public HttpSessionContext getSessionContext() throws IllegalStateException
     {
         checkValid();
@@ -237,40 +228,39 @@ public abstract class AbstractSession implements AbstractSessionManager.SessionI
      *             {@link #getAttribute}
      */
     @Deprecated
+    @Override
     public Object getValue(String name) throws IllegalStateException
     {
         return getAttribute(name);
     }
 
+ 
+
+    /* ------------------------------------------------------------ */
+    public void renewId(HttpServletRequest request)
+    {
+        _manager._sessionIdManager.renewSessionId(getClusterId(), getNodeId(), request); 
+        setIdChanged(true);
+    }
+       
     /* ------------------------------------------------------------- */
-    /**
-     * @deprecated As of Version 2.2, this method is replaced by
-     *             {@link #getAttributeNames}
-     */
-    @Deprecated
-    public String[] getValueNames() throws IllegalStateException
+    public SessionManager getSessionManager()
     {
-        synchronized(this)
-        {
-            checkValid();
-            if (_attributes==null)
-                return new String[0];
-            String[] a=new String[_attributes.size()];
-            return (String[])_attributes.keySet().toArray(a);
-        }
+        return _manager;
     }
-    
+
     /* ------------------------------------------------------------ */
-    protected  Map<String,Object> getAttributeMap ()
+    protected void setClusterId (String clusterId)
     {
-        return _attributes;
+        _clusterId = clusterId;
     }
     
     /* ------------------------------------------------------------ */
-    protected void addAttributes(Map<String,Object> map)
+    protected void setNodeId (String nodeId)
     {
-        _attributes.putAll(map);
+        _nodeId = nodeId;
     }
+    
 
     /* ------------------------------------------------------------ */
     protected boolean access(long time)
@@ -283,7 +273,7 @@ public abstract class AbstractSession implements AbstractSessionManager.SessionI
             _lastAccessed=_accessed;
             _accessed=time;
 
-            if (_maxIdleMs>0 && _lastAccessed>0 && _lastAccessed + _maxIdleMs < time) 
+            if (checkExpiry(time))
             {
                 invalidate();
                 return false;
@@ -328,8 +318,10 @@ public abstract class AbstractSession implements AbstractSessionManager.SessionI
     }
 
     /* ------------------------------------------------------------- */
+    @Override
     public void invalidate() throws IllegalStateException
     {
+        checkValid();
         // remove session from context and invalidate other sessions with same ID.
         _manager.removeSession(this,true);
         doInvalidate();
@@ -340,7 +332,8 @@ public abstract class AbstractSession implements AbstractSessionManager.SessionI
     {
         try
         {
-            LOG.debug("invalidate {}",_clusterId);
+            if (LOG.isDebugEnabled())
+                LOG.debug("invalidate {}",_clusterId);
             if (isValid())
                 clearAttributes();
         }
@@ -355,35 +348,9 @@ public abstract class AbstractSession implements AbstractSessionManager.SessionI
     }
 
     /* ------------------------------------------------------------- */
-    public void clearAttributes() 
-    {
-        while (_attributes!=null && _attributes.size()>0)
-        {
-            ArrayList<String> keys;
-            synchronized(this)
-            {
-                keys=new ArrayList<String>(_attributes.keySet());
-            }
-
-            Iterator<String> iter=keys.iterator();
-            while (iter.hasNext())
-            {
-                String key=(String)iter.next();
-
-                Object value;
-                synchronized(this)
-                {
-                    value=doPutOrRemove(key,null);
-                }
-                unbindValue(key,value);
+    public abstract void clearAttributes();
+   
 
-                _manager.doSessionAttributeListeners(this,key,value,null);
-            }
-        } 
-        if (_attributes!=null)
-            _attributes.clear();
-    }
-    
     /* ------------------------------------------------------------- */
     public boolean isIdChanged()
     {
@@ -391,6 +358,7 @@ public abstract class AbstractSession implements AbstractSessionManager.SessionI
     }
 
     /* ------------------------------------------------------------- */
+    @Override
     public boolean isNew() throws IllegalStateException
     {
         checkValid();
@@ -403,12 +371,14 @@ public abstract class AbstractSession implements AbstractSessionManager.SessionI
      *             {@link #setAttribute}
      */
     @Deprecated
+    @Override
     public void putValue(java.lang.String name, java.lang.Object value) throws IllegalStateException
     {
-        setAttribute(name,value);
+        changeAttribute(name,value);
     }
 
     /* ------------------------------------------------------------ */
+    @Override
     public void removeAttribute(String name)
     {
         setAttribute(name,null);
@@ -420,25 +390,85 @@ public abstract class AbstractSession implements AbstractSessionManager.SessionI
      *             {@link #removeAttribute}
      */
     @Deprecated
+    @Override
     public void removeValue(java.lang.String name) throws IllegalStateException
     {
         removeAttribute(name);
     }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public Enumeration<String> getAttributeNames()
+    {
+        synchronized (this)
+        {
+            checkValid();
+            return doGetAttributeNames();
+        }
+    }
+    
+    /* ------------------------------------------------------------- */
+    /**
+     * @deprecated As of Version 2.2, this method is replaced by
+     *             {@link #getAttributeNames}
+     */
+    @Deprecated
+    @Override
+    public String[] getValueNames() throws IllegalStateException
+    {
+        synchronized(this)
+        {
+            checkValid();
+            Enumeration<String> anames = doGetAttributeNames();
+            if (anames == null)
+                return new String[0];
+            ArrayList<String> names = new ArrayList<String>();
+            while (anames.hasMoreElements())
+                names.add(anames.nextElement());
+            return names.toArray(new String[names.size()]);
+        }
+    }
+    
+
+    /* ------------------------------------------------------------ */
+    public abstract Object doPutOrRemove(String name, Object value);
+ 
 
     /* ------------------------------------------------------------ */
-    protected Object doPutOrRemove(String name, Object value)
+    public abstract Object doGet(String name);
+    
+    
+    /* ------------------------------------------------------------ */
+    public abstract Enumeration<String> doGetAttributeNames();
+    
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public Object getAttribute(String name)
     {
-        return value==null?_attributes.remove(name):_attributes.put(name,value);
+        synchronized (this)
+        {
+            checkValid();
+            return doGet(name);
+        }
     }
+   
 
     /* ------------------------------------------------------------ */
-    protected Object doGet(String name)
+    @Override
+    public void setAttribute(String name, Object value)
     {
-        return _attributes.get(name);
+        changeAttribute(name,value);
     }
     
     /* ------------------------------------------------------------ */
-    public void setAttribute(String name, Object value)
+    /**
+     * @param name
+     * @param value
+     * @deprecated use changeAttribute(String,Object) instead
+     * @return
+     */
+    protected boolean updateAttribute (String name, Object value)
     {
         Object old=null;
         synchronized (this)
@@ -446,7 +476,7 @@ public abstract class AbstractSession implements AbstractSessionManager.SessionI
             checkValid();
             old=doPutOrRemove(name,value);
         }
-        
+
         if (value==null || !value.equals(old))
         {
             if (old!=null)
@@ -455,9 +485,58 @@ public abstract class AbstractSession implements AbstractSessionManager.SessionI
                 bindValue(name,value);
 
             _manager.doSessionAttributeListeners(this,name,old,value);
-            
+            return true;
         }
+        return false;
     }
+    
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Either set (perhaps replace) or remove the value of the attribute
+     * in the session. The appropriate session attribute listeners are
+     * also called.
+     * 
+     * @param name
+     * @param value
+     * @return
+     */
+    protected Object changeAttribute (String name, Object value)
+    {
+        Object old=null;
+        synchronized (this)
+        {
+            checkValid();
+            old=doPutOrRemove(name,value);
+        }
+
+        callSessionAttributeListeners(name, value, old);
+
+        return old;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Call binding and attribute listeners based on the new and old
+     * values of the attribute.
+     * 
+     * @param name name of the attribute
+     * @param newValue  new value of the attribute
+     * @param oldValue previous value of the attribute
+     */
+    protected void callSessionAttributeListeners (String name, Object newValue, Object oldValue)
+    {
+        if (newValue==null || !newValue.equals(oldValue))
+        {
+            if (oldValue!=null)
+                unbindValue(name,oldValue);
+            if (newValue!=null)
+                bindValue(name,newValue);
+
+            _manager.doSessionAttributeListeners(this,name,oldValue,newValue);
+        }
+    }
+  
 
     /* ------------------------------------------------------------- */
     public void setIdChanged(boolean changed)
@@ -466,6 +545,7 @@ public abstract class AbstractSession implements AbstractSessionManager.SessionI
     }
 
     /* ------------------------------------------------------------- */
+    @Override
     public void setMaxInactiveInterval(int secs)
     {
         _maxIdleMs=(long)secs*1000L;
@@ -518,7 +598,7 @@ public abstract class AbstractSession implements AbstractSessionManager.SessionI
             _requests=requests;
         }
     }
-    
+
     /* ------------------------------------------------------------- */
     /** If value implements HttpSessionBindingListener, call valueUnbound() */
     public void unbindValue(java.lang.String name, Object value)
@@ -533,7 +613,7 @@ public abstract class AbstractSession implements AbstractSessionManager.SessionI
         synchronized(this)
         {
             HttpSessionEvent event = new HttpSessionEvent(this);
-            for (Iterator<Object> iter = _attributes.values().iterator(); iter.hasNext();)
+            for (Iterator<Object> iter = getAttributeMap().values().iterator(); iter.hasNext();)
             {
                 Object value = iter.next();
                 if (value instanceof HttpSessionActivationListener)
@@ -551,7 +631,7 @@ public abstract class AbstractSession implements AbstractSessionManager.SessionI
         synchronized(this)
         {
             HttpSessionEvent event = new HttpSessionEvent(this);
-            for (Iterator<Object> iter = _attributes.values().iterator(); iter.hasNext();)
+            for (Iterator<Object> iter = getAttributeMap().values().iterator(); iter.hasNext();)
             {
                 Object value = iter.next();
                 if (value instanceof HttpSessionActivationListener)
@@ -562,6 +642,6 @@ public abstract class AbstractSession implements AbstractSessionManager.SessionI
             }
         }
     }
-    
-    
+
+
 }
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionIdManager.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionIdManager.java
index acac346..ba55a2e 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionIdManager.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionIdManager.java
@@ -32,18 +32,19 @@ public abstract class AbstractSessionIdManager extends AbstractLifeCycle impleme
 {
     private static final Logger LOG = Log.getLogger(AbstractSessionIdManager.class);
 
-    private final static String __NEW_SESSION_ID="org.eclipse.jetty.server.newSessionId";  
-    
+    private final static String __NEW_SESSION_ID="org.eclipse.jetty.server.newSessionId";
+
     protected Random _random;
     protected boolean _weakRandom;
     protected String _workerName;
+    protected String _workerAttr;
     protected long _reseed=100000L;
-    
+
     /* ------------------------------------------------------------ */
     public AbstractSessionIdManager()
     {
     }
-    
+
     /* ------------------------------------------------------------ */
     public AbstractSessionIdManager(Random random)
     {
@@ -53,29 +54,12 @@ public abstract class AbstractSessionIdManager extends AbstractLifeCycle impleme
 
     /* ------------------------------------------------------------ */
     /**
-     * @return the reseed probability
-     */
-    public long getReseed()
-    {
-        return _reseed;
-    }
-
-    /* ------------------------------------------------------------ */
-    /** Set the reseed probability.
-     * @param reseed  If non zero then when a random long modulo the reseed value == 1, the {@link SecureRandom} will be reseeded.
-     */
-    public void setReseed(long reseed)
-    {
-        _reseed = reseed;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
      * Get the workname. If set, the workername is dot appended to the session
      * ID and can be used to assist session affinity in a load balancer.
-     * 
+     *
      * @return String or null
      */
+    @Override
     public String getWorkerName()
     {
         return _workerName;
@@ -85,11 +69,16 @@ public abstract class AbstractSessionIdManager extends AbstractLifeCycle impleme
     /**
      * Set the workname. If set, the workername is dot appended to the session
      * ID and can be used to assist session affinity in a load balancer.
-     * 
+     * A worker name starting with $ is used as a request attribute name to
+     * lookup the worker name that can be dynamically set by a request
+     * customiser.
+     *
      * @param workerName
      */
     public void setWorkerName(String workerName)
     {
+        if (isRunning())
+            throw new IllegalStateException(getState());
         if (workerName.contains("."))
             throw new IllegalArgumentException("Name cannot contain '.'");
         _workerName=workerName;
@@ -107,94 +96,131 @@ public abstract class AbstractSessionIdManager extends AbstractLifeCycle impleme
         _random=random;
         _weakRandom=false;
     }
-    
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the reseed probability
+     */
+    public long getReseed()
+    {
+        return _reseed;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the reseed probability.
+     * @param reseed  If non zero then when a random long modulo the reseed value == 1, the {@link SecureRandom} will be reseeded.
+     */
+    public void setReseed(long reseed)
+    {
+        _reseed = reseed;
+    }
+
     /* ------------------------------------------------------------ */
-    /** 
+    /**
      * Create a new session id if necessary.
-     * 
+     *
      * @see org.eclipse.jetty.server.SessionIdManager#newSessionId(javax.servlet.http.HttpServletRequest, long)
      */
+    @Override
     public String newSessionId(HttpServletRequest request, long created)
     {
         synchronized (this)
         {
-            if (request!=null)
+            if (request==null)
+                return newSessionId(created);
+
+            // A requested session ID can only be used if it is in use already.
+            String requested_id=request.getRequestedSessionId();
+            if (requested_id!=null)
+            {
+                String cluster_id=getClusterId(requested_id);
+                if (idInUse(cluster_id))
+                    return cluster_id;
+            }
+
+            // Else reuse any new session ID already defined for this request.
+            String new_id=(String)request.getAttribute(__NEW_SESSION_ID);
+            if (new_id!=null&&idInUse(new_id))
+                return new_id;
+
+            // pick a new unique ID!
+            String id = newSessionId(request.hashCode());
+
+            request.setAttribute(__NEW_SESSION_ID,id);
+            return id;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public String newSessionId(long seedTerm)
+    {
+        // pick a new unique ID!
+        String id=null;
+        while (id==null||id.length()==0||idInUse(id))
+        {
+            long r0=_weakRandom
+                    ?(hashCode()^Runtime.getRuntime().freeMemory()^_random.nextInt()^((seedTerm)<<32))
+                    :_random.nextLong();
+            if (r0<0)
+                r0=-r0;
+                    
+            // random chance to reseed
+            if (_reseed>0 && (r0%_reseed)== 1L)
             {
-                // A requested session ID can only be used if it is in use already.
-                String requested_id=request.getRequestedSessionId();
-                if (requested_id!=null)
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Reseeding {}",this);
+                if (_random instanceof SecureRandom)
                 {
-                    String cluster_id=getClusterId(requested_id);
-                    if (idInUse(cluster_id))
-                        return cluster_id;
+                    SecureRandom secure = (SecureRandom)_random;
+                    secure.setSeed(secure.generateSeed(8));
+                }
+                else
+                {
+                    _random.setSeed(_random.nextLong()^System.currentTimeMillis()^seedTerm^Runtime.getRuntime().freeMemory());
                 }
-
-                // Else reuse any new session ID already defined for this request.
-                String new_id=(String)request.getAttribute(__NEW_SESSION_ID);
-                if (new_id!=null&&idInUse(new_id))
-                    return new_id;
             }
             
-            // pick a new unique ID!
-            String id=null;
-            while (id==null||id.length()==0||idInUse(id))
-            {
-                long r0=_weakRandom
-                ?(hashCode()^Runtime.getRuntime().freeMemory()^_random.nextInt()^(((long)request.hashCode())<<32))
-                :_random.nextLong();
-                if (r0<0)
-                    r0=-r0;
-
-		// random chance to reseed
-		if (_reseed>0 && (r0%_reseed)== 1L)
-		{
-		    LOG.debug("Reseeding {}",this);
-		    if (_random instanceof SecureRandom)
-		    {
-			SecureRandom secure = (SecureRandom)_random;
-			secure.setSeed(secure.generateSeed(8));
-		    }
-		    else
-		    {
-			_random.setSeed(_random.nextLong()^System.currentTimeMillis()^request.hashCode()^Runtime.getRuntime().freeMemory());
-		    }
-		}
-
-                long r1=_weakRandom
-                ?(hashCode()^Runtime.getRuntime().freeMemory()^_random.nextInt()^(((long)request.hashCode())<<32))
+            long r1=_weakRandom
+                ?(hashCode()^Runtime.getRuntime().freeMemory()^_random.nextInt()^((seedTerm)<<32))
                 :_random.nextLong();
-                if (r1<0)
-                    r1=-r1;
-                id=Long.toString(r0,36)+Long.toString(r1,36);
-                
-                //add in the id of the node to ensure unique id across cluster
-                //NOTE this is different to the node suffix which denotes which node the request was received on
-                if (_workerName!=null)
-                    id=_workerName + id;
-            }
+            if (r1<0)
+                r1=-r1;
+            
+            id=Long.toString(r0,36)+Long.toString(r1,36);
 
-            request.setAttribute(__NEW_SESSION_ID,id);
-            return id;
+            //add in the id of the node to ensure unique id across cluster
+            //NOTE this is different to the node suffix which denotes which node the request was received on
+            if (_workerName!=null)
+                id=_workerName + id;
+    
         }
+        return id;
     }
 
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public abstract void renewSessionId(String oldClusterId, String oldNodeId, HttpServletRequest request);
+
+    
     /* ------------------------------------------------------------ */
     @Override
     protected void doStart() throws Exception
     {
        initRandom();
+       _workerAttr=(_workerName!=null && _workerName.startsWith("$"))?_workerName.substring(1):null;
     }
-    
+
     /* ------------------------------------------------------------ */
     @Override
     protected void doStop() throws Exception
     {
     }
-    
+
     /* ------------------------------------------------------------ */
     /**
      * Set up a random number generator for the sessionids.
-     * 
+     *
      * By preference, use a SecureRandom but allow to be injected.
      */
     public void initRandom ()
@@ -213,8 +239,42 @@ public abstract class AbstractSessionIdManager extends AbstractLifeCycle impleme
             }
         }
         else
-            _random.setSeed(_random.nextLong()^System.currentTimeMillis()^hashCode()^Runtime.getRuntime().freeMemory()); 
+            _random.setSeed(_random.nextLong()^System.currentTimeMillis()^hashCode()^Runtime.getRuntime().freeMemory());
     }
+
+    /** Get the session ID with any worker ID.
+     *
+     * @param clusterId
+     * @param request
+     * @return sessionId plus any worker ID.
+     */
+    @Override
+    public String getNodeId(String clusterId, HttpServletRequest request)
+    {
+        if (_workerName!=null)
+        {
+            if (_workerAttr==null)
+                return clusterId+'.'+_workerName;
+
+            String worker=(String)request.getAttribute(_workerAttr);
+            if (worker!=null)
+                return clusterId+'.'+worker;
+        }
     
-    
+        return clusterId;
+    }
+
+    /** Get the session ID without any worker ID.
+     *
+     * @param nodeId the node id
+     * @return sessionId without any worker ID.
+     */
+    @Override
+    public String getClusterId(String nodeId)
+    {
+        int dot=nodeId.lastIndexOf('.');
+        return (dot>0)?nodeId.substring(0,dot):nodeId;
+    }
+
+
 }
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionManager.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionManager.java
index 6497eff..5a2e1b4 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionManager.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionManager.java
@@ -25,13 +25,10 @@ import java.util.Collections;
 import java.util.Enumeration;
 import java.util.EventListener;
 import java.util.HashSet;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.CopyOnWriteArrayList;
 
-import javax.servlet.ServletRequest;
 import javax.servlet.SessionCookieConfig;
 import javax.servlet.SessionTrackingMode;
 import javax.servlet.http.HttpServletRequest;
@@ -40,16 +37,18 @@ import javax.servlet.http.HttpSessionAttributeListener;
 import javax.servlet.http.HttpSessionBindingEvent;
 import javax.servlet.http.HttpSessionContext;
 import javax.servlet.http.HttpSessionEvent;
+import javax.servlet.http.HttpSessionIdListener;
 import javax.servlet.http.HttpSessionListener;
 
 import org.eclipse.jetty.http.HttpCookie;
-import org.eclipse.jetty.server.AbstractConnector;
-import org.eclipse.jetty.server.Request;
 import org.eclipse.jetty.server.Server;
 import org.eclipse.jetty.server.SessionIdManager;
 import org.eclipse.jetty.server.SessionManager;
 import org.eclipse.jetty.server.handler.ContextHandler;
-import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.annotation.ManagedOperation;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
 import org.eclipse.jetty.util.log.Logger;
 import org.eclipse.jetty.util.statistic.CounterStatistic;
 import org.eclipse.jetty.util.statistic.SampleStatistic;
@@ -65,7 +64,8 @@ import org.eclipse.jetty.util.statistic.SampleStatistic;
  * <p>
  */
 @SuppressWarnings("deprecation")
-public abstract class AbstractSessionManager extends AbstractLifeCycle implements SessionManager
+ at ManagedObject("Abstract Session Manager")
+public abstract class AbstractSessionManager extends ContainerLifeCycle implements SessionManager
 {
     final static Logger __log = SessionHandler.LOG;
 
@@ -73,26 +73,28 @@ public abstract class AbstractSessionManager extends AbstractLifeCycle implement
         Collections.unmodifiableSet(
             new HashSet<SessionTrackingMode>(
                     Arrays.asList(new SessionTrackingMode[]{SessionTrackingMode.COOKIE,SessionTrackingMode.URL})));
-        
-    public final static String SESSION_KNOWN_ONLY_TO_AUTHENTICATED="org.eclipse.jetty.security.sessionKnownOnlytoAuthenticated";
+
     
+
     /* ------------------------------------------------------------ */
     public final static int __distantFuture=60*60*24*7*52*20;
 
     static final HttpSessionContext __nullSessionContext=new HttpSessionContext()
     {
+        @Override
         public HttpSession getSession(String sessionId)
         {
             return null;
         }
-        
+
+        @Override
         @SuppressWarnings({ "rawtypes", "unchecked" })
         public Enumeration getIds()
         {
             return Collections.enumeration(Collections.EMPTY_LIST);
         }
     };
-    
+
     private boolean _usingCookies=true;
 
     /* ------------------------------------------------------------ */
@@ -107,6 +109,7 @@ public abstract class AbstractSessionManager extends AbstractLifeCycle implement
 
     protected final List<HttpSessionAttributeListener> _sessionAttributeListeners = new CopyOnWriteArrayList<HttpSessionAttributeListener>();
     protected final List<HttpSessionListener> _sessionListeners= new CopyOnWriteArrayList<HttpSessionListener>();
+    protected final List<HttpSessionIdListener> _sessionIdListeners = new CopyOnWriteArrayList<HttpSessionIdListener>();
 
     protected ClassLoader _loader;
     protected ContextHandler.Context _context;
@@ -124,32 +127,11 @@ public abstract class AbstractSessionManager extends AbstractLifeCycle implement
     public Set<SessionTrackingMode> _sessionTrackingModes;
 
     private boolean _usingURLs;
-    
+
     protected final CounterStatistic _sessionsStats = new CounterStatistic();
     protected final SampleStatistic _sessionTimeStats = new SampleStatistic();
-    
-    
-    /* ------------------------------------------------------------ */
-    public static HttpSession renewSession (HttpServletRequest request, HttpSession httpSession, boolean authenticated)
-    {
-        Map<String,Object> attributes = new HashMap<String, Object>();
 
-        for (Enumeration<String> e=httpSession.getAttributeNames();e.hasMoreElements();)
-        {
-            String name=e.nextElement();
-            attributes.put(name,httpSession.getAttribute(name));
-            httpSession.removeAttribute(name);
-        }
 
-        httpSession.invalidate();       
-        httpSession = request.getSession(true);
-        if (authenticated)
-            httpSession.setAttribute(SESSION_KNOWN_ONLY_TO_AUTHENTICATED, Boolean.TRUE);
-        for (Map.Entry<String, Object> entry: attributes.entrySet())
-            httpSession.setAttribute(entry.getKey(),entry.getValue());
-        return httpSession;
-    }
-    
     /* ------------------------------------------------------------ */
     public AbstractSessionManager()
     {
@@ -167,18 +149,21 @@ public abstract class AbstractSessionManager extends AbstractLifeCycle implement
     {
         return _context.getContextHandler();
     }
-    
+
+    @ManagedAttribute("path of the session cookie, or null for default")
     public String getSessionPath()
     {
         return _sessionPath;
     }
 
+    @ManagedAttribute("if greater the zero, the time in seconds a session cookie will last for")
     public int getMaxCookieAge()
     {
         return _maxCookieAge;
     }
 
     /* ------------------------------------------------------------ */
+    @Override
     public HttpCookie access(HttpSession session,boolean secure)
     {
         long now=System.currentTimeMillis();
@@ -204,22 +189,31 @@ public abstract class AbstractSessionManager extends AbstractLifeCycle implement
     }
 
     /* ------------------------------------------------------------ */
+    @Override
     public void addEventListener(EventListener listener)
     {
         if (listener instanceof HttpSessionAttributeListener)
             _sessionAttributeListeners.add((HttpSessionAttributeListener)listener);
         if (listener instanceof HttpSessionListener)
             _sessionListeners.add((HttpSessionListener)listener);
+        if (listener instanceof HttpSessionIdListener)
+            _sessionIdListeners.add((HttpSessionIdListener)listener);
+        addBean(listener,false);
     }
 
     /* ------------------------------------------------------------ */
+    @Override
     public void clearEventListeners()
     {
+        for (EventListener e :getBeans(EventListener.class))
+            removeBean(e);
         _sessionAttributeListeners.clear();
         _sessionListeners.clear();
+        _sessionIdListeners.clear();
     }
 
     /* ------------------------------------------------------------ */
+    @Override
     public void complete(HttpSession session)
     {
         AbstractSession s = ((SessionIf)session).getSession();
@@ -233,21 +227,38 @@ public abstract class AbstractSessionManager extends AbstractLifeCycle implement
         _context=ContextHandler.getCurrentContext();
         _loader=Thread.currentThread().getContextClassLoader();
 
-        if (_sessionIdManager==null)
+        final Server server=getSessionHandler().getServer();
+        synchronized (server)
         {
-            final Server server=getSessionHandler().getServer();
-            synchronized (server)
+            if (_sessionIdManager==null)
             {
                 _sessionIdManager=server.getSessionIdManager();
                 if (_sessionIdManager==null)
                 {
-                    _sessionIdManager=new HashSessionIdManager();
-                    server.setSessionIdManager(_sessionIdManager);
+                    //create a default SessionIdManager and set it as the shared
+                    //SessionIdManager for the Server, being careful NOT to use
+                    //the webapp context's classloader, otherwise if the context
+                    //is stopped, the classloader is leaked.
+                    ClassLoader serverLoader = server.getClass().getClassLoader();
+                    try
+                    {
+                        Thread.currentThread().setContextClassLoader(serverLoader);
+                        _sessionIdManager=new HashSessionIdManager();
+                        server.setSessionIdManager(_sessionIdManager);
+                        server.manage(_sessionIdManager);
+                        _sessionIdManager.start();
+                    }
+                    finally
+                    {
+                        Thread.currentThread().setContextClassLoader(_loader);
+                    }
                 }
+
+                // server session id is never managed by this manager
+                addBean(_sessionIdManager,false);
             }
         }
-        if (!_sessionIdManager.isStarted())
-            _sessionIdManager.start();
+        
 
         // Look for a session cookie name
         if (_context!=null)
@@ -275,7 +286,7 @@ public abstract class AbstractSessionManager extends AbstractLifeCycle implement
             // set up the sessionPath if it isn't already
             if (_sessionPath==null)
                 _sessionPath=_context.getInitParameter(SessionManager.__SessionPathProperty);
-            
+
             tmp=_context.getInitParameter(SessionManager.__CheckRemoteSessionEncoding);
             if (tmp!=null)
                 _checkingRemoteSessionIdEncoding=Boolean.parseBoolean(tmp);
@@ -290,7 +301,7 @@ public abstract class AbstractSessionManager extends AbstractLifeCycle implement
     {
         super.doStop();
 
-        invalidateSessions();
+        shutdownSessions();
 
         _loader=null;
     }
@@ -299,12 +310,15 @@ public abstract class AbstractSessionManager extends AbstractLifeCycle implement
     /**
      * @return Returns the httpOnly.
      */
+    @Override
+    @ManagedAttribute("true if cookies use the http only flag")
     public boolean getHttpOnly()
     {
         return _httpOnly;
     }
 
     /* ------------------------------------------------------------ */
+    @Override
     public HttpSession getHttpSession(String nodeId)
     {
         String cluster_id = getSessionIdManager().getClusterId(nodeId);
@@ -317,18 +331,10 @@ public abstract class AbstractSessionManager extends AbstractLifeCycle implement
 
     /* ------------------------------------------------------------ */
     /**
-     * @return Returns the metaManager used for cross context session management
-     * @deprecated Use {@link #getSessionIdManager()}
-     */
-    public SessionIdManager getIdManager()
-    {
-        return getSessionIdManager();
-    }
-    
-    /* ------------------------------------------------------------ */
-    /**
      * @return Returns the SessionIdManager used for cross context session management
      */
+    @Override
+    @ManagedAttribute("Session ID Manager")
     public SessionIdManager getSessionIdManager()
     {
         return _sessionIdManager;
@@ -340,25 +346,17 @@ public abstract class AbstractSessionManager extends AbstractLifeCycle implement
      * @return seconds
      */
     @Override
+    @ManagedAttribute("defailt maximum time a session may be idle for (in s)")
     public int getMaxInactiveInterval()
     {
         return _dftMaxIdleSecs;
     }
-    
-    /* ------------------------------------------------------------ */
-    /**
-     * @see #getSessionsMax()
-     */
-    @Deprecated
-    public int getMaxSessions()
-    {
-        return getSessionsMax();
-    }
 
     /* ------------------------------------------------------------ */
     /**
      * @return maximum number of sessions
      */
+    @ManagedAttribute("maximum number of simultaneous sessions")
     public int getSessionsMax()
     {
         return (int)_sessionsStats.getMax();
@@ -368,49 +366,31 @@ public abstract class AbstractSessionManager extends AbstractLifeCycle implement
     /**
      * @return total number of sessions
      */
+    @ManagedAttribute("total number of sessions")
     public int getSessionsTotal()
     {
         return (int)_sessionsStats.getTotal();
     }
 
     /* ------------------------------------------------------------ */
-    /**
-     * @deprecated use {@link #getSessionIdManager()}
-     */
-    @Deprecated
-    public SessionIdManager getMetaManager()
-    {
-        return getSessionIdManager();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @deprecated always returns 0. no replacement available.
-     */
-    @Deprecated
-    public int getMinSessions()
-    {
-        return 0;
-    }
-
-    /* ------------------------------------------------------------ */
+    @ManagedAttribute("time before a session cookie is re-set (in s)")
     public int getRefreshCookieAge()
     {
         return _refreshCookieAge;
     }
 
-
     /* ------------------------------------------------------------ */
     /**
      * @return same as SessionCookieConfig.getSecure(). If true, session
      * cookies are ALWAYS marked as secure. If false, a session cookie is
      * ONLY marked as secure if _secureRequestOnly == true and it is a HTTPS request.
      */
+    @ManagedAttribute("if true, secure cookie flag is set on session cookies")
     public boolean getSecureCookies()
     {
         return _secureCookies;
     }
-    
+
     /* ------------------------------------------------------------ */
     /**
      * @return true if session cookie is to be marked as secure only on HTTPS requests
@@ -419,11 +399,10 @@ public abstract class AbstractSessionManager extends AbstractLifeCycle implement
     {
         return _secureRequestOnly;
     }
-    
-    
+
+
     /* ------------------------------------------------------------ */
     /**
-     * @return if true, session cookie will be marked as secure only iff 
      * HTTPS request. Can be overridden by setting SessionCookieConfig.setSecure(true),
      * in which case the session cookie will be marked as secure on both HTTPS and HTTP.
      */
@@ -432,71 +411,71 @@ public abstract class AbstractSessionManager extends AbstractLifeCycle implement
         _secureRequestOnly = secureRequestOnly;
     }
 
-    
-    
     /* ------------------------------------------------------------ */
+    @ManagedAttribute("the set session cookie")
     public String getSessionCookie()
     {
         return _sessionCookie;
     }
 
     /* ------------------------------------------------------------ */
-    /** 
+    /**
      * A sessioncookie is marked as secure IFF any of the following conditions are true:
      * <ol>
      * <li>SessionCookieConfig.setSecure == true</li>
      * <li>SessionCookieConfig.setSecure == false && _secureRequestOnly==true && request is HTTPS</li>
      * </ol>
      * According to SessionCookieConfig javadoc, case 1 can be used when:
-     * "... even though the request that initiated the session came over HTTP, 
-     * is to support a topology where the web container is front-ended by an 
-     * SSL offloading load balancer. In this case, the traffic between the client 
-     * and the load balancer will be over HTTPS, whereas the traffic between the 
+     * "... even though the request that initiated the session came over HTTP,
+     * is to support a topology where the web container is front-ended by an
+     * SSL offloading load balancer. In this case, the traffic between the client
+     * and the load balancer will be over HTTPS, whereas the traffic between the
      * load balancer and the web container will be over HTTP."
-     * 
+     *
      * For case 2, you can use _secureRequestOnly to determine if you want the
-     * Servlet Spec 3.0  default behaviour when SessionCookieConfig.setSecure==false, 
+     * Servlet Spec 3.0  default behaviour when SessionCookieConfig.setSecure==false,
      * which is:
-     * "they shall be marked as secure only if the request that initiated the 
+     * "they shall be marked as secure only if the request that initiated the
      * corresponding session was also secure"
-     * 
+     *
      * The default for _secureRequestOnly is true, which gives the above behaviour. If
      * you set it to false, then a session cookie is NEVER marked as secure, even if
      * the initiating request was secure.
-     * 
+     *
      * @see org.eclipse.jetty.server.SessionManager#getSessionCookie(javax.servlet.http.HttpSession, java.lang.String, boolean)
      */
+    @Override
     public HttpCookie getSessionCookie(HttpSession session, String contextPath, boolean requestIsSecure)
     {
         if (isUsingCookies())
         {
-            String sessionPath = (_sessionPath==null) ? contextPath : _sessionPath;
+            String sessionPath = (_cookieConfig.getPath()==null) ? contextPath : _cookieConfig.getPath();
             sessionPath = (sessionPath==null||sessionPath.length()==0) ? "/" : sessionPath;
             String id = getNodeId(session);
             HttpCookie cookie = null;
             if (_sessionComment == null)
             {
                 cookie = new HttpCookie(
-                                        _sessionCookie,
+                                        _cookieConfig.getName(),
                                         id,
-                                        _sessionDomain,
+                                        _cookieConfig.getDomain(),
                                         sessionPath,
                                         _cookieConfig.getMaxAge(),
                                         _cookieConfig.isHttpOnly(),
-                                        _cookieConfig.isSecure() || (isSecureRequestOnly() && requestIsSecure));                  
+                                        _cookieConfig.isSecure() || (isSecureRequestOnly() && requestIsSecure));
             }
             else
             {
                 cookie = new HttpCookie(
-                                        _sessionCookie,
+                                        _cookieConfig.getName(),
                                         id,
-                                        _sessionDomain,
+                                        _cookieConfig.getDomain(),
                                         sessionPath,
                                         _cookieConfig.getMaxAge(),
                                         _cookieConfig.isHttpOnly(),
                                         _cookieConfig.isSecure() || (isSecureRequestOnly() && requestIsSecure),
                                         _sessionComment,
-                                        1);    
+                                        1);
             }
 
             return cookie;
@@ -504,6 +483,7 @@ public abstract class AbstractSessionManager extends AbstractLifeCycle implement
         return null;
     }
 
+    @ManagedAttribute("domain of the session cookie, or null for the default")
     public String getSessionDomain()
     {
         return _sessionDomain;
@@ -519,30 +499,22 @@ public abstract class AbstractSessionManager extends AbstractLifeCycle implement
     }
 
     /* ------------------------------------------------------------ */
-    /**
-     * @deprecated  Need to review if it is needed.
-     */
-    @SuppressWarnings("rawtypes")
-    public Map getSessionMap()
-    {
-        throw new UnsupportedOperationException();
-    }
-
-   
-
-    /* ------------------------------------------------------------ */
+    @ManagedAttribute("number of currently active sessions")
     public int getSessions()
     {
         return (int)_sessionsStats.getCurrent();
     }
 
     /* ------------------------------------------------------------ */
+    @Override
+    @ManagedAttribute("name of use for URL session tracking")
     public String getSessionIdPathParameterName()
     {
         return _sessionIdPathParameterName;
     }
 
     /* ------------------------------------------------------------ */
+    @Override
     public String getSessionIdPathParameterNamePrefix()
     {
         return _sessionIdPathParameterNamePrefix;
@@ -552,12 +524,14 @@ public abstract class AbstractSessionManager extends AbstractLifeCycle implement
     /**
      * @return Returns the usingCookies.
      */
+    @Override
     public boolean isUsingCookies()
     {
         return _usingCookies;
     }
 
     /* ------------------------------------------------------------ */
+    @Override
     public boolean isValid(HttpSession session)
     {
         AbstractSession s = ((SessionIf)session).getSession();
@@ -565,6 +539,7 @@ public abstract class AbstractSessionManager extends AbstractLifeCycle implement
     }
 
     /* ------------------------------------------------------------ */
+    @Override
     public String getClusterId(HttpSession session)
     {
         AbstractSession s = ((SessionIf)session).getSession();
@@ -572,6 +547,7 @@ public abstract class AbstractSessionManager extends AbstractLifeCycle implement
     }
 
     /* ------------------------------------------------------------ */
+    @Override
     public String getNodeId(HttpSession session)
     {
         AbstractSession s = ((SessionIf)session).getSession();
@@ -582,6 +558,7 @@ public abstract class AbstractSessionManager extends AbstractLifeCycle implement
     /**
      * Create a new HttpSession for a request
      */
+    @Override
     public HttpSession newHttpSession(HttpServletRequest request)
     {
         AbstractSession session=newSession(request);
@@ -591,28 +568,23 @@ public abstract class AbstractSessionManager extends AbstractLifeCycle implement
     }
 
     /* ------------------------------------------------------------ */
+    @Override
     public void removeEventListener(EventListener listener)
     {
         if (listener instanceof HttpSessionAttributeListener)
             _sessionAttributeListeners.remove(listener);
         if (listener instanceof HttpSessionListener)
             _sessionListeners.remove(listener);
+        if (listener instanceof HttpSessionIdListener)
+            _sessionIdListeners.remove(listener);
+        removeBean(listener);
     }
     
     /* ------------------------------------------------------------ */
     /**
-     * @see #statsReset()
-     */
-    @Deprecated
-    public void resetStats()
-    {
-        statsReset();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
      * Reset statistics values
      */
+    @ManagedOperation(value="reset statistics", impact="ACTION")
     public void statsReset()
     {
         _sessionsStats.reset(getSessions());
@@ -632,61 +604,50 @@ public abstract class AbstractSessionManager extends AbstractLifeCycle implement
     /* ------------------------------------------------------------ */
     /**
      * @param metaManager The metaManager used for cross context session management.
-     * @deprecated use {@link #setSessionIdManager(SessionIdManager)}
-     */
-    public void setIdManager(SessionIdManager metaManager)
-    {
-        setSessionIdManager(metaManager);
-    }
-    
-    /* ------------------------------------------------------------ */
-    /**
-     * @param metaManager The metaManager used for cross context session management.
      */
+    @Override
     public void setSessionIdManager(SessionIdManager metaManager)
     {
+        updateBean(_sessionIdManager, metaManager);
         _sessionIdManager=metaManager;
     }
 
-
-
     /* ------------------------------------------------------------ */
     /**
      * @param seconds
      */
+    @Override
     public void setMaxInactiveInterval(int seconds)
     {
         _dftMaxIdleSecs=seconds;
     }
 
-
     /* ------------------------------------------------------------ */
     public void setRefreshCookieAge(int ageInSeconds)
     {
         _refreshCookieAge=ageInSeconds;
     }
 
-
-
+    /* ------------------------------------------------------------ */
     public void setSessionCookie(String cookieName)
     {
         _sessionCookie=cookieName;
     }
 
-
-
     /* ------------------------------------------------------------ */
     /**
      * @param sessionHandler
      *            The sessionHandler to set.
      */
+    @Override
     public void setSessionHandler(SessionHandler sessionHandler)
     {
         _sessionHandler=sessionHandler;
     }
 
- 
+
     /* ------------------------------------------------------------ */
+    @Override
     public void setSessionIdPathParameterName(String param)
     {
         _sessionIdPathParameterName =(param==null||"none".equals(param))?null:param;
@@ -738,7 +699,12 @@ public abstract class AbstractSessionManager extends AbstractLifeCycle implement
      */
     public abstract AbstractSession getSession(String idInCluster);
 
-    protected abstract void invalidateSessions() throws Exception;
+    /**
+     * Prepare sessions for session manager shutdown
+     * 
+     * @throws Exception
+     */
+    protected abstract void shutdownSessions() throws Exception;
 
 
     /* ------------------------------------------------------------ */
@@ -786,49 +752,56 @@ public abstract class AbstractSessionManager extends AbstractLifeCycle implement
      * @param invalidate True if {@link HttpSessionListener#sessionDestroyed(HttpSessionEvent)} and
      * {@link SessionIdManager#invalidateAll(String)} should be called.
      */
-    public void removeSession(AbstractSession session, boolean invalidate)
+    public boolean removeSession(AbstractSession session, boolean invalidate)
     {
         // Remove session from context and global maps
         boolean removed = removeSession(session.getClusterId());
-        
+
         if (removed)
         {
             _sessionsStats.decrement();
             _sessionTimeStats.set(round((System.currentTimeMillis() - session.getCreationTime())/1000.0));
-            
+
             // Remove session from all context and global id maps
             _sessionIdManager.removeSession(session);
             if (invalidate)
                 _sessionIdManager.invalidateAll(session.getClusterId());
-            
+
             if (invalidate && _sessionListeners!=null)
             {
-                HttpSessionEvent event=new HttpSessionEvent(session);
-                for (HttpSessionListener listener : _sessionListeners)
-                    listener.sessionDestroyed(event);
+                HttpSessionEvent event=new HttpSessionEvent(session);      
+                for (int i = _sessionListeners.size()-1; i>=0; i--)
+                {
+                    _sessionListeners.get(i).sessionDestroyed(event);
+                }
             }
         }
+        
+        return removed;
     }
 
     /* ------------------------------------------------------------ */
     protected abstract boolean removeSession(String idInCluster);
-    
+
     /* ------------------------------------------------------------ */
     /**
      * @return maximum amount of time session remained valid
      */
+    @ManagedAttribute("maximum amount of time sessions have remained active (in s)")
     public long getSessionTimeMax()
     {
         return _sessionTimeStats.getMax();
     }
 
     /* ------------------------------------------------------------ */
+    @Override
     public Set<SessionTrackingMode> getDefaultSessionTrackingModes()
     {
         return __defaultSessionTrackingModes;
     }
 
     /* ------------------------------------------------------------ */
+    @Override
     public Set<SessionTrackingMode> getEffectiveSessionTrackingModes()
     {
         return Collections.unmodifiableSet(_sessionTrackingModes);
@@ -850,126 +823,43 @@ public abstract class AbstractSessionManager extends AbstractLifeCycle implement
         return _usingURLs;
     }
 
-
     /* ------------------------------------------------------------ */
+    @Override
     public SessionCookieConfig getSessionCookieConfig()
     {
         return _cookieConfig;
-    } 
+    }
 
     /* ------------------------------------------------------------ */
     private SessionCookieConfig _cookieConfig =
-        new SessionCookieConfig()
-        {
-            @Override
-            public String getComment()
-            {
-                return _sessionComment;
-            }
-
-            @Override
-            public String getDomain()
-            {
-                return _sessionDomain;
-            }
-
-            @Override
-            public int getMaxAge()
-            {
-                return _maxCookieAge;
-            }
-
-            @Override
-            public String getName()
-            {
-                return _sessionCookie;
-            }
-
-            @Override
-            public String getPath()
-            {
-                return _sessionPath;
-            }
-
-            @Override
-            public boolean isHttpOnly()
-            {
-                return _httpOnly;
-            }
-
-            @Override
-            public boolean isSecure()
-            {
-                return _secureCookies;
-            }
-
-            @Override
-            public void setComment(String comment)
-            {
-                _sessionComment = comment; 
-            }
-
-            @Override
-            public void setDomain(String domain)
-            {
-                _sessionDomain=domain;
-            }
-
-            @Override
-            public void setHttpOnly(boolean httpOnly)
-            {
-                _httpOnly=httpOnly;
-            }
-
-            @Override
-            public void setMaxAge(int maxAge)
-            {
-                _maxCookieAge=maxAge;
-            }
-
-            @Override
-            public void setName(String name)
-            {
-                _sessionCookie=name;
-            }
-
-            @Override
-            public void setPath(String path)
-            {
-                _sessionPath=path;
-            }
-
-            @Override
-            public void setSecure(boolean secure)
-            {
-                _secureCookies=secure;
-            }
-        
-        };
+        new CookieConfig();
 
 
     /* ------------------------------------------------------------ */
     /**
      * @return total amount of time all sessions remained valid
      */
+    @ManagedAttribute("total time sessions have remained valid")
     public long getSessionTimeTotal()
     {
         return _sessionTimeStats.getTotal();
     }
-    
+
     /* ------------------------------------------------------------ */
     /**
      * @return mean amount of time session remained valid
      */
+    @ManagedAttribute("mean time sessions remain valid (in s)")
     public double getSessionTimeMean()
     {
         return _sessionTimeStats.getMean();
     }
-    
+
     /* ------------------------------------------------------------ */
     /**
      * @return standard deviation of amount of time session remained valid
      */
+    @ManagedAttribute("standard deviation a session remained valid (in s)")
     public double getSessionTimeStdDev()
     {
         return _sessionTimeStats.getStdDev();
@@ -979,6 +869,8 @@ public abstract class AbstractSessionManager extends AbstractLifeCycle implement
     /**
      * @see org.eclipse.jetty.server.SessionManager#isCheckingRemoteSessionIdEncoding()
      */
+    @Override
+    @ManagedAttribute("check remote session id encoding")
     public boolean isCheckingRemoteSessionIdEncoding()
     {
         return _checkingRemoteSessionIdEncoding;
@@ -988,11 +880,141 @@ public abstract class AbstractSessionManager extends AbstractLifeCycle implement
     /**
      * @see org.eclipse.jetty.server.SessionManager#setCheckingRemoteSessionIdEncoding(boolean)
      */
+    @Override
     public void setCheckingRemoteSessionIdEncoding(boolean remote)
     {
         _checkingRemoteSessionIdEncoding=remote;
     }
     
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Tell the HttpSessionIdListeners the id changed.
+     * NOTE: this method must be called LAST in subclass overrides, after the session has been updated
+     * with the new id.
+     * @see org.eclipse.jetty.server.SessionManager#renewSessionId(java.lang.String, java.lang.String, java.lang.String, java.lang.String)
+     */
+    @Override
+    public void renewSessionId(String oldClusterId, String oldNodeId, String newClusterId, String newNodeId)
+    {
+        if (!_sessionIdListeners.isEmpty())
+        {
+            AbstractSession session = getSession(newClusterId);
+            HttpSessionEvent event = new HttpSessionEvent(session);
+            for (HttpSessionIdListener l:_sessionIdListeners)
+            {
+                l.sessionIdChanged(event, oldClusterId);
+            }
+        }
+
+    }
+
+    /**
+     * CookieConfig
+     * 
+     * Implementation of the javax.servlet.SessionCookieConfig.
+     */
+    public final class CookieConfig implements SessionCookieConfig
+    {
+        @Override
+        public String getComment()
+        {
+            return _sessionComment;
+        }
+
+        @Override
+        public String getDomain()
+        {
+            return _sessionDomain;
+        }
+
+        @Override
+        public int getMaxAge()
+        {
+            return _maxCookieAge;
+        }
+
+        @Override
+        public String getName()
+        {
+            return _sessionCookie;
+        }
+
+        @Override
+        public String getPath()
+        {
+            return _sessionPath;
+        }
+
+        @Override
+        public boolean isHttpOnly()
+        {
+            return _httpOnly;
+        }
+
+        @Override
+        public boolean isSecure()
+        {
+            return _secureCookies;
+        }
+
+        @Override
+        public void setComment(String comment)
+        {  
+            if (_context != null && _context.getContextHandler().isAvailable())
+                throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
+            _sessionComment = comment;
+        }
+
+        @Override
+        public void setDomain(String domain)
+        {
+            if (_context != null && _context.getContextHandler().isAvailable())
+                throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
+            _sessionDomain=domain;
+        }
+
+        @Override
+        public void setHttpOnly(boolean httpOnly)
+        {   
+            if (_context != null && _context.getContextHandler().isAvailable())
+                throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
+            _httpOnly=httpOnly;
+        }
+
+        @Override
+        public void setMaxAge(int maxAge)
+        {               
+            if (_context != null && _context.getContextHandler().isAvailable())
+                throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
+            _maxCookieAge=maxAge;
+        }
+
+        @Override
+        public void setName(String name)
+        {  
+                if (_context != null && _context.getContextHandler().isAvailable())
+                    throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
+            _sessionCookie=name;
+        }
+
+        @Override
+        public void setPath(String path)
+        {
+            if (_context != null && _context.getContextHandler().isAvailable())
+                throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started"); 
+            _sessionPath=path;
+        }
+
+        @Override
+        public void setSecure(boolean secure)
+        {
+            if (_context != null && _context.getContextHandler().isAvailable())
+                throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
+            _secureCookies=secure;
+        }
+    }
+
     /* ------------------------------------------------------------ */
     /* ------------------------------------------------------------ */
     /* ------------------------------------------------------------ */
@@ -1023,4 +1045,11 @@ public abstract class AbstractSessionManager extends AbstractLifeCycle implement
             }
         }
     }
+
+    @Override
+    @Deprecated
+    public SessionIdManager getMetaManager()
+    {
+        throw new UnsupportedOperationException();
+    }
 }
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/HashSessionIdManager.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/HashSessionIdManager.java
index c7f6af6..65e5033 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/HashSessionIdManager.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/HashSessionIdManager.java
@@ -32,8 +32,6 @@ import java.util.Set;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpSession;
 
-import org.eclipse.jetty.server.SessionIdManager;
-
 /* ------------------------------------------------------------ */
 /**
  * HashSessionIdManager. An in-memory implementation of the session ID manager.
@@ -61,7 +59,7 @@ public class HashSessionIdManager extends AbstractSessionIdManager
     {
         return Collections.unmodifiableCollection(_sessions.keySet());
     }
-    
+
     /* ------------------------------------------------------------ */
     /**
      * @return Collection of Sessions for the passed session ID
@@ -81,42 +79,11 @@ public class HashSessionIdManager extends AbstractSessionIdManager
         }
         return sessions;
     }
-    /* ------------------------------------------------------------ */
-    /** Get the session ID with any worker ID.
-     * 
-     * @param clusterId
-     * @param request
-     * @return sessionId plus any worker ID.
-     */
-    public String getNodeId(String clusterId,HttpServletRequest request) 
-    {
-        // used in Ajp13Parser
-        String worker=request==null?null:(String)request.getAttribute("org.eclipse.jetty.ajp.JVMRoute");
-        if (worker!=null) 
-            return clusterId+'.'+worker; 
-        
-        if (_workerName!=null) 
-            return clusterId+'.'+_workerName;
-       
-        return clusterId;
-    }
-
-    /* ------------------------------------------------------------ */
-    /** Get the session ID without any worker ID.
-     * 
-     * @param nodeId the node id
-     * @return sessionId without any worker ID.
-     */
-    public String getClusterId(String nodeId) 
-    {
-        int dot=nodeId.lastIndexOf('.');
-        return (dot>0)?nodeId.substring(0,dot):nodeId;
-    }
     
     /* ------------------------------------------------------------ */
     @Override
     protected void doStart() throws Exception
-    {        
+    {
         super.doStart();
     }
 
@@ -124,7 +91,7 @@ public class HashSessionIdManager extends AbstractSessionIdManager
     @Override
     protected void doStop() throws Exception
     {
-        _sessions.clear(); 
+        _sessions.clear();
         super.doStop();
     }
 
@@ -132,6 +99,7 @@ public class HashSessionIdManager extends AbstractSessionIdManager
     /**
      * @see SessionIdManager#idInUse(String)
      */
+    @Override
     public boolean idInUse(String id)
     {
         synchronized (this)
@@ -144,11 +112,12 @@ public class HashSessionIdManager extends AbstractSessionIdManager
     /**
      * @see SessionIdManager#addSession(HttpSession)
      */
+    @Override
     public void addSession(HttpSession session)
     {
         String id = getClusterId(session.getId());
         WeakReference<HttpSession> ref = new WeakReference<HttpSession>(session);
-        
+
         synchronized (this)
         {
             Set<WeakReference<HttpSession>> sessions = _sessions.get(id);
@@ -165,10 +134,11 @@ public class HashSessionIdManager extends AbstractSessionIdManager
     /**
      * @see SessionIdManager#removeSession(HttpSession)
      */
+    @Override
     public void removeSession(HttpSession session)
     {
         String id = getClusterId(session.getId());
-        
+
         synchronized (this)
         {
             Collection<WeakReference<HttpSession>> sessions = _sessions.get(id);
@@ -199,6 +169,7 @@ public class HashSessionIdManager extends AbstractSessionIdManager
     /**
      * @see SessionIdManager#invalidateAll(String)
      */
+    @Override
     public void invalidateAll(String id)
     {
         Collection<WeakReference<HttpSession>> sessions;
@@ -206,7 +177,7 @@ public class HashSessionIdManager extends AbstractSessionIdManager
         {
             sessions = _sessions.remove(id);
         }
-        
+
         if (sessions!=null)
         {
             for (WeakReference<HttpSession> ref: sessions)
@@ -218,5 +189,41 @@ public class HashSessionIdManager extends AbstractSessionIdManager
             sessions.clear();
         }
     }
+    
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public void renewSessionId (String oldClusterId, String oldNodeId, HttpServletRequest request)
+    {
+        //generate a new id
+        String newClusterId = newSessionId(request.hashCode());
+
+
+        synchronized (this)
+        {
+            Set<WeakReference<HttpSession>> sessions = _sessions.remove(oldClusterId); //get the list of sessions with same id from other contexts
+            if (sessions!=null)
+            {
+                for (Iterator<WeakReference<HttpSession>> iter = sessions.iterator(); iter.hasNext();)
+                {
+                    WeakReference<HttpSession> ref = iter.next();
+                    HttpSession s = ref.get();
+                    if (s == null)
+                    {
+                        continue;
+                    }
+                    else
+                    {
+                        if (s instanceof AbstractSession)
+                        {
+                            AbstractSession abstractSession = (AbstractSession)s;
+                            abstractSession.getSessionManager().renewSessionId(oldClusterId, oldNodeId, newClusterId, getNodeId(newClusterId, request));
+                        }
+                    }
+                }
+                _sessions.put(newClusterId, sessions);
+            }
+        }
+    }
 
 }
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/HashSessionManager.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/HashSessionManager.java
index 554e2ed..89e8e13 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/HashSessionManager.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/HashSessionManager.java
@@ -23,23 +23,21 @@ import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
-import java.io.ObjectInputStream;
-import java.net.URI;
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.Map;
-import java.util.Set;
-import java.util.Timer;
-import java.util.TimerTask;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.TimeUnit;
 
 import javax.servlet.ServletContext;
 import javax.servlet.http.HttpServletRequest;
 
 import org.eclipse.jetty.server.handler.ContextHandler;
-import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.ClassLoadingObjectInputStream;
 import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
+import org.eclipse.jetty.util.thread.Scheduler;
 
 
 /* ------------------------------------------------------------ */
@@ -57,24 +55,68 @@ import org.eclipse.jetty.util.log.Logger;
  */
 public class HashSessionManager extends AbstractSessionManager
 {
-    final static Logger __log = SessionHandler.LOG;
+    final static Logger LOG = SessionHandler.LOG;
 
     protected final ConcurrentMap<String,HashedSession> _sessions=new ConcurrentHashMap<String,HashedSession>();
-    private static int __id;
-    private Timer _timer;
-    private boolean _timerStop=false;
-    private TimerTask _task;
+    private Scheduler _timer;
+    private Scheduler.Task _task;
     long _scavengePeriodMs=30000;
     long _savePeriodMs=0; //don't do period saves by default
     long _idleSavePeriodMs = 0; // don't idle save sessions by default.
-    private TimerTask _saveTask;
+    private Scheduler.Task _saveTask;
     File _storeDir;
     private boolean _lazyLoad=false;
     private volatile boolean _sessionsLoaded=false;
     private boolean _deleteUnrestorableSessions=false;
-    
 
 
+    /**
+     * Scavenger
+     *
+     */
+    protected class Scavenger implements Runnable
+    {
+        @Override
+        public void run()
+        {
+            try
+            {
+                scavenge();
+            }
+            finally
+            {
+                if (_timer != null && _timer.isRunning()) {
+                    _task = _timer.schedule(this, _scavengePeriodMs, TimeUnit.MILLISECONDS);
+                }
+            }
+        }
+    }
+
+    /**
+     * Saver
+     *
+     */
+    protected class Saver implements Runnable
+    {
+        @Override
+        public void run()
+        {
+            try
+            {
+                saveSessions(true);
+            }
+            catch (Exception e)
+            {       
+                LOG.warn(e);
+            }
+            finally
+            {
+                if (_timer != null && _timer.isRunning())
+                    _saveTask = _timer.schedule(this, _savePeriodMs, TimeUnit.MILLISECONDS);
+            }
+        }        
+    }
+
 
     /* ------------------------------------------------------------ */
     public HashSessionManager()
@@ -84,22 +126,31 @@ public class HashSessionManager extends AbstractSessionManager
 
     /* ------------------------------------------------------------ */
     /**
-     * @see org.eclipse.jetty.servlet.AbstractSessionManager#doStart()
+     * @see AbstractSessionManager#doStart()
      */
     @Override
     public void doStart() throws Exception
     {
-        super.doStart();
-
-        _timerStop=false;
-        ServletContext context = ContextHandler.getCurrentContext();
-        if (context!=null)
-            _timer=(Timer)context.getAttribute("org.eclipse.jetty.server.session.timer");
-        if (_timer==null)
+        //try shared scheduler from Server first
+        _timer = getSessionHandler().getServer().getBean(Scheduler.class);
+        if (_timer == null)
         {
-            _timerStop=true;
-            _timer=new Timer("HashSessionScavenger-"+__id++, true);
+            //try one passed into the context
+            ServletContext context = ContextHandler.getCurrentContext();
+            if (context!=null)
+                _timer = (Scheduler)context.getAttribute("org.eclipse.jetty.server.session.timer");   
+        }    
+      
+        if (_timer == null)
+        {
+            //make a scheduler if none useable
+            _timer=new ScheduledExecutorScheduler(toString()+"Timer",true);
+            addBean(_timer,true);
         }
+        else
+            addBean(_timer,false);
+        
+        super.doStart();
 
         setScavengePeriod(getScavengePeriod());
 
@@ -117,7 +168,7 @@ public class HashSessionManager extends AbstractSessionManager
 
     /* ------------------------------------------------------------ */
     /**
-     * @see org.eclipse.jetty.servlet.AbstractSessionManager#doStop()
+     * @see AbstractSessionManager#doStop()
      */
     @Override
     public void doStop() throws Exception
@@ -127,14 +178,15 @@ public class HashSessionManager extends AbstractSessionManager
         {
             if (_saveTask!=null)
                 _saveTask.cancel();
+
             _saveTask=null;
             if (_task!=null)
                 _task.cancel();
+
             _task=null;
-            if (_timer!=null && _timerStop)
-                _timer.cancel();
             _timer=null;
         }
+       
 
         // This will callback invalidate sessions - where we decide if we will save
         super.doStop();
@@ -158,10 +210,10 @@ public class HashSessionManager extends AbstractSessionManager
     public int getSessions()
     {
         int sessions=super.getSessions();
-        if (__log.isDebugEnabled())
+        if (LOG.isDebugEnabled())
         {
             if (_sessions.size()!=sessions)
-                __log.warn("sessions: "+_sessions.size()+"!="+sessions);
+                LOG.warn("sessions: "+_sessions.size()+"!="+sessions);
         }
         return sessions;
     }
@@ -218,24 +270,10 @@ public class HashSessionManager extends AbstractSessionManager
             {
                 if (_saveTask!=null)
                     _saveTask.cancel();
+                _saveTask = null;
                 if (_savePeriodMs > 0 && _storeDir!=null) //only save if we have a directory configured
                 {
-                    _saveTask = new TimerTask()
-                    {
-                        @Override
-                        public void run()
-                        {
-                            try
-                            {
-                                saveSessions(true);
-                            }
-                            catch (Exception e)
-                            {
-                                __log.warn(e);
-                            }
-                        }
-                    };
-                    _timer.schedule(_saveTask,_savePeriodMs,_savePeriodMs);
+                    _saveTask = _timer.schedule(new Saver(),_savePeriodMs,TimeUnit.MILLISECONDS);
                 }
             }
         }
@@ -271,21 +309,17 @@ public class HashSessionManager extends AbstractSessionManager
 
         _scavengePeriodMs=period;
     
-        if (_timer!=null && (period!=old_period || _task==null))
+        synchronized (this)
         {
-            synchronized (this)
+            if (_timer!=null && (period!=old_period || _task==null))
             {
                 if (_task!=null)
-                    _task.cancel();
-                _task = new TimerTask()
                 {
-                    @Override
-                    public void run()
-                    {
-                        scavenge();
-                    }
-                };
-                _timer.schedule(_task,_scavengePeriodMs,_scavengePeriodMs);
+                    _task.cancel();
+                    _task = null;
+                }
+
+                _task = _timer.schedule(new Scavenger(),_scavengePeriodMs, TimeUnit.MILLISECONDS);
             }
         }
     }
@@ -304,13 +338,14 @@ public class HashSessionManager extends AbstractSessionManager
         Thread thread=Thread.currentThread();
         ClassLoader old_loader=thread.getContextClassLoader();
         try
-        {
+        {      
             if (_loader!=null)
                 thread.setContextClassLoader(_loader);
 
             // For each session
             long now=System.currentTimeMillis();
-          
+            __log.debug("Scavenging sessions at {}", now); 
+            
             for (Iterator<HashedSession> i=_sessions.values().iterator(); i.hasNext();)
             {
                 HashedSession session=i.next();
@@ -366,7 +401,7 @@ public class HashSessionManager extends AbstractSessionManager
             }
             catch(Exception e)
             {
-                __log.warn(e);
+                LOG.warn(e);
             }
         }
 
@@ -389,8 +424,8 @@ public class HashSessionManager extends AbstractSessionManager
 
     /* ------------------------------------------------------------ */
     @Override
-    protected void invalidateSessions() throws Exception
-    {
+    protected void shutdownSessions() throws Exception
+    {   
         // Invalidate all sessions to cause unbind events
         ArrayList<HashedSession> sessions=new ArrayList<HashedSession>(_sessions.values());
         int loop=100;
@@ -399,11 +434,11 @@ public class HashSessionManager extends AbstractSessionManager
             // If we are called from doStop
             if (isStopping() && _storeDir != null && _storeDir.exists() && _storeDir.canWrite())
             {
-                // Then we only save and remove the session - it is not invalidated.
+                // Then we only save and remove the session from memory- it is not invalidated.
                 for (HashedSession session : sessions)
                 {
                     session.save(false);
-                    removeSession(session,false);
+                    _sessions.remove(session.getClusterId());
                 }
             }
             else
@@ -416,6 +451,39 @@ public class HashSessionManager extends AbstractSessionManager
             sessions=new ArrayList<HashedSession>(_sessions.values());
         }
     }
+    
+    
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.SessionManager#renewSessionId(java.lang.String, java.lang.String, java.lang.String, java.lang.String)
+     */
+    @Override
+    public void renewSessionId(String oldClusterId, String oldNodeId, String newClusterId, String newNodeId)
+    {
+        try
+        {
+            Map<String,HashedSession> sessions=_sessions;
+            if (sessions == null)
+                return;
+
+            HashedSession session = sessions.remove(oldClusterId);
+            if (session == null)
+                return;
+
+            session.remove(); //delete any previously saved session
+            session.setClusterId(newClusterId); //update ids
+            session.setNodeId(newNodeId);
+            session.save(); //save updated session: TODO consider only saving file if idled
+            sessions.put(newClusterId, session);
+            
+            super.renewSessionId(oldClusterId, oldNodeId, newClusterId, newNodeId);
+        }
+        catch (Exception e)
+        {
+            LOG.warn(e);
+        }
+    }
 
     /* ------------------------------------------------------------ */
     @Override
@@ -462,13 +530,13 @@ public class HashSessionManager extends AbstractSessionManager
     {
         return _lazyLoad;
     }
-    
+
     /* ------------------------------------------------------------ */
     public boolean isDeleteUnrestorableSessions()
     {
         return _deleteUnrestorableSessions;
     }
-    
+
     /* ------------------------------------------------------------ */
     public void setDeleteUnrestorableSessions(boolean deleteUnrestorableSessions)
     {
@@ -487,7 +555,7 @@ public class HashSessionManager extends AbstractSessionManager
 
         if (!_storeDir.canRead())
         {
-            __log.warn ("Unable to restore Sessions: Cannot read from Session storage directory "+_storeDir.getAbsolutePath());
+            LOG.warn ("Unable to restore Sessions: Cannot read from Session storage directory "+_storeDir.getAbsolutePath());
             return;
         }
 
@@ -503,18 +571,22 @@ public class HashSessionManager extends AbstractSessionManager
     {        
         File file = new File(_storeDir,idInCuster);
 
-        FileInputStream in = null;
         Exception error = null;
-        try
+        if (!file.exists())
         {
-            if (file.exists())
+            if (LOG.isDebugEnabled())
             {
-                in = new FileInputStream(file);
-                HashedSession session = restoreSession(in, null);
-                addSession(session, false);
-                session.didActivate();
-                return session;
+                LOG.debug("Not loading: {}",file);
             }
+            return null;
+        }
+        
+        try (FileInputStream in = new FileInputStream(file))
+        {
+            HashedSession session = restoreSession(in,null);
+            addSession(session,false);
+            session.didActivate();
+            return session;
         }
         catch (Exception e)
         {
@@ -522,14 +594,12 @@ public class HashSessionManager extends AbstractSessionManager
         }
         finally
         {
-            if (in != null) IO.close(in);
-            
             if (error != null)
             {
                 if (isDeleteUnrestorableSessions() && file.exists() && file.getParentFile().equals(_storeDir) )
                 {
                     file.delete();
-                    __log.warn("Deleting file for unrestorable session "+idInCuster, error);
+                    LOG.warn("Deleting file for unrestorable session "+idInCuster, error);
                 }
                 else
                 {
@@ -537,8 +607,10 @@ public class HashSessionManager extends AbstractSessionManager
                 }
             }
             else
-               file.delete(); //delete successfully restored file
-                
+            {
+                // delete successfully restored file
+                file.delete();
+            }
         }
         return null;
     }
@@ -553,87 +625,65 @@ public class HashSessionManager extends AbstractSessionManager
 
         if (!_storeDir.canWrite())
         {
-            __log.warn ("Unable to save Sessions: Session persistence storage directory "+_storeDir.getAbsolutePath()+ " is not writeable");
+            LOG.warn ("Unable to save Sessions: Session persistence storage directory "+_storeDir.getAbsolutePath()+ " is not writeable");
             return;
         }
 
         for (HashedSession session : _sessions.values())
-            session.save(true);
+            session.save(reactivate);
     }
+    
 
     /* ------------------------------------------------------------ */
     public HashedSession restoreSession (InputStream is, HashedSession session) throws Exception
     {
-        /*
-         * Take care of this class's fields first by calling
-         * defaultReadObject
-         */
-        DataInputStream in = new DataInputStream(is);
+        DataInputStream di = new DataInputStream(is);
+
+        String clusterId = di.readUTF();
+        di.readUTF(); // nodeId
+
+        long created = di.readLong();
+        long accessed = di.readLong();
+        int requests = di.readInt();
+
+        if (session == null)
+            session = (HashedSession)newSession(created, accessed, clusterId);
+        
+        session.setRequests(requests);
+
+        // Attributes
+        int size = di.readInt();
+
+        restoreSessionAttributes(di, size, session);
+
         try
         {
-            String clusterId = in.readUTF();
-            in.readUTF(); // nodeId
-            long created = in.readLong();
-            long accessed = in.readLong();
-            int requests = in.readInt();
-
-            if (session == null)
-                session = (HashedSession)newSession(created, accessed, clusterId);
-            session.setRequests(requests);
-            int size = in.readInt();
-            if (size>0)
-            {
-                ClassLoadingObjectInputStream ois = new ClassLoadingObjectInputStream(in);
-                try
-                {
-                    for (int i=0; i<size;i++)
-                    {
-                        String key = ois.readUTF();
-                        Object value = ois.readObject();
-                        session.setAttribute(key,value);
-                    }
-                }
-                finally
-                {
-                    IO.close(ois);
-                }
-            }
-            return session;
+            int maxIdle = di.readInt();
+            session.setMaxInactiveInterval(maxIdle);
         }
-        finally
+        catch (IOException e)
         {
-            IO.close(in);
+            LOG.debug("No maxInactiveInterval persisted for session "+clusterId);
+            LOG.ignore(e);
         }
+
+        return session;
     }
 
 
-    /* ------------------------------------------------------------ */
-    /* ------------------------------------------------------------ */
-    protected class ClassLoadingObjectInputStream extends ObjectInputStream
+    @SuppressWarnings("resource")
+    private void restoreSessionAttributes (InputStream is, int size, HashedSession session)
+    throws Exception
     {
-        /* ------------------------------------------------------------ */
-        public ClassLoadingObjectInputStream(java.io.InputStream in) throws IOException
-        {
-            super(in);
-        }
-
-        /* ------------------------------------------------------------ */
-        public ClassLoadingObjectInputStream () throws IOException
+        if (size>0)
         {
-            super();
-        }
-
-        /* ------------------------------------------------------------ */
-        @Override
-        public Class<?> resolveClass (java.io.ObjectStreamClass cl) throws IOException, ClassNotFoundException
-        {
-            try
-            {
-                return Class.forName(cl.getName(), false, Thread.currentThread().getContextClassLoader());
-            }
-            catch (ClassNotFoundException e)
+            // input stream should not be closed here
+            ClassLoadingObjectInputStream ois =  new ClassLoadingObjectInputStream(is);
+            for (int i=0; i<size;i++)
             {
-                return super.resolveClass(cl);
+                String key = ois.readUTF();
+                Object value = ois.readObject();
+                session.setAttribute(key,value);
             }
         }
     }
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/HashedSession.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/HashedSession.java
index 2dac866..8e8e365 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/HashedSession.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/HashedSession.java
@@ -34,13 +34,13 @@ import org.eclipse.jetty.util.IO;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
 
-public class HashedSession extends AbstractSession
+public class HashedSession extends MemSession
 {
     private static final Logger LOG = Log.getLogger(HashedSession.class);
 
     private final HashSessionManager _hashSessionManager;
 
-    /** Whether the session has been saved because it has been deemed idle; 
+    /** Whether the session has been saved because it has been deemed idle;
      * in which case its attribute map will have been saved and cleared. */
     private transient boolean _idled = false;
 
@@ -50,6 +50,12 @@ public class HashedSession extends AbstractSession
      * due to serialization failures that are most likely caused by user
      * data stored in the session that is not serializable. */
     private transient boolean _saveFailed = false;
+    
+    /**
+     * True if an attempt has been made to de-idle a session and it failed. Once
+     * true, the session will not be attempted to be de-idled again.
+     */
+    private transient boolean _deIdleFailed = false;
 
     /* ------------------------------------------------------------- */
     protected HashedSession(HashSessionManager hashSessionManager, HttpServletRequest request)
@@ -68,11 +74,11 @@ public class HashedSession extends AbstractSession
     /* ------------------------------------------------------------- */
     protected void checkValid()
     {
-        if (_hashSessionManager._idleSavePeriodMs!=0)
+        if (!_deIdleFailed && _hashSessionManager._idleSavePeriodMs!=0)
             deIdle();
         super.checkValid();
     }
-    
+
     /* ------------------------------------------------------------- */
     @Override
     public void setMaxInactiveInterval(int secs)
@@ -88,8 +94,16 @@ public class HashedSession extends AbstractSession
     throws IllegalStateException
     {
         super.doInvalidate();
-        
-        // Remove from the disk
+        remove();
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Remove from the disk
+     */
+    synchronized void remove ()
+    {
         if (_hashSessionManager._storeDir!=null && getId()!=null)
         {
             String id=getId();
@@ -108,50 +122,67 @@ public class HashedSession extends AbstractSession
             if (LOG.isDebugEnabled())
                 LOG.debug("Saving {} {}",super.getId(),reactivate);
 
-            File file = null;
-            FileOutputStream fos = null;
-            
             try
             {
-                file = new File(_hashSessionManager._storeDir, super.getId());
-
-                if (file.exists())
-                    file.delete();
-                file.createNewFile();
-                fos = new FileOutputStream(file);
                 willPassivate();
-                save(fos);
-                IO.close(fos);
+                save();
                 if (reactivate)
                     didActivate();
                 else
                     clearAttributes();
             }
             catch (Exception e)
+            {       
+                LOG.warn("Problem saving session " + super.getId(), e);
+                _idled=false; // assume problem was before _values.clear();
+            }
+        }
+    }
+    
+    
+    
+    synchronized void save ()
+    throws Exception
+    {   
+        File file = null;
+        if (!_saveFailed && _hashSessionManager._storeDir != null)
+        {
+            file = new File(_hashSessionManager._storeDir, super.getId());
+            if (file.exists())
+            {
+                file.delete();
+            }
+
+            try(FileOutputStream fos = new FileOutputStream(file,false))
+            {
+                save(fos);
+            }
+            catch (Exception e)
             {
                 saveFailed(); // We won't try again for this session
-                if (fos != null) IO.close(fos);
-                if (file != null) file.delete(); // No point keeping the file if we didn't save the whole session
+                if (file != null) 
+                    file.delete(); // No point keeping the file if we didn't save the whole session
                 throw e;             
             }
         }
     }
+    
+    
     /* ------------------------------------------------------------ */
-    public synchronized void save(OutputStream os)  throws IOException 
+    public synchronized void save(OutputStream os)  throws IOException
     {
         DataOutputStream out = new DataOutputStream(os);
         out.writeUTF(getClusterId());
         out.writeUTF(getNodeId());
         out.writeLong(getCreationTime());
         out.writeLong(getAccessed());
-        
+
         /* Don't write these out, as they don't make sense to store because they
-         * either they cannot be true or their value will be restored in the 
+         * either they cannot be true or their value will be restored in the
          * Session constructor.
          */
         //out.writeBoolean(_invalid);
         //out.writeBoolean(_doInvalidate);
-        //out.writeLong(_maxIdleMs);
         //out.writeBoolean( _newSession);
         out.writeInt(getRequests());
         out.writeInt(getAttributes());
@@ -163,13 +194,14 @@ public class HashedSession extends AbstractSession
             oos.writeUTF(key);
             oos.writeObject(doGet(key));
         }
-        oos.close();
+        
+        out.writeInt(getMaxInactiveInterval());
     }
 
     /* ------------------------------------------------------------ */
     public synchronized void deIdle()
     {
-        if (isIdled())
+        if (isIdled() && !_deIdleFailed)
         {
             // Access now to prevent race with idling period
             access(System.currentTimeMillis());
@@ -198,6 +230,7 @@ public class HashedSession extends AbstractSession
             }
             catch (Exception e)
             {
+                deIdleFailed();
                 LOG.warn("Problem de-idling session " + super.getId(), e);
                 if (fis != null) IO.close(fis);//Must ensure closed before invalidate
                 invalidate();
@@ -205,13 +238,13 @@ public class HashedSession extends AbstractSession
         }
     }
 
-    
+
     /* ------------------------------------------------------------ */
     /**
      * Idle the session to reduce session memory footprint.
-     * 
-     * The session is idled by persisting it, then clearing the session values attribute map and finally setting 
-     * it to an idled state.  
+     *
+     * The session is idled by persisting it, then clearing the session values attribute map and finally setting
+     * it to an idled state.
      */
     public synchronized void idle()
     throws Exception
@@ -219,7 +252,7 @@ public class HashedSession extends AbstractSession
         save(false);
         _idled = true;
     }
-    
+
     /* ------------------------------------------------------------ */
     public synchronized boolean isIdled()
     {
@@ -238,4 +271,15 @@ public class HashedSession extends AbstractSession
         _saveFailed = true;
     }
 
+    /* ------------------------------------------------------------ */
+    public synchronized void deIdleFailed()
+    {
+        _deIdleFailed = true;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public synchronized boolean isDeIdleFailed()
+    {
+        return _deIdleFailed;
+    }
 }
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionIdManager.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionIdManager.java
index 6958ca7..68cc12e 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionIdManager.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionIdManager.java
@@ -29,15 +29,11 @@ import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.sql.Statement;
-import java.util.ArrayList;
-import java.util.Collection;
 import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
 import java.util.Locale;
 import java.util.Random;
-import java.util.Timer;
-import java.util.TimerTask;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
 
 import javax.naming.InitialContext;
 import javax.servlet.http.HttpServletRequest;
@@ -49,20 +45,23 @@ import org.eclipse.jetty.server.Server;
 import org.eclipse.jetty.server.SessionManager;
 import org.eclipse.jetty.server.handler.ContextHandler;
 import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
+import org.eclipse.jetty.util.thread.Scheduler;
 
 
 
 /**
  * JDBCSessionIdManager
  *
- * SessionIdManager implementation that uses a database to store in-use session ids, 
+ * SessionIdManager implementation that uses a database to store in-use session ids,
  * to support distributed sessions.
- * 
+ *
  */
 public class JDBCSessionIdManager extends AbstractSessionIdManager
-{    
+{
     final static Logger LOG = SessionHandler.LOG;
-    
+    public final static int MAX_INTERVAL_NOT_SET = -999;
+
     protected final HashSet<String> _sessionIds = new HashSet<String>();
     protected Server _server;
     protected Driver _driver;
@@ -70,178 +69,557 @@ public class JDBCSessionIdManager extends AbstractSessionIdManager
     protected String _connectionUrl;
     protected DataSource _datasource;
     protected String _jndiName;
-    protected String _sessionIdTable = "JettySessionIds";
-    protected String _sessionTable = "JettySessions";
-    protected String _sessionTableRowId = "rowId";
-    
-    protected Timer _timer; //scavenge timer
-    protected TimerTask _task; //scavenge task
+
+    protected int _deleteBlockSize = 10; //number of ids to include in where 'in' clause
+
+    protected Scheduler.Task _task; //scavenge task
+    protected Scheduler _scheduler;
+    protected Scavenger _scavenger;
+    protected boolean _ownScheduler;
     protected long _lastScavengeTime;
     protected long _scavengeIntervalMs = 1000L * 60 * 10; //10mins
-    protected String _blobType; //if not set, is deduced from the type of the database at runtime
-    protected String _longType; //if not set, is deduced from the type of the database at runtime
-    
+
+
     protected String _createSessionIdTable;
     protected String _createSessionTable;
-                                            
-    protected String _selectBoundedExpiredSessions;
-    protected String _deleteOldExpiredSessions;
 
+    protected String _selectBoundedExpiredSessions;
+    private String _selectExpiredSessions;
+    
     protected String _insertId;
     protected String _deleteId;
     protected String _queryId;
-    
+
     protected  String _insertSession;
     protected  String _deleteSession;
     protected  String _updateSession;
     protected  String _updateSessionNode;
     protected  String _updateSessionAccessTime;
+
+    protected DatabaseAdaptor _dbAdaptor = new DatabaseAdaptor();
+    protected SessionIdTableSchema _sessionIdTableSchema = new SessionIdTableSchema();
+    protected SessionTableSchema _sessionTableSchema = new SessionTableSchema();
     
-    protected DatabaseAdaptor _dbAdaptor;
+  
+
+ 
+    /**
+     * SessionTableSchema
+     *
+     */
+    public static class SessionTableSchema
+    {        
+        protected DatabaseAdaptor _dbAdaptor;
+        protected String _tableName = "JettySessions";
+        protected String _rowIdColumn = "rowId";
+        protected String _idColumn = "sessionId";
+        protected String _contextPathColumn = "contextPath";
+        protected String _virtualHostColumn = "virtualHost"; 
+        protected String _lastNodeColumn = "lastNode";
+        protected String _accessTimeColumn = "accessTime"; 
+        protected String _lastAccessTimeColumn = "lastAccessTime";
+        protected String _createTimeColumn = "createTime";
+        protected String _cookieTimeColumn = "cookieTime";
+        protected String _lastSavedTimeColumn = "lastSavedTime";
+        protected String _expiryTimeColumn = "expiryTime";
+        protected String _maxIntervalColumn = "maxInterval";
+        protected String _mapColumn = "map";
+        
+        
+        protected void setDatabaseAdaptor(DatabaseAdaptor dbadaptor)
+        {
+            _dbAdaptor = dbadaptor;
+        }
+        
+        
+        public String getTableName()
+        {
+            return _tableName;
+        }
+        public void setTableName(String tableName)
+        {
+            checkNotNull(tableName);
+            _tableName = tableName;
+        }
+        public String getRowIdColumn()
+        {       
+            if ("rowId".equals(_rowIdColumn) && _dbAdaptor.isRowIdReserved())
+                _rowIdColumn = "srowId";
+            return _rowIdColumn;
+        }
+        public void setRowIdColumn(String rowIdColumn)
+        {
+            checkNotNull(rowIdColumn);
+            if (_dbAdaptor == null)
+                throw new IllegalStateException ("DbAdaptor is null");
+            
+            if (_dbAdaptor.isRowIdReserved() && "rowId".equals(rowIdColumn))
+                throw new IllegalArgumentException("rowId is reserved word for Oracle");
+            
+            _rowIdColumn = rowIdColumn;
+        }
+        public String getIdColumn()
+        {
+            return _idColumn;
+        }
+        public void setIdColumn(String idColumn)
+        {
+            checkNotNull(idColumn);
+            _idColumn = idColumn;
+        }
+        public String getContextPathColumn()
+        {
+            return _contextPathColumn;
+        }
+        public void setContextPathColumn(String contextPathColumn)
+        {
+            checkNotNull(contextPathColumn);
+            _contextPathColumn = contextPathColumn;
+        }
+        public String getVirtualHostColumn()
+        {
+            return _virtualHostColumn;
+        }
+        public void setVirtualHostColumn(String virtualHostColumn)
+        {
+            checkNotNull(virtualHostColumn);
+            _virtualHostColumn = virtualHostColumn;
+        }
+        public String getLastNodeColumn()
+        {
+            return _lastNodeColumn;
+        }
+        public void setLastNodeColumn(String lastNodeColumn)
+        {
+            checkNotNull(lastNodeColumn);
+            _lastNodeColumn = lastNodeColumn;
+        }
+        public String getAccessTimeColumn()
+        {
+            return _accessTimeColumn;
+        }
+        public void setAccessTimeColumn(String accessTimeColumn)
+        {
+            checkNotNull(accessTimeColumn);
+            _accessTimeColumn = accessTimeColumn;
+        }
+        public String getLastAccessTimeColumn()
+        {
+            return _lastAccessTimeColumn;
+        }
+        public void setLastAccessTimeColumn(String lastAccessTimeColumn)
+        {
+            checkNotNull(lastAccessTimeColumn);
+            _lastAccessTimeColumn = lastAccessTimeColumn;
+        }
+        public String getCreateTimeColumn()
+        {
+            return _createTimeColumn;
+        }
+        public void setCreateTimeColumn(String createTimeColumn)
+        {
+            checkNotNull(createTimeColumn);
+            _createTimeColumn = createTimeColumn;
+        }
+        public String getCookieTimeColumn()
+        {
+            return _cookieTimeColumn;
+        }
+        public void setCookieTimeColumn(String cookieTimeColumn)
+        {
+            checkNotNull(cookieTimeColumn);
+            _cookieTimeColumn = cookieTimeColumn;
+        }
+        public String getLastSavedTimeColumn()
+        {
+            return _lastSavedTimeColumn;
+        }
+        public void setLastSavedTimeColumn(String lastSavedTimeColumn)
+        {
+            checkNotNull(lastSavedTimeColumn);
+            _lastSavedTimeColumn = lastSavedTimeColumn;
+        }
+        public String getExpiryTimeColumn()
+        {
+            return _expiryTimeColumn;
+        }
+        public void setExpiryTimeColumn(String expiryTimeColumn)
+        {
+            checkNotNull(expiryTimeColumn);
+            _expiryTimeColumn = expiryTimeColumn;
+        }
+        public String getMaxIntervalColumn()
+        {
+            return _maxIntervalColumn;
+        }
+        public void setMaxIntervalColumn(String maxIntervalColumn)
+        {
+            checkNotNull(maxIntervalColumn);
+            _maxIntervalColumn = maxIntervalColumn;
+        }
+        public String getMapColumn()
+        {
+            return _mapColumn;
+        }
+        public void setMapColumn(String mapColumn)
+        {
+            checkNotNull(mapColumn);
+            _mapColumn = mapColumn;
+        }
+        
+        public String getCreateStatementAsString ()
+        {
+            if (_dbAdaptor == null)
+                throw new IllegalStateException ("No DBAdaptor");
+            
+            String blobType = _dbAdaptor.getBlobType();
+            String longType = _dbAdaptor.getLongType();
+            
+            return "create table "+_tableName+" ("+getRowIdColumn()+" varchar(120), "+_idColumn+" varchar(120), "+
+                    _contextPathColumn+" varchar(60), "+_virtualHostColumn+" varchar(60), "+_lastNodeColumn+" varchar(60), "+_accessTimeColumn+" "+longType+", "+
+                    _lastAccessTimeColumn+" "+longType+", "+_createTimeColumn+" "+longType+", "+_cookieTimeColumn+" "+longType+", "+
+                    _lastSavedTimeColumn+" "+longType+", "+_expiryTimeColumn+" "+longType+", "+_maxIntervalColumn+" "+longType+", "+
+                    _mapColumn+" "+blobType+", primary key("+getRowIdColumn()+"))";
+        }
+        
+        public String getCreateIndexOverExpiryStatementAsString (String indexName)
+        {
+            return "create index "+indexName+" on "+getTableName()+" ("+getExpiryTimeColumn()+")";
+        }
+        
+        public String getCreateIndexOverSessionStatementAsString (String indexName)
+        {
+            return "create index "+indexName+" on "+getTableName()+" ("+getIdColumn()+", "+getContextPathColumn()+")";
+        }
+        
+        public String getAlterTableForMaxIntervalAsString ()
+        {
+            if (_dbAdaptor == null)
+                throw new IllegalStateException ("No DBAdaptor");
+            String longType = _dbAdaptor.getLongType();
+            String stem = "alter table "+getTableName()+" add "+getMaxIntervalColumn()+" "+longType;
+            if (_dbAdaptor.getDBName().contains("oracle"))
+                return stem + " default "+ MAX_INTERVAL_NOT_SET + " not null";
+            else
+                return stem +" not null default "+ MAX_INTERVAL_NOT_SET;
+        }
+        
+        private void checkNotNull(String s)
+        {
+            if (s == null)
+                throw new IllegalArgumentException(s);
+        }
+        public String getInsertSessionStatementAsString()
+        {
+           return "insert into "+getTableName()+
+            " ("+getRowIdColumn()+", "+getIdColumn()+", "+getContextPathColumn()+", "+getVirtualHostColumn()+", "+getLastNodeColumn()+
+            ", "+getAccessTimeColumn()+", "+getLastAccessTimeColumn()+", "+getCreateTimeColumn()+", "+getCookieTimeColumn()+
+            ", "+getLastSavedTimeColumn()+", "+getExpiryTimeColumn()+", "+getMaxIntervalColumn()+", "+getMapColumn()+") "+
+            " values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
+        }
+        public String getDeleteSessionStatementAsString()
+        {
+            return "delete from "+getTableName()+
+            " where "+getRowIdColumn()+" = ?";
+        }
+        public String getUpdateSessionStatementAsString()
+        {
+            return "update "+getTableName()+
+                    " set "+getIdColumn()+" = ?, "+getLastNodeColumn()+" = ?, "+getAccessTimeColumn()+" = ?, "+
+                    getLastAccessTimeColumn()+" = ?, "+getLastSavedTimeColumn()+" = ?, "+getExpiryTimeColumn()+" = ?, "+
+                    getMaxIntervalColumn()+" = ?, "+getMapColumn()+" = ? where "+getRowIdColumn()+" = ?";
+        }
+        public String getUpdateSessionNodeStatementAsString()
+        {
+            return "update "+getTableName()+
+                    " set "+getLastNodeColumn()+" = ? where "+getRowIdColumn()+" = ?";
+        }
+        public String getUpdateSessionAccessTimeStatementAsString()
+        {
+           return "update "+getTableName()+
+            " set "+getLastNodeColumn()+" = ?, "+getAccessTimeColumn()+" = ?, "+getLastAccessTimeColumn()+" = ?, "+
+                   getLastSavedTimeColumn()+" = ?, "+getExpiryTimeColumn()+" = ?, "+getMaxIntervalColumn()+" = ? where "+getRowIdColumn()+" = ?";
+        }
+        
+        public String getBoundedExpiredSessionsStatementAsString()
+        {
+            return "select * from "+getTableName()+" where "+getLastNodeColumn()+" = ? and "+getExpiryTimeColumn()+" >= ? and "+getExpiryTimeColumn()+" <= ?";
+        }
+        
+        public String getSelectExpiredSessionsStatementAsString()
+        {
+            return "select * from "+getTableName()+" where "+getExpiryTimeColumn()+" >0 and "+getExpiryTimeColumn()+" <= ?";
+        }
+     
+        public PreparedStatement getLoadStatement (Connection connection, String rowId, String contextPath, String virtualHosts)
+        throws SQLException
+        { 
+            if (_dbAdaptor == null)
+                throw new IllegalStateException("No DB adaptor");
 
-    private String _selectExpiredSessions;
 
+            if (contextPath == null || "".equals(contextPath))
+            {
+                if (_dbAdaptor.isEmptyStringNull())
+                {
+                    PreparedStatement statement = connection.prepareStatement("select * from "+getTableName()+
+                                                                              " where "+getIdColumn()+" = ? and "+
+                                                                              getContextPathColumn()+" is null and "+
+                                                                              getVirtualHostColumn()+" = ?");
+                    statement.setString(1, rowId);
+                    statement.setString(2, virtualHosts);
+
+                    return statement;
+                }
+            }
+
+            PreparedStatement statement = connection.prepareStatement("select * from "+getTableName()+
+                                                                      " where "+getIdColumn()+" = ? and "+getContextPathColumn()+
+                                                                      " = ? and "+getVirtualHostColumn()+" = ?");
+            statement.setString(1, rowId);
+            statement.setString(2, contextPath);
+            statement.setString(3, virtualHosts);
+
+            return statement;
+        }
+    }
+    
+    
     
     /**
+     * SessionIdTableSchema
+     *
+     */
+    public static class SessionIdTableSchema
+    {
+        protected DatabaseAdaptor _dbAdaptor;
+        protected String _tableName = "JettySessionIds";
+        protected String _idColumn = "id";
+
+        public void setDatabaseAdaptor(DatabaseAdaptor dbAdaptor)
+        {
+            _dbAdaptor = dbAdaptor;
+        }
+        public String getIdColumn()
+        {
+            return _idColumn;
+        }
+
+        public void setIdColumn(String idColumn)
+        {
+            checkNotNull(idColumn);
+            _idColumn = idColumn;
+        }
+
+        public String getTableName()
+        {
+            return _tableName;
+        }
+
+        public void setTableName(String tableName)
+        {
+            checkNotNull(tableName);
+            _tableName = tableName;
+        }
+
+        public String getInsertStatementAsString ()
+        {
+            return "insert into "+_tableName+" ("+_idColumn+")  values (?)";
+        }
+
+        public String getDeleteStatementAsString ()
+        {
+            return "delete from "+_tableName+" where "+_idColumn+" = ?";
+        }
+
+        public String getSelectStatementAsString ()
+        {
+            return  "select * from "+_tableName+" where "+_idColumn+" = ?";
+        }
+        
+        public String getCreateStatementAsString ()
+        {
+            return "create table "+_tableName+" ("+_idColumn+" varchar(120), primary key("+_idColumn+"))";
+        }
+        
+        private void checkNotNull(String s)
+        {
+            if (s == null)
+                throw new IllegalArgumentException(s);
+        }
+    }
+
+
+    /**
      * DatabaseAdaptor
      *
      * Handles differences between databases.
-     * 
+     *
      * Postgres uses the getBytes and setBinaryStream methods to access
      * a "bytea" datatype, which can be up to 1Gb of binary data. MySQL
      * is happy to use the "blob" type and getBlob() methods instead.
-     * 
+     *
      * TODO if the differences become more major it would be worthwhile
      * refactoring this class.
      */
-    public class DatabaseAdaptor 
+    public static class DatabaseAdaptor
     {
         String _dbName;
         boolean _isLower;
         boolean _isUpper;
-       
+        
+        protected String _blobType; //if not set, is deduced from the type of the database at runtime
+        protected String _longType; //if not set, is deduced from the type of the database at runtime
+
+
+        public DatabaseAdaptor ()
+        {           
+        }
         
         
-        public DatabaseAdaptor (DatabaseMetaData dbMeta)
+        public void adaptTo(DatabaseMetaData dbMeta)  
         throws SQLException
         {
-            _dbName = dbMeta.getDatabaseProductName().toLowerCase(Locale.ENGLISH); 
-            LOG.debug ("Using database {}",_dbName);
+            _dbName = dbMeta.getDatabaseProductName().toLowerCase(Locale.ENGLISH);
+            if (LOG.isDebugEnabled())
+                LOG.debug ("Using database {}",_dbName);
             _isLower = dbMeta.storesLowerCaseIdentifiers();
-            _isUpper = dbMeta.storesUpperCaseIdentifiers();            
-        }
-        
-        /**
-         * Convert a camel case identifier into either upper or lower
-         * depending on the way the db stores identifiers.
-         * 
-         * @param identifier
-         * @return the converted identifier
-         */
-        public String convertIdentifier (String identifier)
-        {
-            if (_isLower)
-                return identifier.toLowerCase(Locale.ENGLISH);
-            if (_isUpper)
-                return identifier.toUpperCase(Locale.ENGLISH);
-            
-            return identifier;
+            _isUpper = dbMeta.storesUpperCaseIdentifiers(); 
         }
         
-        public String getDBName ()
+       
+        public void setBlobType(String blobType)
         {
-            return _dbName;
+            _blobType = blobType;
         }
         
         public String getBlobType ()
         {
             if (_blobType != null)
                 return _blobType;
-            
+
             if (_dbName.startsWith("postgres"))
                 return "bytea";
-            
+
             return "blob";
         }
         
+
+        public void setLongType(String longType)
+        {
+            _longType = longType;
+        }
+        
+
         public String getLongType ()
         {
             if (_longType != null)
                 return _longType;
+
+            if (_dbName == null)
+                throw new IllegalStateException ("DbAdaptor missing metadata");
             
             if (_dbName.startsWith("oracle"))
                 return "number(20)";
-            
+
             return "bigint";
         }
         
+
+        /**
+         * Convert a camel case identifier into either upper or lower
+         * depending on the way the db stores identifiers.
+         *
+         * @param identifier
+         * @return the converted identifier
+         */
+        public String convertIdentifier (String identifier)
+        {
+            if (_dbName == null)
+                throw new IllegalStateException ("DbAdaptor missing metadata");
+            
+            if (_isLower)
+                return identifier.toLowerCase(Locale.ENGLISH);
+            if (_isUpper)
+                return identifier.toUpperCase(Locale.ENGLISH);
+
+            return identifier;
+        }
+
+        public String getDBName ()
+        {
+            return _dbName;
+        }
+
+
         public InputStream getBlobInputStream (ResultSet result, String columnName)
         throws SQLException
         {
+            if (_dbName == null)
+                throw new IllegalStateException ("DbAdaptor missing metadata");
+            
             if (_dbName.startsWith("postgres"))
             {
                 byte[] bytes = result.getBytes(columnName);
                 return new ByteArrayInputStream(bytes);
             }
-            
+
             Blob blob = result.getBlob(columnName);
             return blob.getBinaryStream();
         }
+
+
+        public boolean isEmptyStringNull ()
+        {
+            if (_dbName == null)
+                throw new IllegalStateException ("DbAdaptor missing metadata");
+            
+            return (_dbName.startsWith("oracle"));
+        }
         
         /**
          * rowId is a reserved word for Oracle, so change the name of this column
-         * @return
+         * @return true if db in use is oracle
          */
-        public String getRowIdColumnName ()
+        public boolean isRowIdReserved ()
         {
-            if (_dbName != null && _dbName.startsWith("oracle"))
-                return "srowId";
+            if (_dbName == null)
+                throw new IllegalStateException ("DbAdaptor missing metadata");
             
-            return "rowId";
+            return (_dbName != null && _dbName.startsWith("oracle"));
         }
-        
-        
-        public boolean isEmptyStringNull ()
-        {
-            return (_dbName.startsWith("oracle"));
-        }
-        
-        public PreparedStatement getLoadStatement (Connection connection, String rowId, String contextPath, String virtualHosts) 
-        throws SQLException
-        {
-            if (contextPath == null || "".equals(contextPath))
-            {
-                if (isEmptyStringNull())
-                {
-                    PreparedStatement statement = connection.prepareStatement("select * from "+_sessionTable+
-                    " where sessionId = ? and contextPath is null and virtualHost = ?");
-                    statement.setString(1, rowId);
-                    statement.setString(2, virtualHosts);
-
-                    return statement;
-                }
-            }
-           
-
+    }
 
-            PreparedStatement statement = connection.prepareStatement("select * from "+_sessionTable+
-            " where sessionId = ? and contextPath = ? and virtualHost = ?");
-            statement.setString(1, rowId);
-            statement.setString(2, contextPath);
-            statement.setString(3, virtualHosts);
+    
+    /**
+     * Scavenger
+     *
+     */
+    protected class Scavenger implements Runnable
+    {
 
-            return statement;
+        @Override
+        public void run()
+        {
+           try
+           {
+               scavenge();
+           }
+           finally
+           {
+               if (_scheduler != null && _scheduler.isRunning())
+                   _task = _scheduler.schedule(this, _scavengeIntervalMs, TimeUnit.MILLISECONDS);
+           }
         }
     }
-    
-    
-    
+
+
     public JDBCSessionIdManager(Server server)
     {
         super();
         _server=server;
     }
-    
+
     public JDBCSessionIdManager(Server server, Random random)
     {
        super(random);
@@ -250,7 +628,7 @@ public class JDBCSessionIdManager extends AbstractSessionIdManager
 
     /**
      * Configure jdbc connection information via a jdbc Driver
-     * 
+     *
      * @param driverClassName
      * @param connectionUrl
      */
@@ -259,10 +637,10 @@ public class JDBCSessionIdManager extends AbstractSessionIdManager
         _driverClassName=driverClassName;
         _connectionUrl=connectionUrl;
     }
-    
+
     /**
      * Configure jdbc connection information via a jdbc Driver
-     * 
+     *
      * @param driverClass
      * @param connectionUrl
      */
@@ -271,60 +649,120 @@ public class JDBCSessionIdManager extends AbstractSessionIdManager
         _driver=driverClass;
         _connectionUrl=connectionUrl;
     }
-    
-    
+
+
     public void setDatasource (DataSource ds)
     {
         _datasource = ds;
     }
-    
+
     public DataSource getDataSource ()
     {
         return _datasource;
     }
-    
+
     public String getDriverClassName()
     {
         return _driverClassName;
     }
-    
+
     public String getConnectionUrl ()
     {
         return _connectionUrl;
     }
-    
+
     public void setDatasourceName (String jndi)
     {
         _jndiName=jndi;
     }
-    
+
     public String getDatasourceName ()
     {
         return _jndiName;
     }
-   
+
+    /**
+     * @param name
+     * @deprecated see DbAdaptor.setBlobType
+     */
     public void setBlobType (String name)
     {
-        _blobType = name;
+        _dbAdaptor.setBlobType(name);
     }
-    
+
+    public DatabaseAdaptor getDbAdaptor()
+    {
+        return _dbAdaptor;
+    }
+
+    public void setDbAdaptor(DatabaseAdaptor dbAdaptor)
+    {
+        if (dbAdaptor == null)
+            throw new IllegalStateException ("DbAdaptor cannot be null");
+        
+        _dbAdaptor = dbAdaptor;
+    }
+
+    /**
+     * @return
+     * @deprecated see DbAdaptor.getBlobType
+     */
     public String getBlobType ()
     {
-        return _blobType;
+        return _dbAdaptor.getBlobType();
     }
-    
-    
-    
+
+    /**
+     * @return
+     * @deprecated see DbAdaptor.getLogType
+     */
     public String getLongType()
     {
-        return _longType;
+        return _dbAdaptor.getLongType();
     }
 
+    /**
+     * @param longType
+     * @deprecated see DbAdaptor.setLongType
+     */
     public void setLongType(String longType)
     {
-        this._longType = longType;
+       _dbAdaptor.setLongType(longType);
+    }
+    
+    public SessionIdTableSchema getSessionIdTableSchema()
+    {
+        return _sessionIdTableSchema;
+    }
+
+    public void setSessionIdTableSchema(SessionIdTableSchema sessionIdTableSchema)
+    {
+        if (sessionIdTableSchema == null)
+            throw new IllegalArgumentException("Null SessionIdTableSchema");
+        
+        _sessionIdTableSchema = sessionIdTableSchema;
+    }
+
+    public SessionTableSchema getSessionTableSchema()
+    {
+        return _sessionTableSchema;
+    }
+
+    public void setSessionTableSchema(SessionTableSchema sessionTableSchema)
+    {
+        _sessionTableSchema = sessionTableSchema;
+    }
+
+    public void setDeleteBlockSize (int bsize)
+    {
+        this._deleteBlockSize = bsize;
     }
 
+    public int getDeleteBlockSize ()
+    {
+        return this._deleteBlockSize;
+    }
+    
     public void setScavengeInterval (long sec)
     {
         if (sec<=0)
@@ -332,50 +770,47 @@ public class JDBCSessionIdManager extends AbstractSessionIdManager
 
         long old_period=_scavengeIntervalMs;
         long period=sec*1000L;
-      
+
         _scavengeIntervalMs=period;
-        
+
         //add a bit of variability into the scavenge time so that not all
         //nodes with the same scavenge time sync up
         long tenPercent = _scavengeIntervalMs/10;
         if ((System.currentTimeMillis()%2) == 0)
             _scavengeIntervalMs += tenPercent;
-        
-        if (LOG.isDebugEnabled()) 
+
+        if (LOG.isDebugEnabled())
             LOG.debug("Scavenging every "+_scavengeIntervalMs+" ms");
-        if (_timer!=null && (period!=old_period || _task==null))
+        
+        synchronized (this)
         {
-            synchronized (this)
+            //if (_timer!=null && (period!=old_period || _task==null))
+            if (_scheduler != null && (period!=old_period || _task==null))
             {
                 if (_task!=null)
                     _task.cancel();
-                _task = new TimerTask()
-                {
-                    @Override
-                    public void run()
-                    {
-                        scavenge();
-                    }   
-                };
-                _timer.schedule(_task,_scavengeIntervalMs,_scavengeIntervalMs);
+                if (_scavenger == null)
+                    _scavenger = new Scavenger();
+                _task = _scheduler.schedule(_scavenger,_scavengeIntervalMs,TimeUnit.MILLISECONDS);
             }
-        }  
+        }
     }
-    
+
     public long getScavengeInterval ()
     {
         return _scavengeIntervalMs/1000;
     }
-    
-    
+
+
+    @Override
     public void addSession(HttpSession session)
     {
         if (session == null)
             return;
-        
+
         synchronized (_sessionIds)
         {
-            String id = ((JDBCSessionManager.Session)session).getClusterId();            
+            String id = ((JDBCSessionManager.Session)session).getClusterId();
             try
             {
                 insert(id);
@@ -388,28 +823,51 @@ public class JDBCSessionIdManager extends AbstractSessionIdManager
         }
     }
     
+  
+    public void addSession(String id)
+    {
+        if (id == null)
+            return;
+
+        synchronized (_sessionIds)
+        {           
+            try
+            {
+                insert(id);
+                _sessionIds.add(id);
+            }
+            catch (Exception e)
+            {
+                LOG.warn("Problem storing session id="+id, e);
+            }
+        }
+    }
+
+
+
+    @Override
     public void removeSession(HttpSession session)
     {
         if (session == null)
             return;
-        
+
         removeSession(((JDBCSessionManager.Session)session).getClusterId());
     }
-    
-    
-    
+
+
+
     public void removeSession (String id)
     {
 
         if (id == null)
             return;
-        
+
         synchronized (_sessionIds)
-        {  
+        {
             if (LOG.isDebugEnabled())
-                LOG.debug("Removing session id="+id);
+                LOG.debug("Removing sessionid="+id);
             try
-            {               
+            {
                 _sessionIds.remove(id);
                 delete(id);
             }
@@ -418,48 +876,23 @@ public class JDBCSessionIdManager extends AbstractSessionIdManager
                 LOG.warn("Problem removing session id="+id, e);
             }
         }
-        
-    }
-    
-
-    /** 
-     * Get the session id without any node identifier suffix.
-     * 
-     * @see org.eclipse.jetty.server.SessionIdManager#getClusterId(java.lang.String)
-     */
-    public String getClusterId(String nodeId)
-    {
-        int dot=nodeId.lastIndexOf('.');
-        return (dot>0)?nodeId.substring(0,dot):nodeId;
-    }
-    
-
-    /** 
-     * Get the session id, including this node's id as a suffix.
-     * 
-     * @see org.eclipse.jetty.server.SessionIdManager#getNodeId(java.lang.String, javax.servlet.http.HttpServletRequest)
-     */
-    public String getNodeId(String clusterId, HttpServletRequest request)
-    {
-        if (_workerName!=null)
-            return clusterId+'.'+_workerName;
 
-        return clusterId;
     }
 
 
+    @Override
     public boolean idInUse(String id)
     {
         if (id == null)
             return false;
-        
+
         String clusterId = getClusterId(id);
         boolean inUse = false;
         synchronized (_sessionIds)
         {
             inUse = _sessionIds.contains(clusterId);
         }
-        
+
         
         if (inUse)
             return true; //optimisation - if this session is one we've been managing, we can check locally
@@ -476,16 +909,17 @@ public class JDBCSessionIdManager extends AbstractSessionIdManager
         }
     }
 
-    /** 
+    /**
      * Invalidate the session matching the id on all contexts.
-     * 
+     *
      * @see org.eclipse.jetty.server.SessionIdManager#invalidateAll(java.lang.String)
      */
+    @Override
     public void invalidateAll(String id)
-    {            
+    {
         //take the id out of the list of known sessionids for this node
         removeSession(id);
-        
+
         synchronized (_sessionIds)
         {
             //tell all contexts that may have a session object with this id to
@@ -493,8 +927,8 @@ public class JDBCSessionIdManager extends AbstractSessionIdManager
             Handler[] contexts = _server.getChildHandlersByClass(ContextHandler.class);
             for (int i=0; contexts!=null && i<contexts.length; i++)
             {
-                SessionHandler sessionHandler = (SessionHandler)((ContextHandler)contexts[i]).getChildHandlerByClass(SessionHandler.class);
-                if (sessionHandler != null) 
+                SessionHandler sessionHandler = ((ContextHandler)contexts[i]).getChildHandlerByClass(SessionHandler.class);
+                if (sessionHandler != null)
                 {
                     SessionManager manager = sessionHandler.getSessionManager();
 
@@ -508,9 +942,39 @@ public class JDBCSessionIdManager extends AbstractSessionIdManager
     }
 
 
-    /** 
+    @Override
+    public void renewSessionId (String oldClusterId, String oldNodeId, HttpServletRequest request)
+    {
+        //generate a new id
+        String newClusterId = newSessionId(request.hashCode());
+
+        synchronized (_sessionIds)
+        {
+            removeSession(oldClusterId);//remove the old one from the list (and database)
+            addSession(newClusterId); //add in the new session id to the list (and database)
+
+            //tell all contexts to update the id 
+            Handler[] contexts = _server.getChildHandlersByClass(ContextHandler.class);
+            for (int i=0; contexts!=null && i<contexts.length; i++)
+            {
+                SessionHandler sessionHandler = ((ContextHandler)contexts[i]).getChildHandlerByClass(SessionHandler.class);
+                if (sessionHandler != null) 
+                {
+                    SessionManager manager = sessionHandler.getSessionManager();
+
+                    if (manager != null && manager instanceof JDBCSessionManager)
+                    {
+                        ((JDBCSessionManager)manager).renewSessionId(oldClusterId, oldNodeId, newClusterId, getNodeId(newClusterId, request));
+                    }
+                }
+            }
+        }
+    }
+
+
+    /**
      * Start up the id manager.
-     * 
+     *
      * Makes necessary database tables and starts a Session
      * scavenger thread.
      */
@@ -520,36 +984,47 @@ public class JDBCSessionIdManager extends AbstractSessionIdManager
     {           
         initializeDatabase();
         prepareTables();   
-        cleanExpiredSessions();
         super.doStart();
         if (LOG.isDebugEnabled()) 
             LOG.debug("Scavenging interval = "+getScavengeInterval()+" sec");
-        _timer=new Timer("JDBCSessionScavenger", true);
+        
+         //try and use a common scheduler, fallback to own
+         _scheduler =_server.getBean(Scheduler.class);
+         if (_scheduler == null)
+         {
+             _scheduler = new ScheduledExecutorScheduler();
+             _ownScheduler = true;
+             _scheduler.start();
+         }
+         else if (!_scheduler.isStarted())
+             throw new IllegalStateException("Shared scheduler not started");
+  
         setScavengeInterval(getScavengeInterval());
     }
 
-    /** 
+    /**
      * Stop the scavenger.
      */
     @Override
-    public void doStop () 
+    public void doStop ()
     throws Exception
     {
         synchronized(this)
         {
             if (_task!=null)
                 _task.cancel();
-            if (_timer!=null)
-                _timer.cancel();
-            _timer=null;
+            _task=null;
+            if (_ownScheduler && _scheduler !=null)
+                _scheduler.stop();
+            _scheduler=null;
         }
         _sessionIds.clear();
         super.doStop();
     }
-  
+
     /**
      * Get a connection from the driver or datasource.
-     * 
+     *
      * @return the connection for the datasource
      * @throws SQLException
      */
@@ -562,203 +1037,188 @@ public class JDBCSessionIdManager extends AbstractSessionIdManager
             return DriverManager.getConnection(_connectionUrl);
     }
     
-    
-   
-    
-    
+
+
+
+
+
     /**
      * Set up the tables in the database
      * @throws SQLException
      */
+    /**
+     * @throws SQLException
+     */
     private void prepareTables()
     throws SQLException
     {
-        _createSessionIdTable = "create table "+_sessionIdTable+" (id varchar(120), primary key(id))";
-        _selectBoundedExpiredSessions = "select * from "+_sessionTable+" where expiryTime >= ? and expiryTime <= ?";
-        _selectExpiredSessions = "select * from "+_sessionTable+" where expiryTime >0 and expiryTime <= ?";
-        _deleteOldExpiredSessions = "delete from "+_sessionTable+" where expiryTime >0 and expiryTime <= ?";
-
-        _insertId = "insert into "+_sessionIdTable+" (id)  values (?)";
-        _deleteId = "delete from "+_sessionIdTable+" where id = ?";
-        _queryId = "select * from "+_sessionIdTable+" where id = ?";
-
-        Connection connection = null;
-        try
+        if (_sessionIdTableSchema == null)
+            throw new IllegalStateException ("No SessionIdTableSchema");
+        
+        if (_sessionTableSchema == null)
+            throw new IllegalStateException ("No SessionTableSchema");
+        
+        try (Connection connection = getConnection();
+             Statement statement = connection.createStatement())
         {
             //make the id table
-            connection = getConnection();
             connection.setAutoCommit(true);
             DatabaseMetaData metaData = connection.getMetaData();
-            _dbAdaptor = new DatabaseAdaptor(metaData);
-            _sessionTableRowId = _dbAdaptor.getRowIdColumnName();
-
+            _dbAdaptor.adaptTo(metaData);
+            _sessionTableSchema.setDatabaseAdaptor(_dbAdaptor);
+            _sessionIdTableSchema.setDatabaseAdaptor(_dbAdaptor);
+            
+            _createSessionIdTable = _sessionIdTableSchema.getCreateStatementAsString();
+            _insertId = _sessionIdTableSchema.getInsertStatementAsString();
+            _deleteId =  _sessionIdTableSchema.getDeleteStatementAsString();
+            _queryId = _sessionIdTableSchema.getSelectStatementAsString();
+            
             //checking for table existence is case-sensitive, but table creation is not
-            String tableName = _dbAdaptor.convertIdentifier(_sessionIdTable);
-            ResultSet result = metaData.getTables(null, null, tableName, null);
-            if (!result.next())
+            String tableName = _dbAdaptor.convertIdentifier(_sessionIdTableSchema.getTableName());
+            try (ResultSet result = metaData.getTables(null, null, tableName, null))
             {
-                //table does not exist, so create it
-                connection.createStatement().executeUpdate(_createSessionIdTable);
-            }
+                if (!result.next())
+                {
+                    //table does not exist, so create it
+                    statement.executeUpdate(_createSessionIdTable);
+                }
+            }         
             
             //make the session table if necessary
-            tableName = _dbAdaptor.convertIdentifier(_sessionTable);   
-            result = metaData.getTables(null, null, tableName, null);
-            if (!result.next())
+            tableName = _dbAdaptor.convertIdentifier(_sessionTableSchema.getTableName());
+            try (ResultSet result = metaData.getTables(null, null, tableName, null))
             {
-                //table does not exist, so create it
-                String blobType = _dbAdaptor.getBlobType();
-                String longType = _dbAdaptor.getLongType();
-                _createSessionTable = "create table "+_sessionTable+" ("+_sessionTableRowId+" varchar(120), sessionId varchar(120), "+
-                                           " contextPath varchar(60), virtualHost varchar(60), lastNode varchar(60), accessTime "+longType+", "+
-                                           " lastAccessTime "+longType+", createTime "+longType+", cookieTime "+longType+", "+
-                                           " lastSavedTime "+longType+", expiryTime "+longType+", map "+blobType+", primary key("+_sessionTableRowId+"))";
-                connection.createStatement().executeUpdate(_createSessionTable);
+                if (!result.next())
+                {
+                    //table does not exist, so create it
+                    _createSessionTable = _sessionTableSchema.getCreateStatementAsString();
+                    statement.executeUpdate(_createSessionTable);
+                }
+                else
+                {
+                    //session table exists, check it has maxinterval column
+                    ResultSet colResult = null;
+                    try
+                    {
+                        colResult = metaData.getColumns(null, null,
+                                                        _dbAdaptor.convertIdentifier(_sessionTableSchema.getTableName()), 
+                                                        _dbAdaptor.convertIdentifier(_sessionTableSchema.getMaxIntervalColumn()));
+                    }
+                    catch (SQLException s)
+                    {
+                        LOG.warn("Problem checking if "+_sessionTableSchema.getTableName()+
+                                 " table contains "+_sessionTableSchema.getMaxIntervalColumn()+" column. Ensure table contains column definition: \""
+                                +_sessionTableSchema.getMaxIntervalColumn()+" long not null default -999\"");
+                        throw s;
+                    }
+                    try
+                    {
+                        if (!colResult.next())
+                        {
+                            try
+                            {
+                                //add the maxinterval column
+                                statement.executeUpdate(_sessionTableSchema.getAlterTableForMaxIntervalAsString());
+                            }
+                            catch (SQLException s)
+                            {
+                                LOG.warn("Problem adding "+_sessionTableSchema.getMaxIntervalColumn()+
+                                         " column. Ensure table contains column definition: \""+_sessionTableSchema.getMaxIntervalColumn()+
+                                         " long not null default -999\"");
+                                throw s;
+                            }
+                        }
+                    }
+                    finally
+                    {
+                        colResult.close();
+                    }
+                }
             }
-            
             //make some indexes on the JettySessions table
-            String index1 = "idx_"+_sessionTable+"_expiry";
-            String index2 = "idx_"+_sessionTable+"_session";
-            
-            result = metaData.getIndexInfo(null, null, tableName, false, false);
+            String index1 = "idx_"+_sessionTableSchema.getTableName()+"_expiry";
+            String index2 = "idx_"+_sessionTableSchema.getTableName()+"_session";
+
             boolean index1Exists = false;
             boolean index2Exists = false;
-            while (result.next())
-            {
-                String idxName = result.getString("INDEX_NAME");
-                if (index1.equalsIgnoreCase(idxName))
-                    index1Exists = true;
-                else if (index2.equalsIgnoreCase(idxName))
-                    index2Exists = true;
-            }
-            if (!(index1Exists && index2Exists))
+            try (ResultSet result = metaData.getIndexInfo(null, null, tableName, false, false))
             {
-                Statement statement = connection.createStatement();
-                try
-                {
-                    if (!index1Exists)
-                        statement.executeUpdate("create index "+index1+" on "+_sessionTable+" (expiryTime)");
-                    if (!index2Exists)
-                        statement.executeUpdate("create index "+index2+" on "+_sessionTable+" (sessionId, contextPath)");
-                }
-                finally
+                while (result.next())
                 {
-                    if (statement!=null)
-                    {
-                        try { statement.close(); }
-                        catch(Exception e) { LOG.warn(e); }
-                    }
+                    String idxName = result.getString("INDEX_NAME");
+                    if (index1.equalsIgnoreCase(idxName))
+                        index1Exists = true;
+                    else if (index2.equalsIgnoreCase(idxName))
+                        index2Exists = true;
                 }
             }
+            if (!index1Exists)
+                statement.executeUpdate(_sessionTableSchema.getCreateIndexOverExpiryStatementAsString(index1));
+            if (!index2Exists)
+                statement.executeUpdate(_sessionTableSchema.getCreateIndexOverSessionStatementAsString(index2));
 
             //set up some strings representing the statements for session manipulation
-            _insertSession = "insert into "+_sessionTable+
-            " ("+_sessionTableRowId+", sessionId, contextPath, virtualHost, lastNode, accessTime, lastAccessTime, createTime, cookieTime, lastSavedTime, expiryTime, map) "+
-            " values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
-
-            _deleteSession = "delete from "+_sessionTable+
-            " where "+_sessionTableRowId+" = ?";
-            
-            _updateSession = "update "+_sessionTable+
-            " set lastNode = ?, accessTime = ?, lastAccessTime = ?, lastSavedTime = ?, expiryTime = ?, map = ? where "+_sessionTableRowId+" = ?";
-
-            _updateSessionNode = "update "+_sessionTable+
-            " set lastNode = ? where "+_sessionTableRowId+" = ?";
-
-            _updateSessionAccessTime = "update "+_sessionTable+
-            " set lastNode = ?, accessTime = ?, lastAccessTime = ?, lastSavedTime = ?, expiryTime = ? where "+_sessionTableRowId+" = ?";
-
-            
-        }
-        finally
-        {
-            if (connection != null)
-                connection.close();
+            _insertSession = _sessionTableSchema.getInsertSessionStatementAsString();
+            _deleteSession = _sessionTableSchema.getDeleteSessionStatementAsString();
+            _updateSession = _sessionTableSchema.getUpdateSessionStatementAsString();
+            _updateSessionNode = _sessionTableSchema.getUpdateSessionNodeStatementAsString();
+            _updateSessionAccessTime = _sessionTableSchema.getUpdateSessionAccessTimeStatementAsString();
+            _selectBoundedExpiredSessions = _sessionTableSchema.getBoundedExpiredSessionsStatementAsString();
+            _selectExpiredSessions = _sessionTableSchema.getSelectExpiredSessionsStatementAsString();
         }
     }
-    
+
     /**
      * Insert a new used session id into the table.
-     * 
+     *
      * @param id
      * @throws SQLException
      */
     private void insert (String id)
-    throws SQLException 
+    throws SQLException
     {
-        Connection connection = null;
-        PreparedStatement statement = null;
-        PreparedStatement query = null;
-        try
+        try (Connection connection = getConnection();
+                PreparedStatement query = connection.prepareStatement(_queryId))
         {
-            connection = getConnection();
-            connection.setAutoCommit(true);            
-            query = connection.prepareStatement(_queryId);
+            connection.setAutoCommit(true);
             query.setString(1, id);
-            ResultSet result = query.executeQuery();
-            //only insert the id if it isn't in the db already 
-            if (!result.next())
-            {
-                statement = connection.prepareStatement(_insertId);
-                statement.setString(1, id);
-                statement.executeUpdate();
-            }
-        }
-        finally
-        {
-            if (query!=null)
+            try (ResultSet result = query.executeQuery())
             {
-                try { query.close(); }
-                catch(Exception e) { LOG.warn(e); }
-            }
-
-            if (statement!=null)
-            {
-                try { statement.close(); }
-                catch(Exception e) { LOG.warn(e); }
+                //only insert the id if it isn't in the db already
+                if (!result.next())
+                {
+                    try (PreparedStatement statement = connection.prepareStatement(_insertId))
+                    {
+                        statement.setString(1, id);
+                        statement.executeUpdate();
+                    }
+                }
             }
-
-            if (connection != null)
-                connection.close();
         }
     }
-    
+
     /**
      * Remove a session id from the table.
-     * 
+     *
      * @param id
      * @throws SQLException
      */
     private void delete (String id)
     throws SQLException
     {
-        Connection connection = null;
-        PreparedStatement statement = null;
-        try
+        try (Connection connection = getConnection();
+                PreparedStatement statement = connection.prepareStatement(_deleteId))
         {
-            connection = getConnection();
             connection.setAutoCommit(true);
-            statement = connection.prepareStatement(_deleteId);
             statement.setString(1, id);
             statement.executeUpdate();
         }
-        finally
-        {
-            if (statement!=null)
-            {
-                try { statement.close(); }
-                catch(Exception e) { LOG.warn(e); }
-            }
-
-            if (connection != null)
-                connection.close();
-        }
     }
-    
-    
+
+
     /**
      * Check if a session id exists.
-     * 
+     *
      * @param id
      * @return
      * @throws SQLException
@@ -766,37 +1226,25 @@ public class JDBCSessionIdManager extends AbstractSessionIdManager
     private boolean exists (String id)
     throws SQLException
     {
-        Connection connection = null;
-        PreparedStatement statement = null;
-        try
+        try (Connection connection = getConnection();
+                PreparedStatement statement = connection.prepareStatement(_queryId))
         {
-            connection = getConnection();
             connection.setAutoCommit(true);
-            statement = connection.prepareStatement(_queryId);
             statement.setString(1, id);
-            ResultSet result = statement.executeQuery();
-            return result.next();
-        }
-        finally
-        {
-            if (statement!=null)
+            try (ResultSet result = statement.executeQuery())
             {
-                try { statement.close(); }
-                catch(Exception e) { LOG.warn(e); }
+                return result.next();
             }
-
-            if (connection != null)
-                connection.close();
         }
     }
-    
+
     /**
      * Look for sessions in the database that have expired.
-     * 
+     *
      * We do this in the SessionIdManager and not the SessionManager so
      * that we only have 1 scavenger, otherwise if there are n SessionManagers
      * there would be n scavengers, all contending for the database.
-     * 
+     *
      * We look first for sessions that expired in the previous interval, then
      * for sessions that expired previously - these are old sessions that no
      * node is managing any more and have become stuck in the database.
@@ -804,88 +1252,105 @@ public class JDBCSessionIdManager extends AbstractSessionIdManager
     private void scavenge ()
     {
         Connection connection = null;
-        PreparedStatement statement = null;
-        List<String> expiredSessionIds = new ArrayList<String>();
         try
-        {            
-            if (LOG.isDebugEnabled()) 
-                LOG.debug("Scavenge sweep started at "+System.currentTimeMillis());
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug(getWorkerName()+"- Scavenge sweep started at "+System.currentTimeMillis());
             if (_lastScavengeTime > 0)
             {
                 connection = getConnection();
                 connection.setAutoCommit(true);
-                //"select sessionId from JettySessions where expiryTime > (lastScavengeTime - scanInterval) and expiryTime < lastScavengeTime";
-                statement = connection.prepareStatement(_selectBoundedExpiredSessions);
+                Set<String> expiredSessionIds = new HashSet<String>();
+                
+                
+                //Pass 1: find sessions for which we were last managing node that have just expired since last pass
                 long lowerBound = (_lastScavengeTime - _scavengeIntervalMs);
                 long upperBound = _lastScavengeTime;
-                if (LOG.isDebugEnabled()) 
-                    LOG.debug (" Searching for sessions expired between "+lowerBound + " and "+upperBound);
-                
-                statement.setLong(1, lowerBound);
-                statement.setLong(2, upperBound);
-                ResultSet result = statement.executeQuery();
-                while (result.next())
-                {
-                    String sessionId = result.getString("sessionId");
-                    expiredSessionIds.add(sessionId);
-                    if (LOG.isDebugEnabled()) LOG.debug (" Found expired sessionId="+sessionId); 
-                }
+                if (LOG.isDebugEnabled())
+                    LOG.debug (getWorkerName()+"- Pass 1: Searching for sessions expired between "+lowerBound + " and "+upperBound);
 
-                //tell the SessionManagers to expire any sessions with a matching sessionId in memory
-                Handler[] contexts = _server.getChildHandlersByClass(ContextHandler.class);
-                for (int i=0; contexts!=null && i<contexts.length; i++)
+                try (PreparedStatement statement = connection.prepareStatement(_selectBoundedExpiredSessions))
                 {
-
-                    SessionHandler sessionHandler = (SessionHandler)((ContextHandler)contexts[i]).getChildHandlerByClass(SessionHandler.class);
-                    if (sessionHandler != null) 
-                    { 
-                        SessionManager manager = sessionHandler.getSessionManager();
-                        if (manager != null && manager instanceof JDBCSessionManager)
+                    statement.setString(1, getWorkerName());
+                    statement.setLong(2, lowerBound);
+                    statement.setLong(3, upperBound);
+                    try (ResultSet result = statement.executeQuery())
+                    {
+                        while (result.next())
                         {
-                            ((JDBCSessionManager)manager).expire(expiredSessionIds);
+                            String sessionId = result.getString(_sessionTableSchema.getIdColumn());
+                            expiredSessionIds.add(sessionId);
+                            if (LOG.isDebugEnabled()) LOG.debug ("Found expired sessionId="+sessionId);
                         }
                     }
                 }
+                scavengeSessions(expiredSessionIds, false);
+
 
-                //find all sessions that have expired at least a couple of scanIntervals ago and just delete them
-                upperBound = _lastScavengeTime - (2 * _scavengeIntervalMs);
-                if (upperBound > 0)
+                //Pass 2: find sessions that have expired a while ago for which this node was their last manager
+                try (PreparedStatement selectExpiredSessions = connection.prepareStatement(_selectExpiredSessions))
                 {
-                    if (LOG.isDebugEnabled()) LOG.debug("Deleting old expired sessions expired before "+upperBound);
-                    try
+                    expiredSessionIds.clear();
+                    upperBound = _lastScavengeTime - (2 * _scavengeIntervalMs);
+                    if (upperBound > 0)
                     {
-                        statement = connection.prepareStatement(_deleteOldExpiredSessions);
-                        statement.setLong(1, upperBound);
-                        int rows = statement.executeUpdate();
-                        if (LOG.isDebugEnabled()) LOG.debug("Deleted "+rows+" rows of old sessions expired before "+upperBound);
+                        if (LOG.isDebugEnabled()) LOG.debug(getWorkerName()+"- Pass 2: Searching for sessions expired before "+upperBound);
+                        selectExpiredSessions.setLong(1, upperBound);
+                        try (ResultSet result = selectExpiredSessions.executeQuery())
+                        {
+                            while (result.next())
+                            {
+                                String sessionId = result.getString(_sessionTableSchema.getIdColumn());
+                                String lastNode = result.getString(_sessionTableSchema.getLastNodeColumn());
+                                if ((getWorkerName() == null && lastNode == null) || (getWorkerName() != null && getWorkerName().equals(lastNode)))
+                                    expiredSessionIds.add(sessionId);
+                                if (LOG.isDebugEnabled()) LOG.debug ("Found expired sessionId="+sessionId+" last managed by "+getWorkerName());
+                            }
+                        }
+                        scavengeSessions(expiredSessionIds, false);
                     }
-                    finally
+
+
+                    //Pass 3:
+                    //find all sessions that have expired at least a couple of scanIntervals ago
+                    //if we did not succeed in loading them (eg their related context no longer exists, can't be loaded etc) then
+                    //they are simply deleted
+                    upperBound = _lastScavengeTime - (3 * _scavengeIntervalMs);
+                    expiredSessionIds.clear();
+                    if (upperBound > 0)
                     {
-                        if (statement!=null)
+                        if (LOG.isDebugEnabled()) LOG.debug(getWorkerName()+"- Pass 3: searching for sessions expired before "+upperBound);
+                        selectExpiredSessions.setLong(1, upperBound);
+                        try (ResultSet result = selectExpiredSessions.executeQuery())
                         {
-                            try { statement.close(); }
-                            catch(Exception e) { LOG.warn(e); }
+                            while (result.next())
+                            {
+                                String sessionId = result.getString(_sessionTableSchema.getIdColumn());
+                                expiredSessionIds.add(sessionId);
+                                if (LOG.isDebugEnabled()) LOG.debug ("Found expired sessionId="+sessionId);
+                            }
                         }
+                        scavengeSessions(expiredSessionIds, true);
                     }
                 }
             }
         }
         catch (Exception e)
         {
-            if (isRunning())    
+            if (isRunning())
                 LOG.warn("Problem selecting expired sessions", e);
             else
                 LOG.ignore(e);
         }
         finally
-        {           
+        {
             _lastScavengeTime=System.currentTimeMillis();
-            if (LOG.isDebugEnabled()) LOG.debug("Scavenge sweep ended at "+_lastScavengeTime);
+            if (LOG.isDebugEnabled()) LOG.debug(getWorkerName()+"- Scavenge sweep ended at "+_lastScavengeTime);
             if (connection != null)
             {
                 try
                 {
-                connection.close();
+                    connection.close();
                 }
                 catch (SQLException e)
                 {
@@ -895,124 +1360,122 @@ public class JDBCSessionIdManager extends AbstractSessionIdManager
         }
     }
     
+    
     /**
-     * Get rid of sessions and sessionids from sessions that have already expired
-     * @throws Exception
+     * @param expiredSessionIds
      */
-    private void cleanExpiredSessions ()
-    {
-        Connection connection = null;
-        PreparedStatement statement = null;
-        Statement sessionsTableStatement = null;
-        Statement sessionIdsTableStatement = null;
-        List<String> expiredSessionIds = new ArrayList<String>();
-        try
-        {     
-            connection = getConnection();
-            connection.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
-            connection.setAutoCommit(false);
-
-            statement = connection.prepareStatement(_selectExpiredSessions);
-            long now = System.currentTimeMillis();
-            if (LOG.isDebugEnabled()) LOG.debug ("Searching for sessions expired before {}", now);
-
-            statement.setLong(1, now);
-            ResultSet result = statement.executeQuery();
-            while (result.next())
-            {
-                String sessionId = result.getString("sessionId");
-                expiredSessionIds.add(sessionId);
-                if (LOG.isDebugEnabled()) LOG.debug ("Found expired sessionId={}", sessionId); 
-            }
-            
-            sessionsTableStatement = null;
-            sessionIdsTableStatement = null;
-
-            if (!expiredSessionIds.isEmpty())
-            {
-                sessionsTableStatement = connection.createStatement();
-                sessionsTableStatement.executeUpdate(createCleanExpiredSessionsSql("delete from "+_sessionTable+" where sessionId in ", expiredSessionIds));
-                sessionIdsTableStatement = connection.createStatement();
-                sessionIdsTableStatement.executeUpdate(createCleanExpiredSessionsSql("delete from "+_sessionIdTable+" where id in ", expiredSessionIds));
-            }
-            connection.commit();
-
-            synchronized (_sessionIds)
-            {
-                _sessionIds.removeAll(expiredSessionIds); //in case they were in our local cache of session ids
-            }
-        }
-        catch (Exception e)
+    private void scavengeSessions (Set<String> expiredSessionIds, boolean forceDelete)
+    {       
+        Set<String> remainingIds = new HashSet<String>(expiredSessionIds);
+        Handler[] contexts = _server.getChildHandlersByClass(ContextHandler.class);
+        for (int i=0; contexts!=null && i<contexts.length; i++)
         {
-            if (connection != null)
+            SessionHandler sessionHandler = ((ContextHandler)contexts[i]).getChildHandlerByClass(SessionHandler.class);
+            if (sessionHandler != null)
             {
-                try 
-                { 
-                    LOG.warn("Rolling back clean of expired sessions", e);
-                    connection.rollback();
+                SessionManager manager = sessionHandler.getSessionManager();
+                if (manager != null && manager instanceof JDBCSessionManager)
+                {
+                    Set<String> successfullyExpiredIds = ((JDBCSessionManager)manager).expire(expiredSessionIds);
+                    if (successfullyExpiredIds != null)
+                        remainingIds.removeAll(successfullyExpiredIds);
                 }
-                catch (Exception x) { LOG.warn("Rollback of expired sessions failed", x);}
             }
         }
-        finally
+
+        //Any remaining ids are of those sessions that no context removed
+        if (!remainingIds.isEmpty() && forceDelete)
         {
-            if (sessionIdsTableStatement!=null)
+            LOG.info("Forcibly deleting unrecoverable expired sessions {}", remainingIds);
+            try
             {
-                try { sessionIdsTableStatement.close(); }
-                catch(Exception e) { LOG.warn(e); }
+                //ensure they aren't in the local list of in-use session ids
+                synchronized (_sessionIds)
+                {
+                    _sessionIds.removeAll(remainingIds);
+                }
+                
+                cleanExpiredSessionIds(remainingIds);
             }
-
-            if (sessionsTableStatement!=null)
+            catch (Exception e)
             {
-                try { sessionsTableStatement.close(); }
-                catch(Exception e) { LOG.warn(e); }
+                LOG.warn("Error removing expired session ids", e);
             }
+        }
+    }
 
-            if (statement!=null)
-            {
-                try { statement.close(); }
-                catch(Exception e) { LOG.warn(e); }
-            }
 
-            try
+   
+    
+    private void cleanExpiredSessionIds (Set<String> expiredIds)
+    throws Exception
+    {
+        if (expiredIds == null || expiredIds.isEmpty())
+            return;
+
+        String[] ids = expiredIds.toArray(new String[expiredIds.size()]);
+        try (Connection con = getConnection())
+        {
+            con.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
+            con.setAutoCommit(false);
+
+            int start = 0;
+            int end = 0;
+            int blocksize = _deleteBlockSize;
+            int block = 0;
+       
+            try (Statement statement = con.createStatement())
             {
-                if (connection != null)
-                    connection.close();
+                while (end < ids.length)
+                {
+                    start = block*blocksize;
+                    if ((ids.length -  start)  >= blocksize)
+                        end = start + blocksize;
+                    else
+                        end = ids.length;
+
+                    //take them out of the sessionIds table
+                    statement.executeUpdate(fillInClause("delete from "+_sessionIdTableSchema.getTableName()+" where "+_sessionIdTableSchema.getIdColumn()+" in ", ids, start, end));
+                    //take them out of the sessions table
+                    statement.executeUpdate(fillInClause("delete from "+_sessionTableSchema.getTableName()+" where "+_sessionTableSchema.getIdColumn()+" in ", ids, start, end));
+                    block++;
+                }
             }
-            catch (SQLException e)
+            catch (Exception e)
             {
-                LOG.warn(e);
+                con.rollback();
+                throw e;
             }
+            con.commit();
         }
     }
+
     
     
     /**
      * 
      * @param sql
-     * @param connection
-     * @param expiredSessionIds
+     * @param atoms
      * @throws Exception
      */
-    private String createCleanExpiredSessionsSql (String sql,Collection<String> expiredSessionIds)
+    private String fillInClause (String sql, String[] literals, int start, int end)
     throws Exception
     {
         StringBuffer buff = new StringBuffer();
         buff.append(sql);
         buff.append("(");
-        Iterator<String> itor = expiredSessionIds.iterator();
-        while (itor.hasNext())
+        for (int i=start; i<end; i++)
         {
-            buff.append("'"+(itor.next())+"'");
-            if (itor.hasNext())
+            buff.append("'"+(literals[i])+"'");
+            if (i+1<end)
                 buff.append(",");
         }
         buff.append(")");
-        
-        if (LOG.isDebugEnabled()) LOG.debug("Cleaning expired sessions with: {}", buff);
         return buff.toString();
     }
     
+    
+    
     private void initializeDatabase ()
     throws Exception
     {
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionManager.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionManager.java
index 5e458bc..66c98be 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionManager.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionManager.java
@@ -21,30 +21,25 @@ package org.eclipse.jetty.server.session;
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
-import java.io.IOException;
 import java.io.InputStream;
-import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
 import java.sql.Connection;
 import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.SQLException;
-import java.sql.Statement;
-import java.util.HashMap;
-import java.util.List;
-import java.util.ListIterator;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
 
-import javax.servlet.SessionTrackingMode;
 import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpSessionEvent;
-import javax.servlet.http.HttpSessionListener;
-
-import org.eclipse.jetty.server.SessionIdManager;
 import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.session.JDBCSessionIdManager.SessionTableSchema;
+import org.eclipse.jetty.util.ClassLoadingObjectInputStream;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
 
@@ -76,9 +71,10 @@ public class JDBCSessionManager extends AbstractSessionManager
 {
     private static final Logger LOG = Log.getLogger(JDBCSessionManager.class);
 
-    private ConcurrentHashMap<String, AbstractSession> _sessions;
+    private ConcurrentHashMap<String, Session> _sessions;
     protected JDBCSessionIdManager _jdbcSessionIdMgr = null;
     protected long _saveIntervalSec = 60; //only persist changes to session access times every 60 secs
+    protected SessionTableSchema _sessionTableSchema;
 
    
 
@@ -88,56 +84,56 @@ public class JDBCSessionManager extends AbstractSessionManager
      *
      * Session instance.
      */
-    public class Session extends AbstractSession
+    public class Session extends MemSession
     {
         private static final long serialVersionUID = 5208464051134226143L;
         
         /**
          * If dirty, session needs to be (re)persisted
          */
-        private boolean _dirty=false;
+        protected boolean _dirty=false;
         
         
         /**
          * Time in msec since the epoch that a session cookie was set for this session
          */
-        private long _cookieSet;
+        protected long _cookieSet;
         
         
         /**
          * Time in msec since the epoch that the session will expire
          */
-        private long _expiryTime;
+        protected long _expiryTime;
         
         
         /**
          * Time in msec since the epoch that the session was last persisted
          */
-        private long _lastSaved;
+        protected long _lastSaved;
         
         
         /**
          * Unique identifier of the last node to host the session
          */
-        private String _lastNode;
+        protected String _lastNode;
         
         
         /**
          * Virtual host for context (used to help distinguish 2 sessions with same id on different contexts)
          */
-        private String _virtualHost;
+        protected String _virtualHost;
         
         
         /**
          * Unique row in db for session
          */
-        private String _rowId;
+        protected String _rowId;
         
         
         /**
          * Mangled context name (used to help distinguish 2 sessions with same id on different contexts)
          */
-        private String _canonicalContext;
+        protected String _canonicalContext;
         
    
         /**
@@ -163,10 +159,12 @@ public class JDBCSessionManager extends AbstractSessionManager
          * @param created
          * @param accessed
          */
-        protected Session (String sessionId, String rowId, long created, long accessed)
+        protected Session (String sessionId, String rowId, long created, long accessed, long maxInterval)
         {
             super(JDBCSessionManager.this, created, accessed, sessionId);
             _rowId = rowId;
+            super.setMaxInactiveInterval((int)maxInterval); //restore the session's previous inactivity interval setting
+            _expiryTime = (maxInterval <= 0 ? 0 : (System.currentTimeMillis() + maxInterval*1000L));
         }
         
         
@@ -244,15 +242,19 @@ public class JDBCSessionManager extends AbstractSessionManager
         @Override
         public void setAttribute (String name, Object value)
         {
-            super.setAttribute(name, value);
-            _dirty=true;
+            Object old = changeAttribute(name, value);
+            if (value == null && old == null)
+                return; //if same as remove attribute but attribute was already removed, no change
+            
+            _dirty = true;
         }
 
         @Override
         public void removeAttribute (String name)
         {
-            super.removeAttribute(name);
-            _dirty=true;
+            Object old = changeAttribute(name, null);
+            if (old != null) //only dirty if there was a previous value
+                _dirty=true;
         }
 
         @Override
@@ -282,6 +284,33 @@ public class JDBCSessionManager extends AbstractSessionManager
             }
         }
         
+        
+        
+
+
+        /** 
+         * Change the max idle time for this session. This recalculates the expiry time.
+         * @see org.eclipse.jetty.server.session.AbstractSession#setMaxInactiveInterval(int)
+         */
+        @Override
+        public void setMaxInactiveInterval(int secs)
+        {
+            synchronized (this)
+            {
+                super.setMaxInactiveInterval(secs);
+                int maxInterval=getMaxInactiveInterval();
+                _expiryTime = (maxInterval <= 0 ? 0 : (System.currentTimeMillis() + maxInterval*1000L));
+                //force the session to be written out right now
+                try
+                {
+                    updateSessionAccessTime(this);
+                }
+                catch (Exception e)
+                {
+                    LOG.warn("Problem saving changed max idle time for session "+ this, e);
+                }
+            }
+        }
 
 
         /**
@@ -302,9 +331,7 @@ public class JDBCSessionManager extends AbstractSessionManager
                         {
                             //The session attributes have changed, write to the db, ensuring
                             //http passivation/activation listeners called
-                            willPassivate();                      
-                            updateSession(this);
-                            didActivate();
+                            save(true);
                         }
                         else if ((getAccessed() - _lastSaved) >= (getSaveInterval() * 1000L))
                         {
@@ -323,6 +350,38 @@ public class JDBCSessionManager extends AbstractSessionManager
             }
         }
 
+        protected void save() throws Exception
+        {
+            synchronized (this)
+            {
+                try
+                {
+                    updateSession(this);
+                }
+                finally
+                {
+                    _dirty = false;
+                }
+            }
+        }
+
+        protected void save (boolean reactivate) throws Exception
+        {
+            synchronized (this)
+            {
+                if (_dirty)
+                {
+                    //The session attributes have changed, write to the db, ensuring
+                    //http passivation/activation listeners called
+                    willPassivate();                      
+                    updateSession(this);
+                    if (reactivate)
+                        didActivate();  
+                }
+            }
+        }
+
+        
         @Override
         protected void timeout() throws IllegalStateException
         {
@@ -331,13 +390,14 @@ public class JDBCSessionManager extends AbstractSessionManager
             super.timeout();
         }
         
+        
         @Override
         public String toString ()
         {
             return "Session rowId="+_rowId+",id="+getId()+",lastNode="+_lastNode+
                             ",created="+getCreationTime()+",accessed="+getAccessed()+
                             ",lastAccessed="+getLastAccessedTime()+",cookieSet="+_cookieSet+
-                            ",lastSaved="+_lastSaved+",expiry="+_expiryTime;
+                            ",maxInterval="+getMaxInactiveInterval()+",lastSaved="+_lastSaved+",expiry="+_expiryTime;
         }
     }
 
@@ -345,38 +405,6 @@ public class JDBCSessionManager extends AbstractSessionManager
 
 
     /**
-     * ClassLoadingObjectInputStream
-     *
-     * Used to persist the session attribute map
-     */
-    protected class ClassLoadingObjectInputStream extends ObjectInputStream
-    {
-        public ClassLoadingObjectInputStream(java.io.InputStream in) throws IOException
-        {
-            super(in);
-        }
-
-        public ClassLoadingObjectInputStream () throws IOException
-        {
-            super();
-        }
-
-        @Override
-        public Class<?> resolveClass (java.io.ObjectStreamClass cl) throws IOException, ClassNotFoundException
-        {
-            try
-            {
-                return Class.forName(cl.getName(), false, Thread.currentThread().getContextClassLoader());
-            }
-            catch (ClassNotFoundException e)
-            {
-                return super.resolveClass(cl);
-            }
-        }
-    }
-
-
-    /**
      * Set the time in seconds which is the interval between
      * saving the session access time to the database.
      *
@@ -443,112 +471,123 @@ public class JDBCSessionManager extends AbstractSessionManager
     public Session getSession(String idInCluster)
     {
         Session session = null;
-        Session memSession = (Session)_sessions.get(idInCluster);
-
+        
         synchronized (this)
         {
-                //check if we need to reload the session -
-                //as an optimization, don't reload on every access
-                //to reduce the load on the database. This introduces a window of
-                //possibility that the node may decide that the session is local to it,
-                //when the session has actually been live on another node, and then
-                //re-migrated to this node. This should be an extremely rare occurrence,
-                //as load-balancers are generally well-behaved and consistently send
-                //sessions to the same node, changing only iff that node fails.
-                //Session data = null;
-                long now = System.currentTimeMillis();
-                if (LOG.isDebugEnabled())
-                {
-                    if (memSession==null)
-                        LOG.debug("getSession("+idInCluster+"): not in session map,"+
-                                " now="+now+
-                                " lastSaved="+(memSession==null?0:memSession._lastSaved)+
-                                " interval="+(_saveIntervalSec * 1000L));
-                    else
-                        LOG.debug("getSession("+idInCluster+"): in session map, "+
-                                " now="+now+
-                                " lastSaved="+(memSession==null?0:memSession._lastSaved)+
-                                " interval="+(_saveIntervalSec * 1000L)+
-                                " lastNode="+memSession._lastNode+
-                                " thisNode="+getSessionIdManager().getWorkerName()+
-                                " difference="+(now - memSession._lastSaved));
-                }
+            Session memSession = (Session)_sessions.get(idInCluster);
+            
+            //check if we need to reload the session -
+            //as an optimization, don't reload on every access
+            //to reduce the load on the database. This introduces a window of
+            //possibility that the node may decide that the session is local to it,
+            //when the session has actually been live on another node, and then
+            //re-migrated to this node. This should be an extremely rare occurrence,
+            //as load-balancers are generally well-behaved and consistently send
+            //sessions to the same node, changing only iff that node fails.
+            //Session data = null;
+            long now = System.currentTimeMillis();
+            if (LOG.isDebugEnabled())
+            {
+                if (memSession==null)
+                    LOG.debug("getSession("+idInCluster+"): not in session map,"+
+                            " now="+now+
+                            " lastSaved="+(memSession==null?0:memSession._lastSaved)+
+                            " interval="+(_saveIntervalSec * 1000L));
+                else
+                    LOG.debug("getSession("+idInCluster+"): in session map, "+
+                            " hashcode="+memSession.hashCode()+
+                            " now="+now+
+                            " lastSaved="+(memSession==null?0:memSession._lastSaved)+
+                            " interval="+(_saveIntervalSec * 1000L)+
+                            " lastNode="+memSession._lastNode+
+                            " thisNode="+getSessionIdManager().getWorkerName()+
+                            " difference="+(now - memSession._lastSaved));
+            }
 
-                try
+            try
+            {
+                if (memSession==null)
                 {
-                    if (memSession==null)
-                    {
+                    if (LOG.isDebugEnabled())
                         LOG.debug("getSession("+idInCluster+"): no session in session map. Reloading session data from db.");
-                        session = loadSession(idInCluster, canonicalize(_context.getContextPath()), getVirtualHost(_context));
-                    }
-                    else if ((now - memSession._lastSaved) >= (_saveIntervalSec * 1000L))
-                    {
+                    session = loadSession(idInCluster, canonicalize(_context.getContextPath()), getVirtualHost(_context));
+                }
+                else if ((now - memSession._lastSaved) >= (_saveIntervalSec * 1000L))
+                {
+                    if (LOG.isDebugEnabled())
                         LOG.debug("getSession("+idInCluster+"): stale session. Reloading session data from db.");
-                        session = loadSession(idInCluster, canonicalize(_context.getContextPath()), getVirtualHost(_context));
-                    }
-                    else
-                    {
-                        LOG.debug("getSession("+idInCluster+"): session in session map");
-                        session = memSession;
-                    }
+                    session = loadSession(idInCluster, canonicalize(_context.getContextPath()), getVirtualHost(_context));
                 }
-                catch (Exception e)
+                else
                 {
-                    LOG.warn("Unable to load session "+idInCluster, e);
-                    return null;
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("getSession("+idInCluster+"): session in session map");
+                    session = memSession;
                 }
+            }
+            catch (Exception e)
+            {
+                LOG.warn("Unable to load session "+idInCluster, e);
+                return null;
+            }
 
-                
-                //If we have a session
-                if (session != null)
+
+            //If we have a session
+            if (session != null)
+            {
+                //If the session was last used on a different node, or session doesn't exist on this node
+                if (!session.getLastNode().equals(getSessionIdManager().getWorkerName()) || memSession==null)
                 {
-                    //If the session was last used on a different node, or session doesn't exist on this node
-                    if (!session.getLastNode().equals(getSessionIdManager().getWorkerName()) || memSession==null)
+                    //if session doesn't expire, or has not already expired, update it and put it in this nodes' memory
+                    if (session._expiryTime <= 0 || session._expiryTime > now)
                     {
-                        //if session doesn't expire, or has not already expired, update it and put it in this nodes' memory
-                        if (session._expiryTime <= 0 || session._expiryTime > now)
+                        if (LOG.isDebugEnabled()) 
+                            LOG.debug("getSession("+idInCluster+"): lastNode="+session.getLastNode()+" thisNode="+getSessionIdManager().getWorkerName());
+
+                        session.setLastNode(getSessionIdManager().getWorkerName());                            
+                        _sessions.put(idInCluster, session);
+
+                        //update in db
+                        try
                         {
-                            if (LOG.isDebugEnabled()) 
-                                LOG.debug("getSession("+idInCluster+"): lastNode="+session.getLastNode()+" thisNode="+getSessionIdManager().getWorkerName());
-                            
-                            session.setLastNode(getSessionIdManager().getWorkerName());                            
-                            _sessions.put(idInCluster, session);
-                            
-                            //update in db: if unable to update, session will be scavenged later
-                            try
-                            {
-                                updateSessionNode(session);
-                                session.didActivate();
-                            }
-                            catch (Exception e)
-                            {
-                                LOG.warn("Unable to update freshly loaded session "+idInCluster, e);
-                                return null;
-                            }
+                            updateSessionNode(session);
+                            session.didActivate();
                         }
-                        else
+                        catch (Exception e)
                         {
-                            LOG.debug("getSession ({}): Session has expired", idInCluster);  
-                            session=null;
+                            LOG.warn("Unable to update freshly loaded session "+idInCluster, e);
+                            return null;
                         }
-
                     }
                     else
                     {
-                       //the session loaded from the db and the one in memory are the same, so keep using the one in memory
-                       session = memSession;
-                       LOG.debug("getSession({}): Session not stale {}", idInCluster,session);
+                        if (LOG.isDebugEnabled())
+                            LOG.debug("getSession ({}): Session has expired", idInCluster);
+                        //ensure that the session id for the expired session is deleted so that a new session with the 
+                        //same id cannot be created (because the idInUse() test would succeed)
+                        _jdbcSessionIdMgr.removeSession(idInCluster);
+                        session=null;
                     }
+
                 }
                 else
                 {
-                    //No session in db with matching id and context path.
-                    LOG.debug("getSession({}): No session in database matching id={}",idInCluster,idInCluster);
+                    //the session loaded from the db and the one in memory are the same, so keep using the one in memory
+                    session = memSession;
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("getSession({}): Session not stale {}", idInCluster,session);
                 }
+            }
+            else
+            {
+                //No session in db with matching id and context path.
+                LOG.debug("getSession({}): No session in database matching id={}",idInCluster,idInCluster);
+            }
 
-                return session;
+            return session;
         }
     }
+    
 
     /**
      * Get the number of sessions.
@@ -558,12 +597,7 @@ public class JDBCSessionManager extends AbstractSessionManager
     @Override
     public int getSessions()
     {
-        int size = 0;
-        synchronized (this)
-        {
-            size = _sessions.size();
-        }
-        return size;
+        return _sessions.size();
     }
 
 
@@ -579,8 +613,9 @@ public class JDBCSessionManager extends AbstractSessionManager
             throw new IllegalStateException("No session id manager defined");
 
         _jdbcSessionIdMgr = (JDBCSessionIdManager)_sessionIdManager;
-        
-        _sessions = new ConcurrentHashMap<String, AbstractSession>();
+        _sessionTableSchema = _jdbcSessionIdMgr.getSessionTableSchema();
+
+        _sessions = new ConcurrentHashMap<String, Session>();
 
         super.doStart();
     }
@@ -594,23 +629,79 @@ public class JDBCSessionManager extends AbstractSessionManager
     @Override
     public void doStop() throws Exception
     {
+        super.doStop();
         _sessions.clear();
         _sessions = null;
-
-        super.doStop();
     }
 
     @Override
-    protected void invalidateSessions()
+    protected void shutdownSessions()
+    {
+        //Save the current state of all of our sessions,
+        //do NOT delete them (so other nodes can manage them)
+        long gracefulStopMs = getContextHandler().getServer().getStopTimeout();
+        long stopTime = 0;
+        if (gracefulStopMs > 0)
+            stopTime = System.nanoTime() + (TimeUnit.NANOSECONDS.convert(gracefulStopMs, TimeUnit.MILLISECONDS));        
+
+        ArrayList<Session> sessions = (_sessions == null? new ArrayList<Session>() :new ArrayList<Session>(_sessions.values()) );
+
+        // loop while there are sessions, and while there is stop time remaining, or if no stop time, just 1 loop
+        while (sessions.size() > 0 && ((stopTime > 0 && (System.nanoTime() < stopTime)) || (stopTime == 0)))
+        {
+            for (Session session : sessions)
+            {
+                try
+                {
+                    session.save(false);
+                }
+                catch (Exception e)
+                {
+                    LOG.warn(e);
+                }
+                _sessions.remove(session.getClusterId());
+            }
+
+            //check if we should terminate our loop if we're not using the stop timer
+            if (stopTime == 0)
+                break;
+            
+            // Get any sessions that were added by other requests during processing and go around the loop again
+            sessions=new ArrayList<Session>(_sessions.values());
+        }
+    }
+
+    
+    /**
+     * 
+     * @see org.eclipse.jetty.server.SessionManager#renewSessionId(java.lang.String, java.lang.String, java.lang.String, java.lang.String)
+     */
+    public void renewSessionId (String oldClusterId, String oldNodeId, String newClusterId, String newNodeId)
     {
-        //Do nothing - we don't want to remove and
-        //invalidate all the sessions because this
-        //method is called from doStop(), and just
-        //because this context is stopping does not
-        //mean that we should remove the session from
-        //any other nodes
+        Session session = null;
+        try
+        {
+            session = (Session)_sessions.remove(oldClusterId);
+            if (session != null)
+            {
+                synchronized (session)
+                {
+                    session.setClusterId(newClusterId); //update ids
+                    session.setNodeId(newNodeId);
+                    _sessions.put(newClusterId, session); //put it into list in memory
+                    updateSession(session); //update database
+                }
+            }
+        }
+        catch (Exception e)
+        {
+            LOG.warn(e);
+        }
+
+        super.renewSessionId(oldClusterId, oldNodeId, newClusterId, newNodeId);
     }
 
+    
 
     /**
      * Invalidate a session.
@@ -619,11 +710,7 @@ public class JDBCSessionManager extends AbstractSessionManager
      */
     protected void invalidateSession (String idInCluster)
     {
-        Session session = null;
-        synchronized (this)
-        {
-            session = (Session)_sessions.get(idInCluster);
-        }
+        Session session = (Session)_sessions.get(idInCluster);
 
         if (session != null)
         {
@@ -640,20 +727,17 @@ public class JDBCSessionManager extends AbstractSessionManager
     @Override
     protected boolean removeSession(String idInCluster)
     {
-        synchronized (this)
+        Session session = (Session)_sessions.remove(idInCluster);
+        try
         {
-            Session session = (Session)_sessions.remove(idInCluster);
-            try
-            {
-                if (session != null)
-                    deleteSession(session);
-            }
-            catch (Exception e)
-            {
-                LOG.warn("Problem deleting session id="+idInCluster, e);
-            }
-            return session!=null;
+            if (session != null)
+                deleteSession(session);
         }
+        catch (Exception e)
+        {
+            LOG.warn("Problem deleting session id="+idInCluster, e);
+        }
+        return session!=null;
     }
 
 
@@ -668,13 +752,8 @@ public class JDBCSessionManager extends AbstractSessionManager
         if (session==null)
             return;
 
-        synchronized (this)
-        {
-            _sessions.put(session.getClusterId(), session);
-        }
+        _sessions.put(session.getClusterId(), (Session)session);
 
-        //TODO or delay the store until exit out of session? If we crash before we store it
-        //then session data will be lost.
         try
         {
             synchronized (session)
@@ -701,6 +780,20 @@ public class JDBCSessionManager extends AbstractSessionManager
     {
         return new Session(request);
     }
+    
+    
+    /**
+     * @param sessionId
+     * @param rowId
+     * @param created
+     * @param accessed
+     * @param maxInterval
+     * @return
+     */
+    protected AbstractSession newSession (String sessionId, String rowId, long created, long accessed, long maxInterval)
+    {
+        return new Session(sessionId, rowId, created, accessed, maxInterval);
+    }
 
     /* ------------------------------------------------------------ */
     /** Remove session from manager
@@ -709,40 +802,20 @@ public class JDBCSessionManager extends AbstractSessionManager
      * {@link SessionIdManager#invalidateAll(String)} should be called.
      */
     @Override
-    public void removeSession(AbstractSession session, boolean invalidate)
+    public boolean removeSession(AbstractSession session, boolean invalidate)
     {
         // Remove session from context and global maps
-        boolean removed = false;
-
-        synchronized (this)
-        {
-            //take this session out of the map of sessions for this context
-            if (getSession(session.getClusterId()) != null)
-            {
-                removed = true;
-                removeSession(session.getClusterId());
-            }
-        }
+        boolean removed = super.removeSession(session, invalidate);
 
         if (removed)
         {
-            // Remove session from all context and global id maps
-            _sessionIdManager.removeSession(session);
-
-            if (invalidate)
-                _sessionIdManager.invalidateAll(session.getClusterId());
-
-            if (invalidate && !_sessionListeners.isEmpty())
-            {
-                HttpSessionEvent event=new HttpSessionEvent(session);
-                for (HttpSessionListener l : _sessionListeners)
-                    l.sessionDestroyed(event);
-            }
             if (!invalidate)
             {
                 session.willPassivate();
             }
         }
+        
+        return removed;
     }
 
 
@@ -752,19 +825,20 @@ public class JDBCSessionManager extends AbstractSessionManager
      *
      * @param sessionIds
      */
-    protected void expire (List<?> sessionIds)
+    protected Set<String> expire (Set<String> sessionIds)
     {
         //don't attempt to scavenge if we are shutting down
         if (isStopping() || isStopped())
-            return;
+            return null;
 
-        //Remove any sessions we already have in memory that match the ids
+        
         Thread thread=Thread.currentThread();
         ClassLoader old_loader=thread.getContextClassLoader();
-        ListIterator<?> itor = sessionIds.listIterator();
-
+        
+        Set<String> successfullyExpiredIds = new HashSet<String>();
         try
         {
+            Iterator<?> itor = sessionIds.iterator();
             while (itor.hasNext())
             {
                 String sessionId = (String)itor.next();
@@ -772,29 +846,46 @@ public class JDBCSessionManager extends AbstractSessionManager
                     LOG.debug("Expiring session id "+sessionId);
 
                 Session session = (Session)_sessions.get(sessionId);
-                if (session != null)
+
+                //if session is not in our memory, then fetch from db so we can call the usual listeners on it
+                if (session == null)
                 {
-                    session.timeout();
-                    itor.remove();
+                    if (LOG.isDebugEnabled())LOG.debug("Force loading session id "+sessionId);
+                    session = loadSession(sessionId, canonicalize(_context.getContextPath()), getVirtualHost(_context));
+                    if (session != null)
+                    {
+                        //loaded an expired session last managed on this node for this context, add it to the list so we can 
+                        //treat it like a normal expired session
+                        _sessions.put(session.getClusterId(), session);
+                    }
+                    else
+                    {
+                        if (LOG.isDebugEnabled())
+                            LOG.debug("Unrecognized session id="+sessionId);
+                        continue;
+                    }
                 }
-                else
+
+                if (session != null)
                 {
-                    if (LOG.isDebugEnabled())
-                        LOG.debug("Unrecognized session id="+sessionId);
+                    session.timeout();
+                    successfullyExpiredIds.add(session.getClusterId());
                 }
             }
+            return successfullyExpiredIds;
         }
         catch (Throwable t)
         {
             LOG.warn("Problem expiring sessions", t);
+            return successfullyExpiredIds;
         }
         finally
         {
             thread.setContextClassLoader(old_loader);
         }
     }
-
-
+    
+  
     /**
      * Load a session from the database
      * @param id
@@ -808,57 +899,55 @@ public class JDBCSessionManager extends AbstractSessionManager
         final AtomicReference<Exception> _exception = new AtomicReference<Exception>();
         Runnable load = new Runnable()
         {
+            /** 
+             * @see java.lang.Runnable#run()
+             */
             @SuppressWarnings("unchecked")
             public void run()
             {
-                Session session = null;
-                Connection connection=null;
-                PreparedStatement statement = null;
-                try
+                try (Connection connection = getConnection();
+                        PreparedStatement statement = _sessionTableSchema.getLoadStatement(connection, id, canonicalContextPath, vhost);
+                        ResultSet result = statement.executeQuery())
                 {
-                    connection = getConnection();
-                    statement = _jdbcSessionIdMgr._dbAdaptor.getLoadStatement(connection, id, canonicalContextPath, vhost);
-                    ResultSet result = statement.executeQuery();
+                    Session session = null;
                     if (result.next())
                     {                    
-                        session = new Session(id, result.getString(_jdbcSessionIdMgr._sessionTableRowId), result.getLong("createTime"), result.getLong("accessTime"));
-                        session.setCookieSet(result.getLong("cookieTime"));
-                        session.setLastAccessedTime(result.getLong("lastAccessTime"));
-                        session.setLastNode(result.getString("lastNode"));
-                        session.setLastSaved(result.getLong("lastSavedTime"));
-                        session.setExpiryTime(result.getLong("expiryTime"));
-                        session.setCanonicalContext(result.getString("contextPath"));
-                        session.setVirtualHost(result.getString("virtualHost"));
+                        long maxInterval = result.getLong(_sessionTableSchema.getMaxIntervalColumn());
+                        if (maxInterval == JDBCSessionIdManager.MAX_INTERVAL_NOT_SET)
+                        {
+                            maxInterval = getMaxInactiveInterval(); //if value not saved for maxInactiveInterval, use current value from sessionmanager
+                        }
+                        session = (Session)newSession(id, result.getString(_sessionTableSchema.getRowIdColumn()), 
+                                                  result.getLong(_sessionTableSchema.getCreateTimeColumn()), 
+                                                  result.getLong(_sessionTableSchema.getAccessTimeColumn()), 
+                                                  maxInterval);
+                        session.setCookieSet(result.getLong(_sessionTableSchema.getCookieTimeColumn()));
+                        session.setLastAccessedTime(result.getLong(_sessionTableSchema.getLastAccessTimeColumn()));
+                        session.setLastNode(result.getString(_sessionTableSchema.getLastNodeColumn()));
+                        session.setLastSaved(result.getLong(_sessionTableSchema.getLastSavedTimeColumn()));
+                        session.setExpiryTime(result.getLong(_sessionTableSchema.getExpiryTimeColumn()));
+                        session.setCanonicalContext(result.getString(_sessionTableSchema.getContextPathColumn()));
+                        session.setVirtualHost(result.getString(_sessionTableSchema.getVirtualHostColumn()));
                                            
-                        InputStream is = ((JDBCSessionIdManager)getSessionIdManager())._dbAdaptor.getBlobInputStream(result, "map");
-                        ClassLoadingObjectInputStream ois = new ClassLoadingObjectInputStream (is);
-                        Object o = ois.readObject();
-                        session.addAttributes((Map<String,Object>)o);
-                        ois.close();
+                        try (InputStream is = ((JDBCSessionIdManager)getSessionIdManager())._dbAdaptor.getBlobInputStream(result, _sessionTableSchema.getMapColumn());
+                                ClassLoadingObjectInputStream ois = new ClassLoadingObjectInputStream(is))
+                        {
+                            Object o = ois.readObject();
+                            session.addAttributes((Map<String,Object>)o);
+                        }
 
                         if (LOG.isDebugEnabled())
                             LOG.debug("LOADED session "+session);
                     }
+                    else
+                        if (LOG.isDebugEnabled())
+                            LOG.debug("Failed to load session "+id);
                     _reference.set(session);
                 }
                 catch (Exception e)
                 {
                     _exception.set(e);
                 }
-                finally
-                {
-                    if (statement!=null)
-                    {
-                        try { statement.close(); }
-                        catch(Exception e) { LOG.warn(e); }
-                    }
-
-                    if (connection!=null)
-                    {
-                        try { connection.close();}
-                        catch(Exception e) { LOG.warn(e); }
-                    }
-                }
             }
         };
 
@@ -881,7 +970,7 @@ public class JDBCSessionManager extends AbstractSessionManager
     /**
      * Insert a session into the database.
      *
-     * @param data
+     * @param session
      * @throws Exception
      */
     protected void storeSession (Session session)
@@ -891,17 +980,15 @@ public class JDBCSessionManager extends AbstractSessionManager
             return;
 
         //put into the database
-        Connection connection = getConnection();
-        PreparedStatement statement = null;
-        try
+        try (Connection connection = getConnection();
+                PreparedStatement statement = connection.prepareStatement(_jdbcSessionIdMgr._insertSession))
         {
             String rowId = calculateRowId(session);
 
             long now = System.currentTimeMillis();
             connection.setAutoCommit(true);
-            statement = connection.prepareStatement(_jdbcSessionIdMgr._insertSession);
             statement.setString(1, rowId); //rowId
-            statement.setString(2, session.getId()); //session id
+            statement.setString(2, session.getClusterId()); //session id
             statement.setString(3, session.getCanonicalContext()); //context path
             statement.setString(4, session.getVirtualHost()); //first vhost
             statement.setString(5, getSessionIdManager().getWorkerName());//my node id
@@ -911,34 +998,24 @@ public class JDBCSessionManager extends AbstractSessionManager
             statement.setLong(9, session.getCookieSet());//time cookie was set
             statement.setLong(10, now); //last saved time
             statement.setLong(11, session.getExpiryTime());
+            statement.setLong(12, session.getMaxInactiveInterval());
 
             ByteArrayOutputStream baos = new ByteArrayOutputStream();
             ObjectOutputStream oos = new ObjectOutputStream(baos);
             oos.writeObject(session.getAttributeMap());
+            oos.flush();
             byte[] bytes = baos.toByteArray();
 
             ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
-            statement.setBinaryStream(12, bais, bytes.length);//attribute map as blob
+            statement.setBinaryStream(13, bais, bytes.length);//attribute map as blob
+           
 
             statement.executeUpdate();
             session.setRowId(rowId); //set it on the in-memory data as well as in db
             session.setLastSaved(now);
-
-
-            if (LOG.isDebugEnabled())
-                LOG.debug("Stored session "+session);
-        }
-        finally
-        {
-            if (statement!=null)
-            {
-                try { statement.close(); }
-                catch(Exception e) { LOG.warn(e); }
-            }
-
-            if (connection!=null)
-                connection.close();
         }
+        if (LOG.isDebugEnabled())
+            LOG.debug("Stored session "+session);
     }
 
 
@@ -954,44 +1031,34 @@ public class JDBCSessionManager extends AbstractSessionManager
         if (data==null)
             return;
 
-        Connection connection = getConnection();
-        PreparedStatement statement = null;
-        try
+        try (Connection connection = getConnection();
+                PreparedStatement statement = connection.prepareStatement(_jdbcSessionIdMgr._updateSession))
         {
             long now = System.currentTimeMillis();
             connection.setAutoCommit(true);
-            statement = connection.prepareStatement(_jdbcSessionIdMgr._updateSession);
-            statement.setString(1, getSessionIdManager().getWorkerName());//my node id
-            statement.setLong(2, data.getAccessed());//accessTime
-            statement.setLong(3, data.getLastAccessedTime()); //lastAccessTime
-            statement.setLong(4, now); //last saved time
-            statement.setLong(5, data.getExpiryTime());
+            statement.setString(1, data.getClusterId());
+            statement.setString(2, getSessionIdManager().getWorkerName());//my node id
+            statement.setLong(3, data.getAccessed());//accessTime
+            statement.setLong(4, data.getLastAccessedTime()); //lastAccessTime
+            statement.setLong(5, now); //last saved time
+            statement.setLong(6, data.getExpiryTime());
+            statement.setLong(7, data.getMaxInactiveInterval());
 
             ByteArrayOutputStream baos = new ByteArrayOutputStream();
             ObjectOutputStream oos = new ObjectOutputStream(baos);
             oos.writeObject(data.getAttributeMap());
+            oos.flush();
             byte[] bytes = baos.toByteArray();
             ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
 
-            statement.setBinaryStream(6, bais, bytes.length);//attribute map as blob
-            statement.setString(7, data.getRowId()); //rowId
+            statement.setBinaryStream(8, bais, bytes.length);//attribute map as blob
+            statement.setString(9, data.getRowId()); //rowId
             statement.executeUpdate();
 
             data.setLastSaved(now);
-            if (LOG.isDebugEnabled())
-                LOG.debug("Updated session "+data);
-        }
-        finally
-        {
-            if (statement!=null)
-            {
-                try { statement.close(); }
-                catch(Exception e) { LOG.warn(e); }
-            }
-
-            if (connection!=null)
-                connection.close();
         }
+        if (LOG.isDebugEnabled())
+            LOG.debug("Updated session "+data);
     }
 
 
@@ -1005,30 +1072,16 @@ public class JDBCSessionManager extends AbstractSessionManager
     throws Exception
     {
         String nodeId = getSessionIdManager().getWorkerName();
-        Connection connection = getConnection();
-        PreparedStatement statement = null;
-        try
+        try (Connection connection = getConnection();
+                PreparedStatement statement = connection.prepareStatement(_jdbcSessionIdMgr._updateSessionNode))
         {
             connection.setAutoCommit(true);
-            statement = connection.prepareStatement(_jdbcSessionIdMgr._updateSessionNode);
             statement.setString(1, nodeId);
             statement.setString(2, data.getRowId());
             statement.executeUpdate();
-            statement.close();
-            if (LOG.isDebugEnabled())
-                LOG.debug("Updated last node for session id="+data.getId()+", lastNode = "+nodeId);
-        }
-        finally
-        {
-            if (statement!=null)
-            {
-                try { statement.close(); }
-                catch(Exception e) { LOG.warn(e); }
-            }
-
-            if (connection!=null)
-                connection.close();
         }
+        if (LOG.isDebugEnabled())
+            LOG.debug("Updated last node for session id="+data.getId()+", lastNode = "+nodeId);
     }
 
     /**
@@ -1040,36 +1093,24 @@ public class JDBCSessionManager extends AbstractSessionManager
     private void updateSessionAccessTime (Session data)
     throws Exception
     {
-        Connection connection = getConnection();
-        PreparedStatement statement = null;
-        try
+        try (Connection connection = getConnection();
+                PreparedStatement statement = connection.prepareStatement(_jdbcSessionIdMgr._updateSessionAccessTime))
         {
             long now = System.currentTimeMillis();
             connection.setAutoCommit(true);
-            statement = connection.prepareStatement(_jdbcSessionIdMgr._updateSessionAccessTime);
             statement.setString(1, getSessionIdManager().getWorkerName());
             statement.setLong(2, data.getAccessed());
             statement.setLong(3, data.getLastAccessedTime());
             statement.setLong(4, now);
             statement.setLong(5, data.getExpiryTime());
-            statement.setString(6, data.getRowId());
+            statement.setLong(6, data.getMaxInactiveInterval());
+            statement.setString(7, data.getRowId());
+          
             statement.executeUpdate();
             data.setLastSaved(now);
-            statement.close();
-            if (LOG.isDebugEnabled())
-                LOG.debug("Updated access time session id="+data.getId());
-        }
-        finally
-        {
-            if (statement!=null)
-            {
-                try { statement.close(); }
-                catch(Exception e) { LOG.warn(e); }
-            }
-
-            if (connection!=null)
-                connection.close();
         }
+        if (LOG.isDebugEnabled())
+            LOG.debug("Updated access time session id="+data.getId()+" with lastsaved="+data.getLastSaved());
     }
 
 
@@ -1085,28 +1126,15 @@ public class JDBCSessionManager extends AbstractSessionManager
     protected void deleteSession (Session data)
     throws Exception
     {
-        Connection connection = getConnection();
-        PreparedStatement statement = null;
-        try
+        try (Connection connection = getConnection();
+                PreparedStatement statement = connection.prepareStatement(_jdbcSessionIdMgr._deleteSession))
         {
             connection.setAutoCommit(true);
-            statement = connection.prepareStatement(_jdbcSessionIdMgr._deleteSession);
             statement.setString(1, data.getRowId());
             statement.executeUpdate();
             if (LOG.isDebugEnabled())
                 LOG.debug("Deleted Session "+data);
         }
-        finally
-        {
-            if (statement!=null)
-            {
-                try { statement.close(); }
-                catch(Exception e) { LOG.warn(e); }
-            }
-
-            if (connection!=null)
-                connection.close();
-        }
     }
 
 
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/MemSession.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/MemSession.java
new file mode 100644
index 0000000..b2322d6
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/MemSession.java
@@ -0,0 +1,146 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.session;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.servlet.http.HttpServletRequest;
+
+
+/**
+ * MemSession
+ *
+ * A session whose data is kept in memory
+ */
+public class MemSession extends AbstractSession
+{
+
+    private final Map<String,Object> _attributes=new HashMap<String, Object>();
+
+    protected MemSession(AbstractSessionManager abstractSessionManager, HttpServletRequest request)
+    {
+        super(abstractSessionManager, request);
+    }
+
+    public MemSession(AbstractSessionManager abstractSessionManager, long created, long accessed, String clusterId)
+    {
+        super(abstractSessionManager, created, accessed, clusterId);
+    }
+    
+    
+    /* ------------------------------------------------------------- */
+    @Override
+    public Map<String,Object> getAttributeMap()
+    {
+        return _attributes;
+    }
+   
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public int getAttributes()
+    {
+        synchronized (this)
+        {
+            checkValid();
+            return _attributes.size();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @SuppressWarnings({ "unchecked" })
+    @Override
+    public Enumeration<String> doGetAttributeNames()
+    {
+        List<String> names=_attributes==null?Collections.EMPTY_LIST:new ArrayList<String>(_attributes.keySet());
+        return Collections.enumeration(names);
+    }
+
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public Set<String> getNames()
+    {
+        synchronized (this)
+        {
+            return new HashSet<String>(_attributes.keySet());
+        }
+    }
+   
+    
+    /* ------------------------------------------------------------- */
+    @Override
+    public void clearAttributes()
+    {
+        while (_attributes!=null && _attributes.size()>0)
+        {
+            ArrayList<String> keys;
+            synchronized(this)
+            {
+                keys=new ArrayList<String>(_attributes.keySet());
+            }
+
+            Iterator<String> iter=keys.iterator();
+            while (iter.hasNext())
+            {
+                String key=(String)iter.next();
+
+                Object value;
+                synchronized(this)
+                {
+                    value=doPutOrRemove(key,null);
+                }
+                unbindValue(key,value);
+
+                ((AbstractSessionManager)getSessionManager()).doSessionAttributeListeners(this,key,value,null);
+            }
+        }
+        if (_attributes!=null)
+            _attributes.clear();
+    }
+
+    /* ------------------------------------------------------------ */
+    public void addAttributes(Map<String,Object> map)
+    {
+        _attributes.putAll(map);
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public Object doPutOrRemove(String name, Object value)
+    {
+        return value==null?_attributes.remove(name):_attributes.put(name,value);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public Object doGet(String name)
+    {
+        return _attributes.get(name);
+    }
+    
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionHandler.java
index 95839e8..dbbe4d6 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionHandler.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionHandler.java
@@ -21,6 +21,7 @@ package org.eclipse.jetty.server.session;
 import java.io.IOException;
 import java.util.EnumSet;
 import java.util.EventListener;
+
 import javax.servlet.DispatcherType;
 import javax.servlet.ServletException;
 import javax.servlet.SessionTrackingMode;
@@ -28,10 +29,12 @@ import javax.servlet.http.Cookie;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpSession;
+import javax.servlet.http.HttpSessionAttributeListener;
+import javax.servlet.http.HttpSessionIdListener;
+import javax.servlet.http.HttpSessionListener;
 
 import org.eclipse.jetty.http.HttpCookie;
 import org.eclipse.jetty.server.Request;
-import org.eclipse.jetty.server.Server;
 import org.eclipse.jetty.server.SessionManager;
 import org.eclipse.jetty.server.handler.ScopedHandler;
 import org.eclipse.jetty.util.log.Log;
@@ -47,6 +50,14 @@ public class SessionHandler extends ScopedHandler
 
     public final static EnumSet<SessionTrackingMode> DEFAULT_TRACKING = EnumSet.of(SessionTrackingMode.COOKIE,SessionTrackingMode.URL);
 
+    @SuppressWarnings("unchecked")
+    public static final Class<? extends EventListener>[] SESSION_LISTENER_TYPES = 
+        new Class[] {HttpSessionAttributeListener.class,
+                     HttpSessionIdListener.class,
+                     HttpSessionListener.class};
+
+
+
     /* -------------------------------------------------------------- */
     private SessionManager _sessionManager;
 
@@ -87,30 +98,10 @@ public class SessionHandler extends ScopedHandler
     {
         if (isStarted())
             throw new IllegalStateException();
-        SessionManager old_session_manager = _sessionManager;
-
-        if (getServer() != null)
-            getServer().getContainer().update(this,old_session_manager,sessionManager,"sessionManager",true);
-
         if (sessionManager != null)
             sessionManager.setSessionHandler(this);
-
-        _sessionManager = sessionManager;
-
-        if (old_session_manager != null)
-            old_session_manager.setSessionHandler(null);
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public void setServer(Server server)
-    {
-        Server old_server = getServer();
-        if (old_server != null && old_server != server)
-            old_server.getContainer().update(this,_sessionManager,null,"sessionManager",true);
-        super.setServer(server);
-        if (server != null && server != old_server)
-            server.getContainer().update(this,null,_sessionManager,"sessionManager",true);
+        updateBean(_sessionManager,sessionManager);
+        _sessionManager=sessionManager;
     }
 
     /* ------------------------------------------------------------ */
@@ -120,7 +111,8 @@ public class SessionHandler extends ScopedHandler
     @Override
     protected void doStart() throws Exception
     {
-        _sessionManager.start();
+        if (_sessionManager==null)
+            setSessionManager(new HashSessionManager());
         super.doStart();
     }
 
@@ -132,10 +124,10 @@ public class SessionHandler extends ScopedHandler
     protected void doStop() throws Exception
     {
         // Destroy sessions before destroying servlets/filters see JETTY-1266
-        _sessionManager.stop();
         super.doStop();
     }
 
+
     /* ------------------------------------------------------------ */
     /*
      * @see org.eclipse.jetty.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
@@ -272,7 +264,8 @@ public class SessionHandler extends ScopedHandler
                         requested_session_id = cookies[i].getValue();
                         requested_session_id_from_cookie = true;
 
-                        LOG.debug("Got Session ID {} from cookie",requested_session_id);
+                        if (LOG.isDebugEnabled())
+                            LOG.debug("Got Session ID {} from cookie",requested_session_id);
 
                         if (requested_session_id != null)
                         {
@@ -322,7 +315,7 @@ public class SessionHandler extends ScopedHandler
         }
 
         baseRequest.setRequestedSessionId(requested_session_id);
-        baseRequest.setRequestedSessionIdFromCookie(requested_session_id != null && requested_session_id_from_cookie);
+        baseRequest.setRequestedSessionIdFromCookie(requested_session_id!=null && requested_session_id_from_cookie);
         if (session != null && sessionManager.isValid(session))
             baseRequest.setSession(session);
     }
@@ -336,6 +329,16 @@ public class SessionHandler extends ScopedHandler
         if (_sessionManager != null)
             _sessionManager.addEventListener(listener);
     }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param listener
+     */
+    public void removeEventListener(EventListener listener)
+    {
+        if (_sessionManager != null)
+            _sessionManager.removeEventListener(listener);
+    }
 
     /* ------------------------------------------------------------ */
     public void clearEventListeners()
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/jmx/package-info.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/jmx/package-info.java
new file mode 100644
index 0000000..082ae26
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/jmx/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Server : Session Management JMX Integration
+ */
+package org.eclipse.jetty.server.session.jmx;
+
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/package-info.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/package-info.java
new file mode 100644
index 0000000..d1e2613
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Server : Session Management Implementations
+ */
+package org.eclipse.jetty.server.session;
+
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ssl/ServletSSL.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ssl/ServletSSL.java
deleted file mode 100644
index 0fe9aec..0000000
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/ssl/ServletSSL.java
+++ /dev/null
@@ -1,88 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.server.ssl;
-
-/* --------------------------------------------------------------------- */
-/**
- * Jetty Servlet SSL support utilities.
- * <p>
- * A collection of utilities required to support the SSL requirements of the Servlet 2.2 and 2.3
- * specs.
- * 
- * <p>
- * Used by the SSL listener classes.
- * 
- * 
- */
-public class ServletSSL
-{
-    /* ------------------------------------------------------------ */
-    /**
-     * Given the name of a TLS/SSL cipher suite, return an int representing it effective stream
-     * cipher key strength. i.e. How much entropy material is in the key material being fed into the
-     * encryption routines.
-     * 
-     * <p>
-     * This is based on the information on effective key lengths in RFC 2246 - The TLS Protocol
-     * Version 1.0, Appendix C. CipherSuite definitions:
-     * 
-     * <pre>
-     *                         Effective 
-     *     Cipher       Type    Key Bits 
-     * 		       	       
-     *     NULL       * Stream     0     
-     *     IDEA_CBC     Block    128     
-     *     RC2_CBC_40 * Block     40     
-     *     RC4_40     * Stream    40     
-     *     RC4_128      Stream   128     
-     *     DES40_CBC  * Block     40     
-     *     DES_CBC      Block     56     
-     *     3DES_EDE_CBC Block    168     
-     * </pre>
-     * 
-     * @param cipherSuite String name of the TLS cipher suite.
-     * @return int indicating the effective key entropy bit-length.
-     */
-    public static int deduceKeyLength(String cipherSuite)
-    {
-        // Roughly ordered from most common to least common.
-        if (cipherSuite == null)
-            return 0;
-        else if (cipherSuite.indexOf("WITH_AES_256_") >= 0)
-            return 256;
-        else if (cipherSuite.indexOf("WITH_RC4_128_") >= 0)
-            return 128;
-        else if (cipherSuite.indexOf("WITH_AES_128_") >= 0)
-            return 128;
-        else if (cipherSuite.indexOf("WITH_RC4_40_") >= 0)
-            return 40;
-        else if (cipherSuite.indexOf("WITH_3DES_EDE_CBC_") >= 0)
-            return 168;
-        else if (cipherSuite.indexOf("WITH_IDEA_CBC_") >= 0)
-            return 128;
-        else if (cipherSuite.indexOf("WITH_RC2_CBC_40_") >= 0)
-            return 40;
-        else if (cipherSuite.indexOf("WITH_DES40_CBC_") >= 0)
-            return 40;
-        else if (cipherSuite.indexOf("WITH_DES_CBC_") >= 0)
-            return 56;
-        else
-            return 0;
-    }
-}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ssl/SslCertificates.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ssl/SslCertificates.java
deleted file mode 100644
index d569898..0000000
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/ssl/SslCertificates.java
+++ /dev/null
@@ -1,182 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.server.ssl;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.security.cert.X509Certificate;
-
-import javax.net.ssl.SSLPeerUnverifiedException;
-import javax.net.ssl.SSLSession;
-import javax.net.ssl.SSLSocket;
-
-import org.eclipse.jetty.http.HttpSchemes;
-import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.io.bio.SocketEndPoint;
-import org.eclipse.jetty.server.Request;
-import org.eclipse.jetty.util.TypeUtil;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-
-public class SslCertificates
-{
-    private static final Logger LOG = Log.getLogger(SslCertificates.class);
-
-    /**
-     * The name of the SSLSession attribute that will contain any cached information.
-     */
-    static final String CACHED_INFO_ATTR = CachedInfo.class.getName();
-
-    public static X509Certificate[] getCertChain(SSLSession sslSession)
-    {
-        try
-        {
-            javax.security.cert.X509Certificate javaxCerts[]=sslSession.getPeerCertificateChain();
-            if (javaxCerts==null||javaxCerts.length==0)
-                return null;
-
-            int length=javaxCerts.length;
-            X509Certificate[] javaCerts=new X509Certificate[length];
-
-            java.security.cert.CertificateFactory cf=java.security.cert.CertificateFactory.getInstance("X.509");
-            for (int i=0; i<length; i++)
-            {
-                byte bytes[]=javaxCerts[i].getEncoded();
-                ByteArrayInputStream stream=new ByteArrayInputStream(bytes);
-                javaCerts[i]=(X509Certificate)cf.generateCertificate(stream);
-            }
-
-            return javaCerts;
-        }
-        catch (SSLPeerUnverifiedException pue)
-        {
-            return null;
-        }
-        catch (Exception e)
-        {
-            LOG.warn(Log.EXCEPTION,e);
-            return null;
-        }
-    }
-    
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Allow the Listener a chance to customise the request. before the server
-     * does its stuff. <br>
-     * This allows the required attributes to be set for SSL requests. <br>
-     * The requirements of the Servlet specs are:
-     * <ul>
-     * <li> an attribute named "javax.servlet.request.ssl_session_id" of type
-     * String (since Servlet Spec 3.0).</li>
-     * <li> an attribute named "javax.servlet.request.cipher_suite" of type
-     * String.</li>
-     * <li> an attribute named "javax.servlet.request.key_size" of type Integer.</li>
-     * <li> an attribute named "javax.servlet.request.X509Certificate" of type
-     * java.security.cert.X509Certificate[]. This is an array of objects of type
-     * X509Certificate, the order of this array is defined as being in ascending
-     * order of trust. The first certificate in the chain is the one set by the
-     * client, the next is the one used to authenticate the first, and so on.
-     * </li>
-     * </ul>
-     * 
-     * @param endpoint
-     *                The Socket the request arrived on. This should be a
-     *                {@link SocketEndPoint} wrapping a {@link SSLSocket}.
-     * @param request
-     *                HttpRequest to be customised.
-     */
-    public static void customize(SSLSession sslSession, EndPoint endpoint, Request request) throws IOException
-    {
-        request.setScheme(HttpSchemes.HTTPS);
-
-        try
-        {
-            String cipherSuite=sslSession.getCipherSuite();
-            Integer keySize;
-            X509Certificate[] certs;
-            String idStr;
-
-            CachedInfo cachedInfo=(CachedInfo)sslSession.getValue(CACHED_INFO_ATTR);
-            if (cachedInfo!=null)
-            {
-                keySize=cachedInfo.getKeySize();
-                certs=cachedInfo.getCerts();
-                idStr=cachedInfo.getIdStr();
-            }
-            else
-            {
-                keySize=new Integer(ServletSSL.deduceKeyLength(cipherSuite));
-                certs=SslCertificates.getCertChain(sslSession);
-                byte[] bytes = sslSession.getId();
-                idStr = TypeUtil.toHexString(bytes);
-                cachedInfo=new CachedInfo(keySize,certs,idStr);
-                sslSession.putValue(CACHED_INFO_ATTR,cachedInfo);
-            }
-
-            if (certs!=null)
-                request.setAttribute("javax.servlet.request.X509Certificate",certs);
-
-            request.setAttribute("javax.servlet.request.cipher_suite",cipherSuite);
-            request.setAttribute("javax.servlet.request.key_size",keySize);
-            request.setAttribute("javax.servlet.request.ssl_session_id", idStr);
-        }
-        catch (Exception e)
-        {
-            LOG.warn(Log.EXCEPTION,e);
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    /* ------------------------------------------------------------ */
-    /* ------------------------------------------------------------ */
-    /**
-     * Simple bundle of information that is cached in the SSLSession. Stores the
-     * effective keySize and the client certificate chain.
-     */
-    private static class CachedInfo
-    {
-        private final X509Certificate[] _certs;
-        private final Integer _keySize;
-        private final String _idStr;
-
-        CachedInfo(Integer keySize, X509Certificate[] certs,String idStr)
-        {
-            this._keySize=keySize;
-            this._certs=certs;
-            this._idStr=idStr;
-        }
-
-        X509Certificate[] getCerts()
-        {
-            return _certs;
-        }
-
-        Integer getKeySize()
-        {
-            return _keySize;
-        }
-        
-        String getIdStr()
-        {
-            return _idStr;
-        }
-    }
-
-}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ssl/SslConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ssl/SslConnector.java
deleted file mode 100644
index fcce6b5..0000000
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/ssl/SslConnector.java
+++ /dev/null
@@ -1,348 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.server.ssl;
-
-import java.io.File;
-import java.security.SecureRandom;
-import java.security.Security;
-
-import javax.net.ssl.KeyManagerFactory;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLEngine;
-import javax.net.ssl.TrustManagerFactory;
-
-import org.eclipse.jetty.server.Connector;
-import org.eclipse.jetty.util.ssl.SslContextFactory;
-
-
-/* ------------------------------------------------------------ */
-/** The interface for SSL connectors and their configuration methods.
- * 
- */
-public interface SslConnector extends Connector
-{
-    @Deprecated
-    public static final String DEFAULT_KEYSTORE_ALGORITHM=(Security.getProperty("ssl.KeyManagerFactory.algorithm")==null?"SunX509":Security.getProperty("ssl.KeyManagerFactory.algorithm"));
-    @Deprecated
-    public static final String DEFAULT_TRUSTSTORE_ALGORITHM=(Security.getProperty("ssl.TrustManagerFactory.algorithm")==null?"SunX509":Security.getProperty("ssl.TrustManagerFactory.algorithm"));
-
-    /** Default value for the keystore location path. @deprecated */
-    @Deprecated
-    public static final String DEFAULT_KEYSTORE = System.getProperty("user.home") + File.separator + ".keystore";
-    
-    /** String name of key password property. @deprecated */
-    @Deprecated
-    public static final String KEYPASSWORD_PROPERTY = "org.eclipse.jetty.ssl.keypassword";
-    
-    /** String name of keystore password property. @deprecated */
-    @Deprecated
-    public static final String PASSWORD_PROPERTY = "org.eclipse.jetty.ssl.password";
-    
-    
-    /* ------------------------------------------------------------ */
-    /**
-     * @return the instance of SslContextFactory associated with the connector
-     */
-    public SslContextFactory getSslContextFactory();
-        
-    /* ------------------------------------------------------------ */
-    /**
-     * @return The array of Ciphersuite names to exclude from 
-     * {@link SSLEngine#setEnabledCipherSuites(String[])}
-     * @deprecated
-     */
-    @Deprecated
-    public abstract String[] getExcludeCipherSuites();
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param cipherSuites The array of Ciphersuite names to exclude from 
-     * {@link SSLEngine#setEnabledCipherSuites(String[])}
-     * @deprecated
-     */
-    @Deprecated
-    public abstract void setExcludeCipherSuites(String[] cipherSuites);
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return The array of Ciphersuite names to include in
-     * {@link SSLEngine#setEnabledCipherSuites(String[])}
-     * @deprecated
-     */
-    @Deprecated
-    public abstract String[] getIncludeCipherSuites();
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param cipherSuites The array of Ciphersuite names to include in 
-     * {@link SSLEngine#setEnabledCipherSuites(String[])}
-     * @deprecated
-     */
-    @Deprecated
-    public abstract void setIncludeCipherSuites(String[] cipherSuites);
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param password The password for the key store
-     * @deprecated
-     */
-    @Deprecated
-    public abstract void setPassword(String password);
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param password The password for the trust store
-     * @deprecated
-     */
-    @Deprecated
-    public abstract void setTrustPassword(String password);
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param password The password (if any) for the specific key within 
-     * the key store
-     * @deprecated
-     */
-    @Deprecated
-    public abstract void setKeyPassword(String password);
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return The SSL protocol (default "TLS") passed to {@link SSLContext#getInstance(String, String)}
-     * @deprecated
-     */
-    @Deprecated
-    public abstract String getProtocol();
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param protocol The SSL protocol (default "TLS") passed to {@link SSLContext#getInstance(String, String)}
-     * @deprecated
-     */
-    @Deprecated
-    public abstract void setProtocol(String protocol);
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param keystore The file or URL of the SSL Key store.
-     * @deprecated
-     */
-    @Deprecated
-    public abstract void setKeystore(String keystore);
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return The file or URL of the SSL Key store.
-     * @deprecated
-     */
-    @Deprecated
-    public abstract String getKeystore();
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return The type of the key store (default "JKS")
-     * @deprecated
-     */
-    @Deprecated
-    public abstract String getKeystoreType();
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return True if SSL needs client authentication.
-     * @see SSLEngine#getNeedClientAuth()
-     * @deprecated
-     */
-    @Deprecated
-    public abstract boolean getNeedClientAuth();
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return True if SSL wants client authentication.
-     * @see SSLEngine#getWantClientAuth()
-     * @deprecated
-     */
-    @Deprecated
-    public abstract boolean getWantClientAuth();
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param needClientAuth True if SSL needs client authentication.
-     * @see SSLEngine#getNeedClientAuth()
-     * @deprecated
-     */
-    @Deprecated
-    public abstract void setNeedClientAuth(boolean needClientAuth);
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param wantClientAuth True if SSL wants client authentication.
-     * @see SSLEngine#getWantClientAuth()
-     * @deprecated
-     */
-    @Deprecated
-    public abstract void setWantClientAuth(boolean wantClientAuth);
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param keystoreType The type of the key store (default "JKS")
-     * @deprecated
-     */
-    @Deprecated
-    public abstract void setKeystoreType(String keystoreType);
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return The SSL provider name, which if set is passed to 
-     * {@link SSLContext#getInstance(String, String)}
-     * @deprecated
-     */
-    @Deprecated
-    public abstract String getProvider();
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return The algorithm name, which if set is passed to 
-     * {@link SecureRandom#getInstance(String)} to obtain the {@link SecureRandom}
-     * instance passed to {@link SSLContext#init(javax.net.ssl.KeyManager[], javax.net.ssl.TrustManager[], SecureRandom)}
-     * @deprecated
-     */
-    @Deprecated
-    public abstract String getSecureRandomAlgorithm();
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return The algorithm name (default "SunX509") used by the {@link KeyManagerFactory}
-     * @deprecated
-     */
-    @Deprecated
-    public abstract String getSslKeyManagerFactoryAlgorithm();
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return The algorithm name (default "SunX509") used by the {@link TrustManagerFactory}
-     * @deprecated
-     */
-    @Deprecated
-    public abstract String getSslTrustManagerFactoryAlgorithm();
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return The file name or URL of the trust store location
-     * @deprecated
-     */
-    @Deprecated
-    public abstract String getTruststore();
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return The type of the trust store (default "JKS")
-     * @deprecated
-     */
-    @Deprecated
-    public abstract String getTruststoreType();
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param provider The SSL provider name, which if set is passed to 
-     * {@link SSLContext#getInstance(String, String)}
-     * @deprecated
-     */
-    @Deprecated
-    public abstract void setProvider(String provider);
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param algorithm The algorithm name, which if set is passed to 
-     * {@link SecureRandom#getInstance(String)} to obtain the {@link SecureRandom}
-     * instance passed to {@link SSLContext#init(javax.net.ssl.KeyManager[], javax.net.ssl.TrustManager[], SecureRandom)}
-     * @deprecated
-     */
-    @Deprecated
-    public abstract void setSecureRandomAlgorithm(String algorithm);
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param algorithm The algorithm name (default "SunX509") used by 
-     * the {@link KeyManagerFactory}
-     * @deprecated
-     */
-    @Deprecated
-    public abstract void setSslKeyManagerFactoryAlgorithm(String algorithm);
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param algorithm The algorithm name (default "SunX509") used by the {@link TrustManagerFactory}
-     * @deprecated
-     */
-    @Deprecated
-    public abstract void setSslTrustManagerFactoryAlgorithm(String algorithm);
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param truststore The file name or URL of the trust store location
-     * @deprecated
-     */
-    @Deprecated
-    public abstract void setTruststore(String truststore);
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param truststoreType The type of the trust store (default "JKS")
-     * @deprecated
-     */
-    @Deprecated
-    public abstract void setTruststoreType(String truststoreType);
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param sslContext Set a preconfigured SSLContext
-     * @deprecated
-     */
-    @Deprecated
-    public abstract void setSslContext(SSLContext sslContext);
-    
-    /* ------------------------------------------------------------ */
-    /**
-     * @return The SSLContext
-     * @deprecated
-     */
-    @Deprecated
-    public abstract SSLContext getSslContext();
-    
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return True if SSL re-negotiation is allowed (default false)
-     * @deprecated
-     */
-    @Deprecated
-    public boolean isAllowRenegotiate();
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Set if SSL re-negotiation is allowed. CVE-2009-3555 discovered
-     * a vulnerability in SSL/TLS with re-negotiation.  If your JVM
-     * does not have CVE-2009-3555 fixed, then re-negotiation should 
-     * not be allowed.
-     * @param allowRenegotiate true if re-negotiation is allowed (default false)
-     * @deprecated
-     */
-    @Deprecated
-    public void setAllowRenegotiate(boolean allowRenegotiate);
-}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ssl/SslSelectChannelConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ssl/SslSelectChannelConnector.java
deleted file mode 100644
index a50baa0..0000000
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/ssl/SslSelectChannelConnector.java
+++ /dev/null
@@ -1,653 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.server.ssl;
-
-import java.io.IOException;
-import java.nio.channels.SocketChannel;
-
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLEngine;
-import javax.net.ssl.SSLSession;
-import javax.net.ssl.SSLSocket;
-
-import org.eclipse.jetty.http.HttpSchemes;
-import org.eclipse.jetty.io.AsyncEndPoint;
-import org.eclipse.jetty.io.Buffers;
-import org.eclipse.jetty.io.Buffers.Type;
-import org.eclipse.jetty.io.BuffersFactory;
-import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.io.RuntimeIOException;
-import org.eclipse.jetty.io.bio.SocketEndPoint;
-import org.eclipse.jetty.io.nio.AsyncConnection;
-import org.eclipse.jetty.io.nio.SslConnection;
-import org.eclipse.jetty.server.Request;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
-import org.eclipse.jetty.util.component.AggregateLifeCycle;
-import org.eclipse.jetty.util.ssl.SslContextFactory;
-
-/* ------------------------------------------------------------ */
-/**
- * SslSelectChannelConnector.
- *
- * @org.apache.xbean.XBean element="sslConnector" description="Creates an NIO ssl connector"
- */
-public class SslSelectChannelConnector extends SelectChannelConnector implements SslConnector
-{
-    private final SslContextFactory _sslContextFactory;
-    private Buffers _sslBuffers;
-
-    /* ------------------------------------------------------------ */
-    public SslSelectChannelConnector()
-    {
-        this(new SslContextFactory(SslContextFactory.DEFAULT_KEYSTORE_PATH));
-        setSoLingerTime(30000);
-    }
-
-    /* ------------------------------------------------------------ */
-    /** Construct with explicit SslContextFactory.
-     * The SslContextFactory passed is added via {@link #addBean(Object)} so that 
-     * it's lifecycle may be managed with {@link AggregateLifeCycle}.
-     * @param sslContextFactory
-     */
-    public SslSelectChannelConnector(SslContextFactory sslContextFactory)
-    {
-        _sslContextFactory = sslContextFactory;
-        addBean(_sslContextFactory);
-        setUseDirectBuffers(false);
-        setSoLingerTime(30000);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Allow the Listener a chance to customise the request. before the server
-     * does its stuff. <br>
-     * This allows the required attributes to be set for SSL requests. <br>
-     * The requirements of the Servlet specs are:
-     * <ul>
-     * <li> an attribute named "javax.servlet.request.ssl_session_id" of type
-     * String (since Servlet Spec 3.0).</li>
-     * <li> an attribute named "javax.servlet.request.cipher_suite" of type
-     * String.</li>
-     * <li> an attribute named "javax.servlet.request.key_size" of type Integer.</li>
-     * <li> an attribute named "javax.servlet.request.X509Certificate" of type
-     * java.security.cert.X509Certificate[]. This is an array of objects of type
-     * X509Certificate, the order of this array is defined as being in ascending
-     * order of trust. The first certificate in the chain is the one set by the
-     * client, the next is the one used to authenticate the first, and so on.
-     * </li>
-     * </ul>
-     *
-     * @param endpoint
-     *                The Socket the request arrived on. This should be a
-     *                {@link SocketEndPoint} wrapping a {@link SSLSocket}.
-     * @param request
-     *                HttpRequest to be customised.
-     */
-    @Override
-    public void customize(EndPoint endpoint, Request request) throws IOException
-    {
-        request.setScheme(HttpSchemes.HTTPS);
-        super.customize(endpoint,request);
-
-        SslConnection.SslEndPoint sslEndpoint=(SslConnection.SslEndPoint)endpoint;
-        SSLEngine sslEngine=sslEndpoint.getSslEngine();
-        SSLSession sslSession=sslEngine.getSession();
-
-        SslCertificates.customize(sslSession,endpoint,request);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return True if SSL re-negotiation is allowed (default false)
-     * @deprecated
-     */
-    @Deprecated
-    public boolean isAllowRenegotiate()
-    {
-        return _sslContextFactory.isAllowRenegotiate();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Set if SSL re-negotiation is allowed. CVE-2009-3555 discovered
-     * a vulnerability in SSL/TLS with re-negotiation.  If your JVM
-     * does not have CVE-2009-3555 fixed, then re-negotiation should
-     * not be allowed.  CVE-2009-3555 was fixed in Sun java 1.6 with a ban
-     * of renegotiate in u19 and with RFC5746 in u22.
-     * @param allowRenegotiate true if re-negotiation is allowed (default false)
-     * @deprecated
-     */
-    @Deprecated
-    public void setAllowRenegotiate(boolean allowRenegotiate)
-    {
-        _sslContextFactory.setAllowRenegotiate(allowRenegotiate);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.server.ssl.SslConnector#getExcludeCipherSuites()
-     * @deprecated
-     */
-    @Deprecated
-    public String[] getExcludeCipherSuites()
-    {
-        return _sslContextFactory.getExcludeCipherSuites();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.server.ssl.SslConnector#setExcludeCipherSuites(java.lang.String[])
-     * @deprecated
-     */
-    @Deprecated
-    public void setExcludeCipherSuites(String[] cipherSuites)
-    {
-        _sslContextFactory.setExcludeCipherSuites(cipherSuites);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.server.ssl.SslConnector#getExcludeCipherSuites()
-     * @deprecated
-     */
-    @Deprecated
-    public String[] getIncludeCipherSuites()
-    {
-        return _sslContextFactory.getIncludeCipherSuites();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.server.ssl.SslConnector#setExcludeCipherSuites(java.lang.String[])
-     * @deprecated
-     */
-    @Deprecated
-    public void setIncludeCipherSuites(String[] cipherSuites)
-    {
-        _sslContextFactory.setIncludeCipherSuites(cipherSuites);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.server.ssl.SslConnector#setPassword(java.lang.String)
-     * @deprecated
-     */
-    @Deprecated
-    public void setPassword(String password)
-    {
-        _sslContextFactory.setKeyStorePassword(password);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.server.ssl.SslConnector#setTrustPassword(java.lang.String)
-     * @deprecated
-     */
-    @Deprecated
-    public void setTrustPassword(String password)
-    {
-        _sslContextFactory.setTrustStorePassword(password);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.server.ssl.SslConnector#setKeyPassword(java.lang.String)
-     * @deprecated
-     */
-    @Deprecated
-    public void setKeyPassword(String password)
-    {
-        _sslContextFactory.setKeyManagerPassword(password);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Unsupported.
-     *
-     * TODO: we should remove this as it is no longer an overridden method from SslConnector (like it was in the past)
-     * @deprecated
-     */
-    @Deprecated
-    public String getAlgorithm()
-    {
-        throw new UnsupportedOperationException();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Unsupported.
-     *
-     * TODO: we should remove this as it is no longer an overridden method from SslConnector (like it was in the past)
-     * @deprecated
-     */
-    @Deprecated
-    public void setAlgorithm(String algorithm)
-    {
-        throw new UnsupportedOperationException();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.server.ssl.SslConnector#getProtocol()
-     * @deprecated
-     */
-    @Deprecated
-    public String getProtocol()
-    {
-        return _sslContextFactory.getProtocol();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.server.ssl.SslConnector#setProtocol(java.lang.String)
-     * @deprecated
-     */
-    @Deprecated
-    public void setProtocol(String protocol)
-    {
-        _sslContextFactory.setProtocol(protocol);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.server.ssl.SslConnector#setKeystore(java.lang.String)
-     * @deprecated
-     */
-    @Deprecated
-    public void setKeystore(String keystore)
-    {
-        _sslContextFactory.setKeyStorePath(keystore);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.server.ssl.SslConnector#getKeystore()
-     * @deprecated
-     */
-    @Deprecated
-    public String getKeystore()
-    {
-        return _sslContextFactory.getKeyStorePath();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.server.ssl.SslConnector#getKeystoreType()
-     * @deprecated
-     */
-    @Deprecated
-    public String getKeystoreType()
-    {
-        return _sslContextFactory.getKeyStoreType();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.server.ssl.SslConnector#getNeedClientAuth()
-     * @deprecated
-     */
-    @Deprecated
-    public boolean getNeedClientAuth()
-    {
-        return _sslContextFactory.getNeedClientAuth();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.server.ssl.SslConnector#getWantClientAuth()
-     * @deprecated
-     */
-    @Deprecated
-    public boolean getWantClientAuth()
-    {
-        return _sslContextFactory.getWantClientAuth();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.server.ssl.SslConnector#setNeedClientAuth(boolean)
-     * @deprecated
-     */
-    @Deprecated
-    public void setNeedClientAuth(boolean needClientAuth)
-    {
-        _sslContextFactory.setNeedClientAuth(needClientAuth);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.server.ssl.SslConnector#setWantClientAuth(boolean)
-     * @deprecated
-     */
-    @Deprecated
-    public void setWantClientAuth(boolean wantClientAuth)
-    {
-        _sslContextFactory.setWantClientAuth(wantClientAuth);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.server.ssl.SslConnector#setKeystoreType(java.lang.String)
-     * @deprecated
-     */
-    @Deprecated
-    public void setKeystoreType(String keystoreType)
-    {
-        _sslContextFactory.setKeyStoreType(keystoreType);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.server.ssl.SslConnector#getProvider()
-     * @deprecated
-     */
-    @Deprecated
-    public String getProvider()
-    {
-        return _sslContextFactory.getProvider();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.server.ssl.SslConnector#getSecureRandomAlgorithm()
-     * @deprecated
-     */
-    @Deprecated
-    public String getSecureRandomAlgorithm()
-    {
-        return _sslContextFactory.getSecureRandomAlgorithm();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.server.ssl.SslConnector#getSslKeyManagerFactoryAlgorithm()
-     * @deprecated
-     */
-    @Deprecated
-    public String getSslKeyManagerFactoryAlgorithm()
-    {
-        return _sslContextFactory.getSslKeyManagerFactoryAlgorithm();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.server.ssl.SslConnector#getSslTrustManagerFactoryAlgorithm()
-     * @deprecated
-     */
-    @Deprecated
-    public String getSslTrustManagerFactoryAlgorithm()
-    {
-        return _sslContextFactory.getTrustManagerFactoryAlgorithm();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.server.ssl.SslConnector#getTruststore()
-     * @deprecated
-     */
-    @Deprecated
-    public String getTruststore()
-    {
-        return _sslContextFactory.getTrustStore();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.server.ssl.SslConnector#getTruststoreType()
-     * @deprecated
-     */
-    @Deprecated
-    public String getTruststoreType()
-    {
-        return _sslContextFactory.getTrustStoreType();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.server.ssl.SslConnector#setProvider(java.lang.String)
-     * @deprecated
-     */
-    @Deprecated
-    public void setProvider(String provider)
-    {
-        _sslContextFactory.setProvider(provider);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.server.ssl.SslConnector#setSecureRandomAlgorithm(java.lang.String)
-     * @deprecated
-     */
-    @Deprecated
-    public void setSecureRandomAlgorithm(String algorithm)
-    {
-        _sslContextFactory.setSecureRandomAlgorithm(algorithm);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.server.ssl.SslConnector#setSslKeyManagerFactoryAlgorithm(java.lang.String)
-     * @deprecated
-     */
-    @Deprecated
-    public void setSslKeyManagerFactoryAlgorithm(String algorithm)
-    {
-        _sslContextFactory.setSslKeyManagerFactoryAlgorithm(algorithm);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.server.ssl.SslConnector#setSslTrustManagerFactoryAlgorithm(java.lang.String)
-     * @deprecated
-     */
-    @Deprecated
-    public void setSslTrustManagerFactoryAlgorithm(String algorithm)
-    {
-        _sslContextFactory.setTrustManagerFactoryAlgorithm(algorithm);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.server.ssl.SslConnector#setTruststore(java.lang.String)
-     * @deprecated
-     */
-    @Deprecated
-    public void setTruststore(String truststore)
-    {
-        _sslContextFactory.setTrustStore(truststore);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.server.ssl.SslConnector#setTruststoreType(java.lang.String)
-     * @deprecated
-     */
-    @Deprecated
-    public void setTruststoreType(String truststoreType)
-    {
-        _sslContextFactory.setTrustStoreType(truststoreType);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.server.ssl.SslConnector#setSslContext(javax.net.ssl.SSLContext)
-     * @deprecated
-     */
-    @Deprecated
-    public void setSslContext(SSLContext sslContext)
-    {
-        _sslContextFactory.setSslContext(sslContext);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.server.ssl.SslConnector#setSslContext(javax.net.ssl.SSLContext)
-     * @deprecated
-     */
-    @Deprecated
-    public SSLContext getSslContext()
-    {
-        return _sslContextFactory.getSslContext();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.server.ssl.SslConnector#getSslContextFactory()
-     */
-    public SslContextFactory getSslContextFactory()
-    {
-        return _sslContextFactory;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * By default, we're confidential, given we speak SSL. But, if we've been
-     * told about an confidential port, and said port is not our port, then
-     * we're not. This allows separation of listeners providing INTEGRAL versus
-     * CONFIDENTIAL constraints, such as one SSL listener configured to require
-     * client certs providing CONFIDENTIAL, whereas another SSL listener not
-     * requiring client certs providing mere INTEGRAL constraints.
-     */
-    @Override
-    public boolean isConfidential(Request request)
-    {
-        final int confidentialPort=getConfidentialPort();
-        return confidentialPort==0||confidentialPort==request.getServerPort();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * By default, we're integral, given we speak SSL. But, if we've been told
-     * about an integral port, and said port is not our port, then we're not.
-     * This allows separation of listeners providing INTEGRAL versus
-     * CONFIDENTIAL constraints, such as one SSL listener configured to require
-     * client certs providing CONFIDENTIAL, whereas another SSL listener not
-     * requiring client certs providing mere INTEGRAL constraints.
-     */
-    @Override
-    public boolean isIntegral(Request request)
-    {
-        final int integralPort=getIntegralPort();
-        return integralPort==0||integralPort==request.getServerPort();
-    }
-
-    /* ------------------------------------------------------------------------------- */
-    @Override
-    protected AsyncConnection newConnection(SocketChannel channel, AsyncEndPoint endpoint)
-    {
-        try
-        {
-            SSLEngine engine = createSSLEngine(channel);
-            SslConnection connection = newSslConnection(endpoint, engine);
-            AsyncConnection delegate = newPlainConnection(channel, connection.getSslEndPoint());
-            connection.getSslEndPoint().setConnection(delegate);
-            connection.setAllowRenegotiate(_sslContextFactory.isAllowRenegotiate());
-            return connection;
-        }
-        catch (IOException e)
-        {
-            throw new RuntimeIOException(e);
-        }
-    }
-
-    protected AsyncConnection newPlainConnection(SocketChannel channel, AsyncEndPoint endPoint)
-    {
-        return super.newConnection(channel, endPoint);
-    }
-
-    protected SslConnection newSslConnection(AsyncEndPoint endpoint, SSLEngine engine)
-    {
-        return new SslConnection(engine, endpoint);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param channel A channel which if passed is used as to extract remote
-     * host and port for the purposes of SSL session caching
-     * @return A SSLEngine for a new or cached SSL Session
-     * @throws IOException if the SSLEngine cannot be created
-     */
-    protected SSLEngine createSSLEngine(SocketChannel channel) throws IOException
-    {
-        SSLEngine engine;
-        if (channel != null)
-        {
-            String peerHost = channel.socket().getInetAddress().getHostAddress();
-            int peerPort = channel.socket().getPort();
-            engine = _sslContextFactory.newSslEngine(peerHost, peerPort);
-        }
-        else
-        {
-            engine = _sslContextFactory.newSslEngine();
-        }
-
-        engine.setUseClientMode(false);
-        return engine;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.server.nio.SelectChannelConnector#doStart()
-     */
-    @Override
-    protected void doStart() throws Exception
-    {
-        _sslContextFactory.checkKeyStore();
-        _sslContextFactory.start();
-
-        SSLEngine sslEngine = _sslContextFactory.newSslEngine();
-
-        sslEngine.setUseClientMode(false);
-
-        SSLSession sslSession = sslEngine.getSession();
-
-        _sslBuffers = BuffersFactory.newBuffers(
-                getUseDirectBuffers()?Type.DIRECT:Type.INDIRECT,sslSession.getApplicationBufferSize(),
-                getUseDirectBuffers()?Type.DIRECT:Type.INDIRECT,sslSession.getApplicationBufferSize(),
-                getUseDirectBuffers()?Type.DIRECT:Type.INDIRECT,getMaxBuffers()
-        );
-
-        if (getRequestHeaderSize()<sslSession.getApplicationBufferSize())
-            setRequestHeaderSize(sslSession.getApplicationBufferSize());
-        if (getRequestBufferSize()<sslSession.getApplicationBufferSize())
-            setRequestBufferSize(sslSession.getApplicationBufferSize());
-
-        super.doStart();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.server.nio.SelectChannelConnector#doStop()
-     */
-    @Override
-    protected void doStop() throws Exception
-    {
-        _sslBuffers=null;
-        super.doStop();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return SSL buffers
-     */
-    public Buffers getSslBuffers()
-    {
-        return _sslBuffers;
-    }
-}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ssl/SslSocketConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ssl/SslSocketConnector.java
deleted file mode 100644
index bb9fe03..0000000
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/ssl/SslSocketConnector.java
+++ /dev/null
@@ -1,712 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.server.ssl;
-
-import java.io.IOException;
-import java.net.ServerSocket;
-import java.net.Socket;
-
-import javax.net.ssl.HandshakeCompletedEvent;
-import javax.net.ssl.HandshakeCompletedListener;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLException;
-import javax.net.ssl.SSLServerSocket;
-import javax.net.ssl.SSLSession;
-import javax.net.ssl.SSLSocket;
-
-import org.eclipse.jetty.http.HttpSchemes;
-import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.io.RuntimeIOException;
-import org.eclipse.jetty.io.bio.SocketEndPoint;
-import org.eclipse.jetty.server.Request;
-import org.eclipse.jetty.server.bio.SocketConnector;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.util.ssl.SslContextFactory;
-
-/* ------------------------------------------------------------ */
-/**
- * SSL Socket Connector.
- *
- * This specialization of SocketConnector is an abstract listener that can be used as the basis for a
- * specific JSSE listener.
- *
- * The original of this class was heavily based on the work from Court Demas, which in turn is
- * based on the work from Forge Research. Since JSSE, this class has evolved significantly from
- * that early work.
- *
- * @org.apache.xbean.XBean element="sslSocketConnector" description="Creates an ssl socket connector"
- *
- *
- */
-public class SslSocketConnector extends SocketConnector  implements SslConnector
-{
-    private static final Logger LOG = Log.getLogger(SslSocketConnector.class);
-
-    private final SslContextFactory _sslContextFactory;
-    private int _handshakeTimeout = 0; //0 means use maxIdleTime
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Constructor.
-     */
-    public SslSocketConnector()
-    {
-        this(new SslContextFactory(SslContextFactory.DEFAULT_KEYSTORE_PATH));
-        setSoLingerTime(30000);
-    }
-
-    /* ------------------------------------------------------------ */
-    public SslSocketConnector(SslContextFactory sslContextFactory)
-    {
-        _sslContextFactory = sslContextFactory;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return True if SSL re-negotiation is allowed (default false)
-     */
-    public boolean isAllowRenegotiate()
-    {
-        return _sslContextFactory.isAllowRenegotiate();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Set if SSL re-negotiation is allowed. CVE-2009-3555 discovered
-     * a vulnerability in SSL/TLS with re-negotiation.  If your JVM
-     * does not have CVE-2009-3555 fixed, then re-negotiation should
-     * not be allowed.
-     * @param allowRenegotiate true if re-negotiation is allowed (default false)
-     */
-    public void setAllowRenegotiate(boolean allowRenegotiate)
-    {
-        _sslContextFactory.setAllowRenegotiate(allowRenegotiate);
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public void accept(int acceptorID)
-        throws IOException, InterruptedException
-    {
-        Socket socket = _serverSocket.accept();
-        configure(socket);
-
-        ConnectorEndPoint connection=new SslConnectorEndPoint(socket);
-        connection.dispatch();
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    protected void configure(Socket socket)
-        throws IOException
-    {
-        super.configure(socket);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Allow the Listener a chance to customise the request. before the server does its stuff. <br>
-     * This allows the required attributes to be set for SSL requests. <br>
-     * The requirements of the Servlet specs are:
-     * <ul>
-     * <li> an attribute named "javax.servlet.request.ssl_id" of type String (since Spec 3.0).</li>
-     * <li> an attribute named "javax.servlet.request.cipher_suite" of type String.</li>
-     * <li> an attribute named "javax.servlet.request.key_size" of type Integer.</li>
-     * <li> an attribute named "javax.servlet.request.X509Certificate" of type
-     * java.security.cert.X509Certificate[]. This is an array of objects of type X509Certificate,
-     * the order of this array is defined as being in ascending order of trust. The first
-     * certificate in the chain is the one set by the client, the next is the one used to
-     * authenticate the first, and so on. </li>
-     * </ul>
-     *
-     * @param endpoint The Socket the request arrived on.
-     *        This should be a {@link SocketEndPoint} wrapping a {@link SSLSocket}.
-     * @param request HttpRequest to be customised.
-     */
-    @Override
-    public void customize(EndPoint endpoint, Request request)
-        throws IOException
-    {
-        super.customize(endpoint, request);
-        request.setScheme(HttpSchemes.HTTPS);
-
-        SocketEndPoint socket_end_point = (SocketEndPoint)endpoint;
-        SSLSocket sslSocket = (SSLSocket)socket_end_point.getTransport();
-        SSLSession sslSession = sslSocket.getSession();
-
-        SslCertificates.customize(sslSession,endpoint,request);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.server.ssl.SslConnector#getExcludeCipherSuites()
-     * @deprecated
-     */
-    @Deprecated
-    public String[] getExcludeCipherSuites() {
-        return _sslContextFactory.getExcludeCipherSuites();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.server.ssl.SslConnector#getIncludeCipherSuites()
-     * @deprecated
-     */
-    @Deprecated
-    public String[] getIncludeCipherSuites()
-    {
-        return _sslContextFactory.getIncludeCipherSuites();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.server.ssl.SslConnector#getKeystore()
-     * @deprecated
-     */
-    @Deprecated
-    public String getKeystore()
-    {
-        return _sslContextFactory.getKeyStorePath();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.server.ssl.SslConnector#getKeystoreType()
-     * @deprecated
-     */
-    @Deprecated
-    public String getKeystoreType()
-    {
-        return _sslContextFactory.getKeyStoreType();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.server.ssl.SslConnector#getNeedClientAuth()
-     * @deprecated
-     */
-    @Deprecated
-    public boolean getNeedClientAuth()
-    {
-        return _sslContextFactory.getNeedClientAuth();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.server.ssl.SslConnector#getProtocol()
-     * @deprecated
-     */
-    @Deprecated
-    public String getProtocol()
-    {
-        return _sslContextFactory.getProtocol();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.server.ssl.SslConnector#getProvider()
-     * @deprecated
-     */
-    @Deprecated
-    public String getProvider() {
-	return _sslContextFactory.getProvider();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.server.ssl.SslConnector#getSecureRandomAlgorithm()
-     * @deprecated
-     */
-    @Deprecated
-    public String getSecureRandomAlgorithm()
-    {
-        return _sslContextFactory.getSecureRandomAlgorithm();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.server.ssl.SslConnector#getSslKeyManagerFactoryAlgorithm()
-     * @deprecated
-     */
-    @Deprecated
-    public String getSslKeyManagerFactoryAlgorithm()
-    {
-        return _sslContextFactory.getSslKeyManagerFactoryAlgorithm();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.server.ssl.SslConnector#getSslTrustManagerFactoryAlgorithm()
-     * @deprecated
-     */
-    @Deprecated
-    public String getSslTrustManagerFactoryAlgorithm()
-    {
-        return _sslContextFactory.getTrustManagerFactoryAlgorithm();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.server.ssl.SslConnector#getTruststore()
-     * @deprecated
-     */
-    @Deprecated
-    public String getTruststore()
-    {
-        return _sslContextFactory.getTrustStore();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.server.ssl.SslConnector#getSslContextFactory()
-     */
-//    @Override
-    public SslContextFactory getSslContextFactory()
-    {
-        return _sslContextFactory;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.server.ssl.SslConnector#getTruststoreType()
-     * @deprecated
-     */
-    @Deprecated
-    public String getTruststoreType()
-    {
-        return _sslContextFactory.getTrustStoreType();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.server.ssl.SslConnector#getWantClientAuth()
-     * @deprecated
-     */
-    @Deprecated
-    public boolean getWantClientAuth()
-    {
-        return _sslContextFactory.getWantClientAuth();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * By default, we're confidential, given we speak SSL. But, if we've been told about an
-     * confidential port, and said port is not our port, then we're not. This allows separation of
-     * listeners providing INTEGRAL versus CONFIDENTIAL constraints, such as one SSL listener
-     * configured to require client certs providing CONFIDENTIAL, whereas another SSL listener not
-     * requiring client certs providing mere INTEGRAL constraints.
-     */
-    @Override
-    public boolean isConfidential(Request request)
-    {
-        final int confidentialPort = getConfidentialPort();
-        return confidentialPort == 0 || confidentialPort == request.getServerPort();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * By default, we're integral, given we speak SSL. But, if we've been told about an integral
-     * port, and said port is not our port, then we're not. This allows separation of listeners
-     * providing INTEGRAL versus CONFIDENTIAL constraints, such as one SSL listener configured to
-     * require client certs providing CONFIDENTIAL, whereas another SSL listener not requiring
-     * client certs providing mere INTEGRAL constraints.
-     */
-    @Override
-    public boolean isIntegral(Request request)
-    {
-        final int integralPort = getIntegralPort();
-        return integralPort == 0 || integralPort == request.getServerPort();
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public void open() throws IOException
-    {
-        _sslContextFactory.checkKeyStore();
-        try
-        {
-            _sslContextFactory.start();
-        }
-        catch(Exception e)
-        {
-            throw new RuntimeIOException(e);
-        }
-        super.open();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    protected void doStart() throws Exception
-    {
-        _sslContextFactory.checkKeyStore();
-        _sslContextFactory.start();
-
-        super.doStart();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.server.bio.SocketConnector#doStop()
-     */
-    @Override
-    protected void doStop() throws Exception
-    {
-        _sslContextFactory.stop();
-
-        super.doStop();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param host The host name that this server should listen on
-     * @param port the port that this server should listen on
-     * @param backlog See {@link ServerSocket#bind(java.net.SocketAddress, int)}
-     * @return A new {@link ServerSocket socket object} bound to the supplied address with all other
-     * settings as per the current configuration of this connector.
-     * @see #setWantClientAuth(boolean)
-     * @see #setNeedClientAuth(boolean)
-     * @exception IOException
-     */
-    @Override
-    protected ServerSocket newServerSocket(String host, int port,int backlog) throws IOException
-    {
-       return _sslContextFactory.newSslServerSocket(host,port,backlog);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.server.ssl.SslConnector#setExcludeCipherSuites(java.lang.String[])
-     * @deprecated
-     */
-    @Deprecated
-    public void setExcludeCipherSuites(String[] cipherSuites)
-    {
-        _sslContextFactory.setExcludeCipherSuites(cipherSuites);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.server.ssl.SslConnector#setIncludeCipherSuites(java.lang.String[])
-     * @deprecated
-     */
-    @Deprecated
-    public void setIncludeCipherSuites(String[] cipherSuites)
-    {
-        _sslContextFactory.setIncludeCipherSuites(cipherSuites);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.server.ssl.SslConnector#setKeyPassword(java.lang.String)
-     * @deprecated
-     */
-    @Deprecated
-    public void setKeyPassword(String password)
-    {
-        _sslContextFactory.setKeyManagerPassword(password);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param keystore The resource path to the keystore, or null for built in keystores.
-     * @deprecated
-     */
-    @Deprecated
-    public void setKeystore(String keystore)
-    {
-        _sslContextFactory.setKeyStorePath(keystore);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.server.ssl.SslConnector#setKeystoreType(java.lang.String)
-     * @deprecated
-     */
-    @Deprecated
-    public void setKeystoreType(String keystoreType)
-    {
-        _sslContextFactory.setKeyStoreType(keystoreType);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Set the value of the needClientAuth property
-     *
-     * @param needClientAuth true iff we require client certificate authentication.
-     * @deprecated
-     */
-    @Deprecated
-    public void setNeedClientAuth(boolean needClientAuth)
-    {
-        _sslContextFactory.setNeedClientAuth(needClientAuth);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.server.ssl.SslConnector#setPassword(java.lang.String)
-     * @deprecated
-     */
-    @Deprecated
-    public void setPassword(String password)
-    {
-        _sslContextFactory.setKeyStorePassword(password);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.server.ssl.SslConnector#setTrustPassword(java.lang.String)
-     * @deprecated
-     */
-    @Deprecated
-    public void setTrustPassword(String password)
-    {
-        _sslContextFactory.setTrustStorePassword(password);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.server.ssl.SslConnector#setProtocol(java.lang.String)
-     * @deprecated
-     */
-    @Deprecated
-    public void setProtocol(String protocol)
-    {
-        _sslContextFactory.setProtocol(protocol);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.server.ssl.SslConnector#setProvider(java.lang.String)
-     * @deprecated
-     */
-    @Deprecated
-    public void setProvider(String provider) {
-        _sslContextFactory.setProvider(provider);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.server.ssl.SslConnector#setSecureRandomAlgorithm(java.lang.String)
-     * @deprecated
-     */
-    @Deprecated
-    public void setSecureRandomAlgorithm(String algorithm)
-    {
-        _sslContextFactory.setSecureRandomAlgorithm(algorithm);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.server.ssl.SslConnector#setSslKeyManagerFactoryAlgorithm(java.lang.String)
-     * @deprecated
-     */
-    @Deprecated
-    public void setSslKeyManagerFactoryAlgorithm(String algorithm)
-    {
-        _sslContextFactory.setSslKeyManagerFactoryAlgorithm(algorithm);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.server.ssl.SslConnector#setSslTrustManagerFactoryAlgorithm(java.lang.String)
-     * @deprecated
-     */
-    @Deprecated
-    public void setSslTrustManagerFactoryAlgorithm(String algorithm)
-    {
-        _sslContextFactory.setTrustManagerFactoryAlgorithm(algorithm);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.server.ssl.SslConnector#setTruststore(java.lang.String)
-     * @deprecated
-     */
-    @Deprecated
-    public void setTruststore(String truststore)
-    {
-        _sslContextFactory.setTrustStore(truststore);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.server.ssl.SslConnector#setTruststoreType(java.lang.String)
-     * @deprecated
-     */
-    @Deprecated
-    public void setTruststoreType(String truststoreType)
-    {
-        _sslContextFactory.setTrustStoreType(truststoreType);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.server.ssl.SslConnector#setSslContext(javax.net.ssl.SSLContext)
-     * @deprecated
-     */
-    @Deprecated
-    public void setSslContext(SSLContext sslContext)
-    {
-        _sslContextFactory.setSslContext(sslContext);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see org.eclipse.jetty.server.ssl.SslConnector#setSslContext(javax.net.ssl.SSLContext)
-     * @deprecated
-     */
-    @Deprecated
-    public SSLContext getSslContext()
-    {
-        return _sslContextFactory.getSslContext();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Set the value of the _wantClientAuth property. This property is used
-     * internally when opening server sockets.
-     *
-     * @param wantClientAuth true if we want client certificate authentication.
-     * @see SSLServerSocket#setWantClientAuth
-     * @deprecated
-     */
-    @Deprecated
-    public void setWantClientAuth(boolean wantClientAuth)
-    {
-        _sslContextFactory.setWantClientAuth(wantClientAuth);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Set the time in milliseconds for so_timeout during ssl handshaking
-     * @param msec a non-zero value will be used to set so_timeout during
-     * ssl handshakes. A zero value means the maxIdleTime is used instead.
-     */
-    public void setHandshakeTimeout (int msec)
-    {
-        _handshakeTimeout = msec;
-    }
-
-
-    /* ------------------------------------------------------------ */
-    public int getHandshakeTimeout ()
-    {
-        return _handshakeTimeout;
-    }
-
-    /* ------------------------------------------------------------ */
-    public class SslConnectorEndPoint extends ConnectorEndPoint
-    {
-        public SslConnectorEndPoint(Socket socket) throws IOException
-        {
-            super(socket);
-        }
-
-        @Override
-        public void shutdownOutput() throws IOException
-        {
-            close();
-        }
-
-        @Override
-        public void shutdownInput() throws IOException
-        {
-            close();
-        }
-
-        @Override
-        public void run()
-        {
-            try
-            {
-                int handshakeTimeout = getHandshakeTimeout();
-                int oldTimeout = _socket.getSoTimeout();
-                if (handshakeTimeout > 0)
-                    _socket.setSoTimeout(handshakeTimeout);
-
-                final SSLSocket ssl=(SSLSocket)_socket;
-                ssl.addHandshakeCompletedListener(new HandshakeCompletedListener()
-                {
-                    boolean handshook=false;
-                    public void handshakeCompleted(HandshakeCompletedEvent event)
-                    {
-                        if (handshook)
-                        {
-                            if (!_sslContextFactory.isAllowRenegotiate())
-                            {
-                                LOG.warn("SSL renegotiate denied: "+ssl);
-                                try{ssl.close();}catch(IOException e){LOG.warn(e);}
-                            }
-                        }
-                        else
-                            handshook=true;
-                    }
-                });
-                ssl.startHandshake();
-
-                if (handshakeTimeout>0)
-                    _socket.setSoTimeout(oldTimeout);
-
-                super.run();
-            }
-            catch (SSLException e)
-            {
-                LOG.debug(e);
-                try{close();}
-                catch(IOException e2){LOG.ignore(e2);}
-            }
-            catch (IOException e)
-            {
-                LOG.debug(e);
-                try{close();}
-                catch(IOException e2){LOG.ignore(e2);}
-            }
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Unsupported.
-     *
-     * TODO: we should remove this as it is no longer an overridden method from SslConnector (like it was in the past)
-     * @deprecated
-     */
-    @Deprecated
-    public String getAlgorithm()
-    {
-        throw new UnsupportedOperationException();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Unsupported.
-     *
-     * TODO: we should remove this as it is no longer an overridden method from SslConnector (like it was in the past)
-     * @deprecated
-     */
-    @Deprecated
-    public void setAlgorithm(String algorithm)
-    {
-        throw new UnsupportedOperationException();
-    }
-}
diff --git a/jetty-server/src/main/resources/org/eclipse/jetty/server/handler/jmx/AbstractHandler-mbean.properties b/jetty-server/src/main/resources/org/eclipse/jetty/server/handler/jmx/AbstractHandler-mbean.properties
deleted file mode 100644
index 8b82f1a..0000000
--- a/jetty-server/src/main/resources/org/eclipse/jetty/server/handler/jmx/AbstractHandler-mbean.properties
+++ /dev/null
@@ -1 +0,0 @@
-AbstractHandler: Jetty Handler.
\ No newline at end of file
diff --git a/jetty-server/src/main/resources/org/eclipse/jetty/server/handler/jmx/ContextHandler-mbean.properties b/jetty-server/src/main/resources/org/eclipse/jetty/server/handler/jmx/ContextHandler-mbean.properties
deleted file mode 100644
index faf27f0..0000000
--- a/jetty-server/src/main/resources/org/eclipse/jetty/server/handler/jmx/ContextHandler-mbean.properties
+++ /dev/null
@@ -1,24 +0,0 @@
-ContextHandler: URI Context
-aliases: True if alias checking is performed on resource
-allowNullPathInfo: Checks if the /context is not redirected to /context/
-classPath: RO: The file classpath
-compactPath: True if URLs are compacted to replace the multiple '/'s with a single '/'
-connectorNames: Names and ports of accepted connectors
-contextAttributes: RO:MBean: Map of context attributes
-contextPath: URI prefix of context
-displayName: RO: Display name of the Context
-errorHandler: MObject: The error handler to use for the context
-initParams: Initial Parameter map for the context
-maxFormContentSize: The maximum content size
-removeContextAttribute(java.lang.String): MBean:ACTION: remove context attribute
-removeContextAttribute(java.lang.String)[0]: name: The attribute name
-resourceBase: Document root for the context
-setContextAttribute(java.lang.String,java.lang.Object): MBean:ACTION: Set context attribute
-setContextAttribute(java.lang.String,java.lang.Object)[0]: name: The attribute name
-setContextAttribute(java.lang.String,java.lang.Object)[1]: value: The attribute value
-setContextAttribute(java.lang.String,java.lang.String): MBean:ACTION: Set context attribute
-setContextAttribute(java.lang.String,java.lang.String)[0]: name: The attribute name
-setContextAttribute(java.lang.String,java.lang.String)[1]: value: The attribute value
-shutdown: False if this context is accepting new requests. True for graceful shutdown, which allows existing requests to complete
-virtualHosts: Virtual hosts accepted by the context
-welcomeFiles: Partial URIs of directory welcome files
diff --git a/jetty-server/src/main/resources/org/eclipse/jetty/server/handler/jmx/ContextHandlerCollection-mbean.properties b/jetty-server/src/main/resources/org/eclipse/jetty/server/handler/jmx/ContextHandlerCollection-mbean.properties
deleted file mode 100644
index 4ab1fef..0000000
--- a/jetty-server/src/main/resources/org/eclipse/jetty/server/handler/jmx/ContextHandlerCollection-mbean.properties
+++ /dev/null
@@ -1,2 +0,0 @@
-ContextHandlerCollection: Context Handler Collection
-mapContexts(): Update the mapping of context path to context
\ No newline at end of file
diff --git a/jetty-server/src/main/resources/org/eclipse/jetty/server/handler/jmx/HandlerCollection-mbean.properties b/jetty-server/src/main/resources/org/eclipse/jetty/server/handler/jmx/HandlerCollection-mbean.properties
deleted file mode 100644
index 37152f5..0000000
--- a/jetty-server/src/main/resources/org/eclipse/jetty/server/handler/jmx/HandlerCollection-mbean.properties
+++ /dev/null
@@ -1,2 +0,0 @@
-HandlerCollection: Handler of multiple Handlers
-handlers: MObject:Wrapped handlers
diff --git a/jetty-server/src/main/resources/org/eclipse/jetty/server/handler/jmx/HandlerWrapper-mbean.properties b/jetty-server/src/main/resources/org/eclipse/jetty/server/handler/jmx/HandlerWrapper-mbean.properties
deleted file mode 100644
index 3395f64..0000000
--- a/jetty-server/src/main/resources/org/eclipse/jetty/server/handler/jmx/HandlerWrapper-mbean.properties
+++ /dev/null
@@ -1,2 +0,0 @@
-HandlerWrapper: Handler wrapping another Handler
-handler: MObject:Wrapped handler
diff --git a/jetty-server/src/main/resources/org/eclipse/jetty/server/handler/jmx/StatisticsHandler-mbean.properties b/jetty-server/src/main/resources/org/eclipse/jetty/server/handler/jmx/StatisticsHandler-mbean.properties
deleted file mode 100644
index 295f4de..0000000
--- a/jetty-server/src/main/resources/org/eclipse/jetty/server/handler/jmx/StatisticsHandler-mbean.properties
+++ /dev/null
@@ -1,28 +0,0 @@
-StatisticsHandler: Request Statistics gathering
-statsOnMs: Time in milliseconds stats have been collected for.
-statsReset(): Resets statistics.
-requests: Number of requests since statsReset() called.
-requestsActive: Number of requests currently active since statsReset() called.
-requestsActiveMax: Maximum number of active requests since statsReset() called.
-requestTimeMax: Maximum time in milliseconds of request handling since statsReset() called.
-requestTimeTotal: Total time in milliseconds of all request handling since statsReset() called.
-requestTimeMean: Mean of time in milliseconds of request handling since statsReset() called.
-requestTimeStdDev: Standard deviation of time in milliseconds of request handling since statsReset() called.
-dispatched: Number of dispatches since statsReset() called.
-dispatchedActive: Number of dispatches currently active since statsReset() called.
-dispatchedActiveMax: Maximum number of active dispatches since statsReset() called.
-dispatchedTimeMax: Maximum time in milliseconds of dispatched handling since statsReset() called.
-dispatchedTimeTotal: Total time in milliseconds of all dispatched handling since statsReset() called.
-dispatchedTimeMean: Mean of time in milliseconds of dispatch handling since statsReset() called.
-dispatchedTimeStdDev: Standard deviation of time in milliseconds of dispatch handling since statsReset() called.
-suspends: Number of requests suspended since statsReset() called.
-suspendsActive: Number of dispatches currently active since statsReset() called.
-suspendsActiveMax: Maximum number of active dispatches since statsReset() called.
-resumes: Number of requests resumed since statsReset() called.
-expires: Number of requests expired since statsReset() called.
-responses1xx: Number of responses with a 1xx status since statsReset() called.
-responses2xx: Number of responses with a 2xx status since statsReset() called.
-responses3xx: Number of responses with a 3xx status since statsReset() called.
-responses4xx: Number of responses with a 4xx status since statsReset() called.
-responses5xx: Number of responses with a 5xx status since statsReset() called.
-responsesBytesTotal: Total number of bytes of all responses since statsReset() called.
\ No newline at end of file
diff --git a/jetty-server/src/main/resources/org/eclipse/jetty/server/jmx/AbstractConnector-mbean.properties b/jetty-server/src/main/resources/org/eclipse/jetty/server/jmx/AbstractConnector-mbean.properties
deleted file mode 100644
index fd76672..0000000
--- a/jetty-server/src/main/resources/org/eclipse/jetty/server/jmx/AbstractConnector-mbean.properties
+++ /dev/null
@@ -1,19 +0,0 @@
-AbstractConnector: Abstract implementation of the Connector interface.
-acceptors: The number of acceptor threads.
-acceptQueueSize: The size of the accept queue.
-acceptorPriorityOffset: Priority offset of the acceptor threads. The priority is adjusted by this amount to either favor the acceptance of new threads and newly active connections or to favor the handling of already dispatched connections.
-forwardedForHeader: The header name for forwarded for (default x-forwarded-for). 
-forwardedHostHeader: The header name for forwarded hosts (default x-forwarded-host)
-forwardedServerHeader: The header name for forwarded server (default x-forwarded-server)
-forwarded: Whether reverse proxy handling is on. True if this connector is checking the forwarded for/host/server headers.
-host: Host name of the server.
-hostHeader: Forced value for the host header. Only used if forwarded is true.
-soLingerTime: Enable or disable SO_LINGER with the specified linger time in seconds.
-reuseAddress: Whether the server socket will be opened in SO_REUSEADDR mode.
-name: Name of the connector.
-resolveNames: Whether or not to use DNS when handling forwards. 
-confidentialPort: Port to use for confidential redirections.
-confidentialScheme: Scheme to use for confidential redirections.
-integralPort: Port to use for integral redirections.
-integralScheme: Scheme to use for integral redirections.
-lowResourcesMaxIdleTime: The period in ms that a connection may be idle when the connector has low resources, before it is closed.
diff --git a/jetty-server/src/main/resources/org/eclipse/jetty/server/jmx/Connector-mbean.properties b/jetty-server/src/main/resources/org/eclipse/jetty/server/jmx/Connector-mbean.properties
deleted file mode 100644
index efd5cef..0000000
--- a/jetty-server/src/main/resources/org/eclipse/jetty/server/jmx/Connector-mbean.properties
+++ /dev/null
@@ -1,29 +0,0 @@
-Connector: HTTP Connector.
-server: MObject:RO:The server for this connector
-requestHeaderSize: The size of a request header buffer
-requestBufferSize: The size of a request content buffer
-responseHeaderSize: The size of a response header buffer
-responseBufferSize: The size of a response content buffer
-integralPort: Port to use for integral redirections
-integralScheme: Scheme to use for integral redirections
-confidentialPort: Port to use for confidential redirections
-confidentialScheme: Scheme to use for confidential redirections
-host: Host name to accept connections on
-port: TCP/IP port to accept connections on
-maxIdleTime: Maximum time in ms that a connection can be idle before being closed
-statsOn: True if statistics collection is turned on.
-statsOnMs: Time in milliseconds stats have been collected for. 
-statsReset(): Reset statistics.
-connections: Number of connections accepted by the server since statsReset() called. Undefined if setStatsOn(false).
-connectionsOpen: Number of connections currently open that were opened since statsReset() called. Undefined if setStatsOn(false).
-connectionsOpenMax: Maximum number of connections opened simultaneously since statsReset() called. Undefined if setStatsOn(false).
-connectionsDurationMean: Mean duration in milliseconds of open connections since statsReset() called. Undefined if setStatsOn(false).
-connectionsDurationStdDev: Standard deviation of duration in milliseconds of an open connection since statsReset() called. Undefined if setStatsOn(false).
-connectionsDurationMax: Maximum duration in milliseconds of an open connection since statsReset() called. Undefined if setStatsOn(false).
-connectionsDurationTotal: Total duration in milliseconds of all open connection since statsReset() called. Undefined if setStatsOn(false).
-connectionsRequestsMean: Mean number of requests per connection since statsReset() called. Undefined if setStatsOn(false).
-connectionsRequestsStdDev: Standard deviation of number of requests per connection since statsReset() called. Undefined if setStatsOn(false).
-connectionsRequestsMax: Maximum number of requests per connection since statsReset() called. Undefined if setStatsOn(false).
-requests: Number of requests since statsReset() called. Undefined if setStatsOn(false).
-open(): Open the listening port
-close(): Close the listening port (but allow existing connections to continue for graceful shutdown)
\ No newline at end of file
diff --git a/jetty-server/src/main/resources/org/eclipse/jetty/server/jmx/Handler-mbean.properties b/jetty-server/src/main/resources/org/eclipse/jetty/server/jmx/Handler-mbean.properties
deleted file mode 100644
index eea633f..0000000
--- a/jetty-server/src/main/resources/org/eclipse/jetty/server/jmx/Handler-mbean.properties
+++ /dev/null
@@ -1,3 +0,0 @@
-Handler: Jetty Handler.
-server: MObject:RO:The Jetty server for this handler
-destroy(): destroy associated resources (eg MBean)
\ No newline at end of file
diff --git a/jetty-server/src/main/resources/org/eclipse/jetty/server/jmx/HandlerContainer-mbean.properties b/jetty-server/src/main/resources/org/eclipse/jetty/server/jmx/HandlerContainer-mbean.properties
deleted file mode 100644
index 374e020..0000000
--- a/jetty-server/src/main/resources/org/eclipse/jetty/server/jmx/HandlerContainer-mbean.properties
+++ /dev/null
@@ -1,3 +0,0 @@
-HandlerContainer: Handler of multiple Handlers
-handlers: MObject:RO:Handlers in this container
-childHandlers: MObject:RO:All contained handlers
diff --git a/jetty-server/src/main/resources/org/eclipse/jetty/server/jmx/NCSARequestLog-mbean.properties b/jetty-server/src/main/resources/org/eclipse/jetty/server/jmx/NCSARequestLog-mbean.properties
deleted file mode 100644
index 0c0e340..0000000
--- a/jetty-server/src/main/resources/org/eclipse/jetty/server/jmx/NCSARequestLog-mbean.properties
+++ /dev/null
@@ -1,6 +0,0 @@
-NCSARequestLog : NCSA standard format request log
-filename : Filename of log
-retainDays : Number of days that the log files are kept
-append : Existing log files are appended to the new one
-extended : Use the extended NCSA format
-LogTimeZone : The timezone
diff --git a/jetty-server/src/main/resources/org/eclipse/jetty/server/jmx/Server-mbean.properties b/jetty-server/src/main/resources/org/eclipse/jetty/server/jmx/Server-mbean.properties
deleted file mode 100644
index 017711c..0000000
--- a/jetty-server/src/main/resources/org/eclipse/jetty/server/jmx/Server-mbean.properties
+++ /dev/null
@@ -1,9 +0,0 @@
-Server: Jetty HTTP Servlet server
-connectors: MObject:HTTP Connectors for this server
-version: RO: The version of this server
-sendServerVersion: If true include the server version in HTTP headers
-threadPool: MObject:The server Thread Pool
-contexts: MMBean:RO:The contexts of this server
-startupTime: MBean:RO:The startup time, in milliseconds, since January 1st 1970
-dumpAfterStart: RW:Dump state to stderr after start
-dumpBeforeStop: RW:Dump state to stderr before stop
diff --git a/jetty-server/src/main/resources/org/eclipse/jetty/server/nio/jmx/SelectChannelConnector-mbean.properties b/jetty-server/src/main/resources/org/eclipse/jetty/server/nio/jmx/SelectChannelConnector-mbean.properties
deleted file mode 100644
index dbe718d..0000000
--- a/jetty-server/src/main/resources/org/eclipse/jetty/server/nio/jmx/SelectChannelConnector-mbean.properties
+++ /dev/null
@@ -1,2 +0,0 @@
-SelectChannelConnector: HTTP connector using NIO ByteChannels and Selectors
-lowResourcesConnections: The number of connections, which if exceeded represents low resources
diff --git a/jetty-server/src/main/resources/org/eclipse/jetty/server/session/jmx/AbstractSessionManager-mbean.properties b/jetty-server/src/main/resources/org/eclipse/jetty/server/session/jmx/AbstractSessionManager-mbean.properties
deleted file mode 100644
index 92deb5f..0000000
--- a/jetty-server/src/main/resources/org/eclipse/jetty/server/session/jmx/AbstractSessionManager-mbean.properties
+++ /dev/null
@@ -1,19 +0,0 @@
-AbstractSessionManager: Abstract Session Manager
-httpOnly: True if cookies use the http only flag
-idManager: MObject:RO:The ID Manager instance
-maxCookieAge: if greater than zero, the time in seconds a session cookie will last for
-maxInactiveInterval: default maximum time in seconds a session may be idle
-refreshCookieAge: The time in seconds after which a session cookie is re-set
-secureCookies: If true, the secure cookie flag is set on session cookies
-sessionCookie: The set session cookie 
-sessionDomain: The domain of the session cookie or null for the default
-sessionPath: The path of the session cookie or null for the default
-sessionsTotal: The total number of sessions
-sessionIdPathParameterName: The name to use for URL session tracking
-statsReset(): Reset statistics
-sessions: Current instantaneous number of sessions
-sessionsMax: Maximum number of simultaneous sessions since statsReset() was called
-sessionTimeMax: Maximum amount of time in seconds session remained valid since statsReset() was called
-sessionTimeTotal: Total amount of time in seconds sessions remained valid since statsReset() was called
-sessionTimeMean: Mean amount of time in seconds  a session remained valid since statsReset() was called
-sessionTimeStdDev: Standard deviation of amount of time in seconds  a session remained valid since statsReset() was called
\ No newline at end of file
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/AbstractConnectorTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/AbstractConnectorTest.java
deleted file mode 100644
index a418c70..0000000
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/AbstractConnectorTest.java
+++ /dev/null
@@ -1,259 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.server;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.PrintWriter;
-import java.net.Socket;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.CyclicBarrier;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jetty.io.Connection;
-import org.eclipse.jetty.server.handler.AbstractHandler;
-import org.eclipse.jetty.server.handler.HandlerWrapper;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-import org.junit.AfterClass;
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.Test;
-
-public class AbstractConnectorTest
-{
-    private static final Logger LOG = Log.getLogger(AbstractConnectorTest.class);
-
-    private static Server _server;
-    private static AbstractConnector _connector;
-    private static CyclicBarrier _connect;
-    private static CountDownLatch _closed;
-
-    private Socket[] _socket;
-    private PrintWriter[] _out;
-    private BufferedReader[] _in;
-
-    @BeforeClass
-    public static void init() throws Exception
-    {
-        _connect = new CyclicBarrier(2);
-
-        _server = new Server();
-        _connector = new SelectChannelConnector()
-        {
-            public void connectionClosed(Connection connection)
-            {
-                super.connectionClosed(connection);
-                _closed.countDown();
-            }
-
-        };
-        _connector.setStatsOn(true);
-        _server.addConnector(_connector);
-
-        HandlerWrapper wrapper = new HandlerWrapper()
-        {
-            public void handle(String path, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException
-            {
-                try
-                {
-                    _connect.await();
-                 }
-                catch (Exception ex)
-                {
-                    LOG.debug(ex);
-                }
-                finally
-                {
-                    super.handle(path, request, httpRequest, httpResponse);
-                }
-            }
-        };
-        _server.setHandler(wrapper);
-
-        Handler handler = new AbstractHandler()
-        {
-            public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
-                throws IOException, ServletException
-            {
-                try{Thread.sleep(1);} catch(Exception e){}
-                baseRequest.setHandled(true);
-                PrintWriter out = response.getWriter();
-                out.write("Server response\n");
-                out.close();
-
-                response.setStatus(HttpServletResponse.SC_OK);
-            }
-        };
-        wrapper.setHandler(handler);
-
-        _server.start();
-    }
-
-    @AfterClass
-    public static void destroy() throws Exception
-    {
-        _server.stop();
-        _server.join();
-    }
-
-    @Before
-    public void reset()
-    {
-        _connector.statsReset();
-    }
-
-    @Test
-    public void testSingleRequest() throws Exception
-    {
-        doInit(1);
-
-        sendRequest(1, 1);
-
-        doClose(1);
-
-        assertEquals(1, _connector.getConnections());
-        assertEquals(0, _connector.getConnectionsOpen());
-        assertEquals(1, _connector.getConnectionsOpenMax());
-        assertTrue(_connector.getConnectionsOpen() <= _connector.getConnectionsOpenMax());
-
-        assertTrue(_connector.getConnectionsDurationMean() > 0);
-        assertTrue(_connector.getConnectionsDurationMax() > 0);
-        assertTrue(_connector.getConnectionsDurationMean() <= _connector.getConnectionsDurationMax());
-
-        assertEquals(1, _connector.getRequests());
-        assertEquals(1.0, _connector.getConnectionsRequestsMean(), 0.01);
-        assertEquals(1, _connector.getConnectionsRequestsMax());
-        assertTrue(_connector.getConnectionsRequestsMean() <= _connector.getConnectionsRequestsMax());
-    }
-
-    @Test
-    public void testMultipleRequests() throws Exception
-    {
-        doInit(1);
-
-        sendRequest(1, 1);
-
-        sendRequest(1, 1);
-
-        doClose(1);
-
-        assertEquals(1, _connector.getConnections());
-        assertEquals(0, _connector.getConnectionsOpen());
-        assertEquals(1, _connector.getConnectionsOpenMax());
-        assertTrue(_connector.getConnectionsOpen() <= _connector.getConnectionsOpenMax());
-
-        assertTrue(_connector.getConnectionsDurationMean() > 0);
-        assertTrue(_connector.getConnectionsDurationMax() > 0);
-        assertTrue(_connector.getConnectionsDurationMean() <= _connector.getConnectionsDurationMax());
-
-        assertEquals(2, _connector.getRequests());
-        assertEquals(2.0, _connector.getConnectionsRequestsMean(), 0.01);
-        assertEquals(2, _connector.getConnectionsRequestsMax());
-        assertTrue(_connector.getConnectionsRequestsMean() <= _connector.getConnectionsRequestsMax());
-    }
-
-    @Test
-    public void testMultipleConnections() throws Exception
-    {
-        doInit(3);
-
-        sendRequest(1, 1); // request 1 connection 1
-
-        sendRequest(2, 2); // request 1 connection 2
-
-        sendRequest(3, 3); // request 1 connection 3
-
-        sendRequest(2, 3); // request 2 connection 2
-
-        sendRequest(3, 3); // request 2 connection 3
-
-        sendRequest(3, 3); // request 3 connection 3
-
-        doClose(3);
-
-        assertEquals(3, _connector.getConnections());
-        assertEquals(0, _connector.getConnectionsOpen());
-        assertEquals(3, _connector.getConnectionsOpenMax());
-        assertTrue(_connector.getConnectionsOpen() <= _connector.getConnectionsOpenMax());
-
-        assertTrue(_connector.getConnectionsDurationMean() > 0);
-        assertTrue(_connector.getConnectionsDurationMax() > 0);
-        assertTrue(_connector.getConnectionsDurationMean() <= _connector.getConnectionsDurationMax());
-
-        assertEquals(6, _connector.getRequests());
-        assertEquals(2.0, _connector.getConnectionsRequestsMean(), 0.01);
-        assertEquals(3, _connector.getConnectionsRequestsMax());
-        assertTrue(_connector.getConnectionsRequestsMean() <= _connector.getConnectionsRequestsMax());
-    }
-
-    protected void doInit(int count)
-    {
-        _socket = new Socket[count];
-        _out = new PrintWriter[count];
-        _in = new BufferedReader[count];
-
-        _closed = new CountDownLatch(count);
-    }
-
-    private void doClose(int count) throws Exception
-    {
-        for (int idx=0; idx < count; idx++)
-        {
-            if (_socket[idx] != null)
-                _socket[idx].close();
-        }
-
-        _closed.await();
-    }
-
-    private void sendRequest(int id, int count) throws Exception
-    {
-        int idx = id - 1;
-
-        if (idx < 0)
-            throw new IllegalArgumentException("Connection ID <= 0");
-
-        _socket[idx]  = _socket[idx] == null ? new Socket("localhost", _connector.getLocalPort()) : _socket[idx];
-        _out[idx] = _out[idx] == null ? new PrintWriter(_socket[idx].getOutputStream(), true) : _out[idx];
-        _in[idx] = _in[idx] == null ? new BufferedReader(new InputStreamReader(_socket[idx].getInputStream())) : _in[idx];
-
-        _connect.reset();
-
-        _out[idx].write("GET / HTTP/1.1\r\nHost: localhost\r\n\r\n");
-        _out[idx].flush();
-
-        _connect.await();
-
-        assertEquals(count, _connector.getConnectionsOpen());
-
-        while(_in[idx].ready())
-        {
-            _in[idx].readLine();
-        }
-    }
-}
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/AbstractHttpTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/AbstractHttpTest.java
new file mode 100644
index 0000000..ae6a977
--- /dev/null
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/AbstractHttpTest.java
@@ -0,0 +1,144 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.net.Socket;
+import java.net.URISyntaxException;
+import java.nio.charset.StandardCharsets;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.io.ArrayByteBufferPool;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.toolchain.test.http.SimpleHttpParser;
+import org.eclipse.jetty.toolchain.test.http.SimpleHttpResponse;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.StdErrLog;
+import org.junit.After;
+import org.junit.Before;
+
+public abstract class AbstractHttpTest
+{
+    protected static Server server;
+    protected static ServerConnector connector;
+    protected String httpVersion;
+    protected SimpleHttpParser httpParser;
+
+    public AbstractHttpTest(String httpVersion)
+    {
+        this.httpVersion = httpVersion;
+    }
+
+    @Before
+    public void setUp() throws Exception
+    {
+        server = new Server();
+        connector = new ServerConnector(server,null,null,new ArrayByteBufferPool(64,2048,64*1024),1,1,new HttpConnectionFactory());
+        connector.setIdleTimeout(10000);
+        
+        server.addConnector(connector);
+        httpParser = new SimpleHttpParser();
+        ((StdErrLog)Log.getLogger(HttpChannel.class)).setHideStacks(true);
+    }
+
+    @After
+    public void tearDown() throws Exception
+    {
+        server.stop();
+        ((StdErrLog)Log.getLogger(HttpChannel.class)).setHideStacks(false);
+    }
+
+    protected SimpleHttpResponse executeRequest() throws URISyntaxException, IOException
+    {
+        Socket socket = new Socket("localhost", connector.getLocalPort());
+        socket.setSoTimeout((int)connector.getIdleTimeout());
+        BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8));
+        PrintWriter writer = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()));
+        String request = "GET / " + httpVersion + "\r\n";
+
+        writer.write(request);
+        writer.write("Host: localhost");
+        writer.println("\r\n");
+        writer.flush();
+
+        SimpleHttpResponse response = httpParser.readResponse(reader);
+        if ("HTTP/1.1".equals(httpVersion) && response.getHeaders().get("content-length") == null && response
+                .getHeaders().get("transfer-encoding") == null)
+            assertThat("If HTTP/1.1 response doesn't contain transfer-encoding or content-length headers, " +
+                    "it should contain connection:close", response.getHeaders().get("connection"), is("close"));
+        return response;
+    }
+
+    protected void assertResponseBody(SimpleHttpResponse response, String expectedResponseBody)
+    {
+        assertThat("response body is" + expectedResponseBody, response.getBody(), is(expectedResponseBody));
+    }
+
+    protected void assertHeader(SimpleHttpResponse response, String headerName, String expectedValue)
+    {
+        assertThat(headerName + "=" + expectedValue, response.getHeaders().get(headerName), is(expectedValue));
+    }
+
+    protected static class TestCommitException extends IllegalStateException
+    {
+        public TestCommitException()
+        {
+            super("Thrown by test");
+        }
+    }
+
+    protected class ThrowExceptionOnDemandHandler extends AbstractHandler
+    {
+        private final boolean throwException;
+        private volatile Throwable failure;
+
+        protected ThrowExceptionOnDemandHandler(boolean throwException)
+        {
+            this.throwException = throwException;
+        }
+
+        @Override
+        public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+        {
+            if (throwException)
+                throw new TestCommitException();
+        }
+
+        protected void markFailed(Throwable x)
+        {
+            this.failure = x;
+        }
+
+        public Throwable failure()
+        {
+            return failure;
+        }
+    }
+
+}
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/AsyncRequestReadTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/AsyncRequestReadTest.java
index 375efe1..9078f39 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/AsyncRequestReadTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/AsyncRequestReadTest.java
@@ -18,163 +18,183 @@
 
 package org.eclipse.jetty.server;
 
+import static org.hamcrest.Matchers.containsString;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
 
+import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.InputStreamReader;
 import java.io.OutputStream;
+import java.io.PrintWriter;
 import java.net.Socket;
+import java.nio.charset.StandardCharsets;
 import java.util.Arrays;
-import java.util.concurrent.Exchanger;
+import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.TimeUnit;
 
+import javax.servlet.AsyncContext;
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import org.eclipse.jetty.continuation.Continuation;
-import org.eclipse.jetty.continuation.ContinuationSupport;
 import org.eclipse.jetty.server.handler.AbstractHandler;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
+import org.eclipse.jetty.util.BlockingArrayQueue;
 import org.eclipse.jetty.util.IO;
-import org.eclipse.jetty.util.StringUtil;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.Ignore;
+import org.junit.After;
+import org.junit.Before;
 import org.junit.Test;
 
 public class AsyncRequestReadTest
 {
     private static Server server;
-    private static Connector connector;
-    private final static Exchanger<Long> __total=new Exchanger<Long>();
+    private static ServerConnector connector;
+    private final static BlockingQueue<Long> __total=new BlockingArrayQueue<>();
 
-    @BeforeClass
-    public static void startServer() throws Exception
+    @Before
+    public void startServer() throws Exception
     {
         server = new Server();
-        connector = new SelectChannelConnector();
-        connector.setMaxIdleTime(10000);
+        connector = new ServerConnector(server);
+        connector.setIdleTimeout(10000);
+        connector.getConnectionFactory(HttpConnectionFactory.class).getHttpConfiguration().setSendDateHeader(false);
         server.addConnector(connector);
-        server.setHandler(new EmptyHandler());
-        server.start();
     }
 
-    @AfterClass
-    public static void stopServer() throws Exception
+    @After
+    public void stopServer() throws Exception
     {
         server.stop();
         server.join();
     }
-    
+
     @Test
-    @Ignore
-    public void test() throws Exception
+    public void testPipelined() throws Exception
     {
-        final Socket socket =  new Socket("localhost",connector.getLocalPort());
-
-        byte[] content = new byte[16*4096];
-        Arrays.fill(content, (byte)120);
-
-        OutputStream out = socket.getOutputStream();
-        String header=
-            "POST / HTTP/1.1\r\n"+
-            "Host: localhost\r\n"+
-            "Content-Length: "+content.length+"\r\n"+
-            "Content-Type: bytes\r\n"+
-            "Connection: close\r\n"+
-            "\r\n";
-        byte[] h=header.getBytes(StringUtil.__ISO_8859_1);
+        server.setHandler(new AsyncStreamHandler());
+        server.start();
+        
+        try (final Socket socket =  new Socket("localhost",connector.getLocalPort()))
+        {
+            socket.setSoTimeout(1000);
             
-        out.write(h);
-        out.flush();
-
-        out.write(content,0,4*4096);
-        Thread.sleep(100);
-        out.write(content,8192,4*4096);
-        Thread.sleep(100);
-        out.write(content,8*4096,content.length-8*4096);
+            byte[] content = new byte[32*4096];
+            Arrays.fill(content, (byte)120);
 
-        out.flush();
+            OutputStream out = socket.getOutputStream();
+            String header=
+                "POST / HTTP/1.1\r\n"+
+                    "Host: localhost\r\n"+
+                    "Content-Length: "+content.length+"\r\n"+
+                    "Content-Type: bytes\r\n"+
+                    "\r\n";
+            byte[] h=header.getBytes(StandardCharsets.ISO_8859_1);
+            out.write(h);
+            out.write(content);
+            
+            
+            header=
+                "POST / HTTP/1.1\r\n"+
+                    "Host: localhost\r\n"+
+                    "Content-Length: "+content.length+"\r\n"+
+                    "Content-Type: bytes\r\n"+
+                    "Connection: close\r\n"+
+                    "\r\n";
+            h=header.getBytes(StandardCharsets.ISO_8859_1);
+            out.write(h);
+            out.write(content);
+            out.flush();
 
-        InputStream in = socket.getInputStream();
-        String response = IO.toString(in);
-        assertTrue(response.indexOf("200 OK")>0);
+            InputStream in = socket.getInputStream();
+            String response = IO.toString(in);
+            assertTrue(response.indexOf("200 OK")>0);
 
-        long total=__total.exchange(0L,30,TimeUnit.SECONDS);
-        assertEquals(content.length, total);
+            long total=__total.poll(5,TimeUnit.SECONDS);
+            assertEquals(content.length, total);
+            total=__total.poll(5,TimeUnit.SECONDS);
+            assertEquals(content.length, total);
+        }
     }
-    
+
     @Test
-    @Ignore
-    public void tests() throws Exception
+    public void testAsyncReadsWithDelays() throws Exception
     {
-        runTest(64,4,4,20);
-        runTest(256,16,16,50);
-        runTest(256,1,128,10);
-        runTest(128*1024,1,64,10);
-        runTest(256*1024,5321,10,100);
-        runTest(512*1024,32*1024,10,10);
+        server.setHandler(new AsyncStreamHandler());
+        server.start();
+        
+        asyncReadTest(64,4,4,20);
+        asyncReadTest(256,16,16,50);
+        asyncReadTest(256,1,128,10);
+        asyncReadTest(128*1024,1,64,10);
+        asyncReadTest(256*1024,5321,10,100);
+        asyncReadTest(512*1024,32*1024,10,10);
     }
-    
-    
-    public void runTest(int contentSize, int chunkSize, int chunks, int delayMS) throws Exception
+
+
+    public void asyncReadTest(int contentSize, int chunkSize, int chunks, int delayMS) throws Exception
     {
         String tst=contentSize+","+chunkSize+","+chunks+","+delayMS;
         //System.err.println(tst);
-        
-        final Socket socket =  new Socket("localhost",connector.getLocalPort());
-
-        byte[] content = new byte[contentSize];
-        Arrays.fill(content, (byte)120);
-
-        OutputStream out = socket.getOutputStream();
-        out.write("POST / HTTP/1.1\r\n".getBytes());
-        out.write("Host: localhost\r\n".getBytes());
-        out.write(("Content-Length: "+content.length+"\r\n").getBytes());
-        out.write("Content-Type: bytes\r\n".getBytes());
-        out.write("Connection: close\r\n".getBytes());
-        out.write("\r\n".getBytes());
-        out.flush();
-
-        int offset=0;
-        for (int i=0;i<chunks;i++)
+
+        try(final Socket socket =  new Socket("localhost",connector.getLocalPort()))
         {
-            out.write(content,offset,chunkSize);
-            offset+=chunkSize;
-            Thread.sleep(delayMS);
-        }
-        out.write(content,offset,content.length-offset);
 
-        out.flush();
+            byte[] content = new byte[contentSize];
+            Arrays.fill(content, (byte)120);
+
+            OutputStream out = socket.getOutputStream();
+            out.write("POST / HTTP/1.1\r\n".getBytes());
+            out.write("Host: localhost\r\n".getBytes());
+            out.write(("Content-Length: "+content.length+"\r\n").getBytes());
+            out.write("Content-Type: bytes\r\n".getBytes());
+            out.write("Connection: close\r\n".getBytes());
+            out.write("\r\n".getBytes());
+            out.flush();
+
+            int offset=0;
+            for (int i=0;i<chunks;i++)
+            {
+                out.write(content,offset,chunkSize);
+                offset+=chunkSize;
+                Thread.sleep(delayMS);
+            }
+            out.write(content,offset,content.length-offset);
 
-        InputStream in = socket.getInputStream();
-        String response = IO.toString(in);
-        assertTrue(tst,response.indexOf("200 OK")>0);
+            out.flush();
 
-        long total=__total.exchange(0L,30,TimeUnit.SECONDS);
-        assertEquals(tst,content.length, total);
+            InputStream in = socket.getInputStream();
+            String response = IO.toString(in);
+            assertTrue(tst,response.indexOf("200 OK")>0);
+
+            long total=__total.poll(30,TimeUnit.SECONDS);
+            assertEquals(tst,content.length, total);
+        }
     }
 
-    
-    private static class EmptyHandler extends AbstractHandler
+
+    private static class AsyncStreamHandler extends AbstractHandler
     {
+        @Override
         public void handle(String path, final Request request, HttpServletRequest httpRequest, final HttpServletResponse httpResponse) throws IOException, ServletException
         {
-            final Continuation continuation = ContinuationSupport.getContinuation(request);
             httpResponse.setStatus(500);
             request.setHandled(true);
 
+            final AsyncContext async = request.startAsync();
+            // System.err.println("handle "+request.getContentLength());
+            
             new Thread()
             {
                 @Override
                 public void run()
                 {
                     long total=0;
-                    try
+                    try(InputStream in = request.getInputStream();)
                     {
-                        InputStream in = request.getInputStream();
+                        // System.err.println("reading...");
+                        
                         byte[] b = new byte[4*4096];
                         int read;
                         while((read =in.read(b))>=0)
@@ -188,20 +208,157 @@ public class AsyncRequestReadTest
                     finally
                     {
                         httpResponse.setStatus(200);
-                        continuation.complete();
-                        try
-                        {
-                            __total.exchange(total);
-                        }
-                        catch (InterruptedException e)
-                        {
-                            e.printStackTrace();
-                        }
+                        async.complete();
+                        // System.err.println("read "+total);
+                        __total.offer(total);
                     }
                 }
             }.start();
+        }
+    }
+    
 
-            continuation.suspend();
+    @Test
+    public void testPartialRead() throws Exception
+    {
+        server.setHandler(new PartialReaderHandler());
+        server.start();
+        
+        try (final Socket socket =  new Socket("localhost",connector.getLocalPort()))
+        {
+            socket.setSoTimeout(1000);
+            
+            byte[] content = new byte[32*4096];
+            Arrays.fill(content, (byte)88);
+
+            OutputStream out = socket.getOutputStream();
+            String header=
+                "POST /?read=10 HTTP/1.1\r\n"+
+                    "Host: localhost\r\n"+
+                    "Content-Length: "+content.length+"\r\n"+
+                    "Content-Type: bytes\r\n"+
+                    "\r\n";
+            byte[] h=header.getBytes(StandardCharsets.ISO_8859_1);
+            out.write(h);
+            out.write(content);
+            
+            header= "POST /?read=10 HTTP/1.1\r\n"+
+                    "Host: localhost\r\n"+
+                    "Content-Length: "+content.length+"\r\n"+
+                    "Content-Type: bytes\r\n"+
+                    "Connection: close\r\n"+
+                    "\r\n";
+            h=header.getBytes(StandardCharsets.ISO_8859_1);
+            out.write(h);
+            out.write(content);
+            out.flush();
+            
+            BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
+            assertThat(in.readLine(),containsString("HTTP/1.1 200 OK"));
+            assertThat(in.readLine(),containsString("Content-Length:"));
+            assertThat(in.readLine(),containsString("Server:"));
+            in.readLine();
+            assertThat(in.readLine(),containsString("XXXXXXX"));
+            assertThat(in.readLine(),containsString("HTTP/1.1 200 OK"));
+            assertThat(in.readLine(),containsString("Connection: close"));
+            assertThat(in.readLine(),containsString("Server:"));
+            in.readLine();
+            assertThat(in.readLine(),containsString("XXXXXXX"));
+
+        }
+    }
+
+    @Test
+    public void testPartialReadThenShutdown() throws Exception
+    {
+        server.setHandler(new PartialReaderHandler());
+        server.start();
+        
+        try (final Socket socket =  new Socket("localhost",connector.getLocalPort()))
+        {
+            socket.setSoTimeout(10000);
+            
+            byte[] content = new byte[32*4096];
+            Arrays.fill(content, (byte)88);
+
+            OutputStream out = socket.getOutputStream();
+            String header=
+                "POST /?read=10 HTTP/1.1\r\n"+
+                    "Host: localhost\r\n"+
+                    "Content-Length: "+content.length+"\r\n"+
+                    "Content-Type: bytes\r\n"+
+                    "\r\n";
+            byte[] h=header.getBytes(StandardCharsets.ISO_8859_1);
+            out.write(h);
+            out.write(content,0,4096);
+            out.flush();
+            socket.shutdownOutput();
+
+            BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
+            assertThat(in.readLine(),containsString("HTTP/1.1 200 OK"));
+            assertThat(in.readLine(),containsString("Content-Length:"));
+            assertThat(in.readLine(),containsString("Server:"));
+            in.readLine();
+            assertThat(in.readLine(),containsString("XXXXXXX"));
+        }
+    }
+
+    @Test
+    public void testPartialReadThenClose() throws Exception
+    {
+        server.setHandler(new PartialReaderHandler());
+        server.start();
+        
+        try (final Socket socket =  new Socket("localhost",connector.getLocalPort()))
+        {
+            socket.setSoTimeout(1000);
+            
+            byte[] content = new byte[32*4096];
+            Arrays.fill(content, (byte)88);
+
+            OutputStream out = socket.getOutputStream();
+            String header=
+                "POST /?read=10 HTTP/1.1\r\n"+
+                    "Host: localhost\r\n"+
+                    "Content-Length: "+content.length+"\r\n"+
+                    "Content-Type: bytes\r\n"+
+                    "\r\n";
+            byte[] h=header.getBytes(StandardCharsets.ISO_8859_1);
+            out.write(h);
+            out.write(content,0,4096);
+            out.flush();
+
+            BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
+            assertThat(in.readLine(),containsString("HTTP/1.1 200 OK"));
+            assertThat(in.readLine(),containsString("Content-Length:"));
+            assertThat(in.readLine(),containsString("Server:"));
+            in.readLine();
+            assertThat(in.readLine(),containsString("XXXXXXX"));
+            
+            socket.close();
+        }
+    }
+
+    private static class PartialReaderHandler extends AbstractHandler
+    {
+        @Override
+        public void handle(String path, final Request request, HttpServletRequest httpRequest, final HttpServletResponse httpResponse) throws IOException, ServletException
+        {
+            httpResponse.setStatus(200);
+            request.setHandled(true);
+                        
+            BufferedReader in = request.getReader();
+            PrintWriter out =httpResponse.getWriter();
+            int read=Integer.valueOf(request.getParameter("read"));
+            // System.err.println("read="+read);
+            for (int i=read;i-->0;)
+            {
+                int c=in.read();
+                if (c<0)
+                    break;
+                out.write(c);
+            }
+            out.println();
         }
     }
 }
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/AsyncStressTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/AsyncStressTest.java
index 8e0b14d..7c3f4c9 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/AsyncStressTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/AsyncStressTest.java
@@ -18,15 +18,17 @@
 
 package org.eclipse.jetty.server;
 
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
 
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.InetAddress;
 import java.net.Socket;
+import java.nio.charset.StandardCharsets;
 import java.util.Random;
 import java.util.Timer;
 import java.util.TimerTask;
+
 import javax.servlet.AsyncContext;
 import javax.servlet.AsyncEvent;
 import javax.servlet.AsyncListener;
@@ -35,11 +37,10 @@ import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import org.eclipse.jetty.continuation.Continuation;
-import org.eclipse.jetty.continuation.ContinuationListener;
 import org.eclipse.jetty.server.handler.HandlerWrapper;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
+import org.eclipse.jetty.toolchain.test.AdvancedRunner;
 import org.eclipse.jetty.toolchain.test.PropertyFlag;
+import org.eclipse.jetty.toolchain.test.annotation.Stress;
 import org.eclipse.jetty.util.IO;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
@@ -47,18 +48,20 @@ import org.eclipse.jetty.util.thread.QueuedThreadPool;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 
+ at RunWith(AdvancedRunner.class)
 public class AsyncStressTest
 {
     private static final Logger LOG = Log.getLogger(AsyncStressTest.class);
 
-    protected Server _server = new Server();
+    protected QueuedThreadPool _threads=new QueuedThreadPool();
+    protected Server _server = new Server(_threads);
     protected SuspendHandler _handler = new SuspendHandler();
-    protected SelectChannelConnector _connector;
+    protected ServerConnector _connector;
     protected InetAddress _addr;
     protected int _port;
     protected Random _random = new Random();
-    protected QueuedThreadPool _threads=new QueuedThreadPool();
     private final static String[][] __paths =
     {
         {"/path","NORMAL"},
@@ -72,10 +75,10 @@ public class AsyncStressTest
     @Before
     public void init() throws Exception
     {
+        _server.manage(_threads);
         _threads.setMaxThreads(50);
-        _server.setThreadPool(_threads);
-        _connector = new SelectChannelConnector();
-        _connector.setMaxIdleTime(120000);
+        _connector = new ServerConnector(_server);
+        _connector.setIdleTimeout(120000);
         _server.setConnectors(new Connector[]{ _connector });
         _server.setHandler(_handler);
         _server.start();
@@ -91,6 +94,7 @@ public class AsyncStressTest
     }
 
     @Test
+    @Stress("High connection count")
     public void testAsync() throws Throwable
     {
         if (PropertyFlag.isEnabled("test.stress"))
@@ -137,7 +141,7 @@ public class AsyncStressTest
                                 "result: "+__paths[p][1]+"\r\n"+
                                 ((l+1<loops)?"":"Connection: close\r\n")+
                                 "\r\n";
-                socket[i].getOutputStream().write(request.getBytes("UTF-8"));
+                socket[i].getOutputStream().write(request.getBytes(StandardCharsets.UTF_8));
                 socket[i].getOutputStream().flush();
             }
             if (l%80==0)
@@ -152,7 +156,7 @@ public class AsyncStressTest
         String[] results=new String[connections];
         for (int i=0;i<connections;i++)
         {
-            results[i]=IO.toString(socket[i].getInputStream(),"UTF-8");
+            results[i]=IO.toString(socket[i].getInputStream(),StandardCharsets.UTF_8);
             if (i%80==0)
                 System.err.println();
             System.err.print('-');
@@ -252,7 +256,7 @@ public class AsyncStressTest
                                     System.err.println("\n"+e.toString());
                                     System.err.println(baseRequest+"=="+br);
                                     System.err.println(uri+"=="+br.getUri());
-                                    System.err.println(asyncContext+"=="+br.getAsyncContinuation());
+                                    System.err.println(asyncContext+"=="+br.getHttpChannelState());
 
                                     LOG.warn(e);
                                     System.exit(1);
@@ -327,8 +331,8 @@ public class AsyncStressTest
             }
         }
     }
-    
-    
+
+
     private static AsyncListener __asyncListener = new AsyncListener()
     {
         @Override
@@ -346,7 +350,7 @@ public class AsyncStressTest
         @Override
         public void onError(AsyncEvent event) throws IOException
         {
-            
+
         }
 
         @Override
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/BlockingChannelCloseTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/BlockingChannelCloseTest.java
deleted file mode 100644
index 424384c..0000000
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/BlockingChannelCloseTest.java
+++ /dev/null
@@ -1,34 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.server;
-
-import org.eclipse.jetty.server.nio.BlockingChannelConnector;
-import org.junit.BeforeClass;
-
-/**
- * HttpServer Tester.
- */
-public class BlockingChannelCloseTest extends HttpServerTestBase
-{
-    @BeforeClass
-    public static void init() throws Exception
-    {
-        startServer(new BlockingChannelConnector());
-    }
-}
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/BlockingChannelServerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/BlockingChannelServerTest.java
deleted file mode 100644
index ceb9006..0000000
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/BlockingChannelServerTest.java
+++ /dev/null
@@ -1,34 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.server;
-
-import org.eclipse.jetty.server.nio.BlockingChannelConnector;
-import org.junit.BeforeClass;
-
-/**
- * HttpServer Tester.
- */
-public class BlockingChannelServerTest extends HttpServerTestBase
-{
-    @BeforeClass
-    public static void init() throws Exception
-    {
-        startServer(new BlockingChannelConnector());
-    }
-}
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/BlockingChannelTimeoutTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/BlockingChannelTimeoutTest.java
deleted file mode 100644
index 45c7c81..0000000
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/BlockingChannelTimeoutTest.java
+++ /dev/null
@@ -1,46 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.server;
-
-import org.eclipse.jetty.server.nio.BlockingChannelConnector;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-import org.junit.BeforeClass;
-import org.junit.Test;
-
-public class BlockingChannelTimeoutTest extends ConnectorTimeoutTest
-{
-    private static final Logger LOG = Log.getLogger(BlockingChannelTimeoutTest.class);
-
-   
-    @BeforeClass
-    public static void init() throws Exception
-    {
-        BlockingChannelConnector connector = new BlockingChannelConnector();
-        connector.setMaxIdleTime(MAX_IDLE_TIME); //250 msec max idle
-   
-        startServer(connector);
-    }
-    @Test
-    public void testMaxIdleWithWait() throws Exception
-    {  
-        // TODO
-        LOG.warn("skipped BlockingChannelTimeoutTest#testMaxIdleWithWait");
-    }
-}
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/CheckReverseProxyHeadersTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/CheckReverseProxyHeadersTest.java
index 76bed3e..f73c92b 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/CheckReverseProxyHeadersTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/CheckReverseProxyHeadersTest.java
@@ -23,6 +23,7 @@ import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 import java.io.IOException;
+import java.util.concurrent.TimeUnit;
 
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
@@ -44,6 +45,7 @@ public class CheckReverseProxyHeadersTest
                     "X-Forwarded-For: 10.20.30.40\n" +
                     "X-Forwarded-Host: example.com", new RequestValidator()
         {
+            @Override
             public void validate(HttpServletRequest request)
             {
                 assertEquals("example.com", request.getServerName());
@@ -55,6 +57,42 @@ public class CheckReverseProxyHeadersTest
                 assertFalse(request.isSecure());
             }
         });
+        
+        // IPv6 ProxyPass from example.com:80 to localhost:8080
+        testRequest("Host: localhost:8080\n" +
+                    "X-Forwarded-For: 10.20.30.40\n" +
+                    "X-Forwarded-Host: [::1]", new RequestValidator()
+        {
+            @Override
+            public void validate(HttpServletRequest request)
+            {
+                assertEquals("[::1]", request.getServerName());
+                assertEquals(80, request.getServerPort());
+                assertEquals("10.20.30.40", request.getRemoteAddr());
+                assertEquals("10.20.30.40", request.getRemoteHost());
+                assertEquals("[::1]", request.getHeader("Host"));
+                assertEquals("http",request.getScheme());
+                assertFalse(request.isSecure());
+            }
+        });
+        
+        // IPv6 ProxyPass from example.com:80 to localhost:8080
+        testRequest("Host: localhost:8080\n" +
+                    "X-Forwarded-For: 10.20.30.40\n" +
+                    "X-Forwarded-Host: [::1]:8888", new RequestValidator()
+        {
+            @Override
+            public void validate(HttpServletRequest request)
+            {
+                assertEquals("[::1]", request.getServerName());
+                assertEquals(8888, request.getServerPort());
+                assertEquals("10.20.30.40", request.getRemoteAddr());
+                assertEquals("10.20.30.40", request.getRemoteHost());
+                assertEquals("[::1]:8888", request.getHeader("Host"));
+                assertEquals("http",request.getScheme());
+                assertFalse(request.isSecure());
+            }
+        });
 
         // ProxyPass from example.com:81 to localhost:8080
         testRequest("Host: localhost:8080\n" +
@@ -63,6 +101,7 @@ public class CheckReverseProxyHeadersTest
                     "X-Forwarded-Server: example.com\n"+
                     "X-Forwarded-Proto: https", new RequestValidator()
         {
+            @Override
             public void validate(HttpServletRequest request)
             {
                 assertEquals("example.com", request.getServerName());
@@ -82,6 +121,7 @@ public class CheckReverseProxyHeadersTest
                     "X-Forwarded-Server: example.com, rp.example.com\n"+
                     "X-Forwarded-Proto: https, http", new RequestValidator()
         {
+            @Override
             public void validate(HttpServletRequest request)
             {
                 assertEquals("example.com", request.getServerName());
@@ -98,10 +138,11 @@ public class CheckReverseProxyHeadersTest
     private void testRequest(String headers, RequestValidator requestValidator) throws Exception
     {
         Server server = new Server();
-        LocalConnector connector = new LocalConnector();
-
         // Activate reverse proxy headers checking
-        connector.setForwarded(true);
+        HttpConnectionFactory http = new HttpConnectionFactory();
+        http.getHttpConfiguration().addCustomizer(new ForwardedRequestCustomizer());
+
+        LocalConnector connector = new LocalConnector(server,http);
 
         server.setConnectors(new Connector[] {connector});
         ValidationHandler validationHandler = new ValidationHandler(requestValidator);
@@ -110,7 +151,8 @@ public class CheckReverseProxyHeadersTest
         try
         {
             server.start();
-            connector.getResponses("GET / HTTP/1.1\n" + headers + "\n\n");
+            connector.getResponses("GET / HTTP/1.1\r\n" +"Connection: close\r\n" + headers + "\r\n\r\n",
+                1000,TimeUnit.SECONDS);
 
             Error error = validationHandler.getError();
 
@@ -159,6 +201,7 @@ public class CheckReverseProxyHeadersTest
             return _error;
         }
 
+        @Override
         public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
         {
             try
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ConnectionOpenCloseTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ConnectionOpenCloseTest.java
new file mode 100644
index 0000000..e56b4b0
--- /dev/null
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ConnectionOpenCloseTest.java
@@ -0,0 +1,235 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.Socket;
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.eclipse.jetty.toolchain.test.annotation.Slow;
+import org.eclipse.jetty.toolchain.test.http.SimpleHttpResponse;
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class ConnectionOpenCloseTest extends AbstractHttpTest
+{
+    public ConnectionOpenCloseTest()
+    {
+        super(HttpVersion.HTTP_1_1.asString());
+    }
+    
+    @Slow
+    @Test
+    public void testOpenClose() throws Exception
+    {
+        server.setHandler(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                throw new IllegalStateException();
+            }
+        });
+        server.start();
+
+        final AtomicInteger callbacks = new AtomicInteger();
+        final CountDownLatch openLatch = new CountDownLatch(1);
+        final CountDownLatch closeLatch = new CountDownLatch(1);
+        connector.addBean(new Connection.Listener.Adapter()
+        {
+            @Override
+            public void onOpened(Connection connection)
+            {
+                callbacks.incrementAndGet();
+                openLatch.countDown();
+            }
+
+            @Override
+            public void onClosed(Connection connection)
+            {
+                callbacks.incrementAndGet();
+                closeLatch.countDown();
+            }
+        });
+
+        try (Socket socket = new Socket("localhost", connector.getLocalPort());)
+        {
+            socket.setSoTimeout((int)connector.getIdleTimeout());
+
+            Assert.assertTrue(openLatch.await(5, TimeUnit.SECONDS));
+            socket.shutdownOutput();
+            Assert.assertTrue(closeLatch.await(5, TimeUnit.SECONDS));
+            String response=IO.toString(socket.getInputStream());
+            Assert.assertEquals(0,response.length());
+
+            // Wait some time to see if the callbacks are called too many times
+            TimeUnit.MILLISECONDS.sleep(200);
+            Assert.assertEquals(2, callbacks.get());
+        }
+    }
+    
+    @Slow
+    @Test
+    public void testOpenRequestClose() throws Exception
+    {
+        server.setHandler(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+            }
+        });
+        server.start();
+
+        final AtomicInteger callbacks = new AtomicInteger();
+        final CountDownLatch openLatch = new CountDownLatch(1);
+        final CountDownLatch closeLatch = new CountDownLatch(1);
+        connector.addBean(new Connection.Listener.Adapter()
+        {
+            @Override
+            public void onOpened(Connection connection)
+            {
+                callbacks.incrementAndGet();
+                openLatch.countDown();
+            }
+
+            @Override
+            public void onClosed(Connection connection)
+            {
+                callbacks.incrementAndGet();
+                closeLatch.countDown();
+            }
+        });
+
+        Socket socket = new Socket("localhost", connector.getLocalPort());
+        socket.setSoTimeout((int)connector.getIdleTimeout());
+        OutputStream output = socket.getOutputStream();
+        output.write((
+                "GET / HTTP/1.1\r\n" +
+                "Host: localhost:" + connector.getLocalPort() + "\r\n" +
+                "Connection: close\r\n" +
+                "\r\n").getBytes(StandardCharsets.UTF_8));
+        output.flush();
+
+        BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
+        SimpleHttpResponse response = httpParser.readResponse(reader);
+        Assert.assertEquals("200", response.getCode());
+
+        Assert.assertEquals(-1, reader.read());
+        socket.close();
+
+        Assert.assertTrue(openLatch.await(5, TimeUnit.SECONDS));
+        Assert.assertTrue(closeLatch.await(5, TimeUnit.SECONDS));
+
+        // Wait some time to see if the callbacks are called too many times
+        TimeUnit.SECONDS.sleep(1);
+
+        Assert.assertEquals(2, callbacks.get());
+    }
+
+    @Slow
+    @Test
+    public void testSSLOpenRequestClose() throws Exception
+    {
+        SslContextFactory sslContextFactory = new SslContextFactory();
+        File keystore = MavenTestingUtils.getTestResourceFile("keystore");
+        sslContextFactory.setKeyStoreResource(Resource.newResource(keystore));
+        sslContextFactory.setKeyStorePassword("storepwd");
+        sslContextFactory.setKeyManagerPassword("keypwd");
+        server.addBean(sslContextFactory);
+
+        server.removeConnector(connector);
+        connector = new ServerConnector(server, sslContextFactory);
+        server.addConnector(connector);
+
+        server.setHandler(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+            }
+        });
+        server.start();
+
+        final AtomicInteger callbacks = new AtomicInteger();
+        final CountDownLatch openLatch = new CountDownLatch(1);
+        final CountDownLatch closeLatch = new CountDownLatch(1);
+        connector.addBean(new Connection.Listener.Adapter()
+        {
+            @Override
+            public void onOpened(Connection connection)
+            {
+                callbacks.incrementAndGet();
+                openLatch.countDown();
+            }
+
+            @Override
+            public void onClosed(Connection connection)
+            {
+                callbacks.incrementAndGet();
+                closeLatch.countDown();
+            }
+        });
+
+        Socket socket = sslContextFactory.getSslContext().getSocketFactory().createSocket("localhost", connector.getLocalPort());
+        socket.setSoTimeout((int)connector.getIdleTimeout());
+        OutputStream output = socket.getOutputStream();
+        output.write(("" +
+                "GET / HTTP/1.1\r\n" +
+                "Host: localhost:" + connector.getLocalPort() + "\r\n" +
+                "Connection: close\r\n" +
+                "\r\n").getBytes(StandardCharsets.UTF_8));
+        output.flush();
+
+        BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
+        SimpleHttpResponse response = httpParser.readResponse(reader);
+        Assert.assertEquals("200", response.getCode());
+
+        Assert.assertEquals(-1, reader.read());
+        socket.close();
+
+        Assert.assertTrue(openLatch.await(5, TimeUnit.SECONDS));
+        Assert.assertTrue(closeLatch.await(5, TimeUnit.SECONDS));
+
+        // Wait some time to see if the callbacks are called too many times
+        TimeUnit.SECONDS.sleep(1);
+
+        Assert.assertEquals(4, callbacks.get());
+    }
+}
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ConnectorCloseTestBase.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ConnectorCloseTestBase.java
index 49ace52..6201c80 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/ConnectorCloseTestBase.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ConnectorCloseTestBase.java
@@ -17,7 +17,7 @@
 //
 
 package org.eclipse.jetty.server;
-import static org.junit.Assert.assertEquals;
+
 import static org.junit.Assert.assertTrue;
 
 import java.io.BufferedReader;
@@ -25,6 +25,8 @@ import java.io.IOException;
 import java.io.InputStreamReader;
 import java.io.OutputStream;
 import java.net.Socket;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
@@ -49,25 +51,25 @@ public abstract class ConnectorCloseTestBase extends HttpServerTestFixture
         "Aliquam purus mauris, consectetur nec convallis lacinia, porta sed ante. Suspendisse "+
         "et cursus magna. Donec orci enim, molestie a lobortis eu, imperdiet vitae neque.";
     private static int __length = __content.length();
-    
+
     /* ------------------------------------------------------------ */
     @Test
     public void testCloseBetweenRequests() throws Exception
     {
-        int maxLength = 32;
-        int requestCount = iterations(maxLength);
+        final int requestCount = 32;
         final CountDownLatch latch = new CountDownLatch(requestCount);
-        
+
         configureServer(new HelloWorldHandler());
 
-        Socket client = newSocket(HOST,_connector.getLocalPort());
+        URI uri = _server.getURI();
+        Socket client = newSocket(uri.getHost(),uri.getPort());
         try
         {
             OutputStream os = client.getOutputStream();
 
             ResponseReader reader = new ResponseReader(client) {
                 private int _index = 0;
-                
+
                 /* ------------------------------------------------------------ */
                 @Override
                 protected int doRead() throws IOException, InterruptedException
@@ -82,37 +84,38 @@ public abstract class ConnectorCloseTestBase extends HttpServerTestFixture
                             _index = idx + 15;
                         }
                     }
-                    
+
                     return count;
                 }
             };
-            
+
             Thread runner = new Thread(reader);
             runner.start();
 
-            for (int pipeline = 1; pipeline < maxLength; pipeline++)
+            for (int pipeline = 1; pipeline <= requestCount; pipeline++)
             {
-                if (pipeline == maxLength / 2)
+                if (pipeline == requestCount / 2)
+                {
+                    // wait for at least 1 request to have been received
+                    if (latch.getCount()==requestCount)
+                        Thread.sleep(1);
                     _connector.close();
+                }
 
-                String request = "";
-                for (int i = 0; i < pipeline; i++)
-                {
-                    request +=
-                        "GET /data?writes=1&block=16&id="+i+" HTTP/1.1\r\n"+
-                        "host: "+HOST+":"+_connector.getLocalPort()+"\r\n"+
+                String request =
+                        "GET /data?writes=1&block=16&id="+pipeline+" HTTP/1.1\r\n"+
+                        "host: "+uri.getHost()+":"+uri.getPort()+"\r\n"+
                         "user-agent: testharness/1.0 (blah foo/bar)\r\n"+
                         "accept-encoding: nothing\r\n"+
                         "cookie: aaa=1234567890\r\n"+
                         "\r\n";
-                }
                 os.write(request.getBytes());
                 os.flush();
 
                 Thread.sleep(25);
             }
-            
-            latch.await(30, TimeUnit.SECONDS);
+
+            assertTrue(latch.await(5, TimeUnit.SECONDS));
 
             reader.setDone();
             runner.join();
@@ -120,8 +123,6 @@ public abstract class ConnectorCloseTestBase extends HttpServerTestFixture
         finally
         {
             client.close();
-
-            assertEquals(requestCount, requestCount - latch.getCount());
         }
     }
 
@@ -137,7 +138,8 @@ public abstract class ConnectorCloseTestBase extends HttpServerTestFixture
     {
         configureServer(new EchoHandler());
 
-        Socket client = newSocket(HOST,_connector.getLocalPort());
+        URI uri = _server.getURI();
+        Socket client = newSocket(uri.getHost(),uri.getPort());
         try
         {
             OutputStream os = client.getOutputStream();
@@ -147,14 +149,14 @@ public abstract class ConnectorCloseTestBase extends HttpServerTestFixture
             runner.start();
 
             byte[] bytes = __content.getBytes("utf-8");
-            
+
             os.write((
                 "POST /echo?charset=utf-8 HTTP/1.1\r\n"+
-                "host: "+HOST+":"+_connector.getLocalPort()+"\r\n"+
+                "host: "+uri.getHost()+":"+uri.getPort()+"\r\n"+
                 "content-type: text/plain; charset=utf-8\r\n"+
                 "content-length: "+bytes.length+"\r\n"+
                 "\r\n"
-            ).getBytes("iso-8859-1"));
+            ).getBytes(StandardCharsets.ISO_8859_1));
 
             int len = bytes.length;
             int offset = 0;
@@ -165,7 +167,7 @@ public abstract class ConnectorCloseTestBase extends HttpServerTestFixture
                 offset += 64;
                 Thread.sleep(25);
             }
-            
+
             _connector.close();
 
             while (offset < len)
@@ -178,7 +180,7 @@ public abstract class ConnectorCloseTestBase extends HttpServerTestFixture
 
             reader.setDone();
             runner.join();
-            
+
             String in = reader.getResponse().toString();
             assertTrue(in.indexOf(__content.substring(__length-64))>0);
         }
@@ -197,7 +199,7 @@ public abstract class ConnectorCloseTestBase extends HttpServerTestFixture
         protected char[] _buffer;
         protected StringBuffer _response;
         protected BufferedReader _reader;
-        
+
         /* ------------------------------------------------------------ */
         public ResponseReader(Socket client) throws IOException
         {
@@ -205,18 +207,18 @@ public abstract class ConnectorCloseTestBase extends HttpServerTestFixture
             _response = new StringBuffer();
             _reader = new BufferedReader(new InputStreamReader(client.getInputStream()));
         }
-        
+
         /* ------------------------------------------------------------ */
         public void setDone()
         {
             _done = true;
         }
-        
+
         public StringBuffer getResponse()
         {
             return _response;
         }
-        
+
         /* ------------------------------------------------------------ */
         /**
          * @see java.lang.Runnable#run()
@@ -251,7 +253,7 @@ public abstract class ConnectorCloseTestBase extends HttpServerTestFixture
                 Thread.sleep(25);
             }
 
-            int count = 0;           
+            int count = 0;
             if (_reader.ready())
             {
                 count = _reader.read(_buffer);
@@ -260,7 +262,7 @@ public abstract class ConnectorCloseTestBase extends HttpServerTestFixture
                     _response.append(_buffer, 0, count);
                 }
             }
-            
+
             return count;
         }
     }
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ConnectorTimeoutTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ConnectorTimeoutTest.java
index 212f455..373218d 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/ConnectorTimeoutTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ConnectorTimeoutTest.java
@@ -25,25 +25,23 @@ import java.net.Socket;
 import java.net.SocketException;
 import java.util.concurrent.Exchanger;
 import java.util.concurrent.TimeUnit;
+
 import javax.net.ssl.SSLException;
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
 import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.io.nio.SslConnection;
+import org.eclipse.jetty.io.ssl.SslConnection;
 import org.eclipse.jetty.server.handler.AbstractHandler;
 import org.eclipse.jetty.util.IO;
+import org.hamcrest.Matchers;
 import org.junit.Assert;
 import org.junit.Test;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.matchers.JUnitMatchers.containsString;
-
 public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
 {
-    protected static final int MAX_IDLE_TIME=250;
+    protected static final int MAX_IDLE_TIME=500;
     private int sleepTime = MAX_IDLE_TIME + MAX_IDLE_TIME/5;
     private int minimumTestRuntime = MAX_IDLE_TIME-MAX_IDLE_TIME/5;
     private int maximumTestRuntime = MAX_IDLE_TIME*10;
@@ -58,17 +56,18 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
     public void testMaxIdleWithRequest10() throws Exception
     {
         configureServer(new HelloWorldHandler());
-        Socket client=newSocket(HOST,_connector.getLocalPort());
+        
+        Socket client=newSocket(_serverURI.getHost(),_serverURI.getPort());
         client.setSoTimeout(10000);
 
-        assertFalse(client.isClosed());
+        Assert.assertFalse(client.isClosed());
 
         OutputStream os=client.getOutputStream();
         InputStream is=client.getInputStream();
 
         os.write((
                 "GET / HTTP/1.0\r\n"+
-                "host: "+HOST+":"+_connector.getLocalPort()+"\r\n"+
+                "host: "+_serverURI.getHost()+":"+_serverURI.getPort()+"\r\n"+
                 "connection: keep-alive\r\n"+
         "\r\n").getBytes("utf-8"));
         os.flush();
@@ -77,20 +76,20 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
         IO.toString(is);
 
         Thread.sleep(sleepTime);
-        assertEquals(-1, is.read());
+        Assert.assertEquals(-1, is.read());
 
-        Assert.assertTrue(System.currentTimeMillis()-start>minimumTestRuntime);
-        Assert.assertTrue(System.currentTimeMillis()-start<maximumTestRuntime);
+        Assert.assertTrue(System.currentTimeMillis() - start > minimumTestRuntime);
+        Assert.assertTrue(System.currentTimeMillis() - start < maximumTestRuntime);
     }
 
     @Test
     public void testMaxIdleWithRequest11() throws Exception
     {
         configureServer(new EchoHandler());
-        Socket client=newSocket(HOST,_connector.getLocalPort());
+        Socket client=newSocket(_serverURI.getHost(),_serverURI.getPort());
         client.setSoTimeout(10000);
 
-        assertFalse(client.isClosed());
+        Assert.assertFalse(client.isClosed());
 
         OutputStream os=client.getOutputStream();
         InputStream is=client.getInputStream();
@@ -99,7 +98,7 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
         byte[] contentB=content.getBytes("utf-8");
         os.write((
                 "POST /echo HTTP/1.1\r\n"+
-                "host: "+HOST+":"+_connector.getLocalPort()+"\r\n"+
+                "host: "+_serverURI.getHost()+":"+_serverURI.getPort()+"\r\n"+
                 "content-type: text/plain; charset=utf-8\r\n"+
                 "content-length: "+contentB.length+"\r\n"+
         "\r\n").getBytes("utf-8"));
@@ -110,16 +109,16 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
         IO.toString(is);
 
         Thread.sleep(sleepTime);
-        assertEquals(-1, is.read());
+        Assert.assertEquals(-1, is.read());
 
-        Assert.assertTrue(System.currentTimeMillis()-start>minimumTestRuntime);
-        Assert.assertTrue(System.currentTimeMillis()-start<maximumTestRuntime);
+        Assert.assertTrue(System.currentTimeMillis() - start > minimumTestRuntime);
+        Assert.assertTrue(System.currentTimeMillis() - start < maximumTestRuntime);
     }
 
     @Test
     public void testMaxIdleWithRequest10NoClientClose() throws Exception
     {
-        final Exchanger<EndPoint> endpoint = new Exchanger<EndPoint>();
+        final Exchanger<EndPoint> exchanger = new Exchanger<>();
         configureServer(new HelloWorldHandler()
         {
             @Override
@@ -128,44 +127,45 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
             {
                 try
                 {
-                    endpoint.exchange(baseRequest.getConnection().getEndPoint());
+                    exchanger.exchange(baseRequest.getHttpChannel().getEndPoint());
                 }
-                catch(Exception e)
-                {}
-                super.handle(target,baseRequest,request,response);
+                catch (Exception e)
+                {
+                    e.printStackTrace();
+                }
+                super.handle(target, baseRequest, request, response);
             }
 
         });
-        Socket client=newSocket(HOST,_connector.getLocalPort());
+        Socket client=newSocket(_serverURI.getHost(),_serverURI.getPort());
         client.setSoTimeout(10000);
 
-        assertFalse(client.isClosed());
+        Assert.assertFalse(client.isClosed());
 
         OutputStream os=client.getOutputStream();
         InputStream is=client.getInputStream();
 
         os.write((
                 "GET / HTTP/1.0\r\n"+
-                "host: "+HOST+":"+_connector.getLocalPort()+"\r\n"+
+                "host: "+_serverURI.getHost()+":"+_serverURI.getPort()+"\r\n"+
                 "connection: close\r\n"+
         "\r\n").getBytes("utf-8"));
         os.flush();
 
         // Get the server side endpoint
-        EndPoint endp = endpoint.exchange(null,10,TimeUnit.SECONDS);
-        if (endp instanceof SslConnection.SslEndPoint)
-            endp=((SslConnection.SslEndPoint)endp).getEndpoint();
+        EndPoint endPoint = exchanger.exchange(null,10,TimeUnit.SECONDS);
+        if (endPoint instanceof SslConnection.DecryptedEndPoint)
+            endPoint=endPoint.getConnection().getEndPoint();
 
         // read the response
         String result=IO.toString(is);
-        Assert.assertThat("OK",result,containsString("200 OK"));
+        Assert.assertThat("OK",result, Matchers.containsString("200 OK"));
 
         // check client reads EOF
-        assertEquals(-1, is.read());
+        Assert.assertEquals(-1, is.read());
 
         // wait for idle timeout
-        TimeUnit.MILLISECONDS.sleep(MAX_IDLE_TIME+MAX_IDLE_TIME/2);
-
+        TimeUnit.MILLISECONDS.sleep(3 * MAX_IDLE_TIME);
 
         // further writes will get broken pipe or similar
         try
@@ -174,7 +174,7 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
             {
                 os.write((
                         "GET / HTTP/1.0\r\n"+
-                        "host: "+HOST+":"+_connector.getLocalPort()+"\r\n"+
+                        "host: "+_serverURI.getHost()+":"+_serverURI.getPort()+"\r\n"+
                         "connection: keep-alive\r\n"+
                 "\r\n").getBytes("utf-8"));
                 os.flush();
@@ -186,13 +186,89 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
             // expected
         }
         // check the server side is closed
-        Assert.assertFalse(endp.isOpen());
+        Assert.assertFalse(endPoint.isOpen());
+    }
+
+    @Test
+    public void testMaxIdleWithRequest10ClientIgnoresClose() throws Exception
+    {
+        final Exchanger<EndPoint> exchanger = new Exchanger<>();
+        configureServer(new HelloWorldHandler()
+        {
+            @Override
+            public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException,
+                    ServletException
+            {
+                try
+                {
+                    exchanger.exchange(baseRequest.getHttpChannel().getEndPoint());
+                }
+                catch (Exception e)
+                {
+                    e.printStackTrace();
+                }
+                super.handle(target, baseRequest, request, response);
+            }
+
+        });
+        Socket client=newSocket(_serverURI.getHost(),_serverURI.getPort());
+        client.setSoTimeout(10000);
+
+        Assert.assertFalse(client.isClosed());
+
+        OutputStream os=client.getOutputStream();
+        InputStream is=client.getInputStream();
+
+        os.write((
+                "GET / HTTP/1.0\r\n"+
+                "host: "+_serverURI.getHost()+":"+_serverURI.getPort()+"\r\n"+
+                "connection: close\r\n"+
+        "\r\n").getBytes("utf-8"));
+        os.flush();
+
+        // Get the server side endpoint
+        EndPoint endPoint = exchanger.exchange(null,10,TimeUnit.SECONDS);
+        if (endPoint instanceof SslConnection.DecryptedEndPoint)
+            endPoint=endPoint.getConnection().getEndPoint();
+
+        // read the response
+        String result=IO.toString(is);
+        Assert.assertThat("OK",result, Matchers.containsString("200 OK"));
+
+        // check client reads EOF
+        Assert.assertEquals(-1, is.read());
+        Assert.assertTrue(endPoint.isOutputShutdown());
+
+        Thread.sleep(2 * MAX_IDLE_TIME);
+
+        // further writes will get broken pipe or similar
+        try
+        {
+            long end=System.currentTimeMillis()+MAX_IDLE_TIME+3000;
+            while (System.currentTimeMillis()<end)
+            {
+                os.write("THIS DATA SHOULD NOT BE PARSED!\n\n".getBytes("utf-8"));
+                os.flush();
+                Thread.sleep(100);
+            }
+            Assert.fail("half close should have timed out");
+        }
+        catch(SocketException e)
+        {
+            // expected
+
+            // Give the SSL onClose time to act
+            Thread.sleep(100);
+        }
+
+        // check the server side is closed
+        Assert.assertFalse(endPoint.isOpen());
     }
 
     @Test
     public void testMaxIdleWithRequest11NoClientClose() throws Exception
     {
-        final Exchanger<EndPoint> endpoint = new Exchanger<EndPoint>();
+        final Exchanger<EndPoint> exchanger = new Exchanger<>();
         configureServer(new EchoHandler()
         {
             @Override
@@ -201,18 +277,20 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
             {
                 try
                 {
-                    endpoint.exchange(baseRequest.getConnection().getEndPoint());
+                    exchanger.exchange(baseRequest.getHttpChannel().getEndPoint());
                 }
-                catch(Exception e)
-                {}
-                super.handle(target,baseRequest,request,response);
+                catch (Exception e)
+                {
+                    e.printStackTrace();
+                }
+                super.handle(target, baseRequest, request, response);
             }
 
         });
-        Socket client=newSocket(HOST,_connector.getLocalPort());
+        Socket client=newSocket(_serverURI.getHost(),_serverURI.getPort());
         client.setSoTimeout(10000);
 
-        assertFalse(client.isClosed());
+        Assert.assertFalse(client.isClosed());
 
         OutputStream os=client.getOutputStream();
         InputStream is=client.getInputStream();
@@ -220,25 +298,25 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
         String content="Wibble";
         byte[] contentB=content.getBytes("utf-8");
         os.write((
-                "POST /echo HTTP/1.1\r\n"+
-                "host: "+HOST+":"+_connector.getLocalPort()+"\r\n"+
-                "content-type: text/plain; charset=utf-8\r\n"+
-                "content-length: "+contentB.length+"\r\n"+
-                "connection: close\r\n"+
-        "\r\n").getBytes("utf-8"));
+                "POST /echo HTTP/1.1\r\n" +
+                        "host: " + _serverURI.getHost() + ":" + _serverURI.getPort() + "\r\n" +
+                        "content-type: text/plain; charset=utf-8\r\n" +
+                        "content-length: " + contentB.length + "\r\n" +
+                        "connection: close\r\n" +
+                        "\r\n").getBytes("utf-8"));
         os.write(contentB);
         os.flush();
 
         // Get the server side endpoint
-        EndPoint endp = endpoint.exchange(null,10,TimeUnit.SECONDS);
+        EndPoint endPoint = exchanger.exchange(null,10,TimeUnit.SECONDS);
 
         // read the response
         IO.toString(is);
 
         // check client reads EOF
-        assertEquals(-1, is.read());
+        Assert.assertEquals(-1, is.read());
 
-        TimeUnit.MILLISECONDS.sleep(MAX_IDLE_TIME+MAX_IDLE_TIME/2);
+        TimeUnit.MILLISECONDS.sleep(3*MAX_IDLE_TIME);
 
 
         // further writes will get broken pipe or similar
@@ -248,7 +326,7 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
             {
                 os.write((
                         "GET / HTTP/1.0\r\n"+
-                        "host: "+HOST+":"+_connector.getLocalPort()+"\r\n"+
+                        "host: "+_serverURI.getHost()+":"+_serverURI.getPort()+"\r\n"+
                         "connection: keep-alive\r\n"+
                 "\r\n").getBytes("utf-8"));
                 os.flush();
@@ -261,25 +339,24 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
         }
 
         // check the server side is closed
-        Assert.assertFalse(endp.isOpen());
+        Assert.assertFalse(endPoint.isOpen());
     }
 
-
     @Test
     public void testMaxIdleNoRequest() throws Exception
     {
         configureServer(new EchoHandler());
-        Socket client=newSocket(HOST,_connector.getLocalPort());
+        Socket client=newSocket(_serverURI.getHost(),_serverURI.getPort());
         client.setSoTimeout(10000);
         InputStream is=client.getInputStream();
-        assertFalse(client.isClosed());
+        Assert.assertFalse(client.isClosed());
 
         Thread.sleep(sleepTime);
         long start = System.currentTimeMillis();
         try
         {
             IO.toString(is);
-            assertEquals(-1, is.read());
+            Assert.assertEquals(-1, is.read());
         }
         catch(SSLException e)
         {
@@ -289,7 +366,7 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
         {
             e.printStackTrace();
         }
-        Assert.assertTrue(System.currentTimeMillis()-start<maximumTestRuntime);
+        Assert.assertTrue(System.currentTimeMillis() - start < maximumTestRuntime);
 
     }
 
@@ -297,10 +374,10 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
     public void testMaxIdleWithSlowRequest() throws Exception
     {
         configureServer(new EchoHandler());
-        Socket client=newSocket(HOST,_connector.getLocalPort());
+        Socket client=newSocket(_serverURI.getHost(),_serverURI.getPort());
         client.setSoTimeout(10000);
 
-        assertFalse(client.isClosed());
+        Assert.assertFalse(client.isClosed());
 
         OutputStream os=client.getOutputStream();
         InputStream is=client.getInputStream();
@@ -309,7 +386,7 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
         byte[] contentB=content.getBytes("utf-8");
         os.write((
                 "GET / HTTP/1.0\r\n"+
-                "host: "+HOST+":"+_connector.getLocalPort()+"\r\n"+
+                "host: "+_serverURI.getHost()+":"+_serverURI.getPort()+"\r\n"+
                 "connection: keep-alive\r\n"+
                 "Content-Length: "+(contentB.length*20)+"\r\n"+
                 "Content-Type: text/plain\r\n"+
@@ -329,7 +406,7 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
         for (int i =0;i<20;i++)
         {
             offset=in.indexOf("Wibble",offset+1);
-            Assert.assertTrue(""+i,offset>0);
+            Assert.assertTrue("" + i, offset > 0);
         }
     }
 
@@ -337,17 +414,17 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
     public void testMaxIdleWithSlowResponse() throws Exception
     {
         configureServer(new SlowResponseHandler());
-        Socket client=newSocket(HOST,_connector.getLocalPort());
+        Socket client=newSocket(_serverURI.getHost(),_serverURI.getPort());
         client.setSoTimeout(10000);
 
-        assertFalse(client.isClosed());
+        Assert.assertFalse(client.isClosed());
 
         OutputStream os=client.getOutputStream();
         InputStream is=client.getInputStream();
 
         os.write((
                 "GET / HTTP/1.0\r\n"+
-                "host: "+HOST+":"+_connector.getLocalPort()+"\r\n"+
+                "host: "+_serverURI.getHost()+":"+_serverURI.getPort()+"\r\n"+
                 "connection: keep-alive\r\n"+
                 "Connection: close\r\n"+
         "\r\n").getBytes("utf-8"));
@@ -358,7 +435,7 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
         for (int i =0;i<20;i++)
         {
             offset=in.indexOf("Hello World",offset+1);
-            Assert.assertTrue(""+i,offset>0);
+            Assert.assertTrue("" + i, offset > 0);
         }
     }
 
@@ -366,17 +443,17 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
     public void testMaxIdleWithWait() throws Exception
     {
         configureServer(new WaitHandler());
-        Socket client=newSocket(HOST,_connector.getLocalPort());
+        Socket client=newSocket(_serverURI.getHost(),_serverURI.getPort());
         client.setSoTimeout(10000);
 
-        assertFalse(client.isClosed());
+        Assert.assertFalse(client.isClosed());
 
         OutputStream os=client.getOutputStream();
         InputStream is=client.getInputStream();
 
         os.write((
                 "GET / HTTP/1.0\r\n"+
-                "host: "+HOST+":"+_connector.getLocalPort()+"\r\n"+
+                "host: "+_serverURI.getHost()+":"+_serverURI.getPort()+"\r\n"+
                 "connection: keep-alive\r\n"+
                 "Connection: close\r\n"+
         "\r\n").getBytes("utf-8"));
@@ -384,11 +461,12 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
 
         String in = IO.toString(is);
         int offset=in.indexOf("Hello World");
-        Assert.assertTrue(offset>0);
+        Assert.assertTrue(offset > 0);
     }
 
     protected static class SlowResponseHandler extends AbstractHandler
     {
+        @Override
         public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
         {
             baseRequest.setHandled(true);
@@ -407,6 +485,7 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
 
     protected static class WaitHandler extends AbstractHandler
     {
+        @Override
         public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
         {
             baseRequest.setHandled(true);
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/DumpHandler.java b/jetty-server/src/test/java/org/eclipse/jetty/server/DumpHandler.java
index 269c1dc..c46ab4a 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/DumpHandler.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/DumpHandler.java
@@ -24,6 +24,7 @@ import java.io.OutputStream;
 import java.io.OutputStreamWriter;
 import java.io.Reader;
 import java.io.Writer;
+import java.nio.charset.StandardCharsets;
 import java.util.Enumeration;
 
 import javax.servlet.ServletException;
@@ -31,12 +32,9 @@ import javax.servlet.http.Cookie;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import org.eclipse.jetty.continuation.Continuation;
-import org.eclipse.jetty.continuation.ContinuationSupport;
-import org.eclipse.jetty.http.HttpHeaders;
+import org.eclipse.jetty.http.HttpHeader;
 import org.eclipse.jetty.http.MimeTypes;
 import org.eclipse.jetty.server.handler.AbstractHandler;
-import org.eclipse.jetty.util.StringUtil;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
 
@@ -44,85 +42,86 @@ import org.eclipse.jetty.util.log.Logger;
 /** Dump request handler.
  * Dumps GET and POST requests.
  * Useful for testing and debugging.
- * 
+ *
  * @version $Id: DumpHandler.java,v 1.14 2005/08/13 00:01:26 gregwilkins Exp $
- * 
+ *
  */
 public class DumpHandler extends AbstractHandler
 {
     private static final Logger LOG = Log.getLogger(DumpHandler.class);
 
     String label="Dump HttpHandler";
-    
+
     public DumpHandler()
     {
     }
-    
+
     public DumpHandler(String label)
     {
         this.label=label;
     }
-    
+
     /* ------------------------------------------------------------ */
-    /* 
+    /*
      * @see org.eclipse.jetty.server.server.Handler#handle(java.lang.String, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
      */
+    @Override
     public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
-    {        
+    {
         if (!isStarted())
             return;
 
+        StringBuilder read = null;
         if (request.getParameter("read")!=null)
         {
+            read=new StringBuilder();
+            int len=Integer.parseInt(request.getParameter("read"));
             Reader in = request.getReader();
-            for (int i=Integer.parseInt(request.getParameter("read"));i-->0;)
-                in.read();
+            for (int i=len;i-->0;)
+                read.append((char)in.read());
         }
+
+        if (request.getParameter("date")!=null)
+            response.setHeader("Date",request.getParameter("date"));
         
         if (request.getParameter("ISE")!=null)
         {
-            throw new IllegalStateException();
+            throw new IllegalStateException("Testing ISE");
         }
-        
+
         if (request.getParameter("error")!=null)
         {
             response.sendError(Integer.parseInt(request.getParameter("error")));
             return;
         }
-        
-        if (request.getParameter("continue")!=null)
-        {
-            Continuation continuation = ContinuationSupport.getContinuation(request,response);
-            continuation.setTimeout(Long.parseLong(request.getParameter("continue")));
-            continuation.suspend();
-        }
-        
+
         baseRequest.setHandled(true);
-        response.setHeader(HttpHeaders.CONTENT_TYPE,MimeTypes.TEXT_HTML);
-        
+        response.setHeader(HttpHeader.CONTENT_TYPE.asString(),MimeTypes.Type.TEXT_HTML.asString());
+
         OutputStream out = response.getOutputStream();
         ByteArrayOutputStream buf = new ByteArrayOutputStream(2048);
-        Writer writer = new OutputStreamWriter(buf,StringUtil.__ISO_8859_1);
+        Writer writer = new OutputStreamWriter(buf,StandardCharsets.ISO_8859_1);
         writer.write("<html><h1>"+label+"</h1>");
         writer.write("<pre>\npathInfo="+request.getPathInfo()+"\n</pre>\n");
         writer.write("<pre>\ncontentType="+request.getContentType()+"\n</pre>\n");
         writer.write("<pre>\nencoding="+request.getCharacterEncoding()+"\n</pre>\n");
+        writer.write("<pre>\nservername="+request.getServerName()+"\n</pre>\n");
         writer.write("<h3>Header:</h3><pre>");
         writer.write(request.getMethod()+" "+request.getRequestURI()+" "+request.getProtocol()+"\n");
-        Enumeration headers = request.getHeaderNames();
+        Enumeration<String> headers = request.getHeaderNames();
         while(headers.hasMoreElements())
         {
-            String name=(String)headers.nextElement();
+            String name=headers.nextElement();
             writer.write(name);
             writer.write(": ");
             writer.write(request.getHeader(name));
             writer.write("\n");
         }
         writer.write("</pre>\n<h3>Parameters:</h3>\n<pre>");
-        Enumeration names=request.getParameterNames();
+        Enumeration<String> names=request.getParameterNames();
         while(names.hasMoreElements())
         {
-            String name=names.nextElement().toString();
+            String name=names.nextElement();
             String[] values=request.getParameterValues(name);
             if (values==null || values.length==0)
             {
@@ -147,7 +146,7 @@ public class DumpHandler extends AbstractHandler
                 }
             }
         }
-        
+
         String cookie_name=request.getParameter("CookieName");
         if (cookie_name!=null && cookie_name.trim().length()>0)
         {
@@ -167,7 +166,7 @@ public class DumpHandler extends AbstractHandler
                 writer.write(e.toString());
             }
         }
-        
+
         writer.write("</pre>\n<h3>Cookies:</h3>\n<pre>");
         Cookie[] cookies=request.getCookies();
         if (cookies!=null && cookies.length>0)
@@ -181,9 +180,9 @@ public class DumpHandler extends AbstractHandler
                 writer.write("\n");
             }
         }
-        
+
         writer.write("</pre>\n<h3>Attributes:</h3>\n<pre>");
-        Enumeration attributes=request.getAttributeNames();
+        Enumeration<String> attributes=request.getAttributeNames();
         if (attributes!=null && attributes.hasMoreElements())
         {
             while(attributes.hasMoreElements())
@@ -195,42 +194,50 @@ public class DumpHandler extends AbstractHandler
                 writer.write("\n");
             }
         }
-        
+
         writer.write("</pre>\n<h3>Content:</h3>\n<pre>");
 
-        char[] content= new char[4096];
-        int len;
-        try{
-            Reader in=request.getReader();
-            while((len=in.read(content))>=0)
-                writer.write(new String(content,0,len));
+        if (read!=null)
+        {
+            writer.write(read.toString());
         }
-        catch(IOException e)
+        else
         {
-            writer.write(e.toString());
+            char[] content= new char[4096];
+            int len;
+            try{
+                Reader in=request.getReader();
+                while((len=in.read(content))>=0)
+                    writer.write(new String(content,0,len));
+            }
+            catch(IOException e)
+            {
+                writer.write(e.toString());
+            }
         }
-        
-        
-        writer.write("</pre>");
-        writer.write("</html>");
-        
-        // commit now
+
+        writer.write("</pre>\n");
+        writer.write("</html>\n");
         writer.flush();
+
+        // commit now
         response.setContentLength(buf.size()+1000);
+        response.addHeader("Before-Flush",response.isCommitted()?"Committed???":"Not Committed");
+        buf.writeTo(out);
+        out.flush();
+        response.addHeader("After-Flush","These headers should not be seen in the response!!!");
+        response.addHeader("After-Flush",response.isCommitted()?"Committed":"Not Committed?");
 
+        // write remaining content after commit
         try
         {
-            buf.writeTo(out);
-
             buf.reset();
             writer.flush();
-            for (int pad=998-buf.size();pad-->0;)
+            for (int pad=998;pad-->0;)
                 writer.write(" ");
-            writer.write("\015\012");
+            writer.write("\r\n");
             writer.flush();
             buf.writeTo(out);
-
-            response.setHeader("IgnoreMe","ignored");
         }
         catch(Exception e)
         {
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/EncodedHttpURITest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/EncodedHttpURITest.java
deleted file mode 100644
index b213c50..0000000
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/EncodedHttpURITest.java
+++ /dev/null
@@ -1,50 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.server;
-
-import static org.junit.Assert.assertEquals;
-
-import java.net.URLDecoder;
-import java.net.URLEncoder;
-
-import org.eclipse.jetty.http.EncodedHttpURI;
-import org.junit.Test;
-
-public class EncodedHttpURITest
-{
-    @Test
-    public void testNonURIAscii() throws Exception
-    {
-        String url = "http://www.foo.com/ma\u00F1ana";
-        byte[] asISO = url.getBytes("ISO-8859-1");
-        new String(asISO, "ISO-8859-1");
-
-        //use a non UTF-8 charset as the encoding and url-escape as per
-        //http://www.w3.org/TR/html40/appendix/notes.html#non-ascii-chars
-        String s = URLEncoder.encode(url, "ISO-8859-1");
-        EncodedHttpURI uri = new EncodedHttpURI("ISO-8859-1");
-
-        //parse it, using the same encoding
-        uri.parse(s);
-
-        //decode the url encoding
-        String d = URLDecoder.decode(uri.getCompletePath(), "ISO-8859-1");
-        assertEquals(url, d);
-    }
-}
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ExtendedServerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ExtendedServerTest.java
new file mode 100644
index 0000000..be46562
--- /dev/null
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ExtendedServerTest.java
@@ -0,0 +1,159 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.Socket;
+import java.nio.ByteBuffer;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.SocketChannel;
+import java.nio.charset.StandardCharsets;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.SelectChannelEndPoint;
+import org.eclipse.jetty.io.SelectorManager.ManagedSelector;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.util.thread.Scheduler;
+import org.hamcrest.Matchers;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Extended Server Tester.
+ */
+public class ExtendedServerTest extends HttpServerTestBase
+{
+    @Before
+    public void init() throws Exception
+    {
+        startServer(new ServerConnector(_server,new HttpConnectionFactory()
+        {
+            @Override
+            public Connection newConnection(Connector connector, EndPoint endPoint)
+            {
+                return configure(new ExtendedHttpConnection(getHttpConfiguration(), connector, endPoint), connector, endPoint);
+            }
+        })
+        {
+
+            @Override
+            protected SelectChannelEndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey key) throws IOException
+            {
+                return new ExtendedEndPoint(channel,selectSet,key, getScheduler(), getIdleTimeout());
+            }
+
+        });
+    }
+
+    private static class ExtendedEndPoint extends SelectChannelEndPoint
+    {
+        private volatile long _lastSelected;
+
+        public ExtendedEndPoint(SocketChannel channel, ManagedSelector selector, SelectionKey key, Scheduler scheduler, long idleTimeout)
+        {
+            super(channel,selector,key,scheduler,idleTimeout);
+        }
+
+        @Override
+        public void onSelected()
+        {
+            _lastSelected=System.currentTimeMillis();
+            super.onSelected();
+        }
+
+        long getLastSelected()
+        {
+            return _lastSelected;
+        }
+    }
+
+    private static class ExtendedHttpConnection extends HttpConnection
+    {
+        public ExtendedHttpConnection(HttpConfiguration config, Connector connector, EndPoint endPoint)
+        {
+            super(config,connector,endPoint);
+        }
+
+        @Override
+        protected HttpChannelOverHttp newHttpChannel(HttpInput<ByteBuffer> httpInput)
+        {
+            return new HttpChannelOverHttp(getConnector(), getHttpConfiguration(), getEndPoint(), this, httpInput)
+            {
+                @Override
+                public boolean startRequest(HttpMethod httpMethod, String method, ByteBuffer uri, HttpVersion version)
+                {
+                    getRequest().setAttribute("DispatchedAt",((ExtendedEndPoint)getEndPoint()).getLastSelected());
+                    return super.startRequest(httpMethod,method,uri,version);
+                }
+            };
+        }
+    }
+
+    @Test
+    public void testExtended() throws Exception
+    {
+        configureServer(new DispatchedAtHandler());
+
+        try (Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort()))
+        {
+            OutputStream os = client.getOutputStream();
+
+            long start=System.currentTimeMillis();
+            os.write("GET / HTTP/1.0\r\n".getBytes(StandardCharsets.ISO_8859_1));
+            os.flush();
+            Thread.sleep(200);
+            long end=System.currentTimeMillis();
+            os.write("\r\n".getBytes(StandardCharsets.ISO_8859_1));
+            
+            // Read the response.
+            String response = readResponse(client);
+
+            Assert.assertThat(response, Matchers.containsString("HTTP/1.1 200 OK"));
+            Assert.assertThat(response, Matchers.containsString("DispatchedAt="));
+            
+            String s=response.substring(response.indexOf("DispatchedAt=")+13);
+            s=s.substring(0,s.indexOf('\n'));
+            long dispatched=Long.valueOf(s);
+            
+            Assert.assertThat(dispatched, Matchers.greaterThanOrEqualTo(start));
+            Assert.assertThat(dispatched, Matchers.lessThan(end));
+        }
+    }
+    
+
+    protected static class DispatchedAtHandler extends AbstractHandler
+    {
+        @Override
+        public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+        {
+            baseRequest.setHandled(true);
+            response.setStatus(200);
+            response.getOutputStream().print("DispatchedAt="+request.getAttribute("DispatchedAt")+"\r\n");
+        }
+    }
+}
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/GracefulStopTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/GracefulStopTest.java
new file mode 100644
index 0000000..696533e
--- /dev/null
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/GracefulStopTest.java
@@ -0,0 +1,134 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.net.Socket;
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.TimeUnit;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.server.handler.StatisticsHandler;
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.StdErrLog;
+import org.hamcrest.Matchers;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class GracefulStopTest
+{
+    private Server server;
+
+    @Before
+    public void setup() throws Exception
+    {
+        server = new Server(0);
+        StatisticsHandler stats = new StatisticsHandler();
+        TestHandler test=new TestHandler();
+        server.setHandler(stats);
+        stats.setHandler(test);
+        server.setStopTimeout(10 * 1000);
+        
+        server.start();
+    }
+
+    @Test
+    public void testGraceful() throws Exception
+    {
+        new Thread()
+        {
+            @Override
+            public void run()
+            {
+                try
+                {
+                    TimeUnit.SECONDS.sleep(1);
+                    server.stop();
+                }
+                catch (Exception e)
+                {
+                    e.printStackTrace();
+                }
+            }
+        }.start();
+
+        try(Socket socket = new Socket("localhost",server.getBean(NetworkConnector.class).getLocalPort());)
+        {
+            socket.getOutputStream().write("GET / HTTP/1.0\r\n\r\n".getBytes(StandardCharsets.ISO_8859_1));
+            String out = IO.toString(socket.getInputStream());
+            Assert.assertThat(out,Matchers.containsString("200 OK"));
+        }
+    }
+    
+    @Test
+    public void testGracefulTimout() throws Exception
+    {
+        server.setStopTimeout(100);
+        new Thread()
+        {
+            @Override
+            public void run()
+            {
+                try
+                {
+                    TimeUnit.SECONDS.sleep(1);
+                    server.stop();
+                }
+                catch (Exception e)
+                {
+                    //e.printStackTrace();
+                }
+            }
+        }.start();
+
+        try(Socket socket = new Socket("localhost",server.getBean(NetworkConnector.class).getLocalPort());)
+        {
+            socket.getOutputStream().write("GET / HTTP/1.0\r\n\r\n".getBytes(StandardCharsets.ISO_8859_1));
+            String out = IO.toString(socket.getInputStream());
+            Assert.assertEquals("",out);
+        }
+    }
+
+    private static class TestHandler extends AbstractHandler
+    {
+        @Override
+        public void handle(final String s, final Request request, final HttpServletRequest httpServletRequest, final HttpServletResponse httpServletResponse)
+            throws IOException, ServletException
+        {
+            try
+            {
+                TimeUnit.SECONDS.sleep(2);
+            }
+            catch (InterruptedException e)
+            {
+            }
+
+            httpServletResponse.getWriter().write("OK");
+            httpServletResponse.setStatus(200);
+            request.setHandled(true);
+        }
+    }
+
+}
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/HalfCloseRaceTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/HalfCloseRaceTest.java
deleted file mode 100644
index ab48ecd..0000000
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/HalfCloseRaceTest.java
+++ /dev/null
@@ -1,86 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.server;
-  
-import static org.junit.Assert.assertEquals;
-
-import java.io.IOException;
-import java.net.Socket;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jetty.server.Request;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.handler.AbstractHandler;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
-import org.junit.Assert;
-import org.junit.Test;
-
-public class HalfCloseRaceTest
-{
-    @Test
-    public void testHalfCloseRace() throws Exception
-    {
-        Server server = new Server();
-        SelectChannelConnector connector = new SelectChannelConnector();
-        connector.setPort(0);
-        connector.setMaxIdleTime(500);
-        server.addConnector(connector);
-        TestHandler handler = new TestHandler();
-        server.setHandler(handler);
-
-        server.start();
-        
-        Socket client = new Socket("localhost",connector.getLocalPort());
-        
-        int in = client.getInputStream().read();
-        assertEquals(-1,in);
-        
-        client.getOutputStream().write("GET / HTTP/1.0\r\n\r\n".getBytes());
-        
-        Thread.sleep(200);
-        assertEquals(0,handler.getHandled());
-        
-    }
-
-    public static class TestHandler extends AbstractHandler
-    {
-        transient int handled;
-        
-        public TestHandler()
-        {
-        }
-
-        public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
-        {
-            baseRequest.setHandled(true);
-            handled++;
-            response.setContentType("text/html;charset=utf-8");
-            response.setStatus(HttpServletResponse.SC_OK);
-            response.getWriter().println("<h1>Test</h1>");
-        }
-        
-        public int getHandled()
-        {
-            return handled;
-        }
-    }
-}
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/HalfCloseTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/HalfCloseTest.java
new file mode 100644
index 0000000..aacdb59
--- /dev/null
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/HalfCloseTest.java
@@ -0,0 +1,214 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+  
+import static org.junit.Assert.assertEquals;
+
+import java.io.IOException;
+import java.net.Socket;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.util.IO;
+import org.junit.Test;
+
+public class HalfCloseTest
+{
+    @Test
+    public void testHalfCloseRace() throws Exception
+    {
+        Server server = new Server();
+        ServerConnector connector = new ServerConnector(server,1,1);
+        connector.setPort(0);
+        connector.setIdleTimeout(500);
+        server.addConnector(connector);
+        TestHandler handler = new TestHandler();
+        server.setHandler(handler);
+
+        server.start();
+        
+        try(Socket client = new Socket("localhost",connector.getLocalPort());)
+        {
+            int in = client.getInputStream().read();
+            assertEquals(-1,in);
+
+            client.getOutputStream().write("GET / HTTP/1.0\r\n\r\n".getBytes());
+
+            Thread.sleep(200);
+            assertEquals(0,handler.getHandled());
+        }
+        
+    }
+    
+    @Test
+    public void testCompleteClose() throws Exception
+    {
+        Server server = new Server();
+        ServerConnector connector = new ServerConnector(server,1,1);
+        connector.setPort(0);
+        connector.setIdleTimeout(5000);
+        final AtomicInteger opened = new AtomicInteger(0);
+        final CountDownLatch closed = new CountDownLatch(1);
+        connector.addBean(new Connection.Listener()
+        {
+            @Override
+            public void onOpened(Connection connection)
+            {
+                opened.incrementAndGet();
+            }
+
+            @Override
+            public void onClosed(Connection connection)
+            {
+                closed.countDown();
+            }
+            
+        });
+        server.addConnector(connector);
+        TestHandler handler = new TestHandler();
+        server.setHandler(handler);
+        
+        server.start();
+        
+        try(Socket client = new Socket("localhost",connector.getLocalPort());)
+        {
+            client.getOutputStream().write("GET / HTTP/1.0\r\n\r\n".getBytes());
+            IO.toString(client.getInputStream());
+            assertEquals(1,handler.getHandled());
+            assertEquals(1,opened.get());
+        }
+        assertEquals(true,closed.await(1,TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testAsyncClose() throws Exception
+    {
+        Server server = new Server();
+        ServerConnector connector = new ServerConnector(server,1,1);
+        connector.setPort(0);
+        connector.setIdleTimeout(5000);
+        final AtomicInteger opened = new AtomicInteger(0);
+        final CountDownLatch closed = new CountDownLatch(1);
+        connector.addBean(new Connection.Listener()
+        {
+            @Override
+            public void onOpened(Connection connection)
+            {
+                opened.incrementAndGet();
+            }
+
+            @Override
+            public void onClosed(Connection connection)
+            {
+                closed.countDown();
+            }
+            
+        });
+        server.addConnector(connector);
+        AsyncHandler handler = new AsyncHandler();
+        server.setHandler(handler);
+        
+        server.start();
+        
+        try(Socket client = new Socket("localhost",connector.getLocalPort());)
+        {
+            client.getOutputStream().write("GET / HTTP/1.0\r\n\r\n".getBytes());
+            IO.toString(client.getInputStream());
+            assertEquals(1,handler.getHandled());
+            assertEquals(1,opened.get());
+        }
+        assertEquals(true,closed.await(1,TimeUnit.SECONDS));
+    }
+
+    public static class TestHandler extends AbstractHandler
+    {
+        transient int handled;
+        
+        public TestHandler()
+        {
+        }
+
+        @Override
+        public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+        {
+            baseRequest.setHandled(true);
+            handled++;
+            response.setContentType("text/html;charset=utf-8");
+            response.setStatus(HttpServletResponse.SC_OK);
+            response.getWriter().println("<h1>Test</h1>");
+        }
+        
+        public int getHandled()
+        {
+            return handled;
+        }
+    }
+    
+    public static class AsyncHandler extends AbstractHandler
+    {
+        transient int handled;
+        
+        public AsyncHandler()
+        {
+        }
+
+        @Override
+        public void handle(String target, Request baseRequest, final HttpServletRequest request, final HttpServletResponse response) throws IOException, ServletException
+        {
+            baseRequest.setHandled(true);
+            handled++;
+
+            final AsyncContext async = request.startAsync();
+            new Thread()
+            {
+                @Override
+                public void run()
+                {
+                    try
+                    {
+                        response.setContentType("text/html;charset=utf-8");
+                        response.setStatus(HttpServletResponse.SC_OK);
+                        response.getWriter().println("<h1>Test</h1>"); 
+                    }
+                    catch (Exception ex)
+                    {
+                        System.err.println(ex);
+                    }
+                    finally
+                    {
+                        async.complete();
+                    }
+                }
+            }.start();
+        }
+        
+        public int getHandled()
+        {
+            return handled;
+        }
+    }
+}
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/HostHeaderCustomizerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/HostHeaderCustomizerTest.java
new file mode 100644
index 0000000..13d3a00
--- /dev/null
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/HostHeaderCustomizerTest.java
@@ -0,0 +1,97 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.Socket;
+import java.nio.charset.StandardCharsets;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.toolchain.test.TestTracker;
+import org.eclipse.jetty.toolchain.test.http.SimpleHttpParser;
+import org.eclipse.jetty.toolchain.test.http.SimpleHttpResponse;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class HostHeaderCustomizerTest
+{
+    @Rule
+    public TestTracker tracker = new TestTracker();
+
+    @Test
+    public void testHostHeaderCustomizer() throws Exception
+    {
+        Server server = new Server();
+        HttpConfiguration httpConfig = new HttpConfiguration();
+        final String serverName = "test_server_name";
+        final int serverPort = 13;
+        final String redirectPath = "/redirect";
+        httpConfig.addCustomizer(new HostHeaderCustomizer(serverName, serverPort));
+        ServerConnector connector = new ServerConnector(server, new HttpConnectionFactory(httpConfig));
+        server.addConnector(connector);
+        server.setHandler(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                Assert.assertEquals(serverName, request.getServerName());
+                Assert.assertEquals(serverPort, request.getServerPort());
+                response.sendRedirect(redirectPath);
+            }
+        });
+        server.start();
+        try
+        {
+            try (Socket socket = new Socket("localhost", connector.getLocalPort()))
+            {
+                OutputStream output = socket.getOutputStream();
+                String request = "" +
+                        "GET / HTTP/1.0\r\n" +
+                        "\r\n";
+                output.write(request.getBytes(StandardCharsets.UTF_8));
+                output.flush();
+
+                BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8));
+                SimpleHttpParser parser = new SimpleHttpParser();
+                SimpleHttpResponse response = parser.readResponse(input);
+
+                String location = response.getHeaders().get("location");
+                Assert.assertNotNull(location);
+                String schemePrefix = "http://";
+                Assert.assertTrue(location.startsWith(schemePrefix));
+                Assert.assertTrue(location.endsWith(redirectPath));
+                String hostPort = location.substring(schemePrefix.length(), location.length() - redirectPath.length());
+                Assert.assertEquals(serverName + ":" + serverPort, hostPort);
+            }
+        }
+        finally
+        {
+            server.stop();
+        }
+    }
+}
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpConnectionTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpConnectionTest.java
index a608c26..020e7d5 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpConnectionTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpConnectionTest.java
@@ -24,23 +24,39 @@
  */
 package org.eclipse.jetty.server;
 
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.greaterThan;
+import static org.hamcrest.Matchers.startsWith;
+import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
 
+import java.io.BufferedReader;
 import java.io.IOException;
+import java.io.OutputStream;
 import java.io.PrintWriter;
+import java.io.StringReader;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import org.eclipse.jetty.http.HttpHeaders;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpParser;
 import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.server.handler.ErrorHandler;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
 import org.eclipse.jetty.util.log.StdErrLog;
+import org.hamcrest.Matchers;
 import org.junit.After;
+import org.junit.Assert;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 
 /**
@@ -48,8 +64,6 @@ import org.junit.Test;
  */
 public class HttpConnectionTest
 {
-    private static final Logger LOG = Log.getLogger(HttpConnectionTest.class);
-
     private Server server;
     private LocalConnector connector;
 
@@ -57,11 +71,19 @@ public class HttpConnectionTest
     public void init() throws Exception
     {
         server = new Server();
-        connector = new LocalConnector();
+
+        HttpConnectionFactory http = new HttpConnectionFactory();
+        http.getHttpConfiguration().setRequestHeaderSize(1024);
+        http.getHttpConfiguration().setResponseHeaderSize(1024);
+        
+        connector = new LocalConnector(server,http,null);
+        connector.setIdleTimeout(5000);
+        connector.getConnectionFactory(HttpConnectionFactory.class).getHttpConfiguration().setSendDateHeader(true);
         server.addConnector(connector);
-        connector.setRequestHeaderSize(1024);
-        connector.setResponseHeaderSize(1024);
         server.setHandler(new DumpHandler());
+        ErrorHandler eh=new ErrorHandler();
+        eh.setServer(server);
+        server.addBean(eh);
         server.start();
     }
 
@@ -73,7 +95,7 @@ public class HttpConnectionTest
     }
 
     @Test
-    public void testFragmentedChunk()
+    public void testFragmentedChunk() throws Exception
     {
         String response=null;
         try
@@ -86,6 +108,7 @@ public class HttpConnectionTest
                                            "Host: localhost\n"+
                                            "Transfer-Encoding: chunked\n"+
                                            "Content-Type: text/plain\n"+
+                                           "Connection: close\n"+
                                            "\015\012"+
                                            "5;\015\012"+
                                            "12345\015\012"+
@@ -99,6 +122,7 @@ public class HttpConnectionTest
                                            "Host: localhost\n"+
                                            "Transfer-Encoding: chunked\n"+
                                            "Content-Type: text/plain\n"+
+                                           "Connection: close\n"+
                                            "\015\012"+
                                            "5;\015\012"+
                                            "ABCDE\015\012"+
@@ -109,25 +133,53 @@ public class HttpConnectionTest
         }
         catch(Exception e)
         {
-            e.printStackTrace();
-            assertTrue(false);
-            if (response!=null)
+            if(response != null)
                 System.err.println(response);
+            throw e;
         }
     }
-    
+
     @Test
     public void testNoPath() throws Exception
     {
         String response=connector.getResponses("GET http://localhost:80 HTTP/1.1\n"+
                 "Host: localhost:80\n"+
+                "Connection: close\n"+
                 "\n");
 
         int offset=0;
         offset = checkContains(response,offset,"HTTP/1.1 200");
         checkContains(response,offset,"pathInfo=/");
     }
-    
+
+    @Test
+    public void testDate() throws Exception
+    {
+        String response=connector.getResponses("GET / HTTP/1.1\n"+
+                "Host: localhost:80\n"+
+                "Connection: close\n"+
+                "\n");
+
+        int offset=0;
+        offset = checkContains(response,offset,"HTTP/1.1 200");
+        offset = checkContains(response,offset,"Date: ");
+        checkContains(response,offset,"pathInfo=/");
+    }
+
+    @Test
+    public void testSetDate() throws Exception
+    {
+        String response=connector.getResponses("GET /?date=1+Jan+1970 HTTP/1.1\n"+
+                "Host: localhost:80\n"+
+                "Connection: close\n"+
+                "\n");
+
+        int offset=0;
+        offset = checkContains(response,offset,"HTTP/1.1 200");
+        offset = checkContains(response,offset,"Date: 1 Jan 1970");
+        checkContains(response,offset,"pathInfo=/");
+    }
+
     @Test
     public void testBadNoPath() throws Exception
     {
@@ -190,12 +242,26 @@ public class HttpConnectionTest
     }
 
     @Test
-    public void testEmpty() throws Exception
+    public void testSimple() throws Exception
+    {
+        String response=connector.getResponses("GET /R1 HTTP/1.1\n"+
+                "Host: localhost\n"+
+                "Connection: close\n"+
+                "\n");
+
+        int offset=0;
+        offset = checkContains(response,offset,"HTTP/1.1 200");
+        checkContains(response,offset,"/R1");
+    }
+
+    @Test
+    public void testEmptyChunk() throws Exception
     {
         String response=connector.getResponses("GET /R1 HTTP/1.1\n"+
                 "Host: localhost\n"+
                 "Transfer-Encoding: chunked\n"+
                 "Content-Type: text/plain\n"+
+                "Connection: close\n"+
                 "\015\012"+
         "0\015\012\015\012");
 
@@ -203,93 +269,153 @@ public class HttpConnectionTest
         offset = checkContains(response,offset,"HTTP/1.1 200");
         checkContains(response,offset,"/R1");
     }
-    
+
     @Test
     public void testHead() throws Exception
     {
         String responsePOST=connector.getResponses("POST /R1 HTTP/1.1\015\012"+
                 "Host: localhost\015\012"+
+                "Connection: close\015\012"+
                 "\015\012");
         
         String responseHEAD=connector.getResponses("HEAD /R1 HTTP/1.1\015\012"+
-                "Host: localhost\015\012"+
-                "\015\012");
-        
-        assertTrue(responsePOST.startsWith(responseHEAD.substring(0,responseHEAD.length()-2)));
-        assertTrue(responsePOST.length()>responseHEAD.length());
+            "Host: localhost\015\012"+
+            "Connection: close\015\012"+
+            "\015\012");
+
+        String postLine;
+        boolean postDate=false;
+        Set<String> postHeaders = new HashSet<>();
+        try(BufferedReader in = new BufferedReader(new StringReader(responsePOST)))
+        {
+            postLine = in.readLine();
+            String line=in.readLine();
+            while (line!=null && line.length()>0)
+            {    
+                if (line.startsWith("Date:"))
+                    postDate=true;
+                else
+                    postHeaders.add(line);
+                line=in.readLine();
+            }
+        }
+        String headLine;
+        boolean headDate=false;
+        Set<String> headHeaders = new HashSet<>();
+        try(BufferedReader in = new BufferedReader(new StringReader(responseHEAD)))
+        {
+            headLine = in.readLine();
+            String line=in.readLine();
+            while (line!=null && line.length()>0)
+            {    
+                if (line.startsWith("Date:"))
+                    headDate=true;
+                else
+                    headHeaders.add(line);
+                line=in.readLine();
+            }
+        }
         
-        responsePOST=connector.getResponses("POST /R1 HTTP/1.1\015\012"+
-                "Host: localhost\015\012"+
-                "\015\012");
+        assertThat(postLine,equalTo(headLine));
+        assertThat(postDate,equalTo(headDate));
+        assertTrue(postHeaders.equals(headHeaders));
         
-        assertTrue(responsePOST.startsWith(responseHEAD.substring(0,responseHEAD.length()-2)));
-        assertTrue(responsePOST.length()>responseHEAD.length());
-
     }
 
     @Test
-    public void testBad() throws Exception
+    public void testBadHostPort() throws Exception
     {
-        try
-        {
-            ((StdErrLog)Log.getLogger(AbstractHttpConnection.class)).setHideStacks(true);
+        Log.getLogger(HttpParser.class).info("badMessage: Number formate exception expected ...");
+        String response;
 
-            String response;
-            
-            response=connector.getResponses("GET % HTTP/1.1\n"+
-                    "Host: localhost\n"+
+        response=connector.getResponses("GET http://localhost:EXPECTED_NUMBER_FORMAT_EXCEPTION/ HTTP/1.1\n"+
+            "Host: localhost\n"+
+            "Connection: close\015\012"+
             "\015\012");
-            checkContains(response,0,"HTTP/1.1 400");
+        checkContains(response,0,"HTTP/1.1 400");
+    }
 
-            response=connector.getResponses("GET http://localhost:WRONG/ HTTP/1.1\n"+
-                    "Host: localhost\n"+
-            "\015\012");
-            checkContains(response,0,"HTTP/1.1 400");
+    @Test
+    public void testBadURIencoding() throws Exception
+    {
+        Log.getLogger(HttpParser.class).info("badMessage: bad encoding expected ...");
+        String response;
 
-            response=connector.getResponses("GET /bad/encoding%1 HTTP/1.1\n"+
-                    "Host: localhost\n"+
+        response=connector.getResponses("GET /bad/encoding%1 HTTP/1.1\n"+
+            "Host: localhost\n"+
+            "Connection: close\n"+
             "\015\012");
-            checkContains(response,0,"HTTP/1.1 400");
+        checkContains(response,0,"HTTP/1.1 400");
+    }
+
+    @Test
+    public void testBadUTF8FallsbackTo8859() throws Exception
+    {
+        Log.getLogger(HttpParser.class).info("badMessage: bad encoding expected ...");
+        String response;
 
-            response=connector.getResponses("GET /foo/bar%c0%00 HTTP/1.1\n"+
-                    "Host: localhost\n"+
+        response=connector.getResponses("GET /foo/bar%c0%00 HTTP/1.1\n"+
+            "Host: localhost\n"+
+            "Connection: close\n"+
             "\015\012");
-            checkContains(response,0,"HTTP/1.1 200"); //now fallback to iso-8859-1
+        checkContains(response,0,"HTTP/1.1 200"); //now fallback to iso-8859-1
 
-            response=connector.getResponses("GET /bad/utf8%c1 HTTP/1.1\n"+
-                    "Host: localhost\n"+
+        response=connector.getResponses("GET /bad/utf8%c1 HTTP/1.1\n"+
+            "Host: localhost\n"+
+            "Connection: close\n"+
             "\015\012");
-            checkContains(response,0,"HTTP/1.1 200"); //now fallback to iso-8859-1
-        }
-        finally
-        {
-            ((StdErrLog)Log.getLogger(AbstractHttpConnection.class)).setHideStacks(false);
-        }
+        checkContains(response,0,"HTTP/1.1 200"); //now fallback to iso-8859-1
     }
 
     @Test
     public void testAutoFlush() throws Exception
     {
         String response=null;
-            int offset=0;
+        int offset=0;
 
-            offset=0;
-            response=connector.getResponses("GET /R1 HTTP/1.1\n"+
-                                           "Host: localhost\n"+
-                                           "Transfer-Encoding: chunked\n"+
-                                           "Content-Type: text/plain\n"+
-                                           "\015\012"+
-                                           "5;\015\012"+
-                                           "12345\015\012"+
-                                           "0;\015\012\015\012");
-            offset = checkContains(response,offset,"HTTP/1.1 200");
-            checkNotContained(response,offset,"IgnoreMe");
-            offset = checkContains(response,offset,"/R1");
-            offset = checkContains(response,offset,"12345");
+        offset=0;
+        response=connector.getResponses("GET /R1 HTTP/1.1\n"+
+            "Host: localhost\n"+
+            "Transfer-Encoding: chunked\n"+
+            "Content-Type: text/plain\n"+
+            "Connection: close\n"+
+            "\015\012"+
+            "5;\015\012"+
+            "12345\015\012"+
+            "0;\015\012\015\012");
+        offset = checkContains(response,offset,"HTTP/1.1 200");
+        checkNotContained(response,offset,"IgnoreMe");
+        offset = checkContains(response,offset,"/R1");
+        offset = checkContains(response,offset,"12345");
+    }
+    
+    @Test
+    public void testEmptyFlush() throws Exception
+    {
+        server.stop();
+        server.setHandler(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                response.setStatus(200);
+                OutputStream out =response.getOutputStream();
+                out.flush();
+                out.flush();
+            }
+        });
+        server.start();
+        
+        String response=connector.getResponses("GET / HTTP/1.1\n"+
+            "Host: localhost\n"+
+            "Connection: close\n"+
+            "\n");
+        
+        assertThat(response, Matchers.containsString("200 OK"));
     }
 
     @Test
-    public void testCharset()
+    public void testCharset() throws Exception
     {
 
         String response=null;
@@ -302,6 +428,7 @@ public class HttpConnectionTest
                                            "Host: localhost\n"+
                                            "Transfer-Encoding: chunked\n"+
                                            "Content-Type: text/plain; charset=utf-8\n"+
+                                           "Connection: close\n"+
                                            "\015\012"+
                                            "5;\015\012"+
                                            "12345\015\012"+
@@ -316,12 +443,13 @@ public class HttpConnectionTest
                                            "Host: localhost\n"+
                                            "Transfer-Encoding: chunked\n"+
                                            "Content-Type: text/plain; charset =  iso-8859-1 ; other=value\n"+
+                                           "Connection: close\n"+
                                            "\015\012"+
                                            "5;\015\012"+
                                            "12345\015\012"+
                                            "0;\015\012\015\012");
             offset = checkContains(response,offset,"HTTP/1.1 200");
-            offset = checkContains(response,offset,"encoding=iso-8859-1");
+            offset = checkContains(response,offset,"encoding=ISO-8859-1");
             offset = checkContains(response,offset,"/R1");
             offset = checkContains(response,offset,"12345");
 
@@ -330,6 +458,7 @@ public class HttpConnectionTest
                                            "Host: localhost\n"+
                                            "Transfer-Encoding: chunked\n"+
                                            "Content-Type: text/plain; charset=unknown\n"+
+                                           "Connection: close\n"+
                                            "\015\012"+
                                            "5;\015\012"+
                                            "12345\015\012"+
@@ -343,23 +472,90 @@ public class HttpConnectionTest
         }
         catch(Exception e)
         {
-            e.printStackTrace();
-            assertTrue(false);
-            if (response!=null)
+            if(response != null)
                 System.err.println(response);
+            throw e;
         }
     }
 
     @Test
-    public void testUnconsumedError() throws Exception
+    public void testUnconsumed() throws Exception
     {
+        String response=null;
+        String requests=null;
+        int offset=0;
+
+        offset=0;
+        requests=
+        "GET /R1?read=4 HTTP/1.1\n"+
+        "Host: localhost\n"+
+        "Transfer-Encoding: chunked\n"+
+        "Content-Type: text/plain; charset=utf-8\n"+
+        "\015\012"+
+        "5;\015\012"+
+        "12345\015\012"+
+        "5;\015\012"+
+        "67890\015\012"+
+        "0;\015\012\015\012"+
+        "GET /R2 HTTP/1.1\n"+
+        "Host: localhost\n"+
+        "Content-Type: text/plain; charset=utf-8\n"+
+        "Content-Length: 10\n"+
+        "Connection: close\n"+
+        "\n"+
+        "abcdefghij\n";
+
+        response=connector.getResponses(requests);
+        
+        offset = checkContains(response,offset,"HTTP/1.1 200");
+        offset = checkContains(response,offset,"pathInfo=/R1");
+        offset = checkContains(response,offset,"1234");
+        checkNotContained(response,offset,"56789");
+        offset = checkContains(response,offset,"HTTP/1.1 200");
+        offset = checkContains(response,offset,"pathInfo=/R2");
+        offset = checkContains(response,offset,"encoding=UTF-8");
+        offset = checkContains(response,offset,"abcdefghij");
+    }
 
+    @Test
+    public void testUnconsumedTimeout() throws Exception
+    {
+        connector.setIdleTimeout(500);
         String response=null;
         String requests=null;
         int offset=0;
 
         offset=0;
-        requests="GET /R1?read=1&error=500 HTTP/1.1\n"+
+        requests=
+        "GET /R1?read=4 HTTP/1.1\n"+
+        "Host: localhost\n"+
+        "Transfer-Encoding: chunked\n"+
+        "Content-Type: text/plain; charset=utf-8\n"+
+        "\015\012"+
+        "5;\015\012"+
+        "12345\015\012";
+
+        long start=System.currentTimeMillis();
+        response=connector.getResponses(requests,2000,TimeUnit.MILLISECONDS);
+        if ((System.currentTimeMillis()-start)>=2000)
+            Assert.fail();
+        
+        offset = checkContains(response,offset,"HTTP/1.1 200");
+        offset = checkContains(response,offset,"pathInfo=/R1");
+        offset = checkContains(response,offset,"1234");
+        checkNotContained(response,offset,"56789");
+    }
+    
+    @Test
+    public void testUnconsumedErrorRead() throws Exception
+    {
+        String response=null;
+        String requests=null;
+        int offset=0;
+
+        offset=0;
+        requests=
+        "GET /R1?read=1&error=499 HTTP/1.1\n"+
         "Host: localhost\n"+
         "Transfer-Encoding: chunked\n"+
         "Content-Type: text/plain; charset=utf-8\n"+
@@ -373,16 +569,53 @@ public class HttpConnectionTest
         "Host: localhost\n"+
         "Content-Type: text/plain; charset=utf-8\n"+
         "Content-Length: 10\n"+
+        "Connection: close\n"+
         "\n"+
         "abcdefghij\n";
 
         response=connector.getResponses(requests);
-        offset = checkContains(response,offset,"HTTP/1.1 500");
+
+        offset = checkContains(response,offset,"HTTP/1.1 499");
         offset = checkContains(response,offset,"HTTP/1.1 200");
         offset = checkContains(response,offset,"/R2");
         offset = checkContains(response,offset,"encoding=UTF-8");
         offset = checkContains(response,offset,"abcdefghij");
+    }
+    
+    @Test
+    public void testUnconsumedErrorStream() throws Exception
+    {
+        String response=null;
+        String requests=null;
+        int offset=0;
+
+        offset=0;
+        requests=
+        "GET /R1?error=599 HTTP/1.1\n"+
+        "Host: localhost\n"+
+        "Transfer-Encoding: chunked\n"+
+        "Content-Type: application/data; charset=utf-8\n"+
+        "\015\012"+
+        "5;\015\012"+
+        "12345\015\012"+
+        "5;\015\012"+
+        "67890\015\012"+
+        "0;\015\012\015\012"+
+        "GET /R2 HTTP/1.1\n"+
+        "Host: localhost\n"+
+        "Content-Type: text/plain; charset=utf-8\n"+
+        "Content-Length: 10\n"+
+        "Connection: close\n"+
+        "\n"+
+        "abcdefghij\n";
+
+        response=connector.getResponses(requests);
 
+        offset = checkContains(response,offset,"HTTP/1.1 599");
+        offset = checkContains(response,offset,"HTTP/1.1 200");
+        offset = checkContains(response,offset,"/R2");
+        offset = checkContains(response,offset,"encoding=UTF-8");
+        offset = checkContains(response,offset,"abcdefghij");
     }
 
     @Test
@@ -410,10 +643,11 @@ public class HttpConnectionTest
         "\n"+
         "abcdefghij\n";
 
-        Logger logger=null;
+        Logger logger = Log.getLogger(HttpChannel.class);
         try
         {
-            ((StdErrLog)Log.getLogger(AbstractHttpConnection.class)).setHideStacks(true);
+            logger.info("EXPECTING: java.lang.IllegalStateException...");
+            ((StdErrLog)logger).setHideStacks(true);
             response=connector.getResponses(requests);
             offset = checkContains(response,offset,"HTTP/1.1 500");
             offset = checkContains(response,offset,"Connection: close");
@@ -421,12 +655,12 @@ public class HttpConnectionTest
         }
         finally
         {
-            ((StdErrLog)Log.getLogger(AbstractHttpConnection.class)).setHideStacks(false);
+            ((StdErrLog)logger).setHideStacks(false);
         }
     }
 
     @Test
-    public void testConnection()
+    public void testConnection() throws Exception
     {
         String response=null;
         try
@@ -447,10 +681,9 @@ public class HttpConnectionTest
         }
         catch (Exception e)
         {
-            e.printStackTrace();
-            assertTrue(false);
-            if (response!=null)
-                 System.err.println(response);
+            if(response != null)
+                System.err.println(response);
+            throw e;
         }
     }
 
@@ -490,7 +723,7 @@ public class HttpConnectionTest
     {
         String response = null;
         int offset = 0;
-        
+
         StringBuilder request = new StringBuilder();
         request.append("GET / HTTP/1.1\n");
         request.append("Host: localhost\n");
@@ -499,10 +732,10 @@ public class HttpConnectionTest
             request.append(String.format("X-Header-%04d: %08x\n", i, i));
         }
         request.append("\015\012");
-        
+
         response = connector.getResponses(request.toString());
-        System.err.println(response);
-        checkContains(response, offset, "HTTP/1.1 413");
+        offset = checkContains(response, offset, "HTTP/1.1 413");
+        checkContains(response, offset, "<h1>Bad Message 413</h1><pre>reason: Request Entity Too Large</pre>");
     }
 
     @Test
@@ -512,35 +745,31 @@ public class HttpConnectionTest
         for (int i=0;i<500;i++)
             str+="xxxxxxxxxxxx";
         final String longstr = str;
-        
-        String response = null;        
+        final CountDownLatch checkError = new CountDownLatch(1);
+        String response = null;
         server.stop();
         server.setHandler(new DumpHandler()
         {
+            @Override
             public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
             {
-                try
-                {
-                    baseRequest.setHandled(true);
-                    response.setHeader(HttpHeaders.CONTENT_TYPE,MimeTypes.TEXT_HTML);
-                    response.setHeader("LongStr", longstr);
-                    PrintWriter writer = response.getWriter();
-                    writer.write("<html><h1>FOO</h1></html>");
-                    writer.flush();
-                    if (!writer.checkError())
-                        throw new RuntimeException("SHOULD NOT GET HERE");
-                }
-                catch(Exception e)
-                {
-                    LOG.debug(e);
-                    LOG.info("correctly ignored "+e);
-                }
+                baseRequest.setHandled(true);
+                response.setHeader(HttpHeader.CONTENT_TYPE.toString(),MimeTypes.Type.TEXT_HTML.toString());
+                response.setHeader("LongStr", longstr);
+                PrintWriter writer = response.getWriter();
+                writer.write("<html><h1>FOO</h1></html>");
+                writer.flush();
+                if (writer.checkError())
+                    checkError.countDown();
+                response.flushBuffer();
             }
         });
         server.start();
 
         try
         {
+            ((StdErrLog)Log.getLogger(HttpChannel.class)).info("Excpect IOException: Response header too large...");
+            ((StdErrLog)Log.getLogger(HttpChannel.class)).setHideStacks(true);
             int offset = 0;
 
             response = connector.getResponses("GET / HTTP/1.1\n"+
@@ -549,56 +778,60 @@ public class HttpConnectionTest
              );
 
             checkContains(response, offset, "HTTP/1.1 500");
+            assertTrue(checkError.await(1,TimeUnit.SECONDS));
         }
         catch(Exception e)
         {
-            e.printStackTrace();
             if(response != null)
                 System.err.println(response);
-            fail("Exception");
+            throw e;
+        }
+        finally
+        {
+
+            ((StdErrLog)Log.getLogger(HttpChannel.class)).setHideStacks(false);
         }
     }
 
     @Test
-    public void testAsterisk()
+    public void testAsterisk() throws Exception
     {
         String response = null;
 
         try
         {
+            ((StdErrLog)HttpParser.LOG).setHideStacks(true);
             int offset=0;
 
             response=connector.getResponses("OPTIONS * HTTP/1.1\n"+
                                            "Host: localhost\n"+
                                            "Transfer-Encoding: chunked\n"+
                                            "Content-Type: text/plain; charset=utf-8\n"+
+                                           "Connection: close\n"+
                                            "\015\012"+
                                            "5;\015\012"+
                                            "12345\015\012"+
                                            "0;\015\012\015\012");
             offset = checkContains(response,offset,"HTTP/1.1 200");
-            offset = checkContains(response,offset,"*");
 
-            // to prevent the DumpHandler from picking this up and returning 200 OK
-            server.stop();
-            server.setHandler(null);
-            server.start();
             offset=0;
             response=connector.getResponses("GET * HTTP/1.1\n"+
                                            "Host: localhost\n"+
                                            "Transfer-Encoding: chunked\n"+
                                            "Content-Type: text/plain; charset=utf-8\n"+
+                                           "Connection: close\n"+
                                            "\015\012"+
                                            "5;\015\012"+
                                            "12345\015\012"+
                                            "0;\015\012\015\012");
-            offset = checkContains(response,offset,"HTTP/1.1 404 Not Found");
+            offset = checkContains(response,offset,"HTTP/1.1 400");
 
             offset=0;
             response=connector.getResponses("GET ** HTTP/1.1\n"+
                                            "Host: localhost\n"+
                                            "Transfer-Encoding: chunked\n"+
                                            "Content-Type: text/plain; charset=utf-8\n"+
+                                           "Connection: close\n"+
                                            "\015\012"+
                                            "5;\015\012"+
                                            "12345\015\012"+
@@ -607,16 +840,19 @@ public class HttpConnectionTest
         }
         catch (Exception e)
         {
-            e.printStackTrace();
-            assertTrue(false);
-            if (response!=null)
-                 System.err.println(response);
+            if(response != null)
+                System.err.println(response);
+            throw e;
+        }
+        finally
+        {
+            ((StdErrLog)HttpParser.LOG).setHideStacks(false);
         }
 
     }
 
     @Test
-    public void testCONNECT()
+    public void testCONNECT() throws Exception
     {
         String response = null;
 
@@ -626,49 +862,28 @@ public class HttpConnectionTest
 
             response=connector.getResponses("CONNECT www.webtide.com:8080 HTTP/1.1\n"+
                                            "Host: myproxy:8888\015\012"+
-                                           "\015\012");
+                                           "\015\012",200,TimeUnit.MILLISECONDS);
             checkContains(response,offset,"HTTP/1.1 200");
 
         }
         catch (Exception e)
         {
-            e.printStackTrace();
-            assertTrue(false);
-            if (response!=null)
-                 System.err.println(response);
+            if(response != null)
+                System.err.println(response);
+            throw e;
         }
 
     }
 
     private int checkContains(String s,int offset,String c)
     {
-        int o=s.indexOf(c,offset);
-        if (o<offset)
-        {
-            System.err.println("FAILED");
-            System.err.println("'"+c+"' not in:");
-            System.err.println(s.substring(offset));
-            System.err.flush();
-            System.out.println("--\n"+s);
-            System.out.flush();
-            assertTrue(false);
-        }
-        return o;
+        Assert.assertThat(s.substring(offset),Matchers.containsString(c));
+        return s.indexOf(c,offset);
     }
 
     private void checkNotContained(String s,int offset,String c)
     {
-        int o=s.indexOf(c,offset);
-        if (o>=offset)
-        {
-            System.err.println("FAILED");
-            System.err.println("'"+c+"' IS in:");
-            System.err.println(s.substring(offset));
-            System.err.flush();
-            System.out.println("--\n"+s);
-            System.out.flush();
-            assertTrue(false);
-        }
+        Assert.assertThat(s.substring(offset),Matchers.not(Matchers.containsString(c)));
     }
 }
 
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpManyWaysToAsyncCommitBadBehaviourTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpManyWaysToAsyncCommitBadBehaviourTest.java
new file mode 100644
index 0000000..3526827
--- /dev/null
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpManyWaysToAsyncCommitBadBehaviourTest.java
@@ -0,0 +1,124 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.concurrent.BrokenBarrierException;
+import java.util.concurrent.CyclicBarrier;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.toolchain.test.http.SimpleHttpResponse;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+//TODO: reset buffer tests
+//TODO: add protocol specific tests for connection: close and/or chunking
+
+ at RunWith(value = Parameterized.class)
+public class HttpManyWaysToAsyncCommitBadBehaviourTest extends AbstractHttpTest
+{
+    private final String CONTEXT_ATTRIBUTE = getClass().getName() + ".asyncContext";
+    private boolean dispatch; // if true we dispatch, otherwise we complete
+
+    @Parameterized.Parameters
+    public static Collection<Object[]> data()
+    {
+        Object[][] data = new Object[][]{{HttpVersion.HTTP_1_0.asString(), true}, {HttpVersion.HTTP_1_0.asString(),
+                false}, {HttpVersion.HTTP_1_1.asString(), true}, {HttpVersion.HTTP_1_1.asString(), false}};
+        return Arrays.asList(data);
+    }
+
+    public HttpManyWaysToAsyncCommitBadBehaviourTest(String httpVersion, boolean dispatch)
+    {
+        super(httpVersion);
+        this.httpVersion = httpVersion;
+        this.dispatch = dispatch;
+    }
+
+    @Test
+    public void testHandlerSetsHandledAndWritesSomeContent() throws Exception
+    {
+        server.setHandler(new SetHandledWriteSomeDataHandler(false));
+        server.start();
+
+        SimpleHttpResponse response = executeRequest();
+
+        assertThat("response code is 500", response.getCode(), is("500"));
+    }
+
+    private class SetHandledWriteSomeDataHandler extends ThrowExceptionOnDemandHandler
+    {
+        private SetHandledWriteSomeDataHandler(boolean throwException)
+        {
+            super(throwException);
+        }
+
+        @Override
+        public void handle(String target, Request baseRequest, final HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+        {
+            final CyclicBarrier resumeBarrier = new CyclicBarrier(1);
+            if (request.getAttribute(CONTEXT_ATTRIBUTE) == null)
+            {
+                final AsyncContext asyncContext = baseRequest.startAsync();
+                new Thread(new Runnable()
+                {
+                    @Override
+                    public void run()
+                    {
+                        try
+                        {
+                            asyncContext.getResponse().getWriter().write("foobar");
+                            if (dispatch)
+                                asyncContext.dispatch();
+                            else
+                                asyncContext.complete();
+                            resumeBarrier.await(5, TimeUnit.SECONDS);
+                        }
+                        catch (IOException | TimeoutException | InterruptedException | BrokenBarrierException e)
+                        {
+                            e.printStackTrace();
+                        }
+                    }
+                }).run();
+            }
+            try
+            {
+                resumeBarrier.await(5, TimeUnit.SECONDS);
+            }
+            catch (InterruptedException | BrokenBarrierException | TimeoutException e)
+            {
+                e.printStackTrace();
+            }
+            throw new TestCommitException();
+        }
+    }
+}
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpManyWaysToAsyncCommitTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpManyWaysToAsyncCommitTest.java
new file mode 100644
index 0000000..f4cf4a4
--- /dev/null
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpManyWaysToAsyncCommitTest.java
@@ -0,0 +1,817 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.nullValue;
+import static org.junit.Assert.assertThat;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collection;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.toolchain.test.http.SimpleHttpResponse;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+//TODO: reset buffer tests
+//TODO: add protocol specific tests for connection: close and/or chunking
+ at RunWith(value = Parameterized.class)
+public class HttpManyWaysToAsyncCommitTest extends AbstractHttpTest
+{
+    private final String CONTEXT_ATTRIBUTE = getClass().getName() + ".asyncContext";
+    private boolean dispatch; // if true we dispatch, otherwise we complete
+
+    @Parameterized.Parameters
+    public static Collection<Object[]> data()
+    {
+        Object[][] data = new Object[][]
+            {
+            {HttpVersion.HTTP_1_0.asString(), true}, 
+            {HttpVersion.HTTP_1_1.asString(), true}, 
+            {HttpVersion.HTTP_1_0.asString(), false}, 
+            {HttpVersion.HTTP_1_1.asString(), false}
+            };
+        return Arrays.asList(data);
+    }
+
+    public HttpManyWaysToAsyncCommitTest(String httpVersion, boolean dispatch)
+    {
+        super(httpVersion);
+        this.httpVersion = httpVersion;
+        this.dispatch = dispatch;
+    }
+
+    @Test
+    public void testHandlerDoesNotSetHandled() throws Exception
+    {
+        DoesNotSetHandledHandler handler = new DoesNotSetHandledHandler(false);
+        server.setHandler(handler);
+        server.start();
+
+        SimpleHttpResponse response = executeRequest();
+
+        assertThat("response code is 404", response.getCode(), is("404"));
+        assertThat("no exceptions", handler.failure(), is(nullValue()));
+    }
+
+    @Test
+    public void testHandlerDoesNotSetHandledAndThrow() throws Exception
+    {
+        DoesNotSetHandledHandler handler = new DoesNotSetHandledHandler(true);
+        server.setHandler(handler);
+        server.start();
+
+        SimpleHttpResponse response = executeRequest();
+
+        assertThat("response code is 500", response.getCode(), is("500"));
+        assertThat("no exceptions", handler.failure(), is(nullValue()));
+    }
+
+    private class DoesNotSetHandledHandler extends ThrowExceptionOnDemandHandler
+    {
+        private DoesNotSetHandledHandler(boolean throwException)
+        {
+            super(throwException);
+        }
+
+        @Override
+        public void handle(String target, Request baseRequest, final HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+        {
+            if (request.getAttribute(CONTEXT_ATTRIBUTE) == null)
+            {
+                final AsyncContext asyncContext = baseRequest.startAsync();
+                request.setAttribute(CONTEXT_ATTRIBUTE, asyncContext);
+                new Thread(new Runnable()
+                {
+                    @Override
+                    public void run()
+                    {
+                        if (dispatch)
+                            asyncContext.dispatch();
+                        else
+                            asyncContext.complete();
+                    }
+                }).run();
+            }
+            super.handle(target, baseRequest, request, response);
+        }
+    }
+
+    @Test
+    public void testHandlerSetsHandledTrueOnly() throws Exception
+    {
+        OnlySetHandledHandler handler = new OnlySetHandledHandler(false);
+        server.setHandler(handler);
+        server.start();
+
+        SimpleHttpResponse response = executeRequest();
+
+        assertThat("response code is 200", response.getCode(), is("200"));
+        assertHeader(response, "content-length", "0");
+        assertThat("no exceptions", handler.failure(), is(nullValue()));
+    }
+
+    @Test
+    public void testHandlerSetsHandledTrueOnlyAndThrow() throws Exception
+    {
+        OnlySetHandledHandler handler = new OnlySetHandledHandler(true);
+        server.setHandler(handler);
+        server.start();
+
+        SimpleHttpResponse response = executeRequest();
+
+        assertThat("response code is 500", response.getCode(), is("500"));
+        assertThat("no exceptions", handler.failure(), is(nullValue()));
+    }
+
+    private class OnlySetHandledHandler extends ThrowExceptionOnDemandHandler
+    {
+        private OnlySetHandledHandler(boolean throwException)
+        {
+            super(throwException);
+        }
+
+        @Override
+        public void handle(String target, Request baseRequest, final HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+        {
+            if (request.getAttribute(CONTEXT_ATTRIBUTE) == null)
+            {
+                final AsyncContext asyncContext = baseRequest.startAsync();
+                request.setAttribute(CONTEXT_ATTRIBUTE, asyncContext);
+                new Thread(new Runnable()
+                {
+                    @Override
+                    public void run()
+                    {
+                        if (dispatch)
+                            asyncContext.dispatch();
+                        else
+                            asyncContext.complete();
+                    }
+                }).run();
+            }
+            baseRequest.setHandled(true);
+            super.handle(target, baseRequest, request, response);
+        }
+    }
+
+    @Test
+    public void testHandlerSetsHandledAndWritesSomeContent() throws Exception
+    {
+        SetHandledWriteSomeDataHandler handler = new SetHandledWriteSomeDataHandler(false);
+        server.setHandler(handler);
+        server.start();
+
+        SimpleHttpResponse response = executeRequest();
+
+        assertThat("response code is 200", response.getCode(), is("200"));
+        assertHeader(response, "content-length", "6");
+        assertThat("no exceptions", handler.failure(), is(nullValue()));
+    }
+
+    @Test
+    public void testHandlerSetsHandledAndWritesSomeContentAndThrow() throws Exception
+    {
+        SetHandledWriteSomeDataHandler handler = new SetHandledWriteSomeDataHandler(true);
+        server.setHandler(handler);
+        server.start();
+
+        SimpleHttpResponse response = executeRequest();
+
+        assertThat("response code is 500", response.getCode(), is("500"));
+        assertThat("no exceptions", handler.failure(), is(nullValue()));
+    }
+
+    private class SetHandledWriteSomeDataHandler extends ThrowExceptionOnDemandHandler
+    {
+        private SetHandledWriteSomeDataHandler(boolean throwException)
+        {
+            super(throwException);
+        }
+
+        @Override
+        public void handle(String target, Request baseRequest, final HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+        {
+            if (request.getAttribute(CONTEXT_ATTRIBUTE) == null)
+            {
+                final AsyncContext asyncContext = baseRequest.startAsync();
+                request.setAttribute(CONTEXT_ATTRIBUTE, asyncContext);
+                new Thread(new Runnable()
+                {
+                    @Override
+                    public void run()
+                    {
+                        try
+                        {
+                            asyncContext.getResponse().getWriter().write("foobar");
+                            if (dispatch)
+                                asyncContext.dispatch();
+                            else
+                                asyncContext.complete();
+                        }
+                        catch (IOException e)
+                        {
+                            markFailed(e);
+                        }
+                    }
+                }).run();
+            }
+            baseRequest.setHandled(true);
+            super.handle(target, baseRequest, request, response);
+        }
+    }
+
+    @Test
+    public void testHandlerExplicitFlush() throws Exception
+    {
+        ExplicitFlushHandler handler = new ExplicitFlushHandler(false);
+        server.setHandler(handler);
+        server.start();
+
+        SimpleHttpResponse response = executeRequest();
+
+
+        assertThat("response code is 200", response.getCode(), is("200"));
+        assertThat("no exceptions", handler.failure(), is(nullValue()));
+        if (!"HTTP/1.0".equals(httpVersion))
+            assertHeader(response, "transfer-encoding", "chunked");
+    }
+
+    @Test
+    public void testHandlerExplicitFlushAndThrow() throws Exception
+    {
+        ExplicitFlushHandler handler = new ExplicitFlushHandler(true);
+        server.setHandler(handler);
+        server.start();
+
+        SimpleHttpResponse response = executeRequest();
+
+        assertThat("response code is 200", response.getCode(), is("200"));
+        assertThat("no exceptions", handler.failure(), is(nullValue()));
+        if (!"HTTP/1.0".equals(httpVersion))
+            assertHeader(response, "transfer-encoding", "chunked");
+    }
+
+    private class ExplicitFlushHandler extends ThrowExceptionOnDemandHandler
+    {
+        private ExplicitFlushHandler(boolean throwException)
+        {
+            super(throwException);
+        }
+
+        @Override
+        public void handle(String target, Request baseRequest, final HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+        {
+            if (request.getAttribute(CONTEXT_ATTRIBUTE) == null)
+            {
+                final AsyncContext asyncContext = baseRequest.startAsync();
+                request.setAttribute(CONTEXT_ATTRIBUTE, asyncContext);
+                new Thread(new Runnable()
+                {
+                    @Override
+                    public void run()
+                    {
+                        try
+                        {
+                            ServletResponse asyncContextResponse = asyncContext.getResponse();
+                            asyncContextResponse.getWriter().write("foobar");
+                            asyncContextResponse.flushBuffer();
+                            if (dispatch)
+                                asyncContext.dispatch();
+                            else
+                                asyncContext.complete();
+                        }
+                        catch (IOException e)
+                        {
+                            markFailed(e);
+                        }
+                    }
+                }).run();
+            }
+            baseRequest.setHandled(true);
+            super.handle(target, baseRequest, request, response);
+        }
+    }
+
+    @Test
+    public void testHandledAndFlushWithoutContent() throws Exception
+    {
+        SetHandledAndFlushWithoutContentHandler handler = new SetHandledAndFlushWithoutContentHandler(false);
+        server.setHandler(handler);
+        server.start();
+
+        SimpleHttpResponse response = executeRequest();
+
+        assertThat("response code is 200", response.getCode(), is("200"));
+        assertThat("no exceptions", handler.failure(), is(nullValue()));
+        if (!"HTTP/1.0".equals(httpVersion))
+            assertHeader(response, "transfer-encoding", "chunked");
+    }
+
+    @Test
+    public void testHandledAndFlushWithoutContentAndThrow() throws Exception
+    {
+        SetHandledAndFlushWithoutContentHandler handler = new SetHandledAndFlushWithoutContentHandler(true);
+        server.setHandler(handler);
+        server.start();
+
+        SimpleHttpResponse response = executeRequest();
+
+        assertThat("response code is 200", response.getCode(), is("200"));
+        assertThat("no exceptions", handler.failure(), is(nullValue()));
+        if (!"HTTP/1.0".equals(httpVersion))
+            assertHeader(response, "transfer-encoding", "chunked");
+    }
+
+    private class SetHandledAndFlushWithoutContentHandler extends ThrowExceptionOnDemandHandler
+    {
+        private SetHandledAndFlushWithoutContentHandler(boolean throwException)
+        {
+            super(throwException);
+        }
+
+        @Override
+        public void handle(String target, Request baseRequest, final HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+        {
+            if (request.getAttribute(CONTEXT_ATTRIBUTE) == null)
+            {
+                final AsyncContext asyncContext = baseRequest.startAsync();
+                request.setAttribute(CONTEXT_ATTRIBUTE, asyncContext);
+                new Thread(new Runnable()
+                {
+                    @Override
+                    public void run()
+                    {
+                        try
+                        {
+                            asyncContext.getResponse().flushBuffer();
+                            if (dispatch)
+                                asyncContext.dispatch();
+                            else
+                                asyncContext.complete();
+                        }
+                        catch (IOException e)
+                        {
+                            markFailed(e);
+                        }
+                    }
+                }).run();
+            }
+            baseRequest.setHandled(true);
+            super.handle(target, baseRequest, request, response);
+        }
+    }
+
+    @Test
+    public void testWriteFlushWriteMore() throws Exception
+    {
+        WriteFlushWriteMoreHandler handler = new WriteFlushWriteMoreHandler(false);
+        server.setHandler(handler);
+        server.start();
+
+        SimpleHttpResponse response = executeRequest();
+
+        assertThat("response code is 200", response.getCode(), is("200"));
+        assertThat("no exceptions", handler.failure(), is(nullValue()));
+        if (!"HTTP/1.0".equals(httpVersion))
+            assertHeader(response, "transfer-encoding", "chunked"); // HTTP/1.0 does not do chunked.  it will just send content and close
+    }
+
+    @Test
+    public void testWriteFlushWriteMoreAndThrow() throws Exception
+    {
+        WriteFlushWriteMoreHandler handler = new WriteFlushWriteMoreHandler(true);
+        server.setHandler(handler);
+        server.start();
+
+        SimpleHttpResponse response = executeRequest();
+
+        assertThat("response code is 200", response.getCode(), is("200"));
+        assertThat("no exceptions", handler.failure(), is(nullValue()));
+        if (!"HTTP/1.0".equals(httpVersion))
+            assertHeader(response, "transfer-encoding", "chunked"); 
+    }
+
+    private class WriteFlushWriteMoreHandler extends ThrowExceptionOnDemandHandler
+    {
+        private WriteFlushWriteMoreHandler(boolean throwException)
+        {
+            super(throwException);
+        }
+
+        @Override
+        public void handle(String target, Request baseRequest, final HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+        {
+            if (request.getAttribute(CONTEXT_ATTRIBUTE) == null)
+            {
+                final AsyncContext asyncContext = baseRequest.startAsync();
+                request.setAttribute(CONTEXT_ATTRIBUTE, asyncContext);
+                new Thread(new Runnable()
+                {
+                    @Override
+                    public void run()
+                    {
+                        try
+                        {
+                            ServletResponse asyncContextResponse = asyncContext.getResponse();
+                            asyncContextResponse.getWriter().write("foo");
+                            asyncContextResponse.flushBuffer();
+                            asyncContextResponse.getWriter().write("bar");
+                            if (dispatch)
+                                asyncContext.dispatch();
+                            else
+                                asyncContext.complete();
+                        }
+                        catch (IOException e)
+                        {
+                            markFailed(e);
+                        }
+                    }
+                }).run();
+            }
+            baseRequest.setHandled(true);
+            super.handle(target, baseRequest, request, response);
+        }
+    }
+
+    @Test
+    public void testBufferOverflow() throws Exception
+    {
+        OverflowHandler handler = new OverflowHandler(false);
+        server.setHandler(handler);
+        server.start();
+
+        SimpleHttpResponse response = executeRequest();
+
+        assertThat("response code is 200", response.getCode(), is("200"));
+        assertResponseBody(response, "foobar");
+        if (!"HTTP/1.0".equals(httpVersion))
+            assertHeader(response, "transfer-encoding", "chunked");
+        assertThat("no exceptions", handler.failure(), is(nullValue()));
+    }
+
+    @Test
+    public void testBufferOverflowAndThrow() throws Exception
+    {
+        OverflowHandler handler = new OverflowHandler(true);
+        server.setHandler(handler);
+        server.start();
+
+        SimpleHttpResponse response = executeRequest();
+
+        // Buffer size is too small, so the content is written directly producing a 200 response
+        assertThat("response code is 200", response.getCode(), is("200"));
+        assertResponseBody(response, "foobar");
+        if (!"HTTP/1.0".equals(httpVersion))
+            assertHeader(response, "transfer-encoding", "chunked");
+        assertThat("no exceptions", handler.failure(), is(nullValue()));
+    }
+
+    private class OverflowHandler extends ThrowExceptionOnDemandHandler
+    {
+        private OverflowHandler(boolean throwException)
+        {
+            super(throwException);
+        }
+
+        @Override
+        public void handle(String target, Request baseRequest, final HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+        {
+            if (request.getAttribute(CONTEXT_ATTRIBUTE) == null)
+            {
+                final AsyncContext asyncContext = baseRequest.startAsync();
+                request.setAttribute(CONTEXT_ATTRIBUTE, asyncContext);
+                new Thread(new Runnable()
+                {
+                    @Override
+                    public void run()
+                    {
+                        try
+                        {
+                            ServletResponse asyncContextResponse = asyncContext.getResponse();
+                            asyncContextResponse.setBufferSize(3);
+                            asyncContextResponse.getWriter().write("foobar");
+                            if (dispatch)
+                                asyncContext.dispatch();
+                            else
+                                asyncContext.complete();
+                        }
+                        catch (IOException e)
+                        {
+                            markFailed(e);
+                        }
+                    }
+                }).run();
+            }
+            baseRequest.setHandled(true);
+            super.handle(target, baseRequest, request, response);
+        }
+    }
+
+    @Test
+    public void testSetContentLengthAndWriteExactlyThatAmountOfBytes() throws Exception
+    {
+        SetContentLengthAndWriteThatAmountOfBytesHandler handler = new SetContentLengthAndWriteThatAmountOfBytesHandler(false);
+        server.setHandler(handler);
+        server.start();
+
+        SimpleHttpResponse response = executeRequest();
+
+        assertThat("response code is 200", response.getCode(), is("200"));
+        assertThat("response body is foo", response.getBody(), is("foo"));
+        assertHeader(response, "content-length", "3");
+        assertThat("no exceptions", handler.failure(), is(nullValue()));
+    }
+
+    @Test
+    public void testSetContentLengthAndWriteExactlyThatAmountOfBytesAndThrow() throws Exception
+    {
+        SetContentLengthAndWriteThatAmountOfBytesHandler handler = new SetContentLengthAndWriteThatAmountOfBytesHandler(true);
+        server.setHandler(handler);
+        server.start();
+
+        SimpleHttpResponse response = executeRequest();
+
+        //TODO: should we expect 500 here?
+        assertThat("response code is 200", response.getCode(), is("200"));
+        assertThat("response body is foo", response.getBody(), is("foo"));
+        assertHeader(response, "content-length", "3");
+        assertThat("no exceptions", handler.failure(), is(nullValue()));
+    }
+
+    private class SetContentLengthAndWriteThatAmountOfBytesHandler extends ThrowExceptionOnDemandHandler
+    {
+        private SetContentLengthAndWriteThatAmountOfBytesHandler(boolean throwException)
+        {
+            super(throwException);
+        }
+
+        @Override
+        public void handle(String target, Request baseRequest, final HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+        {
+            if (request.getAttribute(CONTEXT_ATTRIBUTE) == null)
+            {
+                final AsyncContext asyncContext = baseRequest.startAsync();
+                request.setAttribute(CONTEXT_ATTRIBUTE, asyncContext);
+                new Thread(new Runnable()
+                {
+                    @Override
+                    public void run()
+                    {
+                        try
+                        {
+                            ServletResponse asyncContextResponse = asyncContext.getResponse();
+                            asyncContextResponse.setContentLength(3);
+                            asyncContextResponse.getWriter().write("foo");
+                            if (dispatch)
+                                asyncContext.dispatch();
+                            else
+                                asyncContext.complete();
+                        }
+                        catch (IOException e)
+                        {
+                            markFailed(e);
+                        }
+                    }
+                }).run();
+            }
+            baseRequest.setHandled(true);
+            super.handle(target, baseRequest, request, response);
+        }
+    }
+
+    @Test
+    public void testSetContentLengthAndWriteMoreBytes() throws Exception
+    {
+        SetContentLengthAndWriteMoreBytesHandler handler = new SetContentLengthAndWriteMoreBytesHandler(false);
+        server.setHandler(handler);
+        server.start();
+
+        SimpleHttpResponse response = executeRequest();
+
+        assertThat("response code is 200", response.getCode(), is("200"));
+        // jetty truncates the body when content-length is reached.! This is correct and desired behaviour?
+        assertThat("response body is foo", response.getBody(), is("foo"));
+        assertHeader(response, "content-length", "3");
+        assertThat("no exceptions", handler.failure(), is(nullValue()));
+    }
+
+    @Test
+    public void testSetContentLengthAndWriteMoreAndThrow() throws Exception
+    {
+        SetContentLengthAndWriteMoreBytesHandler handler = new SetContentLengthAndWriteMoreBytesHandler(true);
+        server.setHandler(handler);
+        server.start();
+
+        SimpleHttpResponse response = executeRequest();
+
+        // TODO: we throw before response is committed. should we expect 500?
+        assertThat("response code is 200", response.getCode(), is("200"));
+        assertThat("response body is foo", response.getBody(), is("foo"));
+        assertHeader(response, "content-length", "3");
+        assertThat("no exceptions", handler.failure(), is(nullValue()));
+    }
+
+    private class SetContentLengthAndWriteMoreBytesHandler extends ThrowExceptionOnDemandHandler
+    {
+        private SetContentLengthAndWriteMoreBytesHandler(boolean throwException)
+        {
+            super(throwException);
+        }
+
+        @Override
+        public void handle(String target, Request baseRequest, final HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+        {
+            if (request.getAttribute(CONTEXT_ATTRIBUTE) == null)
+            {
+                final AsyncContext asyncContext = baseRequest.startAsync();
+                request.setAttribute(CONTEXT_ATTRIBUTE, asyncContext);
+                new Thread(new Runnable()
+                {
+                    @Override
+                    public void run()
+                    {
+                        try
+                        {
+                            ServletResponse asyncContextResponse = asyncContext.getResponse();
+                            asyncContextResponse.setContentLength(3);
+                            asyncContextResponse.getWriter().write("foobar");
+                            if (dispatch)
+                                asyncContext.dispatch();
+                            else
+                                asyncContext.complete();
+                        }
+                        catch (IOException e)
+                        {
+                            markFailed(e);
+                        }
+                    }
+                }).run();
+            }
+            baseRequest.setHandled(true);
+            super.handle(target, baseRequest, request, response);
+        }
+    }
+
+    @Test
+    public void testWriteAndSetContentLength() throws Exception
+    {
+        WriteAndSetContentLengthHandler handler = new WriteAndSetContentLengthHandler(false);
+        server.setHandler(handler);
+        server.start();
+
+        SimpleHttpResponse response = executeRequest();
+
+        assertThat("response code is 200", response.getCode(), is("200"));
+        assertThat("no exceptions", handler.failure(), is(nullValue()));
+        //TODO: jetty ignores setContentLength and sends transfer-encoding header. Correct?
+    }
+
+    @Test
+    public void testWriteAndSetContentLengthAndThrow() throws Exception
+    {
+        WriteAndSetContentLengthHandler handler = new WriteAndSetContentLengthHandler(true);
+        server.setHandler(handler);
+        server.start();
+
+        SimpleHttpResponse response = executeRequest();
+
+        assertThat("response code is 200", response.getCode(), is("200"));
+        assertThat("no exceptions", handler.failure(), is(nullValue()));
+    }
+
+    private class WriteAndSetContentLengthHandler extends ThrowExceptionOnDemandHandler
+    {
+        private WriteAndSetContentLengthHandler(boolean throwException)
+        {
+            super(throwException);
+        }
+
+        @Override
+        public void handle(String target, Request baseRequest, final HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+        {
+            if (request.getAttribute(CONTEXT_ATTRIBUTE) == null)
+            {
+                final AsyncContext asyncContext = baseRequest.startAsync();
+                request.setAttribute(CONTEXT_ATTRIBUTE, asyncContext);
+                new Thread(new Runnable()
+                {
+                    @Override
+                    public void run()
+                    {
+                        try
+                        {
+                            ServletResponse asyncContextResponse = asyncContext.getResponse();
+                            asyncContextResponse.getWriter().write("foo");
+                            asyncContextResponse.setContentLength(3); // This should commit the response
+                            if (dispatch)
+                                asyncContext.dispatch();
+                            else
+                                asyncContext.complete();
+                        }
+                        catch (IOException e)
+                        {
+                            markFailed(e);
+                        }
+                    }
+                }).run();
+            }
+            baseRequest.setHandled(true);
+            super.handle(target, baseRequest, request, response);
+        }
+    }
+
+    @Test
+    public void testWriteAndSetContentLengthTooSmall() throws Exception
+    {
+        WriteAndSetContentLengthTooSmallHandler handler = new WriteAndSetContentLengthTooSmallHandler(false);
+        server.setHandler(handler);
+        server.start();
+
+        SimpleHttpResponse response = executeRequest();
+
+        // Setting a content-length too small throws an IllegalStateException
+        assertThat("response code is 500", response.getCode(), is("500"));
+        assertThat("no exceptions", handler.failure(), is(nullValue()));
+    }
+
+    @Test
+    public void testWriteAndSetContentLengthTooSmallAndThrow() throws Exception
+    {
+        WriteAndSetContentLengthTooSmallHandler handler = new WriteAndSetContentLengthTooSmallHandler(true);
+        server.setHandler(handler);
+        server.start();
+
+        SimpleHttpResponse response = executeRequest();
+
+        // Setting a content-length too small throws an IllegalStateException
+        assertThat(response.getCode(), is("500"));
+        assertThat("no exceptions", handler.failure(), is(nullValue()));
+    }
+
+    private class WriteAndSetContentLengthTooSmallHandler extends ThrowExceptionOnDemandHandler
+    {
+        private WriteAndSetContentLengthTooSmallHandler(boolean throwException)
+        {
+            super(throwException);
+        }
+
+        @Override
+        public void handle(String target, Request baseRequest, final HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+        {
+            if (request.getAttribute(CONTEXT_ATTRIBUTE) == null)
+            {
+                final AsyncContext asyncContext = baseRequest.startAsync();
+                request.setAttribute(CONTEXT_ATTRIBUTE, asyncContext);
+                new Thread(new Runnable()
+                {
+                    @Override
+                    public void run()
+                    {
+                        try
+                        {
+                            ServletResponse asyncContextResponse = asyncContext.getResponse();
+                            asyncContextResponse.getWriter().write("foobar");
+                            asyncContextResponse.setContentLength(3);
+                            if (dispatch)
+                                asyncContext.dispatch();
+                            else
+                                asyncContext.complete();
+                        }
+                        catch (IOException e)
+                        {
+                            markFailed(e);
+                        }
+                    }
+                }).run();
+            }
+            baseRequest.setHandled(true);
+            super.handle(target, baseRequest, request, response);
+        }
+    }
+}
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpManyWaysToCommitTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpManyWaysToCommitTest.java
new file mode 100644
index 0000000..3eeef86
--- /dev/null
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpManyWaysToCommitTest.java
@@ -0,0 +1,593 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+import static org.junit.Assert.assertThat;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collection;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.toolchain.test.http.SimpleHttpResponse;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+//TODO: reset buffer tests
+//TODO: add protocol specific tests for connection: close and/or chunking
+ at RunWith(value = Parameterized.class)
+public class HttpManyWaysToCommitTest extends AbstractHttpTest
+{
+    @Parameterized.Parameters
+    public static Collection<Object[]> data()
+    {
+        Object[][] data = new Object[][]{{HttpVersion.HTTP_1_0.asString()}, {HttpVersion.HTTP_1_1.asString()}};
+        return Arrays.asList(data);
+    }
+
+    public HttpManyWaysToCommitTest(String httpVersion)
+    {
+        super(httpVersion);
+    }
+
+    @Test
+    public void testHandlerDoesNotSetHandled() throws Exception
+    {
+        server.setHandler(new DoesNotSetHandledHandler(false));
+        server.start();
+
+        SimpleHttpResponse response = executeRequest();
+
+        assertThat("response code is 404", response.getCode(), is("404"));
+    }
+
+    @Test
+    public void testHandlerDoesNotSetHandledAndThrow() throws Exception
+    {
+        server.setHandler(new DoesNotSetHandledHandler(true));
+        server.start();
+
+        SimpleHttpResponse response = executeRequest();
+
+        assertThat("response code is 500", response.getCode(), is("500"));
+    }
+
+    private class DoesNotSetHandledHandler extends ThrowExceptionOnDemandHandler
+    {
+        private DoesNotSetHandledHandler(boolean throwException)
+        {
+            super(throwException);
+        }
+
+        @Override
+        public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+        {
+            baseRequest.setHandled(false); // not needed, but lets be explicit about what the test does
+            super.handle(target, baseRequest, request, response);
+        }
+    }
+
+    @Test
+    public void testHandlerSetsHandledTrueOnly() throws Exception
+    {
+        server.setHandler(new OnlySetHandledHandler(false));
+        server.start();
+
+        SimpleHttpResponse response = executeRequest();
+
+        assertThat("response code is 200", response.getCode(), is("200"));
+        assertHeader(response, "content-length", "0");
+    }
+
+    @Test
+    public void testHandlerSetsHandledTrueOnlyAndThrow() throws Exception
+    {
+        server.setHandler(new OnlySetHandledHandler(true));
+        server.start();
+
+        SimpleHttpResponse response = executeRequest();
+
+        assertThat("response code is 500", response.getCode(), is("500"));
+    }
+
+    private class OnlySetHandledHandler extends ThrowExceptionOnDemandHandler
+    {
+        private OnlySetHandledHandler(boolean throwException)
+        {
+            super(throwException);
+        }
+
+        @Override
+        public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+        {
+            baseRequest.setHandled(true);
+            super.handle(target, baseRequest, request, response);
+        }
+    }
+
+    @Test
+    public void testHandlerSetsHandledAndWritesSomeContent() throws Exception
+    {
+        server.setHandler(new SetHandledWriteSomeDataHandler(false));
+        server.start();
+
+        SimpleHttpResponse response = executeRequest();
+
+        assertThat("response code is 200", response.getCode(), is("200"));
+        assertResponseBody(response, "foobar");
+        assertHeader(response, "content-length", "6");
+    }
+
+    @Test
+    public void testHandlerSetsHandledAndWritesSomeContentAndThrow() throws Exception
+    {
+        server.setHandler(new SetHandledWriteSomeDataHandler(true));
+        server.start();
+
+        SimpleHttpResponse response = executeRequest();
+
+        assertThat("response code is 500", response.getCode(), is("500"));
+        assertThat("response body is not foobar", response.getBody(), not(is("foobar")));
+    }
+
+    private class SetHandledWriteSomeDataHandler extends ThrowExceptionOnDemandHandler
+    {
+        private SetHandledWriteSomeDataHandler(boolean throwException)
+        {
+            super(throwException);
+        }
+
+        @Override
+        public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+        {
+            baseRequest.setHandled(true);
+            response.getWriter().write("foobar");
+            super.handle(target, baseRequest, request, response);
+        }
+    }
+
+    @Test
+    public void testHandlerExplicitFlush() throws Exception
+    {
+        server.setHandler(new ExplicitFlushHandler(false));
+        server.start();
+
+        SimpleHttpResponse response = executeRequest();
+
+        assertThat("response code is 200", response.getCode(), is("200"));
+        assertResponseBody(response, "foobar");
+        if (!"HTTP/1.0".equals(httpVersion))
+            assertHeader(response, "transfer-encoding", "chunked");
+    }
+
+    @Test
+    public void testHandlerExplicitFlushAndThrow() throws Exception
+    {
+        server.setHandler(new ExplicitFlushHandler(true));
+        server.start();
+
+        SimpleHttpResponse response = executeRequest();
+
+        // Since the 200 was committed, the 500 did not get the chance to be written
+        assertThat("response code is 200", response.getCode(), is("200"));
+        assertThat("response body is foobar", response.getBody(), is("foobar"));
+        if (!"HTTP/1.0".equals(httpVersion))
+            assertHeader(response, "transfer-encoding", "chunked");
+    }
+
+    private class ExplicitFlushHandler extends ThrowExceptionOnDemandHandler
+    {
+        private ExplicitFlushHandler(boolean throwException)
+        {
+            super(throwException);
+        }
+
+        @Override
+        public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+        {
+            baseRequest.setHandled(true);
+            response.getWriter().write("foobar");
+            response.flushBuffer();
+            super.handle(target, baseRequest, request, response);
+        }
+    }
+
+    @Test
+    public void testHandledAndFlushWithoutContent() throws Exception
+    {
+        server.setHandler(new SetHandledAndFlushWithoutContentHandler(false));
+        server.start();
+
+        SimpleHttpResponse response = executeRequest();
+
+        assertThat("response code is 200", response.getCode(), is("200"));
+        if (!"HTTP/1.0".equals(httpVersion))
+            assertHeader(response, "transfer-encoding", "chunked");
+    }
+
+    @Test
+    public void testHandledAndFlushWithoutContentAndThrow() throws Exception
+    {
+        server.setHandler(new SetHandledAndFlushWithoutContentHandler(true));
+        server.start();
+
+        SimpleHttpResponse response = executeRequest();
+
+        assertThat("response code is 200", response.getCode(), is("200"));
+        if (!"HTTP/1.0".equals(httpVersion))
+            assertHeader(response, "transfer-encoding", "chunked");
+    }
+
+    private class SetHandledAndFlushWithoutContentHandler extends ThrowExceptionOnDemandHandler
+    {
+        private SetHandledAndFlushWithoutContentHandler(boolean throwException)
+        {
+            super(throwException);
+        }
+
+        @Override
+        public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+        {
+            baseRequest.setHandled(true);
+            response.flushBuffer();
+            super.handle(target, baseRequest, request, response);
+        }
+    }
+
+    @Test
+    public void testHandledWriteFlushWriteMore() throws Exception
+    {
+        server.setHandler(new WriteFlushWriteMoreHandler(false));
+        server.start();
+
+        SimpleHttpResponse response = executeRequest();
+
+        assertThat("response code is 200", response.getCode(), is("200"));
+        assertResponseBody(response, "foobar");
+        if (!"HTTP/1.0".equals(httpVersion))
+            assertHeader(response, "transfer-encoding", "chunked");
+    }
+
+    @Test
+    public void testHandledWriteFlushWriteMoreAndThrow() throws Exception
+    {
+        server.setHandler(new WriteFlushWriteMoreHandler(true));
+        server.start();
+
+        SimpleHttpResponse response = executeRequest();
+
+        // Since the 200 was committed, the 500 did not get the chance to be written
+        assertThat("response code is 200", response.getCode(), is("200"));
+        assertThat("response code is 200", response.getCode(), is("200"));
+        if (!"HTTP/1.0".equals(httpVersion))
+            assertHeader(response, "transfer-encoding", "chunked");
+    }
+
+    private class WriteFlushWriteMoreHandler extends ThrowExceptionOnDemandHandler
+    {
+        private WriteFlushWriteMoreHandler(boolean throwException)
+        {
+            super(throwException);
+        }
+
+        @Override
+        public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+        {
+            baseRequest.setHandled(true);
+            response.getWriter().write("foo");
+            response.flushBuffer();
+            response.getWriter().write("bar");
+            super.handle(target, baseRequest, request, response);
+        }
+    }
+
+    @Test
+    public void testHandledOverflow() throws Exception
+    {
+        server.setHandler(new OverflowHandler(false));
+        server.start();
+
+        SimpleHttpResponse response = executeRequest();
+
+        assertThat("response code is 200", response.getCode(), is("200"));
+        assertResponseBody(response, "foobar");
+        if (!"HTTP/1.0".equals(httpVersion))
+            assertHeader(response, "transfer-encoding", "chunked");
+    }
+    
+    @Test
+    public void testHandledOverflow2() throws Exception
+    {
+        server.setHandler(new Overflow2Handler(false));
+        server.start();
+
+        SimpleHttpResponse response = executeRequest();
+
+        assertThat("response code is 200", response.getCode(), is("200"));
+        assertResponseBody(response, "foobarfoobar");
+        if (!"HTTP/1.0".equals(httpVersion))
+            assertHeader(response, "transfer-encoding", "chunked");
+    }
+    
+    @Test
+    public void testHandledOverflow3() throws Exception
+    {
+        server.setHandler(new Overflow3Handler(false));
+        server.start();
+
+        SimpleHttpResponse response = executeRequest();
+
+        assertThat("response code is 200", response.getCode(), is("200"));
+        assertResponseBody(response, "foobarfoobar");
+        if (!"HTTP/1.0".equals(httpVersion))
+            assertHeader(response, "transfer-encoding", "chunked");
+    }
+
+    @Test
+    public void testHandledBufferOverflowAndThrow() throws Exception
+    {
+        server.setHandler(new OverflowHandler(true));
+        server.start();
+
+        SimpleHttpResponse response = executeRequest();
+
+        // Response was committed when we throw, so 200 expected
+        assertThat("response code is 200", response.getCode(), is("200"));
+        assertResponseBody(response, "foobar");
+        if (!"HTTP/1.0".equals(httpVersion))
+            assertHeader(response, "transfer-encoding", "chunked");
+    }
+
+    private class OverflowHandler extends ThrowExceptionOnDemandHandler
+    {
+        private OverflowHandler(boolean throwException)
+        {
+            super(throwException);
+        }
+
+        @Override
+        public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+        {
+            baseRequest.setHandled(true);
+            response.setBufferSize(4);
+            response.getWriter().write("foobar");
+            super.handle(target, baseRequest, request, response);
+        }
+    }
+
+    private class Overflow2Handler extends ThrowExceptionOnDemandHandler
+    {
+        private Overflow2Handler(boolean throwException)
+        {
+            super(throwException);
+        }
+
+        @Override
+        public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+        {
+            baseRequest.setHandled(true);
+            response.setBufferSize(8);
+            response.getWriter().write("fo");
+            response.getWriter().write("obarfoobar");
+            super.handle(target, baseRequest, request, response);
+        }
+    }
+    
+    private class Overflow3Handler extends ThrowExceptionOnDemandHandler
+    {
+        private Overflow3Handler(boolean throwException)
+        {
+            super(throwException);
+        }
+
+        @Override
+        public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+        {
+            baseRequest.setHandled(true);
+            response.setBufferSize(8);
+            response.getWriter().write("fo");
+            response.getWriter().write("ob");
+            response.getWriter().write("ar");
+            response.getWriter().write("fo");
+            response.getWriter().write("ob");
+            response.getWriter().write("ar");
+            super.handle(target, baseRequest, request, response);
+        }
+    }
+
+    @Test
+    public void testSetContentLengthAndWriteExactlyThatAmountOfBytes() throws Exception
+    {
+        server.setHandler(new SetContentLengthAndWriteThatAmountOfBytesHandler(false));
+        server.start();
+
+        SimpleHttpResponse response = executeRequest();
+
+        assertThat("response code is 200", response.getCode(), is("200"));
+        assertThat("response body is foo", response.getBody(), is("foo"));
+        assertHeader(response, "content-length", "3");
+    }
+
+    @Test
+    public void testSetContentLengthAndWriteExactlyThatAmountOfBytesAndThrow() throws Exception
+    {
+        server.setHandler(new SetContentLengthAndWriteThatAmountOfBytesHandler(true));
+        server.start();
+
+        SimpleHttpResponse response = executeRequest();
+
+        // Setting the content-length and then writing the bytes commits the response
+        assertThat("response code is 200", response.getCode(), is("200"));
+        assertThat("response body is foo", response.getBody(), is("foo"));
+    }
+
+    private class SetContentLengthAndWriteThatAmountOfBytesHandler extends ThrowExceptionOnDemandHandler
+    {
+        private SetContentLengthAndWriteThatAmountOfBytesHandler(boolean throwException)
+        {
+            super(throwException);
+        }
+
+        @Override
+        public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+        {
+            baseRequest.setHandled(true);
+            response.setContentLength(3);
+            response.getWriter().write("foo");
+            super.handle(target, baseRequest, request, response);
+        }
+    }
+
+    @Test
+    public void testSetContentLengthAndWriteMoreBytes() throws Exception
+    {
+        server.setHandler(new SetContentLengthAndWriteMoreBytesHandler(false));
+        server.start();
+
+        SimpleHttpResponse response = executeRequest();
+
+        assertThat("response code is 200", response.getCode(), is("200"));
+        assertThat("response body is foo", response.getBody(), is("foo"));
+        assertHeader(response, "content-length", "3");
+    }
+
+    @Test
+    public void testSetContentLengthAndWriteMoreAndThrow() throws Exception
+    {
+        server.setHandler(new SetContentLengthAndWriteMoreBytesHandler(true));
+        server.start();
+
+        SimpleHttpResponse response = executeRequest();
+
+        // Setting the content-length and then writing the bytes commits the response
+        assertThat("response code is 200", response.getCode(), is("200"));
+        assertThat("response body is foo", response.getBody(), is("foo"));
+    }
+
+    private class SetContentLengthAndWriteMoreBytesHandler extends ThrowExceptionOnDemandHandler
+    {
+        private SetContentLengthAndWriteMoreBytesHandler(boolean throwException)
+        {
+            super(throwException);
+        }
+
+        @Override
+        public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+        {
+            baseRequest.setHandled(true);
+            response.setContentLength(3);
+            // Only "foo" will get written and "bar" will be discarded
+            response.getWriter().write("foobar");
+            super.handle(target, baseRequest, request, response);
+        }
+    }
+
+    @Test
+    public void testWriteAndSetContentLength() throws Exception
+    {
+        server.setHandler(new WriteAndSetContentLengthHandler(false));
+        server.start();
+
+        SimpleHttpResponse response = executeRequest();
+
+        assertThat("response code is 200", response.getCode(), is("200"));
+        assertThat("response body is foo", response.getBody(), is("foo"));
+        assertHeader(response, "content-length", "3");
+    }
+
+    @Test
+    public void testWriteAndSetContentLengthAndThrow() throws Exception
+    {
+        server.setHandler(new WriteAndSetContentLengthHandler(true));
+        server.start();
+
+        SimpleHttpResponse response = executeRequest();
+
+        // Writing the bytes and then setting the content-length commits the response
+        assertThat("response code is 200", response.getCode(), is("200"));
+        assertThat("response body is foo", response.getBody(), is("foo"));
+    }
+
+    private class WriteAndSetContentLengthHandler extends ThrowExceptionOnDemandHandler
+    {
+        private WriteAndSetContentLengthHandler(boolean throwException)
+        {
+            super(throwException);
+        }
+
+        @Override
+        public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+        {
+            baseRequest.setHandled(true);
+            response.getWriter().write("foo");
+            response.setContentLength(3);
+            super.handle(target, baseRequest, request, response);
+        }
+    }
+
+    @Test
+    public void testWriteAndSetContentLengthTooSmall() throws Exception
+    {
+        server.setHandler(new WriteAndSetContentLengthTooSmallHandler(false));
+        server.start();
+
+        SimpleHttpResponse response = executeRequest();
+
+        // Setting a content-length too small throws an IllegalStateException
+        assertThat("response code is 500", response.getCode(), is("500"));
+        assertThat("response body is not foo", response.getBody(), not(is("foo")));
+    }
+
+    @Test
+    public void testWriteAndSetContentLengthTooSmallAndThrow() throws Exception
+    {
+        server.setHandler(new WriteAndSetContentLengthTooSmallHandler(true));
+        server.start();
+
+        SimpleHttpResponse response = executeRequest();
+
+        // Setting a content-length too small throws an IllegalStateException
+        assertThat("response code is 500", response.getCode(), is("500"));
+        assertThat("response body is not foo", response.getBody(), not(is("foo")));
+    }
+
+    private class WriteAndSetContentLengthTooSmallHandler extends ThrowExceptionOnDemandHandler
+    {
+        private WriteAndSetContentLengthTooSmallHandler(boolean throwException)
+        {
+            super(throwException);
+        }
+
+        @Override
+        public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+        {
+            baseRequest.setHandled(true);
+            response.getWriter().write("foobar");
+            response.setContentLength(3);
+            super.handle(target, baseRequest, request, response);
+        }
+    }
+}
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpOutputTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpOutputTest.java
new file mode 100644
index 0000000..d664d1c
--- /dev/null
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpOutputTest.java
@@ -0,0 +1,760 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import static org.hamcrest.Matchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.channels.ReadableByteChannel;
+import java.util.Arrays;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.ServletException;
+import javax.servlet.WriteListener;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.resource.Resource;
+import org.hamcrest.Matchers;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ *
+ */
+public class HttpOutputTest
+{
+    private Server _server;
+    private LocalConnector _connector;
+    private ContentHandler _handler;
+
+    @Before
+    public void init() throws Exception
+    {
+        _server = new Server();
+
+        HttpConnectionFactory http = new HttpConnectionFactory();
+        http.getHttpConfiguration().setRequestHeaderSize(1024);
+        http.getHttpConfiguration().setResponseHeaderSize(1024);
+        http.getHttpConfiguration().setOutputBufferSize(4096);
+        
+        _connector = new LocalConnector(_server,http,null);
+        _server.addConnector(_connector);
+        _handler=new ContentHandler();
+        _server.setHandler(_handler);
+        _server.start();
+    }
+
+    @After
+    public void destroy() throws Exception
+    {
+        _server.stop();
+        _server.join();
+    }
+
+    @Test
+    public void testSimple() throws Exception
+    {
+        String response=_connector.getResponses("GET / HTTP/1.0\nHost: localhost:80\n\n");
+        assertThat(response,containsString("HTTP/1.1 200 OK"));
+    }
+    
+
+
+    @Test
+    public void testByteUnknown() throws Exception
+    {
+        String response=_connector.getResponses("GET / HTTP/1.0\nHost: localhost:80\n\n");
+        assertThat(response,containsString("HTTP/1.1 200 OK"));
+    }
+    
+    
+    @Test
+    public void testSendArray() throws Exception
+    {
+        byte[] buffer=new byte[16*1024];
+        Arrays.fill(buffer,0,4*1024,(byte)0x99);
+        Arrays.fill(buffer,4*1024,12*1024,(byte)0x58);
+        Arrays.fill(buffer,12*1024,16*1024,(byte)0x66);
+        _handler._content=ByteBuffer.wrap(buffer);
+        _handler._content.limit(12*1024);
+        _handler._content.position(4*1024);
+        String response=_connector.getResponses("GET / HTTP/1.0\nHost: localhost:80\n\n");
+        assertThat(response,containsString("HTTP/1.1 200 OK"));
+        assertThat(response,containsString("\r\nXXXXXXXXXXXXXXXXXXXXXXXXXXX"));
+        
+        for (int i=0;i<4*1024;i++)
+            assertEquals("i="+i,(byte)0x99,buffer[i]);
+        for (int i=12*1024;i<16*1024;i++)
+            assertEquals("i="+i,(byte)0x66,buffer[i]);
+    }
+    
+    @Test
+    public void testSendInputStreamSimple() throws Exception
+    {
+        Resource simple = Resource.newClassPathResource("simple/simple.txt");
+        _handler._contentInputStream=simple.getInputStream();
+        String response=_connector.getResponses("GET / HTTP/1.0\nHost: localhost:80\n\n");
+        assertThat(response,containsString("HTTP/1.1 200 OK"));
+        assertThat(response,containsString("Content-Length: 11"));
+    }
+    
+    @Test
+    public void testSendInputStreamBig() throws Exception
+    {
+        Resource big = Resource.newClassPathResource("simple/big.txt");
+        _handler._contentInputStream=big.getInputStream();
+        String response=_connector.getResponses("GET / HTTP/1.0\nHost: localhost:80\n\n");
+        assertThat(response,containsString("HTTP/1.1 200 OK"));
+        assertThat(response,Matchers.not(containsString("Content-Length")));
+    }
+    
+    @Test
+    public void testSendInputStreamBigChunked() throws Exception
+    {
+        Resource big = Resource.newClassPathResource("simple/big.txt");
+        _handler._contentInputStream= new FilterInputStream(big.getInputStream())
+        {
+            @Override
+            public int read(byte[] b, int off, int len) throws IOException
+            {
+                int filled= super.read(b,off,len>2000?2000:len);
+                return filled;
+            }
+        };
+        String response=_connector.getResponses(
+            "GET / HTTP/1.1\nHost: localhost:80\n\n"+
+            "GET / HTTP/1.1\nHost: localhost:80\nConnection: close\n\n"
+        );
+        response=response.substring(0,response.lastIndexOf("HTTP/1.1 200 OK"));
+        
+        assertThat(response,containsString("HTTP/1.1 200 OK"));
+        assertThat(response,containsString("Transfer-Encoding: chunked"));
+        assertThat(response,containsString("400\tThis is a big file"));
+        assertThat(response,containsString("\r\n0\r\n"));
+    }
+
+    @Test
+    public void testSendChannelSimple() throws Exception
+    {
+        Resource simple = Resource.newClassPathResource("simple/simple.txt");
+        _handler._contentChannel=simple.getReadableByteChannel();
+        String response=_connector.getResponses("GET / HTTP/1.0\nHost: localhost:80\n\n");
+        assertThat(response,containsString("HTTP/1.1 200 OK"));
+        assertThat(response,containsString("Content-Length: 11"));
+    }
+    
+    @Test
+    public void testSendChannelBig() throws Exception
+    {
+        Resource big = Resource.newClassPathResource("simple/big.txt");
+        _handler._contentChannel=big.getReadableByteChannel();
+        String response=_connector.getResponses("GET / HTTP/1.0\nHost: localhost:80\n\n");
+        assertThat(response,containsString("HTTP/1.1 200 OK"));
+        assertThat(response,Matchers.not(containsString("Content-Length")));
+        assertThat(response,containsString("400\tThis is a big file"));
+    }
+    
+    @Test
+    public void testSendBigDirect() throws Exception
+    {
+        Resource big = Resource.newClassPathResource("simple/big.txt");
+        _handler._content=BufferUtil.toBuffer(big,true);
+        String response=_connector.getResponses("GET / HTTP/1.0\nHost: localhost:80\n\n");
+        assertThat(response,containsString("HTTP/1.1 200 OK"));
+        assertThat(response,containsString("Content-Length"));
+        assertThat(response,containsString("400\tThis is a big file"));
+    }
+    
+    @Test
+    public void testSendBigInDirect() throws Exception
+    {
+        Resource big = Resource.newClassPathResource("simple/big.txt");
+        _handler._content=BufferUtil.toBuffer(big,false);
+        String response=_connector.getResponses("GET / HTTP/1.0\nHost: localhost:80\n\n");
+        assertThat(response,containsString("HTTP/1.1 200 OK"));
+        assertThat(response,containsString("Content-Length"));
+        assertThat(response,containsString("400\tThis is a big file"));
+    }
+    
+    
+    @Test
+    public void testSendChannelBigChunked() throws Exception
+    {
+        Resource big = Resource.newClassPathResource("simple/big.txt");
+        final ReadableByteChannel channel = big.getReadableByteChannel();
+        _handler._contentChannel=new ReadableByteChannel()
+        {
+            @Override
+            public boolean isOpen()
+            {
+                return channel.isOpen();
+            }
+            
+            @Override
+            public void close() throws IOException
+            {
+                channel.close();
+            }
+            
+            @Override
+            public int read(ByteBuffer dst) throws IOException
+            {
+                int filled=0;
+                if (dst.position()==0 && dst.limit()>2000)
+                {
+                    int limit=dst.limit();
+                    dst.limit(2000);
+                    filled=channel.read(dst);
+                    dst.limit(limit);
+                }
+                else 
+                    filled=channel.read(dst);
+                return filled;
+            }
+        };
+        
+        String response=_connector.getResponses(
+            "GET / HTTP/1.1\nHost: localhost:80\n\n"+
+            "GET / HTTP/1.1\nHost: localhost:80\nConnection: close\n\n"
+        );
+        response=response.substring(0,response.lastIndexOf("HTTP/1.1 200 OK"));
+        
+        assertThat(response,containsString("HTTP/1.1 200 OK"));
+        assertThat(response,containsString("Transfer-Encoding: chunked"));
+        assertThat(response,containsString("\r\n0\r\n"));
+    }
+
+    @Test
+    public void testWriteByte() throws Exception
+    {
+        final Resource big = Resource.newClassPathResource("simple/big.txt");
+        _handler._writeLengthIfKnown=false;
+        _handler._content=BufferUtil.toBuffer(big,false);
+        _handler._arrayBuffer=new byte[1];
+        
+        String response=_connector.getResponses("GET / HTTP/1.0\nHost: localhost:80\n\n");
+        assertThat(response,containsString("HTTP/1.1 200 OK"));
+        assertThat(response,Matchers.not(containsString("Content-Length")));
+        assertThat(response,containsString("400\tThis is a big file"));
+    }
+
+    @Test
+    public void testWriteSmall() throws Exception
+    {
+        final Resource big = Resource.newClassPathResource("simple/big.txt");
+        _handler._writeLengthIfKnown=false;
+        _handler._content=BufferUtil.toBuffer(big,false);
+        _handler._arrayBuffer=new byte[8];
+        
+        String response=_connector.getResponses("GET / HTTP/1.0\nHost: localhost:80\n\n");
+        assertThat(response,containsString("HTTP/1.1 200 OK"));
+        assertThat(response,Matchers.not(containsString("Content-Length")));
+        assertThat(response,containsString("400\tThis is a big file"));
+    }
+    
+    @Test
+    public void testWriteMed() throws Exception
+    {
+        final Resource big = Resource.newClassPathResource("simple/big.txt");
+        _handler._writeLengthIfKnown=false;
+        _handler._content=BufferUtil.toBuffer(big,false);
+        _handler._arrayBuffer=new byte[4000];
+        
+        String response=_connector.getResponses("GET / HTTP/1.0\nHost: localhost:80\n\n");
+        assertThat(response,containsString("HTTP/1.1 200 OK"));
+        assertThat(response,Matchers.not(containsString("Content-Length")));
+        assertThat(response,containsString("400\tThis is a big file"));
+    }
+    
+    @Test
+    public void testWriteLarge() throws Exception
+    {
+        final Resource big = Resource.newClassPathResource("simple/big.txt");
+        _handler._writeLengthIfKnown=false;
+        _handler._content=BufferUtil.toBuffer(big,false);
+        _handler._arrayBuffer=new byte[8192];
+        
+        String response=_connector.getResponses("GET / HTTP/1.0\nHost: localhost:80\n\n");
+        assertThat(response,containsString("HTTP/1.1 200 OK"));
+        assertThat(response,Matchers.not(containsString("Content-Length")));
+        assertThat(response,containsString("400\tThis is a big file"));
+    }
+
+    @Test
+    public void testWriteByteKnown() throws Exception
+    {
+        final Resource big = Resource.newClassPathResource("simple/big.txt");
+        _handler._writeLengthIfKnown=true;
+        _handler._content=BufferUtil.toBuffer(big,false);
+        _handler._arrayBuffer=new byte[1];
+        
+        String response=_connector.getResponses("GET / HTTP/1.0\nHost: localhost:80\n\n");
+        assertThat(response,containsString("HTTP/1.1 200 OK"));
+        assertThat(response,containsString("Content-Length"));
+        assertThat(response,containsString("400\tThis is a big file"));
+    }
+
+    @Test
+    public void testWriteSmallKnown() throws Exception
+    {
+        final Resource big = Resource.newClassPathResource("simple/big.txt");
+        _handler._writeLengthIfKnown=true;
+        _handler._content=BufferUtil.toBuffer(big,false);
+        _handler._arrayBuffer=new byte[8];
+        
+        String response=_connector.getResponses("GET / HTTP/1.0\nHost: localhost:80\n\n");
+        assertThat(response,containsString("HTTP/1.1 200 OK"));
+        assertThat(response,containsString("Content-Length"));
+        assertThat(response,containsString("400\tThis is a big file"));
+    }
+    
+    @Test
+    public void testWriteMedKnown() throws Exception
+    {
+        final Resource big = Resource.newClassPathResource("simple/big.txt");
+        _handler._writeLengthIfKnown=true;
+        _handler._content=BufferUtil.toBuffer(big,false);
+        _handler._arrayBuffer=new byte[4000];
+        
+        String response=_connector.getResponses("GET / HTTP/1.0\nHost: localhost:80\n\n");
+        assertThat(response,containsString("HTTP/1.1 200 OK"));
+        assertThat(response,containsString("Content-Length"));
+        assertThat(response,containsString("400\tThis is a big file"));
+    }
+
+    @Test
+    public void testWriteLargeKnown() throws Exception
+    {
+        final Resource big = Resource.newClassPathResource("simple/big.txt");
+        _handler._writeLengthIfKnown=true;
+        _handler._content=BufferUtil.toBuffer(big,false);
+        _handler._arrayBuffer=new byte[8192];
+        
+        String response=_connector.getResponses("GET / HTTP/1.0\nHost: localhost:80\n\n");
+        assertThat(response,containsString("HTTP/1.1 200 OK"));
+        assertThat(response,containsString("Content-Length"));
+        assertThat(response,containsString("400\tThis is a big file"));
+    }
+
+    
+    @Test
+    public void testWriteHugeKnown() throws Exception
+    {
+        _handler._writeLengthIfKnown=true;
+        _handler._content=BufferUtil.allocate(4*1024*1024);
+        _handler._content.limit(_handler._content.capacity());
+        for (int i=_handler._content.capacity();i-->0;)
+            _handler._content.put(i,(byte)'x');
+        _handler._arrayBuffer=new byte[8192];
+        
+        String response=_connector.getResponses("GET / HTTP/1.0\nHost: localhost:80\n\n");
+        assertThat(response,containsString("HTTP/1.1 200 OK"));
+        assertThat(response,containsString("Content-Length"));
+    }
+    
+
+    @Test
+    public void testWriteBufferSmall() throws Exception
+    {
+        final Resource big = Resource.newClassPathResource("simple/big.txt");
+        _handler._writeLengthIfKnown=false;
+        _handler._content=BufferUtil.toBuffer(big,false);
+        _handler._byteBuffer=BufferUtil.allocate(8);
+        
+        String response=_connector.getResponses("GET / HTTP/1.0\nHost: localhost:80\n\n");
+        assertThat(response,containsString("HTTP/1.1 200 OK"));
+        assertThat(response,Matchers.not(containsString("Content-Length")));
+        assertThat(response,containsString("400\tThis is a big file"));
+    }
+
+    @Test
+    public void testWriteBufferMed() throws Exception
+    {
+        final Resource big = Resource.newClassPathResource("simple/big.txt");
+        _handler._writeLengthIfKnown=false;
+        _handler._content=BufferUtil.toBuffer(big,false);
+        _handler._byteBuffer=BufferUtil.allocate(4000);
+        
+        String response=_connector.getResponses("GET / HTTP/1.0\nHost: localhost:80\n\n");
+        assertThat(response,containsString("HTTP/1.1 200 OK"));
+        assertThat(response,Matchers.not(containsString("Content-Length")));
+        assertThat(response,containsString("400\tThis is a big file"));
+    }
+    
+    @Test
+    public void testWriteBufferLarge() throws Exception
+    {
+        final Resource big = Resource.newClassPathResource("simple/big.txt");
+        _handler._writeLengthIfKnown=false;
+        _handler._content=BufferUtil.toBuffer(big,false);
+        _handler._byteBuffer=BufferUtil.allocate(8192);
+        
+        String response=_connector.getResponses("GET / HTTP/1.0\nHost: localhost:80\n\n");
+        assertThat(response,containsString("HTTP/1.1 200 OK"));
+        assertThat(response,Matchers.not(containsString("Content-Length")));
+        assertThat(response,containsString("400\tThis is a big file"));
+    }
+
+
+    @Test
+    public void testAsyncWriteByte() throws Exception
+    {
+        final Resource big = Resource.newClassPathResource("simple/big.txt");
+        _handler._writeLengthIfKnown=false;
+        _handler._content=BufferUtil.toBuffer(big,false);
+        _handler._arrayBuffer=new byte[1];
+        _handler._async=true;
+        
+        String response=_connector.getResponses("GET / HTTP/1.0\nHost: localhost:80\n\n");
+        assertThat(response,containsString("HTTP/1.1 200 OK"));
+        assertThat(response,Matchers.not(containsString("Content-Length")));
+        assertThat(response,containsString("400\tThis is a big file"));
+    }
+    
+    @Test
+    public void testAsyncWriteSmall() throws Exception
+    {
+        final Resource big = Resource.newClassPathResource("simple/big.txt");
+        _handler._writeLengthIfKnown=false;
+        _handler._content=BufferUtil.toBuffer(big,false);
+        _handler._arrayBuffer=new byte[8];
+        _handler._async=true;
+        
+        String response=_connector.getResponses("GET / HTTP/1.0\nHost: localhost:80\n\n");
+        assertThat(response,containsString("HTTP/1.1 200 OK"));
+        assertThat(response,Matchers.not(containsString("Content-Length")));
+        assertThat(response,containsString("400\tThis is a big file"));
+    }
+    
+    @Test
+    public void testAsyncWriteMed() throws Exception
+    {
+        final Resource big = Resource.newClassPathResource("simple/big.txt");
+        _handler._writeLengthIfKnown=false;
+        _handler._content=BufferUtil.toBuffer(big,false);
+        _handler._arrayBuffer=new byte[4000];
+        _handler._async=true;
+        
+        String response=_connector.getResponses("GET / HTTP/1.0\nHost: localhost:80\n\n");
+        assertThat(response,containsString("HTTP/1.1 200 OK"));
+        assertThat(response,Matchers.not(containsString("Content-Length")));
+        assertThat(response,containsString("400\tThis is a big file"));
+    }
+    
+    @Test
+    public void testAsyncWriteLarge() throws Exception
+    {
+        final Resource big = Resource.newClassPathResource("simple/big.txt");
+        _handler._writeLengthIfKnown=false;
+        _handler._content=BufferUtil.toBuffer(big,false);
+        _handler._arrayBuffer=new byte[8192];
+        _handler._async=true;
+        
+        String response=_connector.getResponses("GET / HTTP/1.0\nHost: localhost:80\n\n");
+        assertThat(response,containsString("HTTP/1.1 200 OK"));
+        assertThat(response,Matchers.not(containsString("Content-Length")));
+        assertThat(response,containsString("400\tThis is a big file"));
+    }
+
+    
+    @Test
+    public void testAsyncWriteHuge() throws Exception
+    {
+        _handler._writeLengthIfKnown=true;
+        _handler._content=BufferUtil.allocate(4*1024*1024);
+        _handler._content.limit(_handler._content.capacity());
+        for (int i=_handler._content.capacity();i-->0;)
+            _handler._content.put(i,(byte)'x');
+        _handler._arrayBuffer=new byte[8192];
+        _handler._async=true;
+        
+        String response=_connector.getResponses("GET / HTTP/1.0\nHost: localhost:80\n\n");
+        assertThat(response,containsString("HTTP/1.1 200 OK"));
+        assertThat(response,containsString("Content-Length"));
+    }
+
+    
+    
+    @Test
+    public void testAsyncWriteBufferSmall() throws Exception
+    {
+        final Resource big = Resource.newClassPathResource("simple/big.txt");
+        _handler._writeLengthIfKnown=false;
+        _handler._content=BufferUtil.toBuffer(big,false);
+        _handler._byteBuffer=BufferUtil.allocate(8);
+        _handler._async=true;
+        
+        String response=_connector.getResponses("GET / HTTP/1.0\nHost: localhost:80\n\n");
+        assertThat(response,containsString("HTTP/1.1 200 OK"));
+        assertThat(response,Matchers.not(containsString("Content-Length")));
+        assertThat(response,containsString("400\tThis is a big file"));
+    }
+
+    @Test
+    public void testAsyncWriteBufferMed() throws Exception
+    {
+        final Resource big = Resource.newClassPathResource("simple/big.txt");
+        _handler._writeLengthIfKnown=false;
+        _handler._content=BufferUtil.toBuffer(big,false);
+        _handler._byteBuffer=BufferUtil.allocate(4000);
+        _handler._async=true;
+        
+        String response=_connector.getResponses("GET / HTTP/1.0\nHost: localhost:80\n\n");
+        assertThat(response,containsString("HTTP/1.1 200 OK"));
+        assertThat(response,Matchers.not(containsString("Content-Length")));
+        assertThat(response,containsString("400\tThis is a big file"));
+    }
+    
+    @Test
+    public void testAsyncWriteBufferLarge() throws Exception
+    {
+        final Resource big = Resource.newClassPathResource("simple/big.txt");
+        _handler._writeLengthIfKnown=false;
+        _handler._content=BufferUtil.toBuffer(big,false);
+        _handler._byteBuffer=BufferUtil.allocate(8192);
+        _handler._async=true;
+        
+        String response=_connector.getResponses("GET / HTTP/1.0\nHost: localhost:80\n\n");
+        assertThat(response,containsString("HTTP/1.1 200 OK"));
+        assertThat(response,Matchers.not(containsString("Content-Length")));
+        assertThat(response,containsString("400\tThis is a big file"));
+    }
+    
+    @Test
+    public void testAsyncWriteBufferLargeHEAD() throws Exception
+    {
+        final Resource big = Resource.newClassPathResource("simple/big.txt");
+        _handler._writeLengthIfKnown=false;
+        _handler._content=BufferUtil.toBuffer(big,false);
+        _handler._byteBuffer=BufferUtil.allocate(8192);
+        _handler._async=true;
+        
+        int start=_handler._owp.get();
+        String response=_connector.getResponses("HEAD / HTTP/1.0\nHost: localhost:80\n\n");
+        assertThat(_handler._owp.get()-start,Matchers.greaterThan(0));
+        assertThat(response,containsString("HTTP/1.1 200 OK"));
+        assertThat(response,Matchers.not(containsString("Content-Length")));
+        assertThat(response,Matchers.not(containsString("400\tThis is a big file")));
+    }
+
+    @Test
+    public void testAsyncWriteSimpleKnown() throws Exception
+    {
+        final Resource big = Resource.newClassPathResource("simple/simple.txt");
+        
+        _handler._async=true;
+        _handler._writeLengthIfKnown=true;
+        _handler._content=BufferUtil.toBuffer(big,false);
+        _handler._arrayBuffer=new byte[4000];
+        
+        String response=_connector.getResponses("GET / HTTP/1.0\nHost: localhost:80\n\n");
+        assertThat(response,containsString("HTTP/1.1 200 OK"));
+        assertThat(response,containsString("Content-Length: 11"));
+        assertThat(response,containsString("simple text"));
+    }
+
+    @Test
+    public void testAsyncWriteSimpleKnownHEAD() throws Exception
+    {
+        final Resource big = Resource.newClassPathResource("simple/simple.txt");
+        
+        _handler._async=true;
+        _handler._writeLengthIfKnown=true;
+        _handler._content=BufferUtil.toBuffer(big,false);
+        _handler._arrayBuffer=new byte[4000];
+
+        int start=_handler._owp.get();
+        String response=_connector.getResponses("HEAD / HTTP/1.0\nHost: localhost:80\n\n");
+        assertThat(_handler._owp.get()-start,Matchers.equalTo(1));
+        assertThat(response,containsString("HTTP/1.1 200 OK"));
+        assertThat(response,containsString("Content-Length: 11"));
+        assertThat(response,Matchers.not(containsString("simple text")));
+    }
+    
+    static class ContentHandler extends AbstractHandler
+    {
+        AtomicInteger _owp = new AtomicInteger();
+        boolean _writeLengthIfKnown=true;
+        boolean _async;
+        ByteBuffer _byteBuffer;
+        byte[] _arrayBuffer;
+        InputStream _contentInputStream;
+        ReadableByteChannel _contentChannel;
+        ByteBuffer _content;
+        
+        @Override
+        public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+        {
+            baseRequest.setHandled(true);
+            response.setContentType("text/plain");
+            
+            final HttpOutput out = (HttpOutput) response.getOutputStream();
+            
+            
+            if (_contentInputStream!=null)
+            {
+                out.sendContent(_contentInputStream);
+                _contentInputStream=null;
+                return;
+            }
+            
+            if (_contentChannel!=null)
+            {
+                out.sendContent(_contentChannel);
+                _contentChannel=null;
+                return;
+            }
+            
+            if (_content!=null && _writeLengthIfKnown)
+                response.setContentLength(_content.remaining());
+            
+            if (_arrayBuffer!=null)
+            {
+                if (_async)
+                {
+                    final AsyncContext async = request.startAsync();
+                    out.setWriteListener(new WriteListener()
+                    {
+                        @Override
+                        public void onWritePossible() throws IOException
+                        {
+                            _owp.incrementAndGet();
+                            
+                            while (out.isReady())
+                            {
+                                Assert.assertTrue(out.isReady());
+                                int len=_content.remaining();
+                                if (len>_arrayBuffer.length)
+                                    len=_arrayBuffer.length;
+                                if (len==0)
+                                {
+                                    async.complete();
+                                    break;
+                                }
+                                
+                                _content.get(_arrayBuffer,0,len);
+                                if (len==1)
+                                    out.write(_arrayBuffer[0]);
+                                else
+                                    out.write(_arrayBuffer,0,len);
+                            }
+                            // Assert.assertFalse(out.isReady());
+                        }
+
+                        @Override
+                        public void onError(Throwable t)
+                        {
+                            t.printStackTrace();
+                            async.complete();
+                        }
+                    });
+                    
+                    return;  
+                }
+                
+                while(BufferUtil.hasContent(_content))
+                {
+                    int len=_content.remaining();
+                    if (len>_arrayBuffer.length)
+                        len=_arrayBuffer.length;
+                    _content.get(_arrayBuffer,0,len);
+                    if (len==1)
+                        out.write(_arrayBuffer[0]);
+                    else
+                        out.write(_arrayBuffer,0,len);
+                }
+                
+                return;
+            }
+            
+            if (_byteBuffer!=null)
+            {
+                if (_async)
+                {
+                    final AsyncContext async = request.startAsync();
+                    out.setWriteListener(new WriteListener()
+                    {
+                        @Override
+                        public void onWritePossible() throws IOException
+                        {
+                            _owp.incrementAndGet();
+                            
+                            while (out.isReady())
+                            {
+                                Assert.assertTrue(out.isReady());
+                                if(BufferUtil.isEmpty(_content))
+                                {
+                                    async.complete();
+                                    break;
+                                }
+                                    
+                                BufferUtil.clearToFill(_byteBuffer);
+                                BufferUtil.put(_content,_byteBuffer);
+                                BufferUtil.flipToFlush(_byteBuffer,0);
+                                out.write(_byteBuffer);
+                            }
+                        }
+
+                        @Override
+                        public void onError(Throwable t)
+                        {
+                            t.printStackTrace();
+                            async.complete();
+                        }
+                    });
+                    
+                    return;  
+                }
+                
+                while(BufferUtil.hasContent(_content))
+                {
+                    BufferUtil.clearToFill(_byteBuffer);
+                    BufferUtil.put(_content,_byteBuffer);
+                    BufferUtil.flipToFlush(_byteBuffer,0);
+                    out.write(_byteBuffer);
+                }
+                
+                return;
+            }
+            
+            if (_content!=null)
+            {
+                if (_content.hasArray())
+                    out.write(_content.array(),_content.arrayOffset()+_content.position(),_content.remaining());
+                else
+                    out.sendContent(_content);
+                _content=null;
+                return;
+            }
+        }
+    }
+}
+
+
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestBase.java b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestBase.java
index 8e78dcd..9d0dad2 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestBase.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestBase.java
@@ -26,10 +26,9 @@ import java.io.InputStreamReader;
 import java.io.LineNumberReader;
 import java.io.OutputStream;
 import java.net.Socket;
-import java.net.SocketException;
 import java.net.URL;
+import java.nio.charset.StandardCharsets;
 import java.util.Arrays;
-import java.util.Random;
 import java.util.concurrent.Exchanger;
 import java.util.concurrent.atomic.AtomicBoolean;
 import javax.servlet.ServletException;
@@ -41,19 +40,21 @@ import javax.servlet.http.HttpServletResponse;
 import org.eclipse.jetty.io.EndPoint;
 import org.eclipse.jetty.io.EofException;
 import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.toolchain.test.annotation.Slow;
 import org.eclipse.jetty.util.IO;
-import org.eclipse.jetty.util.StringUtil;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.StdErrLog;
 import org.hamcrest.Matchers;
 import org.junit.Assert;
 import org.junit.Test;
-import org.junit.matchers.JUnitMatchers;
 
+import static org.hamcrest.Matchers.containsString;
 import static org.hamcrest.Matchers.greaterThanOrEqualTo;
 import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+import static org.hamcrest.Matchers.startsWith;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
 
@@ -62,92 +63,179 @@ import static org.junit.Assert.assertTrue;
  */
 public abstract class HttpServerTestBase extends HttpServerTestFixture
 {
-    /** The request. */
-    private static final String REQUEST1_HEADER="POST / HTTP/1.0\n"+"Host: localhost\n"+"Content-Type: text/xml; charset=utf-8\n"+"Connection: close\n"+"Content-Length: ";
-    private static final String REQUEST1_CONTENT="<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n"
-            +"<nimbus xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"+"        xsi:noNamespaceSchemaLocation=\"nimbus.xsd\" version=\"1.0\">\n"
-            +"</nimbus>";
-    private static final String REQUEST1=REQUEST1_HEADER+REQUEST1_CONTENT.getBytes().length+"\n\n"+REQUEST1_CONTENT;
+    private static final String REQUEST1_HEADER = "POST / HTTP/1.0\n" + "Host: localhost\n" + "Content-Type: text/xml; charset=utf-8\n" + "Connection: close\n" + "Content-Length: ";
+    private static final String REQUEST1_CONTENT = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+            + "<nimbus xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" + "        xsi:noNamespaceSchemaLocation=\"nimbus.xsd\" version=\"1.0\">\n"
+            + "</nimbus>";
+    private static final String REQUEST1 = REQUEST1_HEADER + REQUEST1_CONTENT.getBytes().length + "\n\n" + REQUEST1_CONTENT;
 
-    /** The expected response. */
-    private static final String RESPONSE1="HTTP/1.1 200 OK\n"+"Content-Length: 13\n"+"Server: Jetty("+Server.getVersion()+")\n"+"\n"+"Hello world\n";
+    private static final String RESPONSE1 = "HTTP/1.1 200 OK\n" + "Content-Length: 13\n" + "Server: Jetty(" + Server.getVersion() + ")\n" + "\n" + "Hello world\n";
 
     // Break the request up into three pieces, splitting the header.
-    private static final String FRAGMENT1=REQUEST1.substring(0,16);
-    private static final String FRAGMENT2=REQUEST1.substring(16,34);
-    private static final String FRAGMENT3=REQUEST1.substring(34);
-
-    /** Second test request. */
-    protected static final String REQUEST2_HEADER=
-        "POST / HTTP/1.0\n"+
-        "Host: localhost\n"+
-        "Content-Type: text/xml;charset=ISO-8859-1\n"+
-        "Content-Length: ";
-    protected static final String REQUEST2_CONTENT=
-        "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n"+
-        "<nimbus xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"+
-        "        xsi:noNamespaceSchemaLocation=\"nimbus.xsd\" version=\"1.0\">\n"+
-        "    <request requestId=\"1\">\n"+
-        "        <getJobDetails>\n"+
-        "            <jobId>73</jobId>\n"+
-        "        </getJobDetails>\n"+
-        "    </request>\n"+
-        "</nimbus>";
-    protected static final String REQUEST2=REQUEST2_HEADER+REQUEST2_CONTENT.getBytes().length+"\n\n"+REQUEST2_CONTENT;
-
-    /** The second expected response. */
-    protected static final String RESPONSE2_CONTENT=
-            "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n"+
-            "<nimbus xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"+
-            "        xsi:noNamespaceSchemaLocation=\"nimbus.xsd\" version=\"1.0\">\n"+
-            "    <request requestId=\"1\">\n"+
-            "        <getJobDetails>\n"+
-            "            <jobId>73</jobId>\n"+
-            "        </getJobDetails>\n"+
-            "    </request>\n"
-            +"</nimbus>\n";
-    protected static final String RESPONSE2=
-        "HTTP/1.1 200 OK\n"+
-        "Content-Type: text/xml;charset=ISO-8859-1\n"+
-        "Content-Length: "+RESPONSE2_CONTENT.getBytes().length+"\n"+
-        "Server: Jetty("+Server.getVersion()+")\n"+
-        "\n"+
-        RESPONSE2_CONTENT;
+    private static final String FRAGMENT1 = REQUEST1.substring(0, 16);
+    private static final String FRAGMENT2 = REQUEST1.substring(16, 34);
+    private static final String FRAGMENT3 = REQUEST1.substring(34);
+
+    protected static final String REQUEST2_HEADER =
+            "POST / HTTP/1.0\n" +
+                    "Host: localhost\n" +
+                    "Content-Type: text/xml; charset=ISO-8859-1\n" +
+                    "Content-Length: ";
+    protected static final String REQUEST2_CONTENT =
+            "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+                    "<nimbus xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" +
+                    "        xsi:noNamespaceSchemaLocation=\"nimbus.xsd\" version=\"1.0\">\n" +
+                    "    <request requestId=\"1\">\n" +
+                    "        <getJobDetails>\n" +
+                    "            <jobId>73</jobId>\n" +
+                    "        </getJobDetails>\n" +
+                    "    </request>\n" +
+                    "</nimbus>";
+    protected static final String REQUEST2 = REQUEST2_HEADER + REQUEST2_CONTENT.getBytes().length + "\n\n" + REQUEST2_CONTENT;
+
+    protected static final String RESPONSE2_CONTENT =
+            "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+                    "<nimbus xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" +
+                    "        xsi:noNamespaceSchemaLocation=\"nimbus.xsd\" version=\"1.0\">\n" +
+                    "    <request requestId=\"1\">\n" +
+                    "        <getJobDetails>\n" +
+                    "            <jobId>73</jobId>\n" +
+                    "        </getJobDetails>\n" +
+                    "    </request>\n"
+                    + "</nimbus>\n";
+    protected static final String RESPONSE2 =
+            "HTTP/1.1 200 OK\n" +
+                    "Content-Type: text/xml; charset=ISO-8859-1\n" +
+                    "Content-Length: " + RESPONSE2_CONTENT.getBytes().length + "\n" +
+                    "Server: Jetty(" + Server.getVersion() + ")\n" +
+                    "\n" +
+                    RESPONSE2_CONTENT;
 
+    @Test
+    public void testSimple() throws Exception
+    {
+        configureServer(new HelloWorldHandler());
+
+        try (Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort()))
+        {
+            OutputStream os = client.getOutputStream();
+
+            os.write("GET / HTTP/1.0\r\n\r\n".getBytes(StandardCharsets.ISO_8859_1));
+            os.flush();
+
+            // Read the response.
+            String response = readResponse(client);
 
+            Assert.assertThat(response, Matchers.containsString("HTTP/1.1 200 OK"));
+            Assert.assertThat(response, Matchers.containsString("Hello world"));
+        }
+    }
+
+    @Test
+    public void testOPTIONS() throws Exception
+    {
+        configureServer(new OptionsHandler());
+
+        try (Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort()))
+        {
+            OutputStream os = client.getOutputStream();
+
+            os.write(("OPTIONS * HTTP/1.1\r\n"
+                    + "Host: "+_serverURI.getHost()+"\r\n"
+                    + "Connection: close\r\n"
+                    + "\r\n").getBytes(StandardCharsets.ISO_8859_1));
+            os.flush();
+
+            // Read the response.
+            String response = readResponse(client);
+
+            Assert.assertThat(response, Matchers.containsString("HTTP/1.1 200 OK"));
+            Assert.assertThat(response, Matchers.containsString("Allow: GET"));
+        }
+        
+        try (Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort()))
+        {
+            OutputStream os = client.getOutputStream();
+
+            os.write(("GET * HTTP/1.1\r\n"
+                    + "Host: "+_serverURI.getHost()+"\r\n"
+                    + "Connection: close\r\n"
+                    + "\r\n").getBytes(StandardCharsets.ISO_8859_1));
+            os.flush();
+
+            // Read the response.
+            String response = readResponse(client);
+
+            Assert.assertThat(response, Matchers.containsString("HTTP/1.1 400 "));
+            Assert.assertThat(response, Matchers.not(Matchers.containsString("Allow: ")));
+        }
+    }
 
     /*
-     * Feed a full header method
-     */
+    * Feed a full header method
+    */
     @Test
-    public void testFull() throws Exception
+    public void testFullMethod() throws Exception
     {
         configureServer(new HelloWorldHandler());
 
-        Socket client=newSocket(HOST,_connector.getLocalPort());
-        try
+        try (Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort()))
         {
-            OutputStream os=client.getOutputStream();
+            client.setSoTimeout(10000);
+            ((StdErrLog)Log.getLogger(HttpConnection.class)).setHideStacks(true);
+            ((StdErrLog) Log.getLogger(HttpConnection.class)).info("expect request is too large, then ISE extra data ...");
+            OutputStream os = client.getOutputStream();
 
-            byte[] buffer = new byte[64*1024];
-            Arrays.fill(buffer,(byte)'A');
+            byte[] buffer = new byte[64 * 1024];
+            Arrays.fill(buffer, (byte)'A');
 
             os.write(buffer);
             os.flush();
 
             // Read the response.
-            String response=readResponse(client);
+            String response = readResponse(client);
 
             Assert.assertThat(response, Matchers.containsString("HTTP/1.1 413 "));
         }
-        catch(SocketException e)
+        finally
         {
-            // TODO looks like a close is overtaking the 413 in SSL
-            System.err.println("Investigate this "+e);
+            ((StdErrLog)Log.getLogger(HttpConnection.class)).setHideStacks(true);
+        }
+    }
+
+    /*
+    * Feed a full header method
+    */
+    @Test
+    public void testFullURI() throws Exception
+    {
+        configureServer(new HelloWorldHandler());
+
+        try (Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort()))
+        {
+            ((StdErrLog)Log.getLogger(HttpConnection.class)).setHideStacks(true);
+            ((StdErrLog)Log.getLogger(HttpConnection.class)).info("expect URI is too large, then ISE extra data ...");
+            OutputStream os = client.getOutputStream();
+
+            byte[] buffer = new byte[64 * 1024];
+            buffer[0]='G';
+            buffer[1]='E';
+            buffer[2]='T';
+            buffer[3]=' ';
+            buffer[4]='/';
+            Arrays.fill(buffer, 5,buffer.length-1,(byte)'A');
+
+            os.write(buffer);
+            os.flush();
+
+            // Read the response.
+            String response = readResponse(client);
+
+            Assert.assertThat(response, Matchers.containsString("HTTP/1.1 414 "));
         }
         finally
         {
-            client.close();
+            ((StdErrLog)Log.getLogger(HttpConnection.class)).setHideStacks(true);
         }
     }
 
@@ -156,25 +244,33 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
     {
         configureServer(new AbstractHandler()
         {
+            @Override
             public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
             {
-                throw new ServletException("handler exception");
+                throw new QuietServletException("TEST handler exception");
             }
         });
 
-        ((StdErrLog)Log.getLogger(AbstractHttpConnection.class)).setHideStacks(true);
-
         StringBuffer request = new StringBuffer("GET / HTTP/1.0\r\n");
         request.append("Host: localhost\r\n\r\n");
 
-        Socket client = newSocket(HOST, _connector.getLocalPort());
+        Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort());
         OutputStream os = client.getOutputStream();
 
-        os.write(request.toString().getBytes());
-        os.flush();
+        try
+        { 
+            ((StdErrLog)Log.getLogger(HttpChannel.class)).setHideStacks(true);
+            Log.getLogger(HttpChannel.class).info("Expecting ServletException: TEST handler exception...");
+            os.write(request.toString().getBytes());
+            os.flush();
 
-        String response = readResponse(client);
-        assertThat("response code is 500", response.contains("500"), is(true));
+            String response = readResponse(client);
+            assertThat("response code is 500", response.contains("500"), is(true));
+        }
+        finally
+        {
+            ((StdErrLog)Log.getLogger(HttpChannel.class)).setHideStacks(false);
+        }
     }
 
     @Test
@@ -184,6 +280,7 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
         final AtomicBoolean earlyEOFException = new AtomicBoolean(false);
         configureServer(new AbstractHandler()
         {
+            @Override
             public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
             {
                 baseRequest.setHandled(true);
@@ -211,7 +308,7 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
         request.append("Content-length: 6\n\n");
         request.append("foo");
 
-        Socket client = newSocket(HOST, _connector.getLocalPort());
+        Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort());
         OutputStream os = client.getOutputStream();
 
         os.write(request.toString().getBytes());
@@ -224,32 +321,76 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
         assertThat("The 4th byte (-1) has not been passed to the handler", fourBytesRead.get(), is(false));
         assertThat("EofException has been caught", earlyEOFException.get(), is(true));
     }
-
+    
     /*
-     * Feed the server the entire request at once.
+     * Feed a full header method
      */
     @Test
-    public void testRequest1() throws Exception
+    public void testFullHeader() throws Exception
     {
+
         configureServer(new HelloWorldHandler());
 
-        Socket client=newSocket(HOST,_connector.getLocalPort());
-        try
+        try (Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort()))
         {
-            OutputStream os=client.getOutputStream();
+            ((StdErrLog)Log.getLogger(HttpConnection.class)).setHideStacks(true);
+            ((StdErrLog)Log.getLogger(HttpConnection.class)).info("expect header is too large, then ISE extra data ...");
+            OutputStream os = client.getOutputStream();
+
+            byte[] buffer = new byte[64 * 1024];
+            buffer[0]='G';
+            buffer[1]='E';
+            buffer[2]='T';
+            buffer[3]=' ';
+            buffer[4]='/';
+            buffer[5]=' ';
+            buffer[6]='H';
+            buffer[7]='T';
+            buffer[8]='T';
+            buffer[9]='P';
+            buffer[10]='/';
+            buffer[11]='1';
+            buffer[12]='.';
+            buffer[13]='0';
+            buffer[14]='\n';
+            buffer[15]='H';
+            buffer[16]=':';
+            Arrays.fill(buffer,17,buffer.length-1,(byte)'A');
 
-            os.write(REQUEST1.getBytes());
+            os.write(buffer);
             os.flush();
 
             // Read the response.
-            String response=readResponse(client);
+            String response = readResponse(client);
 
-            // Check the response
-            assertEquals("response",RESPONSE1,response);
+            Assert.assertThat(response, Matchers.containsString("HTTP/1.1 413 "));
         }
         finally
         {
-            client.close();
+            ((StdErrLog)Log.getLogger(HttpConnection.class)).setHideStacks(true);
+        }
+    }
+
+    /*
+    * Feed the server the entire request at once.
+    */
+    @Test
+    public void testRequest1() throws Exception
+    {
+        configureServer(new HelloWorldHandler());
+
+        try (Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort()))
+        {
+            OutputStream os = client.getOutputStream();
+
+            os.write(REQUEST1.getBytes());
+            os.flush();
+
+            // Read the response.
+            String response = readResponse(client);
+
+            // Check the response
+            assertEquals("response", RESPONSE1, response);
         }
     }
 
@@ -258,48 +399,70 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
     {
         configureServer(new EchoHandler());
 
-        Socket client=newSocket(HOST,_connector.getLocalPort());
-        try
+        try (Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort()))
         {
-            OutputStream os=client.getOutputStream();
+            OutputStream os = client.getOutputStream();
 
-            os.write(("GET /R2 HTTP/1.1\015\012"+
-                    "Host: localhost\015\012"+
-                    "Transfer-Encoding: chunked\015\012"+
-                    "Content-Type: text/plain\015\012"+
-                    "Connection: close\015\012"+
+            os.write(("GET /R2 HTTP/1.1\015\012" +
+                    "Host: localhost\015\012" +
+                    "Transfer-Encoding: chunked\015\012" +
+                    "Content-Type: text/plain\015\012" +
+                    "Connection: close\015\012" +
                     "\015\012").getBytes());
             os.flush();
-            Thread.sleep(PAUSE);
-            os.write(("5\015\012").getBytes());
+            Thread.sleep(1000);
+            os.write(("5").getBytes());
+            Thread.sleep(1000);
+            os.write(("\015\012").getBytes());
             os.flush();
-            Thread.sleep(PAUSE);
-            os.write(("ABCDE\015\012"+
-                      "0;\015\012\015\012").getBytes());
+            Thread.sleep(1000);
+            os.write(("ABCDE\015\012" +
+                    "0;\015\012\015\012").getBytes());
             os.flush();
 
             // Read the response.
-            String response=readResponse(client);
-            assertTrue (response.indexOf("200")>0);
+            String response = readResponse(client);
+            assertThat(response,containsString("200"));
         }
-        finally
+    }
+
+    @Test
+    public void testTrailingContent() throws Exception
+    {
+        configureServer(new EchoHandler());
+
+        try (Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort()))
         {
-            client.close();
+            OutputStream os = client.getOutputStream();
+
+            os.write(("GET /R2 HTTP/1.1\015\012" +
+                    "Host: localhost\015\012" +
+                    "Content-Length: 5\015\012" +
+                    "Content-Type: text/plain\015\012" +
+                    "Connection: close\015\012" +
+                    "\015\012" +
+                    "ABCDE\015\012" +
+                    "\015\012"
+            ).getBytes());
+            os.flush();
+
+            // Read the response.
+            String response = readResponse(client);
+            assertTrue(response.indexOf("200") > 0);
         }
     }
 
     /*
-     * Feed the server fragmentary headers and see how it copes with it.
-     */
+    * Feed the server fragmentary headers and see how it copes with it.
+    */
     @Test
-    public void testRequest1Fragments() throws Exception, InterruptedException
+    public void testRequest1Fragments() throws Exception
     {
         configureServer(new HelloWorldHandler());
 
-        Socket client=newSocket(HOST,_connector.getLocalPort());
-        try
+        try (Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort()))
         {
-            OutputStream os=client.getOutputStream();
+            OutputStream os = client.getOutputStream();
 
             // Write a fragment, flush, sleep, write the next fragment, etc.
             os.write(FRAGMENT1.getBytes());
@@ -315,13 +478,8 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
             String response = readResponse(client);
 
             // Check the response
-            assertEquals("response",RESPONSE1,response);
-        }
-        finally
-        {
-            client.close();
+            assertEquals("response", RESPONSE1, response);
         }
-
     }
 
     @Test
@@ -329,182 +487,124 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
     {
         configureServer(new EchoHandler());
 
-        byte[] bytes=REQUEST2.getBytes();
-        for (int i=0; i<LOOPS; i++)
+        byte[] bytes = REQUEST2.getBytes();
+        for (int i = 0; i < LOOPS; i++)
         {
-            Socket client=newSocket(HOST,_connector.getLocalPort());
-            try
+            try (Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort()))
             {
-                OutputStream os=client.getOutputStream();
+                OutputStream os = client.getOutputStream();
 
                 os.write(bytes);
                 os.flush();
 
                 // Read the response
-                String response=readResponse(client);
+                String response = readResponse(client);
 
                 // Check the response
-                assertEquals("response "+i,RESPONSE2,response);
+                assertEquals("response " + i, RESPONSE2, response);
             }
-            catch(IOException e)
+            catch (IOException e)
             {
                 e.printStackTrace();
                 _server.dumpStdErr();
                 throw e;
             }
-            finally
-            {
-                client.close();
-            }
         }
     }
 
+
     @Test
-    public void testRequest2Fragments() throws Exception
+    @Slow
+    public void testRequest2Sliced2() throws Exception
     {
         configureServer(new EchoHandler());
 
-        byte[] bytes=REQUEST2.getBytes();
-        final int pointCount=2;
-        Random random=new Random(System.currentTimeMillis());
-        for (int i=0; i<LOOPS; i++)
+        byte[] bytes = REQUEST2.getBytes();
+        int splits = bytes.length-REQUEST2_CONTENT.length()+5;
+        for (int i = 0; i < splits; i += 1)
         {
-            int[] points=new int[pointCount];
-            StringBuilder message=new StringBuilder();
+            int[] points = new int[]{i};
+            StringBuilder message = new StringBuilder();
 
             message.append("iteration #").append(i + 1);
 
-            // Pick fragment points at random
-            for (int j=0; j<points.length; ++j)
-            {
-                points[j]=random.nextInt(bytes.length);
-            }
-
-            // Sort the list
-            Arrays.sort(points);
-
-            Socket client=newSocket(HOST,_connector.getLocalPort());
-            try
+            try (Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort()))
             {
-                OutputStream os=client.getOutputStream();
+                OutputStream os = client.getOutputStream();
 
-                writeFragments(bytes,points,message,os);
+                writeFragments(bytes, points, message, os);
 
                 // Read the response
-                String response=readResponse(client);
+                String response = readResponse(client);
 
                 // Check the response
-                assertEquals("response for "+i+" "+message.toString(),RESPONSE2,response);
-            }
-            finally
-            {
-                client.close();
+                assertEquals("response for " + i + " " + message.toString(), RESPONSE2, response);
+                
+                Thread.sleep(10);
             }
         }
     }
-
+    
     @Test
-    public void testRequest2Iterate() throws Exception
+    @Slow
+    public void testRequest2Sliced3() throws Exception
     {
         configureServer(new EchoHandler());
 
-        byte[] bytes=REQUEST2.getBytes();
-        for (int i=0; i<bytes.length; i+=3)
+        byte[] bytes = REQUEST2.getBytes();
+        int splits = bytes.length-REQUEST2_CONTENT.length()+5;
+        for (int i = 0; i < splits; i += 1)
         {
-            int[] points=new int[] { i };
-            StringBuilder message=new StringBuilder();
+            int[] points = new int[]{i,i+1};
+            StringBuilder message = new StringBuilder();
 
             message.append("iteration #").append(i + 1);
 
-            // Sort the list
-            Arrays.sort(points);
-
-            Socket client=newSocket(HOST,_connector.getLocalPort());
-            try
+            try (Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort()))
             {
-                OutputStream os=client.getOutputStream();
+                OutputStream os = client.getOutputStream();
 
-                writeFragments(bytes,points,message,os);
+                writeFragments(bytes, points, message, os);
 
                 // Read the response
-                String response=readResponse(client);
+                String response = readResponse(client);
 
                 // Check the response
-                assertEquals("response for "+i+" "+message.toString(),RESPONSE2,response);
-            }
-            finally
-            {
-                client.close();
-            }
-        }
-    }
-
-    /*
-     * After several iterations, I generated some known bad fragment points.
-     */
-    @Test
-    public void testRequest2KnownBad() throws Exception
-    {
-        configureServer(new EchoHandler());
-
-        byte[] bytes=REQUEST2.getBytes();
-        int[][] badPoints=new int[][]
-        {
-                { 70 }, // beginning here, drops last line of request
-                { 71 }, // no response at all
-                { 72 }, // again starts drops last line of request
-                { 74 }, // again, no response at all
-        };
-        for (int i=0; i<badPoints.length; ++i)
-        {
-            Socket client=newSocket(HOST,_connector.getLocalPort());
-            try
-            {
-                OutputStream os=client.getOutputStream();
-                StringBuilder message=new StringBuilder();
-
-                message.append("iteration #").append(i + 1);
-                writeFragments(bytes,badPoints[i],message,os);
-
-                // Read the response
-                String response=readResponse(client);
-
-                // Check the response
-                // TODO - change to equals when code gets fixed
-                assertNotSame("response for "+message.toString(),RESPONSE2,response);
-            }
-            finally
-            {
-                client.close();
+                assertEquals("response for " + i + " " + message.toString(), RESPONSE2, response);
+                
+                Thread.sleep(10);
             }
         }
     }
+    
+    
+    
 
     @Test
     public void testFlush() throws Exception
     {
         configureServer(new DataHandler());
 
-        String[] encoding = {"NONE","UTF-8","ISO-8859-1","ISO-8859-2"};
-        for (int e =0; e<encoding.length;e++)
+        String[] encoding = {"NONE", "UTF-8", "ISO-8859-1", "ISO-8859-2"};
+        for (int e = 0; e < encoding.length; e++)
         {
-            for (int b=1;b<=128;b=b==1?2:b==2?32:b==32?128:129)
+            for (int b = 1; b <= 128; b = b == 1 ? 2 : b == 2 ? 32 : b == 32 ? 128 : 129)
             {
-                for (int w=41;w<42;w+=4096)
+                for (int w = 41; w < 42; w += 4096)
                 {
-                    for (int c=0;c<1;c++)
+                    for (int c = 0; c < 1; c++)
                     {
-                        String test=encoding[e]+"x"+b+"x"+w+"x"+c;
+                        String test = encoding[e] + "x" + b + "x" + w + "x" + c;
                         try
                         {
-                            URL url=new URL(_scheme+"://"+HOST+":"+_connector.getLocalPort()+"/?writes="+w+"&block="+b+ (e==0?"":("&encoding="+encoding[e]))+(c==0?"&chars=true":""));
+                            URL url = new URL(_scheme + "://" + _serverURI.getHost() + ":" + _serverURI.getPort() + "/?writes=" + w + "&block=" + b + (e == 0 ? "" : ("&encoding=" + encoding[e])) + (c == 0 ? "&chars=true" : ""));
 
                             InputStream in = (InputStream)url.getContent();
-                            String response=IO.toString(in,e==0?null:encoding[e]);
+                            String response = IO.toString(in, e == 0 ? null : encoding[e]);
 
-                            assertEquals(test,b*w,response.length());
+                            assertEquals(test, b * w, response.length());
                         }
-                        catch(Exception x)
+                        catch (Exception x)
                         {
                             System.err.println(test);
                             x.printStackTrace();
@@ -521,20 +621,19 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
     {
         configureServer(new DataHandler());
 
-        long start=System.currentTimeMillis();
-        Socket client=newSocket(HOST,_connector.getLocalPort());
-        try
+        long start = System.currentTimeMillis();
+        try (Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort()))
         {
-            OutputStream os=client.getOutputStream();
-            InputStream is=client.getInputStream();
+            OutputStream os = client.getOutputStream();
+            InputStream is = client.getInputStream();
 
             os.write((
-                    "GET /data?writes=1024&block=256 HTTP/1.1\r\n"+
-                    "host: "+HOST+":"+_connector.getLocalPort()+"\r\n"+
-                    "connection: close\r\n"+
-                    "content-type: unknown\r\n"+
-                    "content-length: 30\r\n"+
-                    "\r\n"
+                    "GET /data?writes=1024&block=256 HTTP/1.1\r\n" +
+                            "host: " + _serverURI.getHost() + ":" + _serverURI.getPort() + "\r\n" +
+                            "connection: close\r\n" +
+                            "content-type: unknown\r\n" +
+                            "content-length: 30\r\n" +
+                            "\r\n"
             ).getBytes());
             os.flush();
             Thread.sleep(200);
@@ -553,24 +652,28 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
             ).getBytes());
             os.flush();
 
-            int total=0;
-            int len=0;
-            byte[] buf=new byte[1024*64];
+            int total = 0;
+            int len = 0;
+
 
-            while(len>=0)
+            byte[] buf = new byte[1024 * 64];
+            int sleeps=0;
+            while (len >= 0)
             {
-                Thread.sleep(100);
-                len=is.read(buf);
-                if (len>0)
-                    total+=len;
+                len = is.read(buf);
+                if (len > 0)
+                {
+                    total += len;
+                    if ((total/10240)>sleeps)
+                    {
+                        sleeps++;
+                        Thread.sleep(100);
+                    }
+                }
             }
 
-            assertTrue(total>(1024*256));
-            assertTrue(30000L>(System.currentTimeMillis()-start));
-        }
-        finally
-        {
-            client.close();
+            assertTrue(total > (1024 * 256));
+            assertTrue(30000L > (System.currentTimeMillis() - start));
         }
     }
 
@@ -579,167 +682,212 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
     {
         configureServer(new DataHandler());
 
-        long start=System.currentTimeMillis();
-        Socket client=newSocket(HOST,_connector.getLocalPort());
-        int total=0;
-        try
+        long start = System.currentTimeMillis();
+        int total = 0;
+        try (Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort()))
         {
-            OutputStream os=client.getOutputStream();
-            InputStream is=client.getInputStream();
+            OutputStream os = client.getOutputStream();
+            InputStream is = client.getInputStream();
 
             os.write((
-                    "GET /data?writes=512&block=1024 HTTP/1.1\r\n"+
-                    "host: "+HOST+":"+_connector.getLocalPort()+"\r\n"+
-                    "connection: close\r\n"+
-                    "content-type: unknown\r\n"+
-                    "\r\n"
+                    "GET /data?writes=256&block=1024 HTTP/1.1\r\n" +
+                            "host: " + _serverURI.getHost() + ":" + _serverURI.getPort() + "\r\n" +
+                            "connection: close\r\n" +
+                            "content-type: unknown\r\n" +
+                            "\r\n"
             ).getBytes());
             os.flush();
 
-            int len=0;
-            byte[] buf=new byte[1024*32];
-
-            int i=0;
-            while(len>=0)
+            int len = 0;
+            byte[] buf = new byte[1024 * 32];
+            int sleeps=0;
+            while (len >= 0)
             {
-                if (i++%10==0)
-                    Thread.sleep(1000);
-                len=is.read(buf);
-                if (len>0)
-                    total+=len;
+                len = is.read(buf);
+                if (len > 0)
+                {
+                    total += len;
+                    if ((total/10240)>sleeps)
+                    {
+                        Thread.sleep(200);
+                        sleeps++;
+                    }
+                }
             }
 
-            assertTrue(total>(512*1024));
-            assertTrue(30000L>(System.currentTimeMillis()-start));
+            assertTrue(total > (256 * 1024));
+            assertTrue(30000L > (System.currentTimeMillis() - start));
         }
-        finally
+    }
+
+    @Test
+    public void testCloseWhileWriteBlocked() throws Exception
+    {
+        configureServer(new DataHandler());
+
+        try (Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort()))
         {
-            //System.err.println("Got "+total+" of "+(512*1024));
-            client.close();
+            OutputStream os = client.getOutputStream();
+            InputStream is = client.getInputStream();
+
+            os.write((
+                    "GET /data?encoding=iso-8859-1&writes=100&block=100000 HTTP/1.1\r\n" +
+                            "host: " + _serverURI.getHost() + ":" + _serverURI.getPort() + "\r\n" +
+                            "connection: close\r\n" +
+                            "content-type: unknown\r\n" +
+                            "\r\n"
+            ).getBytes());
+            os.flush();
+
+            // Read the first part of the response
+            byte[] buf = new byte[1024 * 8];
+            is.read(buf);
+            
+            // sleep to ensure server is blocking
+            Thread.sleep(500);
+            
+            // Close the client
+            client.close();            
         }
-    }
 
+        Thread.sleep(200);
+        // check server is still handling requests quickly
+        try (Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort()))
+        {
+            client.setSoTimeout(500);
+            OutputStream os = client.getOutputStream();
+            InputStream is = client.getInputStream();
+
+            os.write(("GET /data?writes=1&block=1024 HTTP/1.1\r\n" +
+                            "host: " + _serverURI.getHost() + ":" + _serverURI.getPort() + "\r\n" +
+                            "connection: close\r\n" +
+                            "content-type: unknown\r\n" +
+                            "\r\n"
+            ).getBytes());
+            os.flush();
+  
+            String response = IO.toString(is);
+            assertThat(response,startsWith("HTTP/1.1 200 OK"));
+        }
+    }
+    
     @Test
     public void testBigBlocks() throws Exception
     {
         configureServer(new BigBlockHandler());
 
-        Socket client=newSocket(HOST,_connector.getLocalPort());
-        client.setSoTimeout(20000);
-        try
+        try (Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort()))
         {
-            OutputStream os=client.getOutputStream();
+            client.setSoTimeout(20000);
+
+            OutputStream os = client.getOutputStream();
             BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream()));
 
             os.write((
-                    "GET /r1 HTTP/1.1\r\n"+
-                    "host: "+HOST+":"+_connector.getLocalPort()+"\r\n"+
-                    "\r\n"+
-                    "GET /r2 HTTP/1.1\r\n"+
-                    "host: "+HOST+":"+_connector.getLocalPort()+"\r\n"+
-                    "connection: close\r\n"+
-                    "\r\n"
+                    "GET /r1 HTTP/1.1\r\n" +
+                            "host: " + _serverURI.getHost() + ":" + _serverURI.getPort() + "\r\n" +
+                            "\r\n" +
+                            "GET /r2 HTTP/1.1\r\n" +
+                            "host: " + _serverURI.getHost() + ":" + _serverURI.getPort() + "\r\n" +
+                            "connection: close\r\n" +
+                            "\r\n"
             ).getBytes());
             os.flush();
 
             // read the chunked response header
-            boolean chunked=false;
-            boolean closed=false;
-            while(true)
+            boolean chunked = false;
+            boolean closed = false;
+            while (true)
             {
-                String line=in.readLine();
-                if (line==null || line.length()==0)
+                String line = in.readLine();
+                if (line == null || line.length() == 0)
                     break;
 
-                chunked|="Transfer-Encoding: chunked".equals(line);
-                closed|="Connection: close".equals(line);
+                chunked |= "Transfer-Encoding: chunked".equals(line);
+                closed |= "Connection: close".equals(line);
             }
             Assert.assertTrue(chunked);
             Assert.assertFalse(closed);
 
             // Read the chunks
-            int max=Integer.MIN_VALUE;
-            while(true)
+            int max = Integer.MIN_VALUE;
+            while (true)
             {
-                String chunk=in.readLine();
-                String line=in.readLine();
-                if (line.length()==0)
+                String chunk = in.readLine();
+                String line = in.readLine();
+                if (line.length() == 0)
                     break;
-                int len=line.length();
-                Assert.assertEquals(Integer.valueOf(chunk,16).intValue(),len);
-                if (max<len)
-                    max=len;
+                int len = line.length();
+                Assert.assertEquals(Integer.valueOf(chunk, 16).intValue(), len);
+                if (max < len)
+                    max = len;
             }
 
-            // Check that a direct content buffer was used as a chunk
-            Assert.assertEquals(128*1024,max);
+            // Check that biggest chunk was <= buffer size
+            Assert.assertEquals(_connector.getBean(HttpConnectionFactory.class).getHttpConfiguration().getOutputBufferSize() , max);
 
             // read and check the times are < 999ms
-            String[] times=in.readLine().split(",");
-            for (String t: times)
-               Assert.assertTrue(Integer.valueOf(t).intValue()<999);
+            String[] times = in.readLine().split(",");
+            for (String t : times)
+                Assert.assertTrue(Integer.valueOf(t) < 999);
 
 
             // read the EOF chunk
-            String end=in.readLine();
-            Assert.assertEquals("0",end);
-            end=in.readLine();
-            Assert.assertEquals(0,end.length());
-
+            String end = in.readLine();
+            Assert.assertEquals("0", end);
+            end = in.readLine();
+            Assert.assertEquals(0, end.length());
 
             // read the non-chunked response header
-            chunked=false;
-            closed=false;
-            while(true)
+            chunked = false;
+            closed = false;
+            while (true)
             {
-                String line=in.readLine();
-                if (line==null || line.length()==0)
+                String line = in.readLine();
+                if (line == null || line.length() == 0)
                     break;
 
-                chunked|="Transfer-Encoding: chunked".equals(line);
-                closed|="Connection: close".equals(line);
+                chunked |= "Transfer-Encoding: chunked".equals(line);
+                closed |= "Connection: close".equals(line);
             }
             Assert.assertFalse(chunked);
             Assert.assertTrue(closed);
 
             String bigline = in.readLine();
-            Assert.assertEquals(10*128*1024,bigline.length());
+            Assert.assertEquals(10 * 128 * 1024, bigline.length());
 
             // read and check the times are < 999ms
-            times=in.readLine().split(",");
-            for (String t: times)
-                Assert.assertTrue(t,Integer.valueOf(t).intValue()<999);
+            times = in.readLine().split(",");
+            for (String t : times)
+                Assert.assertTrue(t, Integer.valueOf(t) < 999);
 
             // check close
-            Assert.assertTrue(in.readLine()==null);
-        }
-        finally
-        {
-            client.close();
+            Assert.assertTrue(in.readLine() == null);
         }
     }
 
     // Handler that sends big blocks of data in each of 10 writes, and then sends the time it took for each big block.
     protected static class BigBlockHandler extends AbstractHandler
     {
+        @Override
         public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
         {
-            byte[] buf = new byte[128*1024];
-            for (int i=0;i<buf.length;i++)
-                buf[i]=(byte)("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_".charAt(i%63));
+            byte[] buf = new byte[128 * 1024];
+            for (int i = 0; i < buf.length; i++)
+                buf[i] = (byte)("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_".charAt(i % 63));
 
             baseRequest.setHandled(true);
             response.setStatus(200);
             response.setContentType("text/plain");
-            ServletOutputStream out=response.getOutputStream();
-            long[] times=new long[10];
-            for (int i=0;i<times.length;i++)
+            ServletOutputStream out = response.getOutputStream();
+            long[] times = new long[10];
+            for (int i = 0; i < times.length; i++)
             {
                 // System.err.println("\nBLOCK "+request.getRequestURI()+" "+i);
-                long start=System.currentTimeMillis();
+                long start = System.currentTimeMillis();
                 out.write(buf);
-                long end=System.currentTimeMillis();
-                times[i]=end-start;
+                long end = System.currentTimeMillis();
+                times[i] = end - start;
                 // System.err.println("Block "+request.getRequestURI()+" "+i+" "+times[i]);
             }
             out.println();
@@ -752,40 +900,38 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
         }
     }
 
-
     @Test
     public void testPipeline() throws Exception
     {
         configureServer(new HelloWorldHandler());
 
         //for (int pipeline=1;pipeline<32;pipeline++)
-        for (int pipeline=1;pipeline<32;pipeline++)
+        for (int pipeline = 1; pipeline < 32; pipeline++)
         {
-            Socket client=newSocket(HOST,_connector.getLocalPort());
-            try
+            try (Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort()))
             {
                 client.setSoTimeout(5000);
-                OutputStream os=client.getOutputStream();
-
-                String request="";
-
-                for (int i=1;i<pipeline;i++)
-                    request+=
-                        "GET /data?writes=1&block=16&id="+i+" HTTP/1.1\r\n"+
-                        "host: "+HOST+":"+_connector.getLocalPort()+"\r\n"+
-                        "user-agent: testharness/1.0 (blah foo/bar)\r\n"+
-                        "accept-encoding: nothing\r\n"+
-                        "cookie: aaa=1234567890\r\n"+
-                        "\r\n";
-
-                request+=
-                    "GET /data?writes=1&block=16 HTTP/1.1\r\n"+
-                    "host: "+HOST+":"+_connector.getLocalPort()+"\r\n"+
-                    "user-agent: testharness/1.0 (blah foo/bar)\r\n"+
-                    "accept-encoding: nothing\r\n"+
-                    "cookie: aaa=bbbbbb\r\n"+
-                    "Connection: close\r\n"+
-                    "\r\n";
+                OutputStream os = client.getOutputStream();
+
+                String request = "";
+
+                for (int i = 1; i < pipeline; i++)
+                    request +=
+                            "GET /data?writes=1&block=16&id=" + i + " HTTP/1.1\r\n" +
+                                    "host: " + _serverURI.getHost() + ":" + _serverURI.getPort() + "\r\n" +
+                                    "user-agent: testharness/1.0 (blah foo/bar)\r\n" +
+                                    "accept-encoding: nothing\r\n" +
+                                    "cookie: aaa=1234567890\r\n" +
+                                    "\r\n";
+
+                request +=
+                        "GET /data?writes=1&block=16 HTTP/1.1\r\n" +
+                                "host: " + _serverURI.getHost() + ":" + _serverURI.getPort() + "\r\n" +
+                                "user-agent: testharness/1.0 (blah foo/bar)\r\n" +
+                                "accept-encoding: nothing\r\n" +
+                                "cookie: aaa=bbbbbb\r\n" +
+                                "Connection: close\r\n" +
+                                "\r\n";
 
                 os.write(request.getBytes());
                 os.flush();
@@ -793,18 +939,14 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
                 LineNumberReader in = new LineNumberReader(new InputStreamReader(client.getInputStream()));
 
                 String line = in.readLine();
-                int count=0;
-                while (line!=null)
+                int count = 0;
+                while (line != null)
                 {
                     if ("HTTP/1.1 200 OK".equals(line))
                         count++;
                     line = in.readLine();
                 }
-                assertEquals(pipeline,count);
-            }
-            finally
-            {
-                client.close();
+                assertEquals(pipeline, count);
             }
         }
     }
@@ -813,88 +955,82 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
     public void testRecycledWriters() throws Exception
     {
         configureServer(new EchoHandler());
-
-        Socket client=newSocket(HOST,_connector.getLocalPort());
-        try
+        try (Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort()))
         {
-            OutputStream os=client.getOutputStream();
-            InputStream is=client.getInputStream();
+            OutputStream os = client.getOutputStream();
+            InputStream is = client.getInputStream();
 
             os.write((
-                    "POST /echo?charset=utf-8 HTTP/1.1\r\n"+
-                    "host: "+HOST+":"+_connector.getLocalPort()+"\r\n"+
-                    "content-type: text/plain; charset=utf-8\r\n"+
-                    "content-length: 10\r\n"+
-                    "\r\n").getBytes("iso-8859-1"));
+                    "POST /echo?charset=utf-8 HTTP/1.1\r\n" +
+                            "host: " + _serverURI.getHost() + ":" + _serverURI.getPort() + "\r\n" +
+                            "content-type: text/plain; charset=utf-8\r\n" +
+                            "content-length: 10\r\n" +
+                            "\r\n").getBytes(StandardCharsets.ISO_8859_1));
 
             os.write((
                     "123456789\n"
             ).getBytes("utf-8"));
 
             os.write((
-                    "POST /echo?charset=utf-8 HTTP/1.1\r\n"+
-                    "host: "+HOST+":"+_connector.getLocalPort()+"\r\n"+
-                    "content-type: text/plain; charset=utf-8\r\n"+
-                    "content-length: 10\r\n"+
-                    "\r\n"
-            ).getBytes("iso-8859-1"));
+                    "POST /echo?charset=utf-8 HTTP/1.1\r\n" +
+                            "host: " + _serverURI.getHost() + ":" + _serverURI.getPort() + "\r\n" +
+                            "content-type: text/plain; charset=utf-8\r\n" +
+                            "content-length: 10\r\n" +
+                            "\r\n"
+            ).getBytes(StandardCharsets.ISO_8859_1));
 
             os.write((
                     "abcdefghZ\n"
             ).getBytes("utf-8"));
 
-            String content="Wibble";
-            byte[] contentB=content.getBytes("utf-8");
+            String content = "Wibble";
+            byte[] contentB = content.getBytes("utf-8");
             os.write((
-                    "POST /echo?charset=utf-16 HTTP/1.1\r\n"+
-                    "host: "+HOST+":"+_connector.getLocalPort()+"\r\n"+
-                    "content-type: text/plain; charset=utf-8\r\n"+
-                    "content-length: "+contentB.length+"\r\n"+
-                    "connection: close\r\n"+
-                    "\r\n"
-            ).getBytes("iso-8859-1"));
+                    "POST /echo?charset=utf-16 HTTP/1.1\r\n" +
+                            "host: " + _serverURI.getHost() + ":" + _serverURI.getPort() + "\r\n" +
+                            "content-type: text/plain; charset=utf-8\r\n" +
+                            "content-length: " + contentB.length + "\r\n" +
+                            "connection: close\r\n" +
+                            "\r\n"
+            ).getBytes(StandardCharsets.ISO_8859_1));
             os.write(contentB);
 
             os.flush();
 
             ByteArrayOutputStream bout = new ByteArrayOutputStream();
-            IO.copy(is,bout);
-            byte[] b=bout.toByteArray();
+            IO.copy(is, bout);
+            byte[] b = bout.toByteArray();
 
             //System.err.println("OUTPUT: "+new String(b));
-            int i=0;
-            while (b[i]!='Z')
+            int i = 0;
+            while (b[i] != 'Z')
                 i++;
-            int state=0;
-            while(state!=4)
+            int state = 0;
+            while (state != 4)
             {
-                switch(b[i++])
+                switch (b[i++])
                 {
                     case '\r':
-                        if (state==0||state==2)
+                        if (state == 0 || state == 2)
                             state++;
                         continue;
                     case '\n':
-                        if (state==1||state==3)
+                        if (state == 1 || state == 3)
                             state++;
                         continue;
 
                     default:
-                        state=0;
+                        state = 0;
                 }
             }
 
-            String in = new String(b,0,i,"utf-8");
-            assertTrue(in.indexOf("123456789")>=0);
-            assertTrue(in.indexOf("abcdefghZ")>=0);
-            assertTrue(in.indexOf("Wibble")<0);
+            String in = new String(b, 0, i, StandardCharsets.UTF_8);
+            assertTrue(in.contains("123456789"));
+            assertTrue(in.contains("abcdefghZ"));
+            assertFalse(in.contains("Wibble"));
 
-            in = new String(b,i,b.length-i,"utf-16");
-            assertEquals("Wibble\n",in);
-        }
-        finally
-        {
-            client.close();
+            in = new String(b, i, b.length - i, StandardCharsets.UTF_16);
+            assertEquals("Wibble\n", in);
         }
     }
 
@@ -903,50 +1039,41 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
     {
         configureServer(new EchoHandler(false));
 
-        Socket client=newSocket(HOST,_connector.getLocalPort());
-        try
+        try (Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort()))
         {
-            OutputStream os=client.getOutputStream();
-            InputStream is=client.getInputStream();
+            OutputStream os = client.getOutputStream();
+            InputStream is = client.getInputStream();
 
             os.write((
-                "POST /R1 HTTP/1.1\015\012"+
-                "Host: "+HOST+":"+_connector.getLocalPort()+"\r\n"+
-                "content-type: text/plain; charset=utf-8\r\n"+
-                "content-length: 10\r\n"+
-                "\015\012"+
-                "123456789\n" +
-
-                "HEAD /R1 HTTP/1.1\015\012"+
-                "Host: "+HOST+":"+_connector.getLocalPort()+"\015\012"+
-                "content-type: text/plain; charset=utf-8\r\n"+
-                "content-length: 10\r\n"+
-                "\015\012"+
-                "123456789\n"+
-
-                "POST /R1 HTTP/1.1\015\012"+
-                "Host: "+HOST+":"+_connector.getLocalPort()+"\015\012"+
-                "content-type: text/plain; charset=utf-8\r\n"+
-                "content-length: 10\r\n"+
-                "Connection: close\015\012"+
-                "\015\012"+
-                "123456789\n"
-
-                ).getBytes("iso-8859-1"));
-
-            String in = IO.toString(is);
+                    "POST /R1 HTTP/1.1\015\012" +
+                            "Host: " + _serverURI.getHost() + ":" + _serverURI.getPort() + "\r\n" +
+                            "content-type: text/plain; charset=utf-8\r\n" +
+                            "content-length: 10\r\n" +
+                            "\015\012" +
+                            "123456789\n" +
+
+                            "HEAD /R2 HTTP/1.1\015\012" +
+                            "Host: " + _serverURI.getHost() + ":" + _serverURI.getPort() + "\015\012" +
+                            "content-type: text/plain; charset=utf-8\r\n" +
+                            "content-length: 10\r\n" +
+                            "\015\012" +
+                            "ABCDEFGHI\n" +
+
+                            "POST /R3 HTTP/1.1\015\012" +
+                            "Host: " + _serverURI.getHost() + ":" + _serverURI.getPort() + "\015\012" +
+                            "content-type: text/plain; charset=utf-8\r\n" +
+                            "content-length: 10\r\n" +
+                            "Connection: close\015\012" +
+                            "\015\012" +
+                            "abcdefghi\n"
+
+            ).getBytes(StandardCharsets.ISO_8859_1));
 
-            int index=in.indexOf("123456789");
-            assertTrue(index>0);
-            index=in.indexOf("123456789",index+1);
-            assertTrue(index>0);
-            index=in.indexOf("123456789",index+1);
-            assertTrue(index==-1);
 
-        }
-        finally
-        {
-            client.close();
+            String in = IO.toString(is);
+            Assert.assertThat(in,containsString("123456789"));
+            Assert.assertThat(in,not(containsString("ABCDEFGHI")));
+            Assert.assertThat(in,containsString("abcdefghi"));
         }
     }
 
@@ -955,57 +1082,52 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
     {
         configureServer(new EchoHandler());
 
-        Socket client=newSocket(HOST,_connector.getLocalPort());
-        try
+        try (Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort()))
         {
-            OutputStream os=client.getOutputStream();
-            InputStream is=client.getInputStream();
+            OutputStream os = client.getOutputStream();
+            InputStream is = client.getInputStream();
 
             os.write((
-                    "POST /echo?charset=utf-8 HTTP/1.1\r\n"+
-                    "host: "+HOST+":"+_connector.getLocalPort()+"\r\n"+
-                    "content-type: text/plain; charset=utf-8\r\n"+
-                    "content-length: 10\r\n"+
-                    "\r\n").getBytes("iso-8859-1"));
+                    "POST /echo?charset=utf-8 HTTP/1.1\r\n" +
+                            "host: " + _serverURI.getHost() + ":" + _serverURI.getPort() + "\r\n" +
+                            "content-type: text/plain; charset=utf-8\r\n" +
+                            "content-length: 10\r\n" +
+                            "\r\n").getBytes(StandardCharsets.ISO_8859_1));
 
             os.write((
                     "123456789\n"
             ).getBytes("utf-8"));
 
             os.write((
-                    "POST /echo?charset=utf-8 HTTP/1.1\r\n"+
-                    "host: "+HOST+":"+_connector.getLocalPort()+"\r\n"+
-                    "content-type: text/plain; charset=utf-8\r\n"+
-                    "content-length: 10\r\n"+
-                    "\r\n"
-            ).getBytes("iso-8859-1"));
+                    "POST /echo?charset=utf-8 HTTP/1.1\r\n" +
+                            "host: " + _serverURI.getHost() + ":" + _serverURI.getPort() + "\r\n" +
+                            "content-type: text/plain; charset=utf-8\r\n" +
+                            "content-length: 10\r\n" +
+                            "\r\n"
+            ).getBytes(StandardCharsets.ISO_8859_1));
 
             os.write((
                     "abcdefghi\n"
-            ).getBytes("utf-8"));
+            ).getBytes(StandardCharsets.UTF_8));
 
-            String content="Wibble";
-            byte[] contentB=content.getBytes("utf-16");
+            String content = "Wibble";
+            byte[] contentB = content.getBytes(StandardCharsets.UTF_16);
             os.write((
-                    "POST /echo?charset=utf-8 HTTP/1.1\r\n"+
-                    "host: "+HOST+":"+_connector.getLocalPort()+"\r\n"+
-                    "content-type: text/plain; charset=utf-16\r\n"+
-                    "content-length: "+contentB.length+"\r\n"+
-                    "connection: close\r\n"+
-                    "\r\n"
-            ).getBytes("iso-8859-1"));
+                    "POST /echo?charset=utf-8 HTTP/1.1\r\n" +
+                            "host: " + _serverURI.getHost() + ":" + _serverURI.getPort() + "\r\n" +
+                            "content-type: text/plain; charset=utf-16\r\n" +
+                            "content-length: " + contentB.length + "\r\n" +
+                            "connection: close\r\n" +
+                            "\r\n"
+            ).getBytes(StandardCharsets.ISO_8859_1));
             os.write(contentB);
 
             os.flush();
 
             String in = IO.toString(is);
-            assertTrue(in.indexOf("123456789")>=0);
-            assertTrue(in.indexOf("abcdefghi")>=0);
-            assertTrue(in.indexOf("Wibble")>=0);
-        }
-        finally
-        {
-            client.close();
+            assertTrue(in.contains("123456789"));
+            assertTrue(in.contains("abcdefghi"));
+            assertTrue(in.contains("Wibble"));
         }
     }
 
@@ -1014,20 +1136,19 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
     {
         configureServer(new HelloWorldHandler());
 
-        Socket client=newSocket(HOST,_connector.getLocalPort());
-        try
+        try (Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort()))
         {
-            OutputStream os=client.getOutputStream();
-            InputStream is=client.getInputStream();
+            OutputStream os = client.getOutputStream();
+            InputStream is = client.getInputStream();
 
             // Send a request with chunked input and expect 100
             os.write((
-                    "GET / HTTP/1.1\r\n"+
-                    "Host: "+HOST+":"+_connector.getLocalPort()+"\r\n"+
-                    "Transfer-Encoding: chunked\r\n"+
-                    "Expect: 100-continue\r\n"+
-                    "Connection: Keep-Alive\r\n"+
-                    "\r\n"
+                    "GET / HTTP/1.1\r\n" +
+                            "Host: " + _serverURI.getHost() + ":" + _serverURI.getPort() + "\r\n" +
+                            "Transfer-Encoding: chunked\r\n" +
+                            "Expect: 100-continue\r\n" +
+                            "Connection: Keep-Alive\r\n" +
+                            "\r\n"
             ).getBytes());
 
             // Never send a body.
@@ -1037,71 +1158,69 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
             os.flush();
 
             client.setSoTimeout(2000);
-            long start=System.currentTimeMillis();
+            long start = System.currentTimeMillis();
             String in = IO.toString(is);
-            assertTrue(System.currentTimeMillis()-start<1000);
-            assertTrue(in.indexOf("Connection: close")>0);
-            assertTrue(in.indexOf("Hello world")>0);
-
-        }
-        finally
-        {
-            client.close();
+            assertTrue(System.currentTimeMillis() - start < 1000);
+            assertTrue(in.indexOf("Connection: close") > 0);
+            assertTrue(in.indexOf("Hello world") > 0);
         }
     }
 
     @Test
     public void testCommittedError() throws Exception
     {
-        CommittedErrorHandler handler =new CommittedErrorHandler();
+        CommittedErrorHandler handler = new CommittedErrorHandler();
         configureServer(handler);
 
-        Socket client=newSocket(HOST,_connector.getLocalPort());
+        Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort());
         try
         {
-            ((StdErrLog)Log.getLogger(AbstractHttpConnection.class)).setHideStacks(true);
-            OutputStream os=client.getOutputStream();
-            InputStream is=client.getInputStream();
+            ((StdErrLog)Log.getLogger(HttpChannel.class)).setHideStacks(true);
+            ((StdErrLog)Log.getLogger(HttpChannel.class)).info("Expecting exception after commit then could not send 500....");
+            OutputStream os = client.getOutputStream();
+            InputStream is = client.getInputStream();
 
             // Send a request
-            os.write((
-                    "GET / HTTP/1.1\r\n"+
-                    "Host: "+HOST+":"+_connector.getLocalPort()+"\r\n" +
+            os.write(("GET / HTTP/1.1\r\n" +
+                    "Host: " + _serverURI.getHost() + ":" + _serverURI.getPort() + "\r\n" +
                     "\r\n"
             ).getBytes());
             os.flush();
 
+
             client.setSoTimeout(2000);
             String in = IO.toString(is);
 
-            assertEquals(-1,is.read()); // Closed by error!
+            assertEquals(-1, is.read()); // Closed by error!
 
-            assertTrue(in.indexOf("HTTP/1.1 200 OK")>=0);
-            assertTrue(in.indexOf("Transfer-Encoding: chunked")>0);
-            assertTrue(in.indexOf("Now is the time for all good men to come to the aid of the party")>0);
-            assertTrue(in.indexOf("\r\n0\r\n")==-1); // chunking is interrupted by error close
+            assertTrue(in.contains("HTTP/1.1 200 OK"));
+            assertTrue(in.indexOf("Transfer-Encoding: chunked") > 0);
+            assertTrue(in.indexOf("Now is the time for all good men to come to the aid of the party") > 0);
+            assertThat(in, Matchers.not(Matchers.containsString("\r\n0\r\n")));
 
             client.close();
-            Thread.sleep(100);
+            Thread.sleep(200);
+
             assertTrue(!handler._endp.isOpen());
         }
         finally
         {
-            ((StdErrLog)Log.getLogger(AbstractHttpConnection.class)).setHideStacks(false);
+            ((StdErrLog)Log.getLogger(HttpChannel.class)).setHideStacks(false);
 
             if (!client.isClosed())
                 client.close();
         }
     }
 
-    protected static class CommittedErrorHandler extends AbstractHandler
+    public static class CommittedErrorHandler extends AbstractHandler
     {
         public EndPoint _endp;
 
+        @Override
         public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
         {
-            _endp=baseRequest.getConnection().getEndPoint();
-            response.setHeader("test","value");
+            _endp = baseRequest.getHttpChannel().getEndPoint();
+            response.setHeader("test", "value");
             response.setStatus(200);
             response.setContentType("text/plain");
             response.getWriter().println("Now is the time for all good men to come to the aid of the party");
@@ -1114,26 +1233,32 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
 
     protected static class AvailableHandler extends AbstractHandler
     {
-        public Exchanger<Object> _ex = new Exchanger<Object>();
+        public Exchanger<Object> _ex = new Exchanger<>();
 
+        @Override
         public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
         {
             baseRequest.setHandled(true);
             response.setStatus(200);
             response.setContentType("text/plain");
             InputStream in = request.getInputStream();
-            ServletOutputStream out=response.getOutputStream();
+            ServletOutputStream out = response.getOutputStream();
 
-            // should always be some input available, because of deferred dispatch.
-            int avail=in.available();
+            // this should initially be 0 bytes available.
+            int avail = in.available();
             out.println(avail);
 
-            String buf="";
-            for (int i=0;i<avail;i++)
-                buf+=(char)in.read();
-
+            // block for the first character
+            String buf = "";
+            buf += (char)in.read();
+            
+            // read remaining available bytes
+            avail = in.available();
+            out.println(avail);
+            for (int i = 0; i < avail; i++)
+                buf += (char)in.read();
 
-            avail=in.available();
+            avail = in.available();
             out.println(avail);
 
             try
@@ -1141,63 +1266,70 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
                 _ex.exchange(null);
                 _ex.exchange(null);
             }
-            catch(InterruptedException e)
+            catch (InterruptedException e)
             {
                 e.printStackTrace();
             }
 
-            avail=in.available();
+            avail = in.available();
 
-            if (avail==0)
+            if (avail == 0)
             {
                 // handle blocking channel connectors
-                buf+=(char)in.read();
-                avail=in.available();
-                out.println(avail+1);
+                buf += (char)in.read();
+                avail = in.available();
+                out.println(avail + 1);
             }
-            else if (avail==1)
+            else if (avail == 1)
             {
                 // handle blocking socket connectors
-                buf+=(char)in.read();
-                avail=in.available();
-                out.println(avail+1);
+                buf += (char)in.read();
+                avail = in.available();
+                out.println(avail + 1);
             }
             else
                 out.println(avail);
 
-            while (avail>0)
+            while (avail > 0)
             {
-                buf+=(char)in.read();
-                avail=in.available();
+                buf += (char)in.read();
+                avail = in.available();
             }
 
 
             out.println(avail);
+
+            // read remaining no matter what
+            int b = in.read();
+            while (b >= 0)
+            {
+                buf += (char)b;
+                b = in.read();
+            }
             out.println(buf);
             out.close();
         }
     }
 
-
     @Test
     public void testAvailable() throws Exception
     {
-        AvailableHandler ah=new AvailableHandler();
+        AvailableHandler ah = new AvailableHandler();
         configureServer(ah);
+        _connector.getConnectionFactory(HttpConnectionFactory.class).getHttpConfiguration().setDelayDispatchUntilContent(false);
 
-        Socket client=newSocket(HOST,_connector.getLocalPort());
-        try
+        try (Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort()))
         {
-            OutputStream os=client.getOutputStream();
-            InputStream is=client.getInputStream();
+            OutputStream os = client.getOutputStream();
+            InputStream is = client.getInputStream();
 
             os.write((
-                    "GET /data?writes=1024&block=256 HTTP/1.1\r\n"+
-                    "host: "+HOST+":"+_connector.getLocalPort()+"\r\n"+
-                    "connection: close\r\n"+
-                    "content-type: unknown\r\n"+
-                    "content-length: 30\r\n"+
-                    "\r\n"
+                    "GET /data?writes=1024&block=256 HTTP/1.1\r\n" +
+                            "host: " + _serverURI.getHost() + ":" + _serverURI.getPort() + "\r\n" +
+                            "connection: close\r\n" +
+                            "content-type: unknown\r\n" +
+                            "content-length: 30\r\n" +
+                            "\r\n"
             ).getBytes());
             os.flush();
             Thread.sleep(500);
@@ -1217,32 +1349,25 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
 
             BufferedReader reader = new BufferedReader(new InputStreamReader(is));
             // skip header
-            while(reader.readLine().length()>0);
-            assertThat(Integer.parseInt(reader.readLine()),Matchers.greaterThan(0));
-            assertEquals(0,Integer.parseInt(reader.readLine()));
-            assertThat(Integer.parseInt(reader.readLine()),Matchers.greaterThan(0));
-            assertEquals(0,Integer.parseInt(reader.readLine()));
-            assertEquals("1234567890abcdefghijklmnopqrst",reader.readLine());
-
-        }
-        finally
-        {
-            client.close();
+            while (reader.readLine().length() > 0) ;
+            assertThat(Integer.parseInt(reader.readLine()), Matchers.equalTo(0));
+            assertThat(Integer.parseInt(reader.readLine()), Matchers.equalTo(9));
+            assertThat(Integer.parseInt(reader.readLine()), Matchers.equalTo(0));
+            assertThat(Integer.parseInt(reader.readLine()), Matchers.greaterThan(0));
+            assertThat(Integer.parseInt(reader.readLine()), Matchers.equalTo(0));
+            assertEquals("1234567890abcdefghijklmnopqrst", reader.readLine());
         }
     }
 
-
     @Test
     public void testDualRequest1() throws Exception
     {
         configureServer(new HelloWorldHandler());
 
-        Socket client1=newSocket(HOST,_connector.getLocalPort());
-        Socket client2=newSocket(HOST,_connector.getLocalPort());
-        try
+        try (Socket client1 = newSocket(_serverURI.getHost(), _serverURI.getPort()); Socket client2 = newSocket(_serverURI.getHost(), _serverURI.getPort()))
         {
-            OutputStream os1=client1.getOutputStream();
-            OutputStream os2=client2.getOutputStream();
+            OutputStream os1 = client1.getOutputStream();
+            OutputStream os2 = client2.getOutputStream();
 
             os1.write(REQUEST1.getBytes());
             os2.write(REQUEST1.getBytes());
@@ -1250,17 +1375,12 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
             os2.flush();
 
             // Read the response.
-            String response1=readResponse(client1);
-            String response2=readResponse(client2);
+            String response1 = readResponse(client1);
+            String response2 = readResponse(client2);
 
             // Check the response
-            assertEquals("client1",RESPONSE1,response1);
-            assertEquals("client2",RESPONSE1,response2);
-        }
-        finally
-        {
-            client1.close();
-            client2.close();
+            assertEquals("client1", RESPONSE1, response1);
+            assertEquals("client2", RESPONSE1, response2);
         }
     }
 
@@ -1273,16 +1393,13 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
      */
     protected static String readResponse(Socket client) throws IOException
     {
-        BufferedReader br=null;
 
-        StringBuilder sb=new StringBuilder();
-        try
+        StringBuilder sb = new StringBuilder();
+        try (BufferedReader br = new BufferedReader(new InputStreamReader(client.getInputStream())))
         {
-            br=new BufferedReader(new InputStreamReader(client.getInputStream()));
-
             String line;
 
-            while ((line=br.readLine())!=null)
+            while ((line = br.readLine()) != null)
             {
                 sb.append(line);
                 sb.append('\n');
@@ -1290,31 +1407,24 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
 
             return sb.toString();
         }
-        catch(IOException e)
+        catch (IOException e)
         {
-            System.err.println(e+" while reading '"+sb+"'");
+            System.err.println(e + " while reading '" + sb + "'");
             throw e;
         }
-        finally
-        {
-            if (br!=null)
-            {
-                br.close();
-            }
-        }
     }
 
-    private void writeFragments(byte[] bytes, int[] points, StringBuilder message, OutputStream os) throws IOException, InterruptedException
+    protected void writeFragments(byte[] bytes, int[] points, StringBuilder message, OutputStream os) throws IOException, InterruptedException
     {
-        int last=0;
+        int last = 0;
 
         // Write out the fragments
-        for (int j=0; j<points.length; ++j)
+        for (int j = 0; j < points.length; ++j)
         {
-            int point=points[j];
+            int point = points[j];
 
-            os.write(bytes,last,point-last);
-            last=point;
+            os.write(bytes, last, point - last);
+            last = point;
             os.flush();
             Thread.sleep(PAUSE);
 
@@ -1323,50 +1433,51 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
         }
 
         // Write the last fragment
-        os.write(bytes,last,bytes.length-last);
+        os.write(bytes, last, bytes.length - last);
         os.flush();
         Thread.sleep(PAUSE);
     }
 
-
-
     @Test
-    public void testUnreadInput () throws Exception
+    public void testUnreadInput() throws Exception
     {
         configureServer(new NoopHandler());
-        final int REQS=5;
-        String content="This is a loooooooooooooooooooooooooooooooooo"+
-        "ooooooooooooooooooooooooooooooooooooooooooooo"+
-        "ooooooooooooooooooooooooooooooooooooooooooooo"+
-        "ooooooooooooooooooooooooooooooooooooooooooooo"+
-        "ooooooooooooooooooooooooooooooooooooooooooooo"+
-        "ooooooooooooooooooooooooooooooooooooooooooooo"+
-        "ooooooooooooooooooooooooooooooooooooooooooooo"+
-        "ooooooooooooooooooooooooooooooooooooooooooooo"+
-        "ooooooooooooooooooooooooooooooooooooooooooooo"+
-        "oooooooooooonnnnnnnnnnnnnnnnggggggggg content"+
-        new String(new char[65*1024]);
+        final int REQS = 2;
+        char[] fill = new char[65 * 1024];
+        Arrays.fill(fill, '.');
+        String content = "This is a loooooooooooooooooooooooooooooooooo" +
+                "ooooooooooooooooooooooooooooooooooooooooooooo" +
+                "ooooooooooooooooooooooooooooooooooooooooooooo" +
+                "ooooooooooooooooooooooooooooooooooooooooooooo" +
+                "ooooooooooooooooooooooooooooooooooooooooooooo" +
+                "ooooooooooooooooooooooooooooooooooooooooooooo" +
+                "ooooooooooooooooooooooooooooooooooooooooooooo" +
+                "ooooooooooooooooooooooooooooooooooooooooooooo" +
+                "ooooooooooooooooooooooooooooooooooooooooooooo" +
+                "oooooooooooonnnnnnnnnnnnnnnnggggggggg content" +
+                new String(fill);
         final byte[] bytes = content.getBytes();
 
-        Socket client=newSocket(HOST,_connector.getLocalPort());
-        final OutputStream out=client.getOutputStream();
+        Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort());
+        final OutputStream out = client.getOutputStream();
 
         new Thread()
         {
+            @Override
             public void run()
             {
                 try
                 {
-                    for (int i=0; i<REQS; i++)
+                    for (int i = 0; i < REQS; i++)
                     {
-                        out.write("GET / HTTP/1.1\r\nHost: localhost\r\n".getBytes(StringUtil.__ISO_8859_1));
-                        out.write(("Content-Length: "+bytes.length+"\r\n" + "\r\n").getBytes(StringUtil.__ISO_8859_1));
-                        out.write(bytes,0,bytes.length);
+                        out.write("GET / HTTP/1.1\r\nHost: localhost\r\n".getBytes(StandardCharsets.ISO_8859_1));
+                        out.write(("Content-Length: " + bytes.length + "\r\n" + "\r\n").getBytes(StandardCharsets.ISO_8859_1));
+                        out.write(bytes, 0, bytes.length);
                     }
-                    out.write("GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n".getBytes(StringUtil.__ISO_8859_1));
+                    out.write("GET / HTTP/1.1\r\nHost: last\r\nConnection: close\r\n\r\n".getBytes(StandardCharsets.ISO_8859_1));
                     out.flush();
                 }
-                catch(Exception e)
+                catch (Exception e)
                 {
                     e.printStackTrace();
                 }
@@ -1375,22 +1486,23 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
 
         String resps = readResponse(client);
 
-        int offset=0;
-        for (int i=0;i<(REQS+1);i++)
+        int offset = 0;
+        for (int i = 0; i < (REQS + 1); i++)
         {
-            int ok=resps.indexOf("HTTP/1.1 200 OK",offset);
-            assertThat("resp"+i,ok,greaterThanOrEqualTo(offset));
-            offset=ok+15;
+            int ok = resps.indexOf("HTTP/1.1 200 OK", offset);
+            assertThat("resp" + i, ok, greaterThanOrEqualTo(offset));
+            offset = ok + 15;
         }
     }
 
     public class NoopHandler extends AbstractHandler
     {
+        @Override
         public void handle(String target, Request baseRequest,
-                HttpServletRequest request, HttpServletResponse response) throws IOException,
+                           HttpServletRequest request, HttpServletResponse response) throws IOException,
                 ServletException
         {
-           //don't read the input, just send something back
+            //don't read the input, just send something back
             ((Request)request).setHandled(true);
             response.setStatus(200);
         }
@@ -1405,36 +1517,36 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
         suspend.setResumeAfter(1000);
         configureServer(suspend);
 
-        long start=System.currentTimeMillis();
-        Socket client=newSocket(HOST,_connector.getLocalPort());
+        long start = System.currentTimeMillis();
+        Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort());
+        client.setSoTimeout(5000);
         try
         {
-            OutputStream os=client.getOutputStream();
-            InputStream is=client.getInputStream();
+            OutputStream os = client.getOutputStream();
 
             // write an initial request
             os.write((
-                    "GET / HTTP/1.1\r\n"+
-                    "host: "+HOST+":"+_connector.getLocalPort()+"\r\n"+
-                    "\r\n"
+                    "GET / HTTP/1.1\r\n" +
+                            "host: " + _serverURI.getHost() + ":" + _serverURI.getPort() + "\r\n" +
+                            "\r\n"
             ).getBytes());
             os.flush();
-            
+
             Thread.sleep(200);
-            
+
             // write an pipelined request
             os.write((
-                    "GET / HTTP/1.1\r\n"+
-                    "host: "+HOST+":"+_connector.getLocalPort()+"\r\n"+
-                    "connection: close\r\n"+
-                    "\r\n"
+                    "GET / HTTP/1.1\r\n" +
+                            "host: " + _serverURI.getHost() + ":" + _serverURI.getPort() + "\r\n" +
+                            "connection: close\r\n" +
+                            "\r\n"
             ).getBytes());
             os.flush();
-            
-            String response=readResponse(client);
-            assertThat(response,JUnitMatchers.containsString("RESUMEDHTTP/1.1 200 OK"));
-            assertThat((System.currentTimeMillis()-start),greaterThanOrEqualTo(1999L));
-            
+
+            String response = readResponse(client);
+            assertThat(response, containsString("RESUMEDHTTP/1.1 200 OK"));
+            assertThat((System.currentTimeMillis() - start), greaterThanOrEqualTo(1999L));
+
             // TODO This test should also check that that the CPU did not spin during the suspend.
         }
         finally
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestFixture.java b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestFixture.java
index 367521c..c060342 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestFixture.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestFixture.java
@@ -23,13 +23,12 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.io.PrintWriter;
-import java.io.Writer;
 import java.net.Socket;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
 
 import javax.net.ssl.HostnameVerifier;
 import javax.net.ssl.SSLSession;
-import javax.net.ssl.TrustManager;
-import javax.net.ssl.X509TrustManager;
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
@@ -38,17 +37,17 @@ import org.eclipse.jetty.server.handler.AbstractHandler;
 import org.eclipse.jetty.server.handler.HandlerWrapper;
 import org.eclipse.jetty.toolchain.test.PropertyFlag;
 import org.eclipse.jetty.util.IO;
-import org.junit.AfterClass;
-
+import org.junit.After;
+import org.junit.Before;
 
 public class HttpServerTestFixture
 {    // Useful constants
     protected static final long PAUSE=10L;
-    protected static final int LOOPS=PropertyFlag.isEnabled("test.stress")?250:50;
-    protected static final String HOST="localhost";
-    
-    protected static Server _server;
-    protected static Connector _connector;
+    protected static final int LOOPS= PropertyFlag.isEnabled("test.stress")?250:50;
+
+    protected Server _server;
+    protected URI _serverURI;
+    protected ServerConnector _connector;
     protected String _scheme="http";
 
     protected Socket newSocket(String host,int port) throws Exception
@@ -59,21 +58,29 @@ public class HttpServerTestFixture
         socket.setSoLinger(false,0);
         return socket;
     }
-    
-    protected static void startServer(Connector connector) throws Exception
+
+    @Before
+    public void before()
     {
         _server = new Server();
+    }
+
+    protected void startServer(ServerConnector connector) throws Exception
+    {
         _connector = connector;
+        _connector.getConnectionFactory(HttpConnectionFactory.class).getHttpConfiguration().setSendDateHeader(false);
         _server.addConnector(_connector);
         _server.setHandler(new HandlerWrapper());
         _server.start();
+        _serverURI = _server.getURI();
     }
 
-    @AfterClass
-    public static void stopServer() throws Exception
+    @After
+    public void stopServer() throws Exception
     {
         _server.stop();
         _server.join();
+        _server.setConnectors(new Connector[]{});
     }
 
     protected void configureServer(Handler handler) throws Exception
@@ -83,20 +90,21 @@ public class HttpServerTestFixture
         current.setHandler(handler);
         current.start();
     }
-    
+
 
     protected static class EchoHandler extends AbstractHandler
     {
-        boolean musthavecontent=true;
-        
+        boolean _musthavecontent=true;
+
         public EchoHandler()
         {}
-        
+
         public EchoHandler(boolean content)
         {
-            musthavecontent=false;
+            _musthavecontent=false;
         }
-        
+
+        @Override
         public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
         {
             baseRequest.setHandled(true);
@@ -112,21 +120,22 @@ public class HttpServerTestFixture
 
             int count=0;
             BufferedReader reader=request.getReader();
+
             if (request.getContentLength()!=0)
             {
-                String line;
-                
-                while ((line=reader.readLine())!=null)
+                String line=reader.readLine();
+                while (line!=null)
                 {
                     writer.print(line);
                     writer.print("\n");
                     count+=line.length();
+                    line=reader.readLine();
                 }
             }
-            
+
             if (count==0)
             {
-                if (musthavecontent)
+                if (_musthavecontent)
                     throw new IllegalStateException("no input recieved");
 
                 writer.println("No content");
@@ -141,8 +150,24 @@ public class HttpServerTestFixture
         }
     }
 
+    protected static class OptionsHandler extends AbstractHandler
+    {
+        @Override
+        public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+        {
+            baseRequest.setHandled(true);
+            if (request.getMethod().equals("OPTIONS"))
+                response.setStatus(200);
+            else
+                response.setStatus(500);
+
+            response.setHeader("Allow", "GET");
+        }
+    }
+    
     protected static class HelloWorldHandler extends AbstractHandler
     {
+        @Override
         public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
         {
             baseRequest.setHandled(true);
@@ -153,13 +178,14 @@ public class HttpServerTestFixture
 
     protected static class DataHandler extends AbstractHandler
     {
+        @Override
         public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
         {
             baseRequest.setHandled(true);
             response.setStatus(200);
 
             InputStream in = request.getInputStream();
-            String input=IO.toString(in);
+            String input= IO.toString(in);
 
             String tmp = request.getParameter("writes");
             int writes=Integer.parseInt(tmp==null?"10":tmp);
@@ -171,12 +197,12 @@ public class HttpServerTestFixture
             String data = "\u0a870123456789A\u0a87CDEFGHIJKLMNOPQRSTUVWXYZ\u0250bcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
             while (data.length()<block)
                 data+=data;
-            
+
             String chunk = (input+data).substring(0,block);
             response.setContentType("text/plain");
             if (encoding==null)
             {
-                byte[] bytes=chunk.getBytes("ISO-8859-1");
+                byte[] bytes=chunk.getBytes(StandardCharsets.ISO_8859_1);
                 OutputStream out=response.getOutputStream();
                 for (int i=0;i<writes;i++)
                 {
@@ -186,27 +212,31 @@ public class HttpServerTestFixture
             else if ("true".equals(chars))
             {
                 response.setCharacterEncoding(encoding);
-                Writer out=response.getWriter();
+                PrintWriter out=response.getWriter();
                 char[] c=chunk.toCharArray();
                 for (int i=0;i<writes;i++)
                 {
                     out.write(c);
+                    if (out.checkError())
+                        break;
                 }
             }
             else
             {
                 response.setCharacterEncoding(encoding);
-                Writer out=response.getWriter();
+                PrintWriter out=response.getWriter();
                 for (int i=0;i<writes;i++)
                 {
                     out.write(chunk);
+                    if (out.checkError())
+                        break;
                 }
             }
 
         }
     }
 
-    
+
     public final static HostnameVerifier __hostnameverifier = new HostnameVerifier()
     {
         public boolean verify(String hostname, SSLSession session)
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpURITest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpURITest.java
index 2ef5ae2..7976217 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpURITest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpURITest.java
@@ -27,16 +27,14 @@ import static org.junit.Assert.fail;
 import java.io.UnsupportedEncodingException;
 import java.net.URLDecoder;
 import java.net.URLEncoder;
-import java.util.Iterator;
-import java.util.Set;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
 
-import junit.framework.Assert;
-
-import org.eclipse.jetty.http.EncodedHttpURI;
 import org.eclipse.jetty.http.HttpURI;
-import org.eclipse.jetty.io.ByteArrayBuffer;
 import org.eclipse.jetty.util.MultiMap;
 import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.Utf8Appendable;
+import org.junit.Assert;
 import org.junit.Test;
 
 public class HttpURITest
@@ -217,7 +215,6 @@ public class HttpURITest
     @Test
     public void testNoPercentEncodingOfQueryUsingNonUTF8() throws Exception
     {
-        
         byte[] utf8_bytes = "/%D0%A1%D1%82%D1%80%D0%BE%D0%BD%D0%B3-%D1%84%D0%B8%D0%BB%D1%8C%D1%82%D1%80/%D0%BA%D0%B0%D1%82%D0%B0%D0%BB%D0%BE%D0%B3?".getBytes("UTF-8");
         byte[] cp1251_bytes = TypeUtil.fromHexString("e2fbe1f0e0edee3dd2e5ecefe5f0e0f2f3f0e0");
         String expectedCP1251String = new String(cp1251_bytes, "cp1251");
@@ -234,7 +231,7 @@ public class HttpURITest
             allbytes[i+j] = cp1251_bytes[j];
         
         //Test using a HttpUri that expects a particular charset encoding. See URIUtil.__CHARSET
-        EncodedHttpURI uri = new EncodedHttpURI("cp1251");
+        HttpURI uri = new HttpURI(Charset.forName("cp1251"));
         uri.parse(allbytes, 0, allbytes.length);
         assertEquals(expectedCP1251String, uri.getQuery("cp1251"));
         
@@ -259,7 +256,7 @@ public class HttpURITest
         assertEquals(expectedCP1251Value, val);
         
         //test able to set the query encoding and call getQueryString multiple times
-        Request request = new Request();
+        Request request = new Request(null,null);
         request.setUri(httpuri);    
         request.setAttribute("org.eclipse.jetty.server.Request.queryEncoding", "ISO-8859-1");
         assertNotNull (request.getQueryString()); //will be incorrect encoding but not null
@@ -304,7 +301,7 @@ public class HttpURITest
       assertEquals(expectedCP1251Value, val);
       
       //test able to set the query encoding and call getQueryString multiple times
-      Request request = new Request();
+      Request request = new Request(null,null);
       request.setUri(httpuri);    
       request.setAttribute("org.eclipse.jetty.server.Request.queryEncoding", "ISO-8859-1");
       assertNotNull (request.getQueryString()); //will be incorrect encoding but not null
@@ -326,30 +323,16 @@ public class HttpURITest
         {
         }
 
-        try
-        {
-            HttpURI huri=new HttpURI(uri);
-            MultiMap<String> params = new MultiMap<String>();
-            huri.decodeQueryTo(params);
-            System.err.println(params);
-            Assert.assertTrue(false);
-        }
-        catch (IllegalArgumentException e)
-        {
-        }
-        
-        try
-        {
-            HttpURI huri=new HttpURI(uri);
-            MultiMap<String> params = new MultiMap<String>();
-            huri.decodeQueryTo(params,"UTF-8");
-            System.err.println(params);
-            Assert.assertTrue(false);
-        }
-        catch (IllegalArgumentException e)
-        {
-        }        
-        
+        HttpURI huri=new HttpURI(uri);
+        MultiMap<String> params = new MultiMap<>();
+        huri.decodeQueryTo(params);
+        assertEquals("data"+Utf8Appendable.REPLACEMENT+"here"+Utf8Appendable.REPLACEMENT,params.getValue("invalid",0));
+
+        huri=new HttpURI(uri);
+        params = new MultiMap<>();
+        huri.decodeQueryTo(params,StandardCharsets.UTF_8);
+        assertEquals("data"+Utf8Appendable.REPLACEMENT+"here"+Utf8Appendable.REPLACEMENT,params.getValue("invalid",0));
+
     }
 
     @Test
@@ -358,14 +341,14 @@ public class HttpURITest
         for (String value: new String[]{"a","abcdABCD","\u00C0","\u697C","\uD869\uDED5","\uD840\uDC08"} )
         {
             HttpURI uri = new HttpURI("/path?value="+URLEncoder.encode(value,"UTF-8"));
-            
-            MultiMap<String> parameters = new MultiMap<String>();
-            uri.decodeQueryTo(parameters,"UTF-8");
-            assertEquals(value,parameters.get("value"));
+
+            MultiMap<String> parameters = new MultiMap<>();
+            uri.decodeQueryTo(parameters,StandardCharsets.UTF_8);
+            assertEquals(value,parameters.getString("value"));
         }
     }
-    
-    
+
+
     private final String[][] connect_tests=
     {
        /* 0*/ {"  localhost:8080  ","localhost","8080"},
@@ -383,9 +366,10 @@ public class HttpURITest
         {
             try
             {
-                ByteArrayBuffer buf = new ByteArrayBuffer(connect_tests[i][0]);
-                uri.parseConnect(buf.array(),2,buf.length()-4);
-                assertEquals("path"+i,connect_tests[i][1]+":"+connect_tests[i][2],uri.getPath());
+                byte[] buf = connect_tests[i][0].getBytes(StandardCharsets.UTF_8);
+
+                uri.parseConnect(buf,2,buf.length-4);
+                assertEquals("path"+i,connect_tests[i][0].trim(),uri.getPath());
                 assertEquals("host"+i,connect_tests[i][1],uri.getHost());
                 assertEquals("port"+i,Integer.parseInt(connect_tests[i][2]),uri.getPort());
             }
@@ -395,4 +379,24 @@ public class HttpURITest
             }
         }
     }
+
+    @Test
+    public void testNonURIAscii() throws Exception
+    {
+        String url = "http://www.foo.com/ma\u00F1ana";
+        byte[] asISO = url.getBytes(StandardCharsets.ISO_8859_1);
+        new String(asISO, StandardCharsets.ISO_8859_1);
+
+        //use a non UTF-8 charset as the encoding and url-escape as per
+        //http://www.w3.org/TR/html40/appendix/notes.html#non-ascii-chars
+        String s = URLEncoder.encode(url, "ISO-8859-1");
+        HttpURI uri = new HttpURI(StandardCharsets.ISO_8859_1);
+
+        //parse it, using the same encoding
+        uri.parse(s);
+
+        //decode the url encoding
+        String d = URLDecoder.decode(uri.getCompletePath(), "ISO-8859-1");
+        assertEquals(url, d);
+    }
 }
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpWriterTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpWriterTest.java
index 644c3d8..4bc9d99 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpWriterTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpWriterTest.java
@@ -19,19 +19,14 @@
 package org.eclipse.jetty.server;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
 
 import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
 
-import org.eclipse.jetty.http.AbstractGenerator;
-import org.eclipse.jetty.http.HttpFields;
-import org.eclipse.jetty.http.HttpGenerator;
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.Buffers;
-import org.eclipse.jetty.io.ByteArrayBuffer;
-import org.eclipse.jetty.io.ByteArrayEndPoint;
-import org.eclipse.jetty.io.Connection;
-import org.eclipse.jetty.io.SimpleBuffers;
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.MappedByteBufferPool;
+import org.eclipse.jetty.util.BufferUtil;
 import org.eclipse.jetty.util.StringUtil;
 import org.eclipse.jetty.util.TypeUtil;
 import org.eclipse.jetty.util.Utf8StringBuilder;
@@ -40,115 +35,82 @@ import org.junit.Test;
 
 public class HttpWriterTest
 {
-    private HttpWriter _writer;
-    private ByteArrayBuffer _bytes;
+    private HttpOutput _httpOut;
+    private ByteBuffer _bytes;
 
     @Before
     public void init() throws Exception
     {
-        _bytes = new ByteArrayBuffer(2048);
+        _bytes = BufferUtil.allocate(2048);
 
-        Buffers buffers = new SimpleBuffers(new ByteArrayBuffer(1024),new ByteArrayBuffer(1024));
-        ByteArrayEndPoint endp = new ByteArrayEndPoint();
-        AbstractGenerator generator =  new AbstractGenerator(buffers,endp)
+        final ByteBufferPool bufferPool = new MappedByteBufferPool();
+        HttpChannel<?> channel = new HttpChannel<ByteBuffer>(null,new HttpConfiguration(),null,null,new ByteBufferQueuedHttpInput())
         {
             @Override
-            public boolean isRequest()
+            public ByteBufferPool getByteBufferPool()
             {
-                return false;
+                return bufferPool;
             }
-
-            @Override
-            public boolean isResponse()
-            {
-                return true;
-            }
-
-            @Override
-            public void completeHeader(HttpFields fields, boolean allContentAdded) throws IOException
-            {
-            }
-
-            @Override
-            public int flushBuffer() throws IOException
-            {
-                return 0;
-            }
-
-            @Override
-            public int prepareUncheckedAddContent() throws IOException
-            {
-                return 1024;
-            }
-
-            public void addContent(Buffer content, boolean last) throws IOException
-            {
-                _bytes.put(content);
-                content.clear();
-            }
-
-            public boolean addContent(byte b) throws IOException
-            {
-                return false;
-            }
-
         };
 
-        AbstractHttpConnection connection = new AbstractHttpConnection(null,endp,new Server(),null,generator,null)
+        _httpOut = new HttpOutput(channel)
         {
             @Override
-            public Connection handle() throws IOException
+            public void write(byte[] b, int off, int len) throws IOException
             {
-                return null;
+                BufferUtil.append(_bytes, b, off, len);
             }
         };
-        endp.setMaxIdleTime(60000);
-   
-        HttpOutput httpOut = new HttpOutput(connection);
-        _writer = new HttpWriter(httpOut);
     }
 
     @Test
     public void testSimpleUTF8() throws Exception
     {
-        _writer.setCharacterEncoding(StringUtil.__UTF8);
+        HttpWriter _writer = new Utf8HttpWriter(_httpOut);
         _writer.write("Now is the time");
-        assertArrayEquals("Now is the time".getBytes(StringUtil.__UTF8),_bytes.asArray());
+        assertArrayEquals("Now is the time".getBytes(StandardCharsets.UTF_8),BufferUtil.toArray(_bytes));
     }
 
     @Test
     public void testUTF8() throws Exception
     {
-        _writer.setCharacterEncoding(StringUtil.__UTF8);
+        HttpWriter _writer = new Utf8HttpWriter(_httpOut);
         _writer.write("How now \uFF22rown cow");
-        assertArrayEquals("How now \uFF22rown cow".getBytes(StringUtil.__UTF8),_bytes.asArray());
+        assertArrayEquals("How now \uFF22rown cow".getBytes(StandardCharsets.UTF_8),BufferUtil.toArray(_bytes));
     }
-    
+
+    @Test
+    public void testUTF16() throws Exception
+    {
+        HttpWriter _writer = new EncodingHttpWriter(_httpOut,StringUtil.__UTF16);
+        _writer.write("How now \uFF22rown cow");
+        assertArrayEquals("How now \uFF22rown cow".getBytes(StandardCharsets.UTF_16),BufferUtil.toArray(_bytes));
+    }
+
     @Test
     public void testNotCESU8() throws Exception
     {
-        _writer.setCharacterEncoding(StringUtil.__UTF8);
+        HttpWriter _writer = new Utf8HttpWriter(_httpOut);
         String data="xxx\uD801\uDC00xxx";
         _writer.write(data);
-        assertEquals("787878F0909080787878",TypeUtil.toHexString(_bytes.asArray()));
-        assertArrayEquals(data.getBytes(StringUtil.__UTF8),_bytes.asArray());
-        assertEquals(3+4+3,_bytes.length());
-        
+        assertEquals("787878F0909080787878",TypeUtil.toHexString(BufferUtil.toArray(_bytes)));
+        assertArrayEquals(data.getBytes(StandardCharsets.UTF_8),BufferUtil.toArray(_bytes));
+        assertEquals(3+4+3,_bytes.remaining());
+
         Utf8StringBuilder buf = new Utf8StringBuilder();
-        buf.append(_bytes.asArray(),0,_bytes.length());
+        buf.append(BufferUtil.toArray(_bytes),0,_bytes.remaining());
         assertEquals(data,buf.toString());
-        
     }
 
     @Test
     public void testMultiByteOverflowUTF8() throws Exception
     {
-        _writer.setCharacterEncoding(StringUtil.__UTF8);
+        HttpWriter _writer = new Utf8HttpWriter(_httpOut);
         final String singleByteStr = "a";
         final String multiByteDuplicateStr = "\uFF22";
         int remainSize = 1;
 
-        int multiByteStrByteLength = multiByteDuplicateStr.getBytes("UTF-8").length;
+        int multiByteStrByteLength = multiByteDuplicateStr.getBytes(StandardCharsets.UTF_8).length;
         StringBuilder sb = new StringBuilder();
         for (int i = 0; i < HttpWriter.MAX_OUTPUT_CHARS - multiByteStrByteLength; i++) {
           sb.append(singleByteStr);
@@ -164,86 +126,48 @@ public class HttpWriterTest
 
         _writer.write(buf, 0, length);
 
-        assertEquals(sb.toString(),new String(_bytes.asArray(),StringUtil.__UTF8));
+        assertEquals(sb.toString(),new String(BufferUtil.toArray(_bytes),StandardCharsets.UTF_8));
     }
 
     @Test
     public void testISO8859() throws Exception
     {
-        _writer.setCharacterEncoding(StringUtil.__ISO_8859_1);
+        HttpWriter _writer = new Iso88591HttpWriter(_httpOut);
         _writer.write("How now \uFF22rown cow");
-        assertEquals("How now ?rown cow",new String(_bytes.asArray(),StringUtil.__ISO_8859_1));
-    }
-
-    @Test
-    public void testOutput() throws Exception
-    {
-        Buffer sb=new ByteArrayBuffer(1500);
-        Buffer bb=new ByteArrayBuffer(8096);
-        HttpFields fields = new HttpFields();
-        ByteArrayEndPoint endp = new ByteArrayEndPoint(new byte[0],4096);
-
-        HttpGenerator hb = new HttpGenerator(new SimpleBuffers(sb,bb),endp);
-
-        hb.setResponse(200,"OK");
-
-        AbstractHttpConnection connection = new AbstractHttpConnection(null,endp,new Server(),null,hb,null)
-        {
-            @Override
-            public Connection handle() throws IOException
-            {
-                return null;
-            }
-        };
-        endp.setMaxIdleTime(10000);
-        hb.setSendServerVersion(false);
-        HttpOutput output = new HttpOutput(connection);
-        HttpWriter writer = new HttpWriter(output);
-        writer.setCharacterEncoding(StringUtil.__UTF8);
-
-        char[] chars = new char[1024];
-        for (int i=0;i<chars.length;i++)
-            chars[i]=(char)('0'+(i%10));
-        chars[0]='\u0553';
-        writer.write(chars);
-
-        hb.completeHeader(fields,true);
-        hb.flush(10000);
-        String response = new String(endp.getOut().asArray(),StringUtil.__UTF8);
-        assertTrue(response.startsWith("HTTP/1.1 200 OK\r\nContent-Length: 1025\r\n\r\n\u05531234567890"));
+        assertEquals("How now ?rown cow",new String(BufferUtil.toArray(_bytes),StandardCharsets.ISO_8859_1));
     }
 
     @Test
     public void testUTF16x2() throws Exception
     {
-        _writer.setCharacterEncoding(StringUtil.__UTF8);
+        HttpWriter _writer = new Utf8HttpWriter(_httpOut);
 
         String source = "\uD842\uDF9F";
 
-        byte[] bytes = source.getBytes(StringUtil.__UTF8);
+        byte[] bytes = source.getBytes(StandardCharsets.UTF_8);
         _writer.write(source.toCharArray(),0,source.toCharArray().length);
 
         java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
-        java.io.OutputStreamWriter osw = new java.io.OutputStreamWriter(baos ,StringUtil.__UTF8 );
+        java.io.OutputStreamWriter osw = new java.io.OutputStreamWriter(baos, StandardCharsets.UTF_8);
         osw.write(source.toCharArray(),0,source.toCharArray().length);
         osw.flush();
 
         myReportBytes(bytes);
         myReportBytes(baos.toByteArray());
-        myReportBytes(_bytes.asArray());
+        myReportBytes(BufferUtil.toArray(_bytes));
 
-        assertArrayEquals(bytes,_bytes.asArray());
-        assertArrayEquals(baos.toByteArray(),_bytes.asArray());
+        assertArrayEquals(bytes,BufferUtil.toArray(_bytes));
+        assertArrayEquals(baos.toByteArray(),BufferUtil.toArray(_bytes));
     }
 
     @Test
     public void testMultiByteOverflowUTF16x2() throws Exception
     {
-        _writer.setCharacterEncoding(StringUtil.__UTF8);
+        HttpWriter _writer = new Utf8HttpWriter(_httpOut);
 
         final String singleByteStr = "a";
         int remainSize = 1;
-        final String multiByteDuplicateStr = "\uD842\uDF9F"; 
+        final String multiByteDuplicateStr = "\uD842\uDF9F";
         int adjustSize = -1;
 
         StringBuilder sb = new StringBuilder();
@@ -258,31 +182,31 @@ public class HttpWriterTest
         }
         String source = sb.toString();
 
-        byte[] bytes = source.getBytes(StringUtil.__UTF8);
+        byte[] bytes = source.getBytes(StandardCharsets.UTF_8);
         _writer.write(source.toCharArray(),0,source.toCharArray().length);
 
         java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
-        java.io.OutputStreamWriter osw = new java.io.OutputStreamWriter(baos ,StringUtil.__UTF8);
+        java.io.OutputStreamWriter osw = new java.io.OutputStreamWriter(baos, StandardCharsets.UTF_8);
         osw.write(source.toCharArray(),0,source.toCharArray().length);
         osw.flush();
 
         myReportBytes(bytes);
         myReportBytes(baos.toByteArray());
-        myReportBytes(_bytes.asArray());
+        myReportBytes(BufferUtil.toArray(_bytes));
 
-        assertArrayEquals(bytes,_bytes.asArray());
-        assertArrayEquals(baos.toByteArray(),_bytes.asArray());
+        assertArrayEquals(bytes,BufferUtil.toArray(_bytes));
+        assertArrayEquals(baos.toByteArray(),BufferUtil.toArray(_bytes));
     }
-    
+
     @Test
     public void testMultiByteOverflowUTF16x2_2() throws Exception
     {
-        _writer.setCharacterEncoding(StringUtil.__UTF8);
+        HttpWriter _writer = new Utf8HttpWriter(_httpOut);
 
         final String singleByteStr = "a";
         int remainSize = 1;
-        final String multiByteDuplicateStr = "\uD842\uDF9F"; 
-        int adjustSize = -2;   
+        final String multiByteDuplicateStr = "\uD842\uDF9F";
+        int adjustSize = -2;
 
         StringBuilder sb = new StringBuilder();
         for (int i = 0; i < HttpWriter.MAX_OUTPUT_CHARS + adjustSize; i++)
@@ -296,32 +220,31 @@ public class HttpWriterTest
         }
         String source = sb.toString();
 
-        byte[] bytes = source.getBytes(StringUtil.__UTF8);
+        byte[] bytes = source.getBytes(StandardCharsets.UTF_8);
         _writer.write(source.toCharArray(),0,source.toCharArray().length);
 
         java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
-        java.io.OutputStreamWriter osw = new java.io.OutputStreamWriter(baos,StringUtil.__UTF8);
+        java.io.OutputStreamWriter osw = new java.io.OutputStreamWriter(baos,StandardCharsets.UTF_8);
         osw.write(source.toCharArray(),0,source.toCharArray().length);
         osw.flush();
 
         myReportBytes(bytes);
         myReportBytes(baos.toByteArray());
-        myReportBytes(_bytes.asArray());
+        myReportBytes(BufferUtil.toArray(_bytes));
 
-        assertArrayEquals(bytes,_bytes.asArray());
-        assertArrayEquals(baos.toByteArray(),_bytes.asArray());
+        assertArrayEquals(bytes,BufferUtil.toArray(_bytes));
+        assertArrayEquals(baos.toByteArray(),BufferUtil.toArray(_bytes));
     }
 
     private void myReportBytes(byte[] bytes) throws Exception
     {
-        for (int i = 0; i < bytes.length; i++)
-        {
-            // System.err.format("%s%x",(i == 0)?"[":(i % (HttpWriter.MAX_OUTPUT_CHARS) == 0)?"][":",",bytes[i]);
-        }
-        // System.err.format("]->%s\n",new String(bytes,StringUtil.__UTF8));
+//        for (int i = 0; i < bytes.length; i++)
+//        {
+//            System.err.format("%s%x",(i == 0)?"[":(i % (HttpWriter.MAX_OUTPUT_CHARS) == 0)?"][":",",bytes[i]);
+//        }
+//        System.err.format("]->%s\n",new String(bytes,StringUtil.__UTF8));
     }
 
-
     private void assertArrayEquals(byte[] b1, byte[] b2)
     {
         String test=new String(b1)+"=="+new String(b2);
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/LocalAsyncContextTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/LocalAsyncContextTest.java
index 18e4e3f..8c8317d 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/LocalAsyncContextTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/LocalAsyncContextTest.java
@@ -18,9 +18,12 @@
 
 package org.eclipse.jetty.server;
 
+import static org.junit.Assert.assertEquals;
+
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.concurrent.atomic.AtomicInteger;
+
 import javax.servlet.AsyncContext;
 import javax.servlet.AsyncEvent;
 import javax.servlet.AsyncListener;
@@ -31,13 +34,12 @@ import javax.servlet.http.HttpServletResponse;
 
 import org.eclipse.jetty.server.handler.HandlerWrapper;
 import org.eclipse.jetty.server.session.SessionHandler;
+import org.hamcrest.Matchers;
 import org.junit.After;
+import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
 public class LocalAsyncContextTest
 {
     protected Server _server = new Server();
@@ -55,11 +57,14 @@ public class LocalAsyncContextTest
 
         _server.setHandler(session);
         _server.start();
+
+        __completed.set(0);
+        __completed1.set(0);
     }
 
     protected Connector initConnector()
     {
-        return new LocalConnector();
+        return new LocalConnector(_server);
     }
 
     @After
@@ -70,11 +75,9 @@ public class LocalAsyncContextTest
     }
 
     @Test
-    public void testSuspendResume() throws Exception
+    public void testSuspendTimeout() throws Exception
     {
         String response;
-        __completed.set(0);
-        __completed1.set(0);
         _handler.setRead(0);
         _handler.setSuspendFor(1000);
         _handler.setResumeAfter(-1);
@@ -83,41 +86,88 @@ public class LocalAsyncContextTest
         check(response,"TIMEOUT");
         assertEquals(1,__completed.get());
         assertEquals(1,__completed1.get());
+    }
 
+    @Test
+    public void testSuspendResume0() throws Exception
+    {
+        String response;
+        _handler.setRead(0);
         _handler.setSuspendFor(10000);
-
         _handler.setResumeAfter(0);
         _handler.setCompleteAfter(-1);
         response=process(null);
-        check(response,"DISPATCHED");
+        check(response,"STARTASYNC","DISPATCHED");
+    }
 
+    @Test
+    public void testSuspendResume100() throws Exception
+    {
+        String response;
+        _handler.setRead(0);
+        _handler.setSuspendFor(10000);
         _handler.setResumeAfter(100);
         _handler.setCompleteAfter(-1);
         response=process(null);
-        check(response,"DISPATCHED");
+        check(response,"STARTASYNC","DISPATCHED");
+    }
 
+    @Test
+    public void testSuspendComplete0() throws Exception
+    {
+        String response;
+        _handler.setRead(0);
+        _handler.setSuspendFor(10000);
         _handler.setResumeAfter(-1);
         _handler.setCompleteAfter(0);
         response=process(null);
-        check(response,"COMPLETED");
+        check(response,"STARTASYNC","COMPLETED");
+    }
 
+    @Test
+    public void testSuspendComplete200() throws Exception
+    {
+        String response;
+        _handler.setRead(0);
+        _handler.setSuspendFor(10000);
         _handler.setResumeAfter(-1);
         _handler.setCompleteAfter(200);
         response=process(null);
-        check(response,"COMPLETED");
+        check(response,"STARTASYNC","COMPLETED");
 
-        _handler.setRead(-1);
+    }
 
+    @Test
+    public void testSuspendReadResume0() throws Exception
+    {
+        String response;
+        _handler.setSuspendFor(10000);
+        _handler.setRead(-1);
         _handler.setResumeAfter(0);
         _handler.setCompleteAfter(-1);
         response=process("wibble");
-        check(response,"DISPATCHED");
+        check(response,"STARTASYNC","DISPATCHED");
+    }
 
+    @Test
+    public void testSuspendReadResume100() throws Exception
+    {
+        String response;
+        _handler.setSuspendFor(10000);
+        _handler.setRead(-1);
         _handler.setResumeAfter(100);
         _handler.setCompleteAfter(-1);
         response=process("wibble");
         check(response,"DISPATCHED");
 
+    }
+
+    @Test
+    public void testSuspendOther() throws Exception
+    {
+        String response;
+        _handler.setSuspendFor(10000);
+        _handler.setRead(-1);
         _handler.setResumeAfter(-1);
         _handler.setCompleteAfter(0);
         response=process("wibble");
@@ -175,15 +225,14 @@ public class LocalAsyncContextTest
 
     protected void check(String response,String... content)
     {
-        assertEquals("HTTP/1.1 200 OK",response.substring(0,15));
+        Assert.assertThat(response,Matchers.startsWith("HTTP/1.1 200 OK"));
         int i=0;
         for (String m:content)
         {
+            Assert.assertThat(response,Matchers.containsString(m));
             i=response.indexOf(m,i);
-            assertTrue(i>=0);
             i+=m.length();
         }
-
     }
 
     private synchronized String process(String content) throws Exception
@@ -197,12 +246,16 @@ public class LocalAsyncContextTest
         else
             request+="Content-Length: "+content.length()+"\r\n" +"\r\n" + content;
 
-        return getResponse(request);
+        String response=getResponse(request);
+        return response;
     }
 
     protected String getResponse(String request) throws Exception
     {
-        return ((LocalConnector)_connector).getResponses(request);
+        LocalConnector connector=(LocalConnector)_connector;
+        LocalConnector.LocalEndPoint endp = connector.executeRequest(request);
+        endp.waitUntilClosed();
+        return endp.takeOutputString();
     }
 
     private static class SuspendHandler extends HandlerWrapper
@@ -321,6 +374,7 @@ public class LocalAsyncContextTest
         }
 
 
+        /* ------------------------------------------------------------ */
         @Override
         public void handle(String target, final Request baseRequest, final HttpServletRequest request, final HttpServletResponse response) throws IOException, ServletException
         {
@@ -341,7 +395,6 @@ public class LocalAsyncContextTest
                             b=in.read();
                     }
 
-
                     final AsyncContext asyncContext = baseRequest.startAsync();
                     response.getOutputStream().println("STARTASYNC");
                     asyncContext.addListener(__asyncListener);
@@ -406,9 +459,13 @@ public class LocalAsyncContextTest
                 else
                 {
                     if (request.getAttribute("TIMEOUT")!=null)
+                    {
                         response.getOutputStream().println("TIMEOUT");
+                    }
                     else
+                    {
                         response.getOutputStream().println("DISPATCHED");
+                    }
 
                     if (_suspendFor2>=0)
                     {
@@ -514,12 +571,10 @@ public class LocalAsyncContextTest
             event.getSuppliedRequest().setAttribute("TIMEOUT",Boolean.TRUE);
             event.getAsyncContext().dispatch();
         }
-
     };
 
     private static AsyncListener __asyncListener1 = new AsyncListener()
     {
-
         @Override
         public void onComplete(AsyncEvent event) throws IOException
         {
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/LocalConnectorTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/LocalConnectorTest.java
new file mode 100644
index 0000000..c01b16f
--- /dev/null
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/LocalConnectorTest.java
@@ -0,0 +1,173 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import static org.hamcrest.Matchers.containsString;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.io.Connection;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class LocalConnectorTest
+{
+    private Server _server;
+    private LocalConnector _connector;
+
+    @Before
+    public void prepare() throws Exception
+    {
+        _server = new Server();
+        _connector = new LocalConnector(_server);
+        _connector.setIdleTimeout(60000);
+        _server.addConnector(_connector);
+        _server.setHandler(new DumpHandler());
+        _server.start();
+    }
+
+    @After
+    public void dispose() throws Exception
+    {
+        _server.stop();
+        _server=null;
+        _connector=null;
+    }
+
+    @Test
+    public void testOpenClose() throws Exception
+    {
+        final CountDownLatch openLatch = new CountDownLatch(1);
+        final CountDownLatch closeLatch = new CountDownLatch(1);
+        _connector.addBean(new Connection.Listener.Adapter()
+        {
+            @Override
+            public void onOpened(Connection connection)
+            {
+                openLatch.countDown();
+            }
+
+            @Override
+            public void onClosed(Connection connection)
+            {
+                closeLatch.countDown();
+            }
+        });
+
+        _connector.getResponses("" +
+                "GET / HTTP/1.1\r\n" +
+                "Host: localhost\r\n" +
+                "Connection: close\r\n" +
+                "\r\n");
+
+        assertTrue(openLatch.await(5, TimeUnit.SECONDS));
+        assertTrue(closeLatch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testOneGET() throws Exception
+    {
+        String response=_connector.getResponses("GET /R1 HTTP/1.0\r\n\r\n");
+        assertThat(response,containsString("HTTP/1.1 200 OK"));
+        assertThat(response,containsString("pathInfo=/R1"));
+    }
+
+    @Test
+    public void testStopStart() throws Exception
+    {
+        String response=_connector.getResponses("GET /R1 HTTP/1.0\r\n\r\n");
+        assertThat(response,containsString("HTTP/1.1 200 OK"));
+        assertThat(response,containsString("pathInfo=/R1"));
+
+        _server.stop();
+        _server.start();
+
+        response=_connector.getResponses("GET /R2 HTTP/1.0\r\n\r\n");
+        assertThat(response,containsString("HTTP/1.1 200 OK"));
+        assertThat(response,containsString("pathInfo=/R2"));
+    }
+
+    @Test
+    public void testTwoGETs() throws Exception
+    {
+        String response=_connector.getResponses(
+            "GET /R1 HTTP/1.1\r\n"+
+            "Host: localhost\r\n"+
+            "\r\n"+
+            "GET /R2 HTTP/1.0\r\n\r\n");
+
+        assertThat(response,containsString("HTTP/1.1 200 OK"));
+        assertThat(response,containsString("pathInfo=/R1"));
+
+        response=response.substring(response.indexOf("</html>")+8);
+
+        assertThat(response,containsString("HTTP/1.1 200 OK"));
+        assertThat(response,containsString("pathInfo=/R2"));
+    }
+    
+    @Test
+    public void testManyGETs() throws Exception
+    {
+        String response=_connector.getResponses(
+            "GET /R1 HTTP/1.1\r\n"+
+            "Host: localhost\r\n"+
+            "\r\n"+
+            "GET /R2 HTTP/1.1\r\n"+
+            "Host: localhost\r\n"+
+            "\r\n"+
+            "GET /R3 HTTP/1.1\r\n"+
+            "Host: localhost\r\n"+
+            "\r\n"+
+            "GET /R4 HTTP/1.1\r\n"+
+            "Host: localhost\r\n"+
+            "\r\n"+
+            "GET /R5 HTTP/1.1\r\n"+
+            "Host: localhost\r\n"+
+            "\r\n"+
+            "GET /R6 HTTP/1.1\r\n"+
+            "Host: localhost\r\n"+
+            "Connection: close\r\n"+
+            "\r\n");
+        
+        String r=response;
+        
+        for (int i=1;i<=6;i++)
+        {
+            assertThat(r,containsString("HTTP/1.1 200 OK"));
+            assertThat(r,containsString("pathInfo=/R"+i));
+            r=r.substring(r.indexOf("</html>")+8);
+        }
+    }
+
+    @Test
+    public void testGETandGET() throws Exception
+    {
+        String response=_connector.getResponses("GET /R1 HTTP/1.0\r\n\r\n");
+        assertThat(response,containsString("HTTP/1.1 200 OK"));
+        assertThat(response,containsString("pathInfo=/R1"));
+
+        response=_connector.getResponses("GET /R2 HTTP/1.0\r\n\r\n");
+        assertThat(response,containsString("HTTP/1.1 200 OK"));
+        assertThat(response,containsString("pathInfo=/R2"));
+    }
+}
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/LowResourcesMonitorTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/LowResourcesMonitorTest.java
new file mode 100644
index 0000000..5700094
--- /dev/null
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/LowResourcesMonitorTest.java
@@ -0,0 +1,197 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.not;
+import static org.junit.Assert.assertThat;
+
+import java.net.Socket;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.concurrent.CountDownLatch;
+
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.eclipse.jetty.util.thread.TimerScheduler;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class LowResourcesMonitorTest
+{
+    QueuedThreadPool _threadPool;
+    Server _server;
+    ServerConnector _connector;
+    LowResourceMonitor _lowResourcesMonitor;
+    
+    @Before
+    public void before() throws Exception
+    {
+        _threadPool = new QueuedThreadPool();
+        _threadPool.setMaxThreads(50);
+
+        _server = new Server(_threadPool);
+        _server.manage(_threadPool);
+
+        _server.addBean(new TimerScheduler());
+        
+        _connector = new ServerConnector(_server);
+        _connector.setPort(0);
+        _connector.setIdleTimeout(35000);
+        _server.addConnector(_connector);
+
+        _server.setHandler(new DumpHandler());
+
+        _lowResourcesMonitor=new LowResourceMonitor(_server);
+        _lowResourcesMonitor.setLowResourcesIdleTimeout(200);
+        _lowResourcesMonitor.setMaxConnections(20);
+        _lowResourcesMonitor.setPeriod(900);
+        _server.addBean(_lowResourcesMonitor);
+
+        _server.start();
+    }
+    
+    @After
+    public void after() throws Exception
+    {
+        _server.stop();
+    }
+    
+    
+    @Test
+    public void testLowOnThreads() throws Exception
+    {
+        Thread.sleep(1200);
+        _threadPool.setMaxThreads(_threadPool.getThreads()-_threadPool.getIdleThreads()+10);
+        Thread.sleep(1200);
+        Assert.assertFalse(_lowResourcesMonitor.isLowOnResources());
+        
+        final CountDownLatch latch = new CountDownLatch(1);
+        
+        for (int i=0;i<100;i++)
+        {
+            _threadPool.execute(new Runnable()
+            {
+                @Override
+                public void run()
+                {
+                    try
+                    {
+                        latch.await();
+                    }
+                    catch (InterruptedException e)
+                    {
+                        e.printStackTrace();
+                    }
+                }
+            });
+        }
+        
+        Thread.sleep(1200);
+        Assert.assertTrue(_lowResourcesMonitor.isLowOnResources());
+        
+        latch.countDown();
+        Thread.sleep(1200);
+        Assert.assertFalse(_lowResourcesMonitor.isLowOnResources());      
+    }
+    
+
+    @Ignore ("not reliable")
+    @Test
+    public void testLowOnMemory() throws Exception
+    {
+        _lowResourcesMonitor.setMaxMemory(Runtime.getRuntime().totalMemory()-Runtime.getRuntime().freeMemory()+(100*1024*1024));
+        Thread.sleep(1200);
+        Assert.assertFalse(_lowResourcesMonitor.isLowOnResources());
+
+        byte[] data = new byte[100*1024*1024];
+        Arrays.fill(data,(byte)1);
+        int hash = Arrays.hashCode(data);
+        assertThat(hash,not(equalTo(0)));
+
+        Thread.sleep(1200);
+        Assert.assertTrue(_lowResourcesMonitor.isLowOnResources());
+        data=null;
+        System.gc();
+        System.gc();
+
+        Thread.sleep(1200);
+        Assert.assertFalse(_lowResourcesMonitor.isLowOnResources());   
+    }
+    
+
+    @Test
+    public void testMaxConnectionsAndMaxIdleTime() throws Exception
+    {
+        _lowResourcesMonitor.setMaxMemory(0);
+        Assert.assertFalse(_lowResourcesMonitor.isLowOnResources());
+
+        Socket[] socket = new Socket[_lowResourcesMonitor.getMaxConnections()+1];
+        for (int i=0;i<socket.length;i++)
+            socket[i]=new Socket("localhost",_connector.getLocalPort());
+        
+        Thread.sleep(1200);
+        Assert.assertTrue(_lowResourcesMonitor.isLowOnResources());
+
+        Socket newSocket = new Socket("localhost",_connector.getLocalPort());
+        
+        // wait for low idle time to close sockets, but not new Socket
+        Thread.sleep(1200);
+        Assert.assertFalse(_lowResourcesMonitor.isLowOnResources());   
+
+        for (int i=0;i<socket.length;i++)
+            Assert.assertEquals(-1,socket[i].getInputStream().read());
+        
+        newSocket.getOutputStream().write("GET / HTTP/1.0\r\n\r\n".getBytes(StandardCharsets.UTF_8));
+        Assert.assertEquals('H',newSocket.getInputStream().read());
+        
+    }
+    
+    @Test
+    public void testMaxLowResourceTime() throws Exception
+    {
+        _lowResourcesMonitor.setMaxLowResourcesTime(2000);
+        Assert.assertFalse(_lowResourcesMonitor.isLowOnResources());
+
+        Socket socket0 = new Socket("localhost",_connector.getLocalPort());
+        _lowResourcesMonitor.setMaxMemory(1);
+
+        Thread.sleep(1200);
+        Assert.assertTrue(_lowResourcesMonitor.isLowOnResources());
+
+        Socket socket1 = new Socket("localhost",_connector.getLocalPort());
+
+        Thread.sleep(1200);
+        Assert.assertTrue(_lowResourcesMonitor.isLowOnResources());
+        Assert.assertEquals(-1,socket0.getInputStream().read());
+        socket1.getOutputStream().write("G".getBytes(StandardCharsets.UTF_8));
+
+        Thread.sleep(1200);
+        Assert.assertTrue(_lowResourcesMonitor.isLowOnResources());
+        socket1.getOutputStream().write("E".getBytes(StandardCharsets.UTF_8));
+
+        Thread.sleep(1200);
+        Assert.assertTrue(_lowResourcesMonitor.isLowOnResources());
+        Assert.assertEquals(-1,socket1.getInputStream().read());
+
+    }
+}
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/NetworkTrafficListenerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/NetworkTrafficListenerTest.java
index a034357..4684061 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/NetworkTrafficListenerTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/NetworkTrafficListenerTest.java
@@ -26,6 +26,8 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.net.Socket;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
@@ -35,10 +37,9 @@ import javax.servlet.ServletOutputStream;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import org.eclipse.jetty.io.Buffer;
 import org.eclipse.jetty.io.NetworkTrafficListener;
 import org.eclipse.jetty.server.handler.AbstractHandler;
-import org.eclipse.jetty.server.nio.NetworkTrafficSelectChannelConnector;
+import org.eclipse.jetty.util.BufferUtil;
 import org.junit.After;
 import org.junit.Test;
 
@@ -47,15 +48,15 @@ public class NetworkTrafficListenerTest
     private static final byte END_OF_CONTENT = '~';
 
     private Server server;
-    private NetworkTrafficSelectChannelConnector connector;
+    private NetworkTrafficServerConnector connector;
 
     public void initConnector(Handler handler) throws Exception
     {
         server = new Server();
-        server.setSendDateHeader(false);
-        server.setSendServerVersion(false);
 
-        connector = new NetworkTrafficSelectChannelConnector();
+        connector = new NetworkTrafficServerConnector(server);
+        connector.getConnectionFactory(HttpConfiguration.ConnectionFactory.class).getHttpConfiguration().setSendDateHeader(false);
+        connector.getConnectionFactory(HttpConfiguration.ConnectionFactory.class).getHttpConfiguration().setSendServerVersion(false);
         server.addConnector(connector);
         server.setHandler(handler);
         server.start();
@@ -78,7 +79,7 @@ public class NetworkTrafficListenerTest
 
         final CountDownLatch openedLatch = new CountDownLatch(1);
         final CountDownLatch closedLatch = new CountDownLatch(1);
-        connector.addNetworkTrafficListener(new NetworkTrafficListener.Empty()
+        connector.addNetworkTrafficListener(new NetworkTrafficListener.Adapter()
         {
             public volatile Socket socket;
 
@@ -101,7 +102,7 @@ public class NetworkTrafficListenerTest
         // Connect to the server
         Socket socket = new Socket("localhost", port);
         assertTrue(openedLatch.await(10, TimeUnit.SECONDS));
-        
+
         socket.close();
         assertTrue(closedLatch.await(10, TimeUnit.SECONDS));
     }
@@ -111,29 +112,30 @@ public class NetworkTrafficListenerTest
     {
         initConnector(new AbstractHandler()
         {
+            @Override
             public void handle(String uri, Request request, HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws IOException, ServletException
             {
                 request.setHandled(true);
             }
         });
 
-        final AtomicReference<String> incomingData = new AtomicReference<String>();
+        final AtomicReference<String> incomingData = new AtomicReference<>();
         final CountDownLatch incomingLatch = new CountDownLatch(1);
-        final AtomicReference<String> outgoingData = new AtomicReference<String>("");
+        final AtomicReference<String> outgoingData = new AtomicReference<>("");
         final CountDownLatch outgoingLatch = new CountDownLatch(1);
-        connector.addNetworkTrafficListener(new NetworkTrafficListener.Empty()
+        connector.addNetworkTrafficListener(new NetworkTrafficListener.Adapter()
         {
             @Override
-            public void incoming(Socket socket, Buffer bytes)
+            public void incoming(Socket socket, ByteBuffer bytes)
             {
-                incomingData.set(bytes.toString("UTF-8"));
+                incomingData.set(BufferUtil.toString(bytes,StandardCharsets.UTF_8));
                 incomingLatch.countDown();
             }
 
             @Override
-            public void outgoing(Socket socket, Buffer bytes)
+            public void outgoing(Socket socket, ByteBuffer bytes)
             {
-                outgoingData.set(outgoingData.get() + bytes.toString("UTF-8"));
+                outgoingData.set(outgoingData.get() + BufferUtil.toString(bytes,StandardCharsets.UTF_8));
                 outgoingLatch.countDown();
             }
         });
@@ -151,7 +153,7 @@ public class NetworkTrafficListenerTest
 
         Socket socket = new Socket("localhost", port);
         OutputStream output = socket.getOutputStream();
-        output.write(request.getBytes("UTF-8"));
+        output.write(request.getBytes(StandardCharsets.UTF_8));
         output.flush();
 
         assertTrue(incomingLatch.await(1, TimeUnit.SECONDS));
@@ -161,7 +163,7 @@ public class NetworkTrafficListenerTest
         assertEquals(expectedResponse, outgoingData.get());
 
         byte[] responseBytes = readResponse(socket);
-        String response = new String(responseBytes, "UTF-8");
+        String response = new String(responseBytes, StandardCharsets.UTF_8);
         assertEquals(expectedResponse, response);
 
         socket.close();
@@ -173,30 +175,33 @@ public class NetworkTrafficListenerTest
         final String responseContent = "response_content";
         initConnector(new AbstractHandler()
         {
+            @Override
             public void handle(String uri, Request request, HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws IOException, ServletException
             {
                 request.setHandled(true);
                 ServletOutputStream output = servletResponse.getOutputStream();
-                output.write(responseContent.getBytes("UTF-8"));
+                output.write(responseContent.getBytes(StandardCharsets.UTF_8));
                 output.write(END_OF_CONTENT);
             }
         });
 
-        final AtomicReference<String> incomingData = new AtomicReference<String>();
+        final AtomicReference<String> incomingData = new AtomicReference<>();
         final CountDownLatch incomingLatch = new CountDownLatch(1);
-        final AtomicReference<String> outgoingData = new AtomicReference<String>("");
+        final AtomicReference<String> outgoingData = new AtomicReference<>("");
         final CountDownLatch outgoingLatch = new CountDownLatch(2);
-        connector.addNetworkTrafficListener(new NetworkTrafficListener.Empty()
+        connector.addNetworkTrafficListener(new NetworkTrafficListener.Adapter()
         {
-            public void incoming(Socket socket, Buffer bytes)
+            @Override
+            public void incoming(Socket socket, ByteBuffer bytes)
             {
-                incomingData.set(bytes.toString("UTF-8"));
+                incomingData.set(BufferUtil.toString(bytes,StandardCharsets.UTF_8));
                 incomingLatch.countDown();
             }
 
-            public void outgoing(Socket socket, Buffer bytes)
+            @Override
+            public void outgoing(Socket socket, ByteBuffer bytes)
             {
-                outgoingData.set(outgoingData.get() + bytes.toString("UTF-8"));
+                outgoingData.set(outgoingData.get() + BufferUtil.toString(bytes,StandardCharsets.UTF_8));
                 outgoingLatch.countDown();
             }
         });
@@ -214,7 +219,7 @@ public class NetworkTrafficListenerTest
 
         Socket socket = new Socket("localhost", port);
         OutputStream output = socket.getOutputStream();
-        output.write(request.getBytes("UTF-8"));
+        output.write(request.getBytes(StandardCharsets.UTF_8));
         output.flush();
 
         assertTrue(incomingLatch.await(1, TimeUnit.SECONDS));
@@ -224,7 +229,7 @@ public class NetworkTrafficListenerTest
         assertEquals(expectedResponse, outgoingData.get());
 
         byte[] responseBytes = readResponse(socket);
-        String response = new String(responseBytes, "UTF-8");
+        String response = new String(responseBytes, StandardCharsets.UTF_8);
         assertEquals(expectedResponse, response);
 
         socket.close();
@@ -238,33 +243,37 @@ public class NetworkTrafficListenerTest
         final String responseChunk2 = "response_content".substring(responseContent.length() / 2, responseContent.length());
         initConnector(new AbstractHandler()
         {
+            @Override
             public void handle(String uri, Request request, HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws IOException, ServletException
             {
                 request.setHandled(true);
                 ServletOutputStream output = servletResponse.getOutputStream();
-                output.write(responseChunk1.getBytes("UTF-8"));
+                output.write(responseChunk1.getBytes(StandardCharsets.UTF_8));
                 output.flush();
-                output.write(responseChunk2.getBytes("UTF-8"));
+                output.write(responseChunk2.getBytes(StandardCharsets.UTF_8));
                 output.flush();
             }
         });
 
-        final AtomicReference<String> incomingData = new AtomicReference<String>();
+        final AtomicReference<String> incomingData = new AtomicReference<>();
         final CountDownLatch incomingLatch = new CountDownLatch(1);
-        final AtomicReference<String> outgoingData = new AtomicReference<String>("");
-        final CountDownLatch outgoingLatch = new CountDownLatch(4);
-        connector.addNetworkTrafficListener(new NetworkTrafficListener.Empty()
+        final AtomicReference<String> outgoingData = new AtomicReference<>("");
+        final CountDownLatch outgoingLatch = new CountDownLatch(1);
+        connector.addNetworkTrafficListener(new NetworkTrafficListener.Adapter()
         {
-            public void incoming(Socket socket, Buffer bytes)
+            @Override
+            public void incoming(Socket socket, ByteBuffer bytes)
             {
-                incomingData.set(bytes.toString("UTF-8"));
+                incomingData.set(BufferUtil.toString(bytes,StandardCharsets.UTF_8));
                 incomingLatch.countDown();
             }
 
-            public void outgoing(Socket socket, Buffer bytes)
+            @Override
+            public void outgoing(Socket socket, ByteBuffer bytes)
             {
-                outgoingData.set(outgoingData.get() + bytes.toString("UTF-8"));
-                outgoingLatch.countDown();
+                outgoingData.set(outgoingData.get() + BufferUtil.toString(bytes,StandardCharsets.UTF_8));                
+                if (outgoingData.get().endsWith("\r\n0\r\n\r\n"))
+                    outgoingLatch.countDown();
             }
         });
         int port = connector.getLocalPort();
@@ -286,7 +295,7 @@ public class NetworkTrafficListenerTest
 
         Socket socket = new Socket("localhost", port);
         OutputStream output = socket.getOutputStream();
-        output.write(request.getBytes("UTF-8"));
+        output.write(request.getBytes(StandardCharsets.UTF_8));
         output.flush();
 
         assertTrue(incomingLatch.await(1, TimeUnit.SECONDS));
@@ -296,7 +305,7 @@ public class NetworkTrafficListenerTest
         assertEquals(expectedResponse, outgoingData.get());
 
         byte[] responseBytes = readResponse(socket);
-        String response = new String(responseBytes, "UTF-8");
+        String response = new String(responseBytes, StandardCharsets.UTF_8);
         assertEquals(expectedResponse, response);
 
         socket.close();
@@ -308,6 +317,7 @@ public class NetworkTrafficListenerTest
         final String location = "/redirect";
         initConnector(new AbstractHandler()
         {
+            @Override
             public void handle(String uri, Request request, HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws IOException, ServletException
             {
                 request.setHandled(true);
@@ -315,21 +325,23 @@ public class NetworkTrafficListenerTest
             }
         });
 
-        final AtomicReference<String> incomingData = new AtomicReference<String>();
+        final AtomicReference<String> incomingData = new AtomicReference<>();
         final CountDownLatch incomingLatch = new CountDownLatch(1);
-        final AtomicReference<String> outgoingData = new AtomicReference<String>("");
+        final AtomicReference<String> outgoingData = new AtomicReference<>("");
         final CountDownLatch outgoingLatch = new CountDownLatch(1);
-        connector.addNetworkTrafficListener(new NetworkTrafficListener.Empty()
+        connector.addNetworkTrafficListener(new NetworkTrafficListener.Adapter()
         {
-            public void incoming(Socket socket, Buffer bytes)
+            @Override
+            public void incoming(Socket socket, ByteBuffer bytes)
             {
-                incomingData.set(bytes.toString("UTF-8"));
+                incomingData.set(BufferUtil.toString(bytes,StandardCharsets.UTF_8));
                 incomingLatch.countDown();
             }
 
-            public void outgoing(Socket socket, Buffer bytes)
+            @Override
+            public void outgoing(Socket socket, ByteBuffer bytes)
             {
-                outgoingData.set(outgoingData.get() + bytes.toString("UTF-8"));
+                outgoingData.set(outgoingData.get() + BufferUtil.toString(bytes,StandardCharsets.UTF_8));
                 outgoingLatch.countDown();
             }
         });
@@ -351,7 +363,7 @@ public class NetworkTrafficListenerTest
 
         Socket socket = new Socket("localhost", port);
         OutputStream output = socket.getOutputStream();
-        output.write(request.getBytes("UTF-8"));
+        output.write(request.getBytes(StandardCharsets.UTF_8));
         output.flush();
 
         assertTrue(incomingLatch.await(1, TimeUnit.SECONDS));
@@ -361,7 +373,7 @@ public class NetworkTrafficListenerTest
         assertEquals(expectedResponse, outgoingData.get());
 
         byte[] responseBytes = readResponse(socket);
-        String response = new String(responseBytes, "UTF-8");
+        String response = new String(responseBytes, StandardCharsets.UTF_8);
         assertEquals(expectedResponse, response);
 
         socket.close();
@@ -372,6 +384,7 @@ public class NetworkTrafficListenerTest
     {
         initConnector(new AbstractHandler()
         {
+            @Override
             public void handle(String uri, Request request, HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws IOException, ServletException
             {
                 // Read and discard the request body to make the test more
@@ -389,19 +402,21 @@ public class NetworkTrafficListenerTest
             }
         });
 
-        final AtomicReference<String> incomingData = new AtomicReference<String>("");
-        final AtomicReference<String> outgoingData = new AtomicReference<String>("");
+        final AtomicReference<String> incomingData = new AtomicReference<>("");
+        final AtomicReference<String> outgoingData = new AtomicReference<>("");
         final CountDownLatch outgoingLatch = new CountDownLatch(1);
-        connector.addNetworkTrafficListener(new NetworkTrafficListener.Empty()
+        connector.addNetworkTrafficListener(new NetworkTrafficListener.Adapter()
         {
-            public void incoming(Socket socket, Buffer bytes)
+            @Override
+            public void incoming(Socket socket, ByteBuffer bytes)
             {
-                incomingData.set(incomingData.get() + bytes.toString("UTF-8"));
+                incomingData.set(incomingData.get() + BufferUtil.toString(bytes,StandardCharsets.UTF_8));
             }
 
-            public void outgoing(Socket socket, Buffer bytes)
+            @Override
+            public void outgoing(Socket socket, ByteBuffer bytes)
             {
-                outgoingData.set(outgoingData.get() + bytes.toString("UTF-8"));
+                outgoingData.set(outgoingData.get() + BufferUtil.toString(bytes,StandardCharsets.UTF_8));
                 outgoingLatch.countDown();
             }
         });
@@ -425,14 +440,14 @@ public class NetworkTrafficListenerTest
 
         Socket socket = new Socket("localhost", port);
         OutputStream output = socket.getOutputStream();
-        output.write(request.getBytes("UTF-8"));
+        output.write(request.getBytes(StandardCharsets.UTF_8));
         output.flush();
 
         assertTrue(outgoingLatch.await(1, TimeUnit.SECONDS));
         assertEquals(expectedResponse, outgoingData.get());
 
         byte[] responseBytes = readResponse(socket);
-        String response = new String(responseBytes, "UTF-8");
+        String response = new String(responseBytes, StandardCharsets.UTF_8);
         assertEquals(expectedResponse, response);
 
         assertEquals(request, incomingData.get());
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/PartialRFC2616Test.java b/jetty-server/src/test/java/org/eclipse/jetty/server/PartialRFC2616Test.java
new file mode 100644
index 0000000..439ac20
--- /dev/null
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/PartialRFC2616Test.java
@@ -0,0 +1,627 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/*
+ * Created on 9/01/2004
+ *
+ * To change the template for this generated file go to
+ * Window>Preferences>Java>Code Generation>Code and Comments
+ */
+package org.eclipse.jetty.server;
+
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.not;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.HandlerCollection;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ *
+ */
+public class PartialRFC2616Test
+{
+    private Server server;
+    private LocalConnector connector;
+
+    @Before
+    public void init() throws Exception
+    {
+        server = new Server();
+        connector = new LocalConnector(server);
+        connector.setIdleTimeout(10000);
+        server.addConnector(connector);
+
+        ContextHandler vcontext=new ContextHandler();
+        vcontext.setContextPath("/");
+        vcontext.setVirtualHosts(new String[]
+        { "VirtualHost" });
+        vcontext.setHandler(new DumpHandler("Virtual Dump"));
+
+        ContextHandler context=new ContextHandler();
+        context.setContextPath("/");
+        context.setHandler(new DumpHandler());
+
+        HandlerCollection collection=new HandlerCollection();
+        collection.setHandlers(new Handler[]
+        { vcontext, context });
+
+        server.setHandler(collection);
+
+        server.start();
+    }
+
+    @After
+    public void destroy() throws Exception
+    {
+        server.stop();
+        server.join();
+    }
+
+    @Test
+    public void test3_3()
+    {
+        try
+        {
+            HttpFields fields=new HttpFields();
+
+            fields.put("D1","Sun, 6 Nov 1994 08:49:37 GMT");
+            fields.put("D2","Sunday, 6-Nov-94 08:49:37 GMT");
+            fields.put("D3","Sun Nov  6 08:49:37 1994");
+            Date d1=new Date(fields.getDateField("D1"));
+            Date d2=new Date(fields.getDateField("D2"));
+            Date d3=new Date(fields.getDateField("D3"));
+
+            assertEquals("3.3.1 RFC 822 RFC 850",d2,d1);
+            assertEquals("3.3.1 RFC 850 ANSI C",d3,d2);
+
+            fields.putDateField("Date",d1.getTime());
+            assertEquals("3.3.1 RFC 822 preferred","Sun, 06 Nov 1994 08:49:37 GMT",fields.getStringField("Date"));
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+            assertTrue(false);
+        }
+    }
+
+    @Test
+    public void test3_6_a() throws Exception
+    {
+        int offset=0;
+        // Chunk last
+        String response = connector.getResponses(
+                "GET /R1 HTTP/1.1\n" +
+                        "Host: localhost\n" +
+                        "Transfer-Encoding: chunked,identity\n" +
+                        "Content-Type: text/plain\n" +
+                        "\015\012" +
+                        "5;\015\012" +
+                        "123\015\012\015\012" +
+                        "0;\015\012\015\012");
+        checkContains(response,offset,"HTTP/1.1 400 Bad","Chunked last");
+    }
+
+    @Test
+    public void test3_6_b() throws Exception
+    {
+        int offset=0;
+        // Chunked
+        String response = connector.getResponses(
+                "GET /R1 HTTP/1.1\n" +
+                        "Host: localhost\n" +
+                        "Transfer-Encoding: chunked\n" +
+                        "Content-Type: text/plain\n" +
+                        "\n" +
+                        "2;\n" +
+                        "12\n" +
+                        "3;\n" +
+                        "345\n" +
+                        "0;\n\n" +
+
+                        "GET /R2 HTTP/1.1\n" +
+                        "Host: localhost\n" +
+                        "Transfer-Encoding: chunked\n" +
+                        "Content-Type: text/plain\n" +
+                        "\n" +
+                        "4;\n" +
+                        "6789\n" +
+                        "5;\n" +
+                        "abcde\n" +
+                        "0;\n\n" +
+
+                        "GET /R3 HTTP/1.1\n" +
+                        "Host: localhost\n" +
+                        "Connection: close\n" +
+                        "\n");
+        offset=checkContains(response,offset,"HTTP/1.1 200","3.6.1 Chunking");
+        offset=checkContains(response,offset,"12345","3.6.1 Chunking");
+        offset=checkContains(response,offset,"HTTP/1.1 200","3.6.1 Chunking");
+        offset=checkContains(response,offset,"6789abcde","3.6.1 Chunking");
+        offset=checkContains(response,offset,"/R3","3.6.1 Chunking");
+    }
+
+    @Test
+    public void test3_6_c() throws Exception
+    {
+        int offset=0;
+        String response = connector.getResponses(
+                "POST /R1 HTTP/1.1\n" +
+                        "Host: localhost\n" +
+                        "Transfer-Encoding: chunked\n" +
+                        "Content-Type: text/plain\n" +
+                        "\n" +
+                        "3;\n" +
+                        "fgh\n" +
+                        "3;\n" +
+                        "Ijk\n" +
+                        "0;\n\n" +
+
+                        "POST /R2 HTTP/1.1\n" +
+                        "Host: localhost\n" +
+                        "Transfer-Encoding: chunked\n" +
+                        "Content-Type: text/plain\n" +
+                        "\n" +
+                        "4;\n" +
+                        "lmno\n" +
+                        "5;\n" +
+                        "Pqrst\n" +
+                        "0;\n\n" +
+
+                        "GET /R3 HTTP/1.1\n" +
+                        "Host: localhost\n" +
+                        "Connection: close\n" +
+                        "\n");
+        checkNotContained(response,"HTTP/1.1 100","3.6.1 Chunking");
+        offset=checkContains(response,offset,"HTTP/1.1 200","3.6.1 Chunking");
+        offset=checkContains(response,offset,"fghIjk","3.6.1 Chunking");
+        offset=checkContains(response,offset,"HTTP/1.1 200","3.6.1 Chunking");
+        offset=checkContains(response,offset,"lmnoPqrst","3.6.1 Chunking");
+        offset=checkContains(response,offset,"/R3","3.6.1 Chunking");
+    }
+
+    @Test
+    public void test3_6_d() throws Exception
+    {
+        int offset=0;
+        // Chunked and keep alive
+        String response = connector.getResponses(
+                "GET /R1 HTTP/1.1\n" +
+                        "Host: localhost\n" +
+                        "Transfer-Encoding: chunked\n" +
+                        "Content-Type: text/plain\n" +
+                        "Connection: keep-alive\n" +
+                        "\n" +
+                        "3;\n" +
+                        "123\n" +
+                        "3;\n" +
+                        "456\n" +
+                        "0;\n\n" +
+
+                        "GET /R2 HTTP/1.1\n" +
+                        "Host: localhost\n" +
+                        "Connection: close\n" +
+                        "\n");
+        offset=checkContains(response,offset,"HTTP/1.1 200","3.6.1 Chunking")+10;
+        offset=checkContains(response,offset,"123456","3.6.1 Chunking");
+        offset=checkContains(response,offset,"/R2","3.6.1 Chunking")+10;
+    }
+
+    @Test
+    public void test3_9() throws Exception
+    {
+        HttpFields fields=new HttpFields();
+
+        fields.put("Q","bbb;q=0.5,aaa,ccc;q=0.002,d;q=0,e;q=0.0001,ddd;q=0.001,aa2,abb;q=0.7");
+        Enumeration<String> qualities=fields.getValues("Q",", \t");
+        List<String> list=HttpFields.qualityList(qualities);
+        assertEquals("Quality parameters","aaa",HttpFields.valueParameters(list.get(0),null));
+        assertEquals("Quality parameters","aa2",HttpFields.valueParameters(list.get(1),null));
+        assertEquals("Quality parameters","abb",HttpFields.valueParameters(list.get(2),null));
+        assertEquals("Quality parameters","bbb",HttpFields.valueParameters(list.get(3),null));
+        assertEquals("Quality parameters","ccc",HttpFields.valueParameters(list.get(4),null));
+        assertEquals("Quality parameters","ddd",HttpFields.valueParameters(list.get(5),null));
+    }
+
+    @Test
+    public void test4_4_2() throws Exception
+    {
+        int offset=0;
+        // If _content length not used, second request will not be read.
+        String response = connector.getResponses(
+                "GET /R1 HTTP/1.1\n" +
+                        "Host: localhost\n" +
+                        "Transfer-Encoding: identity\n" +
+                        "Content-Type: text/plain\n" +
+                        "Content-Length: 5\n" +
+                        "\n" +
+                        "123\015\012" +
+
+                        "GET /R2 HTTP/1.1\n" +
+                        "Host: localhost\n" +
+                        "Transfer-Encoding: other\n" +
+                        "Connection: close\n" +
+                        "\n");
+        offset=checkContains(response,offset,"HTTP/1.1 200 OK","2. identity")+10;
+        offset=checkContains(response,offset,"/R1","2. identity")+3;
+        offset=checkContains(response,offset,"HTTP/1.1 200 OK","2. identity")+10;
+        offset=checkContains(response,offset,"/R2","2. identity")+3;
+    }
+
+    @Test
+    public void test4_4_3() throws Exception
+    {
+        // _content length is ignored, as chunking is used. If it is
+        // not ignored, the second request wont be seen.
+        int offset=0;
+        String response = connector.getResponses(
+                "GET /R1 HTTP/1.1\n" +
+                        "Host: localhost\n" +
+                        "Transfer-Encoding: chunked\n" +
+                        "Content-Type: text/plain\n" +
+                        "Content-Length: 100\n" +
+                        "\n" +
+                        "3;\n" +
+                        "123\n" +
+                        "3;\n" +
+                        "456\n" +
+                        "0;\n" +
+                        "\n" +
+
+                        "GET /R2 HTTP/1.1\n" +
+                        "Host: localhost\n" +
+                        "Connection: close\n" +
+                        "Content-Type: text/plain\n" +
+                        "Content-Length: 6\n" +
+                        "\n" +
+                        "abcdef");
+        offset=checkContains(response,offset,"HTTP/1.1 200 OK","3. ignore c-l")+1;
+        offset=checkContains(response,offset,"/R1","3. ignore c-l")+1;
+        offset=checkContains(response,offset,"123456","3. ignore c-l")+1;
+        offset=checkContains(response,offset,"HTTP/1.1 200 OK","3. ignore c-l")+1;
+        offset=checkContains(response,offset,"/R2","3. _content-length")+1;
+        offset=checkContains(response,offset,"abcdef","3. _content-length")+1;
+    }
+
+    @Test
+    public void test4_4_4() throws Exception
+    {
+        // No _content length
+        assertTrue("Skip 411 checks as IE breaks this rule",true);
+        // offset=0; connector.reopen();
+        // response=connector.getResponses("GET /R2 HTTP/1.1\n"+
+        // "Host: localhost\n"+
+        // "Content-Type: text/plain\n"+
+        // "Connection: close\n"+
+        // "\n"+
+        // "123456");
+        // offset=checkContains(response,offset,
+        // "HTTP/1.1 411 ","411 length required")+10;
+        // offset=0; connector.reopen();
+        // response=connector.getResponses("GET /R2 HTTP/1.0\n"+
+        // "Content-Type: text/plain\n"+
+        // "\n"+
+        // "123456");
+        // offset=checkContains(response,offset,
+        // "HTTP/1.0 411 ","411 length required")+10;
+
+    }
+
+    @Test
+    public void test5_2_1() throws Exception
+    {
+        // Default Host
+        int offset=0;
+        String response = connector.getResponses("GET http://VirtualHost:8888/path/R1 HTTP/1.1\n" + "Host: wronghost\n" + "Connection: close\n" + "\n");
+        offset=checkContains(response,offset,"HTTP/1.1 200","Virtual host")+1;
+        offset=checkContains(response,offset,"Virtual Dump","Virtual host")+1;
+        offset=checkContains(response,offset,"pathInfo=/path/R1","Virtual host")+1;
+        offset=checkContains(response,offset,"servername=VirtualHost","Virtual host")+1;
+    }
+
+    @Test
+    public void test5_2_2() throws Exception
+    {
+        // Default Host
+        int offset=0;
+        String response = connector.getResponses("GET /path/R1 HTTP/1.1\n" + "Host: localhost\n" + "Connection: close\n" + "\n");
+        offset=checkContains(response,offset,"HTTP/1.1 200","Default host")+1;
+        offset=checkContains(response,offset,"Dump HttpHandler","Default host")+1;
+        offset=checkContains(response,offset,"pathInfo=/path/R1","Default host")+1;
+
+        // Virtual Host
+        offset=0;
+        response=connector.getResponses("GET /path/R2 HTTP/1.1\n"+"Host: VirtualHost\n"+"Connection: close\n"+"\n");
+        offset=checkContains(response,offset,"HTTP/1.1 200","Default host")+1;
+        offset=checkContains(response,offset,"Virtual Dump","virtual host")+1;
+        offset=checkContains(response,offset,"pathInfo=/path/R2","Default host")+1;
+    }
+
+    @Test
+    public void test5_2() throws Exception
+    {
+        // Virtual Host
+        int offset=0;
+        String response = connector.getResponses("GET /path/R1 HTTP/1.1\n" + "Host: VirtualHost\n" + "Connection: close\n" + "\n");
+        offset=checkContains(response,offset,"HTTP/1.1 200","2. virtual host field")+1;
+        offset=checkContains(response,offset,"Virtual Dump","2. virtual host field")+1;
+        offset=checkContains(response,offset,"pathInfo=/path/R1","2. virtual host field")+1;
+
+        // Virtual Host case insensitive
+        offset=0;
+        response=connector.getResponses("GET /path/R1 HTTP/1.1\n"+"Host: ViRtUalhOst\n"+"Connection: close\n"+"\n");
+        offset=checkContains(response,offset,"HTTP/1.1 200","2. virtual host field")+1;
+        offset=checkContains(response,offset,"Virtual Dump","2. virtual host field")+1;
+        offset=checkContains(response,offset,"pathInfo=/path/R1","2. virtual host field")+1;
+
+        // Virtual Host
+        offset=0;
+        response=connector.getResponses("GET /path/R1 HTTP/1.1\n"+"\n");
+        offset=checkContains(response,offset,"HTTP/1.1 400","3. no host")+1;
+    }
+
+    @Test
+    public void test8_1() throws Exception
+    {
+        int offset=0;
+        String response = connector.getResponses("GET /R1 HTTP/1.1\n" + "Host: localhost\n" + "\n", 250, TimeUnit.MILLISECONDS);
+        offset=checkContains(response,offset,"HTTP/1.1 200 OK\015\012","8.1.2 default")+10;
+        checkContains(response,offset,"Content-Length: ","8.1.2 default");
+
+        offset=0;
+        response=connector.getResponses("GET /R1 HTTP/1.1\n"+"Host: localhost\n"+"\n"+
+            "GET /R2 HTTP/1.1\n"+"Host: localhost\n"+"Connection: close\n"+"\n"+
+            "GET /R3 HTTP/1.1\n"+"Host: localhost\n"+"Connection: close\n"+"\n");
+
+        offset=checkContains(response,offset,"HTTP/1.1 200 OK\015\012","8.1.2 default")+1;
+        offset=checkContains(response,offset,"/R1","8.1.2 default")+1;
+
+        offset=checkContains(response,offset,"HTTP/1.1 200 OK\015\012","8.1.2.2 pipeline")+11;
+        offset=checkContains(response,offset,"Connection: close","8.1.2.2 pipeline")+1;
+        offset=checkContains(response,offset,"/R2","8.1.2.1 close")+3;
+
+        assertEquals("8.1.2.1 close",-1,response.indexOf("/R3"));
+
+    }
+
+    @Test
+    public void test10_4_18() throws Exception
+    {
+        // Expect Failure
+        int offset=0;
+        String response = connector.getResponses(
+                "GET /R1 HTTP/1.1\n" +
+                        "Host: localhost\n" +
+                        "Expect: unknown\n" +
+                        "Content-Type: text/plain\n" +
+                        "Content-Length: 8\n" +
+                        "\n");
+        offset=checkContains(response,offset,"HTTP/1.1 417","8.2.3 expect failure")+1;
+    }
+
+    @Test
+    public void test8_2_3_dash5() throws Exception
+    {
+        // Expect with body: client sends the content right away, we should not send 100-Continue
+        int offset=0;
+        String response = connector.getResponses(
+                "GET /R1 HTTP/1.1\n" +
+                        "Host: localhost\n" +
+                        "Expect: 100-continue\n" +
+                        "Content-Type: text/plain\n" +
+                        "Content-Length: 8\n" +
+                        "Connection: close\n" +
+                        "\n" +
+                        "123456\015\012");
+        checkNotContained(response, offset, "HTTP/1.1 100 ", "8.2.3 expect 100");
+        offset=checkContains(response,offset,"HTTP/1.1 200 OK","8.2.3 expect with body")+1;
+    }
+
+    @Test
+    public void test8_2_3() throws Exception
+    {
+        int offset=0;
+        // Expect 100
+        LocalConnector.LocalEndPoint endp =connector.executeRequest("GET /R1 HTTP/1.1\n"+
+                "Host: localhost\n"+
+                "Connection: close\n"+
+                "Expect: 100-continue\n"+
+                "Content-Type: text/plain\n"+
+                "Content-Length: 8\n"+
+                "\n");
+        Thread.sleep(200);
+        String infomational= endp.takeOutputString();
+        offset=checkContains(infomational,offset,"HTTP/1.1 100 ","8.2.3 expect 100")+1;
+        checkNotContained(infomational,offset,"HTTP/1.1 200","8.2.3 expect 100");
+
+        endp.addInput("654321\015\012");
+
+        Thread.sleep(200);
+        String response= endp.takeOutputString();
+        offset=0;
+        offset=checkContains(response,offset,"HTTP/1.1 200","8.2.3 expect 100")+1;
+        offset=checkContains(response,offset,"654321","8.2.3 expect 100")+1;
+    }
+
+    @Test
+    public void test8_2_4() throws Exception
+    {
+        // Expect 100 not sent
+        int offset=0;
+        String response = connector.getResponses("GET /R1?error=401 HTTP/1.1\n" +
+                "Host: localhost\n" +
+                "Expect: 100-continue\n" +
+                "Content-Type: text/plain\n" +
+                "Content-Length: 8\n" +
+                "\n");
+        checkNotContained(response,offset,"HTTP/1.1 100","8.2.3 expect 100");
+        offset=checkContains(response,offset,"HTTP/1.1 401 ","8.2.3 expect 100")+1;
+        offset=checkContains(response,offset,"Connection: close","8.2.3 expect 100")+1;
+    }
+
+    @Test
+    public void test9_2() throws Exception
+    {
+         int offset=0;
+
+         String response=connector.getResponses("OPTIONS * HTTP/1.1\n"+
+             "Connection: close\n"+
+             "Host: localhost\n"+
+             "\n");
+         offset=checkContains(response,offset, "HTTP/1.1 200","200")+1;
+
+         offset=0;
+         response=connector.getResponses("GET * HTTP/1.1\n"+
+             "Connection: close\n"+
+             "Host: localhost\n"+
+             "\n");
+         offset=checkContains(response,offset, "HTTP/1.1 400","400")+1;
+    }
+
+    @Test
+    public void test9_4()
+    {
+        try
+        {
+            String get=connector.getResponses("GET /R1 HTTP/1.0\n"+"Host: localhost\n"+"\n");
+
+            checkContains(get,0,"HTTP/1.1 200","GET");
+            checkContains(get,0,"Content-Type: text/html","GET _content");
+            checkContains(get,0,"<html>","GET body");
+
+            String head=connector.getResponses("HEAD /R1 HTTP/1.0\n"+"Host: localhost\n"+"\n");
+            checkContains(head,0,"HTTP/1.1 200","HEAD");
+            checkContains(head,0,"Content-Type: text/html","HEAD _content");
+            assertEquals("HEAD no body",-1,head.indexOf("<html>"));
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+            assertTrue(false);
+        }
+    }
+
+
+
+    @Test
+    public void test14_23() throws Exception
+    {
+        int offset=0;
+        String response = connector.getResponses("GET /R1 HTTP/1.0\n" + "Connection: close\n" + "\n");
+        offset=checkContains(response,offset,"HTTP/1.1 200","200")+1;
+
+        offset=0;
+        response=connector.getResponses("GET /R1 HTTP/1.1\n"+"Connection: close\n"+"\n");
+        offset=checkContains(response,offset,"HTTP/1.1 400","400")+1;
+
+        offset=0;
+        response=connector.getResponses("GET /R1 HTTP/1.1\n"+"Host: localhost\n"+"Connection: close\n"+"\n");
+        offset=checkContains(response,offset,"HTTP/1.1 200","200")+1;
+
+        offset=0;
+        response=connector.getResponses("GET /R1 HTTP/1.1\n"+"Host:\n"+"Connection: close\n"+"\n");
+        offset=checkContains(response,offset,"HTTP/1.1 400","400")+1;
+    }
+
+    @Test
+    public void test19_6()
+    {
+        try
+        {
+            int offset=0;
+            String response = connector.getResponses("GET /R1 HTTP/1.0\n" + "\n");
+            offset=checkContains(response,offset,"HTTP/1.1 200 OK\015\012","19.6.2 default close")+10;
+            checkNotContained(response,offset,"Connection: close","19.6.2 not assumed");
+
+            offset=0;
+            response=connector.getResponses("GET /R1 HTTP/1.0\n"+"Host: localhost\n"+"Connection: keep-alive\n"+"\n"+
+
+            "GET /R2 HTTP/1.0\n"+"Host: localhost\n"+"Connection: close\n"+"\n"+
+
+            "GET /R3 HTTP/1.0\n"+"Host: localhost\n"+"Connection: close\n"+"\n");
+
+            offset=checkContains(response,offset,"HTTP/1.1 200 OK\015\012","19.6.2 Keep-alive 1")+1;
+            offset=checkContains(response,offset,"Connection: keep-alive","19.6.2 Keep-alive 1")+1;
+
+            offset=checkContains(response,offset,"<html>","19.6.2 Keep-alive 1")+1;
+
+            offset=checkContains(response,offset,"/R1","19.6.2 Keep-alive 1")+1;
+
+            offset=checkContains(response,offset,"HTTP/1.1 200 OK\015\012","19.6.2 Keep-alive 2")+11;
+            offset=checkContains(response,offset,"/R2","19.6.2 Keep-alive close")+3;
+
+            assertEquals("19.6.2 closed",-1,response.indexOf("/R3"));
+
+            offset=0;
+            response=connector.getResponses("GET /R1 HTTP/1.0\n"+"Host: localhost\n"+"Connection: keep-alive\n"+"Content-Length: 10\n"+"\n"+"1234567890\n"+
+
+            "GET /RA HTTP/1.0\n"+"Host: localhost\n"+"Connection: keep-alive\n"+"Content-Length: 10\n"+"\n"+"ABCDEFGHIJ\n"+
+
+            "GET /R2 HTTP/1.0\n"+"Host: localhost\n"+"Connection: close\n"+"\n"+
+
+            "GET /R3 HTTP/1.0\n"+"Host: localhost\n"+"Connection: close\n"+"\n");
+            offset=checkContains(response,offset,"HTTP/1.1 200 OK\015\012","19.6.2 Keep-alive 1")+1;
+            offset=checkContains(response,offset,"Connection: keep-alive","19.6.2 Keep-alive 1")+1;
+            offset=checkContains(response,offset,"<html>","19.6.2 Keep-alive 1")+1;
+            offset=checkContains(response,offset,"1234567890","19.6.2 Keep-alive 1")+1;
+
+            offset=checkContains(response,offset,"HTTP/1.1 200 OK\015\012","19.6.2 Keep-alive 1")+1;
+            offset=checkContains(response,offset,"Connection: keep-alive","19.6.2 Keep-alive 1")+1;
+            offset=checkContains(response,offset,"<html>","19.6.2 Keep-alive 1")+1;
+            offset=checkContains(response,offset,"ABCDEFGHIJ","19.6.2 Keep-alive 1")+1;
+
+            offset=checkContains(response,offset,"HTTP/1.1 200 OK\015\012","19.6.2 Keep-alive 2")+11;
+            offset=checkContains(response,offset,"/R2","19.6.2 Keep-alive close")+3;
+
+            assertEquals("19.6.2 closed",-1,response.indexOf("/R3"));
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+            assertTrue(false);
+        }
+    }
+
+    private int checkContains(String s, int offset, String c, String test)
+    {
+        Assert.assertThat(test,s.substring(offset),containsString(c));
+        return s.indexOf(c,offset);
+    }
+
+    private void checkNotContained(String s, int offset, String c, String test)
+    {
+        Assert.assertThat(test,s.substring(offset),not(containsString(c)));
+    }
+
+    private void checkNotContained(String s, String c, String test)
+    {
+        checkNotContained(s,0,c,test);
+    }
+
+}
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/QueuedHttpInputTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/QueuedHttpInputTest.java
new file mode 100644
index 0000000..cac01b0
--- /dev/null
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/QueuedHttpInputTest.java
@@ -0,0 +1,33 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import org.junit.Test;
+
+public class QueuedHttpInputTest
+{
+    @Test
+    public void testNoContentMessageComplete() throws Exception
+    {
+        ByteBufferQueuedHttpInput input = new ByteBufferQueuedHttpInput();
+        input.messageComplete();
+
+        input.getNextContent();
+    }
+}
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/RFC2616Test.java b/jetty-server/src/test/java/org/eclipse/jetty/server/RFC2616Test.java
deleted file mode 100644
index 69e75c7..0000000
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/RFC2616Test.java
+++ /dev/null
@@ -1,852 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-/*
- * Created on 9/01/2004
- *
- * To change the template for this generated file go to
- * Window>Preferences>Java>Code Generation>Code and Comments
- */
-package org.eclipse.jetty.server;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import java.util.Date;
-import java.util.Enumeration;
-import java.util.List;
-
-import org.eclipse.jetty.http.HttpFields;
-import org.eclipse.jetty.server.handler.ContextHandler;
-import org.eclipse.jetty.server.handler.HandlerCollection;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-/**
- *
- */
-public class RFC2616Test
-{
-    private Server server;
-    private LocalConnector connector;
-
-    @Before
-    public void init() throws Exception
-    {
-        server = new Server();
-        connector = new LocalConnector();
-        server.addConnector(connector);
-
-        ContextHandler vcontext=new ContextHandler();
-        vcontext.setContextPath("/");
-        vcontext.setVirtualHosts(new String[]
-        { "VirtualHost" });
-        vcontext.setHandler(new DumpHandler("Virtual Dump"));
-
-        ContextHandler context=new ContextHandler();
-        context.setContextPath("/");
-        context.setHandler(new DumpHandler());
-
-        HandlerCollection collection=new HandlerCollection();
-        collection.setHandlers(new Handler[]
-        { vcontext, context });
-
-        server.setHandler(collection);
-
-        server.start();
-    }
-
-    @After
-    public void destroy() throws Exception
-    {
-        server.stop();
-        server.join();
-    }
-
-    @Test
-    public void test3_3()
-    {
-        try
-        {
-            HttpFields fields=new HttpFields();
-
-            fields.put("D1","Sun, 6 Nov 1994 08:49:37 GMT");
-            fields.put("D2","Sunday, 6-Nov-94 08:49:37 GMT");
-            fields.put("D3","Sun Nov  6 08:49:37 1994");
-            Date d1=new Date(fields.getDateField("D1"));
-            Date d2=new Date(fields.getDateField("D2"));
-            Date d3=new Date(fields.getDateField("D3"));
-
-            assertEquals("3.3.1 RFC 822 RFC 850",d2,d1);
-            assertEquals("3.3.1 RFC 850 ANSI C",d3,d2);
-
-            fields.putDateField("Date",d1.getTime());
-            assertEquals("3.3.1 RFC 822 preferred","Sun, 06 Nov 1994 08:49:37 GMT",fields.getStringField("Date"));
-        }
-        catch (Exception e)
-        {
-            e.printStackTrace();
-            assertTrue(false);
-        }
-    }
-
-    @Test
-    public void test3_6()
-    {
-        String response=null;
-        try
-        {
-            int offset=0;
-
-            // Chunk last
-            response=connector.getResponses("GET /R1 HTTP/1.1\n"+"Host: localhost\n"+"Transfer-Encoding: chunked,identity\n"+"Content-Type: text/plain\n"
-                    +"\015\012"+"5;\015\012"+"123\015\012\015\012"+"0;\015\012\015\012");
-            checkContains(response,offset,"HTTP/1.1 400 Bad","Chunked last");
-
-            // Chunked
-            offset=0;
-            response=connector.getResponses("GET /R1 HTTP/1.1\n"+"Host: localhost\n"+"Transfer-Encoding: chunked\n"+"Content-Type: text/plain\n"+"\n"+"2;\n"
-                    +"12\n"+"3;\n"+"345\n"+"0;\n\n"+
-
-                    "GET /R2 HTTP/1.1\n"+"Host: localhost\n"+"Transfer-Encoding: chunked\n"+"Content-Type: text/plain\n"+"\n"+"4;\n"+"6789\n"+"5;\n"+"abcde\n"
-                    +"0;\n\n"+
-
-                    "GET /R3 HTTP/1.1\n"+"Host: localhost\n"+"Connection: close\n"+"\n");
-            offset=checkContains(response,offset,"HTTP/1.1 200","3.6.1 Chunking");
-            offset=checkContains(response,offset,"12345","3.6.1 Chunking");
-            offset=checkContains(response,offset,"HTTP/1.1 200","3.6.1 Chunking");
-            offset=checkContains(response,offset,"6789abcde","3.6.1 Chunking");
-            offset=checkContains(response,offset,"/R3","3.6.1 Chunking");
-
-            // Chunked
-            offset=0;
-            response=connector.getResponses("POST /R1 HTTP/1.1\n"+"Host: localhost\n"+"Transfer-Encoding: chunked\n"+"Content-Type: text/plain\n"+"\n"+"3;\n"
-                    +"fgh\n"+"3;\n"+"Ijk\n"+"0;\n\n"+
-
-                    "POST /R2 HTTP/1.1\n"+"Host: localhost\n"+"Transfer-Encoding: chunked\n"+"Content-Type: text/plain\n"+"\n"+"4;\n"+"lmno\n"+"5;\n"+"Pqrst\n"
-                    +"0;\n\n"+
-
-                    "GET /R3 HTTP/1.1\n"+"Host: localhost\n"+"Connection: close\n"+"\n");
-            checkNotContained(response,"HTTP/1.1 100","3.6.1 Chunking");
-            offset=checkContains(response,offset,"HTTP/1.1 200","3.6.1 Chunking");
-            offset=checkContains(response,offset,"fghIjk","3.6.1 Chunking");
-            offset=checkContains(response,offset,"HTTP/1.1 200","3.6.1 Chunking");
-            offset=checkContains(response,offset,"lmnoPqrst","3.6.1 Chunking");
-            offset=checkContains(response,offset,"/R3","3.6.1 Chunking");
-
-            // Chunked and keep alive
-            offset=0;
-            response=connector.getResponses("GET /R1 HTTP/1.1\n"+"Host: localhost\n"+"Transfer-Encoding: chunked\n"+"Content-Type: text/plain\n"
-                    +"Connection: keep-alive\n"+"\n"+"3;\n"+"123\n"+"3;\n"+"456\n"+"0;\n\n"+
-
-                    "GET /R2 HTTP/1.1\n"+"Host: localhost\n"+"Connection: close\n"+"\n");
-            offset=checkContains(response,offset,"HTTP/1.1 200","3.6.1 Chunking")+10;
-            offset=checkContains(response,offset,"123456","3.6.1 Chunking");
-            offset=checkContains(response,offset,"/R2","3.6.1 Chunking")+10;
-        }
-        catch (Exception e)
-        {
-            e.printStackTrace();
-            assertTrue(false);
-            if (response!=null)
-                System.err.println(response);
-        }
-    }
-
-    @Test
-    public void test3_9()
-    {
-        try
-        {
-            HttpFields fields=new HttpFields();
-
-            fields.put("Q","bbb;q=0.5,aaa,ccc;q=0.002,d;q=0,e;q=0.0001,ddd;q=0.001,aa2,abb;q=0.7");
-            Enumeration qualities=fields.getValues("Q",", \t");
-            List list=HttpFields.qualityList(qualities);
-            assertEquals("Quality parameters","aaa",HttpFields.valueParameters(list.get(0).toString(),null));
-            assertEquals("Quality parameters","aa2",HttpFields.valueParameters(list.get(1).toString(),null));
-            assertEquals("Quality parameters","abb",HttpFields.valueParameters(list.get(2).toString(),null));
-            assertEquals("Quality parameters","bbb",HttpFields.valueParameters(list.get(3).toString(),null));
-            assertEquals("Quality parameters","ccc",HttpFields.valueParameters(list.get(4).toString(),null));
-            assertEquals("Quality parameters","ddd",HttpFields.valueParameters(list.get(5).toString(),null));
-        }
-        catch (Exception e)
-        {
-            e.printStackTrace();
-            assertTrue(false);
-        }
-    }
-
-    @Test
-    public void test4_4()
-    {
-        try
-        {
-            String response;
-            int offset=0;
-
-            // 2
-            // If _content length not used, second request will not be read.
-            offset=0;
-            response=connector.getResponses("GET /R1 HTTP/1.1\n"+"Host: localhost\n"+"Transfer-Encoding: identity\n"+"Content-Type: text/plain\n"
-                    +"Content-Length: 5\n"+"\n"+"123\015\012"+
-
-                    "GET /R2 HTTP/1.1\n"+"Host: localhost\n"+"Connection: close\n"+"\n");
-            offset=checkContains(response,offset,"HTTP/1.1 200 OK","2. identity")+10;
-            offset=checkContains(response,offset,"/R1","2. identity")+3;
-            offset=checkContains(response,offset,"HTTP/1.1 200 OK","2. identity")+10;
-            offset=checkContains(response,offset,"/R2","2. identity")+3;
-
-            // 3
-            // _content length is ignored, as chunking is used. If it is
-            // not ignored, the second request wont be seen.
-            offset=0;
-            response=connector.getResponses("GET /R1 HTTP/1.1\n"+"Host: localhost\n"+"Transfer-Encoding: chunked\n"+"Content-Type: text/plain\n"
-                    +"Content-Length: 100\n"+"\n"+"3;\n"+"123\n"+"3;\n"+"456\n"+"0;\n"+"\n"+
-
-                    "GET /R2 HTTP/1.1\n"+"Host: localhost\n"+"Connection: close\n"+"Content-Type: text/plain\n"+"Content-Length: 6\n"+"\n"+"123456");
-            offset=checkContains(response,offset,"HTTP/1.1 200 OK","3. ignore c-l")+1;
-            offset=checkContains(response,offset,"/R1","3. ignore c-l")+1;
-            offset=checkContains(response,offset,"123456","3. ignore c-l")+1;
-            offset=checkContains(response,offset,"HTTP/1.1 200 OK","3. ignore c-l")+1;
-            offset=checkContains(response,offset,"/R2","3. _content-length")+1;
-            offset=checkContains(response,offset,"123456","3. _content-length")+1;
-
-            // No _content length
-            assertTrue("Skip 411 checks as IE breaks this rule",true);
-            // offset=0; connector.reopen();
-            // response=connector.getResponses("GET /R2 HTTP/1.1\n"+
-            // "Host: localhost\n"+
-            // "Content-Type: text/plain\n"+
-            // "Connection: close\n"+
-            // "\n"+
-            // "123456");
-            // offset=checkContains(response,offset,
-            // "HTTP/1.1 411 ","411 length required")+10;
-            // offset=0; connector.reopen();
-            // response=connector.getResponses("GET /R2 HTTP/1.0\n"+
-            // "Content-Type: text/plain\n"+
-            // "\n"+
-            // "123456");
-            // offset=checkContains(response,offset,
-            // "HTTP/1.0 411 ","411 length required")+10;
-
-        }
-        catch (Exception e)
-        {
-            e.printStackTrace();
-            assertTrue(false);
-        }
-    }
-
-    @Test
-    public void test5_2() throws Exception
-    {
-        String response;
-        int offset=0;
-
-        // Default Host
-        offset=0;
-        response=connector.getResponses("GET /path/R1 HTTP/1.1\n"+"Host: localhost\n"+"\n");
-
-        offset=checkContains(response,offset,"HTTP/1.1 200","Default host")+1;
-        offset=checkContains(response,offset,"Dump HttpHandler","Default host")+1;
-        offset=checkContains(response,offset,"pathInfo=/path/R1","Default host")+1;
-
-        // Virtual Host
-        offset=0;
-        response=connector.getResponses("GET /path/R1 HTTP/1.1\n"+"Host: VirtualHost\n"+"\n");
-        offset=checkContains(response,offset,"HTTP/1.1 200","2. virtual host field")+1;
-        offset=checkContains(response,offset,"Virtual Dump","2. virtual host field")+1;
-        offset=checkContains(response,offset,"pathInfo=/path/R1","2. virtual host field")+1;
-
-        // Virtual Host case insensitive
-        offset=0;
-        response=connector.getResponses("GET /path/R1 HTTP/1.1\n"+"Host: ViRtUalhOst\n"+"\n");
-        offset=checkContains(response,offset,"HTTP/1.1 200","2. virtual host field")+1;
-        offset=checkContains(response,offset,"Virtual Dump","2. virtual host field")+1;
-        offset=checkContains(response,offset,"pathInfo=/path/R1","2. virtual host field")+1;
-
-        // Virtual Host
-        offset=0;
-        response=connector.getResponses("GET /path/R1 HTTP/1.1\n"+"\n");
-        offset=checkContains(response,offset,"HTTP/1.1 400","3. no host")+1;
-    }
-
-    @Test
-    public void test8_1()
-    {
-        try
-        {
-            String response;
-            int offset=0;
-
-            offset=0;
-            response=connector.getResponses("GET /R1 HTTP/1.1\n"+"Host: localhost\n"+"\n");
-            offset=checkContains(response,offset,"HTTP/1.1 200 OK\015\012","8.1.2 default")+10;
-            checkContains(response,offset,"Content-Length: ","8.1.2 default");
-
-            offset=0;
-            response=connector.getResponses("GET /R1 HTTP/1.1\n"+"Host: localhost\n"+"\n"+
-            "GET /R2 HTTP/1.1\n"+"Host: localhost\n"+"Connection: close\n"+"\n"+
-            "GET /R3 HTTP/1.1\n"+"Host: localhost\n"+"Connection: close\n"+"\n");
-            
-            offset=checkContains(response,offset,"HTTP/1.1 200 OK\015\012","8.1.2 default")+1;
-            offset=checkContains(response,offset,"/R1","8.1.2 default")+1;
-
-            assertEquals("8.1.2.1 close",-1,response.indexOf("/R3"));
-
-            offset=checkContains(response,offset,"HTTP/1.1 200 OK\015\012","8.1.2.2 pipeline")+11;
-            offset=checkContains(response,offset,"Connection: close","8.1.2.2 pipeline")+1;
-            offset=checkContains(response,offset,"/R2","8.1.2.1 close")+3;
-        }
-        catch (Exception e)
-        {
-            e.printStackTrace();
-            assertTrue(false);
-        }
-    }
-
-    @Test
-    public void test8_2() throws Exception
-    {
-        String response;
-        int offset=0;
-        // No Expect 100
-        offset=0;
-        response=connector.getResponses("GET /R1 HTTP/1.1\n"+
-                "Host: localhost\n"+
-                "Content-Type: text/plain\n"+
-                "Content-Length: 8\n"+
-                "\n",true);
-
-        assertTrue("8.2.3 no expect 100",response==null || response.length()==0);
-        response=connector.getResponses("GET /R1 HTTP/1.1\n"+
-                "Host: localhost\n"+
-                "Content-Type: text/plain\n"+
-                "Content-Length: 8\n"+
-                "\n"+
-                "AbCdEf\015\012",true);
-        offset=checkContains(response,offset,"HTTP/1.1 200","8.2.3 no expect 100")+1;
-        offset=checkContains(response,offset,"AbCdEf","8.2.3 no expect 100")+1;
-
-        // Expect Failure
-        offset=0;
-        response=connector.getResponses("GET /R1 HTTP/1.1\n"+"Host: localhost\n"+"Expect: unknown\n"+"Content-Type: text/plain\n"+"Content-Length: 8\n"
-                +"\n");
-        offset=checkContains(response,offset,"HTTP/1.1 417","8.2.3 expect failure")+1;
-
-        // Expect with body
-        offset=0;
-        response=connector.getResponses("GET /R1 HTTP/1.1\n"+"Host: localhost\n"+"Expect: 100-continue\n"+"Content-Type: text/plain\n"
-                +"Content-Length: 8\n"+"Connection: close\n"+"\n"+"123456\015\012");
-        checkNotContained(response,offset,"HTTP/1.1 100 ","8.2.3 expect 100");
-        offset=checkContains(response,offset,"HTTP/1.1 200 OK","8.2.3 expect with body")+1;
-    }
-
-    @Test
-    public void test8_2_3() throws Exception
-    {
-        String response;
-        int offset=0;
-        // Expect 100
-        response=connector.getResponses("GET /R1 HTTP/1.1\n"+
-                "Host: localhost\n"+
-                "Connection: close\n"+
-                "Expect: 100-continue\n"+
-                "Content-Type: text/plain\n"+
-                "Content-Length: 8\n"+
-                "\n",true);
-        offset=checkContains(response,offset,"HTTP/1.1 100 ","8.2.3 expect 100")+1;
-        checkNotContained(response,offset,"HTTP/1.1 200","8.2.3 expect 100");
-        /* can't test this with localconnector.
-            response=connector.getResponses("654321\015\012");
-            offset=checkContains(response,offset,"HTTP/1.1 200","8.2.3 expect 100")+1;
-            offset=checkContains(response,offset,"654321","8.2.3 expect 100")+1;
-         */
-    }
-    
-    @Test
-    public void test8_2_4() throws Exception
-    {
-        String response;
-        int offset=0;
-        // Expect 100 not sent
-        offset=0;
-
-        response=connector.getResponses("GET /R1?error=401 HTTP/1.1\n"+
-                "Host: localhost\n"+
-                "Expect: 100-continue\n"+
-                "Content-Type: text/plain\n"+
-                "Content-Length: 8\n"+
-                "\n",true);
-        checkNotContained(response,offset,"HTTP/1.1 100","8.2.3 expect 100");
-        offset=checkContains(response,offset,"HTTP/1.1 401 ","8.2.3 expect 100")+1;
-        offset=checkContains(response,offset,"Connection: close","8.2.3 expect 100")+1;
-    }
-
-    @Test
-    public void test9_2()
-    {
-        // TODO
-        /*
-         * try { TestListener listener = new TestListener(); String response;
-         * int offset=0; connector.reopen();
-         *  // Default Host offset=0; connector.reopen();
-         * response=connector.getResponses("OPTIONS * HTTP/1.1\n"+ "Connection:
-         * close\n"+ "Host: localhost\n"+ "\n");
-         * offset=checkContains(response,offset, "HTTP/1.1 200","200")+1;
-         * offset=checkContains(response,offset, "Allow: GET, HEAD, POST, PUT,
-         * DELETE, MOVE, OPTIONS, TRACE","Allow")+1;
-         *  } catch(Exception e) { e.printStackTrace(); assertTrue(false); }
-         */
-    }
-
-    @Test
-    public void test9_4()
-    {
-        try
-        {
-            String get=connector.getResponses("GET /R1 HTTP/1.0\n"+"Host: localhost\n"+"\n");
-
-            checkContains(get,0,"HTTP/1.1 200","GET");
-            checkContains(get,0,"Content-Type: text/html","GET _content");
-            checkContains(get,0,"<html>","GET body");
-
-            String head=connector.getResponses("HEAD /R1 HTTP/1.0\n"+"Host: localhost\n"+"\n");
-            checkContains(head,0,"HTTP/1.1 200","HEAD");
-            checkContains(head,0,"Content-Type: text/html","HEAD _content");
-            assertEquals("HEAD no body",-1,head.indexOf("<html>"));
-        }
-        catch (Exception e)
-        {
-            e.printStackTrace();
-            assertTrue(false);
-        }
-    }
-
-    @Test
-    public void test9_8()
-    {
-        // TODO
-        /*
-         * try { TestListener listener = new TestListener(); String response;
-         * int offset=0; connector.reopen();
-         *  // Default Host offset=0; connector.reopen();
-         * response=connector.getResponses("TRACE /path HTTP/1.1\n"+ "Host:
-         * localhost\n"+ "Connection: close\n"+ "\n");
-         * offset=checkContains(response,offset, "HTTP/1.1 200","200")+1;
-         * offset=checkContains(response,offset, "Content-Type: message/http",
-         * "message/http")+1; offset=checkContains(response,offset, "TRACE /path
-         * HTTP/1.1\r\n"+ "Host: localhost\r\n", "Request"); } catch(Exception
-         * e) { e.printStackTrace(); assertTrue(false); }
-         */
-    }
-
-    @Test
-    public void test10_2_7()
-    {
-        // TODO
-        /*
-         *
-         * try { TestListener listener = new TestListener(); String response;
-         * int offset=0; connector.reopen();
-         *  // check to see if corresponging GET w/o range would return // a)
-         * ETag // b) Content-Location // these same headers will be required
-         * for corresponding // sub range requests
-         *
-         * response=connector.getResponses("GET /" +
-         * TestRFC2616.testFiles[0].name + " HTTP/1.1\n"+ "Host: localhost\n"+
-         * "Connection: close\n"+ "\n");
-         *
-         * boolean noRangeHasContentLocation =
-         * (response.indexOf("\r\nContent-Location: ") != -1);
-         *
-         *  // now try again for the same resource but this time WITH range
-         * header
-         *
-         * response=connector.getResponses("GET /" +
-         * TestRFC2616.testFiles[0].name + " HTTP/1.1\n"+ "Host: localhost\n"+
-         * "Connection: close\n"+ "Range: bytes=1-3\n"+ "\n");
-         *
-         * offset=0; connector.reopen(); offset=checkContains(response,offset,
-         * "HTTP/1.1 206 Partial Content\r\n", "1. proper 206 status code");
-         * offset=checkContains(response,offset, "Content-Type: text/plain", "2.
-         * _content type") + 2; offset=checkContains(response,offset,
-         * "Last-Modified: " + TestRFC2616.testFiles[0].modDate + "\r\n", "3.
-         * correct resource mod date");
-         *  // if GET w/o range had Content-Location, then the corresponding //
-         * response for the a GET w/ range must also have that same header
-         *
-         * offset=checkContains(response,offset, "Content-Range: bytes 1-3/26",
-         * "4. _content range") + 2;
-         *
-         * if (noRangeHasContentLocation) {
-         * offset=checkContains(response,offset, "Content-Location: ", "5.
-         * Content-Location header as with 200"); } else { // no need to check
-         * for Conten-Location header in 206 response // spec does not require
-         * existence or absence if these want any // header for the get w/o
-         * range }
-         *
-         * String expectedData = TestRFC2616.testFiles[0].data.substring(1,
-         * 3+1); offset=checkContains(response,offset, expectedData, "6.
-         * subrange data: \"" + expectedData + "\""); } catch(Exception e) {
-         * e.printStackTrace(); assertTrue(false); }
-         *
-         */
-    }
-
-    @Test
-    public void test10_3()
-    {
-        // TODO
-        /*
-         * try { TestListener listener = new TestListener(); String response;
-         * int offset=0; connector.reopen();
-         *  // HTTP/1.0 offset=0; connector.reopen();
-         * response=connector.getResponses("GET /redirect HTTP/1.0\n"+
-         * "Connection: Keep-Alive\n"+ "\n" );
-         * offset=checkContains(response,offset, "HTTP/1.1 302","302")+1;
-         * checkContains(response,offset, "Location: /dump", "redirected");
-         * checkContains(response,offset, "Connection: close", "Connection:
-         * close");
-         *
-         *  // HTTP/1.1 offset=0; connector.reopen();
-         * response=connector.getResponses("GET /redirect HTTP/1.1\n"+ "Host:
-         * localhost\n"+ "\n"+ "GET /redirect HTTP/1.1\n"+ "Host: localhost\n"+
-         * "Connection: close\n"+ "\n" ); offset=checkContains(response,offset,
-         * "HTTP/1.1 302","302")+1; checkContains(response,offset, "Location:
-         * /dump", "redirected"); checkContains(response,offset,
-         * "Transfer-Encoding: chunked", "Transfer-Encoding: chunked");
-         *
-         * offset=checkContains(response,offset, "HTTP/1.1 302","302")+1;
-         * checkContains(response,offset, "Location: /dump", "redirected");
-         * checkContains(response,offset, "Connection: close", "closed");
-         *  // HTTP/1.0 _content offset=0; connector.reopen();
-         * response=connector.getResponses("GET /redirect/_content HTTP/1.0\n"+
-         * "Connection: Keep-Alive\n"+ "\n"+ "GET /redirect/_content
-         * HTTP/1.0\n"+ "\n" ); offset=checkContains(response,offset, "HTTP/1.1
-         * 302","302")+1; checkContains(response,offset, "Location: /dump",
-         * "redirected"); checkContains(response,offset, "Connection: close",
-         * "close no _content length");
-         *  // HTTP/1.1 _content offset=0; connector.reopen();
-         * response=connector.getResponses("GET /redirect/_content HTTP/1.1\n"+
-         * "Host: localhost\n"+ "\n"+ "GET /redirect/_content HTTP/1.1\n"+
-         * "Host: localhost\n"+ "Connection: close\n"+ "\n" );
-         * offset=checkContains(response,offset, "HTTP/1.1 302","302")+1;
-         * checkContains(response,offset, "Location: /dump", "redirected");
-         * checkContains(response,offset, "Transfer-Encoding: chunked", "chunked
-         * _content length");
-         *
-         * offset=checkContains(response,offset, "HTTP/1.1 302","302")+1;
-         * checkContains(response,offset, "Location: /dump", "redirected");
-         * checkContains(response,offset, "Connection: close", "closed");
-         *  } catch(Exception e) { e.printStackTrace(); assertTrue(false); }
-         *
-         */
-    }
-
-    @Test
-    public void test14_16()
-    {
-        // TODO
-        /*
-         * try { TestListener listener = new TestListener();
-         *
-         * int id = 0;
-         *
-         *  // // calibrate with normal request (no ranges); if this doesnt //
-         * work, dont expect ranges to work either //
-         * connector.checkContentRange( t, Integer.toString(id++),
-         * TestRFC2616.testFiles[0].name, null, 200, null,
-         * TestRFC2616.testFiles[0].data );
-         *
-         *  // // server should ignore all range headers which include // at
-         * least one syntactically invalid range // String [] totallyBadRanges = {
-         * "bytes=a-b", "bytes=-1-2", "bytes=-1-2,2-3", "bytes=-", "bytes=-1-",
-         * "bytes=a-b,-1-1-1", "doublehalfwords=1-2", };
-         *
-         * for (int i = 0; i < totallyBadRanges.length; i++) {
-         * connector.checkContentRange( t, "BadRange"+i,
-         * TestRFC2616.testFiles[0].name, totallyBadRanges[i], 416, null,
-         * TestRFC2616.testFiles[0].data ); }
-         *
-         *  // // should test for combinations of good and syntactically //
-         * invalid ranges here, but I am not certain what the right // behavior
-         * is abymore // // a) Range: bytes=a-b,5-8 // // b) Range:
-         * bytes=a-b,bytes=5-8 // // c) Range: bytes=a-b // Range: bytes=5-8 //
-         *
-         *  // // return data for valid ranges while ignoring unsatisfiable //
-         * ranges //
-         *
-         * connector.checkContentRange( t, "bytes=5-8",
-         * TestRFC2616.testFiles[0].name, "bytes=5-8", 206, "5-8/26",
-         * TestRFC2616.testFiles[0].data.substring(5,8+1) );
-         *  // // server should not return a 416 if at least syntactically valid
-         * ranges // are is satisfiable // connector.checkContentRange( t,
-         * "bytes=5-8,50-60", TestRFC2616.testFiles[0].name, "bytes=5-8,50-60",
-         * 206, "5-8/26", TestRFC2616.testFiles[0].data.substring(5,8+1) );
-         * connector.checkContentRange( t, "bytes=50-60,5-8",
-         * TestRFC2616.testFiles[0].name, "bytes=50-60,5-8", 206, "5-8/26",
-         * TestRFC2616.testFiles[0].data.substring(5,8+1) );
-         *  // 416 as none are satisfiable connector.checkContentRange( t,
-         * "bytes=50-60", TestRFC2616.testFiles[0].name, "bytes=50-60", 416,
-         * "*REMOVE_THIS/26", null );
-         *
-         *  }
-         *
-         * catch(Exception e) { e.printStackTrace(); assertTrue(false); }
-         */
-    }
-
-    @Test
-    public void test14_23()
-    {
-        try
-        {
-            String response;
-            int offset=0;
-
-            // HTTP/1.0 OK with no host
-            offset=0;
-            response=connector.getResponses("GET /R1 HTTP/1.0\n"+"\n");
-            offset=checkContains(response,offset,"HTTP/1.1 200","200")+1;
-
-            offset=0;
-            response=connector.getResponses("GET /R1 HTTP/1.1\n"+"\n");
-            offset=checkContains(response,offset,"HTTP/1.1 400","400")+1;
-
-            offset=0;
-            response=connector.getResponses("GET /R1 HTTP/1.1\n"+"Host: localhost\n"+"\n");
-            offset=checkContains(response,offset,"HTTP/1.1 200","200")+1;
-
-            offset=0;
-            response=connector.getResponses("GET /R1 HTTP/1.1\n"+"Host:\n"+"\n");
-            offset=checkContains(response,offset,"HTTP/1.1 200","200")+1;
-
-        }
-        catch (Exception e)
-        {
-            e.printStackTrace();
-            assertTrue(false);
-        }
-    }
-
-    @Test
-    public void test14_35()
-    {
-        // TODO
-        /*
-         * try { TestListener listener = new TestListener();
-         *  // // test various valid range specs that have not been // tested
-         * yet //
-         *
-         * connector.checkContentRange( t, "bytes=0-2",
-         * TestRFC2616.testFiles[0].name, "bytes=0-2", 206, "0-2/26",
-         * TestRFC2616.testFiles[0].data.substring(0,2+1) );
-         *
-         * connector.checkContentRange( t, "bytes=23-",
-         * TestRFC2616.testFiles[0].name, "bytes=23-", 206, "23-25/26",
-         * TestRFC2616.testFiles[0].data.substring(23,25+1) );
-         *
-         * connector.checkContentRange( t, "bytes=23-42",
-         * TestRFC2616.testFiles[0].name, "bytes=23-42", 206, "23-25/26",
-         * TestRFC2616.testFiles[0].data.substring(23,25+1) );
-         *
-         * connector.checkContentRange( t, "bytes=-3",
-         * TestRFC2616.testFiles[0].name, "bytes=-3", 206, "23-25/26",
-         * TestRFC2616.testFiles[0].data.substring(23,25+1) );
-         *
-         * connector.checkContentRange( t, "bytes=23-23,-2",
-         * TestRFC2616.testFiles[0].name, "bytes=23-23,-2", 206, "23-23/26",
-         * TestRFC2616.testFiles[0].data.substring(23,23+1) );
-         *
-         * connector.checkContentRange( t, "bytes=-1,-2,-3",
-         * TestRFC2616.testFiles[0].name, "bytes=-1,-2,-3", 206, "25-25/26",
-         * TestRFC2616.testFiles[0].data.substring(25,25+1) );
-         *  }
-         *
-         * catch(Exception e) { e.printStackTrace(); assertTrue(false); }
-         */}
-
-    @Test
-    public void test14_39()
-    {
-        // TODO
-        /*
-         * try { TestListener listener = new TestListener(); String response;
-         * int offset=0; connector.reopen();
-         *  // Gzip accepted offset=0; connector.reopen();
-         * response=connector.getResponses("GET /R1?gzip HTTP/1.1\n"+ "Host:
-         * localhost\n"+ "TE: gzip;q=0.5\n"+ "Connection: close\n"+ "\n");
-         * offset=checkContains(response,offset, "HTTP/1.1 200","TE: coding")+1;
-         * offset=checkContains(response,offset, "Transfer-Encoding: gzip","TE:
-         * coding")+1;
-         *  // Gzip not accepted offset=0; connector.reopen();
-         * response=connector.getResponses("GET /R1?gzip HTTP/1.1\n"+ "Host:
-         * localhost\n"+ "TE: deflate\n"+ "Connection: close\n"+ "\n");
-         * offset=checkContains(response,offset, "HTTP/1.1 501","TE: coding not
-         * accepted")+1;
-         *  } catch(Exception e) { e.printStackTrace(); assertTrue(false); }
-         */
-    }
-
-    @Test
-    public void test19_6()
-    {
-        try
-        {
-            String response;
-            int offset=0;
-
-            offset=0;
-            response=connector.getResponses("GET /R1 HTTP/1.0\n"+"\n");
-            offset=checkContains(response,offset,"HTTP/1.1 200 OK\015\012","19.6.2 default close")+10;
-            checkNotContained(response,offset,"Connection: close","19.6.2 not assumed");
-
-            offset=0;
-            response=connector.getResponses("GET /R1 HTTP/1.0\n"+"Host: localhost\n"+"Connection: keep-alive\n"+"\n"+
-
-            "GET /R2 HTTP/1.0\n"+"Host: localhost\n"+"Connection: close\n"+"\n"+
-
-            "GET /R3 HTTP/1.0\n"+"Host: localhost\n"+"Connection: close\n"+"\n");
-            
-            offset=checkContains(response,offset,"HTTP/1.1 200 OK\015\012","19.6.2 Keep-alive 1")+1;
-            offset=checkContains(response,offset,"Connection: keep-alive","19.6.2 Keep-alive 1")+1;
-
-            offset=checkContains(response,offset,"<html>","19.6.2 Keep-alive 1")+1;
-
-            offset=checkContains(response,offset,"/R1","19.6.2 Keep-alive 1")+1;
-
-            offset=checkContains(response,offset,"HTTP/1.1 200 OK\015\012","19.6.2 Keep-alive 2")+11;
-            offset=checkContains(response,offset,"/R2","19.6.2 Keep-alive close")+3;
-
-            assertEquals("19.6.2 closed",-1,response.indexOf("/R3"));
-
-            offset=0;
-            response=connector.getResponses("GET /R1 HTTP/1.0\n"+"Host: localhost\n"+"Connection: keep-alive\n"+"Content-Length: 10\n"+"\n"+"1234567890\n"+
-
-            "GET /RA HTTP/1.0\n"+"Host: localhost\n"+"Connection: keep-alive\n"+"Content-Length: 10\n"+"\n"+"ABCDEFGHIJ\n"+
-
-            "GET /R2 HTTP/1.0\n"+"Host: localhost\n"+"Connection: close\n"+"\n"+
-
-            "GET /R3 HTTP/1.0\n"+"Host: localhost\n"+"Connection: close\n"+"\n");
-            offset=checkContains(response,offset,"HTTP/1.1 200 OK\015\012","19.6.2 Keep-alive 1")+1;
-            offset=checkContains(response,offset,"Connection: keep-alive","19.6.2 Keep-alive 1")+1;
-            offset=checkContains(response,offset,"<html>","19.6.2 Keep-alive 1")+1;
-            offset=checkContains(response,offset,"1234567890","19.6.2 Keep-alive 1")+1;
-
-            offset=checkContains(response,offset,"HTTP/1.1 200 OK\015\012","19.6.2 Keep-alive 1")+1;
-            offset=checkContains(response,offset,"Connection: keep-alive","19.6.2 Keep-alive 1")+1;
-            offset=checkContains(response,offset,"<html>","19.6.2 Keep-alive 1")+1;
-            offset=checkContains(response,offset,"ABCDEFGHIJ","19.6.2 Keep-alive 1")+1;
-
-            offset=checkContains(response,offset,"HTTP/1.1 200 OK\015\012","19.6.2 Keep-alive 2")+11;
-            offset=checkContains(response,offset,"/R2","19.6.2 Keep-alive close")+3;
-
-            assertEquals("19.6.2 closed",-1,response.indexOf("/R3"));
-        }
-        catch (Exception e)
-        {
-            e.printStackTrace();
-            assertTrue(false);
-        }
-    }
-
-    private void checkContentRange(LocalConnector listener, String tname, String path, String reqRanges, int expectedStatus, String expectedRange, String expectedData)
-    {
-        try
-        {
-            String response;
-            int offset=0;
-
-            String byteRangeHeader="";
-            if (reqRanges!=null)
-            {
-                byteRangeHeader="Range: "+reqRanges+"\n";
-            }
-
-            response=connector.getResponses("GET /"+path+" HTTP/1.1\n"+"Host: localhost\n"+byteRangeHeader+"Connection: close\n"+"\n");
-
-            switch (expectedStatus)
-            {
-                case 200:
-                {
-                    offset=checkContains(response,offset,"HTTP/1.1 200 OK\r\n",tname+".1. proper 200 OK status code");
-                    break;
-                }
-                case 206:
-                {
-                    offset=checkContains(response,offset,"HTTP/1.1 206 Partial Content\r\n",tname+".1. proper 206 Partial Content status code");
-                    break;
-                }
-                case 416:
-                {
-                    offset=checkContains(response,offset,"HTTP/1.1 416 Requested Range Not Satisfiable\r\n",tname
-                            +".1. proper 416 Requested Range not Satisfiable status code");
-                    break;
-                }
-            }
-
-            if (expectedRange!=null)
-            {
-                String expectedContentRange="Content-Range: bytes "+expectedRange+"\r\n";
-                offset=checkContains(response,offset,expectedContentRange,tname+".2. _content range "+expectedRange);
-            }
-
-            if (expectedStatus==200||expectedStatus==206)
-            {
-                offset=checkContains(response,offset,expectedData,tname+".3. subrange data: \""+expectedData+"\"");
-            }
-        }
-        catch (Exception e)
-        {
-            e.printStackTrace();
-            assertTrue(false);
-        }
-    }
-
-    private int checkContains(String s, int offset, String c, String test)
-    {
-        int o=s.indexOf(c,offset);
-        if (o<offset)
-        {
-            System.err.println("FAILED: "+test);
-            System.err.println("'"+c+"' not in:");
-            System.err.println(s.substring(offset));
-            System.err.flush();
-            System.out.println("--\n"+s);
-            System.out.flush();
-            assertTrue(test,false);
-        }
-        return o;
-    }
-
-    private void checkNotContained(String s, int offset, String c, String test)
-    {
-        int o=s.indexOf(c,offset);
-        assertTrue(test,o<offset);
-    }
-
-    private void checkNotContained(String s, String c, String test)
-    {
-        checkNotContained(s,0,c,test);
-    }
-
-}
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/RequestTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/RequestTest.java
index 17159b9..307d5dd 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/RequestTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/RequestTest.java
@@ -18,12 +18,17 @@
 
 package org.eclipse.jetty.server;
 
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.startsWith;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
 
 import java.io.BufferedReader;
 import java.io.File;
@@ -31,38 +36,41 @@ import java.io.FileReader;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.Reader;
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Enumeration;
-import java.util.HashMap;
 import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
 
 import javax.servlet.MultipartConfigElement;
 import javax.servlet.ServletException;
+import javax.servlet.ServletInputStream;
 import javax.servlet.ServletRequestEvent;
 import javax.servlet.http.Cookie;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.Part;
 
-import junit.framework.Assert;
-
 import org.eclipse.jetty.http.MimeTypes;
 import org.eclipse.jetty.server.handler.AbstractHandler;
 import org.eclipse.jetty.server.handler.ContextHandler;
 import org.eclipse.jetty.util.IO;
-import org.eclipse.jetty.util.MultiPartInputStream;
+import org.eclipse.jetty.util.MultiPartInputStreamParser;
 import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.Utf8Appendable;
 import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.log.StdErrLog;
+import org.hamcrest.Matchers;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 
-/**
- *
- */
 public class RequestTest
 {
+    private static final Logger LOG = Log.getLogger(RequestTest.class);
     private Server _server;
     private LocalConnector _connector;
     private RequestHandler _handler;
@@ -71,12 +79,13 @@ public class RequestTest
     public void init() throws Exception
     {
         _server = new Server();
-        _connector = new LocalConnector();
-        _connector.setRequestHeaderSize(512);
-        _connector.setRequestBufferSize(1024);
-        _connector.setResponseHeaderSize(512);
-        _connector.setResponseBufferSize(2048);
-        _connector.setForwarded(true);
+        HttpConnectionFactory http = new HttpConnectionFactory();
+        http.setInputBufferSize(1024);
+        http.getHttpConfiguration().setRequestHeaderSize(512);
+        http.getHttpConfiguration().setResponseHeaderSize(512);
+        http.getHttpConfiguration().setOutputBufferSize(2048);
+        http.getHttpConfiguration().addCustomizer(new ForwardedRequestCustomizer());
+        _connector = new LocalConnector(_server,http);
         _server.addConnector(_connector);
         _handler = new RequestHandler();
         _server.setHandler(_handler);
@@ -95,27 +104,14 @@ public class RequestTest
     {
         _handler._checker = new RequestTester()
         {
+            @Override
             public boolean check(HttpServletRequest request,HttpServletResponse response)
             {
-                Map map = null;
-                try
-                {
-                    //do the parse
-                    request.getParameterMap();
-                    Assert.fail("Expected parsing failure");
-                    return false;
-                }
-                catch (Exception e)
-                {
-                    //catch the error and check the param map is not null
-                    map = request.getParameterMap();
-                    System.err.println(map);
-                    assertFalse(map == null);
-                    assertTrue(map.isEmpty());
-
-                    Enumeration names = request.getParameterNames();
-                    assertFalse(names.hasMoreElements());
-                }
+                Map<String,String[]> map = null;
+                //do the parse
+                map = request.getParameterMap();
+                assertEquals("aaa"+Utf8Appendable.REPLACEMENT+"bbb",map.get("param")[0]);
+                assertEquals("value",map.get("other")[0]);
 
                 return true;
             }
@@ -123,21 +119,58 @@ public class RequestTest
 
         //Send a request with query string with illegal hex code to cause
         //an exception parsing the params
-        String request="GET /?param=%ZZaaa HTTP/1.1\r\n"+
+        String request="GET /?param=aaa%ZZbbb&other=value HTTP/1.1\r\n"+
         "Host: whatever\r\n"+
         "Content-Type: text/html;charset=utf8\n"+
+        "Connection: close\n"+
         "\n";
 
         String responses=_connector.getResponses(request);
         assertTrue(responses.startsWith("HTTP/1.1 200"));
 
     }
-    
+
+    @Test
+    public void testEmptyHeaders() throws Exception
+    {
+        _handler._checker = new RequestTester()
+        {
+            @Override
+            public boolean check(HttpServletRequest request,HttpServletResponse response)
+            {
+                assertNotNull(request.getLocale());
+                assertTrue(request.getLocales().hasMoreElements());
+                assertNull(request.getContentType());
+                assertNull(request.getCharacterEncoding());
+                assertEquals(0,request.getQueryString().length());
+                assertEquals(-1,request.getContentLength());
+                assertNull(request.getCookies());
+                assertNull(request.getHeader("Name"));
+                assertFalse(request.getHeaders("Name").hasMoreElements());
+                assertEquals(-1,request.getDateHeader("Name"));
+                return true;
+            }
+        };
+
+        String request="GET /? HTTP/1.1\r\n"+
+        "Host: whatever\r\n"+
+        "Connection: close\n"+
+        "Content-Type: \n"+
+        "Accept-Language: \n"+
+        "Cookie: \n"+
+        "Name: \n"+
+        "\n";
+
+        String responses=_connector.getResponses(request);
+        assertTrue(responses.startsWith("HTTP/1.1 200"));
+    }
+
     @Test
     public void testMultiPartNoConfig() throws Exception
     {
         _handler._checker = new RequestTester()
         {
+            @Override
             public boolean check(HttpServletRequest request,HttpServletResponse response)
             {
                 try
@@ -157,7 +190,7 @@ public class RequestTest
                 }
             }
         };
-        
+
         String multipart =  "--AaB03x\r\n"+
         "content-disposition: form-data; name=\"field1\"\r\n"+
         "\r\n"+
@@ -168,28 +201,30 @@ public class RequestTest
         "\r\n"+
         "000000000000000000000000000000000000000000000000000\r\n"+
         "--AaB03x--\r\n";
-        
+
         String request="GET / HTTP/1.1\r\n"+
         "Host: whatever\r\n"+
         "Content-Type: multipart/form-data; boundary=\"AaB03x\"\r\n"+
         "Content-Length: "+multipart.getBytes().length+"\r\n"+
+        "Connection: close\r\n"+
         "\r\n"+
         multipart;
 
         String responses=_connector.getResponses(request);
         assertTrue(responses.startsWith("HTTP/1.1 200"));
     }
-    
-    
+
+
     @Test
     public void testMultiPart() throws Exception
-    {
-        final File tmpDir = new File (System.getProperty("java.io.tmpdir"));
-        final File testTmpDir = new File (tmpDir, "reqtest");
+    {        
+        final File testTmpDir = File.createTempFile("reqtest", null);
+        if (testTmpDir.exists())
+            testTmpDir.delete();
+        testTmpDir.mkdir();
         testTmpDir.deleteOnExit();
-        assertTrue(testTmpDir.mkdirs());
         assertTrue(testTmpDir.list().length == 0);
-        
+
         ContextHandler contextHandler = new ContextHandler();
         contextHandler.setContextPath("/foo");
         contextHandler.setResourceBase(".");
@@ -200,7 +235,7 @@ public class RequestTest
             @Override
             public void requestDestroyed(ServletRequestEvent sre)
             {
-                MultiPartInputStream m = (MultiPartInputStream)sre.getServletRequest().getAttribute(Request.__MULTIPART_INPUT_STREAM);
+                MultiPartInputStreamParser m = (MultiPartInputStreamParser)sre.getServletRequest().getAttribute(Request.__MULTIPART_INPUT_STREAM);
                 ContextHandler.Context c = (ContextHandler.Context)sre.getServletRequest().getAttribute(Request.__MULTIPART_CONTEXT);
                 assertNotNull (m);
                 assertNotNull (c);
@@ -211,12 +246,12 @@ public class RequestTest
                 String[] files = testTmpDir.list();
                 assertTrue(files.length == 0);
             }
-            
+
         });
         _server.stop();
         _server.setHandler(contextHandler);
         _server.start();
-        
+
         String multipart =  "--AaB03x\r\n"+
         "content-disposition: form-data; name=\"field1\"\r\n"+
         "\r\n"+
@@ -227,24 +262,87 @@ public class RequestTest
         "\r\n"+
         "000000000000000000000000000000000000000000000000000\r\n"+
         "--AaB03x--\r\n";
-        
+
         String request="GET /foo/x.html HTTP/1.1\r\n"+
         "Host: whatever\r\n"+
         "Content-Type: multipart/form-data; boundary=\"AaB03x\"\r\n"+
         "Content-Length: "+multipart.getBytes().length+"\r\n"+
+        "Connection: close\r\n"+
         "\r\n"+
         multipart;
 
         String responses=_connector.getResponses(request);
-        System.err.println(responses);
+        // System.err.println(responses);
         assertTrue(responses.startsWith("HTTP/1.1 200"));
     }
+    
+    @Test
+    public void testBadMultiPart() throws Exception
+    {        
+        //a bad multipart where one of the fields has no name
+        final File testTmpDir = File.createTempFile("badmptest", null);
+        if (testTmpDir.exists())
+            testTmpDir.delete();
+        testTmpDir.mkdir();
+        testTmpDir.deleteOnExit();
+        assertTrue(testTmpDir.list().length == 0);
+
+        ContextHandler contextHandler = new ContextHandler();
+        contextHandler.setContextPath("/foo");
+        contextHandler.setResourceBase(".");
+        contextHandler.setHandler(new BadMultiPartRequestHandler(testTmpDir));
+        contextHandler.addEventListener(new Request.MultiPartCleanerListener()
+        {
+
+            @Override
+            public void requestDestroyed(ServletRequestEvent sre)
+            {
+                MultiPartInputStreamParser m = (MultiPartInputStreamParser)sre.getServletRequest().getAttribute(Request.__MULTIPART_INPUT_STREAM);
+                ContextHandler.Context c = (ContextHandler.Context)sre.getServletRequest().getAttribute(Request.__MULTIPART_CONTEXT);
+                assertNotNull (m);
+                assertNotNull (c);
+                assertTrue(c == sre.getServletContext());
+                super.requestDestroyed(sre);
+                String[] files = testTmpDir.list();
+                assertTrue(files.length == 0);
+            }
+
+        });
+        _server.stop();
+        _server.setHandler(contextHandler);
+        _server.start();
+
+        String multipart =  "--AaB03x\r\n"+
+        "content-disposition: form-data; name=\"xxx\"\r\n"+
+        "\r\n"+
+        "Joe Blow\r\n"+
+        "--AaB03x\r\n"+
+        "content-disposition: form-data;  filename=\"foo.upload\"\r\n"+
+        "Content-Type: text/plain;charset=ISO-8859-1\r\n"+
+        "\r\n"+
+        "000000000000000000000000000000000000000000000000000\r\n"+
+        "--AaB03x--\r\n";
+
+        String request="GET /foo/x.html HTTP/1.1\r\n"+
+        "Host: whatever\r\n"+
+        "Content-Type: multipart/form-data; boundary=\"AaB03x\"\r\n"+
+        "Content-Length: "+multipart.getBytes().length+"\r\n"+
+        "Connection: close\r\n"+
+        "\r\n"+
+        multipart;
+
+        String responses=_connector.getResponses(request);
+        //System.err.println(responses);
+        assertTrue(responses.startsWith("HTTP/1.1 500"));
+    }
+
 
     @Test
     public void testBadUtf8ParamExtraction() throws Exception
     {
         _handler._checker = new RequestTester()
         {
+            @Override
             public boolean check(HttpServletRequest request,HttpServletResponse response)
             {
                 String value=request.getParameter("param");
@@ -257,8 +355,10 @@ public class RequestTest
         String request="GET /?param=aaa%E7bbb HTTP/1.1\r\n"+
         "Host: whatever\r\n"+
         "Content-Type: text/html;charset=utf8\n"+
+        "Connection: close\n"+
         "\n";
 
+        LOG.info("Expecting NotUtf8Exception byte 62 in state 3...");
         String responses=_connector.getResponses(request);
         assertTrue(responses.startsWith("HTTP/1.1 200"));
     }
@@ -267,30 +367,31 @@ public class RequestTest
     public void testInvalidHostHeader() throws Exception
     {
         // Use a contextHandler with vhosts to force call to Request.getServerName()
-        ContextHandler handler = new ContextHandler();
-        handler.addVirtualHosts(new String[1]);
+        ContextHandler context = new ContextHandler();
+        context.addVirtualHosts(new String[]{"something"});
         _server.stop();
-        _server.setHandler(handler);
+        _server.setHandler(context);
         _server.start();
 
+
         // Request with illegal Host header
-        String request="GET / HTTP/1.1\r\n"+
-        "Host: whatever.com:\r\n"+
+        String request="GET / HTTP/1.1\n"+
+        "Host: whatever.com:xxxx\n"+
         "Content-Type: text/html;charset=utf8\n"+
+        "Connection: close\n"+
         "\n";
 
         String responses=_connector.getResponses(request);
-        assertTrue("400 Bad Request response expected",responses.startsWith("HTTP/1.1 400"));
+        assertThat(responses,Matchers.startsWith("HTTP/1.1 400"));
     }
 
-
-
     @Test
     public void testContentTypeEncoding() throws Exception
     {
         final ArrayList<String> results = new ArrayList<String>();
         _handler._checker = new RequestTester()
         {
+            @Override
             public boolean check(HttpServletRequest request,HttpServletResponse response)
             {
                 results.add(request.getContentType());
@@ -318,6 +419,7 @@ public class RequestTest
                 "GET / HTTP/1.1\n"+
                 "Host: whatever\n"+
                 "Content-Type: text/html; other=foo ; blah=\"charset=wrong;\" ; charset =   \" x=z; \"   ; more=values \n"+
+                "Connection: close\n"+
                 "\n"
                 );
 
@@ -326,10 +428,10 @@ public class RequestTest
         assertEquals(null,results.get(i++));
 
         assertEquals("text/html;charset=utf8",results.get(i++));
-        assertEquals("utf8",results.get(i++));
+        assertEquals("UTF-8",results.get(i++));
 
         assertEquals("text/html; charset=\"utf8\"",results.get(i++));
-        assertEquals("utf8",results.get(i++));
+        assertEquals("UTF-8",results.get(i++));
 
         assertTrue(results.get(i++).startsWith("text/html"));
         assertEquals(" x=z; ",results.get(i++));
@@ -341,8 +443,10 @@ public class RequestTest
         final ArrayList<String> results = new ArrayList<String>();
         _handler._checker = new RequestTester()
         {
+            @Override
             public boolean check(HttpServletRequest request,HttpServletResponse response)
             {
+                results.add(request.getRequestURL().toString());
                 results.add(request.getRemoteAddr());
                 results.add(request.getServerName());
                 results.add(String.valueOf(request.getServerPort()));
@@ -350,117 +454,265 @@ public class RequestTest
             }
         };
 
-        _connector.getResponses(
+        results.clear();
+        String response=_connector.getResponses(
                 "GET / HTTP/1.1\n"+
                 "Host: myhost\n"+
-                "\n"+
+                "Connection: close\n"+
+                "\n");
+        int i=0;
+        assertThat(response,Matchers.containsString("200 OK"));
+        assertEquals("http://myhost/",results.get(i++));
+        assertEquals("0.0.0.0",results.get(i++));
+        assertEquals("myhost",results.get(i++));
+        assertEquals("80",results.get(i++));
+
 
+        results.clear();
+        response=_connector.getResponses(
                 "GET / HTTP/1.1\n"+
                 "Host: myhost:8888\n"+
-                "\n"+
+                "Connection: close\n"+
+                "\n");
+        i=0;
+        assertThat(response,Matchers.containsString("200 OK"));
+        assertEquals("http://myhost:8888/",results.get(i++));
+        assertEquals("0.0.0.0",results.get(i++));
+        assertEquals("myhost",results.get(i++));
+        assertEquals("8888",results.get(i++));
+        
+
+        results.clear();
+        response=_connector.getResponses(
+                "GET http://myhost:8888/ HTTP/1.0\n"+
+                "\n");
+        i=0;
+        assertThat(response,Matchers.containsString("200 OK"));
+        assertEquals("http://myhost:8888/",results.get(i++));
+        assertEquals("0.0.0.0",results.get(i++));
+        assertEquals("myhost",results.get(i++));
+        assertEquals("8888",results.get(i++));
+        
+        results.clear();
+        response=_connector.getResponses(
+                "GET http://myhost:8888/ HTTP/1.1\n"+
+                "Host: wrong:666\n"+
+                "Connection: close\n"+
+                "\n");
+        i=0;
+        assertThat(response,Matchers.containsString("200 OK"));
+        assertEquals("http://myhost:8888/",results.get(i++));
+        assertEquals("0.0.0.0",results.get(i++));
+        assertEquals("myhost",results.get(i++));
+        assertEquals("8888",results.get(i++));
+
 
+        results.clear();
+        response=_connector.getResponses(
                 "GET / HTTP/1.1\n"+
                 "Host: 1.2.3.4\n"+
-                "\n"+
+                "Connection: close\n"+
+                "\n");
+        i=0;
 
+        assertThat(response,Matchers.containsString("200 OK"));
+        assertEquals("http://1.2.3.4/",results.get(i++));
+        assertEquals("0.0.0.0",results.get(i++));
+        assertEquals("1.2.3.4",results.get(i++));
+        assertEquals("80",results.get(i++));
+
+
+        results.clear();
+        response=_connector.getResponses(
                 "GET / HTTP/1.1\n"+
                 "Host: 1.2.3.4:8888\n"+
-                "\n"+
+                "Connection: close\n"+
+                "\n");
+        i=0;
+        assertThat(response,Matchers.containsString("200 OK"));
+        assertEquals("http://1.2.3.4:8888/",results.get(i++));
+        assertEquals("0.0.0.0",results.get(i++));
+        assertEquals("1.2.3.4",results.get(i++));
+        assertEquals("8888",results.get(i++));
+
 
+        results.clear();
+        response=_connector.getResponses(
                 "GET / HTTP/1.1\n"+
                 "Host: [::1]\n"+
-                "\n"+
+                "Connection: close\n"+
+                "\n");
+        i=0;
+        assertThat(response,Matchers.containsString("200 OK"));
+        assertEquals("http://[::1]/",results.get(i++));
+        assertEquals("0.0.0.0",results.get(i++));
+        assertEquals("[::1]",results.get(i++));
+        assertEquals("80",results.get(i++));
+
 
+        results.clear();
+        response=_connector.getResponses(
                 "GET / HTTP/1.1\n"+
                 "Host: [::1]:8888\n"+
-                "\n"+
+                "Connection: close\n"+
+                "\n");
+        i=0;
+        assertThat(response,Matchers.containsString("200 OK"));
+        assertEquals("http://[::1]:8888/",results.get(i++));
+        assertEquals("0.0.0.0",results.get(i++));
+        assertEquals("[::1]",results.get(i++));
+        assertEquals("8888",results.get(i++));
+
 
+        results.clear();
+        response=_connector.getResponses(
                 "GET / HTTP/1.1\n"+
                 "Host: [::1]\n"+
                 "x-forwarded-for: remote\n"+
                 "x-forwarded-proto: https\n"+
-                "\n"+
+                "Connection: close\n"+
+                "\n");
+        i=0;
+        assertThat(response,Matchers.containsString("200 OK"));
+        assertEquals("https://[::1]/",results.get(i++));
+        assertEquals("remote",results.get(i++));
+        assertEquals("[::1]",results.get(i++));
+        assertEquals("443",results.get(i++));
+
 
+        results.clear();
+        response=_connector.getResponses(
                 "GET / HTTP/1.1\n"+
                 "Host: [::1]:8888\n"+
+                "Connection: close\n"+
                 "x-forwarded-for: remote\n"+
                 "x-forwarded-proto: https\n"+
-                "\n"
-                );
-
-        int i=0;
-        assertEquals(null,results.get(i++));
-        assertEquals("myhost",results.get(i++));
-        assertEquals("80",results.get(i++));
-        assertEquals(null,results.get(i++));
-        assertEquals("myhost",results.get(i++));
-        assertEquals("8888",results.get(i++));
-        assertEquals(null,results.get(i++));
-        assertEquals("1.2.3.4",results.get(i++));
-        assertEquals("80",results.get(i++));
-        assertEquals(null,results.get(i++));
-        assertEquals("1.2.3.4",results.get(i++));
-        assertEquals("8888",results.get(i++));
-        assertEquals(null,results.get(i++));
-        assertEquals("[::1]",results.get(i++));
-        assertEquals("80",results.get(i++));
-        assertEquals(null,results.get(i++));
-        assertEquals("[::1]",results.get(i++));
-        assertEquals("8888",results.get(i++));
-        assertEquals("remote",results.get(i++));
-        assertEquals("[::1]",results.get(i++));
-        assertEquals("443",results.get(i++));
+                "\n");
+        i=0;
+        assertThat(response,Matchers.containsString("200 OK"));
+        assertEquals("https://[::1]:8888/",results.get(i++));
         assertEquals("remote",results.get(i++));
         assertEquals("[::1]",results.get(i++));
         assertEquals("8888",results.get(i++));
-
     }
 
     @Test
     public void testContent() throws Exception
     {
-        final int[] length=new int[1];
+        final AtomicInteger length=new AtomicInteger();
 
         _handler._checker = new RequestTester()
         {
-            public boolean check(HttpServletRequest request,HttpServletResponse response)
+            @Override
+            public boolean check(HttpServletRequest request,HttpServletResponse response) throws IOException
             {
-                assertEquals(request.getContentLength(), ((Request)request).getContentRead());
-                length[0]=request.getContentLength();
+                int len=request.getContentLength();
+                ServletInputStream in = request.getInputStream();
+                for (int i=0;i<len;i++)
+                {
+                    int b=in.read();
+                    if (b<0)
+                        return false;
+                }
+                if (in.read()>0)
+                    return false;
+
+                length.set(len);
                 return true;
             }
         };
 
+
         String content="";
 
-        for (int l=0;l<1025;l++)
+        for (int l=0;l<1024;l++)
         {
             String request="POST / HTTP/1.1\r\n"+
             "Host: whatever\r\n"+
-            "Content-Type: text/test\r\n"+
+            "Content-Type: multipart/form-data-test\r\n"+
             "Content-Length: "+l+"\r\n"+
             "Connection: close\r\n"+
             "\r\n"+
             content;
+            Log.getRootLogger().debug("test l={}",l);
             String response = _connector.getResponses(request);
-            assertEquals(l,length[0]);
-            if (l>0)
-                assertEquals(l,_handler._content.length());
+            Log.getRootLogger().debug(response);
+            assertThat(response,Matchers.containsString(" 200 OK"));
+            assertEquals(l,length.get());
             content+="x";
         }
     }
 
     @Test
+    public void test8859EncodedForm() throws Exception
+    {
+        _handler._checker = new RequestTester()
+        {
+            @Override
+            public boolean check(HttpServletRequest request,HttpServletResponse response) throws IOException
+            {
+                // Should be "testä"
+                // "test" followed by a LATIN SMALL LETTER A WITH DIAERESIS
+                request.setCharacterEncoding(StandardCharsets.ISO_8859_1.name());
+                String actual = request.getParameter("name2");
+                return "test\u00e4".equals(actual);
+            }
+        };
+
+
+        String content="name1=test&name2=test%E4&name3=&name4=test";
+        String request="POST / HTTP/1.1\r\n"+
+            "Host: whatever\r\n"+
+            "Content-Type: "+MimeTypes.Type.FORM_ENCODED.asString()+"\r\n" +
+            "Content-Length: "+content.length()+"\r\n"+
+            "Connection: close\r\n"+
+            "\r\n"+
+            content;
+        String response = _connector.getResponses(request);
+        assertThat(response,Matchers.containsString(" 200 OK"));
+    }
+    
+    @Test
+    public void testUTF8EncodedForm() throws Exception
+    {
+        _handler._checker = new RequestTester()
+        {
+            @Override
+            public boolean check(HttpServletRequest request,HttpServletResponse response) throws IOException
+            {
+                // http://www.ltg.ed.ac.uk/~richard/utf-8.cgi?input=00e4&mode=hex
+                // Should be "testä"
+                // "test" followed by a LATIN SMALL LETTER A WITH DIAERESIS
+                String actual = request.getParameter("name2");
+                return "test\u00e4".equals(actual);
+            }
+        };
+
+        String content="name1=test&name2=test%C3%A4&name3=&name4=test";
+        String request="POST / HTTP/1.1\r\n"+
+            "Host: whatever\r\n"+
+            "Content-Type: "+MimeTypes.Type.FORM_ENCODED.asString()+"\r\n" +
+            "Content-Length: "+content.length()+"\r\n"+
+            "Connection: close\r\n"+
+            "\r\n"+
+            content;
+        String response = _connector.getResponses(request);
+        assertThat(response,Matchers.containsString(" 200 OK"));
+    }
+    
+    
+    @Test
     public void testPartialRead() throws Exception
     {
         Handler handler = new AbstractHandler()
         {
+            @Override
             public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException,
                     ServletException
             {
                 baseRequest.setHandled(true);
                 Reader reader=request.getReader();
-                byte[] b=("read="+reader.read()+"\n").getBytes(StringUtil.__UTF8);
+                byte[] b=("read="+reader.read()+"\n").getBytes(StandardCharsets.UTF_8);
                 response.setContentLength(b.length);
                 response.getOutputStream().write(b);
                 response.flushBuffer();
@@ -500,6 +752,7 @@ public class RequestTest
     {
         Handler handler = new AbstractHandler()
         {
+            @Override
             public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException,
             ServletException
             {
@@ -508,7 +761,7 @@ public class RequestTest
                 String in = IO.toString(reader);
                 String param = request.getParameter("param");
 
-                byte[] b=("read='"+in+"' param="+param+"\n").getBytes(StringUtil.__UTF8);
+                byte[] b=("read='"+in+"' param="+param+"\n").getBytes(StandardCharsets.UTF_8);
                 response.setContentLength(b.length);
                 response.getOutputStream().write(b);
                 response.flushBuffer();
@@ -531,18 +784,56 @@ public class RequestTest
         assertTrue(responses.indexOf("read='param=wrong' param=right")>0);
 
     }
+    
+    @Test
+    public void testSessionAfterRedirect() throws Exception
+    { 
+        Handler handler = new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException,
+            ServletException
+            {
+                baseRequest.setHandled(true);
+                response.sendRedirect("/foo");
+                try
+                {
+                    request.getSession(true);
+                    fail("Session should not be created after response committed");
+                }
+                catch (IllegalStateException e)
+                {
+                    //expected
+                }
+                catch (Exception e)
+                {
+                    fail("Session creation after response commit should throw IllegalStateException");
+                }
+            }
+        };
+        _server.stop();
+        _server.setHandler(handler);
+        _server.start();
+        String response=_connector.getResponses("GET / HTTP/1.1\n"+
+                                                "Host: myhost\n"+
+                                                "Connection: close\n"+
+                                                "\n");
+        assertThat(response,Matchers.containsString(" 302 Found"));
+        assertThat(response,Matchers.containsString("Location: http://myhost/foo"));
+    }
 
     @Test
     public void testPartialInput() throws Exception
     {
         Handler handler = new AbstractHandler()
         {
+            @Override
             public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException,
             ServletException
             {
                 baseRequest.setHandled(true);
                 InputStream in=request.getInputStream();
-                byte[] b=("read="+in.read()+"\n").getBytes(StringUtil.__UTF8);
+                byte[] b=("read="+in.read()+"\n").getBytes(StandardCharsets.UTF_8);
                 response.setContentLength(b.length);
                 response.getOutputStream().write(b);
                 response.flushBuffer();
@@ -583,6 +874,7 @@ public class RequestTest
 
         _handler._checker = new RequestTester()
         {
+            @Override
             public boolean check(HttpServletRequest request,HttpServletResponse response) throws IOException
             {
                 response.getOutputStream().println("Hello World");
@@ -593,7 +885,8 @@ public class RequestTest
         response=_connector.getResponses(
                     "GET / HTTP/1.1\n"+
                     "Host: whatever\n"+
-                    "\n"
+                    "\n",
+                    200, TimeUnit.MILLISECONDS
                     );
         assertTrue(response.indexOf("200")>0);
         assertFalse(response.indexOf("Connection: close")>0);
@@ -642,7 +935,8 @@ public class RequestTest
                     "GET / HTTP/1.0\n"+
                     "Host: whatever\n"+
                     "Connection: Other,,keep-alive\n"+
-                    "\n"
+                    "\n",
+                    200, TimeUnit.MILLISECONDS
                     );
         assertTrue(response.indexOf("200")>0);
         assertTrue(response.indexOf("Connection: keep-alive")>0);
@@ -650,6 +944,7 @@ public class RequestTest
 
         _handler._checker = new RequestTester()
         {
+            @Override
             public boolean check(HttpServletRequest request,HttpServletResponse response) throws IOException
             {
                 response.setHeader("Connection","TE");
@@ -662,7 +957,8 @@ public class RequestTest
         response=_connector.getResponses(
                     "GET / HTTP/1.1\n"+
                     "Host: whatever\n"+
-                    "\n"
+                    "\n",
+                    200, TimeUnit.MILLISECONDS
                     );
         assertTrue(response.indexOf("200")>0);
         assertTrue(response.indexOf("Connection: TE,Other")>0);
@@ -674,9 +970,9 @@ public class RequestTest
                     "Connection: close\n"+
                     "\n"
                     );
-        assertTrue(response.indexOf("200")>0);
-        assertTrue(response.indexOf("Connection: close")>0);
-        assertTrue(response.indexOf("Hello World")>0);
+        assertThat(response,Matchers.containsString("200 OK"));
+        assertThat(response,Matchers.containsString("Connection: close"));
+        assertThat(response,Matchers.containsString("Hello World"));
     }
 
     @Test
@@ -686,6 +982,7 @@ public class RequestTest
 
         _handler._checker = new RequestTester()
         {
+            @Override
             public boolean check(HttpServletRequest request,HttpServletResponse response) throws IOException
             {
                 javax.servlet.http.Cookie[] ca = request.getCookies();
@@ -702,6 +999,7 @@ public class RequestTest
         response=_connector.getResponses(
                     "GET / HTTP/1.1\n"+
                     "Host: whatever\n"+
+                    "Connection: close\n"+
                     "\n"
                     );
         assertTrue(response.startsWith("HTTP/1.1 200 OK"));
@@ -713,6 +1011,7 @@ public class RequestTest
                     "GET / HTTP/1.1\n"+
                     "Host: whatever\n"+
                     "Cookie: name=quoted=\\\"value\\\"\n" +
+                    "Connection: close\n"+
                     "\n"
         );
         assertTrue(response.startsWith("HTTP/1.1 200 OK"));
@@ -725,6 +1024,7 @@ public class RequestTest
                 "GET / HTTP/1.1\n"+
                 "Host: whatever\n"+
                 "Cookie: name=value; other=\"quoted=;value\"\n" +
+                "Connection: close\n"+
                 "\n"
         );
         assertTrue(response.startsWith("HTTP/1.1 200 OK"));
@@ -734,7 +1034,6 @@ public class RequestTest
         assertEquals("other", cookies.get(1).getName());
         assertEquals("quoted=;value", cookies.get(1).getValue());
 
-
         cookies.clear();
         response=_connector.getResponses(
                 "GET /other HTTP/1.1\n"+
@@ -746,9 +1045,11 @@ public class RequestTest
                 "Host: whatever\n"+
                 "Other: header\n"+
                 "Cookie: name=value; other=\"quoted=;value\"\n" +
+                "Connection: close\n"+
                 "\n"
         );
-        assertTrue(response.startsWith("HTTP/1.1 200 OK"));
+        assertThat(response,startsWith("HTTP/1.1 200 OK"));
+        assertThat(response.substring(15),containsString("HTTP/1.1 200 OK"));
         assertEquals(4,cookies.size());
         assertEquals("name", cookies.get(0).getName());
         assertEquals("value", cookies.get(0).getValue());
@@ -769,9 +1070,11 @@ public class RequestTest
                 "Host: whatever\n"+
                 "Other: header\n"+
                 "Cookie: name=value; other=\"othervalue\"\n" +
+                "Connection: close\n"+
                 "\n"
         );
-        assertTrue(response.startsWith("HTTP/1.1 200 OK"));
+        assertThat(response,startsWith("HTTP/1.1 200 OK"));
+        assertThat(response.substring(15),containsString("HTTP/1.1 200 OK"));
         assertEquals(4,cookies.size());
         assertEquals("name", cookies.get(0).getName());
         assertEquals("value", cookies.get(0).getValue());
@@ -820,6 +1123,7 @@ public class RequestTest
                         "Host: whatever\n"+
                         "Other: header\n"+
                         "Cookie: __utmz=14316.133020.1.1.utr=gna.de|ucn=(real)|utd=reral|utct=/games/hen-one,gnt-50-ba-keys:key,2072262.html\n"+
+                        "Connection: close\n"+
                         "\n"
                 );
         assertTrue(response.startsWith("HTTP/1.1 200 OK"));
@@ -836,6 +1140,7 @@ public class RequestTest
 
         _handler._checker = new RequestTester()
         {
+            @Override
             public boolean check(HttpServletRequest request,HttpServletResponse response)
             {
                 for (int i=0;i<cookie.length; i++)
@@ -902,32 +1207,87 @@ public class RequestTest
 
 
     @Test
-    public void testHashDOS() throws Exception
+    public void testHashDOSKeys() throws Exception
     {
+        ((StdErrLog)Log.getLogger(HttpChannel.class)).setHideStacks(true);
+        LOG.info("Expecting maxFormKeys limit and Closing HttpParser exceptions...");
         _server.setAttribute("org.eclipse.jetty.server.Request.maxFormContentSize",-1);
         _server.setAttribute("org.eclipse.jetty.server.Request.maxFormKeys",1000);
 
-        // This file is not distributed - as it is dangerous
+
+        StringBuilder buf = new StringBuilder(4000000);
+        buf.append("a=b");
+
+        // The evil keys file is not distributed - as it is dangerous
         File evil_keys = new File("/tmp/keys_mapping_to_zero_2m");
-        if (!evil_keys.exists())
+        if (evil_keys.exists())
+        {
+            LOG.info("Using real evil keys!");
+            try (BufferedReader in = new BufferedReader(new FileReader(evil_keys)))
+            {
+                String key=null;
+                while((key=in.readLine())!=null)
+                    buf.append("&").append(key).append("=").append("x");
+            }
+        }
+        else
         {
-            Log.info("testHashDOS skipped");
-            return;
+            // we will just create a lot of keys and make sure the limit is applied
+            for (int i=0;i<2000;i++)
+                buf.append("&").append("K").append(i).append("=").append("x");
         }
+        buf.append("&c=d");
 
-        BufferedReader in = new BufferedReader(new FileReader(evil_keys));
-        StringBuilder buf = new StringBuilder(4000000);
 
-        String key=null;
-        buf.append("a=b");
-        while((key=in.readLine())!=null)
+        _handler._checker = new RequestTester()
         {
-            buf.append("&").append(key).append("=").append("x");
+            @Override
+            public boolean check(HttpServletRequest request,HttpServletResponse response)
+            {
+                return "b".equals(request.getParameter("a")) && request.getParameter("c")==null;
+            }
+        };
+
+        String request="POST / HTTP/1.1\r\n"+
+        "Host: whatever\r\n"+
+        "Content-Type: "+MimeTypes.Type.FORM_ENCODED.asString()+"\r\n"+
+        "Content-Length: "+buf.length()+"\r\n"+
+        "Connection: close\r\n"+
+        "\r\n"+
+        buf;
+
+        try
+        {
+            long start=System.currentTimeMillis();
+            String response = _connector.getResponses(request);
+            assertTrue(response.contains("Form too many keys"));
+            long now=System.currentTimeMillis();
+            assertTrue((now-start)<5000);
         }
+        finally
+        {
+            ((StdErrLog)Log.getLogger(HttpChannel.class)).setHideStacks(false);
+        }
+    }
+    
+    @Test
+    public void testHashDOSSize() throws Exception
+    {
+        ((StdErrLog)Log.getLogger(HttpChannel.class)).setHideStacks(true);
+        LOG.info("Expecting maxFormSize limit and too much data exceptions...");
+        _server.setAttribute("org.eclipse.jetty.server.Request.maxFormContentSize",3396);
+        _server.setAttribute("org.eclipse.jetty.server.Request.maxFormKeys",1000);
+
+        StringBuilder buf = new StringBuilder(4000000);
+        buf.append("a=b");
+        // we will just create a lot of keys and make sure the limit is applied
+        for (int i=0;i<500;i++)
+            buf.append("&").append("K").append(i).append("=").append("x");
         buf.append("&c=d");
 
         _handler._checker = new RequestTester()
         {
+            @Override
             public boolean check(HttpServletRequest request,HttpServletResponse response)
             {
                 return "b".equals(request.getParameter("a")) && request.getParameter("c")==null;
@@ -936,19 +1296,32 @@ public class RequestTest
 
         String request="POST / HTTP/1.1\r\n"+
         "Host: whatever\r\n"+
-        "Content-Type: "+MimeTypes.FORM_ENCODED+"\r\n"+
+        "Content-Type: "+MimeTypes.Type.FORM_ENCODED.asString()+"\r\n"+
         "Content-Length: "+buf.length()+"\r\n"+
         "Connection: close\r\n"+
         "\r\n"+
         buf;
 
-        long start=System.currentTimeMillis();
-        String response = _connector.getResponses(request);
-        assertTrue(response.contains("200 OK"));
-        long now=System.currentTimeMillis();
-        assertTrue((now-start)<5000);
+        try
+        {
+            long start=System.currentTimeMillis();
+            String response = _connector.getResponses(request);
+            assertTrue(response.contains("Form too large:"));
+            long now=System.currentTimeMillis();
+            assertTrue((now-start)<5000);
+        }
+        finally
+        {
+            ((StdErrLog)Log.getLogger(HttpChannel.class)).setHideStacks(false);
+        }
     }
 
+    @Test(expected = UnsupportedEncodingException.class)
+    public void testNotSupportedCharacterEncoding() throws UnsupportedEncodingException
+    {
+        Request request = new Request(null, null);
+        request.setCharacterEncoding("doesNotExist");
+    }
 
     interface RequestTester
     {
@@ -958,14 +1331,16 @@ public class RequestTest
     private class RequestHandler extends AbstractHandler
     {
         private RequestTester _checker;
+        @SuppressWarnings("unused")
         private String _content;
 
+        @Override
         public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
         {
             ((Request)request).setHandled(true);
 
-            if (request.getContentLength()>0 
-                    && !MimeTypes.FORM_ENCODED.equals(request.getContentType()) 
+            if (request.getContentLength()>0
+                    && !MimeTypes.Type.FORM_ENCODED.asString().equals(request.getContentType())
                     && !request.getContentType().startsWith("multipart/form-data"))
                 _content=IO.toString(request.getInputStream());
 
@@ -973,34 +1348,32 @@ public class RequestTest
                 response.setStatus(200);
             else
                 response.sendError(500);
-
-
         }
     }
-    
+
     private class MultiPartRequestHandler extends AbstractHandler
     {
         File tmpDir;
-        
+
         public MultiPartRequestHandler(File tmpDir)
         {
             this.tmpDir = tmpDir;
         }
-        
-        
+
+
         @Override
         public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
         {
             ((Request)request).setHandled(true);
             try
-            { 
+            {
 
                 MultipartConfigElement mpce = new MultipartConfigElement(tmpDir.getAbsolutePath(),-1, -1, 2);
                 request.setAttribute(Request.__MULTIPART_CONFIG_ELEMENT, mpce);
-               
+
                 String field1 = request.getParameter("field1");
                 assertNotNull(field1);
-                
+
                 Part foo = request.getPart("stuff");
                 assertNotNull(foo);
                 assertTrue(foo.getSize() > 0);
@@ -1018,4 +1391,38 @@ public class RequestTest
             }
         }
     }
+    
+    private class BadMultiPartRequestHandler extends AbstractHandler
+    {
+        File tmpDir;
+
+        public  BadMultiPartRequestHandler(File tmpDir)
+        {
+            this.tmpDir = tmpDir;
+        }
+
+
+        @Override
+        public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+        {
+            ((Request)request).setHandled(true);
+            try
+            {
+
+                MultipartConfigElement mpce = new MultipartConfigElement(tmpDir.getAbsolutePath(),-1, -1, 2);
+                request.setAttribute(Request.__MULTIPART_CONFIG_ELEMENT, mpce);
+
+                //We should get an error when we getParams if there was a problem parsing the multipart
+                String field1 = request.getParameter("xxx");
+                //A 200 response is actually wrong here
+            }
+            catch (RuntimeException e)
+            {
+                response.sendError(500);
+            }
+           
+        }
+    }
+    
+    
 }
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ResourceCacheTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ResourceCacheTest.java
index 693edc9..5ed9afa 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/ResourceCacheTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ResourceCacheTest.java
@@ -25,9 +25,11 @@ import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.InputStreamReader;
+import java.io.OutputStream;
 
 import org.eclipse.jetty.http.HttpContent;
 import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.util.BufferUtil;
 import org.eclipse.jetty.util.resource.Resource;
 import org.eclipse.jetty.util.resource.ResourceCollection;
 import org.junit.Test;
@@ -42,26 +44,26 @@ public class ResourceCacheTest
                 "../jetty-util/src/test/resources/org/eclipse/jetty/util/resource/two/",
                 "../jetty-util/src/test/resources/org/eclipse/jetty/util/resource/three/"
         });
-        
+
         Resource[] r = rc.getResources();
         MimeTypes mime = new MimeTypes();
-        
+
         ResourceCache rc3 = new ResourceCache(null,r[2],mime,false,false);
         ResourceCache rc2 = new ResourceCache(rc3,r[1],mime,false,false);
         ResourceCache rc1 = new ResourceCache(rc2,r[0],mime,false,false);
-        
+
         assertEquals("1 - one", getContent(rc1, "1.txt"));
         assertEquals("2 - two", getContent(rc1, "2.txt"));
-        assertEquals("3 - three", getContent(rc1, "3.txt"));    
-        
+        assertEquals("3 - three", getContent(rc1, "3.txt"));
+
         assertEquals("1 - two", getContent(rc2, "1.txt"));
         assertEquals("2 - two", getContent(rc2, "2.txt"));
-        assertEquals("3 - three", getContent(rc2, "3.txt"));   
-        
+        assertEquals("3 - three", getContent(rc2, "3.txt"));
+
         assertEquals(null, getContent(rc3, "1.txt"));
         assertEquals("2 - three", getContent(rc3, "2.txt"));
-        assertEquals("3 - three", getContent(rc3, "3.txt"));        
-        
+        assertEquals("3 - three", getContent(rc3, "3.txt"));
+
     }
 
     @Test
@@ -72,10 +74,10 @@ public class ResourceCacheTest
                 "../jetty-util/src/test/resources/org/eclipse/jetty/util/resource/two/",
                 "../jetty-util/src/test/resources/org/eclipse/jetty/util/resource/three/"
         });
-        
+
         Resource[] r = rc.getResources();
         MimeTypes mime = new MimeTypes();
-        
+
         ResourceCache rc3 = new ResourceCache(null,r[2],mime,false,false);
         ResourceCache rc2 = new ResourceCache(rc3,r[1],mime,false,false)
         {
@@ -85,53 +87,54 @@ public class ResourceCacheTest
                 return super.isCacheable(resource) && resource.getName().indexOf("2.txt")<0;
             }
         };
-        
+
         ResourceCache rc1 = new ResourceCache(rc2,r[0],mime,false,false);
-        
+
         assertEquals("1 - one", getContent(rc1, "1.txt"));
         assertEquals("2 - two", getContent(rc1, "2.txt"));
-        assertEquals("3 - three", getContent(rc1, "3.txt"));    
-        
+        assertEquals("3 - three", getContent(rc1, "3.txt"));
+
         assertEquals("1 - two", getContent(rc2, "1.txt"));
         assertEquals("2 - two", getContent(rc2, "2.txt"));
-        assertEquals("3 - three", getContent(rc2, "3.txt"));   
-        
+        assertEquals("3 - three", getContent(rc2, "3.txt"));
+
         assertEquals(null, getContent(rc3, "1.txt"));
         assertEquals("2 - three", getContent(rc3, "2.txt"));
-        assertEquals("3 - three", getContent(rc3, "3.txt"));        
-        
+        assertEquals("3 - three", getContent(rc3, "3.txt"));
+
     }
-    
-    
+
+
     @Test
     public void testResourceCache() throws Exception
     {
         final Resource directory;
         File[] files=new File[10];
         String[] names=new String[files.length];
-        ResourceCache cache; 
-        
+        ResourceCache cache;
+
         for (int i=0;i<files.length;i++)
         {
             files[i]=File.createTempFile("R-"+i+"-",".txt");
             files[i].deleteOnExit();
             names[i]=files[i].getName();
-            FileOutputStream out = new FileOutputStream(files[i]);
-            for (int j=0;j<(i*10-1);j++)
-                out.write(' ');
-            out.write('\n');
-            out.close();
+            try (OutputStream out = new FileOutputStream(files[i]))
+            {
+                for (int j=0;j<(i*10-1);j++)
+                    out.write(' ');
+                out.write('\n');
+            }
         }
 
         directory=Resource.newResource(files[0].getParentFile().getAbsolutePath());
 
-        
+
         cache=new ResourceCache(null,directory,new MimeTypes(),false,false);
-        
+
         cache.setMaxCacheSize(95);
         cache.setMaxCachedFileSize(85);
         cache.setMaxCachedFiles(4);
-        
+
         assertTrue(cache.lookup("does not exist")==null);
         assertTrue(cache.lookup(names[9]) instanceof HttpContent.ResourceAsHttpContent);
 
@@ -144,107 +147,126 @@ public class ResourceCacheTest
         assertEquals(1,cache.getCachedFiles());
 
         Thread.sleep(200);
-        
+
         content=cache.lookup(names[1]);
         assertEquals(90,cache.getCachedSize());
         assertEquals(2,cache.getCachedFiles());
 
         Thread.sleep(200);
-        
+
         content=cache.lookup(names[2]);
         assertEquals(30,cache.getCachedSize());
         assertEquals(2,cache.getCachedFiles());
 
         Thread.sleep(200);
-        
+
         content=cache.lookup(names[3]);
         assertEquals(60,cache.getCachedSize());
         assertEquals(3,cache.getCachedFiles());
 
         Thread.sleep(200);
-        
+
         content=cache.lookup(names[4]);
         assertEquals(90,cache.getCachedSize());
         assertEquals(3,cache.getCachedFiles());
 
         Thread.sleep(200);
-        
+
         content=cache.lookup(names[5]);
         assertEquals(90,cache.getCachedSize());
         assertEquals(2,cache.getCachedFiles());
 
         Thread.sleep(200);
-        
+
         content=cache.lookup(names[6]);
         assertEquals(60,cache.getCachedSize());
         assertEquals(1,cache.getCachedFiles());
 
         Thread.sleep(200);
-        
-        FileOutputStream out = new FileOutputStream(files[6]);
-        out.write(' ');
-        out.close();
+
+        try (OutputStream out = new FileOutputStream(files[6]))
+        {
+            out.write(' ');
+        }
         content=cache.lookup(names[7]);
         assertEquals(70,cache.getCachedSize());
         assertEquals(1,cache.getCachedFiles());
 
         Thread.sleep(200);
-        
+
         content=cache.lookup(names[6]);
         assertEquals(71,cache.getCachedSize());
         assertEquals(2,cache.getCachedFiles());
 
         Thread.sleep(200);
-        
+
         content=cache.lookup(names[0]);
         assertEquals(72,cache.getCachedSize());
         assertEquals(3,cache.getCachedFiles());
 
         Thread.sleep(200);
-        
+
         content=cache.lookup(names[1]);
         assertEquals(82,cache.getCachedSize());
         assertEquals(4,cache.getCachedFiles());
 
         Thread.sleep(200);
-        
+
         content=cache.lookup(names[2]);
         assertEquals(32,cache.getCachedSize());
         assertEquals(4,cache.getCachedFiles());
 
         Thread.sleep(200);
-        
+
         content=cache.lookup(names[3]);
         assertEquals(61,cache.getCachedSize());
         assertEquals(4,cache.getCachedFiles());
 
         Thread.sleep(200);
-        
+
         cache.flushCache();
         assertEquals(0,cache.getCachedSize());
         assertEquals(0,cache.getCachedFiles());
-        
+
 
         cache.flushCache();
     }
 
+    @Test
+    public void testNoextension() throws Exception
+    {
+        ResourceCollection rc = new ResourceCollection(new String[]{
+                "../jetty-util/src/test/resources/org/eclipse/jetty/util/resource/four/"
+        });
+
+        Resource[] resources = rc.getResources();
+        MimeTypes mime = new MimeTypes();
+
+        ResourceCache cache = new ResourceCache(null,resources[0],mime,false,false);
+
+        assertEquals("4 - four", getContent(cache, "four.txt"));
+        assertEquals("4 - four (no extension)", getContent(cache, "four"));
+    }
+
+    
     static String getContent(Resource r, String path) throws Exception
     {
         StringBuilder buffer = new StringBuilder();
         String line = null;
-        BufferedReader br = new BufferedReader(new InputStreamReader(r.addPath(path).getURL().openStream()));
-        while((line=br.readLine())!=null)
-            buffer.append(line);
-        br.close();        
+        try (BufferedReader br = new BufferedReader(new InputStreamReader(r.addPath(path).getURL().openStream())))
+        {
+            while((line=br.readLine())!=null)
+                buffer.append(line);
+        }
         return buffer.toString();
     }
-    
+
     static String getContent(ResourceCache rc, String path) throws Exception
     {
         HttpContent content = rc.lookup(path);
         if (content==null)
             return null;
-        
-        return content.getIndirectBuffer().toString();
+
+        return BufferUtil.toString(content.getIndirectBuffer());
     }
 }
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ResponseTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ResponseTest.java
index 12ea825..4bd9597 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/ResponseTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ResponseTest.java
@@ -21,6 +21,7 @@ package org.eclipse.jetty.server;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
@@ -29,126 +30,165 @@ import java.io.InputStreamReader;
 import java.io.LineNumberReader;
 import java.io.PrintWriter;
 import java.net.Socket;
+import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Enumeration;
 import java.util.Iterator;
 import java.util.Locale;
-import java.util.Map;
 
-import javax.servlet.ServletContext;
 import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
 import javax.servlet.http.Cookie;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
-import javax.servlet.http.HttpSessionContext;
 
-import org.eclipse.jetty.http.Generator;
-import org.eclipse.jetty.http.HttpHeaders;
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpGenerator.ResponseInfo;
+import org.eclipse.jetty.http.HttpHeader;
 import org.eclipse.jetty.http.HttpURI;
-import org.eclipse.jetty.http.Parser;
-import org.eclipse.jetty.io.ByteArrayBuffer;
+import org.eclipse.jetty.io.AbstractEndPoint;
 import org.eclipse.jetty.io.ByteArrayEndPoint;
-import org.eclipse.jetty.io.Connection;
-import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.server.bio.SocketConnector;
 import org.eclipse.jetty.server.handler.AbstractHandler;
 import org.eclipse.jetty.server.handler.ContextHandler;
-import org.eclipse.jetty.server.session.AbstractSession;
-import org.eclipse.jetty.server.session.AbstractSessionManager;
 import org.eclipse.jetty.server.session.HashSessionIdManager;
 import org.eclipse.jetty.server.session.HashSessionManager;
+import org.eclipse.jetty.server.session.HashedSession;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.thread.Scheduler;
+import org.eclipse.jetty.util.thread.TimerScheduler;
+import org.hamcrest.Matchers;
 import org.junit.After;
+import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 
-/**
- *
- */
 public class ResponseTest
 {
-    private Server server;
-    private LocalConnector connector;
-    
+    private Server _server;
+    private HttpChannel<ByteBuffer> _channel;
+
     @Before
     public void init() throws Exception
     {
-        server = new Server();
-        connector = new LocalConnector();
-        server.addConnector(connector);
-        server.setHandler(new DumpHandler());
-        server.start();
+        _server = new Server();
+        Scheduler _scheduler = new TimerScheduler();
+        HttpConfiguration config = new HttpConfiguration();
+        LocalConnector connector = new LocalConnector(_server,null, _scheduler,null,1,new HttpConnectionFactory(config));
+        _server.addConnector(connector);
+        _server.setHandler(new DumpHandler());
+        _server.start();
+
+        AbstractEndPoint endp = new ByteArrayEndPoint(_scheduler, 5000);
+        ByteBufferQueuedHttpInput input = new ByteBufferQueuedHttpInput();
+        _channel = new HttpChannel<ByteBuffer>(connector, new HttpConfiguration(), endp, new HttpTransport()
+        {
+            @Override
+            public void send(ResponseInfo info, ByteBuffer content, boolean lastContent, Callback callback)
+            {
+                callback.succeeded();
+            }
+            
+            @Override
+            public void send(ByteBuffer responseBodyContent, boolean lastContent, Callback callback)
+            {
+                send(null,responseBodyContent, lastContent, callback);
+            }
+
+            @Override
+            public void completed()
+            {
+            }
+
+            @Override
+            public void abort()
+            {
+                
+            }
+
+        }, input);
     }
 
     @After
     public void destroy() throws Exception
     {
-        server.stop();
-        server.join();
+        _server.stop();
+        _server.join();
     }
 
     @Test
     public void testContentType() throws Exception
     {
-        AbstractHttpConnection connection = new TestHttpConnection(connector,new ByteArrayEndPoint(), connector.getServer());
-        Response response = connection.getResponse();
+        Response response = newResponse();
 
-        assertEquals(null,response.getContentType());
+        assertEquals(null, response.getContentType());
+
+        response.setHeader("Content-Type", "text/something");
+        assertEquals("text/something", response.getContentType());
 
-        response.setHeader("Content-Type","text/something");
-        assertEquals("text/something",response.getContentType());
-        
         response.setContentType("foo/bar");
-        assertEquals("foo/bar",response.getContentType());
+        assertEquals("foo/bar", response.getContentType());
         response.getWriter();
-        assertEquals("foo/bar;charset=ISO-8859-1",response.getContentType());
+        assertEquals("foo/bar; charset=ISO-8859-1", response.getContentType());
         response.setContentType("foo2/bar2");
-        assertEquals("foo2/bar2;charset=ISO-8859-1",response.getContentType());
-        response.setHeader("name","foo");
+        assertEquals("foo2/bar2; charset=ISO-8859-1", response.getContentType());
+        response.setHeader("name", "foo");
 
         Iterator<String> en = response.getHeaders("name").iterator();
-        assertEquals("foo",en.next());
+        assertEquals("foo", en.next());
         assertFalse(en.hasNext());
-        response.addHeader("name","bar");
-        en=response.getHeaders("name").iterator();
-        assertEquals("foo",en.next());
-        assertEquals("bar",en.next());
+        response.addHeader("name", "bar");
+        en = response.getHeaders("name").iterator();
+        assertEquals("foo", en.next());
+        assertEquals("bar", en.next());
         assertFalse(en.hasNext());
 
         response.recycle();
 
         response.setContentType("text/html");
-        assertEquals("text/html",response.getContentType());
+        assertEquals("text/html", response.getContentType());
         response.getWriter();
-        assertEquals("text/html;charset=ISO-8859-1",response.getContentType());
+        assertEquals("text/html; charset=ISO-8859-1", response.getContentType());
         response.setContentType("foo2/bar2");
-        assertEquals("foo2/bar2;charset=ISO-8859-1",response.getContentType());
+        assertEquals("foo2/bar2; charset=ISO-8859-1", response.getContentType());
 
         response.recycle();
         response.setContentType("text/xml;charset=ISO-8859-7");
         response.getWriter();
+        assertEquals("text/xml;charset=ISO-8859-7", response.getContentType());
         response.setContentType("text/html;charset=UTF-8");
-        assertEquals("text/html;charset=ISO-8859-7",response.getContentType());
-        
+        assertEquals("text/html; charset=ISO-8859-7", response.getContentType());
+
         response.recycle();
         response.setContentType("text/html;charset=US-ASCII");
         response.getWriter();
-        assertEquals("text/html;charset=US-ASCII",response.getContentType());
-        
+        assertEquals("text/html;charset=US-ASCII", response.getContentType());
+
+        response.recycle();
+        response.setContentType("text/html; charset=utf-8");
+        response.getWriter();
+        assertEquals("text/html; charset=UTF-8", response.getContentType());
+
         response.recycle();
         response.setContentType("text/json");
         response.getWriter();
-        assertEquals("text/json;charset=UTF-8", response.getContentType());
+        assertEquals("text/json", response.getContentType());
         
         response.recycle();
+        response.setContentType("text/json");
+        response.setCharacterEncoding("UTF-8");
+        response.getWriter();
+        assertEquals("text/json; charset=UTF-8", response.getContentType());
+
+        response.recycle();
         response.setCharacterEncoding("xyz");
         response.setContentType("foo/bar");
-        assertEquals("foo/bar;charset=xyz", response.getContentType());
+        assertEquals("foo/bar; charset=xyz", response.getContentType());
 
         response.recycle();
         response.setContentType("foo/bar");
         response.setCharacterEncoding("xyz");
-        assertEquals("foo/bar;charset=xyz", response.getContentType());
+        assertEquals("foo/bar; charset=xyz", response.getContentType());
 
         response.recycle();
         response.setCharacterEncoding("xyz");
@@ -158,351 +198,332 @@ public class ResponseTest
         response.recycle();
         response.setContentType("foo/bar;charset=abc");
         response.setCharacterEncoding("xyz");
-        assertEquals("foo/bar;charset=xyz", response.getContentType());
+        assertEquals("foo/bar; charset=xyz", response.getContentType());
 
         response.recycle();
         response.setCharacterEncoding("xyz");
         response.setContentType("foo/bar");
         response.setCharacterEncoding(null);
         assertEquals("foo/bar", response.getContentType());
-        
+
         response.recycle();
         response.setCharacterEncoding("xyz");
         response.setCharacterEncoding(null);
         response.setContentType("foo/bar");
         assertEquals("foo/bar", response.getContentType());
-
         response.recycle();
         response.addHeader("Content-Type","text/something");
         assertEquals("text/something",response.getContentType());
+        
+        response.recycle();
+        response.addHeader("Content-Type","application/json");
+        response.getWriter();
+        assertEquals("application/json",response.getContentType());
 
     }
 
     @Test
     public void testLocale() throws Exception
     {
+        Response response = newResponse();
 
-        AbstractHttpConnection connection = new TestHttpConnection(connector,new ByteArrayEndPoint(), connector.getServer());
-        Request request = connection.getRequest();
-        Response response = connection.getResponse();
         ContextHandler context = new ContextHandler();
-        context.addLocaleEncoding(Locale.ENGLISH.toString(),"ISO-8859-1");
-        context.addLocaleEncoding(Locale.ITALIAN.toString(),"ISO-8859-2");
-        request.setContext(context.getServletContext());
+        context.addLocaleEncoding(Locale.ENGLISH.toString(), "ISO-8859-1");
+        context.addLocaleEncoding(Locale.ITALIAN.toString(), "ISO-8859-2");
+        response.getHttpChannel().getRequest().setContext(context.getServletContext());
 
         response.setLocale(java.util.Locale.ITALIAN);
-        assertEquals(null,response.getContentType());
+        assertEquals(null, response.getContentType());
         response.setContentType("text/plain");
-        assertEquals("text/plain;charset=ISO-8859-2",response.getContentType());
+        assertEquals("text/plain; charset=ISO-8859-2", response.getContentType());
 
         response.recycle();
         response.setContentType("text/plain");
         response.setCharacterEncoding("utf-8");
         response.setLocale(java.util.Locale.ITALIAN);
-        assertEquals("text/plain;charset=UTF-8",response.getContentType());
-        assertTrue(response.toString().indexOf("charset=UTF-8")>0);
+        assertEquals("text/plain; charset=UTF-8", response.getContentType());
+        assertTrue(response.toString().indexOf("charset=UTF-8") > 0);
     }
 
     @Test
     public void testContentTypeCharacterEncoding() throws Exception
     {
-        AbstractHttpConnection connection = new TestHttpConnection(connector,new ByteArrayEndPoint(), connector.getServer());
-
-        Response response = connection.getResponse();
-
+        Response response = newResponse();
 
         response.setContentType("foo/bar");
         response.setCharacterEncoding("utf-8");
-        assertEquals("foo/bar;charset=utf-8",response.getContentType());
+        assertEquals("foo/bar; charset=UTF-8", response.getContentType());
         response.getWriter();
-        assertEquals("foo/bar;charset=utf-8",response.getContentType());
+        assertEquals("foo/bar; charset=UTF-8", response.getContentType());
         response.setContentType("foo2/bar2");
-        assertEquals("foo2/bar2;charset=utf-8",response.getContentType());
+        assertEquals("foo2/bar2; charset=UTF-8", response.getContentType());
         response.setCharacterEncoding("ISO-8859-1");
-        assertEquals("foo2/bar2;charset=utf-8",response.getContentType());
+        assertEquals("foo2/bar2; charset=UTF-8", response.getContentType());
 
         response.recycle();
 
         response.setContentType("text/html");
         response.setCharacterEncoding("utf-8");
-        assertEquals("text/html;charset=UTF-8",response.getContentType());
+        assertEquals("text/html; charset=UTF-8", response.getContentType());
         response.getWriter();
-        assertEquals("text/html;charset=UTF-8",response.getContentType());
+        assertEquals("text/html; charset=UTF-8", response.getContentType());
         response.setContentType("text/xml");
-        assertEquals("text/xml;charset=UTF-8",response.getContentType());
+        assertEquals("text/xml; charset=UTF-8", response.getContentType());
         response.setCharacterEncoding("ISO-8859-1");
-        assertEquals("text/xml;charset=UTF-8",response.getContentType());
-
+        assertEquals("text/xml; charset=UTF-8", response.getContentType());
     }
 
     @Test
     public void testCharacterEncodingContentType() throws Exception
     {
-        Response response = new Response(new TestHttpConnection(connector,new ByteArrayEndPoint(), connector.getServer()));
-
+        Response response = newResponse();
         response.setCharacterEncoding("utf-8");
         response.setContentType("foo/bar");
-        assertEquals("foo/bar;charset=utf-8",response.getContentType());
+        assertEquals("foo/bar; charset=UTF-8", response.getContentType());
         response.getWriter();
-        assertEquals("foo/bar;charset=utf-8",response.getContentType());
+        assertEquals("foo/bar; charset=UTF-8", response.getContentType());
         response.setContentType("foo2/bar2");
-        assertEquals("foo2/bar2;charset=utf-8",response.getContentType());
+        assertEquals("foo2/bar2; charset=UTF-8", response.getContentType());
         response.setCharacterEncoding("ISO-8859-1");
-        assertEquals("foo2/bar2;charset=utf-8",response.getContentType());
+        assertEquals("foo2/bar2; charset=UTF-8", response.getContentType());
 
         response.recycle();
 
         response.setCharacterEncoding("utf-8");
         response.setContentType("text/html");
-        assertEquals("text/html;charset=UTF-8",response.getContentType());
+        assertEquals("text/html; charset=UTF-8", response.getContentType());
         response.getWriter();
-        assertEquals("text/html;charset=UTF-8",response.getContentType());
+        assertEquals("text/html; charset=UTF-8", response.getContentType());
         response.setContentType("text/xml");
-        assertEquals("text/xml;charset=UTF-8",response.getContentType());
+        assertEquals("text/xml; charset=UTF-8", response.getContentType());
         response.setCharacterEncoding("iso-8859-1");
-        assertEquals("text/xml;charset=UTF-8",response.getContentType());
-
+        assertEquals("text/xml; charset=UTF-8", response.getContentType());
     }
 
     @Test
     public void testContentTypeWithCharacterEncoding() throws Exception
     {
-        Response response = new Response(new TestHttpConnection(connector,new ByteArrayEndPoint(), connector.getServer()));
+        Response response = newResponse();
 
         response.setCharacterEncoding("utf16");
         response.setContentType("foo/bar; charset=utf-8");
-        assertEquals("foo/bar; charset=utf-8",response.getContentType());
+        assertEquals("foo/bar; charset=utf-8", response.getContentType());
         response.getWriter();
-        assertEquals("foo/bar; charset=utf-8",response.getContentType());
+        assertEquals("foo/bar; charset=utf-8", response.getContentType());
         response.setContentType("foo2/bar2");
-        assertEquals("foo2/bar2;charset=utf-8",response.getContentType());
+        assertEquals("foo2/bar2; charset=UTF-8", response.getContentType());
         response.setCharacterEncoding("ISO-8859-1");
-        assertEquals("foo2/bar2;charset=utf-8",response.getContentType());
+        assertEquals("foo2/bar2; charset=UTF-8", response.getContentType());
 
         response.recycle();
 
         response.setCharacterEncoding("utf16");
         response.setContentType("text/html; charset=utf-8");
-        assertEquals("text/html;charset=UTF-8",response.getContentType());
+        assertEquals("text/html; charset=UTF-8", response.getContentType());
         response.getWriter();
-        assertEquals("text/html;charset=UTF-8",response.getContentType());
+        assertEquals("text/html; charset=UTF-8", response.getContentType());
         response.setContentType("text/xml");
-        assertEquals("text/xml;charset=UTF-8",response.getContentType());
+        assertEquals("text/xml; charset=UTF-8", response.getContentType());
         response.setCharacterEncoding("iso-8859-1");
-        assertEquals("text/xml;charset=UTF-8",response.getContentType());
-
+        assertEquals("text/xml; charset=UTF-8", response.getContentType());
     }
 
     @Test
     public void testContentTypeWithOther() throws Exception
     {
-        Response response = new Response(new TestHttpConnection(connector,new ByteArrayEndPoint(), connector.getServer()));
+        Response response = newResponse();
 
         response.setContentType("foo/bar; other=xyz");
-        assertEquals("foo/bar; other=xyz",response.getContentType());
+        assertEquals("foo/bar; other=xyz", response.getContentType());
         response.getWriter();
-        assertEquals("foo/bar; other=xyz;charset=ISO-8859-1",response.getContentType());
+        assertEquals("foo/bar; other=xyz; charset=ISO-8859-1", response.getContentType());
         response.setContentType("foo2/bar2");
-        assertEquals("foo2/bar2;charset=ISO-8859-1",response.getContentType());
+        assertEquals("foo2/bar2; charset=ISO-8859-1", response.getContentType());
 
         response.recycle();
 
         response.setCharacterEncoding("utf-8");
         response.setContentType("text/html; other=xyz");
-        assertEquals("text/html; other=xyz;charset=utf-8",response.getContentType());
+        assertEquals("text/html; other=xyz; charset=UTF-8", response.getContentType());
         response.getWriter();
-        assertEquals("text/html; other=xyz;charset=utf-8",response.getContentType());
+        assertEquals("text/html; other=xyz; charset=UTF-8", response.getContentType());
         response.setContentType("text/xml");
-        assertEquals("text/xml;charset=UTF-8",response.getContentType());
+        assertEquals("text/xml; charset=UTF-8", response.getContentType());
     }
 
     @Test
     public void testContentTypeWithCharacterEncodingAndOther() throws Exception
     {
-        Response response = new Response(new TestHttpConnection(connector,new ByteArrayEndPoint(), connector.getServer()));
+        Response response = newResponse();
 
         response.setCharacterEncoding("utf16");
         response.setContentType("foo/bar; charset=utf-8 other=xyz");
-        assertEquals("foo/bar; charset=utf-8 other=xyz",response.getContentType());
+        assertEquals("foo/bar; charset=utf-8 other=xyz", response.getContentType());
         response.getWriter();
-        assertEquals("foo/bar; charset=utf-8 other=xyz",response.getContentType());
+        assertEquals("foo/bar; charset=utf-8 other=xyz", response.getContentType());
 
         response.recycle();
 
         response.setCharacterEncoding("utf16");
         response.setContentType("text/html; other=xyz charset=utf-8");
-        assertEquals("text/html; other=xyz charset=utf-8",response.getContentType());
+        assertEquals("text/html; other=xyz charset=utf-8; charset=UTF-16", response.getContentType());
         response.getWriter();
-        assertEquals("text/html; other=xyz charset=utf-8",response.getContentType());
+        assertEquals("text/html; other=xyz charset=utf-8; charset=UTF-16", response.getContentType());
 
         response.recycle();
 
         response.setCharacterEncoding("utf16");
         response.setContentType("foo/bar; other=pq charset=utf-8 other=xyz");
-        assertEquals("foo/bar; other=pq charset=utf-8 other=xyz",response.getContentType());
+        assertEquals("foo/bar; other=pq charset=utf-8 other=xyz; charset=UTF-16", response.getContentType());
         response.getWriter();
-        assertEquals("foo/bar; other=pq charset=utf-8 other=xyz",response.getContentType());
-
+        assertEquals("foo/bar; other=pq charset=utf-8 other=xyz; charset=UTF-16", response.getContentType());
     }
 
     @Test
     public void testStatusCodes() throws Exception
     {
-        Response response=newResponse();
+        Response response = newResponse();
 
         response.sendError(404);
         assertEquals(404, response.getStatus());
         assertEquals(null, response.getReason());
 
-        response=newResponse();
+        response = newResponse();
 
         response.sendError(500, "Database Error");
         assertEquals(500, response.getStatus());
         assertEquals("Database Error", response.getReason());
-        assertEquals("must-revalidate,no-cache,no-store", response.getHeader(HttpHeaders.CACHE_CONTROL));
+        assertEquals("must-revalidate,no-cache,no-store", response.getHeader(HttpHeader.CACHE_CONTROL.asString()));
 
-        response=newResponse();
+        response = newResponse();
 
         response.setStatus(200);
         assertEquals(200, response.getStatus());
         assertEquals(null, response.getReason());
 
-        response=newResponse();
+        response = newResponse();
 
         response.sendError(406, "Super Nanny");
         assertEquals(406, response.getStatus());
         assertEquals("Super Nanny", response.getReason());
-        assertEquals("must-revalidate,no-cache,no-store", response.getHeader(HttpHeaders.CACHE_CONTROL));
+        assertEquals("must-revalidate,no-cache,no-store", response.getHeader(HttpHeader.CACHE_CONTROL.asString()));
     }
 
     @Test
     public void testEncodeRedirect()
-        throws Exception
+            throws Exception
     {
-        AbstractHttpConnection connection=new TestHttpConnection(connector,new ByteArrayEndPoint(), connector.getServer());
-        Response response = new Response(connection);
-        Request request = connection.getRequest();
+        Response response = newResponse();
+        Request request = response.getHttpChannel().getRequest();
         request.setServerName("myhost");
         request.setServerPort(8888);
         request.setContextPath("/path");
 
-        assertEquals("http://myhost:8888/path/info;param?query=0&more=1#target",response.encodeURL("http://myhost:8888/path/info;param?query=0&more=1#target"));
+        assertEquals("http://myhost:8888/path/info;param?query=0&more=1#target", response.encodeURL("http://myhost:8888/path/info;param?query=0&more=1#target"));
 
         request.setRequestedSessionId("12345");
         request.setRequestedSessionIdFromCookie(false);
-        AbstractSessionManager manager=new HashSessionManager();
+        HashSessionManager manager = new HashSessionManager();
         manager.setSessionIdManager(new HashSessionIdManager());
         request.setSessionManager(manager);
-        request.setSession(new TestSession(manager,"12345"));
+        request.setSession(new TestSession(manager, "12345"));
 
         manager.setCheckingRemoteSessionIdEncoding(false);
 
-        assertEquals("http://myhost:8888/path/info;param;jsessionid=12345?query=0&more=1#target",response.encodeURL("http://myhost:8888/path/info;param?query=0&more=1#target"));
-        assertEquals("http://other:8888/path/info;param;jsessionid=12345?query=0&more=1#target",response.encodeURL("http://other:8888/path/info;param?query=0&more=1#target"));
-        assertEquals("http://myhost/path/info;param;jsessionid=12345?query=0&more=1#target",response.encodeURL("http://myhost/path/info;param?query=0&more=1#target"));
-        assertEquals("http://myhost:8888/other/info;param;jsessionid=12345?query=0&more=1#target",response.encodeURL("http://myhost:8888/other/info;param?query=0&more=1#target"));
+        assertEquals("http://myhost:8888/path/info;param;jsessionid=12345?query=0&more=1#target", response.encodeURL("http://myhost:8888/path/info;param?query=0&more=1#target"));
+        assertEquals("http://other:8888/path/info;param;jsessionid=12345?query=0&more=1#target", response.encodeURL("http://other:8888/path/info;param?query=0&more=1#target"));
+        assertEquals("http://myhost/path/info;param;jsessionid=12345?query=0&more=1#target", response.encodeURL("http://myhost/path/info;param?query=0&more=1#target"));
+        assertEquals("http://myhost:8888/other/info;param;jsessionid=12345?query=0&more=1#target", response.encodeURL("http://myhost:8888/other/info;param?query=0&more=1#target"));
 
         manager.setCheckingRemoteSessionIdEncoding(true);
-        assertEquals("http://myhost:8888/path/info;param;jsessionid=12345?query=0&more=1#target",response.encodeURL("http://myhost:8888/path/info;param?query=0&more=1#target"));
-        assertEquals("http://other:8888/path/info;param?query=0&more=1#target",response.encodeURL("http://other:8888/path/info;param?query=0&more=1#target"));
-        assertEquals("http://myhost/path/info;param?query=0&more=1#target",response.encodeURL("http://myhost/path/info;param?query=0&more=1#target"));
-        assertEquals("http://myhost:8888/other/info;param?query=0&more=1#target",response.encodeURL("http://myhost:8888/other/info;param?query=0&more=1#target"));
-        
+        assertEquals("http://myhost:8888/path/info;param;jsessionid=12345?query=0&more=1#target", response.encodeURL("http://myhost:8888/path/info;param?query=0&more=1#target"));
+        assertEquals("http://other:8888/path/info;param?query=0&more=1#target", response.encodeURL("http://other:8888/path/info;param?query=0&more=1#target"));
+        assertEquals("http://myhost/path/info;param?query=0&more=1#target", response.encodeURL("http://myhost/path/info;param?query=0&more=1#target"));
+        assertEquals("http://myhost:8888/other/info;param?query=0&more=1#target", response.encodeURL("http://myhost:8888/other/info;param?query=0&more=1#target"));
+
         request.setContextPath("");
-        assertEquals("http://myhost:8888/;jsessionid=12345",response.encodeURL("http://myhost:8888"));
-        assertEquals("https://myhost:8888/;jsessionid=12345",response.encodeURL("https://myhost:8888"));    
+        assertEquals("http://myhost:8888/;jsessionid=12345", response.encodeURL("http://myhost:8888"));
+        assertEquals("https://myhost:8888/;jsessionid=12345", response.encodeURL("https://myhost:8888"));
         assertEquals("mailto:/foo", response.encodeURL("mailto:/foo"));
-        assertEquals("http://myhost:8888/;jsessionid=12345",response.encodeURL("http://myhost:8888/"));
+        assertEquals("http://myhost:8888/;jsessionid=12345", response.encodeURL("http://myhost:8888/"));
         assertEquals("http://myhost:8888/;jsessionid=12345", response.encodeURL("http://myhost:8888/;jsessionid=7777"));
-        assertEquals("http://myhost:8888/;param;jsessionid=12345?query=0&more=1#target",response.encodeURL("http://myhost:8888/;param?query=0&more=1#target"));
-        assertEquals("http://other:8888/path/info;param?query=0&more=1#target",response.encodeURL("http://other:8888/path/info;param?query=0&more=1#target"));
+        assertEquals("http://myhost:8888/;param;jsessionid=12345?query=0&more=1#target", response.encodeURL("http://myhost:8888/;param?query=0&more=1#target"));
+        assertEquals("http://other:8888/path/info;param?query=0&more=1#target", response.encodeURL("http://other:8888/path/info;param?query=0&more=1#target"));
         manager.setCheckingRemoteSessionIdEncoding(false);
         assertEquals("/foo;jsessionid=12345", response.encodeURL("/foo"));
         assertEquals("/;jsessionid=12345", response.encodeURL("/"));
         assertEquals("/foo.html;jsessionid=12345#target", response.encodeURL("/foo.html#target"));
-        assertEquals(";jsessionid=12345", response.encodeURL("")); 
-    }
-
-    @Test
-    public void testCanonicalSendRedirect() throws Exception
-    {
-        ByteArrayEndPoint out=new ByteArrayEndPoint(new byte[]{},4096);
-        AbstractHttpConnection connection=new TestHttpConnection(connector,out, connector.getServer());
-        Response response = new Response(connection);
-        Request request = connection.getRequest();
-        request.setServerName("myhost");
-        request.setServerPort(8888);
-        request.setRequestURI("/path/test/info/");
-        request.setContextPath("/path");
-        request.setServletPath("/test");
-        request.setPathInfo("/info/");
-
-        response.sendRedirect("../other%2Fpath");
-        String location = response.getHeader("Location");
-        assertEquals("http://myhost:8888/path/test/other%2Fpath",location);
+        assertEquals(";jsessionid=12345", response.encodeURL(""));
     }
 
     @Test
     public void testSendRedirect()
-        throws Exception
+            throws Exception
     {
-        String[][] tests={
+        String[][] tests = {
                 // No cookie
                 {"http://myhost:8888/other/location;jsessionid=12345?name=value","http://myhost:8888/other/location;jsessionid=12345?name=value"},
-                {"/other/location;jsessionid=12345?name=value","http://myhost:8888/other/location;jsessionid=12345?name=value"},
-                {"./location;jsessionid=12345?name=value","http://myhost:8888/path/location;jsessionid=12345?name=value"},
-                
+                {"/other/location;jsessionid=12345?name=value","http://@HOST@@PORT@/other/location;jsessionid=12345?name=value"},
+                {"./location;jsessionid=12345?name=value","http://@HOST@@PORT@/path/location;jsessionid=12345?name=value"},
+
                 // From cookie
-                {"/other/location","http://myhost:8888/other/location"},
-                {"/other/l%20cation","http://myhost:8888/other/l%20cation"},
-                {"location","http://myhost:8888/path/location"},
-                {"./location","http://myhost:8888/path/location"},
-                {"../location","http://myhost:8888/location"},
-                {"/other/l%20cation","http://myhost:8888/other/l%20cation"},
-                {"l%20cation","http://myhost:8888/path/l%20cation"},
-                {"./l%20cation","http://myhost:8888/path/l%20cation"},
-                {"../l%20cation","http://myhost:8888/l%20cation"},
-                {"../locati%C3%abn","http://myhost:8888/locati%C3%abn"},
-                {"../other%2fplace","http://myhost:8888/other%2fplace"},
+                {"/other/location","http://@HOST@@PORT@/other/location"},
+                {"/other/l%20cation", "http://@HOST@@PORT@/other/l%20cation"},
+                {"location", "http://@HOST@@PORT@/path/location"},
+                {"./location", "http://@HOST@@PORT@/path/location"},
+                {"../location", "http://@HOST@@PORT@/location"},
+                {"/other/l%20cation", "http://@HOST@@PORT@/other/l%20cation"},
+                {"l%20cation", "http://@HOST@@PORT@/path/l%20cation"},
+                {"./l%20cation", "http://@HOST@@PORT@/path/l%20cation"},
+                {"../l%20cation","http://@HOST@@PORT@/l%20cation"},
+                {"../locati%C3%abn", "http://@HOST@@PORT@/locati%C3%abn"},
+                {"../other%2fplace", "http://@HOST@@PORT@/other%2fplace"},
+                {"http://somehost.com/other/location","http://somehost.com/other/location"},
         };
-        
-        for (int i=0;i<tests.length;i++)
+
+        int[] ports=new int[]{8080,80};
+        String[] hosts=new String[]{"myhost","192.168.0.1","0::1"};
+        for (int port : ports)
         {
-            ByteArrayEndPoint out=new ByteArrayEndPoint(new byte[]{},4096);
-            AbstractHttpConnection connection=new TestHttpConnection(connector,out, connector.getServer());
-            Response response = new Response(connection);
-            Request request = connection.getRequest();
-            request.setServerName("myhost");
-            request.setServerPort(8888);
-            request.setUri(new HttpURI("/path/info;param;jsessionid=12345?query=0&more=1#target"));
-            request.setContextPath("/path");
-            request.setRequestedSessionId("12345");
-            request.setRequestedSessionIdFromCookie(i>2);
-            AbstractSessionManager manager=new HashSessionManager();
-            manager.setSessionIdManager(new HashSessionIdManager());
-            request.setSessionManager(manager);
-            request.setSession(new TestSession(manager,"12345"));
-            manager.setCheckingRemoteSessionIdEncoding(false);
-
-            response.sendRedirect(tests[i][0]);
-
-            String location = out.getOut().toString();
-            int l=location.indexOf("Location: ");
-            int e=location.indexOf('\n',l);
-            location=location.substring(l+10,e).trim();
-            assertEquals("test-"+i,tests[i][1],location);
+            for (String host : hosts)
+            {
+                for (int i=0;i<tests.length;i++)
+                {
+                    Response response = newResponse();
+                    Request request = response.getHttpChannel().getRequest();
+
+                    request.setServerName(host);
+                    request.setServerPort(port);
+                    request.setUri(new HttpURI("/path/info;param;jsessionid=12345?query=0&more=1#target"));
+                    request.setContextPath("/path");
+                    request.setRequestedSessionId("12345");
+                    request.setRequestedSessionIdFromCookie(i>2);
+                    HashSessionManager manager = new HashSessionManager();
+                    manager.setSessionIdManager(new HashSessionIdManager());
+                    request.setSessionManager(manager);
+                    request.setSession(new TestSession(manager, "12345"));
+                    manager.setCheckingRemoteSessionIdEncoding(false);
+
+                    response.sendRedirect(tests[i][0]);
+
+                    String location = response.getHeader("Location");
+
+                    String expected=tests[i][1].replace("@HOST@",host.contains(":")?("["+host+"]"):host).replace("@PORT@",port==80?"":(":"+port));
+                    assertEquals("test-"+i+" "+host+":"+port,expected,location);
+                }
+            }
         }
     }
 
     @Test
-    public void testSetBufferSize () throws Exception
+    public void testSetBufferSizeAfterHavingWrittenContent() throws Exception
     {
-        Response response = new Response(new TestHttpConnection(connector,new ByteArrayEndPoint(), connector.getServer()));
-        response.setBufferSize(20*1024);
+        Response response = newResponse();
+        response.setBufferSize(20 * 1024);
         response.getWriter().print("hello");
         try
         {
-            response.setBufferSize(21*1024);
+            response.setBufferSize(21 * 1024);
             fail("Expected IllegalStateException on Request.setBufferSize");
         }
         catch (Exception e)
@@ -512,9 +533,9 @@ public class ResponseTest
     }
 
     @Test
-    public void testZeroContent () throws Exception
+    public void testZeroContent() throws Exception
     {
-        Response response = new Response (new TestHttpConnection(connector, new ByteArrayEndPoint(), connector.getServer()));
+        Response response = newResponse();
         PrintWriter writer = response.getWriter();
         response.setContentLength(0);
         assertTrue(!response.isCommitted());
@@ -523,20 +544,17 @@ public class ResponseTest
         assertTrue(!writer.checkError());
         assertTrue(response.isCommitted());
     }
-    
-    
+
     @Test
     public void testHead() throws Exception
     {
-        Server server = new Server();
+        Server server = new Server(0);
         try
         {
-            SocketConnector socketConnector = new SocketConnector();
-            socketConnector.setPort(0);
-            server.addConnector(socketConnector);
             server.setHandler(new AbstractHandler()
             {
-                public void handle(String string, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+                @Override
+                public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
                 {
                     response.setStatus(200);
                     response.setContentType("text/plain");
@@ -545,26 +563,37 @@ public class ResponseTest
                     w.println("Geht");
                     w.flush();
                     w.println("Doch");
-                    ((Request) request).setHandled(true);
+                    w.flush();
+                    ((Request)request).setHandled(true);
                 }
             });
             server.start();
 
-            Socket socket = new Socket("localhost",socketConnector.getLocalPort());
+            Socket socket = new Socket("localhost", ((NetworkConnector)server.getConnectors()[0]).getLocalPort());
+            socket.setSoTimeout(500000);
             socket.getOutputStream().write("HEAD / HTTP/1.1\r\nHost: localhost\r\n\r\n".getBytes());
             socket.getOutputStream().write("GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n".getBytes());
             socket.getOutputStream().flush();
 
             LineNumberReader reader = new LineNumberReader(new InputStreamReader(socket.getInputStream()));
             String line = reader.readLine();
-            while (line!=null && line.length()>0)
+            Assert.assertThat(line, Matchers.startsWith("HTTP/1.1 200 OK"));
+            // look for blank line
+            while (line != null && line.length() > 0)
                 line = reader.readLine();
 
-            while (line!=null && line.length()==0)
-                line = reader.readLine();
+            // Read the first line of the GET
+            line = reader.readLine();
+            Assert.assertThat(line, Matchers.startsWith("HTTP/1.1 200 OK"));
 
-            assertTrue(line!=null && line.startsWith("HTTP/1.1 200 OK"));
+            String last = null;
+            while (line != null)
+            {
+                last = line;
+                line = reader.readLine();
+            }
 
+            assertEquals("Doch", last);
         }
         finally
         {
@@ -575,26 +604,26 @@ public class ResponseTest
     @Test
     public void testAddCookie() throws Exception
     {
-        Response response = new Response(new TestHttpConnection(connector,new ByteArrayEndPoint(), connector.getServer()));
+        Response response = newResponse();
 
-        Cookie cookie=new Cookie("name","value");
+        Cookie cookie = new Cookie("name", "value");
         cookie.setDomain("domain");
         cookie.setPath("/path");
         cookie.setSecure(true);
         cookie.setComment("comment__HTTP_ONLY__");
-        
+
         response.addCookie(cookie);
-        
+
         String set = response.getHttpFields().getStringField("Set-Cookie");
-        
-        assertEquals("name=value;Comment=comment;Path=/path;Domain=domain;Secure;HttpOnly",set);
+
+        assertEquals("name=value;Version=1;Path=/path;Domain=domain;Secure;HttpOnly;Comment=comment", set);
     }
-    
-    
+
+
     @Test
     public void testCookiesWithReset() throws Exception
-    { 
-        Response response = new Response(new TestHttpConnection(connector,new ByteArrayEndPoint(), connector.getServer()));
+    {
+        Response response = newResponse();
 
         Cookie cookie=new Cookie("name","value");
         cookie.setDomain("domain");
@@ -602,155 +631,174 @@ public class ResponseTest
         cookie.setSecure(true);
         cookie.setComment("comment__HTTP_ONLY__");
         response.addCookie(cookie);
-        
+
         Cookie cookie2=new Cookie("name2", "value2");
         cookie2.setDomain("domain");
         cookie2.setPath("/path");
         response.addCookie(cookie2);
 
         //keep the cookies
-        response.reset(true);        
+        response.reset(true);
 
         Enumeration<String> set = response.getHttpFields().getValues("Set-Cookie");
 
         assertNotNull(set);
         ArrayList<String> list = Collections.list(set);
         assertEquals(2, list.size());
-        assertTrue(list.contains("name=value;Comment=comment;Path=/path;Domain=domain;Secure;HttpOnly"));
+        assertTrue(list.contains("name=value;Version=1;Path=/path;Domain=domain;Secure;HttpOnly;Comment=comment"));
         assertTrue(list.contains("name2=value2;Path=/path;Domain=domain"));
-        
+
         //get rid of the cookies
         response.reset();
-        
+
         set = response.getHttpFields().getValues("Set-Cookie");
         assertFalse(set.hasMoreElements());
     }
 
-    private Response newResponse()
+    @Test
+    public void testFlushAfterFullContent() throws Exception
     {
-        ByteArrayEndPoint endPoint = new ByteArrayEndPoint();
-        endPoint.setOut(new ByteArrayBuffer(1024));
-        endPoint.setGrowOutput(true);
-        AbstractHttpConnection connection=new TestHttpConnection(connector, endPoint, connector.getServer());
-        connection.getGenerator().reset();
-        AbstractHttpConnection.setCurrentConnection(connection);
-        Response response = connection.getResponse();
-        connection.getRequest().setRequestURI("/test");
-        return response;
+        Response response = _channel.getResponse();
+        byte[] data = new byte[]{(byte)0xCA, (byte)0xFE};
+        ServletOutputStream output = response.getOutputStream();
+        response.setContentLength(data.length);
+        // Write the whole content
+        output.write(data);
+        // Must not throw
+        output.flush();
     }
 
-    private class TestSession extends AbstractSession
-    {
-        public TestSession(AbstractSessionManager abstractSessionManager, String id)
-        {
-            super(abstractSessionManager, System.currentTimeMillis(),System.currentTimeMillis(), id);
-        }
-
-        public Object getAttribute(String name)
-        {
-            return null;
-        }
-
-        public Enumeration getAttributeNames()
-        {
-
-            return null;
-        }
-
-        public long getCreationTime()
-        {
-
-            return 0;
-        }
-
-        public String getId()
-        {
-            return "12345";
-        }
-
-        public long getLastAccessedTime()
-        {
-            return 0;
-        }
-
-        public int getMaxInactiveInterval()
-        {
-            return 0;
-        }
-
-        public ServletContext getServletContext()
-        {
-            return null;
-        }
-
-        public HttpSessionContext getSessionContext()
-        {
-            return null;
-        }
-
-        public Object getValue(String name)
-        {
-            return null;
-        }
-
-        public String[] getValueNames()
-        {
-            return null;
-        }
 
-        public void invalidate()
-        {
-        }
-
-        public boolean isNew()
-        {
-            return false;
-        }
-
-        public void putValue(String name, Object value)
-        {
-        }
-
-        public void removeAttribute(String name)
-        {
-        }
-
-        public void removeValue(String name)
-        {
-        }
+    @Test
+    public void testSetCookie() throws Exception
+    {
+        Response response = _channel.getResponse();
+        HttpFields fields = response.getHttpFields();
 
-        public void setAttribute(String name, Object value)
-        {
-        }
+        response.addSetCookie("null",null,null,null,-1,null,false,false,-1);
+        assertEquals("null=",fields.getStringField("Set-Cookie"));
 
-        public void setMaxInactiveInterval(int interval)
-        {
-        }
+        fields.clear();
+        
+        response.addSetCookie("minimal","value",null,null,-1,null,false,false,-1);
+        assertEquals("minimal=value",fields.getStringField("Set-Cookie"));
+
+        fields.clear();
+        //test cookies with same name, domain and path, only 1 allowed
+        response.addSetCookie("everything","wrong","domain","path",0,"to be replaced",true,true,0);
+        response.addSetCookie("everything","value","domain","path",0,"comment",true,true,0);
+        assertEquals("everything=value;Version=1;Path=path;Domain=domain;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",fields.getStringField("Set-Cookie"));
+        Enumeration<String> e =fields.getValues("Set-Cookie");
+        assertTrue(e.hasMoreElements());
+        assertEquals("everything=value;Version=1;Path=path;Domain=domain;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",e.nextElement());
+        assertFalse(e.hasMoreElements());
+        assertEquals("Thu, 01 Jan 1970 00:00:00 GMT",fields.getStringField("Expires"));
+        assertFalse(e.hasMoreElements());
+        
+        //test cookies with same name, different domain
+        fields.clear();
+        response.addSetCookie("everything","other","domain1","path",0,"blah",true,true,0);
+        response.addSetCookie("everything","value","domain2","path",0,"comment",true,true,0);
+        e =fields.getValues("Set-Cookie");
+        assertTrue(e.hasMoreElements());
+        assertEquals("everything=other;Version=1;Path=path;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=blah",e.nextElement());
+        assertTrue(e.hasMoreElements());
+        assertEquals("everything=value;Version=1;Path=path;Domain=domain2;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",e.nextElement());
+        assertFalse(e.hasMoreElements());
+        
+        //test cookies with same name, same path, one with domain, one without
+        fields.clear();
+        response.addSetCookie("everything","other","domain1","path",0,"blah",true,true,0);
+        response.addSetCookie("everything","value","","path",0,"comment",true,true,0);
+        e =fields.getValues("Set-Cookie");
+        assertTrue(e.hasMoreElements());
+        assertEquals("everything=other;Version=1;Path=path;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=blah",e.nextElement());
+        assertTrue(e.hasMoreElements());
+        assertEquals("everything=value;Version=1;Path=path;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",e.nextElement());
+        assertFalse(e.hasMoreElements());
+        
+        
+        //test cookies with same name, different path
+        fields.clear();
+        response.addSetCookie("everything","other","domain1","path1",0,"blah",true,true,0);
+        response.addSetCookie("everything","value","domain1","path2",0,"comment",true,true,0);
+        e =fields.getValues("Set-Cookie");
+        assertTrue(e.hasMoreElements());
+        assertEquals("everything=other;Version=1;Path=path1;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=blah",e.nextElement());
+        assertTrue(e.hasMoreElements());
+        assertEquals("everything=value;Version=1;Path=path2;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",e.nextElement());
+        assertFalse(e.hasMoreElements());
+        
+        //test cookies with same name, same domain, one with path, one without
+        fields.clear();
+        response.addSetCookie("everything","other","domain1","path1",0,"blah",true,true,0);
+        response.addSetCookie("everything","value","domain1","",0,"comment",true,true,0);
+        e =fields.getValues("Set-Cookie");
+        assertTrue(e.hasMoreElements());
+        assertEquals("everything=other;Version=1;Path=path1;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=blah",e.nextElement());
+        assertTrue(e.hasMoreElements());
+        assertEquals("everything=value;Version=1;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",e.nextElement());
+        assertFalse(e.hasMoreElements());
+        
+        //test cookies same name only, no path, no domain
+        fields.clear();
+        response.addSetCookie("everything","other","","",0,"blah",true,true,0);
+        response.addSetCookie("everything","value","","",0,"comment",true,true,0);
+        e =fields.getValues("Set-Cookie");
+        assertTrue(e.hasMoreElements());
+        assertEquals("everything=value;Version=1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",e.nextElement());
+        assertFalse(e.hasMoreElements());
+
+        fields.clear();
+        response.addSetCookie("ev erything","va lue","do main","pa th",1,"co mment",true,true,1);
+        String setCookie=fields.getStringField("Set-Cookie");
+        assertThat(setCookie,Matchers.startsWith("\"ev erything\"=\"va lue\";Version=1;Path=\"pa th\";Domain=\"do main\";Expires="));
+        assertThat(setCookie,Matchers.endsWith(" GMT;Max-Age=1;Secure;HttpOnly;Comment=\"co mment\""));
+
+        fields.clear();
+        response.addSetCookie("name","value",null,null,-1,null,false,false,0);
+        setCookie=fields.getStringField("Set-Cookie");
+        assertEquals(-1,setCookie.indexOf("Version="));
+        fields.clear();
+        response.addSetCookie("name","v a l u e",null,null,-1,null,false,false,0);
+        setCookie=fields.getStringField("Set-Cookie");
+
+        fields.clear();
+        response.addSetCookie("json","{\"services\":[\"cwa\", \"aa\"]}",null,null,-1,null,false,false,-1);
+        assertEquals("json=\"{\\\"services\\\":[\\\"cwa\\\", \\\"aa\\\"]}\"",fields.getStringField("Set-Cookie"));
+
+        fields.clear();
+        response.addSetCookie("name","value","domain",null,-1,null,false,false,-1);
+        response.addSetCookie("name","other","domain",null,-1,null,false,false,-1);
+        assertEquals("name=other;Domain=domain",fields.getStringField("Set-Cookie"));
+        response.addSetCookie("name","more","domain",null,-1,null,false,false,-1);
+        assertEquals("name=more;Domain=domain",fields.getStringField("Set-Cookie"));
+        response.addSetCookie("foo","bar","domain",null,-1,null,false,false,-1);
+        response.addSetCookie("foo","bob","domain",null,-1,null,false,false,-1);
+        assertEquals("name=more;Domain=domain",fields.getStringField("Set-Cookie"));
+
+        e=fields.getValues("Set-Cookie");
+        assertEquals("name=more;Domain=domain",e.nextElement());
+        assertEquals("foo=bob;Domain=domain",e.nextElement());
+
+        fields.clear();
+        response.addSetCookie("name","value%=",null,null,-1,null,false,false,0);
+        setCookie=fields.getStringField("Set-Cookie");
+        assertEquals("name=value%=",setCookie);
 
-        protected Map newAttributeMap()
-        {
-            return null;
-        }
     }
     
-    static class TestHttpConnection extends AbstractHttpConnection
+    private Response newResponse()
     {
-        
-        public TestHttpConnection(Connector connector, EndPoint endpoint, Server server)
-        {
-            super(connector,endpoint,server);
-        }
-
-        public TestHttpConnection(Connector connector, EndPoint endpoint, Server server, Parser parser, Generator generator, Request request)
-        {
-            super(connector,endpoint,server,parser,generator,request);
-        }
+        _channel.reset();
+        return new Response(_channel, _channel.getResponse().getHttpOutput());
+    }
 
-        @Override
-        public Connection handle() throws IOException
+    private static class TestSession extends HashedSession
+    {
+        protected TestSession(HashSessionManager hashSessionManager, String id)
         {
-            return this;
+            super(hashSessionManager, 0L, 0L, id);
         }
-        
     }
 }
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/SelectChannelAsyncContextTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/SelectChannelAsyncContextTest.java
index f4cb259..206cd92 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/SelectChannelAsyncContextTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/SelectChannelAsyncContextTest.java
@@ -18,81 +18,25 @@
 
 package org.eclipse.jetty.server;
 
-import java.io.IOException;
 import java.net.Socket;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
+import java.nio.charset.StandardCharsets;
 
-import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.io.nio.SelectChannelEndPoint;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
 import org.eclipse.jetty.util.IO;
-import org.junit.Test;
 
 public class SelectChannelAsyncContextTest extends LocalAsyncContextTest
 {
-    volatile SelectChannelEndPoint _endp;
-
     @Override
     protected Connector initConnector()
     {
-        return new SelectChannelConnector(){
-
-            @Override
-            public void customize(EndPoint endpoint, Request request) throws IOException
-            {
-                super.customize(endpoint,request);
-                _endp=(SelectChannelEndPoint)endpoint;
-            }
-            
-        };
+        return new ServerConnector(_server);
     }
 
     @Override
     protected String getResponse(String request) throws Exception
     {
-        SelectChannelConnector connector = (SelectChannelConnector)_connector;
+        ServerConnector connector = (ServerConnector)_connector;
         Socket socket = new Socket((String)null,connector.getLocalPort());
-        socket.getOutputStream().write(request.getBytes("UTF-8"));
+        socket.getOutputStream().write(request.getBytes(StandardCharsets.UTF_8));
         return IO.toString(socket.getInputStream());
     }
-
-    @Test
-    public void testSuspendResumeWithAsyncDispatch() throws Exception
-    {
-        // Test that suspend/resume works in the face of spurious asyncDispatch call that may be
-        // produced by the SslConnection
-        final AtomicBoolean running = new AtomicBoolean(true);
-        Thread thread = new Thread()
-        {
-            public void run()
-            {
-                while (running.get())
-                {
-                    try 
-                    {
-                        TimeUnit.MILLISECONDS.sleep(200);
-                        SelectChannelEndPoint endp=_endp;
-                        if (endp!=null && endp.isOpen())
-                            endp.asyncDispatch();
-                    }
-                    catch(Exception e)
-                    {
-                        e.printStackTrace();
-                    }
-                }
-            }
-        };
-        
-        try
-        {
-            thread.start();
-            testSuspendResume();
-        }
-        finally
-        {
-            running.set(false);
-            thread.join();
-        }
-    }
 }
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/SelectChannelConnectorCloseTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/SelectChannelConnectorCloseTest.java
index 7deced5..b05561e 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/SelectChannelConnectorCloseTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/SelectChannelConnectorCloseTest.java
@@ -18,7 +18,7 @@
 
 package org.eclipse.jetty.server;
 
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
+import org.junit.After;
 import org.junit.Before;
 
 
@@ -30,7 +30,12 @@ public class SelectChannelConnectorCloseTest extends ConnectorCloseTestBase
     @Before
     public void init() throws Exception
     {
-        System.setProperty("org.eclipse.jetty.util.log.DEBUG","true");        
-        startServer(new SelectChannelConnector());
+        startServer(new ServerConnector(_server));
+    }
+    
+    @After
+    public void after() throws Exception
+    {
+        _server.stop();
     }
 }
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/SelectChannelServerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/SelectChannelServerTest.java
index fde947e..c4e4e01 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/SelectChannelServerTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/SelectChannelServerTest.java
@@ -17,31 +17,18 @@
 //
 
 package org.eclipse.jetty.server;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
-import org.junit.BeforeClass;
+
+import org.junit.Before;
 
 /**
  * HttpServer Tester.
  */
 public class SelectChannelServerTest extends HttpServerTestBase
 {
-    @BeforeClass
-    public static void init() throws Exception
-    {
-        startServer(new SelectChannelConnector());
-    }
-
-    @Override
-    public void testCommittedError() throws Exception
-    {
-        super.testCommittedError();
-    }
-
-    @Override
-    public void testSuspendedPipeline() throws Exception
+    @Before
+    public void init() throws Exception
     {
-        super.testSuspendedPipeline();
+        // Run this test with 0 acceptors. Other tests already check the acceptors >0
+        startServer(new ServerConnector(_server,0,1));
     }
-    
-    
 }
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/SelectChannelStatisticsTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/SelectChannelStatisticsTest.java
new file mode 100644
index 0000000..2452363
--- /dev/null
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/SelectChannelStatisticsTest.java
@@ -0,0 +1,266 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.net.Socket;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.CyclicBarrier;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.server.handler.HandlerWrapper;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Ignore;
+import org.junit.Test;
+
+ at Ignore("Ignored while refactoring the connection events and statistics")
+public class SelectChannelStatisticsTest
+{
+    private static final Logger LOG = Log.getLogger(SelectChannelStatisticsTest.class);
+
+    private static Server _server;
+    private static ConnectorStatistics _statistics;
+    private static AbstractNetworkConnector _connector;
+    private static CyclicBarrier _connect;
+    private static CountDownLatch _closed;
+
+    private Socket[] _socket;
+    private PrintWriter[] _out;
+    private BufferedReader[] _in;
+
+    @BeforeClass
+    public static void initClass() throws Exception
+    {
+        _connect = new CyclicBarrier(2);
+
+        _server = new Server();
+        _connector = new ServerConnector(_server);
+        _statistics = new ConnectorStatistics();
+        _connector.addBean(_statistics);
+        _server.addConnector(_connector);
+
+        HandlerWrapper wrapper = new HandlerWrapper()
+        {
+            @Override
+            public void handle(String path, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException
+            {
+                try
+                {
+                    _connect.await();
+                }
+                catch (Exception ex)
+                {
+                    LOG.debug(ex);
+                }
+                finally
+                {
+                    super.handle(path, request, httpRequest, httpResponse);
+                }
+            }
+        };
+        _server.setHandler(wrapper);
+
+        Handler handler = new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
+                throws IOException, ServletException
+            {
+                try{Thread.sleep(1);} catch(Exception e){}
+                baseRequest.setHandled(true);
+                PrintWriter out = response.getWriter();
+                out.write("Server response\n");
+                out.close();
+
+                response.setStatus(HttpServletResponse.SC_OK);
+            }
+        };
+        wrapper.setHandler(handler);
+
+        _server.start();
+    }
+
+    @AfterClass
+    public static void destroy() throws Exception
+    {
+        _server.stop();
+        _server.join();
+    }
+
+    @Before
+    public void init() throws Exception
+    {
+        _statistics.reset();
+    }
+
+    @After
+    public void tini() throws Exception
+    {
+    }
+
+    @Test
+    public void testSingleRequest() throws Exception
+    {
+        doInit(1);
+
+        sendRequest(1, 1);
+
+        doClose(1);
+
+        assertEquals(1, _statistics.getConnections());
+        assertEquals(0, _statistics.getConnectionsOpen());
+        assertEquals(1, _statistics.getConnectionsOpenMax());
+        assertTrue(_statistics.getConnectionsOpen() <= _statistics.getConnectionsOpenMax());
+
+        assertTrue(_statistics.getConnectionDurationMean() > 0);
+        assertTrue(_statistics.getConnectionDurationMax() > 0);
+        assertTrue(_statistics.getConnectionDurationMean() <= _statistics.getConnectionDurationMax());
+
+        assertEquals(1, _statistics.getMessagesIn());
+        assertEquals(1.0, _statistics.getMessagesInPerConnectionMean(), 0.01);
+        assertEquals(1, _statistics.getMessagesInPerConnectionMax());
+        assertTrue(_statistics.getMessagesInPerConnectionMean() <= _statistics.getMessagesInPerConnectionMax());
+    }
+
+    @Test
+    public void testMultipleRequests() throws Exception
+    {
+        doInit(1);
+
+        sendRequest(1, 1);
+
+        sendRequest(1, 1);
+
+        doClose(1);
+
+        assertEquals(1, _statistics.getConnections());
+        assertEquals(0, _statistics.getConnectionsOpen());
+        assertEquals(1, _statistics.getConnectionsOpenMax());
+        assertTrue(_statistics.getConnectionsOpen() <= _statistics.getConnectionsOpenMax());
+
+        assertTrue(_statistics.getConnectionDurationMean() > 0);
+        assertTrue(_statistics.getConnectionDurationMax() > 0);
+        assertTrue(_statistics.getConnectionDurationMean() <= _statistics.getConnectionDurationMax());
+
+        assertEquals(2, _statistics.getMessagesIn());
+        assertEquals(2.0, _statistics.getMessagesInPerConnectionMean(), 0.01);
+        assertEquals(2, _statistics.getMessagesInPerConnectionMax());
+        assertTrue(_statistics.getMessagesInPerConnectionMean() <= _statistics.getMessagesInPerConnectionMax());
+    }
+
+    @Test
+    public void testMultipleConnections() throws Exception
+    {
+        doInit(3);
+
+        sendRequest(1, 1); // request 1 connection 1
+
+        sendRequest(2, 2); // request 1 connection 2
+
+        sendRequest(3, 3); // request 1 connection 3
+
+        sendRequest(2, 3); // request 2 connection 2
+
+        sendRequest(3, 3); // request 2 connection 3
+
+        sendRequest(3, 3); // request 3 connection 3
+
+        doClose(3);
+
+        assertEquals(3, _statistics.getConnections());
+        assertEquals(0, _statistics.getConnectionsOpen());
+        assertEquals(3, _statistics.getConnectionsOpenMax());
+        assertTrue(_statistics.getConnectionsOpen() <= _statistics.getConnectionsOpenMax());
+
+        assertTrue(_statistics.getConnectionDurationMean() > 0);
+        assertTrue(_statistics.getConnectionDurationMax() > 0);
+        assertTrue(_statistics.getConnectionDurationMean() <= _statistics.getConnectionDurationMax());
+
+        assertEquals(6, _statistics.getMessagesIn());
+        assertEquals(2.0, _statistics.getMessagesInPerConnectionMean(), 0.01);
+        assertEquals(3, _statistics.getMessagesInPerConnectionMax());
+        assertTrue(_statistics.getMessagesInPerConnectionMean() <= _statistics.getMessagesInPerConnectionMax());
+    }
+
+    protected void doInit(int count)
+    {
+        _socket = new Socket[count];
+        _out = new PrintWriter[count];
+        _in = new BufferedReader[count];
+
+        _closed = new CountDownLatch(count);
+    }
+
+    private void doClose(int count) throws Exception
+    {
+        for (int idx=0; idx < count; idx++)
+        {
+            if (_socket[idx] != null)
+            {
+                _socket[idx].close();
+            }
+        }
+
+        _closed.await();
+    }
+
+    private void sendRequest(int id, int count) throws Exception
+    {
+        int idx = id - 1;
+
+        if (idx < 0)
+            throw new IllegalArgumentException("Connection ID <= 0");
+
+        _socket[idx]  = _socket[idx] == null ? new Socket("localhost", _connector.getLocalPort()) : _socket[idx];
+        _out[idx] = _out[idx] == null ? new PrintWriter(_socket[idx].getOutputStream(), true) : _out[idx];
+        _in[idx] = _in[idx] == null ? new BufferedReader(new InputStreamReader(_socket[idx].getInputStream())) : _in[idx];
+
+        _connect.reset();
+
+        _out[idx].write("GET / HTTP/1.1\r\nHost: localhost\r\n\r\n");
+        _out[idx].flush();
+
+        _connect.await();
+
+        assertEquals(count, _statistics.getConnectionsOpen());
+
+        String line=_in[idx].readLine();
+        while(line!=null)
+        {
+            if ("Server response".equals(line))
+                break;
+            line=_in[idx].readLine();
+        }
+    }
+}
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/SelectChannelTimeoutTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/SelectChannelTimeoutTest.java
index 553236f..26dbd57 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/SelectChannelTimeoutTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/SelectChannelTimeoutTest.java
@@ -18,32 +18,31 @@
 
 package org.eclipse.jetty.server;
 
+import static org.junit.Assert.assertTrue;
+
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.UnsupportedEncodingException;
 import java.net.Socket;
+import java.nio.charset.StandardCharsets;
 import java.util.Locale;
 
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
 import org.eclipse.jetty.server.session.SessionHandler;
 import org.eclipse.jetty.util.IO;
-import org.junit.BeforeClass;
+import org.junit.Before;
 import org.junit.Test;
 
-import static org.junit.Assert.assertTrue;
-
 public class SelectChannelTimeoutTest extends ConnectorTimeoutTest
 {
-
-    @BeforeClass
-    public static void init() throws Exception
+    @Before
+    public void init() throws Exception
     {
-        SelectChannelConnector connector = new SelectChannelConnector();
-        connector.setMaxIdleTime(MAX_IDLE_TIME); // 250 msec max idle
+        ServerConnector connector = new ServerConnector(_server,1,1);
+        connector.setIdleTimeout(MAX_IDLE_TIME); // 250 msec max idle
         startServer(connector);
     }
 
-    @Test
+    @Test(timeout=60000)
     public void testIdleTimeoutAfterSuspend() throws Exception
     {
         SuspendHandler _handler = new SuspendHandler();
@@ -58,7 +57,7 @@ public class SelectChannelTimeoutTest extends ConnectorTimeoutTest
         assertTrue(process(null).toUpperCase(Locale.ENGLISH).contains("RESUMED"));
     }
 
-    @Test
+    @Test(timeout=60000)
     public void testIdleTimeoutAfterTimeout() throws Exception
     {
         SuspendHandler _handler = new SuspendHandler();
@@ -72,7 +71,7 @@ public class SelectChannelTimeoutTest extends ConnectorTimeoutTest
         assertTrue(process(null).toUpperCase(Locale.ENGLISH).contains("TIMEOUT"));
     }
 
-    @Test
+    @Test(timeout=60000)
     public void testIdleTimeoutAfterComplete() throws Exception
     {
         SuspendHandler _handler = new SuspendHandler();
@@ -100,10 +99,10 @@ public class SelectChannelTimeoutTest extends ConnectorTimeoutTest
 
     private String getResponse(String request) throws UnsupportedEncodingException, IOException, InterruptedException
     {
-        SelectChannelConnector connector = (SelectChannelConnector)_connector;
+        ServerConnector connector = (ServerConnector)_connector;
         Socket socket = new Socket((String)null,connector.getLocalPort());
         socket.setSoTimeout(10 * MAX_IDLE_TIME);
-        socket.getOutputStream().write(request.getBytes("UTF-8"));
+        socket.getOutputStream().write(request.getBytes(StandardCharsets.UTF_8));
         InputStream inputStream = socket.getInputStream();
         long start = System.currentTimeMillis();
         String response = IO.toString(inputStream);
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ServerConnectorTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ServerConnectorTest.java
new file mode 100644
index 0000000..3f8f022
--- /dev/null
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ServerConnectorTest.java
@@ -0,0 +1,197 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.lang.reflect.Field;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.Socket;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.charset.StandardCharsets;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.io.ChannelEndPoint;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.server.handler.DefaultHandler;
+import org.eclipse.jetty.server.handler.HandlerList;
+import org.eclipse.jetty.util.IO;
+import org.junit.Test;
+
+public class ServerConnectorTest
+{
+    public static class ReuseInfoHandler extends AbstractHandler
+    {
+        @Override
+        public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+        {
+            response.setContentType("text/plain");
+
+            EndPoint endPoint = baseRequest.getHttpChannel().getEndPoint();
+            assertThat("Endpoint",endPoint,instanceOf(ChannelEndPoint.class));
+            ChannelEndPoint channelEndPoint = (ChannelEndPoint)endPoint;
+            Socket socket = channelEndPoint.getSocket();
+            ServerConnector connector = (ServerConnector)baseRequest.getHttpChannel().getConnector();
+
+            PrintWriter out = response.getWriter();
+            out.printf("connector.getReuseAddress() = %b%n",connector.getReuseAddress());
+
+            try
+            {
+                Field fld = connector.getClass().getDeclaredField("_reuseAddress");
+                assertThat("Field[_reuseAddress]",fld,notNullValue());
+                fld.setAccessible(true);
+                Object val = fld.get(connector);
+                out.printf("connector._reuseAddress() = %b%n",val);
+            }
+            catch (Throwable t)
+            {
+                t.printStackTrace(out);
+            }
+            
+            out.printf("socket.getReuseAddress() = %b%n",socket.getReuseAddress());
+            
+            baseRequest.setHandled(true);
+        }
+    }
+
+    private URI toServerURI(ServerConnector connector) throws URISyntaxException
+    {
+        String host = connector.getHost();
+        if (host == null)
+        {
+            host = "localhost";
+        }
+        int port = connector.getLocalPort();
+        return new URI(String.format("http://%s:%d/",host,port));
+    }
+
+    private String getResponse(URI uri) throws MalformedURLException, IOException
+    {
+        HttpURLConnection http = (HttpURLConnection)uri.toURL().openConnection();
+        assertThat("Valid Response Code",http.getResponseCode(),anyOf(is(200),is(404)));
+
+        try (InputStream in = http.getInputStream())
+        {
+            return IO.toString(in,StandardCharsets.UTF_8);
+        }
+    }
+
+    @Test
+    public void testReuseAddress_Default() throws Exception
+    {
+        Server server = new Server();
+        ServerConnector connector = new ServerConnector(server);
+        connector.setPort(0);
+        server.addConnector(connector);
+
+        HandlerList handlers = new HandlerList();
+        handlers.addHandler(new ReuseInfoHandler());
+        handlers.addHandler(new DefaultHandler());
+
+        server.setHandler(handlers);
+
+        try
+        {
+            server.start();
+
+            URI uri = toServerURI(connector);
+            String response = getResponse(uri);
+            assertThat("Response",response,containsString("connector.getReuseAddress() = true"));
+            assertThat("Response",response,containsString("connector._reuseAddress() = true"));
+            assertThat("Response",response,containsString("socket.getReuseAddress() = true"));
+        }
+        finally
+        {
+            server.stop();
+        }
+    }
+
+    @Test
+    public void testReuseAddress_True() throws Exception
+    {
+        Server server = new Server();
+        ServerConnector connector = new ServerConnector(server);
+        connector.setPort(0);
+        connector.setReuseAddress(true);
+        server.addConnector(connector);
+
+        HandlerList handlers = new HandlerList();
+        handlers.addHandler(new ReuseInfoHandler());
+        handlers.addHandler(new DefaultHandler());
+
+        server.setHandler(handlers);
+
+        try
+        {
+            server.start();
+
+            URI uri = toServerURI(connector);
+            String response = getResponse(uri);
+            assertThat("Response",response,containsString("connector.getReuseAddress() = true"));
+            assertThat("Response",response,containsString("connector._reuseAddress() = true"));
+            assertThat("Response",response,containsString("socket.getReuseAddress() = true"));
+        }
+        finally
+        {
+            server.stop();
+        }
+    }
+
+    @Test
+    public void testReuseAddress_False() throws Exception
+    {
+        Server server = new Server();
+        ServerConnector connector = new ServerConnector(server);
+        connector.setPort(0);
+        connector.setReuseAddress(false);
+        server.addConnector(connector);
+
+        HandlerList handlers = new HandlerList();
+        handlers.addHandler(new ReuseInfoHandler());
+        handlers.addHandler(new DefaultHandler());
+
+        server.setHandler(handlers);
+
+        try
+        {
+            server.start();
+
+            URI uri = toServerURI(connector);
+            String response = getResponse(uri);
+            assertThat("Response",response,containsString("connector.getReuseAddress() = false"));
+            assertThat("Response",response,containsString("connector._reuseAddress() = false"));
+            assertThat("Response",response,containsString("socket.getReuseAddress() = false"));
+        }
+        finally
+        {
+            server.stop();
+        }
+    }
+}
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ServerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ServerTest.java
deleted file mode 100644
index 1e6cb14..0000000
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/ServerTest.java
+++ /dev/null
@@ -1,60 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.server;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
-
-import java.util.Random;
-
-import org.eclipse.jetty.server.handler.DefaultHandler;
-import org.junit.Test;
-
-/**
- * @version $Revision$
- */
-public class ServerTest
-{
-    /**
-     * JETTY-87, adding a handler to a server without any handlers should not
-     * throw an exception
-     */
-    @Test
-    public void testAddHandlerToEmptyServer()
-    {
-        Server server=new Server();
-        DefaultHandler handler=new DefaultHandler();
-        try
-        {
-            server.setHandler(handler);
-        }
-        catch (Exception e)
-        {
-            fail("Adding handler "+handler+" to server "+server+" threw exception "+e);
-        }
-    }
-
-    @Test
-    public void testServerWithPort()
-    {
-        int port=new Random().nextInt(20000)+10000;
-        Server server=new Server(port);
-        assertEquals(port,server.getConnectors()[0].getPort());
-    }
-}
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ShutdownMonitorTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ShutdownMonitorTest.java
index 57ba9ff..f7bd095 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/ShutdownMonitorTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ShutdownMonitorTest.java
@@ -19,7 +19,6 @@
 package org.eclipse.jetty.server;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
 import java.io.InputStreamReader;
@@ -27,86 +26,193 @@ import java.io.LineNumberReader;
 import java.io.OutputStream;
 import java.net.InetAddress;
 import java.net.Socket;
+import java.util.concurrent.TimeUnit;
 
+import org.eclipse.jetty.util.thread.ShutdownThread;
 import org.junit.Test;
 
 /**
  * ShutdownMonitorTest
- *
- *
- *
  */
 public class ShutdownMonitorTest
 {
-
-
+    public class TestableServer extends Server
+    {
+        boolean destroyed = false;
+        boolean stopped = false;
+        @Override
+        protected void doStop() throws Exception
+        {
+            stopped = true;
+            super.doStop();
+        }
+        @Override
+        public void destroy()
+        {
+            destroyed = true;
+            super.destroy();
+        }
+        @Override
+        protected void doStart() throws Exception
+        {
+            stopped = false;
+            destroyed  = false;
+            super.doStart();
+        }    
+    }
+    
+    
     @Test
-    public void testShutdown ()
-    throws Exception
+    public void testShutdownMonitor() throws Exception
     {
-
-        //test port and key assignment
+        // test port and key assignment
         ShutdownMonitor.getInstance().setPort(0);
         ShutdownMonitor.getInstance().setExitVm(false);
         ShutdownMonitor.getInstance().start();
         String key = ShutdownMonitor.getInstance().getKey();
         int port = ShutdownMonitor.getInstance().getPort();
-        
-        //try starting a 2nd time (should be ignored)
-        ShutdownMonitor.getInstance().start();   
-      
-        
-        stop(port,key,true);
+
+        // try starting a 2nd time (should be ignored)
+        ShutdownMonitor.getInstance().start();
+
+        stop("stop", port,key,true);
         assertTrue(!ShutdownMonitor.getInstance().isAlive());
-      
-        //should be able to change port and key because it is stopped
+
+        // should be able to change port and key because it is stopped
         ShutdownMonitor.getInstance().setPort(0);
-        ShutdownMonitor.getInstance().setKey("foo");        
+        ShutdownMonitor.getInstance().setKey("foo");
         ShutdownMonitor.getInstance().start();
-        
+
         key = ShutdownMonitor.getInstance().getKey();
         port = ShutdownMonitor.getInstance().getPort();
         assertTrue(ShutdownMonitor.getInstance().isAlive());
 
-        stop(port,key,true);
+        stop("stop", port,key,true);
         assertTrue(!ShutdownMonitor.getInstance().isAlive());
     }
-
-
-    public void stop (int port, String key, boolean check)
-    throws Exception
+    
+    
+    @Test
+    public void testForceStopCommand() throws Exception
+    {
+        //create a testable Server with stop(), destroy() overridden to instrument
+        //start server
+        //call "forcestop" and check that server stopped but not destroyed
+        // test port and key assignment
+        System.setProperty("DEBUG", "true");
+        ShutdownMonitor.getInstance().setPort(0);
+        TestableServer server = new TestableServer();
+        server.start();
+       
+        //shouldn't be registered for shutdown on jvm
+        assertTrue(!ShutdownThread.isRegistered(server));
+        assertTrue(ShutdownMonitor.isRegistered(server));
+        
+        String key = ShutdownMonitor.getInstance().getKey();
+        int port = ShutdownMonitor.getInstance().getPort();
+        
+        stop("forcestop", port,key,true);
+        
+        assertTrue(!ShutdownMonitor.getInstance().isAlive());
+        assertTrue(server.stopped);
+        assertTrue(!server.destroyed);
+        assertTrue(!ShutdownThread.isRegistered(server));
+        assertTrue(!ShutdownMonitor.isRegistered(server));
+    }
+    
+    @Test
+    public void testOldStopCommandWithStopOnShutdownTrue() throws Exception
     {
-        Socket s = null;
+        
+        //create a testable Server with stop(), destroy() overridden to instrument
+        //call server.setStopAtShudown(true);
+        //start server
+        //call "stop" and check that server stopped but not destroyed
+        
+        //stop server
+        
+        //call server.setStopAtShutdown(false);
+        //start server
+        //call "stop" and check that the server is not stopped and not destroyed
+        System.setProperty("DEBUG", "true");
+        ShutdownMonitor.getInstance().setExitVm(false);
+      
+        ShutdownMonitor.getInstance().setPort(0);
+        TestableServer server = new TestableServer();
+        server.setStopAtShutdown(true);
+        server.start();
+        
+        //should be registered for shutdown on exit
+        assertTrue(ShutdownThread.isRegistered(server));
+        assertTrue(ShutdownMonitor.isRegistered(server));
+        
+        String key = ShutdownMonitor.getInstance().getKey();
+        int port = ShutdownMonitor.getInstance().getPort();
+        
+        stop("stop", port, key, true);
+        assertTrue(!ShutdownMonitor.getInstance().isAlive());
+        assertTrue(server.stopped);
+        assertTrue(!server.destroyed);
+        assertTrue(!ShutdownThread.isRegistered(server));
+        assertTrue(!ShutdownMonitor.isRegistered(server));
+    }
+    
+    @Test
+    public void testOldStopCommandWithStopOnShutdownFalse() throws Exception
+    {
+        //change so stopatshutdown is false, so stop does nothing in this case (as exitVm is false otherwise we couldn't run test)
+        ShutdownMonitor.getInstance().setExitVm(false);
+        System.setProperty("DEBUG", "true");
+        ShutdownMonitor.getInstance().setPort(0);
+        TestableServer server = new TestableServer();
+        server.setStopAtShutdown(false);
+        server.start();
+        
+        assertTrue(!ShutdownThread.isRegistered(server));
+        assertTrue(ShutdownMonitor.isRegistered(server));
+        
+        String key = ShutdownMonitor.getInstance().getKey();
+        int port = ShutdownMonitor.getInstance().getPort();
+        
+        stop ("stop", port, key, true);
+        assertTrue(!ShutdownMonitor.getInstance().isAlive());
+        assertTrue(!server.stopped);
+        assertTrue(!server.destroyed);
+        assertTrue(!ShutdownThread.isRegistered(server));
+        assertTrue(ShutdownMonitor.isRegistered(server));
+    }
+    
+    
+  
 
-        try
+    public void stop(String command, int port, String key, boolean check) throws Exception
+    {
+        System.out.printf("Attempting to send "+command+" to localhost:%d (%b)%n",port,check);
+        try (Socket s = new Socket(InetAddress.getByName("127.0.0.1"),port))
         {
-            //send stop command
-            s = new Socket(InetAddress.getByName("127.0.0.1"),port);
-
-            OutputStream out = s.getOutputStream();
-            out.write((key + "\r\nstop\r\n").getBytes());
-            out.flush();
-
-            if (check)
+            // send stop command
+            try (OutputStream out = s.getOutputStream())
             {
-                //wait a little
-                Thread.currentThread().sleep(600);
+                out.write((key + "\r\n"+command+"\r\n").getBytes());
+                out.flush();
 
-                //check for stop confirmation
-                LineNumberReader lin = new LineNumberReader(new InputStreamReader(s.getInputStream()));
-                String response;
-                if ((response = lin.readLine()) != null)
+                if (check)
                 {
-                    assertEquals("Stopped", response);
+                    // wait a little
+                    TimeUnit.MILLISECONDS.sleep(600);
+
+                    // check for stop confirmation
+                    LineNumberReader lin = new LineNumberReader(new InputStreamReader(s.getInputStream()));
+                    String response;
+                    if ((response = lin.readLine()) != null)
+                    {
+                        assertEquals("Stopped",response);
+                    }
+                    else
+                        throw new IllegalStateException("No stop confirmation");
                 }
-                else
-                    throw new IllegalStateException("No stop confirmation");
             }
         }
-        finally
-        {
-            if (s != null) s.close();
-        }
     }
 
 }
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/SlowClientWithPipelinedRequestTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/SlowClientWithPipelinedRequestTest.java
index 5787234..eb2def7 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/SlowClientWithPipelinedRequestTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/SlowClientWithPipelinedRequestTest.java
@@ -18,55 +18,54 @@
 
 package org.eclipse.jetty.server;
 
+import static org.hamcrest.Matchers.lessThan;
+
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.net.Socket;
-import java.nio.channels.SocketChannel;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
 import java.util.Arrays;
 import java.util.concurrent.atomic.AtomicInteger;
+
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import org.eclipse.jetty.io.AsyncEndPoint;
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.ByteArrayBuffer;
 import org.eclipse.jetty.io.Connection;
-import org.eclipse.jetty.io.nio.AsyncConnection;
+import org.eclipse.jetty.io.EndPoint;
 import org.eclipse.jetty.server.handler.AbstractHandler;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Test;
 
-import static org.hamcrest.Matchers.lessThan;
-
 public class SlowClientWithPipelinedRequestTest
 {
     private final AtomicInteger handles = new AtomicInteger();
     private Server server;
-    private SelectChannelConnector connector;
+    private ServerConnector connector;
 
     public void startServer(Handler handler) throws Exception
     {
         server = new Server();
-        connector = new SelectChannelConnector()
+        connector = new ServerConnector(server,new HttpConnectionFactory()
         {
             @Override
-            protected AsyncConnection newConnection(SocketChannel channel, AsyncEndPoint endpoint)
+            public Connection newConnection(Connector connector, EndPoint endPoint)
             {
-                return new AsyncHttpConnection(this, endpoint, getServer())
+                return configure(new HttpConnection(new HttpConfiguration(),connector,endPoint)
                 {
                     @Override
-                    public Connection handle() throws IOException
+                    public void onFillable()
                     {
                         handles.incrementAndGet();
-                        return super.handle();
+                        super.onFillable();
                     }
-                };
+                },connector,endPoint);
             }
-        };
+        });
+
         server.addConnector(connector);
         connector.setPort(0);
         server.setHandler(handler);
@@ -89,22 +88,21 @@ public class SlowClientWithPipelinedRequestTest
         final int contentLength = 512 * 1024;
         startServer(new AbstractHandler()
         {
+            @Override
             public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
                     throws IOException, ServletException
             {
                 baseRequest.setHandled(true);
-                System.err.println("target = " + target);
                 if ("/content".equals(target))
                 {
                     // We simulate what the DefaultServlet does, bypassing the blocking
                     // write mechanism otherwise the test does not reproduce the bug
-
                     OutputStream outputStream = response.getOutputStream();
-                    AbstractHttpConnection.Output output = (AbstractHttpConnection.Output)outputStream;
+                    HttpOutput output = (HttpOutput)outputStream;
                     // Since the test is via localhost, we need a really big buffer to stall the write
                     byte[] bytes = new byte[contentLength];
                     Arrays.fill(bytes, (byte)'9');
-                    Buffer buffer = new ByteArrayBuffer(bytes);
+                    ByteBuffer buffer = ByteBuffer.wrap(bytes);
                     // Do a non blocking write
                     output.sendContent(buffer);
                 }
@@ -117,7 +115,7 @@ public class SlowClientWithPipelinedRequestTest
                 "GET /content HTTP/1.1\r\n" +
                 "Host: localhost:" + connector.getLocalPort() + "\r\n" +
                 "\r\n" +
-                "").getBytes("UTF-8"));
+                "").getBytes(StandardCharsets.UTF_8));
         output.flush();
 
         InputStream input = client.getInputStream();
@@ -130,7 +128,7 @@ public class SlowClientWithPipelinedRequestTest
                 "GET /pipelined HTTP/1.1\r\n" +
                 "Host: localhost:" + connector.getLocalPort() + "\r\n" +
                 "\r\n" +
-                "").getBytes("UTF-8"));
+                "").getBytes(StandardCharsets.UTF_8));
         output.flush();
 
         // Simulate a slow reader
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/SocketConnectorCloseTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/SocketConnectorCloseTest.java
deleted file mode 100644
index e9e69f6..0000000
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/SocketConnectorCloseTest.java
+++ /dev/null
@@ -1,36 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.server;
-
-import org.eclipse.jetty.server.bio.SocketConnector;
-import org.junit.Before;
-
-
-/* ------------------------------------------------------------ */
-public class SocketConnectorCloseTest extends ConnectorCloseTestBase
-{
-    
-    /* ------------------------------------------------------------ */
-    @Before
-    public void init() throws Exception
-    {
-        System.setProperty("org.eclipse.jetty.util.log.DEBUG","true");        
-        startServer(new SocketConnector());
-    }
-}
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/SocketServerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/SocketServerTest.java
deleted file mode 100644
index 75eed4d..0000000
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/SocketServerTest.java
+++ /dev/null
@@ -1,36 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.server;
-import org.eclipse.jetty.server.bio.SocketConnector;
-import org.junit.BeforeClass;
-import org.junit.Ignore;
-import org.junit.Test;
-
-/**
- * HttpServer Tester.
- */
-public class SocketServerTest extends HttpServerTestBase
-{
-    @BeforeClass
-    public static void init() throws Exception
-    {
-        startServer(new SocketConnector());
-    }    
-    
-}
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/SocketTimeoutTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/SocketTimeoutTest.java
deleted file mode 100644
index 948c422..0000000
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/SocketTimeoutTest.java
+++ /dev/null
@@ -1,34 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.server;
-
-import org.eclipse.jetty.server.bio.SocketConnector;
-import org.junit.BeforeClass;
-
-public class SocketTimeoutTest extends ConnectorTimeoutTest
-{
-    @BeforeClass
-    public static void init() throws Exception
-    {
-        SocketConnector connector = new SocketConnector();
-        connector.setMaxIdleTime(MAX_IDLE_TIME); //250 msec max idle
-        startServer(connector);
-    }
-
-}
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/StressTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/StressTest.java
index a124a47..b3f401c 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/StressTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/StressTest.java
@@ -34,10 +34,9 @@ import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
 import org.eclipse.jetty.server.handler.HandlerWrapper;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
+import org.eclipse.jetty.toolchain.test.AdvancedRunner;
 import org.eclipse.jetty.toolchain.test.OS;
-import org.eclipse.jetty.toolchain.test.PropertyFlag;
-import org.eclipse.jetty.util.BlockingArrayQueue;
+import org.eclipse.jetty.toolchain.test.annotation.Stress;
 import org.eclipse.jetty.util.IO;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
@@ -46,14 +45,16 @@ import org.junit.AfterClass;
 import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 
+ at RunWith(AdvancedRunner.class)
 public class StressTest
 {
     private static final Logger LOG = Log.getLogger(StressTest.class);
 
     private static QueuedThreadPool _threads;
     private static Server _server;
-    private static SelectChannelConnector _connector;
+    private static ServerConnector _connector;
     private static final AtomicInteger _handled=new AtomicInteger(0);
     private static final ConcurrentLinkedQueue[] _latencies= {
             new ConcurrentLinkedQueue<Long>(),
@@ -89,16 +90,14 @@ public class StressTest
     @BeforeClass
     public static void init() throws Exception
     {
-        _threads = new QueuedThreadPool(new BlockingArrayQueue<Runnable>(4,4));
+        _threads = new QueuedThreadPool();
         _threads.setMaxThreads(200);
 
-        _server = new Server();
-        _server.setThreadPool(_threads);
-
-        _connector = new SelectChannelConnector();
-        _connector.setAcceptors(1);
+        _server = new Server(_threads);
+        _server.manage(_threads);
+        _connector = new ServerConnector(_server,null,null,null,1, 1,new HttpConnectionFactory());
         _connector.setAcceptQueueSize(5000);
-        _connector.setMaxIdleTime(30000);
+        _connector.setIdleTimeout(30000);
         _server.addConnector(_connector);
 
         TestHandler _handler = new TestHandler();
@@ -123,35 +122,45 @@ public class StressTest
     }
 
     @Test
+    public void testMinNonPersistent() throws Throwable
+    {
+        assumeTrue(!OS.IS_OSX);
+        doThreads(10,10,false);
+    }
+
+    @Test
+    @Stress("Hey, its called StressTest for a reason")
     public void testNonPersistent() throws Throwable
     {
         // TODO needs to be further investigated
-        assumeTrue(!OS.IS_OSX || PropertyFlag.isEnabled("test.stress"));
-    	
-        doThreads(10,10,false);
-        if (PropertyFlag.isEnabled("test.stress"))
-        {
-            Thread.sleep(1000);
-            doThreads(200,10,false);
-            Thread.sleep(1000);
-            doThreads(200,200,false);
-        }
+        assumeTrue(!OS.IS_OSX);
+
+        doThreads(20,20,false);
+        Thread.sleep(1000);
+        doThreads(200,10,false);
+        Thread.sleep(1000);
+        doThreads(200,200,false);
     }
 
     @Test
+    public void testMinPersistent() throws Throwable
+    {
+        // TODO needs to be further investigated
+        assumeTrue(!OS.IS_OSX);
+        doThreads(10,10,true);
+    }
+    
+    @Test
+    @Stress("Hey, its called StressTest for a reason")
     public void testPersistent() throws Throwable
     {
         // TODO needs to be further investigated
-        assumeTrue(!OS.IS_OSX || PropertyFlag.isEnabled("test.stress"));
-    	
-        doThreads(20,10,true);
-        if (PropertyFlag.isEnabled("test.stress"))
-        {
-            Thread.sleep(1000);
-            doThreads(200,10,true);
-            Thread.sleep(1000);
-            doThreads(200,200,true);
-        }
+        assumeTrue(!OS.IS_OSX);
+        doThreads(40,40,true);
+        Thread.sleep(1000);
+        doThreads(200,10,true);
+        Thread.sleep(1000);
+        doThreads(200,200,true);
     }
 
     private void doThreads(int threadCount, final int loops, final boolean persistent) throws Throwable
@@ -228,7 +237,6 @@ public class StressTest
                     {
                         System.err.println("STALLED!!!");
                         System.err.println(_server.getThreadPool().toString());
-                        ((SelectChannelConnector)(_server.getConnectors()[0])).dump();
                         Thread.sleep(5000);
                         System.exit(1);
                     }
@@ -253,7 +261,7 @@ public class StressTest
         }
         finally
         {
-            System.err.println();
+            // System.err.println();
             final int quantums=48;
             final int[][] count = new int[_latencies.length][quantums];
             final int length[] = new int[_latencies.length];
@@ -287,22 +295,22 @@ public class StressTest
             System.out.println("           stage:\tbind\twrite\trecv\tdispatch\twrote\ttotal");
             for (int q=0;q<quantums;q++)
             {
-                System.out.print(q+"00<=latency<"+(q+1)+"00");
+                System.out.printf("%02d00<=l<%02d00",q,(q+1));
                 for (int i=0;i<_latencies.length;i++)
                     System.out.print("\t"+count[i][q]);
                 System.out.println();
             }
 
-            System.out.print("other            ");
+            System.out.print("other       ");
             for (int i=0;i<_latencies.length;i++)
                 System.out.print("\t"+other[i]);
             System.out.println();
 
-            System.out.print("HANDLED          ");
+            System.out.print("HANDLED     ");
             for (int i=0;i<_latencies.length;i++)
                 System.out.print("\t"+_handled.get());
             System.out.println();
-            System.out.print("TOTAL             ");
+            System.out.print("TOTAL       ");
             for (int i=0;i<_latencies.length;i++)
                 System.out.print("\t"+length[i]);
             System.out.println();
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/SuspendHandler.java b/jetty-server/src/test/java/org/eclipse/jetty/server/SuspendHandler.java
index 9e9d7ac..f14b6a8 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/SuspendHandler.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/SuspendHandler.java
@@ -20,6 +20,7 @@ package org.eclipse.jetty.server;
 
 import java.io.IOException;
 import java.io.InputStream;
+
 import javax.servlet.AsyncContext;
 import javax.servlet.AsyncEvent;
 import javax.servlet.AsyncListener;
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/AbstractConnectHandlerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/AbstractConnectHandlerTest.java
deleted file mode 100644
index 9c84309..0000000
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/AbstractConnectHandlerTest.java
+++ /dev/null
@@ -1,217 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.server.handler;
-
-import java.io.BufferedReader;
-import java.io.EOFException;
-import java.io.IOException;
-import java.net.Socket;
-import java.net.SocketTimeoutException;
-import java.util.LinkedHashMap;
-import java.util.Locale;
-import java.util.Map;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import org.eclipse.jetty.server.Connector;
-import org.eclipse.jetty.server.Handler;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
-import org.junit.After;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-public abstract class AbstractConnectHandlerTest
-{
-    protected Server server;
-    protected Connector serverConnector;
-    protected Server proxy;
-    protected Connector proxyConnector;
-
-    protected void startServer(Connector connector, Handler handler) throws Exception
-    {
-        server = new Server();
-        serverConnector = connector;
-        server.addConnector(serverConnector);
-        server.setHandler(handler);
-        server.start();
-    }
-
-    protected void startProxy() throws Exception
-    {
-        startProxy(new SelectChannelConnector(), new ConnectHandler());
-    }
-
-    protected void startProxy(Connector connector, ConnectHandler connectHandler) throws Exception
-    {
-        proxy = new Server();
-        proxyConnector = connector;
-        proxy.addConnector(proxyConnector);
-        proxy.setHandler(connectHandler);
-        proxy.start();
-    }
-
-    @After
-    public void stop() throws Exception
-    {
-        stopProxy();
-        stopServer();
-    }
-
-    protected void stopServer() throws Exception
-    {
-        server.stop();
-        server.join();
-    }
-
-    protected void stopProxy() throws Exception
-    {
-        proxy.stop();
-        proxy.join();
-    }
-
-    protected Response readResponse(BufferedReader reader) throws IOException
-    {
-        // Simplified parser for HTTP responses
-        String line = reader.readLine();
-        if (line == null)
-            throw new EOFException();
-        Matcher responseLine = Pattern.compile("HTTP/1\\.1\\s+(\\d+)").matcher(line);
-        assertTrue(responseLine.lookingAt());
-        String code = responseLine.group(1);
-
-        Map<String, String> headers = new LinkedHashMap<String, String>();
-        while ((line = reader.readLine()) != null)
-        {
-            if (line.trim().length() == 0)
-                break;
-
-            Matcher header = Pattern.compile("([^:]+):\\s*(.*)").matcher(line);
-            assertTrue(header.lookingAt());
-            String headerName = header.group(1);
-            String headerValue = header.group(2);
-            headers.put(headerName.toLowerCase(Locale.ENGLISH), headerValue.toLowerCase(Locale.ENGLISH));
-        }
-
-        StringBuilder body;
-        if (headers.containsKey("content-length"))
-        {
-            int readLen = 0;
-            int length = Integer.parseInt(headers.get("content-length"));
-            body = new StringBuilder(length);
-            try
-            {
-                for (int i = 0; i < length; ++i)
-                {
-                    char c = (char)reader.read();
-                    body.append(c);
-                    readLen++;
-                }
-            }
-            catch (SocketTimeoutException e)
-            {
-                System.err.printf("Read %,d bytes (out of an expected %,d bytes)%n", readLen, length);
-                throw e;
-            }
-        }
-        else if ("chunked".equals(headers.get("transfer-encoding")))
-        {
-            body = new StringBuilder(64 * 1024);
-            while ((line = reader.readLine()) != null)
-            {
-                if ("0".equals(line))
-                {
-                    line = reader.readLine();
-                    assertEquals("", line);
-                    break;
-                }
-
-                try
-                {
-                    Thread.sleep(5);
-                }
-                catch (InterruptedException e)
-                {
-                    e.printStackTrace();
-                }
-
-                int length = Integer.parseInt(line, 16);
-                for (int i = 0; i < length; ++i)
-                {
-                    char c = (char)reader.read();
-                    body.append(c);
-                }
-                line = reader.readLine();
-                assertEquals("", line);
-            }
-        }
-        else throw new IllegalStateException();
-
-        return new Response(code, headers, body.toString().trim());
-    }
-
-    protected Socket newSocket() throws IOException
-    {
-        Socket socket = new Socket("localhost", proxyConnector.getLocalPort());
-        socket.setSoTimeout(10000);
-        return socket;
-    }
-
-    protected class Response
-    {
-        private final String code;
-        private final Map<String, String> headers;
-        private final String body;
-
-        private Response(String code, Map<String, String> headers, String body)
-        {
-            this.code = code;
-            this.headers = headers;
-            this.body = body;
-        }
-
-        public String getCode()
-        {
-            return code;
-        }
-
-        public Map<String, String> getHeaders()
-        {
-            return headers;
-        }
-
-        public String getBody()
-        {
-            return body;
-        }
-
-        @Override
-        public String toString()
-        {
-            StringBuilder builder = new StringBuilder();
-            builder.append(code).append("\r\n");
-            for (Map.Entry<String, String> entry : headers.entrySet())
-                builder.append(entry.getKey()).append(": ").append(entry.getValue()).append("\r\n");
-            builder.append("\r\n");
-            builder.append(body);
-            return builder.toString();
-        }
-    }
-}
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/AllowAllVerifier.java b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/AllowAllVerifier.java
new file mode 100644
index 0000000..46e0de7
--- /dev/null
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/AllowAllVerifier.java
@@ -0,0 +1,31 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.handler;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.SSLSession;
+
+public class AllowAllVerifier implements HostnameVerifier
+{
+    @Override
+    public boolean verify(String hostname, SSLSession session)
+    {
+        return true;
+    }
+}
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/BadRequestLogHandlerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/BadRequestLogHandlerTest.java
new file mode 100644
index 0000000..a18f192
--- /dev/null
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/BadRequestLogHandlerTest.java
@@ -0,0 +1,193 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.handler;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.SocketAddress;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.RequestLog;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Testing oddball request scenarios (like error 400) where the error should
+ * be logged 
+ */
+ at RunWith(Parameterized.class)
+ at Ignore
+public class BadRequestLogHandlerTest
+{
+    private static final Logger LOG = Log.getLogger(BadRequestLogHandlerTest.class);
+    
+    public static class CaptureLog extends AbstractLifeCycle implements RequestLog
+    {
+        public List<String> captured = new ArrayList<>();
+
+        @Override
+        public void log(Request request, Response response)
+        {
+            captured.add(String.format("%s %s %s %03d",request.getMethod(),request.getUri().toString(),request.getProtocol(),response.getStatus()));
+        }
+    }
+    
+    private static class HelloHandler extends AbstractHandler
+    {
+        @Override
+        public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+        {
+            response.setContentType("text/plain");
+            response.getWriter().print("Hello World");
+            baseRequest.setHandled(true);
+        }
+    }
+    
+    @Parameters
+    public static List<Object[]> data()
+    {
+        List<Object[]> data = new ArrayList<>();
+        
+        data.add(new String[]{ "GET /bad VER\r\n"
+                + "Host: localhost\r\n"
+                + "Connection: close\r\n\r\n" , 
+                "GET <invalidrequest> HTTP/1.1 400" });
+        data.add(new String[]{ "GET /%%adsasd HTTP/1.1\r\n"
+                + "Host: localhost\r\n"
+                + "Connection: close\r\n\r\n" , 
+                "GET <invalidrequest> HTTP/1.1 400" });
+        
+        return data;
+    }
+    
+    @Parameter(0)
+    public String requestHeader;
+    
+    @Parameter(1)
+    public String expectedLog;
+    
+    @Test(timeout=4000)
+    public void testLogHandler() throws Exception
+    {
+        Server server = new Server();
+        ServerConnector connector = new ServerConnector(server);
+        connector.setPort(0);
+        server.setConnectors(new Connector[] { connector });
+
+        CaptureLog captureLog = new CaptureLog();
+
+        RequestLogHandler requestLog = new RequestLogHandler();
+        requestLog.setRequestLog(captureLog);
+        
+        requestLog.setHandler(new HelloHandler());
+
+        server.setHandler(requestLog);
+
+        try
+        {
+            server.start();
+            
+            String host = connector.getHost();
+            if (host == null)
+            {
+                host = "localhost";
+            }
+
+            InetAddress destAddr = InetAddress.getByName(host);
+            int port = connector.getLocalPort();
+            SocketAddress endpoint = new InetSocketAddress(destAddr,port);
+
+            Socket socket = new Socket();
+            socket.setSoTimeout(1000);
+            socket.connect(endpoint);
+
+            try(OutputStream out = socket.getOutputStream();
+                OutputStreamWriter writer = new OutputStreamWriter(out,StandardCharsets.UTF_8);
+                InputStream in = socket.getInputStream();
+                InputStreamReader reader = new InputStreamReader(in,StandardCharsets.UTF_8))
+            {
+                StringReader request = new StringReader(requestHeader);
+                IO.copy(request,writer);
+                writer.flush();
+                StringWriter response = new StringWriter();
+                IO.copy(reader,response);
+                LOG.info("Response: {}",response);
+            } finally {
+                socket.close();
+            }
+
+            assertRequestLog(captureLog);
+        }
+        finally
+        {
+            server.stop();
+        }
+    }
+    
+    private void assertRequestLog(CaptureLog captureLog)
+    {
+        int captureCount = captureLog.captured.size();
+
+        if (captureCount != 1)
+        {
+            LOG.warn("Capture Log size is {}, expected to be 1",captureCount);
+            if (captureCount > 1)
+            {
+                for (int i = 0; i < captureCount; i++)
+                {
+                    LOG.warn("[{}] {}",i,captureLog.captured.get(i));
+                }
+            }
+            assertThat("Capture Log Entry Count",captureLog.captured.size(),is(1));
+        }
+
+        String actual = captureLog.captured.get(0);
+        assertThat("Capture Log",actual,is(expectedLog));
+    }
+}
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ConnectHandlerSSLTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ConnectHandlerSSLTest.java
deleted file mode 100644
index 4b192a3..0000000
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ConnectHandlerSSLTest.java
+++ /dev/null
@@ -1,362 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.server.handler;
-
-import java.io.BufferedReader;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.net.Socket;
-import java.nio.channels.SocketChannel;
-import java.security.SecureRandom;
-import java.security.cert.CertificateException;
-import java.security.cert.X509Certificate;
-import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.atomic.AtomicReference;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLSocket;
-import javax.net.ssl.SSLSocketFactory;
-import javax.net.ssl.TrustManager;
-import javax.net.ssl.X509TrustManager;
-import javax.servlet.ServletException;
-import javax.servlet.ServletOutputStream;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jetty.io.AsyncEndPoint;
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.server.Connector;
-import org.eclipse.jetty.server.Request;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
-import org.eclipse.jetty.server.ssl.SslSelectChannelConnector;
-import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
-import org.eclipse.jetty.util.ssl.SslContextFactory;
-import org.junit.Before;
-import org.junit.Test;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-
-public class ConnectHandlerSSLTest extends AbstractConnectHandlerTest
-{
-    @Before
-    public void init() throws Exception
-    {
-        startServer(prepareServerConnector(), new ServerHandler());
-        startProxy();
-    }
-
-    private SslSelectChannelConnector prepareServerConnector()
-    {
-        SslSelectChannelConnector connector = new SslSelectChannelConnector();
-        String keyStorePath = MavenTestingUtils.getTestResourceFile("keystore").getAbsolutePath();
-        SslContextFactory cf = connector.getSslContextFactory();
-        cf.setKeyStorePath(keyStorePath);
-        cf.setKeyStorePassword("storepwd");
-        cf.setKeyManagerPassword("keypwd");
-        return connector;
-    }
-
-    @Test
-    public void testGETRequest() throws Exception
-    {
-        String hostPort = "localhost:" + serverConnector.getLocalPort();
-        String request = "" +
-                "CONNECT " + hostPort + " HTTP/1.1\r\n" +
-                "Host: " + hostPort + "\r\n" +
-                "\r\n";
-        Socket socket = newSocket();
-        try
-        {
-            OutputStream output = socket.getOutputStream();
-            BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
-
-            output.write(request.getBytes("UTF-8"));
-            output.flush();
-
-            // Expect 200 OK from the CONNECT request
-            Response response = readResponse(input);
-            System.err.println(response);
-            assertEquals("200", response.getCode());
-
-            // Be sure the buffered input does not have anything buffered
-            assertFalse(input.ready());
-
-            // Upgrade the socket to SSL
-            SSLSocket sslSocket = wrapSocket(socket);
-            try
-            {
-                output = sslSocket.getOutputStream();
-                input = new BufferedReader(new InputStreamReader(sslSocket.getInputStream()));
-
-                request =
-                        "GET /echo HTTP/1.1\r\n" +
-                                "Host: " + hostPort + "\r\n" +
-                                "\r\n";
-                output.write(request.getBytes("UTF-8"));
-                output.flush();
-
-                response = readResponse(input);
-                assertEquals("200", response.getCode());
-                assertEquals("GET /echo", response.getBody());
-            }
-            finally
-            {
-                sslSocket.close();
-            }
-        }
-        finally
-        {
-            socket.close();
-        }
-    }
-
-    @Test
-    public void testPOSTRequests() throws Exception
-    {
-        String hostPort = "localhost:" + serverConnector.getLocalPort();
-        String request = "" +
-                "CONNECT " + hostPort + " HTTP/1.1\r\n" +
-                "Host: " + hostPort + "\r\n" +
-                "\r\n";
-        Socket socket = newSocket();
-        try
-        {
-            OutputStream output = socket.getOutputStream();
-            BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
-
-            output.write(request.getBytes("UTF-8"));
-            output.flush();
-
-            // Expect 200 OK from the CONNECT request
-            Response response = readResponse(input);
-            assertEquals("200", response.getCode());
-
-            // Be sure the buffered input does not have anything buffered
-            assertFalse(input.ready());
-
-            // Upgrade the socket to SSL
-            SSLSocket sslSocket = wrapSocket(socket);
-            try
-            {
-                output = sslSocket.getOutputStream();
-                input = new BufferedReader(new InputStreamReader(sslSocket.getInputStream()));
-
-                for (int i = 0; i < 10; ++i)
-                {
-                    request = "" +
-                            "POST /echo?param=" + i + " HTTP/1.1\r\n" +
-                            "Host: " + hostPort + "\r\n" +
-                            "Content-Length: 5\r\n" +
-                            "\r\n" +
-                            "HELLO";
-                    output.write(request.getBytes("UTF-8"));
-                    output.flush();
-
-                    response = readResponse(input);
-                    assertEquals("200", response.getCode());
-                    assertEquals("POST /echo?param=" + i + "\r\nHELLO", response.getBody());
-                }
-            }
-            finally
-            {
-                sslSocket.close();
-            }
-        }
-        finally
-        {
-            socket.close();
-        }
-    }
-
-    @Test
-    public void testServerHalfClosesClientDoesNotCloseExpectIdleTimeout() throws Exception
-    {
-        stop();
-
-        final String uri = "/echo";
-        int idleTimeout = 2000;
-        startServer(prepareServerConnector(), new AbstractHandler()
-        {
-            public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
-            {
-                String requestURI = request.getRequestURI();
-                if (uri.equals(requestURI))
-                {
-                    baseRequest.setHandled(true);
-                    response.setHeader("Connection", "close");
-                    response.setContentLength(uri.length());
-                    response.getOutputStream().print(uri);
-                }
-            }
-        });
-        Connector proxyConnector = new SelectChannelConnector();
-        proxyConnector.setMaxIdleTime(idleTimeout);
-
-        final AtomicReference<EndPoint> proxyToClientEndPoint = new AtomicReference<EndPoint>();
-        final AtomicReference<EndPoint> proxyToServerEndPoint = new AtomicReference<EndPoint>();
-        ConnectHandler connectHandler = new ConnectHandler()
-        {
-            @Override
-            protected ClientToProxyConnection newClientToProxyConnection(ConcurrentMap<String, Object> context, SocketChannel channel, EndPoint endPoint, long timeStamp)
-            {
-                proxyToClientEndPoint.set(endPoint);
-                return new ClientToProxyConnection(context, channel, endPoint, timeStamp);
-            }
-
-            @Override
-            protected ProxyToServerConnection newProxyToServerConnection(ConcurrentMap<String, Object> context, Buffer buffer)
-            {
-                return new ProxyToServerConnection(context, buffer)
-                {
-                    @Override
-                    public void setEndPoint(AsyncEndPoint endpoint)
-                    {
-                        proxyToServerEndPoint.set(endpoint);
-                        super.setEndPoint(endpoint);
-                    }
-                };
-            }
-        };
-        connectHandler.setWriteTimeout(2 * idleTimeout);
-        startProxy(proxyConnector, connectHandler);
-
-        String hostPort = "localhost:" + serverConnector.getLocalPort();
-        String request = "" +
-                "CONNECT " + hostPort + " HTTP/1.1\r\n" +
-                "Host: " + hostPort + "\r\n" +
-                "\r\n";
-        Socket socket = newSocket();
-        try
-        {
-            OutputStream output = socket.getOutputStream();
-            BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
-
-            output.write(request.getBytes("UTF-8"));
-            output.flush();
-
-            // Expect 200 OK from the CONNECT request
-            Response response = readResponse(input);
-            System.err.println(response);
-            assertEquals("200", response.getCode());
-
-            // Be sure the buffered input does not have anything buffered
-            assertFalse(input.ready());
-
-            // Upgrade the socket to SSL
-            SSLSocket sslSocket = wrapSocket(socket);
-            try
-            {
-                output = sslSocket.getOutputStream();
-                input = new BufferedReader(new InputStreamReader(sslSocket.getInputStream()));
-
-                request = "" +
-                        "GET " + uri + " HTTP/1.1\r\n" +
-                        "Host: " + hostPort + "\r\n" +
-                        "\r\n";
-                output.write(request.getBytes("UTF-8"));
-                output.flush();
-
-                response = readResponse(input);
-                assertEquals("200", response.getCode());
-                assertEquals(uri, response.getBody());
-
-                Thread.sleep(4 * idleTimeout);
-
-                EndPoint p2c = proxyToClientEndPoint.get();
-                assertNotNull(p2c);
-                assertFalse(p2c.isOpen());
-                EndPoint p2s = proxyToServerEndPoint.get();
-                assertNotNull(p2s);
-                assertFalse(p2s.isOpen());
-            }
-            finally
-            {
-                sslSocket.close();
-            }
-        }
-        finally
-        {
-            socket.close();
-        }
-    }
-
-    private SSLSocket wrapSocket(Socket socket) throws Exception
-    {
-        SSLContext sslContext = SSLContext.getInstance("SSLv3");
-        sslContext.init(null, new TrustManager[]{new AlwaysTrustManager()}, new SecureRandom());
-        SSLSocketFactory socketFactory = sslContext.getSocketFactory();
-        SSLSocket sslSocket = (SSLSocket)socketFactory.createSocket(socket, socket.getInetAddress().getHostAddress(), socket.getPort(), true);
-        sslSocket.setUseClientMode(true);
-        sslSocket.startHandshake();
-        return sslSocket;
-    }
-
-    private class AlwaysTrustManager implements X509TrustManager
-    {
-        public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException
-        {
-        }
-
-        public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException
-        {
-        }
-
-        public X509Certificate[] getAcceptedIssuers()
-        {
-            return new X509Certificate[]{};
-        }
-    }
-
-    private static class ServerHandler extends AbstractHandler
-    {
-        public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException
-        {
-            request.setHandled(true);
-
-            String uri = httpRequest.getRequestURI();
-            if ("/echo".equals(uri))
-            {
-                StringBuilder builder = new StringBuilder();
-                builder.append(httpRequest.getMethod()).append(" ").append(uri);
-                if (httpRequest.getQueryString() != null)
-                    builder.append("?").append(httpRequest.getQueryString());
-
-                ByteArrayOutputStream baos = new ByteArrayOutputStream();
-                InputStream input = httpRequest.getInputStream();
-                int read;
-                while ((read = input.read()) >= 0)
-                    baos.write(read);
-                baos.close();
-
-                ServletOutputStream output = httpResponse.getOutputStream();
-                output.println(builder.toString());
-                output.write(baos.toByteArray());
-            }
-            else
-            {
-                throw new ServletException();
-            }
-        }
-    }
-}
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ConnectHandlerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ConnectHandlerTest.java
deleted file mode 100644
index 7b6cee6..0000000
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ConnectHandlerTest.java
+++ /dev/null
@@ -1,676 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.server.handler;
-
-import java.io.BufferedReader;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.net.InetAddress;
-import java.net.Socket;
-import java.net.UnknownHostException;
-import java.nio.channels.SocketChannel;
-import java.util.concurrent.ConcurrentMap;
-import javax.servlet.ServletException;
-import javax.servlet.ServletOutputStream;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.server.Request;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
-import org.eclipse.jetty.toolchain.test.OS;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assume.assumeTrue;
-
-public class ConnectHandlerTest extends AbstractConnectHandlerTest
-{
-    @Before
-    public void init() throws Exception
-    {
-        startServer(new SelectChannelConnector(), new ServerHandler());
-        startProxy();
-    }
-
-    @Test
-    public void testCONNECT() throws Exception
-    {
-        String hostPort = "localhost:" + serverConnector.getLocalPort();
-        String request = "" +
-                "CONNECT " + hostPort + " HTTP/1.1\r\n" +
-                "Host: " + hostPort + "\r\n" +
-                "\r\n";
-        Socket socket = newSocket();
-        try
-        {
-            OutputStream output = socket.getOutputStream();
-            BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
-
-            output.write(request.getBytes("UTF-8"));
-            output.flush();
-
-            // Expect 200 OK from the CONNECT request
-            Response response = readResponse(input);
-            assertEquals("200", response.getCode());
-        }
-        finally
-        {
-            socket.close();
-        }
-    }
-
-    @Test
-    public void testCONNECTAndGET() throws Exception
-    {
-        String hostPort = "localhost:" + serverConnector.getLocalPort();
-        String request = "" +
-                "CONNECT " + hostPort + " HTTP/1.1\r\n" +
-                "Host: " + hostPort + "\r\n" +
-                "\r\n";
-        Socket socket = newSocket();
-        socket.setSoTimeout(30000);
-        try
-        {
-            OutputStream output = socket.getOutputStream();
-            BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
-
-            output.write(request.getBytes("UTF-8"));
-            output.flush();
-
-            // Expect 200 OK from the CONNECT request
-            Response response = readResponse(input);
-            assertEquals("200", response.getCode());
-
-            request = "" +
-                    "GET /echo" + " HTTP/1.1\r\n" +
-                    "Host: " + hostPort + "\r\n" +
-                    "\r\n";
-            output.write(request.getBytes("UTF-8"));
-            output.flush();
-
-            response = readResponse(input);
-            assertEquals("200", response.getCode());
-            assertEquals("GET /echo", response.getBody());
-        }
-        finally
-        {
-            socket.close();
-        }
-    }
-
-    
-    @Test
-    public void testCONNECTBadHostPort() throws Exception
-    {
-        String invalidHostname = "AMAZEBALLS_BADHOST.webtide.com";
-        
-        try
-        {
-            InetAddress addr = InetAddress.getByName(invalidHostname);
-            StringBuilder err = new StringBuilder();
-            err.append("DNS Hijacking detected: ");
-            err.append(invalidHostname).append(" should have not returned a valid IP address [");
-            err.append(addr.getHostAddress()).append("].  ");
-            err.append("Fix your DNS provider to have this test pass.");
-            err.append("\nFor more info see https://en.wikipedia.org/wiki/DNS_hijacking");
-            Assert.assertNull(err.toString(), addr);
-        }
-        catch (UnknownHostException e)
-        {
-            // expected path
-        }
-        
-        String hostPort = String.format("%s:%d",invalidHostname,serverConnector.getLocalPort());
-        String request = "" +
-                "CONNECT " + hostPort + " HTTP/1.1\r\n" +
-                "Host: " + hostPort + "\r\n" +
-                "\r\n";
-        Socket socket = newSocket();
-        socket.setSoTimeout(30000);
-        try
-        {
-            OutputStream output = socket.getOutputStream();
-            BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
-
-            output.write(request.getBytes("UTF-8"));
-            output.flush();
-
-            // Expect 500 OK from the CONNECT request
-            Response response = readResponse(input);
-            assertEquals("Response Code", "500", response.getCode());
-        }
-        finally
-        {
-            socket.close();
-        }
-    }
-    
-    @Test
-    public void testCONNECT10AndGET() throws Exception
-    {
-        String hostPort = "localhost:" + serverConnector.getLocalPort();
-        String request = "" +
-                "CONNECT " + hostPort + " HTTP/1.0\r\n" +
-                "Host: " + hostPort + "\r\n" +
-                "\r\n";
-        Socket socket = newSocket();
-        try
-        {
-            OutputStream output = socket.getOutputStream();
-            BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
-
-            output.write(request.getBytes("UTF-8"));
-            output.flush();
-
-            // Expect 200 OK from the CONNECT request
-            Response response = readResponse(input);
-            assertEquals("200", response.getCode());
-
-            request = "" +
-                    "GET /echo" + " HTTP/1.1\r\n" +
-                    "Host: " + hostPort + "\r\n" +
-                    "\r\n";
-            output.write(request.getBytes("UTF-8"));
-            output.flush();
-
-            response = readResponse(input);
-            assertEquals("200", response.getCode());
-            assertEquals("GET /echo", response.getBody());
-        }
-        finally
-        {
-            socket.close();
-        }
-    }
-
-    @Test
-    public void testCONNECTAndGETPipelined() throws Exception
-    {
-        String hostPort = "localhost:" + serverConnector.getLocalPort();
-        String request = "" +
-                "CONNECT " + hostPort + " HTTP/1.1\r\n" +
-                "Host: " + hostPort + "\r\n" +
-                "\r\n" +
-                "GET /echo" + " HTTP/1.1\r\n" +
-                "Host: " + hostPort + "\r\n" +
-                "\r\n";
-        Socket socket = newSocket();
-        try
-        {
-            OutputStream output = socket.getOutputStream();
-            BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
-
-            output.write(request.getBytes("UTF-8"));
-            output.flush();
-
-            // Expect 200 OK from the CONNECT request
-            Response response = readResponse(input);
-            assertEquals("200", response.getCode());
-
-            // The pipelined request must have gone up to the server as is
-            response = readResponse(input);
-            assertEquals("200", response.getCode());
-            assertEquals("GET /echo", response.getBody());
-        }
-        finally
-        {
-            socket.close();
-        }
-    }
-
-    @Test
-    public void testCONNECTAndMultipleGETs() throws Exception
-    {
-        String hostPort = "localhost:" + serverConnector.getLocalPort();
-        String request = "" +
-                "CONNECT " + hostPort + " HTTP/1.1\r\n" +
-                "Host: " + hostPort + "\r\n" +
-                "\r\n";
-        Socket socket = newSocket();
-        try
-        {
-            OutputStream output = socket.getOutputStream();
-            BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
-
-            output.write(request.getBytes("UTF-8"));
-            output.flush();
-
-            // Expect 200 OK from the CONNECT request
-            Response response = readResponse(input);
-            assertEquals("200", response.getCode());
-
-            for (int i = 0; i < 10; ++i)
-            {
-                request = "" +
-                        "GET /echo" + " HTTP/1.1\r\n" +
-                        "Host: " + hostPort + "\r\n" +
-                        "\r\n";
-                output.write(request.getBytes("UTF-8"));
-                output.flush();
-
-                response = readResponse(input);
-                assertEquals("200", response.getCode());
-                assertEquals("GET /echo", response.getBody());
-            }
-        }
-        finally
-        {
-            socket.close();
-        }
-    }
-
-    @Test
-    public void testCONNECTAndGETServerStop() throws Exception
-    {
-        String hostPort = "localhost:" + serverConnector.getLocalPort();
-        String request = "" +
-                "CONNECT " + hostPort + " HTTP/1.1\r\n" +
-                "Host: " + hostPort + "\r\n" +
-                "\r\n";
-        Socket socket = newSocket();
-        try
-        {
-            OutputStream output = socket.getOutputStream();
-            BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
-
-            output.write(request.getBytes("UTF-8"));
-            output.flush();
-
-            // Expect 200 OK from the CONNECT request
-            Response response = readResponse(input);
-            assertEquals("200", response.getCode());
-
-            request = "" +
-                    "GET /echo HTTP/1.1\r\n" +
-                    "Host: " + hostPort + "\r\n" +
-                    "\r\n";
-            output.write(request.getBytes("UTF-8"));
-            output.flush();
-
-            response = readResponse(input);
-            assertEquals("200", response.getCode());
-            assertEquals("GET /echo", response.getBody());
-
-            // Idle server is shut down
-            stopServer();
-
-            int read = input.read();
-            assertEquals(-1, read);
-        }
-        finally
-        {
-            socket.close();
-            // Restart the server for the next test
-            server.start();
-        }
-    }
-
-    @Test
-    public void testCONNECTAndGETAndServerSideClose() throws Exception
-    {
-        String hostPort = "localhost:" + serverConnector.getLocalPort();
-        String request = "" +
-                "CONNECT " + hostPort + " HTTP/1.1\r\n" +
-                "Host: " + hostPort + "\r\n" +
-                "\r\n";
-        Socket socket = newSocket();
-        try
-        {
-            OutputStream output = socket.getOutputStream();
-            BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
-
-            output.write(request.getBytes("UTF-8"));
-            output.flush();
-
-            // Expect 200 OK from the CONNECT request
-            Response response = readResponse(input);
-            assertEquals("200", response.getCode());
-
-            request = "" +
-                    "GET /close HTTP/1.1\r\n" +
-                    "Host: " + hostPort + "\r\n" +
-                    "\r\n";
-            output.write(request.getBytes("UTF-8"));
-            output.flush();
-
-            int read = input.read();
-            assertEquals(-1, read);
-        }
-        finally
-        {
-            socket.close();
-        }
-    }
-
-    @Test
-    public void testCONNECTAndPOSTAndGET() throws Exception
-    {
-        String hostPort = "localhost:" + serverConnector.getLocalPort();
-        String request = "" +
-                "CONNECT " + hostPort + " HTTP/1.1\r\n" +
-                "Host: " + hostPort + "\r\n" +
-                "\r\n";
-        Socket socket = newSocket();
-        try
-        {
-            OutputStream output = socket.getOutputStream();
-            BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
-
-            output.write(request.getBytes("UTF-8"));
-            output.flush();
-
-            // Expect 200 OK from the CONNECT request
-            Response response = readResponse(input);
-            assertEquals("200", response.getCode());
-
-            request = "" +
-                    "POST /echo HTTP/1.1\r\n" +
-                    "Host: " + hostPort + "\r\n" +
-                    "Content-Length: 5\r\n" +
-                    "\r\n" +
-                    "HELLO";
-            output.write(request.getBytes("UTF-8"));
-            output.flush();
-
-            response = readResponse(input);
-            assertEquals("200", response.getCode());
-            assertEquals("POST /echo\r\nHELLO", response.getBody());
-
-            request = "" +
-                    "GET /echo" + " HTTP/1.1\r\n" +
-                    "Host: " + hostPort + "\r\n" +
-                    "\r\n";
-            output.write(request.getBytes("UTF-8"));
-            output.flush();
-
-            response = readResponse(input);
-            assertEquals("200", response.getCode());
-            assertEquals("GET /echo", response.getBody());
-        }
-        finally
-        {
-            socket.close();
-        }
-    }
-
-    @Test
-    public void testCONNECTAndPOSTWithBigBody() throws Exception
-    {
-    	// fails under windows and occasionally on mac due to OOME
-    	boolean stress = Boolean.getBoolean( "STRESS" );
-    	
-    	if (!stress)
-    	{
-    		return;
-    	}
-    	
-        // Log.getLogger(ConnectHandler.class).setDebugEnabled(true);
-        String hostPort = "localhost:" + serverConnector.getLocalPort();
-        String request = "" +
-                "CONNECT " + hostPort + " HTTP/1.1\r\n" +
-                "Host: " + hostPort + "\r\n" +
-                "\r\n";
-        Socket socket = newSocket();
-        try
-        {
-            OutputStream output = socket.getOutputStream();
-            BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
-
-            output.write(request.getBytes("UTF-8"));
-            output.flush();
-
-            // Expect 200 OK from the CONNECT request
-            Response response = readResponse(input);
-            assertEquals("200", response.getCode());
-
-            StringBuilder body = new StringBuilder();
-            String chunk = "0123456789ABCDEF";
-            for (int i = 0; i < 1024 * 1024; ++i)
-                body.append(chunk);
-
-            request = "" +
-                    "POST /echo HTTP/1.1\r\n" +
-                    "Host: " + hostPort + "\r\n" +
-                    "Content-Length: " + body.length() + "\r\n" +
-                    "\r\n" +
-                    body;
-            output.write(request.getBytes("UTF-8"));
-            output.flush();
-
-            response = readResponse(input);
-            assertEquals("200", response.getCode());
-            assertEquals("POST /echo\r\n" + body, response.getBody());
-        }
-        finally
-        {
-            socket.close();
-        }
-    }
-
-    @Test
-    public void testCONNECTAndPOSTWithContext() throws Exception
-    {
-        final String contextKey = "contextKey";
-        final String contextValue = "contextValue";
-
-        // Replace the default ProxyHandler with a subclass to test context information passing
-        stopProxy();
-        proxy.setHandler(new ConnectHandler()
-        {
-            @Override
-            protected boolean handleAuthentication(HttpServletRequest request, HttpServletResponse response, String address) throws ServletException, IOException
-            {
-                request.setAttribute(contextKey, contextValue);
-                return super.handleAuthentication(request, response, address);
-            }
-
-            @Override
-            protected SocketChannel connect(HttpServletRequest request, String host, int port) throws IOException
-            {
-                assertEquals(contextValue, request.getAttribute(contextKey));
-                return super.connect(request, host, port);
-            }
-
-            @Override
-            protected void prepareContext(HttpServletRequest request, ConcurrentMap<String, Object> context)
-            {
-                // Transfer data from the HTTP request to the connection context
-                assertEquals(contextValue, request.getAttribute(contextKey));
-                context.put(contextKey, request.getAttribute(contextKey));
-            }
-
-            @Override
-            protected int read(EndPoint endPoint, Buffer buffer, ConcurrentMap<String, Object> context) throws IOException
-            {
-                assertEquals(contextValue, context.get(contextKey));
-                return super.read(endPoint, buffer, context);
-            }
-
-            @Override
-            protected int write(EndPoint endPoint, Buffer buffer, ConcurrentMap<String, Object> context) throws IOException
-            {
-                assertEquals(contextValue, context.get(contextKey));
-                return super.write(endPoint, buffer, context);
-            }
-        });
-        proxy.start();
-
-        String hostPort = "localhost:" + serverConnector.getLocalPort();
-        String request = "" +
-                "CONNECT " + hostPort + " HTTP/1.1\r\n" +
-                "Host: " + hostPort + "\r\n" +
-                "\r\n";
-        Socket socket = newSocket();
-        try
-        {
-            OutputStream output = socket.getOutputStream();
-            BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
-
-            output.write(request.getBytes("UTF-8"));
-            output.flush();
-
-            // Expect 200 OK from the CONNECT request
-            Response response = readResponse(input);
-            assertEquals("200", response.getCode());
-
-            String body = "0123456789ABCDEF";
-            request = "" +
-                    "POST /echo HTTP/1.1\r\n" +
-                    "Host: " + hostPort + "\r\n" +
-                    "Content-Length: " + body.length() + "\r\n" +
-                    "\r\n" +
-                    body;
-            output.write(request.getBytes("UTF-8"));
-            output.flush();
-
-            response = readResponse(input);
-            assertEquals("200", response.getCode());
-            assertEquals("POST /echo\r\n" + body, response.getBody());
-        }
-        finally
-        {
-            socket.close();
-        }
-    }
-
-    @Test
-    public void testCONNECTAndGETPipelinedAndOutputShutdown() throws Exception
-    {
-    	// TODO needs to be further investigated
-    	assumeTrue(!OS.IS_OSX);
-
-        String hostPort = "localhost:" + serverConnector.getLocalPort();
-        String request = "" +
-                "CONNECT " + hostPort + " HTTP/1.1\r\n" +
-                "Host: " + hostPort + "\r\n" +
-                "\r\n" +
-                "GET /echo" + " HTTP/1.1\r\n" +
-                "Host: " + hostPort + "\r\n" +
-                "\r\n";
-        Socket socket = newSocket();
-        try
-        {
-            OutputStream output = socket.getOutputStream();
-            BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
-
-            output.write(request.getBytes("UTF-8"));
-            output.flush();
-            socket.shutdownOutput();
-
-            // Expect 200 OK from the CONNECT request
-            Response response = readResponse(input);
-            assertEquals("200", response.getCode());
-
-            // The pipelined request must have gone up to the server as is
-            response = readResponse(input);
-            assertEquals("200", response.getCode());
-            assertEquals("GET /echo", response.getBody());
-        }
-        finally
-        {
-            socket.close();
-        }
-    }
-
-    @Test
-    public void testCONNECTAndGETAndOutputShutdown() throws Exception
-    {
-    	// TODO needs to be further investigated
-    	assumeTrue(!OS.IS_OSX);
-
-        String hostPort = "localhost:" + serverConnector.getLocalPort();
-        String request = "" +
-                "CONNECT " + hostPort + " HTTP/1.1\r\n" +
-                "Host: " + hostPort + "\r\n" +
-                "\r\n";
-        Socket socket = newSocket();
-        try
-        {
-            OutputStream output = socket.getOutputStream();
-            BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
-
-            output.write(request.getBytes("UTF-8"));
-            output.flush();
-
-            // Expect 200 OK from the CONNECT request
-            Response response = readResponse(input);
-            assertEquals("200", response.getCode());
-
-            request = "" +
-                    "GET /echo" + " HTTP/1.1\r\n" +
-                    "Host: " + hostPort + "\r\n" +
-                    "\r\n";
-            output.write(request.getBytes("UTF-8"));
-            output.flush();
-            socket.shutdownOutput();
-
-            // The pipelined request must have gone up to the server as is
-            response = readResponse(input);
-            assertEquals("200", response.getCode());
-            assertEquals("GET /echo", response.getBody());
-        }
-        finally
-        {
-            socket.close();
-        }
-    }
-
-    private static class ServerHandler extends AbstractHandler
-    {
-        public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException
-        {
-            request.setHandled(true);
-
-            String uri = httpRequest.getRequestURI();
-            if ("/echo".equals(uri))
-            {
-                StringBuilder builder = new StringBuilder();
-                builder.append(httpRequest.getMethod()).append(" ").append(uri);
-                if (httpRequest.getQueryString() != null)
-                    builder.append("?").append(httpRequest.getQueryString());
-
-                ByteArrayOutputStream baos = new ByteArrayOutputStream();
-                InputStream input = httpRequest.getInputStream();
-                int read;
-                while ((read = input.read()) >= 0)
-                    baos.write(read);
-                baos.close();
-
-                ServletOutputStream output = httpResponse.getOutputStream();
-                output.println(builder.toString());
-                output.write(baos.toByteArray());
-            }
-            else if ("/close".equals(uri))
-            {
-                request.getConnection().getEndPoint().close();
-            }
-            else
-            {
-                throw new ServletException();
-            }
-        }
-    }
-}
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ConnectHandlerUnitTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ConnectHandlerUnitTest.java
deleted file mode 100644
index 4186991..0000000
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ConnectHandlerUnitTest.java
+++ /dev/null
@@ -1,73 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.server.handler;
-
-import static org.mockito.Mockito.when;
-import static org.mockito.Matchers.*;
-import static org.junit.Assert.*;
-import static org.hamcrest.Matchers.*;
-
-import java.io.IOException;
-
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.ByteArrayBuffer;
-import org.eclipse.jetty.io.EndPoint;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.runners.MockitoJUnitRunner;
-import org.mockito.stubbing.Answer;
-
-/* ------------------------------------------------------------ */
-/**
- */
- at RunWith(MockitoJUnitRunner.class)
-public class ConnectHandlerUnitTest
-{
-    @Mock
-    private EndPoint endPoint;
-
-    @Test
-    public void testPartialWritesWithNonFullBuffer() throws IOException
-    {
-        ConnectHandler connectHandler = new ConnectHandler();
-        final byte[] bytes = "foo bar".getBytes();
-        Buffer buffer = new ByteArrayBuffer(bytes.length * 2);
-        buffer.put(bytes);
-        when(endPoint.flush(buffer)).thenAnswer(new Answer<Object>()
-        {
-            public Object answer(InvocationOnMock invocation)
-            {
-                Object[] args = invocation.getArguments();
-                Buffer buffer = (Buffer)args[0];
-                int skip = bytes.length/2;
-                buffer.skip(skip);
-                return skip;
-            }
-        });
-        when(endPoint.blockWritable(anyInt())).thenReturn(true);
-        
-        // method to test
-        connectHandler.write(endPoint,buffer,null);
-        
-        assertThat(buffer.length(),is(0));
-    }
-
-}
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ContextHandlerCollectionTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ContextHandlerCollectionTest.java
index 8b18c9d..f17e568 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ContextHandlerCollectionTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ContextHandlerCollectionTest.java
@@ -18,74 +18,145 @@
 
 package org.eclipse.jetty.server.handler;
 
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.endsWith;
+import static org.hamcrest.Matchers.not;
+import static org.hamcrest.Matchers.startsWith;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
 
 import java.io.IOException;
 
+import javax.servlet.AsyncContext;
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
 import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.Handler;
 import org.eclipse.jetty.server.LocalConnector;
 import org.eclipse.jetty.server.Request;
 import org.eclipse.jetty.server.Server;
+import org.hamcrest.Matchers;
+import org.junit.Assert;
 import org.junit.Test;
 
 public class ContextHandlerCollectionTest
 {
     @Test
-    public void testVirtualHostNormalization() throws Exception
+    public void testVirtualHosts() throws Exception
     {
         Server server = new Server();
-        LocalConnector connector = new LocalConnector();
+        LocalConnector connector0 = new LocalConnector(server);
+        LocalConnector connector1 = new LocalConnector(server);
+        connector1.setName("connector1");
+        
         server.setConnectors(new Connector[]
-        { connector });
+        { connector0,connector1});
 
-        ContextHandler contextA = new ContextHandler("/");
+        ContextHandler contextA = new ContextHandler("/ctx");
         contextA.setVirtualHosts(new String[]
-        { "www.example.com" });
-        IsHandledHandler handlerA = new IsHandledHandler();
+        { "www.example.com", "alias.example.com" });
+        IsHandledHandler handlerA = new IsHandledHandler("A");
         contextA.setHandler(handlerA);
+        contextA.setAllowNullPathInfo(true);
 
-        ContextHandler contextB = new ContextHandler("/");
-        IsHandledHandler handlerB = new IsHandledHandler();
+        ContextHandler contextB = new ContextHandler("/ctx");
+        IsHandledHandler handlerB = new IsHandledHandler("B");
         contextB.setHandler(handlerB);
         contextB.setVirtualHosts(new String[]
-        { "www.example2.com." });
+        { "*.other.com" , "@connector1"});
 
-        ContextHandler contextC = new ContextHandler("/");
-        IsHandledHandler handlerC = new IsHandledHandler();
+        ContextHandler contextC = new ContextHandler("/ctx");
+        IsHandledHandler handlerC = new IsHandledHandler("C");
         contextC.setHandler(handlerC);
 
-        ContextHandlerCollection c = new ContextHandlerCollection();
+        ContextHandler contextD = new ContextHandler("/");
+        IsHandledHandler handlerD = new IsHandledHandler("D");
+        contextD.setHandler(handlerD);
+        
+        ContextHandler contextE = new ContextHandler("/ctx/foo");
+        IsHandledHandler handlerE = new IsHandledHandler("E");
+        contextE.setHandler(handlerE);
+        
+        ContextHandler contextF = new ContextHandler("/ctxlong");
+        IsHandledHandler handlerF = new IsHandledHandler("F");
+        contextF.setHandler(handlerF);
 
+        ContextHandlerCollection c = new ContextHandlerCollection();
         c.addHandler(contextA);
         c.addHandler(contextB);
         c.addHandler(contextC);
-
+        
+        HandlerList list = new HandlerList();
+        list.addHandler(contextE);
+        list.addHandler(contextF);
+        list.addHandler(contextD);
+        c.addHandler(list);
+        
         server.setHandler(c);
 
         try
         {
             server.start();
-            connector.getResponses("GET / HTTP/1.1\n" + "Host: www.example.com.\n\n");
-
-            assertTrue(handlerA.isHandled());
-            assertFalse(handlerB.isHandled());
-            assertFalse(handlerC.isHandled());
-
-            handlerA.reset();
-            handlerB.reset();
-            handlerC.reset();
-
-            connector.getResponses("GET / HTTP/1.1\n" + "Host: www.example2.com\n\n");
-
-            assertFalse(handlerA.isHandled());
-            assertTrue(handlerB.isHandled());
-            assertFalse(handlerC.isHandled());
+            
+            Object[][] tests = new Object[][] {
+                {connector0,"www.example.com.", "/ctx",    handlerA},
+                {connector0,"www.example.com.", "/ctx/",    handlerA},
+                {connector0,"www.example.com.", "/ctx/info",    handlerA},
+                {connector0,"www.example.com",  "/ctx/info",    handlerA},
+                {connector0,"alias.example.com",  "/ctx/info",    handlerA},
+                {connector1,"www.example.com.", "/ctx/info",    handlerA},
+                {connector1,"www.example.com",  "/ctx/info",    handlerA},
+                {connector1,"alias.example.com",  "/ctx/info",    handlerA},
+
+                {connector1,"www.other.com",  "/ctx",    null},
+                {connector1,"www.other.com",  "/ctx/",    handlerB},
+                {connector1,"www.other.com",  "/ctx/info",    handlerB},
+                {connector0,"www.other.com",  "/ctx/info",    handlerC},
+                
+                {connector0,"www.example.com",  "/ctxinfo",    handlerD},
+                {connector1,"unknown.com",  "/unknown",    handlerD},
+                
+                {connector0,"alias.example.com",  "/ctx/foo/info",    handlerE},
+                {connector0,"alias.example.com",  "/ctxlong/info",    handlerF},
+            };
+            
+            for (int i=0;i<tests.length;i++)
+            {
+                Object[] test=tests[i];
+                LocalConnector connector = (LocalConnector)test[0];
+                String host=(String)test[1];
+                String uri=(String)test[2];
+                IsHandledHandler handler = (IsHandledHandler)test[3];
+
+                handlerA.reset();
+                handlerB.reset();
+                handlerC.reset();
+                handlerD.reset();
+                handlerE.reset();
+                handlerF.reset();
+
+                String t = String.format("test   %d %s@%s --> %s | %s%n",i,uri,host,connector.getName(),handler);
+                String response = connector.getResponses("GET "+uri+" HTTP/1.0\nHost: "+host+"\n\n");
+                
+                if (handler==null)
+                {
+                    Assert.assertThat(t,response,Matchers.containsString(" 302 "));
+                }
+                else
+                {
+                    assertThat(t,response,endsWith(handler.toString()));
+                    if (!handler.isHandled())
+                    {
+                        System.err.printf("FAILED %s",t);
+                        System.err.println(response);
+                        Assert.fail();
+                    }
+                }
+            }
 
         }
         finally
@@ -98,12 +169,12 @@ public class ContextHandlerCollectionTest
     public void testVirtualHostWildcard() throws Exception
     {
         Server server = new Server();
-        LocalConnector connector = new LocalConnector();
+        LocalConnector connector = new LocalConnector(server);
         server.setConnectors(new Connector[] { connector });
 
         ContextHandler context = new ContextHandler("/");
 
-        IsHandledHandler handler = new IsHandledHandler();
+        IsHandledHandler handler = new IsHandledHandler("H");
         context.setHandler(handler);
 
         ContextHandlerCollection c = new ContextHandlerCollection();
@@ -149,7 +220,10 @@ public class ContextHandlerCollectionTest
 
         for(String host : requestHosts)
         {
-            connector.getResponses("GET / HTTP/1.1\n" + "Host: "+host+"\n\n");
+            // System.err.printf("host=%s in %s%n",host,contextHosts==null?Collections.emptyList():Arrays.asList(contextHosts));
+            
+            String response=connector.getResponses("GET / HTTP/1.0\n" + "Host: "+host+"\nConnection:close\n\n");
+            // System.err.println(response);
             if(succeed)
                 assertTrue("'"+host+"' should have been handled.",handler.isHandled());
             else
@@ -166,17 +240,17 @@ public class ContextHandlerCollectionTest
         Server server = new Server();
 
         ContextHandler contextA = new ContextHandler("/a");
-        IsHandledHandler handlerA = new IsHandledHandler();
+        IsHandledHandler handlerA = new IsHandledHandler("A");
         contextA.setHandler(handlerA);
 
         ContextHandler contextB = new ContextHandler("/b");
-        IsHandledHandler handlerB = new IsHandledHandler();
+        IsHandledHandler handlerB = new IsHandledHandler("B");
         HandlerWrapper wrapperB = new HandlerWrapper();
         wrapperB.setHandler(handlerB);
         contextB.setHandler(wrapperB);
 
         ContextHandler contextC = new ContextHandler("/c");
-        IsHandledHandler handlerC = new IsHandledHandler();
+        IsHandledHandler handlerC = new IsHandledHandler("C");
         contextC.setHandler(handlerC);
 
         ContextHandlerCollection collection = new ContextHandlerCollection();
@@ -188,38 +262,228 @@ public class ContextHandlerCollectionTest
         HandlerWrapper wrapper = new HandlerWrapper();
         wrapper.setHandler(collection);
         server.setHandler(wrapper);
-        
+
         assertEquals(wrapper,AbstractHandlerContainer.findContainerOf(server,HandlerWrapper.class,handlerA));
         assertEquals(contextA,AbstractHandlerContainer.findContainerOf(server,ContextHandler.class,handlerA));
         assertEquals(contextB,AbstractHandlerContainer.findContainerOf(server,ContextHandler.class,handlerB));
         assertEquals(wrapper,AbstractHandlerContainer.findContainerOf(server,HandlerWrapper.class,handlerB));
         assertEquals(contextB,AbstractHandlerContainer.findContainerOf(collection,HandlerWrapper.class,handlerB));
         assertEquals(wrapperB,AbstractHandlerContainer.findContainerOf(contextB,HandlerWrapper.class,handlerB));
+
+    }
+    
+    @Test
+    public void testWrappedContext() throws Exception
+    {
+        Server server = new Server();
+        LocalConnector connector = new LocalConnector(server);
+        server.setConnectors(new Connector[] { connector });
+
+        ContextHandler root = new ContextHandler("/");
+        root.setHandler(new IsHandledHandler("root"));
+        
+        ContextHandler left = new ContextHandler("/left");
+        left.setHandler(new IsHandledHandler("left"));
+        
+        HandlerList centre = new HandlerList();
+        ContextHandler centreLeft = new ContextHandler("/leftcentre");
+        centreLeft.setHandler(new IsHandledHandler("left of centre"));
+        ContextHandler centreRight = new ContextHandler("/rightcentre");
+        centreRight.setHandler(new IsHandledHandler("right of centre"));
+        centre.setHandlers(new Handler[]{centreLeft,new WrappedHandler(centreRight)});
+        
+        ContextHandler right = new ContextHandler("/right");
+        right.setHandler(new IsHandledHandler("right"));
+        
+        ContextHandlerCollection contexts = new ContextHandlerCollection();
+        contexts.setHandlers(new Handler[]{root,left,centre,new WrappedHandler(right)});
+        
+        server.setHandler(contexts);
+        server.start();
         
+        String response=connector.getResponses("GET / HTTP/1.0\r\n\r\n");
+        assertThat(response, startsWith("HTTP/1.1 200 OK"));
+        assertThat(response, endsWith("root"));
+        assertThat(response, not(containsString("Wrapped: TRUE")));
+
+        response=connector.getResponses("GET /foobar/info HTTP/1.0\r\n\r\n");
+        assertThat(response, startsWith("HTTP/1.1 200 OK"));
+        assertThat(response, endsWith("root"));
+        assertThat(response, not(containsString("Wrapped: TRUE")));
+
+        response=connector.getResponses("GET /left/info HTTP/1.0\r\n\r\n");
+        assertThat(response, startsWith("HTTP/1.1 200 OK"));
+        assertThat(response, endsWith("left"));
+        assertThat(response, not(containsString("Wrapped: TRUE")));
+
+        response=connector.getResponses("GET /leftcentre/info HTTP/1.0\r\n\r\n");
+        assertThat(response, startsWith("HTTP/1.1 200 OK"));
+        assertThat(response, endsWith("left of centre"));
+        assertThat(response, not(containsString("Wrapped: TRUE")));
+
+        response=connector.getResponses("GET /rightcentre/info HTTP/1.0\r\n\r\n");
+        assertThat(response, startsWith("HTTP/1.1 200 OK"));
+        assertThat(response, endsWith("right of centre"));
+        assertThat(response, containsString("Wrapped: TRUE"));
+
+        response=connector.getResponses("GET /right/info HTTP/1.0\r\n\r\n");
+        assertThat(response, startsWith("HTTP/1.1 200 OK"));
+        assertThat(response, endsWith("right"));
+        assertThat(response, containsString("Wrapped: TRUE"));
     }
 
+
+    @Test
+    public void testAsyncWrappedContext() throws Exception
+    {
+        Server server = new Server();
+        LocalConnector connector = new LocalConnector(server);
+        server.setConnectors(new Connector[] { connector });
+
+        ContextHandler root = new ContextHandler("/");
+        root.setHandler(new AsyncHandler("root"));
+        
+        ContextHandler left = new ContextHandler("/left");
+        left.setHandler(new AsyncHandler("left"));
+        
+        HandlerList centre = new HandlerList();
+        ContextHandler centreLeft = new ContextHandler("/leftcentre");
+        centreLeft.setHandler(new AsyncHandler("left of centre"));
+        ContextHandler centreRight = new ContextHandler("/rightcentre");
+        centreRight.setHandler(new AsyncHandler("right of centre"));
+        centre.setHandlers(new Handler[]{centreLeft,new WrappedHandler(centreRight)});
+        
+        ContextHandler right = new ContextHandler("/right");
+        right.setHandler(new AsyncHandler("right"));
+        
+        ContextHandlerCollection contexts = new ContextHandlerCollection();
+        contexts.setHandlers(new Handler[]{root,left,centre,new WrappedHandler(right)});
+        
+        server.setHandler(contexts);
+        server.start();
+        
+        String response=connector.getResponses("GET / HTTP/1.0\r\n\r\n");
+        assertThat(response, startsWith("HTTP/1.1 200 OK"));
+        assertThat(response, endsWith("root"));
+        assertThat(response, not(containsString("Wrapped: TRUE")));
+
+        response=connector.getResponses("GET /foobar/info HTTP/1.0\r\n\r\n");
+        assertThat(response, startsWith("HTTP/1.1 200 OK"));
+        assertThat(response, endsWith("root"));
+        assertThat(response, not(containsString("Wrapped: TRUE")));
+
+        response=connector.getResponses("GET /left/info HTTP/1.0\r\n\r\n");
+        assertThat(response, startsWith("HTTP/1.1 200 OK"));
+        assertThat(response, endsWith("left"));
+        assertThat(response, not(containsString("Wrapped: TRUE")));
+
+        response=connector.getResponses("GET /leftcentre/info HTTP/1.0\r\n\r\n");
+        assertThat(response, startsWith("HTTP/1.1 200 OK"));
+        assertThat(response, endsWith("left of centre"));
+        assertThat(response, not(containsString("Wrapped: TRUE")));
+
+        response=connector.getResponses("GET /rightcentre/info HTTP/1.0\r\n\r\n");
+        assertThat(response, startsWith("HTTP/1.1 200 OK"));
+        assertThat(response, endsWith("right of centre"));
+        assertThat(response, containsString("Wrapped: ASYNC"));
+
+        response=connector.getResponses("GET /right/info HTTP/1.0\r\n\r\n");
+        assertThat(response, startsWith("HTTP/1.1 200 OK"));
+        assertThat(response, endsWith("right"));
+        assertThat(response, containsString("Wrapped: ASYNC"));
+    }
+    
+    
+    private static final class WrappedHandler extends HandlerWrapper
+    {
+        WrappedHandler(Handler handler)
+        {
+            setHandler(handler);
+        }
+        
+        @Override
+        public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+        {
+            if (response.containsHeader("Wrapped"))
+                response.setHeader("Wrapped", "ASYNC");
+            else
+                response.setHeader("Wrapped", "TRUE");
+            super.handle(target, baseRequest, request, response);
+        }   
+    }
+    
     
     private static final class IsHandledHandler extends AbstractHandler
     {
         private boolean handled;
+        private final String name;
+
+        public IsHandledHandler(String string)
+        {
+            name=string;
+        }
 
         public boolean isHandled()
         {
             return handled;
         }
 
+        @Override
         public void handle(String s, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
         {
             baseRequest.setHandled(true);
             this.handled = true;
+            response.getWriter().print(name);
         }
 
         public void reset()
         {
             handled = false;
         }
+        
+        @Override
+        public String toString()
+        {
+            return name;
+        }
     }
 
 
+    
+    private static final class AsyncHandler extends AbstractHandler
+    {
+        private final String name;
+
+        public AsyncHandler(String string)
+        {
+            name=string;
+        }
+
+        @Override
+        public void handle(String s, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+        {
+            baseRequest.setHandled(true);
+            
+            String n = (String)baseRequest.getAttribute("async");
+            if (n==null)
+            {
+                AsyncContext async=baseRequest.startAsync();
+                async.setTimeout(1000);
+                baseRequest.setAttribute("async", name);
+                async.dispatch();
+            }
+            else
+            {
+                response.getWriter().print(n);
+            }
+        }
+        
+        @Override
+        public String toString()
+        {
+            return name;
+        }
+    }
+
 
 }
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ContextHandlerGetResourceTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ContextHandlerGetResourceTest.java
new file mode 100644
index 0000000..baab4c5
--- /dev/null
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ContextHandlerGetResourceTest.java
@@ -0,0 +1,405 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.handler;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.File;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.nio.file.Files;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.toolchain.test.FS;
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.eclipse.jetty.toolchain.test.OS;
+import org.eclipse.jetty.util.resource.Resource;
+import org.junit.AfterClass;
+import org.junit.Assume;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class ContextHandlerGetResourceTest
+{
+    private static boolean OS_ALIAS_SUPPORTED;
+    private static Server server;
+    private static ContextHandler context;
+    private static File docroot;
+    private static File otherroot;
+    private final static AtomicBoolean allowAliases= new AtomicBoolean(false);
+    private final static AtomicBoolean allowSymlinks= new AtomicBoolean(false);
+    
+    @BeforeClass
+    public static void beforeClass()  throws Exception
+    {
+        File testRoot = MavenTestingUtils.getTargetTestingDir(ContextHandlerGetResourceTest.class.getSimpleName());
+        FS.ensureEmpty(testRoot);
+        docroot = new File(testRoot,"docroot").getCanonicalFile().getAbsoluteFile();
+        FS.ensureEmpty(docroot);
+        File index = new File(docroot,"index.html");
+        index.createNewFile();
+        File sub = new File(docroot,"subdir");
+        sub.mkdir();
+        File data = new File(sub,"data.txt");
+        data.createNewFile();
+        File verylong = new File(sub,"TextFile.Long.txt");
+        verylong.createNewFile();
+
+        otherroot = new File(testRoot, "otherroot").getCanonicalFile().getAbsoluteFile();
+        FS.ensureEmpty(otherroot);
+        File other = new File(otherroot,"other.txt");
+        other.createNewFile();
+        
+        File transit = new File(docroot.getParentFile(),"transit");
+        transit.delete();
+        
+        if (OS.IS_UNIX)
+        {
+            // Create alias as 8.3 name so same test will produce an alias on both windows an normal systems
+            File eightDotThree=new File(sub,"TEXTFI~1.TXT");
+            Files.createSymbolicLink(eightDotThree.toPath(),verylong.toPath());
+            
+            Files.createSymbolicLink(new File(docroot,"other").toPath(),new File("../transit").toPath());
+            Files.createSymbolicLink(transit.toPath(),otherroot.toPath());
+        }
+        
+        OS_ALIAS_SUPPORTED = new File(sub, "TEXTFI~1.TXT").exists(); 
+        
+        server = new Server();
+        context =new ContextHandler("/");
+        context.setBaseResource(Resource.newResource(docroot));
+        context.addAliasCheck(new ContextHandler.AliasCheck()
+        {
+            final AllowSymLinkAliasChecker symlinkcheck = new AllowSymLinkAliasChecker();
+            @Override
+            public boolean check(String path, Resource resource)
+            {
+                if (allowAliases.get())
+                    return true;
+                if (allowSymlinks.get())
+                    return symlinkcheck.check(path,resource);
+                return allowAliases.get();
+            }
+        });
+        
+        server.setHandler(context);
+        server.start();
+    }
+    
+    @AfterClass
+    public static void afterClass()  throws Exception
+    {
+        server.stop(); 
+    }
+
+
+    @Test
+    public void testBadPath() throws Exception
+    {
+        final String path="bad";
+        try
+        {
+            context.getResource(path);
+            fail();
+        }
+        catch(MalformedURLException e)
+        {
+        }
+        
+        try
+        {
+            context.getServletContext().getResource(path);
+            fail();
+        }
+        catch(MalformedURLException e)
+        {
+        }
+    }
+    
+    @Test
+    public void testGetUnknown() throws Exception
+    {
+        final String path="/unknown.txt";
+        Resource resource=context.getResource(path);
+        assertEquals("unknown.txt",resource.getFile().getName());
+        assertEquals(docroot,resource.getFile().getParentFile());
+        assertFalse(resource.exists());
+        
+        URL url=context.getServletContext().getResource(path);
+        assertNull(url);
+    }
+    
+    @Test
+    public void testGetUnknownDir() throws Exception
+    {
+        final String path="/unknown/";
+        Resource resource=context.getResource(path);
+        assertEquals("unknown",resource.getFile().getName());
+        assertEquals(docroot,resource.getFile().getParentFile());
+        assertFalse(resource.exists());
+        
+        URL url=context.getServletContext().getResource(path);
+        assertNull(url);
+    }
+
+    @Test
+    public void testRoot() throws Exception
+    {
+        final String path="/";
+        Resource resource=context.getResource(path);
+        assertEquals(docroot,resource.getFile());
+        assertTrue(resource.exists());
+        assertTrue(resource.isDirectory());
+        
+        URL url=context.getServletContext().getResource(path);
+        assertEquals(docroot,new File(url.toURI()));
+    }
+
+    @Test
+    public void testSubdir() throws Exception
+    {
+        final String path="/subdir";
+        Resource resource=context.getResource(path);
+        assertEquals(docroot,resource.getFile().getParentFile());
+        assertTrue(resource.exists());
+        assertTrue(resource.isDirectory());
+        assertTrue(resource.toString().endsWith("/"));
+        
+        URL url=context.getServletContext().getResource(path);
+        assertEquals(docroot,new File(url.toURI()).getParentFile());
+    }
+
+    @Test
+    public void testSubdirSlash() throws Exception
+    {
+        final String path="/subdir/";
+        Resource resource=context.getResource(path);
+        assertEquals(docroot,resource.getFile().getParentFile());
+        assertTrue(resource.exists());
+        assertTrue(resource.isDirectory());
+        assertTrue(resource.toString().endsWith("/"));
+        
+        URL url=context.getServletContext().getResource(path);
+        assertEquals(docroot,new File(url.toURI()).getParentFile());
+    }
+
+    @Test
+    public void testGetKnown() throws Exception
+    {
+        final String path="/index.html";
+        Resource resource=context.getResource(path);
+        assertEquals("index.html",resource.getFile().getName());
+        assertEquals(docroot,resource.getFile().getParentFile());
+        assertTrue(resource.exists());
+        
+        URL url=context.getServletContext().getResource(path);
+        assertEquals(docroot,new File(url.toURI()).getParentFile());
+    }
+
+    @Test
+    public void testNormalize() throws Exception
+    {
+        final String path="/down/.././index.html";
+        Resource resource=context.getResource(path);
+        assertEquals("index.html",resource.getFile().getName());
+        assertEquals(docroot,resource.getFile().getParentFile());
+        assertTrue(resource.exists());
+        
+        URL url=context.getServletContext().getResource(path);
+        assertEquals(docroot,new File(url.toURI()).getParentFile());
+    }
+
+    @Test
+    public void testTooNormal() throws Exception
+    {
+        final String path="/down/.././../";
+        Resource resource=context.getResource(path);
+        assertNull(resource);
+        URL url=context.getServletContext().getResource(path);
+        assertNull(url);
+    }
+
+    @Test
+    public void testDeep() throws Exception
+    {
+        final String path="/subdir/data.txt";
+        Resource resource=context.getResource(path);
+        assertEquals("data.txt",resource.getFile().getName());
+        assertEquals(docroot,resource.getFile().getParentFile().getParentFile());
+        assertTrue(resource.exists());
+        
+        URL url=context.getServletContext().getResource(path);
+        assertEquals(docroot,new File(url.toURI()).getParentFile().getParentFile());
+    }
+
+    @Test
+    public void testEncodedSlash() throws Exception
+    {
+        final String path="/subdir%2Fdata.txt";
+        
+        Resource resource=context.getResource(path);
+        assertEquals("subdir%2Fdata.txt",resource.getFile().getName());
+        assertEquals(docroot,resource.getFile().getParentFile());
+        assertFalse(resource.exists());
+        
+        URL url=context.getServletContext().getResource(path);
+        assertNull(url);
+    }
+
+    @Test
+    public void testEncodedSlosh() throws Exception
+    {
+        final String path="/subdir%5Cdata.txt";
+        
+        Resource resource=context.getResource(path);
+        assertEquals("subdir%5Cdata.txt",resource.getFile().getName());
+        assertEquals(docroot,resource.getFile().getParentFile());
+        assertFalse(resource.exists());
+        
+        URL url=context.getServletContext().getResource(path);
+        assertNull(url);
+    }
+
+    @Test
+    public void testEncodedNull() throws Exception
+    {
+        final String path="/subdir/data.txt%00";
+        
+        Resource resource=context.getResource(path);
+        assertEquals("data.txt%00",resource.getFile().getName());
+        assertEquals(docroot,resource.getFile().getParentFile().getParentFile());
+        assertFalse(resource.exists());
+        
+        URL url=context.getServletContext().getResource(path);
+        assertNull(url);
+    }
+    
+
+    @Test
+    public void testSlashSlash() throws Exception
+    {
+        String path="//subdir/data.txt";
+        Resource resource=context.getResource(path);
+        assertNull(resource);
+        URL url=context.getServletContext().getResource(path);
+        assertNull(url);
+        
+        path="/subdir//data.txt";
+        resource=context.getResource(path);
+        assertNull(resource);
+        url=context.getServletContext().getResource(path);
+        assertNull(url);
+    }
+
+    @Test
+    public void testAliasedFile() throws Exception
+    {
+        Assume.assumeTrue("OS Supports 8.3 Aliased / Alternate References",OS_ALIAS_SUPPORTED);
+        final String path="/subdir/TEXTFI~1.TXT";
+        
+        Resource resource=context.getResource(path);
+        assertNull(resource);
+        
+        URL url=context.getServletContext().getResource(path);
+        assertNull(url);
+    }
+
+    @Test
+    public void testAliasedFileAllowed() throws Exception
+    {
+        Assume.assumeTrue("OS Supports 8.3 Aliased / Alternate References",OS_ALIAS_SUPPORTED);
+        try
+        {
+            allowAliases.set(true);
+            final String path="/subdir/TEXTFI~1.TXT";
+
+            Resource resource=context.getResource(path);
+            assertNotNull(resource);
+            assertEquals(context.getResource("/subdir/TextFile.Long.txt").getURI(),resource.getAlias());
+            
+            URL url=context.getServletContext().getResource(path);
+            assertNotNull(url);
+            assertEquals(docroot,new File(url.toURI()).getParentFile().getParentFile());
+        }
+        finally
+        {
+            allowAliases.set(false);
+        }
+        
+    }
+
+    @Test
+    public void testSymlinkKnown() throws Exception
+    {
+        if (!OS.IS_UNIX)
+            return;
+        try
+        {
+            allowSymlinks.set(true);
+
+            final String path="/other/other.txt";
+
+            Resource resource=context.getResource(path);
+            assertNotNull(resource);
+            assertEquals("other.txt",resource.getFile().getName());
+            assertEquals(docroot,resource.getFile().getParentFile().getParentFile());
+            assertTrue(resource.exists());
+
+            URL url=context.getServletContext().getResource(path);
+            assertEquals(docroot,new File(url.toURI()).getParentFile().getParentFile());
+        }
+        finally
+        {
+            allowSymlinks.set(false);
+        } 
+        
+    }
+
+    @Test
+    public void testSymlinkUnknown() throws Exception
+    {
+        if (!OS.IS_UNIX)
+            return;
+        try
+        {
+            allowSymlinks.set(true);
+
+            final String path="/other/unknown.txt";
+
+            Resource resource=context.getResource(path);
+            assertNotNull(resource);
+            assertEquals("unknown.txt",resource.getFile().getName());
+            assertEquals(docroot,resource.getFile().getParentFile().getParentFile());
+            assertFalse(resource.exists());
+
+            URL url=context.getServletContext().getResource(path);
+            assertNull(url);
+        }
+        finally
+        {
+            allowSymlinks.set(false);
+        }
+    }
+}
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ContextHandlerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ContextHandlerTest.java
index d0f4158..ee4af44 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ContextHandlerTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ContextHandlerTest.java
@@ -24,25 +24,20 @@ import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import junit.framework.Assert;
 import org.eclipse.jetty.server.Connector;
 import org.eclipse.jetty.server.LocalConnector;
 import org.eclipse.jetty.server.Request;
 import org.eclipse.jetty.server.Server;
 import org.eclipse.jetty.util.resource.Resource;
+import org.hamcrest.Matchers;
+import org.junit.Assert;
 import org.junit.Test;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-/**
- * @version $Revision$
- */
 public class ContextHandlerTest
 {
     @Test
@@ -61,7 +56,7 @@ public class ContextHandlerTest
     public void testVirtualHostNormalization() throws Exception
     {
         Server server = new Server();
-        LocalConnector connector = new LocalConnector();
+        LocalConnector connector = new LocalConnector(server);
         server.setConnectors(new Connector[]
         { connector });
 
@@ -92,21 +87,94 @@ public class ContextHandlerTest
         try
         {
             server.start();
-            connector.getResponses("GET / HTTP/1.1\n" + "Host: www.example.com.\n\n");
+            connector.getResponses("GET / HTTP/1.0\n" + "Host: www.example.com.\n\n");
+
+            Assert.assertTrue(handlerA.isHandled());
+            Assert.assertFalse(handlerB.isHandled());
+            Assert.assertFalse(handlerC.isHandled());
+
+            handlerA.reset();
+            handlerB.reset();
+            handlerC.reset();
+
+            connector.getResponses("GET / HTTP/1.0\n" + "Host: www.example2.com\n\n");
+
+            Assert.assertFalse(handlerA.isHandled());
+            Assert.assertTrue(handlerB.isHandled());
+            Assert.assertFalse(handlerC.isHandled());
+
+        }
+        finally
+        {
+            server.stop();
+        }
+
+    }
+    
+    
+    @Test
+    public void testNamedConnector() throws Exception
+    {
+        Server server = new Server();
+        LocalConnector connector = new LocalConnector(server);
+        LocalConnector connectorN = new LocalConnector(server);
+        connectorN.setName("name");
+        server.setConnectors(new Connector[] { connector, connectorN });
+
+        ContextHandler contextA = new ContextHandler("/");
+        contextA.setVirtualHosts(new String[]{"www.example.com" });
+        IsHandledHandler handlerA = new IsHandledHandler();
+        contextA.setHandler(handlerA);
+
+        ContextHandler contextB = new ContextHandler("/");
+        IsHandledHandler handlerB = new IsHandledHandler();
+        contextB.setHandler(handlerB);
+        contextB.setVirtualHosts(new String[]{ "@name" });
+
+        ContextHandler contextC = new ContextHandler("/");
+        IsHandledHandler handlerC = new IsHandledHandler();
+        contextC.setHandler(handlerC);
+
+        HandlerCollection c = new HandlerCollection();
+        c.addHandler(contextA);
+        c.addHandler(contextB);
+        c.addHandler(contextC);
+        server.setHandler(c);
 
-            assertTrue(handlerA.isHandled());
-            assertFalse(handlerB.isHandled());
-            assertFalse(handlerC.isHandled());
+        server.start();
+        try
+        {
+            connector.getResponses("GET / HTTP/1.0\n" + "Host: www.example.com.\n\n");
+            Assert.assertTrue(handlerA.isHandled());
+            Assert.assertFalse(handlerB.isHandled());
+            Assert.assertFalse(handlerC.isHandled());
+            handlerA.reset();
+            handlerB.reset();
+            handlerC.reset();
 
+            connector.getResponses("GET / HTTP/1.0\n" + "Host: localhost\n\n");
+            Assert.assertFalse(handlerA.isHandled());
+            Assert.assertFalse(handlerB.isHandled());
+            Assert.assertTrue(handlerC.isHandled());
             handlerA.reset();
             handlerB.reset();
             handlerC.reset();
 
-            connector.getResponses("GET / HTTP/1.1\n" + "Host: www.example2.com\n\n");
+            connectorN.getResponses("GET / HTTP/1.0\n" + "Host: www.example.com.\n\n");
+            Assert.assertTrue(handlerA.isHandled());
+            Assert.assertFalse(handlerB.isHandled());
+            Assert.assertFalse(handlerC.isHandled());
+            handlerA.reset();
+            handlerB.reset();
+            handlerC.reset();
 
-            assertFalse(handlerA.isHandled());
-            assertTrue(handlerB.isHandled());
-            assertFalse(handlerC.isHandled());
+            connectorN.getResponses("GET / HTTP/1.0\n" + "Host: localhost\n\n");
+            Assert.assertFalse(handlerA.isHandled());
+            Assert.assertTrue(handlerB.isHandled());
+            Assert.assertFalse(handlerC.isHandled());
+            handlerA.reset();
+            handlerB.reset();
+            handlerC.reset();
 
         }
         finally
@@ -120,7 +188,7 @@ public class ContextHandlerTest
     public void testContextGetContext() throws Exception
     {
         Server server = new Server();
-        LocalConnector connector = new LocalConnector();
+        LocalConnector connector = new LocalConnector(server);
         server.setConnectors(new Connector[] { connector });
         ContextHandlerCollection contexts = new ContextHandlerCollection();
         server.setHandler(contexts);
@@ -133,26 +201,80 @@ public class ContextHandlerTest
 
         // System.err.println(server.dump());
 
-        Assert.assertEquals(rootA._scontext,rootA._scontext.getContext("/"));
-        Assert.assertEquals(fooA._scontext,rootA._scontext.getContext("/foo"));
-        Assert.assertEquals(foobarA._scontext,rootA._scontext.getContext("/foo/bar"));
-        Assert.assertEquals(foobarA._scontext,rootA._scontext.getContext("/foo/bar/bob.jsp"));
-        Assert.assertEquals(rootA._scontext,rootA._scontext.getContext("/other"));
-        Assert.assertEquals(fooA._scontext,rootA._scontext.getContext("/foo/other"));
-
-        Assert.assertEquals(rootA._scontext,foobarA._scontext.getContext("/"));
-        Assert.assertEquals(fooA._scontext,foobarA._scontext.getContext("/foo"));
-        Assert.assertEquals(foobarA._scontext,foobarA._scontext.getContext("/foo/bar"));
-        Assert.assertEquals(foobarA._scontext,foobarA._scontext.getContext("/foo/bar/bob.jsp"));
-        Assert.assertEquals(rootA._scontext,foobarA._scontext.getContext("/other"));
-        Assert.assertEquals(fooA._scontext,foobarA._scontext.getContext("/foo/other"));
+        Assert.assertEquals(rootA._scontext, rootA._scontext.getContext("/"));
+        Assert.assertEquals(fooA._scontext, rootA._scontext.getContext("/foo"));
+        Assert.assertEquals(foobarA._scontext, rootA._scontext.getContext("/foo/bar"));
+        Assert.assertEquals(foobarA._scontext, rootA._scontext.getContext("/foo/bar/bob.jsp"));
+        Assert.assertEquals(rootA._scontext, rootA._scontext.getContext("/other"));
+        Assert.assertEquals(fooA._scontext, rootA._scontext.getContext("/foo/other"));
+
+        Assert.assertEquals(rootA._scontext, foobarA._scontext.getContext("/"));
+        Assert.assertEquals(fooA._scontext, foobarA._scontext.getContext("/foo"));
+        Assert.assertEquals(foobarA._scontext, foobarA._scontext.getContext("/foo/bar"));
+        Assert.assertEquals(foobarA._scontext, foobarA._scontext.getContext("/foo/bar/bob.jsp"));
+        Assert.assertEquals(rootA._scontext, foobarA._scontext.getContext("/other"));
+        Assert.assertEquals(fooA._scontext, foobarA._scontext.getContext("/foo/other"));
+    }
+
+    @Test
+    public void testLifeCycle() throws Exception
+    {
+        Server server = new Server();
+        LocalConnector connector = new LocalConnector(server);
+        server.setConnectors(new Connector[] { connector });
+        ContextHandlerCollection contexts = new ContextHandlerCollection();
+        server.setHandler(contexts);
+
+        ContextHandler root = new ContextHandler(contexts,"/");
+        root.setHandler(new ContextPathHandler());
+        ContextHandler foo = new ContextHandler(contexts,"/foo");
+        foo.setHandler(new ContextPathHandler());
+        ContextHandler foobar = new ContextHandler(contexts,"/foo/bar");
+        foobar.setHandler(new ContextPathHandler());
+
+        // check that all contexts start normally
+        server.start();
+        Assert.assertThat(connector.getResponses("GET / HTTP/1.0\n\n"), Matchers.containsString("ctx=''"));
+        Assert.assertThat(connector.getResponses("GET /foo/xxx HTTP/1.0\n\n"), Matchers.containsString("ctx='/foo'"));
+        Assert.assertThat(connector.getResponses("GET /foo/bar/xxx HTTP/1.0\n\n"), Matchers.containsString("ctx='/foo/bar'"));
+
+        // If we stop foobar, then requests will be handled by foo
+        foobar.stop();
+        Assert.assertThat(connector.getResponses("GET / HTTP/1.0\n\n"), Matchers.containsString("ctx=''"));
+        Assert.assertThat(connector.getResponses("GET /foo/xxx HTTP/1.0\n\n"), Matchers.containsString("ctx='/foo'"));
+        Assert.assertThat(connector.getResponses("GET /foo/bar/xxx HTTP/1.0\n\n"), Matchers.containsString("ctx='/foo'"));
+
+        // If we shutdown foo then requests will be 503'd
+        foo.shutdown().get();
+        Assert.assertThat(connector.getResponses("GET / HTTP/1.0\n\n"), Matchers.containsString("ctx=''"));
+        Assert.assertThat(connector.getResponses("GET /foo/xxx HTTP/1.0\n\n"), Matchers.containsString("503"));
+        Assert.assertThat(connector.getResponses("GET /foo/bar/xxx HTTP/1.0\n\n"), Matchers.containsString("503"));
+
+        // If we stop foo then requests will be handled by root
+        foo.stop();
+        Assert.assertThat(connector.getResponses("GET / HTTP/1.0\n\n"), Matchers.containsString("ctx=''"));
+        Assert.assertThat(connector.getResponses("GET /foo/xxx HTTP/1.0\n\n"), Matchers.containsString("ctx=''"));
+        Assert.assertThat(connector.getResponses("GET /foo/bar/xxx HTTP/1.0\n\n"), Matchers.containsString("ctx=''"));
+
+        // If we start foo then foobar requests will be handled by foo
+        foo.start();
+        Assert.assertThat(connector.getResponses("GET / HTTP/1.0\n\n"), Matchers.containsString("ctx=''"));
+        Assert.assertThat(connector.getResponses("GET /foo/xxx HTTP/1.0\n\n"), Matchers.containsString("ctx='/foo'"));
+        Assert.assertThat(connector.getResponses("GET /foo/bar/xxx HTTP/1.0\n\n"), Matchers.containsString("ctx='/foo'"));
+
+        // If we start foobar then foobar requests will be handled by foobar
+        foobar.start();
+        Assert.assertThat(connector.getResponses("GET / HTTP/1.0\n\n"), Matchers.containsString("ctx=''"));
+        Assert.assertThat(connector.getResponses("GET /foo/xxx HTTP/1.0\n\n"), Matchers.containsString("ctx='/foo'"));
+        Assert.assertThat(connector.getResponses("GET /foo/bar/xxx HTTP/1.0\n\n"), Matchers.containsString("ctx='/foo/bar'"));
     }
 
+
     @Test
     public void testContextVirtualGetContext() throws Exception
     {
         Server server = new Server();
-        LocalConnector connector = new LocalConnector();
+        LocalConnector connector = new LocalConnector(server);
         server.setConnectors(new Connector[] { connector });
         ContextHandlerCollection contexts = new ContextHandlerCollection();
         server.setHandler(contexts);
@@ -181,17 +303,17 @@ public class ContextHandlerTest
 
         // System.err.println(server.dump());
 
-        Assert.assertEquals(rootA._scontext,rootA._scontext.getContext("/"));
-        Assert.assertEquals(fooA._scontext,rootA._scontext.getContext("/foo"));
-        Assert.assertEquals(foobarA._scontext,rootA._scontext.getContext("/foo/bar"));
-        Assert.assertEquals(foobarA._scontext,rootA._scontext.getContext("/foo/bar/bob"));
+        Assert.assertEquals(rootA._scontext, rootA._scontext.getContext("/"));
+        Assert.assertEquals(fooA._scontext, rootA._scontext.getContext("/foo"));
+        Assert.assertEquals(foobarA._scontext, rootA._scontext.getContext("/foo/bar"));
+        Assert.assertEquals(foobarA._scontext, rootA._scontext.getContext("/foo/bar/bob"));
 
-        Assert.assertEquals(rootA._scontext,rootA._scontext.getContext("/other"));
-        Assert.assertEquals(rootB._scontext,rootB._scontext.getContext("/other"));
-        Assert.assertEquals(rootC._scontext,rootC._scontext.getContext("/other"));
+        Assert.assertEquals(rootA._scontext, rootA._scontext.getContext("/other"));
+        Assert.assertEquals(rootB._scontext, rootB._scontext.getContext("/other"));
+        Assert.assertEquals(rootC._scontext, rootC._scontext.getContext("/other"));
 
-        Assert.assertEquals(fooB._scontext,rootB._scontext.getContext("/foo/other"));
-        Assert.assertEquals(rootC._scontext,rootC._scontext.getContext("/foo/other"));
+        Assert.assertEquals(fooB._scontext, rootB._scontext.getContext("/foo/other"));
+        Assert.assertEquals(rootC._scontext, rootC._scontext.getContext("/foo/other"));
     }
 
 
@@ -199,7 +321,7 @@ public class ContextHandlerTest
     public void testVirtualHostWildcard() throws Exception
     {
         Server server = new Server();
-        LocalConnector connector = new LocalConnector();
+        LocalConnector connector = new LocalConnector(server);
         server.setConnectors(new Connector[] { connector });
 
         ContextHandler context = new ContextHandler("/");
@@ -241,27 +363,27 @@ public class ContextHandlerTest
 
         // test singular
         context.setVirtualHosts(new String[] { "www.example.com"} );
-        Assert.assertEquals(1,context.getVirtualHosts().length);
+        Assert.assertEquals(1, context.getVirtualHosts().length);
 
         // test adding two more
         context.addVirtualHosts(new String[] { "www.example2.com", "www.example3.com"});
-        Assert.assertEquals(3,context.getVirtualHosts().length);
+        Assert.assertEquals(3, context.getVirtualHosts().length);
 
         // test adding existing context
         context.addVirtualHosts(new String[] { "www.example.com" });
-        Assert.assertEquals(3,context.getVirtualHosts().length);
+        Assert.assertEquals(3, context.getVirtualHosts().length);
 
         // test removing existing
         context.removeVirtualHosts(new String[] { "www.example3.com" });
-        Assert.assertEquals(2,context.getVirtualHosts().length);
+        Assert.assertEquals(2, context.getVirtualHosts().length);
 
         // test removing non-existent
         context.removeVirtualHosts(new String[] { "www.example3.com" });
-        Assert.assertEquals(2,context.getVirtualHosts().length);
+        Assert.assertEquals(2, context.getVirtualHosts().length);
 
         // test removing all remaining and resets to null
         context.removeVirtualHosts(new String[] { "www.example.com", "www.example2.com" });
-        Assert.assertEquals(null,context.getVirtualHosts());
+        Assert.assertArrayEquals(null, context.getVirtualHosts());
 
     }
 
@@ -269,61 +391,63 @@ public class ContextHandlerTest
     public void testAttributes() throws Exception
     {
         ContextHandler handler = new ContextHandler();
+        handler.setServer(new Server());
         handler.setAttribute("aaa","111");
-        assertEquals("111",handler.getServletContext().getAttribute("aaa"));
-        assertEquals(null,handler.getAttribute("bbb"));
+        Assert.assertEquals("111", handler.getServletContext().getAttribute("aaa"));
+        Assert.assertEquals(null, handler.getAttribute("bbb"));
 
         handler.start();
 
         handler.getServletContext().setAttribute("aaa","000");
         handler.setAttribute("ccc","333");
         handler.getServletContext().setAttribute("ddd","444");
-        assertEquals("111",handler.getServletContext().getAttribute("aaa"));
-        assertEquals(null,handler.getServletContext().getAttribute("bbb"));
+        Assert.assertEquals("111", handler.getServletContext().getAttribute("aaa"));
+        Assert.assertEquals(null, handler.getServletContext().getAttribute("bbb"));
         handler.getServletContext().setAttribute("bbb","222");
-        assertEquals("333",handler.getServletContext().getAttribute("ccc"));
-        assertEquals("444",handler.getServletContext().getAttribute("ddd"));
+        Assert.assertEquals("333", handler.getServletContext().getAttribute("ccc"));
+        Assert.assertEquals("444", handler.getServletContext().getAttribute("ddd"));
 
-        assertEquals("111",handler.getAttribute("aaa"));
-        assertEquals(null,handler.getAttribute("bbb"));
-        assertEquals("333",handler.getAttribute("ccc"));
-        assertEquals(null,handler.getAttribute("ddd"));
+        Assert.assertEquals("111", handler.getAttribute("aaa"));
+        Assert.assertEquals(null, handler.getAttribute("bbb"));
+        Assert.assertEquals("333", handler.getAttribute("ccc"));
+        Assert.assertEquals(null, handler.getAttribute("ddd"));
 
         handler.stop();
 
-        assertEquals("111",handler.getServletContext().getAttribute("aaa"));
-        assertEquals(null,handler.getServletContext().getAttribute("bbb"));
-        assertEquals("333",handler.getServletContext().getAttribute("ccc"));
-        assertEquals(null,handler.getServletContext().getAttribute("ddd"));
+        Assert.assertEquals("111", handler.getServletContext().getAttribute("aaa"));
+        Assert.assertEquals(null, handler.getServletContext().getAttribute("bbb"));
+        Assert.assertEquals("333", handler.getServletContext().getAttribute("ccc"));
+        Assert.assertEquals(null, handler.getServletContext().getAttribute("ddd"));
     }
-    
+
     @Test
     public void testProtected() throws Exception
     {
         ContextHandler handler = new ContextHandler();
         String[] protectedTargets = {"/foo-inf", "/bar-inf"};
         handler.setProtectedTargets(protectedTargets);
-        
-        assertTrue(handler.isProtectedTarget("/foo-inf/x/y/z"));
-        assertFalse(handler.isProtectedTarget("/foo/x/y/z"));
-        assertTrue(handler.isProtectedTarget("/foo-inf?x=y&z=1"));
-        
+
+        Assert.assertTrue(handler.isProtectedTarget("/foo-inf/x/y/z"));
+        Assert.assertFalse(handler.isProtectedTarget("/foo/x/y/z"));
+        Assert.assertTrue(handler.isProtectedTarget("/foo-inf?x=y&z=1"));
+        Assert.assertFalse(handler.isProtectedTarget("/foo-inf-bar"));
+
         protectedTargets = new String[4];
         System.arraycopy(handler.getProtectedTargets(), 0, protectedTargets, 0, 2);
         protectedTargets[2] = "/abc";
         protectedTargets[3] = "/def";
         handler.setProtectedTargets(protectedTargets);
-        
-        assertTrue(handler.isProtectedTarget("/foo-inf/x/y/z"));
-        assertFalse(handler.isProtectedTarget("/foo/x/y/z"));
-        assertTrue(handler.isProtectedTarget("/foo-inf?x=y&z=1"));
-        assertTrue(handler.isProtectedTarget("/abc/124"));
-        assertTrue(handler.isProtectedTarget("//def"));
-       
-        assertTrue(handler.isProtectedTarget("/ABC/7777"));
+
+        Assert.assertTrue(handler.isProtectedTarget("/foo-inf/x/y/z"));
+        Assert.assertFalse(handler.isProtectedTarget("/foo/x/y/z"));
+        Assert.assertTrue(handler.isProtectedTarget("/foo-inf?x=y&z=1"));
+        Assert.assertTrue(handler.isProtectedTarget("/abc/124"));
+        Assert.assertTrue(handler.isProtectedTarget("//def"));
+
+        Assert.assertTrue(handler.isProtectedTarget("/ABC/7777"));
     }
-    
-    
+
+
 
     private void checkResourcePathsForExampleWebApp(String root) throws IOException
     {
@@ -331,67 +455,39 @@ public class ContextHandlerTest
 
         ContextHandler handler = new ContextHandler();
 
-        assertTrue("Not a directory " + testDirectory,testDirectory.isDirectory());
+        Assert.assertTrue("Not a directory " + testDirectory, testDirectory.isDirectory());
         handler.setBaseResource(Resource.newResource(Resource.toURL(testDirectory)));
 
-        List<String> paths = new ArrayList<String>(handler.getResourcePaths(root));
-        assertEquals(2,paths.size());
+        List<String> paths = new ArrayList<>(handler.getResourcePaths(root));
+        Assert.assertEquals(2, paths.size());
 
         Collections.sort(paths);
-        assertEquals("/WEB-INF/jsp/",paths.get(0));
-        assertEquals("/WEB-INF/web.xml",paths.get(1));
+        Assert.assertEquals("/WEB-INF/jsp/", paths.get(0));
+        Assert.assertEquals("/WEB-INF/web.xml", paths.get(1));
     }
 
     private File setupTestDirectory() throws IOException
     {
-        File tmpDir = new File( System.getProperty( "basedir" ) + "/target/tmp/ContextHandlerTest" );
+        File tmpDir = new File( System.getProperty( "basedir",".") + "/target/tmp/ContextHandlerTest" );
+        tmpDir=tmpDir.getCanonicalFile();
         if (!tmpDir.exists())
-            assertTrue(tmpDir.mkdirs());
+            Assert.assertTrue(tmpDir.mkdirs());
         File tmp = File.createTempFile("cht",null, tmpDir );
-        assertTrue(tmp.delete());
-        assertTrue(tmp.mkdir());
+        Assert.assertTrue(tmp.delete());
+        Assert.assertTrue(tmp.mkdir());
         tmp.deleteOnExit();
         File root = new File(tmp,getClass().getName());
-        assertTrue(root.mkdir());
+        Assert.assertTrue(root.mkdir());
 
         File webInf = new File(root,"WEB-INF");
-        assertTrue(webInf.mkdir());
+        Assert.assertTrue(webInf.mkdir());
 
-        assertTrue(new File(webInf,"jsp").mkdir());
-        assertTrue(new File(webInf,"web.xml").createNewFile());
+        Assert.assertTrue(new File(webInf, "jsp").mkdir());
+        Assert.assertTrue(new File(webInf, "web.xml").createNewFile());
 
         return root;
     }
 
-    @Test
-    public void testUncheckedPrintWriter() throws Exception
-    {
-        Server server = new Server();
-        server.setUncheckedPrintWriter(true);
-        LocalConnector connector = new LocalConnector();
-        server.setConnectors(new Connector[] { connector });
-        ContextHandler context = new ContextHandler("/");
-        WriterHandler handler = new WriterHandler();
-        context.setHandler(handler);
-        server.setHandler(context);
-
-        try
-        {
-            server.start();
-
-            String response = connector.getResponses("GET / HTTP/1.1\n" + "Host: www.example.com.\n\n");
-
-            Assert.assertTrue(response.indexOf("Goodbye")>0);
-            Assert.assertTrue(response.indexOf("dead")<0);
-            Assert.assertTrue(handler.error);
-            Assert.assertTrue(handler.throwable!=null);
-        }
-        finally
-        {
-            server.stop();
-        }
-    }
-
     private void checkWildcardHost(boolean succeed, Server server, String[] contextHosts, String[] requestHosts) throws Exception
     {
         LocalConnector connector = (LocalConnector)server.getConnectors()[0];
@@ -401,11 +497,11 @@ public class ContextHandlerTest
         IsHandledHandler handler = (IsHandledHandler)context.getHandler();
         for(String host : requestHosts)
         {
-            connector.getResponses("GET / HTTP/1.1\n" + "Host: "+host+"\n\n");
+            connector.getResponses("GET / HTTP/1.1\n" + "Host: "+host+"\nConnection:close\n\n");
             if(succeed)
-                assertTrue("'"+host+"' should have been handled.",handler.isHandled());
+                Assert.assertTrue("'" + host + "' should have been handled.", handler.isHandled());
             else
-                assertFalse("'"+host + "' should not have been handled.", handler.isHandled());
+                Assert.assertFalse("'" + host + "' should not have been handled.", handler.isHandled());
             handler.reset();
         }
 
@@ -420,6 +516,7 @@ public class ContextHandlerTest
             return handled;
         }
 
+        @Override
         public void handle(String s, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
         {
             baseRequest.setHandled(true);
@@ -432,37 +529,18 @@ public class ContextHandlerTest
         }
     }
 
-    private static final class WriterHandler extends AbstractHandler
+    private static final class ContextPathHandler extends AbstractHandler
     {
-        boolean error;
-        Throwable throwable;
-
-
+        @Override
         public void handle(String s, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
         {
             baseRequest.setHandled(true);
-            error = false;
-            throwable=null;
 
             response.setStatus(200);
             response.setContentType("text/plain; charset=utf-8");
             response.setHeader("Connection","close");
             PrintWriter writer = response.getWriter();
-            try
-            {
-                writer.write("Goodbye cruel world\n");
-                writer.close();
-                response.flushBuffer();
-                //writer.write("speaking from the dead");
-                writer.write("give the printwriter a chance"); //should create an error
-                if (writer.checkError())
-                    writer.write("didn't take the chance, will throw now"); //write after an error
-            }
-            catch(Throwable th)
-            {
-                throwable=th;
-            }
-            error=writer.checkError();
+            writer.println("ctx='"+request.getContextPath()+"'");
         }
     }
 }
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/IPAccessHandlerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/IPAccessHandlerTest.java
index 28dfd8f..2e1cbe9 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/IPAccessHandlerTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/IPAccessHandlerTest.java
@@ -27,6 +27,7 @@ import java.io.IOException;
 import java.io.InputStreamReader;
 import java.io.OutputStream;
 import java.net.Socket;
+import java.nio.charset.StandardCharsets;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.LinkedHashMap;
@@ -41,9 +42,10 @@ import javax.servlet.http.HttpServletResponse;
 
 import org.eclipse.jetty.http.HttpStatus;
 import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.NetworkConnector;
 import org.eclipse.jetty.server.Request;
 import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.bio.SocketConnector;
+import org.eclipse.jetty.server.ServerConnector;
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
 import org.junit.Test;
@@ -55,26 +57,28 @@ import org.junit.runners.Parameterized.Parameters;
 public class IPAccessHandlerTest
 {
     private static Server _server;
-    private static Connector _connector;
+    private static NetworkConnector _connector;
     private static IPAccessHandler _handler;
-    
+
     private String _white;
     private String _black;
     private String _host;
     private String _uri;
     private String _code;
-    
+    private boolean _byPath;
+
     @BeforeClass
     public static void setUp()
         throws Exception
     {
         _server = new Server();
-        _connector = new SocketConnector();
+        _connector = new ServerConnector(_server);
         _server.setConnectors(new Connector[] { _connector });
 
         _handler = new IPAccessHandler();
         _handler.setHandler(new AbstractHandler()
             {
+                @Override
                 public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
                 {
                     baseRequest.setHandled(true);
@@ -84,7 +88,7 @@ public class IPAccessHandlerTest
         _server.setHandler(_handler);
         _server.start();
     }
-    
+
     /* ------------------------------------------------------------ */
     @AfterClass
     public static void tearDown()
@@ -92,17 +96,18 @@ public class IPAccessHandlerTest
     {
         _server.stop();
     }
-    
+
     /* ------------------------------------------------------------ */
-    public IPAccessHandlerTest(String white, String black, String host, String uri, String code)
+    public IPAccessHandlerTest(String white, String black, String host, String uri, String code, boolean byPath)
     {
         _white = white;
         _black = black;
         _host  = host;
         _uri   = uri;
         _code  = code;
+        _byPath = byPath;
     }
-     
+
     /* ------------------------------------------------------------ */
     @Test
     public void testHandler()
@@ -110,7 +115,8 @@ public class IPAccessHandlerTest
     {
         _handler.setWhite(_white.split(";",-1));
         _handler.setBlack(_black.split(";",-1));
-        
+        _handler.setWhiteListByPath(_byPath);
+
         String request = "GET " + _uri + " HTTP/1.1\n" + "Host: "+ _host + "\n\n";
         Socket socket = new Socket("127.0.0.1", _connector.getLocalPort());
         socket.setSoTimeout(5000);
@@ -119,11 +125,14 @@ public class IPAccessHandlerTest
             OutputStream output = socket.getOutputStream();
             BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
 
-            output.write(request.getBytes("UTF-8"));
+            output.write(request.getBytes(StandardCharsets.UTF_8));
             output.flush();
 
             Response response = readResponse(input);
-            assertEquals(_code, response.getCode());
+            Object[] params = new Object[]{
+                    "Request WBHUC", _white, _black, _host, _uri, _code,
+                    "Response", response.getCode()};
+            assertEquals(Arrays.deepToString(params), _code, response.getCode());
         }
         finally
         {
@@ -237,163 +246,319 @@ public class IPAccessHandlerTest
             return builder.toString();
         }
     }
-    
+
    /* ------------------------------------------------------------ */
     @Parameters
     public static Collection<Object[]> data() {
         Object[][] data = new Object[][] {
             // Empty lists
-            {"", "", "127.0.0.1", "/",          "200"},
-            {"", "", "127.0.0.1", "/dump/info", "200"},
-            
-            // White list 
-            {"127.0.0.1", "", "127.0.0.1", "/",          "200"},
-            {"127.0.0.1", "", "127.0.0.1", "/dispatch",  "200"},
-            {"127.0.0.1", "", "127.0.0.1", "/dump/info", "200"},
-            
-            {"127.0.0.1|/", "", "127.0.0.1", "/",          "200"},
-            {"127.0.0.1|/", "", "127.0.0.1", "/dispatch",  "403"},
-            {"127.0.0.1|/", "", "127.0.0.1", "/dump/info", "403"},
-            
-            {"127.0.0.1|/*", "", "127.0.0.1", "/",          "200"},
-            {"127.0.0.1|/*", "", "127.0.0.1", "/dispatch",  "200"},
-            {"127.0.0.1|/*", "", "127.0.0.1", "/dump/info", "200"},
-            
-            {"127.0.0.1|/dump/*", "", "127.0.0.1", "/",          "403"},
-            {"127.0.0.1|/dump/*", "", "127.0.0.1", "/dispatch",  "403"},
-            {"127.0.0.1|/dump/*", "", "127.0.0.1", "/dump/info", "200"},
-            {"127.0.0.1|/dump/*", "", "127.0.0.1", "/dump/test", "200"},
-
-            {"127.0.0.1|/dump/info", "", "127.0.0.1", "/",          "403"},
-            {"127.0.0.1|/dump/info", "", "127.0.0.1", "/dispatch",  "403"},
-            {"127.0.0.1|/dump/info", "", "127.0.0.1", "/dump/info", "200"},
-            {"127.0.0.1|/dump/info", "", "127.0.0.1", "/dump/test", "403"},
-
-            {"127.0.0.1|/dump/info;127.0.0.1|/dump/test", "", "127.0.0.1", "/",          "403"},
-            {"127.0.0.1|/dump/info;127.0.0.1|/dump/test", "", "127.0.0.1", "/dispatch",  "403"},
-            {"127.0.0.1|/dump/info;127.0.0.1|/dump/test", "", "127.0.0.1", "/dump/info", "200"},
-            {"127.0.0.1|/dump/info;127.0.0.1|/dump/test", "", "127.0.0.1", "/dump/test", "200"},
-            {"127.0.0.1|/dump/info;127.0.0.1|/dump/test", "", "127.0.0.1", "/dump/fail", "403"},
-            
-            {"127.0.0.0-2|", "", "127.0.0.1", "/",          "200"},
-            {"127.0.0.0-2|", "", "127.0.0.1", "/dump/info", "200"},
-
-            {"127.0.0.0-2|/", "", "127.0.0.1", "/",          "200"},
-            {"127.0.0.0-2|/", "", "127.0.0.1", "/dispatch",  "403"},
-            {"127.0.0.0-2|/", "", "127.0.0.1", "/dump/info", "403"},
-
-            {"127.0.0.0-2|/dump/*", "", "127.0.0.1", "/",          "403"},
-            {"127.0.0.0-2|/dump/*", "", "127.0.0.1", "/dispatch",  "403"},
-            {"127.0.0.0-2|/dump/*", "", "127.0.0.1", "/dump/info", "200"},
-
-            {"127.0.0.0-2|/dump/info", "", "127.0.0.1", "/",          "403"},
-            {"127.0.0.0-2|/dump/info", "", "127.0.0.1", "/dispatch",  "403"},
-            {"127.0.0.0-2|/dump/info", "", "127.0.0.1", "/dump/info", "200"},
-            {"127.0.0.0-2|/dump/info", "", "127.0.0.1", "/dump/test", "403"},
-
-            {"127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "", "127.0.0.1", "/",          "403"},
-            {"127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "", "127.0.0.1", "/dispatch",  "403"},
-            {"127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "", "127.0.0.1", "/dump/info", "200"},
-            {"127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "", "127.0.0.1", "/dump/test", "200"},
-            {"127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "", "127.0.0.1", "/dump/fail", "403"},
-            
+            {"", "", "127.0.0.1", "/",          "200", false},
+            {"", "", "127.0.0.1", "/dump/info", "200", false},
+
+            // White list
+            {"127.0.0.1", "", "127.0.0.1", "/",          "200", false},
+            {"127.0.0.1", "", "127.0.0.1", "/dispatch",  "200", false},
+            {"127.0.0.1", "", "127.0.0.1", "/dump/info", "200", false},
+
+            {"127.0.0.1|/", "", "127.0.0.1", "/",          "200", false},
+            {"127.0.0.1|/", "", "127.0.0.1", "/dispatch",  "403", false},
+            {"127.0.0.1|/", "", "127.0.0.1", "/dump/info", "403", false},
+
+            {"127.0.0.1|/*", "", "127.0.0.1", "/",          "200", false},
+            {"127.0.0.1|/*", "", "127.0.0.1", "/dispatch",  "200", false},
+            {"127.0.0.1|/*", "", "127.0.0.1", "/dump/info", "200", false},
+
+            {"127.0.0.1|/dump/*", "", "127.0.0.1", "/",          "403", false},
+            {"127.0.0.1|/dump/*", "", "127.0.0.1", "/dispatch",  "403", false},
+            {"127.0.0.1|/dump/*", "", "127.0.0.1", "/dump/info", "200", false},
+            {"127.0.0.1|/dump/*", "", "127.0.0.1", "/dump/test", "200", false},
+
+            {"127.0.0.1|/dump/info", "", "127.0.0.1", "/",          "403", false},
+            {"127.0.0.1|/dump/info", "", "127.0.0.1", "/dispatch",  "403", false},
+            {"127.0.0.1|/dump/info", "", "127.0.0.1", "/dump/info", "200", false},
+            {"127.0.0.1|/dump/info", "", "127.0.0.1", "/dump/test", "403", false},
+
+            {"127.0.0.1|/dump/info;127.0.0.1|/dump/test", "", "127.0.0.1", "/",          "403", false},
+            {"127.0.0.1|/dump/info;127.0.0.1|/dump/test", "", "127.0.0.1", "/dispatch",  "403", false},
+            {"127.0.0.1|/dump/info;127.0.0.1|/dump/test", "", "127.0.0.1", "/dump/info", "200", false},
+            {"127.0.0.1|/dump/info;127.0.0.1|/dump/test", "", "127.0.0.1", "/dump/test", "200", false},
+            {"127.0.0.1|/dump/info;127.0.0.1|/dump/test", "", "127.0.0.1", "/dump/fail", "403", false},
+
+            {"127.0.0.0-2|", "", "127.0.0.1", "/",          "200", false},
+            {"127.0.0.0-2|", "", "127.0.0.1", "/dump/info", "403", false},
+
+            {"127.0.0.0-2|/", "", "127.0.0.1", "/",          "200", false},
+            {"127.0.0.0-2|/", "", "127.0.0.1", "/dispatch",  "403", false},
+            {"127.0.0.0-2|/", "", "127.0.0.1", "/dump/info", "403", false},
+
+            {"127.0.0.0-2|/dump/*", "", "127.0.0.1", "/",          "403", false},
+            {"127.0.0.0-2|/dump/*", "", "127.0.0.1", "/dispatch",  "403", false},
+            {"127.0.0.0-2|/dump/*", "", "127.0.0.1", "/dump/info", "200", false},
+
+            {"127.0.0.0-2|/dump/info", "", "127.0.0.1", "/",          "403", false},
+            {"127.0.0.0-2|/dump/info", "", "127.0.0.1", "/dispatch",  "403", false},
+            {"127.0.0.0-2|/dump/info", "", "127.0.0.1", "/dump/info", "200", false},
+            {"127.0.0.0-2|/dump/info", "", "127.0.0.1", "/dump/test", "403", false},
+
+            {"127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "", "127.0.0.1", "/",          "403", false},
+            {"127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "", "127.0.0.1", "/dispatch",  "403", false},
+            {"127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "", "127.0.0.1", "/dump/info", "200", false},
+            {"127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "", "127.0.0.1", "/dump/test", "200", false},
+            {"127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "", "127.0.0.1", "/dump/fail", "403", false},
+
+            // Black list
+            {"", "127.0.0.1", "127.0.0.1", "/",          "403", false},
+            {"", "127.0.0.1", "127.0.0.1", "/dispatch",  "403", false},
+            {"", "127.0.0.1", "127.0.0.1", "/dump/info", "403", false},
+
+            {"", "127.0.0.1|/", "127.0.0.1", "/",          "403", false},
+            {"", "127.0.0.1|/", "127.0.0.1", "/dispatch",  "200", false},
+            {"", "127.0.0.1|/", "127.0.0.1", "/dump/info", "200", false},
+
+            {"", "127.0.0.1|/*", "127.0.0.1", "/",          "403", false},
+            {"", "127.0.0.1|/*", "127.0.0.1", "/dispatch",  "403", false},
+            {"", "127.0.0.1|/*", "127.0.0.1", "/dump/info", "403", false},
+
+            {"", "127.0.0.1|/dump/*", "127.0.0.1", "/",          "200", false},
+            {"", "127.0.0.1|/dump/*", "127.0.0.1", "/dispatch",  "200", false},
+            {"", "127.0.0.1|/dump/*", "127.0.0.1", "/dump/info", "403", false},
+            {"", "127.0.0.1|/dump/*", "127.0.0.1", "/dump/test", "403", false},
+
+            {"", "127.0.0.1|/dump/info", "127.0.0.1", "/",          "200", false},
+            {"", "127.0.0.1|/dump/info", "127.0.0.1", "/dispatch",  "200", false},
+            {"", "127.0.0.1|/dump/info", "127.0.0.1", "/dump/info", "403", false},
+            {"", "127.0.0.1|/dump/info", "127.0.0.1", "/dump/test", "200", false},
+
+            {"", "127.0.0.1|/dump/info;127.0.0.1|/dump/test", "127.0.0.1", "/",          "200", false},
+            {"", "127.0.0.1|/dump/info;127.0.0.1|/dump/test", "127.0.0.1", "/dispatch",  "200", false},
+            {"", "127.0.0.1|/dump/info;127.0.0.1|/dump/test", "127.0.0.1", "/dump/info", "403", false},
+            {"", "127.0.0.1|/dump/info;127.0.0.1|/dump/test", "127.0.0.1", "/dump/test", "403", false},
+            {"", "127.0.0.1|/dump/info;127.0.0.1|/dump/test", "127.0.0.1", "/dump/fail", "200", false},
+
+            {"", "127.0.0.0-2|", "127.0.0.1", "/",          "403", false},
+            {"", "127.0.0.0-2|", "127.0.0.1", "/dump/info", "200", false},
+
+            {"", "127.0.0.0-2|/", "127.0.0.1", "/",          "403", false},
+            {"", "127.0.0.0-2|/", "127.0.0.1", "/dispatch",  "200", false},
+            {"", "127.0.0.0-2|/", "127.0.0.1", "/dump/info", "200", false},
+
+            {"", "127.0.0.0-2|/dump/*", "127.0.0.1", "/",          "200", false},
+            {"", "127.0.0.0-2|/dump/*", "127.0.0.1", "/dispatch",  "200", false},
+            {"", "127.0.0.0-2|/dump/*", "127.0.0.1", "/dump/info", "403", false},
+
+            {"", "127.0.0.0-2|/dump/info", "127.0.0.1", "/",          "200", false},
+            {"", "127.0.0.0-2|/dump/info", "127.0.0.1", "/dispatch",  "200", false},
+            {"", "127.0.0.0-2|/dump/info", "127.0.0.1", "/dump/info", "403", false},
+            {"", "127.0.0.0-2|/dump/info", "127.0.0.1", "/dump/test", "200", false},
+
+            {"", "127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "127.0.0.1", "/",          "200", false},
+            {"", "127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "127.0.0.1", "/dispatch",  "200", false},
+            {"", "127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "127.0.0.1", "/dump/info", "403", false},
+            {"", "127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "127.0.0.1", "/dump/test", "403", false},
+            {"", "127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "127.0.0.1", "/dump/fail", "200", false},
+
+            // Both lists
+            {"127.0.0.1|/dump", "127.0.0.1|/dump/fail", "127.0.0.1", "/dump",      "200", false},
+            {"127.0.0.1|/dump", "127.0.0.1|/dump/fail", "127.0.0.1", "/dump/info", "403", false},
+            {"127.0.0.1|/dump", "127.0.0.1|/dump/fail", "127.0.0.1", "/dump/fail", "403", false},
+
+            {"127.0.0.1|/dump/*", "127.0.0.1|/dump/fail", "127.0.0.1", "/dump",      "200", false},
+            {"127.0.0.1|/dump/*", "127.0.0.1|/dump/fail", "127.0.0.1", "/dump/info", "200", false},
+            {"127.0.0.1|/dump/*", "127.0.0.1|/dump/fail", "127.0.0.1", "/dump/fail", "403", false},
+
+            {"127.0.0.1|/dump/*", "127.0.0.1|/dump/test;127.0.0.1|/dump/fail", "127.0.0.1", "/dump",      "200", false},
+            {"127.0.0.1|/dump/*", "127.0.0.1|/dump/test;127.0.0.1|/dump/fail", "127.0.0.1", "/dump/info", "200", false},
+            {"127.0.0.1|/dump/*", "127.0.0.1|/dump/test;127.0.0.1|/dump/fail", "127.0.0.1", "/dump/test", "403", false},
+            {"127.0.0.1|/dump/*", "127.0.0.1|/dump/test;127.0.0.1|/dump/fail", "127.0.0.1", "/dump/fail", "403", false},
+
+            {"127.0.0.1|/dump/info;127.0.0.1|/dump/test", "127.0.0.1|/dump/test", "127.0.0.1", "/dump",      "403", false},
+            {"127.0.0.1|/dump/info;127.0.0.1|/dump/test", "127.0.0.1|/dump/test", "127.0.0.1", "/dump/info", "200", false},
+            {"127.0.0.1|/dump/info;127.0.0.1|/dump/test", "127.0.0.1|/dump/test", "127.0.0.1", "/dump/test", "403", false},
+            {"127.0.0.1|/dump/info;127.0.0.1|/dump/test", "127.0.0.1|/dump/test", "127.0.0.1", "/dump/fail", "403", false},
+
+            {"127.0.0.1|/;127.0.0.0-2|/dump/*", "127.0.0.0,1|/dump/fail", "127.0.0.1", "/",          "200", false},
+            {"127.0.0.1|/;127.0.0.0-2|/dump/*", "127.0.0.0,1|/dump/fail", "127.0.0.1", "/dump/info", "200", false},
+            {"127.0.0.1|/;127.0.0.0-2|/dump/*", "127.0.0.0,1|/dump/fail", "127.0.0.1", "/dump/fail", "403", false},
+
+            // Different address
+            {"127.0.0.2", "", "127.0.0.1", "/",          "403", false},
+            {"127.0.0.2", "", "127.0.0.1", "/dump/info", "403", false},
+
+            {"127.0.0.2|/dump/*", "", "127.0.0.1", "/",          "403", false},
+            {"127.0.0.2|/dump/*", "", "127.0.0.1", "/dump/info", "403", false},
+
+            {"127.0.0.2|/dump/info", "", "127.0.0.1", "/",          "403", false},
+            {"127.0.0.2|/dump/info", "", "127.0.0.1", "/dump/info", "403", false},
+            {"127.0.0.2|/dump/info", "", "127.0.0.1", "/dump/test", "403", false},
+
+            {"127.0.0.1|/dump/info;127.0.0.2|/dump/test", "", "127.0.0.1", "/",          "403", false},
+            {"127.0.0.1|/dump/info;127.0.0.2|/dump/test", "", "127.0.0.1", "/dispatch",  "403", false},
+            {"127.0.0.1|/dump/info;127.0.0.2|/dump/test", "", "127.0.0.1", "/dump/info", "200", false},
+            {"127.0.0.1|/dump/info;127.0.0.2|/dump/test", "", "127.0.0.1", "/dump/test", "403", false},
+            {"127.0.0.1|/dump/info;127.0.0.2|/dump/test", "", "127.0.0.1", "/dump/fail", "403", false},
+
+            {"172.0.0.0-255", "", "127.0.0.1", "/",          "403", false},
+            {"172.0.0.0-255", "", "127.0.0.1", "/dump/info", "403", false},
+
+            {"172.0.0.0-255|/dump/*;127.0.0.0-255|/dump/*", "", "127.0.0.1", "/",          "403", false},
+            {"172.0.0.0-255|/dump/*;127.0.0.0-255|/dump/*", "", "127.0.0.1", "/dispatch",  "403", false},
+            {"172.0.0.0-255|/dump/*;127.0.0.0-255|/dump/*", "", "127.0.0.1", "/dump/info", "200", false},
+
+            /*-----------------------------------------------------------------------------------------*/
+            // Match by path starts with [117]
+            // test cases affected by _whiteListByPath highlighted accordingly
+
+            {"", "", "127.0.0.1", "/",          "200", true},
+            {"", "", "127.0.0.1", "/dump/info", "200", true},
+
+            // White list
+            {"127.0.0.1", "", "127.0.0.1", "/",          "200", true},
+            {"127.0.0.1", "", "127.0.0.1", "/dispatch",  "200", true},
+            {"127.0.0.1", "", "127.0.0.1", "/dump/info", "200", true},
+
+            {"127.0.0.1|/", "", "127.0.0.1", "/",          "200", true},
+            {"127.0.0.1|/", "", "127.0.0.1", "/dispatch",  "200", true}, // _whiteListByPath
+            {"127.0.0.1|/", "", "127.0.0.1", "/dump/info", "200", true}, // _whiteListByPath
+
+            {"127.0.0.1|/*", "", "127.0.0.1", "/",          "200", true},
+            {"127.0.0.1|/*", "", "127.0.0.1", "/dispatch",  "200", true},
+            {"127.0.0.1|/*", "", "127.0.0.1", "/dump/info", "200", true},
+
+            {"127.0.0.1|/dump/*", "", "127.0.0.1", "/",          "200", true}, // _whiteListByPath
+            {"127.0.0.1|/dump/*", "", "127.0.0.1", "/dispatch",  "200", true}, // _whiteListByPath
+            {"127.0.0.1|/dump/*", "", "127.0.0.1", "/dump/info", "200", true},
+            {"127.0.0.1|/dump/*", "", "127.0.0.1", "/dump/test", "200", true},
+
+            {"127.0.0.1|/dump/info", "", "127.0.0.1", "/",          "200", true}, // _whiteListByPath
+            {"127.0.0.1|/dump/info", "", "127.0.0.1", "/dispatch",  "200", true}, // _whiteListByPath
+            {"127.0.0.1|/dump/info", "", "127.0.0.1", "/dump/info", "200", true},
+            {"127.0.0.1|/dump/info", "", "127.0.0.1", "/dump/test", "200", true}, // _whiteListByPath
+
+            {"127.0.0.1|/dump/info;127.0.0.1|/dump/test", "", "127.0.0.1", "/",          "200", true}, // _whiteListByPath
+            {"127.0.0.1|/dump/info;127.0.0.1|/dump/test", "", "127.0.0.1", "/dispatch",  "200", true}, // _whiteListByPath
+            {"127.0.0.1|/dump/info;127.0.0.1|/dump/test", "", "127.0.0.1", "/dump/info", "200", true},
+            {"127.0.0.1|/dump/info;127.0.0.1|/dump/test", "", "127.0.0.1", "/dump/test", "200", true},
+            {"127.0.0.1|/dump/info;127.0.0.1|/dump/test", "", "127.0.0.1", "/dump/fail", "200", true}, // _whiteListByPath
+
+            {"127.0.0.0-2|", "", "127.0.0.1", "/",          "200", true},
+            {"127.0.0.0-2|", "", "127.0.0.1", "/dump/info", "200", true},
+
+            {"127.0.0.0-2|/", "", "127.0.0.1", "/",          "200", true},
+            {"127.0.0.0-2|/", "", "127.0.0.1", "/dispatch",  "200", true}, // _whiteListByPath
+            {"127.0.0.0-2|/", "", "127.0.0.1", "/dump/info", "200", true}, // _whiteListByPath
+
+            {"127.0.0.0-2|/dump/*", "", "127.0.0.1", "/",          "200", true}, // _whiteListByPath
+            {"127.0.0.0-2|/dump/*", "", "127.0.0.1", "/dispatch",  "200", true}, // _whiteListByPath
+            {"127.0.0.0-2|/dump/*", "", "127.0.0.1", "/dump/info", "200", true},
+
+            {"127.0.0.0-2|/dump/info", "", "127.0.0.1", "/",          "200", true}, // _whiteListByPath
+            {"127.0.0.0-2|/dump/info", "", "127.0.0.1", "/dispatch",  "200", true}, // _whiteListByPath
+            {"127.0.0.0-2|/dump/info", "", "127.0.0.1", "/dump/info", "200", true},
+            {"127.0.0.0-2|/dump/info", "", "127.0.0.1", "/dump/test", "200", true}, // _whiteListByPath
+
+            {"127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "", "127.0.0.1", "/",          "200", true}, // _whiteListByPath
+            {"127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "", "127.0.0.1", "/dispatch",  "200", true}, // _whiteListByPath
+            {"127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "", "127.0.0.1", "/dump/info", "200", true},
+            {"127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "", "127.0.0.1", "/dump/test", "200", true},
+            {"127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "", "127.0.0.1", "/dump/fail", "200", true}, // _whiteListByPath
+
             // Black list
-            {"", "127.0.0.1", "127.0.0.1", "/",          "403"},
-            {"", "127.0.0.1", "127.0.0.1", "/dispatch",  "403"},
-            {"", "127.0.0.1", "127.0.0.1", "/dump/info", "403"},
-            
-            {"", "127.0.0.1|/", "127.0.0.1", "/",          "403"},
-            {"", "127.0.0.1|/", "127.0.0.1", "/dispatch",  "200"},
-            {"", "127.0.0.1|/", "127.0.0.1", "/dump/info", "200"},
-            
-            {"", "127.0.0.1|/*", "127.0.0.1", "/",          "403"},
-            {"", "127.0.0.1|/*", "127.0.0.1", "/dispatch",  "403"},
-            {"", "127.0.0.1|/*", "127.0.0.1", "/dump/info", "403"},
-            
-            {"", "127.0.0.1|/dump/*", "127.0.0.1", "/",          "200"},
-            {"", "127.0.0.1|/dump/*", "127.0.0.1", "/dispatch",  "200"},
-            {"", "127.0.0.1|/dump/*", "127.0.0.1", "/dump/info", "403"},
-            {"", "127.0.0.1|/dump/*", "127.0.0.1", "/dump/test", "403"},
-
-            {"", "127.0.0.1|/dump/info", "127.0.0.1", "/",          "200"},
-            {"", "127.0.0.1|/dump/info", "127.0.0.1", "/dispatch",  "200"},
-            {"", "127.0.0.1|/dump/info", "127.0.0.1", "/dump/info", "403"},
-            {"", "127.0.0.1|/dump/info", "127.0.0.1", "/dump/test", "200"},
-
-            {"", "127.0.0.1|/dump/info;127.0.0.1|/dump/test", "127.0.0.1", "/",          "200"},
-            {"", "127.0.0.1|/dump/info;127.0.0.1|/dump/test", "127.0.0.1", "/dispatch",  "200"},
-            {"", "127.0.0.1|/dump/info;127.0.0.1|/dump/test", "127.0.0.1", "/dump/info", "403"},
-            {"", "127.0.0.1|/dump/info;127.0.0.1|/dump/test", "127.0.0.1", "/dump/test", "403"},
-            {"", "127.0.0.1|/dump/info;127.0.0.1|/dump/test", "127.0.0.1", "/dump/fail", "200"},
-            
-            {"", "127.0.0.0-2|", "127.0.0.1", "/",          "403"},
-            {"", "127.0.0.0-2|", "127.0.0.1", "/dump/info", "403"},
-
-            {"", "127.0.0.0-2|/", "127.0.0.1", "/",          "403"},
-            {"", "127.0.0.0-2|/", "127.0.0.1", "/dispatch",  "200"},
-            {"", "127.0.0.0-2|/", "127.0.0.1", "/dump/info", "200"},
-
-            {"", "127.0.0.0-2|/dump/*", "127.0.0.1", "/",          "200"},
-            {"", "127.0.0.0-2|/dump/*", "127.0.0.1", "/dispatch",  "200"},
-            {"", "127.0.0.0-2|/dump/*", "127.0.0.1", "/dump/info", "403"},
-
-            {"", "127.0.0.0-2|/dump/info", "127.0.0.1", "/",          "200"},
-            {"", "127.0.0.0-2|/dump/info", "127.0.0.1", "/dispatch",  "200"},
-            {"", "127.0.0.0-2|/dump/info", "127.0.0.1", "/dump/info", "403"},
-            {"", "127.0.0.0-2|/dump/info", "127.0.0.1", "/dump/test", "200"},
-
-            {"", "127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "127.0.0.1", "/",          "200"},
-            {"", "127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "127.0.0.1", "/dispatch",  "200"},
-            {"", "127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "127.0.0.1", "/dump/info", "403"},
-            {"", "127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "127.0.0.1", "/dump/test", "403"},
-            {"", "127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "127.0.0.1", "/dump/fail", "200"},
-                        
+            {"", "127.0.0.1", "127.0.0.1", "/",          "403", true},
+            {"", "127.0.0.1", "127.0.0.1", "/dispatch",  "403", true},
+            {"", "127.0.0.1", "127.0.0.1", "/dump/info", "403", true},
+
+            {"", "127.0.0.1|/", "127.0.0.1", "/",          "403", true},
+            {"", "127.0.0.1|/", "127.0.0.1", "/dispatch",  "200", true},
+            {"", "127.0.0.1|/", "127.0.0.1", "/dump/info", "200", true},
+
+            {"", "127.0.0.1|/*", "127.0.0.1", "/",          "403", true},
+            {"", "127.0.0.1|/*", "127.0.0.1", "/dispatch",  "403", true},
+            {"", "127.0.0.1|/*", "127.0.0.1", "/dump/info", "403", true},
+
+            {"", "127.0.0.1|/dump/*", "127.0.0.1", "/",          "200", true},
+            {"", "127.0.0.1|/dump/*", "127.0.0.1", "/dispatch",  "200", true},
+            {"", "127.0.0.1|/dump/*", "127.0.0.1", "/dump/info", "403", true},
+            {"", "127.0.0.1|/dump/*", "127.0.0.1", "/dump/test", "403", true},
+
+            {"", "127.0.0.1|/dump/info", "127.0.0.1", "/",          "200", true},
+            {"", "127.0.0.1|/dump/info", "127.0.0.1", "/dispatch",  "200", true},
+            {"", "127.0.0.1|/dump/info", "127.0.0.1", "/dump/info", "403", true},
+            {"", "127.0.0.1|/dump/info", "127.0.0.1", "/dump/test", "200", true},
+
+            {"", "127.0.0.1|/dump/info;127.0.0.1|/dump/test", "127.0.0.1", "/",          "200", true},
+            {"", "127.0.0.1|/dump/info;127.0.0.1|/dump/test", "127.0.0.1", "/dispatch",  "200", true},
+            {"", "127.0.0.1|/dump/info;127.0.0.1|/dump/test", "127.0.0.1", "/dump/info", "403", true},
+            {"", "127.0.0.1|/dump/info;127.0.0.1|/dump/test", "127.0.0.1", "/dump/test", "403", true},
+            {"", "127.0.0.1|/dump/info;127.0.0.1|/dump/test", "127.0.0.1", "/dump/fail", "200", true},
+
+            {"", "127.0.0.0-2|", "127.0.0.1", "/",          "403", true},
+            {"", "127.0.0.0-2|", "127.0.0.1", "/dump/info", "200", true},
+
+            {"", "127.0.0.0-2|/", "127.0.0.1", "/",          "403", true},
+            {"", "127.0.0.0-2|/", "127.0.0.1", "/dispatch",  "200", true},
+            {"", "127.0.0.0-2|/", "127.0.0.1", "/dump/info", "200", true},
+
+            {"", "127.0.0.0-2|/dump/*", "127.0.0.1", "/",          "200", true},
+            {"", "127.0.0.0-2|/dump/*", "127.0.0.1", "/dispatch",  "200", true},
+            {"", "127.0.0.0-2|/dump/*", "127.0.0.1", "/dump/info", "403", true},
+
+            {"", "127.0.0.0-2|/dump/info", "127.0.0.1", "/",          "200", true},
+            {"", "127.0.0.0-2|/dump/info", "127.0.0.1", "/dispatch",  "200", true},
+            {"", "127.0.0.0-2|/dump/info", "127.0.0.1", "/dump/info", "403", true},
+            {"", "127.0.0.0-2|/dump/info", "127.0.0.1", "/dump/test", "200", true},
+
+            {"", "127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "127.0.0.1", "/",          "200", true},
+            {"", "127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "127.0.0.1", "/dispatch",  "200", true},
+            {"", "127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "127.0.0.1", "/dump/info", "403", true},
+            {"", "127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "127.0.0.1", "/dump/test", "403", true},
+            {"", "127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "127.0.0.1", "/dump/fail", "200", true},
+
             // Both lists
-            {"127.0.0.1|/dump", "127.0.0.1|/dump/fail", "127.0.0.1", "/dump",      "200"},
-            {"127.0.0.1|/dump", "127.0.0.1|/dump/fail", "127.0.0.1", "/dump/info", "403"},
-            {"127.0.0.1|/dump", "127.0.0.1|/dump/fail", "127.0.0.1", "/dump/fail", "403"},
-
-            {"127.0.0.1|/dump/*", "127.0.0.1|/dump/fail", "127.0.0.1", "/dump",      "200"},
-            {"127.0.0.1|/dump/*", "127.0.0.1|/dump/fail", "127.0.0.1", "/dump/info", "200"},
-            {"127.0.0.1|/dump/*", "127.0.0.1|/dump/fail", "127.0.0.1", "/dump/fail", "403"},
-
-            {"127.0.0.1|/dump/*", "127.0.0.1|/dump/test;127.0.0.1|/dump/fail", "127.0.0.1", "/dump",      "200"},
-            {"127.0.0.1|/dump/*", "127.0.0.1|/dump/test;127.0.0.1|/dump/fail", "127.0.0.1", "/dump/info", "200"},
-            {"127.0.0.1|/dump/*", "127.0.0.1|/dump/test;127.0.0.1|/dump/fail", "127.0.0.1", "/dump/test", "403"},
-            {"127.0.0.1|/dump/*", "127.0.0.1|/dump/test;127.0.0.1|/dump/fail", "127.0.0.1", "/dump/fail", "403"},
-            
-            {"127.0.0.1|/dump/info;127.0.0.1|/dump/test", "127.0.0.1|/dump/test", "127.0.0.1", "/dump",      "403"},
-            {"127.0.0.1|/dump/info;127.0.0.1|/dump/test", "127.0.0.1|/dump/test", "127.0.0.1", "/dump/info", "200"},
-            {"127.0.0.1|/dump/info;127.0.0.1|/dump/test", "127.0.0.1|/dump/test", "127.0.0.1", "/dump/test", "403"},
-            {"127.0.0.1|/dump/info;127.0.0.1|/dump/test", "127.0.0.1|/dump/test", "127.0.0.1", "/dump/fail", "403"},
-
-            {"127.0.0.1|/;127.0.0.0-2|/dump/*", "127.0.0.0,1|/dump/fail", "127.0.0.1", "/",          "200"},
-            {"127.0.0.1|/;127.0.0.0-2|/dump/*", "127.0.0.0,1|/dump/fail", "127.0.0.1", "/dump/info", "200"},
-            {"127.0.0.1|/;127.0.0.0-2|/dump/*", "127.0.0.0,1|/dump/fail", "127.0.0.1", "/dump/fail", "403"},
+            {"127.0.0.1|/dump", "127.0.0.1|/dump/fail", "127.0.0.1", "/dump",      "200", true},
+            {"127.0.0.1|/dump", "127.0.0.1|/dump/fail", "127.0.0.1", "/dump/info", "200", true}, // _whiteListByPath
+            {"127.0.0.1|/dump", "127.0.0.1|/dump/fail", "127.0.0.1", "/dump/fail", "403", true},
+
+            {"127.0.0.1|/dump/*", "127.0.0.1|/dump/fail", "127.0.0.1", "/dump",      "200", true},
+            {"127.0.0.1|/dump/*", "127.0.0.1|/dump/fail", "127.0.0.1", "/dump/info", "200", true},
+            {"127.0.0.1|/dump/*", "127.0.0.1|/dump/fail", "127.0.0.1", "/dump/fail", "403", true},
+
+            {"127.0.0.1|/dump/*", "127.0.0.1|/dump/test;127.0.0.1|/dump/fail", "127.0.0.1", "/dump",      "200", true},
+            {"127.0.0.1|/dump/*", "127.0.0.1|/dump/test;127.0.0.1|/dump/fail", "127.0.0.1", "/dump/info", "200", true},
+            {"127.0.0.1|/dump/*", "127.0.0.1|/dump/test;127.0.0.1|/dump/fail", "127.0.0.1", "/dump/test", "403", true},
+            {"127.0.0.1|/dump/*", "127.0.0.1|/dump/test;127.0.0.1|/dump/fail", "127.0.0.1", "/dump/fail", "403", true},
+
+            {"127.0.0.1|/dump/info;127.0.0.1|/dump/test", "127.0.0.1|/dump/test", "127.0.0.1", "/dump",      "200", true}, // _whiteListByPath
+            {"127.0.0.1|/dump/info;127.0.0.1|/dump/test", "127.0.0.1|/dump/test", "127.0.0.1", "/dump/info", "200", true},
+            {"127.0.0.1|/dump/info;127.0.0.1|/dump/test", "127.0.0.1|/dump/test", "127.0.0.1", "/dump/test", "403", true},
+            {"127.0.0.1|/dump/info;127.0.0.1|/dump/test", "127.0.0.1|/dump/test", "127.0.0.1", "/dump/fail", "200", true}, // _whiteListByPath
+
+            {"127.0.0.1|/;127.0.0.0-2|/dump/*", "127.0.0.0,1|/dump/fail", "127.0.0.1", "/",          "200", true},
+            {"127.0.0.1|/;127.0.0.0-2|/dump/*", "127.0.0.0,1|/dump/fail", "127.0.0.1", "/dump/info", "200", true},
+            {"127.0.0.1|/;127.0.0.0-2|/dump/*", "127.0.0.0,1|/dump/fail", "127.0.0.1", "/dump/fail", "403", true},
 
             // Different address
-            {"127.0.0.2", "", "127.0.0.1", "/",          "403"},
-            {"127.0.0.2", "", "127.0.0.1", "/dump/info", "403"},
-
-            {"127.0.0.2|/dump/*", "", "127.0.0.1", "/",          "403"},
-            {"127.0.0.2|/dump/*", "", "127.0.0.1", "/dump/info", "403"},
-
-            {"127.0.0.2|/dump/info", "", "127.0.0.1", "/",          "403"},
-            {"127.0.0.2|/dump/info", "", "127.0.0.1", "/dump/info", "403"},
-            {"127.0.0.2|/dump/info", "", "127.0.0.1", "/dump/test", "403"},
-
-            {"127.0.0.1|/dump/info;127.0.0.2|/dump/test", "", "127.0.0.1", "/",          "403"},
-            {"127.0.0.1|/dump/info;127.0.0.2|/dump/test", "", "127.0.0.1", "/dispatch",  "403"},
-            {"127.0.0.1|/dump/info;127.0.0.2|/dump/test", "", "127.0.0.1", "/dump/info", "200"},
-            {"127.0.0.1|/dump/info;127.0.0.2|/dump/test", "", "127.0.0.1", "/dump/test", "403"},
-            {"127.0.0.1|/dump/info;127.0.0.2|/dump/test", "", "127.0.0.1", "/dump/fail", "403"},
-            
-            {"172.0.0.0-255", "", "127.0.0.1", "/",          "403"},
-            {"172.0.0.0-255", "", "127.0.0.1", "/dump/info", "403"},
-
-            {"172.0.0.0-255|/dump/*;127.0.0.0-255|/dump/*", "", "127.0.0.1", "/",          "403"},
-            {"172.0.0.0-255|/dump/*;127.0.0.0-255|/dump/*", "", "127.0.0.1", "/dispatch",  "403"},
-            {"172.0.0.0-255|/dump/*;127.0.0.0-255|/dump/*", "", "127.0.0.1", "/dump/info", "200"},
+            {"127.0.0.2", "", "127.0.0.1", "/",          "403", true},
+            {"127.0.0.2", "", "127.0.0.1", "/dump/info", "403", true},
+
+            {"127.0.0.2|/dump/*", "", "127.0.0.1", "/",          "200", true}, // _whiteListByPath
+            {"127.0.0.2|/dump/*", "", "127.0.0.1", "/dump/info", "403", true},
+
+            {"127.0.0.2|/dump/info", "", "127.0.0.1", "/",          "200", true}, // _whiteListByPath
+            {"127.0.0.2|/dump/info", "", "127.0.0.1", "/dump/info", "403", true},
+            {"127.0.0.2|/dump/info", "", "127.0.0.1", "/dump/test", "200", true}, // _whiteListByPath
+
+            {"127.0.0.1|/dump/info;127.0.0.2|/dump/test", "", "127.0.0.1", "/",          "200", true}, // _whiteListByPath
+            {"127.0.0.1|/dump/info;127.0.0.2|/dump/test", "", "127.0.0.1", "/dispatch",  "200", true}, // _whiteListByPath
+            {"127.0.0.1|/dump/info;127.0.0.2|/dump/test", "", "127.0.0.1", "/dump/info", "200", true},
+            {"127.0.0.1|/dump/info;127.0.0.2|/dump/test", "", "127.0.0.1", "/dump/test", "403", true},
+            {"127.0.0.1|/dump/info;127.0.0.2|/dump/test", "", "127.0.0.1", "/dump/fail", "200", true}, // _whiteListByPath
+
+            {"172.0.0.0-255", "", "127.0.0.1", "/",          "403", true},
+            {"172.0.0.0-255", "", "127.0.0.1", "/dump/info", "403", true},
+
+            {"172.0.0.0-255|/dump/*;127.0.0.0-255|/dump/*", "", "127.0.0.1", "/",          "200", true}, // _whiteListByPath
+            {"172.0.0.0-255|/dump/*;127.0.0.0-255|/dump/*", "", "127.0.0.1", "/dispatch",  "200", true}, // _whiteListByPath
+            {"172.0.0.0-255|/dump/*;127.0.0.0-255|/dump/*", "", "127.0.0.1", "/dump/info", "200", true},
         };
         return Arrays.asList(data);
     };
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/RequestLogHandlerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/RequestLogHandlerTest.java
index 3f15c16..b4a82f9 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/RequestLogHandlerTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/RequestLogHandlerTest.java
@@ -24,28 +24,29 @@ import static org.junit.Assert.*;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
+import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.net.HttpURLConnection;
 import java.net.URI;
 import java.util.ArrayList;
 import java.util.List;
 
+import javax.servlet.AsyncContext;
+import javax.servlet.AsyncEvent;
+import javax.servlet.AsyncListener;
+import javax.servlet.RequestDispatcher;
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import org.eclipse.jetty.continuation.Continuation;
-import org.eclipse.jetty.continuation.ContinuationListener;
-import org.eclipse.jetty.continuation.Servlet3Continuation;
 import org.eclipse.jetty.server.Connector;
 import org.eclipse.jetty.server.Handler;
 import org.eclipse.jetty.server.Request;
 import org.eclipse.jetty.server.RequestLog;
 import org.eclipse.jetty.server.Response;
 import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
+import org.eclipse.jetty.server.ServerConnector;
 import org.eclipse.jetty.toolchain.test.IO;
-import org.eclipse.jetty.util.StringUtil;
 import org.eclipse.jetty.util.component.AbstractLifeCycle;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
@@ -56,6 +57,11 @@ import org.junit.runners.Parameterized;
 import org.junit.runners.Parameterized.Parameter;
 import org.junit.runners.Parameterized.Parameters;
 
+/**
+ * Tests for RequestLogHandler behavior.
+ * <p>
+ * Tests different request handler behavior against different server+error configurations
+ */
 @RunWith(Parameterized.class)
 @Ignore
 public class RequestLogHandlerTest
@@ -64,14 +70,15 @@ public class RequestLogHandlerTest
 
     public static class CaptureLog extends AbstractLifeCycle implements RequestLog
     {
-        public List<String> captured = new ArrayList<String>();
+        public List<String> captured = new ArrayList<>();
 
+        @Override
         public void log(Request request, Response response)
         {
-            captured.add(String.format("%s %s %s %03d",request.getMethod(),request.getUri().toString(),request.getProtocol(),response.getStatus()));
+            captured.add(String.format("%s %s %s %03d",request.getMethod(),request.getRequestURI(),request.getProtocol(),response.getStatus()));
         }
     }
-    
+
     private static abstract class AbstractTestHandler extends AbstractHandler
     {
         @Override
@@ -83,6 +90,7 @@ public class RequestLogHandlerTest
 
     private static class HelloHandler extends AbstractTestHandler
     {
+        @Override
         public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
         {
             response.setContentType("text/plain");
@@ -90,132 +98,286 @@ public class RequestLogHandlerTest
             baseRequest.setHandled(true);
         }
     }
-    
+
     private static class ResponseSendErrorHandler extends AbstractTestHandler
     {
+        @Override
         public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
         {
-            response.sendError(500, "Whoops");
+            response.sendError(500,"Whoops");
             baseRequest.setHandled(true);
         }
     }
-    
+
     private static class ServletExceptionHandler extends AbstractTestHandler
     {
+        @Override
         public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
         {
             throw new ServletException("Whoops");
         }
     }
-    
+
     private static class IOExceptionHandler extends AbstractTestHandler
     {
+        @Override
         public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
         {
             throw new IOException("Whoops");
         }
     }
-    
+
     private static class RuntimeExceptionHandler extends AbstractTestHandler
     {
+        @Override
         public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
         {
             throw new RuntimeException("Whoops");
         }
     }
-    
-    private static class ContinuationOnTimeoutCompleteHandler extends AbstractTestHandler implements ContinuationListener
+
+    private static class AsyncOnTimeoutCompleteHandler extends AbstractTestHandler implements AsyncListener
     {
+        @Override
         public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
         {
-            Continuation ac = new Servlet3Continuation(request);
+            AsyncContext ac = request.startAsync();
             ac.setTimeout(1000);
-            ac.addContinuationListener(this);
+            ac.addListener(this);
             baseRequest.setHandled(true);
         }
 
-        public void onComplete(Continuation continuation)
+        @Override
+        public void onTimeout(AsyncEvent event) throws IOException
         {
+            event.getAsyncContext().complete();
         }
 
-        public void onTimeout(Continuation continuation)
+        @Override
+        public void onStartAsync(AsyncEvent event) throws IOException
+        {
+        }
+
+        @Override
+        public void onError(AsyncEvent event) throws IOException
+        {
+        }
+
+        @Override
+        public void onComplete(AsyncEvent event) throws IOException
         {
-            continuation.complete();
         }
     }
-    
-    private static class ContinuationOnTimeoutCompleteUnhandledHandler extends AbstractTestHandler implements ContinuationListener
+
+    private static class AsyncOnTimeoutCompleteUnhandledHandler extends AbstractTestHandler implements AsyncListener
     {
+        @Override
         public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
         {
-            Continuation ac = new Servlet3Continuation(request);
+            AsyncContext ac = request.startAsync();
             ac.setTimeout(1000);
-            ac.addContinuationListener(this);
+            ac.addListener(this);
         }
-        
-        public void onComplete(Continuation continuation)
+
+        @Override
+        public void onTimeout(AsyncEvent event) throws IOException
         {
+            event.getAsyncContext().complete();
         }
 
-        public void onTimeout(Continuation continuation)
+        @Override
+        public void onStartAsync(AsyncEvent event) throws IOException
+        {
+        }
+
+        @Override
+        public void onError(AsyncEvent event) throws IOException
+        {
+        }
+
+        @Override
+        public void onComplete(AsyncEvent event) throws IOException
         {
-            continuation.complete();
         }
     }
-    
-    private static class ContinuationOnTimeoutRuntimeExceptionHandler extends AbstractTestHandler implements ContinuationListener
+
+    private static class AsyncOnTimeoutDispatchHandler extends AbstractTestHandler implements AsyncListener
     {
+        @Override
         public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
         {
-            Continuation ac = new Servlet3Continuation(request);
+            if (request.getAttribute("deep") == null)
+            {
+                AsyncContext ac = request.startAsync();
+                ac.setTimeout(1000);
+                ac.addListener(this);
+                baseRequest.setHandled(true);
+                request.setAttribute("deep",true);
+            }
+        }
+
+        @Override
+        public void onTimeout(AsyncEvent event) throws IOException
+        {
+            event.getAsyncContext().dispatch();
+        }
+
+        @Override
+        public void onStartAsync(AsyncEvent event) throws IOException
+        {
+        }
+
+        @Override
+        public void onError(AsyncEvent event) throws IOException
+        {
+        }
+
+        @Override
+        public void onComplete(AsyncEvent event) throws IOException
+        {
+        }
+    }
+
+    private static class AsyncOnStartIOExceptionHandler extends AbstractTestHandler implements AsyncListener
+    {
+        @Override
+        public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+        {
+            AsyncContext ac = request.startAsync();
             ac.setTimeout(1000);
-            ac.addContinuationListener(this);
+            ac.addListener(this);
             baseRequest.setHandled(true);
         }
 
-        public void onComplete(Continuation continuation)
+        @Override
+        public void onTimeout(AsyncEvent event) throws IOException
+        {
+        }
+
+        @Override
+        public void onStartAsync(AsyncEvent event) throws IOException
         {
+            event.getAsyncContext().complete();
+            throw new IOException("Whoops");
         }
 
-        public void onTimeout(Continuation continuation)
+        @Override
+        public void onError(AsyncEvent event) throws IOException
+        {
+            LOG.warn("onError() -> {}",event);
+        }
+
+        @Override
+        public void onComplete(AsyncEvent event) throws IOException
         {
-            throw new RuntimeException("Ooops");
         }
     }
-    
-    @Parameters(name="{0}")
+
+    public static class OKErrorHandler extends ErrorHandler
+    {
+        @Override
+        public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
+        {
+            if (baseRequest.isHandled() || response.isCommitted())
+            {
+                return;
+            }
+
+            // collect error details
+            String reason = (response instanceof Response)?((Response)response).getReason():null;
+            int status = response.getStatus();
+
+            // intentionally set response status to OK (this is a test to see what is actually logged)
+            response.setStatus(200);
+            response.setContentType("text/plain");
+            PrintWriter out = response.getWriter();
+            out.printf("Error %d: %s%n",status,reason);
+            baseRequest.setHandled(true);
+        }
+    }
+
+    public static class DispatchErrorHandler extends ErrorHandler
+    {
+        @Override
+        public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
+        {
+            if (baseRequest.isHandled() || response.isCommitted())
+            {
+                return;
+            }
+
+            RequestDispatcher dispatcher = request.getRequestDispatcher("/errorok/");
+            assertThat("Dispatcher", dispatcher, notNullValue());
+
+            try
+            {
+                dispatcher.forward(request,response);
+            }
+            catch (ServletException e)
+            {
+                throw new IOException("Dispatch.forward failed",e);
+            }
+        }
+    }
+
+    public static class AltErrorHandler extends ErrorHandler
+    {
+        @Override
+        public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
+        {
+            if (baseRequest.isHandled() || response.isCommitted())
+            {
+                return;
+            }
+
+            // collect error details
+            String reason = (response instanceof Response)?((Response)response).getReason():null;
+            int status = response.getStatus();
+
+            // intentionally set response status to OK (this is a test to see what is actually logged)
+            response.setContentType("text/plain");
+            PrintWriter out = response.getWriter();
+            out.printf("Error %d: %s%n",status,reason);
+            baseRequest.setHandled(true);
+        }
+    }
+
+    @Parameters(name = "{0}")
     public static List<Object[]> data()
     {
-        List<Object[]> data = new ArrayList<Object[]>();
+        List<Object[]> data = new ArrayList<>();
 
-        data.add(new Object[] { new HelloHandler(), "/test", "GET /test HTTP/1.1 200" });
-        data.add(new Object[] { new ContinuationOnTimeoutCompleteHandler(), "/test", "GET /test HTTP/1.1 200" });
-        data.add(new Object[] { new ContinuationOnTimeoutCompleteUnhandledHandler(), "/test", "GET /test HTTP/1.1 200" });
-        
-        data.add(new Object[] { new ContinuationOnTimeoutRuntimeExceptionHandler(), "/test", "GET /test HTTP/1.1 500" });
-        data.add(new Object[] { new ResponseSendErrorHandler(), "/test", "GET /test HTTP/1.1 500" });
-        data.add(new Object[] { new ServletExceptionHandler(), "/test", "GET /test HTTP/1.1 500" });
-        data.add(new Object[] { new IOExceptionHandler(), "/test", "GET /test HTTP/1.1 500" });
-        data.add(new Object[] { new RuntimeExceptionHandler(), "/test", "GET /test HTTP/1.1 500" });
+        data.add(new Object[] { new HelloHandler(), "/test/", "GET /test/ HTTP/1.1 200" });
+        data.add(new Object[] { new AsyncOnTimeoutCompleteHandler(), "/test/", "GET /test/ HTTP/1.1 200" });
+        data.add(new Object[] { new AsyncOnTimeoutCompleteUnhandledHandler(), "/test/", "GET /test/ HTTP/1.1 200" });
+        data.add(new Object[] { new AsyncOnTimeoutDispatchHandler(), "/test/", "GET /test/ HTTP/1.1 200" });
+
+        data.add(new Object[] { new AsyncOnStartIOExceptionHandler(), "/test/", "GET /test/ HTTP/1.1 500" });
+        data.add(new Object[] { new ResponseSendErrorHandler(), "/test/", "GET /test/ HTTP/1.1 500" });
+        data.add(new Object[] { new ServletExceptionHandler(), "/test/", "GET /test/ HTTP/1.1 500" });
+        data.add(new Object[] { new IOExceptionHandler(), "/test/", "GET /test/ HTTP/1.1 500" });
+        data.add(new Object[] { new RuntimeExceptionHandler(), "/test/", "GET /test/ HTTP/1.1 500" });
 
         return data;
     }
 
     @Parameter(0)
     public Handler testHandler;
-    
+
     @Parameter(1)
     public String requestPath;
 
     @Parameter(2)
     public String expectedLogEntry;
 
-    @Test(timeout=4000)
+    /**
+     * Test a RequestLogHandler at the end of a HandlerCollection. all other configuration on server at defaults.
+     */
+    @Test(timeout = 4000)
     public void testLogHandlerCollection() throws Exception
     {
         Server server = new Server();
-        
-        SelectChannelConnector connector = new SelectChannelConnector();
+        ServerConnector connector = new ServerConnector(server);
         connector.setPort(0);
         server.setConnectors(new Connector[] { connector });
 
@@ -270,12 +432,297 @@ public class RequestLogHandlerTest
             server.stop();
         }
     }
+
+    /**
+     * Test a RequestLogHandler at the end of a HandlerCollection and also with the default ErrorHandler as server bean in place.
+     */
+    @Test(timeout = 4000)
+    public void testLogHandlerCollection_ErrorHandler_ServerBean() throws Exception
+    {
+        Server server = new Server();
+        ServerConnector connector = new ServerConnector(server);
+        connector.setPort(0);
+        server.setConnectors(new Connector[] { connector });
+
+        ErrorHandler errorHandler = new ErrorHandler();
+        server.addBean(errorHandler);
+
+        CaptureLog captureLog = new CaptureLog();
+
+        RequestLogHandler requestLog = new RequestLogHandler();
+        requestLog.setRequestLog(captureLog);
+
+        HandlerCollection handlers = new HandlerCollection();
+        handlers.setHandlers(new Handler[] { testHandler, requestLog });
+        server.setHandler(handlers);
+
+        try
+        {
+            server.start();
+
+            String host = connector.getHost();
+            if (host == null)
+            {
+                host = "localhost";
+            }
+            int port = connector.getLocalPort();
+
+            URI serverUri = new URI("http",null,host,port,requestPath,null,null);
+
+            // Make call to test handler
+            HttpURLConnection connection = (HttpURLConnection)serverUri.toURL().openConnection();
+            try
+            {
+                connection.setAllowUserInteraction(false);
+
+                // log response status code
+                int statusCode = connection.getResponseCode();
+                LOG.debug("Response Status Code: {}",statusCode);
+
+                if (statusCode == 200)
+                {
+                    // collect response message and log it
+                    String content = getResponseContent(connection);
+                    LOG.debug("Response Content: {}",content);
+                }
+            }
+            finally
+            {
+                connection.disconnect();
+            }
+
+            assertRequestLog(captureLog);
+        }
+        finally
+        {
+            server.stop();
+        }
+    }
+
+    /**
+     * Test a RequestLogHandler at the end of a HandlerCollection and also with the ErrorHandler in place.
+     */
+    @Test(timeout=4000)
+    public void testLogHandlerCollection_AltErrorHandler() throws Exception
+    {
+        Server server = new Server();
+        ServerConnector connector = new ServerConnector(server);
+        connector.setPort(0);
+        server.setConnectors(new Connector[] { connector });
+        
+        AltErrorHandler errorDispatcher = new AltErrorHandler();
+        server.addBean(errorDispatcher);
+        
+        ContextHandlerCollection contexts = new ContextHandlerCollection();
+        ContextHandler errorContext = new ContextHandler("/errorpage");
+        errorContext.setHandler(new AltErrorHandler());
+        ContextHandler testContext = new ContextHandler("/test");
+        testContext.setHandler(testHandler);
+        contexts.addHandler(errorContext);
+        contexts.addHandler(testContext);
+
+        RequestLogHandler requestLog = new RequestLogHandler();
+        CaptureLog captureLog = new CaptureLog();
+        requestLog.setRequestLog(captureLog);
+
+        HandlerCollection handlers = new HandlerCollection();
+        handlers.setHandlers(new Handler[] { contexts, requestLog });
+        server.setHandler(handlers);
+
+        try
+        {
+            server.start();
+
+            String host = connector.getHost();
+            if (host == null)
+            {
+                host = "localhost";
+            }
+            int port = connector.getLocalPort();
+
+            URI serverUri = new URI("http",null,host,port,requestPath,null,null);
+
+            // Make call to test handler
+            HttpURLConnection connection = (HttpURLConnection)serverUri.toURL().openConnection();
+            try
+            {
+                connection.setAllowUserInteraction(false);
+
+                // log response status code
+                int statusCode = connection.getResponseCode();
+                LOG.debug("Response Status Code: {}",statusCode);
+
+                if (statusCode == 200)
+                {
+                    // collect response message and log it
+                    String content = getResponseContent(connection);
+                    LOG.debug("Response Content: {}",content);
+                }
+            }
+            finally
+            {
+                connection.disconnect();
+            }
+
+            assertRequestLog(captureLog);
+        }
+        finally
+        {
+            server.stop();
+        }
+    }
+    
+    /**
+     * Test a RequestLogHandler at the end of a HandlerCollection and also with the ErrorHandler in place.
+     */
+    @Test(timeout=4000)
+    public void testLogHandlerCollection_OKErrorHandler() throws Exception
+    {
+        Server server = new Server();
+        ServerConnector connector = new ServerConnector(server);
+        connector.setPort(0);
+        server.setConnectors(new Connector[] { connector });
+        
+        OKErrorHandler errorDispatcher = new OKErrorHandler();
+        server.addBean(errorDispatcher);
+        
+        ContextHandlerCollection contexts = new ContextHandlerCollection();
+        ContextHandler errorContext = new ContextHandler("/errorpage");
+        errorContext.setHandler(new AltErrorHandler());
+        ContextHandler testContext = new ContextHandler("/test");
+        testContext.setHandler(testHandler);
+        contexts.addHandler(errorContext);
+        contexts.addHandler(testContext);
+
+        RequestLogHandler requestLog = new RequestLogHandler();
+        CaptureLog captureLog = new CaptureLog();
+        requestLog.setRequestLog(captureLog);
+
+        HandlerCollection handlers = new HandlerCollection();
+        handlers.setHandlers(new Handler[] { contexts, requestLog });
+        server.setHandler(handlers);
+
+        try
+        {
+            server.start();
+
+            String host = connector.getHost();
+            if (host == null)
+            {
+                host = "localhost";
+            }
+            int port = connector.getLocalPort();
+
+            URI serverUri = new URI("http",null,host,port,requestPath,null,null);
+
+            // Make call to test handler
+            HttpURLConnection connection = (HttpURLConnection)serverUri.toURL().openConnection();
+            try
+            {
+                connection.setAllowUserInteraction(false);
+
+                // log response status code
+                int statusCode = connection.getResponseCode();
+                LOG.debug("Response Status Code: {}",statusCode);
+
+                if (statusCode == 200)
+                {
+                    // collect response message and log it
+                    String content = getResponseContent(connection);
+                    LOG.debug("Response Content: {}",content);
+                }
+            }
+            finally
+            {
+                connection.disconnect();
+            }
+
+            assertRequestLog(captureLog);
+        }
+        finally
+        {
+            server.stop();
+        }
+    }
     
+    /**
+     * Test a RequestLogHandler at the end of a HandlerCollection and also with the ErrorHandler in place.
+     */
     @Test(timeout=4000)
+    public void testLogHandlerCollection_DispatchErrorHandler() throws Exception
+    {
+        Server server = new Server();
+        ServerConnector connector = new ServerConnector(server);
+        connector.setPort(0);
+        server.setConnectors(new Connector[] { connector });
+        
+        DispatchErrorHandler errorDispatcher = new DispatchErrorHandler();
+        server.addBean(errorDispatcher);
+        
+        ContextHandlerCollection contexts = new ContextHandlerCollection();
+        ContextHandler errorContext = new ContextHandler("errorok");
+        errorContext.setHandler(new OKErrorHandler());
+        ContextHandler testContext = new ContextHandler("test");
+        testContext.setHandler(testHandler);
+        contexts.addHandler(errorContext);
+        contexts.addHandler(testContext);
+
+        RequestLogHandler requestLog = new RequestLogHandler();
+        CaptureLog captureLog = new CaptureLog();
+        requestLog.setRequestLog(captureLog);
+
+        HandlerCollection handlers = new HandlerCollection();
+        handlers.setHandlers(new Handler[] { contexts, requestLog });
+        server.setHandler(handlers);
+
+        try
+        {
+            server.start();
+
+            String host = connector.getHost();
+            if (host == null)
+            {
+                host = "localhost";
+            }
+            int port = connector.getLocalPort();
+
+            URI serverUri = new URI("http",null,host,port,requestPath,null,null);
+
+            // Make call to test handler
+            HttpURLConnection connection = (HttpURLConnection)serverUri.toURL().openConnection();
+            try
+            {
+                connection.setAllowUserInteraction(false);
+
+                // log response status code
+                int statusCode = connection.getResponseCode();
+                LOG.debug("Response Status Code: {}",statusCode);
+
+                if (statusCode == 200)
+                {
+                    // collect response message and log it
+                    String content = getResponseContent(connection);
+                    LOG.debug("Response Content: {}",content);
+                }
+            }
+            finally
+            {
+                connection.disconnect();
+            }
+
+            assertRequestLog(captureLog);
+        }
+        finally
+        {
+            server.stop();
+        }
+    }
+
+    @Test(timeout = 4000)
     public void testLogHandlerWrapped() throws Exception
     {
         Server server = new Server();
-        SelectChannelConnector connector = new SelectChannelConnector();
+        ServerConnector connector = new ServerConnector(server);
         connector.setPort(0);
         server.setConnectors(new Connector[] { connector });
 
@@ -283,7 +730,7 @@ public class RequestLogHandlerTest
 
         RequestLogHandler requestLog = new RequestLogHandler();
         requestLog.setRequestLog(captureLog);
-        
+
         requestLog.setHandler(testHandler);
 
         server.setHandler(requestLog);
@@ -299,7 +746,7 @@ public class RequestLogHandlerTest
             }
             int port = connector.getLocalPort();
 
-            URI serverUri = new URI("http",null,host,port,"/test",null,null);
+            URI serverUri = new URI("http",null,host,port,requestPath,null,null);
 
             // Make call to test handler
             HttpURLConnection connection = (HttpURLConnection)serverUri.toURL().openConnection();
@@ -354,21 +801,11 @@ public class RequestLogHandlerTest
 
     private String getResponseContent(HttpURLConnection connection) throws IOException
     {
-        InputStream in = null;
-        InputStreamReader reader = null;
-        
-        try
+        try (InputStream in = connection.getInputStream(); InputStreamReader reader = new InputStreamReader(in))
         {
-            in = connection.getInputStream();
-            reader = new InputStreamReader(in,StringUtil.__UTF8_CHARSET);
             StringWriter writer = new StringWriter();
             IO.copy(reader,writer);
             return writer.toString();
         }
-        finally
-        {
-            IO.close(reader);
-            IO.close(in);
-        }
     }
-}
\ No newline at end of file
+}
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ResourceHandlerRangeTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ResourceHandlerRangeTest.java
new file mode 100644
index 0000000..7c0539a
--- /dev/null
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ResourceHandlerRangeTest.java
@@ -0,0 +1,112 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.handler;
+
+import static org.hamcrest.Matchers.is;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.URI;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.toolchain.test.FS;
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.resource.Resource;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Ignore;
+import org.junit.Test;
+
+ at Ignore("Unfixed range bug")
+public class ResourceHandlerRangeTest
+{
+    private static Server server;
+    private static URI serverUri;
+
+    @BeforeClass
+    public static void startServer() throws Exception
+    {
+        server = new Server();
+        ServerConnector connector = new ServerConnector(server);
+        connector.setPort(0);
+        server.addConnector(connector);
+
+        ContextHandlerCollection contexts = new ContextHandlerCollection();
+
+        File dir = MavenTestingUtils.getTargetTestingDir(ResourceHandlerRangeTest.class.getSimpleName());
+        FS.ensureEmpty(dir);
+        File rangeFile = new File(dir,"range.txt");
+        try (FileWriter writer = new FileWriter(rangeFile))
+        {
+            writer.append("0123456789");
+            writer.flush();
+        }
+
+        ContextHandler contextHandler = new ContextHandler();
+        ResourceHandler contentResourceHandler = new ResourceHandler();
+        contextHandler.setBaseResource(Resource.newResource(dir.getAbsolutePath()));
+        contextHandler.setHandler(contentResourceHandler);
+        contextHandler.setContextPath("/");
+
+        contexts.addHandler(contextHandler);
+
+        server.setHandler(contexts);
+        server.start();
+
+        String host = connector.getHost();
+        if (host == null)
+        {
+            host = "localhost";
+        }
+        int port = connector.getLocalPort();
+        serverUri = new URI(String.format("http://%s:%d/",host,port));
+    }
+
+    @AfterClass
+    public static void stopServer() throws Exception
+    {
+        server.stop();
+    }
+
+    @Test
+    public void testGetRange() throws Exception
+    {
+        URI uri = serverUri.resolve("range.txt");
+
+        HttpURLConnection uconn = (HttpURLConnection)uri.toURL().openConnection();
+        uconn.setRequestMethod("GET");
+        uconn.addRequestProperty("Range","bytes=" + 5 + "-");
+
+        int contentLength = Integer.parseInt(uconn.getHeaderField("Content-Length"));
+
+        String response;
+        try (InputStream is = uconn.getInputStream())
+        {
+            response = IO.toString(is);
+        }
+
+        Assert.assertThat("Content Length",contentLength,is(5));
+        Assert.assertThat("Response Content",response,is("56789"));
+    }
+}
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ResourceHandlerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ResourceHandlerTest.java
index 83cac35..7101ad4 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ResourceHandlerTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ResourceHandlerTest.java
@@ -18,17 +18,32 @@
 
 package org.eclipse.jetty.server.handler;
 
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.Socket;
 import java.net.URI;
-
-import junit.framework.Assert;
-import junit.framework.TestCase;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
 
 import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.HttpConnectionFactory;
 import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.bio.SocketConnector;
+import org.eclipse.jetty.server.ServerConnector;
 import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
 import org.eclipse.jetty.toolchain.test.SimpleRequest;
+import org.eclipse.jetty.toolchain.test.annotation.Slow;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.IO;
+import org.hamcrest.Matchers;
 import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
@@ -37,46 +52,192 @@ import org.junit.Test;
  * 
  * TODO: increase the testing going on here
  */
-public class ResourceHandlerTest extends TestCase
+public class ResourceHandlerTest
 {
+    private static String LN = System.getProperty("line.separator");
     private static Server _server;
-    private static Connector _connector;
+    private static HttpConfiguration _config;
+    private static ServerConnector _connector;
     private static ContextHandler _contextHandler;
     private static ResourceHandler _resourceHandler;
- 
-    
+
     @BeforeClass
-    public void setUp() throws Exception
+    public static void setUp() throws Exception
     {
+        File dir = MavenTestingUtils.getTargetFile("test-classes/simple");
+        File bigger = new File(dir,"bigger.txt");
+        File big = new File(dir,"big.txt");
+        try (OutputStream out = new FileOutputStream(bigger))
+        {
+            for (int i = 0; i < 100; i++)
+            {
+                try (InputStream in = new FileInputStream(big))
+                {
+                    IO.copy(in,out);
+                }
+            }
+        }
+        
+        bigger.deleteOnExit();
+
+        // determine how the SCM of choice checked out the big.txt EOL
+        // we can't just use whatever is the OS default.
+        // because, for example, a windows system using git can be configured for EOL handling using
+        // local, remote, file lists, patterns, etc, rendering assumptions about the OS EOL choice
+        // wrong for unit tests.
+        LN = System.getProperty("line.separator");
+        try (BufferedReader reader = Files.newBufferedReader(big.toPath(),StandardCharsets.UTF_8))
+        {
+            // a buffer large enough to capture at least 1 EOL
+            char cbuf[] = new char[128];
+            reader.read(cbuf);
+            String sample = new String(cbuf);
+            if (sample.contains("\r\n"))
+            {
+                LN = "\r\n";
+            }
+            else if (sample.contains("\n\r"))
+            {
+                LN = "\n\r";
+            }
+            else
+            {
+                LN = "\n";
+            }
+        }
+
         _server = new Server();
-        _connector = new SocketConnector();
+        _config = new HttpConfiguration();
+        _config.setOutputBufferSize(2048);
+        _connector = new ServerConnector(_server,new HttpConnectionFactory(_config));
+
         _server.setConnectors(new Connector[] { _connector });
 
         _resourceHandler = new ResourceHandler();
+        _resourceHandler.setMinAsyncContentLength(4096);
+        _resourceHandler.setMinMemoryMappedContentLength(8192);
+
+        _resourceHandler.setResourceBase(MavenTestingUtils.getTargetFile("test-classes/simple").getAbsolutePath());
 
         _contextHandler = new ContextHandler("/resource");
         _contextHandler.setHandler(_resourceHandler);
         _server.setHandler(_contextHandler);
         _server.start();
     }
-    
-    /* ------------------------------------------------------------ */
+
     @AfterClass
-    public void tearDown() throws Exception
+    public static void tearDown() throws Exception
     {
         _server.stop();
     }
-    
+
+    @Before
+    public void before()
+    {
+        _config.setOutputBufferSize(4096);
+    }
+
     @Test
-    public void testSimpleResourceHandler() throws Exception
+    public void testMissing() throws Exception
+    {
+        SimpleRequest sr = new SimpleRequest(new URI("http://localhost:" + _connector.getLocalPort()));
+        Assert.assertNotNull("missing jetty.css",sr.getString("/resource/jetty-dir.css"));
+    }
+
+    @Test
+    public void testSimple() throws Exception
     {
-        _resourceHandler.setResourceBase(MavenTestingUtils.getTestResourceDir("simple").getAbsolutePath());
-        
         SimpleRequest sr = new SimpleRequest(new URI("http://localhost:" + _connector.getLocalPort()));
+        Assert.assertEquals("simple text",sr.getString("/resource/simple.txt"));
+    }
+
+    @Test
+    public void testBigFile() throws Exception
+    {
+        _config.setOutputBufferSize(2048);
+        SimpleRequest sr = new SimpleRequest(new URI("http://localhost:" + _connector.getLocalPort()));
+        String response = sr.getString("/resource/big.txt");
+        Assert.assertThat(response,Matchers.startsWith("     1\tThis is a big file"));
+        Assert.assertThat(response,Matchers.endsWith("   400\tThis is a big file" + LN));
+    }
+
+    @Test
+    public void testBigFileBigBuffer() throws Exception
+    {
+        _config.setOutputBufferSize(16 * 1024);
+        SimpleRequest sr = new SimpleRequest(new URI("http://localhost:" + _connector.getLocalPort()));
+        String response = sr.getString("/resource/big.txt");
+        Assert.assertThat(response,Matchers.startsWith("     1\tThis is a big file"));
+        Assert.assertThat(response,Matchers.endsWith("   400\tThis is a big file" + LN));
+    }
+
+    @Test
+    public void testBigFileLittleBuffer() throws Exception
+    {
+        _config.setOutputBufferSize(8);
+        SimpleRequest sr = new SimpleRequest(new URI("http://localhost:" + _connector.getLocalPort()));
+        String response = sr.getString("/resource/big.txt");
+        Assert.assertThat(response,Matchers.startsWith("     1\tThis is a big file"));
+        Assert.assertThat(response,Matchers.endsWith("   400\tThis is a big file" + LN));
+    }
+
+    @Test
+    public void testBigger() throws Exception
+    {
+        try (Socket socket = new Socket("localhost",_connector.getLocalPort());)
+        {
+            socket.getOutputStream().write("GET /resource/bigger.txt HTTP/1.0\n\n".getBytes());
+            Thread.sleep(1000);
+            String response = IO.toString(socket.getInputStream());
+            Assert.assertThat(response,Matchers.startsWith("HTTP/1.1 200 OK"));
+            Assert.assertThat(response,Matchers.containsString("   400\tThis is a big file" + LN + "     1\tThis is a big file"));
+            Assert.assertThat(response,Matchers.endsWith("   400\tThis is a big file" + LN));
+        }
+    }
+    
+    @Test
+    @Slow
+    public void testSlowBiggest() throws Exception
+    {
+        _connector.setIdleTimeout(10000);
         
-        Assert.assertEquals("simple text", sr.getString("/resource/simple.txt"));
+        File dir = MavenTestingUtils.getTargetFile("test-classes/simple");
+        File biggest = new File(dir,"biggest.txt");
+        try (OutputStream out = new FileOutputStream(biggest))
+        {
+            for (int i = 0; i < 10; i++)
+            {
+                try (InputStream in = new FileInputStream(new File(dir,"bigger.txt")))
+                {
+                    IO.copy(in,out);
+                }
+            }
+            out.write("\nTHE END\n".getBytes(StandardCharsets.ISO_8859_1));
+        }
+        biggest.deleteOnExit();
         
-        Assert.assertNotNull("missing jetty.css" , sr.getString("/resource/jetty-dir.css"));     
+        try (Socket socket = new Socket("localhost",_connector.getLocalPort());OutputStream out=socket.getOutputStream();InputStream in=socket.getInputStream())
+        {
+
+            socket.getOutputStream().write("GET /resource/biggest.txt HTTP/1.0\n\n".getBytes());
+            
+            byte[] array = new byte[102400];
+            ByteBuffer buffer=null;
+            int i=0;
+            while(true)
+            {
+                Thread.sleep(100);
+                int len=in.read(array);
+                if (len<0)
+                    break;
+                buffer=BufferUtil.toBuffer(array,0,len);
+                // System.err.println(++i+": "+BufferUtil.toDetailString(buffer));
+            }
+
+            Assert.assertEquals('E',buffer.get(buffer.limit()-4));
+            Assert.assertEquals('N',buffer.get(buffer.limit()-3));
+            Assert.assertEquals('D',buffer.get(buffer.limit()-2));
+            
+        }
     }
-    
 }
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ScopedHandlerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ScopedHandlerTest.java
index 56395d3..ef44950 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ScopedHandlerTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ScopedHandlerTest.java
@@ -48,7 +48,6 @@ public class ScopedHandlerTest
         handler0.handle("target",null,null,null);
         handler0.stop();
         String history=_history.toString();
-        System.err.println(history);
         assertEquals(">S0>W0<W0<S0",history);
     }
 
@@ -62,7 +61,6 @@ public class ScopedHandlerTest
         handler0.handle("target",null,null,null);
         handler0.stop();
         String history=_history.toString();
-        System.err.println(history);
         assertEquals(">S0>S1>W0>W1<W1<W0<S1<S0",history);
     }
 
@@ -78,7 +76,6 @@ public class ScopedHandlerTest
         handler0.handle("target",null,null,null);
         handler0.stop();
         String history=_history.toString();
-        System.err.println(history);
         assertEquals(">S0>S1>S2>W0>W1>W2<W2<W1<W0<S2<S1<S0",history);
     }
 
@@ -96,7 +93,6 @@ public class ScopedHandlerTest
         handler0.handle("target",null,null,null);
         handler0.stop();
         String history=_history.toString();
-        System.err.println(history);
         assertEquals(">S0>S1>W0>HA>W1>HB<HB<W1<HA<W0<S1<S0",history);
     }
 
@@ -118,7 +114,6 @@ public class ScopedHandlerTest
         handler0.handle("target",null,null,null);
         handler0.stop();
         String history=_history.toString();
-        System.err.println(history);
         assertEquals(">S0>S1>S2>W0>HA>W1>HB>W2>HC<HC<W2<HB<W1<HA<W0<S2<S1<S0",history);
     }
 
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/SecuredRedirectHandlerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/SecuredRedirectHandlerTest.java
new file mode 100644
index 0000000..0ce700a
--- /dev/null
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/SecuredRedirectHandlerTest.java
@@ -0,0 +1,288 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.handler;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.net.HttpURLConnection;
+import java.net.URI;
+import java.net.URL;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLSocketFactory;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.HttpConnectionFactory;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.SecureRequestCustomizer;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.SslConnectionFactory;
+import org.eclipse.jetty.toolchain.test.IO;
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class SecuredRedirectHandlerTest
+{
+    private static Server server;
+    private static HostnameVerifier origVerifier;
+    private static SSLSocketFactory origSsf;
+    private static URI serverHttpUri;
+    private static URI serverHttpsUri;
+
+    @BeforeClass
+    public static void startServer() throws Exception
+    {
+        // Setup SSL
+        File keystore = MavenTestingUtils.getTestResourceFile("keystore");
+        SslContextFactory sslContextFactory = new SslContextFactory();
+        sslContextFactory.setKeyStorePath(keystore.getAbsolutePath());
+        sslContextFactory.setKeyStorePassword("storepwd");
+        sslContextFactory.setKeyManagerPassword("keypwd");
+        sslContextFactory.setTrustStorePath(keystore.getAbsolutePath());
+        sslContextFactory.setTrustStorePassword("storepwd");
+
+        server = new Server();
+
+        int port = 32080;
+        int securePort = 32443;
+
+        // Setup HTTP Configuration
+        HttpConfiguration httpConf = new HttpConfiguration();
+        httpConf.setSecurePort(securePort);
+        httpConf.setSecureScheme("https");
+
+        ServerConnector httpConnector = new ServerConnector(server,new HttpConnectionFactory(httpConf));
+        httpConnector.setName("unsecured");
+        httpConnector.setPort(port);
+
+        // Setup HTTPS Configuration
+        HttpConfiguration httpsConf = new HttpConfiguration(httpConf);
+        httpsConf.addCustomizer(new SecureRequestCustomizer());
+
+        ServerConnector httpsConnector = new ServerConnector(server,new SslConnectionFactory(sslContextFactory,"http/1.1"),new HttpConnectionFactory(httpsConf));
+        httpsConnector.setName("secured");
+        httpsConnector.setPort(securePort);
+
+        // Add connectors
+        server.setConnectors(new Connector[] { httpConnector, httpsConnector });
+
+        // Wire up contexts
+        String secureHosts[] = new String[] { "@secured" };
+
+        ContextHandler test1Context = new ContextHandler();
+        test1Context.setContextPath("/test1");
+        test1Context.setHandler(new HelloHandler("Hello1"));
+        test1Context.setVirtualHosts(secureHosts);
+
+        ContextHandler test2Context = new ContextHandler();
+        test2Context.setContextPath("/test2");
+        test2Context.setHandler(new HelloHandler("Hello2"));
+        test2Context.setVirtualHosts(secureHosts);
+
+        ContextHandler rootContext = new ContextHandler();
+        rootContext.setContextPath("/");
+        rootContext.setHandler(new RootHandler("/test1","/test2"));
+        rootContext.setVirtualHosts(secureHosts);
+
+        // Wire up context for unsecure handling to only
+        // the named 'unsecured' connector
+        ContextHandler redirectHandler = new ContextHandler();
+        redirectHandler.setContextPath("/");
+        redirectHandler.setHandler(new SecuredRedirectHandler());
+        redirectHandler.setVirtualHosts(new String[] { "@unsecured" });
+
+        // Establish all handlers that have a context
+        ContextHandlerCollection contextHandlers = new ContextHandlerCollection();
+        contextHandlers.setHandlers(new Handler[] { redirectHandler, rootContext, test1Context, test2Context });
+
+        // Create server level handler tree
+        HandlerList handlers = new HandlerList();
+        handlers.addHandler(contextHandlers);
+        handlers.addHandler(new DefaultHandler()); // round things out
+
+        server.setHandler(handlers);
+
+        server.start();
+
+        // calculate serverUri
+        String host = httpConnector.getHost();
+        if (host == null)
+        {
+            host = "localhost";
+        }
+        serverHttpUri = new URI(String.format("http://%s:%d/",host,httpConnector.getLocalPort()));
+        serverHttpsUri = new URI(String.format("https://%s:%d/",host,httpsConnector.getLocalPort()));
+
+        origVerifier = HttpsURLConnection.getDefaultHostnameVerifier();
+        origSsf = HttpsURLConnection.getDefaultSSLSocketFactory();
+
+        HttpsURLConnection.setDefaultHostnameVerifier(new AllowAllVerifier());
+        HttpsURLConnection.setDefaultSSLSocketFactory(sslContextFactory.getSslContext().getSocketFactory());
+    }
+
+    @AfterClass
+    public static void stopServer() throws Exception
+    {
+        HttpsURLConnection.setDefaultSSLSocketFactory(origSsf);
+        HttpsURLConnection.setDefaultHostnameVerifier(origVerifier);
+
+        server.stop();
+        server.join();
+    }
+
+    @Test
+    public void testRedirectUnsecuredRoot() throws Exception
+    {
+        URL url = serverHttpUri.resolve("/").toURL();
+        HttpURLConnection connection = (HttpURLConnection)url.openConnection();
+        connection.setInstanceFollowRedirects(false);
+        connection.setAllowUserInteraction(false);
+        assertThat("response code",connection.getResponseCode(),is(302));
+        assertThat("location header",connection.getHeaderField("Location"),is(serverHttpsUri.resolve("/").toASCIIString()));
+        connection.disconnect();
+    }
+
+    @Test
+    public void testRedirectSecuredRoot() throws Exception
+    {
+        URL url = serverHttpsUri.resolve("/").toURL();
+        HttpURLConnection connection = (HttpURLConnection)url.openConnection();
+        connection.setInstanceFollowRedirects(false);
+        connection.setAllowUserInteraction(false);
+        assertThat("response code",connection.getResponseCode(),is(200));
+        String content = getContent(connection);
+        assertThat("response content",content,containsString("<a href=\"/test1\">"));
+        connection.disconnect();
+    }
+
+    @Test
+    public void testAccessUnsecuredHandler() throws Exception
+    {
+        URL url = serverHttpUri.resolve("/test1").toURL();
+        HttpURLConnection connection = (HttpURLConnection)url.openConnection();
+        connection.setInstanceFollowRedirects(false);
+        connection.setAllowUserInteraction(false);
+        assertThat("response code",connection.getResponseCode(),is(302));
+        assertThat("location header",connection.getHeaderField("Location"),is(serverHttpsUri.resolve("/test1").toASCIIString()));
+        connection.disconnect();
+    }
+
+    @Test
+    public void testAccessUnsecured404() throws Exception
+    {
+        URL url = serverHttpUri.resolve("/nothing/here").toURL();
+        HttpURLConnection connection = (HttpURLConnection)url.openConnection();
+        connection.setInstanceFollowRedirects(false);
+        connection.setAllowUserInteraction(false);
+        assertThat("response code",connection.getResponseCode(),is(302));
+        assertThat("location header",connection.getHeaderField("Location"),is(serverHttpsUri.resolve("/nothing/here").toASCIIString()));
+        connection.disconnect();
+    }
+
+    @Test
+    public void testAccessSecured404() throws Exception
+    {
+        URL url = serverHttpsUri.resolve("/nothing/here").toURL();
+        HttpURLConnection connection = (HttpURLConnection)url.openConnection();
+        connection.setInstanceFollowRedirects(false);
+        connection.setAllowUserInteraction(false);
+        assertThat("response code",connection.getResponseCode(),is(404));
+        connection.disconnect();
+    }
+
+    private String getContent(HttpURLConnection connection) throws IOException
+    {
+        try (InputStream in = connection.getInputStream(); InputStreamReader reader = new InputStreamReader(in))
+        {
+            StringWriter writer = new StringWriter();
+            IO.copy(reader,writer);
+            return writer.toString();
+        }
+    }
+
+    public static class HelloHandler extends AbstractHandler
+    {
+        private final String msg;
+
+        public HelloHandler(String msg)
+        {
+            this.msg = msg;
+        }
+
+        @Override
+        public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+        {
+            response.setContentType("text/plain");
+            response.getWriter().printf("%s%n",msg);
+            baseRequest.setHandled(true);
+        }
+    }
+
+    public static class RootHandler extends AbstractHandler
+    {
+        private final String[] childContexts;
+
+        public RootHandler(String... children)
+        {
+            this.childContexts = children;
+        }
+
+        @Override
+        public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+        {
+            if (!"/".equals(target))
+            {
+                response.sendError(404);
+                return;
+            }
+
+            response.setContentType("text/html");
+            PrintWriter out = response.getWriter();
+            out.println("<html>");
+            out.println("<head><title>Contexts</title></head>");
+            out.println("<body>");
+            out.println("<h4>Child Contexts</h4>");
+            out.println("<ul>");
+            for (String child : childContexts)
+            {
+                out.printf("<li><a href=\"%s\">%s</a></li>%n",child,child);
+            }
+            out.println("</ul>");
+            out.println("</body></html>");
+            baseRequest.setHandled(true);
+        }
+    }
+}
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ShutdownHandlerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ShutdownHandlerTest.java
index a37aed9..06a1ee2 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ShutdownHandlerTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ShutdownHandlerTest.java
@@ -22,12 +22,15 @@ import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.when;
 
+import java.net.Inet4Address;
+import java.net.InetSocketAddress;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
+import org.eclipse.jetty.server.Request;
 import org.eclipse.jetty.server.Server;
 import org.eclipse.jetty.util.component.AbstractLifeCycle;
 import org.eclipse.jetty.util.component.LifeCycle;
@@ -38,6 +41,7 @@ import org.mockito.MockitoAnnotations;
 
 public class ShutdownHandlerTest
 {
+    @Mock private Request baseRequest;
     @Mock private HttpServletRequest request;
     @Mock private HttpServletResponse response;
 
@@ -51,8 +55,9 @@ public class ShutdownHandlerTest
     public void startServer() throws Exception
     {
         MockitoAnnotations.initMocks(this);
+        shutdownHandler = new ShutdownHandler(shutdownToken);
+        server.setHandler(shutdownHandler);
         server.start();
-        shutdownHandler = new ShutdownHandler(server,shutdownToken);
     }
 
     @Test
@@ -60,7 +65,7 @@ public class ShutdownHandlerTest
     {
         setDefaultExpectations();
         final CountDownLatch countDown = new CountDownLatch(1);
-        server.addLifeCycleListener(new AbstractLifeCycle.Listener () 
+        server.addLifeCycleListener(new AbstractLifeCycle.Listener ()
         {
 
             public void lifeCycleStarting(LifeCycle event)
@@ -72,23 +77,24 @@ public class ShutdownHandlerTest
             }
 
             public void lifeCycleFailure(LifeCycle event, Throwable cause)
-            {  
+            {
             }
 
             public void lifeCycleStopping(LifeCycle event)
-            {  
+            {
             }
 
             public void lifeCycleStopped(LifeCycle event)
             {
                 countDown.countDown();
             }
-            
+
         });
-        shutdownHandler.handle("/shutdown",null,request,response);
+        when(baseRequest.getRemoteInetSocketAddress()).thenReturn(new InetSocketAddress(Inet4Address.getLoopbackAddress(),45454));
+        shutdownHandler.handle("/shutdown",baseRequest,request,response);
         boolean stopped = countDown.await(1000, TimeUnit.MILLISECONDS); //wait up to 1 sec to stop
         assertTrue("Server lifecycle stop listener called", stopped);
-        assertEquals("Server should be stopped","STOPPED",server.getState());  
+        assertEquals("Server should be stopped","STOPPED",server.getState());
     }
 
     @Test
@@ -96,7 +102,8 @@ public class ShutdownHandlerTest
     {
         setDefaultExpectations();
         when(request.getParameter("token")).thenReturn("anothertoken");
-        shutdownHandler.handle("/shutdown",null,request,response);
+        when(baseRequest.getRemoteInetSocketAddress()).thenReturn(new InetSocketAddress(Inet4Address.getLoopbackAddress(),45454));
+        shutdownHandler.handle("/shutdown",baseRequest,request,response);
         assertEquals("Server should be running","STARTED",server.getState());
     }
 
@@ -105,7 +112,8 @@ public class ShutdownHandlerTest
      {
          setDefaultExpectations();
          when(request.getRemoteAddr()).thenReturn("192.168.3.3");
-         shutdownHandler.handle("/shutdown",null,request,response);
+         when(baseRequest.getRemoteInetSocketAddress()).thenReturn(new InetSocketAddress(Inet4Address.getByName("192.168.3.3"),45454));
+         shutdownHandler.handle("/shutdown",baseRequest,request,response);
          assertEquals("Server should be running","STARTED",server.getState());
      }
 
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/StatisticsHandlerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/StatisticsHandlerTest.java
index c9b7f3a..91a8768 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/StatisticsHandlerTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/StatisticsHandlerTest.java
@@ -18,10 +18,11 @@
 
 package org.eclipse.jetty.server.handler;
 
+import static org.hamcrest.Matchers.greaterThanOrEqualTo;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
 
 import java.io.IOException;
 import java.util.concurrent.CountDownLatch;
@@ -29,13 +30,14 @@ import java.util.concurrent.CyclicBarrier;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
 
+import javax.servlet.AsyncContext;
+import javax.servlet.AsyncEvent;
+import javax.servlet.AsyncListener;
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import org.eclipse.jetty.continuation.Continuation;
-import org.eclipse.jetty.continuation.ContinuationListener;
-import org.eclipse.jetty.continuation.ContinuationSupport;
+import org.eclipse.jetty.server.ConnectorStatistics;
 import org.eclipse.jetty.server.LocalConnector;
 import org.eclipse.jetty.server.Request;
 import org.eclipse.jetty.server.Server;
@@ -46,6 +48,7 @@ import org.junit.Test;
 public class StatisticsHandlerTest
 {
     private Server _server;
+    private ConnectorStatistics _statistics;
     private LocalConnector _connector;
     private LatchHandler _latchHandler;
     private StatisticsHandler _statsHandler;
@@ -55,9 +58,10 @@ public class StatisticsHandlerTest
     {
         _server = new Server();
 
-        _connector = new LocalConnector();
+        _connector = new LocalConnector(_server);
+        _statistics = new ConnectorStatistics();
+        _connector.addBean(_statistics);
         _server.addConnector(_connector);
-        _connector.setStatsOn(true);
 
         _latchHandler = new LatchHandler();
         _statsHandler = new StatisticsHandler();
@@ -76,10 +80,11 @@ public class StatisticsHandlerTest
     @Test
     public void testRequest() throws Exception
     {
-        final CyclicBarrier barrier[] = { new CyclicBarrier(2), new CyclicBarrier(2)};
+        final CyclicBarrier barrier[] = {new CyclicBarrier(2), new CyclicBarrier(2)};
 
         _statsHandler.setHandler(new AbstractHandler()
         {
+            @Override
             public void handle(String path, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException
             {
                 request.setHandled(true);
@@ -99,13 +104,13 @@ public class StatisticsHandlerTest
         _server.start();
 
         String request = "GET / HTTP/1.1\r\n" +
-                         "Host: localhost\r\n" +
-                         "\r\n";
+                "Host: localhost\r\n" +
+                "\r\n";
         _connector.executeRequest(request);
 
         barrier[0].await();
 
-        assertEquals(1, _connector.getConnectionsOpen());
+        assertEquals(1, _statistics.getConnectionsOpen());
 
         assertEquals(1, _statsHandler.getRequests());
         assertEquals(1, _statsHandler.getRequestsActive());
@@ -117,8 +122,7 @@ public class StatisticsHandlerTest
 
 
         barrier[1].await();
-        boolean passed = _latchHandler.await(1000);
-        assertTrue(passed);
+        assertTrue(_latchHandler.await());
 
         assertEquals(1, _statsHandler.getRequests());
         assertEquals(0, _statsHandler.getRequestsActive());
@@ -128,8 +132,8 @@ public class StatisticsHandlerTest
         assertEquals(0, _statsHandler.getDispatchedActive());
         assertEquals(1, _statsHandler.getDispatchedActiveMax());
 
-        assertEquals(0, _statsHandler.getSuspends());
-        assertEquals(0, _statsHandler.getResumes());
+        assertEquals(0, _statsHandler.getAsyncRequests());
+        assertEquals(0, _statsHandler.getAsyncDispatches());
         assertEquals(0, _statsHandler.getExpires());
         assertEquals(1, _statsHandler.getResponses2xx());
 
@@ -141,7 +145,7 @@ public class StatisticsHandlerTest
 
         barrier[0].await();
 
-        assertEquals(2, _connector.getConnectionsOpen());
+        assertEquals(2, _statistics.getConnectionsOpen());
 
         assertEquals(2, _statsHandler.getRequests());
         assertEquals(1, _statsHandler.getRequestsActive());
@@ -153,8 +157,7 @@ public class StatisticsHandlerTest
 
 
         barrier[1].await();
-        passed = _latchHandler.await(1000);
-        assertTrue(passed);
+        assertTrue(_latchHandler.await());
 
         assertEquals(2, _statsHandler.getRequests());
         assertEquals(0, _statsHandler.getRequestsActive());
@@ -164,21 +167,21 @@ public class StatisticsHandlerTest
         assertEquals(0, _statsHandler.getDispatchedActive());
         assertEquals(1, _statsHandler.getDispatchedActiveMax());
 
-        assertEquals(0, _statsHandler.getSuspends());
-        assertEquals(0, _statsHandler.getResumes());
+        assertEquals(0, _statsHandler.getAsyncRequests());
+        assertEquals(0, _statsHandler.getAsyncDispatches());
         assertEquals(0, _statsHandler.getExpires());
         assertEquals(2, _statsHandler.getResponses2xx());
 
         _latchHandler.reset(2);
-        barrier[0]=new CyclicBarrier(3);
-        barrier[1]=new CyclicBarrier(3);
+        barrier[0] = new CyclicBarrier(3);
+        barrier[1] = new CyclicBarrier(3);
 
         _connector.executeRequest(request);
         _connector.executeRequest(request);
 
         barrier[0].await();
 
-        assertEquals(4, _connector.getConnectionsOpen());
+        assertEquals(4, _statistics.getConnectionsOpen());
 
         assertEquals(4, _statsHandler.getRequests());
         assertEquals(2, _statsHandler.getRequestsActive());
@@ -190,8 +193,7 @@ public class StatisticsHandlerTest
 
 
         barrier[1].await();
-        passed = _latchHandler.await(1000);
-        assertTrue(passed);
+        assertTrue(_latchHandler.await());
 
         assertEquals(4, _statsHandler.getRequests());
         assertEquals(0, _statsHandler.getRequestsActive());
@@ -201,41 +203,37 @@ public class StatisticsHandlerTest
         assertEquals(0, _statsHandler.getDispatchedActive());
         assertEquals(2, _statsHandler.getDispatchedActiveMax());
 
-        assertEquals(0, _statsHandler.getSuspends());
-        assertEquals(0, _statsHandler.getResumes());
+        assertEquals(0, _statsHandler.getAsyncRequests());
+        assertEquals(0, _statsHandler.getAsyncDispatches());
         assertEquals(0, _statsHandler.getExpires());
         assertEquals(4, _statsHandler.getResponses2xx());
-
-
     }
 
     @Test
     public void testSuspendResume() throws Exception
     {
-        final AtomicReference<Continuation> continuationHandle = new AtomicReference<Continuation>();
-        final CyclicBarrier barrier[] = { new CyclicBarrier(2), new CyclicBarrier(2), new CyclicBarrier(2)};
+        final long dispatchTime = 10;
+        final long requestTime = 50;
+        final AtomicReference<AsyncContext> asyncHolder = new AtomicReference<>();
+        final CyclicBarrier barrier[] = {new CyclicBarrier(2), new CyclicBarrier(2), new CyclicBarrier(2)};
         _statsHandler.setHandler(new AbstractHandler()
         {
+            @Override
             public void handle(String path, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException
             {
                 request.setHandled(true);
-
                 try
                 {
                     barrier[0].await();
-                    Thread.sleep(10);
-                    Continuation continuation = ContinuationSupport.getContinuation(httpRequest);
-                    if (continuationHandle.get() == null)
-                    {
-                        continuation.suspend();
-                        continuationHandle.set(continuation);
-                    }
 
+                    Thread.sleep(dispatchTime);
+
+                    if (asyncHolder.get() == null)
+                        asyncHolder.set(request.startAsync());
                 }
                 catch (Exception x)
                 {
-                    Thread.currentThread().interrupt();
-                    throw (IOException)new IOException().initCause(x);
+                    throw new ServletException(x);
                 }
                 finally
                 {
@@ -243,126 +241,133 @@ public class StatisticsHandlerTest
                     {
                         barrier[1].await();
                     }
-                    catch (Exception x)
+                    catch (Exception ignored)
                     {
-                        x.printStackTrace();
-                        Thread.currentThread().interrupt();
-                        fail();
                     }
                 }
-
             }
         });
         _server.start();
 
         String request = "GET / HTTP/1.1\r\n" +
-                         "Host: localhost\r\n" +
-                         "\r\n";
+                "Host: localhost\r\n" +
+                "\r\n";
         _connector.executeRequest(request);
 
-
         barrier[0].await();
 
-        assertEquals(1, _connector.getConnectionsOpen());
-
+        assertEquals(1, _statistics.getConnectionsOpen());
         assertEquals(1, _statsHandler.getRequests());
         assertEquals(1, _statsHandler.getRequestsActive());
         assertEquals(1, _statsHandler.getDispatched());
         assertEquals(1, _statsHandler.getDispatchedActive());
 
         barrier[1].await();
-        assertTrue(_latchHandler.await(1000));
-        assertNotNull(continuationHandle.get());
-        assertTrue(continuationHandle.get().isSuspended());
+        assertTrue(_latchHandler.await());
+        assertNotNull(asyncHolder.get());
 
         assertEquals(1, _statsHandler.getRequests());
         assertEquals(1, _statsHandler.getRequestsActive());
         assertEquals(1, _statsHandler.getDispatched());
         assertEquals(0, _statsHandler.getDispatchedActive());
 
-        Thread.sleep(10);
         _latchHandler.reset();
         barrier[0].reset();
         barrier[1].reset();
 
-        continuationHandle.get().addContinuationListener(new ContinuationListener()
+        Thread.sleep(requestTime);
+
+        asyncHolder.get().addListener(new AsyncListener()
         {
-            public void onTimeout(Continuation continuation)
+            @Override
+            public void onTimeout(AsyncEvent event) throws IOException
             {
             }
 
-            public void onComplete(Continuation continuation)
+            @Override
+            public void onStartAsync(AsyncEvent event) throws IOException
             {
-                try { barrier[2].await(); } catch(Exception e) {}
             }
-        });
-
-        continuationHandle.get().resume();
 
+            @Override
+            public void onError(AsyncEvent event) throws IOException
+            {
+            }
 
-        barrier[0].await();
+            @Override
+            public void onComplete(AsyncEvent event) throws IOException
+            {
+                try
+                {
+                    barrier[2].await();
+                }
+                catch (Exception ignored)
+                {
+                }
+            }
+        });
+        asyncHolder.get().dispatch();
 
-        assertEquals(1, _connector.getConnectionsOpen());
+        barrier[0].await(); // entered app handler
 
+        assertEquals(1, _statistics.getConnectionsOpen());
         assertEquals(1, _statsHandler.getRequests());
         assertEquals(1, _statsHandler.getRequestsActive());
         assertEquals(2, _statsHandler.getDispatched());
         assertEquals(1, _statsHandler.getDispatchedActive());
 
-        barrier[1].await();
-        assertTrue(_latchHandler.await(1000));
-        barrier[2].await();
+        barrier[1].await(); // exiting app handler
+        assertTrue(_latchHandler.await()); // exited stats handler
+        barrier[2].await(); // onComplete called
 
         assertEquals(1, _statsHandler.getRequests());
         assertEquals(0, _statsHandler.getRequestsActive());
         assertEquals(2, _statsHandler.getDispatched());
         assertEquals(0, _statsHandler.getDispatchedActive());
 
-
-        assertEquals(1, _statsHandler.getSuspends());
-        assertEquals(1, _statsHandler.getResumes());
+        assertEquals(1, _statsHandler.getAsyncRequests());
+        assertEquals(1, _statsHandler.getAsyncDispatches());
         assertEquals(0, _statsHandler.getExpires());
         assertEquals(1, _statsHandler.getResponses2xx());
 
+        assertThat(_statsHandler.getRequestTimeTotal(), greaterThanOrEqualTo(requestTime * 3 / 4));
+        assertEquals(_statsHandler.getRequestTimeTotal(), _statsHandler.getRequestTimeMax());
+        assertEquals(_statsHandler.getRequestTimeTotal(), _statsHandler.getRequestTimeMean(), 0.01);
 
-        assertTrue(_statsHandler.getRequestTimeTotal()>=30);
-        assertEquals(_statsHandler.getRequestTimeTotal(),_statsHandler.getRequestTimeMax());
-        assertEquals(_statsHandler.getRequestTimeTotal(),_statsHandler.getRequestTimeMean(), 0.01);
-
-        assertTrue(_statsHandler.getDispatchedTimeTotal()>=20);
-        assertTrue(_statsHandler.getDispatchedTimeMean()+10<=_statsHandler.getDispatchedTimeTotal());
-        assertTrue(_statsHandler.getDispatchedTimeMax()+10<=_statsHandler.getDispatchedTimeTotal());
-
+        assertThat(_statsHandler.getDispatchedTimeTotal(), greaterThanOrEqualTo(dispatchTime * 2 * 3 / 4));
+        assertTrue(_statsHandler.getDispatchedTimeMean() + dispatchTime <= _statsHandler.getDispatchedTimeTotal());
+        assertTrue(_statsHandler.getDispatchedTimeMax() + dispatchTime <= _statsHandler.getDispatchedTimeTotal());
     }
 
     @Test
     public void testSuspendExpire() throws Exception
     {
-        final AtomicReference<Continuation> continuationHandle = new AtomicReference<Continuation>();
-        final CyclicBarrier barrier[] = { new CyclicBarrier(2), new CyclicBarrier(2), new CyclicBarrier(2)};
+        final long dispatchTime = 10;
+        final long timeout = 100;
+        final AtomicReference<AsyncContext> asyncHolder = new AtomicReference<>();
+        final CyclicBarrier barrier[] = {new CyclicBarrier(2), new CyclicBarrier(2), new CyclicBarrier(2)};
         _statsHandler.setHandler(new AbstractHandler()
         {
+            @Override
             public void handle(String path, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException
             {
                 request.setHandled(true);
-
                 try
                 {
                     barrier[0].await();
-                    Thread.sleep(10);
-                    Continuation continuation = ContinuationSupport.getContinuation(httpRequest);
-                    if (continuationHandle.get() == null)
+
+                    Thread.sleep(dispatchTime);
+
+                    if (asyncHolder.get() == null)
                     {
-                        continuation.setTimeout(100);
-                        continuation.suspend();
-                        continuationHandle.set(continuation);
+                        AsyncContext async = request.startAsync();
+                        asyncHolder.set(async);
+                        async.setTimeout(timeout);
                     }
-
                 }
                 catch (Exception x)
                 {
-                    Thread.currentThread().interrupt();
-                    throw (IOException)new IOException().initCause(x);
+                    throw new ServletException(x);
                 }
                 finally
                 {
@@ -370,47 +375,58 @@ public class StatisticsHandlerTest
                     {
                         barrier[1].await();
                     }
-                    catch (Exception x)
+                    catch (Exception ignored)
                     {
-                        x.printStackTrace();
-                        Thread.currentThread().interrupt();
-                        fail();
                     }
                 }
-
             }
         });
         _server.start();
 
         String request = "GET / HTTP/1.1\r\n" +
-                         "Host: localhost\r\n" +
-                         "\r\n";
+                "Host: localhost\r\n" +
+                "\r\n";
         _connector.executeRequest(request);
 
-
         barrier[0].await();
 
-        assertEquals(1, _connector.getConnectionsOpen());
-
+        assertEquals(1, _statistics.getConnectionsOpen());
         assertEquals(1, _statsHandler.getRequests());
         assertEquals(1, _statsHandler.getRequestsActive());
         assertEquals(1, _statsHandler.getDispatched());
         assertEquals(1, _statsHandler.getDispatchedActive());
 
         barrier[1].await();
-        assertTrue(_latchHandler.await(1000));
-        assertNotNull(continuationHandle.get());
-        assertTrue(continuationHandle.get().isSuspended());
-
-        continuationHandle.get().addContinuationListener(new ContinuationListener()
+        assertTrue(_latchHandler.await());
+        assertNotNull(asyncHolder.get());
+        asyncHolder.get().addListener(new AsyncListener()
         {
-            public void onTimeout(Continuation continuation)
+            @Override
+            public void onTimeout(AsyncEvent event) throws IOException
+            {
+                event.getAsyncContext().complete();
+            }
+
+            @Override
+            public void onStartAsync(AsyncEvent event) throws IOException
             {
             }
 
-            public void onComplete(Continuation continuation)
+            @Override
+            public void onError(AsyncEvent event) throws IOException
             {
-                try { barrier[2].await(); } catch(Exception e) {}
+            }
+
+            @Override
+            public void onComplete(AsyncEvent event) throws IOException
+            {
+                try
+                {
+                    barrier[2].await();
+                }
+                catch (Exception ignored)
+                {
+                }
             }
         });
 
@@ -419,70 +435,54 @@ public class StatisticsHandlerTest
         assertEquals(1, _statsHandler.getDispatched());
         assertEquals(0, _statsHandler.getDispatchedActive());
 
-        _latchHandler.reset();
-        barrier[0].reset();
-        barrier[1].reset();
-
-        barrier[0].await();
-
-        assertEquals(1, _statsHandler.getRequests());
-        assertEquals(1, _statsHandler.getRequestsActive());
-        assertEquals(2, _statsHandler.getDispatched());
-        assertEquals(1, _statsHandler.getDispatchedActive());
-
-        barrier[1].await();
-        assertTrue(_latchHandler.await(1000));
         barrier[2].await();
 
         assertEquals(1, _statsHandler.getRequests());
         assertEquals(0, _statsHandler.getRequestsActive());
-        assertEquals(2, _statsHandler.getDispatched());
+        assertEquals(1, _statsHandler.getDispatched());
         assertEquals(0, _statsHandler.getDispatchedActive());
 
-        assertEquals(1, _statsHandler.getSuspends());
-        assertEquals(1, _statsHandler.getResumes());
+        assertEquals(1, _statsHandler.getAsyncRequests());
+        assertEquals(0, _statsHandler.getAsyncDispatches());
         assertEquals(1, _statsHandler.getExpires());
         assertEquals(1, _statsHandler.getResponses2xx());
 
+        assertTrue(_statsHandler.getRequestTimeTotal() >= (timeout + dispatchTime) * 3 / 4);
+        assertEquals(_statsHandler.getRequestTimeTotal(), _statsHandler.getRequestTimeMax());
+        assertEquals(_statsHandler.getRequestTimeTotal(), _statsHandler.getRequestTimeMean(), 0.01);
 
-        assertTrue(_statsHandler.getRequestTimeTotal()>=30);
-        assertEquals(_statsHandler.getRequestTimeTotal(),_statsHandler.getRequestTimeMax());
-        assertEquals(_statsHandler.getRequestTimeTotal(),_statsHandler.getRequestTimeMean(), 0.01);
-
-        assertTrue(_statsHandler.getDispatchedTimeTotal()>=20);
-        assertTrue(_statsHandler.getDispatchedTimeMean()+10<=_statsHandler.getDispatchedTimeTotal());
-        assertTrue(_statsHandler.getDispatchedTimeMax()+10<=_statsHandler.getDispatchedTimeTotal());
-
+        assertThat(_statsHandler.getDispatchedTimeTotal(), greaterThanOrEqualTo(dispatchTime * 3 / 4));
     }
 
     @Test
     public void testSuspendComplete() throws Exception
     {
-        final AtomicReference<Continuation> continuationHandle = new AtomicReference<Continuation>();
-        final CyclicBarrier barrier[] = { new CyclicBarrier(2), new CyclicBarrier(2), new CyclicBarrier(2)};
+        final long dispatchTime = 10;
+        final AtomicReference<AsyncContext> asyncHolder = new AtomicReference<>();
+        final CyclicBarrier barrier[] = {new CyclicBarrier(2), new CyclicBarrier(2)};
+        final CountDownLatch latch = new CountDownLatch(1);
+        
         _statsHandler.setHandler(new AbstractHandler()
         {
+            @Override
             public void handle(String path, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException
             {
                 request.setHandled(true);
-
                 try
                 {
                     barrier[0].await();
-                    Thread.sleep(10);
-                    Continuation continuation = ContinuationSupport.getContinuation(httpRequest);
-                    if (continuationHandle.get() == null)
+
+                    Thread.sleep(dispatchTime);
+
+                    if (asyncHolder.get() == null)
                     {
-                        continuation.setTimeout(1000);
-                        continuation.suspend();
-                        continuationHandle.set(continuation);
+                        AsyncContext async = request.startAsync();
+                        asyncHolder.set(async);
                     }
-
                 }
                 catch (Exception x)
                 {
-                    Thread.currentThread().interrupt();
-                    throw (IOException)new IOException().initCause(x);
+                    throw new ServletException(x);
                 }
                 finally
                 {
@@ -490,11 +490,8 @@ public class StatisticsHandlerTest
                     {
                         barrier[1].await();
                     }
-                    catch (Exception x)
+                    catch (Exception ignored)
                     {
-                        x.printStackTrace();
-                        Thread.currentThread().interrupt();
-                        fail();
                     }
                 }
 
@@ -503,67 +500,81 @@ public class StatisticsHandlerTest
         _server.start();
 
         String request = "GET / HTTP/1.1\r\n" +
-                         "Host: localhost\r\n" +
-                         "\r\n";
+                "Host: localhost\r\n" +
+                "\r\n";
         _connector.executeRequest(request);
 
-
         barrier[0].await();
 
-        assertEquals(1, _connector.getConnectionsOpen());
-
+        assertEquals(1, _statistics.getConnectionsOpen());
         assertEquals(1, _statsHandler.getRequests());
         assertEquals(1, _statsHandler.getRequestsActive());
         assertEquals(1, _statsHandler.getDispatched());
         assertEquals(1, _statsHandler.getDispatchedActive());
 
-
         barrier[1].await();
-        assertTrue(_latchHandler.await(1000));
-        assertNotNull(continuationHandle.get());
-        assertTrue(continuationHandle.get().isSuspended());
-        continuationHandle.get().addContinuationListener(new ContinuationListener()
+        assertTrue(_latchHandler.await());
+        assertNotNull(asyncHolder.get());
+
+        assertEquals(1, _statsHandler.getRequests());
+        assertEquals(1, _statsHandler.getRequestsActive());
+        assertEquals(1, _statsHandler.getDispatched());
+        assertEquals(0, _statsHandler.getDispatchedActive());
+
+        asyncHolder.get().addListener(new AsyncListener()
         {
-            public void onTimeout(Continuation continuation)
+            @Override
+            public void onTimeout(AsyncEvent event) throws IOException
             {
             }
 
-            public void onComplete(Continuation continuation)
+            @Override
+            public void onStartAsync(AsyncEvent event) throws IOException
             {
-                try { barrier[2].await(); } catch(Exception e) {}
             }
-        });
 
-        assertEquals(1, _statsHandler.getRequests());
-        assertEquals(1, _statsHandler.getRequestsActive());
-        assertEquals(1, _statsHandler.getDispatched());
-        assertEquals(0, _statsHandler.getDispatchedActive());
+            @Override
+            public void onError(AsyncEvent event) throws IOException
+            {
+            }
 
-        Thread.sleep(10);
-        continuationHandle.get().complete();
-        barrier[2].await();
+            @Override
+            public void onComplete(AsyncEvent event) throws IOException
+            {
+                try
+                {
+                    latch.countDown();
+                }
+                catch (Exception ignored)
+                {
+                }
+            }
+        });
+        long requestTime = 20;
+        Thread.sleep(requestTime);
+        asyncHolder.get().complete();
+        latch.await();
 
         assertEquals(1, _statsHandler.getRequests());
         assertEquals(0, _statsHandler.getRequestsActive());
         assertEquals(1, _statsHandler.getDispatched());
         assertEquals(0, _statsHandler.getDispatchedActive());
 
-        assertEquals(1, _statsHandler.getSuspends());
-        assertEquals(0, _statsHandler.getResumes());
+        assertEquals(1, _statsHandler.getAsyncRequests());
+        assertEquals(0, _statsHandler.getAsyncDispatches());
         assertEquals(0, _statsHandler.getExpires());
         assertEquals(1, _statsHandler.getResponses2xx());
 
-        assertTrue(_statsHandler.getRequestTimeTotal()>=20);
-        assertEquals(_statsHandler.getRequestTimeTotal(),_statsHandler.getRequestTimeMax());
-        assertEquals(_statsHandler.getRequestTimeTotal(),_statsHandler.getRequestTimeMean(), 0.01);
+        assertTrue(_statsHandler.getRequestTimeTotal() >= (dispatchTime + requestTime) * 3 / 4);
+        assertEquals(_statsHandler.getRequestTimeTotal(), _statsHandler.getRequestTimeMax());
+        assertEquals(_statsHandler.getRequestTimeTotal(), _statsHandler.getRequestTimeMean(), 0.01);
 
-        assertTrue(_statsHandler.getDispatchedTimeTotal()>=10);
-        assertTrue(_statsHandler.getDispatchedTimeTotal()<_statsHandler.getRequestTimeTotal());
-        assertEquals(_statsHandler.getDispatchedTimeTotal(),_statsHandler.getDispatchedTimeMax());
-        assertEquals(_statsHandler.getDispatchedTimeTotal(),_statsHandler.getDispatchedTimeMean(), 0.01);
+        assertTrue(_statsHandler.getDispatchedTimeTotal() >= dispatchTime * 3 / 4);
+        assertTrue(_statsHandler.getDispatchedTimeTotal() < _statsHandler.getRequestTimeTotal());
+        assertEquals(_statsHandler.getDispatchedTimeTotal(), _statsHandler.getDispatchedTimeMax());
+        assertEquals(_statsHandler.getDispatchedTimeTotal(), _statsHandler.getDispatchedTimeMean(), 0.01);
     }
 
-
     /**
      * This handler is external to the statistics handler and it is used to ensure that statistics handler's
      * handle() is fully executed before asserting its values in the tests, to avoid race conditions with the
@@ -576,7 +587,7 @@ public class StatisticsHandlerTest
         @Override
         public void handle(String path, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException
         {
-            final CountDownLatch latch=_latch;
+            final CountDownLatch latch = _latch;
             try
             {
                 super.handle(path, request, httpRequest, httpResponse);
@@ -589,17 +600,17 @@ public class StatisticsHandlerTest
 
         private void reset()
         {
-            _latch=new CountDownLatch(1);
+            _latch = new CountDownLatch(1);
         }
 
         private void reset(int count)
         {
-            _latch=new CountDownLatch(count);
+            _latch = new CountDownLatch(count);
         }
 
-        private boolean await(long ms) throws InterruptedException
+        private boolean await() throws InterruptedException
         {
-            return _latch.await(ms, TimeUnit.MILLISECONDS);
+            return _latch.await(10000, TimeUnit.MILLISECONDS);
         }
     }
 }
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/session/HashSessionManagerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/session/HashSessionManagerTest.java
index 381122e..7787bb2 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/session/HashSessionManagerTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/session/HashSessionManagerTest.java
@@ -20,37 +20,20 @@ package org.eclipse.jetty.server.session;
 
 import java.io.File;
 
-import junit.framework.Assert;
-
-import org.eclipse.jetty.server.SessionManager;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.toolchain.test.FS;
 import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.eclipse.jetty.util.IO;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.StdErrLog;
 import org.junit.After;
+import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 
 public class HashSessionManagerTest
 {
-    
-    @After
-    public void enableStacks()
-    {
-        enableStacks(true);
-    }
-
-    @Before
-    public void quietStacks()
-    {
-        enableStacks(false);
-    }
-    
-    protected void enableStacks(boolean enabled)
-    {
-        StdErrLog log = (StdErrLog)Log.getLogger("org.eclipse.jetty.server.session");
-        log.setHideStacks(!enabled);
-    }
-    
     @Test
     public void testDangerousSessionIdRemoval() throws Exception
     {
@@ -60,7 +43,7 @@ public class HashSessionManagerTest
         File testDir = MavenTestingUtils.getTargetTestingDir("hashes");
         testDir.mkdirs();
         manager.setStoreDirectory(testDir);
-        
+
         MavenTestingUtils.getTargetFile("dangerFile.session").createNewFile();
         
         Assert.assertTrue("File should exist!", MavenTestingUtils.getTargetFile("dangerFile.session").exists());
@@ -71,23 +54,75 @@ public class HashSessionManagerTest
 
     }
     
-    @Test
+   @Test
     public void testValidSessionIdRemoval() throws Exception
     {
         final HashSessionManager manager = new HashSessionManager();
         manager.setDeleteUnrestorableSessions(true);
         manager.setLazyLoad(true);
         File testDir = MavenTestingUtils.getTargetTestingDir("hashes");
-        testDir.mkdirs();
-        manager.setStoreDirectory(testDir);
+        FS.ensureEmpty(testDir);
         
-        new File(testDir, "validFile.session").createNewFile();
+        manager.setStoreDirectory(testDir);
+
+        Assert.assertTrue(new File(testDir, "validFile.session").createNewFile());
         
         Assert.assertTrue("File should exist!", new File(testDir, "validFile.session").exists());
        
         manager.getSession("validFile.session");
 
         Assert.assertTrue("File shouldn't exist!", !new File(testDir,"validFile.session").exists());
-
+    }
+    
+    @Test
+    public void testHashSession() throws Exception
+    {
+        File testDir = MavenTestingUtils.getTargetTestingDir("saved");
+        IO.delete(testDir);
+        testDir.mkdirs();
+        
+        Server server = new Server();
+        SessionHandler handler = new SessionHandler();
+        handler.setServer(server);
+        HashSessionManager manager = new HashSessionManager();
+        manager.setStoreDirectory(testDir);
+        manager.setMaxInactiveInterval(5);
+        Assert.assertTrue(testDir.exists());
+        Assert.assertTrue(testDir.canWrite());
+        handler.setSessionManager(manager);
+        
+        AbstractSessionIdManager idManager = new HashSessionIdManager();
+        idManager.setWorkerName("foo");
+        manager.setSessionIdManager(idManager);
+        server.setSessionIdManager(idManager);
+        
+        server.start();
+        manager.start();
+        
+        HashedSession session = (HashedSession)manager.newHttpSession(new Request(null, null));
+        String sessionId = session.getId();
+        
+        session.setAttribute("one", new Integer(1));
+        session.setAttribute("two", new Integer(2));    
+        
+        //stop will persist sessions
+        manager.setMaxInactiveInterval(30); // change max inactive interval for *new* sessions
+        manager.stop();
+        
+        Assert.assertTrue("File should exist!", new File(testDir, session.getId()).exists());
+        
+        //start will restore sessions
+        manager.start();
+        
+        HashedSession restoredSession = (HashedSession)manager.getSession(sessionId);
+        Assert.assertNotNull(restoredSession);
+        
+        Object o = restoredSession.getAttribute("one");
+        Assert.assertNotNull(o);
+        
+        Assert.assertEquals(1, ((Integer)o).intValue());
+        Assert.assertEquals(5, restoredSession.getMaxInactiveInterval());     
+        
+        server.stop();
     }
 }
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/session/SessionCookieTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/session/SessionCookieTest.java
index 488875b..e3322e5 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/session/SessionCookieTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/session/SessionCookieTest.java
@@ -18,10 +18,13 @@
 
 package org.eclipse.jetty.server.session;
 
-import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
+import java.util.Enumeration;
+import java.util.Map;
+import java.util.Set;
+
 import javax.servlet.SessionCookieConfig;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpSession;
@@ -35,11 +38,11 @@ import org.junit.Test;
  */
 public class SessionCookieTest
 {
-    
+
     public class MockSession extends AbstractSession
     {
 
-        
+
         /**
          * @param abstractSessionManager
          * @param created
@@ -50,108 +53,204 @@ public class SessionCookieTest
         {
             super(abstractSessionManager, created, accessed, clusterId);
         }
-        
+
+        /** 
+         * @see javax.servlet.http.HttpSession#getAttribute(java.lang.String)
+         */
+        @Override
+        public Object getAttribute(String name)
+        {
+            return null;
+        }
+
+        /** 
+         * @see javax.servlet.http.HttpSession#getAttributeNames()
+         */
+        @Override
+        public Enumeration<String> getAttributeNames()
+        {
+            return null;
+        }
+
+        /** 
+         * @see javax.servlet.http.HttpSession#getValueNames()
+         */
+        @Override
+        public String[] getValueNames()
+        {
+            return null;
+        }
+
+        /** 
+         * @see org.eclipse.jetty.server.session.AbstractSession#getAttributeMap()
+         */
+        @Override
+        public Map<String, Object> getAttributeMap()
+        {
+            return null;
+        }
+
+        /** 
+         * @see org.eclipse.jetty.server.session.AbstractSession#getAttributes()
+         */
+        @Override
+        public int getAttributes()
+        {
+            return 0;
+        }
+
+        /** 
+         * @see org.eclipse.jetty.server.session.AbstractSession#getNames()
+         */
+        @Override
+        public Set<String> getNames()
+        {
+            return null;
+        }
+
+        /** 
+         * @see org.eclipse.jetty.server.session.AbstractSession#clearAttributes()
+         */
+        @Override
+        public void clearAttributes()
+        {
+            
+        }
+
+        /** 
+         * @see org.eclipse.jetty.server.session.AbstractSession#doPutOrRemove(java.lang.String, java.lang.Object)
+         */
+        @Override
+        public Object doPutOrRemove(String name, Object value)
+        {
+            return null;
+        }
+
+        /** 
+         * @see org.eclipse.jetty.server.session.AbstractSession#doGet(java.lang.String)
+         */
+        @Override
+        public Object doGet(String name)
+        {
+            return null;
+        }
+
+        /** 
+         * @see org.eclipse.jetty.server.session.AbstractSession#doGetAttributeNames()
+         */
+        @Override
+        public Enumeration<String> doGetAttributeNames()
+        {
+            return null;
+        }
+
     }
-    
+
     public class MockSessionIdManager extends AbstractSessionIdManager
     {
 
-        /** 
+        /**
          * @see org.eclipse.jetty.server.SessionIdManager#idInUse(java.lang.String)
          */
+        @Override
         public boolean idInUse(String id)
         {
             return false;
         }
 
-        /** 
+        /**
          * @see org.eclipse.jetty.server.SessionIdManager#addSession(javax.servlet.http.HttpSession)
          */
+        @Override
         public void addSession(HttpSession session)
         {
-           
+
         }
 
-        /** 
+        /**
          * @see org.eclipse.jetty.server.SessionIdManager#removeSession(javax.servlet.http.HttpSession)
          */
+        @Override
         public void removeSession(HttpSession session)
         {
-             
+
         }
 
-        /** 
+        /**
          * @see org.eclipse.jetty.server.SessionIdManager#invalidateAll(java.lang.String)
          */
+        @Override
         public void invalidateAll(String id)
         {
-           
-        }
 
-        /** 
-         * @see org.eclipse.jetty.server.SessionIdManager#getClusterId(java.lang.String)
-         */
-        public String getClusterId(String nodeId)
-        {
-            int dot=nodeId.lastIndexOf('.');
-            return (dot>0)?nodeId.substring(0,dot):nodeId;
         }
 
-        /** 
-         * @see org.eclipse.jetty.server.SessionIdManager#getNodeId(java.lang.String, javax.servlet.http.HttpServletRequest)
-         */
-        public String getNodeId(String clusterId, HttpServletRequest request)
+        @Override
+        public void renewSessionId(String oldClusterId, String oldNodeId, HttpServletRequest request)
         {
-            return clusterId+'.'+_workerName;
+            // TODO Auto-generated method stub
+            
         }
 
     }
-    
+
     public class MockSessionManager extends AbstractSessionManager
     {
 
-        /** 
+        /**
          * @see org.eclipse.jetty.server.session.AbstractSessionManager#addSession(org.eclipse.jetty.server.session.AbstractSession)
          */
+        @Override
         protected void addSession(AbstractSession session)
         {
-            
+
         }
 
-        /** 
+        /**
          * @see org.eclipse.jetty.server.session.AbstractSessionManager#getSession(java.lang.String)
          */
+        @Override
         public AbstractSession getSession(String idInCluster)
         {
             return null;
         }
 
-        /** 
-         * @see org.eclipse.jetty.server.session.AbstractSessionManager#invalidateSessions()
+        /**
+         * @see org.eclipse.jetty.server.session.AbstractSessionManager#shutdownSessions()
          */
-        protected void invalidateSessions() throws Exception
+        @Override
+        protected void shutdownSessions() throws Exception
         {
-            
+
         }
 
-        /** 
+        /**
          * @see org.eclipse.jetty.server.session.AbstractSessionManager#newSession(javax.servlet.http.HttpServletRequest)
          */
+        @Override
         protected AbstractSession newSession(HttpServletRequest request)
         {
             return null;
         }
 
-        /** 
+        /**
          * @see org.eclipse.jetty.server.session.AbstractSessionManager#removeSession(java.lang.String)
          */
+        @Override
         protected boolean removeSession(String idInCluster)
         {
             return false;
         }
-        
+
+        @Override
+        public void renewSessionId(String oldClusterId, String oldNodeId, String newClusterId, String newNodeId)
+        {
+            // TODO Auto-generated method stub
+            
+        }
+
     }
-    
+
     @Test
     public void testSecureSessionCookie () throws Exception
     {
@@ -160,40 +259,40 @@ public class SessionCookieTest
         MockSessionManager mgr = new MockSessionManager();
         mgr.setSessionIdManager(idMgr);
         MockSession session = new MockSession(mgr, System.currentTimeMillis(), System.currentTimeMillis(), "node1123"); //clusterId
-        
+
         SessionCookieConfig sessionCookieConfig = mgr.getSessionCookieConfig();
         sessionCookieConfig.setSecure(true);
-        
+
         //sessionCookieConfig.secure == true, always mark cookie as secure, irrespective of if requestIsSecure
         HttpCookie cookie = mgr.getSessionCookie(session, "/foo", true);
         assertTrue(cookie.isSecure());
         //sessionCookieConfig.secure == true, always mark cookie as secure, irrespective of if requestIsSecure
         cookie = mgr.getSessionCookie(session, "/foo", false);
         assertTrue(cookie.isSecure());
-        
+
         //sessionCookieConfig.secure==false, setSecureRequestOnly==true, requestIsSecure==true
         //cookie should be secure: see SessionCookieConfig.setSecure() javadoc
         sessionCookieConfig.setSecure(false);
         cookie = mgr.getSessionCookie(session, "/foo", true);
         assertTrue(cookie.isSecure());
-        
+
         //sessionCookieConfig.secure=false, setSecureRequestOnly==true, requestIsSecure==false
         //cookie is not secure: see SessionCookieConfig.setSecure() javadoc
         cookie = mgr.getSessionCookie(session, "/foo", false);
         assertFalse(cookie.isSecure());
-        
+
         //sessionCookieConfig.secure=false, setSecureRequestOnly==false, requestIsSecure==false
         //cookie is not secure: not a secure request
         mgr.setSecureRequestOnly(false);
         cookie = mgr.getSessionCookie(session, "/foo", false);
         assertFalse(cookie.isSecure());
-        
+
         //sessionCookieConfig.secure=false, setSecureRequestOnly==false, requestIsSecure==true
         //cookie is not secure: not on secured requests and request is secure
         cookie = mgr.getSessionCookie(session, "/foo", true);
         assertFalse(cookie.isSecure());
-        
-        
+
+
     }
 
 }
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/session/SessionHandlerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/session/SessionHandlerTest.java
deleted file mode 100644
index 6d02a10..0000000
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/session/SessionHandlerTest.java
+++ /dev/null
@@ -1,865 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.server.session;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.security.Principal;
-import java.util.Collection;
-import java.util.Enumeration;
-import java.util.EventListener;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Set;
-import javax.servlet.AsyncContext;
-import javax.servlet.RequestDispatcher;
-import javax.servlet.ServletContext;
-import javax.servlet.ServletException;
-import javax.servlet.ServletInputStream;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
-import javax.servlet.SessionCookieConfig;
-import javax.servlet.SessionTrackingMode;
-import javax.servlet.http.Cookie;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import javax.servlet.http.HttpSession;
-import javax.servlet.http.Part;
-import javax.servlet.DispatcherType;
-
-import org.eclipse.jetty.http.HttpCookie;
-import org.eclipse.jetty.server.Request;
-import org.eclipse.jetty.server.SessionIdManager;
-import org.eclipse.jetty.server.SessionManager;
-import org.junit.Test;
-
-public class SessionHandlerTest
-{
-    @Test
-    public void testRequestedIdFromCookies()
-    {
-        final String cookieName = "SessionId";
-        final String sessionId = "1234.host";
-        HttpServletRequest httpRequest = new MockHttpServletRequest()
-        {
-            public Cookie[] getCookies()
-            {
-                return new Cookie[]
-                { new Cookie(cookieName,sessionId) };
-            }
-        };
-
-        Request baseRequest = new Request();
-        baseRequest.setDispatcherType(DispatcherType.REQUEST);
-        assertEquals(DispatcherType.REQUEST,baseRequest.getDispatcherType());
-
-        SessionHandler sessionHandler = new SessionHandler();
-        sessionHandler.setSessionManager(new MockSessionManager()
-        {
- 
-            
-            public SessionCookieConfig getSessionCookieConfig()
-            {
-                return new SessionCookieConfig()
-                {
-
-                    public String getComment()
-                    {
-                        // TODO Auto-generated method stub
-                        return null;
-                    }
-
-                    public String getDomain()
-                    {
-                        // TODO Auto-generated method stub
-                        return null;
-                    }
-
-                    public int getMaxAge()
-                    {
-                        // TODO Auto-generated method stub
-                        return 0;
-                    }
-
-                    public String getName()
-                    {
-                        return cookieName;
-                    }
-
-                    public String getPath()
-                    {
-                        // TODO Auto-generated method stub
-                        return null;
-                    }
-
-                    public boolean isHttpOnly()
-                    {
-                        // TODO Auto-generated method stub
-                        return false;
-                    }
-
-                    public boolean isSecure()
-                    {
-                        // TODO Auto-generated method stub
-                        return false;
-                    }
-
-                    public void setComment(String comment)
-                    {
-                        // TODO Auto-generated method stub
-                        
-                    }
-
-                    public void setDomain(String domain)
-                    {
-                        // TODO Auto-generated method stub
-                        
-                    }
-
-                    public void setHttpOnly(boolean httpOnly)
-                    {
-                        // TODO Auto-generated method stub
-                        
-                    }
-
-                    public void setMaxAge(int maxAge)
-                    {
-                        // TODO Auto-generated method stub
-                        
-                    }
-
-                    public void setName(String name)
-                    {
-                        // TODO Auto-generated method stub
-                        
-                    }
-
-                    public void setPath(String path)
-                    {
-                        // TODO Auto-generated method stub
-                        
-                    }
-
-                    public void setSecure(boolean secure)
-                    {
-                        // TODO Auto-generated method stub
-                        
-                    }
-                    
-                };
-            }
-            public boolean isUsingCookies()
-            {
-                return true;
-            }
-
-            public String getSessionCookie()
-            {
-                return cookieName;
-            }
-        });
-        sessionHandler.checkRequestedSessionId(baseRequest,httpRequest);
-
-        assertEquals(sessionId,baseRequest.getRequestedSessionId());
-        assertTrue(baseRequest.isRequestedSessionIdFromCookie());
-    }
-
-    @Test
-    public void testRequestedIdFromURI()
-    {
-        final String parameterName = "sessionid";
-        final String sessionId = "1234.host";
-        HttpServletRequest httpRequest = new MockHttpServletRequest()
-        {
-            @Override
-            public String getRequestURI()
-            {
-                return "http://www.foo.net/app/action.do;" + parameterName + "=" + sessionId + ";p1=abc;p2=def";
-            }
-        };
-
-        Request baseRequest = new Request();
-        baseRequest.setDispatcherType(DispatcherType.REQUEST);
-        assertEquals(DispatcherType.REQUEST,baseRequest.getDispatcherType());
-
-        SessionHandler sessionHandler = new SessionHandler();
-        sessionHandler.setSessionManager(new MockSessionManager()
-        {       
-
-            @Override
-            public String getSessionIdPathParameterName()
-            {
-                return parameterName;
-            }
-
-            @Override
-            public String getSessionIdPathParameterNamePrefix()
-            {
-                return ";"+parameterName+"=";
-            }
-        });
-
-        sessionHandler.checkRequestedSessionId(baseRequest,httpRequest);
-
-        assertEquals(sessionId,baseRequest.getRequestedSessionId());
-        assertFalse(baseRequest.isRequestedSessionIdFromCookie());
-    }
-
-    /**
-     * Mock class for HttpServletRequest interface.
-     */
-    @SuppressWarnings("unchecked")
-    private class MockHttpServletRequest implements HttpServletRequest
-    {
-        public String getRequestURI()
-        {
-            return null;
-        }
-
-        public Cookie[] getCookies()
-        {
-            return null;
-        }
-
-        public String getAuthType()
-        {
-            return null;
-        }
-
-        public String getContextPath()
-        {
-            return null;
-        }
-
-        public long getDateHeader(String name)
-        {
-            return 0;
-        }
-
-        public String getHeader(String name)
-        {
-            return null;
-        }
-
-        public Enumeration getHeaderNames()
-        {
-            return null;
-        }
-
-        public Enumeration getHeaders(String name)
-        {
-            return null;
-        }
-
-        public int getIntHeader(String name)
-        {
-            return 0;
-        }
-
-        public String getMethod()
-        {
-            return null;
-        }
-
-        public String getPathInfo()
-        {
-            return null;
-        }
-
-        public String getPathTranslated()
-        {
-            return null;
-        }
-
-        public String getQueryString()
-        {
-            return null;
-        }
-
-        public String getRemoteUser()
-        {
-            return null;
-        }
-
-        public StringBuffer getRequestURL()
-        {
-            return null;
-        }
-
-        public String getRequestedSessionId()
-        {
-            return null;
-        }
-
-        public String getServletPath()
-        {
-            return null;
-        }
-
-        public HttpSession getSession()
-        {
-            return null;
-        }
-
-        public HttpSession getSession(boolean create)
-        {
-            return null;
-        }
-
-        public Principal getUserPrincipal()
-        {
-            return null;
-        }
-
-        public boolean isRequestedSessionIdFromCookie()
-        {
-            return false;
-        }
-
-        public boolean isRequestedSessionIdFromURL()
-        {
-            return false;
-        }
-
-        public boolean isRequestedSessionIdFromUrl()
-        {
-            return false;
-        }
-
-        public boolean isRequestedSessionIdValid()
-        {
-            return false;
-        }
-
-        public boolean isUserInRole(String role)
-        {
-            return false;
-        }
-
-        public Object getAttribute(String name)
-        {
-            return null;
-        }
-
-        public Enumeration getAttributeNames()
-        {
-            return null;
-        }
-
-        public String getCharacterEncoding()
-        {
-            return null;
-        }
-
-        public int getContentLength()
-        {
-            return 0;
-        }
-
-        public String getContentType()
-        {
-            return null;
-        }
-
-        public ServletInputStream getInputStream() throws IOException
-        {
-            return null;
-        }
-
-        public String getLocalAddr()
-        {
-            return null;
-        }
-
-        public String getLocalName()
-        {
-            return null;
-        }
-
-        public int getLocalPort()
-        {
-            return 0;
-        }
-
-        public Locale getLocale()
-        {
-            return null;
-        }
-
-        public Enumeration getLocales()
-        {
-            return null;
-        }
-
-        public String getParameter(String name)
-        {
-            return null;
-        }
-
-        public Map getParameterMap()
-        {
-            return null;
-        }
-
-        public Enumeration getParameterNames()
-        {
-            return null;
-        }
-
-        public String[] getParameterValues(String name)
-        {
-            return null;
-        }
-
-        public String getProtocol()
-        {
-            return null;
-        }
-
-        public BufferedReader getReader() throws IOException
-        {
-            return null;
-        }
-
-        public String getRealPath(String path)
-        {
-            return null;
-        }
-
-        public String getRemoteAddr()
-        {
-            return null;
-        }
-
-        public String getRemoteHost()
-        {
-            return null;
-        }
-
-        public int getRemotePort()
-        {
-            return 0;
-        }
-
-        public RequestDispatcher getRequestDispatcher(String path)
-        {
-            return null;
-        }
-
-        public String getScheme()
-        {
-            return null;
-        }
-
-        public String getServerName()
-        {
-            return null;
-        }
-
-        public int getServerPort()
-        {
-            return 0;
-        }
-
-        public boolean isSecure()
-        {
-            return false;
-        }
-
-        public void removeAttribute(String name)
-        {
-        }
-
-        public void setAttribute(String name, Object o)
-        {
-        }
-
-        public void setCharacterEncoding(String env) throws UnsupportedEncodingException
-        {
-        }
-
-        /** 
-         * @see javax.servlet.http.HttpServletRequest#authenticate(javax.servlet.http.HttpServletResponse)
-         */
-        public boolean authenticate(HttpServletResponse response) throws IOException, ServletException
-        {
-            // TODO Auto-generated method stub
-            return false;
-        }
-
-        /** 
-         * @see javax.servlet.http.HttpServletRequest#getPart(java.lang.String)
-         */
-        public Part getPart(String name) throws IOException, ServletException
-        {
-            // TODO Auto-generated method stub
-            return null;
-        }
-
-        /** 
-         * @see javax.servlet.http.HttpServletRequest#getParts()
-         */
-        public Collection<Part> getParts() throws IOException, ServletException
-        {
-            // TODO Auto-generated method stub
-            return null;
-        }
-
-        /** 
-         * @see javax.servlet.http.HttpServletRequest#login(java.lang.String, java.lang.String)
-         */
-        public void login(String username, String password) throws ServletException
-        {
-            // TODO Auto-generated method stub
-            
-        }
-
-        /** 
-         * @see javax.servlet.http.HttpServletRequest#logout()
-         */
-        public void logout() throws ServletException
-        {
-            // TODO Auto-generated method stub
-            
-        }
-
-        /** 
-         * @see javax.servlet.ServletRequest#getAsyncContext()
-         */
-        public AsyncContext getAsyncContext()
-        {
-            // TODO Auto-generated method stub
-            return null;
-        }
-
-        /** 
-         * @see javax.servlet.ServletRequest#getDispatcherType()
-         */
-        public DispatcherType getDispatcherType()
-        {
-            // TODO Auto-generated method stub
-            return null;
-        }
-
-        /** 
-         * @see javax.servlet.ServletRequest#getServletContext()
-         */
-        public ServletContext getServletContext()
-        {
-            // TODO Auto-generated method stub
-            return null;
-        }
-
-        /** 
-         * @see javax.servlet.ServletRequest#isAsyncStarted()
-         */
-        public boolean isAsyncStarted()
-        {
-            // TODO Auto-generated method stub
-            return false;
-        }
-
-        /** 
-         * @see javax.servlet.ServletRequest#isAsyncSupported()
-         */
-        public boolean isAsyncSupported()
-        {
-            // TODO Auto-generated method stub
-            return false;
-        }
-
-        /** 
-         * @see javax.servlet.ServletRequest#startAsync()
-         */
-        public AsyncContext startAsync() throws IllegalStateException
-        {
-            // TODO Auto-generated method stub
-            return null;
-        }
-
-        /** 
-         * @see javax.servlet.ServletRequest#startAsync(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
-         */
-        public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) throws IllegalStateException
-        {
-            // TODO Auto-generated method stub
-            return null;
-        }
-    }
-
-    /**
-     * Mock class for SessionManager interface.
-     */
-    private class MockSessionManager implements SessionManager
-    {
-        public HttpCookie access(HttpSession session, boolean secure)
-        {
-            return null;
-        }
-
-        public void addEventListener(EventListener listener)
-        {
-        }
-
-        public void clearEventListeners()
-        {
-        }
-
-        public void complete(HttpSession session)
-        {
-        }
-
-        public String getClusterId(HttpSession session)
-        {
-            return null;
-        }
-
-        public boolean getHttpOnly()
-        {
-            return false;
-        }
-
-        public HttpSession getHttpSession(String id)
-        {
-            return null;
-        }
-
-        public SessionIdManager getSessionIdManager()
-        {
-            return null;
-        }
-
-        public int getMaxCookieAge()
-        {
-            return 0;
-        }
-
-        public int getMaxInactiveInterval()
-        {
-            return 0;
-        }
-
-        public SessionIdManager getMetaManager()
-        {
-            return null;
-        }
-
-        public String getNodeId(HttpSession session)
-        {
-            return null;
-        }
-
-        public boolean getSecureCookies()
-        {
-            return false;
-        }
-
-        public HttpCookie getSessionCookie(HttpSession session, String contextPath, boolean requestIsSecure)
-        {
-            return null;
-        }
-
-        public String getSessionCookie()
-        {
-            return null;
-        }
-
-        public String getSessionDomain()
-        {
-            return null;
-        }
-
-        public String getSessionIdPathParameterName()
-        {
-            return null;
-        }
-
-        public String getSessionIdPathParameterNamePrefix()
-        {
-            return null;
-        }
-
-        public String getSessionPath()
-        {
-            return null;
-        }
-
-        public boolean isUsingCookies()
-        {
-            return false;
-        }
-
-        public boolean isValid(HttpSession session)
-        {
-            return false;
-        }
-
-        public HttpSession newHttpSession(HttpServletRequest request)
-        {
-            return null;
-        }
-
-        public void removeEventListener(EventListener listener)
-        {
-        }
-
-        public void setSessionIdManager(SessionIdManager idManager)
-        {
-        }
-
-        public void setMaxCookieAge(int maxCookieAge)
-        {
-        }
-
-        public void setMaxInactiveInterval(int seconds)
-        {
-        }
-
-        public void setSessionCookie(String cookieName)
-        {
-        }
-
-        public void setSessionDomain(String domain)
-        {
-        }
-
-        public void setSessionHandler(SessionHandler handler)
-        {
-        }
-
-        public void setSessionIdPathParameterName(String parameterName)
-        {
-        }
-
-        public void setSessionPath(String path)
-        {
-        }
-
-        public void addLifeCycleListener(Listener listener)
-        {
-        }
-
-        public boolean isFailed()
-        {
-            return false;
-        }
-
-        public boolean isRunning()
-        {
-            return false;
-        }
-
-        public boolean isStarted()
-        {
-            return false;
-        }
-
-        public boolean isStarting()
-        {
-            return false;
-        }
-
-        public boolean isStopped()
-        {
-            return false;
-        }
-
-        public boolean isStopping()
-        {
-            return false;
-        }
-
-        public void removeLifeCycleListener(Listener listener)
-        {
-        }
-
-        public void start() throws Exception
-        {
-        }
-
-        public void stop() throws Exception
-        {
-        }
-
-        /** 
-         * @see org.eclipse.jetty.server.SessionManager#getDefaultSessionTrackingModes()
-         */
-        public Set<SessionTrackingMode> getDefaultSessionTrackingModes()
-        {
-            // TODO Auto-generated method stub
-            return null;
-        }
-
-        /** 
-         * @see org.eclipse.jetty.server.SessionManager#getEffectiveSessionTrackingModes()
-         */
-        public Set<SessionTrackingMode> getEffectiveSessionTrackingModes()
-        {
-            // TODO Auto-generated method stub
-            return null;
-        }
-
-        /** 
-         * @see org.eclipse.jetty.server.SessionManager#getSessionCookieConfig()
-         */
-        public SessionCookieConfig getSessionCookieConfig()
-        {
-            return null;
-        }
-
-        /** 
-         * @see org.eclipse.jetty.server.SessionManager#isUsingURLs()
-         */
-        public boolean isUsingURLs()
-        {
-            // TODO Auto-generated method stub
-            return false;
-        }
-
-        /** 
-         * @see org.eclipse.jetty.server.SessionManager#setSessionTrackingModes(java.util.Set)
-         */
-        public void setSessionTrackingModes(Set<SessionTrackingMode> sessionTrackingModes)
-        {
-            // TODO Auto-generated method stub
-            
-        }
-
-        private boolean _checkRemote=false;
-
-        public boolean isCheckingRemoteSessionIdEncoding()
-        {
-            return _checkRemote;
-        }
-
-        public void setCheckingRemoteSessionIdEncoding(boolean remote)
-        {
-            _checkRemote=remote;
-        }
-
-        public void changeSessionIdOnAuthentication(HttpServletRequest request, HttpServletResponse response)
-        {
-        }
-    }
-}
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SSLCloseTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SSLCloseTest.java
index 20ff381..a0eb826 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SSLCloseTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SSLCloseTest.java
@@ -16,11 +16,6 @@
 //  ========================================================================
 //
 
-// JettyTest.java --
-//
-// Junit test that shows the Jetty SSL bug.
-//
-
 package org.eclipse.jetty.server.ssl;
 
 import java.io.BufferedReader;
@@ -29,83 +24,53 @@ import java.io.IOException;
 import java.io.InputStreamReader;
 import java.io.OutputStream;
 import java.net.Socket;
-import java.security.cert.CertificateException;
-import java.security.cert.X509Certificate;
+import java.nio.charset.StandardCharsets;
+
 import javax.net.ssl.SSLContext;
-import javax.net.ssl.TrustManager;
-import javax.net.ssl.X509TrustManager;
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import junit.framework.TestCase;
-import org.eclipse.jetty.io.AsyncEndPoint;
-import org.eclipse.jetty.server.Connector;
 import org.eclipse.jetty.server.Request;
 import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
 import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.junit.Test;
 
-/**
- * HttpServer Tester.
- */
-public class SSLCloseTest extends TestCase
+public class SSLCloseTest
 {
-    private static AsyncEndPoint __endp;
-    private static class CredulousTM implements TrustManager, X509TrustManager
-    {
-        public X509Certificate[] getAcceptedIssuers()
-        {
-            return new X509Certificate[]{};
-        }
-
-        public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException
-        {
-        }
-
-        public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException
-        {
-        }
-    }
-
-    private static final TrustManager[] s_dummyTrustManagers=new TrustManager[]  { new CredulousTM() };
-
-    // ~ Methods
-    // ----------------------------------------------------------------
-
-    /**
-     * Feed the server the entire request at once.
-     *
-     * @throws Exception
-     */
+    @Test
     public void testClose() throws Exception
     {
-        Server server=new Server();
-        SslSelectChannelConnector connector=new SslSelectChannelConnector();
-
-        String keystore = System.getProperty("user.dir")+File.separator+"src"+File.separator+"test"+File.separator+"resources"+File.separator+"keystore";
+        File keystore = MavenTestingUtils.getTestResourceFile("keystore");
+        SslContextFactory sslContextFactory = new SslContextFactory();
+        sslContextFactory.setKeyStoreResource(Resource.newResource(keystore));
+        sslContextFactory.setKeyStorePassword("storepwd");
+        sslContextFactory.setKeyManagerPassword("keypwd");
 
+        Server server=new Server();
+        ServerConnector connector=new ServerConnector(server, sslContextFactory);
         connector.setPort(0);
-        connector.getSslContextFactory().setKeyStorePath(keystore);
-        connector.getSslContextFactory().setKeyStorePassword("storepwd");
-        connector.getSslContextFactory().setKeyManagerPassword("keypwd");
 
-        server.setConnectors(new Connector[]
-        { connector });
+        server.addConnector(connector);
         server.setHandler(new WriteHandler());
-
         server.start();
 
-
         SSLContext ctx=SSLContext.getInstance("SSLv3");
-        ctx.init(null,s_dummyTrustManagers,new java.security.SecureRandom());
+        ctx.init(null,SslContextFactory.TRUST_ALL_CERTS,new java.security.SecureRandom());
 
         int port=connector.getLocalPort();
 
-        // System.err.println("write:"+i);
         Socket socket=ctx.getSocketFactory().createSocket("localhost",port);
         OutputStream os=socket.getOutputStream();
 
-        os.write("GET /test HTTP/1.1\r\nHost:test\r\nConnection:close\r\n\r\n".getBytes());
+        os.write((
+            "GET /test HTTP/1.1\r\n"+
+            "Host:test\r\n"+
+            "Connection:close\r\n\r\n").getBytes());
         os.flush();
 
         BufferedReader in =new BufferedReader(new InputStreamReader(socket.getInputStream()));
@@ -113,20 +78,16 @@ public class SSLCloseTest extends TestCase
         String line;
         while ((line=in.readLine())!=null)
         {
-            System.err.println(line);
             if (line.trim().length()==0)
                 break;
         }
 
         Thread.sleep(2000);
-        System.err.println(__endp);
-
-        while ((line=in.readLine())!=null)
-            System.err.println(line);
 
+        while (in.readLine()!=null)
+            Thread.yield();
     }
 
-
     private static class WriteHandler extends AbstractHandler
     {
         public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
@@ -136,7 +97,6 @@ public class SSLCloseTest extends TestCase
                 baseRequest.setHandled(true);
                 response.setStatus(200);
                 response.setHeader("test","value");
-                __endp=(AsyncEndPoint)baseRequest.getConnection().getEndPoint();
 
                 OutputStream out=response.getOutputStream();
 
@@ -146,36 +106,19 @@ public class SSLCloseTest extends TestCase
                 // data=data+data+data+data+data+data+data+data+data+data+data+data+data;
                 // data=data+data+data+data+data+data+data+data+data+data+data+data+data;
                 data=data+data+data+data;
-                byte[] bytes=data.getBytes("UTF-8");
+                byte[] bytes=data.getBytes(StandardCharsets.UTF_8);
 
                 for (int i=0;i<2;i++)
                 {
-                    System.err.println("Write "+i+" "+bytes.length);
+                    // System.err.println("Write "+i+" "+bytes.length);
                     out.write(bytes);
                 }
             }
-            catch(RuntimeException e)
-            {
-                e.printStackTrace();
-                throw e;
-            }
-            catch(IOException e)
-            {
-                e.printStackTrace();
-                throw e;
-            }
-            catch(Error e)
-            {
-                e.printStackTrace();
-                throw e;
-            }
             catch(Throwable e)
             {
                 e.printStackTrace();
                 throw new ServletException(e);
             }
         }
-
     }
-
 }
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SSLEngineTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SSLEngineTest.java
index 9de086d..6f6897b 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SSLEngineTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SSLEngineTest.java
@@ -23,10 +23,10 @@
 
 package org.eclipse.jetty.server.ssl;
 
+import static org.hamcrest.Matchers.greaterThan;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertThat;
-import static org.hamcrest.Matchers.greaterThan;
 
 import java.io.BufferedReader;
 import java.io.IOException;
@@ -36,6 +36,7 @@ import java.io.OutputStream;
 import java.io.PrintWriter;
 import java.net.HttpURLConnection;
 import java.net.Socket;
+import java.net.SocketException;
 import java.net.SocketTimeoutException;
 import java.net.URL;
 
@@ -48,15 +49,17 @@ import javax.servlet.ServletOutputStream;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.HttpConnectionFactory;
 import org.eclipse.jetty.server.Request;
 import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
 import org.eclipse.jetty.server.handler.AbstractHandler;
 import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
 import org.eclipse.jetty.util.IO;
 import org.eclipse.jetty.util.ssl.SslContextFactory;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
+import org.hamcrest.Matchers;
+import org.junit.After;
+import org.junit.Before;
 import org.junit.Test;
 
 /**
@@ -66,15 +69,17 @@ public class SSLEngineTest
 {
     // Useful constants
     private static final String HELLO_WORLD="Hello world. The quick brown fox jumped over the lazy dog. How now brown cow. The rain in spain falls mainly on the plain.\n";
-    private static final String JETTY_VERSION=Server.getVersion();
+    private static final String JETTY_VERSION= Server.getVersion();
     private static final String PROTOCOL_VERSION="2.0";
 
     /** The request. */
     private static final String REQUEST0_HEADER="POST /r0 HTTP/1.1\n"+"Host: localhost\n"+"Content-Type: text/xml\n"+"Content-Length: ";
     private static final String REQUEST1_HEADER="POST /r1 HTTP/1.1\n"+"Host: localhost\n"+"Content-Type: text/xml\n"+"Connection: close\n"+"Content-Length: ";
-    private static final String REQUEST_CONTENT="<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n"
-            +"<requests xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"+"        xsi:noNamespaceSchemaLocation=\"commander.xsd\" version=\""
-            +PROTOCOL_VERSION+"\">\n"+"</requests>";
+    private static final String REQUEST_CONTENT=
+        "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"+
+        "<requests xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"+
+        "        xsi:noNamespaceSchemaLocation=\"commander.xsd\" version=\""+PROTOCOL_VERSION+"\">\n"+
+        "</requests>";
 
     private static final String REQUEST0=REQUEST0_HEADER+REQUEST_CONTENT.getBytes().length+"\n\n"+REQUEST_CONTENT;
     private static final String REQUEST1=REQUEST1_HEADER+REQUEST_CONTENT.getBytes().length+"\n\n"+REQUEST_CONTENT;
@@ -85,42 +90,72 @@ public class SSLEngineTest
 
     private static final int BODY_SIZE=300;
 
-    private static Server server;
-    private static SslSelectChannelConnector connector;
+    private Server server;
+    private ServerConnector connector;
+
 
-    @BeforeClass
-    public static void startServer() throws Exception
+    @Before
+    public void startServer() throws Exception
     {
-        server=new Server();
-        connector=new SslSelectChannelConnector();
         String keystore = MavenTestingUtils.getTestResourceFile("keystore").getAbsolutePath();
+        SslContextFactory sslContextFactory = new SslContextFactory();
+        sslContextFactory.setKeyStorePath(keystore);
+        sslContextFactory.setKeyStorePassword("storepwd");
+        sslContextFactory.setKeyManagerPassword("keypwd");
 
+        server=new Server();
+        HttpConnectionFactory http = new HttpConnectionFactory();
+        http.setInputBufferSize(512);
+        http.getHttpConfiguration().setRequestHeaderSize(512);
+        connector=new ServerConnector(server, sslContextFactory, http);
         connector.setPort(0);
-        SslContextFactory cf = connector.getSslContextFactory();
-        cf.setKeyStorePath(keystore);
-        cf.setKeyStorePassword("storepwd");
-        cf.setKeyManagerPassword("keypwd");
-        connector.setRequestBufferSize(512);
-        connector.setRequestHeaderSize(512);
-
-        server.setConnectors(new Connector[]{connector });
+        connector.getConnectionFactory(HttpConnectionFactory.class).getHttpConfiguration().setSendDateHeader(false);
+
+        server.addConnector(connector);
     }
 
-    @AfterClass
-    public static void stopServer() throws Exception
+    @After
+    public void stopServer() throws Exception
     {
         server.stop();
         server.join();
     }
-    
+
+    @Test
+    public void testHelloWorld() throws Exception
+    {
+        server.setHandler(new HelloWorldHandler());
+        server.start();
+
+        SSLContext ctx=SSLContext.getInstance("TLS");
+        ctx.init(null,SslContextFactory.TRUST_ALL_CERTS,new java.security.SecureRandom());
+
+        int port=connector.getLocalPort();
+
+        Socket client=ctx.getSocketFactory().createSocket("localhost",port);
+        OutputStream os=client.getOutputStream();
+
+        String request =
+            "GET / HTTP/1.1\r\n"+
+            "Host: localhost:"+port+"\r\n"+
+            "Connection: close\r\n"+
+            "\r\n";
+
+        os.write(request.getBytes());
+        os.flush();
+
+        String response = IO.toString(client.getInputStream());
+
+        assertThat(response, Matchers.containsString("200 OK"));
+        assertThat(response,Matchers.containsString(HELLO_WORLD));
+    }
 
     @Test
     public void testBigResponse() throws Exception
     {
-        server.stop();
         server.setHandler(new HelloWorldHandler());
         server.start();
-        
+
         SSLContext ctx=SSLContext.getInstance("TLS");
         ctx.init(null,SslContextFactory.TRUST_ALL_CERTS,new java.security.SecureRandom());
 
@@ -146,12 +181,11 @@ public class SSLEngineTest
     @Test
     public void testRequestJettyHttps() throws Exception
     {
-        server.stop();
         server.setHandler(new HelloWorldHandler());
         server.start();
-        
+
         final int loops=10;
-        final int numConns=10;
+        final int numConns=20;
 
         Socket[] client=new Socket[numConns];
 
@@ -164,7 +198,7 @@ public class SSLEngineTest
         {
             for (int l=0;l<loops;l++)
             {
-                System.err.print('.');
+                // System.err.print('.');
                 try
                 {
                     for (int i=0; i<numConns; ++i)
@@ -191,8 +225,8 @@ public class SSLEngineTest
                         // System.err.println("read:"+i);
                         // Read the response.
                         String responses=readResponse(client[i]);
-                        // Check the response
-                        assertEquals(String.format("responses %d %d",l,i),RESPONSE0+RESPONSE0+RESPONSE1,responses);
+                        // Check the responses
+                        assertEquals(String.format("responses loop=%d connection=%d",l,i),RESPONSE0+RESPONSE0+RESPONSE1,responses);
                     }
                 }
                 finally
@@ -201,7 +235,13 @@ public class SSLEngineTest
                     {
                         if (client[i]!=null)
                         {
-                            client[i].close();
+                            try
+                            {
+                                assertEquals(-1,client[i].getInputStream().read());
+                            }
+                            catch(SocketException e)
+                            {
+                            }
                         }
                     }
                 }
@@ -209,14 +249,13 @@ public class SSLEngineTest
         }
         finally
         {
-            System.err.println();
+            // System.err.println();
         }
     }
 
     @Test
-    public void testServletPost() throws Exception
+    public void testURLConnectionChunkedPost() throws Exception
     {
-        server.stop();
         StreamHandler handler = new StreamHandler();
         server.setHandler(handler);
         server.start();
@@ -232,6 +271,7 @@ public class SSLEngineTest
         {
             ((HttpsURLConnection)conn).setHostnameVerifier(new HostnameVerifier()
             {
+                @Override
                 public boolean verify(String urlHostName, SSLSession session)
                 {
                     return true;
@@ -309,12 +349,13 @@ public class SSLEngineTest
 
     private static class HelloWorldHandler extends AbstractHandler
     {
+        @Override
         public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
         {
             // System.err.println("HANDLE "+request.getRequestURI());
             String ssl_id = (String)request.getAttribute("javax.servlet.request.ssl_session_id");
             assertNotNull(ssl_id);
-
+            
             if (request.getParameter("dump")!=null)
             {
                 ServletOutputStream out=response.getOutputStream();
@@ -359,5 +400,5 @@ public class SSLEngineTest
             response.flushBuffer();
         }
     }
-    
+
 }
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SSLSelectChannelConnectorLoadTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SSLSelectChannelConnectorLoadTest.java
index 2965af1..ad57438 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SSLSelectChannelConnectorLoadTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SSLSelectChannelConnectorLoadTest.java
@@ -42,6 +42,7 @@ import javax.servlet.http.HttpServletResponse;
 
 import org.eclipse.jetty.server.Request;
 import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
 import org.eclipse.jetty.server.handler.AbstractHandler;
 import org.eclipse.jetty.util.ssl.SslContextFactory;
 import org.junit.AfterClass;
@@ -51,30 +52,33 @@ import org.junit.Test;
 public class SSLSelectChannelConnectorLoadTest
 {
     private static Server server;
-    private static SslSelectChannelConnector connector;
+    private static ServerConnector connector;
     private static SSLContext sslContext;
 
     @BeforeClass
     public static void startServer() throws Exception
     {
+        String keystorePath = System.getProperty("basedir", ".") + "/src/test/resources/keystore";
+        SslContextFactory sslContextFactory = new SslContextFactory();
+        sslContextFactory.setKeyStorePath(keystorePath);
+        sslContextFactory.setKeyStorePassword("storepwd");
+        sslContextFactory.setKeyManagerPassword("keypwd");
+        sslContextFactory.setTrustStorePath(keystorePath);
+        sslContextFactory.setTrustStorePassword("storepwd");
+
         server = new Server();
-        connector = new SslSelectChannelConnector();
+        connector = new ServerConnector(server, sslContextFactory);
         server.addConnector(connector);
 
-        String keystorePath = System.getProperty("basedir", ".") + "/src/test/resources/keystore";
-        SslContextFactory cf = connector.getSslContextFactory();
-        cf.setKeyStorePath(keystorePath);
-        cf.setKeyStorePassword("storepwd");
-        cf.setKeyManagerPassword("keypwd");
-        cf.setTrustStore(keystorePath);
-        cf.setTrustStorePassword("storepwd");
-
         server.setHandler(new EmptyHandler());
 
         server.start();
 
         KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
-        keystore.load(new FileInputStream(connector.getKeystore()), "storepwd".toCharArray());
+        try (InputStream stream = new FileInputStream(keystorePath))
+        {
+            keystore.load(stream, "storepwd".toCharArray());
+        }
         TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
         trustManagerFactory.init(keystore);
         sslContext = SSLContext.getInstance("SSL");
@@ -114,13 +118,13 @@ public class SSLSelectChannelConnectorLoadTest
             boolean done = true;
             for (Future task : tasks)
                 done &= task.isDone();
-            System.err.print("\rIterations: " + Worker.totalIterations.get() + "/" + clients * iterations);
+            //System.err.print("\rIterations: " + Worker.totalIterations.get() + "/" + clients * iterations);
             if (done)
                 break;
         }
         long end = System.currentTimeMillis();
-        System.err.println();
-        System.err.println("Elapsed time: " + TimeUnit.MILLISECONDS.toSeconds(end - start) + "s");
+        //System.err.println();
+        //System.err.println("Elapsed time: " + TimeUnit.MILLISECONDS.toSeconds(end - start) + "s");
 
         for (Worker worker : workers)
             worker.close();
@@ -160,13 +164,13 @@ public class SSLSelectChannelConnectorLoadTest
             boolean done = true;
             for (Future task : tasks)
                 done &= task.isDone();
-            System.err.print("\rIterations: " + Worker.totalIterations.get() + "/" + clients * iterations);
+            // System.err.print("\rIterations: " + Worker.totalIterations.get() + "/" + clients * iterations);
             if (done)
                 break;
         }
         long end = System.currentTimeMillis();
-        System.err.println();
-        System.err.println("Elapsed time: " + TimeUnit.MILLISECONDS.toSeconds(end - start) + "s");
+        // System.err.println();
+        // System.err.println("Elapsed time: " + TimeUnit.MILLISECONDS.toSeconds(end - start) + "s");
 
         threadPool.shutdown();
 
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SelectChannelServerSslTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SelectChannelServerSslTest.java
index 1bc782c..5f931f5 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SelectChannelServerSslTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SelectChannelServerSslTest.java
@@ -17,31 +17,40 @@
 //
 
 package org.eclipse.jetty.server.ssl;
+
+import static org.junit.Assert.assertEquals;
+
 import java.io.FileInputStream;
-import java.io.IOException;
+import java.io.InputStream;
 import java.io.OutputStream;
 import java.net.Socket;
+import java.net.SocketException;
+import java.net.URI;
 import java.security.KeyStore;
 import java.util.Arrays;
-import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.Executor;
+
 import javax.net.ssl.HttpsURLConnection;
 import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLEngine;
 import javax.net.ssl.TrustManagerFactory;
 
-import org.eclipse.jetty.io.AsyncEndPoint;
-import org.eclipse.jetty.io.Connection;
-import org.eclipse.jetty.io.nio.SslConnection;
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.LeakTrackingByteBufferPool;
+import org.eclipse.jetty.io.MappedByteBufferPool;
+import org.eclipse.jetty.io.ssl.SslConnection;
+import org.eclipse.jetty.server.AbstractConnectionFactory;
+import org.eclipse.jetty.server.HttpConnectionFactory;
 import org.eclipse.jetty.server.HttpServerTestBase;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.toolchain.test.OS;
+import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.ssl.SslContextFactory;
-import org.junit.BeforeClass;
+import org.eclipse.jetty.util.thread.Scheduler;
+import org.junit.Assume;
+import org.junit.Before;
 import org.junit.Ignore;
 import org.junit.Test;
 
-import static org.hamcrest.Matchers.lessThan;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertThat;
-
 /**
  * HttpServer Tester.
  */
@@ -51,70 +60,107 @@ public class SelectChannelServerSslTest extends HttpServerTestBase
     {
         _scheme="https";
     }
-    
+
     @Override
     protected Socket newSocket(String host, int port) throws Exception
     {
         return __sslContext.getSocketFactory().createSocket(host,port);
     }
-    
-    private static final AtomicInteger _handlecount = new AtomicInteger();
 
-    @BeforeClass
-    public static void init() throws Exception
-    {   
-        SslSelectChannelConnector connector = new SslSelectChannelConnector()
-        {
-            @Override
-            protected SslConnection newSslConnection(AsyncEndPoint endPoint, SSLEngine engine)
-            {
-                return new SslConnection(engine, endPoint)
-                {
-                    @Override
-                    public Connection handle() throws IOException
-                    {
-                        _handlecount.incrementAndGet();
-                        return super.handle();
-                    }
-                };
-            }
-        };
+    @Override
+    public void testFullMethod() throws Exception
+    {
+        // Don't run on Windows (buggy JVM)
+        Assume.assumeTrue(!OS.IS_WINDOWS);
         
+        try
+        {
+            super.testFullMethod();
+        }
+        catch (SocketException e)
+        {
+            Log.getLogger(SslConnection.class).warn("Close overtook 400 response");
+        }
+    }
+
+    @Override
+    public void testFullURI() throws Exception
+    {
+        // Don't run on Windows (buggy JVM)
+        Assume.assumeTrue(!OS.IS_WINDOWS);
+        try
+        {
+            super.testFullURI();
+        }
+        catch (SocketException e)
+        {
+            Log.getLogger(SslConnection.class).warn("Close overtook 400 response");
+        }
+    }
+
+    @Override
+    public void testFullHeader() throws Exception
+    {
+        try
+        {
+            super.testFullHeader();
+        }
+        catch (SocketException e)
+        {
+            Log.getLogger(SslConnection.class).warn("Close overtook 400 response");
+        }
+    }
+
+    @Before
+    public void init() throws Exception
+    {
         String keystorePath = System.getProperty("basedir",".") + "/src/test/resources/keystore";
-        SslContextFactory cf = connector.getSslContextFactory();
-        cf.setKeyStorePath(keystorePath);
-        cf.setKeyStorePassword("storepwd");
-        cf.setKeyManagerPassword("keypwd");
-        cf.setTrustStore(keystorePath);
-        cf.setTrustStorePassword("storepwd");
-        connector.setUseDirectBuffers(true);
-        startServer(connector);
+        SslContextFactory sslContextFactory = new SslContextFactory();
+        sslContextFactory.setKeyStorePath(keystorePath);
+        sslContextFactory.setKeyStorePassword("storepwd");
+        sslContextFactory.setKeyManagerPassword("keypwd");
+        sslContextFactory.setTrustStorePath(keystorePath);
+        sslContextFactory.setTrustStorePassword("storepwd");
+        ByteBufferPool pool = new LeakTrackingByteBufferPool(new MappedByteBufferPool.Tagged());
+        ServerConnector connector = new ServerConnector(_server,(Executor)null,(Scheduler)null,pool, 1, 1, AbstractConnectionFactory.getFactories(sslContextFactory,new HttpConnectionFactory()));
+
         
+        startServer(connector);
 
         KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
-        keystore.load(new FileInputStream(connector.getKeystore()), "storepwd".toCharArray());
+        try (InputStream stream = new FileInputStream(sslContextFactory.getKeyStorePath()))
+        {
+            keystore.load(stream, "storepwd".toCharArray());
+        }
         TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
         trustManagerFactory.init(keystore);
         __sslContext = SSLContext.getInstance("TLS");
         __sslContext.init(null, trustManagerFactory.getTrustManagers(), null);
-        
+
         try
         {
             HttpsURLConnection.setDefaultHostnameVerifier(__hostnameverifier);
-            SSLContext sc = SSLContext.getInstance("TLS"); 
-            sc.init(null, SslContextFactory.TRUST_ALL_CERTS, new java.security.SecureRandom()); 
+            SSLContext sc = SSLContext.getInstance("TLS");
+            sc.init(null, SslContextFactory.TRUST_ALL_CERTS, new java.security.SecureRandom());
             HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
         }
         catch(Exception e)
         {
             e.printStackTrace();
             throw new RuntimeException(e);
-        }   
-    }   
+        }
+    }
+
+    @Override
+    public void testBlockingWhileReadingRequestContent() throws Exception
+    {
+        super.testBlockingWhileReadingRequestContent();
+    }
 
-    public void testRequest2Fragments() throws Exception
+    @Override
+    public void testBlockingWhileWritingResponseContent() throws Exception
     {
-        super.testRequest2Fragments();
+        super.testBlockingWhileWritingResponseContent();
     }
 
     @Test
@@ -128,7 +174,8 @@ public class SelectChannelServerSslTest extends HttpServerTestBase
         // Sort the list
         Arrays.sort(points);
 
-        Socket client=newSocket(HOST,_connector.getLocalPort());
+        URI uri=_server.getURI();
+        Socket client=newSocket(uri.getHost(),uri.getPort());
         try
         {
             OutputStream os=client.getOutputStream();
@@ -138,7 +185,7 @@ public class SelectChannelServerSslTest extends HttpServerTestBase
             // Write out the fragments
             for (int j=0; j<points.length; ++j)
             {
-                int point=points[j];                
+                int point=points[j];
                 os.write(bytes,last,point-last);
                 last=point;
                 os.flush();
@@ -150,7 +197,7 @@ public class SelectChannelServerSslTest extends HttpServerTestBase
             os.write(bytes,last,bytes.length-last);
             os.flush();
             Thread.sleep(PAUSE);
-            
+
 
             // Read the response
             String response=readResponse(client);
@@ -175,12 +222,6 @@ public class SelectChannelServerSslTest extends HttpServerTestBase
     public void testAvailable() throws Exception
     {
     }
-    
-    @Override
-    public void testSuspendedPipeline() throws Exception
-    {
-        _handlecount.set(0);
-        super.testSuspendedPipeline();
-        assertThat(_handlecount.get(),lessThan(50));
-    }
+
+
 }
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SslRenegotiateTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SslRenegotiateTest.java
deleted file mode 100644
index 0495e6a..0000000
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SslRenegotiateTest.java
+++ /dev/null
@@ -1,269 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.server.ssl;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import java.io.IOException;
-import java.net.InetSocketAddress;
-import java.net.SocketAddress;
-import java.nio.ByteBuffer;
-import java.nio.channels.SocketChannel;
-
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLEngine;
-import javax.net.ssl.SSLEngineResult;
-import javax.net.ssl.SSLEngineResult.HandshakeStatus;
-import javax.net.ssl.SSLProtocolException;
-import javax.net.ssl.SSLSession;
-import javax.net.ssl.TrustManager;
-import javax.net.ssl.X509TrustManager;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jetty.io.nio.IndirectNIOBuffer;
-import org.eclipse.jetty.server.Connector;
-import org.eclipse.jetty.server.Request;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.handler.AbstractHandler;
-import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
-import org.eclipse.jetty.util.StringUtil;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.util.ssl.SslContextFactory;
-import org.junit.Test;
-
-public class SslRenegotiateTest
-{
-    private static final Logger LOG = Log.getLogger(SslRenegotiateTest.class);
-
-    private ByteBuffer _outAppB;
-    private ByteBuffer _outPacketB;
-    private ByteBuffer _inAppB;
-    private ByteBuffer _inPacketB;
-    private SocketChannel _socket;
-    private SSLEngine _engine;
-
-    @Test
-    public void testRenegNIO() throws Exception
-    {
-        // TODO This test breaks on JVMs with the fix
-//        doRequests(new SslSelectChannelConnector(),true);
-    }
-
-    @Test
-    public void testNoRenegNIO() throws Exception
-    {
-        doRequests(new SslSelectChannelConnector(),false);
-    }
-
-    @Test
-    public void testRenegBIO() throws Exception
-    {
-        // TODO - this test is too non deterministic due to call back timing
-//        doRequests(new SslSocketConnector(),true);
-    }
-
-    @Test
-    public void testNoRenegBIO() throws Exception
-    {
-        // TODO - this test is too non deterministic due to call back timing
-//        doRequests(new SslSocketConnector(),false);
-    }
-
-    private void doRequests(SslConnector connector, boolean reneg) throws Exception
-    {
-        Server server=new Server();
-        try
-        {
-            String keystore = MavenTestingUtils.getTestResourceFile("keystore").getAbsolutePath();
-            connector.setPort(0);
-            SslContextFactory cf = connector.getSslContextFactory();
-            cf.setKeyStorePath(keystore);
-            cf.setKeyStorePassword("storepwd");
-            cf.setKeyManagerPassword("keypwd");
-            cf.setAllowRenegotiate(reneg);
-
-            server.setConnectors(new Connector[] { connector });
-            server.setHandler(new HelloWorldHandler());
-
-            server.start();
-
-            SocketAddress addr = new InetSocketAddress("localhost",connector.getLocalPort());
-            _socket = SocketChannel.open(addr);
-            _socket.configureBlocking(true);
-
-            SSLContext context=SSLContext.getInstance("SSL");
-            context.init( null, SslContextFactory.TRUST_ALL_CERTS, new java.security.SecureRandom() );
-
-            _engine = context.createSSLEngine();
-            _engine.setUseClientMode(true);
-            SSLSession session=_engine.getSession();
-
-            _outAppB = ByteBuffer.allocate(session.getApplicationBufferSize());
-            _outPacketB = ByteBuffer.allocate(session.getPacketBufferSize());
-            _inAppB = ByteBuffer.allocate(session.getApplicationBufferSize());
-            _inPacketB = ByteBuffer.allocate(session.getPacketBufferSize());
-
-
-            _outAppB.put("GET /1 HTTP/1.1\r\nHost: localhost\r\n\r\n".getBytes(StringUtil.__ISO_8859_1));
-            _outAppB.flip();
-
-            _engine.beginHandshake();
-
-            runHandshake();
-
-            doWrap();
-            doUnwrap();
-            _inAppB.flip();
-            String response=new IndirectNIOBuffer(_inAppB,true).toString();
-            // System.err.println(response);
-            assertTrue(response.startsWith("HTTP/1.1 200 OK"));
-
-            if (response.indexOf("HELLO WORLD")<0)
-            {
-                _inAppB.clear();
-                doUnwrap();
-                _inAppB.flip();
-                response=new IndirectNIOBuffer(_inAppB,true).toString();
-            }
-
-            assertTrue(response.indexOf("HELLO WORLD")>=0);
-
-            _inAppB.clear();
-            _outAppB.clear();
-            _outAppB.put("GET /2 HTTP/1.1\r\nHost: localhost\r\n\r\n".getBytes(StringUtil.__ISO_8859_1));
-            _outAppB.flip();
-
-            try
-            {
-                session.invalidate();
-                _engine.beginHandshake();
-                runHandshake();
-
-                doWrap();
-                doUnwrap();
-                _inAppB.flip();
-                response=new IndirectNIOBuffer(_inAppB,true).toString();
-                assertTrue(response.startsWith("HTTP/1.1 200 OK"));
-                assertTrue(response.indexOf("HELLO WORLD")>0);
-
-                assertTrue(reneg);
-            }
-            catch(IOException e)
-            {
-                if (!(e instanceof SSLProtocolException))
-                {
-                    if (reneg)
-                        LOG.warn(e);
-                    assertFalse(reneg);
-                }
-            }
-        }
-        finally
-        {
-            server.stop();
-            server.join();
-        }
-    }
-
-    void runHandshake() throws Exception
-    {
-        while (true)
-        {
-            switch(_engine.getHandshakeStatus())
-            {
-                case NEED_TASK:
-                {
-                    //System.err.println("running task");
-                    _engine.getDelegatedTask().run();
-                    break;
-                }
-
-                case NEED_WRAP:
-                {
-                    doWrap();
-                    break;
-                }
-
-                case NEED_UNWRAP:
-                {
-                    doUnwrap();
-                    break;
-                }
-
-                default:
-                    return;
-            }
-        }
-    }
-
-    private void doWrap() throws Exception
-    {
-        _engine.wrap(_outAppB,_outPacketB);
-//        System.err.println("wrapped "+result.bytesConsumed()+" to "+result.bytesProduced());
-        _outPacketB.flip();
-        while (_outPacketB.hasRemaining())
-        {
-            int p = _outPacketB.remaining();
-            int l =_socket.write(_outPacketB);
-            // System.err.println("wrote "+l+" of "+p);
-        }
-        _outPacketB.clear();
-    }
-
-    private void doUnwrap() throws Exception
-    {
-        _inPacketB.clear();
-        int l=_socket.read(_inPacketB);
-        // System.err.println("read "+l);
-        if (l<0)
-            throw new IOException("EOF");
-
-        _inPacketB.flip();
-
-        SSLEngineResult result;
-        do
-        {
-            result =_engine.unwrap(_inPacketB,_inAppB);
-//            System.err.println("unwrapped "+result.bytesConsumed()+" to "+result.bytesProduced()+" "+_engine.getHandshakeStatus());
-
-        }
-        while(result.bytesConsumed()>0 &&
-              _inPacketB.remaining()>0 &&
-              (_engine.getHandshakeStatus()==HandshakeStatus.NEED_UNWRAP || _engine.getHandshakeStatus()==HandshakeStatus.NOT_HANDSHAKING));
-    }
-
-    private static class HelloWorldHandler extends AbstractHandler
-    {
-        public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
-        {
-            baseRequest.setHandled(true);
-            //System.err.println("HELLO WORLD HANDLING");
-
-//            System.err.println("hello "+baseRequest.getUri());
-            byte[] b=("HELLO WORLD "+baseRequest.getUri()).getBytes(StringUtil.__UTF8);
-            response.setContentLength(b.length);
-            response.getOutputStream().write(b);
-            response.getOutputStream().flush();
-        }
-    }
-}
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SslSelectChannelTimeoutTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SslSelectChannelTimeoutTest.java
index 3f7e6a7..4a409c4 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SslSelectChannelTimeoutTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SslSelectChannelTimeoutTest.java
@@ -19,6 +19,7 @@
 package org.eclipse.jetty.server.ssl;
 
 import java.io.FileInputStream;
+import java.io.InputStream;
 import java.net.Socket;
 import java.security.KeyStore;
 
@@ -26,40 +27,44 @@ import javax.net.ssl.SSLContext;
 import javax.net.ssl.TrustManagerFactory;
 
 import org.eclipse.jetty.server.ConnectorTimeoutTest;
+import org.eclipse.jetty.server.ServerConnector;
 import org.eclipse.jetty.util.ssl.SslContextFactory;
-import org.junit.BeforeClass;
+import org.junit.Before;
 
 public class SslSelectChannelTimeoutTest extends ConnectorTimeoutTest
 {
     static SSLContext __sslContext;
-    
+
     @Override
     protected Socket newSocket(String host, int port) throws Exception
     {
         return __sslContext.getSocketFactory().createSocket(host,port);
     }
 
-    @BeforeClass
-    public static void init() throws Exception
-    {   
-        SslSelectChannelConnector connector = new SslSelectChannelConnector();
-        connector.setMaxIdleTime(MAX_IDLE_TIME); //250 msec max idle
+    @Before
+    public void init() throws Exception
+    {
         String keystorePath = System.getProperty("basedir",".") + "/src/test/resources/keystore";
-        SslContextFactory cf = connector.getSslContextFactory();
-        cf.setKeyStorePath(keystorePath);
-        cf.setKeyStorePassword("storepwd");
-        cf.setKeyManagerPassword("keypwd");
-        cf.setTrustStore(keystorePath);
-        cf.setTrustStorePassword("storepwd");
+        SslContextFactory sslContextFactory = new SslContextFactory();
+        sslContextFactory.setKeyStorePath(keystorePath);
+        sslContextFactory.setKeyStorePassword("storepwd");
+        sslContextFactory.setKeyManagerPassword("keypwd");
+        sslContextFactory.setTrustStorePath(keystorePath);
+        sslContextFactory.setTrustStorePassword("storepwd");
+        ServerConnector connector = new ServerConnector(_server, 1, 1, sslContextFactory);
+        connector.setIdleTimeout(MAX_IDLE_TIME); //250 msec max idle
         startServer(connector);
-        
+
         KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
-        keystore.load(new FileInputStream(connector.getKeystore()), "storepwd".toCharArray());
+        try (InputStream stream = new FileInputStream(keystorePath))
+        {
+            keystore.load(stream, "storepwd".toCharArray());
+        }
         TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
         trustManagerFactory.init(keystore);
         __sslContext = SSLContext.getInstance("SSL");
         __sslContext.init(null, trustManagerFactory.getTrustManagers(), null);
-        
+
     }
 
 }
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SslSocketServerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SslSocketServerTest.java
deleted file mode 100644
index bc1b866..0000000
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SslSocketServerTest.java
+++ /dev/null
@@ -1,110 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.server.ssl;
-import java.io.FileInputStream;
-import java.net.Socket;
-import java.net.SocketException;
-import java.security.KeyStore;
-
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLSocket;
-import javax.net.ssl.TrustManagerFactory;
-
-import org.eclipse.jetty.server.HttpServerTestBase;
-import org.eclipse.jetty.util.ssl.SslContextFactory;
-import org.junit.BeforeClass;
-import org.junit.Ignore;
-import org.junit.Test;
-
-/**
- * HttpServer Tester.
- */
-public class SslSocketServerTest extends HttpServerTestBase
-{
-    static SSLContext __sslContext;
-    {
-        _scheme="https";
-    }
-    
-    @Override
-    protected Socket newSocket(String host, int port) throws Exception
-    {
-        SSLSocket socket = (SSLSocket)__sslContext.getSocketFactory().createSocket(host,port);
-        socket.setEnabledProtocols(new String[] {"TLSv1"});
-        return socket;
-    }
-    
-
-    @BeforeClass
-    public static void init() throws Exception
-    {   
-        SslSocketConnector connector = new SslSocketConnector();
-        String keystorePath = System.getProperty("basedir",".") + "/src/test/resources/keystore";
-        SslContextFactory cf = connector.getSslContextFactory();
-        cf.setKeyStorePath(keystorePath);
-        cf.setKeyStorePassword("storepwd");
-        cf.setKeyManagerPassword("keypwd");
-        cf.setTrustStore(keystorePath);
-        cf.setTrustStorePassword("storepwd");
-        startServer(connector);
-        
-
-        KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
-        keystore.load(new FileInputStream(connector.getKeystore()), "storepwd".toCharArray());
-        TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
-        trustManagerFactory.init(keystore);
-        __sslContext = SSLContext.getInstance("TLSv1");
-        __sslContext.init(null, trustManagerFactory.getTrustManagers(), null);
-        
-
-    }
-
-    @Override
-    @Test
-    @Ignore("Override and ignore this test as SSLSocket.shutdownOutput() is not supported, " +
-            "but shutdownOutput() is needed by the test.")
-    public void testInterruptedRequest(){}
-
-    @Override
-    @Test
-    public void testFlush() throws Exception
-    {
-        // TODO this test uses URL, so noop for now
-    }
-
-
-    @Override
-    @Ignore
-    public void testAvailable() throws Exception
-    {
-    }
-
-    @Override
-    public void testFull() throws Exception
-    {
-        try
-        {
-            super.testFull();
-        }
-        catch(SocketException e)
-        {
-            // For SSL Sockets, the response is closed before the 400 is sent???
-        }
-    }
-}
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SslSocketTimeoutTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SslSocketTimeoutTest.java
deleted file mode 100644
index 1f6256b..0000000
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SslSocketTimeoutTest.java
+++ /dev/null
@@ -1,69 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.server.ssl;
-
-import java.io.FileInputStream;
-import java.net.Socket;
-import java.security.KeyStore;
-
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLSocket;
-import javax.net.ssl.TrustManagerFactory;
-
-import org.eclipse.jetty.server.ConnectorTimeoutTest;
-import org.eclipse.jetty.util.ssl.SslContextFactory;
-import org.junit.BeforeClass;
-
-public class SslSocketTimeoutTest extends ConnectorTimeoutTest
-{
-    static SSLContext __sslContext;
-    
-    @Override
-    protected Socket newSocket(String host, int port) throws Exception
-    {
-        SSLSocket socket = (SSLSocket)__sslContext.getSocketFactory().createSocket(host,port);
-        socket.setEnabledProtocols(new String[] {"TLSv1"});
-        return socket;
-    }
-
-    @BeforeClass
-    public static void init() throws Exception
-    {   
-        SslSocketConnector connector = new SslSocketConnector();
-        connector.setMaxIdleTime(MAX_IDLE_TIME); //250 msec max idle
-        String keystorePath = System.getProperty("basedir",".") + "/src/test/resources/keystore";
-        SslContextFactory cf = connector.getSslContextFactory();
-        cf.setKeyStorePath(keystorePath);
-        cf.setKeyStorePassword("storepwd");
-        cf.setKeyManagerPassword("keypwd");
-        cf.setTrustStore(keystorePath);
-        cf.setTrustStorePassword("storepwd");
-        startServer(connector);
-        
-
-        KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
-        keystore.load(new FileInputStream(connector.getKeystore()), "storepwd".toCharArray());
-        TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
-        trustManagerFactory.init(keystore);
-        __sslContext = SSLContext.getInstance("TLSv1");
-        __sslContext.init(null, trustManagerFactory.getTrustManagers(), null);
-       
-    }
-
-}
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SslUploadTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SslUploadTest.java
index 764a76a..2d57587 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SslUploadTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SslUploadTest.java
@@ -21,13 +21,13 @@ package org.eclipse.jetty.server.ssl;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
+import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.security.KeyStore;
 import java.util.Arrays;
-import java.util.concurrent.TimeUnit;
 
 import javax.net.ssl.SSLContext;
 import javax.net.ssl.SSLSocket;
@@ -38,11 +38,15 @@ import javax.servlet.http.HttpServletResponse;
 
 import org.eclipse.jetty.server.Request;
 import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.SslConnectionFactory;
 import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
 import org.eclipse.jetty.util.IO;
 import org.eclipse.jetty.util.ssl.SslContextFactory;
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
+import org.junit.Ignore;
 import org.junit.Test;
 
 /**
@@ -50,24 +54,25 @@ import org.junit.Test;
 public class SslUploadTest
 {
     private static Server server;
-    private static SslSelectChannelConnector connector;
+    private static ServerConnector connector;
     private static int total;
 
     @BeforeClass
     public static void startServer() throws Exception
     {
+        File keystore = MavenTestingUtils.getTestResourceFile("keystore");
+
+        SslContextFactory sslContextFactory = new SslContextFactory();
+        sslContextFactory.setKeyStorePath(keystore.getAbsolutePath());
+        sslContextFactory.setKeyStorePassword("storepwd");
+        sslContextFactory.setKeyManagerPassword("keypwd");
+        sslContextFactory.setTrustStorePath(keystore.getAbsolutePath());
+        sslContextFactory.setTrustStorePassword("storepwd");
+
         server = new Server();
-        connector = new SslSelectChannelConnector();
+        connector = new ServerConnector(server, sslContextFactory);
         server.addConnector(connector);
 
-        String keystorePath = System.getProperty("basedir",".") + "/src/test/resources/keystore";
-        SslContextFactory cf = connector.getSslContextFactory();
-        cf.setKeyStorePath(keystorePath);
-        cf.setKeyStorePassword("storepwd");
-        cf.setKeyManagerPassword("keypwd");
-        cf.setTrustStore(keystorePath);
-        cf.setTrustStorePassword("storepwd");
-
         server.setHandler(new EmptyHandler());
 
         server.start();
@@ -81,10 +86,15 @@ public class SslUploadTest
     }
 
     @Test
+    @Ignore
     public void test() throws Exception
     {
         KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
-        keystore.load(new FileInputStream(connector.getKeystore()), "storepwd".toCharArray());
+        SslContextFactory ctx=connector.getConnectionFactory(SslConnectionFactory.class).getSslContextFactory();
+        try (InputStream stream = new FileInputStream(ctx.getKeyStorePath()))
+        {
+            keystore.load(stream, "storepwd".toCharArray());
+        }
         TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
         trustManagerFactory.init(keystore);
         SSLContext sslContext = SSLContext.getInstance("SSL");
@@ -136,13 +146,14 @@ public class SslUploadTest
         assertTrue (response.indexOf("200")>0);
         // System.err.println(response);
 
-        long end = System.nanoTime();
-        System.out.println("upload time: " + TimeUnit.NANOSECONDS.toMillis(end - start));
+        // long end = System.nanoTime();
+        // System.out.println("upload time: " + TimeUnit.NANOSECONDS.toMillis(end - start));
         assertEquals(requestContent.length, total);
     }
 
     private static class EmptyHandler extends AbstractHandler
     {
+        @Override
         public void handle(String path, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException
         {
             request.setHandled(true);
diff --git a/jetty-server/src/test/resources/jetty-logging.properties b/jetty-server/src/test/resources/jetty-logging.properties
index d8439d1..adf68c7 100644
--- a/jetty-server/src/test/resources/jetty-logging.properties
+++ b/jetty-server/src/test/resources/jetty-logging.properties
@@ -1,3 +1,3 @@
-# Setup default logging implementation for during testing
 org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
-org.eclipse.jetty.server.LEVEL=INFO
+#org.eclipse.jetty.LEVEL=DEBUG
+#org.eclipse.jetty.server.LEVEL=DEBUG
diff --git a/jetty-server/src/test/resources/simple/big.txt b/jetty-server/src/test/resources/simple/big.txt
new file mode 100644
index 0000000..a6d57f0
--- /dev/null
+++ b/jetty-server/src/test/resources/simple/big.txt
@@ -0,0 +1,400 @@
+     1	This is a big file
+     2	This is a big file
+     3	This is a big file
+     4	This is a big file
+     5	This is a big file
+     6	This is a big file
+     7	This is a big file
+     8	This is a big file
+     9	This is a big file
+    10	This is a big file
+    11	This is a big file
+    12	This is a big file
+    13	This is a big file
+    14	This is a big file
+    15	This is a big file
+    16	This is a big file
+    17	This is a big file
+    18	This is a big file
+    19	This is a big file
+    20	This is a big file
+    21	This is a big file
+    22	This is a big file
+    23	This is a big file
+    24	This is a big file
+    25	This is a big file
+    26	This is a big file
+    27	This is a big file
+    28	This is a big file
+    29	This is a big file
+    30	This is a big file
+    31	This is a big file
+    32	This is a big file
+    33	This is a big file
+    34	This is a big file
+    35	This is a big file
+    36	This is a big file
+    37	This is a big file
+    38	This is a big file
+    39	This is a big file
+    40	This is a big file
+    41	This is a big file
+    42	This is a big file
+    43	This is a big file
+    44	This is a big file
+    45	This is a big file
+    46	This is a big file
+    47	This is a big file
+    48	This is a big file
+    49	This is a big file
+    50	This is a big file
+    51	This is a big file
+    52	This is a big file
+    53	This is a big file
+    54	This is a big file
+    55	This is a big file
+    56	This is a big file
+    57	This is a big file
+    58	This is a big file
+    59	This is a big file
+    60	This is a big file
+    61	This is a big file
+    62	This is a big file
+    63	This is a big file
+    64	This is a big file
+    65	This is a big file
+    66	This is a big file
+    67	This is a big file
+    68	This is a big file
+    69	This is a big file
+    70	This is a big file
+    71	This is a big file
+    72	This is a big file
+    73	This is a big file
+    74	This is a big file
+    75	This is a big file
+    76	This is a big file
+    77	This is a big file
+    78	This is a big file
+    79	This is a big file
+    80	This is a big file
+    81	This is a big file
+    82	This is a big file
+    83	This is a big file
+    84	This is a big file
+    85	This is a big file
+    86	This is a big file
+    87	This is a big file
+    88	This is a big file
+    89	This is a big file
+    90	This is a big file
+    91	This is a big file
+    92	This is a big file
+    93	This is a big file
+    94	This is a big file
+    95	This is a big file
+    96	This is a big file
+    97	This is a big file
+    98	This is a big file
+    99	This is a big file
+   100	This is a big file
+   101	This is a big file
+   102	This is a big file
+   103	This is a big file
+   104	This is a big file
+   105	This is a big file
+   106	This is a big file
+   107	This is a big file
+   108	This is a big file
+   109	This is a big file
+   110	This is a big file
+   111	This is a big file
+   112	This is a big file
+   113	This is a big file
+   114	This is a big file
+   115	This is a big file
+   116	This is a big file
+   117	This is a big file
+   118	This is a big file
+   119	This is a big file
+   120	This is a big file
+   121	This is a big file
+   122	This is a big file
+   123	This is a big file
+   124	This is a big file
+   125	This is a big file
+   126	This is a big file
+   127	This is a big file
+   128	This is a big file
+   129	This is a big file
+   130	This is a big file
+   131	This is a big file
+   132	This is a big file
+   133	This is a big file
+   134	This is a big file
+   135	This is a big file
+   136	This is a big file
+   137	This is a big file
+   138	This is a big file
+   139	This is a big file
+   140	This is a big file
+   141	This is a big file
+   142	This is a big file
+   143	This is a big file
+   144	This is a big file
+   145	This is a big file
+   146	This is a big file
+   147	This is a big file
+   148	This is a big file
+   149	This is a big file
+   150	This is a big file
+   151	This is a big file
+   152	This is a big file
+   153	This is a big file
+   154	This is a big file
+   155	This is a big file
+   156	This is a big file
+   157	This is a big file
+   158	This is a big file
+   159	This is a big file
+   160	This is a big file
+   161	This is a big file
+   162	This is a big file
+   163	This is a big file
+   164	This is a big file
+   165	This is a big file
+   166	This is a big file
+   167	This is a big file
+   168	This is a big file
+   169	This is a big file
+   170	This is a big file
+   171	This is a big file
+   172	This is a big file
+   173	This is a big file
+   174	This is a big file
+   175	This is a big file
+   176	This is a big file
+   177	This is a big file
+   178	This is a big file
+   179	This is a big file
+   180	This is a big file
+   181	This is a big file
+   182	This is a big file
+   183	This is a big file
+   184	This is a big file
+   185	This is a big file
+   186	This is a big file
+   187	This is a big file
+   188	This is a big file
+   189	This is a big file
+   190	This is a big file
+   191	This is a big file
+   192	This is a big file
+   193	This is a big file
+   194	This is a big file
+   195	This is a big file
+   196	This is a big file
+   197	This is a big file
+   198	This is a big file
+   199	This is a big file
+   200	This is a big file
+   201	This is a big file
+   202	This is a big file
+   203	This is a big file
+   204	This is a big file
+   205	This is a big file
+   206	This is a big file
+   207	This is a big file
+   208	This is a big file
+   209	This is a big file
+   210	This is a big file
+   211	This is a big file
+   212	This is a big file
+   213	This is a big file
+   214	This is a big file
+   215	This is a big file
+   216	This is a big file
+   217	This is a big file
+   218	This is a big file
+   219	This is a big file
+   220	This is a big file
+   221	This is a big file
+   222	This is a big file
+   223	This is a big file
+   224	This is a big file
+   225	This is a big file
+   226	This is a big file
+   227	This is a big file
+   228	This is a big file
+   229	This is a big file
+   230	This is a big file
+   231	This is a big file
+   232	This is a big file
+   233	This is a big file
+   234	This is a big file
+   235	This is a big file
+   236	This is a big file
+   237	This is a big file
+   238	This is a big file
+   239	This is a big file
+   240	This is a big file
+   241	This is a big file
+   242	This is a big file
+   243	This is a big file
+   244	This is a big file
+   245	This is a big file
+   246	This is a big file
+   247	This is a big file
+   248	This is a big file
+   249	This is a big file
+   250	This is a big file
+   251	This is a big file
+   252	This is a big file
+   253	This is a big file
+   254	This is a big file
+   255	This is a big file
+   256	This is a big file
+   257	This is a big file
+   258	This is a big file
+   259	This is a big file
+   260	This is a big file
+   261	This is a big file
+   262	This is a big file
+   263	This is a big file
+   264	This is a big file
+   265	This is a big file
+   266	This is a big file
+   267	This is a big file
+   268	This is a big file
+   269	This is a big file
+   270	This is a big file
+   271	This is a big file
+   272	This is a big file
+   273	This is a big file
+   274	This is a big file
+   275	This is a big file
+   276	This is a big file
+   277	This is a big file
+   278	This is a big file
+   279	This is a big file
+   280	This is a big file
+   281	This is a big file
+   282	This is a big file
+   283	This is a big file
+   284	This is a big file
+   285	This is a big file
+   286	This is a big file
+   287	This is a big file
+   288	This is a big file
+   289	This is a big file
+   290	This is a big file
+   291	This is a big file
+   292	This is a big file
+   293	This is a big file
+   294	This is a big file
+   295	This is a big file
+   296	This is a big file
+   297	This is a big file
+   298	This is a big file
+   299	This is a big file
+   300	This is a big file
+   301	This is a big file
+   302	This is a big file
+   303	This is a big file
+   304	This is a big file
+   305	This is a big file
+   306	This is a big file
+   307	This is a big file
+   308	This is a big file
+   309	This is a big file
+   310	This is a big file
+   311	This is a big file
+   312	This is a big file
+   313	This is a big file
+   314	This is a big file
+   315	This is a big file
+   316	This is a big file
+   317	This is a big file
+   318	This is a big file
+   319	This is a big file
+   320	This is a big file
+   321	This is a big file
+   322	This is a big file
+   323	This is a big file
+   324	This is a big file
+   325	This is a big file
+   326	This is a big file
+   327	This is a big file
+   328	This is a big file
+   329	This is a big file
+   330	This is a big file
+   331	This is a big file
+   332	This is a big file
+   333	This is a big file
+   334	This is a big file
+   335	This is a big file
+   336	This is a big file
+   337	This is a big file
+   338	This is a big file
+   339	This is a big file
+   340	This is a big file
+   341	This is a big file
+   342	This is a big file
+   343	This is a big file
+   344	This is a big file
+   345	This is a big file
+   346	This is a big file
+   347	This is a big file
+   348	This is a big file
+   349	This is a big file
+   350	This is a big file
+   351	This is a big file
+   352	This is a big file
+   353	This is a big file
+   354	This is a big file
+   355	This is a big file
+   356	This is a big file
+   357	This is a big file
+   358	This is a big file
+   359	This is a big file
+   360	This is a big file
+   361	This is a big file
+   362	This is a big file
+   363	This is a big file
+   364	This is a big file
+   365	This is a big file
+   366	This is a big file
+   367	This is a big file
+   368	This is a big file
+   369	This is a big file
+   370	This is a big file
+   371	This is a big file
+   372	This is a big file
+   373	This is a big file
+   374	This is a big file
+   375	This is a big file
+   376	This is a big file
+   377	This is a big file
+   378	This is a big file
+   379	This is a big file
+   380	This is a big file
+   381	This is a big file
+   382	This is a big file
+   383	This is a big file
+   384	This is a big file
+   385	This is a big file
+   386	This is a big file
+   387	This is a big file
+   388	This is a big file
+   389	This is a big file
+   390	This is a big file
+   391	This is a big file
+   392	This is a big file
+   393	This is a big file
+   394	This is a big file
+   395	This is a big file
+   396	This is a big file
+   397	This is a big file
+   398	This is a big file
+   399	This is a big file
+   400	This is a big file
diff --git a/jetty-servlet/pom.xml b/jetty-servlet/pom.xml
index 067835a..8f3192c 100644
--- a/jetty-servlet/pom.xml
+++ b/jetty-servlet/pom.xml
@@ -3,7 +3,7 @@
   <parent>
     <artifactId>jetty-project</artifactId>
     <groupId>org.eclipse.jetty</groupId>
-    <version>8.1.17.v20150415</version>
+    <version>9.2.13.v20150730</version>
   </parent>
   <modelVersion>4.0.0</modelVersion>
   <artifactId>jetty-servlet</artifactId>
@@ -26,7 +26,8 @@
             </goals>
             <configuration>
               <instructions>
-                <Import-Package>javax.servlet.*;version="2.6.0",org.eclipse.jetty.jmx.*;version="8.0";resolution:=optional,*</Import-Package>
+                <Import-Package>javax.servlet.*;version="[2.6.0,3.2)",org.eclipse.jetty.jmx.*;version="9.1";resolution:=optional,*</Import-Package>
+                <_nouses>true</_nouses>
               </instructions>
             </configuration>
            </execution>
@@ -53,6 +54,23 @@
         </executions>
       </plugin>
       <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-assembly-plugin</artifactId>
+        <executions>
+          <execution>
+            <phase>package</phase>
+            <goals>
+              <goal>single</goal>
+            </goals>
+            <configuration>
+              <descriptorRefs>
+                <descriptorRef>config</descriptorRef>
+              </descriptorRefs>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
         <groupId>org.codehaus.mojo</groupId>
         <artifactId>findbugs-maven-plugin</artifactId>
         <configuration>
diff --git a/jetty-servlet/src/main/config/modules/servlet.mod b/jetty-servlet/src/main/config/modules/servlet.mod
new file mode 100644
index 0000000..fdb65c5
--- /dev/null
+++ b/jetty-servlet/src/main/config/modules/servlet.mod
@@ -0,0 +1,9 @@
+#
+# Jetty Servlet Module
+#
+
+[depend]
+server
+
+[lib]
+lib/jetty-servlet-${jetty.version}.jar
diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/BaseHolder.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/BaseHolder.java
new file mode 100644
index 0000000..feabdea
--- /dev/null
+++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/BaseHolder.java
@@ -0,0 +1,209 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlet;
+
+import java.io.IOException;
+
+import javax.servlet.ServletContext;
+import javax.servlet.UnavailableException;
+
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.util.Loader;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.component.Dumpable;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * AbstractHolder
+ * 
+ * Base class for all servlet-related classes that may be lazily instantiated  (eg servlet, filter, 
+ * listener), and/or require metadata to be held regarding their origin 
+ * (web.xml, annotation, programmatic api etc).
+ * 
+ */
+public abstract class BaseHolder<T> extends AbstractLifeCycle implements Dumpable
+{
+    private static final Logger LOG = Log.getLogger(BaseHolder.class);
+    
+    
+    public enum Source { EMBEDDED, JAVAX_API, DESCRIPTOR, ANNOTATION };
+    
+    final protected Source _source;
+    protected transient Class<? extends T> _class;
+    protected String _className;
+    protected boolean _extInstance;
+    protected ServletHandler _servletHandler;
+    
+    /* ---------------------------------------------------------------- */
+    protected BaseHolder(Source source)
+    {
+        _source=source;
+    }
+
+    /* ------------------------------------------------------------ */
+    public Source getSource()
+    {
+        return _source;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Do any setup necessary after starting
+     * @throws Exception
+     */
+    public void initialize()
+    throws Exception
+    {
+        if (!isStarted())
+            throw new IllegalStateException("Not started: "+this);
+    }
+
+    /* ------------------------------------------------------------ */
+    @SuppressWarnings("unchecked")
+    @Override
+    public void doStart()
+        throws Exception
+    {
+        //if no class already loaded and no classname, make permanently unavailable
+        if (_class==null && (_className==null || _className.equals("")))
+            throw new UnavailableException("No class in holder");
+        
+        //try to load class
+        if (_class==null)
+        {
+            try
+            {
+                _class=Loader.loadClass(Holder.class, _className);
+                if(LOG.isDebugEnabled())
+                    LOG.debug("Holding {} from {}",_class,_class.getClassLoader());
+            }
+            catch (Exception e)
+            {
+                LOG.warn(e);
+                throw new UnavailableException(e.getMessage());
+            }
+        }
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public void doStop()
+        throws Exception
+    {
+        if (!_extInstance)
+            _class=null;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    @ManagedAttribute(value="Class Name", readonly=true)
+    public String getClassName()
+    {
+        return _className;
+    }
+
+    /* ------------------------------------------------------------ */
+    public Class<? extends T> getHeldClass()
+    {
+        return _class;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the servletHandler.
+     */
+    public ServletHandler getServletHandler()
+    {
+        return _servletHandler;
+    }
+    
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param servletHandler The {@link ServletHandler} that will handle requests dispatched to this servlet.
+     */
+    public void setServletHandler(ServletHandler servletHandler)
+    {
+        _servletHandler = servletHandler;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param className The className to set.
+     */
+    public void setClassName(String className)
+    {
+        _className = className;
+        _class=null;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param held The class to hold
+     */
+    public void setHeldClass(Class<? extends T> held)
+    {
+        _class=held;
+        if (held!=null)
+        {
+            _className=held.getName();
+        }
+    }
+    
+
+    /* ------------------------------------------------------------ */
+    protected void illegalStateIfContextStarted()
+    {
+        if (_servletHandler!=null)
+        {
+            ServletContext context=_servletHandler.getServletContext();
+            if ((context instanceof ContextHandler.Context) && ((ContextHandler.Context)context).getContextHandler().isStarted())
+                throw new IllegalStateException("Started");
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return True if this holder was created for a specific instance.
+     */
+    public boolean isInstance()
+    {
+        return _extInstance;
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public void dump(Appendable out, String indent) throws IOException
+    {
+        out.append(toString())
+        .append(" - ").append(AbstractLifeCycle.getState(this)).append("\n");
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String dump()
+    {
+        return ContainerLifeCycle.dump(this);
+    }
+}
diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java
index dd0073b..56df44c 100644
--- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java
+++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java
@@ -24,11 +24,13 @@ import java.io.InputStream;
 import java.io.OutputStream;
 import java.net.MalformedURLException;
 import java.net.URL;
+import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.Enumeration;
 import java.util.List;
-import java.util.Map;
+import java.util.StringTokenizer;
 
+import javax.servlet.AsyncContext;
 import javax.servlet.RequestDispatcher;
 import javax.servlet.ServletContext;
 import javax.servlet.ServletException;
@@ -38,31 +40,27 @@ import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
 import org.eclipse.jetty.http.HttpContent;
+import org.eclipse.jetty.http.HttpField;
 import org.eclipse.jetty.http.HttpFields;
-import org.eclipse.jetty.http.HttpHeaderValues;
-import org.eclipse.jetty.http.HttpHeaders;
-import org.eclipse.jetty.http.HttpMethods;
+import org.eclipse.jetty.http.HttpGenerator.CachedHttpField;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpMethod;
 import org.eclipse.jetty.http.MimeTypes;
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.ByteArrayBuffer;
+import org.eclipse.jetty.http.PathMap.MappedEntry;
 import org.eclipse.jetty.io.WriterOutputStream;
-import org.eclipse.jetty.server.AbstractHttpConnection;
-import org.eclipse.jetty.server.Connector;
-import org.eclipse.jetty.server.Dispatcher;
 import org.eclipse.jetty.server.HttpOutput;
 import org.eclipse.jetty.server.InclusiveByteRange;
 import org.eclipse.jetty.server.ResourceCache;
 import org.eclipse.jetty.server.Response;
 import org.eclipse.jetty.server.handler.ContextHandler;
-import org.eclipse.jetty.server.nio.NIOConnector;
-import org.eclipse.jetty.server.ssl.SslConnector;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
 import org.eclipse.jetty.util.IO;
 import org.eclipse.jetty.util.MultiPartOutputStream;
 import org.eclipse.jetty.util.QuotedStringTokenizer;
 import org.eclipse.jetty.util.URIUtil;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.util.resource.FileResource;
 import org.eclipse.jetty.util.resource.Resource;
 import org.eclipse.jetty.util.resource.ResourceCollection;
 import org.eclipse.jetty.util.resource.ResourceFactory;
@@ -71,6 +69,7 @@ import org.eclipse.jetty.util.resource.ResourceFactory;
 
 /* ------------------------------------------------------------ */
 /** The default servlet.
+ * 
  * This servlet, normally mapped to /, provides the handling for static
  * content, OPTION and TRACE methods for the context.
  * The following initParameters are supported, these can be set either
@@ -102,23 +101,20 @@ import org.eclipse.jetty.util.resource.ResourceFactory;
  *
  *  resourceBase      Set to replace the context resource base
  *
- *  resourceCache     If set, this is a context attribute name, which the servlet 
- *                    will use to look for a shared ResourceCache instance. 
- *                        
+ *  resourceCache     If set, this is a context attribute name, which the servlet
+ *                    will use to look for a shared ResourceCache instance.
+ *
  *  relativeResourceBase
  *                    Set with a pathname relative to the base of the
  *                    servlet context root. Useful for only serving static content out
  *                    of only specific subdirectories.
  *
- *  pathInfoOnly      If true, only the path info will be applied to the resourceBase 
- *                        
+ *  pathInfoOnly      If true, only the path info will be applied to the resourceBase
+ *
  *  stylesheet	      Set with the location of an optional stylesheet that will be used
  *                    to decorate the directory listing html.
  *
- *  aliases           If True, aliases of resources are allowed (eg. symbolic
- *                    links and caps variations). May bypass security constraints.
- *                    
- *  etags             If True, weak etags will be handled.
+ *  etags             If True, weak etags will be generated and handled.
  *
  *  maxCacheSize      The maximum total size of the cache or 0 for no cache.
  *  maxCachedFileSize The maximum size of a file to cache
@@ -128,10 +124,14 @@ import org.eclipse.jetty.util.resource.ResourceFactory;
  *                    If set to true, it will use mapped file buffer to serve static content
  *                    when using NIO connector. Setting this value to false means that
  *                    a direct buffer will be used instead of a mapped file buffer.
- *                    By default, this is set to true.
+ *                    This is set to false by default by this class, but may be overridden
+ *                    by eg webdefault.xml 
  *
  *  cacheControl      If set, all static content will have this value set as the cache-control
  *                    header.
+ *                    
+ * otherGzipFileExtensions
+ *                    Other file extensions that signify that a file is gzip compressed. Eg ".svgz"
  *
  *
  * </PRE>
@@ -145,6 +145,9 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
     private static final Logger LOG = Log.getLogger(DefaultServlet.class);
 
     private static final long serialVersionUID = 4930458713846881193L;
+    
+    private static final CachedHttpField ACCEPT_RANGES = new CachedHttpField(HttpHeader.ACCEPT_RANGES, "bytes");
+    
     private ServletContext _servletContext;
     private ContextHandler _contextHandler;
 
@@ -153,7 +156,7 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
     private boolean _welcomeServlets=false;
     private boolean _welcomeExactServlets=false;
     private boolean _redirectWelcome=false;
-    private boolean _gzip=true;
+    private boolean _gzip=false;
     private boolean _pathInfoOnly=false;
     private boolean _etags=false;
 
@@ -164,11 +167,11 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
     private String[] _welcomes;
     private Resource _stylesheet;
     private boolean _useFileMappedBuffer=false;
-    private ByteArrayBuffer _cacheControl;
+    private HttpField _cacheControl;
     private String _relativeResourceBase;
     private ServletHandler _servletHandler;
     private ServletHolder _defaultHolder;
-
+    private List<String> _gzipEquivalentFileExtensions;
 
     /* ------------------------------------------------------------ */
     @Override
@@ -198,15 +201,6 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
         else
             _welcomeServlets=getInitBoolean("welcomeServlets", _welcomeServlets);
 
-        if (getInitParameter("aliases")!=null)
-            _contextHandler.setAliases(getInitBoolean("aliases",false));
-
-        boolean aliases=_contextHandler.isAliases();
-        if (!aliases && !FileResource.getCheckAliases())
-            throw new IllegalStateException("Alias checking disabled");
-        if (aliases)
-            _servletContext.log("Aliases are enabled! Security constraints may be bypassed!!!");
-
         _useFileMappedBuffer=getInitBoolean("useFileMappedBuffer",_useFileMappedBuffer);
 
         _relativeResourceBase = getInitParameter("relativeResourceBase");
@@ -240,17 +234,17 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
             {
                 _stylesheet = Resource.newResource(this.getClass().getResource("/jetty-dir.css"));
             }
-        }	
+        }
         catch(Exception e)
         {
             LOG.warn(e.toString());
             LOG.debug(e);
         }
 
-        String t=getInitParameter("cacheControl");
-        if (t!=null)
-            _cacheControl=new ByteArrayBuffer(t);
-
+        String cc=getInitParameter("cacheControl");
+        if (cc!=null)
+            _cacheControl=new CachedHttpField(HttpHeader.CACHE_CONTROL, cc);
+        
         String resourceCache = getInitParameter("resourceCache");
         int max_cache_size=getInitInt("maxCacheSize", -2);
         int max_cached_file_size=getInitInt("maxCachedFileSize", -2);
@@ -263,18 +257,19 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
                 throw new UnavailableException("resourceCache specified with resource bases");
             _cache=(ResourceCache)_servletContext.getAttribute(resourceCache);
 
-            LOG.debug("Cache {}={}",resourceCache,_cache);
+            if (LOG.isDebugEnabled())
+                LOG.debug("Cache {}={}",resourceCache,_cache);
         }
 
         _etags = getInitBoolean("etags",_etags);
         
         try
         {
-            if (_cache==null && max_cached_files>0)
+            if (_cache==null && (max_cached_files!=-2 || max_cache_size!=-2 || max_cached_file_size!=-2))
             {
                 _cache= new ResourceCache(null,this,_mimeTypes,_useFileMappedBuffer,_etags);
 
-                if (max_cache_size>0)
+                if (max_cache_size>=0)
                     _cache.setMaxCacheSize(max_cache_size);
                 if (max_cached_file_size>=-1)
                     _cache.setMaxCachedFileSize(max_cached_file_size);
@@ -287,15 +282,33 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
             LOG.warn(Log.EXCEPTION,e);
             throw new UnavailableException(e.toString());
         }
-
-        _servletHandler= (ServletHandler) _contextHandler.getChildHandlerByClass(ServletHandler.class);
-        for (ServletHolder h :_servletHandler.getServlets())
-            if (h.getServletInstance()==this)
-                _defaultHolder=h;
-
         
-        if (LOG.isDebugEnabled()) 
-            LOG.debug("resource base = "+_resourceBase);
+       _gzipEquivalentFileExtensions = new ArrayList<String>();
+       String otherGzipExtensions = getInitParameter("otherGzipFileExtensions");
+       if (otherGzipExtensions != null)
+       {
+           //comma separated list
+           StringTokenizer tok = new StringTokenizer(otherGzipExtensions,",",false);
+           while (tok.hasMoreTokens())
+           {
+               String s = tok.nextToken().trim();
+               _gzipEquivalentFileExtensions.add((s.charAt(0)=='.'?s:"."+s));
+           }
+       }
+       else
+       {
+           //.svgz files are gzipped svg files and must be served with Content-Encoding:gzip
+           _gzipEquivalentFileExtensions.add(".svgz");   
+       }
+
+       _servletHandler= _contextHandler.getChildHandlerByClass(ServletHandler.class);
+       for (ServletHolder h :_servletHandler.getServlets())
+           if (h.getServletInstance()==this)
+               _defaultHolder=h;
+
+
+       if (LOG.isDebugEnabled())
+           LOG.debug("resource base = "+_resourceBase);
     }
 
     /**
@@ -314,7 +327,7 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
             if (servletContext instanceof ContextHandler.Context)
                 return ((ContextHandler.Context)servletContext).getContextHandler();
             else
-                throw new IllegalArgumentException("The servletContext " + servletContext + " " + 
+                throw new IllegalArgumentException("The servletContext " + servletContext + " " +
                     servletContext.getClass().getName() + " is not " + ContextHandler.Context.class.getName());
         }
         else
@@ -363,8 +376,9 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
      * @param pathInContext The path to find a resource for.
      * @return The resource to serve.
      */
+    @Override
     public Resource getResource(String pathInContext)
-    {	
+    {
         Resource r=null;
         if (_relativeResourceBase!=null)
             pathInContext=URIUtil.addPaths(_relativeResourceBase,pathInContext);
@@ -409,11 +423,11 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
         String servletPath=null;
         String pathInfo=null;
         Enumeration<String> reqRanges = null;
-        Boolean included =request.getAttribute(Dispatcher.INCLUDE_REQUEST_URI)!=null;
+        Boolean included =request.getAttribute(RequestDispatcher.INCLUDE_REQUEST_URI)!=null;
         if (included!=null && included.booleanValue())
         {
-            servletPath=(String)request.getAttribute(Dispatcher.INCLUDE_SERVLET_PATH);
-            pathInfo=(String)request.getAttribute(Dispatcher.INCLUDE_PATH_INFO);
+            servletPath=(String)request.getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH);
+            pathInfo=(String)request.getAttribute(RequestDispatcher.INCLUDE_PATH_INFO);
             if (servletPath==null)
             {
                 servletPath=request.getServletPath();
@@ -427,18 +441,19 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
             pathInfo = request.getPathInfo();
 
             // Is this a Range request?
-            reqRanges = request.getHeaders(HttpHeaders.RANGE);
+            reqRanges = request.getHeaders(HttpHeader.RANGE.asString());
             if (!hasDefinedRange(reqRanges))
                 reqRanges = null;
         }
-        
+
         String pathInContext=URIUtil.addPaths(servletPath,pathInfo);
         boolean endsWithSlash=(pathInfo==null?request.getServletPath():pathInfo).endsWith(URIUtil.SLASH);
-        
+
 
         // Find the resource and content
         Resource resource=null;
         HttpContent content=null;
+        boolean close_content=true;
         try
         {
             // is gzip enabled?
@@ -460,10 +475,10 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
                 if (resource!=null && resource.exists() && !resource.isDirectory())
                 {
                     // Tell caches that response may vary by accept-encoding
-                    response.addHeader(HttpHeaders.VARY,HttpHeaders.ACCEPT_ENCODING);
+                    response.addHeader(HttpHeader.VARY.asString(),HttpHeader.ACCEPT_ENCODING.asString());
                     
                     // Does the client accept gzip?
-                    String accept=request.getHeader(HttpHeaders.ACCEPT_ENCODING);
+                    String accept=request.getHeader(HttpHeader.ACCEPT_ENCODING.asString());
                     if (accept!=null && accept.indexOf("gzip")>=0)
                         gzip=true;
                 }
@@ -482,18 +497,18 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
             }
 
             if (LOG.isDebugEnabled())
-                LOG.debug("uri="+request.getRequestURI()+" resource="+resource+(content!=null?" content":""));
-            
+                LOG.debug(String.format("uri=%s, resource=%s, content=%s",request.getRequestURI(),resource,content));
+
             // Handle resource
             if (resource==null || !resource.exists())
             {
-                if (included) 
+                if (included)
                     throw new FileNotFoundException("!" + pathInContext);
                 response.sendError(HttpServletResponse.SC_NOT_FOUND);
             }
             else if (!resource.isDirectory())
             {
-                if (endsWithSlash && _contextHandler.isAliases() && pathInContext.length()>1)
+                if (endsWithSlash && pathInContext.length()>1)
                 {
                     String q=request.getQueryString();
                     pathInContext=pathInContext.substring(0,pathInContext.length()-1);
@@ -509,14 +524,14 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
 
                     if (included.booleanValue() || passConditionalHeaders(request,response, resource,content))
                     {
-                        if (gzip)
+                        if (gzip || isGzippedContent(pathInContext))
                         {
-                            response.setHeader(HttpHeaders.CONTENT_ENCODING,"gzip");
+                            response.setHeader(HttpHeader.CONTENT_ENCODING.asString(),"gzip");
                             String mt=_servletContext.getMimeType(pathInContext);
                             if (mt!=null)
                                 response.setContentType(mt);
                         }
-                        sendData(request,response,included.booleanValue(),resource,content,reqRanges);
+                        close_content=sendData(request,response,included.booleanValue(),resource,content,reqRanges);
                     }
                 }
             }
@@ -547,7 +562,8 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
                 // else look for a welcome file
                 else if (null!=(welcome=getWelcomeFile(pathInContext)))
                 {
-                    LOG.debug("welcome={}",welcome);
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("welcome={}",welcome);
                     if (_redirectWelcome)
                     {
                         // Redirect to the index
@@ -590,14 +606,31 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
         }
         finally
         {
-            if (content!=null)
-                content.release();
-            else if (resource!=null)
-                resource.release();
+            if (close_content)
+            {
+                if (content!=null)
+                    content.release();
+                else if (resource!=null)
+                    resource.close();
+            }
         }
 
     }
 
+    /**
+     * @param resource
+     * @return
+     */
+    protected boolean isGzippedContent(String path)
+    {
+        if (path == null) return false;
+      
+        for (String suffix:_gzipEquivalentFileExtensions)
+            if (path.endsWith(suffix))
+                return true;
+        return false;
+    }
+
     /* ------------------------------------------------------------ */
     private boolean hasDefinedRange(Enumeration<String> reqRanges)
     {
@@ -658,7 +691,7 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
 
             if ((_welcomeServlets || _welcomeExactServlets) && welcome_servlet==null)
             {
-                Map.Entry entry=_servletHandler.getHolderEntry(welcome_in_context);
+                MappedEntry<?> entry=_servletHandler.getHolderEntry(welcome_in_context);
                 if (entry!=null && entry.getValue()!=_defaultHolder &&
                         (_welcomeServlets || (_welcomeExactServlets && entry.getKey().equals(welcome_in_context))))
                     welcome_servlet=welcome_in_context;
@@ -676,125 +709,101 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
     {
         try
         {
-            if (!request.getMethod().equals(HttpMethods.HEAD) )
+            if (!HttpMethod.HEAD.is(request.getMethod()))
             {
                 if (_etags)
                 {
-                    String ifm=request.getHeader(HttpHeaders.IF_MATCH);
+                    String ifm=request.getHeader(HttpHeader.IF_MATCH.asString());
                     if (ifm!=null)
                     {
                         boolean match=false;
-                        if (content!=null && content.getETag()!=null)
+                        if (content.getETag()!=null)
                         {
                             QuotedStringTokenizer quoted = new QuotedStringTokenizer(ifm,", ",false,true);
                             while (!match && quoted.hasMoreTokens())
                             {
                                 String tag = quoted.nextToken();
-                                if (content.getETag().toString().equals(tag))
+                                if (content.getETag().equals(tag))
                                     match=true;
                             }
                         }
 
                         if (!match)
                         {
-                            Response r = Response.getResponse(response);
-                            r.reset(true);
-                            r.setStatus(HttpServletResponse.SC_PRECONDITION_FAILED);
+                            response.setStatus(HttpServletResponse.SC_PRECONDITION_FAILED);
                             return false;
                         }
                     }
                     
-                    String ifnm=request.getHeader(HttpHeaders.IF_NONE_MATCH);
-                    if (ifnm!=null && content!=null && content.getETag()!=null)
+                    String if_non_match_etag=request.getHeader(HttpHeader.IF_NONE_MATCH.asString());
+                    if (if_non_match_etag!=null && content.getETag()!=null)
                     {
                         // Look for GzipFiltered version of etag
-                        if (content.getETag().toString().equals(request.getAttribute("o.e.j.s.GzipFilter.ETag")))
+                        if (content.getETag().equals(request.getAttribute("o.e.j.s.GzipFilter.ETag")))
                         {
-                            Response r = Response.getResponse(response);
-                            r.reset(true);
-                            r.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
-                            r.getHttpFields().put(HttpHeaders.ETAG_BUFFER,ifnm);
+                            response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+                            response.setHeader(HttpHeader.ETAG.asString(),if_non_match_etag);
                             return false;
                         }
                         
-                        
                         // Handle special case of exact match.
-                        if (content.getETag().toString().equals(ifnm))
+                        if (content.getETag().equals(if_non_match_etag))
                         {
-                            Response r = Response.getResponse(response);
-                            r.reset(true);
-                            r.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
-                            r.getHttpFields().put(HttpHeaders.ETAG_BUFFER,content.getETag());
+                            response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+                            response.setHeader(HttpHeader.ETAG.asString(),content.getETag());
                             return false;
                         }
 
                         // Handle list of tags
-                        QuotedStringTokenizer quoted = new QuotedStringTokenizer(ifnm,", ",false,true);
+                        QuotedStringTokenizer quoted = new QuotedStringTokenizer(if_non_match_etag,", ",false,true);
                         while (quoted.hasMoreTokens())
                         {
                             String tag = quoted.nextToken();
-                            if (content.getETag().toString().equals(tag))
+                            if (content.getETag().equals(tag))
                             {
-                                Response r = Response.getResponse(response);
-                                r.reset(true);
-                                r.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
-                                r.getHttpFields().put(HttpHeaders.ETAG_BUFFER,content.getETag());
+                                response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+                                response.setHeader(HttpHeader.ETAG.asString(),content.getETag());
                                 return false;
                             }
                         }
                         
+                        // If etag requires content to be served, then do not check if-modified-since
                         return true;
                     }
                 }
                 
-                String ifms=request.getHeader(HttpHeaders.IF_MODIFIED_SINCE);
+                // Handle if modified since
+                String ifms=request.getHeader(HttpHeader.IF_MODIFIED_SINCE.asString());
                 if (ifms!=null)
                 {
                     //Get jetty's Response impl
-                    Response r = Response.getResponse(response);
-                                       
-                    if (content!=null)
+                    String mdlm=content.getLastModified();
+                    if (mdlm!=null && ifms.equals(mdlm))
                     {
-                        Buffer mdlm=content.getLastModified();
-                        if (mdlm!=null)
-                        {
-                            if (ifms.equals(mdlm.toString()))
-                            {
-                                r.reset(true);
-                                r.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
-                                if (_etags)
-                                    r.getHttpFields().add(HttpHeaders.ETAG_BUFFER,content.getETag());
-                                r.flushBuffer();
-                                return false;
-                            }
-                        }
+                        response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+                        if (_etags)
+                            response.setHeader(HttpHeader.ETAG.asString(),content.getETag());
+                        response.flushBuffer();
+                        return false;
                     }
 
-                    long ifmsl=request.getDateHeader(HttpHeaders.IF_MODIFIED_SINCE);
-                    if (ifmsl!=-1)
-                    {
-                        if (resource.lastModified()/1000 <= ifmsl/1000)
-                        { 
-                            r.reset(true);
-                            r.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
-                            if (_etags)
-                                r.getHttpFields().add(HttpHeaders.ETAG_BUFFER,content.getETag());
-                            r.flushBuffer();
-                            return false;
-                        }
+                    long ifmsl=request.getDateHeader(HttpHeader.IF_MODIFIED_SINCE.asString());
+                    if (ifmsl!=-1 && resource.lastModified()/1000 <= ifmsl/1000)
+                    { 
+                        response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+                        if (_etags)
+                            response.setHeader(HttpHeader.ETAG.asString(),content.getETag());
+                        response.flushBuffer();
+                        return false;
                     }
                 }
 
                 // Parse the if[un]modified dates and compare to resource
-                long date=request.getDateHeader(HttpHeaders.IF_UNMODIFIED_SINCE);
-
-                if (date!=-1)
+                long date=request.getDateHeader(HttpHeader.IF_UNMODIFIED_SINCE.asString());
+                if (date!=-1 && resource.lastModified()/1000 > date/1000)
                 {
-                    if (resource.lastModified()/1000 > date/1000)
-                    {
-                        response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
-                        return false;
-                    }
+                    response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
+                    return false;
                 }
 
             }
@@ -851,29 +860,16 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
     }
 
     /* ------------------------------------------------------------ */
-    protected void sendData(HttpServletRequest request,
+    protected boolean sendData(HttpServletRequest request,
             HttpServletResponse response,
             boolean include,
             Resource resource,
-            HttpContent content,
-            Enumeration reqRanges)
+            final HttpContent content,
+            Enumeration<String> reqRanges)
     throws IOException
     {
-        boolean direct;
-        long content_length;
-        if (content==null)
-        {
-            direct=false;
-            content_length=resource.length();
-        }
-        else
-        {
-            Connector connector = AbstractHttpConnection.getCurrentConnection().getConnector();
-            direct=connector instanceof NIOConnector && ((NIOConnector)connector).getUseDirectBuffers() && !(connector instanceof SslConnector);
-            content_length=content.getContentLength();
-        }
-
-
+        final long content_length = (content==null)?resource.length():content.getContentLength();
+        
         // Get the output stream (or writer)
         OutputStream out =null;
         boolean written;
@@ -882,16 +878,19 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
             out = response.getOutputStream();
 
             // has a filter already written to the response?
-            written = out instanceof HttpOutput 
-                ? ((HttpOutput)out).isWritten() 
-                : AbstractHttpConnection.getCurrentConnection().getGenerator().isWritten();
+            written = out instanceof HttpOutput
+                ? ((HttpOutput)out).isWritten()
+                : true;
         }
-        catch(IllegalStateException e) 
+        catch(IllegalStateException e)
         {
             out = new WriterOutputStream(response.getWriter());
             written=true; // there may be data in writer buffer, so assume written
         }
         
+        if (LOG.isDebugEnabled())
+            LOG.debug(String.format("sendData content=%s out=%s async=%b",content,out,request.isAsyncSupported()));
+
         if ( reqRanges == null || !reqRanges.hasMoreElements() || content_length<0)
         {
             //  if there were no ranges, send entire entity
@@ -899,74 +898,99 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
             {
                 resource.writeTo(out,0,content_length);
             }
+            // else if we can't do a bypass write because of wrapping
+            else if (content==null || written || !(out instanceof HttpOutput))
+            {
+                // write normally
+                writeHeaders(response,content,written?-1:content_length);
+                ByteBuffer buffer = (content==null)?null:content.getIndirectBuffer();
+                if (buffer!=null)
+                    BufferUtil.writeTo(buffer,out);
+                else
+                    resource.writeTo(out,0,content_length);
+            }
+            // else do a bypass write
             else
             {
-                // See if a direct methods can be used?
-                if (content!=null && !written && out instanceof HttpOutput)
+                // write the headers
+                if (response instanceof Response)
                 {
-                    if (response instanceof Response)
-                    {
-                        writeOptionHeaders(((Response)response).getHttpFields());
-                        ((AbstractHttpConnection.Output)out).sendContent(content);
-                    }
-                    else 
+                    Response r = (Response)response;
+                    writeOptionHeaders(r.getHttpFields());
+                    r.setHeaders(content);
+                }
+                else
+                    writeHeaders(response,content,content_length);
+
+                // write the content asynchronously if supported
+                if (request.isAsyncSupported())
+                {
+                    final AsyncContext context = request.startAsync();
+                    context.setTimeout(0);
+
+                    ((HttpOutput)out).sendContent(content,new Callback()
                     {
-                        Buffer buffer = direct?content.getDirectBuffer():content.getIndirectBuffer();
-                        if (buffer!=null)
+                        @Override
+                        public void succeeded()
+                        {   
+                            context.complete();
+                            content.release();
+                        }
+
+                        @Override
+                        public void failed(Throwable x)
                         {
-                            writeHeaders(response,content,content_length);
-                            ((AbstractHttpConnection.Output)out).sendContent(buffer);
+                            if (x instanceof IOException)
+                                LOG.debug(x);
+                            else
+                                LOG.warn(x);
+                            context.complete();
+                            content.release();
                         }
-                        else
+                        
+                        @Override
+                        public String toString() 
                         {
-                            writeHeaders(response,content,content_length);
-                            resource.writeTo(out,0,content_length);
+                            return String.format("DefaultServlet@%x$CB", DefaultServlet.this.hashCode());
                         }
-                    }
-                }
-                else 
-                {
-                    // Write headers normally
-                    writeHeaders(response,content,written?-1:content_length);
-
-                    // Write content normally
-                    Buffer buffer = (content==null)?null:content.getIndirectBuffer();
-                    if (buffer!=null)
-                        buffer.writeTo(out);
-                    else
-                        resource.writeTo(out,0,content_length);
+                    });
+                    return false;
                 }
+                // otherwise write content blocking
+                ((HttpOutput)out).sendContent(content);
+                
             }
         }
         else
         {
             // Parse the satisfiable ranges
-            List ranges =InclusiveByteRange.satisfiableRanges(reqRanges,content_length);
+            List<InclusiveByteRange> ranges =InclusiveByteRange.satisfiableRanges(reqRanges,content_length);
 
             //  if there are no satisfiable ranges, send 416 response
             if (ranges==null || ranges.size()==0)
             {
                 writeHeaders(response, content, content_length);
                 response.setStatus(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
-                response.setHeader(HttpHeaders.CONTENT_RANGE,
+                response.setHeader(HttpHeader.CONTENT_RANGE.asString(),
                         InclusiveByteRange.to416HeaderRangeString(content_length));
                 resource.writeTo(out,0,content_length);
-                return;
+                return true;
             }
 
             //  if there is only a single valid range (must be satisfiable
             //  since were here now), send that range with a 216 response
             if ( ranges.size()== 1)
             {
-                InclusiveByteRange singleSatisfiableRange =
-                    (InclusiveByteRange)ranges.get(0);
+                InclusiveByteRange singleSatisfiableRange = ranges.get(0);
                 long singleLength = singleSatisfiableRange.getSize(content_length);
                 writeHeaders(response,content,singleLength                     );
                 response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
-                response.setHeader(HttpHeaders.CONTENT_RANGE,
+                if (!response.containsHeader(HttpHeader.DATE.asString()))
+                    response.addDateHeader(HttpHeader.DATE.asString(),System.currentTimeMillis());
+                response.setHeader(HttpHeader.CONTENT_RANGE.asString(),
                         singleSatisfiableRange.toHeaderRangeString(content_length));
                 resource.writeTo(out,singleSatisfiableRange.getFirst(content_length),singleLength);
-                return;
+                return true;
             }
 
             //  multiple non-overlapping valid ranges cause a multipart
@@ -974,17 +998,19 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
             //  content-length header
             //
             writeHeaders(response,content,-1);
-            String mimetype=(content.getContentType()==null?null:content.getContentType().toString());
+            String mimetype=(content==null?null:content.getContentType());
             if (mimetype==null)
                 LOG.warn("Unknown mimetype for "+request.getRequestURI());
             MultiPartOutputStream multi = new MultiPartOutputStream(out);
             response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
+            if (!response.containsHeader(HttpHeader.DATE.asString()))
+                response.addDateHeader(HttpHeader.DATE.asString(),System.currentTimeMillis());
 
             // If the request has a "Request-Range" header then we need to
             // send an old style multipart/x-byteranges Content-Type. This
             // keeps Netscape and acrobat happy. This is what Apache does.
             String ctp;
-            if (request.getHeader(HttpHeaders.REQUEST_RANGE)!=null)
+            if (request.getHeader(HttpHeader.REQUEST_RANGE.asString())!=null)
                 ctp = "multipart/x-byteranges; boundary=";
             else
                 ctp = "multipart/byteranges; boundary=";
@@ -998,13 +1024,13 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
             String[] header = new String[ranges.size()];
             for (int i=0;i<ranges.size();i++)
             {
-                InclusiveByteRange ibr = (InclusiveByteRange) ranges.get(i);
+                InclusiveByteRange ibr = ranges.get(i);
                 header[i]=ibr.toHeaderRangeString(content_length);
                 length+=
                     ((i>0)?2:0)+
                     2+multi.getBoundary().length()+2+
-                    (mimetype==null?0:HttpHeaders.CONTENT_TYPE.length()+2+mimetype.length())+2+
-                    HttpHeaders.CONTENT_RANGE.length()+2+header[i].length()+2+
+                    (mimetype==null?0:HttpHeader.CONTENT_TYPE.asString().length()+2+mimetype.length())+2+
+                    HttpHeader.CONTENT_RANGE.asString().length()+2+header[i].length()+2+
                     2+
                     (ibr.getLast(content_length)-ibr.getFirst(content_length))+1;
             }
@@ -1013,8 +1039,8 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
 
             for (int i=0;i<ranges.size();i++)
             {
-                InclusiveByteRange ibr = (InclusiveByteRange) ranges.get(i);
-                multi.startPart(mimetype,new String[]{HttpHeaders.CONTENT_RANGE+": "+header[i]});
+                InclusiveByteRange ibr =  ranges.get(i);
+                multi.startPart(mimetype,new String[]{HttpHeader.CONTENT_RANGE+": "+header[i]});
 
                 long start=ibr.getFirst(content_length);
                 long size=ibr.getSize(content_length);
@@ -1032,25 +1058,32 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
                         in.skip(start-pos);
                         pos=start;
                     }
+                    
                     IO.copy(in,multi,size);
                     pos+=size;
                 }
                 else
                     // Handle cached resource
                     (resource).writeTo(multi,start,size);
-
             }
             if (in!=null)
                 in.close();
             multi.close();
         }
-        return;
+        return true;
     }
 
     /* ------------------------------------------------------------ */
     protected void writeHeaders(HttpServletResponse response,HttpContent content,long count)
-    throws IOException
-    {        
+    {
+        if (content == null)
+        {
+            // No content, then no headers to process
+            // This is possible during bypass write because of wrapping
+            // See .sendData() for more details.
+            return;
+        }
+        
         if (content.getContentType()!=null && response.getContentType()==null)
             response.setContentType(content.getContentType().toString());
 
@@ -1060,12 +1093,12 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
             HttpFields fields = r.getHttpFields();
 
             if (content.getLastModified()!=null)
-                fields.put(HttpHeaders.LAST_MODIFIED_BUFFER,content.getLastModified());
+                fields.put(HttpHeader.LAST_MODIFIED,content.getLastModified());
             else if (content.getResource()!=null)
             {
                 long lml=content.getResource().lastModified();
                 if (lml!=-1)
-                    fields.putDateField(HttpHeaders.LAST_MODIFIED_BUFFER,lml);
+                    fields.putDateField(HttpHeader.LAST_MODIFIED,lml);
             }
 
             if (count != -1)
@@ -1074,50 +1107,48 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
             writeOptionHeaders(fields);
             
             if (_etags)
-                fields.put(HttpHeaders.ETAG_BUFFER,content.getETag());
+                fields.put(HttpHeader.ETAG,content.getETag());
         }
         else
         {
             long lml=content.getResource().lastModified();
             if (lml>=0)
-                response.setDateHeader(HttpHeaders.LAST_MODIFIED,lml);
+                response.setDateHeader(HttpHeader.LAST_MODIFIED.asString(),lml);
 
             if (count != -1)
             {
                 if (count<Integer.MAX_VALUE)
                     response.setContentLength((int)count);
                 else
-                    response.setHeader(HttpHeaders.CONTENT_LENGTH,Long.toString(count));
+                    response.setHeader(HttpHeader.CONTENT_LENGTH.asString(),Long.toString(count));
             }
 
             writeOptionHeaders(response);
 
             if (_etags)
-                response.setHeader(HttpHeaders.ETAG,content.getETag().toString());
+                response.setHeader(HttpHeader.ETAG.asString(),content.getETag());
         }
     }
 
     /* ------------------------------------------------------------ */
-    protected void writeOptionHeaders(HttpFields fields) throws IOException
+    protected void writeOptionHeaders(HttpFields fields)
     {
         if (_acceptRanges)
-            fields.put(HttpHeaders.ACCEPT_RANGES_BUFFER,HttpHeaderValues.BYTES_BUFFER);
+            fields.put(ACCEPT_RANGES);
 
         if (_cacheControl!=null)
-            fields.put(HttpHeaders.CACHE_CONTROL_BUFFER,_cacheControl);
+            fields.put(_cacheControl);
     }
 
     /* ------------------------------------------------------------ */
-    protected void writeOptionHeaders(HttpServletResponse response) throws IOException
+    protected void writeOptionHeaders(HttpServletResponse response)
     {
         if (_acceptRanges)
-            response.setHeader(HttpHeaders.ACCEPT_RANGES,"bytes");
+            response.setHeader(HttpHeader.ACCEPT_RANGES.asString(),"bytes");
 
         if (_cacheControl!=null)
-            response.setHeader(HttpHeaders.CACHE_CONTROL,_cacheControl.toString());
+            response.setHeader(HttpHeader.CACHE_CONTROL.asString(),_cacheControl.getValue());
     }
-    
-  
 
     /* ------------------------------------------------------------ */
     /*
diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ErrorPageErrorHandler.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ErrorPageErrorHandler.java
index 815a7f1..7100bc2 100644
--- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ErrorPageErrorHandler.java
+++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ErrorPageErrorHandler.java
@@ -23,11 +23,11 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
-import javax.servlet.RequestDispatcher;
 import javax.servlet.ServletContext;
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 
+import org.eclipse.jetty.server.Dispatcher;
 import org.eclipse.jetty.server.handler.ContextHandler;
 import org.eclipse.jetty.server.handler.ErrorHandler;
 
@@ -50,35 +50,36 @@ public class ErrorPageErrorHandler extends ErrorHandler implements ErrorHandler.
     public ErrorPageErrorHandler()
     {}
 
+    /* ------------------------------------------------------------ */
     @Override
     public String getErrorPage(HttpServletRequest request)
     {
         String error_page= null;
-        Class<?> exClass= (Class<?>)request.getAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE);
 
-        if (ServletException.class.equals(exClass))
+        Throwable th= (Throwable)request.getAttribute(Dispatcher.ERROR_EXCEPTION);
+
+        // Walk the cause hierarchy
+        while (error_page == null && th != null )
         {
+            Class<?> exClass=th.getClass();
             error_page= (String)_errorPages.get(exClass.getName());
-            if (error_page == null)
+
+            // walk the inheritance hierarchy
+            while (error_page == null)
             {
-                Throwable th= (Throwable)request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
-                while (th instanceof ServletException)
-                    th= ((ServletException)th).getRootCause();
-                if (th != null)
-                    exClass= th.getClass();
+                exClass= exClass.getSuperclass();
+                if (exClass==null)
+                    break;
+                error_page= (String)_errorPages.get(exClass.getName());
             }
-        }
 
-        while (error_page == null && exClass != null )
-        {
-            error_page= (String)_errorPages.get(exClass.getName());
-            exClass= exClass.getSuperclass();
+            th=(th instanceof ServletException)?((ServletException)th).getRootCause():null;
         }
 
         if (error_page == null)
         {
             // look for an exact code match
-            Integer code=(Integer)request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
+            Integer code=(Integer)request.getAttribute(Dispatcher.ERROR_STATUS_CODE);
             if (code!=null)
             {
                 error_page= (String)_errorPages.get(Integer.toString(code));
@@ -100,15 +101,14 @@ public class ErrorPageErrorHandler extends ErrorHandler implements ErrorHandler.
             }
         }
 
-        //try new servlet 3.0 global error page
+        //try servlet 3.x global error page
         if (error_page == null)
-        {
             error_page = _errorPages.get(GLOBAL_ERROR_PAGE);
-        }
-
+        
         return error_page;
     }
 
+
     /* ------------------------------------------------------------ */
     /**
      * @return Returns the errorPages.
@@ -140,7 +140,7 @@ public class ErrorPageErrorHandler extends ErrorHandler implements ErrorHandler.
     {
         _errorPages.put(exception.getName(),uri);
     }
-    
+
     /* ------------------------------------------------------------ */
     /** Add Error Page mapping for an exception class
      * This method is called as a result of an exception-type element in a web.xml file
diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/FilterHolder.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/FilterHolder.java
index 1e7024c..a11f0bc 100644
--- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/FilterHolder.java
+++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/FilterHolder.java
@@ -18,6 +18,7 @@
 
 package org.eclipse.jetty.servlet;
 
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -28,44 +29,46 @@ import javax.servlet.DispatcherType;
 import javax.servlet.Filter;
 import javax.servlet.FilterConfig;
 import javax.servlet.FilterRegistration;
+import javax.servlet.ServletContext;
 import javax.servlet.ServletException;
 
-import org.eclipse.jetty.servlet.Holder.Source;
 import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.component.Dumpable;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
 
 /* --------------------------------------------------------------------- */
-/** 
- * 
+/**
+ *
  */
 public class FilterHolder extends Holder<Filter>
 {
     private static final Logger LOG = Log.getLogger(FilterHolder.class);
-    
+
     /* ------------------------------------------------------------ */
     private transient Filter _filter;
     private transient Config _config;
     private transient FilterRegistration.Dynamic _registration;
-    
+
     /* ---------------------------------------------------------------- */
-    /** Constructor 
+    /** Constructor
      */
     public FilterHolder()
     {
         this(Source.EMBEDDED);
-    }   
-    
+    }
+
+
     /* ---------------------------------------------------------------- */
-    /** Constructor 
+    /** Constructor
      */
     public FilterHolder(Holder.Source source)
     {
         super(source);
-    }   
-    
+    }
+
     /* ---------------------------------------------------------------- */
-    /** Constructor 
+    /** Constructor
      */
     public FilterHolder(Class<? extends Filter> filter)
     {
@@ -81,14 +84,14 @@ public class FilterHolder extends Holder<Filter>
         this(Source.EMBEDDED);
         setFilter(filter);
     }
-    
+
     /* ------------------------------------------------------------ */
     @Override
     public void doStart()
         throws Exception
     {
         super.doStart();
-        
+
         if (!javax.servlet.Filter.class
             .isAssignableFrom(_class))
         {
@@ -96,12 +99,27 @@ public class FilterHolder extends Holder<Filter>
             super.stop();
             throw new IllegalStateException(msg);
         }
+    }
+    
+    
+    
+
+
 
+    /* ------------------------------------------------------------ */
+    @Override
+    public void initialize() throws Exception
+    {
+        super.initialize();
+        
         if (_filter==null)
         {
             try
             {
-                _filter=((ServletContextHandler.Context)_servletHandler.getServletContext()).createFilter(getHeldClass());
+                ServletContext context=_servletHandler.getServletContext();
+                _filter=(context instanceof ServletContextHandler.Context)
+                    ?((ServletContextHandler.Context)context).createFilter(getHeldClass())
+                    :getHeldClass().newInstance();
             }
             catch (ServletException se)
             {
@@ -113,16 +131,19 @@ public class FilterHolder extends Holder<Filter>
                 throw se;
             }
         }
-        
+
         _config=new Config();
+        if (LOG.isDebugEnabled())
+            LOG.debug("Filter.init {}",_filter);
         _filter.init(_config);
     }
 
+
     /* ------------------------------------------------------------ */
     @Override
     public void doStop()
         throws Exception
-    {      
+    {
         if (_filter!=null)
         {
             try
@@ -136,9 +157,9 @@ public class FilterHolder extends Holder<Filter>
         }
         if (!_extInstance)
             _filter=null;
-        
+
         _config=null;
-        super.doStop();   
+        super.doStop();
     }
 
     /* ------------------------------------------------------------ */
@@ -162,7 +183,7 @@ public class FilterHolder extends Holder<Filter>
         if (getName()==null)
             setName(filter.getClass().getName());
     }
-    
+
     /* ------------------------------------------------------------ */
     public Filter getFilter()
     {
@@ -177,13 +198,23 @@ public class FilterHolder extends Holder<Filter>
     }
     
     /* ------------------------------------------------------------ */
+    @Override
+    public void dump(Appendable out, String indent) throws IOException
+    {
+        super.dump(out, indent);
+        if(_filter instanceof Dumpable) {
+            ((Dumpable) _filter).dump(out, indent);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
     public FilterRegistration.Dynamic getRegistration()
     {
         if (_registration == null)
             _registration = new Registration();
         return _registration;
     }
-    
+
     /* ------------------------------------------------------------ */
     /* ------------------------------------------------------------ */
     /* ------------------------------------------------------------ */
diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/FilterMapping.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/FilterMapping.java
index edf01a9..90aa721 100644
--- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/FilterMapping.java
+++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/FilterMapping.java
@@ -22,13 +22,15 @@ import java.io.IOException;
 import java.util.EnumSet;
 
 import javax.servlet.DispatcherType;
+
 import org.eclipse.jetty.http.PathMap;
-import org.eclipse.jetty.server.Handler;
 import org.eclipse.jetty.util.TypeUtil;
-import org.eclipse.jetty.util.component.AggregateLifeCycle;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
 import org.eclipse.jetty.util.component.Dumpable;
 
-
+ at ManagedObject("Filter Mappings")
 public class FilterMapping implements Dumpable
 {
     /** Dispatch types */
@@ -39,7 +41,7 @@ public class FilterMapping implements Dumpable
     public static final int ERROR=8;
     public static final int ASYNC=16;
     public static final int ALL=31;
-    
+
 
     /* ------------------------------------------------------------ */
     /** Dispatch type from name
@@ -58,33 +60,33 @@ public class FilterMapping implements Dumpable
             return DispatcherType.ASYNC;
         throw new IllegalArgumentException(type);
     }
-    
+
     /* ------------------------------------------------------------ */
     /** Dispatch type from name
      */
     public static int dispatch(DispatcherType type)
     {
-    	switch(type)
-    	{
-    	  case REQUEST:
-    		  return REQUEST;
-    	  case ASYNC:
-    		  return ASYNC;
-    	  case FORWARD:
-    		  return FORWARD;
-    	  case INCLUDE:
-    		  return INCLUDE;
-    	  case ERROR:
-    		  return ERROR;
-    	}
+        switch(type)
+        {
+          case REQUEST:
+                  return REQUEST;
+          case ASYNC:
+                  return ASYNC;
+          case FORWARD:
+                  return FORWARD;
+          case INCLUDE:
+                  return INCLUDE;
+          case ERROR:
+                  return ERROR;
+        }
         throw new IllegalArgumentException(type.toString());
     }
-	
+
 
     /* ------------------------------------------------------------ */
     /* ------------------------------------------------------------ */
-    
-	
+
+
     private int _dispatches=DEFAULT;
     private String _filterName;
     private transient FilterHolder _holder;
@@ -94,7 +96,7 @@ public class FilterMapping implements Dumpable
     /* ------------------------------------------------------------ */
     public FilterMapping()
     {}
-    
+
     /* ------------------------------------------------------------ */
     /** Check if this filter applies to a path.
      * @param path The path to check or null to just check type
@@ -112,7 +114,7 @@ public class FilterMapping implements Dumpable
 
         return false;
     }
-    
+
     /* ------------------------------------------------------------ */
     /** Check if this filter applies to a particular dispatch type.
      * @param type The type of request:
@@ -121,20 +123,33 @@ public class FilterMapping implements Dumpable
      */
     boolean appliesTo(int type)
     {
-    	if (_dispatches==0)
-    		return type==REQUEST || type==ASYNC && _holder.isAsyncSupported();
+        if (_dispatches==0)
+                return type==REQUEST || type==ASYNC && _holder.isAsyncSupported();
         return (_dispatches&type)!=0;
     }
+
+    /* ------------------------------------------------------------ */
+    public boolean appliesTo(DispatcherType t)
+    {
+        return appliesTo(dispatch(t));
+    }
+    
+    /* ------------------------------------------------------------ */
+    public boolean isDefaultDispatches()
+    {
+        return _dispatches==0;
+    }
     
     /* ------------------------------------------------------------ */
     /**
      * @return Returns the filterName.
      */
+    @ManagedAttribute(value="filter name", readonly=true)
     public String getFilterName()
     {
         return _filterName;
     }
-    
+
     /* ------------------------------------------------------------ */
     /**
      * @return Returns the holder.
@@ -143,35 +158,36 @@ public class FilterMapping implements Dumpable
     {
         return _holder;
     }
-    
+
     /* ------------------------------------------------------------ */
     /**
      * @return Returns the pathSpec.
      */
+    @ManagedAttribute(value="url patterns", readonly=true)
     public String[] getPathSpecs()
     {
         return _pathSpecs;
     }
 
     /* ------------------------------------------------------------ */
-    public void setDispatcherTypes(EnumSet<DispatcherType> dispatcherTypes) 
+    public void setDispatcherTypes(EnumSet<DispatcherType> dispatcherTypes)
     {
         _dispatches=DEFAULT;
         if (dispatcherTypes!=null)
         {
-            if (dispatcherTypes.contains(DispatcherType.ERROR)) 
+            if (dispatcherTypes.contains(DispatcherType.ERROR))
                 _dispatches|=ERROR;
-            if (dispatcherTypes.contains(DispatcherType.FORWARD)) 
+            if (dispatcherTypes.contains(DispatcherType.FORWARD))
                 _dispatches|=FORWARD;
-            if (dispatcherTypes.contains(DispatcherType.INCLUDE)) 
+            if (dispatcherTypes.contains(DispatcherType.INCLUDE))
                 _dispatches|=INCLUDE;
-            if (dispatcherTypes.contains(DispatcherType.REQUEST)) 
+            if (dispatcherTypes.contains(DispatcherType.REQUEST))
                 _dispatches|=REQUEST;
-            if (dispatcherTypes.contains(DispatcherType.ASYNC)) 
+            if (dispatcherTypes.contains(DispatcherType.ASYNC))
                 _dispatches|=ASYNC;
         }
     }
-    
+
     /* ------------------------------------------------------------ */
     /**
      * @param dispatches The dispatches to set.
@@ -185,7 +201,7 @@ public class FilterMapping implements Dumpable
     {
         _dispatches = dispatches;
     }
-    
+
     /* ------------------------------------------------------------ */
     /**
      * @param filterName The filterName to set.
@@ -194,7 +210,7 @@ public class FilterMapping implements Dumpable
     {
         _filterName = filterName;
     }
-    
+
     /* ------------------------------------------------------------ */
     /**
      * @param holder The holder to set.
@@ -204,16 +220,16 @@ public class FilterMapping implements Dumpable
         _holder = holder;
         setFilterName(holder.getName());
     }
-    
+
     /* ------------------------------------------------------------ */
     /**
-     * @param pathSpecs The Path specifications to which this filter should be mapped. 
+     * @param pathSpecs The Path specifications to which this filter should be mapped.
      */
     public void setPathSpecs(String[] pathSpecs)
     {
         _pathSpecs = pathSpecs;
     }
-    
+
     /* ------------------------------------------------------------ */
     /**
      * @param pathSpec The pathSpec to set.
@@ -222,16 +238,17 @@ public class FilterMapping implements Dumpable
     {
         _pathSpecs = new String[]{pathSpec};
     }
-    
+
     /* ------------------------------------------------------------ */
     /**
      * @return Returns the servletName.
      */
+    @ManagedAttribute(value="servlet names", readonly=true)
     public String[] getServletNames()
     {
         return _servletNames;
     }
-    
+
     /* ------------------------------------------------------------ */
     /**
      * @param servletNames Maps the {@link #setFilterName(String) named filter} to multiple servlets
@@ -241,7 +258,7 @@ public class FilterMapping implements Dumpable
     {
         _servletNames = servletNames;
     }
-    
+
     /* ------------------------------------------------------------ */
     /**
      * @param servletName Maps the {@link #setFilterName(String) named filter} to a single servlet
@@ -255,11 +272,11 @@ public class FilterMapping implements Dumpable
     /* ------------------------------------------------------------ */
     public String toString()
     {
-        return 
+        return
         TypeUtil.asList(_pathSpecs)+"/"+
         TypeUtil.asList(_servletNames)+"=="+
         _dispatches+"=>"+
-        _filterName; 
+        _filterName;
     }
 
     /* ------------------------------------------------------------ */
@@ -271,6 +288,6 @@ public class FilterMapping implements Dumpable
     /* ------------------------------------------------------------ */
     public String dump()
     {
-        return AggregateLifeCycle.dump(this);
-    }    
+        return ContainerLifeCycle.dump(this);
+    }
 }
diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/Holder.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/Holder.java
index f7128d0..27c53d0 100644
--- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/Holder.java
+++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/Holder.java
@@ -28,42 +28,37 @@ import java.util.Set;
 
 import javax.servlet.Registration;
 import javax.servlet.ServletContext;
-import javax.servlet.UnavailableException;
 
-import org.eclipse.jetty.server.handler.ContextHandler;
-import org.eclipse.jetty.util.Loader;
-import org.eclipse.jetty.util.component.AbstractLifeCycle;
-import org.eclipse.jetty.util.component.AggregateLifeCycle;
-import org.eclipse.jetty.util.component.Dumpable;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
 
 
 /* --------------------------------------------------------------------- */
-/** 
+/**
+ * Holder
+ * 
+ * Specialization of AbstractHolder for servlet-related classes that 
+ * have init-params etc
  * 
  */
-public class Holder<T> extends AbstractLifeCycle implements Dumpable
+ at ManagedObject("Holder - a container for servlets and the like")
+public class Holder<T> extends BaseHolder<T>
 {
-    public enum Source { EMBEDDED, JAVAX_API, DESCRIPTOR, ANNOTATION };
-    final private Source _source;
     private static final Logger LOG = Log.getLogger(Holder.class);
 
-    protected transient Class<? extends T> _class;
     protected final Map<String,String> _initParams=new HashMap<String,String>(3);
-    protected String _className;
     protected String _displayName;
-    protected boolean _extInstance;
     protected boolean _asyncSupported;
-
-    /* ---------------------------------------------------------------- */
     protected String _name;
-    protected ServletHandler _servletHandler;
+
 
     /* ---------------------------------------------------------------- */
     protected Holder(Source source)
     {
-        _source=source;
+        super(source);
         switch(_source)
         {
             case JAVAX_API:
@@ -75,69 +70,11 @@ public class Holder<T> extends AbstractLifeCycle implements Dumpable
                 _asyncSupported=true;
         }
     }
-    
-    public Source getSource()
-    {
-        return _source;
-    }
-    
-    /* ------------------------------------------------------------ */
-    /**
-     * @return True if this holder was created for a specific instance.
-     */
-    public boolean isInstance()
-    {
-        return _extInstance;
-    }
-    
-    /* ------------------------------------------------------------ */
-    @SuppressWarnings("unchecked")
-    public void doStart()
-        throws Exception
-    {
-        //if no class already loaded and no classname, make servlet permanently unavailable
-        if (_class==null && (_className==null || _className.equals("")))
-            throw new UnavailableException("No class for Servlet or Filter for "+_name);
-        
-        //try to load class
-        if (_class==null)
-        {
-            try
-            {
-                _class=Loader.loadClass(Holder.class, _className);
-                if(LOG.isDebugEnabled())
-                    LOG.debug("Holding {}",_class);
-            }
-            catch (Exception e)
-            {
-                LOG.warn(e);
-                throw new UnavailableException(e.getMessage());
-            }
-        }
-    }
-    
-    /* ------------------------------------------------------------ */
-    @Override
-    public void doStop()
-        throws Exception
-    {
-        if (!_extInstance)
-            _class=null;
-    }
-    
-    /* ------------------------------------------------------------ */
-    public String getClassName()
-    {
-        return _className;
-    }
-    
-    /* ------------------------------------------------------------ */
-    public Class<? extends T> getHeldClass()
-    {
-        return _class;
-    }
-    
+
+  
+
     /* ------------------------------------------------------------ */
+    @ManagedAttribute(value="Display Name", readonly=true)
     public String getDisplayName()
     {
         return _displayName;
@@ -150,9 +87,9 @@ public class Holder<T> extends AbstractLifeCycle implements Dumpable
             return null;
         return (String)_initParams.get(param);
     }
-    
+
     /* ------------------------------------------------------------ */
-    public Enumeration getInitParameterNames()
+    public Enumeration<String> getInitParameterNames()
     {
         if (_initParams==null)
             return Collections.enumeration(Collections.EMPTY_LIST);
@@ -160,78 +97,69 @@ public class Holder<T> extends AbstractLifeCycle implements Dumpable
     }
 
     /* ---------------------------------------------------------------- */
+    @ManagedAttribute(value="Initial Parameters", readonly=true)
     public Map<String,String> getInitParameters()
     {
         return _initParams;
     }
-    
+
     /* ------------------------------------------------------------ */
+    @ManagedAttribute(value="Name", readonly=true)
     public String getName()
     {
         return _name;
     }
-    
-    /* ------------------------------------------------------------ */
-    /**
-     * @return Returns the servletHandler.
-     */
-    public ServletHandler getServletHandler()
-    {
-        return _servletHandler;
-    }
-    
+
+  
     /* ------------------------------------------------------------ */
     public void destroyInstance(Object instance)
     throws Exception
     {
     }
-    
     /* ------------------------------------------------------------ */
     /**
      * @param className The className to set.
      */
     public void setClassName(String className)
     {
-        _className = className;
-        _class=null;
+        super.setClassName(className);
         if (_name==null)
             _name=className+"-"+Integer.toHexString(this.hashCode());
     }
-    
+
     /* ------------------------------------------------------------ */
     /**
      * @param held The class to hold
      */
     public void setHeldClass(Class<? extends T> held)
     {
-        _class=held;
+        super.setHeldClass(held);
         if (held!=null)
         {
-            _className=held.getName();
             if (_name==null)
                 _name=held.getName()+"-"+Integer.toHexString(this.hashCode());
         }
     }
-    
+
     /* ------------------------------------------------------------ */
     public void setDisplayName(String name)
     {
         _displayName=name;
     }
-    
+
     /* ------------------------------------------------------------ */
     public void setInitParameter(String param,String value)
     {
         _initParams.put(param,value);
     }
-    
+
     /* ---------------------------------------------------------------- */
     public void setInitParameters(Map<String,String> map)
     {
         _initParams.clear();
         _initParams.putAll(map);
     }
-    
+
     /* ------------------------------------------------------------ */
     /**
      * The name is a primary key for the held object.
@@ -243,15 +171,7 @@ public class Holder<T> extends AbstractLifeCycle implements Dumpable
     {
         _name = name;
     }
-    
-    /* ------------------------------------------------------------ */
-    /**
-     * @param servletHandler The {@link ServletHandler} that will handle requests dispatched to this servlet.
-     */
-    public void setServletHandler(ServletHandler servletHandler)
-    {
-        _servletHandler = servletHandler;
-    }
+
 
     /* ------------------------------------------------------------ */
     public void setAsyncSupported(boolean suspendable)
@@ -264,44 +184,36 @@ public class Holder<T> extends AbstractLifeCycle implements Dumpable
     {
         return _asyncSupported;
     }
-    
-    /* ------------------------------------------------------------ */
-    public String toString()
-    {
-        return _name;
-    }
+
 
     /* ------------------------------------------------------------ */
-    protected void illegalStateIfContextStarted()
+    @Override
+    public void dump(Appendable out, String indent) throws IOException
     {
-        if (_servletHandler!=null)
-        {
-            ContextHandler.Context context=(ContextHandler.Context)_servletHandler.getServletContext();
-            if (context!=null && context.getContextHandler().isStarted())
-                throw new IllegalStateException("Started");
-        }
+        super.dump(out,indent);
+        ContainerLifeCycle.dump(out,indent,_initParams.entrySet());
     }
 
     /* ------------------------------------------------------------ */
-    public void dump(Appendable out, String indent) throws IOException
+    @Override
+    public String dump()
     {
-        out.append(_name).append("==").append(_className)
-        .append(" - ").append(AbstractLifeCycle.getState(this)).append("\n");
-        AggregateLifeCycle.dump(out,indent,_initParams.entrySet());
+        return super.dump();
     }
 
     /* ------------------------------------------------------------ */
-    public String dump()
+    @Override
+    public String toString()
     {
-        return AggregateLifeCycle.dump(this);
-    }    
+        return String.format("%s@%x==%s",_name,hashCode(),_className);
+    }
     
     /* ------------------------------------------------------------ */
     /* ------------------------------------------------------------ */
     /* ------------------------------------------------------------ */
-    protected class HolderConfig 
-    {   
-        
+    protected class HolderConfig
+    {
+
         /* -------------------------------------------------------- */
         public ServletContext getServletContext()
         {
@@ -313,9 +225,9 @@ public class Holder<T> extends AbstractLifeCycle implements Dumpable
         {
             return Holder.this.getInitParameter(param);
         }
-    
+
         /* -------------------------------------------------------- */
-        public Enumeration getInitParameterNames()
+        public Enumeration<String> getInitParameterNames()
         {
             return Holder.this.getInitParameterNames();
         }
@@ -397,8 +309,6 @@ public class Holder<T> extends AbstractLifeCycle implements Dumpable
             Holder.this.getInitParameters().putAll(initParameters);
             return Collections.emptySet();
         }
-        
-        
     }
 }
 
diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/Invoker.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/Invoker.java
index fb5f77b..999a086 100644
--- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/Invoker.java
+++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/Invoker.java
@@ -32,38 +32,38 @@ import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletRequestWrapper;
 import javax.servlet.http.HttpServletResponse;
 
-import org.eclipse.jetty.server.AbstractHttpConnection;
 import org.eclipse.jetty.server.Dispatcher;
 import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.HttpChannel;
 import org.eclipse.jetty.server.Request;
 import org.eclipse.jetty.server.handler.ContextHandler;
 import org.eclipse.jetty.server.handler.HandlerWrapper;
-import org.eclipse.jetty.util.LazyList;
+import org.eclipse.jetty.util.ArrayUtil;
 import org.eclipse.jetty.util.URIUtil;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
 
 /* ------------------------------------------------------------ */
-/**  Dynamic Servlet Invoker.  
- * This servlet invokes anonymous servlets that have not been defined   
- * in the web.xml or by other means. The first element of the pathInfo  
- * of a request passed to the envoker is treated as a servlet name for  
- * an existing servlet, or as a class name of a new servlet.            
- * This servlet is normally mapped to /servlet/*                        
- * This servlet support the following initParams:                       
- * <PRE>                                                                     
- *  nonContextServlets       If false, the invoker can only load        
- *                           servlets from the contexts classloader.    
- *                           This is false by default and setting this  
- *                           to true may have security implications.    
- *                                                                      
- *  verbose                  If true, log dynamic loads                 
- *                                                                      
- *  *                        All other parameters are copied to the     
- *                           each dynamic servlet as init parameters    
+/**  Dynamic Servlet Invoker.
+ * This servlet invokes anonymous servlets that have not been defined
+ * in the web.xml or by other means. The first element of the pathInfo
+ * of a request passed to the envoker is treated as a servlet name for
+ * an existing servlet, or as a class name of a new servlet.
+ * This servlet is normally mapped to /servlet/*
+ * This servlet support the following initParams:
+ * <PRE>
+ *  nonContextServlets       If false, the invoker can only load
+ *                           servlets from the contexts classloader.
+ *                           This is false by default and setting this
+ *                           to true may have security implications.
+ *
+ *  verbose                  If true, log dynamic loads
+ *
+ *  *                        All other parameters are copied to the
+ *                           each dynamic servlet as init parameters
  * </PRE>
  * @version $Id: Invoker.java 4780 2009-03-17 15:36:08Z jesse $
- * 
+ *
  */
 public class Invoker extends HttpServlet
 {
@@ -72,11 +72,11 @@ public class Invoker extends HttpServlet
 
     private ContextHandler _contextHandler;
     private ServletHandler _servletHandler;
-    private Map.Entry _invokerEntry;
-    private Map _parameters;
+    private Map.Entry<String, ServletHolder> _invokerEntry;
+    private Map<String, String> _parameters;
     private boolean _nonContextServlets;
     private boolean _verbose;
-        
+
     /* ------------------------------------------------------------ */
     public void init()
     {
@@ -87,10 +87,10 @@ public class Invoker extends HttpServlet
         while (handler!=null && !(handler instanceof ServletHandler) && (handler instanceof HandlerWrapper))
             handler=((HandlerWrapper)handler).getHandler();
         _servletHandler = (ServletHandler)handler;
-        Enumeration e = getInitParameterNames();
+        Enumeration<String> e = getInitParameterNames();
         while(e.hasMoreElements())
         {
-            String param=(String)e.nextElement();
+            String param=e.nextElement();
             String value=getInitParameter(param);
             String lvalue=value.toLowerCase(Locale.ENGLISH);
             if ("nonContextServlets".equals(param))
@@ -104,15 +104,15 @@ public class Invoker extends HttpServlet
             else
             {
                 if (_parameters==null)
-                    _parameters=new HashMap();
+                    _parameters=new HashMap<String, String>();
                 _parameters.put(param,value);
             }
         }
     }
-    
+
     /* ------------------------------------------------------------ */
     protected void service(HttpServletRequest request, HttpServletResponse response)
-	throws ServletException, IOException
+        throws ServletException, IOException
     {
         // Get the requested path and info
         boolean included=false;
@@ -124,7 +124,7 @@ public class Invoker extends HttpServlet
         String path_info = (String)request.getAttribute(Dispatcher.INCLUDE_PATH_INFO);
         if (path_info==null)
             path_info=request.getPathInfo();
-        
+
         // Get the servlet class
         String servlet = path_info;
         if (servlet==null || servlet.length()<=1 )
@@ -132,8 +132,8 @@ public class Invoker extends HttpServlet
             response.sendError(404);
             return;
         }
-        
-        
+
+
         int i0=servlet.charAt(0)=='/'?1:0;
         int i1=servlet.indexOf('/',i0);
         servlet=i1<0?servlet.substring(i0):servlet.substring(i0,i1);
@@ -141,7 +141,7 @@ public class Invoker extends HttpServlet
         // look for a named holder
         ServletHolder[] holders = _servletHandler.getServlets();
         ServletHolder holder = getHolder (holders, servlet);
-       
+
         if (holder!=null)
         {
             // Found a named servlet (from a user's web.xml file) so
@@ -151,7 +151,7 @@ public class Invoker extends HttpServlet
             ServletMapping mapping = new ServletMapping();
             mapping.setServletName(servlet);
             mapping.setPathSpec(URIUtil.addPaths(servlet_path,servlet)+"/*");
-            _servletHandler.setServletMappings((ServletMapping[])LazyList.addToArray(_servletHandler.getServletMappings(), mapping, ServletMapping.class));
+            _servletHandler.setServletMappings(ArrayUtil.addToArray(_servletHandler.getServletMappings(), mapping, ServletMapping.class));
         }
         else
         {
@@ -162,17 +162,17 @@ public class Invoker extends HttpServlet
             {
                 response.sendError(404);
                 return;
-            }   
-        
+            }
+
             synchronized(_servletHandler)
             {
                 // find the entry for the invoker (me)
                  _invokerEntry=_servletHandler.getHolderEntry(servlet_path);
-            
+
                 // Check for existing mapping (avoid threaded race).
                 String path=URIUtil.addPaths(servlet_path,servlet);
-                Map.Entry entry = _servletHandler.getHolderEntry(path);
-               
+                Map.Entry<String, ServletHolder> entry = _servletHandler.getHolderEntry(path);
+
                 if (entry!=null && !entry.equals(_invokerEntry))
                 {
                     // Use the holder
@@ -184,34 +184,34 @@ public class Invoker extends HttpServlet
                     if (LOG.isDebugEnabled())
                         LOG.debug("Making new servlet="+servlet+" with path="+path+"/*");
                     holder=_servletHandler.addServletWithMapping(servlet, path+"/*");
-                    
+
                     if (_parameters!=null)
                         holder.setInitParameters(_parameters);
-                    
+
                     try {holder.start();}
                     catch (Exception e)
                     {
                         LOG.debug(e);
                         throw new UnavailableException(e.toString());
                     }
-                    
+
                     // Check it is from an allowable classloader
                     if (!_nonContextServlets)
                     {
                         Object s=holder.getServlet();
-                        
+
                         if (_contextHandler.getClassLoader()!=
                             s.getClass().getClassLoader())
                         {
-                            try 
+                            try
                             {
                                 holder.stop();
-                            } 
-                            catch (Exception e) 
+                            }
+                            catch (Exception e)
                             {
                                 LOG.ignore(e);
                             }
-                            
+
                             LOG.warn("Dynamic servlet "+s+
                                          " not loaded from context "+
                                          request.getContextPath());
@@ -224,10 +224,10 @@ public class Invoker extends HttpServlet
                 }
             }
         }
-        
+
         if (holder!=null)
         {
-            final Request baseRequest=(request instanceof Request)?((Request)request):AbstractHttpConnection.getCurrentConnection().getRequest();
+            final Request baseRequest=(request instanceof Request)?((Request)request):HttpChannel.getCurrentHttpChannel().getRequest();
             holder.handle(baseRequest,
                     new InvokedRequest(request,included,servlet,servlet_path,path_info),
                           response);
@@ -237,8 +237,8 @@ public class Invoker extends HttpServlet
             LOG.info("Can't find holder for servlet: "+servlet);
             response.sendError(404);
         }
-            
-        
+
+
     }
 
     /* ------------------------------------------------------------ */
@@ -247,7 +247,7 @@ public class Invoker extends HttpServlet
         String _servletPath;
         String _pathInfo;
         boolean _included;
-        
+
         /* ------------------------------------------------------------ */
         InvokedRequest(HttpServletRequest request,
                 boolean included,
@@ -262,7 +262,7 @@ public class Invoker extends HttpServlet
             if (_pathInfo.length()==0)
                 _pathInfo=null;
         }
-        
+
         /* ------------------------------------------------------------ */
         public String getServletPath()
         {
@@ -270,7 +270,7 @@ public class Invoker extends HttpServlet
                 return super.getServletPath();
             return _servletPath;
         }
-        
+
         /* ------------------------------------------------------------ */
         public String getPathInfo()
         {
@@ -278,7 +278,7 @@ public class Invoker extends HttpServlet
                 return super.getPathInfo();
             return _pathInfo;
         }
-        
+
         /* ------------------------------------------------------------ */
         public Object getAttribute(String name)
         {
@@ -294,13 +294,13 @@ public class Invoker extends HttpServlet
             return super.getAttribute(name);
         }
     }
-    
-    
+
+
     private ServletHolder getHolder(ServletHolder[] holders, String servlet)
     {
         if (holders == null)
             return null;
-       
+
         ServletHolder holder = null;
         for (int i=0; holder==null && i<holders.length; i++)
         {
diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/JspPropertyGroupServlet.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/JspPropertyGroupServlet.java
index 1a6852b..db6345a 100644
--- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/JspPropertyGroupServlet.java
+++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/JspPropertyGroupServlet.java
@@ -27,8 +27,6 @@ import javax.servlet.ServletResponse;
 import javax.servlet.http.HttpServletRequest;
 
 import org.eclipse.jetty.server.Dispatcher;
-import org.eclipse.jetty.server.AbstractHttpConnection;
-import org.eclipse.jetty.server.Request;
 import org.eclipse.jetty.server.handler.ContextHandler;
 import org.eclipse.jetty.util.URIUtil;
 import org.eclipse.jetty.util.resource.Resource;
diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ListenerHolder.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ListenerHolder.java
new file mode 100644
index 0000000..7270394
--- /dev/null
+++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ListenerHolder.java
@@ -0,0 +1,65 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.servlet;
+
+import java.util.EventListener;
+
+/**
+ * ListenerHolder
+ *
+ * Specialization of AbstractHolder for servlet listeners. This
+ * allows us to record where the listener originated - web.xml,
+ * annotation, api etc.
+ */
+public class ListenerHolder extends BaseHolder<EventListener>
+{
+    private EventListener _listener;
+    
+
+    public ListenerHolder(Source source)
+    {
+        super(source);
+    }
+   
+    
+    public void setListener(EventListener listener)
+    {
+        _listener = listener;
+        setClassName(listener.getClass().getName());
+        setHeldClass(listener.getClass());
+        _extInstance=true;
+    }
+
+    public EventListener getListener()
+    {
+        return _listener;
+    }
+
+
+    @Override
+    public void doStart() throws Exception
+    {
+        //Listeners always have an instance eagerly created, it cannot be deferred to the doStart method
+        if (_listener == null)
+            throw new IllegalStateException("No listener instance");
+        
+        super.doStart();
+    }
+}
diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/NoJspServlet.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/NoJspServlet.java
index 3c2a1e8..65649e6 100644
--- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/NoJspServlet.java
+++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/NoJspServlet.java
@@ -28,9 +28,9 @@ import javax.servlet.http.HttpServletResponse;
 public class NoJspServlet extends HttpServlet
 {
     private boolean _warned;
-    
+
     /* ------------------------------------------------------------ */
-    /* 
+    /*
      * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
      */
     protected void doGet(HttpServletRequest req, HttpServletResponse response) throws ServletException, IOException
diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletContextHandler.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletContextHandler.java
index 8aa0219..a96df98 100644
--- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletContextHandler.java
+++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletContextHandler.java
@@ -35,7 +35,6 @@ import javax.servlet.Filter;
 import javax.servlet.FilterRegistration;
 import javax.servlet.RequestDispatcher;
 import javax.servlet.Servlet;
-import javax.servlet.ServletContext;
 import javax.servlet.ServletContextEvent;
 import javax.servlet.ServletContextListener;
 import javax.servlet.ServletException;
@@ -59,8 +58,10 @@ import org.eclipse.jetty.server.handler.ErrorHandler;
 import org.eclipse.jetty.server.handler.HandlerCollection;
 import org.eclipse.jetty.server.handler.HandlerWrapper;
 import org.eclipse.jetty.server.session.SessionHandler;
-import org.eclipse.jetty.util.LazyList;
-import org.eclipse.jetty.util.security.Constraint;
+import org.eclipse.jetty.servlet.BaseHolder.Source;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.component.LifeCycle;
 
 
 /* ------------------------------------------------------------ */
@@ -74,49 +75,48 @@ import org.eclipse.jetty.util.security.Constraint;
  * This class should have been called ServletContext, but this would have
  * cause confusion with {@link ServletContext}.
  */
+ at ManagedObject("Servlet Context Handler")
 public class ServletContextHandler extends ContextHandler
-{   
+{
     public final static int SESSIONS=1;
     public final static int SECURITY=2;
     public final static int NO_SESSIONS=0;
     public final static int NO_SECURITY=0;
+    
+    public interface ServletContainerInitializerCaller extends LifeCycle {};
 
-    protected final List<Decorator> _decorators= new ArrayList<Decorator>();
+    protected final List<Decorator> _decorators= new ArrayList<>();
     protected Class<? extends SecurityHandler> _defaultSecurityHandlerClass=org.eclipse.jetty.security.ConstraintSecurityHandler.class;
     protected SessionHandler _sessionHandler;
     protected SecurityHandler _securityHandler;
     protected ServletHandler _servletHandler;
-    protected HandlerWrapper _wrapper;
     protected int _options;
     protected JspConfigDescriptor _jspConfig;
-    protected Object _restrictedContextListeners;
-    private boolean _restrictListeners = true;
 
     /* ------------------------------------------------------------ */
     public ServletContextHandler()
     {
         this(null,null,null,null,null);
     }
-    
+
     /* ------------------------------------------------------------ */
     public ServletContextHandler(int options)
     {
         this(null,null,options);
     }
-    
+
     /* ------------------------------------------------------------ */
     public ServletContextHandler(HandlerContainer parent, String contextPath)
     {
         this(parent,contextPath,null,null,null,null);
     }
-    
+
     /* ------------------------------------------------------------ */
     public ServletContextHandler(HandlerContainer parent, String contextPath, int options)
     {
-        this(parent,contextPath,null,null,null,null);
-        _options=options;
+        this(parent,contextPath,null,null,null,null,options);
     }
-    
+
     /* ------------------------------------------------------------ */
     public ServletContextHandler(HandlerContainer parent, String contextPath, boolean sessions, boolean security)
     {
@@ -125,31 +125,86 @@ public class ServletContextHandler extends ContextHandler
 
     /* ------------------------------------------------------------ */
     public ServletContextHandler(HandlerContainer parent, SessionHandler sessionHandler, SecurityHandler securityHandler, ServletHandler servletHandler, ErrorHandler errorHandler)
-    {   
+    {
         this(parent,null,sessionHandler,securityHandler,servletHandler,errorHandler);
     }
 
     /* ------------------------------------------------------------ */
     public ServletContextHandler(HandlerContainer parent, String contextPath, SessionHandler sessionHandler, SecurityHandler securityHandler, ServletHandler servletHandler, ErrorHandler errorHandler)
-    {   
+    {
+        this(parent,contextPath,sessionHandler,securityHandler,servletHandler,errorHandler,0);
+    }
+    
+    public ServletContextHandler(HandlerContainer parent, String contextPath, SessionHandler sessionHandler, SecurityHandler securityHandler, ServletHandler servletHandler, ErrorHandler errorHandler,int options)
+    {
         super((ContextHandler.Context)null);
+        _options=options;
         _scontext = new Context();
         _sessionHandler = sessionHandler;
         _securityHandler = securityHandler;
         _servletHandler = servletHandler;
-            
-        if (errorHandler!=null)
-            setErrorHandler(errorHandler);
 
         if (contextPath!=null)
             setContextPath(contextPath);
-
+        
         if (parent instanceof HandlerWrapper)
             ((HandlerWrapper)parent).setHandler(this);
         else if (parent instanceof HandlerCollection)
             ((HandlerCollection)parent).addHandler(this);
-    }    
+        
+        
+        // Link the handlers
+        relinkHandlers();
+        
+        if (errorHandler!=null)
+            setErrorHandler(errorHandler);
+
+    }
+    
+    /* ------------------------------------------------------------ */
+    private void relinkHandlers()
+    {
+        HandlerWrapper handler=this;
+        
+        // Skip any injected handlers
+        while (handler.getHandler() instanceof HandlerWrapper)
+        {
+            HandlerWrapper wrapper = (HandlerWrapper)handler.getHandler();
+            if (wrapper instanceof SessionHandler ||
+                wrapper instanceof SecurityHandler ||
+                wrapper instanceof ServletHandler)
+                break;
+            handler=wrapper;
+        }
+        
+        if (getSessionHandler()!=null)
+        {
+            if (handler==this)
+                super.setHandler(_sessionHandler);
+            else
+                handler.setHandler(_sessionHandler);
+            handler=_sessionHandler;
+        }
 
+        if (getSecurityHandler()!=null)
+        {
+            if (handler==this)
+                super.setHandler(_securityHandler);
+            else
+                handler.setHandler(_securityHandler);
+            handler=_securityHandler;
+        }
+
+        if (getServletHandler()!=null)
+        {
+            if (handler==this)
+                super.setHandler(_servletHandler);
+            else
+                handler.setHandler(_servletHandler);
+            handler=_servletHandler;
+        } 
+    }
+    
     /* ------------------------------------------------------------ */
     /**
      * @see org.eclipse.jetty.server.handler.ContextHandler#doStop()
@@ -160,8 +215,6 @@ public class ServletContextHandler extends ContextHandler
         super.doStop();
         if (_decorators != null)
             _decorators.clear();
-        if (_wrapper != null)
-            _wrapper.setHandler(null);
     }
 
     /* ------------------------------------------------------------ */
@@ -187,7 +240,7 @@ public class ServletContextHandler extends ContextHandler
     {
         return new SessionHandler();
     }
-    
+
     /* ------------------------------------------------------------ */
     protected SecurityHandler newSecurityHandler()
     {
@@ -210,71 +263,51 @@ public class ServletContextHandler extends ContextHandler
     /* ------------------------------------------------------------ */
     /**
      * Finish constructing handlers and link them together.
-     * 
+     *
      * @see org.eclipse.jetty.server.handler.ContextHandler#startContext()
      */
+    @Override
     protected void startContext() throws Exception
     {
-        // force creation of missing handlers.
-        getSessionHandler();
-        getSecurityHandler();
-        getServletHandler();
-        
-        Handler handler = _servletHandler;
-        if (_securityHandler!=null)
-        {
-            _securityHandler.setHandler(handler);
-            handler=_securityHandler;
-        }
-        
-        if (_sessionHandler!=null)
-        {
-            _sessionHandler.setHandler(handler);
-            handler=_sessionHandler;
-        }
-        
-        // skip any wrapped handlers 
-        _wrapper=this;
-        while (_wrapper!=handler && _wrapper.getHandler() instanceof HandlerWrapper)
-            _wrapper=(HandlerWrapper)_wrapper.getHandler();
-        
-        // if we are not already linked
-        if (_wrapper!=handler)
+        ServletContainerInitializerCaller sciBean = getBean(ServletContainerInitializerCaller.class);
+        if (sciBean!=null)
+            sciBean.start();
+
+        if (_servletHandler != null)
         {
-            if (_wrapper.getHandler()!=null )
-                throw new IllegalStateException("!ScopedHandler");
-            _wrapper.setHandler(handler);
+            //Call decorators on all holders, and also on any EventListeners before
+            //decorators are called on any other classes (like servlets and filters)
+            for (int i=_decorators.size()-1;i>=0; i--)
+            {
+                Decorator decorator = _decorators.get(i);
+                //Do any decorations on the ListenerHolders AND the listener instances first up
+                if (_servletHandler.getListeners()!=null)
+                {
+                    for (ListenerHolder holder:_servletHandler.getListeners())
+                    {             
+                        decorator.decorate(holder.getListener());
+                    }
+                }
+            }
         }
         
-    	super.startContext();
-
-    	// OK to Initialize servlet handler now
-    	if (_servletHandler != null && _servletHandler.isStarted())
-    	{
-    	    for (int i=_decorators.size()-1;i>=0; i--)
-    	    {
-    	        Decorator decorator = _decorators.get(i);
-                if (_servletHandler.getFilters()!=null)
-                    for (FilterHolder holder:_servletHandler.getFilters())
-                        decorator.decorateFilterHolder(holder);
-    	        if(_servletHandler.getServlets()!=null)
-    	            for (ServletHolder holder:_servletHandler.getServlets())
-    	                decorator.decorateServletHolder(holder);
-    	    }   
-    	        
-    	    _servletHandler.initialize();
-    	}
+        super.startContext();
+
+        // OK to Initialize servlet handler now that all relevant object trees have been started
+        if (_servletHandler != null)
+            _servletHandler.initialize();
     }
 
     /* ------------------------------------------------------------ */
     /**
      * @return Returns the securityHandler.
      */
+    @ManagedAttribute(value="context security handler", readonly=true)
     public SecurityHandler getSecurityHandler()
     {
-        if (_securityHandler==null && (_options&SECURITY)!=0 && !isStarted()) 
+        if (_securityHandler==null && (_options&SECURITY)!=0 && !isStarted())
             _securityHandler=newSecurityHandler();
-        
+
         return _securityHandler;
     }
 
@@ -282,9 +315,10 @@ public class ServletContextHandler extends ContextHandler
     /**
      * @return Returns the servletHandler.
      */
+    @ManagedAttribute(value="context servlet handler", readonly=true)
     public ServletHandler getServletHandler()
     {
-        if (_servletHandler==null && !isStarted()) 
+        if (_servletHandler==null && !isStarted())
             _servletHandler=newServletHandler();
         return _servletHandler;
     }
@@ -293,9 +327,10 @@ public class ServletContextHandler extends ContextHandler
     /**
      * @return Returns the sessionHandler.
      */
+    @ManagedAttribute(value="context session handler", readonly=true)
     public SessionHandler getSessionHandler()
     {
-        if (_sessionHandler==null && (_options&SESSIONS)!=0 && !isStarted()) 
+        if (_sessionHandler==null && (_options&SESSIONS)!=0 && !isStarted())
             _sessionHandler=newSessionHandler();
         return _sessionHandler;
     }
@@ -315,7 +350,7 @@ public class ServletContextHandler extends ContextHandler
     {
         return getServletHandler().addServletWithMapping(servlet.getName(), pathSpec);
     }
-    
+
     /* ------------------------------------------------------------ */
     /** conveniance method to add a servlet.
      */
@@ -397,30 +432,15 @@ public class ServletContextHandler extends ContextHandler
         return Collections.emptySet();
     }
 
-
-    
-    public void restrictEventListener (EventListener e)
-    {
-        if (_restrictListeners && e instanceof ServletContextListener)
-            _restrictedContextListeners = LazyList.add(_restrictedContextListeners, e);
-    }
-
-    public boolean isRestrictListeners() {
-        return _restrictListeners;
-    }
-
-    public void setRestrictListeners(boolean restrictListeners) {
-        this._restrictListeners = restrictListeners;
-    }
-
+    @Override
     public void callContextInitialized(ServletContextListener l, ServletContextEvent e)
     {
         try
         {
             //toggle state of the dynamic API so that the listener cannot use it
-            if (LazyList.contains(_restrictedContextListeners, l))
+            if(isProgrammaticListener(l))
                 this.getServletContext().setEnabled(false);
-            
+
             super.callContextInitialized(l, e);
         }
         finally
@@ -430,14 +450,13 @@ public class ServletContextHandler extends ContextHandler
         }
     }
 
-    
+
+    @Override
     public void callContextDestroyed(ServletContextListener l, ServletContextEvent e)
     {
         super.callContextDestroyed(l, e);
     }
 
-  
-
     /* ------------------------------------------------------------ */
     /**
      * @param sessionHandler The sessionHandler to set.
@@ -447,7 +466,11 @@ public class ServletContextHandler extends ContextHandler
         if (isStarted())
             throw new IllegalStateException("STARTED");
 
+        if (_sessionHandler!=null)
+            _sessionHandler.setHandler(null);
+
         _sessionHandler = sessionHandler;
+        relinkHandlers();
     }
 
     /* ------------------------------------------------------------ */
@@ -459,7 +482,10 @@ public class ServletContextHandler extends ContextHandler
         if (isStarted())
             throw new IllegalStateException("STARTED");
 
+        if (_securityHandler!=null)
+            _securityHandler.setHandler(null);
         _securityHandler = securityHandler;
+        relinkHandlers();
     }
 
     /* ------------------------------------------------------------ */
@@ -471,7 +497,59 @@ public class ServletContextHandler extends ContextHandler
         if (isStarted())
             throw new IllegalStateException("STARTED");
 
+        Handler next=null;
+        if (_servletHandler!=null)
+        {
+            next=_servletHandler.getHandler();
+            _servletHandler.setHandler(null);
+        }
         _servletHandler = servletHandler;
+        relinkHandlers();
+        _servletHandler.setHandler(next);
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public void setHandler(Handler handler)
+    {
+        if (handler instanceof ServletHandler)
+            setServletHandler((ServletHandler) handler);
+        else if (handler instanceof SessionHandler)
+            setSessionHandler((SessionHandler) handler);
+        else if (handler instanceof SecurityHandler)
+            setSecurityHandler((SecurityHandler)handler);
+        else if (handler == null || handler instanceof HandlerWrapper)
+        {
+            super.setHandler(handler);
+            relinkHandlers();
+        }
+        else
+            throw new IllegalArgumentException();
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Insert a HandlerWrapper before the first Session,Security or ServletHandler
+     * but after any other HandlerWrappers.
+     */
+    public void insertHandler(HandlerWrapper handler)
+    {
+        HandlerWrapper h=this;
+        
+        // Skip any injected handlers
+        while (h.getHandler() instanceof HandlerWrapper)
+        {
+            HandlerWrapper wrapper = (HandlerWrapper)h.getHandler();
+            if (wrapper instanceof SessionHandler ||
+                wrapper instanceof SecurityHandler ||
+                wrapper instanceof ServletHandler)
+                break;
+            h=wrapper;
+        }
+        
+        h.setHandler(handler);
+        relinkHandlers();
     }
 
     /* ------------------------------------------------------------ */
@@ -492,7 +570,7 @@ public class ServletContextHandler extends ContextHandler
         _decorators.clear();
         _decorators.addAll(decorators);
     }
-    
+
     /* ------------------------------------------------------------ */
     /**
      * @param decorator The decorator to add
@@ -506,16 +584,16 @@ public class ServletContextHandler extends ContextHandler
     void destroyServlet(Servlet servlet)
     {
         for (Decorator decorator : _decorators)
-            decorator.destroyServletInstance(servlet);
+            decorator.destroy(servlet);
     }
 
     /* ------------------------------------------------------------ */
     void destroyFilter(Filter filter)
     {
         for (Decorator decorator : _decorators)
-            decorator.destroyFilterInstance(filter);
+            decorator.destroy(filter);
     }
-    
+
     /* ------------------------------------------------------------ */
     public static class JspPropertyGroup implements JspPropertyGroupDescriptor
     {
@@ -531,44 +609,44 @@ public class ServletContextHandler extends ContextHandler
         private String _defaultContentType;
         private String _buffer;
         private String _errorOnUndeclaredNamespace;
-        
-        
 
-        /** 
+
+
+        /**
          * @see javax.servlet.descriptor.JspPropertyGroupDescriptor#getUrlPatterns()
          */
         public Collection<String> getUrlPatterns()
         {
             return new ArrayList<String>(_urlPatterns); // spec says must be a copy
         }
-        
+
         public void addUrlPattern (String s)
         {
             if (!_urlPatterns.contains(s))
                 _urlPatterns.add(s);
         }
 
-        /** 
+        /**
          * @see javax.servlet.descriptor.JspPropertyGroupDescriptor#getElIgnored()
          */
         public String getElIgnored()
         {
             return _elIgnored;
         }
-        
+
         public void setElIgnored (String s)
         {
             _elIgnored = s;
         }
 
-        /** 
+        /**
          * @see javax.servlet.descriptor.JspPropertyGroupDescriptor#getPageEncoding()
          */
         public String getPageEncoding()
         {
             return _pageEncoding;
         }
-        
+
         public void setPageEncoding(String pageEncoding)
         {
             _pageEncoding = pageEncoding;
@@ -609,7 +687,7 @@ public class ServletContextHandler extends ContextHandler
             _errorOnUndeclaredNamespace = errorOnUndeclaredNamespace;
         }
 
-        /** 
+        /**
          * @see javax.servlet.descriptor.JspPropertyGroupDescriptor#getScriptingInvalid()
          */
         public String getScriptingInvalid()
@@ -617,7 +695,7 @@ public class ServletContextHandler extends ContextHandler
             return _scriptingInvalid;
         }
 
-        /** 
+        /**
          * @see javax.servlet.descriptor.JspPropertyGroupDescriptor#getIsXml()
          */
         public String getIsXml()
@@ -625,21 +703,21 @@ public class ServletContextHandler extends ContextHandler
             return _isXml;
         }
 
-        /** 
+        /**
          * @see javax.servlet.descriptor.JspPropertyGroupDescriptor#getIncludePreludes()
          */
         public Collection<String> getIncludePreludes()
         {
             return new ArrayList<String>(_includePreludes); //must be a copy
         }
-        
+
         public void addIncludePrelude(String prelude)
         {
             if (!_includePreludes.contains(prelude))
                 _includePreludes.add(prelude);
         }
 
-        /** 
+        /**
          * @see javax.servlet.descriptor.JspPropertyGroupDescriptor#getIncludeCodas()
          */
         public Collection<String> getIncludeCodas()
@@ -652,8 +730,8 @@ public class ServletContextHandler extends ContextHandler
             if (!_includeCodas.contains(coda))
                 _includeCodas.add(coda);
         }
-        
-        /** 
+
+        /**
          * @see javax.servlet.descriptor.JspPropertyGroupDescriptor#getDeferredSyntaxAllowedAsLiteral()
          */
         public String getDeferredSyntaxAllowedAsLiteral()
@@ -661,7 +739,7 @@ public class ServletContextHandler extends ContextHandler
             return _deferredSyntaxAllowedAsLiteral;
         }
 
-        /** 
+        /**
          * @see javax.servlet.descriptor.JspPropertyGroupDescriptor#getTrimDirectiveWhitespaces()
          */
         public String getTrimDirectiveWhitespaces()
@@ -669,7 +747,7 @@ public class ServletContextHandler extends ContextHandler
             return _trimDirectiveWhitespaces;
         }
 
-        /** 
+        /**
          * @see javax.servlet.descriptor.JspPropertyGroupDescriptor#getDefaultContentType()
          */
         public String getDefaultContentType()
@@ -677,7 +755,7 @@ public class ServletContextHandler extends ContextHandler
             return _defaultContentType;
         }
 
-        /** 
+        /**
          * @see javax.servlet.descriptor.JspPropertyGroupDescriptor#getBuffer()
          */
         public String getBuffer()
@@ -685,14 +763,14 @@ public class ServletContextHandler extends ContextHandler
             return _buffer;
         }
 
-        /** 
+        /**
          * @see javax.servlet.descriptor.JspPropertyGroupDescriptor#getErrorOnUndeclaredNamespace()
          */
         public String getErrorOnUndeclaredNamespace()
         {
             return _errorOnUndeclaredNamespace;
         }
-        
+
         public String toString ()
         {
             StringBuffer sb = new StringBuffer();
@@ -713,80 +791,80 @@ public class ServletContextHandler extends ContextHandler
             return sb.toString();
         }
     }
-    
+
     /* ------------------------------------------------------------ */
     public static class TagLib implements TaglibDescriptor
     {
         private String _uri;
         private String _location;
 
-        /** 
+        /**
          * @see javax.servlet.descriptor.TaglibDescriptor#getTaglibURI()
          */
         public String getTaglibURI()
         {
            return _uri;
         }
-        
+
         public void setTaglibURI(String uri)
         {
             _uri = uri;
         }
 
-        /** 
+        /**
          * @see javax.servlet.descriptor.TaglibDescriptor#getTaglibLocation()
          */
         public String getTaglibLocation()
         {
             return _location;
         }
-        
+
         public void setTaglibLocation(String location)
         {
             _location = location;
         }
-    
+
         public String toString()
         {
             return ("TagLibDescriptor: taglib-uri="+_uri+" location="+_location);
         }
     }
-    
-    
+
+
     /* ------------------------------------------------------------ */
     public static class JspConfig implements JspConfigDescriptor
     {
         private List<TaglibDescriptor> _taglibs = new ArrayList<TaglibDescriptor>();
         private List<JspPropertyGroupDescriptor> _jspPropertyGroups = new ArrayList<JspPropertyGroupDescriptor>();
-        
+
         public JspConfig() {}
 
-        /** 
+        /**
          * @see javax.servlet.descriptor.JspConfigDescriptor#getTaglibs()
          */
         public Collection<TaglibDescriptor> getTaglibs()
         {
             return new ArrayList<TaglibDescriptor>(_taglibs);
         }
-        
+
         public void addTaglibDescriptor (TaglibDescriptor d)
         {
             _taglibs.add(d);
         }
 
-        /** 
+        /**
          * @see javax.servlet.descriptor.JspConfigDescriptor#getJspPropertyGroups()
          */
         public Collection<JspPropertyGroupDescriptor> getJspPropertyGroups()
         {
            return new ArrayList<JspPropertyGroupDescriptor>(_jspPropertyGroups);
         }
-        
+
         public void addJspPropertyGroup(JspPropertyGroupDescriptor g)
         {
             _jspPropertyGroups.add(g);
         }
-        
+
         public String toString()
         {
             StringBuffer sb = new StringBuffer();
@@ -798,13 +876,13 @@ public class ServletContextHandler extends ContextHandler
             return sb.toString();
         }
     }
-    
-    
+
+
     /* ------------------------------------------------------------ */
     public class Context extends ContextHandler.Context
     {
         /* ------------------------------------------------------------ */
-        /* 
+        /*
          * @see javax.servlet.ServletContext#getNamedDispatcher(java.lang.String)
          */
         @Override
@@ -818,7 +896,7 @@ public class ServletContextHandler extends ContextHandler
                 return null;
             return new Dispatcher(context, name);
         }
-        
+
         /* ------------------------------------------------------------ */
         /**
          * @since servlet-api-3.0
@@ -829,6 +907,9 @@ public class ServletContextHandler extends ContextHandler
             if (isStarted())
                 throw new IllegalStateException();
             
+            if (filterName == null || "".equals(filterName.trim()))
+                throw new IllegalStateException("Missing filter name");
+
             if (!_enabled)
                 throw new UnsupportedOperationException();
 
@@ -837,7 +918,7 @@ public class ServletContextHandler extends ContextHandler
             if (holder == null)
             {
                 //new filter
-                holder = handler.newFilterHolder(Holder.Source.JAVAX_API);
+                holder = handler.newFilterHolder(Source.JAVAX_API);
                 holder.setName(filterName);
                 holder.setHeldClass(filterClass);
                 handler.addFilter(holder);
@@ -863,6 +944,9 @@ public class ServletContextHandler extends ContextHandler
             if (isStarted())
                 throw new IllegalStateException();
             
+            if (filterName == null || "".equals(filterName.trim()))
+                throw new IllegalStateException("Missing filter name");
+
             if (!_enabled)
                 throw new UnsupportedOperationException();
 
@@ -871,7 +955,7 @@ public class ServletContextHandler extends ContextHandler
             if (holder == null)
             {
                 //new filter
-                holder = handler.newFilterHolder(Holder.Source.JAVAX_API);
+                holder = handler.newFilterHolder(Source.JAVAX_API);
                 holder.setName(filterName);
                 holder.setClassName(className);
                 handler.addFilter(holder);
@@ -898,21 +982,24 @@ public class ServletContextHandler extends ContextHandler
             if (isStarted())
                 throw new IllegalStateException();
 
+            if (filterName == null || "".equals(filterName.trim()))
+                throw new IllegalStateException("Missing filter name");
+            
             if (!_enabled)
                 throw new UnsupportedOperationException();
-            
+
             final ServletHandler handler = ServletContextHandler.this.getServletHandler();
             FilterHolder holder = handler.getFilter(filterName);
             if (holder == null)
             {
                 //new filter
-                holder = handler.newFilterHolder(Holder.Source.JAVAX_API);
+                holder = handler.newFilterHolder(Source.JAVAX_API);
                 holder.setName(filterName);
                 holder.setFilter(filter);
                 handler.addFilter(holder);
                 return holder.getRegistration();
             }
-            
+
             if (holder.getClassName()==null && holder.getHeldClass()==null)
             {
                 //preliminary filter registration completion
@@ -922,7 +1009,7 @@ public class ServletContextHandler extends ContextHandler
             else
                 return null; //existing filter
         }
-        
+
         /* ------------------------------------------------------------ */
         /**
          * @since servlet-api-3.0
@@ -932,16 +1019,19 @@ public class ServletContextHandler extends ContextHandler
         {
             if (!isStarting())
                 throw new IllegalStateException();
+
+            if (servletName == null || "".equals(servletName.trim()))
+                throw new IllegalStateException("Missing servlet name");
             
             if (!_enabled)
                 throw new UnsupportedOperationException();
-            
+
             final ServletHandler handler = ServletContextHandler.this.getServletHandler();
             ServletHolder holder = handler.getServlet(servletName);
             if (holder == null)
             {
                 //new servlet
-                holder = handler.newServletHolder(Holder.Source.JAVAX_API);
+                holder = handler.newServletHolder(Source.JAVAX_API);
                 holder.setName(servletName);
                 holder.setHeldClass(servletClass);
                 handler.addServlet(holder);
@@ -955,7 +1045,7 @@ public class ServletContextHandler extends ContextHandler
                 return holder.getRegistration();
             }
             else
-                return null; //existing completed registration for servlet name      
+                return null; //existing completed registration for servlet name
         }
 
         /* ------------------------------------------------------------ */
@@ -967,17 +1057,20 @@ public class ServletContextHandler extends ContextHandler
         {
             if (!isStarting())
                 throw new IllegalStateException();
+
+            if (servletName == null || "".equals(servletName.trim()))
+                throw new IllegalStateException("Missing servlet name");
             
             if (!_enabled)
                 throw new UnsupportedOperationException();
 
 
-            final ServletHandler handler = ServletContextHandler.this.getServletHandler();            
+            final ServletHandler handler = ServletContextHandler.this.getServletHandler();
             ServletHolder holder = handler.getServlet(servletName);
             if (holder == null)
             {
                 //new servlet
-                holder = handler.newServletHolder(Holder.Source.JAVAX_API);
+                holder = handler.newServletHolder(Source.JAVAX_API);
                 holder.setName(servletName);
                 holder.setClassName(className);
                 handler.addServlet(holder);
@@ -987,7 +1080,7 @@ public class ServletContextHandler extends ContextHandler
             //complete a partial registration
             if (holder.getClassName()==null && holder.getHeldClass()==null)
             {
-                holder.setClassName(className); 
+                holder.setClassName(className);
                 return holder.getRegistration();
             }
             else
@@ -1003,7 +1096,10 @@ public class ServletContextHandler extends ContextHandler
         {
             if (!isStarting())
                 throw new IllegalStateException();
-
+            
+            if (servletName == null || "".equals(servletName.trim()))
+                throw new IllegalStateException("Missing servlet name");
+            
             if (!_enabled)
                 throw new UnsupportedOperationException();
 
@@ -1011,13 +1107,13 @@ public class ServletContextHandler extends ContextHandler
             ServletHolder holder = handler.getServlet(servletName);
             if (holder == null)
             {
-                holder = handler.newServletHolder(Holder.Source.JAVAX_API);
+                holder = handler.newServletHolder(Source.JAVAX_API);
                 holder.setName(servletName);
                 holder.setServlet(servlet);
                 handler.addServlet(holder);
                 return dynamicHolderAdded(holder);
             }
-            
+
             //complete a partial registration
             if (holder.getClassName()==null && holder.getHeldClass()==null)
             {
@@ -1032,13 +1128,12 @@ public class ServletContextHandler extends ContextHandler
         @Override
         public boolean setInitParameter(String name, String value)
         {
-            // TODO other started conditions
             if (!isStarting())
                 throw new IllegalStateException();
-            
+
             if (!_enabled)
                 throw new UnsupportedOperationException();
-            
+
             return super.setInitParameter(name,value);
         }
 
@@ -1048,19 +1143,15 @@ public class ServletContextHandler extends ContextHandler
         {
             try
             {
-                T f = c.newInstance();
+                T f = createInstance(c);
                 for (int i=_decorators.size()-1; i>=0; i--)
                 {
                     Decorator decorator = _decorators.get(i);
-                    f=decorator.decorateFilterInstance(f);
+                    f=decorator.decorate(f);
                 }
                 return f;
             }
-            catch (InstantiationException e)
-            {
-                throw new ServletException(e);
-            }
-            catch (IllegalAccessException e)
+            catch (Exception e)
             {
                 throw new ServletException(e);
             }
@@ -1072,23 +1163,20 @@ public class ServletContextHandler extends ContextHandler
         {
             try
             {
-                T s = c.newInstance();
+                T s = createInstance(c);
                 for (int i=_decorators.size()-1; i>=0; i--)
                 {
                     Decorator decorator = _decorators.get(i);
-                    s=decorator.decorateServletInstance(s);
+                    s=decorator.decorate(s);
                 }
                 return s;
             }
-            catch (InstantiationException e)
-            {
-                throw new ServletException(e);
-            }
-            catch (IllegalAccessException e)
+            catch (Exception e)
             {
                 throw new ServletException(e);
             }
         }
+        
 
         @Override
         public Set<SessionTrackingMode> getDefaultSessionTrackingModes()
@@ -1111,7 +1199,7 @@ public class ServletContextHandler extends ContextHandler
         {
             if (!_enabled)
                 throw new UnsupportedOperationException();
-            
+
             final FilterHolder holder=ServletContextHandler.this.getServletHandler().getFilter(filterName);
             return (holder==null)?null:holder.getRegistration();
         }
@@ -1121,7 +1209,7 @@ public class ServletContextHandler extends ContextHandler
         {
             if (!_enabled)
                 throw new UnsupportedOperationException();
-            
+
             HashMap<String, FilterRegistration> registrations = new HashMap<String, FilterRegistration>();
             ServletHandler handler=ServletContextHandler.this.getServletHandler();
             FilterHolder[] holders=handler.getFilters();
@@ -1138,7 +1226,7 @@ public class ServletContextHandler extends ContextHandler
         {
             if (!_enabled)
                 throw new UnsupportedOperationException();
-            
+
             final ServletHolder holder=ServletContextHandler.this.getServletHandler().getServlet(servletName);
             return (holder==null)?null:holder.getRegistration();
         }
@@ -1148,7 +1236,7 @@ public class ServletContextHandler extends ContextHandler
         {
             if (!_enabled)
                 throw new UnsupportedOperationException();
-            
+
             HashMap<String, ServletRegistration> registrations = new HashMap<String, ServletRegistration>();
             ServletHandler handler=ServletContextHandler.this.getServletHandler();
             ServletHolder[] holders=handler.getServlets();
@@ -1163,10 +1251,9 @@ public class ServletContextHandler extends ContextHandler
         @Override
         public SessionCookieConfig getSessionCookieConfig()
         {
-            // TODO other started conditions
             if (!_enabled)
                 throw new UnsupportedOperationException();
-            
+
             if (_sessionHandler!=null)
                 return _sessionHandler.getSessionManager().getSessionCookieConfig();
             return null;
@@ -1175,13 +1262,12 @@ public class ServletContextHandler extends ContextHandler
         @Override
         public void setSessionTrackingModes(Set<SessionTrackingMode> sessionTrackingModes)
         {
-            // TODO other started conditions
             if (!isStarting())
                 throw new IllegalStateException();
             if (!_enabled)
                 throw new UnsupportedOperationException();
-            
-            
+
+
             if (_sessionHandler!=null)
                 _sessionHandler.getSessionManager().setSessionTrackingModes(sessionTrackingModes);
         }
@@ -1189,7 +1275,6 @@ public class ServletContextHandler extends ContextHandler
         @Override
         public void addListener(String className)
         {
-            // TODO other started conditions
             if (!isStarting())
                 throw new IllegalStateException();
             if (!_enabled)
@@ -1200,18 +1285,19 @@ public class ServletContextHandler extends ContextHandler
         @Override
         public <T extends EventListener> void addListener(T t)
         {
-            // TODO other started conditions
             if (!isStarting())
                 throw new IllegalStateException();
             if (!_enabled)
                 throw new UnsupportedOperationException();
             super.addListener(t);
+            ListenerHolder holder = getServletHandler().newListenerHolder(Source.JAVAX_API);
+            holder.setListener(t);
+            getServletHandler().addListener(holder);
         }
 
         @Override
         public void addListener(Class<? extends EventListener> listenerClass)
         {
-            // TODO other started conditions
             if (!isStarting())
                 throw new IllegalStateException();
             if (!_enabled)
@@ -1224,20 +1310,15 @@ public class ServletContextHandler extends ContextHandler
         {
             try
             {
-                T l = super.createListener(clazz);
-
+                T l = createInstance(clazz);
                 for (int i=_decorators.size()-1; i>=0; i--)
                 {
                     Decorator decorator = _decorators.get(i);
-                    l=decorator.decorateListenerInstance(l);
+                    l=decorator.decorate(l);
                 }
                 return l;
-            }
-            catch(ServletException e)
-            {
-                throw e;
-            }
-            catch(Exception e)
+            }            
+            catch (Exception e)
             {
                 throw new ServletException(e);
             }
@@ -1249,14 +1330,14 @@ public class ServletContextHandler extends ContextHandler
         {
             return _jspConfig;
         }
-        
+
         @Override
         public void setJspConfigDescriptor(JspConfigDescriptor d)
         {
             _jspConfig = d;
         }
-        
-        
+
+
         @Override
         public void declareRoles(String... roleNames)
         {
@@ -1278,15 +1359,7 @@ public class ServletContextHandler extends ContextHandler
      */
     public interface Decorator
     {
-        <T extends Filter> T decorateFilterInstance(T filter) throws ServletException;
-        <T extends Servlet> T decorateServletInstance(T servlet) throws ServletException;
-        <T extends EventListener> T decorateListenerInstance(T listener) throws ServletException;
-
-        void decorateFilterHolder(FilterHolder filter) throws ServletException;
-        void decorateServletHolder(ServletHolder servlet) throws ServletException;
-        
-        void destroyServletInstance(Servlet s);
-        void destroyFilterInstance(Filter f);
-        void destroyListenerInstance(EventListener f);
+        <T> T decorate (T o);
+        void destroy (Object o);
     }
 }
diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java
index 11ca5cd..c3b152c 100644
--- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java
+++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java
@@ -25,16 +25,15 @@ import java.util.Collections;
 import java.util.EnumSet;
 import java.util.HashMap;
 import java.util.HashSet;
-import java.util.ListIterator;
-import java.util.Set;
 import java.util.List;
+import java.util.ListIterator;
 import java.util.Map;
 import java.util.Queue;
+import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.concurrent.ConcurrentMap;
 
-import javax.servlet.AsyncContext;
 import javax.servlet.DispatcherType;
 import javax.servlet.Filter;
 import javax.servlet.FilterChain;
@@ -47,33 +46,34 @@ import javax.servlet.ServletRequest;
 import javax.servlet.ServletResponse;
 import javax.servlet.ServletSecurityElement;
 import javax.servlet.UnavailableException;
+import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import org.eclipse.jetty.continuation.ContinuationThrowable;
-import org.eclipse.jetty.http.HttpException;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpHeaderValue;
 import org.eclipse.jetty.http.PathMap;
 import org.eclipse.jetty.io.EofException;
 import org.eclipse.jetty.io.RuntimeIOException;
 import org.eclipse.jetty.security.IdentityService;
 import org.eclipse.jetty.security.SecurityHandler;
-import org.eclipse.jetty.server.AbstractHttpConnection;
-import org.eclipse.jetty.server.AsyncContinuation;
-import org.eclipse.jetty.server.Dispatcher;
-import org.eclipse.jetty.server.AbstractHttpConnection;
+import org.eclipse.jetty.server.HttpChannel;
+import org.eclipse.jetty.server.QuietServletException;
 import org.eclipse.jetty.server.Request;
-import org.eclipse.jetty.server.Server;
 import org.eclipse.jetty.server.ServletRequestHttpWrapper;
 import org.eclipse.jetty.server.ServletResponseHttpWrapper;
 import org.eclipse.jetty.server.UserIdentity;
 import org.eclipse.jetty.server.handler.ContextHandler;
 import org.eclipse.jetty.server.handler.ScopedHandler;
-import org.eclipse.jetty.servlet.Holder.Source;
+import org.eclipse.jetty.servlet.BaseHolder.Source;
+import org.eclipse.jetty.util.ArrayUtil;
 import org.eclipse.jetty.util.LazyList;
 import org.eclipse.jetty.util.MultiException;
 import org.eclipse.jetty.util.MultiMap;
-import org.eclipse.jetty.util.TypeUtil;
 import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.component.LifeCycle;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
 
@@ -89,16 +89,21 @@ import org.eclipse.jetty.util.log.Logger;
  * Unless run as part of a {@link ServletContextHandler} or derivative, the {@link #initialize()}
  * method must be called manually after start().
  */
+
+/* ------------------------------------------------------------ */
+/**
+ */
+ at ManagedObject("Servlet Handler")
 public class ServletHandler extends ScopedHandler
 {
     private static final Logger LOG = Log.getLogger(ServletHandler.class);
 
     /* ------------------------------------------------------------ */
     public static final String __DEFAULT_SERVLET="default";
-        
+
     /* ------------------------------------------------------------ */
     private ServletContextHandler _contextHandler;
-    private ContextHandler.Context _servletContext;
+    private ServletContext _servletContext;
     private FilterHolder[] _filters=new FilterHolder[0];
     private FilterMapping[] _filterMappings;
     private int _matchBeforeIndex = -1; //index of last programmatic FilterMapping with isMatchAfter=false
@@ -106,73 +111,63 @@ public class ServletHandler extends ScopedHandler
     private boolean _filterChainsCached=true;
     private int _maxFilterChainsCacheSize=512;
     private boolean _startWithUnavailable=false;
+    private boolean _ensureDefaultServlet=true;
     private IdentityService _identityService;
-    
+
     private ServletHolder[] _servlets=new ServletHolder[0];
     private ServletMapping[] _servletMappings;
-    
-    private final Map<String,FilterHolder> _filterNameMap= new HashMap<String,FilterHolder>();
+    private final Map<String,FilterHolder> _filterNameMap= new HashMap<>();
     private List<FilterMapping> _filterPathMappings;
-    private MultiMap<String> _filterNameMappings;
-    
-    private final Map<String,ServletHolder> _servletNameMap=new HashMap<String,ServletHolder>();
-    private PathMap _servletPathMap;
+    private MultiMap<FilterMapping> _filterNameMappings;
+
+    private final Map<String,ServletHolder> _servletNameMap=new HashMap<>();
+    private PathMap<ServletHolder> _servletPathMap;
     
-    protected final ConcurrentMap<String,FilterChain> _chainCache[] = new ConcurrentMap[FilterMapping.ALL];
+    private ListenerHolder[] _listeners=new ListenerHolder[0];
+
+    @SuppressWarnings("unchecked")
+    protected final ConcurrentMap<String, FilterChain> _chainCache[] = new ConcurrentMap[FilterMapping.ALL];
+
+    @SuppressWarnings("unchecked")
     protected final Queue<String>[] _chainLRU = new Queue[FilterMapping.ALL];
 
 
+
     /* ------------------------------------------------------------ */
-    /** Constructor. 
+    /** Constructor.
      */
     public ServletHandler()
     {
     }
 
-    /* ------------------------------------------------------------ */
-    /* 
-     * @see org.eclipse.jetty.server.handler.AbstractHandler#setServer(org.eclipse.jetty.server.Server)
-     */
-    public void setServer(Server server)
-    {
-        Server old=getServer();
-        if (old!=null && old!=server)
-        {
-            getServer().getContainer().update(this, _filters, null, "filter",true);
-            getServer().getContainer().update(this, _filterMappings, null, "filterMapping",true);
-            getServer().getContainer().update(this, _servlets, null, "servlet",true);
-            getServer().getContainer().update(this, _servletMappings, null, "servletMapping",true);
-        }
-
-        super.setServer(server);
-        
-        if (server!=null && old!=server)
-        {
-            server.getContainer().update(this, null, _filters, "filter",true);
-            server.getContainer().update(this, null, _filterMappings, "filterMapping",true);
-            server.getContainer().update(this, null, _servlets, "servlet",true);
-            server.getContainer().update(this, null, _servletMappings, "servletMapping",true);
-        }
-    }
-
     /* ----------------------------------------------------------------- */
     @Override
     protected synchronized void doStart()
         throws Exception
     {
-        _servletContext=ContextHandler.getCurrentContext();
-        _contextHandler=(ServletContextHandler)(_servletContext==null?null:_servletContext.getContextHandler());
+        ContextHandler.Context context=ContextHandler.getCurrentContext();
+        _servletContext=context==null?new ContextHandler.NoContext():context;
+        _contextHandler=(ServletContextHandler)(context==null?null:context.getContextHandler());
 
         if (_contextHandler!=null)
         {
-            SecurityHandler security_handler = (SecurityHandler)_contextHandler.getChildHandlerByClass(SecurityHandler.class);
+            SecurityHandler security_handler = _contextHandler.getChildHandlerByClass(SecurityHandler.class);
             if (security_handler!=null)
                 _identityService=security_handler.getIdentityService();
         }
-        
+
         updateNameMappings();
-        updateMappings();
+        updateMappings();        
         
+        if (getServletMapping("/")==null && _ensureDefaultServlet)
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("Adding Default404Servlet to {}",this);
+            addServletWithMapping(Default404Servlet.class,"/");
+            updateMappings();  
+            getServletMapping("/").setDefault(true);
+        }
+
         if(_filterChainsCached)
         {
             _chainCache[FilterMapping.REQUEST]=new ConcurrentHashMap<String,FilterChain>();
@@ -180,7 +175,7 @@ public class ServletHandler extends ScopedHandler
             _chainCache[FilterMapping.INCLUDE]=new ConcurrentHashMap<String,FilterChain>();
             _chainCache[FilterMapping.ERROR]=new ConcurrentHashMap<String,FilterChain>();
             _chainCache[FilterMapping.ASYNC]=new ConcurrentHashMap<String,FilterChain>();
-            
+
             _chainLRU[FilterMapping.REQUEST]=new ConcurrentLinkedQueue<String>();
             _chainLRU[FilterMapping.FORWARD]=new ConcurrentLinkedQueue<String>();
             _chainLRU[FilterMapping.INCLUDE]=new ConcurrentLinkedQueue<String>();
@@ -188,12 +183,45 @@ public class ServletHandler extends ScopedHandler
             _chainLRU[FilterMapping.ASYNC]=new ConcurrentLinkedQueue<String>();
         }
 
-        super.doStart();
-        
-        if (_contextHandler==null || !(_contextHandler instanceof ServletContextHandler))
+        if (_contextHandler==null)
             initialize();
-    }   
+        
+        super.doStart();
+    }
+    
     
+    /* ------------------------------------------------------------ */
+    /**
+     * @return true if ServletHandler always has a default servlet, using {@link Default404Servlet} if no other
+     * default servlet is configured.
+     */
+    public boolean isEnsureDefaultServlet()
+    {
+        return _ensureDefaultServlet;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param ensureDefaultServlet true if ServletHandler always has a default servlet, using {@link Default404Servlet} if no other
+     * default servlet is configured.
+     */
+    public void setEnsureDefaultServlet(boolean ensureDefaultServlet)
+    {
+        _ensureDefaultServlet=ensureDefaultServlet;
+    }
+
+    /* ----------------------------------------------------------------- */
+    @Override
+    protected void start(LifeCycle l) throws Exception
+    {
+        //Don't start the whole object tree (ie all the servlet and filter Holders) when
+        //this handler starts. They have a slightly special lifecycle, and should only be
+        //started AFTER the handlers have all started (and the ContextHandler has called
+        //the context listeners).
+        if (!(l instanceof Holder))
+            super.start(l);
+    }
+
     /* ----------------------------------------------------------------- */
     @Override
     protected synchronized void doStop()
@@ -203,12 +231,19 @@ public class ServletHandler extends ScopedHandler
 
         // Stop filters
         List<FilterHolder> filterHolders = new ArrayList<FilterHolder>();
-        List<FilterMapping> filterMappings = LazyList.array2List(_filterMappings);
+        List<FilterMapping> filterMappings = ArrayUtil.asMutableList(_filterMappings);      
         if (_filters!=null)
         {
             for (int i=_filters.length; i-->0;)
             {
-                try { _filters[i].stop(); }catch(Exception e){LOG.warn(Log.EXCEPTION,e);}
+                try 
+                {
+                    _filters[i].stop(); 
+                }
+                catch(Exception e)
+                {
+                    LOG.warn(Log.EXCEPTION,e);
+                }
                 if (_filters[i].getSource() != Source.EMBEDDED)
                 {
                     //remove all of the mappings that were for non-embedded filters
@@ -226,20 +261,34 @@ public class ServletHandler extends ScopedHandler
                     filterHolders.add(_filters[i]); //only retain embedded
             }
         }
-        _filters = (FilterHolder[]) LazyList.toArray(filterHolders, FilterHolder.class);
-        _filterMappings = (FilterMapping[]) LazyList.toArray(filterMappings, FilterMapping.class);
+        
+        //Retain only filters and mappings that were added using jetty api (ie Source.EMBEDDED)
+        FilterHolder[] fhs = (FilterHolder[]) LazyList.toArray(filterHolders, FilterHolder.class);
+        updateBeans(_filters, fhs);
+        _filters = fhs;
+        FilterMapping[] fms = (FilterMapping[]) LazyList.toArray(filterMappings, FilterMapping.class);
+        updateBeans(_filterMappings, fms);
+        _filterMappings = fms;
+        
         _matchAfterIndex = (_filterMappings == null || _filterMappings.length == 0 ? -1 : _filterMappings.length-1);
         _matchBeforeIndex = -1;
 
-
         // Stop servlets
         List<ServletHolder> servletHolders = new ArrayList<ServletHolder>();  //will be remaining servlets
-        List<ServletMapping> servletMappings = LazyList.array2List(_servletMappings); //will be remaining mappings
+        List<ServletMapping> servletMappings = ArrayUtil.asMutableList(_servletMappings); //will be remaining mappings
         if (_servlets!=null)
         {
             for (int i=_servlets.length; i-->0;)
             {
-                try { _servlets[i].stop(); }catch(Exception e){LOG.warn(Log.EXCEPTION,e);}
+                try 
+                { 
+                    _servlets[i].stop(); 
+                }
+                catch(Exception e)
+                {
+                    LOG.warn(Log.EXCEPTION,e);
+                }
+                
                 if (_servlets[i].getSource() != Source.EMBEDDED)
                 {
                     //remove from servlet name map
@@ -257,13 +306,40 @@ public class ServletHandler extends ScopedHandler
                     servletHolders.add(_servlets[i]); //only retain embedded 
             }
         }
-        _servlets = (ServletHolder[]) LazyList.toArray(servletHolders, ServletHolder.class);
-        _servletMappings = (ServletMapping[])LazyList.toArray(servletMappings, ServletMapping.class);
 
+        //Retain only Servlets and mappings added via jetty apis (ie Source.EMBEDDED)
+        ServletHolder[] shs = (ServletHolder[]) LazyList.toArray(servletHolders, ServletHolder.class);
+        updateBeans(_servlets, shs);
+        _servlets = shs;
+        ServletMapping[] sms = (ServletMapping[])LazyList.toArray(servletMappings, ServletMapping.class); 
+        updateBeans(_servletMappings, sms);
+        _servletMappings = sms;
+
+        //Retain only Listeners added via jetty apis (is Source.EMBEDDED)
+        List<ListenerHolder> listenerHolders = new ArrayList<ListenerHolder>();
+        if (_listeners != null)
+        { 
+            for (int i=_listeners.length; i-->0;)
+            {
+                try
+                {
+                    _listeners[i].stop();
+                } 
+                catch(Exception e)
+                {
+                    LOG.warn(Log.EXCEPTION,e);
+                }
+                if (_listeners[i].getSource() == Source.EMBEDDED)
+                    listenerHolders.add(_listeners[i]);
+            }
+        }
+        ListenerHolder[] listeners = (ListenerHolder[])LazyList.toArray(listenerHolders, ListenerHolder.class);
+        updateBeans(_listeners, listeners);
+        _listeners = listeners;
 
         //will be regenerated on next start
         _filterPathMappings=null;
-        _filterNameMappings=null;       
+        _filterNameMappings=null;
         _servletPathMap=null;
     }
 
@@ -272,7 +348,7 @@ public class ServletHandler extends ScopedHandler
     {
         return _identityService;
     }
-    
+
     /* ------------------------------------------------------------ */
     /**
      * @return Returns the contextLog.
@@ -281,47 +357,50 @@ public class ServletHandler extends ScopedHandler
     {
         return null;
     }
-    
+
     /* ------------------------------------------------------------ */
     /**
      * @return Returns the filterMappings.
      */
+    @ManagedAttribute(value="filters", readonly=true)
     public FilterMapping[] getFilterMappings()
     {
         return _filterMappings;
     }
-    
+
     /* ------------------------------------------------------------ */
     /** Get Filters.
      * @return Array of defined servlets
      */
+    @ManagedAttribute(value="filters", readonly=true)
     public FilterHolder[] getFilters()
     {
         return _filters;
     }
-    
+
     /* ------------------------------------------------------------ */
     /** ServletHolder matching path.
      * @param pathInContext Path within _context.
      * @return PathMap Entries pathspec to ServletHolder
      */
-    public PathMap.Entry getHolderEntry(String pathInContext)
+    public PathMap.MappedEntry<ServletHolder> getHolderEntry(String pathInContext)
     {
         if (_servletPathMap==null)
             return null;
         return _servletPathMap.getMatch(pathInContext);
     }
- 
+
     /* ------------------------------------------------------------ */
     public ServletContext getServletContext()
     {
         return _servletContext;
     }
-    
+
     /* ------------------------------------------------------------ */
     /**
      * @return Returns the servletMappings.
      */
+    @ManagedAttribute(value="mappings of servlets", readonly=true)
     public ServletMapping[] getServletMappings()
     {
         return _servletMappings;
@@ -329,33 +408,40 @@ public class ServletHandler extends ScopedHandler
     
     /* ------------------------------------------------------------ */
     /**
-     * @return Returns the servletMappings.
+     * Get the ServletMapping matching the path
+     * 
+     * @param pathSpec
+     * @return
      */
-    public ServletMapping getServletMapping(String pattern)
+    public ServletMapping getServletMapping(String pathSpec)
     {
-        ServletMapping theMapping = null;
-        if (_servletMappings!=null)
+        if (pathSpec == null || _servletMappings == null)
+            return null;
+        
+        ServletMapping mapping = null;
+        for (int i=0; i<_servletMappings.length && mapping == null; i++)
         {
-            for (ServletMapping m:_servletMappings)
+            ServletMapping m = _servletMappings[i];
+            if (m.getPathSpecs() != null)
             {
-                String[] paths=m.getPathSpecs();
-                if (paths!=null)
+                for (String p:m.getPathSpecs())
                 {
-                    for (String path:paths)
+                    if (pathSpec.equals(p))
                     {
-                        if (pattern.equals(path))
-                            theMapping = m;
+                        mapping = m;
+                        break;
                     }
                 }
             }
         }
-        return theMapping;
+        return mapping;
     }
-        
+    
     /* ------------------------------------------------------------ */
     /** Get Servlets.
      * @return Array of defined servlets
      */
+    @ManagedAttribute(value="servlets", readonly=true)
     public ServletHolder[] getServlets()
     {
         return _servlets;
@@ -364,7 +450,7 @@ public class ServletHandler extends ScopedHandler
     /* ------------------------------------------------------------ */
     public ServletHolder getServlet(String name)
     {
-        return (ServletHolder)_servletNameMap.get(name);
+        return _servletNameMap.get(name);
     }
 
     /* ------------------------------------------------------------ */
@@ -376,7 +462,7 @@ public class ServletHandler extends ScopedHandler
         final String old_path_info=baseRequest.getPathInfo();
 
         DispatcherType type = baseRequest.getDispatcherType();
-       
+
         ServletHolder servlet_holder=null;
         UserIdentity.Scope old_scope=null;
 
@@ -384,33 +470,33 @@ public class ServletHandler extends ScopedHandler
         if (target.startsWith("/"))
         {
             // Look for the servlet by path
-            PathMap.Entry entry=getHolderEntry(target);
+            PathMap.MappedEntry<ServletHolder> entry=getHolderEntry(target);
             if (entry!=null)
             {
-                servlet_holder=(ServletHolder)entry.getValue();
+                servlet_holder=entry.getValue();
 
-                String servlet_path_spec=(String)entry.getKey(); 
+                String servlet_path_spec= entry.getKey();
                 String servlet_path=entry.getMapped()!=null?entry.getMapped():PathMap.pathMatch(servlet_path_spec,target);
-                String path_info=PathMap.pathInfo(servlet_path_spec,target); 
-                
+                String path_info=PathMap.pathInfo(servlet_path_spec,target);
+
                 if (DispatcherType.INCLUDE.equals(type))
                 {
-                    baseRequest.setAttribute(Dispatcher.INCLUDE_SERVLET_PATH,servlet_path);
-                    baseRequest.setAttribute(Dispatcher.INCLUDE_PATH_INFO, path_info);
+                    baseRequest.setAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH,servlet_path);
+                    baseRequest.setAttribute(RequestDispatcher.INCLUDE_PATH_INFO, path_info);
                 }
                 else
                 {
                     baseRequest.setServletPath(servlet_path);
                     baseRequest.setPathInfo(path_info);
                 }
-            }      
+            }
         }
         else
         {
             // look for a servlet by name!
-            servlet_holder=(ServletHolder)_servletNameMap.get(target);
+            servlet_holder= _servletNameMap.get(target);
         }
-       
+
         if (LOG.isDebugEnabled())
             LOG.debug("servlet {}|{}|{} -> {}",baseRequest.getContextPath(),baseRequest.getServletPath(),baseRequest.getPathInfo(),servlet_holder);
 
@@ -427,7 +513,7 @@ public class ServletHandler extends ScopedHandler
                 _nextScope.doScope(target,baseRequest,request, response);
             else if (_outerScope!=null)
                 _outerScope.doHandle(target,baseRequest,request, response);
-            else 
+            else
                 doHandle(target,baseRequest,request, response);
             // end manual inline (pathentic attempt to reduce stack depth)
         }
@@ -439,13 +525,13 @@ public class ServletHandler extends ScopedHandler
             if (!(DispatcherType.INCLUDE.equals(type)))
             {
                 baseRequest.setServletPath(old_servlet_path);
-                baseRequest.setPathInfo(old_path_info); 
+                baseRequest.setPathInfo(old_path_info);
             }
         }
     }
-    
+
     /* ------------------------------------------------------------ */
-    /* 
+    /*
      * @see org.eclipse.jetty.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
      */
     @Override
@@ -453,7 +539,7 @@ public class ServletHandler extends ScopedHandler
         throws IOException, ServletException
     {
         DispatcherType type = baseRequest.getDispatcherType();
-        
+
         ServletHolder servlet_holder=(ServletHolder) baseRequest.getUserIdentityScope();
         FilterChain chain=null;
 
@@ -474,18 +560,14 @@ public class ServletHandler extends ScopedHandler
             }
         }
 
-        LOG.debug("chain={}",chain);
+        if (LOG.isDebugEnabled())
+            LOG.debug("chain={}",chain);
 
         Throwable th=null;
         try
         {
             if (servlet_holder==null)
-            {
-                if (getHandler()==null)
-                    notFound(request, response);
-                else
-                    nextHandle(target,baseRequest,request,response);
-            }
+                notFound(baseRequest,request, response);
             else
             {
                 // unwrap any tunnelling of base Servlet request/responses
@@ -497,9 +579,11 @@ public class ServletHandler extends ScopedHandler
                     res = ((ServletResponseHttpWrapper)res).getResponse();
 
                 // Do the filter/handling thang
+                servlet_holder.prepare(baseRequest, req, res);
+                
                 if (chain!=null)
                     chain.doFilter(req, res);
-                else 
+                else
                     servlet_holder.handle(baseRequest,req,res);
             }
         }
@@ -511,10 +595,6 @@ public class ServletHandler extends ScopedHandler
         {
             throw e;
         }
-        catch(ContinuationThrowable e)
-        {   
-            throw e;
-        }
         catch(Exception e)
         {
             if (!(DispatcherType.REQUEST.equals(type) || DispatcherType.ASYNC.equals(type)))
@@ -529,44 +609,32 @@ public class ServletHandler extends ScopedHandler
 
             // unwrap cause
             th=e;
-            if (th instanceof UnavailableException)
+            if (th instanceof ServletException)
             {
-                LOG.debug(th); 
-            }
-            else if (th instanceof ServletException)
-            {
-                LOG.warn(th);
-                Throwable cause=((ServletException)th).getRootCause();
-                if (cause!=null)
-                    th=cause;
+                if (th instanceof QuietServletException)
+                { 
+                    LOG.warn(th.toString());
+                    LOG.debug(th);
+                }
+                else
+                    LOG.warn(th);
             }
-
-            // handle or log exception
-            if (th instanceof HttpException)
-                throw (HttpException)th;
-            else if (th instanceof RuntimeIOException)
-                throw (RuntimeIOException)th;
             else if (th instanceof EofException)
-                throw (EofException)th;
-
-            else if (LOG.isDebugEnabled())
-            {
-                LOG.warn(request.getRequestURI(), th); 
-                LOG.debug(request.toString()); 
-            }
-            else if (th instanceof IOException || th instanceof UnavailableException)
             {
-                LOG.debug(request.getRequestURI(),th);
+                throw (EofException)th;
             }
             else
             {
                 LOG.warn(request.getRequestURI(),th);
+                if (LOG.isDebugEnabled())
+                    LOG.debug(request.toString());
             }
 
             request.setAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE,th.getClass());
             request.setAttribute(RequestDispatcher.ERROR_EXCEPTION,th);
             if (!response.isCommitted())
             {
+                baseRequest.getResponse().getHttpFields().put(HttpHeader.CONNECTION,HttpHeaderValue.CLOSE);
                 if (th instanceof UnavailableException)
                 {
                     UnavailableException ue = (UnavailableException)th;
@@ -579,58 +647,71 @@ public class ServletHandler extends ScopedHandler
                     response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
             }
             else
-                LOG.debug("Response already committed for handling "+th);
-            
+            {
+                if (th instanceof IOException)
+                    throw (IOException)th;
+                if (th instanceof RuntimeException)
+                    throw (RuntimeException)th;
+                if (th instanceof ServletException)
+                    throw (ServletException)th;
+                throw new IllegalStateException("response already committed",th);
+            }
         }
         catch(Error e)
-        {   
-            if (!(DispatcherType.REQUEST.equals(type) || DispatcherType.ASYNC.equals(type)))
+        {
+            if ("ContinuationThrowable".equals(e.getClass().getSimpleName()))
                 throw e;
             th=e;
+            if (!(DispatcherType.REQUEST.equals(type) || DispatcherType.ASYNC.equals(type)))
+                throw e;
             LOG.warn("Error for "+request.getRequestURI(),e);
-            if(LOG.isDebugEnabled())LOG.debug(request.toString());
+            if(LOG.isDebugEnabled())
+                LOG.debug(request.toString());
 
             request.setAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE,e.getClass());
             request.setAttribute(RequestDispatcher.ERROR_EXCEPTION,e);
             if (!response.isCommitted())
+            {
+                baseRequest.getResponse().getHttpFields().put(HttpHeader.CONNECTION,HttpHeaderValue.CLOSE);
                 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+            }
             else
                 LOG.debug("Response already committed for handling ",e);
         }
         finally
         {
+            // Complete async errored requests 
+            if (th!=null && request.isAsyncStarted())
+                baseRequest.getHttpChannelState().errorComplete();
+            
             if (servlet_holder!=null)
                 baseRequest.setHandled(true);
-
-            // Complete async requests 
-            if (th!=null && request.isAsyncStarted())
-                ((AsyncContinuation)request.getAsyncContext()).errorComplete();
         }
     }
 
     /* ------------------------------------------------------------ */
-    protected FilterChain getFilterChain(Request baseRequest, String pathInContext, ServletHolder servletHolder) 
+    protected FilterChain getFilterChain(Request baseRequest, String pathInContext, ServletHolder servletHolder)
     {
         String key=pathInContext==null?servletHolder.getName():pathInContext;
         int dispatch = FilterMapping.dispatch(baseRequest.getDispatcherType());
-        
+
         if (_filterChainsCached && _chainCache!=null)
         {
             FilterChain chain = (FilterChain)_chainCache[dispatch].get(key);
             if (chain!=null)
                 return chain;
         }
-        
-        // Build list of filters
-        Object filters= null;
+
+        // Build list of filters (list of FilterHolder objects)
+        List<FilterHolder> filters = new ArrayList<>();
+
         // Path filters
         if (pathInContext!=null && _filterPathMappings!=null)
         {
-            for (int i= 0; i < _filterPathMappings.size(); i++)
+            for (FilterMapping filterPathMapping : _filterPathMappings)
             {
-                FilterMapping mapping = (FilterMapping)_filterPathMappings.get(i);
-                if (mapping.appliesTo(pathInContext, dispatch))
-                    filters= LazyList.add(filters, mapping.getFilterHolder());
+                if (filterPathMapping.appliesTo(pathInContext, dispatch))
+                    filters.add(filterPathMapping.getFilterHolder());
             }
         }
 
@@ -641,60 +722,61 @@ public class ServletHandler extends ScopedHandler
             if (_filterNameMappings.size() > 0)
             {
                 Object o= _filterNameMappings.get(servletHolder.getName());
+
                 for (int i=0; i<LazyList.size(o);i++)
                 {
                     FilterMapping mapping = (FilterMapping)LazyList.get(o,i);
                     if (mapping.appliesTo(dispatch))
-                        filters=LazyList.add(filters,mapping.getFilterHolder());
+                        filters.add(mapping.getFilterHolder());
                 }
-                
+
                 o= _filterNameMappings.get("*");
                 for (int i=0; i<LazyList.size(o);i++)
                 {
                     FilterMapping mapping = (FilterMapping)LazyList.get(o,i);
                     if (mapping.appliesTo(dispatch))
-                        filters=LazyList.add(filters,mapping.getFilterHolder());
+                        filters.add(mapping.getFilterHolder());
                 }
             }
         }
-        
-        if (filters==null)
+
+        if (filters.isEmpty())
             return null;
-        
-        
+
+
         FilterChain chain = null;
         if (_filterChainsCached)
         {
-            if (LazyList.size(filters) > 0)
+            if (filters.size() > 0)
                 chain= new CachedChain(filters, servletHolder);
 
             final Map<String,FilterChain> cache=_chainCache[dispatch];
             final Queue<String> lru=_chainLRU[dispatch];
 
-        	// Do we have too many cached chains?
-        	while (_maxFilterChainsCacheSize>0 && cache.size()>=_maxFilterChainsCacheSize)
-        	{
-        	    // The LRU list is not atomic with the cache map, so be prepared to invalidate if 
-        	    // a key is not found to delete.
-        	    // Delete by LRU (where U==created)
-        	    String k=lru.poll();
-        	    if (k==null)
-        	    {
-        	        cache.clear();
-        	        break;
-        	    }
-        	    cache.remove(k);
-        	}
-        	
-        	cache.put(key,chain);
-        	lru.add(key);
+                // Do we have too many cached chains?
+                while (_maxFilterChainsCacheSize>0 && cache.size()>=_maxFilterChainsCacheSize)
+                {
+                    // The LRU list is not atomic with the cache map, so be prepared to invalidate if
+                    // a key is not found to delete.
+                    // Delete by LRU (where U==created)
+                    String k=lru.poll();
+                    if (k==null)
+                    {
+                        cache.clear();
+                        break;
+                    }
+                    cache.remove(k);
+                }
+
+                cache.put(key,chain);
+                lru.add(key);
         }
-        else if (LazyList.size(filters) > 0)
+        else if (filters.size() > 0)
             chain = new Chain(baseRequest,filters, servletHolder);
-    
+
         return chain;
     }
-    
+
     /* ------------------------------------------------------------ */
     protected void invalidateChainsCache()
     {
@@ -716,22 +798,21 @@ public class ServletHandler extends ScopedHandler
 
     /* ------------------------------------------------------------ */
     /**
-     * @return true if the handler is started and there are no unavailable servlets 
+     * @return true if the handler is started and there are no unavailable servlets
      */
     public boolean isAvailable()
     {
         if (!isStarted())
             return false;
         ServletHolder[] holders = getServlets();
-        for (int i=0;i<holders.length;i++)
+        for (ServletHolder holder : holders)
         {
-            ServletHolder holder = holders[i];
-            if (holder!=null && !holder.isAvailable())
+            if (holder != null && !holder.isAvailable())
                 return false;
         }
         return true;
     }
-    
+
     /* ------------------------------------------------------------ */
     /**
      * @param start True if this handler will start with unavailable servlets
@@ -740,7 +821,7 @@ public class ServletHandler extends ScopedHandler
     {
         _startWithUnavailable=start;
     }
-    
+
     /* ------------------------------------------------------------ */
     /**
      * @return True if this handler will start with unavailable servlets
@@ -749,57 +830,74 @@ public class ServletHandler extends ScopedHandler
     {
         return _startWithUnavailable;
     }
-    
-    
-    
+
+
+
     /* ------------------------------------------------------------ */
     /** Initialize filters and load-on-startup servlets.
-     * Called automatically from start if autoInitializeServlet is true.
      */
     public void initialize()
         throws Exception
     {
         MultiException mx = new MultiException();
 
-        // Start filters
-        if (_filters!=null)
+        //start filter holders now
+        if (_filters != null)
         {
-            for (int i=0;i<_filters.length; i++)
-                _filters[i].start();
+            for (FilterHolder f: _filters)
+            {
+                try
+                {
+                    f.start();
+                    f.initialize();
+                }
+                catch (Exception e)
+                {
+                    mx.add(e);
+                }
+            }
         }
         
+        // Sort and Initialize servlets
         if (_servlets!=null)
         {
-            // Sort and Initialize servlets
-            ServletHolder[] servlets = (ServletHolder[])_servlets.clone();
+            ServletHolder[] servlets = _servlets.clone();
             Arrays.sort(servlets);
-            for (int i=0; i<servlets.length; i++)
+            for (ServletHolder servlet : servlets)
             {
                 try
                 {
-                    if (servlets[i].getClassName()==null && servlets[i].getForcedPath()!=null)
-                    {
-                        ServletHolder forced_holder = (ServletHolder)_servletPathMap.match(servlets[i].getForcedPath());
-                        if (forced_holder==null || forced_holder.getClassName()==null)
-                        {    
-                            mx.add(new IllegalStateException("No forced path servlet for "+servlets[i].getForcedPath()));
-                            continue;
-                        }
-                        servlets[i].setClassName(forced_holder.getClassName());
-                    }
-                    
-                    servlets[i].start();
+                    servlet.start();
+                    servlet.initialize();
                 }
-                catch(Throwable e)
+                catch (Throwable e)
                 {
-                    LOG.debug(Log.EXCEPTION,e);
+                    LOG.debug(Log.EXCEPTION, e);
                     mx.add(e);
                 }
-            } 
-            mx.ifExceptionThrow();  
+            }
+        }
+
+        //any other beans
+        for (Holder<?> h: getBeans(Holder.class))
+        {
+            try
+            {
+                if (!h.isStarted())
+                {
+                    h.start();
+                    h.initialize();
+                }
+            }
+            catch (Exception e)
+            {
+                mx.add(e);
+            }
         }
+        
+        mx.ifExceptionThrow();
     }
-    
+
     /* ------------------------------------------------------------ */
     /**
      * @return Returns the filterChainsCached.
@@ -808,50 +906,75 @@ public class ServletHandler extends ScopedHandler
     {
         return _filterChainsCached;
     }
-
+    
     /* ------------------------------------------------------------ */
-    /**
-     * see also newServletHolder(Class)
+    /** Add a holder for a listener
+     * @param filter
      */
-    public ServletHolder newServletHolder(Holder.Source source)
+    public void addListener (ListenerHolder listener)
     {
-        return new ServletHolder(source);
+        if (listener != null)
+            setListeners(ArrayUtil.addToArray(getListeners(), listener, ListenerHolder.class));
     }
     
+    
     /* ------------------------------------------------------------ */
-    /** Convenience method to add a servlet Holder.
-    public ServletHolder newServletHolder(Class<? extends Servlet> servlet)
+    public ListenerHolder[] getListeners()
     {
-        return new ServletHolder(servlet);
+        return _listeners;
     }
     
     /* ------------------------------------------------------------ */
+    public void setListeners(ListenerHolder[] listeners)
+    {
+        if (listeners!=null)
+            for (ListenerHolder holder:listeners)
+                holder.setServletHandler(this);
+
+        updateBeans(_listeners,listeners);
+        _listeners = listeners;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public ListenerHolder newListenerHolder(Holder.Source source)
+    {
+        return new ListenerHolder(source);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * see also newServletHolder(Class)
+     */
+    public ServletHolder newServletHolder(Holder.Source source)
+    {
+        return new ServletHolder(source);
+    }
+
+    /* ------------------------------------------------------------ */
     /** Convenience method to add a servlet.
      * @return The servlet holder.
      */
     public ServletHolder addServletWithMapping (String className,String pathSpec)
     {
-        ServletHolder holder = newServletHolder(Holder.Source.EMBEDDED);
+        ServletHolder holder = newServletHolder(Source.EMBEDDED);
         holder.setClassName(className);
         addServletWithMapping(holder,pathSpec);
         return holder;
-    }   
-    
+    }
+
     /* ------------------------------------------------------------ */
     /** conveniance method to add a servlet.
      * @return The servlet holder.
      */
     public ServletHolder addServletWithMapping (Class<? extends Servlet> servlet,String pathSpec)
     {
-        ServletHolder holder = newServletHolder(Holder.Source.EMBEDDED);
+        ServletHolder holder = newServletHolder(Source.EMBEDDED);
         holder.setHeldClass(servlet);
-        //DUPLICATES adding servlet from addServletWithMapping(holder, pathSpec)?
-        //setServlets((ServletHolder[])LazyList.addToArray(getServlets(), holder, ServletHolder.class));
         addServletWithMapping(holder,pathSpec);
-        
+
         return holder;
-    }   
-    
+    }
+
     /* ------------------------------------------------------------ */
     /** conveniance method to add a servlet.
      * @param servlet servlet holder to add
@@ -862,15 +985,15 @@ public class ServletHandler extends ScopedHandler
         ServletHolder[] holders=getServlets();
         if (holders!=null)
             holders = holders.clone();
-        
+
         try
         {
-            setServlets((ServletHolder[])LazyList.addToArray(holders, servlet, ServletHolder.class));
-            
+            setServlets(ArrayUtil.addToArray(holders, servlet, ServletHolder.class));
+
             ServletMapping mapping = new ServletMapping();
             mapping.setServletName(servlet.getName());
             mapping.setPathSpec(pathSpec);
-            setServletMappings((ServletMapping[])LazyList.addToArray(getServletMappings(), mapping, ServletMapping.class));
+            setServletMappings(ArrayUtil.addToArray(getServletMappings(), mapping, ServletMapping.class));
         }
         catch (Exception e)
         {
@@ -881,36 +1004,36 @@ public class ServletHandler extends ScopedHandler
         }
     }
 
-    
-    /* ------------------------------------------------------------ */    
+
+    /* ------------------------------------------------------------ */
     /**Convenience method to add a pre-constructed ServletHolder.
      * @param holder
      */
     public void addServlet(ServletHolder holder)
     {
-        setServlets((ServletHolder[])LazyList.addToArray(getServlets(), holder, ServletHolder.class));
+        setServlets(ArrayUtil.addToArray(getServlets(), holder, ServletHolder.class));
     }
-    
-    /* ------------------------------------------------------------ */    
+
+    /* ------------------------------------------------------------ */
     /** Convenience method to add a pre-constructed ServletMapping.
      * @param mapping
      */
     public void addServletMapping (ServletMapping mapping)
     {
-        setServletMappings((ServletMapping[])LazyList.addToArray(getServletMappings(), mapping, ServletMapping.class));
+        setServletMappings(ArrayUtil.addToArray(getServletMappings(), mapping, ServletMapping.class));
     }
-
-    public Set<String>  setServletSecurity(ServletRegistration.Dynamic registration, ServletSecurityElement servletSecurityElement) {
-        if (_contextHandler != null) {
+    
+    /* ------------------------------------------------------------ */
+    public Set<String>  setServletSecurity(ServletRegistration.Dynamic registration, ServletSecurityElement servletSecurityElement) 
+    {
+        if (_contextHandler != null) 
+        {
             return _contextHandler.setServletSecurity(registration, servletSecurityElement);
         }
         return Collections.emptySet();
     }
 
     /* ------------------------------------------------------------ */
-    /** 
-     * @see #newFilterHolder(Class)
-     */
     public FilterHolder newFilterHolder(Holder.Source source)
     {
         return new FilterHolder(source);
@@ -919,10 +1042,10 @@ public class ServletHandler extends ScopedHandler
     /* ------------------------------------------------------------ */
     public FilterHolder getFilter(String name)
     {
-        return (FilterHolder)_filterNameMap.get(name);
+        return _filterNameMap.get(name);
     }
 
-    
+
     /* ------------------------------------------------------------ */
     /** Convenience method to add a filter.
      * @param filter  class of filter to create
@@ -932,13 +1055,13 @@ public class ServletHandler extends ScopedHandler
      */
     public FilterHolder addFilterWithMapping (Class<? extends Filter> filter,String pathSpec,EnumSet<DispatcherType> dispatches)
     {
-        FilterHolder holder = newFilterHolder(Holder.Source.EMBEDDED);
+        FilterHolder holder = newFilterHolder(Source.EMBEDDED);
         holder.setHeldClass(filter);
         addFilterWithMapping(holder,pathSpec,dispatches);
-        
+
         return holder;
     }
-    
+
     /* ------------------------------------------------------------ */
     /** Convenience method to add a filter.
      * @param className of filter
@@ -948,13 +1071,13 @@ public class ServletHandler extends ScopedHandler
      */
     public FilterHolder addFilterWithMapping (String className,String pathSpec,EnumSet<DispatcherType> dispatches)
     {
-        FilterHolder holder = newFilterHolder(Holder.Source.EMBEDDED);
+        FilterHolder holder = newFilterHolder(Source.EMBEDDED);
         holder.setClassName(className);
-        
+
         addFilterWithMapping(holder,pathSpec,dispatches);
         return holder;
     }
-    
+
     /* ------------------------------------------------------------ */
     /** Convenience method to add a filter.
      * @param holder filter holder to add
@@ -965,17 +1088,16 @@ public class ServletHandler extends ScopedHandler
     {
         FilterHolder[] holders = getFilters();
         if (holders!=null)
-            holders = (FilterHolder[])holders.clone();
-        
+            holders = holders.clone();
+
         try
         {
-            setFilters((FilterHolder[])LazyList.addToArray(holders, holder, FilterHolder.class));
-            
+            setFilters(ArrayUtil.addToArray(holders, holder, FilterHolder.class));
+
             FilterMapping mapping = new FilterMapping();
             mapping.setFilterName(holder.getName());
             mapping.setPathSpec(pathSpec);
             mapping.setDispatcherTypes(dispatches);
-            //setFilterMappings((FilterMapping[])LazyList.addToArray(getFilterMappings(), mapping, FilterMapping.class));
             addFilterMapping(mapping);
             
         }
@@ -989,9 +1111,9 @@ public class ServletHandler extends ScopedHandler
             setFilters(holders);
             throw e;
         }
-            
+
     }
-    
+
     /* ------------------------------------------------------------ */
     /** Convenience method to add a filter.
      * @param filter  class of filter to create
@@ -1001,13 +1123,13 @@ public class ServletHandler extends ScopedHandler
      */
     public FilterHolder addFilterWithMapping (Class<? extends Filter> filter,String pathSpec,int dispatches)
     {
-        FilterHolder holder = newFilterHolder(Holder.Source.EMBEDDED);
+        FilterHolder holder = newFilterHolder(Source.EMBEDDED);
         holder.setHeldClass(filter);
         addFilterWithMapping(holder,pathSpec,dispatches);
-        
+
         return holder;
     }
-    
+
     /* ------------------------------------------------------------ */
     /** Convenience method to add a filter.
      * @param className of filter
@@ -1017,13 +1139,13 @@ public class ServletHandler extends ScopedHandler
      */
     public FilterHolder addFilterWithMapping (String className,String pathSpec,int dispatches)
     {
-        FilterHolder holder = newFilterHolder(Holder.Source.EMBEDDED);
+        FilterHolder holder = newFilterHolder(Source.EMBEDDED);
         holder.setClassName(className);
-        
+
         addFilterWithMapping(holder,pathSpec,dispatches);
         return holder;
     }
-    
+
     /* ------------------------------------------------------------ */
     /** Convenience method to add a filter.
      * @param holder filter holder to add
@@ -1034,17 +1156,16 @@ public class ServletHandler extends ScopedHandler
     {
         FilterHolder[] holders = getFilters();
         if (holders!=null)
-            holders = (FilterHolder[])holders.clone();
-        
+            holders = holders.clone();
+
         try
         {
-            setFilters((FilterHolder[])LazyList.addToArray(holders, holder, FilterHolder.class));
-            
+            setFilters(ArrayUtil.addToArray(holders, holder, FilterHolder.class));
+
             FilterMapping mapping = new FilterMapping();
             mapping.setFilterName(holder.getName());
             mapping.setPathSpec(pathSpec);
             mapping.setDispatches(dispatches);
-            //setFilterMappings((FilterMapping[])LazyList.addToArray(getFilterMappings(), mapping, FilterMapping.class));
             addFilterMapping(mapping);
         }
         catch (RuntimeException e)
@@ -1057,22 +1178,22 @@ public class ServletHandler extends ScopedHandler
             setFilters(holders);
             throw e;
         }
-            
+
     }
-    
+
     /* ------------------------------------------------------------ */
     /** Convenience method to add a filter with a mapping
      * @param className
      * @param pathSpec
      * @param dispatches
      * @return the filter holder created
-     * @deprecated use {@link #addFilterWithMapping(Class, String, EnumSet<DispatcherType>)} instead
+     * @deprecated use {@link #addFilterWithMapping(Class, String, EnumSet)} instead
      */
     public FilterHolder addFilter (String className,String pathSpec,EnumSet<DispatcherType> dispatches)
     {
         return addFilterWithMapping(className, pathSpec, dispatches);
     }
-    
+
     /* ------------------------------------------------------------ */
     /**
      * convenience method to add a filter and mapping
@@ -1082,22 +1203,21 @@ public class ServletHandler extends ScopedHandler
     public void addFilter (FilterHolder filter, FilterMapping filterMapping)
     {
         if (filter != null)
-            setFilters((FilterHolder[])LazyList.addToArray(getFilters(), filter, FilterHolder.class));
+            setFilters(ArrayUtil.addToArray(getFilters(), filter, FilterHolder.class));
         if (filterMapping != null)
-            //setFilterMappings((FilterMapping[])LazyList.addToArray(getFilterMappings(), filterMapping, FilterMapping.class));
             addFilterMapping(filterMapping);
     }
-    
-    /* ------------------------------------------------------------ */  
+
+    /* ------------------------------------------------------------ */
     /** Convenience method to add a preconstructed FilterHolder
      * @param filter
      */
     public void addFilter (FilterHolder filter)
     {
         if (filter != null)
-            setFilters((FilterHolder[])LazyList.addToArray(getFilters(), filter, FilterHolder.class));
+            setFilters(ArrayUtil.addToArray(getFilters(), filter, FilterHolder.class));
     }
-    
+
     /* ------------------------------------------------------------ */
     /** Convenience method to add a preconstructed FilterMapping
      * @param mapping
@@ -1105,7 +1225,7 @@ public class ServletHandler extends ScopedHandler
     public void addFilterMapping (FilterMapping mapping)
     {
         if (mapping != null)
-        { 
+        {
             Source source = (mapping.getFilterHolder()==null?null:mapping.getFilterHolder().getSource());
             FilterMapping[] mappings =getFilterMappings();
             if (mappings==null || mappings.length==0)
@@ -1141,7 +1261,7 @@ public class ServletHandler extends ScopedHandler
         }
     }
     
-    
+
     /* ------------------------------------------------------------ */
     /** Convenience method to add a preconstructed FilterMapping
      * @param mapping
@@ -1246,60 +1366,60 @@ public class ServletHandler extends ScopedHandler
     
     /* ------------------------------------------------------------ */
     protected synchronized void updateNameMappings()
-    {   
+    {
         // update filter name map
         _filterNameMap.clear();
         if (_filters!=null)
-        {   
-            for (int i=0;i<_filters.length;i++)
+        {
+            for (FilterHolder filter : _filters)
             {
-                _filterNameMap.put(_filters[i].getName(),_filters[i]);
-                _filters[i].setServletHandler(this);
+                _filterNameMap.put(filter.getName(), filter);
+                filter.setServletHandler(this);
             }
         }
 
         // Map servlet names to holders
         _servletNameMap.clear();
         if (_servlets!=null)
-        {   
+        {
             // update the maps
-            for (int i=0;i<_servlets.length;i++)
+            for (ServletHolder servlet : _servlets)
             {
-                _servletNameMap.put(_servlets[i].getName(),_servlets[i]);
-                _servlets[i].setServletHandler(this);
+                _servletNameMap.put(servlet.getName(), servlet);
+                servlet.setServletHandler(this);
             }
         }
     }
-    
+
     /* ------------------------------------------------------------ */
     protected synchronized void updateMappings()
-    {   
+    {
         // update filter mappings
         if (_filterMappings==null)
         {
             _filterPathMappings=null;
             _filterNameMappings=null;
         }
-        else 
+        else
         {
-            _filterPathMappings=new ArrayList();
-            _filterNameMappings=new MultiMap();
-            for (int i=0;i<_filterMappings.length;i++)
+            _filterPathMappings=new ArrayList<>();
+            _filterNameMappings=new MultiMap<FilterMapping>();
+            for (FilterMapping filtermapping : _filterMappings)
             {
-                FilterHolder filter_holder = (FilterHolder)_filterNameMap.get(_filterMappings[i].getFilterName());
-                if (filter_holder==null)
-                    throw new IllegalStateException("No filter named "+_filterMappings[i].getFilterName());
-                _filterMappings[i].setFilterHolder(filter_holder);    
-                if (_filterMappings[i].getPathSpecs()!=null)
-                    _filterPathMappings.add(_filterMappings[i]);
-                
-                if (_filterMappings[i].getServletNames()!=null)
+                FilterHolder filter_holder = _filterNameMap.get(filtermapping.getFilterName());
+                if (filter_holder == null)
+                    throw new IllegalStateException("No filter named " + filtermapping.getFilterName());
+                filtermapping.setFilterHolder(filter_holder);
+                if (filtermapping.getPathSpecs() != null)
+                    _filterPathMappings.add(filtermapping);
+
+                if (filtermapping.getServletNames() != null)
                 {
-                    String[] names=_filterMappings[i].getServletNames();
-                    for (int j=0;j<names.length;j++)
+                    String[] names = filtermapping.getServletNames();
+                    for (String name : names)
                     {
-                        if (names[j]!=null)
-                            _filterNameMappings.add(names[j], _filterMappings[i]);  
+                        if (name != null)
+                            _filterNameMappings.add(name, filtermapping);
                     }
                 }
             }
@@ -1312,26 +1432,75 @@ public class ServletHandler extends ScopedHandler
         }
         else
         {
-            PathMap pm = new PathMap();
+            PathMap<ServletHolder> pm = new PathMap<>();
+            Map<String,ServletMapping> servletPathMappings = new HashMap<String,ServletMapping>();
             
-            // update the maps
-            for (int i=0;i<_servletMappings.length;i++)
+            //create a map of paths to set of ServletMappings that define that mapping
+            HashMap<String, Set<ServletMapping>> sms = new HashMap<String, Set<ServletMapping>>();
+            for (ServletMapping servletMapping : _servletMappings)
             {
-                ServletHolder servlet_holder = (ServletHolder)_servletNameMap.get(_servletMappings[i].getServletName());
-                if (servlet_holder==null)
-                    throw new IllegalStateException("No such servlet: "+_servletMappings[i].getServletName());
-                else if (servlet_holder.isEnabled() && _servletMappings[i].getPathSpecs()!=null)
+                String[] pathSpecs = servletMapping.getPathSpecs();
+                if (pathSpecs != null)
                 {
-                    String[] pathSpecs = _servletMappings[i].getPathSpecs();
-                    for (int j=0;j<pathSpecs.length;j++)
-                        if (pathSpecs[j]!=null)
-                            pm.put(pathSpecs[j],servlet_holder);
+                    for (String pathSpec : pathSpecs)
+                    {
+                        Set<ServletMapping> mappings = sms.get(pathSpec);
+                        if (mappings == null)
+                        {
+                            mappings = new HashSet<ServletMapping>();
+                            sms.put(pathSpec, mappings);
+                        }
+                        mappings.add(servletMapping);
+                    }
                 }
             }
-            
+         
+            //evaluate path to servlet map based on servlet mappings
+            for (String pathSpec : sms.keySet())
+            {
+                //for each path, look at the mappings where it is referenced
+                //if a mapping is for a servlet that is not enabled, skip it
+                Set<ServletMapping> mappings = sms.get(pathSpec);
+
+                ServletMapping finalMapping = null;
+                for (ServletMapping mapping : mappings)
+                {
+                    //Get servlet associated with the mapping and check it is enabled
+                    ServletHolder servlet_holder = _servletNameMap.get(mapping.getServletName());
+                    if (servlet_holder == null)
+                        throw new IllegalStateException("No such servlet: " + mapping.getServletName());
+                    //if the servlet related to the mapping is not enabled, skip it from consideration
+                    if (!servlet_holder.isEnabled())
+                        continue;
+
+                    //only accept a default mapping if we don't have any other 
+                    if (finalMapping == null)
+                        finalMapping = mapping;
+                    else
+                    {
+                        //already have a candidate - only accept another one if the candidate is a default
+                        if (finalMapping.isDefault())
+                            finalMapping = mapping;
+                        else
+                        {
+                            //existing candidate isn't a default, if the one we're looking at isn't a default either, then its an error
+                            if (!mapping.isDefault())
+                                throw new IllegalStateException("Multiple servlets map to path: "+pathSpec+": "+finalMapping.getServletName()+","+mapping.getServletName());
+                        }
+                    }
+                }
+                if (finalMapping == null)
+                    throw new IllegalStateException ("No acceptable servlet mappings for "+pathSpec);
+           
+                if (LOG.isDebugEnabled()) LOG.debug("Chose path={} mapped to servlet={} from default={}", pathSpec, finalMapping.getServletName(), finalMapping.isDefault());
+               
+                servletPathMappings.put(pathSpec, finalMapping);
+                pm.put(pathSpec,_servletNameMap.get(finalMapping.getServletName()));
+            }
+     
             _servletPathMap=pm;
         }
-        
+
         // flush filter chain cache
         if (_chainCache!=null)
         {
@@ -1342,7 +1511,7 @@ public class ServletHandler extends ScopedHandler
             }
         }
 
-        if (LOG.isDebugEnabled()) 
+        if (LOG.isDebugEnabled())
         {
             LOG.debug("filterNameMap="+_filterNameMap);
             LOG.debug("pathFilters="+_filterPathMappings);
@@ -1350,7 +1519,7 @@ public class ServletHandler extends ScopedHandler
             LOG.debug("servletPathMap="+_servletPathMap);
             LOG.debug("servletNameMap="+_servletNameMap);
         }
-        
+
         try
         {
             if (_contextHandler!=null && _contextHandler.isStarted() || _contextHandler==null && isStarted())
@@ -1363,15 +1532,14 @@ public class ServletHandler extends ScopedHandler
     }
 
     /* ------------------------------------------------------------ */
-    protected void notFound(HttpServletRequest request,
-                  HttpServletResponse response)
-        throws IOException
+    protected void notFound(Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
     {
-        if(LOG.isDebugEnabled())
-            LOG.debug("Not Found "+request.getRequestURI());
-        //Override to send an error back, eg with: response.sendError(HttpServletResponse.SC_NOT_FOUND);
+        if (LOG.isDebugEnabled())
+            LOG.debug("Not Found {}",request.getRequestURI());
+        if (getHandler()!=null)
+            nextHandle(URIUtil.addPaths(request.getServletPath(),request.getPathInfo()),baseRequest,request,response);
     }
-    
+
     /* ------------------------------------------------------------ */
     /**
      * @param filterChainsCached The filterChainsCached to set.
@@ -1380,51 +1548,55 @@ public class ServletHandler extends ScopedHandler
     {
         _filterChainsCached = filterChainsCached;
     }
-    
+
     /* ------------------------------------------------------------ */
     /**
      * @param filterMappings The filterMappings to set.
      */
     public void setFilterMappings(FilterMapping[] filterMappings)
     {
-        if (getServer()!=null)
-            getServer().getContainer().update(this,_filterMappings,filterMappings,"filterMapping",true);
+        updateBeans(_filterMappings,filterMappings);
         _filterMappings = filterMappings;
-        updateMappings();
+        if (isStarted()) updateMappings();
         invalidateChainsCache();
     }
-    
+
     /* ------------------------------------------------------------ */
     public synchronized void setFilters(FilterHolder[] holders)
     {
-        if (getServer()!=null)
-            getServer().getContainer().update(this,_filters,holders,"filter",true);
+        if (holders!=null)
+            for (FilterHolder holder:holders)
+                holder.setServletHandler(this);
+        
+        updateBeans(_filters,holders);
         _filters=holders;
         updateNameMappings();
         invalidateChainsCache();
     }
-    
+
     /* ------------------------------------------------------------ */
     /**
      * @param servletMappings The servletMappings to set.
      */
     public void setServletMappings(ServletMapping[] servletMappings)
     {
-        if (getServer()!=null)
-            getServer().getContainer().update(this,_servletMappings,servletMappings,"servletMapping",true);
+        updateBeans(_servletMappings,servletMappings);
         _servletMappings = servletMappings;
-        updateMappings();
+        if (isStarted()) updateMappings();
         invalidateChainsCache();
     }
-    
+
     /* ------------------------------------------------------------ */
     /** Set Servlets.
-     * @param holders Array of servletsto define
+     * @param holders Array of servlets to define
      */
     public synchronized void setServlets(ServletHolder[] holders)
     {
-        if (getServer()!=null)
-            getServer().getContainer().update(this,_servlets,holders,"servlet",true);
+        if (holders!=null)
+            for (ServletHolder holder:holders)
+                holder.setServletHandler(this);
+        
+        updateBeans(_servlets,holders);
         _servlets=holders;
         updateNameMappings();
         invalidateChainsCache();
@@ -1439,12 +1611,16 @@ public class ServletHandler extends ScopedHandler
         ServletHolder _servletHolder;
 
         /* ------------------------------------------------------------ */
-        CachedChain(Object filters, ServletHolder servletHolder)
+        /**
+         * @param filters list of {@link FilterHolder} objects
+         * @param servletHolder
+         */
+        CachedChain(List<FilterHolder> filters, ServletHolder servletHolder)
         {
-            if (LazyList.size(filters)>0)
+            if (filters.size()>0)
             {
-                _filterHolder=(FilterHolder)LazyList.get(filters, 0);
-                filters=LazyList.remove(filters,0);
+                _filterHolder=filters.get(0);
+                filters.remove(0);
                 _next=new CachedChain(filters,servletHolder);
             }
             else
@@ -1452,57 +1628,49 @@ public class ServletHandler extends ScopedHandler
         }
 
         /* ------------------------------------------------------------ */
-        public void doFilter(ServletRequest request, ServletResponse response) 
+        @Override
+        public void doFilter(ServletRequest request, ServletResponse response)
             throws IOException, ServletException
-        {                   
-            final Request baseRequest=(request instanceof Request)?((Request)request):AbstractHttpConnection.getCurrentConnection().getRequest();
+        {
+            final Request baseRequest=(request instanceof Request)?((Request)request):HttpChannel.getCurrentHttpChannel().getRequest();
 
             // pass to next filter
             if (_filterHolder!=null)
             {
                 if (LOG.isDebugEnabled())
-                    LOG.debug("call filter " + _filterHolder);
+                    LOG.debug("call filter {}", _filterHolder);
                 Filter filter= _filterHolder.getFilter();
-                if (_filterHolder.isAsyncSupported())
+                
+                //if the request already does not support async, then the setting for the filter
+                //is irrelevant. However if the request supports async but this filter does not
+                //temporarily turn it off for the execution of the filter
+                boolean requestAsyncSupported = baseRequest.isAsyncSupported();
+                try
+                {
+                    if (!_filterHolder.isAsyncSupported() && requestAsyncSupported)
+                        baseRequest.setAsyncSupported(false);
                     filter.doFilter(request, response, _next);
-                else
+                }
+                finally
                 {
-                    final boolean suspendable=baseRequest.isAsyncSupported();
-                    if (suspendable)
-                    {
-                        try
-                        {
-                            baseRequest.setAsyncSupported(false);
-                            filter.doFilter(request, response, _next);
-                        }
-                        finally
-                        {
-                            baseRequest.setAsyncSupported(true);
-                        }
-                    }
-                    else
-                        filter.doFilter(request, response, _next);
+                    baseRequest.setAsyncSupported(requestAsyncSupported);
                 }
                 return;
             }
 
             // Call servlet
-            
             HttpServletRequest srequest = (HttpServletRequest)request;
-            if (_servletHolder != null)
+            if (_servletHolder == null)
+                notFound(baseRequest, srequest, (HttpServletResponse)response);
+            else
             {
                 if (LOG.isDebugEnabled())
                     LOG.debug("call servlet " + _servletHolder);
                 _servletHolder.handle(baseRequest,request, response);
             }
-            else if (getHandler()==null)
-                notFound(srequest, (HttpServletResponse)response);
-            else
-                nextHandle(URIUtil.addPaths(srequest.getServletPath(),srequest.getPathInfo()),
-                           baseRequest,srequest,(HttpServletResponse)response);
-            
         }
-        
+
+        @Override
         public String toString()
         {
             if (_filterHolder!=null)
@@ -1511,19 +1679,19 @@ public class ServletHandler extends ScopedHandler
                 return _servletHolder.toString();
             return "null";
         }
-    }  
-    
+    }
+
     /* ------------------------------------------------------------ */
     /* ------------------------------------------------------------ */
     private class Chain implements FilterChain
     {
         final Request _baseRequest;
-        final Object _chain;
+        final List<FilterHolder> _chain;
         final ServletHolder _servletHolder;
         int _filter= 0;
 
         /* ------------------------------------------------------------ */
-        Chain(Request baseRequest, Object filters, ServletHolder servletHolder)
+        Chain(Request baseRequest, List<FilterHolder> filters, ServletHolder servletHolder)
         {
             _baseRequest=baseRequest;
             _chain= filters;
@@ -1531,66 +1699,58 @@ public class ServletHandler extends ScopedHandler
         }
 
         /* ------------------------------------------------------------ */
+        @Override
         public void doFilter(ServletRequest request, ServletResponse response)
             throws IOException, ServletException
         {
-            if (LOG.isDebugEnabled()) 
+            if (LOG.isDebugEnabled())
                 LOG.debug("doFilter " + _filter);
 
             // pass to next filter
-            if (_filter < LazyList.size(_chain))
+            if (_filter < _chain.size())
             {
-                FilterHolder holder= (FilterHolder)LazyList.get(_chain, _filter++);
-                if (LOG.isDebugEnabled()) 
+                FilterHolder holder= _chain.get(_filter++);
+                if (LOG.isDebugEnabled())
                     LOG.debug("call filter " + holder);
                 Filter filter= holder.getFilter();
-                
-                if (holder.isAsyncSupported() || !_baseRequest.isAsyncSupported())
+
+                //if the request already does not support async, then the setting for the filter
+                //is irrelevant. However if the request supports async but this filter does not
+                //temporarily turn it off for the execution of the filter
+                boolean requestAsyncSupported = _baseRequest.isAsyncSupported();
+                try
                 {
+                    if (!holder.isAsyncSupported() && requestAsyncSupported)
+                        _baseRequest.setAsyncSupported(false);
                     filter.doFilter(request, response, this);
                 }
-                else
+                finally
                 {
-                    try
-                    {
-                        _baseRequest.setAsyncSupported(false);
-                        filter.doFilter(request, response, this);
-                    }
-                    finally
-                    {
-                        _baseRequest.setAsyncSupported(true);
-                    }
+                    _baseRequest.setAsyncSupported(requestAsyncSupported);
                 }
-                    
                 return;
             }
 
             // Call servlet
             HttpServletRequest srequest = (HttpServletRequest)request;
-            if (_servletHolder != null)
+            if (_servletHolder == null)
+                notFound((request instanceof Request)?((Request)request):HttpChannel.getCurrentHttpChannel().getRequest(), srequest, (HttpServletResponse)response);
+            else
             {
-                if (LOG.isDebugEnabled()) 
-                    LOG.debug("call servlet " + _servletHolder);
+                if (LOG.isDebugEnabled())
+                    LOG.debug("call servlet {}", _servletHolder);
                 _servletHolder.handle(_baseRequest,request, response);
-            }
-            else if (getHandler()==null)
-                notFound(srequest, (HttpServletResponse)response);
-            else
-            {            
-                Request baseRequest=(request instanceof Request)?((Request)request):AbstractHttpConnection.getCurrentConnection().getRequest();
-                nextHandle(URIUtil.addPaths(srequest.getServletPath(),srequest.getPathInfo()),
-                           baseRequest,srequest,(HttpServletResponse)response);
-            }
+            }    
         }
 
         /* ------------------------------------------------------------ */
+        @Override
         public String toString()
         {
             StringBuilder b = new StringBuilder();
-            for (int i=0; i<LazyList.size(_chain);i++)
+            for(FilterHolder f: _chain)
             {
-                Object o=LazyList.get(_chain, i);
-                b.append(o.toString());
+                b.append(f.toString());
                 b.append("->");
             }
             b.append(_servletHolder);
@@ -1611,14 +1771,14 @@ public class ServletHandler extends ScopedHandler
     /** Set the maximum filter chain cache size.
      * Filter chains are cached if {@link #isFilterChainsCached()} is true. If the max cache size
      * is greater than zero, then the cache is flushed whenever it grows to be this size.
-     * 
+     *
      * @param maxFilterChainsCacheSize  the maximum number of entries in a filter chain cache.
      */
     public void setMaxFilterChainsCacheSize(int maxFilterChainsCacheSize)
     {
         _maxFilterChainsCacheSize = maxFilterChainsCacheSize;
     }
-    
+
     /* ------------------------------------------------------------ */
     void destroyServlet(Servlet servlet)
     {
@@ -1632,18 +1792,17 @@ public class ServletHandler extends ScopedHandler
         if (_contextHandler!=null)
             _contextHandler.destroyFilter(filter);
     }
-    
+
     /* ------------------------------------------------------------ */
-    @Override
-    public void dump(Appendable out,String indent) throws IOException
-    {
-        super.dumpThis(out);
-        dump(out,indent,
-                TypeUtil.asList(getHandlers()),
-                getBeans(),
-                TypeUtil.asList(getFilterMappings()),
-                TypeUtil.asList(getFilters()),
-                TypeUtil.asList(getServletMappings()),
-                TypeUtil.asList(getServlets()));
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    public static class Default404Servlet extends HttpServlet
+    {
+        @Override
+        protected void doGet(HttpServletRequest req, HttpServletResponse resp)
+                throws ServletException, IOException
+        {
+            resp.sendError(HttpServletResponse.SC_NOT_FOUND);
+        }
     }
 }
diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java
index 34139f8..8f28e6e 100644
--- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java
+++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java
@@ -18,7 +18,9 @@
 
 package org.eclipse.jetty.servlet;
 
+import java.io.File;
 import java.io.IOException;
+import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -33,9 +35,9 @@ import java.util.Stack;
 import javax.servlet.MultipartConfigElement;
 import javax.servlet.Servlet;
 import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
 import javax.servlet.ServletException;
 import javax.servlet.ServletRegistration;
-import javax.servlet.ServletContext;
 import javax.servlet.ServletRequest;
 import javax.servlet.ServletResponse;
 import javax.servlet.ServletSecurityElement;
@@ -48,6 +50,8 @@ import org.eclipse.jetty.server.Request;
 import org.eclipse.jetty.server.UserIdentity;
 import org.eclipse.jetty.server.handler.ContextHandler;
 import org.eclipse.jetty.util.Loader;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
 
@@ -61,14 +65,15 @@ import org.eclipse.jetty.util.log.Logger;
  * This class will organise the loading of the servlet when needed or
  * requested.
  *
- * 
+ *
  */
-public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope, Comparable
+ at ManagedObject("Servlet Holder")
+public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope, Comparable<ServletHolder>
 {
-    private static final Logger LOG = Log.getLogger(ServletHolder.class);
 
     /* ---------------------------------------------------------------- */
-    private int _initOrder;
+    private static final Logger LOG = Log.getLogger(ServletHolder.class);
+    private int _initOrder = -1;
     private boolean _initOnStartup=false;
     private Map<String, String> _roleMap;
     private String _forcedPath;
@@ -76,14 +81,20 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
     private RunAsToken _runAsToken;
     private IdentityService _identityService;
     private ServletRegistration.Dynamic _registration;
-    
-    
+    private JspContainer _jspContainer;
+
+
     private transient Servlet _servlet;
     private transient Config _config;
     private transient long _unavailable;
     private transient boolean _enabled = true;
     private transient UnavailableException _unavailableEx;
+
+    public static final String GLASSFISH_SENTINEL_CLASS = "org.glassfish.jsp.api.ResourceInjector";
+    public static final String APACHE_SENTINEL_CLASS = "org.apache.tomcat.InstanceManager";
+    public static final  String JSP_GENERATED_PACKAGE_NAME = "org.eclipse.jetty.servlet.jspPackagePrefix";
     public static final Map<String,String> NO_MAPPED_ROLES = Collections.emptyMap();
+    public static enum JspContainer {GLASSFISH, APACHE, OTHER}; 
 
     /* ---------------------------------------------------------------- */
     /** Constructor .
@@ -92,7 +103,7 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
     {
         this(Source.EMBEDDED);
     }
-    
+
     /* ---------------------------------------------------------------- */
     /** Constructor .
      */
@@ -100,7 +111,7 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
     {
         super(creator);
     }
-    
+
     /* ---------------------------------------------------------------- */
     /** Constructor for existing servlet.
      */
@@ -119,7 +130,7 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
         setName(name);
         setHeldClass(servlet);
     }
-    
+
     /* ---------------------------------------------------------------- */
     /** Constructor for servlet class.
      */
@@ -129,7 +140,7 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
         setName(name);
         setServlet(servlet);
     }
-    
+
     /* ---------------------------------------------------------------- */
     /** Constructor for servlet class.
      */
@@ -147,7 +158,7 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
     {
         return _unavailableEx;
     }
-    
+
     /* ------------------------------------------------------------ */
     public synchronized void setServlet(Servlet servlet)
     {
@@ -160,8 +171,9 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
         if (getName()==null)
             setName(servlet.getClass().getName()+"-"+super.hashCode());
     }
-    
+
     /* ------------------------------------------------------------ */
+    @ManagedAttribute(value="initialization order", readonly=true)
     public int getInitOrder()
     {
         return _initOrder;
@@ -175,42 +187,33 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
      */
     public void setInitOrder(int order)
     {
-        _initOnStartup=true;
+        _initOnStartup=order>=0;
         _initOrder = order;
     }
-    
-    public boolean isSetInitOrder()
-    {
-        return _initOnStartup;
-    }
 
     /* ------------------------------------------------------------ */
     /** Comparitor by init order.
      */
-    public int compareTo(Object o)
+    @Override
+    public int compareTo(ServletHolder sh)
     {
-        if (o instanceof ServletHolder)
-        {
-            ServletHolder sh= (ServletHolder)o;
-            if (sh==this)
-                return 0;
-            if (sh._initOrder<_initOrder)
-                return 1;
-            if (sh._initOrder>_initOrder)
-                return -1;
-            
-            int c=(_className!=null && sh._className!=null)?_className.compareTo(sh._className):0;
-            if (c==0)
-                c=_name.compareTo(sh._name);
+        if (sh==this)
+            return 0;
+        if (sh._initOrder<_initOrder)
+            return 1;
+        if (sh._initOrder>_initOrder)
+            return -1;
+
+        int c=(_className!=null && sh._className!=null)?_className.compareTo(sh._className):0;
+        if (c==0)
+            c=_name.compareTo(sh._name);
             return c;
-        }
-        return 1;
     }
 
     /* ------------------------------------------------------------ */
     public boolean equals(Object o)
     {
-        return compareTo(o)==0;
+        return o instanceof ServletHolder && compareTo((ServletHolder)o)==0;
     }
 
     /* ------------------------------------------------------------ */
@@ -232,7 +235,7 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
             _roleMap=new HashMap<String, String>();
         _roleMap.put(name,link);
     }
-    
+
     /* ------------------------------------------------------------ */
     /** get a user role link.
      * @param name The name of the role
@@ -248,20 +251,15 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
     }
 
     /* ------------------------------------------------------------ */
-    public Map<String, String> getRoleMap()
-    {
-        return _roleMap == null? NO_MAPPED_ROLES : _roleMap;
-    }
-    
-    /* ------------------------------------------------------------ */
     /**
      * @return Returns the forcedPath.
      */
+    @ManagedAttribute(value="forced servlet path", readonly=true)
     public String getForcedPath()
     {
         return _forcedPath;
     }
-    
+
     /* ------------------------------------------------------------ */
     /**
      * @param forcedPath The forcedPath to set.
@@ -270,7 +268,7 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
     {
         _forcedPath = forcedPath;
     }
-    
+
     public boolean isEnabled()
     {
         return _enabled;
@@ -282,7 +280,7 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
         _enabled = enabled;
     }
 
-
+    
     /* ------------------------------------------------------------ */
     public void doStart()
         throws Exception
@@ -291,12 +289,54 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
         if (!_enabled)
             return;
         
+        // Handle JSP file forced paths
+        if (_forcedPath != null)
+        {
+            // Look for a precompiled JSP Servlet
+            String precompiled=getClassNameForJsp(_forcedPath);
+            if (LOG.isDebugEnabled())
+                LOG.debug("Checking for precompiled servlet {} for jsp {}", precompiled, _forcedPath);
+            ServletHolder jsp=getServletHandler().getServlet(precompiled);
+            if (jsp!=null && jsp.getClassName() !=  null)
+            {
+                if (LOG.isDebugEnabled())
+                    LOG.debug("JSP file {} for {} mapped to Servlet {}",_forcedPath, getName(),jsp.getClassName());
+                // set the className for this servlet to the precompiled one
+                setClassName(jsp.getClassName());
+            }
+            else
+            { 
+                if (getClassName() == null)
+                {
+                    // Look for normal JSP servlet
+                    jsp=getServletHandler().getServlet("jsp");
+                    if (jsp!=null)
+                    {
+                        if (LOG.isDebugEnabled())
+                            LOG.debug("JSP file {} for {} mapped to Servlet class {}",_forcedPath, getName(),jsp.getClassName());
+                        setClassName(jsp.getClassName());
+                        //copy jsp init params that don't exist for this servlet
+                        for (Map.Entry<String, String> entry:jsp.getInitParameters().entrySet())
+                        {
+                            if (!_initParams.containsKey(entry.getKey()))
+                                setInitParameter(entry.getKey(), entry.getValue());
+                        }
+                        //jsp specific: set up the jsp-file on the JspServlet. If load-on-startup is >=0 and the jsp container supports
+                        //precompilation, the jsp will be compiled when this holder is initialized. If not load on startup, or the
+                        //container does not support startup precompilation, it will be compiled at runtime when handling a request for this jsp.
+                        //See also adaptForcedPathToJspContainer
+                        setInitParameter("jspFile", _forcedPath);
+                    }                       
+                }
+            }
+        }
+        
         
         //check servlet has a class (ie is not a preliminary registration). If preliminary, fail startup.
         try
         {
             super.doStart();
-        } 
+        }
         catch (UnavailableException ue)
         {
             makeUnavailable(ue);
@@ -326,17 +366,28 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
             else
                 throw ue;
         }
-        
+
+        //check if we need to forcibly set load-on-startup
+        checkInitOnStartup();
 
         _identityService = _servletHandler.getIdentityService();
         if (_identityService!=null && _runAsRole!=null)
             _runAsToken=_identityService.newRunAsToken(_runAsRole);
-        
+
         _config=new Config();
 
         if (_class!=null && javax.servlet.SingleThreadModel.class.isAssignableFrom(_class))
             _servlet = new SingleThreadedWrapper();
-
+     
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public void initialize ()
+    throws Exception
+    {
+        super.initialize();
         if (_extInstance || _initOnStartup)
         {
             try
@@ -350,16 +401,17 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
                 else
                     throw e;
             }
-        }  
+        }
     }
-
+    
+    
     /* ------------------------------------------------------------ */
     public void doStop()
         throws Exception
     {
         Object old_run_as = null;
         if (_servlet!=null)
-        {       
+        {
             try
             {
                 if (_identityService!=null)
@@ -385,6 +437,7 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
     }
 
     /* ------------------------------------------------------------ */
+    @Override
     public void destroyInstance (Object o)
     throws Exception
     {
@@ -424,7 +477,7 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
     {
         return _servlet;
     }
-        
+
     /* ------------------------------------------------------------ */
     /**
      * Check to ensure class of servlet is acceptable.
@@ -440,14 +493,14 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
     }
 
     /* ------------------------------------------------------------ */
-    /** 
+    /**
      * @return true if the holder is started and is not unavailable
      */
     public boolean isAvailable()
     {
         if (isStarted()&& _unavailable==0)
             return true;
-        try 
+        try
         {
             getServlet();
         }
@@ -460,6 +513,23 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
     }
     
     /* ------------------------------------------------------------ */
+    /**
+     * Check if there is a javax.servlet.annotation.ServletSecurity
+     * annotation on the servlet class. If there is, then we force
+     * it to be loaded on startup, because all of the security 
+     * constraints must be calculated as the container starts.
+     * 
+     */
+    private void checkInitOnStartup()
+    {
+        if (_class==null)
+            return;
+        
+        if ((_class.getAnnotation(javax.servlet.annotation.ServletSecurity.class) != null) && !_initOnStartup)
+            setInitOrder(Integer.MAX_VALUE);    
+    }
+
+    /* ------------------------------------------------------------ */
     private void makeUnavailable(UnavailableException e)
     {
         if (_unavailableEx==e && _unavailable!=0)
@@ -469,7 +539,7 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
 
         _unavailableEx=e;
         _unavailable=-1;
-        if (e.isPermanent())   
+        if (e.isPermanent())
             _unavailable=-1;
         else
         {
@@ -505,7 +575,7 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
 
     /* ------------------------------------------------------------ */
     private void initServlet()
-    	throws ServletException
+        throws ServletException
     {
         Object old_run_as = null;
         try
@@ -515,20 +585,30 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
             if (_config==null)
                 _config=new Config();
             
+          
+
             // Handle run as
             if (_identityService!=null)
             {
                 old_run_as=_identityService.setRunAs(_identityService.getSystemUserIdentity(),_runAsToken);
             }
-            
+
             // Handle configuring servlets that implement org.apache.jasper.servlet.JspServlet
             if (isJspServlet())
             {
                 initJspServlet();
+                detectJspContainer();
             }
 
             initMultiPart();
-            
+
+            if (_forcedPath != null && _jspContainer == null)
+            {
+                detectJspContainer();
+            }
+
+            if (LOG.isDebugEnabled())
+                LOG.debug("Servlet.init {} for {}",_servlet,getName());
             _servlet.init(_config);
         }
         catch (UnavailableException e)
@@ -559,32 +639,45 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
                 _identityService.unsetRunAs(old_run_as);
         }
     }
-    
-    
+
+
     /* ------------------------------------------------------------ */
     /**
      * @throws Exception
      */
     protected void initJspServlet () throws Exception
     {
-        ContextHandler ch = ((ContextHandler.Context)getServletHandler().getServletContext()).getContextHandler();
-        
+        ContextHandler ch = ContextHandler.getContextHandler(getServletHandler().getServletContext());
+            
         /* Set the webapp's classpath for Jasper */
         ch.setAttribute("org.apache.catalina.jsp_classpath", ch.getClassPath());
 
         /* Set the system classpath for Jasper */
-        setInitParameter("com.sun.appserv.jsp.classpath", Loader.getClassPath(ch.getClassLoader().getParent())); 
-        
+        setInitParameter("com.sun.appserv.jsp.classpath", Loader.getClassPath(ch.getClassLoader().getParent()));
+
         /* Set up other classpath attribute */
         if ("?".equals(getInitParameter("classpath")))
         {
             String classpath = ch.getClassPath();
-            LOG.debug("classpath=" + classpath);
-            if (classpath != null) 
+            if (LOG.isDebugEnabled())
+                LOG.debug("classpath=" + classpath);
+            if (classpath != null)
                 setInitParameter("classpath", classpath);
         }
+        
+        /* ensure scratch dir */
+        File scratch = null;
+        if (getInitParameter("scratchdir") == null)
+        {
+            File tmp = (File)getServletHandler().getServletContext().getAttribute(ServletContext.TEMPDIR);
+            scratch = new File(tmp, "jsp");
+            setInitParameter("scratchdir", scratch.getAbsolutePath());
+        }
+     
+        scratch = new File (getInitParameter("scratchdir"));
+        if (!scratch.exists()) scratch.mkdir();
     }
-    
+
     /* ------------------------------------------------------------ */
     /**
      * Register a ServletRequestListener that will ensure tmp multipart
@@ -600,15 +693,17 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
         {
             //Register a listener to delete tmp files that are created as a result of this
             //servlet calling Request.getPart() or Request.getParts()
-            ContextHandler ch = ((ContextHandler.Context)getServletHandler().getServletContext()).getContextHandler();
+
+            ContextHandler ch = ContextHandler.getContextHandler(getServletHandler().getServletContext());
             ch.addEventListener(new Request.MultiPartCleanerListener());
         }
     }
-    
+
     /* ------------------------------------------------------------ */
     /**
      * @see org.eclipse.jetty.server.UserIdentity.Scope#getContextPath()
      */
+    @Override
     public String getContextPath()
     {
         return _config.getServletContext().getContextPath();
@@ -618,25 +713,68 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
     /**
      * @see org.eclipse.jetty.server.UserIdentity.Scope#getRoleRefMap()
      */
+    @Override
     public Map<String, String> getRoleRefMap()
     {
         return _roleMap;
     }
 
     /* ------------------------------------------------------------ */
-    public String getRunAsRole() 
+    @ManagedAttribute(value="role to run servlet as", readonly=true)
+    public String getRunAsRole()
     {
         return _runAsRole;
     }
-    
+
     /* ------------------------------------------------------------ */
-    public void setRunAsRole(String role) 
+    public void setRunAsRole(String role)
     {
         _runAsRole = role;
     }
     
     /* ------------------------------------------------------------ */
+    /**
+     * Prepare to service a request.
+     * 
+     * @param baseRequest
+     * @param request
+     * @param response
+     * @throws ServletException
+     * @throws UnavailableException
+     */
+    protected void prepare (Request baseRequest, ServletRequest request, ServletResponse response)
+    throws ServletException, UnavailableException
+    {
+        ensureInstance();
+        MultipartConfigElement mpce = ((Registration)getRegistration()).getMultipartConfig();
+        if (mpce != null)
+            baseRequest.setAttribute(Request.__MULTIPART_CONFIG_ELEMENT, mpce);
+    }
+    
+    public synchronized Servlet ensureInstance()
+    throws ServletException, UnavailableException
+    {
+        if (_class==null)
+            throw new UnavailableException("Servlet Not Initialized");
+        Servlet servlet=_servlet;
+        if (!isStarted())
+            throw new UnavailableException("Servlet not initialized", -1);
+        if (_unavailable!=0 || (!_initOnStartup && servlet==null))
+            servlet=getServlet();
+        if (servlet==null)
+            throw new UnavailableException("Could not instantiate "+_class);    
+        
+        return servlet;
+    }
+
+    /* ------------------------------------------------------------ */
     /** Service a request with this servlet.
+     * @param baseRequest
+     * @param request
+     * @param response
+     * @throws ServletException
+     * @throws UnavailableException
+     * @throws IOException
      */
     public void handle(Request baseRequest,
                        ServletRequest request,
@@ -647,18 +785,9 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
     {
         if (_class==null)
             throw new UnavailableException("Servlet Not Initialized");
-        
-        Servlet servlet=_servlet;
-        synchronized(this)
-        {
-            if (!isStarted())
-                throw new UnavailableException("Servlet not initialized", -1);
-            if (_unavailable!=0 || !_initOnStartup)
-                servlet=getServlet();
-            if (servlet==null)
-                throw new UnavailableException("Could not instantiate "+_class);
-        }
-        
+
+        Servlet servlet = ensureInstance();
+
         // Service the request
         boolean servlet_error=true;
         Object old_run_as = null;
@@ -667,8 +796,7 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
         {
             // Handle aliased path
             if (_forcedPath!=null)
-                // TODO complain about poor naming to the Jasper folks
-                request.setAttribute("org.apache.catalina.jsp_file",_forcedPath);
+                adaptForcedPathToJspContainer(request);
 
             // Handle run as
             if (_identityService!=null)
@@ -677,10 +805,6 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
             if (!isAsyncSupported())
                 baseRequest.setAsyncSupported(false);
 
-            MultipartConfigElement mpce = ((Registration)getRegistration()).getMultipartConfig();
-            if (mpce != null)
-                request.setAttribute(Request.__MULTIPART_CONFIG_ELEMENT, mpce);
-
             servlet.service(request,response);
             servlet_error=false;
         }
@@ -692,7 +816,7 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
         finally
         {
             baseRequest.setAsyncSupported(suspendable);
-            
+
             // pop run-as role
             if (_identityService!=null)
                 _identityService.unsetRunAs(old_run_as);
@@ -702,27 +826,27 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
                 request.setAttribute("javax.servlet.error.servlet_name",getName());
         }
     }
-    
-    
+
+
     /* ------------------------------------------------------------ */
     private boolean isJspServlet ()
     {
         if (_servlet == null)
             return false;
-        
-        Class c = _servlet.getClass();
-        
+
+        Class<?> c = _servlet.getClass();
+
         boolean result = false;
         while (c != null && !result)
         {
             result = isJspServlet(c.getName());
             c = c.getSuperclass();
         }
-        
+
         return result;
     }
-    
-    
+
+
     /* ------------------------------------------------------------ */
     private boolean isJspServlet (String classname)
     {
@@ -731,18 +855,134 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
         return ("org.apache.jasper.servlet.JspServlet".equals(classname));
     }
 
- 
+    /* ------------------------------------------------------------ */
+    private void adaptForcedPathToJspContainer (ServletRequest request)
+    {
+        if (_forcedPath != null && _jspContainer != null && JspContainer.GLASSFISH.equals(_jspContainer))
+        {
+            //if container is glassfish, set the request attribute org.apache.catalina.jsp_file to
+            //ensure the delegate jsp will be compiled
+
+            request.setAttribute("org.apache.catalina.jsp_file",_forcedPath);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    private void detectJspContainer ()
+    {
+        if (_jspContainer == null)
+        {
+            //check for glassfish
+            try
+            {
+                //if container is glassfish, set the request attribute org.apache.catalina.jsp_file to
+                //ensure the delegate jsp will be compiled
+                Loader.loadClass(Holder.class, GLASSFISH_SENTINEL_CLASS);
+                if (LOG.isDebugEnabled())LOG.debug("Glassfish jasper detected");
+                _jspContainer = JspContainer.GLASSFISH;
+            }
+            catch (ClassNotFoundException e)
+            {
+                try
+                {
+                    //check for apache
+                    Loader.loadClass(Holder.class, APACHE_SENTINEL_CLASS);
+                    if (LOG.isDebugEnabled())LOG.debug("Apache jasper detected");
+                    _jspContainer = JspContainer.APACHE;
+                }
+                catch (ClassNotFoundException x)
+                {
+                    if (LOG.isDebugEnabled())LOG.debug("Other jasper detected");
+                    _jspContainer = JspContainer.OTHER;
+                }
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    private String getNameOfJspClass (String jsp)
+    {
+        if (jsp == null)
+            return "";
+        
+        int i = jsp.lastIndexOf('/') + 1;
+        jsp = jsp.substring(i);
+        try
+        {
+            Class<?> jspUtil = Loader.loadClass(Holder.class, "org.apache.jasper.compiler.JspUtil");
+            Method makeJavaIdentifier = jspUtil.getMethod("makeJavaIdentifier", String.class);
+            return (String)makeJavaIdentifier.invoke(null, jsp);
+        }
+        catch (Exception e)
+        {
+            String tmp = jsp.replace('.','_');
+            LOG.warn("Unable to make identifier for jsp "+jsp +" trying "+tmp+" instead");
+            if (LOG.isDebugEnabled())
+                LOG.warn(e);
+            return tmp;
+        }
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    private String getPackageOfJspClass (String jsp)
+    {
+        if (jsp == null)
+            return "";
+        
+        int i = jsp.lastIndexOf('/');
+        if (i <= 0)
+            return "";
+        try
+        {
+            Class<?> jspUtil = Loader.loadClass(Holder.class, "org.apache.jasper.compiler.JspUtil");
+            Method makeJavaPackage = jspUtil.getMethod("makeJavaPackage", String.class);
+            return (String)makeJavaPackage.invoke(null, jsp.substring(0,i));
+        } 
+        catch (Exception e)
+        {
+            String tmp = jsp.substring(1).replace('/','.');
+            LOG.warn("Unable to make package for jsp "+jsp +" trying "+tmp+" instead");
+            if (LOG.isDebugEnabled())
+                LOG.warn(e);
+            return tmp;
+        }
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    private String getJspPackagePrefix ()
+    {
+        String jspPackageName = (String)getServletHandler().getServletContext().getInitParameter(JSP_GENERATED_PACKAGE_NAME );
+        if (jspPackageName == null)
+            jspPackageName = "org.apache.jsp";
+        
+        return jspPackageName;
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    private String getClassNameForJsp (String jsp)
+    {
+        if (jsp == null)
+            return null; 
+        
+        return getJspPackagePrefix() + "." +getPackageOfJspClass(jsp) + "." + getNameOfJspClass(jsp);
+    }
+
+
     /* ------------------------------------------------------------ */
     /* ------------------------------------------------------------ */
     /* ------------------------------------------------------------ */
     protected class Config extends HolderConfig implements ServletConfig
-    {   
+    {
         /* -------------------------------------------------------- */
+        @Override
         public String getServletName()
         {
             return getName();
         }
-        
+
     }
 
     /* -------------------------------------------------------- */
@@ -750,8 +990,9 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
     /* -------------------------------------------------------- */
     public class Registration extends HolderRegistration implements ServletRegistration.Dynamic
     {
-        protected MultipartConfigElement _multipartConfig;       
-        
+        protected MultipartConfigElement _multipartConfig;
+
+        @Override
         public Set<String> addMapping(String... urlPatterns)
         {
             illegalStateIfContextStarted();
@@ -770,20 +1011,21 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
                     }
                 }
             }
-            
+
             //if there were any clashes amongst the urls, return them
             if (clash!=null)
                 return clash;
-            
+
             //otherwise apply all of them
             ServletMapping mapping = new ServletMapping();
             mapping.setServletName(ServletHolder.this.getName());
             mapping.setPathSpecs(urlPatterns);
             _servletHandler.addServletMapping(mapping);
-            
+
             return Collections.emptySet();
         }
 
+        @Override
         public Collection<String> getMappings()
         {
             ServletMapping[] mappings =_servletHandler.getServletMappings();
@@ -803,7 +1045,7 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
         }
 
         @Override
-        public String getRunAsRole() 
+        public String getRunAsRole()
         {
             return _runAsRole;
         }
@@ -814,50 +1056,51 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
             illegalStateIfContextStarted();
             ServletHolder.this.setInitOrder(loadOnStartup);
         }
-        
+
         public int getInitOrder()
         {
             return ServletHolder.this.getInitOrder();
         }
 
         @Override
-        public void setMultipartConfig(MultipartConfigElement element) 
+        public void setMultipartConfig(MultipartConfigElement element)
         {
             _multipartConfig = element;
         }
-        
+
         public MultipartConfigElement getMultipartConfig()
         {
             return _multipartConfig;
         }
 
         @Override
-        public void setRunAsRole(String role) 
+        public void setRunAsRole(String role)
         {
             _runAsRole = role;
         }
 
         @Override
-        public Set<String> setServletSecurity(ServletSecurityElement securityElement) 
+        public Set<String> setServletSecurity(ServletSecurityElement securityElement)
         {
             return _servletHandler.setServletSecurity(this, securityElement);
         }
     }
-    
+
     public ServletRegistration.Dynamic getRegistration()
     {
         if (_registration == null)
             _registration = new Registration();
         return _registration;
     }
-    
+
     /* -------------------------------------------------------- */
     /* -------------------------------------------------------- */
     /* -------------------------------------------------------- */
     private class SingleThreadedWrapper implements Servlet
     {
         Stack<Servlet> _stack=new Stack<Servlet>();
-        
+
+        @Override
         public void destroy()
         {
             synchronized(this)
@@ -867,16 +1110,19 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
             }
         }
 
+        @Override
         public ServletConfig getServletConfig()
         {
             return _config;
         }
 
+        @Override
         public String getServletInfo()
         {
             return null;
         }
 
+        @Override
         public void init(ServletConfig config) throws ServletException
         {
             synchronized(this)
@@ -901,6 +1147,7 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
             }
         }
 
+        @Override
         public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException
         {
             Servlet s;
@@ -925,7 +1172,7 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
                     }
                 }
             }
-            
+
             try
             {
                 s.service(req,res);
@@ -939,7 +1186,7 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
             }
         }
     }
-    
+
     /* ------------------------------------------------------------ */
     /**
      * @return the newly created Servlet instance
@@ -952,9 +1199,9 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
         try
         {
             ServletContext ctx = getServletHandler().getServletContext();
-            if (ctx==null)
-                return getHeldClass().newInstance();
-            return ((ServletContextHandler.Context)ctx).createServlet(getHeldClass());
+            if (ctx instanceof ServletContextHandler.Context)
+                return ((ServletContextHandler.Context)ctx).createServlet(getHeldClass());
+            return getHeldClass().newInstance();
         }
         catch (ServletException se)
         {
@@ -966,4 +1213,12 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
             throw se;
         }
     }
+    
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        return String.format("%s@%x==%s,%d,%b",_name,hashCode(),_className,_initOrder,_servlet!=null);
+    }
 }
diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletMapping.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletMapping.java
index 5d1dd25..9b257d2 100644
--- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletMapping.java
+++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletMapping.java
@@ -21,7 +21,10 @@ package org.eclipse.jetty.servlet;
 import java.io.IOException;
 import java.util.Arrays;
 
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
 
+ at ManagedObject("Servlet Mapping")
 public class ServletMapping
 {
     private String[] _pathSpecs;
@@ -38,6 +41,7 @@ public class ServletMapping
     /**
      * @return Returns the pathSpecs.
      */
+    @ManagedAttribute(value="url patterns", readonly=true)
     public String[] getPathSpecs()
     {
         return _pathSpecs;
@@ -47,6 +51,7 @@ public class ServletMapping
     /**
      * @return Returns the servletName.
      */
+    @ManagedAttribute(value="servlet name", readonly=true)
     public String getServletName()
     {
         return _servletName;
@@ -84,6 +89,7 @@ public class ServletMapping
     /**
      * @return
      */
+    @ManagedAttribute(value="default", readonly=true)
     public boolean isDefault()
     {
         return _default;
@@ -92,7 +98,7 @@ public class ServletMapping
     
     /* ------------------------------------------------------------ */
     /**
-     * @param default1
+     * @param fromDefault
      */
     public void setDefault(boolean fromDefault)
     {
diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletTester.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletTester.java
new file mode 100644
index 0000000..be92e2d
--- /dev/null
+++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletTester.java
@@ -0,0 +1,259 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlet;
+
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+import java.util.EnumSet;
+import java.util.Enumeration;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import javax.servlet.DispatcherType;
+import javax.servlet.Filter;
+import javax.servlet.Servlet;
+
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.LocalConnector;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.util.Attributes;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+
+public class ServletTester extends ContainerLifeCycle
+{
+    private static final Logger LOG = Log.getLogger(ServletTester.class);
+    
+    private final Server _server=new Server();
+    private final LocalConnector _connector=new LocalConnector(_server);
+    private final ServletContextHandler _context;
+    
+    public Server getServer()
+    {
+        return _server;
+    }
+    
+    public LocalConnector getConnector()
+    {
+        return _connector;
+    }
+    
+    public void setVirtualHosts(String[] vhosts)
+    {
+        _context.setVirtualHosts(vhosts);
+    }
+
+    public void addVirtualHosts(String[] virtualHosts)
+    {
+        _context.addVirtualHosts(virtualHosts);
+    }
+
+    public ServletHolder addServlet(String className, String pathSpec)
+    {
+        return _context.addServlet(className,pathSpec);
+    }
+
+    public ServletHolder addServlet(Class<? extends Servlet> servlet, String pathSpec)
+    {
+        return _context.addServlet(servlet,pathSpec);
+    }
+
+    public void addServlet(ServletHolder servlet, String pathSpec)
+    {
+        _context.addServlet(servlet,pathSpec);
+    }
+
+    public void addFilter(FilterHolder holder, String pathSpec, EnumSet<DispatcherType> dispatches)
+    {
+        _context.addFilter(holder,pathSpec,dispatches);
+    }
+
+    public FilterHolder addFilter(Class<? extends Filter> filterClass, String pathSpec, EnumSet<DispatcherType> dispatches)
+    {
+        return _context.addFilter(filterClass,pathSpec,dispatches);
+    }
+
+    public FilterHolder addFilter(String filterClass, String pathSpec, EnumSet<DispatcherType> dispatches)
+    {
+        return _context.addFilter(filterClass,pathSpec,dispatches);
+    }
+
+    public Object getAttribute(String name)
+    {
+        return _context.getAttribute(name);
+    }
+
+    public Enumeration<String> getAttributeNames()
+    {
+        return _context.getAttributeNames();
+    }
+
+    public Attributes getAttributes()
+    {
+        return _context.getAttributes();
+    }
+
+    public String getContextPath()
+    {
+        return _context.getContextPath();
+    }
+
+    public String getInitParameter(String name)
+    {
+        return _context.getInitParameter(name);
+    }
+
+    public String setInitParameter(String name, String value)
+    {
+        return _context.setInitParameter(name,value);
+    }
+
+    public Enumeration<String> getInitParameterNames()
+    {
+        return _context.getInitParameterNames();
+    }
+
+    public Map<String, String> getInitParams()
+    {
+        return _context.getInitParams();
+    }
+
+    public void removeAttribute(String name)
+    {
+        _context.removeAttribute(name);
+    }
+
+    public void setAttribute(String name, Object value)
+    {
+        _context.setAttribute(name,value);
+    }
+
+    public void setContextPath(String contextPath)
+    {
+        _context.setContextPath(contextPath);
+    }
+
+    public Resource getBaseResource()
+    {
+        return _context.getBaseResource();
+    }
+
+    public String getResourceBase()
+    {
+        return _context.getResourceBase();
+    }
+
+    public void setResourceBase(String resourceBase)
+    {
+        _context.setResourceBase(resourceBase);
+    }
+
+    public ServletTester()
+    {
+        this("/",ServletContextHandler.SECURITY|ServletContextHandler.SESSIONS);
+    }
+
+    public ServletTester(String ctxPath)
+    {
+        this(ctxPath,ServletContextHandler.SECURITY|ServletContextHandler.SESSIONS);
+    }
+
+    public ServletTester(String contextPath,int options)
+    {
+        _context=new ServletContextHandler(_server,contextPath,options);
+        _server.setConnectors(new Connector[]{_connector});
+        addBean(_server);
+    }
+
+    public ServletContextHandler getContext()
+    {
+        return _context;
+    }
+
+    public String getResponses(String request) throws Exception
+    {
+        if (LOG.isDebugEnabled())
+        {
+            LOG.debug("Request: {}",request);
+        }
+        return _connector.getResponses(request);
+    }
+    
+    public String getResponses(String request, long idleFor,TimeUnit units) throws Exception
+    {
+        if (LOG.isDebugEnabled())
+        {
+            LOG.debug("Request: {}",request);
+        }
+        return _connector.getResponses(request, idleFor, units);
+    }
+    
+    public ByteBuffer getResponses(ByteBuffer request) throws Exception
+    {
+        if (LOG.isDebugEnabled())
+        {
+            LOG.debug("Request (Buffer): {}",BufferUtil.toUTF8String(request));
+        }
+        return _connector.getResponses(request);
+    }
+    
+    public ByteBuffer getResponses(ByteBuffer requestsBuffer,long idleFor,TimeUnit units) throws Exception
+    {
+        if (LOG.isDebugEnabled())
+        {
+            LOG.debug("Requests (Buffer): {}",BufferUtil.toUTF8String(requestsBuffer));
+        }
+        return _connector.getResponses(requestsBuffer, idleFor, units);
+    }
+
+    /** Create a port based connector.
+     * This methods adds a port connector to the server
+     * @return A URL to access the server via the connector.
+     * @throws Exception
+     */
+    public String createConnector(boolean localhost) throws Exception
+    {
+        ServerConnector connector = new ServerConnector(_server);
+        if (localhost)
+            connector.setHost("127.0.0.1");
+        _server.addConnector(connector);
+        if (_server.isStarted())
+            connector.start();
+        else
+            connector.open();
+
+        return "http://"+(localhost?"127.0.0.1":
+            InetAddress.getLocalHost().getHostAddress()
+        )+":"+connector.getLocalPort();
+    }
+
+    public LocalConnector createLocalConnector()
+    {
+        LocalConnector connector = new LocalConnector(_server);
+        _server.addConnector(connector);
+        return connector;
+    }
+
+
+
+}
diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/StatisticsServlet.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/StatisticsServlet.java
index 5668a92..c8f3dc9 100644
--- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/StatisticsServlet.java
+++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/StatisticsServlet.java
@@ -31,7 +31,9 @@ import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
+import org.eclipse.jetty.server.AbstractConnector;
 import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.ConnectorStatistics;
 import org.eclipse.jetty.server.Handler;
 import org.eclipse.jetty.server.Server;
 import org.eclipse.jetty.server.handler.ContextHandler;
@@ -39,6 +41,11 @@ import org.eclipse.jetty.server.handler.StatisticsHandler;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
 
+/**
+ * StatisticsServlet
+ *
+ *
+ */
 public class StatisticsServlet extends HttpServlet
 {
     private static final Logger LOG = Log.getLogger(StatisticsServlet.class);
@@ -48,6 +55,11 @@ public class StatisticsServlet extends HttpServlet
     private MemoryMXBean _memoryBean;
     private Connector[] _connectors;
 
+    
+    
+    /** 
+     * @see javax.servlet.GenericServlet#init()
+     */
     public void init() throws ServletException
     {
         ServletContext context = getServletContext();
@@ -73,14 +85,23 @@ public class StatisticsServlet extends HttpServlet
         {
             _restrictToLocalhost = "true".equals(getInitParameter("restrictToLocalhost"));
         }
-
     }
 
+    
+    
+    /** 
+     * @see javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
+     */
     public void doPost(HttpServletRequest sreq, HttpServletResponse sres) throws ServletException, IOException
     {
         doGet(sreq, sres);
     }
 
+    
+    
+    /** 
+     * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
+     */
     protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
     {
         if (_statsHandler == null)
@@ -147,14 +168,16 @@ public class StatisticsServlet extends HttpServlet
         sb.append("    <dispatched>").append(_statsHandler.getDispatched()).append("</dispatched>\n");
         sb.append("    <dispatchedActive>").append(_statsHandler.getDispatchedActive()).append("</dispatchedActive>\n");
         sb.append("    <dispatchedActiveMax>").append(_statsHandler.getDispatchedActiveMax()).append("</dispatchedActiveMax>\n");
-        sb.append("    <dispatchedTimeTotal>").append(_statsHandler.getDispatchedTimeTotal()).append("</dispatchedTimeTotal>\n");
-        sb.append("    <dispatchedTimeMean>").append(_statsHandler.getDispatchedTimeMean()).append("</dispatchedTimeMean>\n");
-        sb.append("    <dispatchedTimeMax>").append(_statsHandler.getDispatchedTimeMax()).append("</dispatchedTimeMax>\n");
-        sb.append("    <dispatchedTimeStdDev>").append(_statsHandler.getDispatchedTimeStdDev()).append("</dispatchedTimeStdDev>\n");
-        
-        sb.append("    <requestsSuspended>").append(_statsHandler.getSuspends()).append("</requestsSuspended>\n");
+        sb.append("    <dispatchedTimeTotalMs>").append(_statsHandler.getDispatchedTimeTotal()).append("</dispatchedTimeTotalMs>\n");
+        sb.append("    <dispatchedTimeMeanMs>").append(_statsHandler.getDispatchedTimeMean()).append("</dispatchedTimeMeanMs>\n");
+        sb.append("    <dispatchedTimeMaxMs>").append(_statsHandler.getDispatchedTimeMax()).append("</dispatchedTimeMaxMs>\n");
+        sb.append("    <dispatchedTimeStdDevMs>").append(_statsHandler.getDispatchedTimeStdDev()).append("</dispatchedTimeStdDevMs>\n");
+ 
+        sb.append("    <asyncRequests>").append(_statsHandler.getAsyncRequests()).append("</asyncRequests>\n");
+        sb.append("    <requestsSuspended>").append(_statsHandler.getAsyncRequestsWaiting()).append("</requestsSuspended>\n");
+        sb.append("    <requestsSuspendedMax>").append(_statsHandler.getAsyncRequestsWaitingMax()).append("</requestsSuspendedMax>\n");
+        sb.append("    <requestsResumed>").append(_statsHandler.getAsyncDispatches()).append("</requestsResumed>\n");
         sb.append("    <requestsExpired>").append(_statsHandler.getExpires()).append("</requestsExpired>\n");
-        sb.append("    <requestsResumed>").append(_statsHandler.getResumes()).append("</requestsResumed>\n");
         sb.append("  </requests>\n");
 
         sb.append("  <responses>\n");
@@ -169,23 +192,31 @@ public class StatisticsServlet extends HttpServlet
         sb.append("  <connections>\n");
         for (Connector connector : _connectors)
         {
-        	sb.append("    <connector>\n");
-        	sb.append("      <name>").append(connector.getName()).append("</name>\n");
-        	sb.append("      <statsOn>").append(connector.getStatsOn()).append("</statsOn>\n");
-            if (connector.getStatsOn())
+            sb.append("    <connector>\n");
+            sb.append("      <name>").append(connector.getClass().getName()).append("@").append(connector.hashCode()).append("</name>\n");
+            sb.append("      <protocols>\n");
+            for (String protocol:connector.getProtocols())
+                sb.append("      <protocol>").append(protocol).append("</protocol>\n");
+            sb.append("      </protocols>\n");
+
+            ConnectorStatistics connectorStats = null;
+
+            if (connector instanceof AbstractConnector)
+                connectorStats = ((AbstractConnector)connector).getBean(ConnectorStatistics.class);
+            if (connectorStats == null)
+                sb.append("      <statsOn>false</statsOn>\n");
+            else
             {
-            	sb.append("    <statsOnMs>").append(connector.getStatsOnMs()).append("</statsOnMs>\n");
-            	sb.append("    <connections>").append(connector.getConnections()).append("</connections>\n");
-            	sb.append("    <connectionsOpen>").append(connector.getConnectionsOpen()).append("</connectionsOpen>\n");
-            	sb.append("    <connectionsOpenMax>").append(connector.getConnectionsOpenMax()).append("</connectionsOpenMax>\n");
-            	sb.append("    <connectionsDurationTotal>").append(connector.getConnectionsDurationTotal()).append("</connectionsDurationTotal>\n");
-            	sb.append("    <connectionsDurationMean>").append(connector.getConnectionsDurationMean()).append("</connectionsDurationMean>\n");
-            	sb.append("    <connectionsDurationMax>").append(connector.getConnectionsDurationMax()).append("</connectionsDurationMax>\n");
-                sb.append("    <connectionsDurationStdDev>").append(connector.getConnectionsDurationStdDev()).append("</connectionsDurationStdDev>\n");
-                sb.append("    <requests>").append(connector.getRequests()).append("</requests>\n");
-                sb.append("    <connectionsRequestsMean>").append(connector.getConnectionsRequestsMean()).append("</connectionsRequestsMean>\n");
-                sb.append("    <connectionsRequestsMax>").append(connector.getConnectionsRequestsMax()).append("</connectionsRequestsMax>\n");
-                sb.append("    <connectionsRequestsStdDev>").append(connector.getConnectionsRequestsStdDev()).append("</connectionsRequestsStdDev>\n");
+                sb.append("      <statsOn>true</statsOn>\n");
+                sb.append("      <connections>").append(connectorStats.getConnections()).append("</connections>\n");
+                sb.append("      <connectionsOpen>").append(connectorStats.getConnectionsOpen()).append("</connectionsOpen>\n");
+                sb.append("      <connectionsOpenMax>").append(connectorStats.getConnectionsOpenMax()).append("</connectionsOpenMax>\n");
+                sb.append("      <connectionsDurationMean>").append(connectorStats.getConnectionDurationMean()).append("</connectionsDurationMean>\n");
+                sb.append("      <connectionsDurationMax>").append(connectorStats.getConnectionDurationMax()).append("</connectionsDurationMax>\n");
+                sb.append("      <connectionsDurationStdDev>").append(connectorStats.getConnectionDurationStdDev()).append("</connectionsDurationStdDev>\n");
+                sb.append("      <messagesIn>").append(connectorStats.getMessagesIn()).append("</messagesIn>\n");
+                sb.append("      <messagesOut>").append(connectorStats.getMessagesIn()).append("</messagesOut>\n");
+                sb.append("      <elapsedMs>").append(connectorStats.getStartedMillis()).append("</elapsedMs>\n");
             }
             sb.append("    </connector>\n");
         }
@@ -195,7 +226,7 @@ public class StatisticsServlet extends HttpServlet
         sb.append("    <heapMemoryUsage>").append(_memoryBean.getHeapMemoryUsage().getUsed()).append("</heapMemoryUsage>\n");
         sb.append("    <nonHeapMemoryUsage>").append(_memoryBean.getNonHeapMemoryUsage().getUsed()).append("</nonHeapMemoryUsage>\n");
         sb.append("  </memory>\n");
-
+        
         sb.append("</statistics>\n");
 
         response.setContentType("text/xml");
@@ -203,6 +234,12 @@ public class StatisticsServlet extends HttpServlet
         pout.write(sb.toString());
     }
 
+    
+    
+    /**
+     * @param response
+     * @throws IOException
+     */
     private void sendTextResponse(HttpServletResponse response) throws IOException
     {
         StringBuilder sb = new StringBuilder();
@@ -211,22 +248,28 @@ public class StatisticsServlet extends HttpServlet
         sb.append("<h2>Connections:</h2>\n");
         for (Connector connector : _connectors)
         {
-            sb.append("<h3>").append(connector.getName()).append("</h3>");
+            sb.append("<h3>").append(connector.getClass().getName()).append("@").append(connector.hashCode()).append("</h3>");
+            sb.append("Protocols:");
+            for (String protocol:connector.getProtocols())
+                sb.append(protocol).append(" ");
+            sb.append("    <br />\n");
+
+            ConnectorStatistics connectorStats = null;
+
+            if (connector instanceof AbstractConnector)
+                connectorStats = ((AbstractConnector)connector).getBean(ConnectorStatistics.class);
 
-            if (connector.getStatsOn())
+            if (connectorStats != null)
             {
-                sb.append("Statistics gathering started ").append(connector.getStatsOnMs()).append("ms ago").append("<br />\n");
-                sb.append("Total connections: ").append(connector.getConnections()).append("<br />\n");
-                sb.append("Current connections open: ").append(connector.getConnectionsOpen()).append("<br />\n");
-                sb.append("Max concurrent connections open: ").append(connector.getConnectionsOpenMax()).append("<br />\n");
-                sb.append("Total connections duration: ").append(connector.getConnectionsDurationTotal()).append("<br />\n");
-                sb.append("Mean connection duration: ").append(connector.getConnectionsDurationMean()).append("<br />\n");
-                sb.append("Max connection duration: ").append(connector.getConnectionsDurationMax()).append("<br />\n");
-                sb.append("Connection duration standard deviation: ").append(connector.getConnectionsDurationStdDev()).append("<br />\n");
-                sb.append("Total requests: ").append(connector.getRequests()).append("<br />\n");
-                sb.append("Mean requests per connection: ").append(connector.getConnectionsRequestsMean()).append("<br />\n");
-                sb.append("Max requests per connection: ").append(connector.getConnectionsRequestsMax()).append("<br />\n");
-                sb.append("Requests per connection standard deviation: ").append(connector.getConnectionsRequestsStdDev()).append("<br />\n");
+                sb.append("Statistics gathering started ").append(connectorStats.getStartedMillis()).append("ms ago").append("<br />\n");
+                sb.append("Total connections: ").append(connectorStats.getConnections()).append("<br />\n");
+                sb.append("Current connections open: ").append(connectorStats.getConnectionsOpen()).append("<br />\n");;
+                sb.append("Max concurrent connections open: ").append(connectorStats.getConnectionsOpenMax()).append("<br />\n");
+                sb.append("Mean connection duration: ").append(connectorStats.getConnectionDurationMean()).append("<br />\n");
+                sb.append("Max connection duration: ").append(connectorStats.getConnectionDurationMax()).append("<br />\n");
+                sb.append("Connection duration standard deviation: ").append(connectorStats.getConnectionDurationStdDev()).append("<br />\n");
+                sb.append("Total messages in: ").append(connectorStats.getMessagesIn()).append("<br />\n");                
+                sb.append("Total messages out: ").append(connectorStats.getMessagesOut()).append("<br />\n");
             }
             else
             {
diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/jmx/package-info.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/jmx/package-info.java
new file mode 100644
index 0000000..31306a0
--- /dev/null
+++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/jmx/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Servlet : Servlet JMX Integration
+ */
+package org.eclipse.jetty.servlet.jmx;
+
diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/listener/ELContextCleaner.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/listener/ELContextCleaner.java
index 6ee1ac2..ed49871 100644
--- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/listener/ELContextCleaner.java
+++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/listener/ELContextCleaner.java
@@ -34,7 +34,7 @@ import org.eclipse.jetty.util.log.Logger;
  *
  * Clean up BeanELResolver when the context is going out
  * of service:
- * 
+ *
  * See http://java.net/jira/browse/GLASSFISH-1649
  * See https://bugs.eclipse.org/bugs/show_bug.cgi?id=353095
  */
@@ -52,42 +52,35 @@ public class ELContextCleaner implements ServletContextListener
         try
         {
             //Check that the BeanELResolver class is on the classpath
-            Class beanELResolver = Loader.loadClass(this.getClass(), "javax.el.BeanELResolver");
+            Class<?> beanELResolver = Loader.loadClass(this.getClass(), "javax.el.BeanELResolver");
 
             //Get a reference via reflection to the properties field which is holding class references
             Field field = getField(beanELResolver);
 
             //Get rid of references
             purgeEntries(field);
-            
-            LOG.info("javax.el.BeanELResolver purged");
+
+            if (LOG.isDebugEnabled())
+                LOG.debug("javax.el.BeanELResolver purged");
         }
-        
+
         catch (ClassNotFoundException e)
         {
             //BeanELResolver not on classpath, ignore
         }
-        catch (SecurityException e)
-        {
-            LOG.warn("Cannot purge classes from javax.el.BeanELResolver", e);
-        }
-        catch (IllegalArgumentException e)
-        {
-            LOG.warn("Cannot purge classes from javax.el.BeanELResolver", e);
-        }
-        catch (IllegalAccessException e)
+        catch (SecurityException | IllegalArgumentException | IllegalAccessException e)
         {
             LOG.warn("Cannot purge classes from javax.el.BeanELResolver", e);
         }
         catch (NoSuchFieldException e)
         {
-            LOG.info("Not cleaning cached beans: no such field javax.el.BeanELResolver.properties");
+            LOG.debug("Not cleaning cached beans: no such field javax.el.BeanELResolver.properties");
         }
-       
+
     }
 
 
-    protected Field getField (Class beanELResolver) 
+    protected Field getField (Class<?> beanELResolver)
     throws SecurityException, NoSuchFieldException
     {
         if (beanELResolver == null)
@@ -96,7 +89,7 @@ public class ELContextCleaner implements ServletContextListener
         return beanELResolver.getDeclaredField("properties");
     }
 
-    protected void purgeEntries (Field properties) 
+    protected void purgeEntries (Field properties)
     throws IllegalArgumentException, IllegalAccessException
     {
         if (properties == null)
@@ -105,22 +98,27 @@ public class ELContextCleaner implements ServletContextListener
         if (!properties.isAccessible())
             properties.setAccessible(true);
 
-        ConcurrentHashMap map = (ConcurrentHashMap) properties.get(null);
+        ConcurrentHashMap<Class<?>, Object> map = (ConcurrentHashMap<Class<?>, Object>) properties.get(null);
         if (map == null)
             return;
-        
-        Iterator<Class> itor = map.keySet().iterator();
-        while (itor.hasNext()) 
+
+        Iterator<Class<?>> itor = map.keySet().iterator();
+        while (itor.hasNext())
         {
-            Class clazz = itor.next();
-            LOG.info("Clazz: "+clazz+" loaded by "+clazz.getClassLoader());
+            Class<?> clazz = itor.next();
+            if (LOG.isDebugEnabled())
+                LOG.debug("Clazz: "+clazz+" loaded by "+clazz.getClassLoader());
             if (Thread.currentThread().getContextClassLoader().equals(clazz.getClassLoader()))
             {
-                itor.remove();  
-                LOG.info("removed");
+                itor.remove();
+                if (LOG.isDebugEnabled())
+                    LOG.debug("removed");
             }
             else
-                LOG.info("not removed: "+"contextclassloader="+Thread.currentThread().getContextClassLoader()+"clazz's classloader="+clazz.getClassLoader());
+            {
+                if (LOG.isDebugEnabled())
+                    LOG.debug("not removed: "+"contextclassloader="+Thread.currentThread().getContextClassLoader()+"clazz's classloader="+clazz.getClassLoader());
+            }
         }
     }
 }
diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/listener/package-info.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/listener/package-info.java
new file mode 100644
index 0000000..90b4392
--- /dev/null
+++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/listener/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Servlet : Useful Servlet Listeners
+ */
+package org.eclipse.jetty.servlet.listener;
+
diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/package-info.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/package-info.java
new file mode 100644
index 0000000..0040dea
--- /dev/null
+++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Server : Modular Servlet Integration
+ */
+package org.eclipse.jetty.servlet;
+
diff --git a/jetty-servlet/src/main/resources/org/eclipse/jetty/servlet/jmx/FilterMapping-mbean.properties b/jetty-servlet/src/main/resources/org/eclipse/jetty/servlet/jmx/FilterMapping-mbean.properties
deleted file mode 100644
index afcbe2f..0000000
--- a/jetty-servlet/src/main/resources/org/eclipse/jetty/servlet/jmx/FilterMapping-mbean.properties
+++ /dev/null
@@ -1,4 +0,0 @@
-FilterMapping: Filter Mapping
-filterName: RO:Filter Name
-pathSpecs: RO:URL patterns
-servletNames: RO:Servlet Names
diff --git a/jetty-servlet/src/main/resources/org/eclipse/jetty/servlet/jmx/Holder-mbean.properties b/jetty-servlet/src/main/resources/org/eclipse/jetty/servlet/jmx/Holder-mbean.properties
deleted file mode 100644
index 7730ff4..0000000
--- a/jetty-servlet/src/main/resources/org/eclipse/jetty/servlet/jmx/Holder-mbean.properties
+++ /dev/null
@@ -1,5 +0,0 @@
-Holder: Holder
-name: RO:Name
-displayName: RO:Display Name
-className: RO:Class Name
-initParameters: RO:Initial parameters
diff --git a/jetty-servlet/src/main/resources/org/eclipse/jetty/servlet/jmx/ServletContextHandler-mbean.properties b/jetty-servlet/src/main/resources/org/eclipse/jetty/servlet/jmx/ServletContextHandler-mbean.properties
deleted file mode 100644
index 2c1a05a..0000000
--- a/jetty-servlet/src/main/resources/org/eclipse/jetty/servlet/jmx/ServletContextHandler-mbean.properties
+++ /dev/null
@@ -1,4 +0,0 @@
-ServletContextHandler: Servlet Context Handler
-securityHandler: MObject: The context's security handler
-servletHandler: MObject: The context's servlet handler
-sessionHandler: MObject: The context's session handler
diff --git a/jetty-servlet/src/main/resources/org/eclipse/jetty/servlet/jmx/ServletHandler-mbean.properties b/jetty-servlet/src/main/resources/org/eclipse/jetty/servlet/jmx/ServletHandler-mbean.properties
deleted file mode 100644
index 9326c19..0000000
--- a/jetty-servlet/src/main/resources/org/eclipse/jetty/servlet/jmx/ServletHandler-mbean.properties
+++ /dev/null
@@ -1,5 +0,0 @@
-ServletHandler: Servlet Handler
-servlets: MObject:RO:Servlets
-servletMappings: MObject:RO:Mappings of servlets
-filters: MObject:RO:Filters
-filterMappings: MObject:RO:Mappings of filters
diff --git a/jetty-servlet/src/main/resources/org/eclipse/jetty/servlet/jmx/ServletHolder-mbean.properties b/jetty-servlet/src/main/resources/org/eclipse/jetty/servlet/jmx/ServletHolder-mbean.properties
deleted file mode 100644
index 3844546..0000000
--- a/jetty-servlet/src/main/resources/org/eclipse/jetty/servlet/jmx/ServletHolder-mbean.properties
+++ /dev/null
@@ -1,4 +0,0 @@
-ServletHolder: Servlet Holder
-initOrder: Initialization order
-runAsRole: Role to run servlet as
-forcedPath: Forced servlet path
diff --git a/jetty-servlet/src/main/resources/org/eclipse/jetty/servlet/jmx/ServletMapping-mbean.properties b/jetty-servlet/src/main/resources/org/eclipse/jetty/servlet/jmx/ServletMapping-mbean.properties
deleted file mode 100644
index efe07ff..0000000
--- a/jetty-servlet/src/main/resources/org/eclipse/jetty/servlet/jmx/ServletMapping-mbean.properties
+++ /dev/null
@@ -1,3 +0,0 @@
-ServletMapping: Servlet Mapping
-servletName: RO:Servlet Name
-pathSpecs: RO:URL patterns
diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncContextDispatchWithQueryStrings.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncContextDispatchWithQueryStrings.java
index 610c032..86eebe5 100644
--- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncContextDispatchWithQueryStrings.java
+++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncContextDispatchWithQueryStrings.java
@@ -23,14 +23,12 @@ import static org.junit.Assert.assertTrue;
 
 import java.io.IOException;
 
+import javax.servlet.AsyncContext;
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import org.eclipse.jetty.continuation.Continuation;
-import org.eclipse.jetty.continuation.ContinuationSupport;
-import org.eclipse.jetty.server.AsyncContinuation;
 import org.eclipse.jetty.server.Connector;
 import org.eclipse.jetty.server.Handler;
 import org.eclipse.jetty.server.LocalConnector;
@@ -43,70 +41,71 @@ import org.junit.Test;
 
 /**
  * This tests verifies that merging of queryStrings works when dispatching
- * Requests via {@link Continuation} multiple times.
- * 
- * @author tbecker
- * 
+ * Requests via {@link AsyncContext} multiple times.
  */
 public class AsyncContextDispatchWithQueryStrings {
 
-	private Server _server = new Server();
-	private ServletContextHandler _contextHandler = new ServletContextHandler(ServletContextHandler.NO_SESSIONS);
-	private LocalConnector _connector = new LocalConnector();
+        private Server _server = new Server();
+        private ServletContextHandler _contextHandler = new ServletContextHandler(ServletContextHandler.NO_SESSIONS);
+        private LocalConnector _connector = new LocalConnector(_server);
 
-	@Before
-	public void setUp() throws Exception {
-		_connector.setMaxIdleTime(30000);
-		_server.setConnectors(new Connector[] { _connector });
+        @Before
+        public void setUp() throws Exception {
+                _connector.setIdleTimeout(30000);
+                _server.setConnectors(new Connector[] { _connector });
 
-		_contextHandler.setContextPath("/");
-		_contextHandler.addServlet(new ServletHolder(new TestServlet()), "/initialCall");
-		_contextHandler.addServlet(new ServletHolder(new TestServlet()), "/firstDispatchWithNewQueryString");
-		_contextHandler.addServlet(new ServletHolder(new TestServlet()), "/secondDispatchNewValueForExistingQueryString");
+                _contextHandler.setContextPath("/");
+                _contextHandler.addServlet(new ServletHolder(new TestServlet()), "/initialCall");
+                _contextHandler.addServlet(new ServletHolder(new TestServlet()), "/firstDispatchWithNewQueryString");
+                _contextHandler.addServlet(new ServletHolder(new TestServlet()), "/secondDispatchNewValueForExistingQueryString");
 
-		HandlerList handlers = new HandlerList();
-		handlers.setHandlers(new Handler[] { _contextHandler, new DefaultHandler() });
+                HandlerList handlers = new HandlerList();
+                handlers.setHandlers(new Handler[] { _contextHandler, new DefaultHandler() });
 
-		_server.setHandler(handlers);
-		_server.start();
-	}
+                _server.setHandler(handlers);
+                _server.start();
+        }
 
-	@Test
-	public void testMultipleDispatchesWithNewQueryStrings() throws Exception {
-		String request = "GET /initialCall?initialParam=right HTTP/1.1\r\n" + "Host: localhost\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n"
-				+ "Connection: close\r\n" + "\r\n";
-		String responseString = _connector.getResponses(request);
-		assertTrue("Not the expected response. Check STDOUT for details.", responseString.startsWith("HTTP/1.1 200"));
-	}
+        @Test
+        public void testMultipleDispatchesWithNewQueryStrings() throws Exception {
+                String request = "GET /initialCall?initialParam=right HTTP/1.1\r\n" + "Host: localhost\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n"
+                                + "Connection: close\r\n" + "\r\n";
+                String responseString = _connector.getResponses(request);
+                assertTrue("Not the expected response. Check STDOUT for details.", responseString.startsWith("HTTP/1.1 200"));
+        }
 
-	@After
-	public void tearDown() throws Exception {
-		_server.stop();
-		_server.join();
-	}
+        @After
+        public void tearDown() throws Exception {
+                _server.stop();
+                _server.join();
+        }
 
-	private class TestServlet extends HttpServlet {
-		private static final long serialVersionUID = 1L;
+        private class TestServlet extends HttpServlet {
+                private static final long serialVersionUID = 1L;
 
-		protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
-			String path = request.getRequestURI();
-			String queryString = request.getQueryString();
-			if ("/initialCall".equals(path)) {
-				AsyncContinuation continuation = (AsyncContinuation) ContinuationSupport.getContinuation(request);
-				continuation.suspend();
-				continuation.dispatch("/firstDispatchWithNewQueryString?newQueryString=initialValue");
-				assertEquals("initialParam=right", queryString);
-			} else if ("/firstDispatchWithNewQueryString".equals(path)) {
-				AsyncContinuation continuation = (AsyncContinuation) ContinuationSupport.getContinuation(request);
-				continuation.suspend();
-				continuation.dispatch("/secondDispatchNewValueForExistingQueryString?newQueryString=newValue");
-				assertEquals("newQueryString=initialValue&initialParam=right", queryString);
-			} else {
-				response.setContentType("text/html");
-				response.setStatus(HttpServletResponse.SC_OK);
-				response.getWriter().println("<h1>woohhooooo</h1>");
-				assertEquals("newQueryString=newValue&initialParam=right", queryString);
-			}
-		}
-	}
+                @Override
+                protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
+                        String path = request.getRequestURI();
+                        String queryString = request.getQueryString();
+                        if ("/initialCall".equals(path)) 
+                        {
+                            AsyncContext async = request.startAsync();
+                            async.dispatch("/firstDispatchWithNewQueryString?newQueryString=initialValue");
+                            assertEquals("initialParam=right", queryString);
+                        } 
+                        else if ("/firstDispatchWithNewQueryString".equals(path)) 
+                        {
+                            AsyncContext async = request.startAsync();
+                            async.dispatch("/secondDispatchNewValueForExistingQueryString?newQueryString=newValue");
+                            assertEquals("newQueryString=initialValue&initialParam=right", queryString);
+                        } 
+                        else 
+                        {
+                            response.setContentType("text/html");
+                            response.setStatus(HttpServletResponse.SC_OK);
+                            response.getWriter().println("<h1>woohhooooo</h1>");
+                            assertEquals("newQueryString=newValue&initialParam=right", queryString);
+                        }
+                }
+        }
 }
diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncContextListenersTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncContextListenersTest.java
new file mode 100644
index 0000000..11485ca
--- /dev/null
+++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncContextListenersTest.java
@@ -0,0 +1,282 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlet;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.Socket;
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.AsyncEvent;
+import javax.servlet.AsyncListener;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.toolchain.test.http.SimpleHttpParser;
+import org.eclipse.jetty.toolchain.test.http.SimpleHttpResponse;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class AsyncContextListenersTest
+{
+    private Server _server;
+    private ServerConnector _connector;
+
+    public void prepare(String path, HttpServlet servlet) throws Exception
+    {
+        _server = new Server();
+        _connector = new ServerConnector(_server);
+        _server.addConnector(_connector);
+
+        ServletContextHandler context = new ServletContextHandler(_server, "/", false, false);
+        context.addServlet(new ServletHolder(servlet), path);
+
+        _server.start();
+    }
+
+    @After
+    public void dispose() throws Exception
+    {
+        _server.stop();
+    }
+
+    @Test
+    public void testListenerClearedOnSecondRequest() throws Exception
+    {
+        final AtomicInteger completes = new AtomicInteger();
+        String path = "/path";
+        prepare(path, new HttpServlet()
+        {
+            @Override
+            protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+            {
+                AsyncContext asyncContext = request.startAsync(request, response);
+                asyncContext.addListener(new AsyncListener()
+                {
+                    @Override
+                    public void onStartAsync(AsyncEvent event) throws IOException
+                    {
+                    }
+
+                    @Override
+                    public void onComplete(AsyncEvent event) throws IOException
+                    {
+                        completes.incrementAndGet();
+                    }
+
+                    @Override
+                    public void onTimeout(AsyncEvent event) throws IOException
+                    {
+                    }
+
+                    @Override
+                    public void onError(AsyncEvent event) throws IOException
+                    {
+                    }
+                });
+                asyncContext.complete();
+            }
+        });
+
+        try (Socket socket = new Socket("localhost", _connector.getLocalPort()))
+        {
+            OutputStream output = socket.getOutputStream();
+
+            String request = "" +
+                    "GET " + path + " HTTP/1.1\r\n" +
+                    "Host: localhost\r\n" +
+                    "\r\n";
+            output.write(request.getBytes(StandardCharsets.UTF_8));
+            output.flush();
+
+            BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8));
+            SimpleHttpParser parser = new SimpleHttpParser();
+            SimpleHttpResponse response = parser.readResponse(reader);
+            Assert.assertEquals("200", response.getCode());
+            Assert.assertEquals(1, completes.get());
+
+            // Send a second request
+            completes.set(0);
+            output.write(request.getBytes(StandardCharsets.UTF_8));
+            output.flush();
+
+            response = parser.readResponse(reader);
+            Assert.assertEquals("200", response.getCode());
+            Assert.assertEquals(1, completes.get());
+        }
+    }
+
+    @Test
+    public void testListenerAddedFromListener() throws Exception
+    {
+        final AtomicInteger completes = new AtomicInteger();
+        String path = "/path";
+        prepare(path, new HttpServlet()
+        {
+            @Override
+            protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+            {
+                AsyncContext asyncContext = request.startAsync(request, response);
+                asyncContext.addListener(new AsyncListener()
+                {
+                    @Override
+                    public void onStartAsync(AsyncEvent event) throws IOException
+                    {
+                        // This method should not be invoked because we add the
+                        // listener *after* having called startAsync(), but we
+                        // add a listener to be sure it's not called (it will
+                        // screw up the completes count and test will fail).
+                        event.getAsyncContext().addListener(this);
+                    }
+
+                    @Override
+                    public void onComplete(AsyncEvent event) throws IOException
+                    {
+                        completes.incrementAndGet();
+                    }
+
+                    @Override
+                    public void onTimeout(AsyncEvent event) throws IOException
+                    {
+                    }
+
+                    @Override
+                    public void onError(AsyncEvent event) throws IOException
+                    {
+                    }
+                });
+                asyncContext.complete();
+            }
+        });
+
+        try (Socket socket = new Socket("localhost", _connector.getLocalPort()))
+        {
+            OutputStream output = socket.getOutputStream();
+
+            String request = "" +
+                    "GET " + path + " HTTP/1.1\r\n" +
+                    "Host: localhost\r\n" +
+                    "\r\n";
+            output.write(request.getBytes(StandardCharsets.UTF_8));
+            output.flush();
+
+            BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8));
+            SimpleHttpParser parser = new SimpleHttpParser();
+            SimpleHttpResponse response = parser.readResponse(reader);
+            Assert.assertEquals("200", response.getCode());
+            Assert.assertEquals(1, completes.get());
+
+            // Send a second request
+            completes.set(0);
+            output.write(request.getBytes(StandardCharsets.UTF_8));
+            output.flush();
+
+            response = parser.readResponse(reader);
+            Assert.assertEquals("200", response.getCode());
+            Assert.assertEquals(1, completes.get());
+        }
+    }
+    @Test
+    public void testAsyncDispatchAsyncCompletePreservesListener() throws Exception
+    {
+        final AtomicInteger completes = new AtomicInteger();
+        final String path = "/path";
+        prepare(path + "/*", new HttpServlet()
+        {
+            @Override
+            protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+            {
+                String requestURI = request.getRequestURI();
+                if (requestURI.endsWith("/one"))
+                {
+                    AsyncContext asyncContext = request.startAsync(request, response);
+                    asyncContext.addListener(new AsyncListener()
+                    {
+                        @Override
+                        public void onStartAsync(AsyncEvent event) throws IOException
+                        {
+                            event.getAsyncContext().addListener(this);
+                        }
+
+                        @Override
+                        public void onComplete(AsyncEvent event) throws IOException
+                        {
+                            completes.incrementAndGet();
+                        }
+
+                        @Override
+                        public void onTimeout(AsyncEvent event) throws IOException
+                        {
+                        }
+
+                        @Override
+                        public void onError(AsyncEvent event) throws IOException
+                        {
+                        }
+                    });
+                    // This dispatch() will call startAsync() again which will
+                    // clear the previous listeners (as per the specification)
+                    // but add a new listener from onStartAsync().
+                    asyncContext.dispatch(path + "/two");
+                }
+                else if (requestURI.endsWith("/two"))
+                {
+                    AsyncContext asyncContext = request.startAsync(request, response);
+                    asyncContext.complete();
+                }
+            }
+        });
+
+        try (Socket socket = new Socket("localhost", _connector.getLocalPort()))
+        {
+            OutputStream output = socket.getOutputStream();
+
+            String request = "" +
+                    "GET " + path + "/one HTTP/1.1\r\n" +
+                    "Host: localhost\r\n" +
+                    "\r\n";
+            output.write(request.getBytes(StandardCharsets.UTF_8));
+            output.flush();
+
+            BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8));
+            SimpleHttpParser parser = new SimpleHttpParser();
+            SimpleHttpResponse response = parser.readResponse(reader);
+            Assert.assertEquals("200", response.getCode());
+            Assert.assertEquals(1, completes.get());
+
+            // Send a second request
+            completes.set(0);
+            output.write(request.getBytes(StandardCharsets.UTF_8));
+            output.flush();
+
+            response = parser.readResponse(reader);
+            Assert.assertEquals("200", response.getCode());
+            Assert.assertEquals(1, completes.get());
+        }
+    }
+}
diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncContextTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncContextTest.java
index fbc71f6..038fc2b 100644
--- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncContextTest.java
+++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncContextTest.java
@@ -38,34 +38,40 @@ import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpServletResponseWrapper;
 
-import org.junit.Assert;
 import org.eclipse.jetty.server.Connector;
 import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.HttpConnectionFactory;
 import org.eclipse.jetty.server.LocalConnector;
+import org.eclipse.jetty.server.QuietServletException;
 import org.eclipse.jetty.server.Request;
 import org.eclipse.jetty.server.Server;
 import org.eclipse.jetty.server.handler.DefaultHandler;
 import org.eclipse.jetty.server.handler.HandlerList;
 import org.junit.After;
+import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 
 /**
  * This tests the correct functioning of the AsyncContext
- * 
+ *
  * tests for #371649 and #371635
  */
 public class AsyncContextTest
 {
 
-    private Server _server = new Server();
-    private ServletContextHandler _contextHandler = new ServletContextHandler(ServletContextHandler.NO_SESSIONS);
-    private LocalConnector _connector = new LocalConnector();
+    private Server _server;
+    private ServletContextHandler _contextHandler;
+    private LocalConnector _connector;
 
     @Before
     public void setUp() throws Exception
     {
-        _connector.setMaxIdleTime(5000);
+        _server = new Server();
+        _contextHandler = new ServletContextHandler(ServletContextHandler.NO_SESSIONS);
+        _connector = new LocalConnector(_server);
+        _connector.setIdleTimeout(5000);
+        _connector.getConnectionFactory(HttpConnectionFactory.class).getHttpConfiguration().setSendDateHeader(false);
         _server.setConnectors(new Connector[]
         { _connector });
 
@@ -93,6 +99,12 @@ public class AsyncContextTest
         _server.start();
     }
 
+    @After
+    public void after() throws Exception
+    {
+        _server.stop();
+    }
+
     @Test
     public void testSimpleAsyncContext() throws Exception
     {
@@ -103,9 +115,10 @@ public class AsyncContextTest
 
         BufferedReader br = parseHeader(responseString);
 
-        Assert.assertEquals("servlet gets right path","doGet:getServletPath:/servletPath",br.readLine());
+        Assert.assertEquals("servlet gets right path", "doGet:getServletPath:/servletPath", br.readLine());
         Assert.assertEquals("async context gets right path in get","doGet:async:getServletPath:/servletPath",br.readLine());
         Assert.assertEquals("async context gets right path in async","async:run:attr:servletPath:/servletPath",br.readLine());
+   
     }
 
     @Test
@@ -127,7 +140,7 @@ public class AsyncContextTest
 
         Assert.assertEquals("error servlet","ERROR: /error",br.readLine());
         Assert.assertEquals("error servlet","PathInfo= /IOE",br.readLine());
-        Assert.assertEquals("error servlet","EXCEPTION: java.io.IOException: Test",br.readLine());
+        Assert.assertEquals("error servlet","EXCEPTION: org.eclipse.jetty.server.QuietServletException: java.io.IOException: Test",br.readLine());
     }
 
     @Test
@@ -148,7 +161,7 @@ public class AsyncContextTest
         br.readLine();// empty
         Assert.assertEquals("error servlet","ERROR: /error",br.readLine());
         Assert.assertEquals("error servlet","PathInfo= /IOE",br.readLine());
-        Assert.assertEquals("error servlet","EXCEPTION: java.io.IOException: Test",br.readLine());
+        Assert.assertEquals("error servlet","EXCEPTION: org.eclipse.jetty.server.QuietServletException: java.io.IOException: Test",br.readLine());
     }
     
     @Test
@@ -169,7 +182,7 @@ public class AsyncContextTest
         br.readLine();// empty
         Assert.assertEquals("error servlet","ERROR: /error",br.readLine());
         Assert.assertEquals("error servlet","PathInfo= /IOE",br.readLine());
-        Assert.assertEquals("error servlet","EXCEPTION: java.io.IOException: Test",br.readLine());
+        Assert.assertEquals("error servlet","EXCEPTION: org.eclipse.jetty.server.QuietServletException: java.io.IOException: Test",br.readLine());
     }
     
     @Test
@@ -208,17 +221,27 @@ public class AsyncContextTest
         Assert.assertEquals("query string attr is correct","async:run:attr:queryString:dispatch=true",br.readLine());
         Assert.assertEquals("context path attr is correct","async:run:attr:contextPath:/ctx",br.readLine());
         Assert.assertEquals("request uri attr is correct","async:run:attr:requestURI:/ctx/servletPath",br.readLine());
+
+        try
+        {
+            __asyncContext.getRequest();
+            Assert.fail();
+        }
+        catch (IllegalStateException e)
+        {
+            
+        }
     }
-    
+
     @Test
     public void testDispatchAsyncContextEncodedPathAndQueryString() throws Exception
     {
         String request = "GET /ctx/path%20with%20spaces/servletPath?dispatch=true&queryStringWithEncoding=space%20space HTTP/1.1\r\n" + "Host: localhost\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n"
                 + "Connection: close\r\n" + "\r\n";
         String responseString = _connector.getResponses(request);
-        
+
         BufferedReader br = parseHeader(responseString);
-        
+
         assertThat("servlet gets right path",br.readLine(),equalTo("doGet:getServletPath:/servletPath2"));
         assertThat("async context gets right path in get",br.readLine(), equalTo("doGet:async:getServletPath:/servletPath2"));
         assertThat("servlet path attr is original",br.readLine(),equalTo("async:run:attr:servletPath:/path with spaces/servletPath"));
@@ -269,17 +292,18 @@ public class AsyncContextTest
                 + "\r\n";
 
         String responseString = _connector.getResponses(request);
-
         BufferedReader br = parseHeader(responseString);
-
         assertThat("!ForwardingServlet",br.readLine(),equalTo("Dispatched back to ForwardingServlet"));
     }
 
     @Test
     public void testDispatchRequestResponse() throws Exception
     {
-        String request = "GET /ctx/forward?dispatchRequestResponse=true HTTP/1.1\r\n" + "Host: localhost\r\n"
-                + "Content-Type: application/x-www-form-urlencoded\r\n" + "Connection: close\r\n" + "\r\n";
+        String request = "GET /ctx/forward?dispatchRequestResponse=true HTTP/1.1\r\n" + 
+           "Host: localhost\r\n" + 
+           "Content-Type: application/x-www-form-urlencoded\r\n" + 
+           "Connection: close\r\n" + 
+           "\r\n";
 
         String responseString = _connector.getResponses(request);
 
@@ -307,7 +331,7 @@ public class AsyncContextTest
         @Override
         protected void doGet(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException
         {
-            if (((Request)request).getDispatcherType() == DispatcherType.ASYNC)
+            if (request.getDispatcherType() == DispatcherType.ASYNC)
             {
                 response.getOutputStream().print("Dispatched back to ForwardingServlet");
             }
@@ -318,10 +342,12 @@ public class AsyncContextTest
         }
     }
 
+    public static volatile AsyncContext __asyncContext; 
+    
     private class AsyncDispatchingServlet extends HttpServlet
     {
         private static final long serialVersionUID = 1L;
-
+        
         @Override
         protected void doGet(HttpServletRequest req, final HttpServletResponse response) throws ServletException, IOException
         {
@@ -338,13 +364,14 @@ public class AsyncContextTest
                 {
                     wrapped = true;
                     asyncContext = request.startAsync(request, new Wrapper(response));
+                    __asyncContext=asyncContext;
                 }
                 else
                 {
                     asyncContext = request.startAsync();
+                    __asyncContext=asyncContext;
                 }
 
-
                 new Thread(new DispatchingRunnable(asyncContext, wrapped)).start();
             }
         }
@@ -390,7 +417,7 @@ public class AsyncContextTest
 
         Assert.assertEquals("error servlet","ERROR: /error",br.readLine());
         Assert.assertEquals("error servlet","PathInfo= /500",br.readLine());
-        Assert.assertEquals("error servlet","EXCEPTION: java.io.IOException: TEST",br.readLine());
+        Assert.assertEquals("error servlet","EXCEPTION: java.lang.RuntimeException: TEST",br.readLine());
     }
 
     private class DispatchingRunnable implements Runnable
@@ -464,7 +491,7 @@ public class AsyncContextTest
                     @Override
                     public void onTimeout(AsyncEvent event) throws IOException
                     {
-                        throw new IOException("TEST");
+                        throw new RuntimeException("TEST");
                     }
                     
                     @Override
@@ -495,20 +522,20 @@ public class AsyncContextTest
         protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
         {
             if (request.getParameter("dispatch") != null)
-            {                
+            {
                 AsyncContext asyncContext = request.startAsync(request,response);
+                __asyncContext=asyncContext;
                 asyncContext.dispatch("/servletPath2");
             }
             else
             {
                 response.getOutputStream().print("doGet:getServletPath:" + request.getServletPath() + "\n");
                 AsyncContext asyncContext = request.startAsync(request,response);
+                __asyncContext=asyncContext;
                 response.getOutputStream().print("doGet:async:getServletPath:" + ((HttpServletRequest)asyncContext.getRequest()).getServletPath() + "\n");
                 asyncContext.start(new AsyncRunnable(asyncContext));
 
             }
-            return;
-
         }
     }
 
@@ -521,9 +548,9 @@ public class AsyncContextTest
         {
             response.getOutputStream().print("doGet:getServletPath:" + request.getServletPath() + "\n");
             AsyncContext asyncContext = request.startAsync(request, response);
-            response.getOutputStream().print("doGet:async:getServletPath:" + ((HttpServletRequest)asyncContext.getRequest()).getServletPath() + "\n");         
+            __asyncContext=asyncContext;
+            response.getOutputStream().print("doGet:async:getServletPath:" + ((HttpServletRequest)asyncContext.getRequest()).getServletPath() + "\n");
             asyncContext.start(new AsyncRunnable(asyncContext));
-            return;
         }
     }
     
@@ -551,7 +578,7 @@ public class AsyncContextTest
                     request.getAsyncContext().complete();
                 }
                     
-                throw new IOException("Test");
+                throw new QuietServletException(new IOException("Test"));
             }
         }
     }
@@ -568,21 +595,21 @@ public class AsyncContextTest
         @Override
         public void run()
         {
-            HttpServletRequest req = (HttpServletRequest)_context.getRequest();         
-                        
+            HttpServletRequest req = (HttpServletRequest)_context.getRequest();
+
             try
             {
                 _context.getResponse().getOutputStream().print("async:run:attr:servletPath:" + req.getAttribute(AsyncContext.ASYNC_SERVLET_PATH) + "\n");
-                _context.getResponse().getOutputStream().print("async:run:attr:pathInfo:" + req.getAttribute(AsyncContext.ASYNC_PATH_INFO) + "\n");              
-                _context.getResponse().getOutputStream().print("async:run:attr:queryString:" + req.getAttribute(AsyncContext.ASYNC_QUERY_STRING) + "\n");              
-                _context.getResponse().getOutputStream().print("async:run:attr:contextPath:" + req.getAttribute(AsyncContext.ASYNC_CONTEXT_PATH) + "\n");              
-                _context.getResponse().getOutputStream().print("async:run:attr:requestURI:" + req.getAttribute(AsyncContext.ASYNC_REQUEST_URI) + "\n");              
+                _context.getResponse().getOutputStream().print("async:run:attr:pathInfo:" + req.getAttribute(AsyncContext.ASYNC_PATH_INFO) + "\n");
+                _context.getResponse().getOutputStream().print("async:run:attr:queryString:" + req.getAttribute(AsyncContext.ASYNC_QUERY_STRING) + "\n");
+                _context.getResponse().getOutputStream().print("async:run:attr:contextPath:" + req.getAttribute(AsyncContext.ASYNC_CONTEXT_PATH) + "\n");
+                _context.getResponse().getOutputStream().print("async:run:attr:requestURI:" + req.getAttribute(AsyncContext.ASYNC_REQUEST_URI) + "\n");
             }
             catch (IOException e)
             {
                 e.printStackTrace();
             }
-            _context.complete();         
+            _context.complete();
         }
     }
 
@@ -593,6 +620,6 @@ public class AsyncContextTest
             super(response);
         }
     }
-    
-    
+
+
 }
diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncIOServletTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncIOServletTest.java
new file mode 100644
index 0000000..51d1b10
--- /dev/null
+++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncIOServletTest.java
@@ -0,0 +1,560 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlet;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.Socket;
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.ReadListener;
+import javax.servlet.ServletException;
+import javax.servlet.ServletInputStream;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.WriteListener;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.toolchain.test.http.SimpleHttpParser;
+import org.eclipse.jetty.toolchain.test.http.SimpleHttpResponse;
+import org.eclipse.jetty.util.IO;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Test;
+
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.instanceOf;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+public class AsyncIOServletTest
+{
+    private Server server;
+    private ServerConnector connector;
+    private ServletContextHandler context;
+    private String path = "/path";
+
+    public void startServer(HttpServlet servlet) throws Exception
+    {
+        server = new Server();
+        connector = new ServerConnector(server);
+        server.addConnector(connector);
+
+        context = new ServletContextHandler(server, "/", false, false);
+        ServletHolder holder = new ServletHolder(servlet);
+        holder.setAsyncSupported(true);
+        context.addServlet(holder, path);
+
+        server.start();
+    }
+
+    @After
+    public void stopServer() throws Exception
+    {
+        server.stop();
+    }
+
+    @Test
+    public void testAsyncReadThrowsException() throws Exception
+    {
+        testAsyncReadThrows(new NullPointerException("explicitly_thrown_by_test"));
+    }
+
+    @Test
+    public void testAsyncReadThrowsError() throws Exception
+    {
+        testAsyncReadThrows(new Error("explicitly_thrown_by_test"));
+    }
+
+    private void testAsyncReadThrows(final Throwable throwable) throws Exception
+    {
+        final CountDownLatch latch = new CountDownLatch(1);
+        startServer(new HttpServlet()
+        {
+            @Override
+            protected void service(HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException
+            {
+                final AsyncContext asyncContext = request.startAsync(request, response);
+                request.getInputStream().setReadListener(new ReadListener()
+                {
+                    @Override
+                    public void onDataAvailable() throws IOException
+                    {
+                        if (throwable instanceof RuntimeException)
+                            throw (RuntimeException)throwable;
+                        if (throwable instanceof Error)
+                            throw (Error)throwable;
+                        throw new IOException(throwable);
+                    }
+
+                    @Override
+                    public void onAllDataRead() throws IOException
+                    {
+                    }
+
+                    @Override
+                    public void onError(Throwable t)
+                    {
+                        Assert.assertThat("onError type",t,instanceOf(throwable.getClass()));
+                        Assert.assertThat("onError message",t.getMessage(),is(throwable.getMessage()));
+                        latch.countDown();
+                        response.setStatus(500);
+                        asyncContext.complete();
+                    }
+                });
+            }
+        });
+
+        String data = "0123456789";
+        String request = "GET " + path + " HTTP/1.1\r\n" +
+                "Host: localhost:" + connector.getLocalPort() + "\r\n" +
+                "Content-Length: " + data.length() + "\r\n" +
+                "\r\n" +
+                data;
+
+        try (Socket client = new Socket("localhost", connector.getLocalPort()))
+        {
+            OutputStream output = client.getOutputStream();
+            output.write(request.getBytes("UTF-8"));
+            output.flush();
+
+            SimpleHttpParser parser = new SimpleHttpParser();
+            SimpleHttpResponse response = parser.readResponse(new BufferedReader(new InputStreamReader(client.getInputStream(), "UTF-8")));
+
+            Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+            Assert.assertEquals("500", response.getCode());
+        }
+    }
+
+    @Test
+    public void testAsyncReadIdleTimeout() throws Exception
+    {
+        final int status = 567;
+        startServer(new HttpServlet()
+        {
+            @Override
+            protected void service(HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException
+            {
+                final AsyncContext asyncContext = request.startAsync(request, response);
+                asyncContext.setTimeout(0);
+                final ServletInputStream inputStream = request.getInputStream();
+                inputStream.setReadListener(new ReadListener()
+                {
+                    @Override
+                    public void onDataAvailable() throws IOException
+                    {
+                        while (inputStream.isReady() && !inputStream.isFinished())
+                            inputStream.read();
+                    }
+
+                    @Override
+                    public void onAllDataRead() throws IOException
+                    {
+                    }
+
+                    @Override
+                    public void onError(Throwable t)
+                    {
+                        response.setStatus(status);
+                        // Do not put Connection: close header here, the test
+                        // verifies that the server closes no matter what.
+                        asyncContext.complete();
+                    }
+                });
+            }
+        });
+        server.stop();
+        long idleTimeout = 1000;
+        connector.setIdleTimeout(idleTimeout);
+        server.start();
+
+        String data1 = "0123456789";
+        String data2 = "ABCDEF";
+        // Only send the first chunk of data and then let it idle timeout.
+        String request = "GET " + path + " HTTP/1.1\r\n" +
+                "Host: localhost:" + connector.getLocalPort() + "\r\n" +
+                "Content-Length: " + (data1.length() + data2.length()) + "\r\n" +
+                "\r\n" +
+                data1;
+
+        try (Socket client = new Socket("localhost", connector.getLocalPort()))
+        {
+            OutputStream output = client.getOutputStream();
+            output.write(request.getBytes("UTF-8"));
+            output.flush();
+
+            SimpleHttpParser parser = new SimpleHttpParser();
+            SimpleHttpResponse response = parser.readResponse(new BufferedReader(new InputStreamReader(client.getInputStream(), "UTF-8")));
+
+            assertEquals(String.valueOf(status), response.getCode());
+
+            // Make sure the connection was closed by the server.
+            assertEquals(-1, client.getInputStream().read());
+        }
+    }
+
+    @Test
+    public void testOnErrorThrows() throws Exception
+    {
+        final AtomicInteger errors = new AtomicInteger();
+        startServer(new HttpServlet()
+        {
+            @Override
+            protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+            {
+                final AsyncContext asyncContext = request.startAsync(request, response);
+                request.getInputStream().setReadListener(new ReadListener()
+                {
+                    @Override
+                    public void onDataAvailable() throws IOException
+                    {
+                        throw new NullPointerException("explicitly_thrown_by_test_1");
+                    }
+
+                    @Override
+                    public void onAllDataRead() throws IOException
+                    {
+                    }
+
+                    @Override
+                    public void onError(Throwable t)
+                    {
+                        errors.incrementAndGet();
+                        throw new NullPointerException("explicitly_thrown_by_test_2");
+                    }
+                });
+            }
+        });
+
+        String data = "0123456789";
+        String request = "GET " + path + " HTTP/1.1\r\n" +
+                "Host: localhost:" + connector.getLocalPort() + "\r\n" +
+                "Content-Length: " + data.length() + "\r\n" +
+                "\r\n" +
+                data;
+
+        try (Socket client = new Socket("localhost", connector.getLocalPort()))
+        {
+            OutputStream output = client.getOutputStream();
+            output.write(request.getBytes("UTF-8"));
+            output.flush();
+
+            SimpleHttpParser parser = new SimpleHttpParser();
+            SimpleHttpResponse response = parser.readResponse(new BufferedReader(new InputStreamReader(client.getInputStream(), "UTF-8")));
+
+            Assert.assertEquals("500", response.getCode());
+            Assert.assertEquals(1, errors.get());
+        }
+    }
+
+    @Test
+    public void testAsyncWriteThrowsException() throws Exception
+    {
+        testAsyncWriteThrows(new NullPointerException("explicitly_thrown_by_test"));
+    }
+
+    @Test
+    public void testAsyncWriteThrowsError() throws Exception
+    {
+        testAsyncWriteThrows(new Error("explicitly_thrown_by_test"));
+    }
+
+    private void testAsyncWriteThrows(final Throwable throwable) throws Exception
+    {
+        final CountDownLatch latch = new CountDownLatch(1);
+        startServer(new HttpServlet()
+        {
+            @Override
+            protected void service(HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException
+            {
+                final AsyncContext asyncContext = request.startAsync(request, response);
+                response.getOutputStream().setWriteListener(new WriteListener()
+                {
+                    @Override
+                    public void onWritePossible() throws IOException
+                    {
+                        if (throwable instanceof RuntimeException)
+                            throw (RuntimeException)throwable;
+                        if (throwable instanceof Error)
+                            throw (Error)throwable;
+                        throw new IOException(throwable);
+                    }
+
+                    @Override
+                    public void onError(Throwable t)
+                    {
+                        latch.countDown();
+                        response.setStatus(500);
+                        asyncContext.complete();
+                        Assert.assertSame(throwable, t);
+                    }
+                });
+            }
+        });
+
+        String request = "GET " + path + " HTTP/1.1\r\n" +
+                "Host: localhost:" + connector.getLocalPort() + "\r\n" +
+                "\r\n";
+
+        try (Socket client = new Socket("localhost", connector.getLocalPort()))
+        {
+            OutputStream output = client.getOutputStream();
+            output.write(request.getBytes("UTF-8"));
+            output.flush();
+
+            SimpleHttpParser parser = new SimpleHttpParser();
+            SimpleHttpResponse response = parser.readResponse(new BufferedReader(new InputStreamReader(client.getInputStream(), "UTF-8")));
+
+            Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+            Assert.assertEquals("500", response.getCode());
+        }
+    }
+    
+
+    @Test
+    public void testAsyncWriteClosed() throws Exception
+    {
+        final CountDownLatch latch = new CountDownLatch(1);
+        String text = "Now is the winter of our discontent. How Now Brown Cow. The quick brown fox jumped over the lazy dog.\n";
+        for (int i=0;i<10;i++)
+            text=text+text;
+        final byte[] data = text.getBytes(StandardCharsets.ISO_8859_1);
+        
+        startServer(new HttpServlet()
+        {
+            @Override
+            protected void service(HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException
+            {
+                response.flushBuffer();
+                
+                final AsyncContext async = request.startAsync();
+                final ServletOutputStream out = response.getOutputStream();
+                out.setWriteListener(new WriteListener()
+                {
+                    @Override
+                    public void onWritePossible() throws IOException
+                    {
+                        while (out.isReady())
+                        {
+                            try
+                            {
+                                Thread.sleep(100);
+                                out.write(data);
+                            }
+                            catch(IOException e)
+                            {
+                                throw e;
+                            }
+                            catch(Exception e)
+                            {
+                                e.printStackTrace();
+                            }
+                        }
+                    }
+
+                    @Override
+                    public void onError(Throwable t)
+                    {
+                        async.complete();
+                        latch.countDown();
+                    }
+                });
+            }
+        });
+
+        String request = "GET " + path + " HTTP/1.1\r\n" +
+                "Host: localhost:" + connector.getLocalPort() + "\r\n" +
+                "\r\n";
+
+        try (Socket client = new Socket("localhost", connector.getLocalPort()))
+        {
+            OutputStream output = client.getOutputStream();
+            output.write(request.getBytes("UTF-8"));
+            output.flush();
+
+            BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream()));
+            String line=in.readLine();
+            assertThat(line, containsString("200 OK"));
+            while (line.length()>0)
+                line=in.readLine();
+            line=in.readLine();
+            assertThat(line, not(containsString(" ")));
+            line=in.readLine();
+            assertThat(line, containsString("discontent. How Now Brown Cow. The "));
+        }
+        
+        if (!latch.await(5, TimeUnit.SECONDS))
+            Assert.fail();
+    }
+    
+
+    @Test
+    public void testIsNotReadyAtEOF() throws Exception
+    {
+        String text = "Test\n";
+        final byte[] data = text.getBytes(StandardCharsets.ISO_8859_1);
+        
+        startServer(new HttpServlet()
+        {
+            @Override
+            protected void service(HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException
+            {
+                response.flushBuffer();
+                
+                final AsyncContext async = request.startAsync();
+                final ServletInputStream in = request.getInputStream();
+                final ServletOutputStream out = response.getOutputStream();
+                
+                in.setReadListener(new ReadListener()
+                {
+                    transient int _i=0;
+                    transient boolean _minusOne=false;;
+                    transient boolean _finished=false;;
+                    
+                    @Override
+                    public void onError(Throwable t)
+                    {
+                        t.printStackTrace();
+                        async.complete();
+                    }
+                    
+                    @Override
+                    public void onDataAvailable() throws IOException
+                    {
+                        while(in.isReady() && !in.isFinished())
+                        {
+                            int b = in.read();
+                            if (b==-1)
+                                _minusOne=true;
+                            else if (data[_i++]!=b)
+                                throw new IllegalStateException();
+                        }
+                        
+                        if (in.isFinished())
+                            _finished=true;
+                    }
+                    
+                    @Override
+                    public void onAllDataRead() throws IOException
+                    {
+                        out.write(String.format("i=%d eof=%b finished=%b",_i,_minusOne,_finished).getBytes(StandardCharsets.ISO_8859_1));
+                        async.complete();                        
+                    }
+                });
+            }
+        });
+
+        String request = "GET " + path + " HTTP/1.1\r\n" +
+                "Host: localhost:" + connector.getLocalPort() + "\r\n" +
+                "Content-Type: text/plain\r\n"+
+                "Content-Length: "+data.length+"\r\n" +
+                "Connection: close\r\n" +
+                "\r\n";
+
+        try (Socket client = new Socket("localhost", connector.getLocalPort()))
+        {
+            OutputStream output = client.getOutputStream();
+            output.write(request.getBytes("UTF-8"));
+            output.write(data);
+            output.flush();
+
+            BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream()));
+            String line=in.readLine();
+            assertThat(line, containsString("200 OK"));
+            while (line.length()>0)
+                line=in.readLine();
+            line=in.readLine();
+            assertThat(line, containsString("i="+data.length+" eof=false finished=true"));
+        }
+    }
+    
+
+    @Test
+    public void testEmptyAsyncRead() throws Exception
+    {
+        final AtomicBoolean oda = new AtomicBoolean();
+        final CountDownLatch latch = new CountDownLatch(1);
+        
+        startServer(new HttpServlet()
+        {
+            @Override
+            protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+            {
+                final AsyncContext asyncContext = request.startAsync(request, response);
+                response.setStatus(200);
+                response.getOutputStream().close();
+                request.getInputStream().setReadListener(new ReadListener()
+                {
+                    @Override
+                    public void onDataAvailable() throws IOException 
+                    {
+                        oda.set(true);
+                    }
+
+                    @Override
+                    public void onAllDataRead() throws IOException 
+                    {
+                        asyncContext.complete();
+                        latch.countDown();
+                    }
+
+                    @Override
+                    public void onError(Throwable t) 
+                    {
+                        t.printStackTrace();
+                        asyncContext.complete();
+                    }        
+                });
+            }
+        });
+
+        String request = "GET " + path + " HTTP/1.1\r\n" +
+                "Host: localhost:" + connector.getLocalPort() + "\r\n" +
+                "Connection: close\r\n" +
+                "\r\n";
+
+        try (Socket client = new Socket("localhost", connector.getLocalPort()))
+        {
+            OutputStream output = client.getOutputStream();
+            output.write(request.getBytes("UTF-8"));
+            output.flush();
+
+            String response = IO.toString(client.getInputStream());
+            assertThat(response,containsString(" 200 OK"));
+            // wait for onAllDataRead BEFORE closing client
+            latch.await();
+        }
+        
+        // ODA not called at all!
+        Assert.assertFalse(oda.get());
+    }
+
+}
diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncServletIOTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncServletIOTest.java
new file mode 100644
index 0000000..cb17eac
--- /dev/null
+++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncServletIOTest.java
@@ -0,0 +1,596 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlet;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.Socket;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.AsyncEvent;
+import javax.servlet.AsyncListener;
+import javax.servlet.ReadListener;
+import javax.servlet.ServletException;
+import javax.servlet.ServletInputStream;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.ServletResponse;
+import javax.servlet.WriteListener;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.HttpConnectionFactory;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.assertEquals;
+
+// TODO need  these on SPDY as well!
+public class AsyncServletIOTest 
+{    
+    private static final Logger LOG = Log.getLogger(AsyncServletIOTest.class);
+    protected AsyncIOServlet _servlet0=new AsyncIOServlet();
+    protected AsyncIOServlet2 _servlet2=new AsyncIOServlet2();
+    protected AsyncIOServlet3 _servlet3=new AsyncIOServlet3();
+    protected int _port;
+    protected Server _server = new Server();
+    protected ServletHandler _servletHandler;
+    protected ServerConnector _connector;
+
+    @Before
+    public void setUp() throws Exception
+    {
+        HttpConfiguration http_config = new HttpConfiguration();
+        http_config.setOutputBufferSize(4096);
+        _connector = new ServerConnector(_server,new HttpConnectionFactory(http_config));
+        
+        _server.setConnectors(new Connector[]{ _connector });
+        ServletContextHandler context = new ServletContextHandler();
+        context.setContextPath("/ctx");
+        _server.setHandler(context);
+        _servletHandler=context.getServletHandler();
+        
+        
+        ServletHolder holder=new ServletHolder(_servlet0);
+        holder.setAsyncSupported(true);
+        _servletHandler.addServletWithMapping(holder,"/path/*");
+        
+        ServletHolder holder2=new ServletHolder(_servlet2);
+        holder2.setAsyncSupported(true);
+        _servletHandler.addServletWithMapping(holder2,"/path2/*");
+        
+        ServletHolder holder3=new ServletHolder(_servlet3);
+        holder3.setAsyncSupported(true);
+        _servletHandler.addServletWithMapping(holder3,"/path3/*");
+        
+        _server.start();
+        _port=_connector.getLocalPort();
+
+        _owp.set(0);
+        _oda.set(0);
+        _read.set(0);
+    }
+
+    @After
+    public void tearDown() throws Exception
+    {
+        _server.stop();
+    }
+    
+    @Test
+    public void testEmpty() throws Exception
+    {
+        process();
+    }
+    
+    @Test
+    public void testWrite() throws Exception
+    {
+        process(10);
+    }
+    
+    @Test
+    public void testWrites() throws Exception
+    {
+        process(10,1,20,10);
+    }
+    
+    @Test
+    public void testWritesFlushWrites() throws Exception
+    {
+        process(10,1,0,20,10);
+    }
+
+    @Test
+    public void testBigWrite() throws Exception
+    {
+        process(102400);
+    }
+    
+    @Test
+    public void testBigWrites() throws Exception
+    {
+        process(102400,102400,102400,102400,102400,102400,102400,102400,102400,102400,102400,102400,102400,102400,102400,102400,102400,102400,102400,102400,102400,102400,102400,102400,102400,102400,102400,102400,102400,102400,102400,102400,102400,102400,102400,102400,102400,102400,102400);
+        Assert.assertThat("On Write Possible",_owp.get(),greaterThanOrEqualTo(1));
+    }
+
+    @Test
+    public void testRead() throws Exception
+    {
+        process("Hello!!!\r\n");
+    }
+
+    @Test
+    public void testBigRead() throws Exception
+    {
+        process("Now is the time for all good men to come to the aid of the party. How now Brown Cow. The quick brown fox jumped over the lazy dog. The moon is blue to a fish in love.\r\n");
+    }
+
+    @Test
+    public void testReadWrite() throws Exception
+    {
+        process("Hello!!!\r\n",10);
+    }
+
+    @Test
+    public void testAsync2() throws Exception
+    {
+        StringBuilder request = new StringBuilder(512);
+        request.append("GET /ctx/path2/info HTTP/1.1\r\n")
+        .append("Host: localhost\r\n")
+        .append("Connection: close\r\n")
+        .append("\r\n");
+        
+        int port=_port;
+        List<String> list = new ArrayList<>();
+        try (Socket socket = new Socket("localhost",port))
+        {
+            socket.setSoTimeout(1000000);
+            OutputStream out = socket.getOutputStream();
+            out.write(request.toString().getBytes(StandardCharsets.ISO_8859_1));
+            
+            BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()),102400);
+            
+            // response line
+            String line = in.readLine();
+            LOG.debug("response-line: "+line);
+            Assert.assertThat(line,startsWith("HTTP/1.1 200 OK"));
+            
+            // Skip headers
+            while (line!=null)
+            {
+                line = in.readLine();
+                LOG.debug("header-line: "+line);
+                if (line.length()==0)
+                    break;
+            }
+
+            // Get body slowly
+            while (true)
+            {
+                line = in.readLine();
+                LOG.debug("body: "+line);
+                if (line==null)
+                    break;
+                list.add(line);
+            }
+        }
+
+        Assert.assertEquals(list.get(0),"data");
+        Assert.assertTrue(_servlet2.completed.await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testAsyncConsumeAll() throws Exception
+    {
+        StringBuilder request = new StringBuilder(512);
+        request.append("GET /ctx/path3/info HTTP/1.1\r\n")
+        .append("Host: localhost\r\n")
+        .append("Content-Type: text/plain\r\n")
+        .append("Content-Length: 10\r\n")
+        .append("\r\n");
+        
+        int port=_port;
+        try (Socket socket = new Socket("localhost",port))
+        {
+            socket.setSoTimeout(1000000);
+            OutputStream out = socket.getOutputStream();
+            out.write(request.toString().getBytes(StandardCharsets.ISO_8859_1));
+            
+            BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()),102400);
+            
+            // response line
+            String line = in.readLine();
+            LOG.debug("response-line: "+line);
+            Assert.assertThat(line,startsWith("HTTP/1.1 200 OK"));
+            
+            // Skip headers
+            while (line!=null)
+            {
+                line = in.readLine();
+                LOG.debug("header-line: "+line);
+                if (line.length()==0)
+                    break;
+            }
+
+            // Get body
+            line = in.readLine();
+            LOG.debug("body: "+line);
+            Assert.assertEquals("DONE",line);
+
+            // The connection should be aborted
+            line = in.readLine();
+            Assert.assertNull(line);
+        }
+    }
+    
+    public synchronized List<String> process(String content,int... writes) throws Exception
+    {
+        return process(content.getBytes(StandardCharsets.ISO_8859_1),writes);
+    }
+
+    public synchronized List<String> process(int... writes) throws Exception
+    {
+        return process((byte[])null,writes);
+    }
+    
+    public synchronized List<String> process(byte[] content, int... writes) throws Exception
+    {
+        StringBuilder request = new StringBuilder(512);
+        request.append("GET /ctx/path/info");
+        char s='?';
+        for (int w: writes)
+        {
+            request.append(s).append("w=").append(w);
+            s='&';
+        }
+        
+        request.append(" HTTP/1.1\r\n")
+        .append("Host: localhost\r\n")
+        .append("Connection: close\r\n");
+        
+        if (content!=null)
+            request.append("Content-Length: ").append(content.length).append("\r\n")
+            .append("Content-Type: text/plain\r\n");
+        
+        request.append("\r\n");
+        
+        int port=_port;
+        List<String> list = new ArrayList<>();
+        try (Socket socket = new Socket("localhost",port))
+        {
+            socket.setSoTimeout(1000000);
+            OutputStream out = socket.getOutputStream();
+            out.write(request.toString().getBytes(StandardCharsets.ISO_8859_1));
+
+            if (content!=null && content.length>0)
+            {
+                Thread.sleep(100);
+                out.write(content[0]);
+                Thread.sleep(100);
+                int half=(content.length-1)/2;
+                out.write(content,1,half);
+                Thread.sleep(100);
+                out.write(content,1+half,content.length-half-1);
+            }
+            
+            BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()),102400);
+            
+            // response line
+            String line = in.readLine();
+            LOG.debug("response-line: "+line);
+            Assert.assertThat(line,startsWith("HTTP/1.1 200 OK"));
+            
+            // Skip headers
+            while (line!=null)
+            {
+                line = in.readLine();
+                LOG.debug("header-line:  "+line);
+                if (line.length()==0)
+                    break;
+            }
+
+            // Get body slowly
+            while (true)
+            {
+                line = in.readLine();
+                if (line==null)
+                    break;
+                LOG.debug("body:  "+brief(line));
+                list.add(line);
+                Thread.sleep(50);
+            }
+        }
+        
+        // check lines
+        int w=0;
+        for (String line : list)
+        {
+            LOG.debug("line:  "+brief(line));
+            if ("-".equals(line))
+                continue;
+            assertEquals("Line Length",writes[w],line.length());
+            assertEquals("Line Contents",line.charAt(0),'0'+(w%10));
+            
+            w++;
+            if (w<writes.length && writes[w]<=0)
+                w++;
+        }
+        
+        if (content!=null)
+            Assert.assertEquals("Content Length",content.length,_read.get());
+        
+        return list;
+    }
+    
+    private static String brief(String line)
+    {
+        return line.length()+"\t"+(line.length()>40?(line.substring(0,40)+"..."):line);
+    }
+
+    static AtomicInteger _owp = new AtomicInteger();
+    static AtomicInteger _oda = new AtomicInteger();
+    static AtomicInteger _read = new AtomicInteger();
+    
+    private static class AsyncIOServlet extends HttpServlet
+    {
+        private static final long serialVersionUID = -8161977157098646562L;
+        
+        public AsyncIOServlet()
+        {
+        }
+        
+        @Override
+        public void doGet(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException
+        {
+            final AsyncContext async = request.startAsync();
+            final AtomicInteger complete = new AtomicInteger(2);
+            final AtomicBoolean onDataAvailable = new AtomicBoolean(false);
+            
+            // Asynchronous Read
+            if (request.getContentLength()>0)
+            {
+                // System.err.println("reading "+request.getContentLength());
+                final ServletInputStream in=request.getInputStream();
+                in.setReadListener(new ReadListener()
+                {
+                    byte[] _buf=new byte[32];
+                    @Override
+                    public void onError(Throwable t)
+                    {      
+                        if (complete.decrementAndGet()==0)
+                            async.complete();                  
+                    }
+                    
+                    @Override
+                    public void onDataAvailable() throws IOException
+                    {                
+                        if (!onDataAvailable.compareAndSet(false,true))
+                            throw new IllegalStateException();
+                        
+                        // System.err.println("ODA");
+                        while (in.isReady() && !in.isFinished())
+                        {
+                            _oda.incrementAndGet();
+                            int len=in.read(_buf);
+                            // System.err.println("read "+len);
+                            if (len>0)
+                                _read.addAndGet(len);
+                        }
+
+                        if (!onDataAvailable.compareAndSet(true,false))
+                            throw new IllegalStateException();
+                        
+                    }
+                    
+                    @Override
+                    public void onAllDataRead() throws IOException
+                    {    
+                        if (onDataAvailable.get())
+                        {
+                            LOG.warn("OADR too early!");
+                            _read.set(-1);
+                        }
+                        
+                        // System.err.println("OADR");
+                        if (complete.decrementAndGet()==0)
+                            async.complete();
+                    }
+                });
+            }
+            else
+                complete.decrementAndGet();
+            
+            
+            // Asynchronous Write
+            final String[] writes = request.getParameterValues("w");
+            final ServletOutputStream out = response.getOutputStream();
+            out.setWriteListener(new WriteListener()
+            {
+                int _w=0;
+                
+                @Override
+                public void onWritePossible() throws IOException
+                {
+                    LOG.debug("OWP");
+                    _owp.incrementAndGet();
+
+                    while (writes!=null && _w< writes.length)
+                    {
+                        int write=Integer.valueOf(writes[_w++]);
+                        
+                        if (write==0)
+                            out.flush();
+                        else
+                        {
+                            byte[] buf=new byte[write+1];
+                            Arrays.fill(buf,(byte)('0'+((_w-1)%10)));
+                            buf[write]='\n';
+                            out.write(buf);
+                        }
+                        
+                        if (!out.isReady())
+                            return;
+                    }
+                    
+                    if (complete.decrementAndGet()==0)
+                        async.complete();
+                }
+
+                @Override
+                public void onError(Throwable t)
+                {
+                    async.complete();
+                } 
+            });
+        }
+    }
+
+    @SuppressWarnings("serial")
+    public class AsyncIOServlet2 extends HttpServlet
+    {
+        public CountDownLatch completed = new CountDownLatch(1);
+        
+        @Override
+        public void doGet(final HttpServletRequest request, final HttpServletResponse response) throws IOException
+        {
+            new SampleAsycListener(request,response);
+        }
+
+        class SampleAsycListener implements WriteListener, AsyncListener
+        {
+            final ServletResponse response;
+            final ServletOutputStream servletOutputStream;
+            final AsyncContext asyncContext;
+            volatile boolean written=false;
+
+            SampleAsycListener(HttpServletRequest request,HttpServletResponse response) throws IOException
+            {
+                asyncContext = request.startAsync();
+                asyncContext.setTimeout(10000L);
+                asyncContext.addListener(this);
+                servletOutputStream = response.getOutputStream();
+                servletOutputStream.setWriteListener(this);
+                this.response=response;
+            }
+
+            @Override
+            public void onWritePossible() throws IOException
+            {
+                if (!written)
+                {
+                    written=true;
+                    response.setContentLength(5);
+                    servletOutputStream.write("data\n".getBytes());
+                }
+               
+                if (servletOutputStream.isReady())
+                {
+                    asyncContext.complete();
+                }
+            }
+
+            @Override
+            public void onError(final Throwable t)
+            {
+                t.printStackTrace();
+                asyncContext.complete();
+            }
+
+            @Override
+            public void onComplete(final AsyncEvent event) throws IOException
+            {
+                completed.countDown();
+            }
+
+            @Override
+            public void onTimeout(final AsyncEvent event) throws IOException
+            {
+                asyncContext.complete();
+            }
+
+            @Override
+            public void onError(final AsyncEvent event) throws IOException
+            {
+                asyncContext.complete();
+            }
+
+            @Override
+            public void onStartAsync(AsyncEvent event) throws IOException
+            {
+
+            }
+        }
+    }
+    
+
+    @SuppressWarnings("serial")
+    public class AsyncIOServlet3 extends HttpServlet
+    {
+        public CountDownLatch completed = new CountDownLatch(1);
+        
+        @Override
+        public void doGet(final HttpServletRequest request, final HttpServletResponse response) throws IOException
+        {
+            AsyncContext async = request.startAsync();
+            
+            request.getInputStream().setReadListener(new ReadListener()
+            {
+                
+                @Override
+                public void onError(Throwable t)
+                {                    
+                }
+                
+                @Override
+                public void onDataAvailable() throws IOException
+                {                    
+                }
+                
+                @Override
+                public void onAllDataRead() throws IOException
+                {                    
+                }
+            });
+            
+            response.setStatus(200);
+            response.getOutputStream().print("DONE");
+            async.complete();
+        }
+    }
+}
diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncServletLongPollTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncServletLongPollTest.java
new file mode 100644
index 0000000..d19b5b9
--- /dev/null
+++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncServletLongPollTest.java
@@ -0,0 +1,166 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlet;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.Socket;
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.toolchain.test.TestTracker;
+import org.eclipse.jetty.toolchain.test.http.SimpleHttpParser;
+import org.eclipse.jetty.toolchain.test.http.SimpleHttpResponse;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class AsyncServletLongPollTest
+{
+    @Rule
+    public TestTracker tracker = new TestTracker();
+    private Server server;
+    private ServerConnector connector;
+    private ServletContextHandler context;
+    private String uri;
+
+    protected void prepare(HttpServlet servlet) throws Exception
+    {
+        server = new Server();
+        connector = new ServerConnector(server);
+        server.addConnector(connector);
+        String contextPath = "/context";
+        context = new ServletContextHandler(server, contextPath, ServletContextHandler.NO_SESSIONS);
+        ServletHolder servletHolder = new ServletHolder(servlet);
+        String servletPath = "/path";
+        context.addServlet(servletHolder, servletPath);
+        uri = contextPath + servletPath;
+        server.start();
+    }
+
+    @After
+    public void destroy() throws Exception
+    {
+        server.stop();
+    }
+
+    @Test
+    public void testSuspendedRequestCompletedByAnotherRequest() throws Exception
+    {
+        final CountDownLatch asyncLatch = new CountDownLatch(1);
+        prepare(new HttpServlet()
+        {
+            private volatile AsyncContext asyncContext;
+
+            @Override
+            protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+            {
+                int suspend = 0;
+                String param = request.getParameter("suspend");
+                if (param != null)
+                    suspend = Integer.parseInt(param);
+
+                if (suspend > 0)
+                {
+                    asyncContext = request.startAsync();
+                    asyncLatch.countDown();
+                }
+            }
+
+            @Override
+            protected void doDelete(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+            {
+                int error = 0;
+                String param = request.getParameter("error");
+                if (param != null)
+                    error = Integer.parseInt(param);
+
+                final AsyncContext asyncContext = this.asyncContext;
+                if (asyncContext != null)
+                {
+                    HttpServletResponse asyncResponse = (HttpServletResponse)asyncContext.getResponse();
+                    asyncResponse.sendError(error);
+                    asyncContext.complete();
+                }
+                else
+                {
+                    response.sendError(404);
+                }
+            }
+        });
+
+        try (Socket socket1 = new Socket("localhost", connector.getLocalPort()))
+        {
+            int wait = 1000;
+            String request1 = "GET " + uri + "?suspend=" + wait + " HTTP/1.1\r\n" +
+                    "Host: localhost:" + connector.getLocalPort() + "\r\n" +
+                    "\r\n";
+            OutputStream output1 = socket1.getOutputStream();
+            output1.write(request1.getBytes(StandardCharsets.UTF_8));
+            output1.flush();
+
+            Assert.assertTrue(asyncLatch.await(5, TimeUnit.SECONDS));
+
+            String error = "408";
+            try (Socket socket2 = new Socket("localhost", connector.getLocalPort()))
+            {
+                String request2 = "DELETE " + uri + "?error=" + error + " HTTP/1.1\r\n" +
+                        "Host: localhost:" + connector.getLocalPort() + "\r\n" +
+                        "\r\n";
+                OutputStream output2 = socket2.getOutputStream();
+                output2.write(request2.getBytes(StandardCharsets.UTF_8));
+                output2.flush();
+
+                SimpleHttpParser parser2 = new SimpleHttpParser();
+                BufferedReader input2 = new BufferedReader(new InputStreamReader(socket2.getInputStream(), StandardCharsets.UTF_8));
+                SimpleHttpResponse response2 = parser2.readResponse(input2);
+                Assert.assertEquals("200", response2.getCode());
+            }
+
+            socket1.setSoTimeout(2 * wait);
+            SimpleHttpParser parser1 = new SimpleHttpParser();
+            BufferedReader input1 = new BufferedReader(new InputStreamReader(socket1.getInputStream(), StandardCharsets.UTF_8));
+            SimpleHttpResponse response1 = parser1.readResponse(input1);
+            Assert.assertEquals(error, response1.getCode());
+
+            // Now try to make another request on the first connection
+            // to verify that we set correctly the read interest (#409842)
+            String request3 = "GET " + uri + " HTTP/1.1\r\n" +
+                    "Host: localhost:" + connector.getLocalPort() + "\r\n" +
+                    "\r\n";
+            output1.write(request3.getBytes(StandardCharsets.UTF_8));
+            output1.flush();
+
+            SimpleHttpResponse response3 = parser1.readResponse(input1);
+            Assert.assertEquals("200", response3.getCode());
+        }
+    }
+}
diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncServletTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncServletTest.java
index ecc264c..2393730 100644
--- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncServletTest.java
+++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncServletTest.java
@@ -19,11 +19,13 @@
 package org.eclipse.jetty.servlet;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
 
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.Socket;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Timer;
 import java.util.TimerTask;
 
@@ -41,40 +43,62 @@ import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpServletResponseWrapper;
 
 import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.RequestLog;
+import org.eclipse.jetty.server.Response;
 import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.handler.RequestLogHandler;
+import org.eclipse.jetty.toolchain.test.AdvancedRunner;
 import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
 import org.hamcrest.Matchers;
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 
 
-public class AsyncServletTest 
-{    
-
+ at RunWith(AdvancedRunner.class)
+public class AsyncServletTest
+{
     protected AsyncServlet _servlet=new AsyncServlet();
     protected int _port;
 
     protected Server _server = new Server();
     protected ServletHandler _servletHandler;
-    protected SelectChannelConnector _connector;
+    protected ServerConnector _connector;
+    protected List<String> _log;
+    protected int _expectedLogs;
+    protected String _expectedCode;
 
     @Before
     public void setUp() throws Exception
     {
-        _connector = new SelectChannelConnector();
+        _connector = new ServerConnector(_server);
         _server.setConnectors(new Connector[]{ _connector });
-        ServletContextHandler context = new ServletContextHandler(ServletContextHandler.NO_SECURITY|ServletContextHandler.NO_SESSIONS);
+        
+        _log=new ArrayList<>();
+        RequestLog log=new Log();
+        RequestLogHandler logHandler = new RequestLogHandler();
+        logHandler.setRequestLog(log);
+        _server.setHandler(logHandler);
+        _expectedLogs=1;
+        _expectedCode="200 ";
+
+        ServletContextHandler context = new ServletContextHandler(ServletContextHandler.NO_SESSIONS);
         context.setContextPath("/ctx");
-        _server.setHandler(context);
+        logHandler.setHandler(context);
+        
         _servletHandler=context.getServletHandler();
         ServletHolder holder=new ServletHolder(_servlet);
         holder.setAsyncSupported(true);
         _servletHandler.addServletWithMapping(holder,"/path/*");
         _servletHandler.addServletWithMapping(holder,"/path1/*");
         _servletHandler.addServletWithMapping(holder,"/path2/*");
+        _servletHandler.addServletWithMapping(holder,"/p th3/*");
         _servletHandler.addServletWithMapping(new ServletHolder(new FwdServlet()),"/fwd/*");
         _server.start();
         _port=_connector.getLocalPort();
@@ -83,16 +107,18 @@ public class AsyncServletTest
     @After
     public void tearDown() throws Exception
     {
+        assertEquals(_expectedLogs,_log.size());
+        Assert.assertThat(_log.get(0), Matchers.containsString(_expectedCode));
         _server.stop();
     }
-    
+
     @Test
     public void testNormal() throws Exception
     {
         String response=process(null,null);
         assertEquals("HTTP/1.1 200 OK",response.substring(0,15));
         assertContains(
-                "history: REQUEST /path\r\n"+
+                "history: REQUEST /ctx/path/info\r\n"+
                 "history: initial\r\n",response);
         assertContains("NORMAL",response);
         assertNotContains("history: onTimeout",response);
@@ -105,7 +131,7 @@ public class AsyncServletTest
         String response=process("sleep=200",null);
         assertEquals("HTTP/1.1 200 OK",response.substring(0,15));
         assertContains(
-                "history: REQUEST /path\r\n"+
+                "history: REQUEST /ctx/path/info\r\n"+
                 "history: initial\r\n",response);
         assertContains("SLEPT",response);
         assertNotContains("history: onTimeout",response);
@@ -115,51 +141,52 @@ public class AsyncServletTest
     @Test
     public void testSuspend() throws Exception
     {
+        _expectedCode="500 ";
         String response=process("suspend=200",null);
         assertEquals("HTTP/1.1 500 Async Timeout",response.substring(0,26));
         assertContains(
-            "history: REQUEST /path\r\n"+
+            "history: REQUEST /ctx/path/info\r\n"+
             "history: initial\r\n"+
             "history: suspend\r\n"+
             "history: onTimeout\r\n"+
-            "history: ERROR /path\r\n"+
+            "history: ERROR /ctx/path/info\r\n"+
             "history: !initial\r\n"+
             "history: onComplete\r\n",response);
-                    
+
         assertContains("ERROR: /ctx/path/info",response);
     }
-    
+
     @Test
     public void testSuspendOnTimeoutDispatch() throws Exception
     {
         String response=process("suspend=200&timeout=dispatch",null);
         assertEquals("HTTP/1.1 200 OK",response.substring(0,15));
         assertContains(
-            "history: REQUEST /path\r\n"+
+            "history: REQUEST /ctx/path/info\r\n"+
             "history: initial\r\n"+
             "history: suspend\r\n"+
             "history: onTimeout\r\n"+
             "history: dispatch\r\n"+
-            "history: ASYNC /path\r\n"+
+            "history: ASYNC /ctx/path/info\r\n"+
             "history: !initial\r\n"+
             "history: onComplete\r\n",response);
-                    
+
         assertContains("DISPATCHED",response);
     }
-    
+
     @Test
     public void testSuspendOnTimeoutComplete() throws Exception
     {
         String response=process("suspend=200&timeout=complete",null);
         assertEquals("HTTP/1.1 200 OK",response.substring(0,15));
         assertContains(
-            "history: REQUEST /path\r\n"+
+            "history: REQUEST /ctx/path/info\r\n"+
             "history: initial\r\n"+
             "history: suspend\r\n"+
             "history: onTimeout\r\n"+
             "history: complete\r\n"+
             "history: onComplete\r\n",response);
-                    
+
         assertContains("COMPLETED",response);
     }
 
@@ -169,11 +196,11 @@ public class AsyncServletTest
         String response=process("suspend=200&resume=10",null);
         assertEquals("HTTP/1.1 200 OK",response.substring(0,15));
         assertContains(
-            "history: REQUEST /path\r\n"+
+            "history: REQUEST /ctx/path/info\r\n"+
             "history: initial\r\n"+
             "history: suspend\r\n"+
             "history: resume\r\n"+
-            "history: ASYNC /path\r\n"+
+            "history: ASYNC /ctx/path/info\r\n"+
             "history: !initial\r\n"+
             "history: onComplete\r\n",response);
         assertNotContains("history: onTimeout",response);
@@ -185,11 +212,11 @@ public class AsyncServletTest
         String response=process("suspend=200&resume=0",null);
         assertEquals("HTTP/1.1 200 OK",response.substring(0,15));
         assertContains(
-            "history: REQUEST /path\r\n"+
+            "history: REQUEST /ctx/path/info\r\n"+
             "history: initial\r\n"+
             "history: suspend\r\n"+
             "history: resume\r\n"+
-            "history: ASYNC /path\r\n"+
+            "history: ASYNC /ctx/path/info\r\n"+
             "history: !initial\r\n"+
             "history: onComplete\r\n",response);
         assertContains("history: onComplete",response);
@@ -201,7 +228,7 @@ public class AsyncServletTest
         String response=process("suspend=200&complete=50",null);
         assertEquals("HTTP/1.1 200 OK",response.substring(0,15));
         assertContains(
-            "history: REQUEST /path\r\n"+
+            "history: REQUEST /ctx/path/info\r\n"+
             "history: initial\r\n"+
             "history: suspend\r\n"+
             "history: complete\r\n"+
@@ -217,7 +244,7 @@ public class AsyncServletTest
         String response=process("suspend=200&complete=0",null);
         assertEquals("HTTP/1.1 200 OK",response.substring(0,15));
         assertContains(
-            "history: REQUEST /path\r\n"+
+            "history: REQUEST /ctx/path/info\r\n"+
             "history: initial\r\n"+
             "history: suspend\r\n"+
             "history: complete\r\n"+
@@ -233,15 +260,15 @@ public class AsyncServletTest
         String response=process("suspend=1000&resume=10&suspend2=1000&resume2=10",null);
         assertEquals("HTTP/1.1 200 OK",response.substring(0,15));
         assertContains(
-            "history: REQUEST /path\r\n"+
+            "history: REQUEST /ctx/path/info\r\n"+
             "history: initial\r\n"+
             "history: suspend\r\n"+
             "history: resume\r\n"+
-            "history: ASYNC /path\r\n"+
+            "history: ASYNC /ctx/path/info\r\n"+
             "history: !initial\r\n"+
             "history: suspend\r\n"+
             "history: resume\r\n"+
-            "history: ASYNC /path\r\n"+
+            "history: ASYNC /ctx/path/info\r\n"+
             "history: !initial\r\n"+
             "history: onComplete\r\n",response);
         assertContains("DISPATCHED",response);
@@ -253,11 +280,11 @@ public class AsyncServletTest
         String response=process("suspend=1000&resume=10&suspend2=1000&complete2=10",null);
         assertEquals("HTTP/1.1 200 OK",response.substring(0,15));
         assertContains(
-            "history: REQUEST /path\r\n"+
+            "history: REQUEST /ctx/path/info\r\n"+
             "history: initial\r\n"+
             "history: suspend\r\n"+
             "history: resume\r\n"+
-            "history: ASYNC /path\r\n"+
+            "history: ASYNC /ctx/path/info\r\n"+
             "history: !initial\r\n"+
             "history: suspend\r\n"+
             "history: complete\r\n"+
@@ -268,18 +295,19 @@ public class AsyncServletTest
     @Test
     public void testSuspendWaitResumeSuspend() throws Exception
     {
+        _expectedCode="500 ";
         String response=process("suspend=1000&resume=10&suspend2=10",null);
         assertEquals("HTTP/1.1 500 Async Timeout",response.substring(0,26));
         assertContains(
-            "history: REQUEST /path\r\n"+
+            "history: REQUEST /ctx/path/info\r\n"+
             "history: initial\r\n"+
             "history: suspend\r\n"+
             "history: resume\r\n"+
-            "history: ASYNC /path\r\n"+
+            "history: ASYNC /ctx/path/info\r\n"+
             "history: !initial\r\n"+
             "history: suspend\r\n"+
             "history: onTimeout\r\n"+
-            "history: ERROR /path\r\n"+
+            "history: ERROR /ctx/path/info\r\n"+
             "history: !initial\r\n"+
             "history: onComplete\r\n",response);
         assertContains("ERROR: /ctx/path/info",response);
@@ -291,15 +319,15 @@ public class AsyncServletTest
         String response=process("suspend=10&suspend2=1000&resume2=10",null);
         assertEquals("HTTP/1.1 200 OK",response.substring(0,15));
         assertContains(
-            "history: REQUEST /path\r\n"+
+            "history: REQUEST /ctx/path/info\r\n"+
             "history: initial\r\n"+
             "history: suspend\r\n"+
             "history: onTimeout\r\n"+
-            "history: ERROR /path\r\n"+
+            "history: ERROR /ctx/path/info\r\n"+
             "history: !initial\r\n"+
             "history: suspend\r\n"+
             "history: resume\r\n"+
-            "history: ASYNC /path\r\n"+
+            "history: ASYNC /ctx/path/info\r\n"+
             "history: !initial\r\n"+
             "history: onComplete\r\n",response);
         assertContains("DISPATCHED",response);
@@ -311,11 +339,11 @@ public class AsyncServletTest
         String response=process("suspend=10&suspend2=1000&complete2=10",null);
         assertEquals("HTTP/1.1 200 OK",response.substring(0,15));
         assertContains(
-            "history: REQUEST /path\r\n"+
+            "history: REQUEST /ctx/path/info\r\n"+
             "history: initial\r\n"+
             "history: suspend\r\n"+
             "history: onTimeout\r\n"+
-            "history: ERROR /path\r\n"+
+            "history: ERROR /ctx/path/info\r\n"+
             "history: !initial\r\n"+
             "history: suspend\r\n"+
             "history: complete\r\n"+
@@ -326,54 +354,73 @@ public class AsyncServletTest
     @Test
     public void testSuspendTimeoutSuspend() throws Exception
     {
+        _expectedCode="500 ";
         String response=process("suspend=10&suspend2=10",null);
         assertContains(
-            "history: REQUEST /path\r\n"+
+            "history: REQUEST /ctx/path/info\r\n"+
             "history: initial\r\n"+
             "history: suspend\r\n"+
             "history: onTimeout\r\n"+
-            "history: ERROR /path\r\n"+
+            "history: ERROR /ctx/path/info\r\n"+
             "history: !initial\r\n"+
             "history: suspend\r\n"+
             "history: onTimeout\r\n"+
-            "history: ERROR /path\r\n"+
+            "history: ERROR /ctx/path/info\r\n"+
             "history: !initial\r\n"+
             "history: onComplete\r\n",response);
         assertContains("ERROR: /ctx/path/info",response);
     }
 
-
     @Test
     public void testWrapStartDispatch() throws Exception
     {
         String response=process("wrap=true&suspend=200&resume=20",null);
         assertEquals("HTTP/1.1 200 OK",response.substring(0,15));
         assertContains(
-            "history: REQUEST /path\r\n"+
+            "history: REQUEST /ctx/path/info\r\n"+
             "history: initial\r\n"+
             "history: suspend\r\n"+
             "history: resume\r\n"+
-            "history: ASYNC /path\r\n"+
+            "history: ASYNC /ctx/path/info\r\n"+
             "history: wrapped REQ RSP\r\n"+
             "history: !initial\r\n"+
             "history: onComplete\r\n",response);
         assertContains("DISPATCHED",response);
     }
 
+
+    @Test
+    public void testStartDispatchEncodedPath() throws Exception
+    {
+        String response=process("suspend=200&resume=20&path=/p%20th3",null);
+        assertEquals("HTTP/1.1 200 OK",response.substring(0,15));
+        assertContains(
+            "history: REQUEST /ctx/path/info\r\n"+
+            "history: initial\r\n"+
+            "history: suspend\r\n"+
+            "history: resume\r\n"+
+            "history: ASYNC /ctx/p%20th3\r\n"+
+            "history: !initial\r\n"+
+            "history: onComplete\r\n",response);
+        assertContains("DISPATCHED",response);
+    }
+    
+    
     @Test
     public void testFwdStartDispatch() throws Exception
     {
         String response=process("fwd","suspend=200&resume=20",null);
         assertEquals("HTTP/1.1 200 OK",response.substring(0,15));
         assertContains(
-            "history: FWD REQUEST /fwd\r\n"+
-            "history: FORWARD /path1\r\n"+
+            "history: FWD REQUEST /ctx/fwd/info\r\n"+
+            "history: FORWARD /ctx/path1\r\n"+
             "history: initial\r\n"+
             "history: suspend\r\n"+
             "history: resume\r\n"+
-            "history: FWD ASYNC /fwd\r\n"+
-            "history: FORWARD /path1\r\n"+
-            "history: !initial\r\n",response);
+            "history: FWD ASYNC /ctx/fwd/info\r\n"+
+            "history: FORWARD /ctx/path1\r\n"+
+            "history: !initial\r\n"+
+            "history: onComplete\r\n",response);
         assertContains("DISPATCHED",response);
     }
     
@@ -383,12 +430,12 @@ public class AsyncServletTest
         String response=process("fwd","suspend=200&resume=20&path=/path2",null);
         assertEquals("HTTP/1.1 200 OK",response.substring(0,15));
         assertContains(
-            "history: FWD REQUEST /fwd\r\n"+
-            "history: FORWARD /path1\r\n"+
+            "history: FWD REQUEST /ctx/fwd/info\r\n"+
+            "history: FORWARD /ctx/path1\r\n"+
             "history: initial\r\n"+
             "history: suspend\r\n"+
             "history: resume\r\n"+
-            "history: ASYNC /path2\r\n"+
+            "history: ASYNC /ctx/path2\r\n"+
             "history: !initial\r\n"+
             "history: onComplete\r\n",response);
         assertContains("DISPATCHED",response);
@@ -400,12 +447,12 @@ public class AsyncServletTest
         String response=process("fwd","wrap=true&suspend=200&resume=20",null);
         assertEquals("HTTP/1.1 200 OK",response.substring(0,15));
         assertContains(
-            "history: FWD REQUEST /fwd\r\n"+
-            "history: FORWARD /path1\r\n"+
+            "history: FWD REQUEST /ctx/fwd/info\r\n"+
+            "history: FORWARD /ctx/path1\r\n"+
             "history: initial\r\n"+
             "history: suspend\r\n"+
             "history: resume\r\n"+
-            "history: ASYNC /path1\r\n"+
+            "history: ASYNC /ctx/path1\r\n"+
             "history: wrapped REQ RSP\r\n"+
             "history: !initial\r\n"+
             "history: onComplete\r\n",response);
@@ -418,12 +465,12 @@ public class AsyncServletTest
         String response=process("fwd","wrap=true&suspend=200&resume=20&path=/path2",null);
         assertEquals("HTTP/1.1 200 OK",response.substring(0,15));
         assertContains(
-            "history: FWD REQUEST /fwd\r\n"+
-            "history: FORWARD /path1\r\n"+
+            "history: FWD REQUEST /ctx/fwd/info\r\n"+
+            "history: FORWARD /ctx/path1\r\n"+
             "history: initial\r\n"+
             "history: suspend\r\n"+
             "history: resume\r\n"+
-            "history: ASYNC /path2\r\n"+
+            "history: ASYNC /ctx/path2\r\n"+
             "history: wrapped REQ RSP\r\n"+
             "history: !initial\r\n"+
             "history: onComplete\r\n",response);
@@ -431,17 +478,44 @@ public class AsyncServletTest
     }
     
     
-    
-    protected void assertContains(String content,String response)
-    {
-        Assert.assertThat(response,Matchers.containsString(content));
-    }
-    
-    protected void assertNotContains(String content,String response)
+    @Test
+    public void testAsyncRead() throws Exception
     {
-        Assert.assertThat(response,Matchers.not(Matchers.containsString(content)));
+        _expectedLogs=2;
+        String header="GET /ctx/path/info?suspend=2000&resume=1500 HTTP/1.1\r\n"+
+            "Host: localhost\r\n"+
+            "Content-Length: 10\r\n"+
+            "\r\n";
+        String body="12345678\r\n";
+        String close="GET /ctx/path/info HTTP/1.1\r\n"+
+            "Host: localhost\r\n"+
+            "Connection: close\r\n"+
+            "\r\n";
+
+        try (Socket socket = new Socket("localhost",_port))
+        {
+            socket.setSoTimeout(10000);
+            socket.getOutputStream().write(header.getBytes(StandardCharsets.ISO_8859_1));
+            Thread.sleep(500);
+            socket.getOutputStream().write(body.getBytes(StandardCharsets.ISO_8859_1),0,2);
+            Thread.sleep(500);
+            socket.getOutputStream().write(body.getBytes(StandardCharsets.ISO_8859_1),2,8);
+            socket.getOutputStream().write(close.getBytes(StandardCharsets.ISO_8859_1));
+
+            String response = IO.toString(socket.getInputStream());
+            assertEquals("HTTP/1.1 200 OK",response.substring(0,15));
+            assertContains(
+                "history: REQUEST /ctx/path/info\r\n"+
+                "history: initial\r\n"+
+                "history: suspend\r\n"+
+                "history: async-read=10\r\n"+
+                "history: resume\r\n"+
+                "history: ASYNC /ctx/path/info\r\n"+
+                "history: !initial\r\n"+
+                "history: onComplete\r\n",response);
+        }
     }
-    
+
     public synchronized String process(String query,String content) throws Exception
     {
         return process("path",query,content);
@@ -450,7 +524,7 @@ public class AsyncServletTest
     public synchronized String process(String path,String query,String content) throws Exception
     {
         String request = "GET /ctx/"+path+"/info";
-        
+
         if (query!=null)
             request+="?"+query;
         request+=" HTTP/1.1\r\n"+
@@ -463,16 +537,13 @@ public class AsyncServletTest
             request+="Content-Length: "+content.length()+"\r\n";
             request+="\r\n" + content;
         }
-        
+
         int port=_port;
-        String response=null;
-        try
+        try (Socket socket = new Socket("localhost",port))
         {
-            Socket socket = new Socket("localhost",port);
             socket.setSoTimeout(1000000);
-            socket.getOutputStream().write(request.getBytes("UTF-8"));
-
-            response = IO.toString(socket.getInputStream());
+            socket.getOutputStream().write(request.getBytes(StandardCharsets.UTF_8));
+            return IO.toString(socket.getInputStream());
         }
         catch(Exception e)
         {
@@ -480,7 +551,16 @@ public class AsyncServletTest
             e.printStackTrace();
             throw e;
         }
-        return response;
+    }
+
+    protected void assertContains(String content,String response)
+    {
+        Assert.assertThat(response, Matchers.containsString(content));
+    }
+
+    protected void assertNotContains(String content,String response)
+    {
+        Assert.assertThat(response,Matchers.not(Matchers.containsString(content)));
     }
 
     private static class FwdServlet extends HttpServlet
@@ -488,27 +568,34 @@ public class AsyncServletTest
         @Override
         public void doGet(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException
         {
-            response.addHeader("history","FWD "+request.getDispatcherType()+" "+request.getServletPath());
+            response.addHeader("history","FWD "+request.getDispatcherType()+" "+request.getRequestURI());
             if (request instanceof ServletRequestWrapper || response instanceof ServletResponseWrapper)
                 response.addHeader("history","wrapped"+((request instanceof ServletRequestWrapper)?" REQ":"")+((response instanceof ServletResponseWrapper)?" RSP":""));
             request.getServletContext().getRequestDispatcher("/path1").forward(request,response);
         }
-    }    
-       
+    }
     
     private static class AsyncServlet extends HttpServlet
     {
         private static final long serialVersionUID = -8161977157098646562L;
-        private Timer _timer=new Timer();
-        
-        public AsyncServlet()
-        {}
-        
-        /* ------------------------------------------------------------ */
+        private final Timer _timer=new Timer();
+
         @Override
         public void doGet(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException
         {
-            response.addHeader("history",request.getDispatcherType()+" "+request.getServletPath());
+            // this should always fail at this point
+            try
+            {
+                request.getAsyncContext();
+                throw new IllegalStateException();
+            }
+            catch(IllegalStateException e)
+            {
+                // ignored
+            }
+            
+            // System.err.println(request.getDispatcherType()+" "+request.getRequestURI());
+            response.addHeader("history",request.getDispatcherType()+" "+request.getRequestURI());
             if (request instanceof ServletRequestWrapper || response instanceof ServletResponseWrapper)
                 response.addHeader("history","wrapped"+((request instanceof ServletRequestWrapper)?" REQ":"")+((response instanceof ServletResponseWrapper)?" RSP":""));
                 
@@ -521,6 +608,7 @@ public class AsyncServletTest
             long resume2_after=-1;
             long complete_after=-1;
             long complete2_after=-1;
+
             
             if (request.getParameter("read")!=null)
                 read_before=Integer.parseInt(request.getParameter("read"));
@@ -543,8 +631,7 @@ public class AsyncServletTest
             if (request.getAttribute("State")==null)
             {
                 request.setAttribute("State",new Integer(1));
-                
-                ((HttpServletResponse)response).addHeader("history","initial");
+                response.addHeader("history","initial");
                 if (read_before>0)
                 {
                     byte[] buf=new byte[read_before];
@@ -557,6 +644,30 @@ public class AsyncServletTest
                     while(b!=-1)
                         b=in.read();
                 }
+                else if (request.getContentLength()>0)
+                {
+                    new Thread()
+                    {
+                        @Override
+                        public void run()
+                        {
+                            int c=0;
+                            try
+                            {
+                                InputStream in=request.getInputStream();
+                                int b=0;
+                                while(b!=-1)
+                                    if((b=in.read())>=0)
+                                        c++;
+                                response.addHeader("history","async-read="+c);
+                            }
+                            catch(Exception e)
+                            {
+                                e.printStackTrace();
+                            }
+                        }
+                    }.start();
+                }
 
                 if (suspend_for>=0)
                 {
@@ -564,8 +675,8 @@ public class AsyncServletTest
                     if (suspend_for>0)
                         async.setTimeout(suspend_for);
                     async.addListener(__listener);
-                    ((HttpServletResponse)response).addHeader("history","suspend");
-                    
+                    response.addHeader("history","suspend");
+
                     if (complete_after>0)
                     {
                         TimerTask complete = new TimerTask()
@@ -607,7 +718,13 @@ public class AsyncServletTest
                             {
                                 ((HttpServletResponse)async.getResponse()).addHeader("history","resume");
                                 if (path!=null)             
-                                    async.dispatch(path);
+                                {
+                                    int q=path.indexOf('?');
+                                    String uriInContext=(q>=0)
+                                        ?URIUtil.encodePath(path.substring(0,q))+path.substring(q)
+                                        :URIUtil.encodePath(path);
+                                    async.dispatch(uriInContext);
+                                }
                                 else
                                     async.dispatch();
                             }
@@ -625,7 +742,7 @@ public class AsyncServletTest
                         else
                             async.dispatch();
                     }
-                    
+
                 }
                 else if (sleep_for>=0)
                 {
@@ -648,7 +765,7 @@ public class AsyncServletTest
             }
             else
             {
-                ((HttpServletResponse)response).addHeader("history","!initial");
+                response.addHeader("history","!initial");
 
                 if (suspend2_for>=0 && request.getAttribute("2nd")==null)
                 {
@@ -661,7 +778,7 @@ public class AsyncServletTest
                         async.setTimeout(suspend2_for);
                     }
                     // continuation.addContinuationListener(__listener);
-                    ((HttpServletResponse)response).addHeader("history","suspend");
+                    response.addHeader("history","suspend");
 
                     if (complete2_after>0)
                     {
@@ -702,7 +819,7 @@ public class AsyncServletTest
                             @Override
                             public void run()
                             {
-                                ((HttpServletResponse)response).addHeader("history","resume");
+                                response.addHeader("history","resume");
                                 async.dispatch();
                             }
                         };
@@ -713,7 +830,7 @@ public class AsyncServletTest
                     }
                     else if (resume2_after==0)
                     {
-                        ((HttpServletResponse)response).addHeader("history","dispatch");
+                        response.addHeader("history","dispatch");
                         async.dispatch();
                     }
                 }
@@ -729,15 +846,15 @@ public class AsyncServletTest
             }
         }
     }
-    
-    
+
+
     private static AsyncListener __listener = new AsyncListener()
     {
         @Override
         public void onTimeout(AsyncEvent event) throws IOException
-        {            
+        {
             ((HttpServletResponse)event.getSuppliedResponse()).addHeader("history","onTimeout");
-            String action=((HttpServletRequest)event.getSuppliedRequest()).getParameter("timeout");
+            String action=event.getSuppliedRequest().getParameter("timeout");
             if (action!=null)
             {
                 ((HttpServletResponse)event.getSuppliedResponse()).addHeader("history",action);
@@ -745,26 +862,22 @@ public class AsyncServletTest
                     event.getAsyncContext().dispatch();
                 if ("complete".equals(action))
                 {
-                    ((HttpServletResponse)event.getSuppliedResponse()).getOutputStream().println("COMPLETED\n");
+                    event.getSuppliedResponse().getOutputStream().println("COMPLETED\n");
                     event.getAsyncContext().complete();
                 }
             }
         }
-        
+
         @Override
         public void onStartAsync(AsyncEvent event) throws IOException
         {
-            // TODO Auto-generated method stub
-            
         }
-        
+
         @Override
         public void onError(AsyncEvent event) throws IOException
         {
-            // TODO Auto-generated method stub
-            
         }
-        
+
         @Override
         public void onComplete(AsyncEvent event) throws IOException
         {
@@ -772,4 +885,12 @@ public class AsyncServletTest
         }
     };
 
+    class Log extends AbstractLifeCycle implements RequestLog
+    {
+        @Override
+        public void log(Request request, Response response)
+        {            
+            _log.add(response.getStatus()+" "+response.getContentCount()+" "+request.getRequestURI());
+        }
+    }
 }
diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DefaultServletRangesTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DefaultServletRangesTest.java
new file mode 100644
index 0000000..6160f29
--- /dev/null
+++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DefaultServletRangesTest.java
@@ -0,0 +1,265 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlet;
+
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.LocalConnector;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.toolchain.test.FS;
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.eclipse.jetty.toolchain.test.OS;
+import org.eclipse.jetty.toolchain.test.TestingDir;
+import org.eclipse.jetty.util.IO;
+import org.hamcrest.Matchers;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class DefaultServletRangesTest
+{
+    public static final String DATA = "01234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWZYZ!@#$%^&*()_+/.,[]";
+    @Rule
+    public TestingDir testdir = new TestingDir();
+
+    private Server server;
+    private LocalConnector connector;
+    private ServletContextHandler context;
+
+    @Before
+    public void init() throws Exception
+    {
+        server = new Server();
+
+        connector = new LocalConnector(server); 
+        connector.getConnectionFactory(HttpConfiguration.ConnectionFactory.class).getHttpConfiguration().setSendServerVersion(false);
+
+        context = new ServletContextHandler();
+        context.setContextPath("/context");
+        context.setWelcomeFiles(new String[]{"index.html", "index.jsp", "index.htm"});
+
+        server.setHandler(context);
+        server.addConnector(connector);
+
+
+        testdir.ensureEmpty();
+        File resBase = testdir.getFile("docroot");
+        FS.ensureDirExists(resBase);
+        File data = new File(resBase, "data.txt");
+        createFile(data, DATA);
+        String resBasePath = resBase.getAbsolutePath();
+
+        ServletHolder defholder = context.addServlet(DefaultServlet.class, "/");
+        defholder.setInitParameter("acceptRanges", "true");
+        defholder.setInitParameter("resourceBase", resBasePath);
+
+        server.start();
+    }
+
+    @After
+    public void destroy() throws Exception
+    {
+        server.stop();
+        server.join();
+    }
+
+    @Test
+    public void testNoRangeRequests() throws Exception
+    {
+        String response;
+
+        response= connector.getResponses(
+                "GET /context/data.txt HTTP/1.1\r\n" +
+                        "Host: localhost\r\n" +
+                        "Connection: close\r\n"+
+                        "\r\n");
+        assertResponseContains("200 OK", response);
+        assertResponseContains("Accept-Ranges: bytes", response);
+        assertResponseContains(DATA,response);
+    }
+
+    @Test
+    public void testPrefixRangeRequests() throws Exception
+    {
+        String response;
+
+        response = connector.getResponses(
+                "GET /context/data.txt HTTP/1.1\r\n" +
+                        "Host: localhost\r\n" +
+                        "Connection: close\r\n"+
+                        "Range: bytes=0-9\r\n" +
+                        "\r\n");
+        assertResponseContains("206 Partial", response);
+        assertResponseContains("Content-Type: text/plain", response);
+        assertResponseContains("Content-Length: 10", response);
+        assertResponseContains("Content-Range: bytes 0-9/80", response);
+        assertResponseContains(DATA.substring(0,10), response);
+    }
+
+    @Test
+    public void testSingleRangeRequests() throws Exception
+    {
+        String response;
+
+        response = connector.getResponses(
+                "GET /context/data.txt HTTP/1.1\r\n" +
+                        "Host: localhost\r\n" +
+                        "Connection: close\r\n"+
+                        "Range: bytes=3-9\r\n" +
+                        "\r\n");
+        assertResponseContains("206 Partial", response);
+        assertResponseContains("Content-Type: text/plain", response);
+        assertResponseContains("Content-Length: 7", response);
+        assertResponseContains("Content-Range: bytes 3-9/80", response);
+        assertResponseContains(DATA.substring(3,10), response);
+    }
+
+    @Test
+    public void testMultipleRangeRequests() throws Exception
+    {
+        String response;
+        response = connector.getResponses(
+                "GET /context/data.txt HTTP/1.1\r\n" +
+                "Host: localhost\r\n" +
+                "Connection: close\r\n"+
+                "Range: bytes=0-9,20-29,40-49\r\n" +
+                "\r\n");
+        int start = response.indexOf("--jetty");
+        String body = response.substring(start);
+        String boundary = body.substring(0, body.indexOf("\r\n"));
+        assertResponseContains("206 Partial", response);
+        assertResponseContains("Content-Type: multipart/byteranges; boundary=", response);
+        assertResponseContains("Content-Range: bytes 0-9/80", response);
+        assertResponseContains("Content-Range: bytes 20-29/80", response);
+        assertResponseContains("Content-Range: bytes 40-49/80", response);
+        assertResponseContains("Content-Length: " + body.length(), response);
+        assertResponseContains(DATA.substring(0,10), response);
+        assertResponseContains(DATA.substring(20,30), response);
+        assertResponseContains(DATA.substring(40,50), response);
+        assertTrue(body.endsWith(boundary + "--\r\n"));
+
+    }
+
+    @Test
+    public void testOpenEndRange() throws Exception
+    {
+        String response;
+        response = connector.getResponses(
+                "GET /context/data.txt HTTP/1.1\r\n" +
+                "Host: localhost\r\n" +
+                "Connection: close\r\n"+
+                "Range: bytes=20-\r\n" +
+                "\r\n");
+        assertResponseContains("206 Partial", response);
+        assertResponseNotContains("Content-Type: multipart/byteranges; boundary=", response);
+        assertResponseContains("Content-Range: bytes 20-79/80", response);
+        assertResponseContains("Content-Length: 60", response);
+        assertResponseContains(DATA.substring(60), response);
+    }
+
+    @Test
+    public void testOpenStartRange() throws Exception
+    {
+        String response;
+        response = connector.getResponses(
+                "GET /context/data.txt HTTP/1.1\r\n" +
+                "Host: localhost\r\n" +
+                "Connection: close\r\n"+
+                "Range: bytes=-20\r\n" +
+                "\r\n");
+        assertResponseContains("206 Partial", response);
+        assertResponseNotContains("Content-Type: multipart/byteranges; boundary=", response);
+        assertResponseContains("Content-Range: bytes 60-79/80", response); // yes the spec says it is these bytes
+        assertResponseContains("Content-Length: 20", response);
+        assertResponseContains(DATA.substring(60), response);
+    }
+
+    @Test
+    public void testUnsatisfiableRanges() throws Exception
+    {
+        String response;
+        response = connector.getResponses(
+                "GET /context/data.txt HTTP/1.1\r\n" +
+                "Host: localhost\r\n" +
+                "Connection: close\r\n"+
+                "Range: bytes=100-110\r\n" +
+                "\r\n");
+        assertResponseContains("416 Requested Range Not Satisfiable", response);
+    }
+
+
+
+
+    private void createFile(File file, String str) throws IOException
+    {
+        FileOutputStream out = null;
+        try
+        {
+            out = new FileOutputStream(file);
+            out.write(str.getBytes(StandardCharsets.UTF_8));
+            out.flush();
+        }
+        finally
+        {
+            IO.close(out);
+        }
+    }
+
+    private void assertResponseNotContains(String forbidden, String response)
+    {
+        Assert.assertThat(response,Matchers.not(Matchers.containsString(forbidden)));
+    }
+
+    private int assertResponseContains(String expected, String response)
+    {
+        Assert.assertThat(response,Matchers.containsString(expected));
+        return response.indexOf(expected);
+    }
+
+    private void deleteFile(File file) throws IOException
+    {
+        if (OS.IS_WINDOWS)
+        {
+            // Windows doesn't seem to like to delete content that was recently created
+            // Attempt a delete and if it fails, attempt a rename
+            boolean deleted = file.delete();
+            if (!deleted)
+            {
+                File deletedDir = MavenTestingUtils.getTargetFile(".deleted");
+                FS.ensureDirExists(deletedDir);
+                File dest = File.createTempFile(file.getName(), "deleted", deletedDir);
+                boolean renamed = file.renameTo(dest);
+                if (!renamed)
+                    System.err.println("WARNING: unable to move file out of the way: " + file.getName());
+            }
+        }
+        else
+        {
+            Assert.assertTrue("Deleting: " + file.getName(), file.delete());
+        }
+    }
+}
diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DefaultServletTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DefaultServletTest.java
index d349d45..207277a 100644
--- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DefaultServletTest.java
+++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DefaultServletTest.java
@@ -23,10 +23,13 @@ import static org.junit.Assert.assertTrue;
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
 import java.util.EnumSet;
-import javax.servlet.DispatcherType;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
+
+import javax.servlet.DispatcherType;
 import javax.servlet.Filter;
 import javax.servlet.FilterChain;
 import javax.servlet.FilterConfig;
@@ -34,17 +37,16 @@ import javax.servlet.ServletException;
 import javax.servlet.ServletRequest;
 import javax.servlet.ServletResponse;
 
-import junit.framework.AssertionFailedError;
-
-import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.DateGenerator;
+import org.eclipse.jetty.server.HttpConfiguration;
 import org.eclipse.jetty.server.LocalConnector;
 import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.handler.ContextHandler;
 import org.eclipse.jetty.toolchain.test.FS;
 import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
 import org.eclipse.jetty.toolchain.test.OS;
 import org.eclipse.jetty.toolchain.test.TestingDir;
 import org.eclipse.jetty.util.IO;
-import org.eclipse.jetty.util.StringUtil;
 import org.hamcrest.Matchers;
 import org.junit.After;
 import org.junit.Assert;
@@ -65,9 +67,9 @@ public class DefaultServletTest
     public void init() throws Exception
     {
         server = new Server();
-        server.setSendServerVersion(false);
 
-        connector = new LocalConnector();
+        connector = new LocalConnector(server);
+        connector.getConnectionFactory(HttpConfiguration.ConnectionFactory.class).getHttpConfiguration().setSendServerVersion(false);
 
         context = new ServletContextHandler();
         context.setContextPath("/context");
@@ -107,9 +109,7 @@ public class DefaultServletTest
         defholder.setInitParameter("resourceBase", resBasePath);
 
         StringBuffer req1 = new StringBuffer();
-        req1.append("GET /context/;JSESSIONID=1234567890 HTTP/1.1\n");
-        req1.append("Host: localhost\n");
-        req1.append("\n");
+        req1.append("GET /context/;JSESSIONID=1234567890 HTTP/1.0\n\n");
 
         String response = connector.getResponses(req1.toString());
 
@@ -149,8 +149,7 @@ public class DefaultServletTest
          * Intentionally bad request URI. Sending a non-encoded URI with typically encoded characters '<', '>', and
          * '"'.
          */
-        req1.append("GET /context/;<script>window.alert(\"hi\");</script> HTTP/1.1\n");
-        req1.append("Host: localhost\n");
+        req1.append("GET /context/;<script>window.alert(\"hi\");</script> HTTP/1.0\n");
         req1.append("\n");
 
         String response = connector.getResponses(req1.toString());
@@ -438,19 +437,17 @@ public class DefaultServletTest
         response = connector.getResponses("GET /context/foobar.txt HTTP/1.0\r\n\r\n");
         assertResponseContains("Foo Bar", response);
 
-        /* Test needs java 1.7
         if (!OS.IS_WINDOWS)
         {
             Files.createSymbolicLink(link.toPath(),foobar.toPath());
             response = connector.getResponses("GET /context/link.txt HTTP/1.0\r\n\r\n");
             assertResponseContains("404", response);
             
-            context.setAliases(true);
+            context.addAliasCheck(new ContextHandler.ApproveAliases());
             
             response = connector.getResponses("GET /context/link.txt HTTP/1.0\r\n\r\n");
             assertResponseContains("Foo Bar", response);
         }
-        */
     }
 
     @Test
@@ -512,31 +509,37 @@ public class DefaultServletTest
         String resBasePath = resBase.getAbsolutePath();
 
         ServletHolder defholder = context.addServlet(DefaultServlet.class, "/");
+        defholder.setInitParameter("dirAllowed", "false");
+        defholder.setInitParameter("redirectWelcome", "false");
+        defholder.setInitParameter("welcomeServlets", "false");
+        defholder.setInitParameter("gzip", "false");
         defholder.setInitParameter("acceptRanges", "true");
         defholder.setInitParameter("resourceBase", resBasePath);
 
-        String response = connector.getResponses(
-                "GET /context/data.txt HTTP/1.1\r\n" +
-                        "Host: localhost\r\n" +
-                        "\r\n");
+        String response = connector.getResponses("GET /context/data.txt HTTP/1.1\r\n" +
+                "Host: localhost\r\n" +
+                "Connection: close\r\n\r\n");
         assertResponseContains("200 OK", response);
         assertResponseContains("Accept-Ranges: bytes", response);
 
-        response = connector.getResponses(
-                "GET /context/data.txt HTTP/1.1\r\n" +
-                        "Host: localhost\r\n" +
-                        "Range: bytes=0-9\r\n" +
-                        "\r\n");
+
+
+        response = connector.getResponses("GET /context/data.txt HTTP/1.1\r\n" +
+                "Host: localhost\r\n" +
+                "Connection: close\r\n"+
+                "Range: bytes=0-9\r\n" +
+                "\r\n");
         assertResponseContains("206 Partial", response);
         assertResponseContains("Content-Type: text/plain", response);
         assertResponseContains("Content-Length: 10", response);
         assertResponseContains("Content-Range: bytes 0-9/80", response);
 
-        response = connector.getResponses(
-                "GET /context/data.txt HTTP/1.1\r\n" +
-                        "Host: localhost\r\n" +
-                        "Range: bytes=0-9,20-29,40-49\r\n" +
-                        "\r\n");
+
+        response = connector.getResponses("GET /context/data.txt HTTP/1.1\r\n" +
+                "Host: localhost\r\n" +
+                "Connection: close\r\n"+
+                "Range: bytes=0-9,20-29,40-49\r\n" +
+                "\r\n");
         int start = response.indexOf("--jetty");
         String body = response.substring(start);
         String boundary = body.substring(0, body.indexOf("\r\n"));
@@ -547,11 +550,11 @@ public class DefaultServletTest
         assertResponseContains("Content-Length: " + body.length(), response);
         assertTrue(body.endsWith(boundary + "--\r\n"));
 
-        response = connector.getResponses(
-                "GET /context/data.txt HTTP/1.1\r\n" +
-                        "Host: localhost\r\n" +
-                        "Range: bytes=0-9,20-29,40-49,70-79\r\n" +
-                        "\r\n");
+        response = connector.getResponses("GET /context/data.txt HTTP/1.1\r\n" +
+                "Host: localhost\r\n" +
+                "Connection: close\r\n"+
+                "Range: bytes=0-9,20-29,40-49,70-79\r\n" +
+                "\r\n");
         start = response.indexOf("--jetty");
         body = response.substring(start);
         boundary = body.substring(0, body.indexOf("\r\n"));
@@ -563,11 +566,11 @@ public class DefaultServletTest
         assertResponseContains("Content-Length: " + body.length(), response);
         assertTrue(body.endsWith(boundary + "--\r\n"));
 
-        response = connector.getResponses(
-                "GET /context/data.txt HTTP/1.1\r\n" +
-                        "Host: localhost\r\n" +
-                        "Range: bytes=0-9,20-29,40-49,60-60,70-79\r\n" +
-                        "\r\n");
+        response = connector.getResponses("GET /context/data.txt HTTP/1.1\r\n" +
+                "Host: localhost\r\n" +
+                "Connection: close\r\n"+
+                "Range: bytes=0-9,20-29,40-49,60-60,70-79\r\n" +
+                "\r\n");
         start = response.indexOf("--jetty");
         body = response.substring(start);
         boundary = body.substring(0, body.indexOf("\r\n"));
@@ -581,30 +584,34 @@ public class DefaultServletTest
         assertTrue(body.endsWith(boundary + "--\r\n"));
 
         //test a range request with a file with no suffix, therefore no mimetype
+
         File nofilesuffix = new File(resBase, "nofilesuffix");
         createFile(nofilesuffix, "01234567890123456789012345678901234567890123456789012345678901234567890123456789");
-        response = connector.getResponses(
-                                          "GET /context/nofilesuffix HTTP/1.1\r\n" +
-                                          "Host: localhost\r\n" +
-        "\r\n");
+
+        response = connector.getResponses("GET /context/nofilesuffix HTTP/1.1\r\n" +
+                "Host: localhost\r\n" +
+                "Connection: close\r\n"+  
+                "\r\n");
         assertResponseContains("200 OK", response);
         assertResponseContains("Accept-Ranges: bytes", response);
 
-        response = connector.getResponses(
-                                          "GET /context/nofilesuffix HTTP/1.1\r\n" +
-                                          "Host: localhost\r\n" +
-                                          "Range: bytes=0-9\r\n" +
-        "\r\n");
+
+
+        response = connector.getResponses("GET /context/nofilesuffix HTTP/1.1\r\n" +
+                "Host: localhost\r\n" +
+                "Connection: close\r\n"+
+                "Range: bytes=0-9\r\n" +
+                "\r\n");
         assertResponseContains("206 Partial", response);
         assertResponseContains("Content-Length: 10", response);
         assertTrue(!response.contains("Content-Type:"));
         assertResponseContains("Content-Range: bytes 0-9/80", response);
 
-        response = connector.getResponses(
-                                          "GET /context/nofilesuffix HTTP/1.1\r\n" +
-                                          "Host: localhost\r\n" +
-                                          "Range: bytes=0-9,20-29,40-49\r\n" +
-        "\r\n");
+        response = connector.getResponses("GET /context/nofilesuffix HTTP/1.1\r\n" +
+                "Host: localhost\r\n" +
+                "Connection: close\r\n"+     
+                "Range: bytes=0-9,20-29,40-49\r\n" +
+                "\r\n");
         start = response.indexOf("--jetty");
         body = response.substring(start);
         boundary = body.substring(0, body.indexOf("\r\n"));
@@ -615,11 +622,13 @@ public class DefaultServletTest
         assertResponseContains("Content-Length: " + body.length(), response);
         assertTrue(body.endsWith(boundary + "--\r\n"));
 
-        response = connector.getResponses(
-                                          "GET /context/nofilesuffix HTTP/1.1\r\n" +
-                                          "Host: localhost\r\n" +
-                                          "Range: bytes=0-9,20-29,40-49,60-60,70-79\r\n" +
-        "\r\n");
+
+
+        response = connector.getResponses("GET /context/nofilesuffix HTTP/1.1\r\n" +
+                "Host: localhost\r\n" +
+                "Connection: close\r\n"+    
+                "Range: bytes=0-9,20-29,40-49,60-60,70-79\r\n" +
+                "\r\n");
         start = response.indexOf("--jetty");
         body = response.substring(start);
         boundary = body.substring(0, body.indexOf("\r\n"));
@@ -631,7 +640,6 @@ public class DefaultServletTest
         assertResponseContains("Content-Range: bytes 70-79/80", response);
         assertResponseContains("Content-Length: " + body.length(), response);
         assertTrue(body.endsWith(boundary + "--\r\n"));
-
     }
 
 
@@ -655,12 +663,12 @@ public class DefaultServletTest
         defholder.setInitParameter("gzip", "false");
         defholder.setInitParameter("resourceBase", resBasePath);
 
-        String response = connector.getResponses("GET /context/data0.txt HTTP/1.1\r\nHost:localhost:8080\r\n\r\n");
+        String response = connector.getResponses("GET /context/data0.txt HTTP/1.0\r\n\r\n");
         assertResponseContains("Content-Length: 12", response);
         assertResponseNotContains("Extra Info", response);
 
         context.addFilter(OutputFilter.class,"/*",EnumSet.of(DispatcherType.REQUEST));
-        response = connector.getResponses("GET /context/data0.txt HTTP/1.1\r\nHost:localhost:8080\r\n\r\n");
+        response = connector.getResponses("GET /context/data0.txt HTTP/1.0\r\n\r\n");
         assertResponseContains("Content-Length: 2", response); // 20 something long
         assertResponseContains("Extra Info", response);
         assertResponseNotContains("Content-Length: 12", response);
@@ -669,7 +677,7 @@ public class DefaultServletTest
         context.getServletHandler().setFilters(new FilterHolder[]{});
 
         context.addFilter(WriterFilter.class,"/*",EnumSet.of(DispatcherType.REQUEST));
-        response = connector.getResponses("GET /context/data0.txt HTTP/1.1\r\nHost:localhost:8080\r\n\r\n");
+        response = connector.getResponses("GET /context/data0.txt HTTP/1.0\r\n\r\n");
         assertResponseContains("Content-Length: 2", response); // 20 something long
         assertResponseContains("Extra Info", response);
         assertResponseNotContains("Content-Length: 12", response);
@@ -696,13 +704,13 @@ public class DefaultServletTest
         defholder.setInitParameter("gzip", "true");
         defholder.setInitParameter("resourceBase", resBasePath);
 
-        String response = connector.getResponses("GET /context/data0.txt HTTP/1.1\r\nHost:localhost:8080\r\n\r\n");
+        String response = connector.getResponses("GET /context/data0.txt HTTP/1.0\r\nHost:localhost:8080\r\n\r\n");
         assertResponseContains("Content-Length: 12", response);
         assertResponseContains("Hello Text 0",response);
         assertResponseContains("Vary: Accept-Encoding",response);
         assertResponseNotContains("Content-Encoding: gzip",response);
         
-        response = connector.getResponses("GET /context/data0.txt HTTP/1.1\r\nHost:localhost:8080\r\nAccept-Encoding:gzip\r\n\r\n");
+        response = connector.getResponses("GET /context/data0.txt HTTP/1.0\r\nHost:localhost:8080\r\nAccept-Encoding:gzip\r\n\r\n");
         assertResponseContains("Content-Length: 9", response);
         assertResponseContains("fake gzip",response);
         assertResponseContains("Vary: Accept-Encoding",response);
@@ -752,16 +760,16 @@ public class DefaultServletTest
         response = connector.getResponses("GET /context/file.txt HTTP/1.1\r\nHost:test\r\nConnection:close\r\nIf-Modified-Since: "+last_modified+"\r\n\r\n");
         assertResponseContains("304", response);
         
-        response = connector.getResponses("GET /context/file.txt HTTP/1.1\r\nHost:test\r\nConnection:close\r\nIf-Modified-Since: "+HttpFields.formatDate(System.currentTimeMillis()-10000)+"\r\n\r\n");
+        response = connector.getResponses("GET /context/file.txt HTTP/1.1\r\nHost:test\r\nConnection:close\r\nIf-Modified-Since: "+DateGenerator.formatDate(System.currentTimeMillis()-10000)+"\r\n\r\n");
         assertResponseContains("200", response);
         
-        response = connector.getResponses("GET /context/file.txt HTTP/1.1\r\nHost:test\r\nConnection:close\r\nIf-Modified-Since: "+HttpFields.formatDate(System.currentTimeMillis()+10000)+"\r\n\r\n");
+        response = connector.getResponses("GET /context/file.txt HTTP/1.1\r\nHost:test\r\nConnection:close\r\nIf-Modified-Since: "+DateGenerator.formatDate(System.currentTimeMillis()+10000)+"\r\n\r\n");
         assertResponseContains("304", response);
         
-        response = connector.getResponses("GET /context/file.txt HTTP/1.1\r\nHost:test\r\nConnection:close\r\nIf-Unmodified-Since: "+HttpFields.formatDate(System.currentTimeMillis()+10000)+"\r\n\r\n");
+        response = connector.getResponses("GET /context/file.txt HTTP/1.1\r\nHost:test\r\nConnection:close\r\nIf-Unmodified-Since: "+DateGenerator.formatDate(System.currentTimeMillis()+10000)+"\r\n\r\n");
         assertResponseContains("200", response);
         
-        response = connector.getResponses("GET /context/file.txt HTTP/1.1\r\nHost:test\r\nConnection:close\r\nIf-Unmodified-Since: "+HttpFields.formatDate(System.currentTimeMillis()-10000)+"\r\n\r\n");
+        response = connector.getResponses("GET /context/file.txt HTTP/1.1\r\nHost:test\r\nConnection:close\r\nIf-Unmodified-Since: "+DateGenerator.formatDate(System.currentTimeMillis()-10000)+"\r\n\r\n");
         assertResponseContains("412", response);
     }
 
@@ -830,16 +838,19 @@ public class DefaultServletTest
     
     public static class OutputFilter implements Filter
     {
+        @Override
         public void init(FilterConfig filterConfig) throws ServletException
         {
         }
 
+        @Override
         public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
         {
             response.getOutputStream().println("Extra Info");
             chain.doFilter(request, response);
         }
 
+        @Override
         public void destroy()
         {
         }
@@ -847,16 +858,19 @@ public class DefaultServletTest
 
     public static class WriterFilter implements Filter
     {
+        @Override
         public void init(FilterConfig filterConfig) throws ServletException
         {
         }
 
+        @Override
         public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
         {
             response.getWriter().println("Extra Info");
             chain.doFilter(request, response);
         }
 
+        @Override
         public void destroy()
         {
         }
@@ -868,7 +882,7 @@ public class DefaultServletTest
         try
         {
             out = new FileOutputStream(file);
-            out.write(str.getBytes(StringUtil.__UTF8));
+            out.write(str.getBytes(StandardCharsets.UTF_8));
             out.flush();
         }
         finally
@@ -879,17 +893,7 @@ public class DefaultServletTest
 
     private void assertResponseNotContains(String forbidden, String response)
     {
-        int idx = response.indexOf(forbidden);
-        if (idx != (-1))
-        {
-            // Found (when should not have)
-            StringBuffer err = new StringBuffer();
-            err.append("Response contain forbidden string \"").append(forbidden).append("\"");
-            err.append("\n").append(response);
-
-            System.err.println(err);
-            throw new AssertionFailedError(err.toString());
-        }
+        Assert.assertThat(response,Matchers.not(Matchers.containsString(forbidden)));
     }
 
     private int assertResponseContains(String expected, String response)
diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DispatcherForwardTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DispatcherForwardTest.java
new file mode 100644
index 0000000..7266601
--- /dev/null
+++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DispatcherForwardTest.java
@@ -0,0 +1,518 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlet;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import javax.servlet.ServletException;
+import javax.servlet.ServletInputStream;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.LocalConnector;
+import org.eclipse.jetty.server.Server;
+import org.hamcrest.Matcher;
+import org.hamcrest.Matchers;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class DispatcherForwardTest
+{
+    private Server server;
+    private LocalConnector connector;
+    private HttpServlet servlet1;
+    private HttpServlet servlet2;
+    private List<Exception> failures = new ArrayList<>();
+
+    public void prepare() throws Exception
+    {
+        server = new Server();
+        connector = new LocalConnector(server);
+        server.addConnector(connector);
+
+        ServletContextHandler context = new ServletContextHandler(server, "/");
+        context.addServlet(new ServletHolder(servlet1), "/one");
+        context.addServlet(new ServletHolder(servlet2), "/two");
+
+        server.start();
+    }
+
+    @After
+    public void dispose() throws Exception
+    {
+        for (Exception failure : failures)
+            throw failure;
+        server.stop();
+    }
+
+    // Replacement for Assert that allows to check failures after the response has been sent.
+    private <S> void checkThat(S item, Matcher<S> matcher)
+    {
+        if (!matcher.matches(item))
+            failures.add(new Exception());
+    }
+
+    @Test
+    public void testQueryRetainedByForwardWithoutQuery() throws Exception
+    {
+        // 1. request /one?a=1
+        // 1. forward /two
+        // 2. assert query => a=1
+        // 1. assert query => a=1
+
+        final String query1 = "a=1";
+        servlet1 = new HttpServlet()
+        {
+            @Override
+            protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+            {
+                checkThat(query1, Matchers.equalTo(req.getQueryString()));
+
+                req.getRequestDispatcher("/two").forward(req, resp);
+
+                checkThat(query1, Matchers.equalTo(req.getQueryString()));
+                checkThat("1", Matchers.equalTo(req.getParameter("a")));
+            }
+        };
+        servlet2 = new HttpServlet()
+        {
+            @Override
+            protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+            {
+                checkThat(query1, Matchers.equalTo(req.getQueryString()));
+                checkThat("1", Matchers.equalTo(req.getParameter("a")));
+            }
+        };
+
+        prepare();
+
+        String request = "" +
+                "GET /one?" + query1 + " HTTP/1.1\r\n" +
+                "Host: localhost\r\n" +
+                "Connection: close\r\n" +
+                "\r\n";
+        String response = connector.getResponses(request);
+        Assert.assertTrue(response, response.startsWith("HTTP/1.1 200"));
+    }
+
+    @Test
+    public void testQueryReplacedByForwardWithQuery() throws Exception
+    {
+        // 1. request /one?a=1
+        // 1. forward /two?a=2
+        // 2. assert query => a=2
+        // 1. assert query => a=1
+
+        final String query1 = "a=1&b=2";
+        final String query2 = "a=3";
+        final String query3 = "a=3&b=2";
+        servlet1 = new HttpServlet()
+        {
+            @Override
+            protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+            {
+                checkThat(query1, Matchers.equalTo(req.getQueryString()));
+
+                req.getRequestDispatcher("/two?" + query2).forward(req, resp);
+
+                checkThat(query1, Matchers.equalTo(req.getQueryString()));
+                checkThat("1", Matchers.equalTo(req.getParameter("a")));
+                checkThat("2", Matchers.equalTo(req.getParameter("b")));
+            }
+        };
+        servlet2 = new HttpServlet()
+        {
+            @Override
+            protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+            {
+                checkThat(query3, Matchers.equalTo(req.getQueryString()));
+                checkThat("3", Matchers.equalTo(req.getParameter("a")));
+                checkThat("2", Matchers.equalTo(req.getParameter("b")));
+            }
+        };
+
+        prepare();
+
+        String request = "" +
+                "GET /one?" + query1 + " HTTP/1.1\r\n" +
+                "Host: localhost\r\n" +
+                "Connection: close\r\n" +
+                "\r\n";
+        String response = connector.getResponses(request);
+        Assert.assertTrue(response, response.startsWith("HTTP/1.1 200"));
+    }
+
+    @Test
+    public void testQueryMergedByForwardWithQuery() throws Exception
+    {
+        // 1. request /one?a=1
+        // 1. forward /two?b=2
+        // 2. assert query => a=1&b=2
+        // 1. assert query => a=1
+
+        final String query1 = "a=1";
+        final String query2 = "b=2";
+        final String query3 = "b=2&a=1";
+        servlet1 = new HttpServlet()
+        {
+            @Override
+            protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+            {
+                checkThat(query1, Matchers.equalTo(req.getQueryString()));
+
+                req.getRequestDispatcher("/two?" + query2).forward(req, resp);
+
+                checkThat(query1, Matchers.equalTo(req.getQueryString()));
+                checkThat("1", Matchers.equalTo(req.getParameter("a")));
+            }
+        };
+        servlet2 = new HttpServlet()
+        {
+            @Override
+            protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+            {
+                checkThat(query3, Matchers.equalTo(req.getQueryString()));
+                checkThat("1", Matchers.equalTo(req.getParameter("a")));
+                checkThat("2", Matchers.equalTo(req.getParameter("b")));
+            }
+        };
+
+        prepare();
+
+        String request = "" +
+                "GET /one?" + query1 + " HTTP/1.1\r\n" +
+                "Host: localhost\r\n" +
+                "Connection: close\r\n" +
+                "\r\n";
+        String response = connector.getResponses(request);
+        Assert.assertTrue(response, response.startsWith("HTTP/1.1 200"));
+    }
+
+    @Test
+    public void testQueryAggregatesWithFormByForwardWithoutQuery() throws Exception
+    {
+        // 1. request /one?a=1 + content a=2
+        // 1. forward /two
+        // 2. assert query => a=1 + params => a=1,2
+        // 1. assert query => a=1 + params => a=1,2
+
+        final String query1 = "a=1";
+        final String form = "a=2";
+        servlet1 = new HttpServlet()
+        {
+            @Override
+            protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+            {
+                checkThat(query1, Matchers.equalTo(req.getQueryString()));
+
+                req.getRequestDispatcher("/two").forward(req, resp);
+
+                checkThat(query1, Matchers.equalTo(req.getQueryString()));
+                String[] values = req.getParameterValues("a");
+                checkThat(values, Matchers.notNullValue());
+                checkThat(2, Matchers.equalTo(values.length));
+                checkThat(values, Matchers.arrayContainingInAnyOrder("1", "2"));
+            }
+        };
+        servlet2 = new HttpServlet()
+        {
+            @Override
+            protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+            {
+                checkThat(query1, Matchers.equalTo(req.getQueryString()));
+                String[] values = req.getParameterValues("a");
+                checkThat(values, Matchers.notNullValue());
+                checkThat(2, Matchers.equalTo(values.length));
+                checkThat(values, Matchers.arrayContainingInAnyOrder("1", "2"));
+            }
+        };
+
+        prepare();
+
+        String request = "" +
+                "POST /one?" + query1 + " HTTP/1.1\r\n" +
+                "Host: localhost\r\n" +
+                "Content-Type: application/x-www-form-urlencoded\r\n" +
+                "Content-Length: " + form.length() + "\r\n" +
+                "Connection: close\r\n" +
+                "\r\n" +
+                form;
+        String response = connector.getResponses(request);
+        Assert.assertTrue(response, response.startsWith("HTTP/1.1 200"));
+    }
+
+    @Test
+    public void testQueryAggregatesWithFormReplacedByForwardWithQuery() throws Exception
+    {
+        // 1. request /one?a=1 + content a=2
+        // 1. forward /two?a=3
+        // 2. assert query => a=3 + params => a=3,2,1
+        // 1. assert query => a=1 + params => a=1,2
+
+        final String query1 = "a=1";
+        final String query2 = "a=3";
+        final String form = "a=2";
+        servlet1 = new HttpServlet()
+        {
+            @Override
+            protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+            {
+                checkThat(query1, Matchers.equalTo(req.getQueryString()));
+
+                req.getRequestDispatcher("/two?" + query2).forward(req, resp);
+
+                checkThat(query1, Matchers.equalTo(req.getQueryString()));
+                String[] values = req.getParameterValues("a");
+                checkThat(values, Matchers.notNullValue());
+                checkThat(2, Matchers.equalTo(values.length));
+                checkThat(values, Matchers.arrayContainingInAnyOrder("1", "2"));
+            }
+        };
+        servlet2 = new HttpServlet()
+        {
+            @Override
+            protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+            {
+                checkThat(query2, Matchers.equalTo(req.getQueryString()));
+                String[] values = req.getParameterValues("a");
+                checkThat(values, Matchers.notNullValue());
+                checkThat(3, Matchers.equalTo(values.length));
+                checkThat(values, Matchers.arrayContainingInAnyOrder("3", "2", "1"));
+            }
+        };
+
+        prepare();
+
+        String request = "" +
+                "POST /one?" + query1 + " HTTP/1.1\r\n" +
+                "Host: localhost\r\n" +
+                "Content-Type: application/x-www-form-urlencoded\r\n" +
+                "Content-Length: " + form.length() + "\r\n" +
+                "Connection: close\r\n" +
+                "\r\n" +
+                form;
+        String response = connector.getResponses(request);
+        Assert.assertTrue(response, response.startsWith("HTTP/1.1 200"));
+    }
+
+    @Test
+    public void testQueryAggregatesWithFormMergedByForwardWithQuery() throws Exception
+    {
+        // 1. request /one?a=1 + content b=2
+        // 1. forward /two?c=3
+        // 2. assert query => a=1&c=3 + params => a=1&b=2&c=3
+        // 1. assert query => a=1 + params => a=1&b=2
+
+        final String query1 = "a=1";
+        final String query2 = "c=3";
+        final String query3 = "c=3&a=1";
+        final String form = "b=2";
+        servlet1 = new HttpServlet()
+        {
+            @Override
+            protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+            {
+                checkThat(query1, Matchers.equalTo(req.getQueryString()));
+
+                req.getRequestDispatcher("/two?" + query2).forward(req, resp);
+
+                checkThat(query1, Matchers.equalTo(req.getQueryString()));
+                checkThat("1", Matchers.equalTo(req.getParameter("a")));
+                checkThat("2", Matchers.equalTo(req.getParameter("b")));
+                checkThat(req.getParameter("c"), Matchers.nullValue());
+            }
+        };
+        servlet2 = new HttpServlet()
+        {
+            @Override
+            protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+            {
+                checkThat(query3, Matchers.equalTo(req.getQueryString()));
+                checkThat("1", Matchers.equalTo(req.getParameter("a")));
+                checkThat("2", Matchers.equalTo(req.getParameter("b")));
+                checkThat("3", Matchers.equalTo(req.getParameter("c")));
+            }
+        };
+
+        prepare();
+
+        String request = "" +
+                "POST /one?" + query1 + " HTTP/1.1\r\n" +
+                "Host: localhost\r\n" +
+                "Content-Type: application/x-www-form-urlencoded\r\n" +
+                "Content-Length: " + form.length() + "\r\n" +
+                "Connection: close\r\n" +
+                "\r\n" +
+                form;
+        String response = connector.getResponses(request);
+        Assert.assertTrue(response, response.startsWith("HTTP/1.1 200"));
+    }
+
+    @Test
+    public void testQueryAggregatesWithFormBeforeAndAfterForward() throws Exception
+    {
+        // 1. request /one?a=1 + content b=2
+        // 1. assert params => a=1&b=2
+        // 1. forward /two?c=3
+        // 2. assert query => a=1&c=3 + params => a=1&b=2&c=3
+        // 1. assert query => a=1 + params => a=1&b=2
+
+        final String query1 = "a=1";
+        final String query2 = "c=3";
+        final String query3 = "c=3&a=1";
+        final String form = "b=2";
+        servlet1 = new HttpServlet()
+        {
+            @Override
+            protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+            {
+                checkThat(query1, Matchers.equalTo(req.getQueryString()));
+                checkThat("1", Matchers.equalTo(req.getParameter("a")));
+                checkThat("2", Matchers.equalTo(req.getParameter("b")));
+
+                req.getRequestDispatcher("/two?" + query2).forward(req, resp);
+
+                checkThat(query1, Matchers.equalTo(req.getQueryString()));
+                checkThat("1", Matchers.equalTo(req.getParameter("a")));
+                checkThat("2", Matchers.equalTo(req.getParameter("b")));
+                checkThat(req.getParameter("c"), Matchers.nullValue());
+            }
+        };
+        servlet2 = new HttpServlet()
+        {
+            @Override
+            protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+            {
+                checkThat(query3, Matchers.equalTo(req.getQueryString()));
+                checkThat("1", Matchers.equalTo(req.getParameter("a")));
+                checkThat("2", Matchers.equalTo(req.getParameter("b")));
+                checkThat("3", Matchers.equalTo(req.getParameter("c")));
+            }
+        };
+
+        prepare();
+
+        String request = "" +
+                "POST /one?" + query1 + " HTTP/1.1\r\n" +
+                "Host: localhost\r\n" +
+                "Content-Type: application/x-www-form-urlencoded\r\n" +
+                "Content-Length: " + form.length() + "\r\n" +
+                "Connection: close\r\n" +
+                "\r\n" +
+                form;
+        String response = connector.getResponses(request);
+        Assert.assertTrue(response, response.startsWith("HTTP/1.1 200"));
+    }
+
+    @Test
+    public void testContentCanBeReadViaInputStreamAfterForwardWithoutQuery() throws Exception
+    {
+        final String query1 = "a=1";
+        final String form = "c=3";
+        servlet1 = new HttpServlet()
+        {
+            @Override
+            protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+            {
+                checkThat(query1, Matchers.equalTo(req.getQueryString()));
+
+                req.getRequestDispatcher("/two").forward(req, resp);
+
+                checkThat(query1, Matchers.equalTo(req.getQueryString()));
+                checkThat(req.getParameter("c"), Matchers.nullValue());
+            }
+        };
+        servlet2 = new HttpServlet()
+        {
+            @Override
+            protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+            {
+                checkThat(query1, Matchers.equalTo(req.getQueryString()));
+                ServletInputStream input = req.getInputStream();
+                for (int i = 0; i < form.length(); ++i)
+                    checkThat(form.charAt(i) & 0xFFFF, Matchers.equalTo(input.read()));
+            }
+        };
+
+        prepare();
+
+        String request = "" +
+                "POST /one?" + query1 + " HTTP/1.1\r\n" +
+                "Host: localhost\r\n" +
+                "Content-Type: application/x-www-form-urlencoded\r\n" +
+                "Content-Length: " + form.length() + "\r\n" +
+                "Connection: close\r\n" +
+                "\r\n" +
+                form;
+        String response = connector.getResponses(request);
+        Assert.assertTrue(response, response.startsWith("HTTP/1.1 200"));
+    }
+
+    @Test
+    public void testContentCanBeReadViaInputStreamAfterForwardWithQuery() throws Exception
+    {
+        final String query1 = "a=1";
+        final String query2 = "b=2";
+        final String query3 = "b=2&a=1";
+        final String form = "c=3";
+        servlet1 = new HttpServlet()
+        {
+            @Override
+            protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+            {
+                checkThat(query1, Matchers.equalTo(req.getQueryString()));
+
+                req.getRequestDispatcher("/two?" + query2).forward(req, resp);
+
+                checkThat(query1, Matchers.equalTo(req.getQueryString()));
+                checkThat(req.getParameter("c"), Matchers.nullValue());
+            }
+        };
+        servlet2 = new HttpServlet()
+        {
+            @Override
+            protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+            {
+                checkThat(query3, Matchers.equalTo(req.getQueryString()));
+                ServletInputStream input = req.getInputStream();
+                for (int i = 0; i < form.length(); ++i)
+                    checkThat(form.charAt(i) & 0xFFFF, Matchers.equalTo(input.read()));
+                checkThat(-1, Matchers.equalTo(input.read()));
+            }
+        };
+
+        prepare();
+
+        String request = "" +
+                "POST /one?" + query1 + " HTTP/1.1\r\n" +
+                "Host: localhost\r\n" +
+                "Content-Type: application/x-www-form-urlencoded\r\n" +
+                "Content-Length: " + form.length() + "\r\n" +
+                "Connection: close\r\n" +
+                "\r\n" +
+                form;
+        String response = connector.getResponses(request);
+        Assert.assertTrue(response, response.startsWith("HTTP/1.1 200"));
+    }
+
+    // TODO: add multipart tests
+}
diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DispatcherTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DispatcherTest.java
index 333af4b..55357bd 100644
--- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DispatcherTest.java
+++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DispatcherTest.java
@@ -18,18 +18,11 @@
 
 package org.eclipse.jetty.servlet;
 
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.assertThat;
-
 import java.io.IOException;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.EnumSet;
-import java.util.HashMap;
 import java.util.List;
-
 import javax.servlet.DispatcherType;
 import javax.servlet.Filter;
 import javax.servlet.FilterChain;
@@ -49,9 +42,8 @@ import javax.servlet.http.HttpServletRequestWrapper;
 import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpServletResponseWrapper;
 
-import junit.framework.Assert;
-
 import org.eclipse.jetty.server.Dispatcher;
+import org.eclipse.jetty.server.HttpConfiguration;
 import org.eclipse.jetty.server.LocalConnector;
 import org.eclipse.jetty.server.Server;
 import org.eclipse.jetty.server.handler.ContextHandler;
@@ -61,9 +53,16 @@ import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
 import org.eclipse.jetty.util.TypeUtil;
 import org.eclipse.jetty.util.UrlEncoded;
 import org.junit.After;
+import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.startsWith;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
 public class DispatcherTest
 {
     private Server _server;
@@ -71,14 +70,15 @@ public class DispatcherTest
     private ContextHandlerCollection _contextCollection;
     private ServletContextHandler _contextHandler;
     private ResourceHandler _resourceHandler;
-    
+
     @Before
     public void init() throws Exception
     {
         _server = new Server();
-        _server.setSendServerVersion(false);
-        _connector = new LocalConnector();
-        
+        _connector = new LocalConnector(_server);
+        _connector.getConnectionFactory(HttpConfiguration.ConnectionFactory.class).getHttpConfiguration().setSendServerVersion(false);
+        _connector.getConnectionFactory(HttpConfiguration.ConnectionFactory.class).getHttpConfiguration().setSendDateHeader(false);
+
         _contextCollection = new ContextHandlerCollection();
         _contextHandler = new ServletContextHandler();
         _contextHandler.setContextPath("/context");
@@ -113,14 +113,13 @@ public class DispatcherTest
             "Content-Length: 0\r\n"+
             "\r\n";
 
-        String responses = _connector.getResponses("GET /context/ForwardServlet?do=assertforward&do=more&test=1 HTTP/1.1\n" + "Host: localhost\n\n");
+        String responses = _connector.getResponses("GET /context/ForwardServlet?do=assertforward&do=more&test=1 HTTP/1.0\n\n");
 
         assertEquals(expected, responses);
     }
-    
- 
 
-   @Test public void testForwardNonUTF8() throws Exception
+   @Test 
+   public void testForwardNonUTF8() throws Exception
     {
         _contextHandler.addServlet(ForwardNonUTF8Servlet.class, "/ForwardServlet/*");
         _contextHandler.addServlet(AssertNonUTF8ForwardServlet.class, "/AssertForwardServlet/*");
@@ -130,7 +129,7 @@ public class DispatcherTest
             "Content-Type: text/html\r\n"+
             "Content-Length: 0\r\n"+
             "\r\n";
-        String responses = _connector.getResponses("GET /context/ForwardServlet?do=assertforward&foreign=%d2%e5%ec%ef%e5%f0%e0%f2%f3%f0%e0&test=1 HTTP/1.1\n" + "Host: localhost\n\n");
+        String responses = _connector.getResponses("GET /context/ForwardServlet?do=assertforward&foreign=%d2%e5%ec%ef%e5%f0%e0%f2%f3%f0%e0&test=1 HTTP/1.0\n\n");
 
         assertEquals(expected, responses);
     }
@@ -151,7 +150,7 @@ public class DispatcherTest
             "/x x\r\n"+
             "/context/EchoURI/x%20x;a=1\r\n";
 
-        String responses = _connector.getResponses("GET /context/ForwardServlet;ignore=true?do=req.echo&uri=EchoURI%2Fx%2520x%3Ba=1%3Fb=2 HTTP/1.1\n" + "Host: localhost\n\n");
+        String responses = _connector.getResponses("GET /context/ForwardServlet;ignore=true?do=req.echo&uri=EchoURI%2Fx%2520x%3Ba=1%3Fb=2 HTTP/1.0\n\n");
 
         assertEquals(expected, responses);
     }
@@ -167,7 +166,7 @@ public class DispatcherTest
             "Content-Length: 0\r\n"+
             "\r\n";
 
-        String responses = _connector.getResponses("GET /context/IncludeServlet?do=assertinclude&do=more&test=1 HTTP/1.1\n" + "Host: localhost\n\n");
+        String responses = _connector.getResponses("GET /context/IncludeServlet?do=assertinclude&do=more&test=1 HTTP/1.0\n\n");
 
         assertEquals(expected, responses);
     }
@@ -184,7 +183,7 @@ public class DispatcherTest
             "Content-Length: 0\r\n"+
             "\r\n";
 
-        String responses = _connector.getResponses("GET /context/ForwardServlet/forwardpath?do=include HTTP/1.1\n" + "Host: localhost\n\n");
+        String responses = _connector.getResponses("GET /context/ForwardServlet/forwardpath?do=include HTTP/1.0\n\n");
 
         assertEquals(expected, responses);
     }
@@ -196,19 +195,15 @@ public class DispatcherTest
         _contextHandler.addServlet(ForwardServlet.class, "/ForwardServlet/*");
         _contextHandler.addServlet(AssertIncludeForwardServlet.class, "/AssertIncludeForwardServlet/*");
 
-
         String expected=
             "HTTP/1.1 200 OK\r\n"+
-            "Transfer-Encoding: chunked\r\n"+
-            "\r\n"+
-            "0\r\n"+
             "\r\n";
 
-        String responses = _connector.getResponses("GET /context/IncludeServlet/includepath?do=forward HTTP/1.1\n" + "Host: localhost\n\n");
+        String responses = _connector.getResponses("GET /context/IncludeServlet/includepath?do=forward HTTP/1.0\n\n");
 
         assertEquals(expected, responses);
     }
-    
+
     @Test
     public void testServletForward() throws Exception
     {
@@ -225,7 +220,7 @@ public class DispatcherTest
 
         assertEquals(expected, responses);
     }
-    
+
     @Test
     public void testServletForwardDotDot() throws Exception
     {
@@ -271,58 +266,58 @@ public class DispatcherTest
 
     @Test
     public void testWorkingResourceHandler() throws Exception
-    {        
+    {
         String responses = _connector.getResponses("GET /resource/content.txt HTTP/1.0\n" + "Host: localhost\n\n");
-        
+
         assertTrue(responses.contains("content goes here")); // from inside the context.txt file
     }
-    
+
     @Test
     public void testIncludeToResourceHandler() throws Exception
     {
         _contextHandler.addServlet(DispatchToResourceServlet.class, "/resourceServlet/*");
 
-        String responses = _connector.getResponses("GET /context/resourceServlet/content.txt?do=include HTTP/1.0\n" + "Host: localhost\n\n");      
-        
+        String responses = _connector.getResponses("GET /context/resourceServlet/content.txt?do=include HTTP/1.0\n" + "Host: localhost\n\n");
+
         // from inside the context.txt file
         Assert.assertNotNull(responses);
-        
+
         assertTrue(responses.contains("content goes here"));
     }
-    
+
     @Test
     public void testForwardToResourceHandler() throws Exception
     {
         _contextHandler.addServlet(DispatchToResourceServlet.class, "/resourceServlet/*");
 
-        String responses = _connector.getResponses("GET /context/resourceServlet/content.txt?do=forward HTTP/1.0\n" + "Host: localhost\n\n");      
-        
+        String responses = _connector.getResponses("GET /context/resourceServlet/content.txt?do=forward HTTP/1.0\n" + "Host: localhost\n\n");
+
         // from inside the context.txt file
         assertTrue(responses.contains("content goes here"));
     }
-    
+
     @Test
     public void testWrappedIncludeToResourceHandler() throws Exception
     {
         _contextHandler.addServlet(DispatchToResourceServlet.class, "/resourceServlet/*");
 
-        String responses = _connector.getResponses("GET /context/resourceServlet/content.txt?do=include&wrapped=true HTTP/1.0\n" + "Host: localhost\n\n");      
-        
+        String responses = _connector.getResponses("GET /context/resourceServlet/content.txt?do=include&wrapped=true HTTP/1.0\n" + "Host: localhost\n\n");
+
         // from inside the context.txt file
         assertTrue(responses.contains("content goes here"));
     }
-    
+
     @Test
     public void testWrappedForwardToResourceHandler() throws Exception
     {
         _contextHandler.addServlet(DispatchToResourceServlet.class, "/resourceServlet/*");
 
-        String responses = _connector.getResponses("GET /context/resourceServlet/content.txt?do=forward&wrapped=true HTTP/1.0\n" + "Host: localhost\n\n");      
-        
+        String responses = _connector.getResponses("GET /context/resourceServlet/content.txt?do=forward&wrapped=true HTTP/1.0\n" + "Host: localhost\n\n");
+
         // from inside the context.txt file
         assertTrue(responses.contains("content goes here"));
     }
-    
+
     @Test
     public void testForwardFilterToRogerServlet() throws Exception
     {
@@ -334,15 +329,15 @@ public class DispatcherTest
         String rogerResponse = _connector.getResponses("GET /context/ HTTP/1.0\n" + "Host: localhost\n\n");
 
         String echoResponse = _connector.getResponses("GET /context/foo?echo=echoText HTTP/1.0\n" + "Host: localhost\n\n");
-        
+
         String rechoResponse = _connector.getResponses("GET /context/?echo=echoText HTTP/1.0\n" + "Host: localhost\n\n");
-                    
+
         assertTrue(rogerResponse.contains("Roger That!"));
         assertTrue(echoResponse.contains("echoText"));
         assertTrue(rechoResponse.contains("txeTohce"));
     }
-    
-    
+
+
     public static class ForwardServlet extends HttpServlet implements Servlet
     {
         protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
@@ -353,7 +348,6 @@ public class DispatcherTest
                 dispatcher = getServletContext().getRequestDispatcher("/IncludeServlet/includepath?do=assertforwardinclude");
             else if(request.getParameter("do").equals("assertincludeforward"))
                 dispatcher = getServletContext().getRequestDispatcher("/AssertIncludeForwardServlet/assertpath?do=end");
-          
             else if(request.getParameter("do").equals("assertforward"))
                 dispatcher = getServletContext().getRequestDispatcher("/AssertForwardServlet?do=end&do=the");
             else if(request.getParameter("do").equals("ctx.echo"))
@@ -363,7 +357,7 @@ public class DispatcherTest
             dispatcher.forward(request, response);
         }
     }
-    
+
     
     public static class ForwardNonUTF8Servlet extends HttpServlet implements Servlet
     {
@@ -377,9 +371,9 @@ public class DispatcherTest
     }
     
     /*
-     * Forward filter works with roger, echo and reverse echo servlets to test various 
+     * Forward filter works with roger, echo and reverse echo servlets to test various
      * forwarding bits using filters.
-     * 
+     *
      * when there is an echo parameter and the path info is / it forwards to the reverse echo
      * anything else in the pathInfo and it sends straight to the echo servlet...otherwise its
      * all roger servlet
@@ -387,7 +381,7 @@ public class DispatcherTest
     public static class ForwardFilter implements Filter
     {
         ServletContext servletContext;
-        
+
         public void init(FilterConfig filterConfig) throws ServletException
         {
             servletContext = filterConfig.getServletContext().getContext("/context");
@@ -395,16 +389,16 @@ public class DispatcherTest
 
         public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
         {
-            
+
             if ( servletContext == null || !(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse))
             {
                 chain.doFilter(request,response);
                 return;
             }
-            
+
             HttpServletRequest req = (HttpServletRequest)request;
             HttpServletResponse resp = (HttpServletResponse)response;
-             
+
             if ( req.getParameter("echo") != null && "/".equals(req.getPathInfo()))
             {
                 RequestDispatcher dispatcher = servletContext.getRequestDispatcher("/recho");
@@ -424,11 +418,11 @@ public class DispatcherTest
 
         public void destroy()
         {
-            
-        }       
+
+        }
     }
-    
-    
+
+
     public static class DispatchServletServlet extends HttpServlet implements Servlet
     {
         @Override
@@ -449,7 +443,7 @@ public class DispatcherTest
                 else
                     response.sendError(404);
             }
-            
+
         }
     }
 
@@ -470,7 +464,7 @@ public class DispatcherTest
             dispatcher.include(request, response);
         }
     }
-    
+
     public static class RogerThatServlet extends GenericServlet
     {
         @Override
@@ -486,7 +480,7 @@ public class DispatcherTest
         public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException
         {
             String echoText = req.getParameter("echo");
-            
+
             if ( echoText == null )
             {
                 throw new ServletException("echo is a required parameter");
@@ -497,14 +491,14 @@ public class DispatcherTest
             }
         }
     }
-    
+
     public static class ReserveEchoServlet extends GenericServlet
     {
         @Override
         public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException
         {
             String echoText = req.getParameter("echo");
-            
+
             if ( echoText == null )
             {
                 throw new ServletException("echo is a required parameter");
@@ -515,18 +509,18 @@ public class DispatcherTest
             }
         }
     }
-    
+
     public static class DispatchToResourceServlet extends HttpServlet implements Servlet
     {
         @Override
         public void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException
         {
-            ServletContext targetContext = getServletConfig().getServletContext().getContext("/resource");        
-        
+            ServletContext targetContext = getServletConfig().getServletContext().getContext("/resource");
+
             RequestDispatcher dispatcher = targetContext.getRequestDispatcher(req.getPathInfo());
-            
+
             if ( "true".equals(req.getParameter("wrapped")))
-            {                
+            {
                 if (req.getParameter("do").equals("forward"))
                 {
                     dispatcher.forward(new HttpServletRequestWrapper(req),new HttpServletResponseWrapper(res));
@@ -557,7 +551,7 @@ public class DispatcherTest
             }
         }
     }
-    
+
     public static class EchoURIServlet extends HttpServlet implements Servlet
     {
         protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
@@ -570,7 +564,7 @@ public class DispatcherTest
             response.getOutputStream().println(request.getRequestURI());
         }
     }
-    
+
     public static class AssertForwardServlet extends HttpServlet implements Servlet
     {
         protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
@@ -621,10 +615,14 @@ public class DispatcherTest
             assertEquals(null, request.getPathInfo());
             assertEquals(null, request.getPathTranslated());
             
-            UrlEncoded query = new UrlEncoded(request.getQueryString());
+            UrlEncoded query = new UrlEncoded();
+            query.decode(request.getQueryString());
             assertThat(query.getString("do"), is("end"));
+            
             // Russian for "selected=Temperature"
-            String russian = new UrlEncoded(query.getString("else")).encode();
+            UrlEncoded q2=new UrlEncoded();
+            q2.decode(query.getString("else"));
+            String russian = q2.encode();
             assertThat(russian, is("%D0%B2%D1%8B%D0%B1%D1%80%D0%B0%D0%BD%D0%BE=%D0%A2%D0%B5%D0%BC%D0%BF%D0%B5%D1%80%D0%B0%D1%82%D1%83%D1%80%D0%B0"));
             assertThat(query.getString("test"), is("1"));
             assertThat(query.containsKey("foreign"), is(true));
diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ErrorPageTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ErrorPageTest.java
index a9466e5..363f5c5 100644
--- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ErrorPageTest.java
+++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ErrorPageTest.java
@@ -18,7 +18,6 @@
 
 package org.eclipse.jetty.servlet;
 
-import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertThat;
 
 import java.io.IOException;
@@ -36,7 +35,6 @@ import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.StdErrLog;
 import org.hamcrest.Matchers;
 import org.junit.After;
-import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -52,10 +50,9 @@ public class ErrorPageTest
     public void init() throws Exception
     {
         _server = new Server();
-        _connector = new LocalConnector();
+        _connector = new LocalConnector(_server);
         ServletContextHandler context = new ServletContextHandler(ServletContextHandler.NO_SECURITY|ServletContextHandler.NO_SESSIONS);
 
-        _server.setSendServerVersion(false);
         _server.addConnector(_connector);
         _server.setHandler(context);
 
@@ -103,8 +100,8 @@ public class ErrorPageTest
         assertThat(response,Matchers.containsString("HTTP/1.1 500 Server Error"));
         assertThat(response,Matchers.containsString("ERROR_PAGE: /TestException"));
         assertThat(response,Matchers.containsString("ERROR_CODE: 500"));
-        assertThat(response,Matchers.containsString("ERROR_EXCEPTION: java.lang.IllegalStateException"));
-        assertThat(response,Matchers.containsString("ERROR_EXCEPTION_TYPE: class java.lang.IllegalStateException"));
+        assertThat(response,Matchers.containsString("ERROR_EXCEPTION: javax.servlet.ServletException: java.lang.IllegalStateException"));
+        assertThat(response,Matchers.containsString("ERROR_EXCEPTION_TYPE: class javax.servlet.ServletException"));
         assertThat(response,Matchers.containsString("ERROR_SERVLET: org.eclipse.jetty.servlet.ErrorPageTest$FailServlet-"));
         assertThat(response,Matchers.containsString("ERROR_REQUEST_URI: /fail/exception"));
     }
diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/HolderTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/HolderTest.java
index 7d389e9..613b325 100644
--- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/HolderTest.java
+++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/HolderTest.java
@@ -38,37 +38,45 @@
 
 package org.eclipse.jetty.servlet;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
 import java.util.Collections;
 import java.util.Set;
 
-import javax.servlet.Registration;
 import javax.servlet.ServletRegistration;
-import org.junit.Test;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
+import org.eclipse.jetty.servlet.BaseHolder.Source;
+import org.junit.Test;
 
 /**
  * @version $Rev$ $Date$
  */
-public class HolderTest {
-    
+public class HolderTest
+{
+
     @Test
-    public void testInitParams() throws Exception {
-        ServletHolder holder = new ServletHolder(Holder.Source.JAVAX_API);
+    public void testInitParams() throws Exception
+    {
+        ServletHolder holder = new ServletHolder(Source.JAVAX_API);
         ServletRegistration reg = holder.getRegistration();
-        try {
+        try
+        {
             reg.setInitParameter(null, "foo");
             fail("null name accepted");
-        } catch (IllegalArgumentException e) {
         }
-        try {
+        catch (IllegalArgumentException e)
+        {
+        }
+        try
+        {
             reg.setInitParameter("foo", null);
             fail("null value accepted");
-        } catch (IllegalArgumentException e) {
+        }
+        catch (IllegalArgumentException e)
+        {
         }
         reg.setInitParameter("foo", "bar");
         assertFalse(reg.setInitParameter("foo", "foo"));
@@ -76,15 +84,21 @@ public class HolderTest {
         Set<String> clash = reg.setInitParameters(Collections.singletonMap("foo", "bax"));
         assertTrue("should be one clash", clash != null && clash.size() == 1);
 
-        try {
-            reg.setInitParameters(Collections.singletonMap((String)null, "bax"));
+        try
+        {
+            reg.setInitParameters(Collections.singletonMap((String) null, "bax"));
             fail("null name in map accepted");
-        } catch (IllegalArgumentException e) {
         }
-        try {
-            reg.setInitParameters(Collections.singletonMap("foo", (String)null));
+        catch (IllegalArgumentException e)
+        {
+        }
+        try
+        {
+            reg.setInitParameters(Collections.singletonMap("foo", (String) null));
             fail("null value in map accepted");
-        } catch (IllegalArgumentException e) {
+        }
+        catch (IllegalArgumentException e)
+        {
         }
 
         Set<String> clash2 = reg.setInitParameters(Collections.singletonMap("FOO", "bax"));
diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/InvokerTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/InvokerTest.java
index 326f2a4..c0a0fa8 100644
--- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/InvokerTest.java
+++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/InvokerTest.java
@@ -28,6 +28,7 @@ import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
+import org.eclipse.jetty.server.HttpConfiguration;
 import org.eclipse.jetty.server.LocalConnector;
 import org.eclipse.jetty.server.Server;
 import org.junit.After;
@@ -46,10 +47,11 @@ public class InvokerTest
     public void init() throws Exception
     {
         _server = new Server();
-        _connector = new LocalConnector();
+        _connector = new LocalConnector(_server);
+        _connector.getConnectionFactory(HttpConfiguration.ConnectionFactory.class).getHttpConfiguration().setSendServerVersion(false);
+        _connector.getConnectionFactory(HttpConfiguration.ConnectionFactory.class).getHttpConfiguration().setSendDateHeader(false);
         ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
 
-        _server.setSendServerVersion(false);
         _server.addConnector(_connector);
         _server.setHandler(context);
 
diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/RequestHeadersTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/RequestHeadersTest.java
new file mode 100644
index 0000000..784519a
--- /dev/null
+++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/RequestHeadersTest.java
@@ -0,0 +1,125 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlet;
+
+import static org.hamcrest.Matchers.is;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.net.HttpURLConnection;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.util.IO;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class RequestHeadersTest
+{
+    @SuppressWarnings("serial")
+    private static class RequestHeaderServlet extends HttpServlet
+    {
+        @Override
+        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+        {
+            resp.setContentType("text/plain");
+            PrintWriter out = resp.getWriter();
+            out.printf("X-Camel-Type = %s",req.getHeader("X-Camel-Type"));
+        }
+    }
+
+    private static Server server;
+    private static ServerConnector connector;
+    private static URI serverUri;
+
+    @BeforeClass
+    public static void startServer() throws Exception
+    {
+        // Configure Server
+        server = new Server();
+        connector = new ServerConnector(server);
+        server.addConnector(connector);
+
+        ServletContextHandler context = new ServletContextHandler();
+        context.setContextPath("/");
+        server.setHandler(context);
+
+        // Serve capture servlet
+        context.addServlet(new ServletHolder(new RequestHeaderServlet()),"/*");
+
+        // Start Server
+        server.start();
+
+        String host = connector.getHost();
+        if (host == null)
+        {
+            host = "localhost";
+        }
+        int port = connector.getLocalPort();
+        serverUri = new URI(String.format("http://%s:%d/",host,port));
+    }
+
+    @AfterClass
+    public static void stopServer()
+    {
+        try
+        {
+            server.stop();
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace(System.err);
+        }
+    }
+
+    @Test
+    public void testGetLowercaseHeader() throws IOException
+    {
+        HttpURLConnection http = null;
+        try
+        {
+            http = (HttpURLConnection)serverUri.toURL().openConnection();
+            // Set header in all lowercase
+            http.setRequestProperty("x-camel-type","bactrian");
+            
+            try (InputStream in = http.getInputStream())
+            {
+                String resp = IO.toString(in, StandardCharsets.UTF_8);
+                Assert.assertThat("Response", resp, is("X-Camel-Type = bactrian"));
+            }
+        }
+        finally
+        {
+            if (http != null)
+            {
+                http.disconnect();
+            }
+        }
+    }
+}
diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/RequestURITest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/RequestURITest.java
new file mode 100644
index 0000000..75f7e49
--- /dev/null
+++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/RequestURITest.java
@@ -0,0 +1,238 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlet;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.net.Socket;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.hamcrest.Matchers;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+ at RunWith(Parameterized.class)
+public class RequestURITest
+{
+    @Parameters(name = "rawpath: \"{0}\"")
+    public static List<String[]> data()
+    {
+        List<String[]> ret = new ArrayList<>();
+
+        ret.add(new String[] { "/hello", "/hello", null });
+        ret.add(new String[] { "/hello%20world", "/hello%20world", null });
+        ret.add(new String[] { "/hello;world", "/hello;world", null });
+        ret.add(new String[] { "/hello:world", "/hello:world", null });
+        ret.add(new String[] { "/hello!world", "/hello!world", null });
+        ret.add(new String[] { "/hello?world", "/hello", "world" });
+        ret.add(new String[] { "/hello?type=world", "/hello", "type=world" });
+        ret.add(new String[] { "/hello?type=wo&rld", "/hello", "type=wo&rld" });
+        ret.add(new String[] { "/hello?type=wo%20rld", "/hello", "type=wo%20rld" });
+        ret.add(new String[] { "/hello?type=wo+rld", "/hello", "type=wo+rld" });
+        ret.add(new String[] { "/It%27s%20me%21", "/It%27s%20me%21", null });
+        // try some slash encoding (with case preservation tests)
+        ret.add(new String[] { "/hello%2fworld", "/hello%2fworld", null });
+        ret.add(new String[] { "/hello%2Fworld", "/hello%2Fworld", null });
+        ret.add(new String[] { "/%2f%2Fhello%2Fworld", "/%2f%2Fhello%2Fworld", null });
+        // try some "?" encoding (should not see as query string)
+        ret.add(new String[] { "/hello%3Fworld", "/hello%3Fworld", null });
+        // try some strange encodings (should preserve them)
+        ret.add(new String[] { "/hello%252Fworld", "/hello%252Fworld", null });
+        ret.add(new String[] { "/hello%u0025world", "/hello%u0025world", null });
+        ret.add(new String[] { "/hello-euro-%E2%82%AC", "/hello-euro-%E2%82%AC", null });
+        ret.add(new String[] { "/hello-euro?%E2%82%AC", "/hello-euro","%E2%82%AC" });
+        // test the ascii control characters (just for completeness)
+        for (int i = 0x0; i < 0x1f; i++)
+        {
+            String raw = String.format("/hello%%%02Xworld",i);
+            ret.add(new String[] { raw, raw, null });
+        }
+
+        return ret;
+    }
+
+    @SuppressWarnings("serial")
+    public static class RequestUriServlet extends HttpServlet
+    {
+        @Override
+        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+        {
+            resp.setContentType("text/plain");
+            PrintWriter out = resp.getWriter();
+            out.println("RequestURI: " + req.getRequestURI());
+            out.println("QueryString: " + req.getQueryString());
+            out.print("FullURI: " + req.getRequestURI());
+            if (req.getQueryString() != null)
+            {
+                out.print('?');
+                out.print(req.getQueryString());
+            }
+            out.println();
+        }
+    }
+
+    private static Server server;
+    private static URI serverURI;
+
+    @Parameter(value = 0)
+    public String rawpath;
+    @Parameter(value = 1)
+    public String expectedReqUri;
+    @Parameter(value = 2)
+    public String expectedQuery;
+
+    @BeforeClass
+    public static void startServer() throws Exception
+    {
+        server = new Server();
+        ServerConnector connector = new ServerConnector(server);
+        connector.setPort(0);
+        server.addConnector(connector);
+
+        ServletContextHandler context = new ServletContextHandler();
+        context.setContextPath("/");
+        server.setHandler(context);
+
+        context.addServlet(RequestUriServlet.class,"/*");
+
+        server.start();
+
+        String host = connector.getHost();
+        if (host == null)
+        {
+            host = "localhost";
+        }
+        int port = connector.getLocalPort();
+        serverURI = new URI(String.format("http://%s:%d/",host,port));
+    }
+
+    @AfterClass
+    public static void stopServer()
+    {
+        try
+        {
+            server.stop();
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace(System.err);
+        }
+    }
+
+    protected Socket newSocket(String host, int port) throws Exception
+    {
+        Socket socket = new Socket(host,port);
+        socket.setSoTimeout(10000);
+        socket.setTcpNoDelay(true);
+        socket.setSoLinger(false,0);
+        return socket;
+    }
+
+    /**
+     * Read entire response from the client. Close the output.
+     * 
+     * @param client
+     *            Open client socket.
+     * @return The response string.
+     * @throws IOException
+     *             in case of I/O problems
+     */
+    protected static String readResponse(Socket client) throws IOException
+    {
+
+        StringBuilder sb = new StringBuilder();
+        try (BufferedReader br = new BufferedReader(new InputStreamReader(client.getInputStream())))
+        {
+            String line;
+
+            while ((line = br.readLine()) != null)
+            {
+                sb.append(line);
+                sb.append('\n');
+            }
+
+            return sb.toString();
+        }
+        catch (IOException e)
+        {
+            System.err.println(e + " while reading '" + sb + "'");
+            throw e;
+        }
+    }
+
+    @Test
+    public void testGetRequestURI_HTTP10() throws Exception
+    {
+        try (Socket client = newSocket(serverURI.getHost(),serverURI.getPort()))
+        {
+            OutputStream os = client.getOutputStream();
+
+            String request = String.format("GET %s HTTP/1.0\r\n\r\n",rawpath);
+            os.write(request.getBytes(StandardCharsets.ISO_8859_1));
+            os.flush();
+
+            // Read the response.
+            String response = readResponse(client);
+
+            // TODO: is HTTP/1.1 response appropriate for a HTTP/1.0 request?
+            Assert.assertThat(response,Matchers.containsString("HTTP/1.1 200 OK"));
+            Assert.assertThat(response,Matchers.containsString("RequestURI: " + expectedReqUri));
+            Assert.assertThat(response,Matchers.containsString("QueryString: " + expectedQuery));
+        }
+    }
+
+    @Test
+    public void testGetRequestURI_HTTP11() throws Exception
+    {
+        try (Socket client = newSocket(serverURI.getHost(),serverURI.getPort()))
+        {
+            OutputStream os = client.getOutputStream();
+
+            String request = String.format("GET %s HTTP/1.1\r\nHost: %s\r\nConnection: close\r\n\r\n",rawpath,serverURI.getHost());
+            os.write(request.getBytes(StandardCharsets.ISO_8859_1));
+            os.flush();
+
+            // Read the response.
+            String response = readResponse(client);
+
+            Assert.assertThat(response,Matchers.containsString("HTTP/1.1 200 OK"));
+            Assert.assertThat(response,Matchers.containsString("RequestURI: " + expectedReqUri));
+            Assert.assertThat(response,Matchers.containsString("QueryString: " + expectedQuery));
+        }
+    }
+}
diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ResponseHeadersTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ResponseHeadersTest.java
new file mode 100644
index 0000000..1fe9b3d
--- /dev/null
+++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ResponseHeadersTest.java
@@ -0,0 +1,167 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlet;
+
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.startsWith;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.SocketAddress;
+import java.net.URI;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.util.IO;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class ResponseHeadersTest
+{
+    /** Pretend to be a WebSocket Upgrade (not real) */
+    @SuppressWarnings("serial")
+    private static class SimulateUpgradeServlet extends HttpServlet
+    {
+        @Override
+        protected void doGet(HttpServletRequest req, HttpServletResponse response) throws ServletException, IOException
+        {
+            response.setHeader("Upgrade","WebSocket");
+            response.addHeader("Connection","Upgrade");
+            response.addHeader("Sec-WebSocket-Accept","123456789==");
+
+            response.setStatus(HttpServletResponse.SC_SWITCHING_PROTOCOLS);
+        }
+    }
+
+    private static Server server;
+    private static ServerConnector connector;
+    private static URI serverUri;
+
+    @BeforeClass
+    public static void startServer() throws Exception
+    {
+        // Configure Server
+        server = new Server();
+        connector = new ServerConnector(server);
+        server.addConnector(connector);
+
+        ServletContextHandler context = new ServletContextHandler();
+        context.setContextPath("/");
+        server.setHandler(context);
+
+        // Serve capture servlet
+        context.addServlet(new ServletHolder(new SimulateUpgradeServlet()),"/*");
+
+        // Start Server
+        server.start();
+
+        String host = connector.getHost();
+        if (host == null)
+        {
+            host = "localhost";
+        }
+        int port = connector.getLocalPort();
+        serverUri = new URI(String.format("http://%s:%d/",host,port));
+    }
+
+    @AfterClass
+    public static void stopServer()
+    {
+        try
+        {
+            server.stop();
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace(System.err);
+        }
+    }
+
+    @Test
+    public void testResponseHeaderFormat() throws IOException
+    {
+        Socket socket = new Socket();
+        SocketAddress endpoint = new InetSocketAddress(serverUri.getHost(),serverUri.getPort());
+        socket.connect(endpoint);
+
+        StringBuilder req = new StringBuilder();
+        req.append("GET / HTTP/1.1\r\n");
+        req.append(String.format("Host: %s:%d\r\n",serverUri.getHost(),serverUri.getPort()));
+        req.append("\r\n");
+
+        OutputStream out = null;
+        InputStream in = null;
+        try
+        {
+            out = socket.getOutputStream();
+            in = socket.getInputStream();
+
+            // Write request
+            out.write(req.toString().getBytes());
+            out.flush();
+
+            // Read response
+            String respHeader = readResponseHeader(in);
+
+            // Now test for properly formatted HTTP Response Headers.
+            Assert.assertThat("Response Code",respHeader,startsWith("HTTP/1.1 101 Switching Protocols"));
+            Assert.assertThat("Response Header Upgrade",respHeader,containsString("Upgrade: WebSocket\r\n"));
+            Assert.assertThat("Response Header Connection",respHeader,containsString("Connection: Upgrade\r\n"));
+        }
+        finally
+        {
+            IO.close(in);
+            IO.close(out);
+            socket.close();
+        }
+    }
+
+    private String readResponseHeader(InputStream in) throws IOException
+    {
+        InputStreamReader isr = new InputStreamReader(in);
+        BufferedReader reader = new BufferedReader(isr);
+        StringBuilder header = new StringBuilder();
+        // Read the response header
+        String line = reader.readLine();
+        Assert.assertNotNull(line);
+        Assert.assertThat(line,startsWith("HTTP/1.1 "));
+        header.append(line).append("\r\n");
+        while ((line = reader.readLine()) != null)
+        {
+            if (line.trim().length() == 0)
+            {
+                break;
+            }
+            header.append(line).append("\r\n");
+        }
+        return header.toString();
+    }
+}
diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/SSLAsyncIOServletTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/SSLAsyncIOServletTest.java
new file mode 100644
index 0000000..12f8ecc
--- /dev/null
+++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/SSLAsyncIOServletTest.java
@@ -0,0 +1,179 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlet;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.Socket;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Random;
+import javax.servlet.AsyncContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.WriteListener;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.toolchain.test.http.SimpleHttpParser;
+import org.eclipse.jetty.toolchain.test.http.SimpleHttpResponse;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+ at RunWith(Parameterized.class)
+public class SSLAsyncIOServletTest
+{
+    @Parameterized.Parameters
+    public static Collection<SslContextFactory[]> parameters()
+    {
+        return Arrays.asList(new SslContextFactory[]{null}, new SslContextFactory[]{new SslContextFactory()});
+    }
+
+    private Server server;
+    private ServerConnector connector;
+    private SslContextFactory sslContextFactory;
+    private String contextPath;
+    private String servletPath;
+
+    public SSLAsyncIOServletTest(SslContextFactory sslContextFactory)
+    {
+        this.sslContextFactory = sslContextFactory;
+        if (sslContextFactory != null)
+        {
+            sslContextFactory.setEndpointIdentificationAlgorithm("");
+            sslContextFactory.setKeyStorePath("src/test/resources/keystore.jks");
+            sslContextFactory.setKeyStorePassword("storepwd");
+            sslContextFactory.setTrustStorePath("src/test/resources/truststore.jks");
+            sslContextFactory.setTrustStorePassword("storepwd");
+        }
+    }
+
+    public void prepare(HttpServlet servlet) throws Exception
+    {
+        server = new Server();
+
+        connector = new ServerConnector(server, sslContextFactory);
+        server.addConnector(connector);
+
+        contextPath = "/context";
+        ServletContextHandler context = new ServletContextHandler(server, contextPath, true, false);
+        servletPath = "/servlet";
+        context.addServlet(new ServletHolder(servlet), servletPath);
+
+        server.start();
+    }
+
+    @After
+    public void dispose() throws Exception
+    {
+        server.stop();
+    }
+
+    @Test
+    public void testAsyncIOWritesWithAggregation() throws Exception
+    {
+        Random random = new Random();
+        String chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
+        final byte[] content = new byte[50000];
+        for (int i = 0; i < content.length; ++i)
+            content[i] = (byte)chars.charAt(random.nextInt(chars.length()));
+
+        prepare(new HttpServlet()
+        {
+            @Override
+            protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+            {
+                final AsyncContext asyncContext = request.startAsync();
+                asyncContext.setTimeout(0);
+                final int bufferSize = 4096;
+                response.setBufferSize(bufferSize);
+                response.getOutputStream().setWriteListener(new WriteListener()
+                {
+                    private int writes;
+                    private int written;
+
+                    @Override
+                    public void onWritePossible() throws IOException
+                    {
+                        ServletOutputStream output = asyncContext.getResponse().getOutputStream();
+                        do
+                        {
+                            int toWrite = content.length - written;
+                            if (toWrite == 0)
+                            {
+                                asyncContext.complete();
+                                return;
+                            }
+
+                            toWrite = Math.min(toWrite, bufferSize);
+
+                            // Perform a write that is smaller than the buffer size to
+                            // trigger the condition where the bytes are aggregated.
+                            if (writes == 1)
+                                toWrite -= 16;
+
+                            output.write(content, written, toWrite);
+                            ++writes;
+                            written += toWrite;
+                        }
+                        while (output.isReady());
+                    }
+
+                    @Override
+                    public void onError(Throwable t)
+                    {
+                        asyncContext.complete();
+                    }
+                });
+            }
+        });
+
+        try (Socket client = newClient())
+        {
+            String request = "" +
+                    "GET " + contextPath + servletPath + " HTTP/1.1\r\n" +
+                    "Host: localhost\r\n" +
+                    "\r\n";
+            OutputStream output = client.getOutputStream();
+            output.write(request.getBytes("UTF-8"));
+            output.flush();
+
+            BufferedReader input = new BufferedReader(new InputStreamReader(client.getInputStream(), "UTF-8"));
+            SimpleHttpParser parser = new SimpleHttpParser();
+            SimpleHttpResponse response = parser.readResponse(input);
+            Assert.assertEquals("200", response.getCode());
+            Assert.assertArrayEquals(content, response.getBody().getBytes("UTF-8"));
+        }
+    }
+
+    private Socket newClient() throws IOException
+    {
+        return sslContextFactory == null ? new Socket("localhost", connector.getLocalPort())
+                : sslContextFactory.getSslContext().getSocketFactory().createSocket("localhost", connector.getLocalPort());
+    }
+}
diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletContextHandlerTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletContextHandlerTest.java
index 850a5ab..031bdc1 100644
--- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletContextHandlerTest.java
+++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletContextHandlerTest.java
@@ -18,27 +18,41 @@
 
 package org.eclipse.jetty.servlet;
 
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.hamcrest.Matchers.nullValue;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
 
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
 
+import javax.servlet.Servlet;
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import junit.framework.AssertionFailedError;
-
 import org.eclipse.jetty.security.ConstraintSecurityHandler;
 import org.eclipse.jetty.security.SecurityHandler;
 import org.eclipse.jetty.server.LocalConnector;
+import org.eclipse.jetty.server.Request;
 import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.handler.AbstractHandler;
 import org.eclipse.jetty.server.handler.AbstractHandlerContainer;
 import org.eclipse.jetty.server.handler.ContextHandler;
 import org.eclipse.jetty.server.handler.ContextHandlerCollection;
+import org.eclipse.jetty.server.handler.HandlerList;
+import org.eclipse.jetty.server.handler.ResourceHandler;
 import org.eclipse.jetty.server.session.SessionHandler;
+import org.hamcrest.Matchers;
 import org.junit.After;
+import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -46,23 +60,26 @@ public class ServletContextHandlerTest
 {
     private Server _server;
     private LocalConnector _connector;
+
+    private static final AtomicInteger __testServlets = new AtomicInteger();
     
     @Before
     public void createServer()
     {
         _server = new Server();
 
-        _connector = new LocalConnector();
+        _connector = new LocalConnector(_server);
         _server.addConnector(_connector);
+        __testServlets.set(0);
     }
-    
+
     @After
     public void destroyServer() throws Exception
     {
         _server.stop();
         _server.join();
     }
-    
+
     @Test
     public void testFindContainer() throws Exception
     {
@@ -70,18 +87,63 @@ public class ServletContextHandlerTest
         _server.setHandler(contexts);
 
         ServletContextHandler root = new ServletContextHandler(contexts,"/",ServletContextHandler.SESSIONS);
-        
+
         SessionHandler session = root.getSessionHandler();
         ServletHandler servlet = root.getServletHandler();
         SecurityHandler security = new ConstraintSecurityHandler();
         root.setSecurityHandler(security);
-        
+
         _server.start();
-        
+
         assertEquals(root, AbstractHandlerContainer.findContainerOf(_server, ContextHandler.class, session));
         assertEquals(root, AbstractHandlerContainer.findContainerOf(_server, ContextHandler.class, security));
         assertEquals(root, AbstractHandlerContainer.findContainerOf(_server, ContextHandler.class, servlet));
     }
+    
+    @Test
+    public void testInitOrder() throws Exception
+    {
+        ServletContextHandler context = new ServletContextHandler();
+        ServletHolder holder0 = context.addServlet(TestServlet.class,"/test0");
+        ServletHolder holder1 = context.addServlet(TestServlet.class,"/test1");
+        ServletHolder holder2 = context.addServlet(TestServlet.class,"/test2");
+        
+        holder1.setInitOrder(1);
+        holder2.setInitOrder(2);
+        
+        context.setContextPath("/");
+        _server.setHandler(context);
+        _server.start();
+        
+        assertEquals(2,__testServlets.get());
+        
+        String response =_connector.getResponses("GET /test1 HTTP/1.0\r\n\r\n");
+        Assert.assertThat(response,Matchers.containsString("200 OK"));
+        
+        assertEquals(2,__testServlets.get());
+        
+        response =_connector.getResponses("GET /test2 HTTP/1.0\r\n\r\n");
+        Assert.assertThat(response,containsString("200 OK"));
+        
+        assertEquals(2,__testServlets.get());
+        
+        assertThat(holder0.getServletInstance(),nullValue());
+        response =_connector.getResponses("GET /test0 HTTP/1.0\r\n\r\n");
+        assertThat(response,containsString("200 OK"));
+        assertEquals(3,__testServlets.get());
+        assertThat(holder0.getServletInstance(),notNullValue(Servlet.class));
+
+        _server.stop();
+        assertEquals(0,__testServlets.get());
+        
+        holder0.setInitOrder(0);
+        _server.start();
+        assertEquals(3,__testServlets.get());
+        assertThat(holder0.getServletInstance(),notNullValue(Servlet.class));
+        _server.stop();
+        assertEquals(0,__testServlets.get());
+        
+    }
 
     @Test
     public void testAddServletAfterStart() throws Exception
@@ -91,9 +153,9 @@ public class ServletContextHandlerTest
         context.setContextPath("/");
         _server.setHandler(context);
         _server.start();
-        
+
         StringBuffer request = new StringBuffer();
-        request.append("GET /test HTTP/1.1\n");
+        request.append("GET /test HTTP/1.0\n");
         request.append("Host: localhost\n");
         request.append("\n");
 
@@ -103,7 +165,7 @@ public class ServletContextHandlerTest
         context.addServlet(HelloServlet.class, "/hello");
 
         request = new StringBuffer();
-        request.append("GET /hello HTTP/1.1\n");
+        request.append("GET /hello HTTP/1.0\n");
         request.append("Host: localhost\n");
         request.append("\n");
 
@@ -121,7 +183,7 @@ public class ServletContextHandlerTest
         _server.start();
 
         StringBuffer request = new StringBuffer();
-        request.append("GET /test HTTP/1.1\n");
+        request.append("GET /test HTTP/1.0\n");
         request.append("Host: localhost\n");
         request.append("\n");
 
@@ -135,7 +197,7 @@ public class ServletContextHandlerTest
         context.start();
 
         request = new StringBuffer();
-        request.append("GET /hello HTTP/1.1\n");
+        request.append("GET /hello HTTP/1.0\n");
         request.append("Host: localhost\n");
         request.append("\n");
 
@@ -153,7 +215,7 @@ public class ServletContextHandlerTest
         _server.start();
 
         StringBuffer request = new StringBuffer();
-        request.append("GET /test HTTP/1.1\n");
+        request.append("GET /test HTTP/1.0\n");
         request.append("Host: localhost\n");
         request.append("\n");
 
@@ -168,13 +230,86 @@ public class ServletContextHandlerTest
         context.addServlet(HelloServlet.class,"/hello");
 
         request = new StringBuffer();
-        request.append("GET /hello HTTP/1.1\n");
+        request.append("GET /hello HTTP/1.0\n");
         request.append("Host: localhost\n");
         request.append("\n");
 
         response = _connector.getResponses(request.toString());
         assertResponseContains("Hello World", response);
     }
+    
+    @Test
+    public void testReplaceHandler () throws Exception
+    {
+        ServletContextHandler servletContextHandler = new ServletContextHandler();
+        ServletHolder sh =  new ServletHolder(new TestServlet());
+        servletContextHandler.addServlet(sh, "/foo");
+        final AtomicBoolean contextInit = new AtomicBoolean(false);
+        final AtomicBoolean contextDestroy = new AtomicBoolean(false);
+        
+        servletContextHandler.addEventListener(new ServletContextListener() {
+
+            @Override
+            public void contextInitialized(ServletContextEvent sce)
+            {
+                if (sce.getServletContext() != null)
+                    contextInit.set(true);
+            }
+
+            @Override
+            public void contextDestroyed(ServletContextEvent sce)
+            {  
+                if (sce.getServletContext() != null)
+                    contextDestroy.set(true);
+            }
+            
+        });
+        ServletHandler shandler = servletContextHandler.getServletHandler();
+
+        ResourceHandler rh = new ResourceHandler();
+
+        servletContextHandler.setHandler(rh);    
+        assertEquals(shandler, servletContextHandler.getServletHandler());
+        assertEquals(rh, servletContextHandler.getHandler());
+        assertEquals(rh.getHandler(), shandler);
+        _server.setHandler(servletContextHandler);
+        _server.start();
+        assertTrue(contextInit.get());
+        _server.stop();
+        assertTrue(contextDestroy.get());
+    }
+    
+
+    @Test
+    public void testFallThrough() throws Exception
+    {
+        HandlerList list = new HandlerList();
+        _server.setHandler(list);
+
+        ServletContextHandler root = new ServletContextHandler(list,"/",ServletContextHandler.SESSIONS);
+
+        ServletHandler servlet = root.getServletHandler();
+        servlet.setEnsureDefaultServlet(false);
+        servlet.addServletWithMapping(HelloServlet.class, "/hello/*");
+        
+        list.addHandler(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                response.sendError(404, "Fell Through");
+            }
+        });
+
+        _server.start();
+
+        String response= _connector.getResponses("GET /hello HTTP/1.0\r\n\r\n");
+        Assert.assertThat(response, Matchers.containsString("200 OK"));
+        
+        response= _connector.getResponses("GET /other HTTP/1.0\r\n\r\n");
+        Assert.assertThat(response, Matchers.containsString("404 Fell Through"));
+        
+    }
 
     private int assertResponseContains(String expected, String response)
     {
@@ -187,11 +322,11 @@ public class ServletContextHandlerTest
             err.append("\n").append(response);
 
             System.err.println(err);
-            throw new AssertionFailedError(err.toString());
+            Assert.fail(err.toString());
         }
         return idx;
     }
-    
+
     public static class HelloServlet extends HttpServlet
     {
         private static final long serialVersionUID = 1L;
@@ -211,6 +346,20 @@ public class ServletContextHandlerTest
         private static final long serialVersionUID = 1L;
 
         @Override
+        public void destroy()
+        {
+            super.destroy();
+            __testServlets.decrementAndGet();
+        }
+
+        @Override
+        public void init() throws ServletException
+        {
+            __testServlets.incrementAndGet();
+            super.init();
+        }
+
+        @Override
         protected void doGet(HttpServletRequest req, HttpServletResponse resp)
             throws ServletException, IOException
         {
diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletHandlerTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletHandlerTest.java
index eb3d480..558faa6 100644
--- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletHandlerTest.java
+++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletHandlerTest.java
@@ -26,25 +26,25 @@ import java.util.EnumSet;
 
 import javax.servlet.DispatcherType;
 
-import org.eclipse.jetty.servlet.Holder.Source;
+import org.eclipse.jetty.servlet.BaseHolder.Source;
 import org.junit.Before;
 import org.junit.Test;
 
 public class ServletHandlerTest
 {
-    FilterHolder fh1 = new FilterHolder(Holder.Source.DESCRIPTOR);
+    FilterHolder fh1 = new FilterHolder(Source.DESCRIPTOR);
     FilterMapping fm1 = new FilterMapping();
     
-    FilterHolder fh2 = new FilterHolder(Holder.Source.DESCRIPTOR);
+    FilterHolder fh2 = new FilterHolder(Source.DESCRIPTOR);
     FilterMapping fm2 = new FilterMapping();
     
-    FilterHolder fh3 = new FilterHolder(Holder.Source.JAVAX_API);
+    FilterHolder fh3 = new FilterHolder(Source.JAVAX_API);
     FilterMapping fm3 = new FilterMapping();
     
-    FilterHolder fh4 = new FilterHolder(Holder.Source.JAVAX_API);
+    FilterHolder fh4 = new FilterHolder(Source.JAVAX_API);
     FilterMapping fm4 = new FilterMapping();
     
-    FilterHolder fh5 = new FilterHolder(Holder.Source.JAVAX_API);
+    FilterHolder fh5 = new FilterHolder(Source.JAVAX_API);
     FilterMapping fm5 = new FilterMapping();
     
     
@@ -335,6 +335,7 @@ public class ServletHandlerTest
 
         //add a non-programmatic one to begin with
         handler.addFilterWithMapping(fh1, "/*", EnumSet.allOf(DispatcherType.class));           
+        handler.updateMappings();
         FilterMapping[] mappings = handler.getFilterMappings();
         assertNotNull(mappings);
         assertTrue(fh1 == mappings[0].getFilterHolder());
@@ -343,6 +344,7 @@ public class ServletHandlerTest
         fh4.setServletHandler(handler);
         handler.addFilter(fh4);
         fh4.getRegistration().addMappingForUrlPatterns(EnumSet.allOf(DispatcherType.class), false, "/*");
+        handler.updateMappings();
         mappings = handler.getFilterMappings();
         assertNotNull(mappings);
         assertEquals(2, mappings.length);
@@ -353,6 +355,7 @@ public class ServletHandlerTest
         fh3.setServletHandler(handler);
         handler.addFilter(fh3);
         fh3.getRegistration().addMappingForUrlPatterns(EnumSet.allOf(DispatcherType.class), true, "/*");
+        handler.updateMappings();
         mappings = handler.getFilterMappings();
         assertNotNull(mappings);
         assertEquals(3, mappings.length);
@@ -364,6 +367,7 @@ public class ServletHandlerTest
         fh5.setServletHandler(handler);
         handler.addFilter(fh5);
         fh5.getRegistration().addMappingForUrlPatterns(EnumSet.allOf(DispatcherType.class), false, "/*");
+        handler.updateMappings();
         mappings = handler.getFilterMappings();
         assertNotNull(mappings);
         assertEquals(4, mappings.length);
@@ -376,6 +380,7 @@ public class ServletHandlerTest
         FilterHolder f = new FilterHolder(Source.EMBEDDED);
         f.setName("non-programmatic");     
         handler.addFilterWithMapping(f, "/*", EnumSet.allOf(DispatcherType.class));
+        handler.updateMappings();
         mappings = handler.getFilterMappings();
         assertNotNull(mappings);
         assertEquals(5, mappings.length);
@@ -391,6 +396,7 @@ public class ServletHandlerTest
         pf.setName("programmaticA");
         handler.addFilter(pf);
         pf.getRegistration().addMappingForUrlPatterns(EnumSet.allOf(DispatcherType.class), true, "/*");
+        handler.updateMappings();
         mappings = handler.getFilterMappings();
         assertNotNull(mappings);
         assertEquals(6, mappings.length);
@@ -407,6 +413,7 @@ public class ServletHandlerTest
         pf2.setName("programmaticB");
         handler.addFilter(pf2);
         pf2.getRegistration().addMappingForUrlPatterns(EnumSet.allOf(DispatcherType.class), false, "/*");
+        handler.updateMappings();
         mappings = handler.getFilterMappings();
 
         assertNotNull(mappings);
diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletRequestLogTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletRequestLogTest.java
new file mode 100644
index 0000000..e135150
--- /dev/null
+++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletRequestLogTest.java
@@ -0,0 +1,681 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlet;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.net.HttpURLConnection;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.AsyncEvent;
+import javax.servlet.AsyncListener;
+import javax.servlet.Servlet;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.RequestLog;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.handler.ContextHandlerCollection;
+import org.eclipse.jetty.server.handler.DefaultHandler;
+import org.eclipse.jetty.server.handler.ErrorHandler;
+import org.eclipse.jetty.server.handler.HandlerCollection;
+import org.eclipse.jetty.server.handler.RequestLogHandler;
+import org.eclipse.jetty.toolchain.test.IO;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Servlet equivalent of the jetty-server's RequestLogHandlerTest, but with more ErrorHandler twists. 
+ */
+ at RunWith(Parameterized.class)
+ at Ignore
+public class ServletRequestLogTest
+{
+    private static final Logger LOG = Log.getLogger(ServletRequestLogTest.class);
+
+    public static class CaptureLog extends AbstractLifeCycle implements RequestLog
+    {
+        public List<String> captured = new ArrayList<>();
+
+        @Override
+        public void log(Request request, Response response)
+        {
+            captured.add(String.format("%s %s %s %03d",request.getMethod(),request.getUri().toString(),request.getProtocol(),response.getStatus()));
+        }
+    }
+    
+    @SuppressWarnings("serial")
+    private static abstract class AbstractTestServlet extends HttpServlet
+    {
+        @Override
+        public String toString()
+        {
+            return this.getClass().getSimpleName();
+        }
+    }
+
+    @SuppressWarnings("serial")
+    private static class HelloServlet extends AbstractTestServlet
+    {
+        @Override
+        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+        {
+            response.setContentType("text/plain");
+            response.getWriter().print("Hello World");
+        }
+    }
+    
+    @SuppressWarnings("serial")
+    private static class ResponseSendErrorServlet extends AbstractTestServlet
+    {
+        @Override
+        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+        {
+            response.sendError(500, "Whoops");
+        }
+    }
+    
+    @SuppressWarnings("serial")
+    private static class ServletExceptionServlet extends AbstractTestServlet
+    {
+        @Override
+        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+        {
+            throw new ServletException("Whoops");
+        }
+    }
+    
+    @SuppressWarnings("serial")
+    private static class IOExceptionServlet extends AbstractTestServlet
+    {
+        @Override
+        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+        {
+            throw new IOException("Whoops");
+        }
+    }
+    
+    @SuppressWarnings("serial")
+    private static class RuntimeExceptionServlet extends AbstractTestServlet
+    {
+        @Override
+        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+        {
+            throw new RuntimeException("Whoops");
+        }
+    }
+    
+    @SuppressWarnings("serial")
+    private static class AsyncOnTimeoutCompleteServlet extends AbstractTestServlet implements AsyncListener
+    {
+        @Override
+        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+        {
+            AsyncContext ac = request.startAsync();
+            ac.setTimeout(1000);
+            ac.addListener(this);
+        }
+
+        @Override
+        public void onTimeout(AsyncEvent event) throws IOException
+        {
+            event.getAsyncContext().complete();
+        }
+
+        @Override
+        public void onStartAsync(AsyncEvent event) throws IOException
+        {
+        }
+
+        @Override
+        public void onError(AsyncEvent event) throws IOException
+        {
+        }
+
+        @Override
+        public void onComplete(AsyncEvent event) throws IOException
+        {
+        }
+    }
+    
+    @SuppressWarnings("serial")
+    private static class AsyncOnTimeoutDispatchServlet extends AbstractTestServlet implements AsyncListener
+    {
+        @Override
+        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+        {
+            if(request.getAttribute("deep") == null)
+            {
+                AsyncContext ac = request.startAsync();
+                ac.setTimeout(1000);
+                ac.addListener(this);
+                request.setAttribute("deep",true);
+            }
+        }
+
+        @Override
+        public void onTimeout(AsyncEvent event) throws IOException
+        {
+            event.getAsyncContext().dispatch();
+        }
+
+        @Override
+        public void onStartAsync(AsyncEvent event) throws IOException
+        {
+        }
+
+        @Override
+        public void onError(AsyncEvent event) throws IOException
+        {
+        }
+
+        @Override
+        public void onComplete(AsyncEvent event) throws IOException
+        {
+        }
+    }
+    
+    @SuppressWarnings("serial")
+    private static class AsyncOnStartIOExceptionServlet extends AbstractTestServlet implements AsyncListener
+    {
+        @Override
+        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+        {
+            AsyncContext ac = request.startAsync();
+            ac.setTimeout(1000);
+            ac.addListener(this);
+        }
+
+        @Override
+        public void onTimeout(AsyncEvent event) throws IOException
+        {
+        }
+
+        @Override
+        public void onStartAsync(AsyncEvent event) throws IOException
+        {
+            event.getAsyncContext().complete();
+            throw new IOException("Whoops");
+        }
+
+        @Override
+        public void onError(AsyncEvent event) throws IOException
+        {
+            LOG.warn("onError() -> {}",event);
+        }
+
+        @Override
+        public void onComplete(AsyncEvent event) throws IOException
+        {
+        }
+    }
+    
+    @SuppressWarnings("serial")
+    public static class CustomErrorServlet extends HttpServlet
+    {
+        @Override
+        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+        {
+            // collect error details
+            String reason = (response instanceof Response)?((Response)response).getReason():null;
+            int status = response.getStatus();
+
+            // intentionally set response status to OK (this is a test to see what is actually logged)
+            response.setStatus(200);
+            response.setContentType("text/plain");
+            PrintWriter out = response.getWriter();
+            out.printf("Error %d: %s%n",status,reason);
+        }
+    }
+    
+    @Parameters(name="{0}")
+    public static List<Object[]> data()
+    {
+        List<Object[]> data = new ArrayList<>();
+
+        data.add(new Object[] { new HelloServlet(), "/test/", "GET /test/ HTTP/1.1 200" });
+        data.add(new Object[] { new AsyncOnTimeoutCompleteServlet(), "/test/", "GET /test/ HTTP/1.1 200" });
+        data.add(new Object[] { new AsyncOnTimeoutDispatchServlet(), "/test/", "GET /test/ HTTP/1.1 200" });
+        
+        data.add(new Object[] { new AsyncOnStartIOExceptionServlet(), "/test/", "GET /test/ HTTP/1.1 500" });
+        data.add(new Object[] { new ResponseSendErrorServlet(), "/test/", "GET /test/ HTTP/1.1 500" });
+        data.add(new Object[] { new ServletExceptionServlet(), "/test/", "GET /test/ HTTP/1.1 500" });
+        data.add(new Object[] { new IOExceptionServlet(), "/test/", "GET /test/ HTTP/1.1 500" });
+        data.add(new Object[] { new RuntimeExceptionServlet(), "/test/", "GET /test/ HTTP/1.1 500" });
+
+        return data;
+    }
+
+    @Parameter(0)
+    public Servlet testServlet;
+    
+    @Parameter(1)
+    public String requestPath;
+
+    @Parameter(2)
+    public String expectedLogEntry;
+
+    /**
+     * Test a RequestLogHandler at the end of a HandlerCollection.
+     * This handler chain is setup to look like Jetty versions up to 9.2. 
+     * Default configuration.
+     */
+    @Test(timeout=4000)
+    public void testLogHandlerCollection() throws Exception
+    {
+        Server server = new Server();
+        ServerConnector connector = new ServerConnector(server);
+        connector.setPort(0);
+        server.setConnectors(new Connector[] { connector });
+
+        // First the behavior as defined in etc/jetty.xml
+        // id="Handlers"
+        HandlerCollection handlers = new HandlerCollection();
+        // id="Contexts"
+        ContextHandlerCollection contexts = new ContextHandlerCollection();
+        // id="DefaultHandler"
+        DefaultHandler defaultHandler = new DefaultHandler();
+        
+        handlers.setHandlers(new Handler[] { contexts, defaultHandler });
+        server.setHandler(handlers);
+
+        // Next the behavior as defined by etc/jetty-requestlog.xml
+        // the id="RequestLog"
+        RequestLogHandler requestLog = new RequestLogHandler();
+        CaptureLog captureLog = new CaptureLog();
+        requestLog.setRequestLog(captureLog);
+
+        handlers.addHandler(requestLog);
+        
+        // Lastly, the behavior as defined by deployment of a webapp
+        // Add the Servlet Context
+        ServletContextHandler app = new ServletContextHandler(ServletContextHandler.SESSIONS);
+        app.setContextPath("/");
+        contexts.addHandler(app);
+        
+        // Add the test servlet
+        ServletHolder testHolder = new ServletHolder(testServlet);
+        app.addServlet(testHolder,"/test");
+
+        try
+        {
+            server.start();
+
+            String host = connector.getHost();
+            if (host == null)
+            {
+                host = "localhost";
+            }
+            int port = connector.getLocalPort();
+
+            URI serverUri = new URI("http",null,host,port,requestPath,null,null);
+
+            // Make call to test handler
+            HttpURLConnection connection = (HttpURLConnection)serverUri.toURL().openConnection();
+            try
+            {
+                connection.setAllowUserInteraction(false);
+
+                // log response status code
+                int statusCode = connection.getResponseCode();
+                LOG.debug("Response Status Code: {}",statusCode);
+
+                if (statusCode == 200)
+                {
+                    // collect response message and log it
+                    String content = getResponseContent(connection);
+                    LOG.debug("Response Content: {}",content);
+                }
+            }
+            finally
+            {
+                connection.disconnect();
+            }
+
+            assertRequestLog(captureLog);
+        }
+        finally
+        {
+            server.stop();
+        }
+    }
+    
+    /**
+     * Test a RequestLogHandler at the end of a HandlerCollection.
+     * and also with the default ErrorHandler as server bean in place.
+     */
+    @Test(timeout=4000)
+    public void testLogHandlerCollection_ErrorHandler_ServerBean() throws Exception
+    {
+        Server server = new Server();
+        ServerConnector connector = new ServerConnector(server);
+        connector.setPort(0);
+        server.setConnectors(new Connector[] { connector });
+        
+        ErrorHandler errorHandler = new ErrorHandler();
+        server.addBean(errorHandler);
+
+        // First the behavior as defined in etc/jetty.xml
+        // id="Handlers"
+        HandlerCollection handlers = new HandlerCollection();
+        // id="Contexts"
+        ContextHandlerCollection contexts = new ContextHandlerCollection();
+        // id="DefaultHandler"
+        DefaultHandler defaultHandler = new DefaultHandler();
+        
+        handlers.setHandlers(new Handler[] { contexts, defaultHandler });
+        server.setHandler(handlers);
+
+        // Next the behavior as defined by etc/jetty-requestlog.xml
+        // the id="RequestLog"
+        RequestLogHandler requestLog = new RequestLogHandler();
+        CaptureLog captureLog = new CaptureLog();
+        requestLog.setRequestLog(captureLog);
+
+        handlers.addHandler(requestLog);
+        
+        // Lastly, the behavior as defined by deployment of a webapp
+        // Add the Servlet Context
+        ServletContextHandler app = new ServletContextHandler(ServletContextHandler.SESSIONS);
+        app.setContextPath("/");
+        contexts.addHandler(app);
+        
+        // Add the test servlet
+        ServletHolder testHolder = new ServletHolder(testServlet);
+        app.addServlet(testHolder,"/test");
+
+        try
+        {
+            server.start();
+
+            String host = connector.getHost();
+            if (host == null)
+            {
+                host = "localhost";
+            }
+            int port = connector.getLocalPort();
+
+            URI serverUri = new URI("http",null,host,port,requestPath,null,null);
+
+            // Make call to test handler
+            HttpURLConnection connection = (HttpURLConnection)serverUri.toURL().openConnection();
+            try
+            {
+                connection.setAllowUserInteraction(false);
+
+                // log response status code
+                int statusCode = connection.getResponseCode();
+                LOG.debug("Response Status Code: {}",statusCode);
+
+                if (statusCode == 200)
+                {
+                    // collect response message and log it
+                    String content = getResponseContent(connection);
+                    LOG.debug("Response Content: {}",content);
+                }
+            }
+            finally
+            {
+                connection.disconnect();
+            }
+
+            assertRequestLog(captureLog);
+        }
+        finally
+        {
+            server.stop();
+        }
+    }
+    
+    /**
+     * Test a RequestLogHandler at the end of a HandlerCollection
+     * using servlet specific error page mapping.
+     */
+    @Test(timeout=4000)
+    public void testLogHandlerCollection_SimpleErrorPageMapping() throws Exception
+    {
+        Server server = new Server();
+        ServerConnector connector = new ServerConnector(server);
+        connector.setPort(0);
+        server.setConnectors(new Connector[] { connector });
+        
+        // First the behavior as defined in etc/jetty.xml
+        // id="Handlers"
+        HandlerCollection handlers = new HandlerCollection();
+        // id="Contexts"
+        ContextHandlerCollection contexts = new ContextHandlerCollection();
+        // id="DefaultHandler"
+        DefaultHandler defaultHandler = new DefaultHandler();
+        
+        handlers.setHandlers(new Handler[] { contexts, defaultHandler });
+        server.setHandler(handlers);
+
+        // Next the behavior as defined by etc/jetty-requestlog.xml
+        // the id="RequestLog"
+        RequestLogHandler requestLog = new RequestLogHandler();
+        CaptureLog captureLog = new CaptureLog();
+        requestLog.setRequestLog(captureLog);
+
+        handlers.addHandler(requestLog);
+        
+        // Lastly, the behavior as defined by deployment of a webapp
+        // Add the Servlet Context
+        ServletContextHandler app = new ServletContextHandler(ServletContextHandler.SESSIONS);
+        app.setContextPath("/");
+        contexts.addHandler(app);
+        
+        // Add the test servlet
+        ServletHolder testHolder = new ServletHolder(testServlet);
+        app.addServlet(testHolder,"/test");
+        app.addServlet(CustomErrorServlet.class,"/errorpage");
+        
+        // Add error page mapping
+        ErrorPageErrorHandler errorMapper = new ErrorPageErrorHandler();
+        errorMapper.addErrorPage(500,"/errorpage");
+        app.setErrorHandler(errorMapper);
+
+        try
+        {
+            server.start();
+
+            String host = connector.getHost();
+            if (host == null)
+            {
+                host = "localhost";
+            }
+            int port = connector.getLocalPort();
+
+            URI serverUri = new URI("http",null,host,port,requestPath,null,null);
+
+            // Make call to test handler
+            HttpURLConnection connection = (HttpURLConnection)serverUri.toURL().openConnection();
+            try
+            {
+                connection.setAllowUserInteraction(false);
+
+                // log response status code
+                int statusCode = connection.getResponseCode();
+                LOG.debug("Response Status Code: {}",statusCode);
+
+                if (statusCode == 200)
+                {
+                    // collect response message and log it
+                    String content = getResponseContent(connection);
+                    LOG.debug("Response Content: {}",content);
+                }
+            }
+            finally
+            {
+                connection.disconnect();
+            }
+
+            assertRequestLog(captureLog);
+        }
+        finally
+        {
+            server.stop();
+        }
+    }
+    
+    /**
+     * Test an alternate (proposed) setup for using RequestLogHandler in a wrapped style
+     */
+    @Test(timeout=4000)
+    public void testLogHandlerWrapped() throws Exception
+    {
+        Server server = new Server();
+        ServerConnector connector = new ServerConnector(server);
+        connector.setPort(0);
+        server.setConnectors(new Connector[] { connector });
+
+        // First the behavior as defined in etc/jetty.xml (as is)
+        // id="Handlers"
+        HandlerCollection handlers = new HandlerCollection();
+        // id="Contexts"
+        ContextHandlerCollection contexts = new ContextHandlerCollection();
+        // id="DefaultHandler"
+        DefaultHandler defaultHandler = new DefaultHandler();
+        
+        handlers.setHandlers(new Handler[] { contexts, defaultHandler });
+        server.setHandler(handlers);
+
+        // Next the proposed behavioral change to etc/jetty-requestlog.xml
+        // the id="RequestLog"
+        RequestLogHandler requestLog = new RequestLogHandler();
+        CaptureLog captureLog = new CaptureLog();
+        requestLog.setRequestLog(captureLog);
+        
+        Handler origServerHandler = server.getHandler();
+        requestLog.setHandler(origServerHandler);
+        server.setHandler(requestLog);
+        
+        // Lastly, the behavior as defined by deployment of a webapp
+        // Add the Servlet Context
+        ServletContextHandler app = new ServletContextHandler(ServletContextHandler.SESSIONS);
+        app.setContextPath("/");
+        contexts.addHandler(app);
+        
+        // Add the test servlet
+        ServletHolder testHolder = new ServletHolder(testServlet);
+        app.addServlet(testHolder,"/test");
+        app.addServlet(CustomErrorServlet.class,"/errorpage");
+        
+        // Add error page mapping
+        ErrorPageErrorHandler errorMapper = new ErrorPageErrorHandler();
+        errorMapper.addErrorPage(500,"/errorpage");
+        app.setErrorHandler(errorMapper);
+
+        try
+        {
+            server.start();
+
+            String host = connector.getHost();
+            if (host == null)
+            {
+                host = "localhost";
+            }
+            int port = connector.getLocalPort();
+
+            URI serverUri = new URI("http",null,host,port,"/test",null,null);
+
+            // Make call to test handler
+            HttpURLConnection connection = (HttpURLConnection)serverUri.toURL().openConnection();
+            try
+            {
+                connection.setAllowUserInteraction(false);
+
+                // log response status code
+                int statusCode = connection.getResponseCode();
+                LOG.info("Response Status Code: {}",statusCode);
+
+                if (statusCode == 200)
+                {
+                    // collect response message and log it
+                    String content = getResponseContent(connection);
+                    LOG.info("Response Content: {}",content);
+                }
+            }
+            finally
+            {
+                connection.disconnect();
+            }
+
+            assertRequestLog(captureLog);
+        }
+        finally
+        {
+            server.stop();
+        }
+    }
+
+    private void assertRequestLog(CaptureLog captureLog)
+    {
+        int captureCount = captureLog.captured.size();
+
+        if (captureCount != 1)
+        {
+            LOG.warn("Capture Log size is {}, expected to be 1",captureCount);
+            if (captureCount > 1)
+            {
+                for (int i = 0; i < captureCount; i++)
+                {
+                    LOG.warn("[{}] {}",i,captureLog.captured.get(i));
+                }
+            }
+            assertThat("Capture Log Entry Count",captureLog.captured.size(),is(1));
+        }
+
+        String actual = captureLog.captured.get(0);
+        assertThat("Capture Log",actual,is(expectedLogEntry));
+    }
+
+    private String getResponseContent(HttpURLConnection connection) throws IOException
+    {
+        try (InputStream in = connection.getInputStream(); InputStreamReader reader = new InputStreamReader(in))
+        {
+            StringWriter writer = new StringWriter();
+            IO.copy(reader,writer);
+            return writer.toString();
+        }
+    }
+
+}
diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/StatisticsServletTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/StatisticsServletTest.java
deleted file mode 100644
index b2eb8c5..0000000
--- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/StatisticsServletTest.java
+++ /dev/null
@@ -1,105 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.servlet;
- 
-import junit.framework.AssertionFailedError;
-
-import org.eclipse.jetty.server.LocalConnector;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.handler.StatisticsHandler;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-public class StatisticsServletTest
-{
-    private Server server;
-    private LocalConnector connector;
-    private ServletContextHandler context;
-
-    @Before
-    public void init() throws Exception
-    {
-        server = new Server();
-        server.setSendServerVersion(false);
-        context = new ServletContextHandler();
-        context.setContextPath("/");
-        ServletHolder holder = new ServletHolder();
-        holder.setServlet(new org.eclipse.jetty.servlet.StatisticsServlet());
-        holder.setInitParameter("restrictToLocalhost", "false");
-        context.addServlet(holder, "/stats");
-
-        server.setHandler(context);
-        connector = new LocalConnector();
-        server.addConnector(connector);
-    }
-
-    @After
-    public void destroy() throws Exception
-    {
-        server.stop();
-        server.join();
-    }
-
-    @Test
-    public void testNoHandler () throws Exception
-    {
-        server.start();
-
-        StringBuffer req1 = new StringBuffer();
-        req1.append("GET /stats HTTP/1.1\n");
-        req1.append("Host: localhost\n");
-        req1.append("\n");
-
-        String response = connector.getResponses(req1.toString());
-        assertResponseContains("503", response);
-    }
-
-    @Test
-    public void testWithHandler () throws Exception
-    {
-        StatisticsHandler statsHandler = new StatisticsHandler();
-        statsHandler.setHandler(context);
-        server.setHandler(statsHandler);
-        server.start();
-
-        StringBuffer req1 = new StringBuffer();
-        req1.append("GET /stats HTTP/1.1\n");
-        req1.append("Host: localhost\n");
-        req1.append("\n");
-
-        String response = connector.getResponses(req1.toString());
-        assertResponseContains("Statistics gathering started ", response);
-    }
-
-    private void assertResponseContains(String expected, String response)
-    {
-        int idx = response.indexOf(expected);
-        if (idx == (-1))
-        {
-            // Not found
-            StringBuffer err = new StringBuffer();
-            err.append("Response does not contain expected string \"").append(expected).append("\"");
-            err.append("\n").append(response);
-
-            System.err.println(err);
-            throw new AssertionFailedError(err.toString());
-        }
-    }
-}
diff --git a/jetty-servlet/src/test/resources/jetty-logging.properties b/jetty-servlet/src/test/resources/jetty-logging.properties
new file mode 100644
index 0000000..50696d6
--- /dev/null
+++ b/jetty-servlet/src/test/resources/jetty-logging.properties
@@ -0,0 +1,5 @@
+org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
+#org.eclipse.jetty.LEVEL=DEBUG
+#org.eclipse.jetty.server.LEVEL=DEBUG
+#org.eclipse.jetty.servlet.LEVEL=DEBUG
+#org.eclipse.jetty.io.ChannelEndPoint.LEVEL=DEBUG
diff --git a/jetty-spdy/spdy-jetty-http-webapp/src/main/resources/keystore.jks b/jetty-servlet/src/test/resources/keystore.jks
similarity index 100%
copy from jetty-spdy/spdy-jetty-http-webapp/src/main/resources/keystore.jks
copy to jetty-servlet/src/test/resources/keystore.jks
diff --git a/jetty-spdy/spdy-jetty-http-webapp/src/main/resources/truststore.jks b/jetty-servlet/src/test/resources/truststore.jks
similarity index 100%
copy from jetty-spdy/spdy-jetty-http-webapp/src/main/resources/truststore.jks
copy to jetty-servlet/src/test/resources/truststore.jks
diff --git a/jetty-servlets/pom.xml b/jetty-servlets/pom.xml
index f712007..6995704 100644
--- a/jetty-servlets/pom.xml
+++ b/jetty-servlets/pom.xml
@@ -3,7 +3,7 @@
   <parent>
     <artifactId>jetty-project</artifactId>
     <groupId>org.eclipse.jetty</groupId>
-    <version>8.1.17.v20150415</version>
+    <version>9.2.13.v20150730</version>
   </parent>
   <modelVersion>4.0.0</modelVersion>
   <artifactId>jetty-servlets</artifactId>
@@ -26,7 +26,7 @@
             </goals>
             <configuration>
               <instructions>
-                <Import-Package>javax.servlet.*;version="2.6.0",*</Import-Package>
+                <Import-Package>javax.servlet.*;version="[2.6.0,3.2)",*</Import-Package>
               </instructions>
             </configuration>
           </execution>
@@ -45,6 +45,23 @@
         </configuration>
       </plugin>
       <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-assembly-plugin</artifactId>
+        <executions>
+          <execution>
+            <phase>package</phase>
+            <goals>
+              <goal>single</goal>
+            </goals>
+            <configuration>
+              <descriptorRefs>
+                <descriptorRef>config</descriptorRef>
+              </descriptorRefs>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
         <groupId>org.codehaus.mojo</groupId>
         <artifactId>findbugs-maven-plugin</artifactId>
         <configuration>
@@ -55,25 +72,20 @@
   </build>
   <dependencies>
     <dependency>
-      <groupId>org.eclipse.jetty.toolchain</groupId>
-      <artifactId>jetty-test-helper</artifactId>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
       <groupId>org.eclipse.jetty</groupId>
       <artifactId>jetty-continuation</artifactId>
       <version>${project.version}</version>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-webapp</artifactId>
+      <artifactId>jetty-http</artifactId>
       <version>${project.version}</version>
-      <scope>provided</scope>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-client</artifactId>
+      <artifactId>jetty-webapp</artifactId>
       <version>${project.version}</version>
+      <scope>provided</scope>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jetty</groupId>
@@ -81,21 +93,25 @@
       <version>${project.version}</version>
     </dependency>
     <dependency>
-      <groupId>org.eclipse.jetty.orbit</groupId>
-      <artifactId>javax.servlet</artifactId>
+      <groupId>javax.servlet</groupId>
+      <artifactId>javax.servlet-api</artifactId>
       <scope>provided</scope>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-jmx</artifactId>
+      <artifactId>jetty-io</artifactId>
       <version>${project.version}</version>
-      <scope>test</scope>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jetty</groupId>
-      <artifactId>test-jetty-servlet</artifactId>
+      <artifactId>jetty-jmx</artifactId>
       <version>${project.version}</version>
       <scope>test</scope>
     </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.toolchain</groupId>
+      <artifactId>jetty-test-helper</artifactId>
+      <scope>test</scope>
+    </dependency>
   </dependencies>
 </project>
diff --git a/jetty-servlets/src/main/config/modules/servlets.mod b/jetty-servlets/src/main/config/modules/servlets.mod
new file mode 100644
index 0000000..e8724b8
--- /dev/null
+++ b/jetty-servlets/src/main/config/modules/servlets.mod
@@ -0,0 +1,10 @@
+#
+# Jetty Servlets Module
+#
+
+[depend]
+servlet
+
+[lib]
+lib/jetty-servlets-${jetty.version}.jar
+
diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/AsyncGzipFilter.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/AsyncGzipFilter.java
new file mode 100644
index 0000000..65c1db9
--- /dev/null
+++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/AsyncGzipFilter.java
@@ -0,0 +1,572 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlets;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+import java.util.StringTokenizer;
+import java.util.regex.Pattern;
+import java.util.zip.Deflater;
+
+import javax.servlet.DispatcherType;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRegistration;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpField;
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpGenerator;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.server.HttpChannel;
+import org.eclipse.jetty.server.HttpOutput;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.servlets.gzip.GzipFactory;
+import org.eclipse.jetty.servlets.gzip.GzipHttpOutput;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/** Async GZIP Filter
+ * This filter is a gzip filter using jetty internal mechanism to apply gzip compression
+ * to output that is compatible with async IO and does not need to wrap the response nor output stream.
+ * The filter will gzip the content of a response if: <ul>
+ * <li>The filter is mapped to a matching path</li>
+ * <li>accept-encoding header is set to either gzip, deflate or a combination of those</li>
+ * <li>The response status code is >=200 and <300
+ * <li>The content length is unknown or more than the <code>minGzipSize</code> initParameter or the minGzipSize is 0(default)</li>
+ * <li>If a list of mimeTypes is set by the <code>mimeTypes</code> init parameter, then the Content-Type is in the list.</li>
+ * <li>If no mimeType list is set, then the content-type is not in the list defined by <code>excludedMimeTypes</code></li>
+ * <li>No content-encoding is specified by the resource</li>
+ * </ul>
+ *
+ * <p>
+ * Compressing the content can greatly improve the network bandwidth usage, but at a cost of memory and
+ * CPU cycles. If this filter is mapped for static content, then use of efficient direct NIO may be
+ * prevented, thus use of the gzip mechanism of the {@link org.eclipse.jetty.servlet.DefaultServlet} is
+ * advised instead.
+ * </p>
+ * <p>
+ * This filter extends {@link UserAgentFilter} and if the the initParameter <code>excludedAgents</code>
+ * is set to a comma separated list of user agents, then these agents will be excluded from gzip content.
+ * </p>
+ * <p>Init Parameters:</p>
+ * <dl>
+ * <dt>bufferSize</dt>       <dd>The output buffer size. Defaults to 8192. Be careful as values <= 0 will lead to an
+ *                            {@link IllegalArgumentException}.
+ *                            See: {@link java.util.zip.GZIPOutputStream#GZIPOutputStream(java.io.OutputStream, int)}
+ *                            and: {@link java.util.zip.DeflaterOutputStream#DeflaterOutputStream(java.io.OutputStream, Deflater, int)}
+ * </dd>
+ * <dt>minGzipSize</dt>       <dd>Content will only be compressed if content length is either unknown or greater
+ *                            than <code>minGzipSize</code>.
+ * </dd>
+ * <dt>deflateCompressionLevel</dt>       <dd>The compression level used for deflate compression. (0-9).
+ *                            See: {@link java.util.zip.Deflater#Deflater(int, boolean)}
+ * </dd>
+ * <dt>deflateNoWrap</dt>       <dd>The noWrap setting for deflate compression. Defaults to true. (true/false)
+ *                            See: {@link java.util.zip.Deflater#Deflater(int, boolean)}
+ * </dd>
+ * <dt>methods</dt>       <dd>Comma separated list of HTTP methods to compress. If not set, only GET requests are compressed.
+ *  </dd>
+ * <dt>mimeTypes</dt>       <dd>Comma separated list of mime types to compress. If it is not set, then the excludedMimeTypes list is used.
+ * </dd>
+ * <dt>excludedMimeTypes</dt>       <dd>Comma separated list of mime types to never compress. If not set, then the default is the commonly known
+ * image, video, audio and compressed types.
+ * </dd>
+
+ * <dt>excludedAgents</dt>       <dd>Comma separated list of user agents to exclude from compression. Does a
+ *                            {@link String#contains(CharSequence)} to check if the excluded agent occurs
+ *                            in the user-agent header. If it does -> no compression
+ * </dd>
+ * <dt>excludeAgentPatterns</dt>       <dd>Same as excludedAgents, but accepts regex patterns for more complex matching.
+ * </dd>
+ * <dt>excludePaths</dt>       <dd>Comma separated list of paths to exclude from compression.
+ *                            Does a {@link String#startsWith(String)} comparison to check if the path matches.
+ *                            If it does match -> no compression. To match subpaths use <code>excludePathPatterns</code>
+ *                            instead.
+ * </dd>
+ * <dt>excludePathPatterns</dt>       <dd>Same as excludePath, but accepts regex patterns for more complex matching.
+ * </dd>
+ * <dt>vary</dt>       <dd>Set to the value of the Vary header sent with responses that could be compressed.  By default it is 
+ *                            set to 'Vary: Accept-Encoding, User-Agent' since IE6 is excluded by default from the excludedAgents. 
+ *                            If user-agents are not to be excluded, then this can be set to 'Vary: Accept-Encoding'.  Note also 
+ *                            that shared caches may cache copies of a resource that is varied by User-Agent - one per variation of 
+ *                            the User-Agent, unless the cache does some normalization of the UA string.
+ * </dd>                         
+ * <dt>checkGzExists</dt>       <dd>If set to true, the filter check if a static resource with ".gz" appended exists.  If so then
+ *                            the normal processing is done so that the default servlet can send  the pre existing gz content. If not
+ *                            set, defaults to the same as the default servlet "gzip" parameter.
+ *  </dd>
+ *  </dl>
+ */
+public class AsyncGzipFilter extends UserAgentFilter implements GzipFactory
+{
+    private static final Logger LOG = Log.getLogger(GzipFilter.class);
+    public final static String GZIP = "gzip";
+    public static final String DEFLATE = "deflate";
+    public final static String ETAG_GZIP="--gzip";
+    public final static String ETAG = "o.e.j.s.GzipFilter.ETag";
+    public final static int DEFAULT_MIN_GZIP_SIZE=256;
+
+    protected ServletContext _context;
+    protected final Set<String> _mimeTypes=new HashSet<>();
+    protected boolean _excludeMimeTypes;
+    protected int _bufferSize=32*1024;
+    protected int _minGzipSize=DEFAULT_MIN_GZIP_SIZE;
+    protected int _deflateCompressionLevel=Deflater.DEFAULT_COMPRESSION;
+    protected boolean _deflateNoWrap = true;
+    protected boolean _checkGzExists = true;
+    
+    // non-static, as other GzipFilter instances may have different configurations
+    protected final ThreadLocal<Deflater> _deflater = new ThreadLocal<Deflater>();
+
+    protected final static ThreadLocal<byte[]> _buffer= new ThreadLocal<byte[]>();
+
+    protected final Set<String> _methods=new HashSet<String>();
+    protected Set<String> _excludedAgents;
+    protected Set<Pattern> _excludedAgentPatterns;
+    protected Set<String> _excludedPaths;
+    protected Set<Pattern> _excludedPathPatterns;
+    protected HttpField _vary=new HttpGenerator.CachedHttpField(HttpHeader.VARY,HttpHeader.ACCEPT_ENCODING+", "+HttpHeader.USER_AGENT);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.servlets.UserAgentFilter#init(javax.servlet.FilterConfig)
+     */
+    @Override
+    public void init(FilterConfig filterConfig) throws ServletException
+    {
+        super.init(filterConfig);
+
+        _context=filterConfig.getServletContext();
+        
+        String tmp=filterConfig.getInitParameter("bufferSize");
+        if (tmp!=null)
+            _bufferSize=Integer.parseInt(tmp);
+        LOG.debug("{} bufferSize={}",this,_bufferSize);
+
+        tmp=filterConfig.getInitParameter("minGzipSize");
+        if (tmp!=null)
+            _minGzipSize=Integer.parseInt(tmp);
+        LOG.debug("{} minGzipSize={}",this,_minGzipSize);
+
+        tmp=filterConfig.getInitParameter("deflateCompressionLevel");
+        if (tmp!=null)
+            _deflateCompressionLevel=Integer.parseInt(tmp);
+        LOG.debug("{} deflateCompressionLevel={}",this,_deflateCompressionLevel);
+
+        tmp=filterConfig.getInitParameter("deflateNoWrap");
+        if (tmp!=null)
+            _deflateNoWrap=Boolean.parseBoolean(tmp);
+        LOG.debug("{} deflateNoWrap={}",this,_deflateNoWrap);
+
+        tmp=filterConfig.getInitParameter("checkGzExists");
+        if (tmp!=null)
+            _checkGzExists=Boolean.parseBoolean(tmp);
+        else
+        {
+            // Look to Default servlet for default
+            ServletRegistration dftServlet = _context.getServletRegistration("default");
+            if (dftServlet!=null && dftServlet.getInitParameter("gzip")!=null)
+                _checkGzExists=Boolean.parseBoolean(dftServlet.getInitParameter("gzip"));
+        }
+        LOG.debug("{} checkGzExists={}",this,_checkGzExists);
+        
+        tmp=filterConfig.getInitParameter("methods");
+        if (tmp!=null)
+        {
+            StringTokenizer tok = new StringTokenizer(tmp,",",false);
+            while (tok.hasMoreTokens())
+                _methods.add(tok.nextToken().trim().toUpperCase(Locale.ENGLISH));
+        }
+        else
+            _methods.add(HttpMethod.GET.asString());
+        LOG.debug("{} methods={}",this,_methods);
+        
+        tmp=filterConfig.getInitParameter("mimeTypes");
+        if (tmp==null)
+        {
+            _excludeMimeTypes=true;
+            tmp=filterConfig.getInitParameter("excludedMimeTypes");
+            if (tmp==null)
+            {
+                for (String type:MimeTypes.getKnownMimeTypes())
+                {
+                    if (type.equals("image/svg+xml")) //always compressable (unless .svgz file)
+                        continue;
+                    if (type.startsWith("image/")||
+                        type.startsWith("audio/")||
+                        type.startsWith("video/"))
+                        _mimeTypes.add(type);
+                }
+                _mimeTypes.add("application/compress");
+                _mimeTypes.add("application/zip");
+                _mimeTypes.add("application/gzip");
+            }
+            else
+            {
+                StringTokenizer tok = new StringTokenizer(tmp,",",false);
+                while (tok.hasMoreTokens())
+                    _mimeTypes.add(tok.nextToken().trim());
+            }
+        }
+        else
+        {
+            StringTokenizer tok = new StringTokenizer(tmp,",",false);
+            while (tok.hasMoreTokens())
+                _mimeTypes.add(tok.nextToken().trim());
+        }
+        LOG.debug("{} mimeTypes={}",this,_mimeTypes);
+        LOG.debug("{} excludeMimeTypes={}",this,_excludeMimeTypes);
+        tmp=filterConfig.getInitParameter("excludedAgents");
+        if (tmp!=null)
+        {
+            _excludedAgents=new HashSet<String>();
+            StringTokenizer tok = new StringTokenizer(tmp,",",false);
+            while (tok.hasMoreTokens())
+               _excludedAgents.add(tok.nextToken().trim());
+        }
+        LOG.debug("{} excludedAgents={}",this,_excludedAgents);
+
+        tmp=filterConfig.getInitParameter("excludeAgentPatterns");
+        if (tmp!=null)
+        {
+            _excludedAgentPatterns=new HashSet<Pattern>();
+            StringTokenizer tok = new StringTokenizer(tmp,",",false);
+            while (tok.hasMoreTokens())
+                _excludedAgentPatterns.add(Pattern.compile(tok.nextToken().trim()));
+        }
+        LOG.debug("{} excludedAgentPatterns={}",this,_excludedAgentPatterns);
+
+        tmp=filterConfig.getInitParameter("excludePaths");
+        if (tmp!=null)
+        {
+            _excludedPaths=new HashSet<String>();
+            StringTokenizer tok = new StringTokenizer(tmp,",",false);
+            while (tok.hasMoreTokens())
+                _excludedPaths.add(tok.nextToken().trim());
+        }
+        LOG.debug("{} excludedPaths={}",this,_excludedPaths);
+
+        tmp=filterConfig.getInitParameter("excludePathPatterns");
+        if (tmp!=null)
+        {
+            _excludedPathPatterns=new HashSet<Pattern>();
+            StringTokenizer tok = new StringTokenizer(tmp,",",false);
+            while (tok.hasMoreTokens())
+                _excludedPathPatterns.add(Pattern.compile(tok.nextToken().trim()));
+        }
+        LOG.debug("{} excludedPathPatterns={}",this,_excludedPathPatterns);
+        
+        tmp=filterConfig.getInitParameter("vary");
+        if (tmp!=null)
+            _vary=new HttpGenerator.CachedHttpField(HttpHeader.VARY,tmp);
+        LOG.debug("{} vary={}",this,_vary);
+        
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.servlets.UserAgentFilter#destroy()
+     */
+    @Override
+    public void destroy()
+    {
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.servlets.UserAgentFilter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)
+     */
+    @Override
+    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
+        throws IOException, ServletException
+    {
+        LOG.debug("{} doFilter {}",this,req);
+        HttpServletRequest request=(HttpServletRequest)req;
+        HttpServletResponse response=(HttpServletResponse)res;
+        HttpChannel<?> channel = HttpChannel.getCurrentHttpChannel();
+        
+        // Have we already started compressing this response?
+        if (req.getDispatcherType()!=DispatcherType.REQUEST)
+        {
+            HttpOutput out = channel.getResponse().getHttpOutput();
+            if (out instanceof GzipHttpOutput && ((GzipHttpOutput)out).mightCompress())
+            {
+                LOG.debug("{} already might compress {}",this,request);
+                super.doFilter(request,response,chain);
+                return;
+            }
+        }
+
+        // If not a supported method or it is an Excluded URI or an excluded UA - no Vary because no matter what client, this URI is always excluded
+        String requestURI = request.getRequestURI();
+        if (!_methods.contains(request.getMethod()))
+        {
+            LOG.debug("{} excluded by method {}",this,request);
+            super.doFilter(request,response,chain);
+            return;
+        }
+        
+        if (isExcludedPath(requestURI))
+        {
+            LOG.debug("{} excluded by path {}",this,request);
+            super.doFilter(request,response,chain);
+            return;
+        }
+
+        // Exclude non compressible mime-types known from URI extension. - no Vary because no matter what client, this URI is always excluded
+        if (_mimeTypes.size()>0 && _excludeMimeTypes)
+        {
+            String mimeType = _context.getMimeType(request.getRequestURI());
+
+            if (mimeType!=null)
+            {
+                mimeType = MimeTypes.getContentTypeWithoutCharset(mimeType);
+                if (_mimeTypes.contains(mimeType))
+                {
+                    LOG.debug("{} excluded by path suffix {}",this,request);
+                    // handle normally without setting vary header
+                    super.doFilter(request,response,chain);
+                    return;
+                }
+            }
+        }
+
+        //If the Content-Encoding is already set, then we won't compress
+        if (response.getHeader("Content-Encoding") != null)
+        {
+            super.doFilter(request,response,chain);
+            return;
+        }
+        
+        if (_checkGzExists && request.getServletContext()!=null)
+        {
+            String path=request.getServletContext().getRealPath(URIUtil.addPaths(request.getServletPath(),request.getPathInfo()));
+            if (path!=null)
+            {
+                File gz=new File(path+".gz");
+                if (gz.exists())
+                {
+                    LOG.debug("{} gzip exists {}",this,request);
+                    // allow default servlet to handle
+                    super.doFilter(request,response,chain);
+                    return;
+                }
+            }
+        }
+        
+        // Special handling for etags
+        String etag = request.getHeader("If-None-Match"); 
+        if (etag!=null)
+        {
+            if (etag.contains(ETAG_GZIP))
+                request.setAttribute(ETAG,etag.replace(ETAG_GZIP,""));
+        }
+
+        HttpOutput out = channel.getResponse().getHttpOutput();
+        if (!(out instanceof GzipHttpOutput))
+        {
+            if (out.getClass()!=HttpOutput.class)
+                throw new IllegalStateException();
+            channel.getResponse().setHttpOutput(out = new GzipHttpOutput(channel));
+        }
+        
+        GzipHttpOutput cout=(GzipHttpOutput)out;
+        
+        try
+        {
+            cout.mightCompress(this);
+            super.doFilter(request,response,chain);
+        }
+        catch(Throwable e)
+        {
+            LOG.debug("{} excepted {}",this,request,e);
+            if (!response.isCommitted())
+            {
+                cout.resetBuffer();
+                cout.noCompressionIfPossible();
+            }
+            throw e;
+        }
+    }
+
+
+    /**
+     * Checks to see if the userAgent is excluded
+     *
+     * @param ua
+     *            the user agent
+     * @return boolean true if excluded
+     */
+    private boolean isExcludedAgent(String ua)
+    {
+        if (ua == null)
+            return false;
+
+        if (_excludedAgents != null)
+        {
+            if (_excludedAgents.contains(ua))
+            {
+                return true;
+            }
+        }
+        if (_excludedAgentPatterns != null)
+        {
+            for (Pattern pattern : _excludedAgentPatterns)
+            {
+                if (pattern.matcher(ua).matches())
+                {
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Checks to see if the path is excluded
+     *
+     * @param requestURI
+     *            the request uri
+     * @return boolean true if excluded
+     */
+    private boolean isExcludedPath(String requestURI)
+    {
+        if (requestURI == null)
+            return false;
+        if (_excludedPaths != null)
+        {
+            for (String excludedPath : _excludedPaths)
+            {
+                if (requestURI.startsWith(excludedPath))
+                {
+                    return true;
+                }
+            }
+        }
+        if (_excludedPathPatterns != null)
+        {
+            for (Pattern pattern : _excludedPathPatterns)
+            {
+                if (pattern.matcher(requestURI).matches())
+                {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public HttpField getVaryField()
+    {
+        return _vary;
+    }
+
+    @Override
+    public Deflater getDeflater(Request request, long content_length)
+    {
+        String ua = getUserAgent(request);
+        if (ua!=null && isExcludedAgent(ua))
+        {
+            LOG.debug("{} excluded user agent {}",this,request);
+            return null;
+        }
+        
+        if (content_length>=0 && content_length<_minGzipSize)
+        {
+            LOG.debug("{} excluded minGzipSize {}",this,request);
+            return null;
+        }
+        
+        String accept = request.getHttpFields().get(HttpHeader.ACCEPT_ENCODING);
+        if (accept==null)
+        {
+            LOG.debug("{} excluded !accept {}",this,request);
+            return null;
+        }
+        
+        boolean gzip=false;
+        if (GZIP.equals(accept) || accept.startsWith("gzip,"))
+            gzip=true;
+        else
+        {
+            List<String> list=HttpFields.qualityList(request.getHttpFields().getValues(HttpHeader.ACCEPT_ENCODING.asString(),","));
+            for (String a:list)
+            {
+                if (GZIP.equalsIgnoreCase(HttpFields.valueParameters(a,null)))
+                {
+                    gzip=true;
+                    break;
+                }
+            }
+        }
+        
+        if (!gzip)
+        {
+            LOG.debug("{} excluded not gzip accept {}",this,request);
+            return null;
+        }
+        
+        Deflater df = _deflater.get();
+        if (df==null)
+            df=new Deflater(_deflateCompressionLevel,_deflateNoWrap);        
+        else
+            _deflater.set(null);
+        
+        return df;
+    }
+
+    @Override
+    public void recycle(Deflater deflater)
+    {
+        deflater.reset();
+        if (_deflater.get()==null)
+            _deflater.set(deflater);
+        
+    }
+    
+    @Override
+    public boolean isExcludedMimeType(String mimetype)
+    {
+        return _mimeTypes.contains(mimetype) == _excludeMimeTypes;
+    }
+
+    @Override
+    public int getBufferSize()
+    {
+        return _bufferSize;
+    }
+    
+    
+}
diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/BalancerServlet.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/BalancerServlet.java
deleted file mode 100644
index eb0ff31..0000000
--- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/BalancerServlet.java
+++ /dev/null
@@ -1,422 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.servlets;
-
-import java.net.MalformedURLException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import javax.servlet.ServletConfig;
-import javax.servlet.ServletException;
-import javax.servlet.UnavailableException;
-import javax.servlet.http.Cookie;
-import javax.servlet.http.HttpServletRequest;
-
-import org.eclipse.jetty.http.HttpURI;
-import org.eclipse.jetty.server.Request;
-
-/**
- * 6
- */
-public class BalancerServlet extends ProxyServlet
-{
-
-    private static final class BalancerMember
-    {
-
-        private String _name;
-
-        private String _proxyTo;
-
-        private HttpURI _backendURI;
-
-        public BalancerMember(String name, String proxyTo)
-        {
-            super();
-            _name = name;
-            _proxyTo = proxyTo;
-            _backendURI = new HttpURI(_proxyTo);
-        }
-
-        public String getProxyTo()
-        {
-            return _proxyTo;
-        }
-
-        public HttpURI getBackendURI()
-        {
-            return _backendURI;
-        }
-
-        @Override
-        public String toString()
-        {
-            return "BalancerMember [_name=" + _name + ", _proxyTo=" + _proxyTo + "]";
-        }
-
-        @Override
-        public int hashCode()
-        {
-            final int prime = 31;
-            int result = 1;
-            result = prime * result + ((_name == null)?0:_name.hashCode());
-            return result;
-        }
-
-        @Override
-        public boolean equals(Object obj)
-        {
-            if (this == obj)
-                return true;
-            if (obj == null)
-                return false;
-            if (getClass() != obj.getClass())
-                return false;
-            BalancerMember other = (BalancerMember)obj;
-            if (_name == null)
-            {
-                if (other._name != null)
-                    return false;
-            }
-            else if (!_name.equals(other._name))
-                return false;
-            return true;
-        }
-
-    }
-
-    private static final class RoundRobinIterator implements Iterator<BalancerMember>
-    {
-
-        private BalancerMember[] _balancerMembers;
-
-        private AtomicInteger _index;
-
-        public RoundRobinIterator(Collection<BalancerMember> balancerMembers)
-        {
-            _balancerMembers = (BalancerMember[])balancerMembers.toArray(new BalancerMember[balancerMembers.size()]);
-            _index = new AtomicInteger(-1);
-        }
-
-        public boolean hasNext()
-        {
-            return true;
-        }
-
-        public BalancerMember next()
-        {
-            BalancerMember balancerMember = null;
-            while (balancerMember == null)
-            {
-                int currentIndex = _index.get();
-                int nextIndex = (currentIndex + 1) % _balancerMembers.length;
-                if (_index.compareAndSet(currentIndex,nextIndex))
-                {
-                    balancerMember = _balancerMembers[nextIndex];
-                }
-            }
-            return balancerMember;
-        }
-
-        public void remove()
-        {
-            throw new UnsupportedOperationException();
-        }
-
-    }
-
-    private static final String BALANCER_MEMBER_PREFIX = "BalancerMember.";
-
-    private static final List<String> FORBIDDEN_CONFIG_PARAMETERS;
-    static
-    {
-        List<String> params = new LinkedList<String>();
-        params.add("HostHeader");
-        params.add("whiteList");
-        params.add("blackList");
-        FORBIDDEN_CONFIG_PARAMETERS = Collections.unmodifiableList(params);
-    }
-
-    private static final List<String> REVERSE_PROXY_HEADERS;
-    static
-    {
-        List<String> params = new LinkedList<String>();
-        params.add("Location");
-        params.add("Content-Location");
-        params.add("URI");
-        REVERSE_PROXY_HEADERS = Collections.unmodifiableList(params);
-    }
-
-    private static final String JSESSIONID = "jsessionid";
-
-    private static final String JSESSIONID_URL_PREFIX = JSESSIONID + "=";
-
-    private boolean _stickySessions;
-
-    private Set<BalancerMember> _balancerMembers = new HashSet<BalancerMember>();
-
-    private boolean _proxyPassReverse;
-
-    private RoundRobinIterator _roundRobinIterator;
-
-    @Override
-    public void init(ServletConfig config) throws ServletException
-    {
-        validateConfig(config);
-        super.init(config);
-        initStickySessions(config);
-        initBalancers(config);
-        initProxyPassReverse(config);
-        postInit();
-    }
-
-    private void validateConfig(ServletConfig config) throws ServletException
-    {
-        @SuppressWarnings("unchecked")
-        List<String> initParameterNames = Collections.list(config.getInitParameterNames());
-        for (String initParameterName : initParameterNames)
-        {
-            if (FORBIDDEN_CONFIG_PARAMETERS.contains(initParameterName))
-            {
-                throw new UnavailableException(initParameterName + " not supported in " + getClass().getName());
-            }
-        }
-    }
-
-    private void initStickySessions(ServletConfig config) throws ServletException
-    {
-        _stickySessions = "true".equalsIgnoreCase(config.getInitParameter("StickySessions"));
-    }
-
-    private void initBalancers(ServletConfig config) throws ServletException
-    {
-        Set<String> balancerNames = getBalancerNames(config);
-        for (String balancerName : balancerNames)
-        {
-            String memberProxyToParam = BALANCER_MEMBER_PREFIX + balancerName + ".ProxyTo";
-            String proxyTo = config.getInitParameter(memberProxyToParam);
-            if (proxyTo == null || proxyTo.trim().length() == 0)
-            {
-                throw new UnavailableException(memberProxyToParam + " parameter is empty.");
-            }
-            _balancerMembers.add(new BalancerMember(balancerName,proxyTo));
-        }
-    }
-
-    private void initProxyPassReverse(ServletConfig config)
-    {
-        _proxyPassReverse = "true".equalsIgnoreCase(config.getInitParameter("ProxyPassReverse"));
-    }
-
-    private void postInit()
-    {
-        _roundRobinIterator = new RoundRobinIterator(_balancerMembers);
-    }
-
-    private Set<String> getBalancerNames(ServletConfig config) throws ServletException
-    {
-        Set<String> names = new HashSet<String>();
-        @SuppressWarnings("unchecked")
-        List<String> initParameterNames = Collections.list(config.getInitParameterNames());
-        for (String initParameterName : initParameterNames)
-        {
-            if (!initParameterName.startsWith(BALANCER_MEMBER_PREFIX))
-            {
-                continue;
-            }
-            int endOfNameIndex = initParameterName.lastIndexOf(".");
-            if (endOfNameIndex <= BALANCER_MEMBER_PREFIX.length())
-            {
-                throw new UnavailableException(initParameterName + " parameter does not provide a balancer member name");
-            }
-            names.add(initParameterName.substring(BALANCER_MEMBER_PREFIX.length(),endOfNameIndex));
-        }
-        return names;
-    }
-
-    @Override
-    protected HttpURI proxyHttpURI(HttpServletRequest request, String uri) throws MalformedURLException
-    {
-        BalancerMember balancerMember = selectBalancerMember(request);
-        try
-        {
-            URI dstUri = new URI(balancerMember.getProxyTo() + "/" + uri).normalize();
-            return new HttpURI(dstUri.toString());
-        }
-        catch (URISyntaxException e)
-        {
-            throw new MalformedURLException(e.getMessage());
-        }
-    }
-
-    private BalancerMember selectBalancerMember(HttpServletRequest request)
-    {
-        BalancerMember balancerMember = null;
-        if (_stickySessions)
-        {
-            String name = getBalancerMemberNameFromSessionId(request);
-            if (name != null)
-            {
-                balancerMember = findBalancerMemberByName(name);
-                if (balancerMember != null)
-                {
-                    return balancerMember;
-                }
-            }
-        }
-        return _roundRobinIterator.next();
-    }
-
-    private BalancerMember findBalancerMemberByName(String name)
-    {
-        BalancerMember example = new BalancerMember(name,"");
-        for (BalancerMember balancerMember : _balancerMembers)
-        {
-            if (balancerMember.equals(example))
-            {
-                return balancerMember;
-            }
-        }
-        return null;
-    }
-
-    private String getBalancerMemberNameFromSessionId(HttpServletRequest request)
-    {
-        String name = getBalancerMemberNameFromSessionCookie(request);
-        if (name == null)
-        {
-            name = getBalancerMemberNameFromURL(request);
-        }
-        return name;
-    }
-
-    private String getBalancerMemberNameFromSessionCookie(HttpServletRequest request)
-    {
-        Cookie[] cookies = request.getCookies();
-        String name = null;
-        for (Cookie cookie : cookies)
-        {
-            if (JSESSIONID.equalsIgnoreCase(cookie.getName()))
-            {
-                name = extractBalancerMemberNameFromSessionId(cookie.getValue());
-                break;
-            }
-        }
-        return name;
-    }
-
-    private String getBalancerMemberNameFromURL(HttpServletRequest request)
-    {
-        String name = null;
-        String requestURI = request.getRequestURI();
-        int idx = requestURI.lastIndexOf(";");
-        if (idx != -1)
-        {
-            String requestURISuffix = requestURI.substring(idx);
-            if (requestURISuffix.startsWith(JSESSIONID_URL_PREFIX))
-            {
-                name = extractBalancerMemberNameFromSessionId(requestURISuffix.substring(JSESSIONID_URL_PREFIX.length()));
-            }
-        }
-        return name;
-    }
-
-    private String extractBalancerMemberNameFromSessionId(String sessionId)
-    {
-        String name = null;
-        int idx = sessionId.lastIndexOf(".");
-        if (idx != -1)
-        {
-            String sessionIdSuffix = sessionId.substring(idx + 1);
-            name = (sessionIdSuffix.length() > 0)?sessionIdSuffix:null;
-        }
-        return name;
-    }
-
-    @Override
-    protected String filterResponseHeaderValue(String headerName, String headerValue, HttpServletRequest request)
-    {
-        if (_proxyPassReverse && REVERSE_PROXY_HEADERS.contains(headerName))
-        {
-            HttpURI locationURI = new HttpURI(headerValue);
-            if (isAbsoluteLocation(locationURI) && isBackendLocation(locationURI))
-            {
-                Request jettyRequest = (Request)request;
-                URI reverseUri;
-                try
-                {
-                    reverseUri = new URI(jettyRequest.getRootURL().append(locationURI.getCompletePath()).toString()).normalize();
-                    return reverseUri.toURL().toString();
-                }
-                catch (Exception e)
-                {
-                    _log.warn("Not filtering header response",e);
-                    return headerValue;
-                }
-            }
-        }
-        return headerValue;
-    }
-
-    private boolean isBackendLocation(HttpURI locationURI)
-    {
-        for (BalancerMember balancerMember : _balancerMembers)
-        {
-            HttpURI backendURI = balancerMember.getBackendURI();
-            if (backendURI.getHost().equals(locationURI.getHost()) && backendURI.getScheme().equals(locationURI.getScheme())
-                    && backendURI.getPort() == locationURI.getPort())
-            {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    private boolean isAbsoluteLocation(HttpURI locationURI)
-    {
-        return locationURI.getHost() != null;
-    }
-
-    @Override
-    public String getHostHeader()
-    {
-        throw new UnsupportedOperationException("HostHeader not supported in " + getClass().getName());
-    }
-
-    @Override
-    public void setHostHeader(String hostHeader)
-    {
-        throw new UnsupportedOperationException("HostHeader not supported in " + getClass().getName());
-    }
-
-    @Override
-    public boolean validateDestination(String host, String path)
-    {
-        return true;
-    }
-
-}
diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/CGI.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/CGI.java
index a1229e4..87679c4 100644
--- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/CGI.java
+++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/CGI.java
@@ -24,17 +24,19 @@ import java.io.InputStream;
 import java.io.OutputStream;
 import java.io.OutputStreamWriter;
 import java.io.Writer;
+import java.nio.charset.Charset;
 import java.util.Enumeration;
 import java.util.HashMap;
 import java.util.Locale;
 import java.util.Map;
 
+import javax.servlet.AsyncContext;
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import org.eclipse.jetty.http.HttpMethods;
+import org.eclipse.jetty.http.HttpMethod;
 import org.eclipse.jetty.util.IO;
 import org.eclipse.jetty.util.MultiMap;
 import org.eclipse.jetty.util.StringUtil;
@@ -45,32 +47,42 @@ import org.eclipse.jetty.util.log.Logger;
 //-----------------------------------------------------------------------------
 /**
  * CGI Servlet.
- * <p/>
- * The cgi bin directory can be set with the "cgibinResourceBase" init parameter or it will default to the resource base of the context. If the
- * "cgibinResourceBaseIsRelative" init parameter is set the resource base is relative to the webapp. For example "WEB-INF/cgi" would work.
- * <br/>
- * Not that this only works for extracted war files as "jar cf" will not reserve the execute permissions on the cgi files.
- * <p/>
- * The "commandPrefix" init parameter may be used to set a prefix to all commands passed to exec. This can be used on systems that need assistance to execute a
- * particular file type. For example on windows this can be set to "perl" so that perl scripts are executed.
- * <p/>
- * The "Path" init param is passed to the exec environment as PATH. Note: Must be run unpacked somewhere in the filesystem.
- * <p/>
- * Any initParameter that starts with ENV_ is used to set an environment variable with the name stripped of the leading ENV_ and using the init parameter value.
+ * <p>
+ * 
+ * The following init parameters are used to configure this servlet:
+ * <dl>
+ * <dt>cgibinResourceBase</dt>
+ * <dd>Path to the cgi bin directory if set or it will default to the resource base of the context.</dd>
+ * <dt>resourceBase</dt>
+ * <dd>An alias for cgibinResourceBase.</dd>
+ * <dt>cgibinResourceBaseIsRelative</dt>
+ * <dd>If true then cgibinResourceBase is relative to the webapp (eg "WEB-INF/cgi")</dd>
+ * <dt>commandPrefix</dt>
+ * <dd>may be used to set a prefix to all commands passed to exec. This can be used on systems that need assistance to execute a
+ * particular file type. For example on windows this can be set to "perl" so that perl scripts are executed.</dd>
+ * <dt>Path</dt>
+ * <dd>passed to the exec environment as PATH.</dd>
+ * <dt>ENV_*</dt>
+ * <dd>used to set an arbitrary environment variable with the name stripped of the leading ENV_ and using the init parameter value</dd>
+ * <dt>useFullPath</dt>
+ * <dd>If true, the full URI path within the context is used for the exec command, otherwise a search is done for a partial URL that matches an exec Command</dd>
+ * <dt>ignoreExitState</dt>
+ * <dd>If true then do not act on a non-zero exec exit status")</dd>
+ * </dl>
+ * 
  */
 public class CGI extends HttpServlet
 {
-    /**
-     *
-     */
-    private static final long serialVersionUID = -6182088932884791073L;
+    private static final long serialVersionUID = -6182088932884791074L;
 
     private static final Logger LOG = Log.getLogger(CGI.class);
 
     private boolean _ok;
     private File _docRoot;
+    private boolean _cgiBinProvided;
     private String _path;
     private String _cmdPrefix;
+    private boolean _useFullPath;
     private EnvList _env;
     private boolean _ignoreExitState;
     private boolean _relative;
@@ -81,16 +93,22 @@ public class CGI extends HttpServlet
     {
         _env = new EnvList();
         _cmdPrefix = getInitParameter("commandPrefix");
+        _useFullPath = Boolean.parseBoolean(getInitParameter("useFullPath"));
         _relative = Boolean.parseBoolean(getInitParameter("cgibinResourceBaseIsRelative"));
 
         String tmp = getInitParameter("cgibinResourceBase");
-        if (tmp == null)
+        if (tmp != null)
+            _cgiBinProvided = true;
+        else
         {
             tmp = getInitParameter("resourceBase");
-            if (tmp == null)
+            if (tmp != null)
+                _cgiBinProvided = true;
+            else
                 tmp = getServletContext().getRealPath("/");
         }
-        else if (_relative)
+
+        if (_relative && _cgiBinProvided)
         {
             tmp = getServletContext().getRealPath(tmp);
         }
@@ -135,10 +153,10 @@ public class CGI extends HttpServlet
             _env.set("PATH",_path);
 
         _ignoreExitState = "true".equalsIgnoreCase(getInitParameter("ignoreExitState"));
-        Enumeration e = getInitParameterNames();
+        Enumeration<String> e = getInitParameterNames();
         while (e.hasMoreElements())
         {
-            String n = (String)e.nextElement();
+            String n = e.nextElement();
             if (n != null && n.startsWith("ENV_"))
                 _env.set(n.substring(4),getInitParameter(n));
         }
@@ -164,7 +182,6 @@ public class CGI extends HttpServlet
             return;
         }
 
-        String pathInContext = (_relative?"":StringUtil.nonNull(req.getServletPath())) + StringUtil.nonNull(req.getPathInfo());
         if (LOG.isDebugEnabled())
         {
             LOG.debug("CGI: ContextPath : " + req.getContextPath());
@@ -178,66 +195,68 @@ public class CGI extends HttpServlet
         // pathInContext may actually comprises scriptName/pathInfo...We will
         // walk backwards up it until we find the script - the rest must
         // be the pathInfo;
+        String pathInContext = (_relative ? "" : StringUtil.nonNull(req.getServletPath())) + StringUtil.nonNull(req.getPathInfo());
+        File execCmd = new File(_docRoot, pathInContext);
+        String pathInfo = pathInContext;
 
-        String both = pathInContext;
-        String first = both;
-        String last = "";
-
-        File exe = new File(_docRoot,first);
-
-        while ((first.endsWith("/") || !exe.exists()) && first.length() >= 0)
+        if(!_useFullPath)
         {
-            int index = first.lastIndexOf('/');
-
-            first = first.substring(0,index);
-            last = both.substring(index,both.length());
-            exe = new File(_docRoot,first);
-        }
+            String path = pathInContext;
+            String info = "";
 
-        if (first.length() == 0 || !exe.exists() || exe.isDirectory() || !exe.getCanonicalPath().equals(exe.getAbsolutePath()))
-        {
-            res.sendError(404);
-        }
-        else
-        {
-            if (LOG.isDebugEnabled())
+            // Search docroot for a matching execCmd
+            while ((path.endsWith("/") || !execCmd.exists()) && path.length() >= 0)
+            {
+                int index = path.lastIndexOf('/');
+                path = path.substring(0,index);
+                info = pathInContext.substring(index,pathInContext.length());
+                execCmd = new File(_docRoot,path);
+            }
+    
+            if (path.length() == 0 || !execCmd.exists() || execCmd.isDirectory() || !execCmd.getCanonicalPath().equals(execCmd.getAbsolutePath()))
             {
-                LOG.debug("CGI: script is " + exe);
-                LOG.debug("CGI: pathInfo is " + last);
+                res.sendError(404);
             }
-            exec(exe,last,req,res);
+            
+            pathInfo = info;
         }
+        exec(execCmd,pathInfo,req,res);
     }
 
-    /* ------------------------------------------------------------ */
+    /** executes the CGI process
     /*
-     * @param root @param path @param req @param res @exception IOException
+     * @param command the command to execute, this command is prefixed by
+     *  the context parameter "commandPrefix".
+     * @param pathInfo The PATH_INFO to process,
+     *  see http://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletRequest.html#getPathInfo%28%29. Cannot be null
+     * @param req
+     * @param res
+     * @exception IOException
      */
     private void exec(File command, String pathInfo, HttpServletRequest req, HttpServletResponse res) throws IOException
     {
-        String path = command.getAbsolutePath();
-        File dir = command.getParentFile();
-        String scriptName = req.getRequestURI().substring(0,req.getRequestURI().length() - pathInfo.length());
-        String scriptPath = getServletContext().getRealPath(scriptName);
-        String pathTranslated = req.getPathTranslated();
+        assert req != null;
+        assert res != null;
+        assert pathInfo != null;
+        assert command != null;
 
-        int len = req.getContentLength();
-        if (len < 0)
-            len = 0;
-        if ((pathTranslated == null) || (pathTranslated.length() == 0))
-            pathTranslated = path;
+        if (LOG.isDebugEnabled())
+        {
+            LOG.debug("CGI: script is " + command);
+            LOG.debug("CGI: pathInfo is " + pathInfo);
+        }
 
         String bodyFormEncoded = null;
-        if ((HttpMethods.POST.equals(req.getMethod()) || HttpMethods.PUT.equals(req.getMethod())) && "application/x-www-form-urlencoded".equals(req.getContentType()))
+        if ((HttpMethod.POST.equals(req.getMethod()) || HttpMethod.PUT.equals(req.getMethod())) && "application/x-www-form-urlencoded".equals(req.getContentType()))
         {
             MultiMap<String> parameterMap = new MultiMap<String>();
-            Enumeration names = req.getParameterNames();
+            Enumeration<String> names = req.getParameterNames();
             while (names.hasMoreElements())
             {
-                String parameterName = (String)names.nextElement();
+                String parameterName = names.nextElement();
                 parameterMap.addValues(parameterName, req.getParameterValues(parameterName));
             }
-            bodyFormEncoded = UrlEncoded.encode(parameterMap, req.getCharacterEncoding(), true);
+            bodyFormEncoded = UrlEncoded.encode(parameterMap, Charset.forName(req.getCharacterEncoding()), true);
         }
 
         EnvList env = new EnvList(_env);
@@ -245,24 +264,33 @@ public class CGI extends HttpServlet
         // look at :
         // http://Web.Golux.Com/coar/cgi/draft-coar-cgi-v11-03-clean.html#6.1.1
         env.set("AUTH_TYPE", req.getAuthType());
+
+        int contentLen = req.getContentLength();
+        if (contentLen < 0)
+            contentLen = 0;
         if (bodyFormEncoded != null)
         {
             env.set("CONTENT_LENGTH", Integer.toString(bodyFormEncoded.length()));
         }
         else
         {
-            env.set("CONTENT_LENGTH", Integer.toString(len));
+            env.set("CONTENT_LENGTH", Integer.toString(contentLen));
         }
         env.set("CONTENT_TYPE", req.getContentType());
         env.set("GATEWAY_INTERFACE", "CGI/1.1");
-        if ((pathInfo != null) && (pathInfo.length() > 0))
+        if (pathInfo.length() > 0)
         {
             env.set("PATH_INFO", pathInfo);
         }
+
+        String pathTranslated = req.getPathTranslated();
+        if ((pathTranslated == null) || (pathTranslated.length() == 0))
+            pathTranslated = pathInfo;
         env.set("PATH_TRANSLATED", pathTranslated);
         env.set("QUERY_STRING", req.getQueryString());
         env.set("REMOTE_ADDR", req.getRemoteAddr());
         env.set("REMOTE_HOST", req.getRemoteHost());
+
         // The identity information reported about the connection by a
         // RFC 1413 [11] request to the remote agent, if
         // available. Servers MAY choose not to support this feature, or
@@ -270,17 +298,33 @@ public class CGI extends HttpServlet
         // "REMOTE_IDENT" => "NYI"
         env.set("REMOTE_USER", req.getRemoteUser());
         env.set("REQUEST_METHOD", req.getMethod());
-        env.set("SCRIPT_NAME", scriptName);
+
+        String scriptPath;
+        String scriptName;
+        // use docRoot for scriptPath, too
+        if(_cgiBinProvided) 
+        {
+            scriptPath = command.getAbsolutePath();
+            scriptName = scriptPath.substring(_docRoot.getAbsolutePath().length());
+        } 
+        else 
+        {
+            String requestURI = req.getRequestURI();
+            scriptName = requestURI.substring(0,requestURI.length() - pathInfo.length());
+            scriptPath = getServletContext().getRealPath(scriptName);
+        }
         env.set("SCRIPT_FILENAME", scriptPath);
+        env.set("SCRIPT_NAME", scriptName);
+
         env.set("SERVER_NAME", req.getServerName());
         env.set("SERVER_PORT", Integer.toString(req.getServerPort()));
         env.set("SERVER_PROTOCOL", req.getProtocol());
         env.set("SERVER_SOFTWARE", getServletContext().getServerInfo());
 
-        Enumeration enm = req.getHeaderNames();
+        Enumeration<String> enm = req.getHeaderNames();
         while (enm.hasMoreElements())
         {
-            String name = (String)enm.nextElement();
+            String name = enm.nextElement();
             String value = req.getHeader(name);
             env.set("HTTP_" + name.toUpperCase(Locale.ENGLISH).replace('-','_'),value);
         }
@@ -291,37 +335,53 @@ public class CGI extends HttpServlet
         // "SERVER_URL" => "NYI - http://us0245",
         // "TZ" => System.getProperty("user.timezone"),
 
-        // are we meant to decode args here ? or does the script get them
-        // via PATH_INFO ? if we are, they should be decoded and passed
+        // are we meant to decode args here? or does the script get them
+        // via PATH_INFO? if we are, they should be decoded and passed
         // into exec here...
-        String execCmd = path;
-        if ((execCmd.charAt(0) != '"') && (execCmd.indexOf(" ") >= 0))
+        String absolutePath = command.getAbsolutePath();
+        String execCmd = absolutePath;
+
+        // escape the execCommand
+        if (execCmd.length() > 0 && execCmd.charAt(0) != '"' && execCmd.indexOf(" ") >= 0)
             execCmd = "\"" + execCmd + "\"";
+
         if (_cmdPrefix != null)
             execCmd = _cmdPrefix + " " + execCmd;
 
+        assert execCmd != null;
         LOG.debug("Environment: " + env.getExportString());
         LOG.debug("Command: " + execCmd);
 
-        Process p;
-        if (dir == null)
-            p = Runtime.getRuntime().exec(execCmd, env.getEnvArray());
-        else
-            p = Runtime.getRuntime().exec(execCmd, env.getEnvArray(), dir);
+        final Process p = Runtime.getRuntime().exec(execCmd, env.getEnvArray(), _docRoot);
 
         // hook processes input to browser's output (async)
         if (bodyFormEncoded != null)
             writeProcessInput(p, bodyFormEncoded);
-        else if (len > 0)
-            writeProcessInput(p, req.getInputStream(), len);
-
-        IO.copyThread(p.getErrorStream(), System.err);
+        else if (contentLen > 0)
+            writeProcessInput(p, req.getInputStream(), contentLen);
 
         // hook processes output to browser's input (sync)
         // if browser closes stream, we should detect it and kill process...
         OutputStream os = null;
+        AsyncContext async=req.startAsync();
         try
         {
+            async.start(new Runnable()
+            {
+                @Override
+                public void run()
+                {
+                    try
+                    {
+                        IO.copy(p.getErrorStream(), System.err);
+                    }
+                    catch (IOException e)
+                    {
+                        LOG.warn(e);
+                    }
+                }
+            });
+
             // read any headers off the top of our input stream
             // NOTE: Multiline header items not supported!
             String line = null;
@@ -366,7 +426,7 @@ public class CGI extends HttpServlet
                 int exitValue = p.exitValue();
                 if (0 != exitValue)
                 {
-                    LOG.warn("Non-zero exit status (" + exitValue + ") from CGI program: " + path);
+                    LOG.warn("Non-zero exit status (" + exitValue + ") from CGI program: " + absolutePath);
                     if (!res.isCommitted())
                         res.sendError(500,"Failed to exec CGI");
                 }
@@ -376,7 +436,7 @@ public class CGI extends HttpServlet
         {
             // browser has probably closed its input stream - we
             // terminate and clean up...
-            LOG.debug("CGI: Client closed connection!");
+            LOG.debug("CGI: Client closed connection!", e);
         }
         catch (InterruptedException ie)
         {
@@ -397,6 +457,7 @@ public class CGI extends HttpServlet
             }
             p.destroy();
             // LOG.debug("CGI: terminated!");
+            async.complete();
         }
     }
 
@@ -404,13 +465,15 @@ public class CGI extends HttpServlet
     {
         new Thread(new Runnable()
         {
+            @Override
             public void run()
             {
                 try
                 {
-                    Writer outToCgi = new OutputStreamWriter(p.getOutputStream());
-                    outToCgi.write(input);
-                    outToCgi.close();
+                    try (Writer outToCgi = new OutputStreamWriter(p.getOutputStream()))
+                    {
+                        outToCgi.write(input);
+                    }
                 }
                 catch (IOException e)
                 {
@@ -426,6 +489,7 @@ public class CGI extends HttpServlet
 
         new Thread(new Runnable()
         {
+            @Override
             public void run()
             {
                 try
@@ -512,4 +576,4 @@ public class CGI extends HttpServlet
             return envMap.toString();
         }
     }
-}
+}
\ No newline at end of file
diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/CloseableDoSFilter.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/CloseableDoSFilter.java
index 37a21da..eac9696 100644
--- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/CloseableDoSFilter.java
+++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/CloseableDoSFilter.java
@@ -18,36 +18,24 @@
 
 package org.eclipse.jetty.servlets;
 
-import java.io.IOException;
-
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import org.eclipse.jetty.server.AbstractHttpConnection;
+import org.eclipse.jetty.server.HttpChannel;
 import org.eclipse.jetty.server.Request;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
 
 /* ------------------------------------------------------------ */
 /** Closeable DoS Filter.
  * This is an extension to the {@link DoSFilter} that uses Jetty APIs to allow
- * connections to be closed cleanly. 
+ * connections to be closed cleanly.
  */
 
 public class CloseableDoSFilter extends DoSFilter
 {
-    private static final Logger LOG = Log.getLogger(CloseableDoSFilter.class);
-
+    @Override
     protected void closeConnection(HttpServletRequest request, HttpServletResponse response, Thread thread)
     {
-        try
-        {
-            Request base_request=(request instanceof Request)?(Request)request:AbstractHttpConnection.getCurrentConnection().getRequest();
-            base_request.getConnection().getEndPoint().close();
-        }
-        catch(IOException e)
-        {
-            LOG.warn(e);
-        }
+        Request base_request=(request instanceof Request)?(Request)request:HttpChannel.getCurrentHttpChannel().getRequest();
+        base_request.getHttpChannel().getEndPoint().close();
     }
 }
diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/ConcatServlet.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/ConcatServlet.java
index 3dd2a10..5207f85 100644
--- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/ConcatServlet.java
+++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/ConcatServlet.java
@@ -19,6 +19,8 @@
 package org.eclipse.jetty.servlets;
 
 import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
 
 import javax.servlet.RequestDispatcher;
 import javax.servlet.ServletContext;
@@ -27,99 +29,118 @@ import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-/* ------------------------------------------------------------ */
-/** Concatenation Servlet
- * This servlet may be used to concatenate multiple resources into
- * a single response.  It is intended to be used to load multiple
- * javascript or css files, but may be used for any content of the 
- * same mime type that can be meaningfully concatenated.
- * <p>
- * The servlet uses {@link RequestDispatcher#include(javax.servlet.ServletRequest, javax.servlet.ServletResponse)}
+import org.eclipse.jetty.util.URIUtil;
+
+/**
+ * <p>This servlet may be used to concatenate multiple resources into
+ * a single response.</p>
+ * <p>It is intended to be used to load multiple
+ * javascript or css files, but may be used for any content of the
+ * same mime type that can be meaningfully concatenated.</p>
+ * <p>The servlet uses {@link RequestDispatcher#include(javax.servlet.ServletRequest, javax.servlet.ServletResponse)}
  * to combine the requested content, so dynamically generated content
- * may be combined (Eg engine.js for DWR).
- * <p>
- * The servlet uses parameter names of the query string as resource names
- * relative to the context root.  So these script tags:
+ * may be combined (Eg engine.js for DWR).</p>
+ * <p>The servlet uses parameter names of the query string as resource names
+ * relative to the context root.  So these script tags:</p>
  * <pre>
- *  <script type="text/javascript" src="../js/behaviour.js"></script>
- *  <script type="text/javascript" src="../js/ajax.js&/chat/chat.js"></script>
- *  <script type="text/javascript" src="../chat/chat.js"></script>
- * </pre> can be replaced with the single tag (with the ConcatServlet mapped to /concat):
+ * <script type="text/javascript" src="../js/behaviour.js"></script>
+ * <script type="text/javascript" src="../js/ajax.js&/chat/chat.js"></script>
+ * <script type="text/javascript" src="../chat/chat.js"></script>
+ * </pre>
+ * <p>can be replaced with the single tag (with the {@code ConcatServlet}
+ * mapped to {@code /concat}):</p>
  * <pre>
- *  <script type="text/javascript" src="../concat?/js/behaviour.js&/js/ajax.js&/chat/chat.js"></script>
+ * <script type="text/javascript" src="../concat?/js/behaviour.js&/js/ajax.js&/chat/chat.js"></script>
  * </pre>
- * The {@link ServletContext#getMimeType(String)} method is used to determine the 
- * mime type of each resource.  If the types of all resources do not match, then a 415 
- * UNSUPPORTED_MEDIA_TYPE error is returned.
- * <p>
- * If the init parameter "development" is set to "true" then the servlet will run in
- * development mode and the content will be concatenated on every request. Otherwise
- * the init time of the servlet is used as the lastModifiedTime of the combined content
- * and If-Modified-Since requests are handled with 206 NOT Modified responses if 
- * appropriate. This means that when not in development mode, the servlet must be 
- * restarted before changed content will be served.
- * 
- * 
- *
+ * <p>The {@link ServletContext#getMimeType(String)} method is used to determine the
+ * mime type of each resource. If the types of all resources do not match, then a 415
+ * UNSUPPORTED_MEDIA_TYPE error is returned.</p>
+ * <p>If the init parameter {@code development} is set to {@code true} then the servlet
+ * will run in development mode and the content will be concatenated on every request.</p>
+ * <p>Otherwise the init time of the servlet is used as the lastModifiedTime of the combined content
+ * and If-Modified-Since requests are handled with 304 NOT Modified responses if
+ * appropriate. This means that when not in development mode, the servlet must be
+ * restarted before changed content will be served.</p>
  */
 public class ConcatServlet extends HttpServlet
 {
-    boolean _development;
-    long _lastModified;
-    ServletContext _context;
+    private boolean _development;
+    private long _lastModified;
 
-    /* ------------------------------------------------------------ */
+    @Override
     public void init() throws ServletException
     {
-        _lastModified=System.currentTimeMillis();
-        _context=getServletContext();   
-        _development="true".equals(getInitParameter("development"));
+        _lastModified = System.currentTimeMillis();
+        _development = Boolean.parseBoolean(getInitParameter("development"));
     }
 
-    /* ------------------------------------------------------------ */
-    /* 
+    /*
      * @return The start time of the servlet unless in development mode, in which case -1 is returned.
      */
+    @Override
     protected long getLastModified(HttpServletRequest req)
     {
-        return _development?-1:_lastModified;
+        return _development ? -1 : _lastModified;
     }
-    
-    /* ------------------------------------------------------------ */
-    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+
+    @Override
+    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
     {
-        String q=req.getQueryString();
-        if (q==null)
+        String query = request.getQueryString();
+        if (query == null)
         {
-            resp.sendError(HttpServletResponse.SC_NO_CONTENT);
+            response.sendError(HttpServletResponse.SC_NO_CONTENT);
             return;
         }
-        
-        String[] parts = q.split("\\&");
-        String type=null;
-        for (int i=0;i<parts.length;i++)
+
+        List<RequestDispatcher> dispatchers = new ArrayList<>();
+        String[] parts = query.split("\\&");
+        String type = null;
+        for (String part : parts)
         {
-            String t = _context.getMimeType(parts[i]);
-            if (t!=null)
+            String path = URIUtil.canonicalPath(URIUtil.decodePath(part));
+            if (path == null)
+            {
+                response.sendError(HttpServletResponse.SC_NOT_FOUND);
+                return;
+            }
+
+            // Verify that the path is not protected.
+            if (startsWith(path, "/WEB-INF/") || startsWith(path, "/META-INF/"))
+            {
+                response.sendError(HttpServletResponse.SC_NOT_FOUND);
+                return;
+            }
+
+            String t = getServletContext().getMimeType(path);
+            if (t != null)
             {
-                if (type==null)
-                    type=t;
+                if (type == null)
+                {
+                    type = t;
+                }
                 else if (!type.equals(t))
                 {
-                    resp.sendError(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE);
+                    response.sendError(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE);
                     return;
                 }
-            }   
+            }
+
+            RequestDispatcher dispatcher = getServletContext().getRequestDispatcher(path);
+            if (dispatcher != null)
+                dispatchers.add(dispatcher);
         }
 
-        if (type!=null)
-            resp.setContentType(type);
+        if (type != null)
+            response.setContentType(type);
 
-        for (int i=0;i<parts.length;i++)
-        {
-            RequestDispatcher dispatcher=_context.getRequestDispatcher(parts[i]);
-            if (dispatcher!=null)
-                dispatcher.include(req,resp);
-        }
+        for (RequestDispatcher dispatcher : dispatchers)
+            dispatcher.include(request, response);
+    }
+
+    private boolean startsWith(String path, String prefix)
+    {
+        // Case insensitive match.
+        return prefix.regionMatches(true, 0, path, 0, prefix.length());
     }
 }
diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/CrossOriginFilter.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/CrossOriginFilter.java
index fec8a39..e1ba9b9 100644
--- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/CrossOriginFilter.java
+++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/CrossOriginFilter.java
@@ -21,6 +21,7 @@ package org.eclipse.jetty.servlets;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.Enumeration;
 import java.util.List;
 import java.util.regex.Matcher;
@@ -61,20 +62,21 @@ import org.eclipse.jetty.util.log.Logger;
  * <b>GET,POST,HEAD</b></li>
  * <li><b>allowedHeaders</b>, a comma separated list of HTTP headers that
  * are allowed to be specified when accessing the resources. Default value
- * is <b>X-Requested-With,Content-Type,Accept,Origin</b></li>
+ * is <b>X-Requested-With,Content-Type,Accept,Origin</b>. If the value is a single "*",
+ * this means that any headers will be accepted.</li>
  * <li><b>preflightMaxAge</b>, the number of seconds that preflight requests
  * can be cached by the client. Default value is <b>1800</b> seconds, or 30
  * minutes</li>
  * <li><b>allowCredentials</b>, a boolean indicating if the resource allows
- * requests with credentials. Default value is <b>false</b></li>
- * <li><b>exposeHeaders</b>, a comma separated list of HTTP headers that
+ * requests with credentials. Default value is <b>true</b></li>
+ * <li><b>exposedHeaders</b>, a comma separated list of HTTP headers that
  * are allowed to be exposed on the client. Default value is the
  * <b>empty list</b></li>
  * <li><b>chainPreflight</b>, if true preflight requests are chained to their
- * target resource for normal handling (as an OPTION request).  Otherwise the 
- * filter will response to the preflight. Default is true.</li>
+ * target resource for normal handling (as an OPTION request).  Otherwise the
+ * filter will response to the preflight. Default is <b>true</b>.</li>
  * </ul></p>
- * <p>A typical configuration could be:
+ * <p>A typical configuration could be:</p>
  * <pre>
  * <web-app ...>
  *     ...
@@ -88,7 +90,7 @@ import org.eclipse.jetty.util.log.Logger;
  *     </filter-mapping>
  *     ...
  * </web-app>
- * </pre></p>
+ * </pre>
  */
 public class CrossOriginFilter implements Filter
 {
@@ -116,8 +118,11 @@ public class CrossOriginFilter implements Filter
     public static final String CHAIN_PREFLIGHT_PARAM = "chainPreflight";
     private static final String ANY_ORIGIN = "*";
     private static final List<String> SIMPLE_HTTP_METHODS = Arrays.asList("GET", "POST", "HEAD");
+    private static final List<String> DEFAULT_ALLOWED_METHODS = Arrays.asList("GET", "POST", "HEAD");
+    private static final List<String> DEFAULT_ALLOWED_HEADERS = Arrays.asList("X-Requested-With", "Content-Type", "Accept", "Origin");
 
     private boolean anyOriginAllowed;
+    private boolean anyHeadersAllowed;
     private List<String> allowedOrigins = new ArrayList<String>();
     private List<String> allowedMethods = new ArrayList<String>();
     private List<String> allowedHeaders = new ArrayList<String>();
@@ -152,13 +157,17 @@ public class CrossOriginFilter implements Filter
 
         String allowedMethodsConfig = config.getInitParameter(ALLOWED_METHODS_PARAM);
         if (allowedMethodsConfig == null)
-            allowedMethodsConfig = "GET,POST,HEAD";
-        allowedMethods.addAll(Arrays.asList(allowedMethodsConfig.split(",")));
+            allowedMethods.addAll(DEFAULT_ALLOWED_METHODS);
+        else
+            allowedMethods.addAll(Arrays.asList(allowedMethodsConfig.split(",")));
 
         String allowedHeadersConfig = config.getInitParameter(ALLOWED_HEADERS_PARAM);
         if (allowedHeadersConfig == null)
-            allowedHeadersConfig = "X-Requested-With,Content-Type,Accept,Origin";
-        allowedHeaders.addAll(Arrays.asList(allowedHeadersConfig.split(",")));
+            allowedHeaders.addAll(DEFAULT_ALLOWED_HEADERS);
+        else if ("*".equals(allowedHeadersConfig))
+            anyHeadersAllowed = true;
+        else
+            allowedHeaders.addAll(Arrays.asList(allowedHeadersConfig.split(",")));
 
         String preflightMaxAgeConfig = config.getInitParameter(PREFLIGHT_MAX_AGE_PARAM);
         if (preflightMaxAgeConfig == null)
@@ -183,8 +192,8 @@ public class CrossOriginFilter implements Filter
         exposedHeaders.addAll(Arrays.asList(exposedHeadersConfig.split(",")));
 
         String chainPreflightConfig = config.getInitParameter(OLD_CHAIN_PREFLIGHT_PARAM);
-        if (chainPreflightConfig!=null) // TODO remove this
-            LOG.warn("DEPRECATED CONFIGURATION: Use "+CHAIN_PREFLIGHT_PARAM+ " instead of "+OLD_CHAIN_PREFLIGHT_PARAM);
+        if (chainPreflightConfig != null)
+            LOG.warn("DEPRECATED CONFIGURATION: Use " + CHAIN_PREFLIGHT_PARAM + " instead of " + OLD_CHAIN_PREFLIGHT_PARAM);
         else
             chainPreflightConfig = config.getInitParameter(CHAIN_PREFLIGHT_PARAM);
         if (chainPreflightConfig == null)
@@ -194,13 +203,13 @@ public class CrossOriginFilter implements Filter
         if (LOG.isDebugEnabled())
         {
             LOG.debug("Cross-origin filter configuration: " +
-                    ALLOWED_ORIGINS_PARAM + " = " + allowedOriginsConfig + ", " +
-                    ALLOWED_METHODS_PARAM + " = " + allowedMethodsConfig + ", " +
-                    ALLOWED_HEADERS_PARAM + " = " + allowedHeadersConfig + ", " +
-                    PREFLIGHT_MAX_AGE_PARAM + " = " + preflightMaxAgeConfig + ", " +
-                    ALLOW_CREDENTIALS_PARAM + " = " + allowedCredentialsConfig + "," +
-                    EXPOSED_HEADERS_PARAM + " = " + exposedHeadersConfig + "," +
-                    CHAIN_PREFLIGHT_PARAM + " = " + chainPreflightConfig
+                            ALLOWED_ORIGINS_PARAM + " = " + allowedOriginsConfig + ", " +
+                            ALLOWED_METHODS_PARAM + " = " + allowedMethodsConfig + ", " +
+                            ALLOWED_HEADERS_PARAM + " = " + allowedHeadersConfig + ", " +
+                            PREFLIGHT_MAX_AGE_PARAM + " = " + preflightMaxAgeConfig + ", " +
+                            ALLOW_CREDENTIALS_PARAM + " = " + allowedCredentialsConfig + "," +
+                            EXPOSED_HEADERS_PARAM + " = " + exposedHeadersConfig + "," +
+                            CHAIN_PREFLIGHT_PARAM + " = " + chainPreflightConfig
             );
         }
     }
@@ -285,7 +294,7 @@ public class CrossOriginFilter implements Filter
             {
                 if (allowedOrigin.contains("*"))
                 {
-                    Matcher matcher = createMatcher(origin,allowedOrigin);
+                    Matcher matcher = createMatcher(origin, allowedOrigin);
                     if (matcher.matches())
                         return true;
                 }
@@ -307,8 +316,8 @@ public class CrossOriginFilter implements Filter
 
     private String parseAllowedWildcardOriginToRegex(String allowedOrigin)
     {
-        String regex = allowedOrigin.replace(".","\\.");
-        return regex.replace("*",".*"); // we want to be greedy here to match multiple subdomains, thus we use .*
+        String regex = allowedOrigin.replace(".", "\\.");
+        return regex.replace("*", ".*"); // we want to be greedy here to match multiple subdomains, thus we use .*
     }
 
     private boolean isSimpleRequest(HttpServletRequest request)
@@ -338,6 +347,9 @@ public class CrossOriginFilter implements Filter
     private void handleSimpleResponse(HttpServletRequest request, HttpServletResponse response, String origin)
     {
         response.setHeader(ACCESS_CONTROL_ALLOW_ORIGIN_HEADER, origin);
+        //W3C CORS spec http://www.w3.org/TR/cors/#resource-implementation
+        if (!anyOriginAllowed)
+            response.addHeader("Vary", ORIGIN_HEADER);
         if (allowCredentials)
             response.setHeader(ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER, "true");
         if (!exposedHeaders.isEmpty())
@@ -347,18 +359,26 @@ public class CrossOriginFilter implements Filter
     private void handlePreflightResponse(HttpServletRequest request, HttpServletResponse response, String origin)
     {
         boolean methodAllowed = isMethodAllowed(request);
+
         if (!methodAllowed)
             return;
-        boolean headersAllowed = areHeadersAllowed(request);
+        List<String> headersRequested = getAccessControlRequestHeaders(request);
+        boolean headersAllowed = areHeadersAllowed(headersRequested);
         if (!headersAllowed)
             return;
         response.setHeader(ACCESS_CONTROL_ALLOW_ORIGIN_HEADER, origin);
+        //W3C CORS spec http://www.w3.org/TR/cors/#resource-implementation
+        if (!anyOriginAllowed)
+            response.addHeader("Vary", ORIGIN_HEADER);
         if (allowCredentials)
             response.setHeader(ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER, "true");
         if (preflightMaxAge > 0)
             response.setHeader(ACCESS_CONTROL_MAX_AGE_HEADER, String.valueOf(preflightMaxAge));
         response.setHeader(ACCESS_CONTROL_ALLOW_METHODS_HEADER, commify(allowedMethods));
-        response.setHeader(ACCESS_CONTROL_ALLOW_HEADERS_HEADER, commify(allowedHeaders));
+        if (anyHeadersAllowed)
+            response.setHeader(ACCESS_CONTROL_ALLOW_HEADERS_HEADER, commify(headersRequested));
+        else
+            response.setHeader(ACCESS_CONTROL_ALLOW_HEADERS_HEADER, commify(allowedHeaders));
     }
 
     private boolean isMethodAllowed(HttpServletRequest request)
@@ -372,33 +392,51 @@ public class CrossOriginFilter implements Filter
         return result;
     }
 
-    private boolean areHeadersAllowed(HttpServletRequest request)
+    private List<String> getAccessControlRequestHeaders(HttpServletRequest request)
     {
         String accessControlRequestHeaders = request.getHeader(ACCESS_CONTROL_REQUEST_HEADERS_HEADER);
         LOG.debug("{} is {}", ACCESS_CONTROL_REQUEST_HEADERS_HEADER, accessControlRequestHeaders);
+        if (accessControlRequestHeaders == null)
+            return Collections.emptyList();
+
+        List<String> requestedHeaders = new ArrayList<String>();
+        String[] headers = accessControlRequestHeaders.split(",");
+        for (String header : headers)
+        {
+            String h = header.trim();
+            if (h.length() > 0)
+                requestedHeaders.add(h);
+        }
+        return requestedHeaders;
+    }
+
+    private boolean areHeadersAllowed(List<String> requestedHeaders)
+    {
+        if (anyHeadersAllowed)
+        {
+            LOG.debug("Any header is allowed");
+            return true;
+        }
+
         boolean result = true;
-        if (accessControlRequestHeaders != null)
+        for (String requestedHeader : requestedHeaders)
         {
-            String[] headers = accessControlRequestHeaders.split(",");
-            for (String header : headers)
+            boolean headerAllowed = false;
+            for (String allowedHeader : allowedHeaders)
             {
-                boolean headerAllowed = false;
-                for (String allowedHeader : allowedHeaders)
+                if (requestedHeader.equalsIgnoreCase(allowedHeader.trim()))
                 {
-                    if (header.trim().equalsIgnoreCase(allowedHeader.trim()))
-                    {
-                        headerAllowed = true;
-                        break;
-                    }
-                }
-                if (!headerAllowed)
-                {
-                    result = false;
+                    headerAllowed = true;
                     break;
                 }
             }
+            if (!headerAllowed)
+            {
+                result = false;
+                break;
+            }
         }
-        LOG.debug("Headers [{}] are" + (result ? "" : " not") + " among allowed headers {}", accessControlRequestHeaders, allowedHeaders);
+        LOG.debug("Headers [{}] are" + (result ? "" : " not") + " among allowed headers {}", requestedHeaders, allowedHeaders);
         return result;
     }
 
diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/DataRateLimitedServlet.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/DataRateLimitedServlet.java
new file mode 100644
index 0000000..8bf0d40
--- /dev/null
+++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/DataRateLimitedServlet.java
@@ -0,0 +1,315 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlets;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel.MapMode;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.WriteListener;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.HttpOutput;
+
+/**
+ * A servlet that uses the Servlet 3.1 asynchronous IO API to server
+ * static content at a limited data rate.
+ * <p>
+ * Two implementations are supported: <ul>
+ * <li>The <code>StandardDataStream</code> impl uses only standard
+ * APIs, but produces more garbage due to the byte[] nature of the API.  
+ * <li>the <code>JettyDataStream</code> impl uses a Jetty API to write a ByteBuffer
+ * and thus allow the efficient use of file mapped buffers without any
+ * temporary buffer copies (I did tell the JSR that this was a good idea to 
+ * have in the standard!).
+ * </ul>
+ * <p>
+ * The data rate is controlled by setting init parameters:
+ * <dl>
+ * <dt>buffersize</dt><dd>The amount of data in bytes written per write</dd>
+ * <dt>pause</dt><dd>The period in ms to wait after a write before attempting another</dd>
+ * <dt>pool</dt><dd>The size of the thread pool used to service the writes (defaults to available processors)</dd>
+ * </dl>
+ * Thus if buffersize = 1024 and pause = 100, the data rate will be limited to 10KB per second.
+ */
+public class DataRateLimitedServlet extends HttpServlet
+{
+    private static final long serialVersionUID = -4771757707068097025L;
+    private int buffersize=8192;
+    private int pause=100;
+    ScheduledThreadPoolExecutor scheduler;
+    private final ConcurrentHashMap<String, ByteBuffer> cache=new ConcurrentHashMap<>();
+    
+    @Override
+    public void init() throws ServletException
+    {
+        // read the init params
+        String tmp = getInitParameter("buffersize");
+        if (tmp!=null)
+            buffersize=Integer.parseInt(tmp);
+        tmp = getInitParameter("pause");
+        if (tmp!=null)
+            pause=Integer.parseInt(tmp);
+        tmp = getInitParameter("pool");
+        int pool=tmp==null?Runtime.getRuntime().availableProcessors():Integer.parseInt(tmp);
+        
+        // Create and start a shared scheduler.  
+        scheduler=new ScheduledThreadPoolExecutor(pool);
+    }
+
+    @Override
+    public void destroy()
+    {
+        scheduler.shutdown();
+    }
+    
+    @Override
+    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+    {
+        // Get the path of the static resource to serve.
+        String info=request.getPathInfo();
+                
+        // We don't handle directories
+        if (info.endsWith("/"))
+        {
+            response.sendError(503,"directories not supported");
+            return;
+        }
+
+        // Set the mime type of the response
+        String content_type=getServletContext().getMimeType(info);
+        response.setContentType(content_type==null?"application/x-data":content_type);
+        
+        // Look for a matching file path
+        String path = request.getPathTranslated();
+        
+        // If we have a file path and this is a jetty response, we can use the JettyStream impl
+        ServletOutputStream out = response.getOutputStream();
+        if (path != null && out instanceof HttpOutput)
+        {
+            // If the file exists
+            File file = new File(path);
+            if (file.exists() && file.canRead())
+            {
+                // Set the content length
+                response.setContentLengthLong(file.length());
+                
+                // Look for a file mapped buffer in the cache
+                ByteBuffer mapped=cache.get(path);
+                
+                // Handle cache miss
+                if (mapped==null)
+                {
+                    // TODO implement LRU cache flush
+                    try (RandomAccessFile raf = new RandomAccessFile(file, "r"))
+                    {
+                        ByteBuffer buf = raf.getChannel().map(MapMode.READ_ONLY,0,raf.length());
+                        mapped=cache.putIfAbsent(path,buf);
+                        if (mapped==null)
+                            mapped=buf;
+                    }
+                }
+
+                // start async request handling
+                AsyncContext async=request.startAsync();
+
+                // Set a JettyStream as the write listener to write the content asynchronously.
+                out.setWriteListener(new JettyDataStream(mapped,async,out));    
+                return;
+            }
+        }
+        
+        // Jetty API was not used, so lets try the standards approach
+        
+        // Can we find the content as an input stream
+        InputStream content = getServletContext().getResourceAsStream(info);
+        if (content==null)
+        {
+            response.sendError(404);
+            return;
+        }
+
+        // Set a StandardStream as he write listener to write the content asynchronously
+        out.setWriteListener(new StandardDataStream(content,request.startAsync(),out));
+    }
+
+    /**
+     * A standard API Stream writer
+     */
+    private final class StandardDataStream implements WriteListener, Runnable
+    {
+        private final InputStream content;
+        private final AsyncContext async;
+        private final ServletOutputStream out;
+
+        private StandardDataStream(InputStream content, AsyncContext async, ServletOutputStream out)
+        {
+            this.content = content;
+            this.async = async;
+            this.out = out;
+        }
+
+        @Override
+        public void onWritePossible() throws IOException
+        {
+            // If we are able to write
+            if(out.isReady())
+            {
+                // Allocated a copy buffer for each write, so as to not hold while paused
+                // TODO put these buffers into a pool
+                byte[] buffer = new byte[buffersize];
+                
+                // read some content into the copy buffer
+                int len=content.read(buffer);
+                
+                // If we are at EOF
+                if (len<0)
+                {
+                    // complete the async lifecycle
+                    async.complete();
+                    return;
+                }
+                
+                // write out the copy buffer.  This will be an asynchronous write
+                // and will always return immediately without blocking.  If a subsequent
+                // call to out.isReady() returns false, then this onWritePossible method
+                // will be called back when a write is possible.
+                out.write(buffer,0,len);
+                
+                // Schedule a timer callback to pause writing.  Because isReady() is not called,
+                // a onWritePossible callback is no scheduled.
+                scheduler.schedule(this,pause,TimeUnit.MILLISECONDS);
+            }
+        }
+        
+        @Override 
+        public void run()
+        {
+            try
+            {
+                // When the pause timer wakes up, call onWritePossible.  Either isReady() will return
+                // true and another chunk of content will be written, or it will return false and the 
+                // onWritePossible() callback will be scheduled when a write is next possible.
+                onWritePossible();
+            }
+            catch(Exception e)
+            {
+                onError(e);
+            }
+        }
+
+        @Override
+        public void onError(Throwable t)
+        {
+            getServletContext().log("Async Error",t);
+            async.complete();
+        }
+    }
+    
+
+    /**
+     * A Jetty API DataStream
+     *
+     */
+    private final class JettyDataStream implements WriteListener, Runnable
+    {
+        private final ByteBuffer content;
+        private final int limit;
+        private final AsyncContext async;
+        private final HttpOutput out;
+
+        private JettyDataStream(ByteBuffer content, AsyncContext async, ServletOutputStream out)
+        {
+            // Make a readonly copy of the passed buffer. This uses the same underlying content
+            // without a copy, but gives this instance its own position and limit.
+            this.content = content.asReadOnlyBuffer();
+            // remember the ultimate limit.
+            this.limit=this.content.limit();
+            this.async = async;
+            this.out = (HttpOutput)out;
+        }
+
+        @Override
+        public void onWritePossible() throws IOException
+        {            
+            // If we are able to write
+            if(out.isReady())
+            {
+                // Position our buffers limit to allow only buffersize bytes to be written
+                int l=content.position()+buffersize;
+                // respect the ultimate limit
+                if (l>limit)
+                    l=limit;
+                content.limit(l);
+
+                // if all content has been written
+                if (!content.hasRemaining())
+                {              
+                    // complete the async lifecycle
+                    async.complete();
+                    return;
+                }
+
+                // write our limited buffer.  This will be an asynchronous write
+                // and will always return immediately without blocking.  If a subsequent
+                // call to out.isReady() returns false, then this onWritePossible method
+                // will be called back when a write is possible.
+                out.write(content);
+
+                // Schedule a timer callback to pause writing.  Because isReady() is not called,
+                // a onWritePossible callback is no scheduled.
+                scheduler.schedule(this,pause,TimeUnit.MILLISECONDS);
+            }
+        }
+        
+        @Override 
+        public void run()
+        {
+            try
+            {
+                // When the pause timer wakes up, call onWritePossible.  Either isReady() will return
+                // true and another chunk of content will be written, or it will return false and the 
+                // onWritePossible() callback will be scheduled when a write is next possible.
+                onWritePossible();
+            }
+            catch(Exception e)
+            {
+                onError(e);
+            }
+        }
+
+        @Override
+        public void onError(Throwable t)
+        {
+            getServletContext().log("Async Error",t);
+            async.complete();
+        }
+    }
+}
diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/DoSFilter.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/DoSFilter.java
index 1bbafdd..f1e3315 100644
--- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/DoSFilter.java
+++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/DoSFilter.java
@@ -31,6 +31,11 @@ import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.AsyncEvent;
+import javax.servlet.AsyncListener;
+import javax.servlet.DispatcherType;
 import javax.servlet.Filter;
 import javax.servlet.FilterChain;
 import javax.servlet.FilterConfig;
@@ -46,13 +51,15 @@ import javax.servlet.http.HttpSessionBindingEvent;
 import javax.servlet.http.HttpSessionBindingListener;
 import javax.servlet.http.HttpSessionEvent;
 
-import org.eclipse.jetty.continuation.Continuation;
-import org.eclipse.jetty.continuation.ContinuationListener;
-import org.eclipse.jetty.continuation.ContinuationSupport;
 import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.annotation.ManagedOperation;
+import org.eclipse.jetty.util.annotation.Name;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.util.thread.Timeout;
+import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
+import org.eclipse.jetty.util.thread.Scheduler;
 
 /**
  * Denial of Service filter
@@ -121,7 +128,12 @@ import org.eclipse.jetty.util.thread.Timeout;
  * manage the configuration of the filter.</dd>
  * </dl>
  * </p>
+ * <p>
+ * This filter should be configured for {@link DispatcherType#REQUEST} and {@link DispatcherType#ASYNC} and with 
+ * <code><async-supported>true</async-supported></code>.
+ * </p>
  */
+ at ManagedObject("limits exposure to abuse from request flooding, whether malicious, or as a result of a misconfigured client")
 public class DoSFilter implements Filter
 {
     private static final Logger LOG = Log.getLogger(DoSFilter.class);
@@ -162,7 +174,10 @@ public class DoSFilter implements Filter
     private static final int USER_IP = 1;
     private static final int USER_UNKNOWN = 0;
 
-    private ServletContext _context;
+    private final String _suspended = "DoSFilter@" + Integer.toHexString(hashCode()) + ".SUSPENDED";
+    private final String _resumed = "DoSFilter@" + Integer.toHexString(hashCode()) + ".RESUMED";
+    private final ConcurrentHashMap<String, RateTracker> _rateTrackers = new ConcurrentHashMap<>();
+    private final List<String> _whitelist = new CopyOnWriteArrayList<>();
     private volatile long _delayMs;
     private volatile long _throttleMs;
     private volatile long _maxWaitMs;
@@ -175,37 +190,18 @@ public class DoSFilter implements Filter
     private Semaphore _passes;
     private volatile int _throttledRequests;
     private volatile int _maxRequestsPerSec;
-    private Queue<Continuation>[] _queue;
-    private ContinuationListener[] _listeners;
-    private final ConcurrentHashMap<String, RateTracker> _rateTrackers = new ConcurrentHashMap<String, RateTracker>();
-    private final List<String> _whitelist = new CopyOnWriteArrayList<String>();
-    private final Timeout _requestTimeoutQ = new Timeout();
-    private final Timeout _trackerTimeoutQ = new Timeout();
-    private Thread _timerThread;
-    private volatile boolean _running;
-
-    public void init(FilterConfig filterConfig)
-    {
-        _context = filterConfig.getServletContext();
+    private Queue<AsyncContext>[] _queues;
+    private AsyncListener[] _listeners;
+    private Scheduler _scheduler;
 
-        _queue = new Queue[getMaxPriority() + 1];
-        _listeners = new ContinuationListener[getMaxPriority() + 1];
-        for (int p = 0; p < _queue.length; p++)
+    public void init(FilterConfig filterConfig) throws ServletException
+    {
+        _queues = new Queue[getMaxPriority() + 1];
+        _listeners = new AsyncListener[_queues.length];
+        for (int p = 0; p < _queues.length; p++)
         {
-            _queue[p] = new ConcurrentLinkedQueue<Continuation>();
-
-            final int priority = p;
-            _listeners[p] = new ContinuationListener()
-            {
-                public void onComplete(Continuation continuation)
-                {
-                }
-
-                public void onTimeout(Continuation continuation)
-                {
-                    _queue[priority].remove(continuation);
-                }
-            };
+            _queues[p] = new ConcurrentLinkedQueue<>();
+            _listeners[p] = new DoSAsyncListener(p);
         }
 
         _rateTrackers.clear();
@@ -270,45 +266,25 @@ public class DoSFilter implements Filter
         parameter = filterConfig.getInitParameter(ENABLED_INIT_PARAM);
         setEnabled(parameter == null || Boolean.parseBoolean(parameter));
 
-        _requestTimeoutQ.setNow();
-        _requestTimeoutQ.setDuration(_maxRequestMs);
+        _scheduler = startScheduler();
 
-        _trackerTimeoutQ.setNow();
-        _trackerTimeoutQ.setDuration(_maxIdleTrackerMs);
+        ServletContext context = filterConfig.getServletContext();
+        if (context != null && Boolean.parseBoolean(filterConfig.getInitParameter(MANAGED_ATTR_INIT_PARAM)))
+            context.setAttribute(filterConfig.getFilterName(), this);
+    }
 
-        _running = true;
-        _timerThread = (new Thread()
+    protected Scheduler startScheduler() throws ServletException
+    {
+        try
         {
-            public void run()
-            {
-                try
-                {
-                    while (_running)
-                    {
-                        long now = _requestTimeoutQ.setNow();
-                        _requestTimeoutQ.tick();
-                        _trackerTimeoutQ.setNow(now);
-                        _trackerTimeoutQ.tick();
-                        try
-                        {
-                            Thread.sleep(100);
-                        }
-                        catch (InterruptedException e)
-                        {
-                            LOG.ignore(e);
-                        }
-                    }
-                }
-                finally
-                {
-                    LOG.debug("DoSFilter timer exited");
-                }
-            }
-        });
-        _timerThread.start();
-
-        if (_context != null && Boolean.parseBoolean(filterConfig.getInitParameter(MANAGED_ATTR_INIT_PARAM)))
-            _context.setAttribute(filterConfig.getFilterName(), this);
+            Scheduler result = new ScheduledExecutorScheduler();
+            result.start();
+            return result;
+        }
+        catch (Exception x)
+        {
+            throw new ServletException(x);
+        }
     }
 
     public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException
@@ -324,39 +300,40 @@ public class DoSFilter implements Filter
             return;
         }
 
-        final long now = _requestTimeoutQ.getNow();
-
-        // Look for the rate tracker for this request
+        // Look for the rate tracker for this request.
         RateTracker tracker = (RateTracker)request.getAttribute(__TRACKER);
-
         if (tracker == null)
         {
             // This is the first time we have seen this request.
+            if (LOG.isDebugEnabled())
+                LOG.debug("Filtering {}", request);
 
-            // get a rate tracker associated with this request, and record one hit
+            // Get a rate tracker associated with this request, and record one hit.
             tracker = getRateTracker(request);
 
             // Calculate the rate and check it is over the allowed limit
-            final boolean overRateLimit = tracker.isRateExceeded(now);
+            final boolean overRateLimit = tracker.isRateExceeded(System.currentTimeMillis());
 
-            // pass it through if  we are not currently over the rate limit
+            // Pass it through if  we are not currently over the rate limit.
             if (!overRateLimit)
             {
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Allowing {}", request);
                 doFilterChain(filterChain, request, response);
                 return;
             }
 
             // We are over the limit.
 
-            // So either reject it, delay it or throttle it
+            // So either reject it, delay it or throttle it.
             long delayMs = getDelayMs();
             boolean insertHeaders = isInsertHeaders();
             switch ((int)delayMs)
             {
                 case -1:
                 {
-                    // Reject this request
-                    LOG.warn("DOS ALERT: Request rejected ip=" + request.getRemoteAddr() + ",session=" + request.getRequestedSessionId() + ",user=" + request.getUserPrincipal());
+                    // Reject this request.
+                    LOG.warn("DOS ALERT: Request rejected ip={}, session={}, user={}", request.getRemoteAddr(), request.getRequestedSessionId(), request.getUserPrincipal());
                     if (insertHeaders)
                         response.addHeader("DoSFilter", "unavailable");
                     response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
@@ -364,39 +341,41 @@ public class DoSFilter implements Filter
                 }
                 case 0:
                 {
-                    // fall through to throttle code
-                    LOG.warn("DOS ALERT: Request throttled ip=" + request.getRemoteAddr() + ",session=" + request.getRequestedSessionId() + ",user=" + request.getUserPrincipal());
+                    // Fall through to throttle the request.
+                    LOG.warn("DOS ALERT: Request throttled ip={}, session={}, user={}", request.getRemoteAddr(), request.getRequestedSessionId(), request.getUserPrincipal());
                     request.setAttribute(__TRACKER, tracker);
                     break;
                 }
                 default:
                 {
-                    // insert a delay before throttling the request
-                    LOG.warn("DOS ALERT: Request delayed="+delayMs+"ms ip=" + request.getRemoteAddr() + ",session=" + request.getRequestedSessionId() + ",user=" + request.getUserPrincipal());
+                    // Insert a delay before throttling the request,
+                    // using the suspend+timeout mechanism of AsyncContext.
+                    LOG.warn("DOS ALERT: Request delayed={}ms, ip={}, session={}, user={}", delayMs, request.getRemoteAddr(), request.getRequestedSessionId(), request.getUserPrincipal());
                     if (insertHeaders)
                         response.addHeader("DoSFilter", "delayed");
-                    Continuation continuation = ContinuationSupport.getContinuation(request);
                     request.setAttribute(__TRACKER, tracker);
+                    AsyncContext asyncContext = request.startAsync();
                     if (delayMs > 0)
-                        continuation.setTimeout(delayMs);
-                    continuation.suspend();
+                        asyncContext.setTimeout(delayMs);
+                    asyncContext.addListener(new DoSTimeoutAsyncListener());
                     return;
                 }
             }
         }
 
-        // Throttle the request
+        if (LOG.isDebugEnabled())
+            LOG.debug("Throttling {}", request);
+
+        // Throttle the request.
         boolean accepted = false;
         try
         {
-            // check if we can afford to accept another request at this time
+            // Check if we can afford to accept another request at this time.
             accepted = _passes.tryAcquire(getMaxWaitMs(), TimeUnit.MILLISECONDS);
-
             if (!accepted)
             {
-                // we were not accepted, so either we suspend to wait,or if we were woken up we insist or we fail
-                final Continuation continuation = ContinuationSupport.getContinuation(request);
-
+                // We were not accepted, so either we suspend to wait,
+                // or if we were woken up we insist or we fail.
                 Boolean throttled = (Boolean)request.getAttribute(__THROTTLED);
                 long throttleMs = getThrottleMs();
                 if (throttled != Boolean.TRUE && throttleMs > 0)
@@ -405,30 +384,39 @@ public class DoSFilter implements Filter
                     request.setAttribute(__THROTTLED, Boolean.TRUE);
                     if (isInsertHeaders())
                         response.addHeader("DoSFilter", "throttled");
+                    AsyncContext asyncContext = request.startAsync();
+                    request.setAttribute(_suspended, Boolean.TRUE);
                     if (throttleMs > 0)
-                        continuation.setTimeout(throttleMs);
-                    continuation.suspend();
-
-                    continuation.addContinuationListener(_listeners[priority]);
-                    _queue[priority].add(continuation);
+                        asyncContext.setTimeout(throttleMs);
+                    asyncContext.addListener(_listeners[priority]);
+                    _queues[priority].add(asyncContext);
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("Throttled {}, {}ms", request, throttleMs);
                     return;
                 }
-                // else were we resumed?
-                else if (request.getAttribute("javax.servlet.resumed") == Boolean.TRUE)
+
+                Boolean resumed = (Boolean)request.getAttribute(_resumed);
+                if (resumed == Boolean.TRUE)
                 {
-                    // we were resumed and somebody stole our pass, so we wait for the next one.
+                    // We were resumed, we wait for the next pass.
                     _passes.acquire();
                     accepted = true;
                 }
             }
 
-            // if we were accepted (either immediately or after throttle)
+            // If we were accepted (either immediately or after throttle)...
             if (accepted)
-                // call the chain
+            {
+                // ...call the chain.
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Allowing {}", request);
                 doFilterChain(filterChain, request, response);
+            }
             else
             {
-                // fail the request
+                // ...otherwise fail the request.
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Rejecting {}", request);
                 if (isInsertHeaders())
                     response.addHeader("DoSFilter", "unavailable");
                 response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
@@ -436,21 +424,28 @@ public class DoSFilter implements Filter
         }
         catch (InterruptedException e)
         {
-            _context.log("DoS", e);
             response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
         }
         finally
         {
             if (accepted)
             {
-                // wake up the next highest priority request.
-                for (int p = _queue.length; p-- > 0; )
+                // Wake up the next highest priority request.
+                for (int p = _queues.length - 1; p >= 0; --p)
                 {
-                    Continuation continuation = _queue[p].poll();
-                    if (continuation != null && continuation.isSuspended())
+                    AsyncContext asyncContext = _queues[p].poll();
+                    if (asyncContext != null)
                     {
-                        continuation.resume();
-                        break;
+                        ServletRequest candidate = asyncContext.getRequest();
+                        Boolean suspended = (Boolean)candidate.getAttribute(_suspended);
+                        if (suspended == Boolean.TRUE)
+                        {
+                            if (LOG.isDebugEnabled())
+                                LOG.debug("Resuming {}", request);
+                            candidate.setAttribute(_resumed, Boolean.TRUE);
+                            asyncContext.dispatch();
+                            break;
+                        }
                     }
                 }
                 _passes.release();
@@ -461,23 +456,22 @@ public class DoSFilter implements Filter
     protected void doFilterChain(FilterChain chain, final HttpServletRequest request, final HttpServletResponse response) throws IOException, ServletException
     {
         final Thread thread = Thread.currentThread();
-
-        final Timeout.Task requestTimeout = new Timeout.Task()
+        Runnable requestTimeout = new Runnable()
         {
-            public void expired()
+            @Override
+            public void run()
             {
                 closeConnection(request, response, thread);
             }
         };
-
+        Scheduler.Task task = _scheduler.schedule(requestTimeout, getMaxRequestMs(), TimeUnit.MILLISECONDS);
         try
         {
-            _requestTimeoutQ.schedule(requestTimeout);
             chain.doFilter(request, response);
         }
         finally
         {
-            requestTimeout.cancel();
+            task.cancel();
         }
     }
 
@@ -568,14 +562,14 @@ public class DoSFilter implements Filter
         }
         else
         {
-            if (_trackSessions && session != null && !session.isNew())
+            if (isTrackSessions() && session != null && !session.isNew())
             {
                 loadId = session.getId();
                 type = USER_SESSION;
             }
             else
             {
-                loadId = _remotePort ? (request.getRemoteAddr() + request.getRemotePort()) : request.getRemoteAddr();
+                loadId = isRemotePort() ? (request.getRemoteAddr() + request.getRemotePort()) : request.getRemoteAddr();
                 type = USER_IP;
             }
         }
@@ -584,17 +578,18 @@ public class DoSFilter implements Filter
 
         if (tracker == null)
         {
-            boolean allowed = checkWhitelist(_whitelist, request.getRemoteAddr());
-            tracker = allowed ? new FixedRateTracker(loadId, type, _maxRequestsPerSec)
-                    : new RateTracker(loadId, type, _maxRequestsPerSec);
+            boolean allowed = checkWhitelist(request.getRemoteAddr());
+            int maxRequestsPerSec = getMaxRequestsPerSec();
+            tracker = allowed ? new FixedRateTracker(loadId, type, maxRequestsPerSec)
+                    : new RateTracker(loadId, type, maxRequestsPerSec);
             RateTracker existing = _rateTrackers.putIfAbsent(loadId, tracker);
             if (existing != null)
                 tracker = existing;
 
             if (type == USER_IP)
             {
-                // USER_IP expiration from _rateTrackers is handled by the _trackerTimeoutQ
-                _trackerTimeoutQ.schedule(tracker);
+                // USER_IP expiration from _rateTrackers is handled by the _scheduler
+                _scheduler.schedule(tracker, getMaxIdleTrackerMs(), TimeUnit.MILLISECONDS);
             }
             else if (session != null)
             {
@@ -606,6 +601,25 @@ public class DoSFilter implements Filter
         return tracker;
     }
 
+    protected boolean checkWhitelist(String candidate)
+    {
+        for (String address : _whitelist)
+        {
+            if (address.contains("/"))
+            {
+                if (subnetMatch(address, candidate))
+                    return true;
+            }
+            else
+            {
+                if (address.equals(candidate))
+                    return true;
+            }
+        }
+        return false;
+    }
+
+    @Deprecated
     protected boolean checkWhitelist(List<String> whitelist, String candidate)
     {
         for (String address : whitelist)
@@ -709,6 +723,10 @@ public class DoSFilter implements Filter
             prefix -= 8;
             ++index;
         }
+        
+        if (index == result.length)
+            return result;
+               
         // Sets the _prefix_ most significant bits to 1
         result[index] = (byte)~((1 << (8 - prefix)) - 1);
         return result;
@@ -717,14 +735,23 @@ public class DoSFilter implements Filter
     public void destroy()
     {
         LOG.debug("Destroy {}",this);
-        _running = false;
-        _timerThread.interrupt();
-        _requestTimeoutQ.cancelAll();
-        _trackerTimeoutQ.cancelAll();
+        stopScheduler();
         _rateTrackers.clear();
         _whitelist.clear();
     }
 
+    protected void stopScheduler()
+    {
+        try
+        {
+            _scheduler.stop();
+        }
+        catch (Exception x)
+        {
+            LOG.ignore(x);
+        }
+    }
+
     /**
      * Returns the user id, used to track this connection.
      * This SHOULD be overridden by subclasses.
@@ -744,6 +771,7 @@ public class DoSFilter implements Filter
      *
      * @return maximum number of requests
      */
+    @ManagedAttribute("maximum number of requests allowed from a connection per second")
     public int getMaxRequestsPerSec()
     {
         return _maxRequestsPerSec;
@@ -765,6 +793,7 @@ public class DoSFilter implements Filter
      * Get delay (in milliseconds) that is applied to all requests
      * over the rate limit, before they are considered at all.
      */
+    @ManagedAttribute("delay applied to all requests over the rate limit (in ms)")
     public long getDelayMs()
     {
         return _delayMs;
@@ -787,6 +816,7 @@ public class DoSFilter implements Filter
      *
      * @return maximum wait time
      */
+    @ManagedAttribute("maximum time the filter will block waiting throttled connections, (0 for no delay, -1 to reject requests)")
     public long getMaxWaitMs()
     {
         return _maxWaitMs;
@@ -809,6 +839,7 @@ public class DoSFilter implements Filter
      *
      * @return number of requests
      */
+    @ManagedAttribute("number of requests over rate limit")
     public int getThrottledRequests()
     {
         return _throttledRequests;
@@ -832,6 +863,7 @@ public class DoSFilter implements Filter
      *
      * @return wait time
      */
+    @ManagedAttribute("amount of time to async wait for semaphore")
     public long getThrottleMs()
     {
         return _throttleMs;
@@ -853,6 +885,7 @@ public class DoSFilter implements Filter
      *
      * @return maximum processing time
      */
+    @ManagedAttribute("maximum time to allow requests to process (in ms)")
     public long getMaxRequestMs()
     {
         return _maxRequestMs;
@@ -876,6 +909,7 @@ public class DoSFilter implements Filter
      *
      * @return maximum tracking time
      */
+    @ManagedAttribute("maximum time to track of request rates for connection before discarding")
     public long getMaxIdleTrackerMs()
     {
         return _maxIdleTrackerMs;
@@ -898,6 +932,7 @@ public class DoSFilter implements Filter
      *
      * @return value of the flag
      */
+    @ManagedAttribute("inser DoSFilter headers in response")
     public boolean isInsertHeaders()
     {
         return _insertHeaders;
@@ -918,6 +953,7 @@ public class DoSFilter implements Filter
      *
      * @return value of the flag
      */
+    @ManagedAttribute("usage rate is tracked by session if one exists")
     public boolean isTrackSessions()
     {
         return _trackSessions;
@@ -939,6 +975,7 @@ public class DoSFilter implements Filter
      *
      * @return value of the flag
      */
+    @ManagedAttribute("usage rate is tracked by IP+port is session tracking not used")
     public boolean isRemotePort()
     {
         return _remotePort;
@@ -958,6 +995,7 @@ public class DoSFilter implements Filter
     /**
      * @return whether this filter is enabled
      */
+    @ManagedAttribute("whether this filter is enabled")
     public boolean isEnabled()
     {
         return _enabled;
@@ -976,6 +1014,7 @@ public class DoSFilter implements Filter
      *
      * @return comma-separated whitelist
      */
+    @ManagedAttribute("list of IPs that will not be rate limited")
     public String getWhitelist()
     {
         StringBuilder result = new StringBuilder();
@@ -992,24 +1031,37 @@ public class DoSFilter implements Filter
     /**
      * Set a list of IP addresses that will not be rate limited.
      *
-     * @param value comma-separated whitelist
+     * @param commaSeparatedList comma-separated whitelist
      */
-    public void setWhitelist(String value)
+    public void setWhitelist(String commaSeparatedList)
     {
-        List<String> result = new ArrayList<String>();
-        for (String address : value.split(","))
+        List<String> result = new ArrayList<>();
+        for (String address : commaSeparatedList.split(","))
             addWhitelistAddress(result, address);
-        _whitelist.clear();
+        clearWhitelist();
         _whitelist.addAll(result);
         LOG.debug("Whitelisted IP addresses: {}", result);
     }
 
+    /**
+     * Clears the list of whitelisted IP addresses
+     */
+    @ManagedOperation("clears the list of IP addresses that will not be rate limited")
     public void clearWhitelist()
     {
         _whitelist.clear();
     }
 
-    public boolean addWhitelistAddress(String address)
+    /**
+     * Adds the given IP address, either in the form of a dotted decimal notation A.B.C.D
+     * or in the CIDR notation A.B.C.D/M, to the list of whitelisted IP addresses.
+     *
+     * @param address the address to add
+     * @return whether the address was added to the list
+     * @see #removeWhitelistAddress(String)
+     */
+    @ManagedOperation("adds an IP address that will not be rate limited")
+    public boolean addWhitelistAddress(@Name("address") String address)
     {
         return addWhitelistAddress(_whitelist, address);
     }
@@ -1020,7 +1072,15 @@ public class DoSFilter implements Filter
         return address.length() > 0 && list.add(address);
     }
 
-    public boolean removeWhitelistAddress(String address)
+    /**
+     * Removes the given address from the list of whitelisted IP addresses.
+     *
+     * @param address the address to remove
+     * @return whether the address was removed from the list
+     * @see #addWhitelistAddress(String)
+     */
+    @ManagedOperation("removes an IP address that will not be rate limited")
+    public boolean removeWhitelistAddress(@Name("address") String address)
     {
         return _whitelist.remove(address);
     }
@@ -1029,14 +1089,14 @@ public class DoSFilter implements Filter
      * A RateTracker is associated with a connection, and stores request rate
      * data.
      */
-    class RateTracker extends Timeout.Task implements HttpSessionBindingListener, HttpSessionActivationListener, Serializable
+    class RateTracker implements Runnable, HttpSessionBindingListener, HttpSessionActivationListener, Serializable
     {
         private static final long serialVersionUID = 3534663738034577872L;
 
-        transient protected final String _id;
-        transient protected final int _type;
-        transient protected final long[] _timestamps;
-        transient protected int _next;
+        protected transient final String _id;
+        protected transient final int _type;
+        protected transient final long[] _timestamps;
+        protected transient int _next;
 
         public RateTracker(String id, int type, int maxRequestsPerSecond)
         {
@@ -1092,7 +1152,8 @@ public class DoSFilter implements Filter
             //and ensure that we take ourselves out of the session so we are not saved
             _rateTrackers.remove(_id);
             se.getSession().removeAttribute(__TRACKER);
-            if (LOG.isDebugEnabled()) LOG.debug("Value removed: {}", getId());
+            if (LOG.isDebugEnabled()) 
+                LOG.debug("Value removed: {}", getId());
         }
 
         public void sessionDidActivate(HttpSessionEvent se)
@@ -1100,15 +1161,15 @@ public class DoSFilter implements Filter
             LOG.warn("Unexpected session activation");
         }
 
-        public void expired()
+        @Override
+        public void run()
         {
-            long now = _trackerTimeoutQ.getNow();
             int latestIndex = _next == 0 ? (_timestamps.length - 1) : (_next - 1);
             long last = _timestamps[latestIndex];
-            boolean hasRecentRequest = last != 0 && (now - last) < 1000L;
+            boolean hasRecentRequest = last != 0 && (System.currentTimeMillis() - last) < 1000L;
 
             if (hasRecentRequest)
-                reschedule();
+                _scheduler.schedule(this, getMaxIdleTrackerMs(), TimeUnit.MILLISECONDS);
             else
                 _rateTrackers.remove(_id);
         }
@@ -1148,4 +1209,45 @@ public class DoSFilter implements Filter
             return "Fixed" + super.toString();
         }
     }
+
+    private class DoSTimeoutAsyncListener implements AsyncListener
+    {
+        @Override
+        public void onStartAsync(AsyncEvent event) throws IOException
+        {
+        }
+
+        @Override
+        public void onComplete(AsyncEvent event) throws IOException
+        {
+        }
+
+        @Override
+        public void onTimeout(AsyncEvent event) throws IOException
+        {
+            event.getAsyncContext().dispatch();
+        }
+
+        @Override
+        public void onError(AsyncEvent event) throws IOException
+        {
+        }
+    }
+
+    private class DoSAsyncListener extends DoSTimeoutAsyncListener
+    {
+        private final int priority;
+
+        public DoSAsyncListener(int priority)
+        {
+            this.priority = priority;
+        }
+
+        @Override
+        public void onTimeout(AsyncEvent event) throws IOException
+        {
+            _queues[priority].remove(event.getAsyncContext());
+            super.onTimeout(event);
+        }
+    }
 }
diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/EventSource.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/EventSource.java
new file mode 100644
index 0000000..a365a29
--- /dev/null
+++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/EventSource.java
@@ -0,0 +1,108 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlets;
+
+import java.io.IOException;
+
+/**
+ * <p>{@link EventSource} is the passive half of an event source connection, as defined by the
+ * <a href="http://www.w3.org/TR/eventsource/">EventSource Specification</a>.</p>
+ * <p>{@link EventSource.Emitter} is the active half of the connection and allows to operate on the connection.</p>
+ * <p>{@link EventSource} allows applications to be notified of events happening on the connection;
+ * two events are being notified: the opening of the event source connection, where method
+ * {@link EventSource#onOpen(Emitter)} is invoked, and the closing of the event source connection,
+ * where method {@link EventSource#onClose()} is invoked.</p>
+ *
+ * @see EventSourceServlet
+ */
+public interface EventSource
+{
+    /**
+     * <p>Callback method invoked when an event source connection is opened.</p>
+     *
+     * @param emitter the {@link Emitter} instance that allows to operate on the connection
+     * @throws IOException if the implementation of the method throws such exception
+     */
+    public void onOpen(Emitter emitter) throws IOException;
+
+    /**
+     * <p>Callback method invoked when an event source connection is closed.</p>
+     */
+    public void onClose();
+
+    /**
+     * <p>{@link Emitter} is the active half of an event source connection, and allows applications
+     * to operate on the connection by sending events, data or comments, or by closing the connection.</p>
+     * <p>An {@link Emitter} instance will be created for each new event source connection.</p>
+     * <p>{@link Emitter} instances are fully thread safe and can be used from multiple threads.</p>
+     */
+    public interface Emitter
+    {
+        /**
+         * <p>Sends a named event with data to the client.</p>
+         * <p>When invoked as: <code>event("foo", "bar")</code>, the client will receive the lines:</p>
+         * <pre>
+         * event: foo
+         * data: bar
+         * </pre>
+         *
+         * @param name the event name
+         * @param data the data to be sent
+         * @throws IOException if an I/O failure occurred
+         * @see #data(String)
+         */
+        public void event(String name, String data) throws IOException;
+
+        /**
+         * <p>Sends a default event with data to the client.</p>
+         * <p>When invoked as: <code>data("baz")</code>, the client will receive the line:</p>
+         * <pre>
+         * data: baz
+         * </pre>
+         * <p>When invoked as: <code>data("foo\r\nbar\rbaz\nbax")</code>, the client will receive the lines:</p>
+         * <pre>
+         * data: foo
+         * data: bar
+         * data: baz
+         * data: bax
+         * </pre>
+         *
+         * @param data the data to be sent
+         * @throws IOException if an I/O failure occurred
+         */
+        public void data(String data) throws IOException;
+
+        /**
+         * <p>Sends a comment to the client.</p>
+         * <p>When invoked as: <code>comment("foo")</code>, the client will receive the line:</p>
+         * <pre>
+         * : foo
+         * </pre>
+         *
+         * @param comment the comment to send
+         * @throws IOException if an I/O failure occurred
+         */
+        public void comment(String comment) throws IOException;
+
+        /**
+         * <p>Closes this event source connection.</p>
+         */
+        public void close();
+    }
+}
diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/EventSourceServlet.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/EventSourceServlet.java
new file mode 100644
index 0000000..9f7b130
--- /dev/null
+++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/EventSourceServlet.java
@@ -0,0 +1,240 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.servlets;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.StringReader;
+import java.nio.charset.StandardCharsets;
+import java.util.Enumeration;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+
+/**
+ * <p>A servlet that implements the <a href="http://www.w3.org/TR/eventsource/">event source protocol</a>,
+ * also known as "server sent events".</p>
+ * <p>This servlet must be subclassed to implement abstract method {@link #newEventSource(HttpServletRequest)}
+ * to return an instance of {@link EventSource} that allows application to listen for event source events
+ * and to emit event source events.</p>
+ * <p>This servlet supports the following configuration parameters:</p>
+ * <ul>
+ *     <li><code>heartBeatPeriod</code>, that specifies the heartbeat period, in seconds, used to check
+ *     whether the connection has been closed by the client; defaults to 10 seconds.</li>
+ * </ul>
+ *
+ * <p>NOTE: there is currently no support for <code>last-event-id</code>.</p>
+ */
+public abstract class EventSourceServlet extends HttpServlet
+{
+    private static final byte[] CRLF = new byte[]{'\r', '\n'};
+    private static final byte[] EVENT_FIELD = "event: ".getBytes(StandardCharsets.UTF_8);
+    private static final byte[] DATA_FIELD = "data: ".getBytes(StandardCharsets.UTF_8);
+    private static final byte[] COMMENT_FIELD = ": ".getBytes(StandardCharsets.UTF_8);
+
+    private ScheduledExecutorService scheduler;
+    private int heartBeatPeriod = 10;
+
+    @Override
+    public void init() throws ServletException
+    {
+        String heartBeatPeriodParam = getServletConfig().getInitParameter("heartBeatPeriod");
+        if (heartBeatPeriodParam != null)
+            heartBeatPeriod = Integer.parseInt(heartBeatPeriodParam);
+        scheduler = Executors.newSingleThreadScheduledExecutor();
+    }
+
+    @Override
+    public void destroy()
+    {
+        if (scheduler != null)
+            scheduler.shutdown();
+    }
+
+    @Override
+    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+    {
+        @SuppressWarnings("unchecked")
+        Enumeration<String> acceptValues = request.getHeaders("Accept");
+        while (acceptValues.hasMoreElements())
+        {
+            String accept = acceptValues.nextElement();
+            if (accept.equals("text/event-stream"))
+            {
+                EventSource eventSource = newEventSource(request);
+                if (eventSource == null)
+                {
+                    response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
+                }
+                else
+                {
+                    respond(request, response);
+                    AsyncContext async = request.startAsync();
+                    // Infinite timeout because the continuation is never resumed,
+                    // but only completed on close
+                    async.setTimeout(0);
+                    EventSourceEmitter emitter = new EventSourceEmitter(eventSource, async);
+                    emitter.scheduleHeartBeat();
+                    open(eventSource, emitter);
+                }
+                return;
+            }
+        }
+        super.doGet(request, response);
+    }
+
+    protected abstract EventSource newEventSource(HttpServletRequest request);
+
+    protected void respond(HttpServletRequest request, HttpServletResponse response) throws IOException
+    {
+        response.setStatus(HttpServletResponse.SC_OK);
+        response.setCharacterEncoding(StandardCharsets.UTF_8.name());
+        response.setContentType("text/event-stream");
+        // By adding this header, and not closing the connection,
+        // we disable HTTP chunking, and we can use write()+flush()
+        // to send data in the text/event-stream protocol
+        response.addHeader("Connection", "close");
+        response.flushBuffer();
+    }
+
+    protected void open(EventSource eventSource, EventSource.Emitter emitter) throws IOException
+    {
+        eventSource.onOpen(emitter);
+    }
+
+    protected class EventSourceEmitter implements EventSource.Emitter, Runnable
+    {
+        private final EventSource eventSource;
+        private final AsyncContext async;
+        private final ServletOutputStream output;
+        private Future<?> heartBeat;
+        private boolean closed;
+
+        public EventSourceEmitter(EventSource eventSource, AsyncContext async) throws IOException
+        {
+            this.eventSource = eventSource;
+            this.async = async;
+            this.output = async.getResponse().getOutputStream();
+        }
+
+        @Override
+        public void event(String name, String data) throws IOException
+        {
+            synchronized (this)
+            {
+                output.write(EVENT_FIELD);
+                output.write(name.getBytes(StandardCharsets.UTF_8));
+                output.write(CRLF);
+                data(data);
+            }
+        }
+
+        @Override
+        public void data(String data) throws IOException
+        {
+            synchronized (this)
+            {
+                BufferedReader reader = new BufferedReader(new StringReader(data));
+                String line;
+                while ((line = reader.readLine()) != null)
+                {
+                    output.write(DATA_FIELD);
+                    output.write(line.getBytes(StandardCharsets.UTF_8));
+                    output.write(CRLF);
+                }
+                output.write(CRLF);
+                flush();
+            }
+        }
+
+        @Override
+        public void comment(String comment) throws IOException
+        {
+            synchronized (this)
+            {
+                output.write(COMMENT_FIELD);
+                output.write(comment.getBytes(StandardCharsets.UTF_8));
+                output.write(CRLF);
+                output.write(CRLF);
+                flush();
+            }
+        }
+
+        @Override
+        public void run()
+        {
+            // If the other peer closes the connection, the first
+            // flush() should generate a TCP reset that is detected
+            // on the second flush()
+            try
+            {
+                synchronized (this)
+                {
+                    output.write('\r');
+                    flush();
+                    output.write('\n');
+                    flush();
+                }
+                // We could write, reschedule heartbeat
+                scheduleHeartBeat();
+            }
+            catch (IOException x)
+            {
+                // The other peer closed the connection
+                close();
+                eventSource.onClose();
+            }
+        }
+
+        protected void flush() throws IOException
+        {
+            async.getResponse().flushBuffer();
+        }
+        
+        @Override
+        public void close()
+        {
+            synchronized (this)
+            {
+                closed = true;
+                heartBeat.cancel(false);
+            }
+            async.complete();
+        }
+
+        private void scheduleHeartBeat()
+        {
+            synchronized (this)
+            {
+                if (!closed)
+                    heartBeat = scheduler.schedule(this, heartBeatPeriod, TimeUnit.SECONDS);
+            }
+        }
+    }
+}
diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/GzipFilter.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/GzipFilter.java
index 926a260..24738a1 100644
--- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/GzipFilter.java
+++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/GzipFilter.java
@@ -18,34 +18,34 @@
 
 package org.eclipse.jetty.servlets;
 
+import java.io.File;
 import java.io.IOException;
+import java.io.OutputStream;
 import java.util.HashSet;
 import java.util.Locale;
 import java.util.Set;
 import java.util.StringTokenizer;
 import java.util.regex.Pattern;
 import java.util.zip.Deflater;
-import java.util.zip.DeflaterOutputStream;
-import java.util.zip.GZIPOutputStream;
 
+import javax.servlet.AsyncEvent;
+import javax.servlet.AsyncListener;
 import javax.servlet.FilterChain;
 import javax.servlet.FilterConfig;
 import javax.servlet.ServletContext;
 import javax.servlet.ServletException;
 import javax.servlet.ServletRequest;
 import javax.servlet.ServletResponse;
-import javax.servlet.ServletResponseWrapper;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
-import javax.servlet.http.HttpServletResponseWrapper;
-
-import org.eclipse.jetty.continuation.Continuation;
-import org.eclipse.jetty.continuation.ContinuationListener;
-import org.eclipse.jetty.continuation.ContinuationSupport;
-import org.eclipse.jetty.http.HttpMethods;
-import org.eclipse.jetty.http.gzip.CompressedResponseWrapper;
-import org.eclipse.jetty.http.gzip.AbstractCompressedStream;
-import org.eclipse.jetty.util.StringUtil;
+
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.servlets.gzip.AbstractCompressedStream;
+import org.eclipse.jetty.servlets.gzip.CompressedResponseWrapper;
+import org.eclipse.jetty.servlets.gzip.DeflatedOutputStream;
+import org.eclipse.jetty.servlets.gzip.GzipOutputStream;
+import org.eclipse.jetty.util.URIUtil;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
 
@@ -56,63 +56,71 @@ import org.eclipse.jetty.util.log.Logger;
  * <li>accept-encoding header is set to either gzip, deflate or a combination of those</li>
  * <li>The response status code is >=200 and <300
  * <li>The content length is unknown or more than the <code>minGzipSize</code> initParameter or the minGzipSize is 0(default)</li>
- * <li>The content-type is in the comma separated list of mimeTypes set in the <code>mimeTypes</code> initParameter or
- * if no mimeTypes are defined the content-type is not "application/gzip"</li>
+ * <li>If a list of mimeTypes is set by the <code>mimeTypes</code> init parameter, then the Content-Type is in the list.</li>
+ * <li>If no mimeType list is set, then the content-type is not in the list defined by <code>excludedMimeTypes</code></li>
  * <li>No content-encoding is specified by the resource</li>
  * </ul>
- * 
+ *
  * <p>
  * If both gzip and deflate are specified in the accept-encoding header, then gzip will be used.
  * </p>
  * <p>
  * Compressing the content can greatly improve the network bandwidth usage, but at a cost of memory and
- * CPU cycles. If this filter is mapped for static content, then use of efficient direct NIO may be 
- * prevented, thus use of the gzip mechanism of the {@link org.eclipse.jetty.servlet.DefaultServlet} is 
+ * CPU cycles. If this filter is mapped for static content, then use of efficient direct NIO may be
+ * prevented, thus use of the gzip mechanism of the {@link org.eclipse.jetty.servlet.DefaultServlet} is
  * advised instead.
  * </p>
  * <p>
- * This filter extends {@link UserAgentFilter} and if the the initParameter <code>excludedAgents</code> 
+ * This filter extends {@link UserAgentFilter} and if the the initParameter <code>excludedAgents</code>
  * is set to a comma separated list of user agents, then these agents will be excluded from gzip content.
  * </p>
  * <p>Init Parameters:</p>
- * <PRE>
- * bufferSize                 The output buffer size. Defaults to 8192. Be careful as values <= 0 will lead to an 
- *                            {@link IllegalArgumentException}. 
+ * <dl>
+ * <dt>bufferSize</dt>       <dd>The output buffer size. Defaults to 8192. Be careful as values <= 0 will lead to an
+ *                            {@link IllegalArgumentException}.
  *                            See: {@link java.util.zip.GZIPOutputStream#GZIPOutputStream(java.io.OutputStream, int)}
  *                            and: {@link java.util.zip.DeflaterOutputStream#DeflaterOutputStream(java.io.OutputStream, Deflater, int)}
- *                      
- * minGzipSize                Content will only be compressed if content length is either unknown or greater
+ * </dd>
+ * <dt>minGzipSize</dt>       <dd>Content will only be compressed if content length is either unknown or greater
  *                            than <code>minGzipSize</code>.
- *                      
- * deflateCompressionLevel    The compression level used for deflate compression. (0-9).
+ * </dd>
+ * <dt>deflateCompressionLevel</dt>       <dd>The compression level used for deflate compression. (0-9).
  *                            See: {@link java.util.zip.Deflater#Deflater(int, boolean)}
- *                            
- * deflateNoWrap              The noWrap setting for deflate compression. Defaults to true. (true/false)
+ * </dd>
+ * <dt>deflateNoWrap</dt>       <dd>The noWrap setting for deflate compression. Defaults to true. (true/false)
  *                            See: {@link java.util.zip.Deflater#Deflater(int, boolean)}
- *
- * methods                    Comma separated list of HTTP methods to compress. If not set, only GET requests are compressed.
- * 
- * mimeTypes                  Comma separated list of mime types to compress. See description above.
- * 
- * excludedAgents             Comma separated list of user agents to exclude from compression. Does a 
+ * </dd>
+ * <dt>methods</dt>       <dd>Comma separated list of HTTP methods to compress. If not set, only GET requests are compressed.
+ *  </dd>
+ * <dt>mimeTypes</dt>       <dd>Comma separated list of mime types to compress. If it is not set, then the excludedMimeTypes list is used.
+ * </dd>
+ * <dt>excludedMimeTypes</dt>       <dd>Comma separated list of mime types to never compress. If not set, then the default is the commonly known
+ * image, video, audio and compressed types.
+ * </dd>
+
+ * <dt>excludedAgents</dt>       <dd>Comma separated list of user agents to exclude from compression. Does a
  *                            {@link String#contains(CharSequence)} to check if the excluded agent occurs
  *                            in the user-agent header. If it does -> no compression
- *                            
- * excludeAgentPatterns       Same as excludedAgents, but accepts regex patterns for more complex matching.
- * 
- * excludePaths               Comma separated list of paths to exclude from compression. 
+ * </dd>
+ * <dt>excludeAgentPatterns</dt>       <dd>Same as excludedAgents, but accepts regex patterns for more complex matching.
+ * </dd>
+ * <dt>excludePaths</dt>       <dd>Comma separated list of paths to exclude from compression.
  *                            Does a {@link String#startsWith(String)} comparison to check if the path matches.
  *                            If it does match -> no compression. To match subpaths use <code>excludePathPatterns</code>
  *                            instead.
- * 
- * excludePathPatterns        Same as excludePath, but accepts regex patterns for more complex matching.
- * 
- * vary                       Set to the value of the Vary header sent with responses that could be compressed.  By default it is 
+ * </dd>
+ * <dt>excludePathPatterns</dt>       <dd>Same as excludePath, but accepts regex patterns for more complex matching.
+ * </dd>
+ * <dt>vary</dt>       <dd>Set to the value of the Vary header sent with responses that could be compressed.  By default it is 
  *                            set to 'Vary: Accept-Encoding, User-Agent' since IE6 is excluded by default from the excludedAgents. 
  *                            If user-agents are not to be excluded, then this can be set to 'Vary: Accept-Encoding'.  Note also 
  *                            that shared caches may cache copies of a resource that is varied by User-Agent - one per variation of 
  *                            the User-Agent, unless the cache does some normalization of the UA string.
- * </PRE>
+ * </dd>                         
+ * <dt>checkGzExists</dt>       <dd>If set to true, the filter check if a static resource with ".gz" appended exists.  If so then
+ *                            the normal processing is done so that the default servlet can send  the pre existing gz content.
+ *  </dd>
+ *  </dl>
  */
 public class GzipFilter extends UserAgentFilter
 {
@@ -124,11 +132,18 @@ public class GzipFilter extends UserAgentFilter
     public final static String ETAG="o.e.j.s.GzipFilter.ETag";
 
     protected ServletContext _context;
-    protected Set<String> _mimeTypes;
+    protected final Set<String> _mimeTypes=new HashSet<>();
+    protected boolean _excludeMimeTypes;
     protected int _bufferSize=8192;
     protected int _minGzipSize=256;
     protected int _deflateCompressionLevel=Deflater.DEFAULT_COMPRESSION;
     protected boolean _deflateNoWrap = true;
+    protected boolean _checkGzExists = true;
+    
+    // non-static, as other GzipFilter instances may have different configurations
+    protected final ThreadLocal<Deflater> _deflater = new ThreadLocal<Deflater>();
+
+    protected final static ThreadLocal<byte[]> _buffer= new ThreadLocal<byte[]>();
 
     protected final Set<String> _methods=new HashSet<String>();
     protected Set<String> _excludedAgents;
@@ -142,7 +157,7 @@ public class GzipFilter extends UserAgentFilter
     private static final int STATE_QVALUE = 2;
     private static final int STATE_DEFAULT = 3;
 
-    
+
     /* ------------------------------------------------------------ */
     /**
      * @see org.eclipse.jetty.servlets.UserAgentFilter#init(javax.servlet.FilterConfig)
@@ -151,7 +166,7 @@ public class GzipFilter extends UserAgentFilter
     public void init(FilterConfig filterConfig) throws ServletException
     {
         super.init(filterConfig);
-        
+
         _context=filterConfig.getServletContext();
         
         String tmp=filterConfig.getInitParameter("bufferSize");
@@ -161,32 +176,61 @@ public class GzipFilter extends UserAgentFilter
         tmp=filterConfig.getInitParameter("minGzipSize");
         if (tmp!=null)
             _minGzipSize=Integer.parseInt(tmp);
-        
+
         tmp=filterConfig.getInitParameter("deflateCompressionLevel");
         if (tmp!=null)
             _deflateCompressionLevel=Integer.parseInt(tmp);
-        
+
         tmp=filterConfig.getInitParameter("deflateNoWrap");
         if (tmp!=null)
             _deflateNoWrap=Boolean.parseBoolean(tmp);
+
+        tmp=filterConfig.getInitParameter("checkGzExists");
+        if (tmp!=null)
+            _checkGzExists=Boolean.parseBoolean(tmp);
         
         tmp=filterConfig.getInitParameter("methods");
         if (tmp!=null)
         {
             StringTokenizer tok = new StringTokenizer(tmp,",",false);
             while (tok.hasMoreTokens())
-                _methods.add(tok.nextToken().trim().toUpperCase());
+                _methods.add(tok.nextToken().trim().toUpperCase(Locale.ENGLISH));
         }
         else
-            _methods.add(HttpMethods.GET);
+            _methods.add(HttpMethod.GET.asString());
         
         tmp=filterConfig.getInitParameter("mimeTypes");
-        if (tmp!=null)
+        if (tmp==null)
+        {
+            _excludeMimeTypes=true;
+            tmp=filterConfig.getInitParameter("excludedMimeTypes");
+            if (tmp==null)
+            {
+                for (String type:MimeTypes.getKnownMimeTypes())
+                {
+                    if (type.equals("image/svg+xml")) //always compressable (unless .svgz file)
+                        continue;
+                    if (type.startsWith("image/")||
+                        type.startsWith("audio/")||
+                        type.startsWith("video/"))
+                        _mimeTypes.add(type);
+                }
+                _mimeTypes.add("application/compress");
+                _mimeTypes.add("application/zip");
+                _mimeTypes.add("application/gzip");
+            }
+            else
+            {
+                StringTokenizer tok = new StringTokenizer(tmp,",",false);
+                while (tok.hasMoreTokens())
+                    _mimeTypes.add(tok.nextToken().trim());
+            }
+        }
+        else
         {
-            _mimeTypes=new HashSet<String>();
             StringTokenizer tok = new StringTokenizer(tmp,",",false);
             while (tok.hasMoreTokens())
-                _mimeTypes.add(tok.nextToken());
+                _mimeTypes.add(tok.nextToken().trim());
         }
         tmp=filterConfig.getInitParameter("excludedAgents");
         if (tmp!=null)
@@ -194,35 +238,35 @@ public class GzipFilter extends UserAgentFilter
             _excludedAgents=new HashSet<String>();
             StringTokenizer tok = new StringTokenizer(tmp,",",false);
             while (tok.hasMoreTokens())
-               _excludedAgents.add(tok.nextToken());
+               _excludedAgents.add(tok.nextToken().trim());
         }
-        
-                tmp=filterConfig.getInitParameter("excludeAgentPatterns");
+
+        tmp=filterConfig.getInitParameter("excludeAgentPatterns");
         if (tmp!=null)
         {
             _excludedAgentPatterns=new HashSet<Pattern>();
             StringTokenizer tok = new StringTokenizer(tmp,",",false);
             while (tok.hasMoreTokens())
-                _excludedAgentPatterns.add(Pattern.compile(tok.nextToken()));            
-        }        
-        
+                _excludedAgentPatterns.add(Pattern.compile(tok.nextToken().trim()));
+        }
+
         tmp=filterConfig.getInitParameter("excludePaths");
         if (tmp!=null)
         {
             _excludedPaths=new HashSet<String>();
             StringTokenizer tok = new StringTokenizer(tmp,",",false);
             while (tok.hasMoreTokens())
-                _excludedPaths.add(tok.nextToken());            
+                _excludedPaths.add(tok.nextToken().trim());
         }
-        
+
         tmp=filterConfig.getInitParameter("excludePathPatterns");
         if (tmp!=null)
         {
             _excludedPathPatterns=new HashSet<Pattern>();
             StringTokenizer tok = new StringTokenizer(tmp,",",false);
             while (tok.hasMoreTokens())
-                _excludedPathPatterns.add(Pattern.compile(tok.nextToken()));            
-        }       
+                _excludedPathPatterns.add(Pattern.compile(tok.nextToken().trim()));
+        }
         
         tmp=filterConfig.getInitParameter("vary");
         if (tmp!=null)
@@ -237,13 +281,13 @@ public class GzipFilter extends UserAgentFilter
     public void destroy()
     {
     }
-    
+
     /* ------------------------------------------------------------ */
     /**
      * @see org.eclipse.jetty.servlets.UserAgentFilter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)
      */
     @Override
-    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) 
+    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
         throws IOException, ServletException
     {
         HttpServletRequest request=(HttpServletRequest)req;
@@ -256,17 +300,43 @@ public class GzipFilter extends UserAgentFilter
             super.doFilter(request,response,chain);
             return;
         }
-        
+
         // Exclude non compressible mime-types known from URI extension. - no Vary because no matter what client, this URI is always excluded
-        if (_mimeTypes!=null && _mimeTypes.size()>0)
+        if (_mimeTypes.size()>0 && _excludeMimeTypes)
         {
             String mimeType = _context.getMimeType(request.getRequestURI());
-            
-            if (mimeType!=null && !_mimeTypes.contains(mimeType))
+
+            if (mimeType!=null)
             {
-                // handle normally without setting vary header
-                super.doFilter(request,response,chain);
-                return;
+                mimeType = MimeTypes.getContentTypeWithoutCharset(mimeType);
+                if (_mimeTypes.contains(mimeType))
+                {
+                    // handle normally without setting vary header
+                    super.doFilter(request,response,chain);
+                    return;
+                }
+            }
+        }
+        
+        //If the Content-Encoding is already set, then we won't compress
+        if (response.getHeader("Content-Encoding") != null)
+        {
+            super.doFilter(request,response,chain);
+            return;
+        }
+
+        if (_checkGzExists && request.getServletContext()!=null)
+        {
+            String path=request.getServletContext().getRealPath(URIUtil.addPaths(request.getServletPath(),request.getPathInfo()));
+            if (path!=null)
+            {
+                File gz=new File(path+".gz");
+                if (gz.exists())
+                {
+                    // allow default servlet to handle
+                    super.doFilter(request,response,chain);
+                    return;
+                }
             }
         }
         
@@ -276,7 +346,7 @@ public class GzipFilter extends UserAgentFilter
         
         // Acceptable compression type
         String compressionType = ua_excluded?null:selectCompression(request.getHeader("accept-encoding"));
-        
+
         // Special handling for etags
         String etag = request.getHeader("If-None-Match"); 
         if (etag!=null)
@@ -296,10 +366,9 @@ public class GzipFilter extends UserAgentFilter
         }
         finally
         {
-            Continuation continuation = ContinuationSupport.getContinuation(request);
-            if (continuation.isSuspended() && continuation.isResponseWrapped())   
+            if (request.isAsyncStarted())
             {
-                continuation.addContinuationListener(new ContinuationListenerWaitingForWrappedResponseToFinish(wrappedResponse));
+                request.getAsyncContext().addListener(new FinishOnCompleteListener(wrappedResponse));
             }
             else if (exceptional && !response.isCommitted())
             {
@@ -399,113 +468,94 @@ public class GzipFilter extends UserAgentFilter
         return true;
     }
     
-    
+
     protected CompressedResponseWrapper createWrappedResponse(HttpServletRequest request, HttpServletResponse response, final String compressionType)
     {
         CompressedResponseWrapper wrappedResponse = null;
-        if (compressionType==null)
+        wrappedResponse = new CompressedResponseWrapper(request,response)
         {
-            wrappedResponse = new CompressedResponseWrapper(request,response)
+            @Override
+            protected AbstractCompressedStream newCompressedStream(HttpServletRequest request, HttpServletResponse response) throws IOException
             {
-                @Override
-                protected AbstractCompressedStream newCompressedStream(HttpServletRequest request,HttpServletResponse response) throws IOException
+                return new AbstractCompressedStream(compressionType,request,this,_vary)
                 {
-                    return new AbstractCompressedStream(null,request,this,_vary)
+                    private Deflater _allocatedDeflater;
+                    private byte[] _allocatedBuffer;
+
+                    @Override
+                    protected OutputStream createStream() throws IOException
                     {
-                        @Override
-                        protected DeflaterOutputStream createStream() throws IOException
+                        if (compressionType == null)
                         {
                             return null;
                         }
-                    };
-                }
-            };
-        }
-        else if (compressionType.equals(GZIP))
-        {
-            wrappedResponse = new CompressedResponseWrapper(request,response)
-            {
-                @Override
-                protected AbstractCompressedStream newCompressedStream(HttpServletRequest request,HttpServletResponse response) throws IOException
-                {
-                    return new AbstractCompressedStream(compressionType,request,this,_vary)
-                    {
-                        @Override
-                        protected DeflaterOutputStream createStream() throws IOException
+                        
+                        // acquire deflater instance
+                        _allocatedDeflater = _deflater.get();   
+                        if (_allocatedDeflater==null)
+                            _allocatedDeflater = new Deflater(_deflateCompressionLevel,_deflateNoWrap);
+                        else
                         {
-                            return new GZIPOutputStream(_response.getOutputStream(),_bufferSize)
-                            {
-                                /**
-                                 * Work around a bug in the jvm GzipOutputStream whereby it is not
-                                 * thread safe when thread A calls finish, but thread B is writing
-                                 * @see java.util.zip.GZIPOutputStream#finish()
-                                 */
-                                @Override
-                                public synchronized void finish() throws IOException
-                                {
-                                    super.finish();
-                                }
-                                
-                                /**
-                                 * Work around a bug in the jvm GzipOutputStream whereby it is not
-                                 * thread safe when thread A calls close(), but thread B is writing
-                                 * @see java.util.zip.GZIPOutputStream#close()
-                                 */
-                                @Override
-                                public synchronized void close() throws IOException
-                                {
-                                    super.close();
-                                }           
-                            };
+                            _deflater.set(null);
+                            _allocatedDeflater.reset();
                         }
-                    };
-                }
-            };
-        }
-        else if (compressionType.equals(DEFLATE))
-        {
-            wrappedResponse = new CompressedResponseWrapper(request,response)
-            {
-                @Override
-                protected AbstractCompressedStream newCompressedStream(HttpServletRequest request,HttpServletResponse response) throws IOException
-                {
-                    return new AbstractCompressedStream(compressionType,request,this,_vary)
+                        
+                        // acquire buffer
+                        _allocatedBuffer = _buffer.get();
+                        if (_allocatedBuffer==null)
+                            _allocatedBuffer = new byte[_bufferSize];
+                        else
+                            _buffer.set(null);
+                        
+                        switch (compressionType)
+                        {
+                            case GZIP:
+                                return new GzipOutputStream(_response.getOutputStream(),_allocatedDeflater,_allocatedBuffer);
+                            case DEFLATE:
+                                return new DeflatedOutputStream(_response.getOutputStream(),_allocatedDeflater,_allocatedBuffer);
+                        }
+                        throw new IllegalStateException(compressionType + " not supported");
+                    }
+
+                    @Override
+                    public void finish() throws IOException
                     {
-                        @Override
-                        protected DeflaterOutputStream createStream() throws IOException
+                        super.finish();
+                        if (_allocatedDeflater != null && _deflater.get() == null)
                         {
-                            return new DeflaterOutputStream(_response.getOutputStream(),new Deflater(_deflateCompressionLevel,_deflateNoWrap));
+                            _deflater.set(_allocatedDeflater);
                         }
-                    };
-                }
-            };
-        } 
-        else
-        {
-            throw new IllegalStateException(compressionType + " not supported");
-        }
+                        if (_allocatedBuffer != null && _buffer.get() == null)
+                        {
+                            _buffer.set(_allocatedBuffer);
+                        }
+                    }
+                };
+            }
+        };
         configureWrappedResponse(wrappedResponse);
         return wrappedResponse;
     }
 
     protected void configureWrappedResponse(CompressedResponseWrapper wrappedResponse)
     {
-        wrappedResponse.setMimeTypes(_mimeTypes);
+        wrappedResponse.setMimeTypes(_mimeTypes,_excludeMimeTypes);
         wrappedResponse.setBufferSize(_bufferSize);
         wrappedResponse.setMinCompressSize(_minGzipSize);
     }
-     
-    private class ContinuationListenerWaitingForWrappedResponseToFinish implements ContinuationListener
+
+    private class FinishOnCompleteListener implements AsyncListener
     {    
         private CompressedResponseWrapper wrappedResponse;
 
-        public ContinuationListenerWaitingForWrappedResponseToFinish(CompressedResponseWrapper wrappedResponse)
+        public FinishOnCompleteListener(CompressedResponseWrapper wrappedResponse)
         {
             this.wrappedResponse = wrappedResponse;
         }
 
-        public void onComplete(Continuation continuation)
-        {
+        @Override
+        public void onComplete(AsyncEvent event) throws IOException
+        {          
             try
             {
                 wrappedResponse.finish();
@@ -516,14 +566,25 @@ public class GzipFilter extends UserAgentFilter
             }
         }
 
-        public void onTimeout(Continuation continuation)
+        @Override
+        public void onTimeout(AsyncEvent event) throws IOException
+        {
+        }
+
+        @Override
+        public void onError(AsyncEvent event) throws IOException
+        {
+        }
+
+        @Override
+        public void onStartAsync(AsyncEvent event) throws IOException
         {
         }
     }
-    
+
     /**
      * Checks to see if the userAgent is excluded
-     * 
+     *
      * @param ua
      *            the user agent
      * @return boolean true if excluded
@@ -556,7 +617,7 @@ public class GzipFilter extends UserAgentFilter
 
     /**
      * Checks to see if the path is excluded
-     * 
+     *
      * @param requestURI
      *            the request uri
      * @return boolean true if excluded
diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/IncludableGzipFilter.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/IncludableGzipFilter.java
index 5917bd6..4421530 100644
--- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/IncludableGzipFilter.java
+++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/IncludableGzipFilter.java
@@ -27,23 +27,24 @@ import java.util.zip.Deflater;
 import java.util.zip.DeflaterOutputStream;
 import java.util.zip.GZIPOutputStream;
 
+import javax.servlet.DispatcherType;
 import javax.servlet.FilterConfig;
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import org.eclipse.jetty.http.gzip.CompressedResponseWrapper;
-import org.eclipse.jetty.http.gzip.AbstractCompressedStream;
 import org.eclipse.jetty.io.UncheckedPrintWriter;
+import org.eclipse.jetty.servlets.gzip.AbstractCompressedStream;
+import org.eclipse.jetty.servlets.gzip.CompressedResponseWrapper;
 
 /* ------------------------------------------------------------ */
 /** Includable GZip Filter.
  * This extension to the {@link GzipFilter} that uses Jetty features to allow
- * headers to be set during calls to 
+ * headers to be set during calls to
  * {@link javax.servlet.RequestDispatcher#include(javax.servlet.ServletRequest, javax.servlet.ServletResponse)}.
  * This allows the gzip filter to function correct during includes and to make a decision to gzip or not
  * at the time the buffer fills and on the basis of all response headers.
- * 
+ *
  * If the init parameter "uncheckedPrintWriter" is set to "true", then the PrintWriter used by
  * the wrapped getWriter will be {@link UncheckedPrintWriter}.
  *
@@ -56,7 +57,7 @@ public class IncludableGzipFilter extends GzipFilter
     public void init(FilterConfig filterConfig) throws ServletException
     {
         super.init(filterConfig);
-        
+
         String tmp=filterConfig.getInitParameter("uncheckedPrintWriter");
         if (tmp!=null)
             _uncheckedPrintWriter=Boolean.valueOf(tmp).booleanValue();
@@ -144,10 +145,16 @@ public class IncludableGzipFilter extends GzipFilter
         @Override
         public void setHeader(String name,String value)
         {
-            super.setHeader(name,value);
-            HttpServletResponse response = (HttpServletResponse)getResponse();
-            if (!response.containsHeader(name))
-                response.setHeader("org.eclipse.jetty.server.include."+name,value);
+            if (getRequest().getDispatcherType()==DispatcherType.INCLUDE)
+            {
+                if (!"etag".equalsIgnoreCase(name) && !name.startsWith("content-"))
+                {
+                    HttpServletResponse response = (HttpServletResponse)getResponse();
+                    response.setHeader("org.eclipse.jetty.server.include."+name,value);
+                }
+            }
+            else
+                super.setHeader(name,value);
         }
 
         @Override
diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/MultiPartFilter.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/MultiPartFilter.java
index decfdc9..9adb917 100644
--- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/MultiPartFilter.java
+++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/MultiPartFilter.java
@@ -19,22 +19,20 @@
 package org.eclipse.jetty.servlets;
 
 import java.io.BufferedInputStream;
-import java.io.BufferedOutputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.File;
-import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
-import java.io.OutputStream;
 import java.io.UnsupportedEncodingException;
-import java.util.ArrayList;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.nio.charset.UnsupportedCharsetException;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Enumeration;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
-import java.util.Locale;
 import java.util.Map;
 
 import javax.servlet.Filter;
@@ -49,41 +47,53 @@ import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletRequestWrapper;
 import javax.servlet.http.Part;
 
-import org.eclipse.jetty.util.IO;
 import org.eclipse.jetty.http.MimeTypes;
-import org.eclipse.jetty.io.ByteArrayBuffer;
+import org.eclipse.jetty.util.IO;
 import org.eclipse.jetty.util.LazyList;
 import org.eclipse.jetty.util.MultiMap;
-import org.eclipse.jetty.util.MultiPartInputStream;
-import org.eclipse.jetty.util.QuotedStringTokenizer;
-import org.eclipse.jetty.util.StringUtil;
-import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.MultiPartInputStreamParser;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
 
+
 /* ------------------------------------------------------------ */
 /**
  * Multipart Form Data Filter.
  * <p>
- * This class decodes the multipart/form-data stream sent by a HTML form that uses a file input
- * item.  Any files sent are stored to a temporary file and a File object added to the request 
+ * This class is ONLY needed if you cannot use the Servlet 3.0 APIs for 
+ * configuring and handling multipart requests. See javax.servlet.http.HttpServletRequest.getParts(). If
+ * you use the new servlet apis then you should REMOVE this filter from your webapp.
+ * <p>
+ * This class decodes the <code>multipart/form-data</code> stream sent by a HTML form that uses a file input
+ * item.  Any files sent are stored to a temporary file and a File object added to the request
  * as an attribute.  All other values are made available via the normal getParameter API and
  * the setCharacterEncoding mechanism is respected when converting bytes to Strings.
  * <p>
- * If the init parameter "delete" is set to "true", any files created will be deleted when the
- * current request returns.
- * <p>
- * The init parameter maxFormKeys sets the maximum number of keys that may be present in a 
- * form (default set by system property org.eclipse.jetty.server.Request.maxFormKeys or 1000) to protect 
- * against DOS attacks by bad hash keys. 
- * <p>
- * The init parameter deleteFiles controls if uploaded files are automatically deleted after the request
- * completes.
- * 
- * Use init parameter "maxFileSize" to set the max size file that can be uploaded.
- * 
- * Use init parameter "maxRequestSize" to limit the size of the multipart request.
- * 
+ * Init Parameters:
+ * <dl>
+ * <dt>delete</dt>
+ * <dd>(boolean)
+ * If set to "true", any files created will be deleted when the current request returns.</dd>
+ * <dt>maxFormKeys</dt>
+ * <dd>(number)
+ * Sets the maximum number of keys that may be present in a
+ * form (default set by system property <code>org.eclipse.jetty.server.Request.maxFormKeys</code> or 1000) to protect
+ * against DOS attacks by bad hash keys.
+ * </dd>
+ * <dt>deleteFiles</dt>
+ * <dd>(boolean)
+ * Controls if uploaded files are automatically deleted after the request completes.
+ * </dd>
+ * <dt>maxFileSize</dt>
+ * <dd>(size in bytes)
+ * Set the max size file that can be uploaded.
+ * </dd>
+ * <dt>maxRequestSize</dt>
+ * <dd>(size in bytes)
+ * To limit the size of the multipart request.
+ * </dd>
+ * </dl>
+ * @deprecated See servlet 3.0 apis like javax.servlet.http.HttpServletRequest.getParts()
  */
 public class MultiPartFilter implements Filter
 {
@@ -96,7 +106,7 @@ public class MultiPartFilter implements Filter
     private int _fileOutputBuffer = 0;
     private long _maxFileSize = -1L;
     private long _maxRequestSize = -1L;
-    private int _maxFormKeys = Integer.getInteger("org.eclipse.jetty.server.Request.maxFormKeys",1000).intValue();
+    private int _maxFormKeys = Integer.getInteger("org.eclipse.jetty.server.Request.maxFormKeys", 1000);
 
     /* ------------------------------------------------------------------------------- */
     /**
@@ -115,7 +125,7 @@ public class MultiPartFilter implements Filter
         String maxRequestSize = filterConfig.getInitParameter("maxRequestSize");
         if (maxRequestSize != null)
             _maxRequestSize = Long.parseLong(maxRequestSize.trim());
-        
+
         _context=filterConfig.getServletContext();
         String mfks = filterConfig.getInitParameter("maxFormKeys");
         if (mfks!=null)
@@ -127,7 +137,7 @@ public class MultiPartFilter implements Filter
      * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest,
      *      javax.servlet.ServletResponse, javax.servlet.FilterChain)
      */
-    public void doFilter(ServletRequest request,ServletResponse response,FilterChain chain) 
+    public void doFilter(ServletRequest request,ServletResponse response,FilterChain chain)
         throws IOException, ServletException
     {
         HttpServletRequest srequest=(HttpServletRequest)request;
@@ -139,24 +149,22 @@ public class MultiPartFilter implements Filter
 
         InputStream in = new BufferedInputStream(request.getInputStream());
         String content_type=srequest.getContentType();
-        
+
         //Get current parameters so we can merge into them
-        MultiMap<String> params = new MultiMap<String>();
-        for (Iterator<Map.Entry<String,String[]>> i = request.getParameterMap().entrySet().iterator();i.hasNext();)
+        MultiMap params = new MultiMap();
+        for (Map.Entry<String, String[]> entry : request.getParameterMap().entrySet())
         {
-            Map.Entry<String,String[]> entry=i.next();
-            Object value=entry.getValue();
+            Object value = entry.getValue();
             if (value instanceof String[])
-                params.addValues(entry.getKey(),(String[])value);
+                params.addValues(entry.getKey(), (String[])value);
             else
-                params.add(entry.getKey(),value);
+                params.add(entry.getKey(), value);
         }
-        
+
         MultipartConfigElement config = new MultipartConfigElement(tempdir.getCanonicalPath(), _maxFileSize, _maxRequestSize, _fileOutputBuffer);
-        MultiPartInputStream mpis = new MultiPartInputStream(in, content_type, config, tempdir);
+        MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(in, content_type, config, tempdir);
         mpis.setDeleteOnExit(_deleteFiles);
         request.setAttribute(MULTIPART, mpis);
-
         try
         {
             Collection<Part> parts = mpis.getParts();
@@ -166,7 +174,7 @@ public class MultiPartFilter implements Filter
                 while (itor.hasNext() && params.size() < _maxFormKeys)
                 {
                     Part p = itor.next();
-                    MultiPartInputStream.MultiPart mp = (MultiPartInputStream.MultiPart)p;
+                    MultiPartInputStreamParser.MultiPart mp = (MultiPartInputStreamParser.MultiPart)p;
                     if (mp.getFile() != null)
                     {
                         request.setAttribute(mp.getName(),mp.getFile());
@@ -204,7 +212,7 @@ public class MultiPartFilter implements Filter
         if (!_deleteFiles)
             return;
         
-        MultiPartInputStream mpis = (MultiPartInputStream)request.getAttribute(MULTIPART);
+        MultiPartInputStreamParser mpis = (MultiPartInputStreamParser)request.getAttribute(MULTIPART);
         if (mpis != null)
         {
             try
@@ -218,8 +226,7 @@ public class MultiPartFilter implements Filter
         }
         request.removeAttribute(MULTIPART);
     }
-    
- 
+
     /* ------------------------------------------------------------------------------- */
     /**
      * @see javax.servlet.Filter#destroy()
@@ -232,9 +239,9 @@ public class MultiPartFilter implements Filter
     /* ------------------------------------------------------------------------------- */
     private static class Wrapper extends HttpServletRequestWrapper
     {
-        String _encoding=StringUtil.__UTF8;
-        MultiMap _params;
-        
+        Charset _encoding=StandardCharsets.UTF_8;
+        MultiMap<Object> _params;
+
         /* ------------------------------------------------------------------------------- */
         /** Constructor.
          * @param request
@@ -244,7 +251,7 @@ public class MultiPartFilter implements Filter
             super(request);
             this._params=map;
         }
-        
+
         /* ------------------------------------------------------------------------------- */
         /**
          * @see javax.servlet.ServletRequest#getContentLength()
@@ -254,7 +261,7 @@ public class MultiPartFilter implements Filter
         {
             return 0;
         }
-        
+
         /* ------------------------------------------------------------------------------- */
         /**
          * @see javax.servlet.ServletRequest#getParameter(java.lang.String)
@@ -265,12 +272,12 @@ public class MultiPartFilter implements Filter
             Object o=_params.get(name);
             if (!(o instanceof byte[]) && LazyList.size(o)>0)
                 o=LazyList.get(o,0);
-            
+
             if (o instanceof byte[])
             {
                 try
                 {
-                   return getParameterBytesAsString(name, (byte[])o);
+                    return getParameterBytesAsString(name, (byte[])o);
                 }
                 catch(Exception e)
                 {
@@ -281,13 +288,13 @@ public class MultiPartFilter implements Filter
                 return String.valueOf(o);
             return null;
         }
-        
+
         /* ------------------------------------------------------------------------------- */
         /**
          * @see javax.servlet.ServletRequest#getParameterMap()
          */
         @Override
-        public Map getParameterMap()
+        public Map<String, String[]> getParameterMap()
         {
             Map<String, String[]> cmap = new HashMap<String,String[]>();
             
@@ -295,20 +302,20 @@ public class MultiPartFilter implements Filter
             {
                 cmap.put((String)key,getParameterValues((String)key));
             }
-            
+
             return Collections.unmodifiableMap(cmap);
         }
-        
+
         /* ------------------------------------------------------------------------------- */
         /**
          * @see javax.servlet.ServletRequest#getParameterNames()
          */
         @Override
-        public Enumeration getParameterNames()
+        public Enumeration<String> getParameterNames()
         {
             return Collections.enumeration(_params.keySet());
         }
-        
+
         /* ------------------------------------------------------------------------------- */
         /**
          * @see javax.servlet.ServletRequest#getParameterValues(java.lang.String)
@@ -339,16 +346,23 @@ public class MultiPartFilter implements Filter
             }
             return v;
         }
-        
+
         /* ------------------------------------------------------------------------------- */
         /**
          * @see javax.servlet.ServletRequest#setCharacterEncoding(java.lang.String)
          */
         @Override
-        public void setCharacterEncoding(String enc) 
+        public void setCharacterEncoding(String enc)
             throws UnsupportedEncodingException
         {
-            _encoding=enc;
+            try
+            {
+                _encoding=Charset.forName(enc);
+            }
+            catch (UnsupportedCharsetException e)
+            {
+                throw new UnsupportedEncodingException(e.getMessage());
+            }
         }
         
         
@@ -357,13 +371,20 @@ public class MultiPartFilter implements Filter
         throws UnsupportedEncodingException
         {
             //check if there is a specific encoding for the parameter
-            Object ct = _params.get(name+CONTENT_TYPE_SUFFIX);
+            Object ct = _params.getValue(name+CONTENT_TYPE_SUFFIX,0);
             //use default if not
-            String contentType = _encoding;
+            Charset contentType = _encoding;
             if (ct != null)
             {
-                String tmp = MimeTypes.getCharsetFromContentType(new ByteArrayBuffer((String)ct));
-                contentType = (tmp == null?_encoding:tmp);
+                String tmp = MimeTypes.getCharsetFromContentType((String)ct);
+                try
+                {
+                    contentType = (tmp == null?_encoding:Charset.forName(tmp));
+                }
+                catch (UnsupportedCharsetException e)
+                {
+                    throw new UnsupportedEncodingException(e.getMessage());
+                }
             }
             
             return new String(bytes,contentType);
diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/ProxyServlet.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/ProxyServlet.java
deleted file mode 100644
index 28d777a..0000000
--- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/ProxyServlet.java
+++ /dev/null
@@ -1,908 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.servlets;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.InetSocketAddress;
-import java.net.MalformedURLException;
-import java.net.Socket;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.Collections;
-import java.util.Enumeration;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.StringTokenizer;
-
-import javax.servlet.Servlet;
-import javax.servlet.ServletConfig;
-import javax.servlet.ServletContext;
-import javax.servlet.ServletException;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
-import javax.servlet.UnavailableException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jetty.client.HttpClient;
-import org.eclipse.jetty.client.HttpExchange;
-import org.eclipse.jetty.continuation.Continuation;
-import org.eclipse.jetty.continuation.ContinuationSupport;
-import org.eclipse.jetty.http.HttpHeaderValues;
-import org.eclipse.jetty.http.HttpHeaders;
-import org.eclipse.jetty.http.HttpSchemes;
-import org.eclipse.jetty.http.HttpURI;
-import org.eclipse.jetty.http.PathMap;
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.EofException;
-import org.eclipse.jetty.util.HostMap;
-import org.eclipse.jetty.util.IO;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.util.thread.QueuedThreadPool;
-
-/**
- * Asynchronous Proxy Servlet.
- *
- * Forward requests to another server either as a standard web proxy (as defined by RFC2616) or as a transparent proxy.
- * <p>
- * This servlet needs the jetty-util and jetty-client classes to be available to the web application.
- * <p>
- * To facilitate JMX monitoring, the "HttpClient" and "ThreadPool" are set as context attributes prefixed with the servlet name.
- * <p>
- * The following init parameters may be used to configure the servlet:
- * <ul>
- * <li>name - Name of Proxy servlet (default: "ProxyServlet"
- * <li>maxThreads - maximum threads
- * <li>maxConnections - maximum connections per destination
- * <li>timeout - the period in ms the client will wait for a response from the proxied server
- * <li>idleTimeout - the period in ms a connection to proxied server can be idle for before it is closed
- * <li>requestHeaderSize - the size of the request header buffer (d. 6,144)
- * <li>requestBufferSize - the size of the request buffer (d. 12,288)
- * <li>responseHeaderSize - the size of the response header buffer (d. 6,144)
- * <li>responseBufferSize - the size of the response buffer (d. 32,768)
- * <li>HostHeader - Force the host header to a particular value
- * <li>whiteList - comma-separated list of allowed proxy destinations
- * <li>blackList - comma-separated list of forbidden proxy destinations
- * </ul>
- *
- * @see org.eclipse.jetty.server.handler.ConnectHandler
- */
-public class ProxyServlet implements Servlet
-{
-    protected Logger _log;
-    protected HttpClient _client;
-    protected String _hostHeader;
-
-    protected HashSet<String> _DontProxyHeaders = new HashSet<String>();
-    {
-        _DontProxyHeaders.add("proxy-connection");
-        _DontProxyHeaders.add("connection");
-        _DontProxyHeaders.add("keep-alive");
-        _DontProxyHeaders.add("transfer-encoding");
-        _DontProxyHeaders.add("te");
-        _DontProxyHeaders.add("trailer");
-        _DontProxyHeaders.add("proxy-authorization");
-        _DontProxyHeaders.add("proxy-authenticate");
-        _DontProxyHeaders.add("upgrade");
-    }
-
-    protected ServletConfig _config;
-    protected ServletContext _context;
-    protected HostMap<PathMap> _white = new HostMap<PathMap>();
-    protected HostMap<PathMap> _black = new HostMap<PathMap>();
-
-    /* ------------------------------------------------------------ */
-    /*
-     * (non-Javadoc)
-     *
-     * @see javax.servlet.Servlet#init(javax.servlet.ServletConfig)
-     */
-    public void init(ServletConfig config) throws ServletException
-    {
-        _config = config;
-        _context = config.getServletContext();
-
-        _hostHeader = config.getInitParameter("HostHeader");
-
-        try
-        {
-            _log = createLogger(config);
-
-            _client = createHttpClient(config);
-
-            if (_context != null)
-            {
-                _context.setAttribute(config.getServletName() + ".ThreadPool",_client.getThreadPool());
-                _context.setAttribute(config.getServletName() + ".HttpClient",_client);
-            }
-
-            String white = config.getInitParameter("whiteList");
-            if (white != null)
-            {
-                parseList(white,_white);
-            }
-            String black = config.getInitParameter("blackList");
-            if (black != null)
-            {
-                parseList(black,_black);
-            }
-        }
-        catch (Exception e)
-        {
-            throw new ServletException(e);
-        }
-    }
-
-    public void destroy()
-    {
-        try
-        {
-            _client.stop();
-        }
-        catch (Exception x)
-        {
-            _log.debug(x);
-        }
-    }
-
-
-    /**
-     * Create and return a logger based on the ServletConfig for use in the
-     * proxy servlet
-     *
-     * @param config
-     * @return Logger
-     */
-    protected Logger createLogger(ServletConfig config)
-    {
-        return Log.getLogger("org.eclipse.jetty.servlets." + config.getServletName());
-    }
-
-    /**
-     * Create and return an HttpClientInstance
-     *
-     * @return HttpClient
-     */
-    protected HttpClient createHttpClientInstance()
-    {
-        return new HttpClient();
-    }
-
-    /**
-     * Create and return an HttpClient based on ServletConfig
-     *
-     * By default this implementation will create an instance of the
-     * HttpClient for use by this proxy servlet.
-     *
-     * @param config
-     * @return HttpClient
-     * @throws Exception
-     */
-    protected HttpClient createHttpClient(ServletConfig config) throws Exception
-    {
-        HttpClient client = createHttpClientInstance();
-        client.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);
-
-        String t = config.getInitParameter("maxThreads");
-
-        if (t != null)
-        {
-            client.setThreadPool(new QueuedThreadPool(Integer.parseInt(t)));
-        }
-        else
-        {
-            client.setThreadPool(new QueuedThreadPool());
-        }
-
-        ((QueuedThreadPool)client.getThreadPool()).setName(config.getServletName());
-
-        t = config.getInitParameter("maxConnections");
-
-        if (t != null)
-        {
-            client.setMaxConnectionsPerAddress(Integer.parseInt(t));
-        }
-
-        t = config.getInitParameter("timeout");
-
-        if ( t != null )
-        {
-            client.setTimeout(Long.parseLong(t));
-        }
-
-        t = config.getInitParameter("idleTimeout");
-
-        if ( t != null )
-        {
-            client.setIdleTimeout(Long.parseLong(t));
-        }
-
-        t = config.getInitParameter("requestHeaderSize");
-
-        if ( t != null )
-        {
-            client.setRequestHeaderSize(Integer.parseInt(t));
-        }
-
-        t = config.getInitParameter("requestBufferSize");
-
-        if ( t != null )
-        {
-            client.setRequestBufferSize(Integer.parseInt(t));
-        }
-
-        t = config.getInitParameter("responseHeaderSize");
-
-        if ( t != null )
-        {
-            client.setResponseHeaderSize(Integer.parseInt(t));
-        }
-
-        t = config.getInitParameter("responseBufferSize");
-
-        if ( t != null )
-        {
-            client.setResponseBufferSize(Integer.parseInt(t));
-        }
-
-        client.start();
-
-        return client;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Helper function to process a parameter value containing a list of new entries and initialize the specified host map.
-     *
-     * @param list
-     *            comma-separated list of new entries
-     * @param hostMap
-     *            target host map
-     */
-    private void parseList(String list, HostMap<PathMap> hostMap)
-    {
-        if (list != null && list.length() > 0)
-        {
-            int idx;
-            String entry;
-
-            StringTokenizer entries = new StringTokenizer(list,",");
-            while (entries.hasMoreTokens())
-            {
-                entry = entries.nextToken();
-                idx = entry.indexOf('/');
-
-                String host = idx > 0?entry.substring(0,idx):entry;
-                String path = idx > 0?entry.substring(idx):"/*";
-
-                host = host.trim();
-                PathMap pathMap = hostMap.get(host);
-                if (pathMap == null)
-                {
-                    pathMap = new PathMap(true);
-                    hostMap.put(host,pathMap);
-                }
-                if (path != null)
-                {
-                    pathMap.put(path,path);
-                }
-            }
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Check the request hostname and path against white- and blacklist.
-     *
-     * @param host
-     *            hostname to check
-     * @param path
-     *            path to check
-     * @return true if request is allowed to be proxied
-     */
-    public boolean validateDestination(String host, String path)
-    {
-        if (_white.size() > 0)
-        {
-            boolean match = false;
-
-            Object whiteObj = _white.getLazyMatches(host);
-            if (whiteObj != null)
-            {
-                List whiteList = (whiteObj instanceof List)?(List)whiteObj:Collections.singletonList(whiteObj);
-
-                for (Object entry : whiteList)
-                {
-                    PathMap pathMap = ((Map.Entry<String, PathMap>)entry).getValue();
-                    if (match = (pathMap != null && (pathMap.size() == 0 || pathMap.match(path) != null)))
-                        break;
-                }
-            }
-
-            if (!match)
-                return false;
-        }
-
-        if (_black.size() > 0)
-        {
-            Object blackObj = _black.getLazyMatches(host);
-            if (blackObj != null)
-            {
-                List blackList = (blackObj instanceof List)?(List)blackObj:Collections.singletonList(blackObj);
-
-                for (Object entry : blackList)
-                {
-                    PathMap pathMap = ((Map.Entry<String, PathMap>)entry).getValue();
-                    if (pathMap != null && (pathMap.size() == 0 || pathMap.match(path) != null))
-                        return false;
-                }
-            }
-        }
-
-        return true;
-    }
-
-    /* ------------------------------------------------------------ */
-    /*
-     * (non-Javadoc)
-     *
-     * @see javax.servlet.Servlet#getServletConfig()
-     */
-    public ServletConfig getServletConfig()
-    {
-        return _config;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Get the hostHeader.
-     *
-     * @return the hostHeader
-     */
-    public String getHostHeader()
-    {
-        return _hostHeader;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Set the hostHeader.
-     *
-     * @param hostHeader
-     *            the hostHeader to set
-     */
-    public void setHostHeader(String hostHeader)
-    {
-        _hostHeader = hostHeader;
-    }
-
-    /* ------------------------------------------------------------ */
-    /*
-     * (non-Javadoc)
-     *
-     * @see javax.servlet.Servlet#service(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
-     */
-    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException
-    {
-        final int debug = _log.isDebugEnabled()?req.hashCode():0;
-
-        final HttpServletRequest request = (HttpServletRequest)req;
-        final HttpServletResponse response = (HttpServletResponse)res;
-
-        if ("CONNECT".equalsIgnoreCase(request.getMethod()))
-        {
-            handleConnect(request,response);
-        }
-        else
-        {
-            final InputStream in = request.getInputStream();
-            final OutputStream out = response.getOutputStream();
-
-            final Continuation continuation = ContinuationSupport.getContinuation(request);
-
-            if (!continuation.isInitial())
-                response.sendError(HttpServletResponse.SC_GATEWAY_TIMEOUT); // Need better test that isInitial
-            else
-            {
-
-                String uri = request.getRequestURI();
-                if (request.getQueryString() != null)
-                    uri += "?" + request.getQueryString();
-
-                HttpURI url = proxyHttpURI(request,uri);
-
-                if (debug != 0)
-                    _log.debug(debug + " proxy " + uri + "-->" + url);
-
-                if (url == null)
-                {
-                    response.sendError(HttpServletResponse.SC_FORBIDDEN);
-                    return;
-                }
-
-                HttpExchange exchange = new HttpExchange()
-                {
-                    @Override
-                    protected void onRequestCommitted() throws IOException
-                    {
-                    }
-
-                    @Override
-                    protected void onRequestComplete() throws IOException
-                    {
-                    }
-
-                    @Override
-                    protected void onResponseComplete() throws IOException
-                    {
-                        if (debug != 0)
-                            _log.debug(debug + " complete");
-                        continuation.complete();
-                    }
-
-                    @Override
-                    protected void onResponseContent(Buffer content) throws IOException
-                    {
-                        if (debug != 0)
-                            _log.debug(debug + " content" + content.length());
-                        content.writeTo(out);
-                    }
-
-                    @Override
-                    protected void onResponseHeaderComplete() throws IOException
-                    {
-                    }
-
-                    @Override
-                    protected void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException
-                    {
-                        if (debug != 0)
-                            _log.debug(debug + " " + version + " " + status + " " + reason);
-
-                        if (reason != null && reason.length() > 0)
-                            response.setStatus(status,reason.toString());
-                        else
-                            response.setStatus(status);
-                    }
-
-                    @Override
-                    protected void onResponseHeader(Buffer name, Buffer value) throws IOException
-                    {
-                        String nameString = name.toString();
-                        String s = nameString.toLowerCase(Locale.ENGLISH);
-                        if (!_DontProxyHeaders.contains(s) || (HttpHeaders.CONNECTION_BUFFER.equals(name) && HttpHeaderValues.CLOSE_BUFFER.equals(value)))
-                        {
-                            if (debug != 0)
-                                _log.debug(debug + " " + name + ": " + value);
-
-                            String filteredHeaderValue = filterResponseHeaderValue(nameString,value.toString(),request);
-                            if (filteredHeaderValue != null && filteredHeaderValue.trim().length() > 0)
-                            {
-                                if (debug != 0)
-                                    _log.debug(debug + " " + name + ": (filtered): " + filteredHeaderValue);
-                                response.addHeader(nameString,filteredHeaderValue);
-                            }
-                        }
-                        else if (debug != 0)
-                            _log.debug(debug + " " + name + "! " + value);
-                    }
-
-                    @Override
-                    protected void onConnectionFailed(Throwable ex)
-                    {
-                        handleOnConnectionFailed(ex,request,response);
-
-                        // it is possible this might trigger before the
-                        // continuation.suspend()
-                        if (!continuation.isInitial())
-                        {
-                            continuation.complete();
-                        }
-                    }
-
-                    @Override
-                    protected void onException(Throwable ex)
-                    {
-                        if (ex instanceof EofException)
-                        {
-                            _log.ignore(ex);
-                            //return;
-                        }
-                        handleOnException(ex,request,response);
-
-                        // it is possible this might trigger before the
-                        // continuation.suspend()
-                        if (!continuation.isInitial())
-                        {
-                            continuation.complete();
-                        }
-                    }
-
-                    @Override
-                    protected void onExpire()
-                    {
-                        handleOnExpire(request,response);
-                        continuation.complete();
-                    }
-
-                };
-
-                exchange.setScheme(HttpSchemes.HTTPS.equals(request.getScheme())?HttpSchemes.HTTPS_BUFFER:HttpSchemes.HTTP_BUFFER);
-                exchange.setMethod(request.getMethod());
-                exchange.setURL(url.toString());
-                exchange.setVersion(request.getProtocol());
-
-
-                if (debug != 0)
-                    _log.debug(debug + " " + request.getMethod() + " " + url + " " + request.getProtocol());
-
-                // check connection header
-                String connectionHdr = request.getHeader("Connection");
-                if (connectionHdr != null)
-                {
-                    connectionHdr = connectionHdr.toLowerCase(Locale.ENGLISH);
-                    if (connectionHdr.indexOf("keep-alive") < 0 && connectionHdr.indexOf("close") < 0)
-                        connectionHdr = null;
-                }
-
-                // force host
-                if (_hostHeader != null)
-                    exchange.setRequestHeader("Host",_hostHeader);
-
-                // copy headers
-                boolean xForwardedFor = false;
-                boolean hasContent = false;
-                long contentLength = -1;
-                Enumeration<?> enm = request.getHeaderNames();
-                while (enm.hasMoreElements())
-                {
-                    // TODO could be better than this!
-                    String hdr = (String)enm.nextElement();
-                    String lhdr = hdr.toLowerCase(Locale.ENGLISH);
-
-                    if ("transfer-encoding".equals(lhdr))
-                    {
-                        if (request.getHeader("transfer-encoding").indexOf("chunk")>=0)
-                            hasContent = true;
-                    }
-                    
-                    if (_DontProxyHeaders.contains(lhdr))
-                        continue;
-                    if (connectionHdr != null && connectionHdr.indexOf(lhdr) >= 0)
-                        continue;
-                    if (_hostHeader != null && "host".equals(lhdr))
-                        continue;
-
-                    if ("content-type".equals(lhdr))
-                        hasContent = true;
-                    else if ("content-length".equals(lhdr))
-                    {
-                        contentLength = request.getContentLength();
-                        exchange.setRequestHeader(HttpHeaders.CONTENT_LENGTH,Long.toString(contentLength));
-                        if (contentLength > 0)
-                            hasContent = true;
-                    }
-                    else if ("x-forwarded-for".equals(lhdr))
-                        xForwardedFor = true;
-
-                    Enumeration<?> vals = request.getHeaders(hdr);
-                    while (vals.hasMoreElements())
-                    {
-                        String val = (String)vals.nextElement();
-                        if (val != null)
-                        {
-                            if (debug != 0)
-                                _log.debug(debug + " " + hdr + ": " + val);
-
-                            exchange.setRequestHeader(hdr,val);
-                        }
-                    }
-                }
-
-                // Proxy headers
-                exchange.setRequestHeader("Via","1.1 (jetty)");
-                if (!xForwardedFor)
-                {
-                    exchange.addRequestHeader("X-Forwarded-For",request.getRemoteAddr());
-                    exchange.addRequestHeader("X-Forwarded-Proto",request.getScheme());
-                    exchange.addRequestHeader("X-Forwarded-Host",request.getHeader("Host"));
-                    exchange.addRequestHeader("X-Forwarded-Server",request.getLocalName());
-                }
-
-                if (hasContent)
-                {
-                    exchange.setRequestContentSource(in);
-                }
-
-                customizeExchange(exchange, request);
-
-                /*
-                 * we need to set the timeout on the continuation to take into
-                 * account the timeout of the HttpClient and the HttpExchange
-                 */
-                long ctimeout = (_client.getTimeout() > exchange.getTimeout()) ? _client.getTimeout() : exchange.getTimeout();
-
-                // continuation fudge factor of 1000, underlying components
-                // should fail/expire first from exchange
-                if ( ctimeout == 0 )
-                {
-                    continuation.setTimeout(0);  // ideally never times out
-                }
-                else
-                {
-                    continuation.setTimeout(ctimeout + 1000);
-                }
-
-                customizeContinuation(continuation);
-
-                continuation.suspend(response);
-                _client.send(exchange);
-
-            }
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    public void handleConnect(HttpServletRequest request, HttpServletResponse response) throws IOException
-    {
-        String uri = request.getRequestURI();
-
-        String port = "";
-        String host = "";
-
-        int c = uri.indexOf(':');
-        if (c >= 0)
-        {
-            port = uri.substring(c + 1);
-            host = uri.substring(0,c);
-            if (host.indexOf('/') > 0)
-                host = host.substring(host.indexOf('/') + 1);
-        }
-
-        // TODO - make this async!
-
-        InetSocketAddress inetAddress = new InetSocketAddress(host,Integer.parseInt(port));
-
-        // if (isForbidden(HttpMessage.__SSL_SCHEME,addrPort.getHost(),addrPort.getPort(),false))
-        // {
-        // sendForbid(request,response,uri);
-        // }
-        // else
-        {
-            InputStream in = request.getInputStream();
-            OutputStream out = response.getOutputStream();
-
-            Socket socket = new Socket(inetAddress.getAddress(),inetAddress.getPort());
-
-            response.setStatus(200);
-            response.setHeader("Connection","close");
-            response.flushBuffer();
-            // TODO prevent real close!
-
-            IO.copyThread(socket.getInputStream(),out);
-            IO.copy(in,socket.getOutputStream());
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    protected HttpURI proxyHttpURI(HttpServletRequest request, String uri) throws MalformedURLException
-    {
-        return proxyHttpURI(request.getScheme(), request.getServerName(), request.getServerPort(), uri);
-    }
-
-    protected HttpURI proxyHttpURI(String scheme, String serverName, int serverPort, String uri) throws MalformedURLException
-    {
-        if (!validateDestination(serverName,uri))
-            return null;
-
-        return new HttpURI(scheme + "://" + serverName + ":" + serverPort + uri);
-    }
-
-    /*
-     * (non-Javadoc)
-     *
-     * @see javax.servlet.Servlet#getServletInfo()
-     */
-    public String getServletInfo()
-    {
-        return "Proxy Servlet";
-    }
-
-
-    /**
-     * Extension point for subclasses to customize an exchange. Useful for setting timeouts etc. The default implementation does nothing.
-     *
-     * @param exchange
-     * @param request
-     */
-    protected void customizeExchange(HttpExchange exchange, HttpServletRequest request)
-    {
-
-    }
-
-    /**
-     * Extension point for subclasses to customize the Continuation after it's initial creation in the service method. Useful for setting timeouts etc. The
-     * default implementation does nothing.
-     *
-     * @param continuation
-     */
-    protected void customizeContinuation(Continuation continuation)
-    {
-
-    }
-
-    /**
-     * Extension point for custom handling of an HttpExchange's onConnectionFailed method. The default implementation delegates to
-     * {@link #handleOnException(Throwable, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)}
-     *
-     * @param ex
-     * @param request
-     * @param response
-     */
-    protected void handleOnConnectionFailed(Throwable ex, HttpServletRequest request, HttpServletResponse response)
-    {
-        handleOnException(ex,request,response);
-    }
-
-    /**
-     * Extension point for custom handling of an HttpExchange's onException method. The default implementation sets the response status to
-     * HttpServletResponse.SC_INTERNAL_SERVER_ERROR (503)
-     *
-     * @param ex
-     * @param request
-     * @param response
-     */
-    protected void handleOnException(Throwable ex, HttpServletRequest request, HttpServletResponse response)
-    {
-        if (ex instanceof IOException)
-        {
-            _log.warn(ex.toString());
-            _log.debug(ex);
-        }
-        else
-            _log.warn(ex);
-        
-        if (!response.isCommitted())
-        {
-            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
-        }
-    }
-
-    /**
-     * Extension point for custom handling of an HttpExchange's onExpire method. The default implementation sets the response status to
-     * HttpServletResponse.SC_GATEWAY_TIMEOUT (504)
-     *
-     * @param request
-     * @param response
-     */
-    protected void handleOnExpire(HttpServletRequest request, HttpServletResponse response)
-    {
-        if (!response.isCommitted())
-        {
-            response.setStatus(HttpServletResponse.SC_GATEWAY_TIMEOUT);
-        }
-    }
-
-    /**
-     * Extension point for remote server response header filtering. The default implementation returns the header value as is. If null is returned, this header
-     * won't be forwarded back to the client.
-     * 
-     * @param headerName
-     * @param headerValue
-     * @param request
-     * @return filteredHeaderValue
-     */
-    protected String filterResponseHeaderValue(String headerName, String headerValue, HttpServletRequest request)
-    {
-        return headerValue;
-    }
-
-    /**
-     * Transparent Proxy.
-     * 
-     * This convenience extension to ProxyServlet configures the servlet as a transparent proxy. The servlet is configured with init parameters:
-     * <ul>
-     * <li>ProxyTo - a URI like http://host:80/context to which the request is proxied.
-     * <li>Prefix - a URI prefix that is striped from the start of the forwarded URI.
-     * </ul>
-     * For example, if a request was received at /foo/bar and the ProxyTo was http://host:80/context and the Prefix was /foo, then the request would be proxied
-     * to http://host:80/context/bar
-     * 
-     */
-    public static class Transparent extends ProxyServlet
-    {
-        String _prefix;
-        String _proxyTo;
-
-        public Transparent()
-        {
-        }
-
-        public Transparent(String prefix, String host, int port)
-        {
-            this(prefix,"http",host,port,null);
-        }
-
-        public Transparent(String prefix, String schema, String host, int port, String path)
-        {
-            try
-            {
-                if (prefix != null)
-                {
-                    _prefix = new URI(prefix).normalize().toString();
-                }
-                _proxyTo = new URI(schema,null,host,port,path,null,null).normalize().toString();
-            }
-            catch (URISyntaxException ex)
-            {
-                _log.debug("Invalid URI syntax",ex);
-            }
-        }
-
-        @Override
-        public void init(ServletConfig config) throws ServletException
-        {
-            super.init(config);
-
-            String prefix = config.getInitParameter("Prefix");
-            _prefix = prefix == null?_prefix:prefix;
-
-            // Adjust prefix value to account for context path
-            String contextPath = _context.getContextPath();
-            _prefix = _prefix == null?contextPath:(contextPath + _prefix);
-
-            String proxyTo = config.getInitParameter("ProxyTo");
-            _proxyTo = proxyTo == null?_proxyTo:proxyTo;
-
-            if (_proxyTo == null)
-                throw new UnavailableException("ProxyTo parameter is requred.");
-
-            if (!_prefix.startsWith("/"))
-                throw new UnavailableException("Prefix parameter must start with a '/'.");
-
-            _log.info(config.getServletName() + " @ " + _prefix + " to " + _proxyTo);
-        }
-
-        @Override
-        protected HttpURI proxyHttpURI(final String scheme, final String serverName, int serverPort, final String uri) throws MalformedURLException
-        {
-            try
-            {
-                if (!uri.startsWith(_prefix))
-                    return null;
-
-                URI dstUri = new URI(_proxyTo + uri.substring(_prefix.length())).normalize();
-
-                if (!validateDestination(dstUri.getHost(),dstUri.getPath()))
-                    return null;
-
-                return new HttpURI(dstUri.toString());
-            }
-            catch (URISyntaxException ex)
-            {
-                throw new MalformedURLException(ex.getMessage());
-            }
-        }
-    }
-}
diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/PutFilter.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/PutFilter.java
index d2c1ba4..b31ab31 100644
--- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/PutFilter.java
+++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/PutFilter.java
@@ -48,10 +48,10 @@ import org.eclipse.jetty.util.URIUtil;
 
 /**
  * PutFilter
- * 
+ *
  * A Filter that handles PUT, DELETE and MOVE methods.
  * Files are hidden during PUT operations, so that 404's result.
- * 
+ *
  * The following init parameters pay be used:<ul>
  * <li><b>baseURI</b> - The file URI of the document root for put content.
  * <li><b>delAllowed</b> - boolean, if true DELETE and MOVE methods are supported.
@@ -59,7 +59,7 @@ import org.eclipse.jetty.util.URIUtil;
  * </ul>
  *
  */
-public class PutFilter implements Filter 
+public class PutFilter implements Filter
 {
     public final static String __PUT="PUT";
     public final static String __DELETE="DELETE";
@@ -74,18 +74,18 @@ public class PutFilter implements Filter
     private boolean _delAllowed;
     private boolean _putAtomic;
     private File _tmpdir;
-    
-    
+
+
     /* ------------------------------------------------------------ */
     public void init(FilterConfig config) throws ServletException
     {
         _context=config.getServletContext();
-        
+
         _tmpdir=(File)_context.getAttribute("javax.servlet.context.tempdir");
-            
+
         if (_context.getRealPath("/")==null)
            throw new UnavailableException("Packed war");
-        
+
         String b = config.getInitParameter("baseURI");
         if (b != null)
         {
@@ -96,7 +96,7 @@ public class PutFilter implements Filter
             File base=new File(_context.getRealPath("/"));
             _baseURI=base.toURI().toString();
         }
-        
+
         _delAllowed = getInitBoolean(config,"delAllowed");
         _putAtomic = getInitBoolean(config,"putAtomic");
 
@@ -124,13 +124,13 @@ public class PutFilter implements Filter
 
         String servletPath =request.getServletPath();
         String pathInfo = request.getPathInfo();
-        String pathInContext = URIUtil.addPaths(servletPath, pathInfo);    
+        String pathInContext = URIUtil.addPaths(servletPath, pathInfo);
+
+        String resource = URIUtil.addPaths(_baseURI,pathInContext);
 
-        String resource = URIUtil.addPaths(_baseURI,pathInContext); 
-       
         String method = request.getMethod();
         boolean op = _operations.contains(method);
-        
+
         if (op)
         {
             File file = null;
@@ -144,7 +144,7 @@ public class PutFilter implements Filter
                     boolean exists = file.exists();
                     if (exists && !passConditionalHeaders(request, response, file))
                         return;
-                    
+
                     if (method.equals(__PUT))
                         handlePut(request, response,pathInContext, file);
                     else if (method.equals(__DELETE))
@@ -214,29 +214,31 @@ public class PutFilter implements Filter
                 parent.mkdirs();
                 int toRead = request.getContentLength();
                 InputStream in = request.getInputStream();
-                
-                    
+
+
                 if (_putAtomic)
                 {
                     File tmp=File.createTempFile(file.getName(),null,_tmpdir);
-                    OutputStream out = new FileOutputStream(tmp,false);
-                    if (toRead >= 0)
-                        IO.copy(in, out, toRead);
-                    else
-                        IO.copy(in, out);
-                    out.close();
-                    
+                    try (OutputStream out = new FileOutputStream(tmp,false))
+                    {
+                        if (toRead >= 0)
+                            IO.copy(in, out, toRead);
+                        else
+                            IO.copy(in, out);
+                    }
+
                     if (!tmp.renameTo(file))
                         throw new IOException("rename from "+tmp+" to "+file+" failed");
                 }
                 else
                 {
-                    OutputStream out = new FileOutputStream(file,false);
-                    if (toRead >= 0)
-                        IO.copy(in, out, toRead);
-                    else
-                        IO.copy(in, out);
-                    out.close();
+                    try (OutputStream out = new FileOutputStream(file,false))
+                    {
+                        if (toRead >= 0)
+                            IO.copy(in, out, toRead);
+                        else
+                            IO.copy(in, out);
+                    }
                 }
 
                 response.setStatus(exists ? HttpServletResponse.SC_OK : HttpServletResponse.SC_CREATED);
@@ -289,7 +291,7 @@ public class PutFilter implements Filter
     }
 
     /* ------------------------------------------------------------------- */
-    public void handleMove(HttpServletRequest request, HttpServletResponse response, String pathInContext, File file) 
+    public void handleMove(HttpServletRequest request, HttpServletResponse response, String pathInContext, File file)
         throws ServletException, IOException, URISyntaxException
     {
         String newPath = URIUtil.canonicalPath(request.getHeader("new-uri"));
@@ -298,7 +300,7 @@ public class PutFilter implements Filter
             response.sendError(HttpServletResponse.SC_BAD_REQUEST);
             return;
         }
-        
+
         String contextPath = request.getContextPath();
         if (contextPath != null && !newPath.startsWith(contextPath))
         {
@@ -335,11 +337,11 @@ public class PutFilter implements Filter
                     for (String o : options)
                         value=value==null?o:(value+", "+o);
                 }
-                    
+
                 super.setHeader(name,value);
             }
         });
-        
+
     }
 
     /* ------------------------------------------------------------ */
@@ -349,7 +351,7 @@ public class PutFilter implements Filter
     protected boolean passConditionalHeaders(HttpServletRequest request, HttpServletResponse response, File file) throws IOException
     {
         long date = 0;
-        
+
         if ((date = request.getDateHeader("if-unmodified-since")) > 0)
         {
             if (file.lastModified() / 1000 > date / 1000)
diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/QoSFilter.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/QoSFilter.java
index f64d985..6d2a1b5 100644
--- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/QoSFilter.java
+++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/QoSFilter.java
@@ -23,7 +23,9 @@ import java.util.Queue;
 import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
-
+import javax.servlet.AsyncContext;
+import javax.servlet.AsyncEvent;
+import javax.servlet.AsyncListener;
 import javax.servlet.Filter;
 import javax.servlet.FilterChain;
 import javax.servlet.FilterConfig;
@@ -35,201 +37,196 @@ import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpSession;
 
-import org.eclipse.jetty.continuation.Continuation;
-import org.eclipse.jetty.continuation.ContinuationListener;
-import org.eclipse.jetty.continuation.ContinuationSupport;
 import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
 
 /**
  * Quality of Service Filter.
- * 
+ * <p/>
  * This filter limits the number of active requests to the number set by the "maxRequests" init parameter (default 10).
- * If more requests are received, they are suspended and placed on priority queues.  Priorities are determined by 
- * the {@link #getPriority(ServletRequest)} method and are a value between 0 and the value given by the "maxPriority" 
+ * If more requests are received, they are suspended and placed on priority queues.  Priorities are determined by
+ * the {@link #getPriority(ServletRequest)} method and are a value between 0 and the value given by the "maxPriority"
  * init parameter (default 10), with higher values having higher priority.
- * </p><p>
- * This filter is ideal to prevent wasting threads waiting for slow/limited 
- * resources such as a JDBC connection pool.  It avoids the situation where all of a 
+ * <p/>
+ * This filter is ideal to prevent wasting threads waiting for slow/limited
+ * resources such as a JDBC connection pool.  It avoids the situation where all of a
  * containers thread pool may be consumed blocking on such a slow resource.
- * By limiting the number of active threads, a smaller thread pool may be used as 
- * the threads are not wasted waiting.  Thus more memory may be available for use by 
+ * By limiting the number of active threads, a smaller thread pool may be used as
+ * the threads are not wasted waiting.  Thus more memory may be available for use by
  * the active threads.
- * </p><p>
+ * <p/>
  * Furthermore, this filter uses a priority when resuming waiting requests. So that if
  * a container is under load, and there are many requests waiting for resources,
- * the {@link #getPriority(ServletRequest)} method is used, so that more important 
- * requests are serviced first.     For example, this filter could be deployed with a 
- * maxRequest limit slightly smaller than the containers thread pool and a high priority 
+ * the {@link #getPriority(ServletRequest)} method is used, so that more important
+ * requests are serviced first.     For example, this filter could be deployed with a
+ * maxRequest limit slightly smaller than the containers thread pool and a high priority
  * allocated to admin users.  Thus regardless of load, admin users would always be
  * able to access the web application.
- * </p><p>
+ * <p/>
  * The maxRequest limit is policed by a {@link Semaphore} and the filter will wait a short while attempting to acquire
  * the semaphore. This wait is controlled by the "waitMs" init parameter and allows the expense of a suspend to be
  * avoided if the semaphore is shortly available.  If the semaphore cannot be obtained, the request will be suspended
  * for the default suspend period of the container or the valued set as the "suspendMs" init parameter.
- * </p><p>
- * If the "managedAttr" init parameter is set to true, then this servlet is set as a {@link ServletContext} attribute with the 
+ * <p/>
+ * If the "managedAttr" init parameter is set to true, then this servlet is set as a {@link ServletContext} attribute with the
  * filter name as the attribute name.  This allows context external mechanism (eg JMX via {@link ContextHandler#MANAGED_ATTRIBUTES}) to
  * manage the configuration of the filter.
- * </p>
- * 
- *
  */
+ at ManagedObject("Quality of Service Filter")
 public class QoSFilter implements Filter
 {
-    final static int __DEFAULT_MAX_PRIORITY=10;
-    final static int __DEFAULT_PASSES=10;
-    final static int __DEFAULT_WAIT_MS=50;
-    final static long __DEFAULT_TIMEOUT_MS = -1;
-    
-    final static String MANAGED_ATTR_INIT_PARAM="managedAttr";
-    final static String MAX_REQUESTS_INIT_PARAM="maxRequests";
-    final static String MAX_PRIORITY_INIT_PARAM="maxPriority";
-    final static String MAX_WAIT_INIT_PARAM="waitMs";
-    final static String SUSPEND_INIT_PARAM="suspendMs";
-    
-    ServletContext _context;
+    private static final Logger LOG = Log.getLogger(QoSFilter.class);
 
-    protected long _waitMs;
-    protected long _suspendMs;
-    protected int _maxRequests;
-    
-    private Semaphore _passes;
-    private Queue<Continuation>[] _queue;
-    private ContinuationListener[] _listener;
-    private String _suspended="QoSFilter@"+this.hashCode();
-    
-    /* ------------------------------------------------------------ */
-    /**
-     * @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
-     */
-    public void init(FilterConfig filterConfig) 
-    {
-        _context=filterConfig.getServletContext();
+    static final int __DEFAULT_MAX_PRIORITY = 10;
+    static final int __DEFAULT_PASSES = 10;
+    static final int __DEFAULT_WAIT_MS = 50;
+    static final long __DEFAULT_TIMEOUT_MS = -1;
 
-        int max_priority=__DEFAULT_MAX_PRIORITY;
-        if (filterConfig.getInitParameter(MAX_PRIORITY_INIT_PARAM)!=null)
-            max_priority=Integer.parseInt(filterConfig.getInitParameter(MAX_PRIORITY_INIT_PARAM));
-        _queue=new Queue[max_priority+1];
-        _listener = new ContinuationListener[max_priority + 1];
-        for (int p=0;p<_queue.length;p++)
-        {
-            _queue[p]=new ConcurrentLinkedQueue<Continuation>();
+    static final String MANAGED_ATTR_INIT_PARAM = "managedAttr";
+    static final String MAX_REQUESTS_INIT_PARAM = "maxRequests";
+    static final String MAX_PRIORITY_INIT_PARAM = "maxPriority";
+    static final String MAX_WAIT_INIT_PARAM = "waitMs";
+    static final String SUSPEND_INIT_PARAM = "suspendMs";
 
-            final int priority=p;
-            _listener[p] = new ContinuationListener()
-            {
-                public void onComplete(Continuation continuation)
-                {}
+    private final String _suspended = "QoSFilter@" + Integer.toHexString(hashCode()) + ".SUSPENDED";
+    private final String _resumed = "QoSFilter@" + Integer.toHexString(hashCode()) + ".RESUMED";
+    private long _waitMs;
+    private long _suspendMs;
+    private int _maxRequests;
+    private Semaphore _passes;
+    private Queue<AsyncContext>[] _queues;
+    private AsyncListener[] _listeners;
 
-                public void onTimeout(Continuation continuation)
-                {
-                    _queue[priority].remove(continuation);
-                }
-            };
+    public void init(FilterConfig filterConfig)
+    {
+        int max_priority = __DEFAULT_MAX_PRIORITY;
+        if (filterConfig.getInitParameter(MAX_PRIORITY_INIT_PARAM) != null)
+            max_priority = Integer.parseInt(filterConfig.getInitParameter(MAX_PRIORITY_INIT_PARAM));
+        _queues = new Queue[max_priority + 1];
+        _listeners = new AsyncListener[_queues.length];
+        for (int p = 0; p < _queues.length; ++p)
+        {
+            _queues[p] = new ConcurrentLinkedQueue<>();
+            _listeners[p] = new QoSAsyncListener(p);
         }
-        
-        int maxRequests=__DEFAULT_PASSES;
-        if (filterConfig.getInitParameter(MAX_REQUESTS_INIT_PARAM)!=null)
-            maxRequests=Integer.parseInt(filterConfig.getInitParameter(MAX_REQUESTS_INIT_PARAM));
-        _passes=new Semaphore(maxRequests,true);
+
+        int maxRequests = __DEFAULT_PASSES;
+        if (filterConfig.getInitParameter(MAX_REQUESTS_INIT_PARAM) != null)
+            maxRequests = Integer.parseInt(filterConfig.getInitParameter(MAX_REQUESTS_INIT_PARAM));
+        _passes = new Semaphore(maxRequests, true);
         _maxRequests = maxRequests;
-        
+
         long wait = __DEFAULT_WAIT_MS;
-        if (filterConfig.getInitParameter(MAX_WAIT_INIT_PARAM)!=null)
-            wait=Integer.parseInt(filterConfig.getInitParameter(MAX_WAIT_INIT_PARAM));
-        _waitMs=wait;
-        
+        if (filterConfig.getInitParameter(MAX_WAIT_INIT_PARAM) != null)
+            wait = Integer.parseInt(filterConfig.getInitParameter(MAX_WAIT_INIT_PARAM));
+        _waitMs = wait;
+
         long suspend = __DEFAULT_TIMEOUT_MS;
-        if (filterConfig.getInitParameter(SUSPEND_INIT_PARAM)!=null)
-            suspend=Integer.parseInt(filterConfig.getInitParameter(SUSPEND_INIT_PARAM));
-        _suspendMs=suspend;
+        if (filterConfig.getInitParameter(SUSPEND_INIT_PARAM) != null)
+            suspend = Integer.parseInt(filterConfig.getInitParameter(SUSPEND_INIT_PARAM));
+        _suspendMs = suspend;
 
-        if (_context!=null && Boolean.parseBoolean(filterConfig.getInitParameter(MANAGED_ATTR_INIT_PARAM)))
-            _context.setAttribute(filterConfig.getFilterName(),this);
+        ServletContext context = filterConfig.getServletContext();
+        if (context != null && Boolean.parseBoolean(filterConfig.getInitParameter(MANAGED_ATTR_INIT_PARAM)))
+            context.setAttribute(filterConfig.getFilterName(), this);
     }
-    
-    /* ------------------------------------------------------------ */
-    /**
-     * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)
-     */
-    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
-    throws IOException, ServletException
+
+    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
     {
-        boolean accepted=false;
+        boolean accepted = false;
         try
         {
-            if (request.getAttribute(_suspended)==null)
+            Boolean suspended = (Boolean)request.getAttribute(_suspended);
+            if (suspended == null)
             {
-                accepted=_passes.tryAcquire(_waitMs,TimeUnit.MILLISECONDS);
+                accepted = _passes.tryAcquire(getWaitMs(), TimeUnit.MILLISECONDS);
                 if (accepted)
                 {
-                    request.setAttribute(_suspended,Boolean.FALSE);
+                    request.setAttribute(_suspended, Boolean.FALSE);
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("Accepted {}", request);
                 }
                 else
                 {
-                    request.setAttribute(_suspended,Boolean.TRUE);
+                    request.setAttribute(_suspended, Boolean.TRUE);
                     int priority = getPriority(request);
-                    Continuation continuation = ContinuationSupport.getContinuation(request);
-                    if (_suspendMs>0)
-                        continuation.setTimeout(_suspendMs);
-                    continuation.suspend();
-                    continuation.addContinuationListener(_listener[priority]);
-                    _queue[priority].add(continuation);
+                    AsyncContext asyncContext = request.startAsync();
+                    long suspendMs = getSuspendMs();
+                    if (suspendMs > 0)
+                        asyncContext.setTimeout(suspendMs);
+                    asyncContext.addListener(_listeners[priority]);
+                    _queues[priority].add(asyncContext);
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("Suspended {}", request);
                     return;
                 }
             }
             else
             {
-                Boolean suspended=(Boolean)request.getAttribute(_suspended);
-                
-                if (suspended.booleanValue())
+                if (suspended)
                 {
-                    request.setAttribute(_suspended,Boolean.FALSE);
-                    if (request.getAttribute("javax.servlet.resumed")==Boolean.TRUE)
+                    request.setAttribute(_suspended, Boolean.FALSE);
+                    Boolean resumed = (Boolean)request.getAttribute(_resumed);
+                    if (resumed == Boolean.TRUE)
                     {
                         _passes.acquire();
-                        accepted=true;
+                        accepted = true;
+                        if (LOG.isDebugEnabled())
+                            LOG.debug("Resumed {}", request);
                     }
-                    else 
+                    else
                     {
                         // Timeout! try 1 more time.
-                        accepted = _passes.tryAcquire(_waitMs,TimeUnit.MILLISECONDS);
+                        accepted = _passes.tryAcquire(getWaitMs(), TimeUnit.MILLISECONDS);
+                        if (LOG.isDebugEnabled())
+                            LOG.debug("Timeout {}", request);
                     }
                 }
                 else
                 {
-                    // pass through resume of previously accepted request
+                    // Pass through resume of previously accepted request.
                     _passes.acquire();
                     accepted = true;
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("Passthrough {}", request);
                 }
             }
 
             if (accepted)
             {
-                chain.doFilter(request,response);
+                chain.doFilter(request, response);
             }
             else
             {
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Rejected {}", request);
                 ((HttpServletResponse)response).sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
             }
         }
-        catch(InterruptedException e)
+        catch (InterruptedException e)
         {
-            _context.log("QoS",e);
             ((HttpServletResponse)response).sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
         }
         finally
         {
             if (accepted)
             {
-                for (int p=_queue.length;p-->0;)
+                for (int p = _queues.length - 1; p >= 0; --p)
                 {
-                    Continuation continutaion=_queue[p].poll();
-                    if (continutaion!=null && continutaion.isSuspended())
+                    AsyncContext asyncContext = _queues[p].poll();
+                    if (asyncContext != null)
                     {
-                        continutaion.resume();
-                        break;
+                        ServletRequest candidate = asyncContext.getRequest();
+                        Boolean suspended = (Boolean)candidate.getAttribute(_suspended);
+                        if (suspended == Boolean.TRUE)
+                        {
+                            candidate.setAttribute(_resumed, Boolean.TRUE);
+                            asyncContext.dispatch();
+                            break;
+                        }
                     }
                 }
                 _passes.release();
@@ -237,57 +234,57 @@ public class QoSFilter implements Filter
         }
     }
 
-    /** 
-     * Get the request Priority.
-     * <p> The default implementation assigns the following priorities:<ul>
-     * <li> 2 - for a authenticated request
-     * <li> 1 - for a request with valid /non new session 
+    /**
+     * Computes the request priority.
+     * <p/>
+     * The default implementation assigns the following priorities:
+     * <ul>
+     * <li> 2 - for an authenticated request
+     * <li> 1 - for a request with valid / non new session
      * <li> 0 - for all other requests.
      * </ul>
-     * This method may be specialised to provide application specific priorities.
-     * 
-     * @param request
-     * @return the request priority
+     * This method may be overridden to provide application specific priorities.
+     *
+     * @param request the incoming request
+     * @return the computed request priority
      */
     protected int getPriority(ServletRequest request)
     {
         HttpServletRequest baseRequest = (HttpServletRequest)request;
-        if (baseRequest.getUserPrincipal() != null )
+        if (baseRequest.getUserPrincipal() != null)
+        {
             return 2;
-        else 
+        }
+        else
         {
             HttpSession session = baseRequest.getSession(false);
-            if (session!=null && !session.isNew()) 
+            if (session != null && !session.isNew())
                 return 1;
             else
                 return 0;
         }
     }
 
+    public void destroy()
+    {
+    }
 
-    /* ------------------------------------------------------------ */
     /**
-     * @see javax.servlet.Filter#destroy()
-     */
-    public void destroy(){}
-
-    /* ------------------------------------------------------------ */
-    /** 
      * Get the (short) amount of time (in milliseconds) that the filter would wait
      * for the semaphore to become available before suspending a request.
-     * 
+     *
      * @return wait time (in milliseconds)
      */
+    @ManagedAttribute("(short) amount of time filter will wait before suspending request (in ms)")
     public long getWaitMs()
     {
         return _waitMs;
     }
 
-    /* ------------------------------------------------------------ */
     /**
      * Set the (short) amount of time (in milliseconds) that the filter would wait
      * for the semaphore to become available before suspending a request.
-     * 
+     *
      * @param value wait time (in milliseconds)
      */
     public void setWaitMs(long value)
@@ -295,23 +292,22 @@ public class QoSFilter implements Filter
         _waitMs = value;
     }
 
-    /* ------------------------------------------------------------ */
     /**
      * Get the amount of time (in milliseconds) that the filter would suspend
      * a request for while waiting for the semaphore to become available.
-     * 
+     *
      * @return suspend time (in milliseconds)
      */
+    @ManagedAttribute("amount of time filter will suspend a request for while waiting for the semaphore to become available (in ms)")
     public long getSuspendMs()
     {
         return _suspendMs;
     }
 
-    /* ------------------------------------------------------------ */
     /**
      * Set the amount of time (in milliseconds) that the filter would suspend
      * a request for while waiting for the semaphore to become available.
-     * 
+     *
      * @param value suspend time (in milliseconds)
      */
     public void setSuspendMs(long value)
@@ -319,29 +315,62 @@ public class QoSFilter implements Filter
         _suspendMs = value;
     }
 
-    /* ------------------------------------------------------------ */
     /**
      * Get the maximum number of requests allowed to be processed
      * at the same time.
-     * 
+     *
      * @return maximum number of requests
      */
+    @ManagedAttribute("maximum number of requests to allow processing of at the same time")
     public int getMaxRequests()
     {
         return _maxRequests;
     }
 
-    /* ------------------------------------------------------------ */
     /**
      * Set the maximum number of requests allowed to be processed
      * at the same time.
-     * 
+     *
      * @param value the number of requests
      */
     public void setMaxRequests(int value)
     {
-        _passes = new Semaphore((value-_maxRequests+_passes.availablePermits()), true);
+        _passes = new Semaphore((value - getMaxRequests() + _passes.availablePermits()), true);
         _maxRequests = value;
     }
 
+    private class QoSAsyncListener implements AsyncListener
+    {
+        private final int priority;
+
+        public QoSAsyncListener(int priority)
+        {
+            this.priority = priority;
+        }
+
+        @Override
+        public void onStartAsync(AsyncEvent event) throws IOException
+        {
+        }
+
+        @Override
+        public void onComplete(AsyncEvent event) throws IOException
+        {
+        }
+
+        @Override
+        public void onTimeout(AsyncEvent event) throws IOException
+        {
+            // Remove before it's redispatched, so it won't be
+            // redispatched again at the end of the filtering.
+            AsyncContext asyncContext = event.getAsyncContext();
+            _queues[priority].remove(asyncContext);
+            asyncContext.dispatch();
+        }
+
+        @Override
+        public void onError(AsyncEvent event) throws IOException
+        {
+        }
+    }
 }
diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/UserAgentFilter.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/UserAgentFilter.java
index 879973f..9211753 100644
--- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/UserAgentFilter.java
+++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/UserAgentFilter.java
@@ -23,6 +23,7 @@ import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
+
 import javax.servlet.Filter;
 import javax.servlet.FilterChain;
 import javax.servlet.FilterConfig;
diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/WelcomeFilter.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/WelcomeFilter.java
index 9937191..f6a331c 100644
--- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/WelcomeFilter.java
+++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/WelcomeFilter.java
@@ -17,6 +17,7 @@
 //
 
 package org.eclipse.jetty.servlets;
+
 import java.io.IOException;
 
 import javax.servlet.Filter;
@@ -29,10 +30,10 @@ import javax.servlet.http.HttpServletRequest;
 
 /* ------------------------------------------------------------ */
 /** Welcome Filter
- * This filter can be used to server an index file for a directory 
+ * This filter can be used to server an index file for a directory
  * when no index file actually exists (thus the web.xml mechanism does
  * not work).
- * 
+ *
  * This filter will dispatch requests to a directory (URLs ending with /)
  * to the welcome URL determined by the "welcome" init parameter.  So if
  * the filter "welcome" init parameter is set to "index.do" then a request
@@ -44,19 +45,19 @@ import javax.servlet.http.HttpServletRequest;
 public  class WelcomeFilter implements Filter
 {
     private String welcome;
-    
+
     public void init(FilterConfig filterConfig)
     {
         welcome=filterConfig.getInitParameter("welcome");
-	if (welcome==null)
-	    welcome="index.html";
+        if (welcome==null)
+            welcome="index.html";
     }
 
     /* ------------------------------------------------------------ */
     public void doFilter(ServletRequest request,
                          ServletResponse response,
                          FilterChain chain)
-	throws IOException, ServletException
+        throws IOException, ServletException
     {
         String path=((HttpServletRequest)request).getServletPath();
         if (welcome!=null && path.endsWith("/"))
diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/AbstractCompressedStream.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/AbstractCompressedStream.java
new file mode 100644
index 0000000..dc2e0d0
--- /dev/null
+++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/AbstractCompressedStream.java
@@ -0,0 +1,401 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlets.gzip;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.WriteListener;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.util.ByteArrayOutputStream2;
+
+/* ------------------------------------------------------------ */
+/**
+ * Skeletal implementation of a CompressedStream. This class adds compression features to a ServletOutputStream and takes care of setting response headers, etc.
+ * Major work and configuration is done here. Subclasses using different kinds of compression only have to implement the abstract methods doCompress() and
+ * setContentEncoding() using the desired compression and setting the appropriate Content-Encoding header string.
+ */
+public abstract class AbstractCompressedStream extends ServletOutputStream
+{
+    private final String _encoding;
+    protected final String _vary;
+    protected final CompressedResponseWrapper _wrapper;
+    protected final HttpServletResponse _response;
+    protected OutputStream _out;
+    protected ByteArrayOutputStream2 _bOut;
+    protected OutputStream _compressedOutputStream;
+    protected boolean _closed;
+    protected boolean _doNotCompress;
+
+    /**
+     * Instantiates a new compressed stream.
+     *
+     */
+    public AbstractCompressedStream(String encoding,HttpServletRequest request, CompressedResponseWrapper wrapper,String vary)
+            throws IOException
+    {
+        _encoding=encoding;
+        _wrapper = wrapper;
+        _response = (HttpServletResponse)wrapper.getResponse();
+        _vary=vary;
+        
+        if (_wrapper.getMinCompressSize()==0)
+            doCompress();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Reset buffer.
+     */
+    public void resetBuffer()
+    {
+        if (_response.isCommitted() || _compressedOutputStream!=null )
+            throw new IllegalStateException("Committed");
+        _closed = false;
+        _out = null;
+        _bOut = null;
+        _doNotCompress = false;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setBufferSize(int bufferSize)
+    {
+        if (_bOut!=null && _bOut.getBuf().length<bufferSize)
+        {
+            ByteArrayOutputStream2 b = new ByteArrayOutputStream2(bufferSize);
+            b.write(_bOut.getBuf(),0,_bOut.size());
+            _bOut=b;
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    public void setContentLength()
+    {
+        if (_doNotCompress)
+        {
+            long length=_wrapper.getContentLength();
+            if (length>=0)
+            {
+                if (length < Integer.MAX_VALUE)
+                    _response.setContentLength((int)length);
+                else
+                    _response.setHeader("Content-Length",Long.toString(length));
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see java.io.OutputStream#flush()
+     */
+    @Override
+    public void flush() throws IOException
+    {
+        if (_out == null || _bOut != null)
+        {
+            long length=_wrapper.getContentLength();
+            if (length > 0 && length < _wrapper.getMinCompressSize())
+                doNotCompress(false);
+            else
+                doCompress();
+        }
+
+        _out.flush();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see java.io.OutputStream#close()
+     */
+    @Override
+    public void close() throws IOException
+    {
+        if (_closed)
+            return;
+
+        if (_wrapper.getRequest().getAttribute("javax.servlet.include.request_uri") != null)
+            flush();
+        else
+        {
+            if (_bOut != null)
+            {
+                long length=_wrapper.getContentLength();
+                if (length < 0)
+                {
+                    length = _bOut.getCount();
+                    _wrapper.setContentLength(length);
+                }
+                if (length < _wrapper.getMinCompressSize())
+                    doNotCompress(false);
+                else
+                    doCompress();
+            }
+            else if (_out == null)
+            {
+                // No output
+                doNotCompress(false);
+            }
+
+            if (_compressedOutputStream != null)
+                _compressedOutputStream.close();
+            else
+                _out.close();
+            _closed = true;
+        }
+    }
+
+    /**
+     * Finish.
+     *
+     * @throws IOException
+     *             Signals that an I/O exception has occurred.
+     */
+    public void finish() throws IOException
+    {
+        if (!_closed)
+        {
+            if (_out == null || _bOut != null)
+            {
+                long length=_wrapper.getContentLength();
+                if (length<0 &&_bOut==null || length >= 0 && length < _wrapper.getMinCompressSize())
+                    doNotCompress(false);
+                else
+                    doCompress();
+            }
+
+            if (_compressedOutputStream != null && !_closed)
+            {
+                _closed = true;
+                _compressedOutputStream.close();
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see java.io.OutputStream#write(int)
+     */
+    @Override
+    public void write(int b) throws IOException
+    {
+        checkOut(1);
+        _out.write(b);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see java.io.OutputStream#write(byte[])
+     */
+    @Override
+    public void write(byte b[]) throws IOException
+    {
+        checkOut(b.length);
+        _out.write(b);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see java.io.OutputStream#write(byte[], int, int)
+     */
+    @Override
+    public void write(byte b[], int off, int len) throws IOException
+    {
+        checkOut(len);
+        _out.write(b,off,len);
+    }
+
+    /**
+     * Do compress.
+     *
+     * @throws IOException Signals that an I/O exception has occurred.
+     */
+    public void doCompress() throws IOException
+    {
+        if (_compressedOutputStream==null)
+        {
+            if (_response.isCommitted())
+                throw new IllegalStateException();
+
+            if (_encoding!=null)
+            {
+                setHeader("Content-Encoding", _encoding);
+                if (_response.containsHeader("Content-Encoding"))
+                {
+                    addHeader("Vary",_vary);
+                    _out=_compressedOutputStream=createStream();
+                    if (_out!=null)
+                    {
+                        if (_bOut!=null)
+                        {
+                            _out.write(_bOut.getBuf(),0,_bOut.getCount());
+                            _bOut=null;
+                        }
+
+                        String etag=_wrapper.getETag();
+                        if (etag!=null)
+                        {
+                            int end = etag.length()-1;
+                            if (etag.charAt(end)=='"')
+                                setHeader("ETag",etag.substring(0,end)+"--"+_encoding+'"');
+                            else
+                                setHeader("ETag",etag+"--"+_encoding);
+                        }
+                        return;
+                    }
+                }
+            }
+            
+            doNotCompress(true); // Send vary as it could have been compressed if encoding was present
+        }
+    }
+
+    /**
+     * Do not compress.
+     *
+     * @throws IOException
+     *             Signals that an I/O exception has occurred.
+     */
+    public void doNotCompress(boolean sendVary) throws IOException
+    {
+        if (_compressedOutputStream != null)
+            throw new IllegalStateException("Compressed output stream is already assigned.");
+        if (_out == null || _bOut != null)
+        {
+            if (sendVary)
+                addHeader("Vary",_vary);
+            if (_wrapper.getETag()!=null)
+                setHeader("ETag",_wrapper.getETag());
+                
+            _doNotCompress = true;
+
+            _out = _response.getOutputStream();
+            setContentLength();
+
+            if (_bOut != null)
+                _out.write(_bOut.getBuf(),0,_bOut.getCount());
+            _bOut = null;
+        }
+    }
+
+    /**
+     * Check out.
+     *
+     * @param lengthToWrite
+     *            the length
+     * @throws IOException
+     *             Signals that an I/O exception has occurred.
+     */
+    private void checkOut(int lengthToWrite) throws IOException
+    {
+        if (_closed)
+            throw new IOException("CLOSED");
+
+        if (_out == null)
+        {            
+            // If this first write is larger than buffer size, then we are committing now
+            if (lengthToWrite>_wrapper.getBufferSize())
+            {
+                // if we know this is all the content and it is less than minimum, then do not compress, otherwise do compress
+                long length=_wrapper.getContentLength();
+                if (length>=0 && length<_wrapper.getMinCompressSize())
+                    doNotCompress(false);  // Not compressing by size, so no vary on request headers
+                else
+                    doCompress();
+            }
+            else
+            {
+                // start aggregating writes into a buffered output stream
+                _out = _bOut = new ByteArrayOutputStream2(_wrapper.getBufferSize());
+            }
+        }
+        // else are we aggregating writes?
+        else if (_bOut !=null)
+        {
+            // We are aggregating into the buffered output stream.  
+
+            // If this write fills the buffer, then we are committing
+            if (lengthToWrite>=(_bOut.getBuf().length - _bOut.getCount()))
+            {
+                // if we know this is all the content and it is less than minimum, then do not compress, otherwise do compress
+                long length=_wrapper.getContentLength();
+                if (length>=0 && length<_wrapper.getMinCompressSize())
+                    doNotCompress(false);  // Not compressing by size, so no vary on request headers
+                else
+                    doCompress();
+            }
+        }
+    }
+
+    public OutputStream getOutputStream()
+    {
+        return _out;
+    }
+
+    public boolean isClosed()
+    {
+        return _closed;
+    }
+
+    /**
+     * Allows derived implementations to replace PrintWriter implementation.
+     */
+    protected PrintWriter newWriter(OutputStream out, String encoding) throws UnsupportedEncodingException
+    {
+        return encoding == null?new PrintWriter(out):new PrintWriter(new OutputStreamWriter(out,encoding));
+    }
+
+    protected void addHeader(String name,String value)
+    {
+        _response.addHeader(name, value);
+    }
+
+    protected void setHeader(String name,String value)
+    {
+        _response.setHeader(name, value);
+    }
+
+    @Override
+    public void setWriteListener(WriteListener writeListener)
+    {
+        throw new UnsupportedOperationException("Use AsyncGzipFilter");
+    }
+    
+
+    @Override
+    public boolean isReady()
+    {
+        throw new UnsupportedOperationException("Use AsyncGzipFilter");
+    }
+
+    /**
+     * Create the stream fitting to the underlying compression type.
+     *
+     * @throws IOException
+     *             Signals that an I/O exception has occurred.
+     */
+    protected abstract OutputStream createStream() throws IOException;
+
+
+}
diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/CompressedResponseWrapper.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/CompressedResponseWrapper.java
new file mode 100644
index 0000000..b9ad084
--- /dev/null
+++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/CompressedResponseWrapper.java
@@ -0,0 +1,485 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlets.gzip;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+import java.util.Set;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpServletResponseWrapper;
+
+import org.eclipse.jetty.util.StringUtil;
+
+/*------------------------------------------------------------ */
+/**
+ */
+public abstract class CompressedResponseWrapper extends HttpServletResponseWrapper
+{
+
+    public static final int DEFAULT_BUFFER_SIZE = 8192;
+    public static final int DEFAULT_MIN_COMPRESS_SIZE = 256;
+
+    private Set<String> _mimeTypes;
+    private boolean _excludeMimeTypes;
+    private int _bufferSize=DEFAULT_BUFFER_SIZE;
+    private int _minCompressSize=DEFAULT_MIN_COMPRESS_SIZE;
+    protected HttpServletRequest _request;
+
+    private PrintWriter _writer;
+    private AbstractCompressedStream _compressedStream;
+    private String _etag;
+    private long _contentLength=-1;
+    private boolean _noCompression;
+
+    /* ------------------------------------------------------------ */
+    public CompressedResponseWrapper(HttpServletRequest request, HttpServletResponse response)
+    {
+        super(response);
+        _request = request;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public long getContentLength()
+    {
+        return _contentLength;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public int getBufferSize()
+    {
+        return _bufferSize;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public int getMinCompressSize()
+    {
+        return _minCompressSize;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public String getETag()
+    {
+        return _etag;
+    }
+
+    /* ------------------------------------------------------------ */
+    public HttpServletRequest getRequest()
+    {
+        return _request;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     */
+    public void setMimeTypes(Set<String> mimeTypes,boolean excludeMimeTypes)
+    {
+        _excludeMimeTypes=excludeMimeTypes;
+        _mimeTypes = mimeTypes;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     */
+    @Override
+    public void setBufferSize(int bufferSize)
+    {
+        _bufferSize = bufferSize;
+        if (_compressedStream!=null)
+            _compressedStream.setBufferSize(bufferSize);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.servlets.gzip.CompressedResponseWrapper#setMinCompressSize(int)
+     */
+    public void setMinCompressSize(int minCompressSize)
+    {
+        _minCompressSize = minCompressSize;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.servlets.gzip.CompressedResponseWrapper#setContentType(java.lang.String)
+     */
+    @Override
+    public void setContentType(String ct)
+    {
+        super.setContentType(ct);
+
+        if (!_noCompression && (_compressedStream==null || _compressedStream.getOutputStream()==null))
+        {
+            if (ct!=null)
+            {
+                int colon=ct.indexOf(";");
+                if (colon>0)
+                    ct=ct.substring(0,colon);
+
+                if (_mimeTypes.contains(StringUtil.asciiToLowerCase(ct))==_excludeMimeTypes)
+                    noCompression();
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.servlets.gzip.CompressedResponseWrapper#setStatus(int, java.lang.String)
+     */
+    @SuppressWarnings("deprecation")
+    @Override
+    public void setStatus(int sc, String sm)
+    {
+        super.setStatus(sc,sm);
+        if (sc<200 || sc==204 || sc==205 || sc>=300)
+            noCompression();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.servlets.gzip.CompressedResponseWrapper#setStatus(int)
+     */
+    @Override
+    public void setStatus(int sc)
+    {
+        super.setStatus(sc);
+        if (sc<200 || sc==204 || sc==205 || sc>=300)
+            noCompression();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.servlets.gzip.CompressedResponseWrapper#setContentLength(int)
+     */
+    @Override
+    public void setContentLength(int length)
+    {
+        if (_noCompression)
+            super.setContentLength(length);
+        else
+            setContentLength((long)length);
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void setContentLength(long length)
+    {
+        _contentLength=length;
+        if (_compressedStream!=null)
+            _compressedStream.setContentLength();
+        else if (_noCompression && _contentLength>=0)
+        {
+            HttpServletResponse response = (HttpServletResponse)getResponse();
+            if(_contentLength<Integer.MAX_VALUE)
+            {
+                response.setContentLength((int)_contentLength);
+            }
+            else
+            {
+                response.setHeader("Content-Length", Long.toString(_contentLength));
+            }
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.servlets.gzip.CompressedResponseWrapper#addHeader(java.lang.String, java.lang.String)
+     */
+    @Override
+    public void addHeader(String name, String value)
+    {
+        if ("content-length".equalsIgnoreCase(name))
+        {
+            _contentLength=Long.parseLong(value);
+            if (_compressedStream!=null)
+                _compressedStream.setContentLength();
+        }
+        else if ("content-type".equalsIgnoreCase(name))
+        {
+            setContentType(value);
+        }
+        else if ("content-encoding".equalsIgnoreCase(name))
+        {
+            super.addHeader(name,value);
+            if (!isCommitted())
+            {
+                noCompression();
+            }
+        }
+        else if ("etag".equalsIgnoreCase(name))
+            _etag=value;
+        else
+            super.addHeader(name,value);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.servlets.gzip.CompressedResponseWrapper#flushBuffer()
+     */
+    @Override
+    public void flushBuffer() throws IOException
+    {
+        if (_writer!=null)
+            _writer.flush();
+        if (_compressedStream!=null)
+            _compressedStream.flush();
+        else
+            getResponse().flushBuffer();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.servlets.gzip.CompressedResponseWrapper#reset()
+     */
+    @Override
+    public void reset()
+    {
+        super.reset();
+        if (_compressedStream!=null)
+            _compressedStream.resetBuffer();
+        _writer=null;
+        _compressedStream=null;
+        _noCompression=false;
+        _contentLength=-1;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.servlets.gzip.CompressedResponseWrapper#resetBuffer()
+     */
+    @Override
+    public void resetBuffer()
+    {
+        super.resetBuffer();
+        if (_compressedStream!=null)
+            _compressedStream.resetBuffer();
+        _writer=null;
+        _compressedStream=null;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.servlets.gzip.CompressedResponseWrapper#sendError(int, java.lang.String)
+     */
+    @Override
+    public void sendError(int sc, String msg) throws IOException
+    {
+        resetBuffer();
+        super.sendError(sc,msg);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.servlets.gzip.CompressedResponseWrapper#sendError(int)
+     */
+    @Override
+    public void sendError(int sc) throws IOException
+    {
+        resetBuffer();
+        super.sendError(sc);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.servlets.gzip.CompressedResponseWrapper#sendRedirect(java.lang.String)
+     */
+    @Override
+    public void sendRedirect(String location) throws IOException
+    {
+        resetBuffer();
+        super.sendRedirect(location);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.servlets.gzip.CompressedResponseWrapper#noCompression()
+     */
+    public void noCompression()
+    {
+        if (!_noCompression)
+            setDeferredHeaders();
+        _noCompression=true;
+        if (_compressedStream!=null)
+        {
+            try
+            {
+                _compressedStream.doNotCompress(false);
+            }
+            catch (IOException e)
+            {
+                throw new IllegalStateException(e);
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.servlets.gzip.CompressedResponseWrapper#finish()
+     */
+    public void finish() throws IOException
+    {
+        if (_writer!=null && !_compressedStream.isClosed())
+            _writer.flush();
+        if (_compressedStream!=null)
+            _compressedStream.finish();
+        else 
+            setDeferredHeaders();
+    }
+
+    /* ------------------------------------------------------------ */
+    private void setDeferredHeaders()
+    {
+        if (!isCommitted())
+        {
+            if (_contentLength>=0)
+            {
+                if (_contentLength < Integer.MAX_VALUE)
+                    super.setContentLength((int)_contentLength);
+                else
+                    super.setHeader("Content-Length",Long.toString(_contentLength));
+            }
+            if(_etag!=null)
+                super.setHeader("ETag",_etag);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.servlets.gzip.CompressedResponseWrapper#setHeader(java.lang.String, java.lang.String)
+     */
+    @Override
+    public void setHeader(String name, String value)
+    {
+        if (_noCompression)
+            super.setHeader(name,value);
+        else if ("content-length".equalsIgnoreCase(name))
+        {
+            setContentLength(Long.parseLong(value));
+        }
+        else if ("content-type".equalsIgnoreCase(name))
+        {
+            setContentType(value);
+        }
+        else if ("content-encoding".equalsIgnoreCase(name))
+        {
+            super.setHeader(name,value);
+            if (!isCommitted())
+            {
+                noCompression();
+            }
+        }
+        else if ("etag".equalsIgnoreCase(name))
+            _etag=value;
+        else
+            super.setHeader(name,value);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean containsHeader(String name)
+    {
+        if (!_noCompression && "etag".equalsIgnoreCase(name) && _etag!=null)
+            return true;
+        return super.containsHeader(name);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.servlets.gzip.CompressedResponseWrapper#getOutputStream()
+     */
+    @Override
+    public ServletOutputStream getOutputStream() throws IOException
+    {
+        if (_compressedStream==null)
+        {
+            if (getResponse().isCommitted() || _noCompression)
+                return getResponse().getOutputStream();
+
+            _compressedStream=newCompressedStream(_request,(HttpServletResponse)getResponse());
+        }
+        else if (_writer!=null)
+            throw new IllegalStateException("getWriter() called");
+
+        return _compressedStream;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.servlets.gzip.CompressedResponseWrapper#getWriter()
+     */
+    @Override
+    public PrintWriter getWriter() throws IOException
+    {
+        if (_writer==null)
+        {
+            if (_compressedStream!=null)
+                throw new IllegalStateException("getOutputStream() called");
+
+            if (getResponse().isCommitted() || _noCompression)
+                return getResponse().getWriter();
+
+            _compressedStream=newCompressedStream(_request,(HttpServletResponse)getResponse());
+            _writer=newWriter(_compressedStream,getCharacterEncoding());
+        }
+        return _writer;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.servlets.gzip.CompressedResponseWrapper#setIntHeader(java.lang.String, int)
+     */
+    @Override
+    public void setIntHeader(String name, int value)
+    {
+        if ("content-length".equalsIgnoreCase(name))
+        {
+            _contentLength=value;
+            if (_compressedStream!=null)
+                _compressedStream.setContentLength();
+        }
+        else
+            super.setIntHeader(name,value);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Allows derived implementations to replace PrintWriter implementation.
+     *
+     * @param out the out
+     * @param encoding the encoding
+     * @return the prints the writer
+     * @throws UnsupportedEncodingException the unsupported encoding exception
+     */
+    protected PrintWriter newWriter(OutputStream out,String encoding) throws UnsupportedEncodingException
+    {
+        return encoding==null?new PrintWriter(out):new PrintWriter(new OutputStreamWriter(out,encoding));
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     *@return the underlying CompressedStream implementation
+     */
+    protected abstract AbstractCompressedStream newCompressedStream(HttpServletRequest _request, HttpServletResponse response) throws IOException;
+
+}
diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/DeflatedOutputStream.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/DeflatedOutputStream.java
new file mode 100644
index 0000000..bcc05a0
--- /dev/null
+++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/DeflatedOutputStream.java
@@ -0,0 +1,101 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlets.gzip;
+
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.zip.Deflater;
+
+/**
+ * Reimplementation of {@link java.util.zip.DeflaterOutputStream} that supports reusing the buffer.
+ */
+public class DeflatedOutputStream extends FilterOutputStream
+{
+    protected final Deflater _def;
+    protected final byte[] _buf;
+    protected boolean closed = false;
+
+    public DeflatedOutputStream(OutputStream out, Deflater deflater, byte[] buffer)
+    {
+        super(out);
+        _def = deflater;
+        _buf = buffer;
+    }
+
+    @Override
+    public void write(int b) throws IOException
+    {
+        byte[] buf = new byte[1];
+        buf[0] = (byte)(b & 0xff);
+        write(buf,0,1);
+    }
+
+    @Override
+    public void write(byte[] b, int off, int len) throws IOException
+    {
+        if (_def.finished())
+            throw new IOException("Stream already finished");
+        if ((off | len | (off + len) | (b.length - (off + len))) < 0)
+            throw new IndexOutOfBoundsException();
+        if (len == 0)
+            return;
+        if (!_def.finished())
+        {
+            _def.setInput(b,off,len);
+            while (!_def.needsInput())
+            {
+                deflate();
+            }
+        }
+    }
+
+    private void deflate() throws IOException
+    {
+        int len = _def.deflate(_buf,0,_buf.length);
+        if (len > 0)
+        {
+            out.write(_buf,0,len);
+        }
+    }
+
+    public synchronized void finish() throws IOException
+    {
+        if (!_def.finished())
+        {
+            _def.finish();
+            while (!_def.finished())
+            {
+                deflate();
+            }
+        }
+    }
+
+    @Override
+    public synchronized void close() throws IOException
+    {
+        if (!closed)
+        {
+            finish();
+            out.close();
+            closed = true;
+        }
+    }
+
+}
diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/GzipFactory.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/GzipFactory.java
new file mode 100644
index 0000000..33daa31
--- /dev/null
+++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/GzipFactory.java
@@ -0,0 +1,38 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlets.gzip;
+
+import java.util.zip.Deflater;
+
+import org.eclipse.jetty.http.HttpField;
+import org.eclipse.jetty.server.Request;
+
+public interface GzipFactory
+{
+    int getBufferSize();
+    
+    HttpField getVaryField();
+
+    Deflater getDeflater(Request request, long content_length);
+
+    boolean isExcludedMimeType(String asciiToLowerCase);
+
+    void recycle(Deflater deflater);
+
+}
diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/GzipHandler.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/GzipHandler.java
new file mode 100644
index 0000000..c9976e4
--- /dev/null
+++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/GzipHandler.java
@@ -0,0 +1,404 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlets.gzip;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.StringTokenizer;
+import java.util.zip.DeflaterOutputStream;
+import java.util.zip.GZIPOutputStream;
+
+import javax.servlet.AsyncEvent;
+import javax.servlet.AsyncListener;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.handler.HandlerWrapper;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/**
+ * GZIP Handler This handler will gzip the content of a response if:
+ * <ul>
+ * <li>The handler is mapped to a matching path</li>
+ * <li>The response status code is >=200 and <300
+ * <li>The content length is unknown or more than the <code>minGzipSize</code> initParameter or the minGzipSize is 0(default)</li>
+ * <li>The content-type matches one of the set of mimetypes to be compressed</li>
+ * <li>The content-type does NOT match one of the set of mimetypes AND setExcludeMimeTypes is <code>true</code></li>
+ * <li>No content-encoding is specified by the resource</li>
+ * </ul>
+ *
+ * <p>
+ * Compressing the content can greatly improve the network bandwidth usage, but at a cost of memory and CPU cycles. If this handler is used for static content,
+ * then use of efficient direct NIO may be prevented, thus use of the gzip mechanism of the <code>org.eclipse.jetty.servlet.DefaultServlet</code> is advised instead.
+ * </p>
+ */
+public class GzipHandler extends HandlerWrapper
+{
+    private static final Logger LOG = Log.getLogger(GzipHandler.class);
+
+    final protected Set<String> _mimeTypes=new HashSet<>();
+    protected boolean _excludeMimeTypes=false;
+    protected Set<String> _excludedUA;
+    protected int _bufferSize = 8192;
+    protected int _minGzipSize = 256;
+    protected String _vary = "Accept-Encoding, User-Agent";
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Instantiates a new gzip handler.
+     */
+    public GzipHandler()
+    {
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Get the mime types.
+     *
+     * @return mime types to set
+     */
+    public Set<String> getMimeTypes()
+    {
+        return _mimeTypes;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the mime types.
+     *
+     * @param mimeTypes
+     *            the mime types to set
+     */
+    public void setMimeTypes(Set<String> mimeTypes)
+    {
+        _excludeMimeTypes=false;
+        _mimeTypes.clear();
+        _mimeTypes.addAll(mimeTypes);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the mime types.
+     *
+     * @param mimeTypes
+     *            the mime types to set
+     */
+    public void setMimeTypes(String mimeTypes)
+    {
+        if (mimeTypes != null)
+        {
+            _excludeMimeTypes=false;
+            _mimeTypes.clear();
+            StringTokenizer tok = new StringTokenizer(mimeTypes,",",false);
+            while (tok.hasMoreTokens())
+            {
+                _mimeTypes.add(tok.nextToken());
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the mime types.
+     */
+    public void setExcludeMimeTypes(boolean exclude)
+    {
+        _excludeMimeTypes=exclude;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Get the excluded user agents.
+     *
+     * @return excluded user agents
+     */
+    public Set<String> getExcluded()
+    {
+        return _excludedUA;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the excluded user agents.
+     *
+     * @param excluded
+     *            excluded user agents to set
+     */
+    public void setExcluded(Set<String> excluded)
+    {
+        _excludedUA = excluded;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the excluded user agents.
+     *
+     * @param excluded
+     *            excluded user agents to set
+     */
+    public void setExcluded(String excluded)
+    {
+        if (excluded != null)
+        {
+            _excludedUA = new HashSet<String>();
+            StringTokenizer tok = new StringTokenizer(excluded,",",false);
+            while (tok.hasMoreTokens())
+                _excludedUA.add(tok.nextToken());
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The value of the Vary header set if a response can be compressed.
+     */
+    public String getVary()
+    {
+        return _vary;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the value of the Vary header sent with responses that could be compressed.  
+     * <p>
+     * By default it is set to 'Accept-Encoding, User-Agent' since IE6 is excluded by 
+     * default from the excludedAgents. If user-agents are not to be excluded, then 
+     * this can be set to 'Accept-Encoding'.  Note also that shared caches may cache 
+     * many copies of a resource that is varied by User-Agent - one per variation of the 
+     * User-Agent, unless the cache does some normalization of the UA string.
+     * @param vary The value of the Vary header set if a response can be compressed.
+     */
+    public void setVary(String vary)
+    {
+        _vary = vary;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Get the buffer size.
+     *
+     * @return the buffer size
+     */
+    public int getBufferSize()
+    {
+        return _bufferSize;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the buffer size.
+     *
+     * @param bufferSize
+     *            buffer size to set
+     */
+    public void setBufferSize(int bufferSize)
+    {
+        _bufferSize = bufferSize;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Get the minimum reponse size.
+     *
+     * @return minimum reponse size
+     */
+    public int getMinGzipSize()
+    {
+        return _minGzipSize;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the minimum reponse size.
+     *
+     * @param minGzipSize
+     *            minimum reponse size
+     */
+    public void setMinGzipSize(int minGzipSize)
+    {
+        _minGzipSize = minGzipSize;
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void doStart() throws Exception
+    {
+        if (_mimeTypes.size()==0 && _excludeMimeTypes)
+        {
+            _excludeMimeTypes = true;
+            for (String type:MimeTypes.getKnownMimeTypes())
+            {
+                if (type.equals("image/svg+xml")) //always compressable (unless .svgz file)
+                    continue;
+                if (type.startsWith("image/")||
+                    type.startsWith("audio/")||
+                    type.startsWith("video/"))
+                    _mimeTypes.add(type);
+            }
+            _mimeTypes.add("application/compress");
+            _mimeTypes.add("application/zip");
+            _mimeTypes.add("application/gzip");
+        }
+        super.doStart();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.handler.HandlerWrapper#handle(java.lang.String, org.eclipse.jetty.server.Request, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
+     */
+    @Override
+    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+    {
+        if (_handler!=null && isStarted())
+        {
+            String ae = request.getHeader("accept-encoding");
+            if (ae != null && ae.indexOf("gzip")>=0 && !response.containsHeader("Content-Encoding")
+                    && !HttpMethod.HEAD.is(request.getMethod()))
+            {
+                if (_excludedUA!=null)
+                {
+                    String ua = request.getHeader("User-Agent");
+                    if (_excludedUA.contains(ua))
+                    {
+                        _handler.handle(target,baseRequest, request, response);
+                        return;
+                    }
+                }
+
+                final CompressedResponseWrapper wrappedResponse = newGzipResponseWrapper(request,response);
+
+                boolean exceptional=true;
+                try
+                {
+                    _handler.handle(target, baseRequest, request, wrappedResponse);
+                    exceptional=false;
+                }
+                finally
+                {
+                    if (request.isAsyncStarted())
+                    {
+                        request.getAsyncContext().addListener(new AsyncListener()
+                        {
+                            
+                            @Override
+                            public void onTimeout(AsyncEvent event) throws IOException
+                            {
+                            }
+                            
+                            @Override
+                            public void onStartAsync(AsyncEvent event) throws IOException
+                            {
+                            }
+                            
+                            @Override
+                            public void onError(AsyncEvent event) throws IOException
+                            {
+                            }
+                            
+                            @Override
+                            public void onComplete(AsyncEvent event) throws IOException
+                            {
+                                try
+                                {
+                                    wrappedResponse.finish();
+                                }
+                                catch(IOException e)
+                                {
+                                    LOG.warn(e);
+                                }
+                            }
+                        });
+                    }
+                    else if (exceptional && !response.isCommitted())
+                    {
+                        wrappedResponse.resetBuffer();
+                        wrappedResponse.noCompression();
+                    }
+                    else
+                        wrappedResponse.finish();
+                }
+            }
+            else
+            {
+                _handler.handle(target,baseRequest, request, response);
+            }
+        }
+    }
+
+    /**
+     * Allows derived implementations to replace ResponseWrapper implementation.
+     *
+     * @param request the request
+     * @param response the response
+     * @return the gzip response wrapper
+     */
+    protected CompressedResponseWrapper newGzipResponseWrapper(HttpServletRequest request, HttpServletResponse response)
+    {
+        return new CompressedResponseWrapper(request,response)
+        {
+            {
+                super.setMimeTypes(GzipHandler.this._mimeTypes,GzipHandler.this._excludeMimeTypes);
+                super.setBufferSize(GzipHandler.this._bufferSize);
+                super.setMinCompressSize(GzipHandler.this._minGzipSize);
+            }
+
+            @Override
+            protected AbstractCompressedStream newCompressedStream(HttpServletRequest request,HttpServletResponse response) throws IOException
+            {
+                return new AbstractCompressedStream("gzip",request,this,_vary)
+                {
+                    @Override
+                    protected DeflaterOutputStream createStream() throws IOException
+                    {
+                        return new GZIPOutputStream(_response.getOutputStream(),_bufferSize);
+                    }
+                };
+            }
+
+            @Override
+            protected PrintWriter newWriter(OutputStream out,String encoding) throws UnsupportedEncodingException
+            {
+                return GzipHandler.this.newWriter(out,encoding);
+            }
+        };
+    }
+
+    /**
+     * Allows derived implementations to replace PrintWriter implementation.
+     *
+     * @param out the out
+     * @param encoding the encoding
+     * @return the prints the writer
+     * @throws UnsupportedEncodingException
+     */
+    protected PrintWriter newWriter(OutputStream out,String encoding) throws UnsupportedEncodingException
+    {
+        return encoding==null?new PrintWriter(out):new PrintWriter(new OutputStreamWriter(out,encoding));
+    }
+}
diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/GzipHttpOutput.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/GzipHttpOutput.java
new file mode 100644
index 0000000..c7ffcd2
--- /dev/null
+++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/GzipHttpOutput.java
@@ -0,0 +1,405 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlets.gzip;
+
+import java.nio.ByteBuffer;
+import java.nio.channels.WritePendingException;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.zip.CRC32;
+import java.util.zip.Deflater;
+
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpGenerator;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.server.HttpChannel;
+import org.eclipse.jetty.server.HttpOutput;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.servlets.AsyncGzipFilter;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.IteratingNestedCallback;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public class GzipHttpOutput extends HttpOutput
+{
+    public static Logger LOG = Log.getLogger(GzipHttpOutput.class);
+    private final static HttpGenerator.CachedHttpField CONTENT_ENCODING_GZIP=new HttpGenerator.CachedHttpField(HttpHeader.CONTENT_ENCODING,"gzip");
+    private final static byte[] GZIP_HEADER = new byte[] { (byte)0x1f, (byte)0x8b, Deflater.DEFLATED, 0, 0, 0, 0, 0, 0, 0 };
+    
+    private enum GZState { NOT_COMPRESSING, MIGHT_COMPRESS, COMMITTING, COMPRESSING, FINISHED};
+    private final AtomicReference<GZState> _state = new AtomicReference<>(GZState.NOT_COMPRESSING);
+    private final CRC32 _crc = new CRC32();
+    
+    private Deflater _deflater;
+    private GzipFactory _factory;
+    private ByteBuffer _buffer;
+    
+    public GzipHttpOutput(HttpChannel<?> channel)
+    {
+        super(channel);
+    }
+
+    @Override
+    public void reset()
+    {
+        _state.set(GZState.NOT_COMPRESSING);
+        super.reset();
+    }
+
+    @Override
+    protected void write(ByteBuffer content, boolean complete, Callback callback)
+    {
+        switch (_state.get())
+        {
+            case NOT_COMPRESSING:
+                super.write(content,complete,callback);
+                return;
+
+            case MIGHT_COMPRESS:
+                commit(content,complete,callback);
+                break;
+                
+            case COMMITTING:
+                callback.failed(new WritePendingException());
+                break;
+
+            case COMPRESSING:
+                gzip(content,complete,callback);
+                break;
+
+            default:
+                callback.failed(new IllegalStateException("state="+_state.get()));
+                break;
+        }
+    }
+
+    private void superWrite(ByteBuffer content, boolean complete, Callback callback)
+    {
+        super.write(content,complete,callback);
+    }
+    
+    private void addTrailer()
+    {
+        int i=_buffer.limit();
+        _buffer.limit(i+8);
+        
+        int v=(int)_crc.getValue();
+        _buffer.put(i++,(byte)(v & 0xFF));
+        _buffer.put(i++,(byte)((v>>>8) & 0xFF));
+        _buffer.put(i++,(byte)((v>>>16) & 0xFF));
+        _buffer.put(i++,(byte)((v>>>24) & 0xFF));
+        
+        v=_deflater.getTotalIn();
+        _buffer.put(i++,(byte)(v & 0xFF));
+        _buffer.put(i++,(byte)((v>>>8) & 0xFF));
+        _buffer.put(i++,(byte)((v>>>16) & 0xFF));
+        _buffer.put(i++,(byte)((v>>>24) & 0xFF));
+    }
+    
+    
+    private void gzip(ByteBuffer content, boolean complete, final Callback callback)
+    {
+        if (content.hasRemaining() || complete)
+        {
+            if (content.hasArray())
+                new GzipArrayCB(content,complete,callback).iterate();
+            else
+                new GzipBufferCB(content,complete,callback).iterate();
+        }
+        else
+            callback.succeeded();
+    }
+
+    protected void commit(ByteBuffer content, boolean complete, Callback callback)
+    {
+        // Are we excluding because of status?
+        Response response=getHttpChannel().getResponse();
+        int sc = response.getStatus();
+        if (sc>0 && (sc<200 || sc==204 || sc==205 || sc>=300))
+        {
+            LOG.debug("{} exclude by status {}",this,sc);
+            noCompression();
+            super.write(content,complete,callback);
+            return;
+        }
+        
+        // Are we excluding because of mime-type?
+        String ct = getHttpChannel().getResponse().getContentType();
+        if (ct!=null)
+        {
+            ct=MimeTypes.getContentTypeWithoutCharset(ct);
+            if (_factory.isExcludedMimeType(StringUtil.asciiToLowerCase(ct)))
+            {
+                LOG.debug("{} exclude by mimeType {}",this,ct);
+                noCompression();
+                super.write(content,complete,callback);
+                return;
+            }
+        }
+        
+        // Has the Content-Encoding header already been set?
+        String ce=getHttpChannel().getResponse().getHeader("Content-Encoding");
+        if (ce != null)
+        {
+            LOG.debug("{} exclude by content-encoding {}",this,ce);
+            noCompression();
+            super.write(content,complete,callback);
+            return;
+        }
+        
+        // Are we the thread that commits?
+        if (_state.compareAndSet(GZState.MIGHT_COMPRESS,GZState.COMMITTING))
+        {
+            // We are varying the response due to accept encoding header.
+            HttpFields fields = response.getHttpFields();
+            fields.add(_factory.getVaryField());
+
+            long content_length = response.getContentLength();
+            if (content_length<0 && complete)
+                content_length=content.remaining();
+            
+            _deflater = _factory.getDeflater(getHttpChannel().getRequest(),content_length);
+            
+            if (_deflater==null)
+            {
+                LOG.debug("{} exclude no deflater",this);
+                _state.set(GZState.NOT_COMPRESSING);
+                super.write(content,complete,callback);
+                return;
+            }
+
+            fields.put(CONTENT_ENCODING_GZIP);
+            _crc.reset();
+            _buffer=getHttpChannel().getByteBufferPool().acquire(_factory.getBufferSize(),false);
+            BufferUtil.fill(_buffer,GZIP_HEADER,0,GZIP_HEADER.length);
+
+            // Adjust headers
+            response.setContentLength(-1);
+            String etag=fields.get(HttpHeader.ETAG);
+            if (etag!=null)
+                fields.put(HttpHeader.ETAG,etag.substring(0,etag.length()-1)+AsyncGzipFilter.ETAG_GZIP+ '"');
+
+            LOG.debug("{} compressing {}",this,_deflater);
+            _state.set(GZState.COMPRESSING);
+            
+            gzip(content,complete,callback);
+        }
+        else
+            callback.failed(new WritePendingException());
+    }
+
+    public void noCompression()
+    {
+        while (true)
+        {
+            switch (_state.get())
+            {
+                case NOT_COMPRESSING:
+                    return;
+
+                case MIGHT_COMPRESS:
+                    if (_state.compareAndSet(GZState.MIGHT_COMPRESS,GZState.NOT_COMPRESSING))
+                        return;
+                    break;
+
+                default:
+                    throw new IllegalStateException(_state.get().toString());
+            }
+        }
+    }
+
+    public void noCompressionIfPossible()
+    {
+        while (true)
+        {
+            switch (_state.get())
+            {
+                case COMPRESSING:
+                case NOT_COMPRESSING:
+                    return;
+
+                case MIGHT_COMPRESS:
+                    if (_state.compareAndSet(GZState.MIGHT_COMPRESS,GZState.NOT_COMPRESSING))
+                        return;
+                    break;
+
+                default:
+                    throw new IllegalStateException(_state.get().toString());
+            }
+        }
+    }
+
+    public boolean mightCompress()
+    {
+        return _state.get()==GZState.MIGHT_COMPRESS;
+    }
+
+    public void mightCompress(GzipFactory factory)
+    {
+        while (true)
+        {
+            switch (_state.get())
+            {
+                case NOT_COMPRESSING:
+                    _factory=factory;
+                    if (_state.compareAndSet(GZState.NOT_COMPRESSING,GZState.MIGHT_COMPRESS))
+                    {
+                        LOG.debug("{} might compress",this);
+                        return;
+                    }
+                    _factory=null;
+                    break;
+                    
+                default:
+                    throw new IllegalStateException(_state.get().toString());
+            }
+        }
+    }
+    
+    private class GzipArrayCB extends IteratingNestedCallback
+    {        
+        private final boolean _complete;
+        public GzipArrayCB(ByteBuffer content, boolean complete, Callback callback)
+        {
+            super(callback);
+            _complete=complete;
+
+             byte[] array=content.array();
+             int off=content.arrayOffset()+content.position();
+             int len=content.remaining();
+             _crc.update(array,off,len);
+             _deflater.setInput(array,off,len);
+             if (complete)
+                 _deflater.finish();
+             content.position(content.limit());
+        }
+
+        @Override
+        protected Action process() throws Exception
+        {
+            if (_deflater.needsInput())
+            {
+                if (_deflater.finished())
+                {
+                    _factory.recycle(_deflater);
+                    _deflater=null;
+                    getHttpChannel().getByteBufferPool().release(_buffer);
+                    _buffer=null;
+                    return Action.SUCCEEDED;
+                }
+
+                if (!_complete)
+                    return Action.SUCCEEDED;
+                
+                _deflater.finish();
+            }
+
+            BufferUtil.compact(_buffer);
+            int off=_buffer.arrayOffset()+_buffer.limit();
+            int len=_buffer.capacity()-_buffer.limit()- (_complete?8:0);
+            if (len>0)
+            {
+                int produced=_deflater.deflate(_buffer.array(),off,len,Deflater.NO_FLUSH);
+                _buffer.limit(_buffer.limit()+produced);
+            }
+            boolean complete=_deflater.finished();
+            if (complete)
+                addTrailer();
+            
+            superWrite(_buffer,complete,this);
+            return Action.SCHEDULED;
+        }
+    }
+    
+    private class GzipBufferCB extends IteratingNestedCallback
+    {        
+        private final ByteBuffer _input;
+        private final ByteBuffer _content;
+        private final boolean _last;
+        public GzipBufferCB(ByteBuffer content, boolean complete, Callback callback)
+        {
+            super(callback);
+            _input=getHttpChannel().getByteBufferPool().acquire(Math.min(_factory.getBufferSize(),content.remaining()),false);
+            _content=content;
+            _last=complete;
+        }
+
+        @Override
+        protected Action process() throws Exception
+        {
+            if (_deflater.needsInput())
+            {                
+                if (BufferUtil.isEmpty(_content))
+                {                    
+                    if (_deflater.finished())
+                    {
+                        _factory.recycle(_deflater);
+                        _deflater=null;
+                        getHttpChannel().getByteBufferPool().release(_buffer);
+                        _buffer=null;
+                        return Action.SUCCEEDED;
+                    }
+                    
+                    if (!_last)
+                    {
+                        return Action.SUCCEEDED;
+                    }
+                    
+                    _deflater.finish();
+                }
+                else
+                {
+                    BufferUtil.clearToFill(_input);
+                    int took=BufferUtil.put(_content,_input);
+                    BufferUtil.flipToFlush(_input,0);
+                    if (took==0)
+                        throw new IllegalStateException();
+                   
+                    byte[] array=_input.array();
+                    int off=_input.arrayOffset()+_input.position();
+                    int len=_input.remaining();
+
+                    _crc.update(array,off,len);
+                    _deflater.setInput(array,off,len);                
+                    if (_last && BufferUtil.isEmpty(_content))
+                        _deflater.finish();
+                }
+            }
+
+            BufferUtil.compact(_buffer);
+            int off=_buffer.arrayOffset()+_buffer.limit();
+            int len=_buffer.capacity()-_buffer.limit() - (_last?8:0);
+            if (len>0)
+            {
+                int produced=_deflater.deflate(_buffer.array(),off,len,Deflater.NO_FLUSH);
+                _buffer.limit(_buffer.limit()+produced);
+            }
+            boolean finished=_deflater.finished();
+            
+            if (finished)
+                addTrailer();
+                
+            superWrite(_buffer,finished,this);
+            return Action.SCHEDULED;
+        }
+    }
+}
diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/GzipOutputStream.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/GzipOutputStream.java
new file mode 100644
index 0000000..cb645a5
--- /dev/null
+++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/GzipOutputStream.java
@@ -0,0 +1,72 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlets.gzip;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.zip.CRC32;
+import java.util.zip.Deflater;
+
+/**
+ * Reimplementation of {@link java.util.zip.GZIPOutputStream} that supports reusing a {@link Deflater} instance.
+ */
+public class GzipOutputStream extends DeflatedOutputStream
+{
+
+    private final static byte[] GZIP_HEADER = new byte[]
+    { (byte)0x1f, (byte)0x8b, Deflater.DEFLATED, 0, 0, 0, 0, 0, 0, 0 };
+
+    private final CRC32 _crc = new CRC32();
+
+    public GzipOutputStream(OutputStream out, Deflater deflater, byte[] buffer) throws IOException
+    {
+        super(out,deflater,buffer);
+        out.write(GZIP_HEADER);
+    }
+
+    @Override
+    public synchronized void write(byte[] buf, int off, int len) throws IOException
+    {
+        super.write(buf,off,len);
+        _crc.update(buf,off,len);
+    }
+
+    @Override
+    public synchronized void finish() throws IOException
+    {
+        if (!_def.finished())
+        {
+            super.finish();
+            byte[] trailer = new byte[8];
+            writeInt((int)_crc.getValue(),trailer,0);
+            writeInt(_def.getTotalIn(),trailer,4);
+            out.write(trailer);
+        }
+    }
+
+    private void writeInt(int i, byte[] buf, int offset)
+    {
+        int o = offset;
+        buf[o++] = (byte)(i & 0xFF);
+        buf[o++] = (byte)((i >>> 8) & 0xFF);
+        buf[o++] = (byte)((i >>> 16) & 0xFF);
+        buf[o++] = (byte)((i >>> 24) & 0xFF);
+    }
+
+}
diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/package-info.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/package-info.java
new file mode 100644
index 0000000..59c969a
--- /dev/null
+++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Servlets : GZIP Filter Classes
+ */
+package org.eclipse.jetty.servlets.gzip;
+
diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/package-info.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/package-info.java
new file mode 100644
index 0000000..8824cd0
--- /dev/null
+++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Servlets : Generally Useful Servlets, Handlers and Filters
+ */
+package org.eclipse.jetty.servlets;
+
diff --git a/jetty-servlets/src/main/resources/org/eclipse/jetty/servlets/jmx/DoSFilter-mbean.properties b/jetty-servlets/src/main/resources/org/eclipse/jetty/servlets/jmx/DoSFilter-mbean.properties
deleted file mode 100644
index 9523d23..0000000
--- a/jetty-servlets/src/main/resources/org/eclipse/jetty/servlets/jmx/DoSFilter-mbean.properties
+++ /dev/null
@@ -1,18 +0,0 @@
-DoSFilter: Limit exposure to abuse from request flooding, whether malicious, or as a result of a misconfigured client.
-maxRequestsPerSec: maximum number of requests from a connection per second. Requests in excess of this are first delayed, then throttled.
-delayMs: delay (in milliseconds) that is applied to all requests over the rate limit, before they are considered at all, 0 - no delay, -1 - reject request.
-maxWaitMs: maximum amount of time (in milliseconds) the filter will blocking wait for the throttle semaphore.
-throttledRequests: number of requests over the rate limit able to be considered at once.
-throttleMs: amount of time (in milliseconds) to async wait for semaphore.
-maxRequestMs: maximum amount of time (in milliseconds) to allow the request to process.
-maxIdleTrackerMs: maximum amount of time (in milliseconds) to keep track of request rates for a connection, before deciding that the user has gone away, and discarding it.
-insertHeaders: insert the DoSFilter headers into the response.
-trackSessions: usage rate is tracked by session if a session exists.
-remotePort: usage rate is tracked by IP+port (effectively connection) if session tracking is not used.
-enabled: whether this filter is enabled
-whitelist: comma separated list of IP addresses that will not be rate limited.
-clearWhitelist(): clears the list of IP addresses that will not be rate limited.
-addWhitelistAddress(java.lang.String):ACTION: adds an IP address that will not be rate limited.
-addWhitelistAddress(java.lang.String)[0]:address: the IP address that will not be rate limited.
-removeWhitelistAddress(java.lang.String):ACTION: removes an IP address that will not be rate limited.
-removeWhitelistAddress(java.lang.String)[0]:address: the IP address that will not be rate limited.
diff --git a/jetty-servlets/src/main/resources/org/eclipse/jetty/servlets/jmx/QoSFilter-mbean.properties b/jetty-servlets/src/main/resources/org/eclipse/jetty/servlets/jmx/QoSFilter-mbean.properties
deleted file mode 100644
index c781d63..0000000
--- a/jetty-servlets/src/main/resources/org/eclipse/jetty/servlets/jmx/QoSFilter-mbean.properties
+++ /dev/null
@@ -1,4 +0,0 @@
-QoSFilter: Quality of Service Filter.
-maxRequests: maximum number of requests allowed to be processedat the same time.
-waitMs: (short) amount of time (in milliseconds) that the filter would wait for the semaphore to become available before suspending a request.
-suspendMs: amount of time (in milliseconds) that the filter would suspend a request for while waiting for the semaphore to become available.
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/AbstractBalancerServletTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/AbstractBalancerServletTest.java
deleted file mode 100644
index e99d7fe..0000000
--- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/AbstractBalancerServletTest.java
+++ /dev/null
@@ -1,162 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.servlets;
-
-import java.io.IOException;
-
-import javax.servlet.http.HttpServlet;
-
-import org.eclipse.jetty.client.ContentExchange;
-import org.eclipse.jetty.client.HttpClient;
-import org.eclipse.jetty.http.HttpCookie;
-import org.eclipse.jetty.http.HttpMethods;
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
-import org.eclipse.jetty.server.session.HashSessionIdManager;
-import org.eclipse.jetty.servlet.ServletContextHandler;
-import org.eclipse.jetty.servlet.ServletHolder;
-import org.junit.After;
-import org.junit.Before;
-
-
-public abstract class AbstractBalancerServletTest
-{
-
-    private boolean _stickySessions;
-
-    private Server _node1;
-
-    private Server _node2;
-
-    private Server _balancerServer;
-
-    private HttpClient _httpClient;
-
-    @Before
-    public void setUp() throws Exception
-    {
-        _httpClient = new HttpClient();
-        _httpClient.registerListener("org.eclipse.jetty.client.RedirectListener");
-        _httpClient.start();
-    }
-
-    @After
-    public void tearDown() throws Exception
-    {
-        stopServer(_node1);
-        stopServer(_node2);
-        stopServer(_balancerServer);
-        _httpClient.stop();
-    }
-
-    private void stopServer(Server server)
-    {
-        try
-        {
-            server.stop();
-        }
-        catch (Exception e)
-        {
-            // Do nothing
-        }
-    }
-
-    protected void setStickySessions(boolean stickySessions)
-    {
-        _stickySessions = stickySessions;
-    }
-
-    protected void startBalancer(Class<? extends HttpServlet> httpServletClass) throws Exception
-    {
-        _node1 = createServer(new ServletHolder(httpServletClass.newInstance()),"/pipo","/molo/*");
-        setSessionIdManager(_node1,"node1");
-        _node1.start();
-
-        _node2 = createServer(new ServletHolder(httpServletClass.newInstance()),"/pipo","/molo/*");
-        setSessionIdManager(_node2,"node2");
-        _node2.start();
-
-        BalancerServlet balancerServlet = new BalancerServlet();
-        ServletHolder balancerServletHolder = new ServletHolder(balancerServlet);
-        balancerServletHolder.setInitParameter("StickySessions",String.valueOf(_stickySessions));
-        balancerServletHolder.setInitParameter("ProxyPassReverse","true");
-        balancerServletHolder.setInitParameter("BalancerMember." + "node1" + ".ProxyTo","http://localhost:" + getServerPort(_node1));
-        balancerServletHolder.setInitParameter("BalancerMember." + "node2" + ".ProxyTo","http://localhost:" + getServerPort(_node2));
-
-        _balancerServer = createServer(balancerServletHolder,"/pipo","/molo/*");
-        _balancerServer.start();
-    }
-
-    private Server createServer(ServletHolder servletHolder, String appContext, String servletUrlPattern)
-    {
-        Server server = new Server();
-        SelectChannelConnector httpConnector = new SelectChannelConnector();
-        server.addConnector(httpConnector);
-
-        ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
-        context.setContextPath(appContext);
-        server.setHandler(context);
-
-        context.addServlet(servletHolder,servletUrlPattern);
-
-        return server;
-    }
-
-    private void setSessionIdManager(Server node, String nodeName)
-    {
-        HashSessionIdManager sessionIdManager = new HashSessionIdManager();
-        sessionIdManager.setWorkerName(nodeName);
-        node.setSessionIdManager(sessionIdManager);
-    }
-
-    private int getServerPort(Server node)
-    {
-        return node.getConnectors()[0].getLocalPort();
-    }
-
-    protected byte[] sendRequestToBalancer(String requestUri) throws IOException, InterruptedException
-    {
-        ContentExchange exchange = new ContentExchange()
-        {
-            @Override
-            protected void onResponseHeader(Buffer name, Buffer value) throws IOException
-            {
-                // Cookie persistence
-                if (name.toString().equals("Set-Cookie"))
-                {
-                    String cookieVal = value.toString();
-                    if (cookieVal.startsWith("JSESSIONID="))
-                    {
-                        String jsessionid = cookieVal.split(";")[0].substring("JSESSIONID=".length());
-                        _httpClient.getDestination(getAddress(),false).addCookie(new HttpCookie("JSESSIONID",jsessionid));
-                    }
-                }
-            }
-        };
-        exchange.setURL("http://localhost:" + getServerPort(_balancerServer) + "/pipo/molo/" + requestUri);
-        exchange.setMethod(HttpMethods.GET);
-
-        _httpClient.send(exchange);
-        exchange.waitForDone();
-
-        return exchange.getResponseContentBytes();
-    }
-
-}
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/AbstractDoSFilterTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/AbstractDoSFilterTest.java
index 82b3b2e..421970f 100644
--- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/AbstractDoSFilterTest.java
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/AbstractDoSFilterTest.java
@@ -18,12 +18,18 @@
 
 package org.eclipse.jetty.servlets;
 
+import static org.hamcrest.Matchers.greaterThan;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
 
 import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
 import java.net.Socket;
+import java.nio.charset.StandardCharsets;
 import java.util.EnumSet;
+
 import javax.servlet.DispatcherType;
 import javax.servlet.Filter;
 import javax.servlet.Servlet;
@@ -34,9 +40,9 @@ import javax.servlet.http.HttpServletResponse;
 
 import org.eclipse.jetty.http.HttpURI;
 import org.eclipse.jetty.servlet.FilterHolder;
-import org.eclipse.jetty.testing.ServletTester;
+import org.eclipse.jetty.servlet.ServletTester;
 import org.eclipse.jetty.util.IO;
-import org.eclipse.jetty.util.log.Log;
+import org.hamcrest.Matchers;
 import org.junit.After;
 import org.junit.AfterClass;
 import org.junit.Before;
@@ -56,15 +62,14 @@ public abstract class AbstractDoSFilterTest
 
     public static void startServer(Class<? extends Filter> filter) throws Exception
     {
-        _tester = new ServletTester();
-        HttpURI uri = new HttpURI(_tester.createChannelConnector(true));
+        _tester = new ServletTester("/ctx");
+        HttpURI uri = new HttpURI(_tester.createConnector(true));
         _host = uri.getHost();
         _port = uri.getPort();
 
-        _tester.setContextPath("/ctx");
-        _tester.addServlet(TestServlet.class, "/*");
+        _tester.getContext().addServlet(TestServlet.class, "/*");
 
-        _dosFilter = _tester.addFilter(filter, "/dos/*", EnumSet.allOf(DispatcherType.class));
+        _dosFilter = _tester.getContext().addFilter(filter, "/dos/*", EnumSet.of(DispatcherType.REQUEST,DispatcherType.ASYNC));
         _dosFilter.setInitParameter("maxRequestsPerSec", "4");
         _dosFilter.setInitParameter("delayMs", "200");
         _dosFilter.setInitParameter("throttledRequests", "1");
@@ -73,7 +78,7 @@ public abstract class AbstractDoSFilterTest
         _dosFilter.setInitParameter("remotePort", "false");
         _dosFilter.setInitParameter("insertHeaders", "true");
 
-        _timeoutFilter = _tester.addFilter(filter, "/timeout/*", EnumSet.allOf(DispatcherType.class));
+        _timeoutFilter = _tester.getContext().addFilter(filter, "/timeout/*", EnumSet.of(DispatcherType.REQUEST,DispatcherType.ASYNC));
         _timeoutFilter.setInitParameter("maxRequestsPerSec", "4");
         _timeoutFilter.setInitParameter("delayMs", "200");
         _timeoutFilter.setInitParameter("throttledRequests", "1");
@@ -96,7 +101,9 @@ public abstract class AbstractDoSFilterTest
     public void startFilters() throws Exception
     {
         _dosFilter.start();
+        _dosFilter.initialize();
         _timeoutFilter.start();
+        _timeoutFilter.initialize();
     }
 
     @After
@@ -106,37 +113,39 @@ public abstract class AbstractDoSFilterTest
         _dosFilter.stop();
     }
 
-    private String doRequests(String requests, int loops, long pause0, long pause1, String request) throws Exception
+    private String doRequests(String loopRequests, int loops, long pauseBetweenLoops, long pauseBeforeLast, String lastRequest) throws Exception
     {
-        Socket socket = new Socket(_host, _port);
-        socket.setSoTimeout(30000);
-
-        for (int i=loops;i-->0;)
+        try (Socket socket = new Socket(_host,_port))
         {
-            socket.getOutputStream().write(requests.getBytes("UTF-8"));
-            socket.getOutputStream().flush();
-            if (i>0 && pause0>0)
-                Thread.sleep(pause0);
-        }
-        if (pause1>0)
-            Thread.sleep(pause1);
-        socket.getOutputStream().write(request.getBytes("UTF-8"));
-        socket.getOutputStream().flush();
+            socket.setSoTimeout(30000);
 
+            OutputStream out = socket.getOutputStream();
 
-        String response;
-        if (requests.contains("/unresponsive"))
-        {
-            // don't read in anything, forcing the request to time out
-            Thread.sleep(_requestMaxTime * 2);
-            response = IO.toString(socket.getInputStream(),"UTF-8");
-        }
-        else
-        {
-            response = IO.toString(socket.getInputStream(),"UTF-8");
+            for (int i = loops; i-- > 0;)
+            {
+                out.write(loopRequests.getBytes(StandardCharsets.UTF_8));
+                out.flush();
+                if (i > 0 && pauseBetweenLoops > 0)
+                {
+                    Thread.sleep(pauseBetweenLoops);
+                }
+            }
+            if (pauseBeforeLast > 0)
+            {
+                Thread.sleep(pauseBeforeLast);
+            }
+            out.write(lastRequest.getBytes(StandardCharsets.UTF_8));
+            out.flush();
+
+            InputStream in = socket.getInputStream();
+            if (loopRequests.contains("/unresponsive"))
+            {
+                // don't read in anything, forcing the request to time out
+                Thread.sleep(_requestMaxTime * 2);
+            }
+            String response = IO.toString(in,StandardCharsets.UTF_8);
+            return response;
         }
-        socket.close();
-        return response;
     }
 
     private int count(String responses,String substring)
@@ -211,7 +220,7 @@ public abstract class AbstractDoSFilterTest
         String request="GET /ctx/dos/test HTTP/1.1\r\nHost: localhost\r\n\r\n";
         String last="GET /ctx/dos/test HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n";
         String responses = doRequests(request+request+request+request,1,0,0,last);
-        System.out.println("responses are " + responses);
+        // System.out.println("responses are " + responses);
         assertEquals("200 OK responses", 5,count(responses,"HTTP/1.1 200 OK"));
         assertEquals("delayed responses", 1,count(responses,"DoSFilter: delayed"));
         assertEquals("throttled responses", 1,count(responses,"DoSFilter: throttled"));
@@ -248,8 +257,8 @@ public abstract class AbstractDoSFilterTest
         String last="GET /ctx/dos/test HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n";
         String responses = doRequests(request+request+request+request,1,0,0,last);
 
-        System.err.println("RESPONSES: \n"+responses);
-        
+        // System.err.println("RESPONSES: \n"+responses);
+
         assertEquals(4,count(responses,"HTTP/1.1 200 OK"));
         assertEquals(1,count(responses,"HTTP/1.1 503"));
         assertEquals(1,count(responses,"DoSFilter: delayed"));
@@ -304,11 +313,12 @@ public abstract class AbstractDoSFilterTest
         assertEquals(0,count(responses,"DoSFilter: delayed"));
 
         // alternate between sessions
-        responses = doRequests(request1+request2+request1+request2+request1,2,350,550,last);
+        responses = doRequests(request1+request2+request1+request2+request1,2,250,250,last);
 
+        // System.err.println(responses);
         assertEquals(11,count(responses,"HTTP/1.1 200 OK"));
         int delayedRequests = count(responses,"DoSFilter: delayed");
-        assertTrue("delayedRequests: " + delayedRequests + " is not between 2 and 3",delayedRequests >= 2 && delayedRequests <= 3);
+        assertTrue("delayedRequests: " + delayedRequests + " is not between 2 and 5",delayedRequests >= 2 && delayedRequests <= 5);
     }
 
     @Test
@@ -321,7 +331,8 @@ public abstract class AbstractDoSFilterTest
         // was expired, and stopped before reaching the end of the requests
         int responseLines = count(responses, "Line:");
         assertTrue(responses.contains("DoSFilter: timeout"));
-        assertTrue(responseLines > 0 && responseLines < numRequests);
+        assertThat(responseLines,greaterThan(0));
+        assertThat(responseLines,Matchers.lessThan(numRequests));
     }
 
     public static class TestServlet extends HttpServlet implements Servlet
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/AsyncProxyServer.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/AsyncProxyServer.java
deleted file mode 100644
index 27656be..0000000
--- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/AsyncProxyServer.java
+++ /dev/null
@@ -1,53 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.servlets;
-
-import java.util.EnumSet;
-
-import javax.servlet.DispatcherType;
-
-import org.eclipse.jetty.server.Connector;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
-import org.eclipse.jetty.servlet.ServletHandler;
-import org.eclipse.jetty.servlet.ServletHolder;
-
-public class AsyncProxyServer
-{
-    public static void main(String[] args)
-        throws Exception
-    {
-        Server server = new Server();
-        Connector connector=new SelectChannelConnector();
-        connector.setPort(8888);
-        server.setConnectors(new Connector[]{connector});
-        
-        ServletHandler handler=new ServletHandler();
-        server.setHandler(handler);
-        
-        //FilterHolder gzip = handler.addFilterWithMapping("org.eclipse.jetty.servlet.GzipFilter","/*",EnumSet.of(DispatcherType.REQUEST,DispatcherType.ASYNC));
-        //gzip.setAsyncSupported(true);
-        //gzip.setInitParameter("minGzipSize","256");
-        ServletHolder proxy = handler.addServletWithMapping("org.eclipse.jetty.servlets.ProxyServlet","/");
-        proxy.setAsyncSupported(true);
-        
-        server.start();
-        server.join();
-    }
-}
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/BalancerServletTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/BalancerServletTest.java
deleted file mode 100644
index 2d614b2..0000000
--- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/BalancerServletTest.java
+++ /dev/null
@@ -1,135 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.servlets;
-
-import static org.junit.Assert.*;
-
-import java.io.BufferedReader;
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStreamReader;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.junit.Test;
-
-/**
- * 
- */
-public class BalancerServletTest extends AbstractBalancerServletTest
-{
-
-    @Test
-    public void testRoundRobinBalancer() throws Exception
-    {
-        setStickySessions(false);
-        startBalancer(CounterServlet.class);
-
-        for (int i = 0; i < 10; i++)
-        {
-            byte[] responseBytes = sendRequestToBalancer("/");
-            String returnedCounter = readFirstLine(responseBytes);
-            // RR : response should increment every other request
-            String expectedCounter = String.valueOf(i / 2);
-            assertEquals(expectedCounter,returnedCounter);
-        }
-    }
-
-    @Test
-    public void testStickySessionsBalancer() throws Exception
-    {
-        setStickySessions(true);
-        startBalancer(CounterServlet.class);
-
-        for (int i = 0; i < 10; i++)
-        {
-            byte[] responseBytes = sendRequestToBalancer("/");
-            String returnedCounter = readFirstLine(responseBytes);
-            // RR : response should increment on each request
-            String expectedCounter = String.valueOf(i);
-            assertEquals(expectedCounter,returnedCounter);
-        }
-    }
-
-    @Test
-    public void testProxyPassReverse() throws Exception
-    {
-        setStickySessions(false);
-        startBalancer(RelocationServlet.class);
-
-        byte[] responseBytes = sendRequestToBalancer("index.html");
-        String msg = readFirstLine(responseBytes);
-        assertEquals("success",msg);
-    }
-
-    private String readFirstLine(byte[] responseBytes) throws IOException
-    {
-        BufferedReader reader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(responseBytes)));
-        return reader.readLine();
-    }
-
-    @SuppressWarnings("serial")
-    public static final class CounterServlet extends HttpServlet
-    {
-
-        private int counter;
-
-        @Override
-        public void init() throws ServletException
-        {
-            counter = 0;
-        }
-
-        @Override
-        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
-        {
-            // Force session creation
-            req.getSession();
-            resp.setContentType("text/plain");
-            resp.getWriter().println(counter++);
-        }
-    }
-
-    @SuppressWarnings("serial")
-    public static final class RelocationServlet extends HttpServlet
-    {
-        @Override
-        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
-        {
-            if (req.getRequestURI().endsWith("/index.html"))
-            {
-                resp.sendRedirect("http://localhost:" + req.getLocalPort() + req.getContextPath() + req.getServletPath() + "/other.html?secret=pipo%20molo");
-                return;
-            }
-            resp.setContentType("text/plain");
-            if ("pipo molo".equals(req.getParameter("secret")))
-            {
-                resp.getWriter().println("success");
-            }
-            else
-            {
-                resp.getWriter().println("failure");
-            }
-        }
-    }
-
-}
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/CloseableDoSFilterTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/CloseableDoSFilterTest.java
index 39047cc..3fea1ce 100644
--- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/CloseableDoSFilterTest.java
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/CloseableDoSFilterTest.java
@@ -38,6 +38,7 @@ public class CloseableDoSFilterTest extends AbstractDoSFilterTest
 
     public static class CloseableDoSFilter2 extends CloseableDoSFilter
     {
+        @Override
         public void closeConnection(HttpServletRequest request, HttpServletResponse response, Thread thread)
         {
             try
@@ -52,4 +53,10 @@ public class CloseableDoSFilterTest extends AbstractDoSFilterTest
             }
         }
     }
+
+    public void testUnresponsiveClient() throws Exception
+    {
+        // TODO work out why this intermittently fails
+        LOG.warn("Ignored Closeable testUnresponsiveClient");
+    }
 }
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/ConcatServletTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/ConcatServletTest.java
new file mode 100644
index 0000000..ad58753
--- /dev/null
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/ConcatServletTest.java
@@ -0,0 +1,175 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlets;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.StringReader;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.LocalConnector;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.eclipse.jetty.webapp.WebAppContext;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class ConcatServletTest
+{
+    private Server server;
+    private LocalConnector connector;
+
+    @Before
+    public void prepareServer() throws Exception
+    {
+        server = new Server();
+        connector = new LocalConnector(server);
+        server.addConnector(connector);
+    }
+
+    @After
+    public void destroy() throws Exception
+    {
+        if (server != null)
+            server.stop();
+    }
+
+    @Test
+    public void testConcatenation() throws Exception
+    {
+        String contextPath = "";
+        ServletContextHandler context = new ServletContextHandler(server, contextPath);
+        server.setHandler(context);
+        String concatPath = "/concat";
+        context.addServlet(ConcatServlet.class, concatPath);
+        ServletHolder resourceServletHolder = new ServletHolder(new HttpServlet()
+        {
+            @Override
+            protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+            {
+                String includedURI = (String)request.getAttribute("javax.servlet.include.request_uri");
+                response.getOutputStream().println(includedURI);
+            }
+        });
+        context.addServlet(resourceServletHolder, "/resource/*");
+        server.start();
+
+        String resource1 = "/resource/one.js";
+        String resource2 = "/resource/two.js";
+        String uri = contextPath + concatPath + "?" + resource1 + "&" + resource2;
+        String request = "" +
+                "GET " + uri + " HTTP/1.1\r\n" +
+                "Host: localhost\r\n" +
+                "Connection: close\r\n" +
+                "\r\n";
+        String response = connector.getResponses(request);
+        try (BufferedReader reader = new BufferedReader(new StringReader(response)))
+        {
+            while (true)
+            {
+                String line = reader.readLine();
+                if (line == null)
+                    Assert.fail();
+                if (line.trim().isEmpty())
+                    break;
+            }
+            Assert.assertEquals(resource1, reader.readLine());
+            Assert.assertEquals(resource2, reader.readLine());
+            Assert.assertNull(reader.readLine());
+        }
+    }
+
+    @Test
+    public void testWEBINFResourceIsNotServed() throws Exception
+    {
+        File directoryFile = MavenTestingUtils.getTargetTestingDir();
+        Path directoryPath = directoryFile.toPath();
+        Path hiddenDirectory = directoryPath.resolve("WEB-INF");
+        Files.createDirectories(hiddenDirectory);
+        Path hiddenResource = hiddenDirectory.resolve("one.js");
+        try (OutputStream output = Files.newOutputStream(hiddenResource))
+        {
+            output.write("function() {}".getBytes(StandardCharsets.UTF_8));
+        }
+
+        String contextPath = "";
+        WebAppContext context = new WebAppContext(server, directoryPath.toString(), contextPath);
+        server.setHandler(context);
+        String concatPath = "/concat";
+        context.addServlet(ConcatServlet.class, concatPath);
+        server.start();
+
+        // Verify that I can get the file programmatically, as required by the spec.
+        Assert.assertNotNull(context.getServletContext().getResource("/WEB-INF/one.js"));
+
+        // Having a path segment and then ".." triggers a special case
+        // that the ConcatServlet must detect and avoid.
+        String uri = contextPath + concatPath + "?/trick/../WEB-INF/one.js";
+        String request = "" +
+                "GET " + uri + " HTTP/1.1\r\n" +
+                "Host: localhost\r\n" +
+                "Connection: close\r\n" +
+                "\r\n";
+        String response = connector.getResponses(request);
+        Assert.assertTrue(response.startsWith("HTTP/1.1 404 "));
+
+        // Make sure ConcatServlet behaves well if it's case insensitive.
+        uri = contextPath + concatPath + "?/trick/../web-inf/one.js";
+        request = "" +
+                "GET " + uri + " HTTP/1.1\r\n" +
+                "Host: localhost\r\n" +
+                "Connection: close\r\n" +
+                "\r\n";
+        response = connector.getResponses(request);
+        Assert.assertTrue(response.startsWith("HTTP/1.1 404 "));
+
+        // Make sure ConcatServlet behaves well if encoded.
+        uri = contextPath + concatPath + "?/trick/..%2FWEB-INF%2Fone.js";
+        request = "" +
+                "GET " + uri + " HTTP/1.1\r\n" +
+                "Host: localhost\r\n" +
+                "Connection: close\r\n" +
+                "\r\n";
+        response = connector.getResponses(request);
+        Assert.assertTrue(response.startsWith("HTTP/1.1 404 "));
+
+        // Make sure ConcatServlet cannot see file system files.
+        uri = contextPath + concatPath + "?/trick/../../" + directoryFile.getName();
+        request = "" +
+                "GET " + uri + " HTTP/1.1\r\n" +
+                "Host: localhost\r\n" +
+                "Connection: close\r\n" +
+                "\r\n";
+        response = connector.getResponses(request);
+        Assert.assertTrue(response.startsWith("HTTP/1.1 404 "));
+    }
+}
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/CrossOriginFilterTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/CrossOriginFilterTest.java
index 845173b..f607a98 100644
--- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/CrossOriginFilterTest.java
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/CrossOriginFilterTest.java
@@ -30,9 +30,8 @@ import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
 import org.eclipse.jetty.servlet.FilterHolder;
-import org.eclipse.jetty.servlet.FilterMapping;
 import org.eclipse.jetty.servlet.ServletHolder;
-import org.eclipse.jetty.testing.ServletTester;
+import org.eclipse.jetty.servlet.ServletTester;
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
@@ -67,6 +66,7 @@ public class CrossOriginFilterTest
         String request = "" +
                 "GET / HTTP/1.1\r\n" +
                 "Host: localhost\r\n" +
+                "Connection: close\r\n" +
                 "\r\n";
         String response = tester.getResponses(request);
         Assert.assertTrue(response.contains("HTTP/1.1 200"));
@@ -88,6 +88,7 @@ public class CrossOriginFilterTest
         String request = "" +
                 "GET / HTTP/1.1\r\n" +
                 "Host: localhost\r\n" +
+                "Connection: close\r\n" +
                 "Origin: " + otherOrigin + "\r\n" +
                 "\r\n";
         String response = tester.getResponses(request);
@@ -96,6 +97,30 @@ public class CrossOriginFilterTest
         Assert.assertFalse(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER));
         Assert.assertTrue(latch.await(1, TimeUnit.SECONDS));
     }
+    
+    @Test
+    public void testSimpleRequestWithWildcardOrigin() throws Exception
+    {
+        FilterHolder filterHolder = new FilterHolder(new CrossOriginFilter());
+        tester.getContext().addFilter(filterHolder, "/*", EnumSet.of(DispatcherType.REQUEST));
+
+        CountDownLatch latch = new CountDownLatch(1);
+        tester.getContext().addServlet(new ServletHolder(new ResourceServlet(latch)), "/*");
+        String origin = "http://foo.example.com";
+        
+        String request = "" +
+        "GET / HTTP/1.1\r\n" +
+        "Host: localhost\r\n" +
+        "Connection: close\r\n" +
+        "Origin: "+origin+"\r\n" +
+        "\r\n";
+        String response = tester.getResponses(request);
+        Assert.assertTrue(response.contains("HTTP/1.1 200"));
+        Assert.assertTrue(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_ORIGIN_HEADER));
+        Assert.assertTrue(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER));
+        Assert.assertTrue(!response.contains("Vary"));
+        Assert.assertTrue(latch.await(1, TimeUnit.SECONDS));
+    }
 
     @Test
     public void testSimpleRequestWithMatchingWildcardOrigin() throws Exception
@@ -111,12 +136,14 @@ public class CrossOriginFilterTest
         String request = "" +
         "GET / HTTP/1.1\r\n" +
         "Host: localhost\r\n" +
+        "Connection: close\r\n" +
         "Origin: " + origin + "\r\n" +
         "\r\n";
         String response = tester.getResponses(request);
         Assert.assertTrue(response.contains("HTTP/1.1 200"));
         Assert.assertTrue(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_ORIGIN_HEADER));
         Assert.assertTrue(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER));
+        Assert.assertTrue(response.contains("Vary"));
         Assert.assertTrue(latch.await(1, TimeUnit.SECONDS));
     }
 
@@ -134,12 +161,14 @@ public class CrossOriginFilterTest
         String request = "" +
         "GET / HTTP/1.1\r\n" +
         "Host: localhost\r\n" +
+        "Connection: close\r\n" +
         "Origin: " + origin + "\r\n" +
         "\r\n";
         String response = tester.getResponses(request);
         Assert.assertTrue(response.contains("HTTP/1.1 200"));
         Assert.assertTrue(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_ORIGIN_HEADER));
         Assert.assertTrue(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER));
+        Assert.assertTrue(response.contains("Vary"));
         Assert.assertTrue(latch.await(1, TimeUnit.SECONDS));
     }
 
@@ -157,12 +186,14 @@ public class CrossOriginFilterTest
         String request = "" +
                 "GET / HTTP/1.1\r\n" +
                 "Host: localhost\r\n" +
+                "Connection: close\r\n" +
                 "Origin: " + origin + "\r\n" +
                 "\r\n";
         String response = tester.getResponses(request);
         Assert.assertTrue(response.contains("HTTP/1.1 200"));
         Assert.assertTrue(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_ORIGIN_HEADER));
         Assert.assertTrue(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER));
+        Assert.assertTrue(response.contains("Vary"));
         Assert.assertTrue(latch.await(1, TimeUnit.SECONDS));
     }
 
@@ -181,6 +212,7 @@ public class CrossOriginFilterTest
         String request = "" +
                 "GET / HTTP/1.1\r\n" +
                 "Host: localhost\r\n" +
+                "Connection: close\r\n" +
                 // Use 2 spaces as separator to test that the implementation does not fail
                 "Origin: " + otherOrigin + " " + " " + origin + "\r\n" +
                 "\r\n";
@@ -188,6 +220,7 @@ public class CrossOriginFilterTest
         Assert.assertTrue(response.contains("HTTP/1.1 200"));
         Assert.assertTrue(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_ORIGIN_HEADER));
         Assert.assertTrue(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER));
+        Assert.assertTrue(response.contains("Vary"));
         Assert.assertTrue(latch.await(1, TimeUnit.SECONDS));
     }
 
@@ -204,6 +237,7 @@ public class CrossOriginFilterTest
         String request = "" +
                 "GET / HTTP/1.1\r\n" +
                 "Host: localhost\r\n" +
+                "Connection: close\r\n" +
                 "Origin: http://localhost\r\n" +
                 "\r\n";
         String response = tester.getResponses(request);
@@ -229,6 +263,7 @@ public class CrossOriginFilterTest
         String request = "" +
                 "PUT / HTTP/1.1\r\n" +
                 "Host: localhost\r\n" +
+                "Connection: close\r\n" +
                 "Origin: http://localhost\r\n" +
                 "\r\n";
         String response = tester.getResponses(request);
@@ -254,6 +289,7 @@ public class CrossOriginFilterTest
         String request = "" +
                 "OPTIONS / HTTP/1.1\r\n" +
                 "Host: localhost\r\n" +
+                "Connection: close\r\n" +
                 "Origin: http://localhost\r\n" +
                 "\r\n";
         String response = tester.getResponses(request);
@@ -263,6 +299,35 @@ public class CrossOriginFilterTest
         Assert.assertTrue(latch.await(1, TimeUnit.SECONDS));
     }
 
+
+    @Test
+    public void testPreflightWithWildcardCustomHeaders() throws Exception
+    {        
+        FilterHolder filterHolder = new FilterHolder(new CrossOriginFilter());
+        filterHolder.setInitParameter(CrossOriginFilter.ALLOWED_HEADERS_PARAM, "*");
+        tester.getContext().addFilter(filterHolder, "/*", EnumSet.of(DispatcherType.REQUEST));
+
+        CountDownLatch latch = new CountDownLatch(1);
+        tester.getContext().addServlet(new ServletHolder(new ResourceServlet(latch)), "/*");
+
+        String request = "" +
+                "OPTIONS / HTTP/1.1\r\n" +
+                "Host: localhost\r\n" +
+                "Connection: close\r\n" +
+                CrossOriginFilter.ACCESS_CONTROL_REQUEST_HEADERS_HEADER + ": X-Foo-Bar\r\n" +
+                CrossOriginFilter.ACCESS_CONTROL_REQUEST_METHOD_HEADER + ": GET\r\n"+
+                "Origin: http://localhost\r\n" +
+                "\r\n";
+        String response = tester.getResponses(request);
+        Assert.assertTrue(response.contains("HTTP/1.1 200"));
+        Assert.assertTrue(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_ORIGIN_HEADER));
+        Assert.assertTrue(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_HEADERS_HEADER));
+        Assert.assertTrue(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER));
+        Assert.assertTrue(latch.await(1, TimeUnit.SECONDS));
+    }
+
+    
+
     @Test
     public void testPUTRequestWithPreflight() throws Exception
     {
@@ -277,6 +342,7 @@ public class CrossOriginFilterTest
         String request = "" +
                 "OPTIONS / HTTP/1.1\r\n" +
                 "Host: localhost\r\n" +
+                "Connection: close\r\n" +
                 CrossOriginFilter.ACCESS_CONTROL_REQUEST_METHOD_HEADER + ": PUT\r\n" +
                 "Origin: http://localhost\r\n" +
                 "\r\n";
@@ -293,6 +359,7 @@ public class CrossOriginFilterTest
         request = "" +
                 "PUT / HTTP/1.1\r\n" +
                 "Host: localhost\r\n" +
+                "Connection: close\r\n" +
                 "Origin: http://localhost\r\n" +
                 "\r\n";
         response = tester.getResponses(request);
@@ -316,6 +383,7 @@ public class CrossOriginFilterTest
         String request = "" +
                 "OPTIONS / HTTP/1.1\r\n" +
                 "Host: localhost\r\n" +
+                "Connection: close\r\n" +
                 CrossOriginFilter.ACCESS_CONTROL_REQUEST_METHOD_HEADER + ": DELETE\r\n" +
                 CrossOriginFilter.ACCESS_CONTROL_REQUEST_HEADERS_HEADER + ": origin,x-custom,x-requested-with\r\n" +
                 "Origin: http://localhost\r\n" +
@@ -333,6 +401,7 @@ public class CrossOriginFilterTest
         request = "" +
                 "DELETE / HTTP/1.1\r\n" +
                 "Host: localhost\r\n" +
+                "Connection: close\r\n" +
                 "X-Custom: value\r\n" +
                 "X-Requested-With: local\r\n" +
                 "Origin: http://localhost\r\n" +
@@ -357,6 +426,7 @@ public class CrossOriginFilterTest
         String request = "" +
                 "OPTIONS / HTTP/1.1\r\n" +
                 "Host: localhost\r\n" +
+                "Connection: close\r\n" +
                 CrossOriginFilter.ACCESS_CONTROL_REQUEST_METHOD_HEADER + ": DELETE\r\n" +
                 CrossOriginFilter.ACCESS_CONTROL_REQUEST_HEADERS_HEADER + ": origin,x-custom,x-requested-with\r\n" +
                 "Origin: http://localhost\r\n" +
@@ -405,6 +475,7 @@ public class CrossOriginFilterTest
         String request = "" +
                 "GET / HTTP/1.1\r\n" +
                 "Host: localhost\r\n" +
+                "Connection: close\r\n" +
                 "Origin: http://localhost\r\n" +
                 "\r\n";
         String response = tester.getResponses(request);
@@ -428,6 +499,7 @@ public class CrossOriginFilterTest
         String request = "" +
                 "OPTIONS / HTTP/1.1\r\n" +
                 "Host: localhost\r\n" +
+                "Connection: close\r\n" +
                 CrossOriginFilter.ACCESS_CONTROL_REQUEST_METHOD_HEADER + ": PUT\r\n" +
                 "Origin: http://localhost\r\n" +
                 "\r\n";
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/DataRateLimitedServletTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/DataRateLimitedServletTest.java
new file mode 100644
index 0000000..052b879
--- /dev/null
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/DataRateLimitedServletTest.java
@@ -0,0 +1,109 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlets;
+
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.greaterThan;
+import static org.junit.Assert.assertThat;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.OutputStream;
+import java.util.Arrays;
+
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.LocalConnector;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jetty.toolchain.test.TestingDir;
+import org.eclipse.jetty.util.resource.Resource;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class DataRateLimitedServletTest
+{
+    public static final int BUFFER=8192;
+    public static final int PAUSE=10;
+    
+    @Rule
+    public TestingDir testdir = new TestingDir();
+
+    private Server server;
+    private LocalConnector connector;
+    private ServletContextHandler context;
+
+    @Before
+    public void init() throws Exception
+    {
+        server = new Server();
+
+        connector = new LocalConnector(server);
+        connector.getConnectionFactory(HttpConfiguration.ConnectionFactory.class).getHttpConfiguration().setSendServerVersion(false);
+
+        context = new ServletContextHandler();
+ 
+        context.setContextPath("/context");
+        context.setWelcomeFiles(new String[]{"index.html", "index.jsp", "index.htm"});
+        context.setBaseResource(Resource.newResource(testdir.getEmptyDir()));
+        
+        ServletHolder holder =context.addServlet(DataRateLimitedServlet.class,"/stream/*");
+        holder.setInitParameter("buffersize",""+BUFFER);
+        holder.setInitParameter("pause",""+PAUSE);
+        server.setHandler(context);
+        server.addConnector(connector);
+
+        server.start();
+    }
+
+    @After
+    public void destroy() throws Exception
+    {
+        server.stop();
+        server.join();
+    }
+
+    @Test
+    public void testStream() throws Exception
+    {
+        File content = testdir.getFile("content.txt");
+        try(OutputStream out = new FileOutputStream(content);)
+        {
+            byte[] b= new byte[1024];
+            
+            for (int i=1024;i-->0;)
+            {
+                Arrays.fill(b,(byte)('0'+(i%10)));
+                out.write(b);
+                out.write('\n');
+            }
+        }
+        
+        long start=System.currentTimeMillis();
+        String response = connector.getResponses("GET /context/stream/content.txt HTTP/1.0\r\n\r\n");
+        long duration=System.currentTimeMillis()-start;
+        
+        assertThat(response.length(),greaterThan(1024*1024));
+        assertThat(response,containsString("200 OK"));
+        assertThat(duration,greaterThan(PAUSE*1024L*1024/BUFFER));
+        
+    }
+}
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/DoSFilterJMXTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/DoSFilterJMXTest.java
index 2c2dcc8..121761e 100644
--- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/DoSFilterJMXTest.java
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/DoSFilterJMXTest.java
@@ -21,6 +21,7 @@ package org.eclipse.jetty.servlets;
 import java.lang.management.ManagementFactory;
 import java.util.EnumSet;
 import java.util.Set;
+
 import javax.management.Attribute;
 import javax.management.MBeanServer;
 import javax.management.ObjectName;
@@ -29,7 +30,7 @@ import javax.servlet.DispatcherType;
 import org.eclipse.jetty.jmx.MBeanContainer;
 import org.eclipse.jetty.server.Connector;
 import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
+import org.eclipse.jetty.server.ServerConnector;
 import org.eclipse.jetty.servlet.FilterHolder;
 import org.eclipse.jetty.servlet.ServletContextHandler;
 import org.junit.Assert;
@@ -41,8 +42,7 @@ public class DoSFilterJMXTest
     public void testDoSFilterJMX() throws Exception
     {
         Server server = new Server();
-        Connector connector = new SelectChannelConnector();
-        connector.setPort(0);
+        Connector connector = new ServerConnector(server);
         server.addConnector(connector);
 
         ServletContextHandler context = new ServletContextHandler(server, "/", ServletContextHandler.SESSIONS);
@@ -57,7 +57,6 @@ public class DoSFilterJMXTest
         MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer();
         MBeanContainer mbeanContainer = new MBeanContainer(mbeanServer);
         server.addBean(mbeanContainer);
-        server.getContainer().addEventListener(mbeanContainer);
 
         server.start();
 
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/DoSFilterTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/DoSFilterTest.java
index 1275533..35001a2 100644
--- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/DoSFilterTest.java
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/DoSFilterTest.java
@@ -48,7 +48,7 @@ public class DoSFilterTest extends AbstractDoSFilterTest
         @Override
         public void closeConnection(HttpServletRequest request, HttpServletResponse response, Thread thread)
         {
-            try 
+            try
             {
                 response.getWriter().append("DoSFilter: timeout");
                 super.closeConnection(request,response,thread);
@@ -61,7 +61,7 @@ public class DoSFilterTest extends AbstractDoSFilterTest
     }
 
     @Test
-    public void isRateExceededTest() throws InterruptedException
+    public void testRateIsRateExceeded() throws InterruptedException
     {
         DoSFilter doSFilter = new DoSFilter();
 
@@ -78,19 +78,20 @@ public class DoSFilterTest extends AbstractDoSFilterTest
     {
         DoSFilter filter = new DoSFilter();
         List<String> whitelist = new ArrayList<String>();
-        whitelist.add("192.168.0.1");
+        whitelist.add("192.168.0.1/32");
         whitelist.add("10.0.0.0/8");
         whitelist.add("4d8:0:a:1234:ABc:1F:b18:17");
         whitelist.add("4d8:0:a:1234:ABc:1F:0:0/96");
-        Assert.assertTrue(filter.checkWhitelist(whitelist, "192.168.0.1"));
-        Assert.assertFalse(filter.checkWhitelist(whitelist, "192.168.0.2"));
-        Assert.assertFalse(filter.checkWhitelist(whitelist, "11.12.13.14"));
-        Assert.assertTrue(filter.checkWhitelist(whitelist, "10.11.12.13"));
-        Assert.assertTrue(filter.checkWhitelist(whitelist, "10.0.0.0"));
-        Assert.assertFalse(filter.checkWhitelist(whitelist, "0.0.0.0"));
-        Assert.assertTrue(filter.checkWhitelist(whitelist, "4d8:0:a:1234:ABc:1F:b18:17"));
-        Assert.assertTrue(filter.checkWhitelist(whitelist, "4d8:0:a:1234:ABc:1F:b18:0"));
-        Assert.assertFalse(filter.checkWhitelist(whitelist, "4d8:0:a:1234:ABc:1D:0:0"));
+        filter.setWhitelist("192.168.0.1/32,10.0.0.0/8,4d8:0:a:1234:ABc:1F:b18:17,4d8:0:a:1234:ABc:1F:0:0/96");
+        Assert.assertTrue(filter.checkWhitelist("192.168.0.1"));
+        Assert.assertFalse(filter.checkWhitelist("192.168.0.2"));
+        Assert.assertFalse(filter.checkWhitelist("11.12.13.14"));
+        Assert.assertTrue(filter.checkWhitelist("10.11.12.13"));
+        Assert.assertTrue(filter.checkWhitelist("10.0.0.0"));
+        Assert.assertFalse(filter.checkWhitelist("0.0.0.0"));
+        Assert.assertTrue(filter.checkWhitelist("4d8:0:a:1234:ABc:1F:b18:17"));
+        Assert.assertTrue(filter.checkWhitelist("4d8:0:a:1234:ABc:1F:b18:0"));
+        Assert.assertFalse(filter.checkWhitelist("4d8:0:a:1234:ABc:1D:0:0"));
     }
 
     private boolean hitRateTracker(DoSFilter doSFilter, int sleep) throws InterruptedException
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/EventSourceServletTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/EventSourceServletTest.java
new file mode 100644
index 0000000..2032c7c
--- /dev/null
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/EventSourceServletTest.java
@@ -0,0 +1,350 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlets;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.Socket;
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.eclipse.jetty.server.NetworkConnector;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class EventSourceServletTest
+{
+    private Server server;
+    private NetworkConnector connector;
+    private ServletContextHandler context;
+
+    @Before
+    public void startServer() throws Exception
+    {
+        server = new Server(0);
+        connector = (NetworkConnector)server.getConnectors()[0];
+
+        String contextPath = "/test";
+        context = new ServletContextHandler(server, contextPath, ServletContextHandler.SESSIONS);
+        server.start();
+    }
+
+    @After
+    public void stopServer() throws Exception
+    {
+        if (server != null)
+            server.stop();
+    }
+
+    @Test
+    public void testBasicFunctionality() throws Exception
+    {
+        final AtomicReference<EventSource.Emitter> emitterRef = new AtomicReference<EventSource.Emitter>();
+        final CountDownLatch emitterLatch = new CountDownLatch(1);
+        final CountDownLatch closeLatch = new CountDownLatch(1);
+        class S extends EventSourceServlet
+        {
+            @Override
+            protected EventSource newEventSource(HttpServletRequest request)
+            {
+                return new EventSource()
+                {
+                    public void onOpen(Emitter emitter) throws IOException
+                    {
+                        emitterRef.set(emitter);
+                        emitterLatch.countDown();
+                    }
+
+                    public void onClose()
+                    {
+                        closeLatch.countDown();
+                    }
+                };
+            }
+        }
+
+        String servletPath = "/eventsource";
+        ServletHolder servletHolder = new ServletHolder(new S());
+        int heartBeatPeriod = 2;
+        servletHolder.setInitParameter("heartBeatPeriod", String.valueOf(heartBeatPeriod));
+        context.addServlet(servletHolder, servletPath);
+
+        Socket socket = new Socket("localhost", connector.getLocalPort());
+        writeHTTPRequest(socket, servletPath);
+        BufferedReader reader = readAndDiscardHTTPResponse(socket);
+
+        Assert.assertTrue(emitterLatch.await(1, TimeUnit.SECONDS));
+        EventSource.Emitter emitter = emitterRef.get();
+        Assert.assertNotNull(emitter);
+
+        String data = "foo";
+        emitter.data(data);
+
+        String line = reader.readLine();
+        String received = "";
+        while (line != null)
+        {
+            received += line;
+            if (line.length() == 0)
+                break;
+            line = reader.readLine();
+        }
+
+        Assert.assertEquals("data: " + data, received);
+
+        socket.close();
+        Assert.assertTrue(closeLatch.await(heartBeatPeriod * 3, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testServerSideClose() throws Exception
+    {
+        final AtomicReference<EventSource.Emitter> emitterRef = new AtomicReference<EventSource.Emitter>();
+        final CountDownLatch emitterLatch = new CountDownLatch(1);
+        class S extends EventSourceServlet
+        {
+            @Override
+            protected EventSource newEventSource(HttpServletRequest request)
+            {
+                return new EventSource()
+                {
+                    public void onOpen(Emitter emitter) throws IOException
+                    {
+                        emitterRef.set(emitter);
+                        emitterLatch.countDown();
+                    }
+
+                    public void onClose()
+                    {
+                    }
+                };
+            }
+        }
+
+        String servletPath = "/eventsource";
+        context.addServlet(new ServletHolder(new S()), servletPath);
+
+        Socket socket = new Socket("localhost", connector.getLocalPort());
+        writeHTTPRequest(socket, servletPath);
+        BufferedReader reader = readAndDiscardHTTPResponse(socket);
+
+        Assert.assertTrue(emitterLatch.await(1, TimeUnit.SECONDS));
+        EventSource.Emitter emitter = emitterRef.get();
+        Assert.assertNotNull(emitter);
+
+        String comment = "foo";
+        emitter.comment(comment);
+
+        String line = reader.readLine();
+        String received = "";
+        while (line != null)
+        {
+            received += line;
+            if (line.length() == 0)
+                break;
+            line = reader.readLine();
+        }
+
+        Assert.assertEquals(": " + comment, received);
+
+        emitter.close();
+
+        line = reader.readLine();
+        Assert.assertNull(line);
+
+        socket.close();
+    }
+
+    @Test
+    public void testEncoding() throws Exception
+    {
+        // The EURO symbol
+        final String data = "\u20AC";
+        class S extends EventSourceServlet
+        {
+            @Override
+            protected EventSource newEventSource(HttpServletRequest request)
+            {
+                return new EventSource()
+                {
+                    public void onOpen(Emitter emitter) throws IOException
+                    {
+                        emitter.data(data);
+                    }
+
+                    public void onClose()
+                    {
+                    }
+                };
+            }
+        }
+
+        String servletPath = "/eventsource";
+        context.addServlet(new ServletHolder(new S()), servletPath);
+
+        Socket socket = new Socket("localhost", connector.getLocalPort());
+        writeHTTPRequest(socket, servletPath);
+        BufferedReader reader = readAndDiscardHTTPResponse(socket);
+
+        String line = reader.readLine();
+        String received = "";
+        while (line != null)
+        {
+            received += line;
+            if (line.length() == 0)
+                break;
+            line = reader.readLine();
+        }
+
+        Assert.assertEquals("data: " + data, received);
+
+        socket.close();
+    }
+
+    @Test
+    public void testMultiLineData() throws Exception
+    {
+        String data1 = "data1";
+        String data2 = "data2";
+        String data3 = "data3";
+        String data4 = "data4";
+        final String data = data1 + "\r\n" + data2 + "\r" + data3 + "\n" + data4;
+        class S extends EventSourceServlet
+        {
+            @Override
+            protected EventSource newEventSource(HttpServletRequest request)
+            {
+                return new EventSource()
+                {
+                    public void onOpen(Emitter emitter) throws IOException
+                    {
+                        emitter.data(data);
+                    }
+
+                    public void onClose()
+                    {
+                    }
+                };
+            }
+        }
+
+        String servletPath = "/eventsource";
+        context.addServlet(new ServletHolder(new S()), servletPath);
+
+        Socket socket = new Socket("localhost", connector.getLocalPort());
+        writeHTTPRequest(socket, servletPath);
+        BufferedReader reader = readAndDiscardHTTPResponse(socket);
+
+        String line1 = reader.readLine();
+        Assert.assertEquals("data: " + data1, line1);
+        String line2 = reader.readLine();
+        Assert.assertEquals("data: " + data2, line2);
+        String line3 = reader.readLine();
+        Assert.assertEquals("data: " + data3, line3);
+        String line4 = reader.readLine();
+        Assert.assertEquals("data: " + data4, line4);
+        String line5 = reader.readLine();
+        Assert.assertEquals(0, line5.length());
+
+        socket.close();
+    }
+
+    @Test
+    public void testEvents() throws Exception
+    {
+        final String name = "event1";
+        final String data = "data2";
+        class S extends EventSourceServlet
+        {
+            @Override
+            protected EventSource newEventSource(HttpServletRequest request)
+            {
+                return new EventSource()
+                {
+                    public void onOpen(Emitter emitter) throws IOException
+                    {
+                        emitter.event(name, data);
+                    }
+
+                    public void onClose()
+                    {
+                    }
+                };
+            }
+        }
+
+        String servletPath = "/eventsource";
+        context.addServlet(new ServletHolder(new S()), servletPath);
+
+        Socket socket = new Socket("localhost", connector.getLocalPort());
+        writeHTTPRequest(socket, servletPath);
+        BufferedReader reader = readAndDiscardHTTPResponse(socket);
+
+        String line1 = reader.readLine();
+        Assert.assertEquals("event: " + name, line1);
+        String line2 = reader.readLine();
+        Assert.assertEquals("data: " + data, line2);
+        String line3 = reader.readLine();
+        Assert.assertEquals(0, line3.length());
+
+        socket.close();
+    }
+
+    private void writeHTTPRequest(Socket socket, String servletPath) throws IOException
+    {
+        int serverPort = socket.getPort();
+        OutputStream output = socket.getOutputStream();
+
+        String handshake = "";
+        handshake += "GET " + context.getContextPath() + servletPath + " HTTP/1.1\r\n";
+        handshake += "Host: localhost:" + serverPort + "\r\n";
+        handshake += "Accept: text/event-stream\r\n";
+        handshake += "\r\n";
+        output.write(handshake.getBytes(StandardCharsets.UTF_8));
+        output.flush();
+    }
+
+    private BufferedReader readAndDiscardHTTPResponse(Socket socket) throws IOException
+    {
+        // Read and discard the HTTP response
+        InputStream input = socket.getInputStream();
+        BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
+        String line = reader.readLine();
+        while (line != null)
+        {
+            if (line.length() == 0)
+                break;
+            line = reader.readLine();
+        }
+        // Now we can parse the event-source stream
+        return reader;
+    }
+}
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipFilterContentLengthTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipFilterContentLengthTest.java
index 5e9498f..d546617 100644
--- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipFilterContentLengthTest.java
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipFilterContentLengthTest.java
@@ -18,16 +18,28 @@
 
 package org.eclipse.jetty.servlets;
 
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
 import java.io.File;
-import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.EnumSet;
 import java.util.List;
+import java.util.concurrent.TimeUnit;
 
-import javax.servlet.Servlet;
+import javax.servlet.DispatcherType;
 
 import org.eclipse.jetty.http.HttpStatus;
-import org.eclipse.jetty.http.gzip.CompressedResponseWrapper;
+import org.eclipse.jetty.http.HttpTester;
+import org.eclipse.jetty.server.HttpConfiguration;
 import org.eclipse.jetty.servlet.FilterHolder;
+import org.eclipse.jetty.servlets.gzip.AsyncScheduledDispatchWrite;
+import org.eclipse.jetty.servlets.gzip.AsyncTimeoutDispatchWrite;
+import org.eclipse.jetty.servlets.gzip.AsyncTimeoutCompleteWrite;
 import org.eclipse.jetty.servlets.gzip.GzipTester;
+import org.eclipse.jetty.servlets.gzip.GzipTester.ContentMetadata;
+import org.eclipse.jetty.servlets.gzip.TestDirContentServlet;
+import org.eclipse.jetty.servlets.gzip.TestServletBufferTypeLengthWrite;
 import org.eclipse.jetty.servlets.gzip.TestServletLengthStreamTypeWrite;
 import org.eclipse.jetty.servlets.gzip.TestServletLengthTypeStreamWrite;
 import org.eclipse.jetty.servlets.gzip.TestServletStreamLengthTypeWrite;
@@ -35,14 +47,15 @@ import org.eclipse.jetty.servlets.gzip.TestServletStreamLengthTypeWriteWithFlush
 import org.eclipse.jetty.servlets.gzip.TestServletStreamTypeLengthWrite;
 import org.eclipse.jetty.servlets.gzip.TestServletTypeLengthStreamWrite;
 import org.eclipse.jetty.servlets.gzip.TestServletTypeStreamLengthWrite;
-import org.eclipse.jetty.testing.HttpTester;
+import org.eclipse.jetty.toolchain.test.TestTracker;
 import org.eclipse.jetty.toolchain.test.TestingDir;
-import org.hamcrest.Matchers;
 import org.junit.Assert;
+import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
 import org.junit.runners.Parameterized.Parameters;
 
 /**
@@ -53,93 +66,96 @@ import org.junit.runners.Parameterized.Parameters;
 @RunWith(Parameterized.class)
 public class GzipFilterContentLengthTest
 {
-    /**
-     * These are the junit parameters for running this test.
-     * <p>
-     * In addition to Jetty's DefaultServlet we have multiple test
-     * servlets that arrange content-length/content-type/get stream
-     * in different order so as to simulate the real world scenario
-     * that caused the bug in Eclipse <a href="Bug 354014">http://bugs.eclipse.org/354014</a>
-     * <p>
-     * This test case will be run with each of the entries in
-     * the array below as setup parameters for the test case.
-     *
-     * @return the junit parameters
-     */
-    @Parameters
-    public static List<Object[]> data()
-    {
-        return Arrays.asList(new Object[][]
-        {
-        { TestServletLengthStreamTypeWrite.class, GzipFilter.GZIP },
-        { TestServletLengthTypeStreamWrite.class, GzipFilter.GZIP },
-        { TestServletStreamLengthTypeWrite.class, GzipFilter.GZIP },
-        { TestServletStreamLengthTypeWriteWithFlush.class, GzipFilter.GZIP },
-        { TestServletStreamTypeLengthWrite.class, GzipFilter.GZIP },
-        { TestServletTypeLengthStreamWrite.class, GzipFilter.GZIP },
-        { TestServletTypeStreamLengthWrite.class, GzipFilter.GZIP }, 
-        { TestServletLengthStreamTypeWrite.class, GzipFilter.DEFLATE },
-        { TestServletLengthTypeStreamWrite.class, GzipFilter.DEFLATE },
-        { TestServletStreamLengthTypeWrite.class, GzipFilter.DEFLATE },
-        { TestServletStreamLengthTypeWriteWithFlush.class, GzipFilter.DEFLATE },
-        { TestServletStreamTypeLengthWrite.class, GzipFilter.DEFLATE },
-        { TestServletTypeLengthStreamWrite.class, GzipFilter.DEFLATE },
-        { TestServletTypeStreamLengthWrite.class, GzipFilter.DEFLATE } 
-        });
-    }
-
-    private static final int LARGE = CompressedResponseWrapper.DEFAULT_BUFFER_SIZE * 8;
-    private static final int MEDIUM = CompressedResponseWrapper.DEFAULT_BUFFER_SIZE;
-    private static final int SMALL = CompressedResponseWrapper.DEFAULT_BUFFER_SIZE / 4;
-    private static final int TINY = CompressedResponseWrapper.DEFAULT_MIN_COMPRESS_SIZE/ 2;
-    
-    private String compressionType;
+    @Rule
+    public final TestTracker tracker = new TestTracker();
 
-    public GzipFilterContentLengthTest(Class<? extends Servlet> testServlet, String compressionType)
-    {
-        this.testServlet = testServlet;
-        this.compressionType = compressionType;
-    }
-    
     @Rule
     public TestingDir testingdir = new TestingDir();
+    
+    private static final HttpConfiguration defaultHttp = new HttpConfiguration();
+    private static final int LARGE = defaultHttp.getOutputBufferSize() * 8;
+    private static final int MEDIUM = defaultHttp.getOutputBufferSize();
+    private static final int SMALL = defaultHttp.getOutputBufferSize() / 4;
+    private static final int TINY = AsyncGzipFilter.DEFAULT_MIN_GZIP_SIZE / 2;
+    private static final boolean EXPECT_COMPRESSED = true;
 
-    private Class<? extends Servlet> testServlet;
-
-    private void assertIsGzipCompressed(String filename, int filesize) throws Exception
+    @Parameters(name = "{0} bytes - {1} - compressed({2}) - type({3}) - filter({4})")
+    public static List<Object[]> data()
     {
-        GzipTester tester = new GzipTester(testingdir, compressionType);
-
-        File testfile = tester.prepareServerFile(testServlet.getSimpleName() + "-" + filename,filesize);
-
-        FilterHolder holder = tester.setContentServlet(testServlet);
-        holder.setInitParameter("mimeTypes","text/plain");
-
-        try
-        {
-            tester.start();
-            tester.assertIsResponseGzipCompressed("GET",testfile.getName());
-        }
-        finally
+        List<Object[]> ret = new ArrayList<Object[]>();
+        
+        String compressionTypes[] = new String[] { GzipFilter.GZIP, GzipFilter.DEFLATE };
+        Class<?> gzipFilters[] = new Class<?>[] { GzipFilter.class, AsyncGzipFilter.class };
+        
+        for(String compressionType: compressionTypes)
         {
-            tester.stop();
+            for(Class<?> gzipFilter: gzipFilters)
+            {
+                ret.add(new Object[] { 0, "empty.txt", !EXPECT_COMPRESSED, compressionType, gzipFilter });
+                ret.add(new Object[] { TINY, "file-tiny.txt", !EXPECT_COMPRESSED, compressionType, gzipFilter });
+                ret.add(new Object[] { SMALL, "file-small.txt", EXPECT_COMPRESSED, compressionType, gzipFilter });
+                ret.add(new Object[] { SMALL, "file-small.mp3", !EXPECT_COMPRESSED, compressionType, gzipFilter });
+                ret.add(new Object[] { MEDIUM, "file-med.txt", EXPECT_COMPRESSED, compressionType, gzipFilter });
+                ret.add(new Object[] { MEDIUM, "file-medium.mp3", !EXPECT_COMPRESSED, compressionType, gzipFilter });
+                ret.add(new Object[] { LARGE, "file-large.txt", EXPECT_COMPRESSED, compressionType, gzipFilter });
+                ret.add(new Object[] { LARGE, "file-large.mp3", !EXPECT_COMPRESSED, compressionType, gzipFilter });
+            }
         }
+
+        return ret;
     }
 
-    private void assertIsNotGzipCompressed(String filename, int filesize) throws Exception
+    @Parameter(0)
+    public int fileSize;
+    @Parameter(1)
+    public String fileName;
+    @Parameter(2)
+    public boolean expectCompressed;
+    @Parameter(3)
+    public String compressionType;
+    @Parameter(4)
+    public Class<? extends GzipFilter> gzipFilterClass;
+    
+    private void testWithGzip(Class<? extends TestDirContentServlet> contentServlet) throws Exception
     {
-        GzipTester tester = new GzipTester(testingdir, compressionType);
-
-        File testfile = tester.prepareServerFile(testServlet.getSimpleName() + "-" + filename,filesize);
-
-        FilterHolder holder = tester.setContentServlet(testServlet);
-        holder.setInitParameter("mimeTypes","text/plain");
+        GzipTester tester = new GzipTester(testingdir, GzipFilter.GZIP);
+        
+        // Add AsyncGzip Filter
+        FilterHolder gzipHolder = new FilterHolder(gzipFilterClass);
+        gzipHolder.setAsyncSupported(true);
+        tester.addFilter(gzipHolder,"*.txt",EnumSet.of(DispatcherType.REQUEST,DispatcherType.ASYNC));
+        tester.addFilter(gzipHolder,"*.mp3",EnumSet.of(DispatcherType.REQUEST,DispatcherType.ASYNC));
+        gzipHolder.setInitParameter("mimeTypes","text/plain");
 
+        // Add content servlet
+        tester.setContentServlet(contentServlet);
+        
         try
         {
+            String testFilename = String.format("%s-%s-%s", gzipFilterClass.getSimpleName(), contentServlet.getSimpleName(), fileName);
+            File testFile = tester.prepareServerFile(testFilename,fileSize);
+            
             tester.start();
-            HttpTester response = tester.assertIsResponseNotGzipCompressed("GET",testfile.getName(),filesize,HttpStatus.OK_200);
-            Assert.assertThat(response.getHeader("ETAG"),Matchers.startsWith("W/etag-"));
+            
+            HttpTester.Response response = tester.executeRequest("GET","/context/" + testFile.getName(),5,TimeUnit.SECONDS);
+            
+            if (response.getStatus()!=200)
+                System.err.println("DANG!!!! "+response);
+            
+            assertThat("Response status", response.getStatus(), is(HttpStatus.OK_200));
+            
+            if (expectCompressed)
+            {
+                // Must be gzip compressed
+                assertThat("Content-Encoding",response.get("Content-Encoding"),containsString(GzipFilter.GZIP));
+            } else
+            {
+                assertThat("Content-Encoding",response.get("Content-Encoding"),not(containsString(GzipFilter.GZIP)));
+            }
+            
+            // Uncompressed content Size
+            ContentMetadata content = tester.getResponseMetadata(response);
+            assertThat("(Uncompressed) Content Length", content.size, is((long)fileSize));
         }
         finally
         {
@@ -148,86 +164,189 @@ public class GzipFilterContentLengthTest
     }
 
     /**
-     * Tests gzip compression of a small size file
+     * Test with content servlet that does:  
+     * AsyncContext create -> timeout -> onTimeout -> write-response -> complete
+     */
+    @Test
+    public void testAsyncTimeoutCompleteWrite_Default() throws Exception
+    {
+        if (expectCompressed && gzipFilterClass==GzipFilter.class)
+            return; // Default startAsync will never work with GzipFilter, which needs wrapping
+        testWithGzip(AsyncTimeoutCompleteWrite.Default.class);
+    }
+    
+    /**
+     * Test with content servlet that does:  
+     * AsyncContext create -> timeout -> onTimeout -> write-response -> complete
+     */
+    @Test
+    public void testAsyncTimeoutCompleteWrite_Passed() throws Exception
+    {
+        testWithGzip(AsyncTimeoutCompleteWrite.Passed.class);
+    }
+    
+    /**
+     * Test with content servlet that does:  
+     * AsyncContext create -> timeout -> onTimeout -> dispatch -> write-response
+     */
+    @Test
+    public void testAsyncTimeoutDispatchWrite_Default() throws Exception
+    {
+        testWithGzip(AsyncTimeoutDispatchWrite.Default.class);
+    }
+    
+    /**
+     * Test with content servlet that does:  
+     * AsyncContext create -> timeout -> onTimeout -> dispatch -> write-response
+     */
+    @Test
+    public void testAsyncTimeoutDispatchWrite_Passed() throws Exception
+    {
+        testWithGzip(AsyncTimeoutDispatchWrite.Passed.class);
+    }
+
+    /**
+     * Test with content servlet that does:  
+     * AsyncContext create -> no-timeout -> scheduler.schedule -> dispatch -> write-response
      */
     @Test
-    public void testEmpty() throws Exception
+    public void testAsyncScheduledDispatchWrite_Default() throws Exception
     {
-        assertIsNotGzipCompressed("empty.txt",0);
+        testWithGzip(AsyncScheduledDispatchWrite.Default.class);
     }
     
     /**
-     * Tests gzip compression of a small size file
+     * Test with content servlet that does:  
+     * AsyncContext create -> no-timeout -> scheduler.schedule -> dispatch -> write-response
+     */
+    @Test
+    public void testAsyncScheduledDispatchWrite_Passed() throws Exception
+    {
+        testWithGzip(AsyncScheduledDispatchWrite.Passed.class);
+    }
+
+    /**
+     * Test with content servlet that does:  
+     * 1) setHeader(content-length)
+     * 2) getOutputStream()
+     * 3) setHeader(content-type)
+     * 4) outputStream.write()
+     * 
+     * @see <a href="Eclipse Bug 354014">http://bugs.eclipse.org/354014</a>
      */
     @Test
-    public void testIsGzipCompressedSmall() throws Exception
+    public void testServletLengthStreamTypeWrite() throws Exception
     {
-        assertIsGzipCompressed("file-small.txt",SMALL);
+        testWithGzip(TestServletLengthStreamTypeWrite.class);
     }
 
     /**
-     * Tests gzip compression of a medium size file
+     * Test with content servlet that does:  
+     * 1) setHeader(content-length)
+     * 2) setHeader(content-type)
+     * 3) getOutputStream()
+     * 4) outputStream.write()
+     * 
+     * @see <a href="Eclipse Bug 354014">http://bugs.eclipse.org/354014</a>
      */
     @Test
-    public void testIsGzipCompressedMedium() throws Exception
+    public void testServletLengthTypeStreamWrite() throws Exception
     {
-        assertIsGzipCompressed("file-med.txt",MEDIUM);
+        testWithGzip(TestServletLengthTypeStreamWrite.class);
     }
 
     /**
-     * Tests gzip compression of a large size file
+     * Test with content servlet that does:  
+     * 1) getOutputStream()
+     * 2) setHeader(content-length)
+     * 3) setHeader(content-type)
+     * 4) outputStream.write()
+     * 
+     * @see <a href="Eclipse Bug 354014">http://bugs.eclipse.org/354014</a>
      */
     @Test
-    public void testIsGzipCompressedLarge() throws Exception
+    public void testServletStreamLengthTypeWrite() throws Exception
     {
-        assertIsGzipCompressed("file-large.txt",LARGE);
+        testWithGzip(TestServletStreamLengthTypeWrite.class);
     }
 
     /**
-     * Tests for problems with Content-Length header on small size files
-     * that are not being compressed encountered when using GzipFilter
-     *
+     * Test with content servlet that does:  
+     * 1) getOutputStream()
+     * 2) setHeader(content-length)
+     * 3) setHeader(content-type)
+     * 4) outputStream.write() (with frequent response flush)
+     * 
      * @see <a href="Eclipse Bug 354014">http://bugs.eclipse.org/354014</a>
      */
     @Test
-    public void testIsNotGzipCompressedTiny() throws Exception
+    public void testServletStreamLengthTypeWriteWithFlush() throws Exception
     {
-        assertIsNotGzipCompressed("file-tiny.txt",TINY);
+        testWithGzip(TestServletStreamLengthTypeWriteWithFlush.class);
     }
 
     /**
-     * Tests for problems with Content-Length header on small size files
-     * that are not being compressed encountered when using GzipFilter
-     *
+     * Test with content servlet that does:  
+     * 1) getOutputStream()
+     * 2) setHeader(content-type)
+     * 3) setHeader(content-length)
+     * 4) outputStream.write()
+     * 
      * @see <a href="Eclipse Bug 354014">http://bugs.eclipse.org/354014</a>
      */
     @Test
-    public void testIsNotGzipCompressedSmall() throws Exception
+    public void testServletStreamTypeLengthWrite() throws Exception
     {
-        assertIsNotGzipCompressed("file-small.mp3",SMALL);
+        testWithGzip(TestServletStreamTypeLengthWrite.class);
     }
 
     /**
-     * Tests for problems with Content-Length header on medium size files
-     * that are not being compressed encountered when using GzipFilter
-     *
+     * Test with content servlet that does:  
+     * 1) setHeader(content-type)
+     * 2) setHeader(content-length)
+     * 3) getOutputStream()
+     * 4) outputStream.write()
+     * 
      * @see <a href="Eclipse Bug 354014">http://bugs.eclipse.org/354014</a>
      */
     @Test
-    public void testIsNotGzipCompressedMedium() throws Exception
+    public void testServletTypeLengthStreamWrite() throws Exception
     {
-        assertIsNotGzipCompressed("file-medium.mp3",MEDIUM);
+        testWithGzip(TestServletTypeLengthStreamWrite.class);
     }
 
     /**
-     * Tests for problems with Content-Length header on large size files
-     * that were not being compressed encountered when using GzipFilter
-     *
+     * Test with content servlet that does:  
+     * 1) setHeader(content-type)
+     * 2) getOutputStream()
+     * 3) setHeader(content-length)
+     * 4) outputStream.write()
+     * 
      * @see <a href="Eclipse Bug 354014">http://bugs.eclipse.org/354014</a>
      */
     @Test
-    public void testIsNotGzipCompressedLarge() throws Exception
+    public void testServletTypeStreamLengthWrite() throws Exception
+    {
+        testWithGzip(TestServletTypeStreamLengthWrite.class);
+    }
+
+    /**
+     * Test with content servlet that does:  
+     * 2) getOutputStream()
+     * 1) setHeader(content-type)
+     * 3) setHeader(content-length)
+     * 4) (unwrapped) HttpOutput.write(ByteBuffer)
+     * 
+     * This is done to demonstrate a bug with using HttpOutput.write()
+     * while also using GzipFilter
+     * 
+     * @see <a href="Eclipse Bug 450873">http://bugs.eclipse.org/450873</a>
+     */
+    @Test
+    public void testHttpOutputWrite() throws Exception
     {
-        assertIsNotGzipCompressed("file-large.mp3",LARGE);
+        if (gzipFilterClass == GzipFilter.class)
+            return;  // Can't downcaste output stream when wrapper is used
+        testWithGzip(TestServletBufferTypeLengthWrite.class);
     }
 }
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipFilterDefaultNoRecompressTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipFilterDefaultNoRecompressTest.java
index 039b584..fb3ad8e 100644
--- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipFilterDefaultNoRecompressTest.java
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipFilterDefaultNoRecompressTest.java
@@ -23,7 +23,8 @@ import java.io.IOException;
 import java.util.Arrays;
 import java.util.List;
 
-import org.eclipse.jetty.servlet.DefaultServlet;
+import javax.servlet.Filter;
+
 import org.eclipse.jetty.servlet.FilterHolder;
 import org.eclipse.jetty.servlets.gzip.GzipTester;
 import org.eclipse.jetty.servlets.gzip.TestStaticMimeTypeServlet;
@@ -49,56 +50,61 @@ public class GzipFilterDefaultNoRecompressTest
         return Arrays.asList(new Object[][]
         {
                 // Some already compressed files
-                { "test_quotes.gz", "application/gzip", GzipFilter.GZIP },
-                { "test_quotes.bz2", "application/bzip2", GzipFilter.GZIP },
-                { "test_quotes.zip", "application/zip", GzipFilter.GZIP },
-                { "test_quotes.rar", "application/octet-stream", GzipFilter.GZIP },
+                { GzipFilter.class, "test_quotes.gz", "application/gzip", GzipFilter.GZIP },
+                { GzipFilter.class, "test_quotes.bz2", "application/bzip2", GzipFilter.GZIP },
+                { GzipFilter.class, "test_quotes.zip", "application/zip", GzipFilter.GZIP },
+                { GzipFilter.class, "test_quotes.rar", "application/octet-stream", GzipFilter.GZIP },
                 // Some images (common first)
-                { "jetty_logo.png", "image/png", GzipFilter.GZIP },
-                { "jetty_logo.gif", "image/gif", GzipFilter.GZIP },
-                { "jetty_logo.jpeg", "image/jpeg", GzipFilter.GZIP },
-                { "jetty_logo.jpg", "image/jpeg", GzipFilter.GZIP },
+                { GzipFilter.class, "jetty_logo.png", "image/png", GzipFilter.GZIP },
+                { GzipFilter.class, "jetty_logo.gif", "image/gif", GzipFilter.GZIP },
+                { GzipFilter.class, "jetty_logo.jpeg", "image/jpeg", GzipFilter.GZIP },
+                { GzipFilter.class, "jetty_logo.jpg", "image/jpeg", GzipFilter.GZIP },
                 // Lesser encountered images (usually found being requested from non-browser clients)
-                { "jetty_logo.bmp", "image/bmp", GzipFilter.GZIP },
-                { "jetty_logo.tga", "application/tga", GzipFilter.GZIP },
-                { "jetty_logo.tif", "image/tiff", GzipFilter.GZIP },
-                { "jetty_logo.tiff", "image/tiff", GzipFilter.GZIP },
-                { "jetty_logo.xcf", "image/xcf", GzipFilter.GZIP },
-                { "jetty_logo.jp2", "image/jpeg2000", GzipFilter.GZIP },
+                { GzipFilter.class, "jetty_logo.bmp", "image/bmp", GzipFilter.GZIP },
+                { GzipFilter.class, "jetty_logo.tga", "application/tga", GzipFilter.GZIP },
+                { GzipFilter.class, "jetty_logo.tif", "image/tiff", GzipFilter.GZIP },
+                { GzipFilter.class, "jetty_logo.tiff", "image/tiff", GzipFilter.GZIP },
+                { GzipFilter.class, "jetty_logo.xcf", "image/xcf", GzipFilter.GZIP },
+                { GzipFilter.class, "jetty_logo.jp2", "image/jpeg2000", GzipFilter.GZIP },
                 //qvalue disables compression
-                { "test_quotes.txt", "text/plain", GzipFilter.GZIP+";q=0"},
-                { "test_quotes.txt", "text/plain", GzipFilter.GZIP+"; q =    0 "},
-               
-
-                // Same tests again for deflate
+                { GzipFilter.class, "test_quotes.txt", "text/plain", GzipFilter.GZIP+";q=0"},
+                { GzipFilter.class, "test_quotes.txt", "text/plain", GzipFilter.GZIP+"; q =    0 "},
+                
+                
                 // Some already compressed files
-                { "test_quotes.gz", "application/gzip", GzipFilter.DEFLATE },
-                { "test_quotes.bz2", "application/bzip2", GzipFilter.DEFLATE },
-                { "test_quotes.zip", "application/zip", GzipFilter.DEFLATE },
-                { "test_quotes.rar", "application/octet-stream", GzipFilter.DEFLATE },
+                { AsyncGzipFilter.class, "test_quotes.gz", "application/gzip", GzipFilter.GZIP },
+                { AsyncGzipFilter.class, "test_quotes.bz2", "application/bzip2", GzipFilter.GZIP },
+                { AsyncGzipFilter.class, "test_quotes.zip", "application/zip", GzipFilter.GZIP },
+                { AsyncGzipFilter.class, "test_quotes.rar", "application/octet-stream", GzipFilter.GZIP },
                 // Some images (common first)
-                { "jetty_logo.png", "image/png", GzipFilter.DEFLATE },
-                { "jetty_logo.gif", "image/gif", GzipFilter.DEFLATE },
-                { "jetty_logo.jpeg", "image/jpeg", GzipFilter.DEFLATE },
-                { "jetty_logo.jpg", "image/jpeg", GzipFilter.DEFLATE },
+                { AsyncGzipFilter.class, "jetty_logo.png", "image/png", GzipFilter.GZIP },
+                { AsyncGzipFilter.class, "jetty_logo.gif", "image/gif", GzipFilter.GZIP },
+                { AsyncGzipFilter.class, "jetty_logo.jpeg", "image/jpeg", GzipFilter.GZIP },
+                { AsyncGzipFilter.class, "jetty_logo.jpg", "image/jpeg", GzipFilter.GZIP },
                 // Lesser encountered images (usually found being requested from non-browser clients)
-                { "jetty_logo.bmp", "image/bmp", GzipFilter.DEFLATE },
-                { "jetty_logo.tga", "application/tga", GzipFilter.DEFLATE },
-                { "jetty_logo.tif", "image/tiff", GzipFilter.DEFLATE },
-                { "jetty_logo.tiff", "image/tiff", GzipFilter.DEFLATE },
-                { "jetty_logo.xcf", "image/xcf", GzipFilter.DEFLATE },
-                { "jetty_logo.jp2", "image/jpeg2000", GzipFilter.DEFLATE } });
+                { AsyncGzipFilter.class, "jetty_logo.bmp", "image/bmp", GzipFilter.GZIP },
+                { AsyncGzipFilter.class, "jetty_logo.tga", "application/tga", GzipFilter.GZIP },
+                { AsyncGzipFilter.class, "jetty_logo.tif", "image/tiff", GzipFilter.GZIP },
+                { AsyncGzipFilter.class, "jetty_logo.tiff", "image/tiff", GzipFilter.GZIP },
+                { AsyncGzipFilter.class, "jetty_logo.xcf", "image/xcf", GzipFilter.GZIP },
+                { AsyncGzipFilter.class, "jetty_logo.jp2", "image/jpeg2000", GzipFilter.GZIP },
+                //qvalue disables compression
+                { AsyncGzipFilter.class, "test_quotes.txt", "text/plain", GzipFilter.GZIP+";q=0"},
+                { AsyncGzipFilter.class, "test_quotes.txt", "text/plain", GzipFilter.GZIP+"; q =    0 "}
+        });
     }
 
     @Rule
     public TestingDir testingdir = new TestingDir();
 
+    private Class<? extends Filter> testFilter;
     private String alreadyCompressedFilename;
     private String expectedContentType;
     private String compressionType;
 
-    public GzipFilterDefaultNoRecompressTest(String testFilename, String expectedContentType, String compressionType)
+    public GzipFilterDefaultNoRecompressTest(Class<? extends Filter> testFilter,String testFilename, String expectedContentType, String compressionType)
     {
+        this.testFilter = testFilter;
         this.alreadyCompressedFilename = testFilename;
         this.expectedContentType = expectedContentType;
         this.compressionType = compressionType;
@@ -108,6 +114,7 @@ public class GzipFilterDefaultNoRecompressTest
     public void testNotGzipFiltered_Default_AlreadyCompressed() throws Exception
     {
         GzipTester tester = new GzipTester(testingdir, compressionType);
+        tester.setGzipFilterClass(testFilter);
 
         copyTestFileToServer(alreadyCompressedFilename);
 
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipFilterDefaultTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipFilterDefaultTest.java
index 4a8bd28..2b2e0c2 100644
--- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipFilterDefaultTest.java
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipFilterDefaultTest.java
@@ -18,24 +18,34 @@
 
 package org.eclipse.jetty.servlets;
 
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+import java.io.File;
 import java.io.IOException;
+import java.io.UnsupportedEncodingException;
 import java.util.Arrays;
-import java.util.Collection;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
 
+import javax.servlet.DispatcherType;
+import javax.servlet.Filter;
 import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
 import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import junit.framework.Assert;
-
 import org.eclipse.jetty.http.HttpStatus;
-import org.eclipse.jetty.http.gzip.CompressedResponseWrapper;
+import org.eclipse.jetty.http.HttpTester;
 import org.eclipse.jetty.servlet.DefaultServlet;
 import org.eclipse.jetty.servlet.FilterHolder;
 import org.eclipse.jetty.servlets.gzip.GzipTester;
-import org.eclipse.jetty.testing.HttpTester;
+import org.eclipse.jetty.toolchain.test.IO;
 import org.eclipse.jetty.toolchain.test.TestingDir;
+import org.eclipse.jetty.util.StringUtil;
+import org.junit.Assert;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -48,42 +58,46 @@ import org.junit.runners.Parameterized.Parameters;
 @RunWith(Parameterized.class)
 public class GzipFilterDefaultTest
 {
-    @Parameters
-    public static Collection<String[]> data()
+    @Parameters(name="{1} - {0}")
+    public static List<Object[]> data()
     {
-        String[][] data = new String[][]
-        {
-        { GzipFilter.GZIP },
-        { GzipFilter.DEFLATE } };
-
-        return Arrays.asList(data);
+        return Arrays.asList(new Object[][]
+        { 
+            { AsyncGzipFilter.class, GzipFilter.GZIP },
+            { GzipFilter.class, GzipFilter.GZIP },
+            { GzipFilter.class, GzipFilter.DEFLATE },
+        });
     }
-    
+
+    private Class<? extends Filter> testFilter;
     private String compressionType;
-    
-    public GzipFilterDefaultTest(String compressionType)
+
+    public GzipFilterDefaultTest(Class<? extends Filter> testFilter, String compressionType)
     {
+        this.testFilter = testFilter;
         this.compressionType = compressionType;
     }
-    
+
+    @SuppressWarnings("serial")
     public static class HttpStatusServlet extends HttpServlet
     {
         private int _status = 204;
-        
+
         public HttpStatusServlet()
         {
             super();
         }
-        
+
         @Override
         protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
         {
             resp.setStatus(_status);
             resp.setHeader("ETag","W/\"204\"");
         }
-        
+
     }
-    
+
+    @SuppressWarnings("serial")
     public static class HttpErrorServlet extends HttpServlet
     {
         private int _status = 400;
@@ -100,312 +114,465 @@ public class GzipFilterDefaultTest
             resp.setStatus(_status);
         }
     }
-    
+
+    @SuppressWarnings("serial")
+    public static class HttpContentTypeWithEncoding extends HttpServlet
+    {
+        public static final String COMPRESSED_CONTENT = "<html><head></head><body><h1>COMPRESSABLE CONTENT</h1>"
+                + "This content must be longer than the default min gzip length, which is 256 bytes. "
+                + "The moon is blue to a fish in love. How now brown cow. The quick brown fox jumped over the lazy dog. A woman needs a man like a fish needs a bicycle!"
+                + "</body></html>";
+
+        @Override
+        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+        {
+            resp.setContentType("text/plain;charset=UTF8");
+            resp.setStatus(200);
+            ServletOutputStream out = resp.getOutputStream();
+            out.print(COMPRESSED_CONTENT);
+        }
+
+    }
+
     @Rule
     public TestingDir testingdir = new TestingDir();
 
-    
-
     @Test
     public void testIsGzipByMethod() throws Exception
     {
-        GzipTester tester = new GzipTester(testingdir, compressionType);
+        GzipTester tester = new GzipTester(testingdir,compressionType);
+
+        // Add Gzip Filter first
+        FilterHolder gzipHolder = new FilterHolder(testFilter);
+        gzipHolder.setAsyncSupported(true);
+        tester.addFilter(gzipHolder,"/*",EnumSet.of(DispatcherType.REQUEST,DispatcherType.ASYNC));
+        gzipHolder.setInitParameter("mimeTypes","text/plain");
+        gzipHolder.setInitParameter("methods","POST, WIBBLE");
 
-        // Test content that is smaller than the buffer.
-        int filesize = CompressedResponseWrapper.DEFAULT_BUFFER_SIZE * 2;
+        // Prepare Server File
+        int filesize = tester.getOutputBufferSize() * 2;
         tester.prepareServerFile("file.txt",filesize);
-        
-        FilterHolder holder = tester.setContentServlet(GetServlet.class);
-        holder.setInitParameter("mimeTypes","text/plain");
-        holder.setInitParameter("methods","POST,WIBBLE");
-                
+
+        // Content Servlet
+        tester.setContentServlet(GetServlet.class);
+
         try
         {
             tester.start();
+            HttpTester.Response response;
+
             tester.assertIsResponseGzipCompressed("POST","file.txt");
             tester.assertIsResponseGzipCompressed("WIBBLE","file.txt");
-            tester.assertIsResponseNotGzipCompressed("GET","file.txt",filesize,200);
+
+            response = tester.executeRequest("GET","/context/file.txt",5,TimeUnit.SECONDS);
+
+            assertThat("Response status",response.getStatus(),is(HttpStatus.OK_200));
+            assertThat("Content-Encoding",response.get("Content-Encoding"),not(containsString(compressionType)));
+
+            String content = tester.readResponse(response);
+            assertThat("Response content size",content.length(),is(filesize));
+            String expectedContent = IO.readToString(testingdir.getFile("file.txt"));
+            assertThat("Response content",content,is(expectedContent));
         }
         finally
         {
             tester.stop();
         }
     }
-    
+
+    @SuppressWarnings("serial")
     public static class GetServlet extends DefaultServlet
     {
         public GetServlet()
-        {    
+        {
             super();
         }
-        
+
         @Override
-        public void service(HttpServletRequest req, HttpServletResponse resp) throws IOException,ServletException
+        public void service(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException
         {
-            String uri=req.getRequestURI();
+            String uri = req.getRequestURI();
             if (uri.endsWith(".deferred"))
             {
                 // System.err.println("type for "+uri.substring(0,uri.length()-9)+" is "+getServletContext().getMimeType(uri.substring(0,uri.length()-9)));
-                resp.setContentType(getServletContext().getMimeType(uri.substring(0,uri.length()-9)));
+                resp.setContentType(getServletContext().getMimeType(uri.substring(0,uri.length() - 9)));
             }
-            
+
             doGet(req,resp);
         }
     }
-    
-   
-    
+
     @Test
     public void testIsGzipCompressedEmpty() throws Exception
     {
-        GzipTester tester = new GzipTester(testingdir, compressionType);
+        GzipTester tester = new GzipTester(testingdir,compressionType);
+
+        // Add Gzip Filter first
+        FilterHolder gzipHolder = new FilterHolder(testFilter);
+        gzipHolder.setAsyncSupported(true);
+        tester.addFilter(gzipHolder,"/*",EnumSet.of(DispatcherType.REQUEST,DispatcherType.ASYNC));
+        gzipHolder.setInitParameter("mimeTypes","text/plain");
 
-        // Test content that is smaller than the buffer.
+        // Prepare server file
         tester.prepareServerFile("empty.txt",0);
-        
-        FilterHolder holder = tester.setContentServlet(org.eclipse.jetty.servlet.DefaultServlet.class);
-        holder.setInitParameter("mimeTypes","text/plain");
+
+        // Set content servlet
+        tester.setContentServlet(DefaultServlet.class);
 
         try
         {
             tester.start();
-            HttpTester http = tester.assertIsResponseNotGzipCompressed("GET","empty.txt",0,200);
+
+            HttpTester.Response response;
+
+            response = tester.executeRequest("GET","/context/empty.txt",5,TimeUnit.SECONDS);
+
+            assertThat("Response status",response.getStatus(),is(HttpStatus.OK_200));
+            assertThat("Content-Encoding",response.get("Content-Encoding"),not(containsString(compressionType)));
+
+            String content = tester.readResponse(response);
+            assertThat("Response content size",content.length(),is(0));
+            String expectedContent = IO.readToString(testingdir.getFile("empty.txt"));
+            assertThat("Response content",content,is(expectedContent));
         }
         finally
         {
             tester.stop();
         }
     }
-    
+
     @Test
     public void testIsGzipCompressedTiny() throws Exception
     {
-        GzipTester tester = new GzipTester(testingdir, compressionType);
+        GzipTester tester = new GzipTester(testingdir,compressionType);
+        tester.setGzipFilterClass(testFilter);
 
-        // Test content that is smaller than the buffer.
-        int filesize = CompressedResponseWrapper.DEFAULT_BUFFER_SIZE / 4;
+        int filesize = tester.getOutputBufferSize() / 4;
         tester.prepareServerFile("file.txt",filesize);
-        
+
         FilterHolder holder = tester.setContentServlet(org.eclipse.jetty.servlet.DefaultServlet.class);
         holder.setInitParameter("mimeTypes","text/plain");
 
         try
         {
             tester.start();
-            HttpTester http = tester.assertIsResponseGzipCompressed("GET","file.txt");
-            Assert.assertEquals("Accept-Encoding",http.getHeader("Vary"));
+            HttpTester.Response http = tester.assertIsResponseGzipCompressed("GET","file.txt");
+            Assert.assertEquals("Accept-Encoding",http.get("Vary"));
         }
         finally
         {
             tester.stop();
         }
     }
-    
+
     @Test
-    public void testIsGzipCompressedTinyWithQ() throws Exception
+    public void testIsGzipCompressedLarge() throws Exception
     {
-        GzipTester tester = new GzipTester(testingdir, compressionType+";q=0.5");
+        GzipTester tester = new GzipTester(testingdir,compressionType);
+        tester.setGzipFilterClass(testFilter);
 
-        // Test content that is smaller than the buffer.
-        int filesize = CompressedResponseWrapper.DEFAULT_BUFFER_SIZE / 4;
+        int filesize = tester.getOutputBufferSize() * 4;
         tester.prepareServerFile("file.txt",filesize);
-        
+
         FilterHolder holder = tester.setContentServlet(org.eclipse.jetty.servlet.DefaultServlet.class);
         holder.setInitParameter("mimeTypes","text/plain");
 
         try
         {
             tester.start();
-            HttpTester http = tester.assertIsResponseGzipCompressed("GET","file.txt");
-            Assert.assertEquals("Accept-Encoding",http.getHeader("Vary"));
+            HttpTester.Response http = tester.assertIsResponseGzipCompressed("GET","file.txt");
+            Assert.assertEquals("Accept-Encoding",http.get("Vary"));
         }
         finally
         {
             tester.stop();
         }
     }
-    
+
     @Test
-    public void testIsGzipCompressedTinyWithBadQ() throws Exception
+    public void testGzipedIfModified() throws Exception
     {
-        GzipTester tester = new GzipTester(testingdir, compressionType+";q=");
+        GzipTester tester = new GzipTester(testingdir,compressionType);
+        tester.setGzipFilterClass(testFilter);
 
-        // Test content that is smaller than the buffer.
-        int filesize = CompressedResponseWrapper.DEFAULT_BUFFER_SIZE / 4;
+        int filesize = tester.getOutputBufferSize() * 4;
         tester.prepareServerFile("file.txt",filesize);
-        
+
         FilterHolder holder = tester.setContentServlet(org.eclipse.jetty.servlet.DefaultServlet.class);
         holder.setInitParameter("mimeTypes","text/plain");
 
         try
         {
             tester.start();
-            HttpTester http = tester.assertIsResponseGzipCompressed("GET","file.txt");
-            Assert.assertEquals("Accept-Encoding",http.getHeader("Vary"));
+            HttpTester.Response http = tester.assertIsResponseGzipCompressed("GET","file.txt",System.currentTimeMillis() - 4000);
+            Assert.assertEquals("Accept-Encoding",http.get("Vary"));
         }
         finally
         {
             tester.stop();
         }
     }
-    
+
     @Test
-    public void testIsGzipCompressedLarge() throws Exception
+    public void testGzippedIfSVG() throws Exception
     {
-        GzipTester tester = new GzipTester(testingdir, compressionType);
-
-        // Test content that is smaller than the buffer.
-        int filesize = CompressedResponseWrapper.DEFAULT_BUFFER_SIZE * 4;
-        tester.prepareServerFile("file.txt",filesize);
-        
+        GzipTester tester = new GzipTester(testingdir,compressionType);
+        tester.setGzipFilterClass(testFilter);
+        tester.copyTestServerFile("test.svg");
+        @SuppressWarnings("unused")
         FilterHolder holder = tester.setContentServlet(org.eclipse.jetty.servlet.DefaultServlet.class);
-        holder.setInitParameter("mimeTypes","text/plain");
-
         try
         {
             tester.start();
-            HttpTester http = tester.assertIsResponseGzipCompressed("GET","file.txt");
-            Assert.assertEquals("Accept-Encoding",http.getHeader("Vary"));
+            HttpTester.Response http = tester.assertIsResponseGzipCompressed("GET","test.svg",System.currentTimeMillis() - 4000);
+            Assert.assertEquals("Accept-Encoding",http.get("Vary"));
         }
         finally
         {
             tester.stop();
         }
     }
-    
 
     @Test
-    public void testGzipedIfModified() throws Exception
+    public void testNotGzipedIfNotModified() throws Exception
     {
-        GzipTester tester = new GzipTester(testingdir, compressionType);
+        GzipTester tester = new GzipTester(testingdir,compressionType);
+        tester.setGzipFilterClass(testFilter);
 
-        // Test content that is smaller than the buffer.
-        int filesize = CompressedResponseWrapper.DEFAULT_BUFFER_SIZE * 4;
+        int filesize = tester.getOutputBufferSize() * 4;
         tester.prepareServerFile("file.txt",filesize);
-        
+
         FilterHolder holder = tester.setContentServlet(org.eclipse.jetty.servlet.DefaultServlet.class);
         holder.setInitParameter("mimeTypes","text/plain");
+        holder.setInitParameter("etags","true");
 
         try
         {
             tester.start();
-            HttpTester http = tester.assertIsResponseGzipCompressed("GET","file.txt",System.currentTimeMillis()-4000);
-            Assert.assertEquals("Accept-Encoding",http.getHeader("Vary"));
+            tester.assertIsResponseNotModified("GET","file.txt",System.currentTimeMillis() + 4000);
         }
         finally
         {
             tester.stop();
         }
     }
-    
 
     @Test
-    public void testNotGzipedIfNotModified() throws Exception
+    public void testIsNotGzipCompressedWithZeroQ() throws Exception
     {
-        GzipTester tester = new GzipTester(testingdir, compressionType);
+        GzipTester tester = new GzipTester(testingdir,compressionType + "; q=0");
 
-        // Test content that is smaller than the buffer.
-        int filesize = CompressedResponseWrapper.DEFAULT_BUFFER_SIZE * 4;
+        // Add Gzip Filter first
+        FilterHolder gzipHolder = new FilterHolder(testFilter);
+        gzipHolder.setAsyncSupported(true);
+        tester.addFilter(gzipHolder,"/*",EnumSet.of(DispatcherType.REQUEST,DispatcherType.ASYNC));
+        gzipHolder.setInitParameter("mimeTypes","text/plain");
+
+        // Prepare server file
+        int filesize = tester.getOutputBufferSize() / 4;
         tester.prepareServerFile("file.txt",filesize);
-        
-        FilterHolder holder = tester.setContentServlet(org.eclipse.jetty.servlet.DefaultServlet.class);
-        holder.setInitParameter("mimeTypes","text/plain");
-        holder.setInitParameter("etags","true");
+
+        // Add content servlet
+        tester.setContentServlet(DefaultServlet.class);
 
         try
         {
             tester.start();
-            HttpTester http = tester.assertIsResponseNotModified("GET","file.txt",System.currentTimeMillis()+4000);
+            HttpTester.Response http = assertIsResponseNotGzipCompressed(tester,"GET","file.txt",filesize,HttpStatus.OK_200);
+            assertThat("Response[Vary]",http.get("Vary"),containsString("Accept-Encoding"));
         }
         finally
         {
             tester.stop();
         }
     }
-    
 
     @Test
-    public void testIsNotGzipCompressedWithQ() throws Exception
+    public void testIsGzipCompressedWithQ() throws Exception
     {
-        GzipTester tester = new GzipTester(testingdir, compressionType+"; q = 0");
-        
-        int filesize = CompressedResponseWrapper.DEFAULT_BUFFER_SIZE / 4;
+        GzipTester tester = new GzipTester(testingdir,compressionType,"something;q=0.1," + compressionType + ";q=0.5");
+        tester.setGzipFilterClass(testFilter);
+
+        int filesize = tester.getOutputBufferSize() / 4;
         tester.prepareServerFile("file.txt",filesize);
-        
+
         FilterHolder holder = tester.setContentServlet(org.eclipse.jetty.servlet.DefaultServlet.class);
         holder.setInitParameter("mimeTypes","text/plain");
 
         try
         {
             tester.start();
-            HttpTester http = tester.assertIsResponseNotGzipCompressed("GET","file.txt", filesize, HttpStatus.OK_200);
-            Assert.assertEquals("Accept-Encoding",http.getHeader("Vary"));
+            HttpTester.Response http = tester.assertIsResponseGzipCompressed("GET","file.txt");
+            Assert.assertEquals("Accept-Encoding",http.get("Vary"));
         }
         finally
         {
             tester.stop();
         }
     }
-    
+
     @Test
     public void testIsNotGzipCompressedByContentType() throws Exception
     {
-        GzipTester tester = new GzipTester(testingdir, compressionType);
+        GzipTester tester = new GzipTester(testingdir,compressionType);
 
-        int filesize = CompressedResponseWrapper.DEFAULT_BUFFER_SIZE * 4;
+        // Add Gzip Filter first
+        FilterHolder gzipHolder = new FilterHolder(testFilter);
+        gzipHolder.setAsyncSupported(true);
+        tester.addFilter(gzipHolder,"/*",EnumSet.of(DispatcherType.REQUEST,DispatcherType.ASYNC));
+        gzipHolder.setInitParameter("mimeTypes","text/plain");
+
+        // Prepare server file
+        int filesize = tester.getOutputBufferSize() * 4;
         tester.prepareServerFile("file.mp3",filesize);
-        
-        FilterHolder holder = tester.setContentServlet(org.eclipse.jetty.servlet.DefaultServlet.class);
-        holder.setInitParameter("mimeTypes","text/plain");
+
+        // Add content servlet
+        tester.setContentServlet(DefaultServlet.class);
 
         try
         {
             tester.start();
-            HttpTester http = tester.assertIsResponseNotGzipCompressed("GET","file.mp3", filesize, HttpStatus.OK_200);
-            Assert.assertNull(http.getHeader("Vary"));
+            HttpTester.Response http = assertIsResponseNotGzipCompressed(tester,"GET","file.mp3",filesize,HttpStatus.OK_200);
+            Assert.assertNull(http.get("Vary"));
         }
         finally
         {
             tester.stop();
         }
     }
-    
+
+    @Test
+    public void testIsNotGzipCompressedByExcludedContentType() throws Exception
+    {
+        GzipTester tester = new GzipTester(testingdir,compressionType);
+
+        // Add Gzip Filter first
+        FilterHolder gzipHolder = new FilterHolder(testFilter);
+        gzipHolder.setAsyncSupported(true);
+        tester.addFilter(gzipHolder,"/*",EnumSet.of(DispatcherType.REQUEST,DispatcherType.ASYNC));
+        gzipHolder.setInitParameter("excludedMimeTypes","text/plain");
+
+        // Prepare server file
+        int filesize = tester.getOutputBufferSize() * 4;
+        tester.prepareServerFile("test_quotes.txt",filesize);
+
+        // Add content servlet
+        tester.setContentServlet(DefaultServlet.class);
+
+        try
+        {
+            tester.start();
+            HttpTester.Response http = assertIsResponseNotGzipCompressed(tester,"GET","test_quotes.txt",filesize,HttpStatus.OK_200);
+            Assert.assertNull(http.get("Vary"));
+        }
+        finally
+        {
+            tester.stop();
+        }
+    }
+
+    @Test
+    public void testIsNotGzipCompressedByExcludedContentTypeWithCharset() throws Exception
+    {
+        GzipTester tester = new GzipTester(testingdir,compressionType);
+
+        // Add Gzip Filter first
+        FilterHolder gzipHolder = new FilterHolder(testFilter);
+        gzipHolder.setAsyncSupported(true);
+        tester.addFilter(gzipHolder,"/*",EnumSet.of(DispatcherType.REQUEST,DispatcherType.ASYNC));
+        gzipHolder.setInitParameter("excludedMimeTypes","text/plain");
+
+        // Prepare server file
+        int filesize = tester.getOutputBufferSize() * 4;
+        tester.prepareServerFile("test_quotes.txt",filesize);
+        tester.addMimeType("txt","text/plain;charset=UTF-8");
+
+        // Add content servlet
+        tester.setContentServlet(DefaultServlet.class);
+
+        try
+        {
+            tester.start();
+            HttpTester.Response http = assertIsResponseNotGzipCompressed(tester,"GET","test_quotes.txt",filesize,HttpStatus.OK_200);
+            Assert.assertNull(http.get("Vary"));
+        }
+        finally
+        {
+            tester.stop();
+        }
+    }
+
+    @Test
+    public void testGzipCompressedByContentTypeWithEncoding() throws Exception
+    {
+        GzipTester tester = new GzipTester(testingdir,compressionType);
+        tester.setGzipFilterClass(testFilter);
+        FilterHolder holder = tester.setContentServlet(HttpContentTypeWithEncoding.class);
+        holder.setInitParameter("mimeTypes","text/plain");
+        try
+        {
+            tester.start();
+            HttpTester.Response http = tester.assertNonStaticContentIsResponseGzipCompressed("GET","xxx",HttpContentTypeWithEncoding.COMPRESSED_CONTENT);
+            Assert.assertEquals("Accept-Encoding",http.get("Vary"));
+        }
+        finally
+        {
+            tester.stop();
+        }
+    }
+
     @Test
     public void testIsNotGzipCompressedByDeferredContentType() throws Exception
     {
-        GzipTester tester = new GzipTester(testingdir, compressionType);
+        GzipTester tester = new GzipTester(testingdir,compressionType);
+
+        // Add Gzip Filter first
+        FilterHolder gzipHolder = new FilterHolder(testFilter);
+        gzipHolder.setAsyncSupported(true);
+        tester.addFilter(gzipHolder,"/*",EnumSet.of(DispatcherType.REQUEST,DispatcherType.ASYNC));
+        gzipHolder.setInitParameter("mimeTypes","text/plain");
 
-        int filesize = CompressedResponseWrapper.DEFAULT_BUFFER_SIZE * 4;
+        // Prepare server file
+        int filesize = tester.getOutputBufferSize() * 4;
         tester.prepareServerFile("file.mp3.deferred",filesize);
-        
-        FilterHolder holder = tester.setContentServlet(GetServlet.class);
-        holder.setInitParameter("mimeTypes","text/plain");
+
+        // Add content servlet
+        tester.setContentServlet(GetServlet.class);
 
         try
         {
             tester.start();
-            HttpTester http = tester.assertIsResponseNotGzipCompressed("GET","file.mp3.deferred", filesize, HttpStatus.OK_200);
-            Assert.assertNull(http.getHeader("Vary"));
+            HttpTester.Response response = assertIsResponseNotGzipCompressed(tester,"GET","file.mp3.deferred",filesize,HttpStatus.OK_200);
+            assertThat("Response[Vary]", response.get("Vary"), isEmptyOrNullString());
         }
         finally
         {
             tester.stop();
         }
     }
-    
+
     @Test
     public void testIsNotGzipCompressedHttpStatus() throws Exception
-    { 
-        GzipTester tester = new GzipTester(testingdir, compressionType);
+    {
+        GzipTester tester = new GzipTester(testingdir,compressionType);
+
+        // Add Gzip Filter first
+        FilterHolder gzipHolder = new FilterHolder(testFilter);
+        gzipHolder.setAsyncSupported(true);
+        tester.addFilter(gzipHolder,"/*",EnumSet.of(DispatcherType.REQUEST,DispatcherType.ASYNC));
+        gzipHolder.setInitParameter("mimeTypes","text/plain");
 
         // Test error code 204
-        FilterHolder holder = tester.setContentServlet(HttpStatusServlet.class);
-        holder.setInitParameter("mimeTypes","text/plain");
+        tester.setContentServlet(HttpStatusServlet.class);
 
         try
         {
             tester.start();
-            tester.assertIsResponseNotGzipCompressed("GET",-1, 204);
+
+            HttpTester.Response response = tester.executeRequest("GET","/context/",5,TimeUnit.SECONDS);
+
+            assertThat("Response status",response.getStatus(),is(HttpStatus.NO_CONTENT_204));
+            assertThat("Content-Encoding",response.get("Content-Encoding"),not(containsString(compressionType)));
         }
         finally
         {
@@ -413,44 +580,63 @@ public class GzipFilterDefaultTest
         }
 
     }
-    
+
     @Test
     public void testIsNotGzipCompressedHttpBadRequestStatus() throws Exception
-    { 
-        GzipTester tester = new GzipTester(testingdir, compressionType);
-        
+    {
+        GzipTester tester = new GzipTester(testingdir,compressionType);
+
+        // Add Gzip Filter first
+        FilterHolder gzipHolder = new FilterHolder(testFilter);
+        gzipHolder.setAsyncSupported(true);
+        gzipHolder.setInitParameter("mimeTypes","text/plain");
+        tester.addFilter(gzipHolder,"/*",EnumSet.of(DispatcherType.REQUEST,DispatcherType.ASYNC));
+
         // Test error code 400
-        FilterHolder holder = tester.setContentServlet(HttpErrorServlet.class);
-        holder.setInitParameter("mimeTypes","text/plain");
-        
+        tester.setContentServlet(HttpErrorServlet.class);
+
         try
         {
             tester.start();
-            tester.assertIsResponseNotGzipCompressedAndEqualToExpectedString("GET","error message", -1, 400);
+
+            HttpTester.Response response = tester.executeRequest("GET","/context/",5,TimeUnit.SECONDS);
+
+            assertThat("Response status",response.getStatus(),is(HttpStatus.BAD_REQUEST_400));
+            assertThat("Content-Encoding",response.get("Content-Encoding"),not(containsString(compressionType)));
+
+            String content = tester.readResponse(response);
+            assertThat("Response content",content,is("error message"));
         }
         finally
         {
             tester.stop();
         }
-        
     }
 
     @Test
     public void testUserAgentExclusion() throws Exception
     {
         GzipTester tester = new GzipTester(testingdir,compressionType);
-
-        FilterHolder holder = tester.setContentServlet(DefaultServlet.class);
-        holder.setInitParameter("excludedAgents","foo");
         tester.setUserAgent("foo");
 
-        int filesize = CompressedResponseWrapper.DEFAULT_BUFFER_SIZE * 4;
+        // Add Gzip Filter first
+        FilterHolder gzipHolder = new FilterHolder(testFilter);
+        gzipHolder.setAsyncSupported(true);
+        gzipHolder.setInitParameter("mimeTypes","text/plain");
+        gzipHolder.setInitParameter("excludedAgents","bar, foo");
+        tester.addFilter(gzipHolder,"/*",EnumSet.of(DispatcherType.REQUEST,DispatcherType.ASYNC));
+
+        // Prepare server file
+        int filesize = tester.getOutputBufferSize() * 4;
         tester.prepareServerFile("file.txt",filesize);
 
+        // Add content servlet
+        tester.setContentServlet(DefaultServlet.class);
+
         try
         {
             tester.start();
-            tester.assertIsResponseNotGzipCompressed("GET","file.txt",filesize,HttpStatus.OK_200);
+            assertIsResponseNotGzipCompressed(tester,"GET","file.txt",filesize,HttpStatus.OK_200);
         }
         finally
         {
@@ -462,19 +648,26 @@ public class GzipFilterDefaultTest
     public void testUserAgentExclusionByExcludedAgentPatterns() throws Exception
     {
         GzipTester tester = new GzipTester(testingdir,compressionType);
-
-        FilterHolder holder = tester.setContentServlet(DefaultServlet.class);
-        holder.setInitParameter("excludedAgents","bar");
-        holder.setInitParameter("excludeAgentPatterns","fo.*");
         tester.setUserAgent("foo");
 
-        int filesize = CompressedResponseWrapper.DEFAULT_BUFFER_SIZE * 4;
+        // Add Gzip Filter first
+        FilterHolder gzipHolder = new FilterHolder(testFilter);
+        gzipHolder.setAsyncSupported(true);
+        gzipHolder.setInitParameter("excludedAgents","bar");
+        gzipHolder.setInitParameter("excludeAgentPatterns","fo.*");
+        tester.addFilter(gzipHolder,"/*",EnumSet.of(DispatcherType.REQUEST,DispatcherType.ASYNC));
+
+        // Prepare server file
+        int filesize = tester.getOutputBufferSize() * 4;
         tester.prepareServerFile("file.txt",filesize);
 
+        // Set content servlet
+        tester.setContentServlet(DefaultServlet.class);
+
         try
         {
             tester.start();
-            tester.assertIsResponseNotGzipCompressed("GET","file.txt",filesize,HttpStatus.OK_200);
+            assertIsResponseNotGzipCompressed(tester,"GET","file.txt",filesize,HttpStatus.OK_200);
         }
         finally
         {
@@ -487,16 +680,23 @@ public class GzipFilterDefaultTest
     {
         GzipTester tester = new GzipTester(testingdir,compressionType);
 
-        FilterHolder holder = tester.setContentServlet(DefaultServlet.class);
-        holder.setInitParameter("excludePaths","/context/");
+        // Add Gzip Filter first
+        FilterHolder gzipHolder = new FilterHolder(testFilter);
+        gzipHolder.setAsyncSupported(true);
+        gzipHolder.setInitParameter("excludePaths","/bar/, /context/");
+        tester.addFilter(gzipHolder,"/*",EnumSet.of(DispatcherType.REQUEST,DispatcherType.ASYNC));
 
-        int filesize = CompressedResponseWrapper.DEFAULT_BUFFER_SIZE * 4;
+        // Prepare server file
+        int filesize = tester.getOutputBufferSize() * 4;
         tester.prepareServerFile("file.txt",filesize);
 
+        // Set content servlet
+        tester.setContentServlet(DefaultServlet.class);
+
         try
         {
             tester.start();
-            tester.assertIsResponseNotGzipCompressed("GET","file.txt",filesize,HttpStatus.OK_200);
+            assertIsResponseNotGzipCompressed(tester,"GET","file.txt",filesize,HttpStatus.OK_200);
         }
         finally
         {
@@ -509,16 +709,82 @@ public class GzipFilterDefaultTest
     {
         GzipTester tester = new GzipTester(testingdir,compressionType);
 
-        FilterHolder holder = tester.setContentServlet(DefaultServlet.class);
-        holder.setInitParameter("excludePathPatterns","/cont.*");
+        // Add Gzip Filter first
+        FilterHolder gzipHolder = new FilterHolder(testFilter);
+        gzipHolder.setAsyncSupported(true);
+        gzipHolder.setInitParameter("excludePathPatterns","/cont.*");
+        tester.addFilter(gzipHolder,"/*",EnumSet.of(DispatcherType.REQUEST,DispatcherType.ASYNC));
 
-        int filesize = CompressedResponseWrapper.DEFAULT_BUFFER_SIZE * 4;
+        // Prepare server file
+        int filesize = tester.getOutputBufferSize() * 4;
         tester.prepareServerFile("file.txt",filesize);
 
+        // Set content servlet
+        tester.setContentServlet(DefaultServlet.class);
+
+        try
+        {
+            tester.start();
+            assertIsResponseNotGzipCompressed(tester,"GET","file.txt",filesize,HttpStatus.OK_200);
+        }
+        finally
+        {
+            tester.stop();
+        }
+    }
+
+    public HttpTester.Response assertIsResponseNotGzipCompressed(GzipTester tester, String method, String filename, int expectedFilesize, int status)
+            throws Exception
+    {
+        HttpTester.Response response = tester.executeRequest(method,"/context/" + filename,5,TimeUnit.SECONDS);
+
+        assertThat("Response status",response.getStatus(),is(status));
+        assertThat("Content-Encoding",response.get("Content-Encoding"),not(containsString(compressionType)));
+
+        assertResponseContent(tester,response,status,filename,expectedFilesize);
+
+        return response;
+    }
+
+    private void assertResponseContent(GzipTester tester, HttpTester.Response response, int status, String filename, int expectedFilesize) throws IOException,
+            UnsupportedEncodingException
+    {
+        if (expectedFilesize >= 0)
+        {
+            assertThat("filename",filename,notNullValue());
+            assertThat("Response contentBytes.length",response.getContentBytes().length,is(expectedFilesize));
+            String contentLength = response.get("Content-Length");
+            if (StringUtil.isNotBlank(contentLength))
+            {
+                assertThat("Content-Length",response.get("Content-Length"),is(Integer.toString(expectedFilesize)));
+            }
+
+            if (status >= 200 && status < 300)
+            {
+                assertThat("ETag",response.get("ETAG"),startsWith("W/"));
+            }
+
+            File serverFile = testingdir.getFile(filename);
+            String expectedResponse = IO.readToString(serverFile);
+
+            String actual = tester.readResponse(response);
+            Assert.assertEquals("Expected response equals actual response",expectedResponse,actual);
+        }
+    }
+
+    @Test
+    public void testIsNotGzipCompressedSVGZ() throws Exception
+    {
+        GzipTester tester = new GzipTester(testingdir,compressionType);
+        tester.setGzipFilterClass(testFilter);
+
+        @SuppressWarnings("unused")
+        FilterHolder holder = tester.setContentServlet(DefaultServlet.class);
+        tester.copyTestServerFile("test.svgz");
         try
         {
             tester.start();
-            tester.assertIsResponseNotGzipCompressed("GET","file.txt",filesize,HttpStatus.OK_200);
+            tester.assertIsResponseNotGzipFiltered("test.svgz","test.svgz.sha1","image/svg+xml","gzip");
         }
         finally
         {
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipFilterLayeredTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipFilterLayeredTest.java
new file mode 100644
index 0000000..85c4236
--- /dev/null
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipFilterLayeredTest.java
@@ -0,0 +1,211 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlets;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import javax.servlet.DispatcherType;
+
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.HttpTester;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.servlet.FilterHolder;
+import org.eclipse.jetty.servlets.gzip.AsyncManipFilter;
+import org.eclipse.jetty.servlets.gzip.AsyncScheduledDispatchWrite;
+import org.eclipse.jetty.servlets.gzip.AsyncTimeoutCompleteWrite;
+import org.eclipse.jetty.servlets.gzip.AsyncTimeoutDispatchWrite;
+import org.eclipse.jetty.servlets.gzip.GzipTester;
+import org.eclipse.jetty.servlets.gzip.GzipTester.ContentMetadata;
+import org.eclipse.jetty.servlets.gzip.TestDirContentServlet;
+import org.eclipse.jetty.servlets.gzip.TestServletLengthStreamTypeWrite;
+import org.eclipse.jetty.toolchain.test.TestTracker;
+import org.eclipse.jetty.toolchain.test.TestingDir;
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Test the GzipFilter support when under several layers of Filters.
+ */
+ at RunWith(Parameterized.class)
+ at Ignore
+public class GzipFilterLayeredTest
+{
+    @Rule
+    public final TestTracker tracker = new TestTracker();
+    
+    private static final HttpConfiguration defaultHttp = new HttpConfiguration();
+    private static final int LARGE = defaultHttp.getOutputBufferSize() * 8;
+    private static final int SMALL = defaultHttp.getOutputBufferSize() / 4;
+    private static final int TINY = AsyncGzipFilter.DEFAULT_MIN_GZIP_SIZE / 2;
+    private static final boolean EXPECT_COMPRESSED = true;
+
+    @Parameters(name = "{0} bytes - {1} - compressed({2}) - filter({3}) - servlet({4}")
+    public static List<Object[]> data()
+    {
+        List<Object[]> ret = new ArrayList<Object[]>();
+        
+        Class<?> gzipFilters[] = new Class<?>[] { GzipFilter.class, AsyncGzipFilter.class };
+        Class<?> contentServlets[] = new Class<?>[] { 
+                TestServletLengthStreamTypeWrite.class, 
+                AsyncTimeoutDispatchWrite.Default.class,
+                AsyncTimeoutDispatchWrite.Passed.class,
+                AsyncTimeoutCompleteWrite.Default.class,
+                AsyncTimeoutCompleteWrite.Passed.class,
+                AsyncScheduledDispatchWrite.Default.class,
+                AsyncScheduledDispatchWrite.Passed.class,
+                };
+
+        for (Class<?> contentServlet: contentServlets)
+        {
+            for (Class<?> gzipFilter : gzipFilters)
+            {
+                ret.add(new Object[] { 0, "empty.txt", !EXPECT_COMPRESSED, gzipFilter, contentServlet });
+                ret.add(new Object[] { TINY, "file-tiny.txt", !EXPECT_COMPRESSED, gzipFilter, contentServlet });
+                ret.add(new Object[] { SMALL, "file-small.txt", EXPECT_COMPRESSED, gzipFilter, contentServlet });
+                ret.add(new Object[] { LARGE, "file-large.txt", EXPECT_COMPRESSED, gzipFilter, contentServlet });
+                ret.add(new Object[] { LARGE, "file-large.mp3", !EXPECT_COMPRESSED, gzipFilter, contentServlet });
+            }
+        }
+
+        return ret;
+    }
+
+    @Parameter(0)
+    public int fileSize;
+    @Parameter(1)
+    public String fileName;
+    @Parameter(2)
+    public boolean expectCompressed;
+    @Parameter(3)
+    public Class<? extends GzipFilter> gzipFilterClass;
+    @Parameter(4)
+    public Class<? extends TestDirContentServlet> contentServletClass;
+
+    @Rule
+    public TestingDir testingdir = new TestingDir();
+    
+    @Test
+    public void testGzipDos() throws Exception
+    {
+        GzipTester tester = new GzipTester(testingdir, GzipFilter.GZIP);
+        
+        // Add Gzip Filter first
+        FilterHolder gzipHolder = new FilterHolder(gzipFilterClass);
+        gzipHolder.setAsyncSupported(true);
+        tester.addFilter(gzipHolder,"*.txt",EnumSet.of(DispatcherType.REQUEST,DispatcherType.ASYNC));
+        tester.addFilter(gzipHolder,"*.mp3",EnumSet.of(DispatcherType.REQUEST,DispatcherType.ASYNC));
+        gzipHolder.setInitParameter("mimeTypes","text/plain");
+
+        // Add (DoSFilter-like) manip filter (in chain of Gzip)
+        FilterHolder manipHolder = new FilterHolder(AsyncManipFilter.class);
+        manipHolder.setAsyncSupported(true);
+        tester.addFilter(manipHolder,"/*",EnumSet.of(DispatcherType.REQUEST,DispatcherType.ASYNC));
+        
+        // Add content servlet
+        tester.setContentServlet(contentServletClass);
+        
+        try
+        {
+            String testFilename = String.format("GzipDos-%s-%s",contentServletClass.getSimpleName(),fileName);
+            File testFile = tester.prepareServerFile(testFilename,fileSize);
+            
+            tester.start();
+            
+            HttpTester.Response response = tester.executeRequest("GET","/context/" + testFile.getName(),5,TimeUnit.SECONDS);
+            
+            assertThat("Response status", response.getStatus(), is(HttpStatus.OK_200));
+            
+            if (expectCompressed)
+            {
+                // Must be gzip compressed
+                assertThat("Content-Encoding",response.get("Content-Encoding"),containsString(GzipFilter.GZIP));
+            }
+            
+            // Uncompressed content Size
+            ContentMetadata content = tester.getResponseMetadata(response);
+            assertThat("(Uncompressed) Content Length", content.size, is((long)fileSize));
+        }
+        finally
+        {
+            tester.stop();
+        }
+    }
+    
+    @Test
+    public void testDosGzip() throws Exception
+    {
+        GzipTester tester = new GzipTester(testingdir, GzipFilter.GZIP);
+        
+        // Add (DoSFilter-like) manip filter
+        FilterHolder manipHolder = new FilterHolder(AsyncManipFilter.class);
+        manipHolder.setAsyncSupported(true);
+        tester.addFilter(manipHolder,"/*",EnumSet.of(DispatcherType.REQUEST,DispatcherType.ASYNC));
+        
+        // Add Gzip Filter first (in chain of DosFilter)
+        FilterHolder gzipHolder = new FilterHolder(gzipFilterClass);
+        gzipHolder.setAsyncSupported(true);
+        tester.addFilter(gzipHolder,"*.txt",EnumSet.of(DispatcherType.REQUEST,DispatcherType.ASYNC));
+        tester.addFilter(gzipHolder,"*.mp3",EnumSet.of(DispatcherType.REQUEST,DispatcherType.ASYNC));
+        gzipHolder.setInitParameter("mimeTypes","text/plain");
+
+        // Add content servlet
+        tester.setContentServlet(contentServletClass);
+        
+        try
+        {
+            String testFilename = String.format("DosGzip-%s-%s",contentServletClass.getSimpleName(),fileName);
+            File testFile = tester.prepareServerFile(testFilename,fileSize);
+            
+            tester.start();
+            
+            HttpTester.Response response = tester.executeRequest("GET","/context/" + testFile.getName(),5,TimeUnit.SECONDS);
+            
+            assertThat("Response status", response.getStatus(), is(HttpStatus.OK_200));
+            
+            if (expectCompressed)
+            {
+                // Must be gzip compressed
+                assertThat("Content-Encoding",response.get("Content-Encoding"),containsString(GzipFilter.GZIP));
+            } else
+            {
+                assertThat("Content-Encoding",response.get("Content-Encoding"),not(containsString(GzipFilter.GZIP)));
+            }
+            
+            // Uncompressed content Size
+            ContentMetadata content = tester.getResponseMetadata(response);
+            assertThat("(Uncompressed) Content Length", content.size, is((long)fileSize));
+        }
+        finally
+        {
+            tester.stop();
+        }
+    }
+}    
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/IncludableGzipFilterMinSizeTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/IncludableGzipFilterMinSizeTest.java
index 4dcc693..4f6bd83 100644
--- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/IncludableGzipFilterMinSizeTest.java
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/IncludableGzipFilterMinSizeTest.java
@@ -36,7 +36,7 @@ import org.junit.runners.Parameterized.Parameters;
 /**
  * Perform specific tests on the IncludableGzipFilter's ability to manage
  * minGzipSize initialization parameter.
- * 
+ *
  * @see <a href="Eclipse Bug 366106">http://bugs.eclipse.org/366106</a>
  */
 @RunWith(Parameterized.class)
@@ -48,12 +48,12 @@ public class IncludableGzipFilterMinSizeTest
         String[][] data = new String[][]
                 {
                 { GzipFilter.GZIP },
-                { GzipFilter.DEFLATE } 
+                { GzipFilter.DEFLATE }
                 };
-        
+
         return Arrays.asList(data);
     }
-    
+
     public IncludableGzipFilterMinSizeTest(String compressionType)
     {
         this.compressionType = compressionType;
@@ -69,7 +69,7 @@ public class IncludableGzipFilterMinSizeTest
     public void testUnderMinSize() throws Exception
     {
         GzipTester tester = new GzipTester(testdir, compressionType);
-        // Use IncludableGzipFilter 
+        // Use IncludableGzipFilter
         tester.setGzipFilterClass(IncludableGzipFilter.class);
 
         FilterHolder holder = tester.setContentServlet(testServlet);
@@ -90,16 +90,16 @@ public class IncludableGzipFilterMinSizeTest
             tester.stop();
         }
     }
-    
+
     @Test
     public void testOverMinSize() throws Exception
     {
         GzipTester tester = new GzipTester(testdir, compressionType);
-        // Use IncludableGzipFilter 
+        // Use IncludableGzipFilter
         tester.setGzipFilterClass(IncludableGzipFilter.class);
 
         FilterHolder holder = tester.setContentServlet(testServlet);
-        holder.setInitParameter("mimeTypes","application/soap+xml,text/javascript,application/x-javascript");
+        holder.setInitParameter("mimeTypes","application/soap+xml,text/javascript,application/javascript");
         holder.setInitParameter("minGzipSize", "2048");
         holder.setInitParameter("uncheckedPrintWriter","true");
 
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/IncludableGzipFilterTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/IncludableGzipFilterTest.java
index 8829b9b..17b941d 100644
--- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/IncludableGzipFilterTest.java
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/IncludableGzipFilterTest.java
@@ -19,7 +19,6 @@
 package org.eclipse.jetty.servlets;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
 
 import java.io.BufferedOutputStream;
 import java.io.ByteArrayInputStream;
@@ -27,6 +26,8 @@ import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.zip.GZIPInputStream;
@@ -35,11 +36,11 @@ import java.util.zip.InflaterInputStream;
 
 import javax.servlet.http.HttpServletResponse;
 
-import org.eclipse.jetty.io.ByteArrayBuffer;
+import org.eclipse.jetty.http.HttpTester;
 import org.eclipse.jetty.servlet.FilterHolder;
-import org.eclipse.jetty.testing.HttpTester;
-import org.eclipse.jetty.testing.ServletTester;
+import org.eclipse.jetty.servlet.ServletTester;
 import org.eclipse.jetty.toolchain.test.TestingDir;
+import org.eclipse.jetty.util.BufferUtil;
 import org.eclipse.jetty.util.IO;
 import org.junit.After;
 import org.junit.Before;
@@ -58,15 +59,15 @@ public class IncludableGzipFilterTest
         String[][] data = new String[][]
                 {
                 { GzipFilter.GZIP },
-                { GzipFilter.DEFLATE } 
+                { GzipFilter.DEFLATE }
                 };
-        
+
         return Arrays.asList(data);
     }
-    
+
     @Rule
     public TestingDir testdir = new TestingDir();
-    
+
     private static String __content =
         "Lorem ipsum dolor sit amet, consectetur adipiscing elit. In quis felis nunc. "+
         "Quisque suscipit mauris et ante auctor ornare rhoncus lacus aliquet. Pellentesque "+
@@ -83,28 +84,28 @@ public class IncludableGzipFilterTest
 
     private ServletTester tester;
     private String compressionType;
-    
+
     public IncludableGzipFilterTest(String compressionType)
     {
         this.compressionType = compressionType;
     }
-    
+
     @Before
     public void setUp() throws Exception
     {
         testdir.ensureEmpty();
 
         File testFile = testdir.getFile("file.txt");
-        BufferedOutputStream testOut = new BufferedOutputStream(new FileOutputStream(testFile));
-        ByteArrayInputStream testIn = new ByteArrayInputStream(__content.getBytes("ISO8859_1"));
-        IO.copy(testIn,testOut);
-        testOut.close();
-        
-        tester=new ServletTester();
-        tester.setContextPath("/context");
-        tester.setResourceBase(testdir.getDir().getCanonicalPath());
-        tester.addServlet(org.eclipse.jetty.servlet.DefaultServlet.class, "/");
-        FilterHolder holder = tester.addFilter(IncludableGzipFilter.class,"/*",null);
+        try (OutputStream testOut = new BufferedOutputStream(new FileOutputStream(testFile)))
+        {
+            ByteArrayInputStream testIn = new ByteArrayInputStream(__content.getBytes("ISO8859_1"));
+            IO.copy(testIn,testOut);
+        }
+
+        tester=new ServletTester("/context");
+        tester.getContext().setResourceBase(testdir.getDir().getCanonicalPath());
+        tester.getContext().addServlet(org.eclipse.jetty.servlet.DefaultServlet.class, "/");
+        FilterHolder holder = tester.getContext().addFilter(IncludableGzipFilter.class,"/*",null);
         holder.setInitParameter("mimeTypes","text/plain");
         tester.start();
     }
@@ -120,23 +121,19 @@ public class IncludableGzipFilterTest
     public void testGzipFilter() throws Exception
     {
         // generated and parsed test
-        HttpTester request = new HttpTester();
-        HttpTester response = new HttpTester();
-
-        request.setMethod("GET");
-        request.setVersion("HTTP/1.0");
-        request.setHeader("Host","tester");
-        request.setHeader("accept-encoding", compressionType);
-        request.setURI("/context/file.txt");
-        
-        ByteArrayBuffer reqsBuff = new ByteArrayBuffer(request.generate().getBytes());
-        ByteArrayBuffer respBuff = tester.getResponses(reqsBuff);
-        response.parse(respBuff.asArray());
-                
-        assertTrue(response.getMethod()==null);
-        assertTrue(response.getHeader("Content-Encoding").equalsIgnoreCase(compressionType));
+
+        ByteBuffer request=BufferUtil.toBuffer(
+            "GET /context/file.txt HTTP/1.0\r\n"+
+            "Host: tester\r\n"+
+            "Accept-Encoding: "+compressionType+"\r\n"+
+            "\r\n");
+
+
+        HttpTester.Response response=HttpTester.parseResponse(tester.getResponses(request));
+
         assertEquals(HttpServletResponse.SC_OK,response.getStatus());
-        
+        assertEquals(compressionType,response.get("Content-Encoding"));
+
         InputStream testIn = null;
         ByteArrayInputStream compressedResponseStream = new ByteArrayInputStream(response.getContentBytes());
         if (compressionType.equals(GzipFilter.GZIP))
@@ -149,7 +146,7 @@ public class IncludableGzipFilterTest
         }
         ByteArrayOutputStream testOut = new ByteArrayOutputStream();
         IO.copy(testIn,testOut);
-        
+
         assertEquals(__content, testOut.toString("ISO8859_1"));
     }
 }
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/MultipartFilterTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/MultipartFilterTest.java
index 666dc26..91426fc 100644
--- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/MultipartFilterTest.java
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/MultipartFilterTest.java
@@ -18,19 +18,22 @@
 
 package org.eclipse.jetty.servlets;
 
-import static org.junit.Assert.*;
-import static org.hamcrest.Matchers.*;
+
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
 
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
-import java.io.OutputStream;
 import java.io.PrintWriter;
-import java.net.Socket;
-import java.net.URL;
+import java.nio.charset.StandardCharsets;
 import java.util.EnumSet;
-import java.util.Enumeration;
 import java.util.Map;
 
 import javax.servlet.DispatcherType;
@@ -39,13 +42,10 @@ import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import org.eclipse.jetty.io.ByteArrayBuffer;
+import org.eclipse.jetty.http.HttpTester;
 import org.eclipse.jetty.servlet.FilterHolder;
-import org.eclipse.jetty.servlet.FilterMapping;
-import org.eclipse.jetty.testing.HttpTester;
-import org.eclipse.jetty.testing.ServletTester;
+import org.eclipse.jetty.servlet.ServletTester;
 import org.eclipse.jetty.util.IO;
-import org.eclipse.jetty.util.StringUtil;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -55,18 +55,30 @@ public class MultipartFilterTest
     private File _dir;
     private ServletTester tester;
 
-    
+
     public static class BoundaryServlet extends TestServlet
     {
         @Override
         protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
         {
-            assertNotNull(req.getParameter("fileName"));
-            assertEquals(getServletContext().getAttribute("fileName"), req.getParameter("fileName"));
-            assertNotNull(req.getParameter("desc"));
-            assertEquals(getServletContext().getAttribute("desc"), req.getParameter("desc"));
-            assertNotNull(req.getParameter("title"));
-            assertEquals(getServletContext().getAttribute("title"), req.getParameter("title"));
+            //we have configured the multipart filter to always store to disk (file size threshold == 1)
+            //but fileName has no filename param, so only the attribute should be set
+            assertNull(req.getParameter("fileName"));
+            assertNotNull(req.getAttribute("fileName"));
+            File f = (File)req.getAttribute("fileName");
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            IO.copy(new FileInputStream(f), baos);
+            assertEquals(getServletContext().getAttribute("fileName"), baos.toString());
+            assertNull(req.getParameter("desc"));
+            assertNotNull(req.getAttribute("desc"));
+            baos = new ByteArrayOutputStream();
+            IO.copy(new FileInputStream((File)req.getAttribute("desc")), baos);
+            assertEquals(getServletContext().getAttribute("desc"), baos.toString());
+            assertNull(req.getParameter("title"));
+            assertNotNull(req.getAttribute("title"));
+            baos = new ByteArrayOutputStream();
+            IO.copy(new FileInputStream((File)req.getAttribute("title")), baos);
+            assertEquals(getServletContext().getAttribute("title"), baos.toString());
             super.doPost(req, resp);
         }
     }
@@ -78,15 +90,16 @@ public class MultipartFilterTest
         protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
         {
             assertNotNull(req.getParameter("fileup"));
+            System.err.println("Fileup="+req.getParameter("fileup"));
             assertNotNull(req.getParameter("fileup"+MultiPartFilter.CONTENT_TYPE_SUFFIX));
             assertEquals(req.getParameter("fileup"+MultiPartFilter.CONTENT_TYPE_SUFFIX), "application/octet-stream");
             super.doPost(req, resp);
         }
-        
+
     }
-    
 
-    
+
+
     @Before
     public void setUp() throws Exception
     {
@@ -96,13 +109,13 @@ public class MultipartFilterTest
         _dir.deleteOnExit();
         assertTrue(_dir.isDirectory());
 
-        tester=new ServletTester();
-        tester.setContextPath("/context");
-        tester.setResourceBase(_dir.getCanonicalPath());
-        tester.addServlet(TestServlet.class, "/");
-        tester.setAttribute("javax.servlet.context.tempdir", _dir);
-        FilterHolder multipartFilter = tester.addFilter(MultiPartFilter.class,"/*", EnumSet.of(DispatcherType.REQUEST));
+        tester=new ServletTester("/context");
+        tester.getContext().setResourceBase(_dir.getCanonicalPath());
+        tester.getContext().addServlet(TestServlet.class, "/");
+        tester.getContext().setAttribute("javax.servlet.context.tempdir", _dir);
+        FilterHolder multipartFilter = tester.getContext().addFilter(MultiPartFilter.class,"/*", EnumSet.of(DispatcherType.REQUEST));
         multipartFilter.setInitParameter("deleteFiles", "true");
+        multipartFilter.setInitParameter("fileOutputBuffer", "1"); //write a file if  there's more than 1 byte content
         tester.start();
     }
 
@@ -110,99 +123,66 @@ public class MultipartFilterTest
     public void tearDown() throws Exception
     {
         tester.stop();
+        tester=null;
     }
 
     @Test
     public void testBadPost() throws Exception
     {
         // generated and parsed test
-        HttpTester request = new HttpTester();
-        HttpTester response = new HttpTester();
+        HttpTester.Request request = HttpTester.newRequest();
+        HttpTester.Response response;
 
         // test GET
         request.setMethod("POST");
         request.setVersion("HTTP/1.0");
         request.setHeader("Host","tester");
         request.setURI("/context/dump");
-        
+
         String boundary="XyXyXy";
         request.setHeader("Content-Type","multipart/form-data; boundary="+boundary);
-        
-        
+
+
         String content = "--" + boundary + "\r\n"+
         "Content-Disposition: form-data; name=\"fileup\"; filename=\"test.upload\"\r\n"+
         "Content-Type: application/octet-stream\r\n\r\n"+
         "How now brown cow."+
         "\r\n--" + boundary + "-\r\n\r\n";
-        
+
         request.setContent(content);
-        
-        
-        response.parse(tester.getResponses(request.generate()));
-        assertTrue(response.getMethod()==null);
+
+
+        response = HttpTester.parseResponse(tester.getResponses(request.generate()));
         assertEquals(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,response.getStatus());
     }
-    
+
 
     @Test
     public void testPost() throws Exception
     {
         // generated and parsed test
-        HttpTester request = new HttpTester();
-        HttpTester response = new HttpTester();
+        HttpTester.Request request = HttpTester.newRequest();
+        HttpTester.Response response;
 
         // test GET
         request.setMethod("POST");
         request.setVersion("HTTP/1.0");
         request.setHeader("Host","tester");
         request.setURI("/context/dump");
-        
+
         String boundary="XyXyXy";
         request.setHeader("Content-Type","multipart/form-data; boundary=\""+boundary+"\"");
-        
-        
-        String content = "--" + boundary + "\r\n"+
-        "Content-Disposition: form-data; name=\"fileup\"; filename=\"test.upload\"\r\n"+
-        "Content-Type: application/octet-stream\r\n\r\n"+
-        "How now brown cow."+
-        "\r\n--" + boundary + "--\r\n\r\n";
-        
-        request.setContent(content);
-        
-        response.parse(tester.getResponses(request.generate()));
-        assertTrue(response.getMethod()==null);
-        assertEquals(HttpServletResponse.SC_OK,response.getStatus());
-        assertTrue(response.getContent().indexOf("brown cow")>=0);
-    }
-    
-    
-    @Test
-    public void testContentTypeWithCharset() throws Exception
-    {
-        // generated and parsed test
-        HttpTester request = new HttpTester();
-        HttpTester response = new HttpTester();
 
-        // test GET
-        request.setMethod("POST");
-        request.setVersion("HTTP/1.0");
-        request.setHeader("Host","tester");
-        request.setURI("/context/dump");
-        
-        String boundary="XyXyXy";
-        request.setHeader("Content-Type","multipart/form-data; boundary=\""+boundary+"\"; charset=ISO-8859-1");
-        
-        
+
         String content = "--" + boundary + "\r\n"+
         "Content-Disposition: form-data; name=\"fileup\"; filename=\"test.upload\"\r\n"+
         "Content-Type: application/octet-stream\r\n\r\n"+
         "How now brown cow."+
         "\r\n--" + boundary + "--\r\n\r\n";
-        
+
         request.setContent(content);
-        
-        response.parse(tester.getResponses(request.generate()));
-        assertTrue(response.getMethod()==null);
+
+        response = HttpTester.parseResponse(tester.getResponses(request.generate()));
         assertEquals(HttpServletResponse.SC_OK,response.getStatus());
         assertTrue(response.getContent().indexOf("brown cow")>=0);
     }
@@ -211,29 +191,28 @@ public class MultipartFilterTest
     public void testEncodedPost() throws Exception
     {
         // generated and parsed test
-        HttpTester request = new HttpTester();
-        HttpTester response = new HttpTester();
+        HttpTester.Request request = HttpTester.newRequest();
+        HttpTester.Response response;
 
         // test GET
         request.setMethod("POST");
         request.setVersion("HTTP/1.0");
         request.setHeader("Host","tester");
         request.setURI("/context/dump");
-        
+
         String boundary="XyXyXy";
         request.setHeader("Content-Type","multipart/form-data; boundary="+boundary);
-        
-        
+
+
         String content = "--" + boundary + "\r\n"+
         "Content-Disposition: form-data; name=\"fileup\"; filename=\"Diplomsko Delo Lektorirano KONČNA.doc\"\r\n"+
         "Content-Type: application/octet-stream\r\n\r\n"+
         "How now brown cow."+
         "\r\n--" + boundary + "--\r\n\r\n";
-        
+
         request.setContent(content);
-        
-        response.parse(tester.getResponses(request.generate()));
-        assertTrue(response.getMethod()==null);
+
+        response = HttpTester.parseResponse(tester.getResponses(request.generate()));
         assertEquals(HttpServletResponse.SC_OK,response.getStatus());
         assertTrue(response.getContent().indexOf("brown cow")>=0);
     }
@@ -242,9 +221,8 @@ public class MultipartFilterTest
     public void testBadlyEncodedFilename() throws Exception
     {
         // generated and parsed test
-        HttpTester request = new HttpTester();
-        HttpTester response = new HttpTester();
-
+        HttpTester.Request request = HttpTester.newRequest();
+        HttpTester.Response response;
         // test GET
         request.setMethod("POST");
         request.setVersion("HTTP/1.0");
@@ -263,13 +241,10 @@ public class MultipartFilterTest
         
         request.setContent(content);
         
-        response.parse(tester.getResponses(request.generate()));
+        response = HttpTester.parseResponse(tester.getResponses(request.generate()));
         
         //System.out.printf("Content: [%s]%n", response.getContent());
-
-        assertThat(response.getMethod(), nullValue());
         assertThat(response.getStatus(), is(HttpServletResponse.SC_OK));
-        
         assertThat(response.getContent(), containsString("Filename [Taken on Aug 22 \\ 2012.jpg]"));
         assertThat(response.getContent(), containsString("How now brown cow."));
     }
@@ -278,9 +253,8 @@ public class MultipartFilterTest
     public void testBadlyEncodedMSFilename() throws Exception
     {
         // generated and parsed test
-        HttpTester request = new HttpTester();
-        HttpTester response = new HttpTester();
-
+        HttpTester.Request request = HttpTester.newRequest();
+        HttpTester.Response response;
         // test GET
         request.setMethod("POST");
         request.setVersion("HTTP/1.0");
@@ -299,13 +273,11 @@ public class MultipartFilterTest
         
         request.setContent(content);
         
-        response.parse(tester.getResponses(request.generate()));
+        response = HttpTester.parseResponse(tester.getResponses(request.generate()));
         
         //System.out.printf("Content: [%s]%n", response.getContent());
 
-        assertThat(response.getMethod(), nullValue());
-        assertThat(response.getStatus(), is(HttpServletResponse.SC_OK));
-        
+        assertThat(response.getStatus(), is(HttpServletResponse.SC_OK));       
         assertThat(response.getContent(), containsString("Filename [c:\\this\\really\\is\\some\\path\\to\\a\\file.txt]"));
         assertThat(response.getContent(), containsString("How now brown cow.")); 
     }
@@ -314,9 +286,8 @@ public class MultipartFilterTest
     public void testCorrectlyEncodedMSFilename() throws Exception
     {
         // generated and parsed test
-        HttpTester request = new HttpTester();
-        HttpTester response = new HttpTester();
-
+        HttpTester.Request request = HttpTester.newRequest();
+        HttpTester.Response response;
         // test GET
         request.setMethod("POST");
         request.setVersion("HTTP/1.0");
@@ -335,13 +306,10 @@ public class MultipartFilterTest
         
         request.setContent(content);
         
-        response.parse(tester.getResponses(request.generate()));
+        response = HttpTester.parseResponse(tester.getResponses(request.generate()));
         
         //System.out.printf("Content: [%s]%n", response.getContent());
-
-        assertThat(response.getMethod(), nullValue());
-        assertThat(response.getStatus(), is(HttpServletResponse.SC_OK));
-        
+        assertThat(response.getStatus(), is(HttpServletResponse.SC_OK));        
         assertThat(response.getContent(), containsString("Filename [c:\\this\\really\\is\\some\\path\\to\\a\\file.txt]"));
         assertThat(response.getContent(), containsString("How now brown cow.")); 
     }
@@ -353,18 +321,18 @@ public class MultipartFilterTest
     @Test
     public void testPostWithContentTransferEncodingBase64() throws Exception {
         // generated and parsed test
-        HttpTester request = new HttpTester();
-        HttpTester response = new HttpTester();
+        HttpTester.Request request = HttpTester.newRequest();
+        HttpTester.Response response;
 
         // test GET
         request.setMethod("POST");
         request.setVersion("HTTP/1.0");
         request.setHeader("Host","tester");
         request.setURI("/context/dump");
-        
+
         String boundary="XyXyXy";
         request.setHeader("Content-Type","multipart/form-data; boundary="+boundary);
-        
+
         // part content is "How now brown cow." run through a base64 encoder
         String content = "--" + boundary + "\r\n"+
         "Content-Disposition: form-data; name=\"fileup\"; filename=\"test.upload\"\r\n"+
@@ -372,11 +340,10 @@ public class MultipartFilterTest
         "Content-Type: application/octet-stream\r\n\r\n"+
         "SG93IG5vdyBicm93biBjb3cuCg=="+
         "\r\n--" + boundary + "--\r\n\r\n";
-        
+
         request.setContent(content);
-        
-        response.parse(tester.getResponses(request.generate()));
-        assertTrue(response.getMethod()==null);
+
+        response = HttpTester.parseResponse(tester.getResponses(request.generate()));
         assertEquals(HttpServletResponse.SC_OK,response.getStatus());
         assertTrue(response.getContent().indexOf("brown cow")>=0);
     }
@@ -385,20 +352,21 @@ public class MultipartFilterTest
      * Test multipart with parts encoded in quoted-printable (RFC1521 section 5)
      */
     @Test
-    public void testPostWithContentTransferEncodingQuotedPrintable() throws Exception {
+    public void testPostWithContentTransferEncodingQuotedPrintable() throws Exception
+    {
         // generated and parsed test
-        HttpTester request = new HttpTester();
-        HttpTester response = new HttpTester();
+        HttpTester.Request request = HttpTester.newRequest();
+        HttpTester.Response response;
 
         // test GET
         request.setMethod("POST");
         request.setVersion("HTTP/1.0");
         request.setHeader("Host","tester");
         request.setURI("/context/dump");
-        
+
         String boundary="XyXyXy";
         request.setHeader("Content-Type","multipart/form-data; boundary="+boundary);
-        
+
         /*
          * Part content is "How now brown cow." run through Apache Commons Codec
          * quoted printable encoding.
@@ -409,11 +377,10 @@ public class MultipartFilterTest
         "Content-Type: application/octet-stream\r\n\r\n"+
         "=48=6F=77=20=6E=6F=77=20=62=72=6F=77=6E=20=63=6F=77=2E"+
         "\r\n--" + boundary + "--\r\n\r\n";
-        
+
         request.setContent(content);
-        
-        response.parse(tester.getResponses(request.generate()));
-        assertTrue(response.getMethod()==null);
+
+        response = HttpTester.parseResponse(tester.getResponses(request.generate()));
         assertEquals(HttpServletResponse.SC_OK,response.getStatus());
         assertTrue(response.getContent().indexOf("brown cow")>=0);
     }
@@ -423,19 +390,20 @@ public class MultipartFilterTest
     public void testNoBoundary() throws Exception
     {
         // generated and parsed test
-        HttpTester request = new HttpTester();
-        HttpTester response = new HttpTester();
-        tester.addServlet(BoundaryServlet.class,"/testb");
+        HttpTester.Request request = HttpTester.newRequest();
+        HttpTester.Response response;
+
         tester.setAttribute("fileName", "abc");
         tester.setAttribute("desc", "123");
         tester.setAttribute("title", "ttt");
         request.setMethod("POST");
         request.setVersion("HTTP/1.0");
         request.setHeader("Host","tester");
-        request.setURI("/context/testb");
+        request.setURI("/context/dump");
 
         request.setHeader("Content-Type","multipart/form-data");
 
+        // generated and parsed test
         String content = "--\r\n"+
         "Content-Disposition: form-data; name=\"fileName\"\r\n"+
         "Content-Type: text/plain; charset=US-ASCII\r\n"+
@@ -463,8 +431,7 @@ public class MultipartFilterTest
         "----\r\n";
         request.setContent(content);
 
-        response.parse(tester.getResponses(request.generate()));
-        assertTrue(response.getMethod()==null);
+        response = HttpTester.parseResponse(tester.getResponses(request.generate()));
         assertEquals(HttpServletResponse.SC_OK,response.getStatus());
     }
     
@@ -474,8 +441,10 @@ public class MultipartFilterTest
     { 
         String boundary="XyXyXy";
         // generated and parsed test
-        HttpTester request = new HttpTester();
-        HttpTester response = new HttpTester();
+        HttpTester.Request request = HttpTester.newRequest();
+        HttpTester.Response response;
+        
+        
         tester.addServlet(BoundaryServlet.class,"/testb");
         tester.setAttribute("fileName", "abc");
         tester.setAttribute("desc", "123");
@@ -513,8 +482,7 @@ public class MultipartFilterTest
         "--XyXyXy--\n";
         request.setContent(content);
 
-        response.parse(tester.getResponses(request.generate()));
-        assertTrue(response.getMethod()==null);
+        response = HttpTester.parseResponse(tester.getResponses(request.generate()));
         assertEquals(HttpServletResponse.SC_OK,response.getStatus()); 
     }
     
@@ -524,8 +492,8 @@ public class MultipartFilterTest
     { 
         String boundary="XyXyXy";
         // generated and parsed test
-        HttpTester request = new HttpTester();
-        HttpTester response = new HttpTester();
+        HttpTester.Request request = HttpTester.newRequest();
+        HttpTester.Response response;
         tester.addServlet(BoundaryServlet.class,"/testb");
         tester.setAttribute("fileName", "abc");
         tester.setAttribute("desc", "123");
@@ -563,8 +531,7 @@ public class MultipartFilterTest
         "--XyXyXy--\r";
         request.setContent(content);
 
-        response.parse(tester.getResponses(request.generate()));
-        assertTrue(response.getMethod()==null);
+        response = HttpTester.parseResponse(tester.getResponses(request.generate()));
         assertEquals(HttpServletResponse.SC_OK,response.getStatus()); 
     }
     
@@ -574,8 +541,8 @@ public class MultipartFilterTest
     { 
         String boundary="XyXyXy";
         // generated and parsed test
-        HttpTester request = new HttpTester();
-        HttpTester response = new HttpTester();
+        HttpTester.Request request = HttpTester.newRequest();
+        HttpTester.Response response;
         tester.addServlet(BoundaryServlet.class,"/testb");
         tester.setAttribute("fileName", "\nabc\n");
         tester.setAttribute("desc", "\n123\n");
@@ -616,28 +583,26 @@ public class MultipartFilterTest
         "--XyXyXy--\r";
         request.setContent(content);
 
-        response.parse(tester.getResponses(request.generate()));
-        assertTrue(response.getMethod()==null);
+        response = HttpTester.parseResponse(tester.getResponses(request.generate()));
         assertEquals(HttpServletResponse.SC_OK,response.getStatus()); 
     }
-    
-    
+
     @Test
     public void testNoBody()
     throws Exception
     {
         String boundary="XyXyXy";
         // generated and parsed test
-        HttpTester request = new HttpTester();
-        HttpTester response = new HttpTester();
+        HttpTester.Request request = HttpTester.newRequest();
+        HttpTester.Response response;
+        
         request.setMethod("POST");
         request.setVersion("HTTP/1.0");
         request.setHeader("Host","tester");
         request.setURI("/context/dump");
         request.setHeader("Content-Type","multipart/form-data; boundary="+boundary);
         
-        response.parse(tester.getResponses(request.generate()));
-        assertTrue(response.getMethod()==null);
+        response = HttpTester.parseResponse(tester.getResponses(request.generate()));
         assertEquals(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, response.getStatus());
         assertTrue(response.getContent().indexOf("Missing content")>=0);
     }
@@ -650,8 +615,8 @@ public class MultipartFilterTest
 
         String boundary="XyXyXy";
         // generated and parsed test
-        HttpTester request = new HttpTester();
-        HttpTester response = new HttpTester();
+        HttpTester.Request request = HttpTester.newRequest();
+        HttpTester.Response response;
         request.setMethod("POST");
         request.setVersion("HTTP/1.0");
         request.setHeader("Host","tester");
@@ -659,8 +624,7 @@ public class MultipartFilterTest
         request.setHeader("Content-Type","multipart/form-data; boundary="+boundary);
         request.setContent(whitespace);
         
-        response.parse(tester.getResponses(request.generate()));
-        assertTrue(response.getMethod()==null);
+        response = HttpTester.parseResponse(tester.getResponses(request.generate()));
         assertEquals(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, response.getStatus());
         assertTrue(response.getContent().indexOf("Missing initial")>=0);
     }
@@ -674,8 +638,8 @@ public class MultipartFilterTest
 
         String boundary="XyXyXy";
         // generated and parsed test
-        HttpTester request = new HttpTester();
-        HttpTester response = new HttpTester();
+        HttpTester.Request request = HttpTester.newRequest();
+        HttpTester.Response response;
         request.setMethod("POST");
         request.setVersion("HTTP/1.0");
         request.setHeader("Host","tester");
@@ -683,8 +647,7 @@ public class MultipartFilterTest
         request.setHeader("Content-Type","multipart/form-data; boundary="+boundary);
         request.setContent(whitespace);
         
-        response.parse(tester.getResponses(request.generate()));
-        assertTrue(response.getMethod()==null);
+        response = HttpTester.parseResponse(tester.getResponses(request.generate()));
         assertEquals(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, response.getStatus());
         assertTrue(response.getContent().indexOf("Missing initial")>=0);
     }
@@ -708,8 +671,8 @@ public class MultipartFilterTest
         "--AaB03x--\r\n";
 
         // generated and parsed test
-        HttpTester request = new HttpTester();
-        HttpTester response = new HttpTester();
+        HttpTester.Request request = HttpTester.newRequest();
+        HttpTester.Response response;
         request.setMethod("POST");
         request.setVersion("HTTP/1.0");
         request.setHeader("Host","tester");
@@ -717,12 +680,10 @@ public class MultipartFilterTest
         request.setHeader("Content-Type","multipart/form-data; boundary="+boundary);
         request.setContent(body);
 
-        response.parse(tester.getResponses(request.generate()));
-        assertTrue(response.getMethod()==null);
+        response = HttpTester.parseResponse(tester.getResponses(request.generate()));
         assertEquals(HttpServletResponse.SC_OK, response.getStatus());
         assertTrue(response.getContent().contains("aaaa,bbbbb"));
     }
-
     @Test
     public void testLeadingWhitespaceBodyWithoutCRLF()
     throws Exception
@@ -742,8 +703,8 @@ public class MultipartFilterTest
         "--AaB03x--\r\n";
 
         // generated and parsed test
-        HttpTester request = new HttpTester();
-        HttpTester response = new HttpTester();
+        HttpTester.Request request = HttpTester.newRequest();
+        HttpTester.Response response;
         request.setMethod("POST");
         request.setVersion("HTTP/1.0");
         request.setHeader("Host","tester");
@@ -751,19 +712,48 @@ public class MultipartFilterTest
         request.setHeader("Content-Type","multipart/form-data; boundary="+boundary);
         request.setContent(body);
 
-        response.parse(tester.getResponses(request.generate()));
-        assertTrue(response.getMethod()==null);
+        response = HttpTester.parseResponse(tester.getResponses(request.generate()));
         assertEquals(HttpServletResponse.SC_OK, response.getStatus());
         assertTrue(response.getContent().contains("aaaa,bbbbb"));
     }
     
+    public void testContentTypeWithCharSet() throws Exception
+    {
+        // generated and parsed test
+        HttpTester.Request request = HttpTester.newRequest();
+        HttpTester.Response response;
+
+        // test GET
+        request.setMethod("POST");
+        request.setVersion("HTTP/1.0");
+        request.setHeader("Host","tester");
+        request.setURI("/context/dump");
+
+        String boundary="XyXyXy";
+        request.setHeader("Content-Type","multipart/form-data; boundary=\""+boundary+"\"; charset=ISO-8859-1");
+
+
+        String content = "--" + boundary + "\r\n"+
+        "Content-Disposition: form-data; name=\"fileup\"; filename=\"test.upload\"\r\n"+
+        "Content-Type: application/octet-stream\r\n\r\n"+
+        "How now brown cow."+
+        "\r\n--" + boundary + "--\r\n\r\n";
+
+        request.setContent(content);
+
+        response = HttpTester.parseResponse(tester.getResponses(request.generate()));
+        assertEquals(HttpServletResponse.SC_OK,response.getStatus());
+        assertTrue(response.getContent().indexOf("brown cow")>=0);
+    }
+    
+    
     @Test
     public void testBufferOverflowNoCRLF () throws Exception
     {
         String boundary="XyXyXy";
         // generated and parsed test
-        HttpTester request = new HttpTester();
-        HttpTester response = new HttpTester();
+        HttpTester.Request request = HttpTester.newRequest();
+        HttpTester.Response response;
         tester.addServlet(BoundaryServlet.class,"/testb");
         tester.setAttribute("fileName", "abc");
         tester.setAttribute("desc", "123");
@@ -785,7 +775,7 @@ public class MultipartFilterTest
         }
         request.setContent(baos.toString());
 
-        response.parse(tester.getResponses(request.generate()));
+        response = HttpTester.parseResponse(tester.getResponses(request.generate()));
         assertTrue(response.getContent().contains("Buffer size exceeded"));
         assertEquals(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, response.getStatus());
     }
@@ -802,33 +792,33 @@ public class MultipartFilterTest
             String[] content = req.getParameterMap().get("\"strup\"Content-Type: application/octet-stream");           
             assertThat (content[0], containsString("How now brown cow."));
             super.doPost(req, resp);
-        } 
+        }
     }
-    
-    /** 
-     * Validate that the getParameterMap() call is correctly unencoding the parameters in the 
+
+    /**
+     * Validate that the getParameterMap() call is correctly unencoding the parameters in the
      * map that it returns.
      * @throws Exception
      */
     @Test
     public void testParameterMap() throws Exception
     {
+        tester.addServlet(TestServletParameterMap.class,"/test2");
+
         // generated and parsed test
-        HttpTester request = new HttpTester();
-        HttpTester response = new HttpTester();
+        HttpTester.Request request = HttpTester.newRequest();
+        HttpTester.Response response;
 
-        tester.addServlet(TestServletParameterMap.class,"/test2");
-        
         // test GET
         request.setMethod("POST");
         request.setVersion("HTTP/1.0");
         request.setHeader("Host","tester");
-        request.setURI("/context/test2");
-        
+        request.setURI("/context/dump");
+
         String boundary="XyXyXy";
         request.setHeader("Content-Type","multipart/form-data; boundary="+boundary);
-        
-        
+
+
         String content = "--" + boundary + "\r\n"+
         "Content-Disposition: form-data; name=\"fileup\"; filename=\"Diplomsko Delo Lektorirano KONČNA.doc\"\r\n"+
         "Content-Type: application/octet-stream\r\n\r\n"+
@@ -838,16 +828,14 @@ public class MultipartFilterTest
         "Content-Type: application/octet-stream\r\n\r\n"+
         "How now brown cow."+
         "\r\n--" + boundary + "--\r\n\r\n";
-        
+
         request.setContent(content);
-        
-        response.parse(tester.getResponses(request.generate()));
-        assertTrue(response.getMethod()==null);
+
+        response = HttpTester.parseResponse(tester.getResponses(request.generate()));
         assertEquals(HttpServletResponse.SC_OK,response.getStatus());
         assertTrue(response.getContent().indexOf("brown cow")>=0);
     }
-    
-    
+
     public static class TestServletCharSet extends HttpServlet
     {
 
@@ -875,18 +863,15 @@ public class MultipartFilterTest
     throws Exception
     {
         // generated and parsed test
-        HttpTester request = new HttpTester() {
-            
-        };
-        HttpTester response = new HttpTester();
-
-        tester.addServlet(TestServletCharSet.class,"/test2");
+        HttpTester.Request request = HttpTester.newRequest();
+        HttpTester.Response response;
+        tester.addServlet(TestServletCharSet.class,"/test3");
         
         // test GET
         request.setMethod("POST");
         request.setVersion("HTTP/1.0");
         request.setHeader("Host","tester");
-        request.setURI("/context/test2");
+        request.setURI("/context/test3");
         
         String boundary="XyXyXy";
         request.setHeader("Content-Type","multipart/form-data; boundary="+boundary);
@@ -896,17 +881,14 @@ public class MultipartFilterTest
         baos.write(("--" + boundary + "\r\n"+
                 "Content-Disposition: form-data; name=\"ttt\"\r\n"+
                 "Content-Type: application/octet-stream; charset=UTF-8\r\n\r\n").getBytes());
-        baos.write("ttt\u01FCzzz".getBytes(StringUtil.__UTF8));
+        baos.write("ttt\u01FCzzz".getBytes(StandardCharsets.UTF_8));
         baos.write(("\r\n--" + boundary + "--\r\n\r\n").getBytes());
   
-        request.setContentBytes(baos.toByteArray());   
-
-        response.parse(tester.getResponses(new ByteArrayBuffer(request.generate().getBytes(StringUtil.__UTF8))).toString());
+        
+        request.setContent(baos.toByteArray());   
+        response = HttpTester.parseResponse(tester.getResponses(request.generate()));
     }
 
-    
-    
-    
     public static class DumpServlet extends HttpServlet
     {
         private static final long serialVersionUID = 201012011130L;
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/PipelineHelper.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/PipelineHelper.java
deleted file mode 100644
index 34a1ec2..0000000
--- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/PipelineHelper.java
+++ /dev/null
@@ -1,306 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.servlets;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.InetSocketAddress;
-import java.net.Socket;
-import java.net.SocketAddress;
-import java.net.URI;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.util.log.StdErrLog;
-import org.junit.Assert;
-
-import static org.hamcrest.Matchers.not;
-
-public class PipelineHelper
-{
-    private static final Logger LOG = Log.getLogger(PipelineHelper.class);
-    private URI uri;
-    private SocketAddress endpoint;
-    private Socket socket;
-    private OutputStream outputStream;
-    private InputStream inputStream;
-    private String encodingHeader;
-
-    public PipelineHelper(URI uri, String encodingHeader)
-    {
-        if (LOG instanceof StdErrLog)
-        {
-            ((StdErrLog)LOG).setLevel(StdErrLog.LEVEL_DEBUG);
-        }
-        this.uri = uri;
-        this.endpoint = new InetSocketAddress(uri.getHost(),uri.getPort());
-        this.encodingHeader = encodingHeader;
-    }
-
-    /**
-     * Open the Socket to the destination endpoint and
-     *
-     * @return the open java Socket.
-     * @throws IOException
-     */
-    public Socket connect() throws IOException
-    {
-        LOG.info("Connecting to endpoint: " + endpoint);
-        socket = new Socket();
-        socket.setTcpNoDelay(true);
-        socket.connect(endpoint,1000);
-
-        outputStream = socket.getOutputStream();
-        inputStream = socket.getInputStream();
-
-        return socket;
-    }
-
-    /**
-     * Issue a HTTP/1.1 GET request with Connection:keep-alive set.
-     *
-     * @param path
-     *            the path to GET
-     * @param acceptGzipped
-     *            to turn on acceptance of GZIP compressed responses
-     * @throws IOException
-     */
-    public void issueGET(String path, boolean acceptGzipped, boolean close) throws IOException
-    {
-        LOG.debug("Issuing GET on " + path);
-        StringBuilder req = new StringBuilder();
-        req.append("GET ").append(uri.resolve(path).getPath()).append(" HTTP/1.1\r\n");
-        req.append("Host: ").append(uri.getHost()).append(":").append(uri.getPort()).append("\r\n");
-        req.append("User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 5_0_1 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A405 Safari/7534.48.3\r\n");
-        req.append("Accept: */*\r\n");
-        req.append("Referer: http://mycompany.com/index.html\r\n");
-        req.append("Accept-Language: en-us\r\n");
-        if (acceptGzipped)
-        {
-            req.append("Accept-Encoding: " + encodingHeader + "\r\n");
-        }
-        req.append("Cookie: JSESSIONID=spqx8v8szylt1336t96vc6mw0\r\n");
-        if ( close )
-        {
-            req.append("Connection: close\r\n");
-        }
-        else
-        {
-            req.append("Connection: keep-alive\r\n");
-        }
-
-        req.append("\r\n");
-
-        LOG.debug("Request:" + req);
-
-        // Send HTTP GET Request
-        byte buf[] = req.toString().getBytes();
-        outputStream.write(buf,0,buf.length);
-        outputStream.flush();
-    }
-
-    public String readResponseHeader() throws IOException
-    {
-        // Read Response Header
-        socket.setSoTimeout(10000);
-
-        LOG.debug("Reading http header");
-        StringBuilder response = new StringBuilder();
-        boolean foundEnd = false;
-        String line;
-        while (!foundEnd)
-        {
-            line = readLine();
-            // System.out.printf("RESP: \"%s\"%n",line);
-            if (line.length() == 0)
-            {
-                foundEnd = true;
-                LOG.debug("Got full http response header");
-            }
-            else
-            {
-                response.append(line).append("\r\n");
-            }
-        }
-
-        return response.toString();
-    }
-
-    public String readLine() throws IOException
-    {
-        StringBuilder line = new StringBuilder();
-        boolean foundCR = false;
-        boolean foundLF = false;
-        int b;
-        while (!(foundCR && foundLF))
-        {
-            b = inputStream.read();
-            Assert.assertThat("Should not have hit EOL (yet) during chunk size read",b,not(-1));
-            if (b == 0x0D)
-            {
-                foundCR = true;
-            }
-            else if (b == 0x0A)
-            {
-                foundLF = true;
-            }
-            else
-            {
-                foundCR = false;
-                foundLF = false;
-                line.append((char)b);
-            }
-        }
-        return line.toString();
-    }
-
-    public long readChunkSize() throws IOException
-    {
-        StringBuilder chunkSize = new StringBuilder();
-        String validHex = "0123456789ABCDEF";
-        boolean foundCR = false;
-        boolean foundLF = false;
-        int b;
-        while (!(foundCR && foundLF))
-        {
-            b = inputStream.read();
-            Assert.assertThat("Should not have hit EOL (yet) during chunk size read",b,not(-1));
-            if (b == 0x0D)
-            {
-                foundCR = true;
-            }
-            else if (b == 0x0A)
-            {
-                foundLF = true;
-            }
-            else
-            {
-                foundCR = false;
-                foundLF = false;
-                // Must be valid char
-                char c = (char)b;
-                if (validHex.indexOf(c) >= 0)
-                {
-                    chunkSize.append(c);
-                }
-                else
-                {
-                    Assert.fail(String.format("Encountered invalid chunk size byte 0x%X",b));
-                }
-            }
-        }
-        return Long.parseLong(chunkSize.toString(),16);
-    }
-
-    public int readBody(OutputStream stream, int size) throws IOException
-    {
-        int left = size;
-        while (left > 0)
-        {
-            int val = inputStream.read();
-            try
-            {
-                if (left % 10 == 0)
-                    Thread.sleep(1);
-            }
-            catch (InterruptedException e)
-            {
-                e.printStackTrace();
-            }
-            if (val == (-1))
-            {
-                Assert.fail(String.format("Encountered an early EOL (expected another %,d bytes)",left));
-            }
-            stream.write(val);
-            left--;
-        }
-        return size - left;
-    }
-
-    public byte[] readResponseBody(int size) throws IOException
-    {
-        byte partial[] = new byte[size];
-        int readBytes = 0;
-        int bytesLeft = size;
-        while (readBytes < size)
-        {
-            int len = inputStream.read(partial,readBytes,bytesLeft);
-            Assert.assertThat("Read should not have hit EOL yet",len,not(-1));
-            System.out.printf("Read %,d bytes%n",len);
-            if (len > 0)
-            {
-                readBytes += len;
-                bytesLeft -= len;
-            }
-        }
-        return partial;
-    }
-
-    public OutputStream getOutputStream()
-    {
-        return outputStream;
-    }
-
-    public InputStream getInputStream()
-    {
-        return inputStream;
-    }
-
-    public SocketAddress getEndpoint()
-    {
-        return endpoint;
-    }
-
-    public Socket getSocket()
-    {
-        return socket;
-    }
-
-    public void disconnect() throws IOException
-    {
-        LOG.debug("disconnect");
-        socket.close();
-    }
-
-    public int getContentLength(String respHeader)
-    {
-        Pattern pat = Pattern.compile("Content-Length: ([0-9]*)",Pattern.CASE_INSENSITIVE);
-        Matcher mat = pat.matcher(respHeader);
-        if (mat.find())
-        {
-            try
-            {
-                return Integer.parseInt(mat.group(1));
-            }
-            catch (NumberFormatException e)
-            {
-                return -1;
-            }
-        }
-        else
-        {
-            // Undefined content length
-            return -1;
-        }
-    }
-
-}
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/ProxyServletTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/ProxyServletTest.java
deleted file mode 100644
index 31af901..0000000
--- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/ProxyServletTest.java
+++ /dev/null
@@ -1,293 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.servlets;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.net.MalformedURLException;
-import java.net.Socket;
-import java.util.Arrays;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
-import javax.servlet.ServletException;
-import javax.servlet.ServletOutputStream;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import junit.framework.Assert;
-import org.eclipse.jetty.client.ContentExchange;
-import org.eclipse.jetty.client.HttpClient;
-import org.eclipse.jetty.client.HttpExchange;
-import org.eclipse.jetty.http.HttpStatus;
-import org.eclipse.jetty.http.HttpURI;
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.server.Connector;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.handler.HandlerCollection;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
-import org.eclipse.jetty.servlet.ServletContextHandler;
-import org.eclipse.jetty.servlet.ServletHolder;
-import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
-import org.eclipse.jetty.util.IO;
-import org.eclipse.jetty.util.StringUtil;
-import org.junit.After;
-import org.junit.Test;
-
-import static org.hamcrest.Matchers.equalTo;
-import static org.junit.Assert.assertThat;
-
-public class ProxyServletTest
-{
-    private Server _server;
-    private Connector _connector;
-    private HttpClient _client;
-
-    public void init(HttpServlet servlet) throws Exception
-    {
-        _server = new Server();
-
-        _connector = new SelectChannelConnector();
-        _server.addConnector(_connector);
-
-        HandlerCollection handlers = new HandlerCollection();
-        _server.setHandler(handlers);
-
-        ServletContextHandler proxyCtx = new ServletContextHandler(handlers, "/proxy", ServletContextHandler.NO_SESSIONS);
-        ServletHolder proxyServletHolder = new ServletHolder(new ProxyServlet()
-        {
-            @Override
-            protected HttpURI proxyHttpURI(String scheme, String serverName, int serverPort, String uri) throws MalformedURLException
-            {
-                // Proxies any call to "/proxy" to "/"
-                return new HttpURI(scheme + "://" + serverName + ":" + serverPort + uri.substring("/proxy".length()));
-            }
-        });
-        proxyServletHolder.setInitParameter("timeout", String.valueOf(5 * 60 * 1000L));
-        proxyCtx.addServlet(proxyServletHolder, "/*");
-
-        ServletContextHandler appCtx = new ServletContextHandler(handlers, "/", ServletContextHandler.SESSIONS);
-        ServletHolder appServletHolder = new ServletHolder(servlet);
-        appCtx.addServlet(appServletHolder, "/*");
-
-        handlers.addHandler(proxyCtx);
-        handlers.addHandler(appCtx);
-
-        _server.start();
-
-        _client = new HttpClient();
-        _client.start();
-    }
-
-    @After
-    public void destroy() throws Exception
-    {
-        if (_client != null)
-            _client.stop();
-
-        if (_server != null)
-        {
-            _server.stop();
-            _server.join();
-        }
-    }
-
-    @Test
-    public void testXForwardedHostHeader() throws Exception
-    {
-        init(new HttpServlet()
-        {
-            private static final long serialVersionUID = 1L;
-
-            @Override
-            protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
-            {
-                PrintWriter writer = resp.getWriter();
-                writer.write(req.getHeader("X-Forwarded-Host"));
-                writer.flush();
-            }
-        });
-
-        String url = "http://localhost:" + _connector.getLocalPort() + "/proxy/test";
-        ContentExchange exchange = new ContentExchange();
-        exchange.setURL(url);
-        _client.send(exchange);
-        exchange.waitForDone();
-        assertThat("Response expected to contain content of X-Forwarded-Host Header from the request",exchange.getResponseContent(),equalTo("localhost:"
-                + _connector.getLocalPort()));
-    }
-
-    @Test
-    public void testBigDownloadWithSlowReader() throws Exception
-    {
-        // Create a 6 MiB file
-        File targetTestingDir = MavenTestingUtils.getTargetTestingDir();
-        targetTestingDir.mkdir();
-        final File file = File.createTempFile("test_", null, targetTestingDir);
-        file.deleteOnExit();
-        FileOutputStream fos = new FileOutputStream(file);
-        byte[] buffer = new byte[1024];
-        Arrays.fill(buffer, (byte)'X');
-        for (int i = 0; i < 6 * 1024; ++i)
-            fos.write(buffer);
-        fos.close();
-
-        init(new HttpServlet()
-        {
-            private static final long serialVersionUID = 1L;
-
-            @Override
-            protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
-            {
-                FileInputStream fis = new FileInputStream(file);
-                ServletOutputStream output = response.getOutputStream();
-                byte[] buffer = new byte[1024];
-                int read;
-                while ((read = fis.read(buffer)) >= 0)
-                    output.write(buffer, 0, read);
-                fis.close();
-            }
-        });
-
-        String url = "http://localhost:" + _connector.getLocalPort() + "/proxy/test";
-        ContentExchange exchange = new ContentExchange(true)
-        {
-            @Override
-            protected void onResponseContent(Buffer content) throws IOException
-            {
-                try
-                {
-                    // Slow down the reader
-                    TimeUnit.MILLISECONDS.sleep(10);
-                    super.onResponseContent(content);
-                }
-                catch (InterruptedException x)
-                {
-                    throw (IOException)new IOException().initCause(x);
-                }
-            }
-        };
-        exchange.setURL(url);
-        long start = System.nanoTime();
-        _client.send(exchange);
-        Assert.assertEquals(HttpExchange.STATUS_COMPLETED, exchange.waitForDone());
-        long elapsed = System.nanoTime() - start;
-        Assert.assertEquals(HttpStatus.OK_200, exchange.getResponseStatus());
-        Assert.assertEquals(file.length(), exchange.getResponseContentBytes().length);
-        long millis = TimeUnit.NANOSECONDS.toMillis(elapsed);
-        long rate = file.length() / 1024 * 1000 / millis;
-        System.out.printf("download rate = %d KiB/s%n", rate);
-    }
-
-    @Test
-    public void testLessContentThanContentLength() throws Exception {
-        init(new HttpServlet() {
-            @Override
-            protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
-                byte[] message = "tooshort".getBytes("ascii");
-                resp.setContentType("text/plain;charset=ascii");
-                resp.setHeader("Content-Length", Long.toString(message.length+1));
-                resp.getOutputStream().write(message);
-            }
-        });
-
-        final AtomicBoolean excepted = new AtomicBoolean(false);
-
-        ContentExchange exchange = new ContentExchange(true)
-        {
-            @Override
-            protected void onResponseContent(Buffer content) throws IOException
-            {
-                try
-                {
-                    // Slow down the reader
-                    TimeUnit.MILLISECONDS.sleep(10);
-                    super.onResponseContent(content);
-                }
-                catch (InterruptedException x)
-                {
-                    throw (IOException)new IOException().initCause(x);
-                }
-            }
-
-            @Override
-            protected void onException(Throwable x)
-            {              
-                excepted.set(true);
-                super.onException(x);
-            }
-            
-            
-        };
-
-        String url = "http://localhost:" + _connector.getLocalPort() + "/proxy/test";
-        exchange.setURL(url);
-
-        _client.send(exchange);
-        exchange.waitForDone();
-        assertThat(excepted.get(),equalTo(true));
-    }
-    
-    
-    @Test
-    public void testChunkedPut() throws Exception 
-    {
-        init(new HttpServlet() 
-        {
-            @Override
-            protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException 
-            {
-                resp.setContentType("text/plain");
-                String message=IO.toString(req.getInputStream());
-                resp.getOutputStream().print(message);
-            }
-        });
-
-
-        Socket client = new Socket("localhost",_connector.getLocalPort());
-        client.setSoTimeout(1000000);
-        client.getOutputStream().write((
-                "PUT /proxy/test HTTP/1.1\r\n"+
-                "Host: localhost:"+_connector.getLocalPort()+"\r\n"+
-                "Transfer-Encoding: chunked\r\n"+
-                "Connection: close\r\n"+
-                "\r\n"+
-                "A\r\n"+
-                "0123456789\r\n"+
-                "9\r\n"+
-                "ABCDEFGHI\r\n"+
-                "8\r\n"+
-                "JKLMNOPQ\r\n"+
-                "7\r\n"+
-                "RSTUVWX\r\n"+
-                "2\r\n"+
-                "YZ\r\n"+
-                "0\r\n"
-                ).getBytes(StringUtil.__ISO_8859_1));
-                
-            
-        String response=IO.toString(client.getInputStream());
-        Assert.assertTrue(response.contains("200 OK"));
-        Assert.assertTrue(response.contains("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"));
-        
-    }
-}
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/PutFilterTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/PutFilterTest.java
index 8f695ad..dbce361 100644
--- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/PutFilterTest.java
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/PutFilterTest.java
@@ -23,21 +23,23 @@ import static org.junit.Assert.assertTrue;
 
 import java.io.File;
 import java.io.FileInputStream;
+import java.io.InputStream;
 import java.io.OutputStream;
 import java.net.Socket;
 import java.net.URL;
-import java.util.EnumSet;
-import javax.servlet.DispatcherType;
 import java.util.Arrays;
+import java.util.EnumSet;
 import java.util.HashSet;
 import java.util.Locale;
 import java.util.Set;
 
+import javax.servlet.DispatcherType;
 import javax.servlet.http.HttpServletResponse;
 
+import org.eclipse.jetty.http.HttpTester;
 import org.eclipse.jetty.servlet.FilterHolder;
-import org.eclipse.jetty.testing.HttpTester;
-import org.eclipse.jetty.testing.ServletTester;
+import org.eclipse.jetty.servlet.ServletTester;
+import org.eclipse.jetty.util.BufferUtil;
 import org.eclipse.jetty.util.IO;
 import org.junit.After;
 import org.junit.Before;
@@ -57,8 +59,7 @@ public class PutFilterTest
         _dir.deleteOnExit();
         assertTrue(_dir.isDirectory());
 
-        tester=new ServletTester();
-        tester.setContextPath("/context");
+        tester=new ServletTester("/context");
         tester.setResourceBase(_dir.getCanonicalPath());
         tester.addServlet(org.eclipse.jetty.servlet.DefaultServlet.class, "/");
         FilterHolder holder = tester.addFilter(PutFilter.class,"/*",EnumSet.of(DispatcherType.REQUEST));
@@ -80,16 +81,15 @@ public class PutFilterTest
     public void testHandlePut() throws Exception
     {
         // generated and parsed test
-        HttpTester request = new HttpTester();
-        HttpTester response = new HttpTester();
+        HttpTester.Request request = HttpTester.newRequest();
+        HttpTester.Response response;
 
         // test GET
         request.setMethod("GET");
         request.setVersion("HTTP/1.0");
         request.setHeader("Host","tester");
         request.setURI("/context/file.txt");
-        response.parse(tester.getResponses(request.generate()));
-        assertTrue(response.getMethod()==null);
+        response = HttpTester.parseResponse(tester.getResponses(request.generate()));
         assertEquals(HttpServletResponse.SC_NOT_FOUND,response.getStatus());
 
         // test PUT0
@@ -98,8 +98,7 @@ public class PutFilterTest
         request.setHeader("Content-Type","text/plain");
         String data0="Now is the time for all good men to come to the aid of the party";
         request.setContent(data0);
-        response.parse(tester.getResponses(request.generate()));
-        assertTrue(response.getMethod()==null);
+        response = HttpTester.parseResponse(tester.getResponses(request.generate()));
         assertEquals(HttpServletResponse.SC_CREATED,response.getStatus());
 
         File file=new File(_dir,"file.txt");
@@ -111,8 +110,7 @@ public class PutFilterTest
         request.setVersion("HTTP/1.0");
         request.setHeader("Host","tester");
         request.setURI("/context/file.txt");
-        response.parse(tester.getResponses(request.generate()));
-        assertTrue(response.getMethod()==null);
+        response = HttpTester.parseResponse(tester.getResponses(request.generate()));
         assertEquals(HttpServletResponse.SC_OK,response.getStatus());
         assertEquals(data0,response.getContent());
 
@@ -122,8 +120,7 @@ public class PutFilterTest
         request.setHeader("Content-Type","text/plain");
         String data1="How Now BROWN COW!!!!";
         request.setContent(data1);
-        response.parse(tester.getResponses(request.generate()));
-        assertTrue(response.getMethod()==null);
+        response = HttpTester.parseResponse(tester.getResponses(request.generate()));
         assertEquals(HttpServletResponse.SC_OK,response.getStatus());
 
         file=new File(_dir,"file.txt");
@@ -136,8 +133,8 @@ public class PutFilterTest
         request.setHeader("Content-Type","text/plain");
         String data2="Blah blah blah Blah blah";
         request.setContent(data2);
-        String to_send = request.generate();
-        URL url = new URL(tester.createSocketConnector(true));
+        String to_send = BufferUtil.toString(request.generate());
+        URL url = new URL(tester.createConnector(true));
         Socket socket=new Socket(url.getHost(),url.getPort());
         OutputStream out = socket.getOutputStream();
         int l = to_send.length();
@@ -159,10 +156,9 @@ public class PutFilterTest
             request.setVersion("HTTP/1.0");
             request.setHeader("Host","tester");
             request.setURI("/context/file.txt");
-            response.parse(tester.getResponses(request.generate()));
+            response = HttpTester.parseResponse(tester.getResponses(request.generate()));
         }
         while(response.getStatus()==200);
-        assertTrue(response.getMethod()==null);
         assertEquals(HttpServletResponse.SC_NOT_FOUND,response.getStatus());
 
         out.write(to_send.substring(l-5).getBytes());
@@ -173,8 +169,7 @@ public class PutFilterTest
         request.setVersion("HTTP/1.0");
         request.setHeader("Host","tester");
         request.setURI("/context/file.txt");
-        response.parse(tester.getResponses(request.generate()));
-        assertTrue(response.getMethod()==null);
+        response = HttpTester.parseResponse(tester.getResponses(request.generate()));
         assertEquals(HttpServletResponse.SC_OK,response.getStatus());
         assertEquals(data2,response.getContent());
     }
@@ -183,8 +178,8 @@ public class PutFilterTest
     public void testHandleDelete() throws Exception
     {
         // generated and parsed test
-        HttpTester request = new HttpTester();
-        HttpTester response = new HttpTester();
+        HttpTester.Request request = HttpTester.newRequest();
+        HttpTester.Response response;
 
         // test PUT1
         request.setMethod("PUT");
@@ -194,28 +189,26 @@ public class PutFilterTest
         request.setHeader("Content-Type","text/plain");
         String data1="How Now BROWN COW!!!!";
         request.setContent(data1);
-        response.parse(tester.getResponses(request.generate()));
-        assertTrue(response.getMethod()==null);
+        response = HttpTester.parseResponse(tester.getResponses(request.generate()));
         assertEquals(HttpServletResponse.SC_CREATED,response.getStatus());
 
         File file=new File(_dir,"file.txt");
         assertTrue(file.exists());
-        FileInputStream fis = new FileInputStream(file);
-        assertEquals(data1,IO.toString(fis));
-        fis.close();
+        try (InputStream fis = new FileInputStream(file))
+        {
+            assertEquals(data1,IO.toString(fis));
+        }
 
         request.setMethod("DELETE");
         request.setURI("/context/file.txt");
-        response.parse(tester.getResponses(request.generate()));
-        assertTrue(response.getMethod()==null);
+        response = HttpTester.parseResponse(tester.getResponses(request.generate()));
         assertEquals(HttpServletResponse.SC_NO_CONTENT,response.getStatus());
 
         assertTrue(!file.exists());
 
         request.setMethod("DELETE");
         request.setURI("/context/file.txt");
-        response.parse(tester.getResponses(request.generate()));
-        assertTrue(response.getMethod()==null);
+        response = HttpTester.parseResponse(tester.getResponses(request.generate()));
         assertEquals(HttpServletResponse.SC_FORBIDDEN,response.getStatus());
     }
 
@@ -223,8 +216,8 @@ public class PutFilterTest
     public void testHandleMove() throws Exception
     {
         // generated and parsed test
-        HttpTester request = new HttpTester();
-        HttpTester response = new HttpTester();
+        HttpTester.Request request = HttpTester.newRequest();
+        HttpTester.Response response;
 
         // test PUT1
         request.setMethod("PUT");
@@ -234,21 +227,21 @@ public class PutFilterTest
         request.setHeader("Content-Type","text/plain");
         String data1="How Now BROWN COW!!!!";
         request.setContent(data1);
-        response.parse(tester.getResponses(request.generate()));
-        assertTrue(response.getMethod()==null);
+        response = HttpTester.parseResponse(tester.getResponses(request.generate()));
+
         assertEquals(HttpServletResponse.SC_CREATED,response.getStatus());
 
         File file=new File(_dir,"file.txt");
         assertTrue(file.exists());
-        FileInputStream fis = new FileInputStream(file);
-        assertEquals(data1,IO.toString(fis));
-        fis.close();
+        try (InputStream fis = new FileInputStream(file))
+        {
+            assertEquals(data1,IO.toString(fis));
+        }
 
         request.setMethod("MOVE");
         request.setURI("/context/file.txt");
         request.setHeader("new-uri","/context/blah.txt");
-        response.parse(tester.getResponses(request.generate()));
-        assertTrue(response.getMethod()==null);
+        response = HttpTester.parseResponse(tester.getResponses(request.generate()));
         assertEquals(HttpServletResponse.SC_NO_CONTENT,response.getStatus());
 
         assertTrue(!file.exists());
@@ -261,20 +254,19 @@ public class PutFilterTest
     public void testHandleOptions() throws Exception
     {
         // generated and parsed test
-        HttpTester request = new HttpTester();
-        HttpTester response = new HttpTester();
+        HttpTester.Request request = HttpTester.newRequest();
+        HttpTester.Response response;
 
         // test PUT1
         request.setMethod("OPTIONS");
         request.setVersion("HTTP/1.0");
-        request.setHeader("Host","tester");
+        request.put("Host","tester");
         request.setURI("/context/file.txt");
-        response.parse(tester.getResponses(request.generate()));
-        assertTrue(response.getMethod()==null);
+        response = HttpTester.parseResponse(tester.getResponses(request.generate()));
         assertEquals(HttpServletResponse.SC_OK,response.getStatus());
 
         Set<String> options = new HashSet<String>();
-        options.addAll(Arrays.asList(response.getHeader("Allow").split(" *, *")));
+        options.addAll(Arrays.asList(response.get("Allow").split(" *, *")));
 
         assertTrue(options.contains("GET"));
         assertTrue(options.contains("POST"));
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/QoSFilterTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/QoSFilterTest.java
index d23ab13..34cd517 100644
--- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/QoSFilterTest.java
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/QoSFilterTest.java
@@ -18,30 +18,28 @@
 
 package org.eclipse.jetty.servlets;
 
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
 import java.io.IOException;
 import java.net.URL;
 import java.util.EnumSet;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import javax.servlet.DispatcherType;
-import javax.servlet.Servlet;
 import javax.servlet.ServletException;
 import javax.servlet.ServletRequest;
 import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
+import org.eclipse.jetty.http.HttpTester;
 import org.eclipse.jetty.server.LocalConnector;
 import org.eclipse.jetty.servlet.FilterHolder;
-import org.eclipse.jetty.servlet.FilterMapping;
-import org.eclipse.jetty.testing.HttpTester;
-import org.eclipse.jetty.testing.ServletTester;
+import org.eclipse.jetty.servlet.ServletTester;
+import org.eclipse.jetty.util.BufferUtil;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
+import org.hamcrest.Matchers;
 import org.junit.After;
+import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -62,14 +60,14 @@ public class QoSFilterTest
         _tester = new ServletTester();
         _tester.setContextPath("/context");
         _tester.addServlet(TestServlet.class, "/test");
-        TestServlet.__maxSleepers=0;
-        TestServlet.__sleepers=0;
+        TestServlet.__maxSleepers = 0;
+        TestServlet.__sleepers = 0;
 
         _connectors = new LocalConnector[NUM_CONNECTIONS];
-        for(int i = 0; i < _connectors.length; ++i)
+        for (int i = 0; i < _connectors.length; ++i)
             _connectors[i] = _tester.createLocalConnector();
 
-        _doneRequests = new CountDownLatch(NUM_CONNECTIONS*NUM_LOOPS);
+        _doneRequests = new CountDownLatch(NUM_CONNECTIONS * NUM_LOOPS);
 
         _tester.start();
     }
@@ -83,15 +81,17 @@ public class QoSFilterTest
     @Test
     public void testNoFilter() throws Exception
     {
-        for(int i = 0; i < NUM_CONNECTIONS; ++i )
+        for (int i = 0; i < NUM_CONNECTIONS; ++i)
         {
             new Thread(new Worker(i)).start();
         }
 
-        _doneRequests.await(10,TimeUnit.SECONDS);
+        _doneRequests.await(10, TimeUnit.SECONDS);
 
-        assertFalse("TEST WAS NOT PARALLEL ENOUGH!",TestServlet.__maxSleepers<=MAX_QOS);
-        assertTrue(TestServlet.__maxSleepers<=NUM_CONNECTIONS);
+        if (TestServlet.__maxSleepers <= MAX_QOS)
+            LOG.warn("TEST WAS NOT PARALLEL ENOUGH!");
+        else
+            Assert.assertThat(TestServlet.__maxSleepers, Matchers.lessThanOrEqualTo(NUM_CONNECTIONS));
     }
 
     @Test
@@ -99,17 +99,19 @@ public class QoSFilterTest
     {
         FilterHolder holder = new FilterHolder(QoSFilter2.class);
         holder.setAsyncSupported(true);
-        holder.setInitParameter(QoSFilter.MAX_REQUESTS_INIT_PARAM, ""+MAX_QOS);
-        _tester.getContext().getServletHandler().addFilterWithMapping(holder,"/*",EnumSet.of(DispatcherType.REQUEST,DispatcherType.ASYNC));
+        holder.setInitParameter(QoSFilter.MAX_REQUESTS_INIT_PARAM, "" + MAX_QOS);
+        _tester.getContext().getServletHandler().addFilterWithMapping(holder, "/*", EnumSet.of(DispatcherType.REQUEST, DispatcherType.ASYNC));
 
-        for(int i = 0; i < NUM_CONNECTIONS; ++i )
+        for (int i = 0; i < NUM_CONNECTIONS; ++i)
         {
             new Thread(new Worker(i)).start();
         }
 
-        _doneRequests.await(10,TimeUnit.SECONDS);
-        assertFalse("TEST WAS NOT PARALLEL ENOUGH!",TestServlet.__maxSleepers<MAX_QOS);
-        assertTrue(TestServlet.__maxSleepers==MAX_QOS);
+        _doneRequests.await(10, TimeUnit.SECONDS);
+        if (TestServlet.__maxSleepers < MAX_QOS)
+            LOG.warn("TEST WAS NOT PARALLEL ENOUGH!");
+        else
+            Assert.assertEquals(TestServlet.__maxSleepers, MAX_QOS);
     }
 
     @Test
@@ -117,105 +119,109 @@ public class QoSFilterTest
     {
         FilterHolder holder = new FilterHolder(QoSFilter2.class);
         holder.setAsyncSupported(true);
-        holder.setInitParameter(QoSFilter.MAX_REQUESTS_INIT_PARAM, ""+MAX_QOS);
-        _tester.getContext().getServletHandler().addFilterWithMapping(holder,"/*",EnumSet.of(DispatcherType.REQUEST,DispatcherType.ASYNC));
-        for(int i = 0; i < NUM_CONNECTIONS; ++i )
+        holder.setInitParameter(QoSFilter.MAX_REQUESTS_INIT_PARAM, String.valueOf(MAX_QOS));
+        _tester.getContext().getServletHandler().addFilterWithMapping(holder, "/*", EnumSet.of(DispatcherType.REQUEST, DispatcherType.ASYNC));
+
+        for (int i = 0; i < NUM_CONNECTIONS; ++i)
         {
             new Thread(new Worker2(i)).start();
         }
 
-        _doneRequests.await(20,TimeUnit.SECONDS);
-        assertFalse("TEST WAS NOT PARALLEL ENOUGH!",TestServlet.__maxSleepers<MAX_QOS);
-        assertTrue(TestServlet.__maxSleepers<=MAX_QOS);
+        _doneRequests.await(20, TimeUnit.SECONDS);
+        if (TestServlet.__maxSleepers < MAX_QOS)
+            LOG.warn("TEST WAS NOT PARALLEL ENOUGH!");
+        else
+            Assert.assertEquals(TestServlet.__maxSleepers, MAX_QOS);
     }
 
-    class Worker implements Runnable {
+    class Worker implements Runnable
+    {
         private int _num;
+
         public Worker(int num)
         {
             _num = num;
         }
 
+        @Override
         public void run()
         {
-            for (int i=0;i<NUM_LOOPS;i++)
+            for (int i = 0; i < NUM_LOOPS; i++)
             {
-                HttpTester request = new HttpTester();
+                HttpTester.Request request = HttpTester.newRequest();
 
                 request.setMethod("GET");
                 request.setHeader("host", "tester");
-                request.setURI("/context/test?priority="+(_num%QoSFilter.__DEFAULT_MAX_PRIORITY));
-                request.setHeader("num", _num+"");
+                request.setURI("/context/test?priority=" + (_num % QoSFilter.__DEFAULT_MAX_PRIORITY));
+                request.setHeader("num", _num + "");
                 try
                 {
-                    String responseString = _tester.getResponses(request.generate(), _connectors[_num]);
-                    if(responseString.indexOf("HTTP")!=-1)
+                    String responseString = _connectors[_num].getResponses(BufferUtil.toString(request.generate()));
+                    if (responseString.contains("HTTP"))
                     {
                         _doneRequests.countDown();
                     }
                 }
                 catch (Exception x)
                 {
-                    assertTrue(false);
+                    Assert.assertTrue(false);
                 }
             }
         }
     }
 
-    class Worker2 implements Runnable {
+    class Worker2 implements Runnable
+    {
         private int _num;
+
         public Worker2(int num)
         {
             _num = num;
         }
 
+        @Override
         public void run()
         {
-            URL url=null;
+            URL url = null;
             try
             {
-                String addr = _tester.createSocketConnector(true);
-                for (int i=0;i<NUM_LOOPS;i++)
+                String addr = _tester.createConnector(true);
+                for (int i = 0; i < NUM_LOOPS; i++)
                 {
-                    url=new URL(addr+"/context/test?priority="+(_num%QoSFilter.__DEFAULT_MAX_PRIORITY)+"&n="+_num+"&l="+i);
-                    // System.err.println(_num+"-"+i+" Try "+url);
+                    url = new URL(addr + "/context/test?priority=" + (_num % QoSFilter.__DEFAULT_MAX_PRIORITY) + "&n=" + _num + "&l=" + i);
                     url.getContent();
                     _doneRequests.countDown();
-                    // System.err.println(_num+"-"+i+" Got "+IO.toString(in)+" "+_doneRequests.getCount());
                 }
             }
-            catch(Exception e)
+            catch (Exception e)
             {
-                LOG.warn(String.valueOf(url));
-                LOG.debug(e);
+                LOG.debug("Request " + url + " failed", e);
             }
         }
     }
 
-    public static class TestServlet extends HttpServlet implements Servlet
+    public static class TestServlet extends HttpServlet
     {
         private static int __sleepers;
         private static int __maxSleepers;
 
+        @Override
         protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
         {
             try
             {
-                synchronized(TestServlet.class)
+                synchronized (TestServlet.class)
                 {
                     __sleepers++;
-                    if(__sleepers > __maxSleepers)
+                    if (__sleepers > __maxSleepers)
                         __maxSleepers = __sleepers;
                 }
 
                 Thread.sleep(50);
 
-                synchronized(TestServlet.class)
+                synchronized (TestServlet.class)
                 {
-                    // System.err.println(_count++);
                     __sleepers--;
-                    if(__sleepers > __maxSleepers)
-                        __maxSleepers = __sleepers;
                 }
 
                 response.setContentType("text/plain");
@@ -223,7 +229,6 @@ public class QoSFilterTest
             }
             catch (InterruptedException e)
             {
-                e.printStackTrace();
                 response.sendError(500);
             }
         }
@@ -231,10 +236,11 @@ public class QoSFilterTest
 
     public static class QoSFilter2 extends QoSFilter
     {
+        @Override
         public int getPriority(ServletRequest request)
         {
             String p = request.getParameter("priority");
-            if (p!=null)
+            if (p != null)
                 return Integer.parseInt(p);
             return 0;
         }
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/TransparentProxyTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/TransparentProxyTest.java
deleted file mode 100644
index 3359a45..0000000
--- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/TransparentProxyTest.java
+++ /dev/null
@@ -1,140 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-
-package org.eclipse.jetty.servlets;
-
-
-import static org.junit.Assert.assertEquals;
-
-import java.io.IOException;
-import java.net.HttpURLConnection;
-import java.net.URL;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
-import org.eclipse.jetty.servlet.ServletContextHandler;
-import org.eclipse.jetty.servlet.ServletHolder;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-
-/**
- * TransparentProxyTest
- *
- *
- */
-public class TransparentProxyTest
-{
-  
-
-        protected Server server;
-        protected Server proxyServer;
-        
-        public static class ServletA extends HttpServlet {
-            @Override
-            protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
-                resp.setContentType("text/plain");
-                resp.getWriter().println("ok");
-            }
-        }
-        
-        @Before
-        public void setUp () throws Exception
-        {
-            //set up the target server
-            server = new Server();
-            SelectChannelConnector connector = new SelectChannelConnector();
-            connector.setPort(8080);
-            server.addConnector(connector);
-            ServletContextHandler handler = new ServletContextHandler(server, "/");
-            handler.addServlet(ServletA.class, "/a");
-            server.setHandler(handler);
-            server.start();
-
-
-            //set up the server that proxies to the target server
-            proxyServer = new Server();
-            SelectChannelConnector proxyConnector = new SelectChannelConnector();
-            proxyConnector.setPort(8081);
-            proxyServer.addConnector(proxyConnector);           
-            ServletContextHandler proxyHandler = new ServletContextHandler(proxyServer, "/");
-            proxyHandler.addServlet(new ServletHolder(new ProxyServlet.Transparent("/", "http", "127.0.0.1", 8080, "/")), "/");
-            proxyServer.setHandler(proxyHandler);
-            proxyServer.start();
-
-        }
-        
-        
-        @After
-        public void tearDown() throws Exception
-        {
-            server.stop();
-            proxyServer.stop();
-        }
-
-
-        @Test
-        public void testDirectNoContentType() throws Exception
-        {
-            // Direct request without Content-Type set works
-            URL url = new URL("http://localhost:8080/a");
-            HttpURLConnection con = (HttpURLConnection) url.openConnection();
-            assertEquals(200, con.getResponseCode());
-        }
-
-        
-        @Test
-        public void testDirectWithContentType() throws Exception
-        {
-            // Direct request with Content-Type works
-            URL url = new URL("http://localhost:8080/a");
-            HttpURLConnection con = (HttpURLConnection) url.openConnection();
-            con.addRequestProperty("Content-Type", "application/x-www-form-urlencoded;charset=utf-8");
-            assertEquals(200, con.getResponseCode());
-        }
-
-        @Test
-        public void testProxiedWithoutContentType() throws Exception
-        {
-            // Proxied request without Content-Type set works
-            URL url = new URL("http://localhost:8081/a");
-            HttpURLConnection con = (HttpURLConnection) url.openConnection();
-            assertEquals(200, con.getResponseCode());
-            System.err.println (con.getContentType());
-        }
-
-        @Test
-        public void testProxiedWithContentType() throws Exception
-        {
-            // Proxied request with Content-Type set fails
-
-            URL url = new URL("http://localhost:8081/a");
-            HttpURLConnection con = (HttpURLConnection) url.openConnection();
-            con.addRequestProperty("Content-Type", "application/x-www-form-urlencoded;charset=utf-8");
-            assertEquals(200, con.getResponseCode());
-            System.err.println(con.getContentType());
-            
-        }
-}
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/AsyncManipFilter.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/AsyncManipFilter.java
new file mode 100644
index 0000000..4935d67
--- /dev/null
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/AsyncManipFilter.java
@@ -0,0 +1,103 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlets.gzip;
+
+import java.io.IOException;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.AsyncEvent;
+import javax.servlet.AsyncListener;
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * Filter that merely manipulates the AsyncContext.
+ * <p>
+ * The pattern of manipulation is modeled after how DOSFilter behaves. The purpose of this filter is to test arbitrary filter chains that could see unintended
+ * side-effects of async context manipulation.
+ */
+public class AsyncManipFilter implements Filter, AsyncListener
+{
+    private static final Logger LOG = Log.getLogger(AsyncManipFilter.class);
+    private static final String MANIP_KEY = AsyncManipFilter.class.getName();
+
+    @Override
+    public void init(FilterConfig filterConfig) throws ServletException
+    {
+    }
+
+    @Override
+    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
+    {
+        LOG.debug("doFilter() - {}", chain);
+        AsyncContext ctx = (AsyncContext)request.getAttribute(MANIP_KEY);
+        if (ctx == null)
+        {
+            LOG.debug("Initial pass through: {}", chain);
+            ctx = request.startAsync();
+            ctx.addListener(this);
+            ctx.setTimeout(1000);
+            LOG.debug("AsyncContext: {}", ctx);
+            request.setAttribute(MANIP_KEY,ctx);
+            return;
+        }
+        else
+        {
+            LOG.debug("Second pass through: {}", chain);
+            chain.doFilter(request,response);
+        }
+    }
+
+    @Override
+    public void destroy()
+    {
+    }
+
+    @Override
+    public void onComplete(AsyncEvent event) throws IOException
+    {
+        LOG.debug("onComplete() {}",event);
+    }
+
+    @Override
+    public void onTimeout(AsyncEvent event) throws IOException
+    {
+        LOG.debug("onTimeout() {}",event.getAsyncContext());
+        event.getAsyncContext().dispatch();
+    }
+
+    @Override
+    public void onError(AsyncEvent event) throws IOException
+    {
+        LOG.debug("onError()",event.getThrowable());
+    }
+
+    @Override
+    public void onStartAsync(AsyncEvent event) throws IOException
+    {
+        LOG.debug("onTimeout() {}",event);
+    }
+}
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/AsyncScheduledDispatchWrite.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/AsyncScheduledDispatchWrite.java
new file mode 100644
index 0000000..84f1b90
--- /dev/null
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/AsyncScheduledDispatchWrite.java
@@ -0,0 +1,121 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlets.gzip;
+
+import java.io.IOException;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+ at SuppressWarnings("serial")
+public abstract class AsyncScheduledDispatchWrite extends TestDirContentServlet
+{
+    public static class Default extends AsyncScheduledDispatchWrite
+    {
+        public Default()
+        {
+            super(true);
+        }
+    }
+    
+    public static class Passed extends AsyncScheduledDispatchWrite
+    {
+        public Passed()
+        {
+            super(false);
+        }
+    }
+
+    private static class DispatchBack implements Runnable
+    {
+        private final AsyncContext ctx;
+
+        public DispatchBack(AsyncContext ctx)
+        {
+            this.ctx = ctx;
+        }
+
+        @Override
+        public void run()
+        {
+            ctx.dispatch();
+        }
+    }
+
+    private final boolean originalReqResp;
+    private ScheduledExecutorService scheduler;
+
+    public AsyncScheduledDispatchWrite(boolean originalReqResp)
+    {
+        this.originalReqResp = originalReqResp;
+    }
+
+    public void init(ServletConfig config) throws ServletException
+    {
+        super.init(config);
+        scheduler = Executors.newScheduledThreadPool(3);
+    }
+
+    @Override
+    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+    {
+        Boolean suspended = (Boolean)request.getAttribute("SUSPENDED");
+        if (suspended == null || !suspended)
+        {
+            request.setAttribute("SUSPENDED",Boolean.TRUE);
+            AsyncContext ctx;
+            if (originalReqResp)
+            {
+                // Use Original Request & Response
+                ctx = request.startAsync();
+            }
+            else
+            {
+                // Pass Request & Response
+                ctx = request.startAsync(request,response);
+            }
+            ctx.setTimeout(0);
+            scheduler.schedule(new DispatchBack(ctx),500,TimeUnit.MILLISECONDS);
+        }
+        else
+        {
+            String fileName = request.getServletPath();
+            byte[] dataBytes = loadContentFileBytes(fileName);
+
+            response.setContentLength(dataBytes.length);
+
+            ServletOutputStream out = response.getOutputStream();
+
+            if (fileName.endsWith("txt"))
+                response.setContentType("text/plain");
+            else if (fileName.endsWith("mp3"))
+                response.setContentType("audio/mpeg");
+            response.setHeader("ETag","W/etag-" + fileName);
+
+            out.write(dataBytes);
+        }
+    }
+}
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/AsyncTimeoutCompleteWrite.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/AsyncTimeoutCompleteWrite.java
new file mode 100644
index 0000000..98465e6
--- /dev/null
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/AsyncTimeoutCompleteWrite.java
@@ -0,0 +1,137 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlets.gzip;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.AsyncEvent;
+import javax.servlet.AsyncListener;
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Respond with requested content, but via AsyncContext manipulation.
+ * <p>
+ * 
+ * <pre>
+ *   1) startAsync
+ *   2) AsyncContext.setTimeout()
+ *   3) onTimeout()
+ *   4) send-response
+ *   5) AsyncContext.complete()
+ * </pre>
+ */
+ at SuppressWarnings("serial")
+public abstract class AsyncTimeoutCompleteWrite extends TestDirContentServlet implements AsyncListener
+{
+    public static class Default extends AsyncTimeoutCompleteWrite
+    {
+        public Default()
+        {
+            super(true);
+        }
+    }
+    
+    public static class Passed extends AsyncTimeoutCompleteWrite
+    {
+        public Passed()
+        {
+            super(false);
+        }
+    }
+
+    private final boolean originalReqResp;
+
+    public AsyncTimeoutCompleteWrite(boolean originalReqResp)
+    {
+        this.originalReqResp = originalReqResp;
+    }
+
+    @Override
+    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+    {
+        assertThat("'filename' request attribute shouldn't be declared",request.getAttribute("filename"),nullValue());
+
+        AsyncContext ctx = (AsyncContext)request.getAttribute(this.getClass().getName());
+        assertThat("AsyncContext (shouldn't be in request attribute)", ctx, nullValue());
+        
+        if (originalReqResp)
+        {
+            // Use Original Request & Response
+            ctx = request.startAsync();
+        }
+        else
+        {
+            // Pass Request & Response
+            ctx = request.startAsync(request,response);
+        }
+        String fileName = request.getServletPath();
+        request.setAttribute("filename",fileName);
+        ctx.addListener(this);
+        ctx.setTimeout(20);
+        
+        // Setup indication of a redispatch (which this scenario shouldn't do)
+        request.setAttribute(this.getClass().getName(),ctx);
+    }
+
+    @Override
+    public void onComplete(AsyncEvent event) throws IOException
+    {
+    }
+
+    @Override
+    public void onTimeout(AsyncEvent event) throws IOException
+    {
+        HttpServletRequest request = (HttpServletRequest)event.getSuppliedRequest();
+        HttpServletResponse response = (HttpServletResponse)event.getSuppliedResponse();
+
+        String fileName = (String)request.getAttribute("filename");
+        byte[] dataBytes = loadContentFileBytes(fileName);
+
+        response.setContentLength(dataBytes.length);
+
+        ServletOutputStream out = response.getOutputStream();
+
+        if (fileName.endsWith("txt"))
+            response.setContentType("text/plain");
+        else if (fileName.endsWith("mp3"))
+            response.setContentType("audio/mpeg");
+        response.setHeader("ETag","W/etag-" + fileName);
+
+        out.write(dataBytes);
+
+        event.getAsyncContext().complete();
+    }
+
+    @Override
+    public void onError(AsyncEvent event) throws IOException
+    {
+    }
+
+    @Override
+    public void onStartAsync(AsyncEvent event) throws IOException
+    {
+    }
+}
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/AsyncTimeoutDispatchWrite.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/AsyncTimeoutDispatchWrite.java
new file mode 100644
index 0000000..80e36fc
--- /dev/null
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/AsyncTimeoutDispatchWrite.java
@@ -0,0 +1,121 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlets.gzip;
+
+import java.io.IOException;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.AsyncEvent;
+import javax.servlet.AsyncListener;
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+ at SuppressWarnings("serial")
+public class AsyncTimeoutDispatchWrite extends TestDirContentServlet implements AsyncListener
+{
+    public static class Default extends AsyncTimeoutDispatchWrite
+    {
+        public Default()
+        {
+            super(true);
+        }
+    }
+    
+    public static class Passed extends AsyncTimeoutDispatchWrite
+    {
+        public Passed()
+        {
+            super(false);
+        }
+    }
+
+    private final boolean originalReqResp;
+
+    public AsyncTimeoutDispatchWrite(boolean originalReqResp)
+    {
+        this.originalReqResp = originalReqResp;
+    }
+
+    @Override
+    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+    {
+        AsyncContext ctx = (AsyncContext)request.getAttribute(AsyncContext.class.getName());
+        if (ctx == null)
+        {
+            // First pass through
+            if (originalReqResp)
+            {
+                // Use Original Request & Response
+                ctx = request.startAsync();
+            }
+            else
+            {
+                // Pass Request & Response
+                ctx = request.startAsync(request,response);
+            }
+            ctx.addListener(this);
+            ctx.setTimeout(200);
+            request.setAttribute(AsyncContext.class.getName(),ctx);
+        }
+        else
+        {
+            // second pass through, as result of timeout -> dispatch
+            String fileName = request.getServletPath();
+            byte[] dataBytes = loadContentFileBytes(fileName);
+
+            response.setContentLength(dataBytes.length);
+
+            ServletOutputStream out = response.getOutputStream();
+
+            if (fileName.endsWith("txt"))
+                response.setContentType("text/plain");
+            else if (fileName.endsWith("mp3"))
+                response.setContentType("audio/mpeg");
+            response.setHeader("ETag","W/etag-" + fileName);
+
+            out.write(dataBytes);
+            // no need to call AsyncContext.complete() from here
+            // in fact, it will cause an IllegalStateException if we do
+            // ctx.complete();
+        }
+    }
+
+    @Override
+    public void onComplete(AsyncEvent event) throws IOException
+    {
+    }
+
+    @Override
+    public void onTimeout(AsyncEvent event) throws IOException
+    {
+        event.getAsyncContext().dispatch();
+    }
+
+    @Override
+    public void onError(AsyncEvent event) throws IOException
+    {
+    }
+
+    @Override
+    public void onStartAsync(AsyncEvent event) throws IOException
+    {
+    }
+}
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/GzipTester.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/GzipTester.java
index 539f6bb..6121221 100644
--- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/GzipTester.java
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/GzipTester.java
@@ -32,108 +32,239 @@ import java.security.DigestOutputStream;
 import java.security.MessageDigest;
 import java.util.EnumSet;
 import java.util.Enumeration;
+import java.util.concurrent.TimeUnit;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import java.util.zip.GZIPInputStream;
 import java.util.zip.Inflater;
-import javax.servlet.DispatcherType;
 import java.util.zip.InflaterInputStream;
 
+import javax.servlet.DispatcherType;
+import javax.servlet.Filter;
 import javax.servlet.Servlet;
 import javax.servlet.http.HttpServletResponse;
 
-import org.eclipse.jetty.http.HttpFields;
-import org.eclipse.jetty.http.HttpHeaders;
-import org.eclipse.jetty.io.ByteArrayBuffer;
+import org.eclipse.jetty.http.DateGenerator;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpTester;
+import org.eclipse.jetty.http.HttpTester.Response;
+import org.eclipse.jetty.server.HttpConnectionFactory;
 import org.eclipse.jetty.servlet.FilterHolder;
 import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jetty.servlet.ServletTester;
 import org.eclipse.jetty.servlets.GzipFilter;
-import org.eclipse.jetty.testing.HttpTester;
-import org.eclipse.jetty.testing.ServletTester;
 import org.eclipse.jetty.toolchain.test.IO;
 import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
 import org.eclipse.jetty.toolchain.test.TestingDir;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
 import org.hamcrest.Matchers;
 import org.junit.Assert;
 
 public class GzipTester
 {
-    private Class<? extends GzipFilter> gzipFilterClass = GzipFilter.class;
+    private static final Logger LOG = Log.getLogger(GzipTester.class);
+
+    public static class ContentMetadata
+    {
+        public final long size;
+        public final String sha1;
+
+        public ContentMetadata(long size, String sha1checksum)
+        {
+            this.size = size;
+            this.sha1 = sha1checksum;
+        }
+    }
+
+    private Class<? extends Filter> gzipFilterClass = null;
     private String encoding = "ISO8859_1";
     private String userAgent = null;
-    private ServletTester servletTester;
+    private final ServletTester tester = new ServletTester();;
     private TestingDir testdir;
+    private String accept;
     private String compressionType;
 
+    public GzipTester(TestingDir testingdir, String compressionType, String accept)
+    {
+        this.testdir = testingdir;
+        this.compressionType = compressionType;
+        this.accept = accept;
+    }
+
     public GzipTester(TestingDir testingdir, String compressionType)
     {
         this.testdir = testingdir;
         this.compressionType = compressionType;
-        // Make sure we start with a clean testing directory.
-        // DOES NOT WORK IN WINDOWS - this.testdir.ensureEmpty();
+        this.accept = compressionType;
     }
 
-    public HttpTester assertIsResponseGzipCompressed(String method,String filename) throws Exception
+    public int getOutputBufferSize()
     {
-        return assertIsResponseGzipCompressed(method,filename,filename);
+        return tester.getConnector().getConnectionFactory(HttpConnectionFactory.class).getHttpConfiguration().getOutputBufferSize();
     }
     
-    public HttpTester assertIsResponseGzipCompressed(String method,String filename,long ifmodifiedsince) throws Exception
+    public ContentMetadata getResponseMetadata(Response response) throws Exception
+    {
+        long size = response.getContentBytes().length;
+
+        String contentEncoding = response.get("Content-Encoding");
+
+        ByteArrayInputStream bais = null;
+        InputStream in = null;
+        DigestOutputStream digester = null;
+        ByteArrayOutputStream uncompressedStream = null;
+        try
+        {
+            MessageDigest digest = MessageDigest.getInstance("SHA1");
+            bais = new ByteArrayInputStream(response.getContentBytes());
+
+            if (contentEncoding == null)
+            {
+                LOG.debug("No response content-encoding");
+                in = new PassThruInputStream(bais);
+            }
+            else if (contentEncoding.contains(GzipFilter.GZIP))
+            {
+                in = new GZIPInputStream(bais);
+            }
+            else if (contentEncoding.contains(GzipFilter.DEFLATE))
+            {
+                in = new InflaterInputStream(bais,new Inflater(true));
+            }
+            else
+            {
+                assertThat("Unexpected response content-encoding", contentEncoding, isEmptyOrNullString());
+            }
+            
+            uncompressedStream = new ByteArrayOutputStream((int)size); 
+
+            digester = new DigestOutputStream(uncompressedStream,digest);
+            IO.copy(in,digester);
+            
+            byte output[] = uncompressedStream.toByteArray();
+            String actualSha1Sum = Hex.asHex(digest.digest());
+            return new ContentMetadata(output.length,actualSha1Sum);
+        }
+        finally
+        {
+            IO.close(digester);
+            IO.close(in);
+            IO.close(bais);
+            IO.close(uncompressedStream);
+        }
+    }
+
+    public HttpTester.Response assertIsResponseGzipCompressed(String method, String filename) throws Exception
+    {
+        return assertIsResponseGzipCompressed(method,filename,filename,-1);
+    }
+
+    public HttpTester.Response assertIsResponseGzipCompressed(String method, String filename, long ifmodifiedsince) throws Exception
     {
         return assertIsResponseGzipCompressed(method,filename,filename,ifmodifiedsince);
     }
 
-    public HttpTester assertIsResponseGzipCompressed(String method,String requestedFilename, String serverFilename) throws Exception
+    public HttpTester.Response assertIsResponseGzipCompressed(String method, String requestedFilename, String serverFilename) throws Exception
     {
         return assertIsResponseGzipCompressed(method,requestedFilename,serverFilename,-1);
     }
-    
-    public HttpTester assertIsResponseGzipCompressed(String method,String requestedFilename, String serverFilename, long ifmodifiedsince) throws Exception
+
+    public HttpTester.Response assertNonStaticContentIsResponseGzipCompressed(String method, String path, String expected) throws Exception
     {
-        //System.err.printf("[GzipTester] requesting /context/%s%n",requestedFilename);
-        HttpTester request = new HttpTester();
-        HttpTester response = new HttpTester();
+        HttpTester.Request request = HttpTester.newRequest();
+        HttpTester.Response response;
+
+        request.setMethod(method);
+        request.setVersion("HTTP/1.0");
+        request.setHeader("Host","tester");
+        request.setHeader("Accept-Encoding",accept);
+
+        if (this.userAgent != null)
+            request.setHeader("User-Agent",this.userAgent);
+        request.setURI("/context/" + path);
+
+        // Issue the request
+        response = HttpTester.parseResponse(tester.getResponses(request.generate()));
+
+        int qindex = compressionType.indexOf(";");
+        if (qindex < 0)
+            Assert.assertThat("Response.header[Content-Encoding]",response.get("Content-Encoding"),containsString(compressionType));
+        else
+            Assert.assertThat("Response.header[Content-Encoding]",response.get("Content-Encoding"),containsString(compressionType.substring(0,qindex)));
+
+        ByteArrayInputStream bais = null;
+        InputStream in = null;
+        ByteArrayOutputStream out = null;
+        String actual = null;
+
+        try
+        {
+            bais = new ByteArrayInputStream(response.getContentBytes());
+            if (compressionType.startsWith(GzipFilter.GZIP))
+            {
+                in = new GZIPInputStream(bais);
+            }
+            else if (compressionType.startsWith(GzipFilter.DEFLATE))
+            {
+                in = new InflaterInputStream(bais,new Inflater(true));
+            }
+            out = new ByteArrayOutputStream();
+            IO.copy(in,out);
+
+            actual = out.toString(encoding);
+            assertThat("Uncompressed contents",actual,equalTo(expected));
+        }
+        finally
+        {
+            IO.close(out);
+            IO.close(in);
+            IO.close(bais);
+        }
+
+        return response;
+    }
+
+    public HttpTester.Response assertIsResponseGzipCompressed(String method, String requestedFilename, String serverFilename, long ifmodifiedsince)
+            throws Exception
+    {
+        HttpTester.Request request = HttpTester.newRequest();
+        HttpTester.Response response;
 
         request.setMethod(method);
         request.setVersion("HTTP/1.0");
         request.setHeader("Host","tester");
         request.setHeader("Accept-Encoding",compressionType);
-        if (ifmodifiedsince>0)
-            request.setHeader(HttpHeaders.IF_MODIFIED_SINCE,HttpFields.formatDate(ifmodifiedsince));
+        if (ifmodifiedsince > 0)
+            request.setHeader(HttpHeader.IF_MODIFIED_SINCE.asString(),DateGenerator.formatDate(ifmodifiedsince));
         if (this.userAgent != null)
-            request.setHeader("User-Agent", this.userAgent);
+            request.setHeader("User-Agent",this.userAgent);
         request.setURI("/context/" + requestedFilename);
 
         // Issue the request
-        ByteArrayBuffer reqsBuff = new ByteArrayBuffer(request.generate().getBytes());
-        // Collect the response(s)
-        ByteArrayBuffer respBuff = servletTester.getResponses(reqsBuff);
-        response.parse(respBuff.asArray());
-        
+        response = HttpTester.parseResponse(tester.getResponses(request.generate()));
+
         // Assert the response headers
-        Assert.assertThat("Response.method",response.getMethod(),nullValue());
-        
+        // Assert.assertThat("Response.status",response.getStatus(),is(HttpServletResponse.SC_OK));
+
         // Response headers should have either a Transfer-Encoding indicating chunked OR a Content-Length
-        String contentLength = response.getHeader("Content-Length");
-        String transferEncoding = response.getHeader("Transfer-Encoding");
-        
-        /* TODO need to check for the 3rd option of EOF content.  To do this properly you might need to look at both HTTP/1.1 and HTTP/1.0 requests
-        boolean chunked = (transferEncoding != null) && (transferEncoding.indexOf("chunk") >= 0);
-        if(!chunked) {
-            Assert.assertThat("Response.header[Content-Length]",contentLength,notNullValue());
-        } else {
-            Assert.assertThat("Response.header[Transfer-Encoding]",transferEncoding,notNullValue());
-        }
-        */
-        
+        /*
+         * TODO need to check for the 3rd option of EOF content. To do this properly you might need to look at both HTTP/1.1 and HTTP/1.0 requests String
+         * contentLength = response.get("Content-Length"); String transferEncoding = response.get("Transfer-Encoding");
+         * 
+         * boolean chunked = (transferEncoding != null) && (transferEncoding.indexOf("chunk") >= 0); if(!chunked) {
+         * Assert.assertThat("Response.header[Content-Length]",contentLength,notNullValue()); } else {
+         * Assert.assertThat("Response.header[Transfer-Encoding]",transferEncoding,notNullValue()); }
+         */
+
         int qindex = compressionType.indexOf(";");
         if (qindex < 0)
-            Assert.assertThat("Response.header[Content-Encoding]",response.getHeader("Content-Encoding"),containsString(compressionType));
+            Assert.assertThat("Response.header[Content-Encoding]",response.get("Content-Encoding"),containsString(compressionType));
         else
-            Assert.assertThat("Response.header[Content-Encoding]", response.getHeader("Content-Encoding"),containsString(compressionType.substring(0,qindex)));
+            Assert.assertThat("Response.header[Content-Encoding]",response.get("Content-Encoding"),containsString(compressionType.substring(0,qindex)));
+
+        Assert.assertThat(response.get("ETag"),Matchers.startsWith("W/"));
 
-        Assert.assertThat(response.getHeader("ETag"),Matchers.startsWith("W/"));
-        
         // Assert that the decompressed contents are what we expect.
         File serverFile = testdir.getFile(serverFilename);
         String expected = IO.readToString(serverFile);
@@ -151,7 +282,7 @@ public class GzipTester
             }
             else if (compressionType.startsWith(GzipFilter.DEFLATE))
             {
-                in = new InflaterInputStream(bais, new Inflater(true));
+                in = new InflaterInputStream(bais,new Inflater(true));
             }
             out = new ByteArrayOutputStream();
             IO.copy(in,out);
@@ -165,93 +296,99 @@ public class GzipTester
             IO.close(in);
             IO.close(bais);
         }
-        
+
         return response;
     }
-    
 
-    public HttpTester assertIsResponseNotModified(String method,String requestedFilename, long ifmodifiedsince) throws Exception
+    public HttpTester.Response assertIsResponseNotModified(String method, String requestedFilename, long ifmodifiedsince) throws Exception
     {
-        return assertIsResponseNotModified(method,requestedFilename,requestedFilename,ifmodifiedsince);
-    }
-    
-    public HttpTester assertIsResponseNotModified(String method,String requestedFilename, String serverFilename, long ifmodifiedsince) throws Exception
-    {
-        //System.err.printf("[GzipTester] requesting /context/%s%n",requestedFilename);
-        HttpTester request = new HttpTester();
-        HttpTester response = new HttpTester();
+        HttpTester.Request request = HttpTester.newRequest();
+        HttpTester.Response response;
 
         request.setMethod(method);
         request.setVersion("HTTP/1.0");
         request.setHeader("Host","tester");
         request.setHeader("Accept-Encoding",compressionType);
-        if (ifmodifiedsince>0)
-            request.setHeader(HttpHeaders.IF_MODIFIED_SINCE,HttpFields.formatDate(ifmodifiedsince));
+        if (ifmodifiedsince > 0)
+            request.setHeader(HttpHeader.IF_MODIFIED_SINCE.asString(),DateGenerator.formatDate(ifmodifiedsince));
         if (this.userAgent != null)
-            request.setHeader("User-Agent", this.userAgent);
+            request.setHeader("User-Agent",this.userAgent);
         request.setURI("/context/" + requestedFilename);
 
         // Issue the request
-        ByteArrayBuffer reqsBuff = new ByteArrayBuffer(request.generate().getBytes());
-        // Collect the response(s)
-        ByteArrayBuffer respBuff = servletTester.getResponses(reqsBuff);
-        response.parse(respBuff.asArray());
-        
+        response = HttpTester.parseResponse(tester.getResponses(request.generate()));
+
         Assert.assertThat(response.getStatus(),Matchers.equalTo(304));
-        Assert.assertThat(response.getHeader("ETag"),Matchers.startsWith("W/"));
-        
+        Assert.assertThat(response.get("ETag"),Matchers.startsWith("W/"));
+
         return response;
     }
 
-
     /**
      * Makes sure that the response contains an unfiltered file contents.
      * <p>
      * This is used to test exclusions and passthroughs in the GzipFilter.
      * <p>
-     * An example is to test that it is possible to configure GzipFilter to not recompress content that shouldn't be
-     * compressed by the GzipFilter.
+     * An example is to test that it is possible to configure GzipFilter to not recompress content that shouldn't be compressed by the GzipFilter.
      *
      * @param requestedFilename
      *            the filename used to on the GET request,.
      * @param testResourceSha1Sum
-     *            the sha1sum file that contains the SHA1SUM checksum that will be used to verify that the response
-     *            contents are what is intended.
+     *            the sha1sum file that contains the SHA1SUM checksum that will be used to verify that the response contents are what is intended.
      * @param expectedContentType
      */
     public void assertIsResponseNotGzipFiltered(String requestedFilename, String testResourceSha1Sum, String expectedContentType) throws Exception
     {
-        // System.err.printf("[GzipTester] requesting /context/%s%n",requestedFilename);
-        HttpTester request = new HttpTester();
-        HttpTester response = new HttpTester();
+        assertIsResponseNotGzipFiltered(requestedFilename,testResourceSha1Sum,expectedContentType,null);
+    }
+
+    /**
+     * Makes sure that the response contains an unfiltered file contents.
+     * <p>
+     * This is used to test exclusions and passthroughs in the GzipFilter.
+     * <p>
+     * An example is to test that it is possible to configure GzipFilter to not recompress content that shouldn't be compressed by the GzipFilter.
+     *
+     * @param requestedFilename
+     *            the filename used to on the GET request,.
+     * @param testResourceSha1Sum
+     *            the sha1sum file that contains the SHA1SUM checksum that will be used to verify that the response contents are what is intended.
+     * @param expectedContentType
+     * @param expectedContentEncoding
+     *            can be non-null in some circumstances, eg when dealing with pre-gzipped .svgz files
+     */
+    public void assertIsResponseNotGzipFiltered(String requestedFilename, String testResourceSha1Sum, String expectedContentType, String expectedContentEncoding)
+            throws Exception
+    {
+        HttpTester.Request request = HttpTester.newRequest();
+        HttpTester.Response response;
 
         request.setMethod("GET");
         request.setVersion("HTTP/1.0");
         request.setHeader("Host","tester");
         request.setHeader("Accept-Encoding",compressionType);
         if (this.userAgent != null)
-            request.setHeader("User-Agent", this.userAgent);
+            request.setHeader("User-Agent",this.userAgent);
         request.setURI("/context/" + requestedFilename);
 
         // Issue the request
-        ByteArrayBuffer reqsBuff = new ByteArrayBuffer(request.generate().getBytes());
-        // Collect the response(s)
-        ByteArrayBuffer respBuff = servletTester.getResponses(reqsBuff);
-        response.parse(respBuff.asArray());
+        response = HttpTester.parseResponse(tester.getResponses(request.generate()));
 
         dumpHeaders(requestedFilename + " / Response Headers",response);
 
         // Assert the response headers
         String prefix = requestedFilename + " / Response";
-        Assert.assertThat(prefix + ".method",response.getMethod(),nullValue());
         Assert.assertThat(prefix + ".status",response.getStatus(),is(HttpServletResponse.SC_OK));
-        Assert.assertThat(prefix + ".header[Content-Length]",response.getHeader("Content-Length"),notNullValue());
-        Assert.assertThat(prefix + ".header[Content-Encoding] (should not be recompressed by GzipFilter)",response.getHeader("Content-Encoding"),nullValue());
-        Assert.assertThat(prefix + ".header[Content-Type] (should have a Content-Type associated with it)",response.getHeader("Content-Type"),notNullValue());
-        Assert.assertThat(prefix + ".header[Content-Type]",response.getHeader("Content-Type"),is(expectedContentType));
+        Assert.assertThat(prefix + ".header[Content-Length]",response.get("Content-Length"),notNullValue());
+        Assert.assertThat(prefix + ".header[Content-Encoding] (should not be recompressed by GzipFilter)",response.get("Content-Encoding"),
+                expectedContentEncoding == null?nullValue():notNullValue());
+        if (expectedContentEncoding != null)
+            Assert.assertThat(prefix + ".header[Content-Encoding]",response.get("Content-Encoding"),is(expectedContentEncoding));
+        Assert.assertThat(prefix + ".header[Content-Type] (should have a Content-Type associated with it)",response.get("Content-Type"),notNullValue());
+        Assert.assertThat(prefix + ".header[Content-Type]",response.get("Content-Type"),is(expectedContentType));
+
+        Assert.assertThat(response.get("ETAG"),Matchers.startsWith("W/"));
 
-        Assert.assertThat(response.getHeader("ETAG"),Matchers.startsWith("W/"));
-        
         ByteArrayInputStream bais = null;
         DigestOutputStream digester = null;
         try
@@ -272,16 +409,15 @@ public class GzipTester
         }
     }
 
-    private void dumpHeaders(String prefix, HttpTester http)
+    private void dumpHeaders(String prefix, HttpTester.Message message)
     {
-        System.out.println(prefix);
-        @SuppressWarnings("unchecked")
-        Enumeration<String> names = http.getHeaderNames();
+        LOG.debug("dumpHeaders: {}",prefix);
+        Enumeration<String> names = message.getFieldNames();
         while (names.hasMoreElements())
         {
             String name = names.nextElement();
-            String value = http.getHeader(name);
-            System.out.printf("  [%s] = %s%n",name,value);
+            String value = message.get(name);
+            LOG.debug("dumpHeaders:   {} = {}",name,value);
         }
     }
 
@@ -295,128 +431,39 @@ public class GzipTester
         return mat.group();
     }
 
-    /**
-     * Asserts that the requested filename results in a properly structured GzipFilter response, where the content is
-     * not compressed, and the content-length is returned appropriately.
-     *
-     * @param filename
-     *            the filename used for the request, and also used to compare the response to the server file, assumes
-     *            that the file is suitable for {@link Assert#assertEquals(Object, Object)} use. (in other words, the
-     *            contents of the file are text)
-     * @param expectedFilesize
-     *            the expected filesize to be specified on the Content-Length portion of the response headers. (note:
-     *            passing -1 will disable the Content-Length assertion)
-     * @throws Exception
-     */
-    public HttpTester assertIsResponseNotGzipCompressed(String method,String filename, int expectedFilesize, int status) throws Exception
+    public HttpTester.Response executeRequest(String method, String path, int idleFor, TimeUnit idleUnit) throws Exception
     {
-        String uri = "/context/"+filename;
-        HttpTester response = executeRequest(method,uri);
-        assertResponseHeaders(expectedFilesize,status,response);
-
-        // Assert that the contents are what we expect.
-        if (filename != null)
-        {
-            File serverFile = testdir.getFile(filename);
-            String expectedResponse = IO.readToString(serverFile);
-            
-            String actual = readResponse(response);
-            Assert.assertEquals("Expected response equals actual response",expectedResponse,actual);
-        }
-        
-        return response;
-    }
-    
+        HttpTester.Request request = HttpTester.newRequest();
 
-    /**
-     * Asserts that the request results in a properly structured GzipFilter response, where the content is
-     * not compressed, and the content-length is returned appropriately.
-     *
-     * @param expectedResponse
-     *            the expected response body string
-     * @param expectedFilesize
-     *            the expected filesize to be specified on the Content-Length portion of the response headers. (note:
-     *            passing -1 will disable the Content-Length assertion)
-     * @throws Exception
-     */
-    public void assertIsResponseNotGzipCompressedAndEqualToExpectedString(String method,String expectedResponse, int expectedFilesize, int status) throws Exception
-    {
-        String uri = "/context/";
-        HttpTester response = executeRequest(method,uri);
-        assertResponseHeaders(expectedFilesize,status,response);
-
-        String actual = readResponse(response);
-        Assert.assertEquals("Expected response equals actual response",expectedResponse,actual);
-    }
-    
-    /**
-     * Asserts that the request results in a properly structured GzipFilter response, where the content is
-     * not compressed, and the content-length is returned appropriately.
-     *
-     * @param expectedFilesize
-     *            the expected filesize to be specified on the Content-Length portion of the response headers. (note:
-     *            passing -1 will disable the Content-Length assertion)
-     * @throws Exception
-     */
-    public void assertIsResponseNotGzipCompressed(String method,int expectedFilesize, int status) throws Exception
-    {
-        String uri = "/context/";
-        HttpTester response = executeRequest(method, uri);
-        assertResponseHeaders(expectedFilesize,status,response);
-    }
+        request.setMethod(method);
+        request.setVersion("HTTP/1.1");
+        request.setHeader("Host","tester");
+        request.setHeader("Accept-Encoding",accept);
+        request.setHeader("Connection","close");
 
-    private void assertResponseHeaders(int expectedFilesize, int status, HttpTester response)
-    {
-        Assert.assertThat("Response.method",response.getMethod(),nullValue());
-        Assert.assertThat("Response.status",response.getStatus(),is(status));
-        Assert.assertThat("Response.header[Content-Encoding]",response.getHeader("Content-Encoding"),not(containsString(compressionType)));
-        if (expectedFilesize != (-1))
+        if (this.userAgent != null)
         {
-            Assert.assertThat("Response.header[Content-Length]",response.getHeader("Content-Length"),notNullValue());
-            int serverLength = Integer.parseInt(response.getHeader("Content-Length"));
-            Assert.assertThat("Response.header[Content-Length]",serverLength,is(expectedFilesize));
+            request.setHeader("User-Agent",this.userAgent);
         }
         
-        if (status>=200 && status<300)
-            Assert.assertThat(response.getHeader("ETAG"),Matchers.startsWith("W/"));
-            
-    }
+        request.setURI(path);
 
-    private HttpTester executeRequest(String method,String uri) throws IOException, Exception
-    {
-        //System.err.printf("[GzipTester] requesting %s%n",uri);
-        HttpTester request = new HttpTester();
-        HttpTester response = new HttpTester();
-        
-        request.setMethod(method);
-        request.setVersion("HTTP/1.0");
-        request.setHeader("Host","tester");
-        request.setHeader("Accept-Encoding",compressionType);
-        if (this.userAgent != null)
-            request.setHeader("User-Agent", this.userAgent);
-        
-        request.setURI(uri);
-        
         // Issue the request
-        ByteArrayBuffer reqsBuff = new ByteArrayBuffer(request.generate().getBytes());
-        // Collect the response(s)
-        ByteArrayBuffer respBuff = servletTester.getResponses(reqsBuff);
-        response.parse(respBuff.asArray());
-        return response;
+        return HttpTester.parseResponse(tester.getResponses(request.generate(),idleFor,idleUnit));
     }
 
-    private String readResponse(HttpTester response) throws IOException, UnsupportedEncodingException
+    public String readResponse(HttpTester.Response response) throws IOException, UnsupportedEncodingException
     {
         String actual = null;
         InputStream in = null;
         ByteArrayOutputStream out = null;
         try
         {
-            byte[] content=response.getContentBytes();
-            if (content!=null)  
-                actual=new String(response.getContentBytes(),encoding);
+            byte[] content = response.getContentBytes();
+            if (content != null)
+                actual = new String(response.getContentBytes(),encoding);
             else
-                actual="";
+                actual = "";
         }
         finally
         {
@@ -426,7 +473,6 @@ public class GzipTester
         return actual;
     }
 
-
     /**
      * Generate string content of arbitrary length.
      *
@@ -499,7 +545,7 @@ public class GzipTester
         finally
         {
             IO.close(in);
-            IO.close(fos);            
+            IO.close(fos);
         }
     }
 
@@ -526,23 +572,31 @@ public class GzipTester
      */
     public FilterHolder setContentServlet(Class<? extends Servlet> servletClass) throws IOException
     {
-        servletTester = new ServletTester();
-        servletTester.setContextPath("/context");
-        servletTester.setResourceBase(testdir.getDir().getCanonicalPath());
-        ServletHolder servletHolder = servletTester.addServlet(servletClass,"/");
+        tester.setContextPath("/context");
+        tester.setResourceBase(testdir.getDir().getCanonicalPath());
+        ServletHolder servletHolder = tester.addServlet(servletClass,"/");
         servletHolder.setInitParameter("baseDir",testdir.getDir().getAbsolutePath());
         servletHolder.setInitParameter("etags","true");
-        FilterHolder holder = servletTester.addFilter(gzipFilterClass,"/*",EnumSet.allOf(DispatcherType.class));
-        holder.setInitParameter("vary","Accept-Encoding");
-        return holder;
+
+        if (gzipFilterClass != null)
+        {
+            FilterHolder holder = tester.addFilter(gzipFilterClass,"/*",EnumSet.of(DispatcherType.REQUEST));
+            holder.setInitParameter("vary","Accept-Encoding");
+            return holder;
+        }
+        else
+        {
+            return null;
+        }
     }
 
-    public Class<? extends GzipFilter> getGzipFilterClass()
+    public Class<? extends Filter> getGzipFilterClass()
     {
         return gzipFilterClass;
     }
-
-    public void setGzipFilterClass(Class<? extends GzipFilter> gzipFilterClass)
+    
+    @Deprecated
+    public void setGzipFilterClass(Class<? extends Filter> gzipFilterClass)
     {
         this.gzipFilterClass = gzipFilterClass;
     }
@@ -551,26 +605,50 @@ public class GzipTester
     {
         this.encoding = encoding;
     }
-    
+
     public void setUserAgent(String ua)
     {
         this.userAgent = ua;
     }
 
+    public void addMimeType(String extension, String mimetype)
+    {
+        this.tester.getContext().getMimeTypes().addMimeMapping(extension,mimetype);
+    }
+
+    /**
+     * Add an arbitrary filter to the test case.
+     * 
+     * @param holder
+     *            the filter to add
+     * @param pathSpec
+     *            the path spec for this filter
+     * @param dispatches
+     *            the set of {@link DispatcherType} to associate with this filter
+     */
+    public void addFilter(FilterHolder holder, String pathSpec, EnumSet<DispatcherType> dispatches) throws IOException
+    {
+        tester.addFilter(holder,pathSpec,dispatches);
+    }
+
     public void start() throws Exception
     {
-        Assert.assertThat("No servlet defined yet.  Did you use #setContentServlet()?",servletTester,notNullValue());
-        servletTester.dump();
-        servletTester.start();
+        Assert.assertThat("No servlet defined yet.  Did you use #setContentServlet()?",tester,notNullValue());
+        
+        if (LOG.isDebugEnabled())
+        {
+            tester.dumpStdErr();
+        }
+        tester.start();
     }
 
     public void stop()
     {
-        // NOTE: Do not cleanup the testdir.  Failures can't be diagnosed if you do that.
+        // NOTE: Do not cleanup the testdir. Failures can't be diagnosed if you do that.
         // IO.delete(testdir.getDir()):
         try
         {
-            servletTester.stop();
+            tester.stop();
         }
         catch (Exception e)
         {
@@ -580,4 +658,5 @@ public class GzipTester
             e.printStackTrace(System.err);
         }
     }
+
 }
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/PassThruInputStream.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/PassThruInputStream.java
new file mode 100644
index 0000000..31444cb
--- /dev/null
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/PassThruInputStream.java
@@ -0,0 +1,36 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlets.gzip;
+
+import java.io.FilterInputStream;
+import java.io.InputStream;
+
+/**
+ * A simple pass-through input stream.
+ * <p>
+ * Used in some test cases where a proper resource open/close is needed for
+ * some potentially optional layers of the input stream.
+ */
+public class PassThruInputStream extends FilterInputStream
+{
+    public PassThruInputStream(InputStream in)
+    {
+        super(in);
+    }
+}
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/TestDirContentServlet.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/TestDirContentServlet.java
index 7cdf939..03b4dce 100644
--- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/TestDirContentServlet.java
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/TestDirContentServlet.java
@@ -53,7 +53,7 @@ public class TestDirContentServlet extends HttpServlet
         String relPath = fileName;
         relPath = relPath.replaceFirst("^/context/","");
         relPath = relPath.replaceFirst("^/","");
-        
+
         File contentFile =  getTestFile(relPath);
 
         FileInputStream in = null;
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/TestMinGzipSizeServlet.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/TestMinGzipSizeServlet.java
index 76ed059..0295115 100644
--- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/TestMinGzipSizeServlet.java
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/TestMinGzipSizeServlet.java
@@ -27,7 +27,6 @@ import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
 import org.eclipse.jetty.http.MimeTypes;
-import org.eclipse.jetty.io.Buffer;
 
 /**
  * Test servlet for testing against unusual minGzip configurable.
@@ -59,11 +58,9 @@ public class TestMinGzipSizeServlet extends TestDirContentServlet
         }
         else
         {
-            Buffer buf = mimeTypes.getMimeByExtension(fileName);
-            if (buf != null)
-            {
-                response.setContentType(buf.toString());
-            }
+            String mime = mimeTypes.getMimeByExtension(fileName);
+            if (mime != null)
+                response.setContentType(mime);
         }
         ServletOutputStream out = response.getOutputStream();
         out.write(dataBytes);
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/TestServletBufferTypeLengthWrite.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/TestServletBufferTypeLengthWrite.java
new file mode 100644
index 0000000..a503e79
--- /dev/null
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/TestServletBufferTypeLengthWrite.java
@@ -0,0 +1,67 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlets.gzip;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.HttpOutput;
+
+/**
+ * A sample servlet to serve static content, using a order of construction that has caused problems for
+ * {@link GzipFilter} in the past.
+ *
+ * Using a real-world pattern of:
+ *
+ * <pre>
+ *  1) get stream
+ *  2) set content type
+ *  2) set content length
+ *  4) write
+ * </pre>
+ *
+ * @see <a href="Eclipse Bug 354014">http://bugs.eclipse.org/354014</a>
+ */
+ at SuppressWarnings("serial")
+public class TestServletBufferTypeLengthWrite extends TestDirContentServlet
+{
+    @Override
+    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+    {
+        String fileName = request.getServletPath();
+        byte[] dataBytes = loadContentFileBytes(fileName);
+
+        ServletOutputStream out = response.getOutputStream();
+
+        if (fileName.endsWith("txt"))
+            response.setContentType("text/plain");
+        else if (fileName.endsWith("mp3"))
+            response.setContentType("audio/mpeg");
+        response.setHeader("ETag","W/etag-"+fileName);
+
+        response.setContentLength(dataBytes.length);
+        
+        ((HttpOutput)out).write(ByteBuffer.wrap(dataBytes).asReadOnlyBuffer());
+    }
+}
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/TestServletLengthStreamTypeWrite.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/TestServletLengthStreamTypeWrite.java
index 6a23710..b769d54 100644
--- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/TestServletLengthStreamTypeWrite.java
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/TestServletLengthStreamTypeWrite.java
@@ -25,21 +25,19 @@ import javax.servlet.ServletOutputStream;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import org.eclipse.jetty.servlets.GzipFilter;
-
 /**
  * A sample servlet to serve static content, using a order of construction that has caused problems for
  * {@link GzipFilter} in the past.
- * 
+ *
  * Using a real-world pattern of:
- * 
+ *
  * <pre>
  *  1) set content length
  *  2) get stream
  *  3) set content type
  *  4) write
  * </pre>
- * 
+ *
  * @see <a href="Eclipse Bug 354014">http://bugs.eclipse.org/354014</a>
  */
 @SuppressWarnings("serial")
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/TestServletLengthTypeStreamWrite.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/TestServletLengthTypeStreamWrite.java
index 5027a4f..da2e43b 100644
--- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/TestServletLengthTypeStreamWrite.java
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/TestServletLengthTypeStreamWrite.java
@@ -25,21 +25,19 @@ import javax.servlet.ServletOutputStream;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import org.eclipse.jetty.servlets.GzipFilter;
-
 /**
  * A sample servlet to serve static content, using a order of construction that has caused problems for
  * {@link GzipFilter} in the past.
- * 
+ *
  * Using a real-world pattern of:
- * 
+ *
  * <pre>
  *  1) set content length
  *  2) set content type
  *  3) get stream
  *  4) write
  * </pre>
- * 
+ *
  * @see <a href="Eclipse Bug 354014">http://bugs.eclipse.org/354014</a>
  */
 @SuppressWarnings("serial")
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/TestServletStreamLengthTypeWrite.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/TestServletStreamLengthTypeWrite.java
index 5ca3ad1..3442e4f 100644
--- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/TestServletStreamLengthTypeWrite.java
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/TestServletStreamLengthTypeWrite.java
@@ -25,21 +25,19 @@ import javax.servlet.ServletOutputStream;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import org.eclipse.jetty.servlets.GzipFilter;
-
 /**
  * A sample servlet to serve static content, using a order of construction that has caused problems for
  * {@link GzipFilter} in the past.
- * 
+ *
  * Using a real-world pattern of:
- * 
+ *
  * <pre>
  *  1) get stream
  *  2) set content length
  *  3) set content type
  *  4) write
  * </pre>
- * 
+ *
  * @see <a href="Eclipse Bug 354014">http://bugs.eclipse.org/354014</a>
  */
 @SuppressWarnings("serial")
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/TestServletStreamLengthTypeWriteWithFlush.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/TestServletStreamLengthTypeWriteWithFlush.java
index 7a595f5..b49761c 100644
--- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/TestServletStreamLengthTypeWriteWithFlush.java
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/TestServletStreamLengthTypeWriteWithFlush.java
@@ -25,8 +25,6 @@ import javax.servlet.ServletOutputStream;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import org.eclipse.jetty.servlets.GzipFilter;
-
 /**
  * A sample servlet to serve static content, using a order of construction that has caused problems for
  * {@link GzipFilter} in the past.
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/TestServletStreamTypeLengthWrite.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/TestServletStreamTypeLengthWrite.java
index aece900..26b3fab 100644
--- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/TestServletStreamTypeLengthWrite.java
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/TestServletStreamTypeLengthWrite.java
@@ -25,21 +25,19 @@ import javax.servlet.ServletOutputStream;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import org.eclipse.jetty.servlets.GzipFilter;
-
 /**
  * A sample servlet to serve static content, using a order of construction that has caused problems for
  * {@link GzipFilter} in the past.
- * 
+ *
  * Using a real-world pattern of:
- * 
+ *
  * <pre>
  *  1) get stream
  *  2) set content type
  *  2) set content length
  *  4) write
  * </pre>
- * 
+ *
  * @see <a href="Eclipse Bug 354014">http://bugs.eclipse.org/354014</a>
  */
 @SuppressWarnings("serial")
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/TestServletTypeLengthStreamWrite.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/TestServletTypeLengthStreamWrite.java
index 7c27732..c27d819 100644
--- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/TestServletTypeLengthStreamWrite.java
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/TestServletTypeLengthStreamWrite.java
@@ -25,21 +25,19 @@ import javax.servlet.ServletOutputStream;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import org.eclipse.jetty.servlets.GzipFilter;
-
 /**
  * A sample servlet to serve static content, using a order of construction that has caused problems for
  * {@link GzipFilter} in the past.
- * 
+ *
  * Using a real-world pattern of:
- * 
+ *
  * <pre>
  *  1) set content type
  *  2) set content length
  *  3) get stream
  *  4) write
  * </pre>
- * 
+ *
  * @see <a href="Eclipse Bug 354014">http://bugs.eclipse.org/354014</a>
  */
 @SuppressWarnings("serial")
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/TestServletTypeStreamLengthWrite.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/TestServletTypeStreamLengthWrite.java
index 4b4c46c..4c0ae3f 100644
--- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/TestServletTypeStreamLengthWrite.java
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/TestServletTypeStreamLengthWrite.java
@@ -25,21 +25,19 @@ import javax.servlet.ServletOutputStream;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import org.eclipse.jetty.servlets.GzipFilter;
-
 /**
  * A sample servlet to serve static content, using a order of construction that has caused problems for
  * {@link GzipFilter} in the past.
- * 
+ *
  * Using a real-world pattern of:
- * 
+ *
  * <pre>
  *  1) set content type
  *  2) get stream
  *  3) set content length
  *  4) write
  * </pre>
- * 
+ *
  * @see <a href="Eclipse Bug 354014">http://bugs.eclipse.org/354014</a>
  */
 @SuppressWarnings("serial")
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/TestStaticMimeTypeServlet.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/TestStaticMimeTypeServlet.java
index f9e19e5..a246624 100644
--- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/TestStaticMimeTypeServlet.java
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/TestStaticMimeTypeServlet.java
@@ -27,7 +27,6 @@ import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
 import org.eclipse.jetty.http.MimeTypes;
-import org.eclipse.jetty.io.Buffer;
 
 /**
  * Test servlet for testing against unusual MimeTypes and Content-Types.
@@ -48,7 +47,7 @@ public class TestStaticMimeTypeServlet extends TestDirContentServlet
         mimeTypes.addMimeMapping("tga","application/tga");
         mimeTypes.addMimeMapping("xcf","image/xcf");
         mimeTypes.addMimeMapping("jp2","image/jpeg2000");
-        
+
         // Some of the other gzip mime-types seen in the wild.
         // NOTE: Using oddball extensions just so that the calling request can specify
         //       which strange mime type to use.
@@ -70,15 +69,11 @@ public class TestStaticMimeTypeServlet extends TestDirContentServlet
         response.setContentLength(dataBytes.length);
         response.setHeader("ETag","W/etag-"+fileName);
 
-        Buffer buf = mimeTypes.getMimeByExtension(fileName);
-        if (buf == null)
-        {
+        String mime = mimeTypes.getMimeByExtension(fileName);
+        if (mime == null)
             response.setContentType("application/octet-stream");
-        }
         else
-        {
-            response.setContentType(buf.toString());
-        }
+            response.setContentType(mime);
 
         ServletOutputStream out = response.getOutputStream();
         out.write(dataBytes);
diff --git a/jetty-servlets/src/test/resources/jetty-logging.properties b/jetty-servlets/src/test/resources/jetty-logging.properties
new file mode 100644
index 0000000..6502639
--- /dev/null
+++ b/jetty-servlets/src/test/resources/jetty-logging.properties
@@ -0,0 +1,7 @@
+org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
+#org.eclipse.jetty.LEVEL=DEBUG
+#org.eclipse.jetty.servlets.LEVEL=DEBUG
+#org.eclipse.jetty.servlet.ServletTester.LEVEL=DEBUG
+#org.eclipse.jetty.servlets.GzipFilter.LEVEL=DEBUG
+#org.eclipse.jetty.servlets.QoSFilter.LEVEL=DEBUG
+#org.eclipse.jetty.servlets.DoSFilter.LEVEL=DEBUG
diff --git a/jetty-servlets/src/test/resources/lots-of-fantasy-names.txt b/jetty-servlets/src/test/resources/lots-of-fantasy-names.txt
deleted file mode 100644
index 50ba170..0000000
--- a/jetty-servlets/src/test/resources/lots-of-fantasy-names.txt
+++ /dev/null
@@ -1,3000 +0,0 @@
-Deglajedivo Umaif Eheab Kracodroestrisk Edebra Wacroogrutemif Ahegreegroinaomu Facast Umareoyupoke Aiskokir
-Ollu Oipeuse Idoxiuj Aefoomiko Phukomaskiik Frov Aeslychab Askem Ujegiod Eautoum
-Apouhustaosk Haiglipluhedro Kluskirkeefraeg Arar Frobeotow Iucoj Teet Ewikeke Ohush Oaprili
-Veosluboajig Up'wu Sickau Kibah Ribu Utooj Coclobrorew Veteph Odrugio Iihodap
-Lofrakegh Nuglijewaej Zuweau Afafrofravoibroa Hozaubeghim Oociphoraju Qu'skawi Oskabroseasish Ovoteglaegruc Aadracatreaduroe
-Uyo Ukacagebob Yusioreafuru Osrimagoe Piashoph Azodreglussioj Joofoowhe Caojibil Eflackao Sugelaagry
-Oesacroud Wameera Awofojetriad Odub Zorofisoshoic Ilazi Itawhisequoup Fiha Iuvionugrebeusa Udajauw'fressi
-Ofufrelobii Defoh Aivaristuwuv Naaguboyif Ubiskughuroske Oonko Vasiit Ahitrokliges Wewhee Mefleta
-Skusenki Dusetosrefe Baewauwike Kriothigraliam Quopallaskex Sehe Pid Neejuhaipeja Woskiscutufaf Ribrushoesauslix
-Uphobet Uvuki Crugeakocu Ukre Beplal Cakodrifi Edrou Ibickistediumi Cark Abughullomihu
-Tisla Prumipunish Ava Xawheclogorkut Asi Mouvisti Iidezoifross Gewafreklipii Hoph Ubropajee
-Aojaitoo Aev'mophe Afruc Eprocehaackio Fawi Iicellegooj Keefan Lodaudredrel Oninimoplode Llar
-Aelu Ic'mu Caivuckillad Elirastu Obathe Itichowhan Nogregrepharioj Navude Asheovofelogly Raplirkow
-Helidraossifli Ookophamigapau Ukemophalooni Slaogh Rith Graoproi Illojeo Ana Whikreud Ickit
-Bekra Iaphosliritai Ekio Oproijuluskob Emaistra Oyonkiijeerk Obeekrevor Olecheh Glialasymeaubo Ougeakij
-Zive Bof Iubougrel Whodastravudio Gijiofrifroufail Eoklusiokoquavo Edasaomadoi Ustrece Shaecu Froifujostrahut
-Gotik Oexosrafleassu Whausestaub'gee Eve Ecky Foewhatrucoerka Usliugriclekod Oaclidra Uquefraohuwe Laemidup
-Pic Ebruslef Crigrode Hypretuckuk Foclestenaewa Iflog Iyossaocosc Sastew Egiquea Skeafle
-Esank Agi Ikiovol Cosaroawiadi Oegresabrug Sleukelloihefi Obibii Dubi Ibehogoma Rugrockorelleock
-Flileuw Brutistra Abetoe Shopafokica Nille Greaumubrawhia Eauthununeeshewho Frassipock Adrotrio Uslikrabusk
-Eefeowaj Ameejalu Clucho Auwegriodrayoofle Busiibroegraegrav Vaifla Kraiteaugeh Sciy'g Ulawhiim Umishujoichu
-Oriotej Clejiocifaizeauth Oobreaupej Daguhaidraca Elenevo Mulowayi Ocicago Ikausark Meklafla Ubrofidre
-Strureesreaulau Seauwackinae Scyl Laski Onow Takreedeoc Furaavi Ciskiatistre Otisrooyaox Grokruhod
-Eskeuweusuflai Opougasruhiack Llossiwejearau Euquularoecani Ibrinuheauxi Viulistrepash Illochoif'cl'ck Nahaefraza Gabii Ooplisaslol
-Backaslai Frajo Coitraeckoafrioli Apre Dekrukadrev Omuk Sopuco Friphoajao Abreekush Assezoichig
-Aiskas Vitavedeg Iastraca Kliklopaacibirk Eemosceukrewi Bilep Llujiah Yiflipufek Aviobrahav Edrauhukaged
-Gitrudussano Orojato Agaci Uxitug Onuneockige Fislearopof Tooy Ecefri Egrick Idressevuy
-Retodruskeno Oohaug Ulucev Owawirurubeau Ichet Iake Anozofritrynk Iwuv Grirashasoo Troneaskuphaip
-Udre Unoan Aridashochi Coukeogee Drakiurut Noxowi Atawi Iaded Vifrabajikab Auslowoichas
-Led Egrileuslagacu Krisc Iukateauslo Skan Ash'plawikiu Ibiv Ofoikroibanipha Ellokriifrequacku Ipedref
-Baepaivar Cush Ozeho Opokloburuck Ecashia Agrochym Evochih Gatukotu Rufo Evubi
-Paebimeach Asti Aufifaiwigru Dramev Oshosc Waachiajeukriujog Pudubriplequa Stihi Ida Lulislirk
-Ryf Haipaej Dilughe Ebres Eroskup Ibu Fico Uclufemechuh Kushir Owaihebaeklihe
-Imae Ileg Aegemicheleeh Oplaokliwefatia Tofeskev Hobrillialuthij Eenenk'wat Oskacrayoesc Stresou Ecru
-Flook Iiskekuyaucrank Aixiaten Cauvihuw Kodr'josc Aackoos Neumec Ajomop Giupou Ealeo
-Phoh Easciraet Chunke Aslaanankou Ifeausussii Oplos'thudu Echokrudraibap Taklu Nos Oleplapaiteclo
-Uhe Griskick Hiwasoikudre Aseclah Imiitekoo Fafoquu Luceho Betroudoch Coclunophoapla Shuwudroleau
-Efidej Plafruvaijihub Aprifraclom Iquaol Hest Shebusti Nakikadrunonk Oadoklethaj Boataik Rislehoukr'viul
-Drusoeteabuwa Llopopeh Efolemuhug Allowooteo Ife Xisedruf Pouhanawo Aapuslukraot Uvaw Brik
-Kreyoa Nirkidemea Aslaploazi Agraobraasoe Set Ouscauve Epidrog Quoomustiochogru Ubauthi Oehougraoch
-Oegreubitibea Linkiglonajai Krir Bylusoekraclauf Ipucroufigaquu Orkeajaagh Eplakaflujoah Woiskollaasaroul Awhapyteshagh Streva
-Outawefefaigro Apiughezaugoj Kistiveflak Evakridu Gaxoaga Nurodi Driuple Umouch Igriol Nauplusrelao
-Opagha Kroiloodiufyj Itaebrapifrogi Ofloegratraehass Abequawoogrojio Epluskoir Dosedikrughik Fizushackug Sunillaaskel Taillivutito
-Ukodale Egli Ufak Phickeukli Prouchucrughoklak Zivaepe Eemu Eclishavibaota Gleamankuni Ogogeoyoehock
-Thuplucko Fraweauthen Akriph Kropaasapubru Sabroxokri Beetriw Eesap Auchakiuseguja Iwoumock Emeni
-Whachiixi Odratapehee Aodradofamoick Aiha Firefroglav Avucra Ehoastroth Ibrus Icogyv Eniokregle
-Leaupulliishee Eogeemegleacri Whur Phuteveopluc Reuhiw Rihideklu Elliore Hos Teesugror Grelifau
-Phoneauniropheah Daglod Bass Pucal Iokoy Pebregevoaw Arkefisa Whobideewisao Ubreexuwas Testrekroej
-Gadriakaiphe Sobe Afrav Skeochup Imefri Sriyed Eapest Hepob Aemakra Ohank
-Slirkoubraopri Aevoxik Hobryn Fiw Gigleghousrobo Oufra Imerocron Ikrealuho Nuplussii Maveeludo
-Eaugroluvoetug Aapiprokiw Ofrazig Grajeauwovewa Luvigre Ifrajochinkea Echepaj Ghorkin Ocleowiglallip Igroprodi
-Huklino Igrikreze Un'p Iphodranko Yachusrofuwhu Icraenoflilik Egumeosh Grufregrass Sludetru Ipuk
-Kath Atestithu Dima Sror Igrip Sloejomu Osselawepraa Laclaanaphi Kiraatryphota Thiboc
-Reedriuv Kraanicravoux Eaudepiidriodri Enkodih Uskegidruditaa Oviacotaewobeu Theskoeyegraeckood Eaziloviveup Grawavarissi Adi
-Chonyy Ekrib Esukrasibipu Cafiag Ustuckaackeome Laubrafup Ogesio Ailowoestrush Oclafefosapru Gleuphophicloce
-Haurusekrir Awug Cynosigheuf Wuckike Ijapepelik Luthubeedro Ufixegece Diniriu Epobuwocifo Wuroria
-Icethiibocrif Aakraquiir Gren Liwocoom Avo Phubomoe Scaexevaith Xupripeotokra Tidruleso Creuthoxawark
-Naoj Iinib Daackailoocloutiss Ica Ujurawi Iwitran Stixamun Hukaessosce Ecaroskew Iziaghenkiu
-Aagrissu Eske Prix Igriskegibiji Quagrasragoh Azeceoho Ithuveaph Reuluplosohi Ustor Osratoemikomu
-Froh Brup Adafethaky Owaghuj Unequusib Ijoom Reodrockow Floeb Fleadevew Odefeowhishar
-Ewewea Trescocaph Brokledaflucu Ocytameetrosc Outo Eruzaglosu Wenudowox Tacloklajib Iphayuf Graeh
-Jeodanequoo Jothuhykuw Owac Eefurafra Aibrefeowe Coredo Iasauleocher Faoliujorkee Esreorodrigh Arifrorashu
-Anaco Dredrifasku Feuph Krowackil Aklu Ushitro Cageaub Kophaude Shaocano Fassollurej
-Awa Ecibepocka Orkowharustraube Oebogeunik Geshupheameollosk Uri Oghojazeb Draeg Ecrihaveuxoeki Brehetreslifo
-Tot Irkeflof Ibrifrisrai Ossi Apoickel Slohae Sloskunkoc Ivew Uve Oawhifranko
-Oupaflumaudri Flekaewhad Oaweonuthimutheu Phiuk Eorujufoikoer Efryboskuglouji Aemeaunive Anoph Heugrebimoogra Ofrapr'weerke
-Weoglosteule Nuphityba Vegeon Uphibromamu Braheodrirag Eosizoceobiwi Emoeskan Rijinuhaog Ojiusujo Uhaug
-Aapleometh Jupouwustecyy Sriro Afribujio Oiveaume Klucrenegreu Oehoglikogih Sroarukukigum Agrikoskubi Iroupeudeesky
-Kofabiplojous Uje Stoashadeplask Aphimigara Cochuquum Cibreakeo Ihobedrejus Estotru Gohiacru Iudrestricra
-Vigroaslughir Ahotacke Nati Abew Frunadewhur Krecostoareeti Egifl'manit Cluwik Ooflaabibi Era
-Aicodregicapra Lif Gavobevedrae Odrinkastriamau Seniob Ustechitugaghi Efro Iwequefagluwo Ostro Wut
-Ostromidogru Eploeyaca Br'bub Wanusheaudugiss Ubochewujoe Gisraplynucough Epeaje Lliibepronedeo Usassaonubrauthou Abuwouth
-Iulepiwucli Udrasywhiof Athetogav Gixoefi Obrile Quuji Ceque Agabra Sulo Asaebegapesra
-Kamaslysl'wiuy Livo Eakrejossauhouv Oidriquav Llossul Ekloughio Ajejio Stodoudroobrirk Solisiopura Agistaplib
-S'drisiusrap Drimag Equiiraakriz Tatai Utequa Poacaje Ugoiwe Drafotrec Jeheroe Axop
-Ilackachuba Flic M'keucassiikuv Gethiog Uceeseu Eweaussuthobith Bufoskuklu Obuquineau Obu Aenonoovona
-Ougrianeoquov Iinewidin Whuhopacrugea Kreozofroru Judipriboopiv Eoglistouf Reauclepiosiiwiz Aicihajin Welanu Whoiroru
-Ecumumij Joib Iicheaflirunosti Tudiklupi Teskuhe Ugevauquophaat Uyeo Mikus Cassumi Ceochaasli
-Igaosi Obigekoj Vowostyghu Izecruvoififi Askid Miugrukridrasceck Ocrage Oapoajaepheshu Ugiphesropepeo Geaukril
-Egrehouklaf Aflyghuwu Safroabrifao Kukrukaisrota Ekroubokreficku Kiuyiusliss Warazoghust Fluc Aixoebraicroa Dreog
-Ubeebru Oogluperonudri Tegreeglechodo Aoreaceflenac Ovowhoxai Craurucascaf Ithoghafrif Ihoj Civ Bugolotano
-Egrotetuh Thaogiozoot Wojipi Iimiuskeuph Krirobratob Istopodib Eotifriocryhaf Aflaackufare Eerexebenass Estrifroithaaquaapra
-Griskeanoi Odriuh Ulaekruw Uquaje Fofetoju Fori Inkii Ysitemojaque Anesihofouv Hefryhoi
-Kimeocink Iabraotredoneetre Orkoduvisk Oc'gipistuska Ucoackaevus Vedoqua Crisk Owaaziulereaupoe Slaheneaugeofri Seeghepaagox
-Icaw Bipadremoeth Abe Ofraujizoc Iuropluxoeflu Uhighush Ehosuck Lakelu Prowukrasar Ouprubibe
-Skapri Vaoquilumiah Oshailicke Iflaekronk Esoicrau Josletivaiv Ileemiiw Eupa Emov Saazu
-Eucuclaeke Ufru Oag'wilusu Wikilaus Dopefrugrihil Crenabu Br'goa Yunkiisunki Tiizighu Odriubifreneunkoe
-Easligariateve Atiapo Kroalae Akrinojeauf Ipitri Driap Idepysk Ughudro Udadusuyuf Juba
-Eucimeaquad Ocoegru Kriir Bapleukaonoip Iugli Pogopacoa Fl'drisuss Aogidisroih Uchoifimisc Otruckass
-Osirkuwagaal Siiglee Yodaiso Eprupape Ofaaj Edrajobro Glodrarist Pop Oiflocun Klewew
-Yesesia Ofuwi Opaamugrurk Tifliy Uxauju Eglafipreufriwhi Uwotoas Kraflin Ireaudiugrae Equideklollek
-Afliulluflawapa Sleceli Oeslepab Fregh Kescuhaol Emiajeuk Ebeubraewu Onkeowhofaruc Ujumewheesh Sasriisima
-Ubibughankoiwhu Tofub Krarku Veglaelluwajum Fliflemiogal Ehathurebeu Avakrotissimo Aetroankax Ibrip'gluchuk Loworez
-Eekeguhahawo Coxi Iphaitaw Gacubeebastrou Oanipoe Eassofrepu Hebi Ochoj Fooghifoog Iitrib
-Cistro Eskiihaofru Auhiwobaen Iwhodita Ifraruc Ilabaremo Orekic Efrah Opefrazecrema Flonioboquubre
-Srum Hyj Uroahem Fraskuli Taaheucush Uproeteyily Henk Urebragupluto Ouvesh Vowiiroedretroe
-Gholeaflonao Aucreckau Ekiigooleegromi Voistopiaklimar Aokauflybocaku Eaupheaquuwissi Viacke Nank Zimadreklesee Ec'hudiflae
-Areoplibaucrak Ala Yad Fleacenkaar Ossumouskiskeeg Ucoafik Apreaubiab Oghaoghukla Viuziose Eeplikenomaidu
-Ackok Bod Eebi Ooheogrebutri Sheosia Istrostrimeta Graucijoachadao Askyjunoubim Nubratist Pupoestralestreth
-Xeaucamub Cenu Tozafaloi Meleaudo Raofeau Ghoghau Caeckoh Vivech Ozan Brukrurex
-Oskajoe Buwalecegra Oirebik Hit Foeg Ichemabrufiugu Ukredrotacki Aobistid Iqueuboca Yfrurkadeo
-Ahelynkod Athou Ostreaweau Awhawhud Nanegyduju Hos Icliflesk Unaliufeeploi Joiplo Iclelefeorum
-Vabucesruth Ecupywhisaoga Grejoiskeglag Iudolloufo Tessadrankerk Dugraa Beustro Ulliolleodoo Zaafaquao Oedaiplugrafup
-Ceereechoovu Fiulib Ocrokloe Aflojefal Vesreod Eocroeskacogri Uchaicri Iobrishosash Brifruscew Awhobretrugej
-Moola Iuckoduy Yekla Cotaruditi Oovoifre Yyaretehe Alav Clakroaj Idivaslu Caubaedim
-Joushejokre Kraleoklove Caawov Ypoplidaga Phinakaaki Ickesleausupo Slekauhystra Rece Ugo Esciafayuhi
-Majau Eghi Eguyasea Ivuj Uglaed Jamadromu Cren'tiipav Ustosc Roiwugh Afrikruchess
-Vianoej Eauwafreghosro Urukupe Ifugraskisso Ujeguskuriita Westraj Haibutobrodraiv Llesleme Riuph Arulisrivabri
-Iathicij Baliupru Ehogrophakok Aadroliibaistyce Feevo Utribradru Eaufakoneepu Sleclunk Iorko Scisloceaph
-Orkinki Soren Jopiikriufoi Aaxukot Oseyekove Obufost Ovioquee Bredaedeabrifrav Quanarkekreviu Asu
-Chafelaudroux Vixetoo Wulofluyadish Strubughash Loun'z Iahoulledrat Usliheduwuste Oofri Vame Ewuwilepliofro
-Oxasibot Esy Eullaroudufeec Ouhebeaurkag Eocruvodusikru Shof Sloziuskiipukeu Dobut Asogroc Denodreubosku
-Inek Oidukiarijinka Ufiuchybeme Dropecrogost Okiocreshif'da Stikriji Teghucregaastri Domakreocaz Uquavucihisli Islobrosc
-Pucaothekillank Peautegra Aafow Epoxapa Glask Beaufio Skulelane Saukraseuthoslia Ulikahink Neslislop
-Laowhiloghefrif Uticrafi Ekrugu Tiiphefajosso Iokoagunumee Katawhe Eckejighoo Rawiucrijosauk Elevackivado Cheofevika
-Iuflovealuchu Ebar Phor Ymescoowai Itaatifi Nestosseflig Ila Gaclogellu Claeho Phoj
-Broaflouphemeeci Odolaskii Jochokliiginka Auslaru Eckaickexi Srafickewhobri Elicirk Wistiviheauv Jegoriclostriar Aokrek
-Egrafraonockeho Uniabreokusab Jastra Eken Cruj Eequedo Mocraagaehaas Kehowusu Eaucrestatoghe Afrowoeprou
-Wed'y Abrodouk Ija Edakroipe Yuthuhi Poohoa Mewegafon Adiirkuchoghar Ankakugleaujee Gleaugookiw
-Iheugrab Igezixyv Useuhi Diloac Uklougadamiwu Huwovosraab Imuhiojiu Friveot Pheheaumupuchasc Ipe
-Gribraiseb Anesk Veodearofloghoick Loakivu Ileane Iskoag Esa Kaklarkum Egikisraig Doustraayi
-Itrigria Acku Boasteatako Oleflu Fustrianustrih Ejitekeso Isheghosada Aizat Efighak Kapatat
-Ikiskevuflea Lugejosrowaan Kislovoproch Luskoenufroc Ogli Ofeasli Fleuhogra Uha Oikorajapu Iudoonk
-Iipax Klebo Vitucebe Arkofaano Lliome Efralefikre Utith Exicraipup Gogaebuflo Evupii
-Ibrigruhotufo Ikrollea Ujax Nefleaukaofigreen Euboa Ciovickaistaisc Aoweslopi Otepheobeaureh Whoekecegiwho Ostrepi
-Aloirkaediklej Llesceabrohofra Uwi Edegathucoa Ucileb Diwakraofreca Tukaoyiubra Sodrostrustun Eogu Sisi
-Owe Sum Idaewub Brateroiph Ubrapegrir Bude Clehedivufri Aabiislapu Strezodripluk Opid
-Llegruru Tetheo Quoutuliipap Udrisraigawai Piufoaw Iora Efastredruc K'scaikrotry Shesef Aojutracon'sho
-Hesesh Geaufrasinu Iusaquuv Joveragroak'nk Myxigi Thoki Weghiv Kaufesajenysc Quuckiatiu Lirine
-Hubrufugiugleck Jenofla Ofreuk Oli Odiijitaithosy Foaniu Clusro Vom Ijoaskitawovee Eofykyscoeky
-Bipodip Pub Osanifiawhi Slovu Uscoubricig Styjichaki Agloarubibraug Breaugleozo Ebrohogu Jupif
-Iastroivushuslosc Ihemi Erae Uhigrunkea Ulopre Nareajahajil Growof Ukruc Ostrogrask Hok
-Goachuloodrisk Iruflaosruv Pearechiurkemog Em'vu Pekabist Oesosovu Forkoovubiigab Itacrepruhelli Viuflidi Aape
-Bupo Scasitach Eefo Oarivaamisle Odirkiw Cerikristalep Iribioba Ghokiosre Kuyeporeushur Frukluwughiaw
-Ivor Riuchustagriflo Muw Tret Abithoevesiub Odriofrer Kreseligyt Oibaogriawagro Ocamog Plasallaedon
-Givo Aebrudidodoj Teolaa Miyoopiut Iheo Oesafleuf Ofriakulistr'r Ov'yav Ethuhod Avujopaima
-Staru Ciufeuwoje Phol Lecullec Froashujoa Rabihogahig Ploat Voogeanov Oscanu Greodesii
-Draijisrath Naicehesk Xeleafikep Eellessiith Lelefroizupro Ghoomocaecij Atrirucilaako Flelloim Praigrekrijen Ukroifraostiv
-Istofu Okividetidru Issastreaubo Oucheyeh Riissalosti Apinkeeta Briwuflox Shewhusagroawhao Maaplitur Ickadreow
-Sriweebeluch Sestekriweaunot Ghumubizu Stoehoiw Opoagruwhu Eceopre Apio Useleetaakraag Credaotociikea Asuku
-Trejafriude Agraenu Ewhiiroxescae Iotrekosh Aquasre Ocliuvisu Luseh Tavuro Oankughescukle Aufoa
-Epexe Otroowhollebaun Ziosixodria Oceubi Phedriar Ybetiskius Badibiwou Mugac Caiyeosookesa Gev
-Ifeb Drehilipai Askam Ofethiiy Kewigrimi Mouphucaqueau Erosleauyov Claassighugaslioy Yvaikiahoomep Oglaj
-Pyssipa Aesravomi Futhu Pawotubronkoi Ookej Seclovojaix Afeeyiv Toseau Yuwiph Oru
-Kifraovo Enkuskaphe Ojoakrood Loetrabraadraiho Aodrutamiugoj Dret D'glilunok Grefiki Orowa Uflyju
-Eadosriadiav Tikodridros Ghoigu Oedew Rofloticoe Ebudaf Ouscopaibesso Aebi Noulligik Aklu
-Oukaekestre Ipho Skooseauju Erkiul Daglaicihafrark Bifriajabiclu Agebon Ekrovodeau Friaskelepryzu Hat
-Becephiuziul Leeckehoclaco Idupruprao Esiditriv Grap Icapaviorka Vescic'fe Ussadokreclaph Grevumufon Exachubog
-Ulaboj Aclo Cey Kus Ereenuratee Cugiovavoru Rogaleclir Mipecheaufudon Joghoz Keth
-Echufresenk Phitafa Loigoci Edre Voshahos Sleaxubasse Euhuxou Iotanoiquuxeuh Imosoloachepa Ubrenkiigh
-Geeweadrioque Thaslojidryso Waafetiosta Ubroinakrem Groaxich'crio Kefradoisosi Tiojigeg Oadogepisera Skih'strinu Goiz
-Ogotoakuki Peneniodriku Aipheaussidae Enkoch Raclithik Ipro Ibrulauphastaucre Klar Oicaonyf Leeche
-Assaifemi Uraboapaidae Odokecoa Udrukiamusuf Ageatouplof Conkinu Ustosraa Ijaut Ellususcige Grub
-Atebro Urutaostrichawo Tesceb Oeboe Brapamogh Onizi Ouplep Krev Aulellise Rick
-Eraayeposs Ewha Aisuprebru Frek Aiquimofevo Chigrosle Brisonounkare Iapephijiu Gifiwach Earkutaenuwaab
-Pojufeag Jepi Irewhoufri Lomoorairketug Kum Ufeumegaf Epludeuck Imut Sluglaad Hofitugarae
-Iosiosraidio Eautoquefribast Rostobu Srow Ebagisoubolli Tuwaaskeskan Asloceeck Zegiunkosher Oeclobroizitau Oejoislaovas
-Troudr'c Declicreecliserk Kam Naofrenyti Locescusriideoj Xojad Odrikrujautap Onkini Okriw Eaghow
-Riop Post Egri Shunijejinou Iateastraghellim Ibo Drastragiawoek Oofeke Jestibreuloogheauw Iibuvij
-Esteva Streegraopa Srimiufedrafoa Aegabugeeve Ooweutop Vax'j Assillamih Icic Okroawaaphea Aescebruskootobrio
-Euwo Garub Phuhigevousk Dacelleauwhoslu Auzakutraok Wibrikopriv Pasruskeroikeo Oneanuw Sciichu Asecufum
-Iholessedoir Eckisawig Clomacrobuquu Haide Life Essuphaossoba Purkaphavyd Whewi Oukrigrenisapa Okrepaumacrid
-Sroede Enku Afankebressouf Eothiloamihia Fripreakreaukrooj Ethe Arifato Voiwir Ooslofruboyepa Uwhowochu
-Skiifrav Krubrugolig Stross Elaixowif Oeruguphoy Doqueaujonofo Graedirydahag Abroesre Quegane Efryc
-Ehur Ozaoss Chope Thabusir Oiscocetouraho Aosonkugluki Sreckeo Cliurakreas'jeek Ene Oipawidaima
-Enkacokla Kidao Vumuzoskal Braheauhaub Uvamufraucu Kreefoxaj Isrukia Drecockuphif Steucicoiwi Vuthoje
-Yrebefu Jeobomor Waoracivi Eohaichoenaewomai Puflubrepa Kiohapeausysc Ukrefleji Ilet Ocraclaibriidive Akradro
-Xeujuca Fludiher Weodref Lebenatydrio Wahack Lesrazid Veosamesadem Ghokodaegra Cucodedresol Kushabo
-Omaufrut Chiforicyti Tafrofril Phophapaco Dapliuwhaw Aolladavohatae Poplarkekou Muwa Oduse Aafaplad
-Ebebucish Ipocro Eabrughii Whegi Eafrukoom Steaslauc Ghock Uduyeskapro Breskellumai Ashifafraoj
-Pouca Ogrusiukeakru Ucliisyvonuv Oxatollu Oyeb Ghoadebut Elar Uyoo Ghuw Augrab
-Skuckoskistreuscij Whiglo Sreslymeoma Prov Uniss Ejubriusowe Aifiilusleba Ilegiume Umoviu Derivu
-Moquullyti Awheafaukleo D'ref Cabrainisakleash Epha Odiohai Ilezahiighuf Brushiajohojooc Kriassiishifre Ychufoz
-Skiohufrunic Ohoufophada Hokusogiobeab Drerato Eauscusla Fubran Grusonkihovat Isakrajaub Urku Degaitriakristi
-Seamewo Eerkillauy Aedru Utorutaebo Ajahoiwhokaku Cugheplabreskuc Towashavyc Foelleulluwasof Gassijiiflo Aglawa
-Clessiankun Iwhiglejogh Eefrock Flipighetrassof Llosovicyp Llesle Inug Defriforuceu Awuskiophoo Ussoxa
-Arulleke Tovu Zivikraockiom Onka Oasliw Fleskeau Etroibe Vestraj Hohaujushec Eskofe
-Krigrisres Skiflagronuj Rioco Wufaayecom Thunkouprevugrub Oifrateakreussiku Ewebrois Efresio Ascew Esli
-Freniustreephedan Onanepalliof Friiss Heoslu Augralinup Klethuwibudou Usrim Phaebrogroupaisce Aukeu Agibulestro
-Sadraebrina Gikriwhabeauphagh Iudyvithi Amapucrawoca Thiusij Uthatadrach Egruskudoghet Nibriphou Ulawepeu Akraig
-Aawetrahorun Bracrizojerkif Echioclegeflidou Arofufim'f Iudeausloajiy Hutikimav Prenkegrura J'rio Aletuglewu Okucait
-Asseor Vep Eohavatra Chistrizaayo Drewere Uravoepraller Vaogrevana Ubrosrigraiteak Scogopakrukem Rime
-Iuflokiwa Shac Ohi Usahoscidiin Vogechisrachess Ocreadrejul Essuvebokle Huroscad Waplupovaug Ilussabrelaur
-Vapefoquub Adru Ele Hureudrun Amiustrokruc Uboclohi Enooskybrabe Oevupludotre Skauc Abivizic
-Vevusso Aiflufi Grageabifec Whaostekuj Rowhillod Udugrosusi Acriclubiskeerk Vausuryj Kijoe Soseathith
-Lipeu Oera Piwiubrae Eutobruve Sat Scewhickidracab Mekikillogib Iquenubi Weklealeuchogluh Grostyghisiw
-Diujirofrouwi Ytheauweuf Abeskaido Ustu Pateuglak Oskegaloakeja Ubaafrost Ewishug Ifiad Jykassokai
-Wubroesafroescan Slisao Yaosso Seaub Evoufokraickeck Irotoi Lekroyotho Akovuduh Owaimessoupa Onagh
-Krodrowa Quutaghujof Athedagessa Itydrugrius Leankudri Owullu Ewhabaclecku Wogo Aoqua Okoreosheo
-Imasuclil Slipiglicu Inetagrath Chesiidofro Fafrii Ukeckoijasaslo Kiutack Olloahaufote Aphapauv Quuckesej
-Pluban Avufagraanias Pinkowa Ifikyb Oasikriqu'h Slyfustricrul Oit'na Opi Greushezockeaufeb Preebraussuhu
-Strigal Mokli Idraufu Ostrohakiuwoaw Eplopilebrebo Quof'shod Fribrokrowa Brium Griorarkujeaufraap Hodedowe
-Eenkuwoathithian Emojoihid Dellinkaokros Oxossun Skigrijubruf Eoklosao Queollomoaklai Ukriharoideso Deuhoihillen Adeel
-Euqu'frigreg Nucloidagahog Evycrofosiidio Ick'hojudin Ise Arkeurkisrastre Nihupufaloach Ankoiclaohostoos Frokoclal Ikriark
-Nephuga Peekribastuv Ekrovef Wez Riliapilliko Ubragises Sudis Clulludautramot Gheskug Loirisrudique
-Neakebrum Ucliciliagi Obri Oathow'thuri Iijiarkoowaogrork Unacrenkedro Ostiguprula Okayubeghusk Erujiric Oapeufr'zaefli
-Epu Vuw Geodiijafaro Uke Kriraefriplir Arker Phisreed Ouwaneli Ecoihof Iguphiawo
-Arkacinki Odeuvo Naklisk Oipheefegro Phydeuvabruyi Klal Veckiliivav Yabresabruru Brodroeglaacop Baillivoxaglil
-Kibouhu Treuligum Ucutuc Oucaabiape Strustotaici Ubroiwho Ofarowellun Irop Asiotrai Gomeclad
-Omemokaxi Bruce Odrorkudri Uti Owiokalunuthu Ageubeestroaclofloe Creenkoitobo Oyimyfliw Derkivogra Taahia
-Eauvihonkeowoe Plecocumid Aghuw Ulefeel Atheklaokastu Upiclocockoo Ekraedais Vujuscoecorkuk Tipohub Eofrucawhekujou
-Bugreyomastrup Cikreark Ugre Sescikapiv Gafrushu Aegraushave Lashitrunk Useghan Lucleausu Wilu
-Whothostriboj Bucleut Ejou Loimexamu Bisea Oiyia Esluprialarkef Hiasheh Brazetekoguth Inki
-If'jeauzofyp Awhikiprubinke Ukesiyiho Afan Adroabristrenk Scysk Stajogiklugh Ewaafrekroclaobu Glaedri Akai
-Apheuballelopea Huvetaegeaureo Granossaos Waeglaglaoplu Ojomouru Nasceec Aamuwoj Drecochomeuv Lujesruse Boidrissikrauhaec
-Aivihutrikro Ewi Stradraathiscus Abucub Mukudaesuku Hig'groekath Howoahe Evoloimauleg Lepukrao Cafequup
-Stressaikestriheh Gakobraoluf Draoku Oska Tasuvogourkou Higroith Edifreravatra Uhii Coresan Abreusco
-Deplec Seckigrumiru Quopoarkafriskek Eaubre Ainkaglufro Asafrevosrao Llit Waeleprewickun Brogruckeu Brecuteda
-Inogrechiv Edoi Bac Tiisceglabir Astibuwhenkit Odeoc'strepheaumoa Levigrex Eweopascewhomi Asaistronee Scifocurooh
-Weoreveuta Iipoofraho Nukihale Reaumoshonailog Feaulaiglegu Ofoohogrufork Yokoafro Foithagrubrassij Trustip Sl'brulo
-Dastriraif Jahoackoskeeg Museod Idrasraapipah Guseuseo Iibu Diivazistrynk Odrakaofuslislea Hijewaj Eadradogickoa
-Awi Darkeg Miukak Esojapraorog Fuglo Ekumobelli Hahuse Druradrewhe Shuno Acliay
-Ucaogigluglufo Aroghir Whaopidegluquup Ocladruscauch Emiaplokugrerku Uscubrabeowheesh Whuxoll'zookros Eveekrossiugi Veochitavaowa Grabiutrad
-Utagreoh Egedeo Asogrudas Stita Hothofesoop Atehau Aneplac Saikasivag Eojace Brose
-Eosciheklii Ejivefovaoslea Fiviolusceau Uthobigu Mivirket Ekleubreb Ulaaludraac Kigruvidivez Krigret Lisriast
-Tenkiados Uglegree Epurkak Plehefi Veuxicih Frunoplonou Kustro Asicusru Rosracas Omup
-Freofumoklique Fiyaidipluthap Scechicelock Lebo Asleof Wurke Gliju Onib Usigrechabaa Ijaos
-Tiowucaghem Likaweeflova Grioconock Iigreorustojej Glefojufoecru Grebofono Sag Volu Aifleauthy Aheckokrave
-Fivaceh Eshagirkestriome Uciliclikrikeau Ticrudaapa Sucroyo Lunkebopleupa Doqueki Wetibifadeu Aphiku Ojaefrenkoewhase
-Whuwikloupaucrea Enagriafrexu Iclekliskagre Ekluc Ekoizai Ustreja Hallagegakrank Llasku Whubiclukrish Reduckalike
-Troy Amivuhy Zicruju Rusoufistef Srulau Ujinev Otrugimast Peesraesciojoso Clauvi Kruboc
-Braanapleofrupru Pojaskufloske Muslu Coklahoir'grio Up'vepemoebri Evobimemon Efrovoacloo Ilonupho Sesrej Lutric
-Kota Ifla Klygoprarar Uneredoiscoplae Quosh Deaufojeaut Aweevy Ufrighalii Quej Astrevoscoc
-Iuprassu Gugurkijooslu Iujewonke Joiwhoki Ifrubiofeklop Urustori Gran Oileslu Kraatauyugronk Xiwhacaleo
-Ucaphon'rej Keshayiv Kifrefiufroigaum Boeh Iavacraofufej Aslaagi Ighema Doek'miwhig Boseodabikreau Cheoziitixunoiw
-Ogricea Egegafrixob Ghogludaed Roplousruchaa Friyoucic Kiokroqueeciusoz Ubod Ler Agiogrii Aslowojep
-Iaskucleklifuw Chad Ithinkasku Iveaumath Cupisroistax Lit Ciizoe Omastal Ucreglovifi Acoveniojis
-Ougre Neelomirk Oscugiugubru Afarkaom Gileasc Apliigrelunkaass Ebofrifufrala Avitiv Meessoplorikoeh Castroacheaprac
-Br'llichudroge Aolle Ollivoexudres Ozagesriulali Bochickadre Fucli Ukaotupomos Eaugiiwoevo Uyihamustre Jaduw
-Fikraw Useanadoplu Woriloiquor Udeumostihoali Haok Udejedreu Opi Jilowyfripe Adriodrascozuba Ososkiluhit
-Frauzujeot Hacriwikeub Mime Iolishephoqui Oiphihobrin Iumuth Uwogreastrop Jixuvecikrew Lajir Oscea
-Ugu Ruwigla Honeaum Euskatribu Utrifea Farustu Utoklewhost Otoovaitrubu Bruzucoju Triskafoeckistid
-Eluliir Eemumi Iudrothoom Stririnish Ewayotabec Iickunk Iofuchaab Auckudo Tubr'vionk Oplovikroifeest
-Jiagowistrenk Daclywarugy Wew Ustaca Logo Shaachatrob Chaesaelo Iocaoflitreenev Puxiriicos Ejecrocuf
-Ussacix Ripiw Adeoclioro Moku Bruwusr'fraora Avegre Edosteaur Oquodehidos Eedririsoogioti Stroafrogaskii
-Augheutru Thug Glaniko Clislup Lotharaph Echahacokao Oireauxemaliri Vopiwhitoof Ghosijabruph Owudasabauj
-Bofrig Chocraob Krihiwogi Daslio Kaka Efrurapafi Jotaukoobri Brigrogralug Shegreogleankud Klastuphek
-Aha Ucoufepu Kitigowhaoscov Oucabro Sioprushucli Luloojetripou Uss'skudruv Haowukla Xero Etifrud
-Vehivou Eskopa Iflustem Plaushija Eostutucop Peepleod Eoquo Aahitaaphuwhize Eprurolliriibu Klaoraididrivad
-Rir Ascoglib Oxaplebrewoze Hidruwubropos Eghiquawawec Sogekru Awameedreerakrao Fam Utaste Ch'n
-Essivebub Etrostrek Ostiasreedad Beobap Obugaay Ukozocostreseu Krugefrutogal Scihasleejele Oekagikrur Ekauben
-Ili Oiseheosk Teankudig Jomooke Fregeslev Flegroosliv Wibogru Udic Ugereufarocu Krescudafius
-Ukockuflada Gicavih Heseusleuma Figuglejoji Icodrow Skefrao Motut Allinusrafrosse Ausradrofutaahu Sinkaupuy
-Weohe Wucke Traoteve Frakregoloaj Aneau Duveselii Aojin Ougaukrik Drauhat Floijadrutig
-Phetaochurufur Biulaogah Strokiakrabakoc Akascauciskutreo Idiwa Ipofraallao Kradrana Feumakigheau Eauscoholloag Stigukepitrap
-Usaep Shifiabozu Oekrikilliug Aetusceubra Owhaha Jiuckenkugesh Hodroleprulo Astraakliafrir Ryruva Ifisrikrebree
-Troanku Apatep Hadatathav Ussiuskyfussesli Pabriirk Ulaucke Neb Uturunigha Fruflijiphoek Scegouce
-Ohufete Fassoebiigighoesc Lirudechiideth Oiweasugegreaug Uprost Erigro Efrofitighioco Mehafobeb Biveadoiwa Keteufup
-Erustapaidau Rubethiora Brisluklakrugl'd Apighasoj Eaufej Vachiucumagu K'l Illissakir Anuyiakloot Shiciuke
-Inkedi Ihogriubaego Igripluskujug Telleephuzukrug Sifi Utreclee Uyosustrehiig Ukuvaackex Nilonu Owecaiwimeau
-Mioturasreunk Plestakibru Okivikli Ozask Eciud Ecrukebraz Krapugaglu Hiires Faceurk Ioliijedraisan
-Ewivosceskaaba Frajyy Hullekythugrik Ipugruvehub Echo Emepheuneau Coj Aiflestreboph Phopii Ghokronkynkupaav
-Ujog Cebro Nagh Uchuqueca Oafeagulagle Xequo Wajukojapop Etheaughuchea Ghepabriupat Quoom
-Azautholluc Oxifelirkala Faalutritraunkul Shearkiudreuhoesaa Arkyh Wetiatizo Dadraogrirogao Lumae Cruvuwami Frikriupih
-Epo Ghoagraiglouma Erkiuvisasav Ugebra Udochef Ikluh Briquoniuhoosta Awofaosank Skaphatoek Frifrab
-Hagh Ubre Uyoi Iglifrisir Priomugiziu Lopluvu Exuholafobru Autinuquunk Ekriiquoack Iclacryx
-Eaca Uxaephigro Taufogashi Inasaebe Depisi Greosrustrodreo Ucupasrastre Kretoubrii Aostrac Mudio
-Urkibrap Ati Etirkiobressi Jath Sc'haketaduw Iomoch Olescubrok Skoigaslocky Ceaunisisciske Ranka
-Evosrivu Orauxiuwhu Iitostroniink Lloipleujuskofi Aipaeskobrapoba Ubreebrooricrof Aghuwhii Umomithug Egudeado Crupru
-Evedroslistri Braiseubribreluf Riimi Ukuce Ogloayazeaucru Vixee Jomuglesayej Ofli Kuz Lockicressyquisk
-Podorec Shikoufakra Slikrofrugod Thaful Niufr'fraskol Ujii Oekrii Mebickedaighigh Uglankasca Odreadris
-Aujeute Batipoogusha Krallove Clegh Ufleegibrihic Efiquiryw Esii Frisk Awhux Oaco
-Iorigravi Vuxoipudel Uroushelec Krilytuflesra Eakyju Ceulaskewhe Loafristu Iwhagoogiuc Golupilebik Hos
-Drumyk Aheliubest Eosteupiojal Eebaof Baudrijamouse Pogri Osraopoisickar Sruk Kik Atobankaf
-Streb Drurk Iunkavauvukout Oboplech Oiflapri Okudaugan Udracko Idioyutullev Fletho Besealebea
-Uleawasho Beckeubrodre Eetillioslem Tank Fosleveslobeu Aequofiweepojo Xiog Epleeflus Eteutabici Kawhow
-Uproetrauphuxu Ofrugham Ouckeh Ihihydrihij Trefaghodrowu Ullaidi Lipuja Uwofubipriu Oemurihe Rekutusko
-Yoif Utrarevae Abrobaquev Acheer Nusteaumup Ghupibeeluwoof Maplajuclej Iarafloviuhun Keklu Uwih
-Eemucuthera Akeugehi Ceb Ebrupleh Plavibeara Ibeafim Priskemof Preogri Aanidoe Moprestrabare
-Viviw Ewi Emudroloahap Adefaciadac Ystrucraemifi Brazan Eevocusakriuj Oixamajubrao Ihiislofla Bisli
-Ekagosleobastriu Oullanojao Este Okrupapocob Tefliirocraope Apifacea Deop Oivaaresio Athoigleyejeelu Shetugughawu
-Oefruquibe Ofiliudeucip Kriprauk'fawuw Jiuvecaarex Iiquafreageaumis Oephaiquelaitaxe Eronuxud Cheaponifreabreuf Ehafurumoi Ucifro
-Yonovyh Digholuhau Heoslu Eflabrotrerka Jissiistrau Kipofestre Eupl'koifeuw Weabrih Friifun Ceyusaebruc
-Tuphivivek Sloojoo Aforkeuv Taquu Edrubexab Fydovej Krupaoscokruwuk Iroa Gev Omoedrif
-Eviuf Iugeau Otesisc Hegenaoga Oclek Srij Iuceshemi Dakus Xoecesse Titroasucezeh
-Euruw Shisku Onoaraweaurkin Edazeedrar Wetri Adratib Imoaga Iuwhoshaeshur Ookrofuneu Ashusli
-Nudiwiplil Ukitrapuplu Jionuquuda Ustuphiof Eheajostubes Uvolo Enatobi Focryssaot Ufroglishetro Kifedurabreer
-Oveac Utreaucru Zokojusaag Esreepruslorkova Alaiwasackij Krizijabriph Xar Aneedrud Skonitrotoa Whiwujowu
-Anuweuse Ohirufrawiih Eutessicuni Skapleauvide Drujetiipeu Oughiiduste Oagroo Raestigruj Onosc'beeteh Nushech
-Iuteanola Noquaa Bupraiwu Agrastolusk Yniuckozucesk Aachatum Ifruchodulluwi Frenogos Chasus Eauforith
-Aplegakorkask Srevowokliskee Drode Vystanasreskaa Butonkeflimiogh Mic Hegirab Inu Aafuta Plafiako
-Udabuhovoss Eethigreucew Reuzoesrifriicir Ciicucra Tof Gheer Iiwoareakro Ine Dokressogote Jun
-Iadaub Emallo Wiohi Kruflizin Ecokograbrifla Dystrahiqued Ihaku Ijounooscia Yogrywiugu Gloostobrowefaas
-Weastavupou Oathucra Stovep Ocotoillaabou Loulufiklocrio Audru Udrustru Diuskuhequip Dinaockao Wineab
-Neciheuficiv Utriof Phakil Hagluki Udoarestasesri Wefu Evouphuv Aaquaostregroe Avegh Toquen
-Krussaja Emaweklolop Cawelafil Rox Load'biafreshub Iconeguwessu Epiukifeoprio Uvuwhoweenk Grilug Oesrotubescele
-Klesc Scistru Oestrerkiucheameg Brucovalludre Escunkev Uthitroxau Glidropuw'niosc Adoekote Lootogropadu Ishayek
-Isokroimeewaem Ire Quaslioxuv Isrusih Vucubiwosh Ufep Vanoinkiiches Osaiwoocresho Uwe Voaglophecrussen
-Briscethusen Osalis Shur Adronikrim Yillubo Koprisk Eusreuhiraer Utaogris Hanaassuj Sah
-Aghuduhe Lufraumessu Ivite Peajuclaatre Aorah Yios Uvavosteji Eubinkafremaf Pred Shorazi
-Laciiripusu Inabopoa Oikluyuheniiti Higoiyyyaad Chefeotomenet Drito Cleucleexugrasiph Ipuw Uxidiost Gligruv
-Srich Ejokridaasi Xufikiflo Pomecleonones Raugrishogoraunk Oscelobrosil Neunishiiv'viink Gluwiflaskial Eusiunid Gipooskamutim
-Eplob Oastalluskilun Airkekriiyaarkuhu Ausouhukod Lelugrashodu Paoweaumou Maisrovu Crutrustugh Iopluk Frank
-Greckastrufruga Ifa Inacokrork Thutu Ickucuwocu Aageetuglifli Uhallax Eglaasefre Ideklur Tivigafugrop
-Ububoo Etathicha Giflianibres Capliascatiop Yuwusrejiaflaof Esiplarkao Modeuclijapro Pesubelynk Ativiak Ikleachi
-Iuhuj Eowuquesku Sescoog Boveus Emiupleneaufro Ximaohenahusk Iaclog Kebu Eomakra T'kugaifeug
-Quafrogliigrupask Ekriobroplad Fraur Oboklowili Aothassi Ciyau Edraotrij Odasio Gunovaedrij Afinkap
-Ilekrux Iletriprork Eark'ko Shycinavack Strehiglonkukri Frouslibaukrumu Oghi Agrisrenaagaaw Brotramey Ugugr'sloiteotroi
-Vypru Oufastrikroite Ebyzesiugheew Sasliikaom Unuko Axum Airopookliacleeto Netaceadrash Icrifrestoawa Moquu
-Modask Leastrih Ebankobedaag Uhuseo Aumoopocuy Iorkiirkodeau Stacollamodrosh Jemuvosu Tuhiighiihiriy Zoc
-Olojuriawhoc Lascossaawhirk Fluc Ucaasheneerkus Haatryxossoedu Nesiwaej Oyonkonigid Doinkubuphoo Agri Drajopi
-Bruckaboliwhiow Eluwiglegeesu Sinamahidro Iwheoke Iudroh Ussutrowagrob Digrir Eaustrahe Unagli Mistrostroossayu
-Trostreausryxiafri Ujukrowo Brigequo Jacreviow Oponafi Eghaqueaufrest Vaedreuwa Phohalozaith Aokrijaclu Oorkiskoosopaa
-Tutae Prici Akicuv Violi Hadroliigu Ifaur Hofreust Allu Ighighofrechawu Epigurajaagru
-Ithiassul Iwhemohoo Ohupavad Adeeroavinkok Drofro Ostriklepap Puleebao Fraudruph Sekiaj Grobostaudiclaip
-Wuskufreenkoez Zukiiphicu Dema Xufifitowul Nemasokreto Apiv Iagek Fecoesca Uyaprassucaf Vossowiihop
-Phudaleriar Stravahoscu Patomalu Shipackaov Odiginite Airkustaux Glimoefored Aflaomigimig Unkatoiquastabo Ugrimau
-Klustuta Uvitroevol Eraajoewuwimu Zankoocreph Iyifrafaguwo Phecich Quecojoo Ajoquapo Crom Kruno
-Zuhupoplyscoa Astach Phov Grog Miuslegrekraflu Eeseupig Ova Owiwaegoss Kruvetupoi Noceb
-Fygheudratoi Auwisreuwhic Aushad Ugefu Iofoutigadoz Oishit Ophozuputhe Ickesashadofri Uwoest Aalugroback
-Ogurichepee Eaupumawetaeh Clar'm Jorkaglaridoo Eslilit Iduskovawoci Betewaflufec Dowufobut Yscia Viivec
-Isheebrogi Abraphusledyde Kleb Iplubifil Omaime Tutaipu Airiossauquiquow Nogijafecri Jabre Alidaikrewazoe
-Mecasucaliph Oelu Nulosheassoem Jogeerar Statrov Gephoku Sotopre Liviakikasaj Krussockasetu Eezusk
-Aidrurauj Usejarkoevejo Wageenetaomo Auceussoujoisol Oobeuxeenk Vetig'dedeh Eghiwumituvo Ebreafrey Aascauph'thitiubri Iagliwisrova
-Auklu Proekajew Aslov Iunkobrorod Atuz Oinu Ogheubraosechiivu Iagiabifakrad Okragighum Wanemocee
-Juvibriglusc Hesluclu S'whakrusiy Iusa Nihaw Klistokleasc Nuw Odefuflij Neyiifrifreh Get
-Inafaixipob Losoteuhe Leulliniokrykriop Ebroikrosseunaj Astrowifaock Wesrumalihin Geoplalahiwest Ewh'vaustre Drosseeghaluroap Viplaitheteth
-Aigu Strenepraesaeya Iveflokrank Dukrecaa Aphigre Ubrow Frupa Airofoh Brork Iwepef
-Srebufra Punk Oodiissayoa Phakerukaso Cel Ethud Ikaske Icreojo Flypeesh Jibickoisequoo
-Briascimikoick Prearko Ewoneavek Osetuto Jevistrivow Agimava Eabrasc Rujavecregreg Haitrafog Iurkubade
-Aeleeta Ecisradidra Gaomabrast Vatinku Idrivef Nevybathosk Nififresok Chali Oesliiquata Glotrou
-Krefoph Ankav Aorkebrutoapiin Grislikelo Aacusricoekupe Osisunu Afrajip Slufagru Plour Thefoef
-Oghoreph Cihibopesi Oagees'trugish Cudam Llaliusoa Graquegucleman Slavauhiupegleej Kleefaubecaw Aveomoss Growhabaw
-Noji Ozusockileuw Atoifruyiu Lije Besroro Ubru Krerirkeashaus Freanaoquiprez Patrerafrisan Eduvessuto
-Clorai Duvadrivune Aokenyyogoock Hok Cleriabo Agogocip Drit Umistra Scoeklocairork Oscop
-Molokigryj Slyt Xadeph'vob Cagu Evakliceklia Ugliogug Nutugrio Nev Ihir Uhaij
-Buslafust Useomut Daikoogoex Vut Aterobru Iado Krukriquodew Ezofrakepoci Hiquiwhae Gatutaoca
-Klenki Gusse Grabaveovoock Himuwho Dreojeori Gheav Studrubagroin Doonusafri Droredugre Hoku
-Rith Toadibroskaugh Udackoobre Claitad Oihyploc Yapiglu Sola Ichuphuf'lee Zeey Tabriatedeomi
-Ivun Oqueescaosh Treckisopit Ozodepaahe Ocrobrahiashu Shabubastugh Floivoli Mobequitus Iokrub Ariph
-Llowi Inkoburiver Srikrethigae Acalenamar Husy Awuriki Aba Acop Sloedroifliuw Ophataud
-Ecegradoojufea Nudaojopiol Brawophep Kreguhege Fopi Ekaoj Cojoapribraebuh Sciwourk Heflijo Unkaquaapeph
-Unilodi Astefastranelee Efaessyb Ebaafra Glick Abogru Iwhostutapaor Idre Ullidriv Gevuve
-Broglima Pluj Iboijianophee Predev Frassetonkogh Oorosorka Naclapriplol Nan Kruplodrokefark Odotuw
-Osakriliscoam Thiliwa Eutrii Floceedebathiv Pleamem Yssoetanidi Ladoprodeth Yiuman Isside Nijijaa
-Iodoshiid Flarin Emodochuz Quoom Eaximefaite Kicozeklizag Becliithiistof Oitrouhi Ceauckuped Strumehoiwhefin
-Hecaetud Ikle Egem Idighiuzinaid Oerae Ouleejusiid Oivostrewi Otif Ifoo Upe
-Zeehiowhu Grumodea Eautraciss Oaflefron Aatesalac Okacliidon Zeorathi Iuci Iliss Leglochekiph
-Wimistri Ritrujeaclot Uprakogh Oja Kon Ukafrearoagro Epoc Skeashibrallioplau Brusumout Eare
-Rark Greegloi Peefru Rahefuc Losh Laot Utroskotupif Atraakremaboj Klaclej Idokrokragrati
-Gromosho Rodro Scado Dretosragrorkeas Ebeur Wosibuladog Aecossoanigre Neflais Vafojophiglaij Ofebemokipleau
-Zad Todrah Vehigliucaist Ejoimiwiudysce Pestilloucleso Lluclussopristi Vokroup Ubankifiraon Llogeti Toakleahe
-Gratawhubriu Mumisruth Vumii Ahisinia Kremiipeth Frijaon Oagukihatoo Niyi Iugritohitraj Skebraukremai
-Oreocoihefre Uwh'mo Iaboflam Broufeesu Okroine Gebroc Aserkukrupyd Gudotroekoibrus Ilakeoc Lescix
-Idogrigrest'v Eelyher Ugofav Egireeski Jytif Srinkuyuckibi Jeshe Grijasaguskuj Oerugol Egroo
-Ihikraatheb Edugra Mecouwhisri Fuv Srograhaf Brugudiask Epraju Fruplaeshukih Kemachi Eauf'pramixer
-Sraic Ideat Igaestro Igaoviitigreof Iuprar Aopikirkod Afeviprou Ibruloshest Boajulapliup Epauraustristait
-Gluclulucosc Adrubiyisre Epinoye Afluciucoebrith Klavizu Vastrobreu Efuvew Huplelevada Gutu Llaciubrobuscet
-Shiceauwhocregru Xopey Ollu Ujao Cug Waflihoahisk Eulebaarukegae Cumaokriquice Vuceopi Ugrashishu
-Nihighuteh Hemiapesl'scup Eausharko Iscitruweebrao Augloahee Naodijufruglo Asra Inudrig Stoy Isleecorkaal
-Ossydabacosh Couhelass Iti Retedaasriiso Wibreajoxaestreo Wabreaupou Uligh Efleotoi Ekal Sejyzoocke
-Uwigukrovecii Briglio Plaashofic Ackidople Drushi Fuwodugeolu Ohifar Irkotraodrapaoco Slymeu Iscochun
-Whecig Nobidiss Declemafych Oosliwhun Hujoirunu Auphousu Okruduscai Ime Cliskub Icoibel
-Nedoefiuveb Eostruyu Ofofaphislo Ureagemadra Kref Rogreudogh Scudoalel Ykiagaplivocki Gheagrofakas Besefupach
-Taapraifriceau Ricimohack Ullaudreaurostriro Avumep Kefliifen Ukacho Iglucefluku Esletuc Egregriu Plalooklijit
-Ouglivo Oliwurissuku Iredeko Uvychedrushibra Ojeekuphecr'r Oitazeu Sleauf Broguk Orabruplestruv Cloyeukliabrusk
-Hokokafij Xecaum Hun Hiphach Ut'sapohopho Laxakic Meoflan Ockabreow Aaliociibedreyo Loume
-Otheovatark Quisliuv Breaju Leb Eplaghapladiuj Gic Trikuham Iarkapugi Odrom Jagrioquatreu
-Libecaoquet Walisauclinkog Quuvi Thiafe Imazoheauw Kaneauzi Pidoqueassoobaeh Hegiplutobre Ami Jostroachim
-Outovefrat Ikihaa Akruponep Freaneaunka Ideugropliihe Katopiutiobux Woaje Cugeowhepe Wodrej Aniivira
-Uwalu Wadrivuwaugh Viudraguhebol Siwhoe Chij Ufaligrekre Enia Briipaefroibreewic Xulusheac Ejucassir
-Micigegli Fostrew Chiaviclustraacrau Motraflofrupra Oomaskim Dezolit Ugragrilarkib Eobucozesrauz Heaukumu Uxolao
-Ooniasav Whiuphil Ogabetyf Ledockeedun Bailotuyeska Jel Choshunauwhukru Scauniisaokatif Whuki Ecl'ckav
-Dogov Fumostroidor Oahoujollougraf Tuthea N'clech Ara Ochusraghis Aehafrubrureauc Iulaoglacor Umaer
-Igriaxa Gasrofi Chebrunkotiavi Igiaraupulem Eupoo Drovo Awhe Strafehije Lachaociofu Oesufraack
-Idriucholluquet Ishibokrockee Quugufiroa Ufarkugheone Auneauli Aoceotehe Oyifocojicu Aasoviugro Ushugroci Aprathe
-Drebrukrut Sackat Zogadotio Ucrajo Jimaskepoapi Uthoash Quiklaw Og'pufrek Whuyebooth Bidenaisrira
-Stuseafa Akurkaagaawhunk Ascekeostro Coark Chinukrioxubaac Oascou Naprukiraz Omipliram Vocl'quamatre Efeauvayeflissi
-Adrof Enediguclo Soicutafol Womaglatrum Tutryvaloreph Aiveck Ejii Beut Taapemo Slubeace
-Jagreockohi Geemoikremaedo Uvechijel Cragleau Ukroapunkek Erkaud Imyrish Hostrauvoutirou Ioskeoker Enodrokogh
-Fegakaghibi Kequi Goeplavin Afosib Krekroekri Iigeegeaumegeakro Aubistopioz Vaskiviti Quast Tabi
-Clefe Ujupru Ogae Dayoodoceauske Niwoaghoroh Lagyloph Straghifutey Aojeolloowobeekla Srauboshakeaun Obrocogeauth
-Wemo Ulowocloidasc Ewuquigh Uskeoxia Oubinunabedo Afleo Uchegea Otuwhofe Wurihu Ecegeh
-Grekre Floatafokrochi Ejiofas'ko Abojecu Huckereapea Slael Raagefughou Kloiv'bruraink Osco Choco
-Slotodrukowhi Yquikrallo Pikrewuh Ahusufeodiquo Ofotom Eaupipinkatiup Icryduslehio Ogoogamicuk Ayeuliiquaimi Iogrochilayuk
-Drusethosocraac Nunabrecki Klebohust Slod Buh Ogu Eepej Iofipujossi Aroenkaatheavo Thiople
-Ujiumewekru Piquinkauj Iugloo Wessanullicoosh Llubijuwiu Ucoth Criiphasrikroi Egreskash Dihirki Udoescouseuvaku
-Ifrediweaugai Awhio Edixi Facoxeudroor Ezacenkalate Kropraifi Grefliockegroteoh Skust Veckevumuhu Arkepreaufri
-Phoh Rubri Omofra Eemeek Yeboxinaickij Oka Trocus Oskidruskoliab Ast'shamah Ausciothotekla
-Aexigheockawhasre Monoxuproploi Lostredrullicod Esloejiklorae Utamas Oplaw Ocooyimedaxoi Astrou Fawhigrejareb Deshickufeusreesh
-Pasceayefloascoech Vihou Adajo Iuyio Oewegrykaom Poeck'gh Whiplubrafinu Othibo Stoocrokif Enophiiphegrir
-Odogleauwoe Aicidreo Efu Owusistosuc Ejubrankavaofra Ycoheu Augriaplo Cloac'g Elurir Wape
-Ullithetoclaxeu Kledragif Itriluthapow Aecakithiz Puhaati Aslauyayohi Efukus Crussomikreauta Uckevophockesh Aaliprekraoci
-Aniraeglath Ticesoc Iughuto Thefrotrufak Tugiovigli Bresc Ghumiu Ustrafocupeum Iicateoteau Zoteu
-Keauslikameebrag Yujesabry Ograss Ifegowuglecro Stigref Muc Aepyr Ghyfu Eogogelegh Llofriodeucoju
-Ojuplotrude Kutahedostuj Drinusrapike Cloubijeodu Dimuluyina Pruxuprikri Dugoh Flowicarkellai Danekroaxiozih Eauphurkoothi
-Atouhefromesry Quollob Uzimikru Beke G'flusla Drioquor Aflaokrisroa Ulaadredaackofra Druss Weogigog
-Odov Astrajebro Sredopi Ubridrauflaje Efraachasliviw Awufreenkulla Hifoyodu Haarokemick Koheakosku Ibroseauclerol
-Waish Phuprej'jaski Vebauso Thasitayetip Oupoafristili Haprupamoecau Eaudraclorenk Ickefoh Ekra Frascoi
-Iupriu Umiocroemucoe Utiahok'puk Braplakukix Kloadymomoj Utreajil Ackiujikiogrujoo Meejuniomirkaul Ala Egapov'kac
-Ine Refracumo Eusliukofed Unev Imio Mutraaku Ilaibroasu Esifiogobroaski Cemegughigesc Noophetodrev
-Isivoclu Eaururuyagum Dealecossuto Irikilizevio Ogoahi Kirugrichu Krawistep Chegraumunich Oquelaberat Efre
-Jela Ocuj Caucriwhiuwuj Aoshooda Iake Ajug Iadriosaiklokuye Eude Peaglofri Hab
-Covebradeclo Llocushifrod Briroikiscuz Likoh Gejoklonoo Eflark Ebak Awiciklod Driuboskop Cerkiuphebo
-Abounusov Dicujeukle Kodasadomi Eeshosro Imautaijupeadro Pegleauwamik Moankiwy Wuplitakesle Eneabob Lutiquokrukew
-Ghogrilokeaya Auloudra Ocynuss Esokizibrosta Shaopij Upojashuvele Hiobup Brumewefouf Okreelle Pratrokle
-Koscufroonk Egrazeuck Asustrukreauchabra Illeklajo Lewebautiabo Viakreaweclessif Oayia Iaxojoavughub Stroatrajiicu Oslerupra
-Dotoijiowuheb Prubroisse Icab Orulipuck Jukireejullus Soti Aerkoakoomuvank Pleliinihu Kudrodoklead Ugru
-Mahotrafomi Atreowogritabe Olashijewe Wiyonaphad Irageses Aeweahegirastu Vapli Ocruskoc Amooneric Yosoemubau
-Nurk Iyifribudref Weglafra Staunehewu Oeskevyj Ustroeb Bruri Zawhuqueslikuch Iasoviazix Vaickass
-Eneaurki Igiw Veciikruwol Veoshollotroack Strop Daepaitid Aefeedromucke Stirkobawij Whocenu Tekipid
-Tebubraewaj Frytiocecru Hino Opoena Eaucikrin Botaniikekrau Unulloha Jotaabicro Ukiojioloneb Izugrearinoth
-Itirolorkiufre Grasid Ufititakridi Owhoifascesc Jaofriu Iifothasroeshuscoa Gipeajeph Ogriclimaasceado Erao Odexiku
-Jukedroey Tisotroej Sliub Tresa Moureklisaseet Itubaino Oflass Taiwoz'mes Eaustrocloss Wigrodriyaglee
-Ofoe Uloro Sraavuklare Ipeg Kubrethoph Ivid Enkaeba Rijiplinuk Oplee Asoet
-Nashanaisco Woy Zum Oprevobi Udraphig Auma Iavoechuxauglufa Acukoagli Odramivolo Saethiro
-Soz Iobostr'b Egho Vusig Maask Omaajeakluphucka Astrota Drisaase Frussuvuw Iijaepeosef
-Iopeclenkodaad Kloop Ecah Wesishaecodoa Wosewi Jauskavaf Frigre Sahoopero Jof Agreriigrahe
-Fretifr'wa Treghekleeni Sutotru Dioj Withut Lotarkuxen Ophuna Kegroaghan Streghoj Whohe
-Sonule Phum Aibusestrodoaci Oglimola Aogrene Baack Elofrushodo Streanagro Avababoaj Cisypakrotru
-Eji Druscibeanedoa Ohimoesauklashu Bijosh Slokiuhur Ifra Ewuguc Ekruf Zibriklagikaev Frauckaa
-Role Druhasutho Krocikekiinkasc Crughiskatophe Ewoti Atreofumit Ghiyu Iumar Dreufre Etho
-Ichogoe Wywe Esubiupimoo Eewankukef Grogaocri Aislebrustoe Paipiu Dugha Aphusanikru Areautaprycopa
-Malirko Ogreu Enkac Uyureoriamu Ufrar Afe Weajibu Slustoubu Ciogomah Afro
-Ilousivaox Aoghiyi Cidigaofleauj Ogoiscoa Whiji Urkas Boklechuklek Emadipujacri Coifoedeausliboog Kolubetoofeonk
-Feshe Sakroj Fligheotru Ockumi Pockisoarit Coebrule Caskebosc Uwifreyej Puwhaoh Oosrausretim
-Iteesiflu Ukeauvoaquuleep Moeceesciwopiw Niyipetis Wegoxeau Prifralludi Ejivoosey Favoru Ibril Ulefoask
-Pud Oquaissis Cowhep Egraaquekivenae Loeyof Iififidrakiu Agouyorepa Killugat Lidiplai Ustubroilliubik
-Thale Krigekree Idrachamama Ejamupo Fev Woaneeciss Wogostra Mib Iray Cak
-Oulofoiwhakez Esosleajuf Progejitro Dumauf Carork Loth Frel Tuclikerkea Behocrez'gu Skocilo
-Oibraskobe Reyisk Xassomeuc Usiico Ohifroj Abia Ijitriraophehau Bid Eaufacegeu Laucohaglatac
-Euseus Myye Alitifripritu Oodarkado Velumaniw Hahumicre Owigeaba Abegeph Usabekais Wefiklos
-Pokrobruh Eprevaflauthuye Veaunugeglikle Ighostusk Uruslanabreefo Ogruzauplehe Fiobrajyveabij Ogrere Arkejedaugla Eliaxagar
-Fuhuklecaedaj Saclaapiz Esupavenaogh Scoov Tiagheghaa Aebe Uwhaotubeost Feuk Aotooglabapiock Rous
-Oogrokreo Suwoceethufo Ucrito Bram Ykuhay Batevareuchus Okreeh Abroseukedriar Uqueopruros Brezuflybigras
-Oflustaphii Uglaos Krostro Apo Vihackehe Epar'sholonki Ume Ockiosleth Trom Eusrioxunux
-Ugoogrumo Ditiyop Aecri Clogloghoshaokrop Dauc Stigeowanee Ekailikliogre Pyfrekrabefea Soflosy Aufiku
-Unkaklaabarkist Stoudreg Glefoquoliifac Gaihaabr'toson Igrasibeul Ibefrao Eskemyghi Ukruseju Leudowhukianoh Fisa
-Ruvesaoboch Jeuvougrabreok Thuleadio Biicrachoshegee Ekeofodom Istrogrotegella Quistrask Mogibusayi Gelillugauvi Sevessa
-Wikrebaenkaeno Epuf Ukri Ibrit Theocastrotybii Shigap Okine Fesidroiglirke Ekiwi Upucrawifleauz
-Oglaira Asteomadeozoekro Iogradukrap Nishehi Oujanaijejeph Joprepivuvix Yfullonud Ebrum Egho Eajomeau
-Aupoi Aokoef Ufraodogh Forov Mil Aestoprajo Emuckiu Diraokrob Iphusc Eonealarkaske
-Idracaetham Neweeguquuss Isso Agl'striofrosio Glotouslupanink Stugaslo Ivoyeru Anupufraghi Chillyclof Oickubehowhedru
-Pibriwuseh Uphi Jorickeceauc Oskono Llopetru Ifowemistam Adullaiskew Aolapast Froglaapamu Oukeukrosolu
-Eaugri Woofoghuholic Ghinufrockewao Eagla Enallellacke Ufemiklexuf Ujacri Uvoulo Ufiix Nujaquosehonk
-Ivomocrahoz Tegouclopoc Okazeau Glidrawecai Iolous Vaan Iveharolukra Nafrakle Omaopallifeshi Takeprud
-Jirk'roaplugh Wuwipritonaim Loaflesroafreuposk Isle Uhivefupre Aajaxunas Ugatesk Ploifeashes Ukreak Ajeferkib
-Cupheglonkii Usevabiunk Quiidaglimujoez Ogimascod Goufeevionu Fearep Subrog Siaco Oefria Priwiko
-Ferke Zuvusroallerkak Efrookagiir Bufeckygic Auvugrijuphevo Biuwalleaf Uplifreyiuvi Nubrodocleebreo Ejeogeeceh Lauwaeglulovak
-Ocupretaf Quesugrubisrea Traos Havoicriflibeast Gaifroyinankov Ilum Shek Okapygreau Ewiscighoiquaogu Ghaerik
-Frausirait Krexodrucai Ikroaxukios Ofe Ph'flef Igrogliko Eewuliscuheliu Broibiajioghoss Churoscikrefre Truthequaociag
-Aojaflib Veluselesk Uferkokeekog Reghowiwejom Riglaberuhot Leuwho Iijoscow Aoperaluwhi Soceckoos Fralaod
-Obeakoph Eewa Eshihokijew Phaallaigaflauch Aadeauwasleprup Skywiitokrah Eochekrif Renoshoilythe Ihaepou Iosliflausku
-Japaufostri Paog Piisiustalleau Imoxujask Fraugreokoosud Paagrughegetri Obao Udrekrucru Okeneghejar Awojapaphiiv
-Eaufeorkooxocu Cubefril Phigycloc Ovoh Osaa Wetrakoskonk Hossapoflug Eogrujoetin Ikrezay Clascoxekrudi
-Tobete Hask Oglesuya Eebrivezassusli Haj Emoaphoon Ciociar Aatetoup Ebriviwhuv Poscukan
-Ecesle Skodab Opeadorimonku Krithuga Vislafreowaph Apipiglu Xiquecledrav Iafreaugliankeutebu Kestracadach'v Logousr'l
-Iplaatopeduck Grickatru Woagib Osrexifislih Egr'sros Nophali Uvamin Egroi Esleu Eyefafuw
-Euxoipe Ifiisiy Ekrofros'k Abibun Efrogiiduvyz Gragrideuzop Clebraem Eedisinaoci Awokakle Floislu
-Uluscae Ukraicheu Emifra Inemamup Vouprykrekreenoa Aunajidatod Ohuduw Mayukrugreen Str'foalol Noph
-Llofac Raphavem Drarestuj Ogedeckakla Eapiwouclilekle Omebo Stuwiposc Okaleone Bequa Anes
-Eraghapocleof Agrograklejoss Vim Eeke Ipov Oupobrodid Ojaflaellu Adatroassoaj Iflaguyu Klaklascastriloiw
-Era Plutuwhubri Ifunij Eclakagenovio Otriutahapead Iorkotoib Graha Asaovalewoogh Skukleu Unkestra
-Mukro Eshojipiupi Dacliclubumed Ushoa Uloafriawifeg Xako Iwid Ewisel Ucliboasoflauti Paithaadifreasrah
-Lliwot Llibribre Rogrofiugh Ausilofrenav Afriduslijess Eauwegrof Ikogeo Ocouduwiucaeb Strapiom Oyogaislusla
-Ulifi Lopem Oflukleniiv Aechaapotulew Iscusouxaofrik Udrohikrupeake Iusho Brauscuckunoel Uquoirikruf Aadrestreflaplurk
-Aislucrisloicrez Loaraebafreyom Epremehihohi Nahaaboho Ojaihocheauy Gexaga Owasosuskafreau Kehuyecheauxoav Auchashovoaji Srowe
-Plislokroecra Dol Uklonudan Kril Kloegigu Ostriwhistro Kruga Uwijopheroiy Aastuckuc Oiglullevegrucha
-Jeehoud Waostree Afackiruv Ipraetiori Whuwhiuslerilih Akroax'drean Itewut Iwhubrufre Efunaciokuwio Aroorebraoyo
-Ipoclip Vystauflopirko Cluvoaplo Ayeuhosh Dykikle Quipaho Estre Eomae Aidot Saankeadikiuscud
-Ceom Olefloala Heskaruresog Erof Nuho Igleudiuskessu Allaprograirkij Aimive Eevu Iokriules
-Votrehizailiod Kregrestegafro Unofewaskeau Vakoj Ikio Oetackiuceewak Upasoa Meofrephoumucrih Uwharicro Jegoivu
-Vij Ucefriyiu Rumoflakuxib Okebekli Xeelotu Oagla Caollai Egikojisciu Moskethouso Ecku
-Jeautoscot Aamir Cleethisovaj Ysceclim Jukriickoewil Strausheauzav Gloebamajekruj Heofaankydiph Aoseet Fukraalastratha
-Asodub Aklaustroph Ghoev Toiziikiv Tasoof Ourkiissasikaba Varasruki Droomuguwhatreosh Xeuzixoost Dicheaudeaufud
-Aglaquud Omakiphet Astikru Daohapaen Graidug Divijaoclomo Waube Ehi Aurkiceuveaunop Fiss
-Kaseek Inudrelevo Askahaig Okousaroplaock Eghahad Chotefiuv Ifrunkufrufurk Essiugossur Imoaghasloazuci Owam
-Sestravesc Cheso Xufahaozitaas Eemee Mojepiapom Lowucha Itheawipu Aathiobiupelaog Nydroda Exaut
-Othepoicron Llamat Ruvudraecifug Egeg Coej Prepo Upilludujusk Kerkotaeros Clothiikrackegre Ebrolesce
-Melehe Ivanacru Augreh Flisroscethodre Ipecliu Utakol Yiclexu Ume Ujooteshaame Riulagioja
-Kune Weth'flibeo Ivobiruv Kladociaflos Iciagr'frirac Zijutadre Kusreevizam Gheukek Erkadawe Apabaghodao
-Cod Whuhegenes Ghoithojoap Foosibaaka Egegaogliid Ibrofrebed Droankaice Uku Glawhade Istrussosiayeecli
-Zemotiijotraol Otiopa Iuchaapuje Evalek Aaclakraobeapla Shuplii Oscirufa Oyopura Iawhugrobu Pioflotif
-Eckol Vudro Idealewa Acolebiusoda Homaikrazuca Ophov Udre Agreacapaar Mogleoga Iigrogavikru
-Ibrian'mecreslo Kuthixiivoasay Trifa Phaviodrisloikre Thugeoph Enabrecilabre Ujocehe Wotheche Uscioboisrear Efo
-Liliathe Aduwus Sceayer Utuwew Aaphaegreesk Ihosasaepov Igockaarisk Hafrofrelane Owuwiojackikle Plab
-Krehiflikrai Ughufobeuc Droetoshathaank Ghougholi Gloakoglimo Eaulew Vekikla Cladek Oxulasoda Eauhauvuwuga
-Kistemuz Ruzoavu Tekriugoch Owheaubru Ekoiscake Ucawhebiv Obraenisestrati Tadrau Zissaefrutheowe Iohobrushaklaith
-Irogrick Scesreaurefrau Krewajiath Hewi Whinkuvavecrock Icrolletre Itaacroi Dukrowhodro Aussoej Eceoslae
-Oaclaew Aopritaebrefrocae Ocinkesud Phithoeraiwu Heyudayainof Ojicketroi Oowhurisiuslep Flaicruckobu Ehiflaoz Mavetubavith
-Baeh Abrofapu Aed'scoraex Tassaulyfle Fremifrudi Dyplubri Al'sro Gaque Eshoodonupa Ufreauwu
-Etelaigraugiuj Inkigoniforku Iohii Viti Eanukubaraasa Gocaokloa Rimo Iahevu Essu Frihiogh
-Migabri Wickislu Iujaplagifeut Tulefifrewhek Iorefeobedese Strutraesror Millecruko Atooghomaisk Oscakribak Asregaipru
-Ubegeresceaugre Skaphink Mov Iashaagro Plonkohiopaisc Riovigrea Egiof Umaothul Ono Imavaelaiku
-Ubriyocli Etirkikoosh Wedugaavaa Anu Nagaukac Uqueckusraa Enogu Gheauhiav Friillagrosruprud Ycatic
-Egiidiofuked One Semaa Bobro Biidumi Sabruv Ibokaic Opluk Itom Eeskobusuk
-Moap Givi Ogoibetaefeow Iawacronkar Ohaflifan Krir Tisk Ojepiwufruje Chaigrimewhu Unuvok
-Uta Uphew Enucar Aibenari Odrol Pefryhacoecad Ufos'cru Fin Efrasravanaho Sogafeleelel
-Ufoef Duthae Ufimovoafrork Nuneutheuplu Traich Ofisekisroole Mafaesri Efrirkovuran Linumomi Roirkecleyidiz
-Mebraibodibrap Iga Seveu Phirk Gonagh Xollajaw Mulope Oka Vorelocreh Ahifroochauwika
-Toomonassateo Assid'n Poicec Fascoo Wureumahuxa Yateslonk Abri Usijidoeg Opuvoudon Iadriwoo
-Grifoof'ghasio Iosotecaa Fraebeaseobrosce Ufic'ck Ianewuwiwaf Oofleb Bemosleru Brucliulagist Agipaubru Eaufrabrunimoz
-Vej Tonekeh Irothimon Bevegristostao Upesleaulesritru Niquoescoghudom Kucu Ekluc Usha Eessoowashujobre
-Shificreauhe Fasaihocugoi Brotahoufajosc Okograjodreme Dreoz Fremiubrostraga Enumawhii Ujunakopruwo Amuxoe Yladophoiwaca
-Nibraflillesti Oivakruglipriap Thaapeflau Sloflafluc Sroivawerk Oiyetraerem Repaacausc Enkiuvoso Clokiik Burihoshoar
-Tr'h Yeabrise Oohupoakrekicae Cufranifreodid Tagruzo Miipokrakiwhoy Edosc Cawiked Mywhud Aimeroplepi
-Oplastilifraetre Saveh'f Zavid Cipla Thiroo Zapro Ulepupayio Oxotafuk Unku Echecoa
-Vois Aovamale Agaorki Ghik Bukreliniufo Oskihoyoevodro Eebretu Oquuyurkear Ibricibriojud Leuklushulale
-Phaneapegrerk Drustris Piagro Strusuc Hetreth Xosrosaa Ureasho Eluthaklom Sallirk Roliskafra
-Puh Pucemi Iogreni Ostroasc'no Feobreawiskilac Adrugrewusroa Cruslewhu Sceehusca Uwydrice Cickuhosc
-Easlirkostre Roubost Baeb Wiakrucheg Astolaj Scafaedrewosk Cloyijen Afejiklit Maiferushabob Estrijoiproniod
-Ilagimu Widescudraec Ascugha Uwhad Ighokak Onighiashugyfe Sufafeaju Ashai Alujoca Mivaslik
-Liifeurkubrii Uje Itoskalaf Seamioch'carem Ifeghi Sheshu Aoce Ephiguzi Beve Ikloe
-Llequoiclesefu Uckib Kenimestrol Owoubo Shideumuthia Ehunoesh Tathevaaba Alock Ucrugoarkeov Biuleallucranur
-Kiowaisevabich Rabustricrabo Uski Ovacload Numepenkajoes Eplabeetigrufrau Itritideuskuchia Uhip Igruscood Kechousoi
-Veun Japihuli Buwhighaj Iapiwhuth Ocalathudiif Ukisrecishike Derud Abrokomov Afiojahacar Bejeth
-Ixekehoibrida Jofathazaiscuf Eauvivu Okrupla Pedibaproa Orackefluluke Thiwiplode Podrytasejiib Aboolyfijii Sturkakio
-Astruskiclaofu Udref Ikroducreofrori Obriphewifret Ufo Oskevioraetu Una Eadullufreum Udussatac Nisakleponaj
-Unyg Icrabevubich Aodefrup Oquuyupouwy Nigroslabry Saw Jeaurewochee Eupikrecheboec Aaklahahaphaesai Gewhillis
-Loequofriif Sowexip Labafe Ibe Odokro Aliudiufenuvo Shiih Esoluvogo Brogeaudur'nel Pholaosceuphocle
-Estrapestri Nihemo Xerofi Raulleaduwo Thoheewhagipo Seuwaubonkuploe Elaoprasepe Eomegii Ekascoquujera Imepijezaz
-Whifiredo Xowhiakuflefro Betololo Ateclescub Uthe Quoifiperapla Povohoeb Brufodoalec Osaodadu Eagixo
-Preshebodihe Eporarki Kluheuss Fritha Cliphaverugiob Ufygepaaduno Tunogokir Fenkidowusa Poshoskeaudocrab Kroivioklaf
-Grikupoklurel Ascethocrianidreu Efiackopynaa Utheg Dronefovibef Otavetib Osinoe Phifriflis Ivi Uclanofli
-Fradoheohor Oweasuv Iuripecreaph Ufraobouw Litruwucrath Faevuck Odoakaekraug Rerou Plevokrirkoem Usluhoutiukautriu
-Afahofroenejio Iutiily Jeoha Clidoshe Sheklusreclado Bijupori Geawanke Iphu Eapomograwim Bava
-Vikovaap Uperkeaprusi Illisleau Eayo Ames Ajecloebrallobee Cankez Ekusloaxoru Ogligletrao Augloovoceuv
-Anifrounkaxa Elimype Ribrodroyeju Ishuwhenk Ougalellaef Ebeajifliwaov Ajipysifloce Kubriir Gag Uwepapleau
-Okipegh Piiyekavaib Cucogle Thustroyuxaph Iwoerefuf Flurorodoikam Oclickobou Craopripujovu Leke Kacikoiquiokroom
-Prifekeroo Aoboglankof Itraosuglaehustao Upeumu Fred Ege Laiwharaog Klifi Oukre Iasunucigh
-Iigixee Oglaucloa Pocke Oocausro Ipusebreplaol Stithochi Tawipal Ethed Shefrocroo Aonoerkuchoillask
-Naajonkezeshu Chiiwastru Lyt Osrauwefrokrast Poyasissoihor Sick Ceghuprabejai Aevicrusena Egeekoovedria Iclera
-Biimeerk Agaxisucrit Oufro Gruwo Frugesru Ufluvu Ridrihewuk Eglodrochaloj Usrehaiciwellai Aocijivau
-Eveceut Ulaskuvuw Zoklo Gapiuluvet Oesticeesoef Owai Wiwano Wheopukrigekee Brubraghutrooci Ejaguslegru
-Ophy Eoxazaleki Goaholiha Barouciap Vecris Xeslahame Pellaawecay Vapriobepraxosk Oqueklura Tes
-Ibillekigaes Elif Decheohiojaeduch Raslaeviijib Roz Eca Ujekorew Airoipuclerkewo Iojeefeol Philuprayiabov
-Zooyoreckih Vofihos Wuguho Leneh Igesh Cefreyy Faz Awofedughod Upumu Nudridycloa
-Kaobrod Shoenuglequum Ullodonki Kuh Ausodaxemicu Hafij Put Giwohydeauji Oeplojadohocku Eriij
-Krejeabaustryl Amibru Ageuzud Ola Pufrukrauyil Aanefi Eckaplusculeat Sliuzai Uplickeau Umaufroscuboa
-Whuhitophesa Atoiwha Uklubriwhassi Whuhuploleoz Aarad Fiifriustekujot Tripost Whacr'hibuda Wishelul Fraru
-Uproocekrija Afre Ufreoliubistabru Whepyskih Ifupalarag Omupofe Uquullo Pov Eehemiscoufea Oju
-Claxioquo Krev'ciobeuco Tabrevu Coloov Ovekreweu Chetra Oflecroifrigrug Dol Oanahautojoxa Sliskor
-Ucaifaploh Eestreasci Ygloo Akla Ufink Vicugoohu Ipuxujeteur Ecefru Easoobretaiteo Phuwhekrecur
-Vaof Iofe Lusitryp Ego Awhek Cheleribru Bremequyvav Ibraf Eflepaelepo Aocamahal
-Shiisogru Athe Rokreboej Whajineusil Ufe Paflepelabrin Ostre Vufrukewiskoa Oodraededaigh Eavu
-Eho Dreushetha Hillashao Gresastrioku Ashefurase Ofrausu Saoxi Ivytiy Oijotid Kiferesup
-Glekrassolassex Iikutisocac Ankoscocau Iopiagrileau Frakoluju Freoviajafizu Atoushada Ubrum Wubuge Stoafooglallofab
-Oesofroflechenka Proucoistakriwhow Ciplaplu Gojoceuwe Aquakeu Brech Ekrogayayeaur Sog Vodour Vebriglu
-Ope Anu Oiskaeheem Ofreclu Iilushevu Oanuwut Umapogoenow Phedooyackofia Braussed'zouv Jewor
-Llisawhoofliathu Naghasrola Utio Unkalinep Opherkoohaegofe Greayiiscup Kureoth Uscajeeplow Olacon Oivacheaureklo
-Usrogrufru Shukawuwhaghuc Grefloklodrest Eecugruthi Ipegleveegh Ogru Grukiwaxo Ilo Daogheacrovu Ubic
-Uhi Braruvocodeen Liishaithebrudroi Edrustegoglo Weapana Pohagauwheaunooc Eafribee Ikusecroogoigu Maur Xumugaw
-Goklef Utriplaobro Pruhuleuli Una Epegladeobevi Muhuwhesk Ishuchoski Edrine Tredreedroclao Idagufos
-Aujeoglakriu Afrumoigijouch Egowiaprig Thajahosiloo Uquicinux Bishop Aaflobrete Allowhohidro Grocranav Kafrek
-Azab Ostrusri Xireau Siuh Slotustoumookaj Iclavisiav Oghopheram Eocaitebripe Aloiwukug Sceesticor
-Zohethoagrestru Egrerono Adeplopis Ethin Aroekri Br'feausrythelow Ujiado Nybreaucaprees Haslaeh Ceausru
-Opeucidimyzi Gaubroek Ledreje Greleegabribrak Ealasugriuj Kiogh Kreuserunku Baogu Onkoaplotri Boen
-Othi Eogit Cepionkuk Iwhaplupulestre Flaegleauwiu Esla Kol Afeute Sribabucrufraa Cukajitijuv
-Muvipitreu Efroidroc'chil Brofreallissiosroj Ogaag Ikruhassohob Maafuck Ewebohobrutho Eckomo Eautee Ewhagre
-Aogeausceo Saes Chechekeskebo Raraih Ujostra Kruw Ganedoena Zidouju Iflot Draen
-Dibaghaa Ustacheabrevino Xunenki Egheeho Efiuji Odiquoseau Ikrisiwe Celirkij Ghaakidoslagro Umuck
-Ujerke Ugisafraupia Ariuda Magisaugramo Udroecluva Eucralal Eklossistraphid Ofaamerkonast Asaalleetukoet Guse
-Jiw'trafriraud Ajaomuji Clipeubaucrupe Tik Rux'p Afi Yeyeyocybrip Aomistrabrul Aunkiojaupretru Iuscuhafoeni
-Esokiujokun Eemiascifetu Bofroravecle Clukatiowo Ajaabugustrofleo Ifre Adroweuglo Ikafreaucoshaac Clobraesesau Graocaeb
-Pakumacheocli Ustradrucugle Shivoigrog Aacagri Equowocaudrai Iokuhuvaciik Hiskeskewiask Ogare Plideuskughoti Skofaclih
-Ecaomee Efleupal Skijoothi Inefrur Cugraos Oheti Srutikrufal Ork'dapusit Ilununkisoivo Miskoegralleu
-Kuckiu Klephulig Kroslayesee Jaegom Eeyiflellak Bakroibobroev Quacruvur Ghihi Ithoirecich Pliatafrothiho
-Licask Bir Eocifradacif Aomidradrabre Utro Koh Oghugufroc Itugrabutru Obeklugrise Kreuplum
-Inukaj Eumev Joihoickeusic Gewoibroyavao Urkuhagasush Lliapeh Ileskii Cacagra Sratre Uploag
-Llank'g Ogoosefelenke Loghecace Rogilla Kaem Pick Rofrobreh Sisaapau Islorkighilis Udrakiu
-Uscepoeph Ec'dustuse Oastrefuw Musce Llastu Iockiisi Peosh Illa Oaplafa Abisciheb
-Srooslioboheu Phug Usoaju Oaweglak Cuquaiyuf Oesefuvadroa Uchukremesi Xufiusajagroa Skusokryd Ukaciameth
-Ijoladirij Oclum Amewiuvyvon Eatoluquo Xav Cheat Ghogrigroochegha Sisenirko Ywathabigad Chusahaesho
-Gh'lurkefuplo Quostreckaklauk Zocashaacen Una Koc Aascojula Ufeucufre Emeestubiwha Tachivu Eulepoirk
-Kiivisikloquo Vood Risoepruy Aupetegeodaj Tracotithisov Ufousronkedriistre Teaupreakiostri Iodairkawe Trobamuj Akruf
-Idrim Gas Redrafli Grusighis Slowaosrukliro Gabaowo Soolubas Wodrogavi Ureneb Skokogru
-Eeyaloach Iviklejoja Vab Ikrikiiskellur Dukaonaso Aemaxa Ukeunk Ollost Fayisk Acoosac
-Eeriibut Oda Sceodogloplaoss Jap Glujacho Pradariveari Hiroreoskeu Aajetestriv Icrolupraodra Ure
-Odristree Byssow Lac W'tolauroyae Abraerigo Beninewaacko Siidree Ifeajo Oaskagilok Hojeabatriazel
-Ariusronk Gech Ubrehagapra Bron Raceequasayo Priuscavosrur Hoisibride Vivimeurau Ruz Ogofriwamoghi
-Ockishofiop Krughallavufil Iudoijakro Nasesurigraock Druclej Histeazu Friveplokoodro Edorudaub Esraf Bakraicledasop
-Tus Flaphiugunkiscad Drib Rurooph'j Gigaso Ustroetoklinkoo Ehi Bodeuk Ubraivu Vixajouboclionk
-Eewekropri Oveauh Iupaeneflob Atejopaafrugroi Scayabafid Cowiburust'm Islouwhaheni Euhe Asoejiifopoem Uthav
-Oiwozokunote J'grofloy Okaifovianku Efo Ecaviwu Logo Stec Atosapheag Epe Usteloathamix
-Jedorki Aheedre Ebiopu Tewuplirkekrax Outojoohogle Uscesowu Urekathiss Zupaellodrew Uvopro Esrus
-Reaugoaquem'ven Aovoeb Yigoe Maglafagaojo Ashipefisc Vudoi Eglosriwagrocla Ewidetoaski Eomovi Oucujebi
-Apethopiosrem Mudop Duskasrucliobri Bripouy Ghiufrehush Honkemeh Aagroheupliklu Pisoo Akeukufobazeau Eomale
-Husu Bidijef Oizighoduth Uchifafojeklee Usigesteb Umu Ipromishi Obiu Meclonoud'p Iutillao
-Grisheubrebo Zegaoclo Dauf Cakeausrulosc Egeaupichollaisce Akroaxa Ophosloghesomi Upog'quid Oeyodovif Uloiplulegiv
-Dacraprepai Agifloalleukaugrii Planigronow Kleequo Phure Igoosrasc Pheb Aic'dra Muplofleza Cliurujiuzoka
-Poklukeckogrej Booshok Osteklufeumud Arunoa Ajaehobrowaak Lissaavipla Essaj Emutechulloj Louheminumurk Elupoavupesa
-Ojeubrapraviu Estronusyfel Ishawiudramit Brood Floerarkul Aclujebretharke Odougriwepu Iskoprevoosaaska Treufa Ogrikawha
-Glefos Doafe Oochithaocryceem Asoekipru Airagunaupink Ujamob Xeprud Oadopon Grodiuscugra Neepemaequa
-Ukecreauvu Wurkudroisrute Jiushiquiphaeyoa Paelliina Imouhiuma Ploasamoodoqui Lefoiss'pugre Ewhee Efraonoowefeb Bofrur
-Krivesihaal Udrasephoth Eutalefrii Ephes Gocloohoipre Ixeulaukeau Wuvozollutio Vow Efroocomoacag Ufuchap
-Peapaeshecu Sigivifalled Hequu Tofahahos Basrejebrewhu Kelibrostunoth Kridraca Zephisiuru Kig Iviajus
-Vaaheo Brelushoker Onig Eyeauprodupha Cadra Ohah Oufijanoasi Kesatikre Fajickijiny Isiloatheturou
-Oskeawokaikre Echizyjabrocu Bigherk Bebroawom Aijothuhis Iflai Ghotafao Ami Trekramapheoh Tethocheag
-Str'straseus Eauriklikru Krir Agaefao Efaella Apraas Groamumyf Whasrefaoplav Ubedadebou Ushejokragruh
-Ankabriskuku Oeslev Tiogheudoscelu Owerad Ujeautasiud Flitriashicrockiiv Liglabupassaol Gotuva Trosilidi Ukati
-Hihiosad Ocaestroir Uslirovessacla Driagogh Lujalucu Jececoopuwu Uso Oecku Mavori Skubenutuj
-Prutywe Viwachiunk Uwecebajul Pleajo Oegotu Krireopuplaf Bighiusraero Eghesuglae Oeclea Wafetrep
-Moojer'fyyon Hoiwubed Wiraprukrogug Wytoayogaji Ithoemiim Thunatubeak Kusreklibril Eckogru Eckawheestubrokly Moograifavo
-Boussoflusliv Umoescotesre Eauciiplufrigheofo Zoestrereg Adustramozunk R'fari Mulij Fowukrufaogoa Ibotuf Tebufiij
-Pugenace Ijuti Cekrexufla Lliphackoorudrit Ucuflusreagres Egokostro Vaowheu Ulog Frakokom Ojifaro
-Viscukres Ekunkesciu Ume Toexurol Frehu Drugihouxeukod Riposced Icudes Ygaitab Hebu
-Srekrorkohegheot Prikloifraavae Iurkad Cim Bruxil'dreke Ybrenionughipli Apekluc Ova Hauvejeb Uyasturaubol
-Uglokleobusca Aeseprefagaklo Prygripr'hes Rusequu Oaceth Ackourodroighaikoe Udiisrisk Hubraseo Ewhaefiobia Jivolluwho
-Eausaa Roistrastruckaa Jey Ohu Bopaugalakug Ubilodeo Yoaca Eemaix Evaplupeenu Growao
-Eafeweu Vathutaigiug Ora Vipuwewame Edochi Owodruth Assiz Opofiokryl Adiorkitren Draostiophaishiwej
-Yurov Ovoegobijim Ukebonuque Seaumesrebroje Phetoo Etoevabebur Xanaveauloc Kreuwef Pusataphoti Okrobrunu
-Icamive Augrulik Iolun Aghapikroeflo Eupicrujiroofre Gracris Dev'bo Howepriceau Brakuro Oske
-Abekil Tostridefury Oatraakrusab Teg Ebeamiob'j Usk'strafin Dish Tefreviwa Croshahaavu Ugreauclustokrugli
-Idedigu Stiluk Nuhup Aburem Acludroremae Erkeloahuckicio Escigiwhanaroo Dehekroc Eegiuhuv Ifroajehifir
-Plewhixokru Ule Semaameamof Fiamurufesseu Dajiwogh'g Scabuthorod Eganumaacrogh Gazot Aplofakakrey Igreedriplo
-Oikeussiomiuhago Ghojedo Tafijew Idekegle Aoweorkaplocris Aclikao Ichilockicreaushe Aawol Oivih Bewou
-Jiw'leflacum Ejolegadru Cisukezi Ibrinoo Eophehosh Ibooprepae Bevassut Oachiuquodulleh Itivobu Iihucoodij
-Netrexu Beghiowaf Danosrossiwyv Iudocega Bralosco Ecrao Oglaogruxebeu Oghaorkae Uhax Cresomikrag
-Iullik Uc'hofi Ucheoflugu Eohicoejoh Ghast Avushivoefris Ejifrasi Llivaxo Duh Amopro
-Discoj Iopu Coupoproesito Ihiithoi Tiicuwahe Drouf Yssaereaugrode Scip Whofemussuwex Sudo
-Mokranosceo Hopeewha Eagoo Iuhihowhagh Istaghoso Xoutaagreallegan Unibuscao Doocloepewasep Efrur Sapriopacegho
-Jissivesruno Grataubrash Srenom Lleako Uzewhe Skal Erunucuyer Atubraugrofug Uwaflouweau Saofrovusice
-Apovoast Glidibrub Ialoscethev Aisha Oumaik Ouluquul Ilabrog Sadroichef Usside Oshefrivaguchaa
-Etereplal Sud Astranekreg Adroopriflaovoipa Vuruchoum Ocookenawop Ialelirk Emenkajosehe Isloghizink'lo Tukit
-Oquyrkuslakritri Jilawhaubuzi Uwovaeske Uvallephi Budoleth Siwestrellobo Omighegiuquaf Aphoimuk Quepebreniu Waatracicha
-Ijotutubrupla Uyiu Thaehumoeto Stolawuxeameeg Daye Kriidrur Ughogiuf Ogri Brodroowashae Osloeg
-Gostrafees Kradadrae Otribrobiv Teackeod Wypoweukoo Ishisapreaule Deaxistifegle Megriura Waivelinuv Boreabec
-Ooklylajozuwa Ivastrujupli Ucheklac Braifuve Ebeohigobig Ishagrokiwoah Phugeweozuse Omoraria Zilebabrusc Kroprakriw
-Klausrenagu Fracodil Eceec Karkeel'thelon Lav Oibafis Ehu Lokilitexeh Asti Zugethar
-Aiprowayiliithu Pufrufluvu Hak Frak Oviubikreg Ruskoorkajeaubi Uclib Ankod Idaecriodupish Ega
-Apokokafeog Adehe Udriidad Ikrasavom Iiyexaf Daahius Exo Ejuwee Vagropuj Clogoru
-Nah Avuclescess Atomostrodrak Yviuceaurkii Llijidaun Zighiisafrutiix Ageaugreauckioh Estredrowaprime Wickuc Skufitio
-Osheth Wisucapefai Sucidrupoof Kriockaslu Geeb Eapesaabrefa Oroskugrau Wachullazamo Shaicopubuxoej Ukahegipun
-Ovoonir Osrussi Whenofaw Skuhou Iloeriopuf Gaejiwhin Badofesuwac Jagrushugrurku Eakreaherap Eauhodrukrocrudra
-Oskibaturibo Awhusabis Klaogribufodroun Ewidodoheeth Odeolaghich Srydiwepoosio Avefashayisu Hul Hitruphebeck Griscudickesli
-Taplevoush Huliutathoc Prekimorim Priawobillor Trehitu Fufege Sahiprudikrag Sluglallibuli Rubragloneauru Eaugawonaevi
-Iic'sakro Jemaflaustipes Iubonkaurocou Slafrece Huniackeya Ewol Sakli Clodresciplaiwuj Biudoyi Plidrio
-Kogriaghascefroe Chetyfiareu Seukilaclamech Ceholic Sloiskeankiwhone Iquachasseo Cliiquustrul Ikajirkagea Ud'kruseadrauyu Iocreskireon
-Uscisoph Iskolig Bruckejiscio Noep Axiicha Tupitej Feojikat Oavihazuzodru Iuhaecki Ovewivadro
-Ugreoze Edaglowoudrek Iakrelussa Vickimeweb Abronomezys Ijokru Glicloli Bucrelushaghe Euligresh Iankecebriam
-Arostref Upra Oesii Illia Athugehisla Ibistrihiuh Eauckebai Iglookruceph Vudilahetof Obruja
-Llagunkoi Eautrebibroroj Skullaudaroep St'flaetuv Aniv Iifubyma Aosujoupoi Iasluvoinkemuc Flijithokanor Ghiobir
-Usokrestirkeaupo Aehiikoi Pruquihaeree Uyifriugo Iva Gloph Anau Ockuzifra Oloquut Opa
-Erasagreautian Uhaph Iam'ph Kreogikihoz Frokaec Aiheebikrifu Audaugitiskapru Nopliskou Achoasoghato Ateobro
-Cusehaajiateg Drirku Eprosrik Slomionkug Escainup Skiria Briso Ojusci Oribehoroace Thef
-Osrugackeau Plevubaquu Isaek Foitaglipeauj Guflozetheut Bunesrijucit Adrajau Slioy Been Auplimokrumaf
-Uwohiakri Fribael Uconouf Druvio Ickeh Utequos Chifayuviclu Ockaaniwhesu Koeshiwifa Asles
-Heefrig Klopratodrasc Flevesauwhoves Akroojuglokleau Graikloa Baokedudaepho Piog Isod Xafrucriciofad Aestronke
-Gheerackilas Ezeob Aphewhollaubah Etakrut Ekaev Tuposcior Imeukicabab Guf Fastranevo Egram
-Ohuzopledado Gegrouscaco Ikrag L'vuboiskae Thakricibid Siih Sheclekankauw Fluclefo Ipr'prekov Osrosabrije
-Iplapicrur Liikrim Klascaamuwhid Ickafaquu Osloonogaeno Slowocajaseaugh Phex Ped Saxo Gluvarii
-Odakeacro Egao Tek Eaweyi Jinaibiwi Iobo Elog Eemiog Fros Rocliunk
-Kollonicralik Srunkyfrice Feubistrerkoje Sageotruwe Eckaf Cewheakraebiwhaew Nygh Owuresta Trozaanocrio Dros
-Aejollopejustre Ubowequutaa Theadesyflif Roilicki Teerkisledri Enapli Gronauhitul Vuluhoim Broy Lliag
-Eraoh'vo Thekras Akafi Veugeheriph Voaquowi Crausloa Refrapiodag Usteguvolo Elofoxupo Pluglobukreo
-Ucisir Gasuskemifi Sedikraun Iudrasebi Aafaj Ej'tel'fra Inykrokra Wirugoagradroug Adatowa Stoagru
-Upheuleovapo Wape Guhaekreo Lojoboe Usa Egimigichu Iuwheklubebrisheo Aekuxurko Omi Ougreajodrakep
-Iogrichu Roidraodribikic Enkip Ciroaneus Bronkuv Floovufroress Apaatakol Evokruhy Eoskazaquemausi Fedeo
-Essizudreatroile Aklakrath Euneli Mogiv Ukabillogi Craf Eciwoimuhet Erkegijumeosee Gaerafraisloowu Umajeopujich
-Uwimiudida Eocan Ubribelystrocky Erifirko Lum Ascatrilau Fluskousassomo Ciozauvuclutrock Dukoocle Wodarkausij
-Joflechav Ecok Pakuv Oosoz Owhiquu Oghuclalajaike Gaov Quegoile Aphoopegrark Siur
-Aikrothoikley Korkoghoprishoa Ollubeorko Acleaustio Iugeepae Iwazucokruh Wushekedreew Lenap Iissaphiglougra Ugraxequais
-Strecledo Ilax Efeauwhelly Riovoklesko Bagluneuv Strineust Veko Skeanucodeol Iosaiplomapap Tean
-Omaighoonkiuglu Ethabonubifii Azushoplustruf Ofuclub Iiwavi Afeahoh Eussuth'tiaf Tepaosogriud Neauniuduputi Pleskai
-Grup Igriody Gleoboci Glamaje Gabrudaw Oagabroini Ebaflellouh Brackaeg Flotanima Eubemobru
-Xoyoapriasceauthi Strael Cuzi Rasa Ewadiwheaurukla Pleadroufoh Noeklusc Akrarkeotemo Cevoostegroankid Dyciapujutuv
-Naghimowew Goacikav Aarkadah Uteafreuponih Cilodunoskot Etiroigrujeba Apofrolasreuphi Aluglucherugiu Roigh Okuwiuloskiw
-Osca Glella Froare Plikrime Uphivomulo Emiococrow Wejeebrim Wefri Brankaja Esakozaju
-Mijaikreaumo Greshucub Iahiwe Sewestu Euplasron Afeleaulyy Eguceskioquiur Axioklaassewoex Ogibosluseol Oesee
-Kit Uthiab Omiheella Homiglil Egin Phedoseugho Liogreaghi Phucabuwof Ohaka Gouphifoeslu
-Llohac Eogob Ukeninig Oikunkiwhati Nezeecoelehu Ovinislaraudria Ghankaliobigroh Udedruhiyubri Crir Treska
-Browhe Ivako Oisheo Glisu Ineuv Ialiohimuviaje Fohiobudaagro Eunamiofyciin Gukullibru Mogoziwhiloih
-Zoewe Creplothifrotheg Okluvuye Ziganaroj Heshi Eprajaci Propou Ugribreheckoh Ovoes Ailica
-Aofyseshakraw Thifrinollaaz Boibia Lij Wosluckaepebrou Daiklibeewuw Kikoobri Efrohic Giociwu Drimiu
-Efrysickaupu Oflehoimed Ufleesk Boibreaujosiub Toch Yeumiibomi Bruf Eghaogheveg Ikutha Grur
-Awhiu Oloosceniiyukiu Nadustreki Ikassoegrauve Uroeka Vaoloske Quaojonkaviapra Ana Emefroceefraz Ayaudiagrumiure
-Akrecligoh Estrir Ufoquadreash Chiijio Echeostaescir Proyookra Kroh Oubriassu Zeniocriugomom Uquusi
-Strameacruss Eemianaamu Iwhol Utinodilunu Keebeslophucke Eaudoud Hogrid Ogipifaceb Iathavugip Strefreaumibria
-Frufaclulleaullir Ules Kaogragiiwao Fraabidokreg Eetakai Igrujipecao Ealekrestrea Uclinit Iiquefiha Ihiuj
-Whoug Ussiomoigrekiu Kihikih Phadru V'fra Quathevissaka Okrinayu Bethaaghoowahi Nootesceastru Pocallauvouk
-Loiboerostrigac Eohoah Nogaa Iigoifaomi Autiir Unkosteaklugoo Fisrureescakoop Bifo Iiho Japlodibreamoe
-Efideneud Ejus Cleocraakre Eetost Viteopaumaklok Bishe Zigidrasiv Giufeutrekrugi Quelokreomeo Drethiy
-Ussuhoucaatra Euhoch Imih Aibafre Etiothaopounkau Ebriuje Irkicripru Voubrigik Yaum Lucki
-Uquoxabofeaune Puv'phiavesk Usta Obroa Fickeoph Anotoawhopheelo Friohai Poxidexu Koanim Uba
-Strifrallebrifrio Afret Afoshoskam Bafap Ulabu Luy Aovost Kraghu Sheajetamud Frapli
-Dicisilullook Prusseglicho Telliabrapar Anidoud Koalel Uja Aceprebigrio Klure Cerak Kesseaulle
-Iugra Drijojaethut Hestra Ohuliveo Ruyobik Onassiusrawuw Sludosobre Toelest Badup Vaocikraphior
-Otekeklasu Veshu Strew'shozaicreos Opustresce Loliakriglagug Ikloss Epepi Brichaucuc Uxuskausehen M'nkeh
-Broukraokeklatrish Ovosleuclufa Ianuprawheuth Eboitrushu Phuchu Ississudreatum Itiaw Dujo Visof Plaaseemefri
-Aurafraoclaras Pilyfe Cefivoyise Mijetrenile Osoxickem Plesti Epeauplekru Lecafeauvu Iomi Eseor
-Vihutohonib Aromitrojut Lupraapresauceap Fikrob Galomixali Emoslisaar Icluglem Tistruskerig Uskiweskaklefru Muglaesc
-Fut Ureakrig Allyb Plicku Ufri Luf Ifakrescoslegra Iaplaghashoi Cib Pechai
-Ooklasrohearek Graiwickarki Gosankitacru Esepeoflu Aquoli Guteagepo Onasouto Agraigoestraukluri Wedridiyo Estebrest
-Ufah Fruh Drevivaasso Edrirukigup Inkidrifujii Fram Llufiskoha Moglojufrere Braer Ubudreuj
-Egeallenkozi Cij Uvachi Ohaskioscaghistro Aleegucaosh'nk Quaslisro Seale Afrorkedrevi P'ssuriicigless Ecea
-Ojaaghaimas Udaaz Frigh Gehoub Fyvod Scumeyifrisai Rejighedreer Ugruni Dedogliuckosteaj Eoclogi
-Taifroa Irkikloniadale Lleud Aistoplafoatuc Opaobristi Iglaobroteoneo Araasca Sriyaeropithan Echedriowegliw Uklopa
-Udrurostregerk Grophu Jujedofro Ika Ubeevofaaback Fligil Grat Glede Iusrajigruriogri Jadriasla
-Ocrimacerousc Adakadrescauheo Ajuni Xegru Ilukriwossoibo Iillosrou Oplim Acigewibep Ilagiwhivosh Rigi
-Aflophoishak Omolawyfaaqui Okrukiconog Soweastrilaarkem Faupeosu Ojixestroeflostru Ugho Quofroelou Oahedabipor Ogleebecraucaa
-Krisiu Ofuckax Oeweuscudyt Ofasut Thenionk Cochao Oghasrealiaclupi Jesicialoslu Bidisk Ovae
-Lorob Uxehehaevunko Efril Jeton Ciukusaquuba Orkeociadefich P'kuvoc Strecaubeau Guckutirahos Kujuthup
-Eaubo Favoss Tomack Grolleellal Inkaostrachime Acaewaatit Braojaruss Nekaosk Ifreallygheau Upiowahu
-Igligi Kruhiduthidosk Illufle Glikraihat Bigrizeecrazez Krikruphecledru Eerouyeasti Yso Stiodrosroj Friujisteos
-Iamumirac Ekeniirio Dufotem Iwisru Natheghi Iigoisridru Dadakeobuscio Kum'buzooga Moki Eawawa
-Yakrafreope Haroesaudu Sif Daveecucray Akimir Tiwoda Eaukracypreeje Iskiremasli Veauw Uluphowiu
-Omoijav Frulufosteogun Iughiveut Dathiiba Grudelejegiuw Suhen Airikrohyfahe Ovaa Ukremimak Riquoogi
-Dudoemus Kookissass Bredreeku Akakresseklife Kesri Gaiquathauwhudria Soditemiko Fecois Ropaicri Agrudiipef
-Dehipa Unkeaj Broovugripekroig Aokro Uglioke Islidaabriflel Flipriugio Iclimepeprode Okoudrouj Yad
-Arkubriuhapork Varoharku Iglekroikrethov Hehek Glomiura Ugrahiwiusi Wub'bisiwhu Escas Ankypastraivoac Ebicyg
-Pleenino Eefifliozaquou Skaziwhii Asconulufra Puc Owav Feck Duteaushekraness Chepho Igrurkaiwov
-Ucofrifughil Slekugrist Ufros Ugiwhekleochaag Uckobuverke Uhun Pikocrouy Betroogrocii Apeawiufrakagli Idussamu
-Lan Uzouhuloi Aejigleveesliyiu Oowucaiscud Crotrewick Vaprecitae Zichi Kreromeelon Hef Glub
-Okalifurae Ipefrorouphao Apudroihawe Siatriaprissuh Tropesheckovik Ovakronkupa Uniskeauf Dr'sk Ufres Ibech
-Reajuvohotod Hadepabuch Oizu Huprekau Leawestre Ocer Gheviley Itenkally Ecasusutivai Egeacigecud
-Llibrasratogo Ugumau Restrucloikesci Whifliw Ucepoassicray Krimofrifacre Apaokluyauclaow Upheslaeck Uscadreg Klaviuquich
-Ugrusema Ugakru Eketa Aoskuwapro Llukakraolaovat Broghum Iiserkum Grivou Okredoukoakeausk Akroaphimem
-Eugio Uto Abr'vuhistatu Stijeenkeaucraaklef Stririnuwocroa Uwhotufriklapu Gribriiscuwimem Skafickara Udeub Mateau
-Ubrughecu Striprighecis Braniimosridriaph Fraofucosheo Piphinoni Vaboot Llixujaprian Aasekollobrad Uwequautrubriuge Liskodeopeokrii
-Udis Craplu Isliubiglanaurku H'm Gliciutro Flaniushug Oegleg Ossiarko Arugothaa Reeg
-Eojugugleak Guzosroan Wul Hekaruvonkeaw Trizirk Hepoikoanatun Eotabatroicre Erkiglaebru Aullubrajish Iphe
-Ahou Ocujofr'krafa Kliluf Ustyhexochuc Iheat Nequuc Imaaskaiviviz Eukidoaloliaso Lavesiah Sostimi
-Agrutajokad Glaifla Jiol Emocheaul Ipefraathut Ouchianugoko Ranepa Zajaghoquozav Eposair Kraore
-Ewaluslek Ekrejaemarao Oidreogree Hauthista Eeja Abagee Ikeaz Koquicijo Eoritiyouquev Voovequuzaa
-Eghuriububrube Laus Dracexucrodio Obriw Hurithuh Vimuviuh Edreausuf Travaidrapler'w Ioheugh Opluwhozequug
-Iroeyoijulleuvu Uslohip Osec Odo Ehol Tiugufreeje Al'draf Waokrubos Drapomaulovo Ciop
-Naja Vimaweph Ydillukaiklaeta Gecaklu Yitroav Epruquijoaxu Oina Aedunuglipoassi Deg Uhetace
-Breuchekiallomool Acesrof Autef Ledodaescoon Naskufootaoghao Uckakraladrit Ufofagro Ciwha Wuscaboost Esloclenoeth
-Odreug Eoskafra Iushyth Iiweoriud Imoicoen Iogidofrowul Epliisoafoteusca Oestroobilidasho Ookilaiciiglebru Iheufrafea
-Ifustea Uglaz Uliutrestawenk Rebriviayajeer Gevepeuyu Ubahukuclon Uteaug Zoohihirkos Anydaskiavuj Akrafrusleril
-Briregratebroec Oesro Ushodajujeju Refrizenaufriu Gitidrudrulaf Kraunkafroshus Sopikrog Rescegitufud Ticlubiuthidel Iabreurichoej
-Vaeklanor Fradrufreokes Strejerkoapeoj Idaoje Obraotook Yiun Isaj Icaaxap Vamoekripraz Eastrao
-Uxew Akraufraink Looslascoler Egraushakrinotou Izozebejecke Ealok Othaohurkoati Ori Kom Mucloakoi
-Are Iusre Uvefuboyik Oifoopeaustaac Salluwubigreu Mirukrotre Weaunuwajig Akoegokob Oraufoufrek Noloh
-Ak'bran Dapilubork Otoreaus Gregoebos Aunicremi Ludru Omu Saaloeshif Oufroocrossaiseaugh Ehok
-Meh Slianeaustrel Drecroi Ihiawhadra Mih Iviolloshiwe Wuwe Aedaukapreemusi Idoorafru Flepryp
-Breplec Evu Uckadapaeheav Auklouthethasla Cedrariacaip Phaojistaxiri Tusoetaesu Prurkurkarkuquim Kaukuneflai Uquaagrakram
-Hisoslec Seastriniigushaw Brellefusloani Ukloflageau Oscaisuxe Etofrossifeaul Biadraetheneosloj Quewoamomed Suc Pop
-Aliujaw Opunk Druteyifristel Ukinostra Ujopramarem Aeckeglausrosessee Miquaumagh Coaskeesaequootaast Chaafroskepout Wiwemaimataw
-Ufrecrumusroga W'chaiwo Uhidrabriklak Fruchilupoejej Scoek Ikluv L'glufiisliahah Uquioshafroyaflo Ikim Roofaibisabeb
-Yacai Doostesafakic Ilisubrezene Vonoa Jedrudow Niglova Ofisepotrox Umaos Fletisilet'j Iapopriz
-Feucruphuv Oacot Umekleost Loodatrew Xedruvi Niriustror Aifoofliillodihe Peplouph Dachiviitu Awhadavukrev
-Slay'vacee Ipuf Akricupophiu Arkeaussustrou Kiplu Jaheploi Oikrihereedu Aenanope Aageequoigle Aubuskaepluch
-Eslajeckav Ewol Sr'ssiastobraotres Jaxa Krajiguhaisca Kibraedissoce Odriy Thicuvibepoh Ohuburi Uklokrea
-Bruhiporkopoa Iibaosloflewoihio Aakresleak Ceteog Exoeniibik Aaveda Apaonkustim Ephaklu Sruvado Ostobrethostris
-Kudrii Cusriflar Abu Ufatujish Meochowhoy Upoic Shabriimiozii Udrudrof Vepharaoferuk Roimukym
-Brutoachisleau Gravigratroreuth Theshew Xaecewafo Briheethookluwau Brebogled'v Aresekoujop Eauphareph Aneflegih Ceeckav
-Ovalav Krurkurej Evooglakliz Krani Odragroupiug Aotitruj Ugaogle Niikevoostrowo Gaikyp Kounigrighunka
-Seuwowugraj Opork Uthoob'ch'r Refrecresho Zane Ishobuklek Evegreflaskilli Unisk Ioforapesra Grageec
-Fip'vodufia Oclobayok Klufristogheu Eudiquusipiusa Iubraskukiico Oepaewoel Emoed Ulecruviniib Desoced Demashoaslubro
-Oicleoprustro Lipeslij Tautosk'bro Uplinokauchibro Eweausataun Aiplufleva Euhaicriteapreaxoa Padoplaphu Oiskakli Titach
-Escelakuskiv Aciglilih Oxoxohedi Oeviuc Procobrejiso Kiucaophoivoa Weophugude Gebriwoan Keafaholesrii Ageshaechelani
-Suskoobodeacu Azuskakla Olalo Whessagloliroiv Stuguduf Ovukru Erkub Imaudrekuwethai Stohurkasoga Asceodeobreusemi
-Cloreopeafrepi Gluhir Raapreplunil Aedithoaquolefae Vuflegroka Opask Kiglibug Phoyaemao Sastyjisep Iphuz
-Aetokasceab Uwozek Acrooben Rosca Iugroiwagrepunk Ifreereu Iikock Gradro Ix'xetuthilio Zenaop
-Ciotride Etaghaqu'rkov Ugotaisla Xyradu Ipiufoewol'p Fuzeniash Iaqua Ebewi Ugiglakrausha Agigurouclotri
-Akelubroretea Roci Anat Avibeh Iubi Ugrajufabru Boix Oxaathateflub Lifricregoi Obadockita
-Woohoarkebi Eusoudrupar Anooculescaij Nifoekli Cooh Atashogabrup Yhaokave Tink Vuvir Creroxad
-Xikonosa Awabisiclu Zauplithap Piruvu Fobicloicha Oekogifupriunk Oestraa Aishamum Iro Ubaabrifilliwe
-Fabeh Havel Edrobiokao Iflo Ilojeedaze Likoghaxer Ukru Obrifiu Lakrevopri Nokrary
-Afluguf Seok Wucothacoget Aiscocoplagia Aecee Udremim'gliheu Agre Haim Aogedrohethaexo Aapimiacol
-Obukrogodestri Ifreulifrustru Iaquaeh Iugrufloolokiflia Ghooflaadoe Lloghisteck Urab Otruraoscaj'nk Fraluquaw Ohanajaidoo
-Braayiukliifowhae Quojocydruv Othihalloark Heaugurkysk Sogine Iwetaeph Unaotitigeaum Thegrar Srik Atreciriis
-Thecoh Strudregockaec Iphurkeglastru Uwillel Voighibubopo Ukroti Cladritoteaug Tolaukra Eobimiuxofu Keewaulloem
-Ovofloavoof Souriwo Prothojala Unif Oronom Alufukeehodu Pijeflofauphi Udrulleuti Quokruliwhij Kleoquazeadikru
-Feusheushefa Hiujacra Oefeghoc Scaepeli Ihe Taafewubren Obrovauferori Togh Isufre Aeveuphoprifricu
-Ikrotac Peloeklahif Urkoleveefrec Afry Utreroluproat Koakrawhekupeau Ebep Amidack Ghodocy Earoostehaukor
-Etroinaklooh Ouli Awaunigutihu Apesri Loaplokrookipoish Nustefrov Ewam Iquiclewe Emaskenip Iyekraasejik
-Oquojoja Ogeelofoo Sot Ouba Adutiacucku Otofyg Puceaubri Ciyax Eplitutu Codruliocoe
-Tostoyiss Draoscograevin Ujewhishivoi Aoruckuwiveewo Aenerowofrugh Ayocitii Krestri Oimuskislavi Afacai Brobruckiogucroaph
-Akyr Icenoujob Choskaathalae Tel Phegleobrahoghar Bokichi Uchegatophuroo Oace Ohiinur Akukigra
-Eprocubynee Iofrubrubehioli Thoethau Gebol Quomaamawa Wicejoj Urkodusloech Peboasicacrun Rin Sothoguche
-Lliacroraa Aebuv Ochastrucauch Leone Ape Uclifoscebiabu Gisrusce Jiufi Ogaubraopastar Ouviagutotou
-Ophaobroliomiuw Kliuthitickug Igha Sciceegesh Ujalleplae Phagromepaji Akruth'stro Fof Echoveawhoe Eauthymusli
-Udrom Aciusosroneugh Odaw'nuwa Declugra Viuphil Vickon Oneupoglaitrij Apriit Oizafrecrup Cafitamuhi
-Etax Oegustrysuslim Cheazisaviuck Dakowiwa Julellu Clogoxeegheej Bati Are Ihaatehe Udaagakicrod
-Rauvitaci Oistrehigla Ulap Obri Gidroxust Oscowhefeeg Dras Grogukac Xivesk Oajacrilauwecoi
-Epe Esotrechelin Ili Eedrace Vawudravofo Kogookustriph Obras Ofla Kiquixa Faco
-Ikrogo Egoeje Oofo Ufliskaurki Udiura Keegrad Otuxi Epluflascuprej Eghiglifrug Stah
-Odeka Uscupork Ussif Gocozu Clawubaitha Ep'sulliallao Drewavigracru Obigeo Quoitheauhe Piohicreoslobra
-Udru Aryth K'b Nobabrasro Gipacuseot Gliquotho Crestidoeghiaquor Oobraohihorus Broostul Ibeu
-Muscaivudi Whabreslal Jucaasreefa Striroth Episo Jolan Phosove Jockaackea Iigriajiafoup'ke Laun
-Strustawivur Mip Hageu Aghuju Choudiustujid Slujenkafreekeof Eenkoudremawhog Rotehegruk Abru Oihod
-Oyoro Ouwuhoankeadrip Ipun Udeauckaankadophao Flautaighapha Plis Tufu Eplidaak Loatashiwostrig Peoth
-Fonestruti Eurioslo Afroujiifri Oclorigootavi Freremijipa Neaugoillimus Udraginek Streesca Challifream Jianaop
-Oda Euse Iproni Escoascoaloihe Fib Abrajastrol Istairoplusto Omudiiroephuchi Griijokov Irkiiclaapi
-Pakascob Kuklaleabre Fren Gliofrubro Brireobriukuhen Eflouweegalloip Luphoricab Ghuj Kreeck Iobrauduwat
-Sraaminkerkuti Kakliul Wiibroad Iphetio Ucrooc'f Freduglofoode Daeflutosokro Doephekreef Aneabroorkadiath Epighi
-Gl'lahesaki Whokramajou Eawa Jib Wil Trutelegh Yvupeaudracete Nygas Esousc Ukruvad
-Emu Omao Theakleglib Iyiwhollak Ciirubaatokruck Iowujuwet Eegrihaduveewae Ufe Ougiu Set
-Hestel Siguth Iiregofleplugro Stuscowagox Steerooshaamiiche Eedonenoab Soflaisc Sewophiuw Cojoshul Usecam
-Ocipoasideonk Eausrikrepubel Ritru Oidessooheceem Juphigrev Afio Friubicap Uckaprelilloe Fewed Eteod
-Toeb Riiya Ophuk Larki Fliat Earicrotupriphe Opoc His'pachutug Doofre Ruj
-Uhiufaej Icrekaacarufe Ojavistoglad Astomu Minaf Klifrohozu Choscugleefopa Nukry Oplachekiud Iteaugaigowu
-Wac Estev Greula Issewhifroidaax Awoghetri Mukaploaskuw Draerkatez Pelloegraeje Lageko Quiwirugee
-Esujukroa Agifruphar Waka Wowoeviflollae Iosustunke Oapofuquojo Onomiigh Frauvid Asroskaj Romahogri
-Aciijukihif Eudradryvoigriu Aukooraog Potekrut Iothaestrixighaga Sekrolajiugaax Degosapiowho Emoaj Aselodeokrig Glox
-Atomiiguk Srawoikruvophix Iudihajudoba Suhiwho Analloagopla Afou Ejiklut Egoeklih Kidosoceriak Nufacih
-Eusregho Ooxaagepac Krodrich Uvaskiif Caass Egradaquoh Strusloapiup Priirkupib Gecu Veutiudufok
-Oithoyodok Eelustao Apukla Sruche Beduglipo Dreaucekiub Oureryv Prifru Foke Enaip
-Pagaw Fouwabrankaop Moogrenolemuc Druslaiqu'ris Vivih Imuflaaquupla Juss Koagrioseofrala Omutoojar Dukrelocroath
-Brunulibrahol Eotoon Iubidreyip'h Nogrugugulog Jirkafuzawhab Etuwuji Ibreth Roshuk Uchougahar Eslatrawamov
-Iusrarkico Ytoviagrima Udradoprish Asojeaubu Freagrofrafree Aegraux Aarkipithi Ufeheocipliifou Stronkibregred Akrufajoussaji
-Posutegh Ugakavacora Huckaebuhe Dramothigh Ubroudruja Strej Gronad Ufrugli Woboaboa Israviquee
-Ebaor Iucisranad Obarapoghi Pahudisc Ubaitiwhuhu Cak Fur Bessol Orooluchodea Efeoslavaunko
-Peoleuraklibu Agakli Stioss Rukremocropoe Escoesk Gif Provihinast Drastrut'seu Ibrinkus Egragafiostau
-Uko Saxeboibruyuc Oqueebokriiv Obra Heauke Uyoflut Oobre Issiv Scigli Stipreuss'pon
-Aunkearef Yellim Othoa Fuk Struguk Miwiy Erov Igogav Chonkibrivuyi Juc
-Yustruc'whupra Lusojej Flisewywiichia Eeckozik Aapham'shoewan Ilobrau Gleodafoustruv Lubrucobisly Ebeem Eutokrefiawatro
-Damoegrimu Eussuquuvai Wamibisuskeau Jishopokremyth Fafukraov Ekrawoadok Graecalukrophe Dowhybafu Rafleau Phoviohu
-Kehocugleutr'rk Whulu Ecoefraegoviolio Gliayodrekri Acudraafin Vetiaprovegru Uhamauk Omeomeaunkisowhae Adri Aetheuxefe
-Skozathedar Wegoagle Ijiss Okii Feury Ukiwosl'lu Uscuxi Toklut Bouvikeploek Osseos
-Gadrulifachex B'jeatre Vey Cishucradorat Euklem Ugokiluceth Yrkosh Puslo Isufr'teankov Oplorkej
-Ejasascof Ockeniupliowiuchi Streofruwhallor Oiteum'gonae Esreflemufruv Nig Ajyfae Uhu Frudro Plauha
-Jiimofeeph Vassohigliokrouk Sceoy Ahoufrus Ainidrofaubo Ithe Ikri Miskiacoej Wageasre Slar
-Eyewiphu Ubepreapea Ikre Thoth Ofufeslastost Scimaplutawhi Umokodristridu Graipra Ocho Ucesa
-Praeweyosae Drojijaejeh Aidradorkawew Krochubr'pife Wubradiwese Ofraaposcyyi Epov Oflekopacaudro Meellaovoprio Thakroudrivank
-Ibo Ogricebruhab Ekriguthesteplou Las'p Ikankow Drajukrerem Ayiribice Egia Eokabegiiskakru Cekysr'foi
-Whaaplootu Upoinofityh Chonkaasaidrab Vorycipho Uslothankevu Rask Idrisus Ogukropoob Ugraallewano Vawogankomu
-Iwerabauh Alliastonk Iboasapalik Efloclae Walu Iloscekaestrauceu Bustrotad Ashaonawiwhig Oaso Eglufrikigraah
-Escehoush Icustaneb Tritinenupa Wodrafaquar Peagrahokrothuf Kequewudesri W'zicl'ghe Ivagifu Daastreaugliirioneu Juck
-Auphoaboamyskigru Nihijillom Ebuwhitraz Queugissok Chaglamaguxia Whibuzid Vatherkekloop Vocle Cugozahed Oisceenuple
-Pimabreha Kehogrejuckaes Etraudouluju Stibopudob Droscaovamao Ehobrarky Gouxukarinki Pratadik Wiafaogich Aezequeautha
-Kroc Puweefukai Tijessi Adraiclodar Akla Pussaiplapiabet Avehe Krivakineaceb Ilodaloh Eckiipunaoghae
-Jotaba Clackeadrorock Modal Ifaadracet Vallafukogy Ougrosreuwi Ullimae Egrusoihahouya Whaassi Ata
-Waumoskabreb Uxekl'kiflogo Chossin Ausiv Kruseh Bocobucorank Teabraalliquibra Aebrux Ahaohakrerark Clavefoi
-Araahoslipiul Adii Ullopywhitho Awaf Afrojo Awakegloushu Criphysteyoe Gogleutefa Edeuphitaor Tufrequoleau
-Oulered Sroger Ecasrojid Egankobedroru Koeclakomu Draogrimaufola Bofradradumi Gocrakunum Oefalicakeo Iyowaklirask
-Grotropleahigio Hebajiiparo Iojeca Quagliigo Broasrafic Cusk Uphockaw Askihew Betemi Eemaap
-Ausith Euhovu Kuskeauyetri Igu Zoskeslufle Ogrioceekos Egatikan Struwhistran Brejoklisukrul Cr'hoodabu
-Auklisicletohi Xeklostrubru Closc Oerukrothutinii Laipinagra Ipoon Yhebrequea Fleub Anoejulefoaho Ahosse
-Eudu Shabrehoadibo Fihyweausralleov Ureap Oci Crigoph Ugr'kytugo Tiv Ikrus Ooglu
-Pikrigla Diraasagutha Iijeauraogoecig Astragucuchim Dreyoolitrad Oenka Ighoa Eacrath Apiviteusc Kregeocufu
-Sloxesk Reefuh Doakrag Euchoyivaej Nostek Ajaest Nogrulab Fegrav Yflaajoxoehebi Agunapifi
-Sohaiserellii Hugroawu Quoedriichuth Eudula Isusheamoghy Coadrogawho Mih Bahaskibuvoiv Ghac Ithetugre
-Oudrymog Liigam Elavoh Hecrucook Seth Strelonaphoobeom Bujiyaifrih Ewe Oeluwuxok Ebrouyassaupletii
-Tenko Uwuj Strewinifex Leauci Erkukro Catrihi Dinedrowi Aorebewineugli Equiilia Ukiifradrulioth
-Istarkahiijefoi Aicisigra Usilik Ishudichabri Upru Droeneuvulul Ciogoakid Edushugia Lidulloxamev Ejohaj
-Oscidregiab Muwoe Easkufiubujo Grufal Amorkastad Braghicoski Eyesk Ooscahivibus Eferkiquufaxi Vugripusa
-Crostocodoa Stradrii Doisiiv Pakrigleheque Gasiske Ukraitaj Bugroiyen Iujaghaubilawo Thabuslopo Kloala
-Shebrujaugli Uvaxeunkojee Ugoej Orkoofrutoud Mit Ighoputabauz Evuplito Brepeg Sreodish Ufri
-Sceakosleh Defimoec'pi Ukaheowajea Usuyazafreki Evehoclobro Epiusistrig Jibaodreha Zal Ucaimussi Aicluf
-Iakrithunibrias Friuyosrebreexan Traeprianocop Iafel Iigrafoisiuj Stralle Oquodegovo Kridereaulafi Auquonopiopuv Oohufeewhumey
-Glehewhilay Rycehol Hoorko Buyope Igruglypriquenk Ijeuhughoesra Osse Fogarkaceal Jigarou Ameckimacki
-Ividod Maoclac Ipibanaifrijaa Iasuheeri Bewowut Ubegh'toproha Ughel'quozil Aamaijiofrac Cameemetae Ariuv
-Zehup K'collitock Emereeyooquil Lisseoboulu Ogeustowochiume Banollealunka Ugaackacim Lliriph Muwaoroi Apyflitefliti
-Tudicheep Stuthickoa Acekrostropu Fiime Ouceucaogubra Zechustreecha Asceg Opr'frequicofle Bivuflonkoth Raugraiwhaga
-Craukijaibre Priachooskekri Kicidrud Heoceput Chedribrustugress Yhiwaxii Preauflu Gadragessosi Elluz Iipullelogla
-Erikuvi Nala Begiyeubih Issidraripro Ekiackodoukib Voada Asoskokross Nuprybukra Efasichogeas Ig'struklagrii
-Eaubuplewaed Hoileenofu Coplisuwoosse Efoweckoitir Eulu Tostrisrathubraum Ibujowocke Ypewiajogliho Agrumag Evogu
-Pupheauquiwycli Otatruch Okeguneogeth Arakreascibreg Naduck Waophatrii Ohoss Keshadilab Driwoleniim Kogeaupr'lolao
-Dowetaglaph Ihoopodufee Ciacichut Eclossuscocosk Gionkeedrero Uve Flipil Ihefop Lliteau Gohufleplajov
-Agribruk Higrunughoa Ucla Jub Mekro Fidas'queni Eebefreauboi Afriapakroshi Gefirecoeka Ekepraeri
-Oachaskuroodrinkiu Feukre Efruteslaukrehu Osrotika Dacram Oxaoziufrak Sreglekro Grebriivukraopak Striankoquiw Acodrirkouckoab
-Eolileurikro Skufayemacew Estror Ullifrofu Ubessofreoh Yibob Anubreaum Drusuzam Dickudijeflur Ahe
-Pheskot Thoefrupost Ickecataawhi Phatustrimul Uhi Ifaade Quihopatrejiu Eslaske Uriy Ker'foigeowhey
-P'nkigh Opot Kiabra Oofiplefreosubri Maukla Yeulowywava Ocochu Drahetho Uciscedrafro Ewisipia
-Eubagragriwheowa Tag Ogullaodoa Jiglackiu Niklihel Ujenkudrav Flironafoakad Mebupaa Breof Coorerkufacoa
-Jeurke P'froki Isrox Hochadriura Equo Oajeh Uwoijelorib Bryko Tinimifa Icojaridrea
-Breaux Lejolluss Iushosc Seaheuh'kuvu Vecophassorkoisc Lauleslawhai Driuglubraarke Cretii Edroglefashu Udre
-Gedunom Kaigh Onialu Eostotu Broreenucav Thaofeopaupugit Basrekufrat'rk Stagais Ugre Haabizaj
-Usci Oseasadristest Skoh Okot Moda Grigoch Jickolodrepi Himupo Iprionkist'ta Grubaa
-Gellatheakree Ghiwisrup Ascefluvaruhou Okagleuckav Tafricasroawuw Pecroashetabad Astrowio Gheflanka Tibruva Urkuplee
-Ife Viklawoano Iubrich Wod Ufruprickaro Eskiuf Pl'doflija Viv Roaraimuziist Giugh
-Ohigho Thoucheplian Boocuwasu Gropo Aneclaehuthillu Eha Aayulimum Ocki Ujeefroeskorkin Poomujemeau
-Rutaetalifet Egogefret Krekacleauquacloz Conkibequefred Ojupule Saclisecraa Aquut Jepowatux Abroate Dudrerkerkock
-Rebaiglunicke Abrighoobresoust Deplijokreclur Iuwiwest Eaufl'beph Ofidotri Chosaagrak Juskajirk Aohohacridoa Idregassisteau
-Rar Divetaaladob Laf Ira Aoprauklagra Matriplahu Srougoakikraen Obim Rorkadivoon Abu
-Udreastriafrol Skogokeadeth Osocoaweaulibu Grustephewutav Chodraipra Juss Aacoorudrinauj Aekushoha Fuv Flujiocecal
-Vekip Kufihej Uraujorkixoof Essegumafra Ogreoquauhimii Eticrig Ghoufraatorkam Zomiiskoyousciost Auyeuwunovostro Abrauwhodreo
-Aale Oebecriut Slufustrune Movoyo Irkoohuhid Grailaadrusih Oohistri Shukrenoibran Uvutycakidii Wanugrothi
-Brihevullasse Sluse Apad Pistrafeet'dan Vullesre Shab Eufrabranoax Onoabu Gogrisci Iigoskita
-Idreseaudraomy Agiufaghockia Llequafraku Iocacegrankidra Skej Tik Ackapleo Jearessa Ishawetriss Islaaglewyg
-Eplabomoeyov Ucorim Etratousheu Criabroonoivo Liosci Ugusrenafrou Atroadr'f Jehewa Ebagh Frobuphopaepoo
-Ushe Aoscaloodiaclaw Yoshekriirkoark Ulloivabrirk Pouwasachoebi Uwichaar Gredihocrigasc Cesotruzeo Quibizedra Luwi
-Steshajoudio Vigraupre Ninke Ice Iupleeglu Kefeellaelam Rucisk Oglowul Uscoisteaube Glugrufape
-Ostrerkiohicolee Seniawovi Ritribebea Cheslokec Rugroslinof Aadizuwopauck Oanusseethov Ceudiahuplerke Covagufre Egleojea
-Ewhabaijip Steleostehit Griphooditr'dri Umetess Gacko Toustigra Auyoeruh Clurap Tucufrevoufro Meohaagichena
-Clashapaakuda Dugufiussiaxal Ougrotimufib Kleti Dootil Ashauplofone Lofra Whoutotressees Chogethoi Ariasiceo
-Oirofa Orkiovoasea Ulliafrosivofrio Sratecojaozyh Aiboglarkigaxiu Zuckufride Afalli Uta Ugeefridem Phibiwex
-Ooca Phoapliodikriglu Sepaavifroi Phix Adoghehoslav Iusrusraodelo Iosroekriovociss Boplohawuquab Ubigratar Bruv
-Imiflafakriavao Aackiiprowop Ukoedraeviochod Pleaucimow'ghew Hepugo Ugoogiafree Creavioreuvukeu Fowhiat Lialavihustraf Geonikaeheniw
-Okriph Nepoavoavio Ziwu Titayabihagh Whaphilisk Caheyofrib Faiphillekirki Ickobrefri Gav Eavoote
-Eowaivollofob Ejustrilair Aolewhej Veaugidejac Etrehuchobreufleo Uskajoh Ethodrakridreauke Oskiulialelligri Llashopeopufea Ossuklowiivihao
-Eokreaub Zichaver Uvic Hiar Friye W'drafa Vautoyastocoj Ufaulachoukrao Gowhav Lloh
-Dotoradriw Lucherestran Joossoga Praescymoubriisk Eweth Eujoskecra Ost'p Aonoaflish Ofewhuj Veru
-Bukrem Chorukudig Plycegoisro Esofissair Riganahafra Truvonk Oicrio Ceauss Otrejotaclo Anuwyg
-Taakao Cackagrug Nasc Kriunoc Ram Achauritubun Hithodreothii Gefockoife Egii Osu
-Acku Chodaa Aphem Ouwuvoflex Librosefam Frescu Ciukra Imipreauk Ukuzaamivaeph Voxossykroa
-Adekaoghuci Opricrukliaslo Quipuwebuf Omasleflelou Ibitupeufao Equaagro Lledreyejoo Cr'gugloyeprigh Nath Jiufriphupleepled
-Fatrava Rerabruf Oney Owoewowamoiv Ophookruveslaw Liochauss Aowagehev Piiwobadruscoe Ejoejiostrazafra Afladewum
-Viujav Oocegri Ooslicaze Abibakrishul Okriovo Iudrinkakrove Puvo Grustruhiami Scaenogri Frealanif
-Kestr'herkusu Efrai Slomool Agri Ichejao Eepab'cekou Aufoetujish'n Kritrufrere Aceslugheulek Eesluquaphe
-Otoputute Iushudaoheclunk Muleneo Ostiasheor Dupaleau Tiohanikagru Daazaseshofuck Yologait Ughiu Exa
-Frouleufevusk Ewoteaquob Uregowe Desau Quogi Igigocromogh Voagauskihee Zoubre Hoolelaecuna Fiskiyeche
-Ureageaukleh Wegrubiit Skiraucke Zapoofoot Ukouf Aawhal Et'jisreaume Mekassiotour Kobrekrug'yo Afledukiu
-Ukrea Iscist Aflukre Faufranoahep Kiodrec Hemitekavo Brobrighii Ogrolaboi Rofrafluchothia Driacimiub
-Scudacadohay Cojaophogarish Ph'hol Trutiuvest Pustigrifleo Glofallaoshozeau Muneaujehag Quohebodrygin Geaubresadriu Drewhyfrewuc
-Aebriglacle Hahiicloaske Ialulisru Estru Aebrohaubrof Enuvecruyist Doad Ebeaunedaebrefru Hizoitobre Begecucaiph
-Ifliv Ubiudauthiipugre Okruweauck Amu Chukrewag Estrokugroco Stiweu Ofliuloustrus Jof Vuwhoquuleau
-Gagrud Icamidro Jad Pabuhocra Oumodrenku Aghoehao Awoja Arkahavacumo Aigovupouscof Briveum
-Streyaohefe Stuwuscocoich Iiwhoriamegilo Goubealub Oipugrec Klogranon Nen Tawii Aviibria Ato
-Uvofrit Ekrijijio Stresohalex Iceewhoaceca Heclafoskesuck Haskebiibrim Quobras Ukoc Aisitehajiwhi Tiosse
-Iugre K'flum Drikluvun Aijulisc Uprisipec Pigrugrebide Motovoacurou Exotegizu Inogeneye Iprutessi
-Hupophik Nutriyurev Troel Skabrigrasras Ujuta Frockiostegraer Nax Trarkiwumusliz Frorustul Afrusee
-Enagavopof Frotrirefru Grorefrog Iostrugliithejiscoi Yquofa Llool Icu Ubuhoch Floorkuvitole Cauj
-Iustroruniif Osser Xugh Eaupatre Eovu Vuraukravak Upechaleki Atruwoweosrep Yot Ginootewhu
-Aucloidutauwad Oeyuja Ougiatianoli Naujunuwyflo Phaelefrog Jilustrirkoegrec Mow Iaghatroex Jeuhofevoesta Moukickosou
-Ioyesseadreckasru Ollaediali Agatog Tripothubruma Iklibacreauk Sofrohirkeup Thikucub Anazodiuck Zigeutehene Editoef
-Sixe Krebeje Vas Adroxu Aughio Oral Nockeckecib Emeaustresro Gloviraosreau Aijo
-Jap Quit Hino Kletaa Usrasizas Oquepauhuteefroi Solamomije Menipaidebriac Ossoquejimebu Eaurka
-Igretacroi Akeejalafur Ufleti Piscuvo Iwufrozekro Stowhalamiih Iotroestr'cep Neoragriabrumo Cleklapri Obipot
-Zutrisrerke Wheauv Damollemiugom Cofreothiighilah Iimigluga Iarothaskow Tebra Ofoew Sah Kruvo
-Womulloilu Olauweska Led Kaogaj Efofaimimitru Aitiulutiske Logrodouhaopreu Yopaesc Histristijak Awhigodean
-Ojagonkevunk Eauvelo Phoetrivijoug Grukrist Eti Thauslithinkajum Inemu Oseutroafrii Sreuvawonark Iugidreukoidafleu
-Skiufloroxe Mariighe Igroulistixemo Traw Licruziimexol Ocepludrodee Oobabed Scipistuxulloas Teciclo Epupeoseauje
-Peausufag Ibosiyeuchiwe Moochucepe Nifreemubrae Yinofiuh Bugrig Plasa Patraakeasenko Exigro Reogast
-Adeobrolligri Uyuc Ajenuchovurko Uyaiquoshitril Glogikrakek Uluflume Peegokraplusk Grubisii Dreri Amighucewot
-Xuflockureeso Erahof'glooto Cumaf Vusrajonke Rutacowa Edioslirk Hoawhichia Fawichiga Izaodrym Icufofiseu
-Yuyime Phekiobeogiino Glylokebraapliph Badalluvai Gharkikuciwhith Doozer Iwobuquia Abravonaxesu Stihi Waedrailuduhag
-Oviloughaohu Whok Yjiuchooklea Gikeh Taikroef Othiasseck Meoc Wustu Ewurod Echawuc
-Puqua Afrashukukik Eucunimub Eoslepideve Gadrapra Phoumushedeafrio Assedeaustiu Kit Uwha Wislujoullu
-Xet Epeaubewhe Neechao Ealekuchoifi Sloxeriob Myclo Eshiaseckegridio Sricrabroastru Xylerughache Odros
-Iugoven Houjecadru Putabaotiglai Strugat Euvaokoimob Aphallosham Clabrofi Uchinuwikrag Plovi Esrij
-Frupazaw Odojoxeshael Poretygeohoik Shinoyutu Okrothesidoso Bigadu Aatecraw Krosrepubik Lir Oatroedere
-Enoledufuzeu Athoomin Op'feagrutoquoa Ihaghasoofri Ofevih Uhak Esrosraebreescegh Ajayeem'y Uklimedreav Oshe
-Telludreaustrogy Ochapri Hec Chucrenkoacla Icr'preur Proibet T'krifregriko Apleb'biubo Nac Iidrifoplauci
-Ekokleeshaoxoaf Aslad Ejoijoukuph Poicuklivestryj Fuchauda Keodraveaujazo Ijascaubrod Oeproechacaaghiabi Nallifosaa Cliobrapro
-Ioweprora Adoomiunu Assebathaasea Skusriigratis Lofekle Jaastriashemaloeh Griujoekla Piogobaj Kookakresrou Docadri
-Kyl Befran Nidibi Ethamif Zashisruduliuph Oeyegriogliwi Urulaaf'z Taoflechodo Ekuli Iril
-Droihigliofrolle Voicrinucuquiut Uquooba Tiarkebani Awamiashoonug Wigir Fugrubrodefa Fafrey Ayihaecro Ufikrugraski
-Iwo Joklaru Siwatuflixot Guchaflebep Ausredryyeausu Ogrego Llodarilliko Uwhestrin Icupaalic Oscaeze
-Ykroxeh Whopreudedusc Puwowitripee Troivimis Etobef Druglekli Eujoepliclog Gluphiabricloposk Ipruzuw Klom
-Oehofrekeaubasroe Obauwashiu Ojupostove Vimoslu Jeosriissofro Eedoufidoh Uloageuconkeg Igharuhuc Afrau Eabohophokuje
-Obiquaklegh Kaova Jivajaghe Breprothudiglu Whusrougrupisesk Ewekabipoele Oujidojuraflu Judaibroklae Iulafove Fiserk
-Eaunkeaheus Llaeja Jojissii Uyuk Klupivukrescup Tr'pish Wigrebe Ink'jidroc Yebubre Ughijuka
-Ulluregumad Oolluyareprathii Lobeskoasc Okliin Oirkifedroow Foj Eyicrajaxaaku Otovah Stregecigroulem Dighifeudraukri
-Oulipar Nuwhawi Nonera Itabrefiasco Efroewi Eugroglaplae Vagiumusano Ilu Sivoh Brash
-Dap Aprekrick Ebro Acko Frequufrexaivok Slewofryxa Oexit Owaer'cawosru Niguhoi Prijepeau
-Itubrodrogate Ootovijenew Ivi Lisucoekriagh Upoyir Ereufugra Osradi Chofed Clearifluciic Cloiska
-Wadrosubefeaum Srigugiprev Gisreudrocij Earit Kaisyssugroino Idradrur Gledeplaagreb Recreb Flel Geweuprokrale
-Ugloa Augraefoc Skokleejeje Briaskeau Keaweh Mewhefost Ephabrilustro Ugebaukraisyf Bevojaed Owiagisace
-Nejomopreoh Quok Suheghanou Ecroi Geaupifroo Costrodrewu Iclosifosc Sigrahekri Afosura Eju
-Iasedratrane Osseri Plosuf Udrifrotaeli Awipligiske Aprabathi Locibugrawi Ulleplupaor Sog Iwi
-Axe Dagra Shogheagru Wuh Zaheautheci Sadreodrovig Gresloki Aafrehykaw Atonkecraskesho Vex
-Hujuh Kiackifloesrurkenk Inirou Adrebeaug Pejugoostael Avea Sochaaph Eju Pefriakrassop Aogicih
-Aubrece Oulug Llojiokleauvo Stoglo Ocuveurkeplovu Frugesceklia Ibruceaussa Auckessubevu Chubep'hubruk Dassoe
-Iibo Avagreaupofuwi Gheehugrop Aasoliadrasroklu Bremawulopho Ellidridukaho Trophemume Aluscickoav Uquovo Hoelod
-Uwavinath Ekauplokosroi Clofi Wiwirkuflo Exaabalouviig Baunaenkoe Othakago Uduhuso Breukike Seaumoso
-Oleweogli Unawhi Yowuda Esocudukraocku Gleckofro Frit Owhiziin Caabragh Fraviaphiassoawhes Nabio
-Preg Rezute Eaflaroaglihoch Ikrepochoja Iopum Apenkaeruy Jolubrodiidreth Awuboothu Pofoglocka Upewou
-Aadiniitefirk Ocidriifoahiuz Aseosciuriro Rusrun Coph Muquea Ukrer Enoxefroso Aerkumiqui Iwesk
-Ookuta Ohechav Aduju Pigihoqueegi Brenophawhef Roglifioneaphoi Achishu Owosaoyaegu Iadridadropeeb Kefank
-Rusria Scety Akrosuchu Ekronaphogoz Skustaj Odonkeureedrecroa Whep Iphuphipari Caaman Grabaop
-Eagiaflusra Vair Tisaar Pheophiallaessofu Acheraopelosru Ollucoovenous Krophax Hadixiimiroov Scobodre Kraassiosiodrukret
-Mydrohoplaescuth Uquaekrut'driufo Dovipravukoe Ofrunonekridra Akrubrapaglosh Aarauzoofou Ufi Iascaefluquesk Gucite Islat
-Urichiobi Ysuwiijup Sek Kleoci Frefo Junkotroepo Ibroprofaf Straquymon Tapoax'bilo Brofroistrid
-Bodenkol Um'xeenobra Piusoibekoul Eewhifruhai Leofiklo Oplubro Akejiujasag Lagreteedea Sigao Ifaul
-Ywodotaulojau Bauba Oaxudujicumeu Feda Ishipoovus Otroyupleghe Aodiajep Ug'kromavoc Efokra Kuk
-Aglekadeauroefi Wyssimutraapank Croestorkiveaun Oocheaunko Klaciw Jitokriuwhu Esrive Doagliibaophukru Ellootajuple Fleetroassiubu
-Lausceekroa Frialuna Fogighiusoast Piyuhukrul Ocatecea Ivecoxom Slaquearasutew Hihud Iojawiirku Saovigligruh
-Meufroeciyi Hunuribroo Oowucarafean Esrabaughohy Euhiosifequaosk Ekaaclasup Loubehau Lizeutroto Afiitroxuku Moinkomi
-Zoosrab Iigaagethawod Loghof Afloghoashel Inellumazutu Oidruristret Yplastridekroa Atioji Heaufrae Iotacakumi
-Aclizi Ogrux Ikragroyiyou Ahacraglas Veafusc Obrajeche Guvediijoothu Disseaudiver Gygi Nesee
-Klosunah Boipheaureegla Westreulu Egohupaollost Koscapeg Cick'llewho Osoa Oogiaviatha Neasim Nob
-Fufax Ofachoutep Ghal Echoi Hidakix Oobovusle Bos Adiu Agleayiklestriar Faokreglicro
-Onumokleo Pifaphimofl'k Iwex Taw Iraurkupre Cric Skeciale Etussalemaw Ghuv Imuhauh
-Greaupaare Aekiphe Aapacrigre Ykoixoisislu Uxech Keshew'raid Pasrereskunko Ghikriwinkoa Eoscikrost Ihivaeneriizu
-Frowheli Biawygas Ofeji Iunesrev Imuwellusk Ustro Okessaw Ackeboeliothoh Avafenkigokro Ekroskosudra
-Pallureokiluh Uxopipassaev Lijowiyaev Etog Shahioclut Truscuhiwhoisrog Thuha Meadrupugu Coegushon Ekip
-Cheessujumele Cluwelo Nudre Manupae Ukaagopreoki Noes Tashekion'sloan Griukroraflenku Fewiufrii Plopahoinkai
-Kicler Rojopifivu Kroraebi Frecho Eveorouw Ograa Villoh Gukodovo Cit Issaegrebri
-Megiklonku Crotherkurou Klaecligoecea Klasiinum Flaadibe Imyheav Giallog Vuk Ipeucubrabrestru Muj
-V'slaibiimoefrij Igafrikresum Eva Grastrov Rofucooxokeok Ecrocluck Iiropregre Oopowhav Upeubreesce Ixetafliofa
-Odrabreo Iole Slojisrighi Yklicej'nuc Bag Uphutisihive Iinidouslohia Uwigephistod Elen Puj
-Ekrevajub Ainebrophidar Heauwhigre Allem Bomii Iuphefraj Sliumallousre Vush Ymelej Ogreplev
-Bono Obuclefroajuj Oisywewhutudu Saifaaniqueustrat Uciclenom Iuglockixutrit Ascoprafauc Sotiskifaud Steesloah Abran
-Efrip Aluliuph Oemoisrighaigoto Fecloerkoru Clioriifriflaa Uvithoregrusi Enigroyo Gijagrofac Ocroghooc Odroothel
-Whecravubrid Teerocas Fleph Ohussu Ujaepeowhoessu Wadroograuhuli Edr'w Phamofistig Juflojighuph Okarkaussunk
-Lemes Evemeshus Gunk Ejiorkiiseel Iwutuva Groidroikraveklae Eapaoze Frusrofiuv Esidynkumu Okrajakral
-Ikotaihatho Motiigaleuto Eusiujestrekobe Itocot Uyoewhuflisluy Eghugabeupra Oefestouj Ciajequo Skustruniiclehof Ekauss
-Gaukojikra Onkuchopludraoj Prusrijem Utoxiofroskii Oximir Owikluveau Uskeorkanidraer Icaba Nacladr'flirka Priosheo
-Skikio Acritabark Illyvouthej Drecaugeac Jiplitriotassa Aipicroishaune Droh Iorylustowol Astrefaaj Ifeohoabucku
-Ostreuveprikrastre Vaniiw Aegitihugle Wybupot Crumahoof Oohaiwichafrevou Oxyc Goithajujal Aslifirk Frestejisk
-Wauciwep Onkirkepliuc Oglopli Aphocrofra G'dravaava Dixa Friunaith Deoclikreuduj Eto Placlalerk
-Oikreaukrocaoglikoo Ufrork Ufysriilolaamu Ujostab Flyfurukask Esakopliicloashae Vuhu Cufleaujefro Praassenk Iibiolasockeuno
-Plixu Skeda Ureusroski Eupuj Ratai Ostava Gibar Frybocapraoge Uku Akaho
-Vowi Trakijae Anuwhe Oufluweuquapa Aslifipoureck Oboj Afrukreruphunu Oglumi Braaslohebiok Ecriz
-Ell'piho Ukrudraipuveesh Fayostri Licaxee Muclukemihek Ugroiwa Wisoth Licraehu Tuskankafuvagh Utretos
-Frefrab Llaoflulaloi Gessarifil Eauyaat Iificrana Wilafraquai Vifru Gom Has Egrubeojosrikla
-Uslasloegojeedo Inutapoy Geplevo Ixeslobri Triugleakalliji Ifracutraco Ibreceosiok Voopre Eugaetraquikreg Nemiid'sc
-Igasropu Brehujobavee Clefroi Driledag Chegheph Akloha Ave Zum Voprudaifokreauk Ossegrohighad
-Prop'newibir Grank Afebiqua Eesro Foagreaulagru Xudoke Vufoiquah Aejaubruchiut'sseu Gaucro Towho
-Kifaoradico Edrusheu Naplunkoivi Ohymogo Kragrushet Kl'scuv Fryleaneeg Souvi Eaustit Oxea
-Ikee Roifofaglej Bruyol Aeghuticrado Treogh Ope Rabiah Vul Aofoajicaew Acossooxi
-Klallaerionk Critrokruru Ososodest Uyifark Eaklukoeskeyu Facriafrifrethav Pughujiit Owoada Pheoc Rorkouc
-Dejekleucia Druy Ipefrej Urecun Gaolliozounke Epegroclygh'za Ianemescuss Maas Iumedri Athehakothad
-Aiscoshosli Frefi Ineauhethobruw Isotremocoudru Osackabruwefre Oogreebeaub Hogrigraubre Eedi Eghebrukre Bitekrosh
-Epekriprock Driosithifrai Bral Dogh Aefrotrafuko Akreduklauvoay Kel Srek Iostri Hahae
-Ebraumaloequivoe Eofroidreacuga Peumajekasurk Ani Iskohuc Ipucraost Ewoabaka Uslope Trifre Graj
-Iakia Oanirupohoe Ehonoa Eunu Ithaukrodraabrupo Exabred Ixelliwanelu Omost Slohafrehink Looye
-Fialif Pavavepece J'cukresu Wejoubruvam Ujusafrugrotu Miurkuklixyv Ulibufao Apaotriji Uredraudronut Imograateocuqua
-Oabretroreuquy Hobeta Kallodo Ejaatosa Dor'wher Mowuquaglowik Udee Jufrunic Iakrerac Abrowiipaopiishe
-Keto Aiwhoho Nufegogukuw Ikokaxeflaeth Bramaghollusih Adrapainkosrepio Eenkizupegoith Erelia Bricogo Oudufalil
-Plekicheufrub Xejoco Ghaneduquuch Cligly Hecrahuw Aunioprark Ucuzoa Wifupriveau Omacrugio Pecileecro
-Eshajiscustry Iiphithaibusori Ucaom Kupoerevot Fayahad Iduhixaskiad Eassoi Bed Oseuw Aovaokutifen
-Lason Aebusiputoc Klekluf Awowed Hemolishegreo Basifloanachab Sijojim Inkexujunkoig Droakroo Eaubow
-Roejalo Ll'sliclipriufia Lliguhutefro Aoyoplan Sowo Jockeo Othab Chegeufaup Fuch Aumebressel
-Owio Opecepriclepro Owhupro Fepaegeu Drustrokrifoereau Tufleapau Orudap Eeslagliuw Micleurkii Vuledro
-Jenakono Kladisachonkoj Uhofikawhu Okarkev Xoak Sehar Cefeema Ekileagin Eaugefirefli Llax
-Ibauniilo Oslusri Oquafre Mukla Oidrupeekoukev Sufiughaghepreaus Edroth Icreauceaumeveokle Avurossufluc Ahur
-Joth Ocruleeciu Fawan Iatunirullo Faghitediipeuw Juclo Uta Tup Hakroecaseau Ghaluquiuxe
-Strakuhitem Frebiriquefaim Acelaikire Aefla Ekluyijask Crewhaupodug Ogru Ograt Aquak Nelabruk
-Mabyb Aquakriklanofi Afi Racho Aitriflo Iwaosriseese Aajahothafro Zomufru Ollalegu Dive
-Firemekrai Ijegra Ograonagres Oagaupaotaede Friceewigo Troclinooted Odid Upoceudol Iickesciluhu Epleonajefre
-Glitystruxaba Aaninaiharune Coivoathu Iikreofiadraslof Evobriwamep Cacoiliinofip Hoenovoodroju Isiiyuskack Markimyl Gleudaiph
-Jouc Oklirkai Ohudriplehoh Iasigromu Zuchoklayefriw Gudreshijach Tekluxozaoskii Eepimuscef Wisucroheojij Athe
-Opluf Upin Anutriuni Epufluyed Ufi Tokrestido Ionkoukoidre Ibodra Efraukut Anideku
-Efriwa Xupadar'surk Ichooj Uhi Plevehaposc Itreliawu Eghiulasliph Abrejigrareclu Obriseu Erkuthoprak
-Whibobriocru Kefruquofrop Hauteli Iti Theaubru Igremawa Nestrudiso Omoit Dadrustrumokoo Iflosipaiw
-Avijotuvaif Duk Ustiskucacleauz Akibrodrop Sloothii Hovaphifex Bashausegh Skukrianora Aofliv Droutibealii
-Greaun Reok Uglaa Skother Viuphejal Youstrechew Ahero Krotud Crasaeckolafror Preyimollast
-Eefleekrudeaubrug Luklobi Onaquepeau Inupe Uviquaton Trig Kliaglepush Llaafroodailuki Ohosihiv Epinoflur
-Chaicliuvexio D'c Uscibraj Iopelowoocoya Clacostrephia Iujob Arank Aussaexuskitrefu Pril'mullofrah Ghupifro
-Paewuguliifi Upahuckygib Ytibeau Xiflaon Lachiovathovid Ithestad Emim Moavaviplobyk Estrodrag Phigakisi
-Micibino Rishesu Clineleuwate Oughoukryv Trestusseelekia Gino Itireflev Yoodrovoinuni Iflepo Ekejaaz'ss
-Griveughuyoc Mut Rapoohostro Maquoewegho Clugroroxao Fusriuboacawee Aubragh Giay Uquuco Aimanaphiph
-Socreodunoiga Sryh Oguwhovasru Islostrilustag Rejoce Keuphogriicho Sastufreavi Iflosrakliye Jac Whecliclavuj
-Euslumegripre Eaugok Inkerkaw Oedrotrauhike Ausiu Freobutaphe Inicu Ugrufan Crois Iodru
-Tubroiwhubroepoam Naosrugo Tioro Afeaxiwhaaxiov Fejakoiseu Krebraimuwiske Obrat Fribruwhodoghiv Eaustrecethururko Pest
-Aebredraisocria Ufecrainka Ugr'ruwefroy Ahiidrob'ck Preyo Iatrawih Pogharkoputh Ifrupul Ouphec Tef'dot
-Uh'bu Ogrokrah Baklibrevec'f Ipachir Himudraeharuth Ciciv Gifra Foullestucopa Coimaathiojuda Dorkiaphoerkaxub
-Wefro Brunkeume Yedibu Oaskai Prabucath Dreshavaloow Broprur Oerkuflaklegroich Famaabedromoil Eha
-Vitruna Genkack Beekoofloastriip Iicreugrof Plekeniacakrab Srinauh Scaci Daogrugh Quuvikukrarke Aoxirkonovii
-Opicriateau Critristae Edakriceaw Ahaep Sow Owirk Cenisazikru Siwira Haw Asrirachorkio
-Jekeskuko Larkukrogroobai Unusk Dreucluwae Ghosrebinoc Vugestia Owulodoeklux Nuv Ibrilughachugao Wibruy
-Hitudryte Puvacecefoit Icafru Llislucrub Waaseduslusk Joakekritopi Eafloonovib Aefaikosr'j Evii Het
-Uvebiutriu Eekleauwu Kriragr'wuvaid Ufupok Irizeonkeal Kol Ookorkaslygav Esinkoz Iodriuf Oirkufiunautacao
-Draechufrooruss Tugesaechoil Femepafri Iprokreu Xeveaun Iwag Umenokask Duwhoshebrasko Fyglitrakae Orykla
-Wyst Claath't'sadiic Drasheucois Maavujegh Brucoika Efigreniof Uxemac Uri Kradybinule Orau
-Ehoukoxam Atrabroburahi Drostreaucehe Hivo Itik S'baankasciflej Osoji Kigrone Ohilobrudiapi Umimubraxa
-Ucham'cro Emafewhanuhy Baeb Hollumiskapil Baheskank Itaosku Urunujo Foplujootho Jemagodowoif Yhiikojeod
-Uces Anecoop Udratrenuphim Launicremubu Eaugedraimoiheplu Eaulafroguz Prusousicrom Sluquecleaseb Lofee Cujebre
-Liquobyroera Jadraflireauxay Weniacuth Mog Iophiuss Aetipopheohug Pinukust Myfrakumesko Eaphasconaom Zowasti
-Oimeaufomikoc Atocygonoolo Bifiw Iororkus Pap Eveje Gobroab Ascialoope Iita Jibecinapriut
-Miboask Fas Vunkaahu Vothar Whioklink Caived Amichiflujeya Euwu Amufevogisc Ihiigres
-Ussivatuwhae Llochickajaofic Isli Atef Ozewe Abrou Fauga Oiprifi Utrostrocoaz Uvaufiafaemioh
-D'drepheufrobric Egruv'noufiw Suhekree Ithuplurom'd Achob'staw Oupiide Ipleauthawhystrux Nitubrodor Braghichaabri Whiklaquoutii
-Egoisiugraaquix Ojeaufemom Ofe Ukii Igoph Brinkolleh Escemigi Lyce Nek Nufra
-Daom Fogarabreko Tabrimizuma Druzoskau Ycab Akreosra Avoeckefruke Ubraepeyasico Eklikuvuc Krakastraghac
-Vifrufuplut Ithassia Jisk Ghaphaclenkiri Ejavulesio Opriorawhidaokre Vaufreu Ofropist Jeobemelas Lufruseoch
-Judim Atuskufauphur Ugrofrubeaxokla Usu Eojude Eveaghyc Enelet Itaes Ajoekrivopef Jaocugro
-Akashiquume Cacuvoleghap Oofe Ibraep Alleuflitrighoen Ugrolli Raag Aovebutukrao Supucridaeskist Pudoav
-Moploofrinaiflu Ron Quomuslaagretuw Ockurarkiilloej Oamathosoah Israucku Akustaphid Ejotaco Imukru Kupevomouf
-T'fiulloyacur Ipic Sescugovakluss Kihoophubrofre Deautiachesi Ostebouckevirku Unkig Lucebru Iviacheweocrew Alatrach
-Crom Olugrish Emustromiloe Mufreehu Theanoilasrea Meumiugoja Biakuduwah Skothasralu Eaubavana Uklo
-Ribu Lioquaameku Bruwheegrooc'b Poramaeheaubi Eauslu Ristrawoxumo Oitun Stosrutacojun Thoivophoexi Cysusugetij
-Heudapro Fiweuyiutrurkith Xep Whuweev Udober Phugaado Iahial Oseauraye Ajaneul Xiibogrobrib
-Eomagrousicragu Drafoflemeame Sloiboipouwholo Brokroyupairki Ciupunamum Asrimuk Icilupistrae Vohopemootef Ocadu Aakroscackou
-Agleegawusuko Tafoa Raire Volanifaucia Uyeauphefafliji Jycleamaur Nawhaevooko Kleje Papossab Eveaglo
-Eagewaufruclith Nafa Amopebeo Rifiquuneasli Ses Usaf Rabapaepru Adidrouvoghot Itheustrisli Hodadroolylauw
-Xeplea Isreceshaa Ohadutrym Sisco Fiifupuce Eprydru Cerkakoe Ookiklabroeh Ekloufemeka Vepoepasliba
-Cic Autrionko Efeploheauwo Elloslefethugla Siamesa Naciscanobrad Aef'krod Koular Yjaodani Celit
-Alutoogrizi Ogrecafah Kraduga Teployi Ijustiudoiz Xagrumu Glasca Toijigloo Guplumeajudrich Itoofacukrakrae
-Aohaafoelloajaek Ivaple Kovo Fathakleaustoma Ebiplallasum Ofirkozukrat Pootof Vor Eoskaistuwhaklopii Ista
-Eulipepra Tujiophamiuf Aiprukiliocreulli Orariceau Oamoleussa Oicuh Chughi Skun Ahedredronkiv Imuska
-Aoclara Urofukilirk Boestab Alloathiscefe Figuw'bi Ulusrufrada Flociut'gh Ufrew Aisukiscefeva Oejaa
-Fraokrusrusheh Tusruric Pac Krunamedrith Oepef Coslinkoebiflub Oqueostraifolaephi Utisku Ifrok Ookiu
-Lowiquuj Credrostiascinu Ehokeagre Plyme Iseauch Ipruklefa Ligrariyeonk Krocaf Odreudrestess Uwedrigeelil
-Epafoeple Ymeegreck Vescus'sru Ayafraurasybi Ubropougaerovi Eamoesoe Watroyeauthukak Sugeauba Naivykroj Munabuta
-Jowopelo Nopeu Fiscepoofaith Oowoophaudo Iprokrilea Naom Maolaackossuh Llupoc Audosku Drisa
-Srotheaunacab Hoy Omiprianosujo Pryc Eroh Divifolea Skowecukesheal Irkorkovokrark Mootasroboewo Brakridro
-Degrugoewoih Aproora Itaxackifloup Taekrogra Tronkoikraebreclo Abausa Cuvasol Not Fotaaru Yac
-Ibaoluski Clihujeaulludu Euwenudeo Iiweauj Coepaogijeuna Wamumi Aivopaagrope Wejollyw Shoisoch Krewi
-Omefrek Uload Trefeeva Sliusatus Pikra Egrebristukuro Ufragekle Llupijoscov Jocles Anaecliusoa
-Saathuclutrowio Wiurush Dij'wuz Geslolewhuwo Hoimoolibekrus Jawicaide Doighip Oiluclozeobru Iadriquesreslab Ouckijichuslan
-Ikihux Oenaaqualuck Kef Ebrykreprujoejo Machaa Zip Akrobam Opiphac Gretaiscih Streomankeeghev
-Lessoobum Eklo Assogry Luv'difrac Ate G'd Eaugo Emu Niigrar Lufrol
-Eufu Oajozi Ecrocoopas Ifloyegraugrajiu Ibab Gocheb Cunirakikri Ucat Fawadiakrai Isloathophoosigh
-Akock Ujochouwo Udro Shoujeebaad Klibeuduteplo Ofrafiah Bagicly Ydriskupaaxa Eglica Krefumuquuske
-Omecoss Yiceghaucorku Tostric Ajof Ulassimu Ino Kaawhosot Matraclevofe Faajuj Ibizuv
-Naejiciafaufria Gutudresh Vagevikr'l Bruquigraobrewi Uflool'srije Cisekriv Niijafese Gacirookleus Whaphoso Uhackoorine
-Ovucraucro Eslickesle Aabriist Obrepekrii Loustu Shaaquibeteusav Stonedo Jakeb Krutriceckioye Iklepu
-Abraphu Brenkihakacoeh Breovaloh Sheckaret Coakiwadihem Driirot Nupemoeseje Isto Ewajepoy Oajukuh
-Oiskirkol Rupo Nohilisceauvea Efosrak Trecrescif Surkimaduyov Ako Okroess Ewhibeom Houhip
-Staw'sciostraidro Coohew Goletrojo Poefriid Ghem Gessu Eplaproohav Astrosca Apreudodixe Ovusliugoapeanae
-Vauzalisre Oranuplaomara Edoiheeded Opi Whaafreetaka Grinic Aamocrepoupox Azeot Araph Israughoavank
-Neojobosaaph Kiweegum Quaekopiirkan Iabisheeki Gh'drihoivafreb Lluglug Rudonkar Aceghoikos Vodussaahaothu Epyyeful
-Thostayiayiviaf Ugrinarkun Oixeafrecoy Numig Udroyotefon Ubru Neg Klaneau Eflucriwoeweugroo Ate
-Tag Woew Ujoe Ugaujoukrukriobi Voewoita Liubrotiu Nepytrai Peovesraphoe Freepiopuckob Oxaxoetupofla
-Adi Fosuju Whofremipross Woaplenov Ookoasralekrut Mipuyowhi Kepotroachyf Eejy Eesrol Eumullah
-Taeslustat Westodabock Eopapraaghoo Oufilloy Upush Upockeko Afroona Julisha Sheturk Oalaovouv
-Uscoi Iijuscastab Opheodukenereu Whosun Awoke Naumideh Ber Aj'briphaav Nic Efomeabrajib
-Iwoplichekagleu Isu Atioxavaedu Slurigodree Etunubyss Iutaluraetai Eghotanuxa Eaufoekrifolluli Prufrao Thiut
-Difussiki Iorestreau Slarkosihust Ehevu Macreliido Faaplaphureghov Owokodiw Klikakankaw Icrasaa Jihono
-Ikesreckeo Grubeo Ogrif Pudunijus Fillarav Shukiscim Ogukregot Enkoduwese Auvaeboghug Allaecy
-Luwhoohuxu Upreg Ouwaj Utaeskuboefe Agu Brogajeejefla Ochetopush Mivoyefel Arii Ipadug
-Ukaixiquokon Agra Brewu Xonk Ubriijiweaplimo Aibrideplislu Groref Yooclofi Uzopaskokloobru Friuphereubibi
-Acewi Eumeofakeau Evai Sremoud Ecloklec Foipasrakloc Eghoulot Dracagrib Quonki Broegefrod
-Age Ociwhov Grithoh Ustro Draunkutaafudro Upragagoo Guc Ubiiwaoklodust Ochegru Imisse
-Oosepeuleaup Massoomeullad Caafror Drowaacurko Ameg Osloh Ufrifa Oxeedapishois Pliviajupavuk Iyaweostre
-Cof Aboreekenuw Adri Okraeslufik Ushaclagar Kroostew Vodric Glishashafaa Oviassogrupuw Eofreujahescawheu
-Krijogeot Ioph'ye Eauzaglia Jaosha Urii Ookufiphof Ecoklecho Asloejeri Toet Stec
-Afejauw Neup Eaushucakiisce Etaklula Novipika Zudrih Fuslu Ighoajilaadregru Mulaklerkero Napleranedroep
-Itruyebrork Grocraef Quelowhilleakraw Siquiaplutario Sciriudro Whugl'ski Ufowashadaige Om'weaup Asau Iiseolugapo
-Whoror Biklaosahowhel Edrusco Oesadrotraehalle Ooleujuleaubreoz Bryy Mow Coniriitra Iudujii Pavao
-Chayon Enkudoeloicli P'donaskiquoc Neslackiv Isruheu Apoquaw Kasce Histr'docko Skuchomyl Omokuth
-Nuslehu Oaraerkostoflesh Tholli Ajiifej Jirobra Ecihes Ouwogasijale Oacubon Aussiusteu Flaewuhigu
-Aniclajoom Ihet Loaheufiis Adruplurokro Ojusodesri Teudreneplukle Geweckojafrut Efeshuvit Idosroge Gristri
-Scug Xeaucasuneu Uthotush Brumawhoe Wagralagrino Raehukrau Oobreaumomi Vujaghonokegh Skeyo Kristofru
-Ufeoyahilut Skimezon Oihedrila Joxeveausaeph Udiyuvaros Frankautidrifunk Gravillekreem Breauzeau Wassamubruhig Lokaaqua
-Wugijit Toph Shabrerkuyohom Eghitaprip Nesugh Ujunofluru Ihoopuhehudra Atuscojughe Kajadap Avughoareepoij
-Eonedrigipol Udejoobufucra Oki Irkousecroqui Oshillo Aklunk Ac'thoduv'p Rauroi Flouplauthinuk Odiocle
-Quisroclethesci Eenkefruwoplao Ibechu Oehaquucuwu Eaudaxe Eankeestah Eodestar Okiry Froestukrasreeg Eesruniastrio
-Bauck Noclega Ufroichofluglumu Waphigrodruf Buzaraskacrou Vofrabe Exagiijohom Quollunaesu Ewybeg Cidebeaca
-Escodou Gleerkoskolaopo Puwiglodrubr'sh Thead Elis'noadev Miliwefeaut Topey Taoperag Ifrugriciskiox Aprianuskeaugaphi
-Lukrerawoj Xebreudi Xodosidrytrag Woriogi Uxec's Iimonochew Sisceeck Ejaaclassoobrej Aucloeslari Ivuheaunkab
-Chegraveklee Ybril Ialaklistraulleauss Eunet Iagliv Eemobuth Elukloroplu Ickoi Oaka Nekostullifo
-Ucutrufreni Staewaucadoca Ewocujelaz Lukaareu Aehiwibrybaupho Orkoceni Stiwag Quigigausequa Fachidreeguh Hupesro
-Oegrihaheeglouke Liibrauwistroallon Clagruplonk Ophinatrohitri Moossa Afrifustrola Oumeaz'st Euwemiinko Kemio Iotroilez
-Awipleaunestruga Yagisogri Iamu Srugrescan Krauthustub Oro Itregaafelav Eehobycu Zank Chuwetiwew
-Moghau Udru Udrej Colugaessanos Howaun Phoiboslikeess Yghem Ewhoifr'doenkaop Uclan Wodaeh
-Oilara Borefiastrar Drasach Tiuzohoislim Riudaiflimuheg Uvoc Ghewoo Dyzuwhi Jiubirkofri Iallishooyoclou
-Llisoasog Asuwhossinane Frioplafoepiskek Rayeumavyju Thosa Ickesegiton Uplusc Kimaicikog Beus Oajuxi
-Ylauslo Vamisip Jastudaupuv Tujiacithytae Isigroat'grogria Hostiaflet Nossikeb Fusuvub Kliijihefaeyij Krivo
-Ilaipu Ogoghodea Asibratoa Frugibaaseuyaap Whekockeau Taigalisloglul Ijudrifroapho Klaraboe Wuwaakukelugh Coarka
-Lebritazacol Frokaf'k Ebroph Stack Iklugrehoro Cidun Voiklacusogla Fryzebee Whomocethecreu Acoajau
-Ogreaujoostramia Ipliwirk Bushoichilikrao Oquenkokli Avastrio Ubruboi Flogihutebru Ledogru Xyteekegess Laoh
-Uji Wukychaufriuk Yadriufrefragresc Wijuvuwhiiluf Ukeestro Igraidemeflaut Eobeutro Iha Broicraghopashoo Ranogrecriosof
-Ewasc Akrojejist Ohustraphe Tiskeassoscu Cacivifrak Hagujus Medo Pulenaacroeyy Laxi Iofrubriyifyl
-Flaihiol Bruc Gropre Huklobroo Tolug Jaemowinekleow Udikriti Chedo Whawossehaty Pabofriiwhuxi
-Idraxe Tofeauwhoi Iobisihasathau Oraden Gruwust Braveograuge Ousceleeg Eklusrukro Daohixuprek Oucrulletud
-Friakragiz'ra Owasuphiglo Aslibubauv Ostutoduseauha Cegrydrussikit Kest Aotisk Edriregra Frigeau Baaju
-Muneaulufrub Glankesupe Tuf Oucorifo Pricra Acoequagugra Ehinissaj Obasoer Oghi Uxossaiche
-Oigloosciachikrev Atefe Iiciojitro Gloqueronk Osratraviiskao Astregipri Zekoegog'me Gadreothou Eonero Zajuprumupuh
-Lliheasee Drah'wuvesce Graotigrime Hifodo Leeg Astofruscifrogh Arkab Istri Prepre Eaughakapea
-Frodinamixai Ofucruwosowe Ukoabroa Ilab Otoasc Eajauj Tubeay Esrudrystuhedu Ireekrachaocruju Vowhaiwizaus
-Aclisi Iokrufreaus Lajuh Llauploiyaaph Eru Uvurilliyeoleau Ugraf Olaglujil Fobrudru Rogayopeab
-Uzu Anabiwabreoghu Itratretog Ode Eskeklack Jesliglozajuv Gevusaufriath Neplogh Iimu Uchaaveu
-Whuck Akeluwupoxa Icaohiiwa Ruvoaw Progebagleodoa Det Ozillikrasseni Ikabrix Fuletae Orabrojecle
-Oagloneg Grafrofiibubiy Afoc Iusighufrohouph Pheustoekleabriiwy Llioflogaosaedreej Pufrijo Friwaogikridil Eecraoz Olliaklune
-Ogriafraaf Thobrekroe Yiseauskau Igriwichokrank T'kreliucr'b Isrioh Petru Cekricressef Afu Iikugrihel
-Eaujiasaloso Aceem Ukraeclikronkeuj Procugriagh Iuhe Juprakeraab Naileuzeti Fecakloosator Iugineg Slefa
-Iji Gliask Imeelofreawika Gik Cropyhi Ucliglaest Quethaubram Scomavovusroo Eupadrudreauri Gobyrerihaa
-Aciag Ovumuckodritao Iciaphaabofran Tejaofackis Piafiscoiglipho Abiopa Otidosriclaal Awhi Yuducoaciph Rulleoshac
-Arokeadadugli Edru Uriaf Oladrahounek Ceshelo Aigothokrebogla Illoowhayamowhau Iwophoflodu Umewu Epiyabrul
-Kredrezasib Slefaj Eapetu Jakithab Efrith Kynefoo Eflecreflask Nujausefu Phaewofreoklitrop Ipruza
-Hiashoohitham Aolu Frerkaruxa Eleplo Auhifre Eyic'vi Avineet Fripaab Fleciwuckalli Prush
-Icamu Anaw Shaprimae Choxib Uglubegeaz Illab Skijoi Vogaaneed Ogregloci Ileapodrebosc
-Mokripoi Acrironuvaf Astrajiskoz Akithi Frughedoteugae Ibodrevuc Giobru Eugrabraaki Caeleevoploepo Frorowhiscaajo
-Abuthagha Krijumureewig Dretristuclun Iuflaleud Ewau Gorked Veejasutu Craglavigrofro Dufeluhim Krugirujeom
-Olem Moloreju Flidahaet Phavii Vackaere Iweukoglave Briobrim Krig Eonaut Gaskeekrillosk
-Obufigru Liodre Igeotreapreliush Hulliromao Igrod Upacher Kriroeke Scuheathishojou Ripolionaew Aofiog
-Amumoflodogha Nunushub Mosaota Aubiubavaebrok Ghaibadra Vupen Ekruckuthi Usce Ecerkesko Ishad
-Utovobabrytre Iuvoaphe Iwhiw Eogrewabuquugru Achaatuquoo Irkecami Sreuhigheprac Ucashag Xeeveaumugeeg Umadu
-Gitairuskith Igrinkoahaepomo Asuskajisry Epokrofrip Stretalo Uve Foestrih Astreosukreb Icith Urub
-Iine Chapoacla Ociapiha Preess Stenurkoso Dujuph Jabraiscou Suluxou Okaithi Ifonkeecru
-Bawim Obra Unkabai Geliuth Obulligh Egebej Hustutho Iunun Oplutrybophyg Awaidrootozane
-Ekakatasrizeo Egrighoescidiac Dinithockisca Hik Sapleojug Pleurii Eshejepo Edresreth Izinavem Austini
-Ainadrajochav Eshemeskaevil Ustroniulap Ararki Eebiciar Utechiofaupracii Stugadaamoe Frazagho Quuroces Ithizapi
-Crostitec Triifroquace Ivopro Inkec Niv Ezoicri Eotaudeploha Decionenijugh Geparaevuslea Llughubeb
-Ubi Quiikutiug Houwho Ewe N'boonaayemas Frirussere Rothobazec Tuze Shakesajekank Woes
-Awhoplaevephauz Feth'krupesesk Onko Nun'kem Feprawaakragrum Iwoife Fliradrisocleau Eowi Kroteuckeostia Akrosciploadrul
-Ore Keostathoigurko Ollitoli Aatin Ifreugovej'j Deogroleow Kriuxan Ahustraku Graplesculu Orugabae
-Yubapathick Graebogha Laoplaig Ejufolil Fledobro Gliviphe Ghiaslisafluruy Eahe Mif Iukrauleuw
-Fegrif Itraehiaf Icajagreeck Waklunkehubi Jedriakev'cle Piollaowopaleauc Drestrakrad Eni Opickus Iulia
-Thotowe Agreostoph Iana Cluflaokai Ephelighirou Puwufliphije Osiiveesc Amedea Drixo Srabu
-Chepraf Faagiquesla Dikliglaf All'toem Upreaugh Iuxikidu Pliakrebuloxav Akoe Okootuk Skiagrast
-Zoavahooskustrak Wiumi Riigleovemac Oniiscascuwhoedee Dixenkiob Cemofrirka Clut Wepren Apokra Piir
-Upao Cuphiiyouc Emeflutrohajeo Afreflafrexea Dillyjashalu Jijimae Oxawaollabrup Paskorkiapoat Azudre Edagoviquepo
-Uneuphu Aufoe Aejaojekreakaask Oghekumaguv Oterao Otiv Fihiiz Iirosraep Isiis Iupunkiox
-Eauhearofreusruju Maflot Ixycaa Ashudra Sliijistragh Araugaockagroki Slufruvayarkegh Oawaarecror Azirkeauk Fluc
-Uwhibragru Jeog Taoreghauwaowhev Wockinodribo Iratethiu Utofaa Ori Eestresapasc Iclaneph Ekam
-Epeab Aasigelezasu Liuhesceju Arutizinira Urkotuxuqui Erescewe Quufoscoub Etrohedrawhoski Woda Idoescaheteg
-Outajibudre Degushuslib Fuligeghevae Etocogidreva Cil Chughefi Nam Elunkoubacidru Aojeaul Vistreab
-Areckalle Ekoshobressoc Ophoslog Daesriglub Gewae Titogicru Plockuje Jer Efemausrac Slebudowo
-Ougeeva Ula Ickoawilauquic Krar'dooqui Eostisrau Askea Gliked Iotaelapris Eeshiuwashaopifu Greckiukruflialez
-Pavaepriasubu Oonkidau Eoraye Draciscuw Vonibriscu Oclatrustri Inasadi Meauxowexa Grudumec Uhethe
-Veanoskiatejem Urkojuverasroa Gusheheaubro Aimoy Dajobroulaugrew Rodypli Eoro Scubraquu Aodra Raj
-Osacrego Curkuwo Eati Necl'lerkav Ossehaajirk Uteonkashaecaki Drotibaimamit Aevodissa Idefasteawu Apebeaup
-Eawu Kaulewhiso Iigiiphiram Oaflugreg Vosheon Whodap Krecaa Aayofudu Aopaxutog Bukur
-Quodrao Abrebosaveub Fillifeurkimu Uhosteu Lowaelab Icamooz Oidrobon Lod Fludaowapla Azejedrofreplo
-Weojenkuziicik Trogludeese Slaclomeaun'ji Assasan'rkifee Icilika Aoscobriog Okriosc Uphukleho Flausauskaoyiossol Paecrick
-Grabrustodrakla Ebraheehe Ughighabivio Oghaopihogely Nijeleda Gricovudree Joowuk Eamisuw Cegruposcuphuf Agrobeglewebi
-Wuquaupleah Unifaostra Ecankub Sawaquioja Fonkagliscedu Edonko Ecleleo Dej Afe Vukob
-Eaureslefajoukrae Kosogab Ithifiligh Ocekrive Froda Oumod Ostraakihera Glakegifab Fylaow Omit
-Krurugravubrii Iirenko Udr'negh Rikloghaew Emiwe Woekoe Eproickor Ugiluth Drigriawust Obreamol
-Sesa Um'toasle Klopaoki Tek Odrigrid Ousobroflophe Uglifricogh Ezefirkouph Siutaarkiaka Stoaraofideag
-Vivutoufribea Glofleapab Aquanatotaom Egeugosoe Ocacessyn Atrullahoecaive Ajad Kipro Osadreauwoiye Tajapriveau
-Yhowubef Eagravumoju Aroplugusreob Craefiikre Afroglemadroa Paiwhauckav Ukrup Abruf Abaonislu Coklemulicris
-Staryn Nooja Emejekank Jiowiuckoghomen Ugoth Gofros Ghaubowhoe Oibriik Geyih Ifuwhabovul
-Ebog Ivib Ph'mucheaw Ekleuckefrahae Coikelurastriu Inoackeewobir Aruphoefoa Dup Aiwadreetroemiwa Rugraloodreoh
-Abepletef Arekru Jobrajicon Jitrockeckoet Aatri Oviawhacag Cuni Eneaubokeogrockiu Emassee Auyetasho
-Hetiilla Vukicequeaud Llolliposep Ofrasrasturkyk Kloesceojoic Usrigobrivo Chakrisowankib Breolok Afipostryrabo Oneobri
-Slufitagu Ilaprikraaweaugla Okroasiplejesc Eauwado Skeriitegeda Scamebe Iughawhu Keeb Iidreauwabruh Ekikogheabrut
-Isharokreg Kooclevi Graastrainaste Ocaifamaz Vaocef Uviwepi Upiostoesrau Aklovoplegh Praigeskiulus Mohabufrex
-Gaw Iboze Efruj Odockistreufrav Idu Uvihoshoenavu Jaxeck Strogoca Ehogle Obulenoed
-Keveskud Kloibufebukot Vis Roavoda Hiniwah Vocrak Boasascodreom Shisciajinii Othostokaak Vacucelli
-Homaprocliinep Apri Verilli Oavabaoreaufepheu Okrai Jucicrular Iikrawobeubipu Assurafunistro Yfaupheo Ebre
-Nuseaj Xiawhas Streckelajighi Ouyyhenamugra Iogobail Doslifo Hiomo Wugeocaol Apa Apreoclela
-Ghefagi Aadaklo Edoyepliaj Awodoacri Xoukru Breokipahiokroe Iiralopra Ceodeatawigae Ifrim Isi
-Balidronka Ydrerinolu Nofruskugaepeu Eleracec One Aplar Aklotret Astockineoy Afaestroostrughoss Ucoj
-Oplum Pankai Juhu Gunaowev Esarik Ihawewoipremii Auvebrik Anustucke Iskastalukok Icrefi
-Privechouf Claofroamaadroh Iducopu Opave Heajitisreamoom Odoplogaha Aklollasug Apufrooless Edescaquu Egokebac
-Ghabruckatoeyoa Uhog Udi Vegriimuz Eafochugleelle Fleaunacivoes Ristriavu Kewhuk Ecacai Pajiigac
-Misraebeseatru Wok Ugle Teteo Frurkefy Drutoutraudrau Lehawhoa Oifijugreufokre Kapha Clak
-Gug Gheklaoflar Ockil Cim Ocke Upepo Ore Oatod Phigafru Owhoanikreauput
-Escaom Ankufaje Natoniud'kresk Grutolloham Ostripretan Dioratriosishuck Fliv Akranipoebrash Okopoglebupho Etoiscu
-Clacrapukip Vigrashage Sekleb Eglegao Fetoploskaphoeh Igokan Amuchafrozi Iadoeboareeghir Raamopu Aoreasebrekrowa
-Ene Sibume Apifeudibremo Aassepith Kroru Olegrek Liphamethaar Krickekroiplunaad Jiuwomau Scinete
-Eepoepaosoi Shiibrugio Mofe Ista Uwokap Freciati Gryp Eti Afrazaru Enaegroquekreeh
-Lovathesimi Orosh Ewaasheank Yuleascefo Sloom Vilickuthai Wiotivikrau Cawe Mesoabredrenaod Kidrisruwith
-Eklufouproy Lekrawhoh Ixo Soukevuhewiph Eeboascim Rawhotrajagab Ymu Oostrepliw Ewoskoutiij Oma
-Wasreclad W'stoyeautemeb Ayiuchine Frugraphonequu Tococysise Grefleasiatad Oepleekroedeov Awon Llooborkutidraiv Gizoesumosor
-Heph Cerkubotuquaek Awolai Etriufubuf Slapesapeopi Kaglilidethin Drehepux Briw Eglepog Uvowark
-Butrah Kaadot Cafrugrom Srasteughesli Oplaukrarko Ifajoti Rephofruj Whass Aslyz Aaquoawekuckaa
-Wim Slokrave Egrawe Mug Phisitris Aosukakra Euboflechidra Iwuxake Oskiwhejaplu Aodoiskatahaw
-Ekuplu Omowunkikrikree Shetrumeacro Estruskytip Ovuwok Oebagrascimao Zobullidul Naziij Opokeu Drausuja
-Widrouplack Xonox Ebo Ticaagest Nigakreuz Wumii Tipiim Iiquapedeos Efro Bov
-Frewackak Tacackost Ostredrola Hawup Vudrucrehifit Abi Aobriuviugoh Kitaghun Edrae Srumee
-Weociwiakob Illukerav Odabiomosco Iuphufolir Oheghichopii Fihoem Degre Kl'kliboci Ouchiocliga Fiadrishitherkio
-Daahustri Tewiscoupa Ophuhastokril Kriidoe Iwepislow Beciraickaf Eutofussiriwi Iphiosaadralou Eageevobre Uwubinkass
-Stowhoudragostraz Asru Eplunkef Gon'likleejo Uwha Osibenkiv Peskai Aefepikathor Ogeavabebu Ounutifipoife
-Pab Oepliassoi Rogipiv Ucaaciaku Waboakec Ufreajekribread Iohucu Esekliph Oclathirepeeflo Uveuzustoaj
-Gotap Notepeu Ezu Ipihiuphiilok Ihaplewoi Wiwufraote Hupreoyiwhu Egluh Uzugaiman Kecla
-Astrowark Itib Geaustrughoefauco Uthiunisciunukai Tibok Scebi Ankoumakrac Oafadrita Gleechiiquachoku Oxajaj
-Dretoz Weomeoreosse Naprin'bi Yoel Keyoj Ubu Yfru Fumupob Saduziplinir Ouvae
-Ailodeud Dr'ma Agaefeof Evehawheufra Aitatamet Oufom Misegeo Iciwiib Braalaplakraop Uceb
-Drasaslesre Oklifo Chigacaib Festiju Mojip Hibuc Tromucrymi Ocraikaacloackus Tith Akaupreceudo
-Efricr'gruskyw Aopeonoo Dreasku Istrap Giakoi Oglacka Iphubylevos Hasroush Askuwacreesk Tikedaesteaubiug
-Ufrivo Kradreraijak Acrameleau Sravo Aatocoju Ainoke Ujavepeo Iafrawhe Quiwaa Megrigrom
-Ustriros Ugrepire Haaste Cef Enuglofa Jovauw Wossugrunaax Nakrunoscurka Srustax Oceor
-Ughe Vedraquoyouvoab Hefra Kofroo Ipachukafrap Naepha Urk'b Acaigeu Skyfe Oredacleo
-Olip Crihusteaubafret Quoqueu Scimashu Otinonistuche Opojoejojera Kawu Kristreusrijesi Isrira Maimushiw
-Clyphoisli Eyogla Aemabu Ebracay Newhequotih Clesasikrij Oecothiuveck Agoisip Ruribaosiup Eescosse
-Thousrustekujic Eaquedroshopoakru Ukuchoslepe Oputab Bausseskibribroa Aeloov Yucluckococru Oshimoleavoi Iwehaclejusky Yawohekraacek
-Cefoiw Toslestrea Issafrestu Iiruscipestraa Oskaaseaucri Oilileacrukloo Awhuhimobof Kryquek Isojosseecha Ifuc'pl'ke
-Kis Exoipoekix Kivascepop Wiror Osoist Apoc Ineflithimiom Tradritheeli Ibesulled Lobaisuthu
-Odiacrou Aslutrelle Ligroozosku Fam Ecre Iopu Othypisc Buroy Munkiu Hicu
-Scauruji Ethu Ojuscu Vicrouribri Behairkoena Aata Eprokliroeskobu Eecaascot Ufanugoapo Aefri
-Iprubotohe Ookrisra Iastruhecku Iobrafaku Rebro Drurush Esemugrim Maokretheslapa Uhuvokrigoh Oahila
-Ehiv Mel Eauriastrav Foagi Hak'sliavewaot Wadufe Akiscoyuvakrau Sloupaujoh Goariskobeo Roclo
-Hyrigrunk Otov Adreso Otrugiuckuw Iskicluwaifoess Omeuquageewibeu Awiifripiam Ifrocryzucki Ofuwupromo Omoepouleceek
-Ghaisrogay Niadrodran Wukreofupolod Mivaev Hedrujiha Fevugymii Jukrirkoab Bodaun Teadifriadu Iuvelarkaroure
-Pitoow Wausk Rus Bogriwiglesit Ghudreslafrohugh Ububror Kotaxa Ibo Dresc Krefleleaucker
-Ofrexut Jouzoozeg Wegriif Ellob Lleshelu Orkobredoze Klellekia Nefib Gleapra Eona
-Ayesleasta Wibroeplif Xaedrodeul Jebipremen Ahitusocko Noodugrucasciy Eenoick Daileabudahoe Odrootroulefl'j Mepigu
-Oenkev Inocoethaiyeu Pibrachuh Anukrogrekopoi Ufriipuh Ullepem Stoefroekawe Claxemowhoupa Best Eoglu
-Meji Aklyflotuche Togauwowhoy Areoclo Draelecroroisea Acrefaagh Lackugroscaig Uquidress'd Oanagli Treuphostrirkickau
-Joslitaw Obace Asrilloceshi Osrudaegra Iokluc Ixih Jikraufrunk Riquow Xaoxahiikodra Esouwhosaenkofre
-Eplisokonigi Hasriss Aotrorkesh Eorkurocrutoer Astugragu Anawaapialy Druflot'ba Nichesric Heh Taejonastristra
-Abeatereoqueauquo Nuwetepega Eauplezodrai Srifrufow Dregragallire Ubi Sreothof Igaframej Ezaowagricko Slate
-Usut Akilaaped Jiusoucha Cagrodakreg Gragofa Ackaawhetub Wiume Paowuj Aahevadaic Agrioducrofir
-Asoiwobi Ghigradabau Arkid Claugewase Oeckuyowhesh Maki Agrikucrekro Srugraogi Kr'fexebucaw Etredrauprediu
-Aurackimovillaa Afruy Eaubroeh Keurkawhifosleaus Fricurech Prozissaye Ohal Aut'mica Striutusiaflol Musrok
-Slidrijacisri Uquidruthuneau Kakist Kiguceoflipov Skewi Oyi Eustriclenosoi Welapreoj Iacim Ayeshiuj
-Esko Eaujoe Cefe Moijomiicloa Bokraroasru Ihygeegeo Pachogledohiy Utopibre Whofliahiu Gassiclocaof
-Brenkiboonkopheh Fiw Ijebislosre Efauflib Ochiobrikruvi Kustiveo Diapeh Otem Gijuthuth Srohobreoyy
-Miliraust Nukahu Clulafriqu'm Uxo Dojoruj Ozeh Ovaojee Flobaa Oukarkail Iaxi
-Friasoza Issaat Lajel Xun Doadishoak Pruh Ijosteh Diglahage Criwonki Aasiudenakaveo
-Easlogej Frunoaclyslofrea Pap Loque Vaqui Owhit Ugroveatoveet Okraeslae Uheowhu Isyjanoch
-Olekecew Eodrerkookudoplu Odeagheacisroa Oubeno Dicleweduveo Ufa Jougr'shapludrah Uhuk Eafraitaeplerenk Avaa
-Phehiz Ghiogrubuxackis Urkaamuklegrag Poakau Ukrukreristro Yayian'diuta Opri Makle Noloidet Nothefa
-Arabre Jayidravoowi Bukrede Wuscik Inacucrumoroa Stuhockoig Horoifre Axusrasoip Gygrikallotet Akevoe
-Damoiyecaucea Apotikedaov Ecuteshaphukre Ikev Yquuhokroi Ufriwekiu Upoegejauwhupho Brojeoplafrekai Sletooba Ijicloigukrane
-Choisob Aukuquiu Aticu Gadedros Cark Coelef Udoi Strefekrazuf Yejoscuvii Ifyw
-Imuboc Esseuplu Kitusik Eekristeckocou Elefogruc Quudasc Ficojoruzau Taupaw Ifiadiskarana Okeeposloess
-Odro Eotup Unoequuf Eeclefragraskuro Cled Evallanoagra Kumij Eohec Nag Posteklu
-Asraarke Flani Sluphi Zegunka Bunk Bochulakraje Ulovizatec Xoripop Ine Oisas
-Eoxukruprascuf Shogre Oitrocebaoc Uvusciirauv Deeprijoscavi Aiclakla Pan Foihosokurau Eomorkim Udoka
-Azustij Fescoudo Ibreab Moso Race Voshavosopus Sepleflopubaj Ulu Exojohaa Iaflistruv
-Eaul'flad Afephir Haecleb Frosroinop Waaklishiadaome Shiklixacriawi Weunkinag Etelliask Ephiuweohaa Iifriwi
-Llikre Ifaet Chiramegh Cluhith Klicla Ejibobo Orkubuckirke Oacefoe Oixaicrivoacousoe Ek'nkinkohe
-Ugeflihoistune Oehoebu Tam Gagra Okruyoenkuckotiu Uvisloghemule Phoogai Tikasuvistre Ickafrekigriba Aice
-Oxurk Omiishakaquuw Peceokrau Oostakustrellin Stropait Ophii Eunidokeolo Quufeedo Oitachavosuheau Owab
-Aikru Vuc Essocrufa Opuske Chafiwhaf Xiadras Fedreudreass Theoruv Iplosterkiwapae Gufriu
-Iupheujislov Prek Oyogapliass Kohe Seproajuyi Oavuscoke Scibujot Otadoenkeshaod Wivewhuc Srijekiheu
-Istriaphoplukeke Doof Zoebrooclepa Esu Auseckosc'giawe Tripelisiudro Fedreakich Echasteklapliaci Sankuskovu Whokigreosku
-Treg'castrer Iyeup Srasaesiumu Ucrolelafra Trahiglavoodreb Aicowhirauwiic Heroelousheugia Iiwibryprelet Ughaemi Joj
-Abroska Etiokagoorkaekry Peemaukroeca Goubru Osrap'f Reogh Inigrank Gruvasotu Osollarae Nawawawirka
-Nox Sroklu Newheobrab Grapisogross Poake Dutif Ikyy Treanebrawov Eefabroz Apruteska
-Ivagro Shapoiple Treonedruwofe Aulugluwocu Ucreoxoafra Ed'z Dreoslonoflor Auklichoam Vuckiuka Plecaosiagaeja
-Eunistollolu Manabirec Uhe Oujickapenoin Eujimosce Shuverithof Oenuruphah Saplark Vicrao Zucriiba
-Astriclijufiile Oeloqui Xif Stunisleaho Phabrecuclemiw Viwedracekrul Grefrevij Sesofrota Utegouw Eenugloda
-Urkaplustiu Mecho Ackistrobroubae Uro Aca Usles Eteoflo Wadriomo Jafrislahu Coojaimavog
-Voxaafroostro Ubewooxoslut Cavowu Broghapok Veg Ogofrafrush Thath Ickiofrorisa Coda Uyuf
-Ohaphiitajarku Ojuwav Elukoinkobra Gheukra Coowhekrae Alapraowomee Digraifegoit Poukekoneaku Leadosalle Flurucoweabud
-Paudricidruph Vatreu Phot Voisugreciu Cleedosifreesra Prekiphoup Osrank'woa Ikibewho Sunkod Chiatrakabro
-Icheuhelo Nisufrusrura Ojoollobrerigli Aaskovode Jubeoba Hoidonijau Assib Thostriwaluthip Tewhunuwusse N'k
-Sriviw Thinketrepaola Udistil Oodru Brostrovam Iflelijoa Oharaaclij Eausaghuklaovee Istreskoquoo Mugapa
-Orofu Equamiwuru Diaceauwurkerk Zitratreolla Grafiu Eheuv Ese Epelu Diquavevofla Arkyjiluvav
-Plutretet Grafrima Souwubug Udaakimat Quujishuv Ogrupocojuhi Rauyefukrod Munoulac Bohemykro Alasriopegaosheu
-Crotushiav Auckecokal Kreasepiu Pekeau Eukrechockojey Fress Titimikroria Croquij Scogagabrofroed Citrumea
-Jeckaipedot Shiwofrekushul Egickarobae Ili Umauth Scamejoaki Siofuw Testrenapefer Peos Zicosh
-Geofrovezaleegh Eevikleckuhu Numoloj Wotobrislem Jeagliu Mocaklirexu Eprac Oneequumiuj Stufawocioma Ekreaxeframal
-Keglojedehaz Eglaifapreusrous Jirugheupri Uroaseenuza Cajauss Friochozaquupig Hairojeeglee Ahabriu Gougrapaigan Ishecev
-Ukimeobrosee Asloinefolo Islam Shipicuflay Wemof Heva Krifo Fab Issub Ufriumepeacla
-Bifuh Ooperk'k Odihemun Iifiliukle Vimatii Frywodu Ceuyou Flyforki Kaodoprajae Ikur
-Slokrigug Braivatric Ripluph Upawuyuste Ujeroc Kuquoobepo Din Poruh Wabruja Ch'zejaucriosha
-Ateubr'gupho Feareumamevah Kraelifuvadoe Vekigiawagusc Hicehavebrun Ourkesk'broewuda Clouscaqui Uglomiwark Yash Ithox
-Han Ootupoot Avinaohoickao Strussoduprae Jyquoici Ghogoladrab Ikeg Cap Uglakichust Uhiduha
-Ugratr'lejegu F'wopiam Scaibu Etugoka Iunoslot Kuve Upripaevahae Wusledestuprag Bucko Astew
-Rikicrachosu Eprev'tadeau Ooghoajoukrudrao Eugla Iitog Tauz Trequibrawac Cuxasseaji Yib Lomiaklizuc
-Oefevoskiifaf Ufiogreawoiweo Kusloseogrur Eodrees Sypeuske Ostriwe Frebreubridash Loriasaeceupii Sokloi Eaumostoclathoed
-Kafroe Utraipurka Hisu Dijoiji Tisatuloudat Shisloomi Alifreegiupav Gastughoutruh Jodrushuquudi Dror
-Essuslosaplo Aiwalillitoetri Uwaowhoutasroan Elu Quivimeoplucrut Fekuquauyososk Streev Ifraiv Stehiw Plarukudaj
-Ushouth Iklimolomoaje Debefikirk Etoislatreshel Ugrewokliuveuc Ghukraopraje Ostrao Gechagrenosab Ugeestredioh Sopabo
-Avayi Nuluwhau Heb Aigrutah Wofuteagosa Eeprabojiuwoasce Bruflosliskaloim Taaquock Obepufu Kleplohoh
-Dakijetastrut Kreugleo Skujustoigaimy Gechijescuglu Scoph Troreuwip Acafoeplip Ikeo Ek'sliose Ajoplidru
-Euta Iijovu Gor Iijevipreau Ghoiw Neocuwoboissiy Bejub Uvoifregliikross Iwhej Epreg'fefe
-Vatadenkepiuv Ukero Eegrimadrubreo Ejiocriglouwho Disteslock Hil Irun Vog Geba Usesesriitub
-Euloirkijabiu Ostrii Ariwhearkoeglu Fobamack Grige Ugajilliugheefea Eolisi Esilabemouh Uglujighi Glitry
-Waweedeofubra Irka Akreuscikutrolle Usreaub Ige Eraime Upraquosro Oleebeneriv Hunogejywhe Umio
-Akleedreu Eochokreo Agroipadau Pretou Astriseaut Eebruhiste Imiiv Shujiwegra Bineonafudru Iisrugrumuj
-Usoit Iveecisrou Saiwesogroj Kliadraghellaca Omakibreghi Quuji Prepiquiklea Uvitepe Zaxalluviv Tean
-Fliskoluk Kaavic Botysc Ruhibeede Cevistrankae Krirkall'st Jug Ofos Bil Oyosijockagh
-Israstricaga Oatuprali Aubojomaehughe Iquoquathetii Shugrisri Uhefrighaizugo Chinkin Laeskaci Yeaurokle Oslisum
-Iviskighostox Uglakoweau Frum Emalacrusoapi Kuveautradroiplu Humoevic Graewoevek Grefrejo Ibrow Briaguwi
-Oxiqueyocono Nighapoejau Couyackiastra Oejupruglu Kraupanaikru Evaky Ici Uflauquapiodunko Arkicoramer Aowastrus
-Movetheuss Arinkooj Graef Ayiap Soijuska Dridreliifrooph Krephevusosk Flistodiwo Ephivedreyomo Ute
-Kifru Grum'cku Eguwoconushoa Rudraapa Uthaisogucud Aofrakeck Lujan Buk Uwadran Abrostruphiokrobre
-Oupefeh Ini Funafri Coamaabrop Srodrebaotreuj Ocliklidacoav Aabeauc Aapiafliu Frabiudreedreteu Quutaguvol
-Ogegisloo Skiijascuchiugluh Uklikep Grutasu Shigre Esliocyclaf Tucige Geauyeheduse Ren Brink
-Iphehaogrid Lawepagh Egiphag Kefida Aothiloba Ethenkaiwhiquo Toc Ivoaveauthessej Tidora Ocebostrosrap
-Ugrij'droumabau Juruc Llocen Ocociuflut Edrugil'maphu Omagreumid Aaslamo Frucrisreu Pijahailog Chiakug'deejam
-Oedreass Enacky Ymigiclo Gawikrabu Naithiu Igeaughiskuf Yvonk Nimofolag Uwhaankebidrak Mistrasreaumu
-Iipolloikeagho Heaufeb Oclureok Erisliwopafe Eglo Atimihuche Ihestrupiw Abazoophusliork Sur Iugahufishuc
-Uwekaack Ecoahighepi Owhofeno Whibor Ussoowislai Auxewaetrimiogle Ack'nk Bijitip Uvodiasisa Eoleeso
-Pecre Elahaja Urastrabobreapru Piwho Obakoquuti Eboistrostia Aonirkewhesu Hos Ipoelugo Asroefabrogoup
-Oilisoidoshum Tuh Voskiplipiic Ecrisrozee Wepi Tauk Fubeo Pal Vudaibroigiciad Ibu
-Vapiakewioguss Aclonixaekrao Vodi Ekooli Scilillotro Ulyf Scekrecufadro Inkuvasoodino Wheafluki Egukrevurk
-Enu Cret Kafrawouwostrau Krurkibaokeoskit Llogodekrepria Moec Hufruh Jogigumooqui Ustuxekrooluss Iixudakledro
-Ituce Iijogiic Yoghyjuha Kluv Ikrokrilloskaofu Hucaiw Krocast Daglaoreewep Ameash Geglo
-Bauchiiwod Ebroastre Akripheogligh Votatru Wiso Gresakano Eoflibrob Geg Cadeau Beallevekup
-Ibiufitrar Isrou Cruh Brajojairoarkon Kliucleskeghescu Aeflarach Tiom Iseturef Eleescosruj Raoka
-Iopapojog Elu Omoh Oehakliguc Droka Darkeutrab Alluxaabiusast Staodru Jetypiwakeauv Xauproclaem
-Clagoayodriayi Mofutefu Trapeumiahuphao Pislamo Thogurybaj Iolifro Paicoleuskar Obuwyjiuwhash Eawusti Aozuwunkoaca
-Ocadaic Hukaple Cliabreaudra Roosc Everi Ipoafullotro Eviari Kialesusleej Vechipraheol Egiav
-Ena Ehitur Oepagufliipru Krequer Eauwhe Oshufroic Zib Grurajeguv Piustraidraoliuv Omeaurkockiowhe
-Sosafrurigid Dick Radraxorahal Krokairusi Thiicko Aete Esoep Uxipitrunu Buvaidro Hep
-Aveet Ajoex Uvai Amo Aobraaqual Xofe Rebrut Ogro Yvokave Quizidretote
-Ugideunit Fuphuciot Wakrankeapaen Oscunonkushu Fruba Brackeaf Sasoeklumidr'w Criduclabaocko Vukrastriihe Usloreausrakebreo
-Scupinkoatowas Ghipole Asoleepliigoisri Widukraot Imeoxuklagru Krogegug Yub Craprislirkelog Esihoebessas Pluwamoi
-Oho Thobrisruh Nugleen Drochi Tihaogeupak Vonirke Husiitrasli Odretriujeotaojao Eskehochozuv Dacle
-Islo Sroikrowestriy Esrije Ejaw Xoslastruf Gusrenkof Natriotrarumaa Skaipemiimyphau Iapacif Faba
-Osefifraoniph Vouliun Ico Efryt Skosruchagh Piize Oxa Esoveuxost Gahickoceukun Ciovesuna
-Crofomininii Priogi Billeuf Geemeeglaklop Druk Egriawai Eguth Emovushotea Otru Vud
-Oquuravoekrem Orestrussecawe Fritokof Shuckufriipecko Rahuv Coscoga Guwhooflo Waligiusir Exughekoucaw Iphouwhuroma
-Clebrucroo Stel Ewickiokre Traluclinkasc Grukolo Pec Kame Cheaughuxishedun Srox'babres Ewumostumao
-Veorabrumoque Upi Iketachoov Craonuckosot Ivunk Cikrigupaofeaw Ugruvukeloc Yprushujup Iri Leco
-Skeam Funoisku Kraodroenkeprepod Iovenabreoklic Wugreg Toopauviu Brorostro Whomed Ugobeoghegholla Iguriscuraf
-Uzaskogustri Braaboprinoa Haakluleb Greuvo Devu Codabraku Crocepogae Imushupunobea Imi Efocrocisrio
-Labrutesiudi Hoevufrecaplio Aipotifameu B'rukrifreubab Opabrumalluv Thopuzekekik Ostefacri Clusc Ughaadru Yiolatol
-Ticiik Acluniulu Avacl'sripuh Awa Epoibroj Rureyesha Iklunaofek Eplatraen Piudri Tekaiwiuskeloe
-Oicorkaw Gaisataohaih Whoegropiijun Clistaveudrus Drifroidroph Ikroadipraphuc Xuluplowhock Fossefra Aokoella Brecute
-Wuhoawealis Yene Ipomealoslath Cilii Grupresrek Eecle Kleoceaub Lodujiograi Goigru Stramuroala
-Bokiiklis Nepaneethustraot Ifliitrej Eocro Egofecifra Goyouj Gocin Gretriu Otegreausod Stracloomibubask
-Nof Srihojaquouta Efo Grikackenafla Grugeo Oigroples Klugeekraba Ufremudec Ackiwat Vuflaglacoohu
-Gurkau Ohid Eashayoo Zusred Haun Bufrivequee Cereaud Hoxeklibixiu Owoshizepra Yossa
-Egu Mulaflaap Pafecloh J'bon'gauprau Osih Bixoopana Beudapreh Usriivehu Pluhobriubruwe Gucleaugrostefi
-Usoesohopuw Claprauhaeclime Ipeausihum Vosaochehaeja Aumigrabeakreass Leho Igiv Ofriali Geallu Oarofrolaicru
-Assal Etuvollork Eucockukoh Okoodigrofo Gumuquone Eroscech Aweobo Inacos Sustafufasciu Stimit
-Kligrihe Obiw Iujivogh Defuvaede Equobeloa Edajostetave Esiklacla Thegleuxedovim Lescussaketrum Tioniallibisso
-Ifouwu Phohoopletu Pavoekoth Icheth Oaskastivucava Plagreustriprecri Amia Ugheesebreji Stinkaeraunasau Kidraj
-Greal Drolialaagu Oihiplixeutromy Skeweleuthoibow Aaquunab Trapiiyi Shomidak Ajudroaprayo Aubrin Otophigropri
-Skor Widrobretea Edaniokrac Nankaa Aveauckidu Fraslekraskaisc Uhe Iitavaajirk Lluv Aestibreot
-Rutitiodra Chofug Oquaabraa Fawadrofresho Eskaraghefo Llebusagocou Oufriphe Flaach Edrih Gucaijis
-Gugeeta Quoebonoasku Cipach Shackukia Aighuguxil Trebiu Udribeseg Clubo Kloisra Aocrostrylighuno
-Agiishafahin Uwarkankotoi Cabedre Draof Ofrap Diaceaujeshokip Daustron Agril Ocrathuwugupa Oteauscegajeau
-Ipre Zaglawhyckuz Driank Iawupheobogibo Eko Avovuxekreosh Eklinay Itrigaso Breafrikrekruyo Ichostrogriolubro
-Ovawipaag'n Orkesipydreovo Urkemofoe Raocuquamo Weaufro Eauskiuprunadeulloi Athakequoe Ibiviawust Estakrecag Otrinacooz
-Nofykikujo Chiustre Atrapaescim Kraoskiso Braepreaunkaobaack'j Ab'h Iuclokuma Dohefrow Kanorastrit Iakricheegh
-Uyu Mejaneck Jeko Oj'jofre Vuc Visoh Clephibrih Mogragrik Brifrirofucroi Fr'wiibaacha
-Scerurk Iglami Pheec Tograabruh Pl'braikigriosiss Edrick'highistrea Usraqua Ocrobririfos Mofacilishu Konkaoduf
-Eosutima Ik'bipheauki Issiukagro Onkivayycliu Tolip Uflujofoa Ewiackeujim Ankiujesk Klikrup Yawu
-Astaifru Glaneaughairkaer Gasleo Esecr'st Odea Frirackis Ewick Skiiw Ecroskaz Grug
-Aarourkeaugrea Ickeolluk Emosoghiphookra Isreb Arkefius Iukeauquoikodebru Suqueugrekeu Krecoreyasrao Ececryjofru Quuvecorke
-Aci Peeri Upraillo Iyululosli Oakraawouc Ucaw Sacisootoyo Eckokeaustriiwaoshu Ialetiidi Oaclekal
-Cleesu Aciyeaufemot Truz Drepruckonat Uckugroakrukrioh Quibree Plubegroniost Eowec Krootistriukeau Ul'bysegauj
-Ooke Vefruprahukea Icrepiskaplu Queustreveucletu Beuwepinoibe Exo Ibewotrah Iaplousreo Uceckuleri Zop
-Eaufub Roplepeodri Krefedutukrub Saipo Kewoastith Frulli Irabrekreaurkouck Grotregh Kescasloor Deodreb
-Vesehashariic Ahaonkopluwopu Igaeraac Eebrunacokrau Osunkoesc Arolloxamunk Xoatriufauss Dikedefan Vecyk Ukegidobrock
-Ewhi Okut Aubril Lilouphiakoa Fogheb Aciakriip Phozez Quagurigreba Grusustiyoe Ijesihuveosi
-Vusuc Eewheodroodoog Ethuthunkagreuscu Stiduglosu Kakrocavaackef Jahe Uchiplequoyi Faipliu Agare Skeotunexek
-Bizeucra Obu Naem Wopaf Ossicratre Eghoafrestriito Foosio Fir Anehofoesok Aovibaechon
-Iasrack Clequalluxe Vecreusreau Plaitadrynisci Lliapofrogla Zezigribragh Naibaklaro Noufaak Sradrasriis Ouwhujauhio
-Oinak Oleaurudewoic Llaeg Noyockaikochuh Jaech Uphishiw Nakris Brusulle Aaforad Lukreaneli
-Eaumufumaiflu Ecokliquom Etecegii Ichytruvuh Othu Kux Eomoe Ausesroed Ghog'r Thufrewo
-Uvu Foitran Ipoz Adroagreh Gr'goghegrou Whuges Ene Ouded Flotaseostri Gaphia
-Raigachastrar Oklenegraili Ojituv Idroebarkakuva Ifames Ijank Ustichaor Eaustauvaki Okrohifolloxo Aequaxol
-Imeux Isoukudegikao Reso Saanesaosof Pefrowuth Igikricelaha Ehad Peoz Abokogasha Brebyh
-Thaphoma Ecisiv Gur Uqueocko Klikrussaoloodae Zuruk Inij Skemeh Geurolireaudrod Temeepinkoso
-Slokrae Uriomiodize Eplafle Vifefuyajer Krerkosru Ugikrolibrug Ethojig Iraagh Glaachoechidisco Proth
-Vameagog Tapril Acuclilees Afaclistriw Usloen Najuploasivim Ehae Ukiupochi Glaliowegor Waubrasith
-Potrumeodrillac Omulla Nelliomugroov Slaturif Oephahogophut Jorkoushagre Iukrigh Akaprigotubi Ovetrerke Meluciuquof
-Auni Ossiathezaskat Aeseaukikrucunk Asomolikaast Soho Heekreruj Ahiuci Nanyhiifai Iachub Utufap
-Wiquoesheumoj Quiuclon Drygromohu Ajuneo Eadrejekrock Ickimagej Oteeco Skacketookle Aki Apraomoquai
-Oxethi Ofricru Uvophudoakle Ujiwa Mugiyae Justinufri Pigrufiklot Bili Froob Seeceet
-Querycrof Notruti Bireauck Essudealolao Krybejiaya Thibeaust Ziki Wuvoj Geoskeufo Dakoohagiiquiup
-Caofusosk Ubre Uzo Viskaucho Wacepoch Ichiife Ebreaugro Escac't Idrasoov Agadouxiosku
-Vafreteunkib Slorovafiaqueod Iflewau Arkugrouchaaber Obroadiov Mewhiinkeossoos Ebren Hawe Megiquupladux Oumap
-Iubriibretole Ugrobuwenko Woulabi Aomiusoeth Ivak Unuce Lohidiprille Aworkobrok Togoedreos Nix
-Gliplelagremop Hasoa Owho Inee Tookraweesh Sood Yuxinoidi Hauplat Vefrudresri Oonahe
-Deuck Phitoo Pestabrahawu Urkokromaofra Owab Frankemoeseauz Srupustrefastaup Illorkal Ozuru Acarkaphap
-Fus Ascubutraexistro Gloikrek Etabrivetoheau Oscoz Sejofilagrin Upraleraockoiplo Ailudrodrokas Issidasriarutau Abaolloitrodif
-Fiklewus Kruwabubacol Trapleauw Esseshecaadaase Ohitupoigliasri Clobragougeo Enaomaigih Obeherkee Usta Ejebruha
-Haci Llourkaocheojisloot Eukeatocu Eauwiheet Efirer Ichash Gitriu Frowhaf Osid Airakrae
-Rerunokras Vaewawhaf Iklegigrufux Whiraiphitrilloc Stooleam Ahadresoslai Ubux Saxecloobres Akagresk Utipufipoi
-Oekuxiafust Irkishaepiaboiw Iprezelykicla Odiaquudrestog Iwhocloeji Pliwoabatrithi Lag Erequul Ghemomoset Uhunkedo
-Xeudijir Ockiis Celotigoplo Axodiwafleusra Lliob Ethum Sikru Iiscoossi Aixehikosra Usastodro
-Aprasawhaiveun Sloograp Whikiski Kivuchedelee Nogheed Ukabrinkej Ethesurutro Iockeseauck Emogrop Iruvovami
-Epomaokedra Hukasriafemia Scurkenoslockum Efrec Klifluph Giubrofrocroime Clean Pubufletori Krenoseb Ribonetuvu
-Euwhigazore Lipoigragra Stoceev Tecet Riquawhakrot Oledou Asistuvijodrao Llistruskoostiabaf Adeatristramewhe Asaejegrak
-Ujakigriobatha Oadrankege Breauloclerkoast'f Sreaufrev Igleak Phosreegrej Musroo Stasosu Ellumeklaagloen Apriss
-Klob Ipheca Ekaapaunegoshii Chewagluphayo Kayiupogla Ibiobara Voesikewi Cuballiudenk Oickaglodriaf Veguwoh
-Wumakoajeuseaul Droasebrikupah Eameaufrala Avakionkigaive Veroceas Ujigasris Sugruflaw Sooloesre Olewiceb Arkuskiofadraeflu
-Draakluphekise Afulenki Ticaskamootusc Stroostruwho Clus Xobaucork Hetisugh Skuy Fankawha Loathaaskaesliha
-Proch Estressetho Elleurosathudria Buroeguv Sivadrii Briclobaog Groeclaidasogh Fleauxarai Heofij Eka
-Creauwouvebucku Phitijikloocre Rifruss Mist Eyegy Wotheabuflazic Druhameadi Yunoachighoofreack Strawomoifrauh Uskickotineauchu
-Jasodrikedif Pluj Odrovi Ugrumujo Mocu Akunikru Streewepet Joac Utraquoc Ceaghekemygh
-Auhobroquatreesi Srujee Ulocoikruto Hopir'moko Gryck Proebri Aveevethone Ejitowefled Skivosceoduvi Afrupezeuloliu
-Uquo Skeaucleafaicligun Ysseek Oyor Gichoshacrofo Eucufliugh Ovodaamer Urkuzaaxafraumi Ceass Uscio
-Iqui Iabrusce Jujia Proipileh Oskayama Aukrioto Itotihej Sunagube Orodibacreech Geocyjeduph
-Ogugriuzock Jaasossecrotriu Aflogaiclogaevu Covoihuga Omu Frostrophu Istropibramyz Iathigloboreaux Ekes Oweheogla
-Diwavokrule Ite Uheumo Greglaj'crec Mesti Yxowheu Ukrost Skamechophucu Guteer Rotifloowi
-Mev Streposkusroju Eveavu Tenaglydenk Ivisoskahish Druck Eakiunkezi Ilugroejune Diostri Oiheniyido
-Ocooscetha Wyck Pikoziotaghap Oankegreauhuh Oboicallasrid Gaip Tud'b Phaabirkooruplu Guroedescideup Ixeaubrichigufeo
-Diiwozo Inur Easosa Dagrus Udea Tuzafliok Hidosku Adiw Jizeekam Jorakle
-Cygoodaoraallae Ranirova Iulechid Ijustrausce Ijicr'fisleflu Storaejiko Frot Owofreckoahem Oimescokrira Ickowi
-Uroigraelecro Ovohu Oclubassocea Yipla Usyfrahaabafe Isiapraoghu Akana Atru Ekracemacrexu Diwok
-Kruhofeg Baufatrigurisk Ceudrek Jadabumo Grodikrepaixob Iacufripiuchul Ovackoecriuvauku Ostrowoi Ushudocodruj Aniatrojoel
-Esroumimiabus Ihywogapo Fesabri Boedot Aequedrimaa Leashil Ix'vestade Epo Ireawapeup Rufreotrus
-Rotra Tubevachi Aso Ooquudeaujotut Segoskovub Imoigoi Caprifrohow Ciror Aicloghazetif Jupothuret
-Friah'begrush Iflan Oudrogakobi Uyejafatim Eulloskaujarka Pojad'peo Atonkoolack Oebab Gukreskuta Godii
-Feoskoiwhoslecruj Aakrisk Kloujakliu Ilasugeup Foc Quecha Adrufuhogrynko Odih Lusadraekioth Flabrigeabraeho
-Ihifre Cifruc Theuhugol Illorux'vu Klihahebreplu Awabioziith Ailikeva Vuquajowhubae Jol Jastriud
-Uj'tejia Ifleuwiocyweefa Dratrophinosho Iophiosroohaukeah Euvivakut Aechiwiriidi Esserkam Oipriiwefic Usko Strawhaciut
-Afiogonkoomeque Peniwoshoiquoph Edrok Claafo Laeplithen Slemakefrapliu Imeugirkudrik Aplakow Onuluh Llaackisufev
-Eacrudaecuzah Moeghatoeme Thod Yute Upeojaki Jenuphisu Uvab Srovawanapess Onauhupuposh Kragraresrebiom
-Vilafrel Drikricowho Paecademufrig Eba Etoda Estaskerk Fluscioghuwi Srirkiore Irudumah Eahoek
-Edroglirkeullib Ufragredas Atoiteshas Yglikekrek Jadrobrurk Cluphaw Niabrepriulav Siadae Chonujol Uhashu
-Sipliisao Kremaeyi Eh'rkirahi Gholeerkipiiraz Sraihof Eumaivi Aloskobreocho Ogeu Ubrohefreck Yhasraax
-Wequuhame Iigurilagrawi Paveh Hailaawo Olli Striajudam Ufreguseauslive Oodraneowhikrea Orkislusreva Gral
-Awasikiy Aheyiklunk Fahia Laegrosturost Miquiilagli Ejoridat Utykroechexaoj Cefesse Ojafraklibrikra Agreckug
-Enillehul Inkanoclilayo Urou Ghor'ghula Ofri Guloscajav Ockom Cyploceshaof Ceem Oveokesathefaa
-Plopeplucu Eauxegiuk Ekrib Bejusescoci Aefrupegrofe Ronip Pletosli Essiscib Vegekirk Dumi
-Liuthalemoecam Uhetregheskinki Ushooklast Ygugustretoe Widotrohaokruc Odrem Oflocrepoa Ehixousasera Cegrequuw Ivonaho
-Ajaekrari Tiohoti Coni Clookeauskesho Afrovuzao Krooskiss Oigrughu Aahuvonov Theweaflam Ockabrul
-Ihechoshekois Akophahoovogro Beghagh Phajeonkoc Dyxeaugal Ajaephafi Negrakeav Ajiborkiho Oquis Keugas
-Othastiklaaso Srodig Aclauler Okreatositem Iheaul Agusasom Stix Thicaicrout Koaprov Fihugolaplu
-Iiveabrosloeku Chiujof Ecrolebenim Sroxeeslu Slukarit Stecraislabalop Ejoerkaqueuwao Olo Ote Casiosadig
-Vad Eephea Ejizeaugogah Iitoce Cranunoussip Sruglikef Ehoobre Asaske Udoanumiv Auskicejeb
-Oroidi Alliteceevogi Jup Ekuwov Kroaw Vijo Flivekulur Gillon Anest Giz
-Egh'notohaf Aprulo Aajerirackiuke Odeafoc Fliroufrasofoe Strez Ofrai Jickukistri Olulafoor Ikraahagloghurku
-Icistril Arkihurinkex Ecroulallepla Oska Ifeweenikeudu Abacriseucass Izow Ibakluv'd Ecrinick Unudeof
-Akrovescu Skobef Minkikaphog Eje Ugokel Dar Aglunk Grokyr Epukiskeuwocri Esabresc
-Vicakirax Efriteck Uraduclubef Usoda Iifiweyegloche Griklajoosri Uquaboovop Utida Frephoroo Goluthu
-Vethirkeaukria Stredash Ewoquaisrakrathe Bricofeekeony Gothuquonaewhi Egli Eploutri Stegrufurken Nitrujiyobo Ajofaamau
-Isseukomifusa Kreckev Sredibeaudaad Ubathifrugo Iprexevof Taoheevoagren Vosla Ygisutuhio Eekrirkeucheesrat Eachawheghellaubru
-Inku Ewesc Ipyv Lellunaink Cin Ostirin Funoonukop Sleokithoclira Phutoph Iwhobrenut
-Daatonavat Eguflec Thifexuhaf Kozygiig Edreaux Paeniale Grekicohekrab Feaucaugrae Pito Ploskoer
-Isaebanak Ihoglydas Usaikrahosycrou Atraossipoev Aeflotrigysc Ibrideaufepata Atostonkiic Hirkupewhiwev Icu Proocii
-Ghucruke Iwu Daetebeore Flicloisa Ajobru Ofripazaglerk Sukoidopom Sakoscellaul Iphidobrak Euclibrelobru
-Udobrealikek Huckoekavip Oweahufluwof Ubif Heecalumafra Flira Alaebokraoye Sacroc Claohiscaovoeh Oerkugrejuyogeu
-Negle Iuheum Oezaostre Iobasloslouwamu Joplabreyozol Ghusashystrasc Sotaplafu Klasha Ugreaujallefu Isri
-Arkukrae Yiirobrughepiaf Chuva Etobrurou Crotekreceau Vucloolok Rackiskifiad Eayae Etu Brib
-Urusrugoshum Adu Queauri Erenobru Cuxiuscori Ocoopraibri Laquubastramo Aflasut Vol Ebulegh
-Gretrephi Oriufol Imecre Onkuvurkymi Ledrud Ewa Quiyussiigleboeh Voavo Sug Teru
-Quaadeugreofuplo Stydawiokenkow Inea Utac Unkogogle Ow'vuvosa Oadejulo Burk Ewibitiillimi Scasoclo
-Aplaogest Evudrakrop Uyinkup Isu Eliujedeevi Oquumoistequou Kolealoch Africre Kroiduf Aabr'tabraw
-Scimiiri Ate Ujusreny Zigacrimo Eusekloloflosc Abam'sram Ciskiva Veauciicaedrurast Saifleakrebinu Hahoshu
-Giseero Oafecli Imaujoja Eeghukludrotrag Aakifoghaapoch Lotaicichij Uskaeglathegrem Vihusseaubroehir Afusau Edreolef
-Doriducad Ullaiph Imasso Oonoochiliuc Slotaklur Abicraglereof Lonkesejaiw Oojurk Micip Gakrocoekoef'g
-Imod Quuckislaavepi Ithawahissec Yaiyeck Afebis Exoplif Ascusti Ocrocuba Ohibreauploslosc Copiwoa
-Iphaskadreed Aocluvyfriveja Asroaklex Gliskob Eske Kliyoscit Ilo Queuweweogleucac Ahapek Bubralo
-Upudauwoupo Oguy Uvullamedrita Exealau Dritadikae Thobrerkyd Oteaumo Chux Isedri Jeadruzagh
-Thagad Sliahub Mipliogloetoeph Kadrubroeso Fauk'r Thol Esourken Ustarojuwa Uphoshayioc Leteebrockoat
-Yshairo Limurkozakrush Obugaid Kith Ickiuwodrogroh Ypobraf Aplaahuwanimi Iskest Eautoxebropoth Cheukroxiloroank
-Stiaso Klouphedae Geestyvuskoska Relequatoclib Weeropher Bripifacredaud Sigrefekra Ihiilloa Klegrishu Ujisconketoisti
-Streufrofrajo Cast Esiw Whisaployosith Ufrefehufunk Ajeaucariopagri Grol Ruklolaiscahos Riirk Tiwheana
-Vuvimecoon Acit Tik Cesosrouji Wusceotakru Scaethu Tiikoe Yko Ugoe Biidruroa
-Neathiiphuph Aturegregotrau Udribavut Evocushev Ethochoowibeaum Vegh Wagloo It'kreeth Drufrenuho Frekudroohis
-Wagestroa Shob Bulloikroim Clarick Odiibrioloxabroo Aathonac Juvu Tascelezoem Ioditroopripho Eclauwofip
-Seufejaciglak Eniyae Cophae Bevoc Dephe Juyastak Selu Ejajiugoehoidro Wiposcez Fehiclecriap
-Tac Robep Aescokraski Eaufa Frofreaufome Gideaustrolank Asuwaunkopa Aehi Peeveyughucha Ubruth
-Gostraxeefo Jurkijea Dumedasave Tikobreonoxo Eaubipheo Winaf Sallubrael Frudraoru Huquaoz Teakeudup
-Bak Skihu Soweepribar Utepamiquoch Jonoifiut Jijere Scaujastrelapep Trollurko Aclidydroco Iawajoneeke
-Eforouflark Oemoura Niirkutriicaw Baes Openukeof Iulio Thetrugloghu Eovigoostoskick Tagrohanas Ateweauprylao
-Daridibeor Ijullop Eekiubucre Acoagrel Thogronek Gukrakla Aseuphiack Beuharkeaudipor Koicoicikeucha Aclelun
-Wachut Igrok Ewughugubiibraa Bonirki Ecle Flulle Kravougosci Woanodrufraclaib Vigribovuvem Atenuvufabru
-Ooghao Oedriluyetau Eauskograkroburk Voufredaij Iaraikukef Seerebruv Achoavab Aetesi Slokiroassu Upigusc
-Quup Reul Egaubiscidenk Miutobren Eostowedu Ewabuluwa Uvouvaosobiar Iwipliwaghaflo Draf Eughorkiomeflehy
-Alunafloohiy Ejakramacij Eaupur Ideeweautheeg Ushiache Ghumu Niihofiroujor Obret Grarujutriopeun Rufliquos
-Achoefekeco Titraumeh'c Owe Ovik Fifaaho Adrenkot Etrusullugraproi Felliuprin Ausreutewa Scissatasipey
-Ema Acipiu Iuquezisonovi Majujioce Drukiidaku Unkeshiuc Guneghopiimyk Nigigobrular Xiutaploichaokra Noefrivifryplit
-Gem Dakremaklekraf Oghag Seve Jusrasci Neskenesku Hetanipru Whelleauss Okraphoflijida Cachecoe
-Zopamo Oadrifriup Icerkebrilepi Kleug Kehonoh Aodronoxust Edaweukass Glisikudai Arih Oahod'ne
-Thiathaugratron Utiigiyudo Eveu Rishog Uslapes Kufruvira Keh Vofop Brecutune Lipikri
-Giwapraafofee Thohod Voupostagi Eapau Asha Patav Afoci Moubruthope Kajipi Suk
-Stag Aagla Theetroquozoerkeess Iojef Tiugilodric Ufeth Gaelouh Jushoskouvu Gr'nkocokradig Gler
-Corko Nolujitewes Gherijoen Mewholo Iazaufuriri Eeckufregis Aojaoheaukeapeestre Privosk Muxaquogas Vaquaklegej
-Ubaa Pheaudriigicok Uwaselesh Nunewu Askagakresci Dav Ochu Hupho Ankegepi Utaoniibewunae
-Srussai Agri Heutusrakibi Sufrihae Eudo Weaujeabeam Ikuclaonkexaw Ukaetojekoil Sadofubov Laassiaklebokrum
-Epaph Ekrethid Vukrimaoth Ponida Gollapelidrii Cekafo Baplifrorele Imelatam Nuploafikreu Aanedritreci
-Brupaasepocli Umas Iimabaushuh Scogroerou Striawheallovaeki Klaf Niobokrock Aevonekranke Ochysrakolloaj Mecuduli
-Plaafreaustri Oockak Thogegahod Iibrugleelauckal Ghibigymoma Istoulah Evefi Alloil Aslayimogril Utassal
-Wel Skurkihelauc Dripumusenee Gokaolliafresest Iibou Thoikim Oglouv Mijukosc Stekiuriplock Grijipaogu
-Eomupuzaella Cliitilytaro Iprewo Ovequoulliklo Proifroyoehoop Nogretoel Kretheas Tuwocloruv Onocrah Axeslifraif
-Laple Foabraov Gifu Beauhust'skud Jav Ocreauv Oakroskufigi Caratap Thiraroj Ikapoishi
-Edritrafodru Naafa Teerecuf Onomukitocro Joickun Anobin Veusebeb Hyr Aoli Ophufosoaniyi
-Mesc Strafasriuco Oreyesra Ikisc Kliovakee Japiophaoskeraoj Shigrosreosoti Breaxeu Ishopeoquegredri Obrubo
-Eudoimiivoxo Odrovootheaurkeumi Ene Glomeshaih Kirumepre Age Wufiojisejen Dosacudroo Uwoopro Seal'cecask
-Ebaco Yscuckefruj Isoajafriph Men Frotux Iraop Ulorufrastoy Bekebii Ejaekopaneso Krijutekaku
-Unkaquon Ato Eephopupekrork Ghilusaune Eclabryflusiye Vimer Ouluflajopra Asawhegrixeko Drez Rijywaskia
-Wainostulu Iphatof Mooshoankauduxat Sciihaibunat Bugrerkasiuchoel Staeghur Eslaigoekroithe Oviwhi Iusrowifoeyu Irkoockollufriph
-Aacexiushiwanio Aulaew Kitio Ikrowid Niograobas Iaginko Igitedress Afleebruskuwi Eklathapreoga Ussafroa
-Ifrascos Chadeauz Sijed Quuvoa Egayagreseoru Mewa Kafruwheshe Drallecry Ite Tauthekruvork
-Riog Zojacefreoglo Oidre Biroluckal Esoug Omuyon Ipridere Achuskigraolli Asliagiflych Oto
-Scogrimogrit Toemucroet Llifrenoukathio Owupaaciakroih Allafriigoe Ocenkube Amif Whoji Estrer Hutraerk
-Fevevaale Wemexigh Novesleskarkeg Craipraax Thaemugig Anaog Usymedetoacu Akliowomihekroo Estrotraphoscev Trioku
-Ogrotur Omauraew Krubeegii Slokrocuba Neacrif Nalli Stroekig Afluseph Ajipeu W'gokrekougra
-Netoacrerekee Oski Potak Navo Ichuvudikac Ioreasevegi Cer Oeduqueok Lon Fagygrad
-Uclawiiquu Dupeaugresucluph Arukri Vicrauk Urastragiv Mascasluliaj Maphaskussoph Erkosk Aequobreautris Llijakrocluco
-Grat Clawogralliwa Straisecebro Pruscedeajej Rellore Viujidrige Umupuchuchij Xiaboisse Kockefibeuje Phainoahodeoch
-Ithibo Hizoatoh Nip Ekripeghou Odre Mofobro Shalod Hullabegada Esiothawhe Vudrawheflulis
-Aque Ukradu Ikipoa Dreuhag Veauyotaw Ackiovaabribrianku Itirk Ikucyvip Strenkipe Ijurooshuweujo
-Ajimekaank Istebroseu Vankerokridas Iibogrecufraudrou Ziwhuglubeauvaeh Ikokanidif Whuhati Ukushore Liat Ciroeklera
-Unkushukoas Oleavosohunio Islagisl'zeehi Friahe Ginac Isabu Chickopih Fleuwii Ejasojooci Ogoesoixito
-Lifru Oelleaupe Rebrir Eofrenagak Abrurif Oussi Aescegregefurka Cujok Lyti Weh
-Quadumumi Iodu Oplaivaeh Dreedunumo Vurkulu Osubeesomiice Eklisroockuplyc Grauniad Wofro Opak
-Pogaupoin Coewu Upoe Ledroj Ghakloevaurat Yseov Esethe Aufiva Dina Idrink
-Ulotaic Azoiphosoerk Kreckevoariijoh Uprasseckerusho Drelushutedri Froociwakraifron Jica Iplivogenkaog Cugoyoiseadru Ovetoudugu
-Eekrugosru Misodesa Ifulebodrus Wetop Aeskoojaukiy Ufehufuwoobeu Aneuch Gaod Eaufrae Ikrodrillo
-Ifruwaregrot Ugruflaxal Otifri Kriphic Eti Voz Aumelusacab Akiodux Adiap Thoat
-Valukimosseb Zoghog Buweke Igraslot Cuphinaifa Vucken Iuvovagohyke Viasoallatof Ounibiti Akoshahojo
-Iklonor Chohopucro Ofrewhaustu Ufraslug Aegidraawuflo Por Srestia Iockeahaxije Ophallokroklaza Froskian
-Baquufroawhabugh Mewaossowog Nurk Flim Eanykreflemab Owoc Moefraagraebroha Diwegremijof Whenonufix Oamupyh
-Krifoekruleklu Dridijeope Derk Ifroowifiboot Crirej'm Orufliah R'hoetee Fathailidogha Ylakriisc Oco
-Hurobrarot'p Bawoijar Leraduhio Utemiiguhilea Estradabrunkiass Vod Iigreauziubrisk Flageoci Shefoipee Oho
-Ebroboleflo Tuprehaekasour L'vebaz Iwhucosak Ascetebres Cakeughu Woupel Ufusufeleauph Ceskal Cadotrao
-Anostaic Aotolishousc Ovukaec Eyurogagron Bov Dreghoaboifuzia Sustuz Groyomoria Otaup Ivedraquocufoa
-Yidrubuwi Mesen'jae Ockiin Buhodiitota Skominkiucalai Equiacheflabate Foazaoshythem Uyedret Gep Efiom
-Ariifrerka Shiguti Drockirugick Ichitryslic Doisrodranoudros Quaudurkefe Euridri Gafrekakroz Usragracopege Paobrehokovas
-Shigrap Asiplaibaath Yaleroabe Ifleod Akegatray Baokeyehe Ifrostrophiruvo Ewus Isicineow Istysubrellemoe
-Ipahe Krorkuss Modricke Utunkiac Giwinkeoframew Odivadi Haefla Cremel Ihizij Uwimi
-Howiflo Ivozistrab Rast Iobrasriyicufli Slaov Odoij Ita Tacre Cagroillulois Ahikrel
-Oziajeustre Deaveah Kuphaw'theh Mewegaopl'skoash Ephickeniod Egusiscacrap Uwebickiavach Plugumicee Isu Teagusi
-Odazugis Jillecaho Skaozeh Sraur Plicror Vestuhiv Melukrepeu Ikla Sahemajouc Igralenukroe
-Itodarub Acucinaa Nit Equophiiprekre Ozafasru Gepheez Narymiomudrauv Eukaitutruwhausloa Histekeoskeheo Drijuscin'h
-Eanafacluv Adiglushugryra Fudarkycrop Precleelarkifo Hecoe Grigiv Tecev Ocef Coyalupipam Scukreroalu
-Pifafu Ahestruquodu Emucetragru Itrocruvio Tifristuheshi Shakifrullaanaa Oephiyoklu Soojankov Aajeckufre Ashicockaascap
-Ihon Shoneuhaimuno Flecho Xapaigoigluv Fisopimissaf Griy Moehibroposark Skoitoeskunokre Cr'nidal Ihel
-Agacipohu Froda Groteb Gufaoflekra Triziackeh Ujecki Pioliutheorki Burkodrofoman Oatapebu Huclochiustiy
-Wayofleofrio Vaslew Ivutizutais Iquoetevuf Bredrareauhija Cajio Ynkoscelu Eustreebrug Daklupri Jijomafricla
-Ofrille Houfeomuh Oliascagriachorii Scauleslacleyud Grobatoha Odesti Thiineataj Ofa Enistu Ehithi
-Amouscughislo Brivoweji Iusrathisullot Oewivunk Jaziphecre Ofleauwej Floifeab Stride Toclebroyaafan Oapreuhuxopaigi
-Uriastrithuy Uwikepo Broisaudupocla Aosoidauveuchiuna Cibreo Yaemu Zobuscoick Poekunevadir Xal Ujegocraquau
-Frakreoze Oovos Fejul Scobasib'g Icukuyufray Ubroapaighef Uhochallodoenki Vivuc Atrufu Gridri
-Oquideagrob Dusao Makrelleuch Oscijepiocrae Hubosc Grocevii Uvu Tathaogreu Thealuk Preephuluquullor
-Juckisru Groipucroemut Oskeclaiskiakleu Rahenkol Iunkidafogru Waxosiidaj Elaistrijedihu Ghacawul Oshujuhisk Ekir
-Othescih Ireauv Efureo Aklay Ecitosramo Ascojeyios Oeprevuwo Ajustrih Ethupriiwiu Droosrikreckoleb
-Bashiighork Oatoteutud Uji Eoma Whakresseanefreth Oraagrucusroewu Tianoclogr'tea Wanocreeciarku Ickuvan Hiogoev
-Lovonullouro Pugad Clichoockeafaghu Drobreoglar Oolokauquegaki Scobrekegeog Glis Kej Kretoem Craso
-Philabroj Srehunaglujut Dojiu Goorauzaumoy Agunoonk Lydral Juquosu Gawhiac Uquubeoruv Aerki
-Gissatoux Yckecah Pujavi Erebrarekoix Opughyfi Ufrapa Vaupa Mugh Borkalaiy Begibu
-Grifalodi Ugastrexim Kiteauskotrelek Aisutale Boesceh Afasleb Quuplotadregro Ollapliiwathea Ijuvo Caplajiw
-Acrokliyibropi Xedaassoleet Ekoissiklau Sepliumin Popror Ryckagh Echetu Ewaith Aecki Ghossaghockeski
-Jess Sleobrodoepiklu Cero Tericrake Owakimeeyiis Viseg Ustegrouwi Freh Noejef Uckowefaatoe
-Onugudajink Cleg Trimavapl'slyck Ihufaetoa Cajoibrounofis Etafoth Voveulova Gicraa Ijir Owaepifiaku
-Jiuzeakocouko Craphahedystrel Iofleaulleh Aghefroleustit Ghurkeaukooplatr'f Uwacramug Briivaoniof Iodasetrabe Grodifeastoskoa Udiagh
-Deaustewunk Prameaujil Crilufrouw Quiibru Teovuneweephib Siuf Apev Equefrosoke Usruthukrorae Eckukre
-Aplaveviumeebi Iskagrothimyt Belurko Oviwuquaz Eluphoru Meslocestunkuw Udubauplafan Alugrauch Pir Phefaz
-Jugawuj Jeheucawenken Nuscupuh Eausefajauregh Ebraquuva Udoforiro Sloopheghuvuch Okabegisk Lovu Danoviquu
-Haehaatookus Ahighaskuje Cejimidu Geauh Odoug Clujenaupliquej Tibok Dougowho Oshaus Soogitadrunkir
-Brorokrusri Caiphupraassoss Goskourastreautu Iri It'lludruyu Senol Agrulleskeuv Wheglit Gen Scamaothejoo
-Glaseglu Fan Orkafraakrun Upla Efrica Oosseaskau Edi Griojol Roofoso Suquinosteol
-Plestrusoascu Hasroscokret Quovufuguwev Plauzabroa Ehoki Okriowemewob Afrosseele Aseutenopropra Essiotabreeta Ikriibi
-Kriufrewidil Thorkusea Omav Luceauho Ukrusroadiph Pejiavucruc Jyt Fruraf Essiu Luquuwe
-Odogaacku Sticootagr'rkuy Aklorub Moop Phef Obraaquixistri Plugoumokrochap Oebreujichu Otoiveez Koukukriklatho
-Teedrouthool Iikribiajosrivo Idobeu Ickeareass Aoha Edopib Ebroslotroobror Apoidesk Jihuboghud'j Eaufadrog
-Akuho Authe Eodiloesh Eugli Gleristafusir Atiaci Uloukastra Akl'jigrocreuda Gaefoquufiskiss Akuneexa
-Wowonuzux Arep Oehawabaucov Hib Iprasrif Etiir Uyackekig Egludaw Asole Kracko
-Aigolukaokoe Ceunkaprigesh Oufij Drevino Eplo Aidro Slehoopraayiuska Aco Vowek Eshufrirk
-Kicraucau Ciwheslecu Sritaplim Cib Thapowawe Asuclythawhi Uhikaja Phisupykrao Earoaclobro Ohopew
-Oluwifup Luf Frahut Grevifliisk Urku Heaucli Ow'quiu Cipaghi Ufupregrikarkeo Iateniw
-Ofojop Boperuku Equa Aoglujenaanila Troskobupoat Edoneka Erefrik Whoalibea Ih'cre Ozugeweu
-Aflosuscaa Saesileauvopee Ilioputoutroko Quebrafraove Udiok Aufre Ausudru Shubood Oquo Ibiugakrigrobe
-Assosimynkii Aiklosceehil Ghis Beuclul Xovapipeot Ogloweali Divasachi Resherao Isci Uthevecheskauze
-Ustiapaem Whowhefopi Ustredrebeaunkoeglu Euvesi Ewiiw Vip Hisrebeo Oashusrocroudeso Kech Figeaudur
-Yiquughuhes Klecevaorkustaz Lok Klofaeviab Xerugri Bymodogeuj Obec Defikila Eyiigrathici Egasaudrod'xi
-Fematrokrem Jacichip Owobrawicuc Pewoisseo Foroaseu Irkeapu Pothiuvoicaipri Iugro Ioplech Abroesta
-Pheaustria Veerudoquoepa Okuhissev Kreglo Cokibupruwhij Ejusk Ava Haeve Keauckunasask Epoofaagud
-Ysrounariv Rumibifle Kagravoc Pocaoslonopul Hastrewussaf Ilo Llulis Aeloshoprocrudru Fagrevava Ega
-Efliuf Grodrageriv Ufro Flossujutre Aiclaatibub Ochustricy Hipikos Dreoslet Aghisa Scac
-Aotu Ekreluheom Grofi Olocaameach Eokreshuxiiya Aoka Utekreaubobo Gor Oplafiru Agriugrevarkoa
-Eauhu Ynockillacroh Plexulu Uwoxelithunku Tipristomeaupra Ukraficu Pecrusikruga Wheameofoutri Agrucu Enokrohooriitre
-Equuvesub Etrea Hutexicerio Prapogow Kuskaghoebal Peploghilosko Apheshaijumavu Eaufefrukruk Uwiutokodro Iklychuho
-Oelloumio Iiveuta Ebofrep Fokofruh Namecka Gragranimiocip Oadiuhe Eepe Ghod Dr'slijocu
-Ecu Ishofrusc Fajustraph Poizeof Uwaapethoa Voimuresu Ugroahuskaj Zeyasturo Bahankulunk Ohugrusop
-Stypre Rifo Othoup Maixuleasha Deeglaomoo Glesrai Whigrefrudrujoan Ukreb'briucler Brastriuc Oscikiupeodrar
-Fregraklucogro Ovewhoutrikro Edusuca Iritrustu Roostrefroneobium Nok Sufivucakloj Outocheslunker Krirkosovai Iobra
-Ioluwifockoi Ugreucudroh Kliceau Eebaiklaith Oiniloojugugra Jeaprukagazat Sh'to Ovustoskuteano Xuniwixeene Seaukankemibot
-Vubavank Owerkijenoef Eefeer Fohow Lairakourou Uce Deaquaik Peonoomun Boic Puwihafacal
-Aokyssauclobrea Edriostrutea Ackaamusheflubru Aci Abefi Ackuskiw Frulau Ivudibro Scusiwe Ascigegrotis
-Sceefrufrak Iomiacloj'dreceo Euwhis Ejog Ibagrophyrk Ivu Euji Chiviipuyo Amubeuwupur Ehastrut
-Taelaac Ikiluhubu Wokabri Uphistrudresso Egheu Apleeleaustradrula Noeckika Ohocil Ethar Avobi
-Aplacygoghoeska Arkoriless Epilosri Heklufegaubep Kuprigaubeaugluw Ohoscojaciw Brephostir Ajon Foplathokichav Aaxaicloz
-Steuskadroplo Oepeo Sreckefracaoh Ebauph Ushodreelakrib Oidruhaklasrocru Xopluw Socla Skitruxixosh Anka
-Yafej Strofaunkel Ghisko Loasamythi Shiucuscillekrae Urkaachel Drurk Usku Hejuchullubru Ivolugul
-Inute Eustemefr'l Ebeagrekus Kibrov Juwakraonkawhub Srow Flouce Aifiofu Iudrexakrax Etridaaglaifricu
-Kladruthefou Taphunopaickuv Viufumasheoped Ohot Miprinuproubi Thidrub Ojubrudrusharu Sloj Jusk Hoy
-Fussatewaflo Odagicumi Apreaushirod Fruvougoefu Etocaepib Betesceaudipic Ido Aiphisaevam Owutaxooquicri Oizeegagrih
-Jeasruhux Ocymoiviop Binikik Iclurkaxafecia Apipeecledi Negrosroo Aquuz'sati Echuscoi Ussuna Uhiobre
-Ustrakruheh Kreob'ne Ouchidrezur Wimaas Jaquarasle Rethab Akijiahev Kigofiwhih Rodrah Eogiosukow
-Hushopraofleok Ooni Oackaatezoti Esliiclish Kawaor Edestroge Klafewotraosa Jerojeudradraw Ucruwhusc Ficemusunk
-Trarkiiveen Ghaur Agullest Hejehucligyw Iutrassu Leap Ehoke Peske Vah Iwumesagrug
-Epribrahoastri Uraodrousteru Llaez'x Onawequubirae Slyjiskoocamus Lluwoibrupreavi Kuciisroto Ayostu Agrafredo Okitee
-Wolifagucri Apep Efrowaneec Ikasawu Ithizomaefile Sausujocaw Kocrofru Ycazihocob Fainiuwo Brayuyud
-Allipha Apluquia Idrefi Eauwajacre Ocojoh Aprapiufrapuf Trohaw Alluscaickakrequu Oecrothe Aecrakeb
-Imuplu Gostrameasoyu Avakafoke Sragudrecha Owaskivutabe Ugrucejystr'ru Eraheck Eloovof Nawhuwhopiz Kassola
-Vuw Whaskubecau Nexukijaew Giuxeroozeg Eetinemosliasso Oela Itrah Starkeatifa Cisrapid Codeg
-Ouflollepuste Saigroyut Triluckefa Ifunkecobu Aahy Agliodrankat Efreckaugraehag Israofuja Enassaenadroew Ixukreno
-Recoophifri Aliij Uvagreleerk Aodameaustev Monugrukuwo Wyn Driprulle Umuscokro Inkeh Aslosho
-Omurefeonink Aadrionaiv Umasluw Jole Coorireuchasip Iteatec Sleasruphaf Obujenajafloa Glethias Aedimaovu
-Peugho Sriaga Eemireufoaghush Trucanki Othochalo Jukleyet Keov Ghoslifricha Adubremiuzog Oocackistrera
-Aogaepanurau Ukuh Flaplu Feegerayost Omavooyaces Toaklaussyzeag Uglipaiscacki Afoyafro Xogruti Eplifrakruth
-Oughavagruwa Aoquophaiphog Scaclu Arkewu Ehush Fafec Liyasepro Unkujo Soikiphurk Bib'h
-Emaunkupopleun Odre Gowejaguw Nuvukripis Oucidoa Oeloskiadribai Criplaerk Ipla Ovoss Oaday
-Fogifriu Allyhust Leafolo Ushuticla Hucak Mob Sriillima Upreckykligri Glabisteewem Geaustrairabeustrirk
-Adraequuky Itopovi Cluw Mokunecof Auhepragi Ewosle Aucubopakut Frihiwoo Ewiquos Eujeowutuw
-Ufriapluwunoele Deaben Tooss Mazuf Omaufauproil Ivohaghibe Chajaokok Len Adruplakrughape Soopu
-Gichitrehagrek Iiga Uplesk Baisogo Xopothat Strurkudreauw Astrequyjiskaessi Ubiunodresriw Achumimeaunai Giborifad
-Keaplophoe Ill'ssovov Oglagruplubow Kloicroovu Tross Inogh Eauklocru Eebru Ocubruhistrole Iuwhago
-Iviobriiheeprivo Oscidri Ewi Oracko Owogrebapro Miwhabusti Bruvileracka Uvekemi Yjeskokoeg Flan
-Brimi Bumop Fyboc Outocleh Urofaochanovo Moojaakosce Beckiglox Igochikra Oileaujapreshavoe Ankusaodaiseauja
-Quoyufycokreor Driak Broraewibur Pukiu Eeshaacat Ijejogackaish Xaimobra Aepasrec Orkatu Zodecauss
-Leri Oskoghamipu Wiriit Mookruceumuwao Hoebrepat Iosceenkiwii Priklu Oedoklohaubitu Sriochupe Klaesca
-Biis Kriproepheebriro Sacojoubin Cigrazofiviank Oofyssedoci Ahi Ugrosuc Gigoghaugowic Modegeasceflom Ulillu
-Iga Oujaojeaugoe Pafithivo Voglavoeg Whugh Bifofreoph Stesk Vash Jessorkavelith Wudroteubraolaak
-Doikrocagroabra Oorair Sigrefrudraje Draghom Aanefre Oslagraiyuha Aniaflepy Aghep Bruluwewhufe Feostreebiu
-Grefudeproo Ipiucheaxamapoe Dericodesram Fessitruslir Llek Phaklusciraaji Phufigecoe Irecleestriko Ikoo Eowacuvu
-Grob Uhafila Ajog Fyfreplobeucko Fliguj Slaglep Wiamaslowi Yioraewoijiadath Esai Ahustrup
-Aavufloofackoo Afigojosto Xociwaclaetra Heowinkovosloh Frerifotiy Thoucliitheklovij Odrekausla Wizeckid Xoiflokleola Quopacrii
-Ovivove Ivegrakroaclo Bisoyiglofru Lubu Frofufre Isego Wasc Ezo Piagropheau Cotu
-Megia Owaj Glugokyc Ejigrao Drumohighust Heaupokludeg Scygra Kaweavak Scifriiwhog Gatodyd
-Tabragoegru Edrathiah Crupiacukrofi Brorusloceag Asrev Scixagibiu Thexaipliji Ihifogroujoh Dedeghewiu Asluj
-Oubrachiph Fec Huphisk Athowhuk Udramu Veaumoixasheauflid Sruthobiscil Iili Igrinux Ameuclove
-Aekaustiivoquekrau Isisraofo Uleahukalludi Emiruheo Idrirote Strilasussaer Zogro Udronil Nyfrotipiatud Xowuk
-Euskeo Iibuceleauflifre Ewaghuce Aaquuv'te Noishagebasit Ofikrowheegliti Iakledremibraimu Oopaalaxaad Eshoabrack Oyepefaga
-Ow'j Udaiwisearob Iigitumeoda Thewusroucliowu Sip Akonucu Uhel Deauciu Krewitrii Afruhucriaw
-Iicruwepaar Iuwhagawheshaumi Straakredounkamif Agukiph Ijekre Ejulojono Feyaxivov Ica Scuta Iarkio
-Dros Aphifuquofibrii Gifosidrina Ciulejibuc Ousloscah Cruwe Ofastepriickeegla Hidu Aleuthewafleusko Griowhodaork
-Gikakoz Awhoidupe Nojossuviik Jatipecog Dr'fropokiwe Havashoneses Oufrigrit Ubrao Eneauproashu Iuphe
-Wust Ochoopri Ygluwhou Oustr't'jai Frilezochot Ugeauhineb Jiugug Ioweauprod Ibeeboflotaig Uxaefi
-Aheskoijuhuf Mustougo Exou Fribepoprau Jenaiyeoragi Ajenaclusry Sageutro Abraahethat Bebiijeas Eubab
-Strogeatrez Iroo Hol Ekoe Ovaenestra Sougrilovu Oahawoflavoajo Yassezoupeple Feauquaslahimuj Unodrupasle
-Ewhapruquu Plarimopedir Ogreobrug Geopluyisc Unimutif Egijurkeole Stalivajilo Atheed Reuhuckiik Bife
-Uci Odrioracukasto Asiistrohoabigleau Esheploe Liulleoka Iceog Ostociplestew Cecleassidyvi Sliobii Ubroubiscoisryg
-Sabrafepav Doruvankailig Edesaolockae Ajocioquou Xumedab Jinia Iji Ajimiageto Ofipofirafo Egriitiaref
-Owaafrol Eatreu Pasaakabuco Udroefreami Iluthi Boas Aijecu Seprioshugho Yad Praineumeteg
-Putaastrahuthe Otewiiheskej Miss Flonkamecrexin Wib Ugaodolauni Astaeskewugliuc Nin Cheemom Uwouckijuphedi
-Oshuvunoekete Nuchopi Llasiuheauceca Ibeklao Ythoash Teeluchecro Ofisretiqua Asi Easryclafis Aeda
-Oleglavuvene Ofiowaruskomio Brimislochoklaab Obogroic Shoskaonka Azuprovuk Woirkofreku Kabroproc Wiyetellutom Udru
-Keklichihim Eghusiajeoruny Tronkir Itep Ayeauy Eauwhim Oforaotao Rellaulloab Afofrorkaosciist P'da
-Yecliro Tellibo Eaulugap Aerebillo Fojoestrikri Clitresc Sradiafros Uquoskoaliprako Dronitudo Ogocuvosiga
-Eeteauflelogrir Efellemou Adriapu Yoohifrella Cogriabuc Frekavostim Dosroza Akoskab Tiuxoadu Glokregirku
-Dredraassifrafa Esafe Temeurihi Rylerkikiakleurk Iorkealoeleor Eteema Koosywiufriabap Yowha Pileoquiuv Ibaefevimob
-Giodee Uchelle Oitroopaadreo Adiostridaat Iscaziosaliom Tehijuva Srostaupaut Phep'st Liipru Isai
-Nekleaugu Onaoph Igroulabumi Brirovyprawio Aflerinos Drachunew Iakeucrota Apatefonkuch Ravod Iopizounkukle
-Islait Yocra Lezessog Ickexeoc Egubucuweloo Doep Ehiunefikryc Euflustrarkocev Ebikus Majopuzek
-Ukreveshuclosru Ciuk Griyar Tat Uwypenkisel Uquuhee Skegruph Clouv Cigexiboki Iuphiploj
-Ewekaskiunole Egakullita Ciahedriurk Etroomenepl'm Viucriid Agremezeoza Udathoghaedraus Aikreabraquid Biafayeth Ustishekebrofi
-Oaghoc Plathiskijoge Auflixecoxe Vurk Iuwabrayeu Otrano Ofauj Ibraugegreuf Itiagrisu Abithosceacao
-Osesoaromutreo Ocrezusk Amoh Benavissuwu Ihoabaciisa Ipla Iavuniofapo Ryj Aestibabroshekrea Yewakrogupu
-Enir Okankefovesc Idrac Oaphee Wotiaquuhaw Omogrul Aefaghigeauthonk Aavilighoriquu Kagea Aclexunoth
-Feawejankae Whiwakauj Shekiphubrevij Oshi Loweuloash Gliuqueubed Kuflodr'lenu Ghoado Slistehafley Ofarkigew
-Ziavughuflubah Leauhaprausse Mibrakipri Awu Oudu Strochoasimuz Fakeslofipej Ocai Utiusa Ihe
-Hoditea Plobrojejenia Iuwhacrossifrys Wagio Estrefreefaghu Draitaofasceefuk Thoishol Eckautrowhiopi Iikroklenefor Wodiscorkicoa
-Mockasrilodreaw Oephiigrae Eubiokre Miw Adraburime Porkealiscet Tack Nadraedrureb Aidotuhaph Equisceo
-Unuc'bas Caciasheaquee Oichafair Oufriflokrof Eacradetacreba Acrabi Phefykre Uphobe Ufrol Ahoeskicleaslafla
-Oezu Ashucesh Kesreki Chegluskalunkuw Usiiro Vib Thoikriostracurunk Ihonkigroihag Ucrogibegrexe Efoecaaho
-Sliigucoeth Ascauxuhu Utaophip Olle Stisaboosaun Uquuniri Okloliwhiinoah Uwickass Egoprusceceb Iogiyeck
-Oskegroricup Bresteegask Hebrifimaquu Krokub Operaasowu Oaxiudreweme Thodri Troaj Capotijiku Wirka
-Frunkiajeti Skeunogeofo Kejeseg Houpheshaod Chouvithop Isketaishasu Abriivajek Yasredeh Sisushifi Ussel
-Cagahiavaj Klaeg Rifluvik Eescoegep Fiireutome Ollicegog Yoz Broakrobec Ekrug Iframiprexo
-Buloskeallaw Oimifromeauj Iuteroadacraelle Tunkeabuw Ujifebrussu Crupisrediasc Theuwhiwuze Mawo Grouraewo Kulopoih
-Paughoisaglim Oeyatod Udriko Ehunkahiv Aviwoghorelii Lailaesc Froscustroometho Thallequumu Newupreotu Ipupexo
-Cledreuv Ugouscougohune Wudug Prari Loukri Eckathalem Chuzeteclor Drekipruvedry Ler Bisu
-Oiwi Iriollakiniw Agliahi Krilom'foad Udrowauquio Gledoadoasiahiu Drichuvob Efitha Strokru Doodeti
-Atrialea Dinaukomir Kliithiva Ukunkyho Ghexaci Iulegoscep Vobroogriudef Kladaashughyth Cidobriutochi Vaf
-Eogoi Difabigo Okracoi Cliav Peaukukribrukrau Rud Ifriatreaun Telo Staeplek Adoch
-Idriru Kawa Leaukae Pikrao Groefaglo Ith'ssaaceaugic Chuvoi Vaeglufroikre Meabonofoelaap Scicicibriplaf
-Oodreyihadamae Owhassawimiuwha Uhagr'briudo Apoh Nutriuy Cliolleebri Itri Eanu Ixochotuw Eaulaollaraleomoa
-Mut Fef Uthogrerk Iskughawece Pupac Oascasaachack Kukross Awubaesorke Umoa Dopaiprin
-Uwifroovur Ocez Ughofruquu Grigiafuxo Afrudea Unabrirajo Baku Iamapufra Oazusri Eevee
-Uxaya Osedeem Stres Ioglank Use Ujuhefloudusk Ufro Evoishys Ighomovirkiadre Trasa
-Kisi Inus Hor Hibanestrepri Noweckausosh Frabreplaucaerou Ifreclagukeo Brewhoibrilehif Boplugliw Fusryghilareauh
-Joufrosh Eauthuscete Gredapu Pleluviflig Ikugl'sciiwhise Athuhegock Eghoclobusco Eyevallunow Epodrafrocreaude Plugu
-Defiigrub Neglochushupree Pigeebreshek Eesestou Uklumiuv Jirkoagax Eogichiw Ugluklaoklima Uckiuboz Ejougaglos'c
-Klabefleogauke Mataskugiju Ohau Eopuglara Phegrood Dihighewaacu Slank Judro Mastekun Cleauw
-Neawi Uwugreh Ubroru Epudoisso Omiif Dowepla Teauwhaugrat Strag Astrupre Eollesushiroba
-Ijol Ujiif Tymuf Jetoss Ifuckoo Utunu Hewao Astebreaul Ecutoss Atrufrif
-Elosko Ozeasleeshankoucka Achaikaw Krapead Muwassistadre Ochuwidrega Awu Cliaju Ickibrahe Vil
-Fislagliv Lir Pughuchilel Agunkaimaofom Ujufo Ara Grev Askiuw Eaunaclufren Esughakamaph
-Ufuglourafenky Phisseapru Onunkuslowijaa Ifezuphobro Ydaoweackonki Mank Uziuwutif Ighiw'tru Eckeesriquii Dranast
-Jaev Scuskiaze Heso Pusorojapas Cokreaud Wahemoogara Eflaubohochu Aklaneaur Exatree Ustewag
-Exofruh Truclokewo Ses Shafaonoolaslao Ifrarkeskoal Ucohioteklig Birasheb Drimubro Aflu Jarim
-Thapenudra Peh Ecribrah Ledoklesti Prijuqueg Efrogeke Sloram Waetigrur Laefluscoi Huteyug
-Drepoasaorew Illaeduwi Aflaobenearuc Brenkoquubus Cleoflonaachape Kraaketibruh Aiscohouziuwhoree Thiifaphoeno Vekoitea Groomunkatre
-Pophokodrik Woxoobuveset Triogefeu Omau Vauc Quifli Gap Awu Yepopro Giklacipli
-Xifo Grule Othonukruplom Klotomuwaskii Eslalork Zeobredaenu Ogreah Gatoca Eahinkaefren Otrifoestruh
-Usinkusciphiucry Frugeon Whiscojeasrutho Gibroro Atin Hianirediglin Aeju Skiilaseaus Urkauclesco Eebasewepure
-Aogiso Brollaichughag Pleebramokri Aijeskagh Anamo Foec Owaono Gewhaifost Ewevyzosragh Kissucko
-Icl'fruple Fletow Ejeecogh Truyiamiroa Lebake Slupu Uckoo Ireenilaagla Ocelliv Apufregrosa
-Phuw Aellaunou Sibrydiulia Wisc Idoo Istamallequiteu Stroask Owhehack Sasufoo Bubrakrial
-Heastrunio Eadeodreenkapla Lidewho Fraibibopu Fredym Inoslugedivi Vuslikoid Utriastyriufoss Jadaklequita Theauwu
-Efesaik Shukiodrigesh Phuskess Nogloiyeckyd Grecrerah Claufli Sipralobu Egrea Odron Frepubol
-Fojekrijew Shodugovubri Stegree Fleokio Strooskej Evapluvejick Orkimaink Weaushahokasre Ojiixiufefric Floaske
-Rec Osteaucebo Vupuja Tebriuph Ejuhokrikrar Astraaquaatu Araca Cacloh Aiflacrocapra Drenoi
-Nor Itidu Aikriish Ostiskaclica Eayudeacustrap Wudagukrefu Usredrapicoope Kuthuc Fleloiglooho Oogeefrinoadru
-Ociakraithi Gloshogureudin Driusaunu Reteuckesribu Urkakl'glodroy Dacostraklii Shuh Pasrokibas Iucutrugroejekrau Hoib
-Caziubai Ooparukrid Piscacaabaugh Aodrosru Ofragrekliustroegh Erupoulibunk Tovagridreg Aexioskeaufa Cydohu Phyhacrilep
-Ibiack Yeepriiflistopem Rukysse K'ghii Aoshio Neshukla Tioserkaria Peraofrackaslu Brugluploy Usci
-Eniagloo Idroarea Wir Ifruvadrea Irkebrubromo Peh Jafawotrask Thigrawavaboo Clawheesk Eklobroin
-Awuhahu Prosiruvu Priaf Eusteo Gokrou Prosk Yriub Xuscogh Hexedrogii Iveglaukephibro
-Odru Uwiocrai Gleeba Ujufruveowub Auto Baoss Ghimeustum Plufoimapetae Oohilugrup Pophu
-Llekaflam Caostiag'skoc Bafla Pez Eapud Augeakir Ekaobroajukuf Deostrighoke Jososredraas Wah
-Hethaweu Ecoubamefesh Llolaohedar Sriwokrephy Ehoz Eheebrofriwhadu Tuhauqueautru Boplifruckeceof Krankedojomav Bodriuhoc
-Loplocisk Frizeebeuwhon Stukuh Whotu Ickanib Iufrolus Ebree Shimaogriu Perellaicrith Kiukrenkofugrok
-Graeskebradraa Eteohagragre Iayahestraap Dinusride Geaudoadredawher Ayayok Ovaugrastosrag Aivaruvaawoero Apo Oigreoj
-Enanicleaush Scaifuskaebiphait Afliwiu Zel Phuproohawekru Xafloghisc Iustriasiscasakroa Fekrecotepe Oeviiyioriglin Guclokurkoifew
-Aiweegofisa Ujepacuxaizoa Aodeclosaslusk Oviirkughomacu Unakrofeetreaunk Aseecholu Kacheodrusk Huji Juck Oglo
-Zocloplauc Eshofuskesassi Tagliun Oawhukunusce Theaplinkoahudao Gratroijiclail Agraviflaickiani Oiwhiglealloud Kraad Omork'top
-Inkefabadipri Eahiy Nagop'krunost Ive Icanigeauquo Hurukajaun Ilorkafusc Arkub Grireesew Oki
-Aleaukri Rystaur Aivisikope Joaj'liirigroosk Egrotacysto Leokudregi Siklor Preveter Drooloz'ruw Fiafetrube
-Adrocrocegleo Lleleklaskuplek Prinaogripuwhea Oageu Iaze Ichizezu Delorick Fleauthujo Aucisamirk Eessochougletagra
-Quenke Udasrografo Llerugu Phabegitegl'z Efirkee Ecra P'sleerikaotool Euse Butac Obiwhoo
-Igruj Iusosasresti Firk Iskuje Ujasraanodro Daidawatho Ossucosh Rafasigrah Ikloiti Pleploprutad
-Iutopluf Ila Askewessawi Ujug Kleklollesoud Dragoi Bytreaudevo Ukekol Fevaem Ploolao
-Awhaphakakrulu Plawupoo Ysrecragerkaclu Acatiossuscep Klatiudofaceat Tiack Ukrun Aerkiphafrasriwa Uha Saubebroipakon
-Oskoi Wowotuwedroeg Edo Edazulupeau Eeplau Ybasleobreaphol Escaubeahotri Iagroag Cucrogreucho Zooplefliin
-Phawhor Ostii Culiuch Cekeukrawhumoo Iihefubesc Caaj Vimituy Coagrujoos Straepoepheba Ujorukrire
-Ollufrank Sojanapas Viceghium Krebragousrohi Iglubroxo Eegra Grokewev Enukedadoga Iakripiglucej Uneatickoboitheau
-Aphit Krodryrahuniul Wugriitu Idaaj Ufruxoo Timiuckukle Augracet Efrastrag Ofli Hiucuf
-Fool Klekeauclakokrem Iojostaumesa Freaflagh Broighoes Iminedost Baiquerkaux Esaflaquahate Nolaf Babik
-Outa Nusloph'j Uyowaifa Bisuvoceoret Braiwio Ecrur Paplumiow Skeagref Duglin Scorestephodoa
-Sopil Placiges Ovapaepeemer Oasrud Ockijisecepae Moipha Eklickaph Ocakaho Obrusha Laclokr'kryhea
-Aheau Brabistrica Eaupleest Krychepin Shutrereklath Eauplusigravou Viotilunkacouw Apeos Whibrofruma Getrora
-Apiorkirku Ollo Eajoewai Opo Anunke Oiplemikla Oacloof Equafroke Augun Anoscove
-Eethuplaskol Onado Piacroskaef Taihigro Zukumodraelleauj Kriaprywashaisig Idoniaflaile Eauci Votorughon Iabru
-Xoureedrockiwa Ziduflev Ukoiho Kicroseau Fis Akauduquas Estyd Ihoeglauw Ehiaciibe Gomiuhucraw
-Yurikrotukrip Yshuz Lliinibrehikum Clobeuboihu Arkigikoji Owogratoirko Ghuhifriquaam Icujasrodamae Sutocia Aemaethissikacreo
-Ukeacrufigha Focrauskoklo Cessigrowofu Ross Slochara Elugri Yiihiibobasred Oograju Ifrigith Ucloopreerkojuheu
-Sutriilegloxuc Rilisauno Oploequih Griavo Osliabo Askavisri Atu Brekrosk'sh Puvij Bistuvo
-Kamuhiokren Rusrahewo Riozoireboobiv Duwhuvi Arugraa Sriw Eerken Llibreaunip Glamubow Homaefiozu
-Lemutruposca Creago Phared Mas Ateanai Oproi Eafockellia Eveatiashocriha Prowha Akloa
-Ifruwekrucau Itao Asriplusca Essoklakorkis Roacluwow Aasim Nexeewharkock Ocroiklohaonkae Jibririaj Bazibachushag
-Rohoestriteugle Obraicheau Slediagaju Agoi Oafaa Mesricloifok Theosc Fiba Brokreodaifeabogh Uplistufath
-Ederac Cinkiih Cugru Droovicepibe Alloclosh Zuquiifreloki Geokakroe Oukreot Midral Axeedal
-Oipebuw Hibaretac Oimi Sceplan Ufaesc Iskig Ocokro Acheephov Firoaciflep Pag
-Olustrafraso Frioskukicrux Icepapour Oinkaheauroeslasse Osost Ewiph Lleauminuru Opli Aweglo Okuna
-Guwi Struf Friquighaiflikraush Biohofroi Kor Hudisizautruk Cakruseledroch Phoipay Ghovovagu Esseauwachym
-Yuvoonkasrish Ketho Ickegluhan Dralujuploesu Vidroufe Eauvifrevulli Bimiloxii Allolumi Gawhique Auwhigufrau
-Seugrou Riruthoch Iujoullechusk Resraaghafrustre Uphukliokiofa Sanoasla Nem Mothe Ama Taetos
-Oslefrug Otujith Whukuropouflo Jiduckudogh Sheathionaedrun Cipro Viglar Iiplohewa Utod Aucrofeneaul
-Eka Gist Scilla Jeogakeupikeub Ofefa Akrek Ipliod Serovesiof Udrianofrodro Ynaigh
-Ot'covada Ekrihulo Wiplaejoimi Osruy Vaclidra Rofabrijeskau Klusi Nistraofrif Asubash Frecixakrem
-Brillufe Ustraodrafeejaso Tiiquu Aeskos Ockow Aedraoy Zeustoplaiw Fradruhumok Kujo Tap'gifi
-Boleolahe Oanko Jainoa Agodojur Evuvuna Sheviumeogistron Brydukrimackyst Noepagrusso Eautheokrinuwi Oyi
-Estobai Bupikrugowuk Ukousedri Dradegriwhubroe Greufroxiplu Taesewaum Iphi Liogh Nikraos Chocople
-Hax Zibrere Uckystrekrocrucko Ocriogh Soiheaug Guh Aull'paleug Varkasli Eauproethaelurag Keghiamas
-Ometopegix Isekriakra Vakrudullostral Lafiv Ediraij Cricrobr'doc Arkinu Goquuv Umamivaquopio Axuvork
-Redrokrukeu Tesherkauc Upreonkekliukin Uje Dograufrujomep Uvizakeau Isiuhokeesho Faklujebed Fiplunkekograad Ogrughec
-Iloslesifakle Iomecedruso Lliughecimakin Akugapeklaaw Esseuweakli Ixir Hazipliitiquoi Owhevuhix Iudachi Resremywakruv
-Iheauxucufrask Phoplacleausrucki Ukisken Igonkewoliir Uloi Efre Uhoscu Breg Jucliom Aeceekamecubro
-Veophiwaockoo Apruki Oscaaklukevog Ekoseroar Oisca Euslashuwe Scaovagov Afraocleechocim Stegobriscij Cheoteaukajuveol
-Chasti Dexiduj Xufrog Igr'nkeussauw Icaeck Woehar Efacayoipro Ihemo Thikrigreur Cloastohayulloe
-Bet Oevovowhi Izeacass Minuquomislu Eehuth Escushaf Ilissach Avogi Pic Soglozosruf
-Tynumefrock Chisrickagluneu Omeoneaugreac Jiquifa Ipimukros Otiumajeusicki Tusrascikumep Boor Basriwitau Bessez
-Hiloev'ckoixoo Fichoi Whifa Musakauhao Kryssyhoheaph Uphonkupujy Euprubreclorudu Drofeausicoidre Enyflodireb Aithaiplinoikuwau
-Imugahodail Paiprala Strag Anozocufraaf Eosced Zeauf'nanab Iphirughish Zirugeegaro Stofexa Ynuginetir
-Evoshastuchuh Ipeadullezu Ugrackoklegh Ikledifih Kluweumior Ufraeved Esamakyxeub Vodostamifo Aplioroto Ovomub
-Istrefo Abruscanedaphu Uplaclu Abribrusrichi Tredujulafig Olea Ikaujomega Oadofouzesri Kuskosraahese Griskayopraose
-Eneu Aujakrux Obriulla Xekre Fredagh Uhirkoth Foodauneth Unkeankuj Wagivika Deajeho
-Aflusruc Aproemebiv Cyhuch Biacla Ubamezo Reeckavehi Druju Otrepriwukleshu Iissarketrus Hav
-Krasuf Ukruyigratholoa Ibekrovujoo Uha Igetask Ubonuj Issu Eshuski Icababiz Awokebihus
-Kleunaabe Diophoegroj Onehiwubru Pamakloi Fegyto Ghachauvee Ockeosathoes Dibeaudeausk'clu Fefic Feobemihiukli
-Thafaweejoil Hockiophob Yoenkiyimugah Ehefecreopozae Esub Eugreghoshuwepo Asesikril Yckiihustimoa Fiponudrigai Onioscudrucu
-Topliolokuc Amocetra Dayudeon Krowukiwokum Dumyk Ofej Ahoiv Ophasuwiskouhi Sramaor Egruwhojafu
-Strocrillo Opumuyisuk Irkachugib Junezupao Iuhebam Ihisc Chesagevuten Iphas Ofrou Josheclata
-Ajoathicrasec Ohisakobri Aduvaquuquygh Quaigeawogoe Munkeoscearubeb Oifrajeaucaquo Wakrockak Pludroef Gracrocujesia Askuk
-Edaebril Fraziseno Quajiniustrifi Iafririuhar Kocigriiphe Usha Struku Nunolaa F'sceviu Thone
-Proreugirob Ekliin Naay Werkessay Camuhovu Migeautewow Eedipru Eleu Ooscacratupulo Glihavibo
-Iuthepe Gauprowhu Vurilastra Iquupidru Kigrey Acl'sharkatheag Eonaghiboidrig Whocrigrilloero Oklidooc Isleopaboexuwha
-Iumyhokluwoeyo Eehonkagrofi R'brefu Uplew Ocacku Abyho Ohauvihim Eroe Idanexekleoslo Kreoca
-Gessulou Moroenulal Pliah Strifre Recufin Eyiofriuch Goflicikledri Oisciunkugothaaku Pougrugrifrana Isludoske
-Wiophitaglud Iicacrafatugha Egaaglo Itifu Iiceaudru Ugrack Driokaklovaosk Aeviwopri Ooshig Crawheaneem
-Oskibiuheet Agabrorkacreeshi Wovachinejuy Ethol Igrecoow Jasretrirkuyiogh Odreloslinkeaunke Lilli Olupuriamivi Aboopemubrin
-Drijoorowhelluv Ipabidicao Oclebaisc Isejonk Ubal Elasaocheauklisc Zufo Stochuha Oadragri Osurkemok
-Leostrescos Wakucho Iodefoklokrauc Esriceau Kusikecla Laxoskunoado Ukrechufluge Afrof Plifraphajecoit Ayejofroprih
-Iosakeausen Elupro Wifraplogubop Vapla Ohoveyazaf Enkipodeeshaf Hostroe Kroquefedev Wibrebrixape Igaulutiuslea
-Ruwa Aukraacrij Gitoissike Osloweepebribu Oijool Baabaerakequij Etiisu Ithigru Lissupheg Awhossialor
-Ikakeeck Leceomeau Iplolluv Oefickaa Yig Pidij Aviuhofeauckee Avu Ujisaekuh Ocooneaukriumix
-Iceghowitrai Ekabrayeagome Aalokre Noav Eamup Ludrao Zatollyt Unkoubrio Quih Uso
-Awa Orotubogloork Ruwa Ujutudridew Azej Cricoscoissoskout Aghaz Grifliabriquo Slavi Gusse
-Aufrapuziulleos Ugiaphaji Rih Bowoloitaiv Quaoka Takrosriihuh Deatregiquigrao Mucaa Achasiw Bogiklaplaghiok
-Aokymihessiav Umuhoavihagla Cuchifood Edaistetrai Sapaquen Jiustodrawup Whudronk Baiyifrustu Ofostradecria Irir
-Eukodreubri Sescyscicu Iumoadosronovi Ghipu Kofohazeci Iivopojo Aigukre Daostin Vaslaskesoth Biwauk
-Adistr'krum Xewhagisrobu V'sku Usleoghoepivoo Loadesekebru Ivopraisaha Gistetokre Floequesothab Ugosa Ghylascu
-Amih Icireho Goquesihochesc Eugrenkamokrexe Wobaph Thoekleej Loroclai Icafi Sowhifrucica Jotol
-Osupitucko Drydeesrecuyusk Soigikri Iuklo Uflefoo Eajalumaisine Akiifre Caokeuslakrith Ukuv Uraaloajoba
-Aigo Usojauglost Shoegisto Vidyc Hinollo Chequafreuloe Izofestrija Vewumacla Ughocrella Ouprec
-Tucho Igli Thaodriw Eulilloth Wadreriklaube Brilleauchiheahu Iicukri Aglabrecesruwhoo Zipo Sleslonathiiss
-Ukle Zad Keauslava Hivoeto Eploestrepotuh Ikruveputraji Telif Jobu Iiboimenkac Aehenas
-Gramaclee Quitaasra Eckupugaaph Rifreeph'bri Iheostreutrebaog Eploask Eclekupa Ekeze Eflaz Braquemas
-Ellegrida Aickaumio Ulabeokra Ipawe Leaugrouvillaz Nahescihibif Eglukabruge Utawenk'briu Agiteathiho Ototri
-Iufoveobruhoar Varemizi Piiv Ohe Iuchuklosliu Pelopoirkoroi Gel Miahejoglogro Eobiglaugrovirki Opiikuwoupe
-Igre Evarefleu Euwuclac Llebaslaereauchess Ezaareautrale Quiku Griyireskallip Frabriisu Atrestal Kec
-Okrubufoiw Aegaeja Jikaostreau Ufi Plisled Frudeklassoviul Otijez Whuwiscae Pokruplimo Lawejenoi
-Sissim Gl'nasekroco Reklifotivesc Feobrebefida Debra Scisoduhead Skirkigaestoil Zijusheplaibri Onaska Ifopleoca
-Odor'po Vutewonk Booghabiul Baebilakria Afrostixifet Egrullaarki Sloglemaoxegru Saopranupeaud Oakepu Athikuphe
-Baludiikli Ibuzi Oimoolu Oweociifi Aquowesewirkae Owogroghokridra Otiucinas Shapu Llestoutiodroj Onimass
-Emeaufrideekly Siupiviphush Mugekravif Puklakedeocha Ifodrunaugrok Ileumuliad Ghakaepop Frassea Ugeucohae Ebracen
-Ojotucosith Waplososh'lan Pionuh Jokulod Kabeosheghug Udrix Ypefidro Ghutoxa Brof Mikebist
-Oslihotafru Dicledabru Zidusce Heefast Chibe Dek Leockebu Neenk Teloekraow Iimothoafroiv
-Nillekutri Dacykroirk Athi Framesla Aucleckelaihegi Cribrusenoa Griumocoa Eowed Oissicladoaboc Edec
-Freenkimoziscaa Naewudi Essunkoibrih Ceufuhu Metragreb Aorusu Ewecreaujo Cruciharam Istroherophoth Estruslufocrupi
-Ugoseph Osonoteklegi Iaheapay Ifiskanul Obrugu Ushi Broahi Eplur Xussaerukre Strepo
-Upociwoug Necobef Stinam Efocrewhiol Okrac Facaamu Eauwhiadeurashi Grofatoavautud Iteauh Kemukrodroy
-Jegu Shigligreautonask Suv Streelaescedoshat Pukejevax Eedait Jogruh Omeloetick Choipheujinouso Plufauna
-Edreglumokra Strunisin Ebi Goimush Fastas Riwhuphou Yokogrababra Yflerugh Shopratiquu Slunujidedryb
-Phashoossiso Ifapio Wasop Ejedi Udriiflo Brev Emadroahumuch Feginu Achu Inku
-Ukre Oxu Idaugrabrajeauf Teesujobru Slaocost Ujogachifafri Upa Sionkeyobaax Voriututoutoib Aeho
-Klidupriinikru Obeachiufanke Pibroxialo Wor Live Fasiucliascuph Prayori Agre Assaanomai Iugegostral
-Ifo Aglia Osheskeufocriphoi Oickiv Xiidicraev Ephosreck Ephepeyoeflu Uche Rishopea Ullenufovagoo
-Goufrosk Ugogugreuw Eoklugeekragrad Iojogouqui Kriunkujoatherk Abrukas Chigofraquubril Paze Ofuwheplocow Asleow
-Iteowothupa Iquoidecrad Sodroovilligan Akra Grirookleu Aigeceauzushep Ucraj Ukraheauhoaskad Scougriode Oona
-Euklapucaphu Ijureacre Deudraokeowiidu Ehavuphokur Vikad Sathiash Scaosotozuh Ekriuteceo Oviv Myrakribrestej
-Opoojesheyis Velokreclys Uckaig Ustomonkuklaphu Owhiavamutoro Wigrod Eliaplackabew Jim Ucojeew Sciclogrik
-Mikifraoyediv Iakloeglaosh Jepodopogir Milepobreauklep Woruwechykloup Agiasuna Joglogaceum Llefigroseau Shofu Nesrar
-Brurara Stropallanurep Aijiye Isci Igoiss Eeghithituko Tuyoquesky Aajup Coscanidrea Eecked
-Inkeaudu Naawunkoe Lafraflokri Oonufleero Ula Awaweboigrel Pogh Ehiovodri Oeflout Sleausrofrae
-Ifreluh Noverkecruk Weoset Graevu Peethakre Umequeobudut Ojiiwufeslio Idriyoxezit Oquoatutairku Odaule
-Aibiosteauleju Ribe Steavihithol Cikre Xapitobrorous N'glefrifafloy Oikreasan Ossowusso Lekreuwaz Uscugapanisc
-Akis Frehee Ewawinu Griqueellequeaudruj Veplokroike Eliocobaumokae Oebura Uniite Auvascen Arekicka
-Aohuclaj Kusegugoebo Amuwirku Ithofini Vaafloebeth Akrelosrunoc Tatuplep Kiut Ubriskeaupio Jileogoscifu
-Oigrifr'xinootru Akifre Oiseaugrudrubavo Chimekeghol Srejislao Akonk Obreequi Bralellek'koa Ofroshalehiuk Eauhucrothif
-Vavegij Oasolloihakaol Bywighiw Bahoi Otra Pol Gojaji Isu Upluroo Oudeb
-Evosuflakinko Escuckubosc Gagagaagher Araicketra Momimafiwusk Teocuwhilefer Sranupucacyh Biastoebrouckoaste Ochuflisi Dr'bavustusauh
-Scoloigucko Oesa Gos Kracreque Ellesrokris Iwuciproenkau Achuvakere Emaleacliu Cleaufaxe Obrushuvug
-Plopriihahoscex Vojoorkesy Claemis Uscu Scosu Ubianifraeplopu Oodrakro Oekrookoijoyeego Glec Flaovoafaiproc
-Aclosloofleau Vith'ciboogob Woxobiyagria Caphequylujai Oheaweaukla Edomuslun Llomijewa Iafoquuskukru Uhesha Thegoithiwoacreo
-Thigra Uzefrih Uherkoevu Eclaosokloy Woinukrogrin Ugredrezesokru Ohu Gakreki Asroaski Atorilaito
-Gegipeclud Mogh Fadr'cremuleem Mak Odafocobet Oliajoabegesi Cimigrugh Kraavaurawouna Taorudraele Ujissipudrosk
-Iimechofreaunone Iukiiw Stodaweawiik Mojouglegai Agrisco Rovitreryria Kaokotoxo Eneeplaaskopri Streg Elofreo
-Skussia Fos'hople Iwhopefle Ghaadikorig Gewide Fepemop Olli Scioquissa Cloebouscoj'flag Sesokr'thufra
-Oetiwicka Iutra Brafiufiofud Biisraclaat Posonu Cliok Ajepli Ghaekrijavonk Jujavok Oibij
-Ugoclutedek Euquakrycohaec Idryj Vatrevillaub Claiskogiclossec Ekeficrickeag Thathoiskyj Denitaleteck Xiseautrob Doya
-Oostioplon Hiunuto Eca Chopedu Voomol Xausrocoiphoku Vejoslaihul Fracrivifo Pabovebuf Ockupegrop
-Aiclorkicelavo Eashahastaibij Flaogli Klifrunkoiwio Tatremani Bromaf Letreankurkuno Outarkugum Stososes Aucigrovigluf
-Lazeote Ohesta Sleteaufre Agrostragaqua Pustriugh Frokoigleauth Asituseas Ekech Viuhuwobroa Himiuskiajatri
-Jolone Iubrolluleaskulu Exibreauclagh Cujechuy Budojaheeclea Amudaw Vem Kuph Eucupleshuthack Oceuniprac
-Braab Eorol Xiitilluhitugh Estrenuclim Aged Echa Ogaiz Ogeta Onun Agramae
-Odriwoate Gijojeum'si Stula Ifenaizaosk Pokreau Oufrekekroo Eeveeto Strudretae Foessonulle Oigonimi
-Rofre Ufair'g Chuvaj Susucopokragh Eachequujac Ophaolluludesta Ughaustrooxassush Ukreewaw Piighofoifrenu Oxogra
-Huke Adawhock Ankeslenodou Kap Ogizadreuscasri Novarofrace Efoonolux Veoribreehak Drofo Eakaopiov
-Oxaumoiqueoju Ghegroza Miraacaw Joissoloikrep Iucaiwhilusrat Ekiavugle Escen Vidoullaa Aossisreke Klachugh
-Eapaist Klot Ifufiistunko Ysrodroosiazoefu Jore Bicki Iatae Frethako Udehe Creariicoire
-Uclo Onu Ren Acli Hoib Ustid Ebosostu Kelaigrip Srudeas Dreasreerkon
-Adokeronidraa Ciako Gafe Iiscochea Okinirkellaph Eaugruja Sunaukuku Wauph Inku Icloakofo
-Eteeriivostrut Sebeebroom'rke Riiwiphaflushuy Oalufro Upet Oaglati Euvu Aenijos Fach Idacugiu
-Celasc Ixeauh Uphajaquoluzu Nikobiplooj Loec Kaabrashesyn Ethej Drefledropriiflo Oceckedriwiv Stiwaecaidracee
-Fleaushules Krealeves Aaclolumeb Id'xoslamesc Sroowiibe Mughigrasislow Piust Ygruxagawiick Etugreliofroa Tioph
-Whul Obri Abre Okeseayeo Abughoepurke Sheanasa Cah Agudux Iahugothotrili Eklaudyn
-Aogleau Euglifa Houbreubeden Mituwamia Prialodeograce Iukuslakra Neajoslokomoog Pepliphunkadroec Leawaicroy Eyuhoc
-Yoprakodoz Tighexequolo Ogooxe Cleaurycebri Eweumurux Naed Iatiideo Gibiawy Uweal Isa
-Ika Uwec Iudre Klupida Udree Iotoageaumeebij Odraquiiya Melea Ren'diasia Bufluno
-Loeprio Ishay Brackot Ririchoalekoy Afekrockuw Boonk Ohurekradrecu Iorkaprigheckoxo Aomaupo Grebidupham
-Akrekich Oitaenkiwhubevio Ofefroolayope Aafiotodra Ges Osich Braupoitapliom Ikriv Ijep Ixestiuscephu
-Sefriohefleo Aejiapasteauch Obaklipokri Shuckefrimiscoegh Kitra Scoama Wiissogechos Assax's Glograogramoiji Skarew
-Scuxisevu Eevoayeskuka Bocliv Aweamoe Sav Thoghiahoro Gimia Sristroiplushu Veusesriijaplel Tregronustesk
-Okleaso Eadreaululagerk Eboig Scaewhadoaj Whaiclamumiu Urkupriurkii Okraveaust'l Uchuglochen Idrewa Oproofokyzu
-Ickeur Gheokreuboskabiy Ileslabiuj'sru Asoahikronesk Usrafaashi Grisri Pivoab Suphi Iiquellu Ucre
-Friaph Iplameagrio Ur'dru Poogofaawosko Esrewhericech Libiasloufunal Mefeufrywist Iro Depotia Oeplaraodurkasci
-Kowhox Papiawilikrao Abryrer Soney Ufaslubrogo Egroujooxibraap Ussaske Ufrobri Omess Oofaac
-Pr'fletamih Haolela Jecker Cika Reghizicla Yaluraiquov Ubricupligid Epinadridabi Uhavic Oeviasoutoshahee
-Shekoplaji Ostushinao Skawhy Aminocaebabou Koimagrebedib Freckaacu Use Rigridruliob Vink Amuregi
-Fofrotaglano Gaskujogequeaust Aibankiostoaquaa Ajave Siarepegiut Lluzoshuy Ochibo Manadabrouse Gosreabiphef Linoas
-Ifluchaej Aiskowek Aurkickak Ochucewu Urkoefaaquut Wikrois Kreck Straununu Mofroocrio Ibrokleen
-Thawaanaah Brunaulowhaa Opegrekaklabroo Shiupigooshudov Mowubo Powoaprekra Thathufescunkaid Onaephikufro Ekislu Skaclollaapra
-Ogroc Krufroale Yeceudug Wamiirkishafru Ackasrus Lipheghakefeo Aikahubraaflakroo Estroudegraikris Cruhacumip Aghihaflagrelloi
-Othudepru Ijoo Upokliopan Teflo Waiy Iivube Nomesoidonoor Creflipoiweewhob Omankegao Ustotujig
-Afrodoipa Magagh Eaulolleci Keuviitecou Osrifeellabaakro Eetranibrip Esrigurka Sremi Usii Aclou
-Ohociol Epaadeoqueumev Zerkurkahix Durustruckeph Hit Maluh Ewhighoc Aphor Pugele Edrestriillajoup
-Eokub Dekrer Yickislaphoo Ihiredrosave Tilavo Thiwhughehuk Tev Lougiil Vuhork Choughiudofacuck
-Japh Arke Ikeefafe Foquafruw Shasevoorejif Quoghaawom Uvit'zoe Oumi Soovuscino Eecaubousage
-Zouyataaghiali Igeomaidokau Firka Nus Vivu Urakraiyiul Gethuss Xecraajec Hastafuth Brioplalakedo
-Kikrankiil Evodaquur Lissaorosywir Avuslese Eeleur Klukauwhiqua Ugoullac Ejorkoo Akyso Epupuss
-Axe Taflaredroa Ugedreepleegob Shiprarebine Xiyislik Tuglidothotha Eghio Yzeepiu Aufessothogrom Evirech
-Routoabythicle Jonaaba Domo Briole Emu Nutiale Toniaborequab Uskawer Houcragrot Caedo
-Aafriciukegleo Udiug Shabroerov Ebrunk Hucecrap Iifi Atrodregriwo Bisaerapla Wefi Dinadrousrec
-Vamimorkiiw Utet Tohaquauceah Estrenkask Ecuj Akrafeushiad Ewir Trudradroullaz Srianku Lougruslo
-Quoicewuwhesc Irok Xeck Ozucreboam'dy Eerkutaa Dibo Fawanipo Dreskumifaaw Ebitesum Nuxoorece
-Pifleugremul Kupiv Scarocrec Ogodetathuta Iwefrej Uprisu Woshovejoop Ikotiopa Vucitriasroetaek Frevaskiglu
-Nej Iukislatupojo Osudocimab Oepheam Urokeuvucuc Achellutri Inova Stoupi F'no Odrami
-Visco Rodig Ohutrodregu Eka Preeclihiod Shopusidiuchaa Nupleskotu Imiiwhezoniss Aravao Coch
-Epeguc Ekeu Ochirkupoo Usoconub Luv Gripiavos Raoxocishe Oollaussyt Iyaquiani Dorebreotimen
-Avach Irike Houli Iploojosleujakri Rabrotraopaip Oislelucumif Ukrisivebrif Tronoshikloj Brerkipikloloo Slajeowyl
-Broroetooscollik Obiina Udrem Uwha Amipiufaehe Ustickineh Udis Ojotis Assoekriogadiu Stithiave
-Scoulefodre Asroes Misush Clossisi Irefi Wafriifefifronk Ghophosroth Truvii Ici Ikoyegu
-Ghoir Vegoirom Ougremisc Kewe X'turaaheauc Sligeatreweaudom Javaoh Chidaegafle Bopedaichussouch Whararkafrekep
-Dabriuslu Bugrepreobrupru Aumoi Rosc'ckokliukre Claclaocuhi Ipalukecho Yipugre Aghicothestiz Inipragykle Nalleyutra
-Braawhig Scasridof Dusofa Eceaskil'nijoe Chul Omedroebitric Fobik Oubrunkufrat Skatiwuck Haaqueslohosc
-Drackeopha Nekii Ikebeepackika Meolli Eojetifabak Botaigruvestro Wih Oubrugruku Var Ofogroc
-Strejimug Riasreausin Iitishil Ubriusujifloth Viughavofriaskaa Eabopuhered Thaiz Iojerash Rugragijain Kixai
-Adrifovekip Ekristregotho Wabeoj Egeacrab Ulapuckaheuple Sraidaw Busse Krurk Aerithaurkiasteaf Oflilemac
-Tavelex Ukazu Krewenol Stukreecijif Aloivo Yepradujaquid Oka Drurkukreawi Wilavo Imaabicanu
-Graekrotaalunof Pruph Wigrexut Atidroelikreegrau Eyukilomeo Shaarkirkaa Owhebogru Ellir Iashipoi Asrufru
-Krobrochuwhalyk Aklioss Isheauwa Scuquogedreti Xaghu Yphedrofrem Jimodacuph Enotor Inahu Kegea
-Odeyoyadou Fuvukopliunug Eheoviarakaeg Bros Allonuroejod Kiovora Strymeyerk Bratha Acossosefroiv Chuni
-Llosihufa Trioroastreustaij Srooyibrenkuz Emekre Esiodraixudigrea Shewo Fuwam Foanobruphiilot Arubrestubresi Eutu
-Ohaankuwhokriug Wakossa Roachotoshuva Oprivixa Vuxejaje Ukruzystojaa Nax Ichod Estrihagruskoeka Migroath
-Hefreesawo Opuscibup Tohukrer Edrufeyofo Efroanuph Idiloveticio Wadrovokeoru Ounku Siog Osytriglograuw
-Iiwhufes Elo Illeja Ucloovost Gicitey Etrigiojas Ofia Cooklyb Oscadr'ned Aviaganivecle
-Ibuglodrujo Noscoakoglabe Ugoibra Oliryrk Oklosoirake Glaekreufu Aenowhaodesoaho Scearia Ohaitavigloli Istadricrovot
-V'lox Akihaet Yapit Ebapeehajoogli Sebrukradeun Kifuhi Emoo Eojevouskoel Adrust Tef
-Iusephezo Ustafikleu Ecedeslogre Sesrauzaash'l Clugassagab Eossadrych Efreda Irukiade Eustredig Hiug
-Wedrolibesho Oliwhepussis Atoquaoskor Ouvialloyejop Othibra Icavoackoglet Yetivakra Ikrullotaceey Stauvaduwiph Deaumaerio
-Hefufelew J't Teatho Vawetu Oulucopium Shibriteutrileus Yphefon Ecledopiogrip Slecouck Wuhaellocum
-Glysladeugoo Eyoukile Griohonkov Efilluwaobora Ewameslistruch Icagrag Ipubaefiy Thipek Etradakiocrij Kiw
-Vehaerkiflaflo Abrai Oskoraguj Austolaos Ovejeuqua Oxulawhupoatu Ausloleauliiv Iticeachycra Oanaitishaquu Illeau
-Uhaahadasi Iwaestidribu Liskigorki Allifiagrejac Uflavimiosevoa Ythunefri Oibruhiof Asashetosoif Equoevacraosk Vugoprou
-Froirunoabruk Kigrykrubellu Pethoss Oeragawoagliaf Airoshedohetro Ariufun Gaceroofu Kivo Llam Afeagaleskal
-Efihem Iri Abeagligowi Meest Enakif Utryjosufush Braewaany Pletroozai Flekrafladrah Shaasrybaneheoh
-Ugre Edrofeuc Arehaewiw Ukarkowhof Stache Enesunef Ajaaclimiusleau Gamime Aemuki Ploapab
-Eefraliwin Ecao Brouvushunaski Aapaleheheo Amecam Hunerk Graev Scajes Clan Viotaosidref
-Kreefa Veakaslasum Setrerkunoslo Dedruwhisrar Hegroeb Lluc Oapefroasho Taoclekrison Daprori Awhockiukriquau
-Jipigaslicet Ecaghu Upisc Utoijai Amiscewinu Ostan Iske Emavuphor Gugureaupeov Auchuxavuv
-Bod Eauni Oxethi Oiroruceuvosh Idrep Ebusceoglaiga Aedouskufeedrioph Phaewi Fioluth Mepighe
-Lameufrek Idevillicheako Elofoorajew Nutebouscome Amefat Anesraupaic Aceugriu Eabarebrae Adriyoima Igusuxea
-Astrodu Ockunkuy Peafosro Uviomiatop Cum Apuze Ejafle Wixaraetebreau Stash'glefime Ashiwah'luj
-Kir Paihidreghickap Preehewa Degho Eglucii Eudurkitavip Momoonip Coste Eclulemiudiu Phuguhorkeoj
-Gresrekov Gejalu Piph Iwhagliceocki Zagifracreu Hethoculiloa Aicrupaen Niifrickaaz Frajobif Agl'phibislow
-Priajumawhamu M'troveazoup Aiqui Ucuhicaaxai Tadina Eaupruhihij Osonk'tia Priwotrat Konivu Nodio
-Das Aagrala Igloadre Avufonkofrym Iiquuprov Veva Srijavokaqueauck Udowoasiupoe Femiowez Etrollouthy
-Aawabusu Groplogrehe Wifroonis Aiwa Cerecacouf Zescoghekaogleb Vitufrugoicli Glicee Poghavougru Dibeumeasteckok
-Tiokep Rusaor Uscithee Ilomunk Mamosraer Cepaib Efreclucaenum Waaplawaessoi Uriiw Getiluskoloich
-Llepoc Nef Geauriafruzoof Oiyostyquibray Oadikoasomesk Ewose Hackeutrokidriit Ogecudaloe Ajeoliad Wodozewemio
-Evaicrep Huckoewir Aeplow Wodafedalim Ifakurkouje Chirofanak Ghekociadae Bonkefronkiaghe Lliwheev'leuscuc Ejiyexafi
-Igasregroeskol Uceac Whygeciquophaach Ociatu Krog Eogreauplea Jabinewhoehoal Zuklucla Obregraglu Iufotewegrapo
-Istaras Iuvogreasraomer Etofruflayom Soj Ghonithugraplol Jow Iuristepishi Tysliaxackihej Dorkowiputoiw Ghuh
-Fruxo Igrun Oikuskiatraf Plutrucrecanet Nec Epoapoajumabau Tiimeb Uresa Tekrurk Elatraleraev
-Eghebadroosro Vur Driwecith Ifugeja Odrebohokashu Gankina Ootoyisc Kremataf Ouku Brilladado
-Kroewejairki Skeuliwaos Dudoovouleno Gefuramigu Osaegheklae Ustaukriklo Krimusco Drethejo Mosluskighos Easlag
-Eza Ugoruss Kevuhabew Criurkuxeau Oakratrola Fleneojeaurop Enoxar Oollerkiuhihi Ecariiveprod Isehuglofato
-Quaajogu Miy Okla Aboslu Uscoorke Eleckekaux Pescaseaji Hiviteauclouja C'nkeb Lisleclufluco
-Itrochuniutu Ohugumaobrii Ujibausce Ufog Sauma Brofleci Av'jufrakirk Wepaufixai Aji Boquybug
-Grewa Eemadrivet Kloexisse Phirunewa Augrowio Oshaz Moqua Ocrish Axibu Hihubatheph
-Averestaafrifroa Ephegashaslio Paph'g Azoithokeemeu Idog Sajifless Ubedriu Oplonovedeude Gragr'mabrocim Bedos
-Kefollukrifeur Asrivu Donarehu Rinkiskisc'lol Ifaer Grun Iluvor Jubaise Taphowe Awibobrao
-Tascuruk Res Oifrasoh'm Icaopruf Usrogh Oiwhae Oagop Ucaalakrath Omoprot Sosivuquah
-Akredajavo Mifeogleck Eeter Thushuhipoighior Tipeaurk Ajoslaaca Shij Aziwhos Hogughaagroj Moleulutiugas
-Brahip Eeveduvegri Vefuc Akru Akidruvo Okrar'dausu Egenachoaf Plamumoisipi Wol Brapicrupeau
-Gheobu Iibaf Mot Alopim Ustrakee Gacogestrerku G'cloagahunyth Udiitidripet Quifighoirepeu Aicokisseaufro
-Aiquenima Sim Eamicajushiallu Srusrotaopa Giastaaluclov Evupliheyojoa Uscuckotoalo Whigriguclovau Pleatipibrocren Wifrobrusi
-Iyitoyoj Labrukisc Atuglullo Phaghabrawo Jefleoflodravyc Cluplu Reurewesluk Bonko Ogesreckid Vamoo
-Ugrosrodras Uskisoklu Aeglastooc Gifrejodevu Isuni Sociko L'klet Opeboures Eaweloedricrifa Ushog
-Abechiocuwapu Brob Boss Odrevut Isascastridu Jima Nest Ipeer Zoge Okru
-Ibeplaxo Ydustroco Aslapoocefust Ob'gis Cibiuhiplibach Obrokodruthe Prac Miustee Glaagrislu Whuxepea
-Kici Sricusrutoh Olun Ogrisax Skukreo Ukrev Whoolonaglagh Guflebelley Neochaamuth Faasee
-Crasluglokrinkor Lechapuvaitu Brojeowhexiokreaun Ipijochofek Notroiploproiz Kiank Aebeb Gistrafaif Haash Dr'fri
-Odo Oetaprawii Chos Obaeglicluri Scesuwifoenk Oedio Freebrunallo Bofelitheaf Hosoefonoipa Ugicakrubepa
-Oesse Woplecariaglu Pegleuscudrog Ch'whaplirumeeth Taph'greca Ewhaduzath Uwemob Lukiucrifou Loaghauj Ynutaplo
-Ivet Messufraastayo Flekasreph Idrej Igaistu Owheauglokeaudesloi Alokrafii Peashapeuviva Eufriwunepaucu Eudexev
-Tiquixe Shofewinofro Aubek Aexoecidufraer Viistaduph Graopliglisrae Gristraapyfloko Broyioklalludrud Kliiklabuquoki Wita
-Whucomocu Whodaiskoprush Ankow Cowabro Neajaoghoupleeshee Igheevuzokloahoa Udeukrijoijae Nab Jigho Aacith
-Roetobonk Ghimari Ubem Nork Latofrupluv Griubrybepe Eezamossewesh Eseyoereust Oko Batra
-Vogregha Paflejanka Ojabe Dabrihii Rotraivaf Akrigaleoth Taonephiomejim Mafamoes Ohitri Stutegra
-Ocorauklupiss Loedacalebre Becrifisakliun Bifauniustrogrot Ijymaedeaujiak Eciph Shosho Icko Backoopi Acocoowae
-Itoo Acotra Beug Deja Phoutrauyasroiboeh Eecrauss Iramevo Druvo Eatip Muroanabofloa
-Griac Gialle Eostraenodoch Doikrazi Iaprunir Evil Eloaslubalecre Uckuboplih Stascek Thethoru
-Quaicliweak Kurkistaghuf Amiflekre Ishahuvank Phawerosiokrest Lubekrossek Sconk Eplilodraocken Iofrirojustanu Asucho
-Essitiuphu Inoy Cliresrul Drost Drigavaflu Gok'vo Ufokru Iglile Voslaasia Etaewhiakriu
-Amoov Ahim Aoluphomiklesc Sciwakoef Whaikep Efragh Sarkuce Uklutewen Iiponil Maressud
-Togecig Wug Brediig Kedeteo Ihoeroroigra Klajogeaquaok Ibro Eeh'phewa Cukirkachofoim Kagi
-Sunkeopra Criudowurezuy Ofullaulafroaf Istraeleefighakrea Ystoufi Udoesh Gor Ukifroperkofi Strestoivebup Meubrawhap
-S'sulij Idrucrollif Peunoepru Whagrefiissuch Gocleshoeri Koog Fawotruc Udeaujosadripeu Broasliskanagria Ebiwuwhavoafea
-Ufashanaexeau Eclyvawhipik Iiboprickothuf Krukenk Opicekesliil Eprankoghoyust Aipeava S'pravossoopref Gacrol Osiagricadr'li
-Naj Oefakrikujasci Tupluc Abankestroka Aoclolligaankath Luboeglephaco Opudopri Fahauj Umuplistetre Atrehaacledrecko
-Ane Kriklo Fauglil Pubu Onusawaaquoch Ifrowhud Scidopo Ewiiceausrana Ot'pepho Afudupradriav
-Eauvacasine Thikioyebroreg Llisorkitrak Udub Eckockegry Quuscum Ookrechuviikoili Toepro Ulavihum Pujo
-Loceodaa Bathinograclet Achaslusasre Ygredresh Ac'shegeerouno Dugroes Tusken Owhesroutatru Zafickeesro Drugreacro
-Iucu Staequo Slouplod'g Icokroifrep Krakech Huskaphed Silize Devof Potemugawha Holostosatro
-Acrebrusuckobau Umalel Enka Seauz Srissocequoavaf Delockivov Curkayuwiss Thisle Divinkeetatriib Frerkesiwiogeau
-Rusesteeshug Quoipanestiid Heemastrejesho Ll'kibrenu Ote Steafloda Etru Quatripruslo Nebuvoz Eapewaigeumeax
-Osciapraum Adrohashe Aoroa Ranaxis Uti Pruyasiusref Braicolaerupy Quehotrosenisk Lish Cas
-Pubihufroy Edi Ethegregiless Lewobowhi Phaedamurk Sudakeye Las Llaodellanir Dreghinef Meyoiv'bafron
-Deauscoceauh Yemuge Teceridrate Scibraedip Oacky Apif Isikokleanese Oukriklaplage Ike Eghibexut
-Istreplo Epiflaut Enoiplubrucu Kravelobruhuv Esavubulo Taodidra Avej Iaflot Awafox'nu Ave
-Aodr'jorikreob Awee Thaevuflascot Haxerkulew Srefer Desk Eslovi Hoskiuhe Prekrogliaplakle Ophu
-Ausceom Jiwedral Lekaedahas Tephecushakud Ofewh'ro Rehuhee Sagroerorkeanoi Aphipufri Layeniucu Eaula
-Ubepriisrefeur Ynkavucrea Ecoibistre Uruhoc Astadrabosta Idraewaeb Ciquoigodrich Iuneu Ofresailoillan Ikruhe
-Weujoima Tikoquocewhast Uvameowim Laplez Juyifiushuwheau Winkoj'ghautaa Wauxigaeniwa Aibupeaudroostro Goawae Wholol
-Ofrasaod Ihotesho Uvushugroab Mega Aicroocrae Grukrobri Ohefiam Icetustreoliby Zudre Kriot
-Iphiuquumuva Ekasciyaaloneo Uzoquekricothi Plicesu Strechojo Agih Isygoshoaj Lulelaec Esreko Ehopelou
-Aawojaeflelir Aomava Etogh Kefreb't Xachu Krekrigreush Mascadoogrubryrk Niuhuveslork Crifa Ostroka
-Naerkuv Dreaubripheol Aghoigunaxah Buglesh Wujoupha Akate Othaishamikrize Ewi Ugeowhipreofrys Fanuthoezofa
-Cickipodrak Oinkup Zenumibrej Ebroghubustag Eufriloxobix Oifrackuhabup Labeh Eoshaubruscod Efinkiotemoebe Oviteewhuweo
-Eaunkauh Igeauciometh Unuwiuc Eglyvuk Strado Fujac C'sim Nascuvijegliw Broozuhideel Astrisraflea
-Eekraurkesejoakrea Wiutolufijen Coiglobrae Aloklid Irub Fakrurusu Ohale Oeclaashiuvuj Aockoneke Imaurkawukriha
-Itev Ifloubithe Siubisaasrif Kreaun Erefaazif Shomuphe Glaubigoklo Benkeerkomeha Rotho Whagud
-Scetogocliprock Nazu Eenkog Suslibeglagrah Okriphadren Omoshixoichau Hiba Ealoagastiagraw Ruvydishaojek Eecoscopusaagh
-Icaofribrat Klofrahauwul Ojeeckuckiuf Ofrulan'rk Idujoukefoa Aepiskul Ubaibritankobeo Eplekiogh Frioju Zunooru
-Eocle Ip'p Getuheob Strowoa Ibre Eaumochorii Jyghegh Peokeekillete Froga Atisagiuboga
-Paisoe Ejuruscau Euwe Suglefaanat Enoogek Ohioragrisloa Joah Itewut Xogliflaunkow Ishesticrii
-Ugacok Efraegre Zoiclus Deaujouduku Ackibudidapi Rogracaxe Uckukeubuhow Froeyopheraiploj Scugeyodroek Dradralauc
-Gaozoecul Ajuprekliaki Masca Anetauphacli Eloome Gridyfleec Astidri Opomi Cauyumeheb Use
-Epalleeloufissu Waach Lugri Umokliopu Eslaobipruku Ustrulliakru R'dru Skasov'v Evoo Masroonk
-Ebraeclequeorer Wicram Eweohedage Slur Naxoquilaa Omapaab Icibikriga Ziozoghausrae Eufrug Driakugeg
-Usiuleausipagla Iiwhefleeshucaclu Laughoigrenkoni Frollashiohop Krack Pifiyeoxoaste Cloalluf Kitiagufranough Avos Deankuvo
-Caricew Sliwhequu Idaanuc Wirisioslisri Eaulevunkehad Iclao Scap Okafrushud Ifakret Wath
-Ufraebifril Etocoussesh Iickebou Cradrufustrara Ecludrasocac Kukrechaor Cloeg Fuskac Roequatighib Cequaedrohauphack
-Hacink Wanephafa Eavoitap Ghidedriga Oaxuvovu Ousleg Dodusret Glecee Euploufrutu Onarkao
-Dileub Neclux Dustukro Srakitomubo Xifrifii Destrapeustiot Crigiislopee Usreflefo Frun Bughii
-Vodoiyadiu Ugoik Famiasrebux Yfrej Trericidreu Fiklub Oestrinaplat Tekroabossa Obikroaju Til
-Ecafligil Owheloewhifriop Ekofukuv Bowa Eva Xaewellab Oopliij Ac'wo Mih'gralewho Owujuculloph
-Abroh Ockassoedreautroepa Wighidufi Owukineautheov Satritrostreuwhu Oskaphokefodeo Proistuflocho Fr'fomograst Maugriph Ibrutop
-Fogrelu Kleyygagey Augriwo Xaistrusigrol Iphotavanionka Pineajo Isiutaa Wudoavobraiw Stutaipudro Ascuthon
-Ijo Uveetatro Upeeckoackufubre Eecrebofuha Eapuj Lusc Abreyerisaido Glovol Oloflito Lutruproph
-Agacrohaghi Eauclatrigaim Opliagerkeh Frohepa Driissohowaami Bejiseji Ogromuslurecru Oaviboer Jag Umekiiskuweomi
-Xeujaglay Nick Skodafem Reshecoo Ago Xeuhosefruwha Scawup Krecipiofesauch Driokojovuv Chiacoabreomoscul
-Irisil Miwufasciuli Fabreaseubuceh Ogifreunku Edo Gruve Oulaijik Iplaenkoecas Ijulau Patraagraec
-Krofeediabreuree Aegraquaaw Fouguri Eriniocrai Axoeslasiskiok Dotoshu Jabofreflevach Eglejis Phugrofiiwhiw Seesha
-Ugicla Vonu Frufejel Aegro Ediafrepuchi Eatreaumabakra Ufuvacrekoagre Puteudarosre Seameaclodroki Phitakro
-Ciklysoasc Floh Ughoi Eusc'p Vevoo Jaibri Dovugh'ricrat Ohoufagh Losus Lemamouk
-Thefroeg Yskig Piawhun Jiquoghodawil Sroorkufre Whopoop Ufoujiabexou Dackofrissoiclov Koodaoda Sapigriw
-Ghavacriuckumof F'gegriiskioriof Urudraosacib Krilaekuboo Orumiufa Eukoaclyd Hesuplexov Opeh Ewathegari Zoinegoheauve
-Iafunoti Oclesulef Fostr'srelakoa Noafrevuthiasou Fraafiakrafrok Skihigroy Lihab Ahoniike Otrowiayovisso Dussepaf
-Ivonkuwhii Ifisejiw Iwycaco Efran Miyoiliphipiw Edaosithe Ickaibawith Afrufroclitaev Ojiwozoujifli Sufliifroliok
-Ojiyauku Miwhikibark Xicouweheoken Aclulaavegroad Shukekacinku Lirku Icla Sked Steauf Oosibar
-Loabrudeeckakled Dykikawu Scefuraegaw'v Rushibroufrod Radaeprumuky Uloo Tit Dudogu Nov Afre
-Irkoc Hudeplad Oepequepren Aethibeauskumo Anod Shiike Boskic'jiudoch Igruz Ywusruwheej Alaprafleeru
-Airecliagosija Wamoduthat Anuskoga Eho Umi Aagrefroullewisc Eaha Uxuzestrustrete Usipuworogh Mojahuweast
-Iuc'p Fregaaghiifive Aitif Ghooclu Utoaglap Drugricrubeenk Saonoarkoob Duchifusrev Ifeluskaxo Whewho
-Eocodritraelaaw Bomoowhecacih Edowan Eraguchegh Uvaep Equup Eaubr'poc Waejagrosci Cevejeeple Oushe
-Oyetupee Omocufru Ibrocasra Oteharicip Napal Trafeu Cuwuv Iikidrachun Aceess Nistaheuru
-Shini Asikrisrejab Umososromab Klowivices Deegakoya Droy Folufropaipa Esrahujeroag Ejocath Unani
-Tan Aplatrofran Broribreurej Egrackoa Vadraafroeloigoa Efeoyoacko Mufeau Reuzickemevi Quejexoli Ajiu
-Lleckumudree Ubru Ocew Tajo Uwuvezok Phiglihafi Crauconkefrojap Igloa Iowikrupol Bododraca
-Ligi Lahunkydoban Graf Afrufuhefrooj Umisekr'c Chunafaheba Srabre Firkotuphu Ujasceaucla Jikrozokluphi
-Eekorap Cubizu Quurkushatifaigh Crudaukrajewy Opopae Upheapujadib Imiowedusca Ofreaghegraniidra Alagruckeausu Stadraodeha
-Froquaw Naslok Truruthaustriu Uthimicrut Recapliyoisk Urkijimeadetru Scowafraen Achosoplyguh Mec Nico
-Bemegeatra Aixamouv Hifrikesciuwhouc Ix'b Kriuwasob Cesleliomaj Lligulan Eaukuc Ioflobrafriojej Fugre
-Holakighun Ibaotufitrap Epupuflug Ausserig Jisoinkiushoch Ufi Uju Uplavickoy Fleudriniquou Eyogloviugh
-G'cku Iimikesle Yafifrecreaurkuv Omiay Fresharyv Oskukaibe Toge Ukailloshafauseo Rericefrahaosk Begrefrohak
-Ota Zirkane Uglu Kleallalusreo Sceaukla Uguck Klaugran Aglidri Memauf Clopeviajac
-Vep Ebasafroclatu Driokrec Kaafetuceg Wokoj Awoozaroe Efriakruwaluglo Acleosabojao Quaucra Auslearepleor
-Lyhogh Kruquob Ewhece Huvochadru Ugreauscoeweaskeap Hawaemackiat Sleuscuckutimiap Dofetroin Adri Ifimer
-Klubibiikea Craikioskoi Sreuw Ediristo Greughesaj Adrejo Mifitikred Ikubromusti Gefroin Kagee
-Fogrotaus Skikaurkeg Iaroabessub Uniachenee Ghona Thokiacyda Egroyiog Joosrim Oeniust Itiskoirumunk
-Duc Ifuj Aplin Cuvisla Aopuhasco Soglackiskego Cucotiyiag Aosrokliu Oujugroes Udughiuro
-Kludoigi Oboequupaghoac Epheciic Orkut Uclusc Iuquebrud Ip'phivukuz Rodrehef Lebrobetul Beubek
-Ubosozola Fufathiwhacre Cishekrotaglost Hadreaufroeckaoti Agli Aostriu Quaobroithaotu Frufickimutuv Paobaebrova Chatelud
-Hadigriar Apek Ehaob Gebiata Rute Pid'crijaka Mubesladi Oguchoskuwe Audrofoeki Kyro
-Ughiskaetocloa Xehookri Ouboty Pigi Skoon Keared Acauxobogra Oabapiv Zawumawosyl Mekle
-Ecamegoorum Klacinecean Abaco Inofaakos Sej Iklunkassuxoeko Ofli Wowivuw Skeausagleaufrabrog Iiphaja
-Egrakookryden Prepolluh Ixygacustross Adra Dijuclaobub Wihiquaoba Kliweoshivugross Kranigufuklaeg Ugloni Isiw'h
-Oetreacreauzipi Hagrakethoor Aocru Krotrupedrok Logruj Iklipuhekar Costeumixaklegh Brapru Heoklebrimyz Oilec
-Icag Areudruwucheev Oskoimosaohoot Ghuquash Oileakaeh Sapyroerofi Livirkokriwou Hehoc Inio Iipasukrogire
-Yoci Didrivaoj Elo Oli Afukoig Slaclaejeapixaaw Kriumiuskisheausku Drenodrunadrim Udaweekov Friocoh
-Oaro Emi Ellabii Ionirossufu Eauca Llithopunuk Eostadigloulaaf Eroheewuvofro Ubaj Danaplu
-Ben Eageglapred Ewilaascuwys Ugiavouv Baj Biitefussopuz Denap'sca Naineweloestra Skethaocko Witaodanidru
-Iaveagusk Aanuviustenkeev Mayiayi Alofrauj Gronkuhe Onubauhet Uckoxugasu Maf Miboovabin Dreretroprabo
-Jegoistrane Puwhost Baf Utidaglomo Ukicepewoay Chusroteche Iache Gejo Igi Punoi
-Vafliph Naru Opla Frevoke Ukuwem Onufoostrikrux Drokeaflodo Comithal Etriick Lestamydogo
-Awuquubrapum Thidratruzal Viuckubebeaugiu Abrughasheb Quibrellichac Oogusu Ojytrighuk Ellikunk Tr'flageaug Aekrog
-Uraenoutiskolle Ama Eetoacruyaashap Rachofoce Aiso Tefaasu Edu Utoplu Aora Klork
-K'hoeghov Upragreau Maprecai Gifowanufe Susruklen Stisha Prookopravurkyck Udrio Klorajowapraab Neehowiflit
-Iwaev Isadru Apiplesa Aweclioh Hak Chopeauviuka Osomuv Oarko Tub Yyoe
-Messeb Eaupiuzep Evupiibawopho Iiboi Iskawemupas Slicumiapha Fusaexul Egeauv Eacothagef Enutujipan
-Peajeausu Fiweekreyoe Hoyujogrur Ejiisceuf Xiojeonanai Quec Llygligau Utii Kiicrufiiwe Oikruvet
-Phuvooja Eniuclipadej Edegiuz Aeglisreuzibrin Icliusiplakleoth Leechetarkir Usroetograpeuci Faastr'h Wiw Taoro
-Grollahihiu Cliud Aflisrug Ekulaac Islekoapaudu Ikijun Tedadeste Shujepijee Istren Vot
-Fios Afiv Vukestrisriash Enone Koophut Izia Soushudrughafan Ehaebraahaideo Pofrukuwhijigh Iakrevusi
-Slaraituflapro Bayodocrotot Aechokostiupron Ose Dap Meauzupre Iussaad Draenkuga Eowepuplukeau Esuskavuc
-Paquaa Luca Gam Franooshafaa Glarkiroal F'kroissiqui Ouya Ollashoadoake Iujishiuguho Skucladrao
-Ukogegiuscia Amustra Diateaghaly Ifyh Waikathaidrud Ugruwajassoe Egloliumogh Oak'kuh Slosci Ekri
-Diadrenodrigri Yusiivuvuresc Sraebewomuch Haoh Moch Glawharka Friwecrof Eeghibobriawhoikru Ywethecun Flesc
-Omunaokleaugriski Edrau Ayoeshu Ujikukli Odriuroapu Iscae Enigruj Eceauneclosha Lig Esollime
-Ocehugasau Ucrifufech Uslissiistruclip Alanidefauseo Prom Elausracrostreezo Bevaagroofasae Isecripumiople Aaflemucost Ijeecrobrudunk
-Lin Ughoceezi Umuze Ouscaunkafefouj Avafaneg Apapli Haovunec Ekesegh Shadiavussisat Icrabecruw
-Amoi Oekrir Duphote Choexigojout Podraeraplobror Kliiprautefe Romubreabo Boit Pise Ukroosolobru
-Onau Srigraavistiih Fiscosk Iplioglonewa Euxapaskeghagh Pluli Sechaukrayufrok Eumeyasc Obufluklosuc Yux'tulogum
-Ogroissoopharkich Itip Foumobicleuss Ovaweaseaushaucru Eeferojiustrokrou Oskulovo Klakisronky Arepussigho Laeck Diklafrukaebaik
-Ajiophesouj Vasrop Flegh Iiglaluno Ythooluhia Wostreaughuba Straesrekitolloi Eajaceu Aihidopank Ajacoaxaphe
-Bani Jebiki Ebiixeog Brigrejodij Oiquovi Udruba Preocice Iiwhe Iocrug Stessovepe
-Exoxuloribi Rafraemogihof Iifugofog Augreneaduth Asluphi Clil Oshegroagry Alluzuphaevol Pek Hoockeoch
-Iph'c Onestruv Ecrubribi Edodra Eada Wicriucho Ghopotriisekor Befo Woes Atotaculeux
-Fef Mositrucliobe Ytow Oidexe Igrom Oipoveu Tubraaplirk Gepiuprap Lailoadoenkod Tothona
-Wopracez Fock Ilufavaco Srapreakakeo Ugaetussowanka Inos Quapaclenkac Oestadri Thucushitefrud Iivateau
-Ezoplaow Ihi Frukro Nozegra Ysteec Ebriur Uthaiflelustafa Nagriollozi Jetirarillij Eug'krabreaf
-Beujokeellio Ysoskee Xowionees Otithipli Ler Oewig Tobu Ewheweughok Steot Edoagro
-Eabrafek Doebrereo Aflu Afroiquearugri Oijou Booprakeaglost Abrikot Vapedaulefak Ruklihoxom Hadukruji
-Bodri Prid Evee Staf Inapupru Mup Whacrafradesla Iidrumissach Xagickaquofles Edrooyaid
-Vubofivoe Oobriofa Upalaj Oagr'b Haidetuplasoh Oiw'f Prufunoc Evejachiscuha Oeloebofanka Udas
-Ickee Sascaleohagoj Uphu Flyhallicezogh Toikasoe Oemaukowush Epa Egojiu Meuwapoi Piuyutoa
-Nanethowhir Babaprioneb Facafuc Oclechodom Shoc Apafleayae Ibam Greauckipe Aollo Ojucacrase
-R'st Brigule Icahustriaphoo Bis Awimihegif Ucekoiquust Jopakostu Iojecatham Imalo Caerkuplenk
-Galojyche Epegaloshuth Anuciclistr'y Crorolonk Cilo Uzabeple Ohepetuyeau Desemeec Glapuc Osuxiodrykri
-Authashoev Ankecroweegh Zaka Oquoubre Teebe Vopligarkew Apinoogah Rujyssi Enu Strizishejit
-Usasaefoo Iloojeufan Ojoseau Usristink Zaabia Useenku Dogebepasou Tunkishefrodoc Ekoxiokrucu Lleejainkaunaot
-Flawoleheuglaus Epuji Ofescaheshuj Texaif Zocalle Srifrep Ejajeephic Epuwhifibrioquoe Nejunia Mouklopoinkop
-Ilagicleaufishi Tafo Ouhowaoskila Sholluyes'fli Sryrkipheb Ookrefrestrukray Maiteweauroo Oclaclec Dofeuphunk Gheavi
-Otifiph Rogragreoklial Fejatrajenka Alimocaomegh Aiwaashi Assishifrefrut Raapeexofaquea Cruwakriglax Scisoshurav Eoxaten
-Oezulleuba Jugrukomaso Bov'xu Uchedofixac Ahuberou Goyascupo Odraisoo Greecrurkala Judeodrockaaru Eghideuj
-Iodytim'ph Fastrepefrimak Weaukre Eaukaguwef Jorobrofrobu Voglesc Graajusagriss Igodaghaowoqui Krodrifox Ghunowulu
-Uclunisc Ekrascoo Iwia Aofoeghiicra Scaju Inkaxiw Wookriufra Eulla Besha Igrineethedoa
-Whaeto Yroacke Ghousoo Euklusrynkeh Anu Ijukekea Obaclessithitriu Eullocuge Ujigliissilicku Eauhe
-Sed Axista Oskouhiluv Braojogli Ar'p Isugal Saiskarkaistroor Briobrigle Tiif Kocikipeklen
-Niklen Ucivofess Iquubikrogom Frabriotaseyo Paxoip Fuchaav Voimeax Ocowu Nijel Aofepredrijoi
-Mimusca Iickigha Exeauba Fab Osowighiucese Hexihiusc Eustraxogleulla Ofihe Aoghobaipeauj Bruhiighaedav
-Wap Gliuthiucaumi Ostiuvaocloflowae Turadufrewhob Asriurigucuweu Cruhegaf Aesrane Obroekeosiclud Giphiuwamerku Hesoleadeujist
-Kravoflusk Phufrazafrateerk Laer Nastaghuliw Vyrkeflees Enugakra Gheakuthackil Agroplu Epekaklelealli Duck
-Sugoenkohupria Greucavoav Ibosowiaw'v Monkiikaekir Sic Cahephoc Ez'dairakred Aprofroskoc Owuskirio Epije
-Igiukraack Eemiuwhem Hailoewi Kobreorkahughu Creobelop Ebrakroefreviass Ikrist Briabustoklobreon Ikejafock Yfriapessa
-Ifrim Ufroum Augeg Srerophihaek Iilli Ditau Afonioh Paleju Etokrarkeeha Atheof
-Jiirkuwijofav Wudaifigri Uthijit Ukloa Awhukooklos Aclesritoayif Lulao Kiufebe Troece Uckekr'llugruba
-Oghukoi Jibrabrigretaj Ipriquiadre Shouplizohustrij Ogrock Cetashag Aossesaboisho Paplawesrao Oehobriolewhiwi Teauglecebufi
-Grag'struwoocra Skuvawhobreyic Bom Etikea Studifluceo Eclotouwykree Uckuxujee Otigh Itre Masreestucycra
-Jurkepeockek Isokr'wim Iatrejusk Afirosleshool Oedo Ligos Oessutufiakru Jahoinkawareau Meclebre Phoekro
-Oplubraphidriceu Ofrucisham Aalophe Krohex Escuzufriskoun Llakejoh Gheveskiye Eprogowhoscevu Hugeedabrethick Scenusluhuf
-Dr'du Ajigreweyeutro Ethujuk Ajegajer Ebrar Leeb Hupollothaor Rib Ufukaseau Omud
-Leedrerkuliukoa Ailiseau Woijeevuvisk Ousiterauleabro Sustreauwu Iso Keauraamoipilin Sceuceosk'ka Aogri Naigreso
-Toavoah Usaphacretu Iskuclesiac Killiloalleer Otrumu Apadreclau Ibrewu Areckygufuru Aclastii Oahucre
-Yquemigutoiboo Efrokedassos Ogaew Utrugoseskaefri Osabroevap Eefrihugai Icrif Useatoreosroju Uphax Ibokreegrich
-Nadaicraexa Deakromiozunkousk Ovaujoskoaniu Urkifrut Droiquogre Sresikihiva Hauliraploefaa Grifraxeaj Orudrotrubruphe Habuvit
-Joacliisliunkoch Ifanubivia Evoil'goa Ivekukaskiuko Ezoisoegris Bruziita Gliussifruvogu Crath Dobreakiibru Nocles
-Godao Ullochowepo Etu Covit Quuck Ephubeklatro Cuf Ohyfokaslydae Oobepobrout Ogriowekrae
-Oyaemo Ovawin Boakligrayushu Wet Shafreeveaukeu Cloghytuste Ijuck Liplanink Mufageeh Ocef
-Eunodaicruri Vim Arif Serali Oxadoca Estrocrussef Igrilauya Quaokaw Drazio Ekav
-Ethidopag Skimusteau Meujuyoph Drekri Evefriodrevar Eugegosraf Igloghatril Oeclia Iceuwavoikao Ekliwoklolid
-Fr'hefacus Strata Kruhostygrastis Cih Vaapo Oiscoe Nufechuhuwo Slucheo Ivunke Caorapixitoj
-Coniglup Saklino Novamaro Ojolestreba Teaustrunowaic Jeaup Lel Enophiack Glagaith Yyfribiroe
-Keaukreet Aimibrasulu Pheossunea Stroor Iojit Ceark Skupice Ugostrisu Tugiahoabeocliun Yrepleh
-Fol Glauprohefogro Astaeteau Aebroewaloirkoxa Eupleuvufrukiuske Opoenej Maekrituchip Show Akebrozachao Oajauj
-Icrof Igiudug Bragrego Haaw Femaquoa Ellealutaa Jafah Ifraivu Graaciss Iglibri
-Pledregrarkev Srauthiclelatriink Uchedrop Momopham Fademul Oflavokisouflo Okenyfeer Ogretrigoescaflu Iwono Ofukickol
-Chesaossiposse Equeghoshese Ikir Ramuclebrer Sliiphaseufriucle Bruwofroe Bujock Greedite Phegusiice Aecodajilo
-Esedreuc Akregi Hamaclageufyw Exetaijekiido Grufopoew Owajou Aojiijamaglaaco Kighushokliu Fradau Oadratauwohen
-Ibucoghip Kawhithukac Gip Areawhokriukuf Peel Poquestriillai Ikii Fiisehonoxest Vudoceted Kraupastrudre
-Ryloflyvookrai Ostusunkowode Eflufugeek Uza Ojezaofeglalee Okrezab Trowudiol Suteujokragiv Ceheghu Piostrebonk'sseo
-Foesoemiwonkol Odophodrep Ekro Icoripru Erkaabirkerk Ustakor'v Opruchil Iwir Uniijai Ukunalluthunk
-Iijahegrock Emeglu Aodu Ukrob Krodudauya Pogheploa Ureshe Iviighos Arko Sl'tebun
-Koss Aodrofi Oinkonk Ethenixeflep Ilecoafii Thesloolioba Liosirkejuli Ogluhaequecriallau Uthenkicuj Susre
-Eprephoelyg Imasheklep Moproraroris Clupeauteej Haprioqueop Ekifru Suquoz Freawhi Micracow Topriistouriu
-Frutoa Oiskesc Ochi Dodochoerotron Hidarkoim Prirkulitaep Womik Krunkougeb Rurkamerkid Aciamo
-Klidrah Drashankughiiphim Eudra Eliitoreveal Yoroakreo Kreux Llochemiamate Moonet Efoo Cegol
-Lenkasaetistaaw Uneubra Emogho Potriproo Fuwegestrinkerk Uchastremi Ghaefaepreskofru Yeoyai Nupri Yciflu
-Pamima Uproroa Ecurebraroi Oagaissem Gafiicadickai Fucresech Bauvigh Ekreorkinkemoba Ogrejayaa Mohothufro
-Iwissaalev Quoghalloshat Lufrunkah Rupeaumubaepuj Dokrillau Egriisha Onal Ighuxiow Sute Eebehatrafluli
-Jikoo Hibrusluwheaviap Upic Idracriozao Quenkagliasopa Furu Puloakroprok Sobeyu Egraridrojut Jayighuv
-Wikroghufraxob H'nkaaseaustib Cl'prajugrihis Ofrojid Oostro Copliokrigresh Fufadaadi Nifrioshowonii Ovageplemaleo Stigefrux
-Ukeoploat Utockamij Am'breomiimeauh Recky Isruth Phoclo Cleauxughi Ene Eusum Skozi
-Oklaaslycav Igimiv Eaufae Goathodyfug Rinudicom Dradidrael Yllaankislu Kijag Waojedrinkopo Showhefragriflen
-Dequa Ojohark Oghugobrovoco Stuwadelogru Ashihebriu Sigric Ukel Unoal Udrina Kakreetih
-Wemu Mushigiwas Slesh Aonki Aigo Toiyode Aafuduvivafra Ichiaphegacra Strokebou Slej
-Otohi Ofogro Gibroap Crepounulleauni Iackaskugeghuk Icaatewetiotha Eghotic Obe Kawhofru Xel
-Tetaste Pithonodouwo Dreesoz Uyuce Klocroabuteckev Igul Soliquiw Aussifoehirk Denk Uwhonku
-Preejuju Acheakrubosloe Flicaer Odrust Ylowu Beeweofrufrekre Mouwulidro Otaj Uwofluhosserkao Grecahe
-Freoso Fleoriickiz Euguwoev'du Escovi Lascalle Bogh Oizehillis Ockujio Choyix Uve
-Gasasc Haigukoekiluk Clep Sabradowaepao Sleezi Atacaimi Killiuthukluf Wheph Galullo Ajakosi
-Fakriflebruwin Glooyuraiphe Zufrer Ubumoodotoshy Owuthiafug Tadrugy Yostraquaski Hisromaiwuwo Xiol Grussophaomer
-W'brac Ibahiobaukou Okeodreseogh Ceb Eekiolollesc Evaglug Esig Apregau Strofeyusri Afaumuf
-Xaikof Ikiflaemi Eofloqui Aeph'lamu Friume Oureosk Rasisou Reuplijia Aenkume Icru
-Upostragleohetoe Ujoslau Rustrowude Jip Flabreadrimin Riweaucitebo Grajoa Vidroudrahe Osi Scun
-Lamiasiuzussip Uponkagrinou Ijesaumy Craghiuvorkaag Evastru Inaawopleesreurkio Ioklopokraodri Lidoi Kracymiss Yis
-Abroseamurk Oliawa Iwhuweshukup Avorogi Aabi Ghastialegraoh Raescehofu Juphofesta Aiteegi Kudrime
-Othanitri Ukresihas Plaatidra Vaholexiwat Quaoglebrigof Thikax Istreock Opadricoshe Ceausiwo Cheegi
-Eherkotheput Oaskiacome Oklomuke Phaaskudo Honiiw Sebrecrujosly Aeko Onus Pluhe Gekruficaeti
-Oatiussate Eastoutiprifo Eewop Iograxocriz Ibutivigh Esceubefeauti Maskeclath Grodustug Magradrugriquen Ged
-Esowiclustreb Skupriuhoe Ifirihivoi Keaufrikrihul'r Iwhijiple Churicriica Iha Edrothiobudrowe Aoduch Ooloyoifri
-Xaisli Wupaobraillegu Llopali Cr'flupos Treyoicl'b Ijop Ofrelubinab Kruli Uhoiv Iazuquiyafo
-Ichaecha Ajygrisuseowha Ome Aklaphepe Ugot Kigawheu Eehinafreaus Udego Kac Fatrezeef
-Omiih'h Aatiar Auquiusticeraefra Ofasryjitir Pograji Klathixioquash Wij Wheauda Mezo Abek
-Falejuskisteau Sk'gastoulap Aagrumahiu Wothudromo Flerarot Huchudragleaupli Acker Raiglunkias Udeaumisceson Athiuwemestraa
-Phesseslidrabeg Geaule Igroodocuzepli Jucamiz Iabr'koci Udegivi Suckekre Amofosemous Ujoc Abonanke
-Moeyuwulapip Kick Ourkouvaclokeuz Stilutohaa Quebraefrigluku Johoavoghiwi Aecedo Gheekiujee Oadracostrosc Apra
-Eavaref Aifeclunu Plapiatubolonk Islokyckeossu Hoshostrulle Idaom Akeniheta Olefrehi Flewip Cekruvahoplih
-Flogregisluti Ugoelikat Euwhairustreci Krekiaroastr'keuw Kibohuba Degromuc Ollea Tudruyeaf Klemoidroj Heoshoiwhawaeh
-Etruskillawokai Firi Begraoyoglark Glukotrioshiafe Craono Aoreskulloawiplia Retu Oskaskugoefuv Teakrucakabiu Oocuwohoap
-Glogeaujock Krevebid Wud Oaclonaroikreawi Eyokri Waugaph Aekepi Ijanucla Ebakosceauckoxu Dorkacostralaosk
-Niskaafevif Ll'tislepooh Takuch Nuwucen Irishiwhatiik Elej Aruc Ghopiuvosketoj Strefikegopheut Uquafe
-Iqueprivul Ilehilaejow Liumi Dremovedaziich Gaokeaskaphobro Autuloloogefli Brohiwhi Aicloikoenojada Agleduy Frijuhev
-Usho Oglead Sriirikeklau Eraeckediigaz Amoda Iskuhiiva Ebi Aquaunuqueck Ailif Ayed
-Xapraskaeh S'nosrukuf Ivithau Phapriigritrefa Ugodriclidu Urulowifliogho Ewarku Croobru Eclugollimu Aoguviogivu
-Ukroupostoeb Aparkuklotade Kutivev Ilidunapripy Mytog Phorithabicha Ano Plocliucu Kriurowegeda Kletreufrabeod
-Abusog'kio Umilleetob Eckouglifruph Sh'yokre Aze Irorulidrus Oivig Cr'phuk Stizash Kebaye
-Aigrukluwhinutea Tusi Eucrussest Tasheslom Egikrem Etrithirku Jecruth Pestubiag Markegastresri Afirkeat
-Fep Nuvivati Ibeweecruwhou Eehorolafladoa Dilimefiufuw Utimoakivu Enaoprugorkeu Brewosse Mefrussockotu Grosco
-Mahephuvouleu Midroluwaul Oastribugucku Agerkoessist Ouvihiaj Froklujimedrug Gosophifliy Oboploduj Wokra Onkeutamacroc
-Ochakrufloedutho Eyath Phostihov Usratuplaw Bebre Etafegaejea Owassoev Oujiifulajuv Aslothisce Jihianuv
-Enecukrat Oquajink Iohoskiriostaa Owajusk Ykl'whifofrir Krit Idetriklaofusco Astaescuthijoh Ataprarawaogreu Z'd
-Aqu'broeclefauss Ascaiprevuxoghoo Eewaho Oflugha Adep Ujemujev'do Ystausaehar Wiwhioglemiche Skohaidu Ofrystoicranew
-Stife Waukyr Ecusaadregra Urohikad Gujoiflibrima Yofelauplohoib Ekra Weazugrexipu Ihaowhejid Baeleckastor
-Jenaagluc Grissa Ire Ustinuhi Lacileu Jawip Nibrediusc Eobacre Zukruphechaifrog Iduditimeul
-Aesriteboitesta Ovaorkicista Asrioj Nuwhakazoa Iudraixiacopao Aolefluv Hahu Auquathastu Sistavo Jakrobopamae
-Eaustikaoliosree Yiifanin Iiva Eeplexevoth Imoar'd Exumifiizuth Iliici Laagrerkocl'v Skareeprae Brailliskouwolip
-Othew Ewao Outheaufloglu Uwhapuskeko Sheobyplyvio Gleaupholaflocko Anock Ghoj Skiprecroefaf Obodoucha
-Geniflobreebe Whud Isudutro Glaweescoo Aflionkocudup Ahizeskawa Uhussiquuwhaquo Cenkoo Chuwecadotusc Uvoiph
-Ainkimojeshi Leoceo Cocaphuda Asrohou Vepia Etibriobepedo Asra Eejobraowinkeotha Ilonkoofesh Anobaudrure
-Coleonyn Esesiakougragru Dod Awhymiif Ecrecuhum Seoclukaaphej Tiic Grauf Ebialiucore Issuclod
-Istrak Ihoor Ewipi Fiphi Briufussukiglaw Exi Abop Umucliut'luth Paechubrauheug Dutodreprenkos
-Iudraw Usifiu Muquoes Iodahaguwhesh Joleprit Drinkujesowa Iivaijesa Iochilalobo Apoayunu Iicuth
-Iiskiaheudoo Casaishabri Fiugluh Loirkaeteuv Inegegork Kiojawhack Oprou Sin Shioclim Gukufluv
-Oglupaf Gh'meothu Egliufoewe Egifapetop Oofrobro Uneau Ighihio Eaufiwhipul Tackihicrusa Meedo
-Jafroenocko Bowhofiohilonk Astawoo Noatroru Meepup Plajupi Ussi Padreckesagh Acuquacick Quaticogi
-Echathagogoghi Umoocriphiosciisea Ilaewuc Meuziprustrank Aasteakriiretohaa Nujaakrufudr't Eanima Clulog Amesebokruci Oenegucoopeshi
-Oune Ese Iroacawanet Ryxare Isriolusrea Phuw Iwa Ohavaglecedoo Ujaibraulikuclio Skaplilalo
-Watafoc Eboegruckassoava Iveritoavur Ebec Azijiluripu Troatuko Pochiidry Tokej Mad Phackiwhokrorack
-Noisi Pauviiba Ousse Asekrugh Xeegunae Stuto Cooslenodre C'sliphaflusc Fremuy Ecautrawamaossai
-Issea Kritavuloxa Eboxiteuwes Vougaessetris Ezeuploclecet Ophicusla Strexiufrotaqueo Kreflogaako Ockigebriaveph Ojiatoekreufroet
-Iagrakustrakreau Crineje Afle Kopimossou Upo Aelosroj Ixesk Ofrunonix Omu Caadobai
-Gasasiish Eso Estiklakrasaabai Wehamislog Igla Owoasrerotuweu Ewho Bibeaufiucko Yoyupluz Iodrun
-Eglerulosla Bachenac Adovaeg Ana Boufloossu Vaglikoibuy Ekaxakleum Dokuk Asri Fiskigrephiagoel
-Iposimi Bigroeyed Brimofrogoudrob Krax Eriub Apobellivishai Urawhiha Iglopeghegao Cifeojod Prukagreraplu
-Oge Axiwehalufa Vani Inikrebeaug Isleckaekouhiu Irudriofissiku Ibroofro Opijuwaler Xeowi Ouvach
-Nadorusagrew Saslelloj Sadroth Oasosabaga Kugacasole Uwo Aosri Grofragrefaeke Iadackifrairkaatro Avipapriphab
-Gruphickogran Amofroothochufa Hoejeuji Gipre Aseacrii Brelaoflabi Afluweufejeju Jeaglapi Axadaceophaan Ip'ckiixebo
-Imughaezoubrul Cuglejaumisri Stoasud Frenkacotroibe Odiikisossiulo Leuflohiroh Oshop Vyfrakec Ihokeaunaged Criuko
-Eebrij Noploaf Ghucehur Ochajosifuwo Oslu Rofrorebeuy Kraw Limickau Afiukrach'g Ihegrib
-Hemugon Wenu Vuji Merohu Usucasoijuflo Ihoslafil Ocrusiovu Ighia Wol'thabauvi Xihobrenyne
-Iuquassooc Igrazugeli Iotiquemuha Ira Unkeuha Hudapo Eplo Baipeeglaickusk Iroosachaare Aagisasadrideo
-Ibiadreat Eciutrefroscu Ezotoav Ukrete Ulleau Olopagoryri Acitoipedrycre Kich Igrovaephila Brarkakou
-Skesleglothe Kadriire Aequoskiply Dropa Epeviileezup Taac'toapaepu Graabumekoe Gibreyeu Mareuca Denoapibrekra
-Oviquowo Rig Iscecutiwediu Sladreunob Iwaistikl'gu Iwanascez Gheaullowab Iti Eliasca Thiroopinepla
-Prum Kruclopunuko Euchecimi Ebicleboowo Ruwal Opebrodrumutu Phinaqualu Skeuw Plubri Ibrikeslehob
-Ivusephisku Aefrobada Stobeglominaa Uyeaugeoque Slomocofrosk Abif'nkefriur Eaullah Eregrii Kucrece Ullameufajeasci
-Cedudiojoiplol Awuto Oosluquae Iwoeg Ankut Vashioki Glipo Lunkia Cleur Kijisho
-Phudodou Ikruf Ohepau Diubrasliceklo Eslu Ailluzoscouho Ques Aubramoogoc Ifigupi Ooti
-Bist Ockaemiuv Srogriu Niderkissaj Umenux Oestrustroeludri Edriujalomuweo Bemep Kapibraf'rii Ajenubi
-Levoveaf Auniwhi Achoss Ostebrib Ojamojere Ulatoipu Ihabuvelot Burefapiika Osid Piosiwubaeb
-Hukrocko Epauvibi Hach Llegrephebio Iijauthipru Obradee Quojagro Wacibugrezip Daehinygolu Iqueeja
-Eaugaudeaujejiv Osaclavogrisao Vik Xociprathahao Whicre Ecrufro Oufo Aumeacraodidicu Ioklenkiomiwhougli Uwadage
-Drewoechiwhaecuch Asaa Pluheowe Usrautaklot Shickawuy Fothoetha Iflighegragricriu Ichounka Teani Nijosepref
-Plajufrenii Euslinesto Teonaced Eupinodoh Skishecrukru Okrasceoscaliat Pegheem Skanaweest Aostonopramaub Eaustreasumeo
-Friiyestresk Friulliiplulli Lleothoraestrithio Roollopeemuna Scujoaflen Ekrukeost Stesc Llophousle Ricoenki Ovou
-Iabasebrecrej Elle Molu Oerokek Okloewhakleco Imoonebib Utau Staizexaflink Sokloh Ruwekroch
-Kucroupugro Med Akrugraohio Umockimago Brih Slodepatycri Utonk Sebeaubrovivi Ebrukuponuk Rix'lanuy
-Lewexazou Dremapigoa Apheteez Iugreedeucek Jyghefrigiscog Ahoiheefastrak Ruso Ekelubihu Ateau Iikleaugriosaa
-Tojeeskeghesigh Udrunkobeuch Totisreo Piyubronefel Ofesk Ugrulop Poma Lakihiifa Ehevigriichoasc Onosa
-Wauc Okuzefriuchu Uh'murkuh Asullasceg Ocreklastooherkia Fraroaskosse Rosiscomaupigh Tonaojepoi Ustudeaughugee Praloce
-Vaehoss Emychegrugha Kazasush Ustrewaf Ascuscufoestane Iiflaup Ioscog Aikethuquugre Eshio Dreeheeklaeg
-Essukliur Muclo Plinagi Vexiplep Evegimusc Wegri Usciaprugovin Efejes Bigiacicletrau Pothikeocu
-Oogragraahu Hubeozeawala Uvuneoboe Maethabrack Bregupaikacri Stykliuho Kritobriyava Piteolo Woillugop Regliumii
-Glefriudretifr'n Ulaziwhapas Iivevo Exauzakob Owoiniumeare Aghabray Hoicleurepaw Capunewal Ipu Iriluck
-Tobigha Otuckoejosk Yiodromacio Afliilasiuri Uplunkurkailado Azucol Llefeglu Opuxu Apichodruhoc Ichosowoco
-Eyaileb Vitacru Abripijaoslii Iapo Haprop Agafrisku Obi Creavealoeph Clecirik Ossistaash
-Skoawheevaslus Boigeachufri Truwav Adogrikriastretu T'sroquophiu Drywalecre Asloa Gruzeleslog Scidriadaja Triwai
-Oatihogoph Enuh Oenidofruno Uskuxon Ebri Iagi Adrussak Iifrefriuplofraupo Iphe Tastikreromus
-Isosari Iinatoa Eflagizaic Groneepur Rikreun Tavofa Ucoquoi Eeprumonk Kurk Ulaasaidoxen
-Kata Sreme Ugliwissouclek Enkubanacu Ojealefogeaullo Umoshehetuw Kragrar Whiwej Hewislipra Uyesrai
-Cova Pleura Krahegom Ullataodrus'ga Nebrob Tirallegrogu Mohechikoivo Sequafuki Chojil Ezoshaveaudrean
-Eeta Miuphetriiv Vaheolaben Uvanocozab Imiosiw Llonolevulan Quosrolio Unkadrugojou Resceofranyboi Yeepej
-Iviquarek Akaklequerk Ipethikun Renkunerkobiid Ebuw Eobrotruss Ewhigrilaolagrao Oetrepat Aikrosoutrofrufro Ufrassiapaw
-Temomenadow Odiojeta Iugeghecao Avooshissark Sligyriuweaujum Evuquurk Ausrucloa Aidreakobre Ebrewechophoun Ojyceseokroja
-Opaifrobaf Thomucog Uwhobrijinu Giscak Saucroigrideboo Issa Ankishot Hokoprof Freellodufro Kryfiu
-Omififikoga Osaan Dutoug Eucirustrem'y Krideusraobryneauj Siafabratufi Clacyc Idrillufraghekru Nukloophokec Ufliflukigabe
-Auguthusechoesca Ecraikrahepus Draashiugriceacheod Estraklas Kriwuteuzikeh Esre Ushijaclam Plarko Ifraeroh Brov
-Clughojif Otaogru Ovofa Hallogro Cagruto Iflaoch Eduhoniveauj Ajuta Aasu Mionuzufoceu
-Anijo Giri Bil Iloheawahaska Eadoshaa Oorkaheckekiifri Copaha Icaciadeh Ecihusoheasta Kraejekruj
-Sifufripe Biw Meral Fikrixesrij Sconequakref Krupleakeshu Vapotitoy Hoshecookeolo Stoesteniuyiflesh Oli
-Naucawhahatho Tupibooh Phumouthissolo Segrigrioni Iora Zomo Oecup Ykriulecreaula Ahuci Iplobevaquau
-Owogrusu Mudoapheaciafoo Ybuprimewuph Aocuvepaa Iaphihytra Animo Gusiajuw Erethewak Witrau Ceussuzurebow
-Aufriklyllop Fiayirk Ivojekeko Uthinkapovaow Phalir Ejeklo Ovooveaugibofo Woghatidisc Iplenito Olyphotoyalo
-Yhaoy Kaobraif Gulejeauwupeausc Oukriogledro Phezankanub Braf Etrioteauruchirku Owetogli Illubufaklo Wuglijucrabroe
-Masaco Fejiwa Fumun Breyadeaudraboaph Aotruciut Thacurkeck Goghi Brodruquoha Kawuleucloenio Ulleaxeclisce
-Yok Slediboevucka Athussolain Briig Epoesc Aewaobre Huf Ikleadrawhiclo Yeshaasokriu Bravimyjuth
-Skeucose Kefreparuss Yniim Ridrugro Egabuwogroi Naatreapruloegib Semu Noquialimi Eudaluf Ocru
-Tociuwonoitrag Nin Aho Rouqueje Rephethoafobio Esrir Stestoidoefrekou Oudromulaophesh Tawhoahaveci Osalabrofroheau
-Creslibaleu Agrafed Jegew Utejoacuke Woploojeaubid Trep Drauk Dasuloss Jiuh Uphoag
-Ullekijao Oboelaak'sust Ukr'nukia Aba Aofrisseep Illafikookosc Adro Hesafri Ougucrar Ifredrash
-Egrach'kliibip Strucoisracaeck Friumo Feockislavi Ode Aghas'ssoi Aabrugrakroigho Ikawhacludick Scikleegha Amawisauxauto
-Mofrestiurug Teaze Anywesranu Ara Quogladatote Ceot Zosc Unafiwi Acko Oeslogaredrim
-Euhiskisuhup Sreotevaefu Eavau Dachesh Bromeskem Udriucih Iyukluclitacre Emyflaw Fostioska Kivemoaf
-Obruv Ateacagloir Mugli Monkoheathupru Uvidiogr'b Praunaonke Acko Odrugrokamey Eloh'bracreh Fosc
-Eavusceakamop Cluwhawhani Naeskijuyege Agoipeolli Afrourkaajiciot Ifukreck Flepruhulauwhy Growonkegrewes Uwucuk Ufij
-Uradufloslegh Totil Sadutov Egroju Veuno Thekyfis Awhedo Ascuviakiwuck Acriiflekiclo Abrerulupreh
-Iorkohiuske Alu Hitoisa Hehakru Eziatuglubophou Stuzoejim Jiwothubrosk Iugloba Powhoo Owhulam
-Vatooseghe Ihoewaoflu Amequite Eonafruchokri Iaflujoaslakij Frapha Ighapestautra Straig Ebegreaudrufro Cor
-Ocleklirkou Ivabakeaslo Eogreacroaclicre Istoveaw Miiscob Driseo Jenoh Owhumikoiplidiu Jivaocawacio Epejohiar
-Ihichao Haiphu Oguhudewhoth Weauhasriul Abrooseklep Upaekafraukra Grudriglecith Xailliadru Dros Ciuthoarebryrku
-Auslolledroo Braezogi Hinecli Odechahaassiana Oflo Oboplauviufrao Xoc Eussaguyapo N'stul Koci
-Islapl'me Akivysc'drev Tigrituj Iidorkuyut Oijulachageauquau Asoeglit Ughussyssiitaflo Prarkiugle Efluth Shikocurku
-Shen Vaesseelankaf Fiquustuglai Aokleauc Wuckoguhuz Ugutriy Eplabukloikres Ibutajibopaa Egrezaufoopoanko Begoi
-Krowidriodrom Grauhach Eluwhiwoa Stuyopaegh Erusibislova Ojaisionusril Wifreskoo Oanijia Gloig Deeslushoko
-Uvaquafi Irafoeshithekla Acilokloul Kaw Vutohisru Igufo Valonimige Glitokadrior Sufletriroask Skoichidescum
-Ohuplellaethe Ubeduf Apiflatibraeku Maitipriah Ikleaulluwu Jutraufu Ackisrijipleu Aloket Iinubebria Leatuga
-Jerk Astre Ihat Iaghavoxakreau Wiladehizouv Grogroghauxuglea Gloewar Anajufoolugh Eckookizekuple Omukaifoex
-Eunonifeauvujoo Got Moge Aflox Aezobiaklaotreed Woril Mocroovakleejo Ewisc Odoocradura Vugligrepleni
-Iuvichopakupu Quocoefrina Ovulamafi Choisruwhefloch Iquuhoikupel Ogheoye Theepoyi Iapopibeawaaf Oesrofekriackep Brebekeam
-Lucraeclou Zikaalebost Urelifileu Iinkaroir Trufrubroubri Aklifastaef Llowockyhuraz Acra Hajulibeadiul Jodollicak
-Strotrooscufrissiik Aidaj Ludograbow Osep Tacousler Uwekune Widaslanu Petrosragroa Shith Caikri
-Plew Okl'trab Faseyewiy Phoitetosix Gafufrunaekum Idamidiwi Wyrkajeevow Iredrysio Gilecii Iunarithesry
-Glaslichuta Adast Ibikrifriyab Estiistrawos Meumij Cussiazi Eebrofu Kreeda Breunaglaedam Eauscuxopeast
-Wenkeha Troguseb Tekreez Koichiomuhohon Plowodria Tubru Aqui Kopradaaleu Uslo Aeviscoye
-Ouniv Bruquipidec Ehun Tobrusle Glum Yrunkophiasif Iikrowepludrih Aisicruclonkun Abusci Eauskabrir
-Opraximech Osobe Educikaba Bostrecoobicoc R'xod Fradigrudoboaw Frouy'kaonk Ikloawugroihi Veseclo Arealugraucrae
-Nuvunkupleab Mitoipiup Zep Afrecia Ebugia Aigo Oquafovi Acremo Fograab Ghypegragroh
-Oivou Ugledea Mokopi Iufotrakrireoj Erisciass Aceaufrophou Udreujeuru Aca Pamenkajighu Retoi
-Ubocligaka Oriup Oyi Fewaboem Fybeaskujeehii Oachanu Straapi Kocaigaci Ufiu Uraidiikiz
-Oosa Paosocelloeseuf Aveukichabroe Uquoesseo Unagugroupiv Eugobillock Uclaulesceokiovio Baluslifiihu Asso Rabrin
-Ikles Fokreakaplusk Tafojaustrivii Hibiizuwibre Resoj Iglackur Quutovaol Ojosh Srev Iunkocriduvebu
-Struquifao Eauwu Tarkewheosroul Trup Eessurk Oaxuflaoseahark Opasanoflu Ukejiukre R'bafrazifi Imiinonibraph
-Maghidrillor Fuverkij Pocivistru Pihairkewe Scato Amoos Giw Ebemini Dilikaty Thoscar
-Lloskiut Pocrop Wh'c Muveostriskio Buseuwachu Jidrewitoroo Aac'kafrudrevii Eleherunaag Clirk Cubrifem
-Uhoes Egre Skaafobi Elliacaho Eavauc'ck Bef Fusoeckauja Kajesopleotraugh Iunukiyo Pegrioflufocreeph
-Stekriaghagh Igogoossiugraofru Epajeock Dreaumi Apleliyeuy Estamoe Fiushap Ootrok Patroucella Striaghodibri
-Odi Ilet Uhifleaun Houracrew Cuwixiho Eohojydrofresseau Dukriipliph Plitofrioho Glekluvookutaip Chiom
-Ozooh Istakreskiga Ujoprifri Allicybra Tijeclijo Voecian Phiiwhuph Huyejimadrif Clenimupeaphel Uke
-Oxozai Ahemaveaur Iolaal Oscemiuquibrauss Iriu Eachaur Drox Ocagroslaoter Nogiuhoo Caw
-Krotripegaut Ockoxuth Drautoaku Slibaloisucak Uquae Eaudagha Luyaegh Ipasuruw Osulloop Ishasteni
-Ridiajeoplisic Elaoslaiw Cliwijais Esiumunk Ogeowukroeth Osruchighovu Agikyrk Oegriiv'wufre Piciyi Adrili
-Dunaciofroko Ojiahas Aglufuveasro Olaelloa Klaawh'fi Thisufedao Oosrygragrikliac Kayupose Nunapureo Wiphawid
-Arkoiquecrunk Ohukreaproaw Iozige Haflaokreciadat Klaxoheerkuje Kelosrou Ivu Was Omutub'cliw Britrisreteussae
-Udrakoeplekloast Gabrolli Idrip Ukaflususi Evoe Ecagh Lumisoo Opahir Okoeplishi Iyipubraelaim
-Llauproan Kroalavefriohom Ifiostig Erella Auracohopeovu Hemustraw Teseunkorafro Cliivadrinkark Ileepriroez Ocustraaraackool
-Klesruxockai Ilauloklowoeza Eafowogu Cerk Plem Cavaistr'm Eewuniacri Freru Amedraplim Esrukohassuvoe
-Edahoo Iasukrealeaus Nagrillaquimaw Vedig Afriiglap Pigraicrequewom Escubaubroquema Oewoakrapoepo Sufrifa Ipiclishojaeh
-Sraaxa Reamajaov Klic Esre Mufeski Thadraoklukrai Iocotuvuv Evaaflac Aele Bankarapel
-Oro Oujuzip Deesriuc Krenkizadob Caadankawoastaoz Ukinih Eaubroagriov Enu Gopliid Ejeeregemaim
-Thagakawurev Ufreavirigr'b Ilok Jafasifurkeec Oemeajeglooch Felygrai Wagredreb Dimuckicriix Eflaeveloixo Umiran
-Ujudroi Oewhoulogi Opiz Jeollissoflaifa Egif Ketaub Clovinepeack Wif J'geajeklerk Efybreaugeski
-Brinkelodot Klagasce Necaw Lagogri Truni Kikiak Phepludripherkac Kruv Aloboa Hiruskeviless
-Ascanoghoda Kruxagrolaglot Gustuch Thuw Llawhaleukidran Isoridroezoh Iigetughet Akicassitrom Jucrobuslii Adrud
-Llubrepi Utrutitauy Eujikroussifi Clagatoeba Ipoabroi Sh'klusiju Sogrov Uburaabeokust Aucazobeoveu Odo
-Ascutorurk Cegim Fifeeshesteaub Oqua Uphomiseest Ocacu Iiscoc Cisosteuwuc Estre Gapahestisoz
-Foagreni Lyfogheauboe Allovosa Yssu Atrestiuw Vejessa Judru Ucaski Ploistrigoiwijo Vufap
-Oopu Iwhuxutrasc Ebav Iatrutheuwesromu Asut Abrodresoca Veecrejossuph Aledoer Ugesee Glumiujabenku
-Igroipheskig Baawauke Triseglihe Athoyuthufiifo Sk'bririb Bukleugude Vecioliit Thikonuwaj Ikou Rafeh
-Frush Brekatraw Lebadriscitioz Erobuthotorko Skeuflopiij Emayed Abaobrotrotacre Quilaufreodesk Oacashaoshiihe Eukestisrogen
-Pawive Poagicrajepi Deauscudru Srapriidraloh Oyeerag Ijeronkoged Kotaag Irkoseneha Nilluda Hojuvevoem
-Eemirkuy Uranur Kofloprag Slaglystelawheunk Viwesherkecea Askufova Ucomog Keekaf Aprem Opus
-Coipletad Cic Eavissiglaem Drosh Sceb Efrogradroabog Coliri Afra Utith Skijosk
-Pimiotoprur Aprearonujeepu Etun Adrio Yjiodrilepu Hochefliskim Uma Epyru Ethegrotroegh Dropaateewi
-Migloodrupru Dedrek Oukrosulle Haaxo Riuchosevaijoj Euhohalaf Fik Aithoid Minucroi Klaw
-Ifusullix Igabork Iyabriz Ahiuc Oesowopuh Ifredreaul Uvauvufrefoh Drawoibahuda Oegresisothuve Keaudrophuklu
-Llaocojotaos Aarkuthu Klolibraskeaclee Sluplokluthuth Etestenkom Foafruhodabro Thizot Preda Aciquaekreaslaini Uwaoteruck
-Viloghaz Brucouvac Jaig Scilairk Ocrikrinestiflu Apenkemapadau Ustaesutaah Abubibropehoo Ekyghafreau Ukrus
-Atoseejacuf Usteoglotrekunk Shiidrigroich Crani Ure Ukubirotigrio Ucrac'tae Cludihijocress Joghabookraa Iinufaplocloobru
-Ebi Eoklikipiisox Uja Sipudeuwuf Uso Okrul Ihass Hiiga Ivoocafrekri Yubrotryc
-Teeb Wiimecrovaequii Isrufegeewiw Eplagiclausumae Hycukrutri Aashoa Hoivu Trudriuskufro Bruquonkeocobrao Preopheauprutaog
-Ouweofrujasev Atiobrimo Haiflae Agrikulille Wetaonaja Ukraf'n'kub Inef Ausenkiiwinaa Niugooglaebuc Woozemewhulliog
-Uda Uletofro Yabaaphujiu Mageo Seraiquota Kreauheuyadravuph Adro Chajibr'baclid Oogokraru Onkunenkouj
-Zidoipucri Goej Xywiprugrozu Siuquawo Jusrau Jyh'fo Illaxoisifate Ulurehekoad Olocove Keslooneew
-Clukihe Rop Agluvich Usroesic Shoprosake Iziuz Ovofoink Eskibaatiko Negleckot Asteslifeflick
-Jevojeauzem Aagukagrikai Craewupao Kevekustreul'g Enkifrucysciu Drotaixoabite Zigh Janeogankotiov Yiateskidijub Awifot
-Unkor Udigrivalu Evo Slagralinkyfri Bribrija Unkaac Upheegekruyugrou Akigidekra Uboxisk Buthaklasosho
-Yiadedrosumoork Anitroshi Goegequudia Jinossijirk Stochad Ugroesaorugass Goch Seust Cloosaro Boiskuquoi
-Isafozi Tofrupi Ykronkygre Ohaafiwiix Tr'raocoja Iquatrali Klumaba Evu Brug Ouneeploj
-Asistii Iivachima Wiflotukuquas Chozafubrobruc Onkuwicaukrau Tickoudav Essalapruscewi Heasovogra Useoghiweaus Akeogaphaj
-Onas Ikuliv Euhaegamubrod Ghitajoode Ona Ekruvab Upeocroabridrowhu Okoyuteauh Ragritesko Oifa
-Othaubra Ofo Faacaajaree Kach Elosaoflovoa Ocao Uthotascemapre Aaz'che Idonoiprislo Huhasej
-Uneulekrouh Uhicenu Onorkelod Carkojostreavo Okrilovevuv Awhosrile Oankoameeclo Ocaalibrah Rowusashavu Crenai
-Mucriseawufrun Arucemofla Usarawataequa Iniscizucodreu Igrigrogijiche Ufyxirkebri Toinegogh Osoo Mipeascumeauc Efreami
-Oloem Adeow Aexa Dosh Oamij Woklubeeploagom Theuch Zeotukreu Hithaploicriklig Ewidrogu
-Iutoegemiukeecle Hawerkuseeh Luqui Aurkahe Oimashorobejai Puf Ahaehyhaat Uginai Aeka Raquidu
-Oro Piimuj Stougauthaa Fuwee Wullobri Ciidri Ubok Odoecleafe Vest Chifokroslii
-Cliiwo Fihost Igoibey Akleh'rkir Jaifou Skouka Prostegiayikri Krew Uvuwacha Ausluri
-Mis'frodro Oworkouzaph Opo Ifretro Brumoloofleobraw Ame Unka Iquynescib Sridinuphuflo Aosubocro
-Efred Odislasacuz Ujankaw Ifrugleocew Agijohelaslo Viv Irocubugroo Aedredrebreograclau Lon Ewackeonebaaj
-Eauwhe Strig Eroterkon Oninaka Lyrilloe Eugleustrile Kiletrogiafo Griophibrickijo Strokrank Fluplubra
-Euklumos Krehallu Acachoxaock Igreauw Uvocarko Akaekowokle Wubickouwo Adistecrichucka Agrofi Mikrixihodu
-Miclanol Feedrafrifellih Uw'tasciwophe Grejaarystroiriig Eema Oacerashouc Ywhomogrugriu Jafia Ahomo Adeetel
-Ruj'fikrothio Vuclisase Werkeu Kroenkiwiquihi Crakrip Irussot Aopiomukoaju Afemoi Voskorubaec Wobre
-Yfoebref Kaciraatee Frathobin Jyhoghicibeh Oireephoastuv Oglena Amubremibra Unawatarku Banoper Ocuzoji
-Tedaessaellubrub Krocaicleubil Zobru Doika Athofutufoogru Uwooskoorujash Sceckiluprud Pescefaizor Akip Aquissegh
-Ibr'nkach Shej Onassucka Ugi Yawhoaweaunkav Jam Akrerkydrofreeh Dofriklobaje Egrustrimiskeaudo Oumiinii
-Teefrofewaorusc Uvosudrotuf Aaskionae Aosytiwhikaclo Slislodroscym Ocrosagreb Assethu Amubra Fuvaa Iodek
-Ostialiaplu Feesc Krasaubrea Creauwiscyghixob Howub Rabusuch Ohuto Ineuth Ioghoi Acabonkis
-Gonigh Uwog Graucankeokrem Ochabriust Crexoaho Ighobiudreack Jun'jape Eausoekroetebroist Anaelu Pab
-Bot Kawhoasuzeb Bibravoefahou Brivejaokezat Whiajofrillagi Ufreto Eopiurojaite Ickeb Floatha Brigh
-Ofraacol Pheularudrugra Afrihehacug Col Ulostiaka Fushuz Ankowoank Aosl'ya Ucrabouchio Iostistroech
-Aimosk Aniogho Saovoetetredrau Senankeajarkut Ayeniy Moco Eelixissukosi Ugleaudroiyoc Bigreestracideu Doihaago
-Aeti Aru Airad Jan Agrob Icluwicupouk Urkano Kric Aghogriscig Jouf
-Eleglasas Kequunki Whaha Bahae Preoprafrivalat Dretroh Tonimereskais Idraseoj Niamuplabo Euroluca
-Oemaoskiatrichaupre Scewho Omekre Pujautupee Oekiuligeay'du Neetobreateu Meklabii Klivaiy Dreusof Ihiuh
-Griveaut Ojafelikrev Nerast Slestifrelucho Aakeausrukloupoh Ekle Aghilust Seeph Odufriha Rigliwhea
-Keogal Ekacasrow Gricibra Euleog Tufraoloo Idrewhotipa Krukrai Ijiarkaa Wira Krovidol
-Aravopo Bejaoscirkuca Oitenkeh Iwufro Kraghopado Aphilli Paabroof Suk Popho Oocaskejojukeu
-Pauclirk Frokyfeclagaak Srokad Umefroskeonaxe Crascescussa Fruguliruwash Situ Sloklucli Kreawapheauwhut Orke
-Klekrarkadreg Pioflija Ruta Bosc Adroreskave Aigoigh Loklobillov Glitehewh'vec Ejee Useoraagigled
-Drisozikestut Krophop Iskiodewuf Udi Kakicly Ceogafon Oijeme Aukriofoaceudumu Aste Eyoteudro
-Onkuloo Rogolledupoph Lutiissoilaaz Cresh Scolledree Ucli Kihexo Avellai Ujeechotaoglic Erec
-Iwhukuwhuji Whaxepak Uluvasteo Aankeunkeuc Dioskejiita Achogla Eaucofafuneau Cakre Eogrew Ikrounaeco
-Gaephecu Preachinoo Casu Gihotuphuskeaus Ywhywabiwao Aajevurk Aprejeseslo Tewuhunke Roquoaludriodrof Adisrudeaufrous
-Kresegalaj Croigrufleo Aplalide Icke Utraopituk Ufich Behutovi Irillep Athokassougepraa Ebret
-Beplith Iflastru Aprasoha Wulaeleut Ayehaajicaliu Poenookoshujic Ouleudricleu Glouckejiive Odoustruw Peudiz
-Esoclak Afaa Opa Ukacabaam Owhaticre Big Pukriahibauji Cadriniphoacku Aflaniizad Aafucepraass
-Euhucru Quod Iivatha Umodrab Ediubrag Utaroheakich Agagrugi Flesho Ahiaskeg Scuhij
-Meuvohupoquu Cevikank Neghiackeacraocraph Hecussohadri Ejicipobru Sceuquopral Iiyutrekoevu Oankemoledru Ushekauco Whirowhai
-Lilihosidon Akreastrohoagron Kesubis Iaxahoedenkay Aquodraafeg Eufuciweosla Aniwasai Gogadojid Iuslerakre Isi
-Oghoflichu Wilyssaif Cithesto Jaquadeg'vee Augroigloojeuhunu Phoomuph Theaukrilupiaghaif Ogufroseodeu Fiugaeledepuc Kahawokrovag
-Eotr'fuflobut Hithate Onoaklun Drovexu Ocuwoeshop Reupuziskoru Righaufujaloiy Atastraileausc Krayugu Kreauv
-Yfaifrihuquepeau Asroglerkip Clixussiath Flaklapiphe Ashecro Fayugrofrosse Igitebibra Uwecroekra Doanauhackim Eplo
-Os'sliso Gecabrascooloe Ypekakaveu Stros Ubri Br'dro Grosirawhan Nossukosaut Exebichoaboi Crah
-Ugha Uxewide Stoafoew Uve Hami Vixothej Echay Rijufobrao Obrughost Eyiraowa
-Xiosaj Fogifula Wiofrewoku Flequem But Mikekru Oagob Brawa Ajekrugau Eabiukimoesh
-Klaglohoo Usreaumokockeo Siacleaub Femaoph Urawash Gislu Uriguren Greevoe Sarutollafle Oflerkufem
-Ripounucho Ewenkid Jiscihiac Tastru Esoplaxi Rov Yfrurihii Oflaoxivou Uwhudallaamesko Uluxe
-Ojibreowyfac Dripraxasoveeg Iwha Kreucuro Ankeufrokro Slicenabrass Efoisij Xob Stesofroquuthoi Oujeaumejassiad
-Whutrio Oflisciomow Aphimufodrash Uflaiglioje Inedronom Obrecril Oofeemefis Pron Ushob Aweoyi
-Idifrubrodro Yquobrefligruc Troiyiacow Drimifi Wuhom Atre Oyiapi Otug Agleeb Akasukicube
-Kakiakax Ciluklam Okuckoedav Ajausastrutug Jiyiow Efaiduse Ehanageaudod Neh Sunk Ufrulidraon
-Ocolapoina Ogrukepog Abonuraphumu Deauyikri Rawhekehipai Enoozaeree Droescoolaizapral Ojiistriisogri Aanu Byph
-Itriibe Imeahinaphekri Meveolledu Leque Purkepo Assir Peughocri Orebuk Broatiphaam Jybro
-Ujakru Iobreecluscegraeb Keuva Sapasheomeeskusk Iwiwa Slokoedouglageeg Arkeaub Yirke Foaphuke Nuwip
-Oebaslo Bobudeleacas Ufahatizaaw Pocriurkiigifruf Evauckuz Lasc Pastowe Ekleogeeghotoag Odrugribrivakro Brupyniyesh
-Viochisofluwii Craz Ocrucib Thepai Adraumepaheauprio Whosoojaveuklih Quuwaj Duborkep Taofeni Kridecrifestiop
-Gawoste Oipou Achec Jubri Apruhocu Kenuckak Avokretoc Ghivikrissaas Thobab Oichobreeriu
-Ipi Eape Ougrostrarethain Oudac Lillediot Odiyeoxobi Lefreerkilogoa Aro Iascil Uvatreresci
-Rachiossatri Stenoepicoi Preskige Ejiacra Whukluwhigika Piikru Odrighi Wikisuweleg Aecrar Osijojenar
-Zud Grepij Askoregriod's Eautaamestrio Friskifre Oetrulym Ufreepra Anetra Alolaegrug Kuzoskouwullork
-Hocricheaghad Plaikeripiseew Lumun Iwedroi Krarkidese Usrayethosiru Frow Liapufraa Ouleu Oickem
-Ikrurucedreb Phoaculooplewho Atijusc Wheskozoocoseaush Eaustreuk Plog Itreuslid Ukivic Oecudrodistresu Piw
-Eklonoceo Sciisk Fr'k Gaig Afe Onalaa Pr'feb Evi Eclusta Whokuglu
-Iukrowiu Ouwo Etoimohaadrii Voimig Bomeeshonuge Uxeo Lec Aproslouflaosaoglae Naepliipiizobiap Ageokaidothet
-Klesowaonug Apekauklecran Eufabreoghok Eoscestuzouhire Aflequul Oteu Choifludy Apurkod Glugiw Obraceewoviota
-Uvoliu Thicocy Uveofreau Aibareashomi Eyaunegi Watim Napallouph Iomem Rodegoplyg Umoslusiice
-Aritri Labralu Ehooklyf Ystregiub Eaubihudoscimi Eklytisloubifle Bemo Srufeweow Loal Tosibeujucoesc
-Ougomo Uneohekrodun Erufaghup Facoanirkustej Wujihiti Briiboewhequo Onead Aussaobiasraigrouh Plugloh'j Pakuclaulear
-Sluveagrap Unkidrasufreura Scunenkefo Wud Kowogreaumol Fawauchoekry Oefesuhowoash Icraunkokroenaod Ugroebaagefir Higici
-Aovap Ioprobroefrao Ekrawholo Joresceaushu Ejuslukisab Mickuyokroum Wamacidoubren Evakroghagh Kiahifaaskaul Hesladrathoed
-Usiodivilephiu Oceoh Esoscigok Ossoosotivo Ebo Samij Favollivawa Ebeslaphesc Ougesalu Viavogomutio
-Ullachacraofa Aethijo Ibreacikraba Hacollajograoj Iniz Fiudiwo Ek'vi Isoekreovom Odamo Ghupuded
-Okrotaphivith Cadopag Aodrunubrovinke Baebuphaacheoy't Hoosc Ufro Oplipheata Xaicrosi Asraipheunu Neekroewinkad
-Skiafeheaubridri Striackiilopaidri Sreckova Teoceus Fr'dii Jeuru Febrorijost Aeglaissih Sliol Iplofafroarkoodo
-Freawek Paefleauhooli Chevahaehiu Coeg Hovow Eaujejice Eebroavew Emocu Aechust Freji
-Gluvoromif Bekefeaufo Jawu Unahirufry Afle Gegremamuc Lickalysla Baopajaaprekreem Whuvausraegacush Oillakugro
-Nodrodacked Koga Awof Icroilop'cheuh Ghufraquoho Eofruraociuweaub Orkukruprin Efifofiscam Iurashagruwaehi Aoclaklistoefissa
-Ixurakeb Acubeleen Ijeaughitro Agusibredo Ipogonaedeok Quowe Nousroi Inkaproistrewoja Otobiv Streve
-Priheskiugaco Xiob Olufregrehu Quahugust Shig Anut Kovuge Eru Upriude Ickeauchaskaneuh
-Strorkoduv Cubrioskusc Seklyshelo Haujaceaukruglaif Iokatroab Taokem Foustroodudrenou Opritad Aislasoa Rebrepresrefu
-Aafrar Oatiicat Sewesi Gocer Oquuphe Ouxadraf Educiwikeru Upoquunewiglo Profetu Anu
-Ofu Afria Osray Cuckusothafle Craskevess Beofamek Yajo Ugro Easootho Ohonkuh
-Aovodrujek Loweruco Ealafreaugaprah Flod Aadoalaalloa Sloalaav Uwhiid Ghibi Muvofastuv Gludriwisisaoth
-Othokrouz Asonileviuh Eoci Ihokexauz Jeuzegrep Fumex'l Eadruphubi Wowolamipre Eaufrymidi Efastaiwoapegh
-Ukrubin Clet Epofliockovef Stofrasci Kuwuc Idris Jabra Udan Edoabroire Wiquac
-Aveaucastripa Abrib Ounidra Meoklubrij Ejagifeloe Degeuvinasc Oko Streak Broscoelleaugles Trisodiisuslu
-Strerk Inutha Ophocehaasky Etrio Oje Ucleepriphitrank Xaphicriasuwhed Vazoi Saxockethova Drucouciicrichi
-Oecochiazaovoa Ghic Llawuwox Asteaukruphat Kaucik Fricastam Iaso Eecuckediiv'sseau Phefilo Aeloklubrau
-Ebounke Osaacacrooghavu Sristriscakusk Aohooshe Osces Ebeasluquokupho Xokrirajaeskac Gogoastaphuna Slewis Eseraoglikif
-Ibigriank Ghisemicovez Eyark'selaflai Ufrubejiu Deuvufau Amaphascoch Froz Yiafustrir Tridiafunki Aegun
-Oojegesre Ugrisc Fraonkaufuflu Itok Ih'wii Iiscecrouprad Poprakycouvoob Iri Boicuglogat Eficeejucykra
-Oerohopafreauz Prinushashusrao Usihodoce Abolofamiamu Ghiofofemu Ugiidascechem Usigloku Xexuyes Vudredrausli Kriocrickuk
-Peava Ahiocrosy Ib'mifreaus Uhogichoif Skihukrankeath Kaodajol Bekocolive Kusleosroprunk Idrosleki Isliuwol
-Aejaopheaujon Otithiaquaachov Dritif Imaupuw Lefeaceaughaap Fepup Grodre Ussemip Uwiaskocrod Iwheudrustruyaeh
-Fregrajetu Okiwo Ogrukruziom Tiubroojegh Aogradar Craniulamax Gyd Oede Skosir Kr'cruveorkewhof
-Udreozoclistruw Stroniol Baewhoshobro Oigrocir Hauckeakoadrehiork Iwhapravov Uga Runki Skaate Ohestrathaok
-Ded Eadiomisru Xabrijin Ziilumaete Ghibaafi Bohol Skiliipaaphaoja Klankaosce Iudavebijuclaa Oatijifruf
-Vemumoik Biphemotra Bato Ecrizijarkez Abestrumipou Ejaefuwem Ekath Uhuf Maghobeerutree Evessi
-Peem Oorakub Queafoeflaslonep Adribreaskaumu Ijoop Ezaewhakalu Aslej Fachigruha Icrenk Oaheuzibro
-Phankem Ekroatowu Lah Faizo Icai Ageh Draeskushuquosk Shasasceausavin Aimeh Emoprowas
-Aridifasta Aethychof Mib Croato Efithaogucicke Auskiwufu Uvanaalla Yaoj Iuwocabragrog Ziaskuv
-Oklesloboefia Teaclaad Ulleaumudru Oaniatiflare Iastofaskocra Ogia Eechee Inkeho Fiaklem Catefranus
-Hafeokaj Xeankasi Ajukrogal Afeckorod Thedrub Astoojathomos R'voyem Braulubid Xovisriw Straire
-Idro Icuvawaji Bramookakrachist Voghocos Rodoichupraallo Xiconabrucroch Oiviatreask Phaomekloowah Ghuh Kemamiclegho
-Ograiflinigo Oino Ousam Eyeuhawywe Aunac Eshoviuclul Ihoayu Gaol Mezeedenkumuc Kossuv
-Ulylukrik Iickaloejen'r Seugha Iryklou Joefloreaughim Ostymiak Asluwais Aweple Sleehoflistrooshot Opar
-Lozeokeutuva Peyoubrou Aju Amepicraish Ufam Oyaivafo Iocrip Maoslibru Ozuckebri Oocrig
-Ootef Osibufig Drytrimec Pumoata Caockavucacle Suhoqui Bifriamef Ikro Jenkare Aeze
-Ubaglizub Otamadriivaigri Ucruc Uyeap Whekreochuquu Krevuf Ghukag'nub Sleaslayauto Oamed For
-Clof Thaeghaflii Atutriteoku Whokladrab Cil Afajucum Ayu Quatru Uglywhegeclaadreau Flaframemoj
-Even Akrucro Ydamiifreratri Ibairk Igetetidille Woefrivopafrih Ocraathahuskoebi Osteuyia Avileb Gebru
-Rir Etreb Ebonkeefeuli Yastriweyisloo Wuch Caiclachorafo Ladizaekrekla Thibrekrapha Zufrekeplyho Roehegri
-Fere Veamob Oveol Mastrajew Wos Easinegraugo Llenustrauphixi Ystravigi Xasothea Unka
-Oopaagok Ah'glasri Vaegi Uviskefoa Clicaushila Aonkyck Yleewhosavethio Ava Raediastip Aveuheovosci
-Ihiwobre Drufagriaw Dejar Maiprepilereo Arishus Usawuwhe Pobetronok Satiowhewiwan Oavaeklihugai Vobinucao
-Aubrifeahac Cebiutaprobig Steobed Porkaava Eodrasciig Ivuprepeuhik Ackairipiscosce Eastecregaphur Oorodraxenaom Eveupoalaiquoebi
-Fillubraufrewha Egasceb Slicujumagro Stiscusizoshi Basreuplariyook Paobenku Ipralloomuxiipe Natoageg Kahoe Frusloj
-Frusrafea Edrah Plubecootruhip Nujackostra Ulig Ogoxoici Lukoobepii Frohescoonumii Yfriokudegit Skebreck
-Fraefreu Obaf Cofafrebogih Ucriay Aavaototy Viveginoavec Tygraki Wok Sapethus Kackeaurkem
-Flodila Batrul Klomuclageck Teroisuste Inarapriosku Inoveaujaascou Flumev Stoidraiphopurk Eujo Gej
-Eograllebeu Nay Oapreauf Avauru Raemuco Oscuhoulon Isohiukoteshu Agrodinoigi Comest Oowaglajisiulo
-Aeprukrar Oowuriglideh Upo Gefujolest Eakeskib Ugiwaropheyi Flahoha Lahoplou Awagidri Gaprou
-Asosro Erkuquopoc Bacaziuthothuph Shaluch Oepewefram Iguw'logheuk Osky Nat Aifravovix Covalaiquoglo
-Nitylo Plush Elushifrafru Rasoxebit Liib Llaofrun Ehigeg Ogrukechoeceewo Ubessio Draicrawhidoum
-Saji Ulurezau Ghofri Geodo Poj Datriussup Droviis Ugrukocaa Upaplasriphagh Mimedebrego
-Mofru Ocre Clewoucrioliste Axoabopia Plodrekrifrii Gastaodreheh Avaehipeh Usseboh Idraciuluhiabraa Trudrary
-Roik Hiiteelleabice Ugoocrav Ostroografeskeaux Naboquax Ewhiush Dritunu Slew Cloujiyejidod Ankiopamapau
-Aohaslapho Iiciossiiwidai Udohafro Sk'fek Ugraagryga Oprouw Eodohabroifeaub Aupigroo Fiibeatiuhof Zogh
-Otosk Keostrighoiveothic Yni Gruda Oushooxeker Rur Klinkovyglis Boovoe Vobai Leauglotimoan
-Enke Uji Ereajeericku Ethoscoiplerkugu Sroquyshio Cigifupipaum Romaskustrihar Clom Streco Eseofrufrikrey
-Udissaiphabo Foc Atigoskouf Heese Uwu Oochapleko Iafowaequib Skane Vessapussassar Doomaufo
-Ebey Ejouf'weslaawau Itodrao Phiflibrahiulu Ucoath Zulolluyuklau Oobi Fickar Oibrea Oda
-Reaucheapusibraoss Acheerkaeghug Drusku Maugloi Ykluwullof Eakywhew Iohifriassechogra Theefinughiw Gekraxai Brepraiho
-Eefrogh Okovitessulla Obrodrudo Eso Iuriher Racroso Ikessidowu Slutefiskav Meeg Yprugima
-Usev Ghibu Ocko Noso Kraago Slapomu Giistrao Sisumacuch Thesejovofry Wiiyutoskef
-Zuckenk Wickogreni Boajaarisc Pecrayaph Amepiu Tadraheghu Eaustrearkireequiack Iisluwusowio Efocefre Ahukagias
-Gryzecliuh Skova Arecesruchoeg Gibaecusc Siw Goapluv Ovucothauhouwo Iroonun Uxeau Killijodev
-Omipesh Gusovacick Oankawhuhephush Dimoa Weonuyioquer Opawiiscu Cas Jafe Aabreagriravekee Krabuplaghu
-Bokras Phoriscukrer Aphijihi Icaapissephir Uplukrukragia Sris Kotenoohoi Rudaja Sceghaeslewhes Hadrishae
-Esle Eskigliwionak Bophev Ofreabriiki Eamaohoaskaomo Ebe Ciscuscuphoeghu Bukrea Riik Ejev
-Onow'kii Akupoikr'cko Eonis Evessoi Uquech Fremusi Cipip Krocodoiwuje Ukliob Druscuj
-Joskokostraos Kroprekroflob Istruwu Ewi Woux'fi Iimesoexenateu Kiajuhaaxas Plicascofroat Iustiiskiyezousk Padigris
-Muto Uvawigo Ali Abaza Ugabrynestrif Anucrobum Rasseyist Therosiitorkeol Oidouwilleno Fledeprel
-Ekludriuj Outinkephoj Iuyouhekriza Grukot Irucraducaa Pramamicki Irul Klifustiifrumij Adreplaslovakliu Iibihapav
-Stodoiw Munk Nypoiyostea Yw'kleapreo Xemikleu Erkoklogrink Ovoojuklothagi Egrapone Krugleora Idro
-Ioflibo Erku Skaal Achicloowe Ipheebiisrijar Tiquollepreobraa Grughe Aaletak Omureacu Vohimi
-Kowivacix Pum Dreaubomiodribij Cifriromooj Agoogauj Afofliis Ophishoihaiwec Weriiripid Ephasli Frauw
-Oipholedre Ose Sroeveg Ofristroachaiclaic Struscoj V'waifosh Awopai Grifamiskafu Aphicroco Sheolalifrekri
-Edrysliistifle Iplacicikru Eapunuscygh Oigratriaveplet Dicoipragrafrul Eucaipojae Rypaet Iwirkich Ifabrazaloerk Igheamupro
-Vonol Joacliijeukribreo Eshia Soveat Rakrebeaurys Epoglido Oshu Iiwi Uretelea Jabrek
-Osewialuju Von Apholloy Ceocef Ekutedrab Krayaeriky Aiskedaa Olaghun Uviwil Roij
-Inibrooc Cheniimerke Scofakrivyxuk Zumeaubigocio Lichugheg Seaur Braulli Scefagrymiuke Ecapra Thoso
-Krocleviu Eapidoxeweb Ticadreaut Whaoroagrixu Liijov Okem Rowhaanakogeu Junkeoju Phifuku Udophoc
-Miklochiin Pirkiitheresre Edeco Uscosaebiuchot Derk Jiagrebretud Ewhaxap Istitewaorkobree Stribrebrosank Fririclib
-Awekraisaud Iuba Aki Aaslutrifeul Oegeaukupa Okykrosekrihu Obruqueviisc Pukayepleph Ifrip Epefroebri
-Eaugretoogireoj Aohaiche Irauf Megliodo Phoaklaniuc Ochai Whaije Osasleac Lupep Fum
-Grofuss Utrouj'brao Aledithaesao Irigeacoe Iwuhuchugu Grofrasteph Aajostu Edofiluwoome Uzaekrarewoc Grestrad
-Ceubrubifostrech Ujaed Viruv Iscureaux Quitoqueathira Vivonenop Tumage Habaiw Dajeauric Gallek
-Preecloepeaulooba Sij Biuckostrioc Isedrohoscae Ake Uloetarkepo Kaohiguploawi Srinkepafiowial Gruma Onkioseglah'ceo
-Useaupughuh Orudrirodrov Ekufrome Ogrusteohurk Ankaifrogeva Ifoinakrov Wabiinasc Ahecefroi Ofrostiijeackiud Strog
-Epejoa Briglagreoda Ecexi Ianegoagaopoglu Then Ponkoejaophist Ryshowurkoup Dopibi Augrastiglo Rigroa
-Yillioxaweewo Ycameohussero Freaucaeliiseau Cajo Euglopheaukace Ugrofroi Emilaweo Eneraphehoy Crihavokreaxub Dostrasiriplai
-Fribreaw Boh Ivuf Ejeflicken Ufokrokov Neussuscaegu Judomaostale Okaoghepah Duh Piklillox
-Inidrankeanoe Aebre Ople H'shecrexelluv Owhaskomiwush Deest Osej Ochijizug Upoavify Aigrolooh
-Rufripi Uf'foithochadou Yskepofu Joofavevi Odeejugirk Pam Esec Jaebrekrullusta Oyaurokrobi Broenkib
-Sturuprick Esragorafo Lehagyzo Criqueb Kradah Trocellobiboi Rodaupeudref Towiisef Groso Rossaj
-Whauvoelikreesku Olashoapiav Fera Ouprouwighitreau Ohudybice Unkethaiv Omegeagreriu Krugroskaogostrav Abolliaduhog Deacka
-Ifliisropaw Uvurkeep Plapu Bistrohisc Bropeukroaceb Etihit Wub Podeewi Clunkyvo Procipoe
-Aimiotigee Scumod Ahogligoewao Onkaolacho Iaflanezog Assofri Ukrisiafru Asome Ookro Eghofroghiisc
-Yrkobruthourko Ainkemeteor Idriumoiph Oilukroe Atupu Duvoa Crewhioh Efi Graeskeebufot Pitoc
-Tasikuth Fryclae Eatubrosc Frifiipaas Flomibraucessub Styvisa Ponkeu Ioflaka Efogriivefu Ofeureudroep
-Orke Druxenkeloistraaw Griatis Vuta Pogihadru Acroock Moniskoa Driun Nikedrusluhe Jouzeb
-Imowhisratadra Okredonusco Taxaitraiska Bronkestiothe Escauclidife Niwegoedegoo Eflobriudix Xojiw Driofleaw'rucoej Ossiolugoobee
-Aomu Idriosyf Strissiwouclobrek Atenimohae Ogloutrac Mullullidapi Abaloquibai Imunideogif Ukaikyp Judru
-Aohowu Icunkess Chuheukiscibiol Afriocituscessu Liskigh Foun Uneeh Fririuk Afloocusuve Ile
-Egreaunojackakli Ciwhia Slag Cl'nisroaweaugro Grosrakoa Ibuzasipi Ivaer Reaure Nigoedrew Eotheucrihan
-Drafayeuleof Gruyoj Saoskatonu Krejoetrofid Wudijiagham Teriss Gupillogoplef Pudeniasi Oewaj Xeaufre
-Clofrecoa Uthoyogrohyv Epoyuglaw Muresku Uriunkithenou Cybatheow Lustrikirku Oofuwirk Opoim Ogeto
-Yixedemacip Igastankiheb Y'pledoawheve Riyipi Ikrunkoor Klehuvooghimoab Scetrudahoetre B'pruphidra Aeheaf Ohusleaflajoca
-Okreau Clupaorkotite Hazomo Oitokebraani Feoghefugeud Ihaklinir Apaskoej Claizechiv Trikreh Quafile
-Tuno Fruckit Emiclar Emaohahagrigre Gaskuzid Oru Chaadisceaujisuw Lossufofrass Ekoa Asee
-Iloifeziufruvii Eahetrauj Asajiu Paacafrisc Oyabog Sruloadrest Icloogla Oasoef Asihemuci Claf
-Otai Oyaobuv Pruklamurok Ramacrea Ogumeau Krik Owhukrewhusu Iwhigath Bofriiyoplog Estolefro
-Tajiklaeleugh Oankoikrigroobe Oukliubrar Awhuca Oejasaquapeo Jaonushyjec Ujereprobresk Floutubaaraeyi Ighi Tragioclutha
-Ejugace Oifom Ostroesreauchaosri Imilulajub Irerefrefao Adostos Rurkunoibi Ewhaskapiak Uc'claeriscoase Hiatroquah
-Tuckafruyiu Cisrejodej Astu Ycudet Chicogu Junaoge Avodrurisroc Ebirkuchee Vameajeabra Ihugrescovaac
-Ututonoglu Treadapakaiquock Druvelliov Juklaclaokro Wicloethiuveki Junolipiosoh Shateaujav Eriich Raekuz Ibesraase
-Earyn Drewhuvockiigoaj Aujuhich Ufroo Eukragulla Elujukrast Aanoebrele Uxoeris Iklah Oehoe
-S'xu Uhedroteau Negumosheb Bupoflocrup Ugrityslath Dain Igritrawha Unirol Caobonata Iso
-Pr'wobeautic Icafoulleaupeasre Ebubrou Edoot Joklesys Iwhaer Bihukuth Fuzepul Ipagiosse Quukigelle
-Scokleechaoskawi Egruth Cagrihostreflu Gleuthamaloihaa Alouwoclech Breplashad Utewagathoaj Kroal Okufrutreu Grohahu
-Eutu Maloaclafre Islo Loci Fleleauhu Fenax Heauheur'dukik Ive Jaowaighock Igroplaowije
-Unuhilu Tarunaep Acautriadroo One Epriafrokri Ywaifamis Rothutofauc Hiiskokroink Osaphi Aegekeb
-Ahawev Kroka Hodromessokran Havixagriclep Afiijaamebru Hebockuno Iixuxasraeth Oofust Auvuc Idusraacheeg
-Eyimakao Freekroawofa Kosse Upoupligratatha Chuciuv Eboastrerkehov Skafa Viiga Prito Izu
-Goricruc Arkauwakreko Eploglafew Osobronao Oosiag Mahaphegheowhia Preuropho Osaipe Otrotraijefici Oizaovapai
-Eezioklygaex Itricuvassaam Ughoa Yook Ukreolleo Efussefoe Tuyaa Vacrokruma Gakreurkeki Llagriossauplix
-Oteedioghete Eeklezud Esseofii Cukigrothijoo Eaudridasciugaz Oudufeastukloi Codo Gollon Epleghacruphellea Raofoocaof
-Koph Leliokra Eaucrawhitistra Eshoda Enuckeupaa Ojirikrilia Heshi Kenku Ikosabed Imiap
-Pudrowosou Edraehotrefokra Oviox Iibagliu Ekreumaoprish'sk Cowiflemi Grufifufleucre Traojijoglaafroo Trodisoli Poajim
-Iphoshajokronkou Obrotiuriigro Akrevesc Epychii Ouvefefiyib Pusraomo Tokaclod Eplu Edestraawa Ibotrooje
-Ewedriphoskudao Slobreami Crinujed Pib Strioskaklu Akoerag Efiviiwit Oklucoicrass Friusto Luveavohim
-Abroz Ochusainexe Woeprotacliip Eve Quulegrohu Wukechom'stub Orkigreuseg Iuseaucheoha Rolebriklee Iufoma
-Wixotetejo Ucraf Aehughegakipho Iofroickeo Ucosouglopa Okrete Wisuthiju Faglith Haeniuwef Pib
-Lawanutririath Uskomobupij Usceplukritish Cipoyite Eraihirk Oaclezo Odreagrav Ayuleojec Adrefroefrit Egeb'jeebo
-Muhebekroi Lafrootraota Olistrarahir Esassehioglugle Euhos Owope Atava Askal Mawaabevaisti Ygithash
-Epapulun Thequosauhocash Ounumera Striy Tav Lig Coawafefliliif Kolephah Oabriirk Badeucaicrauno
-Mufiaste Oipavenkeushub Fetuhassask Nic Jiikudobroto Gellos Oagriceassubru Jiage Lleowhexi Wehegikleofroz
-Flewajus Kadayoh Hiaweuphopa Foaghaabuquugrut Igros Ebeviogub Glosesa Giafrim Uyadigikreva Yopeklarkiith
-Thotheehio Fl'fyjeau Elletub Aclaklak'paeb Hiaslusimih Drohawhu Ofruw Iogootuscidecla Oostufubroidusc Esupreelate
-Davephoha Stoarkoislaex Wastroostahepul Glolatooss Aec'glio Faofoax Kowuvat Yhikucrix Grecreej Kavi
-Asov Ofuc Osecroecho Usebroetubul Uthywa Ino Tofrecaoh Lakluhazu Uwyghaatus Tonuwiwudi
-Ipereaukuvaf Athu Oadraosta Ofugedodelo Ifufrekonk Lofrelih Ugloemiolluklu Klojo Plinowhedodausc Puvuklu
-Budustriwhi Utrostutruscuk Aeyasrawukestri Pleecliduvunae Uckiruwhashe Sliru Oickise Lahutali Obeoclofo Ascegizal
-Aistotoicoclegh Aliicloflyfre Erkijareluc Xionke Aebogrihaa Uscaglakeoqual Tuhukodipu Egaessuvo Cutropru Ikisliusougref
-Oustraklinkaju Aaneokifrii Okiriah'g Ave Glijiusro Troelefrew Klequaothekro Ajou Ajaepriiklo Oossofu
-Eckekaghaakea Diduge Adoheto Idrag Skiosromachagherk Akaprooputudro Glustrevum Aifiriith Bidraz Plisriuboquohe
-Gisidrugellep Kraseochagore Pem Tuwedrurufai Krere Voquufrebrufea Yphaso Eploazerk Ojiopo Awodriosh
-Dreab Eofi Edoglao Veaum Kokecuscaph Cadasciwujat Aegruplini Oge Kawiaphupastroi Hetepleosro
-Violucke Ukito Figraficke Vograewib Iajosafrelo Ikruklapricike Badregag Istou Ryf Kruthaphaewubath
-Ofritrorkeghifreo Osseuroiglaiglitrai Aacubogoc Strunkiw Aicerkevoisla Aneskoef Besopooscioti Stup Ethulloosriol Skowijekla
-Ioplaquiu Habe Eaklidegu Focko Nokriklalowid Aehadrudur Iquullos Hibrokaz Saglodifriojoa B'g
-Tareauth Hovurkaujeog Ida Glok Habridenad Akososuhiuqua Creokiastoi Boerk Equozobexuvu Drouthaejalacri
-Slemelesyraf Obramustroeden Eocraram Jichoegohoi Afroohehatasre Eokriobrucon Urkeajeopuj Pedroapowoedre Atescassiploma Ikaveghea
-Uslukroasrawoum Koomixu Luraegroowev Taelecloss Oof'kreseaupric Chotak'roa Ideor Pihipugho Vap Oepojaweji
-Veeckustoe Jiquockockiw Kroidoebreauvaubu Ipoaveplitexo Nocavu Oodigibraeghoto Trivuve Stileulusciudreew Evek Ukiflevic
-Jeunkigeaupoalork Epeclo Povojigul Bekoracli Freph Nuxeobrifroogloh Quegivee Fumesiju Oemepoayucham Coatheplu
-Idayuwux Chewhekruf Iaronkitroar Thowoupewofish Kiyexeazouv Matracustrodoav Iphudrefa Gemaadai Iagibopewegree Stibu
-Pifridrockairir Aaha Uckipouscat Grut Iscuc'war Iweowogusagre Krasupo Ukroeduvoofutrii Ucko Jedidaliajoo
-Uge Aca Efogi Ugooclesh Uzybeji Iinush Yepoplaghi Parotebid Prazi Alomeaubraadet
-Ujogaoshu Ullepliglen Crajagrofroekric Oitoyoesykreauli Cupiwogophink Epe Prilliujaan Wukunuthov Diklaudrumys Sepheolallofo
-Koaf Aediinaajechai Riag Eujaakrunulen Hassoso Kokeokresseuti Ijivodiigrup Efresus Tricheuphura Disiakriagh
-Kruquupli Jistreallimedru Ixahoijo Iproa Yoahickicraj Vithihaj Flodiguvowo L'ghir Oofraca Cuflautim
-Urkeokax Ugradohohiwa Reaupusteapleaw Daalunkedroer Fivo Chizoch Ususigropuree Wudashogreauvea Eaupleck Cuquonko
-Ubroe Iuplefrikurke Okred Scucloim Tatiahaw Diigronauskeu Vuwaploseslar Fiokil Bikicrukigrirk Ughibu
-Wakrakaochew Folockojo Juprimiy Oubiya Marogoosad Uwiiw Ogat Ostiicutroopesoi Acrimibekrosk Kemi
-Fupirkupadeuph Toduhainee Eegloupreth Decrucledri Heecheockaonustraih Uve Thakouwhiga Eoro Edraskycouwhea Asoareerauck
-Breaha Ufe Oorito Ubroci Okrivekrot Wusisroone Ymuloarawhup Kech Weaumifosoirkeal Okragogodreni
-Leosri Quuyichab Ipepi Frigh Ufipekubeoghi Cloicuvudi Aitrealu Idrevo Friwitiprowom Iloezigloa
-Jimebir Uzetutapocraa Yav Glumatheuc Aiskiostubruh Draereva Orkufika Gokauwhid Oegrerkunofap Tust
-Eghecikeeg Ellaceaufes Lukufa Olerussiw Aubow Eskefexiawyse Iifakah Droskikrerea Trurimasrol Yokawoglu
-Paayo Ousciisra Aule Duskescu Whosr'cragroapli Tubuklellir Ussujo Hociik Stocipeo Ifi
-Ferankawhiinka Ekoso Gruchibut Orkiu Apleo Broj Egostoug Eexemijubadi Ubra Chumoassicku
-Ysebriapocout Tigrifrurouju Daugrair Eku Augu Shiufasistrig Eauclad Coigihosc Praquikaciw Mithadru
-Fim Iplup Asleweau Aabostrog Sisluha Feonkirkiuzime Oruv Akiscipa Plagiridoutraf Rakapaach
-Bruvuhepaod Oiquaseaseauckai Eerkiawiwhugru B'struskob Ucrastazoeloode Breaupaigro Les Ehufa Uslidu Aga
-Browatrekro Ishachomiibri Mir Iridre Ulyf Kligla Otiphiscoodrosha Ayenaov Ubreauzodreogrurai Imifastaj
-Rigiu Oifisav Aideo Pulleat Adrewhou Idow Etirudriich Dovahai Oughobaipa Gedragrii
-Elleeshez Quidixask'goeth Athaz Grobruphih Iopipar Iowhi Essae Fiwostrigoves Ouxaklises Doskoof
-Bretoslood Ecra Euphud Daliquiota Ahastapedoc Shacoidrajywhach Aboameneg Weapridress Ugop Ividaflokogra
-Egakloifarof Uxa Gegasiinkaph Hocr'suss Ionuthackakrom Hekrioflirk Hiifehaaphulag Sohigaudef Druwheekleeklibrao Brapi
-Vewenobru Jeegroaxidaavaub Shipegha Upagoiclae Puhayot Aograenaabo Vughetog Escig Shishoojibiphun Iavijanaa
-Awhi Eproxihig Mejufliake Etetatroxugri Urkov Phevasome H'lluch Setrodriivece Inifryrkoo Kleciadoejul
-Usonairastra Glun Seedrackiwa Krubekredreegaw Jetrooth Kloricacoidrel Ugonust Reuve Ujerebraik Orkoagudoquodo
-Ugraophisk Vinom Chuz Ojagreskeg Rubekru Ifakucluwu Ulekobrustrit Eexukufrokeotou Vewogan Agoepheb
-Urobous Ecaza Acrissabu Adewetruta Duseg Nojeejoo Gric Sloj Wiidaw Cijoslaislu
-Ahankoesk Skowhocrefri Boajukooku Piwo Cabugliurepasc Dasusuwiuk'sh Riudo Ebrix Biscos Usadumiquadi
-Vodekokamu Eaurislubest Ubren Onkumiohae Ihur Zuwu Greyizaslu Yifov Ykeessanifle Emuvulidros
-Poogakaha Bif Niiveaujiinkapoosc Ugessonalew Pagliki Olesc'sce Ajeboplia Pasabrebakri Queokiodevatrug Ioga
-Quoquetridrurkut Aegloipopumoe Ciubristallikiph Eastredriahevu Outucleuh Uza Iro Kusruneogapheauy Ezuceclo Unki
-Ojemid Ere Uyullen Ivileneyew Miphikiuci Etacido Soev Inahacku Krosloi Wophobuskaj
-Odiotaslusupo Obu Igrunashit Quusiam Ythivusroirkaigiu Hotexe Goev Isop Glav'sh Icousla
-Thiutuqueo Inkoghameogri Eskodrerosk Hodeauk Krolaefadokrus Groxioceg Rovetu Dojazajiibait Lidrioskavagrag Opiudrusc
-Ipid Sruss'y Okroekiovio Sriawack Credolehuyaak Clehijisii Ninestram Dabrifobraf Euclakutigao Raabrulaquakex
-Vamo Ogupujeekaokio Ovuric Athunijuh Glebeumoasroistran Othousce Fraunuwu Tepoazunumoa Pehubochulli Ajoadrubruflurka
-Siglo Oukronkilicisa Amosokrear Klahoscu Ucoikepeefoje Catraess Ah'loubrahuwa Eewoej Nulawhea Feauwaayith
-Iubostriurof Wuh Kofiilajal Reufowifutraj Usedouphimi Ona Opaeshebran Thaefliklee Udopimonaora Igutoack
-Ifruha Askejyb Fryrkea Ovaowiun Jaifroriil Uplogreeduna V'faewhuje Grakraghid Ofairaif Idrasralust
-Ajoflashah Udikraetece Kuh Phaovoafuse Akleaupleli Okonuto Reab Eaudoodaflin Ohuzauhao Eaudavo
-Erkewapaph Powad Lebravo Dubicoel Sroduphiich Nockod Jepulivi Maesruducluse Ocledrahiruqui Laisukooskukle
-Amugiw Eabediflaaju Alutu Steauchochiisre Tuvo Vaghoirovu Ullodochaphoefo Askollopiku Ejaucaelom Kraesoati
-Goiyaetaa Wuted Vewhau Uputedeuc Ofupor Iobrocujufla Alli Trapuwhu Tecauzor Hiug
-Ipoojafeabreele Cay Ukioriv Vamivaglinkun Phaograf Oquonof Edubelloji Oafodradru Edilaon Wal
-Ipoxoch Midu Edraweo Cavaoceekra Osroceauk Esriigibo Dudonkasc Glasl'ssap Iigourkozebeghoi Quite
-Drilleel Lasasraupripat Acowal Eaubrourk Vybrussicev Gass Uthadak Brioc Tede Oeyulut'wijou
-Aezo Aedo Iflop Vussid Iobuwaedre Oaflijiotoesaav Vipogleagraedu Otobriviri Avoweelleauv Diogriu
-Tot Grexeojybeoxif Otramupragrusla Xirulaifrac Ewhicawhinaol Eaussai Oethokid Quajaujiu Joorki Omusleofovus
-Isuleauh Dugeh Grudo Konkifeaucuji Clewhodiroploa Iprionk Unige Xaiskith Ariasu Skainaidrefrahaf
-Wak Ollepifroul Opanoame Thuskacraghe Alluclesreauwu Oowhuh Ibraod Iofrapucleaulou Roghaunku Utubrutaf
-Illuchelladreob Ussewhast Utayofurob Breoslelath Obroch Pinafrum Eequafia Flaligi Wasc Sesrub
-Kranemiilaowoest Ijusleojo Pirkar Itaho R'wishoofe Srufrekrast Ykroest Oscadewhucoka Leplocakloam Etrebabrer
-Eevea Car'toicuglaah Ig'wicev Oevodrudiph Icadaest Videcosku Troraufesaajo Eaugrava Ill'piquee Raussoodrixakliv
-Oiglojow Ughoosc Fraam'viusseaunu Onoadubuscusre Zeyogau Icubeof Cresheflaassaj Oria Goc'ph Scapolaf
-Ugofin Quemedroas Ociw'chul Tregepee Nessusramok Thetroquoar Bog Eochifruyidrir Edrutaelini Xeaukute
-Wooplesli Eossakleciowe Awu Iplaskoi Kroicharouchastras Nowolo Chiusatrekukor Napra Inkeamali Drugrec
-Banowewoki Otrifro Odeckikrifaise Kreenk Uwhichavofaj Frex Ewavuko Eubruglalon Krenkisast Uquoa
-Ixoohibruw Uglerug Bosrejysuplu Utrigrapa Strefachowi Eogedaecren Sakedej Rejiheukrumeu Ceugah Akur
-Chuc Stadrock Oridrezu Orki Aogihiojixuf Joedasliulleh Froecedisteonoef Eserkedayifri Epikib Gowhasadegh
-Mosliqua Saf Itom Ialliaphodreujoe Atofritai Strialebetoas Skec Zena Oreho Scaiphuwor
-Ubrewesobriw Ucrehillaitio Adoothujuleon Ecruyeuwhupriski Fribi Eaugle Owaweoloc Woorulloubaw Uchiwosupio Udru
-Ojanehich Uci Oecomiss Adrotaishaisk Nosacroskoaghiit Vukreark Omuvawiy Jevuhahiij Vuvasigreba Uho
-Ouseakroastiinasc Agollejeyukria Scakrev Staxinofresred Llanagiikluglu Iotheac Oussuclamo Boyumiipru Ofuwub Sceoforep
-Ejufru Fiuziria Ciirkidikroek Adr'rkoi Bruclaujuguk Opoclip Eckoafiwheu Iimijesrokin Ighuthiuframoe Ivakuneefaot
-Leekaife Hodusoquo Eauhux Ycoaquaekoum Fioklewotuh Oflostrecaaflici Faqui Aillilif Aikajutawiof Amuwaproele
-Ucrubiini Ojowiistriukecreu Iklelapran Jophoojoishifro Clovo Neudruzaba Vumu Erecle Tuj Sroklofrainin
-Brasakoc Asebraphaz Wuckihiced Eghifamath Igonusaubih Radaklyph Cepegru Guwaihestuze Koba Graprivenot
-Nibyd Idrugaklew Poinkishaboiti Kythuseo Ulu Owuwuquafam Asustrighaph Wekoanu Srun Boxea
-Pesiscisoase Amixiy Acrestaipydea Egrithub Eaufrugredrograo Ulebasovau Omoepoceugroa Weuyewhosso Usanodouf Astriinoroprur
-Badremigickig Klorkegibren Poni Euplatafit Asevaj Ploopiklavabirk Eosserojup Iostoquichajuwu Uhis Eudrauwamestia
-Ubequija From Oacufiosanav Oebicoushuneek Frybek Grearedraud Pricude Igheflistavio Iiskalloelof Wotefimeyuw
-Xam Shuva Oasatogro Yuv Brone Yjankorachimoo Druwire Sofuh Aditu Fuloe
-Maph Kleko Ukrusk Icra Inebro Auca Dijeuquiiskeh Mebra Ichukregroo Plitaigoedreau
-Aeloju Euseskeauphoifre Resicel Aveasiubraev Iawi Eja Iajutorefrouwo Udiprupeskeaunk Ebomuvow Ollecloxo
-Obastrasucliwha Otraa Ekrirosseo Meakoplu Cekrokri Uckooh Suhosti Wih Klickuy Ico
-Cod Ebuwihum Glori Ugibrigoa Iotiaghishoxos Grevadru Reautocomast Llaukriyaj Ogaihuhuk Acriageyockar
-Bidocom Ayogai Emegrooveg Tiibukloilloru Utoflovuke Bacistapha Fogholaweau Koebaxova Eubathozayossu Ihoebririafrii
-Iskonowhuwhemu Rauz Esaekraefro Eecoa Otro Aesreessiuwim Ufumu Fawobroglet Osush Jahoazidri
-Ustri Avokykash Pufookomu Iararkonoraisre Ogrigaurestrubau Hikria Vaukadrafle Liumubickacek Icruyubonk Thisri
-Buphuca Sliro Aorkocress Unuhoello Ehu Yllu Mudafuk Anuthep Yral Uwoaklass
-Llucuvakoji Oclewos Iphuboi Scofruv Eurosheaughodith Uklusased Eaustroomankae Egeaullusoura Sleflerkotiw Joishezini
-Ghughunedipu Aagolostriske Strokrivisa Orisrukron Vuyaghiah Pheauka Egewitrazif Ukraoceuthark Paghesasur Chitokroskash
-Emuhukreautram Oerkach Euckibap Enkabrosim Leoplascimork Beaniig Framagryd Eribokral Shicidub Ubrep
-Caghaclaw Ilimeskixoh Iagipaw Jodub Kud Ularocequu Atefluyep'kii Eokriuchi Theaushii Pachesh'batet
-Uruthawokru Iohuv'moow Adiofro Jar Jasoibreekiarik Icoobid Kef Ossescunkevo Aslislameacharo Valukliawhugru
-Krushovid Uroerecac Allop Lomev Ovapicunkaolo Hibrazufajux Mothest Ashasigrygaope Srakauteh Vukouroascu
-Seapewopleeto Droumiwim Shiikii Ahiprisleosreo Fregauglupoej Misi Misse Mayoropa Waibu Strukrighithiunk
-Oabroochoiglupesi Oruckaos Uphowhufeg Itugliheau Cowoefisileau Odouka Oviglodradrea Glinaapesaami Gaegrilirkucko Egreovo
-Teepeha Slafrobukrug Usadraeb Auskugroyafest Oroatebrive Aobro Pliihokreociufrew Ghuliovisleslouj Oskumusyp Crubrali
-Shugyrik Eausi Osith Osagagleaut Fomio Tradum Oassuliist Ellecoprakruj Abez Apeu
-Ulodrakefi Tricohoph Ihanodrip Ugratohiyephu Efuf Riklufrophegrak Umodr'tri Ostrea Dribeaughoig Amasrograkocro
-Oclea Adaucrefoplonki Ghakaugequiw Oashabretosluploe Uthoutishu Akibuckova Ocroebrossisline Ikosova Ojotid Bullalereo
-Busubalah Eepheghusiumodru Imeloa Apriu Ailex Ikicekobrai Oshiusrea Hoibuci Ohun Poclareph
-Graess Cragru Paorkiigrehoagh Idrerohanoeseu Oulajishe Fresusugef Erkusc Drark Hinkeedavigan Rifiigrou
-Emegeaupizewa Wodissouka Ballotruphii Drenkapheah Efeglujoa Theja Fecuzoflora Ivumuba Esekre Jud
-Okewab Plucask Grulislorao Ubrugakeje Jaesrunko Avibrafeauno Ighuklugru Plesceghamugria Brewecissak Motruwagudu
-Leomo Bugene Ibrouf Doheglaowhu Uloi Hagliudraso Dionessaobrak Laewadre Islixuplen Ojiyigaockest
-Eushuboesiascaho Odipraeslotrumu Egru Ziopise Tunkazakreausiu Klurephadok Iatheniugothu Dochoiscessa Iaclabrusoed Ciki
-Ughepu Eulidaifape Ohegegoo Ohevoob Iplothihecleokru Voglost Oohesehai Sciossid Ghasko Uwo
-Shaankak Kuwheowhan Esubafuv Foacutisiclae Sralucra Eedri Sriiwhe Mishati Idro Fagith
-Omo Upredy Euner Eaujojoiklosry Ebru Jaghaghiogu Oscescaiv Ochaicouphava Oojaru Poceslekeojel
-Priabri Mogreb Oupuvamevih Xugadenagrai Utoej Lliwhoogicrace Sregrupukro Ebivoocagakre Buwhamoiwezia Stighaafreajerk
-Tishobock Zat Oekronar Pow Ozogoiple Eatrahol Nauklah Eaudrij Chopaewubraushom Klofigal
-Whonkel Flebiobrochacoa Oabo Xajooloenku Phigaogab Tustoscelapoo Ybroflochub Oficuvaudeauhi Hifo Opeeglubii
-Ecestiatree Ialial Phaaclankeglizess Aacaphaakol Vaahickiaflissi Ifuklau Eosalluv Oidacloh Icrogosadi Yebuhicur
-Llufidra Tesrovecki Peesoitruscogru Ajotestrej Scighekropi Eaplea Oneojer Iubraox Issai Aebrev
-Allacoikafak Eausotupucu Epabik Agliujuchuluth Bufifleusk Ugriuj Iulliv Ges Lomai Ouquoquoscach
-Eckadayawa Ejefroidrol Iceathaplo Canoessiu Ipujofoetrat Iusloteno K'drud Ecoi Kleax Agrugoev
-Adiurogi Akilenofri Ibafoiloifleaug Osiit Zolaus Ochubresaj Anel Iphoichucullain Oquefli Praofic
-Lasero Alo Akraklusuxuz Utu Ifruw Tirorulluk Iklakit Izaesafrastich Kleesloustazotau Thastoi
-Frocastisag Klicribu Fruproslocev Ibrewo Aseegec Oro Mejeocabre Praobre Oghinock'h Axiulonkukivi
-Nishokragrotrit Plukaphuthur Shofrenaus Ghise Wokro Fuwhaunafru Ipraekajustrew Kickes Slerkuheo Nuse
-Oskazaeceunuk Tickiwankae Tehe Ooclacrawhudref Eveupawo Wudraglash Ediarinebuse Ywurobafee Nemekeunk Ecrygruliv
-Rawoprao Osrirkugaak Eaukigunuweaurkiu Iovakreoshanad Oelonughucroph Graeko Eweanonuprefeau Atowao Eofutosroalutha Ychoklou
-Miucadonkistril Jekawam Opheba Owhoilewhekidri Iasceroimii Oaroboclu Seaudekre Vaufliu Sistruzecoekaoc Cremetucewa
-Cophavin Oliogoquimik Foaglobeleutau Prisho Saquicirohao Cache Hosiph Hafleewuf Neob Cust
-Haphisa Aplob Kestru Ewighu Mocoebudi Eniwobe Eseofifrussube Allazax Iudriicepiuheausci Oretauw
-Roituchi Eclowhemaapib Ibrinid Shussiwowumo Gooplo Etigaphyghe Thoj Miwubregreau Gloalephifrihark Brizacl'streew
-Stox Gleuyainkive Alaisais Ewhufrour Tialesk Quoreuronkak Friquez Froigre Groaquuzuplu Ebrowhioshapip
-Ubragujeghad Jonoobepum Ulojekrana Duj Oci Fofrojusojuc Ibrussaupu Stufriankaubeushun Eojeho Woraunokligriuw
-Imavowo Akif Gefroakiig Ejinudre Weavaudiufralaig Ootraeclisremea Buca Iorifonki Atiossiuyoequo Krafeh
-Aichimegro Quehafluploud Nadrabee Escujicka Pluju Sir Ketoigludoh Philavegexum Aivaofeafli Ahapa
-Jubabrepeo Ioscyj Egliplore Jughi Skankoraich Ebo Brebizowustrer Ucic Ogrowhiaquig Okreajedop
-Facheebriufrupa Vadurkifri Ianapresosrimo Ghoudriubitabiuv Joph Ebapateet Ox'requab Uphash Doreaukrowheakled Fl'cuj
-Iskudrotipuh Lafadre Skaj Obea Iquuhoh Fropeeciabro Ullaphotejiwu Grekak Focece Orkofustaku
-Uskiowifi Avoekreregru Ecefrafoo Stiwheemiubiosh Eyinacesobi Vihudreh Eviud Isam Ashet Miyoocrey
-Gehabreo Wafoco Iwiuscer Akibrochok Skylliatroubomeu Achaefovot Clipleauplog Usiohagorkoelle Anohiif'flap Afayefusohi
-Muh Siiyeser Ostastra Ekric Pur Fustosaso Ouflov'bokofli Tejeegaetafoot Pofejovao Puwisorotru
-Brejobreo Iscaw Pank Fradruw Aecopurki Steusod Ushoshathophou Quiaphoapro Asekre Hegh
-Kegijadile Unkae Ekoilu Ogrysag Stoetoa Ukropeutoscotru Oule Unkobruxagre Yliojeekrassoh Ehakrunkolloi
-Ofesso Umu Vausloneauple Aufiyothaslaphi Supho Hefoghootraub Leanunogeaula Emukeaghisana Abragrich Eglubrulemeepa
-Okywistoglaw Rawhuh Ustair Imonosribiixi Aithakeepi Ugizoakolozea Joegh Tucribrouroa Peokop Giikloleh
-Akrogoghixubre Eklebrusapiopre Nowaraipo Eoth's Igheussavoe Ubaanok Roeklustroivee Ohac Och'ssehusrus Strohijiimic
-Ital Aslujoiprixille Iweguw Chido Acu Higlee Ajustup Tybaogoiphe Assudiwakuba Iscareslirim
-Ohivabrugh Dageahefrab Jipleul'n Wheseno Baerkeecheuwoirkef Abrewuvu Onime Ubeubi Greesar Ropeek
-Scekeow Unoa Oleestromaanio Eeb'bawhoiros Taaclephiskufro Ghelaweagh Obobrawobiu Quokriawi Ghorkaa Aipeklayara
-Doork Brepricetrepaaw Sumootala Noikian Ubeoci Zaiplo Ytrob Avap Zeonotalaaci Eslilea
-Seluje P'sreaullucreupym Thitroevecifu Srasreetiweke Oapojanooshu Clov Yophacelivuc Oalamo Ephepiiv Eoloco
-Glehoum Eaurkuniroonigra Odralle Egrogrewheossa Dipejun Lohi Xisejuwhucae Gaico Bash Epupheyaflew
-Kuvutaska Tefaveedra Filloj Juyo Isriagabruphov Hugul Jaluckeu Oguyaduh Hehinkurkath Oploakesleceo
-Ejeutu Laop Ukroce Aickesibrum Chuwhe Opa Omesh Lligirkeslu Uscoarequuc Epraawidairust
-Pikoej Pefih Aiscuvuzoufaw Driov Ghyfiavoafreuk Ohekreefa Ewoih Clodurethefop Ickeegof Abojiuba
-Ifofrijep Ecrenow Scaglauwiaslufregh Amuzoodrerk Omusk Icrufly At'glehoajaalla Ufribre Veayamuniclow Eejoodupucank
-Iossegrestoa Doavatiislayow Yaukeusegeausun Zaj Agroi Cackadraewutro Pusr'g Ihiipoufoum Bivideuwini Okople
-Echio Oubuvod Ukoshonko Stefloghi Aapau Iapighatovel Ubosrowaad Giinkoraviab Exu Nukaaghoellidre
-Ocoteek Coedrocroa Laecelisraodru Brech Oipa Grigifaaneum Ghelupriniarko Faasoazoastroug Withaanikedo Fragluh
-Kacruplograoc Slobepli Gricrenki Ikoceerkeaujaishou Iuzoovamu Druscun Zeaugleolir Aplut Ifapizio Ouvapisliohas
-Ivikeureaufeau Heed'n Goasrorafewog Voemusratrequoe Iclukloamurai Crafissuskesuv Recucrosez Jeskoakekleudru Abujeau Hiah
-Tekarunkig Esset Veth Dimee Zov Aefoasriguw Rassij Uckagayovu Acegoca Awurkeoflaamo
-Etri Aiplasa Jera Hapag Otugadreyiv Niveesafrii Hist Etatapoa Ves Itrifliin
-Grurkudragli Aupotyllee Yelifaafli Aleey Odotrepabussi Owoe Cladi Kigeraigoc Ifisluflaik Ila
-Uscig Liik Sihofefehyt Llacu Braaxiol Ecricacaquimee Soxumofulit Darkessi Ukisl'wiosescaa Ywiayobraede
-Ufarkegroslodre Gijobrissasca Geza Elaliapooshim Sita Ossaol Odytrawark Walud Srawagaikiru Wiash'rasiogh
-Brom'froin Ocu Woj Ubradiwijoc Siocab Iscodraweerek Wistojosutuh Waewalellaklaa Aquotistrigle Fedroomaibria
-Quiujork Aglob Pumobana Aavadriossahaekreo Peheofussae Teaj Sloidaghee Ofubos Ibrusowetol Icoslife
-Beaugrawhisevi Acimoscata Kerig Aotroorec Ougastre Shigoshe Gaujut Oabagriighar Eukrupra Ghef
-Oucul Risa Strudaebiajohess Flol Oamiasluquagegi Imiupoerosiwu Apu Kridreassut Aklagliwoslezu Saadren
-Shevagio Pavedes Brujonochoefrut Auslo Otufuk Aaledripiwic Ithuga Poew Oefreesti Icozijosrae
-Eji Thinkesko Ibretoe Rethid Iv'caghafre Brairkasii Akreplicibugi Prapeboibrai Doad Imeflu
-Skighobusau Sleepacro Assevugluflale Kapeellujunu Euvefaxidicau Eocubipeanata Nounilohuroaz Igrakre Uzakruj Prolad
-Stoumol Pebrallo Cadiclogor Vijaiboat Slebenediw Unkagra Flara Piiphag'cku Emi Ujukly
-Uwalabekaak Eauviar Truwin'pef Lujukerke Pigipled Tudedrequessun Cohuzeauh Konesrujo Acikeewugo Avohaoki
-Oicu Oedra Srewu Ophegrunufavu Eaje Gemobopu Quailloboumadik Aitotau Ghilobitochea Heesos
-Ukeexallubiipro Aziistabru Quasciihewala Aboyunoameslo Uri Oecligeneauquoizee Sac Slaiscauwhetunem Icoflol Iriklatrex
-Oaquuvejacrograa Iifizilofej Adromeem Refrimeohene Veleupavic Naeceag Quehicrevesu Houhakeav Opuvauleu Oatokygash
-Aohetugud Igig Huceau Ak'crylec Efluss Ehoboomeraat Apheciickunilo Zeasujochauvop Wihutixinu Ovoowautrumuka
-Steagostuclit Meaukreen Onkao Ujutaxea Piufroef Bem Gobiiglific Nusexodru Krevi Emequoehi
-Amos Yij Vijobihoclo Ona Drolatifluj Frehef Nijibecivo Aifrarefeaback Otogafai Opoes
-Quislugroplod Klaeruwhusenkeph Enkioscayoih Owafiajo Usseguskestu Fefolomyp Abaikuj Brevin Hioleflaa Kidre
-Brajorunkuque Joedri Idigriwazak Oedr'y Itoikrid Quealestesrupra Xeey Jeasohe Ovojefrapri Clusseaum
-Koumodisiflaab Iwiajefuquaew Eebreb Nooplekitiakout Rez Iusrawhaboidiyau Esrivoedoo Iobro Pivych Vautayaheka
-Gabil Iojepai Aprec Esrodi Ainiobusaf Uxiquoscoi Leesh Nonkeslat Wheewhec Acliflabroo
-Ajoyov Isicudrovop Uklawhokri Ekiikeoscug Eglasi Gickapewhaha Athustitrag Gabapivafob Irkeama Luredriloiplin
-Priboafaoja Och'sciproaflis Krork Gitawoscoonke Lenoac Stinahu Quulofle Rufretijuwi Pavaxus Giiflopriduni
-Beauzedior Jeaufahoda Stobretejef Ivo Egodrivuphew Ejibacku Urair Iosheauj Ivyroudor Boh
-Feb Nofr'jifreocka Niushecrepafa Udeugliwu Asserutroobeske Ziitopliluga Cubaefroibrostuk Fotiorace Polige Locise
-Irk'yonai Scorounugry Ibiidaestrured Baceevewhumai Arescuwucoki Ebabrussivubo Atru Kachokakresc Ucah Taedrajav
-Inkainkog Ofu Sistec Slocloow Enec'fraw Oobessokij Fezykoa Udrecke Ighaj Ostrankoz
-Estrastrashi Vofi Eatre Scoj Tomiss Kibrepaha Lafoilaez Yegliog'klauslo Ooglihaphagri Cheze
-Eauphaplo Eaufi Stroitaiveuck Aejoyusrudeoleau Evialywoetes Raigaklozoc Opallaaka Etehecelow Ethulebrol'koa Igralu
-Isafomebrifri Ufasoetheana Eassubah Hotiohuwoigraz Avotekraebaest Omo Tiuthioscesceby Illipephij Emibogrudre Euhilaf
-Drumyglohiprap Esekiashecrac Ijibroacroazoa Eaudaurijo Jaisliw Achiroxiglouh Ajiowhugheo Whadith Supal Oriach
-Steafroc Wiurejid'su Ojobra Gidraeme Trakloighu Iopomoas Masaasibrimoin Uhonk Oeckainiferkusee Aonao
-Iamo Hobreaucheg Ywas Ulefro Meon Phoelassa Eklegrobe Eupukoi Oigoinestrov Akraveep
-Glidoclez Shimeumi Yrka Daithacleku Klifemasru Edusraw Iclawascillast Esai Koviu Oiloi
-Enudageplaej Assek Loj Kaick Eujox Arequejutudo Ashadu Ehiviph Geeslifarim Aokla
-Vustoedao Tonkoz Oyaepro Preedroteph Stathavick Llisoosropogob Efud Quiol Frathugh Ootofet
-Eflybodri Crotiakreristroo Hikomee Eabecaechum Coapadakla Huputhogra Keoba Gemayen Auwidesloax Aduxifreloa
-Akraw Liorkighij Dratox Istragi Uwogriowa Iudu Ema Tisai Grivolitao Ani
-Ihop Ifruw Graklikrak Piubemam Asio Eauwher Anoikratocoi Lunkerka Siumoac Leenoe
-Iwhoallacoc Vaph Ajoscilloocki Ajasrukre Piraakli Akubreowha Tuviquum Aosoz Iklacraiklij Roquido
-Anioglifowhabii Phustroutronagem Iphomuwoeckeadre Sluxigleazaplu Beaumap Profuw Dracraije Craullakuciiquia Jiufrae Aidragouces
-Picliocrout Axajise Fiossubuje Yplibahev Agliibiplast Digoquufu Anasaweau Jive Gaonat Akrukri
-Aflihickoskam Oomofoefrawusto Crohistaadeolo Aemujissuxo Goplanogrohe Fawho Crichogliplofru Jogriroohygof Eacrad Ealocha
-Iibaumoemust Strootu Esiska Ioxigrafa Pruhethizia Iawuloequiskora Okridulaij Maucoh Cugaahi Efrobanewaj
-Oacloum Pubeaph Eta Ulok Grupod Hen Eveusu Rasophebap Womiz Ewesk
-Aoposk Noikrou Tuplaneuje Gabak Miugawoek Aegiu Ukoav Klikrawoufroch Uraulijoc Assocroodry
-Igoogausciatiubi Kiumoikrir Uskaad Ailowiucephi Exisra Civeaghuwotev Aaguquim Bipy Eauc'nkufistroje Agi
-Oquugo Kakeheh Eaufoskoepoex Tiyuza Eakruckughiuw Itehimaoko Quiiteg Glekruwhajo Ril Eumussaogrokrudre
-Uchast Yleuv Ruz Iabrowe Uyeaujazove Adiroaceew Xove Ibighislefrask Ilobel Gesufist
-Siucoitri Froopapitrae Ubestachacko Jallark Fragronedra Neclupe Hujum'staslaiph Frestaemurefi Brariplob Mepabraosk
-Prinkiaja Sequiohem Cegribajoa Aghagouquepu Legheeplotic Uveafreejin Eunaewhii Orejaa Frawaetiiteacrej Elu
-Imequeaujipygreu Aosrubijumu Llashorodriigust Azogrogre Isse Fuglimohassa Easekrotoi Yubrut Quukeufeo Olastreunaazoj
-Klihuxais Giophegro Eejaogaija Epeobrubreaug Ghoigasraodec Ogruskuritruwa Ghuhebriiseaug Ipriujaxiap Nath Ugaghaech
-Ufriobroackeslu Vudreflishihi Whiogeuhuxoja Xobriuscacru Edriobraghasko Esal Ephodullo Oeproubrokoscu Eyickaskouga Wehaurkoohankub
-Phapraunamo Pauseassulal Oatestewhiife Whodrite Ikosholoac Lloquaefrowhicliink Adagleesc Utoniari Akameareulia Eki
-Slofiglososh Haoyooz Ferigru Ibu Skegrigothaish Paci Aovaakricrisraekro Ausougla Klifra Ihije
-Litom Eaukaoj Weteoruwac Skasleeklufrute Ohuduch Skivicla Uheabalu Vac Pahi Ouchipegra
-Sap Ipooscekunkack Leev Avyslakrajoev Tuckatruha Skewaboal Eechuwio Chosha Eplejetheda Etog
-Crughifeefre Chescosh Ibretix Ef'cukaekreuslu Hirofunap Essoo Fevajygrusku Ossec Ouze Estriprumeo
-Oolaphuckea Udafoghuj N'fraivyzenoik Saovaz Saprisc Frijaoflaohu Juloyisto Epluss Kiheonufeefra Aomaagrabugle
-Ghazi Iowiliuboono Oshavolophejo Imiskybrupe Outroscib Asu Jaoste Prephuwussy Etriskaromou Feki
-Alegiiwhusaehi Uworifap Aipuh Strikrotau Uphodawof Illasse Straahoj Ocest Ufrasask Ocacedutoenu
-Onistraetac Phopejigiiv Itriplassoplaca Iatabrokeyas Mudrodubriidrir Pypafiwhoe Chidreweustra Ocarkiwasrab Cliciraregh Ufai
-Clabewhibreul Piresloefla Pofrojecruchuj Efetrasog Otru Siackohig Vusreaumo Ughaila Ubimekistra Inogratuwas
-Iujeceslilloescaa Euclarkefoin Uko Ghalatedao Asa Dafripohola Jijossejidrav Igl'tuvebriugro Criwaph Scihaukebesaob
-Liwhom Toankee Iikibethom Edethageslos Ugroku Gudupheaviw Arevisk Opausloskeage Braxosheh Kyz
-Viackurkab Edezydaho Kiclonupu Loidad Toocogloedrac Akoipuraigu Juthifroib Iulenokallul Naplime Fruliiveuxaa
-Eriofoburkioho Iviabakapri Oicraowemoachait Aabranaofrehureau Skomavuk Plagrib Diplekel Jiipicrame Adro Jiwhijastrofoot
-Paquetissiuced Ioghih Pahad Dreprabaghi Demir Braquo Yehou Oskaechuglu Abre Patu
-Oipitorabro Noiklidibroo Flephunijeauf Aquafe Estufri Srir Afeseeklihal Wh'dreafa Xihubily Ghiseasrufri
-Crustaickab Odovihep Iicripa Iprijaaheophao Breachepli Sokor Flauku Fohid Eollakae Usodriogruji
-Icofiva W'ploey Buwheom Udracoklito Gukiu Ajodej Avimeuwoanova Usust Kroapleafriacep Broboollutuch
-Pligufucrankau Scithaemitupo Uphifink Vossulofopo Auk'quire Iudagacouvi Japlallaus Uvok Quaski Ibrich
-Gapliwho Lejajaem Iscasacaocaroi Fikiciirefost Tih Cal Ofe Ayasefi Uditeahut Eaugicu
-Necheeleau Jeoph Hakiclyrk Mistiink Oiporezaifono Graro Hiaklyweumipreg Usluganouglaj Oveclisca Imaplu
-Elleghaeha Aleec Amaagunedrakli Yigockekrifae Gheudekunk Wusoakly Oayaifruflenub Gron Breatyye Uboechu
-Grugoi Pesofriuchaahig Dric Vinafih Cikrakrige Quoeb Oevevacko Reepowheneon Eriiteflack Bathigork
-Islidurefupi Ugle Oucubrathaighao Dremu Sreba Suh Oyilleheri Clulossellauf Er'dudoechoji Ishegicovika
-Pheloetror Vihus Grej Thastoquitrauckun Idugraquodae Echach Eufeob Scedraji Ygleoyafir Mukey
-Gelufloonigrik Ewikudrebroemu Aonekeh Ubiibradrif Nuc Iscume Peax Umedyslio Ogroofohu Crissu
-Whouca Grokopaurabaa Drenest'ti Bigeephiohot Krugucreeshacu Ucleashukriabep Llaquofloh Caorewhicro Codost Yfleclodred
-Dajow Gixostraihoj Siup Upaestreauxim Durkioyulu Wokeaucaov Cosefre Poglulloerut Uriwheuskafo Craxeomilo
-Strex Toxohiifeof Zebouslenu Itai Ivima Trogliir Waakregaw Ushaus Iostreec Oga
-Ocoi Aunaogasuliv Ceoss Yobrayok Ezescecixa Privuniacloc Vaojaukorau Wuro Iakugriw Beevafroas
-Epli Vastiquazafres Awiowhiugha Noglegremyth Meeskujatifrep Iagropowuhink Trapriw Gitoxo Eaudelatiasruh Ubro
-Fayiiwo Awo Steslateskuk Gudoohegroosc Sofisse Eoploss Wafo Bemelai Ebupesteecheg Troved
-Greugreja Ghosuplisiwe Xishusisc Oessozaiscosre Ewhoguckuciv Ebaaplaxutoe Rougupagha Prexusc Uplulu Bufecri
-Isashuphaw Ighehunaark Egedi Aicrikouquej Digopa Juph Kumiume Thithaop'skib Ruyirkidolle Oquigrek
-Ebebe Krasomufelaur Adrog Osiniflagrachi Ostro H'to Reaurokusrestra Raemomu Afli Ufiu
-Boiloclenana Iuflaplenatho Eucracioh Slutheedotrichu Veprukloudema Ewixigrugi Shobriurihiun Uwapha Hiveozickabrou Yconashooy
-Sackoewa Plapruw Ogroju Apafrofibeglo Gew Esiicruta Quaareboisrockif Aaforetuprafla Earujon Atefroj
-Bami Unuchuwucatrai Iahokrataut'flu Groshaf Miop Miuwhu Ivaplograkao Pilloijuc Lasc Illogruphescu
-Yscodreustrun Euva Kroec Vuluhewiv Locuhekodi Drygre Uneubroehifid Meceossaug Fixeebrohah Mowimafith
-Oquaprecloo Ibrero Iipiodawowok Ujoida Tiagrasteauv Puchekrich Iquukick Iaproosi Ikra Creuqueeriprigip
-Cioki Oyilubox Drawotofi Adruquebro Huluya Mifi Bafruflitosc Gheausluga Aneascustriikukru Ulowaflo
-Skeaugouwed Gaj Akokih'jewhou Ellaquunurudre Sriipiseesc Hauweau Dupek Husrokelea Nufo Kleflil
-Anawiwhu Brorkewid Yssifusodrowu Thureojiofum Usiviogrerkami Trissellaolloewoo Oebripajeslaboa Zov Roivo Numowiibrao
-Amidinijedriu Froareshou Iocuskal Siaquoofoghysta Thoeleubothi Meek Boedroucrebafod Koala Icliyupleepheauna Oikopeausrivow
-Hossib'pra Toho Fygaich Aturkeuruma Auchioko Aibovohi Uceenadren Iosru Iuchytrubuvobai Cirioprodiklu
-Aallaropliu Ugram Oskosenkifav Louche Ahogigriri Raawuf Apliuvibani Okradali Obiyu Eeheometink
-Pocruwhu Ockockuskaiwej Irkoibruzuc'l Eostreauskidisha Aachocroikeuc Ajerurahacha Lokirograo Juquaejabaustru Amad Usrekifiprink
-Ekranaghec Gaoquoh Llekrone Osheyih Liagebumetho Chyda Ifrupujiafojio Maleskioglaagri Iubeojeweayaenk Oclivuxoklo
-Laedrotic Sh'chev Grath Oebrotruhaxuc Aomahocikoweu Grodiaph Wagufriumyproej Druyaroosockic Oglerkashute Fipheceofoquo
-Iklauw Lobuth Ejuboogregis Trocudra Higho Ejiath Ukradem Igloch Kistrafimib Vekloagriacugrast
-Ihi Shachaeckeau Ostroma Fleka Glomialliridrar Bihaopashibro Ekrashyth Iucurke Doumeh'fioj Avejeev
-Iscapouy Iluvagripiast Vyglaicloi Kutulikroithu Iikromoackumou Aafu Vafro Gastushimi Ichotam Nudikriloaw
-Uwhififrac'ch Shut Vop'russ Oevigrorivek Fickiuhoy Kreklaoklisef Oenulletru Roxoare Awatrubykipeo Jorechii
-Iuthokriipladan Lobokresedac Sleckofla Ubukrap Vifrakreeglatho Ecu Teagoneau Eckocamo Uklotistrasu Oveauwheazoerkobeu
-Gur Edrulubre Adapaepla Axig Piom Shik Gharaacicio Aivisustraghej Oemaimoosriufass Yplutoy
-Ototia Uproaskifrea Poiwhooxuv Ghiimi Usegocriskop Ofaisrikloss Mafrofeu Ooh'srezi Ullin Boitrulistroga
-Dremicajiu Yanawo Wisrohoacodri Af'prebre Groquiph Cogillaahebom Koobali Oidofescipo Usameu Tockoofohufu
-Ogriov Usru Okludopho Lluduwuc'ceth Eukehazuthoi Nitosu Fluthiinkeechiigrun Hogassin Akiuseut Uwaewesebithu
-Uti Efuroplulous Jikika Shaidrob Ogrusriastekru Oyifopu Frilag Udaglaiy Eethaan'bep Aifukrovii
-Wugystiig Ot'sej Toghiatyteg Iafuv Ucookedres Scequadat Tawissikejab Eteh Boesokloshushor Islololuchitho
-Kadecrebagit Uwacko Oaquiodesciitii Priyijeo Weg Odru Eaulo Lluckanurk Skeoj Iglikle
-Pecuslesc Aviv Oojunu Taudratith Iskeflereof Dreon Deekew Ufrasacaiss Woquoivaevaek Oakaloliifag
-Afroscoagob Paubenic Flubrafathastra Whaciclofo Ikrujaofreflea Etona Ufakianeri Yepla Plema Aakramocowe
-Midayudragrash Eebroubre Craokaashoogu Hyrufagli Kesiupae Chaec Akropoest Friv Esrou Eyolleoyafeau
-Ojiiwhushiac Iyiafloploe Pufrun Odraakrelem Grukristrankinaax Iguwighiapri Idra Xeclai Apleaphiqueeploc Ubri
-Perosc Toanodagaetru Ifaxadrorkoegha Obrucimoib Aniuwophiu Llefasrahiodryb Skehiodiarkaba Iquossukask Unisiflafreche Xiwoujeausucob
-Fl'stugofabrup Griwibroloku Ara Boithoyegheclon Ofra Bribeoglackaplo Idroburkuju Jafrorkeeti Ucuthokrees Uplilo
-Awighaz Ugrikloustob Flackaud Ivegragreof Strakrebaefaphu Usa Shiskaoclelesse Euwhessupripi Ebruscem'slulla Uklur
-Waifrocutoc Ululiph Apa Owu Esaraulluj Clobajisa Eseo Eekriw Acliucezuflasrou Ihipez
-Koukeocoj Obuwovost Onki Agazelleesk Ascafraestomon Itrotijeboabru Raarifaasliuwea Ughorkeodru Ireg Aosrasso
-Glofrelliflusria Crapuch Strokrapib Isadrehikre Jovisu Ystoz Sevoshaora Neshokiy Pojeuneskahu Egroabaska
-Skuhauflohothoe Griuwema Muwhuclusav Tin'ck Creaphecku Gaast Urodagiik Xudralleugrid Nakuriice Uyokaofano
-Baw Eubousce Uclotre Cub Eraebighegru Wathaawauli Arooha Klunoada Noun Sholufrograe
-Noucrik Iskeeforok Oachuj Aekarescao Ephoskiuxadokio Noza Nisreapheaskiji Mobrezinest Pheflauduk Aivask
-Scigepli Yboflo Lome Higoutawag Roove Higimib Oweauwilaicreboe Whenerk Steustiheho Ulijoagor
-Hesseglepla Efaun Anioja Ezipraye Ionogh Eklukrub Oebotip Aechairypogikri Esliplaefrusim Dioguraedruhu
-Uloban Imussiu Whoshaipranud Gushewoede Ivograo Kraboklu Kofruthou Iriwepeeja Soic Bawumusob
-Mobregriup Ivoskoo Uglechuthafun Nacorugugha Idoudrukreokresk Eedrihacri Pubu Ficrehubo Trohogrooflo Epureu
-Eskahouckifaje Usai Thumegrema Apimigraum Crekuyidouskal Oni Oiwhesc'tiofream Oru Whoagugeflumu Exobos
-Koghoh Usrumialloi Pudrubru Fiufesacko Mucha Queflusutrer Straha Keauyuflal Ecaucoeslou Kafiusko
-Udrostuve Aivu Wipri Dolaoshoatho Gumaeghi Uskofra Eako Osutrii Aocojugi Joesre
-Ovib'krepuhi Oojuheulle Kiok Obaivo Isuw Lliga Okuv Regaothifun Eosra Febrakrena
-Iobripife Oafaotek Obradreesiu Iapupanijun Hikabrosloplo Aewadaostrush Haocrusc Rowob Pryfloluhepiss Ughoprevoo
-Ridrabip Ipynuph Nootaobreflaw Pekroohu Keguc Sissiscicokeb Amirk Weshu Groheshabi Chubughickoah
-Emedrugi Web Imasrasa Glaurioskoces Uflollev Slafretathaickosk Isoegreboyootho Sajarkiyu Fragekrifiju Uzimouwe
-Ioviwip Kruboclabr'z Gashevekatao Yawilaquo Uz'ssoogrosyja Oporiquajaap Tofrel Pludi Oirouwhit Flora
-Avicriz Kridrez Woelewhikip Srafubrechac Whofreubrastrorib Evu Clod Afrussa Gipeuph Ijeaugresleasci
-Freoslafluju Eaglaad Ousywigo Onifrisae Enokracem Upux Ageyishiaglud Iakocrujon Buhak'gruce Hob
-Aneafa Amesc'deanig Aatrigugraegoo Uhioci Haamelograti Jikreguphelouc Bydobroflaty Eune Vuclaolah Uhyfoiwac
-Phaegh Evawecaoti Br'habyg Uboot Ibifid Ugriaquucrob Epioguha Igithaowe Iikrotogram Zedisoghedre
-Refaunkivoshu Eokeck Daopai Yonus Skof Iaghirigheaucibroi Cacuwosh Stafruthia Herukliglofar Aegeausre
-Sekofoshaev Anomaa Awicatrosralle Ovigli Eaubrusakodiaprio Ovikrufogro Obrideo Uchisefrikihu Ijoluklegh Aqualabifru
-Biogre Cunkeubeau Teodamulaxiu Namoiklecina Efuhoiwhia Imostecra Slavojofeajiod Deollir Otrankusi Yxe
-Ackojaslij Vathiweaufochiuc Eohoescuwaquaist Iamorkibreo Runkowa Pecreu Draitaewohiatroo Itocuwo Aahojagookrebrii Merequasha
-Lyskascoka Neplufiloot Kis'fucoprih Aglislechuweghao Ankiceerilee Aarubrime Lacioth Borinosheaust Uzaquamaigu Eemoproapiiskio
-Raudiskeallupa Iikagrugonku Dacliustruf Slestrubustre Eutoahi Sroberuhob Aakapif Oosapofro Ehaquecask Okrirk
-Paewaobi Atrudaf Aefiiskou Awerku Ohephis Agamaqua Ialugasiraati Ewhosc Obrehussiubri Uwoquu
-Greeste Duf Ugehi Ugriteseu Aohafeewi Cloadajeamidi Valagrik Abrunkai Scob Oquesipeaub
-Taphiacku Shoedrif Leoghe Urevufuclip Strink Lun Gopu Ocri Eajauziole Frufrethastrau
-Upediwe P'quugefraeveuk Iciba Uscekrevagri Jouvuch Wogheaugu Rohackoikaf Akroew Aussa Eausreni
-Beklotisuhoth Aopalli Ubum Caquikrax Enaujazestrux Keapeughoukoil Tigelloetay Naowhopaplatheaup Gloclaikriutrekle Aerkaechorun
-Esushess Ohupluphiba Asoguh Grollootut Krefroavuy Ikibrib Iimeo Aiwhepegh Rellohae Alif
-Achuchaus Freguguss Teaufudiputia Iapab Ecoqueum Phaiscaeterk Phigi Amoze Truka Aaviwome
-Thoquau Jooghigrobaroe Tr'gliumucko Ukloa Cijaejaovafi Zeaugleniploe Srakreguchai Ijefim Pucha Uraph
-Chuje Iinkaufroshoey Slyyezoajeacu Eawhocok Ugep Glesceaulep Ugleauglidi Iweof Eclaquustetiin Esluwhip
-Dinkegra Vusruclijibop Streehibousrus Ajemakeflem Ar'sk Koeh Lihomoafrokre Aopapicaudig Amiojadrecu Struvo
-Greaupibrilo Sucolleau Ankack Gleskuvonkoowe Wustacliis Seposiuch Flivekrau Puzelle Kukraraafe Iskiunasraku
-Lubraphoiteho Ihessacaa Oikrasoibisla Eloiflunaploa Iiv'nkascit Aodr'dubroikran Krassecko Igutorususrae Shitih Oruflaavaw
-Nidreopekrorke Aefraglifegi Uphafiss Ilothip Jotauj Giasomahala Oarkawa Agresakreau Ghinisoac Beuslaegrifeany
-Plicluca Lepidruluz Xessilakav Mimuvaanea Lohequel Uweuw Ushepuf Kexanona Tigruniu Thunkayokritha
-Etheauwheopliahychoo Ayedricolluf Scogow Omiishedev Nickeauv Onkuph Ayihuwhuclora Ipiifrekukreostro Aprustiopiroco Eneaustreaglupreuv
-Uclourkene Aheu Micucabryr Ifreusrotou Pricichawhu Oodruvepi Ajuclaka Eetossekleau Jaidexe Mealla
-Eyab Yssaka Oquofronkaoxuhau Aukegut'klax Clug Ecaosabiklev Itu Ubufresaon Anerejaotresseau Opiugreothudu
-Ickuchodresepi Ghokipescofu Gay Caibo Istrabrejinokle Drusky Opomolif Odraickiusl'gadri Aslim Doidreneat
-Uyif Coiquolek Uku Ewiwao Okreckuveb Gaklanoss Euhash Plaz Lahi Rakrabru
-Gassonish Iploskaw Esan Krithudos Briodroidavioj Egheockethiploz Iixihugeukin Awhigremaifripre Uhokrao Yebu
-Isafonobae Exograestokal Shuproosrovoges Kegeheuj Imeeskywat Friwacetu Evukl'flaub Wasleri Duvakot Bujossaoke
-Adross Kiipoceuglebisk Ugoleclai Kovubaa Udrasc Uchebronefliol Jawo Lowhapae Rociacade Fohuski
-Tubruwaali Uxabrosoasroas Fiscea Moko Astreoprian Oeveobriugiodot Kligloox Ewi Eplidoumibroiglo Oilizutookri
-Estosciasteokrog Yaiyeacagrugoigh Oolam Uphahik Maociklohaa Epragififi Crepoehoja Hutexeo Ulithopaochou Uvigh'cro
-Idaph Agrockikrodork Wogasiplufred Kooyupeuski Ukeehiquac Higruye Oshequixugh Ececadi Fastaa Urekuwhafoubaa
-Xogloa Grichok Eopyloklaako Ronkyfoac Jaiv Ygugreuvaoci Voolilo Ajum Aiwighaix Jusuci
-Oocli Boat Ock'zoslehoonk Ugoclaithoolo Posteaus Yiyeoj Jaih Clakragiujecem Epu Plaugrodaslaku
-Beoka Krej Juvor'chislah Ehobriohirk Iuwapiork Kautub Upho Gadira Otouxikre Ulatydipai
-Eoclaklaprikuthai Sobruhutio Bregialeuk Greabroyorurkoss Aistrul Iugekrekujez Fat Craikra Ghejoeplo Arytanai
-Sraighoir Uloug Cibuwou Iotaefrotraboo Ankaethiuvafrow Otewatroke Aellikadril Pehal Zakubaobro Llyflekacke
-Thaigayoagliciu Aemastoifuv Iplamineth Minizass Wocissenaenoe Igiupiple Kriphiucrype Pibovawhiclo Gaasuck Kricrabraanasa
-Iruwavejook Ankeoju Chaleploscocli Leaun Fenaethibrob Thyboisciinut Voflijorif Giwi Pliapre Slihokleay
-Soup Iinogeoriamey Oitidrae Sk'gre Broulifinefrec Epruclo Creclech Okoket Phaf Stast
-Troenesheachiclon Kifel Oesloakragrafechu Jigleroa Ozaomico Emoerakrasuzo Scewopligafe Frehasea Adiwinkoe Dokababreukrel
-Phoayi Iisegaeyi Owheru Phioj Viuthaaneghewha Laplythur Igaplush Ogiskughaha Oucosumiv Slirkastriquaaska
-Fr'mapary Joiplastraveeski Ivuyasc Xoaslifiimost Ivu Dastrunoabrev Nuceerac Ixe Slasrareas Isceb
-Fazor Daweautokaok Iafaevocav Zowhobriibrac Aeheo Thepelufuy Yulla Alisosso Icif Agroe
-Buhusupabrij Kemy Jyhast Stoafaovukraigragh Pesogloiwe Proskop Ufrickothaocu Odruglodeecron Porihistai Earybafroink
-Feviag Ikranuthiutoa Ebaog Saulohegughiu Pisc Yiveechocke Gokre Udrik Ewhuwhuvaeckic Oaro
-Ostredem Ostai Ubyrkugroyao Shobrequo Nesciroj Whohicrotru Anolav Mafou Gewirami Oacoskeupehav
-Kroirkufriva Udrankiutonisru Aikrupi Iquoscaiskuth Llav Mad Frean Katoemuteu Sav Ugiglinkapro
-Hafrugo Frofynkeel Cighuteapegaok Stogaglab Iiluj'sa Escau Eotiboinkop Hithimaascip Iwo Ophusufe
-Ghoob Udreji Akughazuwounko Wewab Trunar Phaclethaa Luc Ifrumaafrebiicho Voicloxave Autefraumoseg
-Sretagraj Daabipha Aajop Arestri Pewokroh Itoflac Ideekuniveauh Klecrofe Driatoothescikia Jikretir
-Ciifehuquug Adeusruga Lipha Aivegimoj Uglufim Icoe Ogruchiu Epowuna Ithethemethov Gef
-Eleugitivu Emu Ocihuwa Okra Ylakuf Wosla Iduflaedam Eaucliflossawam Iow'gheeglonopu Bite
-Glelidikaascis Kibosri Vunut Nepetaprescin Astrivafu Baehacetao Drowaesew Tahemuss Fropea Grogliphawofreof
-Nestrita Vusof Nomio Ecagi Jeskiji Heuscoa Cost Ihiaju Clefostehi Moskaphagleoguh
-Duz Odesezikrek Aabeuskebrowhyz Chuproethisopiav Dakreg Nutrodi Oupoono Augheobaqui Namolleslascaut Chuplithai
-Scaw Urax Tracleg Quoolaerimam Fregoosuhoh Egl'lokexipe Iwudrosricro Yefliquej Mefoneatoewhul Ymuw
-Ishosa Stiapegraulaplo Tevig Aata Ojeugejugaek Ubu Duku Igislujist Fridepodro Tiudom
-Claodefreslych Pijidigi Eaunasradi Ojat Ofeoglaerkust'j Ipolaklaash Otr'juv Klubo Xokefaechos Meagykrila
-Ouphijeusiodraet Ufeodri Gova Cr's Ograkruvo Ofojowhocla Stritakrohodrok Kucaifaham Ixo Eepribobauqui
-Weuckochalita Clemo Izo Astoaseka Fitaujoo Eghowawhod Vecinab Eboinu Peufeckigix Riisestrufrepor
-Boicke Klabysrafeashuz Thakeprace Awostrokigrea Esuv Zanosofrunooc Isoisu Ujuscoch Idroxuw Eshosibof
-Nodrouxinuxo Aeckiwoelliapoec Eaufileepiorkupi Rez Ideheegelloeph Ushut Gaab Iclugeomamyr Dradoeboe Ellikrini
-Aboafleo Wegistiluk'gh Sol Momoruvil Eclaegigrec Jesoboamiso Strichujot Nemoi Wiojiscaegrughew Igrea
-Ijilohavosti Froghe Mavaiceauskes Priguj Kasin Astilaizo Ofoeliphukrow Lataepuluveaun Srucagrouthothu Slukekrubregla
-Oxianu Euleurifior Maolissiisluwo Kobekeauwa Woph Zojeos Wohaoquio Neckachithuzeu Cosathiase Nuphosc
-Akare Pepe Mopefli Droijureau Eustribo Iyefiinoss Issakreda Kastrissarater Oedritutifricro Poinillofe
-Frath Skodiuvifri Dreaufekreryhad Rishoscefa Ibibossufrafla Irkii Shufletiyapim Depinu Aeplajikocika Ekrebroujed
-Klaghosroteb Aicipladekro Eefre Wicripheopoup Iufusc Glon Ewofra Eawecr'jaoweau Wackayileaup Saebreklabaesc
-Xewushauv Obubraiwoovac Unkaacruslunix Gocetrahaklit Ploubrel Ewujofet Wahaipriv Lam Egrikacrinkois Tiscoprifru
-Obosynkukrase Febryjerau Ubocojodruju Goghonighifraw Ulleha Viuriis Uvakiod Fofrasrenin Drugroe Eekoroagagasso
-Kareb Peukroci Oopagheze Drokenkiimuweau Oheufluma Juho Xoeyekluslachae Esauba Gufegutissep Sock
-Auwiaprao Rejouy Iuclomafriha Lagrefuchu Draigrelivuh Quututraj Ijeokiplouku Jupesiniuyosc Tout Diin
-Tockeniokradaj Thugh Diwhudeau Grumoophokru Ekleoka Deudruckugegri Aru Nunisroupoc Ooglud Eoneumeasaa
-Ajicavaovu Duyiclena Vofoaskul Aemiquenem Grif Aquu Priwaogosu Ghaclitewapho Riz Eckaaku
-Ukre Inifrio Awuko Iodojoid Haefaiyiraistrit Otaid Igihedra Lazifakibo Aplog Eufaatritokaas
-Oossiglaopiploara Taickagid Whigrouviriwath Gytheukro Diniraid Whaikrerk Uni Ciikiijofe Seslapes Krileellascila
-Straoquatacheoy Amebroepibraadi Udeaupi Ir'pruhick Gacollesh Aloiphav Tysleopholl'cho Tavypleubuthu Oonkost Trewhofrako
-Igreu Eauveckeliboo Sowhaida Lloscer Aifrausaimealouquu Sakluskosreha Oufaslejiuclishu Ahugi Fosakliushebrep Kofec
-Anou Wuluwhesa Uteh Zeaut Obra Coimo Gacoodi Seugra Mabu Orudou
-Steme Okrah Cidruflothigla Jikro Aplon Ejysack Gathiaracicad Kelishajubra Sligu Saevustruhabae
-Iciunkofu Stravio Ullastekojot Gloahut Bewezosri Sriusronejeklu Sroofriakrod Oboilledimal Evubigruquana Doreajuleutu
-Ewickig Udyrapuwath Oequowebusrioho Ostii Buzeyakraabiot Uclituwo Imowecraeshaeno Jafevoroag Ejoukrogumefri Drekeotrenioslab
-Ubae Relai Okaafrolox Floglidijipral Vofionihupio Droigayou Lemelliofli Tootomeshav Ugho Jub
-Okroonk Oehisto Jalo Groajadrucucaph Ufewakikluwe Osriidroyahus Fum Eritreonidako Tudeonadoih Soochegobat
-Alatyveocke Ehesauveflag Fastofufrubij Ebutur Iikafabri Anaawuho Afoklu Ivouquepoe Ewaucea Fijapeataadroux
-Greocuklepoph Iyoidrea Whabrijephigew Aghoideabawaaf Aagedaifeuna Flooslipheaut Kerekaed Xom Eomogrob Eaklaustrokrub
-Mecetri Geghouhioko Ehaglesc Feadeaubastross Wailoe Emapowighoscia Akrucha Iwhali Chodrej'kifiag Aofufresaw
-Fr'nkoanil Nosutaibrio Kes Noske Weauf Cruscastrabucesk Igro Brakiov Sagriukaf Greteocacriflu
-Kemothaisoc Urithihiogree Agleazedeerk Erkibow Toackog Roj Covuflaicliihio Ulushuceplite Jemoecloopio Taslefaub'f'g
-Upoitesc Zukedebra Bedroboh Sissitreheauceo Joochaje Mouwoliubio Aapajubridiabro Oockawaveu Iskidiaheoth Fomoifeauskotrai
-Aren Riwakrioc Choawu Inakazamuthoo Chafeabossin Afepiigaodii Irikegipre Vewhishu Nagelaowa Whessuwhegroophank
-Nuxeushe Iadrest Eveauh Upistretroscaith Ojocrichuwoaph Freauscimigii Fef Daadrekloodra Daghi Gobrynkaza
-Uphoalamuc Pubo Lucku Eeclacapu Medrelubishech Dodistidava Ustugloaflasi Yrigreskishuglo Wek Moiceaujiprastrum
-Asteheuphackor Bedretinuph Ocrak Oudarep Lava Osca Oawemoproje Uwunkufy Eladrofauv Freaujubruw
-Evi Niiragaiphary Iogludrioseecku Frewes Iufle Xirauzelack Aevoa Kraagroagr'jeo Aviosaya Auslasti
-Ifrawiotorkeuglai Thukovoe Iglogefrug Obrijupavyh Friijawiickirur Roshedrebul Skokoikiflii Iiskytoskojaeho Obigri Sasc
-Gejiubiiwhiw Alajod Shuvasseawi Uflii Echam Waraal Toisewhiquuso Edaciwequiane Aahukrea Ihov
-Ofaiplithew Ulitiv Ushulughe Pigliav Shoonkusroti Trihusuvifeom Weollaneexoazysc Ugu Aepaicukelina Ociamede
-Kawim Ekunkank Jun Tete Iwae Uced Hiul Suh Onoiz Breockojuweo
-Cocroislugho Aojucloplaje Elaostuvo Ubroegad Ucovugremido Ykroifroyov Kiskosu Adarkig Bankacopra Levelilu
-Troshupreo Chiribiumacre Eewunkaloib Yojelebeoc Rehegiuw Pul Peveebrinoo Ashojabru Quaraninki Fanoklo
-Stiphoidiogrefloi Udragropased Oobrougroarythe Draghopogrojee Ysud Skywiagigritro Akegur Iweg Oteakla Eskio
-Ukrazoiv Wosrushoecloso Iriollac Faodarkeodroj Ojegrotafloiheo Obre Niwarkoulaaria Ewa Sygrif Stroagriapaecoyi
-Streaufope Atribunytyva Nefo Eausiaflae Treshiscucubrug Ihinaray Oceeteskawu G'tibughoiy Upii Gabura
-Brikrughytith Edriclimav Aazighun've Griteusewhokris Akrimikaghaasee Tiukloteoju Cebraza Eadacefesk Oparkoss Egeudru
-Qu'loighokre Drudrupl'joabru Itakru Disia Unoramegruma Asteveauchumoude Omurerkagii Owheej Turka F'tr'gan
-Eayal Akiaqueborogrii Erkuquaoglaokrio Chihucrusredip Hapoocru Lladagoefeji Eafri Mesiraik Vapeneutri Paichaoqui
-Aawukrap Ewogh Chiurkodrevoiceb Onuvofleeb Eauquiofrih Llest Gitioh Brastigughok Eghojiphoba Euboowodeahog
-Troewola Boughashathibriod Iheeprulig Tadi Emunkumaowhoock Vadoesh Ufliukodrakloph Oshuteule Nibaeho Eedaobeg
-Gisach Iseauwojavi Renofimeutiaz Escuf Asaskeaurooh Aliuprifi Eejaaquastroafid Flasu Peocufeseaugli Frigraehunkiad
-Igrebraahoavarki Uwawhacho Awu Oklichouhi Srewhos Sluproscuxov Droveku Ocunkabruwu Iowasc Eaplifeflophu
-Scuvoubibi Lofuh Itrogig Iijuslozo Aemelim Oce Ililaewi Atuquethatotha Proolletaefy Ala
-Whausoigrusridryx Cruwuplea Ekra Fridrighadodreuf Quoimiovayoif Oevotoigroe Aotreozeojoi Uglo Un'tastunatha Efrurkuskii
-Isucrizaopak Coduclir Odrucradi Laseneau Ogikrihoho Mokagi Ixo Jerourkasloe Hygubresluwo Sauwaiceefaaw
-Biijouwaklev Enibroovio Steviubaege Xonov Ebe Iuladratoab Brinaoquikru Clan Eklugrawax Buleciocize
-Mobejop Paj Iuneonajap Pitheub'growe Unkedazu Quagreafr'gh Awar Agea Uski Iofrouc
-Ipunkiojuha Ujiph'g Ewiubresii Phiufredowodio Ajayucovun Ucrutukaab Icou Looweowoch Giacoovuy Rioriv'roveack
-Onetaconkae Liclaalluch Kreustawaumo Krugugrothepla Akacruloesia Slothokoipiutou Ukruvug Oucruw'harkob Etigou Urkigraahepib
-Acikludane Ocrougreada Omesleausloi Keel Beliy Griuhikrolifii Apriosse Ugeabiotriovejo Mevudawemii Ajoploafrifalu
-Draepuss Wougeek Botullichoowerk Olenkocleauji Ugoro Stemoedroux Nauchocoothuglak Oahak Liti Euclufrebio
-Ilot Fehesc Geaullis Askagrapii Eaussa Irkop'chih Ilepibraoflemo Fodroedolliurau Graic Iafrikiguha
-Klarkooss Slebesadoeru Ekluc Podrojuphuwi Oikulloreth Plikroulo Plaprea Hear Giudugiscili Iji
-Aisref Ousre Kaigociuslucev Tok Thuh Eowoirkykuko Kusligrostahoar Hek Olla Keofacoshud
-Alerawhebrebroe Slepleoxiapa Modissiwehaoth Krejustrusothee Tribufrokliuyoj Dridre Sowehoome Wosoeyestru Whud Dujisci
diff --git a/jetty-servlets/src/test/resources/lots-of-fantasy-names.txt.sha1 b/jetty-servlets/src/test/resources/lots-of-fantasy-names.txt.sha1
deleted file mode 100644
index 5fbfe33..0000000
--- a/jetty-servlets/src/test/resources/lots-of-fantasy-names.txt.sha1
+++ /dev/null
@@ -1 +0,0 @@
-b49b039adf40b695217e6e369513767a7c1e7dc6  lots-of-fantasy-names.txt
diff --git a/jetty-servlets/src/test/resources/test.svg b/jetty-servlets/src/test/resources/test.svg
new file mode 100644
index 0000000..08fbae3
--- /dev/null
+++ b/jetty-servlets/src/test/resources/test.svg
@@ -0,0 +1,2101 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="144"
+   height="144"
+   id="svg2"
+   sodipodi:version="0.32"
+   inkscape:version="0.47 r22583"
+   version="1.0"
+   sodipodi:docname="ijetty_icon (abstract 6).svg"
+   inkscape:output_extension="org.inkscape.output.svg.inkscape"
+   inkscape:export-filename="/home/joakim/code/webtide/i-jetty/trunk/i-jetty-ui/resources/ijetty_icon (abstract 6)_stat_mdpi.png"
+   inkscape:export-xdpi="15.62"
+   inkscape:export-ydpi="15.62">
+  <defs
+     id="defs4">
+    <linearGradient
+       id="linearGradient3767">
+      <stop
+         style="stop-color:#ffccc5;stop-opacity:1;"
+         offset="0"
+         id="stop3769" />
+      <stop
+         style="stop-color:#ff6b56;stop-opacity:1;"
+         offset="1"
+         id="stop3771" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       id="linearGradient3699">
+      <stop
+         style="stop-color:#000000;stop-opacity:1;"
+         offset="0"
+         id="stop3701" />
+      <stop
+         style="stop-color:#000000;stop-opacity:0;"
+         offset="1"
+         id="stop3703" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       id="linearGradient3683">
+      <stop
+         style="stop-color:#ffffff;stop-opacity:0.88687783"
+         offset="0"
+         id="stop3685" />
+      <stop
+         style="stop-color:#ffffff;stop-opacity:0;"
+         offset="1"
+         id="stop3687" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient4069">
+      <stop
+         style="stop-color:#f4f3fe;stop-opacity:1;"
+         offset="0"
+         id="stop4071" />
+      <stop
+         id="stop4249"
+         offset="0.73082942"
+         style="stop-color:#8e84d6;stop-opacity:1;" />
+      <stop
+         style="stop-color:#2815af;stop-opacity:1;"
+         offset="1"
+         id="stop4073" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient3600">
+      <stop
+         style="stop-color:#fc390e;stop-opacity:1;"
+         offset="0"
+         id="stop3602" />
+      <stop
+         style="stop-color:#fd9a85;stop-opacity:1;"
+         offset="1"
+         id="stop3604" />
+    </linearGradient>
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 526.18109 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="744.09448 : 526.18109 : 1"
+       inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+       id="perspective10" />
+    <inkscape:perspective
+       id="perspective2390"
+       inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+       inkscape:vp_z="744.09448 : 526.18109 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 526.18109 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <filter
+       id="filter3692"
+       inkscape:label="Drop shadow"
+       width="1.5"
+       height="1.5"
+       x="-0.25"
+       y="-0.25"
+       color-interpolation-filters="sRGB">
+      <feGaussianBlur
+         id="feGaussianBlur3694"
+         in="SourceAlpha"
+         stdDeviation="1.000000"
+         result="blur" />
+      <feColorMatrix
+         id="feColorMatrix3696"
+         result="bluralpha"
+         type="matrix"
+         values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1.000000 0 " />
+      <feOffset
+         id="feOffset3698"
+         in="bluralpha"
+         dx="0.000000"
+         dy="0.000000"
+         result="offsetBlur" />
+      <feMerge
+         id="feMerge3700">
+        <feMergeNode
+           id="feMergeNode3702"
+           in="offsetBlur" />
+        <feMergeNode
+           id="feMergeNode3704"
+           in="SourceGraphic" />
+      </feMerge>
+    </filter>
+    <inkscape:perspective
+       id="perspective3755"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3600-9"
+       id="linearGradient3606-7"
+       x1="39"
+       y1="22"
+       x2="39"
+       y2="-30"
+       gradientUnits="userSpaceOnUse" />
+    <linearGradient
+       id="linearGradient3600-9">
+      <stop
+         style="stop-color:#fc390e;stop-opacity:1;"
+         offset="0"
+         id="stop3602-1" />
+      <stop
+         style="stop-color:#fed0c6;stop-opacity:1;"
+         offset="1"
+         id="stop3604-3" />
+    </linearGradient>
+    <inkscape:perspective
+       id="perspective3825"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective3847"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective3869"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3600"
+       id="linearGradient3965"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="translate(1.845606,221.31978)"
+       x1="39"
+       y1="22"
+       x2="39"
+       y2="-30" />
+    <inkscape:perspective
+       id="perspective3994"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3600-95"
+       id="linearGradient3965-0"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="translate(1.845606,221.31978)"
+       x1="39"
+       y1="22"
+       x2="39"
+       y2="-30" />
+    <linearGradient
+       id="linearGradient3600-95">
+      <stop
+         style="stop-color:#fc390e;stop-opacity:1;"
+         offset="0"
+         id="stop3602-3" />
+      <stop
+         style="stop-color:#fd9a85;stop-opacity:1;"
+         offset="1"
+         id="stop3604-8" />
+    </linearGradient>
+    <inkscape:perspective
+       id="perspective4032"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 526.18109 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="744.09448 : 526.18109 : 1"
+       inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+       id="perspective2390-4" />
+    <inkscape:perspective
+       id="perspective10-9"
+       inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+       inkscape:vp_z="744.09448 : 526.18109 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 526.18109 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective4259"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective4281"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective4304"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3683"
+       id="radialGradient3689"
+       cx="38.022934"
+       cy="170.49739"
+       fx="38.022934"
+       fy="170.49739"
+       r="29.59375"
+       gradientTransform="matrix(-0.54418933,-1.0688069e-8,0,-0.40006699,58.537282,258.88884)"
+       gradientUnits="userSpaceOnUse" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3699"
+       id="radialGradient3705"
+       cx="37.845608"
+       cy="248.33372"
+       fx="37.845608"
+       fy="248.33372"
+       r="29.593752"
+       gradientTransform="matrix(1,0,0,0.50052795,0,115.93046)"
+       gradientUnits="userSpaceOnUse" />
+    <filter
+       id="filter3751"
+       inkscape:label="Drop shadow"
+       width="1.5"
+       height="1.5"
+       x="-0.25"
+       y="-0.25"
+       color-interpolation-filters="sRGB">
+      <feGaussianBlur
+         id="feGaussianBlur3753"
+         in="SourceAlpha"
+         stdDeviation="1.000000"
+         result="blur" />
+      <feColorMatrix
+         id="feColorMatrix3755"
+         result="bluralpha"
+         type="matrix"
+         values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0.700000 0 " />
+      <feOffset
+         id="feOffset3757"
+         in="bluralpha"
+         dx="0.000000"
+         dy="0.000000"
+         result="offsetBlur" />
+      <feMerge
+         id="feMerge3759"
+         result="fbSourceGraphic">
+        <feMergeNode
+           id="feMergeNode3761"
+           in="offsetBlur" />
+        <feMergeNode
+           id="feMergeNode3763"
+           in="SourceGraphic" />
+      </feMerge>
+      <feColorMatrix
+         result="fbSourceGraphicAlpha"
+         in="fbSourceGraphic"
+         values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
+         id="feColorMatrix3892" />
+      <feGaussianBlur
+         id="feGaussianBlur3894"
+         in="fbSourceGraphicAlpha"
+         stdDeviation="1.000000"
+         result="blur" />
+      <feColorMatrix
+         id="feColorMatrix3896"
+         result="bluralpha"
+         type="matrix"
+         values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0.700000 0 " />
+      <feOffset
+         id="feOffset3898"
+         in="bluralpha"
+         dx="0.000000"
+         dy="0.000000"
+         result="offsetBlur" />
+      <feMerge
+         id="feMerge3900">
+        <feMergeNode
+           id="feMergeNode3902"
+           in="offsetBlur" />
+        <feMergeNode
+           id="feMergeNode3904"
+           in="fbSourceGraphic" />
+      </feMerge>
+    </filter>
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3767"
+       id="radialGradient3868"
+       cx="36.706085"
+       cy="195.12364"
+       fx="36.706085"
+       fy="195.12364"
+       r="31.534483"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(-8.8006918e-4,1.5404146,-1.8180408,-0.00103872,391.64719,139.27166)" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3767"
+       id="radialGradient3877"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(-8.8006918e-4,1.5404146,-1.8180408,-0.00103872,389.80158,-42.04812)"
+       cx="36.706085"
+       cy="195.12364"
+       fx="36.706085"
+       fy="195.12364"
+       r="31.534483" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3767"
+       id="radialGradient3890"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(-8.8006918e-4,1.5404146,-1.8180408,-0.00103872,389.80158,-42.04812)"
+       cx="36.706085"
+       cy="195.12364"
+       fx="36.706085"
+       fy="195.12364"
+       r="31.534483" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3767"
+       id="radialGradient3908"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(-0.00149719,2.6245482,-3.0928769,-0.00176976,675.6881,48.253399)"
+       cx="36.706085"
+       cy="195.12364"
+       fx="36.706085"
+       fy="195.12364"
+       r="31.534483" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3600-9"
+       id="linearGradient3732"
+       x1="126.3456"
+       y1="233.81979"
+       x2="21.345606"
+       y2="128.81978"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1.0192308,0,0,1.0192307,-1.4201081,-3.4868979)" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 526.18109 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="744.09448 : 526.18109 : 1"
+       inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+       id="perspective2390-7" />
+    <inkscape:perspective
+       id="perspective10-95"
+       inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+       inkscape:vp_z="744.09448 : 526.18109 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 526.18109 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       id="perspective3660" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       id="perspective3638" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       id="perspective2823" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 526.18109 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="744.09448 : 526.18109 : 1"
+       inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+       id="perspective2390-9" />
+    <inkscape:perspective
+       id="perspective10-8"
+       inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+       inkscape:vp_z="744.09448 : 526.18109 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 526.18109 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       id="perspective3660-1" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       id="perspective3638-0" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       id="perspective2823-4" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 526.18109 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="744.09448 : 526.18109 : 1"
+       inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+       id="perspective2390-1" />
+    <inkscape:perspective
+       id="perspective10-1"
+       inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+       inkscape:vp_z="744.09448 : 526.18109 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 526.18109 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       id="perspective3660-2" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       id="perspective3638-6" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       id="perspective2823-9" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 526.18109 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="744.09448 : 526.18109 : 1"
+       inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+       id="perspective2390-5" />
+    <inkscape:perspective
+       id="perspective10-7"
+       inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+       inkscape:vp_z="744.09448 : 526.18109 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 526.18109 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <linearGradient
+       gradientUnits="userSpaceOnUse"
+       y2="130.39449"
+       x2="81.738991"
+       y1="232.29347"
+       x1="81.482536"
+       id="linearGradient3827"
+       xlink:href="#linearGradient3821"
+       inkscape:collect="always" />
+    <linearGradient
+       gradientUnits="userSpaceOnUse"
+       y2="130.3461"
+       x2="56.845901"
+       y1="210.33858"
+       x1="56.845901"
+       id="linearGradient3812"
+       xlink:href="#linearGradient3806"
+       inkscape:collect="always" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 526.18109 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="744.09448 : 526.18109 : 1"
+       inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+       id="perspective10-1-8" />
+    <inkscape:perspective
+       id="perspective2390-1-5"
+       inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+       inkscape:vp_z="744.09448 : 526.18109 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 526.18109 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective2823-4-7"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective3638-0-0"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective3660-1-1"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 526.18109 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="744.09448 : 526.18109 : 1"
+       inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+       id="perspective10-8-9" />
+    <inkscape:perspective
+       id="perspective2390-9-4"
+       inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+       inkscape:vp_z="744.09448 : 526.18109 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 526.18109 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective2823-91"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective3638-9"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective3660-15"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 526.18109 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="744.09448 : 526.18109 : 1"
+       inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+       id="perspective10-95-1" />
+    <inkscape:perspective
+       id="perspective2390-7-6"
+       inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+       inkscape:vp_z="744.09448 : 526.18109 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 526.18109 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <linearGradient
+       gradientTransform="matrix(1.0192308,0,0,1.0192307,-1.4201081,-3.4868979)"
+       gradientUnits="userSpaceOnUse"
+       y2="128.81978"
+       x2="21.345606"
+       y1="233.81979"
+       x1="126.3456"
+       id="linearGradient3732-7"
+       xlink:href="#linearGradient3600-9-0"
+       inkscape:collect="always" />
+    <radialGradient
+       r="31.534483"
+       fy="195.12364"
+       fx="36.706085"
+       cy="195.12364"
+       cx="36.706085"
+       gradientTransform="matrix(-0.00149719,2.6245482,-3.0928769,-0.00176976,675.6881,48.253399)"
+       gradientUnits="userSpaceOnUse"
+       id="radialGradient3908-2"
+       xlink:href="#linearGradient3767-8"
+       inkscape:collect="always" />
+    <radialGradient
+       r="31.534483"
+       fy="195.12364"
+       fx="36.706085"
+       cy="195.12364"
+       cx="36.706085"
+       gradientTransform="matrix(-8.8006918e-4,1.5404146,-1.8180408,-0.00103872,389.80158,-42.04812)"
+       gradientUnits="userSpaceOnUse"
+       id="radialGradient3890-0"
+       xlink:href="#linearGradient3767-8"
+       inkscape:collect="always" />
+    <radialGradient
+       r="31.534483"
+       fy="195.12364"
+       fx="36.706085"
+       cy="195.12364"
+       cx="36.706085"
+       gradientTransform="matrix(-8.8006918e-4,1.5404146,-1.8180408,-0.00103872,389.80158,-42.04812)"
+       gradientUnits="userSpaceOnUse"
+       id="radialGradient3877-6"
+       xlink:href="#linearGradient3767-8"
+       inkscape:collect="always" />
+    <radialGradient
+       gradientTransform="matrix(-8.8006918e-4,1.5404146,-1.8180408,-0.00103872,391.64719,139.27166)"
+       gradientUnits="userSpaceOnUse"
+       r="31.534483"
+       fy="195.12364"
+       fx="36.706085"
+       cy="195.12364"
+       cx="36.706085"
+       id="radialGradient3868-9"
+       xlink:href="#linearGradient3767-8"
+       inkscape:collect="always" />
+    <filter
+       color-interpolation-filters="sRGB"
+       y="-0.25"
+       x="-0.25"
+       height="1.5"
+       width="1.5"
+       inkscape:label="Drop shadow"
+       id="filter3751-4">
+      <feGaussianBlur
+         result="blur"
+         stdDeviation="1.000000"
+         in="SourceAlpha"
+         id="feGaussianBlur3753-6" />
+      <feColorMatrix
+         values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0.700000 0 "
+         type="matrix"
+         result="bluralpha"
+         id="feColorMatrix3755-2" />
+      <feOffset
+         result="offsetBlur"
+         dy="0.000000"
+         dx="0.000000"
+         in="bluralpha"
+         id="feOffset3757-6" />
+      <feMerge
+         result="fbSourceGraphic"
+         id="feMerge3759-7">
+        <feMergeNode
+           in="offsetBlur"
+           id="feMergeNode3761-5" />
+        <feMergeNode
+           in="SourceGraphic"
+           id="feMergeNode3763-6" />
+      </feMerge>
+      <feColorMatrix
+         id="feColorMatrix3892-9"
+         values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
+         in="fbSourceGraphic"
+         result="fbSourceGraphicAlpha" />
+      <feGaussianBlur
+         result="blur"
+         stdDeviation="1.000000"
+         in="fbSourceGraphicAlpha"
+         id="feGaussianBlur3894-8" />
+      <feColorMatrix
+         values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0.700000 0 "
+         type="matrix"
+         result="bluralpha"
+         id="feColorMatrix3896-7" />
+      <feOffset
+         result="offsetBlur"
+         dy="0.000000"
+         dx="0.000000"
+         in="bluralpha"
+         id="feOffset3898-2" />
+      <feMerge
+         id="feMerge3900-8">
+        <feMergeNode
+           in="offsetBlur"
+           id="feMergeNode3902-2" />
+        <feMergeNode
+           in="fbSourceGraphic"
+           id="feMergeNode3904-9" />
+      </feMerge>
+    </filter>
+    <radialGradient
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1,0,0,0.50052795,0,115.93046)"
+       r="29.593752"
+       fy="248.33372"
+       fx="37.845608"
+       cy="248.33372"
+       cx="37.845608"
+       id="radialGradient3705-0"
+       xlink:href="#linearGradient3699-6"
+       inkscape:collect="always" />
+    <radialGradient
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(-0.54418933,-1.0688069e-8,0,-0.40006699,58.537282,258.88884)"
+       r="29.59375"
+       fy="170.49739"
+       fx="38.022934"
+       cy="170.49739"
+       cx="38.022934"
+       id="radialGradient3689-0"
+       xlink:href="#linearGradient3683-2"
+       inkscape:collect="always" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       id="perspective4304-4" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       id="perspective4281-6" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       id="perspective4259-6" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 526.18109 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="744.09448 : 526.18109 : 1"
+       inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+       id="perspective10-9-5" />
+    <inkscape:perspective
+       id="perspective2390-4-0"
+       inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+       inkscape:vp_z="744.09448 : 526.18109 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 526.18109 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       id="perspective4032-8" />
+    <linearGradient
+       id="linearGradient3600-95-9">
+      <stop
+         id="stop3602-3-3"
+         offset="0"
+         style="stop-color:#fc390e;stop-opacity:1;" />
+      <stop
+         id="stop3604-8-3"
+         offset="1"
+         style="stop-color:#fd9a85;stop-opacity:1;" />
+    </linearGradient>
+    <linearGradient
+       y2="-30"
+       x2="39"
+       y1="22"
+       x1="39"
+       gradientTransform="translate(1.845606,221.31978)"
+       gradientUnits="userSpaceOnUse"
+       id="linearGradient3965-0-6"
+       xlink:href="#linearGradient3600-95-9"
+       inkscape:collect="always" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       id="perspective3994-9" />
+    <linearGradient
+       y2="-30"
+       x2="39"
+       y1="22"
+       x1="39"
+       gradientTransform="translate(1.845606,221.31978)"
+       gradientUnits="userSpaceOnUse"
+       id="linearGradient3965-1"
+       xlink:href="#linearGradient3600-7"
+       inkscape:collect="always" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       id="perspective3869-9" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       id="perspective3847-3" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       id="perspective3825-0" />
+    <linearGradient
+       id="linearGradient3600-9-0">
+      <stop
+         id="stop3602-1-3"
+         offset="0"
+         style="stop-color:#fc390e;stop-opacity:1;" />
+      <stop
+         id="stop3604-3-4"
+         offset="1"
+         style="stop-color:#fed0c6;stop-opacity:1;" />
+    </linearGradient>
+    <linearGradient
+       gradientUnits="userSpaceOnUse"
+       y2="-30"
+       x2="39"
+       y1="22"
+       x1="39"
+       id="linearGradient3606-7-1"
+       xlink:href="#linearGradient3600-9-0"
+       inkscape:collect="always" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       id="perspective3755-1" />
+    <filter
+       color-interpolation-filters="sRGB"
+       y="-0.25"
+       x="-0.25"
+       height="1.5"
+       width="1.5"
+       inkscape:label="Drop shadow"
+       id="filter3692-2">
+      <feGaussianBlur
+         result="blur"
+         stdDeviation="1.000000"
+         in="SourceAlpha"
+         id="feGaussianBlur3694-9" />
+      <feColorMatrix
+         values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1.000000 0 "
+         type="matrix"
+         result="bluralpha"
+         id="feColorMatrix3696-9" />
+      <feOffset
+         result="offsetBlur"
+         dy="0.000000"
+         dx="0.000000"
+         in="bluralpha"
+         id="feOffset3698-0" />
+      <feMerge
+         id="feMerge3700-8">
+        <feMergeNode
+           in="offsetBlur"
+           id="feMergeNode3702-1" />
+        <feMergeNode
+           in="SourceGraphic"
+           id="feMergeNode3704-3" />
+      </feMerge>
+    </filter>
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 526.18109 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="744.09448 : 526.18109 : 1"
+       inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+       id="perspective2390-6" />
+    <inkscape:perspective
+       id="perspective10-0"
+       inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+       inkscape:vp_z="744.09448 : 526.18109 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 526.18109 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <linearGradient
+       id="linearGradient3600-7">
+      <stop
+         id="stop3602-2"
+         offset="0"
+         style="stop-color:#fc390e;stop-opacity:1;" />
+      <stop
+         id="stop3604-4"
+         offset="1"
+         style="stop-color:#fd9a85;stop-opacity:1;" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient4069-6">
+      <stop
+         id="stop4071-2"
+         offset="0"
+         style="stop-color:#f4f3fe;stop-opacity:1;" />
+      <stop
+         style="stop-color:#8e84d6;stop-opacity:1;"
+         offset="0.73082942"
+         id="stop4249-8" />
+      <stop
+         id="stop4073-4"
+         offset="1"
+         style="stop-color:#2815af;stop-opacity:1;" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient3683-2"
+       inkscape:collect="always">
+      <stop
+         id="stop3685-5"
+         offset="0"
+         style="stop-color:#ffffff;stop-opacity:0.88687783" />
+      <stop
+         id="stop3687-8"
+         offset="1"
+         style="stop-color:#ffffff;stop-opacity:0;" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient3699-6"
+       inkscape:collect="always">
+      <stop
+         id="stop3701-4"
+         offset="0"
+         style="stop-color:#000000;stop-opacity:1;" />
+      <stop
+         id="stop3703-6"
+         offset="1"
+         style="stop-color:#000000;stop-opacity:0;" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient3767-8">
+      <stop
+         id="stop3769-5"
+         offset="0"
+         style="stop-color:#ffccc5;stop-opacity:1;" />
+      <stop
+         id="stop3771-0"
+         offset="1"
+         style="stop-color:#ff6b56;stop-opacity:1;" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient3806">
+      <stop
+         id="stop3808"
+         offset="0"
+         style="stop-color:#70a10b;stop-opacity:1;" />
+      <stop
+         style="stop-color:#a4eb10;stop-opacity:1;"
+         offset="0.5"
+         id="stop3814" />
+      <stop
+         id="stop3810"
+         offset="1"
+         style="stop-color:#d0f67e;stop-opacity:1;" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient3821">
+      <stop
+         id="stop3823"
+         offset="0"
+         style="stop-color:#b82302;stop-opacity:1;" />
+      <stop
+         style="stop-color:#fc390e;stop-opacity:1;"
+         offset="0.5"
+         id="stop3829" />
+      <stop
+         id="stop3825"
+         offset="1"
+         style="stop-color:#fd7a5d;stop-opacity:1;" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3806"
+       id="linearGradient3972"
+       x1="46.61478"
+       y1="211.15619"
+       x2="46.890617"
+       y2="129.17763"
+       gradientUnits="userSpaceOnUse" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3821"
+       id="linearGradient3989"
+       x1="76.815063"
+       y1="233.65622"
+       x2="76.899963"
+       y2="129.2272"
+       gradientUnits="userSpaceOnUse" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3821"
+       id="linearGradient4016"
+       gradientUnits="userSpaceOnUse"
+       x1="76.815063"
+       y1="233.65622"
+       x2="76.899963"
+       y2="129.2272" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3806"
+       id="linearGradient4018"
+       gradientUnits="userSpaceOnUse"
+       x1="46.61478"
+       y1="211.15619"
+       x2="46.890617"
+       y2="129.17763" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3806"
+       id="linearGradient4021"
+       gradientUnits="userSpaceOnUse"
+       x1="46.61478"
+       y1="211.15619"
+       x2="46.890617"
+       y2="129.17763"
+       gradientTransform="translate(-146.28831,0.32690154)" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3821"
+       id="linearGradient4025"
+       gradientUnits="userSpaceOnUse"
+       x1="76.815063"
+       y1="233.65622"
+       x2="76.899963"
+       y2="129.2272"
+       gradientTransform="translate(-146.28831,0.32690154)" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3821"
+       id="linearGradient4036"
+       gradientUnits="userSpaceOnUse"
+       x1="76.815063"
+       y1="233.65622"
+       x2="76.899963"
+       y2="129.2272" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3806"
+       id="linearGradient4038"
+       gradientUnits="userSpaceOnUse"
+       x1="46.61478"
+       y1="211.15619"
+       x2="46.890617"
+       y2="129.17763" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3806"
+       id="linearGradient3791"
+       gradientUnits="userSpaceOnUse"
+       x1="46.61478"
+       y1="211.15619"
+       x2="46.890617"
+       y2="129.17763"
+       gradientTransform="translate(16.092763,0.22975625)" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3821"
+       id="linearGradient3795"
+       gradientUnits="userSpaceOnUse"
+       x1="76.815063"
+       y1="233.65622"
+       x2="76.899963"
+       y2="129.2272"
+       gradientTransform="translate(16.092763,0.22975625)" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3806"
+       id="linearGradient3805"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="translate(16.092763,0.22975625)"
+       x1="46.61478"
+       y1="211.15619"
+       x2="46.890617"
+       y2="129.17763" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3806"
+       id="linearGradient3808"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="translate(-4.502016,0.22975625)"
+       x1="46.61478"
+       y1="211.15619"
+       x2="46.890617"
+       y2="129.17763" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3806"
+       id="linearGradient3822"
+       x1="73.33065"
+       y1="233.82085"
+       x2="73.917023"
+       y2="128.23175"
+       gradientUnits="userSpaceOnUse" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3806"
+       id="linearGradient3828"
+       gradientUnits="userSpaceOnUse"
+       x1="73.33065"
+       y1="233.82085"
+       x2="73.917023"
+       y2="128.23175" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3806"
+       id="linearGradient3834"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="translate(-4.502016,0.22975625)"
+       x1="46.61478"
+       y1="211.15619"
+       x2="46.890617"
+       y2="129.17763" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3806"
+       id="linearGradient3837"
+       gradientUnits="userSpaceOnUse"
+       x1="73.33065"
+       y1="233.82085"
+       x2="73.917023"
+       y2="128.23175"
+       gradientTransform="translate(-1.307605,0)" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3806"
+       id="linearGradient3841"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="translate(-5.3192691,0.22975625)"
+       x1="46.61478"
+       y1="211.15619"
+       x2="46.890617"
+       y2="129.17763" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3806"
+       id="linearGradient3848"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="translate(-1.307605,0)"
+       x1="73.33065"
+       y1="233.82085"
+       x2="73.917023"
+       y2="128.23175" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3806"
+       id="linearGradient3854"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="translate(-5.3192691,0.22975625)"
+       x1="46.61478"
+       y1="211.15619"
+       x2="46.890617"
+       y2="129.17763" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3821"
+       id="linearGradient3869"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="translate(16.092763,0.22975625)"
+       x1="76.815063"
+       y1="233.65622"
+       x2="76.899963"
+       y2="129.2272" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3806"
+       id="linearGradient3871"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="translate(-5.3192691,0.22975625)"
+       x1="46.61478"
+       y1="211.15619"
+       x2="46.890617"
+       y2="129.17763" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3806"
+       id="linearGradient3873"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="translate(-1.307605,0)"
+       x1="73.33065"
+       y1="233.82085"
+       x2="73.917023"
+       y2="128.23175" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3821"
+       id="linearGradient3899"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="translate(16.092763,0.22975625)"
+       x1="76.815063"
+       y1="233.65622"
+       x2="76.899963"
+       y2="129.2272" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3806"
+       id="linearGradient3901"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="translate(-5.3192691,0.22975625)"
+       x1="46.61478"
+       y1="211.15619"
+       x2="46.890617"
+       y2="129.17763" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3806"
+       id="linearGradient3903"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="translate(-1.307605,0)"
+       x1="73.33065"
+       y1="233.82085"
+       x2="73.917023"
+       y2="128.23175" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3821"
+       id="linearGradient3912"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.89293764,0,0,0.89293764,-94.770046,94.513)"
+       x1="76.815063"
+       y1="233.65622"
+       x2="76.899963"
+       y2="129.2272" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3806"
+       id="linearGradient3918"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="translate(-1.307605,0)"
+       x1="73.33065"
+       y1="233.82085"
+       x2="73.917023"
+       y2="128.23175" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3806"
+       id="linearGradient3921"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.89293764,0,0,0.89293764,-110.74534,94.307842)"
+       x1="73.33065"
+       y1="233.82085"
+       x2="73.917023"
+       y2="128.23175" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3806"
+       id="linearGradient3924"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="translate(-5.3192691,0.22975625)"
+       x1="46.61478"
+       y1="211.15619"
+       x2="46.890617"
+       y2="129.17763" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3806"
+       id="linearGradient3927"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.89293764,0,0,0.89293764,-114.47346,94.513)"
+       x1="46.61478"
+       y1="211.15619"
+       x2="46.890617"
+       y2="129.17763" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3821"
+       id="linearGradient3049"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.89293764,0,0,0.89293764,14.90532,19.325713)"
+       x1="76.815063"
+       y1="233.65622"
+       x2="76.899963"
+       y2="129.2272" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3806"
+       id="linearGradient3055"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="translate(-1.307605,0)"
+       x1="73.33065"
+       y1="233.82085"
+       x2="73.917023"
+       y2="128.23175" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3806"
+       id="linearGradient3057"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="translate(-5.3192691,0.22975625)"
+       x1="46.61478"
+       y1="211.15619"
+       x2="46.890617"
+       y2="129.17763" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3821"
+       id="linearGradient3075"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.89293764,0,0,0.89293764,14.90532,19.325713)"
+       x1="76.815063"
+       y1="233.65622"
+       x2="76.899963"
+       y2="129.2272" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3821"
+       id="linearGradient3077"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.89293764,0,0,0.89293764,14.90532,19.325713)"
+       x1="76.815063"
+       y1="233.65622"
+       x2="76.899963"
+       y2="129.2272" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3821"
+       id="linearGradient3131"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.89293764,0,0,0.89293764,14.90532,19.325713)"
+       x1="76.815063"
+       y1="233.65622"
+       x2="76.899963"
+       y2="129.2272" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3821"
+       id="linearGradient3133"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.89293764,0,0,0.89293764,14.90532,19.325713)"
+       x1="76.815063"
+       y1="233.65622"
+       x2="76.899963"
+       y2="129.2272" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3806"
+       id="linearGradient3135"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="translate(-5.3192691,0.22975625)"
+       x1="46.61478"
+       y1="211.15619"
+       x2="46.890617"
+       y2="129.17763" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3806"
+       id="linearGradient3137"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="translate(-1.307605,0)"
+       x1="73.33065"
+       y1="233.82085"
+       x2="73.917023"
+       y2="128.23175" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3821"
+       id="linearGradient3139"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.89293764,0,0,0.89293764,14.90532,19.325713)"
+       x1="76.815063"
+       y1="233.65622"
+       x2="76.899963"
+       y2="129.2272" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3821"
+       id="linearGradient3141"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.89293764,0,0,0.89293764,14.90532,19.325713)"
+       x1="76.815063"
+       y1="233.65622"
+       x2="76.899963"
+       y2="129.2272" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3821"
+       id="linearGradient3156"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.89293764,0,0,0.89293764,-85.41555,95.144254)"
+       x1="76.815063"
+       y1="233.65622"
+       x2="76.899963"
+       y2="129.2272" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3821"
+       id="linearGradient3159"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.89293764,0,0,0.89293764,-85.41555,95.144254)"
+       x1="76.815063"
+       y1="233.65622"
+       x2="76.899963"
+       y2="129.2272" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3821"
+       id="linearGradient3163"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.89293764,0,0,0.89293764,14.90532,19.325713)"
+       x1="76.815063"
+       y1="233.65622"
+       x2="76.899963"
+       y2="129.2272" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3821"
+       id="linearGradient3166"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.89293764,0,0,0.89293764,-95.35518,95.144254)"
+       x1="76.815063"
+       y1="233.65622"
+       x2="76.899963"
+       y2="129.2272" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3821"
+       id="linearGradient3169"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.89293764,0,0,0.89293764,14.90532,19.325713)"
+       x1="76.815063"
+       y1="233.65622"
+       x2="76.899963"
+       y2="129.2272" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3821"
+       id="linearGradient3172"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.89293764,0,0,0.89293764,-95.35518,95.144254)"
+       x1="76.815063"
+       y1="233.65622"
+       x2="76.899963"
+       y2="129.2272" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3806"
+       id="linearGradient3175"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="translate(-1.307605,0)"
+       x1="73.33065"
+       y1="233.82085"
+       x2="73.917023"
+       y2="128.23175" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3806"
+       id="linearGradient3178"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.89293764,0,0,0.89293764,-92.375835,94.939096)"
+       x1="73.33065"
+       y1="233.82085"
+       x2="73.917023"
+       y2="128.23175" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3806"
+       id="linearGradient3181"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="translate(-5.3192691,0.22975625)"
+       x1="46.61478"
+       y1="211.15619"
+       x2="46.890617"
+       y2="129.17763" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3806"
+       id="linearGradient3184"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.89293764,0,0,0.89293764,-85.470867,95.144254)"
+       x1="46.61478"
+       y1="211.15619"
+       x2="46.890617"
+       y2="129.17763" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3821"
+       id="linearGradient3964"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.89293764,0,0,0.89293764,14.90532,19.325713)"
+       x1="76.815063"
+       y1="233.65622"
+       x2="76.899963"
+       y2="129.2272" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3821"
+       id="linearGradient3966"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.89293764,0,0,0.89293764,14.90532,19.325713)"
+       x1="76.815063"
+       y1="233.65622"
+       x2="76.899963"
+       y2="129.2272" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3806"
+       id="linearGradient3968"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="translate(-5.3192691,0.22975625)"
+       x1="46.61478"
+       y1="211.15619"
+       x2="46.890617"
+       y2="129.17763" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3806"
+       id="linearGradient3970"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="translate(-1.307605,0)"
+       x1="73.33065"
+       y1="233.82085"
+       x2="73.917023"
+       y2="128.23175" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3821"
+       id="linearGradient3973"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.89293764,0,0,0.89293764,14.90532,19.325713)"
+       x1="76.815063"
+       y1="233.65622"
+       x2="76.899963"
+       y2="129.2272" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3821"
+       id="linearGradient3975"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.89293764,0,0,0.89293764,14.90532,19.325713)"
+       x1="76.815063"
+       y1="233.65622"
+       x2="76.899963"
+       y2="129.2272" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3821"
+       id="linearGradient3086"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.89293764,0,0,0.89293764,10.051082,19.325713)"
+       x1="76.815063"
+       y1="233.65622"
+       x2="76.899963"
+       y2="129.2272" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3821"
+       id="linearGradient3089"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.89293764,0,0,0.89293764,10.051082,19.325713)"
+       x1="76.815063"
+       y1="233.65622"
+       x2="76.899963"
+       y2="129.2272" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3821"
+       id="linearGradient3097"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1.1088199,0,0,1.1088199,-5.5890938,-22.869645)"
+       x1="76.815063"
+       y1="233.65622"
+       x2="76.899963"
+       y2="129.2272" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3821"
+       id="linearGradient3099"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1.1088199,0,0,1.1088199,-5.5890938,-22.869645)"
+       x1="76.815063"
+       y1="233.65622"
+       x2="76.899963"
+       y2="129.2272" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3821"
+       id="linearGradient3162"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.89293764,0,0,0.89293764,114.98638,-11.076103)"
+       x1="76.815063"
+       y1="233.65622"
+       x2="76.899963"
+       y2="129.2272" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3821"
+       id="linearGradient3164"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.89293764,0,0,0.89293764,114.98638,-11.076103)"
+       x1="76.815063"
+       y1="233.65622"
+       x2="76.899963"
+       y2="129.2272" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3806"
+       id="linearGradient3167"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="translate(112.19767,-33.817208)"
+       x1="46.61478"
+       y1="211.15619"
+       x2="46.890617"
+       y2="129.17763" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3806"
+       id="linearGradient3170"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="translate(116.20934,-34.046964)"
+       x1="73.33065"
+       y1="233.82085"
+       x2="73.917023"
+       y2="128.23175" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3821"
+       id="linearGradient3173"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1.1088199,0,0,1.1088199,124.71603,-60.621597)"
+       x1="76.815063"
+       y1="233.65622"
+       x2="76.899963"
+       y2="129.2272" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3821"
+       id="linearGradient3176"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1.1088199,0,0,1.1088199,124.71603,-60.621597)"
+       x1="76.815063"
+       y1="233.65622"
+       x2="76.899963"
+       y2="129.2272" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3821"
+       id="linearGradient3191"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.89293764,0,0,0.89293764,10.05108,19.325713)"
+       x1="76.815063"
+       y1="233.65622"
+       x2="76.899963"
+       y2="129.2272" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3821"
+       id="linearGradient3194"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.89293764,0,0,0.89293764,10.05108,19.325713)"
+       x1="76.815063"
+       y1="233.65622"
+       x2="76.899963"
+       y2="129.2272" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3821"
+       id="linearGradient3201"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1.1088199,0,0,1.1088199,124.71603,-60.621597)"
+       x1="76.815063"
+       y1="233.65622"
+       x2="76.899963"
+       y2="129.2272" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3821"
+       id="linearGradient3204"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.89293762,0,0,0.89293762,0.11145644,19.325714)"
+       x1="76.815063"
+       y1="233.65622"
+       x2="76.899963"
+       y2="129.2272" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3821"
+       id="linearGradient3207"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1.1088199,0,0,1.1088199,124.71603,-60.621597)"
+       x1="76.815063"
+       y1="233.65622"
+       x2="76.899963"
+       y2="129.2272" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3821"
+       id="linearGradient3210"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.89293762,0,0,0.89293762,0.11145644,19.325714)"
+       x1="76.815063"
+       y1="233.65622"
+       x2="76.899963"
+       y2="129.2272" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3806"
+       id="linearGradient3213"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="translate(116.20934,-34.046964)"
+       x1="73.33065"
+       y1="233.82085"
+       x2="73.917023"
+       y2="128.23175" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3806"
+       id="linearGradient3216"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.89293764,0,0,0.89293764,3.0908038,19.120555)"
+       x1="73.33065"
+       y1="233.82085"
+       x2="73.917023"
+       y2="128.23175" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3806"
+       id="linearGradient3219"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="translate(112.19767,-33.817208)"
+       x1="46.61478"
+       y1="211.15619"
+       x2="46.890617"
+       y2="129.17763" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3806"
+       id="linearGradient3222"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.89293764,0,0,0.89293764,9.9957637,19.325713)"
+       x1="46.61478"
+       y1="211.15619"
+       x2="46.890617"
+       y2="129.17763" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3821"
+       id="linearGradient4002"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.89293764,0,0,0.89293764,6.5321621,19.325714)"
+       x1="76.815063"
+       y1="233.65622"
+       x2="76.899963"
+       y2="129.2272" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3821"
+       id="linearGradient4004"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.89293764,0,0,0.89293764,6.5321621,19.325714)"
+       x1="76.815063"
+       y1="233.65622"
+       x2="76.899963"
+       y2="129.2272" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3806"
+       id="linearGradient4006"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="translate(-9.2601041,0.22975701)"
+       x1="46.61478"
+       y1="211.15619"
+       x2="46.890617"
+       y2="129.17763" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3806"
+       id="linearGradient4008"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="translate(-5.24844,7.5730512e-7)"
+       x1="73.33065"
+       y1="233.82085"
+       x2="73.917023"
+       y2="128.23175" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3821"
+       id="linearGradient4010"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1.1088199,0,0,1.1088199,-9.9587701,-22.869644)"
+       x1="76.815063"
+       y1="233.65622"
+       x2="76.899963"
+       y2="129.2272" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3821"
+       id="linearGradient4012"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1.1088199,0,0,1.1088199,-9.9587701,-22.869644)"
+       x1="76.815063"
+       y1="233.65622"
+       x2="76.899963"
+       y2="129.2272" />
+    <filter
+       inkscape:collect="always"
+       id="filter4022"
+       x="-0.16087916"
+       width="1.3217583"
+       y="-0.15140808"
+       height="1.3028162">
+      <feGaussianBlur
+         inkscape:collect="always"
+         stdDeviation="6.407243"
+         id="feGaussianBlur4024" />
+    </filter>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3821"
+       id="linearGradient3112"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1.0177038,0,0,1.0177038,9.9636946,-3.3089863)"
+       x1="76.815063"
+       y1="233.65622"
+       x2="76.899963"
+       y2="129.2272" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3821"
+       id="linearGradient3115"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1.0177038,0,0,1.0177038,9.9636946,-3.3089863)"
+       x1="76.815063"
+       y1="233.65622"
+       x2="76.899963"
+       y2="129.2272" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3821"
+       id="linearGradient3114"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1.0177038,0,0,1.0177038,9.9636946,-3.3089863)"
+       x1="76.815063"
+       y1="233.65622"
+       x2="76.899963"
+       y2="129.2272" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3821"
+       id="linearGradient3116"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1.0177038,0,0,1.0177038,9.9636946,-3.3089863)"
+       x1="76.815063"
+       y1="233.65622"
+       x2="76.899963"
+       y2="129.2272" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3806"
+       id="linearGradient3118"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="translate(-9.2601041,0.22975701)"
+       x1="46.61478"
+       y1="211.15619"
+       x2="46.890617"
+       y2="129.17763" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3821"
+       id="linearGradient3120"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1.1088199,0,0,1.1088199,-9.9587701,-22.869644)"
+       x1="76.815063"
+       y1="233.65622"
+       x2="76.899963"
+       y2="129.2272" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3821"
+       id="linearGradient3122"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1.1088199,0,0,1.1088199,-9.9587701,-22.869644)"
+       x1="76.815063"
+       y1="233.65622"
+       x2="76.899963"
+       y2="129.2272" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#838383"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     gridtolerance="10000"
+     guidetolerance="10"
+     objecttolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="6.1180556"
+     inkscape:cx="51.650397"
+     inkscape:cy="72"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer2"
+     showgrid="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="1149"
+     inkscape:window-x="0"
+     inkscape:window-y="0"
+     showguides="true"
+     inkscape:guide-bbox="true"
+     showborder="true"
+     inkscape:window-maximized="1"
+     inkscape:snap-grids="false"
+     inkscape:snap-to-guides="false">
+    <sodipodi:guide
+       orientation="1,0"
+       position="15,0"
+       id="guide2406" />
+    <sodipodi:guide
+       orientation="1,0"
+       position="129,0"
+       id="guide2408" />
+    <sodipodi:guide
+       orientation="0,1"
+       position="0,129"
+       id="guide2410" />
+    <inkscape:grid
+       type="xygrid"
+       id="grid3594"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true" />
+    <sodipodi:guide
+       orientation="1,0"
+       position="19,0"
+       id="guide2892" />
+    <sodipodi:guide
+       orientation="0,1"
+       position="0,125"
+       id="guide2898" />
+    <sodipodi:guide
+       orientation="0,1"
+       position="0,19"
+       id="guide2900" />
+    <sodipodi:guide
+       orientation="1,0"
+       position="125,0"
+       id="guide3674" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:groupmode="layer"
+     id="layer1"
+     inkscape:label="Background"
+     transform="translate(0,112)" />
+  <g
+     inkscape:groupmode="layer"
+     id="layer2"
+     inkscape:label="Jetty"
+     style="display:inline"
+     transform="translate(-1.845606,-109.31978)">
+    <g
+       id="g3095"
+       transform="translate(-8.8263341,-1.92039e-6)">
+      <path
+         transform="matrix(1.1397255,0,0,1.1397255,-1.4917798,-25.334995)"
+         sodipodi:nodetypes="cccccccccccccsscccccccccccccccccccccc"
+         id="path3158"
+         d="m 47.298731,130.53853 c -1.856357,0.002 -3.618097,1.40895 -4.03125,3.21875 l -3.40625,14.8125 c -0.206057,0.85823 -0.128135,1.78216 0.21875,2.59375 -0.66284,0.56365 -1.140629,1.34145 -1.34375,2.1875 l -12.59375,53.59375 c -0.551618,2.41398 1.55505,5.05956 4.03125,5.0625 l 23.375,0 c -0.633907,0.72001 -1.005564,1.66605 -1.03125,2.625 l -0.3125,13.25 c -0.04155,2.18878 1.935834,4.21111 4.125,4.21875 l 22.34375,-0.0625 c 0.06273,-0.009 0.12525,-0.0194 0.1875,-0.0312 2.896621,-0. [...]
+         style="font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;color:#000000;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:8.27957058;marker:none;visibility:visible;display:inline;overflow:visible;filter:url(#filter [...]
+      <g
+         transform="matrix(0.41037819,0,0,0.41037819,-291.47034,396.20634)"
+         style="display:inline"
+         inkscape:label="Border"
+         id="layer3" />
+      <g
+         transform="matrix(0.40787727,0,0,0.40787727,-248.96012,287.0931)"
+         style="display:inline"
+         inkscape:label="Border"
+         id="layer3-7" />
+      <g
+         transform="matrix(0.32012003,0,0,0.32012003,-165.62545,297.88255)"
+         style="display:inline"
+         inkscape:label="Border"
+         id="layer3-5" />
+      <path
+         id="path2992"
+         d="m 83.735937,150.55163 -15.285805,66.21009 c -0.90836,2.66452 -2.292043,2.52227 -5.380458,2.64338 l -0.363343,15.07874 25.433999,-0.0908 c 4.935407,-0.27251 8.011263,-3.07039 10.039927,-9.9739 l 17.053643,-73.86745 -31.497963,0 z"
+         style="fill:#fc390e;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:9.43643761;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline" />
+      <path
+         id="path2990"
+         d="m 88.894821,128.20604 -3.900618,16.89545 31.497967,0 3.90061,-16.89545 -31.497959,0 z"
+         style="fill:#fc390e;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:9.43643761;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline" />
+      <path
+         id="path3061"
+         d="m 88.894821,128.20604 -3.900618,16.89545 31.497967,0 3.90061,-16.89545 -31.497959,0 z"
+         style="fill:url(#linearGradient3114);fill-opacity:1;fill-rule:evenodd;stroke:none" />
+      <path
+         id="path3000"
+         d="m 83.735937,150.55163 -15.28581,66.21009 c -0.908355,2.66452 -2.292043,2.52227 -5.380457,2.64338 l -0.363339,15.07874 25.433999,-0.0908 c 4.935407,-0.27251 8.011263,-3.07039 10.039927,-9.9739 l 17.053643,-73.86745 -31.497963,0 z"
+         style="fill:url(#linearGradient3116);fill-opacity:1;fill-rule:evenodd;stroke:none" />
+      <g
+         transform="matrix(1.0177038,0,0,1.0177038,19.324692,-3.5428112)"
+         id="g3850">
+        <path
+           style="fill:#fc390e;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:9.27200031;stroke-linejoin:round;stroke-miterlimit:4.0999999;stroke-opacity:1;stroke-dasharray:none;display:inline"
+           d="m 32.515899,129.40739 -3.824816,16.60897 30.94624,0 3.836408,-16.60897 -30.957832,0 z m -5.064978,21.96372 -14.105466,60.01484 31.201228,0 13.850474,-60.01484 -30.946236,0 z"
+           id="path3693-4" />
+        <path
+           id="path3687-1"
+           d="m 32.515899,129.40739 -3.824816,16.60897 30.94624,0 3.836408,-16.60897 -30.957832,0 z m -5.064978,21.96372 -14.105466,60.01484 31.201228,0 13.850474,-60.01484 -30.946236,0 z"
+           style="fill:url(#linearGradient3118);fill-opacity:1;fill-rule:evenodd;stroke:none;display:inline" />
+      </g>
+      <g
+         transform="matrix(0.91782603,0,0,0.91782603,7.775668,17.681369)"
+         id="g3071">
+        <path
+           style="fill:#fc390e;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:10.28129196;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline"
+           d="m 100.46965,187.31933 -4.747243,20.56251 27.735433,0 4.74725,-20.56251 -27.73544,0 z"
+           id="path2980" />
+        <path
+           style="fill:url(#linearGradient3120);fill-opacity:1;fill-rule:evenodd;stroke:none"
+           d="m 100.46962,187.31933 -4.747249,20.56251 27.735439,0 4.74725,-20.56251 -27.73544,0 z"
+           id="path3065" />
+      </g>
+      <g
+         transform="matrix(0.91782603,0,0,0.91782603,7.775668,17.681369)"
+         id="g3067">
+        <path
+           style="fill:#fc390e;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:10.28129196;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline"
+           d="m 108.08729,154.32368 -4.74725,20.56251 27.73547,0 4.74724,-20.56251 -27.73546,0 z"
+           id="path2982" />
+        <path
+           style="fill:url(#linearGradient3122);fill-opacity:1;fill-rule:evenodd;stroke:none"
+           d="m 108.08725,154.32368 -4.74725,20.56251 27.73548,0 4.74724,-20.56251 -27.73547,0 z"
+           id="path3063" />
+      </g>
+    </g>
+  </g>
+</svg>
diff --git a/jetty-servlets/src/test/resources/test.svg.sha1 b/jetty-servlets/src/test/resources/test.svg.sha1
new file mode 100644
index 0000000..3b170f0
--- /dev/null
+++ b/jetty-servlets/src/test/resources/test.svg.sha1
@@ -0,0 +1 @@
+1ccb7a0b85585d0e9bdc3863ad093d4e53a9ea68  test.svg
diff --git a/jetty-servlets/src/test/resources/test.svgz b/jetty-servlets/src/test/resources/test.svgz
new file mode 100644
index 0000000..c4d595f
Binary files /dev/null and b/jetty-servlets/src/test/resources/test.svgz differ
diff --git a/jetty-servlets/src/test/resources/test.svgz.sha1 b/jetty-servlets/src/test/resources/test.svgz.sha1
new file mode 100644
index 0000000..e8ec3aa
--- /dev/null
+++ b/jetty-servlets/src/test/resources/test.svgz.sha1
@@ -0,0 +1 @@
+62df7c3ac6ee6e4462b6abf9ef15b4e916ecf68f  test.svgz
diff --git a/jetty-spdy/pom.xml b/jetty-spdy/pom.xml
index 9c55f68..7bcdad7 100644
--- a/jetty-spdy/pom.xml
+++ b/jetty-spdy/pom.xml
@@ -3,7 +3,7 @@
     <parent>
         <groupId>org.eclipse.jetty</groupId>
         <artifactId>jetty-project</artifactId>
-        <version>8.1.17.v20150415</version>
+        <version>9.2.13.v20150730</version>
     </parent>
 
     <modelVersion>4.0.0</modelVersion>
@@ -12,246 +12,77 @@
     <packaging>pom</packaging>
     <name>Jetty :: SPDY :: Parent</name>
     <url>http://www.eclipse.org/jetty</url>
-    <properties>
-        <npn.version>1.1.5.v20130313</npn.version>
-        <npn.api.version>1.1.0.v20120525</npn.api.version>
-    </properties>
 
     <modules>
         <module>spdy-core</module>
-        <module>spdy-jetty</module>
-        <module>spdy-jetty-http</module>
-        <module>spdy-jetty-http-webapp</module>
+        <module>spdy-client</module>
+        <module>spdy-server</module>
+        <module>spdy-http-common</module>
+        <module>spdy-http-server</module>
+        <module>spdy-http-client-transport</module>
+        <module>spdy-example-webapp</module>
+        <module>spdy-alpn-tests</module>
     </modules>
 
+    <profiles>
+      <profile>
+        <id>npn</id>
+        <activation>
+          <jdk>1.7</jdk>
+        </activation>
+        <modules>
+<!--
+          <module>spdy-npn-tests</module>
+-->
+        </modules>
+      </profile>
+    </profiles>
+
     <build>
         <plugins>
             <plugin>
-                <artifactId>maven-enforcer-plugin</artifactId>
+                <artifactId>maven-pmd-plugin</artifactId>
+                <configuration>
+                    <skip>true</skip>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
                 <executions>
                     <execution>
-                        <id>require-jdk7</id>
                         <goals>
-                            <goal>enforce</goal>
+                            <goal>manifest</goal>
                         </goals>
                         <configuration>
-                            <rules>
-                                <requireJavaVersion>
-                                    <version>[1.7,)</version>
-                                </requireJavaVersion>
-                            </rules>
-                        </configuration>
-                    </execution>
-                </executions>
-            </plugin>
-            <plugin>
-                <artifactId>maven-compiler-plugin</artifactId>
-                <version>2.3.2</version>
-                <configuration>
-                    <source>1.7</source>
-                    <target>1.7</target>
-                </configuration>
-            </plugin>
-            <plugin>
-                <artifactId>maven-pmd-plugin</artifactId>
-                <configuration>
-                    <skip>true</skip>
-                </configuration>
+                            <instructions>
+                                <Export-Package>org.eclipse.jetty.spdy.*;version="9.1"</Export-Package>
+                                <Import-Package>org.eclipse.jetty.*;version="[9.0,10.0)",*</Import-Package>
+                                <_nouses>true</_nouses>
+                            </instructions>
+                          </configuration>
+                       </execution>
+                  </executions>
             </plugin>
             <plugin>
               <groupId>org.apache.maven.plugins</groupId>
-              <artifactId>maven-surefire-plugin</artifactId>
+              <artifactId>maven-jar-plugin</artifactId>
               <configuration>
-                <skipTests>true</skipTests>
+                  <archive>
+                      <manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
+                  </archive>
               </configuration>
             </plugin>
         </plugins>
     </build>
 
-    <profiles>
-         <profile>
-            <id>7u9</id>
-            <activation>
-                <property>
-                    <name>java.version</name>
-                    <value>1.7.0_9</value>
-                </property>
-            </activation>
-            <properties>
-                <npn.version>1.1.3.v20130313</npn.version>
-            </properties>
-        </profile>
-        <profile>
-            <id>7u10</id>
-            <activation>
-                <property>
-                    <name>java.version</name>
-                    <value>1.7.0_10</value>
-                </property>
-            </activation>
-            <properties>
-                <npn.version>1.1.3.v20130313</npn.version>
-            </properties>
-        </profile>
-        <profile>
-            <id>7u11</id>
-            <activation>
-                <property>
-                    <name>java.version</name>
-                    <value>1.7.0_11</value>
-                </property>
-            </activation>
-            <properties>
-                <npn.version>1.1.3.v20130313</npn.version>
-            </properties>
-        </profile>
-        <profile>
-            <id>7u13</id>
-            <activation>
-                <property>
-                    <name>java.version</name>
-                    <value>1.7.0_13</value>
-                </property>
-            </activation>
-            <properties>
-                <npn.version>1.1.4.v20130313</npn.version>
-            </properties>
-        </profile>
-        <profile>
-            <id>7u15</id>
-            <activation>
-                <property>
-                    <name>java.version</name>
-                    <value>1.7.0_15</value>
-                </property>
-            </activation>
-            <properties>
-                <npn.version>1.1.5.v20130313</npn.version>
-            </properties>
-        </profile>
-        <profile>
-            <id>7u17</id>
-            <activation>
-                <property>
-                    <name>java.version</name>
-                    <value>1.7.0_17</value>
-                </property>
-            </activation>
-            <properties>
-                <npn.version>1.1.5.v20130313</npn.version>
-            </properties>
-        </profile>
-        <profile>
-            <id>7u21</id>
-            <activation>
-                <property>
-                    <name>java.version</name>
-                    <value>1.7.0_21</value>
-                </property>
-            </activation>
-            <properties>
-                <npn.version>1.1.5.v20130313</npn.version>
-            </properties>
-        </profile>
-        <profile>
-           <id>7u25</id>
-            <activation>
-                <property>
-                    <name>java.version</name>
-                    <value>1.7.0_25</value>
-                </property>
-            </activation>
-            <properties>
-                <npn.version>1.1.5.v20130313</npn.version>
-            </properties>
-        </profile>
-        <profile>
-           <id>7u40</id>
-            <activation>
-                <property>
-                    <name>java.version</name>
-                    <value>1.7.0_40</value>
-                </property>
-            </activation>
-            <properties>
-                <npn.version>1.1.6.v20130911</npn.version>
-            </properties>
-        </profile>
-        <profile>
-           <id>7u45</id>
-            <activation>
-                <property>
-                    <name>java.version</name>
-                    <value>1.7.0_45</value>
-                </property>
-            </activation>
-            <properties>
-                <npn.version>1.1.6.v20130911</npn.version>
-            </properties>
-        </profile>
-        <profile>
-           <id>7u51</id>
-            <activation>
-                <property>
-                    <name>java.version</name>
-                    <value>1.7.0_51</value>
-                </property>
-            </activation>
-            <properties>
-                <npn.version>1.1.6.v20130911</npn.version>
-            </properties>
-        </profile>
-        <profile>
-           <id>7u55</id>
-            <activation>
-                <property>
-                    <name>java.version</name>
-                    <value>1.7.0_55</value>
-                </property>
-            </activation>
-            <properties>
-                <npn.version>1.1.7.v20140316</npn.version>
-            </properties>
-        </profile>
-    <profile>
-      <id>7u60</id>
-      <activation>
-        <property>
-          <name>java.version</name>
-          <value>1.7.0_60</value>
-        </property>
-      </activation>
-      <properties>
-        <npn.version>1.1.7.v20140316</npn.version>
-        <alpn.version>7.0.0.v20140317</alpn.version>
-      </properties>
-    </profile>
-    <profile>
-      <id>7u65</id>
-      <activation>
-        <property>
-          <name>java.version</name>
-          <value>1.7.0_65</value>
-        </property>
-      </activation>
-      <properties>
-        <npn.version>1.1.7.v20140316</npn.version>
-        <alpn.version>7.0.0.v20140317</alpn.version>
-      </properties>
-    </profile>
-    <profile>
-      <id>7u67</id>
-      <activation>
-        <property>
-          <name>java.version</name>
-          <value>1.7.0_67</value>
-        </property>
-      </activation>
-      <properties>
-        <npn.version>1.1.7.v20140316</npn.version>
-        <alpn.version>7.0.0.v20140317</alpn.version>
-      </properties>
-    </profile>
-    </profiles>
+    <dependencies>
+        <dependency>
+            <groupId>org.eclipse.jetty.toolchain</groupId>
+            <artifactId>jetty-test-helper</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
 
 </project>
diff --git a/jetty-spdy/spdy-alpn-tests/pom.xml b/jetty-spdy/spdy-alpn-tests/pom.xml
new file mode 100644
index 0000000..032ddf7
--- /dev/null
+++ b/jetty-spdy/spdy-alpn-tests/pom.xml
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>org.eclipse.jetty.spdy</groupId>
+        <artifactId>spdy-parent</artifactId>
+        <version>9.2.13.v20150730</version>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>spdy-alpn-tests</artifactId>
+    <name>Jetty :: SPDY :: ALPN Tests</name>
+
+    <build>
+        <plugins>
+            <plugin>
+                <artifactId>maven-dependency-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>copy</id>
+                        <phase>generate-resources</phase>
+                        <goals>
+                            <goal>copy</goal>
+                        </goals>
+                        <configuration>
+                            <artifactItems>
+                                <artifactItem>
+                                    <groupId>org.mortbay.jetty.alpn</groupId>
+                                    <artifactId>alpn-boot</artifactId>
+                                    <version>${alpn.version}</version>
+                                    <type>jar</type>
+                                    <overWrite>false</overWrite>
+                                    <outputDirectory>${project.build.directory}/alpn</outputDirectory>
+                                </artifactItem>
+                            </artifactItems>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <configuration>
+                    <argLine>-Xbootclasspath/p:${project.build.directory}/alpn/alpn-boot-${alpn.version}.jar</argLine>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.eclipse.jetty.alpn</groupId>
+            <artifactId>alpn-api</artifactId>
+            <version>${alpn.api.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-alpn-server</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-server</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty.spdy</groupId>
+            <artifactId>spdy-server</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty.spdy</groupId>
+            <artifactId>spdy-http-server</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty.spdy</groupId>
+            <artifactId>spdy-http-server</artifactId>
+            <version>${project.version}</version>
+            <classifier>tests</classifier>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+    
+</project>
diff --git a/jetty-spdy/spdy-alpn-tests/src/test/java/org/eclipse/jetty/spdy/server/ALPNNegotiationTest.java b/jetty-spdy/spdy-alpn-tests/src/test/java/org/eclipse/jetty/spdy/server/ALPNNegotiationTest.java
new file mode 100644
index 0000000..d37d1fa
--- /dev/null
+++ b/jetty-spdy/spdy-alpn-tests/src/test/java/org/eclipse/jetty/spdy/server/ALPNNegotiationTest.java
@@ -0,0 +1,198 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.spdy.server;
+
+import java.io.BufferedReader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.InetSocketAddress;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.List;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocket;
+
+import org.eclipse.jetty.alpn.ALPN;
+import org.eclipse.jetty.server.HttpConnectionFactory;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class ALPNNegotiationTest extends AbstractALPNTest
+{
+    @Test
+    public void testClientAdvertisingHTTPServerSpeaksHTTP() throws Exception
+    {
+        InetSocketAddress address = prepare();
+        connector.addConnectionFactory(new HttpConnectionFactory());
+
+        SslContextFactory sslContextFactory = newSslContextFactory();
+        sslContextFactory.start();
+        SSLContext sslContext = sslContextFactory.getSslContext();
+
+        try (SSLSocket client = (SSLSocket)sslContext.getSocketFactory().createSocket(address.getAddress(), address.getPort()))
+        {
+            client.setUseClientMode(true);
+            client.setSoTimeout(5000);
+
+            ALPN.put(client, new ALPN.ClientProvider()
+            {
+                @Override
+                public void unsupported()
+                {
+                }
+
+                @Override
+                public List<String> protocols()
+                {
+                    return Arrays.asList("http/1.1");
+                }
+
+                @Override
+                public void selected(String protocol)
+                {
+                    Assert.assertEquals("http/1.1", protocol);
+                }
+            });
+
+            client.startHandshake();
+
+            // Verify that the server really speaks http/1.1
+
+            OutputStream output = client.getOutputStream();
+            output.write(("" +
+                    "GET / HTTP/1.1\r\n" +
+                    "Host: localhost:" + address.getPort() + "\r\n" +
+                    "\r\n" +
+                    "").getBytes(StandardCharsets.UTF_8));
+            output.flush();
+
+            InputStream input = client.getInputStream();
+            BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
+            String line = reader.readLine();
+            Assert.assertTrue(line.contains(" 404 "));
+        }
+    }
+
+    @Test
+    public void testClientAdvertisingMultipleProtocolsServerSpeaksHTTPWhenNegotiated() throws Exception
+    {
+        InetSocketAddress address = prepare();
+        connector.addConnectionFactory(new HttpConnectionFactory());
+
+        SslContextFactory sslContextFactory = newSslContextFactory();
+        sslContextFactory.start();
+        SSLContext sslContext = sslContextFactory.getSslContext();
+        try (SSLSocket client = (SSLSocket)sslContext.getSocketFactory().createSocket(address.getAddress(), address.getPort()))
+        {
+            client.setUseClientMode(true);
+            client.setSoTimeout(5000);
+
+            ALPN.put(client, new ALPN.ClientProvider()
+            {
+                @Override
+                public void unsupported()
+                {
+                }
+
+                @Override
+                public List<String> protocols()
+                {
+                    return Arrays.asList("unknown/1.0", "http/1.1");
+                }
+
+                @Override
+                public void selected(String protocol)
+                {
+                    Assert.assertEquals("http/1.1", protocol);
+                }
+            });
+
+            client.startHandshake();
+
+            // Verify that the server really speaks http/1.1
+
+            OutputStream output = client.getOutputStream();
+            output.write(("" +
+                    "GET / HTTP/1.1\r\n" +
+                    "Host: localhost:" + address.getPort() + "\r\n" +
+                    "\r\n" +
+                    "").getBytes(StandardCharsets.UTF_8));
+            output.flush();
+
+            InputStream input = client.getInputStream();
+            BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
+            String line = reader.readLine();
+            Assert.assertTrue(line.contains(" 404 "));
+        }
+    }
+
+    @Test
+    public void testClientNotSupportingALPNServerSpeaksDefaultProtocol() throws Exception
+    {
+        InetSocketAddress address = prepare();
+        connector.addConnectionFactory(new HttpConnectionFactory());
+
+        SslContextFactory sslContextFactory = newSslContextFactory();
+        sslContextFactory.start();
+        SSLContext sslContext = sslContextFactory.getSslContext();
+        try (SSLSocket client = (SSLSocket)sslContext.getSocketFactory().createSocket(address.getAddress(), address.getPort()))
+        {
+            client.setUseClientMode(true);
+            client.setSoTimeout(5000);
+
+            ALPN.put(client, new ALPN.ClientProvider()
+            {
+                @Override
+                public void unsupported()
+                {
+                }
+
+                @Override
+                public List<String> protocols()
+                {
+                    return null;
+                }
+
+                @Override
+                public void selected(String s)
+                {
+                }
+            });
+
+            client.startHandshake();
+
+            // Verify that the server really speaks http/1.1
+
+            OutputStream output = client.getOutputStream();
+            output.write(("" +
+                    "GET / HTTP/1.1\r\n" +
+                    "Host: localhost:" + address.getPort() + "\r\n" +
+                    "\r\n" +
+                    "").getBytes(StandardCharsets.UTF_8));
+            output.flush();
+
+            InputStream input = client.getInputStream();
+            BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
+            String line = reader.readLine();
+            Assert.assertTrue(line.contains(" 404 "));
+        }
+    }
+}
diff --git a/jetty-spdy/spdy-alpn-tests/src/test/java/org/eclipse/jetty/spdy/server/ALPNSynReplyTest.java b/jetty-spdy/spdy-alpn-tests/src/test/java/org/eclipse/jetty/spdy/server/ALPNSynReplyTest.java
new file mode 100644
index 0000000..f9ca24f
--- /dev/null
+++ b/jetty-spdy/spdy-alpn-tests/src/test/java/org/eclipse/jetty/spdy/server/ALPNSynReplyTest.java
@@ -0,0 +1,149 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.spdy.server;
+
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.channels.SocketChannel;
+import java.util.Arrays;
+import java.util.List;
+import javax.net.ssl.SSLEngine;
+
+import org.eclipse.jetty.alpn.ALPN;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class ALPNSynReplyTest extends AbstractALPNTest
+{
+    @Test
+    public void testGentleCloseDuringHandshake() throws Exception
+    {
+        InetSocketAddress address = prepare();
+        SslContextFactory sslContextFactory = newSslContextFactory();
+        sslContextFactory.start();
+        SSLEngine sslEngine = sslContextFactory.newSSLEngine(address);
+        sslEngine.setUseClientMode(true);
+        ALPN.put(sslEngine, new ALPN.ClientProvider()
+        {
+            @Override
+            public void unsupported()
+            {
+            }
+
+            @Override
+            public List<String> protocols()
+            {
+                return Arrays.asList("test");
+            }
+
+            @Override
+            public void selected(String protocol)
+            {
+            }
+        });
+        sslEngine.beginHandshake();
+
+        ByteBuffer encrypted = ByteBuffer.allocate(sslEngine.getSession().getPacketBufferSize());
+        sslEngine.wrap(BufferUtil.EMPTY_BUFFER, encrypted);
+        encrypted.flip();
+
+        try (SocketChannel channel = SocketChannel.open(address))
+        {
+            // Send ClientHello, immediately followed by TLS Close Alert and then by FIN
+            channel.write(encrypted);
+            sslEngine.closeOutbound();
+            encrypted.clear();
+            sslEngine.wrap(BufferUtil.EMPTY_BUFFER, encrypted);
+            encrypted.flip();
+            channel.write(encrypted);
+            channel.shutdownOutput();
+
+            // Read ServerHello from server
+            encrypted.clear();
+            int read = channel.read(encrypted);
+            encrypted.flip();
+            Assert.assertTrue(read > 0);
+            // Cannot decrypt, as the SSLEngine has been already closed
+
+            // Now if we read more, we should either read the TLS Close Alert, or directly -1
+            encrypted.clear();
+            read = channel.read(encrypted);
+            // Sending a TLS Close Alert during handshake results in an exception when
+            // unwrapping that the server react to by closing the connection abruptly.
+            Assert.assertTrue(read < 0);
+        }
+    }
+
+    @Test
+    public void testAbruptCloseDuringHandshake() throws Exception
+    {
+        InetSocketAddress address = prepare();
+        SslContextFactory sslContextFactory = newSslContextFactory();
+        sslContextFactory.start();
+        SSLEngine sslEngine = sslContextFactory.newSSLEngine(address);
+        sslEngine.setUseClientMode(true);
+        ALPN.put(sslEngine, new ALPN.ClientProvider()
+        {
+            @Override
+            public void unsupported()
+            {
+            }
+
+            @Override
+            public List<String> protocols()
+            {
+                return Arrays.asList("test");
+            }
+
+            @Override
+            public void selected(String s)
+            {
+            }
+        });
+        sslEngine.beginHandshake();
+
+        ByteBuffer encrypted = ByteBuffer.allocate(sslEngine.getSession().getPacketBufferSize());
+        sslEngine.wrap(BufferUtil.EMPTY_BUFFER, encrypted);
+        encrypted.flip();
+
+        try (SocketChannel channel = SocketChannel.open(address))
+        {
+            // Send ClientHello, immediately followed by FIN (no TLS Close Alert)
+            channel.write(encrypted);
+            channel.shutdownOutput();
+
+            // Read ServerHello from server
+            encrypted.clear();
+            int read = channel.read(encrypted);
+            encrypted.flip();
+            Assert.assertTrue(read > 0);
+            ByteBuffer decrypted = ByteBuffer.allocate(sslEngine.getSession().getApplicationBufferSize());
+            sslEngine.unwrap(encrypted, decrypted);
+
+            // Now if we read more, we should either read the TLS Close Alert, or directly -1
+            encrypted.clear();
+            read = channel.read(encrypted);
+            // Since we have close the connection abruptly, the server also does so
+            Assert.assertTrue(read < 0);
+        }
+    }
+}
diff --git a/jetty-spdy/spdy-alpn-tests/src/test/java/org/eclipse/jetty/spdy/server/AbstractALPNTest.java b/jetty-spdy/spdy-alpn-tests/src/test/java/org/eclipse/jetty/spdy/server/AbstractALPNTest.java
new file mode 100644
index 0000000..48d046d
--- /dev/null
+++ b/jetty-spdy/spdy-alpn-tests/src/test/java/org/eclipse/jetty/spdy/server/AbstractALPNTest.java
@@ -0,0 +1,78 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.spdy.server;
+
+import java.net.InetSocketAddress;
+
+import org.eclipse.jetty.alpn.ALPN;
+import org.eclipse.jetty.alpn.server.ALPNServerConnectionFactory;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.spdy.client.SPDYClient;
+import org.eclipse.jetty.toolchain.test.TestTracker;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.junit.After;
+import org.junit.Rule;
+
+public class AbstractALPNTest
+{
+    @Rule
+    public final TestTracker tracker = new TestTracker();
+    protected Server server;
+    protected SPDYServerConnector connector;
+    protected SPDYClient.Factory clientFactory;
+
+    protected InetSocketAddress prepare() throws Exception
+    {
+        server = new Server();
+        connector = new SPDYServerConnector(server, newSslContextFactory(), null, new ALPNServerConnectionFactory("spdy/3", "spdy/2", "http/1.1"));
+        connector.setPort(0);
+        connector.setIdleTimeout(30000);
+        server.addConnector(connector);
+        server.start();
+
+        QueuedThreadPool threadPool = new QueuedThreadPool();
+        threadPool.setName(threadPool.getName() + "-client");
+        clientFactory = new SPDYClient.Factory(threadPool);
+        clientFactory.start();
+
+        ALPN.debug = true;
+
+        return new InetSocketAddress("localhost", connector.getLocalPort());
+    }
+
+    protected SslContextFactory newSslContextFactory()
+    {
+        SslContextFactory sslContextFactory = new SslContextFactory();
+        sslContextFactory.setKeyStorePath("src/test/resources/keystore.jks");
+        sslContextFactory.setKeyStorePassword("storepwd");
+        sslContextFactory.setTrustStorePath("src/test/resources/truststore.jks");
+        sslContextFactory.setTrustStorePassword("storepwd");
+        sslContextFactory.setProtocol("TLSv1");
+        sslContextFactory.setIncludeProtocols("TLSv1");
+        return sslContextFactory;
+    }
+
+    @After
+    public void dispose() throws Exception
+    {
+        clientFactory.stop();
+        server.stop();
+    }
+}
diff --git a/jetty-spdy/spdy-alpn-tests/src/test/resources/jetty-logging.properties b/jetty-spdy/spdy-alpn-tests/src/test/resources/jetty-logging.properties
new file mode 100644
index 0000000..ead13ec
--- /dev/null
+++ b/jetty-spdy/spdy-alpn-tests/src/test/resources/jetty-logging.properties
@@ -0,0 +1,2 @@
+org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
+#org.eclipse.jetty.spdy.LEVEL=DEBUG
diff --git a/jetty-spdy/spdy-jetty-http-webapp/src/main/resources/keystore.jks b/jetty-spdy/spdy-alpn-tests/src/test/resources/keystore.jks
similarity index 100%
copy from jetty-spdy/spdy-jetty-http-webapp/src/main/resources/keystore.jks
copy to jetty-spdy/spdy-alpn-tests/src/test/resources/keystore.jks
diff --git a/jetty-spdy/spdy-jetty-http-webapp/src/main/resources/truststore.jks b/jetty-spdy/spdy-alpn-tests/src/test/resources/truststore.jks
similarity index 100%
copy from jetty-spdy/spdy-jetty-http-webapp/src/main/resources/truststore.jks
copy to jetty-spdy/spdy-alpn-tests/src/test/resources/truststore.jks
diff --git a/jetty-spdy/spdy-client/pom.xml b/jetty-spdy/spdy-client/pom.xml
new file mode 100644
index 0000000..2158667
--- /dev/null
+++ b/jetty-spdy/spdy-client/pom.xml
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>org.eclipse.jetty.spdy</groupId>
+        <artifactId>spdy-parent</artifactId>
+        <version>9.2.13.v20150730</version>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>spdy-client</artifactId>
+    <name>Jetty :: SPDY :: Client Binding</name>
+
+    <properties>
+        <bundle-symbolic-name>${project.groupId}.client</bundle-symbolic-name>
+    </properties>
+
+    <url>http://www.eclipse.org/jetty</url>
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>manifest</goal>
+                        </goals>
+                        <configuration>
+                            <instructions>
+                                <Export-Package>org.eclipse.jetty.spdy.client;version="9.1"</Export-Package>
+                                <Import-Package>!org.eclipse.jetty.npn,!org.eclipse.jetty.alpn,org.eclipse.jetty.*;version="[9.0,10.0)",*</Import-Package>
+                            </instructions>
+                          </configuration>
+                       </execution>
+                  </executions>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.eclipse.jetty.spdy</groupId>
+            <artifactId>spdy-core</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-alpn-client</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty.alpn</groupId>
+            <artifactId>alpn-api</artifactId>
+            <version>${alpn.api.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty.npn</groupId>
+            <artifactId>npn-api</artifactId>
+            <version>${npn.api.version}</version>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+
+</project>
diff --git a/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/FlowControlStrategyFactory.java b/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/FlowControlStrategyFactory.java
new file mode 100644
index 0000000..b5fbb5c
--- /dev/null
+++ b/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/FlowControlStrategyFactory.java
@@ -0,0 +1,43 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.spdy.client;
+
+import org.eclipse.jetty.spdy.FlowControlStrategy;
+import org.eclipse.jetty.spdy.SPDYv3FlowControlStrategy;
+import org.eclipse.jetty.spdy.api.SPDY;
+
+public class FlowControlStrategyFactory
+{
+    private FlowControlStrategyFactory()
+    {
+    }
+
+    public static FlowControlStrategy newFlowControlStrategy(short version)
+    {
+        switch (version)
+        {
+            case SPDY.V2:
+                return new FlowControlStrategy.None();
+            case SPDY.V3:
+                return new SPDYv3FlowControlStrategy();
+            default:
+                throw new IllegalStateException();
+        }
+    }
+}
diff --git a/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/NPNClientConnection.java b/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/NPNClientConnection.java
new file mode 100644
index 0000000..c742248
--- /dev/null
+++ b/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/NPNClientConnection.java
@@ -0,0 +1,82 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.spdy.client;
+
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Executor;
+import javax.net.ssl.SSLEngine;
+
+import org.eclipse.jetty.io.ClientConnectionFactory;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.NegotiatingClientConnection;
+import org.eclipse.jetty.npn.NextProtoNego;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public class NPNClientConnection extends NegotiatingClientConnection implements NextProtoNego.ClientProvider
+{
+    private static final Logger LOG = Log.getLogger(NPNClientConnection.class);
+
+    private final String protocol;
+
+    public NPNClientConnection(EndPoint endPoint, Executor executor, ClientConnectionFactory connectionFactory, SSLEngine sslEngine, Map<String, Object> context, String protocol)
+    {
+        super(endPoint, executor, sslEngine, connectionFactory, context);
+        this.protocol = protocol;
+        NextProtoNego.put(sslEngine, this);
+    }
+
+    @Override
+    public boolean supports()
+    {
+        return true;
+    }
+
+    @Override
+    public void unsupported()
+    {
+        NextProtoNego.remove(getSSLEngine());
+        completed();
+    }
+
+    @Override
+    public String selectProtocol(List<String> protocols)
+    {
+        if (protocols.contains(protocol))
+        {
+            NextProtoNego.remove(getSSLEngine());
+            completed();
+            return protocol;
+        }
+        else
+        {
+            LOG.info("Could not negotiate protocol: server {} - client {}", protocols, protocol);
+            close();
+            return null;
+        }
+    }
+
+    @Override
+    public void close()
+    {
+        NextProtoNego.remove(getSSLEngine());
+        super.close();
+    }
+}
diff --git a/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/NPNClientConnectionFactory.java b/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/NPNClientConnectionFactory.java
new file mode 100644
index 0000000..208002e
--- /dev/null
+++ b/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/NPNClientConnectionFactory.java
@@ -0,0 +1,50 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.spdy.client;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.concurrent.Executor;
+import javax.net.ssl.SSLEngine;
+
+import org.eclipse.jetty.io.ClientConnectionFactory;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.NegotiatingClientConnectionFactory;
+import org.eclipse.jetty.io.ssl.SslClientConnectionFactory;
+
+public class NPNClientConnectionFactory extends NegotiatingClientConnectionFactory
+{
+    private final Executor executor;
+    private final String protocol;
+
+    public NPNClientConnectionFactory(Executor executor, ClientConnectionFactory connectionFactory, String protocol)
+    {
+        super(connectionFactory);
+        this.executor = executor;
+        this.protocol = protocol;
+    }
+
+    @Override
+    public Connection newConnection(EndPoint endPoint, Map<String, Object> context) throws IOException
+    {
+        return new NPNClientConnection(endPoint, executor, getClientConnectionFactory(),
+                (SSLEngine)context.get(SslClientConnectionFactory.SSL_ENGINE_CONTEXT_KEY), context, protocol);
+    }
+}
diff --git a/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/SPDYClient.java b/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/SPDYClient.java
new file mode 100644
index 0000000..aa21105
--- /dev/null
+++ b/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/SPDYClient.java
@@ -0,0 +1,440 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.spdy.client;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.SocketChannel;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.ClientConnectionFactory;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.MappedByteBufferPool;
+import org.eclipse.jetty.io.NegotiatingClientConnectionFactory;
+import org.eclipse.jetty.io.SelectChannelEndPoint;
+import org.eclipse.jetty.io.SelectorManager;
+import org.eclipse.jetty.io.ssl.SslClientConnectionFactory;
+import org.eclipse.jetty.spdy.FlowControlStrategy;
+import org.eclipse.jetty.spdy.api.GoAwayInfo;
+import org.eclipse.jetty.spdy.api.Session;
+import org.eclipse.jetty.spdy.api.SessionFrameListener;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.FuturePromise;
+import org.eclipse.jetty.util.Promise;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+/**
+ * A {@link SPDYClient} allows applications to connect to one or more SPDY servers,
+ * obtaining {@link Session} objects that can be used to send/receive SPDY frames.
+ * <p/>
+ * {@link SPDYClient} instances are created through a {@link Factory}:
+ * <pre>
+ * SPDYClient.Factory factory = new SPDYClient.Factory();
+ * SPDYClient client = factory.newSPDYClient(SPDY.V3);
+ * </pre>
+ * and then used to connect to the server:
+ * <pre>
+ * FuturePromise<Session> promise = new FuturePromise<>();
+ * client.connect("server.com", null, promise);
+ * Session session = promise.get();
+ * </pre>
+ */
+public class SPDYClient
+{
+    private final short version;
+    private final Factory factory;
+    private volatile SocketAddress bindAddress;
+    private volatile long idleTimeout = -1;
+    private volatile int initialWindowSize;
+    private volatile boolean dispatchIO;
+    private volatile ClientConnectionFactory connectionFactory;
+
+    protected SPDYClient(short version, Factory factory)
+    {
+        this.version = version;
+        this.factory = factory;
+        setInitialWindowSize(65536);
+        setDispatchIO(true);
+    }
+
+    public short getVersion()
+    {
+        return version;
+    }
+
+    public Factory getFactory()
+    {
+        return factory;
+    }
+
+    /**
+     * Equivalent to:
+     * <pre>
+     * Future<Session> promise = new FuturePromise<>();
+     * connect(address, listener, promise);
+     * </pre>
+     *
+     * @param address  the address to connect to
+     * @param listener the session listener that will be notified of session events
+     * @return a {@link Session} when connected
+     */
+    public Session connect(SocketAddress address, SessionFrameListener listener) throws ExecutionException, InterruptedException
+    {
+        FuturePromise<Session> promise = new FuturePromise<>();
+        connect(address, listener, promise);
+        return promise.get();
+    }
+
+    /**
+     * Equivalent to:
+     * <pre>
+     * connect(address, listener, promise, null);
+     * </pre>
+     *
+     * @param address  the address to connect to
+     * @param listener the session listener that will be notified of session events
+     * @param promise  the promise notified of connection success/failure
+     */
+    public void connect(SocketAddress address, SessionFrameListener listener, Promise<Session> promise)
+    {
+        connect(address, listener, promise, new HashMap<String, Object>());
+    }
+
+    /**
+     * Connects to the given {@code address}, binding the given {@code listener} to session events,
+     * and notified the given {@code promise} of the connect result.
+     * <p/>
+     * If the connect operation is successful, the {@code promise} will be invoked with the {@link Session}
+     * object that applications can use to perform SPDY requests.
+     *
+     * @param address  the address to connect to
+     * @param listener the session listener that will be notified of session events
+     * @param promise  the promise notified of connection success/failure
+     * @param context  a context object passed to the {@link #getClientConnectionFactory() ConnectionFactory}
+     *                 for the creation of the connection
+     */
+    public void connect(final SocketAddress address, final SessionFrameListener listener, final Promise<Session> promise, Map<String, Object> context)
+    {
+        if (!factory.isStarted())
+            throw new IllegalStateException(Factory.class.getSimpleName() + " is not started");
+
+        try
+        {
+            SocketChannel channel = SocketChannel.open();
+            if (bindAddress != null)
+                channel.bind(bindAddress);
+            configure(channel);
+            channel.configureBlocking(false);
+
+            context.put(SslClientConnectionFactory.SSL_PEER_HOST_CONTEXT_KEY, ((InetSocketAddress)address).getHostString());
+            context.put(SslClientConnectionFactory.SSL_PEER_PORT_CONTEXT_KEY, ((InetSocketAddress)address).getPort());
+            context.put(SPDYClientConnectionFactory.SPDY_CLIENT_CONTEXT_KEY, this);
+            context.put(SPDYClientConnectionFactory.SPDY_SESSION_LISTENER_CONTEXT_KEY, listener);
+            context.put(SPDYClientConnectionFactory.SPDY_SESSION_PROMISE_CONTEXT_KEY, promise);
+
+            if (channel.connect(address))
+                factory.selector.accept(channel, context);
+            else
+                factory.selector.connect(channel, context);
+        }
+        catch (IOException x)
+        {
+            promise.failed(x);
+        }
+    }
+
+    protected void configure(SocketChannel channel) throws IOException
+    {
+        channel.socket().setTcpNoDelay(true);
+    }
+
+    /**
+     * @return the address to bind the socket channel to
+     * @see #setBindAddress(SocketAddress)
+     */
+    public SocketAddress getBindAddress()
+    {
+        return bindAddress;
+    }
+
+    /**
+     * @param bindAddress the address to bind the socket channel to
+     * @see #getBindAddress()
+     */
+    public void setBindAddress(SocketAddress bindAddress)
+    {
+        this.bindAddress = bindAddress;
+    }
+
+    public long getIdleTimeout()
+    {
+        return idleTimeout;
+    }
+
+    public void setIdleTimeout(long idleTimeout)
+    {
+        this.idleTimeout = idleTimeout;
+    }
+
+    public int getInitialWindowSize()
+    {
+        return initialWindowSize;
+    }
+
+    public void setInitialWindowSize(int initialWindowSize)
+    {
+        this.initialWindowSize = initialWindowSize;
+    }
+
+    public boolean isDispatchIO()
+    {
+        return dispatchIO;
+    }
+
+    public void setDispatchIO(boolean dispatchIO)
+    {
+        this.dispatchIO = dispatchIO;
+    }
+
+    public ClientConnectionFactory getClientConnectionFactory()
+    {
+        return connectionFactory;
+    }
+
+    public void setClientConnectionFactory(ClientConnectionFactory connectionFactory)
+    {
+        this.connectionFactory = connectionFactory;
+    }
+
+    protected FlowControlStrategy newFlowControlStrategy()
+    {
+        return FlowControlStrategyFactory.newFlowControlStrategy(version);
+    }
+
+    public static class Factory extends ContainerLifeCycle
+    {
+        private final Queue<Session> sessions = new ConcurrentLinkedQueue<>();
+        private final Scheduler scheduler;
+        private final Executor executor;
+        private final ByteBufferPool bufferPool;
+        private final SslContextFactory sslContextFactory;
+        private final SelectorManager selector;
+        private final long idleTimeout;
+        private long connectTimeout;
+
+        public Factory()
+        {
+            this(null, null);
+        }
+
+        public Factory(SslContextFactory sslContextFactory)
+        {
+            this(null, null, sslContextFactory);
+        }
+
+        public Factory(Executor executor)
+        {
+            this(executor, null);
+        }
+
+        public Factory(Executor executor, Scheduler scheduler)
+        {
+            this(executor, scheduler, null);
+        }
+
+        public Factory(Executor executor, Scheduler scheduler, SslContextFactory sslContextFactory)
+        {
+            this(executor, scheduler, sslContextFactory, 30000);
+        }
+
+        public Factory(Executor executor, Scheduler scheduler, SslContextFactory sslContextFactory, long idleTimeout)
+        {
+            this(executor, scheduler, null, sslContextFactory, idleTimeout);
+        }
+
+        public Factory(Executor executor, Scheduler scheduler, ByteBufferPool bufferPool, SslContextFactory sslContextFactory, long idleTimeout)
+        {
+            this.idleTimeout = idleTimeout;
+            setConnectTimeout(15000);
+
+            if (executor == null)
+                executor = new QueuedThreadPool();
+            this.executor = executor;
+            addBean(executor);
+
+            if (scheduler == null)
+                scheduler = new ScheduledExecutorScheduler();
+            this.scheduler = scheduler;
+            addBean(scheduler);
+
+            if (bufferPool == null)
+                bufferPool = new MappedByteBufferPool();
+            this.bufferPool = bufferPool;
+            addBean(bufferPool);
+
+            this.sslContextFactory = sslContextFactory;
+            if (sslContextFactory != null)
+                addBean(sslContextFactory);
+
+            selector = new ClientSelectorManager(executor, scheduler);
+            selector.setConnectTimeout(getConnectTimeout());
+            addBean(selector);
+        }
+
+        public ByteBufferPool getByteBufferPool()
+        {
+            return bufferPool;
+        }
+
+        public Scheduler getScheduler()
+        {
+            return scheduler;
+        }
+
+        public Executor getExecutor()
+        {
+            return executor;
+        }
+
+        public SslContextFactory getSslContextFactory()
+        {
+            return sslContextFactory;
+        }
+
+        public long getConnectTimeout()
+        {
+            return connectTimeout;
+        }
+
+        public void setConnectTimeout(long connectTimeout)
+        {
+            this.connectTimeout = connectTimeout;
+        }
+
+        public SPDYClient newSPDYClient(short version)
+        {
+            return newSPDYClient(version, new NPNClientConnectionFactory(getExecutor(), new SPDYClientConnectionFactory(), "spdy/" + version));
+        }
+
+        public SPDYClient newSPDYClient(short version, NegotiatingClientConnectionFactory negotiatingFactory)
+        {
+            SPDYClient client = new SPDYClient(version, this);
+
+            ClientConnectionFactory connectionFactory = negotiatingFactory.getClientConnectionFactory();
+            if (sslContextFactory != null)
+                connectionFactory = new SslClientConnectionFactory(getSslContextFactory(), getByteBufferPool(), getExecutor(), negotiatingFactory);
+
+            client.setClientConnectionFactory(connectionFactory);
+            return client;
+        }
+
+        @Override
+        protected void doStop() throws Exception
+        {
+            closeConnections();
+            super.doStop();
+        }
+
+        boolean sessionOpened(Session session)
+        {
+            // Add sessions only if the factory is not stopping
+            return isRunning() && sessions.offer(session);
+        }
+
+        boolean sessionClosed(Session session)
+        {
+            // Remove sessions only if the factory is not stopping
+            // to avoid concurrent removes during iterations
+            return isRunning() && sessions.remove(session);
+        }
+
+        private void closeConnections()
+        {
+            for (Session session : sessions)
+                session.goAway(new GoAwayInfo(), Callback.Adapter.INSTANCE);
+            sessions.clear();
+        }
+
+        public Collection<Session> getSessions()
+        {
+            return Collections.unmodifiableCollection(sessions);
+        }
+
+        @Override
+        protected void dumpThis(Appendable out) throws IOException
+        {
+            super.dumpThis(out);
+            dump(out, "", sessions);
+        }
+
+        private class ClientSelectorManager extends SelectorManager
+        {
+            private ClientSelectorManager(Executor executor, Scheduler scheduler)
+            {
+                super(executor, scheduler);
+            }
+
+            @Override
+            protected EndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey key) throws IOException
+            {
+                @SuppressWarnings("unchecked")
+                Map<String, Object> context = (Map<String, Object>)key.attachment();
+                SPDYClient client = (SPDYClient)context.get(SPDYClientConnectionFactory.SPDY_CLIENT_CONTEXT_KEY);
+                long clientIdleTimeout = client.getIdleTimeout();
+                if (clientIdleTimeout < 0)
+                    clientIdleTimeout = idleTimeout;
+                return new SelectChannelEndPoint(channel, selectSet, key, getScheduler(), clientIdleTimeout);
+            }
+
+            @Override
+            public Connection newConnection(SocketChannel channel, EndPoint endPoint, Object attachment) throws IOException
+            {
+                @SuppressWarnings("unchecked")
+                Map<String, Object> context = (Map<String, Object>)attachment;
+                try
+                {
+                    SPDYClient client = (SPDYClient)context.get(SPDYClientConnectionFactory.SPDY_CLIENT_CONTEXT_KEY);
+                    return client.getClientConnectionFactory().newConnection(endPoint, context);
+                }
+                catch (Throwable x)
+                {
+                    @SuppressWarnings("unchecked")
+                    Promise<Session> promise = (Promise<Session>)context.get(SPDYClientConnectionFactory.SPDY_SESSION_PROMISE_CONTEXT_KEY);
+                    promise.failed(x);
+                    throw x;
+                }
+            }
+        }
+    }
+}
diff --git a/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/SPDYClientConnectionFactory.java b/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/SPDYClientConnectionFactory.java
new file mode 100644
index 0000000..bf43362
--- /dev/null
+++ b/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/SPDYClientConnectionFactory.java
@@ -0,0 +1,98 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.spdy.client;
+
+import java.io.IOException;
+import java.util.Map;
+
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.ClientConnectionFactory;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.spdy.CompressionFactory;
+import org.eclipse.jetty.spdy.FlowControlStrategy;
+import org.eclipse.jetty.spdy.StandardCompressionFactory;
+import org.eclipse.jetty.spdy.StandardSession;
+import org.eclipse.jetty.spdy.api.Session;
+import org.eclipse.jetty.spdy.api.SessionFrameListener;
+import org.eclipse.jetty.spdy.client.SPDYClient.Factory;
+import org.eclipse.jetty.spdy.generator.Generator;
+import org.eclipse.jetty.spdy.parser.Parser;
+import org.eclipse.jetty.util.Promise;
+
+public class SPDYClientConnectionFactory implements ClientConnectionFactory
+{
+    public static final String SPDY_CLIENT_CONTEXT_KEY = "spdy.client";
+    public static final String SPDY_SESSION_LISTENER_CONTEXT_KEY = "spdy.session.listener";
+    public static final String SPDY_SESSION_PROMISE_CONTEXT_KEY = "spdy.session.promise";
+
+    @Override
+    public Connection newConnection(EndPoint endPoint, Map<String, Object> context) throws IOException
+    {
+        SPDYClient client = (SPDYClient)context.get(SPDY_CLIENT_CONTEXT_KEY);
+        SPDYClient.Factory factory = client.getFactory();
+        ByteBufferPool byteBufferPool = factory.getByteBufferPool();
+        CompressionFactory compressionFactory = new StandardCompressionFactory();
+        Parser parser = new Parser(compressionFactory.newDecompressor());
+        Generator generator = new Generator(byteBufferPool, compressionFactory.newCompressor());
+
+        SPDYConnection connection = new ClientSPDYConnection(endPoint, byteBufferPool, parser, factory, client.isDispatchIO());
+
+        FlowControlStrategy flowControlStrategy = client.newFlowControlStrategy();
+
+        SessionFrameListener listener = (SessionFrameListener)context.get(SPDY_SESSION_LISTENER_CONTEXT_KEY);
+        StandardSession session = new StandardSession(client.getVersion(), byteBufferPool,
+                factory.getScheduler(), connection, endPoint, connection, 1, listener, generator, flowControlStrategy);
+
+        session.setWindowSize(client.getInitialWindowSize());
+        parser.addListener(session);
+        connection.setSession(session);
+
+        @SuppressWarnings("unchecked")
+        Promise<Session> promise = (Promise<Session>)context.get(SPDY_SESSION_PROMISE_CONTEXT_KEY);
+        promise.succeeded(session);
+
+        return connection;
+    }
+
+    private class ClientSPDYConnection extends SPDYConnection
+    {
+        private final Factory factory;
+
+        public ClientSPDYConnection(EndPoint endPoint, ByteBufferPool bufferPool, Parser parser, Factory factory, boolean dispatchIO)
+        {
+            super(endPoint, bufferPool, parser, factory.getExecutor(), dispatchIO);
+            this.factory = factory;
+        }
+
+        @Override
+        public void onOpen()
+        {
+            super.onOpen();
+            factory.sessionOpened(getSession());
+        }
+
+        @Override
+        public void onClose()
+        {
+            super.onClose();
+            factory.sessionClosed(getSession());
+        }
+    }
+}
diff --git a/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/SPDYConnection.java b/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/SPDYConnection.java
new file mode 100644
index 0000000..18264f2
--- /dev/null
+++ b/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/SPDYConnection.java
@@ -0,0 +1,191 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.spdy.client;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.concurrent.Executor;
+
+import org.eclipse.jetty.io.AbstractConnection;
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.RuntimeIOException;
+import org.eclipse.jetty.spdy.Controller;
+import org.eclipse.jetty.spdy.ISession;
+import org.eclipse.jetty.spdy.IdleListener;
+import org.eclipse.jetty.spdy.api.GoAwayInfo;
+import org.eclipse.jetty.spdy.parser.Parser;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public class SPDYConnection extends AbstractConnection implements Controller, IdleListener
+{
+    private static final Logger LOG = Log.getLogger(SPDYConnection.class);
+    private final ByteBufferPool bufferPool;
+    private final Parser parser;
+    private final int bufferSize;
+    private volatile ISession session;
+    private volatile boolean idle = false;
+
+    public SPDYConnection(EndPoint endPoint, ByteBufferPool bufferPool, Parser parser, Executor executor, boolean dispatchIO)
+    {
+        this(endPoint, bufferPool, parser, executor, dispatchIO, 8192);
+    }
+
+    public SPDYConnection(EndPoint endPoint, ByteBufferPool bufferPool, Parser parser, Executor executor, boolean dispatchIO, int bufferSize)
+    {
+        // Since SPDY is multiplexed, onFillable() must never block while calling application code. In fact,
+        // the SPDY code always dispatches to a new thread when calling application code,
+        // so here we can safely pass false as last parameter, and avoid to dispatch to onFillable().
+        // The IO operation (read, parse, etc.) will not block and will be fast in almost all cases.
+        // Big uploads to a server, however, might occupy the Selector thread for a long time and
+        // therefore starve other connections, so by default dispatchIO is true.
+        super(endPoint, executor, dispatchIO);
+        this.bufferPool = bufferPool;
+        this.parser = parser;
+        onIdle(true);
+        this.bufferSize = bufferSize;
+    }
+
+    @Override
+    public void onOpen()
+    {
+        super.onOpen();
+        fillInterested();
+    }
+
+    @Override
+    public void onFillable()
+    {
+        ByteBuffer buffer = bufferPool.acquire(bufferSize, false);
+        boolean readMore = read(buffer) == 0;
+        bufferPool.release(buffer);
+        if (readMore)
+            fillInterested();
+    }
+
+    protected int read(ByteBuffer buffer)
+    {
+        EndPoint endPoint = getEndPoint();
+        while (true)
+        {
+            int filled = fill(endPoint, buffer);
+            if (LOG.isDebugEnabled()) // Avoid boxing of variable 'filled'
+                LOG.debug("Read {} bytes", filled);
+            if (filled == 0)
+            {
+                return 0;
+            }
+            else if (filled < 0)
+            {
+                shutdown(session);
+                return -1;
+            }
+            else
+            {
+                parser.parse(buffer);
+            }
+        }
+    }
+
+    private int fill(EndPoint endPoint, ByteBuffer buffer)
+    {
+        try
+        {
+            if (endPoint.isInputShutdown())
+                return -1;
+            return endPoint.fill(buffer);
+        }
+        catch (IOException x)
+        {
+            endPoint.close();
+            throw new RuntimeIOException(x);
+        }
+    }
+
+    @Override
+    public void write(final Callback callback, ByteBuffer... buffers)
+    {
+        EndPoint endPoint = getEndPoint();
+        endPoint.write(callback, buffers);
+    }
+
+    @Override
+    public void close()
+    {
+        goAway(session);
+    }
+
+    @Override
+    public void close(boolean onlyOutput)
+    {
+        EndPoint endPoint = getEndPoint();
+        // We need to gently close first, to allow
+        // SSL close alerts to be sent by Jetty
+        if (LOG.isDebugEnabled())
+            LOG.debug("Shutting down output {}", endPoint);
+        endPoint.shutdownOutput();
+        if (!onlyOutput)
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("Closing {}", endPoint);
+            endPoint.close();
+        }
+    }
+
+    @Override
+    public void onIdle(boolean idle)
+    {
+        this.idle = idle;
+    }
+
+    @Override
+    protected boolean onReadTimeout()
+    {
+        boolean idle = this.idle;
+        if (LOG.isDebugEnabled())
+            LOG.debug("Idle timeout on {}, idle={}", this, idle);
+        if (idle)
+            goAway(session);
+        return false;
+    }
+
+    protected void goAway(ISession session)
+    {
+        if (session != null)
+            session.goAway(new GoAwayInfo(), Callback.Adapter.INSTANCE);
+    }
+
+    private void shutdown(ISession session)
+    {
+        if (session != null && !getEndPoint().isOutputShutdown())
+            session.shutdown();
+    }
+
+    protected ISession getSession()
+    {
+        return session;
+    }
+
+    public void setSession(ISession session)
+    {
+        this.session = session;
+    }
+}
diff --git a/jetty-spdy/spdy-core/pom.xml b/jetty-spdy/spdy-core/pom.xml
index bc2a280..ec6a7e3 100644
--- a/jetty-spdy/spdy-core/pom.xml
+++ b/jetty-spdy/spdy-core/pom.xml
@@ -3,13 +3,17 @@
     <parent>
         <groupId>org.eclipse.jetty.spdy</groupId>
         <artifactId>spdy-parent</artifactId>
-        <version>8.1.17.v20150415</version>
+        <version>9.2.13.v20150730</version>
     </parent>
 
-  <modelVersion>4.0.0</modelVersion>
-  <artifactId>spdy-core</artifactId>
-  <name>Jetty :: SPDY :: Core</name>
-  <url>http://www.eclipse.org/jetty</url>
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>spdy-core</artifactId>
+    <name>Jetty :: SPDY :: Core</name>
+
+    <properties>
+        <bundle-symbolic-name>${project.groupId}.core</bundle-symbolic-name>
+    </properties>
+
     <dependencies>
         <dependency>
             <groupId>org.eclipse.jetty</groupId>
@@ -17,24 +21,13 @@
             <version>${project.version}</version>
         </dependency>
         <dependency>
-          <groupId>junit</groupId>
-          <artifactId>junit</artifactId>
-          <scope>test</scope>
-        </dependency>
-        <dependency>
-          <groupId>org.hamcrest</groupId>
-          <artifactId>hamcrest-library</artifactId>
-          <scope>test</scope>
-        </dependency>
-        <dependency>
-          <groupId>org.mockito</groupId>
-          <artifactId>mockito-core</artifactId>
-          <scope>test</scope>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-io</artifactId>
+            <version>${project.version}</version>
         </dependency>
         <dependency>
-            <groupId>org.slf4j</groupId>
-            <artifactId>slf4j-log4j12</artifactId>
-            <version>${slf4j-version}</version>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-core</artifactId>
             <scope>test</scope>
         </dependency>
     </dependencies>
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/ByteBufferPool.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/ByteBufferPool.java
deleted file mode 100644
index 9a67b14..0000000
--- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/ByteBufferPool.java
+++ /dev/null
@@ -1,51 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.spdy;
-
-import java.nio.ByteBuffer;
-
-/**
- * <p>A {@link ByteBuffer} pool.</p>
- * <p>Acquired buffers may be {@link #release(ByteBuffer) released} but they do not need to;
- * if they are released, they may be recycled and reused, otherwise they will be garbage
- * collected as usual.</p>
- */
-public interface ByteBufferPool
-{
-    /**
-     * <p>Requests a {@link ByteBuffer} of the given size.</p>
-     * <p>The returned buffer may have a bigger capacity than the size being
-     * requested but it will have the limit set to the given size.</p>
-     *
-     * @param size   the size of the buffer
-     * @param direct whether the buffer must be direct or not
-     * @return the requested buffer
-     * @see #release(ByteBuffer)
-     */
-    public ByteBuffer acquire(int size, boolean direct);
-
-    /**
-     * <p>Returns a {@link ByteBuffer}, usually obtained with {@link #acquire(int, boolean)}
-     * (but not necessarily), making it available for recycling and reuse.</p>
-     *
-     * @param buffer the buffer to return
-     * @see #acquire(int, boolean)
-     */
-    public void release(ByteBuffer buffer);
-}
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/Controller.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/Controller.java
index 6da54e1..32a5b8d 100644
--- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/Controller.java
+++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/Controller.java
@@ -20,11 +20,11 @@ package org.eclipse.jetty.spdy;
 
 import java.nio.ByteBuffer;
 
-import org.eclipse.jetty.spdy.api.Handler;
+import org.eclipse.jetty.util.Callback;
 
-public interface Controller<T>
+public interface Controller
 {
-    public int write(ByteBuffer buffer, Handler<T> handler, T context);
+    public void write(Callback callback, ByteBuffer... buffers);
 
     public void close(boolean onlyOutput);
 }
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/Flusher.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/Flusher.java
new file mode 100644
index 0000000..f18f9d6
--- /dev/null
+++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/Flusher.java
@@ -0,0 +1,266 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.spdy;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.eclipse.jetty.spdy.StandardSession.FrameBytes;
+import org.eclipse.jetty.spdy.api.Stream;
+import org.eclipse.jetty.spdy.api.StreamStatus;
+import org.eclipse.jetty.util.ArrayQueue;
+import org.eclipse.jetty.util.IteratingCallback;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public class Flusher
+{
+    private static final Logger LOG = Log.getLogger(Flusher.class);
+
+    private final IteratingCallback callback = new FlusherCallback();
+    private final Object lock = new Object();
+    private final ArrayQueue<StandardSession.FrameBytes> queue = new ArrayQueue<>(ArrayQueue.DEFAULT_CAPACITY, ArrayQueue.DEFAULT_GROWTH, lock);
+    private final Controller controller;
+    private final int maxGather;
+    private Throwable failure;
+
+    public Flusher(Controller controller)
+    {
+        this(controller, 8);
+    }
+
+    public Flusher(Controller controller, int maxGather)
+    {
+        this.controller = controller;
+        this.maxGather = maxGather;
+    }
+
+    public void removeFrameBytesFromQueue(Stream stream)
+    {
+        synchronized (lock)
+        {
+            for (StandardSession.FrameBytes frameBytes : queue)
+                if (frameBytes.getStream() == stream)
+                    queue.remove(frameBytes);
+        }
+    }
+
+    public Throwable prepend(StandardSession.FrameBytes frameBytes)
+    {
+        synchronized (lock)
+        {
+            Throwable failure = this.failure;
+            if (failure == null)
+            {
+                // Scan from the front of the queue looking to skip higher priority messages
+                int index = 0;
+                int size = queue.size();
+                while (index < size)
+                {
+                    StandardSession.FrameBytes element = queue.getUnsafe(index);
+                    if (element.compareTo(frameBytes) <= 0)
+                        break;
+                    ++index;
+                }
+                queue.add(index, frameBytes);
+            }
+            return failure;
+        }
+    }
+
+    public Throwable append(StandardSession.FrameBytes frameBytes)
+    {
+        synchronized (lock)
+        {
+            Throwable failure = this.failure;
+            if (failure == null)
+            {
+                // Non DataFrameBytes are inserted last
+                queue.add(frameBytes);
+            }
+            return failure;
+        }
+    }
+
+    public Throwable append(StandardSession.DataFrameBytes frameBytes)
+    {
+        synchronized (lock)
+        {
+            Throwable failure = this.failure;
+            if (failure == null)
+            {
+                // DataFrameBytes are inserted by priority
+                int index = queue.size();
+                while (index > 0)
+                {
+                    StandardSession.FrameBytes element = queue.getUnsafe(index - 1);
+                    if (element.compareTo(frameBytes) >= 0)
+                        break;
+                    --index;
+                }
+                queue.add(index, frameBytes);
+            }
+            return failure;
+        }
+    }
+
+    public void flush()
+    {
+        callback.iterate();
+    }
+
+    public int getQueueSize()
+    {
+        synchronized (lock)
+        {
+            return queue.size();
+        }
+    }
+
+    private class FlusherCallback extends IteratingCallback
+    {
+        private final List<StandardSession.FrameBytes> active = new ArrayList<>(maxGather);
+        private final List<StandardSession.FrameBytes> succeeded = new ArrayList<>(maxGather);
+        private final Set<IStream> stalled = new HashSet<>();
+
+        @Override
+        protected Action process() throws Exception
+        {
+            synchronized (lock)
+            {
+                // Scan queue for data to write from first non stalled stream.
+                int index = 0; // The index of the first non-stalled frame.
+                int size = queue.size();
+                while (index < size)
+                {
+                    FrameBytes frameBytes = queue.getUnsafe(index);
+                    IStream stream = frameBytes.getStream();
+
+                    if (stream != null)
+                    {
+                        // Is it a frame belonging to an already stalled stream ?
+                        if (stalled.size() > 0 && stalled.contains(stream))
+                        {
+                            ++index;
+                            continue;
+                        }
+
+                        // Has the stream consumed all its flow control window ?
+                        if (stream.getWindowSize() <= 0)
+                        {
+                            stalled.add(stream);
+                            ++index;
+                            continue;
+                        }
+                    }
+
+                    // We will be possibly writing this frame, so take the frame off the queue.
+                    queue.remove(index);
+                    --size;
+
+                    // Has the stream been reset for this data frame ?
+                    if (stream != null && stream.isReset() && frameBytes instanceof StandardSession.DataFrameBytes)
+                    {
+                        // TODO: notify from within sync block !
+                        frameBytes.failed(new StreamException(frameBytes.getStream().getId(),
+                                StreamStatus.INVALID_STREAM, "Stream: " + frameBytes.getStream() + " is reset!"));
+                        continue;
+                    }
+
+                    active.add(frameBytes);
+                }
+                stalled.clear();
+
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Flushing {} of {} frame(s) in queue", active.size(), queue.size());
+            }
+
+            if (active.isEmpty())
+                return Action.IDLE;
+
+            // Get the bytes to write
+            ByteBuffer[] buffers = new ByteBuffer[active.size()];
+            for (int i = 0; i < buffers.length; i++)
+                buffers[i] = active.get(i).getByteBuffer();
+
+            if (controller != null)
+                controller.write(this, buffers);
+
+            // TODO: optimization
+            // If the callback completely immediately, it means that
+            // the connection is not congested, and therefore we can
+            // write more data without blocking.
+            // Therefore we should check this condition and increase
+            // the write window, which means two things: autotune the
+            // maxGather parameter, and/or autotune the buffer returned
+            // by FrameBytes.getByteBuffer() (see also comment there).
+
+            return Action.SCHEDULED;
+        }
+
+        @Override
+        protected void onCompleteSuccess()
+        {
+            // will never be called as process always returns SCHEDULED or IDLE
+            throw new IllegalStateException();
+        }
+
+        @Override
+        public void succeeded()
+        {
+            synchronized (lock)
+            {
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Succeeded write of {}, q={}", active, queue.size());
+                succeeded.addAll(active);
+                active.clear();
+            }
+            // Notify outside the synchronized block.
+            for (FrameBytes frame : succeeded)
+                frame.succeeded();
+            succeeded.clear();
+            super.succeeded();
+        }
+
+        @Override
+        public void onCompleteFailure(Throwable x)
+        {
+            List<StandardSession.FrameBytes> failed = new ArrayList<>();
+            synchronized (lock)
+            {
+                failure = x;
+                if (LOG.isDebugEnabled())
+                {
+                    String logMessage = String.format("Failed write of %s, failing all %d frame(s) in queue", this, queue.size());
+                    LOG.debug(logMessage, x);
+                }
+                failed.addAll(active);
+                active.clear();
+                failed.addAll(queue);
+                queue.clear();
+            }
+            // Notify outside the synchronized block.
+            for (StandardSession.FrameBytes frame : failed)
+                frame.failed(x);
+        }
+    }
+}
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/ISession.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/ISession.java
index ed66333..d5f80cf 100644
--- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/ISession.java
+++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/ISession.java
@@ -21,20 +21,19 @@ package org.eclipse.jetty.spdy;
 import java.util.concurrent.TimeUnit;
 
 import org.eclipse.jetty.spdy.api.DataInfo;
-import org.eclipse.jetty.spdy.api.Handler;
 import org.eclipse.jetty.spdy.api.Session;
 import org.eclipse.jetty.spdy.frames.ControlFrame;
+import org.eclipse.jetty.util.Callback;
 
 public interface ISession extends Session
 {
-    /**
-     * <p>Initiates the flush of data to the other peer.</p>
-     * <p>Note that the flush may do nothing if, for example, there is nothing to flush, or
-     * if the data to be flushed belong to streams that have their flow-control stalled.</p>
-     */
-    public void flush();
+    public void control(IStream stream, ControlFrame frame, long timeout, TimeUnit unit, Callback callback);
 
-    public <C> void control(IStream stream, ControlFrame frame, long timeout, TimeUnit unit, Handler<C> handler, C context);
+    public void data(IStream stream, DataInfo dataInfo, long timeout, TimeUnit unit, Callback callback);
 
-    public <C> void data(IStream stream, DataInfo dataInfo, long timeout, TimeUnit unit, Handler<C> handler, C context);
+    /**
+     * <p>Gracefully shuts down this session.</p>
+     * <p>A special item is queued that will close the connection when it will be dequeued.</p>
+     */
+    public void shutdown();
 }
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/IStream.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/IStream.java
index 15a4105..8524ab2 100644
--- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/IStream.java
+++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/IStream.java
@@ -24,13 +24,14 @@ import org.eclipse.jetty.spdy.api.Stream;
 import org.eclipse.jetty.spdy.api.StreamFrameListener;
 import org.eclipse.jetty.spdy.api.SynInfo;
 import org.eclipse.jetty.spdy.frames.ControlFrame;
+import org.eclipse.jetty.util.Callback;
 
 /**
  * <p>The internal interface that represents a stream.</p>
  * <p>{@link IStream} contains additional methods used by a SPDY
  * implementation (and not by an application).</p>
  */
-public interface IStream extends Stream
+public interface IStream extends Stream, Callback
 {
     /**
      * <p>Senders of data frames need to know the current window size
@@ -59,6 +60,11 @@ public interface IStream extends Stream
     public void setStreamFrameListener(StreamFrameListener listener);
 
     /**
+     * @return the stream frame listener associated to this stream
+     */
+    public StreamFrameListener getStreamFrameListener();
+
+    /**
      * <p>A stream can be open, {@link #isHalfClosed() half closed} or
      * {@link #isClosed() closed} and this method updates the close state
      * of this stream.</p>
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/Promise.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/Promise.java
deleted file mode 100644
index 2ec18a2..0000000
--- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/Promise.java
+++ /dev/null
@@ -1,102 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.spdy;
-
-import java.util.concurrent.CancellationException;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
-import org.eclipse.jetty.spdy.api.Handler;
-
-/**
- * <p>A {@link Promise} is a {@link Future} that allows a result or a failure to be set,
- * so that the {@link Future} will be {@link #isDone() done}.</p>
- *
- * @param <T> the type of the result object
- */
-public class Promise<T> implements Handler<T>, Future<T>
-{
-    private final CountDownLatch latch = new CountDownLatch(1);
-    private boolean cancelled;
-    private Throwable failure;
-    private T promise;
-
-    @Override
-    public void completed(T result)
-    {
-        this.promise = result;
-        latch.countDown();
-    }
-
-    @Override
-    public void failed(T context, Throwable x)
-    {
-        this.failure = x;
-        latch.countDown();
-    }
-
-    @Override
-    public boolean cancel(boolean mayInterruptIfRunning)
-    {
-        cancelled = true;
-        latch.countDown();
-        return true;
-    }
-
-    @Override
-    public boolean isCancelled()
-    {
-        return cancelled;
-    }
-
-    @Override
-    public boolean isDone()
-    {
-        return cancelled || latch.getCount() == 0;
-    }
-
-    @Override
-    public T get() throws InterruptedException, ExecutionException
-    {
-        latch.await();
-        return result();
-    }
-
-    @Override
-    public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException
-    {
-        boolean elapsed = !latch.await(timeout, unit);
-        if (elapsed)
-            throw new TimeoutException();
-        return result();
-    }
-
-    private T result() throws ExecutionException
-    {
-        if (isCancelled())
-            throw new CancellationException();
-        Throwable failure = this.failure;
-        if (failure != null)
-            throw new ExecutionException(failure);
-        return promise;
-    }
-}
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/PushSynInfo.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/PushSynInfo.java
index a1b48d5..0755515 100644
--- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/PushSynInfo.java
+++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/PushSynInfo.java
@@ -18,6 +18,7 @@
 
 package org.eclipse.jetty.spdy;
 
+import org.eclipse.jetty.spdy.api.PushInfo;
 import org.eclipse.jetty.spdy.api.SynInfo;
 
 /* ------------------------------------------------------------ */
@@ -30,8 +31,8 @@ public class PushSynInfo extends SynInfo
     
     private int associatedStreamId;
     
-    public PushSynInfo(int associatedStreamId, SynInfo synInfo){
-        super(synInfo.getHeaders(), synInfo.isClose(), synInfo.getPriority());
+    public PushSynInfo(int associatedStreamId, PushInfo pushInfo){
+        super(pushInfo.getTimeout(), pushInfo.getUnit(), pushInfo.getHeaders(), pushInfo.isClose(), (byte)0);
         this.associatedStreamId = associatedStreamId;
     }
     
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/SPDYv3FlowControlStrategy.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/SPDYv3FlowControlStrategy.java
index fee5709..a6205eb 100644
--- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/SPDYv3FlowControlStrategy.java
+++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/SPDYv3FlowControlStrategy.java
@@ -23,6 +23,7 @@ import java.util.concurrent.TimeUnit;
 import org.eclipse.jetty.spdy.api.DataInfo;
 import org.eclipse.jetty.spdy.api.Stream;
 import org.eclipse.jetty.spdy.frames.WindowUpdateFrame;
+import org.eclipse.jetty.util.Callback;
 
 public class SPDYv3FlowControlStrategy implements FlowControlStrategy
 {
@@ -83,7 +84,7 @@ public class SPDYv3FlowControlStrategy implements FlowControlStrategy
         if (dataInfo.consumed() == length && !stream.isClosed() && length > 0)
         {
             WindowUpdateFrame windowUpdateFrame = new WindowUpdateFrame(session.getVersion(), stream.getId(), length);
-            session.control(stream, windowUpdateFrame, 0, TimeUnit.MILLISECONDS, null, null);
+            session.control(stream, windowUpdateFrame, 0, TimeUnit.MILLISECONDS, Callback.Adapter.INSTANCE);
         }
     }
 }
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/StandardByteBufferPool.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/StandardByteBufferPool.java
deleted file mode 100644
index 9c85943..0000000
--- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/StandardByteBufferPool.java
+++ /dev/null
@@ -1,102 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.spdy;
-
-import java.nio.ByteBuffer;
-import java.util.Queue;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.concurrent.ConcurrentMap;
-
-public class StandardByteBufferPool implements ByteBufferPool
-{
-    private final ConcurrentMap<Integer, Queue<ByteBuffer>> directBuffers = new ConcurrentHashMap<>();
-    private final ConcurrentMap<Integer, Queue<ByteBuffer>> heapBuffers = new ConcurrentHashMap<>();
-    private final int factor;
-
-    public StandardByteBufferPool()
-    {
-        this(1024);
-    }
-
-    public StandardByteBufferPool(int factor)
-    {
-        this.factor = factor;
-    }
-
-    public ByteBuffer acquire(int size, boolean direct)
-    {
-        int bucket = bucketFor(size);
-        ConcurrentMap<Integer, Queue<ByteBuffer>> buffers = buffersFor(direct);
-
-        ByteBuffer result = null;
-        Queue<ByteBuffer> byteBuffers = buffers.get(bucket);
-        if (byteBuffers != null)
-            result = byteBuffers.poll();
-
-        if (result == null)
-        {
-            int capacity = bucket * factor;
-            result = direct ? ByteBuffer.allocateDirect(capacity) : ByteBuffer.allocate(capacity);
-        }
-
-        result.clear();
-        result.limit(size);
-
-        return result;
-    }
-
-    public void release(ByteBuffer buffer)
-    {
-        int bucket = bucketFor(buffer.capacity());
-        ConcurrentMap<Integer, Queue<ByteBuffer>> buffers = buffersFor(buffer.isDirect());
-
-        // Avoid to create a new queue every time, just to be discarded immediately
-        Queue<ByteBuffer> byteBuffers = buffers.get(bucket);
-        if (byteBuffers == null)
-        {
-            byteBuffers = new ConcurrentLinkedQueue<>();
-            Queue<ByteBuffer> existing = buffers.putIfAbsent(bucket, byteBuffers);
-            if (existing != null)
-                byteBuffers = existing;
-        }
-
-        buffer.clear();
-        byteBuffers.offer(buffer);
-    }
-
-    public void clear()
-    {
-        directBuffers.clear();
-        heapBuffers.clear();
-    }
-
-    private int bucketFor(int size)
-    {
-        int bucket = size / factor;
-        if (size % factor > 0)
-            ++bucket;
-        return bucket;
-    }
-
-    private ConcurrentMap<Integer, Queue<ByteBuffer>> buffersFor(boolean direct)
-    {
-        return direct ? directBuffers : heapBuffers;
-    }
-}
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/StandardSession.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/StandardSession.java
index d9d57b1..0982271 100644
--- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/StandardSession.java
+++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/StandardSession.java
@@ -19,31 +19,33 @@
 package org.eclipse.jetty.spdy;
 
 import java.io.IOException;
+import java.net.InetSocketAddress;
 import java.nio.ByteBuffer;
 import java.nio.channels.InterruptedByTimeoutException;
-import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashSet;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Future;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
 
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.EndPoint;
 import org.eclipse.jetty.spdy.api.ByteBufferDataInfo;
 import org.eclipse.jetty.spdy.api.DataInfo;
 import org.eclipse.jetty.spdy.api.GoAwayInfo;
-import org.eclipse.jetty.spdy.api.Handler;
+import org.eclipse.jetty.spdy.api.GoAwayResultInfo;
 import org.eclipse.jetty.spdy.api.PingInfo;
+import org.eclipse.jetty.spdy.api.PingResultInfo;
+import org.eclipse.jetty.spdy.api.PushInfo;
 import org.eclipse.jetty.spdy.api.RstInfo;
 import org.eclipse.jetty.spdy.api.SPDYException;
 import org.eclipse.jetty.spdy.api.Session;
@@ -70,32 +72,30 @@ import org.eclipse.jetty.spdy.frames.WindowUpdateFrame;
 import org.eclipse.jetty.spdy.generator.Generator;
 import org.eclipse.jetty.spdy.parser.Parser;
 import org.eclipse.jetty.util.Atomics;
-import org.eclipse.jetty.util.component.AggregateLifeCycle;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.FutureCallback;
+import org.eclipse.jetty.util.FuturePromise;
+import org.eclipse.jetty.util.Promise;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
 import org.eclipse.jetty.util.component.Dumpable;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.Scheduler;
 
-public class StandardSession implements ISession, Parser.Listener, Handler<StandardSession.FrameBytes>, Dumpable
+public class StandardSession implements ISession, Parser.Listener, Dumpable
 {
-    private static final Logger logger = Log.getLogger(Session.class);
-    private static final ThreadLocal<Integer> handlerInvocations = new ThreadLocal<Integer>()
-    {
-        @Override
-        protected Integer initialValue()
-        {
-            return 0;
-        }
-    };
+    private static final Logger LOG = Log.getLogger(Session.class);
 
+    private final Flusher flusher;
     private final Map<String, Object> attributes = new ConcurrentHashMap<>();
     private final List<Listener> listeners = new CopyOnWriteArrayList<>();
     private final ConcurrentMap<Integer, IStream> streams = new ConcurrentHashMap<>();
-    private final LinkedList<FrameBytes> queue = new LinkedList<>();
     private final ByteBufferPool bufferPool;
-    private final Executor threadPool;
-    private final ScheduledExecutorService scheduler;
+    private final Scheduler scheduler;
     private final short version;
-    private final Controller<FrameBytes> controller;
+    private final Controller controller;
+    private final EndPoint endPoint;
     private final IdleListener idleListener;
     private final AtomicInteger streamIds;
     private final AtomicInteger pingIds;
@@ -104,25 +104,27 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
     private final AtomicBoolean goAwaySent = new AtomicBoolean();
     private final AtomicBoolean goAwayReceived = new AtomicBoolean();
     private final AtomicInteger lastStreamId = new AtomicInteger();
+    private final AtomicInteger localStreamCount = new AtomicInteger(0);
     private final FlowControlStrategy flowControlStrategy;
-    private boolean flushing;
-    private Throwable failure;
+    private volatile int maxConcurrentLocalStreams = -1;
 
-    public StandardSession(short version, ByteBufferPool bufferPool, Executor threadPool, ScheduledExecutorService scheduler,
-            Controller<FrameBytes> controller, IdleListener idleListener, int initialStreamId, SessionFrameListener listener,
-            Generator generator, FlowControlStrategy flowControlStrategy)
+    public StandardSession(short version, ByteBufferPool bufferPool, Scheduler scheduler,
+                           Controller controller, EndPoint endPoint, IdleListener idleListener, int initialStreamId,
+                           SessionFrameListener listener, Generator generator, FlowControlStrategy flowControlStrategy)
     {
+        // TODO this should probably be an aggregate lifecycle
         this.version = version;
         this.bufferPool = bufferPool;
-        this.threadPool = threadPool;
         this.scheduler = scheduler;
         this.controller = controller;
+        this.endPoint = endPoint;
         this.idleListener = idleListener;
         this.streamIds = new AtomicInteger(initialStreamId);
         this.pingIds = new AtomicInteger(initialStreamId);
         this.listener = listener;
         this.generator = generator;
         this.flowControlStrategy = flowControlStrategy;
+        this.flusher = new Flusher(controller);
     }
 
     @Override
@@ -144,15 +146,18 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
     }
 
     @Override
-    public Future<Stream> syn(SynInfo synInfo, StreamFrameListener listener)
+    public Stream syn(SynInfo synInfo, StreamFrameListener listener) throws ExecutionException, InterruptedException, TimeoutException
     {
-        Promise<Stream> result = new Promise<>();
-        syn(synInfo,listener,0,TimeUnit.MILLISECONDS,result);
-        return result;
+        FuturePromise<Stream> result = new FuturePromise<>();
+        syn(synInfo, listener, result);
+        if (synInfo.getTimeout() > 0)
+            return result.get(synInfo.getTimeout(), synInfo.getUnit());
+        else
+            return result.get();
     }
 
     @Override
-    public void syn(SynInfo synInfo, StreamFrameListener listener, long timeout, TimeUnit unit, Handler<Stream> handler)
+    public void syn(SynInfo synInfo, StreamFrameListener listener, Promise<Stream> promise)
     {
         // Synchronization is necessary.
         // SPEC v3, 2.3.1 requires that the stream creation be monotonically crescent
@@ -169,105 +174,119 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
             int streamId = streamIds.getAndAdd(2);
             // TODO: for SPDYv3 we need to support the "slot" argument
             SynStreamFrame synStream = new SynStreamFrame(version, synInfo.getFlags(), streamId, associatedStreamId, synInfo.getPriority(), (short)0, synInfo.getHeaders());
-            IStream stream = createStream(synStream, listener, true);
-            generateAndEnqueueControlFrame(stream, synStream, timeout, unit, handler, stream);
+            IStream stream = createStream(synStream, listener, true, promise);
+            if (stream == null)
+                return;
+            generateAndEnqueueControlFrame(stream, synStream, synInfo.getTimeout(), synInfo.getUnit(), stream);
         }
-        flush();
     }
 
     @Override
-    public Future<Void> rst(RstInfo rstInfo)
+    public void rst(RstInfo rstInfo) throws InterruptedException, ExecutionException, TimeoutException
     {
-        Promise<Void> result = new Promise<>();
-        rst(rstInfo,0,TimeUnit.MILLISECONDS,result);
-        return result;
+        FutureCallback result = new FutureCallback();
+        rst(rstInfo, result);
+        if (rstInfo.getTimeout() > 0)
+            result.get(rstInfo.getTimeout(), rstInfo.getUnit());
+        else
+            result.get();
     }
 
     @Override
-    public void rst(RstInfo rstInfo, long timeout, TimeUnit unit, Handler<Void> handler)
+    public void rst(RstInfo rstInfo, Callback callback)
     {
         // SPEC v3, 2.2.2
         if (goAwaySent.get())
         {
-            complete(handler,null);
+            complete(callback);
         }
         else
         {
             int streamId = rstInfo.getStreamId();
             IStream stream = streams.get(streamId);
-            RstStreamFrame frame = new RstStreamFrame(version,streamId,rstInfo.getStreamStatus().getCode(version));
-            control(stream,frame,timeout,unit,handler,null);
+            RstStreamFrame frame = new RstStreamFrame(version, streamId, rstInfo.getStreamStatus().getCode(version));
+            control(stream, frame, rstInfo.getTimeout(), rstInfo.getUnit(), callback);
             if (stream != null)
             {
                 stream.process(frame);
+                flusher.removeFrameBytesFromQueue(stream);
                 removeStream(stream);
             }
         }
     }
 
     @Override
-    public Future<Void> settings(SettingsInfo settingsInfo)
+    public void settings(SettingsInfo settingsInfo) throws ExecutionException, InterruptedException, TimeoutException
     {
-        Promise<Void> result = new Promise<>();
-        settings(settingsInfo,0,TimeUnit.MILLISECONDS,result);
-        return result;
+        FutureCallback result = new FutureCallback();
+        settings(settingsInfo, result);
+        if (settingsInfo.getTimeout() > 0)
+            result.get(settingsInfo.getTimeout(), settingsInfo.getUnit());
+        else
+            result.get();
     }
 
     @Override
-    public void settings(SettingsInfo settingsInfo, long timeout, TimeUnit unit, Handler<Void> handler)
+    public void settings(SettingsInfo settingsInfo, Callback callback)
     {
-        SettingsFrame frame = new SettingsFrame(version,settingsInfo.getFlags(),settingsInfo.getSettings());
-        control(null, frame, timeout, unit, handler, null);
+        SettingsFrame frame = new SettingsFrame(version, settingsInfo.getFlags(), settingsInfo.getSettings());
+        control(null, frame, settingsInfo.getTimeout(), settingsInfo.getUnit(), callback);
     }
 
     @Override
-    public Future<PingInfo> ping()
+    public PingResultInfo ping(PingInfo pingInfo) throws ExecutionException, InterruptedException, TimeoutException
     {
-        Promise<PingInfo> result = new Promise<>();
-        ping(0, TimeUnit.MILLISECONDS, result);
-        return result;
+        FuturePromise<PingResultInfo> result = new FuturePromise<>();
+        ping(pingInfo, result);
+        if (pingInfo.getTimeout() > 0)
+            return result.get(pingInfo.getTimeout(), pingInfo.getUnit());
+        else
+            return result.get();
     }
 
     @Override
-    public void ping(long timeout, TimeUnit unit, Handler<PingInfo> handler)
+    public void ping(PingInfo pingInfo, Promise<PingResultInfo> promise)
     {
         int pingId = pingIds.getAndAdd(2);
-        PingInfo pingInfo = new PingInfo(pingId);
-        PingFrame frame = new PingFrame(version,pingId);
-        control(null,frame,timeout,unit,handler,pingInfo);
+        PingInfoCallback pingInfoCallback = new PingInfoCallback(pingId, promise);
+        PingFrame frame = new PingFrame(version, pingId);
+        control(null, frame, pingInfo.getTimeout(), pingInfo.getUnit(), pingInfoCallback);
     }
 
     @Override
-    public Future<Void> goAway()
+    public void goAway(GoAwayInfo goAwayInfo) throws ExecutionException, InterruptedException, TimeoutException
     {
-        return goAway(SessionStatus.OK);
+        goAway(goAwayInfo, SessionStatus.OK);
     }
 
-    private Future<Void> goAway(SessionStatus sessionStatus)
+    private void goAway(GoAwayInfo goAwayInfo, SessionStatus sessionStatus) throws ExecutionException, InterruptedException, TimeoutException
     {
-        Promise<Void> result = new Promise<>();
-        goAway(sessionStatus, 0, TimeUnit.MILLISECONDS, result);
-        return result;
+        FutureCallback result = new FutureCallback();
+        goAway(sessionStatus, goAwayInfo.getTimeout(), goAwayInfo.getUnit(), result);
+        if (goAwayInfo.getTimeout() > 0)
+            result.get(goAwayInfo.getTimeout(), goAwayInfo.getUnit());
+        else
+            result.get();
     }
 
     @Override
-    public void goAway(long timeout, TimeUnit unit, Handler<Void> handler)
+    public void goAway(GoAwayInfo goAwayInfo, Callback callback)
     {
-        goAway(SessionStatus.OK, timeout, unit, handler);
+        goAway(SessionStatus.OK, goAwayInfo.getTimeout(), goAwayInfo.getUnit(), callback);
     }
 
-    private void goAway(SessionStatus sessionStatus, long timeout, TimeUnit unit, Handler<Void> handler)
+    private void goAway(SessionStatus sessionStatus, long timeout, TimeUnit unit, Callback callback)
     {
-        if (goAwaySent.compareAndSet(false,true))
+        if (goAwaySent.compareAndSet(false, true))
         {
             if (!goAwayReceived.get())
             {
-                GoAwayFrame frame = new GoAwayFrame(version,lastStreamId.get(),sessionStatus.getCode());
-                control(null,frame,timeout,unit,handler,null);
+                GoAwayFrame frame = new GoAwayFrame(version, lastStreamId.get(), sessionStatus.getCode());
+                control(null, frame, timeout, unit, callback);
                 return;
             }
         }
-        complete(handler, null);
+        complete(callback);
     }
 
     @Override
@@ -303,16 +322,30 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
     }
 
     @Override
+    public InetSocketAddress getLocalAddress()
+    {
+        return endPoint.getLocalAddress();
+    }
+
+    @Override
+    public InetSocketAddress getRemoteAddress()
+    {
+        return endPoint.getRemoteAddress();
+    }
+
+    @Override
     public void onControlFrame(ControlFrame frame)
     {
         notifyIdle(idleListener, false);
         try
         {
-            logger.debug("Processing {}", frame);
+            if (LOG.isDebugEnabled())
+                LOG.debug("Processing {}", frame);
 
             if (goAwaySent.get())
             {
-                logger.debug("Skipped processing of {}", frame);
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Skipped processing of {}", frame);
                 return;
             }
 
@@ -386,11 +419,13 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
         notifyIdle(idleListener, false);
         try
         {
-            logger.debug("Processing {}, {} data bytes", frame, data.remaining());
+            if (LOG.isDebugEnabled())
+                LOG.debug("Processing {}, {} data bytes", frame, data.remaining());
 
             if (goAwaySent.get())
             {
-                logger.debug("Skipped processing of {}", frame);
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Skipped processing of {}", frame);
                 return;
             }
 
@@ -399,8 +434,9 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
             if (stream == null)
             {
                 RstInfo rstInfo = new RstInfo(streamId, StreamStatus.INVALID_STREAM);
-                logger.debug("Unknown stream {}", rstInfo);
-                rst(rstInfo);
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Unknown stream {}", rstInfo);
+                rst(rstInfo, Callback.Adapter.INSTANCE);
             }
             else
             {
@@ -421,7 +457,7 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
 
     private void processData(final IStream stream, DataFrame frame, ByteBuffer data)
     {
-        ByteBufferDataInfo dataInfo = new ByteBufferDataInfo(data, frame.isClose(), frame.isCompress())
+        ByteBufferDataInfo dataInfo = new ByteBufferDataInfo(data, frame.isClose())
         {
             @Override
             public void consume(int delta)
@@ -439,21 +475,28 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
     @Override
     public void onStreamException(StreamException x)
     {
-        notifyOnException(listener, x);
-        rst(new RstInfo(x.getStreamId(),x.getStreamStatus()));
+        notifyOnFailure(listener, x); // TODO: notify StreamFrameListener if exists?
+        rst(new RstInfo(x.getStreamId(), x.getStreamStatus()), Callback.Adapter.INSTANCE);
     }
 
     @Override
     public void onSessionException(SessionException x)
     {
         Throwable cause = x.getCause();
-        notifyOnException(listener,cause == null?x:cause);
-        goAway(x.getSessionStatus());
+        notifyOnFailure(listener, cause == null ? x : cause);
+        goAway(x.getSessionStatus(), 0, TimeUnit.SECONDS, Callback.Adapter.INSTANCE);
     }
 
-    private void onSyn(SynStreamFrame frame)
+    private void onSyn(final SynStreamFrame frame)
     {
-        IStream stream = createStream(frame, null, false);
+        IStream stream = createStream(frame, null, false, new Promise.Adapter<Stream>()
+        {
+            @Override
+            public void failed(Throwable x)
+            {
+                LOG.debug("Received: {} but creating new Stream failed: {}", frame, x.getMessage());
+            }
+        });
         if (stream != null)
             processSyn(listener, stream, frame);
     }
@@ -463,18 +506,31 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
         stream.process(frame);
         // Update the last stream id before calling the application (which may send a GO_AWAY)
         updateLastStreamId(stream);
-        SynInfo synInfo = new SynInfo(frame.getHeaders(),frame.isClose(),frame.getPriority());
-        StreamFrameListener streamListener = notifyOnSyn(listener,stream,synInfo);
+        StreamFrameListener streamListener;
+        if (stream.isUnidirectional())
+        {
+            PushInfo pushInfo = new PushInfo(frame.getHeaders(), frame.isClose());
+            streamListener = notifyOnPush(stream.getAssociatedStream().getStreamFrameListener(), stream, pushInfo);
+        }
+        else
+        {
+            SynInfo synInfo = new SynInfo(frame.getHeaders(), frame.isClose(), frame.getPriority());
+            streamListener = notifyOnSyn(listener, stream, synInfo);
+        }
         stream.setStreamFrameListener(streamListener);
-        flush();
         // The onSyn() listener may have sent a frame that closed the stream
         if (stream.isClosed())
             removeStream(stream);
     }
 
-    private IStream createStream(SynStreamFrame frame, StreamFrameListener listener, boolean local)
+    private IStream createStream(SynStreamFrame frame, StreamFrameListener listener, boolean local, Promise<Stream> promise)
     {
-        IStream stream = newStream(frame);
+        IStream associatedStream = streams.get(frame.getAssociatedStreamId());
+        IStream stream = new StandardStream(frame.getStreamId(), frame.getPriority(), this, associatedStream,
+                scheduler, promise);
+        stream.setIdleTimeout(endPoint.getIdleTimeout());
+        flowControlStrategy.onNewStream(this, stream);
+
         stream.updateCloseState(frame.isClose(), local);
         stream.setStreamFrameListener(listener);
 
@@ -487,32 +543,51 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
         }
 
         int streamId = stream.getId();
+
+        if (local)
+        {
+            while (true)
+            {
+                int oldStreamCountValue = localStreamCount.get();
+                int maxConcurrentStreams = maxConcurrentLocalStreams;
+                if (maxConcurrentStreams > -1 && oldStreamCountValue >= maxConcurrentStreams)
+                {
+                    String message = String.format("Max concurrent local streams (%d) exceeded.",
+                            maxConcurrentStreams);
+                    LOG.debug(message);
+                    promise.failed(new SPDYException(message));
+                    return null;
+                }
+                if (localStreamCount.compareAndSet(oldStreamCountValue, oldStreamCountValue + 1))
+                    break;
+            }
+        }
+
         if (streams.putIfAbsent(streamId, stream) != null)
         {
+            String message = "Duplicate stream id " + streamId;
+            IllegalStateException duplicateIdException = new IllegalStateException(message);
+            promise.failed(duplicateIdException);
             if (local)
-                throw new IllegalStateException("Duplicate stream id " + streamId);
+            {
+                localStreamCount.decrementAndGet();
+                throw duplicateIdException;
+            }
             RstInfo rstInfo = new RstInfo(streamId, StreamStatus.PROTOCOL_ERROR);
-            logger.debug("Duplicate stream, {}", rstInfo);
-            rst(rstInfo);
+            if (LOG.isDebugEnabled())
+                LOG.debug("Duplicate stream, {}", rstInfo);
+            rst(rstInfo, Callback.Adapter.INSTANCE); // We don't care (too much) if the reset fails.
             return null;
         }
         else
         {
-            logger.debug("Created {}", stream);
-            if (local)
-                notifyStreamCreated(stream);
+            if (LOG.isDebugEnabled())
+                LOG.debug("Created {}", stream);
+            notifyStreamCreated(stream);
             return stream;
         }
     }
 
-    private IStream newStream(SynStreamFrame frame)
-    {
-        IStream associatedStream = streams.get(frame.getAssociatedStreamId());
-        IStream stream = new StandardStream(frame.getStreamId(), frame.getPriority(), this, associatedStream);
-        flowControlStrategy.onNewStream(this, stream);
-        return stream;
-    }
-
     private void notifyStreamCreated(IStream stream)
     {
         for (Listener listener : listeners)
@@ -525,11 +600,11 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
                 }
                 catch (Exception x)
                 {
-                    logger.info("Exception while notifying listener " + listener, x);
+                    LOG.info("Exception while notifying listener " + listener, x);
                 }
                 catch (Error x)
                 {
-                    logger.info("Exception while notifying listener " + listener, x);
+                    LOG.info("Exception while notifying listener " + listener, x);
                     throw x;
                 }
             }
@@ -543,10 +618,16 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
 
         IStream removed = streams.remove(stream.getId());
         if (removed != null)
+        {
             assert removed == stream;
 
-        logger.debug("Removed {}", stream);
-        notifyStreamClosed(stream);
+            if (streamIds.get() % 2 == stream.getId() % 2)
+                localStreamCount.decrementAndGet();
+
+            if (LOG.isDebugEnabled())
+                LOG.debug("Removed {}", stream);
+            notifyStreamClosed(stream);
+        }
     }
 
     private void notifyStreamClosed(IStream stream)
@@ -561,11 +642,11 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
                 }
                 catch (Exception x)
                 {
-                    logger.info("Exception while notifying listener " + listener, x);
+                    LOG.info("Exception while notifying listener " + listener, x);
                 }
                 catch (Error x)
                 {
-                    logger.info("Exception while notifying listener " + listener, x);
+                    LOG.info("Exception while notifying listener " + listener, x);
                     throw x;
                 }
             }
@@ -578,13 +659,14 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
         IStream stream = streams.get(streamId);
         if (stream == null)
         {
-            RstInfo rstInfo = new RstInfo(streamId,StreamStatus.INVALID_STREAM);
-            logger.debug("Unknown stream {}",rstInfo);
-            rst(rstInfo);
+            RstInfo rstInfo = new RstInfo(streamId, StreamStatus.INVALID_STREAM);
+            if (LOG.isDebugEnabled())
+                LOG.debug("Unknown stream {}", rstInfo);
+            rst(rstInfo, Callback.Adapter.INSTANCE);
         }
         else
         {
-            processReply(stream,frame);
+            processReply(stream, frame);
         }
     }
 
@@ -602,9 +684,8 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
         if (stream != null)
             stream.process(frame);
 
-        RstInfo rstInfo = new RstInfo(frame.getStreamId(),StreamStatus.from(frame.getVersion(),frame.getStatusCode()));
+        RstInfo rstInfo = new RstInfo(frame.getStreamId(), StreamStatus.from(frame.getVersion(), frame.getStatusCode()));
         notifyOnRst(listener, rstInfo);
-        flush();
 
         if (stream != null)
             removeStream(stream);
@@ -617,11 +698,19 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
         {
             int windowSize = windowSizeSetting.value();
             setWindowSize(windowSize);
-            logger.debug("Updated session window size to {}", windowSize);
+            if (LOG.isDebugEnabled())
+                LOG.debug("Updated session window size to {}", windowSize);
+        }
+        Settings.Setting maxConcurrentStreamsSetting = frame.getSettings().get(Settings.ID.MAX_CONCURRENT_STREAMS);
+        if (maxConcurrentStreamsSetting != null)
+        {
+            int maxConcurrentStreamsValue = maxConcurrentStreamsSetting.value();
+            maxConcurrentLocalStreams = maxConcurrentStreamsValue;
+            if (LOG.isDebugEnabled())
+                LOG.debug("Updated session maxConcurrentLocalStreams to {}", maxConcurrentStreamsValue);
         }
         SettingsInfo settingsInfo = new SettingsInfo(frame.getSettings(), frame.isClearPersisted());
         notifyOnSettings(listener, settingsInfo);
-        flush();
     }
 
     private void onPing(PingFrame frame)
@@ -629,13 +718,12 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
         int pingId = frame.getPingId();
         if (pingId % 2 == pingIds.get() % 2)
         {
-            PingInfo pingInfo = new PingInfo(frame.getPingId());
-            notifyOnPing(listener, pingInfo);
-            flush();
+            PingResultInfo pingResultInfo = new PingResultInfo(frame.getPingId());
+            notifyOnPing(listener, pingResultInfo);
         }
         else
         {
-            control(null, frame, 0, TimeUnit.MILLISECONDS, null, null);
+            control(null, frame, 0, TimeUnit.MILLISECONDS, Callback.Adapter.INSTANCE);
         }
     }
 
@@ -643,13 +731,11 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
     {
         if (goAwayReceived.compareAndSet(false, true))
         {
-            GoAwayInfo goAwayInfo = new GoAwayInfo(frame.getLastStreamId(),SessionStatus.from(frame.getStatusCode()));
-            notifyOnGoAway(listener,goAwayInfo);
-            flush();
+            GoAwayResultInfo goAwayResultInfo = new GoAwayResultInfo(frame.getLastStreamId(), SessionStatus.from(frame.getStatusCode()));
+            notifyOnGoAway(listener, goAwayResultInfo);
             // SPDY does not require to send back a response to a GO_AWAY.
-            // We notified the application of the last good stream id,
-            // tried our best to flush remaining data, and close.
-            close();
+            // We notified the application of the last good stream id and
+            // tried our best to flush remaining data.
         }
     }
 
@@ -659,13 +745,14 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
         IStream stream = streams.get(streamId);
         if (stream == null)
         {
-            RstInfo rstInfo = new RstInfo(streamId,StreamStatus.INVALID_STREAM);
-            logger.debug("Unknown stream, {}",rstInfo);
-            rst(rstInfo);
+            RstInfo rstInfo = new RstInfo(streamId, StreamStatus.INVALID_STREAM);
+            if (LOG.isDebugEnabled())
+                LOG.debug("Unknown stream, {}", rstInfo);
+            rst(rstInfo, Callback.Adapter.INSTANCE);
         }
         else
         {
-            processHeaders(stream,frame);
+            processHeaders(stream, frame);
         }
     }
 
@@ -681,13 +768,12 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
         int streamId = frame.getStreamId();
         IStream stream = streams.get(streamId);
         flowControlStrategy.onWindowUpdate(this, stream, frame.getWindowDelta());
-        flush();
+        flusher.flush();
     }
 
     private void onCredential(CredentialFrame frame)
     {
-        logger.warn("{} frame not yet supported", ControlFrameType.CREDENTIAL);
-        flush();
+        LOG.warn("{} frame not yet supported", frame.getType());
     }
 
     protected void close()
@@ -697,44 +783,68 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
             controller.close(false);
     }
 
-    private void notifyOnException(SessionFrameListener listener, Throwable x)
+    private void notifyOnFailure(SessionFrameListener listener, Throwable x)
     {
         try
         {
             if (listener != null)
             {
-                logger.debug("Invoking callback with {} on listener {}",x,listener);
-                listener.onException(x);
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Invoking callback with {} on listener {}", x, listener);
+                listener.onFailure(this, x);
             }
         }
         catch (Exception xx)
         {
-            logger.info("Exception while notifying listener " + listener, xx);
+            LOG.info("Exception while notifying listener " + listener, xx);
         }
         catch (Error xx)
         {
-            logger.info("Exception while notifying listener " + listener, xx);
+            LOG.info("Exception while notifying listener " + listener, xx);
             throw xx;
         }
     }
 
+    private StreamFrameListener notifyOnPush(StreamFrameListener listener, Stream stream, PushInfo pushInfo)
+    {
+        try
+        {
+            if (listener == null)
+                return null;
+            if (LOG.isDebugEnabled())
+                LOG.debug("Invoking callback with {} on listener {}", pushInfo, listener);
+            return listener.onPush(stream, pushInfo);
+        }
+        catch (Exception x)
+        {
+            LOG.info("Exception while notifying listener " + listener, x);
+            return null;
+        }
+        catch (Error x)
+        {
+            LOG.info("Exception while notifying listener " + listener, x);
+            throw x;
+        }
+    }
+
     private StreamFrameListener notifyOnSyn(SessionFrameListener listener, Stream stream, SynInfo synInfo)
     {
         try
         {
             if (listener == null)
                 return null;
-            logger.debug("Invoking callback with {} on listener {}",synInfo,listener);
-            return listener.onSyn(stream,synInfo);
+            if (LOG.isDebugEnabled())
+                LOG.debug("Invoking callback with {} on listener {}", synInfo, listener);
+            return listener.onSyn(stream, synInfo);
         }
         catch (Exception x)
         {
-            logger.info("Exception while notifying listener " + listener,x);
+            LOG.info("Exception while notifying listener " + listener, x);
             return null;
         }
         catch (Error x)
         {
-            logger.info("Exception while notifying listener " + listener, x);
+            LOG.info("Exception while notifying listener " + listener, x);
             throw x;
         }
     }
@@ -745,17 +855,18 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
         {
             if (listener != null)
             {
-                logger.debug("Invoking callback with {} on listener {}",rstInfo,listener);
-                listener.onRst(this,rstInfo);
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Invoking callback with {} on listener {}", rstInfo, listener);
+                listener.onRst(this, rstInfo);
             }
         }
         catch (Exception x)
         {
-            logger.info("Exception while notifying listener " + listener, x);
+            LOG.info("Exception while notifying listener " + listener, x);
         }
         catch (Error x)
         {
-            logger.info("Exception while notifying listener " + listener, x);
+            LOG.info("Exception while notifying listener " + listener, x);
             throw x;
         }
     }
@@ -766,95 +877,102 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
         {
             if (listener != null)
             {
-                logger.debug("Invoking callback with {} on listener {}",settingsInfo,listener);
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Invoking callback with {} on listener {}", settingsInfo, listener);
                 listener.onSettings(this, settingsInfo);
             }
         }
         catch (Exception x)
         {
-            logger.info("Exception while notifying listener " + listener, x);
+            LOG.info("Exception while notifying listener " + listener, x);
         }
         catch (Error x)
         {
-            logger.info("Exception while notifying listener " + listener, x);
+            LOG.info("Exception while notifying listener " + listener, x);
             throw x;
         }
     }
 
-    private void notifyOnPing(SessionFrameListener listener, PingInfo pingInfo)
+    private void notifyOnPing(SessionFrameListener listener, PingResultInfo pingResultInfo)
     {
         try
         {
             if (listener != null)
             {
-                logger.debug("Invoking callback with {} on listener {}",pingInfo,listener);
-                listener.onPing(this, pingInfo);
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Invoking callback with {} on listener {}", pingResultInfo, listener);
+                listener.onPing(this, pingResultInfo);
             }
         }
         catch (Exception x)
         {
-            logger.info("Exception while notifying listener " + listener, x);
+            LOG.info("Exception while notifying listener " + listener, x);
         }
         catch (Error x)
         {
-            logger.info("Exception while notifying listener " + listener, x);
+            LOG.info("Exception while notifying listener " + listener, x);
             throw x;
         }
     }
 
-    private void notifyOnGoAway(SessionFrameListener listener, GoAwayInfo goAwayInfo)
+    private void notifyOnGoAway(SessionFrameListener listener, GoAwayResultInfo goAwayResultInfo)
     {
         try
         {
             if (listener != null)
             {
-                logger.debug("Invoking callback with {} on listener {}",goAwayInfo,listener);
-                listener.onGoAway(this, goAwayInfo);
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Invoking callback with {} on listener {}", goAwayResultInfo, listener);
+                listener.onGoAway(this, goAwayResultInfo);
             }
         }
         catch (Exception x)
         {
-            logger.info("Exception while notifying listener " + listener, x);
+            LOG.info("Exception while notifying listener " + listener, x);
         }
         catch (Error x)
         {
-            logger.info("Exception while notifying listener " + listener, x);
+            LOG.info("Exception while notifying listener " + listener, x);
             throw x;
         }
     }
 
     @Override
-    public <C> void control(IStream stream, ControlFrame frame, long timeout, TimeUnit unit, Handler<C> handler, C context)
+    public void control(IStream stream, ControlFrame frame, long timeout, TimeUnit unit, Callback callback)
     {
-        generateAndEnqueueControlFrame(stream,frame,timeout,unit,handler,context);
-        flush();
+        generateAndEnqueueControlFrame(stream, frame, timeout, unit, callback);
     }
 
-    private <C> void generateAndEnqueueControlFrame(IStream stream, ControlFrame frame, long timeout, TimeUnit unit, Handler<C> handler, C context)
+    private void generateAndEnqueueControlFrame(IStream stream, ControlFrame frame, long timeout, TimeUnit unit, Callback callback)
     {
         try
         {
             // Synchronization is necessary, since we may have concurrent replies
             // and those needs to be generated and enqueued atomically in order
             // to maintain a correct compression context
+            ControlFrameBytes frameBytes;
+            Throwable throwable;
             synchronized (this)
             {
                 ByteBuffer buffer = generator.control(frame);
-                logger.debug("Queuing {} on {}", frame, stream);
-                ControlFrameBytes<C> frameBytes = new ControlFrameBytes<>(stream, handler, context, frame, buffer);
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Queuing {} on {}", frame, stream);
+                frameBytes = new ControlFrameBytes(stream, callback, frame, buffer);
                 if (timeout > 0)
                     frameBytes.task = scheduler.schedule(frameBytes, timeout, unit);
 
                 // Special handling for PING frames, they must be sent as soon as possible
                 if (ControlFrameType.PING == frame.getType())
-                    prepend(frameBytes);
+                    throwable = flusher.prepend(frameBytes);
                 else
-                    append(frameBytes);
+                    throwable = flusher.append(frameBytes);
             }
+            // Flush MUST be done outside synchronized blocks
+            flush(frameBytes, throwable);
         }
         catch (Exception x)
         {
-            notifyHandlerFailed(handler, context, x);
+            notifyCallbackFailed(callback, x);
         }
     }
 
@@ -866,225 +984,54 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
     }
 
     @Override
-    public <C> void data(IStream stream, DataInfo dataInfo, long timeout, TimeUnit unit, Handler<C> handler, C context)
+    public void data(IStream stream, DataInfo dataInfo, long timeout, TimeUnit unit, Callback callback)
     {
-        logger.debug("Queuing {} on {}",dataInfo,stream);
-        DataFrameBytes<C> frameBytes = new DataFrameBytes<>(stream,handler,context,dataInfo);
+        if (LOG.isDebugEnabled())
+            LOG.debug("Queuing {} on {}", dataInfo, stream);
+        DataFrameBytes frameBytes = new DataFrameBytes(stream, callback, dataInfo);
         if (timeout > 0)
-            frameBytes.task = scheduler.schedule(frameBytes,timeout,unit);
-        append(frameBytes);
-        flush();
-    }
-
-    private void execute(Runnable task)
-    {
-        threadPool.execute(task);
+            frameBytes.task = scheduler.schedule(frameBytes, timeout, unit);
+        flush(frameBytes, flusher.append(frameBytes));
     }
 
     @Override
-    public void flush()
+    public void shutdown()
     {
-        FrameBytes frameBytes = null;
-        ByteBuffer buffer = null;
-        synchronized (queue)
-        {
-            if (flushing || queue.isEmpty())
-                return;
-
-            Set<IStream> stalledStreams = null;
-            for (int i = 0; i < queue.size(); ++i)
-            {
-                frameBytes = queue.get(i);
-
-                IStream stream = frameBytes.getStream();
-                if (stream != null && stalledStreams != null && stalledStreams.contains(stream))
-                    continue;
-
-                buffer = frameBytes.getByteBuffer();
-                if (buffer != null)
-                {
-                    queue.remove(i);
-                    if (stream != null && stream.isReset())
-                    {
-                        frameBytes.fail(new StreamException(stream.getId(),StreamStatus.INVALID_STREAM));
-                        return;
-                    }
-                    break;
-                }
-
-                if (stalledStreams == null)
-                    stalledStreams = new HashSet<>();
-                if (stream != null)
-                    stalledStreams.add(stream);
-
-                logger.debug("Flush stalled for {}, {} frame(s) in queue",frameBytes,queue.size());
-            }
-
-            if (buffer == null)
-                return;
-
-            flushing = true;
-            logger.debug("Flushing {}, {} frame(s) in queue",frameBytes,queue.size());
-        }
-        write(buffer,this,frameBytes);
+        CloseFrameBytes frameBytes = new CloseFrameBytes();
+        flush(frameBytes, flusher.append(frameBytes));
     }
 
-    private void append(FrameBytes frameBytes)
+    private void flush(FrameBytes frameBytes, Throwable throwable)
     {
-        Throwable failure;
-        synchronized (queue)
-        {
-            failure = this.failure;
-            if (failure == null)
-            {
-                int index = queue.size();
-                while (index > 0)
-                {
-                    FrameBytes element = queue.get(index - 1);
-                    if (element.compareTo(frameBytes) >= 0)
-                        break;
-                    --index;
-                }
-                queue.add(index,frameBytes);
-            }
-        }
-
-        if (failure != null)
-            frameBytes.fail(new SPDYException(failure));
-    }
-
-    private void prepend(FrameBytes frameBytes)
-    {
-        Throwable failure;
-        synchronized (queue)
-        {
-            failure = this.failure;
-            if (failure == null)
-            {
-                int index = 0;
-                while (index < queue.size())
-                {
-                    FrameBytes element = queue.get(index);
-                    if (element.compareTo(frameBytes) <= 0)
-                        break;
-                    ++index;
-                }
-                queue.add(index,frameBytes);
-            }
-        }
-
-        if (failure != null)
-            frameBytes.fail(new SPDYException(failure));
-    }
-
-    @Override
-    public void completed(FrameBytes frameBytes)
-    {
-        synchronized (queue)
-        {
-            logger.debug("Completed write of {}, {} frame(s) in queue",frameBytes,queue.size());
-            flushing = false;
-        }
-        frameBytes.complete();
-    }
-
-    @Override
-    public void failed(FrameBytes frameBytes, Throwable x)
-    {
-        List<FrameBytes> frameBytesToFail = new ArrayList<>();
-        frameBytesToFail.add(frameBytes);
-
-        synchronized (queue)
-        {
-            failure = x;
-            String logMessage = String.format("Failed write of %s, failing all %d frame(s) in queue",frameBytes,queue.size());
-            logger.debug(logMessage,x);
-            frameBytesToFail.addAll(queue);
-            queue.clear();
-            flushing = false;
-        }
-
-        for (FrameBytes fb : frameBytesToFail)
-            fb.fail(x);
-    }
-
-    protected void write(ByteBuffer buffer, Handler<FrameBytes> handler, FrameBytes frameBytes)
-    {
-        if (controller != null)
-        {
-            logger.debug("Writing {} frame bytes of {}",buffer.remaining(),frameBytes);
-            controller.write(buffer,handler,frameBytes);
-        }
-    }
-
-    private <C> void complete(final Handler<C> handler, final C context)
-    {
-        // Applications may send and queue up a lot of frames and
-        // if we call Handler.completed() only synchronously we risk
-        // starvation (for the last frames sent) and stack overflow.
-        // Therefore every some invocation, we dispatch to a new thread
-        Integer invocations = handlerInvocations.get();
-        if (invocations >= 4)
-        {
-            execute(new Runnable()
-            {
-                @Override
-                public void run()
-                {
-                    if (handler != null)
-                        notifyHandlerCompleted(handler,context);
-                    flush();
-                }
-            });
-        }
+        if (throwable != null)
+            frameBytes.failed(throwable);
         else
-        {
-            handlerInvocations.set(invocations + 1);
-            try
-            {
-                if (handler != null)
-                    notifyHandlerCompleted(handler,context);
-                flush();
-            }
-            finally
-            {
-                handlerInvocations.set(invocations);
-            }
-        }
+            flusher.flush();
     }
 
-    private <C> void notifyHandlerCompleted(Handler<C> handler, C context)
+    private void complete(final Callback callback)
     {
         try
         {
-            handler.completed(context);
+            if (callback != null)
+                callback.succeeded();
         }
-        catch (Exception x)
+        catch (Throwable x)
         {
-            logger.info("Exception while notifying handler " + handler, x);
-        }
-        catch (Error x)
-        {
-            logger.info("Exception while notifying handler " + handler, x);
-            throw x;
+            LOG.info("Exception while notifying callback " + callback, x);
         }
     }
 
-    private <C> void notifyHandlerFailed(Handler<C> handler, C context, Throwable x)
+    private void notifyCallbackFailed(Callback callback, Throwable failure)
     {
         try
         {
-            if (handler != null)
-                handler.failed(context, x);
-        }
-        catch (Exception xx)
-        {
-            logger.info("Exception while notifying handler " + handler, xx);
+            if (callback != null)
+                callback.failed(failure);
         }
-        catch (Error xx)
+        catch (Throwable x)
         {
-            logger.info("Exception while notifying handler " + handler, xx);
-            throw xx;
+            LOG.info("Exception while notifying callback " + callback, x);
         }
     }
 
@@ -1098,50 +1045,43 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
         flowControlStrategy.setWindowSize(this, initialWindowSize);
     }
 
+    @Override
     public String toString()
     {
-        return String.format("%s@%x{v%d,queuSize=%d,windowSize=%d,streams=%d}", getClass().getSimpleName(), hashCode(), version, queue.size(), getWindowSize(), streams.size());
+        return String.format("%s@%x{v%d,queueSize=%d,windowSize=%d,streams=%d}", getClass().getSimpleName(),
+                hashCode(), version, flusher.getQueueSize(), getWindowSize(), streams.size());
     }
-    
-    
+
     @Override
     public String dump()
     {
-        return AggregateLifeCycle.dump(this);
+        return ContainerLifeCycle.dump(this);
     }
 
     @Override
     public void dump(Appendable out, String indent) throws IOException
     {
-        AggregateLifeCycle.dumpObject(out,this);
-        AggregateLifeCycle.dump(out,indent,Collections.singletonList(controller),streams.values());
+        ContainerLifeCycle.dumpObject(out, this);
+        ContainerLifeCycle.dump(out, indent, Collections.singletonList(controller), streams.values());
     }
 
-
-
-    public interface FrameBytes extends Comparable<FrameBytes>
+    public interface FrameBytes extends Comparable<FrameBytes>, Callback
     {
         public IStream getStream();
 
         public abstract ByteBuffer getByteBuffer();
-
-        public abstract void complete();
-
-        public abstract void fail(Throwable throwable);
     }
 
-    private abstract class AbstractFrameBytes<C> implements FrameBytes, Runnable
+    abstract class AbstractFrameBytes implements FrameBytes, Runnable
     {
         private final IStream stream;
-        private final Handler<C> handler;
-        private final C context;
-        protected volatile ScheduledFuture<?> task;
+        private final Callback callback;
+        protected volatile Scheduler.Task task;
 
-        protected AbstractFrameBytes(IStream stream, Handler<C> handler, C context)
+        protected AbstractFrameBytes(IStream stream, Callback callback)
         {
             this.stream = stream;
-            this.handler = handler;
-            this.context = context;
+            this.callback = Objects.requireNonNull(callback);
         }
 
         @Override
@@ -1165,44 +1105,43 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
             return thatStream.getPriority() - thisStream.getPriority();
         }
 
-        @Override
-        public void complete()
+        private void cancelTask()
         {
-            cancelTask();
-            StandardSession.this.complete(handler,context);
+            Scheduler.Task task = this.task;
+            if (task != null)
+                task.cancel();
         }
 
         @Override
-        public void fail(Throwable x)
+        public void run()
         {
-            cancelTask();
-            notifyHandlerFailed(handler,context,x);
-            StandardSession.this.flush();
+            close();
+            failed(new InterruptedByTimeoutException());
         }
 
-        private void cancelTask()
+        @Override
+        public void succeeded()
         {
-            ScheduledFuture<?> task = this.task;
-            if (task != null)
-                task.cancel(false);
+            cancelTask();
+            StandardSession.this.complete(callback);
         }
 
         @Override
-        public void run()
+        public void failed(Throwable x)
         {
-            close();
-            fail(new InterruptedByTimeoutException());
+            cancelTask();
+            notifyCallbackFailed(callback, x);
         }
     }
 
-    private class ControlFrameBytes<C> extends AbstractFrameBytes<C>
+    protected class ControlFrameBytes extends AbstractFrameBytes
     {
         private final ControlFrame frame;
         private final ByteBuffer buffer;
 
-        private ControlFrameBytes(IStream stream, Handler<C> handler, C context, ControlFrame frame, ByteBuffer buffer)
+        private ControlFrameBytes(IStream stream, Callback callback, ControlFrame frame, ByteBuffer buffer)
         {
-            super(stream,handler,context);
+            super(stream, callback);
             this.frame = frame;
             this.buffer = buffer;
         }
@@ -1214,11 +1153,11 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
         }
 
         @Override
-        public void complete()
+        public void succeeded()
         {
             bufferPool.release(buffer);
 
-            super.complete();
+            super.succeeded();
 
             if (frame.getType() == ControlFrameType.GO_AWAY)
             {
@@ -1238,15 +1177,15 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
         }
     }
 
-    private class DataFrameBytes<C> extends AbstractFrameBytes<C>
+    protected class DataFrameBytes extends AbstractFrameBytes
     {
         private final DataInfo dataInfo;
         private int size;
         private volatile ByteBuffer buffer;
 
-        private DataFrameBytes(IStream stream, Handler<C> handler, C context, DataInfo dataInfo)
+        private DataFrameBytes(IStream stream, Callback handler, DataInfo dataInfo)
         {
-            super(stream,handler,context);
+            super(stream, handler);
             this.dataInfo = dataInfo;
         }
 
@@ -1257,41 +1196,53 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
             {
                 IStream stream = getStream();
                 int windowSize = stream.getWindowSize();
-                if (windowSize <= 0)
-                    return null;
+
+                // TODO: optimization
+                // Right now, we use the windowSize to chunk big buffers.
+                // However, if the window size is large, we may congest the
+                // connection, or favor one stream that does a big download,
+                // starving the other streams.
+                // Also, SPDY DATA frames have a maximum of 16 MiB size, which
+                // is not enforced here.
+                // We should have a configurable "preferredDataFrameSize"
+                // (or even better autotuning) that will allow to send chunks
+                // that will not starve other streams and small enough to
+                // not congest the connection, while avoiding to send too many
+                // TCP packets.
+                // See also comment in class Flusher.
 
                 size = dataInfo.available();
                 if (size > windowSize)
                     size = windowSize;
 
-                buffer = generator.data(stream.getId(),size,dataInfo);
+                buffer = generator.data(stream.getId(), size, dataInfo);
                 return buffer;
             }
             catch (Throwable x)
             {
-                fail(x);
+                failed(x);
                 return null;
             }
         }
 
         @Override
-        public void complete()
+        public void succeeded()
         {
             bufferPool.release(buffer);
             IStream stream = getStream();
+            dataInfo.consume(size);
             flowControlStrategy.updateWindow(StandardSession.this, stream, -size);
             if (dataInfo.available() > 0)
             {
                 // We have written a frame out of this DataInfo, but there is more to write.
                 // We need to keep the correct ordering of frames, to avoid that another
                 // DataInfo for the same stream is written before this one is finished.
-                prepend(this);
-                flush();
+                flush(this, flusher.prepend(this));
             }
             else
             {
-                super.complete();
-                stream.updateCloseState(dataInfo.isClose(),true);
+                super.succeeded();
+                stream.updateCloseState(dataInfo.isClose(), true);
                 if (stream.isClosed())
                     removeStream(stream);
             }
@@ -1303,4 +1254,50 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
             return String.format("DATA bytes @%x available=%d consumed=%d on %s", dataInfo.hashCode(), dataInfo.available(), dataInfo.consumed(), getStream());
         }
     }
+
+    protected class CloseFrameBytes extends AbstractFrameBytes
+    {
+        private CloseFrameBytes()
+        {
+            super(null, Callback.Adapter.INSTANCE);
+        }
+
+        @Override
+        public ByteBuffer getByteBuffer()
+        {
+            return BufferUtil.EMPTY_BUFFER;
+        }
+
+        @Override
+        public void succeeded()
+        {
+            super.succeeded();
+            close();
+        }
+    }
+
+    private static class PingInfoCallback extends PingResultInfo implements Callback
+    {
+        private final Promise<PingResultInfo> promise;
+
+        public PingInfoCallback(int pingId, Promise<PingResultInfo> promise)
+        {
+            super(pingId);
+            this.promise = promise;
+        }
+
+        @Override
+        public void succeeded()
+        {
+            if (promise != null)
+                promise.succeeded(this);
+        }
+
+        @Override
+        public void failed(Throwable x)
+        {
+            if (promise != null)
+                promise.failed(x);
+        }
+    }
 }
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/StandardStream.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/StandardStream.java
index 0974d7d..3467069 100644
--- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/StandardStream.java
+++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/StandardStream.java
@@ -22,33 +22,39 @@ import java.util.Collections;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
 import java.util.concurrent.atomic.AtomicInteger;
 
+import org.eclipse.jetty.io.IdleTimeout;
 import org.eclipse.jetty.spdy.api.DataInfo;
-import org.eclipse.jetty.spdy.api.Handler;
 import org.eclipse.jetty.spdy.api.HeadersInfo;
+import org.eclipse.jetty.spdy.api.PushInfo;
 import org.eclipse.jetty.spdy.api.ReplyInfo;
 import org.eclipse.jetty.spdy.api.RstInfo;
 import org.eclipse.jetty.spdy.api.Stream;
 import org.eclipse.jetty.spdy.api.StreamFrameListener;
 import org.eclipse.jetty.spdy.api.StreamStatus;
-import org.eclipse.jetty.spdy.api.SynInfo;
 import org.eclipse.jetty.spdy.frames.ControlFrame;
 import org.eclipse.jetty.spdy.frames.HeadersFrame;
 import org.eclipse.jetty.spdy.frames.SynReplyFrame;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.FutureCallback;
+import org.eclipse.jetty.util.FuturePromise;
+import org.eclipse.jetty.util.Promise;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.Scheduler;
 
-public class StandardStream implements IStream
+public class StandardStream extends IdleTimeout implements IStream
 {
-    private static final Logger logger = Log.getLogger(Stream.class);
+    private static final Logger LOG = Log.getLogger(Stream.class);
     private final Map<String, Object> attributes = new ConcurrentHashMap<>();
     private final int id;
     private final byte priority;
     private final ISession session;
     private final IStream associatedStream;
+    private final Promise<Stream> promise;
     private final AtomicInteger windowSize = new AtomicInteger();
     private final Set<Stream> pushedStreams = Collections.newSetFromMap(new ConcurrentHashMap<Stream, Boolean>());
     private volatile StreamFrameListener listener;
@@ -56,12 +62,14 @@ public class StandardStream implements IStream
     private volatile CloseState closeState = CloseState.OPENED;
     private volatile boolean reset = false;
 
-    public StandardStream(int id, byte priority, ISession session, IStream associatedStream)
+    public StandardStream(int id, byte priority, ISession session, IStream associatedStream, Scheduler scheduler, Promise<Stream> promise)
     {
+        super(scheduler);
         this.id = id;
         this.priority = priority;
         this.session = session;
         this.associatedStream = associatedStream;
+        this.promise = promise;
     }
 
     @Override
@@ -101,6 +109,29 @@ public class StandardStream implements IStream
     }
 
     @Override
+    protected void onIdleExpired(TimeoutException timeout)
+    {
+        StreamFrameListener listener = this.listener;
+        if (listener != null)
+            listener.onFailure(this, timeout);
+        // The stream is now gone, we must close it to
+        // avoid that its idle timeout is rescheduled.
+        close();
+    }
+
+    private void close()
+    {
+        closeState = CloseState.CLOSED;
+        onClose();
+    }
+
+    @Override
+    public boolean isOpen()
+    {
+        return !isClosed();
+    }
+
+    @Override
     public int getWindowSize()
     {
         return windowSize.get();
@@ -110,7 +141,8 @@ public class StandardStream implements IStream
     public void updateWindowSize(int delta)
     {
         int size = windowSize.addAndGet(delta);
-        logger.debug("Updated window size {} -> {} for {}", size - delta, size, this);
+        if (LOG.isDebugEnabled())
+            LOG.debug("Updated window size {} -> {} for {}", size - delta, size, this);
     }
 
     @Override
@@ -128,7 +160,7 @@ public class StandardStream implements IStream
     @Override
     public void setAttribute(String key, Object value)
     {
-        attributes.put(key,value);
+        attributes.put(key, value);
     }
 
     @Override
@@ -143,6 +175,7 @@ public class StandardStream implements IStream
         this.listener = listener;
     }
 
+    @Override
     public StreamFrameListener getStreamFrameListener()
     {
         return listener;
@@ -151,6 +184,8 @@ public class StandardStream implements IStream
     @Override
     public void updateCloseState(boolean close, boolean local)
     {
+        if (LOG.isDebugEnabled())
+            LOG.debug("{} close={} local={}", this, close, local);
         if (close)
         {
             switch (closeState)
@@ -165,20 +200,20 @@ public class StandardStream implements IStream
                     if (local)
                         throw new IllegalStateException();
                     else
-                        closeState = CloseState.CLOSED;
+                        close();
                     break;
                 }
                 case REMOTELY_CLOSED:
                 {
                     if (local)
-                        closeState = CloseState.CLOSED;
+                        close();
                     else
                         throw new IllegalStateException();
                     break;
                 }
                 default:
                 {
-                    throw new IllegalStateException();
+                    LOG.warn("Already CLOSED! {} local={}", this, local);
                 }
             }
         }
@@ -187,6 +222,7 @@ public class StandardStream implements IStream
     @Override
     public void process(ControlFrame frame)
     {
+        notIdle();
         switch (frame.getType())
         {
             case SYN_STREAM:
@@ -221,30 +257,45 @@ public class StandardStream implements IStream
                 throw new IllegalStateException();
             }
         }
-        session.flush();
     }
 
     @Override
     public void process(DataInfo dataInfo)
     {
+        notIdle();
         // TODO: in v3 we need to send a rst instead of just ignoring
         // ignore data frame if this stream is remotelyClosed already
         if (isRemotelyClosed())
         {
-            logger.debug("Stream is remotely closed, ignoring {}", dataInfo);
+            if (LOG.isDebugEnabled())
+                LOG.debug("Stream is remotely closed, ignoring {}", dataInfo);
             return;
         }
 
         if (!canReceive())
         {
-            logger.debug("Protocol error receiving {}, resetting" + dataInfo);
-            session.rst(new RstInfo(getId(), StreamStatus.PROTOCOL_ERROR));
+            if (LOG.isDebugEnabled())
+                LOG.debug("Protocol error receiving {}, resetting", dataInfo);
+            session.rst(new RstInfo(getId(), StreamStatus.PROTOCOL_ERROR), Callback.Adapter.INSTANCE);
             return;
         }
 
         updateCloseState(dataInfo.isClose(), false);
         notifyOnData(dataInfo);
-        session.flush();
+    }
+
+    @Override
+    public void succeeded()
+    {
+        if (promise != null)
+            promise.succeeded(this);
+    }
+
+    @Override
+    public void failed(Throwable x)
+    {
+        if (promise != null)
+            promise.failed(x);
     }
 
     private void notifyOnReply(ReplyInfo replyInfo)
@@ -254,17 +305,18 @@ public class StandardStream implements IStream
         {
             if (listener != null)
             {
-                logger.debug("Invoking reply callback with {} on listener {}", replyInfo, listener);
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Invoking reply callback with {} on listener {}", replyInfo, listener);
                 listener.onReply(this, replyInfo);
             }
         }
         catch (Exception x)
         {
-            logger.info("Exception while notifying listener " + listener, x);
+            LOG.info("Exception while notifying listener " + listener, x);
         }
         catch (Error x)
         {
-            logger.info("Exception while notifying listener " + listener, x);
+            LOG.info("Exception while notifying listener " + listener, x);
             throw x;
         }
     }
@@ -276,17 +328,18 @@ public class StandardStream implements IStream
         {
             if (listener != null)
             {
-                logger.debug("Invoking headers callback with {} on listener {}", headersInfo, listener);
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Invoking headers callback with {} on listener {}", headersInfo, listener);
                 listener.onHeaders(this, headersInfo);
             }
         }
         catch (Exception x)
         {
-            logger.info("Exception while notifying listener " + listener, x);
+            LOG.info("Exception while notifying listener " + listener, x);
         }
         catch (Error x)
         {
-            logger.info("Exception while notifying listener " + listener, x);
+            LOG.info("Exception while notifying listener " + listener, x);
             throw x;
         }
     }
@@ -298,113 +351,136 @@ public class StandardStream implements IStream
         {
             if (listener != null)
             {
-                logger.debug("Invoking data callback with {} on listener {}", dataInfo, listener);
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Invoking data callback with {} on listener {}", dataInfo, listener);
                 listener.onData(this, dataInfo);
-                logger.debug("Invoked data callback with {} on listener {}", dataInfo, listener);
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Invoked data callback with {} on listener {}", dataInfo, listener);
             }
         }
         catch (Exception x)
         {
-            logger.info("Exception while notifying listener " + listener, x);
+            LOG.info("Exception while notifying listener " + listener, x);
         }
         catch (Error x)
         {
-            logger.info("Exception while notifying listener " + listener, x);
+            LOG.info("Exception while notifying listener " + listener, x);
             throw x;
         }
     }
 
     @Override
-    public Future<Stream> syn(SynInfo synInfo)
+    public Stream push(PushInfo pushInfo) throws InterruptedException, ExecutionException, TimeoutException
     {
-        Promise<Stream> result = new Promise<>();
-        syn(synInfo,0,TimeUnit.MILLISECONDS,result);
-        return result;
+        FuturePromise<Stream> result = new FuturePromise<>();
+        push(pushInfo, result);
+        if (pushInfo.getTimeout() > 0)
+            return result.get(pushInfo.getTimeout(), pushInfo.getUnit());
+        else
+            return result.get();
     }
 
     @Override
-    public void syn(SynInfo synInfo, long timeout, TimeUnit unit, Handler<Stream> handler)
+    public void push(PushInfo pushInfo, Promise<Stream> promise)
     {
+        notIdle();
         if (isClosed() || isReset())
         {
-            handler.failed(this, new StreamException(getId(), StreamStatus.STREAM_ALREADY_CLOSED));
+            close();
+            promise.failed(new StreamException(getId(), StreamStatus.STREAM_ALREADY_CLOSED,
+                    "Stream: " + this + " already closed or reset!"));
             return;
         }
-        PushSynInfo pushSynInfo = new PushSynInfo(getId(), synInfo);
-        session.syn(pushSynInfo, null, timeout, unit, handler);
+        PushSynInfo pushSynInfo = new PushSynInfo(getId(), pushInfo);
+        session.syn(pushSynInfo, null, new StreamPromise(promise));
     }
 
     @Override
-    public Future<Void> reply(ReplyInfo replyInfo)
+    public void reply(ReplyInfo replyInfo) throws InterruptedException, ExecutionException, TimeoutException
     {
-        Promise<Void> result = new Promise<>();
-        reply(replyInfo,0,TimeUnit.MILLISECONDS,result);
-        return result;
+        FutureCallback result = new FutureCallback();
+        reply(replyInfo, result);
+        if (replyInfo.getTimeout() > 0)
+            result.get(replyInfo.getTimeout(), replyInfo.getUnit());
+        else
+            result.get();
     }
 
     @Override
-    public void reply(ReplyInfo replyInfo, long timeout, TimeUnit unit, Handler<Void> handler)
+    public void reply(ReplyInfo replyInfo, Callback callback)
     {
+        notIdle();
         if (isUnidirectional())
+        {
+            close();
             throw new IllegalStateException("Protocol violation: cannot send SYN_REPLY frames in unidirectional streams");
+        }
         openState = OpenState.REPLY_SENT;
         updateCloseState(replyInfo.isClose(), true);
         SynReplyFrame frame = new SynReplyFrame(session.getVersion(), replyInfo.getFlags(), getId(), replyInfo.getHeaders());
-        session.control(this, frame, timeout, unit, handler, null);
+        session.control(this, frame, replyInfo.getTimeout(), replyInfo.getUnit(), new StreamCallback(callback));
     }
 
     @Override
-    public Future<Void> data(DataInfo dataInfo)
+    public void data(DataInfo dataInfo) throws InterruptedException, ExecutionException, TimeoutException
     {
-        Promise<Void> result = new Promise<>();
-        data(dataInfo,0,TimeUnit.MILLISECONDS,result);
-        return result;
+        FutureCallback result = new FutureCallback();
+        data(dataInfo, result);
+        if (dataInfo.getTimeout() > 0)
+            result.get(dataInfo.getTimeout(), dataInfo.getUnit());
+        else
+            result.get();
     }
 
     @Override
-    public void data(DataInfo dataInfo, long timeout, TimeUnit unit, Handler<Void> handler)
+    public void data(DataInfo dataInfo, Callback callback)
     {
+        notIdle();
         if (!canSend())
         {
-            session.rst(new RstInfo(getId(), StreamStatus.PROTOCOL_ERROR));
+            session.rst(new RstInfo(getId(), StreamStatus.PROTOCOL_ERROR), new StreamCallback());
             throw new IllegalStateException("Protocol violation: cannot send a DATA frame before a SYN_REPLY frame");
         }
         if (isLocallyClosed())
         {
-            session.rst(new RstInfo(getId(), StreamStatus.PROTOCOL_ERROR));
-            throw new IllegalStateException("Protocol violation: cannot send a DATA frame on a closed stream");
+            session.rst(new RstInfo(getId(), StreamStatus.PROTOCOL_ERROR), new StreamCallback());
+            throw new IllegalStateException("Protocol violation: cannot send a DATA frame on a locally closed stream");
         }
 
         // Cannot update the close state here, because the data that we send may
         // be flow controlled, so we need the stream to update the window size.
-        session.data(this, dataInfo, timeout, unit, handler, null);
+        session.data(this, dataInfo, dataInfo.getTimeout(), dataInfo.getUnit(), new StreamCallback(callback));
     }
 
     @Override
-    public Future<Void> headers(HeadersInfo headersInfo)
+    public void headers(HeadersInfo headersInfo) throws InterruptedException, ExecutionException, TimeoutException
     {
-        Promise<Void> result = new Promise<>();
-        headers(headersInfo,0,TimeUnit.MILLISECONDS,result);
-        return result;
+        FutureCallback result = new FutureCallback();
+        headers(headersInfo, result);
+        if (headersInfo.getTimeout() > 0)
+            result.get(headersInfo.getTimeout(), headersInfo.getUnit());
+        else
+            result.get();
     }
 
     @Override
-    public void headers(HeadersInfo headersInfo, long timeout, TimeUnit unit, Handler<Void> handler)
+    public void headers(HeadersInfo headersInfo, Callback callback)
     {
+        notIdle();
         if (!canSend())
         {
-            session.rst(new RstInfo(getId(), StreamStatus.PROTOCOL_ERROR));
+            session.rst(new RstInfo(getId(), StreamStatus.PROTOCOL_ERROR), new StreamCallback());
             throw new IllegalStateException("Protocol violation: cannot send a HEADERS frame before a SYN_REPLY frame");
         }
         if (isLocallyClosed())
         {
-            session.rst(new RstInfo(getId(), StreamStatus.PROTOCOL_ERROR));
+            session.rst(new RstInfo(getId(), StreamStatus.PROTOCOL_ERROR), new StreamCallback());
             throw new IllegalStateException("Protocol violation: cannot send a HEADERS frame on a closed stream");
         }
 
         updateCloseState(headersInfo.isClose(), true);
         HeadersFrame frame = new HeadersFrame(session.getVersion(), headersInfo.getFlags(), getId(), headersInfo.getHeaders());
-        session.control(this, frame, timeout, unit, handler, null);
+        session.control(this, frame, headersInfo.getTimeout(), headersInfo.getUnit(), new StreamCallback(callback));
     }
 
     @Override
@@ -447,7 +523,8 @@ public class StandardStream implements IStream
     @Override
     public String toString()
     {
-        return String.format("stream=%d v%d windowSize=%db reset=%s %s %s", getId(), session.getVersion(), getWindowSize(), isReset(), openState, closeState);
+        return String.format("stream=%d v%d windowSize=%d reset=%s prio=%d %s %s", getId(), session.getVersion(),
+                getWindowSize(), isReset(), priority, openState, closeState);
     }
 
     private boolean canSend()
@@ -471,4 +548,55 @@ public class StandardStream implements IStream
     {
         OPENED, LOCALLY_CLOSED, REMOTELY_CLOSED, CLOSED
     }
+
+    private class StreamCallback implements Callback
+    {
+        private final Callback callback;
+
+        private StreamCallback()
+        {
+            this(Callback.Adapter.INSTANCE);
+        }
+
+        private StreamCallback(Callback callback)
+        {
+            this.callback = callback;
+        }
+
+        @Override
+        public void succeeded()
+        {
+            callback.succeeded();
+        }
+
+        @Override
+        public void failed(Throwable x)
+        {
+            close();
+            callback.failed(x);
+        }
+    }
+
+    private class StreamPromise implements Promise<Stream>
+    {
+        private final Promise<Stream> promise;
+
+        public StreamPromise(Promise<Stream> promise)
+        {
+            this.promise = promise;
+        }
+
+        @Override
+        public void succeeded(Stream result)
+        {
+            promise.succeeded(result);
+        }
+
+        @Override
+        public void failed(Throwable x)
+        {
+            close();
+            promise.failed(x);
+        }
+    }
 }
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/ByteBufferDataInfo.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/ByteBufferDataInfo.java
index b7f8b47..0ac1bd5 100644
--- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/ByteBufferDataInfo.java
+++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/ByteBufferDataInfo.java
@@ -19,6 +19,7 @@
 package org.eclipse.jetty.spdy.api;
 
 import java.nio.ByteBuffer;
+import java.util.concurrent.TimeUnit;
 
 /**
  * <p>Specialized {@link DataInfo} for {@link ByteBuffer} content.</p>
@@ -30,12 +31,12 @@ public class ByteBufferDataInfo extends DataInfo
 
     public ByteBufferDataInfo(ByteBuffer buffer, boolean close)
     {
-        this(buffer, close, false);
+        this(0, TimeUnit.SECONDS, buffer, close);
     }
 
-    public ByteBufferDataInfo(ByteBuffer buffer, boolean close, boolean compress)
+    public ByteBufferDataInfo(long timeout, TimeUnit unit, ByteBuffer buffer, boolean close)
     {
-        super(close, compress);
+        super(timeout, unit, close);
         this.buffer = buffer;
         this.length = buffer.remaining();
     }
@@ -72,6 +73,16 @@ public class ByteBufferDataInfo extends DataInfo
     }
 
     @Override
+    public int readInto(byte[] bytes, int offset, int length)
+    {
+        int available = available();
+        if (available < length)
+            length = available;
+        buffer.get(bytes, offset, length);
+        return length;
+    }
+
+    @Override
     protected ByteBuffer allocate(int size)
     {
         return buffer.isDirect() ? ByteBuffer.allocateDirect(size) : super.allocate(size);
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/BytesDataInfo.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/BytesDataInfo.java
index 15d45d8..ba67279 100644
--- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/BytesDataInfo.java
+++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/BytesDataInfo.java
@@ -19,6 +19,7 @@
 package org.eclipse.jetty.spdy.api;
 
 import java.nio.ByteBuffer;
+import java.util.concurrent.TimeUnit;
 
 /**
  * <p>Specialized {@link DataInfo} for byte array content.</p>
@@ -32,12 +33,17 @@ public class BytesDataInfo extends DataInfo
 
     public BytesDataInfo(byte[] bytes, boolean close)
     {
-        this(bytes, 0, bytes.length, close);
+        this(0, TimeUnit.SECONDS, bytes, close);
     }
 
-    public BytesDataInfo(byte[] bytes, int offset, int length, boolean close)
+    public BytesDataInfo(long timeout, TimeUnit unit, byte[] bytes, boolean close)
     {
-        super(close, false);
+        this(timeout, unit, bytes, 0, bytes.length, close);
+    }
+
+    public BytesDataInfo(long timeout, TimeUnit unit, byte[] bytes, int offset, int length, boolean close)
+    {
+        super(timeout, unit, close);
         this.bytes = bytes;
         this.offset = offset;
         this.length = length;
@@ -65,4 +71,13 @@ public class BytesDataInfo extends DataInfo
         index += chunk;
         return chunk;
     }
+
+    @Override
+    public int readInto(byte[] bytes, int offset, int length)
+    {
+        int chunk = Math.min(available(), length);
+        System.arraycopy(this.bytes, index, bytes, offset, chunk);
+        index += chunk;
+        return chunk;
+    }
 }
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/DataInfo.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/DataInfo.java
index cf32fe6..067c9e7 100644
--- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/DataInfo.java
+++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/DataInfo.java
@@ -20,6 +20,7 @@ package org.eclipse.jetty.spdy.api;
 
 import java.nio.ByteBuffer;
 import java.nio.charset.Charset;
+import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
 
 /**
@@ -41,7 +42,7 @@ import java.util.concurrent.atomic.AtomicInteger;
  * <p>Consuming the data bytes can be done only via {@link #consumeInto(ByteBuffer)} or by a combination
  * of {@link #readInto(ByteBuffer)} and {@link #consume(int)} (possibly at different times).</p>
  */
-public abstract class DataInfo
+public abstract class DataInfo extends Info
 {
     /**
      * <p>Flag that indicates that this {@link DataInfo} is the last frame in the stream.</p>
@@ -50,17 +51,9 @@ public abstract class DataInfo
      * @see #getFlags()
      */
     public final static byte FLAG_CLOSE = 1;
-    /**
-     * <p>Flag that indicates that this {@link DataInfo}'s data is compressed.</p>
-     *
-     * @see #isCompress()
-     * @see #getFlags()
-     */
-    public final static byte FLAG_COMPRESS = 2;
 
     private final AtomicInteger consumed = new AtomicInteger();
     private boolean close;
-    private boolean compress;
 
     /**
      * <p>Creates a new {@link DataInfo} with the given close flag and no compression flag.</p>
@@ -73,33 +66,16 @@ public abstract class DataInfo
     }
 
     /**
-     * <p>Creates a new {@link DataInfo} with the given close flag and given compression flag.</p>
+     * <p>Creates a new {@link DataInfo} with the given close flag and no compression flag.</p>
      *
-     * @param close    the close flag
-     * @param compress the compress flag
-     */
-    public DataInfo(boolean close, boolean compress)
-    {
-        setClose(close);
-        setCompress(compress);
-    }
-
-    /**
-     * @return the value of the compress flag
-     * @see #setCompress(boolean)
-     */
-    public boolean isCompress()
-    {
-        return compress;
-    }
-
-    /**
-     * @param compress the value of the compress flag
-     * @see #isCompress()
+     * @param timeout
+     * @param unit
+     * @param close the value of the close flag
      */
-    public void setCompress(boolean compress)
+    protected DataInfo(long timeout, TimeUnit unit, boolean close)
     {
-        this.compress = compress;
+        super(timeout, unit);
+        this.close = close;
     }
 
     /**
@@ -123,13 +99,10 @@ public abstract class DataInfo
     /**
      * @return the close and compress flags as integer
      * @see #FLAG_CLOSE
-     * @see #FLAG_COMPRESS
      */
     public byte getFlags()
     {
-        byte flags = isClose() ? FLAG_CLOSE : 0;
-        flags |= isCompress() ? FLAG_COMPRESS : 0;
-        return flags;
+        return isClose() ? FLAG_CLOSE : 0;
     }
 
     /**
@@ -154,7 +127,7 @@ public abstract class DataInfo
      * then after the read {@link #available()} will return a positive value, and further content
      * may be retrieved by invoking again this method with a new output buffer.</p>
      *
-     * @param output the {@link ByteBuffer} to copy to bytes into
+     * @param output the {@link ByteBuffer} to copy the bytes into
      * @return the number of bytes copied
      * @see #available()
      * @see #consumeInto(ByteBuffer)
@@ -162,6 +135,19 @@ public abstract class DataInfo
     public abstract int readInto(ByteBuffer output);
 
     /**
+     * <p>Copies the content bytes of this {@link DataInfo} into the given byte array.</p>
+     * <p>If the given byte array cannot contain the whole content of this {@link DataInfo}
+     * then after the read {@link #available()} will return a positive value, and further content
+     * may be retrieved by invoking again this method with a new byte array.</p>
+     *
+     * @param bytes the byte array to copy the bytes into
+     * @param offset the index of the byte array to start copying
+     * @param length the number of bytes to copy
+     * @return the number of bytes copied
+     */
+    public abstract int readInto(byte[] bytes, int offset, int length);
+
+    /**
      * <p>Reads and consumes the content bytes of this {@link DataInfo} into the given {@link ByteBuffer}.</p>
      *
      * @param output the {@link ByteBuffer} to copy the bytes into
@@ -176,6 +162,22 @@ public abstract class DataInfo
     }
 
     /**
+     * <p>Reads and consumes the content bytes of this {@link DataInfo} into the given byte array,
+     * starting from index {@code offset} for {@code length} bytes.</p>
+     *
+     * @param bytes the byte array to copy the bytes into
+     * @param offset the offset of the byte array to start copying
+     * @param length the number of bytes to copy
+     * @return the number of bytes copied
+     */
+    public int consumeInto(byte[] bytes, int offset, int length)
+    {
+        int read = readInto(bytes, offset, length);
+        consume(read);
+        return read;
+    }
+
+    /**
      * <p>Consumes the given number of bytes from this {@link DataInfo}.</p>
      *
      * @param delta the number of bytes consumed
@@ -207,8 +209,19 @@ public abstract class DataInfo
      */
     public String asString(String charset, boolean consume)
     {
+        return asString(Charset.forName(charset), consume);
+    }
+
+    /**
+     *
+     * @param charset the charset used to convert the bytes
+     * @param consume whether to consume the content
+     * @return a String with the content of this {@link DataInfo}
+     */
+    public String asString(Charset charset, boolean consume)
+    {
         ByteBuffer buffer = asByteBuffer(consume);
-        return Charset.forName(charset).decode(buffer).toString();
+        return charset.decode(buffer).toString();
     }
 
     /**
@@ -246,6 +259,6 @@ public abstract class DataInfo
     @Override
     public String toString()
     {
-        return String.format("DATA @%x available=%d consumed=%d close=%b compress=%b", hashCode(), available(), consumed(), isClose(), isCompress());
+        return String.format("DATA @%x available=%d consumed=%d close=%b", hashCode(), available(), consumed(), isClose());
     }
 }
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/GoAwayInfo.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/GoAwayInfo.java
index 1b2ef61..80df510 100644
--- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/GoAwayInfo.java
+++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/GoAwayInfo.java
@@ -18,40 +18,21 @@
 
 package org.eclipse.jetty.spdy.api;
 
+import java.util.concurrent.TimeUnit;
+
 /**
- * <p>A container for GOAWAY frames metadata: the last good stream id and
- * the session status.</p>
+ * A GoAwayInfo container. Currently adding nothing to it's base class, but serves to keep the api unchanged in
+ * future versions when we need to pass more info to the methods having a {@link GoAwayInfo} parameter.
  */
-public class GoAwayInfo
+public class GoAwayInfo extends Info
 {
-    private final int lastStreamId;
-    private final SessionStatus sessionStatus;
-
-    /**
-     * <p>Creates a new {@link GoAwayInfo} with the given last good stream id and session status</p>
-     *
-     * @param lastStreamId  the last good stream id
-     * @param sessionStatus the session status
-     */
-    public GoAwayInfo(int lastStreamId, SessionStatus sessionStatus)
-    {
-        this.lastStreamId = lastStreamId;
-        this.sessionStatus = sessionStatus;
-    }
-
-    /**
-     * @return the last good stream id
-     */
-    public int getLastStreamId()
+    public GoAwayInfo(long timeout, TimeUnit unit)
     {
-        return lastStreamId;
+        super(timeout, unit);
     }
 
-    /**
-     * @return the session status
-     */
-    public SessionStatus getSessionStatus()
+    public GoAwayInfo()
     {
-        return sessionStatus;
+        super();
     }
 }
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/GoAwayResultInfo.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/GoAwayResultInfo.java
new file mode 100644
index 0000000..e07dc8f
--- /dev/null
+++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/GoAwayResultInfo.java
@@ -0,0 +1,57 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.spdy.api;
+
+/**
+ * <p>A container for GOAWAY frames metadata: the last good stream id and
+ * the session status.</p>
+ */
+public class GoAwayResultInfo
+{
+    private final int lastStreamId;
+    private final SessionStatus sessionStatus;
+
+    /**
+     * <p>Creates a new {@link GoAwayResultInfo} with the given last good stream id and session status</p>
+     *
+     * @param lastStreamId  the last good stream id
+     * @param sessionStatus the session status
+     */
+    public GoAwayResultInfo(int lastStreamId, SessionStatus sessionStatus)
+    {
+        this.lastStreamId = lastStreamId;
+        this.sessionStatus = sessionStatus;
+    }
+
+    /**
+     * @return the last good stream id
+     */
+    public int getLastStreamId()
+    {
+        return lastStreamId;
+    }
+
+    /**
+     * @return the session status
+     */
+    public SessionStatus getSessionStatus()
+    {
+        return sessionStatus;
+    }
+}
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/Handler.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/Handler.java
deleted file mode 100644
index 4e2ab33..0000000
--- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/Handler.java
+++ /dev/null
@@ -1,61 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.spdy.api;
-
-/**
- * <p>A callback abstraction that handles completed/failed events of asynchronous operations.</p>
- * <p>Instances of this class capture a context that is made available on the completion callback.</p>
- *
- * @param <C> the type of the context object
- */
-public interface Handler<C>
-{
-    /**
-     * <p>Callback invoked when the operation completes.</p>
-     *
-     * @param context the context
-     * @see #failed(Object, Throwable)
-     */
-    public abstract void completed(C context);
-
-    /**
-     * <p>Callback invoked when the operation fails.</p>
-     * @param context the context
-     * @param x the reason for the operation failure
-     */
-    public void failed(C context, Throwable x);
-
-    /**
-     * <p>Empty implementation of {@link Handler}</p>
-     *
-     * @param <C> the type of the context object
-     */
-    public static class Adapter<C> implements Handler<C>
-    {
-        @Override
-        public void completed(C context)
-        {
-        }
-
-        @Override
-        public void failed(C context, Throwable x)
-        {
-        }
-    }
-}
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/Headers.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/Headers.java
deleted file mode 100644
index d71af34..0000000
--- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/Headers.java
+++ /dev/null
@@ -1,305 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.spdy.api;
-
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.LinkedHashMap;
-import java.util.LinkedHashSet;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * <p>A container for name/value pairs, known as headers.</p>
- * <p>A {@link Header} is composed of a case-insensitive name string and
- * of a case-sensitive set of value strings.</p>
- * <p>The implementation of this class is not thread safe.</p>
- */
-public class Headers implements Iterable<Headers.Header>
-{
-    private final Map<String, Header> headers;
-
-    /**
-     * <p>Creates an empty modifiable {@link Headers} instance.</p>
-     * @see #Headers(Headers, boolean)
-     */
-    public Headers()
-    {
-        headers = new LinkedHashMap<>();
-    }
-
-    /**
-     * <p>Creates a {@link Headers} instance by copying the headers from the given
-     * {@link Headers} and making it (im)mutable depending on the given {@code immutable} parameter</p>
-     *
-     * @param original the {@link Headers} to copy headers from
-     * @param immutable whether this instance is immutable
-     */
-    public Headers(Headers original, boolean immutable)
-    {
-        Map<String, Header> copy = new LinkedHashMap<>();
-        copy.putAll(original.headers);
-        headers = immutable ? Collections.unmodifiableMap(copy) : copy;
-    }
-
-    @Override
-    public boolean equals(Object obj)
-    {
-        if (this == obj)
-            return true;
-        if (obj == null || getClass() != obj.getClass())
-            return false;
-        Headers that = (Headers)obj;
-        return headers.equals(that.headers);
-    }
-
-    @Override
-    public int hashCode()
-    {
-        return headers.hashCode();
-    }
-
-    /**
-     * @return a set of header names
-     */
-    public Set<String> names()
-    {
-        Set<String> result = new LinkedHashSet<>();
-        for (Header header : headers.values())
-            result.add(header.name);
-        return result;
-    }
-
-    /**
-     * @param name the header name
-     * @return the {@link Header} with the given name, or null if no such header exists
-     */
-    public Header get(String name)
-    {
-        return headers.get(name.trim().toLowerCase(Locale.ENGLISH));
-    }
-
-    /**
-     * <p>Inserts or replaces the given name/value pair as a single-valued {@link Header}.</p>
-     *
-     * @param name the header name
-     * @param value the header value
-     */
-    public void put(String name, String value)
-    {
-        name = name.trim();
-        Header header = new Header(name, value.trim());
-        headers.put(name.toLowerCase(Locale.ENGLISH), header);
-    }
-
-    /**
-     * <p>Inserts or replaces the given {@link Header}, mapped to the {@link Header#name() header's name}</p>
-     *
-     * @param header the header to add
-     */
-    public void put(Header header)
-    {
-        if (header != null)
-            headers.put(header.name().toLowerCase(Locale.ENGLISH), header);
-    }
-
-    /**
-     * <p>Adds the given value to a header with the given name, creating a {@link Header} is none exists
-     * for the given name.</p>
-     *
-     * @param name the header name
-     * @param value the header value to add
-     */
-    public void add(String name, String value)
-    {
-        name = name.trim();
-        Header header = headers.get(name.toLowerCase(Locale.ENGLISH));
-        if (header == null)
-        {
-            header = new Header(name, value.trim());
-            headers.put(name.toLowerCase(Locale.ENGLISH), header);
-        }
-        else
-        {
-            header = new Header(header.name(), header.value() + "," + value.trim());
-            headers.put(name.toLowerCase(Locale.ENGLISH), header);
-        }
-    }
-
-    /**
-     * <p>Removes the {@link Header} with the given name</p>
-     *
-     * @param name the name of the header to remove
-     * @return the removed header, or null if no such header existed
-     */
-    public Header remove(String name)
-    {
-        name = name.trim();
-        return headers.remove(name.toLowerCase(Locale.ENGLISH));
-    }
-
-    /**
-     * <p>Empties this {@link Headers} instance from all headers</p>
-     * @see #isEmpty()
-     */
-    public void clear()
-    {
-        headers.clear();
-    }
-
-    /**
-     * @return whether this {@link Headers} instance is empty
-     */
-    public boolean isEmpty()
-    {
-        return headers.isEmpty();
-    }
-
-    /**
-     * @return the number of headers
-     */
-    public int size()
-    {
-        return headers.size();
-    }
-
-    /**
-     * @return an iterator over the {@link Header} present in this instance
-     */
-    @Override
-    public Iterator<Header> iterator()
-    {
-        return headers.values().iterator();
-    }
-
-    @Override
-    public String toString()
-    {
-        return headers.toString();
-    }
-
-    /**
-     * <p>A named list of string values.</p>
-     * <p>The name is case-sensitive and there must be at least one value.</p>
-     */
-    public static class Header
-    {
-        private final String name;
-        private final String[] values;
-
-        private Header(String name, String value, String... values)
-        {
-            this.name = name;
-            this.values = new String[values.length + 1];
-            this.values[0] = value;
-            if (values.length > 0)
-                System.arraycopy(values, 0, this.values, 1, values.length);
-        }
-
-        @Override
-        public boolean equals(Object obj)
-        {
-            if (this == obj)
-                return true;
-            if (obj == null || getClass() != obj.getClass())
-                return false;
-            Header that = (Header)obj;
-            // Header names must be lowercase, thus we lowercase them before transmission, but keep them as is
-            // internally. That's why we've to compare them case insensitive.
-            return name.equalsIgnoreCase(that.name) && Arrays.equals(values, that.values);
-        }
-
-        @Override
-        public int hashCode()
-        {
-            int result = name.toLowerCase(Locale.ENGLISH).hashCode();
-            result = 31 * result + Arrays.hashCode(values);
-            return result;
-        }
-
-        /**
-         * @return the header's name
-         */
-        public String name()
-        {
-            return name;
-        }
-
-        /**
-         * @return the first header's value
-         */
-        public String value()
-        {
-            return values[0];
-        }
-
-        /**
-         * <p>Attempts to convert the result of {@link #value()} to an integer,
-         * returning it if the conversion is successful; returns null if the
-         * result of {@link #value()} is null.</p>
-         *
-         * @return the result of {@link #value()} converted to an integer, or null
-         * @throws NumberFormatException if the conversion fails
-         */
-        public Integer valueAsInt()
-        {
-            final String value = value();
-            return value == null ? null : Integer.valueOf(value);
-        }
-
-        /**
-         * @return the header's values
-         */
-        public String[] values()
-        {
-            return values;
-        }
-
-        /**
-         * @return the values as a comma separated list
-         */
-        public String valuesAsString()
-        {
-            StringBuilder result = new StringBuilder();
-            for (int i = 0; i < values.length; ++i)
-            {
-                if (i > 0)
-                    result.append(", ");
-                result.append(values[i]);
-            }
-            return result.toString();
-        }
-
-        /**
-         * @return whether the header has multiple values
-         */
-        public boolean hasMultipleValues()
-        {
-            return values.length > 1;
-        }
-
-        @Override
-        public String toString()
-        {
-            return Arrays.toString(values);
-        }
-    }
-}
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/HeadersInfo.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/HeadersInfo.java
index dc245c8..65c0ffb 100644
--- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/HeadersInfo.java
+++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/HeadersInfo.java
@@ -18,10 +18,14 @@
 
 package org.eclipse.jetty.spdy.api;
 
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.util.Fields;
+
 /**
  * <p>A container for HEADERS frame metadata and headers.</p>
  */
-public class HeadersInfo
+public class HeadersInfo extends Info
 {
     /**
      * <p>Flag that indicates that this {@link HeadersInfo} is the last frame in the stream.</p>
@@ -40,29 +44,29 @@ public class HeadersInfo
 
     private final boolean close;
     private final boolean resetCompression;
-    private final Headers headers;
+    private final Fields headers;
 
     /**
-     * <p>Creates a new {@link HeadersInfo} instance with the given headers,
-     * the given close flag and no reset compression flag</p>
+     * <p>Creates a new {@link HeadersInfo} instance with the given headers, the given close flag and no reset
+     * compression flag</p>
      *
-     * @param headers the {@link Headers}
-     * @param close the value of the close flag
+     * @param headers the {@link Fields}
+     * @param close   the value of the close flag
      */
-    public HeadersInfo(Headers headers, boolean close)
+    public HeadersInfo(Fields headers, boolean close)
     {
         this(headers, close, false);
     }
 
     /**
-     * <p>Creates a new {@link HeadersInfo} instance with the given headers,
-     * the given close flag and the given reset compression flag</p>
+     * <p>Creates a new {@link HeadersInfo} instance with the given headers, the given close flag and the given reset
+     * compression flag</p>
      *
-     * @param headers the {@link Headers}
-     * @param close the value of the close flag
+     * @param headers          the {@link Fields}
+     * @param close            the value of the close flag
      * @param resetCompression the value of the reset compression flag
      */
-    public HeadersInfo(Headers headers, boolean close, boolean resetCompression)
+    public HeadersInfo(Fields headers, boolean close, boolean resetCompression)
     {
         this.headers = headers;
         this.close = close;
@@ -70,6 +74,24 @@ public class HeadersInfo
     }
 
     /**
+     * <p>Creates a new {@link HeadersInfo} instance with the given headers, the given close flag and the given reset
+     * compression flag</p>
+     *
+     * @param timeout          the operation's timeout
+     * @param unit             the timeout's unit
+     * @param headers          the {@link Fields}
+     * @param close            the value of the close flag
+     * @param resetCompression the value of the reset compression flag
+     */
+    public HeadersInfo(long timeout, TimeUnit unit, boolean close, boolean resetCompression, Fields headers)
+    {
+        super(timeout, unit);
+        this.close = close;
+        this.resetCompression = resetCompression;
+        this.headers = headers;
+    }
+
+    /**
      * @return the value of the close flag
      */
     public boolean isClose()
@@ -86,9 +108,9 @@ public class HeadersInfo
     }
 
     /**
-     * @return the {@link Headers}
+     * @return the {@link Fields}
      */
-    public Headers getHeaders()
+    public Fields getHeaders()
     {
         return headers;
     }
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/Info.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/Info.java
new file mode 100644
index 0000000..5eca93e
--- /dev/null
+++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/Info.java
@@ -0,0 +1,52 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.spdy.api;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A base class for all *Info classes providing timeout and unit and api to access them
+ */
+public class Info
+{
+    private final long timeout;
+    private final TimeUnit unit;
+
+    public Info(long timeout, TimeUnit unit)
+    {
+        this.timeout = timeout;
+        this.unit = unit;
+    }
+
+    public Info()
+    {
+        timeout = 0;
+        unit = TimeUnit.SECONDS;
+    }
+
+    public long getTimeout()
+    {
+        return timeout;
+    }
+
+    public TimeUnit getUnit()
+    {
+        return unit;
+    }
+}
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/PingInfo.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/PingInfo.java
index 6155ddc..322d987 100644
--- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/PingInfo.java
+++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/PingInfo.java
@@ -18,27 +18,21 @@
 
 package org.eclipse.jetty.spdy.api;
 
+import java.util.concurrent.TimeUnit;
+
 /**
- * <p>A container for PING frames data.</p>
+ * A PingInfo container. Currently adding nothing to it's base class, but serves to keep the api unchanged in
+ * future versions when we need to pass more info to the methods having a {@link PingInfo} parameter.
  */
-public class PingInfo
+public class PingInfo extends Info
 {
-    private final int pingId;
-
-    /**
-     * <p>Creates a {@link PingInfo} with the given ping id</p>
-     * @param pingId the ping id
-     */
-    public PingInfo(int pingId)
+    public PingInfo(long timeout, TimeUnit unit)
     {
-        this.pingId = pingId;
+        super(timeout, unit);
     }
 
-    /**
-     * @return the ping id
-     */
-    public int getPingId()
+    public PingInfo()
     {
-        return pingId;
+        this(0, TimeUnit.SECONDS);
     }
 }
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/PingResultInfo.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/PingResultInfo.java
new file mode 100644
index 0000000..d4f2031
--- /dev/null
+++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/PingResultInfo.java
@@ -0,0 +1,44 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.spdy.api;
+
+/**
+ * <p>A container for PING frames data.</p>
+ */
+public class PingResultInfo
+{
+    private final int pingId;
+
+    /**
+     * <p>Creates a {@link PingResultInfo} with the given ping id</p>
+     * @param pingId the ping id
+     */
+    public PingResultInfo(int pingId)
+    {
+        this.pingId = pingId;
+    }
+
+    /**
+     * @return the ping id
+     */
+    public int getPingId()
+    {
+        return pingId;
+    }
+}
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/PushInfo.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/PushInfo.java
new file mode 100644
index 0000000..4654475
--- /dev/null
+++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/PushInfo.java
@@ -0,0 +1,101 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.spdy.api;
+
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.util.Fields;
+
+/**
+ * <p>A container for PUSH_SYN_STREAM frames metadata and data.</p>
+ */
+public class PushInfo extends Info
+{
+    /**
+     * <p>Flag that indicates that this {@link PushInfo} is the last frame in the stream.</p>
+     *
+     * @see #isClose()
+     * @see #getFlags()
+     */
+    public static final byte FLAG_CLOSE = 1;
+
+    private final boolean close;
+    private final Fields headers;
+
+    /**
+     * <p>Creates a {@link PushInfo} instance with the given headers and the given close flag,
+     * not unidirectional, without associated stream, and with default priority.</p>
+     *
+     * @param headers the {@link Fields}
+     * @param close the value of the close flag
+     */
+    public PushInfo(Fields headers, boolean close)
+    {
+        this(0, TimeUnit.SECONDS, headers, close);
+        // either builder or setters for timeout
+    }
+
+    /**
+     * <p>
+     * Creates a {@link PushInfo} instance with the given headers, the given close flag and with the given priority.
+     * </p>
+     * @param timeout the timeout value
+     * @param unit the TimeUnit of the timeout
+     * @param headers
+ *            the {@link Fields}
+     * @param close
+     */
+    public PushInfo(long timeout, TimeUnit unit, Fields headers, boolean close)
+    {
+        super(timeout, unit);
+        this.close = close;
+        this.headers = headers;
+    }
+
+    /**
+     * @return the value of the close flag
+     */
+    public boolean isClose()
+    {
+        return close;
+    }
+
+    /**
+     * @return the {@link Fields}
+     */
+    public Fields getHeaders()
+    {
+        return headers;
+    }
+
+    /**
+     * @return the close flag as integer
+     * @see #FLAG_CLOSE
+     */
+    public byte getFlags()
+    {
+        return isClose() ? FLAG_CLOSE : 0;
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("SYN push close=%b headers=%s", close, headers);
+    }
+}
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/ReplyInfo.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/ReplyInfo.java
index 196333d..e316954 100644
--- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/ReplyInfo.java
+++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/ReplyInfo.java
@@ -18,10 +18,14 @@
 
 package org.eclipse.jetty.spdy.api;
 
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.util.Fields;
+
 /**
  * <p>A container for SYN_REPLY frames metadata and headers.</p>
  */
-public class ReplyInfo
+public class ReplyInfo extends Info
 {
     /**
      * <p>Flag that indicates that this {@link ReplyInfo} is the last frame in the stream.</p>
@@ -31,7 +35,7 @@ public class ReplyInfo
      */
     public static final byte FLAG_CLOSE = 1;
 
-    private final Headers headers;
+    private final Fields headers;
     private final boolean close;
 
     /**
@@ -41,25 +45,39 @@ public class ReplyInfo
      */
     public ReplyInfo(boolean close)
     {
-        this(new Headers(), close);
+        this(new Fields(), close);
     }
 
     /**
      * <p>Creates a {@link ReplyInfo} instance with the given headers and the given close flag.</p>
      *
-     * @param headers the {@link Headers}
-     * @param close the value of the close flag
+     * @param headers the {@link Fields}
+     * @param close   the value of the close flag
+     */
+    public ReplyInfo(Fields headers, boolean close)
+    {
+        this(0, TimeUnit.SECONDS, headers, close);
+    }
+
+    /**
+     * <p>Creates a {@link ReplyInfo} instance with the given headers and the given close flag.</p>
+     *
+     * @param timeout the timeout
+     * @param unit    the time unit for the timeout
+     * @param headers the {@link Fields}
+     * @param close   the value of the close flag
      */
-    public ReplyInfo(Headers headers, boolean close)
+    public ReplyInfo(long timeout, TimeUnit unit, Fields headers, boolean close)
     {
+        super(timeout, unit);
         this.headers = headers;
         this.close = close;
     }
 
     /**
-     * @return the {@link Headers}
+     * @return the {@link Fields}
      */
-    public Headers getHeaders()
+    public Fields getHeaders()
     {
         return headers;
     }
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/RstInfo.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/RstInfo.java
index 5919a10..3b84cd1 100644
--- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/RstInfo.java
+++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/RstInfo.java
@@ -18,10 +18,12 @@
 
 package org.eclipse.jetty.spdy.api;
 
+import java.util.concurrent.TimeUnit;
+
 /**
  * <p>A container for RST_STREAM frames data: the stream id and the stream status.</p>
  */
-public class RstInfo
+public class RstInfo extends Info
 {
     private final int streamId;
     private final StreamStatus streamStatus;
@@ -29,16 +31,30 @@ public class RstInfo
     /**
      * <p>Creates a new {@link RstInfo} with the given stream id and stream status</p>
      *
-     * @param streamId  the stream id
+     * @param timeout      the operation's timeout
+     * @param unit         the timeout's unit
+     * @param streamId     the stream id
      * @param streamStatus the stream status
      */
-    public RstInfo(int streamId, StreamStatus streamStatus)
+    public RstInfo(long timeout, TimeUnit unit, int streamId, StreamStatus streamStatus)
     {
+        super(timeout, unit);
         this.streamId = streamId;
         this.streamStatus = streamStatus;
     }
 
     /**
+     * <p>Creates a new {@link RstInfo} with the given stream id and stream status</p>
+     *
+     * @param streamId
+     * @param streamStatus
+     */
+    public RstInfo(int streamId, StreamStatus streamStatus)
+    {
+        this(0, TimeUnit.SECONDS, streamId, streamStatus);
+    }
+
+    /**
      * @return the stream id
      */
     public int getStreamId()
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/Session.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/Session.java
index 494c853..47da10a 100644
--- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/Session.java
+++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/Session.java
@@ -18,10 +18,14 @@
 
 package org.eclipse.jetty.spdy.api;
 
+import java.net.InetSocketAddress;
 import java.util.EventListener;
 import java.util.Set;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.Promise;
 
 /**
  * <p>A {@link Session} represents the client-side endpoint of a SPDY connection to a single origin server.</p>
@@ -29,7 +33,7 @@ import java.util.concurrent.TimeUnit;
  * <pre>
  * Session session = ...;
  * SynInfo synInfo = new SynInfo(true);
- * session.syn(synInfo, new Stream.FrameListener.Adapter()
+ * session.push(synInfo, new Stream.FrameListener.Adapter()
  * {
  *     public void onReply(Stream stream, ReplyInfo replyInfo)
  *     {
@@ -67,119 +71,105 @@ public interface Session
     public void removeListener(Listener listener);
 
     /**
-     * <p>Sends asynchronously a SYN_FRAME to create a new {@link Stream SPDY stream}.</p>
-     * <p>Callers may use the returned future to wait for the stream to be created, and
-     * use the stream, for example, to send data frames.</p>
+     * <p>Sends a SYN_FRAME to create a new {@link Stream SPDY stream}.</p>
+     * <p>Callers may use the returned Stream for example, to send data frames.</p>
      *
      * @param synInfo  the metadata to send on stream creation
      * @param listener the listener to invoke when events happen on the stream just created
-     * @return a future for the stream that will be created
-     * @see #syn(SynInfo, StreamFrameListener, long, TimeUnit, Handler)
+     * @return the stream that will be created
+     * @see #syn(SynInfo, StreamFrameListener, Promise)
      */
-    public Future<Stream> syn(SynInfo synInfo, StreamFrameListener listener);
+    public Stream syn(SynInfo synInfo, StreamFrameListener listener) throws ExecutionException, InterruptedException, TimeoutException;
 
     /**
      * <p>Sends asynchronously a SYN_FRAME to create a new {@link Stream SPDY stream}.</p>
-     * <p>Callers may pass a non-null completion handler to be notified of when the
+     * <p>Callers may pass a non-null completion callback to be notified of when the
      * stream has been created and use the stream, for example, to send data frames.</p>
      *
+     *
      * @param synInfo  the metadata to send on stream creation
      * @param listener the listener to invoke when events happen on the stream just created
-     * @param timeout  the operation's timeout
-     * @param unit     the timeout's unit
-     * @param handler  the completion handler that gets notified of stream creation
+     * @param promise  the completion callback that gets notified of stream creation
      * @see #syn(SynInfo, StreamFrameListener)
      */
-    public void syn(SynInfo synInfo, StreamFrameListener listener, long timeout, TimeUnit unit, Handler<Stream> handler);
-
+    public void syn(SynInfo synInfo, StreamFrameListener listener, Promise<Stream> promise);
 
     /**
-     * <p>Sends asynchronously a RST_STREAM to abort a stream.</p>
-     * <p>Callers may use the returned future to wait for the reset to be sent.</p>
+     * <p>Sends synchronously a RST_STREAM to abort a stream.</p>
      *
      * @param rstInfo the metadata to reset the stream
-     * @return a future to wait for the reset to be sent
-     * @see #rst(RstInfo, long, TimeUnit, Handler)
+     * @see #rst(RstInfo, Callback)
      */
-    public Future<Void> rst(RstInfo rstInfo);
+    public void rst(RstInfo rstInfo) throws InterruptedException, ExecutionException, TimeoutException;
 
     /**
      * <p>Sends asynchronously a RST_STREAM to abort a stream.</p>
-     * <p>Callers may pass a non-null completion handler to be notified of when the
+     * <p>Callers may pass a non-null completion callback to be notified of when the
      * reset has been actually sent.</p>
      *
      * @param rstInfo the metadata to reset the stream
-     * @param timeout  the operation's timeout
-     * @param unit     the timeout's unit
-     * @param handler the completion handler that gets notified of reset's send
+     * @param callback the completion callback that gets notified of reset's send
      * @see #rst(RstInfo)
      */
-    public void rst(RstInfo rstInfo, long timeout, TimeUnit unit, Handler<Void> handler);
+    public void rst(RstInfo rstInfo, Callback callback);
 
     /**
-     * <p>Sends asynchronously a SETTINGS to configure the SPDY connection.</p>
-     * <p>Callers may use the returned future to wait for the settings to be sent.</p>
+     * <p>Sends synchronously a SETTINGS to configure the SPDY connection.</p>
      *
      * @param settingsInfo the metadata to send
-     * @return a future to wait for the settings to be sent
-     * @see #settings(SettingsInfo, long, TimeUnit, Handler)
+     * @see #settings(SettingsInfo, Callback)
      */
-    public Future<Void> settings(SettingsInfo settingsInfo);
+    public void settings(SettingsInfo settingsInfo) throws ExecutionException, InterruptedException, TimeoutException;
 
     /**
      * <p>Sends asynchronously a SETTINGS to configure the SPDY connection.</p>
-     * <p>Callers may pass a non-null completion handler to be notified of when the
+     * <p>Callers may pass a non-null completion callback to be notified of when the
      * settings has been actually sent.</p>
      *
+     *
      * @param settingsInfo the metadata to send
-     * @param timeout  the operation's timeout
-     * @param unit     the timeout's unit
-     * @param handler      the completion handler that gets notified of settings' send
+     * @param callback      the completion callback that gets notified of settings' send
      * @see #settings(SettingsInfo)
      */
-    public void settings(SettingsInfo settingsInfo, long timeout, TimeUnit unit, Handler<Void> handler);
+    public void settings(SettingsInfo settingsInfo, Callback callback);
 
     /**
-     * <p>Sends asynchronously a PING, normally to measure round-trip time.</p>
-     * <p>Callers may use the returned future to wait for the ping to be sent.</p>
+     * <p>Sends synchronously a PING, normally to measure round-trip time.</p>
      *
-     * @return a future for the metadata sent
-     * @see #ping(long, TimeUnit, Handler)
+     * @see #ping(PingInfo, Promise)
+     * @param pingInfo
      */
-    public Future<PingInfo> ping();
+    public PingResultInfo ping(PingInfo pingInfo) throws ExecutionException, InterruptedException, TimeoutException;
 
     /**
      * <p>Sends asynchronously a PING, normally to measure round-trip time.</p>
-     * <p>Callers may pass a non-null completion handler to be notified of when the
+     * <p>Callers may pass a non-null completion callback to be notified of when the
      * ping has been actually sent.</p>
      *
-     * @param timeout  the operation's timeout
-     * @param unit     the timeout's unit
-     * @param handler the completion handler that gets notified of ping's send
-     * @see #ping()
+     * @param pingInfo
+     * @param promise the completion callback that gets notified of ping's send
+     * @see #ping(PingInfo)
      */
-    public void ping(long timeout, TimeUnit unit, Handler<PingInfo> handler);
+    public void ping(PingInfo pingInfo, Promise<PingResultInfo> promise);
 
     /**
      * <p>Closes gracefully this session, sending a GO_AWAY frame and then closing the TCP connection.</p>
-     * <p>Callers may use the returned future to wait for the go away to be sent.</p>
      *
-     * @return a future to wait for the go away to be sent
-     * @see #goAway(long, TimeUnit, Handler)
+     * @see #goAway(GoAwayInfo, Callback)
+     * @param goAwayInfo
      */
-    public Future<Void> goAway();
+    public void goAway(GoAwayInfo goAwayInfo) throws ExecutionException, InterruptedException, TimeoutException;
 
     /**
      * <p>Closes gracefully this session, sending a GO_AWAY frame and then closing the TCP connection.</p>
-     * <p>Callers may pass a non-null completion handler to be notified of when the
+     * <p>Callers may pass a non-null completion callback to be notified of when the
      * go away has been actually sent.</p>
      *
-     * @param timeout  the operation's timeout
-     * @param unit     the timeout's unit
-     * @param handler the completion handler that gets notified of go away's send
-     * @see #goAway()
+     * @param goAwayInfo
+     * @param callback the completion callback that gets notified of go away's send
+     * @see #goAway(GoAwayInfo)
      */
-    public void goAway(long timeout, TimeUnit unit, Handler<Void> handler);
+    public void goAway(GoAwayInfo goAwayInfo, Callback callback);
 
     /**
      * @return a snapshot of the streams currently active in this session
@@ -217,6 +207,16 @@ public interface Session
     public Object removeAttribute(String key);
 
     /**
+     * @return the local address of the underlying endpoint
+     */
+    public InetSocketAddress getLocalAddress();
+
+    /**
+     * @return the remote address of the underlying endpoint
+     */
+    public InetSocketAddress getRemoteAddress();
+
+    /**
      * <p>Super interface for listeners with callbacks that are invoked on specific session events.</p>
      */
     public interface Listener extends EventListener
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/SessionFrameListener.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/SessionFrameListener.java
index 22e2e78..02382f2 100644
--- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/SessionFrameListener.java
+++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/SessionFrameListener.java
@@ -36,7 +36,7 @@ public interface SessionFrameListener extends EventListener
      * <p>Application code should implement this method and reply to the stream creation, eventually
      * sending data:</p>
      * <pre>
-     * public Stream.FrameListener onSyn(Stream stream, SynInfo synInfo)
+     * public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
      * {
      *     // Do something with the metadata contained in synInfo
      *
@@ -52,7 +52,7 @@ public interface SessionFrameListener extends EventListener
      * </pre>
      * <p>Alternatively, if the stream creation requires reading data sent from the other peer:</p>
      * <pre>
-     * public Stream.FrameListener onSyn(Stream stream, SynInfo synInfo)
+     * public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
      * {
      *     // Do something with the metadata contained in synInfo
      *
@@ -98,26 +98,28 @@ public interface SessionFrameListener extends EventListener
      * <p>Callback invoked when a ping request has completed its round-trip.</p>
      *
      * @param session the session
-     * @param pingInfo the metadata received
+     * @param pingResultInfo the metadata received
      */
-    public void onPing(Session session, PingInfo pingInfo);
+    public void onPing(Session session, PingResultInfo pingResultInfo);
 
     /**
      * <p>Callback invoked when the other peer signals that it is closing the connection.</p>
      *
      * @param session the session
-     * @param goAwayInfo the metadata sent
+     * @param goAwayResultInfo the metadata sent
      */
-    public void onGoAway(Session session, GoAwayInfo goAwayInfo);
+    public void onGoAway(Session session, GoAwayResultInfo goAwayResultInfo);
 
     /**
      * <p>Callback invoked when an exception is thrown during the processing of an event on a
      * SPDY session.</p>
      * <p>Examples of such conditions are invalid frames received, corrupted headers compression state, etc.</p>
      *
+     * @param session the session
      * @param x the exception that caused the event processing failure
      */
-    public void onException(Throwable x);
+    public void onFailure(Session session, Throwable x);
+
 
     /**
      * <p>Empty implementation of {@link SessionFrameListener}</p>
@@ -143,17 +145,17 @@ public interface SessionFrameListener extends EventListener
         }
 
         @Override
-        public void onPing(Session session, PingInfo pingInfo)
+        public void onPing(Session session, PingResultInfo pingResultInfo)
         {
         }
 
         @Override
-        public void onGoAway(Session session, GoAwayInfo goAwayInfo)
+        public void onGoAway(Session session, GoAwayResultInfo goAwayResultInfo)
         {
         }
 
         @Override
-        public void onException(Throwable x)
+        public void onFailure(Session session, Throwable x)
         {
             logger.info("", x);
         }
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/Settings.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/Settings.java
index 8ed38e7..591dbb7 100644
--- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/Settings.java
+++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/Settings.java
@@ -25,10 +25,11 @@ import java.util.Map;
 
 public class Settings implements Iterable<Settings.Setting>
 {
-    private Map<ID, Settings.Setting> settings = new HashMap<>();
+    private final Map<ID, Settings.Setting> settings;
 
     public Settings()
     {
+        settings = new HashMap<>();
     }
 
     public Settings(Settings original, boolean immutable)
@@ -94,13 +95,13 @@ public class Settings implements Iterable<Settings.Setting>
 
     public static final class ID
     {
-        public static ID UPLOAD_BANDWIDTH = new ID(1);
-        public static ID DOWNLOAD_BANDWIDTH = new ID(2);
-        public static ID ROUND_TRIP_TIME = new ID(3);
-        public static ID MAX_CONCURRENT_STREAMS = new ID(4);
-        public static ID CURRENT_CONGESTION_WINDOW = new ID(5);
-        public static ID DOWNLOAD_RETRANSMISSION_RATE = new ID(6);
-        public static ID INITIAL_WINDOW_SIZE = new ID(7);
+        public static final ID UPLOAD_BANDWIDTH = new ID(1);
+        public static final ID DOWNLOAD_BANDWIDTH = new ID(2);
+        public static final ID ROUND_TRIP_TIME = new ID(3);
+        public static final ID MAX_CONCURRENT_STREAMS = new ID(4);
+        public static final ID CURRENT_CONGESTION_WINDOW = new ID(5);
+        public static final ID DOWNLOAD_RETRANSMISSION_RATE = new ID(6);
+        public static final ID INITIAL_WINDOW_SIZE = new ID(7);
 
         public synchronized static ID from(int code)
         {
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/SettingsInfo.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/SettingsInfo.java
index e6ada49..43bc2a8 100644
--- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/SettingsInfo.java
+++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/SettingsInfo.java
@@ -18,7 +18,9 @@
 
 package org.eclipse.jetty.spdy.api;
 
-public class SettingsInfo
+import java.util.concurrent.TimeUnit;
+
+public class SettingsInfo extends Info
 {
     public static final byte CLEAR_PERSISTED = 1;
 
@@ -27,15 +29,21 @@ public class SettingsInfo
 
     public SettingsInfo(Settings settings)
     {
-        this(settings, false);
+        this(0, TimeUnit.SECONDS, settings, false);
     }
 
-    public SettingsInfo(Settings settings, boolean clearPersisted)
+    public SettingsInfo(long timeout, TimeUnit unit, Settings settings, boolean clearPersisted)
     {
+        super(timeout, unit);
         this.settings = settings;
         this.clearPersisted = clearPersisted;
     }
 
+    public SettingsInfo(Settings settings, boolean clearPersisted)
+    {
+        this(0, TimeUnit.SECONDS, settings, clearPersisted);
+    }
+
     public boolean isClearPersisted()
     {
         return clearPersisted;
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/Stream.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/Stream.java
index 835a971..ced832e 100644
--- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/Stream.java
+++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/Stream.java
@@ -20,38 +20,35 @@ package org.eclipse.jetty.spdy.api;
 
 import java.nio.channels.WritePendingException;
 import java.util.Set;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.Promise;
 
 /**
- * <p>A {@link Stream} represents a bidirectional exchange of data on top of a {@link Session}.</p>
- * <p>Differently from socket streams, where the input and output streams are permanently associated
- * with the socket (and hence with the connection that the socket represents), there can be multiple
- * SPDY streams for a SPDY session.</p>
- * <p>SPDY streams may terminate without this implying that the SPDY session is terminated.</p>
- * <p>If SPDY is used to transport the HTTP protocol, then a SPDY stream maps to a HTTP request/response
- * cycle, and after the request/response cycle is completed, the stream is closed, and other streams
- * may be opened. Differently from HTTP, though, multiple SPDY streams may be opened concurrently
- * on the same SPDY session.</p>
- * <p>Like {@link Session}, {@link Stream} is the active part and by calling its API applications
- * can generate events on the stream; conversely, {@link StreamFrameListener} is the passive part, and its
- * callbacks are invoked when events happen on the stream.</p>
- * <p>A {@link Stream} can send multiple data frames one after the other but implementations use a
- * flow control mechanism that only sends the data frames if the other end has signalled that it can
- * accept the frame.</p>
- * <p>Data frames should be sent sequentially only when the previous frame has been completely sent.
- * The reason for this requirement is to avoid potentially confusing code such as:</p>
+ * <p>A {@link Stream} represents a bidirectional exchange of data on top of a {@link Session}.</p> <p>Differently from
+ * socket streams, where the input and output streams are permanently associated with the socket (and hence with the
+ * connection that the socket represents), there can be multiple SPDY streams for a SPDY session.</p> <p>SPDY streams
+ * may terminate without this implying that the SPDY session is terminated.</p> <p>If SPDY is used to transport the HTTP
+ * protocol, then a SPDY stream maps to a HTTP request/response cycle, and after the request/response cycle is
+ * completed, the stream is closed, and other streams may be opened. Differently from HTTP, though, multiple SPDY
+ * streams may be opened concurrently on the same SPDY session.</p> <p>Like {@link Session}, {@link Stream} is the
+ * active part and by calling its API applications can generate events on the stream; conversely, {@link
+ * StreamFrameListener} is the passive part, and its callbacks are invoked when events happen on the stream.</p> <p>A
+ * {@link Stream} can send multiple data frames one after the other but implementations use a flow control mechanism
+ * that only sends the data frames if the other end has signalled that it can accept the frame.</p> <p>Data frames
+ * should be sent sequentially only when the previous frame has been completely sent. The reason for this requirement is
+ * to avoid potentially confusing code such as:</p>
  * <pre>
  * // WRONG CODE, DO NOT USE IT
  * final Stream stream = ...;
  * stream.data(StringDataInfo("chunk1", false), 5, TimeUnit.SECONDS, new Handler<Void>() { ... });
  * stream.data(StringDataInfo("chunk2", true), 1, TimeUnit.SECONDS, new Handler<Void>() { ... });
  * </pre>
- * <p>where the second call to {@link #data(DataInfo, long, TimeUnit, Handler)} has a timeout smaller
- * than the previous call.</p>
- * <p>The behavior of such style of invocations is unspecified (it may even throw an exception - similar
- * to {@link WritePendingException}).</p>
- * <p>The correct sending of data frames is the following:</p>
+ * <p>where the second call to {@link #data(DataInfo, Callback)} has a timeout smaller than the previous call.</p>
+ * <p>The behavior of such style of invocations is unspecified (it may even throw an exception - similar to {@link
+ * WritePendingException}).</p> <p>The correct sending of data frames is the following:</p>
  * <pre>
  * final Stream stream = ...;
  * ...
@@ -89,103 +86,86 @@ public interface Stream
     public Session getSession();
 
     /**
-     * <p>Initiate a unidirectional spdy pushstream associated to this stream asynchronously<p>
-     * <p>Callers may use the returned future to get the pushstream once it got created</p>
+     * <p>Initiate a unidirectional spdy pushstream associated to this stream asynchronously<p> <p>Callers may use the
+     * returned future to get the pushstream once it got created</p>
      *
-     * @param synInfo the metadata to send on stream creation
+     * @param pushInfo the metadata to send on stream creation
      * @return a future containing the stream once it got established
-     * @see #syn(SynInfo, long, TimeUnit, Handler)
+     * @see #push(PushInfo, Promise)
      */
-    public Future<Stream> syn(SynInfo synInfo);
+    public Stream push(PushInfo pushInfo) throws InterruptedException, ExecutionException, TimeoutException;
 
     /**
-     * <p>Initiate a unidirectional spdy pushstream associated to this stream asynchronously<p>
-     * <p>Callers may pass a non-null completion handler to be notified of when the
-     * pushstream has been established.</p>
+     * <p>Initiate a unidirectional spdy pushstream associated to this stream asynchronously<p> <p>Callers may pass a
+     * non-null completion promise to be notified of when the pushstream has been established.</p>
      *
-     * @param synInfo the metadata to send on stream creation
-     * @param timeout  the operation's timeout
-     * @param unit     the timeout's unit
-     * @param handler   the completion handler that gets notified once the pushstream is established
-     * @see #syn(SynInfo)
+     * @param pushInfo the metadata to send on stream creation
+     * @param promise the completion promise that gets notified once the pushstream is established
+     * @see #push(PushInfo)
      */
-    public void syn(SynInfo synInfo, long timeout, TimeUnit unit, Handler<Stream> handler);
+    public void push(PushInfo pushInfo, Promise<Stream> promise);
 
     /**
-     * <p>Sends asynchronously a SYN_REPLY frame in response to a SYN_STREAM frame.</p>
-     * <p>Callers may use the returned future to wait for the reply to be actually sent.</p>
+     * <p>Sends asynchronously a SYN_REPLY frame in response to a SYN_STREAM frame.</p> <p>Callers may use the returned
+     * future to wait for the reply to be actually sent.</p>
      *
      * @param replyInfo the metadata to send
-     * @return a future to wait for the reply to be sent
-     * @see #reply(ReplyInfo, long, TimeUnit, Handler)
+     * @see #reply(ReplyInfo, Callback)
      * @see SessionFrameListener#onSyn(Stream, SynInfo)
      */
-    public Future<Void> reply(ReplyInfo replyInfo);
+    public void reply(ReplyInfo replyInfo) throws InterruptedException, ExecutionException, TimeoutException;
 
     /**
-     * <p>Sends asynchronously a SYN_REPLY frame in response to a SYN_STREAM frame.</p>
-     * <p>Callers may pass a non-null completion handler to be notified of when the
-     * reply has been actually sent.</p>
+     * <p>Sends asynchronously a SYN_REPLY frame in response to a SYN_STREAM frame.</p> <p>Callers may pass a non-null
+     * completion callback to be notified of when the reply has been actually sent.</p>
      *
      * @param replyInfo the metadata to send
-     * @param timeout  the operation's timeout
-     * @param unit     the timeout's unit
-     * @param handler   the completion handler that gets notified of reply sent
+     * @param callback  the completion callback that gets notified of reply sent
      * @see #reply(ReplyInfo)
      */
-    public void reply(ReplyInfo replyInfo, long timeout, TimeUnit unit, Handler<Void> handler);
+    public void reply(ReplyInfo replyInfo, Callback callback);
 
     /**
-     * <p>Sends asynchronously a DATA frame on this stream.</p>
-     * <p>DATA frames should always be sent after a SYN_REPLY frame.</p>
-     * <p>Callers may use the returned future to wait for the data to be actually sent.</p>
+     * <p>Sends asynchronously a DATA frame on this stream.</p> <p>DATA frames should always be sent after a SYN_REPLY
+     * frame.</p> <p>Callers may use the returned future to wait for the data to be actually sent.</p>
      *
      * @param dataInfo the metadata to send
-     * @return a future to wait for the data to be sent
-     * @see #data(DataInfo, long, TimeUnit, Handler)
+     * @see #data(DataInfo, Callback)
      * @see #reply(ReplyInfo)
      */
-    public Future<Void> data(DataInfo dataInfo);
+    public void data(DataInfo dataInfo) throws InterruptedException, ExecutionException, TimeoutException;
 
     /**
-     * <p>Sends asynchronously a DATA frame on this stream.</p>
-     * <p>DATA frames should always be sent after a SYN_REPLY frame.</p>
-     * <p>Callers may pass a non-null completion handler to be notified of when the
-     * data has been actually sent.</p>
+     * <p>Sends asynchronously a DATA frame on this stream.</p> <p>DATA frames should always be sent after a SYN_REPLY
+     * frame.</p> <p>Callers may pass a non-null completion callback to be notified of when the data has been actually
+     * sent.</p>
      *
      * @param dataInfo the metadata to send
-     * @param timeout  the operation's timeout
-     * @param unit     the timeout's unit
-     * @param handler  the completion handler that gets notified of data sent
+     * @param callback the completion callback that gets notified of data sent
      * @see #data(DataInfo)
      */
-    public void data(DataInfo dataInfo, long timeout, TimeUnit unit, Handler<Void> handler);
+    public void data(DataInfo dataInfo, Callback callback);
 
     /**
-     * <p>Sends asynchronously a HEADER frame on this stream.</p>
-     * <p>HEADERS frames should always be sent after a SYN_REPLY frame.</p>
-     * <p>Callers may use the returned future to wait for the headers to be actually sent.</p>
+     * <p>Sends asynchronously a HEADER frame on this stream.</p> <p>HEADERS frames should always be sent after a
+     * SYN_REPLY frame.</p> <p>Callers may use the returned future to wait for the headers to be actually sent.</p>
      *
      * @param headersInfo the metadata to send
-     * @return a future to wait for the headers to be sent
-     * @see #headers(HeadersInfo, long, TimeUnit, Handler)
+     * @see #headers(HeadersInfo, Callback)
      * @see #reply(ReplyInfo)
      */
-    public Future<Void> headers(HeadersInfo headersInfo);
+    public void headers(HeadersInfo headersInfo) throws InterruptedException, ExecutionException, TimeoutException;
 
     /**
-     * <p>Sends asynchronously a HEADER frame on this stream.</p>
-     * <p>HEADERS frames should always be sent after a SYN_REPLY frame.</p>
-     * <p>Callers may pass a non-null completion handler to be notified of when the
-     * headers have been actually sent.</p>
+     * <p>Sends asynchronously a HEADER frame on this stream.</p> <p>HEADERS frames should always be sent after a
+     * SYN_REPLY frame.</p> <p>Callers may pass a non-null completion callback to be notified of when the headers have
+     * been actually sent.</p>
      *
      * @param headersInfo the metadata to send
-     * @param timeout  the operation's timeout
-     * @param unit     the timeout's unit
-     * @param handler     the completion handler that gets notified of headers sent
+     * @param callback    the completion callback that gets notified of headers sent
      * @see #headers(HeadersInfo)
      */
-    public void headers(HeadersInfo headersInfo, long timeout, TimeUnit unit, Handler<Void> handler);
+    public void headers(HeadersInfo headersInfo, Callback callback);
 
     /**
      * @return whether this stream is unidirectional or not
@@ -211,7 +191,8 @@ public interface Stream
 
     /**
      * @param key the attribute key
-     * @return an arbitrary object associated with the given key to this stream
+     * @return an arbitrary object associated with the given key to this stream or null if no object can be found for
+     *         the given key.
      * @see #setAttribute(String, Object)
      */
     public Object getAttribute(String key);
@@ -241,4 +222,16 @@ public interface Stream
      */
     public Set<Stream> getPushedStreams();
 
+    /**
+     * Get the idle timeout set for this particular stream
+     * @return the idle timeout
+     */
+    public long getIdleTimeout();
+
+    /**
+     * Set an idle timeout for this stream
+     * @param timeout
+     */
+    public void setIdleTimeout(long timeout);
+
 }
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/StreamFrameListener.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/StreamFrameListener.java
index b0ac428..534c26b 100644
--- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/StreamFrameListener.java
+++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/StreamFrameListener.java
@@ -51,6 +51,15 @@ public interface StreamFrameListener extends EventListener
     public void onHeaders(Stream stream, HeadersInfo headersInfo);
 
     /**
+     * <p>Callback invoked when a push syn has been received on a stream.</p>
+     *
+     * @param stream the push stream just created
+     * @param pushInfo the push metadata
+     * @return a listener for stream events or null if there is no interest in being notified of stream events
+     */
+    public StreamFrameListener onPush(Stream stream, PushInfo pushInfo);
+
+    /**
      * <p>Callback invoked when data bytes are received on a stream.</p>
      * <p>Implementers should be read or consume the content of the
      * {@link DataInfo} before this method returns.</p>
@@ -61,6 +70,13 @@ public interface StreamFrameListener extends EventListener
     public void onData(Stream stream, DataInfo dataInfo);
 
     /**
+     * <p>Callback invoked on errors.</p>
+     * @param stream the stream
+     * @param x the failure
+     */
+    public void onFailure(Stream stream, Throwable x);
+
+    /**
      * <p>Empty implementation of {@link StreamFrameListener}</p>
      */
     public static class Adapter implements StreamFrameListener
@@ -76,8 +92,19 @@ public interface StreamFrameListener extends EventListener
         }
 
         @Override
+        public StreamFrameListener onPush(Stream stream, PushInfo pushInfo)
+        {
+            return null;
+        }
+
+        @Override
         public void onData(Stream stream, DataInfo dataInfo)
         {
         }
+
+        @Override
+        public void onFailure(Stream stream, Throwable x)
+        {
+        }
     }
 }
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/StringDataInfo.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/StringDataInfo.java
index cb3e6ab..d9cb319 100644
--- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/StringDataInfo.java
+++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/StringDataInfo.java
@@ -18,7 +18,8 @@
 
 package org.eclipse.jetty.spdy.api;
 
-import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.TimeUnit;
 
 /**
  * <p>Specialized {@link DataInfo} for {@link String} content.</p>
@@ -27,6 +28,11 @@ public class StringDataInfo extends BytesDataInfo
 {
     public StringDataInfo(String string, boolean close)
     {
-        super(string.getBytes(Charset.forName("UTF-8")), close);
+        super(string.getBytes(StandardCharsets.UTF_8), close);
+    }
+
+    public StringDataInfo(long timeout, TimeUnit unit, String string, boolean close)
+    {
+        super(timeout, unit, string.getBytes(StandardCharsets.UTF_8), close);
     }
 }
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/SynInfo.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/SynInfo.java
index fc40cfe..8713830 100644
--- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/SynInfo.java
+++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/api/SynInfo.java
@@ -18,13 +18,17 @@
 
 package org.eclipse.jetty.spdy.api;
 
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.util.Fields;
+
 /**
  * <p>A container for SYN_STREAM frames metadata and data.</p>
  */
-public class SynInfo
+public class SynInfo extends Info
 {
     /**
-     * <p>Flag that indicates that this {@link DataInfo} is the last frame in the stream.</p>
+     * <p>Flag that indicates that this {@link SynInfo} is the last frame in the stream.</p>
      *
      * @see #isClose()
      * @see #getFlags()
@@ -33,50 +37,56 @@ public class SynInfo
 
     private final boolean close;
     private final byte priority;
-    private final Headers headers;
+    private final Fields headers;
 
     /**
-     * <p>Creates a new {@link SynInfo} instance with empty headers and the given close flag,
+     * <p>Creates a {@link SynInfo} instance with the given headers and the given close flag,
      * not unidirectional, without associated stream, and with default priority.</p>
      *
+     * @param headers the {@link Fields}
      * @param close the value of the close flag
      */
-    public SynInfo(boolean close)
+    public SynInfo(Fields headers, boolean close)
     {
-        this(new Headers(), close);
+        this(0, TimeUnit.SECONDS, headers, close, (byte)0);
+        // either builder or setters for timeout
     }
 
     /**
-     * <p>Creates a {@link ReplyInfo} instance with the given headers and the given close flag,
-     * not unidirectional, without associated stream, and with default priority.</p>
-     *
-     * @param headers the {@link Headers}
-     * @param close the value of the close flag
+     * <p>
+     * Creates a {@link SynInfo} instance with the given headers, the given close flag and with the given priority.
+     * </p>
+     * @param headers
+     *            the {@link Fields}
+     * @param close
+     *            the value of the close flag
+     * @param priority
      */
-    public SynInfo(Headers headers, boolean close)
+    public SynInfo(Fields headers, boolean close, byte priority)
     {
-        this(headers, close, (byte)0);
+        this(0, TimeUnit.SECONDS, headers, close, priority);
     }
 
     /**
      * <p>
-     * Creates a {@link ReplyInfo} instance with the given headers, the given close flag and with the given priority.
+     * Creates a {@link SynInfo} instance with the given headers, the given close flag and with the given priority.
      * </p>
-     * 
+     * @param timeout the timeout value
+     * @param unit the TimeUnit of the timeout
      * @param headers
-     *            the {@link Headers}
+     *            the {@link Fields}
      * @param close
      *            the value of the close flag
      * @param priority
-     *            the priority
      */
-    public SynInfo(Headers headers, boolean close, byte priority)
+    public SynInfo(long timeout, TimeUnit unit, Fields headers, boolean close, byte priority)
     {
+        super(timeout, unit);
         this.close = close;
         this.priority = priority;
         this.headers = headers;
     }
-    
+
     /**
      * @return the value of the close flag
      */
@@ -94,13 +104,13 @@ public class SynInfo
     }
 
     /**
-     * @return the {@link Headers}
+     * @return the {@link Fields}
      */
-    public Headers getHeaders()
+    public Fields getHeaders()
     {
         return headers;
     }
-    
+
     /**
      * @return the close flag as integer
      * @see #FLAG_CLOSE
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/frames/DataFrame.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/frames/DataFrame.java
index c2c18d8..c58ef2c 100644
--- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/frames/DataFrame.java
+++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/frames/DataFrame.java
@@ -55,14 +55,9 @@ public class DataFrame
         return (flags & DataInfo.FLAG_CLOSE) == DataInfo.FLAG_CLOSE;
     }
 
-    public boolean isCompress()
-    {
-        return (flags & DataInfo.FLAG_COMPRESS) == DataInfo.FLAG_COMPRESS;
-    }
-
     @Override
     public String toString()
     {
-        return String.format("DATA frame stream=%d length=%d close=%b compress=%b", getStreamId(), getLength(), isClose(), isCompress());
+        return String.format("DATA frame stream=%d length=%d close=%b", getStreamId(), getLength(), isClose());
     }
 }
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/frames/HeadersFrame.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/frames/HeadersFrame.java
index 42c021b..82e13eb 100644
--- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/frames/HeadersFrame.java
+++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/frames/HeadersFrame.java
@@ -18,15 +18,15 @@
 
 package org.eclipse.jetty.spdy.frames;
 
-import org.eclipse.jetty.spdy.api.Headers;
 import org.eclipse.jetty.spdy.api.HeadersInfo;
+import org.eclipse.jetty.util.Fields;
 
 public class HeadersFrame extends ControlFrame
 {
     private final int streamId;
-    private final Headers headers;
+    private final Fields headers;
 
-    public HeadersFrame(short version, byte flags, int streamId, Headers headers)
+    public HeadersFrame(short version, byte flags, int streamId, Fields headers)
     {
         super(version, ControlFrameType.HEADERS, flags);
         this.streamId = streamId;
@@ -38,7 +38,7 @@ public class HeadersFrame extends ControlFrame
         return streamId;
     }
 
-    public Headers getHeaders()
+    public Fields getHeaders()
     {
         return headers;
     }
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/frames/SynReplyFrame.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/frames/SynReplyFrame.java
index 85b2189..372b8ea 100644
--- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/frames/SynReplyFrame.java
+++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/frames/SynReplyFrame.java
@@ -18,15 +18,15 @@
 
 package org.eclipse.jetty.spdy.frames;
 
-import org.eclipse.jetty.spdy.api.Headers;
 import org.eclipse.jetty.spdy.api.ReplyInfo;
+import org.eclipse.jetty.util.Fields;
 
 public class SynReplyFrame extends ControlFrame
 {
     private final int streamId;
-    private final Headers headers;
+    private final Fields headers;
 
-    public SynReplyFrame(short version, byte flags, int streamId, Headers headers)
+    public SynReplyFrame(short version, byte flags, int streamId, Fields headers)
     {
         super(version, ControlFrameType.SYN_REPLY, flags);
         this.streamId = streamId;
@@ -38,7 +38,7 @@ public class SynReplyFrame extends ControlFrame
         return streamId;
     }
 
-    public Headers getHeaders()
+    public Fields getHeaders()
     {
         return headers;
     }
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/frames/SynStreamFrame.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/frames/SynStreamFrame.java
index 6a9fd67..c6e5757 100644
--- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/frames/SynStreamFrame.java
+++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/frames/SynStreamFrame.java
@@ -19,8 +19,8 @@
 package org.eclipse.jetty.spdy.frames;
 
 import org.eclipse.jetty.spdy.PushSynInfo;
-import org.eclipse.jetty.spdy.api.Headers;
 import org.eclipse.jetty.spdy.api.SynInfo;
+import org.eclipse.jetty.util.Fields;
 
 public class SynStreamFrame extends ControlFrame
 {
@@ -28,9 +28,9 @@ public class SynStreamFrame extends ControlFrame
     private final int associatedStreamId;
     private final byte priority;
     private final short slot;
-    private final Headers headers;
+    private final Fields headers;
 
-    public SynStreamFrame(short version, byte flags, int streamId, int associatedStreamId, byte priority, short slot, Headers headers)
+    public SynStreamFrame(short version, byte flags, int streamId, int associatedStreamId, byte priority, short slot, Fields headers)
     {
         super(version, ControlFrameType.SYN_STREAM, flags);
         this.streamId = streamId;
@@ -60,7 +60,7 @@ public class SynStreamFrame extends ControlFrame
         return slot;
     }
 
-    public Headers getHeaders()
+    public Fields getHeaders()
     {
         return headers;
     }
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/ControlFrameGenerator.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/ControlFrameGenerator.java
index 338596c..9a68cae 100644
--- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/ControlFrameGenerator.java
+++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/ControlFrameGenerator.java
@@ -20,7 +20,7 @@ package org.eclipse.jetty.spdy.generator;
 
 import java.nio.ByteBuffer;
 
-import org.eclipse.jetty.spdy.ByteBufferPool;
+import org.eclipse.jetty.io.ByteBufferPool;
 import org.eclipse.jetty.spdy.frames.ControlFrame;
 
 public abstract class ControlFrameGenerator
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/CredentialGenerator.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/CredentialGenerator.java
index c94ed3e..14e6cf1 100644
--- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/CredentialGenerator.java
+++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/CredentialGenerator.java
@@ -24,11 +24,12 @@ import java.security.cert.CertificateEncodingException;
 import java.util.ArrayList;
 import java.util.List;
 
-import org.eclipse.jetty.spdy.ByteBufferPool;
+import org.eclipse.jetty.io.ByteBufferPool;
 import org.eclipse.jetty.spdy.SessionException;
 import org.eclipse.jetty.spdy.api.SessionStatus;
 import org.eclipse.jetty.spdy.frames.ControlFrame;
 import org.eclipse.jetty.spdy.frames.CredentialFrame;
+import org.eclipse.jetty.util.BufferUtil;
 
 public class CredentialGenerator extends ControlFrameGenerator
 {
@@ -52,7 +53,8 @@ public class CredentialGenerator extends ControlFrameGenerator
         int frameBodyLength = 2 + 4 + proof.length + certificates.size() * 4 + certificatesLength;
 
         int totalLength = ControlFrame.HEADER_LENGTH + frameBodyLength;
-        ByteBuffer buffer = getByteBufferPool().acquire(totalLength, true);
+        ByteBuffer buffer = getByteBufferPool().acquire(totalLength, Generator.useDirectBuffers);
+        BufferUtil.clearToFill(buffer);
         generateControlFrameHeader(credential, frameBodyLength, buffer);
 
         buffer.putShort(credential.getSlot());
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/DataFrameGenerator.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/DataFrameGenerator.java
index afb8b23..998acd5 100644
--- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/DataFrameGenerator.java
+++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/DataFrameGenerator.java
@@ -20,9 +20,10 @@ package org.eclipse.jetty.spdy.generator;
 
 import java.nio.ByteBuffer;
 
-import org.eclipse.jetty.spdy.ByteBufferPool;
+import org.eclipse.jetty.io.ByteBufferPool;
 import org.eclipse.jetty.spdy.api.DataInfo;
 import org.eclipse.jetty.spdy.frames.DataFrame;
+import org.eclipse.jetty.util.BufferUtil;
 
 public class DataFrameGenerator
 {
@@ -35,7 +36,9 @@ public class DataFrameGenerator
 
     public ByteBuffer generate(int streamId, int length, DataInfo dataInfo)
     {
-        ByteBuffer buffer = bufferPool.acquire(DataFrame.HEADER_LENGTH + length, true);
+        ByteBuffer buffer = bufferPool.acquire(DataFrame.HEADER_LENGTH + length, Generator.useDirectBuffers);
+        BufferUtil.clearToFill(buffer);
+        buffer.limit(length + DataFrame.HEADER_LENGTH);
         buffer.position(DataFrame.HEADER_LENGTH);
         // Guaranteed to always be >= 0
         int read = dataInfo.readInto(buffer);
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/Generator.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/Generator.java
index 41f27f6..46a35a0 100644
--- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/Generator.java
+++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/Generator.java
@@ -21,7 +21,7 @@ package org.eclipse.jetty.spdy.generator;
 import java.nio.ByteBuffer;
 import java.util.EnumMap;
 
-import org.eclipse.jetty.spdy.ByteBufferPool;
+import org.eclipse.jetty.io.ByteBufferPool;
 import org.eclipse.jetty.spdy.CompressionFactory;
 import org.eclipse.jetty.spdy.api.DataInfo;
 import org.eclipse.jetty.spdy.frames.ControlFrame;
@@ -29,6 +29,7 @@ import org.eclipse.jetty.spdy.frames.ControlFrameType;
 
 public class Generator
 {
+    final static boolean useDirectBuffers=false;
     private final EnumMap<ControlFrameType, ControlFrameGenerator> generators = new EnumMap<>(ControlFrameType.class);
     private final DataFrameGenerator dataFrameGenerator;
 
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/GoAwayGenerator.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/GoAwayGenerator.java
index 634a5fe..2a2e717 100644
--- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/GoAwayGenerator.java
+++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/GoAwayGenerator.java
@@ -20,10 +20,11 @@ package org.eclipse.jetty.spdy.generator;
 
 import java.nio.ByteBuffer;
 
-import org.eclipse.jetty.spdy.ByteBufferPool;
+import org.eclipse.jetty.io.ByteBufferPool;
 import org.eclipse.jetty.spdy.api.SPDY;
 import org.eclipse.jetty.spdy.frames.ControlFrame;
 import org.eclipse.jetty.spdy.frames.GoAwayFrame;
+import org.eclipse.jetty.util.BufferUtil;
 
 public class GoAwayGenerator extends ControlFrameGenerator
 {
@@ -39,7 +40,8 @@ public class GoAwayGenerator extends ControlFrameGenerator
 
         int frameBodyLength = 8;
         int totalLength = ControlFrame.HEADER_LENGTH + frameBodyLength;
-        ByteBuffer buffer = getByteBufferPool().acquire(totalLength, true);
+        ByteBuffer buffer = getByteBufferPool().acquire(totalLength, Generator.useDirectBuffers);
+        BufferUtil.clearToFill(buffer);
         generateControlFrameHeader(goAway, frameBodyLength, buffer);
 
         buffer.putInt(goAway.getLastStreamId() & 0x7F_FF_FF_FF);
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/HeadersBlockGenerator.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/HeadersBlockGenerator.java
index 9d482e7..68e56bc 100644
--- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/HeadersBlockGenerator.java
+++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/HeadersBlockGenerator.java
@@ -21,12 +21,15 @@ package org.eclipse.jetty.spdy.generator;
 import java.io.ByteArrayOutputStream;
 import java.nio.ByteBuffer;
 import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.List;
 import java.util.Locale;
 
 import org.eclipse.jetty.spdy.CompressionDictionary;
 import org.eclipse.jetty.spdy.CompressionFactory;
-import org.eclipse.jetty.spdy.api.Headers;
 import org.eclipse.jetty.spdy.api.SPDY;
+import org.eclipse.jetty.util.Fields;
 
 public class HeadersBlockGenerator
 {
@@ -38,30 +41,29 @@ public class HeadersBlockGenerator
         this.compressor = compressor;
     }
 
-    public ByteBuffer generate(short version, Headers headers)
+    public ByteBuffer generate(short version, Fields headers)
     {
         // TODO: ByteArrayOutputStream is quite inefficient, but grows on demand; optimize using ByteBuffer ?
-        Charset iso1 = Charset.forName("ISO-8859-1");
-        ByteArrayOutputStream buffer = new ByteArrayOutputStream(headers.size() * 64);
-        writeCount(version, buffer, headers.size());
-        for (Headers.Header header : headers)
+        final Charset iso1 = StandardCharsets.ISO_8859_1;
+        ByteArrayOutputStream buffer = new ByteArrayOutputStream(headers.getSize() * 64);
+        writeCount(version, buffer, headers.getSize());
+        for (Fields.Field header : headers)
         {
-            String name = header.name().toLowerCase(Locale.ENGLISH);
+            String name = header.getName().toLowerCase(Locale.ENGLISH);
             byte[] nameBytes = name.getBytes(iso1);
             writeNameLength(version, buffer, nameBytes.length);
             buffer.write(nameBytes, 0, nameBytes.length);
 
             // Most common path first
-            String value = header.value();
+            String value = header.getValue();
             byte[] valueBytes = value.getBytes(iso1);
             if (header.hasMultipleValues())
             {
-                String[] values = header.values();
-                for (int i = 1; i < values.length; ++i)
+                List<String> values = header.getValues();
+                for (int i = 1; i < values.size(); ++i)
                 {
-                    byte[] moreValueBytes = values[i].getBytes(iso1);
-                    byte[] newValueBytes = new byte[valueBytes.length + 1 + moreValueBytes.length];
-                    System.arraycopy(valueBytes, 0, newValueBytes, 0, valueBytes.length);
+                    byte[] moreValueBytes = values.get(i).getBytes(iso1);
+                    byte[] newValueBytes = Arrays.copyOf(valueBytes,valueBytes.length + 1 + moreValueBytes.length);
                     newValueBytes[valueBytes.length] = 0;
                     System.arraycopy(moreValueBytes, 0, newValueBytes, valueBytes.length + 1, moreValueBytes.length);
                     valueBytes = newValueBytes;
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/HeadersGenerator.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/HeadersGenerator.java
index 2212c01..23b75eb 100644
--- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/HeadersGenerator.java
+++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/HeadersGenerator.java
@@ -20,12 +20,13 @@ package org.eclipse.jetty.spdy.generator;
 
 import java.nio.ByteBuffer;
 
-import org.eclipse.jetty.spdy.ByteBufferPool;
+import org.eclipse.jetty.io.ByteBufferPool;
 import org.eclipse.jetty.spdy.SessionException;
 import org.eclipse.jetty.spdy.api.SPDY;
 import org.eclipse.jetty.spdy.api.SessionStatus;
 import org.eclipse.jetty.spdy.frames.ControlFrame;
 import org.eclipse.jetty.spdy.frames.HeadersFrame;
+import org.eclipse.jetty.util.BufferUtil;
 
 public class HeadersGenerator extends ControlFrameGenerator
 {
@@ -59,7 +60,8 @@ public class HeadersGenerator extends ControlFrameGenerator
 
         int totalLength = ControlFrame.HEADER_LENGTH + frameLength;
 
-        ByteBuffer buffer = getByteBufferPool().acquire(totalLength, true);
+        ByteBuffer buffer = getByteBufferPool().acquire(totalLength, Generator.useDirectBuffers);
+        BufferUtil.clearToFill(buffer);
         generateControlFrameHeader(headers, frameLength, buffer);
 
         buffer.putInt(headers.getStreamId() & 0x7F_FF_FF_FF);
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/NoOpGenerator.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/NoOpGenerator.java
index 20aaa6a..0c7c650 100644
--- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/NoOpGenerator.java
+++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/NoOpGenerator.java
@@ -20,9 +20,10 @@ package org.eclipse.jetty.spdy.generator;
 
 import java.nio.ByteBuffer;
 
-import org.eclipse.jetty.spdy.ByteBufferPool;
+import org.eclipse.jetty.io.ByteBufferPool;
 import org.eclipse.jetty.spdy.frames.ControlFrame;
 import org.eclipse.jetty.spdy.frames.NoOpFrame;
+import org.eclipse.jetty.util.BufferUtil;
 
 public class NoOpGenerator extends ControlFrameGenerator
 {
@@ -38,7 +39,8 @@ public class NoOpGenerator extends ControlFrameGenerator
 
         int frameBodyLength = 0;
         int totalLength = ControlFrame.HEADER_LENGTH + frameBodyLength;
-        ByteBuffer buffer = getByteBufferPool().acquire(totalLength, true);
+        ByteBuffer buffer = getByteBufferPool().acquire(totalLength, Generator.useDirectBuffers);
+        BufferUtil.clearToFill(buffer);
         generateControlFrameHeader(noOp, frameBodyLength, buffer);
 
         buffer.flip();
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/PingGenerator.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/PingGenerator.java
index 6f7d38d..7237471 100644
--- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/PingGenerator.java
+++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/PingGenerator.java
@@ -20,9 +20,10 @@ package org.eclipse.jetty.spdy.generator;
 
 import java.nio.ByteBuffer;
 
-import org.eclipse.jetty.spdy.ByteBufferPool;
+import org.eclipse.jetty.io.ByteBufferPool;
 import org.eclipse.jetty.spdy.frames.ControlFrame;
 import org.eclipse.jetty.spdy.frames.PingFrame;
+import org.eclipse.jetty.util.BufferUtil;
 
 public class PingGenerator extends ControlFrameGenerator
 {
@@ -38,7 +39,8 @@ public class PingGenerator extends ControlFrameGenerator
 
         int frameBodyLength = 4;
         int totalLength = ControlFrame.HEADER_LENGTH + frameBodyLength;
-        ByteBuffer buffer = getByteBufferPool().acquire(totalLength, true);
+        ByteBuffer buffer = getByteBufferPool().acquire(totalLength, Generator.useDirectBuffers);
+        BufferUtil.clearToFill(buffer);
         generateControlFrameHeader(ping, frameBodyLength, buffer);
 
         buffer.putInt(ping.getPingId());
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/RstStreamGenerator.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/RstStreamGenerator.java
index 2cec592..47b123c 100644
--- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/RstStreamGenerator.java
+++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/RstStreamGenerator.java
@@ -20,9 +20,10 @@ package org.eclipse.jetty.spdy.generator;
 
 import java.nio.ByteBuffer;
 
-import org.eclipse.jetty.spdy.ByteBufferPool;
+import org.eclipse.jetty.io.ByteBufferPool;
 import org.eclipse.jetty.spdy.frames.ControlFrame;
 import org.eclipse.jetty.spdy.frames.RstStreamFrame;
+import org.eclipse.jetty.util.BufferUtil;
 
 public class RstStreamGenerator extends ControlFrameGenerator
 {
@@ -38,7 +39,8 @@ public class RstStreamGenerator extends ControlFrameGenerator
 
         int frameBodyLength = 8;
         int totalLength = ControlFrame.HEADER_LENGTH + frameBodyLength;
-        ByteBuffer buffer = getByteBufferPool().acquire(totalLength, true);
+        ByteBuffer buffer = getByteBufferPool().acquire(totalLength, Generator.useDirectBuffers);
+        BufferUtil.clearToFill(buffer);
         generateControlFrameHeader(rstStream, frameBodyLength, buffer);
 
         buffer.putInt(rstStream.getStreamId() & 0x7F_FF_FF_FF);
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/SettingsGenerator.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/SettingsGenerator.java
index c3fec9c..0fcf2cf 100644
--- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/SettingsGenerator.java
+++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/SettingsGenerator.java
@@ -20,11 +20,12 @@ package org.eclipse.jetty.spdy.generator;
 
 import java.nio.ByteBuffer;
 
-import org.eclipse.jetty.spdy.ByteBufferPool;
+import org.eclipse.jetty.io.ByteBufferPool;
 import org.eclipse.jetty.spdy.api.SPDY;
 import org.eclipse.jetty.spdy.api.Settings;
 import org.eclipse.jetty.spdy.frames.ControlFrame;
 import org.eclipse.jetty.spdy.frames.SettingsFrame;
+import org.eclipse.jetty.util.BufferUtil;
 
 public class SettingsGenerator extends ControlFrameGenerator
 {
@@ -42,7 +43,8 @@ public class SettingsGenerator extends ControlFrameGenerator
         int size = settings.size();
         int frameBodyLength = 4 + 8 * size;
         int totalLength = ControlFrame.HEADER_LENGTH + frameBodyLength;
-        ByteBuffer buffer = getByteBufferPool().acquire(totalLength, true);
+        ByteBuffer buffer = getByteBufferPool().acquire(totalLength, Generator.useDirectBuffers);
+        BufferUtil.clearToFill(buffer);
         generateControlFrameHeader(settingsFrame, frameBodyLength, buffer);
 
         buffer.putInt(size);
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/SynReplyGenerator.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/SynReplyGenerator.java
index 61008e7..26ba734 100644
--- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/SynReplyGenerator.java
+++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/SynReplyGenerator.java
@@ -20,12 +20,13 @@ package org.eclipse.jetty.spdy.generator;
 
 import java.nio.ByteBuffer;
 
-import org.eclipse.jetty.spdy.ByteBufferPool;
+import org.eclipse.jetty.io.ByteBufferPool;
 import org.eclipse.jetty.spdy.SessionException;
 import org.eclipse.jetty.spdy.api.SPDY;
 import org.eclipse.jetty.spdy.api.SessionStatus;
 import org.eclipse.jetty.spdy.frames.ControlFrame;
 import org.eclipse.jetty.spdy.frames.SynReplyFrame;
+import org.eclipse.jetty.util.BufferUtil;
 
 public class SynReplyGenerator extends ControlFrameGenerator
 {
@@ -57,7 +58,8 @@ public class SynReplyGenerator extends ControlFrameGenerator
 
         int totalLength = ControlFrame.HEADER_LENGTH + frameLength;
 
-        ByteBuffer buffer = getByteBufferPool().acquire(totalLength, true);
+        ByteBuffer buffer = getByteBufferPool().acquire(totalLength, Generator.useDirectBuffers);
+        BufferUtil.clearToFill(buffer);
         generateControlFrameHeader(synReply, frameLength, buffer);
 
         buffer.putInt(synReply.getStreamId() & 0x7F_FF_FF_FF);
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/SynStreamGenerator.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/SynStreamGenerator.java
index d9ca20f..2ad58fc 100644
--- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/SynStreamGenerator.java
+++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/SynStreamGenerator.java
@@ -20,7 +20,7 @@ package org.eclipse.jetty.spdy.generator;
 
 import java.nio.ByteBuffer;
 
-import org.eclipse.jetty.spdy.ByteBufferPool;
+import org.eclipse.jetty.io.ByteBufferPool;
 import org.eclipse.jetty.spdy.SessionException;
 import org.eclipse.jetty.spdy.StreamException;
 import org.eclipse.jetty.spdy.api.SPDY;
@@ -28,6 +28,7 @@ import org.eclipse.jetty.spdy.api.SessionStatus;
 import org.eclipse.jetty.spdy.api.StreamStatus;
 import org.eclipse.jetty.spdy.frames.ControlFrame;
 import org.eclipse.jetty.spdy.frames.SynStreamFrame;
+import org.eclipse.jetty.util.BufferUtil;
 
 public class SynStreamGenerator extends ControlFrameGenerator
 {
@@ -59,7 +60,8 @@ public class SynStreamGenerator extends ControlFrameGenerator
 
         int totalLength = ControlFrame.HEADER_LENGTH + frameLength;
 
-        ByteBuffer buffer = getByteBufferPool().acquire(totalLength, true);
+        ByteBuffer buffer = getByteBufferPool().acquire(totalLength, Generator.useDirectBuffers);
+        BufferUtil.clearToFill(buffer);
         generateControlFrameHeader(synStream, frameLength, buffer);
 
         int streamId = synStream.getStreamId();
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/WindowUpdateGenerator.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/WindowUpdateGenerator.java
index 3a05d17..2ee745e 100644
--- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/WindowUpdateGenerator.java
+++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/WindowUpdateGenerator.java
@@ -20,9 +20,10 @@ package org.eclipse.jetty.spdy.generator;
 
 import java.nio.ByteBuffer;
 
-import org.eclipse.jetty.spdy.ByteBufferPool;
+import org.eclipse.jetty.io.ByteBufferPool;
 import org.eclipse.jetty.spdy.frames.ControlFrame;
 import org.eclipse.jetty.spdy.frames.WindowUpdateFrame;
+import org.eclipse.jetty.util.BufferUtil;
 
 public class WindowUpdateGenerator extends ControlFrameGenerator
 {
@@ -38,7 +39,8 @@ public class WindowUpdateGenerator extends ControlFrameGenerator
 
         int frameBodyLength = 8;
         int totalLength = ControlFrame.HEADER_LENGTH + frameBodyLength;
-        ByteBuffer buffer = getByteBufferPool().acquire(totalLength, true);
+        ByteBuffer buffer = getByteBufferPool().acquire(totalLength, Generator.useDirectBuffers);
+        BufferUtil.clearToFill(buffer);
         generateControlFrameHeader(windowUpdate, frameBodyLength, buffer);
 
         buffer.putInt(windowUpdate.getStreamId() & 0x7F_FF_FF_FF);
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/ControlFrameParser.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/ControlFrameParser.java
index 0ce8574..e4162a7 100644
--- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/ControlFrameParser.java
+++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/ControlFrameParser.java
@@ -35,7 +35,8 @@ public abstract class ControlFrameParser
     private short type;
     private byte flags;
     private int length;
-    private ControlFrameBodyParser parser;
+    private ControlFrameBodyParser bodyParser;
+    private int bytesToSkip = 0;
 
     public ControlFrameParser(CompressionFactory.Decompressor decompressor)
     {
@@ -66,6 +67,12 @@ public abstract class ControlFrameParser
         return length;
     }
 
+    public void skip(int bytesToSkip)
+    {
+        state = State.SKIP;
+        this.bytesToSkip = bytesToSkip;
+    }
+
     public boolean parse(ByteBuffer buffer)
     {
         while (buffer.hasRemaining())
@@ -140,9 +147,9 @@ public abstract class ControlFrameParser
 
                     // SPEC v3, 2.2.1: unrecognized control frames must be ignored
                     if (controlFrameType == null)
-                        parser = unknownParser;
+                        bodyParser = unknownParser;
                     else
-                        parser = parsers.get(controlFrameType);
+                        bodyParser = parsers.get(controlFrameType);
 
                     state = State.BODY;
 
@@ -153,13 +160,29 @@ public abstract class ControlFrameParser
                 }
                 case BODY:
                 {
-                    if (parser.parse(buffer))
+                    if (bodyParser.parse(buffer))
                     {
                         reset();
                         return true;
                     }
                     break;
                 }
+                case SKIP:
+                {
+                    int remaining = buffer.remaining();
+                    if (remaining >= bytesToSkip)
+                    {
+                        buffer.position(buffer.position() + bytesToSkip);
+                        reset();
+                        return true;
+                    }
+                    else
+                    {
+                        buffer.position(buffer.limit());
+                        bytesToSkip = bytesToSkip - remaining;
+                        return false;
+                    }
+                }
                 default:
                 {
                     throw new IllegalStateException();
@@ -169,7 +192,7 @@ public abstract class ControlFrameParser
         return false;
     }
 
-    private void reset()
+    void reset()
     {
         state = State.VERSION;
         cursor = 0;
@@ -177,13 +200,14 @@ public abstract class ControlFrameParser
         type = 0;
         flags = 0;
         length = 0;
-        parser = null;
+        bodyParser = null;
+        bytesToSkip = 0;
     }
 
     protected abstract void onControlFrame(ControlFrame frame);
 
     private enum State
     {
-        VERSION, VERSION_BYTES, TYPE, TYPE_BYTES, FLAGS, LENGTH, BODY
+        VERSION, VERSION_BYTES, TYPE, TYPE_BYTES, FLAGS, LENGTH, BODY, SKIP
     }
 }
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/HeadersBlockParser.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/HeadersBlockParser.java
index c68e849..8337d13 100644
--- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/HeadersBlockParser.java
+++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/HeadersBlockParser.java
@@ -20,6 +20,8 @@ package org.eclipse.jetty.spdy.parser;
 
 import java.nio.ByteBuffer;
 import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
 import java.util.zip.ZipException;
 
 import org.eclipse.jetty.spdy.CompressionDictionary;
@@ -57,8 +59,7 @@ public abstract class HeadersBlockParser
         data = null;
         ByteBuffer decompressedHeaders = decompress(version, compressedHeaders);
 
-        Charset iso1 = Charset.forName("ISO-8859-1");
-
+        Charset iso1 = StandardCharsets.ISO_8859_1;
         // We know the decoded bytes contain the full headers,
         // so optimize instead of looping byte by byte
         int count = readCount(version, decompressedHeaders);
@@ -114,16 +115,14 @@ public abstract class HeadersBlockParser
             int needed = length - accumulated;
             if (remaining < needed)
             {
-                byte[] local = new byte[accumulated + remaining];
-                System.arraycopy(data, 0, local, 0, accumulated);
+                byte[] local = Arrays.copyOf(data,accumulated + remaining);
                 buffer.get(local, accumulated, remaining);
                 data = local;
                 return false;
             }
             else
             {
-                byte[] local = new byte[length];
-                System.arraycopy(data, 0, local, 0, accumulated);
+                byte[] local = Arrays.copyOf(data,length);
                 buffer.get(local, accumulated, needed);
                 data = local;
                 return true;
@@ -199,8 +198,7 @@ public abstract class HeadersBlockParser
                         else
                         {
                             // Last pass needed to decompress, merge decompressed bytes
-                            byte[] result = new byte[decompressed.length + count];
-                            System.arraycopy(decompressed, 0, result, 0, decompressed.length);
+                            byte[] result = Arrays.copyOf(decompressed,decompressed.length+count);
                             System.arraycopy(buffer, 0, result, decompressed.length, count);
                             return ByteBuffer.wrap(result);
                         }
@@ -214,8 +212,7 @@ public abstract class HeadersBlockParser
                         }
                         else
                         {
-                            byte[] result = new byte[decompressed.length + buffer.length];
-                            System.arraycopy(decompressed, 0, result, 0, decompressed.length);
+                            byte[] result = Arrays.copyOf(decompressed,decompressed.length+buffer.length);
                             System.arraycopy(buffer, 0, result, decompressed.length, buffer.length);
                             decompressed = result;
                         }
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/HeadersBodyParser.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/HeadersBodyParser.java
index 137b3bb..5484a11 100644
--- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/HeadersBodyParser.java
+++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/HeadersBodyParser.java
@@ -21,15 +21,15 @@ package org.eclipse.jetty.spdy.parser;
 import java.nio.ByteBuffer;
 
 import org.eclipse.jetty.spdy.CompressionFactory;
-import org.eclipse.jetty.spdy.api.Headers;
 import org.eclipse.jetty.spdy.api.HeadersInfo;
 import org.eclipse.jetty.spdy.api.SPDY;
 import org.eclipse.jetty.spdy.frames.ControlFrameType;
 import org.eclipse.jetty.spdy.frames.HeadersFrame;
+import org.eclipse.jetty.util.Fields;
 
 public class HeadersBodyParser extends ControlFrameBodyParser
 {
-    private final Headers headers = new Headers();
+    private final Fields headers = new Fields();
     private final ControlFrameParser controlFrameParser;
     private final HeadersBlockParser headersBlockParser;
     private State state = State.STREAM_ID;
@@ -126,7 +126,7 @@ public class HeadersBodyParser extends ControlFrameBodyParser
                         if (flags != 0 && flags != HeadersInfo.FLAG_CLOSE && flags != HeadersInfo.FLAG_RESET_COMPRESSION)
                             throw new IllegalArgumentException("Invalid flag " + flags + " for frame " + ControlFrameType.HEADERS);
 
-                        HeadersFrame frame = new HeadersFrame(version, flags, streamId, new Headers(headers, true));
+                        HeadersFrame frame = new HeadersFrame(version, flags, streamId, new Fields(headers, true));
                         controlFrameParser.onControlFrame(frame);
 
                         reset();
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/Parser.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/Parser.java
index 7685300..97d1dd0 100644
--- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/Parser.java
+++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/Parser.java
@@ -108,7 +108,14 @@ public class Parser
     {
         for (Listener listener : listeners)
         {
-            listener.onStreamException(x);
+            try
+            {
+                listener.onStreamException(x);
+            }
+            catch (Exception xx)
+            {
+                logger.debug("Could not notify listener " + listener, xx);
+            }
         }
     }
 
@@ -130,49 +137,52 @@ public class Parser
 
     public void parse(ByteBuffer buffer)
     {
+        logger.debug("Parsing {} bytes", buffer.remaining());
         try
         {
-            logger.debug("Parsing {} bytes", buffer.remaining());
             while (buffer.hasRemaining())
             {
-                switch (state)
+                try
                 {
-                    case CONTROL_BIT:
-                    {
-                        // We must only peek the first byte and not advance the buffer
-                        // because the 7 least significant bits may be relevant in data frames
-                        int currByte = buffer.get(buffer.position());
-                        boolean isControlFrame = (currByte & 0x80) == 0x80;
-                        state = isControlFrame ? State.CONTROL_FRAME : State.DATA_FRAME;
-                        break;
-                    }
-                    case CONTROL_FRAME:
+                    switch (state)
                     {
-                        if (controlFrameParser.parse(buffer))
-                            reset();
-                        break;
-                    }
-                    case DATA_FRAME:
-                    {
-                        if (dataFrameParser.parse(buffer))
-                            reset();
-                        break;
-                    }
-                    default:
-                    {
-                        throw new IllegalStateException();
+                        case CONTROL_BIT:
+                        {
+                            // We must only peek the first byte and not advance the buffer
+                            // because the 7 least significant bits may be relevant in data frames
+                            int currByte = buffer.get(buffer.position());
+                            boolean isControlFrame = (currByte & 0x80) == 0x80;
+                            state = isControlFrame ? State.CONTROL_FRAME : State.DATA_FRAME;
+                            break;
+                        }
+                        case CONTROL_FRAME:
+                        {
+                            if (controlFrameParser.parse(buffer))
+                                reset();
+                            break;
+                        }
+                        case DATA_FRAME:
+                        {
+                            if (dataFrameParser.parse(buffer))
+                                reset();
+                            break;
+                        }
+                        default:
+                        {
+                            throw new IllegalStateException();
+                        }
                     }
                 }
+                catch (StreamException x)
+                {
+                    notifyStreamException(x);
+                }
             }
         }
         catch (SessionException x)
         {
             notifySessionException(x);
         }
-        catch (StreamException x)
-        {
-            notifyStreamException(x);
-        }
         catch (Throwable x)
         {
             notifySessionException(new SessionException(SessionStatus.PROTOCOL_ERROR, x));
@@ -227,5 +237,4 @@ public class Parser
     {
         CONTROL_BIT, CONTROL_FRAME, DATA_FRAME
     }
-
 }
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/SynReplyBodyParser.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/SynReplyBodyParser.java
index 4535f23..0fc56c4 100644
--- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/SynReplyBodyParser.java
+++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/SynReplyBodyParser.java
@@ -21,15 +21,15 @@ package org.eclipse.jetty.spdy.parser;
 import java.nio.ByteBuffer;
 
 import org.eclipse.jetty.spdy.CompressionFactory;
-import org.eclipse.jetty.spdy.api.Headers;
 import org.eclipse.jetty.spdy.api.ReplyInfo;
 import org.eclipse.jetty.spdy.api.SPDY;
 import org.eclipse.jetty.spdy.frames.ControlFrameType;
 import org.eclipse.jetty.spdy.frames.SynReplyFrame;
+import org.eclipse.jetty.util.Fields;
 
 public class SynReplyBodyParser extends ControlFrameBodyParser
 {
-    private final Headers headers = new Headers();
+    private final Fields headers = new Fields();
     private final ControlFrameParser controlFrameParser;
     private final HeadersBlockParser headersBlockParser;
     private State state = State.STREAM_ID;
@@ -124,7 +124,7 @@ public class SynReplyBodyParser extends ControlFrameBodyParser
                         if (flags != 0 && flags != ReplyInfo.FLAG_CLOSE)
                             throw new IllegalArgumentException("Invalid flag " + flags + " for frame " + ControlFrameType.SYN_REPLY);
 
-                        SynReplyFrame frame = new SynReplyFrame(version, flags, streamId, new Headers(headers, true));
+                        SynReplyFrame frame = new SynReplyFrame(version, flags, streamId, new Fields(headers, true));
                         controlFrameParser.onControlFrame(frame);
 
                         reset();
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/SynStreamBodyParser.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/SynStreamBodyParser.java
index b77d9af..bfbc84b 100644
--- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/SynStreamBodyParser.java
+++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/SynStreamBodyParser.java
@@ -23,16 +23,16 @@ import java.nio.ByteBuffer;
 import org.eclipse.jetty.spdy.CompressionFactory;
 import org.eclipse.jetty.spdy.PushSynInfo;
 import org.eclipse.jetty.spdy.StreamException;
-import org.eclipse.jetty.spdy.api.Headers;
 import org.eclipse.jetty.spdy.api.SPDY;
 import org.eclipse.jetty.spdy.api.StreamStatus;
 import org.eclipse.jetty.spdy.api.SynInfo;
 import org.eclipse.jetty.spdy.frames.ControlFrameType;
 import org.eclipse.jetty.spdy.frames.SynStreamFrame;
+import org.eclipse.jetty.util.Fields;
 
 public class SynStreamBodyParser extends ControlFrameBodyParser
 {
-    private final Headers headers = new Headers();
+    private final Fields headers = new Fields();
     private final ControlFrameParser controlFrameParser;
     private final HeadersBlockParser headersBlockParser;
     private State state = State.STREAM_ID;
@@ -85,8 +85,31 @@ public class SynStreamBodyParser extends ControlFrameBodyParser
                 {
                     // Now we know the streamId, we can do the version check
                     // and if it is wrong, issue a RST_STREAM
-                    checkVersion(controlFrameParser.getVersion(), streamId);
-
+                    try
+                    {
+                        checkVersion(controlFrameParser.getVersion(), streamId);
+                    }
+                    catch (StreamException e)
+                    {
+                        // We've already read 4 bytes of the streamId which are part of controlFrameParser.getLength
+                        // so we need to substract those from the bytesToSkip.
+                        int bytesToSkip = controlFrameParser.getLength() - 4;
+                        int remaining = buffer.remaining();
+                        if (remaining >= bytesToSkip)
+                        {
+                            buffer.position(buffer.position() + bytesToSkip);
+                            controlFrameParser.reset();
+                            reset();
+                        }
+                        else
+                        {
+                            int bytesToSkipInNextBuffer = bytesToSkip - remaining;
+                            buffer.position(buffer.limit());
+                            controlFrameParser.skip(bytesToSkipInNextBuffer);
+                            reset();
+                        }
+                        throw e;
+                    }
                     if (buffer.remaining() >= 4)
                     {
                         associatedStreamId = buffer.getInt() & 0x7F_FF_FF_FF;
@@ -122,8 +145,6 @@ public class SynStreamBodyParser extends ControlFrameBodyParser
                     else
                     {
                         slot = (short)(currByte & 0xFF);
-                        if (slot < 0)
-                            throw new StreamException(streamId, StreamStatus.INVALID_CREDENTIALS);
                         cursor = 0;
                         state = State.HEADERS;
                     }
@@ -137,9 +158,11 @@ public class SynStreamBodyParser extends ControlFrameBodyParser
                     {
                         byte flags = controlFrameParser.getFlags();
                         if (flags > (SynInfo.FLAG_CLOSE | PushSynInfo.FLAG_UNIDIRECTIONAL))
-                            throw new IllegalArgumentException("Invalid flag " + flags + " for frame " + ControlFrameType.SYN_STREAM);
+                            throw new IllegalArgumentException("Invalid flag " + flags + " for frame " +
+                                    ControlFrameType.SYN_STREAM);
 
-                        SynStreamFrame frame = new SynStreamFrame(version, flags, streamId, associatedStreamId, priority, slot, new Headers(headers, true));
+                        SynStreamFrame frame = new SynStreamFrame(version, flags, streamId, associatedStreamId,
+                                priority, slot, new Fields(headers, false));
                         controlFrameParser.onControlFrame(frame);
 
                         reset();
diff --git a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/AsyncTimeoutTest.java b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/AsyncTimeoutTest.java
index 0ddc832..ed64a55 100644
--- a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/AsyncTimeoutTest.java
+++ b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/AsyncTimeoutTest.java
@@ -22,10 +22,12 @@ import java.nio.ByteBuffer;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
 
-import org.eclipse.jetty.spdy.api.Handler;
+import org.eclipse.jetty.io.ByteArrayEndPoint;
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.MappedByteBufferPool;
 import org.eclipse.jetty.spdy.api.SPDY;
 import org.eclipse.jetty.spdy.api.SPDYException;
 import org.eclipse.jetty.spdy.api.Session;
@@ -33,30 +35,47 @@ import org.eclipse.jetty.spdy.api.Stream;
 import org.eclipse.jetty.spdy.api.StringDataInfo;
 import org.eclipse.jetty.spdy.api.SynInfo;
 import org.eclipse.jetty.spdy.generator.Generator;
+import org.eclipse.jetty.toolchain.test.AdvancedRunner;
+import org.eclipse.jetty.toolchain.test.annotation.Slow;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.Fields;
+import org.eclipse.jetty.util.Promise;
+import org.eclipse.jetty.util.thread.Scheduler;
+import org.eclipse.jetty.util.thread.TimerScheduler;
 import org.junit.Assert;
+import org.junit.Ignore;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 
+ at RunWith(AdvancedRunner.class)
+//TODO: Uncomment comment lines and reimplement tests to fit new design
+ at Ignore("Doesn't work with new Flusher class, needs to be rewritten")
 public class AsyncTimeoutTest
 {
+    EndPoint endPoint = new ByteArrayEndPoint();
+
+    @Slow
     @Test
     public void testAsyncTimeoutInControlFrames() throws Exception
     {
         final long timeout = 1000;
         final TimeUnit unit = TimeUnit.MILLISECONDS;
 
-        ByteBufferPool bufferPool = new StandardByteBufferPool();
+        ByteBufferPool bufferPool = new MappedByteBufferPool();
         Executor threadPool = Executors.newCachedThreadPool();
-        ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
-        Generator generator = new Generator(new StandardByteBufferPool(), new StandardCompressionFactory.StandardCompressor());
-        Session session = new StandardSession(SPDY.V2, bufferPool, threadPool, scheduler, new TestController(), null, 1, null, generator, new FlowControlStrategy.None())
+        Scheduler scheduler = new TimerScheduler();
+        scheduler.start(); // TODO need to use jetty lifecycles better here
+        Generator generator = new Generator(bufferPool, new StandardCompressionFactory.StandardCompressor());
+        Session session = new StandardSession(SPDY.V2, bufferPool, scheduler, new TestController(),
+                endPoint, null, 1, null, generator, new FlowControlStrategy.None())
         {
-            @Override
+//            @Override
             public void flush()
             {
                 try
                 {
                     unit.sleep(2 * timeout);
-                    super.flush();
+//                    super.flush();
                 }
                 catch (InterruptedException x)
                 {
@@ -66,15 +85,10 @@ public class AsyncTimeoutTest
         };
 
         final CountDownLatch failedLatch = new CountDownLatch(1);
-        session.syn(new SynInfo(true), null, timeout, unit, new Handler<Stream>()
+        session.syn(new SynInfo(timeout, unit, new Fields(), true, (byte)0), null, new Promise.Adapter<Stream>()
         {
             @Override
-            public void completed(Stream stream)
-            {
-            }
-
-            @Override
-            public void failed(Stream stream, Throwable x)
+            public void failed(Throwable x)
             {
                 failedLatch.countDown();
             }
@@ -83,27 +97,30 @@ public class AsyncTimeoutTest
         Assert.assertTrue(failedLatch.await(2 * timeout, unit));
     }
 
+    @Slow
     @Test
     public void testAsyncTimeoutInDataFrames() throws Exception
     {
         final long timeout = 1000;
         final TimeUnit unit = TimeUnit.MILLISECONDS;
 
-        ByteBufferPool bufferPool = new StandardByteBufferPool();
+        ByteBufferPool bufferPool = new MappedByteBufferPool();
         Executor threadPool = Executors.newCachedThreadPool();
-        ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
-        Generator generator = new Generator(new StandardByteBufferPool(), new StandardCompressionFactory.StandardCompressor());
-        Session session = new StandardSession(SPDY.V2, bufferPool, threadPool, scheduler, new TestController(), null, 1, null, generator, new FlowControlStrategy.None())
+        Scheduler scheduler = new TimerScheduler();
+        scheduler.start();
+        Generator generator = new Generator(bufferPool, new StandardCompressionFactory.StandardCompressor());
+        Session session = new StandardSession(SPDY.V2, bufferPool, scheduler, new TestController(),
+                endPoint, null, 1, null, generator, new FlowControlStrategy.None())
         {
-            @Override
-            protected void write(ByteBuffer buffer, Handler<FrameBytes> handler, FrameBytes frameBytes)
+//            @Override
+            protected void write(ByteBuffer buffer, Callback callback)
             {
                 try
                 {
                     // Wait if we're writing the data frame (control frame's first byte is 0x80)
                     if (buffer.get(0) == 0)
                         unit.sleep(2 * timeout);
-                    super.write(buffer, handler, frameBytes);
+//                    super.write(buffer, callback);
                 }
                 catch (InterruptedException x)
                 {
@@ -112,17 +129,12 @@ public class AsyncTimeoutTest
             }
         };
 
-        Stream stream = session.syn(new SynInfo(false), null).get(5, TimeUnit.SECONDS);
+        Stream stream = session.syn(new SynInfo(5, TimeUnit.SECONDS, new Fields(), false, (byte)0), null);
         final CountDownLatch failedLatch = new CountDownLatch(1);
-        stream.data(new StringDataInfo("data", true), timeout, unit, new Handler<Void>()
+        stream.data(new StringDataInfo(timeout, unit, "data", true), new Callback.Adapter()
         {
             @Override
-            public void completed(Void context)
-            {
-            }
-
-            @Override
-            public void failed(Void context, Throwable x)
+            public void failed(Throwable x)
             {
                 failedLatch.countDown();
             }
@@ -131,13 +143,12 @@ public class AsyncTimeoutTest
         Assert.assertTrue(failedLatch.await(2 * timeout, unit));
     }
 
-    private static class TestController implements Controller<StandardSession.FrameBytes>
+    private static class TestController implements Controller
     {
         @Override
-        public int write(ByteBuffer buffer, Handler<StandardSession.FrameBytes> handler, StandardSession.FrameBytes context)
+        public void write(Callback callback, ByteBuffer... buffers)
         {
-            handler.completed(context);
-            return buffer.remaining();
+            callback.succeeded();
         }
 
         @Override
diff --git a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/StandardSessionTest.java b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/StandardSessionTest.java
index ec40859..069dab2 100644
--- a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/StandardSessionTest.java
+++ b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/StandardSessionTest.java
@@ -20,32 +20,48 @@ package org.eclipse.jetty.spdy;
 
 import java.nio.ByteBuffer;
 import java.nio.channels.ClosedChannelException;
+import java.util.HashSet;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
 
-import org.eclipse.jetty.spdy.StandardSession.FrameBytes;
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.MappedByteBufferPool;
 import org.eclipse.jetty.spdy.api.ByteBufferDataInfo;
 import org.eclipse.jetty.spdy.api.DataInfo;
-import org.eclipse.jetty.spdy.api.Handler;
-import org.eclipse.jetty.spdy.api.Headers;
 import org.eclipse.jetty.spdy.api.HeadersInfo;
+import org.eclipse.jetty.spdy.api.PushInfo;
+import org.eclipse.jetty.spdy.api.ReplyInfo;
 import org.eclipse.jetty.spdy.api.RstInfo;
 import org.eclipse.jetty.spdy.api.SPDY;
 import org.eclipse.jetty.spdy.api.Session;
+import org.eclipse.jetty.spdy.api.Settings;
 import org.eclipse.jetty.spdy.api.Stream;
 import org.eclipse.jetty.spdy.api.StreamFrameListener;
 import org.eclipse.jetty.spdy.api.StreamStatus;
 import org.eclipse.jetty.spdy.api.StringDataInfo;
 import org.eclipse.jetty.spdy.api.SynInfo;
 import org.eclipse.jetty.spdy.frames.DataFrame;
+import org.eclipse.jetty.spdy.frames.SettingsFrame;
 import org.eclipse.jetty.spdy.frames.SynReplyFrame;
-import org.eclipse.jetty.spdy.frames.SynStreamFrame;
 import org.eclipse.jetty.spdy.generator.Generator;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.Fields;
+import org.eclipse.jetty.util.FuturePromise;
+import org.eclipse.jetty.util.Promise;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
+import org.eclipse.jetty.util.thread.Scheduler;
+import org.junit.After;
+import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Ignore;
 import org.junit.Test;
@@ -59,7 +75,8 @@ import static org.hamcrest.Matchers.greaterThan;
 import static org.hamcrest.Matchers.is;
 import static org.hamcrest.Matchers.not;
 import static org.junit.Assert.assertThat;
-import static org.mockito.Mockito.any;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -67,57 +84,68 @@ import static org.mockito.Mockito.when;
 @RunWith(MockitoJUnitRunner.class)
 public class StandardSessionTest
 {
+    private static final Logger LOG = Log.getLogger(StandardSessionTest.class);
+    private static final short VERSION = SPDY.V2;
+
+    @Mock
+    private Controller controller;
+
     @Mock
-    private Controller<FrameBytes> controller;
+    private EndPoint endPoint;
 
-    private ByteBufferPool bufferPool;
-    private Executor threadPool;
+    private ExecutorService threadPool;
     private StandardSession session;
-    private Generator generator;
-    private ScheduledExecutorService scheduler;
-    private Headers headers;
+    private Scheduler scheduler;
+    private Fields headers;
+    private final ByteBufferPool bufferPool = new MappedByteBufferPool();
+    private final Generator generator = new Generator(bufferPool, new StandardCompressionFactory.StandardCompressor());
 
     @Before
     public void setUp() throws Exception
     {
-        bufferPool = new StandardByteBufferPool();
         threadPool = Executors.newCachedThreadPool();
-        scheduler = Executors.newSingleThreadScheduledExecutor();
-        generator = new Generator(new StandardByteBufferPool(),new StandardCompressionFactory.StandardCompressor());
-        session = new StandardSession(SPDY.V2,bufferPool,threadPool,scheduler,controller,null,1,null,generator,new FlowControlStrategy.None());
-        headers = new Headers();
+        scheduler = new ScheduledExecutorScheduler();
+        scheduler.start();
+        session = new StandardSession(VERSION, bufferPool, scheduler, controller, endPoint, null, 1, null,
+                generator, new FlowControlStrategy.None());
+        when(endPoint.getIdleTimeout()).thenReturn(30000L);
+        headers = new Fields();
+    }
+
+    @After
+    public void after() throws Exception
+    {
+        scheduler.stop();
+        threadPool.shutdownNow();
     }
 
     @SuppressWarnings("unchecked")
-    private void setControllerWriteExpectationToFail(final boolean fail)
+    private void setControllerWriteExpectation(final boolean fail)
     {
-        when(controller.write(any(ByteBuffer.class),any(Handler.class),any(StandardSession.FrameBytes.class))).thenAnswer(new Answer<Integer>()
+        doAnswer(new Answer()
         {
-            public Integer answer(InvocationOnMock invocation)
+            public Object answer(InvocationOnMock invocation)
             {
                 Object[] args = invocation.getArguments();
-
-                Handler<StandardSession.FrameBytes> handler = (Handler<FrameBytes>)args[1];
-                FrameBytes context = (FrameBytes)args[2];
-
+                Callback callback = (Callback)args[0];
                 if (fail)
-                    handler.failed(context,new ClosedChannelException());
+                    callback.failed(new ClosedChannelException());
                 else
-                    handler.completed(context);
-                return 0;
+                    callback.succeeded();
+                return null;
             }
-        });
+        }).when(controller).write(any(Callback.class), any(ByteBuffer.class));
     }
 
     @Test
     public void testStreamIsRemovedFromSessionWhenReset() throws InterruptedException, ExecutionException, TimeoutException
     {
-        setControllerWriteExpectationToFail(false);
+        setControllerWriteExpectation(false);
 
         IStream stream = createStream();
         assertThatStreamIsInSession(stream);
-        assertThat("stream is not reset",stream.isReset(),is(false));
-        session.rst(new RstInfo(stream.getId(),StreamStatus.STREAM_ALREADY_CLOSED));
+        assertThat("stream is not reset", stream.isReset(), is(false));
+        session.rst(new RstInfo(stream.getId(), StreamStatus.STREAM_ALREADY_CLOSED));
         assertThatStreamIsNotInSession(stream);
         assertThatStreamIsReset(stream);
     }
@@ -125,12 +153,12 @@ public class StandardSessionTest
     @Test
     public void testStreamIsAddedAndRemovedFromSession() throws InterruptedException, ExecutionException, TimeoutException
     {
-        setControllerWriteExpectationToFail(false);
+        setControllerWriteExpectation(false);
 
         IStream stream = createStream();
         assertThatStreamIsInSession(stream);
-        stream.updateCloseState(true,true);
-        session.onControlFrame(new SynReplyFrame(SPDY.V2,SynInfo.FLAG_CLOSE,stream.getId(),null));
+        stream.updateCloseState(true, true);
+        session.onControlFrame(new SynReplyFrame(VERSION, SynInfo.FLAG_CLOSE, stream.getId(), null));
         assertThatStreamIsClosed(stream);
         assertThatStreamIsNotInSession(stream);
     }
@@ -138,12 +166,12 @@ public class StandardSessionTest
     @Test
     public void testStreamIsRemovedWhenHeadersWithCloseFlagAreSent() throws InterruptedException, ExecutionException, TimeoutException
     {
-        setControllerWriteExpectationToFail(false);
+        setControllerWriteExpectation(false);
 
         IStream stream = createStream();
         assertThatStreamIsInSession(stream);
-        stream.updateCloseState(true,false);
-        stream.headers(new HeadersInfo(headers,true));
+        stream.updateCloseState(true, false);
+        stream.headers(new HeadersInfo(headers, true));
         assertThatStreamIsClosed(stream);
         assertThatStreamIsNotInSession(stream);
     }
@@ -151,29 +179,29 @@ public class StandardSessionTest
     @Test
     public void testStreamIsUnidirectional() throws InterruptedException, ExecutionException, TimeoutException
     {
-        setControllerWriteExpectationToFail(false);
+        setControllerWriteExpectation(false);
 
         IStream stream = createStream();
-        assertThat("stream is not unidirectional",stream.isUnidirectional(),not(true));
+        assertThat("stream is not unidirectional", stream.isUnidirectional(), not(true));
         Stream pushStream = createPushStream(stream);
-        assertThat("pushStream is unidirectional",pushStream.isUnidirectional(),is(true));
+        assertThat("pushStream is unidirectional", pushStream.isUnidirectional(), is(true));
     }
 
     @Test
     public void testPushStreamCreation() throws InterruptedException, ExecutionException, TimeoutException
     {
-        setControllerWriteExpectationToFail(false);
+        setControllerWriteExpectation(false);
 
         Stream stream = createStream();
         IStream pushStream = createPushStream(stream);
-        assertThat("Push stream must be associated to the first stream created",pushStream.getAssociatedStream().getId(),is(stream.getId()));
-        assertThat("streamIds need to be monotonic",pushStream.getId(),greaterThan(stream.getId()));
+        assertThat("Push stream must be associated to the first stream created", pushStream.getAssociatedStream().getId(), is(stream.getId()));
+        assertThat("streamIds need to be monotonic", pushStream.getId(), greaterThan(stream.getId()));
     }
 
     @Test
     public void testPushStreamIsNotClosedWhenAssociatedStreamIsClosed() throws InterruptedException, ExecutionException, TimeoutException
     {
-        setControllerWriteExpectationToFail(false);
+        setControllerWriteExpectation(false);
 
         IStream stream = createStream();
         Stream pushStream = createPushStream(stream);
@@ -182,13 +210,13 @@ public class StandardSessionTest
         assertThatPushStreamIsHalfClosed(pushStream);
         assertThatPushStreamIsNotClosed(pushStream);
 
-        stream.updateCloseState(true,true);
+        stream.updateCloseState(true, true);
         assertThatStreamIsHalfClosed(stream);
         assertThatStreamIsNotClosed(stream);
         assertThatPushStreamIsHalfClosed(pushStream);
         assertThatPushStreamIsNotClosed(pushStream);
 
-        session.onControlFrame(new SynReplyFrame(SPDY.V2,SynInfo.FLAG_CLOSE,stream.getId(),null));
+        session.onControlFrame(new SynReplyFrame(VERSION, SynInfo.FLAG_CLOSE, stream.getId(), null));
         assertThatStreamIsClosed(stream);
         assertThatPushStreamIsNotClosed(pushStream);
     }
@@ -196,12 +224,12 @@ public class StandardSessionTest
     @Test
     public void testCreatePushStreamOnClosedStream() throws InterruptedException, ExecutionException, TimeoutException
     {
-        setControllerWriteExpectationToFail(false);
+        setControllerWriteExpectation(false);
 
         IStream stream = createStream();
-        stream.updateCloseState(true,true);
+        stream.updateCloseState(true, true);
         assertThatStreamIsHalfClosed(stream);
-        stream.updateCloseState(true,false);
+        stream.updateCloseState(true, false);
         assertThatStreamIsClosed(stream);
         createPushStreamAndMakeSureItFails(stream);
     }
@@ -209,59 +237,59 @@ public class StandardSessionTest
     private void createPushStreamAndMakeSureItFails(IStream stream) throws InterruptedException
     {
         final CountDownLatch failedLatch = new CountDownLatch(1);
-        SynInfo synInfo = new SynInfo(headers,false,stream.getPriority());
-        stream.syn(synInfo,5,TimeUnit.SECONDS,new Handler.Adapter<Stream>()
+        PushInfo pushInfo = new PushInfo(5, TimeUnit.SECONDS, headers, false);
+        stream.push(pushInfo, new Promise.Adapter<Stream>()
         {
             @Override
-            public void failed(Stream stream, Throwable x)
+            public void failed(Throwable x)
             {
                 failedLatch.countDown();
             }
         });
-        assertThat("pushStream creation failed",failedLatch.await(5,TimeUnit.SECONDS),is(true));
+        assertThat("pushStream creation failed", failedLatch.await(5, TimeUnit.SECONDS), is(true));
     }
 
     @Test
     public void testPushStreamIsAddedAndRemovedFromParentAndSessionWhenClosed() throws InterruptedException, ExecutionException, TimeoutException
     {
-        setControllerWriteExpectationToFail(false);
+        setControllerWriteExpectation(false);
 
         IStream stream = createStream();
         IStream pushStream = createPushStream(stream);
         assertThatPushStreamIsHalfClosed(pushStream);
         assertThatPushStreamIsInSession(pushStream);
-        assertThatStreamIsAssociatedWithPushStream(stream,pushStream);
-        session.data(pushStream,new StringDataInfo("close",true),5,TimeUnit.SECONDS,null,null);
+        assertThatStreamIsAssociatedWithPushStream(stream, pushStream);
+        session.data(pushStream, new StringDataInfo("close", true), 5, TimeUnit.SECONDS, new Callback.Adapter());
         assertThatPushStreamIsClosed(pushStream);
         assertThatPushStreamIsNotInSession(pushStream);
-        assertThatStreamIsNotAssociatedWithPushStream(stream,pushStream);
+        assertThatStreamIsNotAssociatedWithPushStream(stream, pushStream);
     }
 
     @Test
     public void testPushStreamIsRemovedWhenReset() throws InterruptedException, ExecutionException, TimeoutException
     {
-        setControllerWriteExpectationToFail(false);
+        setControllerWriteExpectation(false);
 
         IStream stream = createStream();
-        IStream pushStream = (IStream)stream.syn(new SynInfo(false)).get();
+        IStream pushStream = (IStream)stream.push(new PushInfo(new Fields(), false));
         assertThatPushStreamIsInSession(pushStream);
-        session.rst(new RstInfo(pushStream.getId(),StreamStatus.INVALID_STREAM));
+        session.rst(new RstInfo(pushStream.getId(), StreamStatus.INVALID_STREAM));
         assertThatPushStreamIsNotInSession(pushStream);
-        assertThatStreamIsNotAssociatedWithPushStream(stream,pushStream);
+        assertThatStreamIsNotAssociatedWithPushStream(stream, pushStream);
         assertThatStreamIsReset(pushStream);
     }
 
     @Test
     public void testPushStreamWithSynInfoClosedTrue() throws InterruptedException, ExecutionException, TimeoutException
     {
-        setControllerWriteExpectationToFail(false);
+        setControllerWriteExpectation(false);
 
         IStream stream = createStream();
-        SynInfo synInfo = new SynInfo(headers,true,stream.getPriority());
-        IStream pushStream = (IStream)stream.syn(synInfo).get(5,TimeUnit.SECONDS);
+        PushInfo pushInfo = new PushInfo(5, TimeUnit.SECONDS, headers, true);
+        IStream pushStream = (IStream)stream.push(pushInfo);
         assertThatPushStreamIsHalfClosed(pushStream);
         assertThatPushStreamIsClosed(pushStream);
-        assertThatStreamIsNotAssociatedWithPushStream(stream,pushStream);
+        assertThatStreamIsNotAssociatedWithPushStream(stream, pushStream);
         assertThatStreamIsNotInSession(pushStream);
     }
 
@@ -269,73 +297,74 @@ public class StandardSessionTest
     public void testPushStreamSendHeadersWithCloseFlagIsRemovedFromSessionAndDisassociateFromParent() throws InterruptedException, ExecutionException,
             TimeoutException
     {
-        setControllerWriteExpectationToFail(false);
+        setControllerWriteExpectation(false);
 
         IStream stream = createStream();
-        SynInfo synInfo = new SynInfo(headers,false,stream.getPriority());
-        IStream pushStream = (IStream)stream.syn(synInfo).get(5,TimeUnit.SECONDS);
-        assertThatStreamIsAssociatedWithPushStream(stream,pushStream);
+        PushInfo pushInfo = new PushInfo(5, TimeUnit.SECONDS, headers, false);
+        IStream pushStream = (IStream)stream.push(pushInfo);
+        assertThatStreamIsAssociatedWithPushStream(stream, pushStream);
         assertThatPushStreamIsInSession(pushStream);
-        pushStream.headers(new HeadersInfo(headers,true));
+        pushStream.headers(new HeadersInfo(headers, true));
         assertThatPushStreamIsNotInSession(pushStream);
         assertThatPushStreamIsHalfClosed(pushStream);
         assertThatPushStreamIsClosed(pushStream);
-        assertThatStreamIsNotAssociatedWithPushStream(stream,pushStream);
+        assertThatStreamIsNotAssociatedWithPushStream(stream, pushStream);
     }
 
     @Test
     public void testCreatedAndClosedListenersAreCalledForNewStream() throws InterruptedException, ExecutionException, TimeoutException
     {
-        setControllerWriteExpectationToFail(false);
+        setControllerWriteExpectation(false);
 
         final CountDownLatch createdListenerCalledLatch = new CountDownLatch(1);
         final CountDownLatch closedListenerCalledLatch = new CountDownLatch(1);
-        session.addListener(new TestStreamListener(createdListenerCalledLatch,closedListenerCalledLatch));
+        session.addListener(new TestStreamListener(createdListenerCalledLatch, closedListenerCalledLatch));
         IStream stream = createStream();
-        session.onDataFrame(new DataFrame(stream.getId(),SynInfo.FLAG_CLOSE,128),ByteBuffer.allocate(128));
-        stream.data(new StringDataInfo("close",true));
-        assertThat("onStreamCreated listener has been called",createdListenerCalledLatch.await(5,TimeUnit.SECONDS),is(true));
+        session.onControlFrame(new SynReplyFrame(VERSION, (byte)0, stream.getId(), new Fields()));
+        session.onDataFrame(new DataFrame(stream.getId(), SynInfo.FLAG_CLOSE, 128), ByteBuffer.allocate(128));
+        stream.data(new StringDataInfo("close", true));
+        assertThat("onStreamCreated listener has been called", createdListenerCalledLatch.await(5, TimeUnit.SECONDS), is(true));
         assertThatOnStreamClosedListenerHasBeenCalled(closedListenerCalledLatch);
     }
 
     @Test
     public void testListenerIsCalledForResetStream() throws InterruptedException, ExecutionException, TimeoutException
     {
-        setControllerWriteExpectationToFail(false);
+        setControllerWriteExpectation(false);
 
         final CountDownLatch closedListenerCalledLatch = new CountDownLatch(1);
-        session.addListener(new TestStreamListener(null,closedListenerCalledLatch));
+        session.addListener(new TestStreamListener(null, closedListenerCalledLatch));
         IStream stream = createStream();
-        session.rst(new RstInfo(stream.getId(),StreamStatus.CANCEL_STREAM));
+        session.rst(new RstInfo(stream.getId(), StreamStatus.CANCEL_STREAM));
         assertThatOnStreamClosedListenerHasBeenCalled(closedListenerCalledLatch);
     }
 
     @Test
     public void testCreatedAndClosedListenersAreCalledForNewPushStream() throws InterruptedException, ExecutionException, TimeoutException
     {
-        setControllerWriteExpectationToFail(false);
+        setControllerWriteExpectation(false);
 
         final CountDownLatch createdListenerCalledLatch = new CountDownLatch(2);
         final CountDownLatch closedListenerCalledLatch = new CountDownLatch(1);
-        session.addListener(new TestStreamListener(createdListenerCalledLatch,closedListenerCalledLatch));
+        session.addListener(new TestStreamListener(createdListenerCalledLatch, closedListenerCalledLatch));
         IStream stream = createStream();
         IStream pushStream = createPushStream(stream);
-        session.data(pushStream,new StringDataInfo("close",true),5,TimeUnit.SECONDS,null,null);
+        session.data(pushStream, new StringDataInfo("close", true), 5, TimeUnit.SECONDS, new Callback.Adapter());
         assertThat("onStreamCreated listener has been called twice. Once for the stream and once for the pushStream",
-                createdListenerCalledLatch.await(5,TimeUnit.SECONDS),is(true));
+                createdListenerCalledLatch.await(5, TimeUnit.SECONDS), is(true));
         assertThatOnStreamClosedListenerHasBeenCalled(closedListenerCalledLatch);
     }
 
     @Test
     public void testListenerIsCalledForResetPushStream() throws InterruptedException, ExecutionException, TimeoutException
     {
-        setControllerWriteExpectationToFail(false);
+        setControllerWriteExpectation(false);
 
         final CountDownLatch closedListenerCalledLatch = new CountDownLatch(1);
-        session.addListener(new TestStreamListener(null,closedListenerCalledLatch));
+        session.addListener(new TestStreamListener(null, closedListenerCalledLatch));
         IStream stream = createStream();
         IStream pushStream = createPushStream(stream);
-        session.rst(new RstInfo(pushStream.getId(),StreamStatus.CANCEL_STREAM));
+        session.rst(new RstInfo(pushStream.getId(), StreamStatus.CANCEL_STREAM));
         assertThatOnStreamClosedListenerHasBeenCalled(closedListenerCalledLatch);
     }
 
@@ -369,148 +398,284 @@ public class StandardSessionTest
 
     @Test
     @Ignore("In V3 we need to rst the stream if we receive data on a remotely half closed stream.")
-    public void receiveDataOnRemotelyHalfClosedStreamResetsStreamInV3() throws InterruptedException, ExecutionException
+    public void receiveDataOnRemotelyHalfClosedStreamResetsStreamInV3() throws InterruptedException, ExecutionException, TimeoutException
     {
-        setControllerWriteExpectationToFail(false);
+        setControllerWriteExpectation(false);
 
-        IStream stream = (IStream)session.syn(new SynInfo(false),new StreamFrameListener.Adapter()).get();
-        stream.updateCloseState(true,false);
-        assertThat("stream is half closed from remote side",stream.isHalfClosed(),is(true));
+        IStream stream = (IStream)session.syn(new SynInfo(new Fields(), false), new StreamFrameListener.Adapter());
+        stream.updateCloseState(true, false);
+        assertThat("stream is half closed from remote side", stream.isHalfClosed(), is(true));
         stream.process(new ByteBufferDataInfo(ByteBuffer.allocate(256), true));
     }
 
     @Test
     public void testReceiveDataOnRemotelyClosedStreamIsIgnored() throws InterruptedException, ExecutionException, TimeoutException
     {
-        setControllerWriteExpectationToFail(false);
+        setControllerWriteExpectation(false);
 
         final CountDownLatch onDataCalledLatch = new CountDownLatch(1);
-        Stream stream = session.syn(new SynInfo(false),new StreamFrameListener.Adapter()
-        {
-            @Override
-            public void onData(Stream stream, DataInfo dataInfo)
-            {
-                onDataCalledLatch.countDown();
-                super.onData(stream,dataInfo);
-            }
-        }).get(5,TimeUnit.SECONDS);
-        session.onControlFrame(new SynReplyFrame(SPDY.V2,SynInfo.FLAG_CLOSE,stream.getId(),headers));
-        session.onDataFrame(new DataFrame(stream.getId(),(byte)0,0),ByteBuffer.allocate(128));
-        assertThat("onData is never called",onDataCalledLatch.await(1,TimeUnit.SECONDS),not(true));
+        Stream stream = session.syn(new SynInfo(5, TimeUnit.SECONDS, new Fields(), false, (byte)0),
+                new StreamFrameListener.Adapter()
+                {
+                    @Override
+                    public void onData(Stream stream, DataInfo dataInfo)
+                    {
+                        onDataCalledLatch.countDown();
+                        super.onData(stream, dataInfo);
+                    }
+                });
+        session.onControlFrame(new SynReplyFrame(VERSION, SynInfo.FLAG_CLOSE, stream.getId(), headers));
+        session.onDataFrame(new DataFrame(stream.getId(), (byte)0, 0), ByteBuffer.allocate(128));
+        assertThat("onData is never called", onDataCalledLatch.await(1, TimeUnit.SECONDS), not(true));
     }
 
     @SuppressWarnings("unchecked")
     @Test
-    public void testControllerWriteFailsInEndPointFlush() throws InterruptedException
+    public void testControllerWriteFails() throws Exception
     {
-        setControllerWriteExpectationToFail(true);
+        final AtomicInteger writes = new AtomicInteger();
+        final AtomicBoolean fail = new AtomicBoolean();
+        Controller controller = new Controller()
+        {
+            @Override
+            public void write(Callback callback, ByteBuffer... buffers)
+            {
+                writes.incrementAndGet();
+                if (fail.get())
+                    callback.failed(new ClosedChannelException());
+                else
+                    callback.succeeded();
+            }
 
-        final CountDownLatch failedCalledLatch = new CountDownLatch(2);
-        SynStreamFrame synStreamFrame = new SynStreamFrame(SPDY.V2, SynInfo.FLAG_CLOSE, 1, 0, (byte)0, (short)0, null);
-        IStream stream = new StandardStream(synStreamFrame.getStreamId(), synStreamFrame.getPriority(), session, null);
+            @Override
+            public void close(boolean onlyOutput)
+            {
+
+            }
+        };
+        ISession session = new StandardSession(VERSION, bufferPool, scheduler, controller, endPoint, null, 1, null, generator, null);
+        IStream stream = new StandardStream(1, (byte)0, session, null, scheduler, null);
         stream.updateWindowSize(8192);
-        Handler.Adapter<Void> handler = new Handler.Adapter<Void>()
+
+        // Send a reply to comply with the API usage
+        stream.reply(new ReplyInfo(false), new Callback.Adapter());
+
+        // Make the controller fail
+        fail.set(true);
+        final CountDownLatch failedCalledLatch = new CountDownLatch(1);
+        Callback.Adapter callback = new Callback.Adapter()
         {
             @Override
-            public void failed(Void context, Throwable x)
+            public void failed(Throwable x)
             {
                 failedCalledLatch.countDown();
             }
         };
+        // Data frame should fail on controller.write()
+        stream.data(new StringDataInfo(5, TimeUnit.SECONDS, "data", false), callback);
+
+        Assert.assertEquals(2, writes.get());
+        Assert.assertTrue(failedCalledLatch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testControlFramesAreStillSentForResetStreams() throws InterruptedException, ExecutionException, TimeoutException
+    {
+        setControllerWriteExpectation(false);
+
+        // This is necessary to keep the compression context of Headers valid
+        IStream stream = createStream();
+        session.rst(new RstInfo(stream.getId(), StreamStatus.INVALID_STREAM));
+        stream.headers(new HeadersInfo(headers, true));
+
+        verify(controller, times(3)).write(any(Callback.class), any(ByteBuffer.class));
+    }
+
+    @Test
+    public void testMaxConcurrentStreams() throws InterruptedException
+    {
+        final CountDownLatch failedBecauseMaxConcurrentStreamsExceeded = new CountDownLatch(1);
 
-        // first data frame should fail on controller.write()
-        stream.data(new StringDataInfo("data", false), 5, TimeUnit.SECONDS, handler);
-        // second data frame should fail without controller.writer() as the connection is expected to be broken after first controller.write() call failed.
-        stream.data(new StringDataInfo("data", false), 5, TimeUnit.SECONDS, handler);
+        Settings settings = new Settings();
+        settings.put(new Settings.Setting(Settings.ID.MAX_CONCURRENT_STREAMS, 0));
+        SettingsFrame settingsFrame = new SettingsFrame(VERSION, (byte)0, settings);
+        session.onControlFrame(settingsFrame);
 
-        verify(controller, times(1)).write(any(ByteBuffer.class), any(Handler.class), any(FrameBytes.class));
-        assertThat("Handler.failed has been called twice", failedCalledLatch.await(5, TimeUnit.SECONDS), is(true));
+        PushSynInfo pushSynInfo = new PushSynInfo(1, new PushInfo(new Fields(), false));
+        session.syn(pushSynInfo, null, new Promise.Adapter<Stream>()
+        {
+            @Override
+            public void failed(Throwable x)
+            {
+                failedBecauseMaxConcurrentStreamsExceeded.countDown();
+            }
+        });
+
+        assertThat("Opening push stream failed because maxConcurrentStream is exceeded",
+                failedBecauseMaxConcurrentStreamsExceeded.await(5, TimeUnit.SECONDS), is(true));
+    }
+
+    @Test
+    public void testHeaderFramesAreSentInTheOrderTheyAreCreated() throws ExecutionException,
+            TimeoutException, InterruptedException
+    {
+        testHeaderFramesAreSentInOrder((byte)0, (byte)0, (byte)0);
+    }
+
+    @Test
+    public void testHeaderFramesAreSentInTheOrderTheyAreCreatedWithPrioritization() throws ExecutionException,
+            TimeoutException, InterruptedException
+    {
+        testHeaderFramesAreSentInOrder((byte)0, (byte)1, (byte)2);
+    }
+
+    private void testHeaderFramesAreSentInOrder(final byte priority0, final byte priority1, final byte priority2) throws InterruptedException, ExecutionException
+    {
+        final StandardSession testLocalSession = new StandardSession(VERSION, bufferPool, scheduler,
+                new ControllerMock(), endPoint, null, 1, null, generator, new FlowControlStrategy.None());
+        HashSet<Future> tasks = new HashSet<>();
+
+        int numberOfTasksToRun = 128;
+        for (int i = 0; i < numberOfTasksToRun; i++)
+        {
+            tasks.add(threadPool.submit(new Runnable()
+            {
+
+                @Override
+                public void run()
+                {
+                    synStream(priority0);
+                    synStream(priority1);
+                    synStream(priority2);
+                }
+
+                private void synStream(byte priority)
+                {
+                    SynInfo synInfo = new SynInfo(headers, false, priority);
+                    testLocalSession.syn(synInfo, new StreamFrameListener.Adapter(), new FuturePromise<Stream>());
+                }
+            }));
+        }
+
+        for (Future task : tasks)
+        {
+            task.get();
+        }
+
+        threadPool.shutdown();
+        threadPool.awaitTermination(60, TimeUnit.SECONDS);
+    }
+
+    private class ControllerMock implements Controller
+    {
+        long lastStreamId = 0;
+
+        @Override
+        public void write(Callback callback, ByteBuffer... buffers)
+        {
+            StandardSession.FrameBytes frameBytes = (StandardSession.FrameBytes)callback;
+
+            int streamId = frameBytes.getStream().getId();
+            if (LOG.isDebugEnabled())
+                LOG.debug("last: {}, current: {}", lastStreamId, streamId);
+            if (lastStreamId < streamId)
+                lastStreamId = streamId;
+            else
+                throw new IllegalStateException("Last streamId: " + lastStreamId + " is not smaller than current StreamId: " +
+                        streamId);
+            frameBytes.succeeded();
+        }
+
+        @Override
+        public void close(boolean onlyOutput)
+        {
+        }
     }
 
     private IStream createStream() throws InterruptedException, ExecutionException, TimeoutException
     {
-        SynInfo synInfo = new SynInfo(headers,false,(byte)0);
-        return (IStream)session.syn(synInfo,new StreamFrameListener.Adapter()).get(50,TimeUnit.SECONDS);
+        SynInfo synInfo = new SynInfo(5, TimeUnit.SECONDS, headers, false, (byte)0);
+        return (IStream)session.syn(synInfo, new StreamFrameListener.Adapter());
     }
 
     private IStream createPushStream(Stream stream) throws InterruptedException, ExecutionException, TimeoutException
     {
-        SynInfo synInfo = new SynInfo(headers,false,stream.getPriority());
-        return (IStream)stream.syn(synInfo).get(5,TimeUnit.SECONDS);
+        PushInfo pushInfo = new PushInfo(5, TimeUnit.SECONDS, headers, false);
+        return (IStream)stream.push(pushInfo);
     }
 
     private void assertThatStreamIsClosed(IStream stream)
     {
-        assertThat("stream is closed",stream.isClosed(),is(true));
+        assertThat("stream is closed", stream.isClosed(), is(true));
     }
 
     private void assertThatStreamIsReset(IStream stream)
     {
-        assertThat("stream is reset",stream.isReset(),is(true));
+        assertThat("stream is reset", stream.isReset(), is(true));
     }
 
     private void assertThatStreamIsNotInSession(IStream stream)
     {
-        assertThat("stream is not in session",session.getStreams().contains(stream),not(true));
+        assertThat("stream is not in session", session.getStreams().contains(stream), not(true));
     }
 
     private void assertThatStreamIsInSession(IStream stream)
     {
-        assertThat("stream is in session",session.getStreams().contains(stream),is(true));
+        assertThat("stream is in session", session.getStreams().contains(stream), is(true));
     }
 
     private void assertThatStreamIsNotClosed(IStream stream)
     {
-        assertThat("stream is not closed",stream.isClosed(),not(true));
+        assertThat("stream is not closed", stream.isClosed(), not(true));
     }
 
     private void assertThatStreamIsNotHalfClosed(IStream stream)
     {
-        assertThat("stream is not halfClosed",stream.isHalfClosed(),not(true));
+        assertThat("stream is not halfClosed", stream.isHalfClosed(), not(true));
     }
 
     private void assertThatPushStreamIsNotClosed(Stream pushStream)
     {
-        assertThat("pushStream is not closed",pushStream.isClosed(),not(true));
+        assertThat("pushStream is not closed", pushStream.isClosed(), not(true));
     }
 
     private void assertThatStreamIsHalfClosed(IStream stream)
     {
-        assertThat("stream is halfClosed",stream.isHalfClosed(),is(true));
+        assertThat("stream is halfClosed", stream.isHalfClosed(), is(true));
     }
 
     private void assertThatStreamIsNotAssociatedWithPushStream(IStream stream, IStream pushStream)
     {
-        assertThat("pushStream is removed from parent",stream.getPushedStreams().contains(pushStream),not(true));
+        assertThat("pushStream is removed from parent", stream.getPushedStreams().contains(pushStream), not(true));
     }
 
     private void assertThatPushStreamIsNotInSession(Stream pushStream)
     {
-        assertThat("pushStream is not in session",session.getStreams().contains(pushStream.getId()),not(true));
+        assertThat("pushStream is not in session", session.getStreams().contains(pushStream), not(true));
     }
 
     private void assertThatPushStreamIsInSession(Stream pushStream)
     {
-        assertThat("pushStream is in session",session.getStreams().contains(pushStream),is(true));
+        assertThat("pushStream is in session", session.getStreams().contains(pushStream), is(true));
     }
 
     private void assertThatStreamIsAssociatedWithPushStream(IStream stream, Stream pushStream)
     {
-        assertThat("stream is associated with pushStream",stream.getPushedStreams().contains(pushStream),is(true));
+        assertThat("stream is associated with pushStream", stream.getPushedStreams().contains(pushStream), is(true));
     }
 
     private void assertThatPushStreamIsClosed(Stream pushStream)
     {
-        assertThat("pushStream is closed",pushStream.isClosed(),is(true));
+        assertThat("pushStream is closed", pushStream.isClosed(), is(true));
     }
 
     private void assertThatPushStreamIsHalfClosed(Stream pushStream)
     {
-        assertThat("pushStream is half closed ",pushStream.isHalfClosed(),is(true));
+        assertThat("pushStream is half closed ", pushStream.isHalfClosed(), is(true));
     }
 
     private void assertThatOnStreamClosedListenerHasBeenCalled(final CountDownLatch closedListenerCalledLatch) throws InterruptedException
     {
-        assertThat("onStreamClosed listener has been called",closedListenerCalledLatch.await(5,TimeUnit.SECONDS),is(true));
+        assertThat("onStreamClosed listener has been called", closedListenerCalledLatch.await(5, TimeUnit.SECONDS), is(true));
     }
 }
diff --git a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/StandardStreamTest.java b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/StandardStreamTest.java
index 743b30f..27b7c00 100644
--- a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/StandardStreamTest.java
+++ b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/StandardStreamTest.java
@@ -18,21 +18,33 @@
 
 package org.eclipse.jetty.spdy;
 
+import java.nio.channels.ClosedChannelException;
 import java.util.HashSet;
 import java.util.Set;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicInteger;
 
 import org.eclipse.jetty.spdy.api.DataInfo;
-import org.eclipse.jetty.spdy.api.Handler;
+import org.eclipse.jetty.spdy.api.PushInfo;
+import org.eclipse.jetty.spdy.api.ReplyInfo;
 import org.eclipse.jetty.spdy.api.SPDY;
 import org.eclipse.jetty.spdy.api.Stream;
 import org.eclipse.jetty.spdy.api.StreamFrameListener;
 import org.eclipse.jetty.spdy.api.StringDataInfo;
 import org.eclipse.jetty.spdy.api.SynInfo;
+import org.eclipse.jetty.spdy.frames.ControlFrame;
 import org.eclipse.jetty.spdy.frames.SynStreamFrame;
+import org.eclipse.jetty.toolchain.test.annotation.Slow;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.Fields;
+import org.eclipse.jetty.util.Promise;
+import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentMatcher;
@@ -40,11 +52,11 @@ import org.mockito.Mock;
 import org.mockito.runners.MockitoJUnitRunner;
 
 import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.instanceOf;
 import static org.hamcrest.Matchers.is;
 import static org.junit.Assert.assertThat;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyLong;
 import static org.mockito.Matchers.argThat;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
@@ -53,69 +65,71 @@ import static org.mockito.Mockito.when;
 @RunWith(MockitoJUnitRunner.class)
 public class StandardStreamTest
 {
+    private final ScheduledExecutorScheduler scheduler = new ScheduledExecutorScheduler();
     @Mock
     private ISession session;
     @Mock
     private SynStreamFrame synStreamFrame;
 
-    /**
-     * Test method for {@link org.eclipse.jetty.spdy.StandardStream#syn(org.eclipse.jetty.spdy.api.SynInfo)}.
-     */
+    @Before
+    public void setUp() throws Exception
+    {
+        scheduler.start();
+    }
+
+    @After
+    public void tearDown() throws Exception
+    {
+        scheduler.stop();
+    }
+
     @SuppressWarnings("unchecked")
     @Test
     public void testSyn()
     {
-        Stream stream = new StandardStream(synStreamFrame.getStreamId(), synStreamFrame.getPriority(), session, null);
+        Stream stream = new StandardStream(synStreamFrame.getStreamId(), synStreamFrame.getPriority(), session, null, null, null);
         Set<Stream> streams = new HashSet<>();
         streams.add(stream);
         when(synStreamFrame.isClose()).thenReturn(false);
-        SynInfo synInfo = new SynInfo(false);
+        PushInfo pushInfo = new PushInfo(new Fields(), false);
         when(session.getStreams()).thenReturn(streams);
-        stream.syn(synInfo);
-        verify(session).syn(argThat(new PushSynInfoMatcher(stream.getId(), synInfo)), any(StreamFrameListener.class), anyLong(), any(TimeUnit.class), any(Handler.class));
+        stream.push(pushInfo, new Promise.Adapter<Stream>());
+        verify(session).syn(argThat(new PushSynInfoMatcher(stream.getId(), pushInfo)),
+                any(StreamFrameListener.class), any(Promise.class));
     }
 
     private class PushSynInfoMatcher extends ArgumentMatcher<PushSynInfo>
     {
-        int associatedStreamId;
-        SynInfo synInfo;
+        private int associatedStreamId;
+        private PushInfo pushInfo;
 
-        public PushSynInfoMatcher(int associatedStreamId, SynInfo synInfo)
+        public PushSynInfoMatcher(int associatedStreamId, PushInfo pushInfo)
         {
             this.associatedStreamId = associatedStreamId;
-            this.synInfo = synInfo;
+            this.pushInfo = pushInfo;
         }
 
         @Override
         public boolean matches(Object argument)
         {
             PushSynInfo pushSynInfo = (PushSynInfo)argument;
-            if (pushSynInfo.getAssociatedStreamId() != associatedStreamId)
-            {
-                System.out.println("streamIds do not match!");
-                return false;
-            }
-            if (pushSynInfo.isClose() != synInfo.isClose())
-            {
-                System.out.println("isClose doesn't match");
-                return false;
-            }
-            return true;
+            return pushSynInfo.getAssociatedStreamId() == associatedStreamId && pushSynInfo.isClose() == pushInfo.isClose();
         }
     }
 
     @Test
     public void testSynOnClosedStream()
     {
-        IStream stream = new StandardStream(synStreamFrame.getStreamId(), synStreamFrame.getPriority(), session, null);
+        IStream stream = new StandardStream(synStreamFrame.getStreamId(), synStreamFrame.getPriority(), session,
+                null, null , null);
         stream.updateCloseState(true, true);
         stream.updateCloseState(true, false);
         assertThat("stream expected to be closed", stream.isClosed(), is(true));
         final CountDownLatch failedLatch = new CountDownLatch(1);
-        stream.syn(new SynInfo(false), 1, TimeUnit.SECONDS, new Handler.Adapter<Stream>()
+        stream.push(new PushInfo(1, TimeUnit.SECONDS, new Fields(), false), new Promise.Adapter<Stream>()
         {
             @Override
-            public void failed(Stream stream, Throwable x)
+            public void failed(Throwable x)
             {
                 failedLatch.countDown();
             }
@@ -128,11 +142,117 @@ public class StandardStreamTest
     public void testSendDataOnHalfClosedStream() throws InterruptedException, ExecutionException, TimeoutException
     {
         SynStreamFrame synStreamFrame = new SynStreamFrame(SPDY.V2, SynInfo.FLAG_CLOSE, 1, 0, (byte)0, (short)0, null);
-        IStream stream = new StandardStream(synStreamFrame.getStreamId(), synStreamFrame.getPriority(), session, null);
+        IStream stream = new StandardStream(synStreamFrame.getStreamId(), synStreamFrame.getPriority(), session,
+                null, scheduler, null);
         stream.updateWindowSize(8192);
         stream.updateCloseState(synStreamFrame.isClose(), true);
         assertThat("stream is half closed", stream.isHalfClosed(), is(true));
         stream.data(new StringDataInfo("data on half closed stream", true));
-        verify(session, never()).data(any(IStream.class), any(DataInfo.class), anyInt(), any(TimeUnit.class), any(Handler.class), any(void.class));
+        verify(session, never()).data(any(IStream.class), any(DataInfo.class), anyInt(), any(TimeUnit.class), any(Callback.class));
+    }
+
+    @Test
+    @Slow
+    public void testIdleTimeout() throws Exception
+    {
+        IStream stream = new StandardStream(1, (byte)0, session, null, scheduler, null);
+        long idleTimeout = 500;
+        stream.setIdleTimeout(idleTimeout);
+
+        final AtomicInteger failureCount = new AtomicInteger();
+        final CountDownLatch failureLatch = new CountDownLatch(1);
+        stream.setStreamFrameListener(new StreamFrameListener.Adapter()
+        {
+            @Override
+            public void onFailure(Stream stream, Throwable x)
+            {
+                assertThat("exception is a TimeoutException", x, is(instanceOf(TimeoutException.class)));
+                failureCount.incrementAndGet();
+                failureLatch.countDown();
+            }
+        });
+        stream.process(new StringDataInfo("string", false));
+
+        // Wait more than (2 * idleTimeout) to be sure to trigger a failureCount > 1
+        Thread.sleep(3 * idleTimeout);
+
+        assertThat("onFailure has been called", failureLatch.await(5, TimeUnit.SECONDS), is(true));
+        Assert.assertEquals(1, failureCount.get());
+    }
+
+    @Test
+    @Slow
+    public void testIdleTimeoutIsInterruptedWhenReceiving() throws Exception
+    {
+        final CountDownLatch failureLatch = new CountDownLatch(1);
+        IStream stream = new StandardStream(1, (byte)0, session, null, scheduler, null);
+        long idleTimeout = 1000;
+        stream.setIdleTimeout(idleTimeout);
+        stream.setStreamFrameListener(new StreamFrameListener.Adapter()
+        {
+            @Override
+            public void onFailure(Stream stream, Throwable x)
+            {
+                assertThat("exception is a TimeoutException", x, is(instanceOf(TimeoutException.class)));
+                failureLatch.countDown();
+            }
+        });
+        stream.process(new SynStreamFrame(SPDY.V3, (byte)0, 1, 0, (byte)0, (short)0, null));
+        stream.process(new StringDataInfo("string", false));
+        Thread.sleep(idleTimeout / 2);
+        stream.process(new StringDataInfo("string", false));
+        Thread.sleep(idleTimeout / 2);
+        stream.process(new StringDataInfo("string", false));
+        Thread.sleep(idleTimeout / 2);
+        stream.process(new StringDataInfo("string", true));
+        stream.reply(new ReplyInfo(true), new Callback.Adapter());
+        Thread.sleep(idleTimeout);
+        assertThat("onFailure has not been called", failureLatch.await(idleTimeout, TimeUnit.MILLISECONDS), is(false));
+    }
+
+    @Test
+    @Slow
+    public void testReplyFailureClosesStream() throws Exception
+    {
+        ISession session = new StandardSession(SPDY.V3, null, null, null, null, null, 1, null, null, null)
+        {
+            @Override
+            public void control(IStream stream, ControlFrame frame, long timeout, TimeUnit unit, Callback callback)
+            {
+                callback.failed(new ClosedChannelException());
+            }
+        };
+        IStream stream = new StandardStream(1, (byte)0, session, null, scheduler, null);
+        final AtomicInteger failureCount = new AtomicInteger();
+        stream.setStreamFrameListener(new StreamFrameListener.Adapter()
+        {
+            @Override
+            public void onFailure(Stream stream, Throwable x)
+            {
+                failureCount.incrementAndGet();
+            }
+        });
+        long idleTimeout = 500;
+        stream.setIdleTimeout(idleTimeout);
+
+        stream.process(new SynStreamFrame(SPDY.V3, (byte)0, 1, 0, (byte)0, (short)0, null));
+
+        final CountDownLatch failureLatch = new CountDownLatch(1);
+        stream.reply(new ReplyInfo(false), new Callback.Adapter()
+        {
+            @Override
+            public void failed(Throwable x)
+            {
+                failureLatch.countDown();
+            }
+        });
+
+        Assert.assertTrue(failureLatch.await(5, TimeUnit.SECONDS));
+
+        // Make sure that the idle timeout never fires, since the failure above should have closed the stream
+        Thread.sleep(3 * idleTimeout);
+
+        Assert.assertEquals(0, failureCount.get());
+        Assert.assertTrue(stream.isClosed());
     }
 }
diff --git a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/api/ClientUsageTest.java b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/api/ClientUsageTest.java
index 5be8ebe..6a9042c 100644
--- a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/api/ClientUsageTest.java
+++ b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/api/ClientUsageTest.java
@@ -18,10 +18,15 @@
 
 package org.eclipse.jetty.spdy.api;
 
-import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
 
 import org.eclipse.jetty.spdy.StandardSession;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.Fields;
+import org.eclipse.jetty.util.Promise;
 import org.junit.Ignore;
 import org.junit.Test;
 
@@ -33,7 +38,7 @@ public class ClientUsageTest
     {
         Session session = new StandardSession(SPDY.V2, null, null, null, null, null, 1, null, null, null);
 
-        session.syn(new SynInfo(true), new StreamFrameListener.Adapter()
+        session.syn(new SynInfo(new Fields(), true), new StreamFrameListener.Adapter()
         {
             @Override
             public void onReply(Stream stream, ReplyInfo replyInfo)
@@ -42,18 +47,36 @@ public class ClientUsageTest
                 replyInfo.getHeaders().get("host");
 
                 // Then issue another similar request
-                stream.getSession().syn(new SynInfo(true), this);
+                try
+                {
+                    stream.getSession().syn(new SynInfo(new Fields(), true), this);
+                }
+                catch (ExecutionException | InterruptedException | TimeoutException e)
+                {
+                    throw new IllegalStateException(e);
+                }
             }
         });
     }
 
     @Test
-    public void testClientRequestWithBodyResponseNoBody() throws Exception
+    public void testClientReceivesPush1() throws InterruptedException, ExecutionException, TimeoutException
     {
         Session session = new StandardSession(SPDY.V2, null, null, null, null, null, 1, null, null, null);
 
-        Stream stream = session.syn(new SynInfo(false), new StreamFrameListener.Adapter()
+        session.syn(new SynInfo(new Fields(), true), new StreamFrameListener.Adapter()
         {
+            public StreamFrameListener onPush(Stream stream, PushInfo pushInfo)
+            {
+                return new Adapter()
+                {
+                    @Override
+                    public void onData(Stream stream, DataInfo dataInfo)
+                    {
+                    }
+                };
+            };
+
             @Override
             public void onReply(Stream stream, ReplyInfo replyInfo)
             {
@@ -61,20 +84,36 @@ public class ClientUsageTest
                 replyInfo.getHeaders().get("host");
 
                 // Then issue another similar request
-                stream.getSession().syn(new SynInfo(true), this);
+                try
+                {
+                    stream.getSession().syn(new SynInfo(new Fields(), true), this);
+                }
+                catch (ExecutionException | InterruptedException | TimeoutException e)
+                {
+                    throw new IllegalStateException(e);
+                }
             }
-        }).get(5, TimeUnit.SECONDS);
-        // Send-and-forget the data
-        stream.data(new StringDataInfo("data", true));
+        });
     }
 
     @Test
-    public void testAsyncClientRequestWithBodyResponseNoBody() throws Exception
+    public void testClientReceivesPush2() throws InterruptedException, ExecutionException, TimeoutException
     {
-        Session session = new StandardSession(SPDY.V2, null, null, null, null, null, 1, null, null, null);
+        Session session = new StandardSession(SPDY.V2, null, null, null, null, null, 1, new SessionFrameListener.Adapter()
+        {
+            public StreamFrameListener onPush(Stream stream, PushInfo pushInfo)
+            {
+                return new StreamFrameListener.Adapter()
+                {
+                    @Override
+                    public void onData(Stream stream, DataInfo dataInfo)
+                    {
+                    }
+                };
+            }
+        }, null, null);
 
-        final String context = "context";
-        session.syn(new SynInfo(false), new StreamFrameListener.Adapter()
+        session.syn(new SynInfo(new Fields(), true), new StreamFrameListener.Adapter()
         {
             @Override
             public void onReply(Stream stream, ReplyInfo replyInfo)
@@ -83,72 +122,139 @@ public class ClientUsageTest
                 replyInfo.getHeaders().get("host");
 
                 // Then issue another similar request
-                stream.getSession().syn(new SynInfo(true), this);
-            }
-        }, 0, TimeUnit.MILLISECONDS, new Handler.Adapter<Stream>()
-        {
-            @Override
-            public void completed(Stream stream)
-            {
-                // Differently from JDK 7 AIO, there is no need to
-                // have an explicit parameter for the context since
-                // that is captured while the handler is created anyway,
-                // and it is used only by the handler as parameter
-
-                // The style below is fire-and-forget, since
-                // we do not pass the handler nor we call get()
-                // to wait for the data to be sent
-                stream.data(new StringDataInfo(context, true));
+                try
+                {
+                    stream.getSession().syn(new SynInfo(new Fields(), true), this);
+                }
+                catch (ExecutionException | InterruptedException | TimeoutException e)
+                {
+                    throw new IllegalStateException(e);
+                }
             }
         });
     }
 
     @Test
-    public void testAsyncClientRequestWithBodyAndResponseWithBody() throws Exception
+    public void testClientRequestWithBodyResponseNoBody() throws Exception
     {
         Session session = new StandardSession(SPDY.V2, null, null, null, null, null, 1, null, null, null);
 
-        session.syn(new SynInfo(false), new StreamFrameListener.Adapter()
-        {
-            // The good of passing the listener to syn() is that applications can safely
-            // accumulate info from the reply headers to be used in the data callback,
-            // e.g. content-type, charset, etc.
+        Stream stream = session.syn(new SynInfo(5, TimeUnit.SECONDS, new Fields(), false, (byte)0),
+                new StreamFrameListener.Adapter()
+                {
+                    @Override
+                    public void onReply(Stream stream, ReplyInfo replyInfo)
+                    {
+                        // Do something with the response
+                        replyInfo.getHeaders().get("host");
 
-            @Override
-            public void onReply(Stream stream, ReplyInfo replyInfo)
-            {
-                // Do something with the response
-                Headers headers = replyInfo.getHeaders();
-                int contentLength = headers.get("content-length").valueAsInt();
-                stream.setAttribute("content-length", contentLength);
-                if (!replyInfo.isClose())
-                    stream.setAttribute("builder", new StringBuilder());
-
-                // May issue another similar request while waiting for data
-                stream.getSession().syn(new SynInfo(true), this);
-            }
+                        // Then issue another similar request
+                        try
+                        {
+                            stream.getSession().syn(new SynInfo(new Fields(), true), this);
+                        }
+                        catch (ExecutionException | InterruptedException | TimeoutException e)
+                        {
+                            throw new IllegalStateException(e);
+                        }
+                    }
+                });
+        // Send-and-forget the data
+        stream.data(new StringDataInfo("data", true));
+    }
 
-            @Override
-            public void onData(Stream stream, DataInfo dataInfo)
-            {
-                StringBuilder builder = (StringBuilder)stream.getAttribute("builder");
-                builder.append(dataInfo.asString("UTF-8", true));
-                if (dataInfo.isClose())
+    @Test
+    public void testAsyncClientRequestWithBodyResponseNoBody() throws Exception
+    {
+        Session session = new StandardSession(SPDY.V2, null, null, null, null, null, 1, null, null, null);
+
+        final String context = "context";
+        session.syn(new SynInfo(new Fields(), false), new StreamFrameListener.Adapter()
                 {
-                    int receivedLength = builder.toString().getBytes(Charset.forName("UTF-8")).length;
-                    assert receivedLength == (Integer)stream.getAttribute("content-length");
+                    @Override
+                    public void onReply(Stream stream, ReplyInfo replyInfo)
+                    {
+                        // Do something with the response
+                        replyInfo.getHeaders().get("host");
+
+                        // Then issue another similar request
+                        try
+                        {
+                            stream.getSession().syn(new SynInfo(new Fields(), true), this);
+                        }
+                        catch (ExecutionException | InterruptedException | TimeoutException e)
+                        {
+                            throw new IllegalStateException(e);
+                        }
+                    }
+                }, new Promise.Adapter<Stream>()
+                {
+                    @Override
+                    public void succeeded(Stream stream)
+                    {
+                        // Differently from JDK 7 AIO, there is no need to
+                        // have an explicit parameter for the context since
+                        // that is captured while the handler is created anyway,
+                        // and it is used only by the handler as parameter
+
+                        // The style below is fire-and-forget, since
+                        // we do not pass the handler nor we call get()
+                        // to wait for the data to be sent
+                        stream.data(new StringDataInfo(context, true), new Callback.Adapter());
+                    }
                 }
+        );
+    }
 
-            }
-        }, 0, TimeUnit.MILLISECONDS, new Handler.Adapter<Stream>()
-        {
-            @Override
-            public void completed(Stream stream)
-            {
-                stream.data(new BytesDataInfo("wee".getBytes(Charset.forName("UTF-8")), false));
-                stream.data(new StringDataInfo("foo", false));
-                stream.data(new ByteBufferDataInfo(Charset.forName("UTF-8").encode("bar"), true));
-            }
-        });
+    @Test
+    public void testAsyncClientRequestWithBodyAndResponseWithBody() throws Exception
+    {
+        Session session = new StandardSession(SPDY.V2, null, null, null, null, null, 1, null, null, null);
+
+        session.syn(new SynInfo(new Fields(), false), new StreamFrameListener.Adapter()
+                {
+                    // The good of passing the listener to push() is that applications can safely
+                    // accumulate info from the reply headers to be used in the data callback,
+                    // e.g. content-type, charset, etc.
+
+                    @Override
+                    public void onReply(Stream stream, ReplyInfo replyInfo)
+                    {
+                        // Do something with the response
+                        Fields headers = replyInfo.getHeaders();
+                        int contentLength = headers.get("content-length").getValueAsInt();
+                        stream.setAttribute("content-length", contentLength);
+                        if (!replyInfo.isClose())
+                            stream.setAttribute("builder", new StringBuilder());
+
+                        // May issue another similar request while waiting for data
+                        try
+                        {
+                            stream.getSession().syn(new SynInfo(new Fields(), true), this);
+                        }
+                        catch (ExecutionException | InterruptedException | TimeoutException e)
+                        {
+                            throw new IllegalStateException(e);
+                        }
+                    }
+
+                    @Override
+                    public void onData(Stream stream, DataInfo dataInfo)
+                    {
+                        StringBuilder builder = (StringBuilder)stream.getAttribute("builder");
+                        builder.append(dataInfo.asString(StandardCharsets.UTF_8, true));
+
+                    }
+                }, new Promise.Adapter<Stream>()
+                {
+                    @Override
+                    public void succeeded(Stream stream)
+                    {
+                        stream.data(new BytesDataInfo("wee".getBytes(StandardCharsets.UTF_8), false), new Callback.Adapter());
+                        stream.data(new StringDataInfo("foo", false), new Callback.Adapter());
+                        stream.data(new ByteBufferDataInfo(StandardCharsets.UTF_8.encode("bar"), true), new Callback.Adapter());
+                    }
+                }
+        );
     }
 }
diff --git a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/api/ServerUsageTest.java b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/api/ServerUsageTest.java
index 3edeaf2..8e57bc1 100644
--- a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/api/ServerUsageTest.java
+++ b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/api/ServerUsageTest.java
@@ -18,9 +18,11 @@
 
 package org.eclipse.jetty.spdy.api;
 
-import java.util.concurrent.TimeUnit;
-
 import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.Fields;
+import org.eclipse.jetty.util.Promise;
+import org.junit.Assert;
 import org.junit.Ignore;
 import org.junit.Test;
 
@@ -30,34 +32,35 @@ public class ServerUsageTest
     @Test
     public void testServerSynAndReplyWithData() throws Exception
     {
-        new ServerSessionFrameListener.Adapter()
+        ServerSessionFrameListener ssfl = new ServerSessionFrameListener.Adapter()
         {
             @Override
             public StreamFrameListener onSyn(Stream stream, SynInfo streamInfo)
             {
-                Headers synHeaders = streamInfo.getHeaders();
+                Fields synHeaders = streamInfo.getHeaders();
                 // Do something with headers, for example extract them and
                 // perform an http request via Jetty's LocalConnector
 
                 // Get the http response, fill headers and data
-                Headers replyHeaders = new Headers();
+                Fields replyHeaders = new Fields();
                 replyHeaders.put(synHeaders.get("host"));
                 // Sends a reply
-                stream.reply(new ReplyInfo(replyHeaders, false));
+                stream.reply(new ReplyInfo(replyHeaders, false), new Callback.Adapter());
 
                 // Sends data
                 StringDataInfo dataInfo = new StringDataInfo("foo", false);
-                stream.data(dataInfo);
+                stream.data(dataInfo, new Callback.Adapter());
                 // Stream is now closed
                 return null;
             }
         };
+        Assert.assertNotNull(ssfl);
     }
 
     @Test
     public void testServerInitiatesStreamAndPushesData() throws Exception
     {
-        new ServerSessionFrameListener.Adapter()
+        ServerSessionFrameListener ssfl = new ServerSessionFrameListener.Adapter()
         {
             @Override
             public void onConnect(Session session)
@@ -71,46 +74,48 @@ public class ServerUsageTest
                 //
                 // However, the API may allow to initiate the stream
 
-                session.syn(new SynInfo(false), null, 0, TimeUnit.MILLISECONDS, new Handler.Adapter<Stream>()
+                session.syn(new SynInfo(new Fields(), false), null, new Promise.Adapter<Stream>()
                 {
                     @Override
-                    public void completed(Stream stream)
+                    public void succeeded(Stream stream)
                     {
                         // The point here is that we have no idea if the client accepted our stream
                         // So we return a stream, we may be able to send the headers frame, but later
                         // the client sends a rst frame.
                         // We have to atomically set some flag on the stream to signal it's closed
                         // and any operation on it will throw
-                        stream.headers(new HeadersInfo(new Headers(), true));
+                        stream.headers(new HeadersInfo(new Fields(), true), new Callback.Adapter());
                     }
                 });
             }
         };
+        Assert.assertNotNull(ssfl);
     }
 
     @Test
     public void testServerPush() throws Exception
     {
-        new ServerSessionFrameListener.Adapter()
+        ServerSessionFrameListener ssfl = new ServerSessionFrameListener.Adapter()
         {
             @Override
             public StreamFrameListener onSyn(Stream stream, SynInfo streamInfo)
             {
                 // Need to send the reply first
-                stream.reply(new ReplyInfo(false));
+                stream.reply(new ReplyInfo(false), new Callback.Adapter());
 
                 Session session = stream.getSession();
                 // Since it's unidirectional, no need to pass the listener
-                session.syn(new SynInfo(new Headers(), false, (byte)0), null, 0, TimeUnit.MILLISECONDS, new Handler.Adapter<Stream>()
+                session.syn(new SynInfo(new Fields(), false, (byte)0), null, new Promise.Adapter<Stream>()
                 {
                     @Override
-                    public void completed(Stream pushStream)
+                    public void succeeded(Stream pushStream)
                     {
-                        pushStream.data(new StringDataInfo("foo", false));
+                        pushStream.data(new StringDataInfo("foo", false), new Callback.Adapter());
                     }
                 });
                 return null;
             }
         };
+        Assert.assertNotNull(ssfl);
     }
 }
diff --git a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/CredentialGenerateParseTest.java b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/CredentialGenerateParseTest.java
index 02aeabf..0e1026f 100644
--- a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/CredentialGenerateParseTest.java
+++ b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/CredentialGenerateParseTest.java
@@ -23,7 +23,7 @@ import java.nio.ByteBuffer;
 import java.security.KeyStore;
 import java.security.cert.Certificate;
 
-import org.eclipse.jetty.spdy.StandardByteBufferPool;
+import org.eclipse.jetty.io.MappedByteBufferPool;
 import org.eclipse.jetty.spdy.StandardCompressionFactory;
 import org.eclipse.jetty.spdy.api.SPDY;
 import org.eclipse.jetty.spdy.generator.Generator;
@@ -44,7 +44,7 @@ public class CredentialGenerateParseTest
         System.arraycopy(temp, 0, certificates, 0, temp.length);
         System.arraycopy(temp, 0, certificates, temp.length, temp.length);
         CredentialFrame frame1 = new CredentialFrame(SPDY.V3, slot, proof, certificates);
-        Generator generator = new Generator(new StandardByteBufferPool(), new StandardCompressionFactory().newCompressor());
+        Generator generator = new Generator(new MappedByteBufferPool(), new StandardCompressionFactory().newCompressor());
         ByteBuffer buffer = generator.control(frame1);
 
         Assert.assertNotNull(buffer);
@@ -72,7 +72,7 @@ public class CredentialGenerateParseTest
         byte[] proof = new byte[]{0, 1, 2};
         Certificate[] certificates = loadCertificates();
         CredentialFrame frame1 = new CredentialFrame(SPDY.V3, slot, proof, certificates);
-        Generator generator = new Generator(new StandardByteBufferPool(), new StandardCompressionFactory().newCompressor());
+        Generator generator = new Generator(new MappedByteBufferPool(), new StandardCompressionFactory().newCompressor());
         ByteBuffer buffer = generator.control(frame1);
 
         Assert.assertNotNull(buffer);
diff --git a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/DataGenerateParseTest.java b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/DataGenerateParseTest.java
index 143875c..af122a5 100644
--- a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/DataGenerateParseTest.java
+++ b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/DataGenerateParseTest.java
@@ -20,7 +20,7 @@ package org.eclipse.jetty.spdy.frames;
 
 import java.nio.ByteBuffer;
 
-import org.eclipse.jetty.spdy.StandardByteBufferPool;
+import org.eclipse.jetty.io.MappedByteBufferPool;
 import org.eclipse.jetty.spdy.StandardCompressionFactory;
 import org.eclipse.jetty.spdy.api.DataInfo;
 import org.eclipse.jetty.spdy.api.StringDataInfo;
@@ -48,7 +48,7 @@ public class DataGenerateParseTest
         int length = content.length();
         DataInfo data = new StringDataInfo(content, true);
         int streamId = 13;
-        Generator generator = new Generator(new StandardByteBufferPool(), new StandardCompressionFactory().newCompressor());
+        Generator generator = new Generator(new MappedByteBufferPool(), new StandardCompressionFactory().newCompressor());
         ByteBuffer buffer = generator.data(streamId, 2 * length, data);
 
         Assert.assertNotNull(buffer);
@@ -73,7 +73,7 @@ public class DataGenerateParseTest
         int length = content.length();
         DataInfo data = new StringDataInfo(content, true);
         int streamId = 13;
-        Generator generator = new Generator(new StandardByteBufferPool(), new StandardCompressionFactory().newCompressor());
+        Generator generator = new Generator(new MappedByteBufferPool(), new StandardCompressionFactory().newCompressor());
         ByteBuffer buffer = generator.data(streamId, 2 * length, data);
 
         Assert.assertNotNull(buffer);
@@ -103,7 +103,7 @@ public class DataGenerateParseTest
         int length = content.length();
         DataInfo data = new StringDataInfo(content, true);
         int streamId = 13;
-        Generator generator = new Generator(new StandardByteBufferPool(), new StandardCompressionFactory().newCompressor());
+        Generator generator = new Generator(new MappedByteBufferPool(), new StandardCompressionFactory().newCompressor());
         ByteBuffer buffer = generator.data(streamId, 2 * length, data);
 
         Assert.assertNotNull(buffer);
diff --git a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/GoAwayGenerateParseTest.java b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/GoAwayGenerateParseTest.java
index 2a7f0bf..d1952d9 100644
--- a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/GoAwayGenerateParseTest.java
+++ b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/GoAwayGenerateParseTest.java
@@ -20,7 +20,7 @@ package org.eclipse.jetty.spdy.frames;
 
 import java.nio.ByteBuffer;
 
-import org.eclipse.jetty.spdy.StandardByteBufferPool;
+import org.eclipse.jetty.io.MappedByteBufferPool;
 import org.eclipse.jetty.spdy.StandardCompressionFactory;
 import org.eclipse.jetty.spdy.api.SPDY;
 import org.eclipse.jetty.spdy.generator.Generator;
@@ -36,7 +36,7 @@ public class GoAwayGenerateParseTest
         int lastStreamId = 13;
         int statusCode = 1;
         GoAwayFrame frame1 = new GoAwayFrame(SPDY.V3, lastStreamId, statusCode);
-        Generator generator = new Generator(new StandardByteBufferPool(), new StandardCompressionFactory().newCompressor());
+        Generator generator = new Generator(new MappedByteBufferPool(), new StandardCompressionFactory().newCompressor());
         ByteBuffer buffer = generator.control(frame1);
 
         Assert.assertNotNull(buffer);
@@ -62,7 +62,7 @@ public class GoAwayGenerateParseTest
         int lastStreamId = 13;
         int statusCode = 1;
         GoAwayFrame frame1 = new GoAwayFrame(SPDY.V3, lastStreamId, statusCode);
-        Generator generator = new Generator(new StandardByteBufferPool(), new StandardCompressionFactory().newCompressor());
+        Generator generator = new Generator(new MappedByteBufferPool(), new StandardCompressionFactory().newCompressor());
         ByteBuffer buffer = generator.control(frame1);
 
         Assert.assertNotNull(buffer);
diff --git a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/HeadersGenerateParseTest.java b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/HeadersGenerateParseTest.java
index 8c7afc4..a458746 100644
--- a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/HeadersGenerateParseTest.java
+++ b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/HeadersGenerateParseTest.java
@@ -18,26 +18,26 @@
 
 package org.eclipse.jetty.spdy.frames;
 
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.junit.Assert.assertThat;
+
 import java.nio.ByteBuffer;
 
-import org.eclipse.jetty.spdy.StandardByteBufferPool;
+import org.eclipse.jetty.io.MappedByteBufferPool;
 import org.eclipse.jetty.spdy.StandardCompressionFactory;
-import org.eclipse.jetty.spdy.api.Headers;
 import org.eclipse.jetty.spdy.api.HeadersInfo;
 import org.eclipse.jetty.spdy.api.SPDY;
 import org.eclipse.jetty.spdy.generator.Generator;
 import org.eclipse.jetty.spdy.parser.Parser;
+import org.eclipse.jetty.util.Fields;
 import org.junit.Before;
 import org.junit.Test;
 
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.CoreMatchers.notNullValue;
-import static org.junit.Assert.assertThat;
-
 public class HeadersGenerateParseTest
 {
 
-    private Headers headers = new Headers();
+    private Fields headers = new Fields();
     private int streamId = 13;
     private byte flags = HeadersInfo.FLAG_RESET_COMPRESSION;
     private final TestSPDYParserListener listener = new TestSPDYParserListener();
@@ -52,10 +52,10 @@ public class HeadersGenerateParseTest
         buffer = createHeadersFrameBuffer(headers);
     }
 
-    private ByteBuffer createHeadersFrameBuffer(Headers headers)
+    private ByteBuffer createHeadersFrameBuffer(Fields headers)
     {
         HeadersFrame frame1 = new HeadersFrame(SPDY.V2, flags, streamId, headers);
-        Generator generator = new Generator(new StandardByteBufferPool(), new StandardCompressionFactory().newCompressor());
+        Generator generator = new Generator(new MappedByteBufferPool(), new StandardCompressionFactory().newCompressor());
         ByteBuffer buffer = generator.control(frame1);
         assertThat("Buffer is not null", buffer, notNullValue());
         return buffer;
@@ -80,15 +80,15 @@ public class HeadersGenerateParseTest
     @Test
     public void testHeadersAreTranslatedToLowerCase()
     {
-        Headers headers = new Headers();
+        Fields headers = new Fields();
         headers.put("Via","localhost");
         parser.parse(createHeadersFrameBuffer(headers));
         HeadersFrame parsedHeadersFrame = assertExpectationsAreMet(headers);
-        Headers.Header viaHeader = parsedHeadersFrame.getHeaders().get("via");
-        assertThat("Via Header name is lowercase", viaHeader.name(), is("via"));
+        Fields.Field viaHeader = parsedHeadersFrame.getHeaders().get("via");
+        assertThat("Via Header name is lowercase", viaHeader.getName(), is("via"));
     }
 
-    private HeadersFrame assertExpectationsAreMet(Headers headers)
+    private HeadersFrame assertExpectationsAreMet(Fields headers)
     {
         ControlFrame parsedControlFrame = listener.getControlFrame();
         assertThat("listener received controlFrame", parsedControlFrame, notNullValue());
diff --git a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/NoOpGenerateParseTest.java b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/NoOpGenerateParseTest.java
index 94b2032..151dd5e 100644
--- a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/NoOpGenerateParseTest.java
+++ b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/NoOpGenerateParseTest.java
@@ -20,7 +20,7 @@ package org.eclipse.jetty.spdy.frames;
 
 import java.nio.ByteBuffer;
 
-import org.eclipse.jetty.spdy.StandardByteBufferPool;
+import org.eclipse.jetty.io.MappedByteBufferPool;
 import org.eclipse.jetty.spdy.StandardCompressionFactory;
 import org.eclipse.jetty.spdy.generator.Generator;
 import org.eclipse.jetty.spdy.parser.Parser;
@@ -33,7 +33,7 @@ public class NoOpGenerateParseTest
     public void testGenerateParse() throws Exception
     {
         NoOpFrame frame1 = new NoOpFrame();
-        Generator generator = new Generator(new StandardByteBufferPool(), new StandardCompressionFactory().newCompressor());
+        Generator generator = new Generator(new MappedByteBufferPool(), new StandardCompressionFactory().newCompressor());
         ByteBuffer buffer = generator.control(frame1);
 
         Assert.assertNotNull(buffer);
@@ -54,7 +54,7 @@ public class NoOpGenerateParseTest
     public void testGenerateParseOneByteAtATime() throws Exception
     {
         NoOpFrame frame1 = new NoOpFrame();
-        Generator generator = new Generator(new StandardByteBufferPool(), new StandardCompressionFactory().newCompressor());
+        Generator generator = new Generator(new MappedByteBufferPool(), new StandardCompressionFactory().newCompressor());
         ByteBuffer buffer = generator.control(frame1);
 
         Assert.assertNotNull(buffer);
diff --git a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/PingGenerateParseTest.java b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/PingGenerateParseTest.java
index ec8e27a..b613901 100644
--- a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/PingGenerateParseTest.java
+++ b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/PingGenerateParseTest.java
@@ -20,7 +20,7 @@ package org.eclipse.jetty.spdy.frames;
 
 import java.nio.ByteBuffer;
 
-import org.eclipse.jetty.spdy.StandardByteBufferPool;
+import org.eclipse.jetty.io.MappedByteBufferPool;
 import org.eclipse.jetty.spdy.StandardCompressionFactory;
 import org.eclipse.jetty.spdy.api.SPDY;
 import org.eclipse.jetty.spdy.generator.Generator;
@@ -35,7 +35,7 @@ public class PingGenerateParseTest
     {
         int pingId = 13;
         PingFrame frame1 = new PingFrame(SPDY.V2, pingId);
-        Generator generator = new Generator(new StandardByteBufferPool(), new StandardCompressionFactory().newCompressor());
+        Generator generator = new Generator(new MappedByteBufferPool(), new StandardCompressionFactory().newCompressor());
         ByteBuffer buffer = generator.control(frame1);
 
         Assert.assertNotNull(buffer);
@@ -59,7 +59,7 @@ public class PingGenerateParseTest
     {
         int pingId = 13;
         PingFrame frame1 = new PingFrame(SPDY.V2, pingId);
-        Generator generator = new Generator(new StandardByteBufferPool(), new StandardCompressionFactory().newCompressor());
+        Generator generator = new Generator(new MappedByteBufferPool(), new StandardCompressionFactory().newCompressor());
         ByteBuffer buffer = generator.control(frame1);
 
         Assert.assertNotNull(buffer);
diff --git a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/RstStreamGenerateParseTest.java b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/RstStreamGenerateParseTest.java
index 25af593..030a73f 100644
--- a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/RstStreamGenerateParseTest.java
+++ b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/RstStreamGenerateParseTest.java
@@ -26,7 +26,7 @@ import static org.junit.Assert.assertThat;
 
 import java.nio.ByteBuffer;
 
-import org.eclipse.jetty.spdy.StandardByteBufferPool;
+import org.eclipse.jetty.io.MappedByteBufferPool;
 import org.eclipse.jetty.spdy.StandardCompressionFactory;
 import org.eclipse.jetty.spdy.api.SPDY;
 import org.eclipse.jetty.spdy.api.StreamStatus;
@@ -43,7 +43,7 @@ public class RstStreamGenerateParseTest
         int streamId = 13;
         int streamStatus = StreamStatus.UNSUPPORTED_VERSION.getCode(SPDY.V2);
         RstStreamFrame frame1 = new RstStreamFrame(SPDY.V2, streamId, streamStatus);
-        Generator generator = new Generator(new StandardByteBufferPool(), new StandardCompressionFactory().newCompressor());
+        Generator generator = new Generator(new MappedByteBufferPool(), new StandardCompressionFactory().newCompressor());
         ByteBuffer buffer = generator.control(frame1);
 
         assertThat("buffer is not null", buffer, not(nullValue()));
@@ -69,7 +69,7 @@ public class RstStreamGenerateParseTest
         int streamId = 13;
         int streamStatus = StreamStatus.UNSUPPORTED_VERSION.getCode(SPDY.V2);
         RstStreamFrame frame1 = new RstStreamFrame(SPDY.V2, streamId, streamStatus);
-        Generator generator = new Generator(new StandardByteBufferPool(), new StandardCompressionFactory().newCompressor());
+        Generator generator = new Generator(new MappedByteBufferPool(), new StandardCompressionFactory().newCompressor());
         ByteBuffer buffer = generator.control(frame1);
 
         Assert.assertNotNull(buffer);
diff --git a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/SettingsGenerateParseTest.java b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/SettingsGenerateParseTest.java
index 0ad4965..d27b5d7 100644
--- a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/SettingsGenerateParseTest.java
+++ b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/SettingsGenerateParseTest.java
@@ -20,7 +20,7 @@ package org.eclipse.jetty.spdy.frames;
 
 import java.nio.ByteBuffer;
 
-import org.eclipse.jetty.spdy.StandardByteBufferPool;
+import org.eclipse.jetty.io.MappedByteBufferPool;
 import org.eclipse.jetty.spdy.StandardCompressionFactory;
 import org.eclipse.jetty.spdy.api.SPDY;
 import org.eclipse.jetty.spdy.api.Settings;
@@ -40,7 +40,7 @@ public class SettingsGenerateParseTest
         settings.put(new Settings.Setting(Settings.ID.MAX_CONCURRENT_STREAMS, Settings.Flag.PERSIST, 100));
         settings.put(new Settings.Setting(Settings.ID.ROUND_TRIP_TIME, Settings.Flag.PERSISTED, 500));
         SettingsFrame frame1 = new SettingsFrame(SPDY.V2, flags, settings);
-        Generator generator = new Generator(new StandardByteBufferPool(), new StandardCompressionFactory().newCompressor());
+        Generator generator = new Generator(new MappedByteBufferPool(), new StandardCompressionFactory().newCompressor());
         ByteBuffer buffer = generator.control(frame1);
 
         Assert.assertNotNull(buffer);
@@ -67,7 +67,7 @@ public class SettingsGenerateParseTest
         settings.put(new Settings.Setting(Settings.ID.DOWNLOAD_RETRANSMISSION_RATE, 100));
         settings.put(new Settings.Setting(Settings.ID.ROUND_TRIP_TIME, 500));
         SettingsFrame frame1 = new SettingsFrame(SPDY.V2, flags, settings);
-        Generator generator = new Generator(new StandardByteBufferPool(), new StandardCompressionFactory().newCompressor());
+        Generator generator = new Generator(new MappedByteBufferPool(), new StandardCompressionFactory().newCompressor());
         ByteBuffer buffer = generator.control(frame1);
 
         Assert.assertNotNull(buffer);
diff --git a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/SynReplyGenerateParseTest.java b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/SynReplyGenerateParseTest.java
index 2ab45ab..9ef380b 100644
--- a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/SynReplyGenerateParseTest.java
+++ b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/SynReplyGenerateParseTest.java
@@ -20,13 +20,13 @@ package org.eclipse.jetty.spdy.frames;
 
 import java.nio.ByteBuffer;
 
-import org.eclipse.jetty.spdy.StandardByteBufferPool;
+import org.eclipse.jetty.io.MappedByteBufferPool;
 import org.eclipse.jetty.spdy.StandardCompressionFactory;
-import org.eclipse.jetty.spdy.api.Headers;
 import org.eclipse.jetty.spdy.api.ReplyInfo;
 import org.eclipse.jetty.spdy.api.SPDY;
 import org.eclipse.jetty.spdy.generator.Generator;
 import org.eclipse.jetty.spdy.parser.Parser;
+import org.eclipse.jetty.util.Fields;
 import org.junit.Assert;
 import org.junit.Test;
 
@@ -37,10 +37,10 @@ public class SynReplyGenerateParseTest
     {
         byte flags = ReplyInfo.FLAG_CLOSE;
         int streamId = 13;
-        Headers headers = new Headers();
+        Fields headers = new Fields();
         headers.put("a", "b");
         SynReplyFrame frame1 = new SynReplyFrame(SPDY.V2, flags, streamId, headers);
-        Generator generator = new Generator(new StandardByteBufferPool(), new StandardCompressionFactory().newCompressor());
+        Generator generator = new Generator(new MappedByteBufferPool(), new StandardCompressionFactory().newCompressor());
         ByteBuffer buffer = generator.control(frame1);
 
         Assert.assertNotNull(buffer);
@@ -65,10 +65,10 @@ public class SynReplyGenerateParseTest
     {
         byte flags = ReplyInfo.FLAG_CLOSE;
         int streamId = 13;
-        Headers headers = new Headers();
+        Fields headers = new Fields();
         headers.put("a", "b");
         SynReplyFrame frame1 = new SynReplyFrame(SPDY.V2, flags, streamId, headers);
-        Generator generator = new Generator(new StandardByteBufferPool(), new StandardCompressionFactory().newCompressor());
+        Generator generator = new Generator(new MappedByteBufferPool(), new StandardCompressionFactory().newCompressor());
         ByteBuffer buffer = generator.control(frame1);
 
         Assert.assertNotNull(buffer);
diff --git a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/SynStreamGenerateParseTest.java b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/SynStreamGenerateParseTest.java
index 84ea76c..7651d04 100644
--- a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/SynStreamGenerateParseTest.java
+++ b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/SynStreamGenerateParseTest.java
@@ -20,13 +20,13 @@ package org.eclipse.jetty.spdy.frames;
 
 import java.nio.ByteBuffer;
 
-import org.eclipse.jetty.spdy.StandardByteBufferPool;
+import org.eclipse.jetty.io.MappedByteBufferPool;
 import org.eclipse.jetty.spdy.StandardCompressionFactory;
-import org.eclipse.jetty.spdy.api.Headers;
 import org.eclipse.jetty.spdy.api.SPDY;
 import org.eclipse.jetty.spdy.api.SynInfo;
 import org.eclipse.jetty.spdy.generator.Generator;
 import org.eclipse.jetty.spdy.parser.Parser;
+import org.eclipse.jetty.util.Fields;
 import org.junit.Assert;
 import org.junit.Test;
 
@@ -40,11 +40,11 @@ public class SynStreamGenerateParseTest
         int associatedStreamId = 11;
         byte priority = 3;
         short slot = 5;
-        Headers headers = new Headers();
+        Fields headers = new Fields();
         headers.put("a", "b");
         headers.put("c", "d");
         SynStreamFrame frame1 = new SynStreamFrame(SPDY.V2, flags, streamId, associatedStreamId, priority, slot, headers);
-        Generator generator = new Generator(new StandardByteBufferPool(), new StandardCompressionFactory().newCompressor());
+        Generator generator = new Generator(new MappedByteBufferPool(), new StandardCompressionFactory().newCompressor());
         ByteBuffer buffer = generator.control(frame1);
 
         Assert.assertNotNull(buffer);
@@ -75,11 +75,11 @@ public class SynStreamGenerateParseTest
         int associatedStreamId = 11;
         byte priority = 3;
         short slot = 5;
-        Headers headers = new Headers();
+        Fields headers = new Fields();
         headers.put("a", "b");
         headers.put("c", "d");
         SynStreamFrame frame1 = new SynStreamFrame(SPDY.V2, flags, streamId, associatedStreamId, priority, slot, headers);
-        Generator generator = new Generator(new StandardByteBufferPool(), new StandardCompressionFactory().newCompressor());
+        Generator generator = new Generator(new MappedByteBufferPool(), new StandardCompressionFactory().newCompressor());
         ByteBuffer buffer = generator.control(frame1);
 
         Assert.assertNotNull(buffer);
diff --git a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/WindowUpdateGenerateParseTest.java b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/WindowUpdateGenerateParseTest.java
index a465741..062e93b 100644
--- a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/WindowUpdateGenerateParseTest.java
+++ b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/frames/WindowUpdateGenerateParseTest.java
@@ -20,7 +20,7 @@ package org.eclipse.jetty.spdy.frames;
 
 import java.nio.ByteBuffer;
 
-import org.eclipse.jetty.spdy.StandardByteBufferPool;
+import org.eclipse.jetty.io.MappedByteBufferPool;
 import org.eclipse.jetty.spdy.StandardCompressionFactory;
 import org.eclipse.jetty.spdy.api.SPDY;
 import org.eclipse.jetty.spdy.generator.Generator;
@@ -36,7 +36,7 @@ public class WindowUpdateGenerateParseTest
         int streamId = 13;
         int windowDelta = 17;
         WindowUpdateFrame frame1 = new WindowUpdateFrame(SPDY.V2, streamId, windowDelta);
-        Generator generator = new Generator(new StandardByteBufferPool(), new StandardCompressionFactory().newCompressor());
+        Generator generator = new Generator(new MappedByteBufferPool(), new StandardCompressionFactory().newCompressor());
         ByteBuffer buffer = generator.control(frame1);
 
         Assert.assertNotNull(buffer);
@@ -62,7 +62,7 @@ public class WindowUpdateGenerateParseTest
         int streamId = 13;
         int windowDelta = 17;
         WindowUpdateFrame frame1 = new WindowUpdateFrame(SPDY.V2, streamId, windowDelta);
-        Generator generator = new Generator(new StandardByteBufferPool(), new StandardCompressionFactory().newCompressor());
+        Generator generator = new Generator(new MappedByteBufferPool(), new StandardCompressionFactory().newCompressor());
         ByteBuffer buffer = generator.control(frame1);
 
         Assert.assertNotNull(buffer);
diff --git a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/generator/DataFrameGeneratorTest.java b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/generator/DataFrameGeneratorTest.java
new file mode 100644
index 0000000..d9bf4d0
--- /dev/null
+++ b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/generator/DataFrameGeneratorTest.java
@@ -0,0 +1,110 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.spdy.generator;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+import java.nio.ByteBuffer;
+import java.util.concurrent.ThreadLocalRandom;
+
+import org.eclipse.jetty.io.ArrayByteBufferPool;
+import org.eclipse.jetty.spdy.api.ByteBufferDataInfo;
+import org.eclipse.jetty.spdy.api.DataInfo;
+import org.eclipse.jetty.spdy.frames.DataFrame;
+import org.eclipse.jetty.util.BufferUtil;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+ at RunWith(JUnit4.class)
+public class DataFrameGeneratorTest
+{
+    private int increment = 1024;
+    private int streamId = 1;
+    private ArrayByteBufferPool bufferPool;
+    private DataFrameGenerator dataFrameGenerator;
+    private ByteBuffer headerBuffer = ByteBuffer.allocate(DataFrame.HEADER_LENGTH);
+
+    @Before
+    public void setUp()
+    {
+        bufferPool = new ArrayByteBufferPool(64, increment, 8192);
+        dataFrameGenerator = new DataFrameGenerator(bufferPool);
+        headerBuffer.putInt(0, streamId & 0x7F_FF_FF_FF);
+
+    }
+
+    @Test
+    public void testGenerateSmallFrame()
+    {
+        int bufferSize = 256;
+        generateFrame(bufferSize);
+    }
+
+    @Test
+    public void testGenerateFrameWithBufferThatEqualsBucketSize()
+    {
+        int bufferSize = increment;
+        generateFrame(bufferSize);
+    }
+
+    @Test
+    public void testGenerateFrameWithBufferThatEqualsBucketSizeMinusHeaderLength()
+    {
+        int bufferSize = increment - DataFrame.HEADER_LENGTH;
+        generateFrame(bufferSize);
+    }
+
+    private void generateFrame(int bufferSize)
+    {
+        ByteBuffer byteBuffer = createByteBuffer(bufferSize);
+        ByteBufferDataInfo dataInfo = new ByteBufferDataInfo(byteBuffer, true);
+        fillHeaderBuffer(bufferSize);
+        ByteBuffer dataFrameBuffer = dataFrameGenerator.generate(streamId, bufferSize, dataInfo);
+
+        assertThat("The content size in dataFrameBuffer matches the buffersize + header length",
+                dataFrameBuffer.limit(),
+                is(bufferSize + DataFrame.HEADER_LENGTH));
+
+        byte[] headerBytes = new byte[DataFrame.HEADER_LENGTH];
+        dataFrameBuffer.get(headerBytes, 0, DataFrame.HEADER_LENGTH);
+        
+        assertThat("Header bytes are prepended", headerBytes, is(headerBuffer.array()));
+    }
+
+    private ByteBuffer createByteBuffer(int bufferSize)
+    {
+        byte[] bytes = new byte[bufferSize];
+        ThreadLocalRandom.current().nextBytes(bytes);
+        ByteBuffer byteBuffer = bufferPool.acquire(bufferSize, false);
+        BufferUtil.flipToFill(byteBuffer);
+        byteBuffer.put(bytes);
+        BufferUtil.flipToFlush(byteBuffer, 0);
+        return byteBuffer;
+    }
+
+    private void fillHeaderBuffer(int bufferSize)
+    {
+        headerBuffer.putInt(4, bufferSize & 0x00_FF_FF_FF);
+        headerBuffer.put(4, DataInfo.FLAG_CLOSE);
+    }
+
+}
diff --git a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/parser/BrokenFrameTest.java b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/parser/BrokenFrameTest.java
new file mode 100644
index 0000000..df8b2e9
--- /dev/null
+++ b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/parser/BrokenFrameTest.java
@@ -0,0 +1,287 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.spdy.parser;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
+import java.io.ByteArrayOutputStream;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.zip.ZipException;
+
+import org.eclipse.jetty.io.MappedByteBufferPool;
+import org.eclipse.jetty.spdy.CompressionFactory;
+import org.eclipse.jetty.spdy.StreamException;
+import org.eclipse.jetty.spdy.api.SPDY;
+import org.eclipse.jetty.spdy.api.SynInfo;
+import org.eclipse.jetty.spdy.frames.ControlFrame;
+import org.eclipse.jetty.spdy.frames.SynStreamFrame;
+import org.eclipse.jetty.spdy.generator.Generator;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Fields;
+import org.junit.Test;
+
+public class BrokenFrameTest
+{
+
+    @Test
+    public void testInvalidHeaderNameLength() throws Exception
+    {
+        Fields headers = new Fields();
+        headers.add("broken", "header");
+        SynStreamFrame frame = new SynStreamFrame(SPDY.V2, SynInfo.FLAG_CLOSE, 1, 0, (byte)0, (short)0, headers);
+        Generator generator = new Generator(new MappedByteBufferPool(), new NoCompressionCompressionFactory.NoCompressionCompressor());
+
+        ByteBuffer bufferWithBrokenHeaderNameLength = generator.control(frame);
+        // Break the header name length to provoke the Parser to throw a StreamException
+        bufferWithBrokenHeaderNameLength.put(21, (byte)0);
+
+        ByteBuffer bufferWithValidSynStreamFrame = generator.control(frame);
+
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        outputStream.write(BufferUtil.toArray(bufferWithBrokenHeaderNameLength));
+        outputStream.write(BufferUtil.toArray(bufferWithValidSynStreamFrame));
+
+        byte concatenatedFramesByteArray[] = outputStream.toByteArray();
+        ByteBuffer concatenatedBuffer = BufferUtil.toBuffer(concatenatedFramesByteArray);
+
+        final CountDownLatch latch = new CountDownLatch(2);
+        Parser parser = new Parser(new NoCompressionCompressionFactory.NoCompressionDecompressor());
+        parser.addListener(new Parser.Listener.Adapter()
+        {
+            @Override
+            public void onControlFrame(ControlFrame frame)
+            {
+                latch.countDown();
+            }
+
+            @Override
+            public void onStreamException(StreamException x)
+            {
+                latch.countDown();
+            }
+        });
+        parser.parse(concatenatedBuffer);
+
+        assertThat(latch.await(5, TimeUnit.SECONDS), is(true));
+    }
+
+    @Test
+    public void testInvalidVersion() throws Exception
+    {
+        Fields headers = new Fields();
+        headers.add("good", "header");
+        headers.add("another","header");
+        SynStreamFrame frame = new SynStreamFrame(SPDY.V2, SynInfo.FLAG_CLOSE, 1, 0, (byte)0, (short)0, headers);
+        Generator generator = new Generator(new MappedByteBufferPool(), new NoCompressionCompressionFactory.NoCompressionCompressor());
+
+        ByteBuffer bufferWithBrokenVersion = generator.control(frame);
+        // Break the header name length to provoke the Parser to throw a StreamException
+        bufferWithBrokenVersion.put(1, (byte)4);
+
+        ByteBuffer bufferWithValidSynStreamFrame = generator.control(frame);
+
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        outputStream.write(BufferUtil.toArray(bufferWithBrokenVersion));
+        outputStream.write(BufferUtil.toArray(bufferWithValidSynStreamFrame));
+
+        byte concatenatedFramesByteArray[] = outputStream.toByteArray();
+        ByteBuffer concatenatedBuffer = BufferUtil.toBuffer(concatenatedFramesByteArray);
+
+        final CountDownLatch latch = new CountDownLatch(2);
+        Parser parser = new Parser(new NoCompressionCompressionFactory.NoCompressionDecompressor());
+        parser.addListener(new Parser.Listener.Adapter()
+        {
+            @Override
+            public void onControlFrame(ControlFrame frame)
+            {
+                latch.countDown();
+            }
+
+            @Override
+            public void onStreamException(StreamException x)
+            {
+                latch.countDown();
+            }
+        });
+        parser.parse(concatenatedBuffer);
+
+        assertThat(latch.await(5, TimeUnit.SECONDS), is(true));
+    }
+
+    @Test
+    public void testInvalidVersionWithSplitBuffer() throws Exception
+    {
+        Fields headers = new Fields();
+        headers.add("good", "header");
+        headers.add("another","header");
+        SynStreamFrame frame = new SynStreamFrame(SPDY.V2, SynInfo.FLAG_CLOSE, 1, 0, (byte)0, (short)0, headers);
+        Generator generator = new Generator(new MappedByteBufferPool(), new NoCompressionCompressionFactory.NoCompressionCompressor());
+
+        ByteBuffer bufferWithBrokenVersion = generator.control(frame);
+        // Break the header name length to provoke the Parser to throw a StreamException
+        bufferWithBrokenVersion.put(1, (byte)4);
+
+        ByteBuffer bufferWithValidSynStreamFrame = generator.control(frame);
+
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        outputStream.write(BufferUtil.toArray(bufferWithBrokenVersion));
+        outputStream.write(BufferUtil.toArray(bufferWithValidSynStreamFrame));
+
+        byte concatenatedFramesByteArray[] = outputStream.toByteArray();
+        ByteBuffer concatenatedBuffer1 = BufferUtil.toBuffer(Arrays.copyOfRange(concatenatedFramesByteArray,0,20));
+        ByteBuffer concatenatedBuffer2 = BufferUtil.toBuffer(Arrays.copyOfRange(concatenatedFramesByteArray,20,
+                concatenatedFramesByteArray.length));
+
+        final CountDownLatch latch = new CountDownLatch(2);
+        Parser parser = new Parser(new NoCompressionCompressionFactory.NoCompressionDecompressor());
+        parser.addListener(new Parser.Listener.Adapter()
+        {
+            @Override
+            public void onControlFrame(ControlFrame frame)
+            {
+                latch.countDown();
+            }
+
+            @Override
+            public void onStreamException(StreamException x)
+            {
+                latch.countDown();
+            }
+        });
+        parser.parse(concatenatedBuffer1);
+        parser.parse(concatenatedBuffer2);
+
+        assertThat(latch.await(5, TimeUnit.SECONDS), is(true));
+    }
+
+    @Test
+    public void testInvalidVersionAndGoodFrameSplitInThreeBuffers() throws Exception
+    {
+        Fields headers = new Fields();
+        headers.add("good", "header");
+        headers.add("another","header");
+        SynStreamFrame frame = new SynStreamFrame(SPDY.V2, SynInfo.FLAG_CLOSE, 1, 0, (byte)0, (short)0, headers);
+        Generator generator = new Generator(new MappedByteBufferPool(), new NoCompressionCompressionFactory.NoCompressionCompressor());
+
+        ByteBuffer bufferWithBrokenVersion = generator.control(frame);
+        // Break the header name length to provoke the Parser to throw a StreamException
+        bufferWithBrokenVersion.put(1, (byte)4);
+
+        ByteBuffer bufferWithValidSynStreamFrame = generator.control(frame);
+
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        outputStream.write(BufferUtil.toArray(bufferWithBrokenVersion));
+        outputStream.write(BufferUtil.toArray(bufferWithValidSynStreamFrame));
+
+        byte concatenatedFramesByteArray[] = outputStream.toByteArray();
+        ByteBuffer concatenatedBuffer1 = BufferUtil.toBuffer(Arrays.copyOfRange(concatenatedFramesByteArray,0,20));
+        ByteBuffer concatenatedBuffer2 = BufferUtil.toBuffer(Arrays.copyOfRange(concatenatedFramesByteArray,20, 30));
+        ByteBuffer concatenatedBuffer3 = BufferUtil.toBuffer(Arrays.copyOfRange(concatenatedFramesByteArray,30,
+                concatenatedFramesByteArray.length));
+
+        final CountDownLatch latch = new CountDownLatch(2);
+        Parser parser = new Parser(new NoCompressionCompressionFactory.NoCompressionDecompressor());
+        parser.addListener(new Parser.Listener.Adapter()
+        {
+            @Override
+            public void onControlFrame(ControlFrame frame)
+            {
+                latch.countDown();
+            }
+
+            @Override
+            public void onStreamException(StreamException x)
+            {
+                latch.countDown();
+            }
+        });
+        parser.parse(concatenatedBuffer1);
+        parser.parse(concatenatedBuffer2);
+        parser.parse(concatenatedBuffer3);
+
+        assertThat(latch.await(5, TimeUnit.SECONDS), is(true));
+    }
+
+    private static class NoCompressionCompressionFactory implements CompressionFactory
+    {
+
+        @Override
+        public Compressor newCompressor()
+        {
+            return null;
+        }
+
+        @Override
+        public Decompressor newDecompressor()
+        {
+            return null;
+        }
+
+        public static class NoCompressionCompressor implements Compressor
+        {
+
+            private byte[] input;
+
+            @Override
+            public void setInput(byte[] input)
+            {
+                this.input = input;
+            }
+
+            @Override
+            public void setDictionary(byte[] dictionary)
+            {
+            }
+
+            @Override
+            public int compress(byte[] output)
+            {
+                System.arraycopy(input, 0, output, 0, input.length);
+                return input.length;
+            }
+        }
+
+        public static class NoCompressionDecompressor implements Decompressor
+        {
+            private byte[] input;
+
+            @Override
+            public void setDictionary(byte[] dictionary)
+            {
+            }
+
+            @Override
+            public void setInput(byte[] input)
+            {
+                this.input = input;
+            }
+
+            @Override
+            public int decompress(byte[] output) throws ZipException
+            {
+                System.arraycopy(input, 0, output, 0, input.length);
+                return input.length;
+            }
+        }
+    }
+}
diff --git a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/parser/ParseVersusCacheBenchmarkTest.java b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/parser/ParseVersusCacheBenchmarkTest.java
index 94e4d09..d656733 100644
--- a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/parser/ParseVersusCacheBenchmarkTest.java
+++ b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/parser/ParseVersusCacheBenchmarkTest.java
@@ -19,7 +19,7 @@
 package org.eclipse.jetty.spdy.parser;
 
 import java.nio.ByteBuffer;
-import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
@@ -43,8 +43,7 @@ public class ParseVersusCacheBenchmarkTest
 
         String name = "Content-Type";
         String value = "application/octect-stream";
-        Charset charset = Charset.forName("ISO-8859-1");
-        ByteBuffer buffer = ByteBuffer.wrap((name + value).getBytes(charset));
+        ByteBuffer buffer = ByteBuffer.wrap((name + value).getBytes(StandardCharsets.ISO_8859_1));
         int iterations = 100_000_000;
 
         long begin = System.nanoTime();
@@ -52,12 +51,12 @@ public class ParseVersusCacheBenchmarkTest
         {
             byte[] nameBytes = new byte[name.length()];
             buffer.get(nameBytes);
-            String name2 = new String(nameBytes, charset);
+            String name2 = new String(nameBytes, StandardCharsets.ISO_8859_1);
             Assert.assertEquals(name2, name);
 
             byte[] valueBytes = new byte[value.length()];
             buffer.get(valueBytes);
-            String value2 = new String(valueBytes, charset);
+            String value2 = new String(valueBytes, StandardCharsets.ISO_8859_1);
             Assert.assertEquals(value2, value);
 
             buffer.flip();
@@ -66,8 +65,8 @@ public class ParseVersusCacheBenchmarkTest
         System.err.printf("parse time: %d%n", TimeUnit.NANOSECONDS.toMillis(end - begin));
 
         Map<ByteBuffer, String> map = new HashMap<>();
-        map.put(ByteBuffer.wrap(name.getBytes(charset)), name);
-        map.put(ByteBuffer.wrap(value.getBytes(charset)), value);
+        map.put(ByteBuffer.wrap(name.getBytes(StandardCharsets.ISO_8859_1)), name);
+        map.put(ByteBuffer.wrap(value.getBytes(StandardCharsets.ISO_8859_1)), value);
         final Map<ByteBuffer, String> cache = Collections.unmodifiableMap(map);
 
         begin = System.nanoTime();
diff --git a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/parser/UnknownControlFrameTest.java b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/parser/UnknownControlFrameTest.java
index 49d0233..e46a527 100644
--- a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/parser/UnknownControlFrameTest.java
+++ b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/parser/UnknownControlFrameTest.java
@@ -22,17 +22,17 @@ import java.nio.ByteBuffer;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
+import org.eclipse.jetty.io.MappedByteBufferPool;
 import org.eclipse.jetty.spdy.SessionException;
-import org.eclipse.jetty.spdy.StandardByteBufferPool;
 import org.eclipse.jetty.spdy.StandardCompressionFactory;
 import org.eclipse.jetty.spdy.StreamException;
-import org.eclipse.jetty.spdy.api.Headers;
 import org.eclipse.jetty.spdy.api.SPDY;
 import org.eclipse.jetty.spdy.api.SynInfo;
 import org.eclipse.jetty.spdy.frames.ControlFrame;
 import org.eclipse.jetty.spdy.frames.DataFrame;
 import org.eclipse.jetty.spdy.frames.SynStreamFrame;
 import org.eclipse.jetty.spdy.generator.Generator;
+import org.eclipse.jetty.util.Fields;
 import org.junit.Assert;
 import org.junit.Test;
 
@@ -41,8 +41,8 @@ public class UnknownControlFrameTest
     @Test
     public void testUnknownControlFrame() throws Exception
     {
-        SynStreamFrame frame = new SynStreamFrame(SPDY.V2, SynInfo.FLAG_CLOSE, 1, 0, (byte)0, (short)0, new Headers());
-        Generator generator = new Generator(new StandardByteBufferPool(), new StandardCompressionFactory.StandardCompressor());
+        SynStreamFrame frame = new SynStreamFrame(SPDY.V2, SynInfo.FLAG_CLOSE, 1, 0, (byte)0, (short)0, new Fields());
+        Generator generator = new Generator(new MappedByteBufferPool(), new StandardCompressionFactory.StandardCompressor());
         ByteBuffer buffer = generator.control(frame);
         // Change the frame type to unknown
         buffer.putShort(2, (short)0);
diff --git a/jetty-spdy/spdy-core/src/test/resources/jetty-logging.properties b/jetty-spdy/spdy-core/src/test/resources/jetty-logging.properties
new file mode 100644
index 0000000..5250a08
--- /dev/null
+++ b/jetty-spdy/spdy-core/src/test/resources/jetty-logging.properties
@@ -0,0 +1,2 @@
+org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
+org.eclipse.jetty.spdy.LEVEL=WARN
diff --git a/jetty-spdy/spdy-core/src/test/resources/log4j.properties b/jetty-spdy/spdy-core/src/test/resources/log4j.properties
deleted file mode 100644
index aa88d64..0000000
--- a/jetty-spdy/spdy-core/src/test/resources/log4j.properties
+++ /dev/null
@@ -1,14 +0,0 @@
-# LOG4J levels: OFF, FATAL, ERROR, WARN, INFO, DEBUG, ALL
-#
-log4j.rootLogger=ALL,CONSOLE
-
-log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
-#log4j.appender.CONSOLE.threshold=INFO
-log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
-#log4j.appender.CONSOLE.layout.ConversionPattern=%d %t [%5p][%c{1}] %m%n
-log4j.appender.CONSOLE.layout.ConversionPattern=%d %t [%5p][%c{1}] %m%n
-log4j.appender.CONSOLE.target=System.err
-
-# Level tuning
-log4j.logger.org.eclipse.jetty=INFO
-#log4j.logger.org.eclipse.jetty.spdy=DEBUG
diff --git a/jetty-spdy/spdy-example-webapp/pom.xml b/jetty-spdy/spdy-example-webapp/pom.xml
new file mode 100644
index 0000000..cb48621
--- /dev/null
+++ b/jetty-spdy/spdy-example-webapp/pom.xml
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>org.eclipse.jetty.spdy</groupId>
+        <artifactId>spdy-parent</artifactId>
+        <version>9.2.13.v20150730</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>spdy-example-webapp</artifactId>
+    <packaging>war</packaging>
+    <name>Jetty :: SPDY :: HTTP Web Application</name>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.eclipse.jetty</groupId>
+                <artifactId>jetty-maven-plugin</artifactId>
+                <version>${project.version}</version>
+                <configuration>
+                    <stopPort>8888</stopPort>
+                    <stopKey>quit</stopKey>
+                    <jvmArgs>
+                        -Xbootclasspath/p:${settings.localRepository}/org/mortbay/jetty/npn/npn-boot/${npn.version}/npn-boot-${npn.version}.jar
+                    </jvmArgs>
+                    <jettyXml>${basedir}/src/main/config/example-jetty-spdy.xml</jettyXml>
+                    <contextPath>/</contextPath>
+                    <excludedGoals>
+                       <excludedGoal>run</excludedGoal>
+                       <excludedGoal>run-war</excludedGoal>
+                       <excludedGoal>deploy</excludedGoal>
+                       <excludedGoal>start</excludedGoal>
+                       <excludedGoal>stop</excludedGoal>
+                    </excludedGoals>
+                </configuration>
+                <dependencies>
+                    <dependency>
+                        <groupId>org.eclipse.jetty.spdy</groupId>
+                        <artifactId>spdy-http-server</artifactId>
+                        <version>${project.version}</version>
+                    </dependency>
+                </dependencies>
+            </plugin>
+        </plugins>
+    </build>
+
+    <profiles>
+        <profile>
+            <id>proxy</id>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.eclipse.jetty</groupId>
+                        <artifactId>jetty-maven-plugin</artifactId>
+                        <version>${project.version}</version>
+                        <configuration>
+                            <stopPort>8888</stopPort>
+                            <stopKey>quit</stopKey>
+                            <jvmArgs>
+                                -Xbootclasspath/p:${settings.localRepository}/org/mortbay/jetty/npn/npn-boot/${npn.version}/npn-boot-${npn.version}.jar
+                            </jvmArgs>
+                            <jettyXml>${basedir}/src/main/config/example-jetty-spdy-proxy.xml</jettyXml>
+                            <contextPath>/</contextPath>
+                            <excludedGoals>
+                               <excludedGoal>run</excludedGoal>
+                               <excludedGoal>run-war</excludedGoal>
+                               <excludedGoal>deploy</excludedGoal>
+                               <excludedGoal>start</excludedGoal>
+                               <excludedGoal>stop</excludedGoal>
+                            </excludedGoals>
+                        </configuration>
+                        <dependencies>
+                            <dependency>
+                                <groupId>org.eclipse.jetty.spdy</groupId>
+                                <artifactId>spdy-http-server</artifactId>
+                                <version>${project.version}</version>
+                            </dependency>
+                        </dependencies>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+    </profiles>
+
+</project>
diff --git a/jetty-spdy/spdy-example-webapp/src/main/config/example-jetty-spdy-proxy.xml b/jetty-spdy/spdy-example-webapp/src/main/config/example-jetty-spdy-proxy.xml
new file mode 100644
index 0000000..7d3d360
--- /dev/null
+++ b/jetty-spdy/spdy-example-webapp/src/main/config/example-jetty-spdy-proxy.xml
@@ -0,0 +1,147 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
+
+<Configure id="Server" class="org.eclipse.jetty.server.Server">
+
+    <New id="sslContextFactory" class="org.eclipse.jetty.util.ssl.SslContextFactory">
+        <Set name="keyStorePath">src/main/resources/keystore.jks</Set>
+        <Set name="keyStorePassword">storepwd</Set>
+        <Set name="trustStorePath">src/main/resources/truststore.jks</Set>
+        <Set name="trustStorePassword">storepwd</Set>
+        <Set name="protocol">TLSv1</Set>
+    </New>
+
+    <!--
+    <Set class="org.eclipse.jetty.npn.NextProtoNego" name="debug" type="boolean">true</Set>
+    -->
+
+    <New id="tlsHttpConfig" class="org.eclipse.jetty.server.HttpConfiguration">
+        <Arg>
+            <New id="httpConfig" class="org.eclipse.jetty.server.HttpConfiguration">
+                <Set name="secureScheme">https</Set>
+                <Set name="securePort">
+                    <Property name="jetty.tls.port" default="8443"/>
+                </Set>
+                <Set name="outputBufferSize">32768</Set>
+                <Set name="requestHeaderSize">8192</Set>
+                <Set name="responseHeaderSize">8192</Set>
+
+                <!-- Uncomment to enable handling of X-Forwarded- style headers
+                <Call name="addCustomizer">
+                    <Arg><New class="org.eclipse.jetty.server.ForwardedRequestCustomizer"/></Arg>
+                </Call>
+                -->
+            </New>
+        </Arg>
+        <Call name="addCustomizer">
+            <Arg>
+                <New class="org.eclipse.jetty.server.SecureRequestCustomizer"/>
+            </Arg>
+        </Call>
+    </New>
+
+    <!--
+    This is the upstream server connector. It speaks non-SSL SPDY/3(HTTP) on port 9090.
+    -->
+    <Call name="addConnector">
+        <Arg>
+            <New class="org.eclipse.jetty.server.ServerConnector">
+                <Arg name="server">
+                    <Ref refid="Server"/>
+                </Arg>
+                <Arg name="factories">
+                    <Array type="org.eclipse.jetty.server.ConnectionFactory">
+                        <!-- SPDY/3 Connection factory -->
+                        <Item>
+                            <New class="org.eclipse.jetty.spdy.server.http.HTTPSPDYServerConnectionFactory">
+                                <Arg name="version" type="int">3</Arg>
+                                <Arg name="config">
+                                    <Ref refid="tlsHttpConfig"/>
+                                </Arg>
+                            </New>
+                        </Item>
+                    </Array>
+                </Arg>
+                <Set name="port">9090</Set>
+            </New>
+        </Arg>
+    </Call>
+
+    <!--
+    This ProxyEngine translates the incoming SPDY/x(HTTP) request to SPDY/2(HTTP)
+    -->
+    <New id="spdyProxyEngine" class="org.eclipse.jetty.spdy.server.proxy.SPDYProxyEngine">
+        <Arg>
+            <New class="org.eclipse.jetty.spdy.client.SPDYClient$Factory">
+                <Call name="start"/>
+            </New>
+        </Arg>
+    </New>
+
+    <!--
+    The ProxyEngineSelector receives SPDY/x(HTTP) requests from proxy connectors below
+    and is configured to process requests for host "localhost".
+    Such requests are converted from SPDY/x(HTTP) to SPDY/3(HTTP) by the configured ProxyEngine
+    and forwarded to 127.0.0.1:9090, where they are served by the upstream server above.
+    -->
+    <New id="proxyEngineSelector" class="org.eclipse.jetty.spdy.server.proxy.ProxyEngineSelector">
+        <Call name="putProxyEngine">
+            <Arg>spdy/3</Arg>
+            <Arg>
+                <Ref refid="spdyProxyEngine"/>
+            </Arg>
+        </Call>
+        <Set name="proxyServerInfos">
+            <Map>
+                <Entry>
+                    <Item>localhost</Item>
+                    <Item>
+                        <New class="org.eclipse.jetty.spdy.server.proxy.ProxyEngineSelector$ProxyServerInfo">
+                            <Arg type="String">spdy/3</Arg>
+                            <Arg>127.0.0.1</Arg>
+                            <Arg type="int">9090</Arg>
+                        </New>
+                    </Item>
+                </Entry>
+            </Map>
+        </Set>
+    </New>
+
+    <!--
+    These are the reverse proxy connectors accepting requests from clients.
+    They accept non-SSL (on port 8080) and SSL (on port 8443) HTTP,
+    SPDY/2(HTTP) and SPDY/3(HTTP).
+    Non-SPDY HTTP requests are converted to SPDY internally and passed to the
+    ProxyEngine above.
+    -->
+    <Call name="addConnector">
+        <Arg>
+            <New class="org.eclipse.jetty.spdy.server.proxy.HTTPSPDYProxyServerConnector">
+                <Arg>
+                    <Ref refid="Server"/>
+                </Arg>
+                <Arg>
+                    <Ref refid="proxyEngineSelector"/>
+                </Arg>
+                <Set name="Port">8080</Set>
+            </New>
+        </Arg>
+    </Call>
+    <Call name="addConnector">
+        <Arg>
+            <New class="org.eclipse.jetty.spdy.server.proxy.HTTPSPDYProxyServerConnector">
+                <Arg>
+                    <Ref refid="Server"/>
+                </Arg>
+                <Arg>
+                    <Ref refid="sslContextFactory"/>
+                </Arg>
+                <Arg>
+                    <Ref refid="proxyEngineSelector"/>
+                </Arg>
+                <Set name="Port">8443</Set>
+            </New>
+        </Arg>
+    </Call>
+
+</Configure>
diff --git a/jetty-spdy/spdy-example-webapp/src/main/config/example-jetty-spdy.xml b/jetty-spdy/spdy-example-webapp/src/main/config/example-jetty-spdy.xml
new file mode 100644
index 0000000..47f83be
--- /dev/null
+++ b/jetty-spdy/spdy-example-webapp/src/main/config/example-jetty-spdy.xml
@@ -0,0 +1,138 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
+
+<Configure id="Server" class="org.eclipse.jetty.server.Server">
+
+    <New id="sslContextFactory" class="org.eclipse.jetty.util.ssl.SslContextFactory">
+        <Set name="keyStorePath">src/main/resources/keystore.jks</Set>
+        <Set name="keyStorePassword">storepwd</Set>
+        <Set name="trustStorePath">src/main/resources/truststore.jks</Set>
+        <Set name="trustStorePassword">storepwd</Set>
+        <Set name="protocol">TLSv1</Set>
+    </New>
+
+    <New id="tlsHttpConfig" class="org.eclipse.jetty.server.HttpConfiguration">
+        <Arg>
+            <New id="httpConfig" class="org.eclipse.jetty.server.HttpConfiguration">
+                <Set name="secureScheme">https</Set>
+                <Set name="securePort">
+                    <Property name="jetty.tls.port" default="8443"/>
+                </Set>
+                <Set name="outputBufferSize">32768</Set>
+                <Set name="requestHeaderSize">8192</Set>
+                <Set name="responseHeaderSize">8192</Set>
+
+                <!-- Uncomment to enable handling of X-Forwarded- style headers
+                <Call name="addCustomizer">
+                    <Arg><New class="org.eclipse.jetty.server.ForwardedRequestCustomizer"/></Arg>
+                </Call>
+                -->
+            </New>
+        </Arg>
+        <Call name="addCustomizer">
+            <Arg>
+                <New class="org.eclipse.jetty.server.SecureRequestCustomizer"/>
+            </Arg>
+        </Call>
+    </New>
+
+    <New id="pushStrategy" class="org.eclipse.jetty.spdy.server.http.ReferrerPushStrategy">
+        <!-- Uncomment to blacklist browsers for this push strategy. If one of the blacklisted Strings occurs in the
+             user-agent header sent by the client, push will be disabled for this browser. This is case insensitive" -->
+        <!--
+        <Set name="UserAgentBlacklist">
+            <Array type="String">
+                <Item>.*(?i)firefox/14.*</Item>
+                <Item>.*(?i)firefox/15.*</Item>
+                <Item>.*(?i)firefox/16.*</Item>
+            </Array>
+        </Set>
+        -->
+
+        <!-- Uncomment to override default file extensions to push -->
+        <!--
+        <Set name="PushRegexps">
+            <Array type="String">
+               <Item>.*\.css</Item>
+               <Item>.*\.js</Item>
+               <Item>.*\.png</Item>
+               <Item>.*\.jpg</Item>
+               <Item>.*\.gif</Item>
+           </Array>
+        </Set>
+        -->
+        <Set name="referrerPushPeriod">5000</Set>
+        <Set name="maxAssociatedResources">32</Set>
+    </New>
+
+    <Call id="sslConnector" name="addConnector">
+        <Arg>
+            <New class="org.eclipse.jetty.server.ServerConnector">
+                <Arg name="server"><Ref refid="Server"/></Arg>
+                <Arg name="factories">
+                    <Array type="org.eclipse.jetty.server.ConnectionFactory">
+
+                        <!-- SSL Connection factory with NPN as next protocol -->
+                        <Item>
+                            <New class="org.eclipse.jetty.server.SslConnectionFactory">
+                                <Arg name="next">npn</Arg>
+                                <Arg name="sslContextFactory">
+                                    <Ref refid="sslContextFactory"/>
+                                </Arg>
+                            </New>
+                        </Item>
+
+                        <!-- NPN Connection factory with HTTP as default protocol -->
+                        <Item>
+                            <New class="org.eclipse.jetty.spdy.server.NPNServerConnectionFactory">
+                                <Arg name="protocols">
+                                    <Array type="String">
+                                        <Item>spdy/3</Item>
+                                        <Item>spdy/2</Item>
+                                        <Item>http/1.1</Item>
+                                    </Array>
+                                </Arg>
+                                <Set name="defaultProtocol">http/1.1</Set>
+                            </New>
+                        </Item>
+
+                        <!-- SPDY/3 Connection factory -->
+                        <Item>
+                            <New class="org.eclipse.jetty.spdy.server.http.HTTPSPDYServerConnectionFactory">
+                                <Arg name="version" type="int">3</Arg>
+                                <Arg name="config">
+                                    <Ref refid="tlsHttpConfig"/>
+                                </Arg>
+                                <Arg name="pushStrategy">
+                                    <Ref refid="pushStrategy"/>
+                                </Arg>
+                            </New>
+                        </Item>
+
+                        <!-- SPDY/2 Connection factory -->
+                        <Item>
+                            <New class="org.eclipse.jetty.spdy.server.http.HTTPSPDYServerConnectionFactory">
+                                <Arg name="version" type="int">2</Arg>
+                                <Arg name="config">
+                                    <Ref refid="tlsHttpConfig"/>
+                                </Arg>
+                            </New>
+                        </Item>
+
+                        <!-- HTTP Connection factory -->
+                        <Item>
+                            <New class="org.eclipse.jetty.server.HttpConnectionFactory">
+                                <Arg name="config">
+                                    <Ref refid="tlsHttpConfig"/>
+                                </Arg>
+                            </New>
+                        </Item>
+                    </Array>
+                </Arg>
+
+                <Set name="port">8443</Set>
+            </New>
+        </Arg>
+    </Call>
+
+</Configure>
diff --git a/jetty-spdy/spdy-example-webapp/src/main/resources/jetty-logging.properties b/jetty-spdy/spdy-example-webapp/src/main/resources/jetty-logging.properties
new file mode 100644
index 0000000..5250a08
--- /dev/null
+++ b/jetty-spdy/spdy-example-webapp/src/main/resources/jetty-logging.properties
@@ -0,0 +1,2 @@
+org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
+org.eclipse.jetty.spdy.LEVEL=WARN
diff --git a/jetty-spdy/spdy-jetty-http-webapp/src/main/resources/keystore.jks b/jetty-spdy/spdy-example-webapp/src/main/resources/keystore.jks
similarity index 100%
copy from jetty-spdy/spdy-jetty-http-webapp/src/main/resources/keystore.jks
copy to jetty-spdy/spdy-example-webapp/src/main/resources/keystore.jks
diff --git a/jetty-spdy/spdy-jetty-http-webapp/src/main/resources/truststore.jks b/jetty-spdy/spdy-example-webapp/src/main/resources/truststore.jks
similarity index 100%
copy from jetty-spdy/spdy-jetty-http-webapp/src/main/resources/truststore.jks
copy to jetty-spdy/spdy-example-webapp/src/main/resources/truststore.jks
diff --git a/jetty-spdy/spdy-example-webapp/src/main/webapp/WEB-INF/web.xml b/jetty-spdy/spdy-example-webapp/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000..eb49319
--- /dev/null
+++ b/jetty-spdy/spdy-example-webapp/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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_3_0.xsd"
+         version="3.0">
+</web-app>
diff --git a/jetty-spdy/spdy-jetty-http-webapp/src/main/webapp/form.jsp b/jetty-spdy/spdy-example-webapp/src/main/webapp/form.jsp
similarity index 100%
rename from jetty-spdy/spdy-jetty-http-webapp/src/main/webapp/form.jsp
rename to jetty-spdy/spdy-example-webapp/src/main/webapp/form.jsp
diff --git a/jetty-spdy/spdy-jetty-http-webapp/src/main/webapp/included.jsp b/jetty-spdy/spdy-example-webapp/src/main/webapp/included.jsp
similarity index 100%
rename from jetty-spdy/spdy-jetty-http-webapp/src/main/webapp/included.jsp
rename to jetty-spdy/spdy-example-webapp/src/main/webapp/included.jsp
diff --git a/jetty-spdy/spdy-jetty-http-webapp/src/main/webapp/index.jsp b/jetty-spdy/spdy-example-webapp/src/main/webapp/index.jsp
similarity index 100%
rename from jetty-spdy/spdy-jetty-http-webapp/src/main/webapp/index.jsp
rename to jetty-spdy/spdy-example-webapp/src/main/webapp/index.jsp
diff --git a/jetty-spdy/spdy-jetty-http-webapp/src/main/webapp/logo.jpg b/jetty-spdy/spdy-example-webapp/src/main/webapp/logo.jpg
similarity index 100%
rename from jetty-spdy/spdy-jetty-http-webapp/src/main/webapp/logo.jpg
rename to jetty-spdy/spdy-example-webapp/src/main/webapp/logo.jpg
diff --git a/jetty-spdy/spdy-jetty-http-webapp/src/main/webapp/stylesheet.css b/jetty-spdy/spdy-example-webapp/src/main/webapp/stylesheet.css
similarity index 100%
rename from jetty-spdy/spdy-jetty-http-webapp/src/main/webapp/stylesheet.css
rename to jetty-spdy/spdy-example-webapp/src/main/webapp/stylesheet.css
diff --git a/jetty-spdy/spdy-http-client-transport/pom.xml b/jetty-spdy/spdy-http-client-transport/pom.xml
new file mode 100644
index 0000000..f2d3901
--- /dev/null
+++ b/jetty-spdy/spdy-http-client-transport/pom.xml
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>org.eclipse.jetty.spdy</groupId>
+        <artifactId>spdy-parent</artifactId>
+        <version>9.2.13.v20150730</version>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>spdy-http-client-transport</artifactId>
+    <name>Jetty :: SPDY :: HTTP Client Transport</name>
+
+    <properties>
+        <bundle-symbolic-name>${project.groupId}.client.http</bundle-symbolic-name>
+    </properties>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>manifest</goal>
+                        </goals>
+                        <configuration>
+                            <instructions>
+                                <Export-Package>org.eclipse.jetty.spdy.client.http;version="9.1"</Export-Package>
+                                <Import-Package>!org.eclipse.jetty.npn,org.eclipse.jetty.*;version="[9.0,10.0)",*</Import-Package>
+                            </instructions>
+                          </configuration>
+                       </execution>
+                  </executions>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-client</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty.spdy</groupId>
+            <artifactId>spdy-client</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty.spdy</groupId>
+            <artifactId>spdy-http-common</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-server</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty.spdy</groupId>
+            <artifactId>spdy-http-server</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+</project>
diff --git a/jetty-spdy/spdy-http-client-transport/src/main/java/org/eclipse/jetty/spdy/client/http/HttpChannelOverSPDY.java b/jetty-spdy/spdy-http-client-transport/src/main/java/org/eclipse/jetty/spdy/client/http/HttpChannelOverSPDY.java
new file mode 100644
index 0000000..9fa4ac7
--- /dev/null
+++ b/jetty-spdy/spdy-http-client-transport/src/main/java/org/eclipse/jetty/spdy/client/http/HttpChannelOverSPDY.java
@@ -0,0 +1,78 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.spdy.client.http;
+
+import org.eclipse.jetty.client.HttpChannel;
+import org.eclipse.jetty.client.HttpDestination;
+import org.eclipse.jetty.client.HttpExchange;
+import org.eclipse.jetty.client.api.Result;
+import org.eclipse.jetty.spdy.api.Session;
+
+public class HttpChannelOverSPDY extends HttpChannel
+{
+    private final HttpConnectionOverSPDY connection;
+    private final Session session;
+    private final HttpSenderOverSPDY sender;
+    private final HttpReceiverOverSPDY receiver;
+
+    public HttpChannelOverSPDY(HttpDestination destination, HttpConnectionOverSPDY connection, Session session)
+    {
+        super(destination);
+        this.connection = connection;
+        this.session = session;
+        this.sender = new HttpSenderOverSPDY(this);
+        this.receiver = new HttpReceiverOverSPDY(this);
+    }
+
+    public Session getSession()
+    {
+        return session;
+    }
+
+    public HttpSenderOverSPDY getHttpSender()
+    {
+        return sender;
+    }
+
+    public HttpReceiverOverSPDY getHttpReceiver()
+    {
+        return receiver;
+    }
+
+    @Override
+    public void send()
+    {
+        HttpExchange exchange = getHttpExchange();
+        if (exchange != null)
+            sender.send(exchange);
+    }
+
+    @Override
+    public void release()
+    {
+        connection.release(this);
+    }
+
+    @Override
+    public void exchangeTerminated(HttpExchange exchange, Result result)
+    {
+        super.exchangeTerminated(exchange, result);
+        release();
+    }
+}
diff --git a/jetty-spdy/spdy-http-client-transport/src/main/java/org/eclipse/jetty/spdy/client/http/HttpClientTransportOverSPDY.java b/jetty-spdy/spdy-http-client-transport/src/main/java/org/eclipse/jetty/spdy/client/http/HttpClientTransportOverSPDY.java
new file mode 100644
index 0000000..f0734f1
--- /dev/null
+++ b/jetty-spdy/spdy-http-client-transport/src/main/java/org/eclipse/jetty/spdy/client/http/HttpClientTransportOverSPDY.java
@@ -0,0 +1,107 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.spdy.client.http;
+
+import java.io.IOException;
+import java.net.SocketAddress;
+import java.util.Map;
+
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.HttpClientTransport;
+import org.eclipse.jetty.client.HttpDestination;
+import org.eclipse.jetty.client.Origin;
+import org.eclipse.jetty.client.api.Connection;
+import org.eclipse.jetty.io.ClientConnectionFactory;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.spdy.api.Session;
+import org.eclipse.jetty.spdy.api.SessionFrameListener;
+import org.eclipse.jetty.spdy.client.SPDYClient;
+import org.eclipse.jetty.util.Promise;
+
+public class HttpClientTransportOverSPDY implements HttpClientTransport
+{
+    private final SPDYClient client;
+    private final ClientConnectionFactory connectionFactory;
+    private HttpClient httpClient;
+
+    public HttpClientTransportOverSPDY(SPDYClient client)
+    {
+        this.client = client;
+        this.connectionFactory = client.getClientConnectionFactory();
+        client.setClientConnectionFactory(new ClientConnectionFactory()
+        {
+            @Override
+            public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map<String, Object> context) throws IOException
+            {
+                HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY);
+                return destination.getClientConnectionFactory().newConnection(endPoint, context);
+            }
+        });
+    }
+
+    @Override
+    public void setHttpClient(HttpClient client)
+    {
+        httpClient = client;
+    }
+
+    @Override
+    public HttpDestination newHttpDestination(Origin origin)
+    {
+        return new HttpDestinationOverSPDY(httpClient, origin);
+    }
+
+    @Override
+    public void connect(SocketAddress address, Map<String, Object> context)
+    {
+        final HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY);
+        @SuppressWarnings("unchecked")
+        final Promise<Connection> promise = (Promise<Connection>)context.get(HTTP_CONNECTION_PROMISE_CONTEXT_KEY);
+
+        SessionFrameListener.Adapter listener = new SessionFrameListener.Adapter()
+        {
+            @Override
+            public void onFailure(Session session, Throwable x)
+            {
+                destination.abort(x);
+            }
+        };
+
+        client.connect(address, listener, new Promise<Session>()
+        {
+            @Override
+            public void succeeded(Session session)
+            {
+                promise.succeeded(new HttpConnectionOverSPDY(destination, session));
+            }
+
+            @Override
+            public void failed(Throwable x)
+            {
+                promise.failed(x);
+            }
+        }, context);
+    }
+
+    @Override
+    public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map<String, Object> context) throws IOException
+    {
+        return connectionFactory.newConnection(endPoint, context);
+    }
+}
diff --git a/jetty-spdy/spdy-http-client-transport/src/main/java/org/eclipse/jetty/spdy/client/http/HttpConnectionOverSPDY.java b/jetty-spdy/spdy-http-client-transport/src/main/java/org/eclipse/jetty/spdy/client/http/HttpConnectionOverSPDY.java
new file mode 100644
index 0000000..a3902d3
--- /dev/null
+++ b/jetty-spdy/spdy-http-client-transport/src/main/java/org/eclipse/jetty/spdy/client/http/HttpConnectionOverSPDY.java
@@ -0,0 +1,82 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.spdy.client.http;
+
+import java.nio.channels.AsynchronousCloseException;
+import java.util.Set;
+
+import org.eclipse.jetty.client.HttpChannel;
+import org.eclipse.jetty.client.HttpConnection;
+import org.eclipse.jetty.client.HttpDestination;
+import org.eclipse.jetty.client.HttpExchange;
+import org.eclipse.jetty.spdy.api.GoAwayInfo;
+import org.eclipse.jetty.spdy.api.Session;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.ConcurrentHashSet;
+
+public class HttpConnectionOverSPDY extends HttpConnection
+{
+    private final Set<HttpChannel> channels = new ConcurrentHashSet<>();
+    private final Session session;
+
+    public HttpConnectionOverSPDY(HttpDestination destination, Session session)
+    {
+        super(destination);
+        this.session = session;
+    }
+
+    @Override
+    protected void send(HttpExchange exchange)
+    {
+        normalizeRequest(exchange.getRequest());
+        // One connection maps to N channels, so for each exchange we create a new channel
+        HttpChannel channel = new HttpChannelOverSPDY(getHttpDestination(), this, session);
+        channels.add(channel);
+        if (channel.associate(exchange))
+            channel.send();
+        else
+            channel.release();
+    }
+
+    protected void release(HttpChannel channel)
+    {
+        channels.remove(channel);
+    }
+
+    @Override
+    public void close()
+    {
+        // First close then abort, to be sure that the connection cannot be reused
+        // from an onFailure() handler or by blocking code waiting for completion.
+        getHttpDestination().close(this);
+        session.goAway(new GoAwayInfo(), Callback.Adapter.INSTANCE);
+        abort(new AsynchronousCloseException());
+    }
+
+    private void abort(Throwable failure)
+    {
+        for (HttpChannel channel : channels)
+        {
+            HttpExchange exchange = channel.getHttpExchange();
+            if (exchange != null)
+                exchange.getRequest().abort(failure);
+        }
+        channels.clear();
+    }
+}
diff --git a/jetty-spdy/spdy-http-client-transport/src/main/java/org/eclipse/jetty/spdy/client/http/HttpDestinationOverSPDY.java b/jetty-spdy/spdy-http-client-transport/src/main/java/org/eclipse/jetty/spdy/client/http/HttpDestinationOverSPDY.java
new file mode 100644
index 0000000..6c44e59
--- /dev/null
+++ b/jetty-spdy/spdy-http-client-transport/src/main/java/org/eclipse/jetty/spdy/client/http/HttpDestinationOverSPDY.java
@@ -0,0 +1,38 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.spdy.client.http;
+
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.HttpExchange;
+import org.eclipse.jetty.client.MultiplexHttpDestination;
+import org.eclipse.jetty.client.Origin;
+
+public class HttpDestinationOverSPDY extends MultiplexHttpDestination<HttpConnectionOverSPDY>
+{
+    public HttpDestinationOverSPDY(HttpClient client, Origin origin)
+    {
+        super(client, origin);
+    }
+
+    @Override
+    protected void send(HttpConnectionOverSPDY connection, HttpExchange exchange)
+    {
+        connection.send(exchange);
+    }
+}
diff --git a/jetty-spdy/spdy-http-client-transport/src/main/java/org/eclipse/jetty/spdy/client/http/HttpReceiverOverSPDY.java b/jetty-spdy/spdy-http-client-transport/src/main/java/org/eclipse/jetty/spdy/client/http/HttpReceiverOverSPDY.java
new file mode 100644
index 0000000..c3aecaa
--- /dev/null
+++ b/jetty-spdy/spdy-http-client-transport/src/main/java/org/eclipse/jetty/spdy/client/http/HttpReceiverOverSPDY.java
@@ -0,0 +1,152 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.spdy.client.http;
+
+import org.eclipse.jetty.client.HttpExchange;
+import org.eclipse.jetty.client.HttpReceiver;
+import org.eclipse.jetty.client.HttpResponse;
+import org.eclipse.jetty.http.HttpField;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.spdy.api.DataInfo;
+import org.eclipse.jetty.spdy.api.HeadersInfo;
+import org.eclipse.jetty.spdy.api.PushInfo;
+import org.eclipse.jetty.spdy.api.ReplyInfo;
+import org.eclipse.jetty.spdy.api.RstInfo;
+import org.eclipse.jetty.spdy.api.Stream;
+import org.eclipse.jetty.spdy.api.StreamFrameListener;
+import org.eclipse.jetty.spdy.api.StreamStatus;
+import org.eclipse.jetty.spdy.http.HTTPSPDYHeader;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.Fields;
+
+public class HttpReceiverOverSPDY extends HttpReceiver implements StreamFrameListener
+{
+    public HttpReceiverOverSPDY(HttpChannelOverSPDY channel)
+    {
+        super(channel);
+    }
+
+    @Override
+    public HttpChannelOverSPDY getHttpChannel()
+    {
+        return (HttpChannelOverSPDY)super.getHttpChannel();
+    }
+
+    @Override
+    public void onReply(Stream stream, ReplyInfo replyInfo)
+    {
+        HttpExchange exchange = getHttpExchange();
+        if (exchange == null)
+            return;
+
+        try
+        {
+            HttpResponse response = exchange.getResponse();
+
+            Fields fields = replyInfo.getHeaders();
+            short spdy = stream.getSession().getVersion();
+            HttpVersion version = HttpVersion.fromString(fields.get(HTTPSPDYHeader.VERSION.name(spdy)).getValue());
+            response.version(version);
+            String[] status = fields.get(HTTPSPDYHeader.STATUS.name(spdy)).getValue().split(" ", 2);
+
+            Integer code = Integer.parseInt(status[0]);
+            response.status(code);
+            String reason = status.length < 2 ? HttpStatus.getMessage(code) : status[1];
+            response.reason(reason);
+
+            if (responseBegin(exchange))
+            {
+                for (Fields.Field field : fields)
+                {
+                    String name = field.getName();
+                    if (HTTPSPDYHeader.from(spdy, name) != null)
+                        continue;
+                    // TODO: handle multiple values properly
+                    HttpField httpField = new HttpField(name, field.getValue());
+                    responseHeader(exchange, httpField);
+                }
+
+                if (responseHeaders(exchange))
+                {
+                    if (replyInfo.isClose())
+                    {
+                        responseSuccess(exchange);
+                    }
+                }
+            }
+        }
+        catch (Exception x)
+        {
+            responseFailure(x);
+        }
+    }
+
+    @Override
+    public StreamFrameListener onPush(Stream stream, PushInfo pushInfo)
+    {
+        // SPDY push not supported
+        getHttpChannel().getSession().rst(new RstInfo(stream.getId(), StreamStatus.REFUSED_STREAM), Callback.Adapter.INSTANCE);
+        return null;
+    }
+
+    @Override
+    public void onHeaders(Stream stream, HeadersInfo headersInfo)
+    {
+        // TODO: see above handling of headers
+    }
+
+    @Override
+    public void onData(Stream stream, DataInfo dataInfo)
+    {
+        HttpExchange exchange = getHttpExchange();
+        if (exchange == null)
+            return;
+
+        try
+        {
+            int length = dataInfo.length();
+            // TODO: avoid data copy here
+            // TODO: handle callback properly
+            boolean process = responseContent(exchange, dataInfo.asByteBuffer(false), new Callback.Adapter());
+            dataInfo.consume(length);
+
+            if (process)
+            {
+                if (dataInfo.isClose())
+                {
+                    responseSuccess(exchange);
+                }
+            }
+        }
+        catch (Exception x)
+        {
+            responseFailure(x);
+        }
+    }
+
+    @Override
+    public void onFailure(Stream stream, Throwable x)
+    {
+        HttpExchange exchange = getHttpExchange();
+        if (exchange == null)
+            return;
+        exchange.getRequest().abort(x);
+    }
+}
diff --git a/jetty-spdy/spdy-http-client-transport/src/main/java/org/eclipse/jetty/spdy/client/http/HttpSenderOverSPDY.java b/jetty-spdy/spdy-http-client-transport/src/main/java/org/eclipse/jetty/spdy/client/http/HttpSenderOverSPDY.java
new file mode 100644
index 0000000..fcddc5a
--- /dev/null
+++ b/jetty-spdy/spdy-http-client-transport/src/main/java/org/eclipse/jetty/spdy/client/http/HttpSenderOverSPDY.java
@@ -0,0 +1,118 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.spdy.client.http;
+
+import org.eclipse.jetty.client.HttpContent;
+import org.eclipse.jetty.client.HttpExchange;
+import org.eclipse.jetty.client.HttpSender;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.http.HttpField;
+import org.eclipse.jetty.spdy.api.ByteBufferDataInfo;
+import org.eclipse.jetty.spdy.api.Stream;
+import org.eclipse.jetty.spdy.api.SynInfo;
+import org.eclipse.jetty.spdy.http.HTTPSPDYHeader;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.Fields;
+import org.eclipse.jetty.util.Promise;
+
+public class HttpSenderOverSPDY extends HttpSender
+{
+    private volatile Stream stream;
+
+    public HttpSenderOverSPDY(HttpChannelOverSPDY channel)
+    {
+        super(channel);
+    }
+
+    @Override
+    public HttpChannelOverSPDY getHttpChannel()
+    {
+        return (HttpChannelOverSPDY)super.getHttpChannel();
+    }
+
+    @Override
+    protected void sendHeaders(HttpExchange exchange, final HttpContent content, final Callback callback)
+    {
+        final Request request = exchange.getRequest();
+        final long idleTimeout = request.getIdleTimeout();
+        short spdyVersion = getHttpChannel().getSession().getVersion();
+        Fields fields = new Fields();
+        HttpField hostHeader = null;
+        for (HttpField header : request.getHeaders())
+        {
+            String name = header.getName();
+            // The host header needs a special treatment
+            if (HTTPSPDYHeader.from(spdyVersion, name) != HTTPSPDYHeader.HOST)
+                fields.add(name, header.getValue());
+            else
+                hostHeader = header;
+        }
+
+        // Add special SPDY headers
+        fields.put(HTTPSPDYHeader.METHOD.name(spdyVersion), request.getMethod());
+        String path = request.getPath();
+        String query = request.getQuery();
+        if (query != null)
+            path += "?" + query;
+        fields.put(HTTPSPDYHeader.URI.name(spdyVersion), path);
+        fields.put(HTTPSPDYHeader.VERSION.name(spdyVersion), request.getVersion().asString());
+        if (hostHeader != null)
+            fields.put(HTTPSPDYHeader.HOST.name(spdyVersion), hostHeader.getValue());
+
+        SynInfo synInfo = new SynInfo(fields, !content.hasContent());
+        getHttpChannel().getSession().syn(synInfo, getHttpChannel().getHttpReceiver(), new Promise<Stream>()
+        {
+            @Override
+            public void succeeded(Stream stream)
+            {
+                stream.setIdleTimeout(idleTimeout);
+                if (content.hasContent())
+                    HttpSenderOverSPDY.this.stream = stream;
+                callback.succeeded();
+            }
+
+            @Override
+            public void failed(Throwable failure)
+            {
+                callback.failed(failure);
+            }
+        });
+    }
+
+    @Override
+    protected void sendContent(HttpExchange exchange, HttpContent content, Callback callback)
+    {
+        if (content.isConsumed())
+        {
+            callback.succeeded();
+        }
+        else
+        {
+            ByteBufferDataInfo dataInfo = new ByteBufferDataInfo(content.getByteBuffer(), content.isLast());
+            stream.data(dataInfo, callback);
+        }
+    }
+
+    @Override
+    protected void reset()
+    {
+        super.reset();
+        stream = null;
+    }
+}
diff --git a/jetty-spdy/spdy-http-client-transport/src/test/java/org/eclipse/jetty/spdy/client/http/AbstractHttpClientServerTest.java b/jetty-spdy/spdy-http-client-transport/src/test/java/org/eclipse/jetty/spdy/client/http/AbstractHttpClientServerTest.java
new file mode 100644
index 0000000..34a82eb
--- /dev/null
+++ b/jetty-spdy/spdy-http-client-transport/src/test/java/org/eclipse/jetty/spdy/client/http/AbstractHttpClientServerTest.java
@@ -0,0 +1,107 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.spdy.client.http;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.http.HttpScheme;
+import org.eclipse.jetty.server.AbstractConnectionFactory;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.spdy.api.SPDY;
+import org.eclipse.jetty.spdy.client.SPDYClient;
+import org.eclipse.jetty.spdy.server.http.HTTPSPDYServerConnectionFactory;
+import org.eclipse.jetty.toolchain.test.TestTracker;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.junit.After;
+import org.junit.Rule;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+ at RunWith(Parameterized.class)
+public abstract class AbstractHttpClientServerTest
+{
+    @Parameterized.Parameters
+    public static Collection<SslContextFactory[]> parameters()
+    {
+        return Arrays.asList(new SslContextFactory[]{null}, new SslContextFactory[]{new SslContextFactory()});
+    }
+
+    @Rule
+    public final TestTracker tracker = new TestTracker();
+
+    protected SslContextFactory sslContextFactory;
+    protected String scheme;
+    protected Server server;
+    protected ServerConnector connector;
+    protected SPDYClient.Factory factory;
+    protected HttpClient client;
+
+    public AbstractHttpClientServerTest(SslContextFactory sslContextFactory)
+    {
+        this.sslContextFactory = sslContextFactory;
+        this.scheme = (sslContextFactory == null ? HttpScheme.HTTP : HttpScheme.HTTPS).asString();
+    }
+
+    public void start(Handler handler) throws Exception
+    {
+        short version = SPDY.V3;
+
+        HTTPSPDYServerConnectionFactory httpSPDY = new HTTPSPDYServerConnectionFactory(version, new HttpConfiguration());
+        if (sslContextFactory != null)
+        {
+            sslContextFactory.setEndpointIdentificationAlgorithm("");
+            sslContextFactory.setKeyStorePath("src/test/resources/keystore.jks");
+            sslContextFactory.setKeyStorePassword("storepwd");
+            sslContextFactory.setTrustStorePath("src/test/resources/truststore.jks");
+            sslContextFactory.setTrustStorePassword("storepwd");
+        }
+
+        server = new Server();
+        connector = new ServerConnector(server, AbstractConnectionFactory.getFactories(sslContextFactory, httpSPDY));
+        server.addConnector(connector);
+        server.setHandler(handler);
+        server.start();
+
+        QueuedThreadPool executor = new QueuedThreadPool();
+        executor.setName(executor.getName() + "-client");
+
+        factory = new SPDYClient.Factory(executor);
+        factory.start();
+        client = new HttpClient(new HttpClientTransportOverSPDY(factory.newSPDYClient(version)), sslContextFactory);
+        client.setExecutor(executor);
+        client.start();
+    }
+
+    @After
+    public void dispose() throws Exception
+    {
+        if (client != null)
+            client.stop();
+        if (factory != null)
+            factory.stop();
+        if (server != null)
+            server.stop();
+    }
+}
diff --git a/jetty-spdy/spdy-http-client-transport/src/test/java/org/eclipse/jetty/spdy/client/http/EmptyServerHandler.java b/jetty-spdy/spdy-http-client-transport/src/test/java/org/eclipse/jetty/spdy/client/http/EmptyServerHandler.java
new file mode 100644
index 0000000..4544daa
--- /dev/null
+++ b/jetty-spdy/spdy-http-client-transport/src/test/java/org/eclipse/jetty/spdy/client/http/EmptyServerHandler.java
@@ -0,0 +1,37 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.spdy.client.http;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+
+public class EmptyServerHandler extends AbstractHandler
+{
+    @Override
+    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+    {
+        baseRequest.setHandled(true);
+    }
+}
diff --git a/jetty-spdy/spdy-http-client-transport/src/test/java/org/eclipse/jetty/spdy/client/http/HttpClientCustomProxyTest.java b/jetty-spdy/spdy-http-client-transport/src/test/java/org/eclipse/jetty/spdy/client/http/HttpClientCustomProxyTest.java
new file mode 100644
index 0000000..fa28e96
--- /dev/null
+++ b/jetty-spdy/spdy-http-client-transport/src/test/java/org/eclipse/jetty/spdy/client/http/HttpClientCustomProxyTest.java
@@ -0,0 +1,262 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.spdy.client.http;
+
+import java.io.IOException;
+import java.net.URI;
+import java.nio.ByteBuffer;
+import java.util.Map;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.HttpClientTransport;
+import org.eclipse.jetty.client.HttpDestination;
+import org.eclipse.jetty.client.Origin;
+import org.eclipse.jetty.client.ProxyConfiguration;
+import org.eclipse.jetty.client.api.Connection;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.io.AbstractConnection;
+import org.eclipse.jetty.io.ClientConnectionFactory;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.server.AbstractConnectionFactory;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.spdy.api.SPDY;
+import org.eclipse.jetty.spdy.client.SPDYClient;
+import org.eclipse.jetty.spdy.server.http.HTTPSPDYServerConnectionFactory;
+import org.eclipse.jetty.spdy.server.http.PushStrategy;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.Promise;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class HttpClientCustomProxyTest
+{
+    public static final byte[] CAFE_BABE = new byte[]{(byte)0xCA, (byte)0xFE, (byte)0xBA, (byte)0xBE};
+
+    private Server server;
+    private ServerConnector connector;
+    private SPDYClient.Factory factory;
+    private HttpClient httpClient;
+
+    public void prepare(Handler handler) throws Exception
+    {
+        server = new Server();
+        connector = new ServerConnector(server, new CAFEBABEServerConnectionFactory(new HTTPSPDYServerConnectionFactory(SPDY.V3, new HttpConfiguration(), new PushStrategy.None())));
+        server.addConnector(connector);
+        server.setHandler(handler);
+        server.start();
+
+        QueuedThreadPool executor = new QueuedThreadPool();
+        executor.setName(executor.getName() + "-client");
+
+        factory = new SPDYClient.Factory(executor);
+        factory.start();
+
+        httpClient = new HttpClient(new HttpClientTransportOverSPDY(factory.newSPDYClient(SPDY.V3)), null);
+        httpClient.setExecutor(executor);
+        httpClient.start();
+    }
+
+    @After
+    public void dispose() throws Exception
+    {
+        if (httpClient != null)
+            httpClient.stop();
+        if (factory != null)
+            factory.stop();
+        if (server != null)
+            server.stop();
+    }
+
+    @Test
+    public void testCustomProxy() throws Exception
+    {
+        final String serverHost = "server";
+        final int status = HttpStatus.NO_CONTENT_204;
+        prepare(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                if (!URI.create(baseRequest.getUri().toString()).isAbsolute())
+                    response.setStatus(HttpServletResponse.SC_USE_PROXY);
+                else if (serverHost.equals(request.getServerName()))
+                    response.setStatus(status);
+                else
+                    response.setStatus(HttpServletResponse.SC_NOT_ACCEPTABLE);
+            }
+        });
+
+        // Setup the custom proxy
+        int proxyPort = connector.getLocalPort();
+        int serverPort = proxyPort + 1; // Any port will do for these tests - just not the same as the proxy
+        httpClient.getProxyConfiguration().getProxies().add(new CAFEBABEProxy(new Origin.Address("localhost", proxyPort), false));
+
+        ContentResponse response = httpClient.newRequest(serverHost, serverPort)
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        Assert.assertEquals(status, response.getStatus());
+    }
+
+    private class CAFEBABEProxy extends ProxyConfiguration.Proxy
+    {
+        private CAFEBABEProxy(Origin.Address address, boolean secure)
+        {
+            super(address, secure);
+        }
+
+        @Override
+        public ClientConnectionFactory newClientConnectionFactory(ClientConnectionFactory connectionFactory)
+        {
+            return new CAFEBABEClientConnectionFactory(connectionFactory);
+        }
+    }
+
+    private static class CAFEBABEClientConnectionFactory implements ClientConnectionFactory
+    {
+        private final ClientConnectionFactory connectionFactory;
+
+        private CAFEBABEClientConnectionFactory(ClientConnectionFactory connectionFactory)
+        {
+            this.connectionFactory = connectionFactory;
+        }
+
+        @Override
+        public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map<String, Object> context) throws IOException
+        {
+            HttpDestination destination = (HttpDestination)context.get(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY);
+            Executor executor = destination.getHttpClient().getExecutor();
+            return new CAFEBABEConnection(endPoint, executor, connectionFactory, context);
+        }
+    }
+
+    private static class CAFEBABEConnection extends AbstractConnection
+    {
+        private final ClientConnectionFactory connectionFactory;
+        private final Map<String, Object> context;
+
+        public CAFEBABEConnection(EndPoint endPoint, Executor executor, ClientConnectionFactory connectionFactory, Map<String, Object> context)
+        {
+            super(endPoint, executor);
+            this.connectionFactory = connectionFactory;
+            this.context = context;
+        }
+
+        @Override
+        public void onOpen()
+        {
+            super.onOpen();
+            fillInterested();
+            getEndPoint().write(new Callback.Adapter(), ByteBuffer.wrap(CAFE_BABE));
+        }
+
+        @Override
+        public void onFillable()
+        {
+            try
+            {
+                ByteBuffer buffer = BufferUtil.allocate(4);
+                int filled = getEndPoint().fill(buffer);
+                Assert.assertEquals(4, filled);
+                Assert.assertArrayEquals(CAFE_BABE, buffer.array());
+
+                // We are good, upgrade the connection
+                ClientConnectionFactory.Helper.replaceConnection(this, connectionFactory.newConnection(getEndPoint(), context));
+            }
+            catch (Throwable x)
+            {
+                close();
+                @SuppressWarnings("unchecked")
+                Promise<Connection> promise = (Promise<Connection>)context.get(HttpClientTransport.HTTP_CONNECTION_PROMISE_CONTEXT_KEY);
+                promise.failed(x);
+            }
+        }
+    }
+
+    private class CAFEBABEServerConnectionFactory extends AbstractConnectionFactory
+    {
+        private final org.eclipse.jetty.server.ConnectionFactory connectionFactory;
+
+        private CAFEBABEServerConnectionFactory(org.eclipse.jetty.server.ConnectionFactory connectionFactory)
+        {
+            super("cafebabe");
+            this.connectionFactory = connectionFactory;
+        }
+
+        @Override
+        public org.eclipse.jetty.io.Connection newConnection(Connector connector, EndPoint endPoint)
+        {
+            return new CAFEBABEServerConnection(connector, endPoint, connectionFactory);
+        }
+    }
+
+    private class CAFEBABEServerConnection extends AbstractConnection
+    {
+        private final org.eclipse.jetty.server.ConnectionFactory connectionFactory;
+
+        public CAFEBABEServerConnection(Connector connector, EndPoint endPoint, org.eclipse.jetty.server.ConnectionFactory connectionFactory)
+        {
+            super(endPoint, connector.getExecutor());
+            this.connectionFactory = connectionFactory;
+        }
+
+        @Override
+        public void onOpen()
+        {
+            super.onOpen();
+            fillInterested();
+        }
+
+        @Override
+        public void onFillable()
+        {
+            try
+            {
+                ByteBuffer buffer = BufferUtil.allocate(4);
+                int filled = getEndPoint().fill(buffer);
+                Assert.assertEquals(4, filled);
+                Assert.assertArrayEquals(CAFE_BABE, buffer.array());
+                getEndPoint().write(new Callback.Adapter(), buffer);
+
+                // We are good, upgrade the connection
+                ClientConnectionFactory.Helper.replaceConnection(this, connectionFactory.newConnection(connector, getEndPoint()));
+            }
+            catch (Throwable x)
+            {
+                close();
+            }
+        }
+    }
+}
diff --git a/jetty-spdy/spdy-http-client-transport/src/test/java/org/eclipse/jetty/spdy/client/http/HttpClientTest.java b/jetty-spdy/spdy-http-client-transport/src/test/java/org/eclipse/jetty/spdy/client/http/HttpClientTest.java
new file mode 100644
index 0000000..6af71f2
--- /dev/null
+++ b/jetty-spdy/spdy-http-client-transport/src/test/java/org/eclipse/jetty/spdy/client/http/HttpClientTest.java
@@ -0,0 +1,467 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.spdy.client.http;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URLEncoder;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.zip.GZIPOutputStream;
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.client.api.Result;
+import org.eclipse.jetty.client.util.BytesContentProvider;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.toolchain.test.annotation.Slow;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class HttpClientTest extends AbstractHttpClientServerTest
+{
+    public HttpClientTest(SslContextFactory sslContextFactory)
+    {
+        super(sslContextFactory);
+    }
+
+    @Test
+    public void test_GET_ResponseWithoutContent() throws Exception
+    {
+        start(new EmptyServerHandler());
+
+        Response response = client.GET(scheme + "://localhost:" + connector.getLocalPort());
+
+        Assert.assertNotNull(response);
+        Assert.assertEquals(200, response.getStatus());
+    }
+
+    @Test
+    public void test_GET_ResponseWithContent() throws Exception
+    {
+        final byte[] data = new byte[]{0, 1, 2, 3, 4, 5, 6, 7};
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                response.getOutputStream().write(data);
+                baseRequest.setHandled(true);
+            }
+        });
+
+        ContentResponse response = client.GET(scheme + "://localhost:" + connector.getLocalPort());
+
+        Assert.assertNotNull(response);
+        Assert.assertEquals(200, response.getStatus());
+        byte[] content = response.getContent();
+        Assert.assertArrayEquals(data, content);
+    }
+
+    @Test
+    public void test_GET_WithParameters_ResponseWithContent() throws Exception
+    {
+        final String paramName1 = "a";
+        final String paramName2 = "b";
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                response.setCharacterEncoding("UTF-8");
+                ServletOutputStream output = response.getOutputStream();
+                String paramValue1 = request.getParameter(paramName1);
+                output.write(paramValue1.getBytes("UTF-8"));
+                String paramValue2 = request.getParameter(paramName2);
+                Assert.assertEquals("", paramValue2);
+                output.write("empty".getBytes("UTF-8"));
+                baseRequest.setHandled(true);
+            }
+        });
+
+        String value1 = "\u20AC";
+        String paramValue1 = URLEncoder.encode(value1, "UTF-8");
+        String query = paramName1 + "=" + paramValue1 + "&" + paramName2;
+        ContentResponse response = client.GET(scheme + "://localhost:" + connector.getLocalPort() + "/?" + query);
+
+        Assert.assertNotNull(response);
+        Assert.assertEquals(200, response.getStatus());
+        String content = new String(response.getContent(), "UTF-8");
+        Assert.assertEquals(value1 + "empty", content);
+    }
+
+    @Test
+    public void test_GET_WithParametersMultiValued_ResponseWithContent() throws Exception
+    {
+        final String paramName1 = "a";
+        final String paramName2 = "b";
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                response.setCharacterEncoding("UTF-8");
+                ServletOutputStream output = response.getOutputStream();
+                String[] paramValues1 = request.getParameterValues(paramName1);
+                for (String paramValue : paramValues1)
+                    output.write(paramValue.getBytes("UTF-8"));
+                String paramValue2 = request.getParameter(paramName2);
+                output.write(paramValue2.getBytes("UTF-8"));
+                baseRequest.setHandled(true);
+            }
+        });
+
+        String value11 = "\u20AC";
+        String value12 = "\u20AA";
+        String value2 = "&";
+        String paramValue11 = URLEncoder.encode(value11, "UTF-8");
+        String paramValue12 = URLEncoder.encode(value12, "UTF-8");
+        String paramValue2 = URLEncoder.encode(value2, "UTF-8");
+        String query = paramName1 + "=" + paramValue11 + "&" + paramName1 + "=" + paramValue12 + "&" + paramName2 + "=" + paramValue2;
+        ContentResponse response = client.GET(scheme + "://localhost:" + connector.getLocalPort() + "/?" + query);
+
+        Assert.assertNotNull(response);
+        Assert.assertEquals(200, response.getStatus());
+        String content = new String(response.getContent(), "UTF-8");
+        Assert.assertEquals(value11 + value12 + value2, content);
+    }
+
+    @Test
+    public void test_POST_WithParameters() throws Exception
+    {
+        final String paramName = "a";
+        final String paramValue = "\u20AC";
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                String value = request.getParameter(paramName);
+                if (paramValue.equals(value))
+                {
+                    response.setCharacterEncoding("UTF-8");
+                    response.setContentType("text/plain");
+                    response.getOutputStream().print(value);
+                }
+            }
+        });
+
+        ContentResponse response = client.POST(scheme + "://localhost:" + connector.getLocalPort())
+                .param(paramName, paramValue)
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        Assert.assertNotNull(response);
+        Assert.assertEquals(200, response.getStatus());
+        Assert.assertEquals(paramValue, new String(response.getContent(), "UTF-8"));
+    }
+
+    @Test
+    public void test_PUT_WithParameters() throws Exception
+    {
+        final String paramName = "a";
+        final String paramValue = "\u20AC";
+        final String encodedParamValue = URLEncoder.encode(paramValue, "UTF-8");
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                String value = request.getParameter(paramName);
+                if (paramValue.equals(value))
+                {
+                    response.setCharacterEncoding("UTF-8");
+                    response.setContentType("text/plain");
+                    response.getOutputStream().print(value);
+                }
+            }
+        });
+
+        URI uri = URI.create(scheme + "://localhost:" + connector.getLocalPort() + "/path?" + paramName + "=" + encodedParamValue);
+        ContentResponse response = client.newRequest(uri)
+                .method(HttpMethod.PUT)
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        Assert.assertNotNull(response);
+        Assert.assertEquals(200, response.getStatus());
+        Assert.assertEquals(paramValue, new String(response.getContent(), "UTF-8"));
+    }
+
+    @Test
+    public void test_POST_WithParameters_WithContent() throws Exception
+    {
+        final byte[] content = {0, 1, 2, 3};
+        final String paramName = "a";
+        final String paramValue = "\u20AC";
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                String value = request.getParameter(paramName);
+                if (paramValue.equals(value))
+                {
+                    response.setCharacterEncoding("UTF-8");
+                    response.setContentType("application/octet-stream");
+                    response.getOutputStream().write(content);
+                }
+            }
+        });
+
+        ContentResponse response = client.POST(scheme + "://localhost:" + connector.getLocalPort() + "/?b=1")
+                .param(paramName, paramValue)
+                .content(new BytesContentProvider(content))
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        Assert.assertNotNull(response);
+        Assert.assertEquals(200, response.getStatus());
+        Assert.assertArrayEquals(content, response.getContent());
+    }
+
+    @Test
+    public void test_POST_WithContent_NotifiesRequestContentListener() throws Exception
+    {
+        final byte[] content = {0, 1, 2, 3};
+        start(new EmptyServerHandler());
+
+        ContentResponse response = client.POST(scheme + "://localhost:" + connector.getLocalPort())
+                .onRequestContent(new Request.ContentListener()
+                {
+                    @Override
+                    public void onContent(Request request, ByteBuffer buffer)
+                    {
+                        byte[] bytes = new byte[buffer.remaining()];
+                        buffer.get(bytes);
+                        if (!Arrays.equals(content, bytes))
+                            request.abort(new Exception());
+                    }
+                })
+                .content(new BytesContentProvider(content))
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        Assert.assertNotNull(response);
+        Assert.assertEquals(200, response.getStatus());
+    }
+
+    @Test
+    public void test_POST_WithContent_TracksProgress() throws Exception
+    {
+        start(new EmptyServerHandler());
+
+        final AtomicInteger progress = new AtomicInteger();
+        ContentResponse response = client.POST(scheme + "://localhost:" + connector.getLocalPort())
+                .onRequestContent(new Request.ContentListener()
+                {
+                    @Override
+                    public void onContent(Request request, ByteBuffer buffer)
+                    {
+                        byte[] bytes = new byte[buffer.remaining()];
+                        Assert.assertEquals(1, bytes.length);
+                        buffer.get(bytes);
+                        Assert.assertEquals(bytes[0], progress.getAndIncrement());
+                    }
+                })
+                .content(new BytesContentProvider(new byte[]{0}, new byte[]{1}, new byte[]{2}, new byte[]{3}, new byte[]{4}))
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        Assert.assertNotNull(response);
+        Assert.assertEquals(200, response.getStatus());
+        Assert.assertEquals(5, progress.get());
+    }
+
+    @Test
+    public void test_GZIP_ContentEncoding() throws Exception
+    {
+        final byte[] data = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                response.setHeader("Content-Encoding", "gzip");
+                GZIPOutputStream gzipOutput = new GZIPOutputStream(response.getOutputStream());
+                gzipOutput.write(data);
+                gzipOutput.finish();
+            }
+        });
+
+        ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        Assert.assertEquals(200, response.getStatus());
+        Assert.assertArrayEquals(data, response.getContent());
+    }
+
+    @Slow
+    @Test
+    public void test_Request_IdleTimeout() throws Exception
+    {
+        final long idleTimeout = 1000;
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                try
+                {
+                    baseRequest.setHandled(true);
+                    TimeUnit.MILLISECONDS.sleep(2 * idleTimeout);
+                }
+                catch (InterruptedException x)
+                {
+                    throw new ServletException(x);
+                }
+            }
+        });
+
+        final String host = "localhost";
+        final int port = connector.getLocalPort();
+        try
+        {
+            client.newRequest(host, port)
+                    .scheme(scheme)
+                    .idleTimeout(idleTimeout, TimeUnit.MILLISECONDS)
+                    .timeout(3 * idleTimeout, TimeUnit.MILLISECONDS)
+                    .send();
+            Assert.fail();
+        }
+        catch (ExecutionException expected)
+        {
+            Assert.assertTrue(expected.getCause() instanceof TimeoutException);
+        }
+
+        // Make another request without specifying the idle timeout, should not fail
+        ContentResponse response = client.newRequest(host, port)
+                .scheme(scheme)
+                .timeout(3 * idleTimeout, TimeUnit.MILLISECONDS)
+                .send();
+
+        Assert.assertNotNull(response);
+        Assert.assertEquals(200, response.getStatus());
+    }
+
+    @Test
+    public void testSendToIPv6Address() throws Exception
+    {
+        start(new EmptyServerHandler());
+
+        ContentResponse response = client.newRequest("[::1]", connector.getLocalPort())
+                .scheme(scheme)
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        Assert.assertNotNull(response);
+        Assert.assertEquals(200, response.getStatus());
+    }
+
+    @Test
+    public void test_HEAD_With_ResponseContentLength() throws Exception
+    {
+        final int length = 1024;
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                response.getOutputStream().write(new byte[length]);
+            }
+        });
+
+        // HEAD requests receive a Content-Length header, but do not
+        // receive the content so they must handle this case properly
+        ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .method(HttpMethod.HEAD)
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        Assert.assertNotNull(response);
+        Assert.assertEquals(200, response.getStatus());
+        Assert.assertEquals(0, response.getContent().length);
+
+        // Perform a normal GET request to be sure the content is now read
+        response = client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .timeout(5, TimeUnit.SECONDS)
+                .send();
+
+        Assert.assertNotNull(response);
+        Assert.assertEquals(200, response.getStatus());
+        Assert.assertEquals(length, response.getContent().length);
+    }
+
+    @Test
+    public void testLongPollIsAbortedWhenClientIsStopped() throws Exception
+    {
+        final CountDownLatch latch = new CountDownLatch(1);
+        start(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                request.startAsync();
+                latch.countDown();
+            }
+        });
+
+        final CountDownLatch completeLatch = new CountDownLatch(1);
+        client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .send(new Response.CompleteListener()
+                {
+                    @Override
+                    public void onComplete(Result result)
+                    {
+                        if (result.isFailed())
+                            completeLatch.countDown();
+                    }
+                });
+
+        Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+
+        // Stop the client, the complete listener must be invoked.
+        client.stop();
+
+        Assert.assertTrue(completeLatch.await(5, TimeUnit.SECONDS));
+    }
+}
diff --git a/jetty-spdy/spdy-http-client-transport/src/test/resources/jetty-logging.properties b/jetty-spdy/spdy-http-client-transport/src/test/resources/jetty-logging.properties
new file mode 100644
index 0000000..8163013
--- /dev/null
+++ b/jetty-spdy/spdy-http-client-transport/src/test/resources/jetty-logging.properties
@@ -0,0 +1,4 @@
+org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
+#org.eclipse.jetty.LEVEL=DEBUG
+#org.eclipse.jetty.client.LEVEL=DEBUG
+#org.eclipse.jetty.spdy.LEVEL=DEBUG
diff --git a/jetty-spdy/spdy-jetty-http-webapp/src/main/resources/keystore.jks b/jetty-spdy/spdy-http-client-transport/src/test/resources/keystore.jks
similarity index 100%
copy from jetty-spdy/spdy-jetty-http-webapp/src/main/resources/keystore.jks
copy to jetty-spdy/spdy-http-client-transport/src/test/resources/keystore.jks
diff --git a/jetty-spdy/spdy-jetty-http-webapp/src/main/resources/truststore.jks b/jetty-spdy/spdy-http-client-transport/src/test/resources/truststore.jks
similarity index 100%
copy from jetty-spdy/spdy-jetty-http-webapp/src/main/resources/truststore.jks
copy to jetty-spdy/spdy-http-client-transport/src/test/resources/truststore.jks
diff --git a/jetty-spdy/spdy-http-common/pom.xml b/jetty-spdy/spdy-http-common/pom.xml
new file mode 100644
index 0000000..9a21791
--- /dev/null
+++ b/jetty-spdy/spdy-http-common/pom.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>org.eclipse.jetty.spdy</groupId>
+        <artifactId>spdy-parent</artifactId>
+        <version>9.2.13.v20150730</version>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>spdy-http-common</artifactId>
+    <name>Jetty :: SPDY :: HTTP Common</name>
+
+    <properties>
+        <bundle-symbolic-name>${project.groupId}.http.common</bundle-symbolic-name>
+    </properties>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>manifest</goal>
+                        </goals>
+                        <configuration>
+                            <instructions>
+                                <Export-Package>org.eclipse.jetty.spdy.http;version="9.1"</Export-Package>
+                                <Import-Package>!org.eclipse.jetty.npn,org.eclipse.jetty.*;version="[9.0,10.0)",*</Import-Package>
+                            </instructions>
+                          </configuration>
+                       </execution>
+                  </executions>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.eclipse.jetty.spdy</groupId>
+            <artifactId>spdy-core</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+    </dependencies>
+
+</project>
diff --git a/jetty-spdy/spdy-http-common/src/main/java/org/eclipse/jetty/spdy/http/HTTPSPDYHeader.java b/jetty-spdy/spdy-http-common/src/main/java/org/eclipse/jetty/spdy/http/HTTPSPDYHeader.java
new file mode 100644
index 0000000..845ee96
--- /dev/null
+++ b/jetty-spdy/spdy-http-common/src/main/java/org/eclipse/jetty/spdy/http/HTTPSPDYHeader.java
@@ -0,0 +1,82 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.spdy.http;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.jetty.spdy.api.SPDY;
+
+/**
+ * <p>{@link HTTPSPDYHeader} defines the SPDY headers that are not also HTTP headers,
+ * such as <tt>method</tt>, <tt>version</tt>, etc. or that are treated differently
+ * by the SPDY protocol, such as <tt>host</tt>.</p>
+ */
+public enum HTTPSPDYHeader
+{
+    METHOD("method", ":method"),
+    URI("url", ":path"),
+    VERSION("version", ":version"),
+    SCHEME("scheme", ":scheme"),
+    HOST("host", ":host"),
+    STATUS("status", ":status");
+
+    public static HTTPSPDYHeader from(short version, String name)
+    {
+        switch (version)
+        {
+            case SPDY.V2:
+                return Names.v2Names.get(name);
+            case SPDY.V3:
+                return Names.v3Names.get(name);
+            default:
+                throw new IllegalStateException();
+        }
+    }
+
+    private final String v2Name;
+    private final String v3Name;
+
+    private HTTPSPDYHeader(String v2Name, String v3Name)
+    {
+        this.v2Name = v2Name;
+        Names.v2Names.put(v2Name, this);
+        this.v3Name = v3Name;
+        Names.v3Names.put(v3Name, this);
+    }
+
+    public String name(short version)
+    {
+        switch (version)
+        {
+            case SPDY.V2:
+                return v2Name;
+            case SPDY.V3:
+                return v3Name;
+            default:
+                throw new IllegalStateException();
+        }
+    }
+
+    private static class Names
+    {
+        private static final Map<String, HTTPSPDYHeader> v2Names = new HashMap<>();
+        private static final Map<String, HTTPSPDYHeader> v3Names = new HashMap<>();
+    }
+}
diff --git a/jetty-spdy/spdy-http-server/pom.xml b/jetty-spdy/spdy-http-server/pom.xml
new file mode 100644
index 0000000..be4cd9b
--- /dev/null
+++ b/jetty-spdy/spdy-http-server/pom.xml
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>org.eclipse.jetty.spdy</groupId>
+        <artifactId>spdy-parent</artifactId>
+        <version>9.2.13.v20150730</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>spdy-http-server</artifactId>
+    <name>Jetty :: SPDY :: HTTP Server</name>
+
+    <properties>
+        <bundle-symbolic-name>${project.groupId}.http.server</bundle-symbolic-name>
+    </properties>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-assembly-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>single</goal>
+                        </goals>
+                        <configuration>
+                            <descriptorRefs>
+                                <descriptorRef>config</descriptorRef>
+                            </descriptorRefs>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-jar-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>artifact-jars</id>
+                        <goals>
+                            <goal>jar</goal>
+                            <goal>test-jar</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>manifest</goal>
+                        </goals>
+                        <configuration>
+                            <instructions>
+                                <Export-Package>org.eclipse.jetty.spdy.server.http;version="9.1",
+                                    org.eclipse.jetty.spdy.server.proxy;version="9.1"
+                                </Export-Package>
+                                <Import-Package>!org.eclipse.jetty.npn,org.eclipse.jetty.*;version="[9.0,10.0)",*
+                                </Import-Package>
+                                <_nouses>true</_nouses>
+                            </instructions>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.eclipse.jetty.spdy</groupId>
+            <artifactId>spdy-http-common</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty.spdy</groupId>
+            <artifactId>spdy-server</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-client</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-servlet</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-servlets</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty.npn</groupId>
+            <artifactId>npn-api</artifactId>
+            <version>${npn.api.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-continuation</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-core</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+</project>
diff --git a/jetty-spdy/spdy-http-server/src/main/config/etc/jetty-spdy-proxy.xml b/jetty-spdy/spdy-http-server/src/main/config/etc/jetty-spdy-proxy.xml
new file mode 100644
index 0000000..c525bd1
--- /dev/null
+++ b/jetty-spdy/spdy-http-server/src/main/config/etc/jetty-spdy-proxy.xml
@@ -0,0 +1,158 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
+
+<!-- ============================================================= -->
+<!-- Configure the Jetty Server instance with an ID "Server"       -->
+<!-- by adding a SPDY connector.                                   -->
+<!-- This configuration must be used in conjunction with jetty.xml -->
+<!-- It should not be used with jetty-https.xml as this connector  -->
+<!-- can provide both HTTPS and SPDY connections                   -->
+<!-- ============================================================= -->
+<Configure id="Server" class="org.eclipse.jetty.server.Server">
+
+  <!-- =========================================================== -->
+  <!-- Setup the SSL Context factory used to establish all TLS     -->
+  <!-- Connections and session.                                    -->
+  <!--                                                             -->
+  <!-- Consult the javadoc of o.e.j.util.ssl.SslContextFactory     -->
+  <!-- o.e.j.server.HttpConnectionFactory for all configuration    -->
+  <!-- that may be set here.                                       -->
+  <!-- =========================================================== -->
+  <New id="sslContextFactory" class="org.eclipse.jetty.util.ssl.SslContextFactory">
+    <Set name="KeyStorePath"><Property name="jetty.home" default="." />/etc/keystore</Set>
+    <Set name="KeyStorePassword">OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4</Set>
+    <Set name="KeyManagerPassword">OBF:1u2u1wml1z7s1z7a1wnl1u2g</Set>
+    <Set name="TrustStorePath"><Property name="jetty.home" default="." />/etc/keystore</Set>
+    <Set name="TrustStorePassword">OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4</Set>
+  </New>
+
+  <!-- =========================================================== -->
+  <!-- Enables NPN debugging on System.err                         -->
+  <!-- ===========================================================
+  <Set class="org.eclipse.jetty.npn.NextProtoNego" name="debug" type="boolean">true</Set>
+  -->
+
+  <!-- =========================================================== -->
+  <!-- Create a TLS specific HttpConfiguration based on the        -->
+  <!-- common HttpConfiguration defined in jetty.xml               -->
+  <!-- Add a SecureRequestCustomizer to extract certificate and    -->
+  <!-- session information                                         -->
+  <!-- =========================================================== -->
+  <New id="tlsHttpConfig" class="org.eclipse.jetty.server.HttpConfiguration">
+    <Arg><Ref refid="httpConfig"/></Arg>
+    <Call name="addCustomizer">
+      <Arg><New class="org.eclipse.jetty.server.SecureRequestCustomizer"/></Arg>
+    </Call>
+  </New>
+
+  <!-- =========================================================== -->
+  <!-- This is the upstream server connector.                      -->
+  <!-- It speaks non-SSL SPDY/3(HTTP) on port 9090.                -->
+  <!-- =========================================================== -->
+  <Call name="addConnector">
+    <Arg>
+      <New class="org.eclipse.jetty.server.ServerConnector">
+        <Arg name="server">
+          <Ref refid="Server"/>
+        </Arg>
+        <Arg name="factories">
+          <Array type="org.eclipse.jetty.server.ConnectionFactory">
+            <!-- SPDY/3 Connection factory -->
+            <Item>
+              <New class="org.eclipse.jetty.spdy.server.http.HTTPSPDYServerConnectionFactory">
+                <Arg name="version" type="int">3</Arg>
+                <Arg name="config">
+                  <Ref refid="tlsHttpConfig"/>
+                </Arg>
+              </New>
+            </Item>
+          </Array>
+        </Arg>
+        <Set name="port">9090</Set>
+      </New>
+    </Arg>
+  </Call>
+
+  <!-- =========================================================== -->
+  <!-- This ProxyEngine translates the incoming SPDY/x(HTTP)       -->
+  <!-- requests to SPDY/2(HTTP)                                    -->
+  <!-- =========================================================== -->
+  <New id="spdyProxyEngine" class="org.eclipse.jetty.spdy.server.proxy.SPDYProxyEngine">
+    <Arg>
+      <New class="org.eclipse.jetty.spdy.client.SPDYClient$Factory">
+        <Call name="start"/>
+      </New>
+    </Arg>
+  </New>
+
+  <!-- =========================================================== -->
+  <!-- The ProxyEngineSelector receives SPDY/x(HTTP) requests      -->
+  <!-- from proxy connectors below and is configured to process    -->
+  <!-- requests for host "localhost".                              -->
+  <!-- Such requests are converted from SPDY/x(HTTP) to            -->
+  <!-- SPDY/3(HTTP) by the configured ProxyEngine and forwarded    -->
+  <!-- to 127.0.0.1:9090, where they are served by the upstream    -->
+  <!-- server above.                                               -->
+  <!-- =========================================================== -->
+  <New id="proxyEngineSelector" class="org.eclipse.jetty.spdy.server.proxy.ProxyEngineSelector">
+    <Call name="putProxyEngine">
+      <Arg>spdy/3</Arg>
+      <Arg>
+        <Ref refid="spdyProxyEngine"/>
+      </Arg>
+    </Call>
+    <Set name="proxyServerInfos">
+      <Map>
+        <Entry>
+          <Item>localhost</Item>
+          <Item>
+            <New class="org.eclipse.jetty.spdy.server.proxy.ProxyEngineSelector$ProxyServerInfo">
+              <Arg type="String">spdy/3</Arg>
+              <Arg>127.0.0.1</Arg>
+              <Arg type="int">9090</Arg>
+            </New>
+          </Item>
+        </Entry>
+      </Map>
+    </Set>
+  </New>
+
+  <!-- =========================================================== -->
+  <!-- These are the reverse proxy connectors accepting requests   -->
+  <!-- from clients.                                               -->
+  <!-- They accept non-SSL (on port 8080) and SSL (on port 8443)   -->
+  <!-- HTTP, SPDY/2(HTTP) and SPDY/3(HTTP).                        -->
+  <!-- Non-SPDY HTTP requests are converted to SPDY internally     -->
+  <!-- and passed to the ProxyEngine above.                        -->
+  <!-- =========================================================== -->
+  <Call name="addConnector">
+    <Arg>
+      <New class="org.eclipse.jetty.spdy.server.proxy.HTTPSPDYProxyServerConnector">
+        <Arg>
+          <Ref refid="Server"/>
+        </Arg>
+        <Arg>
+          <Ref refid="proxyEngineSelector"/>
+        </Arg>
+        <Set name="Port">8080</Set>
+      </New>
+    </Arg>
+  </Call>
+  <Call name="addConnector">
+    <Arg>
+      <New class="org.eclipse.jetty.spdy.server.proxy.HTTPSPDYProxyServerConnector">
+        <Arg>
+          <Ref refid="Server"/>
+        </Arg>
+        <Arg>
+          <Ref refid="sslContextFactory"/>
+        </Arg>
+        <Arg>
+          <Ref refid="proxyEngineSelector"/>
+        </Arg>
+        <Set name="Port">8443</Set>
+      </New>
+    </Arg>
+  </Call>
+
+</Configure>
diff --git a/jetty-spdy/spdy-http-server/src/main/config/etc/jetty-spdy.xml b/jetty-spdy/spdy-http-server/src/main/config/etc/jetty-spdy.xml
new file mode 100644
index 0000000..b094d7c
--- /dev/null
+++ b/jetty-spdy/spdy-http-server/src/main/config/etc/jetty-spdy.xml
@@ -0,0 +1,139 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
+
+<!-- ============================================================= -->
+<!-- Configure a SPDY connector.                                   -->
+<!-- This configuration must be used in conjunction with jetty.xml -->
+<!-- and jetty-ssl.xml                                             -->
+<!-- ============================================================= -->
+<Configure id="Server" class="org.eclipse.jetty.server.Server">
+
+    <!-- =========================================================== -->
+    <!-- Create a push strategy which can be used by reference by    -->
+    <!-- individual connection factories below.                      -->
+    <!--                                                             -->
+    <!-- Consult the javadoc of o.e.j.spdy.server.http.ReferrerPushStrategy -->
+    <!-- for all configuration that may be set here.                 -->
+    <!-- =========================================================== -->
+    <New id="pushStrategy" class="org.eclipse.jetty.spdy.server.http.ReferrerPushStrategy">
+        <!-- Uncomment to blacklist browsers for this push strategy. If one of the blacklisted Strings occurs in the
+             user-agent header sent by the client, push will be disabled for this browser. This is case insensitive" -->
+        <!--
+        <Set name="UserAgentBlacklist">
+            <Array type="String">
+                <Item>.*(?i)firefox/14.*</Item>
+                <Item>.*(?i)firefox/15.*</Item>
+                <Item>.*(?i)firefox/16.*</Item>
+            </Array>
+        </Set>
+        -->
+
+        <!-- Uncomment to override default file extensions to push -->
+        <!--
+        <Set name="PushRegexps">
+            <Array type="String">
+               <Item>.*\.css</Item>
+               <Item>.*\.js</Item>
+               <Item>.*\.png</Item>
+               <Item>.*\.jpg</Item>
+               <Item>.*\.gif</Item>
+           </Array>
+        </Set>
+        -->
+        <Set name="referrerPushPeriod">5000</Set>
+        <Set name="maxAssociatedResources">32</Set>
+    </New>
+
+    <!-- =========================================================== -->
+    <!-- Add a SPDY/HTTPS Connector.                                 -->
+    <!-- Configure an o.e.j.server.ServerConnector with connection   -->
+    <!-- factories for TLS (aka SSL), ProtoNego, SPDY and HTTP to    -->
+    <!-- provide a connector that can accept HTTPS or SPDY           -->
+    <!-- connections.                                                -->
+    <!--                                                             -->
+    <!-- All accepted TLS connections are initially wired to a       -->
+    <!-- Protonego connection, which attempts to use a TLS extension -->
+    <!-- to negotiation the protocol.  If it is not supported by     -->
+    <!-- the client, then the connection is replaced by a HTTP       -->
+    <!-- connection.  If a specific protocol version (eg spdy/3) is  -->
+    <!-- negotiated, then the appropriate connection factory         -->
+    <!-- is used to create a connection to replace the connection    -->
+    <!--                                                             -->
+    <!-- The final result is a SPDY or HTTP connection wired behind  -->
+    <!-- a TLS (aka SSL) connection.                                 -->
+    <!--                                                             -->
+    <!-- Consult the javadoc of o.e.j.server.ServerConnector and the -->
+    <!-- specific connection factory types for all configuration     -->
+    <!-- that may be set here.                                       -->
+    <!-- =========================================================== -->
+    <Call id="spdyConnector" name="addConnector">
+        <Arg>
+            <New class="org.eclipse.jetty.server.ServerConnector">
+                <Arg name="server">
+                    <Ref refid="Server"/>
+                </Arg>
+                <Arg name="factories">
+                    <Array type="org.eclipse.jetty.server.ConnectionFactory">
+
+                        <!-- SSL Connection factory with Protonego as next protocol -->
+                        <Item>
+                            <New class="org.eclipse.jetty.server.SslConnectionFactory">
+                                <Arg name="next"><Property name="protonego"/></Arg>
+                                <Arg name="sslContextFactory">
+                                    <Ref refid="sslContextFactory"/>
+                                </Arg>
+                            </New>
+                        </Item>
+
+                        <!-- NPN Connection factory with HTTP as default protocol -->
+                        <Item>
+			    <Ref refid="protonego"/>
+                        </Item>
+
+                        <!-- SPDY/3 Connection factory -->
+                        <Item>
+                            <New class="org.eclipse.jetty.spdy.server.http.HTTPSPDYServerConnectionFactory">
+                                <Arg name="version" type="int">3</Arg>
+                                <Arg name="config">
+                                    <Ref refid="sslHttpConfig"/>
+                                </Arg>
+                                <!-- Set the initial window size for this SPDY connector. -->
+                                <!-- See: http://www.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3#TOC-2.6.8-WINDOW_UPDATE -->
+                                <Set name="initialWindowSize"><Property name="spdy.initialWindowSize" default="65536"/></Set>
+                                <!-- Uncomment to enable ReferrerPushStrategy -->
+                                <!--<Arg name="pushStrategy"><Ref refid="pushStrategy"/></Arg>-->
+                            </New>
+                        </Item>
+
+                        <!-- SPDY/2 Connection factory -->
+                        <Item>
+                            <New class="org.eclipse.jetty.spdy.server.http.HTTPSPDYServerConnectionFactory">
+                                <Arg name="version" type="int">2</Arg>
+                                <Arg name="config">
+                                    <Ref refid="sslHttpConfig"/>
+                                </Arg>
+                                <!-- Set the initial window size for this SPDY connector. -->
+                                <!-- See: http://www.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3#TOC-2.6.8-WINDOW_UPDATE -->
+                                <Set name="initialWindowSize"><Property name="spdy.initialWindowSize" default="65536"/></Set>
+                            </New>
+                        </Item>
+
+                        <!-- HTTP Connection factory -->
+                        <Item>
+                            <New class="org.eclipse.jetty.server.HttpConnectionFactory">
+                                <Arg name="config">
+                                    <Ref refid="sslHttpConfig"/>
+                                </Arg>
+                            </New>
+                        </Item>
+                    </Array>
+                </Arg>
+
+                <Set name="host"><Property name="jetty.host"/></Set>
+                <Set name="port"><Property name="spdy.port" default="443"/></Set>
+                <Set name="idleTimeout"><Property name="spdy.timeout" default="30000"/></Set>
+            </New>
+        </Arg>
+    </Call>
+
+</Configure>
diff --git a/jetty-spdy/spdy-http-server/src/main/config/etc/protonego-npn.xml b/jetty-spdy/spdy-http-server/src/main/config/etc/protonego-npn.xml
new file mode 100644
index 0000000..6e30f39
--- /dev/null
+++ b/jetty-spdy/spdy-http-server/src/main/config/etc/protonego-npn.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
+
+<Configure id="protonego" class="org.eclipse.jetty.spdy.server.NPNServerConnectionFactory">
+    <Arg name="protocols">
+	<Array type="String">
+	    <Item>spdy/3</Item>
+	    <Item>spdy/2</Item>
+	    <Item>http/1.1</Item>
+	</Array>
+    </Arg>
+   
+    <Set name="defaultProtocol">http/1.1</Set>
+
+    <!-- =========================================================== -->
+    <!-- Enables NPN debugging on System.err                         -->
+    <!-- ===========================================================
+     <Set class="org.eclipse.jetty.npn.NextProtoNego" name="debug" type="boolean">true</Set>
+    -->
+
+</Configure>
diff --git a/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_04.mod b/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_04.mod
new file mode 100644
index 0000000..007570b
--- /dev/null
+++ b/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_04.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.0.v20120525/npn-boot-1.1.0.v20120525.jar|lib/npn/npn-boot-1.1.0.v20120525.jar
+
+[exec]
+-Xbootclasspath/p:lib/npn/npn-boot-1.1.0.v20120525.jar
diff --git a/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_05.mod b/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_05.mod
new file mode 100644
index 0000000..007570b
--- /dev/null
+++ b/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_05.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.0.v20120525/npn-boot-1.1.0.v20120525.jar|lib/npn/npn-boot-1.1.0.v20120525.jar
+
+[exec]
+-Xbootclasspath/p:lib/npn/npn-boot-1.1.0.v20120525.jar
diff --git a/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_06.mod b/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_06.mod
new file mode 100644
index 0000000..868a7a7
--- /dev/null
+++ b/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_06.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.1.v20121030/npn-boot-1.1.1.v20121030.jar|lib/npn/npn-boot-1.1.1.v20121030.jar
+
+[exec]
+-Xbootclasspath/p:lib/npn/npn-boot-1.1.1.v20121030.jar
diff --git a/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_07.mod b/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_07.mod
new file mode 100644
index 0000000..868a7a7
--- /dev/null
+++ b/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_07.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.1.v20121030/npn-boot-1.1.1.v20121030.jar|lib/npn/npn-boot-1.1.1.v20121030.jar
+
+[exec]
+-Xbootclasspath/p:lib/npn/npn-boot-1.1.1.v20121030.jar
diff --git a/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_09.mod b/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_09.mod
new file mode 100644
index 0000000..20c1db2
--- /dev/null
+++ b/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_09.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.3.v20130313/npn-boot-1.1.3.v20130313.jar|lib/npn/npn-boot-1.1.3.v20130313.jar
+
+[exec]
+-Xbootclasspath/p:lib/npn/npn-boot-1.1.3.v20130313.jar
diff --git a/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_10.mod b/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_10.mod
new file mode 100644
index 0000000..20c1db2
--- /dev/null
+++ b/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_10.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.3.v20130313/npn-boot-1.1.3.v20130313.jar|lib/npn/npn-boot-1.1.3.v20130313.jar
+
+[exec]
+-Xbootclasspath/p:lib/npn/npn-boot-1.1.3.v20130313.jar
diff --git a/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_11.mod b/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_11.mod
new file mode 100644
index 0000000..20c1db2
--- /dev/null
+++ b/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_11.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.3.v20130313/npn-boot-1.1.3.v20130313.jar|lib/npn/npn-boot-1.1.3.v20130313.jar
+
+[exec]
+-Xbootclasspath/p:lib/npn/npn-boot-1.1.3.v20130313.jar
diff --git a/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_13.mod b/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_13.mod
new file mode 100644
index 0000000..1645a52
--- /dev/null
+++ b/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_13.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.4.v20130313/npn-boot-1.1.4.v20130313.jar|lib/npn/npn-boot-1.1.4.v20130313.jar
+
+[exec]
+-Xbootclasspath/p:lib/npn/npn-boot-1.1.4.v20130313.jar
diff --git a/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_15.mod b/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_15.mod
new file mode 100644
index 0000000..73bc090
--- /dev/null
+++ b/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_15.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.5.v20130313/npn-boot-1.1.5.v20130313.jar|lib/npn/npn-boot-1.1.5.v20130313.jar
+
+[exec]
+-Xbootclasspath/p:lib/npn/npn-boot-1.1.5.v20130313.jar
diff --git a/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_17.mod b/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_17.mod
new file mode 100644
index 0000000..73bc090
--- /dev/null
+++ b/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_17.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.5.v20130313/npn-boot-1.1.5.v20130313.jar|lib/npn/npn-boot-1.1.5.v20130313.jar
+
+[exec]
+-Xbootclasspath/p:lib/npn/npn-boot-1.1.5.v20130313.jar
diff --git a/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_21.mod b/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_21.mod
new file mode 100644
index 0000000..73bc090
--- /dev/null
+++ b/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_21.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.5.v20130313/npn-boot-1.1.5.v20130313.jar|lib/npn/npn-boot-1.1.5.v20130313.jar
+
+[exec]
+-Xbootclasspath/p:lib/npn/npn-boot-1.1.5.v20130313.jar
diff --git a/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_25.mod b/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_25.mod
new file mode 100644
index 0000000..73bc090
--- /dev/null
+++ b/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_25.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.5.v20130313/npn-boot-1.1.5.v20130313.jar|lib/npn/npn-boot-1.1.5.v20130313.jar
+
+[exec]
+-Xbootclasspath/p:lib/npn/npn-boot-1.1.5.v20130313.jar
diff --git a/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_40.mod b/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_40.mod
new file mode 100644
index 0000000..465e6f0
--- /dev/null
+++ b/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_40.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.6.v20130911/npn-boot-1.1.6.v20130911.jar|lib/npn/npn-boot-1.1.6.v20130911.jar
+
+[exec]
+-Xbootclasspath/p:lib/npn/npn-boot-1.1.6.v20130911.jar
diff --git a/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_45.mod b/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_45.mod
new file mode 100644
index 0000000..465e6f0
--- /dev/null
+++ b/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_45.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.6.v20130911/npn-boot-1.1.6.v20130911.jar|lib/npn/npn-boot-1.1.6.v20130911.jar
+
+[exec]
+-Xbootclasspath/p:lib/npn/npn-boot-1.1.6.v20130911.jar
diff --git a/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_51.mod b/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_51.mod
new file mode 100644
index 0000000..465e6f0
--- /dev/null
+++ b/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_51.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.6.v20130911/npn-boot-1.1.6.v20130911.jar|lib/npn/npn-boot-1.1.6.v20130911.jar
+
+[exec]
+-Xbootclasspath/p:lib/npn/npn-boot-1.1.6.v20130911.jar
diff --git a/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_55.mod b/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_55.mod
new file mode 100644
index 0000000..5f8704d
--- /dev/null
+++ b/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_55.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.8.v20141013/npn-boot-1.1.8.v20141013.jar|lib/npn/npn-boot-1.1.8.v20141013.jar
+
+[exec]
+-Xbootclasspath/p:lib/npn/npn-boot-1.1.8.v20141013.jar
diff --git a/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_60.mod b/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_60.mod
new file mode 100644
index 0000000..5f8704d
--- /dev/null
+++ b/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_60.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.8.v20141013/npn-boot-1.1.8.v20141013.jar|lib/npn/npn-boot-1.1.8.v20141013.jar
+
+[exec]
+-Xbootclasspath/p:lib/npn/npn-boot-1.1.8.v20141013.jar
diff --git a/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_65.mod b/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_65.mod
new file mode 100644
index 0000000..5f8704d
--- /dev/null
+++ b/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_65.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.8.v20141013/npn-boot-1.1.8.v20141013.jar|lib/npn/npn-boot-1.1.8.v20141013.jar
+
+[exec]
+-Xbootclasspath/p:lib/npn/npn-boot-1.1.8.v20141013.jar
diff --git a/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_67.mod b/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_67.mod
new file mode 100644
index 0000000..5f8704d
--- /dev/null
+++ b/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_67.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.8.v20141013/npn-boot-1.1.8.v20141013.jar|lib/npn/npn-boot-1.1.8.v20141013.jar
+
+[exec]
+-Xbootclasspath/p:lib/npn/npn-boot-1.1.8.v20141013.jar
diff --git a/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_71.mod b/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_71.mod
new file mode 100644
index 0000000..851aca8
--- /dev/null
+++ b/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_71.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.9.v20141016/npn-boot-1.1.9.v20141016.jar|lib/npn/npn-boot-1.1.9.v20141016.jar
+
+[exec]
+-Xbootclasspath/p:lib/npn/npn-boot-1.1.9.v20141016.jar
diff --git a/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_72.mod b/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_72.mod
new file mode 100644
index 0000000..851aca8
--- /dev/null
+++ b/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_72.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.9.v20141016/npn-boot-1.1.9.v20141016.jar|lib/npn/npn-boot-1.1.9.v20141016.jar
+
+[exec]
+-Xbootclasspath/p:lib/npn/npn-boot-1.1.9.v20141016.jar
diff --git a/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_75.mod b/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_75.mod
new file mode 100644
index 0000000..a5fac11
--- /dev/null
+++ b/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_75.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.10.v20150130/npn-boot-1.1.10.v20150130.jar|lib/npn/npn-boot-1.1.10.v20150130.jar
+
+[exec]
+-Xbootclasspath/p:lib/npn/npn-boot-1.1.10.v20150130.jar
diff --git a/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_76.mod b/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_76.mod
new file mode 100644
index 0000000..a5fac11
--- /dev/null
+++ b/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_76.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.10.v20150130/npn-boot-1.1.10.v20150130.jar|lib/npn/npn-boot-1.1.10.v20150130.jar
+
+[exec]
+-Xbootclasspath/p:lib/npn/npn-boot-1.1.10.v20150130.jar
diff --git a/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_79.mod b/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_79.mod
new file mode 100644
index 0000000..a5fac11
--- /dev/null
+++ b/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_79.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.10.v20150130/npn-boot-1.1.10.v20150130.jar|lib/npn/npn-boot-1.1.10.v20150130.jar
+
+[exec]
+-Xbootclasspath/p:lib/npn/npn-boot-1.1.10.v20150130.jar
diff --git a/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_80.mod b/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_80.mod
new file mode 100644
index 0000000..2cce5fa
--- /dev/null
+++ b/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_80.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.11.v20150415/npn-boot-1.1.11.v20150415.jar|lib/npn/npn-boot-1.1.11.v20150415.jar
+
+[exec]
+-Xbootclasspath/p:lib/npn/npn-boot-1.1.11.v20150415.jar
diff --git a/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn.mod b/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn.mod
new file mode 100644
index 0000000..1a2c71d
--- /dev/null
+++ b/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn.mod
@@ -0,0 +1,37 @@
+# NPN is provided via a -Xbootclasspath that modifies the secure connections
+# in java to support the NPN layer needed for SPDY.
+#
+# This modification has a tight dependency on specific updates of Java 1.7.
+# (No support for Java 8 exists for npn / npn-boot, use alpn instead)
+#
+# The npn module will use an appropriate npn-boot jar for your specific
+# version of Java.
+#
+# IMPORTANT: Versions of Java that exist after this module was created are
+#            not guaranteed to work with existing npn-boot jars, and might
+#            need a new npn-boot to be created / tested / deployed by the
+#            Jetty project in order to provide support for these future
+#            Java versions.
+#
+# All versions of npn-boot can be found at
+# http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/
+
+
+[name]
+protonego-impl
+
+[depend]
+protonego-impl/npn-${java.version}
+
+[xml]
+etc/protonego-npn.xml
+
+[files]
+lib/
+lib/npn/
+
+[license]
+NPN is a hosted at github under the GPL v2 with ClassPath Exception.
+NPN replaces/modifies OpenJDK classes in the java.sun.security.ssl package.
+http://github.com/jetty-project/jetty-npn
+http://openjdk.java.net/legal/gplv2+ce.html
diff --git a/jetty-spdy/spdy-http-server/src/main/config/modules/spdy.mod b/jetty-spdy/spdy-http-server/src/main/config/modules/spdy.mod
new file mode 100644
index 0000000..cf79dfa
--- /dev/null
+++ b/jetty-spdy/spdy-http-server/src/main/config/modules/spdy.mod
@@ -0,0 +1,26 @@
+#
+# SPDY Support Module
+#
+
+[depend]
+ssl
+protonego
+
+[lib]
+lib/spdy/*.jar
+
+[xml]
+etc/jetty-ssl.xml
+etc/jetty-spdy.xml
+
+[ini-template]
+## SPDY Configuration
+
+# Port for SPDY connections
+spdy.port=8443
+
+# SPDY idle timeout in milliseconds
+spdy.timeout=30000
+
+# Initial Window Size for SPDY
+#spdy.initialWindowSize=65536
diff --git a/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/HTTPSPDYServerConnectionFactory.java b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/HTTPSPDYServerConnectionFactory.java
new file mode 100644
index 0000000..9be4bcb
--- /dev/null
+++ b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/HTTPSPDYServerConnectionFactory.java
@@ -0,0 +1,167 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.spdy.server.http;
+
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.spdy.api.DataInfo;
+import org.eclipse.jetty.spdy.api.HeadersInfo;
+import org.eclipse.jetty.spdy.api.PushInfo;
+import org.eclipse.jetty.spdy.api.ReplyInfo;
+import org.eclipse.jetty.spdy.api.Stream;
+import org.eclipse.jetty.spdy.api.StreamFrameListener;
+import org.eclipse.jetty.spdy.api.SynInfo;
+import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener;
+import org.eclipse.jetty.spdy.server.SPDYServerConnectionFactory;
+import org.eclipse.jetty.util.Fields;
+import org.eclipse.jetty.util.annotation.Name;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public class HTTPSPDYServerConnectionFactory extends SPDYServerConnectionFactory implements HttpConfiguration.ConnectionFactory
+{
+    private static final String CHANNEL_ATTRIBUTE = "org.eclipse.jetty.spdy.server.http.HTTPChannelOverSPDY";
+    private static final Logger LOG = Log.getLogger(HTTPSPDYServerConnectionFactory.class);
+
+    private final PushStrategy pushStrategy;
+    private final HttpConfiguration httpConfiguration;
+
+    public HTTPSPDYServerConnectionFactory(
+        @Name("version") int version,
+        @Name("config") HttpConfiguration config)
+    {
+        this(version,config,new PushStrategy.None());
+    }
+
+    public HTTPSPDYServerConnectionFactory(
+        @Name("version") int version,
+        @Name("config") HttpConfiguration config,
+        @Name("pushStrategy") PushStrategy pushStrategy)
+    {
+        super(version);
+        this.pushStrategy = pushStrategy;
+        httpConfiguration = config;
+        addBean(httpConfiguration);
+    }
+
+    @Override
+    public HttpConfiguration getHttpConfiguration()
+    {
+        return httpConfiguration;
+    }
+
+    @Override
+    protected ServerSessionFrameListener provideServerSessionFrameListener(Connector connector, EndPoint endPoint)
+    {
+        return new HTTPServerFrameListener(connector,endPoint);
+    }
+
+    private class HTTPServerFrameListener extends ServerSessionFrameListener.Adapter implements StreamFrameListener
+    {
+        private final Connector connector;
+        private final EndPoint endPoint;
+
+        public HTTPServerFrameListener(Connector connector,EndPoint endPoint)
+        {
+            this.endPoint = endPoint;
+            this.connector=connector;
+        }
+
+        @Override
+        public StreamFrameListener onSyn(final Stream stream, SynInfo synInfo)
+        {
+            // Every time we have a SYN, it maps to a HTTP request.
+            // We can have multiple concurrent SYNs on the same connection,
+            // and this is very different from HTTP, where only one request
+            // can arrive on the same connection, so we need to create an
+            // HttpChannel for each SYN in order to run concurrently.
+
+            if (LOG.isDebugEnabled())
+                LOG.debug("Received {} on {}", synInfo, stream);
+
+            Fields headers = synInfo.getHeaders();
+            // According to SPDY/3 spec section 3.2.1 user-agents MUST support gzip compression. Firefox omits the
+            // accept-encoding header as it is redundant to negotiate gzip compression support with the server,
+            // if clients have to accept it.
+            // So we inject the accept-encoding header here, even if not set by the client. This will enforce SPDY
+            // clients to follow the spec and enable gzip compression if GzipFilter or the like is enabled.
+            if (!(headers.get("accept-encoding") != null && headers.get("accept-encoding").getValue().contains
+                    ("gzip")))
+                headers.add("accept-encoding", "gzip");
+            HttpTransportOverSPDY transport = new HttpTransportOverSPDY(connector, httpConfiguration, endPoint,
+                    pushStrategy, stream, headers);
+            HttpInputOverSPDY input = new HttpInputOverSPDY();
+            HttpChannelOverSPDY channel = new HttpChannelOverSPDY(connector, httpConfiguration, endPoint, transport, input, stream);
+            stream.setAttribute(CHANNEL_ATTRIBUTE, channel);
+
+            channel.requestStart(headers, synInfo.isClose());
+
+            if (headers.isEmpty())
+            {
+                // If the SYN has no headers, they may come later in a HEADERS frame
+                return this;
+            }
+            else
+            {
+                if (synInfo.isClose())
+                    return null;
+                else
+                    return this;
+            }
+        }
+
+        @Override
+        public void onReply(Stream stream, ReplyInfo replyInfo)
+        {
+            // Do nothing, servers cannot get replies
+        }
+
+        @Override
+        public void onHeaders(Stream stream, HeadersInfo headersInfo)
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("Received {} on {}", headersInfo, stream);
+            HttpChannelOverSPDY channel = (HttpChannelOverSPDY)stream.getAttribute(CHANNEL_ATTRIBUTE);
+            channel.requestHeaders(headersInfo.getHeaders(), headersInfo.isClose());
+        }
+
+        @Override
+        public StreamFrameListener onPush(Stream stream, PushInfo pushInfo)
+        {
+            return null;
+        }
+
+        @Override
+        public void onData(Stream stream, final DataInfo dataInfo)
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("Received {} on {}", dataInfo, stream);
+            HttpChannelOverSPDY channel = (HttpChannelOverSPDY)stream.getAttribute(CHANNEL_ATTRIBUTE);
+            channel.requestContent(dataInfo, dataInfo.isClose());
+        }
+
+        @Override
+        public void onFailure(Stream stream, Throwable x)
+        {
+            LOG.debug(x);
+        }
+    }
+}
diff --git a/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/HTTPSPDYServerConnector.java b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/HTTPSPDYServerConnector.java
new file mode 100644
index 0000000..fd2d302
--- /dev/null
+++ b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/HTTPSPDYServerConnector.java
@@ -0,0 +1,82 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.spdy.server.http;
+
+import java.util.Collections;
+import java.util.Map;
+
+import org.eclipse.jetty.server.AbstractConnectionFactory;
+import org.eclipse.jetty.server.ConnectionFactory;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.HttpConnectionFactory;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.spdy.api.SPDY;
+import org.eclipse.jetty.spdy.server.NPNServerConnectionFactory;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+
+public class HTTPSPDYServerConnector extends ServerConnector
+{
+    public HTTPSPDYServerConnector(Server server)
+    {
+        this(server, Collections.<Short, PushStrategy>emptyMap());
+    }
+
+    public HTTPSPDYServerConnector(Server server, SslContextFactory sslContextFactory)
+    {
+        this(server, sslContextFactory, Collections.<Short, PushStrategy>emptyMap());
+    }
+
+    public HTTPSPDYServerConnector(Server server, Map<Short, PushStrategy> pushStrategies)
+    {
+        this(server, null, pushStrategies);
+    }
+
+    public HTTPSPDYServerConnector(Server server, SslContextFactory sslContextFactory, Map<Short, PushStrategy> pushStrategies)
+    {
+        this(server, new HttpConfiguration(), sslContextFactory, pushStrategies);
+    }
+
+    public HTTPSPDYServerConnector(Server server, short version, HttpConfiguration httpConfiguration, PushStrategy push)
+    {
+        super(server, new HTTPSPDYServerConnectionFactory(version, httpConfiguration, push));
+    }
+
+    public HTTPSPDYServerConnector(Server server, HttpConfiguration config, SslContextFactory sslContextFactory, Map<Short, PushStrategy> pushStrategies)
+    {
+        super(server, AbstractConnectionFactory.getFactories(sslContextFactory,
+                sslContextFactory == null
+                        ? new ConnectionFactory[]{new HttpConnectionFactory(config)}
+                        : new ConnectionFactory[]{new NPNServerConnectionFactory("spdy/3", "spdy/2", "http/1.1"),
+                        new HttpConnectionFactory(config),
+                        new HTTPSPDYServerConnectionFactory(SPDY.V3, config, getPushStrategy(SPDY.V3, pushStrategies)),
+                        new HTTPSPDYServerConnectionFactory(SPDY.V2, config, getPushStrategy(SPDY.V2, pushStrategies))}));
+        NPNServerConnectionFactory npnConnectionFactory = getConnectionFactory(NPNServerConnectionFactory.class);
+        if (npnConnectionFactory != null)
+            npnConnectionFactory.setDefaultProtocol("http/1.1");
+    }
+
+    private static PushStrategy getPushStrategy(short version, Map<Short, PushStrategy> pushStrategies)
+    {
+        PushStrategy pushStrategy = pushStrategies.get(version);
+        if (pushStrategy == null)
+            pushStrategy = new PushStrategy.None();
+        return pushStrategy;
+    }
+}
diff --git a/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/HttpChannelOverSPDY.java b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/HttpChannelOverSPDY.java
new file mode 100644
index 0000000..cf92578
--- /dev/null
+++ b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/HttpChannelOverSPDY.java
@@ -0,0 +1,246 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.spdy.server.http;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.http.HttpField;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.HttpChannel;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.HttpTransport;
+import org.eclipse.jetty.spdy.api.ByteBufferDataInfo;
+import org.eclipse.jetty.spdy.api.DataInfo;
+import org.eclipse.jetty.spdy.api.Stream;
+import org.eclipse.jetty.spdy.http.HTTPSPDYHeader;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Fields;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public class HttpChannelOverSPDY extends HttpChannel<DataInfo>
+{
+    private static final Logger LOG = Log.getLogger(HttpChannelOverSPDY.class);
+
+    private final Stream stream;
+    private boolean dispatched; // Guarded by synchronization on tasks
+    private boolean redispatch; // Guarded by synchronization on tasks
+    private boolean headersComplete;
+
+    public HttpChannelOverSPDY(Connector connector, HttpConfiguration configuration, EndPoint endPoint, HttpTransport transport, HttpInputOverSPDY input, Stream stream)
+    {
+        super(connector, configuration, endPoint, transport, input);
+        this.stream = stream;
+    }
+
+    @Override
+    public long getIdleTimeout()
+    {
+        return stream.getIdleTimeout();
+    }
+    
+    @Override
+    public boolean headerComplete()
+    {
+        headersComplete = true;
+        return super.headerComplete();
+    }
+
+    private void dispatch()
+    {
+        synchronized (this)
+        {
+            if (dispatched)
+                redispatch=true;
+            else
+            {
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Dispatch {}", this);
+                dispatched=true;
+                execute(this);
+            }
+        }
+    }
+
+    @Override
+    public void run()
+    {
+        boolean execute=true;
+        
+        while(execute)
+        {
+            try
+            {
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Executing {}",this);
+                super.run();
+            }
+            finally
+            {
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Completing {}", this);
+                synchronized (this)
+                {
+                    dispatched = redispatch;
+                    redispatch=false;
+                    execute=dispatched;
+                }
+            }
+        }
+    }
+    
+
+    public void requestStart(final Fields headers, final boolean endRequest)
+    {
+        if (!headers.isEmpty())
+            requestHeaders(headers, endRequest);
+    }
+
+    public void requestHeaders(Fields headers, boolean endRequest)
+    {
+        boolean proceed = performBeginRequest(headers);
+        if (!proceed)
+            return;
+
+        performHeaders(headers);
+
+        if (endRequest)
+        {
+            boolean dispatch = headerComplete();
+            if (messageComplete())
+                dispatch=true;
+            if (dispatch)
+                dispatch();
+        }
+    }
+
+    public void requestContent(final DataInfo dataInfo, boolean endRequest)
+    {
+        boolean dispatch=false;
+        if (!headersComplete && headerComplete())
+            dispatch=true;
+
+        if (LOG.isDebugEnabled())
+            LOG.debug("HTTP > {} bytes of content", dataInfo.length());
+
+        // We need to copy the dataInfo since we do not know when its bytes
+        // will be consumed. When the copy is consumed, we consume also the
+        // original, so the implementation can send a window update.
+        ByteBuffer copyByteBuffer = dataInfo.asByteBuffer(false);
+        ByteBufferDataInfo copyDataInfo = new ByteBufferDataInfo(copyByteBuffer, dataInfo.isClose())
+        {
+            @Override
+            public void consume(int delta)
+            {
+                super.consume(delta);
+                dataInfo.consume(delta);
+            }
+        };
+        if (LOG.isDebugEnabled())
+            LOG.debug("Queuing last={} content {}", endRequest, copyDataInfo);
+
+        if (content(copyDataInfo))
+            dispatch=true;
+
+        if (endRequest && messageComplete())
+            dispatch=true;
+        
+        if (dispatch)
+            dispatch();
+    }
+    
+    @Override
+    public boolean messageComplete()
+    {
+        super.messageComplete();
+        return false;
+    }
+
+    private boolean performBeginRequest(Fields headers)
+    {
+        short version = stream.getSession().getVersion();
+        Fields.Field methodHeader = headers.get(HTTPSPDYHeader.METHOD.name(version));
+        Fields.Field uriHeader = headers.get(HTTPSPDYHeader.URI.name(version));
+        Fields.Field versionHeader = headers.get(HTTPSPDYHeader.VERSION.name(version));
+
+        if (methodHeader == null || uriHeader == null || versionHeader == null)
+        {
+            badMessage(400, "Missing required request line elements");
+            return false;
+        }
+
+        HttpMethod httpMethod = HttpMethod.fromString(methodHeader.getValue());
+        HttpVersion httpVersion = HttpVersion.fromString(versionHeader.getValue());
+
+        // TODO should handle URI as byte buffer as some bad clients send WRONG encodings in query string
+        // that  we have to deal with
+        ByteBuffer uri = BufferUtil.toBuffer(uriHeader.getValue());
+
+        if (LOG.isDebugEnabled())
+            LOG.debug("HTTP > {} {} {}", httpMethod, uriHeader.getValue(), httpVersion);
+        startRequest(httpMethod, httpMethod.asString(), uri, httpVersion);
+
+        Fields.Field schemeHeader = headers.get(HTTPSPDYHeader.SCHEME.name(version));
+        if (schemeHeader != null)
+            getRequest().setScheme(schemeHeader.getValue());
+        return true;
+    }
+
+    private void performHeaders(Fields headers)
+    {
+        for (Fields.Field header : headers)
+        {
+            String name = header.getName();
+
+            // Skip special SPDY headers, unless it's the "host" header
+            HTTPSPDYHeader specialHeader = HTTPSPDYHeader.from(stream.getSession().getVersion(), name);
+            if (specialHeader != null)
+            {
+                if (specialHeader == HTTPSPDYHeader.HOST)
+                    name = "host";
+                else
+                    continue;
+            }
+
+            switch (name)
+            {
+                case "connection":
+                case "keep-alive":
+                case "proxy-connection":
+                case "transfer-encoding":
+                {
+                    // Spec says to ignore these headers
+                    continue;
+                }
+                default:
+                {
+                    // Spec says headers must be single valued
+                    String value = header.getValue();
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("HTTP > {}: {}", name, value);
+                    parsedHeader(new HttpField(name,value));
+                    break;
+                }
+            }
+        }
+    }
+}
diff --git a/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/HttpInputOverSPDY.java b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/HttpInputOverSPDY.java
new file mode 100644
index 0000000..3a4e178
--- /dev/null
+++ b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/HttpInputOverSPDY.java
@@ -0,0 +1,49 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.spdy.server.http;
+
+import org.eclipse.jetty.server.QueuedHttpInput;
+import org.eclipse.jetty.spdy.api.DataInfo;
+
+public class HttpInputOverSPDY extends QueuedHttpInput<DataInfo>
+{
+    @Override
+    protected int remaining(DataInfo item)
+    {
+        return item.available();
+    }
+
+    @Override
+    protected int get(DataInfo item, byte[] buffer, int offset, int length)
+    {
+        return item.readInto(buffer, offset, length);
+    }
+    
+    @Override
+    protected void consume(DataInfo item, int length)
+    {
+        item.consume(length);
+    }
+
+    @Override
+    protected void onContentConsumed(DataInfo dataInfo)
+    {
+        dataInfo.consume(dataInfo.length());
+    }
+}
diff --git a/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/HttpTransportOverSPDY.java b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/HttpTransportOverSPDY.java
new file mode 100644
index 0000000..2fd03f8
--- /dev/null
+++ b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/HttpTransportOverSPDY.java
@@ -0,0 +1,423 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.spdy.server.http;
+
+import java.nio.ByteBuffer;
+import java.util.Queue;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.eclipse.jetty.http.HttpField;
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpGenerator;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.EofException;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.HttpTransport;
+import org.eclipse.jetty.spdy.StreamException;
+import org.eclipse.jetty.spdy.api.ByteBufferDataInfo;
+import org.eclipse.jetty.spdy.api.HeadersInfo;
+import org.eclipse.jetty.spdy.api.PushInfo;
+import org.eclipse.jetty.spdy.api.ReplyInfo;
+import org.eclipse.jetty.spdy.api.SPDY;
+import org.eclipse.jetty.spdy.api.Session;
+import org.eclipse.jetty.spdy.api.Stream;
+import org.eclipse.jetty.spdy.api.StreamStatus;
+import org.eclipse.jetty.spdy.http.HTTPSPDYHeader;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.ConcurrentArrayQueue;
+import org.eclipse.jetty.util.Fields;
+import org.eclipse.jetty.util.Promise;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public class HttpTransportOverSPDY implements HttpTransport
+{
+    private static final Logger LOG = Log.getLogger(HttpTransportOverSPDY.class);
+
+    private final Connector connector;
+    private final HttpConfiguration configuration;
+    private final EndPoint endPoint;
+    private final PushStrategy pushStrategy;
+    private final Stream stream;
+    private final short version;
+    private final Fields requestHeaders;
+    private final AtomicBoolean committed = new AtomicBoolean();
+
+    public HttpTransportOverSPDY(Connector connector, HttpConfiguration configuration, EndPoint endPoint, PushStrategy pushStrategy, Stream stream, Fields requestHeaders)
+    {
+        this.connector = connector;
+        this.configuration = configuration;
+        this.endPoint = endPoint;
+        this.pushStrategy = pushStrategy == null ? new PushStrategy.None() : pushStrategy;
+        this.stream = stream;
+        this.requestHeaders = requestHeaders;
+        Session session = stream.getSession();
+        this.version = session.getVersion();
+    }
+
+    protected Stream getStream()
+    {
+        return stream;
+    }
+
+    protected Fields getRequestHeaders()
+    {
+        return requestHeaders;
+    }
+
+
+    @Override
+    public void send(ByteBuffer responseBodyContent, boolean lastContent, Callback callback)
+    {
+        // TODO can this be more efficient?
+        send(null, responseBodyContent, lastContent, callback);
+    }
+
+    @Override
+    public void send(HttpGenerator.ResponseInfo info, ByteBuffer content, boolean lastContent, final Callback callback)
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("Sending {} {} {} {} last={}", this, stream, info, BufferUtil.toDetailString(content), lastContent);
+
+        if (stream.isClosed() || stream.isReset())
+        {
+            EofException exception = new EofException("stream closed");
+            callback.failed(exception);
+            return;
+        }
+
+        // info==null content==null lastContent==false          should not happen
+        // info==null content==null lastContent==true           signals no more content - complete
+        // info==null content!=null lastContent==false          send data on committed response
+        // info==null content!=null lastContent==true           send last data on committed response - complete
+        // info!=null content==null lastContent==false          reply, commit
+        // info!=null content==null lastContent==true           reply, commit and complete
+        // info!=null content!=null lastContent==false          reply, commit with content
+        // info!=null content!=null lastContent==true           reply, commit with content and complete
+
+        boolean isHeadRequest = HttpMethod.HEAD.name().equalsIgnoreCase(requestHeaders.get(HTTPSPDYHeader.METHOD.name(version)).getValue());
+        boolean hasContent = BufferUtil.hasContent(content) && !isHeadRequest;
+        boolean close = !hasContent && lastContent;
+
+        if (info != null)
+        {
+            if (!committed.compareAndSet(false, true))
+            {
+                StreamException exception = new StreamException(stream.getId(), StreamStatus.PROTOCOL_ERROR,
+                        "Stream already committed!");
+                callback.failed(exception);
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Committed response twice.", exception);
+                return;
+            }
+            sendReply(info, !hasContent ? callback : new Callback.Adapter()
+            {
+                @Override
+                public void failed(Throwable x)
+                {
+                    callback.failed(x);
+                }
+            }, close);
+        }
+
+        // Do we have some content to send as well
+        if (hasContent)
+        {
+            // send the data and let it call the callback
+            if (LOG.isDebugEnabled())
+                LOG.debug("Send content: {} on stream: {} lastContent={}", BufferUtil.toDetailString(content), stream,
+                    lastContent);
+            stream.data(new ByteBufferDataInfo(endPoint.getIdleTimeout(), TimeUnit.MILLISECONDS, content, lastContent
+            ), callback);
+        }
+        // else do we need to close
+        else if (lastContent && info == null)
+        {
+            // send empty data to close and let the send call the callback
+            if (LOG.isDebugEnabled())
+                LOG.debug("No content and lastContent=true. Sending empty ByteBuffer to close stream: {}", stream);
+            stream.data(new ByteBufferDataInfo(endPoint.getIdleTimeout(), TimeUnit.MILLISECONDS,
+                    BufferUtil.EMPTY_BUFFER, lastContent), callback);
+        }
+        else if (!lastContent && !hasContent && info == null)
+            throw new IllegalStateException("not lastContent, no content and no responseInfo!");
+
+    }
+
+    private void sendReply(HttpGenerator.ResponseInfo info, Callback callback, boolean close)
+    {
+        Fields headers = new Fields();
+
+        HttpVersion httpVersion = HttpVersion.HTTP_1_1;
+        headers.put(HTTPSPDYHeader.VERSION.name(version), httpVersion.asString());
+
+        int status = info.getStatus();
+        StringBuilder httpStatus = new StringBuilder().append(status);
+        String reason = info.getReason();
+        if (reason == null)
+            reason = HttpStatus.getMessage(status);
+        if (reason != null)
+            httpStatus.append(" ").append(reason);
+        headers.put(HTTPSPDYHeader.STATUS.name(version), httpStatus.toString());
+        if (LOG.isDebugEnabled())
+            LOG.debug("HTTP < {} {}", httpVersion, httpStatus);
+
+        // TODO merge the two Field classes into one
+        HttpFields fields = info.getHttpFields();
+        if (fields != null)
+        {
+            for (int i = 0; i < fields.size(); ++i)
+            {
+                HttpField field = fields.getField(i);
+                String name = field.getName();
+                String value = field.getValue();
+                headers.add(name, value);
+                if (LOG.isDebugEnabled())
+                    LOG.debug("HTTP < {}: {}", name, value);
+            }
+        }
+
+        if (configuration.getSendServerVersion())
+            headers.add(HttpHeader.SERVER.asString(), HttpConfiguration.SERVER_VERSION);
+        if (configuration.getSendXPoweredBy())
+            headers.add(HttpHeader.X_POWERED_BY.asString(), HttpConfiguration.SERVER_VERSION);
+
+        ReplyInfo reply = new ReplyInfo(headers, close);
+        if (LOG.isDebugEnabled())
+            LOG.debug("Sending reply: {} on stream: {}", reply, stream);
+        reply(stream, reply, callback);
+    }
+
+    @Override
+    public void completed()
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("Completed {}", this);
+    }
+
+    private void reply(Stream stream, ReplyInfo replyInfo, Callback callback)
+    {
+        if (!stream.isUnidirectional())
+            stream.reply(replyInfo, callback);
+        else
+            stream.headers(new HeadersInfo(replyInfo.getHeaders(), replyInfo.isClose()), callback);
+
+        Fields responseHeaders = replyInfo.getHeaders();
+        if (responseHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().startsWith("200") && !stream.isClosed())
+        {
+            Set<String> pushResources = pushStrategy.apply(stream, requestHeaders, responseHeaders);
+            if (pushResources.size() > 0)
+            {
+                PushResourceCoordinator pushResourceCoordinator = new PushResourceCoordinator(pushResources);
+                pushResourceCoordinator.coordinate();
+            }
+        }
+    }
+
+    private static class PushHttpTransportOverSPDY extends HttpTransportOverSPDY
+    {
+        private final PushResourceCoordinator coordinator;
+        private final short version;
+
+        private PushHttpTransportOverSPDY(Connector connector, HttpConfiguration configuration, EndPoint endPoint,
+                                          PushStrategy pushStrategy, Stream stream, Fields requestHeaders,
+                                          PushResourceCoordinator coordinator, short version)
+        {
+            super(connector, configuration, endPoint, pushStrategy, stream, requestHeaders);
+            this.coordinator = coordinator;
+            this.version = version;
+        }
+
+        @Override
+        public void completed()
+        {
+            Stream stream = getStream();
+            if (LOG.isDebugEnabled())
+                LOG.debug("Resource pushed for {} on {}",
+                    getRequestHeaders().get(HTTPSPDYHeader.URI.name(version)), stream);
+            coordinator.complete();
+        }
+    }
+
+    private class PushResourceCoordinator
+    {
+        private final Queue<PushResource> queue = new ConcurrentArrayQueue<>();
+        private final Set<String> resources;
+        private AtomicBoolean active = new AtomicBoolean(false);
+
+        private PushResourceCoordinator(Set<String> resources)
+        {
+            this.resources = resources;
+        }
+
+        private void coordinate()
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("Pushing resources: {}", resources);
+            // Must send all push frames to the client at once before we
+            // return from this method and send the main resource data
+            for (String pushResource : resources)
+                pushResource(pushResource);
+        }
+
+        private void sendNextResourceData()
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("{} sendNextResourceData active: {}", hashCode(), active.get());
+            if (active.compareAndSet(false, true))
+            {
+                PushResource resource = queue.poll();
+                if (resource != null)
+                {
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("Opening new push channel for: {}", resource);
+                    HttpChannelOverSPDY pushChannel = newHttpChannelOverSPDY(resource.getPushStream(), resource.getPushRequestHeaders());
+                    pushChannel.requestStart(resource.getPushRequestHeaders(), true);
+                    return;
+                }
+
+                if (active.compareAndSet(true, false))
+                {
+                    if (queue.peek() != null)
+                        sendNextResourceData();
+                }
+                else
+                {
+                    throw new IllegalStateException("active must not be false here! Concurrency bug!");
+                }
+            }
+        }
+
+        private HttpChannelOverSPDY newHttpChannelOverSPDY(Stream pushStream, Fields pushRequestHeaders)
+        {
+            HttpTransport transport = new PushHttpTransportOverSPDY(connector, configuration, endPoint, pushStrategy,
+                    pushStream, pushRequestHeaders, this, version);
+            HttpInputOverSPDY input = new HttpInputOverSPDY();
+            return new HttpChannelOverSPDY(connector, configuration, endPoint, transport, input, pushStream);
+        }
+
+        private void pushResource(String pushResource)
+        {
+            Fields.Field scheme = requestHeaders.get(HTTPSPDYHeader.SCHEME.name(version));
+            Fields.Field host = requestHeaders.get(HTTPSPDYHeader.HOST.name(version));
+            Fields.Field uri = requestHeaders.get(HTTPSPDYHeader.URI.name(version));
+            final Fields pushHeaders = createPushHeaders(scheme, host, pushResource);
+            final Fields pushRequestHeaders = createRequestHeaders(scheme, host, uri, pushResource);
+
+            stream.push(new PushInfo(pushHeaders, false), new Promise<Stream>()
+            {
+                @Override
+                public void succeeded(Stream pushStream)
+                {
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("Headers pushed for {} on {}", pushHeaders.get(HTTPSPDYHeader.URI.name(version)), pushStream);
+                    queue.offer(new PushResource(pushStream, pushRequestHeaders));
+                    sendNextResourceData();
+                }
+
+                @Override
+                public void failed(Throwable x)
+                {
+                    LOG.debug("Creating push stream failed.", x);
+                    sendNextResourceData();
+                }
+            });
+        }
+
+        private void complete()
+        {
+            if (!active.compareAndSet(true, false))
+                throw new IllegalStateException();
+            sendNextResourceData();
+        }
+
+        private Fields createRequestHeaders(Fields.Field scheme, Fields.Field host, Fields.Field uri, String pushResourcePath)
+        {
+            final Fields newRequestHeaders = new Fields(requestHeaders, false);
+            newRequestHeaders.put(HTTPSPDYHeader.METHOD.name(version), "GET");
+            newRequestHeaders.put(HTTPSPDYHeader.VERSION.name(version), "HTTP/1.1");
+            newRequestHeaders.put(scheme);
+            newRequestHeaders.put(host);
+            newRequestHeaders.put(HTTPSPDYHeader.URI.name(version), pushResourcePath);
+            String referrer = scheme.getValue() + "://" + host.getValue() + uri.getValue();
+            newRequestHeaders.put("referer", referrer);
+            newRequestHeaders.put("x-spdy-push", "true");
+            return newRequestHeaders;
+        }
+
+        private Fields createPushHeaders(Fields.Field scheme, Fields.Field host, String pushResourcePath)
+        {
+            final Fields pushHeaders = new Fields();
+            if (version == SPDY.V2)
+                pushHeaders.put(HTTPSPDYHeader.URI.name(version), scheme.getValue() + "://" + host.getValue() + pushResourcePath);
+            else
+            {
+                pushHeaders.put(HTTPSPDYHeader.URI.name(version), pushResourcePath);
+                pushHeaders.put(scheme);
+                pushHeaders.put(host);
+            }
+            return pushHeaders;
+        }
+    }
+
+    private static class PushResource
+    {
+        private final Stream pushStream;
+        private final Fields pushRequestHeaders;
+
+        public PushResource(Stream pushStream, Fields pushRequestHeaders)
+        {
+            this.pushStream = pushStream;
+            this.pushRequestHeaders = pushRequestHeaders;
+        }
+
+        public Stream getPushStream()
+        {
+            return pushStream;
+        }
+
+        public Fields getPushRequestHeaders()
+        {
+            return pushRequestHeaders;
+        }
+
+        @Override
+        public String toString()
+        {
+            return "PushResource{" +
+                    "pushStream=" + pushStream +
+                    ", pushRequestHeaders=" + pushRequestHeaders +
+                    '}';
+        }
+    }
+
+    @Override
+    public void abort()
+    {
+        // TODO close the stream in a way to indicate an incomplete response?
+    }
+}
diff --git a/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/PushStrategy.java b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/PushStrategy.java
new file mode 100644
index 0000000..061f2a3
--- /dev/null
+++ b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/PushStrategy.java
@@ -0,0 +1,55 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.spdy.server.http;
+
+import java.util.Collections;
+import java.util.Set;
+
+import org.eclipse.jetty.spdy.api.Stream;
+import org.eclipse.jetty.util.Fields;
+
+/**
+ * <p>{@link PushStrategy} encapsulates the decisions about performing
+ * SPDY pushes of secondary resources associated with a primary resource.</p>
+ */
+public interface PushStrategy
+{
+    /**
+     * <p>Applies the SPDY push logic for the primary resource.</p>
+     *
+     * @param stream the primary resource stream
+     * @param requestHeaders the primary resource request headers
+     * @param responseHeaders the primary resource response headers
+     * @return a list of secondary resource URIs to push
+     */
+    public Set<String> apply(Stream stream, Fields requestHeaders, Fields responseHeaders);
+
+    /**
+     * An implementation that returns an empty list of secondary resources
+     */
+    public static class None implements PushStrategy
+    {
+        @Override
+        public Set<String> apply(Stream stream, Fields requestHeaders, Fields responseHeaders)
+        {
+            return Collections.emptySet();
+        }
+    }
+}
diff --git a/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/ReferrerPushStrategy.java b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/ReferrerPushStrategy.java
new file mode 100644
index 0000000..c9e24d6
--- /dev/null
+++ b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/ReferrerPushStrategy.java
@@ -0,0 +1,342 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.spdy.server.http;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.regex.Pattern;
+
+import org.eclipse.jetty.spdy.api.Stream;
+import org.eclipse.jetty.spdy.http.HTTPSPDYHeader;
+import org.eclipse.jetty.util.Fields;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * <p>A SPDY push strategy that auto-populates push metadata based on referrer URLs.<p>A typical request for a main
+ * resource such as {@code index.html} is immediately followed by a number of requests for associated resources.
+ * Associated resource requests will have a {@code Referer} HTTP header that points to {@code index.html}, which is used
+ * to link the associated resource to the main resource.<p>However, also following a hyperlink generates a HTTP request
+ * with a {@code Referer} HTTP header that points to {@code index.html}; therefore a proper value for {@link
+ * #setReferrerPushPeriod(int)} has to be set. If the referrerPushPeriod for a main resource has elapsed, no more
+ * associated resources will be added for that main resource.<p>This class distinguishes associated main resources by
+ * their URL path suffix and content type. CSS stylesheets, images and JavaScript files have recognizable URL path
+ * suffixes that are classified as associated resources. The suffix regexs can be configured by constructor argument</p>
+ * <p>When CSS stylesheets refer to images, the CSS image request will have the CSS stylesheet as referrer. This
+ * implementation will push also the CSS image.<p>The push metadata built by this implementation is limited by the
+ * number of pages of the application itself, and by the {@link #setMaxAssociatedResources(int)} max associated
+ * resources} parameter. This parameter limits the number of associated resources per each main resource, so that if a
+ * main resource has hundreds of associated resources, only up to the number specified by this parameter will be
+ * pushed.
+ */
+public class ReferrerPushStrategy implements PushStrategy
+{
+    private static final Logger LOG = Log.getLogger(ReferrerPushStrategy.class);
+    private final ConcurrentMap<String, MainResource> mainResources = new ConcurrentHashMap<>();
+    private final Set<Pattern> pushRegexps = new HashSet<>();
+    private final Set<String> pushContentTypes = new HashSet<>();
+    private final Set<Pattern> allowedPushOrigins = new HashSet<>();
+    private final Set<Pattern> userAgentBlacklist = new HashSet<>();
+    private volatile int maxAssociatedResources = 32;
+    private volatile int referrerPushPeriod = 5000;
+
+    public ReferrerPushStrategy()
+    {
+        List<String> defaultPushRegexps = Arrays.asList(".*\\.css", ".*\\.js", ".*\\.png", ".*\\.jpeg", ".*\\.jpg",
+                ".*\\.gif", ".*\\.ico");
+        addPushRegexps(defaultPushRegexps);
+
+        List<String> defaultPushContentTypes = Arrays.asList(
+                "text/css",
+                "text/javascript", "application/javascript", "application/x-javascript",
+                "image/png", "image/x-png",
+                "image/jpeg",
+                "image/gif",
+                "image/x-icon", "image/vnd.microsoft.icon");
+        this.pushContentTypes.addAll(defaultPushContentTypes);
+    }
+
+    public void setPushRegexps(List<String> pushRegexps)
+    {
+        pushRegexps.clear();
+        addPushRegexps(pushRegexps);
+    }
+
+    private void addPushRegexps(List<String> pushRegexps)
+    {
+        for (String pushRegexp : pushRegexps)
+            this.pushRegexps.add(Pattern.compile(pushRegexp));
+    }
+
+    public void setPushContentTypes(List<String> pushContentTypes)
+    {
+        pushContentTypes.clear();
+        pushContentTypes.addAll(pushContentTypes);
+    }
+
+    public void setAllowedPushOrigins(List<String> allowedPushOrigins)
+    {
+        allowedPushOrigins.clear();
+        for (String allowedPushOrigin : allowedPushOrigins)
+            this.allowedPushOrigins.add(Pattern.compile(allowedPushOrigin.replace(".", "\\.").replace("*", ".*")));
+    }
+
+    public void setUserAgentBlacklist(List<String> userAgentPatterns)
+    {
+        userAgentBlacklist.clear();
+        for (String userAgentPattern : userAgentPatterns)
+            userAgentBlacklist.add(Pattern.compile(userAgentPattern));
+    }
+
+    public void setMaxAssociatedResources(int maxAssociatedResources)
+    {
+        this.maxAssociatedResources = maxAssociatedResources;
+    }
+
+    public void setReferrerPushPeriod(int referrerPushPeriod)
+    {
+        this.referrerPushPeriod = referrerPushPeriod;
+    }
+
+    public Set<Pattern> getPushRegexps()
+    {
+        return pushRegexps;
+    }
+
+    public Set<String> getPushContentTypes()
+    {
+        return pushContentTypes;
+    }
+
+    public Set<Pattern> getAllowedPushOrigins()
+    {
+        return allowedPushOrigins;
+    }
+
+    public Set<Pattern> getUserAgentBlacklist()
+    {
+        return userAgentBlacklist;
+    }
+
+    public int getMaxAssociatedResources()
+    {
+        return maxAssociatedResources;
+    }
+
+    public int getReferrerPushPeriod()
+    {
+        return referrerPushPeriod;
+    }
+
+    @Override
+    public Set<String> apply(Stream stream, Fields requestHeaders, Fields responseHeaders)
+    {
+        Set<String> result = Collections.<String>emptySet();
+        short version = stream.getSession().getVersion();
+        if (!isIfModifiedSinceHeaderPresent(requestHeaders) && isValidMethod(requestHeaders.get(HTTPSPDYHeader.METHOD
+                .name(version)).getValue()) && !isUserAgentBlacklisted(requestHeaders))
+        {
+            String scheme = requestHeaders.get(HTTPSPDYHeader.SCHEME.name(version)).getValue();
+            String host = requestHeaders.get(HTTPSPDYHeader.HOST.name(version)).getValue();
+            String origin = scheme + "://" + host;
+            String url = requestHeaders.get(HTTPSPDYHeader.URI.name(version)).getValue();
+            String absoluteURL = origin + url;
+            if (LOG.isDebugEnabled())
+                LOG.debug("Applying push strategy for {}", absoluteURL);
+            if (isMainResource(url, responseHeaders))
+            {
+                MainResource mainResource = getOrCreateMainResource(absoluteURL);
+                result = mainResource.getResources();
+            }
+            else if (isPushResource(url, responseHeaders))
+            {
+                Fields.Field referrerHeader = requestHeaders.get("referer");
+                if (referrerHeader != null)
+                {
+                    String referrer = referrerHeader.getValue();
+                    MainResource mainResource = mainResources.get(referrer);
+                    if (mainResource == null)
+                        mainResource = getOrCreateMainResource(referrer);
+
+                    Set<String> pushResources = mainResource.getResources();
+                    if (!pushResources.contains(url))
+                        mainResource.addResource(url, origin, referrer);
+                    else
+                        result = getPushResources(absoluteURL);
+                }
+            }
+            if (LOG.isDebugEnabled())
+                LOG.debug("Pushing {} resources for {}: {}", result.size(), absoluteURL, result);
+        }
+        return result;
+    }
+
+    private Set<String> getPushResources(String absoluteURL)
+    {
+        Set<String> result = Collections.emptySet();
+        if (mainResources.get(absoluteURL) != null)
+            result = mainResources.get(absoluteURL).getResources();
+        return result;
+    }
+
+    private MainResource getOrCreateMainResource(String absoluteURL)
+    {
+        MainResource mainResource = mainResources.get(absoluteURL);
+        if (mainResource == null)
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("Creating new main resource for {}", absoluteURL);
+            MainResource value = new MainResource(absoluteURL);
+            mainResource = mainResources.putIfAbsent(absoluteURL, value);
+            if (mainResource == null)
+                mainResource = value;
+        }
+        return mainResource;
+    }
+
+    private boolean isIfModifiedSinceHeaderPresent(Fields headers)
+    {
+        return headers.get("if-modified-since") != null;
+    }
+
+    private boolean isValidMethod(String method)
+    {
+        return "GET".equalsIgnoreCase(method);
+    }
+
+    private boolean isMainResource(String url, Fields responseHeaders)
+    {
+        return !isPushResource(url, responseHeaders);
+    }
+
+    public boolean isUserAgentBlacklisted(Fields headers)
+    {
+        Fields.Field userAgentHeader = headers.get("user-agent");
+        if (userAgentHeader != null)
+            for (Pattern userAgentPattern : userAgentBlacklist)
+                if (userAgentPattern.matcher(userAgentHeader.getValue()).matches())
+                    return true;
+        return false;
+    }
+
+    private boolean isPushResource(String url, Fields responseHeaders)
+    {
+        for (Pattern pushRegexp : pushRegexps)
+        {
+            if (pushRegexp.matcher(url).matches())
+            {
+                Fields.Field header = responseHeaders.get("content-type");
+                if (header == null)
+                    return true;
+
+                String contentType = header.getValue().toLowerCase(Locale.ENGLISH);
+                for (String pushContentType : pushContentTypes)
+                    if (contentType.startsWith(pushContentType))
+                        return true;
+            }
+        }
+        return false;
+    }
+
+    private class MainResource
+    {
+        private final String name;
+        private final CopyOnWriteArraySet<String> resources = new CopyOnWriteArraySet<>();
+        private final AtomicLong firstResourceAdded = new AtomicLong(-1);
+
+        private MainResource(String name)
+        {
+            this.name = name;
+        }
+
+        public boolean addResource(String url, String origin, String referrer)
+        {
+            // We start the push period here and not when initializing the main resource, because a browser with a
+            // prefilled cache won't request the subresources. If the browser with warmed up cache now hits the main
+            // resource after a server restart, the push period shouldn't start until the first subresource is
+            // being requested.
+            firstResourceAdded.compareAndSet(-1, System.nanoTime());
+
+            long delay = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - firstResourceAdded.get());
+            if (!referrer.startsWith(origin) && !isPushOriginAllowed(origin))
+            {
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Skipped store of push metadata {} for {}: Origin: {} doesn't match or origin not allowed",
+                        url, name, origin);
+                return false;
+            }
+
+            // This check is not strictly concurrent-safe, but limiting
+            // the number of associated resources is achieved anyway
+            // although in rare cases few more resources will be stored
+            if (resources.size() >= maxAssociatedResources)
+            {
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Skipped store of push metadata {} for {}: max associated resources ({}) reached",
+                        url, name, maxAssociatedResources);
+                return false;
+            }
+            if (delay > referrerPushPeriod)
+            {
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Delay: {}ms longer than referrerPushPeriod ({}ms). Not adding resource: {} for: {}",
+                        delay, referrerPushPeriod, url, name);
+                return false;
+            }
+
+            if (LOG.isDebugEnabled())
+                LOG.debug("Adding: {} to: {} with delay: {}ms.", url, this, delay);
+            resources.add(url);
+            return true;
+        }
+
+        public Set<String> getResources()
+        {
+            return Collections.unmodifiableSet(resources);
+        }
+
+        public String toString()
+        {
+            return String.format("%s@%x{name=%s,resources=%s}",
+                    getClass().getSimpleName(),
+                    hashCode(),
+                    name,
+                    resources
+            );
+        }
+
+        private boolean isPushOriginAllowed(String origin)
+        {
+            for (Pattern allowedPushOrigin : allowedPushOrigins)
+                if (allowedPushOrigin.matcher(origin).matches())
+                    return true;
+            return false;
+        }
+    }
+}
diff --git a/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/proxy/HTTPProxyEngine.java b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/proxy/HTTPProxyEngine.java
new file mode 100644
index 0000000..61ff1be
--- /dev/null
+++ b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/proxy/HTTPProxyEngine.java
@@ -0,0 +1,276 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.spdy.server.proxy;
+
+import java.nio.ByteBuffer;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.client.util.DeferredContentProvider;
+import org.eclipse.jetty.http.HttpField;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.spdy.api.ByteBufferDataInfo;
+import org.eclipse.jetty.spdy.api.DataInfo;
+import org.eclipse.jetty.spdy.api.HeadersInfo;
+import org.eclipse.jetty.spdy.api.ReplyInfo;
+import org.eclipse.jetty.spdy.api.RstInfo;
+import org.eclipse.jetty.spdy.api.Stream;
+import org.eclipse.jetty.spdy.api.StreamFrameListener;
+import org.eclipse.jetty.spdy.api.StreamStatus;
+import org.eclipse.jetty.spdy.api.SynInfo;
+import org.eclipse.jetty.spdy.http.HTTPSPDYHeader;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.Fields;
+import org.eclipse.jetty.util.HttpCookieStore;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * <p>{@link HTTPProxyEngine} implements a SPDY to HTTP proxy, that is, converts SPDY events received by clients into
+ * HTTP events for the servers.</p>
+ */
+public class HTTPProxyEngine extends ProxyEngine
+{
+    private static final Logger LOG = Log.getLogger(HTTPProxyEngine.class);
+    private static final Callback LOGGING_CALLBACK = new LoggingCallback();
+
+    private final HttpClient httpClient;
+
+    public HTTPProxyEngine(HttpClient httpClient)
+    {
+        this.httpClient = httpClient;
+        configureHttpClient(httpClient);
+    }
+
+    private void configureHttpClient(HttpClient httpClient)
+    {
+        // Redirects must be proxied as is, not followed
+        httpClient.setFollowRedirects(false);
+        // Must not store cookies, otherwise cookies of different clients will mix
+        httpClient.setCookieStore(new HttpCookieStore.Empty());
+    }
+
+    public StreamFrameListener proxy(final Stream clientStream, SynInfo clientSynInfo, ProxyEngineSelector.ProxyServerInfo proxyServerInfo)
+    {
+        short version = clientStream.getSession().getVersion();
+        String method = clientSynInfo.getHeaders().get(HTTPSPDYHeader.METHOD.name(version)).getValue();
+        String path = clientSynInfo.getHeaders().get(HTTPSPDYHeader.URI.name(version)).getValue();
+
+        Fields headers = new Fields(clientSynInfo.getHeaders(), false);
+
+        removeHopHeaders(headers);
+        addRequestProxyHeaders(clientStream, headers);
+        customizeRequestHeaders(clientStream, headers);
+
+        String host = proxyServerInfo.getHost();
+        int port = proxyServerInfo.getAddress().getPort();
+
+        if (LOG.isDebugEnabled())
+            LOG.debug("Sending HTTP request to: {}", host + ":" + port);
+        final Request request = httpClient.newRequest(host, port)
+                .path(path)
+                .method(HttpMethod.fromString(method));
+        addNonSpdyHeadersToRequest(version, headers, request);
+
+        if (!clientSynInfo.isClose())
+        {
+            request.content(new DeferredContentProvider());
+        }
+
+        sendRequest(clientStream, request);
+
+        return new StreamFrameListener.Adapter()
+        {
+            @Override
+            public void onReply(Stream stream, ReplyInfo replyInfo)
+            {
+                // We proxy to HTTP so we do not receive replies
+                throw new UnsupportedOperationException("Not Yet Implemented");
+            }
+
+            @Override
+            public void onHeaders(Stream stream, HeadersInfo headersInfo)
+            {
+                throw new UnsupportedOperationException("Not Yet Implemented");
+            }
+
+            @Override
+            public void onData(Stream clientStream, final DataInfo clientDataInfo)
+            {
+                if (LOG.isDebugEnabled())
+                    LOG.debug("received clientDataInfo: {} for stream: {}", clientDataInfo, clientStream);
+
+                DeferredContentProvider contentProvider = (DeferredContentProvider)request.getContent();
+                contentProvider.offer(clientDataInfo.asByteBuffer(true));
+
+                if (clientDataInfo.isClose())
+                    contentProvider.close();
+            }
+        };
+    }
+
+    private void sendRequest(final Stream clientStream, Request request)
+    {
+        request.send(new Response.Listener.Adapter()
+        {
+            private volatile boolean committed;
+
+            @Override
+            public void onHeaders(final Response response)
+            {
+                if (LOG.isDebugEnabled())
+                    LOG.debug("onHeaders called with response: {}. Sending replyInfo to client.", response);
+                Fields responseHeaders = createResponseHeaders(clientStream, response);
+                removeHopHeaders(responseHeaders);
+                ReplyInfo replyInfo = new ReplyInfo(responseHeaders, false);
+                clientStream.reply(replyInfo, new Callback.Adapter()
+                {
+                    @Override
+                    public void failed(Throwable x)
+                    {
+                        LOG.debug("failed: ", x);
+                        response.abort(x);
+                    }
+
+                    @Override
+                    public void succeeded()
+                    {
+                        committed = true;
+                    }
+                });
+            }
+
+            @Override
+            public void onContent(final Response response, ByteBuffer content)
+            {
+                if (LOG.isDebugEnabled())
+                    LOG.debug("onContent called with response: {} and content: {}. Sending response content to client.",
+                        response, content);
+                final ByteBuffer contentCopy = httpClient.getByteBufferPool().acquire(content.remaining(), true);
+                BufferUtil.flipPutFlip(content, contentCopy);
+                ByteBufferDataInfo dataInfo = new ByteBufferDataInfo(contentCopy, false);
+                clientStream.data(dataInfo, new Callback()
+                {
+                    @Override
+                    public void failed(Throwable x)
+                    {
+                        LOG.debug("failed: ", x);
+                        releaseBuffer();
+                        response.abort(x);
+                    }
+
+                    @Override
+                    public void succeeded()
+                    {
+                        releaseBuffer();
+                    }
+
+                    private void releaseBuffer()
+                    {
+                        httpClient.getByteBufferPool().release(contentCopy);
+                    }
+                });
+            }
+
+            @Override
+            public void onSuccess(Response response)
+            {
+                if (LOG.isDebugEnabled())
+                    LOG.debug("onSuccess called. Closing client stream.");
+                clientStream.data(new ByteBufferDataInfo(BufferUtil.EMPTY_BUFFER, true), LOGGING_CALLBACK);
+            }
+
+            @Override
+            public void onFailure(Response response, Throwable failure)
+            {
+                LOG.debug("onFailure called: ", failure);
+                if (committed)
+                {
+                    LOG.debug("clientStream already committed. Resetting stream.");
+                    try
+                    {
+                        clientStream.getSession().rst(new RstInfo(clientStream.getId(), StreamStatus.INTERNAL_ERROR));
+                    }
+                    catch (InterruptedException | ExecutionException | TimeoutException e)
+                    {
+                        LOG.debug(e);
+                    }
+                }
+                else
+                {
+                    if (clientStream.isClosed())
+                        return;
+                    Fields responseHeaders = createResponseHeaders(clientStream, response);
+                    if (failure instanceof TimeoutException)
+                        responseHeaders.add(HTTPSPDYHeader.STATUS.name(clientStream.getSession().getVersion()),
+                                String.valueOf(HttpStatus.GATEWAY_TIMEOUT_504));
+                    else
+                        responseHeaders.add(HTTPSPDYHeader.STATUS.name(clientStream.getSession().getVersion()),
+                                String.valueOf(HttpStatus.BAD_GATEWAY_502));
+                    ReplyInfo replyInfo = new ReplyInfo(responseHeaders, true);
+                    clientStream.reply(replyInfo, LOGGING_CALLBACK);
+                }
+            }
+        });
+    }
+
+    private Fields createResponseHeaders(Stream clientStream, Response response)
+    {
+        Fields responseHeaders = new Fields();
+        for (HttpField header : response.getHeaders())
+            responseHeaders.add(header.getName(), header.getValue());
+            short version = clientStream.getSession().getVersion();
+        if (response.getStatus() > 0)
+            responseHeaders.add(HTTPSPDYHeader.STATUS.name(version),
+                    String.valueOf(response.getStatus()));
+        responseHeaders.add(HTTPSPDYHeader.VERSION.name(version), HttpVersion.HTTP_1_1.asString());
+        addResponseProxyHeaders(clientStream, responseHeaders);
+        return responseHeaders;
+    }
+
+    private void addNonSpdyHeadersToRequest(short version, Fields headers, Request request)
+    {
+        for (Fields.Field header : headers)
+            if (HTTPSPDYHeader.from(version, header.getName()) == null)
+                request.header(header.getName(), header.getValue());
+    }
+
+    static class LoggingCallback extends Callback.Adapter
+    {
+        @Override
+        public void failed(Throwable x)
+        {
+            LOG.debug(x);
+        }
+
+        @Override
+        public void succeeded()
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("succeeded");
+        }
+    }
+}
diff --git a/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/proxy/HTTPSPDYProxyServerConnector.java b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/proxy/HTTPSPDYProxyServerConnector.java
new file mode 100644
index 0000000..d5cc889
--- /dev/null
+++ b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/proxy/HTTPSPDYProxyServerConnector.java
@@ -0,0 +1,63 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.spdy.server.proxy;
+
+import java.util.Objects;
+
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.NegotiatingServerConnectionFactory;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.spdy.api.SPDY;
+import org.eclipse.jetty.spdy.server.NPNServerConnectionFactory;
+import org.eclipse.jetty.spdy.server.SPDYServerConnectionFactory;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+
+public class HTTPSPDYProxyServerConnector extends ServerConnector
+{
+    public HTTPSPDYProxyServerConnector(Server server, ProxyEngineSelector proxyEngineSelector)
+    {
+        this(server, new HttpConfiguration(), proxyEngineSelector);
+    }
+
+    public HTTPSPDYProxyServerConnector(Server server, HttpConfiguration config, ProxyEngineSelector proxyEngineSelector)
+    {
+        super(server, (SslContextFactory)null, new ProxyHTTPConnectionFactory(config, SPDY.V2, proxyEngineSelector));
+    }
+
+    public HTTPSPDYProxyServerConnector(Server server, SslContextFactory sslContextFactory, ProxyEngineSelector proxyEngineSelector)
+    {
+        this(server, sslContextFactory, new HttpConfiguration(), proxyEngineSelector);
+    }
+
+    public HTTPSPDYProxyServerConnector(Server server, SslContextFactory sslContextFactory, HttpConfiguration config, ProxyEngineSelector proxyEngineSelector)
+    {
+        this(server, sslContextFactory, config, proxyEngineSelector, new NPNServerConnectionFactory("spdy/3", "spdy/2", "http/1.1"));
+    }
+
+    public HTTPSPDYProxyServerConnector(Server server, SslContextFactory sslContextFactory, HttpConfiguration config, ProxyEngineSelector proxyEngineSelector, NegotiatingServerConnectionFactory negotiatingFactory)
+    {
+        super(server, Objects.requireNonNull(sslContextFactory), negotiatingFactory,
+                new SPDYServerConnectionFactory(SPDY.V3, proxyEngineSelector),
+                new SPDYServerConnectionFactory(SPDY.V2, proxyEngineSelector),
+                new ProxyHTTPConnectionFactory(config, SPDY.V2, proxyEngineSelector));
+        negotiatingFactory.setDefaultProtocol("http/1.1");
+    }
+}
diff --git a/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/proxy/ProxyEngine.java b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/proxy/ProxyEngine.java
new file mode 100644
index 0000000..eadaf6b
--- /dev/null
+++ b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/proxy/ProxyEngine.java
@@ -0,0 +1,129 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.spdy.server.proxy;
+
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.UnknownHostException;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.eclipse.jetty.spdy.api.Stream;
+import org.eclipse.jetty.spdy.api.StreamFrameListener;
+import org.eclipse.jetty.spdy.api.SynInfo;
+import org.eclipse.jetty.spdy.http.HTTPSPDYHeader;
+import org.eclipse.jetty.util.Fields;
+
+/**
+ * <p>{@link ProxyEngine} is the class for SPDY proxy functionalities that receives a SPDY request and converts it to
+ * any protocol to its server side.</p>
+ * <p>This class listens for SPDY events sent by clients; subclasses are responsible for translating
+ * these SPDY client events into appropriate events to forward to the server, in the appropriate
+ * protocol that is understood by the server.</p>
+ */
+public abstract class ProxyEngine
+{
+    private static final Set<String> HOP_HEADERS = new HashSet<>();
+    static
+    {
+        HOP_HEADERS.add("proxy-connection");
+        HOP_HEADERS.add("connection");
+        HOP_HEADERS.add("keep-alive");
+        HOP_HEADERS.add("transfer-encoding");
+        HOP_HEADERS.add("te");
+        HOP_HEADERS.add("trailer");
+        HOP_HEADERS.add("proxy-authorization");
+        HOP_HEADERS.add("proxy-authenticate");
+        HOP_HEADERS.add("upgrade");
+    }
+
+    private final String name;
+
+    protected ProxyEngine()
+    {
+        this(name());
+    }
+
+    private static String name()
+    {
+        try
+        {
+            return InetAddress.getLocalHost().getHostName();
+        }
+        catch (UnknownHostException x)
+        {
+            return "localhost";
+        }
+    }
+
+    public abstract StreamFrameListener proxy(Stream clientStream, SynInfo clientSynInfo, ProxyEngineSelector.ProxyServerInfo proxyServerInfo);
+
+    protected ProxyEngine(String name)
+    {
+        this.name = name;
+    }
+
+    public String getName()
+    {
+        return name;
+    }
+
+    protected void removeHopHeaders(Fields headers)
+    {
+        // Header names are case-insensitive (RFC2616) and oej.util.Fields.add converts the names to lowercase. So we
+        // need to compare with the lowercase values only
+        for (String hopHeader : HOP_HEADERS)
+            headers.remove(hopHeader);
+    }
+
+    protected void addRequestProxyHeaders(Stream stream, Fields headers)
+    {
+        addViaHeader(headers);
+        Fields.Field schemeField = headers.get(HTTPSPDYHeader.SCHEME.name(stream.getSession().getVersion()));
+        if(schemeField != null)
+            headers.add("X-Forwarded-Proto", schemeField.getValue());
+        InetSocketAddress address = stream.getSession().getRemoteAddress();
+        if (address != null)
+        {
+            headers.add("X-Forwarded-Host", address.getHostName());
+            headers.add("X-Forwarded-For", address.toString());
+        }
+        headers.add("X-Forwarded-Server", name());
+    }
+
+    protected void addResponseProxyHeaders(Stream stream, Fields headers)
+    {
+        addViaHeader(headers);
+    }
+
+    private void addViaHeader(Fields headers)
+    {
+        headers.add("Via", "http/1.1 " + getName());
+    }
+
+    protected void customizeRequestHeaders(Stream stream, Fields headers)
+    {
+    }
+
+    protected void customizeResponseHeaders(Stream stream, Fields headers)
+    {
+    }
+
+}
diff --git a/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/proxy/ProxyEngineSelector.java b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/proxy/ProxyEngineSelector.java
new file mode 100644
index 0000000..7b7be83
--- /dev/null
+++ b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/proxy/ProxyEngineSelector.java
@@ -0,0 +1,203 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.spdy.server.proxy;
+
+import java.net.InetSocketAddress;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.eclipse.jetty.spdy.api.GoAwayResultInfo;
+import org.eclipse.jetty.spdy.api.PingResultInfo;
+import org.eclipse.jetty.spdy.api.RstInfo;
+import org.eclipse.jetty.spdy.api.Session;
+import org.eclipse.jetty.spdy.api.Stream;
+import org.eclipse.jetty.spdy.api.StreamFrameListener;
+import org.eclipse.jetty.spdy.api.StreamStatus;
+import org.eclipse.jetty.spdy.api.SynInfo;
+import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener;
+import org.eclipse.jetty.spdy.http.HTTPSPDYHeader;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.Fields;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * <p>{@link ProxyEngineSelector} is the main entry point for push stream events of a jetty SPDY proxy. It receives the
+ * push stream frames from the clients, checks if there's an appropriate {@link ProxyServerInfo} for the given target
+ * host and forwards the push to a {@link ProxyEngine} for the protocol defined in {@link ProxyServerInfo}.</p>
+ *
+ * <p>If no {@link ProxyServerInfo} can be found for the given target host or no {@link ProxyEngine} can be found for
+ * the given protocol, it resets the client stream.</p>
+ *
+ * <p>This class also provides configuration for the proxy rules.</p>
+ */
+public class ProxyEngineSelector extends ServerSessionFrameListener.Adapter
+{
+    protected final Logger LOG = Log.getLogger(getClass());
+    private final Map<String, ProxyServerInfo> proxyInfos = new ConcurrentHashMap<>();
+    private final Map<String, ProxyEngine> proxyEngines = new ConcurrentHashMap<>();
+
+    @Override
+    public final StreamFrameListener onSyn(final Stream clientStream, SynInfo clientSynInfo)
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("C -> P {} on {}", clientSynInfo, clientStream);
+
+        final Session clientSession = clientStream.getSession();
+        short clientVersion = clientSession.getVersion();
+        Fields headers = new Fields(clientSynInfo.getHeaders(), false);
+
+        Fields.Field hostHeader = headers.get(HTTPSPDYHeader.HOST.name(clientVersion));
+        if (hostHeader == null)
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("No host header found: " + headers);
+            rst(clientStream);
+            return null;
+        }
+
+        String host = hostHeader.getValue();
+        int colon = host.indexOf(':');
+        if (colon >= 0)
+            host = host.substring(0, colon);
+
+        ProxyServerInfo proxyServerInfo = getProxyServerInfo(host);
+        if (proxyServerInfo == null)
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("No matching ProxyServerInfo found for: " + host);
+            rst(clientStream);
+            return null;
+        }
+
+        String protocol = proxyServerInfo.getProtocol();
+        ProxyEngine proxyEngine = proxyEngines.get(protocol);
+        if (proxyEngine == null)
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("No matching ProxyEngine found for: " + protocol);
+            rst(clientStream);
+            return null;
+        }
+        if (LOG.isDebugEnabled())
+            LOG.debug("Forwarding request: {} -> {}", clientSynInfo, proxyServerInfo);
+        return proxyEngine.proxy(clientStream, clientSynInfo, proxyServerInfo);
+    }
+
+    @Override
+    public void onPing(Session clientSession, PingResultInfo pingResultInfo)
+    {
+        // We do not know to which upstream server
+        // to send the PING so we just ignore it
+    }
+
+    @Override
+    public void onGoAway(Session session, GoAwayResultInfo goAwayResultInfo)
+    {
+        // TODO:
+    }
+
+    public Map<String, ProxyEngine> getProxyEngines()
+    {
+        return new HashMap<>(proxyEngines);
+    }
+
+    public void setProxyEngines(Map<String, ProxyEngine> proxyEngines)
+    {
+        this.proxyEngines.clear();
+        this.proxyEngines.putAll(proxyEngines);
+    }
+
+    public ProxyEngine getProxyEngine(String protocol)
+    {
+        return proxyEngines.get(protocol);
+    }
+
+    public void putProxyEngine(String protocol, ProxyEngine proxyEngine)
+    {
+        proxyEngines.put(protocol, proxyEngine);
+    }
+
+    public Map<String, ProxyServerInfo> getProxyServerInfos()
+    {
+        return new HashMap<>(proxyInfos);
+    }
+
+    protected ProxyServerInfo getProxyServerInfo(String host)
+    {
+        return proxyInfos.get(host);
+    }
+
+    public void setProxyServerInfos(Map<String, ProxyServerInfo> proxyServerInfos)
+    {
+        this.proxyInfos.clear();
+        this.proxyInfos.putAll(proxyServerInfos);
+    }
+
+    public void putProxyServerInfo(String host, ProxyServerInfo proxyServerInfo)
+    {
+        proxyInfos.put(host, proxyServerInfo);
+    }
+
+    private void rst(Stream stream)
+    {
+        RstInfo rstInfo = new RstInfo(stream.getId(), StreamStatus.REFUSED_STREAM);
+        stream.getSession().rst(rstInfo, Callback.Adapter.INSTANCE);
+    }
+
+    public static class ProxyServerInfo
+    {
+        private final String protocol;
+        private final String host;
+        private final InetSocketAddress address;
+
+        public ProxyServerInfo(String protocol, String host, int port)
+        {
+            this.protocol = protocol;
+            this.host = host;
+            this.address = new InetSocketAddress(host, port);
+        }
+
+        public String getProtocol()
+        {
+            return protocol;
+        }
+
+        public String getHost()
+        {
+            return host;
+        }
+
+        public InetSocketAddress getAddress()
+        {
+            return address;
+        }
+
+        @Override
+        public String toString()
+        {
+            return "ProxyServerInfo{" +
+                    "protocol='" + protocol + '\'' +
+                    ", host='" + host + '\'' +
+                    ", address=" + address +
+                    '}';
+        }
+    }
+}
diff --git a/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/proxy/ProxyHTTPConnectionFactory.java b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/proxy/ProxyHTTPConnectionFactory.java
new file mode 100644
index 0000000..b9a017a
--- /dev/null
+++ b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/proxy/ProxyHTTPConnectionFactory.java
@@ -0,0 +1,57 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.spdy.server.proxy;
+
+
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.server.AbstractConnectionFactory;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.HttpConfiguration;
+
+public class ProxyHTTPConnectionFactory extends AbstractConnectionFactory implements HttpConfiguration.ConnectionFactory
+{
+    private final short version;
+    private final ProxyEngineSelector proxyEngineSelector;
+    private final HttpConfiguration httpConfiguration;
+
+    public ProxyHTTPConnectionFactory(HttpConfiguration httpConfiguration,short version, ProxyEngineSelector proxyEngineSelector)
+    {
+        // replaces http/1.1
+        super(HttpVersion.HTTP_1_1.asString());
+        this.version = version;
+        this.proxyEngineSelector = proxyEngineSelector;
+        this.httpConfiguration=httpConfiguration;
+    }
+
+    @Override
+    public Connection newConnection(Connector connector, EndPoint endPoint)
+    {
+        return configure(new ProxyHTTPSPDYConnection(connector, httpConfiguration, endPoint, version, proxyEngineSelector),connector,endPoint);
+    }
+
+    @Override
+    public HttpConfiguration getHttpConfiguration()
+    {
+        return httpConfiguration;
+    }
+
+}
diff --git a/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/proxy/ProxyHTTPSPDYConnection.java b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/proxy/ProxyHTTPSPDYConnection.java
new file mode 100644
index 0000000..02a0c09
--- /dev/null
+++ b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/proxy/ProxyHTTPSPDYConnection.java
@@ -0,0 +1,385 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.spdy.server.proxy;
+
+import java.nio.ByteBuffer;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.eclipse.jetty.http.HttpField;
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpGenerator;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpHeaderValue;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpParser;
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.HttpConnection;
+import org.eclipse.jetty.server.SslConnectionFactory;
+import org.eclipse.jetty.spdy.ISession;
+import org.eclipse.jetty.spdy.IStream;
+import org.eclipse.jetty.spdy.StandardSession;
+import org.eclipse.jetty.spdy.StandardStream;
+import org.eclipse.jetty.spdy.api.ByteBufferDataInfo;
+import org.eclipse.jetty.spdy.api.DataInfo;
+import org.eclipse.jetty.spdy.api.GoAwayInfo;
+import org.eclipse.jetty.spdy.api.GoAwayResultInfo;
+import org.eclipse.jetty.spdy.api.HeadersInfo;
+import org.eclipse.jetty.spdy.api.PushInfo;
+import org.eclipse.jetty.spdy.api.ReplyInfo;
+import org.eclipse.jetty.spdy.api.RstInfo;
+import org.eclipse.jetty.spdy.api.SessionStatus;
+import org.eclipse.jetty.spdy.api.Stream;
+import org.eclipse.jetty.spdy.api.StreamFrameListener;
+import org.eclipse.jetty.spdy.api.SynInfo;
+import org.eclipse.jetty.spdy.http.HTTPSPDYHeader;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.Fields;
+import org.eclipse.jetty.util.Promise;
+
+public class ProxyHTTPSPDYConnection extends HttpConnection implements HttpParser.RequestHandler<ByteBuffer>
+{
+    private final short version;
+    private final Fields headers = new Fields();
+    private final ProxyEngineSelector proxyEngineSelector;
+    private final ISession session;
+    private HTTPStream stream;
+    private ByteBuffer content;
+
+    public ProxyHTTPSPDYConnection(Connector connector, HttpConfiguration config, EndPoint endPoint, short version, ProxyEngineSelector proxyEngineSelector)
+    {
+        super(config, connector, endPoint);
+        this.version = version;
+        this.proxyEngineSelector = proxyEngineSelector;
+        this.session = new HTTPSession(version, connector);
+    }
+
+    @Override
+    protected HttpParser.RequestHandler<ByteBuffer> newRequestHandler()
+    {
+        return this;
+    }
+
+    @Override
+    public boolean startRequest(HttpMethod method, String methodString, ByteBuffer uri, HttpVersion httpVersion)
+    {
+        Connector connector = getConnector();
+        String scheme = connector.getConnectionFactory(SslConnectionFactory.class) != null ? "https" : "http";
+        headers.put(HTTPSPDYHeader.SCHEME.name(version), scheme);
+        headers.put(HTTPSPDYHeader.METHOD.name(version), methodString);
+        headers.put(HTTPSPDYHeader.URI.name(version), BufferUtil.toUTF8String(uri)); // TODO handle bad encodings
+        headers.put(HTTPSPDYHeader.VERSION.name(version), httpVersion.asString());
+        return false;
+    }
+
+    @Override
+    public boolean parsedHeader(HttpField field)
+    {
+        if (field.getHeader() == HttpHeader.HOST)
+            headers.put(HTTPSPDYHeader.HOST.name(version), field.getValue());
+        else
+            headers.put(field.getName(), field.getValue());
+        return false;
+    }
+
+    @Override
+    public boolean parsedHostHeader(String host, int port)
+    {
+        return false;
+    }
+
+    @Override
+    public boolean headerComplete()
+    {
+        return false;
+    }
+
+    @Override
+    public boolean content(ByteBuffer item)
+    {
+        if (content == null)
+        {
+            stream = syn(false);
+            content = item;
+        }
+        else
+        {
+            stream.getStreamFrameListener().onData(stream, toDataInfo(item, false));
+        }
+        return false;
+    }
+
+    @Override
+    public boolean messageComplete()
+    {
+        if (stream == null)
+        {
+            assert content == null;
+            if (headers.isEmpty())
+                proxyEngineSelector.onGoAway(session, new GoAwayResultInfo(0, SessionStatus.OK));
+            else
+                syn(true);
+        }
+        else
+        {
+            stream.getStreamFrameListener().onData(stream, toDataInfo(content, true));
+        }
+        return false;
+    }
+
+    @Override
+    public void completed()
+    {
+        headers.clear();
+        stream = null;
+        content = null;
+        super.completed();
+    }
+
+    @Override
+    public int getHeaderCacheSize()
+    {
+        // TODO get from configuration
+        return 256;
+    }
+
+    @Override
+    public void earlyEOF()
+    {
+        // TODO
+    }
+
+    @Override
+    public void badMessage(int status, String reason)
+    {
+        // TODO
+    }
+
+    private HTTPStream syn(boolean close)
+    {
+        HTTPStream stream = new HTTPStream(1, (byte)0, session, null);
+        StreamFrameListener streamFrameListener = proxyEngineSelector.onSyn(stream, new SynInfo(headers, close));
+        stream.setStreamFrameListener(streamFrameListener);
+        return stream;
+    }
+
+    private DataInfo toDataInfo(ByteBuffer buffer, boolean close)
+    {
+        return new ByteBufferDataInfo(buffer, close);
+    }
+
+    private class HTTPSession extends StandardSession
+    {
+        private HTTPSession(short version, Connector connector)
+        {
+            super(version, connector.getByteBufferPool(), connector.getScheduler(), null,
+                    getEndPoint(), null, 1, proxyEngineSelector, null, null);
+        }
+
+        @Override
+        public void rst(RstInfo rstInfo, Callback handler)
+        {
+            HttpGenerator.ResponseInfo info = new HttpGenerator.ResponseInfo(HttpVersion.fromString(headers.get
+                    ("version").getValue()), null, 0, 502, "SPDY reset received from upstream server", false);
+            send(info, null, true, Callback.Adapter.INSTANCE);
+        }
+
+        @Override
+        public void goAway(GoAwayInfo goAwayInfo, Callback handler)
+        {
+            ProxyHTTPSPDYConnection.this.close();
+            handler.succeeded();
+        }
+    }
+
+    /**
+     * <p>This stream will convert the SPDY invocations performed by the proxy into HTTP to be sent to the client.</p>
+     */
+    private class HTTPStream extends StandardStream
+    {
+        private final Pattern statusRegexp = Pattern.compile("(\\d{3})\\s+(.*)");
+
+        private HTTPStream(int id, byte priority, ISession session, IStream associatedStream)
+        {
+            super(id, priority, session, associatedStream, getHttpChannel().getScheduler(), null);
+        }
+
+        @Override
+        public void push(PushInfo pushInfo, Promise<Stream> handler)
+        {
+            // HTTP does not support pushed streams
+            handler.succeeded(new HTTPPushStream(2, getPriority(), getSession(), this));
+        }
+
+        @Override
+        public void headers(HeadersInfo headersInfo, Callback handler)
+        {
+            // TODO
+            throw new UnsupportedOperationException("Not Yet Implemented");
+        }
+
+        @Override
+        public void reply(ReplyInfo replyInfo, final Callback handler)
+        {
+            Fields headers = new Fields(replyInfo.getHeaders(), false);
+
+                addPersistenceHeader(headers);
+
+            headers.remove(HTTPSPDYHeader.SCHEME.name(version));
+
+            String status = headers.remove(HTTPSPDYHeader.STATUS.name(version)).getValue();
+            Matcher matcher = statusRegexp.matcher(status);
+            matcher.matches();
+            int code = Integer.parseInt(matcher.group(1));
+            String reason = matcher.group(2).trim();
+
+            HttpVersion httpVersion = HttpVersion.fromString(headers.remove(HTTPSPDYHeader.VERSION.name(version)).getValue());
+
+            // Convert the Host header from a SPDY special header to a normal header
+            Fields.Field host = headers.remove(HTTPSPDYHeader.HOST.name(version));
+            if (host != null)
+                headers.put("host", host.getValue());
+
+            HttpFields fields = new HttpFields();
+            for (Fields.Field header : headers)
+            {
+                String name = camelize(header.getName());
+                fields.put(name, header.getValue());
+            }
+
+            // TODO: handle better the HEAD last parameter
+            long contentLength = fields.getLongField(HttpHeader.CONTENT_LENGTH.asString());
+            HttpGenerator.ResponseInfo info = new HttpGenerator.ResponseInfo(httpVersion, fields, contentLength, code,
+                    reason, false);
+
+            send(info, null, replyInfo.isClose(), new Adapter()
+            {
+                @Override
+                public void failed(Throwable x)
+                {
+                    handler.failed(x);
+                }
+            });
+
+            if (replyInfo.isClose())
+                completed();
+
+            handler.succeeded();
+        }
+
+        private String camelize(String name)
+        {
+            char[] chars = name.toCharArray();
+            chars[0] = Character.toUpperCase(chars[0]);
+
+            for (int i = 0; i < chars.length; ++i)
+            {
+                char c = chars[i];
+                int j = i + 1;
+                if (c == '-' && j < chars.length)
+                    chars[j] = Character.toUpperCase(chars[j]);
+            }
+            return new String(chars);
+        }
+
+        @Override
+        public void data(DataInfo dataInfo, final Callback handler)
+        {
+            // Data buffer must be copied, as the ByteBuffer is pooled
+            ByteBuffer byteBuffer = dataInfo.asByteBuffer(false);
+
+            send(null, byteBuffer, dataInfo.isClose(), new Adapter()
+            {
+                @Override
+                public void failed(Throwable x)
+                {
+                    handler.failed(x);
+                }
+            });
+
+            if (dataInfo.isClose())
+                completed();
+
+            handler.succeeded();
+        }
+    }
+
+    private void addPersistenceHeader(Fields headersToAddTo)
+    {
+        HttpVersion httpVersion = HttpVersion.fromString(headers.get("version").getValue());
+        boolean persistent = false;
+        switch (httpVersion)
+        {
+            case HTTP_1_0:
+            {
+                Fields.Field keepAliveHeader = headers.get(HttpHeader.KEEP_ALIVE.asString());
+                if(keepAliveHeader!=null)
+                    persistent = HttpHeaderValue.KEEP_ALIVE.asString().equals(keepAliveHeader.getValue());
+                if (!persistent)
+                    persistent = HttpMethod.CONNECT.is(headers.get("method").getValue());
+                if (persistent)
+                    headersToAddTo.add(HttpHeader.CONNECTION.asString(), HttpHeaderValue.KEEP_ALIVE.asString());
+                break;
+            }
+            case HTTP_1_1:
+            {
+                Fields.Field connectionHeader = headers.get(HttpHeader.CONNECTION.asString());
+                if(connectionHeader != null)
+                    persistent = !HttpHeaderValue.CLOSE.asString().equals(connectionHeader.getValue());
+                else
+                    persistent = true;
+                if (!persistent)
+                    persistent = HttpMethod.CONNECT.is(headers.get("method").getValue());
+                if (!persistent)
+                    headersToAddTo.add(HttpHeader.CONNECTION.asString(), HttpHeaderValue.CLOSE.asString());
+                break;
+            }
+            default:
+            {
+                throw new IllegalStateException();
+            }
+        }
+    }
+
+    private class HTTPPushStream extends StandardStream
+    {
+        private HTTPPushStream(int id, byte priority, ISession session, IStream associatedStream)
+        {
+            super(id, priority, session, associatedStream, getHttpChannel().getScheduler(), null);
+        }
+
+        @Override
+        public void headers(HeadersInfo headersInfo, Callback handler)
+        {
+            // Ignore pushed headers
+            handler.succeeded();
+        }
+
+        @Override
+        public void data(DataInfo dataInfo, Callback handler)
+        {
+            // Ignore pushed data
+            handler.succeeded();
+        }
+    }
+}
diff --git a/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/proxy/SPDYProxyEngine.java b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/proxy/SPDYProxyEngine.java
new file mode 100644
index 0000000..b1897a6
--- /dev/null
+++ b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/proxy/SPDYProxyEngine.java
@@ -0,0 +1,631 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.spdy.server.proxy;
+
+import java.net.InetSocketAddress;
+import java.util.LinkedList;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.spdy.api.ByteBufferDataInfo;
+import org.eclipse.jetty.spdy.api.DataInfo;
+import org.eclipse.jetty.spdy.api.GoAwayInfo;
+import org.eclipse.jetty.spdy.api.GoAwayResultInfo;
+import org.eclipse.jetty.spdy.api.HeadersInfo;
+import org.eclipse.jetty.spdy.api.Info;
+import org.eclipse.jetty.spdy.api.PushInfo;
+import org.eclipse.jetty.spdy.api.ReplyInfo;
+import org.eclipse.jetty.spdy.api.RstInfo;
+import org.eclipse.jetty.spdy.api.SPDY;
+import org.eclipse.jetty.spdy.api.Session;
+import org.eclipse.jetty.spdy.api.SessionFrameListener;
+import org.eclipse.jetty.spdy.api.Stream;
+import org.eclipse.jetty.spdy.api.StreamFrameListener;
+import org.eclipse.jetty.spdy.api.StreamStatus;
+import org.eclipse.jetty.spdy.api.SynInfo;
+import org.eclipse.jetty.spdy.client.SPDYClient;
+import org.eclipse.jetty.spdy.http.HTTPSPDYHeader;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.Fields;
+import org.eclipse.jetty.util.Promise;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * <p>{@link SPDYProxyEngine} implements a SPDY to SPDY proxy, that is, converts SPDY events received by clients into
+ * SPDY events for the servers.</p>
+ */
+public class SPDYProxyEngine extends ProxyEngine implements StreamFrameListener
+{
+    private static final Logger LOG = Log.getLogger(SPDYProxyEngine.class);
+
+    private static final String STREAM_PROMISE_ATTRIBUTE = "org.eclipse.jetty.spdy.server.http.proxy.streamPromise";
+    private static final String CLIENT_STREAM_ATTRIBUTE = "org.eclipse.jetty.spdy.server.http.proxy.clientStream";
+
+    private final ConcurrentMap<String, Session> serverSessions = new ConcurrentHashMap<>();
+    private final SessionFrameListener sessionListener = new ProxySessionFrameListener();
+    private final SPDYClient.Factory factory;
+    private volatile long connectTimeout = 15000;
+    private volatile long timeout = 60000;
+
+    public SPDYProxyEngine(SPDYClient.Factory factory)
+    {
+        this.factory = factory;
+    }
+
+    public long getConnectTimeout()
+    {
+        return connectTimeout;
+    }
+
+    public void setConnectTimeout(long connectTimeout)
+    {
+        this.connectTimeout = connectTimeout;
+    }
+
+    public long getTimeout()
+    {
+        return timeout;
+    }
+
+    public void setTimeout(long timeout)
+    {
+        this.timeout = timeout;
+    }
+
+    public StreamFrameListener proxy(final Stream clientStream, SynInfo clientSynInfo, ProxyEngineSelector.ProxyServerInfo proxyServerInfo)
+    {
+        Fields headers = new Fields(clientSynInfo.getHeaders(), false);
+
+        short serverVersion = getVersion(proxyServerInfo.getProtocol());
+        InetSocketAddress address = proxyServerInfo.getAddress();
+        Session serverSession = produceSession(proxyServerInfo.getHost(), serverVersion, address);
+        if (serverSession == null)
+        {
+            rst(clientStream);
+            return null;
+        }
+
+        final Session clientSession = clientStream.getSession();
+
+        addRequestProxyHeaders(clientStream, headers);
+        customizeRequestHeaders(clientStream, headers);
+        convert(clientSession.getVersion(), serverVersion, headers);
+
+        SynInfo serverSynInfo = new SynInfo(headers, clientSynInfo.isClose());
+        StreamFrameListener listener = new ProxyStreamFrameListener(clientStream);
+        StreamPromise promise = new StreamPromise(clientStream, serverSynInfo);
+        clientStream.setAttribute(STREAM_PROMISE_ATTRIBUTE, promise);
+        serverSession.syn(serverSynInfo, listener, promise);
+        return this;
+    }
+
+    private static short getVersion(String protocol)
+    {
+        switch (protocol)
+        {
+            case "spdy/2":
+                return SPDY.V2;
+            case "spdy/3":
+                return SPDY.V3;
+            default:
+                throw new IllegalArgumentException("Procotol: " + protocol + " is not a known SPDY protocol");
+        }
+    }
+
+    @Override
+    public StreamFrameListener onPush(Stream stream, PushInfo pushInfo)
+    {
+        throw new IllegalStateException("We shouldn't receive pushes from clients");
+    }
+
+    @Override
+    public void onReply(Stream stream, ReplyInfo replyInfo)
+    {
+        throw new IllegalStateException("Servers do not receive replies");
+    }
+
+    @Override
+    public void onHeaders(Stream stream, HeadersInfo headersInfo)
+    {
+        // TODO
+        throw new UnsupportedOperationException("Not Yet Implemented");
+    }
+
+    @Override
+    public void onData(Stream clientStream, final DataInfo clientDataInfo)
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("C -> P {} on {}", clientDataInfo, clientStream);
+
+        ByteBufferDataInfo serverDataInfo = new ByteBufferDataInfo(clientDataInfo.asByteBuffer(false), clientDataInfo.isClose())
+        {
+            @Override
+            public void consume(int delta)
+            {
+                super.consume(delta);
+                clientDataInfo.consume(delta);
+            }
+        };
+
+        StreamPromise streamPromise = (StreamPromise)clientStream.getAttribute(STREAM_PROMISE_ATTRIBUTE);
+        streamPromise.data(serverDataInfo);
+    }
+
+    @Override
+    public void onFailure(Stream stream, Throwable x)
+    {
+        LOG.debug(x);
+    }
+
+    private Session produceSession(String host, short version, InetSocketAddress address)
+    {
+        try
+        {
+            Session session = serverSessions.get(host);
+            if (session == null)
+            {
+                SPDYClient client = factory.newSPDYClient(version);
+                session = client.connect(address, sessionListener);
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Proxy session connected to {}", address);
+                Session existing = serverSessions.putIfAbsent(host, session);
+                if (existing != null)
+                {
+                    session.goAway(new GoAwayInfo(), Callback.Adapter.INSTANCE);
+                    session = existing;
+                }
+            }
+            return session;
+        }
+        catch (Exception x)
+        {
+            LOG.debug(x);
+            return null;
+        }
+    }
+
+    private void convert(short fromVersion, short toVersion, Fields headers)
+    {
+        if (fromVersion != toVersion)
+        {
+            for (HTTPSPDYHeader httpHeader : HTTPSPDYHeader.values())
+            {
+                Fields.Field header = headers.remove(httpHeader.name(fromVersion));
+                if (header != null)
+                {
+                    String toName = httpHeader.name(toVersion);
+                    for (String value : header.getValues())
+                        headers.add(toName, value);
+                }
+            }
+        }
+    }
+
+    private void rst(Stream stream)
+    {
+        RstInfo rstInfo = new RstInfo(stream.getId(), StreamStatus.REFUSED_STREAM);
+        stream.getSession().rst(rstInfo, Callback.Adapter.INSTANCE);
+    }
+
+    private class ProxyPushStreamFrameListener implements StreamFrameListener
+    {
+        private PushStreamPromise pushStreamPromise;
+
+        private ProxyPushStreamFrameListener(PushStreamPromise pushStreamPromise)
+        {
+            this.pushStreamPromise = pushStreamPromise;
+        }
+
+        @Override
+        public StreamFrameListener onPush(Stream stream, PushInfo pushInfo)
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("S -> P pushed {} on {}. Opening new PushStream P -> C now.", pushInfo, stream);
+            PushStreamPromise newPushStreamPromise = new PushStreamPromise(stream, pushInfo);
+            this.pushStreamPromise.push(newPushStreamPromise);
+            return new ProxyPushStreamFrameListener(newPushStreamPromise);
+        }
+
+        @Override
+        public void onReply(Stream stream, ReplyInfo replyInfo)
+        {
+            // Push streams never send a reply
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void onHeaders(Stream stream, HeadersInfo headersInfo)
+        {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void onData(Stream serverStream, final DataInfo serverDataInfo)
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("S -> P pushed {} on {}", serverDataInfo, serverStream);
+
+            ByteBufferDataInfo clientDataInfo = new ByteBufferDataInfo(serverDataInfo.asByteBuffer(false), serverDataInfo.isClose())
+            {
+                @Override
+                public void consume(int delta)
+                {
+                    super.consume(delta);
+                    serverDataInfo.consume(delta);
+                }
+            };
+
+            pushStreamPromise.data(clientDataInfo);
+        }
+
+        @Override
+        public void onFailure(Stream stream, Throwable x)
+        {
+            LOG.debug(x);
+        }
+    }
+
+    private class ProxyStreamFrameListener extends StreamFrameListener.Adapter
+    {
+        private final Stream receiverStream;
+
+        public ProxyStreamFrameListener(Stream receiverStream)
+        {
+            this.receiverStream = receiverStream;
+        }
+
+        @Override
+        public StreamFrameListener onPush(Stream senderStream, PushInfo pushInfo)
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("S -> P {} on {}");
+            PushInfo newPushInfo = convertPushInfo(pushInfo, senderStream, receiverStream);
+            PushStreamPromise pushStreamPromise = new PushStreamPromise(senderStream, newPushInfo);
+            receiverStream.push(newPushInfo, pushStreamPromise);
+            return new ProxyPushStreamFrameListener(pushStreamPromise);
+        }
+
+        @Override
+        public void onReply(final Stream stream, ReplyInfo replyInfo)
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("S -> P {} on {}", replyInfo, stream);
+            final ReplyInfo clientReplyInfo = new ReplyInfo(convertHeaders(stream, receiverStream, replyInfo.getHeaders()),
+                    replyInfo.isClose());
+            reply(stream, clientReplyInfo);
+        }
+
+        private void reply(final Stream stream, final ReplyInfo clientReplyInfo)
+        {
+            receiverStream.reply(clientReplyInfo, new Callback()
+            {
+                @Override
+                public void succeeded()
+                {
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("P -> C {} from {} to {}", clientReplyInfo, stream, receiverStream);
+                }
+
+                @Override
+                public void failed(Throwable x)
+                {
+                    LOG.debug(x);
+                    rst(receiverStream);
+                }
+            });
+        }
+
+        @Override
+        public void onHeaders(Stream stream, HeadersInfo headersInfo)
+        {
+            // TODO
+            throw new UnsupportedOperationException("Not Yet Implemented");
+        }
+
+        @Override
+        public void onData(final Stream stream, final DataInfo dataInfo)
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("S -> P {} on {}", dataInfo, stream);
+            data(stream, dataInfo);
+        }
+
+        private void data(final Stream stream, final DataInfo serverDataInfo)
+        {
+            final ByteBufferDataInfo clientDataInfo = new ByteBufferDataInfo(serverDataInfo.asByteBuffer(false), serverDataInfo.isClose())
+            {
+                @Override
+                public void consume(int delta)
+                {
+                    super.consume(delta);
+                    serverDataInfo.consume(delta);
+                }
+            };
+
+            receiverStream.data(clientDataInfo, new Callback() //TODO: timeout???
+            {
+                @Override
+                public void succeeded()
+                {
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("P -> C {} from {} to {}", clientDataInfo, stream, receiverStream);
+                }
+
+                @Override
+                public void failed(Throwable x)
+                {
+                    LOG.debug(x);
+                    rst(receiverStream);
+                }
+            });
+        }
+    }
+
+    /**
+     * <p>{@link StreamPromise} implements the forwarding of DATA frames from the client to the server and vice
+     * versa.</p> <p>Instances of this class buffer DATA frames sent by clients and send them to the server. The
+     * buffering happens between the send of the SYN_STREAM to the server (where DATA frames may arrive from the client
+     * before the SYN_STREAM has been fully sent), and between DATA frames, if the client is a fast producer and the
+     * server a slow consumer, or if the client is a SPDY v2 client (and hence without flow control) while the server is
+     * a SPDY v3 server (and hence with flow control).</p>
+     */
+    private class StreamPromise implements Promise<Stream>
+    {
+        private final Queue<DataInfoCallback> queue = new LinkedList<>();
+        private final Stream senderStream;
+        private final Info info;
+        private Stream receiverStream;
+
+        private StreamPromise(Stream senderStream, Info info)
+        {
+            this.senderStream = senderStream;
+            this.info = info;
+        }
+
+        @Override
+        public void succeeded(Stream stream)
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("P -> S {} from {} to {}", info, senderStream, stream);
+
+            stream.setAttribute(CLIENT_STREAM_ATTRIBUTE, senderStream);
+
+            DataInfoCallback dataInfoCallback;
+            synchronized (queue)
+            {
+                this.receiverStream = stream;
+                dataInfoCallback = queue.peek();
+                if (dataInfoCallback != null)
+                {
+                    if (dataInfoCallback.flushing)
+                    {
+                        if (LOG.isDebugEnabled())
+                            LOG.debug("SYN completed, flushing {}, queue size {}", dataInfoCallback.dataInfo, queue.size());
+                        dataInfoCallback = null;
+                    }
+                    else
+                    {
+                        dataInfoCallback.flushing = true;
+                        if (LOG.isDebugEnabled())
+                            LOG.debug("SYN completed, queue size {}", queue.size());
+                    }
+                }
+                else
+                {
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("SYN completed, queue empty");
+                }
+            }
+            if (dataInfoCallback != null)
+                flush(stream, dataInfoCallback);
+        }
+
+        @Override
+        public void failed(Throwable x)
+        {
+            LOG.debug(x);
+            rst(senderStream);
+        }
+
+        public void data(DataInfo dataInfo)
+        {
+            Stream receiverStream;
+            DataInfoCallback dataInfoCallbackToFlush = null;
+            DataInfoCallback dataInfoCallBackToQueue = new DataInfoCallback(dataInfo);
+            synchronized (queue)
+            {
+                queue.offer(dataInfoCallBackToQueue);
+                receiverStream = this.receiverStream;
+                if (receiverStream != null)
+                {
+                    dataInfoCallbackToFlush = queue.peek();
+                    if (dataInfoCallbackToFlush.flushing)
+                    {
+                        if (LOG.isDebugEnabled())
+                            LOG.debug("Queued {}, flushing {}, queue size {}", dataInfo, dataInfoCallbackToFlush.dataInfo, queue.size());
+                        receiverStream = null;
+                    }
+                    else
+                    {
+                        dataInfoCallbackToFlush.flushing = true;
+                        if (LOG.isDebugEnabled())
+                            LOG.debug("Queued {}, queue size {}", dataInfo, queue.size());
+                    }
+                }
+                else
+                {
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("Queued {}, SYN incomplete, queue size {}", dataInfo, queue.size());
+                }
+            }
+            if (receiverStream != null)
+                flush(receiverStream, dataInfoCallbackToFlush);
+        }
+
+        private void flush(Stream receiverStream, DataInfoCallback dataInfoCallback)
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("P -> S {} on {}", dataInfoCallback.dataInfo, receiverStream);
+            receiverStream.data(dataInfoCallback.dataInfo, dataInfoCallback); //TODO: timeout???
+        }
+
+        private class DataInfoCallback implements Callback
+        {
+            private final DataInfo dataInfo;
+            private boolean flushing;
+
+            private DataInfoCallback(DataInfo dataInfo)
+            {
+                this.dataInfo = dataInfo;
+            }
+
+            @Override
+            public void succeeded()
+            {
+                Stream serverStream;
+                DataInfoCallback dataInfoCallback;
+                synchronized (queue)
+                {
+                    serverStream = StreamPromise.this.receiverStream;
+                    assert serverStream != null;
+                    dataInfoCallback = queue.poll();
+                    assert dataInfoCallback == this;
+                    dataInfoCallback = queue.peek();
+                    if (dataInfoCallback != null)
+                    {
+                        assert !dataInfoCallback.flushing;
+                        dataInfoCallback.flushing = true;
+                        if (LOG.isDebugEnabled())
+                            LOG.debug("Completed {}, queue size {}", dataInfo, queue.size());
+                    }
+                    else
+                    {
+                        if (LOG.isDebugEnabled())
+                            LOG.debug("Completed {}, queue empty", dataInfo);
+                    }
+                }
+                if (dataInfoCallback != null)
+                    flush(serverStream, dataInfoCallback);
+            }
+
+            @Override
+            public void failed(Throwable x)
+            {
+                LOG.debug(x);
+                rst(senderStream);
+            }
+        }
+
+        public Stream getSenderStream()
+        {
+            return senderStream;
+        }
+
+        public Info getInfo()
+        {
+            return info;
+        }
+
+        public Stream getReceiverStream()
+        {
+            synchronized (queue)
+            {
+                return receiverStream;
+            }
+        }
+    }
+
+    private class PushStreamPromise extends StreamPromise
+    {
+        private volatile PushStreamPromise pushStreamPromise;
+
+        private PushStreamPromise(Stream senderStream, PushInfo pushInfo)
+        {
+            super(senderStream, pushInfo);
+        }
+
+        @Override
+        public void succeeded(Stream receiverStream)
+        {
+            super.succeeded(receiverStream);
+
+            if (LOG.isDebugEnabled())
+                LOG.debug("P -> C PushStreamPromise.succeeded() called with pushStreamPromise: {}", pushStreamPromise);
+
+            PushStreamPromise promise = pushStreamPromise;
+            if (promise != null)
+                receiverStream.push(convertPushInfo((PushInfo)getInfo(), getSenderStream(), receiverStream), pushStreamPromise);
+        }
+
+        public void push(PushStreamPromise pushStreamPromise)
+        {
+            Stream receiverStream = getReceiverStream();
+
+            if (receiverStream != null)
+                receiverStream.push(convertPushInfo((PushInfo)getInfo(), getSenderStream(), receiverStream), pushStreamPromise);
+            else
+                this.pushStreamPromise = pushStreamPromise;
+        }
+    }
+
+    private class ProxySessionFrameListener extends SessionFrameListener.Adapter
+    {
+        @Override
+        public void onRst(Session serverSession, RstInfo serverRstInfo)
+        {
+            Stream serverStream = serverSession.getStream(serverRstInfo.getStreamId());
+            if (serverStream != null)
+            {
+                Stream clientStream = (Stream)serverStream.getAttribute(CLIENT_STREAM_ATTRIBUTE);
+                if (clientStream != null)
+                {
+                    Session clientSession = clientStream.getSession();
+                    RstInfo clientRstInfo = new RstInfo(clientStream.getId(), serverRstInfo.getStreamStatus());
+                    clientSession.rst(clientRstInfo, Callback.Adapter.INSTANCE);
+                }
+            }
+        }
+
+        @Override
+        public void onGoAway(Session serverSession, GoAwayResultInfo goAwayResultInfo)
+        {
+            serverSessions.values().remove(serverSession);
+        }
+    }
+
+    private PushInfo convertPushInfo(PushInfo pushInfo, Stream from, Stream to)
+    {
+        Fields headersToConvert = pushInfo.getHeaders();
+        Fields headers = convertHeaders(from, to, headersToConvert);
+        return new PushInfo(getTimeout(), TimeUnit.MILLISECONDS, headers, pushInfo.isClose());
+    }
+
+    private Fields convertHeaders(Stream from, Stream to, Fields headersToConvert)
+    {
+        Fields headers = new Fields(headersToConvert, false);
+        addResponseProxyHeaders(from, headers);
+        customizeResponseHeaders(from, headers);
+        convert(from.getSession().getVersion(), to.getSession().getVersion(), headers);
+        return headers;
+    }
+}
diff --git a/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/AbstractHTTPSPDYTest.java b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/AbstractHTTPSPDYTest.java
new file mode 100644
index 0000000..7812970
--- /dev/null
+++ b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/AbstractHTTPSPDYTest.java
@@ -0,0 +1,136 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.spdy.server.http;
+
+import java.net.InetSocketAddress;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.concurrent.Executor;
+
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.spdy.api.SPDY;
+import org.eclipse.jetty.spdy.api.Session;
+import org.eclipse.jetty.spdy.api.SessionFrameListener;
+import org.eclipse.jetty.spdy.client.SPDYClient;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.StdErrLog;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.junit.After;
+import org.junit.Rule;
+import org.junit.rules.TestWatcher;
+import org.junit.runner.Description;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+ at RunWith(Parameterized.class)
+public abstract class AbstractHTTPSPDYTest
+{
+    @Parameterized.Parameters
+    public static Collection<Short[]> parameters()
+    {
+        return Arrays.asList(new Short[]{SPDY.V2}, new Short[]{SPDY.V3});
+    }
+
+    @Rule
+    public final TestWatcher testName = new TestWatcher()
+    {
+
+        @Override
+        public void starting(Description description)
+        {
+            super.starting(description);
+            System.err.printf("Running %s.%s()%n",
+                    description.getClassName(),
+                    description.getMethodName());
+        }
+    };
+
+    protected final short version;
+    protected Server server;
+    protected SPDYClient.Factory clientFactory;
+    protected HTTPSPDYServerConnector connector;
+
+    protected AbstractHTTPSPDYTest(short version)
+    {
+        this.version = version;
+    }
+
+    protected InetSocketAddress startHTTPServer(short version, Handler handler, long idleTimeout) throws Exception
+    {
+        QueuedThreadPool threadPool = new QueuedThreadPool(256);
+        threadPool.setName("serverQTP");
+        server = new Server(threadPool);
+        connector = newHTTPSPDYServerConnector(version);
+        connector.setPort(0);
+        connector.setIdleTimeout(idleTimeout);
+        server.addConnector(connector);
+        server.setHandler(handler);
+        server.start();
+        return new InetSocketAddress("localhost", connector.getLocalPort());
+    }
+
+    protected Server getServer()
+    {
+        return server;
+    }
+
+    protected HTTPSPDYServerConnector newHTTPSPDYServerConnector(short version)
+    {
+        // For these tests, we need the connector to speak HTTP over SPDY even in non-SSL
+        HttpConfiguration httpConfiguration = new HttpConfiguration();
+        httpConfiguration.setSendServerVersion(true);
+        httpConfiguration.setSendXPoweredBy(true);
+        return new HTTPSPDYServerConnector(server, version, httpConfiguration, new PushStrategy.None());
+    }
+
+    protected Session startClient(short version, InetSocketAddress socketAddress, SessionFrameListener listener) throws Exception
+    {
+        if (clientFactory == null)
+        {
+            QueuedThreadPool threadPool = new QueuedThreadPool();
+            threadPool.setName(threadPool.getName() + "-client");
+            clientFactory = newSPDYClientFactory(threadPool);
+            clientFactory.start();
+        }
+        return clientFactory.newSPDYClient(version).connect(socketAddress, listener);
+    }
+
+    protected SPDYClient.Factory newSPDYClientFactory(Executor threadPool)
+    {
+        return new SPDYClient.Factory(threadPool, null, null, connector.getIdleTimeout());
+    }
+
+    @After
+    public void destroy() throws Exception
+    {
+        ((StdErrLog)Log.getLogger(HTTPSPDYServerConnector.class)).setHideStacks(true);
+        if (clientFactory != null)
+        {
+            clientFactory.stop();
+        }
+        if (server != null)
+        {
+            server.stop();
+            server.join();
+        }
+        ((StdErrLog)Log.getLogger(HTTPSPDYServerConnector.class)).setHideStacks(false);
+    }
+}
diff --git a/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/ConcurrentStreamsTest.java b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/ConcurrentStreamsTest.java
new file mode 100644
index 0000000..2621713
--- /dev/null
+++ b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/ConcurrentStreamsTest.java
@@ -0,0 +1,128 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.spdy.server.http;
+
+import java.io.IOException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.spdy.api.ReplyInfo;
+import org.eclipse.jetty.spdy.api.Session;
+import org.eclipse.jetty.spdy.api.Stream;
+import org.eclipse.jetty.spdy.api.StreamFrameListener;
+import org.eclipse.jetty.spdy.api.SynInfo;
+import org.eclipse.jetty.spdy.http.HTTPSPDYHeader;
+import org.eclipse.jetty.util.Fields;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class ConcurrentStreamsTest extends AbstractHTTPSPDYTest
+{
+    public ConcurrentStreamsTest(short version)
+    {
+        super(version);
+    }
+
+    @Test
+    public void testSlowStreamDoesNotBlockOtherStreams() throws Exception
+    {
+        final CountDownLatch slowServerLatch = new CountDownLatch(1);
+        final CountDownLatch fastServerLatch = new CountDownLatch(1);
+        final String fastPath = "/fast";
+        final String slowPath = "/slow";
+        Session session = startClient(version, startHTTPServer(version, new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
+                    throws IOException, ServletException
+            {
+                try
+                {
+                    request.setHandled(true);
+                    switch (target)
+                    {
+                        case slowPath:
+                            Assert.assertTrue(fastServerLatch.await(10, TimeUnit.SECONDS));
+                            slowServerLatch.countDown();
+                            break;
+                        case fastPath:
+                            fastServerLatch.countDown();
+                            break;
+                        default:
+                            Assert.fail();
+                            break;
+                    }
+                }
+                catch (InterruptedException x)
+                {
+                    throw new ServletException(x);
+                }
+            }
+        }, 30000), null);
+
+        // Perform slow request. This will wait on server side until the fast request wakes it up
+        Fields headers = createHeaders(slowPath);
+        final CountDownLatch slowClientLatch = new CountDownLatch(1);
+        session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter()
+        {
+            @Override
+            public void onReply(Stream stream, ReplyInfo replyInfo)
+            {
+                Fields replyHeaders = replyInfo.getHeaders();
+                Assert.assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200"));
+                slowClientLatch.countDown();
+            }
+        });
+
+        // Perform the fast request. This will wake up the slow request
+        headers = createHeaders(fastPath);
+        final CountDownLatch fastClientLatch = new CountDownLatch(1);
+        session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter()
+        {
+            @Override
+            public void onReply(Stream stream, ReplyInfo replyInfo)
+            {
+                Fields replyHeaders = replyInfo.getHeaders();
+                Assert.assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200"));
+                fastClientLatch.countDown();
+            }
+        });
+
+        Assert.assertTrue(fastServerLatch.await(5, TimeUnit.SECONDS));
+        Assert.assertTrue(slowServerLatch.await(5, TimeUnit.SECONDS));
+        Assert.assertTrue(fastClientLatch.await(5, TimeUnit.SECONDS));
+        Assert.assertTrue(slowClientLatch.await(5, TimeUnit.SECONDS));
+    }
+
+    private Fields createHeaders(String path)
+    {
+        Fields headers = new Fields();
+        headers.put(HTTPSPDYHeader.METHOD.name(version), "GET");
+        headers.put(HTTPSPDYHeader.URI.name(version), path);
+        headers.put(HTTPSPDYHeader.VERSION.name(version), "HTTP/1.1");
+        headers.put(HTTPSPDYHeader.HOST.name(version), "localhost:" + connector.getLocalPort());
+        return headers;
+    }
+}
diff --git a/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/HttpTransportOverSPDYTest.java b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/HttpTransportOverSPDYTest.java
new file mode 100644
index 0000000..6d12da6
--- /dev/null
+++ b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/HttpTransportOverSPDYTest.java
@@ -0,0 +1,288 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.spdy.server.http;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.Random;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.http.HttpGenerator;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.spdy.api.ByteBufferDataInfo;
+import org.eclipse.jetty.spdy.api.DataInfo;
+import org.eclipse.jetty.spdy.api.ReplyInfo;
+import org.eclipse.jetty.spdy.api.SPDY;
+import org.eclipse.jetty.spdy.api.Session;
+import org.eclipse.jetty.spdy.api.Stream;
+import org.eclipse.jetty.spdy.http.HTTPSPDYHeader;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.Fields;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
+ at RunWith(MockitoJUnitRunner.class)
+public class HttpTransportOverSPDYTest
+{
+    @Mock
+    Connector connector;
+    @Mock
+    HttpConfiguration httpConfiguration;
+    @Mock
+    EndPoint endPoint;
+    @Mock
+    PushStrategy pushStrategy;
+    @Mock
+    Stream stream;
+    @Mock
+    Callback callback;
+    @Mock
+    Session session;
+    @Mock
+    HttpGenerator.ResponseInfo responseInfo;
+
+    Random random = new Random();
+    short version = SPDY.V3;
+
+    HttpTransportOverSPDY httpTransportOverSPDY;
+
+    @Before
+    public void setUp() throws Exception
+    {
+        Fields requestHeaders = new Fields();
+        requestHeaders.add(HTTPSPDYHeader.METHOD.name(version), "GET");
+        when(responseInfo.getStatus()).thenReturn(HttpStatus.OK_200);
+        when(stream.getSession()).thenReturn(session);
+        when(session.getVersion()).thenReturn(SPDY.V3);
+        when(stream.isClosed()).thenReturn(false);
+        httpTransportOverSPDY = new HttpTransportOverSPDY(connector, httpConfiguration, endPoint, pushStrategy,
+                stream, requestHeaders);
+    }
+
+    @Test
+    public void testSendWithResponseInfoNullAndContentNullAndLastContentTrue() throws Exception
+    {
+        ByteBuffer content = null;
+        boolean lastContent = true;
+
+        httpTransportOverSPDY.send(null, content, lastContent, callback);
+        ArgumentCaptor<ByteBufferDataInfo> dataInfoCaptor = ArgumentCaptor.forClass(ByteBufferDataInfo.class);
+        ArgumentCaptor<Callback> callbackCaptor = ArgumentCaptor.forClass(Callback.class);
+        verify(stream, times(1)).data(dataInfoCaptor.capture(), callbackCaptor.capture());
+        callbackCaptor.getValue().succeeded();
+        verify(callback, times(1)).succeeded();
+        assertThat("lastContent is true", dataInfoCaptor.getValue().isClose(), is(true));
+        assertThat("ByteBuffer is empty", dataInfoCaptor.getValue().length(), is(0));
+    }
+
+    @Test
+    public void testSendWithResponseInfoNullAndContentAndLastContentTrue() throws Exception
+    {
+        ByteBuffer content = createRandomByteBuffer();
+
+        boolean lastContent = true;
+
+        httpTransportOverSPDY.send(null, content, lastContent, callback);
+        ArgumentCaptor<ByteBufferDataInfo> dataInfoCaptor = ArgumentCaptor.forClass(ByteBufferDataInfo.class);
+        ArgumentCaptor<Callback> callbackCaptor = ArgumentCaptor.forClass(Callback.class);
+        verify(stream, times(1)).data(dataInfoCaptor.capture(), callbackCaptor.capture());
+        callbackCaptor.getValue().succeeded();
+        verify(callback, times(1)).succeeded();
+        assertThat("lastContent is true", dataInfoCaptor.getValue().isClose(), is(true));
+        assertThat("ByteBuffer length is 4096", dataInfoCaptor.getValue().length(), is(4096));
+    }
+
+    @Test
+    public void testSendWithResponseInfoNullAndEmptyContentAndLastContentTrue() throws Exception
+    {
+        ByteBuffer content = BufferUtil.EMPTY_BUFFER;
+        boolean lastContent = true;
+
+        httpTransportOverSPDY.send(null, content, lastContent, callback);
+        ArgumentCaptor<ByteBufferDataInfo> dataInfoCaptor = ArgumentCaptor.forClass(ByteBufferDataInfo.class);
+        ArgumentCaptor<Callback> callbackCaptor = ArgumentCaptor.forClass(Callback.class);
+        verify(stream, times(1)).data(dataInfoCaptor.capture(), callbackCaptor.capture());
+        callbackCaptor.getValue().succeeded();
+        verify(callback, times(1)).succeeded();
+        assertThat("lastContent is true", dataInfoCaptor.getValue().isClose(), is(true));
+        assertThat("ByteBuffer is empty", dataInfoCaptor.getValue().length(), is(0));
+    }
+
+    @Test
+    public void testSendWithResponseInfoNullAndContentAndLastContentFalse() throws Exception
+    {
+        ByteBuffer content = createRandomByteBuffer();
+        boolean lastContent = false;
+
+        httpTransportOverSPDY.send(null, content, lastContent, callback);
+        ArgumentCaptor<ByteBufferDataInfo> dataInfoCaptor = ArgumentCaptor.forClass(ByteBufferDataInfo.class);
+        ArgumentCaptor<Callback> callbackCaptor = ArgumentCaptor.forClass(Callback.class);
+        verify(stream, times(1)).data(dataInfoCaptor.capture(), callbackCaptor.capture());
+        callbackCaptor.getValue().succeeded();
+        verify(callback, times(1)).succeeded();
+        assertThat("lastContent is false", dataInfoCaptor.getValue().isClose(), is(false));
+        assertThat("ByteBuffer is empty", dataInfoCaptor.getValue().length(), is(4096));
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testSendWithResponseInfoNullAndContentNullAndLastContentFalse() throws Exception
+    {
+        ByteBuffer content = null;
+        boolean lastContent = false;
+        httpTransportOverSPDY.send(null, content, lastContent, callback);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testSendWithResponseInfoNullAndEmptyContentAndLastContentFalse() throws Exception
+    {
+        ByteBuffer content = BufferUtil.EMPTY_BUFFER;
+        boolean lastContent = false;
+        httpTransportOverSPDY.send(null, content, lastContent, callback);
+    }
+
+    @Test
+    public void testSendWithResponseInfoAndContentNullAndLastContentFalse() throws Exception
+    {
+        ByteBuffer content = null;
+        boolean lastContent = false;
+
+        httpTransportOverSPDY.send(responseInfo, content, lastContent, callback);
+        ArgumentCaptor<ReplyInfo> replyInfoCaptor = ArgumentCaptor.forClass(ReplyInfo.class);
+        ArgumentCaptor<Callback> callbackCaptor = ArgumentCaptor.forClass(Callback.class);
+        verify(stream, times(1)).reply(replyInfoCaptor.capture(), callbackCaptor.capture());
+        callbackCaptor.getValue().succeeded();
+        assertThat("ReplyInfo close is true", replyInfoCaptor.getValue().isClose(), is(false));
+
+        verify(stream, times(0)).data(any(ByteBufferDataInfo.class), any(Callback.class));
+        verify(callback, times(1)).succeeded();
+    }
+
+    @Test
+    public void testSendWithResponseInfoAndContentNullAndLastContentTrue() throws Exception
+    {
+        ByteBuffer content = null;
+        boolean lastContent = true;
+
+        // when stream.isClosed() is called a 2nd time, the reply has closed the stream already
+        when(stream.isClosed()).thenReturn(false).thenReturn(true);
+
+        httpTransportOverSPDY.send(responseInfo, content, lastContent, callback);
+        ArgumentCaptor<ReplyInfo> replyInfoCaptor = ArgumentCaptor.forClass(ReplyInfo.class);
+        ArgumentCaptor<Callback> callbackCaptor = ArgumentCaptor.forClass(Callback.class);
+        verify(stream, times(1)).reply(replyInfoCaptor.capture(), callbackCaptor.capture());
+        callbackCaptor.getValue().succeeded();
+        assertThat("ReplyInfo close is true", replyInfoCaptor.getValue().isClose(), is(true));
+
+        verify(callback, times(1)).succeeded();
+    }
+
+    @Test
+    public void testSendWithResponseInfoAndContentAndLastContentTrue() throws Exception
+    {
+        ByteBuffer content = createRandomByteBuffer();
+        boolean lastContent = true;
+
+        httpTransportOverSPDY.send(responseInfo, content, lastContent, callback);
+        ArgumentCaptor<ReplyInfo> replyInfoCaptor = ArgumentCaptor.forClass(ReplyInfo.class);
+        ArgumentCaptor<Callback> callbackCaptor = ArgumentCaptor.forClass(Callback.class);
+        verify(stream, times(1)).reply(replyInfoCaptor.capture(), callbackCaptor.capture());
+        callbackCaptor.getValue().succeeded();
+        assertThat("ReplyInfo close is false", replyInfoCaptor.getValue().isClose(), is(false));
+        ArgumentCaptor<ByteBufferDataInfo> dataInfoCaptor = ArgumentCaptor.forClass(ByteBufferDataInfo.class);
+        verify(stream, times(1)).data(dataInfoCaptor.capture(), callbackCaptor.capture());
+        callbackCaptor.getValue().succeeded();
+        assertThat("lastContent is true", dataInfoCaptor.getValue().isClose(), is(true));
+        assertThat("ByteBuffer length is 4096", dataInfoCaptor.getValue().length(), is(4096));
+        verify(callback, times(1)).succeeded();
+    }
+
+    @Test
+    public void testSendWithResponseInfoAndContentAndLastContentFalse() throws Exception
+    {
+        ByteBuffer content = createRandomByteBuffer();
+        boolean lastContent = false;
+
+        httpTransportOverSPDY.send(responseInfo, content, lastContent, callback);
+        ArgumentCaptor<ReplyInfo> replyInfoCaptor = ArgumentCaptor.forClass(ReplyInfo.class);
+        ArgumentCaptor<Callback> callbackCaptor = ArgumentCaptor.forClass(Callback.class);
+        verify(stream, times(1)).reply(replyInfoCaptor.capture(), callbackCaptor.capture());
+        callbackCaptor.getValue().succeeded();
+        assertThat("ReplyInfo close is false", replyInfoCaptor.getValue().isClose(), is(false));
+
+        ArgumentCaptor<ByteBufferDataInfo> dataInfoCaptor = ArgumentCaptor.forClass(ByteBufferDataInfo.class);
+        verify(stream, times(1)).data(dataInfoCaptor.capture(), callbackCaptor.capture());
+        callbackCaptor.getValue().succeeded();
+        assertThat("lastContent is false", dataInfoCaptor.getValue().isClose(), is(false));
+        assertThat("ByteBuffer length is 4096", dataInfoCaptor.getValue().length(), is(4096));
+
+        verify(callback, times(1)).succeeded();
+    }
+
+    @Test
+    public void testVerifyThatAStreamIsNotCommittedTwice() throws IOException, InterruptedException
+    {
+        final CountDownLatch failedCalledLatch = new CountDownLatch(1);
+        ByteBuffer content = createRandomByteBuffer();
+        boolean lastContent = false;
+
+        httpTransportOverSPDY.send(responseInfo, content, lastContent, callback);
+        ArgumentCaptor<ReplyInfo> replyInfoCaptor = ArgumentCaptor.forClass(ReplyInfo.class);
+        verify(stream, times(1)).reply(replyInfoCaptor.capture(), any(Callback.class));
+        assertThat("ReplyInfo close is false", replyInfoCaptor.getValue().isClose(), is(false));
+
+        httpTransportOverSPDY.send(HttpGenerator.RESPONSE_500_INFO, null, true, new Callback.Adapter()
+        {
+            @Override
+            public void failed(Throwable x)
+            {
+                failedCalledLatch.countDown();
+            }
+        });
+
+        verify(stream, times(1)).data(any(DataInfo.class), any(Callback.class));
+
+        assertThat("callback.failed has been called", failedCalledLatch.await(5, TimeUnit.SECONDS), is(true));
+    }
+
+    private ByteBuffer createRandomByteBuffer()
+    {
+        ByteBuffer content = BufferUtil.allocate(8192);
+        BufferUtil.flipToFill(content);
+        byte[] randomBytes = new byte[4096];
+        random.nextBytes(randomBytes);
+        content.put(randomBytes);
+        return content;
+    }
+}
diff --git a/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/PushStrategyBenchmarkTest.java b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/PushStrategyBenchmarkTest.java
new file mode 100644
index 0000000..ccaf632
--- /dev/null
+++ b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/PushStrategyBenchmarkTest.java
@@ -0,0 +1,397 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.spdy.server.http;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.util.Collections;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.client.api.Result;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.server.ConnectionFactory;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.HttpConnectionFactory;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.spdy.api.DataInfo;
+import org.eclipse.jetty.spdy.api.GoAwayInfo;
+import org.eclipse.jetty.spdy.api.SPDY;
+import org.eclipse.jetty.spdy.api.Session;
+import org.eclipse.jetty.spdy.api.SessionFrameListener;
+import org.eclipse.jetty.spdy.api.Stream;
+import org.eclipse.jetty.spdy.api.StreamFrameListener;
+import org.eclipse.jetty.spdy.api.SynInfo;
+import org.eclipse.jetty.spdy.http.HTTPSPDYHeader;
+import org.eclipse.jetty.util.Fields;
+import org.junit.Assert;
+import org.junit.Ignore;
+import org.junit.Test;
+
+ at Ignore("make this test pass") // TODO
+public class PushStrategyBenchmarkTest extends AbstractHTTPSPDYTest
+{
+    // Sample resources size from webtide.com home page
+    private final int[] htmlResources = new int[]
+            {8 * 1024};
+    private final int[] cssResources = new int[]
+            {12 * 1024, 2 * 1024};
+    private final int[] jsResources = new int[]
+            {75 * 1024, 24 * 1024, 36 * 1024};
+    private final int[] pngResources = new int[]
+            {1024, 45 * 1024, 6 * 1024, 2 * 1024, 2 * 1024, 2 * 1024, 3 * 1024, 512, 512, 19 * 1024, 512, 128, 32};
+    private final Set<String> pushedResources = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>());
+    private final AtomicReference<CountDownLatch> latch = new AtomicReference<>();
+    private final long roundtrip = 100;
+    private final int runs = 10;
+
+    public PushStrategyBenchmarkTest(short version)
+    {
+        super(version);
+    }
+
+    @Test
+    public void benchmarkPushStrategy() throws Exception
+    {
+        InetSocketAddress address = startHTTPServer(version, new PushStrategyBenchmarkHandler(), 30000);
+
+        // Plain HTTP
+        ConnectionFactory factory = new HttpConnectionFactory(new HttpConfiguration());
+        connector.setDefaultProtocol(factory.getProtocol());
+        HttpClient httpClient = new HttpClient();
+        // Simulate browsers, that open 6 connection per origin
+        httpClient.setMaxConnectionsPerDestination(6);
+        httpClient.start();
+        benchmarkHTTP(httpClient);
+        httpClient.stop();
+
+        // First push strategy
+        PushStrategy pushStrategy = new PushStrategy.None();
+        factory = new HTTPSPDYServerConnectionFactory(version, new HttpConfiguration(), pushStrategy);
+        connector.setDefaultProtocol(factory.getProtocol());
+        Session session = startClient(version, address, new ClientSessionFrameListener());
+        benchmarkSPDY(pushStrategy, session);
+        session.goAway(new GoAwayInfo(5, TimeUnit.SECONDS));
+
+        // Second push strategy
+        pushStrategy = new ReferrerPushStrategy();
+        factory = new HTTPSPDYServerConnectionFactory(version, new HttpConfiguration(), pushStrategy);
+        connector.setDefaultProtocol(factory.getProtocol());
+        session = startClient(version, address, new ClientSessionFrameListener());
+        benchmarkSPDY(pushStrategy, session);
+        session.goAway(new GoAwayInfo(5, TimeUnit.SECONDS));
+    }
+
+    private void benchmarkHTTP(HttpClient httpClient) throws Exception
+    {
+        // Warm up
+        performHTTPRequests(httpClient);
+        performHTTPRequests(httpClient);
+
+        long total = 0;
+        for (int i = 0; i < runs; ++i)
+        {
+            long begin = System.nanoTime();
+            int requests = performHTTPRequests(httpClient);
+            long elapsed = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - begin);
+            total += elapsed;
+            System.err.printf("HTTP: run %d, %d request(s), roundtrip delay %d ms, elapsed = %d%n",
+                    i, requests, roundtrip, elapsed);
+        }
+        System.err.printf("HTTP: roundtrip delay %d ms, average = %d%n%n",
+                roundtrip, total / runs);
+    }
+
+    private int performHTTPRequests(HttpClient httpClient) throws Exception
+    {
+        int result = 0;
+
+        for (int j = 0; j < htmlResources.length; ++j)
+        {
+            latch.set(new CountDownLatch(cssResources.length + jsResources.length + pngResources.length));
+
+            String primaryPath = "/" + j + ".html";
+            String referrer = "http://localhost:" + connector.getLocalPort() + primaryPath;
+            ++result;
+            ContentResponse response = httpClient.newRequest("localhost", connector.getLocalPort())
+                    .path(primaryPath)
+                    .timeout(5, TimeUnit.SECONDS)
+                    .send();
+            Assert.assertEquals(200, response.getStatus());
+
+            for (int i = 0; i < cssResources.length; ++i)
+            {
+                String path = "/" + i + ".css";
+                ++result;
+                httpClient.newRequest("localhost", connector.getLocalPort())
+                        .path(path)
+                        .header(HttpHeader.REFERER, referrer)
+                        .send(new TestListener());
+            }
+            for (int i = 0; i < jsResources.length; ++i)
+            {
+                String path = "/" + i + ".js";
+                ++result;
+                httpClient.newRequest("localhost", connector.getLocalPort())
+                        .path(path)
+                        .header(HttpHeader.REFERER, referrer)
+                        .send(new TestListener());
+            }
+            for (int i = 0; i < pngResources.length; ++i)
+            {
+                String path = "/" + i + ".png";
+                ++result;
+                httpClient.newRequest("localhost", connector.getLocalPort())
+                        .path(path)
+                        .header(HttpHeader.REFERER, referrer)
+                        .send(new TestListener());
+            }
+
+            Assert.assertTrue(latch.get().await(5, TimeUnit.SECONDS));
+        }
+
+        return result;
+    }
+
+    private void benchmarkSPDY(PushStrategy pushStrategy, Session session) throws Exception
+    {
+        // Warm up PushStrategy
+        performRequests(session);
+        performRequests(session);
+
+        long total = 0;
+        for (int i = 0; i < runs; ++i)
+        {
+            long begin = System.nanoTime();
+            int requests = performRequests(session);
+            long elapsed = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - begin);
+            total += elapsed;
+            System.err.printf("SPDY(%s): run %d, %d request(s), roundtrip delay %d ms, elapsed = %d%n",
+                    pushStrategy.getClass().getSimpleName(), i, requests, roundtrip, elapsed);
+        }
+        System.err.printf("SPDY(%s): roundtrip delay %d ms, average = %d%n%n",
+                pushStrategy.getClass().getSimpleName(), roundtrip, total / runs);
+    }
+
+    private int performRequests(Session session) throws Exception
+    {
+        int result = 0;
+
+        for (int j = 0; j < htmlResources.length; ++j)
+        {
+            latch.set(new CountDownLatch(cssResources.length + jsResources.length + pngResources.length));
+            pushedResources.clear();
+
+            String primaryPath = "/" + j + ".html";
+            String referrer = "http://localhost:" + connector.getLocalPort() + primaryPath;
+            Fields headers = new Fields();
+            headers.put(HTTPSPDYHeader.METHOD.name(version), "GET");
+            headers.put(HTTPSPDYHeader.URI.name(version), primaryPath);
+            headers.put(HTTPSPDYHeader.VERSION.name(version), "HTTP/1.1");
+            headers.put(HTTPSPDYHeader.SCHEME.name(version), "http");
+            headers.put(HTTPSPDYHeader.HOST.name(version), "localhost:" + connector.getLocalPort());
+            // Wait for the HTML to simulate browser's behavior
+            ++result;
+            final CountDownLatch htmlLatch = new CountDownLatch(1);
+            session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter()
+            {
+                @Override
+                public void onData(Stream stream, DataInfo dataInfo)
+                {
+                    dataInfo.consume(dataInfo.length());
+                    if (dataInfo.isClose())
+                        htmlLatch.countDown();
+                }
+            });
+            Assert.assertTrue(htmlLatch.await(5, TimeUnit.SECONDS));
+
+            for (int i = 0; i < cssResources.length; ++i)
+            {
+                String path = "/" + i + ".css";
+                if (pushedResources.contains(path))
+                    continue;
+                headers = createRequestHeaders(referrer, path);
+                ++result;
+                session.syn(new SynInfo(headers, true), new DataListener());
+            }
+            for (int i = 0; i < jsResources.length; ++i)
+            {
+                String path = "/" + i + ".js";
+                if (pushedResources.contains(path))
+                    continue;
+                headers = createRequestHeaders(referrer, path);
+                ++result;
+                session.syn(new SynInfo(headers, true), new DataListener());
+            }
+            for (int i = 0; i < pngResources.length; ++i)
+            {
+                String path = "/" + i + ".png";
+                if (pushedResources.contains(path))
+                    continue;
+                headers = createRequestHeaders(referrer, path);
+                ++result;
+                session.syn(new SynInfo(headers, true), new DataListener());
+            }
+
+            Assert.assertTrue(latch.get().await(5, TimeUnit.SECONDS));
+        }
+
+        return result;
+    }
+
+    private Fields createRequestHeaders(String referrer, String path)
+    {
+        Fields headers;
+        headers = new Fields();
+        headers.put(HTTPSPDYHeader.METHOD.name(version), "GET");
+        headers.put(HTTPSPDYHeader.URI.name(version), path);
+        headers.put(HTTPSPDYHeader.VERSION.name(version), "HTTP/1.1");
+        headers.put(HTTPSPDYHeader.SCHEME.name(version), "http");
+        headers.put(HTTPSPDYHeader.HOST.name(version), "localhost:" + connector.getLocalPort());
+        headers.put("referer", referrer);
+        return headers;
+    }
+
+    private void sleep(long delay) throws ServletException
+    {
+        try
+        {
+            TimeUnit.MILLISECONDS.sleep(delay);
+        }
+        catch (InterruptedException x)
+        {
+            throw new ServletException(x);
+        }
+    }
+
+    private class PushStrategyBenchmarkHandler extends AbstractHandler
+    {
+        @Override
+        public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+        {
+            baseRequest.setHandled(true);
+
+            // Sleep half of the roundtrip time, to simulate the delay of responses, even for pushed resources
+            sleep(roundtrip / 2);
+            // If it's not a pushed resource, sleep half of the roundtrip time, to simulate the delay of requests
+            if (request.getHeader("x-spdy-push") == null)
+                sleep(roundtrip / 2);
+
+            String suffix = target.substring(target.indexOf('.') + 1);
+            int index = Integer.parseInt(target.substring(1, target.length() - suffix.length() - 1));
+
+            int contentLength;
+            String contentType;
+            switch (suffix)
+            {
+                case "html":
+                    contentLength = htmlResources[index];
+                    contentType = "text/html";
+                    break;
+                case "css":
+                    contentLength = cssResources[index];
+                    contentType = "text/css";
+                    break;
+                case "js":
+                    contentLength = jsResources[index];
+                    contentType = "text/javascript";
+                    break;
+                case "png":
+                    contentLength = pngResources[index];
+                    contentType = "image/png";
+                    break;
+                default:
+                    throw new ServletException();
+            }
+
+            response.setContentType(contentType);
+            response.setContentLength(contentLength);
+            response.getOutputStream().write(new byte[contentLength]);
+        }
+    }
+
+    private void addPushedResource(String pushedURI)
+    {
+        switch (version)
+        {
+            case SPDY.V2:
+            {
+                Matcher matcher = Pattern.compile("https?://[^:]+:\\d+(/.*)").matcher(pushedURI);
+                Assert.assertTrue(matcher.matches());
+                pushedResources.add(matcher.group(1));
+                break;
+            }
+            case SPDY.V3:
+            {
+                pushedResources.add(pushedURI);
+                break;
+            }
+            default:
+            {
+                throw new IllegalStateException();
+            }
+        }
+    }
+
+    private class ClientSessionFrameListener extends SessionFrameListener.Adapter
+    {
+        @Override
+        public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
+        {
+            String path = synInfo.getHeaders().get(HTTPSPDYHeader.URI.name(version)).getValue();
+            addPushedResource(path);
+            return new DataListener();
+        }
+    }
+
+    private class DataListener extends StreamFrameListener.Adapter
+    {
+        @Override
+        public void onData(Stream stream, DataInfo dataInfo)
+        {
+            dataInfo.consume(dataInfo.length());
+            if (dataInfo.isClose())
+                latch.get().countDown();
+        }
+    }
+
+    private class TestListener extends Response.Listener.Adapter
+    {
+        @Override
+        public void onComplete(Result result)
+        {
+            if (!result.isFailed())
+                latch.get().countDown();
+        }
+    }
+}
diff --git a/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/ReferrerPushStrategyTest.java b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/ReferrerPushStrategyTest.java
new file mode 100644
index 0000000..f50982a
--- /dev/null
+++ b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/ReferrerPushStrategyTest.java
@@ -0,0 +1,1138 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.spdy.server.http;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.core.IsEqual.equalTo;
+import static org.junit.Assert.assertThat;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.net.InetSocketAddress;
+import java.util.Arrays;
+import java.util.Random;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.ConnectionFactory;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.servlets.gzip.GzipHandler;
+import org.eclipse.jetty.spdy.api.DataInfo;
+import org.eclipse.jetty.spdy.api.HeadersInfo;
+import org.eclipse.jetty.spdy.api.PushInfo;
+import org.eclipse.jetty.spdy.api.ReplyInfo;
+import org.eclipse.jetty.spdy.api.RstInfo;
+import org.eclipse.jetty.spdy.api.SPDY;
+import org.eclipse.jetty.spdy.api.Session;
+import org.eclipse.jetty.spdy.api.SessionFrameListener;
+import org.eclipse.jetty.spdy.api.Settings;
+import org.eclipse.jetty.spdy.api.SettingsInfo;
+import org.eclipse.jetty.spdy.api.Stream;
+import org.eclipse.jetty.spdy.api.StreamFrameListener;
+import org.eclipse.jetty.spdy.api.StreamStatus;
+import org.eclipse.jetty.spdy.api.SynInfo;
+import org.eclipse.jetty.spdy.http.HTTPSPDYHeader;
+import org.eclipse.jetty.spdy.server.NPNServerConnectionFactory;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.Fields;
+import org.eclipse.jetty.util.Promise;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.log.StdErrLog;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class ReferrerPushStrategyTest extends AbstractHTTPSPDYTest
+{
+    private static final Logger LOG = Log.getLogger(ReferrerPushStrategyTest.class);
+
+    private final int referrerPushPeriod = 1000;
+    private final String mainResource = "/index.html";
+    private final String cssResource = "/style.css";
+    private InetSocketAddress serverAddress;
+    private ReferrerPushStrategy pushStrategy;
+    private ConnectionFactory defaultFactory;
+    private Fields mainRequestHeaders;
+    private Fields associatedCSSRequestHeaders;
+    private Fields associatedJSRequestHeaders;
+
+    public ReferrerPushStrategyTest(short version)
+    {
+        super(version);
+    }
+
+    @Override
+    protected HTTPSPDYServerConnector newHTTPSPDYServerConnector(short version)
+    {
+        return new HTTPSPDYServerConnector(server, version, new HttpConfiguration(), new ReferrerPushStrategy());
+    }
+
+    @Before
+    public void setUp() throws Exception
+    {
+        serverAddress = createServer();
+        pushStrategy = new ReferrerPushStrategy();
+        pushStrategy.setReferrerPushPeriod(referrerPushPeriod);
+        defaultFactory = new HTTPSPDYServerConnectionFactory(version, new HttpConfiguration(), pushStrategy);
+        connector.addConnectionFactory(defaultFactory);
+        if (connector.getConnectionFactory(NPNServerConnectionFactory.class) != null)
+            connector.getConnectionFactory(NPNServerConnectionFactory.class).setDefaultProtocol(defaultFactory.getProtocol());
+        else
+            connector.setDefaultProtocol(defaultFactory.getProtocol());
+        mainRequestHeaders = createHeadersWithoutReferrer(mainResource);
+        associatedCSSRequestHeaders = createHeaders(cssResource);
+        associatedJSRequestHeaders = createHeaders("/application.js");
+    }
+
+    @Test
+    public void testPushHeadersAreValid() throws Exception
+    {
+        sendMainRequestAndCSSRequest(null, false);
+        run2ndClientRequests(true, true);
+    }
+
+    @Test
+    public void testClientResetsPushStreams() throws Exception
+    {
+        ((StdErrLog)Log.getLogger("org.eclipse.jetty.server.HttpChannel")).setHideStacks(true);
+        sendMainRequestAndCSSRequest(null, false);
+        final CountDownLatch pushDataLatch = new CountDownLatch(1);
+        final CountDownLatch pushSynHeadersValid = new CountDownLatch(1);
+        Session session = startClient(version, serverAddress, null);
+        // Send main request. That should initiate the push push's which get reset by the client
+        sendRequest(session, mainRequestHeaders, pushSynHeadersValid, pushDataLatch, true);
+
+        assertThat("No push data is received", pushDataLatch.await(1, TimeUnit.SECONDS), is(false));
+        assertThat("Push push headers valid", pushSynHeadersValid.await(5, TimeUnit.SECONDS), is(true));
+
+        sendRequest(session, associatedCSSRequestHeaders, pushSynHeadersValid, pushDataLatch, true);
+        ((StdErrLog)Log.getLogger("org.eclipse.jetty.server.HttpChannel")).setHideStacks(false);
+    }
+
+    @Test
+    public void testUserAgentBlackList() throws Exception
+    {
+        pushStrategy.setUserAgentBlacklist(Arrays.asList(".*(?i)firefox/16.*"));
+        sendMainRequestAndCSSRequest(null, false);
+        run2ndClientRequests(false, false);
+    }
+
+    @Test
+    public void testReferrerPushPeriod() throws Exception
+    {
+        Session session = sendMainRequestAndCSSRequest(null, false);
+
+        // Sleep for pushPeriod This should prevent application.js from being mapped as pushResource
+        Thread.sleep(referrerPushPeriod + 1);
+        sendRequest(session, associatedJSRequestHeaders, null, null, false);
+
+        run2ndClientRequests(false, true);
+    }
+
+    @Test
+    public void testMaxAssociatedResources() throws Exception
+    {
+        pushStrategy.setMaxAssociatedResources(1);
+        Session session = sendMainRequestAndCSSRequest(null, false);
+        sendRequest(session, associatedJSRequestHeaders, null, null, false);
+
+        final CountDownLatch mainStreamLatch = new CountDownLatch(2);
+        final CountDownLatch pushDataLatch = new CountDownLatch(2);
+        final CountDownLatch pushSynHeadersValid = new CountDownLatch(1);
+        final CountDownLatch pushResponseHeaders = new CountDownLatch(1);
+        Session session2 = startClient(version, serverAddress, null);
+        session2.syn(new SynInfo(mainRequestHeaders, true), new StreamFrameListener.Adapter()
+        {
+            @Override
+            public StreamFrameListener onPush(Stream stream, PushInfo pushInfo)
+            {
+                validateHeaders(pushInfo.getHeaders(), pushSynHeadersValid);
+
+                assertThat("Stream is unidirectional", stream.isUnidirectional(), is(true));
+                assertThat("URI header ends with css", pushInfo.getHeaders().get(HTTPSPDYHeader.URI.name(version))
+                        .getValue().endsWith
+                                ("" +
+                                        ".css"),
+                        is(true));
+                return new StreamFrameListener.Adapter()
+                {
+                    @Override
+                    public void onHeaders(Stream stream, HeadersInfo headersInfo)
+                    {
+                        Fields headers = headersInfo.getHeaders();
+                        if (validateHeader(headers, HTTPSPDYHeader.STATUS.name(version), "200 OK")
+                                && validateHeader(headers, HTTPSPDYHeader.VERSION.name(version),
+                                "HTTP/1.1") && validateHeader(headers, "content-encoding", "gzip"))
+                            pushResponseHeaders.countDown();
+                    }
+
+                    @Override
+                    public void onData(Stream stream, DataInfo dataInfo)
+                    {
+                        dataInfo.consume(dataInfo.length());
+                        if (dataInfo.isClose())
+                            pushDataLatch.countDown();
+                    }
+                };
+            }
+
+            @Override
+            public void onReply(Stream stream, ReplyInfo replyInfo)
+            {
+                assertThat("replyInfo.isClose() is false", replyInfo.isClose(), is(false));
+                mainStreamLatch.countDown();
+            }
+
+            @Override
+            public void onData(Stream stream, DataInfo dataInfo)
+            {
+                dataInfo.consume(dataInfo.length());
+                if (dataInfo.isClose())
+                    mainStreamLatch.countDown();
+            }
+        });
+
+        assertThat("Main request reply and/or data received", mainStreamLatch.await(5, TimeUnit.SECONDS), is(true));
+        assertThat("Not more than one push is received", pushDataLatch.await(1, TimeUnit.SECONDS), is(false));
+        assertThat("Push push headers valid", pushSynHeadersValid.await(5, TimeUnit.SECONDS), is(true));
+        assertThat("Push response headers are valid", pushResponseHeaders.await(5, TimeUnit.SECONDS), is(true));
+    }
+
+    @Test
+    public void testMaxConcurrentStreamsToDisablePush() throws Exception
+    {
+        final CountDownLatch pushReceivedLatch = new CountDownLatch(1);
+
+        Session pushCacheBuildSession = startClient(version, serverAddress, null);
+
+        pushCacheBuildSession.syn(new SynInfo(mainRequestHeaders, true), new StreamFrameListener.Adapter());
+        pushCacheBuildSession.syn(new SynInfo(associatedCSSRequestHeaders, true), new StreamFrameListener.Adapter());
+
+        Session session = startClient(version, serverAddress, null);
+
+        Settings settings = new Settings();
+        settings.put(new Settings.Setting(Settings.ID.MAX_CONCURRENT_STREAMS, 0));
+        SettingsInfo settingsInfo = new SettingsInfo(settings);
+        session.settings(settingsInfo);
+
+        ((StdErrLog)Log.getLogger("org.eclipse.jetty.spdy.server.http" +
+                        ".HttpTransportOverSPDY$PushResourceCoordinator$1")).setHideStacks(true);
+        session.syn(new SynInfo(mainRequestHeaders, true), new StreamFrameListener.Adapter()
+        {
+            @Override
+            public StreamFrameListener onPush(Stream stream, PushInfo pushInfo)
+            {
+                pushReceivedLatch.countDown();
+                return super.onPush(stream, pushInfo);
+            }
+        });
+
+        assertThat("No push stream is received", pushReceivedLatch.await(1, TimeUnit.SECONDS), is(false));
+        ((StdErrLog)Log.getLogger("org.eclipse.jetty.spdy.server.http" +
+                                ".HttpTransportOverSPDY$PushResourceCoordinator$1")).setHideStacks(false);
+    }
+
+    @Test
+    public void testPushResourceOrder() throws Exception
+    {
+        final CountDownLatch allExpectedPushesReceivedLatch = new CountDownLatch(4);
+        final CountDownLatch allPushDataReceivedLatch = new CountDownLatch(4);
+
+        Session pushCacheBuildSession = startClient(version, serverAddress, null);
+
+        sendRequest(pushCacheBuildSession, mainRequestHeaders, null, null, false);
+        sendRequest(pushCacheBuildSession, associatedCSSRequestHeaders, null, null, false);
+        sendRequest(pushCacheBuildSession, associatedJSRequestHeaders, null, null, false);
+        sendRequest(pushCacheBuildSession, createHeaders("/image1.jpg", mainResource), null, null, false);
+        sendRequest(pushCacheBuildSession, createHeaders("/image2.jpg", mainResource), null, null, false);
+
+        Session session = startClient(version, serverAddress, null);
+
+        session.syn(new SynInfo(mainRequestHeaders, true), new StreamFrameListener.Adapter()
+        {
+            @Override
+            public StreamFrameListener onPush(Stream stream, PushInfo pushInfo)
+            {
+                LOG.info("onPush: stream: {}, pushInfo: {}", stream, pushInfo);
+                String uriHeader = pushInfo.getHeaders().get(HTTPSPDYHeader.URI.name(version)).getValue();
+                switch ((int)allExpectedPushesReceivedLatch.getCount())
+                {
+                    case 4:
+                        assertThat("1st pushed resource is the css", uriHeader.endsWith("css"), is(true));
+                        break;
+                    case 3:
+                        assertThat("2nd pushed resource is the js", uriHeader.endsWith("js"), is(true));
+                        break;
+                    case 2:
+                        assertThat("3rd pushed resource is image1", uriHeader.endsWith("image1.jpg"),
+                                is(true));
+                        break;
+                    case 1:
+                        assertThat("4th pushed resource is image2", uriHeader.endsWith("image2.jpg"),
+                                is(true));
+                        break;
+                }
+                allExpectedPushesReceivedLatch.countDown();
+                return new Adapter()
+                {
+                    @Override
+                    public void onData(Stream stream, DataInfo dataInfo)
+                    {
+                        if(dataInfo.isClose())
+                            allPushDataReceivedLatch.countDown();
+                    }
+                };
+            }
+        });
+
+        assertThat("All expected push resources have been received", allExpectedPushesReceivedLatch.await(5,
+                TimeUnit.SECONDS), is(true));
+        assertThat("All push data has been fully received", allPushDataReceivedLatch.await(5, TimeUnit.SECONDS),
+                is(true));
+    }
+
+    @Test
+    public void testThatPushResourcesAreUnique() throws Exception
+    {
+        final CountDownLatch pushReceivedLatch = new CountDownLatch(2);
+        sendMainRequestAndCSSRequest(null, false);
+        sendMainRequestAndCSSRequest(null, false);
+
+        Session session = startClient(version, serverAddress, null);
+
+        session.syn(new SynInfo(mainRequestHeaders, true), new StreamFrameListener.Adapter()
+        {
+            @Override
+            public StreamFrameListener onPush(Stream stream, PushInfo pushInfo)
+            {
+                pushReceivedLatch.countDown();
+                LOG.info("Push received: {}", pushInfo);
+                return null;
+            }
+        });
+
+        assertThat("style.css has been pushed only once", pushReceivedLatch.await(1, TimeUnit.SECONDS), is(false));
+    }
+
+    @Test
+    public void testPushResourceAreSentNonInterleaved() throws Exception
+    {
+        final CountDownLatch allExpectedPushesReceivedLatch = new CountDownLatch(4);
+        final CountDownLatch allPushDataReceivedLatch = new CountDownLatch(4);
+        final CopyOnWriteArrayList<Integer> dataReceivedOrder = new CopyOnWriteArrayList<>();
+
+        InetSocketAddress bigResponseServerAddress = startHTTPServer(version, new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                byte[] bytes = new byte[32768];
+                new Random().nextBytes(bytes);
+                ServletOutputStream outputStream = response.getOutputStream();
+                outputStream.write(bytes);
+                baseRequest.setHandled(true);
+            }
+        }, 30000);
+        Session pushCacheBuildSession = startClient(version, bigResponseServerAddress, null);
+
+        Fields mainResourceHeaders = createHeadersWithoutReferrer(mainResource);
+        sendRequest(pushCacheBuildSession, mainResourceHeaders, null, null, false);
+        sendRequest(pushCacheBuildSession, createHeaders("/style.css", mainResource), null, null, false);
+        sendRequest(pushCacheBuildSession, createHeaders("/javascript.js", mainResource), null, null, false);
+        sendRequest(pushCacheBuildSession, createHeaders("/image1.jpg", mainResource), null, null, false);
+        sendRequest(pushCacheBuildSession, createHeaders("/image2.jpg", mainResource), null, null, false);
+
+        Session session = startClient(version, bigResponseServerAddress, null);
+
+        session.syn(new SynInfo(mainResourceHeaders, true), new StreamFrameListener.Adapter()
+        {
+            AtomicInteger currentStreamId = new AtomicInteger(2);
+
+            @Override
+            public StreamFrameListener onPush(Stream stream, PushInfo pushInfo)
+            {
+                LOG.info("Received push for stream: {} {}", stream.getId(), pushInfo);
+                String uriHeader = pushInfo.getHeaders().get(HTTPSPDYHeader.URI.name(version)).getValue();
+                switch ((int)allExpectedPushesReceivedLatch.getCount())
+                {
+                    case 4:
+                        assertThat("1st pushed resource is the css", uriHeader.endsWith("css"), is(true));
+                        break;
+                    case 3:
+                        assertThat("2nd pushed resource is the js", uriHeader.endsWith("js"), is(true));
+                        break;
+                    case 2:
+                        assertThat("3rd pushed resource is image1", uriHeader.endsWith("image1.jpg"),
+                                is(true));
+                        break;
+                    case 1:
+                        assertThat("4th pushed resource is image2", uriHeader.endsWith("image2.jpg"),
+                                is(true));
+                        break;
+                }
+                allExpectedPushesReceivedLatch.countDown();
+                return new Adapter()
+                {
+                    @Override
+                    public void onData(Stream stream, DataInfo dataInfo)
+                    {
+                        if (stream.getId() != currentStreamId.get())
+                            throw new IllegalStateException("Streams interleaved. Expected StreamId: " +
+                                    currentStreamId + " but was: " + stream.getId());
+                        dataInfo.consume(dataInfo.available());
+                        if (dataInfo.isClose())
+                        {
+                            currentStreamId.compareAndSet(currentStreamId.get(), currentStreamId.get() + 2);
+                            dataReceivedOrder.add(stream.getId());
+                            allPushDataReceivedLatch.countDown();
+                        }
+                        LOG.info(stream.getId() + ":" + dataInfo);
+                    }
+                };
+            }
+        });
+
+        assertThat("All push resources received", allExpectedPushesReceivedLatch.await(5, TimeUnit.SECONDS), is(true));
+        assertThat("All pushData received", allPushDataReceivedLatch.await(5, TimeUnit.SECONDS), is(true));
+        assertThat("The data for different push streams has not been interleaved",
+                dataReceivedOrder.toString(), equalTo("[2, 4, 6, 8]"));
+        LOG.info(dataReceivedOrder.toString());
+    }
+
+    private InetSocketAddress createServer() throws Exception
+    {
+        GzipHandler gzipHandler = new GzipHandler();
+        gzipHandler.setHandler(new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                String url = request.getRequestURI();
+                PrintWriter output = response.getWriter();
+                if (url.endsWith(".html"))
+                    output.print("<html><head/><body>HELLO</body></html>");
+                else if (url.endsWith(".css"))
+                    output.print("body { background: #FFF; }");
+                else if (url.endsWith(".js"))
+                    output.print("function(){}();");
+                baseRequest.setHandled(true);
+            }
+        });
+        return startHTTPServer(version, gzipHandler, 30000);
+    }
+
+    private Session sendMainRequestAndCSSRequest(SessionFrameListener sessionFrameListener, boolean awaitPush) throws Exception
+    {
+        Session session = startClient(version, serverAddress, sessionFrameListener);
+
+        CountDownLatch pushDataLatch = new CountDownLatch(2);
+        sendRequest(session, mainRequestHeaders, null, pushDataLatch, false);
+        sendRequest(session, associatedCSSRequestHeaders, null, pushDataLatch, false);
+        if (awaitPush)
+            assertThat("pushes have been received", pushDataLatch.await(5, TimeUnit.SECONDS), is(true));
+
+        return session;
+    }
+
+    private void sendRequest(Session session, Fields requestHeaders, final CountDownLatch pushSynHeadersValid,
+                             final CountDownLatch pushDataLatch, final boolean resetPush) throws InterruptedException
+    {
+        LOG.info("sendRequest. headers={},resetPush={}", requestHeaders, resetPush);
+        final CountDownLatch dataReceivedLatch = new CountDownLatch(1);
+        session.syn(new SynInfo(requestHeaders, true), new StreamFrameListener.Adapter()
+        {
+            @Override
+            public StreamFrameListener onPush(Stream stream, PushInfo pushInfo)
+            {
+                if (pushSynHeadersValid != null)
+                    validateHeaders(pushInfo.getHeaders(), pushSynHeadersValid);
+
+                assertThat("Stream is unidirectional", stream.isUnidirectional(), is(true));
+                assertThat("URI header ends with css", pushInfo.getHeaders().get(HTTPSPDYHeader.URI.name(version))
+                        .getValue().endsWith
+                                ("" +
+                                        ".css"),
+                        is(true));
+                if (resetPush)
+                    stream.getSession().rst(new RstInfo(stream.getId(), StreamStatus.REFUSED_STREAM), new Callback.Adapter());
+                return new StreamFrameListener.Adapter()
+                {
+
+                    @Override
+                    public void onData(Stream stream, DataInfo dataInfo)
+                    {
+                        dataInfo.consume(dataInfo.length());
+                        pushDataLatch.countDown();
+                    }
+                };
+            }
+
+            @Override
+            public void onReply(Stream stream, ReplyInfo replyInfo)
+            {
+                assertThat(replyInfo.getHeaders().get(HTTPSPDYHeader.STATUS.name(version)).getValue(), is("200 OK"));
+            }
+
+            @Override
+            public void onData(Stream stream, DataInfo dataInfo)
+            {
+                dataInfo.consume(dataInfo.length());
+                if (dataInfo.isClose())
+                    dataReceivedLatch.countDown();
+            }
+        }, new Promise.Adapter<Stream>());
+        assertThat(dataReceivedLatch.await(5, TimeUnit.SECONDS), is(true));
+    }
+
+    private void run2ndClientRequests(final boolean validateHeaders,
+                                      boolean expectPushResource) throws Exception
+    {
+        final CountDownLatch mainStreamLatch = new CountDownLatch(2);
+        final CountDownLatch pushDataLatch = new CountDownLatch(1);
+        final CountDownLatch pushSynHeadersValid = new CountDownLatch(1);
+        final CountDownLatch pushResponseHeaders = new CountDownLatch(1);
+        Session session2 = startClient(version, serverAddress, null);
+        session2.syn(new SynInfo(mainRequestHeaders, true), new StreamFrameListener.Adapter()
+        {
+            @Override
+            public StreamFrameListener onPush(Stream stream, PushInfo pushInfo)
+            {
+                if (validateHeaders)
+                    validateHeaders(pushInfo.getHeaders(), pushSynHeadersValid);
+
+                assertThat("Stream is unidirectional", stream.isUnidirectional(), is(true));
+                assertThat("URI header ends with css", pushInfo.getHeaders().get(HTTPSPDYHeader.URI.name(version))
+                        .getValue().endsWith
+                                ("" +
+                                        ".css"),
+                        is(true));
+                return new StreamFrameListener.Adapter()
+                {
+                    @Override
+                    public void onHeaders(Stream stream, HeadersInfo headersInfo)
+                    {
+                        Fields headers = headersInfo.getHeaders();
+                        if (validateHeader(headers, HTTPSPDYHeader.STATUS.name(version), "200 OK")
+                                && validateHeader(headers, HTTPSPDYHeader.VERSION.name(version),
+                                "HTTP/1.1") && validateHeader(headers, "content-encoding", "gzip"))
+                            pushResponseHeaders.countDown();
+                    }
+
+                    @Override
+                    public void onData(Stream stream, DataInfo dataInfo)
+                    {
+                        dataInfo.consume(dataInfo.length());
+                        if (dataInfo.isClose())
+                            pushDataLatch.countDown();
+                    }
+                };
+            }
+
+            @Override
+            public void onReply(Stream stream, ReplyInfo replyInfo)
+            {
+                assertThat("replyInfo.isClose() is false", replyInfo.isClose(), is(false));
+                mainStreamLatch.countDown();
+            }
+
+            @Override
+            public void onData(Stream stream, DataInfo dataInfo)
+            {
+                dataInfo.consume(dataInfo.length());
+                if (dataInfo.isClose())
+                    mainStreamLatch.countDown();
+            }
+        });
+
+        assertThat("Main request reply and/or data received", mainStreamLatch.await(5, TimeUnit.SECONDS), is(true));
+        if (expectPushResource)
+            assertThat("Pushed data received", pushDataLatch.await(5, TimeUnit.SECONDS), is(true));
+        else
+            assertThat("No push data is received", pushDataLatch.await(1, TimeUnit.SECONDS), is(false));
+        if (validateHeaders)
+        {
+            assertThat("Push push headers valid", pushSynHeadersValid.await(5, TimeUnit.SECONDS), is(true));
+            assertThat("Push response headers are valid", pushResponseHeaders.await(5, TimeUnit.SECONDS), is(true));
+        }
+    }
+
+    @Test
+    public void testAssociatedResourceIsPushed() throws Exception
+    {
+        InetSocketAddress address = startHTTPServer(version, new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                String url = request.getRequestURI();
+                PrintWriter output = response.getWriter();
+                if (url.endsWith(".html"))
+                    output.print("<html><head/><body>HELLO</body></html>");
+                else if (url.endsWith(".css"))
+                    output.print("body { background: #FFF; }");
+                baseRequest.setHandled(true);
+            }
+        }, 30000);
+        Session session1 = startClient(version, address, null);
+
+        final CountDownLatch mainResourceLatch = new CountDownLatch(1);
+        Fields mainRequestHeaders = createHeadersWithoutReferrer(mainResource);
+
+        session1.syn(new SynInfo(mainRequestHeaders, true), new StreamFrameListener.Adapter()
+        {
+            @Override
+            public void onData(Stream stream, DataInfo dataInfo)
+            {
+                dataInfo.consume(dataInfo.length());
+                if (dataInfo.isClose())
+                    mainResourceLatch.countDown();
+            }
+        });
+        Assert.assertTrue(mainResourceLatch.await(5, TimeUnit.SECONDS));
+
+        sendRequest(session1, createHeaders(cssResource), null, null, false);
+
+        // Create another client, and perform the same request for the main resource, we expect the css being pushed
+
+        final CountDownLatch mainStreamLatch = new CountDownLatch(2);
+        final CountDownLatch pushDataLatch = new CountDownLatch(1);
+        Session session2 = startClient(version, address, null);
+        session2.syn(new SynInfo(mainRequestHeaders, true), new StreamFrameListener.Adapter()
+        {
+            @Override
+            public StreamFrameListener onPush(Stream stream, PushInfo pushInfo)
+            {
+                Assert.assertTrue(stream.isUnidirectional());
+                return new StreamFrameListener.Adapter()
+                {
+                    @Override
+                    public void onData(Stream stream, DataInfo dataInfo)
+                    {
+                        dataInfo.consume(dataInfo.length());
+                        if (dataInfo.isClose())
+                            pushDataLatch.countDown();
+                    }
+                };
+            }
+
+            @Override
+            public void onReply(Stream stream, ReplyInfo replyInfo)
+            {
+                Assert.assertFalse(replyInfo.isClose());
+                mainStreamLatch.countDown();
+            }
+
+            @Override
+            public void onData(Stream stream, DataInfo dataInfo)
+            {
+                dataInfo.consume(dataInfo.length());
+                if (dataInfo.isClose())
+                    mainStreamLatch.countDown();
+            }
+        });
+
+        Assert.assertTrue(mainStreamLatch.await(5, TimeUnit.SECONDS));
+        Assert.assertTrue(pushDataLatch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testAssociatedResourceWithWrongContentTypeIsNotPushed() throws Exception
+    {
+        final String fakeResource = "/fake.png";
+        InetSocketAddress address = startHTTPServer(version, new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                String url = request.getRequestURI();
+                PrintWriter output = response.getWriter();
+                if (url.endsWith(".html"))
+                {
+                    response.setContentType("text/html");
+                    output.print("<html><head/><body>HELLO</body></html>");
+                }
+                else if (url.equals(fakeResource))
+                {
+                    response.setContentType("text/html");
+                    output.print("<html><head/><body>IMAGE</body></html>");
+                }
+                else if (url.endsWith(".css"))
+                {
+                    response.setContentType("text/css");
+                    output.print("body { background: #FFF; }");
+                }
+                baseRequest.setHandled(true);
+            }
+        }, 30000);
+        Session session1 = startClient(version, address, null);
+
+        final CountDownLatch mainResourceLatch = new CountDownLatch(1);
+        Fields mainRequestHeaders = createHeadersWithoutReferrer(mainResource);
+
+        session1.syn(new SynInfo(mainRequestHeaders, true), new StreamFrameListener.Adapter()
+        {
+            @Override
+            public void onData(Stream stream, DataInfo dataInfo)
+            {
+                dataInfo.consume(dataInfo.length());
+                if (dataInfo.isClose())
+                    mainResourceLatch.countDown();
+            }
+        });
+        Assert.assertTrue(mainResourceLatch.await(5, TimeUnit.SECONDS));
+
+        final CountDownLatch associatedResourceLatch = new CountDownLatch(1);
+        String cssResource = "/stylesheet.css";
+        Fields associatedRequestHeaders = createHeaders(cssResource);
+        session1.syn(new SynInfo(associatedRequestHeaders, true), new StreamFrameListener.Adapter()
+        {
+            @Override
+            public void onData(Stream stream, DataInfo dataInfo)
+            {
+                dataInfo.consume(dataInfo.length());
+                if (dataInfo.isClose())
+                    associatedResourceLatch.countDown();
+            }
+        });
+        Assert.assertTrue(associatedResourceLatch.await(5, TimeUnit.SECONDS));
+
+        final CountDownLatch fakeAssociatedResourceLatch = new CountDownLatch(1);
+        Fields fakeAssociatedRequestHeaders = createHeaders(fakeResource);
+        session1.syn(new SynInfo(fakeAssociatedRequestHeaders, true), new StreamFrameListener.Adapter()
+        {
+            @Override
+            public void onData(Stream stream, DataInfo dataInfo)
+            {
+                dataInfo.consume(dataInfo.length());
+                if (dataInfo.isClose())
+                    fakeAssociatedResourceLatch.countDown();
+            }
+        });
+        Assert.assertTrue(fakeAssociatedResourceLatch.await(5, TimeUnit.SECONDS));
+
+        // Create another client, and perform the same request for the main resource,
+        // we expect the css being pushed but not the fake PNG
+
+        final CountDownLatch mainStreamLatch = new CountDownLatch(2);
+        final CountDownLatch pushDataLatch = new CountDownLatch(1);
+        Session session2 = startClient(version, address, null);
+        session2.syn(new SynInfo(mainRequestHeaders, true), new StreamFrameListener.Adapter()
+        {
+            @Override
+            public StreamFrameListener onPush(Stream stream, PushInfo pushInfo)
+            {
+                Assert.assertTrue(stream.isUnidirectional());
+                Assert.assertTrue(pushInfo.getHeaders().get(HTTPSPDYHeader.URI.name(version)).getValue().endsWith("" +
+                        ".css"));
+                return new StreamFrameListener.Adapter()
+                {
+                    @Override
+                    public void onData(Stream stream, DataInfo dataInfo)
+                    {
+                        dataInfo.consume(dataInfo.length());
+                        if (dataInfo.isClose())
+                            pushDataLatch.countDown();
+                    }
+                };
+            }
+
+            @Override
+            public void onReply(Stream stream, ReplyInfo replyInfo)
+            {
+                Assert.assertFalse(replyInfo.isClose());
+                mainStreamLatch.countDown();
+            }
+
+            @Override
+            public void onData(Stream stream, DataInfo dataInfo)
+            {
+                dataInfo.consume(dataInfo.length());
+                if (dataInfo.isClose())
+                    mainStreamLatch.countDown();
+            }
+        });
+
+        Assert.assertTrue(mainStreamLatch.await(5, TimeUnit.SECONDS));
+        Assert.assertTrue(pushDataLatch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testNestedAssociatedResourceIsPushed() throws Exception
+    {
+        InetSocketAddress address = startHTTPServer(version, new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                String url = request.getRequestURI();
+                PrintWriter output = response.getWriter();
+                if (url.endsWith(".html"))
+                    output.print("<html><head/><body>HELLO</body></html>");
+                else if (url.endsWith(".css"))
+                    output.print("body { background: #FFF; }");
+                else if (url.endsWith(".gif"))
+                    output.print("\u0000");
+                baseRequest.setHandled(true);
+            }
+        }, 30000);
+        Session session1 = startClient(version, address, null);
+
+        final CountDownLatch mainResourceLatch = new CountDownLatch(1);
+        Fields mainRequestHeaders = createHeadersWithoutReferrer(mainResource);
+
+        session1.syn(new SynInfo(mainRequestHeaders, true), new StreamFrameListener.Adapter()
+        {
+            @Override
+            public void onData(Stream stream, DataInfo dataInfo)
+            {
+                dataInfo.consume(dataInfo.length());
+                if (dataInfo.isClose())
+                    mainResourceLatch.countDown();
+            }
+        });
+        Assert.assertTrue(mainResourceLatch.await(5, TimeUnit.SECONDS));
+
+        final CountDownLatch associatedResourceLatch = new CountDownLatch(1);
+        Fields associatedRequestHeaders = createHeaders(cssResource);
+        session1.syn(new SynInfo(associatedRequestHeaders, true), new StreamFrameListener.Adapter()
+        {
+            @Override
+            public void onData(Stream stream, DataInfo dataInfo)
+            {
+                dataInfo.consume(dataInfo.length());
+                if (dataInfo.isClose())
+                    associatedResourceLatch.countDown();
+            }
+        });
+        Assert.assertTrue(associatedResourceLatch.await(5, TimeUnit.SECONDS));
+
+        final CountDownLatch nestedResourceLatch = new CountDownLatch(1);
+        String imageUrl = "/image.gif";
+        Fields nestedRequestHeaders = createHeaders(imageUrl, cssResource);
+
+        session1.syn(new SynInfo(nestedRequestHeaders, true), new StreamFrameListener.Adapter()
+        {
+            @Override
+            public void onData(Stream stream, DataInfo dataInfo)
+            {
+                dataInfo.consume(dataInfo.length());
+                if (dataInfo.isClose())
+                    nestedResourceLatch.countDown();
+            }
+        });
+        Assert.assertTrue(nestedResourceLatch.await(5, TimeUnit.SECONDS));
+
+        // Create another client, and perform the same request for the main resource, we expect the css and the image being pushed
+
+        final CountDownLatch mainStreamLatch = new CountDownLatch(2);
+        final CountDownLatch pushDataLatch = new CountDownLatch(2);
+        Session session2 = startClient(version, address, null);
+        session2.syn(new SynInfo(mainRequestHeaders, true), new StreamFrameListener.Adapter()
+        {
+            @Override
+            public StreamFrameListener onPush(Stream stream, PushInfo pushInfo)
+            {
+                Assert.assertTrue(stream.isUnidirectional());
+                return new StreamFrameListener.Adapter()
+                {
+                    @Override
+                    public StreamFrameListener onPush(Stream stream, PushInfo pushInfo)
+                    {
+                        return new Adapter()
+                        {
+                            @Override
+                            public void onData(Stream stream, DataInfo dataInfo)
+                            {
+                                dataInfo.consume(dataInfo.length());
+                                if (dataInfo.isClose())
+                                    pushDataLatch.countDown();
+                            }
+                        };
+                    }
+
+                    @Override
+                    public void onData(Stream stream, DataInfo dataInfo)
+                    {
+                        dataInfo.consume(dataInfo.length());
+                        if (dataInfo.isClose())
+                            pushDataLatch.countDown();
+                    }
+                };
+            }
+
+            @Override
+            public void onReply(Stream stream, ReplyInfo replyInfo)
+            {
+                Assert.assertFalse(replyInfo.isClose());
+                mainStreamLatch.countDown();
+            }
+
+            @Override
+            public void onData(Stream stream, DataInfo dataInfo)
+            {
+                dataInfo.consume(dataInfo.length());
+                if (dataInfo.isClose())
+                    mainStreamLatch.countDown();
+            }
+        });
+
+        Assert.assertTrue(mainStreamLatch.await(5, TimeUnit.SECONDS));
+        Assert.assertTrue(pushDataLatch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testMainResourceWithReferrerIsNotPushed() throws Exception
+    {
+        InetSocketAddress address = startHTTPServer(version, new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                String url = request.getRequestURI();
+                PrintWriter output = response.getWriter();
+                if (url.endsWith(".html"))
+                    output.print("<html><head/><body>HELLO</body></html>");
+                baseRequest.setHandled(true);
+            }
+        }, 30000);
+        Session session1 = startClient(version, address, null);
+
+        final CountDownLatch mainResourceLatch = new CountDownLatch(1);
+        Fields mainRequestHeaders = createHeadersWithoutReferrer(mainResource);
+
+        session1.syn(new SynInfo(mainRequestHeaders, true), new StreamFrameListener.Adapter()
+        {
+            @Override
+            public void onData(Stream stream, DataInfo dataInfo)
+            {
+                dataInfo.consume(dataInfo.length());
+                if (dataInfo.isClose())
+                    mainResourceLatch.countDown();
+            }
+        });
+        Assert.assertTrue(mainResourceLatch.await(5, TimeUnit.SECONDS));
+
+        final CountDownLatch associatedResourceLatch = new CountDownLatch(1);
+        String associatedResource = "/home.html";
+        Fields associatedRequestHeaders = createHeaders(associatedResource);
+
+        session1.syn(new SynInfo(associatedRequestHeaders, true), new StreamFrameListener.Adapter()
+        {
+            @Override
+            public void onData(Stream stream, DataInfo dataInfo)
+            {
+                dataInfo.consume(dataInfo.length());
+                if (dataInfo.isClose())
+                    associatedResourceLatch.countDown();
+            }
+        });
+        Assert.assertTrue(associatedResourceLatch.await(5, TimeUnit.SECONDS));
+
+        // Create another client, and perform the same request for the main resource, we expect nothing being pushed
+
+        final CountDownLatch mainStreamLatch = new CountDownLatch(2);
+        final CountDownLatch pushLatch = new CountDownLatch(1);
+        Session session2 = startClient(version, address, new SessionFrameListener.Adapter()
+        {
+            @Override
+            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
+            {
+                pushLatch.countDown();
+                return null;
+            }
+        });
+        session2.syn(new SynInfo(mainRequestHeaders, true), new StreamFrameListener.Adapter()
+        {
+            @Override
+            public void onReply(Stream stream, ReplyInfo replyInfo)
+            {
+                Assert.assertFalse(replyInfo.isClose());
+                mainStreamLatch.countDown();
+            }
+
+            @Override
+            public void onData(Stream stream, DataInfo dataInfo)
+            {
+                dataInfo.consume(dataInfo.length());
+                if (dataInfo.isClose())
+                    mainStreamLatch.countDown();
+            }
+        });
+
+        Assert.assertTrue(mainStreamLatch.await(5, TimeUnit.SECONDS));
+        Assert.assertFalse(pushLatch.await(1, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testRequestWithIfModifiedSinceHeaderPreventsPush() throws Exception
+    {
+        InetSocketAddress address = startHTTPServer(version, new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                String url = request.getRequestURI();
+                PrintWriter output = response.getWriter();
+                if (url.endsWith(".html"))
+                    output.print("<html><head/><body>HELLO</body></html>");
+                else if (url.endsWith(".css"))
+                    output.print("body { background: #FFF; }");
+                baseRequest.setHandled(true);
+            }
+        }, 30000);
+        Session session1 = startClient(version, address, null);
+
+        final CountDownLatch mainResourceLatch = new CountDownLatch(1);
+        Fields mainRequestHeaders = createHeaders(mainResource);
+        mainRequestHeaders.put("If-Modified-Since", "Tue, 27 Mar 2012 16:36:52 GMT");
+        session1.syn(new SynInfo(mainRequestHeaders, true), new StreamFrameListener.Adapter()
+        {
+            @Override
+            public void onData(Stream stream, DataInfo dataInfo)
+            {
+                dataInfo.consume(dataInfo.length());
+                if (dataInfo.isClose())
+                    mainResourceLatch.countDown();
+            }
+        });
+        Assert.assertTrue(mainResourceLatch.await(5, TimeUnit.SECONDS));
+
+        final CountDownLatch associatedResourceLatch = new CountDownLatch(1);
+        Fields associatedRequestHeaders = createHeaders(cssResource);
+        session1.syn(new SynInfo(associatedRequestHeaders, true), new StreamFrameListener.Adapter()
+        {
+            @Override
+            public void onData(Stream stream, DataInfo dataInfo)
+            {
+                dataInfo.consume(dataInfo.length());
+                if (dataInfo.isClose())
+                    associatedResourceLatch.countDown();
+            }
+        });
+        Assert.assertTrue(associatedResourceLatch.await(5, TimeUnit.SECONDS));
+
+        // Create another client, and perform the same request for the main resource, we expect the css NOT being pushed as the main request contains an
+        // if-modified-since header
+
+        final CountDownLatch mainStreamLatch = new CountDownLatch(2);
+        final CountDownLatch pushDataLatch = new CountDownLatch(1);
+        Session session2 = startClient(version, address, new SessionFrameListener.Adapter()
+        {
+            @Override
+            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
+            {
+                Assert.assertTrue(stream.isUnidirectional());
+                return new StreamFrameListener.Adapter()
+                {
+                    @Override
+                    public void onData(Stream stream, DataInfo dataInfo)
+                    {
+                        dataInfo.consume(dataInfo.length());
+                        if (dataInfo.isClose())
+                            pushDataLatch.countDown();
+                    }
+                };
+            }
+        });
+        session2.syn(new SynInfo(mainRequestHeaders, true), new StreamFrameListener.Adapter()
+        {
+            @Override
+            public void onReply(Stream stream, ReplyInfo replyInfo)
+            {
+                Assert.assertFalse(replyInfo.isClose());
+                mainStreamLatch.countDown();
+            }
+
+            @Override
+            public void onData(Stream stream, DataInfo dataInfo)
+            {
+                dataInfo.consume(dataInfo.length());
+                if (dataInfo.isClose())
+                    mainStreamLatch.countDown();
+            }
+        });
+
+        Assert.assertTrue(mainStreamLatch.await(5, TimeUnit.SECONDS));
+        Assert.assertFalse("We don't expect data to be pushed as the main request contained an if-modified-since header", pushDataLatch.await(1, TimeUnit.SECONDS));
+    }
+
+    private void validateHeaders(Fields headers, CountDownLatch pushSynHeadersValid)
+    {
+        if (validateUriHeader(headers))
+            pushSynHeadersValid.countDown();
+    }
+
+    private boolean validateHeader(Fields headers, String name, String expectedValue)
+    {
+        Fields.Field header = headers.get(name);
+        if (header != null && expectedValue.equals(header.getValue()))
+            return true;
+        System.out.println(name + " not valid! Expected: " + expectedValue + " headers received:" + headers);
+        return false;
+    }
+
+    private boolean validateUriHeader(Fields headers)
+    {
+        Fields.Field uriHeader = headers.get(HTTPSPDYHeader.URI.name(version));
+        if (uriHeader != null)
+            if (version == SPDY.V2 && uriHeader.getValue().startsWith("http://"))
+                return true;
+            else if (version == SPDY.V3 && uriHeader.getValue().startsWith("/")
+                    && headers.get(HTTPSPDYHeader.HOST.name(version)) != null && headers.get(HTTPSPDYHeader.SCHEME.name(version)) != null)
+                return true;
+        System.out.println(HTTPSPDYHeader.URI.name(version) + " not valid!");
+        return false;
+    }
+
+    private Fields createHeaders(String resource)
+    {
+        return createHeaders(resource, mainResource);
+    }
+
+    private Fields createHeaders(String resource, String referrer)
+    {
+        Fields associatedRequestHeaders = createHeadersWithoutReferrer(resource);
+        associatedRequestHeaders.put("referer", "http://localhost:" + connector.getLocalPort() + referrer);
+        return associatedRequestHeaders;
+    }
+
+    private Fields createHeadersWithoutReferrer(String resource)
+    {
+        Fields requestHeaders = new Fields();
+        requestHeaders.put("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; rv:16.0) " +
+                "Gecko/20100101 Firefox/16.0");
+        requestHeaders.put("accept-encoding", "gzip");
+        requestHeaders.put(HTTPSPDYHeader.METHOD.name(version), "GET");
+        requestHeaders.put(HTTPSPDYHeader.URI.name(version), resource);
+        requestHeaders.put(HTTPSPDYHeader.VERSION.name(version), "HTTP/1.1");
+        requestHeaders.put(HTTPSPDYHeader.SCHEME.name(version), "http");
+        requestHeaders.put(HTTPSPDYHeader.HOST.name(version), "localhost:" + connector.getLocalPort());
+        return requestHeaders;
+    }
+}
diff --git a/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/ReferrerPushStrategyUnitTest.java b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/ReferrerPushStrategyUnitTest.java
new file mode 100644
index 0000000..a1001b5
--- /dev/null
+++ b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/ReferrerPushStrategyUnitTest.java
@@ -0,0 +1,149 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.spdy.server.http;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.when;
+
+import java.util.Arrays;
+import java.util.Set;
+
+import org.eclipse.jetty.spdy.api.SPDY;
+import org.eclipse.jetty.spdy.api.Session;
+import org.eclipse.jetty.spdy.api.Stream;
+import org.eclipse.jetty.spdy.http.HTTPSPDYHeader;
+import org.eclipse.jetty.util.Fields;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
+ at RunWith(MockitoJUnitRunner.class)
+public class ReferrerPushStrategyUnitTest
+{
+    public static final short VERSION = SPDY.V3;
+    public static final String SCHEME = "http";
+    public static final String HOST = "localhost";
+    public static final String MAIN_URI = "/index.html";
+    public static final String METHOD = "GET";
+
+    // class under test
+    private ReferrerPushStrategy referrerPushStrategy = new ReferrerPushStrategy();
+
+    @Mock
+    Stream stream;
+    @Mock
+    Session session;
+
+
+    @Before
+    public void setup()
+    {
+        referrerPushStrategy.setUserAgentBlacklist(Arrays.asList(".*(?i)firefox/16.*"));
+    }
+
+    @Test
+    public void testReferrerCallsAfterTimeoutAreNotAddedAsPushResources() throws InterruptedException
+    {
+        Fields requestHeaders = getBaseHeaders(VERSION);
+        int referrerCallTimeout = 1000;
+        referrerPushStrategy.setReferrerPushPeriod(referrerCallTimeout);
+        setMockExpectations();
+
+        String referrerUrl = fillPushStrategyCache(requestHeaders);
+
+        // sleep to pretend that the user manually clicked on a linked resource instead the browser requesting sub
+        // resources immediately
+        Thread.sleep(referrerCallTimeout + 1);
+
+        requestHeaders.put(HTTPSPDYHeader.URI.name(VERSION), "image2.jpg");
+        requestHeaders.put("referer", referrerUrl);
+        Set<String> pushResources = referrerPushStrategy.apply(stream, requestHeaders, new Fields());
+        assertThat("pushResources is empty", pushResources.size(), is(0));
+
+        requestHeaders.put(HTTPSPDYHeader.URI.name(VERSION), MAIN_URI);
+        pushResources = referrerPushStrategy.apply(stream, requestHeaders, new Fields());
+        // as the image2.jpg request has been a link and not a sub resource, we expect that pushResources.size() is
+        // still 2
+        assertThat("pushResources contains two elements image.jpg and style.css", pushResources.size(), is(2));
+    }
+
+    @Test
+    public void testUserAgentFilter() throws InterruptedException
+    {
+        Fields requestHeaders = getBaseHeaders(VERSION);
+        setMockExpectations();
+
+        fillPushStrategyCache(requestHeaders);
+
+        Set<String> pushResources = referrerPushStrategy.apply(stream, requestHeaders, new Fields());
+        assertThat("pushResources contains two elements image.jpg and style.css as no user-agent header is present",
+                pushResources.size(), is(2));
+
+        requestHeaders.put("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_5) AppleWebKit/537.4 (KHTML, like Gecko) Chrome/22.0.1229.94 Safari/537.4");
+        pushResources = referrerPushStrategy.apply(stream, requestHeaders, new Fields());
+        assertThat("pushResources contains two elements image.jpg and style.css as chrome is not blacklisted",
+                pushResources.size(), is(2));
+
+        requestHeaders.put("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; rv:16.0) Gecko/20100101 Firefox/16.0");
+        pushResources = referrerPushStrategy.apply(stream, requestHeaders, new Fields());
+        assertThat("no resources are returned as we want to filter firefox", pushResources.size(), is(0));
+    }
+
+    private Fields getBaseHeaders(short version)
+    {
+        Fields requestHeaders = new Fields();
+        requestHeaders.put(HTTPSPDYHeader.SCHEME.name(version), SCHEME);
+        requestHeaders.put(HTTPSPDYHeader.HOST.name(version), HOST);
+        requestHeaders.put(HTTPSPDYHeader.URI.name(version), MAIN_URI);
+        requestHeaders.put(HTTPSPDYHeader.METHOD.name(version), METHOD);
+        return requestHeaders;
+    }
+
+    private void setMockExpectations()
+    {
+        when(stream.getSession()).thenReturn(session);
+        when(session.getVersion()).thenReturn(VERSION);
+    }
+
+    private String fillPushStrategyCache(Fields requestHeaders)
+    {
+        Set<String> pushResources = referrerPushStrategy.apply(stream, requestHeaders, new Fields());
+        assertThat("pushResources is empty", pushResources.size(), is(0));
+
+        String origin = SCHEME + "://" + HOST;
+        String referrerUrl = origin + MAIN_URI;
+
+        requestHeaders.put(HTTPSPDYHeader.URI.name(VERSION), "image.jpg");
+        requestHeaders.put("referer", referrerUrl);
+        pushResources = referrerPushStrategy.apply(stream, requestHeaders, new Fields());
+        assertThat("pushResources is empty", pushResources.size(), is(0));
+
+        requestHeaders.put(HTTPSPDYHeader.URI.name(VERSION), "style.css");
+        pushResources = referrerPushStrategy.apply(stream, requestHeaders, new Fields());
+        assertThat("pushResources is empty", pushResources.size(), is(0));
+
+        requestHeaders.put(HTTPSPDYHeader.URI.name(VERSION), MAIN_URI);
+        pushResources = referrerPushStrategy.apply(stream, requestHeaders, new Fields());
+        assertThat("pushResources contains two elements image.jpg and style.css", pushResources.size(), is(2));
+        return referrerUrl;
+    }
+}
diff --git a/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/SPDYTestUtils.java b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/SPDYTestUtils.java
new file mode 100644
index 0000000..f467bfa
--- /dev/null
+++ b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/SPDYTestUtils.java
@@ -0,0 +1,51 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.spdy.server.http;
+
+import org.eclipse.jetty.spdy.http.HTTPSPDYHeader;
+import org.eclipse.jetty.util.Fields;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+
+public class SPDYTestUtils
+{
+    public static Fields createHeaders(String host, int port, short version, String httpMethod, String path)
+    {
+        Fields headers = new Fields();
+        headers.put(HTTPSPDYHeader.METHOD.name(version), httpMethod);
+        headers.put(HTTPSPDYHeader.URI.name(version), path);
+        headers.put(HTTPSPDYHeader.VERSION.name(version), "HTTP/1.1");
+        headers.put(HTTPSPDYHeader.SCHEME.name(version), "http");
+        headers.put(HTTPSPDYHeader.HOST.name(version), host + ":" + port);
+        return headers;
+    }
+
+    public static SslContextFactory newSslContextFactory()
+    {
+        SslContextFactory sslContextFactory = new SslContextFactory();
+        sslContextFactory.setEndpointIdentificationAlgorithm("");
+        sslContextFactory.setKeyStorePath("src/test/resources/keystore.jks");
+        sslContextFactory.setKeyStorePassword("storepwd");
+        sslContextFactory.setTrustStorePath("src/test/resources/truststore.jks");
+        sslContextFactory.setTrustStorePassword("storepwd");
+        sslContextFactory.setProtocol("TLSv1");
+        sslContextFactory.setIncludeProtocols("TLSv1");
+        return sslContextFactory;
+    }
+}
diff --git a/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/SSLExternalServerTest.java b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/SSLExternalServerTest.java
new file mode 100644
index 0000000..89018f4
--- /dev/null
+++ b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/SSLExternalServerTest.java
@@ -0,0 +1,108 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.spdy.server.http;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.eclipse.jetty.spdy.api.ReplyInfo;
+import org.eclipse.jetty.spdy.api.SPDY;
+import org.eclipse.jetty.spdy.api.Session;
+import org.eclipse.jetty.spdy.api.Stream;
+import org.eclipse.jetty.spdy.api.StreamFrameListener;
+import org.eclipse.jetty.spdy.api.SynInfo;
+import org.eclipse.jetty.spdy.client.SPDYClient;
+import org.eclipse.jetty.spdy.http.HTTPSPDYHeader;
+import org.eclipse.jetty.util.Fields;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.junit.Assert;
+import org.junit.Assume;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class SSLExternalServerTest extends AbstractHTTPSPDYTest
+{
+    public SSLExternalServerTest(short version)
+    {
+        // Google Servers do not support SPDY/2 anymore
+        super(SPDY.V3);
+    }
+
+    @Override
+    protected SPDYClient.Factory newSPDYClientFactory(Executor threadPool)
+    {
+        SslContextFactory sslContextFactory = new SslContextFactory();
+        // Force TLSv1
+        sslContextFactory.setIncludeProtocols("TLSv1");
+        sslContextFactory.setEndpointIdentificationAlgorithm("");
+        return new SPDYClient.Factory(threadPool, null, sslContextFactory, 30000);
+    }
+
+    @Test
+    @Ignore("Relies on an external server")
+    public void testExternalServer() throws Exception
+    {
+        String host = "encrypted.google.com";
+        int port = 443;
+        InetSocketAddress address = new InetSocketAddress(host, port);
+
+        try
+        {
+            // Test whether there is connectivity to avoid fail the test when offline
+            Socket socket = new Socket();
+            socket.connect(address, 5000);
+            socket.close();
+        }
+        catch (IOException x)
+        {
+            Assume.assumeNoException(x);
+        }
+
+        Session session = startClient(version, address, null);
+        Fields headers = new Fields();
+        headers.put(HTTPSPDYHeader.SCHEME.name(version), "https");
+        headers.put(HTTPSPDYHeader.HOST.name(version), host + ":" + port);
+        headers.put(HTTPSPDYHeader.METHOD.name(version), "GET");
+        headers.put(HTTPSPDYHeader.URI.name(version), "/");
+        headers.put(HTTPSPDYHeader.VERSION.name(version), "HTTP/1.1");
+        final CountDownLatch latch = new CountDownLatch(1);
+        session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter()
+        {
+            @Override
+            public void onReply(Stream stream, ReplyInfo replyInfo)
+            {
+                Fields headers = replyInfo.getHeaders();
+                Fields.Field versionHeader = headers.get(HTTPSPDYHeader.STATUS.name(version));
+                if (versionHeader != null)
+                {
+                    Matcher matcher = Pattern.compile("(\\d{3}).*").matcher(versionHeader.getValue());
+                    if (matcher.matches() && Integer.parseInt(matcher.group(1)) < 400)
+                        latch.countDown();
+                }
+            }
+        });
+        Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+    }
+}
diff --git a/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/ServerHTTPSPDYTest.java b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/ServerHTTPSPDYTest.java
new file mode 100644
index 0000000..cbc577b
--- /dev/null
+++ b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/ServerHTTPSPDYTest.java
@@ -0,0 +1,1625 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.spdy.server.http;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicInteger;
+import javax.servlet.AsyncContext;
+import javax.servlet.AsyncEvent;
+import javax.servlet.AsyncListener;
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.server.HttpChannel;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.spdy.api.BytesDataInfo;
+import org.eclipse.jetty.spdy.api.DataInfo;
+import org.eclipse.jetty.spdy.api.ReplyInfo;
+import org.eclipse.jetty.spdy.api.Session;
+import org.eclipse.jetty.spdy.api.Stream;
+import org.eclipse.jetty.spdy.api.StreamFrameListener;
+import org.eclipse.jetty.spdy.api.StringDataInfo;
+import org.eclipse.jetty.spdy.api.SynInfo;
+import org.eclipse.jetty.spdy.http.HTTPSPDYHeader;
+import org.eclipse.jetty.util.Fields;
+import org.eclipse.jetty.util.log.StdErrLog;
+import org.junit.Assert;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.core.IsInstanceOf.instanceOf;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
+{
+    public static final String SUSPENDED_ATTRIBUTE = ServerHTTPSPDYTest.class.getName() + ".SUSPENDED";
+
+    public ServerHTTPSPDYTest(short version)
+    {
+        super(version);
+    }
+
+    @Test
+    public void testSimpleGET() throws Exception
+    {
+        final String path = "/foo";
+        final CountDownLatch handlerLatch = new CountDownLatch(1);
+        Session session = startClient(version, startHTTPServer(version, new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
+                    throws IOException, ServletException
+            {
+                request.setHandled(true);
+                assertEquals("GET", httpRequest.getMethod());
+                assertEquals(path, target);
+                assertEquals(path, httpRequest.getRequestURI());
+                assertThat("accept-encoding is set to gzip, even if client didn't set it",
+                        httpRequest.getHeader("accept-encoding"), containsString("gzip"));
+                assertThat(httpRequest.getHeader("host"), is("localhost:" + connector.getLocalPort()));
+                handlerLatch.countDown();
+            }
+        }, 30000), null);
+
+        Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getLocalPort(), version, "GET", path);
+        final CountDownLatch replyLatch = new CountDownLatch(1);
+        session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter()
+        {
+            @Override
+            public void onReply(Stream stream, ReplyInfo replyInfo)
+            {
+                assertTrue(replyInfo.isClose());
+                Fields replyHeaders = replyInfo.getHeaders();
+                assertThat(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200"), is(true));
+                assertThat(replyHeaders.get(HttpHeader.SERVER.asString()), is(notNullValue()));
+                assertThat(replyHeaders.get(HttpHeader.X_POWERED_BY.asString()), is(notNullValue()));
+                replyLatch.countDown();
+            }
+        });
+        assertTrue(handlerLatch.await(5, TimeUnit.SECONDS));
+        assertTrue(replyLatch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testGETWithQueryString() throws Exception
+    {
+        final String path = "/foo";
+        final String query = "p=1";
+        final String uri = path + "?" + query;
+        final CountDownLatch handlerLatch = new CountDownLatch(1);
+        Session session = startClient(version, startHTTPServer(version, new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
+                    throws IOException, ServletException
+            {
+                request.setHandled(true);
+                assertEquals("GET", httpRequest.getMethod());
+                assertEquals(path, target);
+                assertEquals(path, httpRequest.getRequestURI());
+                assertEquals(query, httpRequest.getQueryString());
+                handlerLatch.countDown();
+            }
+        }, 30000), null);
+
+        Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "GET", uri);
+        final CountDownLatch replyLatch = new CountDownLatch(1);
+        session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter()
+        {
+            @Override
+            public void onReply(Stream stream, ReplyInfo replyInfo)
+            {
+                assertTrue(replyInfo.isClose());
+                Fields replyHeaders = replyInfo.getHeaders();
+                assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200"));
+                replyLatch.countDown();
+            }
+        });
+        assertTrue(handlerLatch.await(5, TimeUnit.SECONDS));
+        assertTrue(replyLatch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testGETWithCookies() throws Exception
+    {
+        final String path = "/foo";
+        final String uri = path;
+        final String cookie1 = "cookie1";
+        final String cookie2 = "cookie2";
+        final String cookie1Value = "cookie 1 value";
+        final String cookie2Value = "cookie 2 value";
+        final CountDownLatch handlerLatch = new CountDownLatch(1);
+        Session session = startClient(version, startHTTPServer(version, new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
+                    throws IOException, ServletException
+            {
+                request.setHandled(true);
+                httpResponse.addCookie(new Cookie(cookie1, cookie1Value));
+                httpResponse.addCookie(new Cookie(cookie2, cookie2Value));
+                assertThat("method is GET", httpRequest.getMethod(), is("GET"));
+                assertThat("target is /foo", target, is(path));
+                assertThat("requestUri is /foo", httpRequest.getRequestURI(), is(path));
+                handlerLatch.countDown();
+            }
+        }, 30000), null);
+
+        Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "GET", uri);
+        final CountDownLatch replyLatch = new CountDownLatch(1);
+        session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter()
+        {
+            @Override
+            public void onReply(Stream stream, ReplyInfo replyInfo)
+            {
+                assertThat("isClose is true", replyInfo.isClose(), is(true));
+                Fields replyHeaders = replyInfo.getHeaders();
+                assertThat("response code is 200 OK", replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue()
+                        .contains("200"), is(true));
+                assertThat(replyInfo.getHeaders().get("Set-Cookie").getValues().get(0), is(cookie1 + "=\"" + cookie1Value +
+                        "\";Version=1"));
+                assertThat(replyInfo.getHeaders().get("Set-Cookie").getValues().get(1), is(cookie2 + "=\"" + cookie2Value +
+                        "\";Version=1"));
+                replyLatch.countDown();
+            }
+        });
+        assertTrue(handlerLatch.await(5, TimeUnit.SECONDS));
+        assertTrue(replyLatch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testHEAD() throws Exception
+    {
+        final String path = "/foo";
+        final CountDownLatch handlerLatch = new CountDownLatch(1);
+        Session session = startClient(version, startHTTPServer(version, new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
+                    throws IOException, ServletException
+            {
+                request.setHandled(true);
+                assertEquals("HEAD", httpRequest.getMethod());
+                assertEquals(path, target);
+                assertEquals(path, httpRequest.getRequestURI());
+                httpResponse.getWriter().write("body that shouldn't be sent on a HEAD request");
+                handlerLatch.countDown();
+            }
+        }, 30000), null);
+
+        Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "HEAD", path);
+        final CountDownLatch replyLatch = new CountDownLatch(1);
+        session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter()
+        {
+            @Override
+            public void onReply(Stream stream, ReplyInfo replyInfo)
+            {
+                assertTrue(replyInfo.isClose());
+                Fields replyHeaders = replyInfo.getHeaders();
+                assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200"));
+                replyLatch.countDown();
+            }
+
+            @Override
+            public void onData(Stream stream, DataInfo dataInfo)
+            {
+                fail("HEAD request shouldn't send any data");
+            }
+        });
+        assertTrue(handlerLatch.await(5, TimeUnit.SECONDS));
+        assertTrue(replyLatch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testPOSTWithDelayedContentBody() throws Exception
+    {
+        final String path = "/foo";
+        final CountDownLatch handlerLatch = new CountDownLatch(1);
+        Session session = startClient(version, startHTTPServer(version, new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
+                    throws IOException, ServletException
+            {
+                // don't read the request body, reply immediately
+                request.setHandled(true);
+                handlerLatch.countDown();
+            }
+        }, 30000), null);
+
+        Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "POST", path);
+        headers.put("content-type", "application/x-www-form-urlencoded");
+        final CountDownLatch replyLatch = new CountDownLatch(1);
+        Stream stream = session.syn(new SynInfo(5, TimeUnit.SECONDS, headers, false, (byte)0),
+                new StreamFrameListener.Adapter()
+                {
+                    @Override
+                    public void onReply(Stream stream, ReplyInfo replyInfo)
+                    {
+                        assertTrue(replyInfo.isClose());
+                        Fields replyHeaders = replyInfo.getHeaders();
+                        assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200"));
+                        replyLatch.countDown();
+                    }
+                });
+        stream.data(new StringDataInfo("a", false));
+        assertTrue(handlerLatch.await(5, TimeUnit.SECONDS));
+        assertTrue(replyLatch.await(5, TimeUnit.SECONDS));
+        stream.data(new StringDataInfo("b", true));
+    }
+
+    @Test
+    public void testPOSTWithParameters() throws Exception
+    {
+        final String path = "/foo";
+        final String data = "a=1&b=2";
+        final CountDownLatch handlerLatch = new CountDownLatch(1);
+        Session session = startClient(version, startHTTPServer(version, new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
+                    throws IOException, ServletException
+            {
+                request.setHandled(true);
+                assertEquals("POST", httpRequest.getMethod());
+                assertEquals("1", httpRequest.getParameter("a"));
+                assertEquals("2", httpRequest.getParameter("b"));
+                assertNotNull(httpRequest.getRemoteHost());
+                assertNotNull(httpRequest.getRemotePort());
+                assertNotNull(httpRequest.getRemoteAddr());
+                assertNotNull(httpRequest.getLocalPort());
+                assertNotNull(httpRequest.getLocalName());
+                assertNotNull(httpRequest.getLocalAddr());
+                assertNotNull(httpRequest.getServerPort());
+                assertNotNull(httpRequest.getServerName());
+                handlerLatch.countDown();
+            }
+        }, 30000), null);
+
+        Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "POST", path);
+        headers.put("content-type", "application/x-www-form-urlencoded");
+        final CountDownLatch replyLatch = new CountDownLatch(1);
+        Stream stream = session.syn(new SynInfo(5, TimeUnit.SECONDS, headers, false, (byte)0),
+                new StreamFrameListener.Adapter()
+                {
+                    @Override
+                    public void onReply(Stream stream, ReplyInfo replyInfo)
+                    {
+                        assertTrue(replyInfo.isClose());
+                        Fields replyHeaders = replyInfo.getHeaders();
+                        assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200"));
+                        replyLatch.countDown();
+                    }
+                });
+        stream.data(new StringDataInfo(data, true));
+
+        assertTrue(handlerLatch.await(5, TimeUnit.SECONDS));
+        assertTrue(replyLatch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testPOSTWithParametersInTwoFramesTwoReads() throws Exception
+    {
+        final String path = "/foo";
+        final String data1 = "a=1&";
+        final String data2 = "b=2";
+        final CountDownLatch handlerLatch = new CountDownLatch(1);
+        Session session = startClient(version, startHTTPServer(version, new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
+                    throws IOException, ServletException
+            {
+                request.setHandled(true);
+                assertEquals("POST", httpRequest.getMethod());
+                assertEquals("1", httpRequest.getParameter("a"));
+                assertEquals("2", httpRequest.getParameter("b"));
+                handlerLatch.countDown();
+            }
+        }, 30000), null);
+
+        Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "POST", path);
+        headers.put("content-type", "application/x-www-form-urlencoded");
+        final CountDownLatch replyLatch = new CountDownLatch(1);
+        Stream stream = session.syn(new SynInfo(5, TimeUnit.SECONDS, headers, false, (byte)0),
+                new StreamFrameListener.Adapter()
+                {
+                    @Override
+                    public void onReply(Stream stream, ReplyInfo replyInfo)
+                    {
+                        assertTrue(replyInfo.isClose());
+                        Fields replyHeaders = replyInfo.getHeaders();
+                        assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200"));
+                        replyLatch.countDown();
+                    }
+                });
+        // Sleep between the data frames so that they will be read in 2 reads
+        stream.data(new StringDataInfo(data1, false));
+        Thread.sleep(1000);
+        stream.data(new StringDataInfo(data2, true));
+
+        assertTrue(handlerLatch.await(5, TimeUnit.SECONDS));
+        assertTrue(replyLatch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testPOSTWithParametersInTwoFramesOneRead() throws Exception
+    {
+        final String path = "/foo";
+        final String data1 = "a=1&";
+        final String data2 = "b=2";
+        final CountDownLatch handlerLatch = new CountDownLatch(1);
+        Session session = startClient(version, startHTTPServer(version, new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
+                    throws IOException, ServletException
+            {
+                request.setHandled(true);
+                assertEquals("POST", httpRequest.getMethod());
+                assertEquals("1", httpRequest.getParameter("a"));
+                assertEquals("2", httpRequest.getParameter("b"));
+                handlerLatch.countDown();
+            }
+        }, 30000), null);
+
+        Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "POST", path);
+        headers.put("content-type", "application/x-www-form-urlencoded");
+        final CountDownLatch replyLatch = new CountDownLatch(1);
+        Stream stream = session.syn(new SynInfo(5, TimeUnit.SECONDS, headers, false, (byte)0), new StreamFrameListener.Adapter()
+        {
+            @Override
+            public void onReply(Stream stream, ReplyInfo replyInfo)
+            {
+                assertTrue(replyInfo.isClose());
+                Fields replyHeaders = replyInfo.getHeaders();
+                assertTrue(replyHeaders.toString(), replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200"));
+                replyLatch.countDown();
+            }
+        });
+
+        // Send the data frames consecutively, so the server reads both frames in one read
+        stream.data(new StringDataInfo(data1, false));
+        stream.data(new StringDataInfo(data2, true));
+
+        assertTrue(handlerLatch.await(5, TimeUnit.SECONDS));
+        assertTrue(replyLatch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testGETWithSmallResponseContent() throws Exception
+    {
+        final String data = "0123456789ABCDEF";
+        final CountDownLatch handlerLatch = new CountDownLatch(1);
+        Session session = startClient(version, startHTTPServer(version, new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
+                    throws IOException, ServletException
+            {
+                request.setHandled(true);
+                httpResponse.setStatus(HttpServletResponse.SC_OK);
+                ServletOutputStream output = httpResponse.getOutputStream();
+                output.write(data.getBytes(StandardCharsets.UTF_8));
+                handlerLatch.countDown();
+            }
+        }, 30000), null);
+
+        Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "GET", "/foo");
+        final CountDownLatch replyLatch = new CountDownLatch(1);
+        final CountDownLatch dataLatch = new CountDownLatch(1);
+        session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter()
+        {
+            @Override
+            public void onReply(Stream stream, ReplyInfo replyInfo)
+            {
+                Assert.assertFalse(replyInfo.isClose());
+                Fields replyHeaders = replyInfo.getHeaders();
+                assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200"));
+                replyLatch.countDown();
+            }
+
+            @Override
+            public void onData(Stream stream, DataInfo dataInfo)
+            {
+                assertTrue(dataInfo.isClose());
+                assertEquals(data, dataInfo.asString(StandardCharsets.UTF_8, true));
+                dataLatch.countDown();
+            }
+        });
+        assertTrue(handlerLatch.await(5, TimeUnit.SECONDS));
+        assertTrue(replyLatch.await(5, TimeUnit.SECONDS));
+        assertTrue(dataLatch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testGETWithOneByteResponseContent() throws Exception
+    {
+        final char data = 'x';
+        final CountDownLatch handlerLatch = new CountDownLatch(1);
+        Session session = startClient(version, startHTTPServer(version, new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
+                    throws IOException, ServletException
+            {
+                request.setHandled(true);
+                httpResponse.setStatus(HttpServletResponse.SC_OK);
+                ServletOutputStream output = httpResponse.getOutputStream();
+                output.write(data);
+                handlerLatch.countDown();
+            }
+        }, 30000), null);
+
+        Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "GET", "/foo");
+        final CountDownLatch replyLatch = new CountDownLatch(1);
+        final CountDownLatch dataLatch = new CountDownLatch(1);
+        session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter()
+        {
+            @Override
+            public void onReply(Stream stream, ReplyInfo replyInfo)
+            {
+                Assert.assertFalse(replyInfo.isClose());
+                Fields replyHeaders = replyInfo.getHeaders();
+                assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200"));
+                replyLatch.countDown();
+            }
+
+            @Override
+            public void onData(Stream stream, DataInfo dataInfo)
+            {
+                assertTrue(dataInfo.isClose());
+                byte[] bytes = dataInfo.asBytes(true);
+                assertEquals(1, bytes.length);
+                assertEquals(data, bytes[0]);
+                dataLatch.countDown();
+            }
+        });
+        assertTrue(handlerLatch.await(5, TimeUnit.SECONDS));
+        assertTrue(replyLatch.await(5, TimeUnit.SECONDS));
+        assertTrue(dataLatch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testGETWithSmallResponseContentInTwoChunks() throws Exception
+    {
+        final String data1 = "0123456789ABCDEF";
+        final String data2 = "FEDCBA9876543210";
+        final CountDownLatch handlerLatch = new CountDownLatch(1);
+        Session session = startClient(version, startHTTPServer(version, new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
+                    throws IOException, ServletException
+            {
+                request.setHandled(true);
+                httpResponse.setStatus(HttpServletResponse.SC_OK);
+                ServletOutputStream output = httpResponse.getOutputStream();
+                output.write(data1.getBytes(StandardCharsets.UTF_8));
+                output.flush();
+                output.write(data2.getBytes(StandardCharsets.UTF_8));
+                handlerLatch.countDown();
+            }
+        }, 30000), null);
+
+        Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "GET", "/foo");
+        final CountDownLatch replyLatch = new CountDownLatch(1);
+        final CountDownLatch dataLatch = new CountDownLatch(2);
+        session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter()
+        {
+            private final AtomicInteger replyFrames = new AtomicInteger();
+            private final AtomicInteger dataFrames = new AtomicInteger();
+
+            @Override
+            public void onReply(Stream stream, ReplyInfo replyInfo)
+            {
+                assertEquals(1, replyFrames.incrementAndGet());
+                Assert.assertFalse(replyInfo.isClose());
+                Fields replyHeaders = replyInfo.getHeaders();
+                assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200"));
+                replyLatch.countDown();
+            }
+
+            @Override
+            public void onData(Stream stream, DataInfo dataInfo)
+            {
+                int data = dataFrames.incrementAndGet();
+                assertTrue(data >= 1 && data <= 2);
+                if (data == 1)
+                    assertEquals(data1, dataInfo.asString(StandardCharsets.UTF_8, true));
+                else
+                    assertEquals(data2, dataInfo.asString(StandardCharsets.UTF_8, true));
+                dataLatch.countDown();
+            }
+        });
+        assertTrue(handlerLatch.await(5, TimeUnit.SECONDS));
+        assertTrue(replyLatch.await(5, TimeUnit.SECONDS));
+        assertTrue(dataLatch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testGETWithBigResponseContentInOneWrite() throws Exception
+    {
+        final byte[] data = new byte[128 * 1024];
+        Arrays.fill(data, (byte)'x');
+        final CountDownLatch handlerLatch = new CountDownLatch(1);
+        Session session = startClient(version, startHTTPServer(version, new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
+                    throws IOException, ServletException
+            {
+                request.setHandled(true);
+                httpResponse.setStatus(HttpServletResponse.SC_OK);
+                ServletOutputStream output = httpResponse.getOutputStream();
+                output.write(data);
+                handlerLatch.countDown();
+            }
+        }, 30000), null);
+
+        Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "GET", "/foo");
+        final CountDownLatch replyLatch = new CountDownLatch(1);
+        final CountDownLatch dataLatch = new CountDownLatch(1);
+        session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter()
+        {
+            private final AtomicInteger contentBytes = new AtomicInteger();
+
+            @Override
+            public void onReply(Stream stream, ReplyInfo replyInfo)
+            {
+                Assert.assertFalse(replyInfo.isClose());
+                Fields replyHeaders = replyInfo.getHeaders();
+                assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200"));
+                replyLatch.countDown();
+            }
+
+            @Override
+            public void onData(Stream stream, DataInfo dataInfo)
+            {
+                contentBytes.addAndGet(dataInfo.asByteBuffer(true).remaining());
+                if (dataInfo.isClose())
+                {
+                    assertEquals(data.length, contentBytes.get());
+                    dataLatch.countDown();
+                }
+            }
+        });
+        assertTrue(handlerLatch.await(5, TimeUnit.SECONDS));
+        assertTrue(replyLatch.await(5, TimeUnit.SECONDS));
+        assertTrue(dataLatch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testGETWithBigResponseContentInMultipleWrites() throws Exception
+    {
+        final byte[] data = new byte[4 * 1024];
+        Arrays.fill(data, (byte)'x');
+        final int writeTimes = 16;
+        final CountDownLatch handlerLatch = new CountDownLatch(1);
+        Session session = startClient(version, startHTTPServer(version, new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
+                    throws IOException, ServletException
+            {
+                request.setHandled(true);
+                httpResponse.setStatus(HttpServletResponse.SC_OK);
+                ServletOutputStream output = httpResponse.getOutputStream();
+                for (int i = 0; i < writeTimes; i++)
+                {
+                    output.write(data);
+                }
+                handlerLatch.countDown();
+            }
+        }, 30000), null);
+
+        Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "GET", "/foo");
+        final CountDownLatch replyLatch = new CountDownLatch(1);
+        final CountDownLatch dataLatch = new CountDownLatch(1);
+        session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter()
+        {
+            private final AtomicInteger contentBytes = new AtomicInteger();
+
+            @Override
+            public void onReply(Stream stream, ReplyInfo replyInfo)
+            {
+                Assert.assertFalse(replyInfo.isClose());
+                Fields replyHeaders = replyInfo.getHeaders();
+                assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200"));
+                replyLatch.countDown();
+            }
+
+            @Override
+            public void onData(Stream stream, DataInfo dataInfo)
+            {
+                contentBytes.addAndGet(dataInfo.asByteBuffer(true).remaining());
+                if (dataInfo.isClose())
+                {
+                    assertEquals(data.length * writeTimes, contentBytes.get());
+                    dataLatch.countDown();
+                }
+            }
+        });
+        assertTrue(handlerLatch.await(5, TimeUnit.SECONDS));
+        assertTrue(replyLatch.await(5, TimeUnit.SECONDS));
+        assertTrue(dataLatch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testGETWithBigResponseContentInTwoWrites() throws Exception
+    {
+        final byte[] data = new byte[128 * 1024];
+        Arrays.fill(data, (byte)'y');
+        final CountDownLatch handlerLatch = new CountDownLatch(1);
+        Session session = startClient(version, startHTTPServer(version, new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
+                    throws IOException, ServletException
+            {
+                request.setHandled(true);
+                httpResponse.setStatus(HttpServletResponse.SC_OK);
+                ServletOutputStream output = httpResponse.getOutputStream();
+                output.write(data);
+                output.write(data);
+                handlerLatch.countDown();
+            }
+        }, 30000), null);
+
+        Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "GET", "/foo");
+        final CountDownLatch replyLatch = new CountDownLatch(1);
+        final CountDownLatch dataLatch = new CountDownLatch(1);
+        session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter()
+        {
+            private final AtomicInteger contentBytes = new AtomicInteger();
+
+            @Override
+            public void onReply(Stream stream, ReplyInfo replyInfo)
+            {
+                Assert.assertFalse(replyInfo.isClose());
+                Fields replyHeaders = replyInfo.getHeaders();
+                assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200"));
+                replyLatch.countDown();
+            }
+
+            @Override
+            public void onData(Stream stream, DataInfo dataInfo)
+            {
+                contentBytes.addAndGet(dataInfo.asByteBuffer(true).remaining());
+                if (dataInfo.isClose())
+                {
+                    assertEquals(2 * data.length, contentBytes.get());
+                    dataLatch.countDown();
+                }
+            }
+        });
+        assertTrue(handlerLatch.await(5, TimeUnit.SECONDS));
+        assertTrue(replyLatch.await(5, TimeUnit.SECONDS));
+        assertTrue(dataLatch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testGETWithOutputStreamFlushedAndClosed() throws Exception
+    {
+        final String data = "0123456789ABCDEF";
+        final CountDownLatch handlerLatch = new CountDownLatch(1);
+        Session session = startClient(version, startHTTPServer(version, new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
+                    throws IOException, ServletException
+            {
+                request.setHandled(true);
+                httpResponse.setStatus(HttpServletResponse.SC_OK);
+                ServletOutputStream output = httpResponse.getOutputStream();
+                output.write(data.getBytes(StandardCharsets.UTF_8));
+                output.flush();
+                output.close();
+                handlerLatch.countDown();
+            }
+        }, 30000), null);
+
+        Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "GET", "/foo");
+        final CountDownLatch replyLatch = new CountDownLatch(1);
+        final CountDownLatch dataLatch = new CountDownLatch(1);
+        session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter()
+        {
+            private final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+
+            @Override
+            public void onReply(Stream stream, ReplyInfo replyInfo)
+            {
+                Assert.assertFalse(replyInfo.isClose());
+                Fields replyHeaders = replyInfo.getHeaders();
+                assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200"));
+                replyLatch.countDown();
+            }
+
+            @Override
+            public void onData(Stream stream, DataInfo dataInfo)
+            {
+                ByteBuffer byteBuffer = dataInfo.asByteBuffer(true);
+                while (byteBuffer.hasRemaining())
+                    buffer.write(byteBuffer.get());
+                if (dataInfo.isClose())
+                {
+                    assertEquals(data, new String(buffer.toByteArray(), StandardCharsets.UTF_8));
+                    dataLatch.countDown();
+                }
+            }
+        });
+        assertTrue(handlerLatch.await(5, TimeUnit.SECONDS));
+        assertTrue(replyLatch.await(5, TimeUnit.SECONDS));
+        assertTrue(dataLatch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testGETWithResponseResetBuffer() throws Exception
+    {
+        final String data1 = "0123456789ABCDEF";
+        final String data2 = "FEDCBA9876543210";
+        final CountDownLatch handlerLatch = new CountDownLatch(1);
+        Session session = startClient(version, startHTTPServer(version, new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
+                    throws IOException, ServletException
+            {
+                request.setHandled(true);
+                httpResponse.setStatus(HttpServletResponse.SC_OK);
+                ServletOutputStream output = httpResponse.getOutputStream();
+                // Write some
+                output.write(data1.getBytes(StandardCharsets.UTF_8));
+                // But then change your mind and reset the buffer
+                httpResponse.resetBuffer();
+                output.write(data2.getBytes(StandardCharsets.UTF_8));
+                handlerLatch.countDown();
+            }
+        }, 30000), null);
+
+        Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "GET", "/foo");
+        final CountDownLatch replyLatch = new CountDownLatch(1);
+        final CountDownLatch dataLatch = new CountDownLatch(1);
+        session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter()
+        {
+            private final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+
+            @Override
+            public void onReply(Stream stream, ReplyInfo replyInfo)
+            {
+                Assert.assertFalse(replyInfo.isClose());
+                Fields replyHeaders = replyInfo.getHeaders();
+                assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200"));
+                replyLatch.countDown();
+            }
+
+            @Override
+            public void onData(Stream stream, DataInfo dataInfo)
+            {
+                ByteBuffer byteBuffer = dataInfo.asByteBuffer(true);
+                while (byteBuffer.hasRemaining())
+                    buffer.write(byteBuffer.get());
+                if (dataInfo.isClose())
+                {
+                    assertEquals(data2, new String(buffer.toByteArray(), StandardCharsets.UTF_8));
+                    dataLatch.countDown();
+                }
+            }
+        });
+        assertTrue(handlerLatch.await(5, TimeUnit.SECONDS));
+        assertTrue(replyLatch.await(5, TimeUnit.SECONDS));
+        assertTrue(dataLatch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testGETWithRedirect() throws Exception
+    {
+        final String suffix = "/redirect";
+        final CountDownLatch handlerLatch = new CountDownLatch(1);
+        Session session = startClient(version, startHTTPServer(version, new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
+                    throws IOException, ServletException
+            {
+                request.setHandled(true);
+                String location = httpResponse.encodeRedirectURL(String.format("%s://%s:%d%s",
+                        request.getScheme(), request.getLocalAddr(), request.getLocalPort(), target + suffix));
+                httpResponse.sendRedirect(location);
+                handlerLatch.countDown();
+            }
+        }, 30000), null);
+
+        Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "GET", "/foo");
+        final CountDownLatch replyLatch = new CountDownLatch(1);
+        session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter()
+        {
+            private final AtomicInteger replies = new AtomicInteger();
+
+            @Override
+            public void onReply(Stream stream, ReplyInfo replyInfo)
+            {
+                assertEquals(1, replies.incrementAndGet());
+                assertTrue(replyInfo.isClose());
+                Fields replyHeaders = replyInfo.getHeaders();
+                assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("302"));
+                assertTrue(replyHeaders.get("location").getValue().endsWith(suffix));
+                replyLatch.countDown();
+            }
+        });
+        assertTrue(handlerLatch.await(5, TimeUnit.SECONDS));
+        assertTrue(replyLatch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testGETWithSendError() throws Exception
+    {
+        final CountDownLatch handlerLatch = new CountDownLatch(1);
+        Session session = startClient(version, startHTTPServer(version, new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
+                    throws IOException, ServletException
+            {
+                request.setHandled(true);
+                httpResponse.sendError(HttpServletResponse.SC_NOT_FOUND);
+                handlerLatch.countDown();
+            }
+        }, 30000), null);
+
+        Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "GET", "/foo");
+        final CountDownLatch replyLatch = new CountDownLatch(1);
+        final CountDownLatch dataLatch = new CountDownLatch(1);
+        session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter()
+        {
+            private final AtomicInteger replies = new AtomicInteger();
+
+            @Override
+            public void onReply(Stream stream, ReplyInfo replyInfo)
+            {
+                assertEquals(1, replies.incrementAndGet());
+                Assert.assertFalse(replyInfo.isClose());
+                Fields replyHeaders = replyInfo.getHeaders();
+                assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("404"));
+                replyLatch.countDown();
+            }
+
+            @Override
+            public void onData(Stream stream, DataInfo dataInfo)
+            {
+                if (dataInfo.isClose())
+                    dataLatch.countDown();
+            }
+        });
+        assertTrue(handlerLatch.await(5, TimeUnit.SECONDS));
+        assertTrue(replyLatch.await(5, TimeUnit.SECONDS));
+        assertTrue(dataLatch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testGETWithException() throws Exception
+    {
+        StdErrLog log = StdErrLog.getLogger(HttpChannel.class);
+        log.setHideStacks(true);
+
+        Session session = startClient(version, startHTTPServer(version, new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
+                    throws IOException, ServletException
+            {
+                throw new NullPointerException("thrown_explicitly_by_the_test");
+            }
+        }, 30000), null);
+
+        Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "GET", "/foo");
+        final CountDownLatch replyLatch = new CountDownLatch(1);
+        final CountDownLatch latch = new CountDownLatch(1);
+        session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter()
+        {
+            private final AtomicInteger replies = new AtomicInteger();
+
+            @Override
+            public void onReply(Stream stream, ReplyInfo replyInfo)
+            {
+                assertEquals(1, replies.incrementAndGet());
+                Fields replyHeaders = replyInfo.getHeaders();
+                assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("500"));
+                replyLatch.countDown();
+                if (replyInfo.isClose())
+                    latch.countDown();
+            }
+
+            @Override
+            public void onData(Stream stream, DataInfo dataInfo)
+            {
+                if (dataInfo.isClose())
+                    latch.countDown();
+            }
+        });
+        assertTrue(replyLatch.await(5, TimeUnit.SECONDS));
+        assertTrue(latch.await(5, TimeUnit.SECONDS));
+
+        log.setHideStacks(false);
+    }
+
+    @Test
+    public void testGETWithSmallResponseContentChunked() throws Exception
+    {
+        final String pangram1 = "the quick brown fox jumps over the lazy dog";
+        final String pangram2 = "qualche vago ione tipo zolfo, bromo, sodio";
+        final CountDownLatch handlerLatch = new CountDownLatch(1);
+        Session session = startClient(version, startHTTPServer(version, new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
+                    throws IOException, ServletException
+            {
+                request.setHandled(true);
+                httpResponse.setHeader("Transfer-Encoding", "chunked");
+                ServletOutputStream output = httpResponse.getOutputStream();
+                output.write(pangram1.getBytes(StandardCharsets.UTF_8));
+                httpResponse.setHeader("EXTRA", "X");
+                output.flush();
+                output.write(pangram2.getBytes(StandardCharsets.UTF_8));
+                handlerLatch.countDown();
+            }
+        }, 30000), null);
+
+        Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "GET", "/foo");
+        final CountDownLatch replyLatch = new CountDownLatch(1);
+        final CountDownLatch dataLatch = new CountDownLatch(2);
+        session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter()
+        {
+            private final AtomicInteger replyFrames = new AtomicInteger();
+            private final AtomicInteger dataFrames = new AtomicInteger();
+
+            @Override
+            public void onReply(Stream stream, ReplyInfo replyInfo)
+            {
+                assertEquals(1, replyFrames.incrementAndGet());
+                Assert.assertFalse(replyInfo.isClose());
+                Fields replyHeaders = replyInfo.getHeaders();
+                assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200"));
+                assertTrue(replyHeaders.get("extra").getValue().contains("X"));
+                replyLatch.countDown();
+            }
+
+            @Override
+            public void onData(Stream stream, DataInfo dataInfo)
+            {
+                int count = dataFrames.incrementAndGet();
+                if (count == 1)
+                {
+                    Assert.assertFalse(dataInfo.isClose());
+                    assertEquals(pangram1, dataInfo.asString(StandardCharsets.UTF_8, true));
+                }
+                else if (count == 2)
+                {
+                    assertTrue(dataInfo.isClose());
+                    assertEquals(pangram2, dataInfo.asString(StandardCharsets.UTF_8, true));
+                }
+                dataLatch.countDown();
+            }
+        });
+        assertTrue(handlerLatch.await(5, TimeUnit.SECONDS));
+        assertTrue(replyLatch.await(5, TimeUnit.SECONDS));
+        assertTrue(dataLatch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testGETWithMediumContentAsBufferByPassed() throws Exception
+    {
+        final byte[] data = new byte[2048];
+
+        final CountDownLatch handlerLatch = new CountDownLatch(1);
+        Session session = startClient(version, startHTTPServer(version, new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
+                    throws IOException, ServletException
+            {
+                request.setHandled(true);
+                request.getResponse().getHttpOutput().sendContent(ByteBuffer.wrap(data));
+                handlerLatch.countDown();
+            }
+        }, 30000), null);
+
+        Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "GET", "/foo");
+        final CountDownLatch replyLatch = new CountDownLatch(1);
+        final CountDownLatch dataLatch = new CountDownLatch(1);
+        session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter()
+        {
+            private final AtomicInteger replyFrames = new AtomicInteger();
+            private final AtomicInteger contentLength = new AtomicInteger();
+
+            @Override
+            public void onReply(Stream stream, ReplyInfo replyInfo)
+            {
+                assertEquals(1, replyFrames.incrementAndGet());
+                Assert.assertFalse(replyInfo.isClose());
+                Fields replyHeaders = replyInfo.getHeaders();
+                assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200"));
+                replyLatch.countDown();
+            }
+
+            @Override
+            public void onData(Stream stream, DataInfo dataInfo)
+            {
+                contentLength.addAndGet(dataInfo.asBytes(true).length);
+                if (dataInfo.isClose())
+                {
+                    Assert.assertEquals(data.length, contentLength.get());
+                    dataLatch.countDown();
+                }
+            }
+        });
+        assertTrue(handlerLatch.await(5, TimeUnit.SECONDS));
+        assertTrue(replyLatch.await(5, TimeUnit.SECONDS));
+        assertTrue(dataLatch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testGETWithMultipleMediumContentByPassed() throws Exception
+    {
+        final byte[] data = new byte[2048];
+        Session session = startClient(version, startHTTPServer(version, new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
+                    throws IOException, ServletException
+            {
+                // The sequence of write/flush/write/write below triggers a condition where
+                // HttpGenerator._bypass is set to true on the second write(), and the
+                // third write causes an infinite spin loop on the third write().
+                request.setHandled(true);
+                OutputStream output = httpResponse.getOutputStream();
+                output.write(data);
+                output.flush();
+                output.write(data);
+                output.write(data);
+            }
+        }, 30000), null);
+
+        Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "GET", "/foo");
+        final CountDownLatch replyLatch = new CountDownLatch(1);
+        final CountDownLatch dataLatch = new CountDownLatch(1);
+        final AtomicInteger contentLength = new AtomicInteger();
+        session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter()
+        {
+            @Override
+            public void onReply(Stream stream, ReplyInfo replyInfo)
+            {
+                Assert.assertFalse(replyInfo.isClose());
+                Fields replyHeaders = replyInfo.getHeaders();
+                assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200"));
+                replyLatch.countDown();
+            }
+
+            @Override
+            public void onData(Stream stream, DataInfo dataInfo)
+            {
+                dataInfo.consume(dataInfo.available());
+                contentLength.addAndGet(dataInfo.length());
+                if (dataInfo.isClose())
+                    dataLatch.countDown();
+            }
+        });
+        assertTrue(dataLatch.await(5, TimeUnit.SECONDS));
+        assertEquals(3 * data.length, contentLength.get());
+    }
+
+    @Test
+    public void testPOSTThenSuspendRequestThenReadOneChunkThenComplete() throws Exception
+    {
+        final byte[] data = new byte[2000];
+        final CountDownLatch latch = new CountDownLatch(1);
+        Session session = startClient(version, startHTTPServer(version, new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, final Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
+                    throws IOException, ServletException
+            {
+                request.setHandled(true);
+                final AsyncContext asyncContext = request.startAsync();
+                new Thread()
+                {
+                    @Override
+                    public void run()
+                    {
+                        try
+                        {
+                            readRequestData(request, data.length);
+                            asyncContext.complete();
+                            latch.countDown();
+                        }
+                        catch (IOException x)
+                        {
+                            x.printStackTrace();
+                        }
+                    }
+                }.start();
+            }
+        }, 30000), null);
+
+        Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "POST", "/foo");
+        final CountDownLatch replyLatch = new CountDownLatch(1);
+        Stream stream = session.syn(new SynInfo(5, TimeUnit.SECONDS, headers, false, (byte)0), new StreamFrameListener.Adapter()
+        {
+            @Override
+            public void onReply(Stream stream, ReplyInfo replyInfo)
+            {
+                Fields replyHeaders = replyInfo.getHeaders();
+                assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200"));
+                replyLatch.countDown();
+            }
+        });
+        stream.data(new BytesDataInfo(data, true));
+
+        assertTrue(latch.await(5, TimeUnit.SECONDS));
+        assertTrue(replyLatch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testPOSTThenSuspendExpire() throws Exception
+    {
+        final CountDownLatch dispatchedAgainAfterExpire = new CountDownLatch(1);
+        Session session = startClient(version, startHTTPServer(version, new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, final Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
+                    throws IOException, ServletException
+            {
+                request.setHandled(true);
+                if (request.getAttribute(SUSPENDED_ATTRIBUTE) == Boolean.TRUE)
+                {
+                    dispatchedAgainAfterExpire.countDown();
+                }
+                else
+                {
+                    AsyncContext asyncContext = request.startAsync();
+                    asyncContext.setTimeout(1000);
+                    asyncContext.addListener(new AsyncListenerAdapter());
+                    request.setAttribute(SUSPENDED_ATTRIBUTE, Boolean.TRUE);
+                }
+            }
+        }, 30000), null);
+
+        Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "POST", "/foo");
+        final CountDownLatch replyLatch = new CountDownLatch(1);
+        session.syn(new SynInfo(5, TimeUnit.SECONDS, headers, true, (byte)0), new StreamFrameListener.Adapter()
+        {
+            @Override
+            public void onReply(Stream stream, ReplyInfo replyInfo)
+            {
+                Fields replyHeaders = replyInfo.getHeaders();
+                assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200"));
+                replyLatch.countDown();
+            }
+        });
+
+        assertTrue("Not dispatched again after expire", dispatchedAgainAfterExpire.await(5,
+                TimeUnit.SECONDS));
+        assertTrue("Reply not sent", replyLatch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testPOSTThenSuspendExpireWithRequestData() throws Exception
+    {
+        final byte[] data = new byte[2000];
+        final CountDownLatch dispatchedAgainAfterExpire = new CountDownLatch(1);
+        Session session = startClient(version, startHTTPServer(version, new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, final Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
+                    throws IOException, ServletException
+            {
+                request.setHandled(true);
+                if (request.getAttribute(SUSPENDED_ATTRIBUTE) == Boolean.TRUE)
+                {
+                    dispatchedAgainAfterExpire.countDown();
+                }
+                else
+                {
+                    readRequestData(request, data.length);
+                    AsyncContext asyncContext = request.startAsync();
+                    asyncContext.setTimeout(1000);
+                    asyncContext.addListener(new AsyncListenerAdapter());
+                    request.setAttribute(SUSPENDED_ATTRIBUTE, Boolean.TRUE);
+                }
+            }
+        }, 30000), null);
+
+        Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "POST", "/foo");
+        final CountDownLatch replyLatch = new CountDownLatch(1);
+        Stream stream = session.syn(new SynInfo(5, TimeUnit.SECONDS, headers, false, (byte)0), new StreamFrameListener.Adapter()
+        {
+            @Override
+            public void onReply(Stream stream, ReplyInfo replyInfo)
+            {
+                Fields replyHeaders = replyInfo.getHeaders();
+                assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200"));
+                replyLatch.countDown();
+            }
+        });
+        stream.data(new BytesDataInfo(data, true));
+
+        assertTrue("Not dispatched again after expire", dispatchedAgainAfterExpire.await(5,
+                TimeUnit.SECONDS));
+        assertTrue("Reply not sent", replyLatch.await(5, TimeUnit.SECONDS));
+    }
+
+    private void readRequestData(Request request, int expectedDataLength) throws IOException
+    {
+        InputStream input = request.getInputStream();
+        byte[] buffer = new byte[512];
+        int read = 0;
+        while (read < expectedDataLength)
+            read += input.read(buffer);
+    }
+
+    @Test
+    public void testPOSTThenSuspendRequestThenReadTwoChunksThenComplete() throws Exception
+    {
+        final byte[] data = new byte[2000];
+        final CountDownLatch latch = new CountDownLatch(1);
+        Session session = startClient(version, startHTTPServer(version, new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, final Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
+                    throws IOException, ServletException
+            {
+                request.setHandled(true);
+                final AsyncContext asyncContext = request.startAsync();
+                new Thread()
+                {
+                    @Override
+                    public void run()
+                    {
+                        try
+                        {
+                            InputStream input = request.getInputStream();
+                            byte[] buffer = new byte[512];
+                            int read = 0;
+                            while (read < 2 * data.length)
+                                read += input.read(buffer);
+                            asyncContext.complete();
+                            latch.countDown();
+                        }
+                        catch (IOException x)
+                        {
+                            x.printStackTrace();
+                        }
+                    }
+                }.start();
+            }
+        }, 30000), null);
+
+        Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "POST", "/foo");
+        final CountDownLatch replyLatch = new CountDownLatch(1);
+        Stream stream = session.syn(new SynInfo(5, TimeUnit.SECONDS, headers, false, (byte)0), new StreamFrameListener.Adapter()
+        {
+            @Override
+            public void onReply(Stream stream, ReplyInfo replyInfo)
+            {
+                Fields replyHeaders = replyInfo.getHeaders();
+                assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200"));
+                replyLatch.countDown();
+            }
+        });
+        stream.data(new BytesDataInfo(data, false));
+        stream.data(new BytesDataInfo(data, true));
+
+        assertTrue(latch.await(5, TimeUnit.SECONDS));
+        assertTrue(replyLatch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testPOSTThenSuspendRequestThenResumeThenRespond() throws Exception
+    {
+        final byte[] data = new byte[1000];
+        final CountDownLatch latch = new CountDownLatch(1);
+        Session session = startClient(version, startHTTPServer(version, new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, final Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
+                    throws IOException, ServletException
+            {
+                request.setHandled(true);
+                if (request.getAttribute(SUSPENDED_ATTRIBUTE) == Boolean.TRUE)
+                {
+                    OutputStream output = httpResponse.getOutputStream();
+                    output.write(data);
+                }
+                else
+                {
+                    final AsyncContext asyncContext = request.startAsync();
+                    request.setAttribute(SUSPENDED_ATTRIBUTE, Boolean.TRUE);
+                    InputStream input = request.getInputStream();
+                    byte[] buffer = new byte[256];
+                    int read = 0;
+                    while (read < data.length)
+                        read += input.read(buffer);
+                    new Thread()
+                    {
+                        @Override
+                        public void run()
+                        {
+                            try
+                            {
+                                TimeUnit.SECONDS.sleep(1);
+                                asyncContext.dispatch();
+                                latch.countDown();
+                            }
+                            catch (InterruptedException x)
+                            {
+                                x.printStackTrace();
+                            }
+                        }
+                    }.start();
+                }
+            }
+        }, 30000), null);
+
+        Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "POST", "/foo");
+        final CountDownLatch responseLatch = new CountDownLatch(2);
+        Stream stream = session.syn(new SynInfo(5, TimeUnit.SECONDS, headers, false, (byte)0), new StreamFrameListener.Adapter()
+        {
+            @Override
+            public void onReply(Stream stream, ReplyInfo replyInfo)
+            {
+                Fields replyHeaders = replyInfo.getHeaders();
+                assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200"));
+                responseLatch.countDown();
+            }
+
+            @Override
+            public void onData(Stream stream, DataInfo dataInfo)
+            {
+                if (dataInfo.isClose())
+                    responseLatch.countDown();
+            }
+        });
+        stream.data(new BytesDataInfo(data, true));
+
+        assertTrue(latch.await(5, TimeUnit.SECONDS));
+        assertTrue(responseLatch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testPOSTThenResponseWithoutReadingContent() throws Exception
+    {
+        final byte[] data = new byte[1000];
+        final CountDownLatch latch = new CountDownLatch(1);
+        Session session = startClient(version, startHTTPServer(version, new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, final Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
+                    throws IOException, ServletException
+            {
+                request.setHandled(true);
+                latch.countDown();
+            }
+        }, 30000), null);
+
+        Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "POST", "/foo");
+        final CountDownLatch responseLatch = new CountDownLatch(1);
+        Stream stream = session.syn(new SynInfo(5, TimeUnit.SECONDS, headers, false, (byte)0), new StreamFrameListener.Adapter()
+        {
+            @Override
+            public void onReply(Stream stream, ReplyInfo replyInfo)
+            {
+                Fields replyHeaders = replyInfo.getHeaders();
+                assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200"));
+                responseLatch.countDown();
+            }
+        });
+        stream.data(new BytesDataInfo(data, false));
+        stream.data(new BytesDataInfo(5, TimeUnit.SECONDS, data, true));
+
+        assertTrue(latch.await(5, TimeUnit.SECONDS));
+        assertTrue(responseLatch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testIdleTimeout() throws Exception
+    {
+        final int idleTimeout = 500;
+        final CountDownLatch timeoutReceivedLatch = new CountDownLatch(1);
+
+        Session session = startClient(version, startHTTPServer(version, new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, final Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
+                    throws IOException, ServletException
+            {
+                try
+                {
+                    Thread.sleep(2 * idleTimeout);
+                }
+                catch (InterruptedException e)
+                {
+                    throw new RuntimeException(e);
+                }
+                request.setHandled(true);
+            }
+        }, 30000), null);
+
+        Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "GET", "/");
+        Stream stream = session.syn(new SynInfo(5, TimeUnit.SECONDS, headers, true, (byte)0),
+                new StreamFrameListener.Adapter()
+                {
+                    @Override
+                    public void onFailure(Stream stream, Throwable x)
+                    {
+                        assertThat("we got a TimeoutException", x, instanceOf(TimeoutException.class));
+                        timeoutReceivedLatch.countDown();
+                    }
+                });
+        stream.setIdleTimeout(idleTimeout);
+
+        assertThat("idle timeout hit", timeoutReceivedLatch.await(5, TimeUnit.SECONDS), is(true));
+    }
+
+    @Test
+    public void testIdleTimeoutSetOnConnectionOnly() throws Exception
+    {
+        final int idleTimeout = 500;
+        final CountDownLatch timeoutReceivedLatch = new CountDownLatch(1);
+        Session session = startClient(version, startHTTPServer(version, new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, final Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
+                    throws IOException, ServletException
+            {
+                try
+                {
+                    Thread.sleep(2 * idleTimeout);
+                }
+                catch (InterruptedException e)
+                {
+                    throw new RuntimeException(e);
+                }
+                request.setHandled(true);
+            }
+        }, idleTimeout), null);
+
+        Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "GET", "/");
+        session.syn(new SynInfo(5, TimeUnit.SECONDS, headers, true, (byte)0),
+                new StreamFrameListener.Adapter()
+                {
+                    @Override
+                    public void onFailure(Stream stream, Throwable x)
+                    {
+                        assertThat("we got a TimeoutException", x, instanceOf(TimeoutException.class));
+                        timeoutReceivedLatch.countDown();
+                    }
+                });
+
+        assertThat("idle timeout hit", timeoutReceivedLatch.await(5, TimeUnit.SECONDS), is(true));
+    }
+
+    @Test
+    public void testSingleStreamIdleTimeout() throws Exception
+    {
+        final int idleTimeout = 500;
+        final CountDownLatch timeoutReceivedLatch = new CountDownLatch(1);
+        final CountDownLatch replyReceivedLatch = new CountDownLatch(3);
+        Session session = startClient(version, startHTTPServer(version, new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, final Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
+                    throws IOException, ServletException
+            {
+                if ("true".equals(request.getHeader("slow")))
+                {
+                    try
+                    {
+                        Thread.sleep(2 * idleTimeout);
+                    }
+                    catch (InterruptedException e)
+                    {
+                        throw new RuntimeException(e);
+                    }
+                }
+                request.setHandled(true);
+            }
+        }, idleTimeout), null);
+
+        Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "GET", "/");
+        Fields slowHeaders = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "GET", "/");
+        slowHeaders.add("slow", "true");
+        sendSingleRequestThatIsNotExpectedToTimeout(replyReceivedLatch, session, headers);
+        session.syn(new SynInfo(5, TimeUnit.SECONDS, slowHeaders, true, (byte)0),
+                new StreamFrameListener.Adapter()
+                {
+                    @Override
+                    public void onFailure(Stream stream, Throwable x)
+                    {
+                        assertThat("we got a TimeoutException", x, instanceOf(TimeoutException.class));
+                        timeoutReceivedLatch.countDown();
+                    }
+                });
+        Thread.sleep(idleTimeout / 2);
+        sendSingleRequestThatIsNotExpectedToTimeout(replyReceivedLatch, session, headers);
+        Thread.sleep(idleTimeout / 2);
+        sendSingleRequestThatIsNotExpectedToTimeout(replyReceivedLatch, session, headers);
+        assertThat("idle timeout hit", timeoutReceivedLatch.await(5, TimeUnit.SECONDS), is(true));
+        assertThat("received replies on 3 non idle requests", replyReceivedLatch.await(5, TimeUnit.SECONDS),
+                is(true));
+    }
+
+    private void sendSingleRequestThatIsNotExpectedToTimeout(final CountDownLatch replyReceivedLatch, Session session, Fields headers) throws ExecutionException, InterruptedException, TimeoutException
+    {
+        session.syn(new SynInfo(5, TimeUnit.SECONDS, headers, true, (byte)0),
+                new StreamFrameListener.Adapter()
+                {
+                    @Override
+                    public void onReply(Stream stream, ReplyInfo replyInfo)
+                    {
+                        replyReceivedLatch.countDown();
+                    }
+                });
+    }
+
+    private class AsyncListenerAdapter implements AsyncListener
+    {
+        @Override
+        public void onStartAsync(AsyncEvent event) throws IOException
+        {
+        }
+
+        @Override
+        public void onComplete(AsyncEvent event) throws IOException
+        {
+        }
+
+        @Override
+        public void onTimeout(AsyncEvent event) throws IOException
+        {
+            event.getAsyncContext().dispatch();
+        }
+
+        @Override
+        public void onError(AsyncEvent event) throws IOException
+        {
+        }
+    }
+}
diff --git a/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/SimpleHTTPBenchmarkTest.java b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/SimpleHTTPBenchmarkTest.java
new file mode 100644
index 0000000..9f3a3c7
--- /dev/null
+++ b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/SimpleHTTPBenchmarkTest.java
@@ -0,0 +1,160 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.spdy.server.http;
+
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+import java.util.Random;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.spdy.api.DataInfo;
+import org.eclipse.jetty.spdy.api.ReplyInfo;
+import org.eclipse.jetty.spdy.api.Session;
+import org.eclipse.jetty.spdy.api.Stream;
+import org.eclipse.jetty.spdy.api.StreamFrameListener;
+import org.eclipse.jetty.spdy.api.SynInfo;
+import org.eclipse.jetty.spdy.http.HTTPSPDYHeader;
+import org.eclipse.jetty.util.Fields;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.hamcrest.CoreMatchers;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+
+ at Ignore("So far only used for testing performance tweaks. So no need to run it in a build")
+public class SimpleHTTPBenchmarkTest extends AbstractHTTPSPDYTest
+{
+    private static final Logger LOG = Log.getLogger(SimpleHTTPBenchmarkTest.class);
+    private final int dataSize = 4096 * 100;
+    private Session session;
+    private int requestCount = 100;
+
+    public SimpleHTTPBenchmarkTest(short version)
+    {
+        super(version);
+    }
+
+    @Before
+    public void setUp() throws Exception
+    {
+        final byte[] data = new byte[dataSize];
+        new Random().nextBytes(data);
+        session = startClient(version, startHTTPServer(version, new AbstractHandler()
+        {
+            @Override
+            public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
+                    throws IOException, ServletException
+            {
+                request.setHandled(true);
+                assertEquals("GET", httpRequest.getMethod());
+                assertThat("accept-encoding is set to gzip, even if client didn't set it",
+                        httpRequest.getHeader("accept-encoding"), containsString("gzip"));
+                assertThat(httpRequest.getHeader("host"), is("localhost:" + connector.getLocalPort()));
+                httpResponse.getOutputStream().write(data);
+            }
+        }, 0), null);
+    }
+
+    @Test
+    public void testRunBenchmark() throws Exception
+    {
+        long overallStart = System.nanoTime();
+        int iterations = 20;
+        for (int j = 0; j < iterations; j++)
+        {
+            long start = System.nanoTime();
+            for (int i = 0; i < requestCount; i++)
+                sendGetRequestWithData();
+            long timeElapsed = System.nanoTime() - start;
+            LOG.info("Requests with {}b response took: {}ms", dataSize, timeElapsed / 1000 / 1000);
+        }
+        long timeElapsedOverall = (System.nanoTime() - overallStart) / 1000 / 1000;
+        LOG.info("Time elapsed overall: {}ms avg: {}ms", timeElapsedOverall, timeElapsedOverall / iterations);
+    }
+
+    private void sendGetRequest() throws Exception
+    {
+        final CountDownLatch replyLatch = new CountDownLatch(1);
+        final String path = "/foo";
+
+        Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getLocalPort(), version, "GET", path);
+        session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter()
+        {
+            @Override
+            public void onReply(Stream stream, ReplyInfo replyInfo)
+            {
+                assertTrue(replyInfo.isClose());
+                Fields replyHeaders = replyInfo.getHeaders();
+                assertThat(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200"), CoreMatchers.is(true));
+                assertThat(replyHeaders.get(HttpHeader.SERVER.asString()), CoreMatchers.is(notNullValue()));
+                assertThat(replyHeaders.get(HttpHeader.X_POWERED_BY.asString()), CoreMatchers.is(notNullValue()));
+                replyLatch.countDown();
+            }
+        });
+
+        assertThat("reply has been received", replyLatch.await(5, TimeUnit.SECONDS), is(true));
+    }
+
+    private void sendGetRequestWithData() throws Exception
+    {
+        final CountDownLatch replyLatch = new CountDownLatch(1);
+        final CountDownLatch dataLatch = new CountDownLatch(1);
+        final String path = "/foo";
+
+        Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getLocalPort(), version, "GET", path);
+        session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter()
+        {
+            @Override
+            public void onReply(Stream stream, ReplyInfo replyInfo)
+            {
+                Fields replyHeaders = replyInfo.getHeaders();
+                assertThat(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).getValue().contains("200"), CoreMatchers.is(true));
+                assertThat(replyHeaders.get(HttpHeader.SERVER.asString()), CoreMatchers.is(notNullValue()));
+                assertThat(replyHeaders.get(HttpHeader.X_POWERED_BY.asString()), CoreMatchers.is(notNullValue()));
+                replyLatch.countDown();
+            }
+
+            @Override
+            public void onData(Stream stream, DataInfo dataInfo)
+            {
+                dataInfo.consume(dataInfo.available());
+                if (dataInfo.isClose())
+                    dataLatch.countDown();
+            }
+        });
+
+        assertThat("reply has been received", replyLatch.await(5, TimeUnit.SECONDS), is(true));
+        assertThat("data has been received", dataLatch.await(5, TimeUnit.SECONDS), is(true));
+    }
+}
diff --git a/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/proxy/ProxyHTTPToSPDYTest.java b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/proxy/ProxyHTTPToSPDYTest.java
new file mode 100644
index 0000000..cbf9202
--- /dev/null
+++ b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/proxy/ProxyHTTPToSPDYTest.java
@@ -0,0 +1,408 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.spdy.server.proxy;
+
+import java.net.InetSocketAddress;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.util.StringContentProvider;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.spdy.api.BytesDataInfo;
+import org.eclipse.jetty.spdy.api.DataInfo;
+import org.eclipse.jetty.spdy.api.GoAwayResultInfo;
+import org.eclipse.jetty.spdy.api.PushInfo;
+import org.eclipse.jetty.spdy.api.ReplyInfo;
+import org.eclipse.jetty.spdy.api.RstInfo;
+import org.eclipse.jetty.spdy.api.SPDY;
+import org.eclipse.jetty.spdy.api.Session;
+import org.eclipse.jetty.spdy.api.Stream;
+import org.eclipse.jetty.spdy.api.StreamFrameListener;
+import org.eclipse.jetty.spdy.api.StreamStatus;
+import org.eclipse.jetty.spdy.api.SynInfo;
+import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener;
+import org.eclipse.jetty.spdy.client.SPDYClient;
+import org.eclipse.jetty.spdy.http.HTTPSPDYHeader;
+import org.eclipse.jetty.spdy.server.SPDYServerConnectionFactory;
+import org.eclipse.jetty.spdy.server.SPDYServerConnector;
+import org.eclipse.jetty.toolchain.test.TestTracker;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.Fields;
+import org.eclipse.jetty.util.Promise;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+ at RunWith(Parameterized.class)
+public class ProxyHTTPToSPDYTest
+{
+    @Parameterized.Parameters
+    public static Collection<Short[]> parameters()
+    {
+        return Arrays.asList(new Short[]{SPDY.V2}, new Short[]{SPDY.V3});
+    }
+
+    @Rule
+    public final TestTracker tracker = new TestTracker();
+    private final short version;
+    private HttpClient httpClient;
+    private HttpClient httpClient2;
+    private SPDYClient.Factory factory;
+    private Server server;
+    private Server proxy;
+    private ServerConnector proxyConnector;
+
+    public ProxyHTTPToSPDYTest(short version)
+    {
+        this.version = version;
+    }
+
+    protected InetSocketAddress startServer(ServerSessionFrameListener listener) throws Exception
+    {
+        server = new Server();
+        SPDYServerConnector serverConnector = new SPDYServerConnector(server, listener);
+        serverConnector.addConnectionFactory(new SPDYServerConnectionFactory(version, listener));
+        serverConnector.setPort(0);
+        server.addConnector(serverConnector);
+        server.start();
+        return new InetSocketAddress("localhost", serverConnector.getLocalPort());
+    }
+
+    protected InetSocketAddress startProxy(InetSocketAddress address) throws Exception
+    {
+        proxy = new Server();
+        ProxyEngineSelector proxyEngineSelector = new ProxyEngineSelector();
+        SPDYProxyEngine spdyProxyEngine = new SPDYProxyEngine(factory);
+        proxyEngineSelector.putProxyEngine("spdy/" + version, spdyProxyEngine);
+        proxyEngineSelector.putProxyServerInfo("localhost", new ProxyEngineSelector.ProxyServerInfo("spdy/" + version, address.getHostName(), address.getPort()));
+        proxyConnector = new HTTPSPDYProxyServerConnector(proxy, proxyEngineSelector);
+        proxyConnector.setPort(9999);
+        proxy.addConnector(proxyConnector);
+        proxy.start();
+        return new InetSocketAddress("localhost", proxyConnector.getLocalPort());
+    }
+
+    @Before
+    public void init() throws Exception
+    {
+        factory = new SPDYClient.Factory();
+        factory.start();
+        httpClient = new HttpClient();
+        httpClient.start();
+        httpClient2 = new HttpClient();
+        httpClient2.start();
+    }
+
+    @After
+    public void destroy() throws Exception
+    {
+        httpClient.stop();
+        httpClient2.stop();
+        if (server != null)
+        {
+            server.stop();
+            server.join();
+        }
+        if (proxy != null)
+        {
+            proxy.stop();
+            proxy.join();
+        }
+        factory.stop();
+    }
+
+    @Test
+    public void testClosingClientDoesNotCloseServer() throws Exception
+    {
+        final CountDownLatch closeLatch = new CountDownLatch(1);
+        InetSocketAddress proxyAddress = startProxy(startServer(new ServerSessionFrameListener.Adapter()
+        {
+            @Override
+            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
+            {
+                Fields responseHeaders = new Fields();
+                responseHeaders.put(HTTPSPDYHeader.VERSION.name(version), "HTTP/1.1");
+                responseHeaders.put(HTTPSPDYHeader.STATUS.name(version), "200 OK");
+                stream.reply(new ReplyInfo(responseHeaders, true), new Callback.Adapter());
+                return null;
+            }
+
+            @Override
+            public void onGoAway(Session session, GoAwayResultInfo goAwayInfo)
+            {
+                closeLatch.countDown();
+            }
+        }));
+
+        Request request = httpClient.newRequest("localhost", proxyAddress.getPort()).method("GET");
+        request.header("Connection", "close");
+        ContentResponse response = request.send();
+
+        assertThat("response status is 200 OK", response.getStatus(), is(200));
+
+        // Must not close, other clients may still be connected
+        Assert.assertFalse(closeLatch.await(1, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testGETThenNoContentFromTwoClients() throws Exception
+    {
+        InetSocketAddress proxyAddress = startProxy(startServer(new ServerSessionFrameListener.Adapter()
+        {
+            @Override
+            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
+            {
+                Assert.assertTrue(synInfo.isClose());
+                Fields requestHeaders = synInfo.getHeaders();
+                Assert.assertNotNull(requestHeaders.get("via"));
+
+                Fields responseHeaders = new Fields();
+                responseHeaders.put(HTTPSPDYHeader.VERSION.name(version), "HTTP/1.1");
+                responseHeaders.put(HTTPSPDYHeader.STATUS.name(version), "200 OK");
+                ReplyInfo replyInfo = new ReplyInfo(responseHeaders, true);
+                stream.reply(replyInfo, new Callback.Adapter());
+                return null;
+            }
+        }));
+
+        ContentResponse response = httpClient.newRequest("localhost", proxyAddress.getPort()).method(HttpMethod.GET)
+                .send();
+        assertThat("response code is 200 OK", response.getStatus(), is(200));
+
+        // Perform another request with another client
+        ContentResponse response2 = httpClient2.newRequest("localhost", proxyAddress.getPort()).method(HttpMethod.GET)
+                .send();
+        assertThat("response2 code is 200 OK", response2.getStatus(), is(200));
+    }
+
+    @Test
+    public void testHEADRequest() throws Exception
+    {
+        InetSocketAddress proxyAddress = startProxy(startServer(new ServerSessionFrameListener.Adapter()
+        {
+            @Override
+            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
+            {
+                Assert.assertTrue(synInfo.isClose());
+                Fields requestHeaders = synInfo.getHeaders();
+                Assert.assertNotNull(requestHeaders.get("via"));
+
+                Fields responseHeaders = new Fields();
+                responseHeaders.put(HTTPSPDYHeader.VERSION.name(version), "HTTP/1.1");
+                responseHeaders.put(HTTPSPDYHeader.STATUS.name(version), "200 OK");
+                ReplyInfo replyInfo = new ReplyInfo(responseHeaders, true);
+                stream.reply(replyInfo, new Callback.Adapter());
+
+                return null;
+            }
+        }));
+        ContentResponse response = httpClient.newRequest("localhost", proxyAddress.getPort()).method(HttpMethod.HEAD).send();
+        assertThat("response code is 200 OK", response.getStatus(), is(200));
+    }
+
+    @Test
+    public void testGETThenSmallResponseContent() throws Exception
+    {
+        final byte[] data = "0123456789ABCDEF".getBytes(StandardCharsets.UTF_8);
+        InetSocketAddress proxyAddress = startProxy(startServer(new ServerSessionFrameListener.Adapter()
+        {
+            @Override
+            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
+            {
+                Assert.assertTrue(synInfo.isClose());
+                Fields requestHeaders = synInfo.getHeaders();
+                Assert.assertNotNull(requestHeaders.get("via"));
+
+                Fields responseHeaders = new Fields();
+                responseHeaders.put(HTTPSPDYHeader.VERSION.name(version), "HTTP/1.1");
+                responseHeaders.put(HTTPSPDYHeader.STATUS.name(version), "200 OK");
+                responseHeaders.put("content-length", String.valueOf(data.length));
+
+                ReplyInfo replyInfo = new ReplyInfo(responseHeaders, false);
+                stream.reply(replyInfo, new Callback.Adapter());
+                stream.data(new BytesDataInfo(data, true), new Callback.Adapter());
+
+                return null;
+            }
+        }));
+
+        ContentResponse response = httpClient.newRequest("localhost", proxyAddress.getPort()).method(HttpMethod.GET)
+                .send();
+        assertThat("response code is 200 OK", response.getStatus(), is(200));
+        assertThat(Arrays.equals(response.getContent(), data), is(true));
+
+        // Perform another request so that we are sure we reset the states of parsers and generators
+        ContentResponse response2 = httpClient.newRequest("localhost", proxyAddress.getPort()).method(HttpMethod.GET)
+                .send();
+        assertThat("response2 code is 200 OK", response2.getStatus(), is(200));
+        assertThat(Arrays.equals(response2.getContent(), data), is(true));
+    }
+
+    @Test
+    public void testPOSTWithSmallRequestContentThenRedirect() throws Exception
+    {
+        final String data = "0123456789ABCDEF";
+        InetSocketAddress proxyAddress = startProxy(startServer(new ServerSessionFrameListener.Adapter()
+        {
+            @Override
+            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
+            {
+                return new StreamFrameListener.Adapter()
+                {
+                    @Override
+                    public void onData(Stream stream, DataInfo dataInfo)
+                    {
+                        dataInfo.consume(dataInfo.length());
+                        if (dataInfo.isClose())
+                        {
+                            Fields headers = new Fields();
+                            headers.put(HTTPSPDYHeader.VERSION.name(version), "HTTP/1.1");
+                            headers.put(HTTPSPDYHeader.STATUS.name(version), "303 See Other");
+                            headers.put(HttpHeader.LOCATION.asString(),"http://other.location");
+                            stream.reply(new ReplyInfo(headers, true), new Callback.Adapter());
+                        }
+                    }
+                };
+            }
+        }));
+
+        ContentResponse response = httpClient.newRequest("localhost", proxyAddress.getPort()).method(HttpMethod.POST).content(new
+                StringContentProvider(data)).followRedirects(false).send();
+        assertThat("response code is 303", response.getStatus(), is(303));
+
+        // Perform another request so that we are sure we reset the states of parsers and generators
+        ContentResponse response2 = httpClient.newRequest("localhost", proxyAddress.getPort()).method(HttpMethod
+                .POST).content(new StringContentProvider(data)).followRedirects(false).send();
+        assertThat("response2 code is 303", response2.getStatus(), is(303));
+    }
+
+    @Test
+    public void testPOSTWithSmallRequestContentThenSmallResponseContent() throws Exception
+    {
+        String dataString = "0123456789ABCDEF";
+        final byte[] data = dataString.getBytes(StandardCharsets.UTF_8);
+        InetSocketAddress proxyAddress = startProxy(startServer(new ServerSessionFrameListener.Adapter()
+        {
+            @Override
+            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
+            {
+                return new StreamFrameListener.Adapter()
+                {
+                    @Override
+                    public void onData(Stream stream, DataInfo dataInfo)
+                    {
+                        dataInfo.consume(dataInfo.length());
+                        if (dataInfo.isClose())
+                        {
+                            Fields responseHeaders = new Fields();
+                            responseHeaders.put(HTTPSPDYHeader.VERSION.name(version), "HTTP/1.1");
+                            responseHeaders.put(HTTPSPDYHeader.STATUS.name(version), "200 OK");
+                            responseHeaders.put("content-length", String.valueOf(data.length));
+                            ReplyInfo replyInfo = new ReplyInfo(responseHeaders, false);
+                            stream.reply(replyInfo, new Callback.Adapter());
+                            stream.data(new BytesDataInfo(data, true), new Callback.Adapter());
+                        }
+                    }
+                };
+            }
+        }));
+
+        ContentResponse response = httpClient.POST("http://localhost:" + proxyAddress.getPort() + "/").content(new
+                StringContentProvider(dataString)).send();
+        assertThat("response status is 200 OK", response.getStatus(), is(200));
+        assertThat("response content matches expected dataString", response.getContentAsString(), is(dataString));
+
+        // Perform another request so that we are sure we reset the states of parsers and generators
+        response = httpClient.POST("http://localhost:" + proxyAddress.getPort() + "/").content(new
+                StringContentProvider(dataString)).send();
+        assertThat("response status is 200 OK", response.getStatus(), is(200));
+        assertThat("response content matches expected dataString", response.getContentAsString(), is(dataString));
+    }
+
+    @Test
+    public void testGETThenSPDYPushIsIgnored() throws Exception
+    {
+        final byte[] data = "0123456789ABCDEF".getBytes(StandardCharsets.UTF_8);
+        InetSocketAddress proxyAddress = startProxy(startServer(new ServerSessionFrameListener.Adapter()
+        {
+            @Override
+            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
+            {
+                Fields responseHeaders = new Fields();
+                responseHeaders.put(HTTPSPDYHeader.VERSION.name(version), "HTTP/1.1");
+                responseHeaders.put(HTTPSPDYHeader.STATUS.name(version), "200 OK");
+
+                Fields pushHeaders = new Fields();
+                pushHeaders.put(HTTPSPDYHeader.URI.name(version), "/push");
+                stream.push(new PushInfo(5, TimeUnit.SECONDS, pushHeaders, false), new Promise.Adapter<Stream>()
+                {
+                    @Override
+                    public void succeeded(Stream pushStream)
+                    {
+                        pushStream.data(new BytesDataInfo(data, true), new Callback.Adapter());
+                    }
+                });
+
+                stream.reply(new ReplyInfo(responseHeaders, true), new Callback.Adapter());
+                return null;
+            }
+        }));
+
+        ContentResponse response = httpClient.newRequest("localhost", proxyAddress.getPort()).method(HttpMethod.GET).send();
+        assertThat("response code is 200 OK", response.getStatus(), is(200));
+    }
+
+    @Test
+    public void testGETThenReset() throws Exception
+    {
+        InetSocketAddress proxyAddress = startProxy(startServer(new ServerSessionFrameListener.Adapter()
+        {
+            @Override
+            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
+            {
+                Assert.assertTrue(synInfo.isClose());
+                Fields requestHeaders = synInfo.getHeaders();
+                Assert.assertNotNull(requestHeaders.get("via"));
+
+                stream.getSession().rst(new RstInfo(stream.getId(), StreamStatus.REFUSED_STREAM), new Callback.Adapter());
+
+                return null;
+            }
+        }));
+
+        ContentResponse response = httpClient.newRequest("localhost", proxyAddress.getPort()).method(HttpMethod.GET).send();
+        assertThat("response code is 502 Gateway Error", response.getStatus(), is(502));
+    }
+}
diff --git a/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/proxy/ProxySPDYToHTTPLoadTest.java b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/proxy/ProxySPDYToHTTPLoadTest.java
new file mode 100644
index 0000000..a883e63
--- /dev/null
+++ b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/proxy/ProxySPDYToHTTPLoadTest.java
@@ -0,0 +1,319 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.spdy.server.proxy;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.NegotiatingServerConnectionFactory;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.handler.DefaultHandler;
+import org.eclipse.jetty.spdy.api.DataInfo;
+import org.eclipse.jetty.spdy.api.ReplyInfo;
+import org.eclipse.jetty.spdy.api.SPDY;
+import org.eclipse.jetty.spdy.api.Session;
+import org.eclipse.jetty.spdy.api.Stream;
+import org.eclipse.jetty.spdy.api.StreamFrameListener;
+import org.eclipse.jetty.spdy.api.StringDataInfo;
+import org.eclipse.jetty.spdy.api.SynInfo;
+import org.eclipse.jetty.spdy.client.SPDYClient;
+import org.eclipse.jetty.spdy.server.http.SPDYTestUtils;
+import org.eclipse.jetty.toolchain.test.TestTracker;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.Fields;
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import static junit.framework.Assert.fail;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.junit.Assert.assertThat;
+
+ at Ignore
+ at RunWith(value = Parameterized.class)
+public abstract class ProxySPDYToHTTPLoadTest
+{
+    private static final Logger LOG = Log.getLogger(ProxySPDYToHTTPLoadTest.class);
+
+    @Parameterized.Parameters
+    public static Collection<Short[]> parameters()
+    {
+        return Arrays.asList(new Short[]{SPDY.V2}, new Short[]{SPDY.V3});
+    }
+
+    @Rule
+    public final TestTracker tracker = new TestTracker();
+    private final short version;
+    private final NegotiatingServerConnectionFactory negotiator;
+    private final String server1String = "server1";
+    private final String server2String = "server2";
+    private SPDYClient.Factory factory;
+    private Server server1;
+    private Server server2;
+    private Server proxy;
+    private ServerConnector proxyConnector;
+    private SslContextFactory sslContextFactory = SPDYTestUtils.newSslContextFactory();
+
+    public ProxySPDYToHTTPLoadTest(short version, NegotiatingServerConnectionFactory negotiator)
+    {
+        this.version = version;
+        this.negotiator = negotiator;
+    }
+
+    @Before
+    public void init() throws Exception
+    {
+        // change the ports if you want to trace the network traffic
+        server1 = startServer(new TestServerHandler(server1String, null), 0);
+        server2 = startServer(new TestServerHandler(server2String, null), 0);
+        factory = new SPDYClient.Factory(sslContextFactory);
+        factory.start();
+    }
+
+    @After
+    public void destroy() throws Exception
+    {
+        stopServer(server1);
+        stopServer(server2);
+        if (proxy != null)
+        {
+            proxy.stop();
+            proxy.join();
+        }
+        factory.stop();
+    }
+
+    private void stopServer(Server server) throws Exception
+    {
+        if (server != null)
+        {
+            server.stop();
+            server.join();
+        }
+    }
+
+    protected Server startServer(Handler handler, int port) throws Exception
+    {
+        QueuedThreadPool threadPool = new QueuedThreadPool(256);
+        threadPool.setName("upstreamServerQTP");
+        Server server = new Server(threadPool);
+        ServerConnector connector = new ServerConnector(server);
+        connector.setPort(port);
+        server.setHandler(handler);
+        server.addConnector(connector);
+        server.start();
+        return server;
+    }
+
+    private InetSocketAddress getServerAddress(Server server)
+    {
+        return new InetSocketAddress("localhost", ((ServerConnector)server.getConnectors()[0]).getLocalPort());
+    }
+
+    protected InetSocketAddress startProxy(InetSocketAddress server1, InetSocketAddress server2,
+                                           long proxyConnectorTimeout, long proxyEngineTimeout) throws Exception
+    {
+        QueuedThreadPool threadPool = new QueuedThreadPool(256);
+        threadPool.setName("proxyQTP");
+        proxy = new Server(threadPool);
+        ProxyEngineSelector proxyEngineSelector = new ProxyEngineSelector();
+        HttpClient httpClient = new HttpClient();
+        httpClient.start();
+        httpClient.setIdleTimeout(proxyEngineTimeout);
+        HTTPProxyEngine httpProxyEngine = new HTTPProxyEngine(httpClient);
+        proxyEngineSelector.putProxyEngine("http/1.1", httpProxyEngine);
+
+        proxyEngineSelector.putProxyServerInfo("localhost", new ProxyEngineSelector.ProxyServerInfo("http/1.1",
+                server1.getHostName(), server1.getPort()));
+        // server2 will be available at two different ProxyServerInfos with different hosts
+        proxyEngineSelector.putProxyServerInfo("127.0.0.1", new ProxyEngineSelector.ProxyServerInfo("http/1.1",
+                server2.getHostName(), server2.getPort()));
+        proxyEngineSelector.putProxyServerInfo("127.0.0.2", new ProxyEngineSelector.ProxyServerInfo("http/1.1",
+                server2.getHostName(), server2.getPort()));
+
+        proxyConnector = new HTTPSPDYProxyServerConnector(proxy, sslContextFactory, new HttpConfiguration(), proxyEngineSelector, negotiator);
+        proxyConnector.setPort(0);
+        proxyConnector.setIdleTimeout(proxyConnectorTimeout);
+        proxy.addConnector(proxyConnector);
+        proxy.start();
+        return new InetSocketAddress("localhost", proxyConnector.getLocalPort());
+    }
+
+    @Test
+    public void testSimpleLoadTest() throws Exception
+    {
+        final InetSocketAddress proxyAddress = startProxy(getServerAddress(server1), getServerAddress(server2), 30000,
+                30000);
+
+        final int requestsPerClient = 50;
+
+        ExecutorService executorService = Executors.newFixedThreadPool(3);
+
+        Runnable client1 = createClientRunnable(proxyAddress, requestsPerClient, server1String, "localhost");
+        Runnable client2 = createClientRunnable(proxyAddress, requestsPerClient, server2String, "127.0.0.1");
+        Runnable client3 = createClientRunnable(proxyAddress, requestsPerClient, server2String, "127.0.0.2");
+
+        List<Future> futures = new ArrayList<>();
+
+        futures.add(executorService.submit(client1));
+        futures.add(executorService.submit(client2));
+        futures.add(executorService.submit(client3));
+
+        for (Future future : futures)
+        {
+            future.get(60, TimeUnit.SECONDS);
+        }
+    }
+
+    private Runnable createClientRunnable(final InetSocketAddress proxyAddress, final int requestsPerClient,
+                                          final String serverIdentificationString, final String serverHost)
+    {
+        Runnable client = new Runnable()
+        {
+            @Override
+            public void run()
+            {
+                try
+                {
+                    Session client = factory.newSPDYClient(version).connect(proxyAddress, null);
+                    for (int i = 0; i < requestsPerClient; i++)
+                    {
+                        sendSingleClientRequest(proxyAddress, client, serverIdentificationString, serverHost);
+                    }
+                }
+                catch (InterruptedException | ExecutionException | TimeoutException e)
+                {
+                    e.printStackTrace();
+                    fail();
+                }
+            }
+        };
+        return client;
+    }
+
+    private void sendSingleClientRequest(InetSocketAddress proxyAddress, Session client, final String serverIdentificationString, String serverHost) throws ExecutionException, InterruptedException, TimeoutException
+    {
+        final String data = UUID.randomUUID().toString();
+
+        final CountDownLatch replyLatch = new CountDownLatch(1);
+        final CountDownLatch dataLatch = new CountDownLatch(1);
+
+        Fields headers = SPDYTestUtils.createHeaders(serverHost, proxyAddress.getPort(), version, "POST", "/");
+
+        Stream stream = client.syn(new SynInfo(headers, false), new StreamFrameListener.Adapter()
+        {
+            private final ByteArrayOutputStream result = new ByteArrayOutputStream();
+
+            @Override
+            public void onReply(Stream stream, ReplyInfo replyInfo)
+            {
+                LOG.debug("Got reply: {}", replyInfo);
+                Fields headers = replyInfo.getHeaders();
+                assertThat("response comes from the given server", headers.get(serverIdentificationString),
+                        is(notNullValue()));
+                replyLatch.countDown();
+            }
+
+            @Override
+            public void onData(Stream stream, DataInfo dataInfo)
+            {
+                result.write(dataInfo.asBytes(true), 0, dataInfo.length());
+                if (dataInfo.isClose())
+                {
+                    LOG.debug("Got last dataFrame: {}", dataInfo);
+                    assertThat("received data matches send data", result.toString(), is(data));
+                    dataLatch.countDown();
+                }
+            }
+        });
+
+        stream.data(new StringDataInfo(data, true), new Callback.Adapter());
+
+        assertThat("reply has been received", replyLatch.await(15, TimeUnit.SECONDS), is(true));
+        assertThat("data has been received", dataLatch.await(15, TimeUnit.SECONDS), is(true));
+        LOG.debug("Successfully received response");
+    }
+
+    private class TestServerHandler extends DefaultHandler
+    {
+        private final String responseHeader;
+        private final byte[] responseData;
+
+        private TestServerHandler(String responseHeader, byte[] responseData)
+        {
+            this.responseHeader = responseHeader;
+            this.responseData = responseData;
+        }
+
+        @Override
+        public void handle(String target, Request baseRequest, HttpServletRequest request,
+                           HttpServletResponse response) throws IOException, ServletException
+        {
+            assertThat("Via Header is set", baseRequest.getHeader("X-Forwarded-For"), is(notNullValue()));
+            assertThat("X-Forwarded-For Header is set", baseRequest.getHeader("X-Forwarded-For"),
+                    is(notNullValue()));
+            assertThat("X-Forwarded-Host Header is set", baseRequest.getHeader("X-Forwarded-Host"),
+                    is(notNullValue()));
+            assertThat("X-Forwarded-Proto Header is set", baseRequest.getHeader("X-Forwarded-Proto"),
+                    is(notNullValue()));
+            assertThat("X-Forwarded-Server Header is set", baseRequest.getHeader("X-Forwarded-Server"),
+                    is(notNullValue()));
+            baseRequest.setHandled(true);
+
+            IO.copy(request.getInputStream(), response.getOutputStream());
+
+            if (responseHeader != null)
+                response.addHeader(responseHeader, "bar");
+            if (responseData != null)
+                response.getOutputStream().write(responseData);
+        }
+    }
+
+}
diff --git a/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/proxy/ProxySPDYToHTTPTest.java b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/proxy/ProxySPDYToHTTPTest.java
new file mode 100644
index 0000000..581ff28
--- /dev/null
+++ b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/proxy/ProxySPDYToHTTPTest.java
@@ -0,0 +1,545 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.spdy.server.proxy;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.HttpChannel;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.handler.DefaultHandler;
+import org.eclipse.jetty.spdy.api.DataInfo;
+import org.eclipse.jetty.spdy.api.GoAwayResultInfo;
+import org.eclipse.jetty.spdy.api.PingInfo;
+import org.eclipse.jetty.spdy.api.PingResultInfo;
+import org.eclipse.jetty.spdy.api.ReplyInfo;
+import org.eclipse.jetty.spdy.api.RstInfo;
+import org.eclipse.jetty.spdy.api.SPDY;
+import org.eclipse.jetty.spdy.api.Session;
+import org.eclipse.jetty.spdy.api.SessionFrameListener;
+import org.eclipse.jetty.spdy.api.Stream;
+import org.eclipse.jetty.spdy.api.StreamFrameListener;
+import org.eclipse.jetty.spdy.api.StringDataInfo;
+import org.eclipse.jetty.spdy.api.SynInfo;
+import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener;
+import org.eclipse.jetty.spdy.client.SPDYClient;
+import org.eclipse.jetty.spdy.http.HTTPSPDYHeader;
+import org.eclipse.jetty.spdy.server.http.SPDYTestUtils;
+import org.eclipse.jetty.toolchain.test.TestTracker;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.Fields;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.StdErrLog;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.hamcrest.Matchers.nullValue;
+import static org.junit.Assert.assertThat;
+
+ at RunWith(value = Parameterized.class)
+public abstract class ProxySPDYToHTTPTest
+{
+    @Parameterized.Parameters
+    public static Collection<Short[]> parameters()
+    {
+        return Arrays.asList(new Short[]{SPDY.V2}, new Short[]{SPDY.V3});
+    }
+
+    @Rule
+    public final TestTracker tracker = new TestTracker();
+    private final short version;
+    private SPDYClient.Factory factory;
+    private Server server;
+    private Server proxy;
+    private ServerConnector proxyConnector;
+    private SslContextFactory sslContextFactory = SPDYTestUtils.newSslContextFactory();
+
+    public ProxySPDYToHTTPTest(short version)
+    {
+        this.version = version;
+    }
+
+    protected InetSocketAddress startServer(Handler handler) throws Exception
+    {
+        server = new Server();
+        ServerConnector connector = new ServerConnector(server);
+        server.setHandler(handler);
+        server.addConnector(connector);
+        server.start();
+        return new InetSocketAddress("localhost", connector.getLocalPort());
+    }
+
+    protected InetSocketAddress startProxy(InetSocketAddress address, long proxyConnectorTimeout, long proxyEngineTimeout) throws Exception
+    {
+        proxy = new Server();
+        ProxyEngineSelector proxyEngineSelector = new ProxyEngineSelector();
+        HttpClient httpClient = new HttpClient();
+        httpClient.start();
+        httpClient.setIdleTimeout(proxyEngineTimeout);
+        HTTPProxyEngine httpProxyEngine = new HTTPProxyEngine(httpClient);
+        proxyEngineSelector.putProxyEngine("http/1.1", httpProxyEngine);
+        proxyEngineSelector.putProxyServerInfo("localhost", new ProxyEngineSelector.ProxyServerInfo("http/1.1", address.getHostName(), address.getPort()));
+        proxyConnector = new HTTPSPDYProxyServerConnector(proxy, sslContextFactory, proxyEngineSelector);
+        proxyConnector.setPort(0);
+        proxyConnector.setIdleTimeout(proxyConnectorTimeout);
+        proxy.addConnector(proxyConnector);
+        proxy.start();
+        return new InetSocketAddress("localhost", proxyConnector.getLocalPort());
+    }
+
+    @Before
+    public void init() throws Exception
+    {
+        factory = new SPDYClient.Factory(sslContextFactory);
+        factory.start();
+    }
+
+    @After
+    public void destroy() throws Exception
+    {
+        if (server != null)
+        {
+            server.stop();
+            server.join();
+        }
+        if (proxy != null)
+        {
+            proxy.stop();
+            proxy.join();
+        }
+        factory.stop();
+        ((StdErrLog)Log.getLogger(HttpChannel.class)).setHideStacks(false);
+    }
+
+    @Test
+    public void testSYNThenREPLY() throws Exception
+    {
+        final String header = "foo";
+
+        InetSocketAddress proxyAddress = startProxy(startServer(new TestServerHandler(header, null)), 30000, 30000);
+
+        Session client = factory.newSPDYClient(version).connect(proxyAddress, null);
+
+        final CountDownLatch replyLatch = new CountDownLatch(1);
+
+        Fields headers = SPDYTestUtils.createHeaders("localhost", proxyAddress.getPort(), version, "GET", "/");
+        headers.put(header, "bar");
+        headers.put("connection", "close");
+
+        client.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter()
+        {
+            @Override
+            public void onReply(Stream stream, ReplyInfo replyInfo)
+            {
+                Fields headers = replyInfo.getHeaders();
+                assertThat("Version header is set", headers.get(HTTPSPDYHeader.VERSION.name(version)), is(notNullValue()));
+                assertThat("Custom set header foo is set on response", headers.get(header), is(notNullValue()));
+                assertThat("HOP headers like connection are removed before forwarding",
+                        headers.get("connection"), is(nullValue()));
+                replyLatch.countDown();
+            }
+        });
+
+        assertThat("Reply is send to SPDY client", replyLatch.await(5, TimeUnit.SECONDS), is(true));
+    }
+
+    @Test
+    public void testSYNThenREPLYAndDATA() throws Exception
+    {
+        final byte[] data = "0123456789ABCDEF".getBytes(StandardCharsets.UTF_8);
+        final String header = "foo";
+
+        InetSocketAddress proxyAddress = startProxy(startServer(new TestServerHandler(header, data)), 30000, 30000);
+
+        Session client = factory.newSPDYClient(version).connect(proxyAddress, null);
+
+        final CountDownLatch replyLatch = new CountDownLatch(1);
+        final CountDownLatch dataLatch = new CountDownLatch(1);
+
+        Fields headers = SPDYTestUtils.createHeaders("localhost", proxyAddress.getPort(), version, "GET", "/");
+        headers.put(header, "bar");
+        headers.put("connection", "close");
+
+        client.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter()
+        {
+            private final ByteArrayOutputStream result = new ByteArrayOutputStream();
+
+            @Override
+            public void onReply(Stream stream, ReplyInfo replyInfo)
+            {
+                Fields headers = replyInfo.getHeaders();
+                assertThat("Trailer header has been filtered by proxy", headers.get("trailer"),
+                        is(nullValue()));
+                assertThat("custom header exists in response", headers.get(header), is(notNullValue()));
+                replyLatch.countDown();
+            }
+
+            @Override
+            public void onData(Stream stream, DataInfo dataInfo)
+            {
+                result.write(dataInfo.asBytes(true), 0, dataInfo.length());
+                if (dataInfo.isClose())
+                {
+                    assertThat("received data matches send data", result.toByteArray(), is(data));
+                    dataLatch.countDown();
+                }
+            }
+        });
+
+        assertThat("reply has been received", replyLatch.await(5, TimeUnit.SECONDS), is(true));
+        assertThat("data has been received", dataLatch.await(5, TimeUnit.SECONDS), is(true));
+    }
+
+    @Test
+    public void testHttpServerCommitsResponseTwice() throws Exception
+    {
+        final long timeout = 1000;
+
+        InetSocketAddress proxyAddress = startProxy(startServer(new DefaultHandler()
+        {
+            @Override
+            public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                response.addHeader("some response", "header");
+                response.flushBuffer();
+                try
+                {
+                    Thread.sleep(timeout * 2);
+                }
+                catch (InterruptedException e)
+                {
+                    e.printStackTrace();
+                }
+
+            }
+        }), 30000, timeout);
+
+        final CountDownLatch replyLatch = new CountDownLatch(1);
+        final CountDownLatch resetLatch = new CountDownLatch(1);
+
+        Session client = factory.newSPDYClient(version).connect(proxyAddress, new ServerSessionFrameListener.Adapter()
+        {
+            @Override
+            public void onRst(Session session, RstInfo rstInfo)
+            {
+                resetLatch.countDown();
+            }
+        });
+
+        Fields headers = SPDYTestUtils.createHeaders("localhost", proxyAddress.getPort(), version, "GET", "/");
+        headers.put("connection", "close");
+
+        client.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter()
+        {
+            @Override
+            public void onReply(Stream stream, ReplyInfo replyInfo)
+            {
+                replyLatch.countDown();
+            }
+        });
+
+        assertThat("reply has been received", replyLatch.await(5, TimeUnit.SECONDS), is(true));
+        assertThat("stream is reset", resetLatch.await(5, TimeUnit.SECONDS), is(true));
+    }
+
+    @Test
+    public void testHttpServerSendsRedirect() throws Exception
+    {
+        InetSocketAddress proxyAddress = startProxy(startServer(new DefaultHandler()
+        {
+            @Override
+            public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                baseRequest.setHandled(true);
+                response.setStatus(HttpServletResponse.SC_FOUND);
+                response.setHeader("Location", "http://doesnot.exist");
+            }
+        }), 30000, 30000);
+
+        final CountDownLatch replyLatch = new CountDownLatch(1);
+
+        Session client = factory.newSPDYClient(version).connect(proxyAddress, null);
+        Fields headers = SPDYTestUtils.createHeaders("localhost", proxyAddress.getPort(), version, "GET", "/");
+
+        client.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter()
+        {
+            @Override
+            public void onReply(Stream stream, ReplyInfo replyInfo)
+            {
+                assertThat("Status code is 302", replyInfo.getHeaders().get(HTTPSPDYHeader.STATUS.name(version)).getValue(),
+                        is("302"));
+                assertThat("Location header has been received", replyInfo.getHeaders().get("Location"), is(notNullValue()));
+                replyLatch.countDown();
+            }
+        });
+
+        assertThat("reply has been received", replyLatch.await(5, TimeUnit.SECONDS), is(true));
+    }
+
+    @Test
+    public void testSYNWithRequestContentThenREPLYAndDATA() throws Exception
+    {
+        final String data = "0123456789ABCDEF";
+        final String header = "foo";
+
+        InetSocketAddress proxyAddress = startProxy(startServer(new TestServerHandler(header, null)), 30000, 30000);
+
+        Session client = factory.newSPDYClient(version).connect(proxyAddress, null);
+
+        final CountDownLatch replyLatch = new CountDownLatch(1);
+        final CountDownLatch dataLatch = new CountDownLatch(1);
+
+        Fields headers = SPDYTestUtils.createHeaders("localhost", proxyAddress.getPort(), version, "POST", "/");
+        headers.put(header, "bar");
+        headers.put("connection", "close");
+
+        Stream stream = client.syn(new SynInfo(headers, false), new StreamFrameListener.Adapter()
+        {
+            private final ByteArrayOutputStream result = new ByteArrayOutputStream();
+
+            @Override
+            public void onReply(Stream stream, ReplyInfo replyInfo)
+            {
+                Fields headers = replyInfo.getHeaders();
+                assertThat("custom header exists in response", headers.get(header), is(notNullValue()));
+                replyLatch.countDown();
+            }
+
+            @Override
+            public void onData(Stream stream, DataInfo dataInfo)
+            {
+                result.write(dataInfo.asBytes(true), 0, dataInfo.length());
+                if (dataInfo.isClose())
+                {
+                    assertThat("received data matches send data", data, is(result.toString()));
+                    dataLatch.countDown();
+                }
+            }
+        });
+
+        stream.data(new StringDataInfo(data, true), new Callback.Adapter());
+
+        assertThat("reply has been received", replyLatch.await(5, TimeUnit.SECONDS), is(true));
+        assertThat("data has been received", dataLatch.await(5, TimeUnit.SECONDS), is(true));
+    }
+
+    @Test
+    public void testSYNWithSplitRequestContentThenREPLYAndDATA() throws Exception
+    {
+        final String data = "0123456789ABCDEF";
+        final String data2 = "ABCDEF";
+        final String header = "foo";
+
+        InetSocketAddress proxyAddress = startProxy(startServer(new TestServerHandler(header, null)), 30000, 30000);
+
+        Session client = factory.newSPDYClient(version).connect(proxyAddress, null);
+
+        final CountDownLatch replyLatch = new CountDownLatch(1);
+        final CountDownLatch dataLatch = new CountDownLatch(1);
+
+        Fields headers = SPDYTestUtils.createHeaders("localhost", proxyAddress.getPort(), version, "POST", "/");
+        headers.put(header, "bar");
+        headers.put("connection", "close");
+
+        Stream stream = client.syn(new SynInfo(headers, false), new StreamFrameListener.Adapter()
+        {
+            private final ByteArrayOutputStream result = new ByteArrayOutputStream();
+
+            @Override
+            public void onReply(Stream stream, ReplyInfo replyInfo)
+            {
+                Fields headers = replyInfo.getHeaders();
+                assertThat("custom header exists in response", headers.get(header), is(notNullValue()));
+                replyLatch.countDown();
+            }
+
+            @Override
+            public void onData(Stream stream, DataInfo dataInfo)
+            {
+                result.write(dataInfo.asBytes(true), 0, dataInfo.length());
+                if (dataInfo.isClose())
+                {
+                    assertThat("received data matches send data", result.toString(), is(data + data2));
+                    dataLatch.countDown();
+                }
+            }
+        });
+
+        stream.data(new StringDataInfo(data, false), new Callback.Adapter());
+        stream.data(new StringDataInfo(data2, true), new Callback.Adapter());
+
+        assertThat("reply has been received", replyLatch.await(5, TimeUnit.SECONDS), is(true));
+        assertThat("data has been received", dataLatch.await(5, TimeUnit.SECONDS), is(true));
+    }
+
+    @Test
+    public void testClientTimeout() throws Exception
+    {
+        long timeout = 1000;
+
+        InetSocketAddress proxyAddress = startProxy(startServer(new TestServerHandler(null, null)), timeout, 30000);
+
+        final CountDownLatch goAwayLatch = new CountDownLatch(1);
+
+        Session client = factory.newSPDYClient(version).connect(proxyAddress, new SessionFrameListener.Adapter()
+        {
+            @Override
+            public void onGoAway(Session session, GoAwayResultInfo goAwayReceivedInfo)
+            {
+                goAwayLatch.countDown();
+            }
+        });
+
+        Fields headers = SPDYTestUtils.createHeaders("localhost", proxyAddress.getPort(), version, "POST", "/");
+        ((StdErrLog)Log.getLogger(HttpChannel.class)).setHideStacks(true);
+        client.syn(new SynInfo(headers, false), null);
+        assertThat("goAway has been received by proxy", goAwayLatch.await(2 * timeout, TimeUnit.MILLISECONDS),
+                is(true));
+    }
+
+    @Test
+    public void testServerTimeout() throws Exception
+    {
+        final int timeout = 1000;
+        final String header = "foo";
+
+        InetSocketAddress proxyAddress = startProxy(startServer(new DefaultHandler()
+        {
+            @Override
+            public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+            {
+                try
+                {
+                    Thread.sleep(2 * timeout);
+                }
+                catch (InterruptedException e)
+                {
+                    e.printStackTrace();
+                }
+            }
+        }), 30000, timeout);
+
+        Session client = factory.newSPDYClient(version).connect(proxyAddress, null);
+
+        final CountDownLatch replyLatch = new CountDownLatch(1);
+
+        Fields headers = SPDYTestUtils.createHeaders("localhost", proxyAddress.getPort(), version, "POST", "/");
+        headers.put(header, "bar");
+        headers.put("connection", "close");
+
+        client.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter()
+        {
+            @Override
+            public void onReply(Stream stream, ReplyInfo replyInfo)
+            {
+                Fields headers = replyInfo.getHeaders();
+                assertThat("status is 504", headers.get(HTTPSPDYHeader.STATUS.name(version)).getValue(), is("504"));
+                replyLatch.countDown();
+            }
+
+        });
+
+        assertThat("reply has been received", replyLatch.await(5, TimeUnit.SECONDS), is(true));
+    }
+
+    @Test
+    public void testPING() throws Exception
+    {
+        // PING is per hop, and it does not carry the information to which server to ping to
+        // We just verify that it works
+
+        InetSocketAddress proxyAddress = startProxy(startServer(null), 30000, 30000);
+        proxyConnector.addConnectionFactory(proxyConnector.getConnectionFactory("spdy/" + version));
+
+        final CountDownLatch pingLatch = new CountDownLatch(1);
+        Session client = factory.newSPDYClient(version).connect(proxyAddress, new SessionFrameListener.Adapter()
+        {
+            @Override
+            public void onPing(Session session, PingResultInfo pingInfo)
+            {
+                pingLatch.countDown();
+            }
+        });
+
+        client.ping(new PingInfo(5, TimeUnit.SECONDS));
+
+        Assert.assertTrue(pingLatch.await(5, TimeUnit.SECONDS));
+    }
+
+    private class TestServerHandler extends DefaultHandler
+    {
+        private final String responseHeader;
+        private final byte[] responseData;
+
+        private TestServerHandler(String responseHeader, byte[] responseData)
+        {
+            this.responseHeader = responseHeader;
+            this.responseData = responseData;
+        }
+
+        @Override
+        public void handle(String target, Request baseRequest, HttpServletRequest request,
+                           HttpServletResponse response) throws IOException, ServletException
+        {
+            assertThat("Via Header is set", baseRequest.getHeader("X-Forwarded-For"), is(notNullValue()));
+            assertThat("X-Forwarded-For Header is set", baseRequest.getHeader("X-Forwarded-For"),
+                    is(notNullValue()));
+            assertThat("X-Forwarded-Host Header is set", baseRequest.getHeader("X-Forwarded-Host"),
+                    is(notNullValue()));
+            assertThat("X-Forwarded-Proto Header is set", baseRequest.getHeader("X-Forwarded-Proto"),
+                    is(notNullValue()));
+            assertThat("X-Forwarded-Server Header is set", baseRequest.getHeader("X-Forwarded-Server"),
+                    is(notNullValue()));
+            baseRequest.setHandled(true);
+            BufferedReader bufferedReader = request.getReader();
+            int read;
+            while ((read = bufferedReader.read()) != -1)
+                response.getOutputStream().write(read);
+
+            // add some hop header to be removed on the proxy
+            response.addHeader("Trailer", "bla");
+            if (responseHeader != null)
+                response.addHeader(responseHeader, "bar");
+            if (responseData != null)
+                response.getOutputStream().write(responseData);
+        }
+    }
+}
diff --git a/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/proxy/ProxySPDYToSPDYLoadTest.java b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/proxy/ProxySPDYToSPDYLoadTest.java
new file mode 100644
index 0000000..ebeec4c
--- /dev/null
+++ b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/proxy/ProxySPDYToSPDYLoadTest.java
@@ -0,0 +1,275 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.spdy.server.proxy;
+
+import java.io.ByteArrayOutputStream;
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.spdy.api.DataInfo;
+import org.eclipse.jetty.spdy.api.ReplyInfo;
+import org.eclipse.jetty.spdy.api.SPDY;
+import org.eclipse.jetty.spdy.api.Session;
+import org.eclipse.jetty.spdy.api.Stream;
+import org.eclipse.jetty.spdy.api.StreamFrameListener;
+import org.eclipse.jetty.spdy.api.StringDataInfo;
+import org.eclipse.jetty.spdy.api.SynInfo;
+import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener;
+import org.eclipse.jetty.spdy.client.SPDYClient;
+import org.eclipse.jetty.spdy.server.SPDYServerConnectionFactory;
+import org.eclipse.jetty.spdy.server.SPDYServerConnector;
+import org.eclipse.jetty.spdy.server.http.SPDYTestUtils;
+import org.eclipse.jetty.toolchain.test.TestTracker;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.Fields;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import static junit.framework.Assert.fail;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
+ at RunWith(value = Parameterized.class)
+public abstract class ProxySPDYToSPDYLoadTest
+{
+    @Parameterized.Parameters
+    public static Collection<Short[]> parameters()
+    {
+        return Arrays.asList(new Short[]{SPDY.V2}, new Short[]{SPDY.V3});
+    }
+
+    @Rule
+    public final TestTracker tracker = new TestTracker();
+    private final short version;
+    private static final String UUID_HEADER_NAME = "uuidHeader";
+    private static final String SERVER_ID_HEADER = "serverId";
+    private SPDYClient.Factory factory;
+    private Server server;
+    private Server proxy;
+    private ServerConnector proxyConnector;
+    private SslContextFactory sslContextFactory = SPDYTestUtils.newSslContextFactory();
+
+    public ProxySPDYToSPDYLoadTest(short version)
+    {
+        this.version = version;
+    }
+
+    protected InetSocketAddress startServer(ServerSessionFrameListener listener) throws Exception
+    {
+        server = new Server();
+        SPDYServerConnector serverConnector = new SPDYServerConnector(server, sslContextFactory, listener);
+        serverConnector.addConnectionFactory(new SPDYServerConnectionFactory(version, listener));
+        serverConnector.setPort(0);
+        server.addConnector(serverConnector);
+        server.start();
+        return new InetSocketAddress("localhost", serverConnector.getLocalPort());
+    }
+
+    protected InetSocketAddress startProxy(InetSocketAddress server1, InetSocketAddress server2) throws Exception
+    {
+        proxy = new Server();
+        ProxyEngineSelector proxyEngineSelector = new ProxyEngineSelector();
+
+        SPDYProxyEngine spdyProxyEngine = new SPDYProxyEngine(factory);
+        proxyEngineSelector.putProxyEngine("spdy/" + version, spdyProxyEngine);
+
+        proxyEngineSelector.putProxyServerInfo("localhost", new ProxyEngineSelector.ProxyServerInfo("spdy/" + version,
+                "localhost", server1.getPort()));
+        // server2 will be available at two different ProxyServerInfos with different hosts
+        proxyEngineSelector.putProxyServerInfo("127.0.0.1", new ProxyEngineSelector.ProxyServerInfo("spdy/" + version,
+                "127.0.0.1", server2.getPort()));
+        // ProxyServerInfo is mapped to 127.0.0.2 in the proxyEngineSelector. However to be able to connect the
+        // ProxyServerInfo contains 127.0.0.1 as target host
+        proxyEngineSelector.putProxyServerInfo("127.0.0.2", new ProxyEngineSelector.ProxyServerInfo("spdy/" + version,
+                "127.0.0.1", server2.getPort()));
+
+        proxyConnector = new HTTPSPDYProxyServerConnector(proxy, sslContextFactory, proxyEngineSelector);
+        proxyConnector.setPort(0);
+        proxy.addConnector(proxyConnector);
+        proxy.start();
+        return new InetSocketAddress("localhost", proxyConnector.getLocalPort());
+    }
+
+    @Before
+    public void init() throws Exception
+    {
+        factory = new SPDYClient.Factory(sslContextFactory);
+        factory.start();
+    }
+
+    @After
+    public void destroy() throws Exception
+    {
+        server.stop();
+        server.join();
+        proxy.stop();
+        proxy.join();
+        factory.stop();
+    }
+
+    @Test
+    public void testSimpleLoadTest() throws Exception
+    {
+        String server1String = "server1";
+        String server2String = "server2";
+
+        InetSocketAddress server1 = startServer(new TestServerFrameListener(server1String));
+        InetSocketAddress server2 = startServer(new TestServerFrameListener(server2String));
+        final InetSocketAddress proxyAddress = startProxy(server1, server2);
+
+        final int requestsPerClient = 50;
+
+        ExecutorService executorService = Executors.newFixedThreadPool(3);
+
+        Runnable client1 = createClientRunnable(proxyAddress, requestsPerClient, server1String, "localhost");
+        Runnable client2 = createClientRunnable(proxyAddress, requestsPerClient, server2String, "127.0.0.1");
+        Runnable client3 = createClientRunnable(proxyAddress, requestsPerClient, server2String, "127.0.0.2");
+
+        List<Future> futures = new ArrayList<>();
+
+        futures.add(executorService.submit(client1));
+        futures.add(executorService.submit(client2));
+        futures.add(executorService.submit(client3));
+
+        for (Future future : futures)
+        {
+            future.get(60, TimeUnit.SECONDS);
+        }
+    }
+
+    private Runnable createClientRunnable(final InetSocketAddress proxyAddress, final int requestsPerClient,
+                                          final String serverIdentificationString, final String serverHost)
+    {
+        Runnable client = new Runnable()
+        {
+            @Override
+            public void run()
+            {
+                try
+                {
+                    Session client = factory.newSPDYClient(version).connect(proxyAddress, null);
+                    for (int i = 0; i < requestsPerClient; i++)
+                    {
+                        sendSingleClientRequest(proxyAddress, client, serverIdentificationString, serverHost);
+                    }
+                }
+                catch (InterruptedException | ExecutionException | TimeoutException e)
+                {
+                    fail();
+                    e.printStackTrace();
+                }
+            }
+        };
+        return client;
+    }
+
+    private void sendSingleClientRequest(InetSocketAddress proxyAddress, Session client, final String serverIdentificationString, String serverHost) throws ExecutionException, InterruptedException, TimeoutException
+    {
+        final String uuid = UUID.randomUUID().toString();
+
+        final CountDownLatch replyLatch = new CountDownLatch(1);
+        final CountDownLatch dataLatch = new CountDownLatch(1);
+
+        Fields headers = SPDYTestUtils.createHeaders(serverHost, proxyAddress.getPort(), version, "POST", "/");
+        headers.add(UUID_HEADER_NAME, uuid);
+
+        Stream stream = client.syn(new SynInfo(headers, false), new StreamFrameListener.Adapter()
+        {
+            private final ByteArrayOutputStream result = new ByteArrayOutputStream();
+
+            @Override
+            public void onReply(Stream stream, ReplyInfo replyInfo)
+            {
+                Fields headers = replyInfo.getHeaders();
+                assertThat("uuid matches expected uuid", headers.get(UUID_HEADER_NAME).getValue(), is(uuid));
+                assertThat("response comes from the given server", headers.get(SERVER_ID_HEADER).getValue(),
+                        is(serverIdentificationString));
+                replyLatch.countDown();
+            }
+
+            @Override
+            public void onData(Stream stream, DataInfo dataInfo)
+            {
+                result.write(dataInfo.asBytes(true), 0, dataInfo.length());
+                if (dataInfo.isClose())
+                {
+                    assertThat("received data matches send data", uuid, is(result.toString()));
+                    dataLatch.countDown();
+                }
+            }
+        });
+
+        stream.data(new StringDataInfo(uuid, true), new Callback.Adapter());
+
+        assertThat("reply has been received", replyLatch.await(5, TimeUnit.SECONDS), is(true));
+        assertThat("data has been received", dataLatch.await(5, TimeUnit.SECONDS), is(true));
+    }
+
+    private class TestServerFrameListener extends ServerSessionFrameListener.Adapter
+    {
+        private String serverId;
+
+        private TestServerFrameListener(String serverId)
+        {
+            this.serverId = serverId;
+        }
+
+        @Override
+        public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
+        {
+            Fields requestHeaders = synInfo.getHeaders();
+            Assert.assertNotNull(requestHeaders.get("via"));
+            Fields.Field uuidHeader = requestHeaders.get(UUID_HEADER_NAME);
+            Assert.assertNotNull(uuidHeader);
+
+            Fields responseHeaders = new Fields();
+            responseHeaders.put(UUID_HEADER_NAME, uuidHeader.getValue());
+            responseHeaders.put(SERVER_ID_HEADER, serverId);
+            stream.reply(new ReplyInfo(responseHeaders, false), new Callback.Adapter());
+            return new StreamFrameListener.Adapter()
+            {
+                @Override
+                public void onData(Stream stream, DataInfo dataInfo)
+                {
+                    stream.data(dataInfo, new Callback.Adapter());
+                }
+            };
+        }
+    }
+
+}
diff --git a/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/proxy/ProxySPDYToSPDYTest.java b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/proxy/ProxySPDYToSPDYTest.java
new file mode 100644
index 0000000..802d1c4
--- /dev/null
+++ b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/proxy/ProxySPDYToSPDYTest.java
@@ -0,0 +1,553 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.spdy.server.proxy;
+
+import java.io.ByteArrayOutputStream;
+import java.net.InetSocketAddress;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.spdy.api.BytesDataInfo;
+import org.eclipse.jetty.spdy.api.DataInfo;
+import org.eclipse.jetty.spdy.api.GoAwayInfo;
+import org.eclipse.jetty.spdy.api.PingInfo;
+import org.eclipse.jetty.spdy.api.PingResultInfo;
+import org.eclipse.jetty.spdy.api.PushInfo;
+import org.eclipse.jetty.spdy.api.ReplyInfo;
+import org.eclipse.jetty.spdy.api.RstInfo;
+import org.eclipse.jetty.spdy.api.SPDY;
+import org.eclipse.jetty.spdy.api.Session;
+import org.eclipse.jetty.spdy.api.SessionFrameListener;
+import org.eclipse.jetty.spdy.api.Stream;
+import org.eclipse.jetty.spdy.api.StreamFrameListener;
+import org.eclipse.jetty.spdy.api.StreamStatus;
+import org.eclipse.jetty.spdy.api.SynInfo;
+import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener;
+import org.eclipse.jetty.spdy.client.SPDYClient;
+import org.eclipse.jetty.spdy.http.HTTPSPDYHeader;
+import org.eclipse.jetty.spdy.server.SPDYServerConnectionFactory;
+import org.eclipse.jetty.spdy.server.SPDYServerConnector;
+import org.eclipse.jetty.spdy.server.http.SPDYTestUtils;
+import org.eclipse.jetty.toolchain.test.TestTracker;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.Fields;
+import org.eclipse.jetty.util.Promise;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+
+ at RunWith(value = Parameterized.class)
+public abstract class ProxySPDYToSPDYTest
+{
+    @Parameterized.Parameters
+    public static Collection<Short[]> parameters()
+    {
+        return Arrays.asList(new Short[]{SPDY.V2}, new Short[]{SPDY.V3});
+    }
+
+    @Rule
+    public final TestTracker tracker = new TestTracker();
+    private final short version;
+    private SPDYClient.Factory factory;
+    private Server server;
+    private Server proxy;
+    private ServerConnector proxyConnector;
+    private SslContextFactory sslContextFactory = SPDYTestUtils.newSslContextFactory();
+
+    public ProxySPDYToSPDYTest(short version)
+    {
+        this.version = version;
+    }
+
+    protected InetSocketAddress startServer(ServerSessionFrameListener listener) throws Exception
+    {
+        server = new Server();
+        SPDYServerConnector serverConnector = new SPDYServerConnector(server, sslContextFactory, listener);
+        serverConnector.addConnectionFactory(new SPDYServerConnectionFactory(version, listener));
+        serverConnector.setPort(0);
+        server.addConnector(serverConnector);
+        server.start();
+        return new InetSocketAddress("localhost", serverConnector.getLocalPort());
+    }
+
+    protected InetSocketAddress startProxy(InetSocketAddress address) throws Exception
+    {
+        proxy = new Server();
+        ProxyEngineSelector proxyEngineSelector = new ProxyEngineSelector();
+        SPDYProxyEngine spdyProxyEngine = new SPDYProxyEngine(factory);
+        proxyEngineSelector.putProxyEngine("spdy/" + version, spdyProxyEngine);
+        proxyEngineSelector.putProxyServerInfo("localhost", new ProxyEngineSelector.ProxyServerInfo("spdy/" + version, address.getHostName(), address.getPort()));
+        proxyConnector = new HTTPSPDYProxyServerConnector(proxy, sslContextFactory, proxyEngineSelector);
+        proxyConnector.setPort(0);
+        proxy.addConnector(proxyConnector);
+        proxy.start();
+        return new InetSocketAddress("localhost", proxyConnector.getLocalPort());
+    }
+
+    @Before
+    public void init() throws Exception
+    {
+        factory = new SPDYClient.Factory(sslContextFactory);
+        factory.start();
+    }
+
+    @After
+    public void destroy() throws Exception
+    {
+        if (server != null)
+        {
+            server.stop();
+            server.join();
+        }
+        if (proxy != null)
+        {
+            proxy.stop();
+            proxy.join();
+        }
+        factory.stop();
+    }
+
+    @Test
+    public void testSYNThenREPLY() throws Exception
+    {
+        final String header = "foo";
+        InetSocketAddress proxyAddress = startProxy(startServer(new ServerSessionFrameListener.Adapter()
+        {
+            @Override
+            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
+            {
+                Fields requestHeaders = synInfo.getHeaders();
+                Assert.assertNotNull(requestHeaders.get("via"));
+                Assert.assertNotNull(requestHeaders.get(header));
+
+                Fields responseHeaders = new Fields();
+                responseHeaders.put(header, "baz");
+                stream.reply(new ReplyInfo(responseHeaders, true), new Callback.Adapter());
+                return null;
+            }
+        }));
+        proxyConnector.addConnectionFactory(proxyConnector.getConnectionFactory("spdy/" + version));
+
+        Session client = factory.newSPDYClient(version).connect(proxyAddress, null);
+
+        final CountDownLatch replyLatch = new CountDownLatch(1);
+        Fields headers = SPDYTestUtils.createHeaders("localhost", proxyAddress.getPort(), version, "GET", "/");
+        headers.put(header, "bar");
+        client.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter()
+        {
+            @Override
+            public void onReply(Stream stream, ReplyInfo replyInfo)
+            {
+                Fields headers = replyInfo.getHeaders();
+                Assert.assertNotNull(headers.get(header));
+                replyLatch.countDown();
+            }
+        });
+
+        Assert.assertTrue(replyLatch.await(5, TimeUnit.SECONDS));
+
+        client.goAway(new GoAwayInfo(5, TimeUnit.SECONDS));
+    }
+ at Test
+    public void testSYNThenRSTFromUpstreamServer() throws Exception
+    {
+        final String header = "foo";
+        InetSocketAddress proxyAddress = startProxy(startServer(new ServerSessionFrameListener.Adapter()
+        {
+            @Override
+            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
+            {
+                Fields requestHeaders = synInfo.getHeaders();
+                Assert.assertNotNull(requestHeaders.get("via"));
+                Assert.assertNotNull(requestHeaders.get(header));
+                stream.getSession().rst(new RstInfo(stream.getId(), StreamStatus.REFUSED_STREAM), new Callback.Adapter());
+                return null;
+            }
+        }));
+        proxyConnector.addConnectionFactory(proxyConnector.getConnectionFactory("spdy/" + version));
+
+        final CountDownLatch resetLatch = new CountDownLatch(1);
+        Session client = factory.newSPDYClient(version).connect(proxyAddress, new SessionFrameListener.Adapter()
+        {
+            @Override
+            public void onRst(Session session, RstInfo rstInfo)
+            {
+                resetLatch.countDown();
+            }
+        });
+
+        Fields headers = SPDYTestUtils.createHeaders("localhost", proxyAddress.getPort(), version, "GET", "/");
+        headers.put(header, "bar");
+        client.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter());
+
+        assertThat("reset is received by client", resetLatch.await(5, TimeUnit.SECONDS), is(true));
+    }
+
+    @Test
+    public void testSYNThenREPLYAndDATA() throws Exception
+    {
+        final byte[] data = "0123456789ABCDEF".getBytes(StandardCharsets.UTF_8);
+        final String header = "foo";
+        InetSocketAddress proxyAddress = startProxy(startServer(new ServerSessionFrameListener.Adapter()
+        {
+            @Override
+            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
+            {
+                Fields requestHeaders = synInfo.getHeaders();
+                Assert.assertNotNull(requestHeaders.get("via"));
+                Assert.assertNotNull(requestHeaders.get(header));
+
+                Fields responseHeaders = new Fields();
+                responseHeaders.put(header, "baz");
+                stream.reply(new ReplyInfo(responseHeaders, false), new Callback.Adapter());
+                stream.data(new BytesDataInfo(data, true), new Callback.Adapter());
+                return null;
+            }
+        }));
+        proxyConnector.addConnectionFactory(proxyConnector.getConnectionFactory("spdy/" + version));
+
+        Session client = factory.newSPDYClient(version).connect(proxyAddress, null);
+
+        final CountDownLatch replyLatch = new CountDownLatch(1);
+        final CountDownLatch dataLatch = new CountDownLatch(1);
+        Fields headers = new Fields();
+        headers.put(HTTPSPDYHeader.HOST.name(version), "localhost:" + proxyAddress.getPort());
+        headers.put(header, "bar");
+        client.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter()
+        {
+            private final ByteArrayOutputStream result = new ByteArrayOutputStream();
+
+            @Override
+            public void onReply(Stream stream, ReplyInfo replyInfo)
+            {
+                Fields headers = replyInfo.getHeaders();
+                Assert.assertNotNull(headers.get(header));
+                replyLatch.countDown();
+            }
+
+            @Override
+            public void onData(Stream stream, DataInfo dataInfo)
+            {
+                result.write(dataInfo.asBytes(true), 0, dataInfo.length());
+                if (dataInfo.isClose())
+                {
+                    Assert.assertArrayEquals(data, result.toByteArray());
+                    dataLatch.countDown();
+                }
+            }
+        });
+
+        Assert.assertTrue(replyLatch.await(5, TimeUnit.SECONDS));
+        Assert.assertTrue(dataLatch.await(5, TimeUnit.SECONDS));
+
+        client.goAway(new GoAwayInfo(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testSYNThenSPDYPushIsReceived() throws Exception
+    {
+        final byte[] data = "0123456789ABCDEF".getBytes(StandardCharsets.UTF_8);
+        InetSocketAddress proxyAddress = startProxy(startServer(new ServerSessionFrameListener.Adapter()
+        {
+            @Override
+            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
+            {
+                Fields responseHeaders = new Fields();
+                responseHeaders.put(HTTPSPDYHeader.VERSION.name(version), "HTTP/1.1");
+                responseHeaders.put(HTTPSPDYHeader.STATUS.name(version), "200 OK");
+                stream.reply(new ReplyInfo(responseHeaders, false), new Callback.Adapter());
+
+                Fields pushHeaders = new Fields();
+                pushHeaders.put(HTTPSPDYHeader.URI.name(version), "/push");
+                stream.push(new PushInfo(5, TimeUnit.SECONDS, pushHeaders, false), new Promise.Adapter<Stream>()
+                {
+                    @Override
+                    public void succeeded(Stream pushStream)
+                    {
+                        pushStream.data(new BytesDataInfo(data, true), new Callback.Adapter());
+                    }
+                });
+
+                stream.data(new BytesDataInfo(data, true), new Callback.Adapter());
+
+                return null;
+            }
+        }));
+        proxyConnector.addConnectionFactory(proxyConnector.getConnectionFactory("spdy/" + version));
+
+        final CountDownLatch pushSynLatch = new CountDownLatch(1);
+        final CountDownLatch pushDataLatch = new CountDownLatch(1);
+        Session client = factory.newSPDYClient(version).connect(proxyAddress, null);
+
+        Fields headers = new Fields();
+        headers.put(HTTPSPDYHeader.HOST.name(version), "localhost:" + proxyAddress.getPort());
+        final CountDownLatch replyLatch = new CountDownLatch(1);
+        final CountDownLatch dataLatch = new CountDownLatch(1);
+        client.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter()
+        {
+            @Override
+            public StreamFrameListener onPush(Stream stream, PushInfo pushInfo)
+            {
+                pushSynLatch.countDown();
+                return new StreamFrameListener.Adapter()
+                {
+                    @Override
+                    public void onData(Stream stream, DataInfo dataInfo)
+                    {
+                        dataInfo.consume(dataInfo.length());
+                        if (dataInfo.isClose())
+                            pushDataLatch.countDown();
+                    }
+                };
+            }
+
+            @Override
+            public void onReply(Stream stream, ReplyInfo replyInfo)
+            {
+                replyLatch.countDown();
+            }
+
+            @Override
+            public void onData(Stream stream, DataInfo dataInfo)
+            {
+                dataInfo.consume(dataInfo.length());
+                if (dataInfo.isClose())
+                    dataLatch.countDown();
+            }
+        });
+
+        Assert.assertTrue(replyLatch.await(5, TimeUnit.SECONDS));
+        Assert.assertTrue(pushSynLatch.await(5, TimeUnit.SECONDS));
+        Assert.assertTrue(pushDataLatch.await(5, TimeUnit.SECONDS));
+        Assert.assertTrue(dataLatch.await(5, TimeUnit.SECONDS));
+
+        client.goAway(new GoAwayInfo(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testSYNThenSPDYNestedPushIsReceived() throws Exception
+    {
+        final byte[] data = "0123456789ABCDEF".getBytes(StandardCharsets.UTF_8);
+        InetSocketAddress proxyAddress = startProxy(startServer(new ServerSessionFrameListener.Adapter()
+        {
+            @Override
+            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
+            {
+                Fields responseHeaders = new Fields();
+                responseHeaders.put(HTTPSPDYHeader.VERSION.name(version), "HTTP/1.1");
+                responseHeaders.put(HTTPSPDYHeader.STATUS.name(version), "200 OK");
+                stream.reply(new ReplyInfo(responseHeaders, false), new Callback.Adapter());
+
+                final Fields pushHeaders = new Fields();
+                pushHeaders.put(HTTPSPDYHeader.URI.name(version), "/push");
+                stream.push(new PushInfo(5, TimeUnit.SECONDS, pushHeaders, false), new Promise.Adapter<Stream>()
+                {
+                    @Override
+                    public void succeeded(Stream pushStream)
+                    {
+                        pushHeaders.put(HTTPSPDYHeader.URI.name(version), "/nestedpush");
+                        pushStream.push(new PushInfo(5, TimeUnit.SECONDS, pushHeaders, false), new Adapter<Stream>()
+                        {
+                            @Override
+                            public void succeeded(Stream pushStream)
+                            {
+                                pushHeaders.put(HTTPSPDYHeader.URI.name(version), "/anothernestedpush");
+                                pushStream.push(new PushInfo(5, TimeUnit.SECONDS, pushHeaders, false), new Adapter<Stream>()
+                                {
+                                    @Override
+                                    public void succeeded(Stream pushStream)
+                                    {
+                                        pushStream.data(new BytesDataInfo(data, true), new Callback.Adapter());
+                                    }
+                                });
+                                pushStream.data(new BytesDataInfo(data, true), new Callback.Adapter());
+                            }
+                        });
+                        pushStream.data(new BytesDataInfo(data, true), new Callback.Adapter());
+                    }
+                });
+
+                stream.data(new BytesDataInfo(data, true), new Callback.Adapter());
+
+                return null;
+            }
+        }));
+        proxyConnector.addConnectionFactory(proxyConnector.getConnectionFactory("spdy/" + version));
+
+        final CountDownLatch pushSynLatch = new CountDownLatch(3);
+        final CountDownLatch pushDataLatch = new CountDownLatch(3);
+        Session client = factory.newSPDYClient(version).connect(proxyAddress, null);
+
+        Fields headers = new Fields();
+        headers.put(HTTPSPDYHeader.HOST.name(version), "localhost:" + proxyAddress.getPort());
+        final CountDownLatch replyLatch = new CountDownLatch(1);
+        final CountDownLatch dataLatch = new CountDownLatch(1);
+        client.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter()
+        {
+            // onPush for 1st push stream
+            @Override
+            public StreamFrameListener onPush(Stream stream, PushInfo pushInfo)
+            {
+                pushSynLatch.countDown();
+                return new StreamFrameListener.Adapter()
+                {
+                    // onPush for 2nd nested push stream
+                    @Override
+                    public StreamFrameListener onPush(Stream stream, PushInfo pushInfo)
+                    {
+                        pushSynLatch.countDown();
+                        return new Adapter()
+                        {
+                            // onPush for 3rd nested push stream
+                            @Override
+                            public StreamFrameListener onPush(Stream stream, PushInfo pushInfo)
+                            {
+                                pushSynLatch.countDown();
+                                return new Adapter()
+                                {
+                                    @Override
+                                    public void onData(Stream stream, DataInfo dataInfo)
+                                    {
+                                        dataInfo.consume(dataInfo.length());
+                                        if (dataInfo.isClose())
+                                            pushDataLatch.countDown();
+                                    }
+                                };
+                            }
+
+                            @Override
+                            public void onData(Stream stream, DataInfo dataInfo)
+                            {
+                                dataInfo.consume(dataInfo.length());
+                                if (dataInfo.isClose())
+                                    pushDataLatch.countDown();
+                            }
+                        };
+                    }
+
+                    @Override
+                    public void onData(Stream stream, DataInfo dataInfo)
+                    {
+                        dataInfo.consume(dataInfo.length());
+                        if (dataInfo.isClose())
+                            pushDataLatch.countDown();
+                    }
+                };
+            }
+
+            @Override
+            public void onReply(Stream stream, ReplyInfo replyInfo)
+            {
+                replyLatch.countDown();
+            }
+
+            @Override
+            public void onData(Stream stream, DataInfo dataInfo)
+            {
+                dataInfo.consume(dataInfo.length());
+                if (dataInfo.isClose())
+                    dataLatch.countDown();
+            }
+        });
+
+        Assert.assertTrue(replyLatch.await(5, TimeUnit.SECONDS));
+        Assert.assertTrue(pushSynLatch.await(5, TimeUnit.SECONDS));
+        Assert.assertTrue(pushDataLatch.await(5, TimeUnit.SECONDS));
+        Assert.assertTrue(dataLatch.await(5, TimeUnit.SECONDS));
+
+        client.goAway(new GoAwayInfo(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testPING() throws Exception
+    {
+        // PING is per hop, and it does not carry the information to which server to ping to
+        // We just verify that it works
+
+        InetSocketAddress proxyAddress = startProxy(startServer(new ServerSessionFrameListener.Adapter()));
+        proxyConnector.addConnectionFactory(proxyConnector.getConnectionFactory("spdy/" + version));
+
+        final CountDownLatch pingLatch = new CountDownLatch(1);
+        Session client = factory.newSPDYClient(version).connect(proxyAddress, new SessionFrameListener.Adapter()
+        {
+            @Override
+            public void onPing(Session session, PingResultInfo pingInfo)
+            {
+                pingLatch.countDown();
+            }
+        });
+
+        client.ping(new PingInfo(5, TimeUnit.SECONDS));
+
+        Assert.assertTrue(pingLatch.await(5, TimeUnit.SECONDS));
+
+        client.goAway(new GoAwayInfo(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testSYNThenReset() throws Exception
+    {
+        InetSocketAddress proxyAddress = startProxy(startServer(new ServerSessionFrameListener.Adapter()
+        {
+            @Override
+            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
+            {
+                Assert.assertTrue(synInfo.isClose());
+                Fields requestHeaders = synInfo.getHeaders();
+                Assert.assertNotNull(requestHeaders.get("via"));
+
+                stream.getSession().rst(new RstInfo(stream.getId(), StreamStatus.REFUSED_STREAM), new Callback.Adapter());
+
+                return null;
+            }
+        }));
+        proxyConnector.addConnectionFactory(proxyConnector.getConnectionFactory("spdy/" + version));
+
+        final CountDownLatch resetLatch = new CountDownLatch(1);
+        Session client = factory.newSPDYClient(version).connect(proxyAddress, new SessionFrameListener.Adapter()
+        {
+            @Override
+            public void onRst(Session session, RstInfo rstInfo)
+            {
+                resetLatch.countDown();
+            }
+        });
+
+        Fields headers = new Fields();
+        headers.put(HTTPSPDYHeader.HOST.name(version), "localhost:" + proxyAddress.getPort());
+        client.syn(new SynInfo(headers, true), null);
+
+        Assert.assertTrue(resetLatch.await(5, TimeUnit.SECONDS));
+
+        client.goAway(new GoAwayInfo(5, TimeUnit.SECONDS));
+    }
+}
diff --git a/jetty-spdy/spdy-http-server/src/test/resources/big_script.js b/jetty-spdy/spdy-http-server/src/test/resources/big_script.js
new file mode 100644
index 0000000..37202fd
--- /dev/null
+++ b/jetty-spdy/spdy-http-server/src/test/resources/big_script.js
@@ -0,0 +1,791 @@
+//----------------------------------------------------------------------
+//
+// Silly / Pointless Javascript to test GZIP compression.
+//
+//----------------------------------------------------------------------
+
+var LOGO = {
+  dat: [
+    0x50, 0x89, 0x47, 0x4e, 0x0a, 0x0d, 0x0a, 0x1a, 0x00, 0x00, 0x0d, 0x00, 0x48, 0x49, 0x52, 0x44,
+    0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x78, 0x00, 0x06, 0x08, 0x00, 0x00, 0x2a, 0x00, 0x21, 0x96,
+    0x00, 0x0f, 0x00, 0x00, 0x73, 0x04, 0x49, 0x42, 0x08, 0x54, 0x08, 0x08, 0x7c, 0x08, 0x64, 0x08,
+    0x00, 0x88, 0x00, 0x00, 0x70, 0x09, 0x59, 0x48, 0x00, 0x73, 0x04, 0x00, 0x00, 0x27, 0x04, 0x00,
+    0x01, 0x27, 0x4f, 0xd9, 0x80, 0x1d, 0x00, 0x00, 0x19, 0x00, 0x45, 0x74, 0x74, 0x58, 0x6f, 0x53,
+    0x74, 0x66, 0x61, 0x77, 0x65, 0x72, 0x77, 0x00, 0x77, 0x77, 0x69, 0x2e, 0x6b, 0x6e, 0x63, 0x73,
+    0x70, 0x61, 0x2e, 0x65, 0x72, 0x6f, 0x9b, 0x67, 0x3c, 0xee, 0x00, 0x1a, 0x20, 0x00, 0x49, 0x00,
+    0x41, 0x44, 0x78, 0x54, 0xed, 0x9c, 0x79, 0x9d, 0x1c, 0xd8, 0x95, 0x55, 0x3f, 0xff, 0xfb, 0xa7,
+    0xb2, 0xdd, 0x24, 0xef, 0x24, 0x81, 0x81, 0x2c, 0x20, 0x40, 0xd5, 0x91, 0x8b, 0xb0, 0x3f, 0xbb,
+    0x1d, 0x04, 0x54, 0x1c, 0x46, 0x74, 0x17, 0x18, 0xd1, 0x98, 0x19, 0xd1, 0x05, 0x11, 0x37, 0xf4,
+    0xe2, 0x8f, 0xcc, 0xb8, 0x28, 0x8c, 0x82, 0x02, 0x22, 0x8c, 0xe0, 0xe8, 0xe2, 0x86, 0x02, 0x88,
+    0x8e, 0xa2, 0x08, 0xe8, 0x42, 0x42, 0x4b, 0x08, 0xc2, 0xc8, 0x12, 0x12, 0xf6, 0x42, 0x7d, 0x90,
+    0xf7, 0x7d, 0x3e, 0xee, 0x47, 0xf3, 0x77, 0x75, 0xeb, 0x6a, 0xdf, 0x7e, 0xee, 0xea, 0xab, 0x7b,
+    0xdc, 0x93, 0xf3, 0xcf, 0xd0, 0xf0, 0xa9, 0x55, 0x53, 0xae, 0x6f, 0x5d, 0x3d, 0xd5, 0x9e, 0xf7,
+    0xee, 0x7b, 0x8a, 0xf9, 0xe2, 0xaa, 0x38, 0x70, 0x0e, 0x1c, 0xc3, 0x87, 0x99, 0x2e, 0x2f, 0xb4,
+    0xe1, 0xc0, 0x38, 0x70, 0x8e, 0x1c, 0x11, 0x83, 0x80, 0xe7, 0x0e, 0x1d, 0xc3, 0x87, 0x48, 0xe1,
+    0xe7, 0x01, 0x1d, 0x80, 0x87, 0x0e, 0xe1, 0xc3, 0x01, 0x48, 0x80, 0xe7, 0x0e, 0x1d, 0xc3, 0x87,
+    0x48, 0xe1, 0xe7, 0x01, 0x1d, 0x80, 0x87, 0x0e, 0xe1, 0xc3, 0x01, 0x48, 0x80, 0xe7, 0x0e, 0x1d,
+    0xc3, 0x87, 0x48, 0xe1, 0xe7, 0x01, 0x1d, 0x80, 0x87, 0x0e, 0xe1, 0xc3, 0x01, 0x48, 0x80, 0xe7,
+    0x0e, 0x1d, 0xc3, 0x87, 0x48, 0xe1, 0xe7, 0x01, 0x1d, 0x80, 0x87, 0x0e, 0xe1, 0xc3, 0x01, 0x48,
+    0x80, 0xe7, 0x0e, 0x1d, 0xc3, 0x87, 0x48, 0xe1, 0xe7, 0x01, 0x1d, 0x80, 0x87, 0x0e, 0xe1, 0xc3,
+    0x01, 0x48, 0x80, 0xe7, 0x0e, 0x1d, 0xc3, 0x87, 0x48, 0xe1, 0x96, 0x81, 0x2f, 0xb4, 0x44, 0x40,
+    0x2c, 0x3a, 0xe9, 0x98, 0xa7, 0x55, 0xe1, 0x3a, 0x38, 0x70, 0x8e, 0x1c, 0x42, 0x26, 0xf0, 0xd2,
+    0x22, 0x4b, 0x16, 0x32, 0x0c, 0xb8, 0x02, 0xb8, 0xde, 0x38, 0xc9, 0x82, 0xe0, 0x4e, 0x80, 0xbf,
+    0xa9, 0x4f, 0xbf, 0x6a, 0x7b, 0x05, 0x88, 0xb1, 0x6c, 0xc8, 0xc3, 0xe0, 0xfb, 0xc0, 0xd1, 0x81,
+    0xcd, 0x86, 0x81, 0xe5, 0xc0, 0xe5, 0xab, 0xdf, 0x02, 0xea, 0xb6, 0xc3, 0x0e, 0x1c, 0xa3, 0x87,
+    0x44, 0x6e, 0x12, 0x64, 0x29, 0x70, 0x41, 0xf0, 0x7a, 0x60, 0xaf, 0xc2, 0x01, 0x77, 0x80, 0xf3,
+    0x55, 0x9b, 0x21, 0xf5, 0xf6, 0x0b, 0x81, 0xba, 0x81, 0xc7, 0x54, 0x5b, 0x77, 0xf5, 0xbf, 0x09,
+    0xd9, 0xeb, 0xed, 0xb7, 0x45, 0x80, 0x0b, 0x24, 0x04, 0x2c, 0x5b, 0x66, 0xec, 0x35, 0x28, 0xf1,
+    0xd7, 0xf0, 0xba, 0xaa, 0xb6, 0xd5, 0x11, 0x61, 0x23, 0x79, 0x27, 0xf0, 0x76, 0xdb, 0x5e, 0x81,
+    0x27, 0x3c, 0xc3, 0xfc, 0x6c, 0x14, 0x1c, 0x3b, 0xc7, 0x0e, 0x10, 0xa0, 0x23, 0x91, 0x67, 0x81,
+    0x91, 0x81, 0x9e, 0x75, 0x13, 0xaa, 0x4b, 0x38, 0x17, 0x55, 0xb2, 0x5b, 0x05, 0xd7, 0xa3, 0x9c,
+    0x0b, 0xaa, 0x7e, 0x93, 0x8d, 0x31, 0xe0, 0x39, 0x48, 0x2b, 0xf9, 0xc7, 0x9c, 0x02, 0xbc, 0x0b,
+    0xb6, 0xdb, 0x62, 0xd1, 0xe3, 0xa7, 0xdb, 0x66, 0x8b, 0x76, 0x03, 0xb4, 0xa5, 0x37, 0xdb, 0x64,
+    0x70, 0xe1, 0x06, 0x38, 0x2d, 0xcb, 0xef, 0xd4, 0x01, 0x0c, 0x01, 0x86, 0x8b, 0xf7, 0xab, 0x48,
+    0x7b, 0x25, 0x81, 0x43, 0x0d, 0x5f, 0x5e, 0xc2, 0x34, 0x84, 0x80, 0xe6, 0x50, 0x3f, 0x20, 0xfa,
+    0x98, 0x19, 0xfa, 0xfd, 0xd7, 0xc4, 0x0c, 0x9c, 0x96, 0x55, 0x92, 0x3c, 0x0b, 0x43, 0x3d, 0xe5,
+    0xcc, 0x33, 0x8c, 0x1a, 0x0e, 0x65, 0xab, 0x30, 0x31, 0xb4, 0x4d, 0xb9, 0xd6, 0x98, 0xb6, 0x6e,
+    0xf3, 0xef, 0x9f, 0x6a, 0xba, 0xb2, 0xfc, 0xb7, 0xc7, 0xa3, 0xc8, 0x88, 0x55, 0x04, 0x62, 0xdd,
+    0xa8, 0xd4, 0xe1, 0xc3, 0xd4, 0x70, 0x71, 0x40, 0xee, 0x7a, 0xd2, 0x82, 0xb0, 0xf6, 0x30, 0x8c,
+    0x58, 0x6b, 0x36, 0xb2, 0x55, 0x72, 0x81, 0x4f, 0xfd, 0x4d, 0x88, 0xe5, 0x34, 0xee, 0x69, 0xbc,
+    0x63, 0x38, 0xd6, 0xf6, 0x16, 0xf4, 0xd8, 0xd8, 0xb6, 0x57, 0x38, 0x77, 0xa8, 0x50, 0x78, 0x72,
+    0x56, 0x2c, 0xb0, 0x1d, 0xb4, 0x88, 0xa7, 0x03, 0xb6, 0x95, 0x1e, 0xa7, 0xe5, 0x97, 0x9f, 0x9a,
+    0x33, 0x0f, 0x73, 0x6a, 0xba, 0xdb, 0x9f, 0x02, 0x79, 0x3c, 0x7f, 0xb7, 0x34, 0xd7, 0x06, 0xa3,
+    0x39, 0xe3, 0xbf, 0xdb, 0xdd, 0x71, 0x76, 0x94, 0x9f, 0x2e, 0x66, 0xd8, 0xe0, 0xd4, 0xaf, 0x95,
+    0x70, 0xf4, 0xab, 0xeb, 0xfe, 0x7d, 0x87, 0x5d, 0xce, 0x03, 0x3b, 0x01, 0x8e, 0x1c, 0xe4, 0x66,
+    0xff, 0x5a, 0xa7, 0xc6, 0x6d, 0x0e, 0x8b, 0xe3, 0xdb, 0x53, 0xfd, 0x07, 0x02, 0xe5, 0xfc, 0x70,
+    0xbd, 0xc2, 0x07, 0x7e, 0x53, 0xbc, 0xab, 0x55, 0xc4, 0x39, 0xea, 0x6b, 0xa7, 0xb1, 0x89, 0xc0,
+    0xf6, 0x8b, 0x1d, 0xfa, 0xa7, 0x70, 0x56, 0xaa, 0xf8, 0x74, 0xb0, 0x95, 0x82, 0x1d, 0x15, 0x3e,
+    0x24, 0x2f, 0xc0, 0x0a, 0x73, 0x31, 0xfb, 0xcc, 0x65, 0xff, 0xe4, 0x4f, 0xbb, 0xc2, 0x1a, 0x96,
+    0x1a, 0x37, 0xe0, 0x25, 0xcf, 0x80, 0x69, 0x1a, 0x77, 0xfe, 0xdd, 0xcf, 0x78, 0x13, 0xf2, 0x16,
+    0x32, 0xc0, 0x46, 0xe3, 0x0e, 0x1d, 0x23, 0x87, 0x22, 0x21, 0x38, 0x72, 0x49, 0x70, 0x7b, 0x69,
+    0x46, 0x68, 0xc4, 0xf8, 0x64, 0xe4, 0x94, 0x03, 0xb7, 0x7b, 0xb3, 0xf5, 0x27, 0xa2, 0x6f, 0xe0,
+    0xb6, 0x2b, 0x45, 0x77, 0xef, 0x7b, 0xc7, 0xab, 0x83, 0xde, 0x72, 0x3b, 0xdf, 0x3c, 0xb0, 0x15,
+    0x3c, 0xb7, 0x09, 0xd1, 0xd8, 0x8a, 0xc0, 0x76, 0x47, 0x01, 0x63, 0x34, 0xd6, 0x4e, 0xc1, 0xb8,
+    0x16, 0x97, 0x3a, 0x44, 0x8f, 0x25, 0x37, 0x19, 0xe5, 0x1a, 0xd2, 0xac, 0xb1, 0x87, 0xc2, 0x2d,
+    0x21, 0xcc, 0x6f, 0x66, 0xde, 0xfb, 0xb2, 0xbc, 0x2b, 0xb8, 0xbb, 0xf0, 0xa8, 0x97, 0x1e, 0xea,
+    0x46, 0xa3, 0x0e, 0x1d, 0xa3, 0x87, 0x3e, 0x36, 0x2f, 0x8d, 0xfb, 0x1a, 0x43, 0xa1, 0x19, 0x5a,
+    0x22, 0xdf, 0x4e, 0x89, 0xfd, 0x70, 0xbe, 0xfa, 0xae, 0xf0, 0xcd, 0x1b, 0xeb, 0xda, 0xef, 0x0d,
+    0x66, 0xfa, 0x13, 0xa2, 0xb1, 0x14, 0x07, 0x3d, 0x74, 0x1c, 0xa7, 0xc0, 0x37, 0x9b, 0xd2, 0xff,
+    0xc0, 0xfc, 0x38, 0x08, 0xcc, 0x0f, 0x6e, 0x37, 0x87, 0xd4, 0x1c, 0x88, 0x1c, 0x03, 0xda, 0x52,
+    0x63, 0x3e, 0x96, 0x44, 0x7f, 0x64, 0xe4, 0xea, 0xd8, 0x2c, 0x27, 0x9b, 0x4c, 0x1f, 0x9f, 0x6e,
+    0xd8, 0x6b, 0x01, 0xe4, 0x88, 0x83, 0x0d, 0x1c, 0x0e, 0x5c, 0x07, 0x9c, 0xff, 0x46, 0x0a, 0x54,
+    0xc2, 0x6c, 0x32, 0x5b, 0x67, 0xf1, 0x46, 0x53, 0x64, 0x44, 0x5e, 0x08, 0xe1, 0xe2, 0x62, 0xdf,
+    0xe9, 0x7e, 0x37, 0x5b, 0x08, 0xf0, 0x15, 0xf0, 0x8d, 0x55, 0x9e, 0x84, 0x8e, 0x1c, 0x22, 0x30,
+    0x1e, 0x32, 0x48, 0xf8, 0xbb, 0x69, 0xe0, 0x45, 0x43, 0xaa, 0x8d, 0x93, 0xff, 0x46, 0x57, 0x77,
+    0x67, 0x8e, 0x03, 0x3a, 0x8e, 0x03, 0xc0, 0x15, 0x9b, 0x7f, 0x37, 0xb2, 0x4f, 0x77, 0x79, 0x9e,
+    0x08, 0xc1, 0x1a, 0xe3, 0xee, 0xe0, 0x27, 0x44, 0xd9, 0x29, 0xe5, 0xaf, 0x75, 0x4b, 0x1e, 0x50,
+    0x8e, 0x09, 0x98, 0x9e, 0xc2, 0x61, 0xb3, 0x34, 0xc1, 0x23, 0xdd, 0xae, 0xda, 0xca, 0x03, 0x17,
+    0x6a, 0x37, 0xaa, 0x91, 0x35, 0xee, 0x34, 0x6a, 0x30, 0x4a, 0x3c, 0xfc, 0xfc, 0xc2, 0x3f, 0xa8,
+    0x7e, 0x14, 0xe7, 0x06, 0x07, 0x80, 0x88, 0x85, 0x1b, 0xfc, 0x59, 0xf0, 0x3a, 0xcc, 0x30, 0xde,
+    0x88, 0x17, 0xa7, 0xc8, 0xf5, 0x55, 0x8d, 0x5b, 0xb1, 0x3e, 0xcc, 0x88, 0x8b, 0xc2, 0x8c, 0xf8,
+    0xf4, 0x6a, 0xab, 0xb9, 0x7a, 0xf0, 0xf5, 0xe0, 0xf2, 0x22, 0x55, 0x5e, 0x6c, 0xdd, 0xae, 0xd1,
+    0xff, 0x63, 0x9f, 0xe4, 0xb2, 0xf0, 0x01, 0x88, 0xef, 0x78, 0x56, 0xb8, 0x4f, 0x0e, 0xa0, 0x98,
+    0xb5, 0xfa, 0xe8, 0xe8, 0x1b, 0xf7, 0xe6, 0x55, 0xeb, 0x7f, 0x17, 0xb6, 0xfa, 0x37, 0xb5, 0xad,
+    0x69, 0xc3, 0x04, 0x2d, 0x22, 0x2d, 0x80, 0x33, 0xa5, 0x09, 0x1b, 0x6d, 0xe7, 0xe1, 0x4f, 0x15,
+    0xb2, 0x05, 0x21, 0x9f, 0x47, 0x1d, 0x70, 0x14, 0x67, 0xc0, 0x30, 0x8f, 0xe7, 0xdf, 0xe7, 0x99,
+    0x70, 0x1c, 0x44, 0x62, 0x38, 0xe4, 0x6a, 0xe0, 0x3a, 0xec, 0xf0, 0x5f, 0xc1, 0x3a, 0x8b, 0x37,
+    0x39, 0xc8, 0xce, 0x06, 0x13, 0x7d, 0x9d, 0x76, 0x89, 0x6f, 0x80, 0xf3, 0x52, 0xdb, 0xeb, 0xb0,
+    0x8f, 0xd8, 0x91, 0x10, 0xc0, 0x61, 0xfc, 0x27, 0xae, 0xfb, 0x6c, 0x39, 0x89, 0xf0, 0x00, 0x50,
+    0x74, 0xcf, 0xf9, 0xe6, 0xae, 0xd3, 0xef, 0x80, 0x04, 0xdb, 0x65, 0xdc, 0xde, 0xca, 0x5d, 0x73,
+    0x7e, 0x05, 0x23, 0xbb, 0x6f, 0x60, 0x70, 0x1b, 0xa2, 0x47, 0xf8, 0x93, 0x39, 0xb0, 0xb6, 0x02,
+    0x7e, 0x1f, 0xa2, 0x7e, 0xe6, 0x29, 0xcb, 0x7f, 0xbb, 0xbf, 0xe0, 0x55, 0xe1, 0xb4, 0x3b, 0x66,
+    0x05, 0x1e, 0x7e, 0x60, 0x01, 0xd0, 0x53, 0xaf, 0x35, 0xd5, 0x8d, 0x46, 0x18, 0x1e, 0x13, 0xfc,
+    0xf7, 0xbe, 0x3d, 0xa1, 0x3e, 0x63, 0xdc, 0xfe, 0xec, 0x1b, 0xce, 0x1c, 0x81, 0xa4, 0x67, 0xcf,
+    0x99, 0x71, 0x9b, 0xc5, 0xdb, 0x4a, 0xf1, 0x59, 0x3f, 0x9e, 0xf4, 0x93, 0x02, 0x15, 0x30, 0xeb,
+    0x9a, 0x66, 0xe6, 0xb5, 0x00, 0x38, 0x02, 0xb8, 0x5b, 0x18, 0x38, 0xda, 0x4c, 0x7f, 0xb3, 0x0b,
+    0x26, 0x86, 0x8d, 0x1d, 0x46, 0x46, 0x37, 0xbf, 0x6a, 0xa9, 0x85, 0x4f, 0x2f, 0xc3, 0xd7, 0xaf,
+    0xd7, 0xde, 0xf4, 0x37, 0x2a, 0x12, 0x75, 0x5d, 0xaa, 0xab, 0x26, 0x76, 0x89, 0x3a, 0xf4, 0x8f,
+    0x70, 0x1c, 0x19, 0x3a, 0x56, 0xd0, 0x82, 0x47, 0x32, 0x22, 0x38, 0x0e, 0xb4, 0xae, 0xca, 0x7d,
+    0x2c, 0xf0, 0xf3, 0x86, 0x58, 0xaf, 0x99, 0xd2, 0x4f, 0x67, 0x02, 0x70, 0x8d, 0xd8, 0x07, 0x7e,
+    0x79, 0x47, 0xda, 0x04, 0x81, 0x9e, 0xaf, 0xed, 0x89, 0x1d, 0xa5, 0xc9, 0x8b, 0xda, 0xf3, 0x3b,
+    0xb2, 0x9c, 0xf0, 0x38, 0x1e, 0xde, 0x60, 0xd3, 0x81, 0x13, 0xf5, 0x11, 0x87, 0xf5, 0xf0, 0x77,
+    0x84, 0xc9, 0x9e, 0x99, 0x79, 0x49, 0x73, 0xd3, 0x3c, 0x5d, 0xbb, 0xb2, 0xce, 0xfc, 0x17, 0x4d,
+    0x71, 0x11, 0xd8, 0x35, 0x71, 0x1d, 0x8b, 0x14, 0x5c, 0x56, 0xdf, 0xe5, 0xed, 0x77, 0xc9, 0xa1,
+    0xa3, 0x46, 0x7a, 0x2b, 0xdc, 0x0a, 0x2d, 0xbb, 0x59, 0x50, 0x37, 0x78, 0x9f, 0xf0, 0xec, 0x55,
+    0xba, 0x7d, 0x7b, 0x1e, 0x7a, 0x6b, 0xfc, 0x0b, 0x6b, 0x6c, 0x5e, 0xc0, 0x5e, 0x17, 0x8f, 0x31,
+    0xb1, 0x9a, 0x05, 0x99, 0x76, 0x5d, 0x6d, 0xc0, 0x84, 0x43, 0x67, 0xc3, 0x1d, 0x9b, 0xe6, 0x09,
+    0xe1, 0xfb, 0x67, 0xe5, 0x23, 0x02, 0x55, 0xc1, 0xba, 0xaa, 0xa8, 0xde, 0x28, 0xd1, 0xe2, 0x67,
+    0x48, 0x1b, 0x9d, 0x9d, 0xfc, 0xce, 0x3c, 0xef, 0x46, 0xe3, 0x73, 0xf7, 0x64, 0x44, 0xbe, 0x14,
+    0x29, 0x42, 0x0c, 0xa7, 0x1a, 0xcb, 0xbe, 0x75, 0xfb, 0x10, 0x6a, 0x77, 0xb3, 0xf4, 0x70, 0x19,
+    0xa6, 0xc0, 0xbd, 0x9f, 0xc2, 0x9c, 0x7b, 0x93, 0xbf, 0x03, 0xa3, 0x69, 0xbe, 0x73, 0x2a, 0x8e,
+    0x0d, 0xfc, 0xb5, 0x30, 0x71, 0xb4, 0x88, 0xc6, 0xe7, 0x2c, 0x4c, 0x8c, 0x8c, 0xf6, 0x73, 0x7e,
+    0x5f, 0x43, 0xf2, 0xb8, 0x77, 0xc5, 0x75, 0x55, 0x85, 0x57, 0xdf, 0xc3, 0xaf, 0x5f, 0x1b, 0xbd,
+    0xf6, 0x37, 0x0b, 0x91, 0xd1, 0x3e, 0xa8, 0x77, 0xb6, 0xea, 0x27, 0x44, 0x61, 0x09, 0x01, 0xc5,
+    0x47, 0x17, 0xc7, 0x82, 0xb6, 0x96, 0x9f, 0xa7, 0x25, 0x92, 0x78, 0x6b, 0xbc, 0x00, 0x49, 0x6a,
+    0xbe, 0x9e, 0xc0, 0xee, 0x4a, 0xdd, 0xfc, 0x23, 0x68, 0x1c, 0xcf, 0x04, 0xf3, 0x72, 0xcd, 0xbf,
+    0x59, 0xca, 0xde, 0xf8, 0x1b, 0x05, 0x25, 0x1d, 0x7f, 0x0b, 0xf2, 0xa7, 0x37, 0xb3, 0x23, 0x34,
+    0x34, 0x9c, 0xec, 0xcc, 0x7a, 0x6f, 0x33, 0xb6, 0x57, 0x4f, 0x45, 0xc1, 0x1c, 0x5e, 0x23, 0x03,
+    0x42, 0x22, 0x10, 0xa8, 0xb5, 0xc6, 0xe7, 0x09, 0x5f, 0x62, 0x57, 0xed, 0xda, 0xee, 0x08, 0x12,
+    0xf7, 0x3f, 0x61, 0x52, 0x51, 0xe9, 0xec, 0x23, 0xcb, 0x6d, 0x77, 0x29, 0x0a, 0x6e, 0x7e, 0x8c,
+    0xc0, 0x73, 0x89, 0x0d, 0x12, 0x4e, 0xad, 0x83, 0xf0, 0x11, 0xf8, 0x59, 0x82, 0x46, 0xc2, 0x36,
+    0x2f, 0xcf, 0x48, 0x2d, 0xfc, 0x37, 0x24, 0x5c, 0x76, 0x10, 0xd3, 0x3f, 0xc3, 0x7e, 0x89, 0xde,
+    0x6c, 0x57, 0x51, 0xdc, 0x50, 0x9d, 0xba, 0xa6, 0x8e, 0xf5, 0x17, 0x52, 0xfc, 0x0d, 0x89, 0x3b,
+    0x14, 0xc8, 0x15, 0x7c, 0x5e, 0xdf, 0x3c, 0x3b, 0xc2, 0x4b, 0x8e, 0x65, 0x71, 0x89, 0xf7, 0x99,
+    0x04, 0x8e, 0x03, 0xbc, 0x0a, 0x9f, 0xf1, 0xde, 0x32, 0x43, 0x38, 0x5c, 0xd9, 0x26, 0x77, 0x3b,
+    0xc6, 0xcb, 0x70, 0xbe, 0xc5, 0x0e, 0x54, 0x8f, 0x63, 0x75, 0xc3, 0x85, 0x04, 0x2f, 0xa8, 0x4e,
+    0xde, 0xc7, 0x37, 0xb7, 0x85, 0xf6, 0x98, 0x3b, 0x37, 0x77, 0xb7, 0x22, 0x96, 0xc6, 0x0e, 0x03,
+    0x60, 0x25, 0x28, 0x59, 0x11, 0xc0, 0xff, 0x9d, 0x3a, 0xb5, 0x16, 0x02, 0x36, 0x91, 0x65, 0x7c,
+    0x8f, 0x37, 0xc8, 0xee, 0xd1, 0x30, 0x16, 0x70, 0xba, 0xcc, 0x02, 0xbe, 0x82, 0x6b, 0xe5, 0x4f,
+    0xb5, 0x13, 0x47, 0x94, 0x08, 0x3b, 0x44, 0x09, 0x4c, 0x2a, 0x94, 0x77, 0x07, 0xd6, 0x74, 0xeb,
+    0xf7, 0x83, 0x6a, 0x77, 0xba, 0xe4, 0xab, 0x59, 0x67, 0xe1, 0x91, 0x70, 0x47, 0x17, 0x02, 0x22,
+    0x27, 0x65, 0x73, 0x3f, 0x7b, 0x58, 0x84, 0xa2, 0xdd, 0xc7, 0xe5, 0x79, 0xc1, 0x3b, 0xbb, 0x32,
+    0xe0, 0x05, 0xa6, 0xeb, 0x75, 0xec, 0x94, 0x16, 0x07, 0x6f, 0x29, 0xed, 0x7d, 0x70, 0x53, 0x82,
+    0xc4, 0x54, 0x03, 0x96, 0x27, 0x2e, 0x88, 0x98, 0x31, 0xc0, 0xd9, 0xa7, 0x80, 0xc8, 0xb4, 0x0e,
+    0x3b, 0x67, 0xe7, 0x81, 0x1b, 0x8c, 0x32, 0x0d, 0xe8, 0x0d, 0x6d, 0x28, 0x58, 0xd8, 0xeb, 0xff,
+    0xc1, 0x1a, 0x14, 0xe4, 0x33, 0x93, 0x94, 0xe6, 0x67, 0xb6, 0x64, 0x74, 0x60, 0x98, 0xa3, 0xb8,
+    0x6f, 0x34, 0xf1, 0x8f, 0x4c, 0xe8, 0xf4, 0xa8, 0x8d, 0xa9, 0x2f, 0x67, 0x9b, 0xf0, 0x93, 0x76,
+    0xc9, 0x4e, 0x47, 0x57, 0x53, 0x93, 0x6e, 0x5c, 0xae, 0x57, 0x31, 0x07, 0x2d, 0xb5, 0xfb, 0xc3,
+    0x27, 0xc7, 0xe4, 0x13, 0xee, 0xf9, 0x7e, 0xa6, 0x05, 0x76, 0x4d, 0x13, 0xa5, 0x7f, 0x2f, 0xaa,
+    0xb0, 0x55, 0x36, 0x77, 0xf7, 0xbe, 0x16, 0xa1, 0x77, 0x7b, 0xea, 0x6e, 0x5b, 0x67, 0xf2, 0x70,
+    0x41, 0xf7, 0x6d, 0x55, 0xf3, 0xc8, 0x2b, 0x6d, 0xba, 0x0b, 0x54, 0x3c, 0x0c, 0x97, 0xf1, 0x7c,
+    0xb3, 0x7c, 0x30, 0xb5, 0x05, 0x0a, 0xd7, 0x65, 0xc0, 0x96, 0x9b, 0x0d, 0x42, 0x97, 0xc8, 0x42,
+    0xbc, 0x5a, 0x59, 0x9e, 0xe0, 0x06, 0x3b, 0x0f, 0x9c, 0x73, 0xb8, 0xba, 0x6c, 0xe6, 0x41, 0xf5,
+    0xb9, 0xb1, 0xb2, 0x3f, 0x7c, 0xf6, 0x86, 0xc0, 0x58, 0xbd, 0x75, 0x17, 0xe9, 0x22, 0x53, 0x48,
+    0x16, 0x0e, 0xf6, 0x05, 0xad, 0xe0, 0xdb, 0xf1, 0xcb, 0x5e, 0x52, 0xdf, 0x0e, 0x44, 0x5e, 0x25,
+    0x63, 0x88, 0x30, 0x06, 0xb4, 0xa4, 0xc7, 0x61, 0x45, 0x59, 0x4a, 0x3a, 0x45, 0xbb, 0x8c, 0xe4,
+    0x9a, 0x06, 0x43, 0x78, 0x43, 0xe9, 0x38, 0x06, 0xf0, 0xc3, 0xb7, 0xd2, 0x82, 0x57, 0x30, 0xaa,
+    0x55, 0xe0, 0x9a, 0x22, 0x4d, 0xe3, 0xd5, 0xbd, 0x3f, 0x44, 0x52, 0xb0, 0x77, 0x55, 0x48, 0x88,
+    0x38, 0x06, 0x5f, 0x11, 0x79, 0x0d, 0xf4, 0x43, 0x2b, 0x00, 0x16, 0x92, 0x11, 0xea, 0xc3, 0x91,
+    0x25, 0xf0, 0x18, 0xf7, 0x7c, 0xa2, 0xaa, 0x3f, 0x47, 0x5c, 0x70, 0x2b, 0x60, 0x12, 0x01, 0xfa,
+    0x01, 0xee, 0xd7, 0x96, 0xf4, 0xb1, 0xb3, 0xe6, 0x8d, 0xfe, 0x0f, 0x2b, 0x4b, 0x6d, 0x3c, 0xb4,
+    0xb7, 0xae, 0xdf, 0x00, 0x18, 0xda, 0x84, 0x29, 0x35, 0xf1, 0xf6, 0x53, 0x15, 0xfa, 0xdc, 0x6e,
+    0x59, 0x10, 0x54, 0xea, 0x5e, 0xcd, 0x6c, 0x22, 0xe0, 0x39, 0x1f, 0xf2, 0x90, 0xab, 0x0d, 0x87,
+    0xad, 0xcb, 0x45, 0x47, 0x6b, 0xbf, 0x20, 0xdb, 0xdb, 0x5e, 0x03, 0xb7, 0x07, 0x18, 0xd2, 0x5e,
+    0xab, 0xc0, 0x56, 0xfd, 0xf7, 0x7f, 0x40, 0xcb, 0xc4, 0xa4, 0xb1, 0x61, 0xc4, 0xe0, 0xc0, 0x25,
+    0x78, 0x19, 0xf2, 0x21, 0x36, 0xf1, 0xaa, 0x2f, 0xf4, 0x01, 0xc8, 0x8b, 0xc0, 0xab, 0xe0, 0x02,
+    0xc0, 0xd7, 0x55, 0x03, 0x1f, 0x32, 0x91, 0x11, 0x00, 0xb7, 0x0c, 0xff, 0xcf, 0x9c, 0x1a, 0x20,
+    0x1b, 0x27, 0xbf, 0xf3, 0x77, 0x73, 0x9e, 0x47, 0x86, 0x83, 0xb3, 0x84, 0x98, 0xe2, 0x34, 0x39,
+    0x68, 0xc3, 0x13, 0xc3, 0xf3, 0xc0, 0xbf, 0xa2, 0x14, 0xe7, 0xf1, 0xe0, 0x4f, 0x3a, 0x2d, 0xdb,
+    0x77, 0x22, 0xef, 0x00, 0x53, 0xc4, 0xb2, 0xf2, 0x16, 0xc1, 0x79, 0x11, 0xaa, 0x8f, 0x32, 0x3e,
+    0x81, 0xd0, 0xf2, 0x22, 0xe0, 0x0e, 0xc0, 0xe3, 0xb0, 0x5b, 0x79, 0xd3, 0x16, 0xed, 0x2b, 0x91,
+    0xf5, 0x54, 0xa1, 0x27, 0x98, 0xeb, 0x5c, 0x02, 0x5c, 0x09, 0xaf, 0x86, 0x91, 0xd0, 0x36, 0x61,
+    0xc8, 0x89, 0x55, 0xbb, 0xd1, 0x35, 0x5e, 0xb4, 0x32, 0xb1, 0xdb, 0xdb, 0xdb, 0x4b, 0x5b, 0x63,
+    0xcb, 0x84, 0x26, 0x27, 0x8d, 0x1b, 0x68, 0xfe, 0x5f, 0x4b, 0xb3, 0xb8, 0x67, 0xf7, 0x7d, 0x55,
+    0x82, 0xb2, 0x13, 0xbd, 0x49, 0xf0, 0xd6, 0x0e, 0xef, 0x62, 0x5b, 0x67, 0x59, 0xfb, 0x17, 0xdb,
+    0xd8, 0x18, 0x4d, 0xcc, 0xbb, 0xfa, 0x61, 0xab, 0xb5, 0xbc, 0xf6, 0x29, 0xaf, 0x10, 0x6d, 0x34,
+    0x0a, 0xa7, 0x50, 0x7f, 0xfb, 0xd5, 0xb0, 0x53, 0x59, 0xfb, 0xce, 0x8a, 0x25, 0x37, 0xad, 0x3e,
+    0xe5, 0xaa, 0x9b, 0xae, 0x4c, 0x88, 0xbe, 0x04, 0x4f, 0x81, 0x79, 0x8f, 0xa5, 0x3f, 0x6f, 0x15,
+    0x31, 0xc4, 0xb8, 0x15, 0xe8, 0x18, 0x91, 0x13, 0x80, 0x07, 0x54, 0xeb, 0x30, 0x35, 0x20, 0xcd,
+    0x33, 0x22, 0x07, 0x81, 0xdf, 0x8b, 0x14, 0x19, 0xe7, 0xa6, 0x3b, 0x4b, 0xca, 0x0b, 0x2e, 0xa2,
+    0x1d, 0x7b, 0x73, 0xb1, 0x74, 0x6c, 0xa2, 0x28, 0xfa, 0x23, 0x30, 0x46, 0xaf, 0x04, 0x8a, 0x38,
+    0x26, 0x4d, 0x7f, 0x00, 0x91, 0x14, 0x86, 0x0f, 0x9d, 0x1d, 0x11, 0x1f, 0x15, 0x39, 0x0f, 0xb8,
+    0x55, 0xbb, 0x86, 0xd3, 0x3f, 0x00, 0x91, 0x16, 0xaa, 0xe9, 0x25, 0xfa, 0xfe, 0xdf, 0x00, 0x9f,
+    0xb3, 0xaf, 0x1d, 0x78, 0x45, 0xe0, 0xfe, 0xcc, 0x78, 0xb7, 0x7e, 0x9f, 0xe0, 0x9a, 0xd7, 0x7b,
+    0xbb, 0xe2, 0x1f, 0x67, 0xd4, 0x9f, 0xb0, 0xc6, 0xe0, 0xcc, 0x61, 0x6f, 0x6f, 0x01, 0x51, 0xe9,
+    0xff, 0x88, 0xf2, 0xa8, 0x6b, 0x95, 0xb1, 0xea, 0x78, 0xa7, 0x37, 0x85, 0x34, 0x42, 0xf4, 0x6c,
+    0x76, 0x0b, 0x6e, 0x7a, 0x01, 0x5f, 0x8a, 0xcc, 0x56, 0xfe, 0xfa, 0xc7, 0x57, 0xe8, 0x26, 0x44,
+    0x95, 0xe3, 0x4c, 0x35, 0x35, 0x8b, 0x7a, 0xaa, 0xf1, 0x5f, 0x32, 0x5a, 0xf2, 0x22, 0xc0, 0x8f,
+    0xec, 0x8b, 0xce, 0xff, 0x8e, 0x37, 0x3c, 0x36, 0xbc, 0x47, 0x44, 0x50, 0x26, 0xbe, 0x43, 0x22,
+    0xff, 0x7d, 0x4d, 0xf6, 0x38, 0x12, 0xf1, 0xdf, 0xc2, 0x2d, 0x86, 0xb1, 0xa5, 0x2b, 0xb1, 0x3c,
+    0x22, 0x27, 0x61, 0x94, 0x3b, 0x14, 0xc7, 0xb5, 0x28, 0x0f, 0x85, 0xdd, 0xe0, 0x16, 0x01, 0xd2,
+    0xb9, 0x8e, 0xfb, 0x01, 0x4b, 0x25, 0x7c, 0x4b, 0xf4, 0xb8, 0x44, 0x41, 0xc2, 0x2e, 0xf3, 0xbe,
+    0xd1, 0x2d, 0x27, 0x8a, 0x31, 0xf0, 0xe2, 0x28, 0x00, 0x24, 0x9e, 0x3f, 0x30, 0xb0, 0xaf, 0xcc,
+    0xf5, 0xb7, 0x52, 0xf3, 0x72, 0x50, 0xa1, 0x70, 0xfe, 0xaa, 0x82, 0xa1, 0x69, 0xbd, 0x6b, 0x78,
+    0x6b, 0x7f, 0x17, 0xb6, 0x39, 0x23, 0x1c, 0xf8, 0x9b, 0xf0, 0x27, 0x44, 0x00, 0x19, 0x21, 0x1b,
+    0x0b, 0xe8, 0x1f, 0x4b, 0x8f, 0x86, 0xde, 0x15, 0xb9, 0x7a, 0xa9, 0xd9, 0xae, 0x9c, 0xca, 0x5d,
+    0xbf, 0xef, 0xe8, 0x1b, 0xcc, 0xb5, 0x6a, 0x30, 0x9e, 0x30, 0xe1, 0xb7, 0x57, 0x9b, 0xcb, 0x7c,
+    0x16, 0xe8, 0x31, 0x4e, 0x0e, 0xbc, 0x4f, 0xf3, 0x72, 0xbb, 0xb5, 0x3c, 0x10, 0xaf, 0x54, 0xc2,
+    0xe9, 0x11, 0x53, 0xc0, 0xfa, 0x7f, 0xf0, 0x39, 0xb8, 0xae, 0x4c, 0xef, 0x13, 0x6e, 0x19, 0x4e,
+    0xe5, 0x96, 0x21, 0xb8, 0x5a, 0x19, 0x9f, 0x6a, 0x33, 0xaf, 0x02, 0x8e, 0x72, 0xbb, 0x86, 0xca,
+    0x02, 0xbe, 0xfa, 0x1b, 0x95, 0x95, 0x05, 0x3d, 0xe4, 0x76, 0x5e, 0x22, 0x15, 0xac, 0xc7, 0xaf,
+    0x57, 0xfb, 0x72, 0x22, 0xb0, 0x3e, 0x2f, 0x05, 0x08, 0x3c, 0x94, 0xc0, 0x0c, 0xb6, 0x3b, 0x7f,
+    0xba, 0xa1, 0x3e, 0x2f, 0xdd, 0xb2, 0x3d, 0xfc, 0xc8, 0xe8, 0xe2, 0x48, 0x88, 0x35, 0xf0, 0xf5,
+    0x51, 0xc6, 0x5f, 0x66, 0x02, 0xf1, 0xce, 0xf0, 0xad, 0x31, 0x38, 0x5c, 0x71, 0xa6, 0x0c, 0xe7,
+    0xcb, 0x3d, 0x7b, 0xbc, 0x2b, 0x5c, 0x3b, 0xd3, 0x76, 0xcc, 0x7e, 0x8c, 0xad, 0xb1, 0x95, 0x9f,
+    0x1a, 0xfb, 0x8a, 0xf7, 0x86, 0x6d, 0x8c, 0x88, 0xe7, 0xc1, 0x8e, 0xf4, 0xcf, 0x68, 0x49, 0x70,
+    0xa4, 0xc2, 0xa4, 0x9e, 0xbd, 0xc4, 0xc5, 0xdb, 0x8b, 0x72, 0xf9, 0x17, 0xfb, 0x8f, 0x21, 0xc8,
+    0x8c, 0xad, 0xb5, 0x6f, 0x9f, 0x7a, 0x4e, 0x8e, 0xd5, 0xf0, 0xf0, 0xab, 0x9b, 0xf9, 0xd7, 0x0f,
+    0x01, 0x22, 0x1d, 0x18, 0x56, 0xfd, 0xba, 0x9b, 0x0a, 0xb9, 0xe7, 0x5f, 0x16, 0xbb, 0x5f, 0x7b,
+    0x8a, 0x8b, 0x5c, 0x2e, 0xe8, 0xdf, 0x22, 0x5c, 0x6f, 0xa3, 0x31, 0x67, 0x58, 0x04, 0x63, 0x9e,
+    0xfe, 0x3c, 0x16, 0xec, 0xfa, 0xfe, 0x8e, 0xea, 0xdf, 0x2a, 0x9f, 0xa8, 0x1f, 0x67, 0x0f, 0xea,
+    0xe0, 0x3b, 0x36, 0x06, 0x83, 0x0d, 0x90, 0x26, 0xfa, 0x85, 0x0f, 0xff, 0xe7, 0x6b, 0xc3, 0x5c,
+    0x3d, 0xc9, 0xbc, 0xcf, 0x08, 0x10, 0x0a, 0x03, 0xcd, 0x5e, 0x2f, 0xd3, 0x72, 0x11, 0xad, 0xbe,
+    0x57, 0x02, 0xda, 0x1d, 0x27, 0xce, 0xb5, 0x26, 0xb9, 0x31, 0x79, 0xad, 0x6e, 0x9d, 0xf2, 0x35,
+    0x8f, 0x0a, 0xce, 0xec, 0xef, 0x71, 0x1c, 0xb6, 0x6f, 0x77, 0x64, 0x8d, 0x1e, 0x44, 0x3c, 0x0b,
+    0x17, 0x8a, 0x2a, 0x2a, 0xf4, 0xdf, 0xc6, 0x0b, 0xf0, 0xb4, 0x69, 0xd5, 0x1c, 0xf6, 0x25, 0x5f,
+    0x1a, 0xce, 0xb5, 0x91, 0x2a, 0x32, 0x45, 0xb5, 0xf8, 0x06, 0x83, 0x72, 0xeb, 0xef, 0xe0, 0xc2,
+    0xcc, 0xb3, 0x87, 0xf5, 0x33, 0xb7, 0x70, 0xcd, 0xb2, 0x69, 0x83, 0xbb, 0x06, 0x25, 0x4d, 0xab,
+    0x05, 0x1d, 0x1a, 0x6a, 0x34, 0x5c, 0xfc, 0xd6, 0x15, 0x73, 0xb7, 0x7a, 0x78, 0x33, 0xda, 0x6d,
+    0x7c, 0x46, 0x4c, 0xed, 0x06, 0x47, 0x39, 0x6e, 0x08, 0x6a, 0x5f, 0xa6, 0xd0, 0xe9, 0x1a, 0x7d,
+    0xdb, 0x54, 0x2c, 0x5a, 0x74, 0xc4, 0x69, 0x79, 0x45, 0xbb, 0x53, 0xe0, 0x25, 0x09, 0xff, 0x00,
+    0xea, 0x1c, 0x01, 0x94, 0x81, 0x2b, 0x98, 0x5f, 0x37, 0xb2, 0x4f, 0x77, 0xc7, 0x9e, 0x24, 0x1b,
+    0x58, 0x39, 0xd3, 0x0d, 0xe8, 0x21, 0x7a, 0xc0, 0x63, 0xc4, 0xb0, 0xcf, 0x80, 0x51, 0x32, 0x23,
+    0xb5, 0x1f, 0xb8, 0xc0, 0x28, 0xd1, 0x05, 0xd6, 0x9e, 0x18, 0x3e, 0x08, 0x1b, 0x2c, 0xf7, 0x81,
+    0xd3, 0xe2, 0x04, 0xbd, 0x6f, 0x38, 0x0b, 0x64, 0x9c, 0xcf, 0x9c, 0x38, 0x4e, 0xaf, 0xdf, 0x6b,
+    0x9d, 0x6f, 0x78, 0x2f, 0x02, 0xc2, 0x1c, 0x6f, 0xc2, 0xd5, 0x47, 0xad, 0xf0, 0x75, 0xc9, 0xc2,
+    0xb9, 0xc3, 0x42, 0x6c, 0x38, 0x6b, 0x78, 0x1e, 0x10, 0x18, 0xf7, 0x90, 0x90, 0x3b, 0xde, 0x9c,
+    0x4c, 0x81, 0x4c, 0x58, 0x4a, 0x64, 0xc6, 0x5b, 0xf3, 0xb8, 0x59, 0x8d, 0xff, 0x3f, 0x95, 0x54,
+    0xad, 0xc3, 0x8b, 0xca, 0xbc, 0xc6, 0x1b, 0xdb, 0x75, 0x54, 0x31, 0x63, 0xa3, 0xa7, 0x02, 0x3c,
+    0x75, 0x3d, 0xd6, 0x58, 0xf3, 0xb8, 0xb9, 0x8d, 0xb1, 0x4f, 0x4f, 0xc9, 0x04, 0x31, 0x00, 0x24,
+    0x19, 0x2f, 0x83, 0x5f, 0x60, 0x04, 0xf4, 0x74, 0x8d, 0xfb, 0x35, 0x2a, 0x3e, 0x0e, 0x6f, 0x81,
+    0xb3, 0xda, 0x7b, 0x16, 0xa3, 0x31, 0x6f, 0xdf, 0x6a, 0xaa, 0xdc, 0x7f, 0xf5, 0xb1, 0xd5, 0x60,
+    0xdb, 0x01, 0x40, 0x51, 0x5d, 0x4a, 0x61, 0x80, 0x3e, 0x2c, 0x86, 0x01, 0x87, 0x93, 0x17, 0x99,
+    0x58, 0x60, 0x99, 0xda, 0x77, 0x67, 0x14, 0x70, 0xc1, 0x98, 0xb8, 0x4b, 0x73, 0x2a, 0xc4, 0xc5,
+    0xfe, 0x36, 0xc2, 0x74, 0x66, 0x50, 0xbe, 0x9a, 0xcb, 0x18, 0xd2, 0x1c, 0x7c, 0x2a, 0xe8, 0xff,
+    0xfc, 0x21, 0xd8, 0xe2, 0x55, 0xa1, 0x37, 0xeb, 0x58, 0xdb, 0xe4, 0x5e, 0x9f, 0xa8, 0xba, 0xe7,
+    0xcf, 0x82, 0x13, 0x72, 0x9f, 0xae, 0x96, 0x0f, 0x1d, 0x97, 0xb6, 0x69, 0x8b, 0x4d, 0xb7, 0x79,
+    0xd7, 0x4b, 0x96, 0x76, 0xe7, 0x7f, 0x86, 0xd9, 0x03, 0xaf, 0xab, 0x6a, 0x10, 0x33, 0x01, 0x27,
+    0x75, 0x78, 0x11, 0x42, 0x67, 0x84, 0xf3, 0x3b, 0xb4, 0x3c, 0xd0, 0x33, 0x6f, 0xae, 0x7e, 0x06,
+    0xc1, 0x50, 0xd0, 0xde, 0xed, 0x7a, 0xd7, 0x3d, 0xe0, 0x55, 0x0d, 0x77, 0x1c, 0x94, 0x86, 0xac,
+    0x07, 0x35, 0x31, 0xdc, 0x38, 0x4c, 0x96, 0x7c, 0x01, 0x79, 0x95, 0x86, 0x83, 0x0b, 0x0a, 0x61,
+    0x7d, 0x55, 0xa8, 0xd5, 0x28, 0xd1, 0x10, 0x81, 0x4a, 0x55, 0x0c, 0x02, 0xf1, 0x13, 0x5f, 0x85,
+    0x3f, 0x5f, 0x85, 0xaa, 0x1d, 0x6f, 0x36, 0x69, 0x9f, 0xf4, 0x17, 0x36, 0x6d, 0x8d, 0xbe, 0xe1,
+    0x86, 0xe3, 0x96, 0xc6, 0x9c, 0x5c, 0x26, 0xdc, 0x69, 0x1c, 0x5d, 0x5a, 0xd1, 0xc1, 0xc2, 0x10,
+    0xb2, 0xcf, 0x75, 0x95, 0xd1, 0xd9, 0x54, 0x65, 0x8e, 0xb1, 0x58, 0xa5, 0xee, 0xdd, 0xa5, 0xb5,
+    0xa3, 0xed, 0x32, 0x3a, 0x32, 0x4c, 0xb4, 0x5c, 0x4a, 0xa5, 0x4f, 0x89, 0xd5, 0xeb, 0x62, 0xce,
+    0xcf, 0x96, 0x46, 0x0d, 0xe5, 0xe8, 0x7c, 0x72, 0xfd, 0x21, 0x49, 0x06, 0xde, 0x0e, 0x45, 0x5c,
+    0xf0, 0x72, 0xf8, 0x23, 0x3d, 0xa6, 0x41, 0x9b, 0xb0, 0x72, 0xc6, 0x1a, 0xa5, 0x5a, 0xe1, 0x62,
+    0xa3, 0x83, 0xdb, 0x4a, 0x9f, 0x47, 0x25, 0x96, 0xf8, 0x63, 0x59, 0x19, 0x38, 0xb5, 0x5f, 0x4f,
+    0xea, 0x4f, 0xe1, 0x05, 0xa8, 0x53, 0x54, 0x42, 0x19, 0x79, 0xd1, 0xc8, 0x19, 0x1d, 0x99, 0xee,
+    0xc4, 0x31, 0x16, 0xb8, 0x33, 0x6c, 0xe6, 0xf0, 0x2d, 0x51, 0x74, 0x7c, 0x74, 0x62, 0xe7, 0xae,
+    0x0b, 0x4c, 0x71, 0x4b, 0x8d, 0x1c, 0x34, 0x21, 0xc6, 0xef, 0x16, 0x39, 0xcb, 0x1c, 0x41, 0x63,
+    0x05, 0x9b, 0xaf, 0x2a, 0x3e, 0x61, 0x5f, 0x05, 0xbb, 0x75, 0x4b, 0x94, 0x96, 0x7c, 0x19, 0xdb,
+    0x4e, 0xc1, 0xf9, 0x9d, 0x67, 0x50, 0xc7, 0x20, 0xd3, 0x00, 0x8d, 0x7b, 0x1c, 0x90, 0xb9, 0x7c,
+    0xc0, 0xa7, 0xb7, 0x2f, 0x22, 0x07, 0x7b, 0xbf, 0xdb, 0x80, 0xd8, 0x2a, 0xa1, 0x6b, 0xc9, 0x4e,
+    0xd5, 0xc1, 0x24, 0x06, 0xab, 0x07, 0x72, 0x61, 0x90, 0x58, 0xfc, 0xc2, 0xf3, 0x6f, 0x30, 0x09,
+    0xa4, 0xa4, 0x54, 0x80, 0xc6, 0xe6, 0x3a, 0x23, 0x6b, 0x18, 0x12, 0xba, 0x13, 0x7f, 0xdd, 0x71,
+    0xf6, 0xd4, 0xb6, 0x48, 0x9b, 0xa3, 0xdd, 0xff, 0xf0, 0x7f, 0x46, 0x8f, 0x67, 0x87, 0xd9, 0x85,
+    0xcd, 0x43, 0xed, 0xfe, 0xe6, 0xf6, 0x25, 0x95, 0x75, 0xc1, 0x4b, 0xd9, 0x75, 0x54, 0x85, 0x77,
+    0xad, 0xc3, 0xff, 0xce, 0xbc, 0x42, 0x8a, 0xb8, 0x0c, 0x88, 0xeb, 0xc1, 0x00, 0x0c, 0xdc, 0x70,
+    0x8c, 0x90, 0x77, 0xf1, 0x4f, 0x7c, 0x3e, 0xf4, 0x54, 0x2d, 0xae, 0xd5, 0xbe, 0x01, 0x7e, 0xf6,
+    0xd5, 0x7c, 0x6a, 0xe7, 0x1c, 0x95, 0x2d, 0x0c, 0xbb, 0xdb, 0x55, 0x5d, 0x56, 0x77, 0xfc, 0x38,
+    0xf8, 0x7d, 0xb6, 0x8a, 0x62, 0xd4, 0x26, 0xef, 0x92, 0x03, 0xd5, 0x83, 0xe7, 0xb0, 0x6d, 0x80,
+    0xff, 0xcc, 0xec, 0xa6, 0xc3, 0x80, 0x2a, 0x21, 0x02, 0x5b, 0xa1, 0x0c, 0xfe, 0xc2, 0x26, 0x65,
+    0x0a, 0xb5, 0x1d, 0x6f, 0x70, 0x7d, 0xfe, 0x8d, 0xb4, 0xc6, 0xff, 0x08, 0xca, 0x1a, 0xb5, 0x4a,
+    0x52, 0x31, 0xd4, 0x71, 0xc6, 0xcf, 0xe5, 0x3e, 0xdf, 0x15, 0x7c, 0xb3, 0xf0, 0xfa, 0x59, 0x2c,
+    0x7d, 0xc3, 0x27, 0xc7, 0x46, 0xf6, 0x65, 0xd6, 0x7a, 0x0f, 0xf7, 0xfe, 0xc3, 0x6c, 0xeb, 0x9d,
+    0xa2, 0x2a, 0x73, 0x1d, 0x2d, 0xf0, 0xb1, 0xb1, 0xff, 0x31, 0xf0, 0x9b, 0x35, 0x3e, 0x02, 0x44,
+    0x53, 0x70, 0x72, 0x54, 0x8f, 0xb0, 0x92, 0x0a, 0x45, 0x83, 0xb2, 0xea, 0x35, 0xb7, 0x72, 0x5e,
+    0x9b, 0xf0, 0x4e, 0x89, 0x10, 0x92, 0x0e, 0x93, 0xdc, 0xf8, 0x87, 0xd2, 0x0b, 0x6c, 0x3f, 0x4c,
+    0x82, 0xd5, 0x52, 0x02, 0x03, 0x30, 0x0d, 0x3a, 0x86, 0x01, 0x88, 0x40, 0xa5, 0x2a, 0x92, 0x04,
+    0x7f, 0x32, 0xa1, 0x33, 0x78, 0xd5, 0xd6, 0x03, 0xbc, 0x8c, 0xec, 0x6b, 0x75, 0xbe, 0x43, 0x99,
+    0x18, 0x32, 0xc2, 0xd7, 0x34, 0x75, 0xb0, 0x86, 0x45, 0x48, 0xf9, 0x4a, 0xd5, 0x13, 0x4b, 0x1c,
+    0xf1, 0x39, 0x4c, 0x69, 0x30, 0x8b, 0xe1, 0x33, 0x62, 0xdc, 0x62, 0x52, 0x9c, 0x0a, 0xc2, 0x44,
+    0x60, 0xcf, 0x9d, 0xa9, 0xe4, 0x19, 0xb9, 0x3c, 0xba, 0x8f, 0x00, 0x25, 0xd9, 0x6f, 0x47, 0xd8,
+    0xee, 0xb0, 0x3f, 0x06, 0xd5, 0x56, 0x71, 0xf5, 0x8a, 0xc7, 0x05, 0xc8, 0xec, 0xc0, 0xec, 0x7a,
+    0x16, 0xdd, 0x1c, 0x95, 0x99, 0xfc, 0xae, 0xaa, 0x74, 0x4e, 0x84, 0x92, 0x79, 0x18, 0x44, 0x82,
+    0x24, 0x64, 0x9a, 0xf0, 0xf6, 0xd2, 0xb3, 0xe1, 0xb4, 0xb3, 0x35, 0x0f, 0x09, 0xeb, 0xad, 0x36,
+    0xb0, 0x2a, 0x73, 0x6b, 0xaf, 0xe0, 0x78, 0xf5, 0x02, 0x0a, 0xcd, 0x0c, 0x62, 0xf0, 0xb9, 0x94,
+    0x42, 0x6c, 0xb5, 0xb2, 0x07, 0x6f, 0x87, 0x0a, 0x0a, 0xb4, 0x8a, 0x63, 0xb9, 0x21, 0x86, 0xd3,
+    0x0f, 0x67, 0xf9, 0x8a, 0x03, 0xef, 0xf0, 0x81, 0x6f, 0xfc, 0x25, 0x93, 0x1d, 0x60, 0x1c, 0x03,
+    0xda, 0x52, 0x63, 0x3e, 0xd6, 0x44, 0x29, 0x78, 0x2a, 0xac, 0x66, 0xd7, 0x9d, 0xbd, 0x41, 0xa9,
+    0xb9, 0xef, 0x09, 0x2e, 0x3d, 0xc0, 0xe5, 0x79, 0x9b, 0x3b, 0x12, 0x22, 0x46, 0x80, 0x07, 0x25,
+    0x8d, 0x6f, 0x0e, 0x4a, 0xb4, 0x36, 0x64, 0xec, 0xa6, 0x1c, 0x17, 0x62, 0xe2, 0x67, 0xee, 0x73,
+    0xc2, 0x36, 0xcb, 0xcf, 0x5e, 0x53, 0x54, 0x7e, 0x94, 0x24, 0x95, 0x80, 0x1d, 0x5e, 0xe9, 0xde,
+    0x99, 0x1d, 0xd9, 0xc4, 0xf5, 0x7f, 0x6c, 0xf0, 0x9e, 0x67, 0x6d, 0xef, 0x67, 0xea, 0x4f, 0x45,
+    0x97, 0x81, 0x0a, 0x7b, 0x30, 0x28, 0x4d, 0xa5, 0xd6, 0x98, 0xe1, 0x9e, 0xa3, 0x6d, 0xf8, 0x5b,
+    0x00, 0xc0, 0xa8, 0x55, 0x4d, 0x1a, 0x4d, 0x69, 0xfc, 0xe4, 0x79, 0x3d, 0x3f, 0xc6, 0xa7, 0xb9,
+    0xb1, 0xea, 0x1e, 0x37, 0xc1, 0xd1, 0x86, 0xa5, 0xef, 0xaf, 0xcf, 0xcd, 0xf1, 0x77, 0xa7, 0x6c,
+    0x1a, 0x9d, 0xc7, 0xd0, 0xc9, 0x0e, 0x77, 0x30, 0xd9, 0x96, 0xaf, 0xea, 0xde, 0x3f, 0xcf, 0xd2,
+    0xab, 0x95, 0x25, 0x2a, 0xd6, 0xa2, 0xa7, 0x46, 0xc6, 0x2f, 0x15, 0x99, 0x04, 0x2f, 0x92, 0x6d,
+    0x7a, 0xbc, 0x55, 0xa1, 0xeb, 0x1f, 0x8c, 0xab, 0x82, 0x78, 0x8f, 0xcb, 0x3b, 0xec, 0x02, 0xbd,
+    0xbf, 0x30, 0xda, 0x18, 0x8f, 0x2d, 0x27, 0xc4, 0x0a, 0xb6, 0x1b, 0x47, 0x12, 0xae, 0x73, 0x16,
+    0x56, 0x9f, 0xea, 0xa9, 0x4a, 0xc6, 0x37, 0xc7, 0x02, 0x42, 0xf6, 0xf0, 0xfd, 0x4d, 0x0c, 0xec,
+    0xc6, 0x4e, 0x5a, 0xfe, 0x5f, 0x55, 0x60, 0xac, 0x4c, 0xef, 0x11, 0x7c, 0x5a, 0xd7, 0x07, 0x25,
+    0x07, 0xb7, 0xfd, 0xed, 0x51, 0xb6, 0x83, 0x92, 0x30, 0xd5, 0x21, 0xe5, 0xaf, 0xec, 0x6d, 0xff,
+    0x04, 0xb2, 0xb1, 0xac, 0x62, 0x2d, 0xcd, 0x7c, 0xde, 0xed, 0xb2, 0xbc, 0x42, 0xb8, 0xff, 0x61,
+    0x59, 0xd7, 0xf7, 0x5a, 0x5d, 0xda, 0x2f, 0x80, 0xed, 0xae, 0xa6, 0xe1, 0x91, 0x8d, 0x09, 0x64,
+    0xf7, 0x56, 0x8d, 0x7a, 0xdd, 0xc8, 0xfb, 0x5b, 0x7f, 0xb9, 0x8e, 0x47, 0xa7, 0x3b, 0x30, 0x77,
+    0x42, 0xc2, 0x7c, 0x4c, 0x5e, 0x4f, 0xd4, 0xd9, 0x5d, 0xef, 0xbf, 0x4c, 0x7e, 0x12, 0x23, 0x91,
+    0x6a, 0x9c, 0xa1, 0xb8, 0x2a, 0xef, 0x73, 0x28, 0xe7, 0x77, 0xb7, 0xe8, 0x8f, 0x14, 0x9d, 0x31,
+    0x04, 0xb8, 0x97, 0xf0, 0xb9, 0xdd, 0xef, 0x01, 0x3d, 0x6b, 0x1e, 0xcc, 0x35, 0x9a, 0xac, 0xfe,
+    0xe9, 0x2c, 0xb3, 0xca, 0xf8, 0x27, 0x56, 0xd0, 0xb9, 0x53, 0x74, 0x09, 0x4d, 0xae, 0x5c, 0x8b,
+    0xbc, 0xc4, 0x4b, 0xdb, 0x75, 0x55, 0x51, 0x57, 0x68, 0x51, 0x69, 0x74, 0x8d, 0xa7, 0xcf, 0x24,
+    0x5d, 0xc5, 0xf6, 0x79, 0xaf, 0xc6, 0xae, 0x45, 0x5d, 0x44, 0x80, 0x12, 0x0a, 0x7d, 0x27, 0x37,
+    0x1c, 0x93, 0x8c, 0x0c, 0x1b, 0x7e, 0x39, 0x24, 0x4d, 0x58, 0xa1, 0xe4, 0x98, 0x61, 0x10, 0xf2,
+    0xfa, 0xe7, 0x52, 0x37, 0x80, 0xa8, 0x60, 0x35, 0x7c, 0x8a, 0x49, 0xa3, 0x44, 0x23, 0x20, 0xb5,
+    0x18, 0x9a, 0xe6, 0x51, 0x16, 0x75, 0xdf, 0x46, 0x57, 0x00, 0xea, 0xaf, 0x86, 0xe1, 0xa6, 0x68,
+    0x84, 0x62, 0xb7, 0x7b, 0xf3, 0xf5, 0x3b, 0x3f, 0x14, 0xed, 0x5f, 0xed, 0x13, 0xd1, 0x94, 0x9f,
+    0xc7, 0x16, 0x0b, 0x18, 0xa5, 0x1d, 0xf7, 0x27, 0xad, 0xe6, 0x5f, 0x39, 0x2d, 0x48, 0xd9, 0x24,
+    0x9a, 0x50, 0xfe, 0x70, 0x6a, 0xd7, 0xbb, 0x75, 0x77, 0x98, 0x3e, 0x3b, 0x6c, 0xfc, 0xa3, 0xa1,
+    0xe4, 0x9d, 0x35, 0x3e, 0x02, 0x42, 0xc7, 0xf0, 0xfa, 0x5b, 0x10, 0xd9, 0x1c, 0x94, 0x44, 0x7c,
+    0x9f, 0x55, 0x60, 0xa8, 0x26, 0x6f, 0x64, 0x9e, 0xcd, 0x65, 0x62, 0xf6, 0x07, 0x24, 0x35, 0xe7,
+    0x72, 0x52, 0x1a, 0xb0, 0x6f, 0x0d, 0x8b, 0xa5, 0xbf, 0x6b, 0x94, 0xe6, 0x27, 0xb6, 0xcf, 0x4d,
+    0x72, 0x30, 0x59, 0xbc, 0xd4, 0x57, 0x4b, 0xb5, 0xbf, 0x59, 0x71, 0x2c, 0x7c, 0x8a, 0x69, 0xa3,
+    0x79, 0x86, 0x32, 0x99, 0x6b, 0xa6, 0x83, 0x4f, 0x48, 0xd7, 0xbe, 0xdc, 0xd2, 0x29, 0x04, 0x73,
+    0x72, 0xaf, 0x37, 0x4c, 0x09, 0x30, 0x33, 0xdf, 0x60, 0xb6, 0x9e, 0x6f, 0x8f, 0x77, 0x1f, 0x33,
+    0x5e, 0x8a, 0x75, 0x1e, 0x6b, 0xc0, 0xed, 0x81, 0xcf, 0xc5, 0xf0, 0xc3, 0xf0, 0x95, 0xd3, 0x3b,
+    0x47, 0x42, 0xe6, 0x29, 0xb4, 0xef, 0x6f, 0x12, 0x63, 0x3d, 0x98, 0xa3, 0x4f, 0x81, 0xc3, 0xfc,
+    0xb3, 0xf4, 0x35, 0xb7, 0x2c, 0xa7, 0x0e, 0xeb, 0xe3, 0xdc, 0xc0, 0xdd, 0x75, 0x2b, 0x76, 0x9e,
+    0xbe, 0x0a, 0x0d, 0x82, 0x54, 0xcd, 0xab, 0x01, 0x5b, 0x58, 0x6a, 0xd8, 0xbb, 0x75, 0x4e, 0x2a,
+    0xd0, 0x2f, 0x58, 0xce, 0x8b, 0x59, 0x2c, 0x9c, 0xab, 0x01, 0xc9, 0x6e, 0x1b, 0xc1, 0x12, 0xa2,
+    0xb6, 0x80, 0x07, 0x25, 0x8c, 0xad, 0xc1, 0x7e, 0x08, 0x4c, 0x54, 0xfa, 0x8d, 0x7c, 0x95, 0x81,
+    0x47, 0xe5, 0xe6, 0x4f, 0x04, 0xd0, 0xf2, 0x29, 0xb0, 0x86, 0xa2, 0x1a, 0xca, 0x82, 0xd0, 0x19,
+    0xda, 0xd3, 0x1c, 0x33, 0xb8, 0x61, 0x93, 0xf2, 0x1f, 0x02, 0xd9, 0x5d, 0xc8, 0x43, 0xdd, 0xd5,
+    0x7c, 0x07, 0x34, 0xac, 0x54, 0x3f, 0xcb, 0x2c, 0x5e, 0x59, 0xb7, 0x43, 0x73, 0xa8, 0x23, 0xf0,
+    0x7a, 0xd3, 0xde, 0x58, 0xb1, 0x1d, 0x89, 0xf3, 0x26, 0x92, 0x88, 0xb4, 0x10, 0x7c, 0x19, 0x5f,
+    0x94, 0xbb, 0xe5, 0x12, 0xae, 0xde, 0xcb, 0xaa, 0x71, 0x1a, 0xe2, 0xee, 0xe0, 0x9c, 0x8a, 0x36,
+    0xdc, 0xef, 0x16, 0x21, 0x04, 0xe6, 0xc9, 0xc3, 0xc0, 0x52, 0x55, 0xf7, 0x53, 0xf5, 0x9c, 0xf5,
+    0x44, 0x53, 0x8f, 0x7e, 0x22, 0xa7, 0xa4, 0x06, 0x80, 0x92, 0x17, 0xb5, 0x54, 0x58, 0x50, 0xe1,
+    0x4e, 0xfb, 0x10, 0x6f, 0xb0, 0x15, 0x64, 0x86, 0xce, 0xbc, 0xe5, 0x80, 0xd9, 0x6b, 0xc4, 0x0d,
+    0x8d, 0xce, 0x42, 0x36, 0xf0, 0x02, 0xed, 0xbe, 0xe1, 0xfd, 0xdd, 0xce, 0x55, 0x22, 0x7d, 0xfd,
+    0x7b, 0x05, 0xf0, 0x53, 0x22, 0x49, 0x48, 0x36, 0xf0, 0x72, 0x1a, 0x79, 0x39, 0x2c, 0x0d, 0x58,
+    0x5e, 0x13, 0x7e, 0xc2, 0xf9, 0xf8, 0x81, 0x89, 0x68, 0x7b, 0x1a, 0xc5, 0xef, 0xa1, 0xce, 0x01,
+    0xb9, 0x20, 0x67, 0x96, 0x58, 0x01, 0x7c, 0x07, 0x55, 0x5b, 0x55, 0x77, 0xa6, 0x38, 0x92, 0xfc,
+    0x59, 0xb6, 0x60, 0x12, 0x5f, 0x98, 0x01, 0xc1, 0x98, 0xdb, 0x7d, 0xff, 0x6f, 0x7a, 0xc5, 0x9e,
+    0xa4, 0xd1, 0x1e, 0xa2, 0x3a, 0xe0, 0x27, 0xd9, 0x18, 0x6d, 0x7f, 0xa8, 0xc1, 0x39, 0x3a, 0x52,
+    0x4c, 0x72, 0x7a, 0x08, 0xef, 0xb9, 0xa8, 0x73, 0x35, 0x31, 0x7c, 0xfb, 0x30, 0x2a, 0x98, 0x3f,
+    0xb2, 0xc0, 0x51, 0xa5, 0xb7, 0xce, 0xf1, 0xc8, 0x54, 0xf8, 0xec, 0x69, 0x28, 0x74, 0x53, 0x22,
+    0x75, 0x09, 0xeb, 0x85, 0x24, 0x44, 0x97, 0x83, 0x09, 0xb8, 0x49, 0x78, 0x53, 0x34, 0xcc, 0xdb,
+    0x22, 0x76, 0xf6, 0xd6, 0xc2, 0x15, 0xc7, 0x61, 0xaa, 0x27, 0xf7, 0x14, 0x1a, 0xb7, 0xad, 0xf6,
+    0xc0, 0x20, 0xaa, 0xb0, 0x33, 0xa8, 0x64, 0x7e, 0xe7, 0x69, 0x2d, 0x6b, 0xf8, 0x64, 0xee, 0xaf,
+    0xb0, 0x2d, 0x2f, 0xae, 0xba, 0xb2, 0x52, 0xa4, 0xba, 0x4f, 0xc9, 0x7e, 0x68, 0xc1, 0x49, 0xd9,
+    0x92, 0xdb, 0x5f, 0x83, 0xb9, 0xb3, 0xc6, 0x7a, 0xeb, 0x44, 0x3d, 0x63, 0x6b, 0x01, 0x00, 0xf9,
+    0x58, 0x09, 0x72, 0x22, 0x70, 0x15, 0x30, 0x1d, 0x4e, 0xb2, 0x97, 0x53, 0xc8, 0x89, 0x85, 0x5f,
+    0xd0, 0x1b, 0x00, 0x88, 0xf0, 0xc3, 0x86, 0x2c, 0x21, 0x07, 0xe9, 0x2c, 0x24, 0x8c, 0x94, 0x90,
+    0x31, 0xb1, 0x7b, 0xff, 0xf6, 0xdf, 0xf9, 0xd8, 0x4b, 0xdc, 0x7d, 0xaa, 0x18, 0xe7, 0x37, 0xd3,
+    0x48, 0xa5, 0x04, 0x28, 0xc0, 0x5d, 0xf9, 0x0b, 0xb0, 0x96, 0x97, 0x3a, 0x7c, 0xf7, 0x21, 0x57,
+    0xac, 0x9c, 0x98, 0x34, 0xb9, 0xfa, 0xb0, 0x49, 0x79, 0x5e, 0x86, 0xad, 0x29, 0x25, 0x31, 0x03,
+    0xdf, 0x1b, 0xd9, 0xbb, 0xcb, 0x4c, 0x8a, 0x8f, 0xbd, 0x58, 0x31, 0xc7, 0x0c, 0x02, 0x0e, 0xa5,
+    0xd5, 0x8e, 0x23, 0xce, 0x0e, 0x24, 0xd7, 0xd3, 0x01, 0x25, 0xf0, 0xf8, 0x1c, 0xae, 0x86, 0x4f,
+    0xad, 0xb2, 0xf4, 0xf1, 0xe3, 0xcc, 0x8d, 0xec, 0xae, 0x01, 0xc7, 0xa8, 0x8c, 0x5e, 0xe0, 0xe4,
+    0x0c, 0x5a, 0x0e, 0x48, 0xa3, 0x56, 0x2d, 0xa1, 0x31, 0x62, 0x56, 0x1c, 0x48, 0x7e, 0x4d, 0x46,
+    0x26, 0x10, 0x65, 0x1d, 0x45, 0x38, 0x1f, 0xbe, 0x7e, 0x5e, 0x70, 0x26, 0x5f, 0x13, 0xa7, 0xf1,
+    0x8b, 0xc8, 0x0c, 0xc8, 0x98, 0xbc, 0xbd, 0x7e, 0x17, 0xce, 0xad, 0x3c, 0x27, 0xd9, 0x35, 0x8a,
+    0xfd, 0x47, 0x83, 0x34, 0x43, 0x00, 0x1b, 0x19, 0xbf, 0xf3, 0xed, 0xf7, 0xae, 0x48, 0x65, 0xcc,
+    0x34, 0x3a, 0x62, 0x5a, 0x30, 0x45, 0xb5, 0x84, 0x55, 0x56, 0x21, 0x7b, 0xb9, 0xad, 0xc6, 0xbc,
+    0x16, 0x8e, 0xb0, 0x63, 0x94, 0x5a, 0xb7, 0xbf, 0x4f, 0x00, 0x23, 0x07, 0x2f, 0x1f, 0x40, 0x37,
+    0xc4, 0xf0, 0x88, 0x7a, 0xe6, 0x37, 0x89, 0xfd, 0xbb, 0xfd, 0x48, 0x88, 0x70, 0x3b, 0x69, 0x5a,
+    0x46, 0xdb, 0xbb, 0x75, 0x06, 0xc7, 0xff, 0x39, 0xd2, 0x9b, 0x79, 0x3c, 0x0e, 0xa5, 0xdd, 0x64,
+    0x80, 0x12, 0x25, 0x09, 0x3f, 0x07, 0x0c, 0x0e, 0xc7, 0xaf, 0x2d, 0x9e, 0xc1, 0xc9, 0x34, 0x6a,
+    0xf9, 0xba, 0x89, 0x99, 0x91, 0x2f, 0xc6, 0xc2, 0x77, 0xe8, 0x92, 0xd5, 0x05, 0x01, 0x3e, 0x18,
+    0x2f, 0x8b, 0xd1, 0x4c, 0x46, 0x00, 0x13, 0xe1, 0x28, 0x99, 0x34, 0xd2, 0x9f, 0xf5, 0xb7, 0x27,
+    0xf1, 0x89, 0xe6, 0x91, 0x9e, 0x8a, 0x78, 0x42, 0x64, 0xde, 0xd8, 0x50, 0xc0, 0x70, 0xfb, 0x9b,
+    0xad, 0x95, 0x11, 0xc1, 0x3f, 0xa7, 0x1c, 0xfc, 0x28, 0xe8, 0xad, 0x25, 0x5b, 0x13, 0xb5, 0x0b,
+    0xb8, 0x48, 0xa4, 0x25, 0xec, 0xfc, 0xc9, 0x0d, 0x29, 0xc0, 0x47, 0x86, 0xcf, 0xf4, 0xe6, 0x74,
+    0x55, 0x09, 0x6d, 0x3e, 0xef, 0xc4, 0xdb, 0x49, 0xe7, 0x01, 0xf9, 0x81, 0x0e, 0x15, 0x0d, 0x3d,
+    0xd7, 0x28, 0x4c, 0x34, 0x39, 0x39, 0x98, 0xec, 0x0e, 0xd2, 0x02, 0x5b, 0x8f, 0x0c, 0x8e, 0xed,
+    0xa7, 0xdc, 0x36, 0x48, 0x71, 0x72, 0xef, 0x7a, 0xfc, 0x9f, 0x92, 0xfb, 0x00, 0x4a, 0xea, 0x2e,
+    0xf3, 0xcc, 0xe0, 0x87, 0x89, 0x2a, 0xc0, 0x2d, 0xe3, 0xf7, 0x2d, 0x8e, 0xc2, 0xd6, 0xe7, 0xfe,
+    0xec, 0x7a, 0x48, 0xc5, 0x6e, 0x0e, 0xbe, 0x07, 0xe8, 0x97, 0x0d, 0x24, 0xd1, 0xa0, 0xa2, 0x9e,
+    0x05, 0x19, 0x5e, 0x18, 0xfa, 0x5f, 0xd6, 0xd0, 0x7c, 0x21, 0x8f, 0xf9, 0xc9, 0xc3, 0x5c, 0x24,
+    0xf8, 0x5f, 0x6f, 0xe4, 0xb8, 0xfa, 0xab, 0xe7, 0xa2, 0x81, 0x33, 0x03, 0x97, 0x80, 0xdb, 0x7c,
+    0xd0, 0xcd, 0x05, 0x33, 0x84, 0xec, 0x27, 0x9f, 0x0a, 0xb5, 0x9d, 0xcf, 0x7c, 0x34, 0x03, 0xe0,
+    0x1c, 0x43, 0xc1, 0x65, 0x3f, 0x01, 0x9d, 0x1a, 0x7c, 0x9f, 0xf7, 0xc6, 0xed, 0x39, 0x52, 0x8e,
+    0xf0, 0x0f, 0xa3, 0x74, 0x2d, 0xce, 0x93, 0x22, 0x75, 0xf0, 0x6d, 0x06, 0xfd, 0x54, 0xec, 0x1a,
+    0x31, 0xc8, 0xe5, 0x21, 0xcb, 0xbf, 0xac, 0x88, 0xcb, 0x30, 0x3a, 0xf0, 0x3d, 0xe6, 0x65, 0x79,
+    0x54, 0x69, 0xa1, 0x80, 0x56, 0x52, 0xda, 0x7a, 0x76, 0xef, 0xfb, 0x27, 0xc3, 0x92, 0x53, 0xca,
+    0x19, 0x29, 0x73, 0x81, 0x1a, 0x96, 0xc8, 0x51, 0x10, 0x0b, 0x10, 0x93, 0x37, 0xb5, 0xb0, 0xe6,
+    0xe4, 0x80, 0xdb, 0xe0, 0x91, 0x6b, 0x8c, 0x1c, 0x7e, 0x8e, 0x51, 0x6f, 0xee, 0xd5, 0x87, 0x0a,
+    0x18, 0x7f, 0x50, 0x98, 0x3d, 0x9f, 0x92, 0x7b, 0xd5, 0x83, 0xeb, 0x30, 0x6d, 0x80, 0x40, 0x64,
+    0xa9, 0x57, 0x15, 0x80, 0x5a, 0x4e, 0x7e, 0x98, 0x96, 0x4a, 0x8e, 0xc3, 0xfe, 0x4d, 0x6d, 0x02,
+    0x1f, 0x5b, 0x6b, 0x99, 0x2f, 0x2d, 0x8a, 0x71, 0x60, 0x13, 0xc4, 0x38, 0x00, 0x82, 0x9a, 0xc3,
+    0x02, 0x0a, 0x36, 0x0c, 0xb0, 0x12, 0x67, 0x86, 0x63, 0x85, 0x34, 0x87, 0x7d, 0x67, 0x47, 0xc7,
+    0x73, 0xa3, 0x8f, 0x7b, 0x3e, 0xf8, 0x6d, 0x5b, 0x37, 0x4c, 0x29, 0xf6, 0x83, 0x2b, 0xd1, 0xf3,
+    0xaa, 0x4f, 0xec, 0x6a, 0xad, 0xba, 0x49, 0x1a, 0x4e, 0x61, 0xb1, 0xb0, 0x6f, 0x05, 0xe4, 0x44,
+    0x7c, 0x30, 0x36, 0x0a, 0x28, 0x4d, 0x60, 0xc0, 0xba, 0xb5, 0xcc, 0x5d, 0x5d, 0xbb, 0x60, 0x12,
+    0x5f, 0x00, 0x3f, 0xa7, 0xf0, 0xaf, 0x34, 0x62, 0xdf, 0xdb, 0xff, 0x34, 0xde, 0x19, 0x27, 0x11,
+    0x78, 0x01, 0xc2, 0x4d, 0xe8, 0xd1, 0x9e, 0xca, 0xff, 0x02, 0x2d, 0xbd, 0xf9, 0x10, 0x0b, 0xdd,
+    0x1a, 0xdc, 0xac, 0x77, 0x64, 0x88, 0xb5, 0xeb, 0xba, 0xb7, 0xc0, 0xb7, 0x2d, 0xcf, 0x0e, 0x4a,
+    0xa3, 0x56, 0x43, 0xd1, 0xb2, 0x92, 0x6e, 0x03, 0x2a, 0x1f, 0x9b, 0x4c, 0x5d, 0x65, 0x61, 0x80,
+    0x41, 0x17, 0x86, 0x01, 0xcb, 0xc0, 0xcc, 0x73, 0xda, 0x9c, 0x9e, 0x5e, 0x93, 0x90, 0x11, 0xcf,
+    0xc3, 0x0c, 0x95, 0x34, 0x83, 0x00, 0x02, 0xad, 0xcd, 0x1c, 0x23, 0xca, 0x90, 0xd1, 0x63, 0xd9,
+    0xee, 0x50, 0x05, 0x28, 0xf2, 0xe6, 0x77, 0x4c, 0x1a, 0x94, 0xac, 0xed, 0x48, 0x62, 0xf6, 0xbb,
+    0x76, 0xf8, 0x85, 0xe0, 0x4f, 0x3a, 0x2c, 0x19, 0xd8, 0xf1, 0x09, 0xc4, 0x36, 0x58, 0xdb, 0xaa,
+    0x30, 0x0d, 0xf1, 0x65, 0xe0, 0x43, 0xa3, 0x7e, 0x50, 0x17, 0x05, 0x99, 0x87, 0xc4, 0x03, 0x84,
+    0x83, 0x92, 0xd6, 0x67, 0x01, 0x20, 0x63, 0x78, 0x8a, 0xb4, 0xf7, 0xde, 0x75, 0x54, 0x85, 0x47,
+    0x2f, 0xc3, 0x37, 0xc1, 0xaa, 0xa8, 0xde, 0xc9, 0x88, 0x86, 0xe0, 0xe4, 0xaa, 0x9d, 0x35, 0xba,
+    0x49, 0xd1, 0x44, 0x1a, 0x5a, 0xc3, 0x62, 0xeb, 0xb6, 0x4f, 0x63, 0x7c, 0x3e, 0x8e, 0x4b, 0x2d,
+    0xb0, 0xd6, 0xd8, 0x2f, 0x3a, 0xbc, 0x80, 0x56, 0xbf, 0xc1, 0x14, 0x23, 0x4f, 0x12, 0x72, 0xfe,
+    0xed, 0x77, 0xd6, 0x52, 0x8b, 0x05, 0x3c, 0x7c, 0x0a, 0x1b, 0x04, 0x55, 0x21, 0x47, 0x1c, 0x16,
+    0x25, 0x60, 0x86, 0x01, 0xad, 0x09, 0x1b, 0xe6, 0x66, 0x88, 0x47, 0x66, 0x12, 0x2e, 0x5c, 0x5a,
+    0xd3, 0xe8, 0x3d, 0x76, 0xf4, 0x95, 0x4a, 0x3b, 0x4e, 0x8d, 0xb2, 0xc0, 0xca, 0x2a, 0xac, 0xbe,
+    0x10, 0xbb, 0x5f, 0x9e, 0xd7, 0x9f, 0x25, 0x80, 0x69, 0x1b, 0xff, 0xcf, 0xe5, 0x42, 0xde, 0xcc,
+    0xf0, 0xe3, 0x56, 0x85, 0x16, 0x53, 0x40, 0x60, 0x77, 0x55, 0xc8, 0x8b, 0xc0, 0x45, 0x80, 0xe7,
+    0x00, 0x0f, 0x0d, 0x63, 0x0e, 0x5f, 0xb3, 0xc0, 0x73, 0x78, 0xf7, 0xa3, 0x97, 0x87, 0xc5, 0x1f,
+    0x0e, 0x49, 0x93, 0x5e, 0x02, 0x50, 0x73, 0x70, 0xf2, 0xbf, 0xcd, 0x83, 0xa0, 0x81, 0x3f, 0x50,
+    0x25, 0xd5, 0xeb, 0x00, 0xb7, 0xb2, 0xa7, 0x35, 0x7f, 0x7c, 0x44, 0x73, 0xf0, 0x72, 0x44, 0x86,
+    0x69, 0x27, 0x8d, 0x20, 0x16, 0xf4, 0x15, 0x4d, 0x2e, 0x7e, 0xaf, 0xb2, 0x12, 0x81, 0xe6, 0x38,
+    0xe4, 0xb4, 0xb4, 0xd7, 0xa9, 0xfc, 0x5a, 0x1c, 0xe9, 0x3d, 0x7a, 0xc4, 0xf6, 0x91, 0x2c, 0x98,
+    0x00, 0x49, 0x3d, 0xce, 0x47, 0xc8, 0x8f, 0xbf, 0xc9, 0xed, 0xe7, 0x87, 0x1e, 0x75, 0x7d, 0xf6,
+    0xbb, 0x4e, 0x99, 0x52, 0xc0, 0xd2, 0x48, 0x25, 0xc4, 0xc5, 0x93, 0x97, 0xdb, 0x4b, 0x87, 0x33,
+    0xca, 0x64, 0x50, 0x0a, 0x30, 0xa6, 0x50, 0x94, 0xfe, 0x56, 0xa4, 0xbb, 0xc0, 0x24, 0x56, 0xb0,
+    0xd7, 0x55, 0x74, 0x0c, 0x90, 0x6d, 0xe7, 0x4a, 0x00, 0xa0, 0xee, 0x3c, 0x51, 0xdf, 0xf0, 0xec,
+    0x5b, 0xfc, 0xbf, 0xf1, 0xb9, 0xb4, 0x98, 0x00, 0xda, 0x55, 0x45, 0xa8, 0xf0, 0x02, 0xa8, 0xd6,
+    0xe0, 0xe4, 0x54, 0x4f, 0x5d, 0x75, 0xb1, 0xdc, 0xf2, 0x22, 0x80, 0xff, 0xeb, 0x13, 0xf7, 0xb1,
+    0x14, 0xed, 0x07, 0x24, 0xd1, 0xab, 0xe1, 0xc8, 0x53, 0x52, 0x60, 0x25, 0xc8, 0x89, 0xe0, 0x30,
+    0xd2, 0xa4, 0x61, 0xf6, 0x65, 0xc7, 0x3a, 0x19, 0x79, 0x2a, 0x35, 0x03, 0x22, 0x88, 0x65, 0x1f,
+    0x92, 0x65, 0x2d, 0xb1, 0x71, 0xc2, 0x04, 0x16, 0x96, 0x18, 0x2c, 0xa4, 0xd0, 0xc0, 0xdc, 0xac,
+    0x3d, 0xb7, 0x82, 0xb2, 0x3f, 0xc0, 0x1b, 0xff, 0xae, 0x5c, 0xb8, 0x63, 0xd2, 0xb3, 0xd0, 0x55,
+    0xfa, 0xb0, 0xf3, 0xd1, 0xc2, 0x55, 0xb5, 0x6a, 0x5d, 0x60, 0x3d, 0x94, 0xa4, 0x26, 0x6f, 0x5e,
+    0x8e, 0x01, 0xf8, 0xe1, 0xd4, 0x1a, 0x0c, 0xd7, 0x32, 0xcd, 0xf0, 0xdc, 0x70, 0x04, 0x61, 0x42,
+    0xd0, 0x81, 0xaf, 0x12, 0x04, 0x54, 0x76, 0x18, 0x3a, 0x57, 0x09, 0xbe, 0x5b, 0xa8, 0xf0, 0x72,
+    0xa8, 0xb6, 0x60, 0xe4, 0xc2, 0xb5, 0x01, 0x1b, 0x87, 0x91, 0x24, 0x5a, 0xbf, 0x07, 0x95, 0x1d,
+    0xac, 0x1c, 0xcf, 0x66, 0x8d, 0x38, 0x16, 0xf4, 0x0a, 0xe5, 0xd9, 0x58, 0x2f, 0x16, 0x6d, 0x04,
+    0x18, 0x9a, 0x06, 0x01, 0xdc, 0xff, 0x1c, 0xec, 0x23, 0x7c, 0x19, 0xfc, 0x6a, 0x73, 0x9e, 0xbb,
+    0x9c, 0x01, 0x40, 0x7c, 0xc1, 0x80, 0x5d, 0x46, 0x98, 0xe1, 0x92, 0x04, 0xe7, 0x32, 0xc4, 0x1d,
+    0xb8, 0x0e, 0x23, 0x4f, 0x41, 0xc5, 0x81, 0xba, 0xfb, 0x3f, 0xad, 0xb6, 0x94, 0x76, 0xdc, 0x9e,
+    0x0f, 0x9b, 0x33, 0xcf, 0xff, 0xed, 0xbf, 0xf3, 0xd1, 0xc1, 0x1f, 0x59, 0x54, 0xf0, 0xa7, 0x9d,
+    0x1d, 0x3c, 0x5f, 0x28, 0x33, 0x78, 0x9f, 0x85, 0x58, 0x8b, 0x6e, 0xad, 0x23, 0x17, 0x60, 0xc0,
+    0x10, 0x45, 0x16, 0xa0, 0xe4, 0x44, 0x7c, 0x2c, 0x94, 0x1d, 0x24, 0x5a, 0xbf, 0x00, 0x95, 0x17,
+    0x7c, 0x1c, 0x55, 0x40, 0x73, 0x63, 0x44, 0x0a, 0x74, 0xe4, 0xbc, 0xe0, 0xec, 0x7a, 0x11, 0xdd,
+    0x39, 0x2f, 0x24, 0xb8, 0x49, 0xd1, 0x4c, 0x1a, 0xba, 0x43, 0x22, 0x74, 0x0a, 0x32, 0x9a, 0xaf,
+    0x00, 0x0d, 0x66, 0xd3, 0x69, 0x65, 0x6a, 0x1f, 0x80, 0x41, 0xc5, 0xe1, 0xdc, 0xc8, 0x5f, 0xac,
+    0xa1, 0x80, 0x90, 0xee, 0x85, 0x78, 0x1f, 0x22, 0x7c, 0x1b, 0x69, 0xdb, 0x35, 0x87, 0x8c, 0xcb,
+    0x31, 0x69, 0x7c, 0xbf, 0x99, 0xa3, 0xe3, 0xf9, 0x1c, 0xce, 0x82, 0x3b, 0xd7, 0x2f, 0xa5, 0x83,
+    0xfa, 0xb9, 0x31, 0x62, 0xf2, 0x96, 0x85, 0xb2, 0x04, 0x94, 0x8c, 0x18, 0xff, 0xcd, 0x2a, 0x42,
+    0x0c, 0x02, 0xa9, 0x4f, 0xbd, 0x6a, 0xc0, 0x82, 0x9b, 0x4d, 0x25, 0x80, 0x87, 0x22, 0x5b, 0xe0,
+    0x6b, 0x77, 0xba, 0xa3, 0x40, 0xdd, 0x03, 0x9d, 0x19, 0x11, 0xbc, 0x0d, 0xc6, 0x89, 0x35, 0xd6,
+    0xa3, 0x88, 0xaf, 0x17, 0xad, 0x3d, 0xb0, 0x26, 0x42, 0x61, 0xb0, 0x72, 0x08, 0x9a, 0xdd, 0x42,
+    0x83, 0x92, 0x45, 0x37, 0x3a, 0x97, 0x93, 0x55, 0xb4, 0x38, 0xa3, 0x42, 0x2a, 0x62, 0xe2, 0x67,
+    0x4d, 0x1b, 0x98, 0xdb, 0x5d, 0xff, 0xd4, 0xfe, 0x02, 0x80, 0x81, 0x0c, 0xaa, 0x06, 0xa2, 0x96,
+    0xab, 0x20, 0xe4, 0x96, 0xeb, 0xe9, 0x1a, 0xae, 0xb2, 0xae, 0x0c, 0xbf, 0x42, 0xa4, 0xd6, 0x02,
+    0x16, 0x39, 0x4a, 0x9c, 0x73, 0x33, 0xb6, 0xcf, 0xf8, 0x48, 0x1e, 0xf9, 0xe7, 0xdf, 0xb3, 0xb4,
+    0xa1, 0x85, 0x23, 0x81, 0x62, 0xe0, 0x4b, 0xf2, 0x76, 0xb9, 0xc4, 0xf2, 0x61, 0x56, 0x7d, 0xba,
+    0x86, 0x01, 0x17, 0x86, 0x48, 0xe0, 0x14, 0x61, 0x07, 0xbf, 0x5c, 0x58, 0xd0, 0xe1, 0xfc, 0xb3,
+    0x56, 0x1b, 0xe7, 0x46, 0x9e, 0x15, 0x11, 0x19, 0x12, 0x39, 0x57, 0xf8, 0xdd, 0xe0, 0x84, 0xf8,
+    0x0c, 0x6e, 0x27, 0xd3, 0x9f, 0x22, 0xd5, 0x50, 0x71, 0x48, 0xa2, 0x8a, 0xe0, 0xe4, 0xa5, 0x85,
+    0x5a, 0xed, 0x00, 0x24, 0xb2, 0x7f, 0x9f, 0xa5, 0x41, 0xf5, 0xc1, 0xc9, 0x55, 0x47, 0xf1, 0xf5,
+    0x63, 0xb8, 0xc9, 0x8b, 0x17, 0x70, 0x63, 0xd5, 0xee, 0xef, 0xc9, 0x78, 0x47, 0xc1, 0x9d, 0x13,
+    0x00, 0xc4, 0xf2, 0x8d, 0xf6, 0x18, 0x7f, 0xe7, 0xcc, 0xab, 0x86, 0xcd, 0xa8, 0x1b, 0x13, 0xd1,
+    0x4c, 0x33, 0x96, 0x98, 0x81, 0xbc, 0x44, 0x1a, 0x0f, 0x91, 0x21, 0xab, 0x98, 0xaa, 0x92, 0x04,
+    0xaf, 0x32, 0x75, 0x1b, 0x26, 0xf0, 0xf5, 0x60, 0xe0, 0x16, 0x60, 0xfe, 0xb9, 0xf8, 0x78, 0x13,
+    0xb7, 0xd0, 0xb6, 0x9d, 0xc3, 0x00, 0x55, 0x46, 0x59, 0x5d, 0xf0, 0xe1, 0x38, 0x5a, 0xcb, 0x01,
+    0xec, 0xa2, 0x02, 0x31, 0xf5, 0x0c, 0xff, 0xae, 0xe2, 0xcd, 0xa3, 0x09, 0x70, 0x00, 0x7b, 0x78,
+    0x29, 0x86, 0x05, 0x86, 0x62, 0x18, 0x05, 0x84, 0x09, 0xaa, 0x58, 0x30, 0xff, 0x9d, 0x10, 0x2d,
+    0xb7, 0x79, 0xfb, 0xd7, 0x83, 0x92, 0x07, 0x6e, 0x6c, 0xde, 0x02, 0xf4, 0xb4, 0xa2, 0xb7, 0x01,
+    0xc8, 0x8b, 0x55, 0x14, 0x2e, 0xbd, 0x6f, 0xf4, 0x4b, 0x75, 0xc6, 0x00, 0x0e, 0x48, 0x1b, 0x56,
+    0x5e, 0x8d, 0xaf, 0x5d, 0x6f, 0xbd, 0x88, 0xc4, 0x0c, 0x2e, 0x04, 0xfa, 0x69, 0x06, 0x53, 0xd4,
+    0x50, 0x15, 0xb2, 0x40, 0x82, 0x51, 0x9a, 0xb2, 0x54, 0x73, 0x15, 0x55, 0xf2, 0x91, 0x3c, 0xd3,
+    0x92, 0x66, 0xf4, 0xf0, 0x92, 0xef, 0xf8, 0x97, 0x26, 0x7c, 0x1d, 0xfa, 0x93, 0xbf, 0x08, 0x0f,
+    0x9c, 0x30, 0x21, 0x62, 0xb5, 0x44, 0xab, 0xb4, 0x00, 0x8a, 0x85, 0x83, 0xfa, 0x1e, 0xbb, 0xc3,
+    0x54, 0x72, 0x80, 0x18, 0xca, 0xa7, 0xae, 0xe2, 0xf1, 0x48, 0x07, 0xfa, 0xd3, 0x43, 0x56, 0x11,
+    0x4a, 0x3b, 0x02, 0x31, 0x66, 0x0c, 0x7f, 0xe7, 0x4c, 0x9b, 0x7d, 0x94, 0xcc, 0x90, 0x46, 0x06,
+    0x36, 0x94, 0x70, 0x9a, 0x58, 0x59, 0x1e, 0xe0, 0x5e, 0x9b, 0x15, 0xe2, 0xae, 0x23, 0x1a, 0x52,
+    0xbe, 0xfd, 0xfb, 0x15, 0xd7, 0xce, 0xc5, 0xcf, 0x42, 0x78, 0x00, 0x36, 0xc8, 0x88, 0xea, 0x54,
+    0x1c, 0x94, 0x7f, 0xbc, 0x2e, 0x7b, 0x44, 0x5c, 0x09, 0x64, 0xdb, 0xf0, 0x63, 0xb8, 0xcb, 0x8b,
+    0x3f, 0x5b, 0x8f, 0x50, 0xdf, 0xbd, 0xc8, 0xec, 0x7c, 0xf1, 0xde, 0xd0, 0xc0, 0xf3, 0x89, 0x03,
+    0x62, 0x4e, 0xba, 0x88, 0x65, 0x3d, 0xc0, 0xb1, 0x9c, 0xc1, 0xf6, 0xd2, 0xa3, 0xa4, 0x8c, 0x32,
+    0x60, 0x9a, 0x18, 0x36, 0xcf, 0x9a, 0xe6, 0x45, 0x57, 0x66, 0xe6, 0x85, 0x01, 0x66, 0x51, 0x96,
+    0x61, 0xec, 0xb2, 0xbf, 0xcf, 0x28, 0x97, 0x8d, 0x5d, 0xd5, 0x6c, 0x65, 0xc1, 0x23, 0x1f, 0x22,
+    0x05, 0xd6, 0x2a, 0x18, 0x3f, 0x2d, 0xe0, 0x12, 0xc3, 0x78, 0xbf, 0xf3, 0x85, 0x79, 0x96, 0x8b,
+    0x87, 0x75, 0x48, 0x33, 0x95, 0x9b, 0x82, 0xb2, 0x58, 0x4a, 0xc1, 0x80, 0x47, 0x46, 0x46, 0x29,
+    0xc1, 0x80, 0x03, 0xa8, 0xc7, 0x3e, 0x00, 0xbe, 0xb2, 0xc3, 0x14, 0x06, 0x48, 0x2e, 0xfc, 0x3b,
+    0x95, 0x0c, 0x7f, 0xe7, 0xf0, 0x3b, 0xec, 0x0a, 0x9e, 0x00, 0x97, 0x8a, 0xa2, 0xe9, 0x55, 0x2d,
+    0x38, 0x3a, 0xfb, 0xe7, 0x7e, 0x77, 0x72, 0x6a, 0xc7, 0x1b, 0x4e, 0x18, 0xbc, 0x20, 0x4b, 0x6b,
+    0x5f, 0xbf, 0xf1, 0x0e, 0x11, 0x68, 0xf8, 0xc9, 0x00, 0xd6, 0x90, 0x9b, 0xbc, 0x00, 0xca, 0xbe,
+    0xf2, 0x7a, 0xe1, 0x4f, 0xc6, 0x8d, 0xb6, 0x6b, 0x7d, 0x77, 0x7b, 0x32, 0x69, 0x56, 0x50, 0xc4,
+    0x50, 0x35, 0xc0, 0xe0, 0xe8, 0xc6, 0xf5, 0x77, 0x73, 0xc0, 0x00, 0xb3, 0xe1, 0x0f, 0x71, 0x0b,
+    0x80, 0x35, 0x09, 0x7f, 0x44, 0x2d, 0xbb, 0x4f, 0x70, 0xae, 0x69, 0x99, 0x19, 0xed, 0xaa, 0xe3,
+    0x31, 0x3e, 0x37, 0xcb, 0x95, 0x9a, 0xc0, 0x05, 0xbe, 0x7d, 0xb4, 0xed, 0x72, 0xd7, 0x41, 0x82,
+    0xa4, 0x05, 0x19, 0x21, 0xc9, 0x38, 0x28, 0xb0, 0xd3, 0xfb, 0x01, 0x51, 0x86, 0x86, 0x7f, 0xd6,
+    0xa6, 0x86, 0x60, 0x13, 0x83, 0x98, 0xd1, 0xaf, 0xb9, 0x4f, 0x10, 0x73, 0xb6, 0x28, 0x2f, 0xf2,
+    0xda, 0x53, 0x6d, 0x19, 0xbd, 0x78, 0x77, 0xf7, 0x05, 0x36, 0x7f, 0x22, 0x78, 0x2d, 0x14, 0x62,
+    0xc5, 0xdd, 0x5f, 0xa8, 0x12, 0x5d, 0x7f, 0x80, 0x9d, 0xd9, 0x41, 0x67, 0xd4, 0x30, 0x1a, 0xbe,
+    0x59, 0xf8, 0xb1, 0xdc, 0xa4, 0xc5, 0xbf, 0xb3, 0xc7, 0xaf, 0x05, 0x5e, 0xc1, 0xc9, 0x26, 0x9f,
+    0x89, 0x3a, 0x1a, 0x41, 0x6c, 0xd1, 0xc2, 0x07, 0x69, 0xcf, 0xff, 0xaf, 0xa1, 0x2d, 0x5f, 0xaa,
+    0xeb, 0xc0, 0xd5, 0x3d, 0x34, 0x1b, 0x01, 0xdd, 0x08, 0xdc, 0x15, 0xdc, 0x6f, 0xda, 0x44, 0x35,
+    0x95, 0x05, 0xb0, 0x13, 0x58, 0x66, 0x7e, 0xa8, 0x4d, 0x55, 0xb0, 0xfe, 0xc8, 0x49, 0xcd, 0xe3,
+    0x5e, 0xfb, 0x9a, 0x1a, 0x9b, 0xd7, 0x3f, 0xe9, 0x4c, 0xe8, 0x1d, 0x0b, 0x67, 0xa5, 0x0d, 0x82,
+    0x17, 0x4c, 0x35, 0x5e, 0x86, 0xa1, 0x22, 0x20, 0xf1, 0x23, 0x9c, 0x55, 0x21, 0xb3, 0xf0, 0xc0,
+    0x67, 0x4c, 0xc3, 0xc3, 0xe0, 0xcf, 0x44, 0xeb, 0xcc, 0x8c, 0xb3, 0x0a, 0x77, 0x0c, 0x76, 0x22,
+    0x95, 0xe7, 0xc1, 0xa5, 0xb1, 0xdf, 0x82, 0xb8, 0x00, 0xb0, 0xae, 0x58, 0x0c, 0xff, 0x02, 0xf1,
+    0xe1, 0x0c, 0x2b, 0x62, 0x0f, 0xc7, 0x18, 0xcd, 0xbe, 0x77, 0xee, 0x15, 0x49, 0x53, 0xa1, 0x14,
+    0xc9, 0x6e, 0x98, 0xc1, 0xef, 0xd1, 0xbe, 0x0d, 0xd7, 0x5a, 0xfe, 0x61, 0xdf, 0x11, 0xc5, 0x34,
+    0x2a, 0xfe, 0x58, 0x39, 0x46, 0x8d, 0x59, 0x3c, 0x58, 0xcd, 0x0b, 0x01, 0x55, 0x00, 0x49, 0xbd,
+    0xfe, 0x44, 0x37, 0x8c, 0xae, 0x12, 0x7b, 0x25, 0x03, 0x70, 0x09, 0x5e, 0xc0, 0xb6, 0x8b, 0x72,
+    0x00, 0xb0, 0x8d, 0x83, 0xd5, 0x10, 0xbe, 0xda, 0x38, 0x42, 0xaf, 0x8b, 0xb1, 0xcc, 0x54, 0x16,
+    0x62, 0x86, 0xff, 0x9c, 0x03, 0xbf, 0x8d, 0x9b, 0x1e, 0x1b, 0x3d, 0x1c, 0x6b, 0x78, 0xd7, 0x08,
+    0xfc, 0xc7, 0x24, 0xdb, 0xc6, 0xff, 0xed, 0x11, 0xa7, 0x86, 0x14, 0x47, 0x0e, 0x5e, 0xf6, 0x96,
+    0xb1, 0x79, 0x03, 0x4a, 0x0b, 0x53, 0xfd, 0x93, 0x47, 0x1b, 0x6c, 0x25, 0x6a, 0x90, 0xf4, 0x21,
+    0x00, 0x7b, 0xd6, 0x96, 0xbe, 0x73, 0x3c, 0xe2, 0xd4, 0xe2, 0xf6, 0xd2, 0x23, 0xe9, 0x0d, 0xb2,
+    0x4a, 0x2f, 0xe6, 0x1f, 0x3d, 0x89, 0x42, 0x79, 0x62, 0xbf, 0x02, 0xf0, 0x86, 0x0c, 0xd7, 0x3b,
+    0x04, 0x95, 0x44, 0x18, 0x5c, 0xe4, 0x6d, 0x7c, 0x8d, 0xb4, 0x16, 0x55, 0xa3, 0xf3, 0xa9, 0xf7,
+    0x4b, 0x54, 0x20, 0x3d, 0x28, 0x39, 0x97, 0x24, 0x5c, 0x00, 0x99, 0xdc, 0x7f, 0xe7, 0x92, 0x82,
+    0x5b, 0x83, 0x3b, 0x81, 0x8e, 0xe3, 0x4e, 0x2d, 0x7c, 0x03, 0x1e, 0xb2, 0xcd, 0x7b, 0x39, 0x22,
+    0x8d, 0x58, 0xfe, 0xba, 0xc5, 0xa2, 0xe3, 0x6c, 0xc3, 0xf2, 0x72, 0x3a, 0x30, 0xbc, 0xba, 0x69,
+    0x06, 0xf9, 0x46, 0x21, 0xa1, 0x80, 0x83, 0x62, 0xaa, 0xa0, 0xd8, 0x8b, 0xe7, 0x27, 0x28, 0xd5,
+    0xd7, 0xec, 0xae, 0x15, 0xfd, 0x92, 0x63, 0x64, 0x1c, 0x04, 0xab, 0x23, 0x1b, 0x77, 0xf1, 0x5e,
+    0x34, 0x93, 0x01, 0xc9, 0x06, 0xdb, 0xcf, 0x98, 0xc4, 0x39, 0x31, 0xbf, 0x70, 0xde, 0x69, 0x47,
+    0x6f, 0x75, 0xe0, 0x81, 0x0a, 0xea, 0x89, 0x26, 0x4d, 0xc8, 0x3a, 0xc0, 0xf0, 0xbc, 0x06, 0xd7,
+    0xfb, 0xbc, 0xc7, 0x97, 0x7e, 0x4b, 0xfb, 0xcd, 0x8b, 0xfe, 0xee, 0xdb, 0xd5, 0x54, 0xbe, 0x5d,
+    0x05, 0xf3, 0x8f, 0x7e, 0x8e, 0xe9, 0x78, 0x1f, 0xcf, 0xfa, 0xae, 0x21, 0x91, 0x15, 0x80, 0x17,
+    0x78, 0x55, 0xc4, 0x21, 0x44, 0xdd, 0xbf, 0xaf, 0x6f, 0xd2, 0x11, 0xcb, 0x9a, 0x8a, 0x34, 0xa5,
+    0xf0, 0x2d, 0x51, 0x42, 0x31, 0xce, 0xf2, 0x8e, 0xa4, 0x12, 0x16, 0x71, 0xb6, 0x8a, 0x49, 0x54,
+    0x6b, 0x0e, 0x79, 0xa6, 0x8f, 0xf2, 0xe3, 0x00, 0x3b, 0x4b, 0xaf, 0xcf, 0x02, 0x41, 0xeb, 0xf0,
+    0x75, 0xd1, 0xb7, 0xb8, 0x92, 0x86, 0xfd, 0x25, 0x08, 0x7c, 0xb3, 0xdf, 0x8b, 0x5e, 0x66, 0xbd,
+    0x1c, 0x91, 0x46, 0xac, 0x5d, 0xbd, 0xe3, 0xaa, 0x15, 0xf1, 0xb7, 0x04, 0xff, 0x31, 0x65, 0xbb,
+    0x81, 0x4d, 0x9b, 0x9d, 0x0d, 0x02, 0xfc, 0xc2, 0x91, 0x70, 0x0b, 0x70, 0x1d, 0xa4, 0x0a, 0xa2,
+    0xba, 0x60, 0x58, 0x81, 0x5d, 0x13, 0xd1, 0xbe, 0xe7, 0x81, 0x9a, 0x4c, 0xed, 0x81, 0x72, 0x22,
+    0xaa, 0xa9, 0x66, 0xc6, 0x12, 0x51, 0x58, 0x72, 0x5a, 0x13, 0xde, 0xcc, 0x18, 0xab, 0x75, 0x67,
+    0xbe, 0x28, 0x7c, 0x9e, 0x88, 0x2d, 0x42, 0xc8, 0xe2, 0xe0, 0x61, 0x62, 0xe0, 0x84, 0x69, 0xef,
+    0xdf, 0x35, 0x5a, 0xf9, 0xfd, 0x19, 0x17, 0x4d, 0xf2, 0x37, 0x29, 0xfc, 0xe0, 0x39, 0x17, 0x6f,
+    0xab, 0xff, 0x8d, 0x8a, 0x2d, 0xa2, 0xd6, 0x55, 0x37, 0x8c, 0xa1, 0x43, 0xb9, 0x9f, 0x90, 0x8d,
+    0x7c, 0x1c, 0xb7, 0xa5, 0x5e, 0x10, 0xdf, 0xab, 0x75, 0x49, 0xc1, 0xc9, 0xd4, 0xcf, 0xaf, 0x63,
+    0x24, 0x99, 0xab, 0x07, 0xef, 0x51, 0x9b, 0xdb, 0xfa, 0xc2, 0xe3, 0x5f, 0xa6, 0x19, 0x21, 0x83,
+    0x04, 0xed, 0x00, 0x92, 0x47, 0xa6, 0xd5, 0xc0, 0x7f, 0x9c, 0x30, 0x13, 0x78, 0x16, 0x44, 0x40,
+    0xaa, 0xae, 0xef, 0xf0, 0x4b, 0x13, 0x46, 0x1f, 0x05, 0x64, 0x11, 0xd3, 0x13, 0xdb, 0x66, 0x86,
+    0x30, 0x31, 0x77, 0x4d, 0xf0, 0x32, 0x88, 0xa4, 0x8d, 0x9c, 0xf7, 0xef, 0x57, 0x80, 0xc3, 0x84,
+    0x27, 0x34, 0x9c, 0x1b, 0x6d, 0x9f, 0x98, 0x11, 0xf8, 0x63, 0xc9, 0xfd, 0x2c, 0x29, 0xf1, 0xa8,
+    0x4e, 0x19, 0x5c, 0x41, 0x00, 0xa5, 0x17, 0xcc, 0x0c, 0x91, 0x5a, 0xbe, 0x93, 0x06, 0x84, 0xdb,
+    0x0c, 0xa3, 0x35, 0x4f, 0xdc, 0xc5, 0x35, 0xa7, 0x61, 0x45, 0xf7, 0x84, 0xd3, 0xe1, 0xae, 0x6c,
+    0x02, 0x55, 0x94, 0x30, 0x79, 0xc3, 0xaa, 0x87, 0xab, 0x6e, 0xf8, 0x70, 0x80, 0x7b, 0xea, 0xa3,
+    0xd7, 0xb5, 0x92, 0x2c, 0xd5, 0x83, 0xac, 0x68, 0xb6, 0x03, 0xfe, 0xb2, 0x5c, 0xd7, 0xa1, 0x85,
+    0x04, 0x04, 0x54, 0x42, 0x1a, 0xc7, 0x80, 0x76, 0xaa, 0x7b, 0x30, 0x08, 0xcb, 0x0c, 0x87, 0x08,
+    0x2e, 0x19, 0x10, 0x60, 0x05, 0x53, 0xd9, 0xa9, 0xe0, 0xc8, 0xb4, 0x49, 0x51, 0xc5, 0xd0, 0x8e,
+    0x33, 0x8c, 0xc0, 0x9c, 0x70, 0x02, 0x9b, 0x58, 0xdb, 0xf0, 0x43, 0x99, 0x75, 0x39, 0xb6, 0x78,
+    0x99, 0xd1, 0xe3, 0xe9, 0x8f, 0x80, 0x92, 0x12, 0x9b, 0xa0, 0xc1, 0x60, 0xdf, 0x01, 0x64, 0x7c,
+    0x1f, 0x07, 0xc4, 0x3e, 0xe3, 0xbb, 0x00, 0x58, 0x0f, 0x00, 0x49, 0x3b, 0x41, 0x44, 0xa4, 0x54,
+    0x88, 0xd5, 0xd7, 0xad, 0xcd, 0x0c, 0x2a, 0x26, 0x50, 0xaa, 0x8b, 0x0b, 0xf2, 0xbb, 0x16, 0x74,
+    0xcb, 0x6a, 0x37, 0xe0, 0x72, 0x2d, 0x23, 0x11, 0xb2, 0xae, 0x28, 0xa4, 0x30, 0xc0, 0x18, 0x0b,
+    0xda, 0x55, 0x63, 0x69, 0xc5, 0x95, 0xce, 0xa2, 0x7d, 0xc8, 0xb5, 0x2a, 0x75, 0xad, 0x00, 0x49,
+    0x89, 0x6e, 0x0e, 0x4a, 0x38, 0xe6, 0x24, 0x88, 0xab, 0x07, 0xef, 0x51, 0xb5, 0x5f, 0x80, 0xec,
+    0x87, 0xdb, 0x87, 0x0a, 0xb6, 0xcf, 0xc0, 0x20, 0x1c, 0x10, 0x87, 0x01, 0x18, 0x05, 0xd3, 0x8c,
+    0x02, 0x6c, 0x43, 0x0c, 0x18, 0x32, 0x3f, 0xb7, 0x45, 0xb6, 0x24, 0xb8, 0xe1, 0x61, 0x1b, 0x73,
+    0xd7, 0x3c, 0x67, 0x95, 0x57, 0x69, 0x3c, 0x39, 0x5e, 0x3a, 0xc6, 0x44, 0x28, 0xc4, 0x95, 0x0e,
+    0x5d, 0x33, 0x85, 0x43, 0x8d, 0xd8, 0xc6, 0xf1, 0x2d, 0x51, 0x31, 0x3c, 0x7b, 0xdb, 0xb6, 0xb5,
+    0x94, 0xe6, 0xbd, 0x75, 0xd6, 0x05, 0x2a, 0xf4, 0xfa, 0x6b, 0xbc, 0x0a, 0xa7, 0xda, 0x54, 0xe4,
+    0x6b, 0xc9, 0x72, 0x71, 0x8b, 0x54, 0xa3, 0x93, 0x79, 0x0a, 0x7d, 0xd5, 0x81, 0x9f, 0xc2, 0x82,
+    0x77, 0x1f, 0xd8, 0xe5, 0x5f, 0xd2, 0xee, 0xbe, 0x84, 0x19, 0x10, 0x04, 0xfc, 0x6c, 0x71, 0xa6,
+    0xc2, 0x2d, 0xd3, 0xed, 0x1c, 0xbd, 0x9e, 0xc6, 0x97, 0x82, 0xb8, 0x10, 0xb7, 0xae, 0x9a, 0xc0,
+    0x65, 0x3e, 0x6f, 0x6d, 0xce, 0x81, 0xc6, 0xbc, 0x96, 0xfc, 0x67, 0xd2, 0xfc, 0x0d, 0x95, 0x9e,
+    0x05, 0xbd, 0xfb, 0xe6, 0x74, 0x3a, 0x56, 0x36, 0xec, 0x0f, 0x69, 0xca, 0xcf, 0xcd, 0xa9, 0xef,
+    0xb3, 0x86, 0x63, 0xb3, 0xed, 0xde, 0x0b, 0x66, 0x5f, 0x3f, 0xaf, 0x80, 0x7f, 0xce, 0x92, 0x4d,
+    0xaf, 0x83, 0x11, 0xf6, 0x83, 0xac, 0x4f, 0xc1, 0x49, 0x2b, 0x16, 0x40, 0x8f, 0x0b, 0xcb, 0xec,
+    0x6e, 0xfc, 0xe4, 0x8c, 0xcf, 0xe0, 0x94, 0xd3, 0xac, 0x1c, 0xcd, 0x46, 0x58, 0x0e, 0xa6, 0x44,
+    0x47, 0x00, 0xb6, 0x94, 0x9f, 0xa7, 0x25, 0x9a, 0xb8, 0x6b, 0xd6, 0x93, 0x5b, 0xb5, 0xb7, 0x59,
+    0xf0, 0x2c, 0x2c, 0x90, 0x61, 0x4a, 0xbd, 0x52, 0xea, 0x19, 0x97, 0x0a, 0x91, 0xb1, 0x75, 0x05,
+    0xd4, 0xdd, 0x3d, 0x64, 0x5b, 0x50, 0x70, 0x7c, 0xb7, 0x45, 0x01, 0xdf, 0x37, 0x83, 0x1a, 0xdf,
+    0xc0, 0x76, 0x81, 0x2b, 0xc1, 0x63, 0x62, 0x5b, 0x35, 0xb1, 0x89, 0xa7, 0xf2, 0x17, 0xc3, 0xd7,
+    0x16, 0xf8, 0x7c, 0x61, 0x96, 0x4b, 0x86, 0x93, 0x7c, 0x0d, 0x1c, 0x6c, 0x3e, 0x87, 0x27, 0xb5,
+    0x09, 0xb0, 0xf7, 0x2c, 0x88, 0xef, 0x50, 0xc9, 0x4a, 0x36, 0xc6, 0x47, 0xfa, 0xcb, 0xa6, 0x3f,
+    0x70, 0xc6, 0xbf, 0xe8, 0xed, 0xae, 0x38, 0x0d, 0xa4, 0xe0, 0x4a, 0xcb, 0xe1, 0x6a, 0xa3, 0x0d,
+    0x78, 0x5a, 0x93, 0x43, 0x6a, 0x96, 0x52, 0xad, 0x23, 0x80, 0x5d, 0x10, 0x5d, 0x34, 0xa5, 0x6c,
+    0xb5, 0xca, 0xcb, 0x7c, 0x23, 0xbf, 0x04, 0xa9, 0xde, 0xe0, 0x72, 0xbc, 0x32, 0x5b, 0xc0, 0x09,
+    0x4b, 0x46, 0x42, 0x0e, 0x94, 0x93, 0x8c, 0x9d, 0x9e, 0xa3, 0xdf, 0xa7, 0xf8, 0x7a, 0xc5, 0x79,
+    0x39, 0x53, 0xe9, 0x42, 0xe9, 0x56, 0xff, 0xcf, 0x48, 0x5a, 0x8a, 0xc0, 0x31, 0x79, 0x95, 0x36,
+    0xf4, 0x3e, 0x4e, 0xdb, 0x6b, 0x3f, 0x42, 0x22, 0xd3, 0x73, 0xc0, 0xdd, 0x31, 0x4b, 0xad, 0x87,
+    0x6f, 0xf0, 0xdf, 0x3c, 0xff, 0xb5, 0xb0, 0xdc, 0x3d, 0xe5, 0x36, 0x05, 0x07, 0xf7, 0xf2, 0x7a,
+    0x08, 0x8b, 0xc6, 0xfd, 0x89, 0x50, 0xa6, 0xbd, 0xfc, 0x27, 0x5b, 0x2c, 0x0b, 0xec, 0x25, 0x35,
+    0x49, 0x83, 0x1a, 0xc0, 0x11, 0x9a, 0x34, 0x4e, 0x12, 0x3c, 0x88, 0x87, 0xf3, 0xde, 0x60, 0x1e,
+    0xbe, 0x38, 0xf5, 0x8c, 0x24, 0x9a, 0x37, 0x07, 0xc7, 0xf7, 0x94, 0xa9, 0x17, 0x3d, 0xac, 0x77,
+    0xbc, 0x88, 0x78, 0x16, 0x3d, 0x43, 0x9a, 0xf6, 0x72, 0x51, 0x1a, 0xb0, 0x95, 0xfb, 0xae, 0x03,
+    0xc0, 0x26, 0x91, 0x60, 0x5d, 0x40, 0xd3, 0x61, 0xaa, 0x21, 0x01, 0x18, 0xd5, 0x86, 0xb5, 0x78,
+    0x01, 0x4c, 0xf8, 0x4f, 0x52, 0xbd, 0xe8, 0x7d, 0x99, 0x03, 0x7d, 0x8d, 0xce, 0x1a, 0x7d, 0xce,
+    0xc2, 0xb2, 0xfb, 0x02, 0xfc, 0x67, 0xbf, 0x1b, 0xaa, 0x8c, 0x35, 0x26, 0x15, 0x2d, 0x84, 0x96,
+    0xc4, 0xac, 0xf6, 0x5b, 0xc0, 0x03, 0x77, 0x6f, 0xc2, 0xe4, 0xbd, 0xe1, 0x25, 0xa6, 0x84, 0xaf,
+    0x20, 0xab, 0x54, 0xf1, 0x71, 0x05, 0x83, 0x91, 0x98, 0x81, 0x7b, 0x77, 0xaa, 0xbd, 0x2e, 0xae,
+    0x3f, 0xd6, 0x27, 0x2e, 0xcd, 0x3d, 0x61, 0x19, 0xb1, 0x24, 0xc0, 0x2f, 0xbd, 0x2b, 0x36, 0x05,
+    0xd7, 0x06, 0x3d, 0xcf, 0x27, 0x8e, 0x50, 0x82, 0x16, 0xa6, 0xc0, 0x09, 0xa3, 0x9b, 0x8f, 0x4b,
+    0x4b, 0x6c, 0x56, 0x0e, 0x97, 0xb3, 0xf5, 0x3a, 0xe0, 0x38, 0xcf, 0x72, 0x93, 0x28, 0xa3, 0x85,
+    0x6b, 0x5f, 0xfe, 0x61, 0x10, 0x77, 0xb0, 0x15, 0x92, 0x4c, 0x00, 0x86, 0x92, 0xc3, 0x09, 0x68,
+    0xb9, 0xc8, 0xc5, 0x78, 0x01, 0xcb, 0x9e, 0x2f, 0xf4, 0xf3, 0xd8, 0x41, 0xcd, 0x88, 0xd6, 0x8b,
+    0xae, 0x5b, 0x1e, 0x54, 0x7e, 0xf1, 0xec, 0xa0, 0xef, 0x75, 0xd6, 0xd9, 0xce, 0x1f, 0x6c, 0x8e,
+    0xe6, 0x5a, 0x15, 0x46, 0x1e, 0x88, 0x2e, 0x2b, 0xbb, 0xb9, 0xb4, 0xbf, 0x4f, 0x63, 0xf9, 0x5e,
+    0x8e, 0xcd, 0xa3, 0xfd, 0x11, 0x53, 0x2c, 0xf3, 0x54, 0x57, 0x3a, 0xf7, 0x88, 0x18, 0x57, 0x11,
+    0x9d, 0x89, 0xb5, 0xff, 0xca, 0xb1, 0x66, 0x22, 0xbe, 0x24, 0x78, 0x13, 0x69, 0x47, 0x16, 0xa3,
+    0xc0, 0x09, 0x6e, 0x9f, 0x0f, 0xed, 0x5f, 0x2f, 0xab, 0x9c, 0x95, 0xaa, 0x6f, 0xea, 0x00, 0xcf,
+    0x55, 0xde, 0xbd, 0x8f, 0xc9, 0x18, 0xc7, 0xc1, 0x41, 0x9b, 0xb0, 0x72, 0x35, 0x1a, 0x0f, 0xb9,
+    0x19, 0x11, 0x97, 0x85, 0x07, 0xa5, 0xe1, 0xc0, 0xb2, 0xb3, 0x0c, 0x74, 0x2f, 0x33, 0xb0, 0xc0,
+    0xe9, 0xe2, 0x48, 0xe0, 0x42, 0xb0, 0x23, 0xe5, 0x58, 0x93, 0x60, 0x17, 0x30, 0xa8, 0x13, 0x2f,
+    0x3c, 0x48, 0x18, 0x3b, 0xb1, 0x1b, 0x16, 0xc1, 0x57, 0xd6, 0xed, 0xd5, 0x75, 0x78, 0x01, 0xc1,
+    0x23, 0x2f, 0x8e, 0xf3, 0x91, 0xcd, 0x72, 0x5e, 0x12, 0x53, 0x8d, 0xfa, 0xc0, 0x7d, 0xc5, 0x93,
+    0x3f, 0xcf, 0xff, 0xf1, 0xf5, 0xc3, 0x23, 0xeb, 0x2e, 0x4b, 0x92, 0x9a, 0xe5, 0xb8, 0x2f, 0x6c,
+    0xc3, 0x47, 0x07, 0xec, 0x83, 0x0d, 0xff, 0x9d, 0xa9, 0x4d, 0x09, 0x9d, 0x75, 0xb4, 0x24, 0x16,
+    0xaf, 0x00, 0xe7, 0xc6, 0x1a, 0x8f, 0xaf, 0x64, 0x47, 0xa9, 0x50, 0xbf, 0x08, 0xfb, 0x2c, 0xf8,
+    0x77, 0xff, 0x14, 0x6d, 0x58, 0xe0, 0x74, 0xb3, 0x02, 0x50, 0xa6, 0x0c, 0xaa, 0xb1, 0x0b, 0x2b,
+    0x1d, 0x15, 0xa0, 0x4d, 0xfc, 0x88, 0xb7, 0x0f, 0x57, 0xf4, 0x92, 0xcc, 0x50, 0x3e, 0x35, 0x09,
+    0x0a, 0x18, 0xab, 0xcc, 0xf8, 0x72, 0xfd, 0xf7, 0x9f, 0x1b, 0xdd, 0x5d, 0x2b, 0xcb, 0xcd, 0xbd,
+    0xb7, 0x1d, 0x96, 0xdd, 0xb0, 0xd3, 0xdb, 0x64, 0xbe, 0x33, 0xa8, 0x92, 0x01, 0x0f, 0x2c, 0xe5,
+    0x85, 0xd2, 0x79, 0x9d, 0x23, 0xfe, 0xf8, 0xda, 0x1d, 0x34, 0xd6, 0x95, 0x7f, 0x93, 0x4d, 0x7e,
+    0xec, 0x4f, 0x03, 0xfe, 0xc1, 0x9d, 0xc0, 0x54, 0xe0, 0xca, 0xea, 0x95, 0x4d, 0x34, 0x82, 0x8c,
+    0x94, 0xd7, 0x87, 0xd2, 0x24, 0x5a, 0x1f, 0x00, 0x9e, 0xd8, 0x85, 0xe3, 0xf3, 0xe0, 0x1c, 0xfb,
+    0x9b, 0xf0, 0x63, 0xb8, 0x64, 0x45, 0x70, 0x32, 0x3d, 0x69, 0x9a, 0xf6, 0x72, 0x59, 0x1a, 0xb0,
+    0x3a, 0xb5, 0xfb, 0x60, 0x38, 0x05, 0x0e, 0x52, 0x17, 0x3f, 0xfc, 0x09, 0x14, 0xee, 0x18, 0x04,
+    0xe3, 0x76, 0x13, 0xad, 0x09, 0x5c, 0x2c, 0x2c, 0x5c, 0xed, 0x5d, 0xd6, 0xae, 0xe0, 0xfb, 0x2d,
+    0x18, 0x47, 0x11, 0xb2, 0x15, 0x74, 0x67, 0x94, 0x05, 0x83, 0x9f, 0xe3, 0xd5, 0x53, 0x55, 0x9d,
+    0x72, 0xbe, 0xf0, 0x2f, 0x69, 0x48, 0x6f, 0x63, 0xf9, 0x5e, 0xf2, 0xc0, 0xb0, 0xee, 0x6f, 0x66,
+    0x51, 0x53, 0xbb, 0x4d, 0x28, 0xb6, 0x11, 0x36, 0x16, 0xc8, 0xea, 0xfd, 0x5e, 0xba, 0xda, 0x1e,
+    0x76, 0x99, 0xd0, 0x65, 0x54, 0xea, 0xc0, 0x72, 0x6e, 0x77, 0xe7, 0xee, 0xdb, 0xd7, 0xfb, 0x9b,
+    0x4d, 0xda, 0x37, 0x30, 0x6e, 0xf8, 0xc5, 0xef, 0x60, 0x13, 0x0e, 0x18, 0x58, 0x9c, 0x79, 0xda,
+    0xd0, 0xc2, 0xa3, 0x2c, 0x66, 0xb2, 0x8b, 0xa3, 0x73, 0x3b, 0x35, 0x5a, 0xb1, 0x39, 0x09, 0x26,
+    0x0d, 0xc0, 0x24, 0x89, 0xaf, 0x00, 0x53, 0xc4, 0xaa, 0x5c, 0x5e, 0xdd, 0x03, 0xb4, 0xf5, 0xfa,
+    0x91, 0x66, 0xac, 0x1c, 0xad, 0x46, 0x38, 0x0e, 0x80, 0xa8, 0xba, 0x74, 0xf5, 0xf5, 0x60, 0xbf,
+    0xfe, 0x7f, 0x0d, 0xb7, 0xad, 0x38, 0x3d, 0xb4, 0x23, 0xa3, 0xbc, 0x63, 0xc2, 0x08, 0xde, 0x9a,
+    0x00, 0x88, 0xbf, 0x83, 0xe6, 0xec, 0xfc, 0x57, 0xf0, 0xff, 0xd5, 0xb9, 0x07, 0x3d, 0x42, 0x4d,
+    0x82, 0xcb, 0xf9, 0x3d, 0xc8, 0x70, 0xea, 0xb5, 0x50, 0xb3, 0x11, 0x7c, 0x82, 0x3f, 0xfe, 0x57,
+    0xf0, 0x0e, 0xf3, 0x8a, 0xbd, 0x9f, 0x93, 0xb4, 0x96, 0xe5, 0xf1, 0x97, 0x96, 0x0c, 0xd1, 0x79,
+    0xdf, 0xf9, 0x6f, 0xc0, 0xd5, 0x54, 0x81, 0xbb, 0x97, 0x5f, 0xfb, 0xb6, 0x2e, 0x15, 0xb1, 0x7c,
+    0x5b, 0x8b, 0x4b, 0xa3, 0x9a, 0x30, 0xf5, 0x82, 0x1a, 0x7d, 0x1c, 0x16, 0x6c, 0x08, 0xf2, 0x5c,
+    0x17, 0x52, 0xbe, 0xff, 0x52, 0x3e, 0xf7, 0xbb, 0x25, 0x80, 0x5c, 0x46, 0x89, 0xe5, 0xc7, 0x62,
+    0x74, 0xea, 0xdc, 0xa0, 0xda, 0xa0, 0x3f, 0x08, 0xdf, 0xcf, 0xaf, 0x9b, 0xdf, 0x78, 0x91, 0x6b,
+    0x7c, 0x00, 0x77, 0x74, 0xb0, 0x3e, 0x0c, 0xd4, 0x6c, 0xaf, 0xdd, 0xed, 0xc7, 0x71, 0xa7, 0x16,
+    0x3f, 0x33, 0x8f, 0x5a, 0xc7, 0xbd, 0x47, 0x76, 0x07, 0x24, 0x11, 0xd7, 0xa6, 0x9a, 0x56, 0x69,
+    0x3b, 0x12, 0xb0, 0xe0, 0xd0, 0x12, 0x23, 0xc4, 0x8c, 0x32, 0xc4, 0x3a, 0x00, 0xba, 0x6e, 0xc3,
+    0x89, 0x7c, 0x96, 0x48, 0x0a, 0x08, 0x24, 0x30, 0x8d, 0x2c, 0x0b, 0x56, 0xe6, 0xd5, 0x55, 0x39,
+    0x5e, 0xf5, 0xed, 0x3c, 0xc0, 0x5b, 0x59, 0x0b, 0xe9, 0xbe, 0x2e, 0xb9, 0xb5, 0x7e, 0xff, 0xbd,
+    0x6f, 0x80, 0x1e, 0xc4, 0xe4, 0x1b, 0xaf, 0xfc, 0x55, 0x1f, 0x19, 0x7d, 0x4c, 0x4f, 0xcc, 0xbc,
+    0xee, 0xb2, 0x67, 0x02, 0xe9, 0x2f, 0x1b, 0xe4, 0xfa, 0x1b, 0xc5, 0xfc, 0x9a, 0x2e, 0x41, 0x82,
+    0xc6, 0xfe, 0x01, 0x8f, 0x4b, 0x1b, 0x79, 0x1b, 0x2b, 0x85, 0xe9, 0x5f, 0x92, 0xe1, 0xba, 0x97,
+    0xae, 0x9b, 0x68, 0x7c, 0xd2, 0x4c, 0x8d, 0xdc, 0xb2, 0xc0, 0x46, 0xd2, 0xe1, 0x5e, 0x6b, 0xf3,
+    0x79, 0x7a, 0xf3, 0xcb, 0x3c, 0x5d, 0x23, 0xb0, 0x93, 0x47, 0x39, 0xfd, 0x4e, 0x1a, 0xe5, 0x95,
+    0xc1, 0x47, 0x5f, 0xf9, 0xed, 0x0b, 0xdc, 0xcc, 0x75, 0x2a, 0x6a, 0x06, 0x00, 0x91, 0x19, 0x8c,
+    0x56, 0xfd, 0x00, 0x93, 0x18, 0xfc, 0xd2, 0x30, 0xbd, 0xa2, 0xa2, 0xa6, 0xd8, 0x96, 0x78, 0xf1,
+    0x8e, 0x7c, 0x46, 0xc8, 0x79, 0xf8, 0xda, 0xcb, 0x3b, 0x02, 0x6d, 0x5e, 0x01, 0x32, 0xf4, 0x86,
+    0x7f, 0xe6, 0x7c, 0xfd, 0xcf, 0x08, 0x0f, 0x09, 0x6f, 0x03, 0xfa, 0x11, 0x96, 0x7b, 0x73, 0x75,
+    0x90, 0xdc, 0xfe, 0x5e, 0x62, 0x61, 0x67, 0x1b, 0xc8, 0x8c, 0xb9, 0x32, 0x98, 0x4d, 0x9a, 0xd4,
+    0xa0, 0x39, 0x5b, 0xd6, 0x0b, 0x56, 0x56, 0xcf, 0x55, 0x43, 0x2f, 0x6f, 0xb8, 0xf6, 0x83, 0xff,
+    0xe7, 0x62, 0x7b, 0x73, 0xf9, 0x4e, 0xea, 0xec, 0x3e, 0x1e, 0xa6, 0xbf, 0xbf, 0x87, 0xdb, 0x1a,
+    0xdb, 0xca, 0xb4, 0xc7, 0xa5, 0x30, 0x2b, 0xcd, 0x79, 0xef, 0x9b, 0x58, 0xdc, 0x30, 0xf8, 0x70,
+    0x8e, 0x2f, 0x42, 0xde, 0x83, 0x44, 0x25, 0xf7, 0xdd, 0x55, 0x3e, 0x12, 0x55, 0x4e, 0x15, 0x5f,
+    0x37, 0x91, 0xe9, 0xe3, 0x1f, 0x20, 0xda, 0x51, 0xcb, 0xff, 0xfd, 0x6d, 0x72, 0xfc, 0x3f, 0x5b,
+    0x0f, 0xc7, 0x70, 0xcd, 0x84, 0xd9, 0x8e, 0x36, 0x92, 0x1d, 0x4a, 0x61, 0x70, 0x9b, 0x9b, 0x68,
+    0x9b, 0x27, 0xc6, 0x99, 0x10, 0xa3, 0xce, 0x53, 0x21, 0xf4, 0x31, 0x3c, 0x87, 0x84, 0x69, 0x81,
+    0x9d, 0xa5, 0xef, 0x0f, 0xf1, 0xca, 0xae, 0xf0, 0x63, 0x1c, 0x84, 0x5b, 0x8f, 0x77, 0xe5, 0x6b,
+    0x11, 0xec, 0x26, 0x59, 0x9f, 0x16, 0x89, 0xe1, 0xc2, 0xad, 0x56, 0xb8, 0x2e, 0x31, 0x68, 0x5d,
+    0x2a, 0x83, 0xf6, 0xef, 0xfe, 0x61, 0x76, 0x9d, 0x60, 0x2a, 0x87, 0xc5, 0x85, 0xff, 0x22, 0xe4,
+    0x4b, 0x08, 0x48, 0x13, 0x8a, 0x00, 0x3b, 0x48, 0xc9, 0x21, 0x06, 0xc1, 0xbb, 0xd9, 0xd1, 0x3d,
+    0x52, 0x49, 0x6a, 0x44, 0x93, 0x09, 0xc8, 0x8b, 0x8a, 0x5e, 0x7e, 0x8d, 0x0b, 0x26, 0xc3, 0x59,
+    0xbb, 0xad, 0x92, 0xaa, 0x46, 0x0b, 0xbe, 0xd6, 0xaa, 0xa0, 0x36, 0x5f, 0x34, 0x6a, 0x88, 0x84,
+    0x0a, 0xfc, 0xa8, 0xb8, 0xbd, 0xb4, 0xa4, 0xf4, 0xc6, 0xe1, 0x88, 0x55, 0x5d, 0x4e, 0xc9, 0xdc,
+    0x7d, 0xa2, 0x71, 0x0d, 0x18, 0x1e, 0x16, 0x1d, 0x17, 0x11, 0xb3, 0x91, 0x92, 0xf0, 0xc6, 0x1b,
+    0x3a, 0x54, 0x80, 0x8f, 0x11, 0x71, 0x9b, 0x02, 0x16, 0xf4, 0x8f, 0xf0, 0x37, 0xf2, 0xea, 0xa8,
+    0x95, 0x61, 0x8e, 0x8f, 0x22, 0x22, 0xc0, 0xef, 0x51, 0x0b, 0x18, 0x8d, 0xd8, 0xe8, 0x0b, 0x76,
+    0x3e, 0xa5, 0x28, 0xc3, 0x6f, 0xde, 0xf1, 0xf4, 0x55, 0x7d, 0x48, 0xfd, 0xe3, 0xa5, 0x64, 0x45,
+    0x70, 0x3c, 0xbe, 0x0f, 0x06, 0x22, 0xc8, 0x95, 0x89, 0x0a, 0x56, 0xd6, 0x8a, 0x36, 0x42, 0xbe,
+    0xe6, 0x20, 0x00, 0x5c, 0xaa, 0xc6, 0x2e, 0xea, 0x19, 0x11, 0xfc, 0x07, 0x38, 0x08, 0x30, 0x7f,
+    0xb1, 0xe7, 0xfd, 0x71, 0x1c, 0x37, 0xc1, 0xd1, 0x4c, 0x3f, 0x16, 0x4c, 0x4d, 0x06, 0x69, 0xca,
+    0x3b, 0x8b, 0x79, 0xfd, 0x05, 0x0c, 0x8c, 0x60, 0xee, 0xaa, 0x91, 0x16, 0xf1, 0xab, 0x8a, 0x55,
+    0xf1, 0xb2, 0x85, 0xfc, 0xa7, 0xde, 0x6f, 0x32, 0xd3, 0x1f, 0xfd, 0xc2, 0x0d, 0xc7, 0x74, 0x4d,
+    0xcb, 0xae, 0x77, 0x96, 0xd3, 0xf3, 0x0a, 0xa0, 0x97, 0xd1, 0xf5, 0x54, 0xb8, 0xba, 0x45, 0x63,
+    0x1f, 0xe4, 0xef, 0x80, 0x63, 0xd4, 0xef, 0xef, 0x74, 0x56, 0x93, 0xf3, 0xfe, 0x60, 0x97, 0xcb,
+    0xf5, 0x55, 0x89, 0x0b, 0x92, 0x4e, 0xb5, 0x22, 0xe3, 0x0e, 0xc4, 0x5e, 0xc9, 0x0b, 0xc8, 0x52,
+    0xc1, 0x43, 0x0a, 0x6e, 0x6e, 0xee, 0xe9, 0xd2, 0x58, 0xe1, 0x08, 0xd5, 0x93, 0xdd, 0x96, 0x57,
+    0x8b, 0x06, 0x2f, 0xb4, 0x3b, 0x0a, 0x00, 0x5f, 0x9d, 0x55, 0x14, 0x57, 0xfe, 0xee, 0xbe, 0x19,
+    0x61, 0xa5, 0x63, 0x81, 0x6a, 0xf0, 0x1e, 0xf8, 0xdc, 0xa0, 0xf4, 0x98, 0xaa, 0x0b, 0x40, 0xfa,
+    0xea, 0xb1, 0xb7, 0xce, 0xb7, 0x80, 0x3b, 0x56, 0x09, 0xb6, 0xa5, 0x92, 0x84, 0x0b, 0x01, 0xca,
+    0x51, 0x86, 0xad, 0xd5, 0x1a, 0xc5, 0x9f, 0xba, 0x2b, 0xc5, 0x30, 0x66, 0xd2, 0xbc, 0xa5, 0xb1,
+    0xcd, 0x3a, 0x73, 0x29, 0x49, 0x47, 0x58, 0xe3, 0xb7, 0x55, 0xeb, 0x15, 0x7f, 0x0d, 0xf8, 0x12,
+    0x55, 0x02, 0x92, 0x3a, 0xe7, 0x60, 0xdf, 0xfa, 0x3a, 0x3a, 0x01, 0x35, 0xf0, 0x06, 0xe5, 0x85,
+    0xdd, 0x21, 0xef, 0xe7, 0x84, 0xda, 0x08, 0x22, 0xa2, 0xab, 0x83, 0x92, 0x25, 0x15, 0x8b, 0x00,
+    0x4f, 0x6a, 0xc9, 0x01, 0xa4, 0xc1, 0x62, 0xf6, 0x07, 0x24, 0x19, 0xbb, 0x5d, 0xe0, 0x36, 0x69,
+    0xf5, 0x6a, 0xef, 0x22, 0x73, 0xc7, 0xb6, 0xc2, 0x03, 0xc9, 0x53, 0x9f, 0x4d, 0xd5, 0x1e, 0x03,
+    0x00, 0xd9, 0xa4, 0x44, 0x44, 0x45, 0x15, 0x2e, 0xdb, 0x91, 0x09, 0xf0, 0xd8, 0x30, 0x0b, 0x08,
+    0x29, 0x55, 0x1e, 0xd2, 0x58, 0xa1, 0xed, 0xda, 0xe0, 0x64, 0xec, 0x83, 0x2b, 0x5b, 0x30, 0x7a,
+    0x90, 0xb0, 0x5f, 0x1a, 0x55, 0x38, 0xa6, 0x5d, 0x6f, 0xaa, 0x4e, 0x03, 0x6e, 0x00, 0x0b, 0xc6,
+    0x35, 0x5d, 0x37, 0x23, 0xea, 0xa9, 0x81, 0xa2, 0x52, 0x0e, 0x9c, 0xd5, 0x7e, 0xaa, 0x98, 0x05,
+    0x5c, 0x0c, 0xf3, 0x41, 0x07, 0x3e, 0x81, 0x6b, 0xf1, 0x7f, 0x50, 0xef, 0x9b, 0x8f, 0xc2, 0xf0,
+    0x97, 0xaf, 0xbf, 0x00, 0x52, 0x02, 0x57, 0x59, 0xfc, 0x75, 0x0c, 0x90, 0x1a, 0x47, 0x17, 0x0e,
+    0xf0, 0x0d, 0x3f, 0x6e, 0x97, 0x80, 0x99, 0x60, 0x37, 0x6a, 0xcc, 0xe9, 0xd9, 0x11, 0x09, 0xc4,
+    0x09, 0x60, 0x00, 0x25, 0x06, 0x2f, 0xb6, 0x8e, 0xaf, 0x68, 0xa9, 0x29, 0xab, 0xa9, 0xaa, 0xa7,
+    0x45, 0xcb, 0x0c, 0xe4, 0x85, 0xbc, 0x27, 0xd5, 0xa6, 0x00, 0x72, 0xa5, 0xc0, 0x72, 0xf1, 0xef,
+    0x71, 0x42, 0x0c, 0x1b, 0x02, 0xdb, 0x44, 0x40, 0x0b, 0xde, 0x15, 0x7c, 0x43, 0xdf, 0xe2, 0x52,
+    0x0b, 0x2c, 0x9f, 0xf5, 0x24, 0xab, 0xc4, 0x69, 0xaa, 0xa2, 0xe0, 0x39, 0xe0, 0x2e, 0x11, 0x2e,
+    0x11, 0x39, 0x0f, 0x38, 0x5e, 0x6f, 0x2a, 0x70, 0xa3, 0x5e, 0x9c, 0xdc, 0xe8, 0xd5, 0x63, 0x6b,
+    0x70, 0x05, 0xf0, 0x27, 0xbd, 0x70, 0x0d, 0x4b, 0xf5, 0x54, 0xe0, 0x79, 0x11, 0x4a, 0x16, 0xb9,
+    0x03, 0x2f, 0x58, 0xf5, 0x39, 0xbc, 0xa9, 0xc9, 0xff, 0xc5, 0x94, 0x6a, 0xa8, 0x51, 0x05, 0x0b,
+    0x82, 0xfe, 0xa0, 0xd7, 0xd5, 0xc4, 0xae, 0xb7, 0x45, 0xfc, 0x4a, 0x2f, 0x07, 0x72, 0x87, 0x70,
+    0x1c, 0x88, 0x97, 0x8d, 0x59, 0x30, 0x3d, 0xfa, 0x80, 0xd3, 0xd8, 0x09, 0xaa, 0x8f, 0xbc, 0x03,
+    0xfc, 0x0a, 0xaf, 0x00, 0x70, 0x16, 0xa4, 0xec, 0xaa, 0xa3, 0xe3, 0x76, 0xd2, 0x85, 0x11, 0xef,
+    0x91, 0x91, 0x1c, 0xc0, 0x67, 0xbc, 0x50, 0xf8, 0x67, 0xbc, 0x32, 0x78, 0xf5, 0xde, 0xed, 0x27,
+    0x67, 0x01, 0x12, 0xf0, 0x46, 0x7d, 0x49, 0xe3, 0x75, 0xfc, 0x3a, 0x54, 0xe2, 0xf0, 0xe6, 0x71,
+    0xbf, 0x55, 0x14, 0xaa, 0x28, 0xe0, 0xec, 0x65, 0x5e, 0x7f, 0x16, 0x44, 0x97, 0x02, 0x92, 0x13,
+    0x34, 0x9f, 0x4b, 0xc4, 0xd3, 0x78, 0xe5, 0x6b, 0x99, 0x29, 0xa3, 0xa4, 0xad, 0xd1, 0xe5, 0x39,
+    0xc9, 0x07, 0x00, 0x24, 0x65, 0x03, 0x6b, 0x27, 0xb7, 0xb1, 0x48, 0xbf, 0x56, 0x0e, 0xe6, 0xa3,
+    0xb9, 0x46, 0x42, 0x18, 0xe8, 0xfa, 0x07, 0x80, 0x87, 0xee, 0xc8, 0x88, 0xc0, 0x21, 0x89, 0x8f,
+    0x58, 0xe9, 0x08, 0xb4, 0x6b, 0xbc, 0x79, 0xac, 0x56, 0x5f, 0xa6, 0x4b, 0x89, 0x6f, 0x88, 0xe2,
+    0xc0, 0x69, 0x93, 0x51, 0x1f, 0x63, 0xda, 0xaa, 0xe7, 0x8b, 0xfe, 0xf4, 0xf6, 0x92, 0x34, 0xb5,
+    0x55, 0x0a, 0x41, 0x5d, 0x8c, 0x48, 0x2d, 0x22, 0x64, 0x44, 0xf0, 0x22, 0xc0, 0x77, 0x44, 0x67,
+    0xb4, 0xc4, 0x2b, 0x97, 0x85, 0xe0, 0x4b, 0xe1, 0x77, 0x0e, 0x83, 0x04, 0x74, 0xe8, 0x6d, 0x4c,
+    0xf0, 0xcb, 0x84, 0xe9, 0x60, 0x4b, 0x21, 0x6a, 0x73, 0xa6, 0x49, 0x1d, 0x2c, 0x22, 0x72, 0xae,
+    0xd7, 0xb8, 0xc5, 0xf8, 0x22, 0x00, 0x76, 0xf2, 0x85, 0x7c, 0x8e, 0x30, 0x92, 0x1f, 0x02, 0xe1,
+    0x12, 0x83, 0xc5, 0x80, 0xc1, 0xc4, 0x0d, 0x53, 0x5b, 0xd8, 0xe8, 0x95, 0x4d, 0x24, 0x81, 0xc0,
+    0x2a, 0x34, 0x24, 0x6a, 0x47, 0x7f, 0xf3, 0x8c, 0x15, 0x6d, 0xda, 0xf8, 0x1d, 0xe1, 0x6d, 0x1c,
+    0x00, 0xb8, 0x4c, 0x74, 0xfa, 0xd1, 0xaa, 0x35, 0xce, 0xba, 0x51, 0xa8, 0xc3, 0x87, 0x22, 0x10,
+    0x21, 0xf2, 0xbc, 0xbc, 0xa6, 0x8d, 0x6f, 0x6d, 0x74, 0x66, 0xb8, 0x64, 0xe6, 0x7f, 0xe3, 0x50,
+    0xef, 0x59, 0x02, 0x31, 0xeb, 0x0c, 0x69, 0x2a, 0x5a, 0xe3, 0x73, 0xe4, 0x8d, 0xfe, 0xcd, 0xab,
+    0x00, 0x4b, 0x46, 0x06, 0x0d, 0xbf, 0x57, 0xb2, 0xe2, 0x4d, 0x69, 0xb0, 0xda, 0x69, 0x22, 0x17,
+    0xde, 0x65, 0xdf, 0xe4, 0x3b, 0x98, 0x18, 0x6b, 0xda, 0xd3, 0x25, 0xbd, 0x36, 0x1c, 0xa5, 0x56,
+    0x8e, 0x2c, 0x60, 0x17, 0x38, 0x70, 0x3b, 0xf6, 0x64, 0x44, 0x5e, 0x34, 0xd8, 0xe3, 0x6d, 0x74,
+    0xd8, 0xcd, 0xe1, 0x16, 0x31, 0xc2, 0x5c, 0x2d, 0xb6, 0x34, 0xb7, 0x95, 0x6e, 0x8e, 0x2e, 0x31,
+    0x02, 0xac, 0x20, 0x9e, 0x05, 0x45, 0x86, 0x01, 0x10, 0x54, 0xe3, 0x91, 0xad, 0xf0, 0x9e, 0x3d,
+    0x96, 0xda, 0x7d, 0xe1, 0x25, 0x06, 0x45, 0x00, 0x48, 0xe4, 0xcd, 0xe0, 0xec, 0x06, 0x4e, 0x2d,
+    0x92, 0x74, 0xa1, 0x26, 0x5e, 0xe9, 0x26, 0x8a, 0x30, 0x21, 0x34, 0x47, 0x44, 0x67, 0xea, 0xd6,
+    0x59, 0xa4, 0x39, 0xb5, 0x87, 0x22, 0x3f, 0x63, 0x52, 0xe3, 0xda, 0xbc, 0x80, 0x01, 0x45, 0x17,
+    0xdd, 0x32, 0xcd, 0x89, 0x8c, 0x22, 0xc2, 0xc8, 0x16, 0xa8, 0x44, 0x61, 0x18, 0x56, 0x15, 0x99,
+    0x66, 0x46, 0x64, 0x61, 0x18, 0x56, 0x22, 0xd5, 0xce, 0x8c, 0xe7, 0x0a, 0xcc, 0x8e, 0xba, 0xf2,
+    0x2d, 0x91, 0xd7, 0xd6, 0x37, 0xc7, 0xbb, 0xe1, 0xc9, 0x3d, 0xf1, 0xbf, 0x93, 0x8f, 0x28, 0xdb,
+    0xf4, 0x28, 0x32, 0x0e, 0xa2, 0xb3, 0x35, 0x5f, 0x02, 0x4e, 0xda, 0xb0, 0xf7, 0xe8, 0xcb, 0x50,
+    0x9a, 0xf6, 0xe7, 0x1a, 0xe3, 0x80, 0x4f, 0x29, 0xb5, 0x04, 0xfd, 0x8a, 0x95, 0x8c, 0x09, 0xa4,
+    0x0e, 0x58, 0x13, 0x47, 0xa8, 0x13, 0x34, 0xec, 0xf6, 0x7f, 0x2b, 0x30, 0xf2, 0xe2, 0xca, 0xcd,
+    0x44, 0xff, 0x75, 0xeb, 0xfd, 0xa7, 0x8f, 0x6e, 0x6f, 0xf4, 0x6e, 0x7c, 0x0f, 0x75, 0x5b, 0x9f,
+    0x97, 0x5d, 0xc6, 0x38, 0xaa, 0xe3, 0x2d, 0x5a, 0x22, 0x8f, 0xcc, 0xb0, 0x82, 0xcc, 0xa6, 0xbd,
+    0x00, 0xe6, 0xcf, 0x2a, 0x50, 0xd0, 0x0e, 0xca, 0x4d, 0xb8, 0x27, 0xec, 0x86, 0x8c, 0xe4, 0xaa,
+    0xc1, 0xec, 0xd9, 0x7e, 0x87, 0x4d, 0x51, 0xa3, 0x1d, 0x94, 0x88, 0xf0, 0x30, 0xac, 0x20, 0xfb,
+    0xbe, 0x76, 0xf2, 0xbb, 0x1f, 0xca, 0x0e, 0x83, 0x13, 0x78, 0x6c, 0xf0, 0x97, 0x4a, 0x62, 0x53,
+    0xd0, 0x21, 0xa5, 0xc8, 0x5f, 0x3b, 0xe0, 0x1a, 0x17, 0xdf, 0x9b, 0x01, 0x68, 0x1b, 0x6a, 0xcf,
+    0xa6, 0x41, 0xe3, 0x46, 0x70, 0x1c, 0xfb, 0x3c, 0xb0, 0x1c, 0x3b, 0xe5, 0x7c, 0xf4, 0x21, 0x77,
+    0x62, 0x2c, 0x2a, 0x5d, 0xee, 0xd2, 0xec, 0x70, 0x88, 0x57, 0x11, 0xc8, 0x42, 0xf8, 0x67, 0x8e,
+    0xc8, 0x8c, 0xd4, 0x1e, 0xce, 0x0d, 0xdb, 0x03, 0xf4, 0x73, 0x43, 0x05, 0xbf, 0xad, 0xa1, 0x4c,
+    0x6e, 0xa4, 0x55, 0x00, 0x0b, 0x5d, 0x07, 0xbc, 0x19, 0x6f, 0x3d, 0x4f, 0x80, 0xe4, 0x53, 0x7f,
+    0xdf, 0xd5, 0xc2, 0x0f, 0xc5, 0xde, 0xfa, 0x78, 0xf5, 0xd6, 0xfb, 0xda, 0xaa, 0xba, 0xae, 0xfe,
+    0xf3, 0xce, 0x8a, 0xa4, 0x41, 0x0b, 0xe3, 0xc7, 0x01, 0x1b, 0x35, 0xdb, 0x83, 0xfc, 0x91, 0x4d,
+    0xf2, 0xe7, 0x76, 0x0f, 0xc0, 0xaf, 0x68, 0xe1, 0x81, 0x18, 0xb3, 0xf0, 0x02, 0x8d, 0xcd, 0x36,
+    0x01, 0x4a, 0x94, 0xe2, 0x7e, 0xaa, 0xc2, 0x91, 0x44, 0xa5, 0xd5, 0x50, 0x45, 0x87, 0xf5, 0xe4,
+    0x3b, 0xc0, 0xd9, 0x81, 0x1b, 0x24, 0x29, 0x98, 0xd4, 0x9e, 0x7f, 0xe7, 0xea, 0xa9, 0x83, 0xab,
+    0xf7, 0xb4, 0x88, 0x98, 0x87, 0x9c, 0xf6, 0xb7, 0x35, 0xf8, 0xd8, 0x35, 0x4b, 0xfb, 0xde, 0xd1,
+    0x81, 0xc6, 0x6e, 0x0e, 0x9c, 0x76, 0x8e, 0x03, 0x95, 0x27, 0x74, 0x10, 0xc2, 0x9f, 0x82, 0x8f,
+    0x4d, 0x75, 0x78, 0xf3, 0x91, 0xeb, 0x8e, 0x1d, 0x91, 0xfd, 0x80, 0x80, 0x39, 0xc9, 0xb1, 0x07,
+    0xbe, 0x03, 0x63, 0x65, 0x58, 0x5f, 0x74, 0x2e, 0x4d, 0x35, 0xe6, 0xb4, 0x55, 0x5c, 0x62, 0xe7,
+    0x3e, 0x71, 0x55, 0x5a, 0x03, 0xe7, 0x6d, 0xf3, 0x6b, 0xd9, 0x0e, 0x56, 0x88, 0xe6, 0x35, 0x50,
+    0x09, 0x52, 0xdf, 0x41, 0xad, 0xbb, 0xad, 0x9f, 0xf4, 0xc1, 0x07, 0xfa, 0x84, 0x0f, 0x9e, 0x5e,
+    0xa0, 0xe3, 0x3c, 0xa5, 0x6e, 0x02, 0x6f, 0x11, 0xc1, 0x35, 0xc8, 0xc1, 0xce, 0xe2, 0x5f, 0x3c,
+    0x13, 0x58, 0xac, 0xa9, 0xc5, 0xf9, 0xc3, 0xb4, 0x8e, 0xcf, 0x71, 0xf4, 0x38, 0x0e, 0xeb, 0x1e,
+    0xe8, 0x21, 0x39, 0xad, 0x9a, 0xe5, 0x36, 0x68, 0x9d, 0xe0, 0xac, 0x76, 0x1c, 0x3b, 0xa5, 0x8d,
+    0xfe, 0xb8, 0x84, 0xf7, 0xf6, 0xd2, 0xc3, 0x89, 0x0c, 0xb2, 0x1c, 0xcb, 0x9a, 0x40, 0x83, 0x98,
+    0x97, 0xe4, 0xfa, 0xdb, 0x77, 0x39, 0x17, 0x69, 0xc1, 0x9d, 0x4d, 0xaa, 0x54, 0x7f, 0xff, 0xd5,
+    0xe9, 0x4a, 0x1c, 0x92, 0x84, 0x4d, 0x41, 0x0b, 0xb3, 0xc7, 0x19, 0x6f, 0xa5, 0x92, 0xe3, 0x46,
+    0x15, 0x8a, 0x6c, 0xdd, 0x66, 0x08, 0xbc, 0x68, 0xa7, 0x80, 0xea, 0xf3, 0xec, 0x70, 0x9c, 0x8f,
+    0x6f, 0x89, 0x5f, 0x05, 0x42, 0xd2, 0xfb, 0xfb, 0x0b, 0x3b, 0xf3, 0x3b, 0xb2, 0xdc, 0x8f, 0xb1,
+    0x6e, 0x1f, 0xe8, 0x89, 0xef, 0xc2, 0xfe, 0x02, 0x85, 0x3e, 0x72, 0x4b, 0x21, 0x34, 0x01, 0xce,
+    0x63, 0xc7, 0x04, 0x6d, 0x33, 0xbc, 0x7c, 0xa7, 0x55, 0x7a, 0x0f, 0x0f, 0x45, 0x04, 0xf3, 0xa5,
+    0xe5, 0xc0, 0xfb, 0x2e, 0xb1, 0xd9, 0x73, 0x1f, 0x7f, 0x96, 0x40, 0xe3, 0xc0, 0x76, 0xf6, 0xaf,
+    0x2b, 0x2b, 0x0a, 0x7b, 0x7f, 0xde, 0x05, 0xbd, 0xbf, 0x7e, 0x17, 0x23, 0x25, 0x2e, 0x62, 0x5b,
+    0xf0, 0x27, 0x55, 0x4e, 0xc5, 0x7d, 0x25, 0xf2, 0x9a, 0x3a, 0xe7, 0x14, 0xe3, 0x80, 0xea, 0x31,
+    0x0b, 0x80, 0x8b, 0xc0, 0x05, 0x5d, 0xdd, 0x1e, 0xe3, 0x93, 0x6b, 0xcb, 0x79, 0x7b, 0x3f, 0xb5,
+    0x06, 0x52, 0x2b, 0xe6, 0x3a, 0xaa, 0xf1, 0xaf, 0x1d, 0x96, 0x6b, 0x0e, 0x52, 0x04, 0xd7, 0x7e,
+    0x69, 0xf7, 0x2b, 0x58, 0x69, 0xb6, 0xf5, 0x50, 0xbc, 0x7a, 0x85, 0x79, 0x42, 0x9c, 0x95, 0x5e,
+    0x60, 0x5c, 0x72, 0x1b, 0xe4, 0xaa, 0xb6, 0xf1, 0xe6, 0x77, 0xec, 0xb4, 0x5f, 0x6c, 0xd1, 0xe9,
+    0x28, 0xb0, 0x25, 0x40, 0xc2, 0x5e, 0xbe, 0x73, 0x06, 0xcb, 0xd2, 0x3c, 0xd0, 0x71, 0x75, 0x20,
+    0xb9, 0xaa, 0x90, 0x1d, 0xc8, 0x88, 0xe0, 0x4a, 0xf0, 0x48, 0x07, 0x8a, 0x1c, 0xcc, 0x41, 0x92,
+    0x27, 0xd9, 0xa9, 0xd0, 0x2f, 0x78, 0x64, 0x6d, 0x79, 0x5f, 0x06, 0xbf, 0xa1, 0xf6, 0xe3, 0xc1,
+    0xf7, 0xd6, 0xb8, 0x69, 0xac, 0x1e, 0xdf, 0x9f, 0x17, 0x00, 0xd1, 0xb9, 0x63, 0xaf, 0x46, 0x7f,
+    0x2e, 0x44, 0x7e, 0x01, 0xf6, 0x96, 0x34, 0x75, 0xdd, 0x01, 0xf7, 0xc0, 0x2f, 0x80, 0x15, 0x14,
+    0x1c, 0xe4, 0x32, 0x8e, 0x01, 0xce, 0x20, 0xc7, 0x5f, 0x22, 0x3e, 0x02, 0xd9, 0x6f, 0x01, 0x6c,
+    0x18, 0x4f, 0x5a, 0xfe, 0x6d, 0x55, 0xa1, 0xce, 0xc3, 0x82, 0x48, 0x31, 0xe4, 0x44, 0xe0, 0x28,
+    0x3c, 0x19, 0xc0, 0x39, 0x91, 0x83, 0xc0, 0xe5, 0x81, 0x77, 0xa8, 0x1f, 0xf6, 0xea, 0x2f, 0xb4,
+    0xd1, 0xc6, 0x38, 0x9c, 0x1c, 0x07, 0x88, 0x83, 0x07, 0x4c, 0xc6, 0x9e, 0xe3, 0x5e, 0x08, 0xb1,
+    0x98, 0xf8, 0x3e, 0xaa, 0xc9, 0x6e, 0xc3, 0x9e, 0x1c, 0x61, 0x99, 0x11, 0xdc, 0x01, 0x1c, 0x07,
+    0xf6, 0x97, 0x18, 0xb5, 0x13, 0xa2, 0xe9, 0x58, 0xef, 0xfb, 0xe2, 0xe5, 0x97, 0xff, 0xea, 0xa9,
+    0x34, 0xca, 0xcc, 0x2f, 0x7f, 0xb1, 0x1c, 0xe0, 0x05, 0x70, 0x64, 0x44, 0xf0, 0x0c, 0xe0, 0x19,
+    0xc0, 0x63, 0x2a, 0x10, 0x9c, 0x44, 0xdc, 0x6b, 0xaa, 0x57, 0xbc, 0xef, 0x78, 0x00, 0x39, 0xc4,
+    0xc7, 0x5e, 0x8a, 0x81, 0x8c, 0x88, 0xde, 0x00, 0x8c, 0x06, 0xbe, 0xc3, 0xc9, 0xae, 0x51, 0x60,
+    0xd3, 0x8a, 0x78, 0xc0, 0x0e, 0x25, 0xff, 0xfd, 0xdb, 0x8f, 0x09, 0xd7, 0x52, 0xac, 0x4d, 0xd5,
+    0x5c, 0xa9, 0xe3, 0xad, 0xc1, 0x80, 0x60, 0x39, 0xc3, 0x87, 0x70, 0xe1, 0x52, 0x38, 0x15, 0xc0,
+    0x70, 0xe2, 0x1c, 0x38, 0x87, 0x0e, 0x05, 0x23, 0x03, 0x9c, 0x38, 0x76, 0x0e, 0x1c, 0x23, 0x87,
+    0x9c, 0x05, 0x76, 0x03, 0x1c, 0x38, 0x87, 0x0e, 0x05, 0x23, 0x03, 0x9c, 0x38, 0x76, 0x0e, 0x1c,
+    0x23, 0x87, 0x9c, 0x05, 0x76, 0x03, 0x1c, 0x38, 0x87, 0x0e, 0x05, 0x23, 0x03, 0x9c, 0x38, 0x76,
+    0x0e, 0x1c, 0x23, 0x87, 0x9c, 0x05, 0x76, 0x03, 0x1c, 0x38, 0x87, 0x0e, 0x05, 0x23, 0x03, 0x9c,
+    0x38, 0x76, 0x0e, 0x1c, 0x23, 0x87, 0x9c, 0x05, 0x76, 0x03, 0x1c, 0x38, 0x87, 0x0e, 0x05, 0x23,
+    0x03, 0x9c, 0x38, 0x76, 0x0e, 0x1c, 0x23, 0x87, 0x9c, 0x05, 0x76, 0x03, 0x1c, 0x38, 0x87, 0x0e,
+    0x05, 0x23, 0x03, 0x9c, 0x38, 0x76, 0x0e, 0x1c, 0x23, 0x87, 0x9c, 0x05, 0x76, 0x03, 0x1c, 0x38,
+    0x87, 0x0e, 0x05, 0x23, 0x0f, 0xfe, 0xf6, 0xa7, 0xbf, 0x78, 0xfb, 0x2d, 0xfb, 0xc5, 0x00, 0x00,
+    0x00, 0x00, 0x45, 0x49, 0x44, 0x4e, 0x42, 0xae, 0x82, 0x60,
+  ],
+  disp: function()
+  {
+    // Do Nothing
+
+    throw "Does Nothing!";
+  }
+
+};
+
+try
+{
+  LOGO.disp();
+}
+catch(e)
+{
+  alert("Error: " + e + "\n");
+}
diff --git a/jetty-spdy/spdy-http-server/src/test/resources/jetty-logging.properties b/jetty-spdy/spdy-http-server/src/test/resources/jetty-logging.properties
new file mode 100644
index 0000000..be1b7aa
--- /dev/null
+++ b/jetty-spdy/spdy-http-server/src/test/resources/jetty-logging.properties
@@ -0,0 +1,10 @@
+org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
+#org.eclipse.jetty.spdy.LEVEL=DEBUG
+#org.eclipse.jetty.server.LEVEL=DEBUG
+#org.eclipse.jetty.io.ssl.LEVEL=DEBUG
+#org.eclipse.jetty.spdy.LEVEL=DEBUG
+#org.eclipse.jetty.client.LEVEL=DEBUG
+#org.eclipse.jetty.spdy.server.http.ReferrerPushStrategy.LEVEL=DEBUG
+#org.eclipse.jetty.spdy.server.proxy.LEVEL=DEBUG
+#org.mortbay.LEVEL=DEBUG
+
diff --git a/jetty-spdy/spdy-jetty-http-webapp/src/main/resources/keystore.jks b/jetty-spdy/spdy-http-server/src/test/resources/keystore.jks
similarity index 100%
copy from jetty-spdy/spdy-jetty-http-webapp/src/main/resources/keystore.jks
copy to jetty-spdy/spdy-http-server/src/test/resources/keystore.jks
diff --git a/jetty-spdy/spdy-jetty-http-webapp/src/main/resources/truststore.jks b/jetty-spdy/spdy-http-server/src/test/resources/truststore.jks
similarity index 100%
copy from jetty-spdy/spdy-jetty-http-webapp/src/main/resources/truststore.jks
copy to jetty-spdy/spdy-http-server/src/test/resources/truststore.jks
diff --git a/jetty-spdy/spdy-jetty-http-webapp/pom.xml b/jetty-spdy/spdy-jetty-http-webapp/pom.xml
deleted file mode 100644
index fc46ad9..0000000
--- a/jetty-spdy/spdy-jetty-http-webapp/pom.xml
+++ /dev/null
@@ -1,104 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<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/xsd/maven-4.0.0.xsd">
-    <parent>
-        <groupId>org.eclipse.jetty.spdy</groupId>
-        <artifactId>spdy-parent</artifactId>
-        <version>8.1.17.v20150415</version>
-    </parent>
-    <modelVersion>4.0.0</modelVersion>
-    <artifactId>spdy-jetty-http-webapp</artifactId>
-    <packaging>war</packaging>
-    <name>Jetty :: SPDY :: Jetty HTTP Web Application</name>
-    <url>http://www.eclipse.org/jetty</url>
-    <build>
-        <plugins>
-            <plugin>
-                <groupId>org.apache.maven.plugins</groupId>
-                <artifactId>maven-assembly-plugin</artifactId>
-                <executions>
-                    <execution>
-                        <phase>package</phase>
-                        <goals>
-                            <goal>single</goal>
-                        </goals>
-                        <configuration>
-                            <descriptorRefs>
-                                <descriptorRef>config</descriptorRef>
-                            </descriptorRefs>
-                        </configuration>
-                    </execution>
-                </executions>
-            </plugin>
-<!--
-            <plugin>
-                <groupId>org.mortbay.jetty</groupId>
-                <artifactId>jetty-maven-plugin</artifactId>
-                <version>${project.version}</version>
-                <configuration>
-                    <stopPort>8888</stopPort>
-                    <stopKey>quit</stopKey>
-                    <jvmArgs>
-                        -Dlog4j.configuration=file://${basedir}/src/main/resources/log4j.properties
-                        -Xbootclasspath/p:${settings.localRepository}/org/mortbay/jetty/npn/npn-boot/${npn.version}/npn-boot-${npn.version}.jar
-                    </jvmArgs>
-                    <jettyXml>${basedir}/src/main/config/etc/jetty-spdy.xml</jettyXml>
-                    <contextPath>/</contextPath>
-                </configuration>
-                <dependencies>
-                    <dependency>
-                        <groupId>org.eclipse.jetty.spdy</groupId>
-                        <artifactId>spdy-jetty-http</artifactId>
-                        <version>${project.version}</version>
-                    </dependency>
-                    <dependency>
-                        <groupId>org.slf4j</groupId>
-                        <artifactId>slf4j-log4j12</artifactId>
-                        <version>${slf4j-version}</version>
-                    </dependency>
-                </dependencies>
-            </plugin>
--->
-        </plugins>
-    </build>
-
-<!--
-    <profiles>
-        <profile>
-            <id>proxy</id>
-            <build>
-                <plugins>
-                    <plugin>
-                        <groupId>org.mortbay.jetty</groupId>
-                        <artifactId>jetty-maven-plugin</artifactId>
-                        <version>${project.version}</version>
-                        <configuration>
-                            <stopPort>8888</stopPort>
-                            <stopKey>quit</stopKey>
-                            <jvmArgs>
-                                -Dlog4j.configuration=file://${basedir}/src/main/resources/log4j.properties
-                                -Xbootclasspath/p:${settings.localRepository}/org/mortbay/jetty/npn/npn-boot/${npn.version}/npn-boot-${npn.version}.jar
-                                -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005
-                            </jvmArgs>
-                            <jettyXml>${basedir}/src/main/config/etc/jetty-spdy-proxy.xml</jettyXml>
-                            <contextPath>/</contextPath>
-                        </configuration>
-                        <dependencies>
-                            <dependency>
-                                <groupId>org.eclipse.jetty.spdy</groupId>
-                                <artifactId>spdy-jetty-http</artifactId>
-                                <version>${project.version}</version>
-                            </dependency>
-                            <dependency>
-                                <groupId>org.slf4j</groupId>
-                                <artifactId>slf4j-log4j12</artifactId>
-                                <version>${slf4j-version}</version>
-                            </dependency>
-                        </dependencies>
-                    </plugin>
-                </plugins>
-            </build>
-        </profile>
-    </profiles>
--->
-
-</project>
diff --git a/jetty-spdy/spdy-jetty-http-webapp/src/main/config/etc/jetty-spdy-proxy.xml b/jetty-spdy/spdy-jetty-http-webapp/src/main/config/etc/jetty-spdy-proxy.xml
deleted file mode 100644
index 9c637ec..0000000
--- a/jetty-spdy/spdy-jetty-http-webapp/src/main/config/etc/jetty-spdy-proxy.xml
+++ /dev/null
@@ -1,98 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
-
-<Configure id="Server" class="org.eclipse.jetty.server.Server">
-
-    <New id="sslContextFactory" class="org.eclipse.jetty.util.ssl.SslContextFactory">
-        <Set name="keyStorePath">src/main/resources/keystore.jks</Set>
-        <Set name="keyStorePassword">storepwd</Set>
-        <Set name="trustStore">src/main/resources/truststore.jks</Set>
-        <Set name="trustStorePassword">storepwd</Set>
-        <Set name="protocol">TLSv1</Set>
-    </New>
-
-    <!--
-    <Set class="org.eclipse.jetty.npn.NextProtoNego" name="debug" type="boolean">true</Set>
-    -->
-
-    <!--
-    This is the upstream server connector. It speaks non-SSL SPDY/2(HTTP) on port 9090.
-    -->
-    <Call name="addConnector">
-        <Arg>
-            <New class="org.eclipse.jetty.spdy.http.HTTPSPDYServerConnector">
-                <Set name="Port">9090</Set>
-                <Set name="defaultAsyncConnectionFactory">
-                    <Call name="getAsyncConnectionFactory">
-                        <Arg>spdy/2</Arg>
-                    </Call>
-                </Set>
-            </New>
-        </Arg>
-    </Call>
-
-    <!--
-    This ProxyEngine translates the incoming SPDY/x(HTTP) request to SPDY/2(HTTP)
-    -->
-    <New id="spdyProxyEngine" class="org.eclipse.jetty.spdy.proxy.SPDYProxyEngine">
-        <Arg>
-            <New class="org.eclipse.jetty.spdy.SPDYClient$Factory">
-                <Call name="start"/>
-            </New>
-        </Arg>
-    </New>
-
-    <!--
-    The ProxyEngineSelector receives SPDY/x(HTTP) requests from proxy connectors below
-    and is configured to process requests for host "localhost".
-    Such requests are converted from SPDY/x(HTTP) to SPDY/2(HTTP) by the configured ProxyEngine
-    and forwarded to 127.0.0.1:9090, where they are served by the upstream server above.
-    -->
-    <New id="proxyEngineSelector" class="org.eclipse.jetty.spdy.proxy.ProxyEngineSelector">
-        <Call name="putProxyEngine">
-            <Arg>spdy/2</Arg>
-            <Arg><Ref id="spdyProxyEngine" /></Arg>
-        </Call>
-        <Set name="proxyServerInfos">
-            <Map>
-                <Entry>
-                    <Item>localhost</Item>
-                    <Item>
-                        <New class="org.eclipse.jetty.spdy.proxy.ProxyEngineSelector$ProxyServerInfo">
-                            <Arg type="String">spdy/2</Arg>
-                            <Arg>127.0.0.1</Arg>
-                            <Arg type="int">9090</Arg>
-                        </New>
-                    </Item>
-                </Entry>
-            </Map>
-        </Set>
-    </New>
-
-    <!--
-    These are the reverse proxy connectors accepting requests from clients.
-    They accept non-SSL (on port 8080) and SSL (on port 8443) HTTP,
-    SPDY/2(HTTP) and SPDY/3(HTTP).
-    Non-SPDY HTTP requests are converted to SPDY internally and passed to the
-    ProxyEngine above.
-    -->
-    <Call name="addConnector">
-        <Arg>
-            <New class="org.eclipse.jetty.spdy.proxy.HTTPSPDYProxyConnector">
-                <Arg><Ref id="proxyEngineSelector" /></Arg>
-                <Set name="Port">8080</Set>
-            </New>
-        </Arg>
-    </Call>
-    <Call name="addConnector">
-        <Arg>
-            <New class="org.eclipse.jetty.spdy.proxy.HTTPSPDYProxyConnector">
-                <Arg><Ref id="proxyEngineSelector" /></Arg>
-                <Arg><Ref id="sslContextFactory" /></Arg>
-                <Set name="Port">8443</Set>
-            </New>
-        </Arg>
-    </Call>
-
-
-</Configure>
diff --git a/jetty-spdy/spdy-jetty-http-webapp/src/main/config/etc/jetty-spdy.xml b/jetty-spdy/spdy-jetty-http-webapp/src/main/config/etc/jetty-spdy.xml
deleted file mode 100644
index 0d847bc..0000000
--- a/jetty-spdy/spdy-jetty-http-webapp/src/main/config/etc/jetty-spdy.xml
+++ /dev/null
@@ -1,86 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
-
-<Configure id="Server" class="org.eclipse.jetty.server.Server">
-
-    <New id="sslContextFactory" class="org.eclipse.jetty.util.ssl.SslContextFactory">
-        <Set name="keyStorePath">src/main/resources/keystore.jks</Set>
-        <Set name="keyStorePassword">storepwd</Set>
-        <Set name="trustStore">src/main/resources/truststore.jks</Set>
-        <Set name="trustStorePassword">storepwd</Set>
-        <Set name="protocol">TLSv1</Set>
-    </New>
-
-    <!-- Uncomment to create a ReferrerPushStrategy that can be added to the Connectors -->
-
-    <!--
-    <New id="pushStrategy" class="org.eclipse.jetty.spdy.http.ReferrerPushStrategy">
-        <Arg type="List">
-            <Array type="String">
-                <Item>.*\.css</Item>
-                <Item>.*\.js</Item>
-                <Item>.*\.png</Item>
-                <Item>.*\.jpg</Item>
-                <Item>.*\.gif</Item>
-            </Array>
-        </Arg>
-    </New>
-    -->
-
-    <!--<Set class="org.eclipse.jetty.npn.NextProtoNego" name="debug" type="boolean">true</Set>-->
-
-    <Call name="addConnector">
-        <Arg>
-            <New class="org.eclipse.jetty.spdy.http.HTTPSPDYServerConnector">
-                <!-- uncomment to enable to apply ReferrerPushStrategy for spdy/3
-                     if you want to support it in both spdy/2 and spdy/3, just replace the
-                     value in the first map entry.
-                -->
-                <!--
-                <Arg name="pushStrategies">
-                    <Map>
-                        <Entry>
-                            <Item type="short">2</Item>
-                            <Item><New class="org.eclipse.jetty.spdy.http.PushStrategy$None" /></Item>
-                        </Entry>
-                        <Entry>
-                            <Item type="short">3</Item>
-                            <Item><Ref id="pushStrategy" /></Item>
-                        </Entry>
-                    </Map>
-                </Arg>
-                -->
-                <Set name="Port">8080</Set>
-            </New>
-        </Arg>
-    </Call>
-    <Call name="addConnector">
-        <Arg>
-            <New class="org.eclipse.jetty.spdy.http.HTTPSPDYServerConnector">
-                <Arg>
-                    <Ref id="sslContextFactory" />
-                </Arg>
-                <!-- uncomment to enable to apply ReferrerPushStrategy for spdy/3
-                     if you want to support it in both spdy/2 and spdy/3, just replace the
-                     value in the first map entry.
-                -->
-                <!--
-                <Arg name="pushStrategies">
-                    <Map>
-                        <Entry>
-                            <Item type="short">2</Item>
-                            <Item><New class="org.eclipse.jetty.spdy.http.PushStrategy$None" /></Item>
-                        </Entry>
-                        <Entry>
-                            <Item type="short">3</Item>
-                            <Item><Ref id="pushStrategy" /></Item>
-                        </Entry>
-                    </Map>
-                </Arg>
-                -->
-                <Set name="Port">8443</Set>
-            </New>
-        </Arg>
-    </Call>
-
-</Configure>
diff --git a/jetty-spdy/spdy-jetty-http-webapp/src/main/resources/log4j.properties b/jetty-spdy/spdy-jetty-http-webapp/src/main/resources/log4j.properties
deleted file mode 100644
index d15b6be..0000000
--- a/jetty-spdy/spdy-jetty-http-webapp/src/main/resources/log4j.properties
+++ /dev/null
@@ -1,16 +0,0 @@
-# LOG4J levels: OFF, FATAL, ERROR, WARN, INFO, DEBUG, ALL
-#
-log4j.rootLogger=ALL,CONSOLE
-
-log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
-#log4j.appender.CONSOLE.threshold=INFO
-log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
-#log4j.appender.CONSOLE.layout.ConversionPattern=%d %t [%5p][%c{1}] %m%n
-log4j.appender.CONSOLE.layout.ConversionPattern=%d %t [%5p][%c] %m%n
-log4j.appender.CONSOLE.target=System.err
-
-# Level tuning
-log4j.logger.jndi=INFO
-log4j.logger.org.mortbay.jetty=INFO
-log4j.logger.org.eclipse.jetty=INFO
-log4j.logger.org.eclipse.jetty.spdy=DEBUG
diff --git a/jetty-spdy/spdy-jetty-http-webapp/src/main/webapp/WEB-INF/web.xml b/jetty-spdy/spdy-jetty-http-webapp/src/main/webapp/WEB-INF/web.xml
deleted file mode 100644
index e1f0eae..0000000
--- a/jetty-spdy/spdy-jetty-http-webapp/src/main/webapp/WEB-INF/web.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<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">
-</web-app>
diff --git a/jetty-spdy/spdy-jetty-http/pom.xml b/jetty-spdy/spdy-jetty-http/pom.xml
deleted file mode 100644
index 62a9d5d..0000000
--- a/jetty-spdy/spdy-jetty-http/pom.xml
+++ /dev/null
@@ -1,82 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<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/xsd/maven-4.0.0.xsd">
-    <parent>
-        <groupId>org.eclipse.jetty.spdy</groupId>
-        <artifactId>spdy-parent</artifactId>
-        <version>8.1.17.v20150415</version>
-    </parent>
-    <modelVersion>4.0.0</modelVersion>
-    <artifactId>spdy-jetty-http</artifactId>
-    <name>Jetty :: SPDY :: Jetty HTTP Layer</name>
-    <url>http://www.eclipse.org/jetty</url>
-    <build>
-        <plugins>
-            <plugin>
-                <artifactId>maven-dependency-plugin</artifactId>
-                <executions>
-                    <execution>
-                        <id>copy</id>
-                        <phase>generate-resources</phase>
-                        <goals>
-                            <goal>copy</goal>
-                        </goals>
-                        <configuration>
-                            <artifactItems>
-                                <artifactItem>
-                                    <groupId>org.mortbay.jetty.npn</groupId>
-                                    <artifactId>npn-boot</artifactId>
-                                    <version>${npn.version}</version>
-                                    <type>jar</type>
-                                    <overWrite>false</overWrite>
-                                    <outputDirectory>${project.build.directory}/npn</outputDirectory>
-                                </artifactItem>
-                            </artifactItems>
-                        </configuration>
-                    </execution>
-                </executions>
-            </plugin>
-            <plugin>
-                <artifactId>maven-surefire-plugin</artifactId>
-                <configuration>
-                    <argLine>-Xbootclasspath/p:${project.build.directory}/npn/npn-boot-${npn.version}.jar</argLine>
-                </configuration>
-            </plugin>
-        </plugins>
-    </build>
-
-    <dependencies>
-        <dependency>
-            <groupId>org.eclipse.jetty.spdy</groupId>
-            <artifactId>spdy-jetty</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-        <dependency>
-            <groupId>junit</groupId>
-            <artifactId>junit</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.eclipse.jetty.npn</groupId>
-            <artifactId>npn-api</artifactId>
-            <version>${npn.api.version}</version>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.eclipse.jetty</groupId>
-            <artifactId>jetty-client</artifactId>
-            <version>${project.version}</version>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.slf4j</groupId>
-            <artifactId>slf4j-log4j12</artifactId>
-            <version>${slf4j-version}</version>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.mockito</groupId>
-            <artifactId>mockito-core</artifactId>
-            <scope>test</scope>
-        </dependency>
-    </dependencies>
-
-</project>
diff --git a/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/AbstractHTTPSPDYServerConnector.java b/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/AbstractHTTPSPDYServerConnector.java
deleted file mode 100644
index fbe01d1..0000000
--- a/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/AbstractHTTPSPDYServerConnector.java
+++ /dev/null
@@ -1,66 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.spdy.http;
-
-import java.io.IOException;
-
-import org.eclipse.jetty.http.HttpSchemes;
-import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.server.Request;
-import org.eclipse.jetty.spdy.SPDYServerConnector;
-import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener;
-import org.eclipse.jetty.util.ssl.SslContextFactory;
-
-public class AbstractHTTPSPDYServerConnector extends SPDYServerConnector
-{
-    public AbstractHTTPSPDYServerConnector(ServerSessionFrameListener listener, SslContextFactory sslContextFactory)
-    {
-        super(listener, sslContextFactory);
-    }
-
-    @Override
-    public void customize(EndPoint endPoint, Request request) throws IOException
-    {
-        super.customize(endPoint, request);
-        if (getSslContextFactory() != null)
-            request.setScheme(HttpSchemes.HTTPS);
-    }
-
-    @Override
-    public boolean isConfidential(Request request)
-    {
-        if (getSslContextFactory() != null)
-        {
-            int confidentialPort = getConfidentialPort();
-            return confidentialPort == 0 || confidentialPort == request.getServerPort();
-        }
-        return super.isConfidential(request);
-    }
-
-    @Override
-    public boolean isIntegral(Request request)
-    {
-        if (getSslContextFactory() != null)
-        {
-            int integralPort = getIntegralPort();
-            return integralPort == 0 || integralPort == request.getServerPort();
-        }
-        return super.isIntegral(request);
-    }
-}
diff --git a/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/HTTPSPDYHeader.java b/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/HTTPSPDYHeader.java
deleted file mode 100644
index 70e6509..0000000
--- a/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/HTTPSPDYHeader.java
+++ /dev/null
@@ -1,78 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-
-package org.eclipse.jetty.spdy.http;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import org.eclipse.jetty.spdy.api.SPDY;
-
-public enum HTTPSPDYHeader
-{
-    METHOD("method", ":method"),
-    URI("url", ":path"),
-    VERSION("version", ":version"),
-    SCHEME("scheme", ":scheme"),
-    HOST("host", ":host"),
-    STATUS("status", ":status");
-
-    public static HTTPSPDYHeader from(short version, String name)
-    {
-        switch (version)
-        {
-            case SPDY.V2:
-                return Names.v2Names.get(name);
-            case SPDY.V3:
-                return Names.v3Names.get(name);
-            default:
-                throw new IllegalStateException();
-        }
-    }
-
-    private final String v2Name;
-    private final String v3Name;
-
-    private HTTPSPDYHeader(String v2Name, String v3Name)
-    {
-        this.v2Name = v2Name;
-        Names.v2Names.put(v2Name, this);
-        this.v3Name = v3Name;
-        Names.v3Names.put(v3Name, this);
-    }
-
-    public String name(short version)
-    {
-        switch (version)
-        {
-            case SPDY.V2:
-                return v2Name;
-            case SPDY.V3:
-                return v3Name;
-            default:
-                throw new IllegalStateException();
-        }
-    }
-
-    private static class Names
-    {
-        private static final Map<String, HTTPSPDYHeader> v2Names = new HashMap<>();
-        private static final Map<String, HTTPSPDYHeader> v3Names = new HashMap<>();
-    }
-}
diff --git a/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/HTTPSPDYServerConnector.java b/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/HTTPSPDYServerConnector.java
deleted file mode 100644
index 0d0e7b2..0000000
--- a/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/HTTPSPDYServerConnector.java
+++ /dev/null
@@ -1,69 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-
-package org.eclipse.jetty.spdy.http;
-
-import java.util.Collections;
-import java.util.Map;
-
-import org.eclipse.jetty.spdy.api.SPDY;
-import org.eclipse.jetty.util.ssl.SslContextFactory;
-
-public class HTTPSPDYServerConnector extends AbstractHTTPSPDYServerConnector
-{
-    public HTTPSPDYServerConnector()
-    {
-        this(null, Collections.<Short, PushStrategy>emptyMap());
-    }
-
-    public HTTPSPDYServerConnector(Map<Short, PushStrategy> pushStrategies)
-    {
-        this(null, pushStrategies);
-    }
-
-    public HTTPSPDYServerConnector(SslContextFactory sslContextFactory)
-    {
-        this(sslContextFactory, Collections.<Short, PushStrategy>emptyMap());
-    }
-
-    public HTTPSPDYServerConnector(SslContextFactory sslContextFactory, Map<Short, PushStrategy> pushStrategies)
-    {
-        // We pass a null ServerSessionFrameListener because for
-        // HTTP over SPDY we need one that references the endPoint
-        super(null, sslContextFactory);
-        clearAsyncConnectionFactories();
-        // The "spdy/3" protocol handles HTTP over SPDY
-        putAsyncConnectionFactory("spdy/3", new ServerHTTPSPDYAsyncConnectionFactory(SPDY.V3, getByteBufferPool(), getExecutor(), getScheduler(), this, getPushStrategy(SPDY.V3,pushStrategies)));
-        // The "spdy/2" protocol handles HTTP over SPDY
-        putAsyncConnectionFactory("spdy/2", new ServerHTTPSPDYAsyncConnectionFactory(SPDY.V2, getByteBufferPool(), getExecutor(), getScheduler(), this, getPushStrategy(SPDY.V2,pushStrategies)));
-        // The "http/1.1" protocol handles browsers that support NPN but not SPDY
-        putAsyncConnectionFactory("http/1.1", new ServerHTTPAsyncConnectionFactory(this));
-        // The default connection factory handles plain HTTP on non-SSL or non-NPN connections
-        setDefaultAsyncConnectionFactory(getAsyncConnectionFactory("http/1.1"));
-    }
-
-    private PushStrategy getPushStrategy(short version, Map<Short, PushStrategy> pushStrategies)
-    {
-        PushStrategy pushStrategy = pushStrategies.get(version);
-        if(pushStrategy == null)
-            pushStrategy = new PushStrategy.None();
-        return pushStrategy;
-    }
-
-}
diff --git a/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/PushStrategy.java b/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/PushStrategy.java
deleted file mode 100644
index fae27e9..0000000
--- a/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/PushStrategy.java
+++ /dev/null
@@ -1,43 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-
-package org.eclipse.jetty.spdy.http;
-
-import java.util.Collections;
-import java.util.Set;
-
-import org.eclipse.jetty.spdy.api.Headers;
-import org.eclipse.jetty.spdy.api.Stream;
-
-/**
- *
- */
-public interface PushStrategy
-{
-    public Set<String> apply(Stream stream, Headers requestHeaders, Headers responseHeaders);
-
-    public static class None implements PushStrategy
-    {
-        @Override
-        public Set<String> apply(Stream stream, Headers requestHeaders, Headers responseHeaders)
-        {
-            return Collections.emptySet();
-        }
-    }
-}
diff --git a/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/ReferrerPushStrategy.java b/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/ReferrerPushStrategy.java
deleted file mode 100644
index e7cd871..0000000
--- a/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/ReferrerPushStrategy.java
+++ /dev/null
@@ -1,285 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-
-package org.eclipse.jetty.spdy.http;
-
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Locale;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicLong;
-import java.util.regex.Pattern;
-
-import org.eclipse.jetty.spdy.api.Headers;
-import org.eclipse.jetty.spdy.api.Stream;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-
-/**
- * <p>A SPDY push strategy that auto-populates push metadata based on referrer URLs.</p>
- * <p>A typical request for a main resource such as <tt>index.html</tt> is immediately
- * followed by a number of requests for associated resources. Associated resource requests
- * will have a <tt>Referer</tt> HTTP header that points to <tt>index.html</tt>, which we
- * use to link the associated resource to the main resource.</p>
- * <p>However, also following a hyperlink generates a HTTP request with a <tt>Referer</tt>
- * HTTP header that points to <tt>index.html</tt>; therefore a proper value for {@link #getReferrerPushPeriod()}
- * has to be set. If the referrerPushPeriod for a main resource has been passed, no more
- * associated resources will be added for that main resource.</p>
- * <p>This class distinguishes associated main resources by their URL path suffix and content
- * type.
- * CSS stylesheets, images and JavaScript files have recognizable URL path suffixes that
- * are classified as associated resources. The suffix regexs can be configured by constructor argument</p>
- * <p>When CSS stylesheets refer to images, the CSS image request will have the CSS
- * stylesheet as referrer. This implementation will push also the CSS image.</p>
- * <p>The push metadata built by this implementation is limited by the number of pages
- * of the application itself, and by the
- * {@link #getMaxAssociatedResources() max associated resources} parameter.
- * This parameter limits the number of associated resources per each main resource, so
- * that if a main resource has hundreds of associated resources, only up to the number
- * specified by this parameter will be pushed.</p>
- */
-public class ReferrerPushStrategy implements PushStrategy
-{
-    private static final Logger logger = Log.getLogger(ReferrerPushStrategy.class);
-    private final ConcurrentMap<String, MainResource> mainResources = new ConcurrentHashMap<>();
-    private final Set<Pattern> pushRegexps = new HashSet<>();
-    private final Set<String> pushContentTypes = new HashSet<>();
-    private final Set<Pattern> allowedPushOrigins = new HashSet<>();
-    private volatile int maxAssociatedResources = 32;
-    private volatile int referrerPushPeriod = 5000;
-
-    public ReferrerPushStrategy()
-    {
-        this(Arrays.asList(".*\\.css", ".*\\.js", ".*\\.png", ".*\\.jpeg", ".*\\.jpg", ".*\\.gif", ".*\\.ico"));
-    }
-
-    public ReferrerPushStrategy(List<String> pushRegexps)
-    {
-        this(pushRegexps, Arrays.asList(
-                "text/css",
-                "text/javascript", "application/javascript", "application/x-javascript",
-                "image/png", "image/x-png",
-                "image/jpeg",
-                "image/gif",
-                "image/x-icon", "image/vnd.microsoft.icon"));
-    }
-
-    public ReferrerPushStrategy(List<String> pushRegexps, List<String> pushContentTypes)
-    {
-        this(pushRegexps, pushContentTypes, Collections.<String>emptyList());
-    }
-
-    public ReferrerPushStrategy(List<String> pushRegexps, List<String> pushContentTypes, List<String> allowedPushOrigins)
-    {
-        for (String pushRegexp : pushRegexps)
-            this.pushRegexps.add(Pattern.compile(pushRegexp));
-        this.pushContentTypes.addAll(pushContentTypes);
-        for (String allowedPushOrigin : allowedPushOrigins)
-            this.allowedPushOrigins.add(Pattern.compile(allowedPushOrigin.replace(".", "\\.").replace("*", ".*")));
-    }
-
-    public int getMaxAssociatedResources()
-    {
-        return maxAssociatedResources;
-    }
-
-    public void setMaxAssociatedResources(int maxAssociatedResources)
-    {
-        this.maxAssociatedResources = maxAssociatedResources;
-    }
-
-    public int getReferrerPushPeriod()
-    {
-        return referrerPushPeriod;
-    }
-
-    public void setReferrerPushPeriod(int referrerPushPeriod)
-    {
-        this.referrerPushPeriod = referrerPushPeriod;
-    }
-
-    @Override
-    public Set<String> apply(Stream stream, Headers requestHeaders, Headers responseHeaders)
-    {
-        Set<String> result = Collections.<String>emptySet();
-        short version = stream.getSession().getVersion();
-        if (!isIfModifiedSinceHeaderPresent(requestHeaders) && isValidMethod(requestHeaders.get(HTTPSPDYHeader.METHOD.name(version)).value()))
-        {
-            String scheme = requestHeaders.get(HTTPSPDYHeader.SCHEME.name(version)).value();
-            String host = requestHeaders.get(HTTPSPDYHeader.HOST.name(version)).value();
-            String origin = scheme + "://" + host;
-            String url = requestHeaders.get(HTTPSPDYHeader.URI.name(version)).value();
-            String absoluteURL = origin + url;
-            logger.debug("Applying push strategy for {}", absoluteURL);
-            if (isMainResource(url, responseHeaders))
-            {
-                MainResource mainResource = getOrCreateMainResource(absoluteURL);
-                result = mainResource.getResources();
-            }
-            else if (isPushResource(url, responseHeaders))
-            {
-                Headers.Header referrerHeader = requestHeaders.get("referer");
-                if (referrerHeader != null)
-                {
-                    String referrer = referrerHeader.value();
-                    MainResource mainResource = mainResources.get(referrer);
-                    if (mainResource == null)
-                        mainResource = getOrCreateMainResource(referrer);
-
-                    Set<String> pushResources = mainResource.getResources();
-                    if (!pushResources.contains(url))
-                        mainResource.addResource(url, origin, referrer);
-                    else
-                        result = getPushResources(absoluteURL);
-                }
-            }
-            logger.debug("Pushing {} resources for {}: {}", result.size(), absoluteURL, result);
-        }
-        return result;
-    }
-
-    private Set<String> getPushResources(String absoluteURL)
-    {
-        Set<String> result = Collections.emptySet();
-        if (mainResources.get(absoluteURL) != null)
-            result = mainResources.get(absoluteURL).getResources();
-        return result;
-    }
-
-    private MainResource getOrCreateMainResource(String absoluteURL)
-    {
-        MainResource mainResource = mainResources.get(absoluteURL);
-        if (mainResource == null)
-        {
-            logger.debug("Creating new main resource for {}", absoluteURL);
-            MainResource value = new MainResource(absoluteURL);
-            mainResource = mainResources.putIfAbsent(absoluteURL, value);
-            if (mainResource == null)
-                mainResource = value;
-        }
-        return mainResource;
-    }
-
-    private boolean isIfModifiedSinceHeaderPresent(Headers headers)
-    {
-        return headers.get("if-modified-since") != null;
-    }
-
-    private boolean isValidMethod(String method)
-    {
-        return "GET".equalsIgnoreCase(method);
-    }
-
-    private boolean isMainResource(String url, Headers responseHeaders)
-    {
-        return !isPushResource(url, responseHeaders);
-    }
-
-    private boolean isPushResource(String url, Headers responseHeaders)
-    {
-        for (Pattern pushRegexp : pushRegexps)
-        {
-            if (pushRegexp.matcher(url).matches())
-            {
-                Headers.Header header = responseHeaders.get("content-type");
-                if (header == null)
-                    return true;
-
-                String contentType = header.value().toLowerCase(Locale.ENGLISH);
-                for (String pushContentType : pushContentTypes)
-                    if (contentType.startsWith(pushContentType))
-                        return true;
-            }
-        }
-        return false;
-    }
-
-    private class MainResource
-    {
-        private final String name;
-        private final Set<String> resources = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>());
-        private final AtomicLong firstResourceAdded = new AtomicLong(-1);
-
-        private MainResource(String name)
-        {
-            this.name = name;
-        }
-
-        public boolean addResource(String url, String origin, String referrer)
-        {
-            // We start the push period here and not when initializing the main resource, because a browser with a
-            // prefilled cache won't request the subresources. If the browser with warmed up cache now hits the main
-            // resource after a server restart, the push period shouldn't start until the first subresource is
-            // being requested.
-            firstResourceAdded.compareAndSet(-1, System.nanoTime());
-
-            long delay = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - firstResourceAdded.get());
-            if (!referrer.startsWith(origin) && !isPushOriginAllowed(origin))
-            {
-                logger.debug("Skipped store of push metadata {} for {}: Origin: {} doesn't match or origin not allowed",
-                        url, name, origin);
-                return false;
-            }
-
-            // This check is not strictly concurrent-safe, but limiting
-            // the number of associated resources is achieved anyway
-            // although in rare cases few more resources will be stored
-            if (resources.size() >= maxAssociatedResources)
-            {
-                logger.debug("Skipped store of push metadata {} for {}: max associated resources ({}) reached",
-                        url, name, maxAssociatedResources);
-                return false;
-            }
-            if (delay > referrerPushPeriod)
-            {
-                logger.debug("Delay: {}ms longer than referrerPushPeriod: {}ms. Not adding resource: {} for: {}", delay, referrerPushPeriod, url, name);
-                return false;
-            }
-
-            logger.debug("Adding resource: {} for: {} with delay: {}ms.", url, name, delay);
-            resources.add(url);
-            return true;
-        }
-
-        public Set<String> getResources()
-        {
-            return Collections.unmodifiableSet(resources);
-        }
-
-        public String toString()
-        {
-            return "MainResource: " + name + " associated resources:" + resources.size();
-        }
-
-        private boolean isPushOriginAllowed(String origin)
-        {
-            for (Pattern allowedPushOrigin : allowedPushOrigins)
-            {
-                if (allowedPushOrigin.matcher(origin).matches())
-                    return true;
-            }
-            return false;
-        }
-    }
-}
diff --git a/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/ServerHTTPAsyncConnectionFactory.java b/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/ServerHTTPAsyncConnectionFactory.java
deleted file mode 100644
index b2f4180..0000000
--- a/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/ServerHTTPAsyncConnectionFactory.java
+++ /dev/null
@@ -1,49 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-
-package org.eclipse.jetty.spdy.http;
-
-import java.nio.channels.SocketChannel;
-
-import org.eclipse.jetty.io.AsyncEndPoint;
-import org.eclipse.jetty.io.nio.AsyncConnection;
-import org.eclipse.jetty.server.AsyncHttpConnection;
-import org.eclipse.jetty.spdy.AsyncConnectionFactory;
-import org.eclipse.jetty.spdy.SPDYServerConnector;
-
-public class ServerHTTPAsyncConnectionFactory implements AsyncConnectionFactory
-{
-    private final SPDYServerConnector connector;
-
-    public ServerHTTPAsyncConnectionFactory(SPDYServerConnector connector)
-    {
-        this.connector = connector;
-    }
-
-    public SPDYServerConnector getConnector()
-    {
-        return connector;
-    }
-
-    @Override
-    public AsyncConnection newAsyncConnection(SocketChannel channel, AsyncEndPoint endPoint, Object attachment)
-    {
-        return new AsyncHttpConnection(connector, endPoint, connector.getServer());
-    }
-}
diff --git a/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/ServerHTTPSPDYAsyncConnection.java b/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/ServerHTTPSPDYAsyncConnection.java
deleted file mode 100644
index 26ffe23..0000000
--- a/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/ServerHTTPSPDYAsyncConnection.java
+++ /dev/null
@@ -1,794 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-
-package org.eclipse.jetty.spdy.http;
-
-import java.io.EOFException;
-import java.io.IOException;
-import java.io.InterruptedIOException;
-import java.nio.ByteBuffer;
-import java.util.LinkedList;
-import java.util.Locale;
-import java.util.Queue;
-import java.util.Set;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
-import org.eclipse.jetty.http.HttpException;
-import org.eclipse.jetty.http.HttpFields;
-import org.eclipse.jetty.http.HttpGenerator;
-import org.eclipse.jetty.http.HttpParser;
-import org.eclipse.jetty.http.HttpStatus;
-import org.eclipse.jetty.io.AsyncEndPoint;
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.Buffers;
-import org.eclipse.jetty.io.ByteArrayBuffer;
-import org.eclipse.jetty.io.Connection;
-import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.io.nio.AsyncConnection;
-import org.eclipse.jetty.io.nio.DirectNIOBuffer;
-import org.eclipse.jetty.io.nio.IndirectNIOBuffer;
-import org.eclipse.jetty.io.nio.NIOBuffer;
-import org.eclipse.jetty.server.AbstractHttpConnection;
-import org.eclipse.jetty.server.Connector;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.spdy.SPDYAsyncConnection;
-import org.eclipse.jetty.spdy.api.ByteBufferDataInfo;
-import org.eclipse.jetty.spdy.api.BytesDataInfo;
-import org.eclipse.jetty.spdy.api.DataInfo;
-import org.eclipse.jetty.spdy.api.Handler;
-import org.eclipse.jetty.spdy.api.Headers;
-import org.eclipse.jetty.spdy.api.ReplyInfo;
-import org.eclipse.jetty.spdy.api.RstInfo;
-import org.eclipse.jetty.spdy.api.SPDY;
-import org.eclipse.jetty.spdy.api.Stream;
-import org.eclipse.jetty.spdy.api.StreamStatus;
-import org.eclipse.jetty.spdy.api.SynInfo;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-
-public class ServerHTTPSPDYAsyncConnection extends AbstractHttpConnection implements AsyncConnection
-{
-    private static final Logger logger = Log.getLogger(ServerHTTPSPDYAsyncConnection.class);
-    private static final ByteBuffer ZERO_BYTES = ByteBuffer.allocate(0);
-    private static final DataInfo END_OF_CONTENT = new ByteBufferDataInfo(ZERO_BYTES, true);
-
-    private final Queue<Runnable> tasks = new LinkedList<>();
-    private final BlockingQueue<DataInfo> dataInfos = new LinkedBlockingQueue<>();
-    private final short version;
-    private final SPDYAsyncConnection connection;
-    private final PushStrategy pushStrategy;
-    private final Stream stream;
-    private Headers headers; // No need for volatile, guarded by state
-    private DataInfo dataInfo; // No need for volatile, guarded by state
-    private NIOBuffer buffer; // No need for volatile, guarded by state
-    private volatile State state = State.INITIAL;
-    private boolean dispatched; // Guarded by synchronization on tasks
-
-    public ServerHTTPSPDYAsyncConnection(Connector connector, AsyncEndPoint endPoint, Server server, short version, SPDYAsyncConnection connection, PushStrategy pushStrategy, Stream stream)
-    {
-        super(connector, endPoint, server);
-        this.version = version;
-        this.connection = connection;
-        this.pushStrategy = pushStrategy;
-        this.stream = stream;
-        getParser().setPersistent(true);
-    }
-
-    @Override
-    protected HttpParser newHttpParser(Buffers requestBuffers, EndPoint endPoint, HttpParser.EventHandler requestHandler)
-    {
-        return new HTTPSPDYParser(requestBuffers, endPoint);
-    }
-
-    @Override
-    protected HttpGenerator newHttpGenerator(Buffers responseBuffers, EndPoint endPoint)
-    {
-        return new HTTPSPDYGenerator(responseBuffers, endPoint);
-    }
-
-    @Override
-    public AsyncEndPoint getEndPoint()
-    {
-        return (AsyncEndPoint)super.getEndPoint();
-    }
-
-    private void post(Runnable task)
-    {
-        synchronized (tasks)
-        {
-            logger.debug("Posting task {}", task);
-            tasks.offer(task);
-            dispatch();
-        }
-    }
-
-    private void dispatch()
-    {
-        synchronized (tasks)
-        {
-            if (dispatched)
-                return;
-
-            final Runnable task = tasks.poll();
-            if (task != null)
-            {
-                dispatched = true;
-                logger.debug("Dispatching task {}", task);
-                execute(new Runnable()
-                {
-                    @Override
-                    public void run()
-                    {
-                        logger.debug("Executing task {}", task);
-                        task.run();
-                        logger.debug("Completing task {}", task);
-                        dispatched = false;
-                        dispatch();
-                    }
-                });
-            }
-        }
-    }
-
-    protected void execute(Runnable task)
-    {
-        getServer().getThreadPool().dispatch(task);
-    }
-
-    @Override
-    public Connection handle()
-    {
-        setCurrentConnection(this);
-        try
-        {
-            switch (state)
-            {
-                case INITIAL:
-                {
-                    break;
-                }
-                case REQUEST:
-                {
-                    Headers.Header method = headers.get(HTTPSPDYHeader.METHOD.name(version));
-                    Headers.Header uri = headers.get(HTTPSPDYHeader.URI.name(version));
-                    Headers.Header version = headers.get(HTTPSPDYHeader.VERSION.name(this.version));
-
-                    if (method == null || uri == null || version == null)
-                        throw new HttpException(HttpStatus.BAD_REQUEST_400);
-
-                    String m = method.value();
-                    String u = uri.value();
-                    String v = version.value();
-                    logger.debug("HTTP > {} {} {}", m, u, v);
-                    startRequest(new ByteArrayBuffer(m), new ByteArrayBuffer(u), new ByteArrayBuffer(v));
-
-                    Headers.Header schemeHeader = headers.get(HTTPSPDYHeader.SCHEME.name(this.version));
-                    if(schemeHeader != null)
-                        _request.setScheme(schemeHeader.value());
-
-                    updateState(State.HEADERS);
-                    handle();
-                    break;
-                }
-                case HEADERS:
-                {
-                    for (Headers.Header header : headers)
-                    {
-                        String name = header.name();
-
-                        // Skip special SPDY headers, unless it's the "host" header
-                        HTTPSPDYHeader specialHeader = HTTPSPDYHeader.from(version, name);
-                        if (specialHeader != null)
-                        {
-                            if (specialHeader == HTTPSPDYHeader.HOST)
-                                name = "host";
-                            else
-                                continue;
-                        }
-
-                        switch (name)
-                        {
-                            case "connection":
-                            case "keep-alive":
-                            case "proxy-connection":
-                            case "transfer-encoding":
-                            {
-                                // Spec says to ignore these headers
-                                continue;
-                            }
-                            default:
-                            {
-                                // Spec says headers must be single valued
-                                String value = header.value();
-                                logger.debug("HTTP > {}: {}", name, value);
-                                parsedHeader(new ByteArrayBuffer(name), new ByteArrayBuffer(value));
-                                break;
-                            }
-                        }
-                    }
-                    break;
-                }
-                case HEADERS_COMPLETE:
-                {
-                    headerComplete();
-                    break;
-                }
-                case CONTENT:
-                {
-                    final Buffer buffer = this.buffer;
-                    if (buffer != null && buffer.length() > 0)
-                        content(buffer);
-                    break;
-                }
-                case FINAL:
-                {
-                    messageComplete(0);
-                    break;
-                }
-                case ASYNC:
-                {
-                    handleRequest();
-                    break;
-                }
-                default:
-                {
-                    throw new IllegalStateException();
-                }
-            }
-            return this;
-        }
-        catch (HttpException x)
-        {
-            respond(stream, x.getStatus());
-            return this;
-        }
-        catch (IOException x)
-        {
-            close(stream);
-            return this;
-        }
-        finally
-        {
-            setCurrentConnection(null);
-        }
-    }
-
-    private void respond(Stream stream, int status)
-    {
-        if (stream.isUnidirectional())
-        {
-            stream.getSession().rst(new RstInfo(stream.getId(), StreamStatus.INTERNAL_ERROR));
-        }
-        else
-        {
-            Headers headers = new Headers();
-            headers.put(HTTPSPDYHeader.STATUS.name(version), String.valueOf(status));
-            headers.put(HTTPSPDYHeader.VERSION.name(version), "HTTP/1.1");
-            stream.reply(new ReplyInfo(headers, true));
-        }
-    }
-
-    private void close(Stream stream)
-    {
-        stream.getSession().goAway();
-    }
-
-    @Override
-    public void onInputShutdown() throws IOException
-    {
-    }
-
-    private void updateState(State newState)
-    {
-        logger.debug("State update {} -> {}", state, newState);
-        state = newState;
-    }
-
-    public void beginRequest(final Headers headers, final boolean endRequest)
-    {
-        this.headers = headers.isEmpty() ? null : headers;
-        post(new Runnable()
-        {
-            @Override
-            public void run()
-            {
-                if (!headers.isEmpty())
-                    updateState(State.REQUEST);
-                handle();
-                if (endRequest)
-                    performEndRequest();
-            }
-        });
-    }
-
-    public void headers(Headers headers)
-    {
-        this.headers = headers;
-        post(new Runnable()
-        {
-            @Override
-            public void run()
-            {
-                updateState(state == State.INITIAL ? State.REQUEST : State.HEADERS);
-                handle();
-            }
-        });
-    }
-
-    public void content(final DataInfo dataInfo, boolean endRequest)
-    {
-        // We need to copy the dataInfo since we do not know when its bytes
-        // will be consumed. When the copy is consumed, we consume also the
-        // original, so the implementation can send a window update.
-        ByteBufferDataInfo copyDataInfo = new ByteBufferDataInfo(dataInfo.asByteBuffer(false), dataInfo.isClose(), dataInfo.isCompress())
-        {
-            @Override
-            public void consume(int delta)
-            {
-                super.consume(delta);
-                dataInfo.consume(delta);
-            }
-        };
-        logger.debug("Queuing last={} content {}", endRequest, copyDataInfo);
-        dataInfos.offer(copyDataInfo);
-        if (endRequest)
-            dataInfos.offer(END_OF_CONTENT);
-        post(new Runnable()
-        {
-            @Override
-            public void run()
-            {
-                logger.debug("HTTP > {} bytes of content", dataInfo.length());
-                if (state == State.HEADERS)
-                {
-                    updateState(State.HEADERS_COMPLETE);
-                    handle();
-                }
-                updateState(State.CONTENT);
-                handle();
-            }
-        });
-    }
-
-    public void endRequest()
-    {
-        post(new Runnable()
-        {
-            public void run()
-            {
-                performEndRequest();
-            }
-        });
-    }
-
-    private void performEndRequest()
-    {
-        if (state == State.HEADERS)
-        {
-            updateState(State.HEADERS_COMPLETE);
-            handle();
-        }
-        updateState(State.FINAL);
-        handle();
-    }
-
-    public void async()
-    {
-        post(new Runnable()
-        {
-            @Override
-            public void run()
-            {
-                State oldState = state;
-                updateState(State.ASYNC);
-                handle();
-                updateState(oldState);
-            }
-        });
-    }
-
-    protected void reply(Stream stream, ReplyInfo replyInfo)
-    {
-        if (!stream.isUnidirectional())
-            stream.reply(replyInfo);
-        if (replyInfo.getHeaders().get(HTTPSPDYHeader.STATUS.name(version)).value().startsWith("200") &&
-                !stream.isClosed())
-        {
-            // We have a 200 OK with some content to send
-
-            Headers.Header scheme = headers.get(HTTPSPDYHeader.SCHEME.name(version));
-            Headers.Header host = headers.get(HTTPSPDYHeader.HOST.name(version));
-            Headers.Header uri = headers.get(HTTPSPDYHeader.URI.name(version));
-            Set<String> pushResources = pushStrategy.apply(stream, headers, replyInfo.getHeaders());
-
-            for (String pushResourcePath : pushResources)
-            {
-                final Headers requestHeaders = createRequestHeaders(scheme, host, uri, pushResourcePath);
-                final Headers pushHeaders = createPushHeaders(scheme, host, pushResourcePath);
-
-                stream.syn(new SynInfo(pushHeaders, false), getMaxIdleTime(), TimeUnit.MILLISECONDS, new Handler.Adapter<Stream>()
-                {
-                    @Override
-                    public void completed(Stream pushStream)
-                    {
-                        ServerHTTPSPDYAsyncConnection pushConnection =
-                                new ServerHTTPSPDYAsyncConnection(getConnector(), getEndPoint(), getServer(), version, connection, pushStrategy, pushStream);
-                        pushConnection.beginRequest(requestHeaders, true);
-                    }
-                });
-            }
-        }
-    }
-
-    private Headers createRequestHeaders(Headers.Header scheme, Headers.Header host, Headers.Header uri, String pushResourcePath)
-    {
-        final Headers requestHeaders = new Headers();
-        requestHeaders.put(HTTPSPDYHeader.METHOD.name(version), "GET");
-        requestHeaders.put(HTTPSPDYHeader.VERSION.name(version), "HTTP/1.1");
-        requestHeaders.put(scheme);
-        requestHeaders.put(host);
-        requestHeaders.put(HTTPSPDYHeader.URI.name(version), pushResourcePath);
-        String referrer = scheme.value() + "://" + host.value() + uri.value();
-        requestHeaders.put("referer", referrer);
-        // Remember support for gzip encoding
-        requestHeaders.put(headers.get("accept-encoding"));
-        requestHeaders.put("x-spdy-push", "true");
-        return requestHeaders;
-    }
-
-    private Headers createPushHeaders(Headers.Header scheme, Headers.Header host, String pushResourcePath)
-    {
-        final Headers pushHeaders = new Headers();
-        if (version == SPDY.V2)
-            pushHeaders.put(HTTPSPDYHeader.URI.name(version), scheme.value() + "://" + host.value() + pushResourcePath);
-        else
-        {
-            pushHeaders.put(HTTPSPDYHeader.URI.name(version), pushResourcePath);
-            pushHeaders.put(scheme);
-            pushHeaders.put(host);
-        }
-        pushHeaders.put(HTTPSPDYHeader.STATUS.name(version), "200");
-        pushHeaders.put(HTTPSPDYHeader.VERSION.name(version), "HTTP/1.1");
-        return pushHeaders;
-    }
-
-    private Buffer consumeContent(long maxIdleTime) throws IOException, InterruptedException
-    {
-        while (true)
-        {
-            // Volatile read to ensure visibility
-            State state = this.state;
-            if (state != State.HEADERS_COMPLETE && state != State.CONTENT && state != State.FINAL)
-                throw new IllegalStateException();
-
-            if (buffer != null)
-            {
-                if (buffer.length() > 0)
-                {
-                    logger.debug("Consuming content bytes, {} available", buffer.length());
-                    return buffer;
-                }
-                else
-                {
-                    // The application has consumed the buffer, so consume also the DataInfo
-                    dataInfo.consume(dataInfo.length());
-                    logger.debug("Consumed {} content bytes, queue size {}", dataInfo.consumed(), dataInfos.size());
-                    dataInfo = null;
-                    buffer = null;
-                    // Loop to get content bytes from DataInfos
-                }
-            }
-            else
-            {
-                logger.debug("Waiting at most {} ms for content bytes", maxIdleTime);
-                long begin = System.nanoTime();
-                dataInfo = dataInfos.poll(maxIdleTime, TimeUnit.MILLISECONDS);
-                long elapsed = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - begin);
-                logger.debug("Waited {} ms for content bytes", elapsed);
-                if (dataInfo != null)
-                {
-                    if (dataInfo == END_OF_CONTENT)
-                    {
-                        logger.debug("End of content bytes, queue size {}", dataInfos.size());
-                        return null;
-                    }
-
-                    ByteBuffer byteBuffer = dataInfo.asByteBuffer(false);
-                    buffer = byteBuffer.isDirect() ? new DirectNIOBuffer(byteBuffer, false) : new IndirectNIOBuffer(byteBuffer, false);
-                    // Loop to return the buffer
-                }
-                else
-                {
-                    stream.getSession().goAway();
-                    throw new EOFException("read timeout");
-                }
-            }
-        }
-    }
-
-    private int availableContent()
-    {
-        // Volatile read to ensure visibility
-        State state = this.state;
-        if (state != State.HEADERS_COMPLETE && state != State.CONTENT)
-            throw new IllegalStateException();
-        return buffer == null ? 0 : buffer.length();
-    }
-
-    @Override
-    public void commitResponse(boolean last) throws IOException
-    {
-        // Keep the original behavior since it just delegates to the generator
-        super.commitResponse(last);
-    }
-
-    @Override
-    public void flushResponse() throws IOException
-    {
-        // Just commit the response, if necessary: flushing buffers will be taken care of in complete()
-        commitResponse(false);
-    }
-
-    @Override
-    public void completeResponse() throws IOException
-    {
-        // Keep the original behavior since it just delegates to the generator
-        super.completeResponse();
-    }
-
-    private enum State
-    {
-        INITIAL, REQUEST, HEADERS, HEADERS_COMPLETE, CONTENT, FINAL, ASYNC
-    }
-
-    /**
-     * Needed in order to override parser methods that read content.
-     */
-    private class HTTPSPDYParser extends HttpParser
-    {
-        public HTTPSPDYParser(Buffers buffers, EndPoint endPoint)
-        {
-            super(buffers, endPoint, new HTTPSPDYParserHandler());
-        }
-
-        @Override
-        public Buffer blockForContent(long maxIdleTime) throws IOException
-        {
-            try
-            {
-                return consumeContent(maxIdleTime);
-            }
-            catch (InterruptedException x)
-            {
-                throw new InterruptedIOException();
-            }
-        }
-
-        @Override
-        public int available() throws IOException
-        {
-            return availableContent();
-        }
-    }
-
-    /**
-     * Empty implementation, since it won't parse anything
-     */
-    private static class HTTPSPDYParserHandler extends HttpParser.EventHandler
-    {
-        @Override
-        public void startRequest(Buffer method, Buffer url, Buffer version) throws IOException
-        {
-        }
-
-        @Override
-        public void content(Buffer ref) throws IOException
-        {
-        }
-
-        @Override
-        public void startResponse(Buffer version, int status, Buffer reason) throws IOException
-        {
-        }
-    }
-
-    /**
-     * Needed in order to override generator methods that would generate HTTP,
-     * since we must generate SPDY instead.
-     */
-    private class HTTPSPDYGenerator extends HttpGenerator
-    {
-        private boolean closed;
-
-        private HTTPSPDYGenerator(Buffers buffers, EndPoint endPoint)
-        {
-            super(buffers, endPoint);
-        }
-
-        @Override
-        public void send1xx(int code) throws IOException
-        {
-            // TODO: not supported yet, but unlikely to be called
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public void sendResponse(Buffer response) throws IOException
-        {
-            // Do not think this method is ever used.
-            // Jetty calls it from Request.setAttribute() only if the attribute
-            // "org.eclipse.jetty.server.ResponseBuffer", seems like a hack.
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public void sendError(int code, String reason, String content, boolean close) throws IOException
-        {
-            // Keep original behavior because it's delegating to other methods that we override.
-            super.sendError(code, reason, content, close);
-        }
-
-        @Override
-        public void completeHeader(HttpFields fields, boolean allContentAdded) throws IOException
-        {
-            Headers headers = new Headers();
-            String version = "HTTP/1.1";
-            headers.put(HTTPSPDYHeader.VERSION.name(ServerHTTPSPDYAsyncConnection.this.version), version);
-            StringBuilder status = new StringBuilder().append(_status);
-            if (_reason != null)
-                status.append(" ").append(_reason.toString("UTF-8"));
-            headers.put(HTTPSPDYHeader.STATUS.name(ServerHTTPSPDYAsyncConnection.this.version), status.toString());
-            logger.debug("HTTP < {} {}", version, status);
-
-            if (fields != null)
-            {
-                for (int i = 0; i < fields.size(); ++i)
-                {
-                    HttpFields.Field field = fields.getField(i);
-                    String name = field.getName().toLowerCase(Locale.ENGLISH);
-                    String value = field.getValue();
-                    headers.put(name, value);
-                    logger.debug("HTTP < {}: {}", name, value);
-                }
-            }
-
-            // We have to query the HttpGenerator and its buffers to know
-            // whether there is content buffered and update the generator state
-            Buffer content = getContentBuffer();
-            reply(stream, new ReplyInfo(headers, content == null));
-            if (content != null)
-            {
-                closed = false;
-                // Update HttpGenerator fields so that they remain consistent
-                _state = HttpGenerator.STATE_CONTENT;
-            }
-            else
-            {
-                closed = true;
-                // Update HttpGenerator fields so that they remain consistent
-                _state = HttpGenerator.STATE_END;
-            }
-        }
-
-        private Buffer getContentBuffer()
-        {
-            if (_buffer != null && _buffer.length() > 0)
-                return _buffer;
-            if (_content != null && _content.length() > 0)
-                return _content;
-            return null;
-        }
-
-        @Override
-        public void addContent(Buffer content, boolean last) throws IOException
-        {
-            // Keep the original behavior since adding content will
-            // just accumulate bytes until the response is committed.
-            super.addContent(content, last);
-        }
-
-        @Override
-        public void flush(long maxIdleTime) throws IOException
-        {
-            try
-            {
-                Buffer content = getContentBuffer();
-                while (content != null)
-                {
-                    DataInfo dataInfo = toDataInfo(content, closed);
-                    logger.debug("HTTP < {} bytes of content", dataInfo.length());
-                    stream.data(dataInfo).get(maxIdleTime, TimeUnit.MILLISECONDS);
-                    content.clear();
-                    _bypass = false;
-                    content = getContentBuffer();
-                }
-            }
-            catch (TimeoutException x)
-            {
-                stream.getSession().goAway();
-                throw new EOFException("write timeout");
-            }
-            catch (InterruptedException x)
-            {
-                throw new InterruptedIOException();
-            }
-            catch (ExecutionException x)
-            {
-                throw new IOException(x.getCause());
-            }
-        }
-
-        private DataInfo toDataInfo(Buffer buffer, boolean close)
-        {
-            if (buffer instanceof ByteArrayBuffer)
-                return new BytesDataInfo(buffer.array(), buffer.getIndex(), buffer.length(), close);
-
-            if (buffer instanceof NIOBuffer)
-            {
-                ByteBuffer byteBuffer = ((NIOBuffer)buffer).getByteBuffer();
-                byteBuffer.limit(buffer.putIndex());
-                byteBuffer.position(buffer.getIndex());
-                return new ByteBufferDataInfo(byteBuffer, close);
-            }
-
-            return new BytesDataInfo(buffer.asArray(), close);
-        }
-
-        @Override
-        public int flushBuffer() throws IOException
-        {
-            // Must never be called because it's where the HttpGenerator writes
-            // the HTTP content to the EndPoint (we should write SPDY instead).
-            // If it's called it's our bug.
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public void blockForOutput(long maxIdleTime) throws IOException
-        {
-            // The semantic of this method is weird: not only it has to block
-            // but also need to flush. Since we have a blocking flush method
-            // we delegate to that, because it has the same semantic.
-            flush(maxIdleTime);
-        }
-
-        @Override
-        public void complete() throws IOException
-        {
-            Buffer content = getContentBuffer();
-            if (content != null)
-            {
-                closed = true;
-                _state = STATE_END;
-                flush(getMaxIdleTime());
-            }
-            else if (!closed)
-            {
-                closed = true;
-                _state = STATE_END;
-                // Send the last, empty, data frame
-                stream.data(new ByteBufferDataInfo(ZERO_BYTES, true));
-            }
-        }
-    }
-}
diff --git a/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/ServerHTTPSPDYAsyncConnectionFactory.java b/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/ServerHTTPSPDYAsyncConnectionFactory.java
deleted file mode 100644
index 2437940..0000000
--- a/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/ServerHTTPSPDYAsyncConnectionFactory.java
+++ /dev/null
@@ -1,188 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-
-package org.eclipse.jetty.spdy.http;
-
-import java.util.concurrent.Executor;
-import java.util.concurrent.ScheduledExecutorService;
-
-import org.eclipse.jetty.io.AsyncEndPoint;
-import org.eclipse.jetty.server.Connector;
-import org.eclipse.jetty.spdy.ByteBufferPool;
-import org.eclipse.jetty.spdy.EmptyAsyncEndPoint;
-import org.eclipse.jetty.spdy.SPDYAsyncConnection;
-import org.eclipse.jetty.spdy.ServerSPDYAsyncConnectionFactory;
-import org.eclipse.jetty.spdy.api.DataInfo;
-import org.eclipse.jetty.spdy.api.Headers;
-import org.eclipse.jetty.spdy.api.HeadersInfo;
-import org.eclipse.jetty.spdy.api.ReplyInfo;
-import org.eclipse.jetty.spdy.api.Stream;
-import org.eclipse.jetty.spdy.api.StreamFrameListener;
-import org.eclipse.jetty.spdy.api.SynInfo;
-import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-
-public class ServerHTTPSPDYAsyncConnectionFactory extends ServerSPDYAsyncConnectionFactory
-{
-    private static final String CONNECTION_ATTRIBUTE = "org.eclipse.jetty.spdy.http.connection";
-    private static final Logger logger = Log.getLogger(ServerHTTPSPDYAsyncConnectionFactory.class);
-
-    private final Connector connector;
-    private final PushStrategy pushStrategy;
-
-    public ServerHTTPSPDYAsyncConnectionFactory(short version, ByteBufferPool bufferPool, Executor threadPool, ScheduledExecutorService scheduler, Connector connector, PushStrategy pushStrategy)
-    {
-        super(version, bufferPool, threadPool, scheduler);
-        this.connector = connector;
-        this.pushStrategy = pushStrategy;
-    }
-
-    @Override
-    protected ServerSessionFrameListener provideServerSessionFrameListener(AsyncEndPoint endPoint, Object attachment)
-    {
-        return new HTTPServerFrameListener(endPoint);
-    }
-
-    private class HTTPServerFrameListener extends ServerSessionFrameListener.Adapter implements StreamFrameListener
-    {
-        private final AsyncEndPoint endPoint;
-
-        public HTTPServerFrameListener(AsyncEndPoint endPoint)
-        {
-            this.endPoint = endPoint;
-        }
-
-        @Override
-        public StreamFrameListener onSyn(final Stream stream, SynInfo synInfo)
-        {
-            // Every time we have a SYN, it maps to a HTTP request.
-            // We can have multiple concurrent SYNs on the same connection,
-            // and this is very different from HTTP, where only one request/response
-            // cycle is processed at a time, so we need to fake an http connection
-            // for each SYN in order to run concurrently.
-
-            logger.debug("Received {} on {}", synInfo, stream);
-
-            HTTPSPDYAsyncEndPoint asyncEndPoint = new HTTPSPDYAsyncEndPoint(endPoint, stream);
-            ServerHTTPSPDYAsyncConnection connection = new ServerHTTPSPDYAsyncConnection(connector, asyncEndPoint,
-                    connector.getServer(), getVersion(), (SPDYAsyncConnection)endPoint.getConnection(),
-                    pushStrategy, stream);
-            asyncEndPoint.setConnection(connection);
-            stream.setAttribute(CONNECTION_ATTRIBUTE, connection);
-
-            Headers headers = synInfo.getHeaders();
-            connection.beginRequest(headers, synInfo.isClose());
-
-            if (headers.isEmpty())
-            {
-                // If the SYN has no headers, they may come later in a HEADERS frame
-                return this;
-            }
-            else
-            {
-                if (synInfo.isClose())
-                    return null;
-                else
-                    return this;
-            }
-        }
-
-        @Override
-        public void onReply(Stream stream, ReplyInfo replyInfo)
-        {
-            // Do nothing, servers cannot get replies
-        }
-
-        @Override
-        public void onHeaders(Stream stream, HeadersInfo headersInfo)
-        {
-            logger.debug("Received {} on {}", headersInfo, stream);
-            ServerHTTPSPDYAsyncConnection connection = (ServerHTTPSPDYAsyncConnection)stream.getAttribute(CONNECTION_ATTRIBUTE);
-            connection.headers(headersInfo.getHeaders());
-            if (headersInfo.isClose())
-                connection.endRequest();
-        }
-
-        @Override
-        public void onData(Stream stream, DataInfo dataInfo)
-        {
-            logger.debug("Received {} on {}", dataInfo, stream);
-            ServerHTTPSPDYAsyncConnection connection = (ServerHTTPSPDYAsyncConnection)stream.getAttribute(CONNECTION_ATTRIBUTE);
-            connection.content(dataInfo, dataInfo.isClose());
-            if (dataInfo.isClose())
-                connection.endRequest();
-        }
-    }
-
-    private class HTTPSPDYAsyncEndPoint extends EmptyAsyncEndPoint
-    {
-        private final AsyncEndPoint endPoint;
-        private final Stream stream;
-
-        private HTTPSPDYAsyncEndPoint(AsyncEndPoint endPoint, Stream stream)
-        {
-            this.endPoint = endPoint;
-            this.stream = stream;
-        }
-
-        @Override
-        public void asyncDispatch()
-        {
-            ServerHTTPSPDYAsyncConnection connection = (ServerHTTPSPDYAsyncConnection)stream.getAttribute(CONNECTION_ATTRIBUTE);
-            connection.async();
-        }
-
-        @Override
-        public String getLocalAddr()
-        {
-            return endPoint.getLocalAddr();
-        }
-
-        @Override
-        public String getLocalHost()
-        {
-            return endPoint.getLocalHost();
-        }
-
-        @Override
-        public int getLocalPort()
-        {
-            return endPoint.getLocalPort();
-        }
-
-        @Override
-        public String getRemoteAddr()
-        {
-            return endPoint.getRemoteAddr();
-        }
-
-        @Override
-        public String getRemoteHost()
-        {
-            return endPoint.getRemoteHost();
-        }
-
-        @Override
-        public int getRemotePort()
-        {
-            return endPoint.getRemotePort();
-        }
-    }
-}
diff --git a/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/proxy/HTTPSPDYProxyConnector.java b/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/proxy/HTTPSPDYProxyConnector.java
deleted file mode 100644
index 8461d69..0000000
--- a/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/proxy/HTTPSPDYProxyConnector.java
+++ /dev/null
@@ -1,44 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-
-package org.eclipse.jetty.spdy.proxy;
-
-import org.eclipse.jetty.spdy.ServerSPDYAsyncConnectionFactory;
-import org.eclipse.jetty.spdy.api.SPDY;
-import org.eclipse.jetty.spdy.http.AbstractHTTPSPDYServerConnector;
-import org.eclipse.jetty.util.ssl.SslContextFactory;
-
-public class HTTPSPDYProxyConnector extends AbstractHTTPSPDYServerConnector
-{
-    public HTTPSPDYProxyConnector(ProxyEngineSelector proxyEngineSelector)
-    {
-        this(proxyEngineSelector, null);
-    }
-
-    public HTTPSPDYProxyConnector(ProxyEngineSelector proxyEngineSelector, SslContextFactory sslContextFactory)
-    {
-        super(proxyEngineSelector, sslContextFactory);
-        clearAsyncConnectionFactories();
-
-        putAsyncConnectionFactory("spdy/3", new ServerSPDYAsyncConnectionFactory(SPDY.V3, getByteBufferPool(), getExecutor(), getScheduler(), proxyEngineSelector));
-        putAsyncConnectionFactory("spdy/2", new ServerSPDYAsyncConnectionFactory(SPDY.V2, getByteBufferPool(), getExecutor(), getScheduler(), proxyEngineSelector));
-        putAsyncConnectionFactory("http/1.1", new ProxyHTTPAsyncConnectionFactory(this, SPDY.V2, proxyEngineSelector));
-        setDefaultAsyncConnectionFactory(getAsyncConnectionFactory("http/1.1"));
-    }
-}
diff --git a/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/proxy/ProxyEngine.java b/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/proxy/ProxyEngine.java
deleted file mode 100644
index d389352..0000000
--- a/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/proxy/ProxyEngine.java
+++ /dev/null
@@ -1,99 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-
-package org.eclipse.jetty.spdy.proxy;
-
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-
-import org.eclipse.jetty.spdy.api.Headers;
-import org.eclipse.jetty.spdy.api.Stream;
-import org.eclipse.jetty.spdy.api.StreamFrameListener;
-import org.eclipse.jetty.spdy.api.SynInfo;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-
-/**
- * <p>{@link ProxyEngine} is the class for SPDY proxy functionalities that receives a SPDY request and converts it to
- * any protocol to its server side.</p>
- * <p>This class listens for SPDY events sent by clients; subclasses are responsible for translating
- * these SPDY client events into appropriate events to forward to the server, in the appropriate
- * protocol that is understood by the server.</p>
- */
-public abstract class ProxyEngine
-{
-    protected final Logger logger = Log.getLogger(getClass());
-    private final String name;
-
-    protected ProxyEngine()
-    {
-        this(name());
-    }
-
-    private static String name()
-    {
-        try
-        {
-            return InetAddress.getLocalHost().getHostName();
-        }
-        catch (UnknownHostException x)
-        {
-            return "localhost";
-        }
-    }
-
-    public abstract StreamFrameListener proxy(Stream clientStream, SynInfo clientSynInfo, ProxyEngineSelector.ProxyServerInfo proxyServerInfo);
-
-    protected ProxyEngine(String name)
-    {
-        this.name = name;
-    }
-
-    public String getName()
-    {
-        return name;
-    }
-
-    protected void addRequestProxyHeaders(Stream stream, Headers headers)
-    {
-        addViaHeader(headers);
-        String address = (String)stream.getSession().getAttribute("org.eclipse.jetty.spdy.remoteAddress");
-        if (address != null)
-            headers.add("X-Forwarded-For", address);
-    }
-
-    protected void addResponseProxyHeaders(Stream stream, Headers headers)
-    {
-        addViaHeader(headers);
-    }
-
-    private void addViaHeader(Headers headers)
-    {
-        headers.add("Via", "http/1.1 " + getName());
-    }
-
-    protected void customizeRequestHeaders(Stream stream, Headers headers)
-    {
-    }
-
-    protected void customizeResponseHeaders(Stream stream, Headers headers)
-    {
-    }
-
-}
diff --git a/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/proxy/ProxyEngineSelector.java b/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/proxy/ProxyEngineSelector.java
deleted file mode 100644
index d34c918..0000000
--- a/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/proxy/ProxyEngineSelector.java
+++ /dev/null
@@ -1,187 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.spdy.proxy;
-
-import java.net.InetSocketAddress;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
-import org.eclipse.jetty.spdy.api.GoAwayInfo;
-import org.eclipse.jetty.spdy.api.Headers;
-import org.eclipse.jetty.spdy.api.PingInfo;
-import org.eclipse.jetty.spdy.api.RstInfo;
-import org.eclipse.jetty.spdy.api.Session;
-import org.eclipse.jetty.spdy.api.Stream;
-import org.eclipse.jetty.spdy.api.StreamFrameListener;
-import org.eclipse.jetty.spdy.api.StreamStatus;
-import org.eclipse.jetty.spdy.api.SynInfo;
-import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener;
-import org.eclipse.jetty.spdy.http.HTTPSPDYHeader;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-
-/**
- * <p>{@link ProxyEngineSelector} is the main entry point for syn stream events of a jetty SPDY proxy. It receives the
- * syn stream frames from the clients, checks if there's an appropriate {@link ProxyServerInfo} for the given target
- * host and forwards the syn to a {@link ProxyEngine} for the protocol defined in {@link ProxyServerInfo}.</p>
- *
- * <p>If no {@link ProxyServerInfo} can be found for the given target host or no {@link ProxyEngine} can be found for
- * the given protocol, it resets the client stream.</p>
- *
- * <p>This class also provides configuration for the proxy rules.</p>
- */
-public class ProxyEngineSelector extends ServerSessionFrameListener.Adapter
-{
-    protected final Logger logger = Log.getLogger(getClass());
-    private final Map<String, ProxyServerInfo> proxyInfos = new ConcurrentHashMap<>();
-    private final Map<String, ProxyEngine> proxyEngines = new ConcurrentHashMap<>();
-
-    @Override
-    public final StreamFrameListener onSyn(final Stream clientStream, SynInfo clientSynInfo)
-    {
-        logger.debug("C -> P {} on {}", clientSynInfo, clientStream);
-
-        final Session clientSession = clientStream.getSession();
-        short clientVersion = clientSession.getVersion();
-        Headers headers = new Headers(clientSynInfo.getHeaders(), false);
-
-        Headers.Header hostHeader = headers.get(HTTPSPDYHeader.HOST.name(clientVersion));
-        if (hostHeader == null)
-        {
-            logger.debug("No host header found: " + headers);
-            rst(clientStream);
-            return null;
-        }
-
-        String host = hostHeader.value();
-        int colon = host.indexOf(':');
-        if (colon >= 0)
-            host = host.substring(0, colon);
-
-        ProxyServerInfo proxyServerInfo = getProxyServerInfo(host);
-        if (proxyServerInfo == null)
-        {
-            logger.debug("No matching ProxyServerInfo found for: " + host);
-            rst(clientStream);
-            return null;
-        }
-
-        String protocol = proxyServerInfo.getProtocol();
-        ProxyEngine proxyEngine = proxyEngines.get(protocol);
-        if (proxyEngine == null)
-        {
-            logger.debug("No matching ProxyEngine found for: " + protocol);
-            rst(clientStream);
-            return null;
-        }
-
-        return proxyEngine.proxy(clientStream, clientSynInfo, proxyServerInfo);
-    }
-
-    @Override
-    public void onPing(Session clientSession, PingInfo pingInfo)
-    {
-        // We do not know to which upstream server
-        // to send the PING so we just ignore it
-    }
-
-    @Override
-    public void onGoAway(Session session, GoAwayInfo goAwayInfo)
-    {
-        // TODO:
-    }
-
-    public Map<String, ProxyEngine> getProxyEngines()
-    {
-        return new HashMap<>(proxyEngines);
-    }
-
-    public void setProxyEngines(Map<String, ProxyEngine> proxyEngines)
-    {
-        this.proxyEngines.clear();
-        this.proxyEngines.putAll(proxyEngines);
-    }
-
-    public ProxyEngine getProxyEngine(String protocol)
-    {
-        return proxyEngines.get(protocol);
-    }
-
-    public void putProxyEngine(String protocol, ProxyEngine proxyEngine)
-    {
-        proxyEngines.put(protocol, proxyEngine);
-    }
-
-    public Map<String, ProxyServerInfo> getProxyServerInfos()
-    {
-        return new HashMap<>(proxyInfos);
-    }
-
-    protected ProxyServerInfo getProxyServerInfo(String host)
-    {
-        return proxyInfos.get(host);
-    }
-
-    public void setProxyServerInfos(Map<String, ProxyServerInfo> proxyServerInfos)
-    {
-        this.proxyInfos.clear();
-        this.proxyInfos.putAll(proxyServerInfos);
-    }
-
-    public void putProxyServerInfo(String host, ProxyServerInfo proxyServerInfo)
-    {
-        proxyInfos.put(host, proxyServerInfo);
-    }
-
-    private void rst(Stream stream)
-    {
-        RstInfo rstInfo = new RstInfo(stream.getId(), StreamStatus.REFUSED_STREAM);
-        stream.getSession().rst(rstInfo);
-    }
-
-    public static class ProxyServerInfo
-    {
-        private final String protocol;
-        private final String host;
-        private final InetSocketAddress address;
-
-        public ProxyServerInfo(String protocol, String host, int port)
-        {
-            this.protocol = protocol;
-            this.host = host;
-            this.address = new InetSocketAddress(host, port);
-        }
-
-        public String getProtocol()
-        {
-            return protocol;
-        }
-
-        public String getHost()
-        {
-            return host;
-        }
-
-        public InetSocketAddress getAddress()
-        {
-            return address;
-        }
-    }
-}
diff --git a/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/proxy/ProxyHTTPAsyncConnectionFactory.java b/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/proxy/ProxyHTTPAsyncConnectionFactory.java
deleted file mode 100644
index e0d1b8d..0000000
--- a/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/proxy/ProxyHTTPAsyncConnectionFactory.java
+++ /dev/null
@@ -1,46 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-
-package org.eclipse.jetty.spdy.proxy;
-
-import java.nio.channels.SocketChannel;
-
-import org.eclipse.jetty.io.AsyncEndPoint;
-import org.eclipse.jetty.io.nio.AsyncConnection;
-import org.eclipse.jetty.spdy.SPDYServerConnector;
-import org.eclipse.jetty.spdy.http.ServerHTTPAsyncConnectionFactory;
-
-public class ProxyHTTPAsyncConnectionFactory extends ServerHTTPAsyncConnectionFactory
-{
-    private final short version;
-    private final ProxyEngineSelector proxyEngineSelector;
-
-    public ProxyHTTPAsyncConnectionFactory(SPDYServerConnector connector, short version, ProxyEngineSelector proxyEngineSelector)
-    {
-        super(connector);
-        this.version = version;
-        this.proxyEngineSelector = proxyEngineSelector;
-    }
-
-    @Override
-    public AsyncConnection newAsyncConnection(SocketChannel channel, AsyncEndPoint endPoint, Object attachment)
-    {
-        return new ProxyHTTPSPDYAsyncConnection(getConnector(), endPoint, version, proxyEngineSelector);
-    }
-}
diff --git a/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/proxy/ProxyHTTPSPDYAsyncConnection.java b/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/proxy/ProxyHTTPSPDYAsyncConnection.java
deleted file mode 100644
index 1c8d274..0000000
--- a/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/proxy/ProxyHTTPSPDYAsyncConnection.java
+++ /dev/null
@@ -1,343 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-
-package org.eclipse.jetty.spdy.proxy;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.util.Locale;
-import java.util.concurrent.TimeUnit;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import org.eclipse.jetty.http.HttpFields;
-import org.eclipse.jetty.http.HttpGenerator;
-import org.eclipse.jetty.io.AsyncEndPoint;
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.ByteArrayBuffer;
-import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.io.nio.DirectNIOBuffer;
-import org.eclipse.jetty.io.nio.IndirectNIOBuffer;
-import org.eclipse.jetty.io.nio.NIOBuffer;
-import org.eclipse.jetty.server.AsyncHttpConnection;
-import org.eclipse.jetty.spdy.ISession;
-import org.eclipse.jetty.spdy.IStream;
-import org.eclipse.jetty.spdy.SPDYServerConnector;
-import org.eclipse.jetty.spdy.StandardSession;
-import org.eclipse.jetty.spdy.StandardStream;
-import org.eclipse.jetty.spdy.api.ByteBufferDataInfo;
-import org.eclipse.jetty.spdy.api.BytesDataInfo;
-import org.eclipse.jetty.spdy.api.DataInfo;
-import org.eclipse.jetty.spdy.api.GoAwayInfo;
-import org.eclipse.jetty.spdy.api.Handler;
-import org.eclipse.jetty.spdy.api.Headers;
-import org.eclipse.jetty.spdy.api.HeadersInfo;
-import org.eclipse.jetty.spdy.api.ReplyInfo;
-import org.eclipse.jetty.spdy.api.RstInfo;
-import org.eclipse.jetty.spdy.api.SessionStatus;
-import org.eclipse.jetty.spdy.api.Stream;
-import org.eclipse.jetty.spdy.api.StreamFrameListener;
-import org.eclipse.jetty.spdy.api.SynInfo;
-import org.eclipse.jetty.spdy.http.HTTPSPDYHeader;
-
-public class ProxyHTTPSPDYAsyncConnection extends AsyncHttpConnection
-{
-    private final Headers headers = new Headers();
-    private final short version;
-    private final ProxyEngineSelector proxyEngineSelector;
-    private final HttpGenerator generator;
-    private final ISession session;
-    private HTTPStream stream;
-    private Buffer content;
-
-    public ProxyHTTPSPDYAsyncConnection(SPDYServerConnector connector, EndPoint endPoint, short version, ProxyEngineSelector proxyEngineSelector)
-    {
-        super(connector, endPoint, connector.getServer());
-        this.version = version;
-        this.proxyEngineSelector = proxyEngineSelector;
-        this.generator = (HttpGenerator)_generator;
-        this.session = new HTTPSession(version, connector);
-        this.session.setAttribute("org.eclipse.jetty.spdy.remoteAddress", endPoint.getRemoteAddr());
-    }
-
-    @Override
-    public AsyncEndPoint getEndPoint()
-    {
-        return (AsyncEndPoint)super.getEndPoint();
-    }
-
-    @Override
-    protected void startRequest(Buffer method, Buffer uri, Buffer httpVersion) throws IOException
-    {
-        SPDYServerConnector connector = (SPDYServerConnector)getConnector();
-        String scheme = connector.getSslContextFactory() != null ? "https" : "http";
-        headers.put(HTTPSPDYHeader.SCHEME.name(version), scheme);
-        headers.put(HTTPSPDYHeader.METHOD.name(version), method.toString("UTF-8"));
-        headers.put(HTTPSPDYHeader.URI.name(version), uri.toString("UTF-8"));
-        headers.put(HTTPSPDYHeader.VERSION.name(version), httpVersion.toString("UTF-8"));
-    }
-
-    @Override
-    protected void parsedHeader(Buffer name, Buffer value) throws IOException
-    {
-        String headerName = name.toString("UTF-8").toLowerCase(Locale.ENGLISH);
-        String headerValue = value.toString("UTF-8");
-        switch (headerName)
-        {
-            case "host":
-                headers.put(HTTPSPDYHeader.HOST.name(version), headerValue);
-                break;
-            default:
-                headers.put(headerName, headerValue);
-                break;
-        }
-    }
-
-    @Override
-    protected void headerComplete() throws IOException
-    {
-    }
-
-    @Override
-    protected void content(Buffer buffer) throws IOException
-    {
-        if (content == null)
-        {
-            stream = syn(false);
-            content = buffer;
-        }
-        else
-        {
-            stream.getStreamFrameListener().onData(stream, toDataInfo(buffer, false));
-        }
-    }
-
-    @Override
-    public void messageComplete(long contentLength) throws IOException
-    {
-        if (stream == null)
-        {
-            assert content == null;
-            if (headers.isEmpty())
-                proxyEngineSelector.onGoAway(session, new GoAwayInfo(0, SessionStatus.OK));
-            else
-                syn(true);
-        }
-        else
-        {
-            stream.getStreamFrameListener().onData(stream, toDataInfo(content, true));
-        }
-        headers.clear();
-        stream = null;
-        content = null;
-    }
-
-    private HTTPStream syn(boolean close)
-    {
-        HTTPStream stream = new HTTPStream(1, (byte)0, session, null);
-        StreamFrameListener streamFrameListener = proxyEngineSelector.onSyn(stream, new SynInfo(headers, close));
-        stream.setStreamFrameListener(streamFrameListener);
-        return stream;
-    }
-
-    private DataInfo toDataInfo(Buffer buffer, boolean close)
-    {
-        if (buffer instanceof ByteArrayBuffer)
-            return new BytesDataInfo(buffer.array(), buffer.getIndex(), buffer.length(), close);
-
-        if (buffer instanceof NIOBuffer)
-        {
-            ByteBuffer byteBuffer = ((NIOBuffer)buffer).getByteBuffer();
-            byteBuffer.limit(buffer.putIndex());
-            byteBuffer.position(buffer.getIndex());
-            return new ByteBufferDataInfo(byteBuffer, close);
-        }
-
-        return new BytesDataInfo(buffer.asArray(), close);
-    }
-
-    private class HTTPSession extends StandardSession
-    {
-        private HTTPSession(short version, SPDYServerConnector connector)
-        {
-            super(version, connector.getByteBufferPool(), connector.getExecutor(), connector.getScheduler(), null, null, 1, proxyEngineSelector, null, null);
-        }
-
-        @Override
-        public void rst(RstInfo rstInfo, long timeout, TimeUnit unit, Handler<Void> handler)
-        {
-            // Not much we can do in HTTP land: just close the connection
-            goAway(timeout, unit, handler);
-        }
-
-        @Override
-        public void goAway(long timeout, TimeUnit unit, Handler<Void> handler)
-        {
-            try
-            {
-                getEndPoint().close();
-                handler.completed(null);
-            }
-            catch (IOException x)
-            {
-                handler.failed(null, x);
-            }
-        }
-    }
-
-    /**
-     * <p>This stream will convert the SPDY invocations performed by the proxy into HTTP to be sent to the client.</p>
-     */
-    private class HTTPStream extends StandardStream
-    {
-        private final Pattern statusRegexp = Pattern.compile("(\\d{3})\\s*(.*)");
-
-        private HTTPStream(int id, byte priority, ISession session, IStream associatedStream)
-        {
-            super(id, priority, session, associatedStream);
-        }
-
-        @Override
-        public void syn(SynInfo synInfo, long timeout, TimeUnit unit, Handler<Stream> handler)
-        {
-            // HTTP does not support pushed streams
-            handler.completed(new HTTPPushStream(2, getPriority(), getSession(), this));
-        }
-
-        @Override
-        public void headers(HeadersInfo headersInfo, long timeout, TimeUnit unit, Handler<Void> handler)
-        {
-            // TODO
-            throw new UnsupportedOperationException("Not Yet Implemented");
-        }
-
-        @Override
-        public void reply(ReplyInfo replyInfo, long timeout, TimeUnit unit, Handler<Void> handler)
-        {
-            try
-            {
-                Headers headers = new Headers(replyInfo.getHeaders(), false);
-
-                headers.remove(HTTPSPDYHeader.SCHEME.name(version));
-
-                String status = headers.remove(HTTPSPDYHeader.STATUS.name(version)).value();
-                Matcher matcher = statusRegexp.matcher(status);
-                matcher.matches();
-                int code = Integer.parseInt(matcher.group(1));
-                String reason = matcher.group(2);
-                generator.setResponse(code, reason);
-
-                String httpVersion = headers.remove(HTTPSPDYHeader.VERSION.name(version)).value();
-                generator.setVersion(Integer.parseInt(httpVersion.replaceAll("\\D", "")));
-
-                Headers.Header host = headers.remove(HTTPSPDYHeader.HOST.name(version));
-                if (host != null)
-                    headers.put("host", host.value());
-
-                HttpFields fields = new HttpFields();
-                for (Headers.Header header : headers)
-                {
-                    String name = camelize(header.name());
-                    fields.put(name, header.value());
-                }
-                generator.completeHeader(fields, replyInfo.isClose());
-
-                if (replyInfo.isClose())
-                    complete();
-
-                handler.completed(null);
-            }
-            catch (IOException x)
-            {
-                handler.failed(null, x);
-            }
-        }
-
-        private String camelize(String name)
-        {
-            char[] chars = name.toCharArray();
-            chars[0] = Character.toUpperCase(chars[0]);
-
-            for (int i = 0; i < chars.length; ++i)
-            {
-                char c = chars[i];
-                int j = i + 1;
-                if (c == '-' && j < chars.length)
-                    chars[j] = Character.toUpperCase(chars[j]);
-            }
-            return new String(chars);
-        }
-
-        @Override
-        public void data(DataInfo dataInfo, long timeout, TimeUnit unit, Handler<Void> handler)
-        {
-            try
-            {
-                // Data buffer must be copied, as the ByteBuffer is pooled
-                ByteBuffer byteBuffer = dataInfo.asByteBuffer(false);
-
-                Buffer buffer = byteBuffer.isDirect() ?
-                        new DirectNIOBuffer(byteBuffer, false) :
-                        new IndirectNIOBuffer(byteBuffer, false);
-
-                generator.addContent(buffer, dataInfo.isClose());
-                generator.flush(unit.toMillis(timeout));
-
-                if (dataInfo.isClose())
-                    complete();
-
-                handler.completed(null);
-            }
-            catch (IOException x)
-            {
-                handler.failed(null, x);
-            }
-        }
-
-        private void complete() throws IOException
-        {
-            generator.complete();
-            // We need to call asyncDispatch() as if the HTTP request
-            // has been suspended and now we complete the response
-            getEndPoint().asyncDispatch();
-        }
-    }
-
-    private class HTTPPushStream extends StandardStream
-    {
-        private HTTPPushStream(int id, byte priority, ISession session, IStream associatedStream)
-        {
-            super(id, priority, session, associatedStream);
-        }
-
-        @Override
-        public void headers(HeadersInfo headersInfo, long timeout, TimeUnit unit, Handler<Void> handler)
-        {
-            // Ignore pushed headers
-            handler.completed(null);
-        }
-
-        @Override
-        public void data(DataInfo dataInfo, long timeout, TimeUnit unit, Handler<Void> handler)
-        {
-            // Ignore pushed data
-            handler.completed(null);
-        }
-    }
-}
diff --git a/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/proxy/SPDYProxyEngine.java b/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/proxy/SPDYProxyEngine.java
deleted file mode 100644
index fb6cf28..0000000
--- a/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/proxy/SPDYProxyEngine.java
+++ /dev/null
@@ -1,519 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-
-package org.eclipse.jetty.spdy.proxy;
-
-import java.net.InetSocketAddress;
-import java.util.LinkedList;
-import java.util.Queue;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.TimeUnit;
-
-import org.eclipse.jetty.spdy.SPDYClient;
-import org.eclipse.jetty.spdy.api.ByteBufferDataInfo;
-import org.eclipse.jetty.spdy.api.DataInfo;
-import org.eclipse.jetty.spdy.api.GoAwayInfo;
-import org.eclipse.jetty.spdy.api.Handler;
-import org.eclipse.jetty.spdy.api.Headers;
-import org.eclipse.jetty.spdy.api.HeadersInfo;
-import org.eclipse.jetty.spdy.api.ReplyInfo;
-import org.eclipse.jetty.spdy.api.RstInfo;
-import org.eclipse.jetty.spdy.api.SPDY;
-import org.eclipse.jetty.spdy.api.Session;
-import org.eclipse.jetty.spdy.api.SessionFrameListener;
-import org.eclipse.jetty.spdy.api.Stream;
-import org.eclipse.jetty.spdy.api.StreamFrameListener;
-import org.eclipse.jetty.spdy.api.StreamStatus;
-import org.eclipse.jetty.spdy.api.SynInfo;
-import org.eclipse.jetty.spdy.http.HTTPSPDYHeader;
-
-/**
- * <p>{@link SPDYProxyEngine} implements a SPDY to SPDY proxy, that is, converts SPDY events received by
- * clients into SPDY events for the servers.</p>
- */
-public class SPDYProxyEngine extends ProxyEngine implements StreamFrameListener
-{
-    private static final String STREAM_HANDLER_ATTRIBUTE = "org.eclipse.jetty.spdy.http.proxy.streamHandler";
-    private static final String CLIENT_STREAM_ATTRIBUTE = "org.eclipse.jetty.spdy.http.proxy.clientStream";
-
-    private final ConcurrentMap<String, Session> serverSessions = new ConcurrentHashMap<>();
-    private final SessionFrameListener sessionListener = new ProxySessionFrameListener();
-    private final SPDYClient.Factory factory;
-    private volatile long connectTimeout = 15000;
-    private volatile long timeout = 60000;
-
-    public SPDYProxyEngine(SPDYClient.Factory factory)
-    {
-        this.factory = factory;
-    }
-
-    public long getConnectTimeout()
-    {
-        return connectTimeout;
-    }
-
-    public void setConnectTimeout(long connectTimeout)
-    {
-        this.connectTimeout = connectTimeout;
-    }
-
-    public long getTimeout()
-    {
-        return timeout;
-    }
-
-    public void setTimeout(long timeout)
-    {
-        this.timeout = timeout;
-    }
-
-    public StreamFrameListener proxy(final Stream clientStream, SynInfo clientSynInfo, ProxyEngineSelector.ProxyServerInfo proxyServerInfo)
-    {
-        Headers headers = new Headers(clientSynInfo.getHeaders(), false);
-
-        short serverVersion = getVersion(proxyServerInfo.getProtocol());
-        InetSocketAddress address = proxyServerInfo.getAddress();
-        Session serverSession = produceSession(proxyServerInfo.getHost(), serverVersion, address);
-        if (serverSession == null)
-        {
-            rst(clientStream);
-            return null;
-        }
-
-        final Session clientSession = clientStream.getSession();
-
-        addRequestProxyHeaders(clientStream, headers);
-        customizeRequestHeaders(clientStream, headers);
-        convert(clientSession.getVersion(), serverVersion, headers);
-
-        SynInfo serverSynInfo = new SynInfo(headers, clientSynInfo.isClose());
-        StreamFrameListener listener = new ProxyStreamFrameListener(clientStream);
-        StreamHandler handler = new StreamHandler(clientStream, serverSynInfo);
-        clientStream.setAttribute(STREAM_HANDLER_ATTRIBUTE, handler);
-        serverSession.syn(serverSynInfo, listener, timeout, TimeUnit.MILLISECONDS, handler);
-        return this;
-    }
-
-    private static short getVersion(String protocol)
-    {
-        switch (protocol)
-        {
-            case "spdy/2":
-                return SPDY.V2;
-            case "spdy/3":
-                return SPDY.V3;
-            default:
-                throw new IllegalArgumentException("Procotol: " + protocol + " is not a known SPDY protocol");
-        }
-    }
-
-    @Override
-    public void onReply(Stream stream, ReplyInfo replyInfo)
-    {
-        // Servers do not receive replies
-    }
-
-    @Override
-    public void onHeaders(Stream stream, HeadersInfo headersInfo)
-    {
-        // TODO
-        throw new UnsupportedOperationException("Not Yet Implemented");
-    }
-
-    @Override
-    public void onData(Stream clientStream, final DataInfo clientDataInfo)
-    {
-        logger.debug("C -> P {} on {}", clientDataInfo, clientStream);
-
-        ByteBufferDataInfo serverDataInfo = new ByteBufferDataInfo(clientDataInfo.asByteBuffer(false), clientDataInfo.isClose())
-        {
-            @Override
-            public void consume(int delta)
-            {
-                super.consume(delta);
-                clientDataInfo.consume(delta);
-            }
-        };
-
-        StreamHandler streamHandler = (StreamHandler)clientStream.getAttribute(STREAM_HANDLER_ATTRIBUTE);
-        streamHandler.data(serverDataInfo);
-    }
-
-    private Session produceSession(String host, short version, InetSocketAddress address)
-    {
-        try
-        {
-            Session session = serverSessions.get(host);
-            if (session == null)
-            {
-                SPDYClient client = factory.newSPDYClient(version);
-                session = client.connect(address, sessionListener).get(getConnectTimeout(), TimeUnit.MILLISECONDS);
-                logger.debug("Proxy session connected to {}", address);
-                Session existing = serverSessions.putIfAbsent(host, session);
-                if (existing != null)
-                {
-                    session.goAway(getTimeout(), TimeUnit.MILLISECONDS, new Handler.Adapter<Void>());
-                    session = existing;
-                }
-            }
-            return session;
-        }
-        catch (Exception x)
-        {
-            logger.debug(x);
-            return null;
-        }
-    }
-
-    private void convert(short fromVersion, short toVersion, Headers headers)
-    {
-        if (fromVersion != toVersion)
-        {
-            for (HTTPSPDYHeader httpHeader : HTTPSPDYHeader.values())
-            {
-                Headers.Header header = headers.remove(httpHeader.name(fromVersion));
-                if (header != null)
-                {
-                    String toName = httpHeader.name(toVersion);
-                    for (String value : header.values())
-                        headers.add(toName, value);
-                }
-            }
-        }
-    }
-
-    private void rst(Stream stream)
-    {
-        RstInfo rstInfo = new RstInfo(stream.getId(), StreamStatus.REFUSED_STREAM);
-        stream.getSession().rst(rstInfo, getTimeout(), TimeUnit.MILLISECONDS, new Handler.Adapter<Void>());
-    }
-
-    private class ProxyStreamFrameListener extends StreamFrameListener.Adapter
-    {
-        private final Stream clientStream;
-        private volatile ReplyInfo replyInfo;
-
-        public ProxyStreamFrameListener(Stream clientStream)
-        {
-            this.clientStream = clientStream;
-        }
-
-        @Override
-        public void onReply(final Stream stream, ReplyInfo replyInfo)
-        {
-            logger.debug("S -> P {} on {}", replyInfo, stream);
-
-            short serverVersion = stream.getSession().getVersion();
-            Headers headers = new Headers(replyInfo.getHeaders(), false);
-
-            addResponseProxyHeaders(stream, headers);
-            customizeResponseHeaders(stream, headers);
-            short clientVersion = this.clientStream.getSession().getVersion();
-            convert(serverVersion, clientVersion, headers);
-
-            this.replyInfo = new ReplyInfo(headers, replyInfo.isClose());
-            if (replyInfo.isClose())
-                reply(stream);
-        }
-
-        @Override
-        public void onHeaders(Stream stream, HeadersInfo headersInfo)
-        {
-            // TODO
-            throw new UnsupportedOperationException("Not Yet Implemented");
-        }
-
-        @Override
-        public void onData(final Stream stream, final DataInfo dataInfo)
-        {
-            logger.debug("S -> P {} on {}", dataInfo, stream);
-
-            if (replyInfo != null)
-            {
-                if (dataInfo.isClose())
-                    replyInfo.getHeaders().put("content-length", String.valueOf(dataInfo.available()));
-                reply(stream);
-            }
-            data(stream, dataInfo);
-        }
-
-        private void reply(final Stream stream)
-        {
-            final ReplyInfo replyInfo = this.replyInfo;
-            this.replyInfo = null;
-            clientStream.reply(replyInfo, getTimeout(), TimeUnit.MILLISECONDS, new Handler<Void>()
-            {
-                @Override
-                public void completed(Void context)
-                {
-                    logger.debug("P -> C {} from {} to {}", replyInfo, stream, clientStream);
-                }
-
-                @Override
-                public void failed(Void context, Throwable x)
-                {
-                    logger.debug(x);
-                    rst(clientStream);
-                }
-            });
-        }
-
-        private void data(final Stream stream, final DataInfo dataInfo)
-        {
-            clientStream.data(dataInfo, getTimeout(), TimeUnit.MILLISECONDS, new Handler<Void>()
-            {
-                @Override
-                public void completed(Void context)
-                {
-                    dataInfo.consume(dataInfo.length());
-                    logger.debug("P -> C {} from {} to {}", dataInfo, stream, clientStream);
-                }
-
-                @Override
-                public void failed(Void context, Throwable x)
-                {
-                    logger.debug(x);
-                    rst(clientStream);
-                }
-            });
-        }
-    }
-
-    /**
-     * <p>{@link StreamHandler} implements the forwarding of DATA frames from the client to the server.</p>
-     * <p>Instances of this class buffer DATA frames sent by clients and send them to the server.
-     * The buffering happens between the send of the SYN_STREAM to the server (where DATA frames may arrive
-     * from the client before the SYN_STREAM has been fully sent), and between DATA frames, if the client
-     * is a fast producer and the server a slow consumer, or if the client is a SPDY v2 client (and hence
-     * without flow control) while the server is a SPDY v3 server (and hence with flow control).</p>
-     */
-    private class StreamHandler implements Handler<Stream>
-    {
-        private final Queue<DataInfoHandler> queue = new LinkedList<>();
-        private final Stream clientStream;
-        private final SynInfo serverSynInfo;
-        private Stream serverStream;
-
-        private StreamHandler(Stream clientStream, SynInfo serverSynInfo)
-        {
-            this.clientStream = clientStream;
-            this.serverSynInfo = serverSynInfo;
-        }
-
-        @Override
-        public void completed(Stream serverStream)
-        {
-            logger.debug("P -> S {} from {} to {}", serverSynInfo, clientStream, serverStream);
-
-            serverStream.setAttribute(CLIENT_STREAM_ATTRIBUTE, clientStream);
-
-            DataInfoHandler dataInfoHandler;
-            synchronized (queue)
-            {
-                this.serverStream = serverStream;
-                dataInfoHandler = queue.peek();
-                if (dataInfoHandler != null)
-                {
-                    if (dataInfoHandler.flushing)
-                    {
-                        logger.debug("SYN completed, flushing {}, queue size {}", dataInfoHandler.dataInfo, queue.size());
-                        dataInfoHandler = null;
-                    }
-                    else
-                    {
-                        dataInfoHandler.flushing = true;
-                        logger.debug("SYN completed, queue size {}", queue.size());
-                    }
-                }
-                else
-                {
-                    logger.debug("SYN completed, queue empty");
-                }
-            }
-            if (dataInfoHandler != null)
-                flush(serverStream, dataInfoHandler);
-        }
-
-        @Override
-        public void failed(Stream serverStream, Throwable x)
-        {
-            logger.debug(x);
-            rst(clientStream);
-        }
-
-        public void data(DataInfo dataInfo)
-        {
-            Stream serverStream;
-            DataInfoHandler dataInfoHandler = null;
-            DataInfoHandler item = new DataInfoHandler(dataInfo);
-            synchronized (queue)
-            {
-                queue.offer(item);
-                serverStream = this.serverStream;
-                if (serverStream != null)
-                {
-                    dataInfoHandler = queue.peek();
-                    if (dataInfoHandler.flushing)
-                    {
-                        logger.debug("Queued {}, flushing {}, queue size {}", dataInfo, dataInfoHandler.dataInfo, queue.size());
-                        serverStream = null;
-                    }
-                    else
-                    {
-                        dataInfoHandler.flushing = true;
-                        logger.debug("Queued {}, queue size {}", dataInfo, queue.size());
-                    }
-                }
-                else
-                {
-                    logger.debug("Queued {}, SYN incomplete, queue size {}", dataInfo, queue.size());
-                }
-            }
-            if (serverStream != null)
-                flush(serverStream, dataInfoHandler);
-        }
-
-        private void flush(Stream serverStream, DataInfoHandler dataInfoHandler)
-        {
-            logger.debug("P -> S {} on {}", dataInfoHandler.dataInfo, serverStream);
-            serverStream.data(dataInfoHandler.dataInfo, getTimeout(), TimeUnit.MILLISECONDS, dataInfoHandler);
-        }
-
-        private class DataInfoHandler implements Handler<Void>
-        {
-            private final DataInfo dataInfo;
-            private boolean flushing;
-
-            private DataInfoHandler(DataInfo dataInfo)
-            {
-                this.dataInfo = dataInfo;
-            }
-
-            @Override
-            public void completed(Void context)
-            {
-                Stream serverStream;
-                DataInfoHandler dataInfoHandler;
-                synchronized (queue)
-                {
-                    serverStream = StreamHandler.this.serverStream;
-                    assert serverStream != null;
-                    dataInfoHandler = queue.poll();
-                    assert dataInfoHandler == this;
-                    dataInfoHandler = queue.peek();
-                    if (dataInfoHandler != null)
-                    {
-                        assert !dataInfoHandler.flushing;
-                        dataInfoHandler.flushing = true;
-                        logger.debug("Completed {}, queue size {}", dataInfo, queue.size());
-                    }
-                    else
-                    {
-                        logger.debug("Completed {}, queue empty", dataInfo);
-                    }
-                }
-                if (dataInfoHandler != null)
-                    flush(serverStream, dataInfoHandler);
-            }
-
-            @Override
-            public void failed(Void context, Throwable x)
-            {
-                logger.debug(x);
-                rst(clientStream);
-            }
-        }
-    }
-
-    private class ProxySessionFrameListener extends SessionFrameListener.Adapter implements StreamFrameListener
-    {
-        @Override
-        public StreamFrameListener onSyn(Stream serverStream, SynInfo serverSynInfo)
-        {
-            logger.debug("S -> P pushed {} on {}", serverSynInfo, serverStream);
-
-            Headers headers = new Headers(serverSynInfo.getHeaders(), false);
-
-            addResponseProxyHeaders(serverStream, headers);
-            customizeResponseHeaders(serverStream, headers);
-            Stream clientStream = (Stream)serverStream.getAssociatedStream().getAttribute(CLIENT_STREAM_ATTRIBUTE);
-            convert(serverStream.getSession().getVersion(), clientStream.getSession().getVersion(), headers);
-
-            StreamHandler handler = new StreamHandler(clientStream, serverSynInfo);
-            serverStream.setAttribute(STREAM_HANDLER_ATTRIBUTE, handler);
-            clientStream.syn(new SynInfo(headers, serverSynInfo.isClose()), getTimeout(), TimeUnit.MILLISECONDS, handler);
-
-            return this;
-        }
-
-        @Override
-        public void onRst(Session serverSession, RstInfo serverRstInfo)
-        {
-            Stream serverStream = serverSession.getStream(serverRstInfo.getStreamId());
-            if (serverStream != null)
-            {
-                Stream clientStream = (Stream)serverStream.getAttribute(CLIENT_STREAM_ATTRIBUTE);
-                if (clientStream != null)
-                {
-                    Session clientSession = clientStream.getSession();
-                    RstInfo clientRstInfo = new RstInfo(clientStream.getId(), serverRstInfo.getStreamStatus());
-                    clientSession.rst(clientRstInfo, getTimeout(), TimeUnit.MILLISECONDS, new Handler.Adapter<Void>());
-                }
-            }
-        }
-
-        @Override
-        public void onGoAway(Session serverSession, GoAwayInfo goAwayInfo)
-        {
-            serverSessions.values().remove(serverSession);
-        }
-
-        @Override
-        public void onReply(Stream stream, ReplyInfo replyInfo)
-        {
-            // Push streams never send a reply
-        }
-
-        @Override
-        public void onHeaders(Stream stream, HeadersInfo headersInfo)
-        {
-            throw new UnsupportedOperationException(); //TODO
-        }
-
-        @Override
-        public void onData(Stream serverStream, final DataInfo serverDataInfo)
-        {
-            logger.debug("S -> P pushed {} on {}", serverDataInfo, serverStream);
-
-            ByteBufferDataInfo clientDataInfo = new ByteBufferDataInfo(serverDataInfo.asByteBuffer(false), serverDataInfo.isClose())
-            {
-                @Override
-                public void consume(int delta)
-                {
-                    super.consume(delta);
-                    serverDataInfo.consume(delta);
-                }
-            };
-
-            StreamHandler handler = (StreamHandler)serverStream.getAttribute(STREAM_HANDLER_ATTRIBUTE);
-            handler.data(clientDataInfo);
-        }
-    }
-}
diff --git a/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/AbstractHTTPSPDYTest.java b/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/AbstractHTTPSPDYTest.java
deleted file mode 100644
index 39c5a5f..0000000
--- a/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/AbstractHTTPSPDYTest.java
+++ /dev/null
@@ -1,124 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-
-package org.eclipse.jetty.spdy.http;
-
-import java.net.InetSocketAddress;
-import java.util.concurrent.Executor;
-import java.util.concurrent.TimeUnit;
-
-import org.eclipse.jetty.server.Handler;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.spdy.AsyncConnectionFactory;
-import org.eclipse.jetty.spdy.SPDYClient;
-import org.eclipse.jetty.spdy.SPDYServerConnector;
-import org.eclipse.jetty.spdy.api.SPDY;
-import org.eclipse.jetty.spdy.api.Session;
-import org.eclipse.jetty.spdy.api.SessionFrameListener;
-import org.eclipse.jetty.util.thread.QueuedThreadPool;
-import org.junit.After;
-import org.junit.Rule;
-import org.junit.rules.TestWatchman;
-import org.junit.runners.model.FrameworkMethod;
-
-public abstract class AbstractHTTPSPDYTest
-{
-    @Rule
-    public final TestWatchman testName = new TestWatchman()
-    {
-        @Override
-        public void starting(FrameworkMethod method)
-        {
-            super.starting(method);
-            System.err.printf("Running %s.%s()%n",
-                    method.getMethod().getDeclaringClass().getName(),
-                    method.getName());
-        }
-    };
-
-    protected Server server;
-    protected SPDYClient.Factory clientFactory;
-    protected SPDYServerConnector connector;
-
-    protected InetSocketAddress startHTTPServer(Handler handler) throws Exception
-    {
-        return startHTTPServer(SPDY.V2, handler);
-    }
-
-    protected InetSocketAddress startHTTPServer(short version, Handler handler) throws Exception
-    {
-        server = new Server();
-        connector = newHTTPSPDYServerConnector(version);
-        connector.setPort(0);
-        server.addConnector(connector);
-        server.setHandler(handler);
-        server.start();
-        return new InetSocketAddress("localhost", connector.getLocalPort());
-    }
-
-    protected SPDYServerConnector newHTTPSPDYServerConnector(short version)
-    {
-        // For these tests, we need the connector to speak HTTP over SPDY even in non-SSL
-        SPDYServerConnector connector = new HTTPSPDYServerConnector();
-        AsyncConnectionFactory defaultFactory = new ServerHTTPSPDYAsyncConnectionFactory(version, connector.getByteBufferPool(), connector.getExecutor(), connector.getScheduler(), connector, new PushStrategy.None());
-        connector.setDefaultAsyncConnectionFactory(defaultFactory);
-        return connector;
-    }
-
-    protected Session startClient(InetSocketAddress socketAddress, SessionFrameListener listener) throws Exception
-    {
-        return startClient(SPDY.V2, socketAddress, listener);
-    }
-
-    protected Session startClient(short version, InetSocketAddress socketAddress, SessionFrameListener listener) throws Exception
-    {
-        if (clientFactory == null)
-        {
-            QueuedThreadPool threadPool = new QueuedThreadPool();
-            threadPool.setName(threadPool.getName() + "-client");
-            clientFactory = newSPDYClientFactory(threadPool);
-            clientFactory.start();
-        }
-        return clientFactory.newSPDYClient(version).connect(socketAddress, listener).get(5, TimeUnit.SECONDS);
-    }
-
-    protected SPDYClient.Factory newSPDYClientFactory(Executor threadPool)
-    {
-        return new SPDYClient.Factory(threadPool);
-    }
-
-    @After
-    public void destroy() throws Exception
-    {
-        if (clientFactory != null)
-        {
-            clientFactory.stop();
-        }
-        if (server != null)
-        {
-            server.stop();
-            server.join();
-        }
-    }
-
-    protected short version()
-    {
-        return SPDY.V2;
-    }
-}
diff --git a/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/ConcurrentStreamsTest.java b/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/ConcurrentStreamsTest.java
deleted file mode 100644
index 20e7c19..0000000
--- a/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/ConcurrentStreamsTest.java
+++ /dev/null
@@ -1,118 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-
-package org.eclipse.jetty.spdy.http;
-
-import java.io.IOException;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jetty.server.Request;
-import org.eclipse.jetty.server.handler.AbstractHandler;
-import org.eclipse.jetty.spdy.api.Headers;
-import org.eclipse.jetty.spdy.api.ReplyInfo;
-import org.eclipse.jetty.spdy.api.Session;
-import org.eclipse.jetty.spdy.api.Stream;
-import org.eclipse.jetty.spdy.api.StreamFrameListener;
-import org.eclipse.jetty.spdy.api.SynInfo;
-import org.junit.Assert;
-import org.junit.Test;
-
-public class ConcurrentStreamsTest extends AbstractHTTPSPDYTest
-{
-    @Test
-    public void testSlowStreamDoesNotBlockOtherStreams() throws Exception
-    {
-        final CountDownLatch slowServerLatch = new CountDownLatch(1);
-        final CountDownLatch fastServerLatch = new CountDownLatch(1);
-        Session session = startClient(startHTTPServer(new AbstractHandler()
-        {
-            @Override
-            public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
-                    throws IOException, ServletException
-            {
-                try
-                {
-                    request.setHandled(true);
-                    switch (target)
-                    {
-                        case "/slow":
-                            Assert.assertTrue(fastServerLatch.await(10, TimeUnit.SECONDS));
-                            slowServerLatch.countDown();
-                            break;
-                        case "/fast":
-                            fastServerLatch.countDown();
-                            break;
-                        default:
-                            Assert.fail();
-                            break;
-                    }
-                }
-                catch (InterruptedException x)
-                {
-                    throw new ServletException(x);
-                }
-            }
-        }), null);
-
-        // Perform slow request. This will wait on server side until the fast request wakes it up
-        Headers headers = new Headers();
-        headers.put(HTTPSPDYHeader.METHOD.name(version()), "GET");
-        headers.put(HTTPSPDYHeader.URI.name(version()), "/slow");
-        headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1");
-        headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort());
-        final CountDownLatch slowClientLatch = new CountDownLatch(1);
-        session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter()
-        {
-            @Override
-            public void onReply(Stream stream, ReplyInfo replyInfo)
-            {
-                Headers replyHeaders = replyInfo.getHeaders();
-                Assert.assertTrue(replyHeaders.get("status").value().contains("200"));
-                slowClientLatch.countDown();
-            }
-        });
-
-        // Perform the fast request. This will wake up the slow request
-        headers.clear();
-        headers.put(HTTPSPDYHeader.METHOD.name(version()), "GET");
-        headers.put(HTTPSPDYHeader.URI.name(version()), "/fast");
-        headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1");
-        headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort());
-        final CountDownLatch fastClientLatch = new CountDownLatch(1);
-        session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter()
-        {
-            @Override
-            public void onReply(Stream stream, ReplyInfo replyInfo)
-            {
-                Headers replyHeaders = replyInfo.getHeaders();
-                Assert.assertTrue(replyHeaders.get("status").value().contains("200"));
-                fastClientLatch.countDown();
-            }
-        });
-
-        Assert.assertTrue(fastServerLatch.await(5, TimeUnit.SECONDS));
-        Assert.assertTrue(slowServerLatch.await(5, TimeUnit.SECONDS));
-        Assert.assertTrue(fastClientLatch.await(5, TimeUnit.SECONDS));
-        Assert.assertTrue(slowClientLatch.await(5, TimeUnit.SECONDS));
-    }
-}
diff --git a/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/ProtocolNegotiationTest.java b/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/ProtocolNegotiationTest.java
deleted file mode 100644
index d12f741..0000000
--- a/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/ProtocolNegotiationTest.java
+++ /dev/null
@@ -1,254 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-
-package org.eclipse.jetty.spdy.http;
-
-import java.io.BufferedReader;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.net.InetSocketAddress;
-import java.util.List;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLSocket;
-
-import org.eclipse.jetty.npn.NextProtoNego;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.spdy.SPDYServerConnector;
-import org.eclipse.jetty.util.ssl.SslContextFactory;
-import org.junit.Assert;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TestWatchman;
-import org.junit.runners.model.FrameworkMethod;
-
-public class ProtocolNegotiationTest
-{
-    @Rule
-    public final TestWatchman testName = new TestWatchman()
-    {
-        @Override
-        public void starting(FrameworkMethod method)
-        {
-            super.starting(method);
-            System.err.printf("Running %s.%s()%n",
-                    method.getMethod().getDeclaringClass().getName(),
-                    method.getName());
-        }
-    };
-
-    protected Server server;
-    protected SPDYServerConnector connector;
-
-    protected InetSocketAddress startServer(SPDYServerConnector connector) throws Exception
-    {
-        server = new Server();
-        if (connector == null)
-            connector = new SPDYServerConnector(null, newSslContextFactory());
-        connector.setPort(0);
-        this.connector = connector;
-        server.addConnector(connector);
-        server.start();
-        return new InetSocketAddress("localhost", connector.getLocalPort());
-    }
-
-    protected SslContextFactory newSslContextFactory()
-    {
-        SslContextFactory sslContextFactory = new SslContextFactory();
-        sslContextFactory.setKeyStorePath("src/test/resources/keystore.jks");
-        sslContextFactory.setKeyStorePassword("storepwd");
-        sslContextFactory.setTrustStore("src/test/resources/truststore.jks");
-        sslContextFactory.setTrustStorePassword("storepwd");
-        sslContextFactory.setProtocol("TLSv1");
-        sslContextFactory.setIncludeProtocols("TLSv1");
-        return sslContextFactory;
-    }
-
-    @Test
-    public void testServerAdvertisingHTTPSpeaksHTTP() throws Exception
-    {
-        InetSocketAddress address = startServer(null);
-        connector.removeAsyncConnectionFactory("spdy/2");
-        connector.putAsyncConnectionFactory("http/1.1", new ServerHTTPAsyncConnectionFactory(connector));
-
-        SslContextFactory sslContextFactory = newSslContextFactory();
-        sslContextFactory.start();
-        SSLContext sslContext = sslContextFactory.getSslContext();
-        SSLSocket client = (SSLSocket)sslContext.getSocketFactory().createSocket(address.getAddress(), address.getPort());
-        client.setUseClientMode(true);
-        client.setSoTimeout(5000);
-
-        NextProtoNego.put(client, new NextProtoNego.ClientProvider()
-        {
-            @Override
-            public boolean supports()
-            {
-                return true;
-            }
-
-            @Override
-            public void unsupported()
-            {
-            }
-
-            @Override
-            public String selectProtocol(List<String> strings)
-            {
-                Assert.assertNotNull(strings);
-                String protocol = "http/1.1";
-                Assert.assertTrue(strings.contains(protocol));
-                return protocol;
-            }
-        });
-
-        client.startHandshake();
-
-        // Verify that the server really speaks http/1.1
-
-        OutputStream output = client.getOutputStream();
-        output.write(("" +
-                "GET / HTTP/1.1\r\n" +
-                "Host: localhost:" + address.getPort() + "\r\n" +
-                "\r\n" +
-                "").getBytes("UTF-8"));
-        output.flush();
-
-        InputStream input = client.getInputStream();
-        BufferedReader reader = new BufferedReader(new InputStreamReader(input, "UTF-8"));
-        String line = reader.readLine();
-        Assert.assertTrue(line.contains(" 404 "));
-
-        client.close();
-    }
-
-    @Test
-    public void testServerAdvertisingSPDYAndHTTPSpeaksHTTPWhenNegotiated() throws Exception
-    {
-        InetSocketAddress address = startServer(null);
-        connector.putAsyncConnectionFactory("http/1.1", new ServerHTTPAsyncConnectionFactory(connector));
-
-        SslContextFactory sslContextFactory = newSslContextFactory();
-        sslContextFactory.start();
-        SSLContext sslContext = sslContextFactory.getSslContext();
-        SSLSocket client = (SSLSocket)sslContext.getSocketFactory().createSocket(address.getAddress(), address.getPort());
-        client.setUseClientMode(true);
-        client.setSoTimeout(5000);
-
-        NextProtoNego.put(client, new NextProtoNego.ClientProvider()
-        {
-            @Override
-            public boolean supports()
-            {
-                return true;
-            }
-
-            @Override
-            public void unsupported()
-            {
-            }
-
-            @Override
-            public String selectProtocol(List<String> strings)
-            {
-                Assert.assertNotNull(strings);
-                String spdyProtocol = "spdy/2";
-                Assert.assertTrue(strings.contains(spdyProtocol));
-                String httpProtocol = "http/1.1";
-                Assert.assertTrue(strings.contains(httpProtocol));
-                Assert.assertTrue(strings.indexOf(spdyProtocol) < strings.indexOf(httpProtocol));
-                return httpProtocol;
-            }
-        });
-
-        client.startHandshake();
-
-        // Verify that the server really speaks http/1.1
-
-        OutputStream output = client.getOutputStream();
-        output.write(("" +
-                "GET / HTTP/1.1\r\n" +
-                "Host: localhost:" + address.getPort() + "\r\n" +
-                "\r\n" +
-                "").getBytes("UTF-8"));
-        output.flush();
-
-        InputStream input = client.getInputStream();
-        BufferedReader reader = new BufferedReader(new InputStreamReader(input, "UTF-8"));
-        String line = reader.readLine();
-        Assert.assertTrue(line.contains(" 404 "));
-
-        client.close();
-    }
-
-    @Test
-    public void testServerAdvertisingSPDYAndHTTPSpeaksDefaultProtocolWhenNPNMissing() throws Exception
-    {
-        SPDYServerConnector connector = new SPDYServerConnector(null, newSslContextFactory());
-        connector.setDefaultAsyncConnectionFactory(new ServerHTTPAsyncConnectionFactory(connector));
-        InetSocketAddress address = startServer(connector);
-        connector.putAsyncConnectionFactory("http/1.1", new ServerHTTPAsyncConnectionFactory(connector));
-
-        SslContextFactory sslContextFactory = newSslContextFactory();
-        sslContextFactory.start();
-        SSLContext sslContext = sslContextFactory.getSslContext();
-        SSLSocket client = (SSLSocket)sslContext.getSocketFactory().createSocket(address.getAddress(), address.getPort());
-        client.setUseClientMode(true);
-        client.setSoTimeout(5000);
-
-        NextProtoNego.put(client, new NextProtoNego.ClientProvider()
-        {
-            @Override
-            public boolean supports()
-            {
-                return false;
-            }
-
-            @Override
-            public void unsupported()
-            {
-            }
-
-            @Override
-            public String selectProtocol(List<String> strings)
-            {
-                return null;
-            }
-        });
-
-        client.startHandshake();
-
-        // Verify that the server really speaks http/1.1
-
-        OutputStream output = client.getOutputStream();
-        output.write(("" +
-                "GET / HTTP/1.1\r\n" +
-                "Host: localhost:" + address.getPort() + "\r\n" +
-                "\r\n" +
-                "").getBytes("UTF-8"));
-        output.flush();
-
-        InputStream input = client.getInputStream();
-        BufferedReader reader = new BufferedReader(new InputStreamReader(input, "UTF-8"));
-        String line = reader.readLine();
-        Assert.assertTrue(line.contains(" 404 "));
-
-        client.close();
-    }
-
-}
diff --git a/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/PushStrategyBenchmarkTest.java b/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/PushStrategyBenchmarkTest.java
deleted file mode 100644
index 7228fa9..0000000
--- a/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/PushStrategyBenchmarkTest.java
+++ /dev/null
@@ -1,400 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-
-package org.eclipse.jetty.spdy.http;
-
-import java.io.IOException;
-import java.net.InetSocketAddress;
-import java.util.Collections;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import junit.framework.Assert;
-import org.eclipse.jetty.client.Address;
-import org.eclipse.jetty.client.ContentExchange;
-import org.eclipse.jetty.client.HttpClient;
-import org.eclipse.jetty.client.HttpExchange;
-import org.eclipse.jetty.server.Request;
-import org.eclipse.jetty.server.handler.AbstractHandler;
-import org.eclipse.jetty.spdy.AsyncConnectionFactory;
-import org.eclipse.jetty.spdy.api.DataInfo;
-import org.eclipse.jetty.spdy.api.Headers;
-import org.eclipse.jetty.spdy.api.SPDY;
-import org.eclipse.jetty.spdy.api.Session;
-import org.eclipse.jetty.spdy.api.SessionFrameListener;
-import org.eclipse.jetty.spdy.api.Stream;
-import org.eclipse.jetty.spdy.api.StreamFrameListener;
-import org.eclipse.jetty.spdy.api.SynInfo;
-import org.junit.Test;
-
-public class PushStrategyBenchmarkTest extends AbstractHTTPSPDYTest
-{
-    // Sample resources size from webtide.com home page
-    private final int[] htmlResources = new int[]
-            {8 * 1024};
-    private final int[] cssResources = new int[]
-            {12 * 1024, 2 * 1024};
-    private final int[] jsResources = new int[]
-            {75 * 1024, 24 * 1024, 36 * 1024};
-    private final int[] pngResources = new int[]
-            {1024, 45 * 1024, 6 * 1024, 2 * 1024, 2 * 1024, 2 * 1024, 3 * 1024, 512, 512, 19 * 1024, 512, 128, 32};
-    private final Set<String> pushedResources = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>());
-    private final AtomicReference<CountDownLatch> latch = new AtomicReference<>();
-    private final long roundtrip = 100;
-    private final int runs = 10;
-
-    @Test
-    public void benchmarkPushStrategy() throws Exception
-    {
-        InetSocketAddress address = startHTTPServer(version(), new PushStrategyBenchmarkHandler());
-
-        // Plain HTTP
-        AsyncConnectionFactory dacf = new ServerHTTPAsyncConnectionFactory(connector);
-        connector.setDefaultAsyncConnectionFactory(dacf);
-        HttpClient httpClient = new HttpClient();
-        // Simulate browsers, that open 6 connection per origin
-        httpClient.setMaxConnectionsPerAddress(6);
-        httpClient.start();
-        benchmarkHTTP(httpClient);
-        httpClient.stop();
-
-        // First push strategy
-        PushStrategy pushStrategy = new PushStrategy.None();
-        dacf = new ServerHTTPSPDYAsyncConnectionFactory(version(), connector.getByteBufferPool(), connector.getExecutor(), connector.getScheduler(), connector, pushStrategy);
-        connector.setDefaultAsyncConnectionFactory(dacf);
-        Session session = startClient(version(), address, new ClientSessionFrameListener());
-        benchmarkSPDY(pushStrategy, session);
-        session.goAway().get(5, TimeUnit.SECONDS);
-
-        // Second push strategy
-        pushStrategy = new ReferrerPushStrategy();
-        dacf = new ServerHTTPSPDYAsyncConnectionFactory(version(), connector.getByteBufferPool(), connector.getExecutor(), connector.getScheduler(), connector, pushStrategy);
-        connector.setDefaultAsyncConnectionFactory(dacf);
-        session = startClient(version(), address, new ClientSessionFrameListener());
-        benchmarkSPDY(pushStrategy, session);
-        session.goAway().get(5, TimeUnit.SECONDS);
-    }
-
-    private void benchmarkHTTP(HttpClient httpClient) throws Exception
-    {
-        // Warm up
-        performHTTPRequests(httpClient);
-        performHTTPRequests(httpClient);
-
-        long total = 0;
-        for (int i = 0; i < runs; ++i)
-        {
-            long begin = System.nanoTime();
-            int requests = performHTTPRequests(httpClient);
-            long elapsed = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - begin);
-            total += elapsed;
-            System.err.printf("HTTP: run %d, %d request(s), roundtrip delay %d ms, elapsed = %d%n",
-                    i, requests, roundtrip, elapsed);
-        }
-        System.err.printf("HTTP: roundtrip delay %d ms, average = %d%n%n",
-                roundtrip, total / runs);
-    }
-
-    private int performHTTPRequests(HttpClient httpClient) throws Exception
-    {
-        int result = 0;
-
-        for (int j = 0; j < htmlResources.length; ++j)
-        {
-            latch.set(new CountDownLatch(cssResources.length + jsResources.length + pngResources.length));
-
-            String primaryPath = "/" + j + ".html";
-            String referrer = new StringBuilder("http://localhost:").append(connector.getLocalPort()).append(primaryPath).toString();
-            ContentExchange exchange = new ContentExchange(true);
-            exchange.setMethod("GET");
-            exchange.setRequestURI(primaryPath);
-            exchange.setVersion("HTTP/1.1");
-            exchange.setAddress(new Address("localhost", connector.getLocalPort()));
-            exchange.setRequestHeader("Host", "localhost:" + connector.getLocalPort());
-            ++result;
-            httpClient.send(exchange);
-            Assert.assertEquals(HttpExchange.STATUS_COMPLETED, exchange.waitForDone());
-            Assert.assertEquals(200, exchange.getResponseStatus());
-
-            for (int i = 0; i < cssResources.length; ++i)
-            {
-                String path = "/" + i + ".css";
-                exchange = createExchangeWithReferrer(referrer, path);
-                ++result;
-                httpClient.send(exchange);
-            }
-            for (int i = 0; i < jsResources.length; ++i)
-            {
-                String path = "/" + i + ".js";
-                exchange = createExchangeWithReferrer(referrer, path);
-                ++result;
-                httpClient.send(exchange);
-            }
-            for (int i = 0; i < pngResources.length; ++i)
-            {
-                String path = "/" + i + ".png";
-                exchange = createExchangeWithReferrer(referrer, path);
-                ++result;
-                httpClient.send(exchange);
-            }
-
-            Assert.assertTrue(latch.get().await(5, TimeUnit.SECONDS));
-        }
-
-        return result;
-    }
-
-    private ContentExchange createExchangeWithReferrer(String referrer, String path)
-    {
-        ContentExchange exchange;
-        exchange = new TestExchange();
-        exchange.setMethod("GET");
-        exchange.setRequestURI(path);
-        exchange.setVersion("HTTP/1.1");
-        exchange.setAddress(new Address("localhost", connector.getLocalPort()));
-        exchange.setRequestHeader("Host", "localhost:" + connector.getLocalPort());
-        exchange.setRequestHeader("referer", referrer);
-        return exchange;
-    }
-
-
-    private void benchmarkSPDY(PushStrategy pushStrategy, Session session) throws Exception
-    {
-        // Warm up PushStrategy
-        performRequests(session);
-        performRequests(session);
-
-        long total = 0;
-        for (int i = 0; i < runs; ++i)
-        {
-            long begin = System.nanoTime();
-            int requests = performRequests(session);
-            long elapsed = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - begin);
-            total += elapsed;
-            System.err.printf("SPDY(%s): run %d, %d request(s), roundtrip delay %d ms, elapsed = %d%n",
-                    pushStrategy.getClass().getSimpleName(), i, requests, roundtrip, elapsed);
-        }
-        System.err.printf("SPDY(%s): roundtrip delay %d ms, average = %d%n%n",
-                pushStrategy.getClass().getSimpleName(), roundtrip, total / runs);
-    }
-
-    private int performRequests(Session session) throws Exception
-    {
-        int result = 0;
-
-        for (int j = 0; j < htmlResources.length; ++j)
-        {
-            latch.set(new CountDownLatch(cssResources.length + jsResources.length + pngResources.length));
-            pushedResources.clear();
-
-            String primaryPath = "/" + j + ".html";
-            String referrer = new StringBuilder("http://localhost:").append(connector.getLocalPort()).append(primaryPath).toString();
-            Headers headers = new Headers();
-            headers.put(HTTPSPDYHeader.METHOD.name(version()), "GET");
-            headers.put(HTTPSPDYHeader.URI.name(version()), primaryPath);
-            headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1");
-            headers.put(HTTPSPDYHeader.SCHEME.name(version()), "http");
-            headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort());
-            // Wait for the HTML to simulate browser's behavior
-            ++result;
-            final CountDownLatch htmlLatch = new CountDownLatch(1);
-            session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter()
-            {
-                @Override
-                public void onData(Stream stream, DataInfo dataInfo)
-                {
-                    dataInfo.consume(dataInfo.length());
-                    if (dataInfo.isClose())
-                        htmlLatch.countDown();
-                }
-            });
-            Assert.assertTrue(htmlLatch.await(5, TimeUnit.SECONDS));
-
-            for (int i = 0; i < cssResources.length; ++i)
-            {
-                String path = "/" + i + ".css";
-                if (pushedResources.contains(path))
-                    continue;
-                headers = createRequestHeaders(referrer, path);
-                ++result;
-                session.syn(new SynInfo(headers, true), new DataListener());
-            }
-            for (int i = 0; i < jsResources.length; ++i)
-            {
-                String path = "/" + i + ".js";
-                if (pushedResources.contains(path))
-                    continue;
-                headers = createRequestHeaders(referrer, path);
-                ++result;
-                session.syn(new SynInfo(headers, true), new DataListener());
-            }
-            for (int i = 0; i < pngResources.length; ++i)
-            {
-                String path = "/" + i + ".png";
-                if (pushedResources.contains(path))
-                    continue;
-                headers = createRequestHeaders(referrer, path);
-                ++result;
-                session.syn(new SynInfo(headers, true), new DataListener());
-            }
-
-            Assert.assertTrue(latch.get().await(5, TimeUnit.SECONDS));
-        }
-
-        return result;
-    }
-
-    private Headers createRequestHeaders(String referrer, String path)
-    {
-        Headers headers;
-        headers = new Headers();
-        headers.put(HTTPSPDYHeader.METHOD.name(version()), "GET");
-        headers.put(HTTPSPDYHeader.URI.name(version()), path);
-        headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1");
-        headers.put(HTTPSPDYHeader.SCHEME.name(version()), "http");
-        headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort());
-        headers.put("referer", referrer);
-        return headers;
-    }
-
-    private void sleep(long delay) throws ServletException
-    {
-        try
-        {
-            TimeUnit.MILLISECONDS.sleep(delay);
-        }
-        catch (InterruptedException x)
-        {
-            throw new ServletException(x);
-        }
-    }
-
-    private class PushStrategyBenchmarkHandler extends AbstractHandler
-    {
-        @Override
-        public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
-        {
-            baseRequest.setHandled(true);
-
-            // Sleep half of the roundtrip time, to simulate the delay of responses, even for pushed resources
-            sleep(roundtrip / 2);
-            // If it's not a pushed resource, sleep half of the roundtrip time, to simulate the delay of requests
-            if (request.getHeader("x-spdy-push") == null)
-                sleep(roundtrip / 2);
-
-            String suffix = target.substring(target.indexOf('.') + 1);
-            int index = Integer.parseInt(target.substring(1, target.length() - suffix.length() - 1));
-
-            int contentLength;
-            String contentType;
-            switch (suffix)
-            {
-                case "html":
-                    contentLength = htmlResources[index];
-                    contentType = "text/html";
-                    break;
-                case "css":
-                    contentLength = cssResources[index];
-                    contentType = "text/css";
-                    break;
-                case "js":
-                    contentLength = jsResources[index];
-                    contentType = "text/javascript";
-                    break;
-                case "png":
-                    contentLength = pngResources[index];
-                    contentType = "image/png";
-                    break;
-                default:
-                    throw new ServletException();
-            }
-
-            response.setContentType(contentType);
-            response.setContentLength(contentLength);
-            response.getOutputStream().write(new byte[contentLength]);
-        }
-    }
-
-    private void addPushedResource(String pushedURI)
-    {
-        switch (version())
-        {
-            case SPDY.V2:
-            {
-                Matcher matcher = Pattern.compile("https?://[^:]+:\\d+(/.*)").matcher(pushedURI);
-                Assert.assertTrue(matcher.matches());
-                pushedResources.add(matcher.group(1));
-                break;
-            }
-            case SPDY.V3:
-            {
-                pushedResources.add(pushedURI);
-                break;
-            }
-            default:
-            {
-                throw new IllegalStateException();
-            }
-        }
-    }
-
-    private class ClientSessionFrameListener extends SessionFrameListener.Adapter
-    {
-        @Override
-        public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
-        {
-            String path = synInfo.getHeaders().get(HTTPSPDYHeader.URI.name(version())).value();
-            addPushedResource(path);
-            return new DataListener();
-        }
-    }
-
-    private class DataListener extends StreamFrameListener.Adapter
-    {
-        @Override
-        public void onData(Stream stream, DataInfo dataInfo)
-        {
-            dataInfo.consume(dataInfo.length());
-            if (dataInfo.isClose())
-                latch.get().countDown();
-        }
-    }
-
-    private class TestExchange extends ContentExchange
-    {
-        private TestExchange()
-        {
-            super(true);
-        }
-
-        @Override
-        protected void onResponseComplete() throws IOException
-        {
-            latch.get().countDown();
-        }
-    }
-}
diff --git a/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/ReferrerPushStrategyUnitTest.java b/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/ReferrerPushStrategyUnitTest.java
deleted file mode 100644
index 060515e..0000000
--- a/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/ReferrerPushStrategyUnitTest.java
+++ /dev/null
@@ -1,124 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.spdy.http;
-
-import java.util.Set;
-
-import org.eclipse.jetty.spdy.api.Headers;
-import org.eclipse.jetty.spdy.api.SPDY;
-import org.eclipse.jetty.spdy.api.Session;
-import org.eclipse.jetty.spdy.api.Stream;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.runners.MockitoJUnitRunner;
-
-import static org.hamcrest.CoreMatchers.is;
-import static org.junit.Assert.assertThat;
-import static org.mockito.Mockito.when;
-
- at RunWith(MockitoJUnitRunner.class)
-public class ReferrerPushStrategyUnitTest
-{
-    public static final short VERSION = SPDY.V3;
-    public static final String SCHEME = "http";
-    public static final String HOST = "localhost";
-    public static final String MAIN_URI = "/index.html";
-    public static final String METHOD = "GET";
-
-    // class under test
-    private ReferrerPushStrategy referrerPushStrategy;
-
-    @Mock
-    Stream stream;
-    @Mock
-    Session session;
-
-
-    @Before
-    public void setup()
-    {
-        referrerPushStrategy = new ReferrerPushStrategy();
-    }
-
-    @Test
-    public void testReferrerCallsAfterTimeoutAreNotAddedAsPushResources() throws InterruptedException
-    {
-        Headers requestHeaders = getBaseHeaders(VERSION);
-        int referrerCallTimeout = 1000;
-        referrerPushStrategy.setReferrerPushPeriod(referrerCallTimeout);
-        setMockExpectations();
-
-        String referrerUrl = fillPushStrategyCache(requestHeaders);
-        Set<String> pushResources;
-
-        // sleep to pretend that the user manually clicked on a linked resource instead the browser requesting subresources immediately
-        Thread.sleep(referrerCallTimeout + 1);
-
-        requestHeaders.put(HTTPSPDYHeader.URI.name(VERSION), "image2.jpg");
-        requestHeaders.put("referer", referrerUrl);
-        pushResources = referrerPushStrategy.apply(stream, requestHeaders, new Headers());
-        assertThat("pushResources is empty", pushResources.size(), is(0));
-
-        requestHeaders.put(HTTPSPDYHeader.URI.name(VERSION), MAIN_URI);
-        pushResources = referrerPushStrategy.apply(stream, requestHeaders, new Headers());
-        // as the image2.jpg request has been a link and not a subresource, we expect that pushResources.size() is still 2
-        assertThat("pushResources contains two elements image.jpg and style.css", pushResources.size(), is(2));
-    }
-
-    private Headers getBaseHeaders(short version)
-    {
-        Headers requestHeaders = new Headers();
-        requestHeaders.put(HTTPSPDYHeader.SCHEME.name(version), SCHEME);
-        requestHeaders.put(HTTPSPDYHeader.HOST.name(version), HOST);
-        requestHeaders.put(HTTPSPDYHeader.URI.name(version), MAIN_URI);
-        requestHeaders.put(HTTPSPDYHeader.METHOD.name(version), METHOD);
-        return requestHeaders;
-    }
-
-    private void setMockExpectations()
-    {
-        when(stream.getSession()).thenReturn(session);
-        when(session.getVersion()).thenReturn(VERSION);
-    }
-
-    private String fillPushStrategyCache(Headers requestHeaders)
-    {
-        Set<String> pushResources = referrerPushStrategy.apply(stream, requestHeaders, new Headers());
-        assertThat("pushResources is empty", pushResources.size(), is(0));
-
-        String origin = SCHEME + "://" + HOST;
-        String referrerUrl = origin + MAIN_URI;
-
-        requestHeaders.put(HTTPSPDYHeader.URI.name(VERSION), "image.jpg");
-        requestHeaders.put("referer", referrerUrl);
-        pushResources = referrerPushStrategy.apply(stream, requestHeaders, new Headers());
-        assertThat("pushResources is empty", pushResources.size(), is(0));
-
-        requestHeaders.put(HTTPSPDYHeader.URI.name(VERSION), "style.css");
-        pushResources = referrerPushStrategy.apply(stream, requestHeaders, new Headers());
-        assertThat("pushResources is empty", pushResources.size(), is(0));
-
-        requestHeaders.put(HTTPSPDYHeader.URI.name(VERSION), MAIN_URI);
-        pushResources = referrerPushStrategy.apply(stream, requestHeaders, new Headers());
-        assertThat("pushResources contains two elements image.jpg and style.css", pushResources.size(), is(2));
-        return referrerUrl;
-    }
-}
diff --git a/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/ReferrerPushStrategyV2Test.java b/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/ReferrerPushStrategyV2Test.java
deleted file mode 100644
index c1e3309..0000000
--- a/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/ReferrerPushStrategyV2Test.java
+++ /dev/null
@@ -1,802 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-
-package org.eclipse.jetty.spdy.http;
-
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.net.InetSocketAddress;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jetty.server.Request;
-import org.eclipse.jetty.server.handler.AbstractHandler;
-import org.eclipse.jetty.spdy.AsyncConnectionFactory;
-import org.eclipse.jetty.spdy.SPDYServerConnector;
-import org.eclipse.jetty.spdy.api.DataInfo;
-import org.eclipse.jetty.spdy.api.Headers;
-import org.eclipse.jetty.spdy.api.ReplyInfo;
-import org.eclipse.jetty.spdy.api.SPDY;
-import org.eclipse.jetty.spdy.api.Session;
-import org.eclipse.jetty.spdy.api.SessionFrameListener;
-import org.eclipse.jetty.spdy.api.Stream;
-import org.eclipse.jetty.spdy.api.StreamFrameListener;
-import org.eclipse.jetty.spdy.api.SynInfo;
-import org.junit.Assert;
-import org.junit.Test;
-
-public class ReferrerPushStrategyV2Test extends AbstractHTTPSPDYTest
-{
-
-    private final String mainResource = "/index.html";
-    private final String cssResource = "/style.css";
-
-    @Override
-    protected SPDYServerConnector newHTTPSPDYServerConnector(short version)
-    {
-        SPDYServerConnector connector = super.newHTTPSPDYServerConnector(version);
-        AsyncConnectionFactory defaultFactory = new ServerHTTPSPDYAsyncConnectionFactory(version, connector.getByteBufferPool(), connector.getExecutor(), connector.getScheduler(), connector, new ReferrerPushStrategy());
-        connector.setDefaultAsyncConnectionFactory(defaultFactory);
-        return connector;
-    }
-
-    @Test
-    public void testPushHeadersAreValid() throws Exception
-    {
-        InetSocketAddress address = createServer();
-
-        ReferrerPushStrategy pushStrategy = new ReferrerPushStrategy();
-        int referrerPushPeriod = 1000;
-        pushStrategy.setReferrerPushPeriod(referrerPushPeriod);
-        AsyncConnectionFactory defaultFactory = new ServerHTTPSPDYAsyncConnectionFactory(version(), connector.getByteBufferPool(), connector.getExecutor(), connector.getScheduler(), connector, pushStrategy);
-        connector.setDefaultAsyncConnectionFactory(defaultFactory);
-
-        Headers mainRequestHeaders = createHeadersWithoutReferrer(mainResource);
-        Session session1 = sendMainRequestAndCSSRequest(address, mainRequestHeaders);
-
-        // Sleep for pushPeriod This should prevent application.js from being mapped as pushResource
-        Thread.sleep(referrerPushPeriod + 1);
-
-        sendJSRequest(session1);
-
-        run2ndClientRequests(address, mainRequestHeaders, true);
-    }
-
-    @Test
-    public void testReferrerPushPeriod() throws Exception
-    {
-        InetSocketAddress address = createServer();
-
-        ReferrerPushStrategy pushStrategy = new ReferrerPushStrategy();
-        int referrerPushPeriod = 1000;
-        pushStrategy.setReferrerPushPeriod(referrerPushPeriod);
-        AsyncConnectionFactory defaultFactory = new ServerHTTPSPDYAsyncConnectionFactory(version(), connector.getByteBufferPool(), connector.getExecutor(), connector.getScheduler(), connector, pushStrategy);
-        connector.setDefaultAsyncConnectionFactory(defaultFactory);
-
-        Headers mainRequestHeaders = createHeadersWithoutReferrer(mainResource);
-        Session session1 = sendMainRequestAndCSSRequest(address, mainRequestHeaders);
-
-        // Sleep for pushPeriod This should prevent application.js from being mapped as pushResource
-        Thread.sleep(referrerPushPeriod+1);
-
-        sendJSRequest(session1);
-
-        run2ndClientRequests(address, mainRequestHeaders, false);
-    }
-
-    @Test
-    public void testMaxAssociatedResources() throws Exception
-    {
-        InetSocketAddress address = createServer();
-
-        ReferrerPushStrategy pushStrategy = new ReferrerPushStrategy();
-        pushStrategy.setMaxAssociatedResources(1);
-        AsyncConnectionFactory defaultFactory = new ServerHTTPSPDYAsyncConnectionFactory(version(), connector.getByteBufferPool(), connector.getExecutor(), connector.getScheduler(), connector, pushStrategy);
-        connector.setDefaultAsyncConnectionFactory(defaultFactory);
-
-        Headers mainRequestHeaders = createHeadersWithoutReferrer(mainResource);
-        Session session1 = sendMainRequestAndCSSRequest(address, mainRequestHeaders);
-
-        sendJSRequest(session1);
-
-        run2ndClientRequests(address, mainRequestHeaders, false);
-    }
-
-    private InetSocketAddress createServer() throws Exception
-    {
-        return startHTTPServer(version(), new AbstractHandler()
-        {
-            @Override
-            public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
-            {
-                String url = request.getRequestURI();
-                PrintWriter output = response.getWriter();
-                if (url.endsWith(".html"))
-                    output.print("<html><head/><body>HELLO</body></html>");
-                else if (url.endsWith(".css"))
-                    output.print("body { background: #FFF; }");
-                else if (url.endsWith(".js"))
-                    output.print("function(){}();");
-                baseRequest.setHandled(true);
-            }
-        });
-    }
-
-    private Session sendMainRequestAndCSSRequest(InetSocketAddress address, Headers mainRequestHeaders) throws Exception
-    {
-        Session session1 = startClient(version(), address, null);
-
-        final CountDownLatch mainResourceLatch = new CountDownLatch(1);
-        session1.syn(new SynInfo(mainRequestHeaders, true), new StreamFrameListener.Adapter()
-        {
-            @Override
-            public void onData(Stream stream, DataInfo dataInfo)
-            {
-                dataInfo.consume(dataInfo.length());
-                if (dataInfo.isClose())
-                    mainResourceLatch.countDown();
-            }
-        });
-        Assert.assertTrue(mainResourceLatch.await(5, TimeUnit.SECONDS));
-
-        final CountDownLatch associatedResourceLatch1 = new CountDownLatch(1);
-        Headers associatedRequestHeaders1 = createHeaders(cssResource);
-        session1.syn(new SynInfo(associatedRequestHeaders1, true), new StreamFrameListener.Adapter()
-        {
-            @Override
-            public void onData(Stream stream, DataInfo dataInfo)
-            {
-                dataInfo.consume(dataInfo.length());
-                if (dataInfo.isClose())
-                    associatedResourceLatch1.countDown();
-            }
-        });
-        Assert.assertTrue(associatedResourceLatch1.await(5, TimeUnit.SECONDS));
-        return session1;
-    }
-
-
-    private void sendJSRequest(Session session1) throws InterruptedException
-    {
-        final CountDownLatch associatedResourceLatch2 = new CountDownLatch(1);
-        String jsResource = "/application.js";
-        Headers associatedRequestHeaders2 = createHeaders(jsResource);
-        session1.syn(new SynInfo(associatedRequestHeaders2, true), new StreamFrameListener.Adapter()
-        {
-            @Override
-            public void onData(Stream stream, DataInfo dataInfo)
-            {
-                dataInfo.consume(dataInfo.length());
-                if (dataInfo.isClose())
-                    associatedResourceLatch2.countDown();
-            }
-        });
-        Assert.assertTrue(associatedResourceLatch2.await(5, TimeUnit.SECONDS));
-    }
-
-    private void run2ndClientRequests(InetSocketAddress address, Headers mainRequestHeaders, final boolean validateHeaders) throws Exception
-    {
-        // Create another client, and perform the same request for the main resource,
-        // we expect the css being pushed, but not the js
-
-        final CountDownLatch mainStreamLatch = new CountDownLatch(2);
-        final CountDownLatch pushDataLatch = new CountDownLatch(1);
-        final CountDownLatch pushSynHeadersValid = new CountDownLatch(1);
-        Session session2 = startClient(version(), address, new SessionFrameListener.Adapter()
-        {
-            @Override
-            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
-            {
-                if(validateHeaders)
-                    validateHeaders(synInfo.getHeaders(), pushSynHeadersValid);
-
-                Assert.assertTrue(stream.isUnidirectional());
-                Assert.assertTrue(synInfo.getHeaders().get(HTTPSPDYHeader.URI.name(version())).value().endsWith(".css"));
-                return new StreamFrameListener.Adapter()
-                {
-                    @Override
-                    public void onData(Stream stream, DataInfo dataInfo)
-                    {
-
-                        dataInfo.consume(dataInfo.length());
-                        if (dataInfo.isClose())
-                            pushDataLatch.countDown();
-                    }
-                };
-            }
-        });
-        session2.syn(new SynInfo(mainRequestHeaders, true), new StreamFrameListener.Adapter()
-        {
-            @Override
-            public void onReply(Stream stream, ReplyInfo replyInfo)
-            {
-                Assert.assertFalse(replyInfo.isClose());
-                mainStreamLatch.countDown();
-            }
-
-            @Override
-            public void onData(Stream stream, DataInfo dataInfo)
-            {
-                dataInfo.consume(dataInfo.length());
-                if (dataInfo.isClose())
-                    mainStreamLatch.countDown();
-            }
-        });
-
-        Assert.assertTrue("Main request reply and/or data not received", mainStreamLatch.await(5, TimeUnit.SECONDS));
-        Assert.assertTrue("Pushed data not received", pushDataLatch.await(5, TimeUnit.SECONDS));
-        if(validateHeaders)
-            Assert.assertTrue("Push syn headers not valid", pushSynHeadersValid.await(5, TimeUnit.SECONDS));
-    }
-
-    @Test
-    public void testAssociatedResourceIsPushed() throws Exception
-    {
-        InetSocketAddress address = startHTTPServer(version(), new AbstractHandler()
-        {
-            @Override
-            public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
-            {
-                String url = request.getRequestURI();
-                PrintWriter output = response.getWriter();
-                if (url.endsWith(".html"))
-                    output.print("<html><head/><body>HELLO</body></html>");
-                else if (url.endsWith(".css"))
-                    output.print("body { background: #FFF; }");
-                baseRequest.setHandled(true);
-            }
-        });
-        Session session1 = startClient(version(), address, null);
-
-        final CountDownLatch mainResourceLatch = new CountDownLatch(1);
-        Headers mainRequestHeaders = createHeadersWithoutReferrer(mainResource);
-
-        session1.syn(new SynInfo(mainRequestHeaders, true), new StreamFrameListener.Adapter()
-        {
-            @Override
-            public void onData(Stream stream, DataInfo dataInfo)
-            {
-                dataInfo.consume(dataInfo.length());
-                if (dataInfo.isClose())
-                    mainResourceLatch.countDown();
-            }
-        });
-        Assert.assertTrue(mainResourceLatch.await(5, TimeUnit.SECONDS));
-
-        final CountDownLatch associatedResourceLatch = new CountDownLatch(1);
-        Headers associatedRequestHeaders = createHeaders(cssResource);
-        session1.syn(new SynInfo(associatedRequestHeaders, true), new StreamFrameListener.Adapter()
-        {
-            @Override
-            public void onData(Stream stream, DataInfo dataInfo)
-            {
-                dataInfo.consume(dataInfo.length());
-                if (dataInfo.isClose())
-                    associatedResourceLatch.countDown();
-            }
-        });
-        Assert.assertTrue(associatedResourceLatch.await(5, TimeUnit.SECONDS));
-
-        // Create another client, and perform the same request for the main resource, we expect the css being pushed
-
-        final CountDownLatch mainStreamLatch = new CountDownLatch(2);
-        final CountDownLatch pushDataLatch = new CountDownLatch(1);
-        Session session2 = startClient(version(), address, new SessionFrameListener.Adapter()
-        {
-            @Override
-            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
-            {
-                Assert.assertTrue(stream.isUnidirectional());
-                return new StreamFrameListener.Adapter()
-                {
-                    @Override
-                    public void onData(Stream stream, DataInfo dataInfo)
-                    {
-                        dataInfo.consume(dataInfo.length());
-                        if (dataInfo.isClose())
-                            pushDataLatch.countDown();
-                    }
-                };
-            }
-        });
-        session2.syn(new SynInfo(mainRequestHeaders, true), new StreamFrameListener.Adapter()
-        {
-            @Override
-            public void onReply(Stream stream, ReplyInfo replyInfo)
-            {
-                Assert.assertFalse(replyInfo.isClose());
-                mainStreamLatch.countDown();
-            }
-
-            @Override
-            public void onData(Stream stream, DataInfo dataInfo)
-            {
-                dataInfo.consume(dataInfo.length());
-                if (dataInfo.isClose())
-                    mainStreamLatch.countDown();
-            }
-        });
-
-        Assert.assertTrue(mainStreamLatch.await(5, TimeUnit.SECONDS));
-        Assert.assertTrue(pushDataLatch.await(5, TimeUnit.SECONDS));
-    }
-
-    @Test
-    public void testAssociatedResourceWithWrongContentTypeIsNotPushed() throws Exception
-    {
-        final String fakeResource = "/fake.png";
-        InetSocketAddress address = startHTTPServer(version(), new AbstractHandler()
-        {
-            @Override
-            public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
-            {
-                String url = request.getRequestURI();
-                PrintWriter output = response.getWriter();
-                if (url.endsWith(".html"))
-                {
-                    response.setContentType("text/html");
-                    output.print("<html><head/><body>HELLO</body></html>");
-                }
-                else if (url.equals(fakeResource))
-                {
-                    response.setContentType("text/html");
-                    output.print("<html><head/><body>IMAGE</body></html>");
-                }
-                else if (url.endsWith(".css"))
-                {
-                    response.setContentType("text/css");
-                    output.print("body { background: #FFF; }");
-                }
-                baseRequest.setHandled(true);
-            }
-        });
-        Session session1 = startClient(version(), address, null);
-
-        final CountDownLatch mainResourceLatch = new CountDownLatch(1);
-        Headers mainRequestHeaders = createHeadersWithoutReferrer(mainResource);
-
-        session1.syn(new SynInfo(mainRequestHeaders, true), new StreamFrameListener.Adapter()
-        {
-            @Override
-            public void onData(Stream stream, DataInfo dataInfo)
-            {
-                dataInfo.consume(dataInfo.length());
-                if (dataInfo.isClose())
-                    mainResourceLatch.countDown();
-            }
-        });
-        Assert.assertTrue(mainResourceLatch.await(5, TimeUnit.SECONDS));
-
-        final CountDownLatch associatedResourceLatch = new CountDownLatch(1);
-        String cssResource = "/stylesheet.css";
-        Headers associatedRequestHeaders = createHeaders(cssResource);
-        session1.syn(new SynInfo(associatedRequestHeaders, true), new StreamFrameListener.Adapter()
-        {
-            @Override
-            public void onData(Stream stream, DataInfo dataInfo)
-            {
-                dataInfo.consume(dataInfo.length());
-                if (dataInfo.isClose())
-                    associatedResourceLatch.countDown();
-            }
-        });
-        Assert.assertTrue(associatedResourceLatch.await(5, TimeUnit.SECONDS));
-
-        final CountDownLatch fakeAssociatedResourceLatch = new CountDownLatch(1);
-        Headers fakeAssociatedRequestHeaders = createHeaders(fakeResource);
-        session1.syn(new SynInfo(fakeAssociatedRequestHeaders, true), new StreamFrameListener.Adapter()
-        {
-            @Override
-            public void onData(Stream stream, DataInfo dataInfo)
-            {
-                dataInfo.consume(dataInfo.length());
-                if (dataInfo.isClose())
-                    fakeAssociatedResourceLatch.countDown();
-            }
-        });
-        Assert.assertTrue(fakeAssociatedResourceLatch.await(5, TimeUnit.SECONDS));
-
-        // Create another client, and perform the same request for the main resource,
-        // we expect the css being pushed but not the fake PNG
-
-        final CountDownLatch mainStreamLatch = new CountDownLatch(2);
-        final CountDownLatch pushDataLatch = new CountDownLatch(1);
-        Session session2 = startClient(version(), address, new SessionFrameListener.Adapter()
-        {
-            @Override
-            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
-            {
-                Assert.assertTrue(stream.isUnidirectional());
-                Assert.assertTrue(synInfo.getHeaders().get(HTTPSPDYHeader.URI.name(version())).value().endsWith(".css"));
-                return new StreamFrameListener.Adapter()
-                {
-                    @Override
-                    public void onData(Stream stream, DataInfo dataInfo)
-                    {
-                        dataInfo.consume(dataInfo.length());
-                        if (dataInfo.isClose())
-                            pushDataLatch.countDown();
-                    }
-                };
-            }
-        });
-        session2.syn(new SynInfo(mainRequestHeaders, true), new StreamFrameListener.Adapter()
-        {
-            @Override
-            public void onReply(Stream stream, ReplyInfo replyInfo)
-            {
-                Assert.assertFalse(replyInfo.isClose());
-                mainStreamLatch.countDown();
-            }
-
-            @Override
-            public void onData(Stream stream, DataInfo dataInfo)
-            {
-                dataInfo.consume(dataInfo.length());
-                if (dataInfo.isClose())
-                    mainStreamLatch.countDown();
-            }
-        });
-
-        Assert.assertTrue(mainStreamLatch.await(5, TimeUnit.SECONDS));
-        Assert.assertTrue(pushDataLatch.await(5, TimeUnit.SECONDS));
-    }
-
-    @Test
-    public void testNestedAssociatedResourceIsPushed() throws Exception
-    {
-        InetSocketAddress address = startHTTPServer(version(), new AbstractHandler()
-        {
-            @Override
-            public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
-            {
-                String url = request.getRequestURI();
-                PrintWriter output = response.getWriter();
-                if (url.endsWith(".html"))
-                    output.print("<html><head/><body>HELLO</body></html>");
-                else if (url.endsWith(".css"))
-                    output.print("body { background: #FFF; }");
-                else if (url.endsWith(".gif"))
-                    output.print("\u0000");
-                baseRequest.setHandled(true);
-            }
-        });
-        Session session1 = startClient(version(), address, null);
-
-        final CountDownLatch mainResourceLatch = new CountDownLatch(1);
-        Headers mainRequestHeaders = createHeadersWithoutReferrer(mainResource);
-
-        session1.syn(new SynInfo(mainRequestHeaders, true), new StreamFrameListener.Adapter()
-        {
-            @Override
-            public void onData(Stream stream, DataInfo dataInfo)
-            {
-                dataInfo.consume(dataInfo.length());
-                if (dataInfo.isClose())
-                    mainResourceLatch.countDown();
-            }
-        });
-        Assert.assertTrue(mainResourceLatch.await(5, TimeUnit.SECONDS));
-
-        final CountDownLatch associatedResourceLatch = new CountDownLatch(1);
-        Headers associatedRequestHeaders = createHeaders(cssResource);
-        session1.syn(new SynInfo(associatedRequestHeaders, true), new StreamFrameListener.Adapter()
-        {
-            @Override
-            public void onData(Stream stream, DataInfo dataInfo)
-            {
-                dataInfo.consume(dataInfo.length());
-                if (dataInfo.isClose())
-                    associatedResourceLatch.countDown();
-            }
-        });
-        Assert.assertTrue(associatedResourceLatch.await(5, TimeUnit.SECONDS));
-
-        final CountDownLatch nestedResourceLatch = new CountDownLatch(1);
-        String imageUrl = "/image.gif";
-        Headers nestedRequestHeaders = createHeaders(imageUrl, cssResource);
-
-        session1.syn(new SynInfo(nestedRequestHeaders, true), new StreamFrameListener.Adapter()
-        {
-            @Override
-            public void onData(Stream stream, DataInfo dataInfo)
-            {
-                dataInfo.consume(dataInfo.length());
-                if (dataInfo.isClose())
-                    nestedResourceLatch.countDown();
-            }
-        });
-        Assert.assertTrue(nestedResourceLatch.await(5, TimeUnit.SECONDS));
-
-        // Create another client, and perform the same request for the main resource, we expect the css and the image being pushed
-
-        final CountDownLatch mainStreamLatch = new CountDownLatch(2);
-        final CountDownLatch pushDataLatch = new CountDownLatch(2);
-        Session session2 = startClient(version(), address, new SessionFrameListener.Adapter()
-        {
-            @Override
-            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
-            {
-                Assert.assertTrue(stream.isUnidirectional());
-                return new StreamFrameListener.Adapter()
-                {
-                    @Override
-                    public void onData(Stream stream, DataInfo dataInfo)
-                    {
-                        dataInfo.consume(dataInfo.length());
-                        if (dataInfo.isClose())
-                            pushDataLatch.countDown();
-                    }
-                };
-            }
-        });
-        session2.syn(new SynInfo(mainRequestHeaders, true), new StreamFrameListener.Adapter()
-        {
-            @Override
-            public void onReply(Stream stream, ReplyInfo replyInfo)
-            {
-                Assert.assertFalse(replyInfo.isClose());
-                mainStreamLatch.countDown();
-            }
-
-            @Override
-            public void onData(Stream stream, DataInfo dataInfo)
-            {
-                dataInfo.consume(dataInfo.length());
-                if (dataInfo.isClose())
-                    mainStreamLatch.countDown();
-            }
-        });
-
-        Assert.assertTrue(mainStreamLatch.await(5, TimeUnit.SECONDS));
-        Assert.assertTrue(pushDataLatch.await(5, TimeUnit.SECONDS));
-    }
-
-    @Test
-    public void testMainResourceWithReferrerIsNotPushed() throws Exception
-    {
-        InetSocketAddress address = startHTTPServer(version(), new AbstractHandler()
-        {
-            @Override
-            public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
-            {
-                String url = request.getRequestURI();
-                PrintWriter output = response.getWriter();
-                if (url.endsWith(".html"))
-                    output.print("<html><head/><body>HELLO</body></html>");
-                baseRequest.setHandled(true);
-            }
-        });
-        Session session1 = startClient(version(), address, null);
-
-        final CountDownLatch mainResourceLatch = new CountDownLatch(1);
-        Headers mainRequestHeaders = createHeadersWithoutReferrer(mainResource);
-
-        session1.syn(new SynInfo(mainRequestHeaders, true), new StreamFrameListener.Adapter()
-        {
-            @Override
-            public void onData(Stream stream, DataInfo dataInfo)
-            {
-                dataInfo.consume(dataInfo.length());
-                if (dataInfo.isClose())
-                    mainResourceLatch.countDown();
-            }
-        });
-        Assert.assertTrue(mainResourceLatch.await(5, TimeUnit.SECONDS));
-
-        final CountDownLatch associatedResourceLatch = new CountDownLatch(1);
-        String associatedResource = "/home.html";
-        Headers associatedRequestHeaders = createHeaders(associatedResource);
-
-        session1.syn(new SynInfo(associatedRequestHeaders, true), new StreamFrameListener.Adapter()
-        {
-            @Override
-            public void onData(Stream stream, DataInfo dataInfo)
-            {
-                dataInfo.consume(dataInfo.length());
-                if (dataInfo.isClose())
-                    associatedResourceLatch.countDown();
-            }
-        });
-        Assert.assertTrue(associatedResourceLatch.await(5, TimeUnit.SECONDS));
-
-        // Create another client, and perform the same request for the main resource, we expect nothing being pushed
-
-        final CountDownLatch mainStreamLatch = new CountDownLatch(2);
-        final CountDownLatch pushLatch = new CountDownLatch(1);
-        Session session2 = startClient(version(), address, new SessionFrameListener.Adapter()
-        {
-            @Override
-            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
-            {
-                pushLatch.countDown();
-                return null;
-            }
-        });
-        session2.syn(new SynInfo(mainRequestHeaders, true), new StreamFrameListener.Adapter()
-        {
-            @Override
-            public void onReply(Stream stream, ReplyInfo replyInfo)
-            {
-                Assert.assertFalse(replyInfo.isClose());
-                mainStreamLatch.countDown();
-            }
-
-            @Override
-            public void onData(Stream stream, DataInfo dataInfo)
-            {
-                dataInfo.consume(dataInfo.length());
-                if (dataInfo.isClose())
-                    mainStreamLatch.countDown();
-            }
-        });
-
-        Assert.assertTrue(mainStreamLatch.await(5, TimeUnit.SECONDS));
-        Assert.assertFalse(pushLatch.await(1, TimeUnit.SECONDS));
-    }
-
-    @Test
-    public void testRequestWithIfModifiedSinceHeaderPreventsPush() throws Exception
-    {
-        InetSocketAddress address = startHTTPServer(version(), new AbstractHandler()
-        {
-            @Override
-            public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
-            {
-                String url = request.getRequestURI();
-                PrintWriter output = response.getWriter();
-                if (url.endsWith(".html"))
-                    output.print("<html><head/><body>HELLO</body></html>");
-                else if (url.endsWith(".css"))
-                    output.print("body { background: #FFF; }");
-                baseRequest.setHandled(true);
-            }
-        });
-        Session session1 = startClient(version(), address, null);
-
-        final CountDownLatch mainResourceLatch = new CountDownLatch(1);
-        Headers mainRequestHeaders = createHeaders(mainResource);
-        mainRequestHeaders.put("If-Modified-Since", "Tue, 27 Mar 2012 16:36:52 GMT");
-        session1.syn(new SynInfo(mainRequestHeaders, true), new StreamFrameListener.Adapter()
-        {
-            @Override
-            public void onData(Stream stream, DataInfo dataInfo)
-            {
-                dataInfo.consume(dataInfo.length());
-                if (dataInfo.isClose())
-                    mainResourceLatch.countDown();
-            }
-        });
-        Assert.assertTrue(mainResourceLatch.await(5, TimeUnit.SECONDS));
-
-        final CountDownLatch associatedResourceLatch = new CountDownLatch(1);
-        Headers associatedRequestHeaders = createHeaders(cssResource);
-        session1.syn(new SynInfo(associatedRequestHeaders, true), new StreamFrameListener.Adapter()
-        {
-            @Override
-            public void onData(Stream stream, DataInfo dataInfo)
-            {
-                dataInfo.consume(dataInfo.length());
-                if (dataInfo.isClose())
-                    associatedResourceLatch.countDown();
-            }
-        });
-        Assert.assertTrue(associatedResourceLatch.await(5, TimeUnit.SECONDS));
-
-        // Create another client, and perform the same request for the main resource, we expect the css NOT being pushed as the main request contains an
-        // if-modified-since header
-
-        final CountDownLatch mainStreamLatch = new CountDownLatch(2);
-        final CountDownLatch pushDataLatch = new CountDownLatch(1);
-        Session session2 = startClient(version(), address, new SessionFrameListener.Adapter()
-        {
-            @Override
-            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
-            {
-                Assert.assertTrue(stream.isUnidirectional());
-                return new StreamFrameListener.Adapter()
-                {
-                    @Override
-                    public void onData(Stream stream, DataInfo dataInfo)
-                    {
-                        dataInfo.consume(dataInfo.length());
-                        if (dataInfo.isClose())
-                            pushDataLatch.countDown();
-                    }
-                };
-            }
-        });
-        session2.syn(new SynInfo(mainRequestHeaders, true), new StreamFrameListener.Adapter()
-        {
-            @Override
-            public void onReply(Stream stream, ReplyInfo replyInfo)
-            {
-                Assert.assertFalse(replyInfo.isClose());
-                mainStreamLatch.countDown();
-            }
-
-            @Override
-            public void onData(Stream stream, DataInfo dataInfo)
-            {
-                dataInfo.consume(dataInfo.length());
-                if (dataInfo.isClose())
-                    mainStreamLatch.countDown();
-            }
-        });
-
-        Assert.assertTrue(mainStreamLatch.await(5, TimeUnit.SECONDS));
-        Assert.assertFalse("We don't expect data to be pushed as the main request contained an if-modified-since header",pushDataLatch.await(1, TimeUnit.SECONDS));
-    }
-
-    private void validateHeaders(Headers headers, CountDownLatch pushSynHeadersValid)
-    {
-        if (validateHeader(headers, HTTPSPDYHeader.STATUS.name(version()), "200")
-                && validateHeader(headers, HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1")
-                && validateUriHeader(headers))
-            pushSynHeadersValid.countDown();
-    }
-
-    private boolean validateHeader(Headers headers, String name, String expectedValue)
-    {
-        Headers.Header header = headers.get(name);
-        if (header != null && expectedValue.equals(header.value()))
-            return true;
-        System.out.println(name + " not valid! " + headers);
-        return false;
-    }
-
-    private boolean validateUriHeader(Headers headers)
-    {
-        Headers.Header uriHeader = headers.get(HTTPSPDYHeader.URI.name(version()));
-        if (uriHeader != null)
-            if (version() == SPDY.V2 && uriHeader.value().startsWith("http://"))
-                return true;
-            else if (version() == SPDY.V3 && uriHeader.value().startsWith("/")
-                    && headers.get(HTTPSPDYHeader.HOST.name(version())) != null && headers.get(HTTPSPDYHeader.SCHEME.name(version())) != null)
-                return true;
-        System.out.println(HTTPSPDYHeader.URI.name(version()) + " not valid!");
-        return false;
-    }
-
-    private Headers createHeaders(String resource)
-    {
-        return createHeaders(resource, mainResource);
-    }
-
-    private Headers createHeaders(String resource, String referrer)
-    {
-        Headers associatedRequestHeaders = createHeadersWithoutReferrer(resource);
-        associatedRequestHeaders.put("referer", "http://localhost:" + connector.getLocalPort() + referrer);
-        return associatedRequestHeaders;
-    }
-
-    private Headers createHeadersWithoutReferrer(String resource)
-    {
-        Headers associatedRequestHeaders = new Headers();
-        associatedRequestHeaders.put(HTTPSPDYHeader.METHOD.name(version()), "GET");
-        associatedRequestHeaders.put(HTTPSPDYHeader.URI.name(version()), resource);
-        associatedRequestHeaders.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1");
-        associatedRequestHeaders.put(HTTPSPDYHeader.SCHEME.name(version()), "http");
-        associatedRequestHeaders.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort());
-        return associatedRequestHeaders;
-    }
-}
diff --git a/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/ReferrerPushStrategyV3Test.java b/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/ReferrerPushStrategyV3Test.java
deleted file mode 100644
index 9ef3cf2..0000000
--- a/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/ReferrerPushStrategyV3Test.java
+++ /dev/null
@@ -1,31 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-
-package org.eclipse.jetty.spdy.http;
-
-import org.eclipse.jetty.spdy.api.SPDY;
-
-public class ReferrerPushStrategyV3Test extends ReferrerPushStrategyV2Test
-{
-    @Override
-    protected short version()
-    {
-        return SPDY.V3;
-    }
-}
diff --git a/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/SSLExternalServerTest.java b/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/SSLExternalServerTest.java
deleted file mode 100644
index 26416a7..0000000
--- a/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/SSLExternalServerTest.java
+++ /dev/null
@@ -1,101 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.spdy.http;
-
-import java.io.IOException;
-import java.net.InetSocketAddress;
-import java.net.Socket;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.Executor;
-import java.util.concurrent.TimeUnit;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import org.eclipse.jetty.spdy.SPDYClient;
-import org.eclipse.jetty.spdy.api.Headers;
-import org.eclipse.jetty.spdy.api.ReplyInfo;
-import org.eclipse.jetty.spdy.api.SPDY;
-import org.eclipse.jetty.spdy.api.Session;
-import org.eclipse.jetty.spdy.api.Stream;
-import org.eclipse.jetty.spdy.api.StreamFrameListener;
-import org.eclipse.jetty.spdy.api.SynInfo;
-import org.eclipse.jetty.util.ssl.SslContextFactory;
-import org.junit.Assert;
-import org.junit.Assume;
-import org.junit.Ignore;
-import org.junit.Test;
-
- at Ignore("Not functional anymore")
-public class SSLExternalServerTest extends AbstractHTTPSPDYTest
-{
-    @Override
-    protected SPDYClient.Factory newSPDYClientFactory(Executor threadPool)
-    {
-        SslContextFactory sslContextFactory = new SslContextFactory();
-        // Force TLSv1
-        sslContextFactory.setIncludeProtocols("TLSv1");
-        return new SPDYClient.Factory(threadPool, sslContextFactory);
-    }
-
-    @Test
-    public void testExternalServer() throws Exception
-    {
-        String host = "encrypted.google.com";
-        int port = 443;
-        InetSocketAddress address = new InetSocketAddress(host, port);
-
-        try
-        {
-            // Test whether there is connectivity to avoid fail the test when offline
-            Socket socket = new Socket();
-            socket.connect(address, 5000);
-            socket.close();
-        }
-        catch (IOException x)
-        {
-            Assume.assumeNoException(x);
-        }
-
-        final short version = SPDY.V2;
-        Session session = startClient(version, address, null);
-        Headers headers = new Headers();
-        headers.put(HTTPSPDYHeader.SCHEME.name(version), "https");
-        headers.put(HTTPSPDYHeader.HOST.name(version), host + ":" + port);
-        headers.put(HTTPSPDYHeader.METHOD.name(version), "GET");
-        headers.put(HTTPSPDYHeader.URI.name(version), "/");
-        headers.put(HTTPSPDYHeader.VERSION.name(version), "HTTP/1.1");
-        final CountDownLatch latch = new CountDownLatch(1);
-        session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter()
-        {
-            @Override
-            public void onReply(Stream stream, ReplyInfo replyInfo)
-            {
-                Headers headers = replyInfo.getHeaders();
-                Headers.Header versionHeader = headers.get(HTTPSPDYHeader.STATUS.name(version));
-                if (versionHeader != null)
-                {
-                    Matcher matcher = Pattern.compile("(\\d{3}).*").matcher(versionHeader.value());
-                    if (matcher.matches() && Integer.parseInt(matcher.group(1)) < 400)
-                        latch.countDown();
-                }
-            }
-        });
-        Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
-    }
-}
diff --git a/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/ServerHTTPSPDYv2Test.java b/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/ServerHTTPSPDYv2Test.java
deleted file mode 100644
index f2b52a5..0000000
--- a/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/ServerHTTPSPDYv2Test.java
+++ /dev/null
@@ -1,1277 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-
-package org.eclipse.jetty.spdy.http;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.nio.ByteBuffer;
-import java.nio.charset.Charset;
-import java.util.Arrays;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
-import javax.servlet.ServletException;
-import javax.servlet.ServletOutputStream;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jetty.continuation.Continuation;
-import org.eclipse.jetty.continuation.ContinuationSupport;
-import org.eclipse.jetty.io.ByteArrayBuffer;
-import org.eclipse.jetty.server.Request;
-import org.eclipse.jetty.server.handler.AbstractHandler;
-import org.eclipse.jetty.spdy.api.BytesDataInfo;
-import org.eclipse.jetty.spdy.api.DataInfo;
-import org.eclipse.jetty.spdy.api.Headers;
-import org.eclipse.jetty.spdy.api.ReplyInfo;
-import org.eclipse.jetty.spdy.api.Session;
-import org.eclipse.jetty.spdy.api.Stream;
-import org.eclipse.jetty.spdy.api.StreamFrameListener;
-import org.eclipse.jetty.spdy.api.StringDataInfo;
-import org.eclipse.jetty.spdy.api.SynInfo;
-import org.junit.Assert;
-import org.junit.Test;
-
-public class ServerHTTPSPDYv2Test extends AbstractHTTPSPDYTest
-{
-    @Test
-    public void testSimpleGET() throws Exception
-    {
-        final String path = "/foo";
-        final CountDownLatch handlerLatch = new CountDownLatch(1);
-        Session session = startClient(version(), startHTTPServer(version(), new AbstractHandler()
-        {
-            @Override
-            public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
-                    throws IOException, ServletException
-            {
-                request.setHandled(true);
-                Assert.assertEquals("GET", httpRequest.getMethod());
-                Assert.assertEquals(path, target);
-                Assert.assertEquals(path, httpRequest.getRequestURI());
-                Assert.assertEquals("localhost:" + connector.getLocalPort(), httpRequest.getHeader("host"));
-                handlerLatch.countDown();
-            }
-        }), null);
-
-        Headers headers = new Headers();
-        headers.put(HTTPSPDYHeader.METHOD.name(version()), "GET");
-        headers.put(HTTPSPDYHeader.URI.name(version()), path);
-        headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1");
-        headers.put(HTTPSPDYHeader.SCHEME.name(version()), "http");
-        headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort());
-        final CountDownLatch replyLatch = new CountDownLatch(1);
-        session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter()
-        {
-            @Override
-            public void onReply(Stream stream, ReplyInfo replyInfo)
-            {
-                Assert.assertTrue(replyInfo.isClose());
-                Headers replyHeaders = replyInfo.getHeaders();
-                Assert.assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version())).value().contains("200"));
-                replyLatch.countDown();
-            }
-        });
-        Assert.assertTrue(handlerLatch.await(5, TimeUnit.SECONDS));
-        Assert.assertTrue(replyLatch.await(5, TimeUnit.SECONDS));
-    }
-
-    @Test
-    public void testGETWithQueryString() throws Exception
-    {
-        final String path = "/foo";
-        final String query = "p=1";
-        final String uri = path + "?" + query;
-        final CountDownLatch handlerLatch = new CountDownLatch(1);
-        Session session = startClient(version(), startHTTPServer(version(), new AbstractHandler()
-        {
-            @Override
-            public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
-                    throws IOException, ServletException
-            {
-                request.setHandled(true);
-                Assert.assertEquals("GET", httpRequest.getMethod());
-                Assert.assertEquals(path, target);
-                Assert.assertEquals(path, httpRequest.getRequestURI());
-                Assert.assertEquals(query, httpRequest.getQueryString());
-                handlerLatch.countDown();
-            }
-        }), null);
-
-        Headers headers = new Headers();
-        headers.put(HTTPSPDYHeader.METHOD.name(version()), "GET");
-        headers.put(HTTPSPDYHeader.URI.name(version()), uri);
-        headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1");
-        headers.put(HTTPSPDYHeader.SCHEME.name(version()), "http");
-        headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort());
-        final CountDownLatch replyLatch = new CountDownLatch(1);
-        session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter()
-        {
-            @Override
-            public void onReply(Stream stream, ReplyInfo replyInfo)
-            {
-                Assert.assertTrue(replyInfo.isClose());
-                Headers replyHeaders = replyInfo.getHeaders();
-                Assert.assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version())).value().contains("200"));
-                replyLatch.countDown();
-            }
-        });
-        Assert.assertTrue(handlerLatch.await(5, TimeUnit.SECONDS));
-        Assert.assertTrue(replyLatch.await(5, TimeUnit.SECONDS));
-    }
-
-    @Test
-    public void testHEAD() throws Exception
-    {
-        final String path = "/foo";
-        final CountDownLatch handlerLatch = new CountDownLatch(1);
-        Session session = startClient(version(), startHTTPServer(version(), new AbstractHandler()
-        {
-            @Override
-            public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
-                    throws IOException, ServletException
-            {
-                request.setHandled(true);
-                Assert.assertEquals("HEAD", httpRequest.getMethod());
-                Assert.assertEquals(path, target);
-                Assert.assertEquals(path, httpRequest.getRequestURI());
-                handlerLatch.countDown();
-            }
-        }), null);
-
-        Headers headers = new Headers();
-        headers.put(HTTPSPDYHeader.METHOD.name(version()), "HEAD");
-        headers.put(HTTPSPDYHeader.URI.name(version()), path);
-        headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1");
-        headers.put(HTTPSPDYHeader.SCHEME.name(version()), "http");
-        headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort());
-        final CountDownLatch replyLatch = new CountDownLatch(1);
-        session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter()
-        {
-            @Override
-            public void onReply(Stream stream, ReplyInfo replyInfo)
-            {
-                Assert.assertTrue(replyInfo.isClose());
-                Headers replyHeaders = replyInfo.getHeaders();
-                Assert.assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version())).value().contains("200"));
-                replyLatch.countDown();
-            }
-        });
-        Assert.assertTrue(handlerLatch.await(5, TimeUnit.SECONDS));
-        Assert.assertTrue(replyLatch.await(5, TimeUnit.SECONDS));
-    }
-
-    @Test
-    public void testPOSTWithParameters() throws Exception
-    {
-        final String path = "/foo";
-        final String data = "a=1&b=2";
-        final CountDownLatch handlerLatch = new CountDownLatch(1);
-        Session session = startClient(version(), startHTTPServer(version(), new AbstractHandler()
-        {
-            @Override
-            public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
-                    throws IOException, ServletException
-            {
-                request.setHandled(true);
-                Assert.assertEquals("POST", httpRequest.getMethod());
-                Assert.assertEquals("1", httpRequest.getParameter("a"));
-                Assert.assertEquals("2", httpRequest.getParameter("b"));
-                Assert.assertNotNull(httpRequest.getRemoteHost());
-                Assert.assertNotNull(httpRequest.getRemotePort());
-                Assert.assertNotNull(httpRequest.getRemoteAddr());
-                Assert.assertNotNull(httpRequest.getLocalPort());
-                Assert.assertNotNull(httpRequest.getLocalName());
-                Assert.assertNotNull(httpRequest.getLocalAddr());
-                Assert.assertNotNull(httpRequest.getServerPort());
-                Assert.assertNotNull(httpRequest.getServerName());
-                handlerLatch.countDown();
-            }
-        }), null);
-
-        Headers headers = new Headers();
-        headers.put(HTTPSPDYHeader.METHOD.name(version()), "POST");
-        headers.put(HTTPSPDYHeader.URI.name(version()), path);
-        headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1");
-        headers.put(HTTPSPDYHeader.SCHEME.name(version()), "http");
-        headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort());
-        headers.put("content-type", "application/x-www-form-urlencoded");
-        final CountDownLatch replyLatch = new CountDownLatch(1);
-        Stream stream = session.syn(new SynInfo(headers, false), new StreamFrameListener.Adapter()
-        {
-            @Override
-            public void onReply(Stream stream, ReplyInfo replyInfo)
-            {
-                Assert.assertTrue(replyInfo.isClose());
-                Headers replyHeaders = replyInfo.getHeaders();
-                Assert.assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version())).value().contains("200"));
-                replyLatch.countDown();
-            }
-        }).get(5, TimeUnit.SECONDS);
-        stream.data(new StringDataInfo(data, true));
-
-        Assert.assertTrue(handlerLatch.await(5, TimeUnit.SECONDS));
-        Assert.assertTrue(replyLatch.await(5, TimeUnit.SECONDS));
-    }
-
-    @Test
-    public void testPOSTWithParametersInTwoFramesTwoReads() throws Exception
-    {
-        final String path = "/foo";
-        final String data1 = "a=1&";
-        final String data2 = "b=2";
-        final CountDownLatch handlerLatch = new CountDownLatch(1);
-        Session session = startClient(version(), startHTTPServer(version(), new AbstractHandler()
-        {
-            @Override
-            public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
-                    throws IOException, ServletException
-            {
-                request.setHandled(true);
-                Assert.assertEquals("POST", httpRequest.getMethod());
-                Assert.assertEquals("1", httpRequest.getParameter("a"));
-                Assert.assertEquals("2", httpRequest.getParameter("b"));
-                handlerLatch.countDown();
-            }
-        }), null);
-
-        Headers headers = new Headers();
-        headers.put(HTTPSPDYHeader.METHOD.name(version()), "POST");
-        headers.put(HTTPSPDYHeader.URI.name(version()), path);
-        headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1");
-        headers.put(HTTPSPDYHeader.SCHEME.name(version()), "http");
-        headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort());
-        headers.put("content-type", "application/x-www-form-urlencoded");
-        final CountDownLatch replyLatch = new CountDownLatch(1);
-        Stream stream = session.syn(new SynInfo(headers, false), new StreamFrameListener.Adapter()
-        {
-            @Override
-            public void onReply(Stream stream, ReplyInfo replyInfo)
-            {
-                Assert.assertTrue(replyInfo.isClose());
-                Headers replyHeaders = replyInfo.getHeaders();
-                Assert.assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version())).value().contains("200"));
-                replyLatch.countDown();
-            }
-        }).get(5, TimeUnit.SECONDS);
-        // Sleep between the data frames so that they will be read in 2 reads
-        stream.data(new StringDataInfo(data1, false));
-        Thread.sleep(1000);
-        stream.data(new StringDataInfo(data2, true));
-
-        Assert.assertTrue(handlerLatch.await(5, TimeUnit.SECONDS));
-        Assert.assertTrue(replyLatch.await(5, TimeUnit.SECONDS));
-    }
-
-    @Test
-    public void testPOSTWithParametersInTwoFramesOneRead() throws Exception
-    {
-        final String path = "/foo";
-        final String data1 = "a=1&";
-        final String data2 = "b=2";
-        final CountDownLatch handlerLatch = new CountDownLatch(1);
-        Session session = startClient(version(), startHTTPServer(version(), new AbstractHandler()
-        {
-            @Override
-            public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
-                    throws IOException, ServletException
-            {
-                request.setHandled(true);
-                Assert.assertEquals("POST", httpRequest.getMethod());
-                Assert.assertEquals("1", httpRequest.getParameter("a"));
-                Assert.assertEquals("2", httpRequest.getParameter("b"));
-                handlerLatch.countDown();
-            }
-        }), null);
-
-        Headers headers = new Headers();
-        headers.put(HTTPSPDYHeader.METHOD.name(version()), "POST");
-        headers.put(HTTPSPDYHeader.URI.name(version()), path);
-        headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1");
-        headers.put(HTTPSPDYHeader.SCHEME.name(version()), "http");
-        headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort());
-        headers.put("content-type", "application/x-www-form-urlencoded");
-        final CountDownLatch replyLatch = new CountDownLatch(1);
-        Stream stream = session.syn(new SynInfo(headers, false), new StreamFrameListener.Adapter()
-        {
-            @Override
-            public void onReply(Stream stream, ReplyInfo replyInfo)
-            {
-                Assert.assertTrue(replyInfo.isClose());
-                Headers replyHeaders = replyInfo.getHeaders();
-                Assert.assertTrue(replyHeaders.toString(), replyHeaders.get(HTTPSPDYHeader.STATUS.name(version())).value().contains("200"));
-                replyLatch.countDown();
-            }
-        }).get(5, TimeUnit.SECONDS);
-        // Send the data frames consecutively, so the server reads both frames in one read
-        stream.data(new StringDataInfo(data1, false));
-        stream.data(new StringDataInfo(data2, true));
-
-        Assert.assertTrue(handlerLatch.await(5, TimeUnit.SECONDS));
-        Assert.assertTrue(replyLatch.await(5, TimeUnit.SECONDS));
-    }
-
-    @Test
-    public void testGETWithSmallResponseContent() throws Exception
-    {
-        final String data = "0123456789ABCDEF";
-        final CountDownLatch handlerLatch = new CountDownLatch(1);
-        Session session = startClient(version(), startHTTPServer(version(), new AbstractHandler()
-        {
-            @Override
-            public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
-                    throws IOException, ServletException
-            {
-                request.setHandled(true);
-                httpResponse.setStatus(HttpServletResponse.SC_OK);
-                ServletOutputStream output = httpResponse.getOutputStream();
-                output.write(data.getBytes("UTF-8"));
-                handlerLatch.countDown();
-            }
-        }), null);
-
-        Headers headers = new Headers();
-        headers.put(HTTPSPDYHeader.METHOD.name(version()), "GET");
-        headers.put(HTTPSPDYHeader.URI.name(version()), "/foo");
-        headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1");
-        headers.put(HTTPSPDYHeader.SCHEME.name(version()), "http");
-        headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort());
-        final CountDownLatch replyLatch = new CountDownLatch(1);
-        final CountDownLatch dataLatch = new CountDownLatch(1);
-        session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter()
-        {
-            @Override
-            public void onReply(Stream stream, ReplyInfo replyInfo)
-            {
-                Assert.assertFalse(replyInfo.isClose());
-                Headers replyHeaders = replyInfo.getHeaders();
-                Assert.assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version())).value().contains("200"));
-                replyLatch.countDown();
-            }
-
-            @Override
-            public void onData(Stream stream, DataInfo dataInfo)
-            {
-                Assert.assertTrue(dataInfo.isClose());
-                Assert.assertEquals(data, dataInfo.asString("UTF-8", true));
-                dataLatch.countDown();
-            }
-        });
-        Assert.assertTrue(handlerLatch.await(5, TimeUnit.SECONDS));
-        Assert.assertTrue(replyLatch.await(5, TimeUnit.SECONDS));
-        Assert.assertTrue(dataLatch.await(5, TimeUnit.SECONDS));
-    }
-
-    @Test
-    public void testGETWithOneByteResponseContent() throws Exception
-    {
-        final char data = 'x';
-        final CountDownLatch handlerLatch = new CountDownLatch(1);
-        Session session = startClient(version(), startHTTPServer(version(), new AbstractHandler()
-        {
-            @Override
-            public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
-                    throws IOException, ServletException
-            {
-                request.setHandled(true);
-                httpResponse.setStatus(HttpServletResponse.SC_OK);
-                ServletOutputStream output = httpResponse.getOutputStream();
-                output.write(data);
-                handlerLatch.countDown();
-            }
-        }), null);
-
-        Headers headers = new Headers();
-        headers.put(HTTPSPDYHeader.METHOD.name(version()), "GET");
-        headers.put(HTTPSPDYHeader.URI.name(version()), "/foo");
-        headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1");
-        headers.put(HTTPSPDYHeader.SCHEME.name(version()), "http");
-        headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort());
-        final CountDownLatch replyLatch = new CountDownLatch(1);
-        final CountDownLatch dataLatch = new CountDownLatch(1);
-        session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter()
-        {
-            @Override
-            public void onReply(Stream stream, ReplyInfo replyInfo)
-            {
-                Assert.assertFalse(replyInfo.isClose());
-                Headers replyHeaders = replyInfo.getHeaders();
-                Assert.assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version())).value().contains("200"));
-                replyLatch.countDown();
-            }
-
-            @Override
-            public void onData(Stream stream, DataInfo dataInfo)
-            {
-                Assert.assertTrue(dataInfo.isClose());
-                byte[] bytes = dataInfo.asBytes(true);
-                Assert.assertEquals(1, bytes.length);
-                Assert.assertEquals(data, bytes[0]);
-                dataLatch.countDown();
-            }
-        });
-        Assert.assertTrue(handlerLatch.await(5, TimeUnit.SECONDS));
-        Assert.assertTrue(replyLatch.await(5, TimeUnit.SECONDS));
-        Assert.assertTrue(dataLatch.await(5, TimeUnit.SECONDS));
-    }
-
-    @Test
-    public void testGETWithSmallResponseContentInTwoChunks() throws Exception
-    {
-        final String data1 = "0123456789ABCDEF";
-        final String data2 = "FEDCBA9876543210";
-        final CountDownLatch handlerLatch = new CountDownLatch(1);
-        Session session = startClient(version(), startHTTPServer(version(), new AbstractHandler()
-        {
-            @Override
-            public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
-                    throws IOException, ServletException
-            {
-                request.setHandled(true);
-                httpResponse.setStatus(HttpServletResponse.SC_OK);
-                ServletOutputStream output = httpResponse.getOutputStream();
-                output.write(data1.getBytes("UTF-8"));
-                output.flush();
-                output.write(data2.getBytes("UTF-8"));
-                handlerLatch.countDown();
-            }
-        }), null);
-
-        Headers headers = new Headers();
-        headers.put(HTTPSPDYHeader.METHOD.name(version()), "GET");
-        headers.put(HTTPSPDYHeader.URI.name(version()), "/foo");
-        headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1");
-        headers.put(HTTPSPDYHeader.SCHEME.name(version()), "http");
-        headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort());
-        final CountDownLatch replyLatch = new CountDownLatch(1);
-        final CountDownLatch dataLatch = new CountDownLatch(2);
-        session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter()
-        {
-            private final AtomicInteger replyFrames = new AtomicInteger();
-            private final AtomicInteger dataFrames = new AtomicInteger();
-
-            @Override
-            public void onReply(Stream stream, ReplyInfo replyInfo)
-            {
-                Assert.assertEquals(1, replyFrames.incrementAndGet());
-                Assert.assertFalse(replyInfo.isClose());
-                Headers replyHeaders = replyInfo.getHeaders();
-                Assert.assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version())).value().contains("200"));
-                replyLatch.countDown();
-            }
-
-            @Override
-            public void onData(Stream stream, DataInfo dataInfo)
-            {
-                int data = dataFrames.incrementAndGet();
-                Assert.assertTrue(data >= 1 && data <= 2);
-                if (data == 1)
-                    Assert.assertEquals(data1, dataInfo.asString("UTF8", true));
-                else
-                    Assert.assertEquals(data2, dataInfo.asString("UTF8", true));
-                dataLatch.countDown();
-            }
-        });
-        Assert.assertTrue(handlerLatch.await(5, TimeUnit.SECONDS));
-        Assert.assertTrue(replyLatch.await(5, TimeUnit.SECONDS));
-        Assert.assertTrue(dataLatch.await(5, TimeUnit.SECONDS));
-    }
-
-    @Test
-    public void testGETWithBigResponseContentInOneWrite() throws Exception
-    {
-        final byte[] data = new byte[128 * 1024];
-        Arrays.fill(data, (byte)'x');
-        final CountDownLatch handlerLatch = new CountDownLatch(1);
-        Session session = startClient(version(), startHTTPServer(version(), new AbstractHandler()
-        {
-            @Override
-            public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
-                    throws IOException, ServletException
-            {
-                request.setHandled(true);
-                httpResponse.setStatus(HttpServletResponse.SC_OK);
-                ServletOutputStream output = httpResponse.getOutputStream();
-                output.write(data);
-                handlerLatch.countDown();
-            }
-        }), null);
-
-        Headers headers = new Headers();
-        headers.put(HTTPSPDYHeader.METHOD.name(version()), "GET");
-        headers.put(HTTPSPDYHeader.URI.name(version()), "/foo");
-        headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1");
-        headers.put(HTTPSPDYHeader.SCHEME.name(version()), "http");
-        headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort());
-        final CountDownLatch replyLatch = new CountDownLatch(1);
-        final CountDownLatch dataLatch = new CountDownLatch(1);
-        session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter()
-        {
-            private final AtomicInteger contentBytes = new AtomicInteger();
-
-            @Override
-            public void onReply(Stream stream, ReplyInfo replyInfo)
-            {
-                Assert.assertFalse(replyInfo.isClose());
-                Headers replyHeaders = replyInfo.getHeaders();
-                Assert.assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version())).value().contains("200"));
-                replyLatch.countDown();
-            }
-
-            @Override
-            public void onData(Stream stream, DataInfo dataInfo)
-            {
-                contentBytes.addAndGet(dataInfo.asByteBuffer(true).remaining());
-                if (dataInfo.isClose())
-                {
-                    Assert.assertEquals(data.length, contentBytes.get());
-                    dataLatch.countDown();
-                }
-            }
-        });
-        Assert.assertTrue(handlerLatch.await(5, TimeUnit.SECONDS));
-        Assert.assertTrue(replyLatch.await(5, TimeUnit.SECONDS));
-        Assert.assertTrue(dataLatch.await(5, TimeUnit.SECONDS));
-    }
-
-    @Test
-    public void testGETWithBigResponseContentInTwoWrites() throws Exception
-    {
-        final byte[] data = new byte[128 * 1024];
-        Arrays.fill(data, (byte)'y');
-        final CountDownLatch handlerLatch = new CountDownLatch(1);
-        Session session = startClient(version(), startHTTPServer(version(), new AbstractHandler()
-        {
-            @Override
-            public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
-                    throws IOException, ServletException
-            {
-                request.setHandled(true);
-                httpResponse.setStatus(HttpServletResponse.SC_OK);
-                ServletOutputStream output = httpResponse.getOutputStream();
-                output.write(data);
-                output.write(data);
-                handlerLatch.countDown();
-            }
-        }), null);
-
-        Headers headers = new Headers();
-        headers.put(HTTPSPDYHeader.METHOD.name(version()), "GET");
-        headers.put(HTTPSPDYHeader.URI.name(version()), "/foo");
-        headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1");
-        headers.put(HTTPSPDYHeader.SCHEME.name(version()), "http");
-        headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort());
-        final CountDownLatch replyLatch = new CountDownLatch(1);
-        final CountDownLatch dataLatch = new CountDownLatch(1);
-        session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter()
-        {
-            private final AtomicInteger contentBytes = new AtomicInteger();
-
-            @Override
-            public void onReply(Stream stream, ReplyInfo replyInfo)
-            {
-                Assert.assertFalse(replyInfo.isClose());
-                Headers replyHeaders = replyInfo.getHeaders();
-                Assert.assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version())).value().contains("200"));
-                replyLatch.countDown();
-            }
-
-            @Override
-            public void onData(Stream stream, DataInfo dataInfo)
-            {
-                contentBytes.addAndGet(dataInfo.asByteBuffer(true).remaining());
-                if (dataInfo.isClose())
-                {
-                    Assert.assertEquals(2 * data.length, contentBytes.get());
-                    dataLatch.countDown();
-                }
-            }
-        });
-        Assert.assertTrue(handlerLatch.await(5, TimeUnit.SECONDS));
-        Assert.assertTrue(replyLatch.await(5, TimeUnit.SECONDS));
-        Assert.assertTrue(dataLatch.await(5, TimeUnit.SECONDS));
-    }
-
-    @Test
-    public void testGETWithOutputStreamFlushedAndClosed() throws Exception
-    {
-        final String data = "0123456789ABCDEF";
-        final CountDownLatch handlerLatch = new CountDownLatch(1);
-        Session session = startClient(version(), startHTTPServer(version(), new AbstractHandler()
-        {
-            @Override
-            public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
-                    throws IOException, ServletException
-            {
-                request.setHandled(true);
-                httpResponse.setStatus(HttpServletResponse.SC_OK);
-                ServletOutputStream output = httpResponse.getOutputStream();
-                output.write(data.getBytes("UTF-8"));
-                output.flush();
-                output.close();
-                handlerLatch.countDown();
-            }
-        }), null);
-
-        Headers headers = new Headers();
-        headers.put(HTTPSPDYHeader.METHOD.name(version()), "GET");
-        headers.put(HTTPSPDYHeader.URI.name(version()), "/foo");
-        headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1");
-        headers.put(HTTPSPDYHeader.SCHEME.name(version()), "http");
-        headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort());
-        final CountDownLatch replyLatch = new CountDownLatch(1);
-        final CountDownLatch dataLatch = new CountDownLatch(1);
-        session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter()
-        {
-            private final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
-
-            @Override
-            public void onReply(Stream stream, ReplyInfo replyInfo)
-            {
-                Assert.assertFalse(replyInfo.isClose());
-                Headers replyHeaders = replyInfo.getHeaders();
-                Assert.assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version())).value().contains("200"));
-                replyLatch.countDown();
-            }
-
-            @Override
-            public void onData(Stream stream, DataInfo dataInfo)
-            {
-                ByteBuffer byteBuffer = dataInfo.asByteBuffer(true);
-                while (byteBuffer.hasRemaining())
-                    buffer.write(byteBuffer.get());
-                if (dataInfo.isClose())
-                {
-                    Assert.assertEquals(data, new String(buffer.toByteArray(), Charset.forName("UTF-8")));
-                    dataLatch.countDown();
-                }
-            }
-        });
-        Assert.assertTrue(handlerLatch.await(5, TimeUnit.SECONDS));
-        Assert.assertTrue(replyLatch.await(5, TimeUnit.SECONDS));
-        Assert.assertTrue(dataLatch.await(5, TimeUnit.SECONDS));
-    }
-
-    @Test
-    public void testGETWithResponseResetBuffer() throws Exception
-    {
-        final String data1 = "0123456789ABCDEF";
-        final String data2 = "FEDCBA9876543210";
-        final CountDownLatch handlerLatch = new CountDownLatch(1);
-        Session session = startClient(version(), startHTTPServer(version(), new AbstractHandler()
-        {
-            @Override
-            public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
-                    throws IOException, ServletException
-            {
-                request.setHandled(true);
-                httpResponse.setStatus(HttpServletResponse.SC_OK);
-                ServletOutputStream output = httpResponse.getOutputStream();
-                // Write some
-                output.write(data1.getBytes("UTF-8"));
-                // But then change your mind and reset the buffer
-                httpResponse.resetBuffer();
-                output.write(data2.getBytes("UTF-8"));
-                handlerLatch.countDown();
-            }
-        }), null);
-
-        Headers headers = new Headers();
-        headers.put(HTTPSPDYHeader.METHOD.name(version()), "GET");
-        headers.put(HTTPSPDYHeader.URI.name(version()), "/foo");
-        headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1");
-        headers.put(HTTPSPDYHeader.SCHEME.name(version()), "http");
-        headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort());
-        final CountDownLatch replyLatch = new CountDownLatch(1);
-        final CountDownLatch dataLatch = new CountDownLatch(1);
-        session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter()
-        {
-            private final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
-
-            @Override
-            public void onReply(Stream stream, ReplyInfo replyInfo)
-            {
-                Assert.assertFalse(replyInfo.isClose());
-                Headers replyHeaders = replyInfo.getHeaders();
-                Assert.assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version())).value().contains("200"));
-                replyLatch.countDown();
-            }
-
-            @Override
-            public void onData(Stream stream, DataInfo dataInfo)
-            {
-                ByteBuffer byteBuffer = dataInfo.asByteBuffer(true);
-                while (byteBuffer.hasRemaining())
-                    buffer.write(byteBuffer.get());
-                if (dataInfo.isClose())
-                {
-                    Assert.assertEquals(data2, new String(buffer.toByteArray(), Charset.forName("UTF-8")));
-                    dataLatch.countDown();
-                }
-            }
-        });
-        Assert.assertTrue(handlerLatch.await(5, TimeUnit.SECONDS));
-        Assert.assertTrue(replyLatch.await(5, TimeUnit.SECONDS));
-        Assert.assertTrue(dataLatch.await(5, TimeUnit.SECONDS));
-    }
-
-    @Test
-    public void testGETWithRedirect() throws Exception
-    {
-        final String suffix = "/redirect";
-        final CountDownLatch handlerLatch = new CountDownLatch(1);
-        Session session = startClient(version(), startHTTPServer(version(), new AbstractHandler()
-        {
-            @Override
-            public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
-                    throws IOException, ServletException
-            {
-                request.setHandled(true);
-                String location = httpResponse.encodeRedirectURL(String.format("%s://%s:%d%s",
-                        request.getScheme(), request.getLocalAddr(), request.getLocalPort(), target + suffix));
-                httpResponse.sendRedirect(location);
-                handlerLatch.countDown();
-            }
-        }), null);
-
-        Headers headers = new Headers();
-        headers.put(HTTPSPDYHeader.METHOD.name(version()), "GET");
-        headers.put(HTTPSPDYHeader.URI.name(version()), "/foo");
-        headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1");
-        headers.put(HTTPSPDYHeader.SCHEME.name(version()), "http");
-        headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort());
-        final CountDownLatch replyLatch = new CountDownLatch(1);
-        session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter()
-        {
-            private final AtomicInteger replies = new AtomicInteger();
-
-            @Override
-            public void onReply(Stream stream, ReplyInfo replyInfo)
-            {
-                Assert.assertEquals(1, replies.incrementAndGet());
-                Assert.assertTrue(replyInfo.isClose());
-                Headers replyHeaders = replyInfo.getHeaders();
-                Assert.assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version())).value().contains("302"));
-                Assert.assertTrue(replyHeaders.get("location").value().endsWith(suffix));
-                replyLatch.countDown();
-            }
-        });
-        Assert.assertTrue(handlerLatch.await(5, TimeUnit.SECONDS));
-        Assert.assertTrue(replyLatch.await(5, TimeUnit.SECONDS));
-    }
-
-    @Test
-    public void testGETWithSendError() throws Exception
-    {
-        final CountDownLatch handlerLatch = new CountDownLatch(1);
-        Session session = startClient(version(), startHTTPServer(version(), new AbstractHandler()
-        {
-            @Override
-            public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
-                    throws IOException, ServletException
-            {
-                request.setHandled(true);
-                httpResponse.sendError(HttpServletResponse.SC_NOT_FOUND);
-                handlerLatch.countDown();
-            }
-        }), null);
-
-        Headers headers = new Headers();
-        headers.put(HTTPSPDYHeader.METHOD.name(version()), "GET");
-        headers.put(HTTPSPDYHeader.URI.name(version()), "/foo");
-        headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1");
-        headers.put(HTTPSPDYHeader.SCHEME.name(version()), "http");
-        headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort());
-        final CountDownLatch replyLatch = new CountDownLatch(1);
-        final CountDownLatch dataLatch = new CountDownLatch(1);
-        session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter()
-        {
-            private final AtomicInteger replies = new AtomicInteger();
-
-            @Override
-            public void onReply(Stream stream, ReplyInfo replyInfo)
-            {
-                Assert.assertEquals(1, replies.incrementAndGet());
-                Assert.assertFalse(replyInfo.isClose());
-                Headers replyHeaders = replyInfo.getHeaders();
-                Assert.assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version())).value().contains("404"));
-                replyLatch.countDown();
-            }
-
-            @Override
-            public void onData(Stream stream, DataInfo dataInfo)
-            {
-                if (dataInfo.isClose())
-                    dataLatch.countDown();
-            }
-        });
-        Assert.assertTrue(handlerLatch.await(5, TimeUnit.SECONDS));
-        Assert.assertTrue(replyLatch.await(5, TimeUnit.SECONDS));
-        Assert.assertTrue(dataLatch.await(5, TimeUnit.SECONDS));
-    }
-
-    @Test
-    public void testGETWithException() throws Exception
-    {
-        Session session = startClient(version(), startHTTPServer(version(), new AbstractHandler()
-        {
-            @Override
-            public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
-                    throws IOException, ServletException
-            {
-                throw new NullPointerException("thrown_explicitly_by_the_test");
-            }
-        }), null);
-
-        Headers headers = new Headers();
-        headers.put(HTTPSPDYHeader.METHOD.name(version()), "GET");
-        headers.put(HTTPSPDYHeader.URI.name(version()), "/foo");
-        headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1");
-        headers.put(HTTPSPDYHeader.SCHEME.name(version()), "http");
-        headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort());
-        final CountDownLatch replyLatch = new CountDownLatch(1);
-        session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter()
-        {
-            private final AtomicInteger replies = new AtomicInteger();
-
-            @Override
-            public void onReply(Stream stream, ReplyInfo replyInfo)
-            {
-                Assert.assertEquals(1, replies.incrementAndGet());
-                Assert.assertTrue(replyInfo.isClose());
-                Headers replyHeaders = replyInfo.getHeaders();
-                Assert.assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version())).value().contains("500"));
-                replyLatch.countDown();
-            }
-        });
-        Assert.assertTrue(replyLatch.await(5, TimeUnit.SECONDS));
-    }
-
-    @Test
-    public void testGETWithSmallResponseChunked() throws Exception
-    {
-        final String pangram1 = "the quick brown fox jumps over the lazy dog";
-        final String pangram2 = "qualche vago ione tipo zolfo, bromo, sodio";
-        final CountDownLatch handlerLatch = new CountDownLatch(1);
-        Session session = startClient(version(), startHTTPServer(version(), new AbstractHandler()
-        {
-            @Override
-            public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
-                    throws IOException, ServletException
-            {
-                request.setHandled(true);
-                httpResponse.setHeader("Transfer-Encoding", "chunked");
-                ServletOutputStream output = httpResponse.getOutputStream();
-                output.write(pangram1.getBytes("UTF-8"));
-                httpResponse.setHeader("EXTRA", "X");
-                output.flush();
-                output.write(pangram2.getBytes("UTF-8"));
-                handlerLatch.countDown();
-            }
-        }), null);
-
-        Headers headers = new Headers();
-        headers.put(HTTPSPDYHeader.METHOD.name(version()), "GET");
-        headers.put(HTTPSPDYHeader.URI.name(version()), "/foo");
-        headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1");
-        headers.put(HTTPSPDYHeader.SCHEME.name(version()), "http");
-        headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort());
-        final CountDownLatch replyLatch = new CountDownLatch(1);
-        final CountDownLatch dataLatch = new CountDownLatch(2);
-        session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter()
-        {
-            private final AtomicInteger replyFrames = new AtomicInteger();
-            private final AtomicInteger dataFrames = new AtomicInteger();
-
-            @Override
-            public void onReply(Stream stream, ReplyInfo replyInfo)
-            {
-                Assert.assertEquals(1, replyFrames.incrementAndGet());
-                Assert.assertFalse(replyInfo.isClose());
-                Headers replyHeaders = replyInfo.getHeaders();
-                Assert.assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version())).value().contains("200"));
-                Assert.assertTrue(replyHeaders.get("extra").value().contains("X"));
-                replyLatch.countDown();
-            }
-
-            @Override
-            public void onData(Stream stream, DataInfo dataInfo)
-            {
-                int count = dataFrames.incrementAndGet();
-                if (count == 1)
-                {
-                    Assert.assertFalse(dataInfo.isClose());
-                    Assert.assertEquals(pangram1, dataInfo.asString("UTF-8", true));
-                }
-                else if (count == 2)
-                {
-                    Assert.assertTrue(dataInfo.isClose());
-                    Assert.assertEquals(pangram2, dataInfo.asString("UTF-8", true));
-                }
-                dataLatch.countDown();
-            }
-        });
-        Assert.assertTrue(handlerLatch.await(5, TimeUnit.SECONDS));
-        Assert.assertTrue(replyLatch.await(5, TimeUnit.SECONDS));
-        Assert.assertTrue(dataLatch.await(5, TimeUnit.SECONDS));
-    }
-
-    @Test
-    public void testGETWithMediumContentAsInputStreamByPassed() throws Exception
-    {
-        byte[] data = new byte[2048];
-        testGETWithContentByPassed(new ByteArrayInputStream(data), data.length);
-    }
-
-    @Test
-    public void testGETWithBigContentAsInputStreamByPassed() throws Exception
-    {
-        byte[] data = new byte[128 * 1024];
-        testGETWithContentByPassed(new ByteArrayInputStream(data), data.length);
-    }
-
-    @Test
-    public void testGETWithMediumContentAsBufferByPassed() throws Exception
-    {
-        byte[] data = new byte[2048];
-        testGETWithContentByPassed(new ByteArrayBuffer(data), data.length);
-    }
-
-    private void testGETWithContentByPassed(final Object content, final int length) throws Exception
-    {
-        final CountDownLatch handlerLatch = new CountDownLatch(1);
-        Session session = startClient(version(), startHTTPServer(version(), new AbstractHandler()
-        {
-            @Override
-            public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
-                    throws IOException, ServletException
-            {
-                request.setHandled(true);
-                // We use this trick that's present in Jetty code: if we add a request attribute
-                // called "org.eclipse.jetty.server.sendContent", then it will trigger the
-                // content bypass that we want to test
-                request.setAttribute("org.eclipse.jetty.server.sendContent", content);
-                handlerLatch.countDown();
-            }
-        }), null);
-
-        Headers headers = new Headers();
-        headers.put(HTTPSPDYHeader.METHOD.name(version()), "GET");
-        headers.put(HTTPSPDYHeader.URI.name(version()), "/foo");
-        headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1");
-        headers.put(HTTPSPDYHeader.SCHEME.name(version()), "http");
-        headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort());
-        final CountDownLatch replyLatch = new CountDownLatch(1);
-        final CountDownLatch dataLatch = new CountDownLatch(1);
-        session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter()
-        {
-            private final AtomicInteger replyFrames = new AtomicInteger();
-            private final AtomicInteger contentLength = new AtomicInteger();
-
-            @Override
-            public void onReply(Stream stream, ReplyInfo replyInfo)
-            {
-                Assert.assertEquals(1, replyFrames.incrementAndGet());
-                Assert.assertFalse(replyInfo.isClose());
-                Headers replyHeaders = replyInfo.getHeaders();
-                Assert.assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version())).value().contains("200"));
-                replyLatch.countDown();
-            }
-
-            @Override
-            public void onData(Stream stream, DataInfo dataInfo)
-            {
-                contentLength.addAndGet(dataInfo.asBytes(true).length);
-                if (dataInfo.isClose())
-                {
-                    Assert.assertEquals(length, contentLength.get());
-                    dataLatch.countDown();
-                }
-            }
-        });
-        Assert.assertTrue(handlerLatch.await(5, TimeUnit.SECONDS));
-        Assert.assertTrue(replyLatch.await(5, TimeUnit.SECONDS));
-        Assert.assertTrue(dataLatch.await(5, TimeUnit.SECONDS));
-    }
-
-    @Test
-    public void testGETWithMultipleMediumContentByPassed() throws Exception
-    {
-        final byte[] data = new byte[2048];
-        Session session = startClient(version(), startHTTPServer(version(), new AbstractHandler()
-        {
-            @Override
-            public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
-                    throws IOException, ServletException
-            {
-                // The sequence of write/flush/write/write below triggers a condition where
-                // HttpGenerator._bypass is set to true on the second write(), and the
-                // third write causes an infinite spin loop on the third write().
-                request.setHandled(true);
-                OutputStream output = httpResponse.getOutputStream();
-                output.write(data);
-                output.flush();
-                output.write(data);
-                output.write(data);
-            }
-        }), null);
-
-        Headers headers = new Headers();
-        headers.put(HTTPSPDYHeader.METHOD.name(version()), "GET");
-        headers.put(HTTPSPDYHeader.URI.name(version()), "/foo");
-        headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1");
-        headers.put(HTTPSPDYHeader.SCHEME.name(version()), "http");
-        headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort());
-        final CountDownLatch replyLatch = new CountDownLatch(1);
-        final CountDownLatch dataLatch = new CountDownLatch(1);
-        final AtomicInteger contentLength = new AtomicInteger();
-        session.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter()
-        {
-            @Override
-            public void onReply(Stream stream, ReplyInfo replyInfo)
-            {
-                Assert.assertFalse(replyInfo.isClose());
-                Headers replyHeaders = replyInfo.getHeaders();
-                Assert.assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version())).value().contains("200"));
-                replyLatch.countDown();
-            }
-
-            @Override
-            public void onData(Stream stream, DataInfo dataInfo)
-            {
-                dataInfo.consume(dataInfo.available());
-                contentLength.addAndGet(dataInfo.length());
-                if (dataInfo.isClose())
-                    dataLatch.countDown();
-            }
-        });
-        Assert.assertTrue(dataLatch.await(5, TimeUnit.SECONDS));
-        Assert.assertEquals(3 * data.length, contentLength.get());
-    }
-
-    @Test
-    public void testPOSTThenSuspendRequestThenReadOneChunkThenComplete() throws Exception
-    {
-        final byte[] data = new byte[2000];
-        final CountDownLatch latch = new CountDownLatch(1);
-        Session session = startClient(version(), startHTTPServer(version(), new AbstractHandler()
-        {
-            @Override
-            public void handle(String target, final Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
-                    throws IOException, ServletException
-            {
-                request.setHandled(true);
-
-                final Continuation continuation = ContinuationSupport.getContinuation(request);
-                continuation.suspend();
-
-                new Thread()
-                {
-                    @Override
-                    public void run()
-                    {
-                        try
-                        {
-                            InputStream input = request.getInputStream();
-                            byte[] buffer = new byte[512];
-                            int read = 0;
-                            while (read < data.length)
-                                read += input.read(buffer);
-                            continuation.complete();
-                            latch.countDown();
-                        }
-                        catch (IOException x)
-                        {
-                            x.printStackTrace();
-                        }
-                    }
-                }.start();
-            }
-        }), null);
-
-        Headers headers = new Headers();
-        headers.put(HTTPSPDYHeader.METHOD.name(version()), "POST");
-        headers.put(HTTPSPDYHeader.URI.name(version()), "/foo");
-        headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1");
-        headers.put(HTTPSPDYHeader.SCHEME.name(version()), "http");
-        headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort());
-        final CountDownLatch replyLatch = new CountDownLatch(1);
-        Stream stream = session.syn(new SynInfo(headers, false), new StreamFrameListener.Adapter()
-        {
-            @Override
-            public void onReply(Stream stream, ReplyInfo replyInfo)
-            {
-                Headers replyHeaders = replyInfo.getHeaders();
-                Assert.assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version())).value().contains("200"));
-                replyLatch.countDown();
-            }
-        }).get(5, TimeUnit.SECONDS);
-        stream.data(new BytesDataInfo(data, true));
-
-        Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
-        Assert.assertTrue(replyLatch.await(5, TimeUnit.SECONDS));
-    }
-
-    @Test
-    public void testPOSTThenSuspendRequestThenReadTwoChunksThenComplete() throws Exception
-    {
-        final byte[] data = new byte[2000];
-        final CountDownLatch latch = new CountDownLatch(1);
-        Session session = startClient(version(), startHTTPServer(version(), new AbstractHandler()
-        {
-            @Override
-            public void handle(String target, final Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
-                    throws IOException, ServletException
-            {
-                request.setHandled(true);
-
-                final Continuation continuation = ContinuationSupport.getContinuation(request);
-                continuation.suspend();
-
-                new Thread()
-                {
-                    @Override
-                    public void run()
-                    {
-                        try
-                        {
-                            InputStream input = request.getInputStream();
-                            byte[] buffer = new byte[512];
-                            int read = 0;
-                            while (read < 2 * data.length)
-                                read += input.read(buffer);
-                            continuation.complete();
-                            latch.countDown();
-                        }
-                        catch (IOException x)
-                        {
-                            x.printStackTrace();
-                        }
-                    }
-                }.start();
-            }
-        }), null);
-
-        Headers headers = new Headers();
-        headers.put(HTTPSPDYHeader.METHOD.name(version()), "POST");
-        headers.put(HTTPSPDYHeader.URI.name(version()), "/foo");
-        headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1");
-        headers.put(HTTPSPDYHeader.SCHEME.name(version()), "http");
-        headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort());
-        final CountDownLatch replyLatch = new CountDownLatch(1);
-        Stream stream = session.syn(new SynInfo(headers, false), new StreamFrameListener.Adapter()
-        {
-            @Override
-            public void onReply(Stream stream, ReplyInfo replyInfo)
-            {
-                Headers replyHeaders = replyInfo.getHeaders();
-                Assert.assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version())).value().contains("200"));
-                replyLatch.countDown();
-            }
-        }).get(5, TimeUnit.SECONDS);
-        stream.data(new BytesDataInfo(data, false));
-        stream.data(new BytesDataInfo(data, true));
-
-        Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
-        Assert.assertTrue(replyLatch.await(5, TimeUnit.SECONDS));
-    }
-
-    @Test
-    public void testPOSTThenSuspendRequestThenResumeThenRespond() throws Exception
-    {
-        final byte[] data = new byte[1000];
-        final CountDownLatch latch = new CountDownLatch(1);
-        Session session = startClient(version(), startHTTPServer(version(), new AbstractHandler()
-        {
-            @Override
-            public void handle(String target, final Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
-                    throws IOException, ServletException
-            {
-                request.setHandled(true);
-
-                final Continuation continuation = ContinuationSupport.getContinuation(request);
-
-                if (continuation.isInitial())
-                {
-                    InputStream input = request.getInputStream();
-                    byte[] buffer = new byte[256];
-                    int read = 0;
-                    while (read < data.length)
-                        read += input.read(buffer);
-                    continuation.suspend();
-                    new Thread()
-                    {
-                        @Override
-                        public void run()
-                        {
-                            try
-                            {
-                                TimeUnit.SECONDS.sleep(1);
-                                continuation.resume();
-                                latch.countDown();
-                            }
-                            catch (InterruptedException x)
-                            {
-                                x.printStackTrace();
-                            }
-                        }
-                    }.start();
-                }
-                else
-                {
-                    OutputStream output = httpResponse.getOutputStream();
-                    output.write(data);
-                }
-            }
-        }), null);
-
-        Headers headers = new Headers();
-        headers.put(HTTPSPDYHeader.METHOD.name(version()), "POST");
-        headers.put(HTTPSPDYHeader.URI.name(version()), "/foo");
-        headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1");
-        headers.put(HTTPSPDYHeader.SCHEME.name(version()), "http");
-        headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + connector.getLocalPort());
-        final CountDownLatch responseLatch = new CountDownLatch(2);
-        Stream stream = session.syn(new SynInfo(headers, false), new StreamFrameListener.Adapter()
-        {
-            @Override
-            public void onReply(Stream stream, ReplyInfo replyInfo)
-            {
-                Headers replyHeaders = replyInfo.getHeaders();
-                Assert.assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version())).value().contains("200"));
-                responseLatch.countDown();
-            }
-
-            @Override
-            public void onData(Stream stream, DataInfo dataInfo)
-            {
-                if (dataInfo.isClose())
-                    responseLatch.countDown();
-            }
-        }).get(5, TimeUnit.SECONDS);
-        stream.data(new BytesDataInfo(data, true));
-
-        Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
-        Assert.assertTrue(responseLatch.await(5, TimeUnit.SECONDS));
-    }
-}
diff --git a/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/ServerHTTPSPDYv3Test.java b/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/ServerHTTPSPDYv3Test.java
deleted file mode 100644
index 3ab41e3..0000000
--- a/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/http/ServerHTTPSPDYv3Test.java
+++ /dev/null
@@ -1,31 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-
-package org.eclipse.jetty.spdy.http;
-
-import org.eclipse.jetty.spdy.api.SPDY;
-
-public class ServerHTTPSPDYv3Test extends ServerHTTPSPDYv2Test
-{
-    @Override
-    protected short version()
-    {
-        return SPDY.V3;
-    }
-}
diff --git a/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/proxy/ProxyHTTPSPDYv2Test.java b/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/proxy/ProxyHTTPSPDYv2Test.java
deleted file mode 100644
index 089a05c..0000000
--- a/jetty-spdy/spdy-jetty-http/src/test/java/org/eclipse/jetty/spdy/proxy/ProxyHTTPSPDYv2Test.java
+++ /dev/null
@@ -1,769 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.spdy.proxy;
-
-import java.io.BufferedReader;
-import java.io.ByteArrayOutputStream;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.net.InetSocketAddress;
-import java.net.Socket;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.spdy.SPDYClient;
-import org.eclipse.jetty.spdy.SPDYServerConnector;
-import org.eclipse.jetty.spdy.ServerSPDYAsyncConnectionFactory;
-import org.eclipse.jetty.spdy.api.BytesDataInfo;
-import org.eclipse.jetty.spdy.api.DataInfo;
-import org.eclipse.jetty.spdy.api.GoAwayInfo;
-import org.eclipse.jetty.spdy.api.Handler;
-import org.eclipse.jetty.spdy.api.Headers;
-import org.eclipse.jetty.spdy.api.PingInfo;
-import org.eclipse.jetty.spdy.api.ReplyInfo;
-import org.eclipse.jetty.spdy.api.RstInfo;
-import org.eclipse.jetty.spdy.api.SPDY;
-import org.eclipse.jetty.spdy.api.Session;
-import org.eclipse.jetty.spdy.api.SessionFrameListener;
-import org.eclipse.jetty.spdy.api.Stream;
-import org.eclipse.jetty.spdy.api.StreamFrameListener;
-import org.eclipse.jetty.spdy.api.StreamStatus;
-import org.eclipse.jetty.spdy.api.SynInfo;
-import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener;
-import org.eclipse.jetty.spdy.http.HTTPSPDYHeader;
-import org.junit.After;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TestWatchman;
-import org.junit.runners.model.FrameworkMethod;
-
-public class ProxyHTTPSPDYv2Test
-{
-    @Rule
-    public final TestWatchman testName = new TestWatchman()
-    {
-        @Override
-        public void starting(FrameworkMethod method)
-        {
-            super.starting(method);
-            System.err.printf("Running %s.%s()%n",
-                    method.getMethod().getDeclaringClass().getName(),
-                    method.getName());
-        }
-    };
-
-    private SPDYClient.Factory factory;
-    private Server server;
-    private Server proxy;
-    private SPDYServerConnector proxyConnector;
-
-    protected short version()
-    {
-        return SPDY.V2;
-    }
-
-    protected InetSocketAddress startServer(ServerSessionFrameListener listener) throws Exception
-    {
-        server = new Server();
-        SPDYServerConnector serverConnector = new SPDYServerConnector(listener);
-        serverConnector.setDefaultAsyncConnectionFactory(new ServerSPDYAsyncConnectionFactory(version(), serverConnector.getByteBufferPool(), serverConnector.getExecutor(), serverConnector.getScheduler(), listener));
-        serverConnector.setPort(0);
-        server.addConnector(serverConnector);
-        server.start();
-        return new InetSocketAddress("localhost", serverConnector.getLocalPort());
-    }
-
-    protected InetSocketAddress startProxy(InetSocketAddress address) throws Exception
-    {
-        proxy = new Server();
-        ProxyEngineSelector proxyEngineSelector = new ProxyEngineSelector();
-        SPDYProxyEngine spdyProxyEngine = new SPDYProxyEngine(factory);
-        proxyEngineSelector.putProxyEngine("spdy/" + version(), spdyProxyEngine);
-        proxyEngineSelector.putProxyServerInfo("localhost", new ProxyEngineSelector.ProxyServerInfo("spdy/" + version(), address.getHostName(), address.getPort()));
-        proxyConnector = new HTTPSPDYProxyConnector(proxyEngineSelector);
-        proxyConnector.setPort(0);
-        proxy.addConnector(proxyConnector);
-        proxy.start();
-        return new InetSocketAddress("localhost", proxyConnector.getLocalPort());
-    }
-
-    @Before
-    public void init() throws Exception
-    {
-        factory = new SPDYClient.Factory();
-        factory.start();
-    }
-
-    @After
-    public void destroy() throws Exception
-    {
-        if (server != null)
-        {
-            server.stop();
-            server.join();
-        }
-        if (proxy != null)
-        {
-            proxy.stop();
-            proxy.join();
-        }
-        factory.stop();
-    }
-
-    @Test
-    public void testClosingClientDoesNotCloseServer() throws Exception
-    {
-        final CountDownLatch closeLatch = new CountDownLatch(1);
-        InetSocketAddress proxyAddress = startProxy(startServer(new ServerSessionFrameListener.Adapter()
-        {
-            @Override
-            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
-            {
-                Headers responseHeaders = new Headers();
-                responseHeaders.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1");
-                responseHeaders.put(HTTPSPDYHeader.STATUS.name(version()), "200 OK");
-                stream.reply(new ReplyInfo(responseHeaders, true));
-                return null;
-            }
-
-            @Override
-            public void onGoAway(Session session, GoAwayInfo goAwayInfo)
-            {
-                closeLatch.countDown();
-            }
-        }));
-
-        Socket client = new Socket();
-        client.connect(proxyAddress);
-        OutputStream output = client.getOutputStream();
-
-        String request = "" +
-                "GET / HTTP/1.1\r\n" +
-                "Host: localhost:" + proxyAddress.getPort() + "\r\n" +
-                "\r\n";
-        output.write(request.getBytes("UTF-8"));
-        output.flush();
-
-        InputStream input = client.getInputStream();
-        BufferedReader reader = new BufferedReader(new InputStreamReader(input, "UTF-8"));
-        String line = reader.readLine();
-        Assert.assertTrue(line.contains(" 200"));
-        while (line.length() > 0)
-            line = reader.readLine();
-        Assert.assertFalse(reader.ready());
-
-        client.close();
-
-        // Must not close, other clients may still be connected
-        Assert.assertFalse(closeLatch.await(1, TimeUnit.SECONDS));
-    }
-
-    @Test
-    public void testGETThenNoContentFromTwoClients() throws Exception
-    {
-        InetSocketAddress proxyAddress = startProxy(startServer(new ServerSessionFrameListener.Adapter()
-        {
-            @Override
-            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
-            {
-                Assert.assertTrue(synInfo.isClose());
-                Headers requestHeaders = synInfo.getHeaders();
-                Assert.assertNotNull(requestHeaders.get("via"));
-
-                Headers responseHeaders = new Headers();
-                responseHeaders.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1");
-                responseHeaders.put(HTTPSPDYHeader.STATUS.name(version()), "200 OK");
-                ReplyInfo replyInfo = new ReplyInfo(responseHeaders, true);
-                stream.reply(replyInfo);
-                return null;
-            }
-        }));
-
-        Socket client1 = new Socket();
-        client1.connect(proxyAddress);
-        OutputStream output1 = client1.getOutputStream();
-
-        String request = "" +
-                "GET / HTTP/1.1\r\n" +
-                "Host: localhost:" + proxyAddress.getPort() + "\r\n" +
-                "\r\n";
-        output1.write(request.getBytes("UTF-8"));
-        output1.flush();
-
-        InputStream input1 = client1.getInputStream();
-        BufferedReader reader1 = new BufferedReader(new InputStreamReader(input1, "UTF-8"));
-        String line = reader1.readLine();
-        Assert.assertTrue(line.contains(" 200"));
-        while (line.length() > 0)
-            line = reader1.readLine();
-        Assert.assertFalse(reader1.ready());
-
-        // Perform another request with another client
-        Socket client2 = new Socket();
-        client2.connect(proxyAddress);
-        OutputStream output2 = client2.getOutputStream();
-
-        output2.write(request.getBytes("UTF-8"));
-        output2.flush();
-
-        InputStream input2 = client2.getInputStream();
-        BufferedReader reader2 = new BufferedReader(new InputStreamReader(input2, "UTF-8"));
-        line = reader2.readLine();
-        Assert.assertTrue(line.contains(" 200"));
-        while (line.length() > 0)
-            line = reader2.readLine();
-        Assert.assertFalse(reader2.ready());
-
-        client1.close();
-        client2.close();
-    }
-
-    @Test
-    public void testGETThenSmallResponseContent() throws Exception
-    {
-        final byte[] data = "0123456789ABCDEF".getBytes("UTF-8");
-        InetSocketAddress proxyAddress = startProxy(startServer(new ServerSessionFrameListener.Adapter()
-        {
-            @Override
-            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
-            {
-                Assert.assertTrue(synInfo.isClose());
-                Headers requestHeaders = synInfo.getHeaders();
-                Assert.assertNotNull(requestHeaders.get("via"));
-
-                Headers responseHeaders = new Headers();
-                responseHeaders.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1");
-                responseHeaders.put(HTTPSPDYHeader.STATUS.name(version()), "200 OK");
-                ReplyInfo replyInfo = new ReplyInfo(responseHeaders, false);
-                stream.reply(replyInfo);
-                stream.data(new BytesDataInfo(data, true));
-
-                return null;
-            }
-        }));
-
-        Socket client = new Socket();
-        client.connect(proxyAddress);
-        OutputStream output = client.getOutputStream();
-
-        String request = "" +
-                "GET / HTTP/1.1\r\n" +
-                "Host: localhost:" + proxyAddress.getPort() + "\r\n" +
-                "\r\n";
-        output.write(request.getBytes("UTF-8"));
-        output.flush();
-
-        InputStream input = client.getInputStream();
-        BufferedReader reader = new BufferedReader(new InputStreamReader(input, "UTF-8"));
-        String line = reader.readLine();
-        Assert.assertTrue(line.contains(" 200"));
-        while (line.length() > 0)
-            line = reader.readLine();
-        for (byte datum : data)
-            Assert.assertEquals(datum, reader.read());
-        Assert.assertFalse(reader.ready());
-
-        // Perform another request so that we are sure we reset the states of parsers and generators
-        output.write(request.getBytes("UTF-8"));
-        output.flush();
-
-        line = reader.readLine();
-        Assert.assertTrue(line.contains(" 200"));
-        while (line.length() > 0)
-            line = reader.readLine();
-        for (byte datum : data)
-            Assert.assertEquals(datum, reader.read());
-        Assert.assertFalse(reader.ready());
-
-        client.close();
-    }
-
-    @Test
-    public void testPOSTWithSmallRequestContentThenRedirect() throws Exception
-    {
-        final byte[] data = "0123456789ABCDEF".getBytes("UTF-8");
-        InetSocketAddress proxyAddress = startProxy(startServer(new ServerSessionFrameListener.Adapter()
-        {
-            @Override
-            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
-            {
-                return new StreamFrameListener.Adapter()
-                {
-                    @Override
-                    public void onData(Stream stream, DataInfo dataInfo)
-                    {
-                        dataInfo.consume(dataInfo.length());
-                        if (dataInfo.isClose())
-                        {
-                            Headers headers = new Headers();
-                            headers.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1");
-                            headers.put(HTTPSPDYHeader.STATUS.name(version()), "303 See Other");
-                            stream.reply(new ReplyInfo(headers, true));
-                        }
-                    }
-                };
-            }
-        }));
-
-        Socket client = new Socket();
-        client.connect(proxyAddress);
-        OutputStream output = client.getOutputStream();
-
-        String request = "" +
-                "POST / HTTP/1.1\r\n" +
-                "Host: localhost:" + proxyAddress.getPort() + "\r\n" +
-                "Content-Length: " + data.length + "\r\n" +
-                "Content-Type: application/octet-stream\r\n" +
-                "\r\n";
-        output.write(request.getBytes("UTF-8"));
-        output.write(data);
-        output.flush();
-
-        InputStream input = client.getInputStream();
-        BufferedReader reader = new BufferedReader(new InputStreamReader(input, "UTF-8"));
-        String line = reader.readLine();
-        Assert.assertTrue(line.contains(" 303"));
-        while (line.length() > 0)
-            line = reader.readLine();
-        Assert.assertFalse(reader.ready());
-
-        // Perform another request so that we are sure we reset the states of parsers and generators
-        output.write(request.getBytes("UTF-8"));
-        output.write(data);
-        output.flush();
-
-        line = reader.readLine();
-        Assert.assertTrue(line.contains(" 303"));
-        while (line.length() > 0)
-            line = reader.readLine();
-        Assert.assertFalse(reader.ready());
-
-        client.close();
-    }
-
-    @Test
-    public void testPOSTWithSmallRequestContentThenSmallResponseContent() throws Exception
-    {
-        final byte[] data = "0123456789ABCDEF".getBytes("UTF-8");
-        InetSocketAddress proxyAddress = startProxy(startServer(new ServerSessionFrameListener.Adapter()
-        {
-            @Override
-            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
-            {
-                return new StreamFrameListener.Adapter()
-                {
-                    @Override
-                    public void onData(Stream stream, DataInfo dataInfo)
-                    {
-                        dataInfo.consume(dataInfo.length());
-                        if (dataInfo.isClose())
-                        {
-                            Headers responseHeaders = new Headers();
-                            responseHeaders.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1");
-                            responseHeaders.put(HTTPSPDYHeader.STATUS.name(version()), "200 OK");
-                            ReplyInfo replyInfo = new ReplyInfo(responseHeaders, false);
-                            stream.reply(replyInfo);
-                            stream.data(new BytesDataInfo(data, true));
-                        }
-                    }
-                };
-            }
-        }));
-
-        Socket client = new Socket();
-        client.connect(proxyAddress);
-        OutputStream output = client.getOutputStream();
-
-        String request = "" +
-                "POST / HTTP/1.1\r\n" +
-                "Host: localhost:" + proxyAddress.getPort() + "\r\n" +
-                "Content-Length: " + data.length + "\r\n" +
-                "Content-Type: application/octet-stream\r\n" +
-                "\r\n";
-        output.write(request.getBytes("UTF-8"));
-        output.write(data);
-        output.flush();
-
-        InputStream input = client.getInputStream();
-        BufferedReader reader = new BufferedReader(new InputStreamReader(input, "UTF-8"));
-        String line = reader.readLine();
-        Assert.assertTrue(line.contains(" 200"));
-        while (line.length() > 0)
-            line = reader.readLine();
-        for (byte datum : data)
-            Assert.assertEquals(datum, reader.read());
-        Assert.assertFalse(reader.ready());
-
-        // Perform another request so that we are sure we reset the states of parsers and generators
-        output.write(request.getBytes("UTF-8"));
-        output.write(data);
-        output.flush();
-
-        line = reader.readLine();
-        Assert.assertTrue(line.contains(" 200"));
-        while (line.length() > 0)
-            line = reader.readLine();
-        for (byte datum : data)
-            Assert.assertEquals(datum, reader.read());
-        Assert.assertFalse(reader.ready());
-
-        client.close();
-    }
-
-    @Test
-    public void testSYNThenREPLY() throws Exception
-    {
-        final String header = "foo";
-        InetSocketAddress proxyAddress = startProxy(startServer(new ServerSessionFrameListener.Adapter()
-        {
-            @Override
-            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
-            {
-                Headers requestHeaders = synInfo.getHeaders();
-                Assert.assertNotNull(requestHeaders.get("via"));
-                Assert.assertNotNull(requestHeaders.get(header));
-
-                Headers responseHeaders = new Headers();
-                responseHeaders.put(header, "baz");
-                stream.reply(new ReplyInfo(responseHeaders, true));
-                return null;
-            }
-        }));
-        proxyConnector.setDefaultAsyncConnectionFactory(proxyConnector.getAsyncConnectionFactory("spdy/" + version()));
-
-        Session client = factory.newSPDYClient(version()).connect(proxyAddress, null).get(5, TimeUnit.SECONDS);
-
-        final CountDownLatch replyLatch = new CountDownLatch(1);
-        Headers headers = new Headers();
-        headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + proxyAddress.getPort());
-        headers.put(header, "bar");
-        client.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter()
-        {
-            @Override
-            public void onReply(Stream stream, ReplyInfo replyInfo)
-            {
-                Headers headers = replyInfo.getHeaders();
-                Assert.assertNotNull(headers.get(header));
-                replyLatch.countDown();
-            }
-        });
-
-        Assert.assertTrue(replyLatch.await(5, TimeUnit.SECONDS));
-
-        client.goAway().get(5, TimeUnit.SECONDS);
-    }
-
-    @Test
-    public void testSYNThenREPLYAndDATA() throws Exception
-    {
-        final byte[] data = "0123456789ABCDEF".getBytes("UTF-8");
-        final String header = "foo";
-        InetSocketAddress proxyAddress = startProxy(startServer(new ServerSessionFrameListener.Adapter()
-        {
-            @Override
-            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
-            {
-                Headers requestHeaders = synInfo.getHeaders();
-                Assert.assertNotNull(requestHeaders.get("via"));
-                Assert.assertNotNull(requestHeaders.get(header));
-
-                Headers responseHeaders = new Headers();
-                responseHeaders.put(header, "baz");
-                stream.reply(new ReplyInfo(responseHeaders, false));
-                stream.data(new BytesDataInfo(data, true));
-                return null;
-            }
-        }));
-        proxyConnector.setDefaultAsyncConnectionFactory(proxyConnector.getAsyncConnectionFactory("spdy/" + version()));
-
-        Session client = factory.newSPDYClient(version()).connect(proxyAddress, null).get(5, TimeUnit.SECONDS);
-
-        final CountDownLatch replyLatch = new CountDownLatch(1);
-        final CountDownLatch dataLatch = new CountDownLatch(1);
-        Headers headers = new Headers();
-        headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + proxyAddress.getPort());
-        headers.put(header, "bar");
-        client.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter()
-        {
-            private final ByteArrayOutputStream result = new ByteArrayOutputStream();
-
-            @Override
-            public void onReply(Stream stream, ReplyInfo replyInfo)
-            {
-                Headers headers = replyInfo.getHeaders();
-                Assert.assertNotNull(headers.get(header));
-                replyLatch.countDown();
-            }
-
-            @Override
-            public void onData(Stream stream, DataInfo dataInfo)
-            {
-                result.write(dataInfo.asBytes(true), 0, dataInfo.length());
-                if (dataInfo.isClose())
-                {
-                    Assert.assertArrayEquals(data, result.toByteArray());
-                    dataLatch.countDown();
-                }
-            }
-        });
-
-        Assert.assertTrue(replyLatch.await(5, TimeUnit.SECONDS));
-        Assert.assertTrue(dataLatch.await(5, TimeUnit.SECONDS));
-
-        client.goAway().get(5, TimeUnit.SECONDS);
-    }
-
-    @Test
-    public void testGETThenSPDYPushIsIgnored() throws Exception
-    {
-        final byte[] data = "0123456789ABCDEF".getBytes("UTF-8");
-        InetSocketAddress proxyAddress = startProxy(startServer(new ServerSessionFrameListener.Adapter()
-        {
-            @Override
-            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
-            {
-                Headers responseHeaders = new Headers();
-                responseHeaders.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1");
-                responseHeaders.put(HTTPSPDYHeader.STATUS.name(version()), "200 OK");
-
-                Headers pushHeaders = new Headers();
-                pushHeaders.put(HTTPSPDYHeader.URI.name(version()), "/push");
-                stream.syn(new SynInfo(pushHeaders, false), 5, TimeUnit.SECONDS, new Handler.Adapter<Stream>()
-                {
-                    @Override
-                    public void completed(Stream pushStream)
-                    {
-                        pushStream.data(new BytesDataInfo(data, true));
-                    }
-                });
-
-                stream.reply(new ReplyInfo(responseHeaders, true));
-                return null;
-            }
-        }));
-
-        Socket client = new Socket();
-        client.connect(proxyAddress);
-        OutputStream output = client.getOutputStream();
-
-        String request = "" +
-                "GET / HTTP/1.1\r\n" +
-                "Host: localhost:" + proxyAddress.getPort() + "\r\n" +
-                "\r\n";
-        output.write(request.getBytes("UTF-8"));
-        output.flush();
-
-        client.setSoTimeout(1000);
-        InputStream input = client.getInputStream();
-        BufferedReader reader = new BufferedReader(new InputStreamReader(input, "UTF-8"));
-        String line = reader.readLine();
-        Assert.assertTrue(line.contains(" 200"));
-        while (line.length() > 0)
-            line = reader.readLine();
-        Assert.assertFalse(reader.ready());
-
-        client.close();
-    }
-
-    @Test
-    public void testSYNThenSPDYPushIsReceived() throws Exception
-    {
-        final byte[] data = "0123456789ABCDEF".getBytes("UTF-8");
-        InetSocketAddress proxyAddress = startProxy(startServer(new ServerSessionFrameListener.Adapter()
-        {
-            @Override
-            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
-            {
-                Headers responseHeaders = new Headers();
-                responseHeaders.put(HTTPSPDYHeader.VERSION.name(version()), "HTTP/1.1");
-                responseHeaders.put(HTTPSPDYHeader.STATUS.name(version()), "200 OK");
-                stream.reply(new ReplyInfo(responseHeaders, false));
-
-                Headers pushHeaders = new Headers();
-                pushHeaders.put(HTTPSPDYHeader.URI.name(version()), "/push");
-                stream.syn(new SynInfo(pushHeaders, false), 5, TimeUnit.SECONDS, new Handler.Adapter<Stream>()
-                {
-                    @Override
-                    public void completed(Stream pushStream)
-                    {
-                        pushStream.data(new BytesDataInfo(data, true));
-                    }
-                });
-
-                stream.data(new BytesDataInfo(data, true));
-
-                return null;
-            }
-        }));
-        proxyConnector.setDefaultAsyncConnectionFactory(proxyConnector.getAsyncConnectionFactory("spdy/" + version()));
-
-        final CountDownLatch pushSynLatch = new CountDownLatch(1);
-        final CountDownLatch pushDataLatch = new CountDownLatch(1);
-        Session client = factory.newSPDYClient(version()).connect(proxyAddress, new SessionFrameListener.Adapter()
-        {
-            @Override
-            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
-            {
-                pushSynLatch.countDown();
-                return new StreamFrameListener.Adapter()
-                {
-                    @Override
-                    public void onData(Stream stream, DataInfo dataInfo)
-                    {
-                        dataInfo.consume(dataInfo.length());
-                        if (dataInfo.isClose())
-                            pushDataLatch.countDown();
-                    }
-                };
-            }
-        }).get(5, TimeUnit.SECONDS);
-
-        Headers headers = new Headers();
-        headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + proxyAddress.getPort());
-        final CountDownLatch replyLatch = new CountDownLatch(1);
-        final CountDownLatch dataLatch = new CountDownLatch(1);
-        client.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter()
-        {
-            @Override
-            public void onReply(Stream stream, ReplyInfo replyInfo)
-            {
-                replyLatch.countDown();
-            }
-
-            @Override
-            public void onData(Stream stream, DataInfo dataInfo)
-            {
-                dataInfo.consume(dataInfo.length());
-                if (dataInfo.isClose())
-                    dataLatch.countDown();
-            }
-        });
-
-        Assert.assertTrue(replyLatch.await(5, TimeUnit.SECONDS));
-        Assert.assertTrue(pushSynLatch.await(5, TimeUnit.SECONDS));
-        Assert.assertTrue(pushDataLatch.await(5, TimeUnit.SECONDS));
-        Assert.assertTrue(dataLatch.await(5, TimeUnit.SECONDS));
-
-        client.goAway().get(5, TimeUnit.SECONDS);
-    }
-
-    @Test
-    public void testPING() throws Exception
-    {
-        // PING is per hop, and it does not carry the information to which server to ping to
-        // We just verify that it works
-
-        InetSocketAddress proxyAddress = startProxy(startServer(new ServerSessionFrameListener.Adapter()));
-        proxyConnector.setDefaultAsyncConnectionFactory(proxyConnector.getAsyncConnectionFactory("spdy/" + version()));
-
-        final CountDownLatch pingLatch = new CountDownLatch(1);
-        Session client = factory.newSPDYClient(version()).connect(proxyAddress, new SessionFrameListener.Adapter()
-        {
-            @Override
-            public void onPing(Session session, PingInfo pingInfo)
-            {
-                pingLatch.countDown();
-            }
-        }).get(5, TimeUnit.SECONDS);
-
-        client.ping().get(5, TimeUnit.SECONDS);
-
-        Assert.assertTrue(pingLatch.await(5, TimeUnit.SECONDS));
-
-        client.goAway().get(5, TimeUnit.SECONDS);
-    }
-
-    @Test
-    public void testGETThenReset() throws Exception
-    {
-        InetSocketAddress proxyAddress = startProxy(startServer(new ServerSessionFrameListener.Adapter()
-        {
-            @Override
-            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
-            {
-                Assert.assertTrue(synInfo.isClose());
-                Headers requestHeaders = synInfo.getHeaders();
-                Assert.assertNotNull(requestHeaders.get("via"));
-
-                stream.getSession().rst(new RstInfo(stream.getId(), StreamStatus.REFUSED_STREAM));
-
-                return null;
-            }
-        }));
-
-        Socket client = new Socket();
-        client.connect(proxyAddress);
-        OutputStream output = client.getOutputStream();
-
-        String request = "" +
-                "GET / HTTP/1.1\r\n" +
-                "Host: localhost:" + proxyAddress.getPort() + "\r\n" +
-                "\r\n";
-        output.write(request.getBytes("UTF-8"));
-        output.flush();
-
-        InputStream input = client.getInputStream();
-        BufferedReader reader = new BufferedReader(new InputStreamReader(input, "UTF-8"));
-        Assert.assertNull(reader.readLine());
-
-        client.close();
-    }
-
-    @Test
-    public void testSYNThenReset() throws Exception
-    {
-        InetSocketAddress proxyAddress = startProxy(startServer(new ServerSessionFrameListener.Adapter()
-        {
-            @Override
-            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
-            {
-                Assert.assertTrue(synInfo.isClose());
-                Headers requestHeaders = synInfo.getHeaders();
-                Assert.assertNotNull(requestHeaders.get("via"));
-
-                stream.getSession().rst(new RstInfo(stream.getId(), StreamStatus.REFUSED_STREAM));
-
-                return null;
-            }
-        }));
-        proxyConnector.setDefaultAsyncConnectionFactory(proxyConnector.getAsyncConnectionFactory("spdy/" + version()));
-
-        final CountDownLatch resetLatch = new CountDownLatch(1);
-        Session client = factory.newSPDYClient(version()).connect(proxyAddress, new SessionFrameListener.Adapter()
-        {
-            @Override
-            public void onRst(Session session, RstInfo rstInfo)
-            {
-                resetLatch.countDown();
-            }
-        }).get(5, TimeUnit.SECONDS);
-
-        Headers headers = new Headers();
-        headers.put(HTTPSPDYHeader.HOST.name(version()), "localhost:" + proxyAddress.getPort());
-        client.syn(new SynInfo(headers, true), null);
-
-        Assert.assertTrue(resetLatch.await(5, TimeUnit.SECONDS));
-
-        client.goAway().get(5, TimeUnit.SECONDS);
-    }
-}
diff --git a/jetty-spdy/spdy-jetty-http/src/test/resources/log4j.properties b/jetty-spdy/spdy-jetty-http/src/test/resources/log4j.properties
deleted file mode 100644
index aa88d64..0000000
--- a/jetty-spdy/spdy-jetty-http/src/test/resources/log4j.properties
+++ /dev/null
@@ -1,14 +0,0 @@
-# LOG4J levels: OFF, FATAL, ERROR, WARN, INFO, DEBUG, ALL
-#
-log4j.rootLogger=ALL,CONSOLE
-
-log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
-#log4j.appender.CONSOLE.threshold=INFO
-log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
-#log4j.appender.CONSOLE.layout.ConversionPattern=%d %t [%5p][%c{1}] %m%n
-log4j.appender.CONSOLE.layout.ConversionPattern=%d %t [%5p][%c{1}] %m%n
-log4j.appender.CONSOLE.target=System.err
-
-# Level tuning
-log4j.logger.org.eclipse.jetty=INFO
-#log4j.logger.org.eclipse.jetty.spdy=DEBUG
diff --git a/jetty-spdy/spdy-jetty/pom.xml b/jetty-spdy/spdy-jetty/pom.xml
deleted file mode 100644
index 686b614..0000000
--- a/jetty-spdy/spdy-jetty/pom.xml
+++ /dev/null
@@ -1,82 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<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/xsd/maven-4.0.0.xsd">
-    <parent>
-        <groupId>org.eclipse.jetty.spdy</groupId>
-        <artifactId>spdy-parent</artifactId>
-        <version>8.1.17.v20150415</version>
-    </parent>
-    <modelVersion>4.0.0</modelVersion>
-    <artifactId>spdy-jetty</artifactId>
-    <name>Jetty :: SPDY :: Jetty Binding</name>
-    <url>http://www.eclipse.org/jetty</url>
-    <build>
-        <plugins>
-            <plugin>
-                <artifactId>maven-dependency-plugin</artifactId>
-                <executions>
-                    <execution>
-                        <id>copy</id>
-                        <phase>generate-resources</phase>
-                        <goals>
-                            <goal>copy</goal>
-                        </goals>
-                        <configuration>
-                            <artifactItems>
-                                <artifactItem>
-                                    <groupId>org.mortbay.jetty.npn</groupId>
-                                    <artifactId>npn-boot</artifactId>
-                                    <version>${npn.version}</version>
-                                    <type>jar</type>
-                                    <overWrite>false</overWrite>
-                                    <outputDirectory>${project.build.directory}/npn</outputDirectory>
-                                </artifactItem>
-                            </artifactItems>
-                        </configuration>
-                    </execution>
-                </executions>
-            </plugin>
-            <plugin>
-                <artifactId>maven-surefire-plugin</artifactId>
-                <configuration>
-                    <argLine>-Xbootclasspath/p:${project.build.directory}/npn/npn-boot-${npn.version}.jar</argLine>
-                </configuration>
-            </plugin>
-        </plugins>
-    </build>
-
-    <dependencies>
-        <dependency>
-            <groupId>org.eclipse.jetty.spdy</groupId>
-            <artifactId>spdy-core</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-        <dependency>
-            <groupId>org.eclipse.jetty</groupId>
-            <artifactId>jetty-server</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-        <dependency>
-            <groupId>org.eclipse.jetty.npn</groupId>
-            <artifactId>npn-api</artifactId>
-            <version>${npn.api.version}</version>
-            <scope>provided</scope>
-        </dependency>
-        <dependency>
-            <groupId>junit</groupId>
-            <artifactId>junit</artifactId>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-          <groupId>org.hamcrest</groupId>
-          <artifactId>hamcrest-library</artifactId>
-          <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.slf4j</groupId>
-            <artifactId>slf4j-log4j12</artifactId>
-            <version>${slf4j-version}</version>
-            <scope>test</scope>
-        </dependency>
-    </dependencies>
-
-</project>
diff --git a/jetty-spdy/spdy-jetty/src/main/java/org/eclipse/jetty/spdy/AsyncConnectionFactory.java b/jetty-spdy/spdy-jetty/src/main/java/org/eclipse/jetty/spdy/AsyncConnectionFactory.java
deleted file mode 100644
index eb9ce1e..0000000
--- a/jetty-spdy/spdy-jetty/src/main/java/org/eclipse/jetty/spdy/AsyncConnectionFactory.java
+++ /dev/null
@@ -1,30 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-
-package org.eclipse.jetty.spdy;
-
-import java.nio.channels.SocketChannel;
-
-import org.eclipse.jetty.io.AsyncEndPoint;
-import org.eclipse.jetty.io.nio.AsyncConnection;
-
-public interface AsyncConnectionFactory
-{
-    public AsyncConnection newAsyncConnection(SocketChannel channel, AsyncEndPoint endPoint, Object attachment);
-}
diff --git a/jetty-spdy/spdy-jetty/src/main/java/org/eclipse/jetty/spdy/EmptyAsyncConnection.java b/jetty-spdy/spdy-jetty/src/main/java/org/eclipse/jetty/spdy/EmptyAsyncConnection.java
deleted file mode 100644
index 9ff9f28..0000000
--- a/jetty-spdy/spdy-jetty/src/main/java/org/eclipse/jetty/spdy/EmptyAsyncConnection.java
+++ /dev/null
@@ -1,68 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-
-package org.eclipse.jetty.spdy;
-
-import java.io.IOException;
-
-import org.eclipse.jetty.io.AbstractConnection;
-import org.eclipse.jetty.io.AsyncEndPoint;
-import org.eclipse.jetty.io.Connection;
-import org.eclipse.jetty.io.nio.AsyncConnection;
-
-public class EmptyAsyncConnection extends AbstractConnection implements AsyncConnection
-{
-    public EmptyAsyncConnection(AsyncEndPoint endPoint)
-    {
-        super(endPoint);
-    }
-
-    public Connection handle() throws IOException
-    {
-        return this;
-    }
-
-    @Override
-    public AsyncEndPoint getEndPoint()
-    {
-        return (AsyncEndPoint)super.getEndPoint();
-    }
-
-    @Override
-    public boolean isIdle()
-    {
-        return false;
-    }
-
-    @Override
-    public boolean isSuspended()
-    {
-        return false;
-    }
-
-    @Override
-    public void onClose()
-    {
-    }
-
-    @Override
-    public void onInputShutdown() throws IOException
-    {
-    }
-}
diff --git a/jetty-spdy/spdy-jetty/src/main/java/org/eclipse/jetty/spdy/EmptyAsyncEndPoint.java b/jetty-spdy/spdy-jetty/src/main/java/org/eclipse/jetty/spdy/EmptyAsyncEndPoint.java
deleted file mode 100644
index 42cd9dd..0000000
--- a/jetty-spdy/spdy-jetty/src/main/java/org/eclipse/jetty/spdy/EmptyAsyncEndPoint.java
+++ /dev/null
@@ -1,234 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-
-package org.eclipse.jetty.spdy;
-
-import java.io.IOException;
-
-import org.eclipse.jetty.io.AsyncEndPoint;
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.Connection;
-import org.eclipse.jetty.util.thread.Timeout;
-
-public class EmptyAsyncEndPoint implements AsyncEndPoint
-{
-    private boolean checkForIdle;
-    private Connection connection;
-    private boolean oshut;
-    private boolean ishut;
-    private boolean closed;
-    private int maxIdleTime;
-
-    @Override
-    public void dispatch()
-    {
-    }
-    
-    @Override
-    public void asyncDispatch()
-    {
-    }
-
-    @Override
-    public void scheduleWrite()
-    {
-    }
-
-    @Override
-    public void onIdleExpired(long idleForMs)
-    {
-    }
-
-    @Override
-    public void setCheckForIdle(boolean check)
-    {
-        this.checkForIdle = check;
-    }
-
-    @Override
-    public boolean isCheckForIdle()
-    {
-        return checkForIdle;
-    }
-
-    @Override
-    public boolean isWritable()
-    {
-        return false;
-    }
-
-    @Override
-    public boolean hasProgressed()
-    {
-        return false;
-    }
-
-    @Override
-    public void scheduleTimeout(Timeout.Task task, long timeoutMs)
-    {
-    }
-
-    @Override
-    public void cancelTimeout(Timeout.Task task)
-    {
-    }
-
-    @Override
-    public Connection getConnection()
-    {
-        return connection;
-    }
-
-    @Override
-    public void setConnection(Connection connection)
-    {
-        this.connection = connection;
-    }
-
-    @Override
-    public void shutdownOutput() throws IOException
-    {
-        oshut = true;
-    }
-
-    @Override
-    public boolean isOutputShutdown()
-    {
-        return oshut;
-    }
-
-    @Override
-    public void shutdownInput() throws IOException
-    {
-        ishut = true;
-    }
-
-    @Override
-    public boolean isInputShutdown()
-    {
-        return ishut;
-    }
-
-    @Override
-    public void close() throws IOException
-    {
-        closed = true;
-    }
-
-    @Override
-    public int fill(Buffer buffer) throws IOException
-    {
-        return 0;
-    }
-
-    @Override
-    public int flush(Buffer buffer) throws IOException
-    {
-        return 0;
-    }
-
-    @Override
-    public int flush(Buffer header, Buffer buffer, Buffer trailer) throws IOException
-    {
-        return 0;
-    }
-
-    @Override
-    public String getLocalAddr()
-    {
-        return null;
-    }
-
-    @Override
-    public String getLocalHost()
-    {
-        return null;
-    }
-
-    @Override
-    public int getLocalPort()
-    {
-        return -1;
-    }
-
-    @Override
-    public String getRemoteAddr()
-    {
-        return null;
-    }
-
-    @Override
-    public String getRemoteHost()
-    {
-        return null;
-    }
-
-    @Override
-    public int getRemotePort()
-    {
-        return -1;
-    }
-
-    @Override
-    public boolean isBlocking()
-    {
-        return false;
-    }
-
-    @Override
-    public boolean blockReadable(long millisecs) throws IOException
-    {
-        return false;
-    }
-
-    @Override
-    public boolean blockWritable(long millisecs) throws IOException
-    {
-        return false;
-    }
-
-    @Override
-    public boolean isOpen()
-    {
-        return !closed;
-    }
-
-    @Override
-    public Object getTransport()
-    {
-        return null;
-    }
-
-    @Override
-    public void flush() throws IOException
-    {
-    }
-
-    @Override
-    public int getMaxIdleTime()
-    {
-        return maxIdleTime;
-    }
-
-    @Override
-    public void setMaxIdleTime(int timeMs) throws IOException
-    {
-        this.maxIdleTime = timeMs;
-    }
-}
diff --git a/jetty-spdy/spdy-jetty/src/main/java/org/eclipse/jetty/spdy/FlowControlStrategyFactory.java b/jetty-spdy/spdy-jetty/src/main/java/org/eclipse/jetty/spdy/FlowControlStrategyFactory.java
deleted file mode 100644
index c510225..0000000
--- a/jetty-spdy/spdy-jetty/src/main/java/org/eclipse/jetty/spdy/FlowControlStrategyFactory.java
+++ /dev/null
@@ -1,42 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-
-package org.eclipse.jetty.spdy;
-
-import org.eclipse.jetty.spdy.api.SPDY;
-
-public class FlowControlStrategyFactory
-{
-    private FlowControlStrategyFactory()
-    {
-    }
-
-    public static FlowControlStrategy newFlowControlStrategy(short version)
-    {
-        switch (version)
-        {
-            case SPDY.V2:
-                return new FlowControlStrategy.None();
-            case SPDY.V3:
-                return new SPDYv3FlowControlStrategy();
-            default:
-                throw new IllegalStateException();
-        }
-    }
-}
diff --git a/jetty-spdy/spdy-jetty/src/main/java/org/eclipse/jetty/spdy/SPDYAsyncConnection.java b/jetty-spdy/spdy-jetty/src/main/java/org/eclipse/jetty/spdy/SPDYAsyncConnection.java
deleted file mode 100644
index b0f23ee..0000000
--- a/jetty-spdy/spdy-jetty/src/main/java/org/eclipse/jetty/spdy/SPDYAsyncConnection.java
+++ /dev/null
@@ -1,247 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-
-package org.eclipse.jetty.spdy;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-
-import org.eclipse.jetty.io.AbstractConnection;
-import org.eclipse.jetty.io.AsyncEndPoint;
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.Connection;
-import org.eclipse.jetty.io.nio.AsyncConnection;
-import org.eclipse.jetty.io.nio.DirectNIOBuffer;
-import org.eclipse.jetty.io.nio.IndirectNIOBuffer;
-import org.eclipse.jetty.io.nio.NIOBuffer;
-import org.eclipse.jetty.spdy.api.Handler;
-import org.eclipse.jetty.spdy.api.Session;
-import org.eclipse.jetty.spdy.parser.Parser;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-
-public class SPDYAsyncConnection extends AbstractConnection implements AsyncConnection, Controller<StandardSession.FrameBytes>, IdleListener
-{
-    private static final Logger logger = Log.getLogger(SPDYAsyncConnection.class);
-    private final ByteBufferPool bufferPool;
-    private final Parser parser;
-    private volatile Session session;
-    private ByteBuffer writeBuffer;
-    private Handler<StandardSession.FrameBytes> writeHandler;
-    private StandardSession.FrameBytes writeContext;
-    private volatile boolean writePending;
-
-    public SPDYAsyncConnection(AsyncEndPoint endPoint, ByteBufferPool bufferPool, Parser parser)
-    {
-        super(endPoint);
-        this.bufferPool = bufferPool;
-        this.parser = parser;
-        onIdle(true);
-    }
-
-    @Override
-    public Connection handle() throws IOException
-    {
-        AsyncEndPoint endPoint = getEndPoint();
-        boolean progress = true;
-        while (endPoint.isOpen() && progress)
-        {
-            int filled = fill();
-            progress = filled > 0;
-
-            int flushed = flush();
-            progress |= flushed > 0;
-
-            endPoint.flush();
-
-            progress |= endPoint.hasProgressed();
-
-            if (!progress && filled < 0)
-            {
-                onInputShutdown();
-                close(false);
-            }
-        }
-        return this;
-    }
-
-    public int fill() throws IOException
-    {
-        ByteBuffer buffer = bufferPool.acquire(8192, true);
-        NIOBuffer jettyBuffer = new DirectNIOBuffer(buffer, false);
-        jettyBuffer.setPutIndex(jettyBuffer.getIndex());
-        AsyncEndPoint endPoint = getEndPoint();
-        int filled = endPoint.fill(jettyBuffer);
-        logger.debug("Filled {} from {}", filled, endPoint);
-        if (filled <= 0)
-            return filled;
-
-        buffer.limit(jettyBuffer.putIndex());
-        buffer.position(jettyBuffer.getIndex());
-        parser.parse(buffer);
-
-        bufferPool.release(buffer);
-
-        return filled;
-    }
-
-    public int flush()
-    {
-        int result = 0;
-        // Volatile read to ensure visibility of buffer and handler
-        if (writePending)
-            result = write(writeBuffer, writeHandler, writeContext);
-        logger.debug("Flushed {} to {}", result, getEndPoint());
-        return result;
-    }
-
-    @Override
-    public int write(ByteBuffer buffer, Handler<StandardSession.FrameBytes> handler, StandardSession.FrameBytes context)
-    {
-        int remaining = buffer.remaining();
-        Buffer jettyBuffer = buffer.isDirect() ? new DirectNIOBuffer(buffer, false) : new IndirectNIOBuffer(buffer, false);
-        AsyncEndPoint endPoint = getEndPoint();
-        try
-        {
-            int written = endPoint.flush(jettyBuffer);
-            logger.debug("Written {} bytes, {} remaining", written, jettyBuffer.length());
-        }
-        catch (Exception x)
-        {
-            close(false);
-            handler.failed(context, x);
-            return -1;
-        }
-        finally
-        {
-            buffer.limit(jettyBuffer.putIndex());
-            buffer.position(jettyBuffer.getIndex());
-        }
-
-        if (buffer.hasRemaining())
-        {
-            // Save buffer and handler in order to finish the write later in flush()
-            this.writeBuffer = buffer;
-            this.writeHandler = handler;
-            this.writeContext = context;
-            // Volatile write to ensure visibility of write fields
-            writePending = true;
-            endPoint.scheduleWrite();
-        }
-        else
-        {
-            if (writePending)
-            {
-                this.writeBuffer = null;
-                this.writeHandler = null;
-                this.writeContext = null;
-                // Volatile write to ensure visibility of write fields
-                writePending = false;
-            }
-            handler.completed(context);
-        }
-
-        return remaining - buffer.remaining();
-    }
-
-    @Override
-    public void close(boolean onlyOutput)
-    {
-        try
-        {
-            AsyncEndPoint endPoint = getEndPoint();
-            try
-            {
-                // We need to gently close first, to allow
-                // SSL close alerts to be sent by Jetty
-                logger.debug("Shutting down output {}", endPoint);
-                endPoint.shutdownOutput();
-                if (!onlyOutput)
-                {
-                    logger.debug("Closing {}", endPoint);
-                    endPoint.close();
-                }
-            }
-            catch (IOException x)
-            {
-                endPoint.close();
-            }
-        }
-        catch (IOException x)
-        {
-            logger.ignore(x);
-        }
-    }
-
-    @Override
-    public void onIdle(boolean idle)
-    {
-        getEndPoint().setCheckForIdle(idle);
-    }
-
-    @Override
-    public AsyncEndPoint getEndPoint()
-    {
-        return (AsyncEndPoint)super.getEndPoint();
-    }
-
-    @Override
-    public boolean isIdle()
-    {
-        return false;
-    }
-
-    @Override
-    public boolean isSuspended()
-    {
-        return false;
-    }
-
-    @Override
-    public void onClose()
-    {
-    }
-
-    @Override
-    public void onInputShutdown() throws IOException
-    {
-    }
-
-    @Override
-    public void onIdleExpired(long idleForMs)
-    {
-        logger.debug("Idle timeout expired for {}", getEndPoint());
-        session.goAway();
-    }
-
-    protected Session getSession()
-    {
-        return session;
-    }
-
-    protected void setSession(Session session)
-    {
-        this.session = session;
-    }
-
-    public String toString()
-    {
-        return String.format("%s@%x{endp=%s@%x}",getClass().getSimpleName(),hashCode(),getEndPoint().getClass().getSimpleName(),getEndPoint().hashCode());
-    }
-}
diff --git a/jetty-spdy/spdy-jetty/src/main/java/org/eclipse/jetty/spdy/SPDYClient.java b/jetty-spdy/spdy-jetty/src/main/java/org/eclipse/jetty/spdy/SPDYClient.java
deleted file mode 100644
index 83de9cd..0000000
--- a/jetty-spdy/spdy-jetty/src/main/java/org/eclipse/jetty/spdy/SPDYClient.java
+++ /dev/null
@@ -1,494 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-
-package org.eclipse.jetty.spdy;
-
-import java.io.IOException;
-import java.net.InetSocketAddress;
-import java.net.SocketAddress;
-import java.nio.channels.SelectionKey;
-import java.nio.channels.SocketChannel;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Queue;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
-import java.util.concurrent.RejectedExecutionException;
-import java.util.concurrent.ScheduledExecutorService;
-import javax.net.ssl.SSLEngine;
-import javax.net.ssl.SSLException;
-
-import org.eclipse.jetty.io.AsyncEndPoint;
-import org.eclipse.jetty.io.ConnectedEndPoint;
-import org.eclipse.jetty.io.Connection;
-import org.eclipse.jetty.io.nio.AsyncConnection;
-import org.eclipse.jetty.io.nio.SelectChannelEndPoint;
-import org.eclipse.jetty.io.nio.SelectorManager;
-import org.eclipse.jetty.io.nio.SslConnection;
-import org.eclipse.jetty.npn.NextProtoNego;
-import org.eclipse.jetty.spdy.api.Session;
-import org.eclipse.jetty.spdy.api.SessionFrameListener;
-import org.eclipse.jetty.spdy.generator.Generator;
-import org.eclipse.jetty.spdy.parser.Parser;
-import org.eclipse.jetty.util.component.AggregateLifeCycle;
-import org.eclipse.jetty.util.ssl.SslContextFactory;
-import org.eclipse.jetty.util.thread.QueuedThreadPool;
-
-public class SPDYClient
-{
-    private final Map<String, AsyncConnectionFactory> factories = new ConcurrentHashMap<>();
-    private final short version;
-    private final Factory factory;
-    private SocketAddress bindAddress;
-    private long maxIdleTime = -1;
-    private volatile int initialWindowSize = 65536;
-
-    protected SPDYClient(short version, Factory factory)
-    {
-        this.version = version;
-        this.factory = factory;
-    }
-
-    /**
-     * @return the address to bind the socket channel to
-     * @see #setBindAddress(SocketAddress)
-     */
-    public SocketAddress getBindAddress()
-    {
-        return bindAddress;
-    }
-
-    /**
-     * @param bindAddress the address to bind the socket channel to
-     * @see #getBindAddress()
-     */
-    public void setBindAddress(SocketAddress bindAddress)
-    {
-        this.bindAddress = bindAddress;
-    }
-
-    public Future<Session> connect(InetSocketAddress address, SessionFrameListener listener) throws IOException
-    {
-        if (!factory.isStarted())
-            throw new IllegalStateException(Factory.class.getSimpleName() + " is not started");
-
-        SocketChannel channel = SocketChannel.open();
-        if (bindAddress != null)
-            channel.bind(bindAddress);
-        channel.socket().setTcpNoDelay(true);
-        channel.configureBlocking(false);
-
-        SessionPromise result = new SessionPromise(channel, this, listener);
-
-        channel.connect(address);
-        factory.selector.register(channel, result);
-
-        return result;
-    }
-
-    public long getMaxIdleTime()
-    {
-        return maxIdleTime;
-    }
-
-    public void setMaxIdleTime(long maxIdleTime)
-    {
-        this.maxIdleTime = maxIdleTime;
-    }
-
-    public int getInitialWindowSize()
-    {
-        return initialWindowSize;
-    }
-
-    public void setInitialWindowSize(int initialWindowSize)
-    {
-        this.initialWindowSize = initialWindowSize;
-    }
-
-    protected String selectProtocol(List<String> serverProtocols)
-    {
-        if (serverProtocols == null)
-            return "spdy/2";
-
-        for (String serverProtocol : serverProtocols)
-        {
-            for (String protocol : factories.keySet())
-            {
-                if (serverProtocol.equals(protocol))
-                    return protocol;
-            }
-            String protocol = factory.selectProtocol(serverProtocols);
-            if (protocol != null)
-                return protocol;
-        }
-
-        return null;
-    }
-
-    public AsyncConnectionFactory getAsyncConnectionFactory(String protocol)
-    {
-        for (Map.Entry<String, AsyncConnectionFactory> entry : factories.entrySet())
-        {
-            if (protocol.equals(entry.getKey()))
-                return entry.getValue();
-        }
-        for (Map.Entry<String, AsyncConnectionFactory> entry : factory.factories.entrySet())
-        {
-            if (protocol.equals(entry.getKey()))
-                return entry.getValue();
-        }
-        return null;
-    }
-
-    public void putAsyncConnectionFactory(String protocol, AsyncConnectionFactory factory)
-    {
-        factories.put(protocol, factory);
-    }
-
-    public AsyncConnectionFactory removeAsyncConnectionFactory(String protocol)
-    {
-        return factories.remove(protocol);
-    }
-
-    protected SSLEngine newSSLEngine(SslContextFactory sslContextFactory, SocketChannel channel)
-    {
-        String peerHost = channel.socket().getInetAddress().getHostAddress();
-        int peerPort = channel.socket().getPort();
-        SSLEngine engine = sslContextFactory.newSslEngine(peerHost, peerPort);
-        engine.setUseClientMode(true);
-        return engine;
-    }
-
-    protected FlowControlStrategy newFlowControlStrategy()
-    {
-        return FlowControlStrategyFactory.newFlowControlStrategy(version);
-    }
-
-    public static class Factory extends AggregateLifeCycle
-    {
-        private final Map<String, AsyncConnectionFactory> factories = new ConcurrentHashMap<>();
-        private final Queue<Session> sessions = new ConcurrentLinkedQueue<>();
-        private final ByteBufferPool bufferPool = new StandardByteBufferPool();
-        private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
-        private final Executor threadPool;
-        private final SslContextFactory sslContextFactory;
-        private final SelectorManager selector;
-
-        public Factory()
-        {
-            this(null, null);
-        }
-
-        public Factory(SslContextFactory sslContextFactory)
-        {
-            this(null, sslContextFactory);
-        }
-
-        public Factory(Executor threadPool)
-        {
-            this(threadPool, null);
-        }
-
-        public Factory(Executor threadPool, SslContextFactory sslContextFactory)
-        {
-            if (threadPool == null)
-                threadPool = new QueuedThreadPool();
-            this.threadPool = threadPool;
-            addBean(threadPool);
-
-            this.sslContextFactory = sslContextFactory;
-            if (sslContextFactory != null)
-                addBean(sslContextFactory);
-
-            selector = new ClientSelectorManager();
-            addBean(selector);
-
-            factories.put("spdy/2", new ClientSPDYAsyncConnectionFactory());
-        }
-
-        public SPDYClient newSPDYClient(short version)
-        {
-            return new SPDYClient(version, this);
-        }
-
-        @Override
-        protected void doStop() throws Exception
-        {
-            closeConnections();
-            super.doStop();
-        }
-
-        protected String selectProtocol(List<String> serverProtocols)
-        {
-            for (String serverProtocol : serverProtocols)
-            {
-                for (String protocol : factories.keySet())
-                {
-                    if (serverProtocol.equals(protocol))
-                        return protocol;
-                }
-            }
-            return null;
-        }
-
-        private boolean sessionOpened(Session session)
-        {
-            // Add sessions only if the factory is not stopping
-            return isRunning() && sessions.offer(session);
-        }
-
-        private boolean sessionClosed(Session session)
-        {
-            // Remove sessions only if the factory is not stopping
-            // to avoid concurrent removes during iterations
-            return isRunning() && sessions.remove(session);
-        }
-
-        private void closeConnections()
-        {
-            for (Session session : sessions)
-                session.goAway();
-            sessions.clear();
-        }
-
-        protected Collection<Session> getSessions()
-        {
-            return Collections.unmodifiableCollection(sessions);
-        }
-
-        private class ClientSelectorManager extends SelectorManager
-        {
-            @Override
-            public boolean dispatch(Runnable task)
-            {
-                try
-                {
-                    threadPool.execute(task);
-                    return true;
-                }
-                catch (RejectedExecutionException x)
-                {
-                    return false;
-                }
-            }
-
-            @Override
-            protected SelectChannelEndPoint newEndPoint(SocketChannel channel, SelectSet selectSet, SelectionKey key) throws IOException
-            {
-                SessionPromise attachment = (SessionPromise)key.attachment();
-
-                long maxIdleTime = attachment.client.getMaxIdleTime();
-                if (maxIdleTime < 0)
-                    maxIdleTime = getMaxIdleTime();
-                SelectChannelEndPoint result = new SelectChannelEndPoint(channel, selectSet, key, (int)maxIdleTime);
-
-                AsyncConnection connection = newConnection(channel, result, attachment);
-                result.setConnection(connection);
-
-                return result;
-            }
-
-            @Override
-            protected void endPointOpened(SelectChannelEndPoint endpoint)
-            {
-            }
-
-            @Override
-            protected void endPointUpgraded(ConnectedEndPoint endpoint, Connection oldConnection)
-            {
-            }
-
-            @Override
-            protected void endPointClosed(SelectChannelEndPoint endpoint)
-            {
-                endpoint.getConnection().onClose();
-            }
-
-            @Override
-            public AsyncConnection newConnection(final SocketChannel channel, AsyncEndPoint endPoint, final Object attachment)
-            {
-                SessionPromise sessionPromise = (SessionPromise)attachment;
-                final SPDYClient client = sessionPromise.client;
-
-                try
-                {
-                    if (sslContextFactory != null)
-                    {
-                        final SSLEngine engine = client.newSSLEngine(sslContextFactory, channel);
-                        SslConnection sslConnection = new SslConnection(engine, endPoint)
-                        {
-                            @Override
-                            public void onClose()
-                            {
-                                NextProtoNego.remove(engine);
-                                super.onClose();
-                            }
-                        };
-                        endPoint.setConnection(sslConnection);
-                        final AsyncEndPoint sslEndPoint = sslConnection.getSslEndPoint();
-                        NextProtoNego.put(engine, new NextProtoNego.ClientProvider()
-                        {
-                            @Override
-                            public boolean supports()
-                            {
-                                return true;
-                            }
-
-                            @Override
-                            public void unsupported()
-                            {
-                                // Server does not support NPN, but this is a SPDY client, so hardcode SPDY
-                                ClientSPDYAsyncConnectionFactory connectionFactory = new ClientSPDYAsyncConnectionFactory();
-                                AsyncConnection connection = connectionFactory.newAsyncConnection(channel, sslEndPoint, attachment);
-                                sslEndPoint.setConnection(connection);
-                            }
-
-                            @Override
-                            public String selectProtocol(List<String> protocols)
-                            {
-                                String protocol = client.selectProtocol(protocols);
-                                if (protocol == null)
-                                    return null;
-
-                                AsyncConnectionFactory connectionFactory = client.getAsyncConnectionFactory(protocol);
-                                AsyncConnection connection = connectionFactory.newAsyncConnection(channel, sslEndPoint, attachment);
-                                sslEndPoint.setConnection(connection);
-                                return protocol;
-                            }
-                        });
-
-                        AsyncConnection connection = new EmptyAsyncConnection(sslEndPoint);
-                        sslEndPoint.setConnection(connection);
-
-                        startHandshake(engine);
-
-                        return sslConnection;
-                    }
-                    else
-                    {
-                        AsyncConnectionFactory connectionFactory = new ClientSPDYAsyncConnectionFactory();
-                        AsyncConnection connection = connectionFactory.newAsyncConnection(channel, endPoint, attachment);
-                        endPoint.setConnection(connection);
-                        return connection;
-                    }
-                }
-                catch (RuntimeException x)
-                {
-                    sessionPromise.failed(null,x);
-                    throw x;
-                }
-            }
-
-            private void startHandshake(SSLEngine engine)
-            {
-                try
-                {
-                    engine.beginHandshake();
-                }
-                catch (SSLException x)
-                {
-                    throw new RuntimeException(x);
-                }
-            }
-        }
-    }
-
-    private static class SessionPromise extends Promise<Session>
-    {
-        private final SocketChannel channel;
-        private final SPDYClient client;
-        private final SessionFrameListener listener;
-
-        private SessionPromise(SocketChannel channel, SPDYClient client, SessionFrameListener listener)
-        {
-            this.channel = channel;
-            this.client = client;
-            this.listener = listener;
-        }
-
-        @Override
-        public boolean cancel(boolean mayInterruptIfRunning)
-        {
-            try
-            {
-                super.cancel(mayInterruptIfRunning);
-                channel.close();
-                return true;
-            }
-            catch (IOException x)
-            {
-                return true;
-            }
-        }
-    }
-
-    private static class ClientSPDYAsyncConnectionFactory implements AsyncConnectionFactory
-    {
-        @Override
-        public AsyncConnection newAsyncConnection(SocketChannel channel, AsyncEndPoint endPoint, Object attachment)
-        {
-            SessionPromise sessionPromise = (SessionPromise)attachment;
-            SPDYClient client = sessionPromise.client;
-            Factory factory = client.factory;
-
-            CompressionFactory compressionFactory = new StandardCompressionFactory();
-            Parser parser = new Parser(compressionFactory.newDecompressor());
-            Generator generator = new Generator(factory.bufferPool, compressionFactory.newCompressor());
-
-            SPDYAsyncConnection connection = new ClientSPDYAsyncConnection(endPoint, factory.bufferPool, parser, factory);
-            endPoint.setConnection(connection);
-
-            FlowControlStrategy flowControlStrategy = client.newFlowControlStrategy();
-
-            StandardSession session = new StandardSession(client.version, factory.bufferPool, factory.threadPool, factory.scheduler, connection, connection, 1, sessionPromise.listener, generator, flowControlStrategy);
-            session.setWindowSize(client.getInitialWindowSize());
-            parser.addListener(session);
-            sessionPromise.completed(session);
-            connection.setSession(session);
-
-            factory.sessionOpened(session);
-
-            return connection;
-        }
-
-        private class ClientSPDYAsyncConnection extends SPDYAsyncConnection
-        {
-            private final Factory factory;
-
-            public ClientSPDYAsyncConnection(AsyncEndPoint endPoint, ByteBufferPool bufferPool, Parser parser, Factory factory)
-            {
-                super(endPoint, bufferPool, parser);
-                this.factory = factory;
-            }
-
-            @Override
-            public void onClose()
-            {
-                super.onClose();
-                factory.sessionClosed(getSession());
-            }
-        }
-    }
-}
diff --git a/jetty-spdy/spdy-jetty/src/main/java/org/eclipse/jetty/spdy/SPDYServerConnector.java b/jetty-spdy/spdy-jetty/src/main/java/org/eclipse/jetty/spdy/SPDYServerConnector.java
deleted file mode 100644
index 40c1ad8..0000000
--- a/jetty-spdy/spdy-jetty/src/main/java/org/eclipse/jetty/spdy/SPDYServerConnector.java
+++ /dev/null
@@ -1,330 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-
-package org.eclipse.jetty.spdy;
-
-import java.io.IOException;
-import java.nio.channels.SocketChannel;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Queue;
-import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-import java.util.concurrent.RejectedExecutionException;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
-import javax.net.ssl.SSLEngine;
-import javax.net.ssl.SSLException;
-
-import org.eclipse.jetty.io.AsyncEndPoint;
-import org.eclipse.jetty.io.nio.AsyncConnection;
-import org.eclipse.jetty.io.nio.SslConnection;
-import org.eclipse.jetty.npn.NextProtoNego;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
-import org.eclipse.jetty.spdy.api.SPDY;
-import org.eclipse.jetty.spdy.api.Session;
-import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener;
-import org.eclipse.jetty.util.component.AggregateLifeCycle;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.util.ssl.SslContextFactory;
-import org.eclipse.jetty.util.thread.ThreadPool;
-
-public class SPDYServerConnector extends SelectChannelConnector
-{
-    private static final Logger logger = Log.getLogger(SPDYServerConnector.class);
-
-    // Order is important on server side, so we use a LinkedHashMap
-    private final Map<String, AsyncConnectionFactory> factories = new LinkedHashMap<>();
-    private final Queue<Session> sessions = new ConcurrentLinkedQueue<>();
-    private final ByteBufferPool bufferPool = new StandardByteBufferPool();
-    private final Executor executor = new LazyExecutor();
-    private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
-    private final ServerSessionFrameListener listener;
-    private final SslContextFactory sslContextFactory;
-    private volatile AsyncConnectionFactory defaultConnectionFactory;
-    private volatile int initialWindowSize = 65536;
-
-    public SPDYServerConnector(ServerSessionFrameListener listener)
-    {
-        this(listener, null);
-    }
-
-    public SPDYServerConnector(ServerSessionFrameListener listener, SslContextFactory sslContextFactory)
-    {
-        this.listener = listener;
-        this.sslContextFactory = sslContextFactory;
-        if (sslContextFactory != null)
-            addBean(sslContextFactory);
-        putAsyncConnectionFactory("spdy/3", new ServerSPDYAsyncConnectionFactory(SPDY.V3, bufferPool, executor, scheduler, listener));
-        putAsyncConnectionFactory("spdy/2", new ServerSPDYAsyncConnectionFactory(SPDY.V2, bufferPool, executor, scheduler, listener));
-        setDefaultAsyncConnectionFactory(getAsyncConnectionFactory("spdy/2"));
-    }
-
-    public ByteBufferPool getByteBufferPool()
-    {
-        return bufferPool;
-    }
-
-    public Executor getExecutor()
-    {
-        return executor;
-    }
-
-    public ScheduledExecutorService getScheduler()
-    {
-        return scheduler;
-    }
-
-    public ServerSessionFrameListener getServerSessionFrameListener()
-    {
-        return listener;
-    }
-
-    public SslContextFactory getSslContextFactory()
-    {
-        return sslContextFactory;
-    }
-
-    @Override
-    protected void doStart() throws Exception
-    {
-        super.doStart();
-        logger.info("SPDY support is experimental. Please report feedback at jetty-dev at eclipse.org");
-    }
-
-    @Override
-    protected void doStop() throws Exception
-    {
-        closeSessions();
-        scheduler.shutdown();
-        super.doStop();
-    }
-
-    @Override
-    public void join() throws InterruptedException
-    {
-        scheduler.awaitTermination(0, TimeUnit.MILLISECONDS);
-        super.join();
-    }
-
-    public AsyncConnectionFactory getAsyncConnectionFactory(String protocol)
-    {
-        synchronized (factories)
-        {
-            return factories.get(protocol);
-        }
-    }
-
-    public AsyncConnectionFactory putAsyncConnectionFactory(String protocol, AsyncConnectionFactory factory)
-    {
-        synchronized (factories)
-        {
-            return factories.put(protocol, factory);
-        }
-    }
-
-    public AsyncConnectionFactory removeAsyncConnectionFactory(String protocol)
-    {
-        synchronized (factories)
-        {
-            return factories.remove(protocol);
-        }
-    }
-
-    public Map<String, AsyncConnectionFactory> getAsyncConnectionFactories()
-    {
-        synchronized (factories)
-        {
-            return new LinkedHashMap<>(factories);
-        }
-    }
-
-    public void clearAsyncConnectionFactories()
-    {
-        synchronized (factories)
-        {
-            factories.clear();
-        }
-    }
-
-    protected List<String> provideProtocols()
-    {
-        synchronized (factories)
-        {
-            return new ArrayList<>(factories.keySet());
-        }
-    }
-
-    public AsyncConnectionFactory getDefaultAsyncConnectionFactory()
-    {
-        return defaultConnectionFactory;
-    }
-
-    public void setDefaultAsyncConnectionFactory(AsyncConnectionFactory defaultConnectionFactory)
-    {
-        this.defaultConnectionFactory = defaultConnectionFactory;
-    }
-
-    @Override
-    protected AsyncConnection newConnection(final SocketChannel channel, AsyncEndPoint endPoint)
-    {
-        if (sslContextFactory != null)
-        {
-            final SSLEngine engine = newSSLEngine(sslContextFactory, channel);
-            SslConnection sslConnection = new SslConnection(engine, endPoint)
-            {
-                @Override
-                public void onClose()
-                {
-                    NextProtoNego.remove(engine);
-                    super.onClose();
-                }
-            };
-            endPoint.setConnection(sslConnection);
-            final AsyncEndPoint sslEndPoint = sslConnection.getSslEndPoint();
-            NextProtoNego.put(engine, new NextProtoNego.ServerProvider()
-            {
-                @Override
-                public void unsupported()
-                {
-                    AsyncConnectionFactory connectionFactory = getDefaultAsyncConnectionFactory();
-                    AsyncConnection connection = connectionFactory.newAsyncConnection(channel, sslEndPoint, SPDYServerConnector.this);
-                    sslEndPoint.setConnection(connection);
-                }
-
-                @Override
-                public List<String> protocols()
-                {
-                    return provideProtocols();
-                }
-
-                @Override
-                public void protocolSelected(String protocol)
-                {
-                    AsyncConnectionFactory connectionFactory = getAsyncConnectionFactory(protocol);
-                    AsyncConnection connection = connectionFactory.newAsyncConnection(channel, sslEndPoint, SPDYServerConnector.this);
-                    sslEndPoint.setConnection(connection);
-                }
-            });
-
-            AsyncConnection connection = new EmptyAsyncConnection(sslEndPoint);
-            sslEndPoint.setConnection(connection);
-
-            startHandshake(engine);
-
-            return sslConnection;
-        }
-        else
-        {
-            AsyncConnectionFactory connectionFactory = getDefaultAsyncConnectionFactory();
-            AsyncConnection connection = connectionFactory.newAsyncConnection(channel, endPoint, this);
-            endPoint.setConnection(connection);
-            return connection;
-        }
-    }
-
-    protected FlowControlStrategy newFlowControlStrategy(short version)
-    {
-        return FlowControlStrategyFactory.newFlowControlStrategy(version);
-    }
-
-    protected SSLEngine newSSLEngine(SslContextFactory sslContextFactory, SocketChannel channel)
-    {
-        String peerHost = channel.socket().getInetAddress().getHostAddress();
-        int peerPort = channel.socket().getPort();
-        SSLEngine engine = sslContextFactory.newSslEngine(peerHost, peerPort);
-        engine.setUseClientMode(false);
-        return engine;
-    }
-
-    private void startHandshake(SSLEngine engine)
-    {
-        try
-        {
-            engine.beginHandshake();
-        }
-        catch (SSLException x)
-        {
-            throw new RuntimeException(x);
-        }
-    }
-
-    protected boolean sessionOpened(Session session)
-    {
-        // Add sessions only if the connector is not stopping
-        return isRunning() && sessions.offer(session);
-    }
-
-    protected boolean sessionClosed(Session session)
-    {
-        // Remove sessions only if the connector is not stopping
-        // to avoid concurrent removes during iterations
-        return isRunning() && sessions.remove(session);
-    }
-
-    private void closeSessions()
-    {
-        for (Session session : sessions)
-            session.goAway();
-        sessions.clear();
-    }
-
-    protected Collection<Session> getSessions()
-    {
-        return Collections.unmodifiableCollection(sessions);
-    }
-
-    public int getInitialWindowSize()
-    {
-        return initialWindowSize;
-    }
-
-    public void setInitialWindowSize(int initialWindowSize)
-    {
-        this.initialWindowSize = initialWindowSize;
-    }
-
-    private class LazyExecutor implements Executor
-    {
-        @Override
-        public void execute(Runnable command)
-        {
-            ThreadPool threadPool = getThreadPool();
-            if (threadPool == null)
-                throw new RejectedExecutionException();
-            threadPool.dispatch(command);
-        }
-    }
-
-
-    @Override
-    public void dump(Appendable out, String indent) throws IOException
-    {
-        super.dump(out,indent);
-        AggregateLifeCycle.dump(out, indent, new ArrayList<Session>(sessions));
-    }
-    
-    
-}
diff --git a/jetty-spdy/spdy-jetty/src/main/java/org/eclipse/jetty/spdy/ServerSPDYAsyncConnectionFactory.java b/jetty-spdy/spdy-jetty/src/main/java/org/eclipse/jetty/spdy/ServerSPDYAsyncConnectionFactory.java
deleted file mode 100644
index df59789..0000000
--- a/jetty-spdy/spdy-jetty/src/main/java/org/eclipse/jetty/spdy/ServerSPDYAsyncConnectionFactory.java
+++ /dev/null
@@ -1,125 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-
-package org.eclipse.jetty.spdy;
-
-import java.io.IOException;
-import java.nio.channels.SocketChannel;
-import java.util.concurrent.Executor;
-import java.util.concurrent.ScheduledExecutorService;
-
-import org.eclipse.jetty.io.AsyncEndPoint;
-import org.eclipse.jetty.io.Connection;
-import org.eclipse.jetty.io.nio.AsyncConnection;
-import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener;
-import org.eclipse.jetty.spdy.generator.Generator;
-import org.eclipse.jetty.spdy.parser.Parser;
-
-public class ServerSPDYAsyncConnectionFactory implements AsyncConnectionFactory
-{
-    private final ByteBufferPool bufferPool;
-    private final Executor threadPool;
-    private final ScheduledExecutorService scheduler;
-    private final short version;
-    private final ServerSessionFrameListener listener;
-
-    public ServerSPDYAsyncConnectionFactory(short version, ByteBufferPool bufferPool, Executor threadPool, ScheduledExecutorService scheduler)
-    {
-        this(version, bufferPool, threadPool, scheduler, null);
-    }
-
-    public ServerSPDYAsyncConnectionFactory(short version, ByteBufferPool bufferPool, Executor threadPool, ScheduledExecutorService scheduler, ServerSessionFrameListener listener)
-    {
-        this.version = version;
-        this.bufferPool = bufferPool;
-        this.threadPool = threadPool;
-        this.scheduler = scheduler;
-        this.listener = listener;
-    }
-
-    public short getVersion()
-    {
-        return version;
-    }
-
-    @Override
-    public AsyncConnection newAsyncConnection(SocketChannel channel, AsyncEndPoint endPoint, Object attachment)
-    {
-        CompressionFactory compressionFactory = new StandardCompressionFactory();
-        Parser parser = new Parser(compressionFactory.newDecompressor());
-        Generator generator = new Generator(bufferPool, compressionFactory.newCompressor());
-
-        SPDYServerConnector connector = (SPDYServerConnector)attachment;
-
-        ServerSessionFrameListener listener = provideServerSessionFrameListener(endPoint, attachment);
-        SPDYAsyncConnection connection = new ServerSPDYAsyncConnection(endPoint, bufferPool, parser, listener, connector);
-        endPoint.setConnection(connection);
-
-        FlowControlStrategy flowControlStrategy = connector.newFlowControlStrategy(version);
-
-        StandardSession session = new StandardSession(version, bufferPool, threadPool, scheduler, connection, connection, 2, listener, generator, flowControlStrategy);
-        session.setAttribute("org.eclipse.jetty.spdy.remoteAddress", endPoint.getRemoteAddr());
-        session.setWindowSize(connector.getInitialWindowSize());
-        parser.addListener(session);
-        connection.setSession(session);
-
-        connector.sessionOpened(session);
-
-        return connection;
-    }
-
-    protected ServerSessionFrameListener provideServerSessionFrameListener(AsyncEndPoint endPoint, Object attachment)
-    {
-        return listener;
-    }
-
-    private static class ServerSPDYAsyncConnection extends SPDYAsyncConnection
-    {
-        private final ServerSessionFrameListener listener;
-        private final SPDYServerConnector connector;
-        private volatile boolean connected;
-
-        private ServerSPDYAsyncConnection(AsyncEndPoint endPoint, ByteBufferPool bufferPool, Parser parser, ServerSessionFrameListener listener, SPDYServerConnector connector)
-        {
-            super(endPoint, bufferPool, parser);
-            this.listener = listener;
-            this.connector = connector;
-        }
-
-        @Override
-        public Connection handle() throws IOException
-        {
-            if (!connected)
-            {
-                // NPE guard to support tests
-                if (listener != null)
-                    listener.onConnect(getSession());
-                connected = true;
-            }
-            return super.handle();
-        }
-
-        @Override
-        public void onClose()
-        {
-            super.onClose();
-            connector.sessionClosed(getSession());
-        }
-    }
-}
diff --git a/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/AbstractTest.java b/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/AbstractTest.java
deleted file mode 100644
index ad56c04..0000000
--- a/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/AbstractTest.java
+++ /dev/null
@@ -1,128 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-
-package org.eclipse.jetty.spdy;
-
-import java.net.InetSocketAddress;
-import java.util.concurrent.Executor;
-import java.util.concurrent.TimeUnit;
-
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.spdy.api.SPDY;
-import org.eclipse.jetty.spdy.api.Session;
-import org.eclipse.jetty.spdy.api.SessionFrameListener;
-import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener;
-import org.eclipse.jetty.util.ssl.SslContextFactory;
-import org.eclipse.jetty.util.thread.QueuedThreadPool;
-import org.junit.After;
-import org.junit.Rule;
-import org.junit.rules.TestWatchman;
-import org.junit.runners.model.FrameworkMethod;
-
-public abstract class AbstractTest
-{
-    @Rule
-    public final TestWatchman testName = new TestWatchman()
-    {
-        @Override
-        public void starting(FrameworkMethod method)
-        {
-            super.starting(method);
-            System.err.printf("Running %s.%s()%n",
-                    method.getMethod().getDeclaringClass().getName(),
-                    method.getName());
-        }
-    };
-
-    protected Server server;
-    protected SPDYClient.Factory clientFactory;
-    protected SPDYServerConnector connector;
-
-    protected InetSocketAddress startServer(ServerSessionFrameListener listener) throws Exception
-    {
-        return startServer(SPDY.V2, listener);
-    }
-
-    protected InetSocketAddress startServer(short version, ServerSessionFrameListener listener) throws Exception
-    {
-        if (connector == null)
-            connector = newSPDYServerConnector(listener);
-        if (listener == null)
-            listener = connector.getServerSessionFrameListener();
-        connector.setDefaultAsyncConnectionFactory(new ServerSPDYAsyncConnectionFactory(version, connector.getByteBufferPool(), connector.getExecutor(), connector.getScheduler(), listener));
-        connector.setPort(0);
-        server = new Server();
-        server.addConnector(connector);
-        server.start();
-        return new InetSocketAddress("localhost", connector.getLocalPort());
-    }
-
-    protected SPDYServerConnector newSPDYServerConnector(ServerSessionFrameListener listener)
-    {
-        return new SPDYServerConnector(listener);
-    }
-
-    protected Session startClient(InetSocketAddress socketAddress, SessionFrameListener listener) throws Exception
-    {
-        return startClient(SPDY.V2, socketAddress, listener);
-    }
-
-    protected Session startClient(short version, InetSocketAddress socketAddress, SessionFrameListener listener) throws Exception
-    {
-        if (clientFactory == null)
-        {
-            QueuedThreadPool threadPool = new QueuedThreadPool();
-            threadPool.setName(threadPool.getName() + "-client");
-            clientFactory = newSPDYClientFactory(threadPool);
-            clientFactory.start();
-        }
-        return clientFactory.newSPDYClient(version).connect(socketAddress, listener).get(5, TimeUnit.SECONDS);
-    }
-
-    protected SPDYClient.Factory newSPDYClientFactory(Executor threadPool)
-    {
-        return new SPDYClient.Factory(threadPool);
-    }
-
-    protected SslContextFactory newSslContextFactory()
-    {
-        SslContextFactory sslContextFactory = new SslContextFactory();
-        sslContextFactory.setKeyStorePath("src/test/resources/keystore.jks");
-        sslContextFactory.setKeyStorePassword("storepwd");
-        sslContextFactory.setTrustStore("src/test/resources/truststore.jks");
-        sslContextFactory.setTrustStorePassword("storepwd");
-        sslContextFactory.setProtocol("TLSv1");
-        sslContextFactory.setIncludeProtocols("TLSv1");
-        return sslContextFactory;
-    }
-
-    @After
-    public void destroy() throws Exception
-    {
-        if (clientFactory != null)
-        {
-            clientFactory.stop();
-        }
-        if (server != null)
-        {
-            server.stop();
-            server.join();
-        }
-    }
-}
diff --git a/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/ClosedStreamTest.java b/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/ClosedStreamTest.java
deleted file mode 100644
index 43fca88..0000000
--- a/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/ClosedStreamTest.java
+++ /dev/null
@@ -1,269 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.spdy;
-
-import java.io.IOException;
-import java.net.InetSocketAddress;
-import java.nio.ByteBuffer;
-import java.nio.channels.ServerSocketChannel;
-import java.nio.channels.SocketChannel;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-import org.eclipse.jetty.spdy.api.BytesDataInfo;
-import org.eclipse.jetty.spdy.api.DataInfo;
-import org.eclipse.jetty.spdy.api.GoAwayInfo;
-import org.eclipse.jetty.spdy.api.Headers;
-import org.eclipse.jetty.spdy.api.ReplyInfo;
-import org.eclipse.jetty.spdy.api.SPDY;
-import org.eclipse.jetty.spdy.api.Session;
-import org.eclipse.jetty.spdy.api.SessionStatus;
-import org.eclipse.jetty.spdy.api.Stream;
-import org.eclipse.jetty.spdy.api.StreamFrameListener;
-import org.eclipse.jetty.spdy.api.StringDataInfo;
-import org.eclipse.jetty.spdy.api.SynInfo;
-import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener;
-import org.eclipse.jetty.spdy.frames.ControlFrame;
-import org.eclipse.jetty.spdy.frames.GoAwayFrame;
-import org.eclipse.jetty.spdy.frames.RstStreamFrame;
-import org.eclipse.jetty.spdy.frames.SynReplyFrame;
-import org.eclipse.jetty.spdy.frames.SynStreamFrame;
-import org.eclipse.jetty.spdy.generator.Generator;
-import org.eclipse.jetty.spdy.parser.Parser;
-import org.eclipse.jetty.spdy.parser.Parser.Listener;
-import org.junit.Assert;
-import org.junit.Ignore;
-import org.junit.Test;
-
-import static org.hamcrest.Matchers.is;
-import static org.hamcrest.Matchers.not;
-import static org.junit.Assert.assertThat;
-
-public class ClosedStreamTest extends AbstractTest
-{
-    //TODO: Right now it sends a rst as the stream is unknown to the session once it's closed.
-    //TODO: But according to the spec we probably should just ignore the data?!
-    @Test
-    public void testDataSentOnClosedStreamIsIgnored() throws Exception
-    {
-        ServerSocketChannel server = ServerSocketChannel.open();
-        server.bind(new InetSocketAddress("localhost", 0));
-
-        Session session = startClient(new InetSocketAddress("localhost", server.socket().getLocalPort()), null);
-        final CountDownLatch dataLatch = new CountDownLatch(2);
-        session.syn(new SynInfo(true), new StreamFrameListener.Adapter()
-        {
-            @Override
-            public void onData(Stream stream, DataInfo dataInfo)
-            {
-                dataLatch.countDown();
-            }
-        });
-
-        SocketChannel channel = server.accept();
-        ByteBuffer readBuffer = ByteBuffer.allocate(1024);
-        channel.read(readBuffer);
-        readBuffer.flip();
-        int streamId = readBuffer.getInt(8);
-
-        Generator generator = new Generator(new StandardByteBufferPool(), new StandardCompressionFactory.StandardCompressor());
-
-        ByteBuffer writeBuffer = generator.control(new SynReplyFrame(SPDY.V2, (byte)0, streamId, new Headers()));
-        channel.write(writeBuffer);
-        Assert.assertThat(writeBuffer.hasRemaining(), is(false));
-
-        byte[] bytes = new byte[1];
-        writeBuffer = generator.data(streamId, bytes.length, new BytesDataInfo(bytes, true));
-        channel.write(writeBuffer);
-        Assert.assertThat(writeBuffer.hasRemaining(), is(false));
-
-        // Write again to simulate the faulty condition
-        writeBuffer.flip();
-        channel.write(writeBuffer);
-        Assert.assertThat(writeBuffer.hasRemaining(), is(false));
-
-        Assert.assertFalse(dataLatch.await(1, TimeUnit.SECONDS));
-
-        session.goAway().get(5, TimeUnit.SECONDS);
-
-        server.close();
-    }
-
-    @Test
-    public void testSendDataOnHalfClosedStreamCausesExceptionOnServer() throws Exception
-    {
-        final CountDownLatch replyReceivedLatch = new CountDownLatch(1);
-        final CountDownLatch clientReceivedDataLatch = new CountDownLatch(1);
-        final CountDownLatch exceptionWhenSendingData = new CountDownLatch(1);
-
-        Session clientSession = startClient(startServer(new ServerSessionFrameListener.Adapter()
-        {
-            @Override
-            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
-            {
-                stream.reply(new ReplyInfo(true));
-                try
-                {
-                    replyReceivedLatch.await(5,TimeUnit.SECONDS);
-                }
-                catch (InterruptedException e)
-                {
-                    e.printStackTrace();
-                }
-                try
-                {
-                    stream.data(new StringDataInfo("data send after half closed",false));
-                }
-                catch (RuntimeException e)
-                {
-                    // we expect an exception here, but we don't want it to be logged
-                    exceptionWhenSendingData.countDown();
-                }
-
-                return null;
-            }
-        }),null);
-
-        Stream stream = clientSession.syn(new SynInfo(false),new StreamFrameListener.Adapter()
-        {
-            @Override
-            public void onReply(Stream stream, ReplyInfo replyInfo)
-            {
-                replyReceivedLatch.countDown();
-            }
-
-            @Override
-            public void onData(Stream stream, DataInfo dataInfo)
-            {
-                clientReceivedDataLatch.countDown();
-            }
-        }).get();
-        assertThat("reply has been received by client",replyReceivedLatch.await(5,TimeUnit.SECONDS),is(true));
-        assertThat("stream is half closed from server",stream.isHalfClosed(),is(true));
-        assertThat("client has not received any data sent after stream was half closed by server",clientReceivedDataLatch.await(1,TimeUnit.SECONDS),
-                is(false));
-        assertThat("sending data threw an exception",exceptionWhenSendingData.await(5,TimeUnit.SECONDS),is(true));
-    }
-
-    @Test
-    public void testV2ReceiveDataOnHalfClosedStream() throws Exception
-    {
-        runReceiveDataOnHalfClosedStream(SPDY.V2);
-    }
-
-    @Test
-    @Ignore("until v3 is properly implemented")
-    public void testV3ReceiveDataOnHalfClosedStream() throws Exception
-    {
-        runReceiveDataOnHalfClosedStream(SPDY.V3);
-    }
-
-    private void runReceiveDataOnHalfClosedStream(short version) throws Exception
-    {
-        final CountDownLatch clientResetReceivedLatch = new CountDownLatch(1);
-        final CountDownLatch serverReplySentLatch = new CountDownLatch(1);
-        final CountDownLatch clientReplyReceivedLatch = new CountDownLatch(1);
-        final CountDownLatch serverDataReceivedLatch = new CountDownLatch(1);
-        final CountDownLatch goAwayReceivedLatch = new CountDownLatch(1);
-
-        InetSocketAddress startServer = startServer(new ServerSessionFrameListener.Adapter()
-        {
-            @Override
-            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
-            {
-                stream.reply(new ReplyInfo(false));
-                serverReplySentLatch.countDown();
-                try
-                {
-                    clientReplyReceivedLatch.await(5,TimeUnit.SECONDS);
-                }
-                catch (InterruptedException e)
-                {
-                    e.printStackTrace();
-                }
-                return new StreamFrameListener.Adapter()
-                {
-                    @Override
-                    public void onData(Stream stream, DataInfo dataInfo)
-                    {
-                        serverDataReceivedLatch.countDown();
-                    }
-                };
-            }
-            @Override
-            public void onGoAway(Session session, GoAwayInfo goAwayInfo)
-            {
-                goAwayReceivedLatch.countDown();
-            }
-        });
-
-        final Generator generator = new Generator(new StandardByteBufferPool(),new StandardCompressionFactory().newCompressor());
-        int streamId = 1;
-        ByteBuffer synData = generator.control(new SynStreamFrame(version,SynInfo.FLAG_CLOSE, streamId,0,(byte)0,(short)0,new Headers()));
-
-        final SocketChannel socketChannel = SocketChannel.open(startServer);
-        socketChannel.write(synData);
-        assertThat("synData is fully written", synData.hasRemaining(), is(false));
-
-        assertThat("server: syn reply is sent",serverReplySentLatch.await(5,TimeUnit.SECONDS),is(true));
-
-        Parser parser = new Parser(new StandardCompressionFactory.StandardDecompressor());
-        parser.addListener(new Listener.Adapter()
-        {
-            @Override
-            public void onControlFrame(ControlFrame frame)
-            {
-                if (frame instanceof SynReplyFrame)
-                {
-                    SynReplyFrame synReplyFrame = (SynReplyFrame)frame;
-                    clientReplyReceivedLatch.countDown();
-                    int streamId = synReplyFrame.getStreamId();
-                    ByteBuffer data = generator.data(streamId,0,new StringDataInfo("data",false));
-                    try
-                    {
-                        socketChannel.write(data);
-                    }
-                    catch (IOException e)
-                    {
-                        e.printStackTrace();
-                    }
-                }
-                else if (frame instanceof RstStreamFrame)
-                {
-                    clientResetReceivedLatch.countDown();
-                }
-            }
-        });
-        ByteBuffer response = ByteBuffer.allocate(28);
-        socketChannel.read(response);
-        response.flip();
-        parser.parse(response);
-
-        assertThat("server didn't receive data",serverDataReceivedLatch.await(1,TimeUnit.SECONDS),not(true));
-        assertThat("client didn't receive reset",clientResetReceivedLatch.await(1,TimeUnit.SECONDS),not(true));
-
-        ByteBuffer buffer = generator.control(new GoAwayFrame(version, streamId, SessionStatus.OK.getCode()));
-        socketChannel.write(buffer);
-        Assert.assertThat(buffer.hasRemaining(), is(false));
-
-        assertThat("GoAway frame is received by server", goAwayReceivedLatch.await(5,TimeUnit.SECONDS), is(true));
-
-        socketChannel.close();
-    }
-}
diff --git a/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/FlowControlTest.java b/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/FlowControlTest.java
deleted file mode 100644
index b15166f..0000000
--- a/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/FlowControlTest.java
+++ /dev/null
@@ -1,490 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-
-package org.eclipse.jetty.spdy;
-
-import java.nio.ByteBuffer;
-import java.util.concurrent.Callable;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.Exchanger;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicReference;
-
-import org.eclipse.jetty.spdy.api.ByteBufferDataInfo;
-import org.eclipse.jetty.spdy.api.BytesDataInfo;
-import org.eclipse.jetty.spdy.api.DataInfo;
-import org.eclipse.jetty.spdy.api.ReplyInfo;
-import org.eclipse.jetty.spdy.api.SPDY;
-import org.eclipse.jetty.spdy.api.SPDYException;
-import org.eclipse.jetty.spdy.api.Session;
-import org.eclipse.jetty.spdy.api.SessionFrameListener;
-import org.eclipse.jetty.spdy.api.Settings;
-import org.eclipse.jetty.spdy.api.SettingsInfo;
-import org.eclipse.jetty.spdy.api.Stream;
-import org.eclipse.jetty.spdy.api.StreamFrameListener;
-import org.eclipse.jetty.spdy.api.SynInfo;
-import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener;
-import org.junit.Assert;
-import org.junit.Test;
-
-import static org.hamcrest.Matchers.is;
-import static org.junit.Assert.assertThat;
-
-public class FlowControlTest extends AbstractTest
-{
-    @Test
-    public void testFlowControlWithConcurrentSettings() throws Exception
-    {
-        // Initial window is 64 KiB. We allow the client to send 1024 B
-        // then we change the window to 512 B. At this point, the client
-        // must stop sending data (although the initial window allows it)
-
-        final int size = 512;
-        final AtomicReference<DataInfo> dataInfoRef = new AtomicReference<>();
-        final CountDownLatch dataLatch = new CountDownLatch(2);
-        final CountDownLatch settingsLatch = new CountDownLatch(1);
-        Session session = startClient(SPDY.V3, startServer(SPDY.V3, new ServerSessionFrameListener.Adapter()
-        {
-            @Override
-            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
-            {
-                stream.reply(new ReplyInfo(true));
-                return new StreamFrameListener.Adapter()
-                {
-                    private final AtomicInteger dataFrames = new AtomicInteger();
-
-                    @Override
-                    public void onData(Stream stream, DataInfo dataInfo)
-                    {
-                        int dataFrameCount = dataFrames.incrementAndGet();
-                        if (dataFrameCount == 1)
-                        {
-                            dataInfoRef.set(dataInfo);
-                            Settings settings = new Settings();
-                            settings.put(new Settings.Setting(Settings.ID.INITIAL_WINDOW_SIZE, size));
-                            stream.getSession().settings(new SettingsInfo(settings));
-                        }
-                        else if (dataFrameCount > 1)
-                        {
-                            dataInfo.consume(dataInfo.length());
-                            dataLatch.countDown();
-                        }
-                    }
-                };
-            }
-        }), new SessionFrameListener.Adapter()
-        {
-            @Override
-            public void onSettings(Session session, SettingsInfo settingsInfo)
-            {
-                settingsLatch.countDown();
-            }
-        });
-
-        Stream stream = session.syn(new SynInfo(false), null).get(5, TimeUnit.SECONDS);
-        stream.data(new BytesDataInfo(new byte[size * 2], false));
-        settingsLatch.await(5, TimeUnit.SECONDS);
-
-        // Send the second chunk of data, must not arrive since we're flow control stalled now
-        stream.data(new BytesDataInfo(new byte[size * 2], true));
-        Assert.assertFalse(dataLatch.await(1, TimeUnit.SECONDS));
-
-        // Consume the data arrived to server, this will resume flow control
-        DataInfo dataInfo = dataInfoRef.get();
-        dataInfo.consume(dataInfo.length());
-
-        Assert.assertTrue(dataLatch.await(5, TimeUnit.SECONDS));
-    }
-
-    @Test
-    public void testServerFlowControlOneBigWrite() throws Exception
-    {
-        final int windowSize = 1536;
-        final int length = 5 * windowSize;
-        final CountDownLatch settingsLatch = new CountDownLatch(1);
-        Session session = startClient(SPDY.V3, startServer(SPDY.V3, new ServerSessionFrameListener.Adapter()
-        {
-            @Override
-            public void onSettings(Session session, SettingsInfo settingsInfo)
-            {
-                settingsLatch.countDown();
-            }
-
-            @Override
-            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
-            {
-                stream.reply(new ReplyInfo(false));
-                stream.data(new BytesDataInfo(new byte[length], true));
-                return null;
-            }
-        }), null);
-
-        Settings settings = new Settings();
-        settings.put(new Settings.Setting(Settings.ID.INITIAL_WINDOW_SIZE, windowSize));
-        session.settings(new SettingsInfo(settings));
-
-        Assert.assertTrue(settingsLatch.await(5, TimeUnit.SECONDS));
-
-        final Exchanger<DataInfo> exchanger = new Exchanger<>();
-        session.syn(new SynInfo(true), new StreamFrameListener.Adapter()
-        {
-            private AtomicInteger dataFrames = new AtomicInteger();
-
-            @Override
-            public void onData(Stream stream, DataInfo dataInfo)
-            {
-                try
-                {
-                    int dataFrames = this.dataFrames.incrementAndGet();
-                    if (dataFrames == 1)
-                    {
-                        // Do not consume nor read from the data frame.
-                        // We should then be flow-control stalled
-                        exchanger.exchange(dataInfo);
-                    }
-                    else if (dataFrames == 2)
-                    {
-                        // Read but not consume, we should be flow-control stalled
-                        dataInfo.asByteBuffer(false);
-                        exchanger.exchange(dataInfo);
-                    }
-                    else if (dataFrames == 3)
-                    {
-                        // Consume partially, we should be flow-control stalled
-                        dataInfo.consumeInto(ByteBuffer.allocate(dataInfo.length() / 2));
-                        exchanger.exchange(dataInfo);
-                    }
-                    else if (dataFrames == 4 || dataFrames == 5)
-                    {
-                        // Consume totally
-                        dataInfo.asByteBuffer(true);
-                        exchanger.exchange(dataInfo);
-                    }
-                    else
-                    {
-                        Assert.fail();
-                    }
-                }
-                catch (InterruptedException x)
-                {
-                    throw new SPDYException(x);
-                }
-            }
-        });
-
-        DataInfo dataInfo = exchanger.exchange(null, 5, TimeUnit.SECONDS);
-        checkThatWeAreFlowControlStalled(exchanger);
-
-        Assert.assertEquals(windowSize, dataInfo.available());
-        Assert.assertEquals(0, dataInfo.consumed());
-        dataInfo.asByteBuffer(true);
-
-        dataInfo = exchanger.exchange(null, 5, TimeUnit.SECONDS);
-        checkThatWeAreFlowControlStalled(exchanger);
-
-        Assert.assertEquals(0, dataInfo.available());
-        Assert.assertEquals(0, dataInfo.consumed());
-        dataInfo.consume(dataInfo.length());
-
-        dataInfo = exchanger.exchange(null, 5, TimeUnit.SECONDS);
-        checkThatWeAreFlowControlStalled(exchanger);
-
-        Assert.assertEquals(dataInfo.length() / 2, dataInfo.consumed());
-        dataInfo.asByteBuffer(true);
-
-        dataInfo = exchanger.exchange(null, 5, TimeUnit.SECONDS);
-        Assert.assertEquals(dataInfo.length(), dataInfo.consumed());
-        // Check that we are not flow control stalled
-        dataInfo = exchanger.exchange(null, 5, TimeUnit.SECONDS);
-        Assert.assertEquals(dataInfo.length(), dataInfo.consumed());
-    }
-
-    @Test
-    public void testClientFlowControlOneBigWrite() throws Exception
-    {
-        final int windowSize = 1536;
-        final Exchanger<DataInfo> exchanger = new Exchanger<>();
-        final CountDownLatch settingsLatch = new CountDownLatch(1);
-        Session session = startClient(SPDY.V3, startServer(SPDY.V3, new ServerSessionFrameListener.Adapter()
-        {
-            @Override
-            public void onConnect(Session session)
-            {
-                Settings settings = new Settings();
-                settings.put(new Settings.Setting(Settings.ID.INITIAL_WINDOW_SIZE, windowSize));
-                session.settings(new SettingsInfo(settings));
-            }
-
-            @Override
-            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
-            {
-                stream.reply(new ReplyInfo(false));
-                return new StreamFrameListener.Adapter()
-                {
-                    private AtomicInteger dataFrames = new AtomicInteger();
-
-                    @Override
-                    public void onData(Stream stream, DataInfo dataInfo)
-                    {
-                        try
-                        {
-                            int dataFrames = this.dataFrames.incrementAndGet();
-                            if (dataFrames == 1)
-                            {
-                                // Do not consume nor read from the data frame.
-                                // We should then be flow-control stalled
-                                exchanger.exchange(dataInfo);
-                            }
-                            else if (dataFrames == 2)
-                            {
-                                // Read but not consume, we should be flow-control stalled
-                                dataInfo.asByteBuffer(false);
-                                exchanger.exchange(dataInfo);
-                            }
-                            else if (dataFrames == 3)
-                            {
-                                // Consume partially, we should be flow-control stalled
-                                dataInfo.consumeInto(ByteBuffer.allocate(dataInfo.length() / 2));
-                                exchanger.exchange(dataInfo);
-                            }
-                            else if (dataFrames == 4 || dataFrames == 5)
-                            {
-                                // Consume totally
-                                dataInfo.asByteBuffer(true);
-                                exchanger.exchange(dataInfo);
-                            }
-                            else
-                            {
-                                Assert.fail();
-                            }
-                        }
-                        catch (InterruptedException x)
-                        {
-                            throw new SPDYException(x);
-                        }
-                    }
-                };
-            }
-        }), new SessionFrameListener.Adapter()
-        {
-            @Override
-            public void onSettings(Session session, SettingsInfo settingsInfo)
-            {
-                settingsLatch.countDown();
-            }
-        });
-
-        Assert.assertTrue(settingsLatch.await(5, TimeUnit.SECONDS));
-
-        Stream stream = session.syn(new SynInfo(false), null).get(5, TimeUnit.SECONDS);
-        final int length = 5 * windowSize;
-        stream.data(new BytesDataInfo(new byte[length], true));
-
-        DataInfo dataInfo = exchanger.exchange(null, 5, TimeUnit.SECONDS);
-        checkThatWeAreFlowControlStalled(exchanger);
-
-        Assert.assertEquals(windowSize, dataInfo.available());
-        Assert.assertEquals(0, dataInfo.consumed());
-        dataInfo.asByteBuffer(true);
-
-        dataInfo = exchanger.exchange(null, 5, TimeUnit.SECONDS);
-        checkThatWeAreFlowControlStalled(exchanger);
-
-        Assert.assertEquals(0, dataInfo.available());
-        Assert.assertEquals(0, dataInfo.consumed());
-        dataInfo.consume(dataInfo.length());
-
-        dataInfo = exchanger.exchange(null, 5, TimeUnit.SECONDS);
-        checkThatWeAreFlowControlStalled(exchanger);
-
-        Assert.assertEquals(dataInfo.length() / 2, dataInfo.consumed());
-        dataInfo.asByteBuffer(true);
-
-        dataInfo = exchanger.exchange(null, 5, TimeUnit.SECONDS);
-        Assert.assertEquals(dataInfo.length(), dataInfo.consumed());
-        // Check that we are not flow control stalled
-        dataInfo = exchanger.exchange(null, 5, TimeUnit.SECONDS);
-        Assert.assertEquals(dataInfo.length(), dataInfo.consumed());
-    }
-
-    @Test
-    public void testStreamsStalledDoesNotStallOtherStreams() throws Exception
-    {
-        final int windowSize = 1024;
-        final CountDownLatch settingsLatch = new CountDownLatch(1);
-        Session session = startClient(SPDY.V3, startServer(SPDY.V3, new ServerSessionFrameListener.Adapter()
-        {
-            @Override
-            public void onSettings(Session session, SettingsInfo settingsInfo)
-            {
-                settingsLatch.countDown();
-            }
-
-            @Override
-            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
-            {
-                stream.reply(new ReplyInfo(false));
-                stream.data(new BytesDataInfo(new byte[windowSize * 2], true));
-                return null;
-            }
-        }), null);
-        Settings settings = new Settings();
-        settings.put(new Settings.Setting(Settings.ID.INITIAL_WINDOW_SIZE, windowSize));
-        session.settings(new SettingsInfo(settings));
-
-        Assert.assertTrue(settingsLatch.await(5, TimeUnit.SECONDS));
-
-        final CountDownLatch latch = new CountDownLatch(3);
-        final AtomicReference<DataInfo> dataInfoRef1 = new AtomicReference<>();
-        final AtomicReference<DataInfo> dataInfoRef2 = new AtomicReference<>();
-        session.syn(new SynInfo(true), new StreamFrameListener.Adapter()
-        {
-            private final AtomicInteger dataFrames = new AtomicInteger();
-
-            @Override
-            public void onData(Stream stream, DataInfo dataInfo)
-            {
-                int frames = dataFrames.incrementAndGet();
-                if (frames == 1)
-                {
-                    // Do not consume it to stall flow control
-                    dataInfoRef1.set(dataInfo);
-                }
-                else
-                {
-                    dataInfo.consume(dataInfo.length());
-                    if (dataInfo.isClose())
-                        latch.countDown();
-                }
-            }
-        }).get(5, TimeUnit.SECONDS);
-        session.syn(new SynInfo(true), new StreamFrameListener.Adapter()
-        {
-            private final AtomicInteger dataFrames = new AtomicInteger();
-
-            @Override
-            public void onData(Stream stream, DataInfo dataInfo)
-            {
-                int frames = dataFrames.incrementAndGet();
-                if (frames == 1)
-                {
-                    // Do not consume it to stall flow control
-                    dataInfoRef2.set(dataInfo);
-                }
-                else
-                {
-                    dataInfo.consume(dataInfo.length());
-                    if (dataInfo.isClose())
-                        latch.countDown();
-                }
-            }
-        }).get(5, TimeUnit.SECONDS);
-        session.syn(new SynInfo(true), new StreamFrameListener.Adapter()
-        {
-            @Override
-            public void onData(Stream stream, DataInfo dataInfo)
-            {
-                DataInfo dataInfo1 = dataInfoRef1.getAndSet(null);
-                if (dataInfo1 != null)
-                    dataInfo1.consume(dataInfo1.length());
-                DataInfo dataInfo2 = dataInfoRef2.getAndSet(null);
-                if (dataInfo2 != null)
-                    dataInfo2.consume(dataInfo2.length());
-                dataInfo.consume(dataInfo.length());
-                if (dataInfo.isClose())
-                    latch.countDown();
-            }
-        }).get(5, TimeUnit.SECONDS);
-
-        Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
-    }
-
-    @Test
-    public void testSendBigFileWithoutFlowControl() throws Exception
-    {
-        testSendBigFile(SPDY.V2);
-    }
-
-    @Test
-    public void testSendBigFileWithFlowControl() throws Exception
-    {
-        testSendBigFile(SPDY.V3);
-    }
-
-    private void testSendBigFile(short version) throws Exception
-    {
-        final int dataSize = 1024 * 1024;
-        final ByteBufferDataInfo bigByteBufferDataInfo = new ByteBufferDataInfo(ByteBuffer.allocate(dataSize),false);
-        final CountDownLatch allDataReceivedLatch = new CountDownLatch(1);
-
-        Session session = startClient(version, startServer(version, new ServerSessionFrameListener.Adapter()
-        {
-            @Override
-            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
-            {
-                stream.reply(new ReplyInfo(false));
-                stream.data(bigByteBufferDataInfo);
-                return null;
-            }
-        }),new SessionFrameListener.Adapter());
-
-        session.syn(new SynInfo(false),new StreamFrameListener.Adapter()
-        {
-            private int dataBytesReceived;
-
-            @Override
-            public void onData(Stream stream, DataInfo dataInfo)
-            {
-                dataBytesReceived = dataBytesReceived + dataInfo.length();
-                dataInfo.consume(dataInfo.length());
-                if (dataBytesReceived == dataSize)
-                    allDataReceivedLatch.countDown();
-            }
-        });
-
-        assertThat("all data bytes have been received by the client", allDataReceivedLatch.await(5, TimeUnit.SECONDS), is(true));
-    }
-
-    private void checkThatWeAreFlowControlStalled(final Exchanger<DataInfo> exchanger)
-    {
-        expectException(TimeoutException.class, new Callable<DataInfo>()
-        {
-            @Override
-            public DataInfo call() throws Exception
-            {
-                return exchanger.exchange(null, 1, TimeUnit.SECONDS);
-            }
-        });
-    }
-
-    private void expectException(Class<? extends Exception> exception, Callable<DataInfo> command)
-    {
-        try
-        {
-            command.call();
-            Assert.fail();
-        }
-        catch (Exception x)
-        {
-            Assert.assertSame(exception, x.getClass());
-        }
-    }
-}
diff --git a/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/GoAwayTest.java b/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/GoAwayTest.java
deleted file mode 100644
index 9531eb6..0000000
--- a/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/GoAwayTest.java
+++ /dev/null
@@ -1,230 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.spdy;
-
-import java.nio.channels.ClosedChannelException;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicReference;
-
-import org.eclipse.jetty.spdy.api.DataInfo;
-import org.eclipse.jetty.spdy.api.GoAwayInfo;
-import org.eclipse.jetty.spdy.api.ReplyInfo;
-import org.eclipse.jetty.spdy.api.SPDYException;
-import org.eclipse.jetty.spdy.api.Session;
-import org.eclipse.jetty.spdy.api.SessionFrameListener;
-import org.eclipse.jetty.spdy.api.SessionStatus;
-import org.eclipse.jetty.spdy.api.Stream;
-import org.eclipse.jetty.spdy.api.StreamFrameListener;
-import org.eclipse.jetty.spdy.api.StringDataInfo;
-import org.eclipse.jetty.spdy.api.SynInfo;
-import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener;
-import org.hamcrest.CoreMatchers;
-import org.junit.Assert;
-import org.junit.Test;
-
-public class GoAwayTest extends AbstractTest
-{
-    @Test
-    public void testServerReceivesGoAwayOnClientGoAway() throws Exception
-    {
-        final CountDownLatch latch = new CountDownLatch(1);
-        ServerSessionFrameListener serverSessionFrameListener = new ServerSessionFrameListener.Adapter()
-        {
-            @Override
-            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
-            {
-                stream.reply(new ReplyInfo(true));
-                return null;
-            }
-
-            @Override
-            public void onGoAway(Session session, GoAwayInfo goAwayInfo)
-            {
-                Assert.assertEquals(0, goAwayInfo.getLastStreamId());
-                Assert.assertSame(SessionStatus.OK, goAwayInfo.getSessionStatus());
-                latch.countDown();
-            }
-        };
-        Session session = startClient(startServer(serverSessionFrameListener), null);
-
-        session.syn(new SynInfo(true), null);
-
-        session.goAway();
-
-        Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
-    }
-
-    @Test
-    public void testClientReceivesGoAwayOnServerGoAway() throws Exception
-    {
-        ServerSessionFrameListener serverSessionFrameListener = new ServerSessionFrameListener.Adapter()
-        {
-            @Override
-            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
-            {
-                stream.reply(new ReplyInfo(true));
-                stream.getSession().goAway();
-                return null;
-            }
-        };
-        final AtomicReference<GoAwayInfo> ref = new AtomicReference<>();
-        final CountDownLatch latch = new CountDownLatch(1);
-        SessionFrameListener clientSessionFrameListener = new SessionFrameListener.Adapter()
-        {
-            @Override
-            public void onGoAway(Session session, GoAwayInfo goAwayInfo)
-            {
-                ref.set(goAwayInfo);
-                latch.countDown();
-            }
-        };
-        Session session = startClient(startServer(serverSessionFrameListener), clientSessionFrameListener);
-
-        Stream stream1 = session.syn(new SynInfo(true), null).get(5, TimeUnit.SECONDS);
-
-        Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
-        GoAwayInfo goAwayInfo = ref.get();
-        Assert.assertNotNull(goAwayInfo);
-        Assert.assertEquals(stream1.getId(), goAwayInfo.getLastStreamId());
-        Assert.assertSame(SessionStatus.OK, goAwayInfo.getSessionStatus());
-    }
-
-    @Test
-    public void testSynStreamIgnoredAfterGoAway() throws Exception
-    {
-        final CountDownLatch latch = new CountDownLatch(1);
-        ServerSessionFrameListener serverSessionFrameListener = new ServerSessionFrameListener.Adapter()
-        {
-            private final AtomicInteger syns = new AtomicInteger();
-
-            @Override
-            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
-            {
-                int synCount = syns.incrementAndGet();
-                if (synCount == 1)
-                {
-                    stream.reply(new ReplyInfo(true));
-                    stream.getSession().goAway();
-                }
-                else
-                {
-                    latch.countDown();
-                }
-                return null;
-            }
-        };
-        SessionFrameListener clientSessionFrameListener = new SessionFrameListener.Adapter()
-        {
-            @Override
-            public void onGoAway(Session session, GoAwayInfo goAwayInfo)
-            {
-                session.syn(new SynInfo(true), null);
-            }
-        };
-        Session session = startClient(startServer(serverSessionFrameListener), clientSessionFrameListener);
-
-        session.syn(new SynInfo(true), null);
-
-        Assert.assertFalse(latch.await(1, TimeUnit.SECONDS));
-    }
-
-    @Test
-    public void testDataNotProcessedAfterGoAway() throws Exception
-    {
-        final CountDownLatch closeLatch = new CountDownLatch(1);
-        final CountDownLatch dataLatch = new CountDownLatch(1);
-        ServerSessionFrameListener serverSessionFrameListener = new ServerSessionFrameListener.Adapter()
-        {
-            private AtomicInteger syns = new AtomicInteger();
-
-            @Override
-            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
-            {
-                stream.reply(new ReplyInfo(true));
-                int synCount = syns.incrementAndGet();
-                if (synCount == 1)
-                {
-                    return null;
-                }
-                else
-                {
-                    stream.getSession().goAway();
-                    closeLatch.countDown();
-                    return new StreamFrameListener.Adapter()
-                    {
-                        @Override
-                        public void onData(Stream stream, DataInfo dataInfo)
-                        {
-                            dataLatch.countDown();
-                        }
-                    };
-                }
-            }
-        };
-        final AtomicReference<GoAwayInfo> goAwayRef = new AtomicReference<>();
-        final CountDownLatch goAwayLatch = new CountDownLatch(1);
-        SessionFrameListener clientSessionFrameListener = new SessionFrameListener.Adapter()
-        {
-            @Override
-            public void onGoAway(Session session, GoAwayInfo goAwayInfo)
-            {
-                goAwayRef.set(goAwayInfo);
-                goAwayLatch.countDown();
-            }
-        };
-        Session session = startClient(startServer(serverSessionFrameListener), clientSessionFrameListener);
-
-        // First stream is processed ok
-        final CountDownLatch reply1Latch = new CountDownLatch(1);
-        Stream stream1 = session.syn(new SynInfo(true), new StreamFrameListener.Adapter()
-        {
-            @Override
-            public void onReply(Stream stream, ReplyInfo replyInfo)
-            {
-                reply1Latch.countDown();
-            }
-        }).get(5, TimeUnit.SECONDS);
-        Assert.assertTrue(reply1Latch.await(5, TimeUnit.SECONDS));
-
-        // Second stream is closed in the middle
-        Stream stream2 = session.syn(new SynInfo(false), null).get(5, TimeUnit.SECONDS);
-        Assert.assertTrue(closeLatch.await(5, TimeUnit.SECONDS));
-
-        // There is a race between the data we want to send, and the client
-        // closing the connection because the server closed it after the
-        // go_away, so we guard with a try/catch to have the test pass cleanly
-        try
-        {
-            stream2.data(new StringDataInfo("foo", true));
-            Assert.assertFalse(dataLatch.await(1, TimeUnit.SECONDS));
-        }
-        catch (SPDYException x)
-        {
-            Assert.assertThat(x.getCause(), CoreMatchers.instanceOf(ClosedChannelException.class));
-        }
-
-        // The last good stream is the second, because it was received by the server
-        Assert.assertTrue(goAwayLatch.await(5, TimeUnit.SECONDS));
-        GoAwayInfo goAway = goAwayRef.get();
-        Assert.assertNotNull(goAway);
-        Assert.assertEquals(stream2.getId(), goAway.getLastStreamId());
-    }
-}
diff --git a/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/HeadersTest.java b/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/HeadersTest.java
deleted file mode 100644
index 264b167..0000000
--- a/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/HeadersTest.java
+++ /dev/null
@@ -1,85 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-
-package org.eclipse.jetty.spdy;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-import org.eclipse.jetty.spdy.api.Headers;
-import org.eclipse.jetty.spdy.api.HeadersInfo;
-import org.eclipse.jetty.spdy.api.ReplyInfo;
-import org.eclipse.jetty.spdy.api.Session;
-import org.eclipse.jetty.spdy.api.Stream;
-import org.eclipse.jetty.spdy.api.StreamFrameListener;
-import org.eclipse.jetty.spdy.api.SynInfo;
-import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener;
-import org.junit.Assert;
-import org.junit.Test;
-
-public class HeadersTest extends AbstractTest
-{
-    @Test
-    public void testHeaders() throws Exception
-    {
-        ServerSessionFrameListener serverSessionFrameListener = new ServerSessionFrameListener.Adapter()
-        {
-            @Override
-            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
-            {
-                stream.reply(new ReplyInfo(false));
-                return new StreamFrameListener.Adapter()
-                {
-                    @Override
-                    public void onHeaders(Stream stream, HeadersInfo headersInfo)
-                    {
-                        Assert.assertTrue(stream.isHalfClosed());
-                        stream.headers(new HeadersInfo(new Headers(), true));
-                        Assert.assertTrue(stream.isClosed());
-                    }
-                };
-            }
-        };
-
-        Session session = startClient(startServer(serverSessionFrameListener), null);
-
-        final CountDownLatch latch = new CountDownLatch(1);
-        session.syn(new SynInfo(false), new StreamFrameListener.Adapter()
-        {
-            @Override
-            public void onReply(Stream stream, ReplyInfo replyInfo)
-            {
-                Headers headers = new Headers();
-                headers.put("foo", "bar");
-                headers.put("baz", "woo");
-                stream.headers(new HeadersInfo(headers, true));
-                Assert.assertTrue(stream.isHalfClosed());
-            }
-
-            @Override
-            public void onHeaders(Stream stream, HeadersInfo headersInfo)
-            {
-                Assert.assertTrue(stream.isClosed());
-                latch.countDown();
-            }
-        });
-
-        Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
-    }
-}
diff --git a/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/IdleTimeoutTest.java b/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/IdleTimeoutTest.java
deleted file mode 100644
index 033a881..0000000
--- a/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/IdleTimeoutTest.java
+++ /dev/null
@@ -1,253 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-
-package org.eclipse.jetty.spdy;
-
-import java.net.InetSocketAddress;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-import org.eclipse.jetty.spdy.api.GoAwayInfo;
-import org.eclipse.jetty.spdy.api.ReplyInfo;
-import org.eclipse.jetty.spdy.api.SPDY;
-import org.eclipse.jetty.spdy.api.Session;
-import org.eclipse.jetty.spdy.api.SessionFrameListener;
-import org.eclipse.jetty.spdy.api.Stream;
-import org.eclipse.jetty.spdy.api.StreamFrameListener;
-import org.eclipse.jetty.spdy.api.SynInfo;
-import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener;
-import org.eclipse.jetty.util.thread.QueuedThreadPool;
-import org.junit.Assert;
-import org.junit.Test;
-
-public class IdleTimeoutTest extends AbstractTest
-{
-    @Test
-    public void testServerEnforcingIdleTimeout() throws Exception
-    {
-        connector = newSPDYServerConnector(new ServerSessionFrameListener.Adapter()
-        {
-            @Override
-            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
-            {
-                stream.reply(new ReplyInfo(true));
-                return null;
-            }
-        });
-        int maxIdleTime = 1000;
-        connector.setMaxIdleTime(maxIdleTime);
-
-        final CountDownLatch latch = new CountDownLatch(1);
-        Session session = startClient(startServer(null), new SessionFrameListener.Adapter()
-        {
-            @Override
-            public void onGoAway(Session session, GoAwayInfo goAwayInfo)
-            {
-                latch.countDown();
-            }
-        });
-
-        session.syn(new SynInfo(true), null);
-
-        Assert.assertTrue(latch.await(2 * maxIdleTime, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void testServerEnforcingIdleTimeoutWithUnrespondedStream() throws Exception
-    {
-        connector = newSPDYServerConnector(null);
-        int maxIdleTime = 1000;
-        connector.setMaxIdleTime(maxIdleTime);
-
-        final CountDownLatch latch = new CountDownLatch(1);
-        Session session = startClient(startServer(null), new SessionFrameListener.Adapter()
-        {
-            @Override
-            public void onGoAway(Session session, GoAwayInfo goAwayInfo)
-            {
-                latch.countDown();
-            }
-        });
-
-        // The SYN is not replied, and the server should idle timeout
-        session.syn(new SynInfo(true), null);
-
-        Assert.assertTrue(latch.await(2 * maxIdleTime, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void testServerNotEnforcingIdleTimeoutWithPendingStream() throws Exception
-    {
-        final int maxIdleTime = 1000;
-        connector = newSPDYServerConnector(new ServerSessionFrameListener.Adapter()
-        {
-            @Override
-            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
-            {
-                try
-                {
-                    Thread.sleep(2 * maxIdleTime);
-                    stream.reply(new ReplyInfo(true));
-                    return null;
-                }
-                catch (InterruptedException x)
-                {
-                    Assert.fail();
-                    return null;
-                }
-            }
-        });
-        connector.setMaxIdleTime(maxIdleTime);
-
-        final CountDownLatch latch = new CountDownLatch(1);
-        Session session = startClient(startServer(null), new SessionFrameListener.Adapter()
-        {
-            @Override
-            public void onGoAway(Session session, GoAwayInfo goAwayInfo)
-            {
-                latch.countDown();
-            }
-        });
-
-        final CountDownLatch replyLatch = new CountDownLatch(1);
-        session.syn(new SynInfo(true), new StreamFrameListener.Adapter()
-        {
-            @Override
-            public void onReply(Stream stream, ReplyInfo replyInfo)
-            {
-                replyLatch.countDown();
-            }
-        });
-
-        Assert.assertTrue(replyLatch.await(3 * maxIdleTime, TimeUnit.MILLISECONDS));
-        Assert.assertFalse(latch.await(1000, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void testClientEnforcingIdleTimeout() throws Exception
-    {
-        final CountDownLatch latch = new CountDownLatch(1);
-        InetSocketAddress address = startServer(new ServerSessionFrameListener.Adapter()
-        {
-            @Override
-            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
-            {
-                stream.reply(new ReplyInfo(true));
-                return null;
-            }
-
-            @Override
-            public void onGoAway(Session session, GoAwayInfo goAwayInfo)
-            {
-                latch.countDown();
-            }
-        });
-
-        QueuedThreadPool threadPool = new QueuedThreadPool();
-        threadPool.setName(threadPool.getName() + "-client");
-        clientFactory = newSPDYClientFactory(threadPool);
-        clientFactory.start();
-        SPDYClient client = clientFactory.newSPDYClient(SPDY.V2);
-        long maxIdleTime = 1000;
-        client.setMaxIdleTime(maxIdleTime);
-        Session session = client.connect(address, null).get(5, TimeUnit.SECONDS);
-
-        session.syn(new SynInfo(true), null);
-
-        Assert.assertTrue(latch.await(2 * maxIdleTime, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void testClientEnforcingIdleTimeoutWithUnrespondedStream() throws Exception
-    {
-        final CountDownLatch latch = new CountDownLatch(1);
-        InetSocketAddress address = startServer(new ServerSessionFrameListener.Adapter()
-        {
-            @Override
-            public void onGoAway(Session session, GoAwayInfo goAwayInfo)
-            {
-                latch.countDown();
-            }
-        });
-
-        QueuedThreadPool threadPool = new QueuedThreadPool();
-        threadPool.setName(threadPool.getName() + "-client");
-        clientFactory = newSPDYClientFactory(threadPool);
-        clientFactory.start();
-        SPDYClient client = clientFactory.newSPDYClient(SPDY.V2);
-        long maxIdleTime = 1000;
-        client.setMaxIdleTime(maxIdleTime);
-        Session session = client.connect(address, null).get(5, TimeUnit.SECONDS);
-
-        session.syn(new SynInfo(true), null);
-
-        Assert.assertTrue(latch.await(2 * maxIdleTime, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void testClientNotEnforcingIdleTimeoutWithPendingStream() throws Exception
-    {
-        final long maxIdleTime = 1000;
-        final CountDownLatch latch = new CountDownLatch(1);
-        InetSocketAddress address = startServer(new ServerSessionFrameListener.Adapter()
-        {
-            @Override
-            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
-            {
-                stream.reply(new ReplyInfo(true));
-                return null;
-            }
-
-            @Override
-            public void onGoAway(Session session, GoAwayInfo goAwayInfo)
-            {
-                latch.countDown();
-            }
-        });
-
-        QueuedThreadPool threadPool = new QueuedThreadPool();
-        threadPool.setName(threadPool.getName() + "-client");
-        clientFactory = newSPDYClientFactory(threadPool);
-        clientFactory.start();
-        SPDYClient client = clientFactory.newSPDYClient(SPDY.V2);
-        client.setMaxIdleTime(maxIdleTime);
-        Session session = client.connect(address, null).get(5, TimeUnit.SECONDS);
-
-        final CountDownLatch replyLatch = new CountDownLatch(1);
-        session.syn(new SynInfo(true), new StreamFrameListener.Adapter()
-        {
-            @Override
-            public void onReply(Stream stream, ReplyInfo replyInfo)
-            {
-                try
-                {
-                    Thread.sleep(2 * maxIdleTime);
-                    replyLatch.countDown();
-                }
-                catch (InterruptedException e)
-                {
-                    Assert.fail();
-                }
-            }
-        });
-
-        Assert.assertFalse(latch.await(2 * maxIdleTime, TimeUnit.MILLISECONDS));
-        Assert.assertTrue(replyLatch.await(3 * maxIdleTime, TimeUnit.MILLISECONDS));
-    }
-}
diff --git a/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/PingTest.java b/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/PingTest.java
deleted file mode 100644
index 4685541..0000000
--- a/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/PingTest.java
+++ /dev/null
@@ -1,93 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-
-package org.eclipse.jetty.spdy;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicReference;
-
-import org.eclipse.jetty.spdy.api.Handler;
-import org.eclipse.jetty.spdy.api.PingInfo;
-import org.eclipse.jetty.spdy.api.Session;
-import org.eclipse.jetty.spdy.api.SessionFrameListener;
-import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener;
-import org.junit.Assert;
-import org.junit.Test;
-
-public class PingTest extends AbstractTest
-{
-    @Test
-    public void testPingPong() throws Exception
-    {
-        final AtomicReference<PingInfo> ref = new AtomicReference<>();
-        final CountDownLatch latch = new CountDownLatch(1);
-        SessionFrameListener clientSessionFrameListener = new SessionFrameListener.Adapter()
-        {
-            @Override
-            public void onPing(Session session, PingInfo pingInfo)
-            {
-                ref.set(pingInfo);
-                latch.countDown();
-            }
-        };
-        Session session = startClient(startServer(null), clientSessionFrameListener);
-        PingInfo pingInfo = session.ping().get(5, TimeUnit.SECONDS);
-        Assert.assertEquals(1, pingInfo.getPingId() % 2);
-
-        Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
-        PingInfo pongInfo = ref.get();
-        Assert.assertNotNull(pongInfo);
-        Assert.assertEquals(pingInfo.getPingId(), pongInfo.getPingId());
-    }
-
-    @Test
-    public void testServerPingPong() throws Exception
-    {
-        final CountDownLatch pingLatch = new CountDownLatch(1);
-        ServerSessionFrameListener serverSessionFrameListener = new ServerSessionFrameListener.Adapter()
-        {
-            public volatile int pingId;
-
-            @Override
-            public void onConnect(Session session)
-            {
-                session.ping(0, TimeUnit.MILLISECONDS, new Handler.Adapter<PingInfo>()
-                {
-                    @Override
-                    public void completed(PingInfo pingInfo)
-                    {
-                        pingId = pingInfo.getPingId();
-                    }
-                });
-            }
-
-            @Override
-            public void onPing(Session session, PingInfo pingInfo)
-            {
-                Assert.assertEquals(0, pingInfo.getPingId() % 2);
-                Assert.assertEquals(pingId, pingInfo.getPingId());
-                pingLatch.countDown();
-            }
-        };
-        startClient(startServer(serverSessionFrameListener), null);
-
-        Assert.assertTrue(pingLatch.await(5, TimeUnit.SECONDS));
-    }
-}
diff --git a/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/ProtocolViolationsTest.java b/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/ProtocolViolationsTest.java
deleted file mode 100644
index 56fbe11..0000000
--- a/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/ProtocolViolationsTest.java
+++ /dev/null
@@ -1,181 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.spdy;
-
-import static org.junit.Assert.*;
-import static org.hamcrest.Matchers.*;
-
-import java.net.InetSocketAddress;
-import java.nio.ByteBuffer;
-import java.nio.channels.ServerSocketChannel;
-import java.nio.channels.SocketChannel;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-import org.eclipse.jetty.spdy.api.BytesDataInfo;
-import org.eclipse.jetty.spdy.api.DataInfo;
-import org.eclipse.jetty.spdy.api.Headers;
-import org.eclipse.jetty.spdy.api.HeadersInfo;
-import org.eclipse.jetty.spdy.api.RstInfo;
-import org.eclipse.jetty.spdy.api.SPDY;
-import org.eclipse.jetty.spdy.api.Session;
-import org.eclipse.jetty.spdy.api.SessionFrameListener;
-import org.eclipse.jetty.spdy.api.Stream;
-import org.eclipse.jetty.spdy.api.StreamFrameListener;
-import org.eclipse.jetty.spdy.api.StreamStatus;
-import org.eclipse.jetty.spdy.api.StringDataInfo;
-import org.eclipse.jetty.spdy.api.SynInfo;
-import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener;
-import org.eclipse.jetty.spdy.frames.ControlFrameType;
-import org.eclipse.jetty.spdy.frames.SynReplyFrame;
-import org.eclipse.jetty.spdy.generator.Generator;
-import org.junit.Assert;
-import org.junit.Test;
-
-public class ProtocolViolationsTest extends AbstractTest
-{
-    @Test
-    public void testSendDataBeforeReplyIsIllegal() throws Exception
-    {
-        final CountDownLatch resetLatch = new CountDownLatch(1);
-        final CountDownLatch latch = new CountDownLatch(1);
-        Session session = startClient(startServer(new ServerSessionFrameListener.Adapter()
-        {
-            @Override
-            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
-            {
-                try
-                {
-                    stream.data(new StringDataInfo("failure", true));
-                    return null;
-                }
-                catch (IllegalStateException x)
-                {
-                    latch.countDown();
-                    return null;
-                }
-            }
-        }), new SessionFrameListener.Adapter()
-        {
-            @Override
-            public void onRst(Session session, RstInfo rstInfo)
-            {
-                Assert.assertSame(StreamStatus.PROTOCOL_ERROR, rstInfo.getStreamStatus());
-                resetLatch.countDown();
-            }
-        });
-        session.syn(new SynInfo(true), null);
-        Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
-        Assert.assertTrue(resetLatch.await(5, TimeUnit.SECONDS));
-    }
-
-    @Test
-    public void testReceiveDataBeforeReplyIsIllegal() throws Exception
-    {
-        ServerSocketChannel server = ServerSocketChannel.open();
-        server.bind(new InetSocketAddress("localhost", 0));
-
-        Session session = startClient(new InetSocketAddress("localhost", server.socket().getLocalPort()), null);
-        session.syn(new SynInfo(true), null);
-
-        SocketChannel channel = server.accept();
-        ByteBuffer readBuffer = ByteBuffer.allocate(1024);
-        channel.read(readBuffer);
-        readBuffer.flip();
-        int streamId = readBuffer.getInt(8);
-
-        Generator generator = new Generator(new StandardByteBufferPool(), new StandardCompressionFactory.StandardCompressor());
-        byte[] bytes = new byte[1];
-        ByteBuffer writeBuffer = generator.data(streamId, bytes.length, new BytesDataInfo(bytes, true));
-        channel.write(writeBuffer);
-        assertThat("data is fully written", writeBuffer.hasRemaining(),is(false));
-
-        readBuffer.clear();
-        channel.read(readBuffer);
-        readBuffer.flip();
-        Assert.assertEquals(ControlFrameType.RST_STREAM.getCode(), readBuffer.getShort(2));
-        Assert.assertEquals(streamId, readBuffer.getInt(8));
-
-        session.goAway().get(5,TimeUnit.SECONDS);
-        
-        server.close();
-    }
-
-    @Test(expected = IllegalStateException.class)
-    public void testSendDataAfterCloseIsIllegal() throws Exception
-    {
-        Session session = startClient(startServer(null), null);
-        Stream stream = session.syn(new SynInfo(true), null).get(5, TimeUnit.SECONDS);
-        stream.data(new StringDataInfo("test", true));
-    }
-
-    @Test(expected = IllegalStateException.class)
-    public void testSendHeadersAfterCloseIsIllegal() throws Exception
-    {
-        Session session = startClient(startServer(null), null);
-        Stream stream = session.syn(new SynInfo(true), null).get(5, TimeUnit.SECONDS);
-        stream.headers(new HeadersInfo(new Headers(), true));
-    }
-
-    @Test //TODO: throws an ISException in StandardStream.updateCloseState(). But instead we should send a rst or something to the server probably?!
-    public void testServerClosesStreamTwice() throws Exception
-    {
-        ServerSocketChannel server = ServerSocketChannel.open();
-        server.bind(new InetSocketAddress("localhost", 0));
-
-        Session session = startClient(new InetSocketAddress("localhost", server.socket().getLocalPort()), null);
-        final CountDownLatch dataLatch = new CountDownLatch(2);
-        session.syn(new SynInfo(false), new StreamFrameListener.Adapter()
-        {
-            @Override
-            public void onData(Stream stream, DataInfo dataInfo)
-            {
-                dataLatch.countDown();
-            }
-        });
-
-        SocketChannel channel = server.accept();
-        ByteBuffer readBuffer = ByteBuffer.allocate(1024);
-        channel.read(readBuffer);
-        readBuffer.flip();
-        int streamId = readBuffer.getInt(8);
-
-        Generator generator = new Generator(new StandardByteBufferPool(), new StandardCompressionFactory.StandardCompressor());
-
-        ByteBuffer writeBuffer = generator.control(new SynReplyFrame(SPDY.V2, (byte)0, streamId, new Headers()));
-        channel.write(writeBuffer);
-        assertThat("SynReply is fully written", writeBuffer.hasRemaining(), is(false));
-
-        byte[] bytes = new byte[1];
-        writeBuffer = generator.data(streamId, bytes.length, new BytesDataInfo(bytes, true));
-        channel.write(writeBuffer);
-        assertThat("data is fully written", writeBuffer.hasRemaining(), is(false));
-
-        // Write again to simulate the faulty condition
-        writeBuffer.flip();
-        channel.write(writeBuffer);
-        assertThat("data is fully written", writeBuffer.hasRemaining(), is(false));
-
-        Assert.assertFalse(dataLatch.await(1, TimeUnit.SECONDS));
-
-        session.goAway().get(5,TimeUnit.SECONDS);
-
-        server.close();
-    }
-}
diff --git a/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/PushStreamTest.java b/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/PushStreamTest.java
deleted file mode 100644
index f274cda..0000000
--- a/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/PushStreamTest.java
+++ /dev/null
@@ -1,560 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-
-package org.eclipse.jetty.spdy;
-
-import java.io.IOException;
-import java.net.InetSocketAddress;
-import java.nio.ByteBuffer;
-import java.nio.channels.SocketChannel;
-import java.util.Arrays;
-import java.util.Random;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.CyclicBarrier;
-import java.util.concurrent.Exchanger;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicReference;
-
-import org.eclipse.jetty.spdy.api.BytesDataInfo;
-import org.eclipse.jetty.spdy.api.DataInfo;
-import org.eclipse.jetty.spdy.api.GoAwayInfo;
-import org.eclipse.jetty.spdy.api.Handler;
-import org.eclipse.jetty.spdy.api.Headers;
-import org.eclipse.jetty.spdy.api.ReplyInfo;
-import org.eclipse.jetty.spdy.api.RstInfo;
-import org.eclipse.jetty.spdy.api.SPDY;
-import org.eclipse.jetty.spdy.api.Session;
-import org.eclipse.jetty.spdy.api.SessionFrameListener;
-import org.eclipse.jetty.spdy.api.SessionStatus;
-import org.eclipse.jetty.spdy.api.Stream;
-import org.eclipse.jetty.spdy.api.StreamFrameListener;
-import org.eclipse.jetty.spdy.api.StreamStatus;
-import org.eclipse.jetty.spdy.api.StringDataInfo;
-import org.eclipse.jetty.spdy.api.SynInfo;
-import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener;
-import org.eclipse.jetty.spdy.frames.ControlFrame;
-import org.eclipse.jetty.spdy.frames.DataFrame;
-import org.eclipse.jetty.spdy.frames.GoAwayFrame;
-import org.eclipse.jetty.spdy.frames.RstStreamFrame;
-import org.eclipse.jetty.spdy.frames.SynStreamFrame;
-import org.eclipse.jetty.spdy.frames.WindowUpdateFrame;
-import org.eclipse.jetty.spdy.generator.Generator;
-import org.eclipse.jetty.spdy.parser.Parser;
-import org.eclipse.jetty.spdy.parser.Parser.Listener;
-import org.junit.Assert;
-import org.junit.Test;
-
-import static org.hamcrest.Matchers.is;
-import static org.hamcrest.Matchers.notNullValue;
-import static org.hamcrest.Matchers.sameInstance;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.fail;
-
-public class PushStreamTest extends AbstractTest
-{
-    @Test
-    public void testSynPushStream() throws Exception
-    {
-        final AtomicReference<Stream> pushStreamRef = new AtomicReference<>();
-        final CountDownLatch pushStreamLatch = new CountDownLatch(1);
-
-        Session clientSession = startClient(startServer(new ServerSessionFrameListener.Adapter()
-        {
-            @Override
-            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
-            {
-                stream.reply(new ReplyInfo(false));
-                stream.syn(new SynInfo(true));
-                return null;
-            }
-        }), new SessionFrameListener.Adapter()
-        {
-            @Override
-            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
-            {
-                assertThat("streamId is even",stream.getId() % 2,is(0));
-                assertThat("stream is unidirectional",stream.isUnidirectional(),is(true));
-                assertThat("stream is closed",stream.isClosed(),is(true));
-                assertThat("stream has associated stream",stream.getAssociatedStream(),notNullValue());
-                try
-                {
-                    stream.reply(new ReplyInfo(false));
-                    fail("Cannot reply to push streams");
-                }
-                catch (IllegalStateException x)
-                {
-                    // Expected
-                }
-                pushStreamRef.set(stream);
-                pushStreamLatch.countDown();
-                return null;
-            }
-        });
-
-        Stream stream = clientSession.syn(new SynInfo(true),null).get();
-        assertThat("onSyn has been called",pushStreamLatch.await(5,TimeUnit.SECONDS),is(true));
-        Stream pushStream = pushStreamRef.get();
-        assertThat("main stream and associated stream are the same",stream,sameInstance(pushStream.getAssociatedStream()));
-    }
-
-    @Test
-    public void testSendDataOnPushStreamAfterAssociatedStreamIsClosed() throws Exception
-    {
-        final Exchanger<Stream> streamExchanger = new Exchanger<>();
-        final CountDownLatch pushStreamSynLatch = new CountDownLatch(1);
-        final CyclicBarrier replyBarrier = new CyclicBarrier(3);
-        final CyclicBarrier closeBarrier = new CyclicBarrier(3);
-        final CountDownLatch streamDataSent = new CountDownLatch(2);
-        final CountDownLatch pushStreamDataReceived = new CountDownLatch(2);
-        final CountDownLatch exceptionCountDownLatch = new CountDownLatch(1);
-
-        Session clientSession = startClient(startServer(new ServerSessionFrameListener.Adapter()
-        {
-            @Override
-            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
-            {
-                stream.reply(new ReplyInfo(false));
-                try
-                {
-                    replyBarrier.await(5,TimeUnit.SECONDS);
-                    return new StreamFrameListener.Adapter()
-                    {
-                        @Override
-                        public void onData(Stream stream, DataInfo dataInfo)
-                        {
-                            try
-                            {
-                                if (dataInfo.isClose())
-                                {
-                                    stream.data(new StringDataInfo("close stream",true));
-                                    closeBarrier.await(5,TimeUnit.SECONDS);
-                                }
-                                streamDataSent.countDown();
-                                if (pushStreamDataReceived.getCount() == 2)
-                                {
-                                    Stream pushStream = stream.syn(new SynInfo(false)).get();
-                                    streamExchanger.exchange(pushStream,5,TimeUnit.SECONDS);
-                                }
-                            }
-                            catch (Exception e)
-                            {
-                                exceptionCountDownLatch.countDown();
-                            }
-                        }
-                    };
-                }
-                catch (Exception e)
-                {
-                    exceptionCountDownLatch.countDown();
-                    throw new IllegalStateException(e);
-                }
-            }
-
-        }),new SessionFrameListener.Adapter()
-        {
-            @Override
-            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
-            {
-                pushStreamSynLatch.countDown();
-                return new StreamFrameListener.Adapter()
-                {
-                    @Override
-                    public void onData(Stream stream, DataInfo dataInfo)
-                    {
-                        pushStreamDataReceived.countDown();
-                        super.onData(stream,dataInfo);
-                    }
-                };
-            }
-        });
-
-        Stream stream = clientSession.syn(new SynInfo(false),new StreamFrameListener.Adapter()
-        {
-            @Override
-            public void onReply(Stream stream, ReplyInfo replyInfo)
-            {
-                try
-                {
-                    replyBarrier.await(5,TimeUnit.SECONDS);
-                }
-                catch (Exception e)
-                {
-                    exceptionCountDownLatch.countDown();
-                }
-            }
-
-            @Override
-            public void onData(Stream stream, DataInfo dataInfo)
-            {
-                try
-                {
-                    closeBarrier.await(5,TimeUnit.SECONDS);
-                }
-                catch (Exception e)
-                {
-                    exceptionCountDownLatch.countDown();
-                }
-            }
-        }).get();
-
-        replyBarrier.await(5,TimeUnit.SECONDS);
-        stream.data(new StringDataInfo("client data",false));
-        Stream pushStream = streamExchanger.exchange(null,5,TimeUnit.SECONDS);
-        pushStream.data(new StringDataInfo("first push data frame",false));
-        // nasty, but less complex than using another cyclicBarrier for example
-        while (pushStreamDataReceived.getCount() != 1)
-            Thread.sleep(1);
-        stream.data(new StringDataInfo("client close",true));
-        closeBarrier.await(5,TimeUnit.SECONDS);
-        assertThat("stream is closed",stream.isClosed(),is(true));
-        pushStream.data(new StringDataInfo("second push data frame while associated stream has been closed already",false));
-        assertThat("2 pushStream data frames have been received.",pushStreamDataReceived.await(5,TimeUnit.SECONDS),is(true));
-        assertThat("2 data frames have been sent",streamDataSent.await(5,TimeUnit.SECONDS),is(true));
-        assertThatNoExceptionOccured(exceptionCountDownLatch);
-    }
-
-    @Test
-    public void testSynPushStreamOnClosedStream() throws Exception
-    {
-        final CountDownLatch pushStreamFailedLatch = new CountDownLatch(1);
-
-        Session clientSession = startClient(startServer(new ServerSessionFrameListener.Adapter()
-        {
-            @Override
-            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
-            {
-                stream.reply(new ReplyInfo(true));
-                stream.syn(new SynInfo(false),1,TimeUnit.SECONDS,new Handler.Adapter<Stream>()
-                {
-                    @Override
-                    public void failed(Stream stream, Throwable x)
-                    {
-                        pushStreamFailedLatch.countDown();
-                    }
-                });
-                return super.onSyn(stream,synInfo);
-            }
-        }),new SessionFrameListener.Adapter());
-
-        clientSession.syn(new SynInfo(true),null);
-        assertThat("pushStream syn has failed",pushStreamFailedLatch.await(5,TimeUnit.SECONDS),is(true));
-    }
-
-    @Test
-    public void testSendBigDataOnPushStreamWhenAssociatedStreamIsClosed() throws Exception
-    {
-        final CountDownLatch streamClosedLatch = new CountDownLatch(1);
-        final CountDownLatch allDataReceived = new CountDownLatch(1);
-        final CountDownLatch exceptionCountDownLatch = new CountDownLatch(1);
-        final Exchanger<ByteBuffer> exchanger = new Exchanger<>();
-        final int dataSizeInBytes = 1024 * 1024 * 1;
-        final byte[] transferBytes = createHugeByteArray(dataSizeInBytes);
-
-        Session clientSession = startClient(startServer(new ServerSessionFrameListener.Adapter()
-        {
-            @Override
-            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
-            {
-                try
-                {
-                    Stream pushStream = stream.syn(new SynInfo(false)).get();
-                    stream.reply(new ReplyInfo(true));
-                    // wait until stream is closed
-                    streamClosedLatch.await(5,TimeUnit.SECONDS);
-                    pushStream.data(new BytesDataInfo(transferBytes,true));
-                    return null;
-                }
-                catch (Exception e)
-                {
-                    exceptionCountDownLatch.countDown();
-                    throw new IllegalStateException(e);
-                }
-            }
-        }),new SessionFrameListener.Adapter()
-        {
-            @Override
-            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
-            {
-                return new StreamFrameListener.Adapter()
-                {
-                    ByteBuffer receivedBytes = ByteBuffer.allocate(dataSizeInBytes);
-
-                    @Override
-                    public void onData(Stream stream, DataInfo dataInfo)
-                    {
-                        dataInfo.consumeInto(receivedBytes);
-                        if (dataInfo.isClose())
-                        {
-                            allDataReceived.countDown();
-                            try
-                            {
-                                receivedBytes.flip();
-                                exchanger.exchange(receivedBytes.slice(),5,TimeUnit.SECONDS);
-                            }
-                            catch (Exception e)
-                            {
-                                exceptionCountDownLatch.countDown();
-                            }
-                        }
-                    }
-                };
-            }
-        });
-
-        Stream stream = clientSession.syn(new SynInfo(true),new StreamFrameListener.Adapter()
-        {
-            @Override
-            public void onReply(Stream stream, ReplyInfo replyInfo)
-            {
-                streamClosedLatch.countDown();
-                super.onReply(stream,replyInfo);
-            }
-        }).get();
-
-        ByteBuffer receivedBytes = exchanger.exchange(null,5,TimeUnit.SECONDS);
-
-        assertThat("received byte array is the same as transferred byte array",Arrays.equals(transferBytes,receivedBytes.array()),is(true));
-        assertThat("onReply has been called to close the stream",streamClosedLatch.await(5,TimeUnit.SECONDS),is(true));
-        assertThat("stream is closed",stream.isClosed(),is(true));
-        assertThat("all data has been received",allDataReceived.await(20,TimeUnit.SECONDS),is(true));
-        assertThatNoExceptionOccured(exceptionCountDownLatch);
-    }
-
-    private byte[] createHugeByteArray(int sizeInBytes)
-    {
-        byte[] bytes = new byte[sizeInBytes];
-        new Random().nextBytes(bytes);
-        return bytes;
-    }
-
-
-    @Test
-    public void testClientResetsStreamAfterPushSynDoesPreventSendingDataFramesWithFlowControl() throws Exception
-    {
-        final boolean flowControl = true;
-        testNoMoreFramesAreSentOnPushStreamAfterClientResetsThePushStream(flowControl);
-    }
-
-    @Test
-    public void testClientResetsStreamAfterPushSynDoesPreventSendingDataFramesWithoutFlowControl() throws Exception
-    {
-        final boolean flowControl = false;
-        testNoMoreFramesAreSentOnPushStreamAfterClientResetsThePushStream(flowControl);
-    }
-
-    private volatile boolean read = true;
-    private void testNoMoreFramesAreSentOnPushStreamAfterClientResetsThePushStream(final boolean flowControl) throws Exception, IOException, InterruptedException
-    {
-        final short version = SPDY.V3;
-        final AtomicBoolean unexpectedExceptionOccured = new AtomicBoolean(false);
-        final CountDownLatch resetReceivedLatch = new CountDownLatch(1);
-        final CountDownLatch allDataFramesReceivedLatch = new CountDownLatch(1);
-        final CountDownLatch goAwayReceivedLatch = new CountDownLatch(1);
-        final int dataSizeInBytes = 1024 * 256;
-        final byte[] transferBytes = createHugeByteArray(dataSizeInBytes);
-
-        InetSocketAddress serverAddress = startServer(new ServerSessionFrameListener.Adapter()
-        {
-            @Override
-            public StreamFrameListener onSyn(final Stream stream, SynInfo synInfo)
-            {
-                new Thread(new Runnable()
-                {
-
-                    @Override
-                    public void run()
-                    {
-                        Stream pushStream=null;
-                        try
-                        {
-                            stream.reply(new ReplyInfo(false));
-                            pushStream = stream.syn(new SynInfo(false)).get();
-                            resetReceivedLatch.await(5,TimeUnit.SECONDS);
-                        }
-                        catch (InterruptedException | ExecutionException e)
-                        {
-                            e.printStackTrace();
-                            unexpectedExceptionOccured.set(true);
-                        }
-                        pushStream.data(new BytesDataInfo(transferBytes,true));
-                        stream.data(new StringDataInfo("close",true));
-                    }
-                }).start();
-                return null;
-            }
-
-            @Override
-            public void onRst(Session session, RstInfo rstInfo)
-            {
-                resetReceivedLatch.countDown();
-            }
-
-            @Override
-            public void onGoAway(Session session, GoAwayInfo goAwayInfo)
-            {
-                goAwayReceivedLatch.countDown();
-            }
-        }/*TODO, flowControl*/);
-
-        final SocketChannel channel = SocketChannel.open(serverAddress);
-        final Generator generator = new Generator(new StandardByteBufferPool(),new StandardCompressionFactory.StandardCompressor());
-        int streamId = 1;
-        ByteBuffer writeBuffer = generator.control(new SynStreamFrame(version,(byte)0,streamId,0,(byte)0,(short)0,new Headers()));
-        channel.write(writeBuffer);
-        assertThat("writeBuffer is fully written",writeBuffer.hasRemaining(), is(false));
-
-        final Parser parser = new Parser(new StandardCompressionFactory.StandardDecompressor());
-        parser.addListener(new Listener.Adapter()
-        {
-            int bytesRead = 0;
-
-            @Override
-            public void onControlFrame(ControlFrame frame)
-            {
-                if(frame instanceof SynStreamFrame){
-                    int pushStreamId = ((SynStreamFrame)frame).getStreamId();
-                    ByteBuffer writeBuffer = generator.control(new RstStreamFrame(version,pushStreamId,StreamStatus.CANCEL_STREAM.getCode(version)));
-                    try
-                    {
-                        channel.write(writeBuffer);
-                    }
-                    catch (IOException e)
-                    {
-                        e.printStackTrace();
-                        unexpectedExceptionOccured.set(true);
-                    }
-                }
-            }
-
-            @Override
-            public void onDataFrame(DataFrame frame, ByteBuffer data)
-            {
-                if(frame.getStreamId() == 2)
-                    bytesRead = bytesRead + frame.getLength();
-                if(bytesRead == dataSizeInBytes){
-                    allDataFramesReceivedLatch.countDown();
-                    return;
-                }
-                if (flowControl)
-                {
-                    ByteBuffer writeBuffer = generator.control(new WindowUpdateFrame(version,frame.getStreamId(),frame.getLength()));
-                    try
-                    {
-                        channel.write(writeBuffer);
-                    }
-                    catch (IOException e)
-                    {
-                        e.printStackTrace();
-                        unexpectedExceptionOccured.set(true);
-                    }
-                }
-            }
-        });
-
-        Thread reader = new Thread(new Runnable()
-        {
-            @Override
-            public void run()
-            {
-                ByteBuffer readBuffer = ByteBuffer.allocate(dataSizeInBytes*2);
-                while (read)
-                {
-                    try
-                    {
-                        channel.read(readBuffer);
-                    }
-                    catch (IOException e)
-                    {
-                        e.printStackTrace();
-                        unexpectedExceptionOccured.set(true);
-                    }
-                    readBuffer.flip();
-                    parser.parse(readBuffer);
-                    readBuffer.clear();
-                }
-
-            }
-        });
-        reader.start();
-        read = false;
-
-        assertThat("no unexpected exceptions occured", unexpectedExceptionOccured.get(), is(false));
-        assertThat("not all dataframes have been received as the pushstream has been reset by the client.",allDataFramesReceivedLatch.await(streamId,TimeUnit.SECONDS),is(false));
-
-
-        ByteBuffer buffer = generator.control(new GoAwayFrame(version, streamId, SessionStatus.OK.getCode()));
-        channel.write(buffer);
-        Assert.assertThat(buffer.hasRemaining(), is(false));
-
-        assertThat("GoAway frame is received by server", goAwayReceivedLatch.await(5,TimeUnit.SECONDS), is(true));
-        channel.shutdownOutput();
-        channel.close();
-    }
-
-    @Test
-    public void testOddEvenStreamIds() throws Exception
-    {
-        final CountDownLatch pushStreamIdIsEvenLatch = new CountDownLatch(3);
-
-        Session clientSession = startClient(startServer(new ServerSessionFrameListener.Adapter()
-        {
-            @Override
-            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
-            {
-                stream.syn(new SynInfo(false));
-                return null;
-            }
-        }),new SessionFrameListener.Adapter()
-        {
-            @Override
-            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
-            {
-                assertStreamIdIsEven(stream);
-                pushStreamIdIsEvenLatch.countDown();
-                return super.onSyn(stream,synInfo);
-            }
-        });
-
-        Stream stream = clientSession.syn(new SynInfo(false),null).get();
-        Stream stream2 = clientSession.syn(new SynInfo(false),null).get();
-        Stream stream3 = clientSession.syn(new SynInfo(false),null).get();
-        assertStreamIdIsOdd(stream);
-        assertStreamIdIsOdd(stream2);
-        assertStreamIdIsOdd(stream3);
-
-        assertThat("all pushStreams had even ids",pushStreamIdIsEvenLatch.await(5,TimeUnit.SECONDS),is(true));
-    }
-
-    private void assertStreamIdIsEven(Stream stream)
-    {
-        assertThat("streamId is odd",stream.getId() % 2,is(0));
-    }
-
-    private void assertStreamIdIsOdd(Stream stream)
-    {
-        assertThat("streamId is odd",stream.getId() % 2,is(1));
-    }
-
-    private void assertThatNoExceptionOccured(final CountDownLatch exceptionCountDownLatch) throws InterruptedException
-    {
-        assertThat("No exception occured",exceptionCountDownLatch.await(1,TimeUnit.SECONDS),is(false));
-    }
-}
diff --git a/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/ResetStreamTest.java b/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/ResetStreamTest.java
deleted file mode 100644
index 69d6133..0000000
--- a/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/ResetStreamTest.java
+++ /dev/null
@@ -1,202 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.spdy;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicReference;
-
-import org.eclipse.jetty.spdy.api.DataInfo;
-import org.eclipse.jetty.spdy.api.Handler;
-import org.eclipse.jetty.spdy.api.RstInfo;
-import org.eclipse.jetty.spdy.api.Session;
-import org.eclipse.jetty.spdy.api.SessionFrameListener;
-import org.eclipse.jetty.spdy.api.Stream;
-import org.eclipse.jetty.spdy.api.StreamFrameListener;
-import org.eclipse.jetty.spdy.api.StreamStatus;
-import org.eclipse.jetty.spdy.api.StringDataInfo;
-import org.eclipse.jetty.spdy.api.SynInfo;
-import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener;
-import org.junit.Test;
-
-import static org.hamcrest.Matchers.is;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.assertTrue;
-
-public class ResetStreamTest extends AbstractTest
-{
-    @Test
-    public void testResetStreamIsRemoved() throws Exception
-    {
-        Session session = startClient(startServer(new ServerSessionFrameListener.Adapter()/*TODO, true*/),null);
-
-        Stream stream = session.syn(new SynInfo(false),null).get(5,TimeUnit.SECONDS);
-        session.rst(new RstInfo(stream.getId(),StreamStatus.CANCEL_STREAM)).get(5,TimeUnit.SECONDS);
-
-        assertEquals("session expected to contain 0 streams",0,session.getStreams().size());
-    }
-
-    @Test
-    public void testRefusedStreamIsRemoved() throws Exception
-    {
-        final AtomicReference<Session> serverSessionRef = new AtomicReference<>();
-        final CountDownLatch synLatch = new CountDownLatch(1);
-        final CountDownLatch rstLatch = new CountDownLatch(1);
-        Session clientSession = startClient(startServer(new ServerSessionFrameListener.Adapter()
-        {
-            @Override
-            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
-            {
-                Session serverSession = stream.getSession();
-                serverSessionRef.set(serverSession);
-                serverSession.rst(new RstInfo(stream.getId(),StreamStatus.REFUSED_STREAM));
-                synLatch.countDown();
-                return null;
-            }
-        }),new SessionFrameListener.Adapter()
-        {
-            @Override
-            public void onRst(Session session, RstInfo rstInfo)
-            {
-                rstLatch.countDown();
-            }
-        });
-
-        Stream stream = clientSession.syn(new SynInfo(false),null).get(5,TimeUnit.SECONDS);
-
-        assertTrue("syncLatch didn't count down",synLatch.await(5,TimeUnit.SECONDS));
-        Session serverSession = serverSessionRef.get();
-        assertEquals("serverSession expected to contain 0 streams",0,serverSession.getStreams().size());
-
-        assertTrue("rstLatch didn't count down",rstLatch.await(5,TimeUnit.SECONDS));
-        // Need to sleep a while to give the chance to the implementation to remove the stream
-        TimeUnit.SECONDS.sleep(1);
-        assertTrue("stream is expected to be reset",stream.isReset());
-        assertEquals("clientSession expected to contain 0 streams",0,clientSession.getStreams().size());
-    }
-
-    @Test
-    public void testRefusedStreamIgnoresData() throws Exception
-    {
-        final CountDownLatch synLatch = new CountDownLatch(1);
-        final CountDownLatch dataLatch = new CountDownLatch(1);
-        final CountDownLatch rstLatch = new CountDownLatch(1);
-        Session session = startClient(startServer(new ServerSessionFrameListener.Adapter()
-        {
-            @Override
-            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
-            {
-                try
-                {
-                    // Refuse the stream, we must ignore data frames
-                    assertTrue(synLatch.await(5,TimeUnit.SECONDS));
-                    stream.getSession().rst(new RstInfo(stream.getId(),StreamStatus.REFUSED_STREAM));
-                    return new StreamFrameListener.Adapter()
-                    {
-                        @Override
-                        public void onData(Stream stream, DataInfo dataInfo)
-                        {
-                            dataLatch.countDown();
-                        }
-                    };
-                }
-                catch (InterruptedException x)
-                {
-                    x.printStackTrace();
-                    return null;
-                }
-            }
-        }),new SessionFrameListener.Adapter()
-        {
-            @Override
-            public void onRst(Session session, RstInfo rstInfo)
-            {
-                rstLatch.countDown();
-            }
-        });
-
-        Stream stream = session.syn(new SynInfo(false),null).get(5,TimeUnit.SECONDS);
-        stream.data(new StringDataInfo("data",true),5,TimeUnit.SECONDS,new Handler.Adapter<Void>()
-        {
-            @Override
-            public void completed(Void context)
-            {
-                synLatch.countDown();
-            }
-        });
-
-        assertTrue("rstLatch didn't count down",rstLatch.await(5,TimeUnit.SECONDS));
-        assertTrue("stream is expected to be reset",stream.isReset());
-        assertFalse("dataLatch shouln't be count down",dataLatch.await(1,TimeUnit.SECONDS));
-    }
-
-    @Test
-    public void testResetAfterServerReceivedFirstDataFrameAndSecondDataFrameFails() throws Exception
-    {
-        final CountDownLatch synLatch = new CountDownLatch(1);
-        final CountDownLatch dataLatch = new CountDownLatch(1);
-        final CountDownLatch rstLatch = new CountDownLatch(1);
-        final CountDownLatch failLatch = new CountDownLatch(1);
-        Session session = startClient(startServer(new ServerSessionFrameListener.Adapter()
-        {
-            @Override
-            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
-            {
-                synLatch.countDown();
-                return new StreamFrameListener.Adapter()
-                {
-                    @Override
-                    public void onData(Stream stream, DataInfo dataInfo)
-                    {
-                        dataLatch.countDown();
-                        stream.getSession().rst(new RstInfo(stream.getId(),StreamStatus.REFUSED_STREAM));
-                    }
-                };
-            }
-        }),new SessionFrameListener.Adapter()
-        {
-            @Override
-            public void onRst(Session session, RstInfo rstInfo)
-            {
-                rstLatch.countDown();
-            }
-        });
-
-        Stream stream = session.syn(new SynInfo(false),null).get(5,TimeUnit.SECONDS);
-        assertThat("syn is received by server", synLatch.await(5,TimeUnit.SECONDS),is(true));
-        stream.data(new StringDataInfo("data",false),5,TimeUnit.SECONDS,null);
-        assertThat("stream is reset",rstLatch.await(5,TimeUnit.SECONDS),is(true));
-        stream.data(new StringDataInfo("2nd dataframe",false),5L,TimeUnit.SECONDS,new Handler.Adapter<Void>()
-        {
-            @Override
-            public void failed(Void context, Throwable x)
-            {
-                failLatch.countDown();
-            }
-        });
-
-        assertThat("2nd data call failed",failLatch.await(5,TimeUnit.SECONDS),is(true));
-        assertThat("stream is reset",stream.isReset(),is(true));
-    }
-
-    // TODO: If server already received 2nd dataframe after it rst, it should ignore it. Not easy to do.
-
-}
diff --git a/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/SPDYClientFactoryTest.java b/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/SPDYClientFactoryTest.java
deleted file mode 100644
index 6603f1f..0000000
--- a/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/SPDYClientFactoryTest.java
+++ /dev/null
@@ -1,69 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-
-package org.eclipse.jetty.spdy;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-import junit.framework.Assert;
-import org.eclipse.jetty.spdy.api.GoAwayInfo;
-import org.eclipse.jetty.spdy.api.Session;
-import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener;
-import org.junit.Test;
-
-public class SPDYClientFactoryTest extends AbstractTest
-{
-    @Test
-    public void testStoppingClientFactorySendsGoAway() throws Exception
-    {
-        final CountDownLatch latch = new CountDownLatch(1);
-        startClient(startServer(new ServerSessionFrameListener.Adapter()
-        {
-            @Override
-            public void onGoAway(Session session, GoAwayInfo goAwayInfo)
-            {
-                latch.countDown();
-            }
-        }), null);
-
-        // Sleep a while to avoid the factory is
-        // stopped before a session can be opened
-        TimeUnit.SECONDS.sleep(1);
-
-        clientFactory.stop();
-
-        Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
-        Assert.assertTrue(clientFactory.getSessions().isEmpty());
-    }
-
-    @Test
-    public void testSessionClosedIsRemovedFromClientFactory() throws Exception
-    {
-        Session session = startClient(startServer(null), null);
-
-        session.goAway().get(5, TimeUnit.SECONDS);
-
-        // Sleep a while to allow the factory to remove the session
-        // since it is done asynchronously by the selector thread
-        TimeUnit.SECONDS.sleep(1);
-
-        Assert.assertTrue(clientFactory.getSessions().isEmpty());
-    }
-}
diff --git a/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/SPDYServerConnectorTest.java b/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/SPDYServerConnectorTest.java
deleted file mode 100644
index a370bf7..0000000
--- a/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/SPDYServerConnectorTest.java
+++ /dev/null
@@ -1,69 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-
-package org.eclipse.jetty.spdy;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-import junit.framework.Assert;
-import org.eclipse.jetty.spdy.api.GoAwayInfo;
-import org.eclipse.jetty.spdy.api.Session;
-import org.eclipse.jetty.spdy.api.SessionFrameListener;
-import org.junit.Test;
-
-public class SPDYServerConnectorTest extends AbstractTest
-{
-    @Test
-    public void testStoppingServerConnectorSendsGoAway() throws Exception
-    {
-        final CountDownLatch latch = new CountDownLatch(1);
-        startClient(startServer(null), new SessionFrameListener.Adapter()
-        {
-            @Override
-            public void onGoAway(Session session, GoAwayInfo goAwayInfo)
-            {
-                latch.countDown();
-            }
-        });
-
-        // Sleep a while to avoid the connector is
-        // stopped before a session can be opened
-        TimeUnit.SECONDS.sleep(1);
-
-        connector.stop();
-
-        Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
-        Assert.assertTrue(connector.getSessions().isEmpty());
-    }
-
-    @Test
-    public void testSessionClosedIsRemovedFromServerConnector() throws Exception
-    {
-        Session session = startClient(startServer(null), null);
-
-        session.goAway().get(5, TimeUnit.SECONDS);
-
-        // Sleep a while to allow the connector to remove the session
-        // since it is done asynchronously by the selector thread
-        TimeUnit.SECONDS.sleep(1);
-
-        Assert.assertTrue(connector.getSessions().isEmpty());
-    }
-}
diff --git a/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/SSLEngineLeakTest.java b/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/SSLEngineLeakTest.java
deleted file mode 100644
index fa1cf20..0000000
--- a/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/SSLEngineLeakTest.java
+++ /dev/null
@@ -1,78 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.spdy;
-
-import java.lang.reflect.Field;
-import java.util.Map;
-import java.util.concurrent.Executor;
-import java.util.concurrent.TimeUnit;
-
-import org.eclipse.jetty.npn.NextProtoNego;
-import org.eclipse.jetty.spdy.api.Session;
-import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener;
-import org.eclipse.jetty.util.ssl.SslContextFactory;
-import org.junit.Assert;
-import org.junit.Test;
-
-public class SSLEngineLeakTest extends AbstractTest
-{
-    @Override
-    protected SPDYServerConnector newSPDYServerConnector(ServerSessionFrameListener listener)
-    {
-        SslContextFactory sslContextFactory = newSslContextFactory();
-        return new SPDYServerConnector(listener, sslContextFactory);
-    }
-
-    @Override
-    protected SPDYClient.Factory newSPDYClientFactory(Executor threadPool)
-    {
-        SslContextFactory sslContextFactory = newSslContextFactory();
-        return new SPDYClient.Factory(threadPool, sslContextFactory);
-    }
-
-    @Test
-    public void testSSLEngineLeak() throws Exception
-    {
-        System.gc();
-        Thread.sleep(1000);
-
-        Field field = NextProtoNego.class.getDeclaredField("objects");
-        field.setAccessible(true);
-        @SuppressWarnings("unchecked")
-        Map<Object, NextProtoNego.Provider> objects = (Map<Object, NextProtoNego.Provider>)field.get(null);
-        int initialSize = objects.size();
-
-        avoidStackLocalVariables();
-        // Allow the close to arrive to the server and the selector to process it
-        Thread.sleep(1000);
-
-        // Perform GC to be sure that the WeakHashMap is cleared
-        System.gc();
-        Thread.sleep(1000);
-
-        // Check that the WeakHashMap is empty
-        Assert.assertEquals(initialSize, objects.size());
-    }
-
-    private void avoidStackLocalVariables() throws Exception
-    {
-        Session session = startClient(startServer(null), null);
-        session.goAway().get(5, TimeUnit.SECONDS);
-    }
-}
diff --git a/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/SSLSynReplyTest.java b/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/SSLSynReplyTest.java
deleted file mode 100644
index faefc63..0000000
--- a/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/SSLSynReplyTest.java
+++ /dev/null
@@ -1,50 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-
-package org.eclipse.jetty.spdy;
-
-import java.util.concurrent.Executor;
-
-import org.eclipse.jetty.npn.NextProtoNego;
-import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener;
-import org.eclipse.jetty.util.ssl.SslContextFactory;
-import org.junit.Before;
-
-public class SSLSynReplyTest extends SynReplyTest
-{
-    @Override
-    protected SPDYServerConnector newSPDYServerConnector(ServerSessionFrameListener listener)
-    {
-        SslContextFactory sslContextFactory = newSslContextFactory();
-        return new SPDYServerConnector(listener, sslContextFactory);
-    }
-
-    @Override
-    protected SPDYClient.Factory newSPDYClientFactory(Executor threadPool)
-    {
-        SslContextFactory sslContextFactory = newSslContextFactory();
-        return new SPDYClient.Factory(threadPool, sslContextFactory);
-    }
-
-    @Before
-    public void init()
-    {
-        NextProtoNego.debug = true;
-    }
-}
diff --git a/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/SettingsTest.java b/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/SettingsTest.java
deleted file mode 100644
index 31ca65c..0000000
--- a/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/SettingsTest.java
+++ /dev/null
@@ -1,167 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-
-package org.eclipse.jetty.spdy;
-
-import java.net.InetSocketAddress;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicReference;
-
-import org.eclipse.jetty.spdy.api.SPDY;
-import org.eclipse.jetty.spdy.api.Session;
-import org.eclipse.jetty.spdy.api.SessionFrameListener;
-import org.eclipse.jetty.spdy.api.Settings;
-import org.eclipse.jetty.spdy.api.SettingsInfo;
-import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener;
-import org.junit.Assert;
-import org.junit.Test;
-
-public class SettingsTest extends AbstractTest
-{
-    @Test
-    public void testSettingsUsage() throws Exception
-    {
-        Settings settings = new Settings();
-        int streamsValue = 100;
-        settings.put(new Settings.Setting(Settings.ID.MAX_CONCURRENT_STREAMS, Settings.Flag.PERSIST, streamsValue));
-        int windowValue = 32768;
-        settings.put(new Settings.Setting(Settings.ID.INITIAL_WINDOW_SIZE, windowValue));
-        int newCode = 91;
-        Settings.ID newID = Settings.ID.from(newCode);
-        int newValue = 97;
-        settings.put(new Settings.Setting(newID, newValue));
-
-        Settings.Setting setting1 = settings.get(Settings.ID.MAX_CONCURRENT_STREAMS);
-        Assert.assertSame(Settings.ID.MAX_CONCURRENT_STREAMS, setting1.id());
-        Assert.assertSame(Settings.Flag.PERSIST, setting1.flag());
-        Assert.assertEquals(streamsValue, setting1.value());
-
-        Settings.Setting setting2 = settings.get(Settings.ID.INITIAL_WINDOW_SIZE);
-        Assert.assertSame(Settings.ID.INITIAL_WINDOW_SIZE, setting2.id());
-        Assert.assertSame(Settings.Flag.NONE, setting2.flag());
-        Assert.assertEquals(windowValue, setting2.value());
-
-        int size = settings.size();
-        Settings.Setting setting3 = settings.remove(Settings.ID.from(newCode));
-        Assert.assertEquals(size - 1, settings.size());
-        Assert.assertNotNull(setting3);
-        Assert.assertSame(newID, setting3.id());
-        Assert.assertEquals(newValue, setting3.value());
-    }
-
-    @Test
-    public void testSettings() throws Exception
-    {
-        Settings settings = new Settings();
-        settings.put(new Settings.Setting(Settings.ID.UPLOAD_BANDWIDTH, 1024 * 1024));
-        settings.put(new Settings.Setting(Settings.ID.DOWNLOAD_BANDWIDTH, 1024 * 1024));
-        settings.put(new Settings.Setting(Settings.ID.CURRENT_CONGESTION_WINDOW, Settings.Flag.PERSISTED, 1024));
-        final SettingsInfo clientSettingsInfo = new SettingsInfo(settings);
-        final CountDownLatch latch = new CountDownLatch(1);
-        ServerSessionFrameListener serverSessionFrameListener = new ServerSessionFrameListener.Adapter()
-        {
-            @Override
-            public void onSettings(Session session, SettingsInfo serverSettingsInfo)
-            {
-                Assert.assertEquals(clientSettingsInfo.getFlags(), serverSettingsInfo.getFlags());
-                Assert.assertEquals(clientSettingsInfo.getSettings(), serverSettingsInfo.getSettings());
-                latch.countDown();
-            }
-        };
-        Session session = startClient(startServer(serverSessionFrameListener), null);
-
-        session.settings(clientSettingsInfo);
-
-        Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
-    }
-
-    @Test
-    public void testServerSettings() throws Exception
-    {
-        Settings settings = new Settings();
-        settings.put(new Settings.Setting(Settings.ID.UPLOAD_BANDWIDTH, 1024 * 1024));
-        settings.put(new Settings.Setting(Settings.ID.DOWNLOAD_BANDWIDTH, 1024 * 1024));
-        settings.put(new Settings.Setting(Settings.ID.CURRENT_CONGESTION_WINDOW, Settings.Flag.PERSIST, 1024));
-        final SettingsInfo serverSettingsInfo = new SettingsInfo(settings);
-        ServerSessionFrameListener serverSessionFrameListener = new ServerSessionFrameListener.Adapter()
-        {
-            @Override
-            public void onConnect(Session session)
-            {
-                session.settings(serverSettingsInfo);
-            }
-        };
-
-        final CountDownLatch latch = new CountDownLatch(1);
-        SessionFrameListener clientSessionFrameListener = new SessionFrameListener.Adapter()
-        {
-            @Override
-            public void onSettings(Session session, SettingsInfo clientSettingsInfo)
-            {
-                Assert.assertEquals(serverSettingsInfo.getFlags(), clientSettingsInfo.getFlags());
-                Assert.assertEquals(serverSettingsInfo.getSettings(), clientSettingsInfo.getSettings());
-                latch.countDown();
-            }
-        };
-
-        startClient(startServer(serverSessionFrameListener), clientSessionFrameListener);
-
-        Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
-    }
-
-    @Test
-    public void testSettingIDIsTheSameInBothV2AndV3() throws Exception
-    {
-        final AtomicReference<SettingsInfo> v2 = new AtomicReference<>();
-        final AtomicReference<SettingsInfo> v3 = new AtomicReference<>();
-        final CountDownLatch settingsLatch = new CountDownLatch(2);
-        InetSocketAddress address = startServer(new ServerSessionFrameListener.Adapter()
-        {
-            private final AtomicInteger count = new AtomicInteger();
-
-            @Override
-            public void onSettings(Session session, SettingsInfo settingsInfo)
-            {
-                int count = this.count.incrementAndGet();
-                if (count == 1)
-                    v2.set(settingsInfo);
-                else if (count == 2)
-                    v3.set(settingsInfo);
-                else
-                    Assert.fail();
-                settingsLatch.countDown();
-            }
-        });
-
-        Settings settings = new Settings();
-        settings.put(new Settings.Setting(Settings.ID.INITIAL_WINDOW_SIZE, Settings.Flag.PERSIST, 0xC0_00));
-        SettingsInfo settingsInfo = new SettingsInfo(settings);
-
-        Session sessionV2 = startClient(address, null);
-        sessionV2.settings(settingsInfo);
-
-        Session sessionV3 = clientFactory.newSPDYClient(SPDY.V3).connect(address, null).get(5, TimeUnit.SECONDS);
-        sessionV3.settings(settingsInfo);
-
-        Assert.assertTrue(settingsLatch.await(5, TimeUnit.SECONDS));
-        Assert.assertEquals(v2.get().getSettings(), v3.get().getSettings());
-    }
-}
diff --git a/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/SynDataReplyDataLoadTest.java b/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/SynDataReplyDataLoadTest.java
deleted file mode 100644
index c229ae7..0000000
--- a/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/SynDataReplyDataLoadTest.java
+++ /dev/null
@@ -1,224 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-
-package org.eclipse.jetty.spdy;
-
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import org.eclipse.jetty.spdy.api.ByteBufferDataInfo;
-import org.eclipse.jetty.spdy.api.DataInfo;
-import org.eclipse.jetty.spdy.api.Handler;
-import org.eclipse.jetty.spdy.api.Headers;
-import org.eclipse.jetty.spdy.api.ReplyInfo;
-import org.eclipse.jetty.spdy.api.Session;
-import org.eclipse.jetty.spdy.api.Stream;
-import org.eclipse.jetty.spdy.api.StreamFrameListener;
-import org.eclipse.jetty.spdy.api.StringDataInfo;
-import org.eclipse.jetty.spdy.api.SynInfo;
-import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener;
-import org.junit.Assert;
-import org.junit.Test;
-
-public class SynDataReplyDataLoadTest extends AbstractTest
-{
-    @Test
-    public void testSynDataReplyDataLoad() throws Exception
-    {
-        ServerSessionFrameListener serverSessionFrameListener = new ServerSessionFrameListener.Adapter()
-        {
-            @Override
-            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
-            {
-                stream.reply(new ReplyInfo(synInfo.getHeaders(), false));
-                return new StreamFrameListener.Adapter()
-                {
-                    @Override
-                    public void onData(Stream stream, DataInfo dataInfo)
-                    {
-                        ByteBuffer buffer = dataInfo.asByteBuffer(true);
-                        stream.data(new ByteBufferDataInfo(buffer, dataInfo.isClose()));
-                    }
-                };
-            }
-        };
-        final Session session = startClient(startServer(serverSessionFrameListener), null);
-
-        final int iterations = 500;
-        final int count = 50;
-
-        final Headers headers = new Headers();
-        headers.put("method", "get");
-        headers.put("url", "/");
-        headers.put("version", "http/1.1");
-        headers.put("host", "localhost:8080");
-        headers.put("content-type", "application/octet-stream");
-
-        final CountDownLatch latch = new CountDownLatch(count * iterations);
-        session.addListener(new Session.StreamListener.Adapter()
-        {
-            @Override
-            public void onStreamClosed(Stream stream)
-            {
-                latch.countDown();
-            }
-        });
-
-        ExecutorService threadPool = Executors.newFixedThreadPool(count);
-        List<Callable<Object>> tasks = new ArrayList<>();
-
-        tasks.clear();
-        for (int i = 0; i < count; ++i)
-        {
-            tasks.add(new Callable<Object>()
-            {
-                @Override
-                public Object call() throws Exception
-                {
-                    synGetDataGet(session, headers, iterations);
-                    return null;
-                }
-            });
-        }
-        {
-            long begin = System.nanoTime();
-            List<Future<Object>> futures = threadPool.invokeAll(tasks);
-            for (Future<Object> future : futures)
-                future.get(iterations, TimeUnit.SECONDS);
-            Assert.assertTrue(latch.await(count * iterations, TimeUnit.SECONDS));
-            long end = System.nanoTime();
-            System.err.printf("SYN+GET+DATA+GET completed in %d ms%n", TimeUnit.NANOSECONDS.toMillis(end - begin));
-        }
-
-        tasks.clear();
-        for (int i = 0; i < count; ++i)
-        {
-            tasks.add(new Callable<Object>()
-            {
-                @Override
-                public Object call() throws Exception
-                {
-                    synCompletedData(session, headers, iterations);
-                    return null;
-                }
-            });
-        }
-        {
-            long begin = System.nanoTime();
-            List<Future<Object>> futures = threadPool.invokeAll(tasks);
-            for (Future<Object> future : futures)
-                future.get(iterations, TimeUnit.SECONDS);
-            Assert.assertTrue(latch.await(count * iterations, TimeUnit.SECONDS));
-            long end = System.nanoTime();
-            System.err.printf("SYN+COMPLETED+DATA completed in %d ms%n", TimeUnit.NANOSECONDS.toMillis(end - begin));
-        }
-
-        threadPool.shutdown();
-    }
-
-    private void synCompletedData(Session session, Headers headers, int iterations) throws Exception
-    {
-        final Map<Integer, Integer> counter = new ConcurrentHashMap<>(iterations);
-        final CountDownLatch latch = new CountDownLatch(2 * iterations);
-        for (int i = 0; i < iterations; ++i)
-        {
-            final AtomicInteger count = new AtomicInteger(2);
-            final int index = i;
-            counter.put(index, index);
-            session.syn(new SynInfo(headers, false), new StreamFrameListener.Adapter()
-                    {
-                        @Override
-                        public void onReply(Stream stream, ReplyInfo replyInfo)
-                        {
-                            Assert.assertEquals(2, count.getAndDecrement());
-                            latch.countDown();
-                        }
-
-                        @Override
-                        public void onData(Stream stream, DataInfo dataInfo)
-                        {
-                            // TCP can split the data frames, so I may be receiving more than 1 data frame
-                            dataInfo.asBytes(true);
-                            if (dataInfo.isClose())
-                            {
-                                Assert.assertEquals(1, count.getAndDecrement());
-                                counter.remove(index);
-                                latch.countDown();
-                            }
-                        }
-                    }, 0, TimeUnit.SECONDS, new Handler.Adapter<Stream>()
-            {
-                @Override
-                public void completed(Stream stream)
-                {
-                    stream.data(new StringDataInfo("data_" + stream.getId(), true), 0, TimeUnit.SECONDS, null);
-                }
-            });
-        }
-        Assert.assertTrue(latch.await(iterations, TimeUnit.SECONDS));
-        Assert.assertTrue(counter.toString(), counter.isEmpty());
-    }
-
-    private void synGetDataGet(Session session, Headers headers, int iterations) throws Exception
-    {
-        final Map<Integer, Integer> counter = new ConcurrentHashMap<>(iterations);
-        final CountDownLatch latch = new CountDownLatch(2 * iterations);
-        for (int i = 0; i < iterations; ++i)
-        {
-            final AtomicInteger count = new AtomicInteger(2);
-            final int index = i;
-            counter.put(index, index);
-            Stream stream = session.syn(new SynInfo(headers, false), new StreamFrameListener.Adapter()
-            {
-                @Override
-                public void onReply(Stream stream, ReplyInfo replyInfo)
-                {
-                    Assert.assertEquals(2, count.getAndDecrement());
-                    latch.countDown();
-                }
-
-                @Override
-                public void onData(Stream stream, DataInfo dataInfo)
-                {
-                    // TCP can split the data frames, so I may be receiving more than 1 data frame
-                    dataInfo.asBytes(true);
-                    if (dataInfo.isClose())
-                    {
-                        Assert.assertEquals(1, count.getAndDecrement());
-                        counter.remove(index);
-                        latch.countDown();
-                    }
-                }
-            }).get(5, TimeUnit.SECONDS);
-            stream.data(new StringDataInfo("data_" + stream.getId(), true)).get(5, TimeUnit.SECONDS);
-        }
-        Assert.assertTrue(latch.await(iterations, TimeUnit.SECONDS));
-        Assert.assertTrue(counter.toString(), counter.isEmpty());
-    }
-}
diff --git a/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/SynReplyTest.java b/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/SynReplyTest.java
deleted file mode 100644
index 50cc860..0000000
--- a/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/SynReplyTest.java
+++ /dev/null
@@ -1,372 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.spdy;
-
-import java.io.ByteArrayOutputStream;
-import java.nio.ByteBuffer;
-import java.nio.charset.Charset;
-import java.util.Arrays;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicReference;
-
-import org.eclipse.jetty.spdy.api.BytesDataInfo;
-import org.eclipse.jetty.spdy.api.DataInfo;
-import org.eclipse.jetty.spdy.api.Handler;
-import org.eclipse.jetty.spdy.api.Headers;
-import org.eclipse.jetty.spdy.api.ReplyInfo;
-import org.eclipse.jetty.spdy.api.Session;
-import org.eclipse.jetty.spdy.api.SessionFrameListener;
-import org.eclipse.jetty.spdy.api.Stream;
-import org.eclipse.jetty.spdy.api.StreamFrameListener;
-import org.eclipse.jetty.spdy.api.StringDataInfo;
-import org.eclipse.jetty.spdy.api.SynInfo;
-import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener;
-import org.junit.Assert;
-import org.junit.Test;
-
-public class SynReplyTest extends AbstractTest
-{
-    @Test
-    public void testSynReply() throws Exception
-    {
-        final AtomicReference<Session> sessionRef = new AtomicReference<>();
-        final CountDownLatch sessionLatch = new CountDownLatch(1);
-        final CountDownLatch synLatch = new CountDownLatch(1);
-        ServerSessionFrameListener serverSessionFrameListener = new ServerSessionFrameListener.Adapter()
-        {
-            @Override
-            public void onConnect(Session session)
-            {
-                sessionRef.set(session);
-                sessionLatch.countDown();
-            }
-
-            @Override
-            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
-            {
-                Assert.assertTrue(stream.isHalfClosed());
-                stream.reply(new ReplyInfo(new Headers(), true));
-                synLatch.countDown();
-                return null;
-            }
-        };
-
-        Session session = startClient(startServer(serverSessionFrameListener), null);
-
-        Assert.assertTrue(sessionLatch.await(5, TimeUnit.SECONDS));
-        Session serverSession = sessionRef.get();
-        Assert.assertNotNull(serverSession);
-
-        final CountDownLatch streamCreatedLatch = new CountDownLatch(1);
-        final CountDownLatch streamRemovedLatch = new CountDownLatch(1);
-        session.addListener(new Session.StreamListener()
-        {
-            @Override
-            public void onStreamCreated(Stream stream)
-            {
-                streamCreatedLatch.countDown();
-            }
-
-            @Override
-            public void onStreamClosed(Stream stream)
-            {
-                streamRemovedLatch.countDown();
-            }
-        });
-
-        final CountDownLatch replyLatch = new CountDownLatch(1);
-        Stream stream = session.syn(new SynInfo(new Headers(), true), new StreamFrameListener.Adapter()
-        {
-            @Override
-            public void onReply(Stream stream, ReplyInfo replyInfo)
-            {
-                Assert.assertTrue(stream.isClosed());
-                replyLatch.countDown();
-            }
-        }).get(5, TimeUnit.SECONDS);
-
-        Assert.assertTrue(synLatch.await(5, TimeUnit.SECONDS));
-
-        Assert.assertTrue(streamCreatedLatch.await(5, TimeUnit.SECONDS));
-        Assert.assertTrue(replyLatch.await(5, TimeUnit.SECONDS));
-        Assert.assertTrue(stream.isClosed());
-
-        Assert.assertTrue(streamRemovedLatch.await(5, TimeUnit.SECONDS));
-        Assert.assertEquals(0, session.getStreams().size());
-    }
-
-    @Test
-    public void testSynDataReply() throws Exception
-    {
-        final byte[] dataBytes = "foo".getBytes(Charset.forName("UTF-8"));
-
-        final CountDownLatch synLatch = new CountDownLatch(1);
-        final CountDownLatch dataLatch = new CountDownLatch(1);
-        ServerSessionFrameListener serverSessionFrameListener = new ServerSessionFrameListener.Adapter()
-        {
-            @Override
-            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
-            {
-                Assert.assertFalse(stream.isHalfClosed());
-                Assert.assertFalse(stream.isClosed());
-                synLatch.countDown();
-                return new StreamFrameListener.Adapter()
-                {
-                    @Override
-                    public void onData(Stream stream, DataInfo dataInfo)
-                    {
-                        ByteArrayOutputStream bytes = new ByteArrayOutputStream();
-                        ByteBuffer buffer = ByteBuffer.allocate(2);
-                        while (dataInfo.available() > 0)
-                        {
-                            dataInfo.readInto(buffer);
-                            buffer.flip();
-                            bytes.write(buffer.array(), buffer.arrayOffset(), buffer.remaining());
-                            buffer.clear();
-                        }
-                        Assert.assertTrue(Arrays.equals(dataBytes, bytes.toByteArray()));
-                        Assert.assertTrue(stream.isHalfClosed());
-                        Assert.assertFalse(stream.isClosed());
-
-                        stream.reply(new ReplyInfo(true));
-                        Assert.assertTrue(stream.isClosed());
-                        dataLatch.countDown();
-                    }
-                };
-            }
-        };
-
-        Session session = startClient(startServer(serverSessionFrameListener), null);
-
-        final CountDownLatch streamRemovedLatch = new CountDownLatch(1);
-        session.addListener(new Session.StreamListener.Adapter()
-        {
-            @Override
-            public void onStreamClosed(Stream stream)
-            {
-                streamRemovedLatch.countDown();
-            }
-        });
-
-        final CountDownLatch replyLatch = new CountDownLatch(1);
-        Stream stream = session.syn(new SynInfo(false), new StreamFrameListener.Adapter()
-        {
-            @Override
-            public void onReply(Stream stream, ReplyInfo replyInfo)
-            {
-                replyLatch.countDown();
-            }
-        }).get(5, TimeUnit.SECONDS);
-        stream.data(new BytesDataInfo(dataBytes, true));
-
-        Assert.assertTrue(synLatch.await(5, TimeUnit.SECONDS));
-
-        Assert.assertTrue(dataLatch.await(5, TimeUnit.SECONDS));
-        Assert.assertTrue(replyLatch.await(5, TimeUnit.SECONDS));
-
-        Assert.assertTrue(streamRemovedLatch.await(5, TimeUnit.SECONDS));
-        Assert.assertEquals(0, session.getStreams().size());
-    }
-
-    @Test
-    public void testSynReplyDataData() throws Exception
-    {
-        final String data1 = "foo";
-        final String data2 = "bar";
-        Session session = startClient(startServer(new ServerSessionFrameListener.Adapter()
-        {
-            @Override
-            public StreamFrameListener onSyn(final Stream stream, SynInfo synInfo)
-            {
-                Assert.assertTrue(stream.isHalfClosed());
-
-                stream.reply(new ReplyInfo(false));
-                stream.data(new StringDataInfo(data1, false), 5, TimeUnit.SECONDS, new Handler.Adapter<Void>()
-                {
-                    @Override
-                    public void completed(Void context)
-                    {
-                        stream.data(new StringDataInfo(data2, true));
-                    }
-                });
-
-                return null;
-            }
-        }), null);
-
-        final CountDownLatch replyLatch = new CountDownLatch(1);
-        final CountDownLatch dataLatch1 = new CountDownLatch(1);
-        final CountDownLatch dataLatch2 = new CountDownLatch(1);
-        session.syn(new SynInfo(true), new StreamFrameListener.Adapter()
-        {
-            private AtomicInteger dataCount = new AtomicInteger();
-
-            @Override
-            public void onReply(Stream stream, ReplyInfo replyInfo)
-            {
-                Assert.assertFalse(replyInfo.isClose());
-                replyLatch.countDown();
-            }
-
-            @Override
-            public void onData(Stream stream, DataInfo dataInfo)
-            {
-                int dataCount = this.dataCount.incrementAndGet();
-                if (dataCount == 1)
-                {
-                    String chunk1 = dataInfo.asString("UTF-8", true);
-                    Assert.assertEquals(data1, chunk1);
-                    dataLatch1.countDown();
-                }
-                else if (dataCount == 2)
-                {
-                    String chunk2 = dataInfo.asString("UTF-8", true);
-                    Assert.assertEquals(data2, chunk2);
-                    dataLatch2.countDown();
-                }
-            }
-        });
-
-        Assert.assertTrue(replyLatch.await(5, TimeUnit.SECONDS));
-        Assert.assertTrue(dataLatch1.await(5, TimeUnit.SECONDS));
-        Assert.assertTrue(dataLatch2.await(5, TimeUnit.SECONDS));
-    }
-
-    @Test
-    public void testServerSynDataReplyData() throws Exception
-    {
-        final String serverData = "server";
-        final String clientData = "client";
-
-        final CountDownLatch replyLatch = new CountDownLatch(1);
-        final CountDownLatch clientDataLatch = new CountDownLatch(1);
-        ServerSessionFrameListener serverSessionFrameListener = new ServerSessionFrameListener.Adapter()
-        {
-            @Override
-            public void onConnect(Session session)
-            {
-                session.syn(new SynInfo(false), new StreamFrameListener.Adapter()
-                {
-                    @Override
-                    public void onReply(Stream stream, ReplyInfo replyInfo)
-                    {
-                        replyLatch.countDown();
-                    }
-
-                    @Override
-                    public void onData(Stream stream, DataInfo dataInfo)
-                    {
-                        String data = dataInfo.asString("UTF-8", true);
-                        Assert.assertEquals(clientData, data);
-                        clientDataLatch.countDown();
-                    }
-                }, 0, TimeUnit.MILLISECONDS, new Handler.Adapter<Stream>()
-                {
-                    @Override
-                    public void completed(Stream stream)
-                    {
-                        stream.data(new StringDataInfo(serverData, true));
-                    }
-                });
-            }
-        };
-
-        final CountDownLatch synLatch = new CountDownLatch(1);
-        final CountDownLatch serverDataLatch = new CountDownLatch(1);
-        SessionFrameListener clientSessionFrameListener = new SessionFrameListener.Adapter()
-        {
-            @Override
-            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
-            {
-                Assert.assertEquals(0, stream.getId() % 2);
-
-                stream.reply(new ReplyInfo(false));
-                stream.data(new StringDataInfo(clientData, true));
-                synLatch.countDown();
-
-                return new StreamFrameListener.Adapter()
-                {
-                    @Override
-                    public void onData(Stream stream, DataInfo dataInfo)
-                    {
-                        ByteBuffer buffer = dataInfo.asByteBuffer(false);
-                        String data = Charset.forName("UTF-8").decode(buffer).toString();
-                        Assert.assertEquals(serverData, data);
-                        serverDataLatch.countDown();
-                    }
-                };
-            }
-        };
-
-        startClient(startServer(serverSessionFrameListener), clientSessionFrameListener);
-
-        Assert.assertTrue(synLatch.await(5, TimeUnit.SECONDS));
-        Assert.assertTrue(replyLatch.await(5, TimeUnit.SECONDS));
-        Assert.assertTrue(serverDataLatch.await(5, TimeUnit.SECONDS));
-        Assert.assertTrue(clientDataLatch.await(5, TimeUnit.SECONDS));
-    }
-
-    @Test
-    public void testSynReplyDataSynReplyData() throws Exception
-    {
-        final String data = "foo";
-        ServerSessionFrameListener serverSessionFrameListener = new ServerSessionFrameListener.Adapter()
-        {
-            @Override
-            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
-            {
-                Assert.assertTrue(stream.isHalfClosed());
-
-                stream.reply(new ReplyInfo(false));
-                stream.data(new StringDataInfo(data, true));
-
-                return null;
-            }
-        };
-
-        Session session = startClient(startServer(serverSessionFrameListener), null);
-
-        final CountDownLatch replyLatch = new CountDownLatch(2);
-        final CountDownLatch dataLatch = new CountDownLatch(2);
-        StreamFrameListener clientStreamFrameListener = new StreamFrameListener.Adapter()
-        {
-            @Override
-            public void onReply(Stream stream, ReplyInfo replyInfo)
-            {
-                Assert.assertFalse(replyInfo.isClose());
-                replyLatch.countDown();
-            }
-
-            @Override
-            public void onData(Stream stream, DataInfo dataInfo)
-            {
-                String chunk = dataInfo.asString("UTF-8", true);
-                Assert.assertEquals(data, chunk);
-                dataLatch.countDown();
-            }
-        };
-        session.syn(new SynInfo(true), clientStreamFrameListener);
-        session.syn(new SynInfo(true), clientStreamFrameListener);
-
-        Assert.assertTrue(replyLatch.await(5, TimeUnit.SECONDS));
-        Assert.assertTrue(dataLatch.await(5, TimeUnit.SECONDS));
-    }
-}
diff --git a/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/UnsupportedVersionTest.java b/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/UnsupportedVersionTest.java
deleted file mode 100644
index 76f968e..0000000
--- a/jetty-spdy/spdy-jetty/src/test/java/org/eclipse/jetty/spdy/UnsupportedVersionTest.java
+++ /dev/null
@@ -1,97 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.spdy;
-
-import java.net.InetSocketAddress;
-import java.nio.ByteBuffer;
-import java.nio.channels.SocketChannel;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-import org.eclipse.jetty.spdy.api.Headers;
-import org.eclipse.jetty.spdy.api.SPDY;
-import org.eclipse.jetty.spdy.api.Stream;
-import org.eclipse.jetty.spdy.api.StreamFrameListener;
-import org.eclipse.jetty.spdy.api.StreamStatus;
-import org.eclipse.jetty.spdy.api.SynInfo;
-import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener;
-import org.eclipse.jetty.spdy.frames.ControlFrame;
-import org.eclipse.jetty.spdy.frames.ControlFrameType;
-import org.eclipse.jetty.spdy.frames.RstStreamFrame;
-import org.eclipse.jetty.spdy.frames.SynStreamFrame;
-import org.eclipse.jetty.spdy.generator.Generator;
-import org.eclipse.jetty.spdy.parser.Parser;
-import org.junit.Assert;
-import org.junit.Test;
-
-public class UnsupportedVersionTest extends AbstractTest
-{
-    @Test
-    public void testSynWithUnsupportedVersion() throws Exception
-    {
-        final CountDownLatch synLatch = new CountDownLatch(1);
-        InetSocketAddress address = startServer(new ServerSessionFrameListener.Adapter()
-        {
-            @Override
-            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
-            {
-                synLatch.countDown();
-                return null;
-            }
-
-            @Override
-            public void onException(Throwable x)
-            {
-                // Suppress exception logging for this test
-            }
-        });
-
-        SynStreamFrame frame = new SynStreamFrame(SPDY.V2, SynInfo.FLAG_CLOSE, 1, 0, (byte)0, (short)0, new Headers());
-        Generator generator = new Generator(new StandardByteBufferPool(), new StandardCompressionFactory.StandardCompressor());
-        ByteBuffer buffer = generator.control(frame);
-        // Replace the version byte with an unsupported version
-        buffer.putShort(0, (short)0x8001);
-
-        SocketChannel channel = SocketChannel.open(address);
-        channel.write(buffer);
-        Assert.assertFalse(buffer.hasRemaining());
-
-        Assert.assertFalse(synLatch.await(1, TimeUnit.SECONDS));
-
-        buffer = ByteBuffer.allocate(1024);
-        channel.read(buffer);
-        buffer.flip();
-
-        final CountDownLatch rstLatch = new CountDownLatch(1);
-        Parser parser = new Parser(new StandardCompressionFactory.StandardDecompressor());
-        parser.addListener(new Parser.Listener.Adapter()
-        {
-            @Override
-            public void onControlFrame(ControlFrame frame)
-            {
-                Assert.assertSame(ControlFrameType.RST_STREAM, frame.getType());
-                Assert.assertEquals(StreamStatus.UNSUPPORTED_VERSION.getCode(frame.getVersion()), ((RstStreamFrame)frame).getStatusCode());
-                rstLatch.countDown();
-            }
-        });
-        parser.parse(buffer);
-
-        Assert.assertTrue(rstLatch.await(5, TimeUnit.SECONDS));
-    }
-}
diff --git a/jetty-spdy/spdy-jetty/src/test/resources/log4j.properties b/jetty-spdy/spdy-jetty/src/test/resources/log4j.properties
deleted file mode 100644
index aa88d64..0000000
--- a/jetty-spdy/spdy-jetty/src/test/resources/log4j.properties
+++ /dev/null
@@ -1,14 +0,0 @@
-# LOG4J levels: OFF, FATAL, ERROR, WARN, INFO, DEBUG, ALL
-#
-log4j.rootLogger=ALL,CONSOLE
-
-log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
-#log4j.appender.CONSOLE.threshold=INFO
-log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
-#log4j.appender.CONSOLE.layout.ConversionPattern=%d %t [%5p][%c{1}] %m%n
-log4j.appender.CONSOLE.layout.ConversionPattern=%d %t [%5p][%c{1}] %m%n
-log4j.appender.CONSOLE.target=System.err
-
-# Level tuning
-log4j.logger.org.eclipse.jetty=INFO
-#log4j.logger.org.eclipse.jetty.spdy=DEBUG
diff --git a/jetty-spdy/spdy-npn-tests/pom.xml b/jetty-spdy/spdy-npn-tests/pom.xml
new file mode 100644
index 0000000..7b54c55
--- /dev/null
+++ b/jetty-spdy/spdy-npn-tests/pom.xml
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>org.eclipse.jetty.spdy</groupId>
+        <artifactId>spdy-parent</artifactId>
+        <version>9.2.11-SNAPSHOT</version>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>spdy-npn-tests</artifactId>
+    <name>Jetty :: SPDY :: NPN Tests</name>
+
+    <build>
+        <plugins>
+            <plugin>
+                <artifactId>maven-dependency-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>copy</id>
+                        <phase>generate-resources</phase>
+                        <goals>
+                            <goal>copy</goal>
+                        </goals>
+                        <configuration>
+                            <artifactItems>
+                                <artifactItem>
+                                    <groupId>org.mortbay.jetty.npn</groupId>
+                                    <artifactId>npn-boot</artifactId>
+                                    <version>${npn.version}</version>
+                                    <type>jar</type>
+                                    <overWrite>false</overWrite>
+                                    <outputDirectory>${project.build.directory}/npn</outputDirectory>
+                                </artifactItem>
+                            </artifactItems>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <configuration>
+                    <argLine>-Xbootclasspath/p:${project.build.directory}/npn/npn-boot-${npn.version}.jar</argLine>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.eclipse.jetty.npn</groupId>
+            <artifactId>npn-api</artifactId>
+            <version>${npn.api.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-start</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-server</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty.spdy</groupId>
+            <artifactId>spdy-server</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty.spdy</groupId>
+            <artifactId>spdy-http-server</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty.spdy</groupId>
+            <artifactId>spdy-http-server</artifactId>
+            <version>${project.version}</version>
+            <classifier>tests</classifier>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+    
+</project>
diff --git a/jetty-spdy/spdy-npn-tests/src/test/java/org/eclipse/jetty/spdy/server/AbstractNPNTest.java b/jetty-spdy/spdy-npn-tests/src/test/java/org/eclipse/jetty/spdy/server/AbstractNPNTest.java
new file mode 100644
index 0000000..74c292a
--- /dev/null
+++ b/jetty-spdy/spdy-npn-tests/src/test/java/org/eclipse/jetty/spdy/server/AbstractNPNTest.java
@@ -0,0 +1,77 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.spdy.server;
+
+import java.net.InetSocketAddress;
+
+import org.eclipse.jetty.npn.NextProtoNego;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.spdy.client.SPDYClient;
+import org.eclipse.jetty.toolchain.test.TestTracker;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.junit.After;
+import org.junit.Rule;
+
+public class AbstractNPNTest
+{
+    @Rule
+    public final TestTracker tracker = new TestTracker();
+    protected Server server;
+    protected SPDYServerConnector connector;
+    protected SPDYClient.Factory clientFactory;
+
+    protected InetSocketAddress prepare() throws Exception
+    {
+        server = new Server();
+        connector = new SPDYServerConnector(server, newSslContextFactory(), null);
+        connector.setPort(0);
+        connector.setIdleTimeout(30000);
+        server.addConnector(connector);
+        server.start();
+
+        QueuedThreadPool threadPool = new QueuedThreadPool();
+        threadPool.setName(threadPool.getName() + "-client");
+        clientFactory = new SPDYClient.Factory(threadPool);
+        clientFactory.start();
+
+        NextProtoNego.debug = true;
+
+        return new InetSocketAddress("localhost", connector.getLocalPort());
+    }
+
+    protected SslContextFactory newSslContextFactory()
+    {
+        SslContextFactory sslContextFactory = new SslContextFactory();
+        sslContextFactory.setKeyStorePath("src/test/resources/keystore.jks");
+        sslContextFactory.setKeyStorePassword("storepwd");
+        sslContextFactory.setTrustStorePath("src/test/resources/truststore.jks");
+        sslContextFactory.setTrustStorePassword("storepwd");
+        sslContextFactory.setProtocol("TLSv1");
+        sslContextFactory.setIncludeProtocols("TLSv1");
+        return sslContextFactory;
+    }
+
+    @After
+    public void dispose() throws Exception
+    {
+        clientFactory.stop();
+        server.stop();
+    }
+}
diff --git a/jetty-spdy/spdy-npn-tests/src/test/java/org/eclipse/jetty/spdy/server/NPNModuleTest.java b/jetty-spdy/spdy-npn-tests/src/test/java/org/eclipse/jetty/spdy/server/NPNModuleTest.java
new file mode 100644
index 0000000..c2497eb
--- /dev/null
+++ b/jetty-spdy/spdy-npn-tests/src/test/java/org/eclipse/jetty/spdy/server/NPNModuleTest.java
@@ -0,0 +1,193 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.spdy.server;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import org.eclipse.jetty.start.BaseHome;
+import org.eclipse.jetty.start.FileArg;
+import org.eclipse.jetty.start.Module;
+import org.eclipse.jetty.toolchain.test.FS;
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.eclipse.jetty.util.IO;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+ at RunWith(Parameterized.class)
+public class NPNModuleTest
+{
+    /** This is here to prevent pointless download attempts */
+    private static final List<String> KNOWN_GOOD_NPN_URLS = new ArrayList<>();
+
+    static
+    {
+        /** The main() method in this test case can be run to validate this list independently */
+        KNOWN_GOOD_NPN_URLS.add("http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.11.v20150415/npn-boot-1.1.11.v20150415.jar");
+        KNOWN_GOOD_NPN_URLS.add("http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.10.v20150130/npn-boot-1.1.10.v20150130.jar");
+        KNOWN_GOOD_NPN_URLS.add("http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.9.v20141016/npn-boot-1.1.9.v20141016.jar");
+        KNOWN_GOOD_NPN_URLS.add("http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.8.v20141013/npn-boot-1.1.8.v20141013.jar");
+        KNOWN_GOOD_NPN_URLS.add("http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.6.v20130911/npn-boot-1.1.6.v20130911.jar");
+        KNOWN_GOOD_NPN_URLS.add("http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.5.v20130313/npn-boot-1.1.5.v20130313.jar");
+        KNOWN_GOOD_NPN_URLS.add("http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.4.v20130313/npn-boot-1.1.4.v20130313.jar");
+        KNOWN_GOOD_NPN_URLS.add("http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.3.v20130313/npn-boot-1.1.3.v20130313.jar");
+        KNOWN_GOOD_NPN_URLS.add("http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.2.v20130305/npn-boot-1.1.2.v20130305.jar");
+        KNOWN_GOOD_NPN_URLS.add("http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.1.v20121030/npn-boot-1.1.1.v20121030.jar");
+        KNOWN_GOOD_NPN_URLS.add("http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.0.v20120525/npn-boot-1.1.0.v20120525.jar");
+    }
+
+    @Parameters(name = "{index}: mod:{0}")
+    public static List<Object[]> data()
+    {
+        File npnBootModDir = MavenTestingUtils.getProjectDir("../spdy-http-server/src/main/config/modules/protonego-impl");
+        List<Object[]> data = new ArrayList<>();
+        for (File file : npnBootModDir.listFiles())
+        {
+            if (Pattern.matches("npn-.*\\.mod",file.getName()))
+            {
+                data.add(new Object[] { file.getName() });
+            }
+        }
+        return data;
+    }
+
+    @Parameter(value = 0)
+    public String modBootFile;
+
+    private static BaseHome basehome;
+
+    @BeforeClass
+    public static void initBaseHome() throws IOException
+    {
+        File homeDir = MavenTestingUtils.getProjectDir("../spdy-http-server/src/main/config");
+        File baseDir = MavenTestingUtils.getTargetTestingDir(NPNModuleTest.class.getName());
+        FS.ensureEmpty(baseDir);
+        
+        String cmdLine[] = { "jetty.home="+homeDir.getAbsolutePath(),"jetty.base="+baseDir.getAbsolutePath() };
+        basehome = new BaseHome(cmdLine);
+    }
+
+    /**
+     * Check the sanity of the npn-boot file module 
+     */
+    @Test
+    public void testModuleValues() throws IOException
+    {
+        Path modFile = basehome.getPath("modules/protonego-impl/" + modBootFile);
+        Module mod = new Module(basehome,modFile);
+        assertNotNull("module",mod);
+        
+        // Validate logical name
+        assertThat("Module name",mod.getName(),is("protonego-boot"));
+
+        List<String> expectedBootClasspath = new ArrayList<>();
+
+        for (String line : mod.getFiles())
+        {
+            FileArg farg = new FileArg(line);
+            if (farg.uri != null)
+            {
+                assertTrue("Not a known good NPN URL: " + farg.uri,KNOWN_GOOD_NPN_URLS.contains(farg.uri));
+                expectedBootClasspath.add("-Xbootclasspath/p:" + farg.location);
+            }
+        }
+
+        for (String line : mod.getJvmArgs())
+        {
+            expectedBootClasspath.remove(line);
+        }
+
+        if (expectedBootClasspath.size() > 0)
+        {
+            StringBuilder err = new StringBuilder();
+            err.append("XBootClasspath mismatch between [files] and [exec]");
+            err.append("\nThe following are inferred from your [files] definition in ");
+            err.append(modFile.toAbsolutePath().toString());
+            err.append("\nbut are not referenced in your [exec] section");
+            for (String entry : expectedBootClasspath)
+            {
+                err.append("\n").append(entry);
+            }
+            fail(err.toString());
+        }
+    }
+
+    public static void main(String[] args)
+    {
+        File outputDir = MavenTestingUtils.getTargetTestingDir(NPNModuleTest.class.getSimpleName() + "-main");
+        FS.ensureEmpty(outputDir);
+        for (String ref : KNOWN_GOOD_NPN_URLS)
+        {
+            try
+            {
+                URL url = new URL(ref);
+                System.err.printf("Attempting: %s%n",ref);
+                HttpURLConnection connection = (HttpURLConnection)url.openConnection();
+                String refname = url.toURI().getPath();
+                int idx = refname.lastIndexOf('/');
+                File outputFile = new File(outputDir,refname.substring(idx));
+                try (InputStream stream = connection.getInputStream(); FileOutputStream out = new FileOutputStream(outputFile))
+                {
+                    assertThat("Response Status Code",connection.getResponseCode(),is(200));
+                    IO.copy(stream,out);
+                    System.err.printf("Downloaded %,d bytes%n",outputFile.length());
+                }
+                catch (IOException e)
+                {
+                    e.printStackTrace(System.err);
+                }
+            }
+            catch (MalformedURLException e)
+            {
+                System.err.printf("Bad Ref: %s%n",ref);
+                e.printStackTrace(System.err);
+            }
+            catch (URISyntaxException e)
+            {
+                System.err.printf("Bad Ref Syntax: %s%n",ref);
+                e.printStackTrace(System.err);
+            }
+            catch (IOException e)
+            {
+                System.err.printf("Bad Connection: %s%n",ref);
+                e.printStackTrace(System.err);
+            }
+        }
+    }
+}
diff --git a/jetty-spdy/spdy-npn-tests/src/test/java/org/eclipse/jetty/spdy/server/NPNNegotiationTest.java b/jetty-spdy/spdy-npn-tests/src/test/java/org/eclipse/jetty/spdy/server/NPNNegotiationTest.java
new file mode 100644
index 0000000..62b4449
--- /dev/null
+++ b/jetty-spdy/spdy-npn-tests/src/test/java/org/eclipse/jetty/spdy/server/NPNNegotiationTest.java
@@ -0,0 +1,207 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.spdy.server;
+
+import java.io.BufferedReader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.InetSocketAddress;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocket;
+
+import org.eclipse.jetty.npn.NextProtoNego;
+import org.eclipse.jetty.server.HttpConnectionFactory;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class NPNNegotiationTest extends AbstractNPNTest
+{
+    @Test
+    public void testServerAdvertisingHTTPSpeaksHTTP() throws Exception
+    {
+        InetSocketAddress address = prepare();
+        connector.addConnectionFactory(new HttpConnectionFactory());
+
+        SslContextFactory sslContextFactory = newSslContextFactory();
+        sslContextFactory.start();
+        SSLContext sslContext = sslContextFactory.getSslContext();
+
+        try (SSLSocket client = (SSLSocket)sslContext.getSocketFactory().createSocket(address.getAddress(), address.getPort()))
+        {
+            client.setUseClientMode(true);
+            client.setSoTimeout(5000);
+
+            NextProtoNego.put(client, new NextProtoNego.ClientProvider()
+            {
+                @Override
+                public boolean supports()
+                {
+                    return true;
+                }
+
+                @Override
+                public void unsupported()
+                {
+                }
+
+                @Override
+                public String selectProtocol(List<String> strings)
+                {
+                    Assert.assertNotNull(strings);
+                    String protocol = "http/1.1";
+                    Assert.assertTrue(strings.contains(protocol));
+                    return protocol;
+                }
+            });
+
+            client.startHandshake();
+
+            // Verify that the server really speaks http/1.1
+
+            OutputStream output = client.getOutputStream();
+            output.write(("" +
+                    "GET / HTTP/1.1\r\n" +
+                    "Host: localhost:" + address.getPort() + "\r\n" +
+                    "\r\n" +
+                    "").getBytes(StandardCharsets.UTF_8));
+            output.flush();
+
+            InputStream input = client.getInputStream();
+            BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
+            String line = reader.readLine();
+            Assert.assertTrue(line.contains(" 404 "));
+        }
+    }
+
+    @Test
+    public void testServerAdvertisingSPDYAndHTTPSpeaksHTTPWhenNegotiated() throws Exception
+    {
+        InetSocketAddress address = prepare();
+        connector.addConnectionFactory(new HttpConnectionFactory());
+
+        SslContextFactory sslContextFactory = newSslContextFactory();
+        sslContextFactory.start();
+        SSLContext sslContext = sslContextFactory.getSslContext();
+        try (SSLSocket client = (SSLSocket)sslContext.getSocketFactory().createSocket(address.getAddress(), address.getPort()))
+        {
+            client.setUseClientMode(true);
+            client.setSoTimeout(5000);
+
+            NextProtoNego.put(client, new NextProtoNego.ClientProvider()
+            {
+                @Override
+                public boolean supports()
+                {
+                    return true;
+                }
+
+                @Override
+                public void unsupported()
+                {
+                }
+
+                @Override
+                public String selectProtocol(List<String> strings)
+                {
+                    Assert.assertNotNull(strings);
+                    String spdyProtocol = "spdy/2";
+                    Assert.assertTrue(strings.contains(spdyProtocol));
+                    String httpProtocol = "http/1.1";
+                    Assert.assertTrue(strings.contains(httpProtocol));
+                    Assert.assertTrue(strings.indexOf(spdyProtocol) < strings.indexOf(httpProtocol));
+                    return httpProtocol;
+                }
+            });
+
+            client.startHandshake();
+
+            // Verify that the server really speaks http/1.1
+
+            OutputStream output = client.getOutputStream();
+            output.write(("" +
+                    "GET / HTTP/1.1\r\n" +
+                    "Host: localhost:" + address.getPort() + "\r\n" +
+                    "\r\n" +
+                    "").getBytes(StandardCharsets.UTF_8));
+            output.flush();
+
+            InputStream input = client.getInputStream();
+            BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
+            String line = reader.readLine();
+            Assert.assertTrue(line.contains(" 404 "));
+        }
+    }
+
+    @Test
+    public void testServerAdvertisingSPDYAndHTTPSpeaksDefaultProtocolWhenNPNMissing() throws Exception
+    {
+        InetSocketAddress address = prepare();
+        connector.addConnectionFactory(new HttpConnectionFactory());
+
+        SslContextFactory sslContextFactory = newSslContextFactory();
+        sslContextFactory.start();
+        SSLContext sslContext = sslContextFactory.getSslContext();
+        try (SSLSocket client = (SSLSocket)sslContext.getSocketFactory().createSocket(address.getAddress(), address.getPort()))
+        {
+            client.setUseClientMode(true);
+            client.setSoTimeout(5000);
+
+            NextProtoNego.put(client, new NextProtoNego.ClientProvider()
+            {
+                @Override
+                public boolean supports()
+                {
+                    return false;
+                }
+
+                @Override
+                public void unsupported()
+                {
+                }
+
+                @Override
+                public String selectProtocol(List<String> strings)
+                {
+                    return null;
+                }
+            });
+
+            client.startHandshake();
+
+            // Verify that the server really speaks http/1.1
+
+            OutputStream output = client.getOutputStream();
+            output.write(("" +
+                    "GET / HTTP/1.1\r\n" +
+                    "Host: localhost:" + address.getPort() + "\r\n" +
+                    "\r\n" +
+                    "").getBytes(StandardCharsets.UTF_8));
+            output.flush();
+
+            InputStream input = client.getInputStream();
+            BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
+            String line = reader.readLine();
+            Assert.assertTrue(line.contains(" 404 "));
+        }
+    }
+}
diff --git a/jetty-spdy/spdy-npn-tests/src/test/java/org/eclipse/jetty/spdy/server/SSLEngineLeakTest.java b/jetty-spdy/spdy-npn-tests/src/test/java/org/eclipse/jetty/spdy/server/SSLEngineLeakTest.java
new file mode 100644
index 0000000..01074eb
--- /dev/null
+++ b/jetty-spdy/spdy-npn-tests/src/test/java/org/eclipse/jetty/spdy/server/SSLEngineLeakTest.java
@@ -0,0 +1,71 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.spdy.server;
+
+import java.lang.reflect.Field;
+import java.net.InetSocketAddress;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.npn.NextProtoNego;
+import org.eclipse.jetty.spdy.api.GoAwayInfo;
+import org.eclipse.jetty.spdy.api.SPDY;
+import org.eclipse.jetty.spdy.api.Session;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class SSLEngineLeakTest extends AbstractNPNTest
+{
+    @Test
+    public void testSSLEngineLeak() throws Exception
+    {
+        System.gc();
+        Thread.sleep(1000);
+
+        Field field = NextProtoNego.class.getDeclaredField("objects");
+        field.setAccessible(true);
+        @SuppressWarnings("unchecked")
+        Map<Object, NextProtoNego.Provider> objects = (Map<Object, NextProtoNego.Provider>)field.get(null);
+        int initialSize = objects.size();
+
+        avoidStackLocalVariables();
+        // Allow the close to arrive to the server and the selector to process it
+        Thread.sleep(1000);
+
+        // Perform GC to be sure that the map is cleared
+        System.gc();
+        Thread.sleep(1000);
+
+        // Check that the map is empty
+        if (objects.size() != initialSize)
+        {
+            System.err.println(objects);
+            server.dumpStdErr();
+        }
+
+        Assert.assertEquals(initialSize, objects.size());
+    }
+
+    private void avoidStackLocalVariables() throws Exception
+    {
+        InetSocketAddress address = prepare();
+        Session session = clientFactory.newSPDYClient(SPDY.V3).connect(address, null);
+        session.goAway(new GoAwayInfo(5, TimeUnit.SECONDS));
+    }
+}
diff --git a/jetty-spdy/spdy-npn-tests/src/test/java/org/eclipse/jetty/spdy/server/SSLSynReplyTest.java b/jetty-spdy/spdy-npn-tests/src/test/java/org/eclipse/jetty/spdy/server/SSLSynReplyTest.java
new file mode 100644
index 0000000..a1b2169
--- /dev/null
+++ b/jetty-spdy/spdy-npn-tests/src/test/java/org/eclipse/jetty/spdy/server/SSLSynReplyTest.java
@@ -0,0 +1,150 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.spdy.server;
+
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.channels.SocketChannel;
+import java.util.List;
+import javax.net.ssl.SSLEngine;
+
+import org.eclipse.jetty.npn.NextProtoNego;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class SSLSynReplyTest extends AbstractNPNTest
+{
+    @Test
+    public void testGentleCloseDuringHandshake() throws Exception
+    {
+        InetSocketAddress address = prepare();
+        SslContextFactory sslContextFactory = newSslContextFactory();
+        sslContextFactory.start();
+        SSLEngine sslEngine = sslContextFactory.newSSLEngine(address);
+        sslEngine.setUseClientMode(true);
+        NextProtoNego.put(sslEngine, new NextProtoNego.ClientProvider()
+        {
+            @Override
+            public boolean supports()
+            {
+                return true;
+            }
+
+            @Override
+            public void unsupported()
+            {
+            }
+
+            @Override
+            public String selectProtocol(List<String> protocols)
+            {
+                return null;
+            }
+        });
+        sslEngine.beginHandshake();
+
+        ByteBuffer encrypted = ByteBuffer.allocate(sslEngine.getSession().getPacketBufferSize());
+        sslEngine.wrap(BufferUtil.EMPTY_BUFFER, encrypted);
+        encrypted.flip();
+
+        try (SocketChannel channel = SocketChannel.open(address))
+        {
+            // Send ClientHello, immediately followed by TLS Close Alert and then by FIN
+            channel.write(encrypted);
+            sslEngine.closeOutbound();
+            encrypted.clear();
+            sslEngine.wrap(BufferUtil.EMPTY_BUFFER, encrypted);
+            encrypted.flip();
+            channel.write(encrypted);
+            channel.shutdownOutput();
+
+            // Read ServerHello from server
+            encrypted.clear();
+            int read = channel.read(encrypted);
+            encrypted.flip();
+            Assert.assertTrue(read > 0);
+            // Cannot decrypt, as the SSLEngine has been already closed
+
+            // Now if we read more, we should either read the TLS Close Alert, or directly -1
+            encrypted.clear();
+            read = channel.read(encrypted);
+            // Sending a TLS Close Alert during handshake results in an exception when
+            // unwrapping that the server react to by closing the connection abruptly.
+            Assert.assertTrue(read < 0);
+        }
+    }
+
+    @Test
+    public void testAbruptCloseDuringHandshake() throws Exception
+    {
+        InetSocketAddress address = prepare();
+        SslContextFactory sslContextFactory = newSslContextFactory();
+        sslContextFactory.start();
+        SSLEngine sslEngine = sslContextFactory.newSSLEngine(address);
+        sslEngine.setUseClientMode(true);
+        NextProtoNego.put(sslEngine, new NextProtoNego.ClientProvider()
+        {
+            @Override
+            public boolean supports()
+            {
+                return true;
+            }
+
+            @Override
+            public void unsupported()
+            {
+            }
+
+            @Override
+            public String selectProtocol(List<String> protocols)
+            {
+                return null;
+            }
+        });
+        sslEngine.beginHandshake();
+
+        ByteBuffer encrypted = ByteBuffer.allocate(sslEngine.getSession().getPacketBufferSize());
+        sslEngine.wrap(BufferUtil.EMPTY_BUFFER, encrypted);
+        encrypted.flip();
+
+        try (SocketChannel channel = SocketChannel.open(address))
+        {
+            // Send ClientHello, immediately followed by FIN (no TLS Close Alert)
+            channel.write(encrypted);
+            channel.shutdownOutput();
+
+            // Read ServerHello from server
+            encrypted.clear();
+            int read = channel.read(encrypted);
+            encrypted.flip();
+            Assert.assertTrue(read > 0);
+            ByteBuffer decrypted = ByteBuffer.allocate(sslEngine.getSession().getApplicationBufferSize());
+            sslEngine.unwrap(encrypted, decrypted);
+
+            // Now if we read more, we should either read the TLS Close Alert, or directly -1
+            encrypted.clear();
+            read = channel.read(encrypted);
+            // Since we have close the connection abruptly, the server also does so
+            Assert.assertTrue(read < 0);
+        }
+    }
+}
diff --git a/jetty-spdy/spdy-npn-tests/src/test/java/org/eclipse/jetty/spdy/server/proxy/NPNProxySPDYToHTTPLoadTest.java b/jetty-spdy/spdy-npn-tests/src/test/java/org/eclipse/jetty/spdy/server/proxy/NPNProxySPDYToHTTPLoadTest.java
new file mode 100644
index 0000000..b91c8bf
--- /dev/null
+++ b/jetty-spdy/spdy-npn-tests/src/test/java/org/eclipse/jetty/spdy/server/proxy/NPNProxySPDYToHTTPLoadTest.java
@@ -0,0 +1,29 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.spdy.server.proxy;
+
+import org.eclipse.jetty.spdy.server.NPNServerConnectionFactory;
+
+public class NPNProxySPDYToHTTPLoadTest extends ProxySPDYToHTTPLoadTest
+{
+    public NPNProxySPDYToHTTPLoadTest(short version)
+    {
+        super(version, new NPNServerConnectionFactory("spdy/3", "spdy/2", "http/1.1"));
+    }
+}
diff --git a/jetty-spdy/spdy-npn-tests/src/test/java/org/eclipse/jetty/spdy/server/proxy/NPNProxySPDYToHTTPTest.java b/jetty-spdy/spdy-npn-tests/src/test/java/org/eclipse/jetty/spdy/server/proxy/NPNProxySPDYToHTTPTest.java
new file mode 100644
index 0000000..5a23193
--- /dev/null
+++ b/jetty-spdy/spdy-npn-tests/src/test/java/org/eclipse/jetty/spdy/server/proxy/NPNProxySPDYToHTTPTest.java
@@ -0,0 +1,27 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.spdy.server.proxy;
+
+public class NPNProxySPDYToHTTPTest extends ProxySPDYToHTTPTest
+{
+    public NPNProxySPDYToHTTPTest(short version)
+    {
+        super(version);
+    }
+}
diff --git a/jetty-spdy/spdy-npn-tests/src/test/java/org/eclipse/jetty/spdy/server/proxy/NPNProxySPDYToSPDYLoadTest.java b/jetty-spdy/spdy-npn-tests/src/test/java/org/eclipse/jetty/spdy/server/proxy/NPNProxySPDYToSPDYLoadTest.java
new file mode 100644
index 0000000..feaae88
--- /dev/null
+++ b/jetty-spdy/spdy-npn-tests/src/test/java/org/eclipse/jetty/spdy/server/proxy/NPNProxySPDYToSPDYLoadTest.java
@@ -0,0 +1,27 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.spdy.server.proxy;
+
+public class NPNProxySPDYToSPDYLoadTest extends ProxySPDYToSPDYLoadTest
+{
+    public NPNProxySPDYToSPDYLoadTest(short version)
+    {
+        super(version);
+    }
+}
diff --git a/jetty-spdy/spdy-npn-tests/src/test/java/org/eclipse/jetty/spdy/server/proxy/NPNProxySPDYToSPDYTest.java b/jetty-spdy/spdy-npn-tests/src/test/java/org/eclipse/jetty/spdy/server/proxy/NPNProxySPDYToSPDYTest.java
new file mode 100644
index 0000000..4a85912
--- /dev/null
+++ b/jetty-spdy/spdy-npn-tests/src/test/java/org/eclipse/jetty/spdy/server/proxy/NPNProxySPDYToSPDYTest.java
@@ -0,0 +1,27 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.spdy.server.proxy;
+
+public class NPNProxySPDYToSPDYTest extends ProxySPDYToSPDYTest
+{
+    public NPNProxySPDYToSPDYTest(short version)
+    {
+        super(version);
+    }
+}
diff --git a/jetty-spdy/spdy-npn-tests/src/test/resources/jetty-logging.properties b/jetty-spdy/spdy-npn-tests/src/test/resources/jetty-logging.properties
new file mode 100644
index 0000000..ead13ec
--- /dev/null
+++ b/jetty-spdy/spdy-npn-tests/src/test/resources/jetty-logging.properties
@@ -0,0 +1,2 @@
+org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
+#org.eclipse.jetty.spdy.LEVEL=DEBUG
diff --git a/jetty-spdy/spdy-jetty-http-webapp/src/main/resources/keystore.jks b/jetty-spdy/spdy-npn-tests/src/test/resources/keystore.jks
similarity index 100%
rename from jetty-spdy/spdy-jetty-http-webapp/src/main/resources/keystore.jks
rename to jetty-spdy/spdy-npn-tests/src/test/resources/keystore.jks
diff --git a/jetty-spdy/spdy-jetty-http-webapp/src/main/resources/truststore.jks b/jetty-spdy/spdy-npn-tests/src/test/resources/truststore.jks
similarity index 100%
rename from jetty-spdy/spdy-jetty-http-webapp/src/main/resources/truststore.jks
rename to jetty-spdy/spdy-npn-tests/src/test/resources/truststore.jks
diff --git a/jetty-spdy/spdy-server/pom.xml b/jetty-spdy/spdy-server/pom.xml
new file mode 100644
index 0000000..e9b8bda
--- /dev/null
+++ b/jetty-spdy/spdy-server/pom.xml
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>org.eclipse.jetty.spdy</groupId>
+        <artifactId>spdy-parent</artifactId>
+        <version>9.2.13.v20150730</version>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>spdy-server</artifactId>
+    <name>Jetty :: SPDY :: Server Binding</name>
+
+    <properties>
+        <bundle-symbolic-name>${project.groupId}.server</bundle-symbolic-name>
+    </properties>
+
+    <url>http://www.eclipse.org/jetty</url>
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>manifest</goal>
+                        </goals>
+                        <configuration>
+                            <instructions>
+                                <Export-Package>org.eclipse.jetty.spdy.server;version="9.1"</Export-Package>
+                                <Import-Package>org.eclipse.jetty.alpn;resolution:=optional,org.eclipse.jetty.alpn.server;resolution:=optional, org.eclipse.jetty.npn;resolution:=optional,org.eclipse.jetty.*;version="[9.0,10.0)",*</Import-Package>
+                                <_nouses>true</_nouses>
+                            </instructions>
+                          </configuration>
+                       </execution>
+                  </executions>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.eclipse.jetty.spdy</groupId>
+            <artifactId>spdy-core</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty.spdy</groupId>
+            <artifactId>spdy-client</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-server</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty.npn</groupId>
+            <artifactId>npn-api</artifactId>
+            <version>${npn.api.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty.alpn</groupId>
+            <artifactId>alpn-api</artifactId>
+            <version>${alpn.api.version}</version>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+
+</project>
diff --git a/jetty-spdy/spdy-server/src/main/java/org/eclipse/jetty/spdy/server/NPNServerConnection.java b/jetty-spdy/spdy-server/src/main/java/org/eclipse/jetty/spdy/server/NPNServerConnection.java
new file mode 100644
index 0000000..fbc1af3
--- /dev/null
+++ b/jetty-spdy/spdy-server/src/main/java/org/eclipse/jetty/spdy/server/NPNServerConnection.java
@@ -0,0 +1,68 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.spdy.server;
+
+import java.util.List;
+import javax.net.ssl.SSLEngine;
+
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.npn.NextProtoNego;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.NegotiatingServerConnection;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public class NPNServerConnection extends NegotiatingServerConnection implements NextProtoNego.ServerProvider
+{
+    private static final Logger LOG = Log.getLogger(NPNServerConnection.class);
+
+    public NPNServerConnection(EndPoint endPoint, SSLEngine engine, Connector connector, List<String> protocols, String defaultProtocol)
+    {
+        super(connector, endPoint, engine, protocols, defaultProtocol);
+        NextProtoNego.put(engine, this);
+    }
+
+    @Override
+    public void unsupported()
+    {
+        protocolSelected(getDefaultProtocol());
+    }
+
+    @Override
+    public List<String> protocols()
+    {
+        return getProtocols();
+    }
+
+    @Override
+    public void protocolSelected(String protocol)
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("{} protocol selected {}", this, protocol);
+        setProtocol(protocol != null ? protocol : getDefaultProtocol());
+        NextProtoNego.remove(getSSLEngine());
+    }
+
+    @Override
+    public void close()
+    {
+        NextProtoNego.remove(getSSLEngine());
+        super.close();
+    }
+}
diff --git a/jetty-spdy/spdy-server/src/main/java/org/eclipse/jetty/spdy/server/NPNServerConnectionFactory.java b/jetty-spdy/spdy-server/src/main/java/org/eclipse/jetty/spdy/server/NPNServerConnectionFactory.java
new file mode 100644
index 0000000..e4fab41
--- /dev/null
+++ b/jetty-spdy/spdy-server/src/main/java/org/eclipse/jetty/spdy/server/NPNServerConnectionFactory.java
@@ -0,0 +1,61 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.spdy.server;
+
+import java.util.List;
+import javax.net.ssl.SSLEngine;
+
+import org.eclipse.jetty.io.AbstractConnection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.npn.NextProtoNego;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.NegotiatingServerConnectionFactory;
+import org.eclipse.jetty.util.annotation.Name;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public class NPNServerConnectionFactory extends NegotiatingServerConnectionFactory
+{
+    private static final Logger LOG = Log.getLogger(NPNServerConnectionFactory.class);
+
+    public NPNServerConnectionFactory(@Name("protocols") String... protocols)
+    {
+        super("npn", protocols);
+        try
+        {
+            ClassLoader npnClassLoader = NextProtoNego.class.getClassLoader();
+            if (npnClassLoader != null)
+            {
+                LOG.warn("NPN must be in the boot classloader, not in: " + npnClassLoader);
+                throw new IllegalStateException("NPN must be in the boot classloader");
+            }
+        }
+        catch (Throwable x)
+        {
+            LOG.warn("NPN not available: " + x);
+            throw new IllegalStateException("NPN not available", x);
+        }
+    }
+
+    @Override
+    protected AbstractConnection newServerConnection(Connector connector, EndPoint endPoint, SSLEngine engine, List<String> protocols, String defaultProtocol)
+    {
+        return new NPNServerConnection(endPoint, engine, connector, protocols, defaultProtocol);
+    }
+}
diff --git a/jetty-spdy/spdy-server/src/main/java/org/eclipse/jetty/spdy/server/SPDYServerConnectionFactory.java b/jetty-spdy/spdy-server/src/main/java/org/eclipse/jetty/spdy/server/SPDYServerConnectionFactory.java
new file mode 100644
index 0000000..8bf234f
--- /dev/null
+++ b/jetty-spdy/spdy-server/src/main/java/org/eclipse/jetty/spdy/server/SPDYServerConnectionFactory.java
@@ -0,0 +1,245 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.spdy.server;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.server.AbstractConnectionFactory;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.spdy.CompressionFactory;
+import org.eclipse.jetty.spdy.FlowControlStrategy;
+import org.eclipse.jetty.spdy.StandardCompressionFactory;
+import org.eclipse.jetty.spdy.StandardSession;
+import org.eclipse.jetty.spdy.api.GoAwayInfo;
+import org.eclipse.jetty.spdy.api.Session;
+import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener;
+import org.eclipse.jetty.spdy.client.FlowControlStrategyFactory;
+import org.eclipse.jetty.spdy.client.SPDYConnection;
+import org.eclipse.jetty.spdy.generator.Generator;
+import org.eclipse.jetty.spdy.parser.Parser;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+
+ at ManagedObject("SPDY Server Connection Factory")
+public class SPDYServerConnectionFactory extends AbstractConnectionFactory
+{
+    /**
+     * @deprecated use {@link #checkProtocolNegotiationAvailable()} instead.
+     */
+    @Deprecated
+    public static void checkNPNAvailable()
+    {
+        checkProtocolNegotiationAvailable();
+    }
+
+    public static void checkProtocolNegotiationAvailable()
+    {
+        if (!isAvailableInBootClassPath("org.eclipse.jetty.alpn.ALPN") &&
+                !isAvailableInBootClassPath("org.eclipse.jetty.npn.NextProtoNego"))
+            throw new IllegalStateException("No ALPN nor NPN classes available");
+    }
+
+    private static boolean isAvailableInBootClassPath(String className)
+    {
+        try
+        {
+            Class<?> klass = ClassLoader.getSystemClassLoader().loadClass(className);
+            if (klass.getClassLoader() != null)
+                throw new IllegalStateException(className + " must be on JVM boot classpath");
+            return true;
+        }
+        catch (ClassNotFoundException x)
+        {
+            return false;
+        }
+    }
+
+    private final Queue<Session> sessions = new ConcurrentLinkedQueue<>();
+    private final short version;
+    private final ServerSessionFrameListener listener;
+    private int initialWindowSize;
+    private boolean dispatchIO;
+
+    public SPDYServerConnectionFactory(int version)
+    {
+        this(version, null);
+    }
+
+    public SPDYServerConnectionFactory(int version, ServerSessionFrameListener listener)
+    {
+        super("spdy/" + version);
+        this.version = (short)version;
+        this.listener = listener;
+        setInitialWindowSize(65536);
+        setDispatchIO(true);
+    }
+
+    @ManagedAttribute("SPDY version")
+    public short getVersion()
+    {
+        return version;
+    }
+
+    public ServerSessionFrameListener getServerSessionFrameListener()
+    {
+        return listener;
+    }
+
+    @Override
+    public Connection newConnection(Connector connector, EndPoint endPoint)
+    {
+        CompressionFactory compressionFactory = new StandardCompressionFactory();
+        Parser parser = new Parser(compressionFactory.newDecompressor());
+        Generator generator = new Generator(connector.getByteBufferPool(), compressionFactory.newCompressor());
+
+        ServerSessionFrameListener listener = provideServerSessionFrameListener(connector, endPoint);
+        SPDYConnection connection = new ServerSPDYConnection(connector, endPoint, parser, listener,
+                isDispatchIO(), getInputBufferSize());
+
+        FlowControlStrategy flowControlStrategy = newFlowControlStrategy(version);
+
+        StandardSession session = new StandardSession(getVersion(), connector.getByteBufferPool(),
+                connector.getScheduler(), connection, endPoint, connection, 2, listener,
+                generator, flowControlStrategy);
+        session.setWindowSize(getInitialWindowSize());
+        parser.addListener(session);
+        connection.setSession(session);
+
+        sessionOpened(session);
+
+        return configure(connection, connector, endPoint);
+    }
+
+    protected FlowControlStrategy newFlowControlStrategy(short version)
+    {
+        return FlowControlStrategyFactory.newFlowControlStrategy(version);
+    }
+
+    protected ServerSessionFrameListener provideServerSessionFrameListener(Connector connector, EndPoint endPoint)
+    {
+        return listener;
+    }
+
+    @ManagedAttribute("Initial Window Size")
+    public int getInitialWindowSize()
+    {
+        return initialWindowSize;
+    }
+
+    public void setInitialWindowSize(int initialWindowSize)
+    {
+        this.initialWindowSize = initialWindowSize;
+    }
+
+    @ManagedAttribute("Dispatch I/O to a pooled thread")
+    public boolean isDispatchIO()
+    {
+        return dispatchIO;
+    }
+
+    public void setDispatchIO(boolean dispatchIO)
+    {
+        this.dispatchIO = dispatchIO;
+    }
+
+    protected boolean sessionOpened(Session session)
+    {
+        // Add sessions only if the connector is not stopping
+        return sessions.offer(session);
+    }
+
+    protected boolean sessionClosed(Session session)
+    {
+        // Remove sessions only if the connector is not stopping
+        // to avoid concurrent removes during iterations
+        return sessions.remove(session);
+    }
+
+    void closeSessions()
+    {
+        for (Session session : sessions)
+            session.goAway(new GoAwayInfo(), Callback.Adapter.INSTANCE);
+        sessions.clear();
+    }
+
+    @Override
+    protected void doStop() throws Exception
+    {
+        closeSessions();
+        super.doStop();
+    }
+
+    public Collection<Session> getSessions()
+    {
+        return Collections.unmodifiableCollection(sessions);
+    }
+
+    @Override
+    protected void dumpThis(Appendable out) throws IOException
+    {
+        super.dumpThis(out);
+        dump(out, "", sessions);
+    }
+
+    private class ServerSPDYConnection extends SPDYConnection implements Runnable
+    {
+        private final ServerSessionFrameListener listener;
+        private final AtomicBoolean connected = new AtomicBoolean();
+
+        private ServerSPDYConnection(Connector connector, EndPoint endPoint, Parser parser,
+                                     ServerSessionFrameListener listener, boolean dispatchIO, int bufferSize)
+        {
+            super(endPoint, connector.getByteBufferPool(), parser, connector.getExecutor(),
+                    dispatchIO, bufferSize);
+            this.listener = listener;
+        }
+
+        @Override
+        public void onOpen()
+        {
+            super.onOpen();
+            if (connected.compareAndSet(false, true))
+                getExecutor().execute(this);
+        }
+
+        @Override
+        public void onClose()
+        {
+            super.onClose();
+            sessionClosed(getSession());
+        }
+
+        @Override
+        public void run()
+        {
+            // NPE guard to support tests
+            if (listener != null)
+                listener.onConnect(getSession());
+        }
+    }
+
+}
diff --git a/jetty-spdy/spdy-server/src/main/java/org/eclipse/jetty/spdy/server/SPDYServerConnector.java b/jetty-spdy/spdy-server/src/main/java/org/eclipse/jetty/spdy/server/SPDYServerConnector.java
new file mode 100644
index 0000000..5ec6f32
--- /dev/null
+++ b/jetty-spdy/spdy-server/src/main/java/org/eclipse/jetty/spdy/server/SPDYServerConnector.java
@@ -0,0 +1,52 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.spdy.server;
+
+import java.util.Objects;
+
+import org.eclipse.jetty.server.HttpConnectionFactory;
+import org.eclipse.jetty.server.NegotiatingServerConnectionFactory;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.spdy.api.SPDY;
+import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+
+public class SPDYServerConnector extends ServerConnector
+{
+    public SPDYServerConnector(Server server, ServerSessionFrameListener listener)
+    {
+        super(server, (SslContextFactory)null, new SPDYServerConnectionFactory(SPDY.V2, listener));
+    }
+
+    public SPDYServerConnector(Server server, SslContextFactory sslContextFactory, ServerSessionFrameListener listener)
+    {
+        this(server, sslContextFactory, listener, new NPNServerConnectionFactory("spdy/3", "spdy/2", "http/1.1"));
+    }
+
+    public SPDYServerConnector(Server server, SslContextFactory sslContextFactory, ServerSessionFrameListener listener, NegotiatingServerConnectionFactory negotiator)
+    {
+        super(server, Objects.requireNonNull(sslContextFactory),
+                negotiator,
+                new SPDYServerConnectionFactory(SPDY.V3, listener),
+                new SPDYServerConnectionFactory(SPDY.V2, listener),
+                new HttpConnectionFactory());
+        negotiator.setDefaultProtocol("http/1.1");
+    }
+}
diff --git a/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/AbstractTest.java b/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/AbstractTest.java
new file mode 100644
index 0000000..56e87d7
--- /dev/null
+++ b/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/AbstractTest.java
@@ -0,0 +1,150 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.spdy.server;
+
+import java.net.InetSocketAddress;
+import java.util.concurrent.Executor;
+
+import org.eclipse.jetty.server.ConnectionFactory;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.spdy.api.SPDY;
+import org.eclipse.jetty.spdy.api.Session;
+import org.eclipse.jetty.spdy.api.SessionFrameListener;
+import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener;
+import org.eclipse.jetty.spdy.client.SPDYClient;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.junit.After;
+import org.junit.Rule;
+import org.junit.rules.TestWatcher;
+import org.junit.runner.Description;
+
+public abstract class AbstractTest
+{
+    @Rule
+    public final TestWatcher testName = new TestWatcher()
+    {
+
+        @Override
+        public void starting(Description description)
+        {
+            super.starting(description);
+            System.err.printf("Running %s.%s()%n",
+                    description.getClassName(),
+                    description.getMethodName());
+        }
+    };
+
+    protected final short version = SPDY.V2;
+
+    protected Server server;
+    protected SPDYClient.Factory clientFactory;
+    protected ServerConnector connector;
+
+    protected InetSocketAddress startServer(ServerSessionFrameListener listener) throws Exception
+    {
+        return startServer(version, listener);
+    }
+
+    protected InetSocketAddress startServer(short version, ServerSessionFrameListener listener) throws Exception
+    {
+        if (server == null)
+            server = newServer();
+        if (connector == null)
+            connector = newSPDYServerConnector(server, listener);
+        if (listener == null)
+            listener = connector.getConnectionFactory(SPDYServerConnectionFactory.class).getServerSessionFrameListener();
+
+        ConnectionFactory spdy = new SPDYServerConnectionFactory(version, listener);
+        connector.addConnectionFactory(spdy);
+        connector.setPort(0);
+        server.addConnector(connector);
+
+        if (connector.getConnectionFactory(NPNServerConnectionFactory.class)!=null)
+            connector.getConnectionFactory(NPNServerConnectionFactory.class).setDefaultProtocol(spdy.getProtocol());
+        else
+            connector.setDefaultProtocol(spdy.getProtocol());
+
+        server.start();
+        return new InetSocketAddress("localhost", connector.getLocalPort());
+    }
+
+    protected Server newServer()
+    {
+        QueuedThreadPool pool = new QueuedThreadPool();
+        pool.setName(pool.getName()+"-server");
+        return new Server(pool);
+    }
+
+    protected ServerConnector newSPDYServerConnector(Server server, ServerSessionFrameListener listener)
+    {
+        return new SPDYServerConnector(server, listener);
+    }
+
+    protected Session startClient(InetSocketAddress socketAddress, SessionFrameListener listener) throws Exception
+    {
+        return startClient(version, socketAddress, listener);
+    }
+
+    protected Session startClient(short version, InetSocketAddress socketAddress, SessionFrameListener listener) throws Exception
+    {
+        if (clientFactory == null)
+        {
+            QueuedThreadPool threadPool = new QueuedThreadPool();
+            threadPool.setName(threadPool.getName() + "-client");
+            clientFactory = newSPDYClientFactory(threadPool);
+        }
+        clientFactory.start();
+
+        return clientFactory.newSPDYClient(version).connect(socketAddress, listener);
+    }
+
+    protected SPDYClient.Factory newSPDYClientFactory(Executor threadPool)
+    {
+        return new SPDYClient.Factory(threadPool);
+    }
+
+    protected SslContextFactory newSslContextFactory()
+    {
+        SslContextFactory sslContextFactory = new SslContextFactory();
+        sslContextFactory.setKeyStorePath("src/test/resources/keystore.jks");
+        sslContextFactory.setKeyStorePassword("storepwd");
+        sslContextFactory.setTrustStorePath("src/test/resources/truststore.jks");
+        sslContextFactory.setTrustStorePassword("storepwd");
+        sslContextFactory.setProtocol("TLSv1");
+        sslContextFactory.setIncludeProtocols("TLSv1");
+        return sslContextFactory;
+    }
+
+    @After
+    public void destroy() throws Exception
+    {
+        if (clientFactory != null)
+        {
+            clientFactory.stop();
+        }
+        if (server != null)
+        {
+            server.stop();
+            server.join();
+        }
+    }
+}
diff --git a/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/ClosedStreamTest.java b/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/ClosedStreamTest.java
new file mode 100644
index 0000000..f099414
--- /dev/null
+++ b/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/ClosedStreamTest.java
@@ -0,0 +1,273 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.spdy.server;
+
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+import static org.junit.Assert.assertThat;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.channels.ServerSocketChannel;
+import java.nio.channels.SocketChannel;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.io.MappedByteBufferPool;
+import org.eclipse.jetty.spdy.StandardCompressionFactory;
+import org.eclipse.jetty.spdy.api.BytesDataInfo;
+import org.eclipse.jetty.spdy.api.DataInfo;
+import org.eclipse.jetty.spdy.api.GoAwayInfo;
+import org.eclipse.jetty.spdy.api.GoAwayResultInfo;
+import org.eclipse.jetty.spdy.api.ReplyInfo;
+import org.eclipse.jetty.spdy.api.SPDY;
+import org.eclipse.jetty.spdy.api.Session;
+import org.eclipse.jetty.spdy.api.SessionStatus;
+import org.eclipse.jetty.spdy.api.Stream;
+import org.eclipse.jetty.spdy.api.StreamFrameListener;
+import org.eclipse.jetty.spdy.api.StringDataInfo;
+import org.eclipse.jetty.spdy.api.SynInfo;
+import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener;
+import org.eclipse.jetty.spdy.frames.ControlFrame;
+import org.eclipse.jetty.spdy.frames.GoAwayFrame;
+import org.eclipse.jetty.spdy.frames.RstStreamFrame;
+import org.eclipse.jetty.spdy.frames.SynReplyFrame;
+import org.eclipse.jetty.spdy.frames.SynStreamFrame;
+import org.eclipse.jetty.spdy.generator.Generator;
+import org.eclipse.jetty.spdy.parser.Parser;
+import org.eclipse.jetty.spdy.parser.Parser.Listener;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.Fields;
+import org.junit.Assert;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class ClosedStreamTest extends AbstractTest
+{
+    //TODO: Right now it sends a rst as the stream is unknown to the session once it's closed.
+    //TODO: But according to the spec we probably should just ignore the data?!
+    @Test
+    public void testDataSentOnClosedStreamIsIgnored() throws Exception
+    {
+        ServerSocketChannel server = ServerSocketChannel.open();
+        server.bind(new InetSocketAddress("localhost", 0));
+
+        Session session = startClient(new InetSocketAddress("localhost", server.socket().getLocalPort()), null);
+        final CountDownLatch dataLatch = new CountDownLatch(2);
+        session.syn(new SynInfo(new Fields(), true), new StreamFrameListener.Adapter()
+        {
+            @Override
+            public void onData(Stream stream, DataInfo dataInfo)
+            {
+                dataLatch.countDown();
+            }
+        });
+
+        SocketChannel channel = server.accept();
+        ByteBuffer readBuffer = ByteBuffer.allocate(1024);
+        channel.read(readBuffer);
+        readBuffer.flip();
+        int streamId = readBuffer.getInt(8);
+
+        Generator generator = new Generator(new MappedByteBufferPool(), new StandardCompressionFactory.StandardCompressor());
+
+        ByteBuffer writeBuffer = generator.control(new SynReplyFrame(SPDY.V2, (byte)0, streamId, new Fields()));
+        channel.write(writeBuffer);
+        Assert.assertThat(writeBuffer.hasRemaining(), is(false));
+
+        byte[] bytes = new byte[1];
+        writeBuffer = generator.data(streamId, bytes.length, new BytesDataInfo(bytes, true));
+        channel.write(writeBuffer);
+        Assert.assertThat(writeBuffer.hasRemaining(), is(false));
+
+        // Write again to simulate the faulty condition
+        writeBuffer.flip();
+        channel.write(writeBuffer);
+        Assert.assertThat(writeBuffer.hasRemaining(), is(false));
+
+        Assert.assertFalse(dataLatch.await(1, TimeUnit.SECONDS));
+
+        session.goAway(new GoAwayInfo(5, TimeUnit.SECONDS));
+
+        server.close();
+    }
+
+    @Test
+    public void testSendDataOnHalfClosedStreamCausesExceptionOnServer() throws Exception
+    {
+        final CountDownLatch replyReceivedLatch = new CountDownLatch(1);
+        final CountDownLatch clientReceivedDataLatch = new CountDownLatch(1);
+        final CountDownLatch exceptionWhenSendingData = new CountDownLatch(1);
+
+        Session clientSession = startClient(startServer(new ServerSessionFrameListener.Adapter()
+        {
+            @Override
+            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
+            {
+                stream.reply(new ReplyInfo(true), new Callback.Adapter());
+                try
+                {
+                    replyReceivedLatch.await(5,TimeUnit.SECONDS);
+                }
+                catch (InterruptedException e)
+                {
+                    e.printStackTrace();
+                }
+                try
+                {
+                    stream.data(new StringDataInfo("data send after half closed",false), new Callback.Adapter());
+                }
+                catch (RuntimeException e)
+                {
+                    // we expect an exception here, but we don't want it to be logged
+                    exceptionWhenSendingData.countDown();
+                }
+
+                return null;
+            }
+        }),null);
+
+        Stream stream = clientSession.syn(new SynInfo(new Fields(), false),new StreamFrameListener.Adapter()
+        {
+            @Override
+            public void onReply(Stream stream, ReplyInfo replyInfo)
+            {
+                replyReceivedLatch.countDown();
+            }
+
+            @Override
+            public void onData(Stream stream, DataInfo dataInfo)
+            {
+                clientReceivedDataLatch.countDown();
+            }
+        });
+        assertThat("reply has been received by client",replyReceivedLatch.await(5,TimeUnit.SECONDS),is(true));
+        assertThat("stream is half closed from server",stream.isHalfClosed(),is(true));
+        assertThat("client has not received any data sent after stream was half closed by server",
+                clientReceivedDataLatch.await(1,TimeUnit.SECONDS), is(false));
+        assertThat("sending data threw an exception",exceptionWhenSendingData.await(5,TimeUnit.SECONDS), is(true));
+    }
+
+    @Test
+    public void testV2ReceiveDataOnHalfClosedStream() throws Exception
+    {
+        runReceiveDataOnHalfClosedStream(SPDY.V2);
+    }
+
+    @Test
+    @Ignore("until v3 is properly implemented")
+    public void testV3ReceiveDataOnHalfClosedStream() throws Exception
+    {
+        runReceiveDataOnHalfClosedStream(SPDY.V3);
+    }
+
+    private void runReceiveDataOnHalfClosedStream(short version) throws Exception
+    {
+        final CountDownLatch clientResetReceivedLatch = new CountDownLatch(1);
+        final CountDownLatch serverReplySentLatch = new CountDownLatch(1);
+        final CountDownLatch clientReplyReceivedLatch = new CountDownLatch(1);
+        final CountDownLatch serverDataReceivedLatch = new CountDownLatch(1);
+        final CountDownLatch goAwayReceivedLatch = new CountDownLatch(1);
+
+        InetSocketAddress startServer = startServer(new ServerSessionFrameListener.Adapter()
+        {
+            @Override
+            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
+            {
+                stream.reply(new ReplyInfo(false), new Callback.Adapter());
+                serverReplySentLatch.countDown();
+                try
+                {
+                    clientReplyReceivedLatch.await(5,TimeUnit.SECONDS);
+                }
+                catch (InterruptedException e)
+                {
+                    e.printStackTrace();
+                }
+                return new StreamFrameListener.Adapter()
+                {
+                    @Override
+                    public void onData(Stream stream, DataInfo dataInfo)
+                    {
+                        serverDataReceivedLatch.countDown();
+                    }
+                };
+            }
+            @Override
+            public void onGoAway(Session session, GoAwayResultInfo goAwayInfo)
+            {
+                goAwayReceivedLatch.countDown();
+            }
+        });
+
+        final Generator generator = new Generator(new MappedByteBufferPool(), new StandardCompressionFactory().newCompressor());
+        int streamId = 1;
+        ByteBuffer synData = generator.control(new SynStreamFrame(version,SynInfo.FLAG_CLOSE, streamId,0,(byte)0,(short)0,new Fields()));
+
+        final SocketChannel socketChannel = SocketChannel.open(startServer);
+        socketChannel.write(synData);
+        assertThat("synData is fully written", synData.hasRemaining(), is(false));
+
+        assertThat("server: push reply is sent",serverReplySentLatch.await(5,TimeUnit.SECONDS),is(true));
+
+        Parser parser = new Parser(new StandardCompressionFactory.StandardDecompressor());
+        parser.addListener(new Listener.Adapter()
+        {
+            @Override
+            public void onControlFrame(ControlFrame frame)
+            {
+                if (frame instanceof SynReplyFrame)
+                {
+                    SynReplyFrame synReplyFrame = (SynReplyFrame)frame;
+                    clientReplyReceivedLatch.countDown();
+                    int streamId = synReplyFrame.getStreamId();
+                    ByteBuffer data = generator.data(streamId,0,new StringDataInfo("data",false));
+                    try
+                    {
+                        socketChannel.write(data);
+                    }
+                    catch (IOException e)
+                    {
+                        e.printStackTrace();
+                    }
+                }
+                else if (frame instanceof RstStreamFrame)
+                {
+                    clientResetReceivedLatch.countDown();
+                }
+            }
+        });
+        ByteBuffer response = ByteBuffer.allocate(28);
+        socketChannel.read(response);
+        response.flip();
+        parser.parse(response);
+
+        assertThat("server didn't receive data",serverDataReceivedLatch.await(1,TimeUnit.SECONDS),not(true));
+        assertThat("client didn't receive reset",clientResetReceivedLatch.await(1,TimeUnit.SECONDS),not(true));
+
+        ByteBuffer buffer = generator.control(new GoAwayFrame(version, streamId, SessionStatus.OK.getCode()));
+        socketChannel.write(buffer);
+        Assert.assertThat(buffer.hasRemaining(), is(false));
+
+        assertThat("GoAway frame is received by server", goAwayReceivedLatch.await(5,TimeUnit.SECONDS), is(true));
+
+        socketChannel.close();
+    }
+}
diff --git a/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/FlowControlTest.java b/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/FlowControlTest.java
new file mode 100644
index 0000000..6dfc9e8
--- /dev/null
+++ b/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/FlowControlTest.java
@@ -0,0 +1,493 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.spdy.server;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
+import java.nio.ByteBuffer;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Exchanger;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.jetty.spdy.api.ByteBufferDataInfo;
+import org.eclipse.jetty.spdy.api.BytesDataInfo;
+import org.eclipse.jetty.spdy.api.DataInfo;
+import org.eclipse.jetty.spdy.api.ReplyInfo;
+import org.eclipse.jetty.spdy.api.SPDY;
+import org.eclipse.jetty.spdy.api.SPDYException;
+import org.eclipse.jetty.spdy.api.Session;
+import org.eclipse.jetty.spdy.api.SessionFrameListener;
+import org.eclipse.jetty.spdy.api.Settings;
+import org.eclipse.jetty.spdy.api.SettingsInfo;
+import org.eclipse.jetty.spdy.api.Stream;
+import org.eclipse.jetty.spdy.api.StreamFrameListener;
+import org.eclipse.jetty.spdy.api.SynInfo;
+import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.Fields;
+import org.eclipse.jetty.util.FutureCallback;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class FlowControlTest extends AbstractTest
+{
+    @Test
+    public void testFlowControlWithConcurrentSettings() throws Exception
+    {
+        // Initial window is 64 KiB. We allow the client to send 1024 B
+        // then we change the window to 512 B. At this point, the client
+        // must stop sending data (although the initial window allows it)
+
+        final int size = 512;
+        final AtomicReference<DataInfo> dataInfoRef = new AtomicReference<>();
+        final CountDownLatch dataLatch = new CountDownLatch(2);
+        final CountDownLatch settingsLatch = new CountDownLatch(1);
+        Session session = startClient(SPDY.V3, startServer(SPDY.V3, new ServerSessionFrameListener.Adapter()
+        {
+            @Override
+            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
+            {
+                stream.reply(new ReplyInfo(true), new Callback.Adapter());
+                return new StreamFrameListener.Adapter()
+                {
+                    private final AtomicInteger dataFrames = new AtomicInteger();
+
+                    @Override
+                    public void onData(Stream stream, DataInfo dataInfo)
+                    {
+                        int dataFrameCount = dataFrames.incrementAndGet();
+                        if (dataFrameCount == 1)
+                        {
+                            dataInfoRef.set(dataInfo);
+                            Settings settings = new Settings();
+                            settings.put(new Settings.Setting(Settings.ID.INITIAL_WINDOW_SIZE, size));
+                            stream.getSession().settings(new SettingsInfo(settings), new FutureCallback());
+                        }
+                        else if (dataFrameCount > 1)
+                        {
+                            dataInfo.consume(dataInfo.length());
+                            dataLatch.countDown();
+                        }
+                    }
+                };
+            }
+        }), new SessionFrameListener.Adapter()
+        {
+            @Override
+            public void onSettings(Session session, SettingsInfo settingsInfo)
+            {
+                settingsLatch.countDown();
+            }
+        });
+
+        Stream stream = session.syn(new SynInfo(5, TimeUnit.SECONDS, new Fields(), false, (byte)0), null);
+        stream.data(new BytesDataInfo(new byte[size * 2], false));
+        settingsLatch.await(5, TimeUnit.SECONDS);
+
+        // Send the second chunk of data, must not arrive since we're flow control stalled now
+        stream.data(new BytesDataInfo(new byte[size * 2], true), new Callback.Adapter());
+        Assert.assertFalse(dataLatch.await(1, TimeUnit.SECONDS));
+
+        // Consume the data arrived to server, this will resume flow control
+        DataInfo dataInfo = dataInfoRef.get();
+        dataInfo.consume(dataInfo.length());
+
+        Assert.assertTrue(dataLatch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testServerFlowControlOneBigWrite() throws Exception
+    {
+        final int windowSize = 1536;
+        final int length = 5 * windowSize;
+        final CountDownLatch settingsLatch = new CountDownLatch(1);
+        Session session = startClient(SPDY.V3, startServer(SPDY.V3, new ServerSessionFrameListener.Adapter()
+        {
+            @Override
+            public void onSettings(Session session, SettingsInfo settingsInfo)
+            {
+                settingsLatch.countDown();
+            }
+
+            @Override
+            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
+            {
+                stream.reply(new ReplyInfo(false), new Callback.Adapter());
+                stream.data(new BytesDataInfo(new byte[length], true), new Callback.Adapter());
+                return null;
+            }
+        }), null);
+
+        Settings settings = new Settings();
+        settings.put(new Settings.Setting(Settings.ID.INITIAL_WINDOW_SIZE, windowSize));
+        session.settings(new SettingsInfo(settings));
+
+        Assert.assertTrue(settingsLatch.await(5, TimeUnit.SECONDS));
+
+        final Exchanger<DataInfo> exchanger = new Exchanger<>();
+        session.syn(new SynInfo(new Fields(), true), new StreamFrameListener.Adapter()
+        {
+            private AtomicInteger dataFrames = new AtomicInteger();
+
+            @Override
+            public void onData(Stream stream, DataInfo dataInfo)
+            {
+                try
+                {
+                    int dataFrames = this.dataFrames.incrementAndGet();
+                    if (dataFrames == 1)
+                    {
+                        // Do not consume nor read from the data frame.
+                        // We should then be flow-control stalled
+                        exchanger.exchange(dataInfo);
+                    }
+                    else if (dataFrames == 2)
+                    {
+                        // Read but not consume, we should be flow-control stalled
+                        dataInfo.asByteBuffer(false);
+                        exchanger.exchange(dataInfo);
+                    }
+                    else if (dataFrames == 3)
+                    {
+                        // Consume partially, we should be flow-control stalled
+                        dataInfo.consumeInto(ByteBuffer.allocate(dataInfo.length() / 2));
+                        exchanger.exchange(dataInfo);
+                    }
+                    else if (dataFrames == 4 || dataFrames == 5)
+                    {
+                        // Consume totally
+                        dataInfo.asByteBuffer(true);
+                        exchanger.exchange(dataInfo);
+                    }
+                    else
+                    {
+                        Assert.fail();
+                    }
+                }
+                catch (InterruptedException x)
+                {
+                    throw new SPDYException(x);
+                }
+            }
+        });
+
+        DataInfo dataInfo = exchanger.exchange(null, 5, TimeUnit.SECONDS);
+        checkThatWeAreFlowControlStalled(exchanger);
+
+        Assert.assertEquals(windowSize, dataInfo.available());
+        Assert.assertEquals(0, dataInfo.consumed());
+        dataInfo.asByteBuffer(true);
+
+        dataInfo = exchanger.exchange(null, 5, TimeUnit.SECONDS);
+        checkThatWeAreFlowControlStalled(exchanger);
+
+        Assert.assertEquals(0, dataInfo.available());
+        Assert.assertEquals(0, dataInfo.consumed());
+        dataInfo.consume(dataInfo.length());
+
+        dataInfo = exchanger.exchange(null, 5, TimeUnit.SECONDS);
+        checkThatWeAreFlowControlStalled(exchanger);
+
+        Assert.assertEquals(dataInfo.length() / 2, dataInfo.consumed());
+        dataInfo.asByteBuffer(true);
+
+        dataInfo = exchanger.exchange(null, 5, TimeUnit.SECONDS);
+        Assert.assertEquals(dataInfo.length(), dataInfo.consumed());
+        // Check that we are not flow control stalled
+        dataInfo = exchanger.exchange(null, 5, TimeUnit.SECONDS);
+        Assert.assertEquals(dataInfo.length(), dataInfo.consumed());
+    }
+
+    @Test
+    public void testClientFlowControlOneBigWrite() throws Exception
+    {
+        final int windowSize = 1536;
+        final Exchanger<DataInfo> exchanger = new Exchanger<>();
+        final CountDownLatch settingsLatch = new CountDownLatch(1);
+        Session session = startClient(SPDY.V3, startServer(SPDY.V3, new ServerSessionFrameListener.Adapter()
+        {
+            @Override
+            public void onConnect(Session session)
+            {
+                Settings settings = new Settings();
+                settings.put(new Settings.Setting(Settings.ID.INITIAL_WINDOW_SIZE, windowSize));
+                session.settings(new SettingsInfo(settings), new FutureCallback());
+            }
+
+            @Override
+            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
+            {
+                stream.reply(new ReplyInfo(false), new Callback.Adapter());
+                return new StreamFrameListener.Adapter()
+                {
+                    private AtomicInteger dataFrames = new AtomicInteger();
+
+                    @Override
+                    public void onData(Stream stream, DataInfo dataInfo)
+                    {
+                        try
+                        {
+                            int dataFrames = this.dataFrames.incrementAndGet();
+                            if (dataFrames == 1)
+                            {
+                                // Do not consume nor read from the data frame.
+                                // We should then be flow-control stalled
+                                exchanger.exchange(dataInfo);
+                            }
+                            else if (dataFrames == 2)
+                            {
+                                // Read but not consume, we should be flow-control stalled
+                                dataInfo.asByteBuffer(false);
+                                exchanger.exchange(dataInfo);
+                            }
+                            else if (dataFrames == 3)
+                            {
+                                // Consume partially, we should be flow-control stalled
+                                dataInfo.consumeInto(ByteBuffer.allocate(dataInfo.length() / 2));
+                                exchanger.exchange(dataInfo);
+                            }
+                            else if (dataFrames == 4 || dataFrames == 5)
+                            {
+                                // Consume totally
+                                dataInfo.asByteBuffer(true);
+                                exchanger.exchange(dataInfo);
+                            }
+                            else
+                            {
+                                Assert.fail();
+                            }
+                        }
+                        catch (InterruptedException x)
+                        {
+                            throw new SPDYException(x);
+                        }
+                    }
+                };
+            }
+        }), new SessionFrameListener.Adapter()
+        {
+            @Override
+            public void onSettings(Session session, SettingsInfo settingsInfo)
+            {
+                settingsLatch.countDown();
+            }
+        });
+
+        Assert.assertTrue(settingsLatch.await(5, TimeUnit.SECONDS));
+
+        Stream stream = session.syn(new SynInfo(5, TimeUnit.SECONDS, new Fields(), false, (byte)0), null);
+        final int length = 5 * windowSize;
+        stream.data(new BytesDataInfo(new byte[length], true), new Callback.Adapter());
+
+        DataInfo dataInfo = exchanger.exchange(null, 5, TimeUnit.SECONDS);
+        checkThatWeAreFlowControlStalled(exchanger);
+
+        Assert.assertEquals(windowSize, dataInfo.available());
+        Assert.assertEquals(0, dataInfo.consumed());
+        dataInfo.asByteBuffer(true);
+
+        dataInfo = exchanger.exchange(null, 5, TimeUnit.SECONDS);
+        checkThatWeAreFlowControlStalled(exchanger);
+
+        Assert.assertEquals(0, dataInfo.available());
+        Assert.assertEquals(0, dataInfo.consumed());
+        dataInfo.consume(dataInfo.length());
+
+        dataInfo = exchanger.exchange(null, 5, TimeUnit.SECONDS);
+        checkThatWeAreFlowControlStalled(exchanger);
+
+        Assert.assertEquals(dataInfo.length() / 2, dataInfo.consumed());
+        dataInfo.asByteBuffer(true);
+
+        dataInfo = exchanger.exchange(null, 5, TimeUnit.SECONDS);
+        Assert.assertEquals(dataInfo.length(), dataInfo.consumed());
+        // Check that we are not flow control stalled
+        dataInfo = exchanger.exchange(null, 5, TimeUnit.SECONDS);
+        Assert.assertEquals(dataInfo.length(), dataInfo.consumed());
+    }
+
+    @Test
+    public void testStreamsStalledDoesNotStallOtherStreams() throws Exception
+    {
+        final int windowSize = 1024;
+        final CountDownLatch settingsLatch = new CountDownLatch(1);
+        Session session = startClient(SPDY.V3, startServer(SPDY.V3, new ServerSessionFrameListener.Adapter()
+        {
+            @Override
+            public void onSettings(Session session, SettingsInfo settingsInfo)
+            {
+                settingsLatch.countDown();
+            }
+
+            @Override
+            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
+            {
+                stream.reply(new ReplyInfo(false), new Callback.Adapter());
+                stream.data(new BytesDataInfo(new byte[windowSize * 2], true), new Callback.Adapter());
+                return null;
+            }
+        }), null);
+        Settings settings = new Settings();
+        settings.put(new Settings.Setting(Settings.ID.INITIAL_WINDOW_SIZE, windowSize));
+        session.settings(new SettingsInfo(settings));
+
+        Assert.assertTrue(settingsLatch.await(5, TimeUnit.SECONDS));
+
+        final CountDownLatch latch = new CountDownLatch(3);
+        final AtomicReference<DataInfo> dataInfoRef1 = new AtomicReference<>();
+        final AtomicReference<DataInfo> dataInfoRef2 = new AtomicReference<>();
+        session.syn(new SynInfo(5, TimeUnit.SECONDS, new Fields(), true, (byte)0), new StreamFrameListener.Adapter()
+        {
+            private final AtomicInteger dataFrames = new AtomicInteger();
+
+            @Override
+            public void onData(Stream stream, DataInfo dataInfo)
+            {
+                int frames = dataFrames.incrementAndGet();
+                if (frames == 1)
+                {
+                    // Do not consume it to stall flow control
+                    dataInfoRef1.set(dataInfo);
+                }
+                else
+                {
+                    dataInfo.consume(dataInfo.length());
+                    if (dataInfo.isClose())
+                        latch.countDown();
+                }
+            }
+        });
+        session.syn(new SynInfo(5, TimeUnit.SECONDS, new Fields(), true, (byte)0), new StreamFrameListener.Adapter()
+        {
+            private final AtomicInteger dataFrames = new AtomicInteger();
+
+            @Override
+            public void onData(Stream stream, DataInfo dataInfo)
+            {
+                int frames = dataFrames.incrementAndGet();
+                if (frames == 1)
+                {
+                    // Do not consume it to stall flow control
+                    dataInfoRef2.set(dataInfo);
+                }
+                else
+                {
+                    dataInfo.consume(dataInfo.length());
+                    if (dataInfo.isClose())
+                        latch.countDown();
+                }
+            }
+        });
+        session.syn(new SynInfo(5, TimeUnit.SECONDS, new Fields(), true, (byte)0), new StreamFrameListener.Adapter()
+        {
+            @Override
+            public void onData(Stream stream, DataInfo dataInfo)
+            {
+                DataInfo dataInfo1 = dataInfoRef1.getAndSet(null);
+                if (dataInfo1 != null)
+                    dataInfo1.consume(dataInfo1.length());
+                DataInfo dataInfo2 = dataInfoRef2.getAndSet(null);
+                if (dataInfo2 != null)
+                    dataInfo2.consume(dataInfo2.length());
+                dataInfo.consume(dataInfo.length());
+                if (dataInfo.isClose())
+                    latch.countDown();
+            }
+        });
+
+        Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testSendBigFileWithoutFlowControl() throws Exception
+    {
+        testSendBigFile(SPDY.V2);
+    }
+
+    @Test
+    public void testSendBigFileWithFlowControl() throws Exception
+    {
+        testSendBigFile(SPDY.V3);
+    }
+
+    private void testSendBigFile(short version) throws Exception
+    {
+        final int dataSize = 1024 * 1024;
+        final ByteBufferDataInfo bigByteBufferDataInfo = new ByteBufferDataInfo(ByteBuffer.allocate(dataSize),false);
+        final CountDownLatch allDataReceivedLatch = new CountDownLatch(1);
+
+        Session session = startClient(version, startServer(version, new ServerSessionFrameListener.Adapter()
+        {
+            @Override
+            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
+            {
+                stream.reply(new ReplyInfo(false), new Callback.Adapter());
+                stream.data(bigByteBufferDataInfo, new Callback.Adapter());
+                return null;
+            }
+        }),new SessionFrameListener.Adapter());
+
+        session.syn(new SynInfo(new Fields(), false),new StreamFrameListener.Adapter()
+        {
+            private int dataBytesReceived;
+
+            @Override
+            public void onData(Stream stream, DataInfo dataInfo)
+            {
+                dataBytesReceived = dataBytesReceived + dataInfo.length();
+                dataInfo.consume(dataInfo.length());
+                if (dataBytesReceived == dataSize)
+                    allDataReceivedLatch.countDown();
+            }
+        });
+
+        assertThat("all data bytes have been received by the client", allDataReceivedLatch.await(5, TimeUnit.SECONDS), is(true));
+    }
+
+    private void checkThatWeAreFlowControlStalled(final Exchanger<DataInfo> exchanger)
+    {
+        expectException(TimeoutException.class, new Callable<DataInfo>()
+        {
+            @Override
+            public DataInfo call() throws Exception
+            {
+                return exchanger.exchange(null, 1, TimeUnit.SECONDS);
+            }
+        });
+    }
+
+    private void expectException(Class<? extends Exception> exception, Callable<DataInfo> command)
+    {
+        try
+        {
+            command.call();
+            Assert.fail();
+        }
+        catch (Exception x)
+        {
+            Assert.assertSame(exception, x.getClass());
+        }
+    }
+}
diff --git a/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/GoAwayTest.java b/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/GoAwayTest.java
new file mode 100644
index 0000000..9bf8aea
--- /dev/null
+++ b/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/GoAwayTest.java
@@ -0,0 +1,234 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.spdy.server;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.jetty.spdy.api.DataInfo;
+import org.eclipse.jetty.spdy.api.GoAwayInfo;
+import org.eclipse.jetty.spdy.api.GoAwayResultInfo;
+import org.eclipse.jetty.spdy.api.ReplyInfo;
+import org.eclipse.jetty.spdy.api.Session;
+import org.eclipse.jetty.spdy.api.SessionFrameListener;
+import org.eclipse.jetty.spdy.api.SessionStatus;
+import org.eclipse.jetty.spdy.api.Stream;
+import org.eclipse.jetty.spdy.api.StreamFrameListener;
+import org.eclipse.jetty.spdy.api.StringDataInfo;
+import org.eclipse.jetty.spdy.api.SynInfo;
+import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.Fields;
+import org.eclipse.jetty.util.FutureCallback;
+import org.eclipse.jetty.util.FuturePromise;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class GoAwayTest extends AbstractTest
+{
+    @Test
+    public void testServerReceivesGoAwayOnClientGoAway() throws Exception
+    {
+        final CountDownLatch latch = new CountDownLatch(1);
+        ServerSessionFrameListener serverSessionFrameListener = new ServerSessionFrameListener.Adapter()
+        {
+            @Override
+            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
+            {
+                stream.reply(new ReplyInfo(true), new Callback.Adapter());
+                return null;
+            }
+
+            @Override
+            public void onGoAway(Session session, GoAwayResultInfo goAwayInfo)
+            {
+                Assert.assertEquals(0, goAwayInfo.getLastStreamId());
+                Assert.assertSame(SessionStatus.OK, goAwayInfo.getSessionStatus());
+                latch.countDown();
+            }
+        };
+        Session session = startClient(startServer(serverSessionFrameListener), null);
+
+        session.syn(new SynInfo(new Fields(), true), null);
+
+        session.goAway(new GoAwayInfo());
+
+        Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testClientReceivesGoAwayOnServerGoAway() throws Exception
+    {
+        ServerSessionFrameListener serverSessionFrameListener = new ServerSessionFrameListener.Adapter()
+        {
+            @Override
+            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
+            {
+                stream.reply(new ReplyInfo(true), new Callback.Adapter());
+                stream.getSession().goAway(new GoAwayInfo(), new FutureCallback());
+                return null;
+            }
+        };
+        final AtomicReference<GoAwayResultInfo> ref = new AtomicReference<>();
+        final CountDownLatch latch = new CountDownLatch(1);
+        SessionFrameListener clientSessionFrameListener = new SessionFrameListener.Adapter()
+        {
+            @Override
+            public void onGoAway(Session session, GoAwayResultInfo goAwayInfo)
+            {
+                ref.set(goAwayInfo);
+                latch.countDown();
+            }
+        };
+        Session session = startClient(startServer(serverSessionFrameListener), clientSessionFrameListener);
+
+        Stream stream1 = session.syn(new SynInfo(5, TimeUnit.SECONDS, new Fields(), true, (byte)0), null);
+
+        Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+        GoAwayResultInfo goAwayResultInfo = ref.get();
+        Assert.assertNotNull(goAwayResultInfo);
+        Assert.assertEquals(stream1.getId(), goAwayResultInfo.getLastStreamId());
+        Assert.assertSame(SessionStatus.OK, goAwayResultInfo.getSessionStatus());
+    }
+
+    @Test
+    public void testSynStreamIgnoredAfterGoAway() throws Exception
+    {
+        final CountDownLatch latch = new CountDownLatch(1);
+        ServerSessionFrameListener serverSessionFrameListener = new ServerSessionFrameListener.Adapter()
+        {
+            private final AtomicInteger syns = new AtomicInteger();
+
+            @Override
+            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
+            {
+                int synCount = syns.incrementAndGet();
+                if (synCount == 1)
+                {
+                    stream.reply(new ReplyInfo(true), new Callback.Adapter());
+                    stream.getSession().goAway(new GoAwayInfo(), new FutureCallback());
+                }
+                else
+                {
+                    latch.countDown();
+                }
+                return null;
+            }
+        };
+        SessionFrameListener clientSessionFrameListener = new SessionFrameListener.Adapter()
+        {
+            @Override
+            public void onGoAway(Session session, GoAwayResultInfo goAwayInfo)
+            {
+                session.syn(new SynInfo(new Fields(), true), null, new FuturePromise<Stream>());
+            }
+        };
+        Session session = startClient(startServer(serverSessionFrameListener), clientSessionFrameListener);
+
+        session.syn(new SynInfo(new Fields(), true), null);
+
+        Assert.assertFalse(latch.await(1, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testDataNotProcessedAfterGoAway() throws Exception
+    {
+        final CountDownLatch closeLatch = new CountDownLatch(1);
+        final CountDownLatch dataLatch = new CountDownLatch(1);
+        ServerSessionFrameListener serverSessionFrameListener = new ServerSessionFrameListener.Adapter()
+        {
+            private AtomicInteger syns = new AtomicInteger();
+
+            @Override
+            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
+            {
+                stream.reply(new ReplyInfo(true), new Callback.Adapter());
+                int synCount = syns.incrementAndGet();
+                if (synCount == 1)
+                {
+                    return null;
+                }
+                else
+                {
+                    stream.getSession().goAway(new GoAwayInfo(), new FutureCallback());
+                    closeLatch.countDown();
+                    return new StreamFrameListener.Adapter()
+                    {
+                        @Override
+                        public void onData(Stream stream, DataInfo dataInfo)
+                        {
+                            dataLatch.countDown();
+                        }
+                    };
+                }
+            }
+        };
+        final AtomicReference<GoAwayResultInfo> goAwayRef = new AtomicReference<>();
+        final CountDownLatch goAwayLatch = new CountDownLatch(1);
+        SessionFrameListener clientSessionFrameListener = new SessionFrameListener.Adapter()
+        {
+            @Override
+            public void onGoAway(Session session, GoAwayResultInfo goAwayInfo)
+            {
+                goAwayRef.set(goAwayInfo);
+                goAwayLatch.countDown();
+            }
+        };
+        Session session = startClient(startServer(serverSessionFrameListener), clientSessionFrameListener);
+
+        // First stream is processed ok
+        final CountDownLatch reply1Latch = new CountDownLatch(1);
+        session.syn(new SynInfo(5, TimeUnit.SECONDS, new Fields(), true, (byte)0), new StreamFrameListener.Adapter()
+        {
+            @Override
+            public void onReply(Stream stream, ReplyInfo replyInfo)
+            {
+                reply1Latch.countDown();
+            }
+        });
+        Assert.assertTrue(reply1Latch.await(5, TimeUnit.SECONDS));
+
+        // Second stream is closed in the middle
+        Stream stream2 = session.syn(new SynInfo(5, TimeUnit.SECONDS, new Fields(), false, (byte)0), null);
+        Assert.assertTrue(closeLatch.await(5, TimeUnit.SECONDS));
+
+        // There is a race between the data we want to send, and the client
+        // closing the connection because the server closed it after the
+        // go_away, so we guard with a try/catch to have the test pass cleanly
+        try
+        {
+            stream2.data(new StringDataInfo("foo", true));
+            Assert.assertFalse(dataLatch.await(1, TimeUnit.SECONDS));
+        }
+        catch (ExecutionException x)
+        {
+            // doesn't matter which exception we get, it's important that the data is not been written and the
+            // previous assertion is true
+        }
+
+        // The last good stream is the second, because it was received by the server
+        Assert.assertTrue(goAwayLatch.await(5, TimeUnit.SECONDS));
+        GoAwayResultInfo goAway = goAwayRef.get();
+        Assert.assertNotNull(goAway);
+        Assert.assertEquals(stream2.getId(), goAway.getLastStreamId());
+    }
+}
diff --git a/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/HeadersTest.java b/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/HeadersTest.java
new file mode 100644
index 0000000..afc3172
--- /dev/null
+++ b/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/HeadersTest.java
@@ -0,0 +1,86 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.spdy.server;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.spdy.api.HeadersInfo;
+import org.eclipse.jetty.spdy.api.ReplyInfo;
+import org.eclipse.jetty.spdy.api.Session;
+import org.eclipse.jetty.spdy.api.Stream;
+import org.eclipse.jetty.spdy.api.StreamFrameListener;
+import org.eclipse.jetty.spdy.api.SynInfo;
+import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.Fields;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class HeadersTest extends AbstractTest
+{
+    @Test
+    public void testHeaders() throws Exception
+    {
+        ServerSessionFrameListener serverSessionFrameListener = new ServerSessionFrameListener.Adapter()
+        {
+            @Override
+            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
+            {
+                stream.reply(new ReplyInfo(false), new Callback.Adapter());
+                return new StreamFrameListener.Adapter()
+                {
+                    @Override
+                    public void onHeaders(Stream stream, HeadersInfo headersInfo)
+                    {
+                        Assert.assertTrue(stream.isHalfClosed());
+                        stream.headers(new HeadersInfo(new Fields(), true), new Callback.Adapter());
+                        Assert.assertTrue(stream.isClosed());
+                    }
+                };
+            }
+        };
+
+        Session session = startClient(startServer(serverSessionFrameListener), null);
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        session.syn(new SynInfo(new Fields(), false), new StreamFrameListener.Adapter()
+        {
+            @Override
+            public void onReply(Stream stream, ReplyInfo replyInfo)
+            {
+                Fields headers = new Fields();
+                headers.put("foo", "bar");
+                headers.put("baz", "woo");
+                stream.headers(new HeadersInfo(headers, true), new Callback.Adapter());
+                Assert.assertTrue(stream.isHalfClosed());
+            }
+
+            @Override
+            public void onHeaders(Stream stream, HeadersInfo headersInfo)
+            {
+                Assert.assertTrue(stream.isClosed());
+                latch.countDown();
+            }
+        });
+
+        Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+    }
+}
diff --git a/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/IdleTimeoutTest.java b/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/IdleTimeoutTest.java
new file mode 100644
index 0000000..c0e436a
--- /dev/null
+++ b/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/IdleTimeoutTest.java
@@ -0,0 +1,257 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.spdy.server;
+
+import java.net.InetSocketAddress;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.spdy.api.GoAwayResultInfo;
+import org.eclipse.jetty.spdy.api.ReplyInfo;
+import org.eclipse.jetty.spdy.api.SPDY;
+import org.eclipse.jetty.spdy.api.Session;
+import org.eclipse.jetty.spdy.api.SessionFrameListener;
+import org.eclipse.jetty.spdy.api.Stream;
+import org.eclipse.jetty.spdy.api.StreamFrameListener;
+import org.eclipse.jetty.spdy.api.SynInfo;
+import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener;
+import org.eclipse.jetty.spdy.client.SPDYClient;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.Fields;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class IdleTimeoutTest extends AbstractTest
+{
+    private final int idleTimeout = 1000;
+
+    @Test
+    public void testServerEnforcingIdleTimeout() throws Exception
+    {
+        server = newServer();
+        connector = newSPDYServerConnector(server, new ServerSessionFrameListener.Adapter()
+        {
+            @Override
+            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
+            {
+                stream.reply(new ReplyInfo(true), new Callback.Adapter());
+                return null;
+            }
+        });
+        connector.setIdleTimeout(idleTimeout);
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        Session session = startClient(startServer(null), new SessionFrameListener.Adapter()
+        {
+            @Override
+            public void onGoAway(Session session, GoAwayResultInfo goAwayInfo)
+            {
+                latch.countDown();
+            }
+        });
+
+        session.syn(new SynInfo(new Fields(), true), null);
+
+        Assert.assertTrue(latch.await(2 * idleTimeout, TimeUnit.MILLISECONDS));
+    }
+
+    @Test
+    public void testServerEnforcingIdleTimeoutWithUnrespondedStream() throws Exception
+    {
+        server = newServer();
+        connector = newSPDYServerConnector(server, null);
+        connector.setIdleTimeout(idleTimeout);
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        Session session = startClient(startServer(null), new SessionFrameListener.Adapter()
+        {
+            @Override
+            public void onGoAway(Session session, GoAwayResultInfo goAwayInfo)
+            {
+                latch.countDown();
+            }
+        });
+
+        // The SYN is not replied, and the server should idle timeout
+        session.syn(new SynInfo(new Fields(), true), null);
+
+        Assert.assertTrue(latch.await(2 * idleTimeout, TimeUnit.MILLISECONDS));
+    }
+
+    @Test
+    public void testServerNotEnforcingIdleTimeoutWithPendingStream() throws Exception
+    {
+        server = newServer();
+        connector = newSPDYServerConnector(server, new ServerSessionFrameListener.Adapter()
+        {
+            @Override
+            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
+            {
+                try
+                {
+                    Thread.sleep(2 * idleTimeout);
+                    stream.reply(new ReplyInfo(true), new Callback.Adapter());
+                    return null;
+                }
+                catch (InterruptedException x)
+                {
+                    Assert.fail();
+                    return null;
+                }
+            }
+        });
+        connector.setIdleTimeout(idleTimeout);
+
+        final CountDownLatch goAwayLatch = new CountDownLatch(1);
+        Session session = startClient(startServer(null), new SessionFrameListener.Adapter()
+        {
+            @Override
+            public void onGoAway(Session session, GoAwayResultInfo goAwayInfo)
+            {
+                goAwayLatch.countDown();
+            }
+        });
+
+        final CountDownLatch replyLatch = new CountDownLatch(1);
+        session.syn(new SynInfo(new Fields(), true), new StreamFrameListener.Adapter()
+        {
+            @Override
+            public void onReply(Stream stream, ReplyInfo replyInfo)
+            {
+                replyLatch.countDown();
+            }
+        });
+
+        Assert.assertTrue(replyLatch.await(3 * idleTimeout, TimeUnit.MILLISECONDS));
+
+        // Just make sure onGoAway has never been called, but don't wait too much
+        Assert.assertFalse(goAwayLatch.await(idleTimeout / 2, TimeUnit.MILLISECONDS));
+    }
+
+    @Test
+    public void testClientEnforcingIdleTimeout() throws Exception
+    {
+        final CountDownLatch latch = new CountDownLatch(1);
+        InetSocketAddress address = startServer(new ServerSessionFrameListener.Adapter()
+        {
+            @Override
+            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
+            {
+                stream.reply(new ReplyInfo(true), new Callback.Adapter());
+                return null;
+            }
+
+            @Override
+            public void onGoAway(Session session, GoAwayResultInfo goAwayInfo)
+            {
+                latch.countDown();
+            }
+        });
+
+        QueuedThreadPool threadPool = new QueuedThreadPool();
+        threadPool.setName(threadPool.getName() + "-client");
+        clientFactory = newSPDYClientFactory(threadPool);
+        clientFactory.start();
+        SPDYClient client = clientFactory.newSPDYClient(SPDY.V2);
+        client.setIdleTimeout(idleTimeout);
+        Session session = client.connect(address, null);
+
+        session.syn(new SynInfo(new Fields(), true), null);
+
+        Assert.assertTrue(latch.await(2 * idleTimeout, TimeUnit.MILLISECONDS));
+    }
+
+    @Test
+    public void testClientEnforcingIdleTimeoutWithUnrespondedStream() throws Exception
+    {
+        final CountDownLatch latch = new CountDownLatch(1);
+        InetSocketAddress address = startServer(new ServerSessionFrameListener.Adapter()
+        {
+            @Override
+            public void onGoAway(Session session, GoAwayResultInfo goAwayInfo)
+            {
+                latch.countDown();
+            }
+        });
+
+        QueuedThreadPool threadPool = new QueuedThreadPool();
+        threadPool.setName(threadPool.getName() + "-client");
+        clientFactory = newSPDYClientFactory(threadPool);
+        clientFactory.start();
+        SPDYClient client = clientFactory.newSPDYClient(SPDY.V2);
+        client.setIdleTimeout(idleTimeout);
+        Session session = client.connect(address, null);
+
+        session.syn(new SynInfo(new Fields(), true), null);
+
+        Assert.assertTrue(latch.await(2 * idleTimeout, TimeUnit.MILLISECONDS));
+    }
+
+    @Test
+    public void testClientNotEnforcingIdleTimeoutWithPendingStream() throws Exception
+    {
+        final CountDownLatch latch = new CountDownLatch(1);
+        InetSocketAddress address = startServer(new ServerSessionFrameListener.Adapter()
+        {
+            @Override
+            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
+            {
+                stream.reply(new ReplyInfo(true), new Callback.Adapter());
+                return null;
+            }
+
+            @Override
+            public void onGoAway(Session session, GoAwayResultInfo goAwayInfo)
+            {
+                latch.countDown();
+            }
+        });
+
+        QueuedThreadPool threadPool = new QueuedThreadPool();
+        threadPool.setName(threadPool.getName() + "-client");
+        clientFactory = newSPDYClientFactory(threadPool);
+        clientFactory.start();
+        SPDYClient client = clientFactory.newSPDYClient(SPDY.V2);
+        client.setIdleTimeout(idleTimeout);
+        Session session = client.connect(address, null);
+
+        final CountDownLatch replyLatch = new CountDownLatch(1);
+        session.syn(new SynInfo(new Fields(), true), new StreamFrameListener.Adapter()
+        {
+            @Override
+            public void onReply(Stream stream, ReplyInfo replyInfo)
+            {
+                try
+                {
+                    Thread.sleep(2 * idleTimeout);
+                    replyLatch.countDown();
+                }
+                catch (InterruptedException e)
+                {
+                    Assert.fail();
+                }
+            }
+        });
+
+        Assert.assertFalse(latch.await(2 * idleTimeout, TimeUnit.MILLISECONDS));
+        Assert.assertTrue(replyLatch.await(3 * idleTimeout, TimeUnit.MILLISECONDS));
+    }
+}
diff --git a/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/MaxConcurrentStreamTest.java b/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/MaxConcurrentStreamTest.java
new file mode 100644
index 0000000..c6caee3
--- /dev/null
+++ b/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/MaxConcurrentStreamTest.java
@@ -0,0 +1,121 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.spdy.server;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.eclipse.jetty.spdy.api.ByteBufferDataInfo;
+import org.eclipse.jetty.spdy.api.DataInfo;
+import org.eclipse.jetty.spdy.api.ReplyInfo;
+import org.eclipse.jetty.spdy.api.Session;
+import org.eclipse.jetty.spdy.api.SessionFrameListener;
+import org.eclipse.jetty.spdy.api.Settings;
+import org.eclipse.jetty.spdy.api.SettingsInfo;
+import org.eclipse.jetty.spdy.api.Stream;
+import org.eclipse.jetty.spdy.api.StreamFrameListener;
+import org.eclipse.jetty.spdy.api.SynInfo;
+import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Fields;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+ at RunWith(JUnit4.class)
+public class MaxConcurrentStreamTest extends AbstractTest
+{
+    @Test
+    public void testMaxConcurrentStreamsSetByServer() throws Exception, ExecutionException
+    {
+        final CountDownLatch settingsReceivedLatch = new CountDownLatch(1);
+        final CountDownLatch dataReceivedLatch = new CountDownLatch(1);
+
+        Session session = startClient(startServer(new ServerSessionFrameListener.Adapter()
+        {
+            @Override
+            public void onConnect(Session session)
+            {
+                Settings settings = new Settings();
+                settings.put(new Settings.Setting(Settings.ID.MAX_CONCURRENT_STREAMS, 1));
+                try
+                {
+                    session.settings(new SettingsInfo(settings));
+                }
+                catch (ExecutionException | InterruptedException | TimeoutException e)
+                {
+                    e.printStackTrace();
+                }
+            }
+
+            @Override
+            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
+            {
+                try
+                {
+                    stream.reply(new ReplyInfo(true));
+                }
+                catch (ExecutionException | InterruptedException | TimeoutException e)
+                {
+                    e.printStackTrace();
+                }
+                return new StreamFrameListener.Adapter()
+                {
+                    @Override
+                    public void onData(Stream stream, DataInfo dataInfo)
+                    {
+                        dataReceivedLatch.countDown();
+                    }
+                };
+            }
+        }), new SessionFrameListener.Adapter()
+        {
+            @Override
+            public void onSettings(Session session, SettingsInfo settingsInfo)
+            {
+                settingsReceivedLatch.countDown();
+            }
+        });
+
+        assertThat("Settings frame received", settingsReceivedLatch.await(5, TimeUnit.SECONDS), is(true));
+
+        SynInfo synInfo = new SynInfo(new Fields(), false);
+        Stream stream = session.syn(synInfo, null);
+
+        boolean failed = false;
+        try
+        {
+            session.syn(synInfo, null);
+        }
+        catch (ExecutionException | InterruptedException | TimeoutException e)
+        {
+            failed = true;
+        }
+
+        assertThat("Opening second stream failed", failed, is(true));
+
+        stream.data(new ByteBufferDataInfo(BufferUtil.EMPTY_BUFFER, true));
+        assertThat("Data has been received on first stream.", dataReceivedLatch.await(5, TimeUnit.SECONDS), is(true));
+    }
+}
diff --git a/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/PingTest.java b/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/PingTest.java
new file mode 100644
index 0000000..c7bcd59
--- /dev/null
+++ b/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/PingTest.java
@@ -0,0 +1,106 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.spdy.server;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.jetty.spdy.api.PingInfo;
+import org.eclipse.jetty.spdy.api.PingResultInfo;
+import org.eclipse.jetty.spdy.api.Session;
+import org.eclipse.jetty.spdy.api.SessionFrameListener;
+import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener;
+import org.eclipse.jetty.util.Promise;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class PingTest extends AbstractTest
+{
+    @Test
+    public void testPingPong() throws Exception
+    {
+        final AtomicReference<PingResultInfo> ref = new AtomicReference<>();
+        final CountDownLatch latch = new CountDownLatch(1);
+        SessionFrameListener clientSessionFrameListener = new SessionFrameListener.Adapter()
+        {
+            @Override
+            public void onPing(Session session, PingResultInfo pingInfo)
+            {
+                ref.set(pingInfo);
+                latch.countDown();
+            }
+        };
+        Session session = startClient(startServer(null), clientSessionFrameListener);
+        PingResultInfo pingResultInfo = session.ping(new PingInfo(5, TimeUnit.SECONDS));
+        Assert.assertEquals(1, pingResultInfo.getPingId() % 2);
+
+        Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+        PingResultInfo pongInfo = ref.get();
+        Assert.assertNotNull(pongInfo);
+        Assert.assertEquals(pingResultInfo.getPingId(), pongInfo.getPingId());
+    }
+
+    @Test
+    public void testServerPingPong() throws Exception
+    {
+        final CountDownLatch pingReceived = new CountDownLatch(1);
+        ServerSessionFrameListener serverSessionFrameListener = new ServerSessionFrameListener.Adapter()
+        {
+            private final CountDownLatch pingSent = new CountDownLatch(1);
+            private int pingId;
+
+            @Override
+            public void onConnect(Session session)
+            {
+                session.ping(new PingInfo(), new Promise.Adapter<PingResultInfo>()
+                {
+                    @Override
+                    public void succeeded(PingResultInfo pingInfo)
+                    {
+                        pingId = pingInfo.getPingId();
+                        pingSent.countDown();
+                    }
+                });
+            }
+
+            @Override
+            public void onPing(Session session, PingResultInfo pingInfo)
+            {
+                try
+                {
+                    // This callback may be notified before the promise above,
+                    // so make sure we wait here to know the pingId
+                    Assert.assertTrue(pingSent.await(5, TimeUnit.SECONDS));
+                    Assert.assertEquals(0, pingInfo.getPingId() % 2);
+                    Assert.assertEquals(pingId, pingInfo.getPingId());
+                    pingReceived.countDown();
+                }
+                catch (InterruptedException x)
+                {
+                    Assert.fail();
+                }
+            }
+        };
+        startClient(startServer(serverSessionFrameListener), null);
+
+        Assert.assertTrue(pingReceived.await(5, TimeUnit.SECONDS));
+    }
+}
diff --git a/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/ProtocolViolationsTest.java b/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/ProtocolViolationsTest.java
new file mode 100644
index 0000000..96cc112
--- /dev/null
+++ b/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/ProtocolViolationsTest.java
@@ -0,0 +1,185 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.spdy.server;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.channels.ServerSocketChannel;
+import java.nio.channels.SocketChannel;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.io.MappedByteBufferPool;
+import org.eclipse.jetty.spdy.StandardCompressionFactory;
+import org.eclipse.jetty.spdy.api.BytesDataInfo;
+import org.eclipse.jetty.spdy.api.DataInfo;
+import org.eclipse.jetty.spdy.api.GoAwayInfo;
+import org.eclipse.jetty.spdy.api.HeadersInfo;
+import org.eclipse.jetty.spdy.api.RstInfo;
+import org.eclipse.jetty.spdy.api.SPDY;
+import org.eclipse.jetty.spdy.api.Session;
+import org.eclipse.jetty.spdy.api.SessionFrameListener;
+import org.eclipse.jetty.spdy.api.Stream;
+import org.eclipse.jetty.spdy.api.StreamFrameListener;
+import org.eclipse.jetty.spdy.api.StreamStatus;
+import org.eclipse.jetty.spdy.api.StringDataInfo;
+import org.eclipse.jetty.spdy.api.SynInfo;
+import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener;
+import org.eclipse.jetty.spdy.frames.ControlFrameType;
+import org.eclipse.jetty.spdy.frames.SynReplyFrame;
+import org.eclipse.jetty.spdy.generator.Generator;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.Fields;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class ProtocolViolationsTest extends AbstractTest
+{
+    @Test
+    public void testSendDataBeforeReplyIsIllegal() throws Exception
+    {
+        final CountDownLatch resetLatch = new CountDownLatch(1);
+        final CountDownLatch latch = new CountDownLatch(1);
+        Session session = startClient(startServer(new ServerSessionFrameListener.Adapter()
+        {
+            @Override
+            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
+            {
+                try
+                {
+                    stream.data(new StringDataInfo("failure", true), new Callback.Adapter());
+                    return null;
+                }
+                catch (IllegalStateException x)
+                {
+                    latch.countDown();
+                    return null;
+                }
+            }
+        }), new SessionFrameListener.Adapter()
+        {
+            @Override
+            public void onRst(Session session, RstInfo rstInfo)
+            {
+                Assert.assertSame(StreamStatus.PROTOCOL_ERROR, rstInfo.getStreamStatus());
+                resetLatch.countDown();
+            }
+        });
+        session.syn(new SynInfo(new Fields(), true), null);
+        Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+        Assert.assertTrue(resetLatch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testReceiveDataBeforeReplyIsIllegal() throws Exception
+    {
+        ServerSocketChannel server = ServerSocketChannel.open();
+        server.bind(new InetSocketAddress("localhost", 0));
+
+        Session session = startClient(new InetSocketAddress("localhost", server.socket().getLocalPort()), null);
+        session.syn(new SynInfo(new Fields(), true), null);
+
+        SocketChannel channel = server.accept();
+        ByteBuffer readBuffer = ByteBuffer.allocate(1024);
+        channel.read(readBuffer);
+        readBuffer.flip();
+        int streamId = readBuffer.getInt(8);
+
+        Generator generator = new Generator(new MappedByteBufferPool(), new StandardCompressionFactory.StandardCompressor());
+        byte[] bytes = new byte[1];
+        ByteBuffer writeBuffer = generator.data(streamId, bytes.length, new BytesDataInfo(bytes, true));
+        channel.write(writeBuffer);
+        assertThat("data is fully written", writeBuffer.hasRemaining(),is(false));
+
+        readBuffer.clear();
+        channel.read(readBuffer);
+        readBuffer.flip();
+        Assert.assertEquals(ControlFrameType.RST_STREAM.getCode(), readBuffer.getShort(2));
+        Assert.assertEquals(streamId, readBuffer.getInt(8));
+
+        session.goAway(new GoAwayInfo(5, TimeUnit.SECONDS));
+
+        server.close();
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testSendDataAfterCloseIsIllegal() throws Exception
+    {
+        Session session = startClient(startServer(null), null);
+        Stream stream = session.syn(new SynInfo(5, TimeUnit.SECONDS, new Fields(), true, (byte)0), null);
+        stream.data(new StringDataInfo("test", true));
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testSendHeadersAfterCloseIsIllegal() throws Exception
+    {
+        Session session = startClient(startServer(null), null);
+        Stream stream = session.syn(new SynInfo(5, TimeUnit.SECONDS, new Fields(), true, (byte)0), null);
+        stream.headers(new HeadersInfo(new Fields(), true));
+    }
+
+    @Test //TODO: throws an ISException in StandardStream.updateCloseState(). But instead we should send a rst or something to the server probably?!
+    public void testServerClosesStreamTwice() throws Exception
+    {
+        ServerSocketChannel server = ServerSocketChannel.open();
+        server.bind(new InetSocketAddress("localhost", 0));
+
+        Session session = startClient(new InetSocketAddress("localhost", server.socket().getLocalPort()), null);
+        final CountDownLatch dataLatch = new CountDownLatch(2);
+        session.syn(new SynInfo(new Fields(), false), new StreamFrameListener.Adapter()
+        {
+            @Override
+            public void onData(Stream stream, DataInfo dataInfo)
+            {
+                dataLatch.countDown();
+            }
+        });
+
+        SocketChannel channel = server.accept();
+        ByteBuffer readBuffer = ByteBuffer.allocate(1024);
+        channel.read(readBuffer);
+        readBuffer.flip();
+        int streamId = readBuffer.getInt(8);
+
+        Generator generator = new Generator(new MappedByteBufferPool(), new StandardCompressionFactory.StandardCompressor());
+
+        ByteBuffer writeBuffer = generator.control(new SynReplyFrame(SPDY.V2, (byte)0, streamId, new Fields()));
+        channel.write(writeBuffer);
+        assertThat("SynReply is fully written", writeBuffer.hasRemaining(), is(false));
+
+        byte[] bytes = new byte[1];
+        writeBuffer = generator.data(streamId, bytes.length, new BytesDataInfo(bytes, true));
+        channel.write(writeBuffer);
+        assertThat("data is fully written", writeBuffer.hasRemaining(), is(false));
+
+        // Write again to simulate the faulty condition
+        writeBuffer.flip();
+        channel.write(writeBuffer);
+        assertThat("data is fully written", writeBuffer.hasRemaining(), is(false));
+
+        Assert.assertFalse(dataLatch.await(1, TimeUnit.SECONDS));
+
+        session.goAway(new GoAwayInfo(5, TimeUnit.SECONDS));
+
+        server.close();
+    }
+}
diff --git a/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/PushStreamTest.java b/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/PushStreamTest.java
new file mode 100644
index 0000000..815baf2
--- /dev/null
+++ b/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/PushStreamTest.java
@@ -0,0 +1,591 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.spdy.server;
+
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.hamcrest.Matchers.sameInstance;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.channels.SocketChannel;
+import java.util.Arrays;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.CyclicBarrier;
+import java.util.concurrent.Exchanger;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.jetty.io.MappedByteBufferPool;
+import org.eclipse.jetty.spdy.StandardCompressionFactory;
+import org.eclipse.jetty.spdy.api.BytesDataInfo;
+import org.eclipse.jetty.spdy.api.DataInfo;
+import org.eclipse.jetty.spdy.api.GoAwayResultInfo;
+import org.eclipse.jetty.spdy.api.PushInfo;
+import org.eclipse.jetty.spdy.api.ReplyInfo;
+import org.eclipse.jetty.spdy.api.RstInfo;
+import org.eclipse.jetty.spdy.api.SPDY;
+import org.eclipse.jetty.spdy.api.Session;
+import org.eclipse.jetty.spdy.api.SessionFrameListener;
+import org.eclipse.jetty.spdy.api.SessionStatus;
+import org.eclipse.jetty.spdy.api.Stream;
+import org.eclipse.jetty.spdy.api.StreamFrameListener;
+import org.eclipse.jetty.spdy.api.StreamStatus;
+import org.eclipse.jetty.spdy.api.StringDataInfo;
+import org.eclipse.jetty.spdy.api.SynInfo;
+import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener;
+import org.eclipse.jetty.spdy.frames.ControlFrame;
+import org.eclipse.jetty.spdy.frames.DataFrame;
+import org.eclipse.jetty.spdy.frames.GoAwayFrame;
+import org.eclipse.jetty.spdy.frames.RstStreamFrame;
+import org.eclipse.jetty.spdy.frames.SynStreamFrame;
+import org.eclipse.jetty.spdy.frames.WindowUpdateFrame;
+import org.eclipse.jetty.spdy.generator.Generator;
+import org.eclipse.jetty.spdy.parser.Parser;
+import org.eclipse.jetty.spdy.parser.Parser.Listener;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.Fields;
+import org.eclipse.jetty.util.Promise;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class PushStreamTest extends AbstractTest
+{
+    private static final Logger LOG = Log.getLogger(PushStreamTest.class);
+
+    @Test
+    public void testSynPushStream() throws Exception
+    {
+        final AtomicReference<Stream> pushStreamRef = new AtomicReference<>();
+        final CountDownLatch pushStreamLatch = new CountDownLatch(1);
+
+        Session clientSession = startClient(startServer(new ServerSessionFrameListener.Adapter()
+        {
+            @Override
+            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
+            {
+                stream.reply(new ReplyInfo(false), new Callback.Adapter());
+                stream.push(new PushInfo(new Fields(), true), new Promise.Adapter<Stream>());
+                return null;
+            }
+        }), null);
+
+        Stream stream = clientSession.syn(new SynInfo(new Fields(), true), new StreamFrameListener.Adapter()
+        {
+            @Override
+            public StreamFrameListener onPush(Stream stream, PushInfo pushInfo)
+            {
+                assertThat("streamId is even", stream.getId() % 2, is(0));
+                assertThat("stream is unidirectional", stream.isUnidirectional(), is(true));
+                assertThat("stream is closed", stream.isClosed(), is(true));
+                assertThat("stream has associated stream", stream.getAssociatedStream(), notNullValue());
+                try
+                {
+                    stream.reply(new ReplyInfo(false), new Callback.Adapter());
+                    fail("Cannot reply to push streams");
+                }
+                catch (IllegalStateException x)
+                {
+                    // Expected
+                }
+                pushStreamRef.set(stream);
+                pushStreamLatch.countDown();
+                return null;
+            }
+        });
+        assertThat("onSyn has been called", pushStreamLatch.await(5, TimeUnit.SECONDS), is(true));
+        Stream pushStream = pushStreamRef.get();
+        assertThat("main stream and associated stream are the same", stream, sameInstance(pushStream.getAssociatedStream()));
+    }
+
+    @Test
+    public void testSendDataOnPushStreamAfterAssociatedStreamIsClosed() throws Exception
+    {
+        final Exchanger<Stream> streamExchanger = new Exchanger<>();
+        final CountDownLatch pushStreamSynLatch = new CountDownLatch(1);
+        final CyclicBarrier replyBarrier = new CyclicBarrier(3);
+        final CyclicBarrier closeBarrier = new CyclicBarrier(3);
+        final CountDownLatch streamDataSent = new CountDownLatch(2);
+        final CountDownLatch pushStreamDataReceived = new CountDownLatch(2);
+        final CountDownLatch exceptionCountDownLatch = new CountDownLatch(1);
+
+        Session clientSession = startClient(startServer(new ServerSessionFrameListener.Adapter()
+        {
+            @Override
+            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
+            {
+                stream.reply(new ReplyInfo(false), new Callback.Adapter());
+                try
+                {
+                    replyBarrier.await(5, TimeUnit.SECONDS);
+                    return new StreamFrameListener.Adapter()
+                    {
+                        @Override
+                        public void onData(Stream stream, DataInfo dataInfo)
+                        {
+                            try
+                            {
+                                if (dataInfo.isClose())
+                                {
+                                    stream.data(new StringDataInfo("close stream", true));
+                                    closeBarrier.await(5, TimeUnit.SECONDS);
+                                }
+                                streamDataSent.countDown();
+                                if (pushStreamDataReceived.getCount() == 2)
+                                {
+                                    Stream pushStream = stream.push(new PushInfo(new Fields(), false));
+                                    streamExchanger.exchange(pushStream, 5, TimeUnit.SECONDS);
+                                }
+                            }
+                            catch (Exception e)
+                            {
+                                exceptionCountDownLatch.countDown();
+                            }
+                        }
+                    };
+                }
+                catch (Exception e)
+                {
+                    exceptionCountDownLatch.countDown();
+                    throw new IllegalStateException(e);
+                }
+            }
+
+        }), null);
+
+        Stream stream = clientSession.syn(new SynInfo(new Fields(), false), new StreamFrameListener.Adapter()
+        {
+            @Override
+            public StreamFrameListener onPush(Stream stream, PushInfo pushInfo)
+            {
+                pushStreamSynLatch.countDown();
+                return new StreamFrameListener.Adapter()
+                {
+                    @Override
+                    public void onData(Stream stream, DataInfo dataInfo)
+                    {
+                        pushStreamDataReceived.countDown();
+                        super.onData(stream, dataInfo);
+                    }
+                };
+            }
+
+            @Override
+            public void onReply(Stream stream, ReplyInfo replyInfo)
+            {
+                try
+                {
+                    replyBarrier.await(5, TimeUnit.SECONDS);
+                }
+                catch (Exception e)
+                {
+                    exceptionCountDownLatch.countDown();
+                }
+            }
+
+            @Override
+            public void onData(Stream stream, DataInfo dataInfo)
+            {
+                try
+                {
+                    closeBarrier.await(5, TimeUnit.SECONDS);
+                }
+                catch (Exception e)
+                {
+                    exceptionCountDownLatch.countDown();
+                }
+            }
+        });
+
+        replyBarrier.await(5, TimeUnit.SECONDS);
+        stream.data(new StringDataInfo("client data", false));
+        Stream pushStream = streamExchanger.exchange(null, 5, TimeUnit.SECONDS);
+        pushStream.data(new StringDataInfo("first push data frame", false));
+        // nasty, but less complex than using another cyclicBarrier for example
+        while (pushStreamDataReceived.getCount() != 1)
+            Thread.sleep(1);
+        stream.data(new StringDataInfo("client close", true));
+        closeBarrier.await(5, TimeUnit.SECONDS);
+        assertThat("stream is closed", stream.isClosed(), is(true));
+        pushStream.data(new StringDataInfo("second push data frame while associated stream has been closed already", false));
+        assertThat("2 pushStream data frames have been received.", pushStreamDataReceived.await(5, TimeUnit.SECONDS), is(true));
+        assertThat("2 data frames have been sent", streamDataSent.await(5, TimeUnit.SECONDS), is(true));
+        assertThatNoExceptionOccurred(exceptionCountDownLatch);
+    }
+
+    @Test
+    public void testSynPushStreamOnClosedStream() throws Exception
+    {
+        final CountDownLatch pushStreamFailedLatch = new CountDownLatch(1);
+
+        Session clientSession = startClient(startServer(new ServerSessionFrameListener.Adapter()
+        {
+            @Override
+            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
+            {
+                stream.reply(new ReplyInfo(true), new Callback.Adapter());
+                stream.push(new PushInfo(1, TimeUnit.SECONDS, new Fields(), false),
+                        new Promise.Adapter<Stream>()
+                        {
+                            @Override
+                            public void failed(Throwable x)
+                            {
+                                pushStreamFailedLatch.countDown();
+                            }
+                        });
+                return super.onSyn(stream, synInfo);
+            }
+        }), new SessionFrameListener.Adapter());
+
+        clientSession.syn(new SynInfo(new Fields(), true), null);
+        assertThat("pushStream push has failed", pushStreamFailedLatch.await(5, TimeUnit.SECONDS), is(true));
+    }
+
+    @Test
+    public void testSendBigDataOnPushStreamWhenAssociatedStreamIsClosed() throws Exception
+    {
+        final CountDownLatch streamClosedLatch = new CountDownLatch(1);
+        final CountDownLatch allDataReceived = new CountDownLatch(1);
+        final CountDownLatch exceptionCountDownLatch = new CountDownLatch(1);
+        final Exchanger<ByteBuffer> exchanger = new Exchanger<>();
+        final int dataSizeInBytes = 1024 * 1024 * 1;
+        final byte[] transferBytes = createHugeByteArray(dataSizeInBytes);
+
+        Session clientSession = startClient(startServer(new ServerSessionFrameListener.Adapter()
+        {
+            @Override
+            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
+            {
+                try
+                {
+                    Stream pushStream = stream.push(new PushInfo(new Fields(), false));
+                    stream.reply(new ReplyInfo(true));
+                    // wait until stream is closed
+                    streamClosedLatch.await(5, TimeUnit.SECONDS);
+                    pushStream.data(new BytesDataInfo(transferBytes, true), new Callback.Adapter());
+                    return null;
+                }
+                catch (Exception e)
+                {
+                    exceptionCountDownLatch.countDown();
+                    throw new IllegalStateException(e);
+                }
+            }
+        }), null);
+
+        Stream stream = clientSession.syn(new SynInfo(new Fields(), true), new StreamFrameListener.Adapter()
+        {
+            @Override
+            public StreamFrameListener onPush(Stream stream, PushInfo pushInfo)
+            {
+                return new StreamFrameListener.Adapter()
+                {
+                    ByteBuffer receivedBytes = ByteBuffer.allocate(dataSizeInBytes);
+
+                    @Override
+                    public void onData(Stream stream, DataInfo dataInfo)
+                    {
+                        dataInfo.consumeInto(receivedBytes);
+                        if (dataInfo.isClose())
+                        {
+                            allDataReceived.countDown();
+                            try
+                            {
+                                receivedBytes.flip();
+                                exchanger.exchange(receivedBytes.slice(), 5, TimeUnit.SECONDS);
+                            }
+                            catch (Exception e)
+                            {
+                                exceptionCountDownLatch.countDown();
+                            }
+                        }
+                    }
+                };
+            }
+
+            @Override
+            public void onReply(Stream stream, ReplyInfo replyInfo)
+            {
+                streamClosedLatch.countDown();
+                super.onReply(stream, replyInfo);
+            }
+        });
+
+        ByteBuffer receivedBytes = exchanger.exchange(null, 5, TimeUnit.SECONDS);
+
+        assertThat("received byte array is the same as transferred byte array", Arrays.equals(transferBytes, receivedBytes.array()), is(true));
+        assertThat("onReply has been called to close the stream", streamClosedLatch.await(5, TimeUnit.SECONDS), is(true));
+        assertThat("stream is closed", stream.isClosed(), is(true));
+        assertThat("all data has been received", allDataReceived.await(20, TimeUnit.SECONDS), is(true));
+        assertThatNoExceptionOccurred(exceptionCountDownLatch);
+    }
+
+    private byte[] createHugeByteArray(int sizeInBytes)
+    {
+        byte[] bytes = new byte[sizeInBytes];
+        ThreadLocalRandom.current().nextBytes(bytes);
+        return bytes;
+    }
+
+
+    @Test
+    public void testClientResetsStreamAfterPushSynDoesPreventSendingDataFramesWithFlowControl() throws Exception
+    {
+        final boolean flowControl = true;
+        testNoMoreFramesAreSentOnPushStreamAfterClientResetsThePushStream(flowControl);
+    }
+
+    @Test
+    public void testClientResetsStreamAfterPushSynDoesPreventSendingDataFramesWithoutFlowControl() throws Exception
+    {
+        final boolean flowControl = false;
+        testNoMoreFramesAreSentOnPushStreamAfterClientResetsThePushStream(flowControl);
+    }
+
+    private volatile boolean read = true;
+
+    private void testNoMoreFramesAreSentOnPushStreamAfterClientResetsThePushStream(final boolean flowControl) throws Exception
+    {
+        final short version = SPDY.V3;
+        final AtomicBoolean unexpectedExceptionOccurred = new AtomicBoolean(false);
+        final CountDownLatch resetReceivedLatch = new CountDownLatch(1);
+        final CountDownLatch allDataFramesReceivedLatch = new CountDownLatch(1);
+        final CountDownLatch goAwayReceivedLatch = new CountDownLatch(1);
+        final int dataSizeInBytes = 1024 * 256;
+        final byte[] transferBytes = createHugeByteArray(dataSizeInBytes);
+
+        InetSocketAddress serverAddress = startServer(new ServerSessionFrameListener.Adapter()
+        {
+            @Override
+            public StreamFrameListener onSyn(final Stream stream, SynInfo synInfo)
+            {
+                new Thread(new Runnable()
+                {
+
+                    @Override
+                    public void run()
+                    {
+                        Stream pushStream = null;
+                        try
+                        {
+                            stream.reply(new ReplyInfo(false), new Callback.Adapter());
+                            pushStream = stream.push(new PushInfo(new Fields(), false));
+                            resetReceivedLatch.await(5, TimeUnit.SECONDS);
+                        }
+                        catch (InterruptedException | ExecutionException | TimeoutException e)
+                        {
+                            e.printStackTrace();
+                            unexpectedExceptionOccurred.set(true);
+                        }
+                        assert pushStream != null;
+                        try
+                        {
+                            pushStream.data(new BytesDataInfo(transferBytes, true));
+                            stream.data(new StringDataInfo("close", true));
+                        }
+                        catch (InterruptedException | ExecutionException | TimeoutException e)
+                        {
+                            LOG.debug(e.getMessage());
+                        }
+                    }
+                }).start();
+                return null;
+            }
+
+            @Override
+            public void onRst(Session session, RstInfo rstInfo)
+            {
+                resetReceivedLatch.countDown();
+            }
+
+            @Override
+            public void onGoAway(Session session, GoAwayResultInfo goAwayInfo)
+            {
+                goAwayReceivedLatch.countDown();
+            }
+        }/*TODO, flowControl*/);
+
+        final SocketChannel channel = SocketChannel.open(serverAddress);
+        final Generator generator = new Generator(new MappedByteBufferPool(), new StandardCompressionFactory.StandardCompressor());
+        int streamId = 1;
+        ByteBuffer writeBuffer = generator.control(new SynStreamFrame(version, (byte)0, streamId, 0, (byte)0, (short)0, new Fields()));
+        channel.write(writeBuffer);
+        assertThat("writeBuffer is fully written", writeBuffer.hasRemaining(), is(false));
+
+        final Parser parser = new Parser(new StandardCompressionFactory.StandardDecompressor());
+        parser.addListener(new Listener.Adapter()
+        {
+            int bytesRead = 0;
+
+            @Override
+            public void onControlFrame(ControlFrame frame)
+            {
+                if (frame instanceof SynStreamFrame)
+                {
+                    int pushStreamId = ((SynStreamFrame)frame).getStreamId();
+                    ByteBuffer writeBuffer = generator.control(new RstStreamFrame(version, pushStreamId, StreamStatus.CANCEL_STREAM.getCode(version)));
+                    try
+                    {
+                        channel.write(writeBuffer);
+                    }
+                    catch (IOException e)
+                    {
+                        e.printStackTrace();
+                        unexpectedExceptionOccurred.set(true);
+                    }
+                }
+            }
+
+            @Override
+            public void onDataFrame(DataFrame frame, ByteBuffer data)
+            {
+                if (frame.getStreamId() == 2)
+                    bytesRead = bytesRead + frame.getLength();
+                if (bytesRead == dataSizeInBytes)
+                {
+                    allDataFramesReceivedLatch.countDown();
+                    return;
+                }
+                if (flowControl)
+                {
+                    ByteBuffer writeBuffer = generator.control(new WindowUpdateFrame(version, frame.getStreamId(), frame.getLength()));
+                    try
+                    {
+                        channel.write(writeBuffer);
+                    }
+                    catch (IOException e)
+                    {
+                        e.printStackTrace();
+                        unexpectedExceptionOccurred.set(true);
+                    }
+                }
+            }
+        });
+
+        Thread reader = new Thread(new Runnable()
+        {
+            @Override
+            public void run()
+            {
+                ByteBuffer readBuffer = ByteBuffer.allocate(dataSizeInBytes * 2);
+                while (read)
+                {
+                    try
+                    {
+                        channel.read(readBuffer);
+                    }
+                    catch (IOException e)
+                    {
+                        e.printStackTrace();
+                        unexpectedExceptionOccurred.set(true);
+                    }
+                    readBuffer.flip();
+                    parser.parse(readBuffer);
+                    readBuffer.clear();
+                }
+
+            }
+        });
+        reader.start();
+        read = false;
+
+        assertThat("no unexpected exceptions occurred", unexpectedExceptionOccurred.get(), is(false));
+        assertThat("not all dataframes have been received as the pushstream has been reset by the client.", allDataFramesReceivedLatch.await(streamId, TimeUnit.SECONDS), is(false));
+
+
+        ByteBuffer buffer = generator.control(new GoAwayFrame(version, streamId, SessionStatus.OK.getCode()));
+        channel.write(buffer);
+        Assert.assertThat(buffer.hasRemaining(), is(false));
+
+        assertThat("GoAway frame is received by server", goAwayReceivedLatch.await(5, TimeUnit.SECONDS), is(true));
+        channel.shutdownOutput();
+        channel.close();
+    }
+
+    @Test
+    public void testOddEvenStreamIds() throws Exception
+    {
+        final CountDownLatch pushStreamIdIsEvenLatch = new CountDownLatch(3);
+
+        Session clientSession = startClient(startServer(new ServerSessionFrameListener.Adapter()
+        {
+            @Override
+            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
+            {
+                stream.push(new PushInfo(new Fields(), false), new Promise.Adapter<Stream>());
+                return null;
+            }
+        }), null);
+
+        Stream stream = clientSession.syn(new SynInfo(new Fields(), false),
+                new VerifyPushStreamIdIsEvenStreamFrameListener(pushStreamIdIsEvenLatch));
+        Stream stream2 = clientSession.syn(new SynInfo(new Fields(), false),
+                new VerifyPushStreamIdIsEvenStreamFrameListener(pushStreamIdIsEvenLatch));
+        Stream stream3 = clientSession.syn(new SynInfo(new Fields(), false),
+                new VerifyPushStreamIdIsEvenStreamFrameListener(pushStreamIdIsEvenLatch));
+        assertStreamIdIsOdd(stream);
+        assertStreamIdIsOdd(stream2);
+        assertStreamIdIsOdd(stream3);
+
+        assertThat("all pushStreams had even ids", pushStreamIdIsEvenLatch.await(5, TimeUnit.SECONDS), is(true));
+    }
+
+    private class VerifyPushStreamIdIsEvenStreamFrameListener extends StreamFrameListener.Adapter
+    {
+        final CountDownLatch pushStreamIdIsEvenLatch;
+
+        private VerifyPushStreamIdIsEvenStreamFrameListener(CountDownLatch pushStreamIdIsEvenLatch)
+        {
+            this.pushStreamIdIsEvenLatch = pushStreamIdIsEvenLatch;
+        }
+
+        @Override
+        public StreamFrameListener onPush(Stream stream, PushInfo pushInfo)
+        {
+            assertStreamIdIsEven(stream);
+            pushStreamIdIsEvenLatch.countDown();
+            return super.onPush(stream, pushInfo);
+        }
+    }
+
+    private void assertStreamIdIsEven(Stream stream)
+    {
+        assertThat("streamId is odd", stream.getId() % 2, is(0));
+    }
+
+    private void assertStreamIdIsOdd(Stream stream)
+    {
+        assertThat("streamId is odd", stream.getId() % 2, is(1));
+    }
+
+    private void assertThatNoExceptionOccurred(final CountDownLatch exceptionCountDownLatch) throws InterruptedException
+    {
+        assertThat("No exception occurred", exceptionCountDownLatch.await(1, TimeUnit.SECONDS), is(false));
+    }
+}
diff --git a/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/ResetStreamTest.java b/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/ResetStreamTest.java
new file mode 100644
index 0000000..b2321d8
--- /dev/null
+++ b/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/ResetStreamTest.java
@@ -0,0 +1,204 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.spdy.server;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.jetty.spdy.api.DataInfo;
+import org.eclipse.jetty.spdy.api.RstInfo;
+import org.eclipse.jetty.spdy.api.Session;
+import org.eclipse.jetty.spdy.api.SessionFrameListener;
+import org.eclipse.jetty.spdy.api.Stream;
+import org.eclipse.jetty.spdy.api.StreamFrameListener;
+import org.eclipse.jetty.spdy.api.StreamStatus;
+import org.eclipse.jetty.spdy.api.StringDataInfo;
+import org.eclipse.jetty.spdy.api.SynInfo;
+import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.Fields;
+import org.eclipse.jetty.util.FutureCallback;
+import org.junit.Test;
+
+public class ResetStreamTest extends AbstractTest
+{
+    @Test
+    public void testResetStreamIsRemoved() throws Exception
+    {
+        Session session = startClient(startServer(new ServerSessionFrameListener.Adapter()/*TODO, true*/), null);
+
+        Stream stream = session.syn(new SynInfo(5, TimeUnit.SECONDS, new Fields(), false, (byte)0), null);
+        session.rst(new RstInfo(5, TimeUnit.SECONDS, stream.getId(), StreamStatus.CANCEL_STREAM));
+
+        assertEquals("session expected to contain 0 streams", 0, session.getStreams().size());
+    }
+
+    @Test
+    public void testRefusedStreamIsRemoved() throws Exception
+    {
+        final AtomicReference<Session> serverSessionRef = new AtomicReference<>();
+        final CountDownLatch synLatch = new CountDownLatch(1);
+        final CountDownLatch rstLatch = new CountDownLatch(1);
+        Session clientSession = startClient(startServer(new ServerSessionFrameListener.Adapter()
+        {
+            @Override
+            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
+            {
+                Session serverSession = stream.getSession();
+                serverSessionRef.set(serverSession);
+                serverSession.rst(new RstInfo(stream.getId(), StreamStatus.REFUSED_STREAM), new FutureCallback());
+                synLatch.countDown();
+                return null;
+            }
+        }), new SessionFrameListener.Adapter()
+        {
+            @Override
+            public void onRst(Session session, RstInfo rstInfo)
+            {
+                rstLatch.countDown();
+            }
+        });
+
+        Stream stream = clientSession.syn(new SynInfo(5, TimeUnit.SECONDS, new Fields(), false, (byte)0), null);
+
+        assertTrue("syncLatch didn't count down", synLatch.await(5, TimeUnit.SECONDS));
+        Session serverSession = serverSessionRef.get();
+        assertEquals("serverSession expected to contain 0 streams", 0, serverSession.getStreams().size());
+
+        assertTrue("rstLatch didn't count down", rstLatch.await(5, TimeUnit.SECONDS));
+        // Need to sleep a while to give the chance to the implementation to remove the stream
+        TimeUnit.SECONDS.sleep(1);
+        assertTrue("stream is expected to be reset", stream.isReset());
+        assertEquals("clientSession expected to contain 0 streams", 0, clientSession.getStreams().size());
+    }
+
+    @Test
+    public void testRefusedStreamIgnoresData() throws Exception
+    {
+        final CountDownLatch synLatch = new CountDownLatch(1);
+        final CountDownLatch dataLatch = new CountDownLatch(1);
+        final CountDownLatch rstLatch = new CountDownLatch(1);
+        Session session = startClient(startServer(new ServerSessionFrameListener.Adapter()
+        {
+            @Override
+            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
+            {
+                try
+                {
+                    // Refuse the stream, we must ignore data frames
+                    assertTrue(synLatch.await(5, TimeUnit.SECONDS));
+                    stream.getSession().rst(new RstInfo(stream.getId(), StreamStatus.REFUSED_STREAM), new FutureCallback());
+                    return new StreamFrameListener.Adapter()
+                    {
+                        @Override
+                        public void onData(Stream stream, DataInfo dataInfo)
+                        {
+                            dataLatch.countDown();
+                        }
+                    };
+                }
+                catch (InterruptedException x)
+                {
+                    x.printStackTrace();
+                    return null;
+                }
+            }
+        }), new SessionFrameListener.Adapter()
+        {
+            @Override
+            public void onRst(Session session, RstInfo rstInfo)
+            {
+                rstLatch.countDown();
+            }
+        });
+
+        Stream stream = session.syn(new SynInfo(5, TimeUnit.SECONDS, new Fields(), false, (byte)0), null);
+        stream.data(new StringDataInfo(5, TimeUnit.SECONDS, "data", true), new Callback.Adapter()
+        {
+            @Override
+            public void succeeded()
+            {
+                synLatch.countDown();
+            }
+        });
+
+        assertTrue("rstLatch didn't count down", rstLatch.await(5, TimeUnit.SECONDS));
+        assertTrue("stream is expected to be reset", stream.isReset());
+        assertFalse("dataLatch shouldn't be count down", dataLatch.await(1, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testResetAfterServerReceivedFirstDataFrameAndSecondDataFrameFails() throws Exception
+    {
+        final CountDownLatch synLatch = new CountDownLatch(1);
+        final CountDownLatch dataLatch = new CountDownLatch(1);
+        final CountDownLatch rstLatch = new CountDownLatch(1);
+        final CountDownLatch failLatch = new CountDownLatch(1);
+        Session session = startClient(startServer(new ServerSessionFrameListener.Adapter()
+        {
+            @Override
+            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
+            {
+                synLatch.countDown();
+                return new StreamFrameListener.Adapter()
+                {
+                    @Override
+                    public void onData(Stream stream, DataInfo dataInfo)
+                    {
+                        dataLatch.countDown();
+                        stream.getSession().rst(new RstInfo(stream.getId(), StreamStatus.REFUSED_STREAM), new FutureCallback());
+                    }
+                };
+            }
+        }), new SessionFrameListener.Adapter()
+        {
+            @Override
+            public void onRst(Session session, RstInfo rstInfo)
+            {
+                rstLatch.countDown();
+            }
+        });
+
+        Stream stream = session.syn(new SynInfo(5, TimeUnit.SECONDS, new Fields(), false, (byte)0), null);
+        assertThat("push is received by server", synLatch.await(5, TimeUnit.SECONDS), is(true));
+        stream.data(new StringDataInfo(5, TimeUnit.SECONDS, "data", false), new Callback.Adapter());
+        assertThat("stream is reset", rstLatch.await(5, TimeUnit.SECONDS), is(true));
+        stream.data(new StringDataInfo(5, TimeUnit.SECONDS, "2nd dataframe", false), new Callback.Adapter()
+        {
+            @Override
+            public void failed(Throwable x)
+            {
+                failLatch.countDown();
+            }
+        });
+
+        assertThat("2nd data call failed", failLatch.await(5, TimeUnit.SECONDS), is(true));
+        assertThat("stream is reset", stream.isReset(), is(true));
+    }
+
+    // TODO: If server already received 2nd dataframe after it rst, it should ignore it. Not easy to do.
+
+}
diff --git a/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/SPDYClientFactoryTest.java b/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/SPDYClientFactoryTest.java
new file mode 100644
index 0000000..cfd85f6
--- /dev/null
+++ b/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/SPDYClientFactoryTest.java
@@ -0,0 +1,75 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.spdy.server;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.spdy.api.GoAwayInfo;
+import org.eclipse.jetty.spdy.api.GoAwayResultInfo;
+import org.eclipse.jetty.spdy.api.Session;
+import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class SPDYClientFactoryTest extends AbstractTest
+{
+    @Test
+    public void testStoppingClientFactorySendsGoAway() throws Exception
+    {
+        final CountDownLatch latch = new CountDownLatch(1);
+        startClient(startServer(new ServerSessionFrameListener.Adapter()
+        {
+            @Override
+            public void onGoAway(Session session, GoAwayResultInfo goAwayResultInfo)
+            {
+                latch.countDown();
+            }
+        }), null);
+
+        // Sleep a while to avoid the factory is
+        // stopped before a session can be opened
+        TimeUnit.SECONDS.sleep(1);
+
+        clientFactory.stop();
+
+        Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+        Assert.assertTrue(clientFactory.getSessions().isEmpty());
+    }
+
+    @Test
+    public void testSessionClosedIsRemovedFromClientFactory() throws Exception
+    {
+        Session session = startClient(startServer(null), null);
+
+        session.goAway(new GoAwayInfo(5, TimeUnit.SECONDS));
+
+        for (int i=0;i<10;i++)
+        {
+            // Sleep a while to allow the factory to remove the session
+            // since it is done asynchronously by the selector thread
+            TimeUnit.SECONDS.sleep(1);
+            if (clientFactory.getSessions().isEmpty())
+                return;
+        }
+
+        Assert.fail(clientFactory.getSessions().toString());
+    }
+}
diff --git a/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/SPDYServerConnectorTest.java b/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/SPDYServerConnectorTest.java
new file mode 100644
index 0000000..673c067
--- /dev/null
+++ b/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/SPDYServerConnectorTest.java
@@ -0,0 +1,70 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.spdy.server;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.spdy.api.GoAwayInfo;
+import org.eclipse.jetty.spdy.api.GoAwayResultInfo;
+import org.eclipse.jetty.spdy.api.Session;
+import org.eclipse.jetty.spdy.api.SessionFrameListener;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class SPDYServerConnectorTest extends AbstractTest
+{
+    @Test
+    public void testStoppingServerConnectorSendsGoAway() throws Exception
+    {
+        final CountDownLatch latch = new CountDownLatch(1);
+        startClient(startServer(null), new SessionFrameListener.Adapter()
+        {
+            @Override
+            public void onGoAway(Session session, GoAwayResultInfo goAwayResultInfo)
+            {
+                latch.countDown();
+            }
+        });
+
+        // Sleep a while to avoid the connector is
+        // stopped before a session can be opened
+        TimeUnit.SECONDS.sleep(1);
+
+        connector.stop();
+
+        Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+        Assert.assertTrue(connector.getConnectionFactory(SPDYServerConnectionFactory.class).getSessions().isEmpty());
+    }
+
+    @Test
+    public void testSessionClosedIsRemovedFromServerConnector() throws Exception
+    {
+        Session session = startClient(startServer(null), null);
+
+        session.goAway(new GoAwayInfo(5, TimeUnit.SECONDS));
+
+        // Sleep a while to allow the connector to remove the session
+        // since it is done asynchronously by the selector thread
+        TimeUnit.SECONDS.sleep(1);
+
+        Assert.assertTrue(connector.getConnectionFactory(SPDYServerConnectionFactory.class).getSessions().isEmpty());
+    }
+}
diff --git a/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/SettingsTest.java b/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/SettingsTest.java
new file mode 100644
index 0000000..c8f61cb
--- /dev/null
+++ b/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/SettingsTest.java
@@ -0,0 +1,168 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.spdy.server;
+
+import java.net.InetSocketAddress;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.jetty.spdy.api.SPDY;
+import org.eclipse.jetty.spdy.api.Session;
+import org.eclipse.jetty.spdy.api.SessionFrameListener;
+import org.eclipse.jetty.spdy.api.Settings;
+import org.eclipse.jetty.spdy.api.SettingsInfo;
+import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener;
+import org.eclipse.jetty.util.FutureCallback;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class SettingsTest extends AbstractTest
+{
+    @Test
+    public void testSettingsUsage() throws Exception
+    {
+        Settings settings = new Settings();
+        int streamsValue = 100;
+        settings.put(new Settings.Setting(Settings.ID.MAX_CONCURRENT_STREAMS, Settings.Flag.PERSIST, streamsValue));
+        int windowValue = 32768;
+        settings.put(new Settings.Setting(Settings.ID.INITIAL_WINDOW_SIZE, windowValue));
+        int newCode = 91;
+        Settings.ID newID = Settings.ID.from(newCode);
+        int newValue = 97;
+        settings.put(new Settings.Setting(newID, newValue));
+
+        Settings.Setting setting1 = settings.get(Settings.ID.MAX_CONCURRENT_STREAMS);
+        Assert.assertSame(Settings.ID.MAX_CONCURRENT_STREAMS, setting1.id());
+        Assert.assertSame(Settings.Flag.PERSIST, setting1.flag());
+        Assert.assertEquals(streamsValue, setting1.value());
+
+        Settings.Setting setting2 = settings.get(Settings.ID.INITIAL_WINDOW_SIZE);
+        Assert.assertSame(Settings.ID.INITIAL_WINDOW_SIZE, setting2.id());
+        Assert.assertSame(Settings.Flag.NONE, setting2.flag());
+        Assert.assertEquals(windowValue, setting2.value());
+
+        int size = settings.size();
+        Settings.Setting setting3 = settings.remove(Settings.ID.from(newCode));
+        Assert.assertEquals(size - 1, settings.size());
+        Assert.assertNotNull(setting3);
+        Assert.assertSame(newID, setting3.id());
+        Assert.assertEquals(newValue, setting3.value());
+    }
+
+    @Test
+    public void testSettings() throws Exception
+    {
+        Settings settings = new Settings();
+        settings.put(new Settings.Setting(Settings.ID.UPLOAD_BANDWIDTH, 1024 * 1024));
+        settings.put(new Settings.Setting(Settings.ID.DOWNLOAD_BANDWIDTH, 1024 * 1024));
+        settings.put(new Settings.Setting(Settings.ID.CURRENT_CONGESTION_WINDOW, Settings.Flag.PERSISTED, 1024));
+        final SettingsInfo clientSettingsInfo = new SettingsInfo(settings);
+        final CountDownLatch latch = new CountDownLatch(1);
+        ServerSessionFrameListener serverSessionFrameListener = new ServerSessionFrameListener.Adapter()
+        {
+            @Override
+            public void onSettings(Session session, SettingsInfo serverSettingsInfo)
+            {
+                Assert.assertEquals(clientSettingsInfo.getFlags(), serverSettingsInfo.getFlags());
+                Assert.assertEquals(clientSettingsInfo.getSettings(), serverSettingsInfo.getSettings());
+                latch.countDown();
+            }
+        };
+        Session session = startClient(startServer(serverSessionFrameListener), null);
+
+        session.settings(clientSettingsInfo);
+
+        Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testServerSettings() throws Exception
+    {
+        Settings settings = new Settings();
+        settings.put(new Settings.Setting(Settings.ID.UPLOAD_BANDWIDTH, 1024 * 1024));
+        settings.put(new Settings.Setting(Settings.ID.DOWNLOAD_BANDWIDTH, 1024 * 1024));
+        settings.put(new Settings.Setting(Settings.ID.CURRENT_CONGESTION_WINDOW, Settings.Flag.PERSIST, 1024));
+        final SettingsInfo serverSettingsInfo = new SettingsInfo(settings);
+        ServerSessionFrameListener serverSessionFrameListener = new ServerSessionFrameListener.Adapter()
+        {
+            @Override
+            public void onConnect(Session session)
+            {
+                session.settings(serverSettingsInfo, new FutureCallback());
+            }
+        };
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        SessionFrameListener clientSessionFrameListener = new SessionFrameListener.Adapter()
+        {
+            @Override
+            public void onSettings(Session session, SettingsInfo clientSettingsInfo)
+            {
+                Assert.assertEquals(serverSettingsInfo.getFlags(), clientSettingsInfo.getFlags());
+                Assert.assertEquals(serverSettingsInfo.getSettings(), clientSettingsInfo.getSettings());
+                latch.countDown();
+            }
+        };
+
+        startClient(startServer(serverSessionFrameListener), clientSessionFrameListener);
+
+        Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testSettingIDIsTheSameInBothV2AndV3() throws Exception
+    {
+        final AtomicReference<SettingsInfo> v2 = new AtomicReference<>();
+        final AtomicReference<SettingsInfo> v3 = new AtomicReference<>();
+        final CountDownLatch settingsLatch = new CountDownLatch(2);
+        InetSocketAddress address = startServer(new ServerSessionFrameListener.Adapter()
+        {
+            private final AtomicInteger count = new AtomicInteger();
+
+            @Override
+            public void onSettings(Session session, SettingsInfo settingsInfo)
+            {
+                int count = this.count.incrementAndGet();
+                if (count == 1)
+                    v2.set(settingsInfo);
+                else if (count == 2)
+                    v3.set(settingsInfo);
+                else
+                    Assert.fail();
+                settingsLatch.countDown();
+            }
+        });
+
+        Settings settings = new Settings();
+        settings.put(new Settings.Setting(Settings.ID.INITIAL_WINDOW_SIZE, Settings.Flag.PERSIST, 0xC0_00));
+        SettingsInfo settingsInfo = new SettingsInfo(settings);
+
+        Session sessionV2 = startClient(address, null);
+        sessionV2.settings(settingsInfo);
+
+        Session sessionV3 = clientFactory.newSPDYClient(SPDY.V3).connect(address, null);
+        sessionV3.settings(settingsInfo);
+
+        Assert.assertTrue(settingsLatch.await(5, TimeUnit.SECONDS));
+        Assert.assertEquals(v2.get().getSettings(), v3.get().getSettings());
+    }
+}
diff --git a/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/SynDataReplyDataLoadTest.java b/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/SynDataReplyDataLoadTest.java
new file mode 100644
index 0000000..3571878
--- /dev/null
+++ b/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/SynDataReplyDataLoadTest.java
@@ -0,0 +1,288 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.spdy.server;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.eclipse.jetty.io.LeakTrackingByteBufferPool;
+import org.eclipse.jetty.io.MappedByteBufferPool;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.spdy.api.ByteBufferDataInfo;
+import org.eclipse.jetty.spdy.api.DataInfo;
+import org.eclipse.jetty.spdy.api.ReplyInfo;
+import org.eclipse.jetty.spdy.api.SPDY;
+import org.eclipse.jetty.spdy.api.Session;
+import org.eclipse.jetty.spdy.api.Stream;
+import org.eclipse.jetty.spdy.api.StreamFrameListener;
+import org.eclipse.jetty.spdy.api.StringDataInfo;
+import org.eclipse.jetty.spdy.api.SynInfo;
+import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener;
+import org.eclipse.jetty.spdy.client.SPDYClient;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.Fields;
+import org.eclipse.jetty.util.Promise;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.eclipse.jetty.util.thread.Scheduler;
+import org.junit.Assert;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
+public class SynDataReplyDataLoadTest extends AbstractTest
+{
+    private static final int TIMEOUT = 60 * 1000;
+    private static final Logger logger = Log.getLogger(SynDataReplyDataLoadTest.class);
+
+    @Test(timeout = TIMEOUT)
+    @Ignore("Test needs to be rewritten")
+    public void testSynDataReplyDataLoad() throws Exception
+    {
+        LeakTrackingByteBufferPool serverBufferPool = new LeakTrackingByteBufferPool(new MappedByteBufferPool.Tagged());
+        LeakTrackingByteBufferPool clientBufferPool = new LeakTrackingByteBufferPool(new MappedByteBufferPool.Tagged());
+
+        ServerSessionFrameListener listener = new ServerSessionFrameListener.Adapter()
+        {
+            @Override
+            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
+            {
+                stream.reply(new ReplyInfo(synInfo.getHeaders(), false), new Callback.Adapter());
+                return new StreamFrameListener.Adapter()
+                {
+                    @Override
+                    public void onData(Stream stream, DataInfo dataInfo)
+                    {
+                        ByteBuffer buffer = dataInfo.asByteBuffer(true);
+                        stream.data(new ByteBufferDataInfo(buffer, dataInfo.isClose()), new Callback.Adapter());
+                    }
+                };
+            }
+        };
+
+        short spdyVersion = SPDY.V2;
+        long idleTimeout = 2 * TIMEOUT;
+
+        server = newServer();
+        connector = new ServerConnector(server, null, null, serverBufferPool, 1,
+                Math.max(1, Runtime.getRuntime().availableProcessors() / 2),
+                new SPDYServerConnectionFactory(spdyVersion, listener));
+        connector.setIdleTimeout(idleTimeout);
+
+        QueuedThreadPool clientExecutor = new QueuedThreadPool();
+        clientExecutor.setName(clientExecutor.getName() + "-client");
+        clientFactory = new SPDYClient.Factory(clientExecutor, null, clientBufferPool, null, idleTimeout);
+        final Session session = startClient(spdyVersion, startServer(spdyVersion, listener), null);
+
+        final Thread testThread = Thread.currentThread();
+        Runnable timeout = new Runnable()
+        {
+            @Override
+            public void run()
+            {
+                logger.warn("Interrupting test, it is taking too long");
+                logger.warn("SERVER: {}", server.dump());
+                logger.warn("CLIENT: {}", clientFactory.dump());
+                testThread.interrupt();
+            }
+        };
+
+        final int iterations = 500;
+        final int count = 50;
+
+        final Fields headers = new Fields();
+        headers.put("method", "get");
+        headers.put("url", "/");
+        headers.put("version", "http/1.1");
+        headers.put("host", "localhost:8080");
+        headers.put("content-type", "application/octet-stream");
+
+        final CountDownLatch latch = new CountDownLatch(count * iterations);
+        session.addListener(new Session.StreamListener.Adapter()
+        {
+            @Override
+            public void onStreamClosed(Stream stream)
+            {
+                latch.countDown();
+            }
+        });
+
+        ExecutorService threadPool = Executors.newFixedThreadPool(count);
+        List<Callable<Object>> tasks = new ArrayList<>();
+
+        tasks.clear();
+        for (int i = 0; i < count; ++i)
+        {
+            tasks.add(new Callable<Object>()
+            {
+                @Override
+                public Object call() throws Exception
+                {
+                    synGetDataGet(session, headers, iterations);
+                    return null;
+                }
+            });
+        }
+        Scheduler.Task syncTimeoutTask = clientFactory.getScheduler().schedule(timeout, TIMEOUT / 2, TimeUnit.MILLISECONDS);
+        {
+            long begin = System.nanoTime();
+            List<Future<Object>> futures = threadPool.invokeAll(tasks);
+            for (Future<Object> future : futures)
+                future.get(iterations, TimeUnit.SECONDS);
+            Assert.assertTrue(latch.await(count * iterations, TimeUnit.SECONDS));
+            long end = System.nanoTime();
+            System.err.printf("SYN+GET+DATA+GET completed in %d ms%n", TimeUnit.NANOSECONDS.toMillis(end - begin));
+        }
+        syncTimeoutTask.cancel();
+
+        tasks.clear();
+        for (int i = 0; i < count; ++i)
+        {
+            tasks.add(new Callable<Object>()
+            {
+                @Override
+                public Object call() throws Exception
+                {
+                    synCompletedData(session, headers, iterations);
+                    return null;
+                }
+            });
+        }
+        Scheduler.Task asyncTimeoutTask = clientFactory.getScheduler().schedule(timeout, TIMEOUT / 2, TimeUnit.MILLISECONDS);
+        {
+            long begin = System.nanoTime();
+            List<Future<Object>> futures = threadPool.invokeAll(tasks);
+            for (Future<Object> future : futures)
+                future.get(iterations, TimeUnit.SECONDS);
+            Assert.assertTrue(latch.await(count * iterations, TimeUnit.SECONDS));
+            long end = System.nanoTime();
+            System.err.printf("SYN+COMPLETED+DATA completed in %d ms%n", TimeUnit.NANOSECONDS.toMillis(end - begin));
+        }
+        asyncTimeoutTask.cancel();
+
+        threadPool.shutdown();
+
+        System.gc();
+
+        assertThat("Server BufferPool - leaked acquires", serverBufferPool.getLeakedAcquires(), is(0L));
+        assertThat("Server BufferPool - leaked releases", serverBufferPool.getLeakedReleases(), is(0L));
+        assertThat("Server BufferPool - unreleased", serverBufferPool.getLeakedResources(), is(0L));
+        
+        assertThat("Client BufferPool - leaked acquires", clientBufferPool.getLeakedAcquires(), is(0L));
+        assertThat("Client BufferPool - leaked releases", clientBufferPool.getLeakedReleases(), is(0L));
+        assertThat("Client BufferPool - unreleased", clientBufferPool.getLeakedResources(), is(0L));
+    }
+
+    private void synCompletedData(Session session, Fields headers, int iterations) throws Exception
+    {
+        final Map<Integer, Integer> counter = new ConcurrentHashMap<>(iterations);
+        final CountDownLatch requestsLatch = new CountDownLatch(2 * iterations);
+        for (int i = 0; i < iterations; ++i)
+        {
+            final AtomicInteger count = new AtomicInteger(2);
+            final int index = i;
+            counter.put(index, index);
+            session.syn(new SynInfo(headers, false), new StreamFrameListener.Adapter()
+                    {
+                        @Override
+                        public void onReply(Stream stream, ReplyInfo replyInfo)
+                        {
+                            Assert.assertEquals(2, count.getAndDecrement());
+                            requestsLatch.countDown();
+                        }
+
+                        @Override
+                        public void onData(Stream stream, DataInfo dataInfo)
+                        {
+                            // TCP can split the data frames, so I may be receiving more than 1 data frame
+                            dataInfo.asBytes(true);
+                            if (dataInfo.isClose())
+                            {
+                                Assert.assertEquals(1, count.getAndDecrement());
+                                counter.remove(index);
+                                requestsLatch.countDown();
+                            }
+                        }
+                    }, new Promise.Adapter<Stream>()
+                    {
+                        @Override
+                        public void succeeded(Stream stream)
+                        {
+                            stream.data(new StringDataInfo("data_" + stream.getId(), true),
+                                    new Callback.Adapter());
+                        }
+                    }
+            );
+        }
+        Assert.assertTrue(requestsLatch.await(iterations, TimeUnit.SECONDS));
+        Assert.assertTrue(counter.toString(), counter.isEmpty());
+    }
+
+    private void synGetDataGet(Session session, Fields headers, int iterations) throws Exception
+    {
+        final Map<Integer, Integer> counter = new ConcurrentHashMap<>(iterations);
+        final CountDownLatch latch = new CountDownLatch(2 * iterations);
+        for (int i = 0; i < iterations; ++i)
+        {
+            final AtomicInteger count = new AtomicInteger(2);
+            final int index = i;
+            counter.put(index, index);
+            Stream stream = session.syn(new SynInfo(5, TimeUnit.SECONDS, headers, false, (byte)0),
+                    new StreamFrameListener.Adapter()
+                    {
+                        @Override
+                        public void onReply(Stream stream, ReplyInfo replyInfo)
+                        {
+                            Assert.assertEquals(2, count.getAndDecrement());
+                            latch.countDown();
+                        }
+
+                        @Override
+                        public void onData(Stream stream, DataInfo dataInfo)
+                        {
+                            // TCP can split the data frames, so I may be receiving more than 1 data frame
+                            dataInfo.asBytes(true);
+                            if (dataInfo.isClose())
+                            {
+                                Assert.assertEquals(1, count.getAndDecrement());
+                                counter.remove(index);
+                                latch.countDown();
+                            }
+                        }
+                    });
+            stream.data(new StringDataInfo(5, TimeUnit.SECONDS, "data_" + stream.getId(), true));
+        }
+        Assert.assertTrue(latch.await(iterations, TimeUnit.SECONDS));
+        Assert.assertTrue(counter.toString(), counter.isEmpty());
+    }
+}
diff --git a/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/SynReplyTest.java b/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/SynReplyTest.java
new file mode 100644
index 0000000..993b593
--- /dev/null
+++ b/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/SynReplyTest.java
@@ -0,0 +1,375 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.spdy.server;
+
+import java.io.ByteArrayOutputStream;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.jetty.spdy.api.BytesDataInfo;
+import org.eclipse.jetty.spdy.api.DataInfo;
+import org.eclipse.jetty.spdy.api.ReplyInfo;
+import org.eclipse.jetty.spdy.api.Session;
+import org.eclipse.jetty.spdy.api.SessionFrameListener;
+import org.eclipse.jetty.spdy.api.Stream;
+import org.eclipse.jetty.spdy.api.StreamFrameListener;
+import org.eclipse.jetty.spdy.api.StringDataInfo;
+import org.eclipse.jetty.spdy.api.SynInfo;
+import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.Fields;
+import org.eclipse.jetty.util.Promise;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class SynReplyTest extends AbstractTest
+{
+    @Test
+    public void testSynReply() throws Exception
+    {
+        final AtomicReference<Session> sessionRef = new AtomicReference<>();
+        final CountDownLatch sessionLatch = new CountDownLatch(1);
+        final CountDownLatch synLatch = new CountDownLatch(1);
+        ServerSessionFrameListener serverSessionFrameListener = new ServerSessionFrameListener.Adapter()
+        {
+            @Override
+            public void onConnect(Session session)
+            {
+                sessionRef.set(session);
+                sessionLatch.countDown();
+            }
+
+            @Override
+            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
+            {
+                Assert.assertTrue(stream.isHalfClosed());
+                stream.reply(new ReplyInfo(new Fields(), true), new Callback.Adapter());
+                synLatch.countDown();
+                return null;
+            }
+        };
+
+        Session session = startClient(startServer(serverSessionFrameListener), null);
+
+        Assert.assertTrue(sessionLatch.await(5, TimeUnit.SECONDS));
+        Session serverSession = sessionRef.get();
+        Assert.assertNotNull(serverSession);
+
+        final CountDownLatch streamCreatedLatch = new CountDownLatch(1);
+        final CountDownLatch streamRemovedLatch = new CountDownLatch(1);
+        session.addListener(new Session.StreamListener()
+        {
+            @Override
+            public void onStreamCreated(Stream stream)
+            {
+                streamCreatedLatch.countDown();
+            }
+
+            @Override
+            public void onStreamClosed(Stream stream)
+            {
+                streamRemovedLatch.countDown();
+            }
+        });
+
+        final CountDownLatch replyLatch = new CountDownLatch(1);
+        Stream stream = session.syn(new SynInfo(5, TimeUnit.SECONDS, new Fields(), true, (byte)0),
+                new StreamFrameListener.Adapter()
+        {
+            @Override
+            public void onReply(Stream stream, ReplyInfo replyInfo)
+            {
+                Assert.assertTrue(stream.isClosed());
+                replyLatch.countDown();
+            }
+        });
+
+        Assert.assertTrue(synLatch.await(5, TimeUnit.SECONDS));
+
+        Assert.assertTrue(streamCreatedLatch.await(5, TimeUnit.SECONDS));
+        Assert.assertTrue(replyLatch.await(5, TimeUnit.SECONDS));
+        Assert.assertTrue(stream.isClosed());
+
+        Assert.assertTrue(streamRemovedLatch.await(5, TimeUnit.SECONDS));
+        Assert.assertEquals(0, session.getStreams().size());
+    }
+
+    @Test
+    public void testSynDataReply() throws Exception
+    {
+        final byte[] dataBytes = "foo".getBytes(StandardCharsets.UTF_8);
+
+        final CountDownLatch synLatch = new CountDownLatch(1);
+        final CountDownLatch dataLatch = new CountDownLatch(1);
+        ServerSessionFrameListener serverSessionFrameListener = new ServerSessionFrameListener.Adapter()
+        {
+            @Override
+            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
+            {
+                Assert.assertFalse(stream.isHalfClosed());
+                Assert.assertFalse(stream.isClosed());
+                synLatch.countDown();
+                return new StreamFrameListener.Adapter()
+                {
+                    @Override
+                    public void onData(Stream stream, DataInfo dataInfo)
+                    {
+                        ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+                        ByteBuffer buffer = ByteBuffer.allocate(2);
+                        while (dataInfo.available() > 0)
+                        {
+                            dataInfo.readInto(buffer);
+                            buffer.flip();
+                            bytes.write(buffer.array(), buffer.arrayOffset(), buffer.remaining());
+                            buffer.clear();
+                        }
+                        Assert.assertTrue(Arrays.equals(dataBytes, bytes.toByteArray()));
+                        Assert.assertTrue(stream.isHalfClosed());
+                        Assert.assertFalse(stream.isClosed());
+
+                        stream.reply(new ReplyInfo(true), new Callback.Adapter());
+                        Assert.assertTrue(stream.isClosed());
+                        dataLatch.countDown();
+                    }
+                };
+            }
+        };
+
+        Session session = startClient(startServer(serverSessionFrameListener), null);
+
+        final CountDownLatch streamRemovedLatch = new CountDownLatch(1);
+        session.addListener(new Session.StreamListener.Adapter()
+        {
+            @Override
+            public void onStreamClosed(Stream stream)
+            {
+                streamRemovedLatch.countDown();
+            }
+        });
+
+        final CountDownLatch replyLatch = new CountDownLatch(1);
+        Stream stream = session.syn(new SynInfo(5, TimeUnit.SECONDS, new Fields(), false, (byte)0),
+                new StreamFrameListener.Adapter()
+        {
+            @Override
+            public void onReply(Stream stream, ReplyInfo replyInfo)
+            {
+                replyLatch.countDown();
+            }
+        });
+        stream.data(new BytesDataInfo(dataBytes, true));
+
+        Assert.assertTrue(synLatch.await(5, TimeUnit.SECONDS));
+
+        Assert.assertTrue(dataLatch.await(5, TimeUnit.SECONDS));
+        Assert.assertTrue(replyLatch.await(5, TimeUnit.SECONDS));
+
+        Assert.assertTrue(streamRemovedLatch.await(5, TimeUnit.SECONDS));
+        Assert.assertEquals(0, session.getStreams().size());
+    }
+
+    @Test
+    public void testSynReplyDataData() throws Exception
+    {
+        final String data1 = "foo";
+        final String data2 = "bar";
+        Session session = startClient(startServer(new ServerSessionFrameListener.Adapter()
+        {
+            @Override
+            public StreamFrameListener onSyn(final Stream stream, SynInfo synInfo)
+            {
+                Assert.assertTrue(stream.isHalfClosed());
+
+                stream.reply(new ReplyInfo(false), new Callback.Adapter());
+                stream.data(new StringDataInfo(5, TimeUnit.SECONDS, data1, false), new Callback.Adapter()
+                {
+                    @Override
+                    public void succeeded()
+                    {
+                        stream.data(new StringDataInfo(data2, true), new Adapter());
+                    }
+                });
+
+                return null;
+            }
+        }), null);
+
+        final CountDownLatch replyLatch = new CountDownLatch(1);
+        final CountDownLatch dataLatch1 = new CountDownLatch(1);
+        final CountDownLatch dataLatch2 = new CountDownLatch(1);
+        session.syn(new SynInfo(new Fields(), true), new StreamFrameListener.Adapter()
+        {
+            private AtomicInteger dataCount = new AtomicInteger();
+
+            @Override
+            public void onReply(Stream stream, ReplyInfo replyInfo)
+            {
+                Assert.assertFalse(replyInfo.isClose());
+                replyLatch.countDown();
+            }
+
+            @Override
+            public void onData(Stream stream, DataInfo dataInfo)
+            {
+                int dataCount = this.dataCount.incrementAndGet();
+                if (dataCount == 1)
+                {
+                    String chunk1 = dataInfo.asString(StandardCharsets.UTF_8, true);
+                    Assert.assertEquals(data1, chunk1);
+                    dataLatch1.countDown();
+                }
+                else if (dataCount == 2)
+                {
+                    String chunk2 = dataInfo.asString(StandardCharsets.UTF_8, true);
+                    Assert.assertEquals(data2, chunk2);
+                    dataLatch2.countDown();
+                }
+            }
+        });
+
+        Assert.assertTrue(replyLatch.await(5, TimeUnit.SECONDS));
+        Assert.assertTrue(dataLatch1.await(5, TimeUnit.SECONDS));
+        Assert.assertTrue(dataLatch2.await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testServerSynDataReplyData() throws Exception
+    {
+        final String serverData = "server";
+        final String clientData = "client";
+
+        final CountDownLatch replyLatch = new CountDownLatch(1);
+        final CountDownLatch clientDataLatch = new CountDownLatch(1);
+        ServerSessionFrameListener serverSessionFrameListener = new ServerSessionFrameListener.Adapter()
+        {
+            @Override
+            public void onConnect(Session session)
+            {
+                session.syn(new SynInfo(new Fields(), false), new StreamFrameListener.Adapter()
+                {
+                    @Override
+                    public void onReply(Stream stream, ReplyInfo replyInfo)
+                    {
+                        replyLatch.countDown();
+                    }
+
+                    @Override
+                    public void onData(Stream stream, DataInfo dataInfo)
+                    {
+                        String data = dataInfo.asString(StandardCharsets.UTF_8, true);
+                        Assert.assertEquals(clientData, data);
+                        clientDataLatch.countDown();
+                    }
+                }, new Promise.Adapter<Stream>()
+                {
+                    @Override
+                    public void succeeded(Stream stream)
+                    {
+                        stream.data(new StringDataInfo(serverData, true), new Callback.Adapter());
+                    }
+                });
+            }
+        };
+
+        final CountDownLatch synLatch = new CountDownLatch(1);
+        final CountDownLatch serverDataLatch = new CountDownLatch(1);
+        SessionFrameListener clientSessionFrameListener = new SessionFrameListener.Adapter()
+        {
+            @Override
+            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
+            {
+                Assert.assertEquals(0, stream.getId() % 2);
+
+                stream.reply(new ReplyInfo(false), new Callback.Adapter());
+                stream.data(new StringDataInfo(clientData, true), new Callback.Adapter());
+                synLatch.countDown();
+
+                return new StreamFrameListener.Adapter()
+                {
+                    @Override
+                    public void onData(Stream stream, DataInfo dataInfo)
+                    {
+                        ByteBuffer buffer = dataInfo.asByteBuffer(false);
+                        String data = StandardCharsets.UTF_8.decode(buffer).toString();
+                        Assert.assertEquals(serverData, data);
+                        serverDataLatch.countDown();
+                    }
+                };
+            }
+        };
+
+        startClient(startServer(serverSessionFrameListener), clientSessionFrameListener);
+
+        Assert.assertTrue(synLatch.await(5, TimeUnit.SECONDS));
+        Assert.assertTrue(replyLatch.await(5, TimeUnit.SECONDS));
+        Assert.assertTrue(serverDataLatch.await(5, TimeUnit.SECONDS));
+        Assert.assertTrue(clientDataLatch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testSynReplyDataSynReplyData() throws Exception
+    {
+        final String data = "foo";
+        ServerSessionFrameListener serverSessionFrameListener = new ServerSessionFrameListener.Adapter()
+        {
+            @Override
+            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
+            {
+                Assert.assertTrue(stream.isHalfClosed());
+
+                stream.reply(new ReplyInfo(false), new Callback.Adapter());
+                stream.data(new StringDataInfo(data, true), new Callback.Adapter());
+
+                return null;
+            }
+        };
+
+        Session session = startClient(startServer(serverSessionFrameListener), null);
+
+        final CountDownLatch replyLatch = new CountDownLatch(2);
+        final CountDownLatch dataLatch = new CountDownLatch(2);
+        StreamFrameListener clientStreamFrameListener = new StreamFrameListener.Adapter()
+        {
+            @Override
+            public void onReply(Stream stream, ReplyInfo replyInfo)
+            {
+                Assert.assertFalse(replyInfo.isClose());
+                replyLatch.countDown();
+            }
+
+            @Override
+            public void onData(Stream stream, DataInfo dataInfo)
+            {
+                String chunk = dataInfo.asString(StandardCharsets.UTF_8, true);
+                Assert.assertEquals(data, chunk);
+                dataLatch.countDown();
+            }
+        };
+        session.syn(new SynInfo(new Fields(), true), clientStreamFrameListener);
+        session.syn(new SynInfo(new Fields(), true), clientStreamFrameListener);
+
+        Assert.assertTrue(replyLatch.await(5, TimeUnit.SECONDS));
+        Assert.assertTrue(dataLatch.await(5, TimeUnit.SECONDS));
+    }
+}
diff --git a/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/UnsupportedVersionTest.java b/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/UnsupportedVersionTest.java
new file mode 100644
index 0000000..d755ee3
--- /dev/null
+++ b/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/UnsupportedVersionTest.java
@@ -0,0 +1,100 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.spdy.server;
+
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.channels.SocketChannel;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.io.MappedByteBufferPool;
+import org.eclipse.jetty.spdy.StandardCompressionFactory;
+import org.eclipse.jetty.spdy.api.SPDY;
+import org.eclipse.jetty.spdy.api.Session;
+import org.eclipse.jetty.spdy.api.Stream;
+import org.eclipse.jetty.spdy.api.StreamFrameListener;
+import org.eclipse.jetty.spdy.api.StreamStatus;
+import org.eclipse.jetty.spdy.api.SynInfo;
+import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener;
+import org.eclipse.jetty.spdy.frames.ControlFrame;
+import org.eclipse.jetty.spdy.frames.ControlFrameType;
+import org.eclipse.jetty.spdy.frames.RstStreamFrame;
+import org.eclipse.jetty.spdy.frames.SynStreamFrame;
+import org.eclipse.jetty.spdy.generator.Generator;
+import org.eclipse.jetty.spdy.parser.Parser;
+import org.eclipse.jetty.util.Fields;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class UnsupportedVersionTest extends AbstractTest
+{
+    @Test
+    public void testSynWithUnsupportedVersion() throws Exception
+    {
+        final CountDownLatch synLatch = new CountDownLatch(1);
+        InetSocketAddress address = startServer(new ServerSessionFrameListener.Adapter()
+        {
+            @Override
+            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
+            {
+                synLatch.countDown();
+                return null;
+            }
+
+            @Override
+            public void onFailure(Session session, Throwable x)
+            {
+                // Suppress exception logging for this test
+            }
+        });
+
+        SynStreamFrame frame = new SynStreamFrame(SPDY.V2, SynInfo.FLAG_CLOSE, 1, 0, (byte)0, (short)0, new Fields());
+        Generator generator = new Generator(new MappedByteBufferPool(), new StandardCompressionFactory.StandardCompressor());
+        ByteBuffer buffer = generator.control(frame);
+        // Replace the version byte with an unsupported version
+        buffer.putShort(0, (short)0x8001);
+
+        SocketChannel channel = SocketChannel.open(address);
+        channel.write(buffer);
+        Assert.assertFalse(buffer.hasRemaining());
+
+        Assert.assertFalse(synLatch.await(1, TimeUnit.SECONDS));
+
+        buffer = ByteBuffer.allocate(1024);
+        channel.read(buffer);
+        buffer.flip();
+
+        final CountDownLatch rstLatch = new CountDownLatch(1);
+        Parser parser = new Parser(new StandardCompressionFactory.StandardDecompressor());
+        parser.addListener(new Parser.Listener.Adapter()
+        {
+            @Override
+            public void onControlFrame(ControlFrame frame)
+            {
+                Assert.assertSame(ControlFrameType.RST_STREAM, frame.getType());
+                Assert.assertEquals(StreamStatus.UNSUPPORTED_VERSION.getCode(frame.getVersion()), ((RstStreamFrame)frame).getStatusCode());
+                rstLatch.countDown();
+            }
+        });
+        parser.parse(buffer);
+
+        Assert.assertTrue(rstLatch.await(5, TimeUnit.SECONDS));
+    }
+}
diff --git a/jetty-spdy/spdy-server/src/test/resources/jetty-logging.properties b/jetty-spdy/spdy-server/src/test/resources/jetty-logging.properties
new file mode 100644
index 0000000..ead13ec
--- /dev/null
+++ b/jetty-spdy/spdy-server/src/test/resources/jetty-logging.properties
@@ -0,0 +1,2 @@
+org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
+#org.eclipse.jetty.spdy.LEVEL=DEBUG
diff --git a/jetty-spring/pom.xml b/jetty-spring/pom.xml
new file mode 100644
index 0000000..d4c2965
--- /dev/null
+++ b/jetty-spring/pom.xml
@@ -0,0 +1,66 @@
+<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">
+  <parent>
+    <groupId>org.eclipse.jetty</groupId>
+    <artifactId>jetty-project</artifactId>
+    <version>9.2.13.v20150730</version>
+  </parent>
+  <modelVersion>4.0.0</modelVersion>
+  <artifactId>jetty-spring</artifactId>
+  <name>Example :: Jetty Spring</name>
+
+  <properties>
+    <spring-version>3.2.8.RELEASE</spring-version>
+    <dependencies>target/dependencies</dependencies>
+  </properties>
+
+  <build>
+    <defaultGoal>install</defaultGoal>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-assembly-plugin</artifactId>
+        <executions>
+          <execution>
+            <phase>package</phase>
+            <goals>
+              <goal>single</goal>
+            </goals>
+            <configuration>
+              <descriptorRefs>
+                <descriptorRef>config</descriptorRef>
+              </descriptorRefs>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+  <dependencies>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-xml</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-util</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.springframework</groupId>
+      <artifactId>spring-beans</artifactId>
+      <version>${spring-version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-server</artifactId>
+      <version>${project.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.toolchain</groupId>
+      <artifactId>jetty-test-helper</artifactId>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/jetty-spring/src/main/config/etc/jetty-spring.xml b/jetty-spring/src/main/config/etc/jetty-spring.xml
new file mode 100644
index 0000000..637450d
--- /dev/null
+++ b/jetty-spring/src/main/config/etc/jetty-spring.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
+
+<!-- =============================================================== -->
+<!-- Configure the Jetty Server with Spring                          -->
+<!-- This file is the similar to jetty.xml, but written in spring    -->
+<!-- XmlBeanFactory format.                                          -->
+<!-- =============================================================== -->
+
+<beans>
+
+  <bean id="contexts" class="org.eclipse.jetty.server.handler.ContextHandlerCollection"/>
+
+  <bean id="server" name="Main" class="org.eclipse.jetty.server.Server" init-method="start" destroy-method="stop">
+    <constructor-arg>
+      <bean id="threadPool" class="org.eclipse.jetty.util.thread.QueuedThreadPool">
+        <property name="minThreads" value="10"/>
+        <property name="maxThreads" value="50"/>
+      </bean>
+    </constructor-arg>
+
+    <property name="connectors">
+      <list>
+        <bean id="connector" class="org.eclipse.jetty.server.ServerConnector">
+          <constructor-arg ref="server"/>
+          <property name="port" value="8080"/>
+        </bean>
+      </list>
+    </property>
+
+    <property name="handler">
+      <bean id="handlers" class="org.eclipse.jetty.server.handler.HandlerCollection">
+        <property name="handlers">
+          <list>
+            <ref bean="contexts"/>
+            <bean id="defaultHandler" class="org.eclipse.jetty.server.handler.DefaultHandler"/>
+          </list>
+        </property>
+      </bean>
+    </property>
+
+    <property name="beans">
+      <list>
+        <bean id="deploymentManager" class="org.eclipse.jetty.deploy.DeploymentManager">
+          <property name="contexts" ref="contexts"/>
+          <property name="appProviders">
+            <list>
+              <bean id="webAppProvider" class="org.eclipse.jetty.deploy.providers.WebAppProvider">
+                <property name="monitoredDirName" value="webapps"/>
+                <property name="scanInterval" value="1"/>
+                <property name="extractWars" value="true"/>
+              </bean>
+            </list>
+          </property>
+        </bean>
+      </list>
+    </property>
+
+  </bean>
+
+</beans>
diff --git a/jetty-spring/src/main/config/modules/spring.mod b/jetty-spring/src/main/config/modules/spring.mod
new file mode 100644
index 0000000..444afb2
--- /dev/null
+++ b/jetty-spring/src/main/config/modules/spring.mod
@@ -0,0 +1,16 @@
+#
+# Spring
+#
+[name]
+spring
+
+[depend]
+server
+
+[lib]
+lib/spring/*.jar
+
+[ini-template]
+## See http://www.eclipse.org/jetty/documentation/current/frameworks.html#framework-jetty-spring
+## for information on how to complete spring configuration
+
diff --git a/jetty-spring/src/main/java/org/eclipse/jetty/spring/Main.java b/jetty-spring/src/main/java/org/eclipse/jetty/spring/Main.java
new file mode 100644
index 0000000..5d9dbe3
--- /dev/null
+++ b/jetty-spring/src/main/java/org/eclipse/jetty/spring/Main.java
@@ -0,0 +1,34 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.spring;
+
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.xml.XmlConfiguration;
+
+/**
+ * Runs Jetty from a Spring configuration file passed as argument.
+ */
+public class Main
+{
+    public static void main(String[] args) throws Exception
+    {
+        Resource config = Resource.newResource(args.length == 1 ? args[0] : "etc/jetty-spring.xml");
+        XmlConfiguration.main(config.getFile().getAbsolutePath());
+    }
+}
diff --git a/jetty-spring/src/main/java/org/eclipse/jetty/spring/SpringConfigurationProcessor.java b/jetty-spring/src/main/java/org/eclipse/jetty/spring/SpringConfigurationProcessor.java
new file mode 100644
index 0000000..52dddc0
--- /dev/null
+++ b/jetty-spring/src/main/java/org/eclipse/jetty/spring/SpringConfigurationProcessor.java
@@ -0,0 +1,164 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.spring;
+
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.ServiceLoader;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.xml.ConfigurationProcessor;
+import org.eclipse.jetty.xml.ConfigurationProcessorFactory;
+import org.eclipse.jetty.xml.XmlConfiguration;
+import org.eclipse.jetty.xml.XmlParser;
+import org.springframework.beans.BeanWrapper;
+import org.springframework.beans.PropertyValues;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.beans.factory.support.DefaultListableBeanFactory;
+import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
+import org.springframework.beans.factory.xml.XmlBeanFactory;
+import org.springframework.core.io.ByteArrayResource;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.UrlResource;
+
+/**
+ * Spring ConfigurationProcessor
+ * <p/>
+ * A {@link ConfigurationProcessor} that uses a spring XML file to emulate the {@link XmlConfiguration} format.
+ * <p/>
+ * {@link XmlConfiguration} expects a primary object that is either passed in to a call to {@link #configure(Object)}
+ * or that is constructed by a call to {@link #configure()}. This processor looks for a bean definition
+ * with an id, name or alias of "Main" as uses that as the primary bean.
+ * <p/>
+ * The objects mapped by {@link XmlConfiguration#getIdMap()} are set as singletons before any configuration calls
+ * and if the spring configuration file contains a definition for the singleton id, the the singleton is updated
+ * with a call to {@link XmlBeanFactory#configureBean(Object, String)}.
+ * <p/>
+ * The property map obtained via {@link XmlConfiguration#getProperties()} is set as a singleton called "properties"
+ * and values can be accessed by somewhat verbose
+ * usage of {@link org.springframework.beans.factory.config.MethodInvokingFactoryBean}.
+ * <p/>
+ * This processor is returned by the {@link SpringConfigurationProcessorFactory} for any XML document whos first
+ * element is "beans". The factory is discovered by a {@link ServiceLoader} for {@link ConfigurationProcessorFactory}.
+ */
+public class SpringConfigurationProcessor implements ConfigurationProcessor
+{
+    private static final Logger LOG = Log.getLogger(SpringConfigurationProcessor.class);
+
+    private XmlConfiguration _configuration;
+    private DefaultListableBeanFactory _beanFactory;
+    private String _main;
+
+    @Override
+    public void init(URL url, XmlParser.Node config, XmlConfiguration configuration)
+    {
+        try
+        {
+            _configuration = configuration;
+
+            Resource resource = url != null
+                    ? new UrlResource(url)
+                    : new ByteArrayResource(("" +
+                    "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
+                    "<!DOCTYPE beans PUBLIC \"-//SPRING//DTD BEAN//EN\" \"http://www.springframework.org/dtd/spring-beans.dtd\">" +
+                    config).getBytes(StandardCharsets.UTF_8));
+
+            _beanFactory = new DefaultListableBeanFactory()
+            {
+                @Override
+                protected void applyPropertyValues(String beanName, BeanDefinition mbd, BeanWrapper bw, PropertyValues pvs)
+                {
+                    _configuration.initializeDefaults(bw.getWrappedInstance());
+                    super.applyPropertyValues(beanName, mbd, bw, pvs);
+                }
+            };
+
+            new XmlBeanDefinitionReader(_beanFactory).loadBeanDefinitions(resource);
+        }
+        catch (Exception e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public Object configure(Object obj) throws Exception
+    {
+        doConfigure();
+        return _beanFactory.configureBean(obj, _main);
+    }
+
+    /**
+     * Return a configured bean.  If a bean has the id or alias of "Main", then it is returned, otherwise the first bean in the file is returned.
+     *
+     * @see org.eclipse.jetty.xml.ConfigurationProcessor#configure()
+     */
+    @Override
+    public Object configure() throws Exception
+    {
+        doConfigure();
+        return _beanFactory.getBean(_main);
+    }
+
+    private void doConfigure()
+    {
+        _beanFactory.registerSingleton("properties", _configuration.getProperties());
+
+        // Look for the main bean;
+        for (String bean : _beanFactory.getBeanDefinitionNames())
+        {
+            LOG.debug("{} - {}", bean, Arrays.asList(_beanFactory.getAliases(bean)));
+            String[] aliases = _beanFactory.getAliases(bean);
+            if ("Main".equals(bean) || aliases != null && Arrays.asList(aliases).contains("Main"))
+            {
+                _main = bean;
+                break;
+            }
+        }
+        if (_main == null)
+            _main = _beanFactory.getBeanDefinitionNames()[0];
+
+        // Register id beans as singletons
+        Map<String, Object> idMap = _configuration.getIdMap();
+        LOG.debug("idMap {}", idMap);
+        for (String id : idMap.keySet())
+        {
+            LOG.debug("register {}", id);
+            _beanFactory.registerSingleton(id, idMap.get(id));
+        }
+
+        // Apply configuration to existing singletons
+        for (String id : idMap.keySet())
+        {
+            if (_beanFactory.containsBeanDefinition(id))
+            {
+                LOG.debug("reconfigure {}", id);
+                _beanFactory.configureBean(idMap.get(id), id);
+            }
+        }
+
+        // Extract id's for next time.
+        for (String id : _beanFactory.getSingletonNames())
+            idMap.put(id, _beanFactory.getBean(id));
+    }
+}
diff --git a/jetty-spring/src/main/java/org/eclipse/jetty/spring/SpringConfigurationProcessorFactory.java b/jetty-spring/src/main/java/org/eclipse/jetty/spring/SpringConfigurationProcessorFactory.java
new file mode 100644
index 0000000..a29366a
--- /dev/null
+++ b/jetty-spring/src/main/java/org/eclipse/jetty/spring/SpringConfigurationProcessorFactory.java
@@ -0,0 +1,42 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.spring;
+
+import org.eclipse.jetty.xml.ConfigurationProcessor;
+import org.eclipse.jetty.xml.ConfigurationProcessorFactory;
+import org.eclipse.jetty.xml.XmlConfiguration;
+
+/**
+ * Spring ConfigurationProcessor Factory
+ * <p/>
+ * Create a {@link SpringConfigurationProcessor} for XML documents with a "beans" element.
+ * The factory is discovered by a {@link java.util.ServiceLoader} for {@link ConfigurationProcessorFactory}.
+ *
+ * @see SpringConfigurationProcessor
+ * @see XmlConfiguration
+ */
+public class SpringConfigurationProcessorFactory implements ConfigurationProcessorFactory
+{
+    public ConfigurationProcessor getConfigurationProcessor(String dtd, String tag)
+    {
+        if ("beans".equals(tag))
+            return new SpringConfigurationProcessor();
+        return null;
+    }
+}
diff --git a/jetty-spring/src/main/java/org/eclipse/jetty/spring/package-info.java b/jetty-spring/src/main/java/org/eclipse/jetty/spring/package-info.java
new file mode 100644
index 0000000..d970d64
--- /dev/null
+++ b/jetty-spring/src/main/java/org/eclipse/jetty/spring/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Spring : Spring IoC Configuration for Jetty
+ */
+package org.eclipse.jetty.spring;
+
diff --git a/jetty-spring/src/main/resources/META-INF/services/org.eclipse.jetty.xml.ConfigurationProcessorFactory b/jetty-spring/src/main/resources/META-INF/services/org.eclipse.jetty.xml.ConfigurationProcessorFactory
new file mode 100644
index 0000000..0289454
--- /dev/null
+++ b/jetty-spring/src/main/resources/META-INF/services/org.eclipse.jetty.xml.ConfigurationProcessorFactory
@@ -0,0 +1 @@
+org.eclipse.jetty.spring.SpringConfigurationProcessorFactory
diff --git a/jetty-spring/src/test/java/org/eclipse/jetty/spring/SpringXmlConfigurationTest.java b/jetty-spring/src/test/java/org/eclipse/jetty/spring/SpringXmlConfigurationTest.java
new file mode 100644
index 0000000..e11b802
--- /dev/null
+++ b/jetty-spring/src/test/java/org/eclipse/jetty/spring/SpringXmlConfigurationTest.java
@@ -0,0 +1,162 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.spring;
+
+import static junit.framework.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.xml.XmlConfiguration;
+import org.junit.Before;
+import org.junit.Test;
+
+public class SpringXmlConfigurationTest
+{
+    protected String _configure="org/eclipse/jetty/spring/configure.xml";
+
+    @Before
+    public void init() throws Exception
+    {
+        // Jetty's XML configuration will make use of java.util.ServiceLoader
+        // to load the proper ConfigurationProcessorFactory, so these tests
+        // will always fail in JDK 5.
+
+        String javaVersion = System.getProperty("java.version");
+        Pattern regexp = Pattern.compile("1\\.(\\d{1})\\..*");
+        Matcher matcher = regexp.matcher(javaVersion);
+        if (matcher.matches())
+        {
+            String minor = matcher.group(1);
+            assumeTrue(Integer.parseInt(minor) > 5);
+        }
+    }
+
+    @Test
+    public void testPassedObject() throws Exception
+    {
+        TestConfiguration.VALUE=77;
+
+        URL url = SpringXmlConfigurationTest.class.getClassLoader().getResource(_configure);
+        XmlConfiguration configuration = new XmlConfiguration(url);
+
+        Map<String,String> properties = new HashMap<>();
+        properties.put("test", "xxx");
+
+        TestConfiguration nested = new TestConfiguration();
+        nested.setTestString0("nested");
+        configuration.getIdMap().put("nested",nested);
+
+        TestConfiguration tc = new TestConfiguration();
+        tc.setTestString0("preconfig");
+        tc.setTestInt0(42);
+        configuration.getProperties().putAll(properties);
+
+        tc=(TestConfiguration)configuration.configure(tc);
+
+        assertEquals("preconfig", tc.getTestString0());
+        assertEquals(42, tc.getTestInt0());
+        assertEquals("SetValue", tc.getTestString1());
+        assertEquals(1, tc.getTestInt1());
+
+        assertEquals("nested", tc.getNested().getTestString0());
+        assertEquals("nested", tc.getNested().getTestString1());
+        assertEquals("default", tc.getNested().getNested().getTestString0());
+        assertEquals("deep", tc.getNested().getNested().getTestString1());
+
+        assertEquals("deep", ((TestConfiguration)configuration.getIdMap().get("nestedDeep")).getTestString1());
+        assertEquals(2, ((TestConfiguration)configuration.getIdMap().get("nestedDeep")).getTestInt2());
+
+        assertEquals("xxx", tc.getTestString2());
+    }
+
+    @Test
+    public void testNewObject() throws Exception
+    {
+        final String newDefaultValue = "NEW DEFAULT";
+        TestConfiguration.VALUE=71;
+
+        URL url = SpringXmlConfigurationTest.class.getClassLoader().getResource(_configure);
+        final AtomicInteger count = new AtomicInteger(0);
+        XmlConfiguration configuration = new XmlConfiguration(url)
+        {
+            @Override
+            public void initializeDefaults(Object object)
+            {
+                super.initializeDefaults(object);
+                if (object instanceof TestConfiguration)
+                {
+                    count.incrementAndGet();
+                    ((TestConfiguration)object).setTestString0(newDefaultValue);
+                    ((TestConfiguration)object).setTestString1("WILL BE OVERRIDDEN");
+                }
+            }
+        };
+
+        Map<String,String> properties = new HashMap<String,String>();
+        properties.put("test", "xxx");
+
+        TestConfiguration nested = new TestConfiguration();
+        nested.setTestString0("nested");
+        configuration.getIdMap().put("nested", nested);
+
+        configuration.getProperties().putAll(properties);
+        TestConfiguration tc = (TestConfiguration)configuration.configure();
+
+        assertEquals(3,count.get());
+
+        assertEquals(newDefaultValue, tc.getTestString0());
+        assertEquals(-1, tc.getTestInt0());
+        assertEquals("SetValue", tc.getTestString1());
+        assertEquals(1, tc.getTestInt1());
+
+        assertEquals(newDefaultValue, tc.getNested().getTestString0());
+        assertEquals("nested", tc.getNested().getTestString1());
+        assertEquals(newDefaultValue, tc.getNested().getNested().getTestString0());
+        assertEquals("deep", tc.getNested().getNested().getTestString1());
+
+        assertEquals("deep", ((TestConfiguration)configuration.getIdMap().get("nestedDeep")).getTestString1());
+        assertEquals(2, ((TestConfiguration)configuration.getIdMap().get("nestedDeep")).getTestInt2());
+
+        assertEquals("xxx", tc.getTestString2());
+    }
+
+    @Test
+    public void testJettyXml() throws Exception
+    {
+        URL url = SpringXmlConfigurationTest.class.getClassLoader().getResource("org/eclipse/jetty/spring/jetty.xml");
+        XmlConfiguration configuration = new XmlConfiguration(url);
+
+        Server server = (Server)configuration.configure();
+
+        server.dumpStdErr();
+    }
+
+    @Test
+    public void XmlConfigurationMain() throws Exception
+    {
+        XmlConfiguration.main("src/test/resources/org/eclipse/jetty/spring/jetty.xml");
+    }
+}
diff --git a/jetty-spring/src/test/java/org/eclipse/jetty/spring/TestConfiguration.java b/jetty-spring/src/test/java/org/eclipse/jetty/spring/TestConfiguration.java
new file mode 100644
index 0000000..06d4e82
--- /dev/null
+++ b/jetty-spring/src/test/java/org/eclipse/jetty/spring/TestConfiguration.java
@@ -0,0 +1,155 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.spring;
+
+import java.net.URL;
+
+import org.junit.Ignore;
+
+ at Ignore
+public class TestConfiguration
+{
+    public static int VALUE = 77;
+
+    public TestConfiguration nested;
+    public String testString0 = "default";
+    public String testString1;
+    public String testString2;
+    public int testInt0 = -1;
+    public int testInt1;
+    public int testInt2;
+    public URL url;
+    public Object[] objArray;
+    public int[] intArray;
+
+
+    public static int getVALUE()
+    {
+        return VALUE;
+    }
+
+    public static void setVALUE(int vALUE)
+    {
+        VALUE = vALUE;
+    }
+
+    public TestConfiguration()
+    {
+    }
+
+    public TestConfiguration getNested()
+    {
+        return nested;
+    }
+
+    public void setNested(TestConfiguration nested)
+    {
+        this.nested = nested;
+    }
+
+    public String getTestString0()
+    {
+        return testString0;
+    }
+
+    public void setTestString0(String testString0)
+    {
+        this.testString0 = testString0;
+    }
+
+    public String getTestString1()
+    {
+        return testString1;
+    }
+
+    public void setTestString1(String testString1)
+    {
+        this.testString1 = testString1;
+    }
+
+    public String getTestString2()
+    {
+        return testString2;
+    }
+
+    public void setTestString2(String testString2)
+    {
+        this.testString2 = testString2;
+    }
+
+    public int getTestInt0()
+    {
+        return testInt0;
+    }
+
+    public void setTestInt0(int testInt0)
+    {
+        this.testInt0 = testInt0;
+    }
+
+    public int getTestInt1()
+    {
+        return testInt1;
+    }
+
+    public void setTestInt1(int testInt1)
+    {
+        this.testInt1 = testInt1;
+    }
+
+    public int getTestInt2()
+    {
+        return testInt2;
+    }
+
+    public void setTestInt2(int testInt2)
+    {
+        this.testInt2 = testInt2;
+    }
+
+    public URL getUrl()
+    {
+        return url;
+    }
+
+    public void setUrl(URL url)
+    {
+        this.url = url;
+    }
+
+    public Object[] getObjArray()
+    {
+        return objArray;
+    }
+
+    public void setObjArray(Object[] objArray)
+    {
+        this.objArray = objArray;
+    }
+
+    public int[] getIntArray()
+    {
+        return intArray;
+    }
+
+    public void setIntArray(int[] intArray)
+    {
+        this.intArray = intArray;
+    }
+}
diff --git a/jetty-spring/src/test/resources/org/eclipse/jetty/spring/configure.xml b/jetty-spring/src/test/resources/org/eclipse/jetty/spring/configure.xml
new file mode 100644
index 0000000..978c1df
--- /dev/null
+++ b/jetty-spring/src/test/resources/org/eclipse/jetty/spring/configure.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+      xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
+
+  <!-- define the singleton properties Map, filled in with XmlConfiguration.getProperties() -->
+  <bean id="properties" class="java.util.Map"/>
+
+  <!-- extract a value from the property map -->
+  <bean id="testProperty" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
+    <property name="targetObject"><ref local="properties" /></property>
+    <property name="targetMethod" value="get" />
+    <property name="arguments"><list><value>test</value></list></property>
+  </bean>
+
+  <bean id="root" name="Some,Names,Main" class="org.eclipse.jetty.spring.TestConfiguration">
+    <property name="testString1" value="SetValue" />
+    <property name="testInt1" value="1" />
+    <property name="nested" ref="nested" />
+    <property name="testString2" ref="testProperty"/>
+  </bean>
+
+  <bean id="nested" class="org.eclipse.jetty.spring.TestConfiguration">
+    <property name="testInt2" value="2" />
+    <property name="testString1" value="nested" />
+    <property name="nested" ref="nestedDeep" />
+  </bean>
+
+  <bean id="nestedDeep" class="org.eclipse.jetty.spring.TestConfiguration">
+    <property name="testString1" value="deep" />
+    <property name="testInt2" value="2" />
+  </bean>
+
+</beans>
diff --git a/jetty-spring/src/test/resources/org/eclipse/jetty/spring/jetty.xml b/jetty-spring/src/test/resources/org/eclipse/jetty/spring/jetty.xml
new file mode 100644
index 0000000..ffa485f
--- /dev/null
+++ b/jetty-spring/src/test/resources/org/eclipse/jetty/spring/jetty.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+      xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
+
+  <bean id="contexts" class="org.eclipse.jetty.server.handler.ContextHandlerCollection"/>
+  <bean id="server" name="Main" class="org.eclipse.jetty.server.Server">
+    <constructor-arg type="org.eclipse.jetty.util.thread.ThreadPool">
+      <bean id="threadPool" class="org.eclipse.jetty.util.thread.QueuedThreadPool">
+        <property name="minThreads" value="10"/>
+        <property name="maxThreads" value="200"/>
+      </bean>
+    </constructor-arg>
+
+    <property name="connectors">
+      <list>
+        <bean id="connector" class="org.eclipse.jetty.server.ServerConnector">
+          <constructor-arg type="org.eclipse.jetty.server.Server" ref="server" />
+        </bean>
+      </list>
+    </property>
+
+    <property name="handler">
+      <bean id="handlers" class="org.eclipse.jetty.server.handler.HandlerCollection">
+        <property name="handlers">
+          <list>
+             <ref bean="contexts"/>
+             <bean id="defaultHandler" class="org.eclipse.jetty.server.handler.DefaultHandler"/>
+          </list>
+        </property>
+      </bean>
+    </property>
+
+    <property name="stopAtShutdown" value="true"/>
+    <property name="stopTimeout" value="1000"/>
+    <property name="dumpAfterStart" value="true"/>
+    <property name="dumpBeforeStop" value="false"/>
+
+  </bean>
+</beans>
+
+
diff --git a/jetty-start/pom.xml b/jetty-start/pom.xml
index fa62751..e567043 100644
--- a/jetty-start/pom.xml
+++ b/jetty-start/pom.xml
@@ -2,7 +2,7 @@
   <parent>
     <groupId>org.eclipse.jetty</groupId>
     <artifactId>jetty-project</artifactId>
-    <version>8.1.17.v20150415</version>
+    <version>9.2.13.v20150730</version>
   </parent>
   <modelVersion>4.0.0</modelVersion>
   <artifactId>jetty-start</artifactId>
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/BaseHome.java b/jetty-start/src/main/java/org/eclipse/jetty/start/BaseHome.java
new file mode 100644
index 0000000..9ffb07b
--- /dev/null
+++ b/jetty-start/src/main/java/org/eclipse/jetty/start/BaseHome.java
@@ -0,0 +1,475 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.start;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.FileVisitOption;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.PathMatcher;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Objects;
+
+import org.eclipse.jetty.start.config.CommandLineConfigSource;
+import org.eclipse.jetty.start.config.ConfigSource;
+import org.eclipse.jetty.start.config.ConfigSources;
+import org.eclipse.jetty.start.config.DirConfigSource;
+import org.eclipse.jetty.start.config.JettyBaseConfigSource;
+import org.eclipse.jetty.start.config.JettyHomeConfigSource;
+
+/**
+ * File access for <code>${jetty.home}</code>, <code>${jetty.base}</code>, directories.
+ * <p>
+ * By default, both <code>${jetty.home}</code> and <code>${jetty.base}</code> are the same directory, but they can point at different directories.
+ * <p>
+ * The <code>${jetty.home}</code> directory is where the main Jetty binaries and default configuration is housed.
+ * <p>
+ * The <code>${jetty.base}</code> directory is where the execution specific configuration and webapps are obtained from.
+ */
+public class BaseHome
+{
+    public static class SearchDir
+    {
+        private Path dir;
+        private String name;
+
+        public SearchDir(String name)
+        {
+            this.name = name;
+        }
+
+        public Path getDir()
+        {
+            return dir;
+        }
+
+        public Path resolve(Path subpath)
+        {
+            return dir.resolve(subpath);
+        }
+
+        public Path resolve(String subpath)
+        {
+            return dir.resolve(FS.separators(subpath));
+        }
+
+        public SearchDir setDir(File path)
+        {
+            if (path != null)
+            {
+                return setDir(path.toPath());
+            }
+            return this;
+        }
+
+        public SearchDir setDir(Path path)
+        {
+            if (path != null)
+            {
+                this.dir = path.toAbsolutePath();
+            }
+            return this;
+        }
+
+        public SearchDir setDir(String path)
+        {
+            if (path != null)
+            {
+                return setDir(FS.toPath(path));
+            }
+            return this;
+        }
+
+        public String toShortForm(Path path)
+        {
+            Path relative = dir.relativize(path);
+            return String.format("${%s}%c%s",name,File.separatorChar,relative.toString());
+        }
+    }
+
+    public static final String JETTY_BASE = "jetty.base";
+    public static final String JETTY_HOME = "jetty.home";
+    private final static EnumSet<FileVisitOption> SEARCH_VISIT_OPTIONS = EnumSet.of(FileVisitOption.FOLLOW_LINKS);
+
+    private final static int MAX_SEARCH_DEPTH = Integer.getInteger("org.eclipse.jetty.start.searchDepth",10);
+
+    private final ConfigSources sources;
+    private final Path homeDir;
+    private final Path baseDir;
+
+    public BaseHome() throws IOException
+    {
+        this(new String[0]);
+    }
+
+    public BaseHome(String cmdLine[]) throws IOException
+    {
+        this(new CommandLineConfigSource(cmdLine));
+    }
+
+    public BaseHome(CommandLineConfigSource cmdLineSource) throws IOException
+    {
+
+        sources = new ConfigSources();
+        sources.add(cmdLineSource);
+        this.homeDir = cmdLineSource.getHomePath();
+        this.baseDir = cmdLineSource.getBasePath();
+
+        // TODO this is cyclic construction as start log uses BaseHome, but BaseHome constructor
+        // calls other constructors that log.   This appears to be a workable sequence.
+        StartLog.getInstance().initialize(this,cmdLineSource);
+        
+        sources.add(new JettyBaseConfigSource(cmdLineSource.getBasePath()));
+        sources.add(new JettyHomeConfigSource(cmdLineSource.getHomePath()));
+
+        System.setProperty(JETTY_HOME,homeDir.toAbsolutePath().toString());
+        System.setProperty(JETTY_BASE,baseDir.toAbsolutePath().toString());
+    }
+
+    public BaseHome(ConfigSources sources)
+    {
+        this.sources = sources;
+        Path home = null;
+        Path base = null;
+        for (ConfigSource source : sources)
+        {
+            if (source instanceof CommandLineConfigSource)
+            {
+                CommandLineConfigSource cmdline = (CommandLineConfigSource)source;
+                home = cmdline.getHomePath();
+                base = cmdline.getBasePath();
+            }
+            else if (source instanceof JettyBaseConfigSource)
+            {
+                base = ((JettyBaseConfigSource)source).getDir();
+            }
+            else if (source instanceof JettyHomeConfigSource)
+            {
+                home = ((JettyHomeConfigSource)source).getDir();
+            }
+        }
+
+        Objects.requireNonNull(home,"jetty.home cannot be null");
+        this.homeDir = home;
+        this.baseDir = (base != null)?base:home;
+
+        System.setProperty(JETTY_HOME,homeDir.toAbsolutePath().toString());
+        System.setProperty(JETTY_BASE,baseDir.toAbsolutePath().toString());
+    }
+
+    public String getBase()
+    {
+        if (baseDir == null)
+        {
+            return null;
+        }
+        return baseDir.toString();
+    }
+
+    public Path getBasePath()
+    {
+        return baseDir;
+    }
+
+    /**
+     * Create a {@link Path} reference to some content in <code>"${jetty.base}"</code>
+     * 
+     * @param path
+     *            the path to reference
+     * @return the file reference
+     */
+    public Path getBasePath(String path)
+    {
+        return baseDir.resolve(path);
+    }
+
+    public ConfigSources getConfigSources()
+    {
+        return this.sources;
+    }
+
+    public String getHome()
+    {
+        return homeDir.toString();
+    }
+
+    public Path getHomePath()
+    {
+        return homeDir;
+    }
+
+    /**
+     * Get a specific path reference.
+     * <p>
+     * Path references are searched based on the config source search order.
+     * <ol>
+     * <li>If provided path is an absolute reference., and exists, return that reference</li>
+     * <li>If exists relative to <code>${jetty.base}</code>, return that reference</li>
+     * <li>If exists relative to and <code>include-jetty-dir</code> locations, return that reference</li>
+     * <li>If exists relative to <code>${jetty.home}</code>, return that reference</li>
+     * <li>Return standard {@link Path} reference obtained from {@link java.nio.file.FileSystem#getPath(String, String...)} (no exists check performed)</li>
+     * </ol>
+     * 
+     * @param path
+     *            the path to get.
+     * @return the path reference.
+     */
+    public Path getPath(final String path)
+    {
+        Path apath = FS.toPath(path);
+
+        if (apath.isAbsolute())
+        {
+            if (FS.exists(apath))
+            {
+                return apath;
+            }
+        }
+
+        for (ConfigSource source : sources)
+        {
+            if (source instanceof DirConfigSource)
+            {
+                DirConfigSource dirsource = (DirConfigSource)source;
+                Path file = dirsource.getDir().resolve(apath);
+                if (FS.exists(file))
+                {
+                    return file;
+                }
+            }
+        }
+
+        // Finally, as an anonymous path
+        return FS.toPath(path);
+    }
+
+    /**
+     * Search specified Path with pattern and return hits
+     * 
+     * @param dir
+     *            the path to a directory to start search from
+     * @param searchDepth
+     *            the number of directories deep to perform the search
+     * @param pattern
+     *            the raw pattern to use for the search (must be relative)
+     * @return the list of Paths found
+     * @throws IOException
+     *             if unable to search the path
+     */
+    public List<Path> getPaths(Path dir, int searchDepth, String pattern) throws IOException
+    {
+        if (PathMatchers.isAbsolute(pattern))
+        {
+            throw new RuntimeException("Pattern cannot be absolute: " + pattern);
+        }
+
+        List<Path> hits = new ArrayList<>();
+        if (FS.isValidDirectory(dir))
+        {
+            PathMatcher matcher = PathMatchers.getMatcher(pattern);
+            PathFinder finder = new PathFinder();
+            finder.setFileMatcher(matcher);
+            finder.setBase(dir);
+            finder.setIncludeDirsInResults(true);
+            Files.walkFileTree(dir,SEARCH_VISIT_OPTIONS,searchDepth,finder);
+            hits.addAll(finder.getHits());
+            Collections.sort(hits,new NaturalSort.Paths());
+        }
+        return hits;
+    }
+
+    /**
+     * Get a List of {@link Path}s from a provided pattern.
+     * <p>
+     * Resolution Steps:
+     * <ol>
+     * <li>If the pattern starts with "regex:" or "glob:" then a standard {@link PathMatcher} is built using
+     * {@link java.nio.file.FileSystem#getPathMatcher(String)} as a file search.</li>
+     * <li>If pattern starts with a known filesystem root (using information from {@link java.nio.file.FileSystem#getRootDirectories()}) then this is assumed to
+     * be a absolute file system pattern.</li>
+     * <li>All other patterns are treated as relative to BaseHome information:
+     * <ol>
+     * <li>Search ${jetty.home} first</li>
+     * <li>Search ${jetty.base} for overrides</li>
+     * </ol>
+     * </li>
+     * </ol>
+     * <p>
+     * Pattern examples:
+     * <dl>
+     * <dt><code>lib/logging/*.jar</code></dt>
+     * <dd>Relative pattern, not recursive, search <code>${jetty.home}</code> then <code>${jetty.base}</code> for lib/logging/*.jar content</dd>
+     * 
+     * <dt><code>lib/**/*-dev.jar</code></dt>
+     * <dd>Relative pattern, recursive search <code>${jetty.home}</code> then <code>${jetty.base}</code> for files under <code>lib</code> ending in
+     * <code>-dev.jar</code></dd>
+     * </dl>
+     * 
+     * <dt><code>etc/jetty.xml</code></dt>
+     * <dd>Relative pattern, no glob, search for <code>${jetty.home}/etc/jetty.xml</code> then <code>${jetty.base}/etc/jetty.xml</code></dd>
+     * 
+     * <dt><code>glob:/opt/app/common/*-corp.jar</code></dt>
+     * <dd>PathMapper pattern, glob, search <code>/opt/app/common/</code> for <code>*-corp.jar</code></code></dd>
+     * 
+     * </dl>
+     * 
+     * <p>
+     * Notes:
+     * <ul>
+     * <li>FileSystem case sensitivity is implementation specific (eg: linux is case-sensitive, windows is case-insensitive).<br/>
+     * See {@link java.nio.file.FileSystem#getPathMatcher(String)} for more details</li>
+     * <li>Pattern slashes are implementation neutral (use '/' always and you'll be fine)</li>
+     * <li>Recursive searching is limited to 30 levels deep (not configurable)</li>
+     * <li>File System loops are detected and skipped</li>
+     * </ul>
+     * 
+     * @param pattern
+     *            the pattern to search.
+     * @return the collection of paths found
+     * @throws IOException
+     *             if error during search operation
+     */
+    public List<Path> getPaths(String pattern) throws IOException
+    {
+        StartLog.debug("getPaths('%s')",pattern);
+        List<Path> hits = new ArrayList<>();
+
+        if (PathMatchers.isAbsolute(pattern))
+        {
+            // Perform absolute path pattern search
+
+            // The root to start search from
+            Path root = PathMatchers.getSearchRoot(pattern);
+            // The matcher for file hits
+            PathMatcher matcher = PathMatchers.getMatcher(pattern);
+
+            if (FS.isValidDirectory(root))
+            {
+                PathFinder finder = new PathFinder();
+                finder.setIncludeDirsInResults(true);
+                finder.setFileMatcher(matcher);
+                finder.setBase(root);
+                Files.walkFileTree(root,SEARCH_VISIT_OPTIONS,MAX_SEARCH_DEPTH,finder);
+                hits.addAll(finder.getHits());
+            }
+        }
+        else
+        {
+            // Perform relative path pattern search
+            Path relativePath = PathMatchers.getSearchRoot(pattern);
+            PathMatcher matcher = PathMatchers.getMatcher(pattern);
+            PathFinder finder = new PathFinder();
+            finder.setIncludeDirsInResults(true);
+            finder.setFileMatcher(matcher);
+
+            // walk config sources backwards ...
+            ListIterator<ConfigSource> iter = sources.reverseListIterator();
+            while (iter.hasPrevious())
+            {
+                ConfigSource source = iter.previous();
+                if (source instanceof DirConfigSource)
+                {
+                    DirConfigSource dirsource = (DirConfigSource)source;
+                    Path dir = dirsource.getDir();
+                    Path deepDir = dir.resolve(relativePath);
+                    if (FS.isValidDirectory(deepDir))
+                    {
+                        finder.setBase(dir);
+                        Files.walkFileTree(deepDir,SEARCH_VISIT_OPTIONS,MAX_SEARCH_DEPTH,finder);
+                    }
+                }
+            }
+
+            hits.addAll(finder.getHits());
+        }
+
+        Collections.sort(hits,new NaturalSort.Paths());
+        return hits;
+    }
+
+    public boolean isBaseDifferent()
+    {
+        return homeDir.compareTo(baseDir) != 0;
+    }
+
+    /**
+     * Convenience method for <code>toShortForm(file.toPath())</code>
+     */
+    public String toShortForm(final File path)
+    {
+        return toShortForm(path.toPath());
+    }
+
+    /**
+     * Replace/Shorten arbitrary path with property strings <code>"${jetty.home}"</code> or <code>"${jetty.base}"</code> where appropriate.
+     * 
+     * @param path
+     *            the path to shorten
+     * @return the potentially shortened path
+     */
+    public String toShortForm(final Path path)
+    {
+        Path apath = path.toAbsolutePath();
+
+        for (ConfigSource source : sources)
+        {
+            if (source instanceof DirConfigSource)
+            {
+                DirConfigSource dirsource = (DirConfigSource)source;
+                Path dir = dirsource.getDir();
+                if (apath.startsWith(dir))
+                {
+                    if (dirsource.isPropertyBased())
+                    {
+                        Path relative = dir.relativize(apath);
+                        return String.format("%s%c%s",dirsource.getId(),File.separatorChar,relative.toString());
+                    }
+                    else
+                    {
+                        return apath.toString();
+                    }
+                }
+            }
+        }
+
+        return apath.toString();
+    }
+
+    /**
+     * Replace/Shorten arbitrary path with property strings <code>"${jetty.home}"</code> or <code>"${jetty.base}"</code> where appropriate.
+     * 
+     * @param path
+     *            the path to shorten
+     * @return the potentially shortened path
+     */
+    public String toShortForm(final String path)
+    {
+        if ((path == null) || (path.charAt(0) == '<'))
+        {
+            return path;
+        }
+
+        return toShortForm(FS.toPath(path));
+    }
+}
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/Classpath.java b/jetty-start/src/main/java/org/eclipse/jetty/start/Classpath.java
index 239401a..51ad4f7 100644
--- a/jetty-start/src/main/java/org/eclipse/jetty/start/Classpath.java
+++ b/jetty-start/src/main/java/org/eclipse/jetty/start/Classpath.java
@@ -21,20 +21,34 @@ package org.eclipse.jetty.start;
 import java.io.File;
 import java.io.IOException;
 import java.io.PrintStream;
-import java.io.UnsupportedEncodingException;
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
 import java.util.StringTokenizer;
-import java.util.Vector;
 
 /**
  * Class to handle CLASSPATH construction
  */
-public class Classpath
+public class Classpath implements Iterable<File>
 {
+    private static class Loader extends URLClassLoader
+    {
+        Loader(URL[] urls, ClassLoader parent)
+        {
+            super(urls,parent);
+        }
+
+        @Override
+        public String toString()
+        {
+            return "startJarLoader@" + Long.toHexString(hashCode());
+        }
+    }
 
-    private final Vector<File> _elements = new Vector<File>();
+    private final List<File> elements = new ArrayList<File>();
 
     public Classpath()
     {
@@ -45,117 +59,88 @@ public class Classpath
         addClasspath(initial);
     }
 
-    public File[] getElements()
-    {
-        return _elements.toArray(new File[_elements.size()]);
-    }
-
-    public int count()
-    {
-        return _elements.size();
-    }
-
-    public boolean addComponent(String component)
+    public boolean addClasspath(String s)
     {
-        if ((component != null) && (component.length() > 0))
+        boolean added = false;
+        if (s != null)
         {
-            try
-            {
-                File f = new File(component);
-                if (f.exists())
-                {
-                    File key = f.getCanonicalFile();
-                    if (!_elements.contains(key))
-                    {
-                        _elements.add(key);
-                        return true;
-                    }
-                }
-            }
-            catch (IOException e)
+            StringTokenizer t = new StringTokenizer(s,File.pathSeparator);
+            while (t.hasMoreTokens())
             {
+                added |= addComponent(t.nextToken());
             }
         }
-        return false;
+        return added;
     }
 
-    public boolean addComponent(File component)
+    public boolean addComponent(File path)
     {
-        if (component != null)
+        StartLog.debug("Adding classpath component: %s",path);
+        if ((path == null) || (!path.exists()))
         {
-            try
-            {
-                if (component.exists())
-                {
-                    File key = component.getCanonicalFile();
-                    if (!_elements.contains(key))
-                    {
-                        _elements.add(key);
-                        return true;
-                    }
-                }
-            }
-            catch (IOException e)
+            // not a valid component
+            return false;
+        }
+
+        try
+        {
+            File key = path.getCanonicalFile();
+            if (!elements.contains(key))
             {
+                elements.add(key);
+                return true;
             }
         }
+        catch (IOException e)
+        {
+            StartLog.debug(e);
+        }
+
         return false;
     }
 
-    public boolean addClasspath(String s)
+    public boolean addComponent(String component)
     {
-        boolean added = false;
-        if (s != null)
+        if ((component == null) || (component.length() <= 0))
         {
-            StringTokenizer t = new StringTokenizer(s, File.pathSeparator);
-            while (t.hasMoreTokens())
-            {
-                added |= addComponent(t.nextToken());
-            }
+            // nothing to add
+            return false;
         }
-        return added;
+
+        return addComponent(new File(component));
     }
 
-    public void dump(PrintStream out)
+    public int count()
     {
-        int i = 0;
-        for (File element : _elements)
-        {
-            out.printf("%2d: %s\n", i++, element.getAbsolutePath());
-        }
+        return elements.size();
     }
 
-    @Override
-    public String toString()
+    public void dump(PrintStream out)
     {
-        StringBuffer cp = new StringBuffer(1024);
-        int cnt = _elements.size();
-        if (cnt >= 1)
-        {
-            cp.append(((_elements.elementAt(0))).getPath());
-        }
-        for (int i = 1; i < cnt; i++)
+        int i = 0;
+        for (File element : elements)
         {
-            cp.append(File.pathSeparatorChar);
-            cp.append(((_elements.elementAt(i))).getPath());
+            out.printf("%2d: %s%n",i++,element.getAbsolutePath());
         }
-        return cp.toString();
     }
 
     public ClassLoader getClassLoader()
     {
-        int cnt = _elements.size();
+        int cnt = elements.size();
         URL[] urls = new URL[cnt];
         for (int i = 0; i < cnt; i++)
         {
             try
             {
-                urls[i] = _elements.elementAt(i).toURI().toURL();
+                urls[i] = elements.get(i).toURI().toURL();
+                StartLog.debug("URLClassLoader.url[%d] = %s",i,urls[i]);
             }
             catch (MalformedURLException e)
             {
+                StartLog.warn(e);
             }
         }
+        StartLog.debug("Loaded %d URLs into URLClassLoader",urls.length);
 
         ClassLoader parent = Thread.currentThread().getContextClassLoader();
         if (parent == null)
@@ -166,46 +151,58 @@ public class Classpath
         {
             parent = ClassLoader.getSystemClassLoader();
         }
-        return new Loader(urls, parent);
+        return new Loader(urls,parent);
     }
 
-    private static class Loader extends URLClassLoader
+    public List<File> getElements()
     {
-        Loader(URL[] urls, ClassLoader parent)
-        {
-            super(urls, parent);
-        }
-
-        @Override
-        public String toString()
-        {
-            return "startJarLoader@" + Long.toHexString(hashCode());
-        }
+        return elements;
     }
 
+    public boolean isEmpty()
+    {
+        return (elements == null) || (elements.isEmpty());
+    }
 
+    @Override
+    public Iterator<File> iterator()
+    {
+        return elements.iterator();
+    }
 
     /**
-     * Overlay another classpath, copying its elements into place on this
-     * Classpath, while eliminating duplicate entries on the classpath.
+     * Overlay another classpath, copying its elements into place on this Classpath, while eliminating duplicate entries on the classpath.
      * 
-     * @param cpOther the other classpath to overlay
+     * @param other
+     *            the other classpath to overlay
      */
-    public void overlay(Classpath cpOther)
+    public void overlay(Classpath other)
     {
-        for (File otherElement : cpOther._elements)
+        for (File otherElement : other.elements)
         {
-            if (this._elements.contains(otherElement))
+            if (this.elements.contains(otherElement))
             {
                 // Skip duplicate entries
                 continue;
             }
-            this._elements.add(otherElement);
+            this.elements.add(otherElement);
         }
     }
 
-    public boolean isEmpty()
+    @Override
+    public String toString()
     {
-        return (_elements == null) || (_elements.isEmpty());
+        StringBuffer cp = new StringBuffer(1024);
+        boolean needDelim = false;
+        for (File element : elements)
+        {
+            if (needDelim)
+            {
+                cp.append(File.pathSeparatorChar);
+            }
+            cp.append(element.getAbsolutePath());
+            needDelim = true;
+        }
+        return cp.toString();
     }
 }
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/CommandLineBuilder.java b/jetty-start/src/main/java/org/eclipse/jetty/start/CommandLineBuilder.java
index 64d64f7..7b3427a 100644
--- a/jetty-start/src/main/java/org/eclipse/jetty/start/CommandLineBuilder.java
+++ b/jetty-start/src/main/java/org/eclipse/jetty/start/CommandLineBuilder.java
@@ -18,16 +18,92 @@
 
 package org.eclipse.jetty.start;
 
+import java.io.File;
 import java.util.ArrayList;
 import java.util.List;
 
 public class CommandLineBuilder
 {
+    public static File findExecutable(File root, String path)
+    {
+        String npath = path.replace('/',File.separatorChar);
+        File exe = new File(root,npath);
+        if (!exe.exists())
+        {
+            return null;
+        }
+        return exe;
+    }
+
+    public static String findJavaBin()
+    {
+        File javaHome = new File(System.getProperty("java.home"));
+        if (!javaHome.exists())
+        {
+            return null;
+        }
+
+        File javabin = findExecutable(javaHome,"bin/java");
+        if (javabin != null)
+        {
+            return javabin.getAbsolutePath();
+        }
+
+        javabin = findExecutable(javaHome,"bin/java.exe");
+        if (javabin != null)
+        {
+            return javabin.getAbsolutePath();
+        }
+
+        return "java";
+    }
+
+    /**
+     * Perform an optional quoting of the argument, being intelligent with spaces and quotes as needed. If a subString is set in quotes it won't the subString
+     * won't be escaped.
+     * 
+     * @param arg
+     * @return
+     */
+    public static String quote(String arg)
+    {
+        boolean needsQuoting = (arg.indexOf(' ') >= 0) || (arg.indexOf('"') >= 0);
+        if (!needsQuoting)
+        {
+            return arg;
+        }
+        StringBuilder buf = new StringBuilder();
+        // buf.append('"');
+        boolean escaped = false;
+        boolean quoted = false;
+        for (char c : arg.toCharArray())
+        {
+            if (!quoted && !escaped && ((c == '"') || (c == ' ')))
+            {
+                buf.append("\\");
+            }
+            // don't quote text in single quotes
+            if (!escaped && (c == '\''))
+            {
+                quoted = !quoted;
+            }
+            escaped = (c == '\\');
+            buf.append(c);
+        }
+        // buf.append('"');
+        return buf.toString();
+    }
+
     private List<String> args;
 
-    public CommandLineBuilder(String bin)
+    public CommandLineBuilder()
     {
         args = new ArrayList<String>();
+    }
+
+    public CommandLineBuilder(String bin)
+    {
+        this();
         args.add(bin);
     }
 
@@ -42,7 +118,9 @@ public class CommandLineBuilder
     public void addArg(String arg)
     {
         if (arg != null)
+        {
             args.add(quote(arg));
+        }
     }
 
     /**
@@ -65,7 +143,7 @@ public class CommandLineBuilder
      */
     public void addEqualsArg(String name, String value)
     {
-        if (value != null && value.length() > 0)
+        if ((value != null) && (value.length() > 0))
         {
             args.add(quote(name + "=" + value));
         }
@@ -86,7 +164,9 @@ public class CommandLineBuilder
     public void addRawArg(String arg)
     {
         if (arg != null)
+        {
             args.add(arg);
+        }
     }
 
     public List<String> getArgs()
@@ -94,51 +174,40 @@ public class CommandLineBuilder
         return args;
     }
 
-    /**
-     * Perform an optional quoting of the argument, being intelligent with spaces and quotes as needed.
-     * 
-     * @param arg
-     * @return
-     */
-    public static String quote(String arg)
-    {
-        boolean needsQuoting = arg.indexOf(' ') >= 0 || arg.indexOf('"') >= 0;
-        if (!needsQuoting)
-        {
-            return arg;
-        }
-        StringBuilder buf = new StringBuilder();
-        // buf.append('"');
-        boolean escaped = false;
-        for (char c : arg.toCharArray())
-        {
-            if (!escaped && ((c == '"') || (c == ' ')))
-            {
-                buf.append("\\");
-            }
-            escaped = (c == '\\');
-            buf.append(c);
-        }
-        // buf.append('"');
-        return buf.toString();
-    }
-
     @Override
     public String toString()
     {
+        return toString(" ");
+    }
+  
+    public String toString(String delim)
+    {
         StringBuilder buf = new StringBuilder();
 
-        boolean delim = false;
         for (String arg : args)
         {
-            if (delim)
+            if (buf.length()>0)
             {
-                buf.append(' ');
+                buf.append(delim);
             }
             buf.append(quote(arg));
-            delim = true;
         }
 
         return buf.toString();
     }
+
+    public void debug()
+    {
+        if (!StartLog.isDebugEnabled())
+        {
+            return;
+        }
+
+        int len = args.size();
+        StartLog.debug("Command Line: %,d entries",args.size());
+        for (int i = 0; i < len; i++)
+        {
+            StartLog.debug(" [%d]: \"%s\"",i,args.get(i));
+        }
+    }
 }
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/Config.java b/jetty-start/src/main/java/org/eclipse/jetty/start/Config.java
deleted file mode 100644
index 1112975..0000000
--- a/jetty-start/src/main/java/org/eclipse/jetty/start/Config.java
+++ /dev/null
@@ -1,1002 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.start;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileFilter;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.Reader;
-import java.io.StringReader;
-import java.net.URL;
-import java.text.CollationKey;
-import java.text.Collator;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Properties;
-import java.util.Set;
-import java.util.StringTokenizer;
-import java.util.TreeSet;
-
-/**
- * <p>
- * It allows an application to be started with the command <code>"java -jar start.jar"</code>.
- * </p>
- * 
- * <p>
- * The behaviour of Main is controlled by the <code>"org/eclipse/start/start.config"</code> file obtained as a resource
- * or file. This can be overridden with the START system property. The format of each line in this file is:
- * </p>
- * 
- * <p>
- * Each line contains entry in the format:
- * </p>
- * 
- * <pre>
- *   SUBJECT [ [!] CONDITION [AND|OR] ]*
- * </pre>
- * 
- * <p>
- * where SUBJECT:
- * </p>
- * <ul>
- * <li>ends with <code>".class"</code> is the Main class to run.</li>
- * <li>ends with <code>".xml"</code> is a configuration file for the command line</li>
- * <li>ends with <code>"/"</code> is a directory from which to add all jar and zip files.</li>
- * <li>ends with <code>"/*"</code> is a directory from which to add all unconsidered jar and zip files.</li>
- * <li>ends with <code>"/**"</code> is a directory from which to recursively add all unconsidered jar and zip files.</li>
- * <li>Containing <code>=</code> are used to assign system properties.</li>
- * <li>Containing <code>~=</code> are used to assign start properties.</li>
- * <li>Containing <code>/=</code> are used to assign a canonical path.</li>
- * <li>all other subjects are treated as files to be added to the classpath.</li>
- * </ul>
- * 
- * <p>
- * property expansion:
- * </p>
- * <ul>
- * <li><code>${name}</code> is expanded to a start property</li>
- * <li><code>$(name)</code> is expanded to either a start property or a system property.</li>
- * <li>The start property <code>${version}</code> is defined as the version of the start.jar</li>
- * </ul>
- * 
- * <p>
- * Files starting with <code>"/"</code> are considered absolute, all others are relative to the home directory.
- * </p>
- * 
- * <p>
- * CONDITION is one of:
- * </p>
- * <ul>
- * <li><code>always</code></li>
- * <li><code>never</code></li>
- * <li><code>available classname</code> - true if class on classpath</li>
- * <li><code>property name</code> - true if set as start property</li>
- * <li><code>system name</code> - true if set as system property</li>
- * <li><code>exists file</code> - true if file/dir exists</li>
- * <li><code>java OPERATOR version</code> - java version compared to literal</li>
- * <li><code>nargs OPERATOR number</code> - number of command line args compared to literal</li>
- * <li>OPERATOR := one of <code>"<"</code>,<code>">"</code>,<code>"<="</code>,<code>">="</code>,
- * <code>"=="</code>,<code>"!="</code></li>
- * </ul>
- * 
- * <p>
- * CONDITIONS can be combined with <code>AND</code> <code>OR</code> or <code>!</code>, with <code>AND</code> being the
- * assume operator for a list of CONDITIONS.
- * </p>
- * 
- * <p>
- * Classpath operations are evaluated on the fly, so once a class or jar is added to the classpath, subsequent available
- * conditions will see that class.
- * </p>
- * 
- * <p>
- * The configuration file may be divided into sections with option names like: [ssl,default]
- * </p>
- * 
- * <p>
- * Note: a special discovered section identifier <code>[=path_to_directory/*]</code> is allowed to auto-create section
- * IDs, based on directory names found in the path specified in the "path_to_directory/" part of the identifier.
- * </p>
- * 
- * <p>
- * Clauses after a section header will only be included if they match one of the tags in the options property. By
- * default options are set to "default,*" or the OPTIONS property may be used to pass in a list of tags, eg. :
- * </p>
- * 
- * <pre>
- *    java -jar start.jar OPTIONS=jetty,jsp,ssl
- * </pre>
- * 
- * <p>
- * The tag '*' is always appended to the options, so any section with the * tag is always applied.
- * </p>
- * 
- * <p>
- * The property map maintained by this class is static and shared between all instances in the same classloader
- * </p>
- */
-public class Config
-{
-    public static final String DEFAULT_SECTION = "";
-    static
-    {
-        String ver = System.getProperty("jetty.version", null);
-        
-        if(ver == null) {
-            Package pkg = Config.class.getPackage();
-            if (pkg != null && 
-                    "Eclipse.org - Jetty".equals(pkg.getImplementationVendor()) &&
-                    (pkg.getImplementationVersion() != null))
-            {
-                ver = pkg.getImplementationVersion();
-            }
-        }
-
-        if (ver == null)
-        {
-            ver = "Unknown";
-        }
-        _version = ver;
-    }
-
-    /**
-     * Natural language sorting for key names.
-     */
-    private final Comparator<String> keySorter = new Comparator<String>()
-    {
-        private final Collator collator = Collator.getInstance();
-
-        public int compare(String o1, String o2)
-        {
-            CollationKey key1 = collator.getCollationKey(o1);
-            CollationKey key2 = collator.getCollationKey(o2);
-            return key1.compareTo(key2);
-        }
-    };
-
-    private static final String _version;
-    private static boolean DEBUG = false;
-    private static final Map<String, String> __properties = new HashMap<String, String>();
-    private final Map<String, Classpath> _classpaths = new HashMap<String, Classpath>();
-    private final List<String> _xml = new ArrayList<String>();
-    private String _classname = null;
-
-    private int argCount = 0;
-    
-    private final Set<String> _activeOptions = new TreeSet<String>(new Comparator<String>()
-    {
-        // Make sure "*" is always at the end of the list
-        public int compare(String o1, String o2)
-        {
-            if ("*".equals(o1))
-            {
-                return 1;
-            }
-            if ("*".equals(o2))
-            {
-                return -1;
-            }
-            return o1.compareTo(o2);
-        }
-    });
-
-    private boolean addClasspathComponent(List<String> sections, String component)
-    {
-        for (String section : sections)
-        {
-            Classpath cp = _classpaths.get(section);
-            if (cp == null)
-                cp = new Classpath();
-
-            boolean added = cp.addComponent(component);
-            _classpaths.put(section,cp);
-
-            if (!added)
-            {
-                // First failure means all failed.
-                return false;
-            }
-        }
-
-        return true;
-    }
-
-    private boolean addClasspathPath(List<String> sections, String path)
-    {
-        for (String section : sections)
-        {
-            Classpath cp = _classpaths.get(section);
-            if (cp == null)
-            {
-                cp = new Classpath();
-            }
-            if (!cp.addClasspath(path))
-            {
-                // First failure means all failed.
-                return false;
-            }
-            _classpaths.put(section,cp);
-        }
-
-        return true;
-    }
-
-    private void addJars(List<String> sections, File dir, boolean recurse) throws IOException
-    {
-        List<File> entries = new ArrayList<File>();
-        File[] files = dir.listFiles();
-        if (files == null)
-        {
-            // No files found, skip it.
-            return;
-        }
-        entries.addAll(Arrays.asList(files));
-        Collections.sort(entries,FilenameComparator.INSTANCE);
-
-        for (File entry : entries)
-        {
-            if (entry.isDirectory())
-            {
-                if (recurse)
-                    addJars(sections,entry,recurse);
-            }
-            else
-            {
-                String name = entry.getName().toLowerCase(Locale.ENGLISH);
-                if (name.endsWith(".jar") || name.endsWith(".zip"))
-                {
-                    String jar = entry.getCanonicalPath();
-                    boolean added = addClasspathComponent(sections,jar);
-                    debug((added?"  CLASSPATH+=":"  !") + jar);
-                }
-            }
-        }
-    }
-
-    private void close(InputStream stream)
-    {
-        if (stream == null)
-            return;
-
-        try
-        {
-            stream.close();
-        }
-        catch (IOException ignore)
-        {
-            /* ignore */
-        }
-    }
-
-    private void close(Reader reader)
-    {
-        if (reader == null)
-            return;
-
-        try
-        {
-            reader.close();
-        }
-        catch (IOException ignore)
-        {
-            /* ignore */
-        }
-    }
-
-    public static boolean isDebug()
-    {
-        return DEBUG;
-    }
-
-    public static void debug(String msg)
-    {
-        if (DEBUG)
-        {
-            System.err.println(msg);
-        }
-    }
-
-    public static void debug(Throwable t)
-    {
-        if (DEBUG)
-        {
-            t.printStackTrace(System.err);
-        }
-    }
-
-    private String expand(String s)
-    {
-        int i1 = 0;
-        int i2 = 0;
-        while (s != null)
-        {
-            i1 = s.indexOf("$(",i2);
-            if (i1 < 0)
-                break;
-            i2 = s.indexOf(")",i1 + 2);
-            if (i2 < 0)
-                break;
-            String name = s.substring(i1 + 2,i2);
-            String property = getProperty(name);
-            s = s.substring(0,i1) + property + s.substring(i2 + 1);
-        }
-
-        i1 = 0;
-        i2 = 0;
-        while (s != null)
-        {
-            i1 = s.indexOf("${",i2);
-            if (i1 < 0)
-                break;
-            i2 = s.indexOf("}",i1 + 2);
-            if (i2 < 0)
-                break;
-            String name = s.substring(i1 + 2,i2);
-            String property = getProperty(name);
-            s = s.substring(0,i1) + property + s.substring(i2 + 1);
-        }
-
-        return s;
-    }
-
-    /**
-     * Get the default classpath.
-     * 
-     * @return the default classpath
-     */
-    public Classpath getClasspath()
-    {
-        return _classpaths.get(DEFAULT_SECTION);
-    }
-
-    /**
-     * Get the active classpath, as dictated by OPTIONS= entries.
-     * 
-     * @return the Active classpath
-     * @see #getCombinedClasspath(Collection)
-     */
-    public Classpath getActiveClasspath()
-    {
-        return getCombinedClasspath(_activeOptions);
-    }
-
-    /**
-     * Get the combined classpath representing the default classpath plus all named sections.
-     * 
-     * NOTE: the default classpath will be prepended, and the '*' classpath will be appended.
-     * 
-     * @param optionIds
-     *            the list of section ids to fetch
-     * @return the {@link Classpath} representing combination all of the selected sectionIds, combined with the default
-     *         section id, and '*' special id.
-     */
-    public Classpath getCombinedClasspath(Collection<String> optionIds)
-    {
-        Classpath cp = new Classpath();
-
-        cp.overlay(_classpaths.get(DEFAULT_SECTION));
-        for (String optionId : optionIds)
-        {
-            Classpath otherCp = _classpaths.get(optionId);
-            if (otherCp == null)
-            {
-                throw new IllegalArgumentException("No such OPTIONS: " + optionId);
-            }
-            cp.overlay(otherCp);
-        }
-        cp.overlay(_classpaths.get("*"));
-        return cp;
-    }
-
-    public String getMainClassname()
-    {
-        return _classname;
-    }
-
-    public static void clearProperties()
-    {
-        __properties.clear();
-    }
-    
-    public static Properties getProperties()
-    {
-        Properties properties = new Properties();
-        // Add System Properties First
-        Enumeration<?> ensysprop = System.getProperties().propertyNames();
-        while(ensysprop.hasMoreElements()) {
-            String name = (String)ensysprop.nextElement();
-            properties.put(name, System.getProperty(name));
-        }
-        // Add Config Properties Next (overwriting any System Properties that exist)
-        for (String key : __properties.keySet()) {
-            properties.put(key,__properties.get(key));
-        }
-        return properties;
-    }
-    
-    public static String getProperty(String name)
-    {
-        if ("version".equalsIgnoreCase(name)) {
-            return _version;
-        }
-        // Search Config Properties First
-        if (__properties.containsKey(name)) {
-            return __properties.get(name);
-        }
-        // Return what exists in System.Properties otherwise.
-        return System.getProperty(name);
-    }
-
-    public static String getProperty(String name, String defaultValue)
-    {
-        // Search Config Properties First
-        if (__properties.containsKey(name))
-            return __properties.get(name);
-        // Return what exists in System.Properties otherwise.
-        return System.getProperty(name, defaultValue);
-    }
-
-    /**
-     * Get the classpath for the named section
-     * 
-     * @param sectionId
-     * @return the classpath for the specified section id
-     */
-    public Classpath getSectionClasspath(String sectionId)
-    {
-        return _classpaths.get(sectionId);
-    }
-
-    /**
-     * Get the list of section Ids.
-     * 
-     * @return the set of unique section ids
-     */
-    public Set<String> getSectionIds()
-    {
-        Set<String> ids = new TreeSet<String>(keySorter);
-        ids.addAll(_classpaths.keySet());
-        return ids;
-    }
-
-    public List<String> getXmlConfigs()
-    {
-        return _xml;
-    }
-
-    private boolean isAvailable(List<String> options, String classname)
-    {
-        // Try default/parent class loader first.
-        try
-        {
-            Class.forName(classname);
-            return true;
-        }
-        catch (NoClassDefFoundError e)
-        {
-            debug(e);
-        }
-        catch (ClassNotFoundException e)
-        {
-            debug("ClassNotFoundException (parent class loader): " + classname);
-        }
-
-        // Try option classloaders instead
-        ClassLoader loader;
-        Classpath classpath;
-        for (String optionId : options)
-        {
-            classpath = _classpaths.get(optionId);
-            if (classpath == null)
-            {
-                // skip, no classpath
-                continue;
-            }
-
-            loader = classpath.getClassLoader();
-
-            try
-            {
-                loader.loadClass(classname);
-                return true;
-            }
-            catch (NoClassDefFoundError e)
-            {
-                debug(e);
-            }
-            catch (ClassNotFoundException e)
-            {
-                debug("ClassNotFoundException (section class loader: " + optionId + "): " + classname);
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Parse the configuration
-     * 
-     * @param buf
-     * @throws IOException
-     */
-    public void parse(CharSequence buf) throws IOException
-    {
-        parse(new StringReader(buf.toString()));
-    }
-
-    /**
-     * Parse the configuration
-     * 
-     * @param stream the stream to read from
-     * @throws IOException
-     */
-    public void parse(InputStream stream) throws IOException
-    {
-        InputStreamReader reader = null;
-        try
-        {
-            reader = new InputStreamReader(stream);
-            parse(reader);
-        }
-        finally
-        {
-            close(reader);
-        }
-    }
-
-    /**
-     */
-    public void parse(Reader reader) throws IOException
-    {
-        BufferedReader buf = null;
-
-        try
-        {
-            buf = new BufferedReader(reader);
-
-            List<String> options = new ArrayList<String>();
-            options.add(DEFAULT_SECTION);
-            _classpaths.put(DEFAULT_SECTION,new Classpath());
-            Version java_version = new Version(System.getProperty("java.version"));
-            Version ver = new Version();
-
-            String line = null;
-            while ((line = buf.readLine()) != null)
-            {
-                String trim = line.trim();
-                if (trim.length() == 0) // empty line
-                    continue;
-
-                if (trim.startsWith("#")) // comment
-                    continue;
-
-                // handle options
-                if (trim.startsWith("[") && trim.endsWith("]"))
-                {
-                    String identifier = trim.substring(1,trim.length() - 1);
-
-                    // Normal case: section identifier (possibly separated by commas)
-                    options = Arrays.asList(identifier.split(","));
-                    List<String> option_ids=new ArrayList<String>();
-                    
-                    // Ensure section classpaths exist
-                    for (String optionId : options)
-                    {
-                        if (optionId.charAt(0) == '=')
-                            continue;
-
-                        if (!_classpaths.containsKey(optionId))
-                            _classpaths.put(optionId,new Classpath());
-                        
-                        if (!option_ids.contains(optionId))
-                            option_ids.add(optionId);
-                    }
-                    
-
-                    // Process Dynamic
-                    for (String optionId : options)
-                    {
-                        if (optionId.charAt(0) != '=')
-                            continue;
-                        
-                        option_ids = processDynamicSectionIdentifier(optionId.substring(1),option_ids);
-                    }
-                    
-                    options = option_ids;
-                    
-                    continue;
-                }
-
-                try
-                {
-                    StringTokenizer st = new StringTokenizer(line);
-                    String subject = st.nextToken();
-                    boolean expression = true;
-                    boolean not = false;
-                    String condition = null;
-                    // Evaluate all conditions
-                    while (st.hasMoreTokens())
-                    {
-                        condition = st.nextToken();
-                        if (condition.equalsIgnoreCase("!"))
-                        {
-                            not = true;
-                            continue;
-                        }
-                        if (condition.equalsIgnoreCase("OR"))
-                        {
-                            if (expression)
-                                break;
-                            expression = true;
-                            continue;
-                        }
-                        if (condition.equalsIgnoreCase("AND"))
-                        {
-                            if (!expression)
-                                break;
-                            continue;
-                        }
-                        boolean eval = true;
-                        if (condition.equals("true") || condition.equals("always"))
-                        {
-                            eval = true;
-                        }
-                        else if (condition.equals("false") || condition.equals("never"))
-                        {
-                            eval = false;
-                        }
-                        else if (condition.equals("available"))
-                        {
-                            String class_to_check = st.nextToken();
-                            eval = isAvailable(options,class_to_check);
-                        }
-                        else if (condition.equals("exists"))
-                        {
-                            try
-                            {
-                                eval = false;
-                                File file = new File(expand(st.nextToken()));
-                                eval = file.exists();
-                            }
-                            catch (Exception e)
-                            {
-                                debug(e);
-                            }
-                        }
-                        else if (condition.equals("property"))
-                        {
-                            String property = getProperty(st.nextToken());
-                            eval = property != null && property.length() > 0;
-                        }
-                        else if (condition.equals("system"))
-                        {
-                            String property = System.getProperty(st.nextToken());
-                            eval = property != null && property.length() > 0;
-                        }
-                        else if (condition.equals("java"))
-                        {
-                            String operator = st.nextToken();
-                            String version = st.nextToken();
-                            ver.parse(version);
-                            eval = (operator.equals("<") && java_version.compare(ver) < 0) || (operator.equals(">") && java_version.compare(ver) > 0)
-                            || (operator.equals("<=") && java_version.compare(ver) <= 0) || (operator.equals("=<") && java_version.compare(ver) <= 0)
-                            || (operator.equals("=>") && java_version.compare(ver) >= 0) || (operator.equals(">=") && java_version.compare(ver) >= 0)
-                            || (operator.equals("==") && java_version.compare(ver) == 0) || (operator.equals("!=") && java_version.compare(ver) != 0);
-                        }
-                        else if (condition.equals("nargs"))
-                        {
-                            String operator = st.nextToken();
-                            int number = Integer.parseInt(st.nextToken());
-                            eval = (operator.equals("<") && argCount < number) || (operator.equals(">") && argCount > number)
-                            || (operator.equals("<=") && argCount <= number) || (operator.equals("=<") && argCount <= number)
-                            || (operator.equals("=>") && argCount >= number) || (operator.equals(">=") && argCount >= number)
-                            || (operator.equals("==") && argCount == number) || (operator.equals("!=") && argCount != number);
-                        }
-                        else
-                        {
-                            System.err.println("ERROR: Unknown condition: " + condition);
-                            eval = false;
-                        }
-                        expression &= not?!eval:eval;
-                        not = false;
-                    }
-
-                    String file = expand(subject);
-                    debug((expression?"T ":"F ") + line);
-                    if (!expression)
-                        continue;
-
-                    // Setting of a start property
-                    if (subject.indexOf("~=") > 0)
-                    {
-                        int i = file.indexOf("~=");
-                        String property = file.substring(0,i);
-                        String value = fixPath(file.substring(i + 2));
-                        debug("  " + property + "~=" + value);
-                        setProperty(property,value);
-                        continue;
-                    }
-
-                    // Setting of start property with canonical path
-                    if (subject.indexOf("/=") > 0)
-                    {
-                        int i = file.indexOf("/=");
-                        String property = file.substring(0,i);
-                        String value = fixPath(file.substring(i + 2));
-                        String canonical = new File(value).getCanonicalPath();
-                        debug("  " + property + "/=" + value + "==" + canonical);
-                        setProperty(property,canonical);
-                        continue;
-                    }
-
-                    // Setting of system property
-                    if (subject.indexOf("=") > 0)
-                    {
-                        int i = file.indexOf("=");
-                        String property = file.substring(0,i);
-                        String value = fixPath(file.substring(i + 1));
-                        debug("  " + property + "=" + value);
-                        System.setProperty(property,value);
-                        continue;
-                    }
-
-                    // Add all unconsidered JAR and ZIP files to classpath
-                    if (subject.endsWith("/*"))
-                    {
-                        // directory of JAR files - only add jars and zips within the directory
-                        File dir = new File(fixPath(file.substring(0,file.length() - 1)));
-                        addJars(options,dir,false);
-                        continue;
-                    }
-
-                    // Recursively add all unconsidered JAR and ZIP files to classpath
-                    if (subject.endsWith("/**"))
-                    {
-                        //directory hierarchy of jar files - recursively add all jars and zips in the hierarchy
-                        File dir = new File(fixPath(file.substring(0,file.length() - 2)));
-                        addJars(options,dir,true);
-                        continue;
-                    }
-
-                    // Add raw classpath directory to classpath
-                    if (subject.endsWith("/"))
-                    {
-                        // class directory
-                        File cd = new File(fixPath(file));
-                        String d = cd.getCanonicalPath();
-                        boolean added = addClasspathComponent(options,d);
-                        debug((added?"  CLASSPATH+=":"  !") + d);
-                        continue;
-                    }
-
-                    // Add XML configuration
-                    if (subject.toLowerCase(Locale.ENGLISH).endsWith(".xml"))
-                    {
-                        // Config file
-                        File f = new File(fixPath(file));
-                        if (f.exists())
-                            _xml.add(f.getCanonicalPath());
-                        debug("  ARGS+=" + f);
-                        continue;
-                    }
-
-                    // Set the main class to execute (overrides any previously set)
-                    if (subject.toLowerCase(Locale.ENGLISH).endsWith(".class"))
-                    {
-                        // Class
-                        String cn = expand(subject.substring(0,subject.length() - 6));
-                        if (cn != null && cn.length() > 0)
-                        {
-                            debug("  CLASS=" + cn);
-                            _classname = cn;
-                        }
-                        continue;
-                    }
-
-                    // Add raw classpath entry
-                    if (subject.toLowerCase(Locale.ENGLISH).endsWith(".path"))
-                    {
-                        // classpath (jetty.class.path?) to add to runtime classpath
-                        String cn = expand(subject.substring(0,subject.length() - 5));
-                        if (cn != null && cn.length() > 0)
-                        {
-                            debug("  PATH=" + cn);
-                            addClasspathPath(options,cn);
-                        }
-                        continue;
-                    }
-
-                    // single JAR file
-                    File f = new File(fixPath(file));
-                    if (f.exists())
-                    {
-                        String d = f.getCanonicalPath();
-                        boolean added = addClasspathComponent(options,d);
-                        if (!added)
-                        {
-                            added = addClasspathPath(options,expand(subject));
-                        }
-                        debug((added?"  CLASSPATH+=":"  !") + d);
-                    }
-                }
-                catch (Exception e)
-                {
-                    System.err.println("on line: '" + line + "'");
-                    e.printStackTrace();
-                }
-            }
-        }
-        finally
-        {
-            close(buf);
-        }
-    }
-
-    private List<String> processDynamicSectionIdentifier(String dynamicPathId,List<String> sections) throws IOException
-    {
-        String rawPath;
-        boolean deep;
-        
-        if (dynamicPathId.endsWith("/*"))
-        {
-            deep=false;
-            rawPath = fixPath(dynamicPathId.substring(0,dynamicPathId.length() - 1));
-        }
-        else if (dynamicPathId.endsWith("/**"))
-        {
-            deep=true;
-            rawPath = fixPath(dynamicPathId.substring(0,dynamicPathId.length() - 2));
-        }
-        else 
-        {
-            String msg = "Illegal dynamic path [" + dynamicPathId + "]";
-            throw new IOException(msg);
-        }
-        
-        File parentDir = new File(expand(rawPath));
-        if (!parentDir.exists())
-            return sections;
-        debug("dynamic: " + parentDir);
-
-        File dirs[] = parentDir.listFiles(new FileFilter()
-        {
-            public boolean accept(File path)
-            {
-                return path.isDirectory();
-            }
-        });
-
-        List<String> dyn_sections = new ArrayList<String>();
-        List<String> super_sections = new ArrayList<String>();
-        if (sections!=null)
-            super_sections.addAll(sections);
-        
-        for (File dir : dirs)
-        {
-            String id = dir.getName();
-            if (!_classpaths.keySet().contains(id))
-                _classpaths.put(id, new Classpath());
-            
-            dyn_sections.clear();
-            if (sections!=null)
-                dyn_sections.addAll(sections);
-            dyn_sections.add(id);
-            super_sections.add(id);
-            debug("dynamic: " + dyn_sections);
-            addJars(dyn_sections,dir,deep);
-        }
-        
-        return super_sections;
-    }
-
-    private String fixPath(String path)
-    {
-        return path.replace('/',File.separatorChar);
-    }
-
-    public void parse(URL url) throws IOException
-    {
-        InputStream stream = null;
-        InputStreamReader reader = null;
-        try
-        {
-            stream = url.openStream();
-            reader = new InputStreamReader(stream);
-            parse(reader);
-        }
-        finally
-        {
-            close(reader);
-            close(stream);
-        }
-    }
-
-    public void setArgCount(int argCount)
-    {
-        this.argCount = argCount;
-    }
-
-    public void setProperty(String name, String value)
-    {
-        if (name.equals("DEBUG"))
-        {
-            DEBUG = Boolean.parseBoolean(value);
-            if (DEBUG)
-            {
-                System.setProperty("org.eclipse.jetty.util.log.stderr.DEBUG","true");
-                System.setProperty("org.eclipse.jetty.start.DEBUG","true");
-            }
-        }
-        if (name.equals("OPTIONS"))
-        {
-            _activeOptions.clear();
-            String ids[] = value.split(",");
-            for (String id : ids)
-            {
-                addActiveOption(id);
-            }
-        }
-        __properties.put(name,value);
-    }
-
-    public void addActiveOption(String option)
-    {
-        _activeOptions.add(option); 
-        __properties.put("OPTIONS",join(_activeOptions,","));
-    }
-
-    public Set<String> getActiveOptions()
-    {
-        return _activeOptions;
-    }
-
-    public void removeActiveOption(String option)
-    {
-        _activeOptions.remove(option);
-        __properties.put("OPTIONS",join(_activeOptions,","));
-    }
-    
-    private String join(Collection<?> coll, String delim)
-    {
-        StringBuffer buf = new StringBuffer();
-        Iterator<?> i = coll.iterator();
-        boolean hasNext = i.hasNext();
-        while (hasNext)
-        {
-            buf.append(String.valueOf(i.next()));
-            hasNext = i.hasNext();
-            if (hasNext)
-                buf.append(delim);
-        }
-
-        return buf.toString();
-    }
-
-}
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/FS.java b/jetty-start/src/main/java/org/eclipse/jetty/start/FS.java
new file mode 100644
index 0000000..d303412
--- /dev/null
+++ b/jetty-start/src/main/java/org/eclipse/jetty/start/FS.java
@@ -0,0 +1,166 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.start;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.attribute.FileTime;
+import java.util.Locale;
+
+public class FS
+{
+    public static boolean canReadDirectory(Path path)
+    {
+        return Files.exists(path) && Files.isDirectory(path) && Files.isReadable(path);
+    }
+
+    public static boolean canReadFile(Path path)
+    {
+        return Files.exists(path) && Files.isRegularFile(path) && Files.isReadable(path);
+    }
+
+    public static boolean canWrite(Path path)
+    {
+        return Files.isWritable(path);
+    }
+
+    public static void close(Closeable c)
+    {
+        if (c == null)
+        {
+            return;
+        }
+
+        try
+        {
+            c.close();
+        }
+        catch (IOException ignore)
+        {
+            /* ignore */
+        }
+    }
+
+    public static boolean createNewFile(Path path) throws IOException
+    {
+        Path ret = Files.createFile(path);
+        return Files.exists(ret);
+    }
+
+    public static void ensureDirectoryExists(Path dir) throws IOException
+    {
+        if (exists(dir))
+        {
+            // exists already, nothing to do
+            return;
+        }
+        Files.createDirectories(dir);
+    }
+
+    public static void ensureDirectoryWritable(Path dir) throws IOException
+    {
+        if (!Files.exists(dir))
+        {
+            throw new IOException("Path does not exist: " + dir.toAbsolutePath());
+        }
+        if (!Files.isDirectory(dir))
+        {
+            throw new IOException("Directory does not exist: " + dir.toAbsolutePath());
+        }
+        if (!Files.isWritable(dir))
+        {
+            throw new IOException("Unable to write to directory: " + dir.toAbsolutePath());
+        }
+    }
+
+    public static boolean exists(Path path)
+    {
+        return Files.exists(path);
+    }
+
+    public static boolean isValidDirectory(Path path)
+    {
+        if (!Files.exists(path))
+        {
+            // doesn't exist, not a valid directory
+            return false;
+        }
+
+        if (!Files.isDirectory(path))
+        {
+            // not a directory (as expected)
+            StartLog.warn("Not a directory: " + path);
+            return false;
+        }
+
+        return true;
+    }
+
+    public static boolean isXml(String filename)
+    {
+        return filename.toLowerCase(Locale.ENGLISH).endsWith(".xml");
+    }
+    
+    public static String toRelativePath(File baseDir, File path)
+    {
+        return baseDir.toURI().relativize(path.toURI()).toASCIIString();
+    }
+    
+    public static boolean isPropertyFile(String filename)
+    {
+        return filename.toLowerCase(Locale.ENGLISH).endsWith(".properties");
+    }
+    
+    public static String separators(String path)
+    {
+        StringBuilder ret = new StringBuilder();
+        for (char c : path.toCharArray())
+        {
+            if ((c == '/') || (c == '\\'))
+            {
+                ret.append(File.separatorChar);
+            }
+            else
+            {
+                ret.append(c);
+            }
+        }
+        return ret.toString();
+    }
+
+    public static Path toPath(String path)
+    {
+        return FileSystems.getDefault().getPath(FS.separators(path));
+    }
+
+    public static void touch(Path path) throws IOException
+    {
+        FileTime now = FileTime.fromMillis(System.currentTimeMillis());
+        Files.setLastModifiedTime(path,now);
+    }
+
+    public static Path toRealPath(Path path) throws IOException
+    {
+        return path.toRealPath();
+    }
+}
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/FileArg.java b/jetty-start/src/main/java/org/eclipse/jetty/start/FileArg.java
new file mode 100644
index 0000000..0f9314e
--- /dev/null
+++ b/jetty-start/src/main/java/org/eclipse/jetty/start/FileArg.java
@@ -0,0 +1,127 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.start;
+
+public class FileArg
+{
+    public final String moduleName;
+    public final String uri;
+    public final String location;
+    
+    public FileArg(final Module module, final String uriLocation)
+    {
+        this(module == null?(String)null:module.getName(),uriLocation);
+    }
+    
+    public FileArg(final String uriLocation)
+    {
+        this((String)null,uriLocation);
+    }
+    
+    private FileArg(final String moduleName, final String uriLocation)
+    {
+        this.moduleName = moduleName;
+        String parts[] = uriLocation.split("\\|",3);
+        if (parts.length > 2)
+        {
+            StringBuilder err = new StringBuilder();
+            final String LN = System.lineSeparator();
+            err.append("Unrecognized [file] argument: ").append(uriLocation);
+            err.append(LN).append("Valid Syntaxes: ");
+            err.append(LN).append("          <relative-path> - eg: resources/");
+            err.append(LN).append(" or       <absolute-path> - eg: /var/run/jetty.pid");
+            err.append(LN).append(" or <uri>|<relative-path> - eg: http://machine/my.conf|resources/my.conf");
+            err.append(LN).append(" or <uri>|<absolute-path> - eg: http://machine/glob.dat|/opt/run/glob.dat");
+            throw new IllegalArgumentException(err.toString());
+        }
+        if (parts.length == 2)
+        {
+            this.uri = parts[0];
+            this.location = parts[1];
+        }
+        else
+        {
+            this.uri = null;
+            this.location = uriLocation;
+        }
+    }
+    
+    @Override
+    public boolean equals(Object obj)
+    {
+        if (this == obj)
+        {
+            return true;
+        }
+        if (obj == null)
+        {
+            return false;
+        }
+        if (getClass() != obj.getClass())
+        {
+            return false;
+        }
+        FileArg other = (FileArg)obj;
+        if (uri == null)
+        {
+            if (other.uri != null)
+            {
+                return false;
+            }
+        }
+        else if (!uri.equals(other.uri))
+        {
+            return false;
+        }
+        if (location == null)
+        {
+            if (other.location != null)
+            {
+                return false;
+            }
+        }
+        else if (!location.equals(other.location))
+        {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public int hashCode()
+    {
+        final int prime = 31;
+        int result = 1;
+        result = (prime * result) + ((uri == null)?0:uri.hashCode());
+        result = (prime * result) + ((location == null)?0:location.hashCode());
+        return result;
+    }
+
+    @Override
+    public String toString()
+    {
+        StringBuilder builder = new StringBuilder();
+        builder.append("DownloadArg [uri=");
+        builder.append(uri);
+        builder.append(", location=");
+        builder.append(location);
+        builder.append("]");
+        return builder.toString();
+    }
+}
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/FilenameComparator.java b/jetty-start/src/main/java/org/eclipse/jetty/start/FilenameComparator.java
deleted file mode 100644
index 8d89756..0000000
--- a/jetty-start/src/main/java/org/eclipse/jetty/start/FilenameComparator.java
+++ /dev/null
@@ -1,70 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.start;
-
-import java.io.File;
-import java.text.CollationKey;
-import java.text.Collator;
-import java.util.Comparator;
-
-/**
- * Smart comparator for filenames, with natural language sorting, and files sorted before sub directories.
- */
-public class FilenameComparator implements Comparator<File>
-{
-    public static final FilenameComparator INSTANCE = new FilenameComparator();
-    private Collator collator = Collator.getInstance();
-
-    public int compare(File o1, File o2)
-    {
-        if (o1.isFile())
-        {
-            if (o2.isFile())
-            {
-                CollationKey key1 = toKey(o1);
-                CollationKey key2 = toKey(o2);
-                return key1.compareTo(key2);
-            }
-            else
-            {
-                // Push o2 directories below o1 files
-                return -1;
-            }
-        }
-        else
-        {
-            if (o2.isDirectory())
-            {
-                CollationKey key1 = toKey(o1);
-                CollationKey key2 = toKey(o2);
-                return key1.compareTo(key2);
-            }
-            else
-            {
-                // Push o2 files above o1 directories
-                return 1;
-            }
-        }
-    }
-
-    private CollationKey toKey(File f)
-    {
-        return collator.getCollationKey(f.getAbsolutePath());
-    }
-}
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/JarVersion.java b/jetty-start/src/main/java/org/eclipse/jetty/start/JarVersion.java
index 131e63d..e4053c3 100644
--- a/jetty-start/src/main/java/org/eclipse/jetty/start/JarVersion.java
+++ b/jetty-start/src/main/java/org/eclipse/jetty/start/JarVersion.java
@@ -50,7 +50,7 @@ public class JarVersion
                 return entry;
             }
         }
-        
+
         return null;
     }
 
@@ -58,11 +58,15 @@ public class JarVersion
     {
         Attributes attribs = manifest.getMainAttributes();
         if (attribs == null)
+        {
             return null;
+        }
 
         String version = attribs.getValue("Bundle-Version");
         if (version == null)
+        {
             return null;
+        }
 
         return stripV(version);
     }
@@ -71,11 +75,15 @@ public class JarVersion
     {
         Attributes attribs = manifest.getMainAttributes();
         if (attribs == null)
+        {
             return null;
+        }
 
         String version = attribs.getValue(Attributes.Name.IMPLEMENTATION_VERSION);
         if (version == null)
+        {
             return null;
+        }
 
         return stripV(version);
     }
@@ -84,40 +92,48 @@ public class JarVersion
     {
         JarEntry pomProp = findEntry(jar,"META-INF/maven/.*/pom\\.properties$");
         if (pomProp == null)
+        {
             return null;
-        
+        }
+
         InputStream stream = null;
-        
+
         try
         {
             stream = jar.getInputStream(pomProp);
             Properties props = new Properties();
             props.load(stream);
-            
+
             String version = props.getProperty("version");
             if (version == null)
+            {
                 return null;
+            }
 
             return stripV(version);
         }
         finally
         {
-            Main.close(stream);
+            FS.close(stream);
         }
     }
 
     private static String getSubManifestImplVersion(Manifest manifest)
     {
         Map<String, Attributes> entries = manifest.getEntries();
-        
+
         for (Attributes attribs : entries.values())
         {
             if (attribs == null)
+            {
                 continue; // skip entry
+            }
 
             String version = attribs.getValue(Attributes.Name.IMPLEMENTATION_VERSION);
             if (version == null)
+            {
                 continue; // empty, no value, skip it
+            }
 
             return stripV(version);
         }
@@ -127,31 +143,42 @@ public class JarVersion
 
     public static String getVersion(File file)
     {
-        try
+        try (JarFile jar = new JarFile(file))
         {
-            JarFile jar = new JarFile(file);
-            
             String version = null;
-            
+
             Manifest manifest = jar.getManifest();
+            
+            if (manifest == null)
+            {
+                return "(none specified)";
+            }
 
             version = getMainManifestImplVersion(manifest);
             if (version != null)
+            {
                 return version;
-            
+            }
+
             version = getSubManifestImplVersion(manifest);
             if (version != null)
+            {
                 return version;
-            
+            }
+
             version = getBundleVersion(manifest);
             if (version != null)
+            {
                 return version;
-            
+            }
+
             version = getMavenVersion(jar);
             if (version != null)
+            {
                 return version;
-            
-            return "(not specified)";
+            }
+
+            return "(none specified)";
         }
         catch (IOException e)
         {
@@ -162,7 +189,9 @@ public class JarVersion
     private static String stripV(String version)
     {
         if (version.charAt(0) == 'v')
+        {
             return version.substring(1);
+        }
 
         return version;
     }
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/Main.java b/jetty-start/src/main/java/org/eclipse/jetty/start/Main.java
index dc90872..e1faf56 100644
--- a/jetty-start/src/main/java/org/eclipse/jetty/start/Main.java
+++ b/jetty-start/src/main/java/org/eclipse/jetty/start/Main.java
@@ -18,1072 +18,882 @@
 
 package org.eclipse.jetty.start;
 
+import static org.eclipse.jetty.start.UsageException.*;
+
 import java.io.BufferedReader;
-import java.io.Closeable;
+import java.io.BufferedWriter;
 import java.io.File;
-import java.io.FileFilter;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.FileReader;
-import java.io.FilenameFilter;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.LineNumberReader;
 import java.io.OutputStream;
-import java.io.PrintStream;
+import java.io.PrintWriter;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.net.ConnectException;
 import java.net.InetAddress;
 import java.net.Socket;
 import java.net.SocketTimeoutException;
-import java.text.SimpleDateFormat;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
 import java.util.ArrayList;
-import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
-import java.util.Date;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Locale;
-import java.util.Properties;
 import java.util.Set;
+import java.util.regex.Pattern;
+
+import org.eclipse.jetty.start.config.CommandLineConfigSource;
 
-/*-------------------------------------------*/
 /**
+ * Main start class.
  * <p>
- * Main start class. This class is intended to be the main class listed in the MANIFEST.MF of the start.jar archive. It
- * allows an application to be started with the command "java -jar start.jar".
- * </p>
- * 
+ * This class is intended to be the main class listed in the MANIFEST.MF of the start.jar archive. It allows the Jetty Application server to be started with the
+ * command "java -jar start.jar".
  * <p>
- * The behaviour of Main is controlled by the parsing of the {@link Config} "org/eclipse/start/start.config" file
- * obtained as a resource or file.
- * </p>
+ * Argument processing steps:
+ * <ol>
+ * <li>Directory Locations:
+ * <ul>
+ * <li>jetty.home=[directory] (the jetty.home location)</li>
+ * <li>jetty.base=[directory] (the jetty.base location)</li>
+ * </ul>
+ * </li>
+ * <li>Start Logging behavior:
+ * <ul>
+ * <li>--debug (debugging enabled)</li>
+ * <li>--start-log-file=logs/start.log (output start logs to logs/start.log location)</li>
+ * </ul>
+ * </li>
+ * <li>Module Resolution</li>
+ * <li>Properties Resolution</li>
+ * <li>Present Optional Informational Options</li>
+ * <li>Normal Startup</li>
+ * </li>
+ * </ol>
  */
 public class Main
 {
-    private static final String START_LOG_FILENAME = "start.log";
-    private static final SimpleDateFormat START_LOG_ROLLOVER_DATEFORMAT = new SimpleDateFormat("yyyy_MM_dd-HHmmSSSSS.'" + START_LOG_FILENAME + "'");
-
+    private static final String EXITING_LICENSE_NOT_ACKNOWLEDGED = "Exiting: license not acknowledged!";
     private static final int EXIT_USAGE = 1;
-    private static final int ERR_LOGGING = -1;
-    private static final int ERR_INVOKE_MAIN = -2;
-    private static final int ERR_NOT_STOPPED = -4;
-    private static final int ERR_UNKNOWN = -5;
-    private boolean _showUsage = false;
-    private boolean _dumpVersions = false;
-    private boolean _listConfig = false;
-    private boolean _listOptions = false;
-    private boolean _dryRun = false;
-    private boolean _exec = false;
-    private final Config _config = new Config();
-    private final Set<String> _sysProps = new HashSet<String>();
-    private final List<String> _jvmArgs = new ArrayList<String>();
-    private String _startConfig = null;
-
-    private String _jettyHome;
+
+    public static String join(Collection<?> objs, String delim)
+    {
+        if (objs==null)
+        {
+            return "";
+        }
+        StringBuilder str = new StringBuilder();
+        boolean needDelim = false;
+        for (Object obj : objs)
+        {
+            if (needDelim)
+            {
+                str.append(delim);
+            }
+            str.append(obj);
+            needDelim = true;
+        }
+        return str.toString();
+    }
 
     public static void main(String[] args)
     {
         try
         {
             Main main = new Main();
-            List<String> arguments = main.expandCommandLine(args);
-            List<String> xmls = main.processCommandLine(arguments);
-            if (xmls != null)
-                main.start(xmls);
+            StartArgs startArgs = main.processCommandLine(args);
+            main.start(startArgs);
+        }
+        catch (UsageException e)
+        {
+            System.err.println(e.getMessage());
+            usageExit(e.getCause(),e.getExitCode());
         }
         catch (Throwable e)
         {
-            usageExit(e,ERR_UNKNOWN);
+            usageExit(e,UsageException.ERR_UNKNOWN);
         }
     }
 
-    Main() throws IOException
+    static void usageExit(int exit)
     {
-        _jettyHome = System.getProperty("jetty.home",".");
-        _jettyHome = new File(_jettyHome).getCanonicalPath();
+        usageExit(null,exit);
     }
 
-    public List<String> expandCommandLine(String[] args) throws Exception
+    static void usageExit(Throwable t, int exit)
     {
-        List<String> arguments = new ArrayList<String>();
-
-        // add the command line args and look for start.ini args
-        boolean ini = false;
-        for (String arg : args)
+        if (t != null)
         {
-            if (arg.startsWith("--ini=") || arg.equals("--ini"))
-            {
-                ini = true;
-                if (arg.length() > 6)
-                {
-                    arguments.addAll(loadStartIni(new File(arg.substring(6))));
-                }
-            }
-            else if (arg.startsWith("--config="))
-            {
-                _startConfig = arg.substring(9);
-            }
-            else
-            {
-                arguments.add(arg);
-            }
+            t.printStackTrace(System.err);
         }
-
-        // if no non-option inis, add the start.ini and start.d
-        if (!ini)
-        {
-            arguments.addAll(0,parseStartIniFiles());
-        }
-
-        return arguments;
+        System.err.println();
+        System.err.println("Usage: java -jar start.jar [options] [properties] [configs]");
+        System.err.println("       java -jar start.jar --help  # for more information");
+        System.exit(exit);
     }
 
-    List<String> parseStartIniFiles()
-    {
-        List<String> ini_args = new ArrayList<String>();
-        File start_ini = new File(_jettyHome,"start.ini");
-        if (start_ini.exists())
-            ini_args.addAll(loadStartIni(start_ini));
+    private BaseHome baseHome;
+    private StartArgs startupArgs;
 
-        File start_d = new File(_jettyHome,"start.d");
-        if (start_d.isDirectory())
-        {
-            File[] inis = start_d.listFiles(new FilenameFilter()
-            {
-                public boolean accept(File dir, String name)
-                {
-                    return name.toLowerCase(Locale.ENGLISH).endsWith(".ini");
-                }
-            });
-            Arrays.sort(inis);
-            for (File i : inis)
-                ini_args.addAll(loadStartIni(i));
-        }
-        return ini_args;
+    public Main() throws IOException
+    {
     }
 
-    public List<String> processCommandLine(List<String> arguments) throws Exception
+    private void copyInThread(final InputStream in, final OutputStream out)
     {
-        // The XML Configuration Files to initialize with
-        List<String> xmls = new ArrayList<String>();
-
-        // Process the arguments
-        int startup = 0;
-        for (String arg : arguments)
+        new Thread(new Runnable()
         {
-            if ("--help".equals(arg) || "-?".equals(arg))
-            {
-                _showUsage = true;
-                continue;
-            }
-
-            if ("--stop".equals(arg))
-            {
-                int port = Integer.parseInt(Config.getProperty("STOP.PORT","-1"));
-                String key = Config.getProperty("STOP.KEY",null);
-                int timeout = Integer.parseInt(Config.getProperty("STOP.WAIT","0"));
-                stop(port,key,timeout);
-                return null;
-            }
-
-            if ("--version".equals(arg) || "-v".equals(arg) || "--info".equals(arg))
-            {
-                _dumpVersions = true;
-                continue;
-            }
-
-            if ("--list-modes".equals(arg) || "--list-options".equals(arg))
-            {
-                _listOptions = true;
-                continue;
-            }
-
-            if ("--list-config".equals(arg))
-            {
-                _listConfig = true;
-                continue;
-            }
-
-            if ("--exec-print".equals(arg) || "--dry-run".equals(arg))
-            {
-                _dryRun = true;
-                continue;
-            }
-
-            if ("--exec".equals(arg))
-            {
-                _exec = true;
-                continue;
-            }
-
-            // Special internal indicator that jetty was started by the jetty.sh Daemon
-            if ("--daemon".equals(arg))
+            @Override
+            public void run()
             {
-                File startDir = new File(System.getProperty("jetty.logs","logs"));
-                if (!startDir.exists() || !startDir.canWrite())
-                    startDir = new File(".");
-
-                File startLog = new File(startDir,START_LOG_ROLLOVER_DATEFORMAT.format(new Date()));
-
-                if (!startLog.exists() && !startLog.createNewFile())
+                try
                 {
-                    // Output about error is lost in majority of cases.
-                    System.err.println("Unable to create: " + startLog.getAbsolutePath());
-                    // Toss a unique exit code indicating this failure.
-                    usageExit(ERR_LOGGING);
+                    byte[] buf = new byte[1024];
+                    int len = in.read(buf);
+                    while (len > 0)
+                    {
+                        out.write(buf,0,len);
+                        len = in.read(buf);
+                    }
                 }
-
-                if (!startLog.canWrite())
+                catch (IOException e)
                 {
-                    // Output about error is lost in majority of cases.
-                    System.err.println("Unable to write to: " + startLog.getAbsolutePath());
-                    // Toss a unique exit code indicating this failure.
-                    usageExit(ERR_LOGGING);
+                    // e.printStackTrace();
                 }
-                PrintStream logger = new PrintStream(new FileOutputStream(startLog,false));
-                System.setOut(logger);
-                System.setErr(logger);
-                System.out.println("Establishing " + START_LOG_FILENAME + " on " + new Date());
-                continue;
             }
 
-            if (arg.startsWith("--pre="))
-            {
-                xmls.add(startup++,arg.substring(6));
-                continue;
-            }
+        }).start();
+    }
 
-            if (arg.startsWith("-D"))
+    private void initFile(StartArgs args, FileArg farg)
+    {
+        try
+        {
+            Path file = baseHome.getBasePath(farg.location);
+            
+            StartLog.debug("[init-file] %s module specified file %s",file.toAbsolutePath(),(FS.exists(file)?"[Exists!]":""));
+            if (FS.exists(file))
             {
-                String[] assign = arg.substring(2).split("=",2);
-                _sysProps.add(assign[0]);
-                switch (assign.length)
-                {
-                    case 2:
-                        System.setProperty(assign[0],assign[1]);
-                        break;
-                    case 1:
-                        System.setProperty(assign[0],"");
-                        break;
-                    default:
-                        break;
-                }
-                continue;
+                // file already initialized / downloaded, skip it
+                return;
             }
 
-            if (arg.startsWith("-"))
+            if (farg.uri!=null)
             {
-                _jvmArgs.add(arg);
-                continue;
-            }
+                URL url = new URL(farg.uri);
 
-            // Is this a Property?
-            if (arg.indexOf('=') >= 0)
-            {
-                String[] assign = arg.split("=",2);
+                StartLog.log("DOWNLOAD", "%s to %s", url, farg.location);
 
-                switch (assign.length)
+                FS.ensureDirectoryExists(file.getParent());
+                
+                if (args.isTestingModeEnabled())
                 {
-                    case 2:
-                        if ("OPTIONS".equals(assign[0]))
-                        {
-                            String opts[] = assign[1].split(",");
-                            for (String opt : opts)
-                                _config.addActiveOption(opt.trim());
-                        }
-                        else
-                        {
-                            this._config.setProperty(assign[0],assign[1]);
-                        }
-                        break;
-                    case 1:
-                        this._config.setProperty(assign[0],null);
-                        break;
-                    default:
-                        break;
+                    StartLog.log("TESTING MODE", "Skipping download of " + url);
+                    return;
                 }
 
-                continue;
-            }
-
-            // Anything else is considered an XML file.
-            if (xmls.contains(arg))
-            {
-                System.out.println("WARN: Argument '" + arg + "' specified multiple times. Check start.ini?");
-                System.out.println("Use \"java -jar start.jar --help\" for more information.");
-            }
-            xmls.add(arg);
-        }
-
-        return xmls;
-    }
-
-    private void usage()
-    {
-        String usageResource = "org/eclipse/jetty/start/usage.txt";
-        InputStream usageStream = getClass().getClassLoader().getResourceAsStream(usageResource);
-
-        if (usageStream == null)
-        {
-            System.err.println("ERROR: detailed usage resource unavailable");
-            usageExit(EXIT_USAGE);
-        }
-
-        BufferedReader buf = null;
-        try
-        {
-            buf = new BufferedReader(new InputStreamReader(usageStream));
-            String line;
-
-            while ((line = buf.readLine()) != null)
-            {
-                if (line.endsWith("@") && line.indexOf('@') != line.lastIndexOf('@'))
+                byte[] buf = new byte[8192];
+                try (InputStream in = url.openStream(); 
+                     OutputStream out = Files.newOutputStream(file,StandardOpenOption.CREATE_NEW,StandardOpenOption.WRITE))
                 {
-                    String indent = line.substring(0,line.indexOf("@"));
-                    String info = line.substring(line.indexOf('@'),line.lastIndexOf('@'));
-
-                    if (info.equals("@OPTIONS"))
+                    while (true)
                     {
-                        List<String> sortedOptions = new ArrayList<String>();
-                        sortedOptions.addAll(_config.getSectionIds());
-                        Collections.sort(sortedOptions);
+                        int len = in.read(buf);
 
-                        for (String option : sortedOptions)
+                        if (len > 0)
                         {
-                            if ("*".equals(option) || option.trim().length() == 0)
-                                continue;
-                            System.out.print(indent);
-                            System.out.println(option);
+                            out.write(buf,0,len);
                         }
-                    }
-                    else if (info.equals("@CONFIGS"))
-                    {
-                        File etc = new File(System.getProperty("jetty.home","."),"etc");
-                        if (!etc.exists() || !etc.isDirectory())
+                        if (len < 0)
                         {
-                            System.out.print(indent);
-                            System.out.println("Unable to find/list " + etc);
-                            continue;
-                        }
-
-                        File configs[] = etc.listFiles(new FileFilter()
-                        {
-                            public boolean accept(File path)
-                            {
-                                if (!path.isFile())
-                                {
-                                    return false;
-                                }
-
-                                String name = path.getName().toLowerCase(Locale.ENGLISH);
-                                return (name.startsWith("jetty") && name.endsWith(".xml"));
-                            }
-                        });
-
-                        List<File> configFiles = new ArrayList<File>();
-                        configFiles.addAll(Arrays.asList(configs));
-                        Collections.sort(configFiles);
-
-                        for (File configFile : configFiles)
-                        {
-                            System.out.print(indent);
-                            System.out.print("etc/");
-                            System.out.println(configFile.getName());
-                        }
-                    }
-                    else if (info.equals("@STARTINI"))
-                    {
-                        List<String> ini = loadStartIni(new File(_jettyHome,"start.ini"));
-                        if (ini != null && ini.size() > 0)
-                        {
-                            for (String a : ini)
-                            {
-                                System.out.print(indent);
-                                System.out.println(a);
-                            }
-                        }
-                        else
-                        {
-                            System.out.print(indent);
-                            System.out.println("none");
+                            break;
                         }
                     }
                 }
-                else
-                {
-                    System.out.println(line);
-                }
             }
-        }
-        catch (IOException e)
-        {
-            usageExit(e,EXIT_USAGE);
-        }
-        finally
-        {
-            close(buf);
-        }
-        System.exit(EXIT_USAGE);
-    }
-
-    public void invokeMain(ClassLoader classloader, String classname, List<String> args) throws IllegalAccessException, InvocationTargetException,
-            NoSuchMethodException, ClassNotFoundException
-    {
-        Class<?> invoked_class = null;
-
-        try
-        {
-            invoked_class = classloader.loadClass(classname);
-        }
-        catch (ClassNotFoundException e)
-        {
-            e.printStackTrace();
-        }
-
-        if (Config.isDebug() || invoked_class == null)
-        {
-            if (invoked_class == null)
+            else if (farg.location.endsWith("/"))
             {
-                System.err.println("ClassNotFound: " + classname);
+                StartLog.log("MKDIR",baseHome.toShortForm(file));
+                FS.ensureDirectoryExists(file);
             }
             else
             {
-                System.err.println(classname + " " + invoked_class.getPackage().getImplementationVersion());
-            }
-
-            if (invoked_class == null)
-            {
-                usageExit(ERR_INVOKE_MAIN);
-                return;
+                String shortRef = baseHome.toShortForm(file);
+                if (args.isTestingModeEnabled())
+                {
+                    StartLog.log("TESTING MODE","Skipping required file check on: %s",shortRef);
+                    return;
+                }
+                StartLog.warn("MISSING: Required file %s",shortRef);
             }
         }
-
-        String argArray[] = args.toArray(new String[0]);
-
-        Class<?>[] method_param_types = new Class[]
-        { argArray.getClass() };
-
-        Method main = invoked_class.getDeclaredMethod("main",method_param_types);
-        Object[] method_params = new Object[]
-        { argArray };
-        main.invoke(null,method_params);
-    }
-
-    /* ------------------------------------------------------------ */
-    public static void close(Closeable c)
-    {
-        if (c == null)
-        {
-            return;
-        }
-        try
-        {
-            c.close();
-        }
-        catch (IOException e)
+        catch (Exception e)
         {
-            e.printStackTrace(System.err);
+            StartLog.warn("ERROR: processing %s%n%s",farg,e);
+            StartLog.warn(e);
+            usageExit(EXIT_USAGE);
         }
     }
 
-    /* ------------------------------------------------------------ */
-    public void start(List<String> xmls) throws IOException, InterruptedException
+    private void dumpClasspathWithVersions(Classpath classpath)
     {
-        // Load potential Config (start.config)
-        List<String> configuredXmls = loadConfig(xmls);
-
-        // No XML defined in start.config or command line. Can't execute.
-        if (configuredXmls.isEmpty())
-        {
-            throw new FileNotFoundException("No XML configuration files specified in start.config or command line.");
-        }
-
-        // Normalize the XML config options passed on the command line.
-        configuredXmls = resolveXmlConfigs(configuredXmls);
-
-        // Get Desired Classpath based on user provided Active Options.
-        Classpath classpath = _config.getActiveClasspath();
-
-        System.setProperty("java.class.path",classpath.toString());
-        ClassLoader cl = classpath.getClassLoader();
-        if (Config.isDebug())
-        {
-            System.err.println("java.class.path=" + System.getProperty("java.class.path"));
-            System.err.println("jetty.home=" + System.getProperty("jetty.home"));
-            System.err.println("java.home=" + System.getProperty("java.home"));
-            System.err.println("java.io.tmpdir=" + System.getProperty("java.io.tmpdir"));
-            System.err.println("java.class.path=" + classpath);
-            System.err.println("classloader=" + cl);
-            System.err.println("classloader.parent=" + cl.getParent());
-            System.err.println("properties=" + Config.getProperties());
-        }
-
-        // Show the usage information and return
-        if (_showUsage)
-        {
-            usage();
-            return;
-        }
-
-        // Show the version information and return
-        if (_dumpVersions)
-        {
-            showClasspathWithVersions(classpath);
-            return;
-        }
-
-        // Show all options with version information
-        if (_listOptions)
-        {
-            showAllOptionsWithVersions();
-            return;
-        }
-
-        if (_listConfig)
-        {
-            listConfig();
-            return;
-        }
-
-        // Show Command Line to execute Jetty
-        if (_dryRun)
-        {
-            CommandLineBuilder cmd = buildCommandLine(classpath,configuredXmls);
-            System.out.println(cmd.toString());
-            return;
-        }
-
-        // execute Jetty in another JVM
-        if (_exec)
+        StartLog.endStartLog();
+        System.out.println();
+        System.out.println("Jetty Server Classpath:");
+        System.out.println("-----------------------");
+        if (classpath.count() == 0)
         {
-            CommandLineBuilder cmd = buildCommandLine(classpath,configuredXmls);
-
-            ProcessBuilder pbuilder = new ProcessBuilder(cmd.getArgs());
-            final Process process = pbuilder.start();
-            Runtime.getRuntime().addShutdownHook(new Thread()
-            {
-                @Override
-                public void run()
-                {
-                    Config.debug("Destroying " + process);
-                    process.destroy();
-                }
-            });
-
-            copyInThread(process.getErrorStream(),System.err);
-            copyInThread(process.getInputStream(),System.out);
-            copyInThread(System.in,process.getOutputStream());
-            process.waitFor();
-            System.exit(0); // exit JVM when child process ends.
+            System.out.println("No classpath entries and/or version information available show.");
             return;
         }
 
-        if (_jvmArgs.size() > 0 || _sysProps.size() > 0)
-        {
-            System.err.println("WARNING: System properties and/or JVM args set.  Consider using --dry-run or --exec");
-        }
-
-        // Set current context class loader to what is selected.
-        Thread.currentThread().setContextClassLoader(cl);
-
-        // Invoke the Main Class
-        try
-        {
-            // Get main class as defined in start.config
-            String classname = _config.getMainClassname();
-
-            // Check for override of start class (via "jetty.server" property)
-            String mainClass = System.getProperty("jetty.server");
-            if (mainClass != null)
-            {
-                classname = mainClass;
-            }
-
-            // Check for override of start class (via "main.class" property)
-            mainClass = System.getProperty("main.class");
-            if (mainClass != null)
-            {
-                classname = mainClass;
-            }
-
-            Config.debug("main.class=" + classname);
+        System.out.println("Version Information on " + classpath.count() + " entr" + ((classpath.count() > 1)?"ies":"y") + " in the classpath.");
+        System.out.println("Note: order presented here is how they would appear on the classpath.");
+        System.out.println("      changes to the --module=name command line options will be reflected here.");
 
-            invokeMain(cl,classname,configuredXmls);
-        }
-        catch (Exception e)
+        int i = 0;
+        for (File element : classpath.getElements())
         {
-            usageExit(e,ERR_INVOKE_MAIN);
+            System.out.printf("%2d: %24s | %s\n",i++,getVersion(element),baseHome.toShortForm(element));
         }
     }
 
-    private void copyInThread(final InputStream in, final OutputStream out)
+    public BaseHome getBaseHome()
     {
-        new Thread(new Runnable()
-        {
-            public void run()
-            {
-                try
-                {
-                    byte[] buf = new byte[1024];
-                    int len = in.read(buf);
-                    while (len > 0)
-                    {
-                        out.write(buf,0,len);
-                        len = in.read(buf);
-                    }
-                }
-                catch (IOException e)
-                {
-                    // e.printStackTrace();
-                }
-            }
-
-        }).start();
+        return baseHome;
     }
 
-    private String resolveXmlConfig(String xmlFilename) throws FileNotFoundException
+    private String getVersion(File element)
     {
-        if (!xmlFilename.toLowerCase(Locale.ENGLISH).endsWith(".xml"))
-        {
-            // Nothing to resolve.
-            return xmlFilename;
-        }
-
-        File xml = new File(xmlFilename);
-        if (xml.exists() && xml.isFile())
-        {
-            return xml.getAbsolutePath();
-        }
-
-        xml = new File(_jettyHome,fixPath(xmlFilename));
-        if (xml.exists() && xml.isFile())
+        if (element.isDirectory())
         {
-            return xml.getAbsolutePath();
+            return "(dir)";
         }
 
-        xml = new File(_jettyHome,fixPath("etc/" + xmlFilename));
-        if (xml.exists() && xml.isFile())
+        if (element.isFile())
         {
-            return xml.getAbsolutePath();
+            String name = element.getName().toLowerCase(Locale.ENGLISH);
+            if (name.endsWith(".jar"))
+            {
+                return JarVersion.getVersion(element);
+            }
         }
 
-        throw new FileNotFoundException("Unable to find XML Config: " + xmlFilename);
+        return "";
     }
 
-    CommandLineBuilder buildCommandLine(Classpath classpath, List<String> xmls) throws IOException
+    public void invokeMain(ClassLoader classloader, StartArgs args) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException, ClassNotFoundException, IOException
     {
-        CommandLineBuilder cmd = new CommandLineBuilder(findJavaBin());
+        Class<?> invoked_class = null;
+        String mainclass = args.getMainClassname();
 
-        for (String x : _jvmArgs)
+        try
         {
-            cmd.addArg(x);
+            invoked_class = classloader.loadClass(mainclass);
         }
-        cmd.addRawArg("-Djetty.home=" + _jettyHome);
-
-        // Special Stop/Shutdown properties
-        ensureSystemPropertySet("STOP.PORT");
-        ensureSystemPropertySet("STOP.KEY");
-
-        // System Properties
-        for (String p : _sysProps)
+        catch (ClassNotFoundException e)
         {
-            String v = System.getProperty(p);
-            cmd.addEqualsArg("-D" + p,v);
+            System.out.println("WARNING: Nothing to start, exiting ...");
+            StartLog.debug(e);
+            usageExit(ERR_INVOKE_MAIN);
+            return;
         }
 
-        cmd.addArg("-cp");
-        cmd.addRawArg(classpath.toString());
-        cmd.addRawArg(_config.getMainClassname());
+        StartLog.debug("%s - %s",invoked_class,invoked_class.getPackage().getImplementationVersion());
 
-        // Check if we need to pass properties as a file
-        Properties properties = Config.getProperties();
-        if (properties.size() > 0)
-        {
-            File prop_file = File.createTempFile("start",".properties");
-            if (!_dryRun)
-                prop_file.deleteOnExit();
-            properties.store(new FileOutputStream(prop_file),"start.jar properties");
-            cmd.addArg(prop_file.getAbsolutePath());
-        }
+        CommandLineBuilder cmd = args.getMainArgs(baseHome,false);
+        String argArray[] = cmd.getArgs().toArray(new String[0]);
+        StartLog.debug("Command Line Args: %s",cmd.toString());
 
-        for (String xml : xmls)
-        {
-            cmd.addRawArg(xml);
-        }
-        return cmd;
+        Class<?>[] method_param_types = new Class[]
+        { argArray.getClass() };
+
+        Method main = invoked_class.getDeclaredMethod("main",method_param_types);
+        Object[] method_params = new Object[] { argArray };
+        StartLog.endStartLog();
+        main.invoke(null,method_params);
     }
 
-    /**
-     * Ensure that the System Properties are set (if defined as a System property, or start.config property, or
-     * start.ini property)
-     * 
-     * @param key
-     *            the key to be sure of
-     */
-    private void ensureSystemPropertySet(String key)
+    public void listConfig(StartArgs args)
     {
-        if (_sysProps.contains(key))
-        {
-            return; // done
-        }
+        StartLog.endStartLog();
+        
+        // Dump Jetty Home / Base
+        args.dumpEnvironment(baseHome);
 
-        Properties props = Config.getProperties();
-        if (props.containsKey(key))
-        {
-            String val = props.getProperty(key,null);
-            if (val == null)
-            {
-                return; // no value to set
-            }
-            // setup system property
-            _sysProps.add(key);
-            System.setProperty(key,val);
-        }
-    }
+        // Dump JVM Args
+        args.dumpJvmArgs();
 
-    private String findJavaBin()
-    {
-        File javaHome = new File(System.getProperty("java.home"));
-        if (!javaHome.exists())
-        {
-            return null;
-        }
+        // Dump System Properties
+        args.dumpSystemProperties();
 
-        File javabin = findExecutable(javaHome,"bin/java");
-        if (javabin != null)
-        {
-            return javabin.getAbsolutePath();
-        }
+        // Dump Properties
+        args.dumpProperties();
 
-        javabin = findExecutable(javaHome,"bin/java.exe");
-        if (javabin != null)
-        {
-            return javabin.getAbsolutePath();
-        }
+        // Dump Classpath
+        dumpClasspathWithVersions(args.getClasspath());
 
-        return "java";
+        // Dump Resolved XMLs
+        args.dumpActiveXmls(baseHome);
     }
 
-    private File findExecutable(File root, String path)
+    private void listModules(StartArgs args)
     {
-        String npath = path.replace('/',File.separatorChar);
-        File exe = new File(root,npath);
-        if (!exe.exists())
-        {
-            return null;
-        }
-        return exe;
+        StartLog.endStartLog();
+        System.out.println();
+        System.out.println("Jetty All Available Modules:");
+        System.out.println("----------------------------");
+        args.getAllModules().dump();
+
+        // Dump Enabled Modules
+        System.out.println();
+        System.out.println("Jetty Active Module Tree:");
+        System.out.println("-------------------------");
+        Modules modules = args.getAllModules();
+        modules.dumpEnabledTree();
     }
 
-    private void showAllOptionsWithVersions()
-    {
-        Set<String> sectionIds = _config.getSectionIds();
+    /**
+     * Build out INI file.
+     * <p>
+     * This applies equally for either <code>${jetty.base}/start.ini</code> or
+     * <code>${jetty.base}/start.d/${name}.ini</code> 
+     * 
+     * @param args the arguments of what modules are enabled
+     * @param name the name of the module to based the build of the ini
+     * @param topLevel 
+     * @param appendStartIni true to append to <code>${jetty.base}/start.ini</code>, 
+     * false to create a <code>${jetty.base}/start.d/${name}.ini</code> entry instead.
+     * @throws IOException
+     */
+    private void buildIni(StartArgs args, String name, boolean topLevel, boolean appendStartIni) throws IOException
+    {        
+        // Find the start.d relative to the base directory only.
+        Path start_d = baseHome.getBasePath("start.d");
 
-        StringBuffer msg = new StringBuffer();
-        msg.append("There ");
-        if (sectionIds.size() > 1)
+        // Is this a module?
+        Modules modules = args.getAllModules();
+        Module module = modules.get(name);
+        if (module == null)
         {
-            msg.append("are ");
-        }
-        else
-        {
-            msg.append("is ");
+            StartLog.warn("ERROR: No known module for %s",name);
+            return;
         }
-        msg.append(String.valueOf(sectionIds.size()));
-        msg.append(" OPTION");
-        if (sectionIds.size() > 1)
+        
+        boolean transitive = module.isEnabled() && (module.getSources().size() == 0);
+
+        // Find any named ini file and check it follows the convention
+        Path start_ini = baseHome.getBasePath("start.ini");
+        String short_start_ini = baseHome.toShortForm(start_ini);
+        Path startd_ini = start_d.resolve(name + ".ini");
+        String short_startd_ini = baseHome.toShortForm(startd_ini);
+        StartIni module_ini = null;
+        if (FS.exists(startd_ini))
         {
-            msg.append("s");
+            module_ini = new StartIni(startd_ini);
+            if (module_ini.getLineMatches(Pattern.compile("--module=(.*, *)*" + name)).size() == 0)
+            {
+                StartLog.warn("ERROR: %s is not enabled in %s!",name,short_startd_ini);
+                return;
+            }
         }
-        msg.append(" available to use.");
-        System.out.println(msg);
-        System.out.println("Each option is listed along with associated available classpath entries,  in the order that they would appear from that mode.");
-        System.out.println("Note: If using multiple options (eg: 'Server,servlet,webapp,jms,jmx') "
-                + "then overlapping entries will not be repeated in the eventual classpath.");
-        System.out.println();
-        System.out.printf("${jetty.home} = %s%n",_jettyHome);
-        System.out.println();
 
-        for (String sectionId : sectionIds)
+        if (!args.isApproveAllLicenses())
         {
-            if (Config.DEFAULT_SECTION.equals(sectionId))
+            if (!module.hasFiles(baseHome) && !module.acknowledgeLicense())
             {
-                System.out.println("GLOBAL option (Prepended Entries)");
+                StartLog.warn(EXITING_LICENSE_NOT_ACKNOWLEDGED);
+                System.exit(1);
             }
-            else if ("*".equals(sectionId))
+        }
+        
+        boolean buildIni=false;
+        if (module.isEnabled())
+        {
+            // is it an explicit request to create an ini file?
+            if (topLevel && !FS.exists(startd_ini) && !appendStartIni)
             {
-                System.out.println("GLOBAL option (Appended Entries) (*)");
+                buildIni=true;
             }
-            else
+            // else is it transitive
+            else if (transitive)
             {
-                System.out.printf("Option [%s]",sectionId);
-                if (Character.isUpperCase(sectionId.charAt(0)))
+                if (module.hasDefaultConfig())
                 {
-                    System.out.print(" (Aggregate)");
+                    buildIni = true;
+                    StartLog.info("%-15s initialised transitively",name);
                 }
-                System.out.println();
-            }
-            System.out.println("-------------------------------------------------------------");
-
-            Classpath sectionCP = _config.getSectionClasspath(sectionId);
-
-            if (sectionCP.isEmpty())
-            {
-                System.out.println("Empty option, no classpath entries active.");
-                System.out.println();
-                continue;
             }
-
-            int i = 0;
-            for (File element : sectionCP.getElements())
+            // else must be initialized explicitly
+            else 
             {
-                String elementPath = element.getAbsolutePath();
-                if (elementPath.startsWith(_jettyHome))
+                for (String source : module.getSources())
                 {
-                    elementPath = "${jetty.home}" + elementPath.substring(_jettyHome.length());
+                    StartLog.info("%-15s initialised in %s",name,baseHome.toShortForm(source));
                 }
-                System.out.printf("%2d: %20s | %s\n",i++,getVersion(element),elementPath);
             }
-
-            System.out.println();
         }
-    }
-
-    private void showClasspathWithVersions(Classpath classpath)
-    {
-        // Iterate through active classpath, and fetch Implementation Version from each entry (if present)
-        // to dump to end user.
-
-        System.out.println("Active Options: " + _config.getActiveOptions());
-
-        if (classpath.count() == 0)
+        else 
         {
-            System.out.println("No version information available show.");
-            return;
+            buildIni=true;
         }
+        
+        String source = "<transitive>";
 
-        System.out.println("Version Information on " + classpath.count() + " entr" + ((classpath.count() > 1)?"ies":"y") + " in the classpath.");
-        System.out.println("Note: order presented here is how they would appear on the classpath.");
-        System.out.println("      changes to the OPTIONS=[option,option,...] command line option will be reflected here.");
+        // If we need an ini
+        if (buildIni)
+        {
+            // File BufferedWriter
+            BufferedWriter writer = null;
+            PrintWriter out = null;
+            try
+            {
+                if (appendStartIni)
+                {
+                    source = short_start_ini;
+                    StartLog.info("%-15s initialised in %s (appended)",name,source);
+                    writer = Files.newBufferedWriter(start_ini,StandardCharsets.UTF_8,StandardOpenOption.CREATE,StandardOpenOption.APPEND);
+                    out = new PrintWriter(writer);
+                }
+                else
+                {
+                    // Create the directory if needed
+                    FS.ensureDirectoryExists(start_d);
+                    FS.ensureDirectoryWritable(start_d);
+                    source = short_startd_ini;
+                    StartLog.info("%-15s initialised in %s (created)",name,source);
+                    writer = Files.newBufferedWriter(startd_ini,StandardCharsets.UTF_8,StandardOpenOption.CREATE_NEW,StandardOpenOption.WRITE);
+                    out = new PrintWriter(writer);
+                }
 
-        int i = 0;
-        for (File element : classpath.getElements())
+                if (appendStartIni)
+                {
+                    out.println();
+                }
+                out.println("# --------------------------------------- ");
+                out.println("# Module: " + name);
+
+                out.println("--module=" + name);
+                
+                args.parse("--module=" + name,source);
+                args.parseModule(module);
+                
+                for (String line : module.getDefaultConfig())
+                {
+                    out.println(line);
+                }
+            }
+            finally
+            {
+                if (out != null)
+                {
+                    out.close();
+                }
+            }
+        }
+        
+        modules.enable(name,Collections.singletonList(source));
+        
+        // Also list other places this module is enabled
+        for (String src : module.getSources())
         {
-            String elementPath = element.getAbsolutePath();
-            if (elementPath.startsWith(_jettyHome))
+            StartLog.debug("also enabled in: %s",src);
+            if (!short_start_ini.equals(src))
             {
-                elementPath = "${jetty.home}" + elementPath.substring(_jettyHome.length());
+                StartLog.info("%-15s enabled in     %s",name,baseHome.toShortForm(src));
             }
-            System.out.printf("%2d: %20s | %s\n",i++,getVersion(element),elementPath);
         }
-    }
-
-    private String fixPath(String path)
-    {
-        return path.replace('/',File.separatorChar);
-    }
 
-    private String getVersion(File element)
-    {
-        if (element.isDirectory())
+        // Do downloads now
+        for (String file : module.getFiles())
         {
-            return "(dir)";
+            initFile(args, new FileArg(module,file));
         }
 
-        if (element.isFile())
+        // Process dependencies
+        module.expandProperties(args.getProperties());
+        modules.registerParentsIfMissing(module);
+        modules.buildGraph();
+        
+        // process new ini modules
+        if (topLevel)
         {
-            String name = element.getName().toLowerCase(Locale.ENGLISH);
-            if (name.endsWith(".jar"))
+            List<Module> depends = new ArrayList<>();
+            for (String depend : modules.resolveParentModulesOf(name))
             {
-                return JarVersion.getVersion(element);
+                if (!name.equals(depend))
+                {
+                    Module m = modules.get(depend);
+                    m.setEnabled(true);
+                    depends.add(m);
+                }
             }
-
-            if (name.endsWith(".zip"))
+            Collections.sort(depends,Collections.reverseOrder(new Module.DepthComparator()));
+            
+            Set<String> done = new HashSet<>(0);
+            while (true)
             {
-                return getZipVersion(element);
+                // initialize known dependencies
+                boolean complete=true;
+                for (Module m : depends)
+                {
+                    if (!done.contains(m.getName()))
+                    {
+                        complete=false;
+                        buildIni(args,m.getName(),false,appendStartIni);
+                        done.add(m.getName());
+                    }
+                }
+                
+                if (complete)
+                {
+                    break;
+                }
+                
+                // look for any new ones resolved via expansion
+                depends.clear();
+                for (String depend : modules.resolveParentModulesOf(name))
+                {
+                    if (!name.equals(depend))
+                    {
+                        Module m = modules.get(depend);
+                        m.setEnabled(true);
+                        depends.add(m);
+                    }
+                }
+                Collections.sort(depends,Collections.reverseOrder(new Module.DepthComparator()));
             }
         }
-
-        return "";
     }
 
-    private String getZipVersion(File element)
+    /**
+     * Convenience for <code>processCommandLine(cmdLine.toArray(new String[cmdLine.size()]))</code>
+     */
+    public StartArgs processCommandLine(List<String> cmdLine) throws Exception
     {
-        // TODO - find version in zip file. Look for META-INF/MANIFEST.MF ?
-        return "";
+        return this.processCommandLine(cmdLine.toArray(new String[cmdLine.size()]));
     }
 
-    private List<String> resolveXmlConfigs(List<String> xmls) throws FileNotFoundException
+    public StartArgs processCommandLine(String[] cmdLine) throws Exception
     {
-        List<String> ret = new ArrayList<String>();
-        for (String xml : xmls)
-        {
-            ret.add(resolveXmlConfig(xml));
-        }
+        // Processing Order is important!
+        // ------------------------------------------------------------
+        // 1) Configuration Locations
+        CommandLineConfigSource cmdLineSource = new CommandLineConfigSource(cmdLine);
+        baseHome = new BaseHome(cmdLineSource);
+
+        StartLog.debug("jetty.home=%s",baseHome.getHome());
+        StartLog.debug("jetty.base=%s",baseHome.getBase());
+
+        // ------------------------------------------------------------
+        // 2) Parse everything provided.
+        // This would be the directory information +
+        // the various start inis
+        // and then the raw command line arguments
+        StartLog.debug("Parsing collected arguments");
+        StartArgs args = new StartArgs();
+        args.parse(baseHome.getConfigSources());
+
+        // ------------------------------------------------------------
+        // 3) Module Registration
+        Modules modules = new Modules(baseHome,args);
+        StartLog.debug("Registering all modules");
+        modules.registerAll();
+
+        // ------------------------------------------------------------
+        // 4) Active Module Resolution
+        for (String enabledModule : args.getEnabledModules())
+        {
+            List<String> msources = args.getSources(enabledModule);
+            modules.enable(enabledModule,msources);
+        }
+        
+        StartLog.debug("Building Module Graph");
+        modules.buildGraph();
+
+        args.setAllModules(modules);
+        List<Module> activeModules = modules.resolveEnabled();
+        
+        // ------------------------------------------------------------
+        // 5) Lib & XML Expansion / Resolution
+        args.expandLibs(baseHome);
+        args.expandModules(baseHome,activeModules);
+
+        // ------------------------------------------------------------
+        // 6) Resolve Extra XMLs
+        args.resolveExtraXmls(baseHome);
+        
+        // ------------------------------------------------------------
+        // 9) Resolve Property Files
+        args.resolvePropertyFiles(baseHome);
 
-        return ret;
+        return args;
     }
 
-    private void listConfig()
+    public void start(StartArgs args) throws IOException, InterruptedException
     {
-        InputStream cfgstream = null;
-        try
+        StartLog.debug("StartArgs: %s",args);
+
+        // Get Desired Classpath based on user provided Active Options.
+        Classpath classpath = args.getClasspath();
+
+        System.setProperty("java.class.path",classpath.toString());
+
+        // Show the usage information and return
+        if (args.isHelp())
         {
-            cfgstream = getConfigStream();
-            byte[] buf = new byte[4096];
+            usage(true);
+        }
 
-            int len = 0;
+        // Show the version information and return
+        if (args.isListClasspath())
+        {
+            dumpClasspathWithVersions(classpath);
+        }
 
-            while (len >= 0)
-            {
-                len = cfgstream.read(buf);
-                if (len > 0)
-                    System.out.write(buf,0,len);
-            }
+        // Show configuration
+        if (args.isListConfig())
+        {
+            listConfig(args);
         }
-        catch (Exception e)
+
+        // Show modules
+        if (args.isListModules())
         {
-            usageExit(e,ERR_UNKNOWN);
+            listModules(args);
         }
-        finally
+        
+        // Generate Module Graph File
+        if (args.getModuleGraphFilename() != null)
         {
-            close(cfgstream);
+            Path outputFile = baseHome.getBasePath(args.getModuleGraphFilename());
+            System.out.printf("Generating GraphViz Graph of Jetty Modules at %s%n",baseHome.toShortForm(outputFile));
+            ModuleGraphWriter writer = new ModuleGraphWriter();
+            writer.config(args.getProperties());
+            writer.write(args.getAllModules(),outputFile);
         }
-    }
 
-    /**
-     * Load Configuration.
-     * 
-     * No specific configuration is real until a {@link Config#getCombinedClasspath(java.util.Collection)} is used to
-     * execute the {@link Class} specified by {@link Config#getMainClassname()} is executed.
-     * 
-     * @param xmls
-     *            the command line specified xml configuration options.
-     * @return the list of xml configurations arriving via command line and start.config choices.
-     */
-    private List<String> loadConfig(List<String> xmls)
-    {
-        InputStream cfgstream = null;
-        try
+        // Show Command Line to execute Jetty
+        if (args.isDryRun())
+        {
+            CommandLineBuilder cmd = args.getMainArgs(baseHome,true);
+            System.out.println(cmd.toString(File.separatorChar=='/'?" \\\n":" "));
+        }
+
+        if (args.isStopCommand())
         {
-            // Pass in xmls.size into Config so that conditions based on "nargs" work.
-            _config.setArgCount(xmls.size());
+            doStop(args);
+        }
+        
+        boolean rebuildGraph = false;
 
-            cfgstream = getConfigStream();
+        // Initialize start.ini
+        for (String module : args.getAddToStartIni())
+        {
+            buildIni(args,module,true,true);
+            rebuildGraph = true;
+        }
 
-            // parse the config
-            _config.parse(cfgstream);
+        // Initialize start.d
+        for (String module : args.getAddToStartdIni())
+        {
+            buildIni(args,module,true,false);
+            rebuildGraph = true;
+        }
+        
+        if (rebuildGraph)
+        {
+            args.getAllModules().clearMissing();
+            args.getAllModules().buildGraph();
+        }
+        
+        // If in --create-files, check licenses
+        if(args.isDownload())
+        {
+            if (!args.isApproveAllLicenses())
+            {
+                for (Module module : args.getAllModules().resolveEnabled())
+                {
+                    if (!module.hasFiles(baseHome) && !module.acknowledgeLicense())
+                    {
+                        StartLog.warn(EXITING_LICENSE_NOT_ACKNOWLEDGED);
+                        System.exit(1);
+                    }
+                }
+            }
+        }
 
-            _jettyHome = Config.getProperty("jetty.home",_jettyHome);
-            if (_jettyHome != null)
+        // Check ini files for download possibilities
+        for (FileArg arg : args.getFiles())
+        {
+            Path file = baseHome.getBasePath(arg.location);
+            if (!FS.exists(file) && args.isDownload())
             {
-                _jettyHome = new File(_jettyHome).getCanonicalPath();
-                System.setProperty("jetty.home",_jettyHome);
+                initFile(args, arg);
             }
 
-            // Collect the configured xml configurations.
-            List<String> ret = new ArrayList<String>();
-            ret.addAll(xmls); // add command line provided xmls first.
-            for (String xmlconfig : _config.getXmlConfigs())
+            if (!FS.exists(file))
             {
-                // add xmlconfigs arriving via start.config
-                if (!ret.contains(xmlconfig))
+                boolean isDir = arg.location.endsWith("/");
+                if (isDir)
                 {
-                    ret.add(xmlconfig);
+                    StartLog.log("MKDIR", baseHome.toShortForm(file));
+                    FS.ensureDirectoryExists(file);
+                    /* Startup should not fail to run on missing directories.
+                     * See Bug #427204
+                     */
+                    // args.setRun(false);
                 }
-            }
+                else
+                {
+                    String shortRef = baseHome.toShortForm(file);
+                    if (args.isTestingModeEnabled())
+                    {
+                        StartLog.log("TESTING MODE","Skipping required file check on: %s",shortRef);
+                        return;
+                    }
 
-            return ret;
+                    StartLog.warn("Missing Required File: %s",baseHome.toShortForm(file));
+                    args.setRun(false);
+                    if (arg.uri != null)
+                    {
+                        StartLog.warn("  Can be downloaded From: %s",arg.uri);
+                        StartLog.warn("  Run start.jar --create-files to download");
+                    }
+                }
+            }
         }
-        catch (Exception e)
+        
+        // Informational command line, don't run jetty
+        if (!args.isRun())
         {
-            usageExit(e,ERR_UNKNOWN);
-            return null; // never executed (just here to satisfy javac compiler)
+            return;
         }
-        finally
+        
+        // execute Jetty in another JVM
+        if (args.isExec())
         {
-            close(cfgstream);
+            CommandLineBuilder cmd = args.getMainArgs(baseHome,true);
+            cmd.debug();
+            ProcessBuilder pbuilder = new ProcessBuilder(cmd.getArgs());
+            StartLog.endStartLog();
+            final Process process = pbuilder.start();
+            Runtime.getRuntime().addShutdownHook(new Thread()
+            {
+                @Override
+                public void run()
+                {
+                    StartLog.debug("Destroying " + process);
+                    process.destroy();
+                }
+            });
+
+            copyInThread(process.getErrorStream(),System.err);
+            copyInThread(process.getInputStream(),System.out);
+            copyInThread(System.in,process.getOutputStream());
+            process.waitFor();
+            System.exit(0); // exit JVM when child process ends.
+            return;
         }
-    }
 
-    private InputStream getConfigStream() throws FileNotFoundException
-    {
-        String config = _startConfig;
-        if (config == null || config.length() == 0)
+        if (args.hasJvmArgs() || args.hasSystemProperties())
         {
-            config = System.getProperty("START","org/eclipse/jetty/start/start.config");
+            System.err.println("WARNING: System properties and/or JVM args set.  Consider using --dry-run or --exec");
         }
 
-        Config.debug("config=" + config);
-
-        // Look up config as resource first.
-        InputStream cfgstream = getClass().getClassLoader().getResourceAsStream(config);
+        ClassLoader cl = classpath.getClassLoader();
+        Thread.currentThread().setContextClassLoader(cl);
 
-        // resource not found, try filesystem next
-        if (cfgstream == null)
+        // Invoke the Main Class
+        try
         {
-            cfgstream = new FileInputStream(config);
+            invokeMain(cl, args);
+        }
+        catch (Exception e)
+        {
+            usageExit(e,ERR_INVOKE_MAIN);
         }
+    }
+
+    private void doStop(StartArgs args)
+    {
+        String stopHost = args.getProperties().getString("STOP.HOST");
+        int stopPort = Integer.parseInt(args.getProperties().getString("STOP.PORT"));
+        String stopKey = args.getProperties().getString("STOP.KEY");
+
+        if (args.getProperties().getString("STOP.WAIT") != null)
+        {
+            int stopWait = Integer.parseInt(args.getProperties().getString("STOP.WAIT"));
 
-        return cfgstream;
+            stop(stopHost,stopPort,stopKey,stopWait);
+        }
+        else
+        {
+            stop(stopHost,stopPort,stopKey);
+        }
     }
 
     /**
      * Stop a running jetty instance.
      */
-    public void stop(int port, String key)
+    public void stop(String host, int port, String key)
     {
-        stop(port,key,0);
+        stop(host,port,key,0);
     }
 
-    public void stop(int port, String key, int timeout)
+    public void stop(String host, int port, String key, int timeout)
     {
-        int _port = port;
-        String _key = key;
-
+        if (host==null || host.length()==0)
+            host="127.0.0.1";
+        
         try
         {
-            if (_port <= 0)
+            if (port <= 0)
             {
                 System.err.println("STOP.PORT system property must be specified");
             }
-            if (_key == null)
+            if (key == null)
             {
-                _key = "";
+                key = "";
                 System.err.println("STOP.KEY system property must be specified");
                 System.err.println("Using empty key");
             }
 
-            Socket s = new Socket(InetAddress.getByName("127.0.0.1"),_port);
-            if (timeout > 0)
-                s.setSoTimeout(timeout * 1000);
-            try
+            try (Socket s = new Socket(InetAddress.getByName(host),port))
             {
-                OutputStream out = s.getOutputStream();
-                out.write((_key + "\r\nstop\r\n").getBytes());
-                out.flush();
-
                 if (timeout > 0)
                 {
-                    System.err.printf("Waiting %,d seconds for jetty to stop%n",timeout);
-                    LineNumberReader lin = new LineNumberReader(new InputStreamReader(s.getInputStream()));
-                    String response;
-                    while ((response = lin.readLine()) != null)
+                    s.setSoTimeout(timeout * 1000);
+                }
+
+                try (OutputStream out = s.getOutputStream())
+                {
+                    out.write((key + "\r\nstop\r\n").getBytes());
+                    out.flush();
+
+                    if (timeout > 0)
                     {
-                        Config.debug("Received \"" + response + "\"");
-                        if ("Stopped".equals(response))
-                            System.err.println("Server reports itself as Stopped");
+                        System.err.printf("Waiting %,d seconds for jetty to stop%n",timeout);
+                        LineNumberReader lin = new LineNumberReader(new InputStreamReader(s.getInputStream()));
+                        String response;
+                        while ((response = lin.readLine()) != null)
+                        {
+                            StartLog.debug("Received \"%s\"",response);
+                            if ("Stopped".equals(response))
+                            {
+                                StartLog.warn("Server reports itself as Stopped");
+                            }
+                        }
                     }
                 }
             }
-            finally
-            {
-                s.close();
-            }
         }
         catch (SocketTimeoutException e)
         {
@@ -1100,70 +910,79 @@ public class Main
         }
     }
 
-    static void usageExit(Throwable t, int exit)
-    {
-        t.printStackTrace(System.err);
-        System.err.println();
-        System.err.println("Usage: java -jar start.jar [options] [properties] [configs]");
-        System.err.println("       java -jar start.jar --help  # for more information");
-        System.exit(exit);
-    }
-
-    static void usageExit(int exit)
-    {
-        System.err.println();
-        System.err.println("Usage: java -jar start.jar [options] [properties] [configs]");
-        System.err.println("       java -jar start.jar --help  # for more information");
-        System.exit(exit);
-    }
-
-    /**
-     * Convert a start.ini format file into an argument list.
-     */
-    static List<String> loadStartIni(File ini)
+    public void usage(boolean exit)
     {
-        if (!ini.exists())
+        StartLog.endStartLog();
+        if(!printTextResource("org/eclipse/jetty/start/usage.txt"))
         {
-            System.err.println("Warning - can't find ini file: " + ini);
-            // No start.ini found, skip load.
-            return Collections.emptyList();
+            System.err.println("ERROR: detailed usage resource unavailable");
         }
-
-        List<String> args = new ArrayList<String>();
-
-        FileReader reader = null;
-        BufferedReader buf = null;
-        try
+        if (exit)
         {
-            reader = new FileReader(ini);
-            buf = new BufferedReader(reader);
-
-            String arg;
-            while ((arg = buf.readLine()) != null)
+            System.exit(EXIT_USAGE);
+        }
+    }
+    
+    public static boolean printTextResource(String resourceName)
+    {
+        boolean resourcePrinted = false;
+        try (InputStream stream = Thread.currentThread().getContextClassLoader().getResourceAsStream(resourceName))
+        {
+            if (stream != null)
             {
-                arg = arg.trim();
-                if (arg.length() == 0 || arg.startsWith("#"))
+                try (InputStreamReader reader = new InputStreamReader(stream); BufferedReader buf = new BufferedReader(reader))
                 {
-                    continue;
+                    resourcePrinted = true;
+                    String line;
+                    while ((line = buf.readLine()) != null)
+                    {
+                        System.out.println(line);
+                    }
                 }
-                args.add(arg);
+            }
+            else
+            {
+                System.out.println("Unable to find resource: " + resourceName);
             }
         }
         catch (IOException e)
         {
-            usageExit(e,ERR_UNKNOWN);
+            StartLog.warn(e);
         }
-        finally
+
+        return resourcePrinted;
+    }
+
+    // ------------------------------------------------------------
+    // implement Apache commons daemon (jsvc) lifecycle methods (init, start, stop, destroy)
+    public void init(String[] args) throws Exception
+    {
+        try
+        {
+            startupArgs = processCommandLine(args);
+        }
+        catch (UsageException e)
+        {
+            System.err.println(e.getMessage());
+            usageExit(e.getCause(),e.getExitCode());
+        }
+        catch (Throwable e)
         {
-            Main.close(buf);
-            Main.close(reader);
+            usageExit(e,UsageException.ERR_UNKNOWN);
         }
+    }
 
-        return args;
+    public void start() throws Exception
+    {
+        start(startupArgs);
+    }
+
+    public void stop() throws Exception
+    {
+        doStop(startupArgs);
     }
 
-    void addJvmArgs(List<String> jvmArgs)
+    public void destroy()
     {
-        _jvmArgs.addAll(jvmArgs);
     }
 }
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/Module.java b/jetty-start/src/main/java/org/eclipse/jetty/start/Module.java
new file mode 100644
index 0000000..f1be504
--- /dev/null
+++ b/jetty-start/src/main/java/org/eclipse/jetty/start/Module.java
@@ -0,0 +1,494 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.start;
+
+import java.io.BufferedReader;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.text.CollationKey;
+import java.text.Collator;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Represents a Module metadata, as defined in Jetty.
+ */
+public class Module
+{
+    public static class DepthComparator implements Comparator<Module>
+    {
+        private Collator collator = Collator.getInstance();
+
+        @Override
+        public int compare(Module o1, Module o2)
+        {
+            // order by depth first.
+            int diff = o1.depth - o2.depth;
+            if (diff != 0)
+            {
+                return diff;
+            }
+            // then by name (not really needed, but makes for predictable test cases)
+            CollationKey k1 = collator.getCollationKey(o1.fileRef);
+            CollationKey k2 = collator.getCollationKey(o2.fileRef);
+            return k1.compareTo(k2);
+        }
+    }
+
+    public static class NameComparator implements Comparator<Module>
+    {
+        private Collator collator = Collator.getInstance();
+
+        @Override
+        public int compare(Module o1, Module o2)
+        {
+            // by name (not really needed, but makes for predictable test cases)
+            CollationKey k1 = collator.getCollationKey(o1.fileRef);
+            CollationKey k2 = collator.getCollationKey(o2.fileRef);
+            return k1.compareTo(k2);
+        }
+    }
+
+    /** The file of the module */
+    private Path file;
+    /** The name of this Module (as a filesystem reference) */
+    private String fileRef;
+    /**
+     * The logical name of this module (for property selected references), And to aid in duplicate detection.
+     */
+    private String logicalName;
+    /** The depth of the module in the tree */
+    private int depth = 0;
+    /** Set of Modules, by name, that this Module depends on */
+    private Set<String> parentNames;
+    /** Set of Modules, by name, that this Module optionally depend on */
+    private Set<String> optionalParentNames;
+    /** The Edges to parent modules */
+    private Set<Module> parentEdges;
+    /** The Edges to child modules */
+    private Set<Module> childEdges;
+    /** List of xml configurations for this Module */
+    private List<String> xmls;
+    /** List of ini template lines */
+    private List<String> defaultConfig;
+    private boolean hasDefaultConfig = false;
+    /** List of library options for this Module */
+    private List<String> libs;
+    /** List of files for this Module */
+    private List<String> files;
+    /** List of jvm Args */
+    private List<String> jvmArgs;
+    /** License lines */
+    private List<String> license;
+
+    /** Is this Module enabled via start.jar command line, start.ini, or start.d/*.ini ? */
+    private boolean enabled = false;
+    /** List of sources that enabled this module */
+    private final Set<String> sources = new HashSet<>();
+    private boolean licenseAck = false;
+
+    public Module(BaseHome basehome, Path file) throws FileNotFoundException, IOException
+    {
+        this.file = file;
+
+        // Strip .mod
+        this.fileRef = Pattern.compile(".mod$",Pattern.CASE_INSENSITIVE).matcher(file.getFileName().toString()).replaceFirst("");
+        this.logicalName = fileRef;
+
+        init(basehome);
+        process(basehome);
+    }
+
+    public void addChildEdge(Module child)
+    {
+        if (childEdges.contains(child))
+        {
+            // already present, skip
+            return;
+        }
+        this.childEdges.add(child);
+    }
+
+    public void addParentEdge(Module parent)
+    {
+        if (parentEdges.contains(parent))
+        {
+            // already present, skip
+            return;
+        }
+        this.parentEdges.add(parent);
+    }
+
+    public void addSources(List<String> sources)
+    {
+        this.sources.addAll(sources);
+    }
+
+    public void clearSources()
+    {
+        this.sources.clear();
+    }
+
+    @Override
+    public boolean equals(Object obj)
+    {
+        if (this == obj)
+        {
+            return true;
+        }
+        if (obj == null)
+        {
+            return false;
+        }
+        if (getClass() != obj.getClass())
+        {
+            return false;
+        }
+        Module other = (Module)obj;
+        if (fileRef == null)
+        {
+            if (other.fileRef != null)
+            {
+                return false;
+            }
+        }
+        else if (!fileRef.equals(other.fileRef))
+        {
+            return false;
+        }
+        return true;
+    }
+
+    public void expandProperties(Props props)
+    {
+        // Expand Parents
+        Set<String> parents = new HashSet<>();
+        for (String parent : parentNames)
+        {
+            parents.add(props.expand(parent));
+        }
+        parentNames.clear();
+        parentNames.addAll(parents);
+    }
+
+    public Set<Module> getChildEdges()
+    {
+        return childEdges;
+    }
+
+    public int getDepth()
+    {
+        return depth;
+    }
+
+    public List<String> getFiles()
+    {
+        return files;
+    }
+
+    public String getFilesystemRef()
+    {
+        return fileRef;
+    }
+
+    public List<String> getDefaultConfig()
+    {
+        return defaultConfig;
+    }
+
+    public boolean hasDefaultConfig()
+    {
+        return hasDefaultConfig;
+    }
+
+    public List<String> getLibs()
+    {
+        return libs;
+    }
+
+    public String getName()
+    {
+        return logicalName;
+    }
+
+    public Set<String> getOptionalParentNames()
+    {
+        return optionalParentNames;
+    }
+
+    public Set<Module> getParentEdges()
+    {
+        return parentEdges;
+    }
+
+    public Set<String> getParentNames()
+    {
+        return parentNames;
+    }
+
+    public Set<String> getSources()
+    {
+        return Collections.unmodifiableSet(sources);
+    }
+
+    public List<String> getXmls()
+    {
+        return xmls;
+    }
+
+    public List<String> getJvmArgs()
+    {
+        return jvmArgs;
+    }
+
+    public boolean hasLicense()
+    {
+        return license != null && license.size() > 0;
+    }
+
+    public boolean acknowledgeLicense() throws IOException
+    {
+        if (!hasLicense() || licenseAck)
+        {
+            return true;
+        }
+        
+        System.err.printf("%nModule %s:%n",getName());
+        System.err.printf(" + contains software not provided by the Eclipse Foundation!%n");
+        System.err.printf(" + contains software not covered by the Eclipse Public License!%n");
+        System.err.printf(" + has not been audited for compliance with its license%n");
+        System.err.printf("%n");
+        for (String l : getLicense())
+        {
+            System.err.printf("    %s%n",l);
+        }
+
+        String propBasedAckName = "org.eclipse.jetty.start.ack.license." + getName();
+        String propBasedAckValue = System.getProperty(propBasedAckName);
+        if (propBasedAckValue != null)
+        {
+            StartLog.log("TESTING MODE", "Programmatic ACK - %s=%s",propBasedAckName,propBasedAckValue);
+            licenseAck = Boolean.parseBoolean(propBasedAckValue);
+        }
+        else
+        {
+            if (Boolean.getBoolean("org.eclipse.jetty.start.testing"))
+            {
+                throw new RuntimeException("Test Configuration Missing - Pre-specify answer to (" + propBasedAckName + ") in test case");
+            }
+
+            try (BufferedReader input = new BufferedReader(new InputStreamReader(System.in)))
+            {
+                System.err.printf("%nProceed (y/N)? ");
+                String line = input.readLine();
+
+                licenseAck = !(line == null || line.length() == 0 || !line.toLowerCase().startsWith("y"));
+            }
+        }
+
+        return licenseAck;
+    }
+
+    public List<String> getLicense()
+    {
+        return license;
+    }
+
+    @Override
+    public int hashCode()
+    {
+        final int prime = 31;
+        int result = 1;
+        result = (prime * result) + ((fileRef == null)?0:fileRef.hashCode());
+        return result;
+    }
+
+    private void init(BaseHome basehome)
+    {
+        parentNames = new HashSet<>();
+        optionalParentNames = new HashSet<>();
+        parentEdges = new HashSet<>();
+        childEdges = new HashSet<>();
+        xmls = new ArrayList<>();
+        defaultConfig = new ArrayList<>();
+        libs = new ArrayList<>();
+        files = new ArrayList<>();
+        jvmArgs = new ArrayList<>();
+        license = new ArrayList<>();
+
+        String name = basehome.toShortForm(file);
+
+        // Find module system name (usually in the form of a filesystem reference)
+        Pattern pat = Pattern.compile("^.*[/\\\\]{1}modules[/\\\\]{1}(.*).mod$",Pattern.CASE_INSENSITIVE);
+        Matcher mat = pat.matcher(name);
+        if (!mat.find())
+        {
+            throw new RuntimeException("Invalid Module location (must be located under /modules/ directory): " + name);
+        }
+        this.fileRef = mat.group(1).replace('\\','/');
+        this.logicalName = this.fileRef;
+    }
+
+    public boolean isEnabled()
+    {
+        return enabled;
+    }
+
+    public boolean hasFiles(BaseHome baseHome)
+    {
+        for (String ref : getFiles())
+        {
+            FileArg farg = new FileArg(this,ref);
+            Path refPath = baseHome.getBasePath(farg.location);
+            if (!Files.exists(refPath))
+            {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public void process(BaseHome basehome) throws FileNotFoundException, IOException
+    {
+        Pattern section = Pattern.compile("\\s*\\[([^]]*)\\]\\s*");
+
+        if (!FS.canReadFile(file))
+        {
+            StartLog.debug("Skipping read of missing file: %s",basehome.toShortForm(file));
+            return;
+        }
+
+        try (BufferedReader buf = Files.newBufferedReader(file,StandardCharsets.UTF_8))
+        {
+            String sectionType = "";
+            String line;
+            while ((line = buf.readLine()) != null)
+            {
+                line = line.trim();
+
+                Matcher sectionMatcher = section.matcher(line);
+
+                if (sectionMatcher.matches())
+                {
+                    sectionType = sectionMatcher.group(1).trim().toUpperCase(Locale.ENGLISH);
+                }
+                else
+                {
+                    // blank lines and comments are valid for ini-template section
+                    if ((line.length() == 0) || line.startsWith("#"))
+                    {
+                        if ("INI-TEMPLATE".equals(sectionType))
+                        {
+                            defaultConfig.add(line);
+                        }
+                    }
+                    else
+                    {
+                        switch (sectionType)
+                        {
+                            case "":
+                                // ignore (this would be entries before first section)
+                                break;
+                            case "DEPEND":
+                                parentNames.add(line);
+                                break;
+                            case "FILES":
+                                files.add(line);
+                                break;
+                            case "DEFAULTS":
+                            case "INI-TEMPLATE":
+                                defaultConfig.add(line);
+                                hasDefaultConfig = true;
+                                break;
+                            case "LIB":
+                                libs.add(line);
+                                break;
+                            case "LICENSE":
+                            case "LICENCE":
+                                license.add(line);
+                                break;
+                            case "NAME":
+                                logicalName = line;
+                                break;
+                            case "OPTIONAL":
+                                optionalParentNames.add(line);
+                                break;
+                            case "EXEC":
+                                jvmArgs.add(line);
+                                break;
+                            case "XML":
+                                xmls.add(line);
+                                break;
+                            default:
+                                throw new IOException("Unrecognized Module section: [" + sectionType + "]");
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    public void setDepth(int depth)
+    {
+        this.depth = depth;
+    }
+
+    public void setEnabled(boolean enabled)
+    {
+        this.enabled = enabled;
+    }
+
+    public void setParentNames(Set<String> parents)
+    {
+        this.parentNames.clear();
+        this.parentEdges.clear();
+        if (parents != null)
+        {
+            this.parentNames.addAll(parents);
+        }
+    }
+
+    @Override
+    public String toString()
+    {
+        StringBuilder str = new StringBuilder();
+        str.append("Module[").append(logicalName);
+        if (!logicalName.equals(fileRef))
+        {
+            str.append(",file=").append(fileRef);
+        }
+        if (enabled)
+        {
+            str.append(",enabled");
+        }
+        str.append(']');
+        return str.toString();
+    }
+}
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/ModuleGraphWriter.java b/jetty-start/src/main/java/org/eclipse/jetty/start/ModuleGraphWriter.java
new file mode 100644
index 0000000..b98a392
--- /dev/null
+++ b/jetty-start/src/main/java/org/eclipse/jetty/start/ModuleGraphWriter.java
@@ -0,0 +1,260 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.start;
+
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Generate a graphviz dot graph of the modules found
+ */
+public class ModuleGraphWriter
+{
+    private String colorModuleBg;
+    private String colorEnabledBg;
+    private String colorTransitiveBg;
+    private String colorCellBg;
+    private String colorHeaderBg;
+    private String colorModuleFont;
+
+    public ModuleGraphWriter()
+    {
+        colorModuleBg = "#B8FFB8";
+        colorEnabledBg = "#66FFCC";
+        colorTransitiveBg = "#66CC66";
+        colorCellBg = "#FFFFFF80";
+        colorHeaderBg = "#00000020";
+        colorModuleFont = "#888888";
+    }
+
+    public void config(Props props)
+    {
+        String prefix = "jetty.graph.";
+        colorModuleBg = getProperty(props,prefix + "color.module.bg",colorModuleBg);
+        colorEnabledBg = getProperty(props,prefix + "color.enabled.bg",colorEnabledBg);
+        colorTransitiveBg = getProperty(props,prefix + "color.transitive.bg",colorTransitiveBg);
+        colorCellBg = getProperty(props,prefix + "color.cell.bg",colorCellBg);
+        colorHeaderBg = getProperty(props,prefix + "color.header.bg",colorHeaderBg);
+        colorModuleFont = getProperty(props,prefix + "color.font",colorModuleFont);
+    }
+
+    private String getProperty(Props props, String key, String defVal)
+    {
+        String val = props.getString(key,defVal);
+        if (val == null)
+        {
+            return defVal;
+        }
+        val = val.trim();
+        if (val.length() <= 0)
+        {
+            return defVal;
+        }
+        return val;
+    }
+
+    public void write(Modules modules, Path outputFile) throws IOException
+    {
+        try (BufferedWriter writer = Files.newBufferedWriter(outputFile,StandardCharsets.UTF_8,StandardOpenOption.CREATE_NEW,StandardOpenOption.WRITE); 
+             PrintWriter out = new PrintWriter(writer);)
+        {
+            writeHeaderMessage(out,outputFile);
+
+            out.println();
+            out.println("digraph modules {");
+
+            // Node Style
+            out.println("  node [color=gray, style=filled, shape=rectangle];");
+            out.println("  node [fontname=\"Verdana\", size=\"20,20\"];");
+            // Graph Style
+            out.println("  graph [");
+            out.println("    concentrate=false,");
+            out.println("    fontname=\"Verdana\",");
+            out.println("    fontsize = 20,");
+            out.println("    rankdir = LR,");
+            out.println("    ranksep = 1.5,");
+            out.println("    nodesep = .5,");
+            out.println("    style = bold,");
+            out.println("    labeljust = l,");
+            out.println("    label = \"Jetty Modules\",");
+            out.println("    ssize = \"20,40\"");
+            out.println("  ];");
+
+            List<Module> enabled = modules.resolveEnabled();
+
+            // Module Nodes
+            writeModules(out,modules,enabled);
+
+            // Module Relationships
+            writeRelationships(out,modules,enabled);
+
+            out.println("}");
+            out.println();
+        }
+    }
+
+    private void writeHeaderMessage(PrintWriter out, Path outputFile)
+    {
+        out.println("/*");
+        out.println(" * GraphViz Graph of Jetty Modules");
+        out.println(" * ");
+        out.println(" * Jetty: http://eclipse.org/jetty/");
+        out.println(" * GraphViz: http://graphviz.org/");
+        out.println(" * ");
+        out.println(" * To Generate Graph image using graphviz:");
+        String filename = outputFile.getFileName().toString();
+        String basename = filename.substring(0,filename.indexOf('.'));
+        out.printf(" *   $ dot -Tpng -Goverlap=false -o %s.png %s%n",basename,filename);
+        out.println(" */");
+    }
+
+    private void writeModuleDetailHeader(PrintWriter out, String header)
+    {
+        writeModuleDetailHeader(out,header,1);
+    }
+
+    private void writeModuleDetailHeader(PrintWriter out, String header, int count)
+    {
+        out.printf("  <TR>");
+        out.printf("<TD BGCOLOR=\"%s\" ALIGN=\"LEFT\"><I>",colorHeaderBg);
+        out.printf("%s%s</I></TD>",header,count > 1?"s":"");
+        out.println("</TR>");
+    }
+
+    private void writeModuleDetailLine(PrintWriter out, String line)
+    {
+        out.printf("  <TR>");
+        StringBuilder escape = new StringBuilder();
+        for(char c: line.toCharArray()) {
+            switch(c) {
+                case '<': escape.append("<"); break;
+                case '>': escape.append(">"); break;
+                default:
+                    escape.append(c);
+                    break;
+            }
+        }
+        
+        out.printf("<TD BGCOLOR=\"%s\" ALIGN=\"LEFT\">%s</TD></TR>%n",colorCellBg,escape.toString());
+    }
+
+    private void writeModuleNode(PrintWriter out, Module module, boolean resolved)
+    {
+        String color = colorModuleBg;
+        if (module.isEnabled())
+        {
+            // specifically enabled by config
+            color = colorEnabledBg;
+        }
+        else if (resolved)
+        {
+            // enabled by transitive reasons
+            color = colorTransitiveBg;
+        }
+
+        out.printf("  \"%s\" [ color=\"%s\" label=<",module.getName(),color);
+        out.printf("<TABLE BORDER=\"0\" CELLBORDER=\"0\" CELLSPACING=\"0\" CELLPADDING=\"2\">%n");
+        out.printf("  <TR><TD ALIGN=\"LEFT\"><B>%s</B></TD></TR>%n",module.getName());
+
+        if (module.isEnabled())
+        {
+            writeModuleDetailHeader(out,"ENABLED");
+            for (String source : module.getSources())
+            {
+                writeModuleDetailLine(out,"via: " + source);
+            }
+        }
+        else if (resolved)
+        {
+            writeModuleDetailHeader(out,"TRANSITIVE");
+        }
+
+        if (!module.getXmls().isEmpty())
+        {
+            List<String> xmls = module.getXmls();
+            writeModuleDetailHeader(out,"XML",xmls.size());
+            for (String xml : xmls)
+            {
+                writeModuleDetailLine(out,xml);
+            }
+        }
+
+        if (!module.getLibs().isEmpty())
+        {
+            List<String> libs = module.getLibs();
+            writeModuleDetailHeader(out,"LIB",libs.size());
+            for (String lib : libs)
+            {
+                writeModuleDetailLine(out,lib);
+            }
+        }
+
+        if (!module.getDefaultConfig().isEmpty())
+        {
+            List<String> inis = module.getDefaultConfig();
+            writeModuleDetailHeader(out,"INI Template",inis.size());
+        }
+
+        out.printf("</TABLE>>];%n");
+    }
+
+    private void writeModules(PrintWriter out, Modules allmodules, List<Module> enabled)
+    {
+        out.println();
+        out.println("  /* Modules */");
+        out.println();
+
+        out.println("  node [ labeljust = l ];");
+
+        for (int depth = 0; depth <= allmodules.getMaxDepth(); depth++)
+        {
+            out.println();
+            Collection<Module> depthModules = allmodules.getModulesAtDepth(depth);
+            if (depthModules.size() > 0)
+            {
+                out.printf("  /* Level %d */%n",depth);
+                out.println("  { rank = same;");
+                for (Module module : depthModules)
+                {
+                    boolean resolved = enabled.contains(module);
+                    writeModuleNode(out,module,resolved);
+                }
+                out.println("  }");
+            }
+        }
+    }
+
+    private void writeRelationships(PrintWriter out, Modules modules, List<Module> enabled)
+    {
+        for (Module module : modules)
+        {
+            for (Module parent : module.getParentEdges())
+            {
+                out.printf("    \"%s\" -> \"%s\";%n",module.getName(),parent.getName());
+            }
+        }
+    }
+}
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/Modules.java b/jetty-start/src/main/java/org/eclipse/jetty/start/Modules.java
new file mode 100644
index 0000000..9a56624
--- /dev/null
+++ b/jetty-start/src/main/java/org/eclipse/jetty/start/Modules.java
@@ -0,0 +1,678 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.start;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.Stack;
+import java.util.regex.Pattern;
+
+/**
+ * Access for all modules declared, as well as what is enabled.
+ */
+public class Modules implements Iterable<Module>
+{
+    private final BaseHome baseHome;
+    private final StartArgs args;
+    
+    private Map<String, Module> modules = new HashMap<>();
+    /*
+     * modules that may appear in the resolved graph but are undefined in the module system
+     * 
+     * ex: modules/npn/npn-1.7.0_01.mod (property expansion resolves to non-existent file)
+     */
+    private Set<String> missingModules = new HashSet<String>();
+
+    private int maxDepth = -1;
+    
+    public Modules(BaseHome basehome, StartArgs args)
+    {
+        this.baseHome = basehome;
+        this.args = args;
+    }
+
+    private Set<String> asNameSet(Set<Module> moduleSet)
+    {
+        Set<String> ret = new HashSet<>();
+        for (Module module : moduleSet)
+        {
+            ret.add(module.getName());
+        }
+        return ret;
+    }
+
+    private void assertNoCycle(Module module, Stack<String> refs)
+    {
+        for (Module parent : module.getParentEdges())
+        {
+            if (refs.contains(parent.getName()))
+            {
+                // Cycle detected.
+                StringBuilder err = new StringBuilder();
+                err.append("A cyclic reference in the modules has been detected: ");
+                for (int i = 0; i < refs.size(); i++)
+                {
+                    if (i > 0)
+                    {
+                        err.append(" -> ");
+                    }
+                    err.append(refs.get(i));
+                }
+                err.append(" -> ").append(parent.getName());
+                throw new IllegalStateException(err.toString());
+            }
+
+            refs.push(parent.getName());
+            assertNoCycle(parent,refs);
+            refs.pop();
+        }
+    }
+
+    private void bfsCalculateDepth(final Module module, final int depthNow)
+    {
+        int depth = depthNow + 1;
+
+        // Set depth on every child first
+        for (Module child : module.getChildEdges())
+        {
+            child.setDepth(Math.max(depth,child.getDepth()));
+            this.maxDepth = Math.max(this.maxDepth,child.getDepth());
+        }
+
+        // Dive down
+        for (Module child : module.getChildEdges())
+        {
+            bfsCalculateDepth(child,depth);
+        }
+    }
+
+    /**
+     * Using the provided dependencies, build the module graph
+     */
+    public void buildGraph() throws FileNotFoundException, IOException
+    {
+        normalizeDependencies();
+        
+        // Connect edges
+        for (Module module : modules.values())
+        {
+            for (String parentName : module.getParentNames())
+            {
+                Module parent = get(parentName);
+
+                if (parent == null)
+                {
+                    if (Props.hasPropertyKey(parentName))
+                    {
+                        StartLog.debug("Module property not expandable (yet) [%s]",parentName);
+                    }
+                    else
+                    {
+                        StartLog.warn("Module not found [%s]",parentName);
+                    }
+                }
+                else
+                {
+                    module.addParentEdge(parent);
+                    parent.addChildEdge(module);
+                }
+            }
+
+            for (String optionalParentName : module.getOptionalParentNames())
+            {
+                Module optional = get(optionalParentName);
+                if (optional == null)
+                {
+                    StartLog.debug("Optional module not found [%s]",optionalParentName);
+                }
+                else if (optional.isEnabled())
+                {
+                    module.addParentEdge(optional);
+                    optional.addChildEdge(module);
+                }
+            }
+        }
+
+        // Verify there is no cyclic references
+        Stack<String> refs = new Stack<>();
+        for (Module module : modules.values())
+        {
+            refs.push(module.getName());
+            assertNoCycle(module,refs);
+            refs.pop();
+        }
+
+        // Calculate depth of all modules for sorting later
+        for (Module module : modules.values())
+        {
+            if (module.getParentEdges().isEmpty())
+            {
+                bfsCalculateDepth(module,0);
+            }
+        }
+    }
+
+    public void clearMissing()
+    {
+        missingModules.clear();
+    }
+    
+    public Integer count()
+    {
+        return modules.size();
+    }
+
+    public void dump()
+    {
+        List<Module> ordered = new ArrayList<>();
+        ordered.addAll(modules.values());
+        Collections.sort(ordered,new Module.NameComparator());
+
+        List<Module> active = resolveEnabled();
+
+        for (Module module : ordered)
+        {
+            boolean activated = active.contains(module);
+            boolean enabled = module.isEnabled();
+            boolean transitive = activated && !enabled;
+
+            char status = '-';
+            if (enabled)
+            {
+                status = '*';
+            }
+            else if (transitive)
+            {
+                status = '+';
+            }
+
+            System.out.printf("%n %s Module: %s%n",status,module.getName());
+            if (!module.getName().equals(module.getFilesystemRef()))
+            {
+                System.out.printf("      Ref: %s%n",module.getFilesystemRef());
+            }
+            for (String parent : module.getParentNames())
+            {
+                System.out.printf("   Depend: %s%n",parent);
+            }
+            for (String lib : module.getLibs())
+            {
+                System.out.printf("      LIB: %s%n",lib);
+            }
+            for (String xml : module.getXmls())
+            {
+                System.out.printf("      XML: %s%n",xml);
+            }
+            if (StartLog.isDebugEnabled())
+            {
+                System.out.printf("    depth: %d%n",module.getDepth());
+            }
+            if (activated)
+            {
+                for (String source : module.getSources())
+                {
+                    System.out.printf("  Enabled: <via> %s%n",source);
+                }
+                if (transitive)
+                {
+                    System.out.printf("  Enabled: <via transitive reference>%n");
+                }
+            }
+            else
+            {
+                System.out.printf("  Enabled: <not enabled in this configuration>%n");
+            }
+        }
+    }
+
+    public void dumpEnabledTree()
+    {
+        List<Module> ordered = new ArrayList<>();
+        ordered.addAll(modules.values());
+        Collections.sort(ordered,new Module.DepthComparator());
+
+        List<Module> active = resolveEnabled();
+
+        for (Module module : ordered)
+        {
+            if (active.contains(module))
+            {
+                // Show module name
+                String indent = toIndent(module.getDepth());
+                System.out.printf("%s + Module: %s [%s]%n",indent,module.getName(),module.isEnabled()?"enabled":"transitive");
+            }
+        }
+    }
+
+    public void enable(String name) throws IOException
+    {
+        List<String> empty = Collections.emptyList();
+        enable(name,empty);
+    }
+    
+    public void enable(String name, List<String> sources) throws IOException
+    {
+        if (name.contains("*"))
+        {
+            // A regex!
+            Pattern pat = Pattern.compile(name);
+            List<Module> matching = new ArrayList<>();
+            do
+            {
+                matching.clear();
+                
+                // find matching entries that are not enabled
+                for (Map.Entry<String, Module> entry : modules.entrySet())
+                {
+                    if (pat.matcher(entry.getKey()).matches())
+                    {
+                        if (!entry.getValue().isEnabled())
+                        {
+                            matching.add(entry.getValue());
+                        }
+                    }
+                }
+                
+                // enable them
+                for (Module module : matching)
+                {
+                    enableModule(module,sources);
+                }
+            }
+            while (!matching.isEmpty());
+        }
+        else
+        {
+            Module module = modules.get(name);
+            if (module == null)
+            {
+                System.err.printf("WARNING: Cannot enable requested module [%s]: not a valid module name.%n",name);
+                return;
+            }
+            enableModule(module,sources);
+        }
+    }
+
+    private void enableModule(Module module, List<String> sources) throws IOException
+    {
+        String via = "<transitive>";
+
+        // Always add the sources
+        if (sources != null)
+        {
+            module.addSources(sources);
+            via = Main.join(sources, ", ");
+        }
+        
+        // If already enabled, nothing else to do
+        if (module.isEnabled())
+        {
+            StartLog.debug("Enabled module: %s (via %s)",module.getName(),via);
+            return;
+        }
+        
+        StartLog.debug("Enabling module: %s (via %s)",module.getName(),via);
+        module.setEnabled(true);
+        args.parseModule(module);
+        module.expandProperties(args.getProperties());
+        
+        // enable any parents that haven't been enabled (yet)
+        Set<String> parentNames = new HashSet<>();
+        parentNames.addAll(module.getParentNames());
+        for(String name: parentNames)
+        {
+            StartLog.debug("Enable parent '%s' of module: %s",name,module.getName());
+            Module parent = modules.get(name);
+            if (parent == null)
+            {
+                // parent module doesn't exist, yet
+                Path file = baseHome.getPath("modules/" + name + ".mod");
+                if (FS.canReadFile(file))
+                {
+                    parent = registerModule(file);
+                    parent.expandProperties(args.getProperties());
+                    updateParentReferencesTo(parent);
+                }
+                else
+                {
+                    if (!Props.hasPropertyKey(name))
+                    {
+                        StartLog.debug("Missing module definition: [ Mod: %s | File: %s ]",name,file);
+                        missingModules.add(name);
+                    }
+                }
+            }
+            if (parent != null)
+            {
+                enableModule(parent,null);
+            }
+        }
+    }
+    
+    private void findChildren(Module module, Set<Module> ret)
+    {
+        ret.add(module);
+        for (Module child : module.getChildEdges())
+        {
+            ret.add(child);
+        }
+    }
+
+    private void findParents(Module module, Map<String, Module> ret)
+    {
+        ret.put(module.getName(),module);
+        for (Module parent : module.getParentEdges())
+        {
+            ret.put(parent.getName(),parent);
+            findParents(parent,ret);
+        }
+    }
+
+    public Module get(String name)
+    {
+        return modules.get(name);
+    }
+
+    public int getMaxDepth()
+    {
+        return maxDepth;
+    }
+
+    public Set<Module> getModulesAtDepth(int depth)
+    {
+        Set<Module> ret = new HashSet<>();
+        for (Module module : modules.values())
+        {
+            if (module.getDepth() == depth)
+            {
+                ret.add(module);
+            }
+        }
+        return ret;
+    }
+
+    @Override
+    public Iterator<Module> iterator()
+    {
+        return modules.values().iterator();
+    }
+
+    public List<String> normalizeLibs(List<Module> active)
+    {
+        List<String> libs = new ArrayList<>();
+        for (Module module : active)
+        {
+            for (String lib : module.getLibs())
+            {
+                if (!libs.contains(lib))
+                {
+                    libs.add(lib);
+                }
+            }
+        }
+        return libs;
+    }
+
+    public List<String> normalizeXmls(List<Module> active)
+    {
+        List<String> xmls = new ArrayList<>();
+        for (Module module : active)
+        {
+            for (String xml : module.getXmls())
+            {
+                if (!xmls.contains(xml))
+                {
+                    xmls.add(xml);
+                }
+            }
+        }
+        return xmls;
+    }
+
+    public Module register(Module module)
+    {
+        modules.put(module.getName(),module);
+        return module;
+    }
+
+    public void registerParentsIfMissing(Module module) throws IOException
+    {
+        Set<String> parents = new HashSet<>(module.getParentNames());
+        for (String name : parents)
+        {
+            if (!modules.containsKey(name))
+            {
+                Path file = baseHome.getPath("modules/" + name + ".mod");
+                if (FS.canReadFile(file))
+                {
+                    Module parent = registerModule(file);
+                    updateParentReferencesTo(parent);
+                    registerParentsIfMissing(parent);
+                }
+            }
+        }
+    }
+    
+    public void registerAll() throws IOException
+    {
+        for (Path path : baseHome.getPaths("modules/*.mod"))
+        {
+            registerModule(path);
+        }
+    }
+    
+    // load missing post-expanded dependent modules
+    private void normalizeDependencies() throws FileNotFoundException, IOException
+    {
+        Set<String> expandedModules = new HashSet<>();
+        boolean done = false;
+        while (!done)
+        {
+            done = true;
+            Set<String> missingParents = new HashSet<>();
+
+            for (Module m : modules.values())
+            {
+                for (String parent : m.getParentNames())
+                {
+                    String expanded = args.getProperties().expand(parent);
+                    if (modules.containsKey(expanded) || missingModules.contains(parent) || expandedModules.contains(parent))
+                    {
+                        continue; // found. skip it.
+                    }
+                    done = false;
+                    StartLog.debug("Missing parent module %s == %s for %s",parent,expanded,m);
+                    missingParents.add(parent);
+                }
+            }
+
+            for (String missingParent : missingParents)
+            {
+                String expanded = args.getProperties().expand(missingParent);
+                Path file = baseHome.getPath("modules/" + expanded + ".mod");
+                if (FS.canReadFile(file))
+                {
+                    Module module = registerModule(file);
+                    updateParentReferencesTo(module);
+                    if (!expanded.equals(missingParent))
+                    {
+                        expandedModules.add(missingParent);
+                    }
+                }
+                else
+                {
+                    if (Props.hasPropertyKey(expanded))
+                    {
+                        StartLog.debug("Module property not expandable (yet) [%s]",expanded);
+                        expandedModules.add(missingParent);
+                    }
+                    else
+                    {
+                        StartLog.debug("Missing module definition: %s expanded to %s",missingParent,expanded);
+                        missingModules.add(missingParent);
+                    }
+                }
+            }
+        }
+    }
+
+    private Module registerModule(Path file) throws FileNotFoundException, IOException
+    {
+        if (!FS.canReadFile(file))
+        {
+            throw new IOException("Cannot read file: " + file);
+        }
+        StartLog.debug("Registering Module: %s",baseHome.toShortForm(file));
+        Module module = new Module(baseHome,file);
+        return register(module);
+    }
+
+    public Set<String> resolveChildModulesOf(String moduleName)
+    {
+        Set<Module> ret = new HashSet<>();
+        Module module = get(moduleName);
+        findChildren(module,ret);
+        return asNameSet(ret);
+    }
+
+    /**
+     * Resolve the execution order of the enabled modules, and all dependant modules, based on depth first transitive reduction.
+     * 
+     * @return the list of active modules (plus dependant modules), in execution order.
+     */
+    public List<Module> resolveEnabled()
+    {
+        Map<String, Module> active = new HashMap<String, Module>();
+
+        for (Module module : modules.values())
+        {
+            if (module.isEnabled())
+            {
+                findParents(module,active);
+            }
+        }
+
+        /*
+         * check against the missing modules
+         * 
+         * Ex: npn should match anything under npn/
+         */
+        for (String missing : missingModules)
+        {
+            for (String activeModule : active.keySet())
+            {
+                if (missing.startsWith(activeModule))
+                {
+                    StartLog.warn("** Unable to continue, required dependency missing. [%s]",missing);
+                    StartLog.warn("** As configured, Jetty is unable to start due to a missing enabled module dependency.");
+                    StartLog.warn("** This may be due to a transitive dependency akin to spdy on npn, which resolves based on the JDK in use.");
+                    throw new UsageException(UsageException.ERR_BAD_ARG, "Missing referenced dependency: " + missing);
+                }
+            }
+        }
+
+        List<Module> ordered = new ArrayList<>();
+        ordered.addAll(active.values());
+        Collections.sort(ordered,new Module.DepthComparator());
+        return ordered;
+    }
+
+    public Set<String> resolveParentModulesOf(String moduleName)
+    {
+        Map<String, Module> ret = new HashMap<>();
+        Module module = get(moduleName);
+        findParents(module,ret);
+        return ret.keySet();
+    }
+
+    private String toIndent(int depth)
+    {
+        char indent[] = new char[depth * 2];
+        Arrays.fill(indent,' ');
+        return new String(indent);
+    }
+
+    /**
+     * Modules can have a different logical name than to their filesystem reference. This updates existing references to the filesystem form to use the logical
+     * name form.
+     * 
+     * @param module
+     *            the module that might have other modules referring to it.
+     */
+    private void updateParentReferencesTo(Module module)
+    {
+        if (module.getName().equals(module.getFilesystemRef()))
+        {
+            // nothing to do, its sane already
+            return;
+        }
+
+        for (Module m : modules.values())
+        {
+            Set<String> resolvedParents = new HashSet<>();
+            for (String parent : m.getParentNames())
+            {
+                if (parent.equals(module.getFilesystemRef()))
+                {
+                    // use logical name instead
+                    resolvedParents.add(module.getName());
+                }
+                else
+                {
+                    // use name as-is
+                    resolvedParents.add(parent);
+                }
+            }
+            m.setParentNames(resolvedParents);
+        }
+    }
+
+    @Override
+    public String toString()
+    {
+        StringBuilder str = new StringBuilder();
+        str.append("Modules[");
+        str.append("count=").append(modules.size());
+        str.append(",<");
+        boolean delim = false;
+        for (String name : modules.keySet())
+        {
+            if (delim)
+            {
+                str.append(',');
+            }
+            str.append(name);
+            delim = true;
+        }
+        str.append(">");
+        str.append("]");
+        return str.toString();
+    }
+
+}
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/NaturalSort.java b/jetty-start/src/main/java/org/eclipse/jetty/start/NaturalSort.java
new file mode 100644
index 0000000..d54cbcd
--- /dev/null
+++ b/jetty-start/src/main/java/org/eclipse/jetty/start/NaturalSort.java
@@ -0,0 +1,70 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.start;
+
+import java.io.File;
+import java.nio.file.Path;
+import java.text.CollationKey;
+import java.text.Collator;
+import java.util.Comparator;
+
+/**
+ * Natural Language Sorting
+ */
+public class NaturalSort
+{
+    public static class Paths implements Comparator<Path>
+    {
+        private final Collator collator = Collator.getInstance();
+
+        @Override
+        public int compare(Path o1, Path o2)
+        {
+            CollationKey key1 = collator.getCollationKey(o1.toString());
+            CollationKey key2 = collator.getCollationKey(o2.toString());
+            return key1.compareTo(key2);
+        }
+    }
+
+    public static class Files implements Comparator<File>
+    {
+        private final Collator collator = Collator.getInstance();
+
+        @Override
+        public int compare(File o1, File o2)
+        {
+            CollationKey key1 = collator.getCollationKey(o1.getAbsolutePath());
+            CollationKey key2 = collator.getCollationKey(o2.getAbsolutePath());
+            return key1.compareTo(key2);
+        }
+    }
+
+    public static class Strings implements Comparator<String>
+    {
+        private final Collator collator = Collator.getInstance();
+
+        @Override
+        public int compare(String o1, String o2)
+        {
+            CollationKey key1 = collator.getCollationKey(o1);
+            CollationKey key2 = collator.getCollationKey(o2);
+            return key1.compareTo(key2);
+        }
+    }
+}
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/PathFinder.java b/jetty-start/src/main/java/org/eclipse/jetty/start/PathFinder.java
new file mode 100644
index 0000000..1037b32
--- /dev/null
+++ b/jetty-start/src/main/java/org/eclipse/jetty/start/PathFinder.java
@@ -0,0 +1,169 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.start;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.FileSystemLoopException;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Path;
+import java.nio.file.PathMatcher;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class PathFinder extends SimpleFileVisitor<Path>
+{
+    // internal tracking of prior notified paths (to avoid repeated notification of same ignored path)
+    private static Set<Path> NOTIFIED_PATHS = new HashSet<>();
+
+    private boolean includeDirsInResults = false;
+    private Map<String, Path> hits = new HashMap<>();
+    private Path basePath = null;
+    private PathMatcher dirMatcher = PathMatchers.getNonHidden();
+    private PathMatcher fileMatcher = PathMatchers.getNonHidden();
+
+    private void addHit(Path path)
+    {
+        String relPath = basePath.relativize(path).toString();
+        StartLog.debug("Found [" + relPath + "]  " + path);
+        hits.put(relPath,path);
+    }
+
+    public PathMatcher getDirMatcher()
+    {
+        return dirMatcher;
+    }
+
+    public PathMatcher getFileMatcher()
+    {
+        return fileMatcher;
+    }
+
+    public List<File> getHitList()
+    {
+        List<File> ret = new ArrayList<>();
+        for (Path path : hits.values())
+        {
+            ret.add(path.toFile());
+        }
+        return ret;
+    }
+
+    public Collection<Path> getHits()
+    {
+        return hits.values();
+    }
+
+    public boolean isIncludeDirsInResults()
+    {
+        return includeDirsInResults;
+    }
+
+    @Override
+    public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException
+    {
+        if (dirMatcher.matches(dir))
+        {
+            StartLog.trace("Following dir: " + dir);
+            if (includeDirsInResults && fileMatcher.matches(dir))
+            {
+                addHit(dir);
+            }
+            return FileVisitResult.CONTINUE;
+        }
+        else
+        {
+            StartLog.trace("Skipping dir: " + dir);
+            return FileVisitResult.SKIP_SUBTREE;
+        }
+    }
+
+    /**
+     * Set the active basePath, used for resolving relative paths.
+     * <p>
+     * When a hit arrives for a subsequent find that has the same relative path as a prior hit, the new hit overrides the prior path as the active hit.
+     * 
+     * @param basePath
+     *            the basePath to tag all hits with
+     */
+    public void setBase(Path basePath)
+    {
+        this.basePath = basePath;
+    }
+
+    public void setDirMatcher(PathMatcher dirMatcher)
+    {
+        this.dirMatcher = dirMatcher;
+    }
+
+    public void setFileMatcher(PathMatcher fileMatcher)
+    {
+        this.fileMatcher = fileMatcher;
+    }
+
+    public void setFileMatcher(String pattern)
+    {
+        this.fileMatcher = PathMatchers.getMatcher(pattern);
+    }
+
+    public void setIncludeDirsInResults(boolean includeDirsInResults)
+    {
+        this.includeDirsInResults = includeDirsInResults;
+    }
+
+    @Override
+    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException
+    {
+        if (fileMatcher.matches(file))
+        {
+            addHit(file);
+        }
+        else
+        {
+            StartLog.trace("Ignoring file: " + file);
+        }
+        return FileVisitResult.CONTINUE;
+    }
+
+    @Override
+    public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException
+    {
+        if (exc instanceof FileSystemLoopException)
+        {
+            if (!NOTIFIED_PATHS.contains(file))
+            {
+                StartLog.warn("skipping detected filesystem loop: " + file);
+                NOTIFIED_PATHS.add(file);
+            }
+            return FileVisitResult.SKIP_SUBTREE;
+        }
+        else
+        {
+            StartLog.warn(exc);
+            return super.visitFileFailed(file,exc);
+        }
+    }
+}
\ No newline at end of file
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/PathMatchers.java b/jetty-start/src/main/java/org/eclipse/jetty/start/PathMatchers.java
new file mode 100644
index 0000000..af58192
--- /dev/null
+++ b/jetty-start/src/main/java/org/eclipse/jetty/start/PathMatchers.java
@@ -0,0 +1,259 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.start;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.PathMatcher;
+
+/**
+ * Common PathMatcher implementations.
+ */
+public class PathMatchers
+{
+    private static class NonHiddenMatcher implements PathMatcher
+    {
+        @Override
+        public boolean matches(Path path)
+        {
+            try
+            {
+                return !Files.isHidden(path);
+            }
+            catch (IOException e)
+            {
+                StartLog.debug(e);
+                return false;
+            }
+        }
+    }
+
+    private static final char GLOB_CHARS[] = "*?".toCharArray();
+    private static final char SYNTAXED_GLOB_CHARS[] = "{}[]|:".toCharArray();
+    private static final Path EMPTY_PATH = new File(".").toPath();
+
+    /**
+     * Convert a pattern to a Path object.
+     * 
+     * @param pattern
+     *            the raw pattern (can contain "glob:" or "regex:" syntax indicator)
+     * @return the Path version of the pattern provided.
+     */
+    private static Path asPath(final String pattern)
+    {
+        String test = pattern;
+        if (test.startsWith("glob:"))
+        {
+            test = test.substring("glob:".length());
+        }
+        else if (test.startsWith("regex:"))
+        {
+            test = test.substring("regex:".length());
+        }
+        return new File(test).toPath();
+    }
+
+    public static PathMatcher getMatcher(final String rawpattern)
+    {
+        FileSystem fs = FileSystems.getDefault();
+        
+        String pattern = rawpattern;
+        
+        // Strip trailing slash (if present)
+        int lastchar = pattern.charAt(pattern.length() - 1);
+        if (lastchar == '/' || lastchar == '\\')
+        {
+            pattern = pattern.substring(0,pattern.length() - 1);
+        }
+
+        // If using FileSystem.getPathMatcher() with "glob:" or "regex:"
+        // use FileSystem default pattern behavior
+        if (pattern.startsWith("glob:") || pattern.startsWith("regex:"))
+        {
+            StartLog.debug("Using Standard " + fs.getClass().getName() + " pattern: " + pattern);
+            return fs.getPathMatcher(pattern);
+        }
+
+        // If the pattern starts with a root path then its assumed to
+        // be a full system path
+        if (isAbsolute(pattern))
+        {
+            String pat = "glob:" + pattern;
+            StartLog.debug("Using absolute path pattern: " + pat);
+            return fs.getPathMatcher(pat);
+        }
+
+        // Doesn't start with filesystem root, then assume the pattern
+        // is a relative file path pattern.
+        String pat = "glob:**/" + pattern;
+        StartLog.debug("Using relative path pattern: " + pat);
+        return fs.getPathMatcher(pat);
+    }
+
+    public static PathMatcher getNonHidden()
+    {
+        return new NonHiddenMatcher();
+    }
+
+    /**
+     * Provide the non-glob / non-regex prefix on the pattern as a Path reference.
+     * 
+     * @param pattern
+     *            the pattern to test
+     * @return the Path representing the search root for the pattern provided.
+     */
+    public static Path getSearchRoot(final String pattern)
+    {
+        StringBuilder root = new StringBuilder();
+
+        int start = 0;
+        boolean syntaxed = false;
+        if (pattern.startsWith("glob:"))
+        {
+            start = "glob:".length();
+            syntaxed = true;
+        }
+        else if (pattern.startsWith("regex:"))
+        {
+            start = "regex:".length();
+            syntaxed = true;
+        }
+        int len = pattern.length();
+        int lastSep = 0;
+        for (int i = start; i < len; i++)
+        {
+            int cp = pattern.codePointAt(i);
+            if (cp < 127)
+            {
+                char c = (char)cp;
+
+                // unix path case
+                if (c == '/')
+                {
+                    root.append(c);
+                    lastSep = root.length();
+                }
+                else if (c == '\\')
+                {
+                    root.append("\\");
+                    lastSep = root.length();
+
+                    // possible escaped sequence.
+                    // only really interested in windows escape sequences "\\"
+                    int count = countChars(pattern,i+1,'\\');
+                    if (count > 0)
+                    {
+                        // skip extra slashes
+                        i += count;
+                    }
+                }
+                else
+                {
+                    if (isGlob(c,syntaxed))
+                    {
+                        break;
+                    }
+                    root.append(c);
+                }
+            }
+            else
+            {
+                root.appendCodePoint(cp);
+            }
+        }
+
+        String rootPath = root.substring(0,lastSep);
+        if (rootPath.length() <= 0)
+        {
+            return EMPTY_PATH;
+        }
+
+        return asPath(rootPath);
+    }
+
+    private static int countChars(String pattern, int offset, char c)
+    {
+        int count = 0;
+        int len = pattern.length();
+        for (int i = offset; i < len; i++)
+        {
+            if (pattern.charAt(i) == c)
+            {
+                count++;
+            }
+            else
+            {
+                break;
+            }
+        }
+        return count;
+    }
+
+    /**
+     * Tests if provided pattern is an absolute reference (or not)
+     * 
+     * @param pattern
+     *            the pattern to test
+     * @return true if pattern is an absolute reference.
+     */
+    public static boolean isAbsolute(final String pattern)
+    {
+        Path searchRoot = getSearchRoot(pattern);
+        if (searchRoot == EMPTY_PATH)
+        {
+            return false;
+        }
+        return searchRoot.isAbsolute();
+    }
+
+    /**
+     * Determine if part is a glob pattern.
+     * 
+     * @param part
+     *            the string to check
+     * @param syntaxed
+     *            true if overall pattern is syntaxed with <code>"glob:"</code> or <code>"regex:"</code>
+     * @return true if part has glob characters
+     */
+    private static boolean isGlob(char c, boolean syntaxed)
+    {
+        for (char g : GLOB_CHARS)
+        {
+            if (c == g)
+            {
+                return true;
+            }
+        }
+        if (syntaxed)
+        {
+            for (char g : SYNTAXED_GLOB_CHARS)
+            {
+                if (c == g)
+                {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+}
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/Props.java b/jetty-start/src/main/java/org/eclipse/jetty/start/Props.java
new file mode 100644
index 0000000..ba3674e
--- /dev/null
+++ b/jetty-start/src/main/java/org/eclipse/jetty/start/Props.java
@@ -0,0 +1,376 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.start;
+
+import static org.eclipse.jetty.start.UsageException.*;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Stack;
+import java.util.TreeMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.eclipse.jetty.start.Props.Prop;
+
+/**
+ * Management of Properties.
+ * <p>
+ * This is larger in scope than the standard {@link java.util.Properties}, as it will also handle tracking the origin of each property, if it was overridden,
+ * and also allowing for <code>${property}</code> expansion.
+ */
+public final class Props implements Iterable<Prop>
+{
+    public static class Prop
+    {
+        public String key;
+        public String value;
+        public String origin;
+        public Prop overrides;
+
+        public Prop(String key, String value, String origin)
+        {
+            this.key = key;
+            this.value = value;
+            this.origin = origin;
+        }
+
+        public Prop(String key, String value, String origin, Prop overrides)
+        {
+            this(key,value,origin);
+            this.overrides = overrides;
+        }
+
+        @Override
+        public String toString()
+        {
+            StringBuilder builder = new StringBuilder();
+            builder.append("Prop [key=");
+            builder.append(key);
+            builder.append(", value=");
+            builder.append(value);
+            builder.append(", origin=");
+            builder.append(origin);
+            builder.append(", overrides=");
+            builder.append(overrides);
+            builder.append("]");
+            return builder.toString();
+        }
+    }
+
+    public static final String ORIGIN_SYSPROP = "<system-property>";
+    
+    public static String getValue(String arg)
+    {
+        int idx = arg.indexOf('=');
+        if (idx == (-1))
+        {
+            throw new UsageException(ERR_BAD_ARG,"Argument is missing a required value: %s",arg);
+        }
+        String value = arg.substring(idx + 1).trim();
+        if (value.length() <= 0)
+        {
+            throw new UsageException(ERR_BAD_ARG,"Argument is missing a required value: %s",arg);
+        }
+        return value;
+    }
+
+    public static List<String> getValues(String arg)
+    {
+        String v = getValue(arg);
+        ArrayList<String> l = new ArrayList<>();
+        for (String s : v.split(","))
+        {
+            if (s != null)
+            {
+                s = s.trim();
+                if (s.length() > 0)
+                {
+                    l.add(s);
+                }
+            }
+        }
+        return l;
+    }
+
+    private Map<String, Prop> props = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+    private List<String> sysPropTracking = new ArrayList<>();
+
+    public void addAll(Props other)
+    {
+        this.props.putAll(other.props);
+        this.sysPropTracking.addAll(other.sysPropTracking);
+    }
+    
+    /**
+     * Add a potential argument as a property.
+     * <p>
+     * If arg is not a property, ignore it.
+     * @param arg the argument to parse for a potential property
+     * @param source the source for this argument (to track origin of property from)
+     */
+    public boolean addPossibleProperty(String arg, String source)
+    {
+        // Start property (syntax similar to System property)
+        if (arg.startsWith("-D"))
+        {
+            String[] assign = arg.substring(2).split("=",2);
+            switch (assign.length)
+            {
+                case 2:
+                    setSystemProperty(assign[0],assign[1]);
+                    setProperty(assign[0],assign[1],source);
+                    return true;
+                case 1:
+                    setSystemProperty(assign[0],"");
+                    setProperty(assign[0],"",source);
+                    return true;
+                default:
+                    return false;
+            }
+        }
+
+        // Is this a raw property declaration?
+        int idx = arg.indexOf('=');
+        if (idx >= 0)
+        {
+            String key = arg.substring(0,idx);
+            String value = arg.substring(idx + 1);
+
+            setProperty(key,value,source);
+            return true;
+        }
+
+        // All other strings are ignored
+        return false;
+    }
+
+    public String cleanReference(String property)
+    {
+        String name = property.trim();
+        if (name.startsWith("${") && name.endsWith("}"))
+        {
+            name = name.substring(2,name.length() - 1);
+        }
+        return name.trim();
+    }
+
+    public boolean containsKey(String key)
+    {
+        return props.containsKey(key);
+    }
+
+    public String expand(String str)
+    {
+        return expand(str,new Stack<String>());
+    }
+
+    public String expand(String str, Stack<String> seenStack)
+    {
+        if (str == null)
+        {
+            return str;
+        }
+
+        if (str.indexOf("${") < 0)
+        {
+            // Contains no potential expressions.
+            return str;
+        }
+
+        Pattern pat = Pattern.compile("(?<=[^$]|^)(\\$\\{[^}]*\\})");
+        Matcher mat = pat.matcher(str);
+        StringBuilder expanded = new StringBuilder();
+        int offset = 0;
+        String property;
+        String value;
+
+        while (mat.find(offset))
+        {
+            property = cleanReference(mat.group(1));
+
+            // Loop detection
+            if (seenStack.contains(property))
+            {
+                StringBuilder err = new StringBuilder();
+                err.append("Property expansion loop detected: ");
+                int idx = seenStack.lastIndexOf(property);
+                for (int i = idx; i < seenStack.size(); i++)
+                {
+                    err.append(seenStack.get(i));
+                    err.append(" -> ");
+                }
+                err.append(property);
+                throw new PropsException(err.toString());
+            }
+
+            seenStack.push(property);
+
+            // find property name
+            expanded.append(str.subSequence(offset,mat.start(1)));
+            // get property value
+            value = getString(property);
+            if (value == null)
+            {
+                StartLog.trace("Unable to expand: %s",property);
+                expanded.append(mat.group(1));
+            }
+            else
+            {
+                // recursively expand
+                value = expand(value,seenStack);
+                expanded.append(value);
+            }
+            // update offset
+            offset = mat.end(1);
+        }
+
+        // leftover
+        expanded.append(str.substring(offset));
+
+        // special case for "$$"
+        if (expanded.indexOf("$$") >= 0)
+        {
+            return expanded.toString().replaceAll("\\$\\$","\\$");
+        }
+
+        return expanded.toString();
+    }
+
+    public Prop getProp(String key)
+    {
+        return getProp(key,true);
+    }
+
+    public Prop getProp(String key, boolean searchSystemProps)
+    {
+        Prop prop = props.get(key);
+        if ((prop == null) && searchSystemProps)
+        {
+            // try system property
+            prop = getSystemProperty(key);
+        }
+        return prop;
+    }
+
+    public String getString(String key)
+    {
+        if (key == null)
+        {
+            throw new PropsException("Cannot get value for null key");
+        }
+
+        String name = cleanReference(key);
+
+        if (name.length() == 0)
+        {
+            throw new PropsException("Cannot get value for empty key");
+        }
+
+        Prop prop = getProp(name);
+        if (prop == null)
+        {
+            return null;
+        }
+        return prop.value;
+    }
+
+    public String getString(String key, String defVal)
+    {
+        String val = getString(key);
+        if (val == null)
+        {
+            return defVal;
+        }
+        return val;
+    }
+
+    private Prop getSystemProperty(String key)
+    {
+        String value = System.getProperty(key);
+        if (value == null)
+        {
+            return null;
+        }
+        return new Prop(key,value,ORIGIN_SYSPROP);
+    }
+
+    public static boolean hasPropertyKey(String name)
+    {
+        return Pattern.compile("(?<=[^$]|^)(\\$\\{[^}]*\\})").matcher(name).find();
+    }
+
+    @Override
+    public Iterator<Prop> iterator()
+    {
+        return props.values().iterator();
+    }
+
+    public void reset()
+    {
+        props.clear();
+    }
+
+    public void setProperty(Prop prop)
+    {
+        props.put(prop.key,prop);
+    }
+
+    public void setProperty(String key, String value, String origin)
+    {
+        Prop prop = props.get(key);
+        if (prop == null)
+        {
+            prop = new Prop(key,value,origin);
+        }
+        else
+        {
+            prop = new Prop(key,value,origin,prop);
+        }
+        props.put(key,prop);
+    }
+
+    public int size()
+    {
+        return props.size();
+    }
+
+    public void store(OutputStream stream, String comments) throws IOException
+    {
+        Properties props = new Properties();
+        // add all Props as normal properties, with expansion performed.
+        for (Prop prop : this)
+        {
+            props.setProperty(prop.key,expand(prop.value));
+        }
+        // write normal properties file
+        props.store(stream,comments);
+    }
+
+    public void setSystemProperty(String key, String value)
+    {
+        System.setProperty(key,value);
+        sysPropTracking.add(key);
+    }
+}
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/PropsException.java b/jetty-start/src/main/java/org/eclipse/jetty/start/PropsException.java
new file mode 100644
index 0000000..9c1daf4
--- /dev/null
+++ b/jetty-start/src/main/java/org/eclipse/jetty/start/PropsException.java
@@ -0,0 +1,41 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.start;
+
+/**
+ * An non-recoverable error with Props usage
+ */
+ at SuppressWarnings("serial")
+public class PropsException extends RuntimeException
+{
+    public PropsException(String message, Throwable cause)
+    {
+        super(message,cause);
+    }
+
+    public PropsException(String message)
+    {
+        super(message);
+    }
+
+    public PropsException(Throwable cause)
+    {
+        super(cause);
+    }
+}
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/README.TXT b/jetty-start/src/main/java/org/eclipse/jetty/start/README.TXT
new file mode 100644
index 0000000..fc569bc
--- /dev/null
+++ b/jetty-start/src/main/java/org/eclipse/jetty/start/README.TXT
@@ -0,0 +1,48 @@
+Jetty start
+-----------
+
+The run directory is either the top-level of a distribution
+or jetty-distribution/target/distribution directory when built from
+source.
+
+Jetty start.jar provides a cross platform replacement for startup scripts.
+It makes use of executable JAR that builds the classpath and then executes
+jetty.
+
+To run with the demo:
+
+  java -jar start.jar --enable=demo
+  java -jar start.jar
+
+To run with the default modules:
+
+  java -jar start.jar
+
+The default options may be specified in the start.ini file, or if
+that is not present, they are defined in the start.config file that
+is within the start.jar.
+
+To run with specific configuration file(s)
+
+  java -jar start.jar etc/jetty.xml
+
+To see the available options
+
+  java -jar start.jar --help
+
+To run with JSP support (if available)
+
+  java -jar start.jar --module=jsp
+
+To run with JMX support
+
+  java -jar start.jar --module=jmx
+
+To run with JSP & JMX support
+
+    java -jar start.jar --module=jsp,jmx
+
+Note that JSP requires the jasper jars to be within $JETTY/lib/jsp  These 
+are currently not distributed with the eclipse release and must be
+obtained from a jetty-hightide release from codehaus.
+
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/README.txt b/jetty-start/src/main/java/org/eclipse/jetty/start/README.txt
deleted file mode 100644
index 3669750..0000000
--- a/jetty-start/src/main/java/org/eclipse/jetty/start/README.txt
+++ /dev/null
@@ -1,47 +0,0 @@
-Jetty start
------------
-
-The run directory is either the top-level of a distribution
-or jetty-distribution/target/distribution directory when built from
-source.
-
-Jetty start.jar provides a cross platform replacement for startup scripts.
-It makes use of executable JAR that builds the classpath and then executes
-jetty.
-
-To run with all the demo options:
-
-  java -jar start.jar OPTIONS=All
-
-To run with the default options:
-
-  java -jar start.jar
-
-The default options may be specified in the start.ini file, or if
-that is not present, they are defined in the start.config file that
-is within the start.jar.
-
-To run with specific configuration file(s)
-
-  java -jar start.jar etc/jetty.xml
-
-To see the available options
-
-  java -jar start.jar --help
-
-To run with JSP support (if available)
-
-  java -jar start.jar OPTIONS=Server,jsp
-
-To run with JMX support
-
-  java -jar start.jar OPTIONS=Server,jmx etc/jetty-jmx.xml etc/jetty.xml
-
-To run with JSP & JMX support
-
-    java -jar start.jar OPTIONS=Server,jsp,jmx etc/jetty-jmx.xml etc/jetty.xml
-
-Note that JSP requires the jasper jars to be within $JETTY/lib/jsp  These 
-are currently not distributed with the eclipse release and must be
-obtained from a jetty-hightide release from codehaus.
-
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/RawArgs.java b/jetty-start/src/main/java/org/eclipse/jetty/start/RawArgs.java
new file mode 100644
index 0000000..add0f38
--- /dev/null
+++ b/jetty-start/src/main/java/org/eclipse/jetty/start/RawArgs.java
@@ -0,0 +1,97 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.start;
+
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.eclipse.jetty.start.RawArgs.Entry;
+
+public class RawArgs implements Iterable<Entry>
+{
+    public class Entry
+    {
+        private String line;
+        private String origin;
+
+        private Entry(String line, String origin)
+        {
+            this.line = line;
+            this.origin = origin;
+        }
+
+        public String getLine()
+        {
+            return line;
+        }
+
+        public String getOrigin()
+        {
+            return origin;
+        }
+
+        public boolean startsWith(String val)
+        {
+            return line.startsWith(val);
+        }
+    }
+
+    /**
+     * All of the args, in argument order
+     */
+    private List<Entry> args = new ArrayList<>();
+
+    public void addAll(List<String> lines, Path sourceFile)
+    {
+        String source = sourceFile.toAbsolutePath().toString();
+        for (String line : lines)
+        {
+            addArg(line,source);
+        }
+    }
+
+    public void addArg(final String rawline, final String source)
+    {
+        if (rawline == null)
+        {
+            return;
+        }
+
+        String line = rawline.trim();
+        if (line.length() == 0)
+        {
+            return;
+        }
+
+        args.add(new Entry(line,source));
+    }
+
+    @Override
+    public Iterator<Entry> iterator()
+    {
+        return args.iterator();
+    }
+
+    public int size()
+    {
+        return args.size();
+    }
+}
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/StartArgs.java b/jetty-start/src/main/java/org/eclipse/jetty/start/StartArgs.java
new file mode 100644
index 0000000..43d9bee
--- /dev/null
+++ b/jetty-start/src/main/java/org/eclipse/jetty/start/StartArgs.java
@@ -0,0 +1,1046 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.start;
+
+import static org.eclipse.jetty.start.UsageException.*;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.StringTokenizer;
+
+import org.eclipse.jetty.start.Props.Prop;
+import org.eclipse.jetty.start.config.ConfigSource;
+import org.eclipse.jetty.start.config.ConfigSources;
+import org.eclipse.jetty.start.config.DirConfigSource;
+
+/**
+ * The Arguments required to start Jetty.
+ */
+public class StartArgs
+{
+    public static final String VERSION;
+
+    static
+    {
+        String ver = System.getProperty("jetty.version",null);
+
+        if (ver == null)
+        {
+            Package pkg = StartArgs.class.getPackage();
+            if ((pkg != null) && "Eclipse.org - Jetty".equals(pkg.getImplementationVendor()) && (pkg.getImplementationVersion() != null))
+            {
+                ver = pkg.getImplementationVersion();
+            }
+        }
+
+        if (ver == null)
+        {
+            ver = "TEST";
+        }
+
+        VERSION = ver;
+        System.setProperty("jetty.version",VERSION);
+    }
+
+    private static final String SERVER_MAIN = "org.eclipse.jetty.xml.XmlConfiguration";
+
+    /** List of enabled modules */
+    private Set<String> modules = new HashSet<>();
+    /** Map of enabled modules to the source of where that activation occurred */
+    private Map<String, List<String>> sources = new HashMap<>();
+    /** Map of properties to where that property was declared */
+    private Map<String, String> propertySource = new HashMap<>();
+    /** List of all active [files] sections from enabled modules */
+    private List<FileArg> files = new ArrayList<>();
+    /** List of all active [lib] sections from enabled modules */
+    private Classpath classpath;
+    /** List of all active [xml] sections from enabled modules */
+    private List<Path> xmls = new ArrayList<>();
+    /** JVM arguments, found via commmand line and in all active [exec] sections from enabled modules */
+    private List<String> jvmArgs = new ArrayList<>();
+
+    /** List of all xml references found directly on command line or start.ini */
+    private List<String> xmlRefs = new ArrayList<>();
+
+    /** List of all property references found directly on command line or start.ini */
+    private List<String> propertyFileRefs = new ArrayList<>();
+    
+    /** List of all property files */
+    private List<Path> propertyFiles = new ArrayList<>();
+
+    private Props properties = new Props();
+    private Set<String> systemPropertyKeys = new HashSet<>();
+    private List<String> rawLibs = new ArrayList<>();
+
+    // jetty.base - build out commands
+    /** --add-to-startd=[module,[module]] */
+    private List<String> addToStartdIni = new ArrayList<>();
+    /** --add-to-start=[module,[module]] */
+    private List<String> addToStartIni = new ArrayList<>();
+
+    // module inspection commands
+    /** --write-module-graph=[filename] */
+    private String moduleGraphFilename;
+
+    /** Collection of all modules */
+    private Modules allModules;
+    /** Should the server be run? */
+    private boolean run = true;
+    private boolean download = false;
+    private boolean help = false;
+    private boolean stopCommand = false;
+    private boolean listModules = false;
+    private boolean listClasspath = false;
+    private boolean listConfig = false;
+    private boolean version = false;
+    private boolean dryRun = false;
+
+    private boolean exec = false;
+    private boolean approveAllLicenses = false;
+    private boolean testingMode = false;
+
+    public StartArgs()
+    {
+        classpath = new Classpath();
+    }
+
+    private void addFile(Module module, String uriLocation)
+    {
+        FileArg arg = new FileArg(module, uriLocation);
+        if (!files.contains(arg))
+        {
+            files.add(arg);
+        }
+    }
+
+    public void addSystemProperty(String key, String value)
+    {
+        this.systemPropertyKeys.add(key);
+        System.setProperty(key,value);
+    }
+
+    private void addUniqueXmlFile(String xmlRef, Path xmlfile) throws IOException
+    {
+        if (!FS.canReadFile(xmlfile))
+        {
+            throw new IOException("Cannot read file: " + xmlRef);
+        }
+        xmlfile = FS.toRealPath(xmlfile);
+        if (!xmls.contains(xmlfile))
+        {
+            xmls.add(xmlfile);
+        }
+    }
+    
+    private void addUniquePropertyFile(String propertyFileRef, Path propertyFile) throws IOException
+    {
+        if (!FS.canReadFile(propertyFile))
+        {
+            throw new IOException("Cannot read file: " + propertyFileRef);
+        }
+        propertyFile = FS.toRealPath(propertyFile);
+        if (!propertyFiles.contains(propertyFile))
+        {
+            propertyFiles.add(propertyFile);
+        }
+    }
+
+    public void dumpActiveXmls(BaseHome baseHome)
+    {
+        System.out.println();
+        System.out.println("Jetty Active XMLs:");
+        System.out.println("------------------");
+        if (xmls.isEmpty())
+        {
+            System.out.println(" (no xml files specified)");
+            return;
+        }
+
+        for (Path xml : xmls)
+        {
+            System.out.printf(" %s%n",baseHome.toShortForm(xml.toAbsolutePath()));
+        }
+    }
+
+    public void dumpEnvironment(BaseHome baseHome)
+    {
+        // Java Details
+        System.out.println();
+        System.out.println("Java Environment:");
+        System.out.println("-----------------");
+        dumpSystemProperty("java.home");
+        dumpSystemProperty("java.vm.vendor");
+        dumpSystemProperty("java.vm.version");
+        dumpSystemProperty("java.vm.name");
+        dumpSystemProperty("java.vm.info");
+        dumpSystemProperty("java.runtime.name");
+        dumpSystemProperty("java.runtime.version");
+        dumpSystemProperty("java.io.tmpdir");
+        dumpSystemProperty("user.dir");
+        dumpSystemProperty("user.language");
+        dumpSystemProperty("user.country");
+
+        // Jetty Environment
+        System.out.println();
+        System.out.println("Jetty Environment:");
+        System.out.println("-----------------");
+        dumpProperty("jetty.version");
+        dumpProperty("jetty.home");
+        dumpProperty("jetty.base");
+        
+        // Jetty Configuration Environment
+        System.out.println();
+        System.out.println("Config Search Order:");
+        System.out.println("--------------------");
+        for (ConfigSource config : baseHome.getConfigSources())
+        {
+            System.out.printf(" %s",config.getId());
+            if (config instanceof DirConfigSource)
+            {
+                DirConfigSource dirsource = (DirConfigSource)config;
+                if (dirsource.isPropertyBased())
+                {
+                    System.out.printf(" -> %s",dirsource.getDir());
+                }
+            }
+            System.out.println();
+        }
+        
+        // Jetty Se
+        System.out.println();
+    }
+
+    public void dumpJvmArgs()
+    {
+        System.out.println();
+        System.out.println("JVM Arguments:");
+        System.out.println("--------------");
+        if (jvmArgs.isEmpty())
+        {
+            System.out.println(" (no jvm args specified)");
+            return;
+        }
+
+        for (String jvmArgKey : jvmArgs)
+        {
+            String value = System.getProperty(jvmArgKey);
+            if (value != null)
+            {
+                System.out.printf(" %s = %s%n",jvmArgKey,value);
+            }
+            else
+            {
+                System.out.printf(" %s%n",jvmArgKey);
+            }
+        }
+    }
+
+    public void dumpProperties()
+    {
+        System.out.println();
+        System.out.println("Properties:");
+        System.out.println("-----------");
+
+        List<String> sortedKeys = new ArrayList<>();
+        for (Prop prop : properties)
+        {
+            if (prop.origin.equals(Props.ORIGIN_SYSPROP))
+            {
+                continue; // skip
+            }
+            sortedKeys.add(prop.key);
+        }
+
+        if (sortedKeys.isEmpty())
+        {
+            System.out.println(" (no properties specified)");
+            return;
+        }
+
+        Collections.sort(sortedKeys);
+
+        for (String key : sortedKeys)
+        {
+            dumpProperty(key);
+        }
+    }
+
+    private void dumpProperty(String key)
+    {
+        Prop prop = properties.getProp(key);
+        if (prop == null)
+        {
+            System.out.printf(" %s (not defined)%n",key);
+        }
+        else
+        {
+            System.out.printf(" %s = %s%n",key,properties.expand(prop.value));
+            if (StartLog.isDebugEnabled())
+            {
+                System.out.printf("   origin: %s%n",prop.origin);
+                while (prop.overrides != null)
+                {
+                    prop = prop.overrides;
+                    System.out.printf("   (overrides)%n");
+                    System.out.printf("     %s = %s%n",key,properties.expand(prop.value));
+                    System.out.printf("     origin: %s%n",prop.origin);
+                }
+            }
+        }
+    }
+
+    public void dumpSystemProperties()
+    {
+        System.out.println();
+        System.out.println("System Properties:");
+        System.out.println("------------------");
+
+        if (systemPropertyKeys.isEmpty())
+        {
+            System.out.println(" (no system properties specified)");
+            return;
+        }
+
+        List<String> sortedKeys = new ArrayList<>();
+        sortedKeys.addAll(systemPropertyKeys);
+        Collections.sort(sortedKeys);
+
+        for (String key : sortedKeys)
+        {
+            String value = System.getProperty(key);
+            System.out.printf(" %s = %s%n",key,properties.expand(value));
+        }
+    }
+
+    private void dumpSystemProperty(String key)
+    {
+        System.out.printf(" %s = %s%n",key,System.getProperty(key));
+    }
+
+    /**
+     * Ensure that the System Properties are set (if defined as a System property, or start.config property, or start.ini property)
+     * 
+     * @param key
+     *            the key to be sure of
+     */
+    private void ensureSystemPropertySet(String key)
+    {
+        if (systemPropertyKeys.contains(key))
+        {
+            return; // done
+        }
+
+        if (properties.containsKey(key))
+        {
+            String val = properties.expand(properties.getString(key));
+            if (val == null)
+            {
+                return; // no value to set
+            }
+            // setup system property
+            systemPropertyKeys.add(key);
+            System.setProperty(key,val);
+        }
+    }
+
+    /**
+     * Expand any command line added <code>--lib</code> lib references.
+     * 
+     * @param baseHome
+     * @throws IOException
+     */
+    public void expandLibs(BaseHome baseHome) throws IOException
+    {
+        StartLog.debug("Expanding Libs");
+        for (String rawlibref : rawLibs)
+        {
+            StartLog.debug("rawlibref = " + rawlibref);
+            String libref = properties.expand(rawlibref);
+            StartLog.debug("expanded = " + libref);
+            
+            // perform path escaping (needed by windows)
+            libref = libref.replaceAll("\\\\([^\\\\])","\\\\\\\\$1");
+            
+            for (Path libpath : baseHome.getPaths(libref))
+            {
+                classpath.addComponent(libpath.toFile());
+            }
+        }
+    }
+
+    /**
+     * Build up the Classpath and XML file references based on enabled Module list.
+     * 
+     * @param baseHome
+     * @param activeModules
+     * @throws IOException
+     */
+    public void expandModules(BaseHome baseHome, List<Module> activeModules) throws IOException
+    {
+        StartLog.debug("Expanding Modules");
+        for (Module module : activeModules)
+        {
+            // Find and Expand Libraries
+            for (String rawlibref : module.getLibs())
+            {
+                StartLog.debug("rawlibref = " + rawlibref);
+                String libref = properties.expand(rawlibref);
+                StartLog.debug("expanded = " + libref);
+
+                for (Path libpath : baseHome.getPaths(libref))
+                {
+                    classpath.addComponent(libpath.toFile());
+                }
+            }
+
+            for (String jvmArg : module.getJvmArgs())
+            {
+                exec = true;
+                jvmArgs.add(jvmArg);
+            }
+
+            // Find and Expand XML files
+            for (String xmlRef : module.getXmls())
+            {
+                // Straight Reference
+                Path xmlfile = baseHome.getPath(xmlRef);
+                addUniqueXmlFile(xmlRef,xmlfile);
+            }
+
+            // Register Download operations
+            for (String file : module.getFiles())
+            {
+                StartLog.debug("Adding module specified file: %s",file);
+                addFile(module,file);
+            }
+        }
+    }
+
+    public List<String> getAddToStartdIni()
+    {
+        return addToStartdIni;
+    }
+
+    public List<String> getAddToStartIni()
+    {
+        return addToStartIni;
+    }
+
+    public Modules getAllModules()
+    {
+        return allModules;
+    }
+
+    public Classpath getClasspath()
+    {
+        return classpath;
+    }
+
+    public Set<String> getEnabledModules()
+    {
+        return this.modules;
+    }
+
+    public List<FileArg> getFiles()
+    {
+        return files;
+    }
+
+    public List<String> getJvmArgs()
+    {
+        return jvmArgs;
+    }
+
+    public CommandLineBuilder getMainArgs(BaseHome baseHome, boolean addJavaInit) throws IOException
+    {
+        CommandLineBuilder cmd = new CommandLineBuilder();
+
+        if (addJavaInit)
+        {
+            cmd.addRawArg(CommandLineBuilder.findJavaBin());
+
+            for (String x : jvmArgs)
+            {
+                cmd.addRawArg(x);
+            }
+
+            cmd.addRawArg("-Djava.io.tmpdir=" + System.getProperty("java.io.tmpdir"));
+            cmd.addRawArg("-Djetty.home=" + baseHome.getHome());
+            cmd.addRawArg("-Djetty.base=" + baseHome.getBase());
+
+            // System Properties
+            for (String propKey : systemPropertyKeys)
+            {
+                String value = System.getProperty(propKey);
+                cmd.addEqualsArg("-D" + propKey,value);
+            }
+
+            cmd.addRawArg("-cp");
+            cmd.addRawArg(classpath.toString());
+            cmd.addRawArg(getMainClassname());
+        }
+
+        // Special Stop/Shutdown properties
+        ensureSystemPropertySet("STOP.PORT");
+        ensureSystemPropertySet("STOP.KEY");
+        ensureSystemPropertySet("STOP.WAIT");
+
+        // pass properties as args or as a file
+        if (dryRun || isExec())
+        {
+            for (Prop p : properties)
+                cmd.addRawArg(CommandLineBuilder.quote(p.key)+"="+CommandLineBuilder.quote(p.value));
+        }
+        else if (properties.size() > 0)
+        {
+            File prop_file = File.createTempFile("start",".properties");
+            prop_file.deleteOnExit();
+            try (FileOutputStream out = new FileOutputStream(prop_file))
+            {
+                properties.store(out,"start.jar properties");
+            }
+            cmd.addRawArg(prop_file.getAbsolutePath());
+        }
+
+        for (Path xml : xmls)
+        {
+            cmd.addRawArg(xml.toAbsolutePath().toString());
+        }
+        
+        for (Path propertyFile : propertyFiles)
+        {
+            cmd.addRawArg(propertyFile.toAbsolutePath().toString());
+        }
+
+        return cmd;
+    }
+
+    public String getMainClassname()
+    {
+        String mainclass = System.getProperty("jetty.server",SERVER_MAIN);
+        return System.getProperty("main.class",mainclass);
+    }
+
+    public String getModuleGraphFilename()
+    {
+        return moduleGraphFilename;
+    }
+
+    public Props getProperties()
+    {
+        return properties;
+    }
+
+    public List<String> getSources(String module)
+    {
+        return sources.get(module);
+    }
+
+    public List<Path> getXmlFiles()
+    {
+        return xmls;
+    }
+
+    public boolean hasJvmArgs()
+    {
+        return jvmArgs.size() > 0;
+    }
+
+    public boolean hasSystemProperties()
+    {
+        for (String key : systemPropertyKeys)
+        {
+            // ignored keys
+            if ("jetty.home".equals(key) || "jetty.base".equals(key) || "main.class".equals(key))
+            {
+                // skip
+                continue;
+            }
+            return true;
+        }
+        return false;
+    }
+
+    public boolean isApproveAllLicenses()
+    {
+        return approveAllLicenses;
+    }
+
+    public boolean isDownload()
+    {
+        return download;
+    }
+
+    public boolean isDryRun()
+    {
+        return dryRun;
+    }
+
+    public boolean isExec()
+    {
+        return exec;
+    }
+    
+    public boolean isNormalMainClass()
+    {
+        return SERVER_MAIN.equals(getMainClassname());
+    }
+
+    public boolean isHelp()
+    {
+        return help;
+    }
+
+    public boolean isListClasspath()
+    {
+        return listClasspath;
+    }
+
+    public boolean isListConfig()
+    {
+        return listConfig;
+    }
+
+    public boolean isListModules()
+    {
+        return listModules;
+    }
+
+    public boolean isRun()
+    {
+        return run;
+    }
+
+    public boolean isStopCommand()
+    {
+        return stopCommand;
+    }
+    
+    public boolean isTestingModeEnabled()
+    {
+        return testingMode;
+    }
+
+    public boolean isVersion()
+    {
+        return version;
+    }
+
+    public void parse(ConfigSources sources)
+    {
+        ListIterator<ConfigSource> iter = sources.reverseListIterator();
+        while (iter.hasPrevious())
+        {
+            ConfigSource source = iter.previous();
+            for (RawArgs.Entry arg : source.getArgs())
+            {
+                parse(arg.getLine(),arg.getOrigin());
+            }
+        }
+    }
+
+    public void parse(final String rawarg, String source)
+    {
+        parse(rawarg,source,true);
+    }
+
+    /**
+     * Parse a single line of argument.
+     * 
+     * @param rawarg the raw argument to parse
+     * @param source the origin of this line of argument
+     * @param replaceProps true if properties in this parse replace previous ones, false to not replace.
+     */
+    private void parse(final String rawarg, String source, boolean replaceProps)
+    {
+        if (rawarg == null)
+        {
+            return;
+        }
+        
+        StartLog.debug("parse(\"%s\", \"%s\", %b)",rawarg,source,replaceProps);
+
+        final String arg = rawarg.trim();
+
+        if (arg.length() <= 0)
+        {
+            return;
+        }
+
+        if (arg.startsWith("#"))
+        {
+            return;
+        }
+
+        if ("--help".equals(arg) || "-?".equals(arg))
+        {
+            help = true;
+            run = false;
+            return;
+        }
+
+        if ("--debug".equals(arg) || arg.startsWith("--start-log-file"))
+        {
+            // valid, but handled in StartLog instead
+            return;
+        }
+        
+        if ("--testing-mode".equals(arg))
+        {
+            System.setProperty("org.eclipse.jetty.start.testing","true");
+            testingMode = true;
+            return;
+        }
+
+        if (arg.startsWith("--include-jetty-dir="))
+        {
+            // valid, but handled in ConfigSources instead
+            return;
+        }
+
+        if ("--stop".equals(arg))
+        {
+            stopCommand = true;
+            run = false;
+            return;
+        }
+
+        if (arg.startsWith("--download="))
+        {
+            addFile(null,Props.getValue(arg));
+            run = false;
+            download = true;
+            return;
+        }
+
+        if (arg.equals("--create-files"))
+        {
+            run = false;
+            download = true;
+            return;
+        }
+
+        if ("--list-classpath".equals(arg) || "--version".equals(arg) || "-v".equals(arg) || "--info".equals(arg))
+        {
+            listClasspath = true;
+            run = false;
+            return;
+        }
+
+        if ("--list-config".equals(arg))
+        {
+            listConfig = true;
+            run = false;
+            return;
+        }
+
+        if ("--dry-run".equals(arg) || "--exec-print".equals(arg))
+        {
+            dryRun = true;
+            run = false;
+            return;
+        }
+
+        // Enable forked execution of Jetty server
+        if ("--exec".equals(arg))
+        {
+            exec = true;
+            return;
+        }
+
+        // Enable forked execution of Jetty server
+        if ("--approve-all-licenses".equals(arg))
+        {
+            approveAllLicenses = true;
+            return;
+        }
+
+        // Arbitrary Libraries
+        if (arg.startsWith("--lib="))
+        {
+            String cp = Props.getValue(arg);
+
+            if (cp != null)
+            {
+                StringTokenizer t = new StringTokenizer(cp,File.pathSeparator);
+                while (t.hasMoreTokens())
+                {
+                    rawLibs.add(t.nextToken());
+                }
+            }
+            return;
+        }
+
+        // Module Management
+        if ("--list-modules".equals(arg))
+        {
+            listModules = true;
+            run = false;
+            return;
+        }
+
+        // jetty.base build-out : add to ${jetty.base}/start.d/
+        if (arg.startsWith("--add-to-startd="))
+        {
+            List<String> moduleNames = Props.getValues(arg);
+            addToStartdIni.addAll(moduleNames);
+            run = false;
+            download = true;
+            return;
+        }
+
+        // jetty.base build-out : add to ${jetty.base}/start.ini
+        if (arg.startsWith("--add-to-start="))
+        {
+            List<String> moduleNames = Props.getValues(arg);
+            addToStartIni.addAll(moduleNames);
+            run = false;
+            download = true;
+            return;
+        }
+
+        // Enable a module
+        if (arg.startsWith("--module="))
+        {
+            List<String> moduleNames = Props.getValues(arg);
+            enableModules(source,moduleNames);
+            return;
+        }
+
+        // Create graphviz output of module graph
+        if (arg.startsWith("--write-module-graph="))
+        {
+            this.moduleGraphFilename = Props.getValue(arg);
+            run = false;
+            return;
+        }
+
+        // Start property (syntax similar to System property)
+        if (arg.startsWith("-D"))
+        {
+            String[] assign = arg.substring(2).split("=",2);
+            systemPropertyKeys.add(assign[0]);
+            switch (assign.length)
+            {
+                case 2:
+                    System.setProperty(assign[0],assign[1]);
+                    setProperty(assign[0],assign[1],source,replaceProps);
+                    break;
+                case 1:
+                    System.setProperty(assign[0],"");
+                    setProperty(assign[0],"",source,replaceProps);
+                    break;
+                default:
+                    break;
+            }
+            return;
+        }
+
+        // Anything else with a "-" is considered a JVM argument
+        if (arg.startsWith("-"))
+        {
+            // Only add non-duplicates
+            if (!jvmArgs.contains(arg))
+            {
+                jvmArgs.add(arg);
+            }
+            return;
+        }
+
+        // Is this a raw property declaration?
+        int idx = arg.indexOf('=');
+        if (idx >= 0)
+        {
+            String key = arg.substring(0,idx);
+            String value = arg.substring(idx + 1);
+
+            if (replaceProps)
+            {
+                if (propertySource.containsKey(key))
+                {
+                    StartLog.warn("Property %s in %s already set in %s",key,source,propertySource.get(key));
+                }
+                propertySource.put(key,source);
+            }
+
+            if ("OPTION".equals(key) || "OPTIONS".equals(key))
+            {
+                StringBuilder warn = new StringBuilder();
+                warn.append("The behavior of the argument ");
+                warn.append(arg).append(" (seen in ").append(source);
+                warn.append(") has changed, and is now considered a normal property.  ");
+                warn.append(key).append(" no longer controls what libraries are on your classpath,");
+                warn.append(" use --module instead. See --help for details.");
+                StartLog.warn(warn.toString());
+            }
+
+            setProperty(key,value,source,replaceProps);
+            return;
+        }
+
+        // Is this an xml file?
+        if (FS.isXml(arg))
+        {
+            // only add non-duplicates
+            if (!xmlRefs.contains(arg))
+            {
+                xmlRefs.add(arg);
+            }
+            return;
+        }
+        
+        if (FS.isPropertyFile(arg))
+        {
+            // only add non-duplicates
+            if (!propertyFileRefs.contains(arg))
+            {
+                propertyFileRefs.add(arg);
+            }
+                return;
+        }
+
+        // Anything else is unrecognized
+        throw new UsageException(ERR_BAD_ARG,"Unrecognized argument: \"%s\" in %s",arg,source);
+    }
+
+    private void enableModules(String source, List<String> moduleNames)
+    {
+        for (String moduleName : moduleNames)
+        {
+            modules.add(moduleName);
+            List<String> list = sources.get(moduleName);
+            if (list == null)
+            {
+                list = new ArrayList<String>();
+                sources.put(moduleName,list);
+            }
+            list.add(source);
+        }
+    }
+
+    public void parseModule(Module module)
+    {
+        if(module.hasDefaultConfig()) 
+        {
+            for(String line: module.getDefaultConfig())
+            {
+                parse(line,module.getFilesystemRef(),false);
+            }
+        }
+    }
+
+    public void resolveExtraXmls(BaseHome baseHome) throws IOException
+    {
+        // Find and Expand XML files
+        for (String xmlRef : xmlRefs)
+        {
+            // Straight Reference
+            Path xmlfile = baseHome.getPath(xmlRef);
+            if (!FS.exists(xmlfile))
+            {
+                xmlfile = baseHome.getPath("etc/" + xmlRef);
+            }
+            addUniqueXmlFile(xmlRef,xmlfile);
+        }
+    }
+    
+    public void resolvePropertyFiles(BaseHome baseHome) throws IOException
+    {
+        // Find and Expand property files
+        for (String propertyFileRef : propertyFileRefs)
+        {
+            // Straight Reference
+            Path propertyFile = baseHome.getPath(propertyFileRef);
+            if (!FS.exists(propertyFile))
+            {
+                propertyFile = baseHome.getPath("etc/" + propertyFileRef);
+            }
+            addUniquePropertyFile(propertyFileRef,propertyFile);
+        }
+    }
+
+    public void setAllModules(Modules allModules)
+    {
+        this.allModules = allModules;
+    }
+
+    private void setProperty(String key, String value, String source, boolean replaceProp)
+    {
+        // Special / Prevent override from start.ini's
+        if (key.equals("jetty.home"))
+        {
+            properties.setProperty("jetty.home",System.getProperty("jetty.home"),source);
+            return;
+        }
+
+        // Special / Prevent override from start.ini's
+        if (key.equals("jetty.base"))
+        {
+            properties.setProperty("jetty.base",System.getProperty("jetty.base"),source);
+            return;
+        }
+
+        // Normal
+        if (replaceProp)
+        {
+            // always override
+            properties.setProperty(key,value,source);
+        }
+        else
+        {
+            // only set if unset
+            if (!properties.containsKey(key))
+            {
+                properties.setProperty(key,value,source);
+            }
+        }
+    }
+
+    public void setRun(boolean run)
+    {
+        this.run = run;
+    }
+
+    @Override
+    public String toString()
+    {
+        StringBuilder builder = new StringBuilder();
+        builder.append("StartArgs [enabledModules=");
+        builder.append(modules);
+        builder.append(", xmlRefs=");
+        builder.append(xmlRefs);
+        builder.append(", properties=");
+        builder.append(properties);
+        builder.append(", jvmArgs=");
+        builder.append(jvmArgs);
+        builder.append("]");
+        return builder.toString();
+    }
+
+}
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/StartIni.java b/jetty-start/src/main/java/org/eclipse/jetty/start/StartIni.java
new file mode 100644
index 0000000..939f26f
--- /dev/null
+++ b/jetty-start/src/main/java/org/eclipse/jetty/start/StartIni.java
@@ -0,0 +1,74 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.start;
+
+import java.io.IOException;
+import java.nio.file.Path;
+
+/**
+ * Simple Start .INI handler
+ */
+public class StartIni extends TextFile
+{
+    private Path basedir;
+
+    public StartIni(Path file) throws IOException
+    {
+        super(file);
+    }
+
+    @Override
+    public void addUniqueLine(String line)
+    {
+        if (line.startsWith("--module="))
+        {
+            int idx = line.indexOf('=');
+            String value = line.substring(idx + 1);
+            for (String part : value.split(","))
+            {
+                super.addUniqueLine("--module=" + expandBaseDir(part));
+            }
+        }
+        else
+        {
+            super.addUniqueLine(expandBaseDir(line));
+        }
+    }
+
+    private String expandBaseDir(String line)
+    {
+        if (line == null)
+        {
+            return line;
+        }
+
+        return line.replace("${start.basedir}",basedir.toString());
+    }
+
+    @Override
+    public void init()
+    {
+        basedir = getFile().getParent().toAbsolutePath();
+    }
+
+    public Path getBaseDir()
+    {
+        return basedir;
+    }
+}
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/StartLog.java b/jetty-start/src/main/java/org/eclipse/jetty/start/StartLog.java
new file mode 100644
index 0000000..28fa9ed
--- /dev/null
+++ b/jetty-start/src/main/java/org/eclipse/jetty/start/StartLog.java
@@ -0,0 +1,195 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.start;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.Date;
+
+import org.eclipse.jetty.start.config.CommandLineConfigSource;
+
+/**
+ * Centralized Place for logging.
+ * <p>
+ * Because startup cannot rely on Jetty's Logging, an alternative logging is established.
+ * <p>
+ * Optional behavior is to create a ${jetty.base}/logs/start.log with whatever output the startup process produces.
+ */
+public class StartLog
+{
+    private final static PrintStream stdout = System.out;
+    private final static PrintStream stderr = System.err;
+    private static volatile PrintStream out = System.out;
+    private static volatile PrintStream err = System.err;
+    private final static StartLog INSTANCE = new StartLog();
+
+    public static void debug(String format, Object... args)
+    {
+        if (INSTANCE.debug)
+        {
+            out.printf(format + "%n",args);
+        }
+    }
+    
+    public static void trace(String format, Object... args)
+    {
+        if (INSTANCE.trace)
+        {
+            out.printf("TRACE: " + format + "%n",args);
+        }
+    }
+
+    public static void debug(Throwable t)
+    {
+        if (INSTANCE.debug)
+        {
+            t.printStackTrace(out);
+        }
+    }
+
+    public static StartLog getInstance()
+    {
+        return INSTANCE;
+    }
+    
+    public static void log(String type, String msg)
+    {
+        err.println(type + ": " + msg);
+    }
+    
+    public static void log(String type, String format, Object... args)
+    {
+        err.printf(type + ": " + format + "%n",args);
+    }
+
+    public static void info(String format, Object... args)
+    {
+        log("INFO",format,args);
+    }
+
+    public static void warn(String format, Object... args)
+    {
+        log("WARNING",format,args);
+    }
+
+    public static void warn(Throwable t)
+    {
+        t.printStackTrace(err);
+    }
+
+    public static boolean isDebugEnabled()
+    {
+        return INSTANCE.debug;
+    }
+
+    private boolean trace = false;
+    private boolean debug = false;
+
+    public void initialize(BaseHome baseHome, CommandLineConfigSource cmdLineSource) throws IOException
+    {
+        String dbgProp = cmdLineSource.getProperty("debug");
+        if (dbgProp != null)
+        {
+            debug = Boolean.parseBoolean(dbgProp);
+        }
+
+        String logFileName = cmdLineSource.getProperty("start-log-file");
+
+        for (RawArgs.Entry arg : cmdLineSource.getArgs())
+        {
+            if ("--debug".equals(arg.getLine()))
+            {
+                debug = true;
+                continue;
+            }
+
+            if (arg.startsWith("--start-log-file"))
+            {
+                logFileName = Props.getValue(arg.getLine());
+                continue;
+            }
+        }
+
+        if (logFileName != null)
+        {
+            Path logfile = baseHome.getPath(logFileName);
+            logfile = logfile.toAbsolutePath();
+            initLogFile(logfile);
+        }
+    }
+
+    public void initLogFile(Path logfile) throws IOException
+    {
+        if (logfile != null)
+        {
+            try
+            {
+                Path logDir = logfile.getParent();
+                FS.ensureDirectoryWritable(logDir);
+
+                Path startLog = logfile;
+
+                if (!FS.exists(startLog) && !FS.createNewFile(startLog))
+                {
+                    // Output about error is lost in majority of cases.
+                    throw new UsageException(UsageException.ERR_LOGGING,new IOException("Unable to create: " + startLog.toAbsolutePath()));
+                }
+
+                if (!FS.canWrite(startLog))
+                {
+                    // Output about error is lost in majority of cases.
+                    throw new UsageException(UsageException.ERR_LOGGING,new IOException("Unable to write to: " + startLog.toAbsolutePath()));
+                }
+
+                err.println("StartLog to " + logfile);
+                OutputStream fileout = Files.newOutputStream(startLog,StandardOpenOption.CREATE,StandardOpenOption.APPEND);
+                PrintStream logger = new PrintStream(fileout);
+                out=logger;
+                err=logger;
+                System.setErr(logger);
+                System.setOut(logger);
+                err.println("StartLog Establishing " + logfile + " on " + new Date());
+            }
+            catch (IOException e)
+            {
+                throw new UsageException(UsageException.ERR_LOGGING,e);
+            }
+        }
+    }
+
+    public static void enableDebug()
+    {
+        getInstance().debug = true;
+    }
+    
+    public static void endStartLog()
+    {
+        if (stderr!=err && getInstance().debug)
+        {
+            err.println("StartLog ended");
+            stderr.println("StartLog ended");
+        }
+        System.setErr(stderr);
+        System.setOut(stdout);
+    }
+}
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/TextFile.java b/jetty-start/src/main/java/org/eclipse/jetty/start/TextFile.java
new file mode 100644
index 0000000..67e7d5b
--- /dev/null
+++ b/jetty-start/src/main/java/org/eclipse/jetty/start/TextFile.java
@@ -0,0 +1,127 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.start;
+
+import java.io.BufferedReader;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.regex.Pattern;
+
+/**
+ * Simple common abstraction for Text files, that consist of a series of lines.
+ * <p>
+ * Ignoring lines that are empty, deemed to be comments, or are duplicates of prior lines.
+ */
+public class TextFile implements Iterable<String>
+{
+    private final Path file;
+    private final List<String> lines = new ArrayList<>();
+
+    public TextFile(Path file) throws FileNotFoundException, IOException
+    {
+        this.file = file;
+        init();
+        
+        if (!FS.canReadFile(file))
+        {
+            StartLog.debug("Skipping read of missing file: %s",file.toAbsolutePath());
+            return;
+        }
+
+        try (BufferedReader buf = Files.newBufferedReader(file,StandardCharsets.UTF_8))
+        {
+            String line;
+            while ((line = buf.readLine()) != null)
+            {
+                if (line.length() == 0)
+                {
+                    continue;
+                }
+
+                if (line.charAt(0) == '#')
+                {
+                    continue;
+                }
+
+                // TODO - bad form calling derived method from base class constructor
+                process(line.trim());
+            }
+        }
+    }
+
+    public void addUniqueLine(String line)
+    {
+        if (lines.contains(line))
+        {
+            // skip
+            return;
+        }
+        lines.add(line);
+    }
+
+    public Path getFile()
+    {
+        return file;
+    }
+
+    public List<String> getLineMatches(Pattern pattern)
+    {
+        List<String> ret = new ArrayList<>();
+        for (String line : lines)
+        {
+            if (pattern.matcher(line).matches())
+            {
+                ret.add(line);
+            }
+        }
+        return ret;
+    }
+
+    public List<String> getLines()
+    {
+        return lines;
+    }
+
+    public void init()
+    {
+    }
+
+    @Override
+    public Iterator<String> iterator()
+    {
+        return lines.iterator();
+    }
+    
+    public ListIterator<String> listIterator()
+    {
+        return lines.listIterator();
+    }
+
+    public void process(String line)
+    {
+        addUniqueLine(line);
+    }
+}
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/UsageException.java b/jetty-start/src/main/java/org/eclipse/jetty/start/UsageException.java
new file mode 100644
index 0000000..d42794d
--- /dev/null
+++ b/jetty-start/src/main/java/org/eclipse/jetty/start/UsageException.java
@@ -0,0 +1,50 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.start;
+
+/**
+ * A Usage Error has occured. Print the usage and exit with the appropriate exit code.
+ */
+ at SuppressWarnings("serial")
+public class UsageException extends RuntimeException
+{
+    public static final int ERR_LOGGING = -1;
+    public static final int ERR_INVOKE_MAIN = -2;
+    public static final int ERR_NOT_STOPPED = -4;
+    public static final int ERR_UNKNOWN = -5;
+    public static final int ERR_BAD_ARG = -6;
+    private int exitCode;
+
+    public UsageException(int exitCode, String format, Object... objs)
+    {
+        super(String.format(format,objs));
+        this.exitCode = exitCode;
+    }
+
+    public UsageException(int exitCode, Throwable cause)
+    {
+        super(cause);
+        this.exitCode = exitCode;
+    }
+
+    public int getExitCode()
+    {
+        return exitCode;
+    }
+}
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/Version.java b/jetty-start/src/main/java/org/eclipse/jetty/start/Version.java
index 16d37ef..26550e5 100644
--- a/jetty-start/src/main/java/org/eclipse/jetty/start/Version.java
+++ b/jetty-start/src/main/java/org/eclipse/jetty/start/Version.java
@@ -19,30 +19,79 @@
 package org.eclipse.jetty.start;
 
 /**
- * Utility class for parsing and comparing version strings.
- * JDK 1.1 compatible.
+ * Utility class for parsing and comparing version strings. JDK 1.1 compatible.
  * 
  */
- 
-public class Version {
- 
+
+public class Version
+{
+
     int _version = 0;
     int _revision = 0;
     int _subrevision = 0;
     String _suffix = "";
-    
-    public Version() {
+
+    public Version()
+    {
     }
-    
-    public Version(String version_string) {
+
+    public Version(String version_string)
+    {
         parse(version_string);
     }
-    
+
+    // java.lang.Comparable is Java 1.2! Cannot use it
     /**
-     * parses version string in the form version[.revision[.subrevision[extension]]]
-     * into this instance.
+     * Compares with other version. Does not take extension into account, as there is no reliable way to order them.
+     * 
+     * @return -1 if this is older version that other, 0 if its same version, 1 if it's newer version than other
      */
-    public void parse(String version_string) {
+    public int compare(Version other)
+    {
+        if (other == null)
+        {
+            throw new NullPointerException("other version is null");
+        }
+        if (this._version < other._version)
+        {
+            return -1;
+        }
+        if (this._version > other._version)
+        {
+            return 1;
+        }
+        if (this._revision < other._revision)
+        {
+            return -1;
+        }
+        if (this._revision > other._revision)
+        {
+            return 1;
+        }
+        if (this._subrevision < other._subrevision)
+        {
+            return -1;
+        }
+        if (this._subrevision > other._subrevision)
+        {
+            return 1;
+        }
+        return 0;
+    }
+
+    /**
+     * Check whether this verion is in range of versions specified
+     */
+    public boolean isInRange(Version low, Version high)
+    {
+        return ((compare(low) >= 0) && (compare(high) <= 0));
+    }
+
+    /**
+     * parses version string in the form version[.revision[.subrevision[extension]]] into this instance.
+     */
+    public void parse(String version_string)
+    {
         _version = 0;
         _revision = 0;
         _subrevision = 0;
@@ -50,33 +99,41 @@ public class Version {
         int pos = 0;
         int startpos = 0;
         int endpos = version_string.length();
-        while ( (pos < endpos) && Character.isDigit(version_string.charAt(pos))) {
+        while ((pos < endpos) && Character.isDigit(version_string.charAt(pos)))
+        {
             pos++;
         }
         _version = Integer.parseInt(version_string.substring(startpos,pos));
-        if ((pos < endpos) && version_string.charAt(pos)=='.') {
+        if ((pos < endpos) && (version_string.charAt(pos) == '.'))
+        {
             startpos = ++pos;
-            while ( (pos < endpos) && Character.isDigit(version_string.charAt(pos))) {
+            while ((pos < endpos) && Character.isDigit(version_string.charAt(pos)))
+            {
                 pos++;
             }
             _revision = Integer.parseInt(version_string.substring(startpos,pos));
         }
-        if ((pos < endpos) && version_string.charAt(pos)=='.') {
+        if ((pos < endpos) && (version_string.charAt(pos) == '.'))
+        {
             startpos = ++pos;
-            while ( (pos < endpos) && Character.isDigit(version_string.charAt(pos))) {
+            while ((pos < endpos) && Character.isDigit(version_string.charAt(pos)))
+            {
                 pos++;
             }
             _subrevision = Integer.parseInt(version_string.substring(startpos,pos));
         }
-        if (pos < endpos) {
+        if (pos < endpos)
+        {
             _suffix = version_string.substring(pos);
         }
     }
-    
+
     /**
      * @return string representation of this version
      */
-    public String toString() {
+    @Override
+    public String toString()
+    {
         StringBuffer sb = new StringBuffer(10);
         sb.append(_version);
         sb.append('.');
@@ -86,30 +143,4 @@ public class Version {
         sb.append(_suffix);
         return sb.toString();
     }
-    
-    // java.lang.Comparable is Java 1.2! Cannot use it
-    /**
-     * Compares with other version. Does not take extension into account,
-     * as there is no reliable way to order them.
-     * @return -1 if this is older version that other,
-     *         0 if its same version,
-     *         1 if it's newer version than other
-     */
-    public int compare(Version other) {
-        if (other == null) throw new NullPointerException("other version is null");
-        if (this._version < other._version) return -1;
-        if (this._version > other._version) return 1;
-        if (this._revision < other._revision) return -1;
-        if (this._revision > other._revision) return 1;
-        if (this._subrevision < other._subrevision) return -1;
-        if (this._subrevision > other._subrevision) return 1;
-        return 0;
-    }
-    
-    /**
-     * Check whether this verion is in range of versions specified
-     */
-    public boolean isInRange(Version low, Version high) {
-        return (compare(low)>=0 && compare(high)<=0);
-    }
 }
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/config/CommandLineConfigSource.java b/jetty-start/src/main/java/org/eclipse/jetty/start/config/CommandLineConfigSource.java
new file mode 100644
index 0000000..5adc475
--- /dev/null
+++ b/jetty-start/src/main/java/org/eclipse/jetty/start/config/CommandLineConfigSource.java
@@ -0,0 +1,245 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.start.config;
+
+import java.io.File;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.nio.file.Path;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.eclipse.jetty.start.BaseHome;
+import org.eclipse.jetty.start.FS;
+import org.eclipse.jetty.start.Props;
+import org.eclipse.jetty.start.Props.Prop;
+import org.eclipse.jetty.start.RawArgs;
+import org.eclipse.jetty.start.UsageException;
+
+/**
+ * Configuration Source representing the Command Line arguments.
+ */
+public class CommandLineConfigSource implements ConfigSource
+{
+    public static final String ORIGIN_INTERNAL_FALLBACK = "<internal-fallback>";
+    public static final String ORIGIN_CMD_LINE = "<command-line>";
+
+    private final RawArgs args;
+    private final Props props;
+    private final Path homePath;
+    private final Path basePath;
+
+    public CommandLineConfigSource(String rawargs[])
+    {
+        this.args = new RawArgs();
+        this.props = new Props();
+        for (String arg : rawargs)
+        {
+            this.args.addArg(arg,ORIGIN_CMD_LINE);
+            this.props.addPossibleProperty(arg,ORIGIN_CMD_LINE);
+        }
+
+        // Setup ${jetty.base} and ${jetty.home}
+        this.homePath = findJettyHomePath().toAbsolutePath();
+        this.basePath = findJettyBasePath().toAbsolutePath();
+
+        // Update System Properties
+        setSystemProperty(BaseHome.JETTY_HOME,homePath.toAbsolutePath().toString());
+        setSystemProperty(BaseHome.JETTY_BASE,basePath.toAbsolutePath().toString());
+    }
+
+    private final Path findJettyBasePath()
+    {
+        // If a jetty property is defined, use it
+        Prop prop = this.props.getProp(BaseHome.JETTY_BASE,false);
+        if (prop != null && !isEmpty(prop.value))
+        {
+            return FS.toPath(prop.value);
+        }
+
+        // If a system property is defined, use it
+        String val = System.getProperty(BaseHome.JETTY_BASE);
+        if (!isEmpty(val))
+        {
+            return FS.toPath(val);
+        }
+
+        // Lastly, fall back to base == ${user.dir}
+        Path base = FS.toPath(this.props.getString("user.dir","."));
+        setProperty(BaseHome.JETTY_BASE,base.toString(),ORIGIN_INTERNAL_FALLBACK);
+        return base;
+    }
+
+    private final Path findJettyHomePath()
+    {
+        // If a jetty property is defined, use it
+        Prop prop = this.props.getProp(BaseHome.JETTY_HOME,false);
+        if (prop != null && !isEmpty(prop.value))
+        {
+            return FS.toPath(prop.value);
+        }
+
+        // If a system property is defined, use it
+        String val = System.getProperty(BaseHome.JETTY_HOME);
+        if (!isEmpty(val))
+        {
+            return FS.toPath(val);
+        }
+
+        // Attempt to find path relative to content in jetty's start.jar
+        // based on lookup for the Main class (from jetty's start.jar)
+        String classRef = "org/eclipse/jetty/start/Main.class";
+        URL jarfile = this.getClass().getClassLoader().getResource(classRef);
+        if (jarfile != null)
+        {
+            Matcher m = Pattern.compile("jar:(file:.*)!/" + classRef).matcher(jarfile.toString());
+            if (m.matches())
+            {
+                // ${jetty.home} is relative to found BaseHome class
+                try
+                {
+                    return new File(new URI(m.group(1))).getParentFile().toPath();
+                }
+                catch (URISyntaxException e)
+                {
+                    throw new UsageException(UsageException.ERR_UNKNOWN,e);
+                }
+            }
+        }
+
+        // Lastly, fall back to ${user.dir} default
+        Path home = FS.toPath(System.getProperty("user.dir","."));
+        setProperty(BaseHome.JETTY_HOME,home.toString(),ORIGIN_INTERNAL_FALLBACK);
+        return home;
+    }
+
+    private boolean isEmpty(String value)
+    {
+        if (value == null)
+        {
+            return true;
+        }
+        int len = value.length();
+        for (int i = 0; i < len; i++)
+        {
+            int c = value.codePointAt(i);
+            if (!Character.isWhitespace(c))
+            {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public boolean equals(Object obj)
+    {
+        if (this == obj)
+        {
+            return true;
+        }
+        if (obj == null)
+        {
+            return false;
+        }
+        if (getClass() != obj.getClass())
+        {
+            return false;
+        }
+        CommandLineConfigSource other = (CommandLineConfigSource)obj;
+        if (args == null)
+        {
+            if (other.args != null)
+            {
+                return false;
+            }
+        }
+        else if (!args.equals(other.args))
+        {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public RawArgs getArgs()
+    {
+        return args;
+    }
+
+    public Path getBasePath()
+    {
+        return basePath;
+    }
+
+    public Path getHomePath()
+    {
+        return homePath;
+    }
+
+    @Override
+    public String getId()
+    {
+        return ORIGIN_CMD_LINE;
+    }
+
+    @Override
+    public String getProperty(String key)
+    {
+        return props.getString(key);
+    }
+
+    @Override
+    public Props getProps()
+    {
+        return props;
+    }
+
+    @Override
+    public int getWeight()
+    {
+        return -1; // default value for command line
+    }
+
+    @Override
+    public int hashCode()
+    {
+        final int prime = 31;
+        int result = 1;
+        result = (prime * result) + ((args == null)?0:args.hashCode());
+        return result;
+    }
+
+    public void setProperty(String key, String value, String origin)
+    {
+        this.props.setProperty(key,value,origin);
+    }
+
+    public void setSystemProperty(String key, String value)
+    {
+        this.props.setSystemProperty(key,value);
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s[%s,args.length=%d]",this.getClass().getSimpleName(),getId(),getArgs().size());
+    }
+}
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/config/ConfigSource.java b/jetty-start/src/main/java/org/eclipse/jetty/start/config/ConfigSource.java
new file mode 100644
index 0000000..b0a54e1
--- /dev/null
+++ b/jetty-start/src/main/java/org/eclipse/jetty/start/config/ConfigSource.java
@@ -0,0 +1,75 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.start.config;
+
+import org.eclipse.jetty.start.Props;
+import org.eclipse.jetty.start.RawArgs;
+
+/**
+ * A Configuration Source
+ */
+public interface ConfigSource
+{
+    /**
+     * The identifier for this source.
+     * <p>
+     * Used in end-user display of the source.
+     * 
+     * @return the configuration source identifier.
+     */
+    public String getId();
+
+    /**
+     * The weight of this source, used for proper ordering of the config source search order.
+     * <p>
+     * Recommended Weights:
+     * <pre>
+     *           -1 = the command line
+     *            0 = the ${jetty.base} source
+     *       [1..n] = include-jetty-dir entries from command line
+     *     [n+1..n] = include-jetty-dir entries from start.ini (or start.d/*.ini) 
+     *      9999999 = the ${jetty.home} source
+     * </pre>
+     * 
+     * @return the weight of the config source. (lower value is more important)
+     */
+    public int getWeight();
+
+    /**
+     * The list of Arguments for this ConfigSource
+     * 
+     * @return the list of Arguments for this ConfigSource
+     */
+    public RawArgs getArgs();
+
+    /**
+     * The properties for this ConfigSource
+     * 
+     * @return the properties for this ConfigSource
+     */
+    public Props getProps();
+    
+    /**
+     * Return the value of the specified property.
+     * 
+     * @param key the key to lookup
+     * @return the value of the property, or null if not found
+     */
+    public String getProperty(String key);
+}
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/config/ConfigSources.java b/jetty-start/src/main/java/org/eclipse/jetty/start/config/ConfigSources.java
new file mode 100644
index 0000000..b9ce307
--- /dev/null
+++ b/jetty-start/src/main/java/org/eclipse/jetty/start/config/ConfigSources.java
@@ -0,0 +1,164 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.start.config;
+
+import static org.eclipse.jetty.start.UsageException.*;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.ListIterator;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.eclipse.jetty.start.FS;
+import org.eclipse.jetty.start.Props;
+import org.eclipse.jetty.start.RawArgs;
+import org.eclipse.jetty.start.Props.Prop;
+import org.eclipse.jetty.start.UsageException;
+
+/**
+ * Weighted List of ConfigSources.
+ * <p>
+ */
+public class ConfigSources implements Iterable<ConfigSource>
+{
+    private static class WeightedConfigSourceComparator implements Comparator<ConfigSource>
+    {
+        @Override
+        public int compare(ConfigSource o1, ConfigSource o2)
+        {
+            return o1.getWeight() - o2.getWeight();
+        }
+    }
+
+    private LinkedList<ConfigSource> sources = new LinkedList<>();
+    private Props props = new Props();
+    private AtomicInteger sourceWeight = new AtomicInteger(1);
+
+    public void add(ConfigSource source) throws IOException
+    {
+        if (sources.contains(source))
+        {
+            // TODO: needs a better/more clear error message
+            throw new UsageException(ERR_BAD_ARG,"Duplicate Configuration Source Reference: " + source);
+        }
+        sources.add(source);
+
+        Collections.sort(sources,new WeightedConfigSourceComparator());
+
+        updateProps();
+
+        // look for --include-jetty-dir entries
+        for (RawArgs.Entry arg : source.getArgs())
+        {
+            if (arg.startsWith("--include-jetty-dir"))
+            {
+                String ref = getValue(arg.getLine());
+                String dirName = props.expand(ref);
+                Path dir = FS.toPath(dirName);
+                DirConfigSource dirsource = new DirConfigSource(ref,dir,sourceWeight.incrementAndGet(),true);
+                add(dirsource);
+            }
+        }
+    }
+
+    public CommandLineConfigSource getCommandLineSource()
+    {
+        for (ConfigSource source : sources)
+        {
+            if (source instanceof CommandLineConfigSource)
+            {
+                return (CommandLineConfigSource)source;
+            }
+        }
+        return null;
+    }
+
+    public Prop getProp(String key)
+    {
+        return props.getProp(key);
+    }
+
+    public Props getProps()
+    {
+        return props;
+    }
+
+    private String getValue(String arg)
+    {
+        int idx = arg.indexOf('=');
+        if (idx == (-1))
+        {
+            throw new UsageException(ERR_BAD_ARG,"Argument is missing a required value: %s",arg);
+        }
+        String value = arg.substring(idx + 1).trim();
+        if (value.length() <= 0)
+        {
+            throw new UsageException(ERR_BAD_ARG,"Argument is missing a required value: %s",arg);
+        }
+        return value;
+    }
+
+    @Override
+    public Iterator<ConfigSource> iterator()
+    {
+        return sources.iterator();
+    }
+
+    public ListIterator<ConfigSource> reverseListIterator()
+    {
+        return sources.listIterator(sources.size());
+    }
+
+    @Override
+    public String toString()
+    {
+        StringBuilder str = new StringBuilder();
+        str.append(this.getClass().getSimpleName());
+        str.append('[');
+        boolean delim = false;
+        for (ConfigSource source : sources)
+        {
+            if (delim)
+            {
+                str.append(',');
+            }
+            str.append(source.getId());
+            delim = true;
+        }
+        str.append(']');
+        return str.toString();
+    }
+
+    private void updateProps()
+    {
+        props.reset();
+
+        // add all properties from config sources (in reverse order)
+        ListIterator<ConfigSource> iter = sources.listIterator(sources.size());
+        while (iter.hasPrevious())
+        {
+            ConfigSource source = iter.previous();
+            props.addAll(source.getProps());
+        }
+    }
+}
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/config/DirConfigSource.java b/jetty-start/src/main/java/org/eclipse/jetty/start/config/DirConfigSource.java
new file mode 100644
index 0000000..f339ce1
--- /dev/null
+++ b/jetty-start/src/main/java/org/eclipse/jetty/start/config/DirConfigSource.java
@@ -0,0 +1,256 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.start.config;
+
+import static org.eclipse.jetty.start.UsageException.*;
+
+import java.io.IOException;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.PathMatcher;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.eclipse.jetty.start.FS;
+import org.eclipse.jetty.start.NaturalSort;
+import org.eclipse.jetty.start.PathMatchers;
+import org.eclipse.jetty.start.Props;
+import org.eclipse.jetty.start.RawArgs;
+import org.eclipse.jetty.start.UsageException;
+import org.eclipse.jetty.start.Props.Prop;
+import org.eclipse.jetty.start.StartIni;
+import org.eclipse.jetty.start.StartLog;
+
+/**
+ * A Directory based {@link ConfigSource}.
+ * <p>
+ * Such as <code>${jetty.base}</code> or and <code>--include-jetty-dir=[path]</code> sources.
+ */
+public class DirConfigSource implements ConfigSource
+{
+    private static final List<String> BANNED_ARGS;
+
+    static
+    {
+        // Arguments that are not allowed to be in start.ini or start.d/{name}.ini files
+        BANNED_ARGS = new ArrayList<>();
+        BANNED_ARGS.add("--help");
+        BANNED_ARGS.add("-?");
+        BANNED_ARGS.add("--stop");
+        BANNED_ARGS.add("--dry-run");
+        BANNED_ARGS.add("--exec-print");
+        BANNED_ARGS.add("--list-config");
+        BANNED_ARGS.add("--list-classpath");
+        BANNED_ARGS.add("--list-modules");
+        BANNED_ARGS.add("--write-module-graph");
+        BANNED_ARGS.add("--version");
+        BANNED_ARGS.add("-v");
+        BANNED_ARGS.add("--download");
+        BANNED_ARGS.add("--create-files");
+        BANNED_ARGS.add("--add-to-startd");
+        BANNED_ARGS.add("--add-to-start");
+    }
+
+    private final String id;
+    private final Path dir;
+    private final int weight;
+    private final RawArgs args;
+    private final Props props;
+
+    /**
+     * Create DirConfigSource with specified identifier and directory.
+     * 
+     * @param id
+     *            the identifier for this {@link ConfigSource}
+     * @param dir
+     *            the directory for this {@link ConfigSource}
+     * @param weight
+     *            the configuration weight (used for search order)
+     * @param canHaveArgs
+     *            true if this directory can have start.ini or start.d entries. (false for directories like ${jetty.home}, for example)
+     * @throws IOException
+     *             if unable to load the configuration args
+     */
+    public DirConfigSource(String id, Path dir, int weight, boolean canHaveArgs) throws IOException
+    {
+        this.id = id;
+        this.dir = dir.toAbsolutePath();
+        this.weight = weight;
+        this.props = new Props();
+
+        this.args = new RawArgs();
+
+        if (canHaveArgs)
+        {
+            Path iniFile = dir.resolve("start.ini");
+            if (FS.canReadFile(iniFile))
+            {
+                StartIni ini = new StartIni(iniFile);
+                args.addAll(ini.getLines(),iniFile);
+                parseAllArgs(ini.getLines(),iniFile.toString());
+            }
+
+            Path startDdir = dir.resolve("start.d");
+
+            if (FS.canReadDirectory(startDdir))
+            {
+                DirectoryStream.Filter<Path> filter = new DirectoryStream.Filter<Path>()
+                {
+                    PathMatcher iniMatcher = PathMatchers.getMatcher("glob:**/start.d/*.ini");
+
+                    @Override
+                    public boolean accept(Path entry) throws IOException
+                    {
+                        return iniMatcher.matches(entry);
+                    }
+                };
+
+                List<Path> paths = new ArrayList<>();
+
+                for (Path diniFile : Files.newDirectoryStream(startDdir,filter))
+                {
+                    if (FS.canReadFile(diniFile))
+                    {
+                        paths.add(diniFile);
+                    }
+                }
+
+                Collections.sort(paths,new NaturalSort.Paths());
+
+                for (Path diniFile : paths)
+                {
+                    StartLog.debug("Reading %s/start.d/%s - %s",id,diniFile.getFileName(),diniFile);
+                    StartIni ini = new StartIni(diniFile);
+                    args.addAll(ini.getLines(),diniFile);
+                    parseAllArgs(ini.getLines(),diniFile.toString());
+                }
+            }
+        }
+    }
+
+    private void parseAllArgs(List<String> lines, String origin)
+    {
+        for (String line : lines)
+        {
+            String arg = line;
+            int idx = line.indexOf('=');
+            if (idx > 0)
+            {
+                arg = line.substring(0,idx);
+            }
+            if (BANNED_ARGS.contains(arg))
+            {
+                throw new UsageException(ERR_BAD_ARG,"%s not allowed in %s",arg,origin);
+            }
+            this.props.addPossibleProperty(line,origin);
+        }
+    }
+
+    @Override
+    public boolean equals(Object obj)
+    {
+        if (this == obj)
+        {
+            return true;
+        }
+        if (obj == null)
+        {
+            return false;
+        }
+        if (getClass() != obj.getClass())
+        {
+            return false;
+        }
+        DirConfigSource other = (DirConfigSource)obj;
+        if (dir == null)
+        {
+            if (other.dir != null)
+            {
+                return false;
+            }
+        }
+        else if (!dir.equals(other.dir))
+        {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public RawArgs getArgs()
+    {
+        return args;
+    }
+
+    public Path getDir()
+    {
+        return dir;
+    }
+
+    @Override
+    public String getId()
+    {
+        return id;
+    }
+
+    @Override
+    public String getProperty(String key)
+    {
+        Prop prop = props.getProp(key,false);
+        if (prop == null)
+        {
+            return null;
+        }
+        return prop.value;
+    }
+
+    @Override
+    public Props getProps()
+    {
+        return props;
+    }
+
+    @Override
+    public int getWeight()
+    {
+        return weight;
+    }
+
+    @Override
+    public int hashCode()
+    {
+        final int prime = 31;
+        int result = 1;
+        result = (prime * result) + ((dir == null)?0:dir.hashCode());
+        return result;
+    }
+
+    public boolean isPropertyBased()
+    {
+        return id.contains("${");
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s[%s,%s,args.length=%d]",this.getClass().getSimpleName(),id,dir,getArgs().size());
+    }
+}
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/config/JettyBaseConfigSource.java b/jetty-start/src/main/java/org/eclipse/jetty/start/config/JettyBaseConfigSource.java
new file mode 100644
index 0000000..a0bd25e
--- /dev/null
+++ b/jetty-start/src/main/java/org/eclipse/jetty/start/config/JettyBaseConfigSource.java
@@ -0,0 +1,36 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.start.config;
+
+import java.io.IOException;
+import java.nio.file.Path;
+
+/**
+ * ${jetty.base} specific ConfigSource
+ */
+public class JettyBaseConfigSource extends DirConfigSource
+{
+    // Standard weight for ${jetty.base}, so that it comes after command line, and before everything else 
+    private final static int WEIGHT = 0;
+    
+    public JettyBaseConfigSource(Path dir) throws IOException
+    {
+        super("${jetty.base}",dir,WEIGHT,true);
+    }
+}
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/config/JettyHomeConfigSource.java b/jetty-start/src/main/java/org/eclipse/jetty/start/config/JettyHomeConfigSource.java
new file mode 100644
index 0000000..cadf1ef
--- /dev/null
+++ b/jetty-start/src/main/java/org/eclipse/jetty/start/config/JettyHomeConfigSource.java
@@ -0,0 +1,36 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.start.config;
+
+import java.io.IOException;
+import java.nio.file.Path;
+
+/**
+ * ${jetty.home} specific ConfigSource
+ */
+public class JettyHomeConfigSource extends DirConfigSource
+{
+    // Standard weight for ${jetty.home}, so that it comes after everything else
+    private final static int WEIGHT = 9999999;
+
+    public JettyHomeConfigSource(Path dir) throws IOException
+    {
+        super("${jetty.home}",dir,WEIGHT,false);
+    }
+}
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/package-info.java b/jetty-start/src/main/java/org/eclipse/jetty/start/package-info.java
new file mode 100644
index 0000000..bf73da7
--- /dev/null
+++ b/jetty-start/src/main/java/org/eclipse/jetty/start/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Start : Generic Java Start Mechanism
+ */
+package org.eclipse.jetty.start;
+
diff --git a/jetty-start/src/main/resources/org/eclipse/jetty/start/base-home-warning.txt b/jetty-start/src/main/resources/org/eclipse/jetty/start/base-home-warning.txt
new file mode 100644
index 0000000..a34bfdb
--- /dev/null
+++ b/jetty-start/src/main/resources/org/eclipse/jetty/start/base-home-warning.txt
@@ -0,0 +1,16 @@
+============================================================================
+WARNING: Jetty is starting using LEGACY behavior. 
+
+A proper {jetty.base} should be configured with no changes being made to the {jetty.home} directory.
+
+Please see http://www.eclipse.org/jetty/documentation/current/startup.html
+
+A demo-base directory has been provided as an example of this setup.
+
+  $ cd demo-base
+  $ java -jar ../start.jar
+
+This warning may be disabled by setting the system property
+
+  -Dorg.eclipse.jetty.start.home.warning=false
+============================================================================
diff --git a/jetty-start/src/main/resources/org/eclipse/jetty/start/start.config b/jetty-start/src/main/resources/org/eclipse/jetty/start/start.config
deleted file mode 100644
index bf3c920..0000000
--- a/jetty-start/src/main/resources/org/eclipse/jetty/start/start.config
+++ /dev/null
@@ -1,161 +0,0 @@
-# This file controls what file are to be put on classpath or command line.
-#
-# Format is as follows:
-#
-# Each line contains entry in the format:
-#
-#  SUBJECT [ [!] CONDITION [AND|OR] ]*
-# 
-# where SUBJECT: 
-#   ends with ".class" is the Main class to run.
-#   ends with ".xml" is a configuration file for the command line
-#   ends with "/" is a directory from which to add all jar and zip files. 
-#   ends with "/*" is a directory from which to add all unconsidered jar and zip files.
-#   ends with "/**" is a directory from which to recursively add all unconsidered jar and zip files.
-#   Containing = are used to assign system properties.
-#   Containing ~= are used to assign start properties.
-#   Containing /= are used to assign a canonical path.
-#   all other subjects are treated as files to be added to the classpath.
-#
-# ${name} is expanded to a start property
-# $(name) is expanded to either a start property or a system property. 
-# The start property ${version} is defined as the version of the start.jar
-#
-# Files starting with "/" are considered absolute, all others are relative to
-# the home directory.
-#
-# CONDITION is one of:
-#   always
-#   never
-#   available classname        # true if class on classpath
-#   property name              # true if set as start property
-#   system   name              # true if set as system property
-#   exists file                # true if file/dir exists
-#   java OPERATOR version      # java version compared to literal
-#   nargs OPERATOR number      # number of command line args compared to literal
-#   OPERATOR := one of "<",">","<=",">=","==","!="
-#
-# CONTITIONS can be combined with AND OR or !, with AND being the assume
-# operator for a list of CONDITIONS.
-#
-# Classpath operations are evaluated on the fly, so once a class or jar is
-# added to the classpath, subsequent available conditions will see that class.
-#
-# The configuration file may be divided into sections with option names like:
-# [ssl,default]
-#
-# Clauses after a section header will only be included if they match one of the tags in the 
-# options property.  By default options are set to "default,*" or the OPTIONS property may
-# be used to pass in a list of tags, eg. :
-#
-#    java -jar start.jar OPTIONS=jetty,jsp,ssl
-#
-# The tag '*' is always appended to the options, so any section with the * tag is always 
-# applied.
-#
-
-# add a property defined classpath
-${path}.path                                     property path
-
-# add a property defined library directory
-${lib}/**                                        exists ${lib}
-
-# Try different settings of jetty.home until the start.jar is found.
-jetty.home=.                                     ! exists $(jetty.home)/start.jar 
-jetty.home=..                                    ! exists $(jetty.home)/start.jar 
-jetty.home=jetty-distribution/src/main/resources     ! exists $(jetty.home)/start.jar 
-jetty.home=../jetty-distribution/src/main/resources  ! exists $(jetty.home)/start.jar 
-jetty.home=.                                     ! exists $(jetty.home)/start.jar
-jetty.home/=$(jetty.home)                        exists $(jetty.home)/start.jar
-
-# The main class to run
-org.eclipse.jetty.xml.XmlConfiguration.class
-${start.class}.class                             property start.class
-
-# The default configuration files
-$(jetty.home)/etc/jetty.xml                      nargs == 0
-./jetty-server/src/main/config/etc/jetty.xml     nargs == 0 AND ! exists $(jetty.home)/etc/jetty.xml
-
-# Default OPTIONS if not specified on the command line
-OPTIONS~=default,*                               ! property OPTIONS
-
-# Add a resources directory if it is there
-[All,resources,default]
-$(jetty.home)/resources/
-           
-# Add jetty modules
-[*]
-$(jetty.home)/lib/jetty-util-$(version).jar                                             ! available org.eclipse.jetty.util.StringUtil
-$(jetty.home)/lib/jetty-io-$(version).jar                                               ! available org.eclipse.jetty.io.Buffer
-
-[Server,All,xml,default]
-$(jetty.home)/lib/jetty-xml-$(version).jar                                              ! available org.eclipse.jetty.xml.XmlParser
-         
-[Server,All,server,default]
-$(jetty.home)/lib/servlet-api-3.0.jar                                                   ! available javax.servlet.ServletContext
-$(jetty.home)/lib/jetty-http-$(version).jar                                             ! available org.eclipse.jetty.http.HttpParser
-$(jetty.home)/lib/jetty-continuation-$(version).jar                                     ! available org.eclipse.jetty.continuation.Continuation
-$(jetty.home)/lib/jetty-server-$(version).jar                                           ! available org.eclipse.jetty.server.Server
-                                             
-[Server,All,security,default]
-$(jetty.home)/lib/jetty-security-$(version).jar                                         ! available org.eclipse.jetty.security.LoginService
-                                                       
-[Server,All,servlet,default]
-$(jetty.home)/lib/servlet-api-3.0.jar                                                   ! available javax.servlet.ServletContext
-$(jetty.home)/lib/jetty-servlet-$(version).jar                                          ! available org.eclipse.jetty.servlet.ServletHandler
-                            
-[Server,All,webapp,default]
-$(jetty.home)/lib/jetty-webapp-$(version).jar                                           ! available org.eclipse.jetty.webapp.WebAppContext
-                                  
-[Server,All,deploy,default]
-$(jetty.home)/lib/jetty-deploy-$(version).jar                                           ! available org.eclipse.jetty.deploy.ContextDeployer
-           
-[Server,All,servlets,default]
-$(jetty.home)/lib/jetty-servlets-$(version).jar                                         ! available org.eclipse.jetty.servlets.WelcomeFilter
-
-[All,rewrite]
-$(jetty.home)/lib/jetty-rewrite-$(version).jar                                          ! available org.eclipse.jetty.rewrite.handler.RewriteHandler
-
-[All,jmx]
-$(jetty.home)/lib/jetty-jmx-$(version).jar                                              ! available org.eclipse.jetty.jmx.MBeanContainer
-                  
-[All,ajp]
-$(jetty.home)/lib/jetty-ajp-$(version).jar                                              ! available org.eclipse.jetty.ajp.Ajp13Connection      
-                   
-[All,plus,jndi]
-$(jetty.home)/lib/jetty-jndi-${version}.jar                                             ! available org.eclipse.jetty.jndi.ContextFactory
-$(jetty.home)/lib/jetty-plus-${version}.jar                                             ! available org.eclipse.jetty.plus.jndi.NamingEntry
-$(jetty.home)/lib/jndi/**                                                               exists $(jetty.home)/lib/jndi 
-
-[All,annotations]
-$(jetty.home)/lib/jetty-annotations-$(version).jar                                      ! available org.eclipse.jetty.annotations.AnnotationParser
-$(jetty.home)/lib/annotations/**                                                        exists $(jetty.home)/lib/annotations 
-          
-[All,setuid]
-$(jetty.home)/lib/jetty-setuid-$(version).jar                                           ! available org.eclipse.jetty.setuid.SetUID
-$(jetty.home)/lib/setuid/**                                                       
-                                    
-[All,policy]
-$(jetty.home)/lib/jetty-policy-$(version).jar                                           ! available org.eclipse.jetty.policy.JettyPolicy
-                                    
-[All,Client,client]
-$(jetty.home)/lib/jetty-http-$(version).jar                                             ! available org.eclipse.jetty.http.HttpParser
-$(jetty.home)/lib/jetty-client-$(version).jar                                           ! available org.eclipse.jetty.client.HttpClient
-
-[Client]
-$(jetty.home)/lib/jetty-http-$(version).jar                                             ! available org.eclipse.jetty.http.HttpParser
-
-[All,websocket]
-$(jetty.home)/lib/jetty-websocket-$(version).jar                                        ! available org.eclipse.jetty.websocket.WebSocket
-       
-[All,overlay,overlays]
-$(jetty.home)/lib/jetty-overlay-deployer-$(version).jar                                 ! available org.eclipse.jetty.overlay.OverlayedAppProvider
-       
-      
-# Add ext if it exists
-[Server,All,default,ext]        
-$(jetty.home)/lib/ext/**
-
-# Add all other sub-directories in /lib/ as options in a dynamic way
-[All,=$(jetty.home)/lib/**]        
-
diff --git a/jetty-start/src/main/resources/org/eclipse/jetty/start/usage.txt b/jetty-start/src/main/resources/org/eclipse/jetty/start/usage.txt
index 5af648a..eacb16b 100644
--- a/jetty-start/src/main/resources/org/eclipse/jetty/start/usage.txt
+++ b/jetty-start/src/main/resources/org/eclipse/jetty/start/usage.txt
@@ -2,132 +2,183 @@ Usage: java -jar start.jar [options...] [properties...] [configs...]
 
   The start.jar builds a classpath and executes a main java class with
   a classloader built from that classpath.  By default the start.jar
-  mechanism is configured to start the jetty server, but it can be 
+  mechanism is configured to start the jetty server, but it can be
   configured to start any java main class.
 
 Command Line Options:
+---------------------
+
   --help           This help / usage information.
-  
+
   --version        Print the version information for Jetty and
                    dependent jars, then exit.
-                   
-  --list-options   List the details of each classpath OPTION
-  
-  --list-config    List the start.config file.
-                                    
+
+  --list-classpath Print the classpath information that will be used to start
+                   Jetty
+
+  --list-config    List the resolved configuration that will be used to
+                   start Jetty.
+                   Output includes:
+                     o  Java Environment
+                     o  Jetty Environment
+                     o  JVM Arguments
+                     o  Properties
+                     o  Server Classpath
+                     o  Server XML Configuration
+
   --dry-run        Print the command line that the start.jar generates,
                    then exit. This may be used to generate command lines
                    when the start.ini includes -X or -D arguments.
-                   
-  --exec           Run the generated command line (see --dry-run) in 
+
+  --exec           Run the generated command line (see --dry-run) in
                    a sub process. This can be used when start.ini
                    contains -X or -D arguments, but creates an extra
                    JVM instance.
-                     
-  --stop           Send a stop signal to the running Jetty instance.
-  
-  --daemon         Start in daemon mode with stderr and stdout 
-                   redirected to ${jetty.log}/start.log
-  
-  --config=<file>  Specify an alternate start.config file.  
-                   The default is the start.config file inside
-                   the start.jar. The default can also be specified
-                   with the START system property.
-  
-  --ini=<file>     Load command line arguments from a file. If 
-                   no --ini options are specified, then the 
-                   start.ini file will be read if it exists in 
-                   jetty.home. If specified jetty.home/start.ini
-                   and additional .ini files in jetty.home/start.d/
-                   will NOT be read. A --ini option with no file indicates that
-                   start.ini should not be read.
+
+
+Debug and Start Logging:
+------------------------
+
+  --debug          Enable debug output of the startup procedure.
+                   Note: this does not setup debug for Jetty itself.
+                   If you want debug for Jetty, configure your logging.
+                   http://www.eclipse.org/jetty/documentation/
+
+  --start-log-file=<filename>
+                   A filename, relative to ${jetty.base}, where all startup
+                   output will be sent.  This is useful for capturing startup
+                   issues where the jetty specific logger has not yet kicked
+                   in due to startup configuration errors.
+
+
+Module Management:
+------------------
+
+  --list-modules   List all modules defined by the system.
+                   Looking for module files in ${jetty.base}/modules/*.mod and
+                   then ${jetty.home}/modules/*.mod
+                   Will also list enabled state based on information
+                   present on ..
+                    o  The command line
+                    o  The ${jetty.base}/start.ini
+                    o  The ${jetty.base}/start.d/*.ini files
+
+  --module=<modulename>(,<modulename>)*
+                   Temporarily enable a module from the command line.
+                   Note: this can also be used in the ${jetty.base}/start.ini
+                   or ${jetty.base}/start.d/*.ini files.
+
+  --add-to-start=<modulename>(,<modulename>)*
+                   Enable a module by appending lines to the
+                   ${jetty.base}/start.ini file.
+                   Lines that are added come from the ini template that
+                   the module itself maintains.
+                   Transitive module dependencies are followed and all
+                   modules that the specified module depends on are also
+                   enabled in the ${jetty.base}/start.ini using the same
+                   techniques.
+                   Note: not all modules have ini templates.
+
+  --add-to-startd=<modulename>(,<modulename>)*
+                   Enable a module via creation of an ini file in the
+                   ${jetty.base}/start.d/ directory.
+                   Uses ini template that the module itself maintains.
+                   Transitive module dependencies are followed and all
+                   modules that the specified module depends on are also
+                   enabled via their own ini files in the same directory.
+                   Note: not all modules have ini templates.
+
+  --write-module-graph=<filename>
+                   Create a graphviz *.dot file of the module graph as it
+                   exists for the active ${jetty.base}.
+                   See http://graphviz.org/ for details on how to post-process
+                   this file into the output best suited for your needs.
                    
-  --pre=<file>     Specify a configuration file that is to be processed
-                   before any configuration files listed in start.ini
-
-System Properties:
-  These are set with a command line like "java -Dname=value ..." and are
-  accessible via the java.lang.System#getProperty(String) API.
-  Some key system properties are:
-  
-    org.eclipse.jetty.util.log.class=[class]   
-      A Low Level Jetty Logger Implementation to use
-      (default: org.eclipse.jetty.util.log.Slf4jLog)
-      
-    org.eclipse.jetty.util.log.DEBUG=[boolean]
-      Debug logging for the stderr and javautil Loggers. Slf4j
-      and other loggers must be separately configured for debug.
-      (default: false)
-      
-    org.eclipse.jetty.util.log.IGNORED=[boolean]
-      Ignored exceptions are logged, independent of DEBUG settings
-      (default: false)
+  --create-files   Create any missing files that are required by initialised 
+                   modules.  This may download a file from the network if the 
+                   module provides a URL.
+
+
+Startup / Shutdown Command Line:
+--------------------------------
+
+  --stop           Send a stop signal to the running Jetty instance.
+                   The server must have been started with a STOP.PORT=<port>
+                   property set and the stop command must have the same property.
 
-    org.eclipse.jetty.util.log.SOURCE=[boolean]
-      The source location of logs is logged in the stderr Logger.
-      (default: false)
-      
-    com.sun.management.jmxremote
-      Enable remote JMX management in Sun JVMS.
-      
-      
 Properties:
-  These are set with a command line like "java -jar start.jar name=value"
-  and only affect the start mechanism.  Some of these are defined in the 
-  default start.config and will not be available if another configuration
-  file is used. NOTE: Not all properties are listed here:
 
-    path=[directory]
-      An additional class path element to add to the started class path. Typically
-      this is used to add directories of classes and/or resources
-      
-    lib=[directory]
-      An additional library directory to add to the started class path. This must
-      be a (deep) directory of jars
+    STOP.HOST=[string]
+      The host to use to stop the running Jetty server (defaults to 127.0.0.1)
+      Required along with STOP.PORT if you want to use the --stop option above.
       
     STOP.PORT=[number]
       The port to use to stop the running Jetty server.
       Required along with STOP.KEY if you want to use the --stop option above.
-      
+
     STOP.KEY=[alphanumeric]
       The passphrase defined to stop the server.
       Requried along with STOP.PORT if you want to use the --stop option above.
 
     STOP.WAIT=[number]
-      The time (in seconds) to wait for confirmation that the running Jetty server
-      has stopped. If not specified, the stopper will wait indefinitely. Use in 
-      conjunction with the --stop option.
-      
-    DEBUG=true
-      Enable debug on the start mechanism and sets the
-      org.eclipse.jetty.util.log.stderr.DEBUG system property to true. 
-      (default: false)
-      
-    OPTIONS=[option,option,...]
-      Enable classpath OPTIONS. Each options represents one or more jars 
-      to be added to the classpath. The options are defined in 
-      the start.config file and can be listed with --help or --list-options.
-      By convention, options starting with a capital letter (eg Server) 
-      are aggregations of other available options. Available OPTIONS:
+      The time (in seconds) to wait for confirmation that the running
+      Jetty server has stopped. If not specified, the stopper will wait
+      indefinitely. Use in conjunction with the --stop option.
 
-        @OPTIONS@
 
+Advanced Commands:
+------------------
 
-Available Configurations:
-  By convention, configuration files are kept in $JETTY_HOME/etc.
-  The known configuration files are:
-  
-    @CONFIGS@
+  --lib=<classpath>
+                   Add arbitrary classpath entries to the the server classpath.
+                   
+  --include-jetty-dir=<path>
+                   Include an extra jetty directory to use as a source
+                   for configuration details.  This directory behaves similarly
+                   to ${jetty.base} but sits at a layer between ${jetty.base}
+                   and ${jetty.home}.  This allows for some complex hierarchies
+                   of configuration details.
+
+  --download=<http-uri>|<location>
+                   Advanced usage, If the file does not exist at the given
+                   location, download it from the given http URI.
+                   Notes: location is always relative to ${jetty.base}.
+                          you might need to escape the slash "\|" to use
+                          this on some environments.
+
+
+Properties:
+-----------
+
+  These are set with a command line like "java -jar start.jar name=value"
+  and only affect the start mechanism.  Some of these are defined in the
+  default start.config and will not be available if another configuration
+  file is used. NOTE: Not all properties are listed here:
+
+    jetty.home=[directory]
+      Set the home directory of the jetty distribution.
+
+    jetty.base=[directory]
+      Set the jetty configuration directory.  This is where the etc, webapps and start
+      files will be looked for. If not found in jetty.base, they are looked for in
+      jetty.home.
 
 
 Defaults:
-  A start.ini file may be used to specify default arguments to start.jar,
-  which are used if no command line arguments are provided and override 
-  the defaults in the start.config file. If the directory jetty.home/start.d
-  exists, then multiple *.ini files will be read from that directory in 
-  alphabetical order. If --ini options are provided on  the command line,
-  then start.ini and start.d will NOT be read. 
-  
-  The current start.ini arguments are:
-
-    @STARTINI@
+---------
+
+  Command line arguments can come from any jetty configuration directory
+  (except ${jetty.home}), such as ${jetty.base} and any added jetty directories
+  (see --include-jetty-dir=<path>).  
+  The contents of <path>/start.ini and <path>/start.d/*.ini are all used
+  to build up your command line arguments.
+  In case of a conflict, the resolution of who wins, will look like this.
+    
+    1) <command-line itself>
+    2) ${jetty.base}/start.ini
+    3) ${jetty.base}/start.d/*.ini
+    4) <jetty-dir>/start.ini
+    5) <jetty-dir>/start.d/*.ini
+
+For more information on startup, see the online documentation at
+    http://www.eclipse.org/jetty/documentation/
diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/BaseHomeTest.java b/jetty-start/src/test/java/org/eclipse/jetty/start/BaseHomeTest.java
new file mode 100644
index 0000000..0592633
--- /dev/null
+++ b/jetty-start/src/test/java/org/eclipse/jetty/start/BaseHomeTest.java
@@ -0,0 +1,211 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.start;
+
+import static org.hamcrest.Matchers.*;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jetty.start.config.ConfigSources;
+import org.eclipse.jetty.start.config.JettyBaseConfigSource;
+import org.eclipse.jetty.start.config.JettyHomeConfigSource;
+import org.eclipse.jetty.toolchain.test.IO;
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class BaseHomeTest
+{
+    public static void assertPathList(BaseHome hb, String message, List<String> expected, PathFinder finder)
+    {
+        List<String> actual = new ArrayList<>();
+        for (Path path : finder.getHits())
+        {
+            actual.add(hb.toShortForm(path.toFile()));
+        }
+
+        if (actual.size() != expected.size())
+        {
+            System.out.printf("Actual Path(s): %,d hits%n",actual.size());
+            for (String path : actual)
+            {
+                System.out.printf(" %s%n",path);
+            }
+            System.out.printf("Expected Path(s): %,d entries%n",expected.size());
+            for (String path : expected)
+            {
+                System.out.printf(" %s%n",path);
+            }
+        }
+        Assert.assertThat(message + ": " + Main.join(actual,", "),actual,containsInAnyOrder(expected.toArray()));
+    }
+    
+    public static void assertPathList(BaseHome hb, String message, List<String> expected, List<Path> paths)
+    {
+        List<String> actual = new ArrayList<>();
+        for (Path path : paths)
+        {
+            actual.add(hb.toShortForm(path.toFile()));
+        }
+
+        if (actual.size() != expected.size())
+        {
+            System.out.printf("Actual Path(s): %,d hits%n",actual.size());
+            for (String path : actual)
+            {
+                System.out.printf(" %s%n",path);
+            }
+            System.out.printf("Expected Path(s): %,d entries%n",expected.size());
+            for (String path : expected)
+            {
+                System.out.printf(" %s%n",path);
+            }
+        }
+        Assert.assertThat(message + ": " + Main.join(actual,", "),actual,containsInAnyOrder(expected.toArray()));
+    }
+
+    public static void assertFileList(BaseHome hb, String message, List<String> expected, List<File> files)
+    {
+        List<String> actual = new ArrayList<>();
+        for (File file : files)
+        {
+            actual.add(hb.toShortForm(file));
+        }
+        Assert.assertThat(message + ": " + Main.join(actual,", "),actual,containsInAnyOrder(expected.toArray()));
+    }
+
+    @Test
+    public void testGetPath_OnlyHome() throws IOException
+    {
+        File homeDir = MavenTestingUtils.getTestResourceDir("hb.1/home");
+        
+        ConfigSources config = new ConfigSources();
+        config.add(new JettyHomeConfigSource(homeDir.toPath()));
+
+        BaseHome hb = new BaseHome(config);
+        Path startIni = hb.getPath("start.ini");
+
+        String ref = hb.toShortForm(startIni);
+        Assert.assertThat("Reference",ref,startsWith("${jetty.home}"));
+
+        String contents = IO.readToString(startIni.toFile());
+        Assert.assertThat("Contents",contents,containsString("Home Ini"));
+    }
+
+    @Test
+    public void testGetPaths_OnlyHome() throws IOException
+    {
+        File homeDir = MavenTestingUtils.getTestResourceDir("hb.1/home");
+
+        ConfigSources config = new ConfigSources();
+        config.add(new JettyHomeConfigSource(homeDir.toPath()));
+
+        BaseHome hb = new BaseHome(config);
+        List<Path> paths = hb.getPaths("start.d/*");
+
+        List<String> expected = new ArrayList<>();
+        expected.add("${jetty.home}/start.d/jmx.ini");
+        expected.add("${jetty.home}/start.d/jndi.ini");
+        expected.add("${jetty.home}/start.d/jsp.ini");
+        expected.add("${jetty.home}/start.d/logging.ini");
+        expected.add("${jetty.home}/start.d/ssl.ini");
+        FSTest.toOsSeparators(expected);
+
+        assertPathList(hb,"Paths found",expected,paths);
+    }
+
+    @Test
+    public void testGetPaths_OnlyHome_InisOnly() throws IOException
+    {
+        File homeDir = MavenTestingUtils.getTestResourceDir("hb.1/home");
+
+        ConfigSources config = new ConfigSources();
+        config.add(new JettyHomeConfigSource(homeDir.toPath()));
+
+        BaseHome hb = new BaseHome(config);
+        List<Path> paths = hb.getPaths("start.d/*.ini");
+
+        List<String> expected = new ArrayList<>();
+        expected.add("${jetty.home}/start.d/jmx.ini");
+        expected.add("${jetty.home}/start.d/jndi.ini");
+        expected.add("${jetty.home}/start.d/jsp.ini");
+        expected.add("${jetty.home}/start.d/logging.ini");
+        expected.add("${jetty.home}/start.d/ssl.ini");
+        FSTest.toOsSeparators(expected);
+
+        assertPathList(hb,"Paths found",expected,paths);
+    }
+
+    @Test
+    public void testGetPaths_Both() throws IOException
+    {
+        File homeDir = MavenTestingUtils.getTestResourceDir("hb.1/home");
+        File baseDir = MavenTestingUtils.getTestResourceDir("hb.1/base");
+
+        ConfigSources config = new ConfigSources();
+        config.add(new JettyBaseConfigSource(baseDir.toPath()));
+        config.add(new JettyHomeConfigSource(homeDir.toPath()));
+
+        BaseHome hb = new BaseHome(config);
+        List<Path> paths = hb.getPaths("start.d/*.ini");
+
+        List<String> expected = new ArrayList<>();
+        expected.add("${jetty.base}/start.d/jmx.ini");
+        expected.add("${jetty.home}/start.d/jndi.ini");
+        expected.add("${jetty.home}/start.d/jsp.ini");
+        expected.add("${jetty.base}/start.d/logging.ini");
+        expected.add("${jetty.home}/start.d/ssl.ini");
+        expected.add("${jetty.base}/start.d/myapp.ini");
+        FSTest.toOsSeparators(expected);
+
+        assertPathList(hb,"Paths found",expected,paths);
+    }
+
+    @Test
+    public void testDefault() throws IOException
+    {
+        BaseHome bh = new BaseHome();
+        Assert.assertThat("Home",bh.getHome(),notNullValue());
+        Assert.assertThat("Base",bh.getBase(),notNullValue());
+    }
+
+    @Test
+    public void testGetPath_Both() throws IOException
+    {
+        File homeDir = MavenTestingUtils.getTestResourceDir("hb.1/home");
+        File baseDir = MavenTestingUtils.getTestResourceDir("hb.1/base");
+
+        ConfigSources config = new ConfigSources();
+        config.add(new JettyBaseConfigSource(baseDir.toPath()));
+        config.add(new JettyHomeConfigSource(homeDir.toPath()));
+
+        BaseHome hb = new BaseHome(config);
+        Path startIni = hb.getPath("start.ini");
+
+        String ref = hb.toShortForm(startIni);
+        Assert.assertThat("Reference",ref,startsWith("${jetty.base}"));
+
+        String contents = IO.readToString(startIni.toFile());
+        Assert.assertThat("Contents",contents,containsString("Base Ini"));
+    }
+}
diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/CommandLineBuilderTest.java b/jetty-start/src/test/java/org/eclipse/jetty/start/CommandLineBuilderTest.java
index 10bc9e3..30c448b 100644
--- a/jetty-start/src/test/java/org/eclipse/jetty/start/CommandLineBuilderTest.java
+++ b/jetty-start/src/test/java/org/eclipse/jetty/start/CommandLineBuilderTest.java
@@ -19,8 +19,8 @@
 package org.eclipse.jetty.start;
 
 import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
 
-import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -38,7 +38,7 @@ public class CommandLineBuilderTest
     @Test
     public void testSimpleCommandline()
     {
-        Assert.assertThat(cmd.toString(),is("java -Djava.io.tmpdir=/home/java/temp\\ dir/ --version"));
+        assertThat(cmd.toString(), is("java -Djava.io.tmpdir=/home/java/temp\\ dir/ --version"));
     }
 
     @Test
@@ -65,9 +65,15 @@ public class CommandLineBuilderTest
         System.out.println(cmd.toString());
     }
 
+    @Test
+    public void testQuoteQuotationMarks()
+    {
+        assertQuoting("-XX:OnOutOfMemoryError='kill -9 %p'","-XX:OnOutOfMemoryError='kill -9 %p'");
+    }
+
     private void assertQuoting(String raw, String expected)
     {
         String actual = CommandLineBuilder.quote(raw);
-        Assert.assertThat("Quoted version of [" + raw + "]",actual,is(expected));
+        assertThat("Quoted version of [" + raw + "]", actual, is(expected));
     }
 }
diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/ConfigTest.java b/jetty-start/src/test/java/org/eclipse/jetty/start/ConfigTest.java
deleted file mode 100644
index bdd1421..0000000
--- a/jetty-start/src/test/java/org/eclipse/jetty/start/ConfigTest.java
+++ /dev/null
@@ -1,649 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.start;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-
-public class ConfigTest
-{
-    private void assertEquals(String msg, Classpath expected, Classpath actual)
-    {
-        Assert.assertNotNull(msg + " : expected classpath should not be null",expected);
-        Assert.assertNotNull(msg + " : actual classpath should not be null",actual);
-        Assert.assertTrue(msg + " : expected should have an entry",expected.count() >= 1);
-        Assert.assertTrue(msg + " : actual should have an entry",actual.count() >= 1);
-        if (expected.count() != actual.count())
-        {
-            expected.dump(System.err);
-            actual.dump(System.err);
-            Assert.assertEquals(msg + " : count",expected.count(),actual.count());
-        }
-
-        List<File> actualEntries = Arrays.asList(actual.getElements());
-        List<File> expectedEntries = Arrays.asList(expected.getElements());
-
-        int len = expectedEntries.size();
-
-        for (int i = 0; i < len; i++)
-        {
-            File expectedFile = expectedEntries.get(i);
-            File actualFile = actualEntries.get(i);
-            if (!expectedFile.equals(actualFile))
-            {
-                expected.dump(System.err);
-                actual.dump(System.err);
-                Assert.assertEquals(msg + ": entry [" + i + "]",expectedEntries.get(i),actualEntries.get(i));
-            }
-        }
-    }
-
-    private void assertEquals(String msg, Collection<String> expected, Collection<String> actual)
-    {
-        Assert.assertTrue(msg + " : expected should have an entry",expected.size() >= 1);
-        Assert.assertEquals(msg + " : size",expected.size(),actual.size());
-        for (String expectedVal : expected)
-        {
-            Assert.assertTrue(msg + " : should contain <" + expectedVal + ">",actual.contains(expectedVal));
-        }
-    }
-
-    private String getJettyEtcFile(String name)
-    {
-        File etc = new File(getTestableJettyHome(),"etc");
-        return new File(etc,name).getAbsolutePath();
-    }
-
-    private File getJettyHomeDir()
-    {
-        return new File(getTestResourcesDir(),"jetty.home");
-    }
-
-    private String getTestableJettyHome()
-    {
-        return getJettyHomeDir().getAbsolutePath();
-    }
-
-    private File getTestResourcesDir()
-    {
-        File src = new File(System.getProperty("user.dir"),"src");
-        File test = new File(src,"test");
-        return new File(test,"resources");
-    }
-    
-    @Before
-    public void reset()
-    {
-        Config.clearProperties();
-    }
-
-    /*
-     * Test for SUBJECT "/=" for assign canonical path
-     */
-    @Test
-    public void testSubjectAssignCanonicalPath() throws IOException
-    {
-        StringBuffer buf = new StringBuffer();
-        buf.append("test.resources.dir/=src/test/resources\n");
-
-        Config cfg = new Config();
-        cfg.parse(buf);
-
-        Assert.assertEquals(getTestResourcesDir().getCanonicalPath(),Config.getProperty("test.resources.dir"));
-    }
-
-    /*
-     * Test for SUBJECT "~=" for assigning Start Properties
-     */
-    @Test
-    public void testSubjectAssignStartProperty() throws IOException
-    {
-        StringBuffer buf = new StringBuffer();
-        buf.append("test.jetty.start.text~=foo\n");
-        buf.append("test.jetty.start.quote~=Eatagramovabits\n");
-
-        Config options = new Config();
-        options.parse(buf);
-
-        Assert.assertEquals("foo",Config.getProperty("test.jetty.start.text"));
-        Assert.assertEquals("Eatagramovabits",Config.getProperty("test.jetty.start.quote"));
-    }
-
-    /*
-     * Test for SUBJECT "=" for assigning System Properties
-     */
-    @Test
-    public void testSubjectAssignSystemProperty() throws IOException
-    {
-        StringBuffer buf = new StringBuffer();
-        buf.append("test.jetty.start.text=foo\n");
-        buf.append("test.jetty.start.quote=Eatagramovabits\n");
-
-        Config options = new Config();
-        options.parse(buf);
-
-        Assert.assertEquals("foo",System.getProperty("test.jetty.start.text"));
-        Assert.assertEquals("Eatagramovabits",System.getProperty("test.jetty.start.quote"));
-    }
-
-    /*
-     * Test for SUBJECT ending with "/**", all jar and zip components in dir (deep, recursive)
-     */
-    @Test
-    public void testSubjectComponentDirDeep() throws IOException
-    {
-        StringBuffer buf = new StringBuffer();
-        buf.append("$(jetty.home)/lib/**\n");
-
-        String jettyHome = getTestableJettyHome();
-
-        Config options = new Config();
-        options.setProperty("jetty.home",jettyHome);
-        options.parse(buf);
-
-        Classpath actual = options.getClasspath();
-        Classpath expected = new Classpath();
-
-        File lib = new File(getJettyHomeDir(),"lib");
-
-        expected.addComponent(new File(lib,"core.jar"));
-        expected.addComponent(new File(lib,"example.jar"));
-        expected.addComponent(new File(lib,"http.jar"));
-        expected.addComponent(new File(lib,"io.jar"));
-        expected.addComponent(new File(lib,"JSR.ZIP"));
-        expected.addComponent(new File(lib,"LOGGING.JAR"));
-        expected.addComponent(new File(lib,"server.jar"));
-        expected.addComponent(new File(lib,"spec.zip"));
-        expected.addComponent(new File(lib,"util.jar"));
-        expected.addComponent(new File(lib,"xml.jar"));
-
-        File ext = new File(lib,"ext");
-        expected.addComponent(new File(ext,"custom-impl.jar"));
-        File foo = new File(lib,"foo");
-        File bar = new File(foo,"bar");
-        expected.addComponent(new File(bar,"foobar.jar"));
-
-        assertEquals("Components (Deep)",expected,actual);
-    }
-
-    /*
-     * Test for SUBJECT ending with "/*", all jar and zip components in dir (shallow, no recursion)
-     */
-    @Test
-    public void testSubjectComponentDirShallow() throws IOException
-    {
-        StringBuffer buf = new StringBuffer();
-        buf.append("# Example of any shallow components in /lib/\n");
-        buf.append("$(jetty.home)/lib/*\n");
-
-        String jettyHome = getTestableJettyHome();
-
-        Config options = new Config();
-        options.setProperty("jetty.home",jettyHome);
-        options.parse(buf);
-
-        Classpath actual = options.getClasspath();
-        Classpath expected = new Classpath();
-
-        File lib = new File(getJettyHomeDir(),"lib");
-
-        expected.addComponent(new File(lib,"core.jar"));
-        expected.addComponent(new File(lib,"example.jar"));
-        expected.addComponent(new File(lib,"http.jar"));
-        expected.addComponent(new File(lib,"io.jar"));
-        expected.addComponent(new File(lib,"JSR.ZIP"));
-        expected.addComponent(new File(lib,"LOGGING.JAR"));
-        expected.addComponent(new File(lib,"server.jar"));
-        expected.addComponent(new File(lib,"spec.zip"));
-        expected.addComponent(new File(lib,"util.jar"));
-        expected.addComponent(new File(lib,"xml.jar"));
-
-        assertEquals("Components (Shallow)",expected,actual);
-    }
-
-    /*
-     * Test for SUBJECT ending with ".class", a Main Class
-     */
-    @Test
-    public void testSubjectMainClass() throws IOException
-    {
-        StringBuffer buf = new StringBuffer();
-        buf.append("org.eclipse.jetty.xml.XmlConfiguration.class");
-
-        Config options = new Config();
-        options.parse(buf);
-
-        Assert.assertEquals("org.eclipse.jetty.xml.XmlConfiguration",options.getMainClassname());
-    }
-
-    /*
-     * Test for SUBJECT ending with ".class", a Main Class
-     */
-    @Test
-    public void testSubjectMainClassConditionalPropertySet() throws IOException
-    {
-        StringBuffer buf = new StringBuffer();
-        buf.append("org.eclipse.jetty.xml.XmlConfiguration.class\n");
-        buf.append("${start.class}.class    property start.class");
-
-        Config options = new Config();
-        options.setProperty("start.class","net.company.server.Start");
-        options.parse(buf);
-
-        Assert.assertEquals("net.company.server.Start",options.getMainClassname());
-    }
-
-    /*
-     * Test for SUBJECT ending with ".class", a Main Class
-     */
-    @Test
-    public void testSubjectMainClassConditionalPropertyUnset() throws IOException
-    {
-        StringBuffer buf = new StringBuffer();
-        buf.append("org.eclipse.jetty.xml.XmlConfiguration.class\n");
-        buf.append("${start.class}.class    property start.class");
-
-        Config options = new Config();
-        // The "start.class" property is unset.
-        options.parse(buf);
-
-        Assert.assertEquals("org.eclipse.jetty.xml.XmlConfiguration",options.getMainClassname());
-    }
-
-    /*
-     * Test for SUBJECT ending with "/", a simple Classpath Entry
-     */
-    @Test
-    public void testSubjectSimpleComponent() throws IOException
-    {
-        StringBuffer buf = new StringBuffer();
-        buf.append("$(jetty.home)/resources/\n");
-
-        String jettyHome = getTestableJettyHome();
-
-        Config options = new Config();
-        options.setProperty("jetty.home",jettyHome);
-        options.parse(buf);
-
-        Classpath actual = options.getClasspath();
-        Classpath expected = new Classpath();
-
-        expected.addComponent(new File(getJettyHomeDir(),"resources"));
-
-        assertEquals("Simple Component",expected,actual);
-    }
-
-    /*
-     * Test for SUBJECT ending with "/", a simple Classpath Entry
-     */
-    @Test
-    public void testSubjectSimpleComponentMultiple() throws IOException
-    {
-        StringBuffer buf = new StringBuffer();
-        buf.append("$(jetty.home)/resources/\n");
-        buf.append("$(jetty.home)/etc/\n");
-
-        String jettyHome = getTestableJettyHome();
-
-        Config options = new Config();
-        options.setProperty("jetty.home",jettyHome);
-        options.parse(buf);
-
-        Classpath actual = options.getClasspath();
-        Classpath expected = new Classpath();
-
-        expected.addComponent(new File(getJettyHomeDir(),"resources"));
-        expected.addComponent(new File(getJettyHomeDir(),"etc"));
-
-        assertEquals("Simple Component",expected,actual);
-    }
-
-    /*
-     * Test for SUBJECT ending with "/", a simple Classpath Entry
-     */
-    @Test
-    public void testSubjectSimpleComponentNotExists() throws IOException
-    {
-        StringBuffer buf = new StringBuffer();
-        buf.append("$(jetty.home)/resources/\n");
-        buf.append("$(jetty.home)/foo/\n");
-
-        String jettyHome = getTestableJettyHome();
-
-        Config options = new Config();
-        options.setProperty("jetty.home",jettyHome);
-        options.parse(buf);
-
-        Classpath actual = options.getClasspath();
-        Classpath expected = new Classpath();
-
-        expected.addComponent(new File(getJettyHomeDir(),"resources"));
-
-        assertEquals("Simple Component",expected,actual);
-    }
-
-    /*
-     * Test for SUBJECT ending with ".xml", an XML Configuration File
-     */
-    @Test
-    public void testSubjectXmlConfigAlt() throws IOException
-    {
-        StringBuffer buf = new StringBuffer();
-        // Doesn't exist
-        buf.append("$(jetty.home)/etc/jetty.xml        nargs == 0\n");
-        // test-alt does exist.
-        buf.append("./src/test/resources/test-alt.xml  nargs == 0 AND ! exists $(jetty.home)/etc/jetty.xml");
-
-        String jettyHome = getTestableJettyHome();
-
-        Config options = new Config();
-        options.setProperty("jetty.home",jettyHome);
-        options.parse(buf);
-
-        List<String> actual = options.getXmlConfigs();
-        String expected = new File("src/test/resources/test-alt.xml").getAbsolutePath();
-        Assert.assertEquals("XmlConfig.size",1,actual.size());
-        Assert.assertEquals(expected,actual.get(0));
-    }
-
-    /*
-     * Test for SUBJECT ending with ".xml", an XML Configuration File
-     */
-    @Test
-    public void testSubjectXmlConfigDefault() throws IOException
-    {
-        StringBuffer buf = new StringBuffer();
-        buf.append("$(jetty.home)/etc/test-jetty.xml                   nargs == 0\n");
-        buf.append("./jetty-server/src/main/config/etc/test-jetty.xml  nargs == 0 AND ! exists $(jetty.home)/etc/test-jetty.xml");
-
-        String jettyHome = getTestableJettyHome();
-
-        Config options = new Config();
-        options.setProperty("jetty.home",jettyHome);
-        options.parse(buf);
-
-        List<String> actual = options.getXmlConfigs();
-        String expected = getJettyEtcFile("test-jetty.xml");
-        Assert.assertEquals("XmlConfig.size",1,actual.size());
-        Assert.assertEquals(expected,actual.get(0));
-    }
-
-    /*
-     * Test for SUBJECT ending with ".xml", an XML Configuration File.
-     */
-    @Test
-    public void testSubjectXmlConfigMultiple() throws IOException
-    {
-        StringBuffer buf = new StringBuffer();
-        buf.append("$(jetty.home)/etc/test-jetty.xml           nargs == 0\n");
-        buf.append("$(jetty.home)/etc/test-jetty-ssl.xml       nargs == 0\n");
-        buf.append("$(jetty.home)/etc/test-jetty-security.xml  nargs == 0\n");
-
-        String jettyHome = getTestableJettyHome();
-
-        Config options = new Config();
-        options.setProperty("jetty.home",jettyHome);
-        options.parse(buf);
-
-        List<String> actual = options.getXmlConfigs();
-        List<String> expected = new ArrayList<String>();
-        expected.add(getJettyEtcFile("test-jetty.xml"));
-        expected.add(getJettyEtcFile("test-jetty-ssl.xml"));
-        expected.add(getJettyEtcFile("test-jetty-security.xml"));
-
-        assertEquals("Multiple XML Configs",expected,actual);
-    }
-
-    /*
-     * Test Section Handling
-     */
-    @Test
-    public void testSectionClasspathSingle() throws IOException
-    {
-        StringBuffer buf = new StringBuffer();
-        buf.append("[All]\n");
-        buf.append("$(jetty.home)/lib/core-test.jar\n");
-        buf.append("$(jetty.home)/lib/util.jar\n");
-
-        String jettyHome = getTestableJettyHome();
-
-        Config options = new Config();
-        options.setProperty("jetty.home",jettyHome);
-        options.parse(buf);
-
-        Classpath defaultClasspath = options.getClasspath();
-        Assert.assertNotNull("Default Classpath should not be null",defaultClasspath);
-        Classpath foocp = options.getSectionClasspath("Foo");
-        Assert.assertNull("Foo Classpath should not exist",foocp);
-
-        Classpath allcp = options.getSectionClasspath("All");
-        Assert.assertNotNull("Classpath section 'All' should exist",allcp);
-
-        File lib = new File(getJettyHomeDir(),"lib");
-
-        Classpath expected = new Classpath();
-        expected.addComponent(new File(lib,"core-test.jar"));
-        expected.addComponent(new File(lib,"util.jar"));
-
-        assertEquals("Single Classpath Section",expected,allcp);
-    }
-
-    /*
-     * Test Section Handling
-     */
-    @Test
-    public void testSectionClasspathAvailable() throws IOException
-    {
-        StringBuffer buf = new StringBuffer();
-        buf.append("[All]\n");
-        buf.append("$(jetty.home)/lib/core.jar  ! available org.eclipse.jetty.dummy.Handler\n");
-        buf.append("$(jetty.home)/lib/util.jar  ! available org.eclipse.jetty.dummy.StringUtils\n");
-
-        String jettyHome = getTestableJettyHome();
-
-        Config options = new Config();
-        options.setProperty("jetty.home",jettyHome);
-        options.parse(buf);
-
-        Classpath defaultClasspath = options.getClasspath();
-        Assert.assertNotNull("Default Classpath should not be null",defaultClasspath);
-        Classpath foocp = options.getSectionClasspath("Foo");
-        Assert.assertNull("Foo Classpath should not exist",foocp);
-
-        Classpath allcp = options.getSectionClasspath("All");
-        Assert.assertNotNull("Classpath section 'All' should exist",allcp);
-
-        File lib = new File(getJettyHomeDir(),"lib");
-
-        Classpath expected = new Classpath();
-        expected.addComponent(new File(lib,"core.jar"));
-        expected.addComponent(new File(lib,"util.jar"));
-
-        assertEquals("Single Classpath Section",expected,allcp);
-    }
-
-    /*
-     * Test Section Handling, with multiple defined sections.
-     */
-    @Test
-    public void testSectionClasspathMultiples() throws IOException
-    {
-        StringBuffer buf = new StringBuffer();
-        buf.append("# default\n");
-        buf.append("$(jetty.home)/lib/spec.zip\n");
-        buf.append("\n");
-        buf.append("[*]\n");
-        buf.append("$(jetty.home)/lib/io.jar\n");
-        buf.append("$(jetty.home)/lib/util.jar\n");
-        buf.append("\n");
-        buf.append("[All,server,default]\n");
-        buf.append("$(jetty.home)/lib/core.jar\n");
-        buf.append("$(jetty.home)/lib/server.jar\n");
-        buf.append("$(jetty.home)/lib/http.jar\n");
-        buf.append("\n");
-        buf.append("[All,xml,default]\n");
-        buf.append("$(jetty.home)/lib/xml.jar\n");
-        buf.append("\n");
-        buf.append("[All,logging]\n");
-        buf.append("$(jetty.home)/lib/LOGGING.JAR\n");
-
-        String jettyHome = getTestableJettyHome();
-
-        Config cfg = new Config();
-        cfg.setProperty("jetty.home",jettyHome);
-        cfg.parse(buf);
-
-        Classpath defaultClasspath = cfg.getClasspath();
-        Assert.assertNotNull("Default Classpath should not be null",defaultClasspath);
-
-        Classpath foocp = cfg.getSectionClasspath("Foo");
-        Assert.assertNull("Foo Classpath should not exist",foocp);
-
-        // Test if entire section list can be fetched
-        Set<String> sections = cfg.getSectionIds();
-
-        Set<String> expected = new HashSet<String>();
-        expected.add(Config.DEFAULT_SECTION);
-        expected.add("*");
-        expected.add("All");
-        expected.add("server");
-        expected.add("default");
-        expected.add("xml");
-        expected.add("logging");
-
-        assertEquals("Multiple Section IDs",expected,sections);
-
-        // Test fetch of specific section by name works
-        Classpath cpAll = cfg.getSectionClasspath("All");
-        Assert.assertNotNull("Classpath section 'All' should exist",cpAll);
-
-        File lib = new File(getJettyHomeDir(),"lib");
-
-        Classpath expectedAll = new Classpath();
-        expectedAll.addComponent(new File(lib,"core.jar"));
-        expectedAll.addComponent(new File(lib,"server.jar"));
-        expectedAll.addComponent(new File(lib,"http.jar"));
-        expectedAll.addComponent(new File(lib,"xml.jar"));
-        expectedAll.addComponent(new File(lib,"LOGGING.JAR"));
-
-        assertEquals("Classpath 'All' Section",expectedAll,cpAll);
-
-        // Test combined classpath fetch of multiple sections works
-        List<String> activated = new ArrayList<String>();
-        activated.add("server");
-        activated.add("logging");
-
-        Classpath cpCombined = cfg.getCombinedClasspath(activated);
-
-        Classpath expectedCombined = new Classpath();
-        // from default
-        expectedCombined.addComponent(new File(lib,"spec.zip"));
-        // from 'server'
-        expectedCombined.addComponent(new File(lib,"core.jar"));
-        expectedCombined.addComponent(new File(lib,"server.jar"));
-        expectedCombined.addComponent(new File(lib,"http.jar"));
-        // from 'logging'
-        expectedCombined.addComponent(new File(lib,"LOGGING.JAR"));
-        // from '*'
-        expectedCombined.addComponent(new File(lib,"io.jar"));
-        expectedCombined.addComponent(new File(lib,"util.jar"));
-
-        assertEquals("Classpath combined 'server,logging'",expectedCombined,cpCombined);
-    }
-
-    @Test
-    public void testDynamicSection() throws IOException
-    {
-        StringBuffer buf = new StringBuffer();
-        buf.append("[All,default,=$(jetty.home)/lib/*]\n");
-
-        String jettyHome = getTestableJettyHome();
-
-        Config options = new Config();
-        options.setProperty("jetty.home",jettyHome);
-        options.parse(buf);
-
-        Classpath defaultClasspath = options.getClasspath();
-        Assert.assertNotNull("Default Classpath should not be null",defaultClasspath);
-        Classpath foocp = options.getSectionClasspath("foo");
-        Assert.assertNotNull("Foo Classpath should not exist",foocp);
-
-        Classpath allcp = options.getSectionClasspath("All");
-        Assert.assertNotNull("Classpath section 'All' should exist",allcp);
-
-        Classpath extcp = options.getSectionClasspath("ext");
-        Assert.assertNotNull("Classpath section 'ext' should exist", extcp);
-
-        Assert.assertEquals("Deep Classpath Section",0,foocp.count());
-
-        File lib = new File(getJettyHomeDir(),"lib");
-        File ext = new File(lib, "ext");
-        Classpath expected = new Classpath();
-        expected.addComponent(new File(ext,"custom-impl.jar"));
-        assertEquals("Single Classpath Section",expected,extcp);
-    }
-
-    @Test
-    public void testDeepDynamicSection() throws IOException
-    {
-        StringBuffer buf = new StringBuffer();
-        buf.append("[All,default,=$(jetty.home)/lib/**]\n");
-
-
-        String jettyHome = getTestableJettyHome();
-
-        Config options = new Config();
-        options.setProperty("jetty.home",jettyHome);
-        options.parse(buf);
-
-        Classpath defaultClasspath = options.getClasspath();
-        Assert.assertNotNull("Default Classpath should not be null",defaultClasspath);
-        Classpath foocp = options.getSectionClasspath("foo");
-        Assert.assertNotNull("Foo Classpath should not exist",foocp);
-
-        Classpath allcp = options.getSectionClasspath("All");
-        Assert.assertNotNull("Classpath section 'All' should exist",allcp);
-
-        Classpath extcp = options.getSectionClasspath("ext");
-        Assert.assertNotNull("Classpath section 'ext' should exist", extcp);
-
-        File lib = new File(getJettyHomeDir(),"lib");
-
-        Classpath expected = new Classpath();
-        File foo = new File(lib, "foo");
-        File bar = new File(foo, "bar");
-        expected.addComponent(new File(bar,"foobar.jar"));
-        assertEquals("Deep Classpath Section",expected,foocp);
-
-        File ext = new File(lib, "ext");
-        expected = new Classpath();
-        expected.addComponent(new File(ext,"custom-impl.jar"));
-        assertEquals("Single Classpath Section",expected,extcp);
-    }
-}
diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/ConfigurationAssert.java b/jetty-start/src/test/java/org/eclipse/jetty/start/ConfigurationAssert.java
new file mode 100644
index 0000000..845811b
--- /dev/null
+++ b/jetty-start/src/test/java/org/eclipse/jetty/start/ConfigurationAssert.java
@@ -0,0 +1,281 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.start;
+
+import static org.hamcrest.Matchers.*;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.eclipse.jetty.start.Props.Prop;
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.junit.Assert;
+
+public class ConfigurationAssert
+{
+    /**
+     * Given a provided StartArgs, assert that the configuration it has determined is valid based on values in a assert text file.
+     * 
+     * @param baseHome
+     *            the BaseHome used. Access it via {@link Main#getBaseHome()}
+     * @param args
+     *            the StartArgs that has been processed via {@link Main#processCommandLine(String[])}
+     * @param filename
+     *            the filename of the assertion values
+     * @throws IOException
+     */
+    public static void assertConfiguration(BaseHome baseHome, StartArgs args, String filename) throws FileNotFoundException, IOException
+    {
+        Path testResourcesDir = MavenTestingUtils.getTestResourcesDir().toPath().toAbsolutePath();
+        File file = MavenTestingUtils.getTestResourceFile(filename);
+        TextFile textFile = new TextFile(file.toPath());
+
+        // Validate XMLs (order is important)
+        List<String> expectedXmls = new ArrayList<>();
+        for (String line : textFile)
+        {
+            if (line.startsWith("XML|"))
+            {
+                expectedXmls.add(FS.separators(getValue(line)));
+            }
+        }
+        List<String> actualXmls = new ArrayList<>();
+        for (Path xml : args.getXmlFiles())
+        {
+            actualXmls.add(shorten(baseHome,xml,testResourcesDir));
+        }
+        assertOrdered("XML Resolution Order",expectedXmls,actualXmls);
+
+        // Validate LIBs (order is not important)
+        List<String> expectedLibs = new ArrayList<>();
+        for (String line : textFile)
+        {
+            if (line.startsWith("LIB|"))
+            {
+                expectedLibs.add(FS.separators(getValue(line)));
+            }
+        }
+        List<String> actualLibs = new ArrayList<>();
+        for (File path : args.getClasspath())
+        {
+            actualLibs.add(shorten(baseHome,path.toPath(),testResourcesDir));
+        }
+        assertContainsUnordered("Libs",expectedLibs,actualLibs);
+
+        // Validate PROPERTIES (order is not important)
+        Set<String> expectedProperties = new HashSet<>();
+        for (String line : textFile)
+        {
+            if (line.startsWith("PROP|"))
+            {
+                expectedProperties.add(getValue(line));
+            }
+        }
+        List<String> actualProperties = new ArrayList<>();
+        for (Prop prop : args.getProperties())
+        {
+            String name = prop.key;
+            if ("jetty.home".equals(name) || "jetty.base".equals(name) ||
+                "user.dir".equals(name) || prop.origin.equals(Props.ORIGIN_SYSPROP))
+            {
+                // strip these out from assertion, to make assertions easier.
+                continue;
+            }
+            actualProperties.add(prop.key + "=" + args.getProperties().expand(prop.value));
+        }
+        assertContainsUnordered("Properties",expectedProperties,actualProperties);
+
+        // Validate Downloads
+        List<String> expectedDownloads = new ArrayList<>();
+        for (String line : textFile)
+        {
+            if (line.startsWith("DOWNLOAD|"))
+            {
+                expectedDownloads.add(getValue(line));
+            }
+        }
+        List<String> actualDownloads = new ArrayList<>();
+        for (FileArg darg : args.getFiles())
+        {
+            if (darg.uri != null)
+            {
+                actualDownloads.add(String.format("%s|%s",darg.uri,darg.location));
+            }
+        }
+        assertContainsUnordered("Downloads",expectedDownloads,actualDownloads);
+        
+        // Validate Files/Dirs creation
+        List<String> expectedFiles = new ArrayList<>();
+        for(String line: textFile)
+        {
+            if(line.startsWith("FILE|"))
+            {
+                expectedFiles.add(getValue(line));
+            }
+        }
+        List<String> actualFiles = new ArrayList<>();
+        for(FileArg farg: args.getFiles())
+        {
+            if(farg.uri == null)
+            {
+                actualFiles.add(farg.location);
+            }
+        }
+        assertContainsUnordered("Files/Dirs",expectedFiles,actualFiles);
+    }
+
+    private static String shorten(BaseHome baseHome, Path path, Path testResourcesDir)
+    {
+        String value = baseHome.toShortForm(path);
+        if (value.startsWith("${"))
+        {
+            return value;
+        }
+
+        if (path.startsWith(testResourcesDir))
+        {
+            int len = testResourcesDir.toString().length();
+            value = "${maven-test-resources}" + value.substring(len);
+        }
+        return value;
+    }
+    
+    public static void assertContainsUnordered(String msg, Collection<String> expectedSet, Collection<String> actualSet)
+    {
+        // same size?
+        boolean mismatch = expectedSet.size() != actualSet.size();
+
+        // test content
+        Set<String> missing = new HashSet<>();
+        for (String expected : expectedSet)
+        {
+            if (!actualSet.contains(expected))
+            {
+                missing.add(expected);
+            }
+        }
+
+        if (mismatch || missing.size() > 0)
+        {
+            // build up detailed error message
+            StringWriter message = new StringWriter();
+            PrintWriter err = new PrintWriter(message);
+
+            err.printf("%s: Assert Contains (Unordered)",msg);
+            if (mismatch)
+            {
+                err.print(" [size mismatch]");
+            }
+            if (missing.size() >= 0)
+            {
+                err.printf(" [%d entries missing]",missing.size());
+            }
+            err.println();
+            err.printf("Actual Entries (size: %d)%n",actualSet.size());
+            for (String actual : actualSet)
+            {
+                char indicator = expectedSet.contains(actual)?' ':'>';
+                err.printf("%s| %s%n",indicator,actual);
+            }
+            err.printf("Expected Entries (size: %d)%n",expectedSet.size());
+            for (String expected : expectedSet)
+            {
+                char indicator = actualSet.contains(expected)?' ':'>';
+                err.printf("%s| %s%n",indicator,expected);
+            }
+            err.flush();
+            Assert.fail(message.toString());
+        }
+    }
+
+    public static void assertOrdered(String msg, List<String> expectedList, List<String> actualList)
+    {
+        // same size?
+        boolean mismatch = expectedList.size() != actualList.size();
+
+        // test content
+        List<Integer> badEntries = new ArrayList<>();
+        int min = Math.min(expectedList.size(),actualList.size());
+        int max = Math.max(expectedList.size(),actualList.size());
+        for (int i = 0; i < min; i++)
+        {
+            if (!expectedList.get(i).equals(actualList.get(i)))
+            {
+                badEntries.add(i);
+            }
+        }
+        for (int i = min; i < max; i++)
+        {
+            badEntries.add(i);
+        }
+
+        if (mismatch || badEntries.size() > 0)
+        {
+            // build up detailed error message
+            StringWriter message = new StringWriter();
+            PrintWriter err = new PrintWriter(message);
+
+            err.printf("%s: Assert Contains (Unordered)",msg);
+            if (mismatch)
+            {
+                err.print(" [size mismatch]");
+            }
+            if (badEntries.size() >= 0)
+            {
+                err.printf(" [%d entries not matched]",badEntries.size());
+            }
+            err.println();
+            err.printf("Actual Entries (size: %d)%n",actualList.size());
+            for (int i = 0; i < actualList.size(); i++)
+            {
+                String actual = actualList.get(i);
+                char indicator = badEntries.contains(i)?'>':' ';
+                err.printf("%s[%d] %s%n",indicator,i,actual);
+            }
+
+            err.printf("Expected Entries (size: %d)%n",expectedList.size());
+            for (int i = 0; i < expectedList.size(); i++)
+            {
+                String expected = expectedList.get(i);
+                char indicator = badEntries.contains(i)?'>':' ';
+                err.printf("%s[%d] %s%n",indicator,i,expected);
+            }
+            err.flush();
+            Assert.fail(message.toString());
+        }
+    }
+
+    private static String getValue(String arg)
+    {
+        int idx = arg.indexOf('|');
+        Assert.assertThat("Expecting '|' sign in [" + arg + "]",idx,greaterThanOrEqualTo(0));
+        String value = arg.substring(idx + 1).trim();
+        Assert.assertThat("Expecting Value after '|' in [" + arg + "]",value.length(),greaterThan(0));
+        return value;
+    }
+}
diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/FSTest.java b/jetty-start/src/test/java/org/eclipse/jetty/start/FSTest.java
new file mode 100644
index 0000000..ce26f24
--- /dev/null
+++ b/jetty-start/src/test/java/org/eclipse/jetty/start/FSTest.java
@@ -0,0 +1,62 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.start;
+
+import java.io.File;
+import java.util.List;
+
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class FSTest
+{
+    @Test
+    public void testCanReadDirectory()
+    {
+        File targetDir = MavenTestingUtils.getTargetDir();
+        Assert.assertTrue("Can read dir: " + targetDir,FS.canReadDirectory(targetDir.toPath()));
+    }
+
+    @Test
+    public void testCanReadDirectory_NotDir()
+    {
+        File bogusFile = MavenTestingUtils.getTestResourceFile("bogus.xml");
+        Assert.assertFalse("Can read dir: " + bogusFile,FS.canReadDirectory(bogusFile.toPath()));
+    }
+
+    @Test
+    public void testCanReadFile()
+    {
+        File pom = MavenTestingUtils.getProjectFile("pom.xml");
+        Assert.assertTrue("Can read file: " + pom,FS.canReadFile(pom.toPath()));
+    }
+    
+    /**
+     * Utility method used by other test cases
+     */
+    public static void toOsSeparators(List<String> expected)
+    {
+        for (int i = 0; i < expected.size(); i++)
+        {
+            String fixed = FS.separators(expected.get(i));
+            expected.set(i,fixed);
+        }
+    }
+}
diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/FileArgTest.java b/jetty-start/src/test/java/org/eclipse/jetty/start/FileArgTest.java
new file mode 100644
index 0000000..049437d
--- /dev/null
+++ b/jetty-start/src/test/java/org/eclipse/jetty/start/FileArgTest.java
@@ -0,0 +1,100 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.start;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+ at RunWith(Parameterized.class)
+public class FileArgTest
+{
+    @SuppressWarnings("serial")
+    private static class UseCases extends ArrayList<String[]>
+    {
+        public void add(String rawfileref, String expectedUri, String expectedLocation)
+        {
+            this.add(new String[] { rawfileref, expectedUri, expectedLocation });
+        }
+    }
+
+    @Parameters(name = "{0}")
+    public static List<String[]> data()
+    {
+        UseCases data = new UseCases();
+        data.add("resource",null,"resource");
+        data.add("lib/logging",null,"lib/logging");
+        
+        // -- URI with relative location --
+        data.add("http://machine.com/my.conf|resources/my.conf","http://machine.com/my.conf","resources/my.conf");
+        data.add("http://machine.com:8080/my.conf|resources/my.conf","http://machine.com:8080/my.conf","resources/my.conf");
+        data.add("https://machine.com:8080/my.conf|resources/my.conf","https://machine.com:8080/my.conf","resources/my.conf");
+        // Windows URI (drive mapped)
+        data.add("file:///Z:/share/my.conf|resources/my.conf","file:///Z:/share/my.conf","resources/my.conf");
+        // Windows URI (network share)
+        data.add("file:////nas/share/my.conf|resources/my.conf","file:////nas/share/my.conf","resources/my.conf");
+        
+        // -- URI with absolute location --
+        data.add("http://machine.com/db.dat|/var/run/db.dat","http://machine.com/db.dat","/var/run/db.dat");
+        data.add("http://machine.com:8080/b/db.dat|/var/run/db.dat","http://machine.com:8080/b/db.dat","/var/run/db.dat");
+        data.add("https://machine.com:8080/c/db.dat|/var/run/db.dat","https://machine.com:8080/c/db.dat","/var/run/db.dat");
+        // Windows URI (drive mapped) to drive mapped output
+        data.add("file:///Z:/share/my.conf|C:/db/db.dat","file:///Z:/share/my.conf","C:/db/db.dat");
+        data.add("file:///Z:/share/my.conf|C:\\db\\db.dat","file:///Z:/share/my.conf","C:\\db\\db.dat");
+        // Windows URI (drive mapped) to network share output
+        data.add("file:///Z:/share/my.conf|\\\\nas\\apps\\db\\db.dat","file:///Z:/share/my.conf","\\\\nas\\apps\\db\\db.dat");
+        // Windows URI (network share) to drive mapped output
+        data.add("file:////nas/share/my.conf|C:/db/db.dat","file:////nas/share/my.conf","C:/db/db.dat");
+        data.add("file:////nas/share/my.conf|C:\\db\\db.dat","file:////nas/share/my.conf","C:\\db\\db.dat");
+        // Windows URI (network share) to network share output
+        data.add("file:////nas/share/my.conf|\\\\nas\\apps\\db\\db.dat","file:////nas/share/my.conf","\\\\nas\\apps\\db\\db.dat");
+        return data;
+    }
+
+    @Parameter(value = 0)
+    public String rawFileRef;
+    @Parameter(value = 1)
+    public String expectedUri;
+    @Parameter(value = 2)
+    public String expectedLocation;
+
+    @Test
+    public void testFileArg()
+    {
+        FileArg arg = new FileArg(null,rawFileRef);
+        if (expectedUri == null)
+        {
+            assertThat("URI",arg.uri,nullValue());
+        }
+        else
+        {
+            assertThat("URI",arg.uri,is(expectedUri));
+        }
+        assertThat("Location",arg.location,is(expectedLocation));
+    }
+
+}
diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/IncludeJettyDirTest.java b/jetty-start/src/test/java/org/eclipse/jetty/start/IncludeJettyDirTest.java
new file mode 100644
index 0000000..a197d64
--- /dev/null
+++ b/jetty-start/src/test/java/org/eclipse/jetty/start/IncludeJettyDirTest.java
@@ -0,0 +1,559 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.start;
+
+import static org.hamcrest.Matchers.*;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jetty.start.Props.Prop;
+import org.eclipse.jetty.start.config.ConfigSource;
+import org.eclipse.jetty.start.config.ConfigSources;
+import org.eclipse.jetty.start.config.DirConfigSource;
+import org.eclipse.jetty.toolchain.test.FS;
+import org.eclipse.jetty.toolchain.test.TestingDir;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class IncludeJettyDirTest
+{
+    private static class MainResult
+    {
+        private Main main;
+        private StartArgs args;
+
+        public void assertSearchOrder(List<String> expectedSearchOrder)
+        {
+            ConfigSources sources = main.getBaseHome().getConfigSources();
+            List<String> actualOrder = new ArrayList<>();
+            for (ConfigSource source : sources)
+            {
+                if (source instanceof DirConfigSource)
+                {
+                    actualOrder.add(source.getId());
+                }
+            }
+            ConfigurationAssert.assertOrdered("Search Order",expectedSearchOrder,actualOrder);
+        }
+
+        public void assertProperty(String key, String expectedValue)
+        {
+            Prop prop = args.getProperties().getProp(key);
+            String prefix = "Prop[" + key + "]";
+            Assert.assertThat(prefix + " should have a value",prop,notNullValue());
+            Assert.assertThat(prefix + " value",prop.value,is(expectedValue));
+        }
+    }
+
+    @Rule
+    public TestingDir testdir = new TestingDir();
+
+    private MainResult runMain(File baseDir, File homeDir, String... cmdLineArgs) throws Exception
+    {
+        MainResult ret = new MainResult();
+        ret.main = new Main();
+        List<String> cmdLine = new ArrayList<>();
+        cmdLine.add("jetty.home=" + homeDir.getAbsolutePath());
+        cmdLine.add("jetty.base=" + baseDir.getAbsolutePath());
+        // cmdLine.add("--debug");
+        for (String arg : cmdLineArgs)
+        {
+            cmdLine.add(arg);
+        }
+        ret.args = ret.main.processCommandLine(cmdLine);
+        return ret;
+    }
+
+    @Test
+    public void testNoExtras() throws Exception
+    {
+        // Create home
+        File home = testdir.getFile("home");
+        FS.ensureEmpty(home);
+        TestEnv.copyTestDir("usecases/home",home);
+
+        // Create base
+        File base = testdir.getFile("base");
+        FS.ensureEmpty(base);
+        TestEnv.makeFile(base,"start.ini", //
+                "jetty.host=127.0.0.1");
+
+        // Simple command line - no reference to include-jetty-dirs
+        MainResult result = runMain(base,home);
+
+        List<String> expectedSearchOrder = new ArrayList<>();
+        expectedSearchOrder.add("${jetty.base}");
+        expectedSearchOrder.add("${jetty.home}");
+        result.assertSearchOrder(expectedSearchOrder);
+
+        result.assertProperty("jetty.host","127.0.0.1");
+    }
+
+    @Test
+    public void testCommandLine_1Extra() throws Exception
+    {
+        // Create home
+        File home = testdir.getFile("home");
+        FS.ensureEmpty(home);
+        TestEnv.copyTestDir("usecases/home",home);
+
+        // Create common
+        File common = testdir.getFile("common");
+        FS.ensureEmpty(common);
+        TestEnv.makeFile(common,"start.ini","jetty.port=8080");
+
+        // Create base
+        File base = testdir.getFile("base");
+        FS.ensureEmpty(base);
+        TestEnv.makeFile(base,"start.ini", //
+                "jetty.host=127.0.0.1");
+
+        // Simple command line reference to include-jetty-dir
+        MainResult result = runMain(base,home,
+        // direct reference via path
+                "--include-jetty-dir=" + common.getAbsolutePath());
+
+        List<String> expectedSearchOrder = new ArrayList<>();
+        expectedSearchOrder.add("${jetty.base}");
+        expectedSearchOrder.add(common.getAbsolutePath());
+        expectedSearchOrder.add("${jetty.home}");
+        result.assertSearchOrder(expectedSearchOrder);
+
+        result.assertProperty("jetty.host","127.0.0.1");
+        result.assertProperty("jetty.port","8080"); // from 'common'
+    }
+
+    @Test
+    public void testCommandLine_1Extra_FromSimpleProp() throws Exception
+    {
+        // Create home
+        File home = testdir.getFile("home");
+        FS.ensureEmpty(home);
+        TestEnv.copyTestDir("usecases/home",home);
+
+        // Create common
+        File common = testdir.getFile("common");
+        FS.ensureEmpty(common);
+        TestEnv.makeFile(common,"start.ini","jetty.port=8080");
+
+        // Create base
+        File base = testdir.getFile("base");
+        FS.ensureEmpty(base);
+        TestEnv.makeFile(base,"start.ini", //
+                "jetty.host=127.0.0.1");
+
+        // Simple command line reference to include-jetty-dir via property (also on command line)
+        MainResult result = runMain(base,home,
+        // property
+                "my.common=" + common.getAbsolutePath(),
+                // reference via property
+                "--include-jetty-dir=${my.common}");
+
+        List<String> expectedSearchOrder = new ArrayList<>();
+        expectedSearchOrder.add("${jetty.base}");
+        expectedSearchOrder.add("${my.common}"); // should see property use
+        expectedSearchOrder.add("${jetty.home}");
+        result.assertSearchOrder(expectedSearchOrder);
+
+        result.assertProperty("jetty.host","127.0.0.1");
+        result.assertProperty("jetty.port","8080"); // from 'common'
+    }
+
+    @Test
+    public void testCommandLine_1Extra_FromPropPrefix() throws Exception
+    {
+        // Create home
+        File home = testdir.getFile("home");
+        FS.ensureEmpty(home);
+        TestEnv.copyTestDir("usecases/home",home);
+
+        // Create opt
+        File opt = testdir.getFile("opt");
+        FS.ensureEmpty(opt);
+
+        // Create common
+        File common = new File(opt,"common");
+        FS.ensureEmpty(common);
+        TestEnv.makeFile(common,"start.ini","jetty.port=8080");
+
+        // Create base
+        File base = testdir.getFile("base");
+        FS.ensureEmpty(base);
+        TestEnv.makeFile(base,"start.ini", //
+                "jetty.host=127.0.0.1");
+
+        String dirRef = "${my.opt}" + File.separator + "common";
+
+        // Simple command line reference to include-jetty-dir via property (also on command line)
+        MainResult result = runMain(base,home,
+        // property to 'opt' dir
+                "my.opt=" + opt.getAbsolutePath(),
+                // reference via property prefix
+                "--include-jetty-dir=" + dirRef);
+
+        List<String> expectedSearchOrder = new ArrayList<>();
+        expectedSearchOrder.add("${jetty.base}");
+        expectedSearchOrder.add(dirRef); // should use property
+        expectedSearchOrder.add("${jetty.home}");
+        result.assertSearchOrder(expectedSearchOrder);
+
+        result.assertProperty("jetty.host","127.0.0.1");
+        result.assertProperty("jetty.port","8080"); // from 'common'
+    }
+
+    @Test
+    public void testCommandLine_1Extra_FromCompoundProp() throws Exception
+    {
+        // Create home
+        File home = testdir.getFile("home");
+        FS.ensureEmpty(home);
+        TestEnv.copyTestDir("usecases/home",home);
+
+        // Create opt
+        File opt = testdir.getFile("opt");
+        FS.ensureEmpty(opt);
+
+        // Create common
+        File common = new File(opt,"common");
+        FS.ensureEmpty(common);
+        TestEnv.makeFile(common,"start.ini","jetty.port=8080");
+
+        // Create base
+        File base = testdir.getFile("base");
+        FS.ensureEmpty(base);
+        TestEnv.makeFile(base,"start.ini", //
+                "jetty.host=127.0.0.1");
+
+        String dirRef = "${my.opt}" + File.separator + "${my.dir}";
+
+        // Simple command line reference to include-jetty-dir via property (also on command line)
+        MainResult result = runMain(base,home,
+        // property to 'opt' dir
+                "my.opt=" + opt.getAbsolutePath(),
+                // property to commmon dir name
+                "my.dir=common",
+                // reference via property prefix
+                "--include-jetty-dir=" + dirRef);
+
+        List<String> expectedSearchOrder = new ArrayList<>();
+        expectedSearchOrder.add("${jetty.base}");
+        expectedSearchOrder.add(dirRef); // should use property
+        expectedSearchOrder.add("${jetty.home}");
+        result.assertSearchOrder(expectedSearchOrder);
+
+        result.assertProperty("jetty.host","127.0.0.1");
+        result.assertProperty("jetty.port","8080"); // from 'common'
+    }
+
+    @Test
+    public void testRefCommon() throws Exception
+    {
+        // Create home
+        File home = testdir.getFile("home");
+        FS.ensureEmpty(home);
+        TestEnv.copyTestDir("usecases/home",home);
+
+        // Create common
+        File common = testdir.getFile("common");
+        FS.ensureEmpty(common);
+        TestEnv.makeFile(common,"start.ini","jetty.port=8080");
+
+        // Create base
+        File base = testdir.getFile("base");
+        FS.ensureEmpty(base);
+        TestEnv.makeFile(base,"start.ini", //
+                "jetty.host=127.0.0.1",//
+                "--include-jetty-dir=" + common.getAbsolutePath());
+
+        MainResult result = runMain(base,home);
+
+        List<String> expectedSearchOrder = new ArrayList<>();
+        expectedSearchOrder.add("${jetty.base}");
+        expectedSearchOrder.add(common.getAbsolutePath());
+        expectedSearchOrder.add("${jetty.home}");
+        result.assertSearchOrder(expectedSearchOrder);
+
+        result.assertProperty("jetty.host","127.0.0.1");
+        result.assertProperty("jetty.port","8080"); // from 'common'
+    }
+
+    @Test
+    public void testRefCommonAndCorp() throws Exception
+    {
+        // Create home
+        File home = testdir.getFile("home");
+        FS.ensureEmpty(home);
+        TestEnv.copyTestDir("usecases/home",home);
+
+        // Create common
+        File common = testdir.getFile("common");
+        FS.ensureEmpty(common);
+        TestEnv.makeFile(common,"start.ini","jetty.port=8080");
+
+        // Create corp
+        File corp = testdir.getFile("corp");
+        FS.ensureEmpty(corp);
+
+        // Create base
+        File base = testdir.getFile("base");
+        FS.ensureEmpty(base);
+        TestEnv.makeFile(base,"start.ini", //
+                "jetty.host=127.0.0.1",//
+                "--include-jetty-dir=" + common.getAbsolutePath(), //
+                "--include-jetty-dir=" + corp.getAbsolutePath());
+
+        MainResult result = runMain(base,home);
+
+        List<String> expectedSearchOrder = new ArrayList<>();
+        expectedSearchOrder.add("${jetty.base}");
+        expectedSearchOrder.add(common.getAbsolutePath());
+        expectedSearchOrder.add(corp.getAbsolutePath());
+        expectedSearchOrder.add("${jetty.home}");
+        result.assertSearchOrder(expectedSearchOrder);
+
+        result.assertProperty("jetty.host","127.0.0.1");
+        result.assertProperty("jetty.port","8080"); // from 'common'
+    }
+
+    @Test
+    public void testRefCommonRefCorp() throws Exception
+    {
+        // Create home
+        File home = testdir.getFile("home");
+        FS.ensureEmpty(home);
+        TestEnv.copyTestDir("usecases/home",home);
+
+        // Create corp
+        File corp = testdir.getFile("corp");
+        FS.ensureEmpty(corp);
+        TestEnv.makeFile(corp,"start.ini","jetty.port=9090");
+
+        // Create common
+        File common = testdir.getFile("common");
+        FS.ensureEmpty(common);
+        TestEnv.makeFile(common,"start.ini", //
+                "--include-jetty-dir=" + corp.getAbsolutePath(), //
+                "jetty.port=8080");
+
+        // Create base
+        File base = testdir.getFile("base");
+        FS.ensureEmpty(base);
+        TestEnv.makeFile(base,"start.ini", //
+                "jetty.host=127.0.0.1",//
+                "--include-jetty-dir=" + common.getAbsolutePath());
+
+        MainResult result = runMain(base,home);
+
+        List<String> expectedSearchOrder = new ArrayList<>();
+        expectedSearchOrder.add("${jetty.base}");
+        expectedSearchOrder.add(common.getAbsolutePath());
+        expectedSearchOrder.add(corp.getAbsolutePath());
+        expectedSearchOrder.add("${jetty.home}");
+        result.assertSearchOrder(expectedSearchOrder);
+
+        result.assertProperty("jetty.host","127.0.0.1");
+        result.assertProperty("jetty.port","8080"); // from 'common'
+    }
+
+    @Test
+    public void testRefCommonRefCorp_FromSimpleProps() throws Exception
+    {
+        // Create home
+        File home = testdir.getFile("home");
+        FS.ensureEmpty(home);
+        TestEnv.copyTestDir("usecases/home",home);
+
+        // Create corp
+        File corp = testdir.getFile("corp");
+        FS.ensureEmpty(corp);
+        TestEnv.makeFile(corp,"start.ini", //
+                "jetty.port=9090");
+
+        // Create common
+        File common = testdir.getFile("common");
+        FS.ensureEmpty(common);
+        TestEnv.makeFile(common,"start.ini", //
+                "my.corp=" + corp.getAbsolutePath(), //
+                "--include-jetty-dir=${my.corp}", //
+                "jetty.port=8080");
+
+        // Create base
+        File base = testdir.getFile("base");
+        FS.ensureEmpty(base);
+        TestEnv.makeFile(base,"start.ini", //
+                "jetty.host=127.0.0.1",//
+                "my.common=" + common.getAbsolutePath(), //
+                "--include-jetty-dir=${my.common}");
+
+        MainResult result = runMain(base,home);
+
+        List<String> expectedSearchOrder = new ArrayList<>();
+        expectedSearchOrder.add("${jetty.base}");
+        expectedSearchOrder.add("${my.common}");
+        expectedSearchOrder.add("${my.corp}");
+        expectedSearchOrder.add("${jetty.home}");
+        result.assertSearchOrder(expectedSearchOrder);
+
+        result.assertProperty("jetty.host","127.0.0.1");
+        result.assertProperty("jetty.port","8080"); // from 'common'
+    }
+
+    @Test
+    public void testRefCommonRefCorp_CmdLineRef() throws Exception
+    {
+        // Create home
+        File home = testdir.getFile("home");
+        FS.ensureEmpty(home);
+        TestEnv.copyTestDir("usecases/home",home);
+
+        // Create devops
+        File devops = testdir.getFile("devops");
+        FS.ensureEmpty(devops);
+        TestEnv.makeFile(devops,"start.ini", //
+                "--module=logging", //
+                "jetty.port=2222");
+
+        // Create corp
+        File corp = testdir.getFile("corp");
+        FS.ensureEmpty(corp);
+        TestEnv.makeFile(corp,"start.ini", //
+                "jetty.port=9090");
+
+        // Create common
+        File common = testdir.getFile("common");
+        FS.ensureEmpty(common);
+        TestEnv.makeFile(common,"start.ini", //
+                "--include-jetty-dir=" + corp.getAbsolutePath(), //
+                "jetty.port=8080");
+
+        // Create base
+        File base = testdir.getFile("base");
+        FS.ensureEmpty(base);
+        TestEnv.makeFile(base,"start.ini", //
+                "jetty.host=127.0.0.1",//
+                "--include-jetty-dir=" + common.getAbsolutePath());
+
+        MainResult result = runMain(base,home,
+        // command line provided include-jetty-dir ref
+                "--include-jetty-dir=" + devops.getAbsolutePath());
+
+        List<String> expectedSearchOrder = new ArrayList<>();
+        expectedSearchOrder.add("${jetty.base}");
+        expectedSearchOrder.add(devops.getAbsolutePath());
+        expectedSearchOrder.add(common.getAbsolutePath());
+        expectedSearchOrder.add(corp.getAbsolutePath());
+        expectedSearchOrder.add("${jetty.home}");
+        result.assertSearchOrder(expectedSearchOrder);
+
+        result.assertProperty("jetty.host","127.0.0.1");
+        result.assertProperty("jetty.port","2222"); // from 'devops'
+    }
+
+    @Test
+    public void testRefCommonRefCorp_CmdLineProp() throws Exception
+    {
+        // Create home
+        File home = testdir.getFile("home");
+        FS.ensureEmpty(home);
+        TestEnv.copyTestDir("usecases/home",home);
+
+        // Create corp
+        File corp = testdir.getFile("corp");
+        FS.ensureEmpty(corp);
+        TestEnv.makeFile(corp,"start.ini", //
+                "jetty.port=9090");
+
+        // Create common
+        File common = testdir.getFile("common");
+        FS.ensureEmpty(common);
+        TestEnv.makeFile(common,"start.ini", //
+                "--include-jetty-dir=" + corp.getAbsolutePath(), //
+                "jetty.port=8080");
+
+        // Create base
+        File base = testdir.getFile("base");
+        FS.ensureEmpty(base);
+        TestEnv.makeFile(base,"start.ini", //
+                "jetty.host=127.0.0.1",//
+                "--include-jetty-dir=" + common.getAbsolutePath());
+
+        MainResult result = runMain(base,home,
+        // command line property should override all others
+                "jetty.port=7070");
+
+        List<String> expectedSearchOrder = new ArrayList<>();
+        expectedSearchOrder.add("${jetty.base}");
+        expectedSearchOrder.add(common.getAbsolutePath());
+        expectedSearchOrder.add(corp.getAbsolutePath());
+        expectedSearchOrder.add("${jetty.home}");
+        result.assertSearchOrder(expectedSearchOrder);
+
+        result.assertProperty("jetty.host","127.0.0.1");
+        result.assertProperty("jetty.port","7070"); // from command line
+    }
+
+    @Test
+    public void testBadDoubleRef() throws Exception
+    {
+        // Create home
+        File home = testdir.getFile("home");
+        FS.ensureEmpty(home);
+        TestEnv.copyTestDir("usecases/home",home);
+
+        // Create common
+        File common = testdir.getFile("common");
+        FS.ensureEmpty(common);
+
+        // Create corp
+        File corp = testdir.getFile("corp");
+        FS.ensureEmpty(corp);
+        TestEnv.makeFile(corp,"start.ini",
+        // standard property
+                "jetty.port=9090",
+                // INTENTIONAL BAD Reference (duplicate)
+                "--include-jetty-dir=" + common.getAbsolutePath());
+
+        // Populate common
+        TestEnv.makeFile(common,"start.ini",
+        // standard property
+                "jetty.port=8080",
+                // reference to corp
+                "--include-jetty-dir=" + corp.getAbsolutePath());
+
+        // Create base
+        File base = testdir.getFile("base");
+        FS.ensureEmpty(base);
+        TestEnv.makeFile(base,"start.ini", //
+                "jetty.host=127.0.0.1",//
+                "--include-jetty-dir=" + common.getAbsolutePath());
+
+        try
+        {
+            runMain(base,home);
+            Assert.fail("Should have thrown a UsageException");
+        }
+        catch (UsageException e)
+        {
+            Assert.assertThat("UsageException",e.getMessage(),containsString("Duplicate"));
+        }
+    }
+}
diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/JarVersionTest.java b/jetty-start/src/test/java/org/eclipse/jetty/start/JarVersionTest.java
new file mode 100644
index 0000000..88b1dfb
--- /dev/null
+++ b/jetty-start/src/test/java/org/eclipse/jetty/start/JarVersionTest.java
@@ -0,0 +1,54 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.start;
+
+import static org.hamcrest.Matchers.*;
+
+import java.io.File;
+
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class JarVersionTest
+{
+    private void assertJarVersion(String jarname, String expectedVersion)
+    {
+        File jarfile = MavenTestingUtils.getTestResourceFile(jarname);
+        Assert.assertThat("Jar: " + jarname,JarVersion.getVersion(jarfile),containsString(expectedVersion));
+    }
+
+    @Test
+    public void testNoManifestJar()
+    {
+        assertJarVersion("bad-libs/no-manifest.jar","(none specified)");
+    }
+
+    @Test
+    public void testNotAJar()
+    {
+        assertJarVersion("bad-libs/not-a.jar","(error: ZipException ");
+    }
+
+    @Test
+    public void testZeroLengthJar()
+    {
+        assertJarVersion("bad-libs/zero-length.jar","(error: ZipException ");
+    }
+}
diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/LicenseTest.java b/jetty-start/src/test/java/org/eclipse/jetty/start/LicenseTest.java
new file mode 100644
index 0000000..5422aa1
--- /dev/null
+++ b/jetty-start/src/test/java/org/eclipse/jetty/start/LicenseTest.java
@@ -0,0 +1,181 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.start;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jetty.toolchain.test.IO;
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.eclipse.jetty.toolchain.test.OS;
+import org.eclipse.jetty.toolchain.test.TestingDir;
+import org.junit.Rule;
+import org.junit.Test;
+
+/**
+ * Test various license handling.
+ */
+public class LicenseTest
+{
+    @Rule
+    public TestingDir testdir = new TestingDir();
+
+    @Rule
+    public SystemExitAsException exitrule = new SystemExitAsException();
+
+    private String assertFileExists(File basePath, String name) throws IOException
+    {
+        File file = new File(basePath, OS.separators(name));
+        FS.exists(file.toPath());
+        return IO.readToString(file);
+    }
+
+    private void execMain(List<String> cmds) throws Exception
+    {
+        int len = cmds.size();
+        String args[] = cmds.toArray(new String[len]);
+
+        System.err.printf("%n## Exec: %s%n", Main.join(cmds,", "));
+        Main main = new Main();
+        StartArgs startArgs = main.processCommandLine(args);
+        main.start(startArgs);
+    }
+
+    public List<String> getBaseCommandLine(File basePath)
+    {
+        List<String> cmds = new ArrayList<String>();
+        cmds.add("-Djava.io.tmpdir=" + MavenTestingUtils.getTargetDir().getAbsolutePath());
+        cmds.add("-Djetty.home=" + MavenTestingUtils.getTestResourceDir("dist-home").getAbsolutePath());
+        cmds.add("-Djetty.base=" + basePath.getAbsolutePath());
+        cmds.add("--testing-mode");
+
+        return cmds;
+    }
+
+    @Test
+    public void testAdd_NoLicensed() throws Exception
+    {
+        File basePath = testdir.getEmptyDir();
+
+        List<String> cmds = getBaseCommandLine(basePath);
+
+        cmds.add("--add-to-start=http,deploy");
+
+        execMain(cmds);
+    }
+
+    @Test
+    public void testAdd_CDI_Licensed() throws Exception
+    {
+        File basePath = testdir.getEmptyDir();
+
+        List<String> cmds = getBaseCommandLine(basePath);
+
+        cmds.add("-Dorg.eclipse.jetty.start.ack.license.cdi=true");
+        cmds.add("--add-to-start=cdi");
+
+        execMain(cmds);
+    }
+    
+    @Test
+    public void testAdd_SPDY_Licensed() throws Exception
+    {
+        File basePath = testdir.getEmptyDir();
+
+        List<String> cmds = getBaseCommandLine(basePath);
+
+        cmds.add("-Dorg.eclipse.jetty.start.ack.license.protonego-impl=true");
+        cmds.add("--add-to-start=spdy");
+
+        execMain(cmds);
+        
+        String contents = assertFileExists(basePath, "start.ini");
+        assertThat("Contents",contents,containsString("--module=spdy"+System.lineSeparator()));
+    }
+    
+    @Test
+    public void testAdd_HttpSpdy_Then_Deploy() throws Exception
+    {
+        File basePath = testdir.getEmptyDir();
+
+        List<String> cmds = getBaseCommandLine(basePath);
+
+        cmds.add("-Dorg.eclipse.jetty.start.ack.license.protonego-impl=true");
+        cmds.add("--add-to-start=http,spdy");
+
+        execMain(cmds);
+        
+        String contents = assertFileExists(basePath, "start.ini");
+        assertThat("Contents",contents,containsString("--module=http"+System.lineSeparator()));
+        assertThat("Contents",contents,containsString("--module=spdy"+System.lineSeparator()));
+        
+        // now request deploy (no license check should occur)
+        List<String> cmds2 = getBaseCommandLine(basePath);
+        cmds2.add("--add-to-start=deploy");
+        execMain(cmds2);
+
+        contents = assertFileExists(basePath, "start.ini");
+        assertThat("Contents",contents,containsString("--module=deploy"+System.lineSeparator()));
+        assertThat("Contents",contents,containsString("--module=spdy"+System.lineSeparator()));
+    }
+    
+    @Test
+    public void testCreate_SPDY_Licensed() throws Exception
+    {
+        File basePath = testdir.getEmptyDir();
+
+        List<String> cmds = getBaseCommandLine(basePath);
+
+        cmds.add("-Dorg.eclipse.jetty.start.ack.license.protonego-impl=true");
+        
+        StringReader startIni = new StringReader("--module=spdy\n");
+        try (FileWriter writer = new FileWriter(new File(basePath,"start.ini")))
+        {
+            IO.copy(startIni,writer);
+        }
+
+        execMain(cmds);
+    }
+
+    @Test
+    public void testCreate_CDI_Licensed() throws Exception
+    {
+        File basePath = testdir.getEmptyDir();
+
+        List<String> cmds = getBaseCommandLine(basePath);
+
+        cmds.add("-Dorg.eclipse.jetty.start.ack.license.cdi=true");
+        cmds.add("--create-files");
+
+        StringReader startIni = new StringReader("--module=cdi\n");
+        try (FileWriter writer = new FileWriter(new File(basePath,"start.ini")))
+        {
+            IO.copy(startIni,writer);
+        }
+
+        execMain(cmds);
+    }
+}
diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/MainTest.java b/jetty-start/src/test/java/org/eclipse/jetty/start/MainTest.java
index f12ae90..8de153f 100644
--- a/jetty-start/src/test/java/org/eclipse/jetty/start/MainTest.java
+++ b/jetty-start/src/test/java/org/eclipse/jetty/start/MainTest.java
@@ -18,123 +18,184 @@
 
 package org.eclipse.jetty.start;
 
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
 import java.io.File;
-import java.io.IOException;
-import java.lang.reflect.Field;
+import java.nio.file.Files;
+import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Vector;
 
 import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.eclipse.jetty.toolchain.test.TestTracker;
 import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Rule;
 import org.junit.Test;
 
-import static org.hamcrest.Matchers.containsString;
-import static org.hamcrest.Matchers.equalTo;
-import static org.hamcrest.Matchers.hasItems;
-import static org.hamcrest.Matchers.notNullValue;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertThat;
-
-/* ------------------------------------------------------------ */
-/**
- */
 public class MainTest
 {
-    /* ------------------------------------------------------------ */
-    /**
-     * @throws java.lang.Exception
-     */
+    @Rule
+    public TestTracker ttracker = new TestTracker();
+    
     @Before
-    public void setUp() throws Exception
+    public void clearSystemProperties()
     {
-        File testJettyHome = MavenTestingUtils.getTestResourceDir("jetty.home");
-        System.setProperty("jetty.home",testJettyHome.getAbsolutePath());
+        System.setProperty("jetty.home","");
+        System.setProperty("jetty.base","");
     }
 
     @Test
-    public void testLoadStartIni() throws IOException
+    public void testBasicProcessing() throws Exception
     {
+        List<String> cmdLineArgs = new ArrayList<>();
+        File testJettyHome = MavenTestingUtils.getTestResourceDir("usecases/home").getAbsoluteFile();
+        cmdLineArgs.add("user.dir=" + testJettyHome);
+        cmdLineArgs.add("jetty.home=" + testJettyHome);
+        cmdLineArgs.add("jetty.port=9090");
+
         Main main = new Main();
-        List<String> args = main.parseStartIniFiles();
-        assertEquals("Expected 5 uncommented lines in start.ini",9,args.size());
-        assertEquals("First uncommented line in start.ini doesn't match expected result","OPTIONS=Server,jsp,resources,websocket,ext",args.get(0));
-        assertEquals("Last uncommented line in start.ini doesn't match expected result","etc/jetty-testrealm.xml",args.get(8));
+        StartArgs args = main.processCommandLine(cmdLineArgs.toArray(new String[cmdLineArgs.size()]));
+        BaseHome baseHome = main.getBaseHome();
+        System.err.println(args);
+
+        ConfigurationAssert.assertConfiguration(baseHome,args,"assert-home.txt");
     }
 
     @Test
-    public void testExpandCommandLine() throws Exception
+    public void testStopProcessing() throws Exception
     {
+        List<String> cmdLineArgs = new ArrayList<>();
+        cmdLineArgs.add("--stop");
+        cmdLineArgs.add("STOP.PORT=10000");
+        cmdLineArgs.add("STOP.KEY=foo");
+        cmdLineArgs.add("STOP.WAIT=300");
+
         Main main = new Main();
-        List<String> args = main.expandCommandLine(new String[] {});
-        assertEquals("start.ini OPTIONS","OPTIONS=Server,jsp,resources,websocket,ext",args.get(0));
-        assertEquals("start.d/jmx OPTIONS","OPTIONS=jmx",args.get(5));
-        assertEquals("start.d/jmx XML","--pre=etc/jetty-jmx.xml",args.get(6));
-        assertEquals("start.d/websocket OPTIONS","OPTIONS=websocket",args.get(7));
+        StartArgs args = main.processCommandLine(cmdLineArgs.toArray(new String[cmdLineArgs.size()]));
+        System.err.println(args);
+
+        // Assert.assertEquals("--stop should not build module tree", 0, args.getEnabledModules().size());
+        assertEquals("--stop missing port","10000",args.getProperties().getString("STOP.PORT"));
+        assertEquals("--stop missing key","foo",args.getProperties().getString("STOP.KEY"));
+        assertEquals("--stop missing wait","300",args.getProperties().getString("STOP.WAIT"));
     }
 
     @Test
-    public void testProcessCommandLine() throws Exception
+    @Ignore("Just a bit noisy for general testing")
+    public void testListConfig() throws Exception
     {
+        List<String> cmdLineArgs = new ArrayList<>();
+        File testJettyHome = MavenTestingUtils.getTestResourceDir("usecases/home");
+        cmdLineArgs.add("jetty.home=" + testJettyHome);
+        cmdLineArgs.add("jetty.port=9090");
+        cmdLineArgs.add("--list-config");
+        // cmdLineArgs.add("--debug");
+
         Main main = new Main();
-        List<String> args = main.expandCommandLine(new String[] {});
-        List<String> xmls = main.processCommandLine(args);
+        StartArgs args = main.processCommandLine(cmdLineArgs.toArray(new String[cmdLineArgs.size()]));
+        main.listConfig(args);
+    }
 
-        assertEquals("jmx --pre","etc/jetty-jmx.xml",xmls.get(0));
-        assertEquals("start.ini","etc/jetty.xml",xmls.get(1));
-        assertEquals("start.d","etc/jetty-testrealm.xml",xmls.get(5));
+    @Test
+    @Ignore("Just a bit noisy for general testing")
+    public void testHelp() throws Exception
+    {
+        Main main = new Main();
+        main.usage(false);
     }
 
     @Test
-    public void testBuildCommandLine() throws IOException, NoSuchFieldException, IllegalAccessException
+    public void testWithCommandLine() throws Exception
+    {
+        List<String> cmdLineArgs = new ArrayList<>();
+
+        File homePath = MavenTestingUtils.getTestResourceDir("usecases/home").getAbsoluteFile();
+        cmdLineArgs.add("jetty.home=" + homePath);
+        cmdLineArgs.add("user.dir=" + homePath);
+
+        // JVM args
+        cmdLineArgs.add("--exec");
+        cmdLineArgs.add("-Xms1024m");
+        cmdLineArgs.add("-Xmx1024m");
+
+        // Arbitrary Libs
+        Path extraJar = MavenTestingUtils.getTestResourceFile("extra-libs/example.jar").toPath().normalize();
+        Path extraDir = MavenTestingUtils.getTestResourceDir("extra-resources").toPath().normalize();
+        
+        extraJar = extraJar.toAbsolutePath();
+        extraDir = extraDir.toAbsolutePath();
+        
+        assertThat("Extra Jar exists: " + extraJar,Files.exists(extraJar),is(true));
+        assertThat("Extra Dir exists: " + extraDir,Files.exists(extraDir),is(true));
+        
+        StringBuilder lib = new StringBuilder();
+        lib.append("--lib=");
+        lib.append(extraJar.toString());
+        lib.append(File.pathSeparator);
+        lib.append(extraDir.toString());
+        
+        cmdLineArgs.add(lib.toString());
+
+        // Arbitrary XMLs
+        cmdLineArgs.add("jetty.xml");
+        cmdLineArgs.add("jetty-jmx.xml");
+        cmdLineArgs.add("jetty-logging.xml");
+
+        Main main = new Main();
+
+        StartArgs args = main.processCommandLine(cmdLineArgs.toArray(new String[cmdLineArgs.size()]));
+        BaseHome baseHome = main.getBaseHome();
+
+        assertThat("jetty.home",baseHome.getHome(),is(homePath.getAbsolutePath()));
+        assertThat("jetty.base",baseHome.getBase(),is(homePath.getAbsolutePath()));
+
+        ConfigurationAssert.assertConfiguration(baseHome,args,"assert-home-with-jvm.txt");
+    }
+    
+    @Test
+    public void testWithSpdy() throws Exception
     {
-        List<String> jvmArgs = new ArrayList<String>();
-        jvmArgs.add("--exec");
-        jvmArgs.add("-Xms1024m");
-        jvmArgs.add("-Xmx1024m");
+        List<String> cmdLineArgs = new ArrayList<>();
 
-        List<String> xmls = new ArrayList<String>();
-        xmls.add("jetty.xml");
-        xmls.add("jetty-jmx.xml");
-        xmls.add("jetty-logging.xml");
+        File homePath = MavenTestingUtils.getTestResourceDir("usecases/home").getAbsoluteFile();
+        cmdLineArgs.add("jetty.home=" + homePath);
+        cmdLineArgs.add("user.dir=" + homePath);
+        cmdLineArgs.add("java.version=1.7.0_60");
+
+        // Modules
+        cmdLineArgs.add("--module=server");
+        cmdLineArgs.add("--module=deploy");
+        cmdLineArgs.add("--module=spdy");
 
         Main main = new Main();
-        main.addJvmArgs(jvmArgs);
-
-        Classpath classpath = nastyWayToCreateAClasspathObject("/jetty/home with spaces/");
-        CommandLineBuilder cmd = main.buildCommandLine(classpath,xmls);
-        assertThat("CommandLineBuilder shouldn't be null",cmd,notNullValue());
-
-        List<String> commandArgs = cmd.getArgs();
-        assertThat("commandArgs should contain 11 elements",commandArgs.size(),equalTo(11));
-        assertThat("args does not contain -cp",commandArgs,hasItems("-cp"));
-        assertThat("Classpath should be correctly quoted and match expected value",commandArgs,
-                hasItems("/jetty/home with spaces/somejar.jar:/jetty/home with spaces/someotherjar.jar"));
-        assertThat("args does not contain --exec",commandArgs,hasItems("--exec"));
-        assertThat("CommandLine should contain jvmArgs",commandArgs,hasItems("-Xms1024m"));
-        assertThat("CommandLine should contain jvmArgs", commandArgs, hasItems("-Xmx1024m"));
-        assertThat("CommandLine should contain xmls",commandArgs,hasItems("jetty.xml"));
-        assertThat("CommandLine should contain xmls",commandArgs,hasItems("jetty-jmx.xml"));
-        assertThat("CommandLine should contain xmls", commandArgs, hasItems("jetty-logging.xml"));
-
-        String commandLine = cmd.toString();
-        assertThat("cmd.toString() should be properly escaped",commandLine,containsString("-cp /jetty/home\\ with\\ " +
-                "spaces/somejar.jar:/jetty/home\\ with\\ spaces/someotherjar.jar"));
-        assertThat("cmd.toString() doesn't contain xml config files",commandLine,containsString(" jetty.xml jetty-jmx.xml jetty-logging.xml"));
+
+        StartArgs args = main.processCommandLine(cmdLineArgs.toArray(new String[cmdLineArgs.size()]));
+        BaseHome baseHome = main.getBaseHome();
+
+        assertThat("jetty.home",baseHome.getHome(),is(homePath.getAbsolutePath()));
+        assertThat("jetty.base",baseHome.getBase(),is(homePath.getAbsolutePath()));
+
+        ConfigurationAssert.assertConfiguration(baseHome,args,"assert-home-with-spdy.txt");
     }
 
-    private Classpath nastyWayToCreateAClasspathObject(String jettyHome) throws NoSuchFieldException, IllegalAccessException
+    @Test
+    public void testJettyHomeWithSpaces() throws Exception
     {
-        Classpath classpath = new Classpath();
-        Field classpathElements = Classpath.class.getDeclaredField("_elements");
-        classpathElements.setAccessible(true);
-        File file = new File(jettyHome + "somejar.jar");
-        File file2 = new File(jettyHome + "someotherjar.jar");
-        Vector<File> elements = new Vector<File>();
-        elements.add(file);
-        elements.add(file2);
-        classpathElements.set(classpath,elements);
-        return classpath;
-    }
+        List<String> cmdLineArgs = new ArrayList<>();
 
+        File homePath = MavenTestingUtils.getTestResourceDir("jetty home with spaces").getAbsoluteFile();
+        cmdLineArgs.add("user.dir=" + homePath);
+        cmdLineArgs.add("jetty.home=" + homePath);
+
+        Main main = new Main();
+        StartArgs args = main.processCommandLine(cmdLineArgs.toArray(new String[cmdLineArgs.size()]));
+        BaseHome baseHome = main.getBaseHome();
+
+        assertThat("jetty.home",baseHome.getHome(),is(homePath.getAbsolutePath()));
+        assertThat("jetty.base",baseHome.getBase(),is(homePath.getAbsolutePath()));
+
+        ConfigurationAssert.assertConfiguration(baseHome,args,"assert-home-with-spaces.txt");
+    }
 }
diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/ModuleGraphWriterTest.java b/jetty-start/src/test/java/org/eclipse/jetty/start/ModuleGraphWriterTest.java
new file mode 100644
index 0000000..4e82d0d
--- /dev/null
+++ b/jetty-start/src/test/java/org/eclipse/jetty/start/ModuleGraphWriterTest.java
@@ -0,0 +1,74 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.start;
+
+import static org.hamcrest.Matchers.*;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Path;
+
+import org.eclipse.jetty.start.config.CommandLineConfigSource;
+import org.eclipse.jetty.start.config.ConfigSources;
+import org.eclipse.jetty.start.config.JettyBaseConfigSource;
+import org.eclipse.jetty.start.config.JettyHomeConfigSource;
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.eclipse.jetty.toolchain.test.TestingDir;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class ModuleGraphWriterTest
+{
+    @Rule
+    public TestingDir testdir = new TestingDir();
+
+    @Test
+    public void testGenerate_NothingEnabled() throws IOException
+    {
+        // Test Env
+        File homeDir = MavenTestingUtils.getTestResourceDir("usecases/home");
+        File baseDir = testdir.getEmptyDir();
+        String cmdLine[] = new String[] {"jetty.version=TEST"};
+        
+        // Configuration
+        CommandLineConfigSource cmdLineSource = new CommandLineConfigSource(cmdLine);
+        ConfigSources config = new ConfigSources();
+        config.add(cmdLineSource);
+        config.add(new JettyHomeConfigSource(homeDir.toPath()));
+        config.add(new JettyBaseConfigSource(baseDir.toPath()));
+        
+        // Initialize
+        BaseHome basehome = new BaseHome(config);
+        
+        StartArgs args = new StartArgs();
+        args.parse(config);
+
+        Modules modules = new Modules(basehome, args);
+        modules.registerAll();
+        modules.buildGraph();
+
+        Path outputFile = basehome.getBasePath("graph.dot");
+
+        ModuleGraphWriter writer = new ModuleGraphWriter();
+        writer.write(modules,outputFile);
+
+        Assert.assertThat("Output File Exists",FS.exists(outputFile),is(true));
+    }
+}
diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/ModuleTest.java b/jetty-start/src/test/java/org/eclipse/jetty/start/ModuleTest.java
new file mode 100644
index 0000000..65dc94b
--- /dev/null
+++ b/jetty-start/src/test/java/org/eclipse/jetty/start/ModuleTest.java
@@ -0,0 +1,70 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.start;
+
+import static org.hamcrest.Matchers.*;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.eclipse.jetty.start.config.CommandLineConfigSource;
+import org.eclipse.jetty.start.config.ConfigSources;
+import org.eclipse.jetty.start.config.JettyBaseConfigSource;
+import org.eclipse.jetty.start.config.JettyHomeConfigSource;
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.eclipse.jetty.toolchain.test.TestingDir;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class ModuleTest
+{
+    @Rule
+    public TestingDir testdir = new TestingDir();
+    
+    @Test
+    public void testLoadWebSocket() throws IOException
+    {
+        // Test Env
+        File homeDir = MavenTestingUtils.getTestResourceDir("usecases/home");
+        File baseDir = testdir.getEmptyDir();
+        String cmdLine[] = new String[] {"jetty.version=TEST"};
+        
+        // Configuration
+        CommandLineConfigSource cmdLineSource = new CommandLineConfigSource(cmdLine);
+        ConfigSources config = new ConfigSources();
+        config.add(cmdLineSource);
+        config.add(new JettyHomeConfigSource(homeDir.toPath()));
+        config.add(new JettyBaseConfigSource(baseDir.toPath()));
+        
+        // Initialize
+        BaseHome basehome = new BaseHome(config);
+        
+        File file = MavenTestingUtils.getTestResourceFile("usecases/home/modules/websocket.mod");
+        Module module = new Module(basehome,file.toPath());
+        
+        Assert.assertThat("Module Name",module.getName(),is("websocket"));
+        Assert.assertThat("Module Parents Size",module.getParentNames().size(),is(2));
+        Assert.assertThat("Module Parents",module.getParentNames(),containsInAnyOrder("annotations","server"));
+        Assert.assertThat("Module Xmls Size",module.getXmls().size(),is(1));
+        Assert.assertThat("Module Xmls",module.getXmls(),contains("etc/jetty-websockets.xml"));
+        Assert.assertThat("Module Options Size",module.getLibs().size(),is(1));
+        Assert.assertThat("Module Options",module.getLibs(),contains("lib/websocket/*.jar"));
+    }
+}
diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/ModulesTest.java b/jetty-start/src/test/java/org/eclipse/jetty/start/ModulesTest.java
new file mode 100644
index 0000000..5256f07
--- /dev/null
+++ b/jetty-start/src/test/java/org/eclipse/jetty/start/ModulesTest.java
@@ -0,0 +1,334 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.start;
+
+import static org.hamcrest.Matchers.contains;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.eclipse.jetty.start.config.CommandLineConfigSource;
+import org.eclipse.jetty.start.config.ConfigSources;
+import org.eclipse.jetty.start.config.JettyBaseConfigSource;
+import org.eclipse.jetty.start.config.JettyHomeConfigSource;
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.eclipse.jetty.toolchain.test.TestingDir;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class ModulesTest
+{
+    private final static List<String> TEST_SOURCE = Collections.singletonList("<test>");
+
+    @Rule
+    public TestingDir testdir = new TestingDir();
+
+    @Test
+    public void testLoadAllModules() throws IOException
+    {
+        // Test Env
+        File homeDir = MavenTestingUtils.getTestResourceDir("usecases/home");
+        File baseDir = testdir.getEmptyDir();
+        String cmdLine[] = new String[] {"jetty.version=TEST"};
+        
+        // Configuration
+        CommandLineConfigSource cmdLineSource = new CommandLineConfigSource(cmdLine);
+        ConfigSources config = new ConfigSources();
+        config.add(cmdLineSource);
+        config.add(new JettyHomeConfigSource(homeDir.toPath()));
+        config.add(new JettyBaseConfigSource(baseDir.toPath()));
+        
+        // Initialize
+        BaseHome basehome = new BaseHome(config);
+        
+        StartArgs args = new StartArgs();
+        args.parse(config);
+
+        // Test Modules
+        Modules modules = new Modules(basehome,args);
+        modules.registerAll();
+
+        List<String> moduleNames = new ArrayList<>();
+        for (Module mod : modules)
+        {
+            // skip npn-boot in this test (as its behavior is jdk specific)
+            if (mod.getName().equals("npn-boot"))
+            {
+                continue;
+            }
+            moduleNames.add(mod.getName());
+        }
+
+        List<String> expected = new ArrayList<>();
+        expected.add("jmx");
+        expected.add("client");
+        expected.add("stats");
+        expected.add("spdy");
+        expected.add("deploy");
+        expected.add("debug");
+        expected.add("security");
+        expected.add("ext");
+        expected.add("websocket");
+        expected.add("rewrite");
+        expected.add("ipaccess");
+        expected.add("xinetd");
+        expected.add("proxy");
+        expected.add("webapp");
+        expected.add("jndi");
+        expected.add("lowresources");
+        expected.add("https");
+        expected.add("plus");
+        expected.add("requestlog");
+        expected.add("jsp");
+        // (only present if enabled) expected.add("jsp-impl");
+        expected.add("monitor");
+        expected.add("xml");
+        expected.add("ssl");
+        expected.add("protonego");
+        expected.add("servlet");
+        expected.add("jaas");
+        expected.add("http");
+        expected.add("base");
+        expected.add("server");
+        expected.add("annotations");
+        expected.add("resources");
+        expected.add("logging"); 
+        
+        ConfigurationAssert.assertContainsUnordered("All Modules",expected,moduleNames);
+    }
+
+    @Test
+    public void testEnableRegexSimple() throws IOException
+    {
+        // Test Env
+        File homeDir = MavenTestingUtils.getTestResourceDir("usecases/home");
+        File baseDir = testdir.getEmptyDir();
+        String cmdLine[] = new String[] {"jetty.version=TEST", "java.version=1.7.0_60"};
+        
+        // Configuration
+        CommandLineConfigSource cmdLineSource = new CommandLineConfigSource(cmdLine);
+        ConfigSources config = new ConfigSources();
+        config.add(cmdLineSource);
+        config.add(new JettyHomeConfigSource(homeDir.toPath()));
+        config.add(new JettyBaseConfigSource(baseDir.toPath()));
+        
+        // Initialize
+        BaseHome basehome = new BaseHome(config);
+        
+        StartArgs args = new StartArgs();
+        args.parse(config);
+
+        // Test Modules
+        Modules modules = new Modules(basehome,args);
+        modules.registerAll();
+        modules.enable("[sj]{1}.*",TEST_SOURCE);
+        modules.buildGraph();
+
+        List<String> expected = new ArrayList<>();
+        expected.add("jmx");
+        expected.add("stats");
+        expected.add("spdy");
+        expected.add("security");
+        expected.add("jndi");
+        expected.add("jsp");
+        expected.add("servlet");
+        expected.add("jaas");
+        expected.add("server");
+        // transitive
+        expected.add("base");
+        expected.add("ssl");
+        expected.add("protonego");
+        expected.add("protonego-boot");
+        expected.add("protonego-impl");
+        expected.add("xml");
+        expected.add("jsp-impl");
+        
+        List<String> resolved = new ArrayList<>();
+        for (Module module : modules.resolveEnabled())
+        {
+            resolved.add(module.getName());
+        }
+
+        ConfigurationAssert.assertContainsUnordered("Enabled Modules",expected,resolved);
+    }
+
+    @Test
+    public void testResolve_ServerHttp() throws IOException
+    {
+        // Test Env
+        File homeDir = MavenTestingUtils.getTestResourceDir("usecases/home");
+        File baseDir = testdir.getEmptyDir();
+        String cmdLine[] = new String[] {"jetty.version=TEST"};
+        
+        // Configuration
+        CommandLineConfigSource cmdLineSource = new CommandLineConfigSource(cmdLine);
+        ConfigSources config = new ConfigSources();
+        config.add(cmdLineSource);
+        config.add(new JettyHomeConfigSource(homeDir.toPath()));
+        config.add(new JettyBaseConfigSource(baseDir.toPath()));
+        
+        // Initialize
+        BaseHome basehome = new BaseHome(config);
+        
+        StartArgs args = new StartArgs();
+        args.parse(config);
+
+        // Test Modules
+        Modules modules = new Modules(basehome, args);
+        modules.registerAll();
+
+        // Enable 2 modules
+        modules.enable("server",TEST_SOURCE);
+        modules.enable("http",TEST_SOURCE);
+
+        modules.buildGraph();
+
+        // Collect active module list
+        List<Module> active = modules.resolveEnabled();
+
+        // Assert names are correct, and in the right order
+        List<String> expectedNames = new ArrayList<>();
+        expectedNames.add("base");
+        expectedNames.add("xml");
+        expectedNames.add("server");
+        expectedNames.add("http");
+
+        List<String> actualNames = new ArrayList<>();
+        for (Module actual : active)
+        {
+            actualNames.add(actual.getName());
+        }
+
+        Assert.assertThat("Resolved Names: " + actualNames,actualNames,contains(expectedNames.toArray()));
+
+        // Assert Library List
+        List<String> expectedLibs = new ArrayList<>();
+        expectedLibs.add("lib/jetty-util-${jetty.version}.jar");
+        expectedLibs.add("lib/jetty-io-${jetty.version}.jar");
+        expectedLibs.add("lib/jetty-xml-${jetty.version}.jar");
+        expectedLibs.add("lib/servlet-api-3.1.jar");
+        expectedLibs.add("lib/jetty-schemas-3.1.jar");
+        expectedLibs.add("lib/jetty-http-${jetty.version}.jar");
+        expectedLibs.add("lib/jetty-continuation-${jetty.version}.jar");
+        expectedLibs.add("lib/jetty-server-${jetty.version}.jar");
+
+        List<String> actualLibs = modules.normalizeLibs(active);
+        Assert.assertThat("Resolved Libs: " + actualLibs,actualLibs,contains(expectedLibs.toArray()));
+
+        // Assert XML List
+        List<String> expectedXmls = new ArrayList<>();
+        expectedXmls.add("etc/jetty.xml");
+        expectedXmls.add("etc/jetty-http.xml");
+
+        List<String> actualXmls = modules.normalizeXmls(active);
+        Assert.assertThat("Resolved XMLs: " + actualXmls,actualXmls,contains(expectedXmls.toArray()));
+    }
+
+    @Test
+    public void testResolve_WebSocket() throws IOException
+    {
+        // Test Env
+        File homeDir = MavenTestingUtils.getTestResourceDir("usecases/home");
+        File baseDir = testdir.getEmptyDir();
+        String cmdLine[] = new String[] {"jetty.version=TEST"};
+        
+        // Configuration
+        CommandLineConfigSource cmdLineSource = new CommandLineConfigSource(cmdLine);
+        ConfigSources config = new ConfigSources();
+        config.add(cmdLineSource);
+        config.add(new JettyHomeConfigSource(homeDir.toPath()));
+        config.add(new JettyBaseConfigSource(baseDir.toPath()));
+        
+        // Initialize
+        BaseHome basehome = new BaseHome(config);
+        
+        StartArgs args = new StartArgs();
+        args.parse(config);
+
+        // Test Modules
+        Modules modules = new Modules(basehome,args);
+        modules.registerAll();
+
+        // Enable 2 modules
+        modules.enable("websocket",TEST_SOURCE);
+        modules.enable("http",TEST_SOURCE);
+
+        modules.buildGraph();
+        // modules.dump();
+
+        // Collect active module list
+        List<Module> active = modules.resolveEnabled();
+
+        // Assert names are correct, and in the right order
+        List<String> expectedNames = new ArrayList<>();
+        expectedNames.add("base");
+        expectedNames.add("xml");
+        expectedNames.add("server");
+        expectedNames.add("http");
+        expectedNames.add("jndi");
+        expectedNames.add("security");
+        expectedNames.add("plus");
+        expectedNames.add("annotations");
+        expectedNames.add("websocket");
+
+        List<String> actualNames = new ArrayList<>();
+        for (Module actual : active)
+        {
+            actualNames.add(actual.getName());
+        }
+
+        Assert.assertThat("Resolved Names: " + actualNames,actualNames,contains(expectedNames.toArray()));
+
+        // Assert Library List
+        List<String> expectedLibs = new ArrayList<>();
+        expectedLibs.add("lib/jetty-util-${jetty.version}.jar");
+        expectedLibs.add("lib/jetty-io-${jetty.version}.jar");
+        expectedLibs.add("lib/jetty-xml-${jetty.version}.jar");
+        expectedLibs.add("lib/servlet-api-3.1.jar");
+        expectedLibs.add("lib/jetty-schemas-3.1.jar");
+        expectedLibs.add("lib/jetty-http-${jetty.version}.jar");
+        expectedLibs.add("lib/jetty-continuation-${jetty.version}.jar");
+        expectedLibs.add("lib/jetty-server-${jetty.version}.jar");
+        expectedLibs.add("lib/jetty-jndi-${jetty.version}.jar");
+        expectedLibs.add("lib/jndi/*.jar");
+        expectedLibs.add("lib/jetty-security-${jetty.version}.jar");
+        expectedLibs.add("lib/jetty-plus-${jetty.version}.jar");
+        expectedLibs.add("lib/jetty-annotations-${jetty.version}.jar");
+        expectedLibs.add("lib/annotations/*.jar");
+        expectedLibs.add("lib/websocket/*.jar");
+
+        List<String> actualLibs = modules.normalizeLibs(active);
+        Assert.assertThat("Resolved Libs: " + actualLibs,actualLibs,contains(expectedLibs.toArray()));
+
+        // Assert XML List
+        List<String> expectedXmls = new ArrayList<>();
+        expectedXmls.add("etc/jetty.xml");
+        expectedXmls.add("etc/jetty-http.xml");
+        expectedXmls.add("etc/jetty-plus.xml");
+        expectedXmls.add("etc/jetty-annotations.xml");
+        expectedXmls.add("etc/jetty-websockets.xml");
+
+        List<String> actualXmls = modules.normalizeXmls(active);
+        Assert.assertThat("Resolved XMLs: " + actualXmls,actualXmls,contains(expectedXmls.toArray()));
+    }
+}
diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/PathFinderTest.java b/jetty-start/src/test/java/org/eclipse/jetty/start/PathFinderTest.java
new file mode 100644
index 0000000..7815c72
--- /dev/null
+++ b/jetty-start/src/test/java/org/eclipse/jetty/start/PathFinderTest.java
@@ -0,0 +1,88 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.start;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.FileVisitOption;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.EnumSet;
+import java.util.List;
+
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.junit.Test;
+
+public class PathFinderTest
+{
+    @Test
+    public void testFindInis() throws IOException
+    {
+        File homeDir = MavenTestingUtils.getTestResourceDir("hb.1/home");
+        Path homePath = homeDir.toPath().toAbsolutePath();
+
+        PathFinder finder = new PathFinder();
+        finder.setFileMatcher("glob:**/*.ini");
+        finder.setBase(homePath);
+
+        Files.walkFileTree(homePath,EnumSet.of(FileVisitOption.FOLLOW_LINKS),30,finder);
+
+        List<String> expected = new ArrayList<>();
+        expected.add("${jetty.home}/start.d/jmx.ini");
+        expected.add("${jetty.home}/start.d/jndi.ini");
+        expected.add("${jetty.home}/start.d/jsp.ini");
+        expected.add("${jetty.home}/start.d/logging.ini");
+        expected.add("${jetty.home}/start.d/ssl.ini");
+        expected.add("${jetty.home}/start.ini");
+        FSTest.toOsSeparators(expected);
+
+        BaseHome hb = new BaseHome(new String[] { "jetty.home=" + homePath.toString() });
+        BaseHomeTest.assertPathList(hb,"Files found",expected,finder);
+    }
+
+    @Test
+    public void testFindMods() throws IOException
+    {
+        File homeDir = MavenTestingUtils.getTestResourceDir("usecases/home");
+        Path homePath = homeDir.toPath().toAbsolutePath();
+
+        List<String> expected = new ArrayList<>();
+        File modulesDir = new File(homeDir,"modules");
+        for (File file : modulesDir.listFiles())
+        {
+            if (file.getName().endsWith(".mod"))
+            {
+                expected.add("${jetty.home}/modules/" + file.getName());
+            }
+        }
+        FSTest.toOsSeparators(expected);
+        
+        Path modulesPath = modulesDir.toPath();
+
+        PathFinder finder = new PathFinder();
+        finder.setFileMatcher(PathMatchers.getMatcher("modules/*.mod"));
+        finder.setBase(modulesPath);
+        
+        Files.walkFileTree(modulesPath,EnumSet.of(FileVisitOption.FOLLOW_LINKS),1,finder);
+
+        BaseHome hb = new BaseHome(new String[] { "jetty.home=" + homePath.toString() });
+        BaseHomeTest.assertPathList(hb,"Files found",expected,finder);
+    }
+}
diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/PathMatchersAbsoluteTest.java b/jetty-start/src/test/java/org/eclipse/jetty/start/PathMatchersAbsoluteTest.java
new file mode 100755
index 0000000..50eba48
--- /dev/null
+++ b/jetty-start/src/test/java/org/eclipse/jetty/start/PathMatchersAbsoluteTest.java
@@ -0,0 +1,82 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.start;
+
+import static org.hamcrest.Matchers.is;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jetty.toolchain.test.OS;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+ at RunWith(Parameterized.class)
+public class PathMatchersAbsoluteTest
+{
+    @Parameters(name="{0} -> {1}")
+    public static List<Object[]> data()
+    {
+        List<Object[]> cases = new ArrayList<>();
+        
+        if(OS.IS_UNIX)
+        {
+            cases.add(new Object[]{"/opt/app",true});
+            cases.add(new Object[]{"/opt/app",true});
+            cases.add(new Object[]{"/opt/florb",true});
+            cases.add(new Object[]{"/home/user/benfranklin",true});
+            cases.add(new Object[]{"glob:/home/user/benfranklin/*.jar",true});
+            cases.add(new Object[]{"glob:/**/*.jar",true});
+            cases.add(new Object[]{"regex:/*-[^dev].ini",true});
+        }
+        
+        if(OS.IS_WINDOWS)
+        {
+            // normal declaration
+            cases.add(new Object[]{"D:\\code\\jetty\\jetty-start\\src\\test\\resources\\extra-libs\\example.jar",true});
+            // escaped declaration
+            cases.add(new Object[]{"C:\\\\System32",true});
+            cases.add(new Object[]{"C:\\\\Program Files",true});
+        }
+        
+        cases.add(new Object[]{"etc",false});
+        cases.add(new Object[]{"lib",false});
+        cases.add(new Object[]{"${user.dir}",false});
+        cases.add(new Object[]{"**/*.jar",false});
+        cases.add(new Object[]{"glob:*.ini",false});
+        cases.add(new Object[]{"regex:*-[^dev].ini",false});
+
+        return cases;
+    }
+    
+    @Parameter(value=0)
+    public String pattern;
+    @Parameter(value=1)
+    public boolean expected;
+
+    @Test
+    public void testIsAbsolute()
+    {
+        Assert.assertThat("isAbsolute(\""+pattern+"\")",PathMatchers.isAbsolute(pattern),is(expected));
+    }
+}
diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/PathMatchersSearchRootTest.java b/jetty-start/src/test/java/org/eclipse/jetty/start/PathMatchersSearchRootTest.java
new file mode 100644
index 0000000..d2ef299
--- /dev/null
+++ b/jetty-start/src/test/java/org/eclipse/jetty/start/PathMatchersSearchRootTest.java
@@ -0,0 +1,89 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.start;
+
+import static org.hamcrest.Matchers.is;
+
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jetty.toolchain.test.OS;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+ at RunWith(Parameterized.class)
+public class PathMatchersSearchRootTest
+{
+    @Parameters(name="{0}")
+    public static List<String[]> data()
+    {
+        List<String[]> cases = new ArrayList<>();
+        
+        if (OS.IS_UNIX)
+        {
+            // absolute first
+            cases.add(new String[]{"/opt/app/*.jar","/opt/app"});
+            cases.add(new String[]{"/lib/jvm/**/jre/lib/*.jar","/lib/jvm"});
+            cases.add(new String[]{"glob:/var/lib/*.xml","/var/lib"});
+            cases.add(new String[]{"glob:/var/lib/*.{xml,java}","/var/lib"});
+            cases.add(new String[]{"glob:/opt/corporate/lib-{dev,prod}/*.ini","/opt/corporate"});
+            cases.add(new String[]{"regex:/opt/jetty/.*/lib-(dev|prod)/*.ini","/opt/jetty"});
+
+            cases.add(new String[]{"/*.ini","/"});
+            cases.add(new String[]{"/etc/jetty.conf","/etc"});
+            cases.add(new String[]{"/common.conf","/"});
+        }
+
+        if (OS.IS_WINDOWS)
+        {
+            // absolute declaration
+            cases.add(new String[]{"D:\\code\\jetty\\jetty-start\\src\\test\\resources\\extra-libs\\example.jar",
+                    "D:\\code\\jetty\\jetty-start\\src\\test\\resources\\extra-libs"});
+            // escaped declaration
+            // absolute patterns (complete with required windows slash escaping)
+            cases.add(new String[]{"C:\\\\corp\\\\lib\\\\*.jar","C:\\corp\\lib"});
+            cases.add(new String[]{"D:\\\\lib\\\\**\\\\jre\\\\lib\\\\*.jar","D:\\lib"});
+        }
+
+        // some relative paths
+        cases.add(new String[]{"lib/*.jar","lib"});
+        cases.add(new String[]{"etc/jetty.xml","etc"});
+        cases.add(new String[]{"start.ini","."});
+        cases.add(new String[]{"start.d/","start.d"});
+        return cases;
+    }
+    
+    @Parameter(value=0)
+    public String pattern;
+    @Parameter(value=1)
+    public String expectedSearchRoot;
+    
+    @Test
+    public void testSearchRoot()
+    {
+        Path actual = PathMatchers.getSearchRoot(pattern);
+        String expectedNormal = FS.separators(expectedSearchRoot);
+        Assert.assertThat(".getSearchRoot(\"" + pattern + "\")",actual.toString(),is(expectedNormal));
+    }
+}
diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/PropertyDump.java b/jetty-start/src/test/java/org/eclipse/jetty/start/PropertyDump.java
new file mode 100644
index 0000000..6ae35f8
--- /dev/null
+++ b/jetty-start/src/test/java/org/eclipse/jetty/start/PropertyDump.java
@@ -0,0 +1,74 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.start;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.Properties;
+
+public class PropertyDump
+{
+    public static void main(String[] args)
+    {
+        // As System Properties
+        Properties props = System.getProperties();
+        Enumeration<?> names = props.propertyNames();
+        while (names.hasMoreElements())
+        {
+            String name = (String)names.nextElement();
+            // only interested in "test." prefixed properties
+            if (name.startsWith("test."))
+            {
+                System.out.printf("%s=%s%n",name,props.getProperty(name));
+            }
+        }
+
+        // As File Argument
+        for (String arg : args)
+        {
+            if (arg.endsWith(".properties"))
+            {
+                Properties aprops = new Properties();
+                File propFile = new File(arg);
+                System.out.printf("[load file %s]%n",propFile.getName());
+                try (FileReader reader = new FileReader(propFile))
+                {
+                    aprops.load(reader);
+                    Enumeration<?> anames = aprops.propertyNames();
+                    while (anames.hasMoreElements())
+                    {
+                        String name = (String)anames.nextElement();
+                        if (name.startsWith("test."))
+                        {
+                            System.out.printf("%s=%s%n",name,aprops.getProperty(name));
+                        }
+                    }
+                }
+                catch (IOException e)
+                {
+                    e.printStackTrace();
+                }
+            }
+        }
+
+        System.exit(0);
+    }
+}
diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/PropertyPassingTest.java b/jetty-start/src/test/java/org/eclipse/jetty/start/PropertyPassingTest.java
new file mode 100644
index 0000000..52e92d5
--- /dev/null
+++ b/jetty-start/src/test/java/org/eclipse/jetty/start/PropertyPassingTest.java
@@ -0,0 +1,214 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.start;
+
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.is;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jetty.toolchain.test.IO;
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.eclipse.jetty.toolchain.test.TestingDir;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class PropertyPassingTest
+{
+    private static class ConsoleCapture implements Runnable
+    {
+        private String mode;
+        private BufferedReader reader;
+        private StringWriter output;
+
+        public ConsoleCapture(String mode, InputStream is)
+        {
+            this.mode = mode;
+            this.reader = new BufferedReader(new InputStreamReader(is));
+            this.output = new StringWriter();
+        }
+
+        @Override
+        public void run()
+        {
+            String line;
+            try (PrintWriter out = new PrintWriter(output))
+            {
+                while ((line = reader.readLine()) != (null))
+                {
+                    out.println(line);
+                }
+            }
+            catch (IOException ignore)
+            {
+                /* ignore */
+            }
+            finally
+            {
+                IO.close(reader);
+            }
+        }
+
+        public String getConsoleOutput()
+        {
+            return output.toString();
+        }
+
+        public ConsoleCapture start()
+        {
+            Thread thread = new Thread(this,"ConsoleCapture/" + mode);
+            thread.start();
+            return this;
+        }
+    }
+
+    @Rule
+    public TestingDir testingdir = new TestingDir();
+
+    @Test
+    public void testAsJvmArg() throws IOException, InterruptedException
+    {
+        File bogusXml = MavenTestingUtils.getTestResourceFile("bogus.xml");
+
+        // Setup command line
+        List<String> commands = new ArrayList<>();
+        commands.add(getJavaBin());
+        commands.add("-Dmain.class=" + PropertyDump.class.getName());
+        commands.add("-cp");
+        commands.add(getClassPath());
+        // addDebug(commands);
+        commands.add("-Dtest.foo=bar"); // TESTING THIS
+        commands.add(getStartJarBin());
+        commands.add(bogusXml.getAbsolutePath());
+
+        // Run command, collect output
+        String output = collectRunOutput(commands);
+
+        // Test for values
+        Assert.assertThat("output",output,containsString("foo=bar"));
+    }
+
+    @Test
+    public void testAsCommandLineArg() throws IOException, InterruptedException
+    {
+        File bogusXml = MavenTestingUtils.getTestResourceFile("bogus.xml");
+
+        // Setup command line
+        List<String> commands = new ArrayList<>();
+        commands.add(getJavaBin());
+        commands.add("-Dmain.class=" + PropertyDump.class.getName());
+        commands.add("-cp");
+        commands.add(getClassPath());
+        // addDebug(commands);
+        commands.add(getStartJarBin());
+        commands.add("test.foo=bar"); // TESTING THIS
+        commands.add(bogusXml.getAbsolutePath());
+
+        // Run command, collect output
+        String output = collectRunOutput(commands);
+
+        // Test for values
+        Assert.assertThat("output",output,containsString("foo=bar"));
+    }
+
+    @Test
+    public void testAsDashDCommandLineArg() throws IOException, InterruptedException
+    {
+        File bogusXml = MavenTestingUtils.getTestResourceFile("bogus.xml");
+
+        // Setup command line
+        List<String> commands = new ArrayList<>();
+        commands.add(getJavaBin());
+        commands.add("-Dmain.class=" + PropertyDump.class.getName());
+        commands.add("-cp");
+        commands.add(getClassPath());
+        // addDebug(commands);
+        commands.add(getStartJarBin());
+        commands.add("-Dtest.foo=bar"); // TESTING THIS
+        commands.add(bogusXml.getAbsolutePath());
+
+        // Run command, collect output
+        String output = collectRunOutput(commands);
+
+        // Test for values
+        Assert.assertThat("output",output,containsString("foo=bar"));
+    }
+
+    private String getClassPath()
+    {
+        StringBuilder cp = new StringBuilder();
+        String pathSep = System.getProperty("path.separator");
+        cp.append(MavenTestingUtils.getProjectDir("target/classes"));
+        cp.append(pathSep);
+        cp.append(MavenTestingUtils.getProjectDir("target/test-classes"));
+        return cp.toString();
+    }
+
+    protected void addDebug(List<String> commands)
+    {
+        commands.add("-Xdebug");
+        commands.add("-Xrunjdwp:server=y,transport=dt_socket,address=4000,suspend=y");
+    }
+
+    private String collectRunOutput(List<String> commands) throws IOException, InterruptedException
+    {
+        StringBuilder cline = new StringBuilder();
+        for (String command : commands)
+        {
+            cline.append(command).append(" ");
+        }
+        System.out.println("Command line: " + cline);
+
+        ProcessBuilder builder = new ProcessBuilder(commands);
+        // Set PWD
+        builder.directory(MavenTestingUtils.getTestResourceDir("empty.home"));
+        Process pid = builder.start();
+
+        ConsoleCapture stdOutPump = new ConsoleCapture("STDOUT",pid.getInputStream()).start();
+        ConsoleCapture stdErrPump = new ConsoleCapture("STDERR",pid.getErrorStream()).start();
+
+        int exitCode = pid.waitFor();
+        if (exitCode != 0)
+        {
+            System.out.printf("STDERR: [" + stdErrPump.getConsoleOutput() + "]%n");
+            System.out.printf("STDOUT: [" + stdOutPump.getConsoleOutput() + "]%n");
+            Assert.assertThat("Exit code",exitCode,is(0));
+        }
+        return stdOutPump.getConsoleOutput();
+    }
+
+    private String getStartJarBin()
+    {
+        return org.eclipse.jetty.start.Main.class.getName();
+    }
+
+    private String getJavaBin()
+    {
+        return CommandLineBuilder.findJavaBin();
+    }
+}
diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/PropsTest.java b/jetty-start/src/test/java/org/eclipse/jetty/start/PropsTest.java
new file mode 100644
index 0000000..c287c86
--- /dev/null
+++ b/jetty-start/src/test/java/org/eclipse/jetty/start/PropsTest.java
@@ -0,0 +1,140 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.start;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+import org.eclipse.jetty.start.Props.Prop;
+import org.junit.Test;
+
+public class PropsTest
+{
+    private static final String FROM_TEST = "(test)";
+
+    private void assertProp(String prefix, Prop prop, String expectedKey, String expectedValue, String expectedOrigin)
+    {
+        assertThat(prefix,prop,notNullValue());
+        assertThat(prefix + ".key",prop.key,is(expectedKey));
+        assertThat(prefix + ".value",prop.value,is(expectedValue));
+        assertThat(prefix + ".origin",prop.origin,is(expectedOrigin));
+    }
+
+    @Test
+    public void testSystemPropsOnly()
+    {
+        Props props = new Props();
+
+        String expected = System.getProperty("java.io.tmpdir");
+        assertThat("System Property",props.getString("java.io.tmpdir"),is(expected));
+
+        Prop prop = props.getProp("java.io.tmpdir");
+        assertProp("System Prop",prop,"java.io.tmpdir",expected,Props.ORIGIN_SYSPROP);
+        assertThat("System Prop.overrides",prop.overrides,nullValue());
+    }
+
+    @Test
+    public void testBasic()
+    {
+        Props props = new Props();
+        props.setProperty("name","jetty",FROM_TEST);
+
+        String prefix = "Basic";
+        assertThat(prefix,props.getString("name"),is("jetty"));
+
+        Prop prop = props.getProp("name");
+        assertProp(prefix,prop,"name","jetty",FROM_TEST);
+        assertThat(prefix + ".overrides",prop.overrides,nullValue());
+    }
+
+    @Test
+    public void testOverride()
+    {
+        Props props = new Props();
+        props.setProperty("name","jetty",FROM_TEST);
+        props.setProperty("name","altjetty","(Alt-Jetty)");
+
+        String prefix = "Overriden";
+        assertThat(prefix,props.getString("name"),is("altjetty"));
+
+        Prop prop = props.getProp("name");
+        assertProp(prefix,prop,"name","altjetty","(Alt-Jetty)");
+        Prop older = prop.overrides;
+        assertThat(prefix + ".overrides",older,notNullValue());
+        assertProp(prefix + ".overridden",older,"name","jetty",FROM_TEST);
+        assertThat(prefix + ".overridden",older.overrides,nullValue());
+    }
+
+    @Test
+    public void testSimpleExpand()
+    {
+        Props props = new Props();
+        props.setProperty("name","jetty",FROM_TEST);
+        props.setProperty("version","9.1",FROM_TEST);
+
+        assertThat(props.expand("port=8080"),is("port=8080"));
+        assertThat(props.expand("jdk=${java.version}"),is("jdk=" + System.getProperty("java.version")));
+        assertThat(props.expand("id=${name}-${version}"),is("id=jetty-9.1"));
+        assertThat(props.expand("id=${unknown}-${wibble}"),is("id=${unknown}-${wibble}"));
+    }
+
+    @Test
+    public void testNoExpandDoubleDollar()
+    {
+        Props props = new Props();
+        props.setProperty("aa","123",FROM_TEST);
+
+        // Should NOT expand double $$ symbols
+        assertThat(props.expand("zz=$${aa}"),is("zz=${aa}"));
+        // Should expand
+        assertThat(props.expand("zz=${aa}"),is("zz=123"));
+    }
+
+    @Test
+    public void testExpandDeep()
+    {
+        Props props = new Props();
+        props.setProperty("name","jetty",FROM_TEST);
+        props.setProperty("version","9.1",FROM_TEST);
+        props.setProperty("id","${name}-${version}",FROM_TEST);
+
+        // Should expand
+        assertThat(props.expand("server-id=corporate-${id}"),is("server-id=corporate-jetty-9.1"));
+    }
+
+    @Test
+    public void testExpandLoop()
+    {
+        Props props = new Props();
+        props.setProperty("aa","${bb}",FROM_TEST);
+        props.setProperty("bb","${cc}",FROM_TEST);
+        props.setProperty("cc","${aa}",FROM_TEST);
+
+        try
+        {
+            // Should throw exception
+            props.expand("val=${aa}");
+            fail("Should have thrown a " + PropsException.class);
+        }
+        catch (PropsException e)
+        {
+            assertThat(e.getMessage(),is("Property expansion loop detected: aa -> bb -> cc -> aa"));
+        }
+    }
+}
diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/RebuildTestResources.java b/jetty-start/src/test/java/org/eclipse/jetty/start/RebuildTestResources.java
new file mode 100644
index 0000000..4f2fe61
--- /dev/null
+++ b/jetty-start/src/test/java/org/eclipse/jetty/start/RebuildTestResources.java
@@ -0,0 +1,190 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.start;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.PathMatcher;
+import java.nio.file.StandardCopyOption;
+import java.util.regex.Pattern;
+
+import org.eclipse.jetty.toolchain.test.FS;
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+
+/**
+ * Utility class to rebuild the src/test/resources/dist-home from the active build tree.
+ * <p>
+ * Not really meant to be run with each build. Nor is it a good idea to attempt to do that (as this would introduce a dependency from jetty-start ->
+ * jetty-distribution which is a circular dependency)
+ */
+public class RebuildTestResources
+{
+    public static void main(String[] args)
+    {
+        File realDistHome = MavenTestingUtils.getProjectDir("../jetty-distribution/target/distribution");
+        File outputDir = MavenTestingUtils.getTestResourceDir("dist-home");
+        try
+        {
+            new RebuildTestResources(realDistHome,outputDir).rebuild();
+        }
+        catch (Throwable t)
+        {
+            t.printStackTrace();
+        }
+    }
+
+    private static interface FileCopier
+    {
+        public void copy(Path from, Path to) throws IOException;
+    }
+
+    private static class NormalFileCopier implements FileCopier
+    {
+        @Override
+        public void copy(Path from, Path to) throws IOException
+        {
+            Files.copy(from,to,StandardCopyOption.REPLACE_EXISTING);
+        }
+    }
+
+    private static class TouchFileCopier implements FileCopier
+    {
+        @Override
+        public void copy(Path from, Path to) throws IOException
+        {
+            Files.createFile(to);
+        }
+    }
+
+    private static interface Renamer
+    {
+        public String getName(Path path);
+    }
+
+    private static class NoRenamer implements Renamer
+    {
+        @Override
+        public String getName(Path path)
+        {
+            return path.getFileName().toString();
+        }
+    }
+
+    private static class RegexRenamer implements Renamer
+    {
+        private final Pattern pat;
+        private final String replacement;
+
+        public RegexRenamer(String regex, String replacement)
+        {
+            this.pat = Pattern.compile(regex);
+            this.replacement = replacement;
+        }
+
+        @Override
+        public String getName(Path path)
+        {
+            String origName = path.getFileName().toString();
+            return pat.matcher(origName).replaceAll(replacement);
+        }
+    }
+
+    private final Path destDir;
+    private final Path srcDir;
+
+    public RebuildTestResources(File realDistHome, File outputDir) throws IOException
+    {
+        this.srcDir = realDistHome.toPath().toRealPath();
+        this.destDir = outputDir.toPath();
+    }
+
+    private void copyLibs() throws IOException
+    {
+        System.out.println("Copying libs (lib dir) ...");
+        Path libsDir = destDir.resolve("lib");
+        FS.ensureDirExists(libsDir.toFile());
+
+        PathMatcher matcher = getPathMatcher("glob:**.jar");
+        Renamer renamer = new RegexRenamer("-9\\.[0-9.]*(v[0-9]*)?(-SNAPSHOT)?(RC[0-9])?(M[0-9])?","-TEST");
+        FileCopier copier = new TouchFileCopier();
+        copyDir(srcDir.resolve("lib"),libsDir,matcher,renamer,copier);
+    }
+
+    private void copyModules() throws IOException
+    {
+        System.out.println("Copying modules ...");
+        Path modulesDir = destDir.resolve("modules");
+        FS.ensureDirExists(modulesDir.toFile());
+
+        PathMatcher matcher = getPathMatcher("glob:**.mod");
+        Renamer renamer = new NoRenamer();
+        FileCopier copier = new NormalFileCopier();
+        copyDir(srcDir.resolve("modules"),modulesDir,matcher,renamer,copier);
+    }
+
+    private void copyXmls() throws IOException
+    {
+        System.out.println("Copying xmls (etc dir) ...");
+        Path xmlDir = destDir.resolve("etc");
+        FS.ensureDirExists(xmlDir.toFile());
+
+        PathMatcher matcher = getPathMatcher("glob:**.xml");
+        Renamer renamer = new NoRenamer();
+        FileCopier copier = new TouchFileCopier();
+        copyDir(srcDir.resolve("etc"),xmlDir,matcher,renamer,copier);
+    }
+
+    private void rebuild() throws IOException
+    {
+        copyModules();
+        copyLibs();
+        copyXmls();
+        System.out.println("Done");
+    }
+
+    private PathMatcher getPathMatcher(String pattern)
+    {
+        return FileSystems.getDefault().getPathMatcher(pattern);
+    }
+
+    private void copyDir(Path from, Path to, PathMatcher fileMatcher, Renamer renamer, FileCopier copier) throws IOException
+    {
+        Files.createDirectories(to);
+
+        for (Path path : Files.newDirectoryStream(from))
+        {
+            String name = renamer.getName(path);
+            Path dest = to.resolve(name);
+            if (Files.isDirectory(path))
+            {
+                copyDir(path,dest,fileMatcher,renamer,copier);
+            }
+            else
+            {
+                if (fileMatcher.matches(path))
+                {
+                    copier.copy(path,dest);
+                }
+            }
+        }
+    }
+}
diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/SystemExitAsException.java b/jetty-start/src/test/java/org/eclipse/jetty/start/SystemExitAsException.java
new file mode 100644
index 0000000..4e713b3
--- /dev/null
+++ b/jetty-start/src/test/java/org/eclipse/jetty/start/SystemExitAsException.java
@@ -0,0 +1,79 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.start;
+
+import java.security.Permission;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+public class SystemExitAsException implements TestRule
+{
+    @SuppressWarnings("serial")
+    public static class SystemExitException extends RuntimeException
+    {
+        public SystemExitException(int status)
+        {
+            super("Encountered System.exit(" + status + ")");
+        }
+    }
+
+    private static class NoExitSecurityManager extends SecurityManager
+    {
+        @Override
+        public void checkPermission(Permission perm)
+        {
+        }
+
+        @Override
+        public void checkPermission(Permission perm, Object context)
+        {
+        }
+
+        @Override
+        public void checkExit(int status)
+        {
+            super.checkExit(status);
+            throw new SystemExitException(status);
+        }
+    }
+
+    @Override
+    public Statement apply(final Statement statement, Description description)
+    {
+        return new Statement()
+        {
+            @Override
+            public void evaluate() throws Throwable
+            {
+                SecurityManager origSecurityManager = System.getSecurityManager();
+                try
+                {
+                    System.setSecurityManager(new NoExitSecurityManager());
+                    statement.evaluate();
+                }
+                finally
+                {
+                    System.setSecurityManager(origSecurityManager);
+                }
+            }
+        };
+    }
+}
diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/TestBadUseCases.java b/jetty-start/src/test/java/org/eclipse/jetty/start/TestBadUseCases.java
new file mode 100644
index 0000000..f949fde
--- /dev/null
+++ b/jetty-start/src/test/java/org/eclipse/jetty/start/TestBadUseCases.java
@@ -0,0 +1,85 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.start;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.junit.Test;
+
+/**
+ * Test bad configuration scenarios.
+ */
+public class TestBadUseCases
+{
+    private void assertBadConfig(String homeName, String baseName, String expectedErrorMessage, String... cmdLineArgs) throws Exception
+    {
+        File homeDir = MavenTestingUtils.getTestResourceDir("usecases/" + homeName);
+        File baseDir = MavenTestingUtils.getTestResourceDir("usecases/" + baseName);
+
+        Main main = new Main();
+        List<String> cmdLine = new ArrayList<>();
+        cmdLine.add("jetty.home=" + homeDir.getAbsolutePath());
+        cmdLine.add("jetty.base=" + baseDir.getAbsolutePath());
+        // cmdLine.add("--debug");
+        for (String arg : cmdLineArgs)
+        {
+            cmdLine.add(arg);
+        }
+
+        try
+        {
+            main.processCommandLine(cmdLine);
+            fail("Expected " + UsageException.class.getName());
+        }
+        catch (UsageException e)
+        {
+            assertThat("Usage error",e.getMessage(),containsString(expectedErrorMessage));
+        }
+    }
+
+    @Test
+    public void testBadJspCommandLine() throws Exception
+    {
+        assertBadConfig("home","base.with.jsp.default",
+                "Missing referenced dependency: jsp-impl/bad-jsp","jsp-impl=bad");
+    }
+
+    @Test
+    public void testBadJspImplName() throws Exception
+    {
+        assertBadConfig("home","base.with.jsp.bad",
+                "Missing referenced dependency: jsp-impl/bogus-jsp");
+    }
+    
+    @Test
+    public void testWithSpdyBadNpnVersion() throws Exception
+    {
+        assertBadConfig("home","base.enable.spdy.bad.npn.version",
+                "Missing referenced dependency: protonego-impl/npn-1.7.0_01",
+                "java.version=1.7.0_01", "protonego=npn");
+    }
+
+
+}
diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/TestEnv.java b/jetty-start/src/test/java/org/eclipse/jetty/start/TestEnv.java
new file mode 100644
index 0000000..69c8f5f
--- /dev/null
+++ b/jetty-start/src/test/java/org/eclipse/jetty/start/TestEnv.java
@@ -0,0 +1,52 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.start;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+import org.eclipse.jetty.toolchain.test.FS;
+import org.eclipse.jetty.toolchain.test.IO;
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.eclipse.jetty.toolchain.test.OS;
+
+public class TestEnv
+{
+    public static void copyTestDir(String testResourceDir, File destDir) throws IOException
+    {
+        FS.ensureDirExists(destDir);
+        File srcDir = MavenTestingUtils.getTestResourceDir(testResourceDir);
+        IO.copyDir(srcDir,destDir);
+    }
+
+    public static void makeFile(File dir, String relFilePath, String... contents) throws IOException
+    {
+        File outputFile = new File(dir,OS.separators(relFilePath));
+        FS.ensureDirExists(outputFile.getParentFile());
+        try (FileWriter writer = new FileWriter(outputFile); PrintWriter out = new PrintWriter(writer))
+        {
+            for (String content : contents)
+            {
+                out.println(content);
+            }
+        }
+    }
+}
diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/TestUseCases.java b/jetty-start/src/test/java/org/eclipse/jetty/start/TestUseCases.java
new file mode 100644
index 0000000..59df17f
--- /dev/null
+++ b/jetty-start/src/test/java/org/eclipse/jetty/start/TestUseCases.java
@@ -0,0 +1,135 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.start;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.junit.Test;
+
+/**
+ * Various Home + Base use cases
+ */
+public class TestUseCases
+{
+    private void assertUseCase(String homeName, String baseName, String assertName, String... cmdLineArgs) throws Exception
+    {
+        File homeDir = MavenTestingUtils.getTestResourceDir("usecases/" + homeName);
+        File baseDir = MavenTestingUtils.getTestResourceDir("usecases/" + baseName);
+
+        Main main = new Main();
+        List<String> cmdLine = new ArrayList<>();
+        cmdLine.add("jetty.home=" + homeDir.getAbsolutePath());
+        cmdLine.add("jetty.base=" + baseDir.getAbsolutePath());
+        // cmdLine.add("--debug");
+        for (String arg : cmdLineArgs)
+        {
+            cmdLine.add(arg);
+        }
+        StartArgs args = main.processCommandLine(cmdLine);
+        BaseHome baseHome = main.getBaseHome();
+        ConfigurationAssert.assertConfiguration(baseHome,args,"usecases/" + assertName);
+    }
+
+    @Test
+    public void testBarebones() throws Exception
+    {
+        assertUseCase("home","base.barebones","assert-barebones.txt");
+    }
+
+    @Test
+    public void testJMX() throws Exception
+    {
+        assertUseCase("home","base.jmx","assert-jmx.txt");
+    }
+    
+    @Test
+    public void testWithLogging() throws Exception
+    {
+        assertUseCase("home","base.logging","assert-logging.txt");
+    }
+
+    @Test
+    public void testWithIncludeJettyDir_Logging() throws Exception
+    {
+        assertUseCase("home","base.with.include.jetty.dirs","assert-include-jetty-dir-logging.txt");
+    }
+
+    @Test
+    public void testWithJspDefault() throws Exception
+    {
+        assertUseCase("home","base.with.jsp.default","assert-jsp-apache.txt");
+    }
+
+    @Test
+    public void testWithJspApache() throws Exception
+    {
+        assertUseCase("home","base.with.jsp.apache","assert-jsp-apache.txt");
+    }
+    
+    @Test
+    public void testWithJspGlassfish() throws Exception
+    {
+        assertUseCase("home","base.with.jsp.glassfish","assert-jsp-glassfish.txt");
+    }
+
+    @Test
+    public void testWithJspGlassfishCmdLine() throws Exception
+    {
+        assertUseCase("home","base.with.jsp.default","assert-jsp-glassfish.txt","jsp-impl=glassfish");
+    }
+
+    @Test
+    public void testWithMissingNpnVersion() throws Exception
+    {
+        assertUseCase("home","base.missing.npn.version","assert-missing-npn-version.txt","java.version=1.7.0_01");
+    }
+    
+    @Test
+    public void testWithSpdy() throws Exception
+    {
+        assertUseCase("home","base.enable.spdy","assert-enable-spdy.txt","java.version=1.7.0_60");
+    }
+    
+    @Test
+    public void testWithDatabase() throws Exception
+    {
+        assertUseCase("home","base.with.db","assert-with-db.txt");
+    }
+
+    @Test
+    public void testWithDeepExt() throws Exception
+    {
+        assertUseCase("home","base.with.ext","assert-with.ext.txt");
+    }
+    
+    @Test
+    public void testWithPropsBasic() throws Exception
+    {
+        assertUseCase("home","base.props.basic","assert-props.basic.txt","port=9090");
+    }
+    
+    @Test
+    public void testWithPropsAgent() throws Exception
+    {
+        assertUseCase("home","base.props.agent","assert-props.agent.txt","java.vm.specification.version=1.6");
+    }
+}
diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/VersionTest.java b/jetty-start/src/test/java/org/eclipse/jetty/start/VersionTest.java
index e607218..7e61d1f 100644
--- a/jetty-start/src/test/java/org/eclipse/jetty/start/VersionTest.java
+++ b/jetty-start/src/test/java/org/eclipse/jetty/start/VersionTest.java
@@ -18,11 +18,11 @@
 
 package org.eclipse.jetty.start;
 
-import org.junit.Test;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
+import org.junit.Test;
+
 public class VersionTest
 {
     @Test
diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/config/ConfigSourcesTest.java b/jetty-start/src/test/java/org/eclipse/jetty/start/config/ConfigSourcesTest.java
new file mode 100644
index 0000000..7aad9a9
--- /dev/null
+++ b/jetty-start/src/test/java/org/eclipse/jetty/start/config/ConfigSourcesTest.java
@@ -0,0 +1,599 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.start.config;
+
+import static org.hamcrest.Matchers.*;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.eclipse.jetty.start.ConfigurationAssert;
+import org.eclipse.jetty.start.Props.Prop;
+import org.eclipse.jetty.start.TestEnv;
+import org.eclipse.jetty.start.UsageException;
+import org.eclipse.jetty.toolchain.test.FS;
+import org.eclipse.jetty.toolchain.test.TestingDir;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class ConfigSourcesTest
+{
+    @Rule
+    public TestingDir testdir = new TestingDir();
+
+    private void assertIdOrder(ConfigSources sources, String... expectedOrder)
+    {
+        List<String> actualList = new ArrayList<>();
+        for (ConfigSource source : sources)
+        {
+            actualList.add(source.getId());
+        }
+        List<String> expectedList = Arrays.asList(expectedOrder);
+        ConfigurationAssert.assertOrdered("ConfigSources.id order",expectedList,actualList);
+    }
+
+    private void assertDirOrder(ConfigSources sources, File... expectedDirOrder)
+    {
+        List<String> actualList = new ArrayList<>();
+        for (ConfigSource source : sources)
+        {
+            if (source instanceof DirConfigSource)
+            {
+                actualList.add(((DirConfigSource)source).getDir().toString());
+            }
+        }
+        List<String> expectedList = new ArrayList<>();
+        for (File path : expectedDirOrder)
+        {
+            expectedList.add(path.getAbsolutePath());
+        }
+        ConfigurationAssert.assertOrdered("ConfigSources.dir order",expectedList,actualList);
+    }
+
+    private void assertProperty(ConfigSources sources, String key, String expectedValue)
+    {
+        Prop prop = sources.getProp(key);
+        Assert.assertThat("getProp('" + key + "') should not be null",prop,notNullValue());
+        Assert.assertThat("getProp('" + key + "')",prop.value,is(expectedValue));
+    }
+
+    @Test
+    public void testOrder_BasicConfig() throws IOException
+    {
+        // Create home
+        File home = testdir.getFile("home");
+        FS.ensureEmpty(home);
+        TestEnv.copyTestDir("usecases/home",home);
+
+        // Create base
+        File base = testdir.getFile("base");
+        FS.ensureEmpty(base);
+        TestEnv.makeFile(base,"start.ini", //
+                "jetty.host=127.0.0.1");
+
+        ConfigSources sources = new ConfigSources();
+
+        String[] cmdLine = new String[0];
+        sources.add(new CommandLineConfigSource(cmdLine));
+        sources.add(new JettyBaseConfigSource(base.toPath()));
+        sources.add(new JettyHomeConfigSource(home.toPath()));
+
+        assertIdOrder(sources,"<command-line>","${jetty.base}","${jetty.home}");
+    }
+
+    @Test
+    public void testOrder_With1ExtraConfig() throws IOException
+    {
+        // Create home
+        File home = testdir.getFile("home");
+        FS.ensureEmpty(home);
+        TestEnv.copyTestDir("usecases/home",home);
+
+        // Create common
+        File common = testdir.getFile("common");
+        FS.ensureEmpty(common);
+
+        // Create base
+        File base = testdir.getFile("base");
+        FS.ensureEmpty(base);
+        TestEnv.makeFile(base,"start.ini", //
+                "jetty.host=127.0.0.1",//
+                "--include-jetty-dir=" + common.getAbsolutePath());
+
+        ConfigSources sources = new ConfigSources();
+
+        String[] cmdLine = new String[0];
+        sources.add(new CommandLineConfigSource(cmdLine));
+        sources.add(new JettyHomeConfigSource(home.toPath()));
+        sources.add(new JettyBaseConfigSource(base.toPath()));
+
+        assertIdOrder(sources,"<command-line>","${jetty.base}",common.getAbsolutePath(),"${jetty.home}");
+    }
+
+    @Test
+    public void testCommandLine_1Extra_FromSimpleProp() throws Exception
+    {
+        // Create home
+        File home = testdir.getFile("home");
+        FS.ensureEmpty(home);
+        TestEnv.copyTestDir("usecases/home",home);
+
+        // Create common
+        File common = testdir.getFile("common");
+        FS.ensureEmpty(common);
+        TestEnv.makeFile(common,"start.ini","jetty.port=8080");
+
+        // Create base
+        File base = testdir.getFile("base");
+        FS.ensureEmpty(base);
+        TestEnv.makeFile(base,"start.ini", //
+                "jetty.host=127.0.0.1");
+
+        ConfigSources sources = new ConfigSources();
+
+        // Simple command line reference to include-jetty-dir via property (also on command line)
+
+        String[] cmdLine = new String[] {
+                // property
+                "my.common=" + common.getAbsolutePath(),
+                // reference via property
+                "--include-jetty-dir=${my.common}" };
+        
+        sources.add(new CommandLineConfigSource(cmdLine));
+        sources.add(new JettyHomeConfigSource(home.toPath()));
+        sources.add(new JettyBaseConfigSource(base.toPath()));
+
+        assertIdOrder(sources,"<command-line>","${jetty.base}","${my.common}","${jetty.home}");
+
+        assertDirOrder(sources,base,common,home);
+
+        assertProperty(sources,"jetty.host","127.0.0.1");
+        assertProperty(sources,"jetty.port","8080"); // from 'common'
+    }
+
+    @Test
+    public void testCommandLine_1Extra_FromPropPrefix() throws Exception
+    {
+        // Create home
+        File home = testdir.getFile("home");
+        FS.ensureEmpty(home);
+        TestEnv.copyTestDir("usecases/home",home);
+
+        // Create opt
+        File opt = testdir.getFile("opt");
+        FS.ensureEmpty(opt);
+
+        // Create common
+        File common = new File(opt,"common");
+        FS.ensureEmpty(common);
+        TestEnv.makeFile(common,"start.ini","jetty.port=8080");
+
+        // Create base
+        File base = testdir.getFile("base");
+        FS.ensureEmpty(base);
+        TestEnv.makeFile(base,"start.ini", //
+                "jetty.host=127.0.0.1");
+
+        String dirRef = "${my.opt}" + File.separator + "common";
+
+        ConfigSources sources = new ConfigSources();
+
+        // Simple command line reference to include-jetty-dir via property (also on command line)
+        String[] cmdLine = new String[] {
+                // property to 'opt' dir
+                "my.opt=" + opt.getAbsolutePath(),
+                // reference via property prefix
+                "--include-jetty-dir=" + dirRef };
+        
+        sources.add(new CommandLineConfigSource(cmdLine));
+        sources.add(new JettyHomeConfigSource(home.toPath()));
+        sources.add(new JettyBaseConfigSource(base.toPath()));
+
+        assertIdOrder(sources,"<command-line>","${jetty.base}",dirRef,"${jetty.home}");
+
+        assertDirOrder(sources,base,common,home);
+
+        assertProperty(sources,"jetty.host","127.0.0.1");
+        assertProperty(sources,"jetty.port","8080"); // from 'common'
+    }
+
+    @Test
+    public void testCommandLine_1Extra_FromCompoundProp() throws Exception
+    {
+        // Create home
+        File home = testdir.getFile("home");
+        FS.ensureEmpty(home);
+        TestEnv.copyTestDir("usecases/home",home);
+
+        // Create opt
+        File opt = testdir.getFile("opt");
+        FS.ensureEmpty(opt);
+
+        // Create common
+        File common = new File(opt,"common");
+        FS.ensureEmpty(common);
+        TestEnv.makeFile(common,"start.ini","jetty.port=8080");
+
+        // Create base
+        File base = testdir.getFile("base");
+        FS.ensureEmpty(base);
+        TestEnv.makeFile(base,"start.ini", //
+                "jetty.host=127.0.0.1");
+
+        String dirRef = "${my.opt}" + File.separator + "${my.dir}";
+
+        ConfigSources sources = new ConfigSources();
+
+        // Simple command line reference to include-jetty-dir via property (also on command line)
+
+        String[] cmdLine = new String[] {
+                // property to 'opt' dir
+                "my.opt=" + opt.getAbsolutePath(),
+                // property to commmon dir name
+                "my.dir=common",
+                // reference via property prefix
+                "--include-jetty-dir=" + dirRef };
+        
+        sources.add(new CommandLineConfigSource(cmdLine));
+        sources.add(new JettyHomeConfigSource(home.toPath()));
+        sources.add(new JettyBaseConfigSource(base.toPath()));
+
+        assertIdOrder(sources,"<command-line>","${jetty.base}",dirRef,"${jetty.home}");
+
+        assertDirOrder(sources,base,common,home);
+
+        assertProperty(sources,"jetty.host","127.0.0.1");
+        assertProperty(sources,"jetty.port","8080"); // from 'common'
+    }
+    
+    @Test
+    public void testRefCommon() throws Exception
+    {
+        // Create home
+        File home = testdir.getFile("home");
+        FS.ensureEmpty(home);
+        TestEnv.copyTestDir("usecases/home",home);
+
+        // Create common
+        File common = testdir.getFile("common");
+        FS.ensureEmpty(common);
+        TestEnv.makeFile(common,"start.ini","jetty.port=8080");
+
+        // Create base
+        File base = testdir.getFile("base");
+        FS.ensureEmpty(base);
+        TestEnv.makeFile(base,"start.ini", //
+                "jetty.host=127.0.0.1",//
+                "--include-jetty-dir=" + common.getAbsolutePath());
+
+        ConfigSources sources = new ConfigSources();
+        
+        String cmdLine[] = new String[0];
+        sources.add(new CommandLineConfigSource(cmdLine));
+        sources.add(new JettyHomeConfigSource(home.toPath()));
+        sources.add(new JettyBaseConfigSource(base.toPath()));
+
+        assertIdOrder(sources,"<command-line>","${jetty.base}",common.getAbsolutePath(),"${jetty.home}");
+
+        assertDirOrder(sources,base,common,home);
+
+        assertProperty(sources,"jetty.host","127.0.0.1");
+        assertProperty(sources,"jetty.port","8080"); // from 'common'
+    }
+
+    @Test
+    public void testRefCommonAndCorp() throws Exception
+    {
+        // Create home
+        File home = testdir.getFile("home");
+        FS.ensureEmpty(home);
+        TestEnv.copyTestDir("usecases/home",home);
+
+        // Create common
+        File common = testdir.getFile("common");
+        FS.ensureEmpty(common);
+        TestEnv.makeFile(common,"start.ini","jetty.port=8080");
+
+        // Create corp
+        File corp = testdir.getFile("corp");
+        FS.ensureEmpty(corp);
+
+        // Create base
+        File base = testdir.getFile("base");
+        FS.ensureEmpty(base);
+        TestEnv.makeFile(base,"start.ini", //
+                "jetty.host=127.0.0.1",//
+                "--include-jetty-dir=" + common.getAbsolutePath(), //
+                "--include-jetty-dir=" + corp.getAbsolutePath());
+
+        ConfigSources sources = new ConfigSources();
+
+        String cmdLine[] = new String[0];
+        sources.add(new CommandLineConfigSource(cmdLine));
+        sources.add(new JettyHomeConfigSource(home.toPath()));
+        sources.add(new JettyBaseConfigSource(base.toPath()));
+
+        assertIdOrder(sources,"<command-line>","${jetty.base}",
+                common.getAbsolutePath(),
+                corp.getAbsolutePath(),
+                "${jetty.home}");
+
+        assertDirOrder(sources,base,common,corp,home);
+
+        assertProperty(sources,"jetty.host","127.0.0.1");
+        assertProperty(sources,"jetty.port","8080"); // from 'common'
+    }
+    
+    @Test
+    public void testRefCommonRefCorp() throws Exception
+    {
+        // Create home
+        File home = testdir.getFile("home");
+        FS.ensureEmpty(home);
+        TestEnv.copyTestDir("usecases/home",home);
+
+        // Create corp
+        File corp = testdir.getFile("corp");
+        FS.ensureEmpty(corp);
+        TestEnv.makeFile(corp,"start.ini", //
+                "jetty.port=9090");
+
+        // Create common
+        File common = testdir.getFile("common");
+        FS.ensureEmpty(common);
+        TestEnv.makeFile(common,"start.ini", //
+                "--include-jetty-dir=" + corp.getAbsolutePath(), //
+                "jetty.port=8080");
+
+        // Create base
+        File base = testdir.getFile("base");
+        FS.ensureEmpty(base);
+        TestEnv.makeFile(base,"start.ini", //
+                "jetty.host=127.0.0.1",//
+                "--include-jetty-dir=" + common.getAbsolutePath());
+
+        ConfigSources sources = new ConfigSources();
+
+        String cmdLine[] = new String[0];
+        sources.add(new CommandLineConfigSource(cmdLine));
+        sources.add(new JettyHomeConfigSource(home.toPath()));
+        sources.add(new JettyBaseConfigSource(base.toPath()));
+
+        assertIdOrder(sources,"<command-line>","${jetty.base}",
+                common.getAbsolutePath(),
+                corp.getAbsolutePath(),
+                "${jetty.home}");
+
+        assertDirOrder(sources,base,common,corp,home);
+
+        assertProperty(sources,"jetty.host","127.0.0.1");
+        assertProperty(sources,"jetty.port","8080"); // from 'common'
+    }
+    
+    @Test
+    public void testRefCommonRefCorp_FromSimpleProps() throws Exception
+    {
+        // Create home
+        File home = testdir.getFile("home");
+        FS.ensureEmpty(home);
+        TestEnv.copyTestDir("usecases/home",home);
+
+        // Create corp
+        File corp = testdir.getFile("corp");
+        FS.ensureEmpty(corp);
+        TestEnv.makeFile(corp,"start.ini", //
+                "jetty.port=9090");
+
+        // Create common
+        File common = testdir.getFile("common");
+        FS.ensureEmpty(common);
+        TestEnv.makeFile(common,"start.ini", //
+                "my.corp=" + corp.getAbsolutePath(), //
+                "--include-jetty-dir=${my.corp}", //
+                "jetty.port=8080");
+
+        // Create base
+        File base = testdir.getFile("base");
+        FS.ensureEmpty(base);
+        TestEnv.makeFile(base,"start.ini", //
+                "jetty.host=127.0.0.1",//
+                "my.common="+common.getAbsolutePath(), //
+                "--include-jetty-dir=${my.common}");
+
+        ConfigSources sources = new ConfigSources();
+
+        String cmdLine[] = new String[0];
+        sources.add(new CommandLineConfigSource(cmdLine));
+        sources.add(new JettyHomeConfigSource(home.toPath()));
+        sources.add(new JettyBaseConfigSource(base.toPath()));
+
+        assertIdOrder(sources,"<command-line>",
+                "${jetty.base}",
+                "${my.common}",
+                "${my.corp}",
+                "${jetty.home}");
+
+        assertDirOrder(sources,base,common,corp,home);
+
+        assertProperty(sources,"jetty.host","127.0.0.1");
+        assertProperty(sources,"jetty.port","8080"); // from 'common'
+    }
+    
+    @Test
+    public void testRefCommonRefCorp_CmdLineRef() throws Exception
+    {
+        // Create home
+        File home = testdir.getFile("home");
+        FS.ensureEmpty(home);
+        TestEnv.copyTestDir("usecases/home",home);
+
+        // Create devops
+        File devops = testdir.getFile("devops");
+        FS.ensureEmpty(devops);
+        TestEnv.makeFile(devops,"start.ini", //
+                "--module=logging", //
+                "jetty.port=2222");
+
+        // Create corp
+        File corp = testdir.getFile("corp");
+        FS.ensureEmpty(corp);
+        TestEnv.makeFile(corp,"start.ini", //
+                "jetty.port=9090");
+
+        // Create common
+        File common = testdir.getFile("common");
+        FS.ensureEmpty(common);
+        TestEnv.makeFile(common,"start.ini", //
+                "--include-jetty-dir=" + corp.getAbsolutePath(), //
+                "jetty.port=8080");
+
+        // Create base
+        File base = testdir.getFile("base");
+        FS.ensureEmpty(base);
+        TestEnv.makeFile(base,"start.ini", //
+                "jetty.host=127.0.0.1",//
+                "--include-jetty-dir=" + common.getAbsolutePath());
+
+        ConfigSources sources = new ConfigSources();
+        
+        String cmdLine[] = new String[]{
+                // command line provided include-jetty-dir ref
+                "--include-jetty-dir=" + devops.getAbsolutePath()};
+        sources.add(new CommandLineConfigSource(cmdLine));
+        sources.add(new JettyHomeConfigSource(home.toPath()));
+        sources.add(new JettyBaseConfigSource(base.toPath()));
+
+        assertIdOrder(sources,"<command-line>",
+                "${jetty.base}",
+                devops.getAbsolutePath(),
+                common.getAbsolutePath(),
+                corp.getAbsolutePath(),
+                "${jetty.home}");
+
+        assertDirOrder(sources,base,devops,common,corp,home);
+
+        assertProperty(sources,"jetty.host","127.0.0.1");
+        assertProperty(sources,"jetty.port","2222"); // from 'common'
+    }
+    
+    @Test
+    public void testRefCommonRefCorp_CmdLineProp() throws Exception
+    {
+        // Create home
+        File home = testdir.getFile("home");
+        FS.ensureEmpty(home);
+        TestEnv.copyTestDir("usecases/home",home);
+
+        // Create corp
+        File corp = testdir.getFile("corp");
+        FS.ensureEmpty(corp);
+        TestEnv.makeFile(corp,"start.ini", //
+                "jetty.port=9090");
+
+        // Create common
+        File common = testdir.getFile("common");
+        FS.ensureEmpty(common);
+        TestEnv.makeFile(common,"start.ini", //
+                "--include-jetty-dir=" + corp.getAbsolutePath(), //
+                "jetty.port=8080");
+
+        // Create base
+        File base = testdir.getFile("base");
+        FS.ensureEmpty(base);
+        TestEnv.makeFile(base,"start.ini", //
+                "jetty.host=127.0.0.1",//
+                "--include-jetty-dir=" + common.getAbsolutePath());
+
+        ConfigSources sources = new ConfigSources();
+        
+        String cmdLine[] = new String[]{
+             // command line property should override all others
+                "jetty.port=7070"
+        };
+        sources.add(new CommandLineConfigSource(cmdLine));
+        sources.add(new JettyHomeConfigSource(home.toPath()));
+        sources.add(new JettyBaseConfigSource(base.toPath()));
+
+        assertIdOrder(sources,"<command-line>","${jetty.base}",
+                common.getAbsolutePath(),
+                corp.getAbsolutePath(),
+                "${jetty.home}");
+
+        assertDirOrder(sources,base,common,corp,home);
+
+        assertProperty(sources,"jetty.host","127.0.0.1");
+        assertProperty(sources,"jetty.port","7070"); // from <command-line>
+    }
+    
+    @Test
+    public void testBadDoubleRef() throws Exception
+    {
+        // Create home
+        File home = testdir.getFile("home");
+        FS.ensureEmpty(home);
+        TestEnv.copyTestDir("usecases/home",home);
+
+        // Create common
+        File common = testdir.getFile("common");
+        FS.ensureEmpty(common);
+
+        // Create corp
+        File corp = testdir.getFile("corp");
+        FS.ensureEmpty(corp);
+        TestEnv.makeFile(corp,"start.ini", 
+                // standard property
+                "jetty.port=9090",
+                // INTENTIONAL BAD Reference (duplicate)
+                "--include-jetty-dir=" + common.getAbsolutePath());
+
+        // Populate common
+        TestEnv.makeFile(common,"start.ini", 
+                // standard property
+                "jetty.port=8080",
+                // reference to corp
+                "--include-jetty-dir=" + corp.getAbsolutePath());
+
+        // Create base
+        File base = testdir.getFile("base");
+        FS.ensureEmpty(base);
+        TestEnv.makeFile(base,"start.ini", //
+                "jetty.host=127.0.0.1",//
+                "--include-jetty-dir=" + common.getAbsolutePath());
+
+        ConfigSources sources = new ConfigSources();
+
+        try
+        {
+            String cmdLine[] = new String[0];
+            sources.add(new CommandLineConfigSource(cmdLine));
+            sources.add(new JettyHomeConfigSource(home.toPath()));
+            sources.add(new JettyBaseConfigSource(base.toPath()));
+            
+            Assert.fail("Should have thrown a UsageException");
+        }
+        catch (UsageException e)
+        {
+            Assert.assertThat("UsageException",e.getMessage(),containsString("Duplicate"));
+        }
+    }
+}
diff --git a/jetty-start/src/test/resources/assert-home-with-jvm.txt b/jetty-start/src/test/resources/assert-home-with-jvm.txt
new file mode 100644
index 0000000..9774d97
--- /dev/null
+++ b/jetty-start/src/test/resources/assert-home-with-jvm.txt
@@ -0,0 +1,44 @@
+# The XMLs we expect (order is important)
+XML|${jetty.base}/etc/jetty-jmx.xml
+XML|${jetty.base}/etc/jetty.xml
+XML|${jetty.base}/etc/jetty-http.xml
+XML|${jetty.base}/etc/jetty-plus.xml
+XML|${jetty.base}/etc/jetty-annotations.xml
+XML|${jetty.base}/etc/jetty-websockets.xml
+XML|${jetty.base}/etc/jetty-logging.xml
+
+# The LIBs we expect (order is irrelevant)
+LIB|${jetty.base}/lib/annotations/javax.annotation-api-1.2.jar
+LIB|${jetty.base}/lib/annotations/org.objectweb.asm-TEST.jar
+LIB|${jetty.base}/lib/jetty-annotations-TEST.jar
+LIB|${jetty.base}/lib/jetty-continuation-TEST.jar
+LIB|${jetty.base}/lib/jetty-http-TEST.jar
+LIB|${jetty.base}/lib/jetty-io-TEST.jar
+LIB|${jetty.base}/lib/jetty-jmx-TEST.jar
+LIB|${jetty.base}/lib/jetty-jndi-TEST.jar
+LIB|${jetty.base}/lib/jetty-plus-TEST.jar
+LIB|${jetty.base}/lib/jetty-schemas-3.1.jar
+LIB|${jetty.base}/lib/jetty-security-TEST.jar
+LIB|${jetty.base}/lib/jetty-server-TEST.jar
+LIB|${jetty.base}/lib/jetty-util-TEST.jar
+LIB|${jetty.base}/lib/jetty-xml-TEST.jar
+LIB|${jetty.base}/lib/jndi/javax.activation-1.1.jar
+LIB|${jetty.base}/lib/jndi/javax.transaction-api-1.2.jar
+LIB|${jetty.base}/lib/servlet-api-3.1.jar
+LIB|${jetty.base}/lib/websocket/javax.websocket-api-1.0.jar
+LIB|${jetty.base}/lib/websocket/javax-websocket-client-impl-TEST.jar
+LIB|${jetty.base}/lib/websocket/javax-websocket-server-impl-TEST.jar
+LIB|${jetty.base}/lib/websocket/websocket-api-TEST.jar
+LIB|${jetty.base}/lib/websocket/websocket-client-TEST.jar
+LIB|${jetty.base}/lib/websocket/websocket-common-TEST.jar
+LIB|${jetty.base}/lib/websocket/websocket-server-TEST.jar
+LIB|${jetty.base}/lib/websocket/websocket-servlet-TEST.jar
+LIB|${maven-test-resources}/extra-resources
+LIB|${maven-test-resources}/extra-libs/example.jar
+
+# The Properties we expect (order is irrelevant)
+# PROP|jetty.port=9090
+
+# JVM Args
+JVM|-Xms1024m
+JVM|-Xmx1024m
diff --git a/jetty-start/src/test/resources/assert-home-with-spaces.txt b/jetty-start/src/test/resources/assert-home-with-spaces.txt
new file mode 100644
index 0000000..d63fcce
--- /dev/null
+++ b/jetty-start/src/test/resources/assert-home-with-spaces.txt
@@ -0,0 +1,11 @@
+# The XMLs we expect (order is important)
+# No XMLs in this home
+
+# The LIBs we expect (order is irrelevant)
+LIB|${jetty.base}/lib/example of a library with spaces.jar
+
+# The Properties we expect (order is irrelevant)
+PROP|test.message=Hello
+
+# JVM Args
+# no jvm args
diff --git a/jetty-start/src/test/resources/assert-home-with-spdy.txt b/jetty-start/src/test/resources/assert-home-with-spdy.txt
new file mode 100644
index 0000000..aa5aea9
--- /dev/null
+++ b/jetty-start/src/test/resources/assert-home-with-spdy.txt
@@ -0,0 +1,72 @@
+# The XMLs we expect (order is important)
+XML|${jetty.base}/etc/jetty-jmx.xml
+XML|${jetty.base}/etc/protonego-alpn.xml
+XML|${jetty.base}/etc/jetty.xml
+XML|${jetty.base}/etc/jetty-http.xml
+XML|${jetty.base}/etc/jetty-ssl.xml
+XML|${jetty.base}/etc/jetty-plus.xml
+XML|${jetty.base}/etc/jetty-spdy.xml
+XML|${jetty.base}/etc/jetty-annotations.xml
+XML|${jetty.base}/etc/jetty-deploy.xml
+XML|${jetty.base}/etc/jetty-websockets.xml
+
+# The LIBs we expect (order is irrelevant)
+LIB|${jetty.base}/lib/annotations/javax.annotation-api-1.2.jar
+LIB|${jetty.base}/lib/annotations/org.objectweb.asm-TEST.jar
+LIB|${jetty.base}/lib/jetty-annotations-TEST.jar
+LIB|${jetty.base}/lib/jetty-continuation-TEST.jar
+LIB|${jetty.base}/lib/jetty-http-TEST.jar
+LIB|${jetty.base}/lib/jetty-io-TEST.jar
+LIB|${jetty.base}/lib/jetty-deploy-TEST.jar
+LIB|${jetty.base}/lib/jetty-jmx-TEST.jar
+LIB|${jetty.base}/lib/jetty-jndi-TEST.jar
+LIB|${jetty.base}/lib/jetty-plus-TEST.jar
+LIB|${jetty.base}/lib/jetty-schemas-3.1.jar
+LIB|${jetty.base}/lib/jetty-security-TEST.jar
+LIB|${jetty.base}/lib/jetty-server-TEST.jar
+LIB|${jetty.base}/lib/jetty-servlet-TEST.jar
+LIB|${jetty.base}/lib/jetty-util-TEST.jar
+LIB|${jetty.base}/lib/jetty-webapp-TEST.jar
+LIB|${jetty.base}/lib/jetty-xml-TEST.jar
+LIB|${jetty.base}/lib/spdy/spdy-client-TEST.jar
+LIB|${jetty.base}/lib/spdy/spdy-core-TEST.jar
+LIB|${jetty.base}/lib/spdy/spdy-http-common-TEST.jar
+LIB|${jetty.base}/lib/spdy/spdy-http-server-TEST.jar
+LIB|${jetty.base}/lib/spdy/spdy-server-TEST.jar
+LIB|${jetty.base}/lib/jndi/javax.activation-1.1.jar
+LIB|${jetty.base}/lib/jndi/javax.transaction-api-1.2.jar
+LIB|${jetty.base}/lib/servlet-api-3.1.jar
+LIB|${jetty.base}/lib/websocket/javax.websocket-api-1.0.jar
+LIB|${jetty.base}/lib/websocket/javax-websocket-client-impl-TEST.jar
+LIB|${jetty.base}/lib/websocket/javax-websocket-server-impl-TEST.jar
+LIB|${jetty.base}/lib/websocket/websocket-api-TEST.jar
+LIB|${jetty.base}/lib/websocket/websocket-client-TEST.jar
+LIB|${jetty.base}/lib/websocket/websocket-common-TEST.jar
+LIB|${jetty.base}/lib/websocket/websocket-server-TEST.jar
+LIB|${jetty.base}/lib/websocket/websocket-servlet-TEST.jar
+
+# The Properties we expect (order is irrelevant)
+PROP|java.version=1.7.0_60
+PROP|jetty.keymanager.password=OBF:1u2u1wml1z7s1z7a1wnl1u2g
+PROP|jetty.keystore=etc/keystore
+PROP|jetty.keystore.password=OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4
+PROP|jetty.secure.port=8443
+PROP|jetty.truststore=etc/keystore
+PROP|jetty.truststore.password=OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4
+PROP|protonego=alpn
+PROP|spdy.port=8443
+PROP|spdy.timeout=30000
+
+# JVM Args
+JVM|-Xms1024m
+JVM|-Xmx1024m
+
+# Downloads
+DOWNLOAD|http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/7.1.0.v20141016/alpn-boot-7.1.0.v20141016.jar|lib/alpn/alpn-boot-7.1.0.v20141016.jar
+DOWNLOAD|http://git.eclipse.org/c/jetty/org.eclipse.jetty.project.git/plain/jetty-server/src/main/config/etc/keystore|etc/keystore
+
+# Files
+FILE|lib/
+FILE|lib/alpn/
+
+
diff --git a/jetty-start/src/test/resources/assert-home.txt b/jetty-start/src/test/resources/assert-home.txt
new file mode 100644
index 0000000..5be8d46
--- /dev/null
+++ b/jetty-start/src/test/resources/assert-home.txt
@@ -0,0 +1,37 @@
+# The XMLs we expect (order is important)
+XML|${jetty.base}/etc/jetty-jmx.xml
+XML|${jetty.base}/etc/jetty.xml
+XML|${jetty.base}/etc/jetty-http.xml
+XML|${jetty.base}/etc/jetty-plus.xml
+XML|${jetty.base}/etc/jetty-annotations.xml
+XML|${jetty.base}/etc/jetty-websockets.xml
+
+# The LIBs we expect (order is irrelevant)
+LIB|${jetty.base}/lib/annotations/javax.annotation-api-1.2.jar
+LIB|${jetty.base}/lib/annotations/org.objectweb.asm-TEST.jar
+LIB|${jetty.base}/lib/jetty-annotations-TEST.jar
+LIB|${jetty.base}/lib/jetty-continuation-TEST.jar
+LIB|${jetty.base}/lib/jetty-http-TEST.jar
+LIB|${jetty.base}/lib/jetty-io-TEST.jar
+LIB|${jetty.base}/lib/jetty-jmx-TEST.jar
+LIB|${jetty.base}/lib/jetty-jndi-TEST.jar
+LIB|${jetty.base}/lib/jetty-plus-TEST.jar
+LIB|${jetty.base}/lib/jetty-schemas-3.1.jar
+LIB|${jetty.base}/lib/jetty-security-TEST.jar
+LIB|${jetty.base}/lib/jetty-server-TEST.jar
+LIB|${jetty.base}/lib/jetty-util-TEST.jar
+LIB|${jetty.base}/lib/jetty-xml-TEST.jar
+LIB|${jetty.base}/lib/jndi/javax.activation-1.1.jar
+LIB|${jetty.base}/lib/jndi/javax.transaction-api-1.2.jar
+LIB|${jetty.base}/lib/servlet-api-3.1.jar
+LIB|${jetty.base}/lib/websocket/javax.websocket-api-1.0.jar
+LIB|${jetty.base}/lib/websocket/javax-websocket-client-impl-TEST.jar
+LIB|${jetty.base}/lib/websocket/javax-websocket-server-impl-TEST.jar
+LIB|${jetty.base}/lib/websocket/websocket-api-TEST.jar
+LIB|${jetty.base}/lib/websocket/websocket-client-TEST.jar
+LIB|${jetty.base}/lib/websocket/websocket-common-TEST.jar
+LIB|${jetty.base}/lib/websocket/websocket-server-TEST.jar
+LIB|${jetty.base}/lib/websocket/websocket-servlet-TEST.jar
+
+# The Properties we expect (order is irrelevant)
+PROP|jetty.port=9090
diff --git a/jetty-start/src/test/resources/bad-libs/no-manifest.jar b/jetty-start/src/test/resources/bad-libs/no-manifest.jar
new file mode 100644
index 0000000..b5f275e
Binary files /dev/null and b/jetty-start/src/test/resources/bad-libs/no-manifest.jar differ
diff --git a/jetty-start/src/test/resources/bad-libs/not-a.jar b/jetty-start/src/test/resources/bad-libs/not-a.jar
new file mode 100644
index 0000000..498e45a
Binary files /dev/null and b/jetty-start/src/test/resources/bad-libs/not-a.jar differ
diff --git a/jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP b/jetty-start/src/test/resources/bad-libs/zero-length.jar
similarity index 100%
rename from jetty-start/src/test/resources/jetty.home/lib/JSR.ZIP
rename to jetty-start/src/test/resources/bad-libs/zero-length.jar
diff --git a/jetty-start/src/test/resources/bogus.xml b/jetty-start/src/test/resources/bogus.xml
new file mode 100644
index 0000000..6660498
--- /dev/null
+++ b/jetty-start/src/test/resources/bogus.xml
@@ -0,0 +1,2 @@
+<?xml version="1.0"?>
+<Configure />
\ No newline at end of file
diff --git a/jetty-start/src/test/resources/jetty.home/lib/LOGGING.JAR b/jetty-start/src/test/resources/dist-home/etc/example-quickstart.xml
similarity index 100%
rename from jetty-start/src/test/resources/jetty.home/lib/LOGGING.JAR
rename to jetty-start/src/test/resources/dist-home/etc/example-quickstart.xml
diff --git a/jetty-start/src/test/resources/jetty.home/lib/example.jar b/jetty-start/src/test/resources/dist-home/etc/hawtio.xml
similarity index 100%
copy from jetty-start/src/test/resources/jetty.home/lib/example.jar
copy to jetty-start/src/test/resources/dist-home/etc/hawtio.xml
diff --git a/jetty-start/src/test/resources/jetty.home/lib/ext/custom-impl.jar b/jetty-start/src/test/resources/dist-home/etc/home-base-warning.xml
similarity index 100%
rename from jetty-start/src/test/resources/jetty.home/lib/ext/custom-impl.jar
rename to jetty-start/src/test/resources/dist-home/etc/home-base-warning.xml
diff --git a/jetty-start/src/test/resources/jetty.home/lib/spec.zip b/jetty-start/src/test/resources/dist-home/etc/jamon.xml
similarity index 100%
rename from jetty-start/src/test/resources/jetty.home/lib/spec.zip
rename to jetty-start/src/test/resources/dist-home/etc/jamon.xml
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/etc/jetty-annotations.xml
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/etc/jetty-annotations.xml
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/etc/jetty-cdi.xml
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/etc/jetty-cdi.xml
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/etc/jetty-debug.xml
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/etc/jetty-debug.xml
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/etc/jetty-deploy.xml
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/etc/jetty-deploy.xml
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/etc/jetty-http.xml
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/etc/jetty-http.xml
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/etc/jetty-https.xml
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/etc/jetty-https.xml
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/etc/jetty-ipaccess.xml
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/etc/jetty-ipaccess.xml
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/etc/jetty-jaas.xml
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/etc/jetty-jaas.xml
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/etc/jetty-jmx-remote.xml
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/etc/jetty-jmx-remote.xml
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/etc/jetty-jmx.xml
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/etc/jetty-jmx.xml
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/etc/jetty-logging.xml
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/etc/jetty-logging.xml
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/etc/jetty-lowresources.xml
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/etc/jetty-lowresources.xml
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/etc/jetty-monitor.xml
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/etc/jetty-monitor.xml
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/etc/jetty-plus.xml
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/etc/jetty-plus.xml
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/etc/jetty-proxy.xml
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/etc/jetty-proxy.xml
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/etc/jetty-requestlog.xml
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/etc/jetty-requestlog.xml
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/etc/jetty-rewrite.xml
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/etc/jetty-rewrite.xml
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/etc/jetty-setuid.xml
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/etc/jetty-setuid.xml
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/etc/jetty-spdy-proxy.xml
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/etc/jetty-spdy-proxy.xml
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/etc/jetty-spdy.xml
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/etc/jetty-spdy.xml
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/etc/jetty-spring.xml
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/etc/jetty-spring.xml
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/etc/jetty-ssl.xml
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/etc/jetty-ssl.xml
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/etc/jetty-started.xml
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/etc/jetty-started.xml
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/etc/jetty-stats.xml
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/etc/jetty-stats.xml
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/etc/jetty-xinetd.xml
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/etc/jetty-xinetd.xml
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/etc/jetty.xml
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/etc/jetty.xml
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/etc/jminix.xml
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/etc/jminix.xml
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/etc/jolokia.xml
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/etc/jolokia.xml
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/etc/protonego-alpn.xml
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/etc/protonego-alpn.xml
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/etc/protonego-npn.xml
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/etc/protonego-npn.xml
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/etc/webdefault.xml
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/etc/webdefault.xml
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/lib/annotations/asm-5.0.1.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/lib/annotations/asm-5.0.1.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/lib/annotations/asm-commons-5.0.1.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/lib/annotations/asm-commons-5.0.1.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/lib/annotations/javax.annotation-api-1.2.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/lib/annotations/javax.annotation-api-1.2.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/lib/apache-jsp/org.eclipse.jetty.apache-jsp-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/lib/apache-jsp/org.eclipse.jetty.apache-jsp-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/lib/apache-jsp/org.eclipse.jetty.orbit.org.eclipse.jdt.core-3.8.2.v20130121.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/lib/apache-jsp/org.eclipse.jetty.orbit.org.eclipse.jdt.core-3.8.2.v20130121.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/lib/apache-jsp/org.mortbay.jasper.apache-el-8.0.9.M3.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/lib/apache-jsp/org.mortbay.jasper.apache-el-8.0.9.M3.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/lib/apache-jsp/org.mortbay.jasper.apache-jsp-8.0.9.M3.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/lib/apache-jsp/org.mortbay.jasper.apache-jsp-8.0.9.M3.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/lib/apache-jstl/org.apache.taglibs.taglibs-standard-impl-1.2.1.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/lib/apache-jstl/org.apache.taglibs.taglibs-standard-impl-1.2.1.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/lib/apache-jstl/org.apache.taglibs.taglibs-standard-spec-1.2.1.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/lib/apache-jstl/org.apache.taglibs.taglibs-standard-spec-1.2.1.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/lib/fcgi/fcgi-client-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/lib/fcgi/fcgi-client-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/lib/fcgi/fcgi-server-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/lib/fcgi/fcgi-server-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/lib/jaspi/javax.security.auth.message-1.0.0.v201108011116.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/lib/jaspi/javax.security.auth.message-1.0.0.v201108011116.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/lib/jetty-alpn-client-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/lib/jetty-alpn-client-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/lib/jetty-alpn-server-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/lib/jetty-alpn-server-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/lib/jetty-annotations-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/lib/jetty-annotations-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/lib/jetty-cdi-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/lib/jetty-cdi-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/lib/jetty-client-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/lib/jetty-client-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/lib/jetty-continuation-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/lib/jetty-continuation-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/lib/jetty-deploy-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/lib/jetty-deploy-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/lib/jetty-http-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/lib/jetty-http-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/lib/jetty-io-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/lib/jetty-io-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/lib/jetty-jaas-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/lib/jetty-jaas-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/lib/jetty-jaspi-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/lib/jetty-jaspi-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/lib/jetty-jmx-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/lib/jetty-jmx-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/lib/jetty-jndi-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/lib/jetty-jndi-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/lib/jetty-plus-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/lib/jetty-plus-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/lib/jetty-proxy-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/lib/jetty-proxy-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/lib/jetty-quickstart-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/lib/jetty-quickstart-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/lib/jetty-rewrite-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/lib/jetty-rewrite-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/lib/jetty-schemas-3.1.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/lib/jetty-schemas-3.1.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/lib/jetty-security-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/lib/jetty-security-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/lib/jetty-server-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/lib/jetty-server-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/lib/jetty-servlet-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/lib/jetty-servlet-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/lib/jetty-servlets-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/lib/jetty-servlets-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/lib/jetty-util-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/lib/jetty-util-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/lib/jetty-webapp-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/lib/jetty-webapp-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/lib/jetty-xml-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/lib/jetty-xml-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/lib/jndi/javax.mail.glassfish-1.4.1.v201005082020.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/lib/jndi/javax.mail.glassfish-1.4.1.v201005082020.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/lib/jndi/javax.transaction-api-1.2.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/lib/jndi/javax.transaction-api-1.2.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/lib/jsp/javax.el-3.0.0.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/lib/jsp/javax.el-3.0.0.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/lib/jsp/javax.servlet.jsp-2.3.2.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/lib/jsp/javax.servlet.jsp-2.3.2.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/lib/jsp/javax.servlet.jsp-api-2.3.1.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/lib/jsp/javax.servlet.jsp-api-2.3.1.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/lib/jsp/javax.servlet.jsp.jstl-1.2.2.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/lib/jsp/javax.servlet.jsp.jstl-1.2.2.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/lib/jsp/jetty-jsp-jdt-2.3.3.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/lib/jsp/jetty-jsp-jdt-2.3.3.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/lib/jsp/org.eclipse.jdt.core-3.8.2.v20130121.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/lib/jsp/org.eclipse.jdt.core-3.8.2.v20130121.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/lib/jsp/org.eclipse.jetty.orbit.javax.servlet.jsp.jstl-1.2.0.v201105211821.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/lib/jsp/org.eclipse.jetty.orbit.javax.servlet.jsp.jstl-1.2.0.v201105211821.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/lib/monitor/jetty-monitor-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/lib/monitor/jetty-monitor-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/lib/servlet-api-3.1.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/lib/servlet-api-3.1.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/lib/setuid/jetty-setuid-java-1.0.1.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/lib/setuid/jetty-setuid-java-1.0.1.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/lib/spdy/spdy-client-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/lib/spdy/spdy-client-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/lib/spdy/spdy-core-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/lib/spdy/spdy-core-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/lib/spdy/spdy-http-common-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/lib/spdy/spdy-http-common-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/lib/spdy/spdy-http-server-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/lib/spdy/spdy-http-server-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/lib/spdy/spdy-server-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/lib/spdy/spdy-server-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/lib/spring/jetty-spring-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/lib/spring/jetty-spring-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/lib/websocket/javax-websocket-client-impl-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/lib/websocket/javax-websocket-client-impl-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/lib/websocket/javax-websocket-server-impl-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/lib/websocket/javax-websocket-server-impl-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/lib/websocket/javax.websocket-api-1.0.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/lib/websocket/javax.websocket-api-1.0.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/lib/websocket/websocket-api-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/lib/websocket/websocket-api-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/lib/websocket/websocket-client-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/lib/websocket/websocket-client-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/lib/websocket/websocket-common-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/lib/websocket/websocket-common-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/lib/websocket/websocket-server-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/lib/websocket/websocket-server-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/dist-home/lib/websocket/websocket-servlet-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/dist-home/lib/websocket/websocket-servlet-TEST.jar
diff --git a/jetty-start/src/test/resources/dist-home/modules/annotations.mod b/jetty-start/src/test/resources/dist-home/modules/annotations.mod
new file mode 100644
index 0000000..65e4654
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/annotations.mod
@@ -0,0 +1,17 @@
+#
+# Jetty Annotation Scanning Module
+#
+
+[depend]
+# Annotations needs plus, and jndi features
+plus
+
+[lib]
+# Annotations needs jetty annotation jars
+lib/jetty-annotations-${jetty.version}.jar
+# Need annotation processing jars too
+lib/annotations/*.jar
+
+[xml]
+# Enable annotation scanning webapp configurations
+etc/jetty-annotations.xml
diff --git a/jetty-start/src/test/resources/dist-home/modules/cdi.mod b/jetty-start/src/test/resources/dist-home/modules/cdi.mod
new file mode 100644
index 0000000..68dea58
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/cdi.mod
@@ -0,0 +1,26 @@
+#
+# CDI / Weld Jetty module
+#
+
+[depend]
+deploy
+annotations
+plus
+# JSP (and EL) are requirements for CDI and Weld
+jsp
+
+[files]
+lib/weld/
+http://central.maven.org/maven2/org/jboss/weld/servlet/weld-servlet/2.2.5.Final/weld-servlet-2.2.5.Final.jar|lib/weld/weld-servlet-2.2.5.Final.jar
+
+[lib]
+lib/weld/weld-servlet-2.2.5.Final.jar
+lib/jetty-cdi-${jetty.version}.jar
+
+[xml]
+etc/jetty-cdi.xml
+
+[license]
+Weld is an open source project hosted on Github and released under the Apache 2.0 license.
+http://weld.cdi-spec.org/
+http://www.apache.org/licenses/LICENSE-2.0.html
diff --git a/jetty-start/src/test/resources/dist-home/modules/client.mod b/jetty-start/src/test/resources/dist-home/modules/client.mod
new file mode 100644
index 0000000..39b58d4
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/client.mod
@@ -0,0 +1,6 @@
+#
+# Client Feature
+#
+
+[lib]
+lib/jetty-client-${jetty.version}.jar
diff --git a/jetty-start/src/test/resources/dist-home/modules/continuation.mod b/jetty-start/src/test/resources/dist-home/modules/continuation.mod
new file mode 100644
index 0000000..231c09d
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/continuation.mod
@@ -0,0 +1,6 @@
+#
+# Classic Jetty Continuation Support Module
+#
+
+[lib]
+lib/jetty-continuation-${jetty.version}.jar
diff --git a/jetty-start/src/test/resources/dist-home/modules/debug.mod b/jetty-start/src/test/resources/dist-home/modules/debug.mod
new file mode 100644
index 0000000..f740ea2
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/debug.mod
@@ -0,0 +1,9 @@
+#
+# Debug module
+#
+
+[depend]
+server
+
+[xml]
+etc/jetty-debug.xml
diff --git a/jetty-start/src/test/resources/dist-home/modules/deploy.mod b/jetty-start/src/test/resources/dist-home/modules/deploy.mod
new file mode 100644
index 0000000..f16b3f2
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/deploy.mod
@@ -0,0 +1,21 @@
+#
+# Deploy Feature
+#
+
+[depend]
+webapp
+
+[lib]
+lib/jetty-deploy-${jetty.version}.jar
+
+[files]
+webapps/
+
+[xml]
+etc/jetty-deploy.xml
+
+[ini-template]
+## DeployManager configuration
+# Monitored Directory name (relative to jetty.base)
+# jetty.deploy.monitoredDirName=webapps
+
diff --git a/jetty-start/src/test/resources/dist-home/modules/ext.mod b/jetty-start/src/test/resources/dist-home/modules/ext.mod
new file mode 100644
index 0000000..56b10f7
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/ext.mod
@@ -0,0 +1,11 @@
+#
+# Module to add all lib/ext/**.jar files to classpath
+#
+
+[lib]
+lib/ext/**.jar
+
+[files]
+lib/
+lib/ext/
+
diff --git a/jetty-start/src/test/resources/dist-home/modules/fcgi.mod b/jetty-start/src/test/resources/dist-home/modules/fcgi.mod
new file mode 100644
index 0000000..e837b00
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/fcgi.mod
@@ -0,0 +1,15 @@
+#
+# FastCGI Module
+#
+
+[depend]
+servlet
+client
+
+[lib]
+lib/jetty-proxy-${jetty.version}.jar
+lib/fcgi/*.jar
+
+[ini-template]
+## For configuration of FastCGI contexts, see
+## TODO: documentation url here
diff --git a/jetty-start/src/test/resources/dist-home/modules/hawtio.mod b/jetty-start/src/test/resources/dist-home/modules/hawtio.mod
new file mode 100644
index 0000000..2dfb31b
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/hawtio.mod
@@ -0,0 +1,28 @@
+#
+# Hawtio x module
+#
+
+[depend]
+stats
+deploy
+jmx
+
+[xml]
+etc/hawtio.xml
+
+[files]
+etc/hawtio/
+lib/hawtio/
+https://oss.sonatype.org/content/repositories/public/io/hawt/hawtio-default/1.4.16/hawtio-default-1.4.16.war|lib/hawtio/hawtio.war
+
+[license]
+Hawtio is a redhat JBoss project released under the Apache License, v2.0
+http://hawt.io/
+http://github.com/hawtio/hawtio
+http://www.apache.org/licenses/LICENSE-2.0.html
+
+[ini-template]
+
+-Dhawtio.authenticationEnabled=false
+-Dhawtio.dirname=/dirname
+-Dhawtio.config.dir=${jetty.base}/etc/hawtio
diff --git a/jetty-start/src/test/resources/dist-home/modules/home-base-warning.mod b/jetty-start/src/test/resources/dist-home/modules/home-base-warning.mod
new file mode 100644
index 0000000..28e5757
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/home-base-warning.mod
@@ -0,0 +1,7 @@
+#
+# Home and Base Warning
+#
+
+[xml]
+etc/home-base-warning.xml
+
diff --git a/jetty-start/src/test/resources/dist-home/modules/http.mod b/jetty-start/src/test/resources/dist-home/modules/http.mod
new file mode 100644
index 0000000..dc34bc3
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/http.mod
@@ -0,0 +1,27 @@
+#
+# Jetty HTTP Connector
+#
+
+[depend]
+server
+
+[xml]
+etc/jetty-http.xml
+
+[ini-template]
+### HTTP Connector Configuration
+
+## HTTP port to listen on
+jetty.port=8080
+
+## HTTP idle timeout in milliseconds
+http.timeout=30000
+
+## HTTP Socket.soLingerTime in seconds. (-1 to disable)
+# http.soLingerTime=-1
+
+## Parameters to control the number and priority of acceptors and selectors
+# http.selectors=1
+# http.acceptors=1
+# http.selectorPriorityDelta=0
+# http.acceptorPriorityDelta=0
diff --git a/jetty-start/src/test/resources/dist-home/modules/https.mod b/jetty-start/src/test/resources/dist-home/modules/https.mod
new file mode 100644
index 0000000..bd1b718
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/https.mod
@@ -0,0 +1,19 @@
+#
+# Jetty HTTPS Connector
+#
+
+[depend]
+ssl
+
+[xml]
+etc/jetty-https.xml
+
+[ini-template]
+## HTTPS Configuration
+# HTTP port to listen on
+https.port=8443
+# HTTPS idle timeout in milliseconds
+https.timeout=30000
+# HTTPS Socket.soLingerTime in seconds. (-1 to disable)
+# https.soLingerTime=-1
+
diff --git a/jetty-start/src/test/resources/dist-home/modules/ipaccess.mod b/jetty-start/src/test/resources/dist-home/modules/ipaccess.mod
new file mode 100644
index 0000000..956ea0f
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/ipaccess.mod
@@ -0,0 +1,9 @@
+#
+# IPAccess module
+#
+
+[depend]
+server
+
+[xml]
+etc/jetty-ipaccess.xml
diff --git a/jetty-start/src/test/resources/dist-home/modules/jaas.mod b/jetty-start/src/test/resources/dist-home/modules/jaas.mod
new file mode 100644
index 0000000..4932140
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/jaas.mod
@@ -0,0 +1,16 @@
+#
+# JAAS Module
+#
+
+[depend]
+server
+
+[lib]
+lib/jetty-jaas-${jetty.version}.jar
+
+[xml]
+etc/jetty-jaas.xml
+
+[ini-template]
+## JAAS Configuration
+jaas.login.conf=etc/login.conf
diff --git a/jetty-start/src/test/resources/dist-home/modules/jamon.mod b/jetty-start/src/test/resources/dist-home/modules/jamon.mod
new file mode 100644
index 0000000..2aeb2ad
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/jamon.mod
@@ -0,0 +1,30 @@
+#
+# JAMon Jetty module
+#
+
+[depend]
+stats
+deploy
+jmx
+jsp
+
+[xml]
+etc/jamon.xml
+
+[files]
+lib/jamon/
+http://central.maven.org/maven2/com/jamonapi/jamon/2.79/jamon-2.79.jar|lib/jamon/jamon-2.79.jar
+http://central.maven.org/maven2/com/jamonapi/jamon_war/2.79/jamon_war-2.79.war|lib/jamon/jamon.war
+
+[lib]
+lib/jamon/**.jar
+
+[license]
+JAMon is a source forge hosted project released under a BSD derived license.
+http://jamonapi.sourceforge.net
+http://jamonapi.sourceforge.net/JAMonLicense.html
+
+[ini-template]
+jamon.summaryLabels=default, request.getStatus().contextpath.value.ms
+#jamon.summaryLabels=demo
+
diff --git a/jetty-start/src/test/resources/dist-home/modules/jaspi.mod b/jetty-start/src/test/resources/dist-home/modules/jaspi.mod
new file mode 100644
index 0000000..e7019ae
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/jaspi.mod
@@ -0,0 +1,10 @@
+#
+# Jetty JASPI Module
+#
+
+[depend]
+security
+
+[lib]
+lib/jetty-jaspi-${jetty.version}.jar
+lib/jaspi/*.jar
diff --git a/jetty-start/src/test/resources/dist-home/modules/jminix.mod b/jetty-start/src/test/resources/dist-home/modules/jminix.mod
new file mode 100644
index 0000000..b35d702
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/jminix.mod
@@ -0,0 +1,41 @@
+#
+# JaMON Jetty module
+#
+
+[depend]
+stats
+jmx
+
+[xml]
+etc/jminix.xml
+
+[files]
+lib/jminix/
+http://central.maven.org/maven2/org/jminix/jminix/1.1.0/jminix-1.1.0.jar|lib/jminix/jminix-1.1.0.jar
+http://maven.restlet.com/org/restlet/org.restlet/1.1.5/org.restlet-1.1.5.jar|lib/jminix/org.restlet-1.1.5.jar
+http://maven.restlet.com/org/restlet/org.restlet.ext.velocity/1.1.5/org.restlet.ext.velocity-1.1.5.jar|lib/jminix/org.restlet.ext.velocity-1.1.5.jar
+http://central.maven.org/maven2/org/apache/velocity/velocity/1.5/velocity-1.5.jar|lib/jminix/velocity-1.5.jar
+http://central.maven.org/maven2/oro/oro/2.0.8/oro-2.0.8.jar|lib/jminix/oro-2.0.8.jar
+http://maven.restlet.com/com/noelios/restlet/com.noelios.restlet/1.1.5/com.noelios.restlet-1.1.5.jar|lib/jminix/com.noelios.restlet-1.1.5.jar
+http://maven.restlet.com/com/noelios/restlet/com.noelios.restlet.ext.servlet/1.1.5/com.noelios.restlet.ext.servlet-1.1.5.jar|lib/jminix/com.noelios.restlet.ext.servlet-1.1.5.jar
+http://central.maven.org/maven2/commons-logging/commons-logging/1.1.1/commons-logging-1.1.1.jar|lib/jminix/commons-logging-1.1.1.jar
+http://repo2.maven.org/maven2/net/sf/json-lib/json-lib/2.2.3/json-lib-2.2.3-jdk15.jar|lib/jminix/json-lib-2.2.3-jdk15.jar
+http://central.maven.org/maven2/commons-lang/commons-lang/2.4/commons-lang-2.4.jar|lib/jminix/commons-lang-2.4.jar
+http://central.maven.org/maven2/commons-beanutils/commons-beanutils/1.7.0/commons-beanutils-1.7.0.jar|lib/jminix/commons-beanutils-1.7.0.jar
+http://central.maven.org/maven2/commons-collections/commons-collections/3.2/commons-collections-3.2.jar|lib/jminix/commons-collections-3.2.jar
+http://central.maven.org/maven2/net/sf/ezmorph/ezmorph/1.0.6/ezmorph-1.0.6.jar|lib/jminix/ezmorph-1.0.6.jar
+http://central.maven.org/maven2/org/jgroups/jgroups/2.12.1.3.Final/jgroups-2.12.1.3.Final.jar|lib/jminix/jgroups-2.12.1.3.Final.jar
+http://central.maven.org/maven2/org/jasypt/jasypt/1.8/jasypt-1.8.jar|lib/jminix/jasypt-1.8.jar
+
+[lib]
+lib/jminix/**.jar
+
+[license]
+JMiniX is a hosted at google code and released under the Apache License 2.0
+https://code.google.com/p/jminix/
+http://www.apache.org/licenses/LICENSE-2.0
+
+[ini-template]
+# Jminix Configuration
+jminix.port=8088
+
diff --git a/jetty-start/src/test/resources/dist-home/modules/jmx-remote.mod b/jetty-start/src/test/resources/dist-home/modules/jmx-remote.mod
new file mode 100644
index 0000000..b6be74a
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/jmx-remote.mod
@@ -0,0 +1,18 @@
+#
+# JMX Remote Module
+#
+
+[depend]
+jmx
+
+[xml]
+etc/jetty-jmx-remote.xml
+
+[ini-template]
+## JMX Configuration
+## Enable for an open port accessible by remote machines
+# jetty.jmxrmihost=localhost
+# jetty.jmxrmiport=1099
+## Strictly speaking you shouldn't need --exec to use this in most environments.
+## If this isn't working, make sure you enable --exec as well
+# -Dcom.sun.management.jmxremote
diff --git a/jetty-start/src/test/resources/dist-home/modules/jmx.mod b/jetty-start/src/test/resources/dist-home/modules/jmx.mod
new file mode 100644
index 0000000..ee091c7
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/jmx.mod
@@ -0,0 +1,13 @@
+#
+# JMX Module
+#
+
+[depend]
+server
+
+[lib]
+lib/jetty-jmx-${jetty.version}.jar
+
+[xml]
+etc/jetty-jmx.xml
+
diff --git a/jetty-start/src/test/resources/dist-home/modules/jndi.mod b/jetty-start/src/test/resources/dist-home/modules/jndi.mod
new file mode 100644
index 0000000..33c077c
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/jndi.mod
@@ -0,0 +1,11 @@
+#
+# JNDI Support
+#
+
+[depend]
+server
+
+[lib]
+lib/jetty-jndi-${jetty.version}.jar
+lib/jndi/*.jar
+
diff --git a/jetty-start/src/test/resources/dist-home/modules/jolokia.mod b/jetty-start/src/test/resources/dist-home/modules/jolokia.mod
new file mode 100644
index 0000000..876c2fc
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/jolokia.mod
@@ -0,0 +1,19 @@
+#
+# Jolokia Jetty module
+#
+
+[depend]
+stats
+deploy
+jmx
+
+[xml]
+etc/jolokia.xml
+
+[files]
+http://repo1.maven.org/maven2/org/jolokia/jolokia-war/1.2.2/jolokia-war-1.2.2.war|lib/jolokia/jolokia.war
+
+[license]
+Jolokia is released under the Apache License 2.0
+http://www.jolokia.org
+http://www.apache.org/licenses/LICENSE-2.0
diff --git a/jetty-start/src/test/resources/dist-home/modules/jsp-impl/apache-jsp.mod b/jetty-start/src/test/resources/dist-home/modules/jsp-impl/apache-jsp.mod
new file mode 100644
index 0000000..aed547c
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/jsp-impl/apache-jsp.mod
@@ -0,0 +1,10 @@
+#
+# Apache JSP Module
+#
+
+[name]
+jsp-impl
+
+[lib]
+lib/apache-jsp/*.jar
+
diff --git a/jetty-start/src/test/resources/dist-home/modules/jsp-impl/apache-jstl.mod b/jetty-start/src/test/resources/dist-home/modules/jsp-impl/apache-jstl.mod
new file mode 100644
index 0000000..804b191
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/jsp-impl/apache-jstl.mod
@@ -0,0 +1,8 @@
+#
+# Apache JSTL 
+#
+[name]
+jstl-impl
+
+[lib]
+lib/apache-jstl/*.jar
diff --git a/jetty-start/src/test/resources/dist-home/modules/jsp-impl/glassfish-jsp.mod b/jetty-start/src/test/resources/dist-home/modules/jsp-impl/glassfish-jsp.mod
new file mode 100644
index 0000000..130d2b3
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/jsp-impl/glassfish-jsp.mod
@@ -0,0 +1,8 @@
+#
+# Glassfish JSP Module
+#
+[name]
+jsp-impl
+
+[lib]
+lib/jsp/*.jar
diff --git a/jetty-start/src/test/resources/dist-home/modules/jsp-impl/glassfish-jstl.mod b/jetty-start/src/test/resources/dist-home/modules/jsp-impl/glassfish-jstl.mod
new file mode 100644
index 0000000..4b8e6f3
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/jsp-impl/glassfish-jstl.mod
@@ -0,0 +1,6 @@
+#
+# Glassfish JSTL
+[name]
+jstl-impl
+
+# This file is empty as glassfish jstl is provided by glassfish jsp
diff --git a/jetty-start/src/test/resources/dist-home/modules/jsp.mod b/jetty-start/src/test/resources/dist-home/modules/jsp.mod
new file mode 100644
index 0000000..bb31ca7
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/jsp.mod
@@ -0,0 +1,21 @@
+#
+# Jetty JSP Module
+#
+
+[depend]
+servlet
+annotations
+jsp-impl/${jsp-impl}-jsp
+
+[ini-template]
+# JSP Configuration
+
+# Select JSP implementation, choices are
+#   glassfish : The reference implementation 
+#               default in jetty <= 9.1
+#   apache    : The apache version 
+#               default jetty >= 9.2
+jsp-impl=apache
+
+# To use a non-jdk compiler for JSP compilation when using glassfish uncomment next line
+# -Dorg.apache.jasper.compiler.disablejsr199=true
diff --git a/jetty-start/src/test/resources/dist-home/modules/jstl.mod b/jetty-start/src/test/resources/dist-home/modules/jstl.mod
new file mode 100644
index 0000000..cb06244
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/jstl.mod
@@ -0,0 +1,14 @@
+#
+# Jetty JSP Module
+#
+
+[depend]
+jsp
+jsp-impl/${jsp-impl}-jstl
+
+[ini-template]
+# JSTL Configuration
+# The glassfish jsp-impl includes JSTL by default and this module
+# is not required to activate it.
+# The apache jsp-impl does not include JSTL by default and this module
+# is required to put JSTL on the container classpath
diff --git a/jetty-start/src/test/resources/dist-home/modules/jvm.mod b/jetty-start/src/test/resources/dist-home/modules/jvm.mod
new file mode 100644
index 0000000..195521c
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/jvm.mod
@@ -0,0 +1,23 @@
+[ini-template]
+## JVM Configuration
+## If JVM args are include in an ini file then --exec is needed
+## to start a new JVM from start.jar with the extra args.
+##
+## If you wish to avoid an extra JVM running, place JVM args
+## on the normal command line and do not use --exec
+# --exec
+# -Xmx2000m
+# -Xmn512m
+# -XX:+UseConcMarkSweepGC
+# -XX:ParallelCMSThreads=2
+# -XX:+CMSClassUnloadingEnabled
+# -XX:+UseCMSCompactAtFullCollection
+# -XX:CMSInitiatingOccupancyFraction=80
+# -verbose:gc
+# -XX:+PrintGCDateStamps
+# -XX:+PrintGCTimeStamps
+# -XX:+PrintGCDetails
+# -XX:+PrintTenuringDistribution
+# -XX:+PrintCommandLineFlags
+# -XX:+DisableExplicitGC
+# -Dorg.apache.jasper.compiler.disablejsr199=true
diff --git a/jetty-start/src/test/resources/dist-home/modules/logging.mod b/jetty-start/src/test/resources/dist-home/modules/logging.mod
new file mode 100644
index 0000000..a39bfe4
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/logging.mod
@@ -0,0 +1,31 @@
+#
+# Jetty std err/out logging
+#
+
+[xml]
+etc/jetty-logging.xml
+
+[files]
+logs/
+
+[lib]
+lib/logging/**.jar
+resources/
+
+[ini-template]
+## Logging Configuration
+# Configure jetty logging for default internal behavior STDERR output
+# -Dorg.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
+
+# Configure jetty logging for slf4j
+# -Dorg.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.Slf4jLog
+
+# Configure jetty logging for java.util.logging
+# -Dorg.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.JavaUtilLog
+
+# STDERR / STDOUT Logging
+# Number of days to retain logs
+# jetty.log.retain=90
+# Directory for logging output
+# Either a path relative to ${jetty.base} or an absolute path
+# jetty.logs=logs
diff --git a/jetty-start/src/test/resources/dist-home/modules/lowresources.mod b/jetty-start/src/test/resources/dist-home/modules/lowresources.mod
new file mode 100644
index 0000000..99112d5
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/lowresources.mod
@@ -0,0 +1,18 @@
+#
+# Low Resources module
+#
+
+[depend]
+server
+
+[xml]
+etc/jetty-lowresources.xml
+
+[ini-template]
+## Low Resources Configuration
+# lowresources.period=1050
+# lowresources.lowResourcesIdleTimeout=200
+# lowresources.monitorThreads=true
+# lowresources.maxConnections=0
+# lowresources.maxMemory=0
+# lowresources.maxLowResourcesTime=5000
diff --git a/jetty-start/src/test/resources/dist-home/modules/monitor.mod b/jetty-start/src/test/resources/dist-home/modules/monitor.mod
new file mode 100644
index 0000000..09132c7
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/monitor.mod
@@ -0,0 +1,13 @@
+#
+# Jetty Monitor module
+#
+
+[depend]
+server
+client
+
+[lib]
+lib/monitor/jetty-monitor-${jetty.version}.jar
+
+[xml]
+etc/jetty-monitor.xml
diff --git a/jetty-start/src/test/resources/dist-home/modules/plus.mod b/jetty-start/src/test/resources/dist-home/modules/plus.mod
new file mode 100644
index 0000000..aac0f8f
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/plus.mod
@@ -0,0 +1,15 @@
+#
+# Jetty Plus module
+#
+
+[depend]
+server
+security
+jndi
+webapp
+
+[lib]
+lib/jetty-plus-${jetty.version}.jar
+
+[xml]
+etc/jetty-plus.xml
diff --git a/jetty-start/src/test/resources/dist-home/modules/protonego-impl/alpn-1.7.0_40.mod b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/alpn-1.7.0_40.mod
new file mode 100644
index 0000000..54d3731
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/alpn-1.7.0_40.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/7.1.0.v20141016/alpn-boot-7.1.0.v20141016.jar|lib/alpn/alpn-boot-7.1.0.v20141016.jar
+
+[exec]
+-Xbootclasspath/p:lib/alpn/alpn-boot-7.1.0.v20141016.jar
diff --git a/jetty-start/src/test/resources/dist-home/modules/protonego-impl/alpn-1.7.0_45.mod b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/alpn-1.7.0_45.mod
new file mode 100644
index 0000000..54d3731
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/alpn-1.7.0_45.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/7.1.0.v20141016/alpn-boot-7.1.0.v20141016.jar|lib/alpn/alpn-boot-7.1.0.v20141016.jar
+
+[exec]
+-Xbootclasspath/p:lib/alpn/alpn-boot-7.1.0.v20141016.jar
diff --git a/jetty-start/src/test/resources/dist-home/modules/protonego-impl/alpn-1.7.0_51.mod b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/alpn-1.7.0_51.mod
new file mode 100644
index 0000000..54d3731
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/alpn-1.7.0_51.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/7.1.0.v20141016/alpn-boot-7.1.0.v20141016.jar|lib/alpn/alpn-boot-7.1.0.v20141016.jar
+
+[exec]
+-Xbootclasspath/p:lib/alpn/alpn-boot-7.1.0.v20141016.jar
diff --git a/jetty-start/src/test/resources/dist-home/modules/protonego-impl/alpn-1.7.0_55.mod b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/alpn-1.7.0_55.mod
new file mode 100644
index 0000000..54d3731
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/alpn-1.7.0_55.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/7.1.0.v20141016/alpn-boot-7.1.0.v20141016.jar|lib/alpn/alpn-boot-7.1.0.v20141016.jar
+
+[exec]
+-Xbootclasspath/p:lib/alpn/alpn-boot-7.1.0.v20141016.jar
diff --git a/jetty-start/src/test/resources/dist-home/modules/protonego-impl/alpn-1.7.0_60.mod b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/alpn-1.7.0_60.mod
new file mode 100644
index 0000000..54d3731
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/alpn-1.7.0_60.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/7.1.0.v20141016/alpn-boot-7.1.0.v20141016.jar|lib/alpn/alpn-boot-7.1.0.v20141016.jar
+
+[exec]
+-Xbootclasspath/p:lib/alpn/alpn-boot-7.1.0.v20141016.jar
diff --git a/jetty-start/src/test/resources/dist-home/modules/protonego-impl/alpn-1.7.0_65.mod b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/alpn-1.7.0_65.mod
new file mode 100644
index 0000000..54d3731
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/alpn-1.7.0_65.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/7.1.0.v20141016/alpn-boot-7.1.0.v20141016.jar|lib/alpn/alpn-boot-7.1.0.v20141016.jar
+
+[exec]
+-Xbootclasspath/p:lib/alpn/alpn-boot-7.1.0.v20141016.jar
diff --git a/jetty-start/src/test/resources/dist-home/modules/protonego-impl/alpn-1.7.0_67.mod b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/alpn-1.7.0_67.mod
new file mode 100644
index 0000000..54d3731
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/alpn-1.7.0_67.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/7.1.0.v20141016/alpn-boot-7.1.0.v20141016.jar|lib/alpn/alpn-boot-7.1.0.v20141016.jar
+
+[exec]
+-Xbootclasspath/p:lib/alpn/alpn-boot-7.1.0.v20141016.jar
diff --git a/jetty-start/src/test/resources/dist-home/modules/protonego-impl/alpn-1.7.0_71.mod b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/alpn-1.7.0_71.mod
new file mode 100644
index 0000000..e9b4e2a
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/alpn-1.7.0_71.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/7.1.2.v20141202/alpn-boot-7.1.2.v20141202.jar|lib/alpn/alpn-boot-7.1.2.v20141202.jar
+
+[exec]
+-Xbootclasspath/p:lib/alpn/alpn-boot-7.1.2.v20141202.jar
diff --git a/jetty-start/src/test/resources/dist-home/modules/protonego-impl/alpn-1.7.0_72.mod b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/alpn-1.7.0_72.mod
new file mode 100644
index 0000000..e9b4e2a
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/alpn-1.7.0_72.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/7.1.2.v20141202/alpn-boot-7.1.2.v20141202.jar|lib/alpn/alpn-boot-7.1.2.v20141202.jar
+
+[exec]
+-Xbootclasspath/p:lib/alpn/alpn-boot-7.1.2.v20141202.jar
diff --git a/jetty-start/src/test/resources/dist-home/modules/protonego-impl/alpn-1.7.0_75.mod b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/alpn-1.7.0_75.mod
new file mode 100644
index 0000000..ac315d6
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/alpn-1.7.0_75.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/7.1.3.v20150130/alpn-boot-7.1.3.v20150130.jar|lib/alpn/alpn-boot-7.1.3.v20150130.jar
+
+[exec]
+-Xbootclasspath/p:lib/alpn/alpn-boot-7.1.3.v20150130.jar
diff --git a/jetty-start/src/test/resources/dist-home/modules/protonego-impl/alpn-1.7.0_76.mod b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/alpn-1.7.0_76.mod
new file mode 100644
index 0000000..ac315d6
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/alpn-1.7.0_76.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/7.1.3.v20150130/alpn-boot-7.1.3.v20150130.jar|lib/alpn/alpn-boot-7.1.3.v20150130.jar
+
+[exec]
+-Xbootclasspath/p:lib/alpn/alpn-boot-7.1.3.v20150130.jar
diff --git a/jetty-start/src/test/resources/dist-home/modules/protonego-impl/alpn-1.7.0_79.mod b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/alpn-1.7.0_79.mod
new file mode 100644
index 0000000..ac315d6
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/alpn-1.7.0_79.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/7.1.3.v20150130/alpn-boot-7.1.3.v20150130.jar|lib/alpn/alpn-boot-7.1.3.v20150130.jar
+
+[exec]
+-Xbootclasspath/p:lib/alpn/alpn-boot-7.1.3.v20150130.jar
diff --git a/jetty-start/src/test/resources/dist-home/modules/protonego-impl/alpn-1.7.0_80.mod b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/alpn-1.7.0_80.mod
new file mode 100644
index 0000000..ac315d6
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/alpn-1.7.0_80.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/7.1.3.v20150130/alpn-boot-7.1.3.v20150130.jar|lib/alpn/alpn-boot-7.1.3.v20150130.jar
+
+[exec]
+-Xbootclasspath/p:lib/alpn/alpn-boot-7.1.3.v20150130.jar
diff --git a/jetty-start/src/test/resources/dist-home/modules/protonego-impl/alpn-1.8.0.mod b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/alpn-1.8.0.mod
new file mode 100644
index 0000000..a81732c
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/alpn-1.8.0.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/8.1.0.v20141016/alpn-boot-8.1.0.v20141016.jar|lib/alpn/alpn-boot-8.1.0.v20141016.jar
+
+[exec]
+-Xbootclasspath/p:lib/alpn/alpn-boot-8.1.0.v20141016.jar
diff --git a/jetty-start/src/test/resources/dist-home/modules/protonego-impl/alpn-1.8.0_05.mod b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/alpn-1.8.0_05.mod
new file mode 100644
index 0000000..a81732c
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/alpn-1.8.0_05.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/8.1.0.v20141016/alpn-boot-8.1.0.v20141016.jar|lib/alpn/alpn-boot-8.1.0.v20141016.jar
+
+[exec]
+-Xbootclasspath/p:lib/alpn/alpn-boot-8.1.0.v20141016.jar
diff --git a/jetty-start/src/test/resources/dist-home/modules/protonego-impl/alpn-1.8.0_11.mod b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/alpn-1.8.0_11.mod
new file mode 100644
index 0000000..a81732c
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/alpn-1.8.0_11.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/8.1.0.v20141016/alpn-boot-8.1.0.v20141016.jar|lib/alpn/alpn-boot-8.1.0.v20141016.jar
+
+[exec]
+-Xbootclasspath/p:lib/alpn/alpn-boot-8.1.0.v20141016.jar
diff --git a/jetty-start/src/test/resources/dist-home/modules/protonego-impl/alpn-1.8.0_20.mod b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/alpn-1.8.0_20.mod
new file mode 100644
index 0000000..a81732c
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/alpn-1.8.0_20.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/8.1.0.v20141016/alpn-boot-8.1.0.v20141016.jar|lib/alpn/alpn-boot-8.1.0.v20141016.jar
+
+[exec]
+-Xbootclasspath/p:lib/alpn/alpn-boot-8.1.0.v20141016.jar
diff --git a/jetty-start/src/test/resources/dist-home/modules/protonego-impl/alpn-1.8.0_25.mod b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/alpn-1.8.0_25.mod
new file mode 100644
index 0000000..8d13261
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/alpn-1.8.0_25.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/8.1.2.v20141202/alpn-boot-8.1.2.v20141202.jar|lib/alpn/alpn-boot-8.1.2.v20141202.jar
+
+[exec]
+-Xbootclasspath/p:lib/alpn/alpn-boot-8.1.2.v20141202.jar
diff --git a/jetty-start/src/test/resources/dist-home/modules/protonego-impl/alpn-1.8.0_31.mod b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/alpn-1.8.0_31.mod
new file mode 100644
index 0000000..4114979
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/alpn-1.8.0_31.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/8.1.3.v20150130/alpn-boot-8.1.3.v20150130.jar|lib/alpn/alpn-boot-8.1.3.v20150130.jar
+
+[exec]
+-Xbootclasspath/p:lib/alpn/alpn-boot-8.1.3.v20150130.jar
diff --git a/jetty-start/src/test/resources/dist-home/modules/protonego-impl/alpn-1.8.0_40.mod b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/alpn-1.8.0_40.mod
new file mode 100644
index 0000000..4114979
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/alpn-1.8.0_40.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/8.1.3.v20150130/alpn-boot-8.1.3.v20150130.jar|lib/alpn/alpn-boot-8.1.3.v20150130.jar
+
+[exec]
+-Xbootclasspath/p:lib/alpn/alpn-boot-8.1.3.v20150130.jar
diff --git a/jetty-start/src/test/resources/dist-home/modules/protonego-impl/alpn-1.8.0_45.mod b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/alpn-1.8.0_45.mod
new file mode 100644
index 0000000..4114979
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/alpn-1.8.0_45.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/8.1.3.v20150130/alpn-boot-8.1.3.v20150130.jar|lib/alpn/alpn-boot-8.1.3.v20150130.jar
+
+[exec]
+-Xbootclasspath/p:lib/alpn/alpn-boot-8.1.3.v20150130.jar
diff --git a/jetty-start/src/test/resources/dist-home/modules/protonego-impl/alpn-1.8.0_51.mod b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/alpn-1.8.0_51.mod
new file mode 100644
index 0000000..ddc18d7
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/alpn-1.8.0_51.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/8.1.4.v20150727/alpn-boot-8.1.4.v20150727.jar|lib/alpn/alpn-boot-8.1.4.v20150727.jar
+
+[exec]
+-Xbootclasspath/p:lib/alpn/alpn-boot-8.1.4.v20150727.jar
diff --git a/jetty-start/src/test/resources/dist-home/modules/protonego-impl/alpn.mod b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/alpn.mod
new file mode 100644
index 0000000..d3e4118
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/alpn.mod
@@ -0,0 +1,42 @@
+# ALPN is provided via a -Xbootclasspath that modifies the secure connections
+# in java to support the ALPN layer needed for SPDY (and eventually HTTP/2)
+#
+# This modification has a tight dependency on specific recent updates of
+# Java 1.7 and Java 1.8
+# (Java versions prior to 1.7u40 are not supported)
+#
+# The alpn protonego module will use an appropriate alpn-boot jar for your
+# specific version of Java.
+#
+# IMPORTANT: Versions of Java that exist after this module was created are
+#            not guaranteed to work with existing alpn-boot jars, and might
+#            need a new alpn-boot to be created / tested / deployed by the
+#            Jetty project in order to provide support for these future
+#            Java versions.
+#
+# All versions of alpn-boot can be found at
+# http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/
+
+[name]
+protonego-impl
+
+[depend]
+protonego-impl/alpn-${java.version}
+
+[lib]
+lib/jetty-alpn-client-${jetty.version}.jar
+lib/jetty-alpn-server-${jetty.version}.jar
+
+[xml]
+etc/protonego-alpn.xml
+
+[files]
+lib/
+lib/alpn/
+
+[license]
+ALPN is a hosted at github under the GPL v2 with ClassPath Exception.
+ALPN replaces/modifies OpenJDK classes in the java.sun.security.ssl package.
+http://github.com/jetty-project/jetty-alpn
+http://openjdk.java.net/legal/gplv2+ce.html
+
diff --git a/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn-1.7.0_04.mod b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn-1.7.0_04.mod
new file mode 100644
index 0000000..007570b
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn-1.7.0_04.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.0.v20120525/npn-boot-1.1.0.v20120525.jar|lib/npn/npn-boot-1.1.0.v20120525.jar
+
+[exec]
+-Xbootclasspath/p:lib/npn/npn-boot-1.1.0.v20120525.jar
diff --git a/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn-1.7.0_05.mod b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn-1.7.0_05.mod
new file mode 100644
index 0000000..007570b
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn-1.7.0_05.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.0.v20120525/npn-boot-1.1.0.v20120525.jar|lib/npn/npn-boot-1.1.0.v20120525.jar
+
+[exec]
+-Xbootclasspath/p:lib/npn/npn-boot-1.1.0.v20120525.jar
diff --git a/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn-1.7.0_06.mod b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn-1.7.0_06.mod
new file mode 100644
index 0000000..868a7a7
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn-1.7.0_06.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.1.v20121030/npn-boot-1.1.1.v20121030.jar|lib/npn/npn-boot-1.1.1.v20121030.jar
+
+[exec]
+-Xbootclasspath/p:lib/npn/npn-boot-1.1.1.v20121030.jar
diff --git a/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn-1.7.0_07.mod b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn-1.7.0_07.mod
new file mode 100644
index 0000000..868a7a7
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn-1.7.0_07.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.1.v20121030/npn-boot-1.1.1.v20121030.jar|lib/npn/npn-boot-1.1.1.v20121030.jar
+
+[exec]
+-Xbootclasspath/p:lib/npn/npn-boot-1.1.1.v20121030.jar
diff --git a/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn-1.7.0_09.mod b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn-1.7.0_09.mod
new file mode 100644
index 0000000..20c1db2
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn-1.7.0_09.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.3.v20130313/npn-boot-1.1.3.v20130313.jar|lib/npn/npn-boot-1.1.3.v20130313.jar
+
+[exec]
+-Xbootclasspath/p:lib/npn/npn-boot-1.1.3.v20130313.jar
diff --git a/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn-1.7.0_10.mod b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn-1.7.0_10.mod
new file mode 100644
index 0000000..20c1db2
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn-1.7.0_10.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.3.v20130313/npn-boot-1.1.3.v20130313.jar|lib/npn/npn-boot-1.1.3.v20130313.jar
+
+[exec]
+-Xbootclasspath/p:lib/npn/npn-boot-1.1.3.v20130313.jar
diff --git a/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn-1.7.0_11.mod b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn-1.7.0_11.mod
new file mode 100644
index 0000000..20c1db2
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn-1.7.0_11.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.3.v20130313/npn-boot-1.1.3.v20130313.jar|lib/npn/npn-boot-1.1.3.v20130313.jar
+
+[exec]
+-Xbootclasspath/p:lib/npn/npn-boot-1.1.3.v20130313.jar
diff --git a/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn-1.7.0_13.mod b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn-1.7.0_13.mod
new file mode 100644
index 0000000..1645a52
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn-1.7.0_13.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.4.v20130313/npn-boot-1.1.4.v20130313.jar|lib/npn/npn-boot-1.1.4.v20130313.jar
+
+[exec]
+-Xbootclasspath/p:lib/npn/npn-boot-1.1.4.v20130313.jar
diff --git a/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn-1.7.0_15.mod b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn-1.7.0_15.mod
new file mode 100644
index 0000000..73bc090
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn-1.7.0_15.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.5.v20130313/npn-boot-1.1.5.v20130313.jar|lib/npn/npn-boot-1.1.5.v20130313.jar
+
+[exec]
+-Xbootclasspath/p:lib/npn/npn-boot-1.1.5.v20130313.jar
diff --git a/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn-1.7.0_17.mod b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn-1.7.0_17.mod
new file mode 100644
index 0000000..73bc090
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn-1.7.0_17.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.5.v20130313/npn-boot-1.1.5.v20130313.jar|lib/npn/npn-boot-1.1.5.v20130313.jar
+
+[exec]
+-Xbootclasspath/p:lib/npn/npn-boot-1.1.5.v20130313.jar
diff --git a/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn-1.7.0_21.mod b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn-1.7.0_21.mod
new file mode 100644
index 0000000..73bc090
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn-1.7.0_21.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.5.v20130313/npn-boot-1.1.5.v20130313.jar|lib/npn/npn-boot-1.1.5.v20130313.jar
+
+[exec]
+-Xbootclasspath/p:lib/npn/npn-boot-1.1.5.v20130313.jar
diff --git a/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn-1.7.0_25.mod b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn-1.7.0_25.mod
new file mode 100644
index 0000000..73bc090
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn-1.7.0_25.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.5.v20130313/npn-boot-1.1.5.v20130313.jar|lib/npn/npn-boot-1.1.5.v20130313.jar
+
+[exec]
+-Xbootclasspath/p:lib/npn/npn-boot-1.1.5.v20130313.jar
diff --git a/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn-1.7.0_40.mod b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn-1.7.0_40.mod
new file mode 100644
index 0000000..465e6f0
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn-1.7.0_40.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.6.v20130911/npn-boot-1.1.6.v20130911.jar|lib/npn/npn-boot-1.1.6.v20130911.jar
+
+[exec]
+-Xbootclasspath/p:lib/npn/npn-boot-1.1.6.v20130911.jar
diff --git a/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn-1.7.0_45.mod b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn-1.7.0_45.mod
new file mode 100644
index 0000000..465e6f0
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn-1.7.0_45.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.6.v20130911/npn-boot-1.1.6.v20130911.jar|lib/npn/npn-boot-1.1.6.v20130911.jar
+
+[exec]
+-Xbootclasspath/p:lib/npn/npn-boot-1.1.6.v20130911.jar
diff --git a/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn-1.7.0_51.mod b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn-1.7.0_51.mod
new file mode 100644
index 0000000..465e6f0
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn-1.7.0_51.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.6.v20130911/npn-boot-1.1.6.v20130911.jar|lib/npn/npn-boot-1.1.6.v20130911.jar
+
+[exec]
+-Xbootclasspath/p:lib/npn/npn-boot-1.1.6.v20130911.jar
diff --git a/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn-1.7.0_55.mod b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn-1.7.0_55.mod
new file mode 100644
index 0000000..5f8704d
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn-1.7.0_55.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.8.v20141013/npn-boot-1.1.8.v20141013.jar|lib/npn/npn-boot-1.1.8.v20141013.jar
+
+[exec]
+-Xbootclasspath/p:lib/npn/npn-boot-1.1.8.v20141013.jar
diff --git a/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn-1.7.0_60.mod b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn-1.7.0_60.mod
new file mode 100644
index 0000000..5f8704d
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn-1.7.0_60.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.8.v20141013/npn-boot-1.1.8.v20141013.jar|lib/npn/npn-boot-1.1.8.v20141013.jar
+
+[exec]
+-Xbootclasspath/p:lib/npn/npn-boot-1.1.8.v20141013.jar
diff --git a/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn-1.7.0_65.mod b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn-1.7.0_65.mod
new file mode 100644
index 0000000..5f8704d
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn-1.7.0_65.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.8.v20141013/npn-boot-1.1.8.v20141013.jar|lib/npn/npn-boot-1.1.8.v20141013.jar
+
+[exec]
+-Xbootclasspath/p:lib/npn/npn-boot-1.1.8.v20141013.jar
diff --git a/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn-1.7.0_67.mod b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn-1.7.0_67.mod
new file mode 100644
index 0000000..5f8704d
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn-1.7.0_67.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.8.v20141013/npn-boot-1.1.8.v20141013.jar|lib/npn/npn-boot-1.1.8.v20141013.jar
+
+[exec]
+-Xbootclasspath/p:lib/npn/npn-boot-1.1.8.v20141013.jar
diff --git a/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn-1.7.0_71.mod b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn-1.7.0_71.mod
new file mode 100644
index 0000000..851aca8
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn-1.7.0_71.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.9.v20141016/npn-boot-1.1.9.v20141016.jar|lib/npn/npn-boot-1.1.9.v20141016.jar
+
+[exec]
+-Xbootclasspath/p:lib/npn/npn-boot-1.1.9.v20141016.jar
diff --git a/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn-1.7.0_72.mod b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn-1.7.0_72.mod
new file mode 100644
index 0000000..851aca8
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn-1.7.0_72.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.9.v20141016/npn-boot-1.1.9.v20141016.jar|lib/npn/npn-boot-1.1.9.v20141016.jar
+
+[exec]
+-Xbootclasspath/p:lib/npn/npn-boot-1.1.9.v20141016.jar
diff --git a/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn-1.7.0_75.mod b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn-1.7.0_75.mod
new file mode 100644
index 0000000..a5fac11
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn-1.7.0_75.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.10.v20150130/npn-boot-1.1.10.v20150130.jar|lib/npn/npn-boot-1.1.10.v20150130.jar
+
+[exec]
+-Xbootclasspath/p:lib/npn/npn-boot-1.1.10.v20150130.jar
diff --git a/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn-1.7.0_76.mod b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn-1.7.0_76.mod
new file mode 100644
index 0000000..a5fac11
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn-1.7.0_76.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.10.v20150130/npn-boot-1.1.10.v20150130.jar|lib/npn/npn-boot-1.1.10.v20150130.jar
+
+[exec]
+-Xbootclasspath/p:lib/npn/npn-boot-1.1.10.v20150130.jar
diff --git a/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn-1.7.0_79.mod b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn-1.7.0_79.mod
new file mode 100644
index 0000000..a5fac11
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn-1.7.0_79.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.10.v20150130/npn-boot-1.1.10.v20150130.jar|lib/npn/npn-boot-1.1.10.v20150130.jar
+
+[exec]
+-Xbootclasspath/p:lib/npn/npn-boot-1.1.10.v20150130.jar
diff --git a/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn-1.7.0_80.mod b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn-1.7.0_80.mod
new file mode 100644
index 0000000..2cce5fa
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn-1.7.0_80.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.11.v20150415/npn-boot-1.1.11.v20150415.jar|lib/npn/npn-boot-1.1.11.v20150415.jar
+
+[exec]
+-Xbootclasspath/p:lib/npn/npn-boot-1.1.11.v20150415.jar
diff --git a/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn.mod b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn.mod
new file mode 100644
index 0000000..1a2c71d
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/protonego-impl/npn.mod
@@ -0,0 +1,37 @@
+# NPN is provided via a -Xbootclasspath that modifies the secure connections
+# in java to support the NPN layer needed for SPDY.
+#
+# This modification has a tight dependency on specific updates of Java 1.7.
+# (No support for Java 8 exists for npn / npn-boot, use alpn instead)
+#
+# The npn module will use an appropriate npn-boot jar for your specific
+# version of Java.
+#
+# IMPORTANT: Versions of Java that exist after this module was created are
+#            not guaranteed to work with existing npn-boot jars, and might
+#            need a new npn-boot to be created / tested / deployed by the
+#            Jetty project in order to provide support for these future
+#            Java versions.
+#
+# All versions of npn-boot can be found at
+# http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/
+
+
+[name]
+protonego-impl
+
+[depend]
+protonego-impl/npn-${java.version}
+
+[xml]
+etc/protonego-npn.xml
+
+[files]
+lib/
+lib/npn/
+
+[license]
+NPN is a hosted at github under the GPL v2 with ClassPath Exception.
+NPN replaces/modifies OpenJDK classes in the java.sun.security.ssl package.
+http://github.com/jetty-project/jetty-npn
+http://openjdk.java.net/legal/gplv2+ce.html
diff --git a/jetty-start/src/test/resources/dist-home/modules/protonego.mod b/jetty-start/src/test/resources/dist-home/modules/protonego.mod
new file mode 100644
index 0000000..fbf4d08
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/protonego.mod
@@ -0,0 +1,24 @@
+#
+# Protocol Negotiatin Selection Module
+#
+
+[depend]
+protonego-impl/${protonego}
+
+[ini-template]
+# Protocol Negotiation Implementation Selection
+#  choices are:
+#    'npn'  : original implementation for SPDY (now deprecated)
+#    'alpn' : replacement for NPN, in use by current SPDY implementations
+#             and the future HTTP/2 spec
+#  Note: java 1.8+ are ALPN only.
+protonego=alpn
+
+# Configuration for NPN
+# npn.protocols=spdy/3,http/1.1
+# npn.defaultProtocol=http/1.1
+
+# Configuration for ALPN
+# alpn.protocols=h2-14,http/1.1
+# alpn.defaultProtocol=http/1.1
+
diff --git a/jetty-start/src/test/resources/dist-home/modules/proxy.mod b/jetty-start/src/test/resources/dist-home/modules/proxy.mod
new file mode 100644
index 0000000..a879ae1
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/proxy.mod
@@ -0,0 +1,22 @@
+#
+# Jetty Proxy module
+#
+
+[depend]
+servlet
+client
+
+[lib]
+lib/jetty-proxy-${jetty.version}.jar
+
+[xml]
+etc/jetty-proxy.xml
+
+[ini-template]
+## Proxy Configuration
+#jetty.proxy.servletClass=org.eclipse.jetty.proxy.ProxyServlet
+#jetty.proxy.servletMapping=/*
+#jetty.proxy.maxThreads=128
+#jetty.proxy.maxConnections=256
+#jetty.proxy.idleTimeout=30000
+#jetty.proxy.timeout=60000
diff --git a/jetty-start/src/test/resources/dist-home/modules/quickstart.mod b/jetty-start/src/test/resources/dist-home/modules/quickstart.mod
new file mode 100644
index 0000000..89db9fd
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/quickstart.mod
@@ -0,0 +1,12 @@
+#
+# Jetty Quickstart module
+#
+
+[depend]
+server
+plus
+annotations
+
+
+[lib]
+lib/jetty-quickstart-${jetty.version}.jar
diff --git a/jetty-start/src/test/resources/dist-home/modules/requestlog.mod b/jetty-start/src/test/resources/dist-home/modules/requestlog.mod
new file mode 100644
index 0000000..f5e0614
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/requestlog.mod
@@ -0,0 +1,30 @@
+#
+# Request Log module
+#
+
+[depend]
+server
+
+[xml]
+etc/jetty-requestlog.xml
+
+[files]
+logs/
+
+[ini-template]
+## Request Log Configuration
+# Filename for Request Log output (relative to jetty.base)
+# requestlog.filename=/logs/yyyy_mm_dd.request.log
+# Date format for rollovered files (uses SimpleDateFormat syntax)
+# requestlog.filenameDateFormat=yyyy_MM_dd
+# How many days to retain the logs
+# requestlog.retain=90
+# If an existing log with the same name is found, just append to it
+# requestlog.append=true
+# Use the extended log output
+# requestlog.extended=true
+# Log http cookie information as well
+# requestlog.cookies=true
+# Set the log output timezone
+# requestlog.timezone=GMT
+
diff --git a/jetty-start/src/test/resources/dist-home/modules/resources.mod b/jetty-start/src/test/resources/dist-home/modules/resources.mod
new file mode 100644
index 0000000..8647d81
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/resources.mod
@@ -0,0 +1,10 @@
+#
+# Module to add resources directory to classpath
+#
+
+[lib]
+resources/
+
+[files]
+resources/
+
diff --git a/jetty-start/src/test/resources/dist-home/modules/rewrite.mod b/jetty-start/src/test/resources/dist-home/modules/rewrite.mod
new file mode 100644
index 0000000..d2e00c8
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/rewrite.mod
@@ -0,0 +1,12 @@
+#
+# Jetty Rewrite module
+#
+
+[depend]
+server
+
+[lib]
+lib/jetty-rewrite-${jetty.version}.jar
+
+[xml]
+etc/jetty-rewrite.xml
diff --git a/jetty-start/src/test/resources/dist-home/modules/security.mod b/jetty-start/src/test/resources/dist-home/modules/security.mod
new file mode 100644
index 0000000..ba31632
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/security.mod
@@ -0,0 +1,9 @@
+#
+# Jetty Security Module
+#
+
+[depend]
+server
+
+[lib]
+lib/jetty-security-${jetty.version}.jar
diff --git a/jetty-start/src/test/resources/dist-home/modules/server.mod b/jetty-start/src/test/resources/dist-home/modules/server.mod
new file mode 100644
index 0000000..3cdac35
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/server.mod
@@ -0,0 +1,49 @@
+#
+# Base Server Module
+#
+
+[optional]
+jvm
+ext
+resources
+
+[lib]
+lib/servlet-api-3.1.jar
+lib/jetty-schemas-3.1.jar
+lib/jetty-http-${jetty.version}.jar
+lib/jetty-server-${jetty.version}.jar
+lib/jetty-xml-${jetty.version}.jar
+lib/jetty-util-${jetty.version}.jar
+lib/jetty-io-${jetty.version}.jar
+
+[xml]
+etc/jetty.xml
+
+[ini-template]
+##
+## Server Threading Configuration
+##
+# minimum number of threads
+threads.min=10
+# maximum number of threads
+threads.max=200
+# thread idle timeout in milliseconds
+threads.timeout=60000
+# buffer size for output
+jetty.output.buffer.size=32768
+# request header buffer size
+jetty.request.header.size=8192
+# response header buffer size
+jetty.response.header.size=8192
+# should jetty send the server version header?
+jetty.send.server.version=true
+# should jetty send the date header?
+jetty.send.date.header=false
+# What host to listen on (leave commented to listen on all interfaces)
+#jetty.host=myhost.com
+# Dump the state of the Jetty server, components, and webapps after startup
+jetty.dump.start=false
+# Dump the state of the Jetty server, before stop
+jetty.dump.stop=false
+
+
diff --git a/jetty-start/src/test/resources/dist-home/modules/servlet.mod b/jetty-start/src/test/resources/dist-home/modules/servlet.mod
new file mode 100644
index 0000000..fdb65c5
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/servlet.mod
@@ -0,0 +1,9 @@
+#
+# Jetty Servlet Module
+#
+
+[depend]
+server
+
+[lib]
+lib/jetty-servlet-${jetty.version}.jar
diff --git a/jetty-start/src/test/resources/dist-home/modules/servlets.mod b/jetty-start/src/test/resources/dist-home/modules/servlets.mod
new file mode 100644
index 0000000..e8724b8
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/servlets.mod
@@ -0,0 +1,10 @@
+#
+# Jetty Servlets Module
+#
+
+[depend]
+servlet
+
+[lib]
+lib/jetty-servlets-${jetty.version}.jar
+
diff --git a/jetty-start/src/test/resources/dist-home/modules/setuid.mod b/jetty-start/src/test/resources/dist-home/modules/setuid.mod
new file mode 100644
index 0000000..64c9e23
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/setuid.mod
@@ -0,0 +1,19 @@
+#
+# Set UID Feature
+#
+
+[depend]
+server
+
+[lib]
+lib/setuid/jetty-setuid-java-1.0.1.jar
+
+[xml]
+etc/jetty-setuid.xml
+
+[ini-template]
+## SetUID Configuration
+# jetty.startServerAsPrivileged=false
+# jetty.username=jetty
+# jetty.groupname=jetty
+# jetty.umask=002
diff --git a/jetty-start/src/test/resources/dist-home/modules/spdy.mod b/jetty-start/src/test/resources/dist-home/modules/spdy.mod
new file mode 100644
index 0000000..cf79dfa
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/spdy.mod
@@ -0,0 +1,26 @@
+#
+# SPDY Support Module
+#
+
+[depend]
+ssl
+protonego
+
+[lib]
+lib/spdy/*.jar
+
+[xml]
+etc/jetty-ssl.xml
+etc/jetty-spdy.xml
+
+[ini-template]
+## SPDY Configuration
+
+# Port for SPDY connections
+spdy.port=8443
+
+# SPDY idle timeout in milliseconds
+spdy.timeout=30000
+
+# Initial Window Size for SPDY
+#spdy.initialWindowSize=65536
diff --git a/jetty-start/src/test/resources/dist-home/modules/spring.mod b/jetty-start/src/test/resources/dist-home/modules/spring.mod
new file mode 100644
index 0000000..444afb2
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/spring.mod
@@ -0,0 +1,16 @@
+#
+# Spring
+#
+[name]
+spring
+
+[depend]
+server
+
+[lib]
+lib/spring/*.jar
+
+[ini-template]
+## See http://www.eclipse.org/jetty/documentation/current/frameworks.html#framework-jetty-spring
+## for information on how to complete spring configuration
+
diff --git a/jetty-start/src/test/resources/dist-home/modules/ssl.mod b/jetty-start/src/test/resources/dist-home/modules/ssl.mod
new file mode 100644
index 0000000..ef47192
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/ssl.mod
@@ -0,0 +1,40 @@
+#
+# SSL Keystore module
+#
+
+[depend]
+server
+
+[xml]
+etc/jetty-ssl.xml
+
+[files]
+http://git.eclipse.org/c/jetty/org.eclipse.jetty.project.git/plain/jetty-server/src/main/config/etc/keystore|etc/keystore
+
+[ini-template]
+### SSL Keystore Configuration
+# define the port to use for secure redirection
+jetty.secure.port=8443
+
+## Setup a demonstration keystore and truststore
+jetty.keystore=etc/keystore
+jetty.truststore=etc/keystore
+
+## Set the demonstration passwords.
+## Note that OBF passwords are not secure, just protected from casual observation
+## See http://www.eclipse.org/jetty/documentation/current/configuring-security-secure-passwords.html
+jetty.keystore.password=OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4
+jetty.keymanager.password=OBF:1u2u1wml1z7s1z7a1wnl1u2g
+jetty.truststore.password=OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4
+
+### Set the client auth behavior
+## Set to true if client certificate authentication is required
+# jetty.ssl.needClientAuth=true
+## Set to true if client certificate authentication is desired
+# jetty.ssl.wantClientAuth=true
+
+## Parameters to control the number and priority of acceptors and selectors
+# ssl.selectors=1
+# ssl.acceptors=1
+# ssl.selectorPriorityDelta=0
+# ssl.acceptorPriorityDelta=0
diff --git a/jetty-start/src/test/resources/dist-home/modules/stats.mod b/jetty-start/src/test/resources/dist-home/modules/stats.mod
new file mode 100644
index 0000000..0922469
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/stats.mod
@@ -0,0 +1,9 @@
+#
+# Stats module
+#
+
+[depend]
+server
+
+[xml]
+etc/jetty-stats.xml
diff --git a/jetty-start/src/test/resources/dist-home/modules/webapp.mod b/jetty-start/src/test/resources/dist-home/modules/webapp.mod
new file mode 100644
index 0000000..6bb37ef
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/webapp.mod
@@ -0,0 +1,10 @@
+#
+# WebApp Support Module
+#
+
+[depend]
+servlet
+security
+
+[lib]
+lib/jetty-webapp-${jetty.version}.jar
diff --git a/jetty-start/src/test/resources/dist-home/modules/websocket.mod b/jetty-start/src/test/resources/dist-home/modules/websocket.mod
new file mode 100644
index 0000000..e866b17
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/websocket.mod
@@ -0,0 +1,12 @@
+#
+# WebSocket Module
+#
+
+[depend]
+# javax.websocket needs annotations
+annotations
+
+[lib]
+lib/websocket/*.jar
+
+
diff --git a/jetty-start/src/test/resources/dist-home/modules/xinetd.mod b/jetty-start/src/test/resources/dist-home/modules/xinetd.mod
new file mode 100644
index 0000000..e53618e
--- /dev/null
+++ b/jetty-start/src/test/resources/dist-home/modules/xinetd.mod
@@ -0,0 +1,17 @@
+#
+# Xinetd module
+#
+
+[depend]
+server
+
+[xml]
+etc/jetty-xinetd.xml
+
+[ini-template]
+## Xinetd Configuration
+## See ${jetty.home}/etc/jetty-xinetd.xml for example service entry
+jetty.xinetd.idleTimeout=300000
+jetty.xinetd.acceptors=2
+jetty.xinetd.statsOn=false
+
diff --git a/jetty-start/src/test/resources/empty.home/start.ini b/jetty-start/src/test/resources/empty.home/start.ini
new file mode 100644
index 0000000..e751e99
--- /dev/null
+++ b/jetty-start/src/test/resources/empty.home/start.ini
@@ -0,0 +1,3 @@
+#===========================================================
+# Empty start.ini
+#===========================================================
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/extra-jetty-dirs/logging/etc/jetty-logging.xml
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/extra-jetty-dirs/logging/etc/jetty-logging.xml
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/extra-jetty-dirs/logging/lib/logging/logback.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/extra-jetty-dirs/logging/lib/logging/logback.jar
diff --git a/jetty-start/src/test/resources/extra-jetty-dirs/logging/start.ini b/jetty-start/src/test/resources/extra-jetty-dirs/logging/start.ini
new file mode 100644
index 0000000..905d6db
--- /dev/null
+++ b/jetty-start/src/test/resources/extra-jetty-dirs/logging/start.ini
@@ -0,0 +1 @@
+--module=logging
\ No newline at end of file
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/extra-jetty-dirs/more-startd/start.d/more.ini
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/extra-jetty-dirs/more-startd/start.d/more.ini
diff --git a/jetty-start/src/test/resources/jetty.home/lib/example.jar b/jetty-start/src/test/resources/extra-libs/example.jar
similarity index 100%
rename from jetty-start/src/test/resources/jetty.home/lib/example.jar
rename to jetty-start/src/test/resources/extra-libs/example.jar
diff --git a/jetty-start/src/test/resources/jetty.home/resources/example.properties b/jetty-start/src/test/resources/extra-resources/example.properties
similarity index 100%
rename from jetty-start/src/test/resources/jetty.home/resources/example.properties
rename to jetty-start/src/test/resources/extra-resources/example.properties
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/hb.1/base/start.d/jmx.ini
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/hb.1/base/start.d/jmx.ini
diff --git a/jetty-start/src/test/resources/hb.1/base/start.d/logging.ini b/jetty-start/src/test/resources/hb.1/base/start.d/logging.ini
new file mode 100644
index 0000000..d648d1b
--- /dev/null
+++ b/jetty-start/src/test/resources/hb.1/base/start.d/logging.ini
@@ -0,0 +1 @@
+# Base Logging
\ No newline at end of file
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/hb.1/base/start.d/myapp.ini
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/hb.1/base/start.d/myapp.ini
diff --git a/jetty-start/src/test/resources/hb.1/base/start.ini b/jetty-start/src/test/resources/hb.1/base/start.ini
new file mode 100644
index 0000000..c0ebe8c
--- /dev/null
+++ b/jetty-start/src/test/resources/hb.1/base/start.ini
@@ -0,0 +1,7 @@
+#===========================================================
+# Base Ini
+#===========================================================
+
+OPTIONS=jmx
+etc/jetty-jmx.xml
+
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/hb.1/home/start.d/jmx.ini
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/hb.1/home/start.d/jmx.ini
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/hb.1/home/start.d/jndi.ini
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/hb.1/home/start.d/jndi.ini
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/hb.1/home/start.d/jsp.ini
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/hb.1/home/start.d/jsp.ini
diff --git a/jetty-start/src/test/resources/hb.1/home/start.d/logging.ini b/jetty-start/src/test/resources/hb.1/home/start.d/logging.ini
new file mode 100644
index 0000000..7161329
--- /dev/null
+++ b/jetty-start/src/test/resources/hb.1/home/start.d/logging.ini
@@ -0,0 +1 @@
+# Home Logging
\ No newline at end of file
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/hb.1/home/start.d/ssl.ini
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/hb.1/home/start.d/ssl.ini
diff --git a/jetty-start/src/test/resources/hb.1/home/start.ini b/jetty-start/src/test/resources/hb.1/home/start.ini
new file mode 100644
index 0000000..69a418e
--- /dev/null
+++ b/jetty-start/src/test/resources/hb.1/home/start.ini
@@ -0,0 +1,11 @@
+#===========================================================
+# Home Ini
+#===========================================================
+
+OPTIONS=Server,jsp,resources,websocket,ext
+etc/jetty.xml
+start.d/
+etc/jetty-deploy.xml
+etc/jetty-webapps.xml
+etc/jetty-contexts.xml
+
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/jetty home with spaces/lib/example of a library with spaces.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/jetty home with spaces/lib/example of a library with spaces.jar
diff --git a/jetty-start/src/test/resources/jetty home with spaces/modules/base.mod b/jetty-start/src/test/resources/jetty home with spaces/modules/base.mod
new file mode 100644
index 0000000..f14d52a
--- /dev/null
+++ b/jetty-start/src/test/resources/jetty home with spaces/modules/base.mod	
@@ -0,0 +1,2 @@
+[lib]
+lib/example*with spaces.jar
\ No newline at end of file
diff --git a/jetty-start/src/test/resources/jetty home with spaces/start.ini b/jetty-start/src/test/resources/jetty home with spaces/start.ini
new file mode 100644
index 0000000..ad2e4c2
--- /dev/null
+++ b/jetty-start/src/test/resources/jetty home with spaces/start.ini	
@@ -0,0 +1,6 @@
+#===========================================================
+# Empty start.ini
+#===========================================================
+
+--module=base
+test.message=Hello
diff --git a/jetty-start/src/test/resources/jetty.home/etc/test-jetty-security.xml b/jetty-start/src/test/resources/jetty.home/etc/test-jetty-security.xml
deleted file mode 100644
index 38c0208..0000000
--- a/jetty-start/src/test/resources/jetty.home/etc/test-jetty-security.xml
+++ /dev/null
@@ -1 +0,0 @@
-<!-- nothing in here, just used to test the start.config logic in ConfigTest.java -->
diff --git a/jetty-start/src/test/resources/jetty.home/etc/test-jetty-ssl.xml b/jetty-start/src/test/resources/jetty.home/etc/test-jetty-ssl.xml
deleted file mode 100644
index 38c0208..0000000
--- a/jetty-start/src/test/resources/jetty.home/etc/test-jetty-ssl.xml
+++ /dev/null
@@ -1 +0,0 @@
-<!-- nothing in here, just used to test the start.config logic in ConfigTest.java -->
diff --git a/jetty-start/src/test/resources/jetty.home/etc/test-jetty.xml b/jetty-start/src/test/resources/jetty.home/etc/test-jetty.xml
deleted file mode 100644
index 38c0208..0000000
--- a/jetty-start/src/test/resources/jetty.home/etc/test-jetty.xml
+++ /dev/null
@@ -1 +0,0 @@
-<!-- nothing in here, just used to test the start.config logic in ConfigTest.java -->
diff --git a/jetty-start/src/test/resources/jetty.home/lib/core.jar b/jetty-start/src/test/resources/jetty.home/lib/core.jar
deleted file mode 100644
index e6110ca..0000000
Binary files a/jetty-start/src/test/resources/jetty.home/lib/core.jar and /dev/null differ
diff --git a/jetty-start/src/test/resources/jetty.home/lib/foo/bar/foobar.jar b/jetty-start/src/test/resources/jetty.home/lib/foo/bar/foobar.jar
deleted file mode 100644
index a032b2d..0000000
Binary files a/jetty-start/src/test/resources/jetty.home/lib/foo/bar/foobar.jar and /dev/null differ
diff --git a/jetty-start/src/test/resources/jetty.home/lib/http.jar b/jetty-start/src/test/resources/jetty.home/lib/http.jar
deleted file mode 100644
index a032b2d..0000000
Binary files a/jetty-start/src/test/resources/jetty.home/lib/http.jar and /dev/null differ
diff --git a/jetty-start/src/test/resources/jetty.home/lib/io.jar b/jetty-start/src/test/resources/jetty.home/lib/io.jar
deleted file mode 100644
index 164dee4..0000000
Binary files a/jetty-start/src/test/resources/jetty.home/lib/io.jar and /dev/null differ
diff --git a/jetty-start/src/test/resources/jetty.home/lib/readme.txt b/jetty-start/src/test/resources/jetty.home/lib/readme.txt
deleted file mode 100644
index f27a277..0000000
--- a/jetty-start/src/test/resources/jetty.home/lib/readme.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-Many of these files are zero length on purpose.
-Of those jars that have content, a simple "Hello World" style class has been
-compiled (included) to allow the ConfigTest of available classes.
-This directory is used by the various tests, and having legitimate jar/zip files
-is often not important.
diff --git a/jetty-start/src/test/resources/jetty.home/lib/server.jar b/jetty-start/src/test/resources/jetty.home/lib/server.jar
deleted file mode 100644
index 67fb429..0000000
Binary files a/jetty-start/src/test/resources/jetty.home/lib/server.jar and /dev/null differ
diff --git a/jetty-start/src/test/resources/jetty.home/lib/util.jar b/jetty-start/src/test/resources/jetty.home/lib/util.jar
deleted file mode 100644
index 515acf6..0000000
Binary files a/jetty-start/src/test/resources/jetty.home/lib/util.jar and /dev/null differ
diff --git a/jetty-start/src/test/resources/jetty.home/lib/xml.jar b/jetty-start/src/test/resources/jetty.home/lib/xml.jar
deleted file mode 100644
index 9650bac..0000000
Binary files a/jetty-start/src/test/resources/jetty.home/lib/xml.jar and /dev/null differ
diff --git a/jetty-start/src/test/resources/jetty.home/start.d/10-jmx.ini b/jetty-start/src/test/resources/jetty.home/start.d/10-jmx.ini
deleted file mode 100644
index 827e41b..0000000
--- a/jetty-start/src/test/resources/jetty.home/start.d/10-jmx.ini
+++ /dev/null
@@ -1,22 +0,0 @@
-#===========================================================
-# Additional Jetty start.jar arguments
-# Each line of this file is prepended to the command line 
-# arguments # of a call to:
-#    java -jar start.jar [arg...]
-#===========================================================
-
-
-
-#===========================================================
-#-----------------------------------------------------------
-OPTIONS=jmx
-#-----------------------------------------------------------
-
-
-#===========================================================
-# Configuration files.
-# For a full list of available configuration files do
-#   java -jar start.jar --help
-#-----------------------------------------------------------
---pre=etc/jetty-jmx.xml
-#===========================================================
diff --git a/jetty-start/src/test/resources/jetty.home/start.d/20-websocket.ini b/jetty-start/src/test/resources/jetty.home/start.d/20-websocket.ini
deleted file mode 100644
index 679a221..0000000
--- a/jetty-start/src/test/resources/jetty.home/start.d/20-websocket.ini
+++ /dev/null
@@ -1,13 +0,0 @@
-#===========================================================
-# Additional Jetty start.jar arguments
-# Each line of this file is prepended to the command line 
-# arguments # of a call to:
-#    java -jar start.jar [arg...]
-#===========================================================
-
-
-
-#===========================================================
-#-----------------------------------------------------------
-OPTIONS=websocket
-#-----------------------------------------------------------
\ No newline at end of file
diff --git a/jetty-start/src/test/resources/jetty.home/start.d/90-testrealm.ini b/jetty-start/src/test/resources/jetty.home/start.d/90-testrealm.ini
deleted file mode 100644
index 59313d3..0000000
--- a/jetty-start/src/test/resources/jetty.home/start.d/90-testrealm.ini
+++ /dev/null
@@ -1 +0,0 @@
-etc/jetty-testrealm.xml
\ No newline at end of file
diff --git a/jetty-start/src/test/resources/jetty.home/start.ini b/jetty-start/src/test/resources/jetty.home/start.ini
deleted file mode 100644
index a9b7249..0000000
--- a/jetty-start/src/test/resources/jetty.home/start.ini
+++ /dev/null
@@ -1,65 +0,0 @@
-#===========================================================
-# Jetty start.jar arguments
-# Each line of this file is prepended to the command line 
-# arguments # of a call to:
-#    java -jar start.jar [arg...]
-#===========================================================
-
-
-
-#===========================================================
-# If the arguements in this file include JVM arguments 
-# (eg -Xmx512m) or JVM System properties (eg com.sun.???),
-# then these will not take affect unless the --exec 
-# parameter is included or if the output from --dry-run
-# is executed like:
-#   eval $(java -jar start.jar --dry-run)
-#
-# Below are some recommended options for Sun's JRE
-#-----------------------------------------------------------
-# --exec
-# -Dorg.apache.jasper.compiler.disablejsr199=true
-# -Dcom.sun.management.jmxremote
-# -Dorg.eclipse.jetty.util.log.IGNORED=true
-# -Dorg.eclipse.jetty.util.log.stderr.DEBUG=true
-# -Dorg.eclipse.jetty.util.log.stderr.SOURCE=true
-# -Xmx2000m
-# -Xmn512m
-# -verbose:gc
-# -XX:+PrintGCDateStamps
-# -XX:+PrintGCTimeStamps
-# -XX:+PrintGCDetails
-# -XX:+PrintTenuringDistribution
-# -XX:+PrintCommandLineFlags
-# -XX:+DisableExplicitGC
-# -XX:+UseConcMarkSweepGC
-# -XX:ParallelCMSThreads=2
-# -XX:+CMSClassUnloadingEnabled  
-# -XX:+UseCMSCompactAtFullCollection
-# -XX:CMSInitiatingOccupancyFraction=80
-#-----------------------------------------------------------
-
-
-#===========================================================
-# Start classpath OPTIONS.
-# These control what classes are on the classpath
-# for a full listing do
-#   java -jar start.jar --list-options
-#-----------------------------------------------------------
-OPTIONS=Server,jsp,resources,websocket,ext
-#-----------------------------------------------------------
-
-
-#===========================================================
-# Configuration files.
-# For a full list of available configuration files do
-#   java -jar start.jar --help
-#-----------------------------------------------------------
-etc/jetty.xml
-# etc/jetty-ssl.xml
-# etc/jetty-requestlog.xml
-etc/jetty-deploy.xml
-#etc/jetty-overlay.xml
-etc/jetty-webapps.xml
-etc/jetty-contexts.xml
-#===========================================================
diff --git a/jetty-start/src/test/resources/test-alt.xml b/jetty-start/src/test/resources/test-alt.xml
deleted file mode 100644
index 38c0208..0000000
--- a/jetty-start/src/test/resources/test-alt.xml
+++ /dev/null
@@ -1 +0,0 @@
-<!-- nothing in here, just used to test the start.config logic in ConfigTest.java -->
diff --git a/jetty-start/src/test/resources/usecases/assert-barebones.txt b/jetty-start/src/test/resources/usecases/assert-barebones.txt
new file mode 100644
index 0000000..164f3f9
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/assert-barebones.txt
@@ -0,0 +1,16 @@
+# The XMLs we expect (order is important)
+XML|${jetty.home}/etc/jetty.xml
+XML|${jetty.home}/etc/jetty-http.xml
+
+# The LIBs we expect (order is irrelevant)
+LIB|${jetty.home}/lib/jetty-continuation-TEST.jar
+LIB|${jetty.home}/lib/jetty-http-TEST.jar
+LIB|${jetty.home}/lib/jetty-io-TEST.jar
+LIB|${jetty.home}/lib/jetty-schemas-3.1.jar
+LIB|${jetty.home}/lib/jetty-server-TEST.jar
+LIB|${jetty.home}/lib/jetty-util-TEST.jar
+LIB|${jetty.home}/lib/jetty-xml-TEST.jar
+LIB|${jetty.home}/lib/servlet-api-3.1.jar
+
+# The Properties we expect (order is irrelevant)
+PROP|jetty.port=9090
diff --git a/jetty-start/src/test/resources/usecases/assert-enable-spdy-bad-npn-version.txt b/jetty-start/src/test/resources/usecases/assert-enable-spdy-bad-npn-version.txt
new file mode 100644
index 0000000..84c8867
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/assert-enable-spdy-bad-npn-version.txt
@@ -0,0 +1,16 @@
+# The XMLs we expect (order is important)
+
+# The LIBs we expect (order is irrelevant)
+
+# The Properties we expect (order is irrelevant)
+PROP|jetty.port=9090
+PROP|jetty.keystore=etc/keystore
+PROP|jetty.keystore.password=friendly
+PROP|jetty.keymanager.password=icecream
+PROP|jetty.truststore=etc/keystore
+PROP|jetty.truststore.password=sundae
+PROP|java.version=1.7.0_01
+
+# The Downloads
+
+# The Bootlib
diff --git a/jetty-start/src/test/resources/usecases/assert-enable-spdy.txt b/jetty-start/src/test/resources/usecases/assert-enable-spdy.txt
new file mode 100644
index 0000000..a69eeb6
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/assert-enable-spdy.txt
@@ -0,0 +1,49 @@
+# The XMLs we expect (order is important)
+XML|${jetty.home}/etc/jetty-jmx.xml
+XML|${jetty.home}/etc/protonego-alpn.xml
+XML|${jetty.home}/etc/jetty.xml
+XML|${jetty.home}/etc/jetty-http.xml
+XML|${jetty.home}/etc/jetty-ssl.xml
+XML|${jetty.home}/etc/jetty-spdy.xml
+
+
+# The LIBs we expect (order is irrelevant)
+LIB|${jetty.home}/lib/jetty-continuation-TEST.jar
+LIB|${jetty.home}/lib/jetty-http-TEST.jar
+LIB|${jetty.home}/lib/jetty-io-TEST.jar
+LIB|${jetty.home}/lib/jetty-jmx-TEST.jar
+LIB|${jetty.home}/lib/jetty-schemas-3.1.jar
+LIB|${jetty.home}/lib/jetty-server-TEST.jar
+LIB|${jetty.home}/lib/jetty-util-TEST.jar
+LIB|${jetty.home}/lib/jetty-xml-TEST.jar
+LIB|${jetty.home}/lib/servlet-api-3.1.jar
+LIB|${jetty.home}/lib/spdy/spdy-client-TEST.jar
+LIB|${jetty.home}/lib/spdy/spdy-http-server-TEST.jar
+LIB|${jetty.home}/lib/spdy/spdy-http-common-TEST.jar
+LIB|${jetty.home}/lib/spdy/spdy-server-TEST.jar
+LIB|${jetty.home}/lib/spdy/spdy-core-TEST.jar
+
+# The Properties we expect (order is irrelevant)
+PROP|jetty.port=9090
+PROP|jetty.secure.port=8443
+PROP|jetty.keystore=etc/keystore
+PROP|jetty.keystore.password=friendly
+PROP|jetty.keymanager.password=icecream
+PROP|jetty.truststore=etc/keystore
+PROP|jetty.truststore.password=sundae
+PROP|java.version=1.7.0_60
+PROP|protonego=alpn
+PROP|spdy.port=8443
+PROP|spdy.timeout=30000
+
+# The Downloads
+DOWNLOAD|http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/7.1.0.v20141016/alpn-boot-7.1.0.v20141016.jar|lib/alpn/alpn-boot-7.1.0.v20141016.jar
+DOWNLOAD|http://git.eclipse.org/c/jetty/org.eclipse.jetty.project.git/plain/jetty-server/src/main/config/etc/keystore|etc/keystore
+
+# The Bootlib
+BOOTLIB|-Xbootclasspath/p:lib/alpn/alpn-boot-7.1.0.v20141016.jar
+
+# The Files
+FILE|lib/
+FILE|lib/alpn/
+
diff --git a/jetty-start/src/test/resources/usecases/assert-include-jetty-dir-logging.txt b/jetty-start/src/test/resources/usecases/assert-include-jetty-dir-logging.txt
new file mode 100644
index 0000000..4ecd847
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/assert-include-jetty-dir-logging.txt
@@ -0,0 +1,24 @@
+# The XMLs we expect (order is important)
+XML|${jetty.home}/etc/jetty-jmx.xml
+XML|${maven-test-resources}/extra-jetty-dirs/logging/etc/jetty-logging.xml
+XML|${jetty.home}/etc/jetty.xml
+XML|${jetty.home}/etc/jetty-http.xml
+
+# The LIBs we expect (order is irrelevant)
+LIB|${jetty.home}/lib/jetty-continuation-TEST.jar
+LIB|${jetty.home}/lib/jetty-http-TEST.jar
+LIB|${jetty.home}/lib/jetty-io-TEST.jar
+LIB|${jetty.home}/lib/jetty-jmx-TEST.jar
+LIB|${jetty.home}/lib/jetty-schemas-3.1.jar
+LIB|${jetty.home}/lib/jetty-server-TEST.jar
+LIB|${jetty.home}/lib/jetty-util-TEST.jar
+LIB|${jetty.home}/lib/jetty-xml-TEST.jar
+LIB|${jetty.home}/lib/servlet-api-3.1.jar
+LIB|${jetty.home}/resources
+LIB|${maven-test-resources}/extra-jetty-dirs/logging/lib/logging/logback.jar
+
+# The Properties we expect (order is irrelevant)
+PROP|jetty.port=9090
+
+# Files
+FILE|logs/
diff --git a/jetty-start/src/test/resources/usecases/assert-jmx.txt b/jetty-start/src/test/resources/usecases/assert-jmx.txt
new file mode 100644
index 0000000..def27d7
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/assert-jmx.txt
@@ -0,0 +1,18 @@
+# The XMLs we expect (order is important)
+XML|${jetty.home}/etc/jetty-jmx.xml
+XML|${jetty.home}/etc/jetty.xml
+XML|${jetty.home}/etc/jetty-http.xml
+
+# The LIBs we expect (order is irrelevant)
+LIB|${jetty.home}/lib/jetty-continuation-TEST.jar
+LIB|${jetty.home}/lib/jetty-http-TEST.jar
+LIB|${jetty.home}/lib/jetty-io-TEST.jar
+LIB|${jetty.home}/lib/jetty-jmx-TEST.jar
+LIB|${jetty.home}/lib/jetty-schemas-3.1.jar
+LIB|${jetty.home}/lib/jetty-server-TEST.jar
+LIB|${jetty.home}/lib/jetty-util-TEST.jar
+LIB|${jetty.home}/lib/jetty-xml-TEST.jar
+LIB|${jetty.home}/lib/servlet-api-3.1.jar
+
+# The Properties we expect (order is irrelevant)
+PROP|jetty.port=9090
diff --git a/jetty-start/src/test/resources/usecases/assert-jsp-apache.txt b/jetty-start/src/test/resources/usecases/assert-jsp-apache.txt
new file mode 100644
index 0000000..fe3d51c
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/assert-jsp-apache.txt
@@ -0,0 +1,26 @@
+# The XMLs we expect (order is important)
+XML|${jetty.home}/etc/jetty.xml
+XML|${jetty.home}/etc/jetty-http.xml
+
+# The LIBs we expect (order is irrelevant)
+LIB|${jetty.home}/lib/jetty-continuation-TEST.jar
+LIB|${jetty.home}/lib/jetty-http-TEST.jar
+LIB|${jetty.home}/lib/jetty-io-TEST.jar
+LIB|${jetty.home}/lib/jetty-schemas-3.1.jar
+LIB|${jetty.home}/lib/jetty-server-TEST.jar
+LIB|${jetty.home}/lib/jetty-servlet-TEST.jar
+LIB|${jetty.home}/lib/jetty-util-TEST.jar
+LIB|${jetty.home}/lib/jetty-xml-TEST.jar
+LIB|${jetty.home}/lib/servlet-api-3.1.jar
+LIB|${jetty.home}/lib/apache-jsp/javax.servlet.jsp.javax.servlet.jsp-api-TEST.jar
+LIB|${jetty.home}/lib/apache-jsp/org.eclipse.jetty.apache-jsp-TEST.jar
+LIB|${jetty.home}/lib/apache-jsp/org.eclipse.jetty.orbit.org.eclipse.jdt.core-TEST.jar
+LIB|${jetty.home}/lib/apache-jsp/org.mortbay.jasper.apache-el-TEST.jar
+LIB|${jetty.home}/lib/apache-jsp/org.mortbay.jasper.apache-jsp-TEST.jar
+
+# The Properties we expect (order is irrelevant)
+PROP|jetty.port=9090
+PROP|jsp-impl=apache
+
+# Files / Directories to create
+# FILE|lib/
diff --git a/jetty-start/src/test/resources/usecases/assert-jsp-glassfish.txt b/jetty-start/src/test/resources/usecases/assert-jsp-glassfish.txt
new file mode 100644
index 0000000..9c17c56
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/assert-jsp-glassfish.txt
@@ -0,0 +1,28 @@
+# The XMLs we expect (order is important)
+XML|${jetty.home}/etc/jetty.xml
+XML|${jetty.home}/etc/jetty-http.xml
+
+# The LIBs we expect (order is irrelevant)
+LIB|${jetty.home}/lib/jetty-continuation-TEST.jar
+LIB|${jetty.home}/lib/jetty-http-TEST.jar
+LIB|${jetty.home}/lib/jetty-io-TEST.jar
+LIB|${jetty.home}/lib/jetty-schemas-3.1.jar
+LIB|${jetty.home}/lib/jetty-server-TEST.jar
+LIB|${jetty.home}/lib/jetty-servlet-TEST.jar
+LIB|${jetty.home}/lib/jetty-util-TEST.jar
+LIB|${jetty.home}/lib/jetty-xml-TEST.jar
+LIB|${jetty.home}/lib/servlet-api-3.1.jar
+LIB|${jetty.home}/lib/jsp/javax.el-TEST.jar
+LIB|${jetty.home}/lib/jsp/javax.servlet.jsp.jstl-TEST.jar
+LIB|${jetty.home}/lib/jsp/javax.servlet.jsp-api-TEST.jar
+LIB|${jetty.home}/lib/jsp/javax.servlet.jsp-TEST.jar
+LIB|${jetty.home}/lib/jsp/jetty-jsp-jdt-TEST.jar
+LIB|${jetty.home}/lib/jsp/org.eclipse.jdt.core-TEST.jar
+LIB|${jetty.home}/lib/jsp/org.eclipse.jetty.orbit.javax.servlet.jsp.jstl-TEST.jar
+
+# The Properties we expect (order is irrelevant)
+PROP|jetty.port=9090
+PROP|jsp-impl=glassfish
+
+# Files / Directories to create
+# FILE|lib/
diff --git a/jetty-start/src/test/resources/usecases/assert-logging.txt b/jetty-start/src/test/resources/usecases/assert-logging.txt
new file mode 100644
index 0000000..c83dc2c
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/assert-logging.txt
@@ -0,0 +1,32 @@
+# The XMLs we expect (order is important)
+XML|${jetty.home}/etc/jetty.xml
+XML|${jetty.home}/etc/jetty-http.xml
+
+# The LIBs we expect (order is irrelevant)
+LIB|${jetty.home}/lib/jetty-continuation-TEST.jar
+LIB|${jetty.home}/lib/jetty-http-TEST.jar
+LIB|${jetty.home}/lib/jetty-io-TEST.jar
+LIB|${jetty.home}/lib/jetty-schemas-3.1.jar
+LIB|${jetty.home}/lib/jetty-server-TEST.jar
+LIB|${jetty.home}/lib/jetty-util-TEST.jar
+LIB|${jetty.home}/lib/jetty-xml-TEST.jar
+LIB|${jetty.home}/lib/servlet-api-3.1.jar
+LIB|${jetty.base}/lib/logging/slf4j-api.jar
+LIB|${jetty.base}/lib/logging/jul-to-slf4j.jar
+LIB|${jetty.base}/lib/logging/logback-core.jar
+LIB|${jetty.base}/lib/logging/logback-classic.jar
+LIB|${jetty.base}/resources
+
+# The Properties we expect (order is irrelevant)
+PROP|jetty.port=9090
+
+# Other File References
+FILE|logs/
+FILE|resources/
+
+# Downloads
+DOWNLOAD|http://central.maven.org/maven2/org/slf4j/slf4j-api/1.6.6/slf4j-api-1.6.6.jar|lib/logging/slf4j-api-1.6.6.jar
+DOWNLOAD|http://repo1.maven.org/maven2/ch/qos/logback/logback-core/1.0.7/logback-core-1.0.7.jar|lib/logging/logback-core-1.0.7.jar
+DOWNLOAD|http://repo1.maven.org/maven2/ch/qos/logback/logback-classic/1.0.7/logback-classic-1.0.7.jar|lib/logging/logback-classic-1.0.7.jar
+DOWNLOAD|https://raw.githubusercontent.com/jetty-project/logging-modules/master/logback/logback.xml|resources/logback.xml
+DOWNLOAD|https://raw.githubusercontent.com/jetty-project/logging-modules/master/logback/jetty-logging.properties|resources/jetty-logging.properties
diff --git a/jetty-start/src/test/resources/usecases/assert-missing-npn-version.txt b/jetty-start/src/test/resources/usecases/assert-missing-npn-version.txt
new file mode 100644
index 0000000..884767a
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/assert-missing-npn-version.txt
@@ -0,0 +1,28 @@
+# The XMLs we expect (order is important)
+XML|${jetty.home}/etc/jetty-jmx.xml
+XML|${jetty.home}/etc/jetty.xml
+XML|${jetty.home}/etc/jetty-http.xml
+
+# The LIBs we expect (order is irrelevant)
+LIB|${jetty.home}/lib/jetty-continuation-TEST.jar
+LIB|${jetty.home}/lib/jetty-http-TEST.jar
+LIB|${jetty.home}/lib/jetty-io-TEST.jar
+LIB|${jetty.home}/lib/jetty-jmx-TEST.jar
+LIB|${jetty.home}/lib/jetty-schemas-3.1.jar
+LIB|${jetty.home}/lib/jetty-server-TEST.jar
+LIB|${jetty.home}/lib/jetty-util-TEST.jar
+LIB|${jetty.home}/lib/jetty-xml-TEST.jar
+LIB|${jetty.home}/lib/servlet-api-3.1.jar
+
+# The Properties we expect (order is irrelevant)
+PROP|jetty.port=9090
+PROP|jetty.keystore=etc/keystore
+PROP|jetty.keystore.password=friendly
+PROP|jetty.keymanager.password=icecream
+PROP|jetty.truststore=etc/keystore
+PROP|jetty.truststore.password=sundae
+PROP|java.version=1.7.0_01
+
+# The Downloads
+
+# The Bootlib
diff --git a/jetty-start/src/test/resources/usecases/assert-props.agent.txt b/jetty-start/src/test/resources/usecases/assert-props.agent.txt
new file mode 100644
index 0000000..27108ab
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/assert-props.agent.txt
@@ -0,0 +1,20 @@
+# The XMLs we expect (order is important)
+XML|${jetty.home}/etc/jetty-jmx.xml
+XML|${jetty.home}/etc/jetty.xml
+XML|${jetty.home}/etc/jetty-http.xml
+
+# The LIBs we expect (order is irrelevant)
+LIB|${jetty.home}/lib/jetty-continuation-TEST.jar
+LIB|${jetty.home}/lib/jetty-http-TEST.jar
+LIB|${jetty.home}/lib/jetty-io-TEST.jar
+LIB|${jetty.home}/lib/jetty-jmx-TEST.jar
+LIB|${jetty.home}/lib/jetty-schemas-3.1.jar
+LIB|${jetty.home}/lib/jetty-server-TEST.jar
+LIB|${jetty.home}/lib/jetty-util-TEST.jar
+LIB|${jetty.home}/lib/jetty-xml-TEST.jar
+LIB|${jetty.home}/lib/servlet-api-3.1.jar
+LIB|${jetty.base}/lib/agent-jdk-1.6.jar
+
+# The Properties we expect (order is irrelevant)
+PROP|jetty.port=9090
+PROP|java.vm.specification.version=1.6
diff --git a/jetty-start/src/test/resources/usecases/assert-props.basic.txt b/jetty-start/src/test/resources/usecases/assert-props.basic.txt
new file mode 100644
index 0000000..e56177e
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/assert-props.basic.txt
@@ -0,0 +1,17 @@
+# The XMLs we expect (order is important)
+XML|${jetty.home}/etc/jetty.xml
+XML|${jetty.home}/etc/jetty-http.xml
+
+# The LIBs we expect (order is irrelevant)
+LIB|${jetty.home}/lib/jetty-continuation-TEST.jar
+LIB|${jetty.home}/lib/jetty-http-TEST.jar
+LIB|${jetty.home}/lib/jetty-io-TEST.jar
+LIB|${jetty.home}/lib/jetty-schemas-3.1.jar
+LIB|${jetty.home}/lib/jetty-server-TEST.jar
+LIB|${jetty.home}/lib/jetty-util-TEST.jar
+LIB|${jetty.home}/lib/jetty-xml-TEST.jar
+LIB|${jetty.home}/lib/servlet-api-3.1.jar
+
+# The Properties we expect (order is irrelevant)
+PROP|jetty.port=9090
+PROP|port=9090
diff --git a/jetty-start/src/test/resources/usecases/assert-with-db.txt b/jetty-start/src/test/resources/usecases/assert-with-db.txt
new file mode 100644
index 0000000..1651974
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/assert-with-db.txt
@@ -0,0 +1,31 @@
+# The XMLs we expect (order is important)
+XML|${jetty.home}/etc/jetty.xml
+XML|${jetty.home}/etc/jetty-http.xml
+XML|${jetty.home}/etc/jetty-plus.xml
+XML|${jetty.home}/etc/jetty-deploy.xml
+XML|${jetty.base}/etc/jetty-db.xml
+
+# The LIBs we expect (order is irrelevant)
+LIB|${jetty.home}/lib/jetty-http-TEST.jar
+LIB|${jetty.home}/lib/jetty-io-TEST.jar
+LIB|${jetty.home}/lib/jetty-schemas-3.1.jar
+LIB|${jetty.home}/lib/jetty-server-TEST.jar
+LIB|${jetty.home}/lib/jetty-util-TEST.jar
+LIB|${jetty.home}/lib/jetty-xml-TEST.jar
+LIB|${jetty.home}/lib/servlet-api-3.1.jar
+LIB|${jetty.home}/lib/jetty-jndi-TEST.jar
+LIB|${jetty.home}/lib/jetty-continuation-TEST.jar
+LIB|${jetty.home}/lib/jndi/javax.transaction-api-1.2.jar
+LIB|${jetty.home}/lib/jetty-plus-TEST.jar
+LIB|${jetty.home}/lib/jetty-deploy-TEST.jar
+LIB|${jetty.home}/lib/jetty-security-TEST.jar
+LIB|${jetty.home}/lib/jndi/javax.activation-1.1.jar
+LIB|${jetty.home}/lib/jetty-webapp-TEST.jar
+LIB|${jetty.home}/lib/jetty-servlet-TEST.jar
+LIB|${jetty.base}/lib/db/mysql-driver.jar
+LIB|${jetty.base}/lib/db/bonecp.jar
+
+# The Properties we expect (order is irrelevant)
+PROP|jetty.port=9090
+PROP|mysql.user=frank
+PROP|mysql.pass=secret
\ No newline at end of file
diff --git a/jetty-start/src/test/resources/usecases/assert-with-module-persistence.txt b/jetty-start/src/test/resources/usecases/assert-with-module-persistence.txt
new file mode 100644
index 0000000..75fb89c
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/assert-with-module-persistence.txt
@@ -0,0 +1,44 @@
+# The XMLs we expect (order is important)
+XML|${jetty.home}/etc/jetty.xml
+XML|${jetty.home}/etc/jetty-ssl.xml
+XML|${jetty.home}/etc/jetty-https.xml
+XML|${jetty.home}/etc/jetty-plus.xml
+XML|${jetty.home}/etc/jetty-annotations.xml
+XML|${jetty.home}/etc/jetty-websockets.xml
+
+# The LIBs we expect (order is irrelevant)
+LIB|${jetty.home}/lib/annotations/javax.annotation-api-1.2.jar
+LIB|${jetty.home}/lib/annotations/org.objectweb.asm-TEST.jar
+LIB|${jetty.home}/lib/jetty-annotations-TEST.jar
+LIB|${jetty.home}/lib/jetty-continuation-TEST.jar
+LIB|${jetty.home}/lib/jetty-http-TEST.jar
+LIB|${jetty.home}/lib/jetty-io-TEST.jar
+LIB|${jetty.home}/lib/jetty-jndi-TEST.jar
+LIB|${jetty.home}/lib/jetty-plus-TEST.jar
+LIB|${jetty.home}/lib/jetty-schemas-3.1.jar
+LIB|${jetty.home}/lib/jetty-security-TEST.jar
+LIB|${jetty.home}/lib/jetty-server-TEST.jar
+LIB|${jetty.home}/lib/jetty-util-TEST.jar
+LIB|${jetty.home}/lib/jetty-xml-TEST.jar
+LIB|${jetty.home}/lib/jndi/javax.activation-1.1.jar
+LIB|${jetty.home}/lib/jndi/javax.transaction-api-1.2.jar
+LIB|${jetty.home}/lib/servlet-api-3.1.jar
+LIB|${jetty.home}/lib/websocket/javax.websocket-api-1.0.jar
+LIB|${jetty.home}/lib/websocket/javax-websocket-client-impl-TEST.jar
+LIB|${jetty.home}/lib/websocket/javax-websocket-server-impl-TEST.jar
+LIB|${jetty.home}/lib/websocket/websocket-api-TEST.jar
+LIB|${jetty.home}/lib/websocket/websocket-client-TEST.jar
+LIB|${jetty.home}/lib/websocket/websocket-common-TEST.jar
+LIB|${jetty.home}/lib/websocket/websocket-server-TEST.jar
+LIB|${jetty.home}/lib/websocket/websocket-servlet-TEST.jar
+
+# The Properties we expect (order is irrelevant)
+PROP|jetty.port=12345
+PROP|jetty.keystore=etc/keystore
+PROP|jetty.keystore.password=friendly
+PROP|jetty.keymanager.password=icecream
+PROP|jetty.truststore=etc/keystore
+PROP|jetty.truststore.password=sundae
+
+# JVM Args
+# JVM|-Xms1024m
diff --git a/jetty-start/src/test/resources/usecases/assert-with.ext.txt b/jetty-start/src/test/resources/usecases/assert-with.ext.txt
new file mode 100644
index 0000000..7759a41
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/assert-with.ext.txt
@@ -0,0 +1,26 @@
+# The XMLs we expect (order is important)
+XML|${jetty.home}/etc/jetty.xml
+XML|${jetty.home}/etc/jetty-http.xml
+
+# The LIBs we expect (order is irrelevant)
+LIB|${jetty.home}/lib/jetty-continuation-TEST.jar
+LIB|${jetty.home}/lib/jetty-http-TEST.jar
+LIB|${jetty.home}/lib/jetty-io-TEST.jar
+LIB|${jetty.home}/lib/jetty-schemas-3.1.jar
+LIB|${jetty.home}/lib/jetty-server-TEST.jar
+LIB|${jetty.home}/lib/jetty-util-TEST.jar
+LIB|${jetty.home}/lib/jetty-xml-TEST.jar
+LIB|${jetty.home}/lib/servlet-api-3.1.jar
+LIB|${jetty.base}/lib/ext/agent.jar
+LIB|${jetty.base}/lib/ext/jdbc/mariadb-jdbc.jar
+LIB|${jetty.base}/lib/ext/logging/slf4j-api.jar
+LIB|${jetty.base}/lib/ext/logging/jul-to-slf4j.jar
+LIB|${jetty.base}/lib/ext/logging/logback-core.jar
+LIB|${jetty.base}/lib/ext/logging/logback-classic.jar
+
+# The Properties we expect (order is irrelevant)
+PROP|jetty.port=9090
+
+# Files / Directories to create
+FILE|lib/
+FILE|lib/ext/
diff --git a/jetty-start/src/test/resources/usecases/base.barebones/start.ini b/jetty-start/src/test/resources/usecases/base.barebones/start.ini
new file mode 100644
index 0000000..1c0e065
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/base.barebones/start.ini
@@ -0,0 +1,5 @@
+
+--module=server
+--module=http
+
+jetty.port=9090
diff --git a/jetty-start/src/test/resources/usecases/base.enable.spdy.bad.npn.version/start.ini b/jetty-start/src/test/resources/usecases/base.enable.spdy.bad.npn.version/start.ini
new file mode 100644
index 0000000..13fa508
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/base.enable.spdy.bad.npn.version/start.ini
@@ -0,0 +1,12 @@
+
+--module=server,http,jmx,spdy
+
+jetty.port=9090
+
+# Some SSL keystore configuration
+jetty.keystore=etc/keystore
+jetty.keystore.password=friendly
+jetty.keymanager.password=icecream
+jetty.truststore=etc/keystore
+jetty.truststore.password=sundae
+
diff --git a/jetty-start/src/test/resources/usecases/base.enable.spdy/start.ini b/jetty-start/src/test/resources/usecases/base.enable.spdy/start.ini
new file mode 100644
index 0000000..13fa508
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/base.enable.spdy/start.ini
@@ -0,0 +1,12 @@
+
+--module=server,http,jmx,spdy
+
+jetty.port=9090
+
+# Some SSL keystore configuration
+jetty.keystore=etc/keystore
+jetty.keystore.password=friendly
+jetty.keymanager.password=icecream
+jetty.truststore=etc/keystore
+jetty.truststore.password=sundae
+
diff --git a/jetty-start/src/test/resources/usecases/base.jmx/start.ini b/jetty-start/src/test/resources/usecases/base.jmx/start.ini
new file mode 100644
index 0000000..d3950e6
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/base.jmx/start.ini
@@ -0,0 +1,4 @@
+
+--module=server,http,jmx
+
+jetty.port=9090
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/base.logging/lib/logging/jul-to-slf4j.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/base.logging/lib/logging/jul-to-slf4j.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/base.logging/lib/logging/logback-classic.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/base.logging/lib/logging/logback-classic.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/base.logging/lib/logging/logback-core.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/base.logging/lib/logging/logback-core.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/base.logging/lib/logging/slf4j-api.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/base.logging/lib/logging/slf4j-api.jar
diff --git a/jetty-start/src/test/resources/usecases/base.logging/modules/logging.mod b/jetty-start/src/test/resources/usecases/base.logging/modules/logging.mod
new file mode 100644
index 0000000..e2d24ce
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/base.logging/modules/logging.mod
@@ -0,0 +1,20 @@
+#
+# Jetty with logback logging
+#
+
+[depend]
+resources
+
+[files]
+logs/
+resources/
+http://central.maven.org/maven2/org/slf4j/slf4j-api/1.6.6/slf4j-api-1.6.6.jar|lib/logging/slf4j-api-1.6.6.jar
+http://repo1.maven.org/maven2/ch/qos/logback/logback-core/1.0.7/logback-core-1.0.7.jar|lib/logging/logback-core-1.0.7.jar
+http://repo1.maven.org/maven2/ch/qos/logback/logback-classic/1.0.7/logback-classic-1.0.7.jar|lib/logging/logback-classic-1.0.7.jar
+https://raw.githubusercontent.com/jetty-project/logging-modules/master/logback/logback.xml|resources/logback.xml
+https://raw.githubusercontent.com/jetty-project/logging-modules/master/logback/jetty-logging.properties|resources/jetty-logging.properties
+
+[lib]
+lib/logging/**.jar
+resources/
+
diff --git a/jetty-start/src/test/resources/usecases/base.logging/resources/jetty-logging.properties b/jetty-start/src/test/resources/usecases/base.logging/resources/jetty-logging.properties
new file mode 100644
index 0000000..5cb8291
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/base.logging/resources/jetty-logging.properties
@@ -0,0 +1,2 @@
+# Configure Jetty for SLf4j Logging
+org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.Slf4jLog
\ No newline at end of file
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/base.logging/resources/logback.xml
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/base.logging/resources/logback.xml
diff --git a/jetty-start/src/test/resources/usecases/base.logging/start.ini b/jetty-start/src/test/resources/usecases/base.logging/start.ini
new file mode 100644
index 0000000..e18ff3d
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/base.logging/start.ini
@@ -0,0 +1,7 @@
+
+--module=server
+--module=http
+--module=logging
+--module=resources
+
+jetty.port=9090
diff --git a/jetty-start/src/test/resources/usecases/base.missing.npn.version/start.ini b/jetty-start/src/test/resources/usecases/base.missing.npn.version/start.ini
new file mode 100644
index 0000000..2915961
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/base.missing.npn.version/start.ini
@@ -0,0 +1,12 @@
+
+--module=server,http,jmx
+
+jetty.port=9090
+
+# Some SSL keystore configuration
+jetty.keystore=etc/keystore
+jetty.keystore.password=friendly
+jetty.keymanager.password=icecream
+jetty.truststore=etc/keystore
+jetty.truststore.password=sundae
+
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/base.props.agent/lib/agent-jdk-1.5.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/base.props.agent/lib/agent-jdk-1.5.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/base.props.agent/lib/agent-jdk-1.6.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/base.props.agent/lib/agent-jdk-1.6.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/base.props.agent/lib/agent-jdk-1.7.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/base.props.agent/lib/agent-jdk-1.7.jar
diff --git a/jetty-start/src/test/resources/usecases/base.props.agent/modules/agent.mod b/jetty-start/src/test/resources/usecases/base.props.agent/modules/agent.mod
new file mode 100644
index 0000000..a4362a2
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/base.props.agent/modules/agent.mod
@@ -0,0 +1,8 @@
+
+[depend]
+server
+jmx
+
+[lib]
+lib/agent-jdk-${java.vm.specification.version}.jar
+
diff --git a/jetty-start/src/test/resources/usecases/base.props.agent/start.ini b/jetty-start/src/test/resources/usecases/base.props.agent/start.ini
new file mode 100644
index 0000000..198a07c
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/base.props.agent/start.ini
@@ -0,0 +1,4 @@
+
+--module=http,agent
+
+jetty.port=9090
diff --git a/jetty-start/src/test/resources/usecases/base.props.basic/start.ini b/jetty-start/src/test/resources/usecases/base.props.basic/start.ini
new file mode 100644
index 0000000..f80bdb2
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/base.props.basic/start.ini
@@ -0,0 +1,5 @@
+
+--module=server
+--module=http
+
+jetty.port=${port}
diff --git a/jetty-start/src/test/resources/usecases/base.with.db/etc/jetty-db.xml b/jetty-start/src/test/resources/usecases/base.with.db/etc/jetty-db.xml
new file mode 100644
index 0000000..7dd6100
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/base.with.db/etc/jetty-db.xml
@@ -0,0 +1 @@
+<!-- build up org.eclipse.jetty.plus.jndi.Resource here  -->
\ No newline at end of file
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/base.with.db/lib/db/bonecp.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/base.with.db/lib/db/bonecp.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/base.with.db/lib/db/mysql-driver.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/base.with.db/lib/db/mysql-driver.jar
diff --git a/jetty-start/src/test/resources/usecases/base.with.db/modules/db.mod b/jetty-start/src/test/resources/usecases/base.with.db/modules/db.mod
new file mode 100644
index 0000000..5acded8
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/base.with.db/modules/db.mod
@@ -0,0 +1,11 @@
+
+[depend]
+deploy
+jndi
+plus
+
+[lib]
+lib/db/*.jar
+
+[xml]
+etc/jetty-db.xml
diff --git a/jetty-start/src/test/resources/usecases/base.with.db/start.ini b/jetty-start/src/test/resources/usecases/base.with.db/start.ini
new file mode 100644
index 0000000..d5513df
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/base.with.db/start.ini
@@ -0,0 +1,7 @@
+
+--module=http,db
+
+mysql.user=frank
+mysql.pass=secret
+
+jetty.port=9090
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/base.with.ext/lib/ext/agent.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/base.with.ext/lib/ext/agent.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/base.with.ext/lib/ext/jdbc/mariadb-jdbc.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/base.with.ext/lib/ext/jdbc/mariadb-jdbc.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/base.with.ext/lib/ext/logging/jul-to-slf4j.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/base.with.ext/lib/ext/logging/jul-to-slf4j.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/base.with.ext/lib/ext/logging/logback-classic.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/base.with.ext/lib/ext/logging/logback-classic.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/base.with.ext/lib/ext/logging/logback-core.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/base.with.ext/lib/ext/logging/logback-core.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/base.with.ext/lib/ext/logging/slf4j-api.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/base.with.ext/lib/ext/logging/slf4j-api.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/base.with.ext/lib/jetty-util-alt.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/base.with.ext/lib/jetty-util-alt.jar
diff --git a/jetty-start/src/test/resources/usecases/base.with.ext/start.ini b/jetty-start/src/test/resources/usecases/base.with.ext/start.ini
new file mode 100644
index 0000000..c8fa930
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/base.with.ext/start.ini
@@ -0,0 +1,6 @@
+
+--module=server
+--module=http
+--module=ext
+
+jetty.port=9090
diff --git a/jetty-start/src/test/resources/usecases/base.with.include.jetty.dirs/start.ini b/jetty-start/src/test/resources/usecases/base.with.include.jetty.dirs/start.ini
new file mode 100644
index 0000000..9e16be6
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/base.with.include.jetty.dirs/start.ini
@@ -0,0 +1,5 @@
+
+--include-jetty-dir=${start.basedir}/../../extra-jetty-dirs/logging
+--module=server,http,jmx
+
+jetty.port=9090
diff --git a/jetty-start/src/test/resources/usecases/base.with.jsp.apache/start.ini b/jetty-start/src/test/resources/usecases/base.with.jsp.apache/start.ini
new file mode 100644
index 0000000..fcdea02
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/base.with.jsp.apache/start.ini
@@ -0,0 +1,7 @@
+
+--module=server
+--module=http
+--module=jsp
+jsp-impl=apache
+
+jetty.port=9090
diff --git a/jetty-start/src/test/resources/usecases/base.with.jsp.bad/start.ini b/jetty-start/src/test/resources/usecases/base.with.jsp.bad/start.ini
new file mode 100644
index 0000000..96e495a
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/base.with.jsp.bad/start.ini
@@ -0,0 +1,7 @@
+
+--module=server
+--module=http
+--module=jsp
+jsp-impl=bogus
+
+jetty.port=9090
diff --git a/jetty-start/src/test/resources/usecases/base.with.jsp.default/start.ini b/jetty-start/src/test/resources/usecases/base.with.jsp.default/start.ini
new file mode 100644
index 0000000..bf58fa8
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/base.with.jsp.default/start.ini
@@ -0,0 +1,6 @@
+
+--module=server
+--module=http
+--module=jsp
+
+jetty.port=9090
diff --git a/jetty-start/src/test/resources/usecases/base.with.jsp.glassfish/start.ini b/jetty-start/src/test/resources/usecases/base.with.jsp.glassfish/start.ini
new file mode 100644
index 0000000..7107ade
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/base.with.jsp.glassfish/start.ini
@@ -0,0 +1,7 @@
+
+--module=server
+--module=http
+--module=jsp
+jsp-impl=glassfish
+
+jetty.port=9090
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/etc/README.spnego
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/etc/README.spnego
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/etc/jdbcRealm.properties
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/etc/jdbcRealm.properties
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/etc/jetty-annotations.xml
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/etc/jetty-annotations.xml
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/etc/jetty-contexts.xml
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/etc/jetty-contexts.xml
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/etc/jetty-debug.xml
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/etc/jetty-debug.xml
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/etc/jetty-demo.xml
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/etc/jetty-demo.xml
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/etc/jetty-deploy.xml
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/etc/jetty-deploy.xml
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/etc/jetty-http.xml
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/etc/jetty-http.xml
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/etc/jetty-https.xml
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/etc/jetty-https.xml
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/etc/jetty-ipaccess.xml
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/etc/jetty-ipaccess.xml
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/etc/jetty-jaas.xml
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/etc/jetty-jaas.xml
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/etc/jetty-jmx.xml
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/etc/jetty-jmx.xml
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/etc/jetty-logging.xml
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/etc/jetty-logging.xml
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/etc/jetty-lowresources.xml
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/etc/jetty-lowresources.xml
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/etc/jetty-monitor.xml
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/etc/jetty-monitor.xml
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/etc/jetty-plus.xml
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/etc/jetty-plus.xml
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/etc/jetty-proxy.xml
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/etc/jetty-proxy.xml
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/etc/jetty-requestlog.xml
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/etc/jetty-requestlog.xml
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/etc/jetty-rewrite.xml
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/etc/jetty-rewrite.xml
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/etc/jetty-setuid.xml
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/etc/jetty-setuid.xml
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/etc/jetty-spdy-proxy.xml
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/etc/jetty-spdy-proxy.xml
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/etc/jetty-spdy.xml
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/etc/jetty-spdy.xml
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/etc/jetty-ssl.xml
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/etc/jetty-ssl.xml
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/etc/jetty-started.xml
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/etc/jetty-started.xml
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/etc/jetty-stats.xml
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/etc/jetty-stats.xml
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/etc/jetty-testrealm.xml
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/etc/jetty-testrealm.xml
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/etc/jetty-webapps.xml
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/etc/jetty-webapps.xml
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/etc/jetty-websockets.xml
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/etc/jetty-websockets.xml
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/etc/jetty-xinetd.xml
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/etc/jetty-xinetd.xml
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/etc/jetty.conf
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/etc/jetty.conf
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/etc/jetty.xml
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/etc/jetty.xml
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/etc/keystore
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/etc/keystore
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/etc/krb5.ini
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/etc/krb5.ini
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/etc/protonego-alpn.xml
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/etc/protonego-alpn.xml
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/etc/protonego-npn.xml
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/etc/protonego-npn.xml
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/etc/realm.properties
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/etc/realm.properties
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/etc/spnego.conf
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/etc/spnego.conf
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/etc/spnego.properties
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/etc/spnego.properties
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/etc/test-realm.xml
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/etc/test-realm.xml
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/etc/webdefault.xml
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/etc/webdefault.xml
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/lib/annotations/javax.annotation-api-1.2.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/lib/annotations/javax.annotation-api-1.2.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/lib/annotations/org.objectweb.asm-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/lib/annotations/org.objectweb.asm-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/lib/apache-jsp/javax.servlet.jsp.javax.servlet.jsp-api-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/lib/apache-jsp/javax.servlet.jsp.javax.servlet.jsp-api-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/lib/apache-jsp/org.eclipse.jetty.apache-jsp-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/lib/apache-jsp/org.eclipse.jetty.apache-jsp-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/lib/apache-jsp/org.eclipse.jetty.orbit.org.eclipse.jdt.core-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/lib/apache-jsp/org.eclipse.jetty.orbit.org.eclipse.jdt.core-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/lib/apache-jsp/org.mortbay.jasper.apache-el-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/lib/apache-jsp/org.mortbay.jasper.apache-el-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/lib/apache-jsp/org.mortbay.jasper.apache-jsp-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/lib/apache-jsp/org.mortbay.jasper.apache-jsp-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/lib/apache-jstl/org.apache.taglibs.taglibs-standard-impl-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/lib/apache-jstl/org.apache.taglibs.taglibs-standard-impl-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/lib/apache-jstl/org.apache.taglibs.taglibs-standard-spec-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/lib/apache-jstl/org.apache.taglibs.taglibs-standard-spec-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/lib/ext/.nodelete
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/lib/ext/.nodelete
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/lib/jetty-annotations-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/lib/jetty-annotations-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/lib/jetty-client-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/lib/jetty-client-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/lib/jetty-continuation-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/lib/jetty-continuation-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/lib/jetty-deploy-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/lib/jetty-deploy-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/lib/jetty-http-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/lib/jetty-http-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/lib/jetty-io-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/lib/jetty-io-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/lib/jetty-jaas-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/lib/jetty-jaas-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/lib/jetty-jmx-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/lib/jetty-jmx-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/lib/jetty-jndi-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/lib/jetty-jndi-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/lib/jetty-jsp-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/lib/jetty-jsp-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/lib/jetty-plus-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/lib/jetty-plus-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/lib/jetty-proxy-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/lib/jetty-proxy-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/lib/jetty-rewrite-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/lib/jetty-rewrite-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/lib/jetty-schemas-3.1.RC0.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/lib/jetty-schemas-3.1.RC0.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/lib/jetty-schemas-3.1.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/lib/jetty-schemas-3.1.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/lib/jetty-security-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/lib/jetty-security-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/lib/jetty-server-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/lib/jetty-server-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/lib/jetty-servlet-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/lib/jetty-servlet-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/lib/jetty-servlets-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/lib/jetty-servlets-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/lib/jetty-util-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/lib/jetty-util-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/lib/jetty-webapp-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/lib/jetty-webapp-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/lib/jetty-xml-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/lib/jetty-xml-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/lib/jndi/javax.activation-1.1.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/lib/jndi/javax.activation-1.1.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/lib/jndi/javax.transaction-api-1.2.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/lib/jndi/javax.transaction-api-1.2.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/lib/jsp/javax.el-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/lib/jsp/javax.el-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/lib/jsp/javax.servlet.jsp-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/lib/jsp/javax.servlet.jsp-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/lib/jsp/javax.servlet.jsp-api-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/lib/jsp/javax.servlet.jsp-api-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/lib/jsp/javax.servlet.jsp.jstl-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/lib/jsp/javax.servlet.jsp.jstl-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/lib/jsp/jetty-jsp-jdt-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/lib/jsp/jetty-jsp-jdt-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/lib/jsp/org.eclipse.jdt.core-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/lib/jsp/org.eclipse.jdt.core-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/lib/jsp/org.eclipse.jetty.orbit.javax.servlet.jsp.jstl-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/lib/jsp/org.eclipse.jetty.orbit.javax.servlet.jsp.jstl-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/lib/monitor/jetty-monitor-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/lib/monitor/jetty-monitor-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/lib/servlet-api-3.1.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/lib/servlet-api-3.1.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/lib/setuid/jetty-setuid-java-1.0.1.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/lib/setuid/jetty-setuid-java-1.0.1.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/lib/setuid/libsetuid-linux.so
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/lib/setuid/libsetuid-linux.so
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/lib/setuid/libsetuid-osx.so
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/lib/setuid/libsetuid-osx.so
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/lib/spdy/spdy-client-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/lib/spdy/spdy-client-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/lib/spdy/spdy-core-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/lib/spdy/spdy-core-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/lib/spdy/spdy-http-common-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/lib/spdy/spdy-http-common-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/lib/spdy/spdy-http-server-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/lib/spdy/spdy-http-server-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/lib/spdy/spdy-server-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/lib/spdy/spdy-server-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/lib/websocket/javax-websocket-client-impl-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/lib/websocket/javax-websocket-client-impl-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/lib/websocket/javax-websocket-server-impl-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/lib/websocket/javax-websocket-server-impl-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/lib/websocket/javax.websocket-api-1.0.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/lib/websocket/javax.websocket-api-1.0.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/lib/websocket/websocket-api-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/lib/websocket/websocket-api-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/lib/websocket/websocket-client-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/lib/websocket/websocket-client-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/lib/websocket/websocket-common-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/lib/websocket/websocket-common-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/lib/websocket/websocket-server-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/lib/websocket/websocket-server-TEST.jar
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/lib/websocket/websocket-servlet-TEST.jar
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/lib/websocket/websocket-servlet-TEST.jar
diff --git a/jetty-start/src/test/resources/usecases/home/modules/annotations.mod b/jetty-start/src/test/resources/usecases/home/modules/annotations.mod
new file mode 100644
index 0000000..65e4654
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/annotations.mod
@@ -0,0 +1,17 @@
+#
+# Jetty Annotation Scanning Module
+#
+
+[depend]
+# Annotations needs plus, and jndi features
+plus
+
+[lib]
+# Annotations needs jetty annotation jars
+lib/jetty-annotations-${jetty.version}.jar
+# Need annotation processing jars too
+lib/annotations/*.jar
+
+[xml]
+# Enable annotation scanning webapp configurations
+etc/jetty-annotations.xml
diff --git a/jetty-start/src/test/resources/usecases/home/modules/base.mod b/jetty-start/src/test/resources/usecases/home/modules/base.mod
new file mode 100644
index 0000000..ad8ea32
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/base.mod
@@ -0,0 +1,11 @@
+#
+# Base Module
+#
+
+[optional]
+# JMX is optional, if it appears in the module tree then depend on it
+jmx
+
+[lib]
+lib/jetty-util-${jetty.version}.jar
+lib/jetty-io-${jetty.version}.jar
diff --git a/jetty-start/src/test/resources/usecases/home/modules/client.mod b/jetty-start/src/test/resources/usecases/home/modules/client.mod
new file mode 100644
index 0000000..6788eac
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/client.mod
@@ -0,0 +1,7 @@
+#
+# Client Feature
+#
+
+[lib]
+# Client jars
+lib/jetty-client-${jetty.version}.jar
diff --git a/jetty-start/src/test/resources/usecases/home/modules/debug.mod b/jetty-start/src/test/resources/usecases/home/modules/debug.mod
new file mode 100644
index 0000000..f740ea2
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/debug.mod
@@ -0,0 +1,9 @@
+#
+# Debug module
+#
+
+[depend]
+server
+
+[xml]
+etc/jetty-debug.xml
diff --git a/jetty-start/src/test/resources/usecases/home/modules/deploy.mod b/jetty-start/src/test/resources/usecases/home/modules/deploy.mod
new file mode 100644
index 0000000..94c0e40
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/deploy.mod
@@ -0,0 +1,14 @@
+#
+# Deploy Feature
+#
+
+[depend]
+webapp
+
+[lib]
+# Deploy jars
+lib/jetty-deploy-${jetty.version}.jar
+
+[xml]
+# Deploy configuration
+etc/jetty-deploy.xml
diff --git a/jetty-start/src/test/resources/usecases/home/modules/ext.mod b/jetty-start/src/test/resources/usecases/home/modules/ext.mod
new file mode 100644
index 0000000..66c0519
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/ext.mod
@@ -0,0 +1,10 @@
+#
+# ext module
+#
+
+[lib]
+lib/ext/**.jar
+
+[files]
+lib/
+lib/ext/
diff --git a/jetty-start/src/test/resources/usecases/home/modules/http.mod b/jetty-start/src/test/resources/usecases/home/modules/http.mod
new file mode 100644
index 0000000..8515414
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/http.mod
@@ -0,0 +1,9 @@
+#
+# Jetty HTTP Server
+#
+
+[depend]
+server
+
+[xml]
+etc/jetty-http.xml
diff --git a/jetty-start/src/test/resources/usecases/home/modules/https.mod b/jetty-start/src/test/resources/usecases/home/modules/https.mod
new file mode 100644
index 0000000..281c5db
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/https.mod
@@ -0,0 +1,10 @@
+#
+# Jetty HTTP Server
+#
+
+[depend]
+server
+
+[xml]
+etc/jetty-ssl.xml
+etc/jetty-https.xml
diff --git a/jetty-start/src/test/resources/usecases/home/modules/ipaccess.mod b/jetty-start/src/test/resources/usecases/home/modules/ipaccess.mod
new file mode 100644
index 0000000..956ea0f
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/ipaccess.mod
@@ -0,0 +1,9 @@
+#
+# IPAccess module
+#
+
+[depend]
+server
+
+[xml]
+etc/jetty-ipaccess.xml
diff --git a/jetty-start/src/test/resources/usecases/home/modules/jaas.mod b/jetty-start/src/test/resources/usecases/home/modules/jaas.mod
new file mode 100644
index 0000000..9fb04f7
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/jaas.mod
@@ -0,0 +1,14 @@
+#
+# JAAS Feature
+#
+
+[depend]
+server
+
+[lib]
+# JAAS jars
+lib/jetty-jaas-${jetty.version}.jar
+
+[xml]
+# JAAS configuration
+etc/jetty-jaas.xml
diff --git a/jetty-start/src/test/resources/usecases/home/modules/jmx.mod b/jetty-start/src/test/resources/usecases/home/modules/jmx.mod
new file mode 100644
index 0000000..fd8740a
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/jmx.mod
@@ -0,0 +1,11 @@
+#
+# JMX Feature
+#
+
+[lib]
+# JMX jars (as defined in start.config)
+lib/jetty-jmx-${jetty.version}.jar
+
+[xml]
+# JMX configuration
+etc/jetty-jmx.xml
diff --git a/jetty-start/src/test/resources/usecases/home/modules/jndi.mod b/jetty-start/src/test/resources/usecases/home/modules/jndi.mod
new file mode 100644
index 0000000..33c077c
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/jndi.mod
@@ -0,0 +1,11 @@
+#
+# JNDI Support
+#
+
+[depend]
+server
+
+[lib]
+lib/jetty-jndi-${jetty.version}.jar
+lib/jndi/*.jar
+
diff --git a/jetty-start/src/test/resources/usecases/home/modules/jsp-impl/apache-jsp.mod b/jetty-start/src/test/resources/usecases/home/modules/jsp-impl/apache-jsp.mod
new file mode 100644
index 0000000..aed547c
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/jsp-impl/apache-jsp.mod
@@ -0,0 +1,10 @@
+#
+# Apache JSP Module
+#
+
+[name]
+jsp-impl
+
+[lib]
+lib/apache-jsp/*.jar
+
diff --git a/jetty-start/src/test/resources/usecases/home/modules/jsp-impl/glassfish-jsp.mod b/jetty-start/src/test/resources/usecases/home/modules/jsp-impl/glassfish-jsp.mod
new file mode 100644
index 0000000..130d2b3
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/jsp-impl/glassfish-jsp.mod
@@ -0,0 +1,8 @@
+#
+# Glassfish JSP Module
+#
+[name]
+jsp-impl
+
+[lib]
+lib/jsp/*.jar
diff --git a/jetty-start/src/test/resources/usecases/home/modules/jsp.mod b/jetty-start/src/test/resources/usecases/home/modules/jsp.mod
new file mode 100644
index 0000000..fa5b9fd
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/jsp.mod
@@ -0,0 +1,20 @@
+#
+# Jetty JSP Module
+#
+
+[depend]
+servlet
+jsp-impl/${jsp-impl}-jsp
+
+[ini-template]
+# JSP Configuration
+
+# Select JSP implementation, choices are
+#   glassfish : The reference implementation 
+#               default in jetty <= 9.1
+#   apache    : The apache version 
+#               default jetty >= 9.2
+jsp-impl=apache
+
+# To use a non-jdk compiler for JSP compilation when using glassfish uncomment next line
+# -Dorg.apache.jasper.compiler.disablejsr199=true
diff --git a/jetty-start/src/test/resources/usecases/home/modules/logging.mod b/jetty-start/src/test/resources/usecases/home/modules/logging.mod
new file mode 100644
index 0000000..a39bfe4
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/logging.mod
@@ -0,0 +1,31 @@
+#
+# Jetty std err/out logging
+#
+
+[xml]
+etc/jetty-logging.xml
+
+[files]
+logs/
+
+[lib]
+lib/logging/**.jar
+resources/
+
+[ini-template]
+## Logging Configuration
+# Configure jetty logging for default internal behavior STDERR output
+# -Dorg.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
+
+# Configure jetty logging for slf4j
+# -Dorg.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.Slf4jLog
+
+# Configure jetty logging for java.util.logging
+# -Dorg.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.JavaUtilLog
+
+# STDERR / STDOUT Logging
+# Number of days to retain logs
+# jetty.log.retain=90
+# Directory for logging output
+# Either a path relative to ${jetty.base} or an absolute path
+# jetty.logs=logs
diff --git a/jetty-start/src/test/resources/usecases/home/modules/lowresources.mod b/jetty-start/src/test/resources/usecases/home/modules/lowresources.mod
new file mode 100644
index 0000000..4ca96de
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/lowresources.mod
@@ -0,0 +1,9 @@
+#
+# Low Resources module
+#
+
+[depend]
+server
+
+[xml]
+etc/jetty-lowresources.xml
diff --git a/jetty-start/src/test/resources/usecases/home/modules/monitor.mod b/jetty-start/src/test/resources/usecases/home/modules/monitor.mod
new file mode 100644
index 0000000..67f006d
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/monitor.mod
@@ -0,0 +1,13 @@
+#
+# Jetty Monitor module
+#
+
+[depend]
+server
+client
+
+[lib]
+lib/jetty-monitor-${jetty.version}.jar
+
+[xml]
+etc/jetty-monitor.xml
\ No newline at end of file
diff --git a/jetty-start/src/test/resources/usecases/home/modules/plus.mod b/jetty-start/src/test/resources/usecases/home/modules/plus.mod
new file mode 100644
index 0000000..b781f00
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/plus.mod
@@ -0,0 +1,15 @@
+#
+# Jetty Proxy module
+#
+
+[depend]
+server
+security
+jndi
+
+[lib]
+lib/jetty-plus-${jetty.version}.jar
+
+[xml]
+# Plus requires configuration
+etc/jetty-plus.xml
diff --git a/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/alpn-1.7.0_40.mod b/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/alpn-1.7.0_40.mod
new file mode 100644
index 0000000..54d3731
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/alpn-1.7.0_40.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/7.1.0.v20141016/alpn-boot-7.1.0.v20141016.jar|lib/alpn/alpn-boot-7.1.0.v20141016.jar
+
+[exec]
+-Xbootclasspath/p:lib/alpn/alpn-boot-7.1.0.v20141016.jar
diff --git a/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/alpn-1.7.0_45.mod b/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/alpn-1.7.0_45.mod
new file mode 100644
index 0000000..54d3731
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/alpn-1.7.0_45.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/7.1.0.v20141016/alpn-boot-7.1.0.v20141016.jar|lib/alpn/alpn-boot-7.1.0.v20141016.jar
+
+[exec]
+-Xbootclasspath/p:lib/alpn/alpn-boot-7.1.0.v20141016.jar
diff --git a/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/alpn-1.7.0_51.mod b/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/alpn-1.7.0_51.mod
new file mode 100644
index 0000000..54d3731
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/alpn-1.7.0_51.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/7.1.0.v20141016/alpn-boot-7.1.0.v20141016.jar|lib/alpn/alpn-boot-7.1.0.v20141016.jar
+
+[exec]
+-Xbootclasspath/p:lib/alpn/alpn-boot-7.1.0.v20141016.jar
diff --git a/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/alpn-1.7.0_55.mod b/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/alpn-1.7.0_55.mod
new file mode 100644
index 0000000..54d3731
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/alpn-1.7.0_55.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/7.1.0.v20141016/alpn-boot-7.1.0.v20141016.jar|lib/alpn/alpn-boot-7.1.0.v20141016.jar
+
+[exec]
+-Xbootclasspath/p:lib/alpn/alpn-boot-7.1.0.v20141016.jar
diff --git a/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/alpn-1.7.0_60.mod b/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/alpn-1.7.0_60.mod
new file mode 100644
index 0000000..54d3731
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/alpn-1.7.0_60.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/7.1.0.v20141016/alpn-boot-7.1.0.v20141016.jar|lib/alpn/alpn-boot-7.1.0.v20141016.jar
+
+[exec]
+-Xbootclasspath/p:lib/alpn/alpn-boot-7.1.0.v20141016.jar
diff --git a/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/alpn-1.8.0.mod b/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/alpn-1.8.0.mod
new file mode 100644
index 0000000..a81732c
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/alpn-1.8.0.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/8.1.0.v20141016/alpn-boot-8.1.0.v20141016.jar|lib/alpn/alpn-boot-8.1.0.v20141016.jar
+
+[exec]
+-Xbootclasspath/p:lib/alpn/alpn-boot-8.1.0.v20141016.jar
diff --git a/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/alpn-1.8.0_05.mod b/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/alpn-1.8.0_05.mod
new file mode 100644
index 0000000..a81732c
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/alpn-1.8.0_05.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/8.1.0.v20141016/alpn-boot-8.1.0.v20141016.jar|lib/alpn/alpn-boot-8.1.0.v20141016.jar
+
+[exec]
+-Xbootclasspath/p:lib/alpn/alpn-boot-8.1.0.v20141016.jar
diff --git a/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/alpn.mod b/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/alpn.mod
new file mode 100644
index 0000000..0e399f0
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/alpn.mod
@@ -0,0 +1,36 @@
+# ALPN is provided via a -Xbootclasspath that modifies the secure connections
+# in java to support the ALPN layer needed for SPDY (and eventually HTTP/2)
+#
+# This modification has a tight dependency on specific recent updates of
+# Java 1.7 and Java 1.8
+# (Java versions prior to 1.7u40 are not supported)
+#
+# The alpn protonego module will use an appropriate alpn-boot jar for your
+# specific version of Java.
+#
+# IMPORTANT: Versions of Java that exist after this module was created are
+#            not guaranteed to work with existing alpn-boot jars, and might
+#            need a new alpn-boot to be created / tested / deployed by the
+#            Jetty project in order to provide support for these future
+#            Java versions.
+#
+# All versions of alpn-boot can be found at
+# http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/
+
+[name]
+protonego-impl
+
+[depend]
+protonego-impl/alpn-${java.version}
+
+[lib]
+lib/jetty-alpn-client-${jetty.version}.jar
+lib/jetty-alpn-server-${jetty.version}.jar
+
+[xml]
+etc/protonego-alpn.xml
+
+[files]
+lib/
+lib/alpn/
+
diff --git a/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/npn-1.7.0_04.mod b/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/npn-1.7.0_04.mod
new file mode 100644
index 0000000..007570b
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/npn-1.7.0_04.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.0.v20120525/npn-boot-1.1.0.v20120525.jar|lib/npn/npn-boot-1.1.0.v20120525.jar
+
+[exec]
+-Xbootclasspath/p:lib/npn/npn-boot-1.1.0.v20120525.jar
diff --git a/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/npn-1.7.0_05.mod b/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/npn-1.7.0_05.mod
new file mode 100644
index 0000000..007570b
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/npn-1.7.0_05.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.0.v20120525/npn-boot-1.1.0.v20120525.jar|lib/npn/npn-boot-1.1.0.v20120525.jar
+
+[exec]
+-Xbootclasspath/p:lib/npn/npn-boot-1.1.0.v20120525.jar
diff --git a/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/npn-1.7.0_06.mod b/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/npn-1.7.0_06.mod
new file mode 100644
index 0000000..868a7a7
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/npn-1.7.0_06.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.1.v20121030/npn-boot-1.1.1.v20121030.jar|lib/npn/npn-boot-1.1.1.v20121030.jar
+
+[exec]
+-Xbootclasspath/p:lib/npn/npn-boot-1.1.1.v20121030.jar
diff --git a/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/npn-1.7.0_07.mod b/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/npn-1.7.0_07.mod
new file mode 100644
index 0000000..868a7a7
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/npn-1.7.0_07.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.1.v20121030/npn-boot-1.1.1.v20121030.jar|lib/npn/npn-boot-1.1.1.v20121030.jar
+
+[exec]
+-Xbootclasspath/p:lib/npn/npn-boot-1.1.1.v20121030.jar
diff --git a/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/npn-1.7.0_09.mod b/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/npn-1.7.0_09.mod
new file mode 100644
index 0000000..20c1db2
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/npn-1.7.0_09.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.3.v20130313/npn-boot-1.1.3.v20130313.jar|lib/npn/npn-boot-1.1.3.v20130313.jar
+
+[exec]
+-Xbootclasspath/p:lib/npn/npn-boot-1.1.3.v20130313.jar
diff --git a/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/npn-1.7.0_10.mod b/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/npn-1.7.0_10.mod
new file mode 100644
index 0000000..20c1db2
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/npn-1.7.0_10.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.3.v20130313/npn-boot-1.1.3.v20130313.jar|lib/npn/npn-boot-1.1.3.v20130313.jar
+
+[exec]
+-Xbootclasspath/p:lib/npn/npn-boot-1.1.3.v20130313.jar
diff --git a/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/npn-1.7.0_11.mod b/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/npn-1.7.0_11.mod
new file mode 100644
index 0000000..20c1db2
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/npn-1.7.0_11.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.3.v20130313/npn-boot-1.1.3.v20130313.jar|lib/npn/npn-boot-1.1.3.v20130313.jar
+
+[exec]
+-Xbootclasspath/p:lib/npn/npn-boot-1.1.3.v20130313.jar
diff --git a/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/npn-1.7.0_13.mod b/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/npn-1.7.0_13.mod
new file mode 100644
index 0000000..1645a52
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/npn-1.7.0_13.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.4.v20130313/npn-boot-1.1.4.v20130313.jar|lib/npn/npn-boot-1.1.4.v20130313.jar
+
+[exec]
+-Xbootclasspath/p:lib/npn/npn-boot-1.1.4.v20130313.jar
diff --git a/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/npn-1.7.0_15.mod b/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/npn-1.7.0_15.mod
new file mode 100644
index 0000000..73bc090
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/npn-1.7.0_15.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.5.v20130313/npn-boot-1.1.5.v20130313.jar|lib/npn/npn-boot-1.1.5.v20130313.jar
+
+[exec]
+-Xbootclasspath/p:lib/npn/npn-boot-1.1.5.v20130313.jar
diff --git a/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/npn-1.7.0_17.mod b/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/npn-1.7.0_17.mod
new file mode 100644
index 0000000..73bc090
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/npn-1.7.0_17.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.5.v20130313/npn-boot-1.1.5.v20130313.jar|lib/npn/npn-boot-1.1.5.v20130313.jar
+
+[exec]
+-Xbootclasspath/p:lib/npn/npn-boot-1.1.5.v20130313.jar
diff --git a/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/npn-1.7.0_21.mod b/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/npn-1.7.0_21.mod
new file mode 100644
index 0000000..73bc090
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/npn-1.7.0_21.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.5.v20130313/npn-boot-1.1.5.v20130313.jar|lib/npn/npn-boot-1.1.5.v20130313.jar
+
+[exec]
+-Xbootclasspath/p:lib/npn/npn-boot-1.1.5.v20130313.jar
diff --git a/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/npn-1.7.0_25.mod b/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/npn-1.7.0_25.mod
new file mode 100644
index 0000000..73bc090
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/npn-1.7.0_25.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.5.v20130313/npn-boot-1.1.5.v20130313.jar|lib/npn/npn-boot-1.1.5.v20130313.jar
+
+[exec]
+-Xbootclasspath/p:lib/npn/npn-boot-1.1.5.v20130313.jar
diff --git a/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/npn-1.7.0_40.mod b/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/npn-1.7.0_40.mod
new file mode 100644
index 0000000..465e6f0
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/npn-1.7.0_40.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.6.v20130911/npn-boot-1.1.6.v20130911.jar|lib/npn/npn-boot-1.1.6.v20130911.jar
+
+[exec]
+-Xbootclasspath/p:lib/npn/npn-boot-1.1.6.v20130911.jar
diff --git a/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/npn-1.7.0_45.mod b/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/npn-1.7.0_45.mod
new file mode 100644
index 0000000..465e6f0
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/npn-1.7.0_45.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.6.v20130911/npn-boot-1.1.6.v20130911.jar|lib/npn/npn-boot-1.1.6.v20130911.jar
+
+[exec]
+-Xbootclasspath/p:lib/npn/npn-boot-1.1.6.v20130911.jar
diff --git a/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/npn-1.7.0_51.mod b/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/npn-1.7.0_51.mod
new file mode 100644
index 0000000..465e6f0
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/npn-1.7.0_51.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.6.v20130911/npn-boot-1.1.6.v20130911.jar|lib/npn/npn-boot-1.1.6.v20130911.jar
+
+[exec]
+-Xbootclasspath/p:lib/npn/npn-boot-1.1.6.v20130911.jar
diff --git a/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/npn-1.7.0_55.mod b/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/npn-1.7.0_55.mod
new file mode 100644
index 0000000..5f8704d
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/npn-1.7.0_55.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.8.v20141013/npn-boot-1.1.8.v20141013.jar|lib/npn/npn-boot-1.1.8.v20141013.jar
+
+[exec]
+-Xbootclasspath/p:lib/npn/npn-boot-1.1.8.v20141013.jar
diff --git a/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/npn-1.7.0_60.mod b/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/npn-1.7.0_60.mod
new file mode 100644
index 0000000..5f8704d
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/npn-1.7.0_60.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.8.v20141013/npn-boot-1.1.8.v20141013.jar|lib/npn/npn-boot-1.1.8.v20141013.jar
+
+[exec]
+-Xbootclasspath/p:lib/npn/npn-boot-1.1.8.v20141013.jar
diff --git a/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/npn.mod b/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/npn.mod
new file mode 100644
index 0000000..040aad1
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/protonego-impl/npn.mod
@@ -0,0 +1,31 @@
+# NPN is provided via a -Xbootclasspath that modifies the secure connections
+# in java to support the NPN layer needed for SPDY.
+#
+# This modification has a tight dependency on specific updates of Java 1.7.
+# (No support for Java 8 exists for npn / npn-boot, use alpn instead)
+#
+# The npn module will use an appropriate npn-boot jar for your specific
+# version of Java.
+#
+# IMPORTANT: Versions of Java that exist after this module was created are
+#            not guaranteed to work with existing npn-boot jars, and might
+#            need a new npn-boot to be created / tested / deployed by the
+#            Jetty project in order to provide support for these future
+#            Java versions.
+#
+# All versions of npn-boot can be found at
+# http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/
+
+
+[name]
+protonego-impl
+
+[depend]
+protonego-impl/npn-${java.version}
+
+[xml]
+etc/protonego-npn.xml
+
+[files]
+lib/
+lib/npn/
diff --git a/jetty-start/src/test/resources/usecases/home/modules/protonego.mod b/jetty-start/src/test/resources/usecases/home/modules/protonego.mod
new file mode 100644
index 0000000..d7bba9f
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/protonego.mod
@@ -0,0 +1,15 @@
+#
+# Protocol Negotiatin Selection Module
+#
+
+[depend]
+protonego-impl/${protonego}
+
+[ini-template]
+# Protocol Negotiation Implementation Selection
+#  choices are:
+#    'npn'  : original implementation for SPDY (now deprecated)
+#    'alpn' : replacement for NPN, in use by current SPDY implementations
+#             and the future HTTP/2 spec
+#  Note: java 1.8+ are ALPN only.
+protonego=alpn
diff --git a/jetty-start/src/test/resources/usecases/home/modules/proxy.mod b/jetty-start/src/test/resources/usecases/home/modules/proxy.mod
new file mode 100644
index 0000000..7873329
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/proxy.mod
@@ -0,0 +1,14 @@
+#
+# Jetty Proxy module
+#
+
+[depend]
+server
+client
+
+[lib]
+lib/jetty-proxy-${jetty.version}.jar
+
+[xml]
+# Proxy requires configuration
+etc/jetty-proxy.xml
diff --git a/jetty-start/src/test/resources/usecases/home/modules/requestlog.mod b/jetty-start/src/test/resources/usecases/home/modules/requestlog.mod
new file mode 100644
index 0000000..2b048db
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/requestlog.mod
@@ -0,0 +1,9 @@
+#
+# Request Log module
+#
+
+[depend]
+server
+
+[xml]
+etc/jetty-requestlog.xml
diff --git a/jetty-start/src/test/resources/usecases/home/modules/resources.mod b/jetty-start/src/test/resources/usecases/home/modules/resources.mod
new file mode 100644
index 0000000..8647d81
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/resources.mod
@@ -0,0 +1,10 @@
+#
+# Module to add resources directory to classpath
+#
+
+[lib]
+resources/
+
+[files]
+resources/
+
diff --git a/jetty-start/src/test/resources/usecases/home/modules/rewrite.mod b/jetty-start/src/test/resources/usecases/home/modules/rewrite.mod
new file mode 100644
index 0000000..85fe5f0
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/rewrite.mod
@@ -0,0 +1,13 @@
+#
+# Jetty Rewrite module
+#
+
+[depend]
+server
+
+[lib]
+lib/jetty-rewrite-${jetty.version}.jar
+
+[xml]
+# Annotations needs annotations configuration
+etc/jetty-rewrite.xml
diff --git a/jetty-start/src/test/resources/usecases/home/modules/security.mod b/jetty-start/src/test/resources/usecases/home/modules/security.mod
new file mode 100644
index 0000000..ba31632
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/security.mod
@@ -0,0 +1,9 @@
+#
+# Jetty Security Module
+#
+
+[depend]
+server
+
+[lib]
+lib/jetty-security-${jetty.version}.jar
diff --git a/jetty-start/src/test/resources/usecases/home/modules/server.mod b/jetty-start/src/test/resources/usecases/home/modules/server.mod
new file mode 100644
index 0000000..b1e9f33
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/server.mod
@@ -0,0 +1,21 @@
+#
+# Base server
+#
+
+[optional]
+ext
+
+[depend]
+base
+xml
+
+[lib]
+lib/servlet-api-3.1.jar
+lib/jetty-schemas-3.1.jar
+lib/jetty-http-${jetty.version}.jar
+lib/jetty-continuation-${jetty.version}.jar
+lib/jetty-server-${jetty.version}.jar
+
+[xml]
+# Annotations needs annotations configuration
+etc/jetty.xml
diff --git a/jetty-start/src/test/resources/usecases/home/modules/servlet.mod b/jetty-start/src/test/resources/usecases/home/modules/servlet.mod
new file mode 100644
index 0000000..fdb65c5
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/servlet.mod
@@ -0,0 +1,9 @@
+#
+# Jetty Servlet Module
+#
+
+[depend]
+server
+
+[lib]
+lib/jetty-servlet-${jetty.version}.jar
diff --git a/jetty-start/src/test/resources/usecases/home/modules/spdy.mod b/jetty-start/src/test/resources/usecases/home/modules/spdy.mod
new file mode 100644
index 0000000..cf79dfa
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/spdy.mod
@@ -0,0 +1,26 @@
+#
+# SPDY Support Module
+#
+
+[depend]
+ssl
+protonego
+
+[lib]
+lib/spdy/*.jar
+
+[xml]
+etc/jetty-ssl.xml
+etc/jetty-spdy.xml
+
+[ini-template]
+## SPDY Configuration
+
+# Port for SPDY connections
+spdy.port=8443
+
+# SPDY idle timeout in milliseconds
+spdy.timeout=30000
+
+# Initial Window Size for SPDY
+#spdy.initialWindowSize=65536
diff --git a/jetty-start/src/test/resources/usecases/home/modules/ssl.mod b/jetty-start/src/test/resources/usecases/home/modules/ssl.mod
new file mode 100644
index 0000000..449f581
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/ssl.mod
@@ -0,0 +1,35 @@
+#
+# SSL Keystore module
+#
+
+[depend]
+server
+
+[xml]
+etc/jetty-ssl.xml
+
+[files]
+http://git.eclipse.org/c/jetty/org.eclipse.jetty.project.git/plain/jetty-server/src/main/config/etc/keystore|etc/keystore
+
+[ini-template]
+## SSL Keystore Configuration
+# define the port to use for secure redirection
+jetty.secure.port=8443
+
+# Setup a demonstration keystore and truststore
+jetty.keystore=etc/keystore
+jetty.truststore=etc/keystore
+
+# Set the demonstration passwords.
+# Note that OBF passwords are not secure, just protected from casual observation
+# See http://www.eclipse.org/jetty/documentation/current/configuring-security-secure-passwords.html
+jetty.keystore.password=OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4
+jetty.keymanager.password=OBF:1u2u1wml1z7s1z7a1wnl1u2g
+jetty.truststore.password=OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4
+
+# Set the client auth behavior
+# Set to true if client certificate authentication is required
+# jetty.ssl.needClientAuth=true
+# Set to true if client certificate authentication is desired
+# jetty.ssl.wantClientAuth=true
+
diff --git a/jetty-start/src/test/resources/usecases/home/modules/stats.mod b/jetty-start/src/test/resources/usecases/home/modules/stats.mod
new file mode 100644
index 0000000..0922469
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/stats.mod
@@ -0,0 +1,9 @@
+#
+# Stats module
+#
+
+[depend]
+server
+
+[xml]
+etc/jetty-stats.xml
diff --git a/jetty-start/src/test/resources/usecases/home/modules/webapp.mod b/jetty-start/src/test/resources/usecases/home/modules/webapp.mod
new file mode 100644
index 0000000..f62c554
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/webapp.mod
@@ -0,0 +1,9 @@
+#
+# Base server
+#
+
+[depend]
+servlet
+
+[lib]
+lib/jetty-webapp-${jetty.version}.jar
diff --git a/jetty-start/src/test/resources/usecases/home/modules/websocket.mod b/jetty-start/src/test/resources/usecases/home/modules/websocket.mod
new file mode 100644
index 0000000..f45babd
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/websocket.mod
@@ -0,0 +1,17 @@
+#
+# WebSocket Feature
+#
+
+# WebSocket needs Annotations feature
+[depend]
+server
+annotations
+
+# WebSocket needs websocket jars (as defined in start.config)
+[lib]
+lib/websocket/*.jar
+
+# WebSocket needs websocket configuration
+[xml]
+etc/jetty-websockets.xml
+
diff --git a/jetty-start/src/test/resources/usecases/home/modules/xinetd.mod b/jetty-start/src/test/resources/usecases/home/modules/xinetd.mod
new file mode 100644
index 0000000..fdc1b3c
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/xinetd.mod
@@ -0,0 +1,9 @@
+#
+# Stats module
+#
+
+[depend]
+server
+
+[xml]
+etc/jetty-xinetd.xml
diff --git a/jetty-start/src/test/resources/usecases/home/modules/xml.mod b/jetty-start/src/test/resources/usecases/home/modules/xml.mod
new file mode 100644
index 0000000..d53107a
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/xml.mod
@@ -0,0 +1,10 @@
+#
+# Jetty XML Configuration
+#
+
+[depend]
+base
+
+[lib]
+lib/jetty-xml-${jetty.version}.jar
+
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-start/src/test/resources/usecases/home/resources/.nodelete
similarity index 100%
copy from jetty-distribution/src/main/resources/webapps/.donotdelete
copy to jetty-start/src/test/resources/usecases/home/resources/.nodelete
diff --git a/jetty-start/src/test/resources/usecases/home/start.ini b/jetty-start/src/test/resources/usecases/home/start.ini
new file mode 100644
index 0000000..a65a9cb
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/start.ini
@@ -0,0 +1,2 @@
+
+--module=server,http,jmx,annotations,websocket
diff --git a/jetty-util-ajax/pom.xml b/jetty-util-ajax/pom.xml
new file mode 100644
index 0000000..db1c0ac
--- /dev/null
+++ b/jetty-util-ajax/pom.xml
@@ -0,0 +1,91 @@
+<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">
+  <parent>
+    <groupId>org.eclipse.jetty</groupId>
+    <artifactId>jetty-project</artifactId>
+    <version>9.2.13.v20150730</version>
+  </parent>
+  <modelVersion>4.0.0</modelVersion>
+  <artifactId>jetty-util-ajax</artifactId>
+  <name>Jetty :: Utilities :: Ajax(JSON)</name>
+  <description>JSON/Ajax Utility classes for Jetty</description>
+  <url>http://www.eclipse.org/jetty</url>
+  <properties>
+    <bundle-symbolic-name>${project.groupId}.util.ajax</bundle-symbolic-name>
+  </properties>
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>maven-bundle-plugin</artifactId>
+        <extensions>true</extensions>
+        <executions>
+          <execution>
+            <goals>
+              <goal>manifest</goal>
+            </goals>
+            <configuration>
+              <instructions>
+                <Import-Package>javax.servlet.*;version="[2.6.0,3.2)",org.slf4j;version="[1.5,2.0)";resolution:=optional,org.slf4j.impl;version="[1.5,2.0)";resolution:=optional,*</Import-Package>
+              </instructions>
+            </configuration>
+           </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <!--
+        Required for OSGI
+        -->
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-jar-plugin</artifactId>
+        <configuration>
+          <archive>
+            <manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
+          </archive>
+        </configuration>
+      </plugin>
+<!--
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-assembly-plugin</artifactId>
+        <executions>
+          <execution>
+            <phase>package</phase>
+            <goals>
+              <goal>single</goal>
+            </goals>
+            <configuration>
+              <descriptorRefs>
+                <descriptorRef>config</descriptorRef>
+              </descriptorRefs>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+-->
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>findbugs-maven-plugin</artifactId>
+        <configuration>
+          <onlyAnalyze>org.eclipse.jetty.util.ajax.*</onlyAnalyze>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+  <dependencies>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-util</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>javax.servlet</groupId>
+      <artifactId>javax.servlet-api</artifactId>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.toolchain</groupId>
+      <artifactId>jetty-test-helper</artifactId>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/jetty-util-ajax/src/main/java/org/eclipse/jetty/util/ajax/JSON.java b/jetty-util-ajax/src/main/java/org/eclipse/jetty/util/ajax/JSON.java
new file mode 100644
index 0000000..ada3663
--- /dev/null
+++ b/jetty-util-ajax/src/main/java/org/eclipse/jetty/util/ajax/JSON.java
@@ -0,0 +1,1640 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.ajax;
+
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.lang.reflect.Array;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.Loader;
+import org.eclipse.jetty.util.QuotedStringTokenizer;
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * JSON Parser and Generator.
+ * <p />
+ * This class provides some static methods to convert POJOs to and from JSON
+ * notation. The mapping from JSON to java is:
+ *
+ * <pre>
+ *   object ==> Map
+ *   array  ==> Object[]
+ *   number ==> Double or Long
+ *   string ==> String
+ *   null   ==> null
+ *   bool   ==> Boolean
+ * </pre>
+
+ * The java to JSON mapping is:
+ *
+ * <pre>
+ *   String --> string
+ *   Number --> number
+ *   Map    --> object
+ *   List   --> array
+ *   Array  --> array
+ *   null   --> null
+ *   Boolean--> boolean
+ *   Object --> string (dubious!)
+ * </pre>
+ *
+ * The interface {@link JSON.Convertible} may be implemented by classes that
+ * wish to externalize and initialize specific fields to and from JSON objects.
+ * Only directed acyclic graphs of objects are supported.
+ * <p />
+ * The interface {@link JSON.Generator} may be implemented by classes that know
+ * how to render themselves as JSON and the {@link #toString(Object)} method
+ * will use {@link JSON.Generator#addJSON(Appendable)} to generate the JSON.
+ * The class {@link JSON.Literal} may be used to hold pre-generated JSON object.
+ * <p />
+ * The interface {@link JSON.Convertor} may be implemented to provide static
+ * converters for objects that may be registered with
+ * {@link #registerConvertor(Class, Convertor)}.
+ * These converters are looked up by class, interface and super class by
+ * {@link #getConvertor(Class)}.
+ * <p />
+ * If a JSON object has a "class" field, then a java class for that name is
+ * loaded and the method {@link #convertTo(Class,Map)} is used to find a
+ * {@link JSON.Convertor} for that class.
+ * <p />
+ * If a JSON object has a "x-class" field then a direct lookup for a
+ * {@link JSON.Convertor} for that class name is done (without loading the class).
+ */
+public class JSON
+{
+    static final Logger LOG = Log.getLogger(JSON.class);
+    public final static JSON DEFAULT = new JSON();
+
+    private Map<String, Convertor> _convertors = new ConcurrentHashMap<String, Convertor>();
+    private int _stringBufferSize = 1024;
+
+    public JSON()
+    {
+    }
+
+    /**
+     * @return the initial stringBuffer size to use when creating JSON strings
+     *         (default 1024)
+     */
+    public int getStringBufferSize()
+    {
+        return _stringBufferSize;
+    }
+
+    /**
+     * @param stringBufferSize
+     *            the initial stringBuffer size to use when creating JSON
+     *            strings (default 1024)
+     */
+    public void setStringBufferSize(int stringBufferSize)
+    {
+        _stringBufferSize = stringBufferSize;
+    }
+
+    /**
+     * Register a {@link Convertor} for a class or interface.
+     *
+     * @param forClass
+     *            The class or interface that the convertor applies to
+     * @param convertor
+     *            the convertor
+     */
+    public static void registerConvertor(Class forClass, Convertor convertor)
+    {
+        DEFAULT.addConvertor(forClass,convertor);
+    }
+
+    public static JSON getDefault()
+    {
+        return DEFAULT;
+    }
+
+    @Deprecated
+    public static void setDefault(JSON json)
+    {
+    }
+
+    public static String toString(Object object)
+    {
+        StringBuilder buffer = new StringBuilder(DEFAULT.getStringBufferSize());
+        DEFAULT.append(buffer,object);
+        return buffer.toString();
+    }
+
+    public static String toString(Map object)
+    {
+        StringBuilder buffer = new StringBuilder(DEFAULT.getStringBufferSize());
+        DEFAULT.appendMap(buffer,object);
+        return buffer.toString();
+    }
+
+    public static String toString(Object[] array)
+    {
+        StringBuilder buffer = new StringBuilder(DEFAULT.getStringBufferSize());
+        DEFAULT.appendArray(buffer,array);
+        return buffer.toString();
+    }
+
+    /**
+     * @param s
+     *            String containing JSON object or array.
+     * @return A Map, Object array or primitive array parsed from the JSON.
+     */
+    public static Object parse(String s)
+    {
+        return DEFAULT.parse(new StringSource(s),false);
+    }
+
+    /**
+     * @param s
+     *            String containing JSON object or array.
+     * @param stripOuterComment
+     *            If true, an outer comment around the JSON is ignored.
+     * @return A Map, Object array or primitive array parsed from the JSON.
+     */
+    public static Object parse(String s, boolean stripOuterComment)
+    {
+        return DEFAULT.parse(new StringSource(s),stripOuterComment);
+    }
+
+    /**
+     * @param in
+     *            Reader containing JSON object or array.
+     * @return A Map, Object array or primitive array parsed from the JSON.
+     */
+    public static Object parse(Reader in) throws IOException
+    {
+        return DEFAULT.parse(new ReaderSource(in),false);
+    }
+
+    /**
+     * @param in
+     *            Reader containing JSON object or array.
+     * @param stripOuterComment
+     *            If true, an outer comment around the JSON is ignored.
+     * @return A Map, Object array or primitive array parsed from the JSON.
+     */
+    public static Object parse(Reader in, boolean stripOuterComment) throws IOException
+    {
+        return DEFAULT.parse(new ReaderSource(in),stripOuterComment);
+    }
+
+    /**
+     * @deprecated use {@link #parse(Reader)}
+     * @param in
+     *            Reader containing JSON object or array.
+     * @return A Map, Object array or primitive array parsed from the JSON.
+     */
+    @Deprecated
+    public static Object parse(InputStream in) throws IOException
+    {
+        return DEFAULT.parse(new StringSource(IO.toString(in)),false);
+    }
+
+    /**
+     * @deprecated use {@link #parse(Reader, boolean)}
+     * @param in
+     *            Stream containing JSON object or array.
+     * @param stripOuterComment
+     *            If true, an outer comment around the JSON is ignored.
+     * @return A Map, Object array or primitive array parsed from the JSON.
+     */
+    @Deprecated
+    public static Object parse(InputStream in, boolean stripOuterComment) throws IOException
+    {
+        return DEFAULT.parse(new StringSource(IO.toString(in)),stripOuterComment);
+    }
+
+    /**
+     * Convert Object to JSON
+     *
+     * @param object
+     *            The object to convert
+     * @return The JSON String
+     */
+    public String toJSON(Object object)
+    {
+        StringBuilder buffer = new StringBuilder(getStringBufferSize());
+        append(buffer,object);
+        return buffer.toString();
+    }
+
+    /**
+     * Convert JSON to Object
+     *
+     * @param json
+     *            The json to convert
+     * @return The object
+     */
+    public Object fromJSON(String json)
+    {
+        Source source = new StringSource(json);
+        return parse(source);
+    }
+
+    @Deprecated
+    public void append(StringBuffer buffer, Object object)
+    {
+        append((Appendable)buffer,object);
+    }
+
+    /**
+     * Append object as JSON to string buffer.
+     *
+     * @param buffer
+     *            the buffer to append to
+     * @param object
+     *            the object to append
+     */
+    public void append(Appendable buffer, Object object)
+    {
+        try
+        {
+            if (object == null)
+            {
+                buffer.append("null");
+            }
+            // Most likely first
+            else if (object instanceof Map)
+            {
+                appendMap(buffer,(Map)object);
+            }
+            else if (object instanceof String)
+            {
+                appendString(buffer,(String)object);
+            }
+            else if (object instanceof Number)
+            {
+                appendNumber(buffer,(Number)object);
+            }
+            else if (object instanceof Boolean)
+            {
+                appendBoolean(buffer,(Boolean)object);
+            }
+            else if (object.getClass().isArray())
+            {
+                appendArray(buffer,object);
+            }
+            else if (object instanceof Character)
+            {
+                appendString(buffer,object.toString());
+            }
+            else if (object instanceof Convertible)
+            {
+                appendJSON(buffer,(Convertible)object);
+            }
+            else if (object instanceof Generator)
+            {
+                appendJSON(buffer,(Generator)object);
+            }
+            else
+            {
+                // Check Convertor before Collection to support JSONCollectionConvertor
+                Convertor convertor = getConvertor(object.getClass());
+                if (convertor != null)
+                {
+                    appendJSON(buffer,convertor,object);
+                }
+                else if (object instanceof Collection)
+                {
+                    appendArray(buffer,(Collection)object);
+                }
+                else
+                {
+                    appendString(buffer,object.toString());
+                }
+            }
+        }
+        catch (IOException e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Deprecated
+    public void appendNull(StringBuffer buffer)
+    {
+        appendNull((Appendable)buffer);
+    }
+
+    public void appendNull(Appendable buffer)
+    {
+        try
+        {
+            buffer.append("null");
+        }
+        catch (IOException e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Deprecated
+    public void appendJSON(final StringBuffer buffer, final Convertor convertor, final Object object)
+    {
+        appendJSON((Appendable)buffer,convertor,object);
+    }
+
+    public void appendJSON(final Appendable buffer, final Convertor convertor, final Object object)
+    {
+        appendJSON(buffer,new Convertible()
+        {
+            public void fromJSON(Map object)
+            {
+            }
+
+            public void toJSON(Output out)
+            {
+                convertor.toJSON(object,out);
+            }
+        });
+    }
+
+    @Deprecated
+    public void appendJSON(final StringBuffer buffer, Convertible converter)
+    {
+        appendJSON((Appendable)buffer,converter);
+    }
+
+    public void appendJSON(final Appendable buffer, Convertible converter)
+    {
+        ConvertableOutput out=new ConvertableOutput(buffer);
+        converter.toJSON(out);
+        out.complete();
+    }
+
+    @Deprecated
+    public void appendJSON(StringBuffer buffer, Generator generator)
+    {
+        generator.addJSON(buffer);
+    }
+
+    public void appendJSON(Appendable buffer, Generator generator)
+    {
+        generator.addJSON(buffer);
+    }
+
+    @Deprecated
+    public void appendMap(StringBuffer buffer, Map<?,?> map)
+    {
+        appendMap((Appendable)buffer,map);
+    }
+
+    public void appendMap(Appendable buffer, Map<?,?> map)
+    {
+        try
+        {
+            if (map == null)
+            {
+                appendNull(buffer);
+                return;
+            }
+
+            buffer.append('{');
+            Iterator<?> iter = map.entrySet().iterator();
+            while (iter.hasNext())
+            {
+                Map.Entry<?,?> entry = (Map.Entry<?,?>)iter.next();
+                QuotedStringTokenizer.quote(buffer,entry.getKey().toString());
+                buffer.append(':');
+                append(buffer,entry.getValue());
+                if (iter.hasNext())
+                    buffer.append(',');
+            }
+
+            buffer.append('}');
+        }
+        catch (IOException e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Deprecated
+    public void appendArray(StringBuffer buffer, Collection collection)
+    {
+        appendArray((Appendable)buffer,collection);
+    }
+
+    public void appendArray(Appendable buffer, Collection collection)
+    {
+        try
+        {
+            if (collection == null)
+            {
+                appendNull(buffer);
+                return;
+            }
+
+            buffer.append('[');
+            Iterator iter = collection.iterator();
+            boolean first = true;
+            while (iter.hasNext())
+            {
+                if (!first)
+                    buffer.append(',');
+
+                first = false;
+                append(buffer,iter.next());
+            }
+
+            buffer.append(']');
+        }
+        catch (IOException e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Deprecated
+    public void appendArray(StringBuffer buffer, Object array)
+    {
+    appendArray((Appendable)buffer,array);
+    }
+
+    public void appendArray(Appendable buffer, Object array)
+    {
+        try
+        {
+            if (array == null)
+            {
+                appendNull(buffer);
+                return;
+            }
+
+            buffer.append('[');
+            int length = Array.getLength(array);
+
+            for (int i = 0; i < length; i++)
+            {
+                if (i != 0)
+                    buffer.append(',');
+                append(buffer,Array.get(array,i));
+            }
+
+            buffer.append(']');
+        }
+        catch (IOException e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Deprecated
+    public void appendBoolean(StringBuffer buffer, Boolean b)
+    {
+        appendBoolean((Appendable)buffer,b);
+    }
+
+    public void appendBoolean(Appendable buffer, Boolean b)
+    {
+        try
+        {
+            if (b == null)
+            {
+                appendNull(buffer);
+                return;
+            }
+            buffer.append(b?"true":"false");
+        }
+        catch (IOException e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Deprecated
+    public void appendNumber(StringBuffer buffer, Number number)
+    {
+        appendNumber((Appendable)buffer,number);
+    }
+
+    public void appendNumber(Appendable buffer, Number number)
+    {
+        try
+        {
+            if (number == null)
+            {
+                appendNull(buffer);
+                return;
+            }
+            buffer.append(String.valueOf(number));
+        }
+        catch (IOException e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Deprecated
+    public void appendString(StringBuffer buffer, String string)
+    {
+        appendString((Appendable)buffer,string);
+    }
+
+    public void appendString(Appendable buffer, String string)
+    {
+        if (string == null)
+        {
+            appendNull(buffer);
+            return;
+        }
+
+        QuotedStringTokenizer.quote(buffer,string);
+    }
+
+    // Parsing utilities
+
+    protected String toString(char[] buffer, int offset, int length)
+    {
+        return new String(buffer,offset,length);
+    }
+
+    protected Map<String, Object> newMap()
+    {
+        return new HashMap<String, Object>();
+    }
+
+    protected Object[] newArray(int size)
+    {
+        return new Object[size];
+    }
+
+    protected JSON contextForArray()
+    {
+        return this;
+    }
+
+    protected JSON contextFor(String field)
+    {
+        return this;
+    }
+
+    protected Object convertTo(Class type, Map map)
+    {
+        if (type != null && Convertible.class.isAssignableFrom(type))
+        {
+            try
+            {
+                Convertible conv = (Convertible)type.newInstance();
+                conv.fromJSON(map);
+                return conv;
+            }
+            catch (Exception e)
+            {
+                throw new RuntimeException(e);
+            }
+        }
+
+        Convertor convertor = getConvertor(type);
+        if (convertor != null)
+        {
+            return convertor.fromJSON(map);
+        }
+        return map;
+    }
+
+    /**
+     * Register a {@link Convertor} for a class or interface.
+     *
+     * @param forClass
+     *            The class or interface that the convertor applies to
+     * @param convertor
+     *            the convertor
+     */
+    public void addConvertor(Class forClass, Convertor convertor)
+    {
+        _convertors.put(forClass.getName(),convertor);
+    }
+
+    /**
+     * Lookup a convertor for a class.
+     * <p>
+     * If no match is found for the class, then the interfaces for the class are
+     * tried. If still no match is found, then the super class and it's
+     * interfaces are tried recursively.
+     *
+     * @param forClass
+     *            The class
+     * @return a {@link JSON.Convertor} or null if none were found.
+     */
+    protected Convertor getConvertor(Class forClass)
+    {
+        Class cls = forClass;
+        Convertor convertor = _convertors.get(cls.getName());
+        if (convertor == null && this != DEFAULT)
+            convertor = DEFAULT.getConvertor(cls);
+
+        while (convertor == null && cls != Object.class)
+        {
+            Class[] ifs = cls.getInterfaces();
+            int i = 0;
+            while (convertor == null && ifs != null && i < ifs.length)
+                convertor = _convertors.get(ifs[i++].getName());
+            if (convertor == null)
+            {
+                cls = cls.getSuperclass();
+                convertor = _convertors.get(cls.getName());
+            }
+        }
+        return convertor;
+    }
+
+    /**
+     * Register a {@link JSON.Convertor} for a named class or interface.
+     *
+     * @param name
+     *            name of a class or an interface that the convertor applies to
+     * @param convertor
+     *            the convertor
+     */
+    public void addConvertorFor(String name, Convertor convertor)
+    {
+        _convertors.put(name,convertor);
+    }
+
+    /**
+     * Lookup a convertor for a named class.
+     *
+     * @param name
+     *            name of the class
+     * @return a {@link JSON.Convertor} or null if none were found.
+     */
+    public Convertor getConvertorFor(String name)
+    {
+        Convertor convertor = _convertors.get(name);
+        if (convertor == null && this != DEFAULT)
+            convertor = DEFAULT.getConvertorFor(name);
+        return convertor;
+    }
+
+    public Object parse(Source source, boolean stripOuterComment)
+    {
+        int comment_state = 0; // 0=no comment, 1="/", 2="/*", 3="/* *" -1="//"
+        if (!stripOuterComment)
+            return parse(source);
+
+        int strip_state = 1; // 0=no strip, 1=wait for /*, 2= wait for */
+
+        Object o = null;
+        while (source.hasNext())
+        {
+            char c = source.peek();
+
+            // handle // or /* comment
+            if (comment_state == 1)
+            {
+                switch (c)
+                {
+                    case '/':
+                        comment_state = -1;
+                        break;
+                    case '*':
+                        comment_state = 2;
+                        if (strip_state == 1)
+                        {
+                            comment_state = 0;
+                            strip_state = 2;
+                        }
+                }
+            }
+            // handle /* */ comment
+            else if (comment_state > 1)
+            {
+                switch (c)
+                {
+                    case '*':
+                        comment_state = 3;
+                        break;
+                    case '/':
+                        if (comment_state == 3)
+                        {
+                            comment_state = 0;
+                            if (strip_state == 2)
+                                return o;
+                        }
+                        else
+                            comment_state = 2;
+                        break;
+                    default:
+                        comment_state = 2;
+                }
+            }
+            // handle // comment
+            else if (comment_state < 0)
+            {
+                switch (c)
+                {
+                    case '\r':
+                    case '\n':
+                        comment_state = 0;
+                    default:
+                        break;
+                }
+            }
+            // handle unknown
+            else
+            {
+                if (!Character.isWhitespace(c))
+                {
+                    if (c == '/')
+                        comment_state = 1;
+                    else if (c == '*')
+                        comment_state = 3;
+                    else if (o == null)
+                    {
+                        o = parse(source);
+                        continue;
+                    }
+                }
+            }
+
+            source.next();
+        }
+
+        return o;
+    }
+
+    public Object parse(Source source)
+    {
+        int comment_state = 0; // 0=no comment, 1="/", 2="/*", 3="/* *" -1="//"
+
+        while (source.hasNext())
+        {
+            char c = source.peek();
+
+            // handle // or /* comment
+            if (comment_state == 1)
+            {
+                switch (c)
+                {
+                    case '/':
+                        comment_state = -1;
+                        break;
+                    case '*':
+                        comment_state = 2;
+                }
+            }
+            // handle /* */ comment
+            else if (comment_state > 1)
+            {
+                switch (c)
+                {
+                    case '*':
+                        comment_state = 3;
+                        break;
+                    case '/':
+                        if (comment_state == 3)
+                            comment_state = 0;
+                        else
+                            comment_state = 2;
+                        break;
+                    default:
+                        comment_state = 2;
+                }
+            }
+            // handle // comment
+            else if (comment_state < 0)
+            {
+                switch (c)
+                {
+                    case '\r':
+                    case '\n':
+                        comment_state = 0;
+                        break;
+                    default:
+                        break;
+                }
+            }
+            // handle unknown
+            else
+            {
+                switch (c)
+                {
+                    case '{':
+                        return parseObject(source);
+                    case '[':
+                        return parseArray(source);
+                    case '"':
+                        return parseString(source);
+                    case '-':
+                        return parseNumber(source);
+
+                    case 'n':
+                        complete("null",source);
+                        return null;
+                    case 't':
+                        complete("true",source);
+                        return Boolean.TRUE;
+                    case 'f':
+                        complete("false",source);
+                        return Boolean.FALSE;
+                    case 'u':
+                        complete("undefined",source);
+                        return null;
+                    case 'N':
+                        complete("NaN",source);
+                        return null;
+
+                    case '/':
+                        comment_state = 1;
+                        break;
+
+                    default:
+                        if (Character.isDigit(c))
+                            return parseNumber(source);
+                        else if (Character.isWhitespace(c))
+                            break;
+                        return handleUnknown(source,c);
+                }
+            }
+            source.next();
+        }
+
+        return null;
+    }
+
+    protected Object handleUnknown(Source source, char c)
+    {
+        throw new IllegalStateException("unknown char '" + c + "'(" + (int)c + ") in " + source);
+    }
+
+    protected Object parseObject(Source source)
+    {
+        if (source.next() != '{')
+            throw new IllegalStateException();
+        Map<String, Object> map = newMap();
+
+        char next = seekTo("\"}",source);
+
+        while (source.hasNext())
+        {
+            if (next == '}')
+            {
+                source.next();
+                break;
+            }
+
+            String name = parseString(source);
+            seekTo(':',source);
+            source.next();
+
+            Object value = contextFor(name).parse(source);
+            map.put(name,value);
+
+            seekTo(",}",source);
+            next = source.next();
+            if (next == '}')
+                break;
+            else
+                next = seekTo("\"}",source);
+        }
+
+        String xclassname = (String)map.get("x-class");
+        if (xclassname != null)
+        {
+            Convertor c = getConvertorFor(xclassname);
+            if (c != null)
+                return c.fromJSON(map);
+            LOG.warn("No Convertor for x-class '{}'", xclassname);
+        }
+
+        String classname = (String)map.get("class");
+        if (classname != null)
+        {
+            try
+            {
+                Class c = Loader.loadClass(JSON.class,classname);
+                return convertTo(c,map);
+            }
+            catch (ClassNotFoundException e)
+            {
+                LOG.warn("No Class for '{}'", classname);
+            }
+        }
+
+        return map;
+    }
+
+    protected Object parseArray(Source source)
+    {
+        if (source.next() != '[')
+            throw new IllegalStateException();
+
+        int size = 0;
+        ArrayList list = null;
+        Object item = null;
+        boolean coma = true;
+
+        while (source.hasNext())
+        {
+            char c = source.peek();
+            switch (c)
+            {
+                case ']':
+                    source.next();
+                    switch (size)
+                    {
+                        case 0:
+                            return newArray(0);
+                        case 1:
+                            Object array = newArray(1);
+                            Array.set(array,0,item);
+                            return array;
+                        default:
+                            return list.toArray(newArray(list.size()));
+                    }
+
+                case ',':
+                    if (coma)
+                        throw new IllegalStateException();
+                    coma = true;
+                    source.next();
+                    break;
+
+                default:
+                    if (Character.isWhitespace(c))
+                        source.next();
+                    else
+                    {
+                        coma = false;
+                        if (size++ == 0)
+                            item = contextForArray().parse(source);
+                        else if (list == null)
+                        {
+                            list = new ArrayList();
+                            list.add(item);
+                            item = contextForArray().parse(source);
+                            list.add(item);
+                            item = null;
+                        }
+                        else
+                        {
+                            item = contextForArray().parse(source);
+                            list.add(item);
+                            item = null;
+                        }
+                    }
+            }
+
+        }
+
+        throw new IllegalStateException("unexpected end of array");
+    }
+
+    protected String parseString(Source source)
+    {
+        if (source.next() != '"')
+            throw new IllegalStateException();
+
+        boolean escape = false;
+
+        StringBuilder b = null;
+        final char[] scratch = source.scratchBuffer();
+
+        if (scratch != null)
+        {
+            int i = 0;
+            while (source.hasNext())
+            {
+                if (i >= scratch.length)
+                {
+                    // we have filled the scratch buffer, so we must
+                    // use the StringBuffer for a large string
+                    b = new StringBuilder(scratch.length * 2);
+                    b.append(scratch,0,i);
+                    break;
+                }
+
+                char c = source.next();
+
+                if (escape)
+                {
+                    escape = false;
+                    switch (c)
+                    {
+                        case '"':
+                            scratch[i++] = '"';
+                            break;
+                        case '\\':
+                            scratch[i++] = '\\';
+                            break;
+                        case '/':
+                            scratch[i++] = '/';
+                            break;
+                        case 'b':
+                            scratch[i++] = '\b';
+                            break;
+                        case 'f':
+                            scratch[i++] = '\f';
+                            break;
+                        case 'n':
+                            scratch[i++] = '\n';
+                            break;
+                        case 'r':
+                            scratch[i++] = '\r';
+                            break;
+                        case 't':
+                            scratch[i++] = '\t';
+                            break;
+                        case 'u':
+                            char uc = (char)((TypeUtil.convertHexDigit((byte)source.next()) << 12) + (TypeUtil.convertHexDigit((byte)source.next()) << 8)
+                                    + (TypeUtil.convertHexDigit((byte)source.next()) << 4) + (TypeUtil.convertHexDigit((byte)source.next())));
+                            scratch[i++] = uc;
+                            break;
+                        default:
+                            scratch[i++] = c;
+                    }
+                }
+                else if (c == '\\')
+                {
+                    escape = true;
+                }
+                else if (c == '\"')
+                {
+                    // Return string that fits within scratch buffer
+                    return toString(scratch,0,i);
+                }
+                else
+                {
+                    scratch[i++] = c;
+                }
+            }
+
+            // Missing end quote, but return string anyway ?
+            if (b == null)
+                return toString(scratch,0,i);
+        }
+        else
+            b = new StringBuilder(getStringBufferSize());
+
+        // parse large string into string buffer
+        final StringBuilder builder=b;
+        while (source.hasNext())
+        {
+            char c = source.next();
+
+            if (escape)
+            {
+                escape = false;
+                switch (c)
+                {
+                    case '"':
+                        builder.append('"');
+                        break;
+                    case '\\':
+                        builder.append('\\');
+                        break;
+                    case '/':
+                        builder.append('/');
+                        break;
+                    case 'b':
+                        builder.append('\b');
+                        break;
+                    case 'f':
+                        builder.append('\f');
+                        break;
+                    case 'n':
+                        builder.append('\n');
+                        break;
+                    case 'r':
+                        builder.append('\r');
+                        break;
+                    case 't':
+                        builder.append('\t');
+                        break;
+                    case 'u':
+                        char uc = (char)((TypeUtil.convertHexDigit((byte)source.next()) << 12) + (TypeUtil.convertHexDigit((byte)source.next()) << 8)
+                                + (TypeUtil.convertHexDigit((byte)source.next()) << 4) + (TypeUtil.convertHexDigit((byte)source.next())));
+                        builder.append(uc);
+                        break;
+                    default:
+                        builder.append(c);
+                }
+            }
+            else if (c == '\\')
+            {
+                escape = true;
+            }
+            else if (c == '\"')
+            {
+                break;
+            }
+            else
+            {
+                builder.append(c);
+            }
+        }
+        return builder.toString();
+    }
+
+    public Number parseNumber(Source source)
+    {
+        boolean minus = false;
+        long number = 0;
+        StringBuilder buffer = null;
+
+        longLoop: while (source.hasNext())
+        {
+            char c = source.peek();
+            switch (c)
+            {
+                case '0':
+                case '1':
+                case '2':
+                case '3':
+                case '4':
+                case '5':
+                case '6':
+                case '7':
+                case '8':
+                case '9':
+                    number = number * 10 + (c - '0');
+                    source.next();
+                    break;
+
+                case '-':
+                case '+':
+                    if (number != 0)
+                        throw new IllegalStateException("bad number");
+                    minus = true;
+                    source.next();
+                    break;
+
+                case '.':
+                case 'e':
+                case 'E':
+                    buffer = new StringBuilder(16);
+                    if (minus)
+                        buffer.append('-');
+                    buffer.append(number);
+                    buffer.append(c);
+                    source.next();
+                    break longLoop;
+
+                default:
+                    break longLoop;
+            }
+        }
+
+        if (buffer == null)
+            return minus ? -1 * number : number;
+
+        doubleLoop: while (source.hasNext())
+        {
+            char c = source.peek();
+            switch (c)
+            {
+                case '0':
+                case '1':
+                case '2':
+                case '3':
+                case '4':
+                case '5':
+                case '6':
+                case '7':
+                case '8':
+                case '9':
+                case '-':
+                case '.':
+                case '+':
+                case 'e':
+                case 'E':
+                    buffer.append(c);
+                    source.next();
+                    break;
+
+                default:
+                    break doubleLoop;
+            }
+        }
+        return new Double(buffer.toString());
+
+    }
+
+    protected void seekTo(char seek, Source source)
+    {
+        while (source.hasNext())
+        {
+            char c = source.peek();
+            if (c == seek)
+                return;
+
+            if (!Character.isWhitespace(c))
+                throw new IllegalStateException("Unexpected '" + c + " while seeking '" + seek + "'");
+            source.next();
+        }
+
+        throw new IllegalStateException("Expected '" + seek + "'");
+    }
+
+    protected char seekTo(String seek, Source source)
+    {
+        while (source.hasNext())
+        {
+            char c = source.peek();
+            if (seek.indexOf(c) >= 0)
+            {
+                return c;
+            }
+
+            if (!Character.isWhitespace(c))
+                throw new IllegalStateException("Unexpected '" + c + "' while seeking one of '" + seek + "'");
+            source.next();
+        }
+
+        throw new IllegalStateException("Expected one of '" + seek + "'");
+    }
+
+    protected static void complete(String seek, Source source)
+    {
+        int i = 0;
+        while (source.hasNext() && i < seek.length())
+        {
+            char c = source.next();
+            if (c != seek.charAt(i++))
+                throw new IllegalStateException("Unexpected '" + c + " while seeking  \"" + seek + "\"");
+        }
+
+        if (i < seek.length())
+            throw new IllegalStateException("Expected \"" + seek + "\"");
+    }
+
+    private final class ConvertableOutput implements Output
+    {
+        private final Appendable _buffer;
+        char c = '{';
+
+        private ConvertableOutput(Appendable buffer)
+        {
+            _buffer = buffer;
+        }
+
+        public void complete()
+        {
+            try
+            {
+                if (c == '{')
+                    _buffer.append("{}");
+                else if (c != 0)
+                    _buffer.append("}");
+            }
+            catch (IOException e)
+            {
+                throw new RuntimeException(e);
+            }
+        }
+
+        public void add(Object obj)
+        {
+            if (c == 0)
+                throw new IllegalStateException();
+            append(_buffer,obj);
+            c = 0;
+        }
+
+        public void addClass(Class type)
+        {
+            try
+            {
+                if (c == 0)
+                    throw new IllegalStateException();
+                _buffer.append(c);
+                _buffer.append("\"class\":");
+                append(_buffer,type.getName());
+                c = ',';
+            }
+            catch (IOException e)
+            {
+                throw new RuntimeException(e);
+            }
+        }
+
+        public void add(String name, Object value)
+        {
+            try
+            {
+                if (c == 0)
+                    throw new IllegalStateException();
+                _buffer.append(c);
+                QuotedStringTokenizer.quote(_buffer,name);
+                _buffer.append(':');
+                append(_buffer,value);
+                c = ',';
+            }
+            catch (IOException e)
+            {
+                throw new RuntimeException(e);
+            }
+        }
+
+        public void add(String name, double value)
+        {
+            try
+            {
+                if (c == 0)
+                    throw new IllegalStateException();
+                _buffer.append(c);
+                QuotedStringTokenizer.quote(_buffer,name);
+                _buffer.append(':');
+                appendNumber(_buffer, value);
+                c = ',';
+            }
+            catch (IOException e)
+            {
+                throw new RuntimeException(e);
+            }
+        }
+
+        public void add(String name, long value)
+        {
+            try
+            {
+                if (c == 0)
+                    throw new IllegalStateException();
+                _buffer.append(c);
+                QuotedStringTokenizer.quote(_buffer,name);
+                _buffer.append(':');
+                appendNumber(_buffer, value);
+                c = ',';
+            }
+            catch (IOException e)
+            {
+                throw new RuntimeException(e);
+            }
+        }
+
+        public void add(String name, boolean value)
+        {
+            try
+            {
+                if (c == 0)
+                    throw new IllegalStateException();
+                _buffer.append(c);
+                QuotedStringTokenizer.quote(_buffer,name);
+                _buffer.append(':');
+                appendBoolean(_buffer,value?Boolean.TRUE:Boolean.FALSE);
+                c = ',';
+            }
+            catch (IOException e)
+            {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
+    public interface Source
+    {
+        boolean hasNext();
+
+        char next();
+
+        char peek();
+
+        char[] scratchBuffer();
+    }
+
+    public static class StringSource implements Source
+    {
+        private final String string;
+        private int index;
+        private char[] scratch;
+
+        public StringSource(String s)
+        {
+            string = s;
+        }
+
+        public boolean hasNext()
+        {
+            if (index < string.length())
+                return true;
+            scratch = null;
+            return false;
+        }
+
+        public char next()
+        {
+            return string.charAt(index++);
+        }
+
+        public char peek()
+        {
+            return string.charAt(index);
+        }
+
+        @Override
+        public String toString()
+        {
+            return string.substring(0,index) + "|||" + string.substring(index);
+        }
+
+        public char[] scratchBuffer()
+        {
+            if (scratch == null)
+                scratch = new char[string.length()];
+            return scratch;
+        }
+    }
+
+    public static class ReaderSource implements Source
+    {
+        private Reader _reader;
+        private int _next = -1;
+        private char[] scratch;
+
+        public ReaderSource(Reader r)
+        {
+            _reader = r;
+        }
+
+        public void setReader(Reader reader)
+        {
+            _reader = reader;
+            _next = -1;
+        }
+
+        public boolean hasNext()
+        {
+            getNext();
+            if (_next < 0)
+            {
+                scratch = null;
+                return false;
+            }
+            return true;
+        }
+
+        public char next()
+        {
+            getNext();
+            char c = (char)_next;
+            _next = -1;
+            return c;
+        }
+
+        public char peek()
+        {
+            getNext();
+            return (char)_next;
+        }
+
+        private void getNext()
+        {
+            if (_next < 0)
+            {
+                try
+                {
+                    _next = _reader.read();
+                }
+                catch (IOException e)
+                {
+                    throw new RuntimeException(e);
+                }
+            }
+        }
+
+        public char[] scratchBuffer()
+        {
+            if (scratch == null)
+                scratch = new char[1024];
+            return scratch;
+        }
+
+    }
+
+    /**
+     * JSON Output class for use by {@link Convertible}.
+     */
+    public interface Output
+    {
+        public void addClass(Class c);
+
+        public void add(Object obj);
+
+        public void add(String name, Object value);
+
+        public void add(String name, double value);
+
+        public void add(String name, long value);
+
+        public void add(String name, boolean value);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * JSON Convertible object. Object can implement this interface in a similar
+     * way to the {@link Externalizable} interface is used to allow classes to
+     * provide their own serialization mechanism.
+     * <p>
+     * A JSON.Convertible object may be written to a JSONObject or initialized
+     * from a Map of field names to values.
+     * <p>
+     * If the JSON is to be convertible back to an Object, then the method
+     * {@link Output#addClass(Class)} must be called from within toJSON()
+     *
+     */
+    public interface Convertible
+    {
+        public void toJSON(Output out);
+
+        public void fromJSON(Map object);
+    }
+
+    /**
+     * Static JSON Convertor.
+     * <p>
+     * may be implemented to provide static convertors for objects that may be
+     * registered with
+     * {@link JSON#registerConvertor(Class, org.eclipse.jetty.util.ajax.JSON.Convertor)}
+     * . These convertors are looked up by class, interface and super class by
+     * {@link JSON#getConvertor(Class)}. Convertors should be used when the
+     * classes to be converted cannot implement {@link Convertible} or
+     * {@link Generator}.
+     */
+    public interface Convertor
+    {
+        public void toJSON(Object obj, Output out);
+
+        public Object fromJSON(Map object);
+    }
+
+    /**
+     * JSON Generator. A class that can add it's JSON representation directly to
+     * a StringBuffer. This is useful for object instances that are frequently
+     * converted and wish to avoid multiple Conversions
+     */
+    public interface Generator
+    {
+        public void addJSON(Appendable buffer);
+    }
+
+    /**
+     * A Literal JSON generator A utility instance of {@link JSON.Generator}
+     * that holds a pre-generated string on JSON text.
+     */
+    public static class Literal implements Generator
+    {
+        private String _json;
+
+        /**
+         * Construct a literal JSON instance for use by
+         * {@link JSON#toString(Object)}. If {@link Logger#isDebugEnabled()} is
+         * true, the JSON will be parsed to check validity
+         *
+         * @param json
+         *            A literal JSON string.
+         */
+        public Literal(String json)
+        {
+            if (LOG.isDebugEnabled()) // TODO: Make this a configurable option on JSON instead!
+                parse(json);
+            _json = json;
+        }
+
+        @Override
+        public String toString()
+        {
+            return _json;
+        }
+
+        public void addJSON(Appendable buffer)
+        {
+            try
+            {
+                buffer.append(_json);
+            }
+            catch(IOException e)
+            {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/ajax/JSONCollectionConvertor.java b/jetty-util-ajax/src/main/java/org/eclipse/jetty/util/ajax/JSONCollectionConvertor.java
similarity index 100%
rename from jetty-util/src/main/java/org/eclipse/jetty/util/ajax/JSONCollectionConvertor.java
rename to jetty-util-ajax/src/main/java/org/eclipse/jetty/util/ajax/JSONCollectionConvertor.java
diff --git a/jetty-util-ajax/src/main/java/org/eclipse/jetty/util/ajax/JSONDateConvertor.java b/jetty-util-ajax/src/main/java/org/eclipse/jetty/util/ajax/JSONDateConvertor.java
new file mode 100644
index 0000000..8c72120
--- /dev/null
+++ b/jetty-util-ajax/src/main/java/org/eclipse/jetty/util/ajax/JSONDateConvertor.java
@@ -0,0 +1,105 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.ajax;
+
+import java.text.DateFormatSymbols;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.Map;
+import java.util.TimeZone;
+
+import org.eclipse.jetty.util.DateCache;
+import org.eclipse.jetty.util.ajax.JSON.Output;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/**
+* Convert a {@link Date} to JSON.
+* If fromJSON is true in the constructor, the JSON generated will
+* be of the form {class="java.util.Date",value="1/1/1970 12:00 GMT"}
+* If fromJSON is false, then only the string value of the date is generated.
+*/
+public class JSONDateConvertor implements JSON.Convertor
+{
+    private static final Logger LOG = Log.getLogger(JSONDateConvertor.class);
+
+    private final boolean _fromJSON;
+    private final DateCache _dateCache;
+    private final SimpleDateFormat _format;
+
+    public JSONDateConvertor()
+    {
+        this(false);
+    }
+
+    public JSONDateConvertor(boolean fromJSON)
+    {
+        this(DateCache.DEFAULT_FORMAT,TimeZone.getTimeZone("GMT"),fromJSON);
+    }
+
+    public JSONDateConvertor(String format,TimeZone zone,boolean fromJSON)
+    {
+        _dateCache=new DateCache(format,null,zone);
+        _fromJSON=fromJSON;
+        _format=new SimpleDateFormat(format);
+        _format.setTimeZone(zone);
+    }
+
+    public JSONDateConvertor(String format, TimeZone zone, boolean fromJSON, Locale locale)
+    {
+        _dateCache = new DateCache(format, locale, zone);
+        _fromJSON = fromJSON;
+        _format = new SimpleDateFormat(format, new DateFormatSymbols(locale));
+        _format.setTimeZone(zone);
+    }
+
+    public Object fromJSON(Map map)
+    {
+        if (!_fromJSON)
+            throw new UnsupportedOperationException();
+        try
+        {
+            synchronized(_format)
+            {
+                return _format.parseObject((String)map.get("value"));
+            }
+        }
+        catch(Exception e)
+        {
+            LOG.warn(e);
+        }
+        return null;
+    }
+
+    public void toJSON(Object obj, Output out)
+    {
+        String date = _dateCache.format((Date)obj);
+        if (_fromJSON)
+        {
+            out.addClass(obj.getClass());
+            out.add("value",date);
+        }
+        else
+        {
+            out.add(date);
+        }
+    }
+}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/ajax/JSONEnumConvertor.java b/jetty-util-ajax/src/main/java/org/eclipse/jetty/util/ajax/JSONEnumConvertor.java
similarity index 100%
rename from jetty-util/src/main/java/org/eclipse/jetty/util/ajax/JSONEnumConvertor.java
rename to jetty-util-ajax/src/main/java/org/eclipse/jetty/util/ajax/JSONEnumConvertor.java
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/ajax/JSONObjectConvertor.java b/jetty-util-ajax/src/main/java/org/eclipse/jetty/util/ajax/JSONObjectConvertor.java
old mode 100755
new mode 100644
similarity index 100%
rename from jetty-util/src/main/java/org/eclipse/jetty/util/ajax/JSONObjectConvertor.java
rename to jetty-util-ajax/src/main/java/org/eclipse/jetty/util/ajax/JSONObjectConvertor.java
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/ajax/JSONPojoConvertor.java b/jetty-util-ajax/src/main/java/org/eclipse/jetty/util/ajax/JSONPojoConvertor.java
similarity index 100%
rename from jetty-util/src/main/java/org/eclipse/jetty/util/ajax/JSONPojoConvertor.java
rename to jetty-util-ajax/src/main/java/org/eclipse/jetty/util/ajax/JSONPojoConvertor.java
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/ajax/JSONPojoConvertorFactory.java b/jetty-util-ajax/src/main/java/org/eclipse/jetty/util/ajax/JSONPojoConvertorFactory.java
similarity index 100%
rename from jetty-util/src/main/java/org/eclipse/jetty/util/ajax/JSONPojoConvertorFactory.java
rename to jetty-util-ajax/src/main/java/org/eclipse/jetty/util/ajax/JSONPojoConvertorFactory.java
diff --git a/jetty-util-ajax/src/main/java/org/eclipse/jetty/util/ajax/package-info.java b/jetty-util-ajax/src/main/java/org/eclipse/jetty/util/ajax/package-info.java
new file mode 100644
index 0000000..b0db44a
--- /dev/null
+++ b/jetty-util-ajax/src/main/java/org/eclipse/jetty/util/ajax/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Util : Simple JSON Utility classes
+ */
+package org.eclipse.jetty.util.ajax;
+
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/ajax/JSONCollectionConvertorTest.java b/jetty-util-ajax/src/test/java/org/eclipse/jetty/util/ajax/JSONCollectionConvertorTest.java
similarity index 100%
rename from jetty-util/src/test/java/org/eclipse/jetty/util/ajax/JSONCollectionConvertorTest.java
rename to jetty-util-ajax/src/test/java/org/eclipse/jetty/util/ajax/JSONCollectionConvertorTest.java
diff --git a/jetty-util-ajax/src/test/java/org/eclipse/jetty/util/ajax/JSONPojoConvertorFactoryTest.java b/jetty-util-ajax/src/test/java/org/eclipse/jetty/util/ajax/JSONPojoConvertorFactoryTest.java
new file mode 100644
index 0000000..324936a
--- /dev/null
+++ b/jetty-util-ajax/src/test/java/org/eclipse/jetty/util/ajax/JSONPojoConvertorFactoryTest.java
@@ -0,0 +1,417 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.ajax;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Map;
+
+import org.junit.Test;
+
+
+/**
+ * Test to convert POJOs to JSON and vice versa with automatic convertor creation.
+ */
+public class JSONPojoConvertorFactoryTest
+{
+    @Test
+    public void testFoo()
+    {
+        JSON jsonOut = new JSON();
+        JSON jsonIn = new JSON();
+
+        jsonOut.addConvertor(Object.class, new JSONPojoConvertorFactory(jsonOut));
+        jsonOut.addConvertor(Enum.class, new JSONEnumConvertor());
+        jsonIn.addConvertor(Object.class, new JSONPojoConvertorFactory(jsonIn));
+        jsonIn.addConvertor(Enum.class, new JSONEnumConvertor());
+
+        Foo foo = new Foo();
+        foo._name = "Foo @ " + System.currentTimeMillis();
+        foo._int1 = 1;
+        foo._int2 = new Integer(2);
+        foo._long1 = 1000001l;
+        foo._long2 = new Long(1000002l);
+        foo._float1 = 10.11f;
+        foo._float2 = new Float(10.22f);
+        foo._double1 = 10000.11111d;
+        foo._double2 = new Double(10000.22222d);
+
+        Bar bar = new Bar("Hello", true, new Baz("World", Boolean.FALSE, foo), new Baz[]{
+            new Baz("baz0", Boolean.TRUE, null), new Baz("baz1", Boolean.FALSE, null)
+        });
+        bar.setColor(Color.Green);
+
+        String s = jsonOut.toJSON(bar);
+
+        Object obj = jsonIn.parse(new JSON.StringSource(s));
+
+        assertTrue(obj instanceof Bar);
+
+        Bar br = (Bar)obj;
+
+        Baz bz = br.getBaz();
+
+        Foo f = bz.getFoo();
+
+        assertEquals(f, foo);
+        assertTrue(br.getBazs().length==2);
+        assertEquals(br.getBazs()[0].getMessage(), "baz0");
+        assertEquals(br.getBazs()[1].getMessage(), "baz1");
+        assertEquals(Color.Green,br.getColor());
+    }
+
+    @Test
+    public void testFoo2Map()
+    {
+        JSON jsonOut = new JSON();
+        JSON jsonIn = new JSON();
+
+        jsonOut.addConvertor(Object.class, new JSONPojoConvertorFactory(jsonOut,false));
+        jsonOut.addConvertor(Enum.class, new JSONEnumConvertor());
+        jsonIn.addConvertor(Object.class, new JSONPojoConvertorFactory(jsonIn,false));
+        jsonIn.addConvertor(Enum.class, new JSONEnumConvertor());
+
+        Foo foo = new Foo();
+        foo._name = "Foo @ " + System.currentTimeMillis();
+        foo._int1 = 1;
+        foo._int2 = new Integer(2);
+        foo._long1 = 1000001l;
+        foo._long2 = new Long(1000002l);
+        foo._float1 = 10.11f;
+        foo._float2 = new Float(10.22f);
+        foo._double1 = 10000.11111d;
+        foo._double2 = new Double(10000.22222d);
+
+        Bar bar = new Bar("Hello", true, new Baz("World", Boolean.FALSE, foo), new Baz[]{
+            new Baz("baz0", Boolean.TRUE, null), new Baz("baz1", Boolean.FALSE, null)
+        });
+        bar.setColor(Color.Green);
+
+        String s = jsonOut.toJSON(bar);
+
+        assertTrue(s.indexOf("class")<0);
+
+        Object obj = jsonIn.parse(new JSON.StringSource(s));
+
+        assertTrue(obj instanceof Map);
+
+        Map<String,Object> br = (Map<String,Object>)obj;
+
+        Map<String,Object> bz = (Map<String,Object>)br.get("baz");
+
+        Map<String,Object> f = (Map<String,Object>)bz.get("foo");
+        assertTrue(f != null);
+        Object[] bazs = (Object[])br.get("bazs");
+        assertTrue(bazs.length==2);
+        assertEquals(((Map)bazs[0]).get("message"), "baz0");
+        assertEquals(((Map)bazs[1]).get("message"), "baz1");
+        assertEquals("Green",br.get("color"));
+    }
+
+
+    enum Color { Red, Green, Blue };
+
+    public static class Bar
+    {
+        private String _title, _nullTest;
+        private Baz _baz;
+        private boolean _boolean1;
+        private Baz[] _bazs;
+        private Color _color;
+
+        public Bar()
+        {
+
+        }
+
+        public Bar(String title, boolean boolean1, Baz baz)
+        {
+            setTitle(title);
+            setBoolean1(boolean1);
+            setBaz(baz);
+        }
+
+        public Bar(String title, boolean boolean1, Baz baz, Baz[] bazs)
+        {
+            this(title, boolean1, baz);
+            setBazs(bazs);
+        }
+
+        @Override
+        public String toString()
+        {
+            return new StringBuffer().append("\n=== ").append(getClass().getSimpleName()).append(" ===")
+                .append("\ntitle: ").append(getTitle())
+                .append("\nboolean1: ").append(isBoolean1())
+                .append("\nnullTest: ").append(getNullTest())
+                .append("\nbaz: ").append(getBaz())
+                .append("\ncolor: ").append(_color).toString();
+        }
+
+        public void setTitle(String title)
+        {
+            _title = title;
+        }
+
+        public String getTitle()
+        {
+            return _title;
+        }
+
+        public void setNullTest(String nullTest)
+        {
+            assert(nullTest==null);
+            _nullTest = nullTest;
+        }
+
+        public String getNullTest()
+        {
+            return _nullTest;
+        }
+
+        public void setBaz(Baz baz)
+        {
+            _baz = baz;
+        }
+
+        public Baz getBaz()
+        {
+            return _baz;
+        }
+
+        public void setBoolean1(boolean boolean1)
+        {
+            _boolean1 = boolean1;
+        }
+
+        public boolean isBoolean1()
+        {
+            return _boolean1;
+        }
+
+        public void setBazs(Baz[] bazs)
+        {
+            _bazs = bazs;
+        }
+
+        public Baz[] getBazs()
+        {
+            return _bazs;
+        }
+
+        public Color getColor()
+        {
+            return _color;
+        }
+
+        public void setColor(Color color)
+        {
+            _color = color;
+        }
+
+    }
+
+    public static class Baz
+    {
+        private String _message;
+        private Foo _foo;
+        private Boolean _boolean2;
+
+        public Baz()
+        {
+
+        }
+
+        public Baz(String message, Boolean boolean2, Foo foo)
+        {
+            setMessage(message);
+            setBoolean2(boolean2);
+            setFoo(foo);
+        }
+
+        @Override
+        public String toString()
+        {
+            return new StringBuffer().append("\n=== ").append(getClass().getSimpleName()).append(" ===")
+                .append("\nmessage: ").append(getMessage())
+                .append("\nboolean2: ").append(isBoolean2())
+                .append("\nfoo: ").append(getFoo()).toString();
+        }
+
+        public void setMessage(String message)
+        {
+            _message = message;
+        }
+
+        public String getMessage()
+        {
+            return _message;
+        }
+
+        public void setFoo(Foo foo)
+        {
+            _foo = foo;
+        }
+
+        public Foo getFoo()
+        {
+            return _foo;
+        }
+
+        public void setBoolean2(Boolean boolean2)
+        {
+            _boolean2 = boolean2;
+        }
+
+        public Boolean isBoolean2()
+        {
+            return _boolean2;
+        }
+
+    }
+
+    public static class Foo
+    {
+        private String _name;
+        private int _int1;
+        private Integer _int2;
+        private long _long1;
+        private Long _long2;
+        private float _float1;
+        private Float _float2;
+        private double _double1;
+        private Double _double2;
+
+        public Foo()
+        {
+
+        }
+
+        @Override
+        public String toString()
+        {
+            return new StringBuffer().append("\n=== ").append(getClass().getSimpleName()).append(" ===")
+                .append("\nname: ").append(_name)
+                .append("\nint1: ").append(_int1)
+                .append("\nint2: ").append(_int2)
+                .append("\nlong1: ").append(_long1)
+                .append("\nlong2: ").append(_long2)
+                .append("\nfloat1: ").append(_float1)
+                .append("\nfloat2: ").append(_float2)
+                .append("\ndouble1: ").append(_double1)
+                .append("\ndouble2: ").append(_double2)
+                .toString();
+        }
+
+        @Override
+        public boolean equals(Object another)
+        {
+            if(another instanceof Foo)
+            {
+                Foo foo = (Foo)another;
+                return getName().equals(foo.getName())
+                    && getInt1()==foo.getInt1()
+                    && getInt2().equals(foo.getInt2())
+                    && getLong1()==foo.getLong1()
+                    && getLong2().equals(foo.getLong2())
+                    && getFloat1()==foo.getFloat1()
+                    && getFloat2().equals(foo.getFloat2())
+                    && getDouble1()==foo.getDouble1()
+                    && getDouble2().equals(foo.getDouble2());
+            }
+
+            return false;
+        }
+
+        public String getName()
+        {
+            return _name;
+        }
+        public void setName(String name)
+        {
+            _name = name;
+        }
+        public int getInt1()
+        {
+            return _int1;
+        }
+        public void setInt1(int int1)
+        {
+            _int1 = int1;
+        }
+        public Integer getInt2()
+        {
+            return _int2;
+        }
+        public void setInt2(Integer int2)
+        {
+            _int2 = int2;
+        }
+        public long getLong1()
+        {
+            return _long1;
+        }
+        public void setLong1(long long1)
+        {
+            _long1 = long1;
+        }
+        public Long getLong2()
+        {
+            return _long2;
+        }
+        public void setLong2(Long long2)
+        {
+            _long2 = long2;
+        }
+        public float getFloat1()
+        {
+            return _float1;
+        }
+        public void setFloat1(float float1)
+        {
+            _float1 = float1;
+        }
+        public Float getFloat2()
+        {
+            return _float2;
+        }
+        public void setFloat2(Float float2)
+        {
+            _float2 = float2;
+        }
+        public double getDouble1()
+        {
+            return _double1;
+        }
+        public void setDouble1(double double1)
+        {
+            _double1 = double1;
+        }
+        public Double getDouble2()
+        {
+            return _double2;
+        }
+        public void setDouble2(Double double2)
+        {
+            _double2 = double2;
+        }
+
+    }
+}
diff --git a/jetty-util-ajax/src/test/java/org/eclipse/jetty/util/ajax/JSONPojoConvertorTest.java b/jetty-util-ajax/src/test/java/org/eclipse/jetty/util/ajax/JSONPojoConvertorTest.java
new file mode 100644
index 0000000..77c21fd
--- /dev/null
+++ b/jetty-util-ajax/src/test/java/org/eclipse/jetty/util/ajax/JSONPojoConvertorTest.java
@@ -0,0 +1,441 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.ajax;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+
+/**
+ * Test to converts POJOs to JSON and vice versa.
+ */
+public class JSONPojoConvertorTest
+{
+    @Test
+    public void testFoo()
+    {
+        JSON json = new JSON();
+        json.addConvertor(Foo.class, new JSONPojoConvertor(Foo.class));
+        json.addConvertor(Bar.class, new JSONPojoConvertor(Bar.class));
+        json.addConvertor(Baz.class, new JSONPojoConvertor(Baz.class));
+        // json.addConvertor(Enum.class, new JSONEnumConvertor(true));
+
+        Foo foo = new Foo();
+        foo._name = "Foo @ " + System.currentTimeMillis();
+        foo._int1 = 1;
+        foo._int2 = new Integer(2);
+        foo._long1 = 1000001l;
+        foo._long2 = new Long(1000002l);
+        foo._float1 = 10.11f;
+        foo._float2 = new Float(10.22f);
+        foo._double1 = 10000.11111d;
+        foo._double2 = new Double(10000.22222d);
+        foo._char1='a';
+        foo._char2=new Character('b');
+
+        Bar bar = new Bar("Hello", true, new Baz("World", Boolean.FALSE, foo), new Baz[]{
+            new Baz("baz0", Boolean.TRUE, null), new Baz("baz1", Boolean.FALSE, null)
+        });
+        bar.setColor(Color.Green);
+
+        String s = json.toJSON(bar);
+
+        Object obj = json.parse(new JSON.StringSource(s));
+
+        assertTrue(obj instanceof Bar);
+
+        Bar br = (Bar)obj;
+
+        Baz bz = br.getBaz();
+
+        Foo f = bz.getFoo();
+
+        assertEquals(foo, f);
+        assertTrue(br.getBazs().length==2);
+        assertEquals(br.getBazs()[0].getMessage(), "baz0");
+        assertEquals(br.getBazs()[1].getMessage(), "baz1");
+        assertEquals(Color.Green,br.getColor());
+    }
+
+    @Test
+    public void testExclude()
+    {
+        JSON json = new JSON();
+        json.addConvertor(Foo.class, new JSONPojoConvertor(Foo.class,
+                new String[]{"name", "long1", "int2"}));
+        json.addConvertor(Bar.class, new JSONPojoConvertor(Bar.class,
+                new String[]{"title", "boolean1"}));
+        json.addConvertor(Baz.class, new JSONPojoConvertor(Baz.class,
+                new String[]{"boolean2"}));
+
+        Foo foo = new Foo();
+        foo._name = "Foo @ " + System.currentTimeMillis();
+        foo._int1 = 1;
+        foo._int2 = new Integer(2);
+        foo._long1 = 1000001l;
+        foo._long2 = new Long(1000002l);
+        foo._float1 = 10.11f;
+        foo._float2 = new Float(10.22f);
+        foo._double1 = 10000.11111d;
+        foo._double2 = new Double(10000.22222d);
+        foo._char1='a';
+        foo._char2=new Character('b');
+
+        Bar bar = new Bar("Hello", true, new Baz("World", Boolean.FALSE, foo));
+        // bar.setColor(Color.Blue);
+
+        String s = json.toJSON(bar);
+        Object obj = json.parse(new JSON.StringSource(s));
+
+        assertTrue(obj instanceof Bar);
+
+        Bar br = (Bar)obj;
+        Baz bz = br.getBaz();
+        Foo f = bz.getFoo();
+
+        assertNull(br.getTitle());
+        assertFalse(bar.getTitle().equals(br.getTitle()));
+        assertFalse(br.isBoolean1()==bar.isBoolean1());
+        assertNull(bz.isBoolean2());
+        assertFalse(bar.getBaz().isBoolean2().equals(bz.isBoolean2()));
+        assertFalse(f.getLong1()==foo.getLong1());
+        assertNull(f.getInt2());
+        assertFalse(foo.getInt2().equals(f.getInt2()));
+        assertNull(f.getName());
+        assertEquals(null,br.getColor());
+    }
+
+    enum Color { Red, Green, Blue };
+
+    public static class Bar
+    {
+        private String _title, _nullTest;
+        private Baz _baz;
+        private boolean _boolean1;
+        private Baz[] _bazs;
+        private Color _color;
+
+        public Bar()
+        {
+
+        }
+
+        public Bar(String title, boolean boolean1, Baz baz)
+        {
+            setTitle(title);
+            setBoolean1(boolean1);
+            setBaz(baz);
+        }
+
+        public Bar(String title, boolean boolean1, Baz baz, Baz[] bazs)
+        {
+            this(title, boolean1, baz);
+            setBazs(bazs);
+        }
+
+        @Override
+        public String toString()
+        {
+            return new StringBuffer()
+                .append("\n=== ").append(getClass().getSimpleName()).append(" ===")
+                .append("\ntitle: ").append(getTitle())
+                .append("\nboolean1: ").append(isBoolean1())
+                .append("\nnullTest: ").append(getNullTest())
+                .append("\nbaz: ").append(getBaz())
+                .append("\ncolor: ").append(_color).toString();
+        }
+
+        public void setTitle(String title)
+        {
+            _title = title;
+        }
+
+        public String getTitle()
+        {
+            return _title;
+        }
+
+        public void setNullTest(String nullTest)
+        {
+            assert(nullTest==null);
+            _nullTest = nullTest;
+        }
+
+        public String getNullTest()
+        {
+            return _nullTest;
+        }
+
+        public void setBaz(Baz baz)
+        {
+            _baz = baz;
+        }
+
+        public Baz getBaz()
+        {
+            return _baz;
+        }
+
+        public void setBoolean1(boolean boolean1)
+        {
+            _boolean1 = boolean1;
+        }
+
+        public boolean isBoolean1()
+        {
+            return _boolean1;
+        }
+
+        public void setBazs(Baz[] bazs)
+        {
+            _bazs = bazs;
+        }
+
+        public Baz[] getBazs()
+        {
+            return _bazs;
+        }
+
+        public Color getColor()
+        {
+            return _color;
+        }
+
+        public void setColor(Color color)
+        {
+            _color = color;
+        }
+
+
+    }
+
+    public static class Baz
+    {
+        private String _message;
+        private Foo _foo;
+        private Boolean _boolean2;
+
+        public Baz()
+        {
+
+        }
+
+        public Baz(String message, Boolean boolean2, Foo foo)
+        {
+            setMessage(message);
+            setBoolean2(boolean2);
+            setFoo(foo);
+        }
+
+        @Override
+        public String toString()
+        {
+            return new StringBuffer().append("\n=== ").append(getClass().getSimpleName()).append(" ===")
+                .append("\nmessage: ").append(getMessage())
+                .append("\nboolean2: ").append(isBoolean2())
+                .append("\nfoo: ").append(getFoo()).toString();
+        }
+
+        public void setMessage(String message)
+        {
+            _message = message;
+        }
+
+        public String getMessage()
+        {
+            return _message;
+        }
+
+        public void setFoo(Foo foo)
+        {
+            _foo = foo;
+        }
+
+        public Foo getFoo()
+        {
+            return _foo;
+        }
+
+        public void setBoolean2(Boolean boolean2)
+        {
+            _boolean2 = boolean2;
+        }
+
+        public Boolean isBoolean2()
+        {
+            return _boolean2;
+        }
+
+    }
+
+    public static class Foo
+    {
+        private String _name;
+        private int _int1;
+        private Integer _int2;
+        private long _long1;
+        private Long _long2;
+        private float _float1;
+        private Float _float2;
+        private double _double1;
+        private Double _double2;
+        private char _char1;
+        private Character _char2;
+
+        public Foo()
+        {
+
+        }
+
+        @Override
+        public String toString()
+        {
+            return new StringBuffer().append("\n=== ").append(getClass().getSimpleName()).append(" ===")
+                .append("\nname: ").append(_name)
+                .append("\nint1: ").append(_int1)
+                .append("\nint2: ").append(_int2)
+                .append("\nlong1: ").append(_long1)
+                .append("\nlong2: ").append(_long2)
+                .append("\nfloat1: ").append(_float1)
+                .append("\nfloat2: ").append(_float2)
+                .append("\ndouble1: ").append(_double1)
+                .append("\ndouble2: ").append(_double2)
+                .append("\nchar1: ").append(_char1)
+                .append("\nchar2: ").append(_char2)
+                .toString();
+        }
+
+        @Override
+        public boolean equals(Object another)
+        {
+            if(another instanceof Foo)
+            {
+                Foo foo = (Foo)another;
+                return getName().equals(foo.getName())
+                    && getInt1()==foo.getInt1()
+                    && getInt2().equals(foo.getInt2())
+                    && getLong1()==foo.getLong1()
+                    && getLong2().equals(foo.getLong2())
+                    && getFloat1()==foo.getFloat1()
+                    && getFloat2().equals(foo.getFloat2())
+                    && getDouble1()==foo.getDouble1()
+                    && getDouble2().equals(foo.getDouble2())
+                    && getChar1()==foo.getChar1()
+                    && getChar2().equals(foo.getChar2());
+            }
+
+            return false;
+        }
+
+        public String getName()
+        {
+            return _name;
+        }
+        public void setName(String name)
+        {
+            _name = name;
+        }
+        public int getInt1()
+        {
+            return _int1;
+        }
+        public void setInt1(int int1)
+        {
+            _int1 = int1;
+        }
+        public Integer getInt2()
+        {
+            return _int2;
+        }
+        public void setInt2(Integer int2)
+        {
+            _int2 = int2;
+        }
+        public long getLong1()
+        {
+            return _long1;
+        }
+        public void setLong1(long long1)
+        {
+            _long1 = long1;
+        }
+        public Long getLong2()
+        {
+            return _long2;
+        }
+        public void setLong2(Long long2)
+        {
+            _long2 = long2;
+        }
+        public float getFloat1()
+        {
+            return _float1;
+        }
+        public void setFloat1(float float1)
+        {
+            _float1 = float1;
+        }
+        public Float getFloat2()
+        {
+            return _float2;
+        }
+        public void setFloat2(Float float2)
+        {
+            _float2 = float2;
+        }
+        public double getDouble1()
+        {
+            return _double1;
+        }
+        public void setDouble1(double double1)
+        {
+            _double1 = double1;
+        }
+        public Double getDouble2()
+        {
+            return _double2;
+        }
+        public void setDouble2(Double double2)
+        {
+            _double2 = double2;
+        }
+        public char getChar1()
+        {
+            return _char1;
+        }
+        public void setChar1(char char1)
+        {
+            _char1 = char1;
+        }
+        public Character getChar2()
+        {
+            return _char2;
+        }
+        public void setChar2(Character char2)
+        {
+            _char2 = char2;
+        }
+
+    }
+
+}
diff --git a/jetty-util-ajax/src/test/java/org/eclipse/jetty/util/ajax/JSONTest.java b/jetty-util-ajax/src/test/java/org/eclipse/jetty/util/ajax/JSONTest.java
new file mode 100644
index 0000000..3c69517
--- /dev/null
+++ b/jetty-util-ajax/src/test/java/org/eclipse/jetty/util/ajax/JSONTest.java
@@ -0,0 +1,469 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.ajax;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.io.StringReader;
+import java.lang.reflect.Array;
+import java.math.BigDecimal;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.TimeZone;
+
+import org.eclipse.jetty.util.DateCache;
+import org.eclipse.jetty.util.ajax.JSON.Output;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+
+public class JSONTest
+{
+    String test="\n\n\n\t\t    "+
+    "// ignore this ,a [ \" \n"+
+    "/* and this \n" +
+    "/* and * // this \n" +
+    "*/" +
+    "{ "+
+    "\"onehundred\" : 100  ,"+
+    "\"small\":-0.2,"+
+    "\"name\" : \"fred\"  ," +
+    "\"empty\" : {}  ," +
+    "\"map\" : {\"a\":-1.0e2}  ," +
+    "\"array\" : [\"a\",-1.0e2,[],null,true,false]  ," +
+    "\"w0\":{\"class\":\"org.eclipse.jetty.util.ajax.JSONTest$Woggle\",\"name\":\"woggle0\",\"nested\":{\"class\":\"org.eclipse.jetty.util.ajax.JSONTest$Woggle\",\"name\":\"woggle1\",\"nested\":null,\"number\":-101},\"number\":100}," +
+    "\"NaN\": NaN," +
+    "\"undefined\": undefined," +
+    "}";
+
+    @BeforeClass
+    public static void setUp() throws Exception
+    {
+        JSON.registerConvertor(Gadget.class,new JSONObjectConvertor(false));
+    }
+
+    @Test
+    public void testToString()
+    {
+        HashMap map = new HashMap();
+        HashMap obj6 = new HashMap();
+        HashMap obj7 = new HashMap();
+
+        Woggle w0 = new Woggle();
+        Woggle w1 = new Woggle();
+
+        w0.name="woggle0";
+        w0.nested=w1;
+        w0.number=100;
+        w1.name="woggle1";
+        w1.nested=null;
+        w1.number=-101;
+
+        map.put("n1",null);
+        map.put("n2",new Integer(2));
+        map.put("n3",new Double(-0.00000000003));
+        map.put("n4","4\n\r\t\"4");
+        map.put("n5",new Object[]{"a",new Character('b'),new Integer(3),new String[]{},null,Boolean.TRUE,Boolean.FALSE});
+        map.put("n6",obj6);
+        map.put("n7",obj7);
+        map.put("n8",new int[]{1,2,3,4});
+        map.put("n9",new JSON.Literal("[{},  [],  {}]"));
+        map.put("w0",w0);
+
+        obj7.put("x","value");
+
+
+        String s = JSON.toString(map);
+        assertTrue(s.indexOf("\"n1\":null")>=0);
+        assertTrue(s.indexOf("\"n2\":2")>=0);
+        assertTrue(s.indexOf("\"n3\":-3.0E-11")>=0);
+        assertTrue(s.indexOf("\"n4\":\"4\\n")>=0);
+        assertTrue(s.indexOf("\"n5\":[\"a\",\"b\",")>=0);
+        assertTrue(s.indexOf("\"n6\":{}")>=0);
+        assertTrue(s.indexOf("\"n7\":{\"x\":\"value\"}")>=0);
+        assertTrue(s.indexOf("\"n8\":[1,2,3,4]")>=0);
+        assertTrue(s.indexOf("\"n9\":[{},  [],  {}]")>=0);
+        assertTrue(s.indexOf("\"w0\":{\"class\":\"org.eclipse.jetty.util.ajax.JSONTest$Woggle\",\"name\":\"woggle0\",\"nested\":{\"class\":\"org.eclipse.jetty.util.ajax.JSONTest$Woggle\",\"name\":\"woggle1\",\"nested\":null,\"number\":-101},\"number\":100}")>=0);
+
+        Gadget gadget = new Gadget();
+        gadget.setShields(42);
+        gadget.setWoggles(new Woggle[]{w0,w1});
+
+        s = JSON.toString(new Gadget[]{gadget});
+        assertTrue(s.startsWith("["));
+        assertTrue(s.indexOf("\"modulated\":false")>=0);
+        assertTrue(s.indexOf("\"shields\":42")>=0);
+        assertTrue(s.indexOf("\"name\":\"woggle0\"")>=0);
+        assertTrue(s.indexOf("\"name\":\"woggle1\"")>=0);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Test
+    public void testParse()
+    {
+        Map map = (Map)JSON.parse(test);
+        assertEquals(new Long(100),map.get("onehundred"));
+        assertEquals("fred",map.get("name"));
+        assertEquals(-0.2,map.get("small"));
+        assertTrue(map.get("array").getClass().isArray());
+        assertTrue(map.get("w0") instanceof Woggle);
+        assertTrue(((Woggle)map.get("w0")).nested instanceof Woggle);
+        assertEquals(-101,((Woggle)((Woggle)map.get("w0")).nested).number);
+        assertTrue(map.containsKey("NaN"));
+        assertEquals(null,map.get("NaN"));
+        assertTrue(map.containsKey("undefined"));
+        assertEquals(null,map.get("undefined"));
+
+        test="{\"data\":{\"source\":\"15831407eqdaawf7\",\"widgetId\":\"Magnet_8\"},\"channel\":\"/magnets/moveStart\",\"connectionId\":null,\"clientId\":\"15831407eqdaawf7\"}";
+        map = (Map)JSON.parse(test);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Test
+    public void testParseReader() throws Exception
+    {
+        Map map = (Map)JSON.parse(new StringReader(test));
+
+        assertEquals(new Long(100),map.get("onehundred"));
+        assertEquals("fred",map.get("name"));
+        assertTrue(map.get("array").getClass().isArray());
+        assertTrue(map.get("w0") instanceof Woggle);
+        assertTrue(((Woggle)map.get("w0")).nested instanceof Woggle);
+
+        test="{\"data\":{\"source\":\"15831407eqdaawf7\",\"widgetId\":\"Magnet_8\"},\"channel\":\"/magnets/moveStart\",\"connectionId\":null,\"clientId\":\"15831407eqdaawf7\"}";
+        map = (Map)JSON.parse(test);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Test
+    public void testStripComment()
+    {
+        String test="\n\n\n\t\t    "+
+        "// ignore this ,a [ \" \n"+
+        "/* "+
+        "{ "+
+        "\"onehundred\" : 100  ,"+
+        "\"name\" : \"fred\"  ," +
+        "\"empty\" : {}  ," +
+        "\"map\" : {\"a\":-1.0e2}  ," +
+        "\"array\" : [\"a\",-1.0e2,[],null,true,false]  ," +
+        "} */";
+
+        Object o = JSON.parse(test,false);
+        assertTrue(o==null);
+        o = JSON.parse(test,true);
+        assertTrue(o instanceof Map);
+        assertEquals("fred",((Map)o).get("name"));
+
+    }
+
+    /* ------------------------------------------------------------ */
+    @Test
+    public void testQuote()
+    {
+        String test="\"abc123|\\\"|\\\\|\\/|\\b|\\f|\\n|\\r|\\t|\\uaaaa|\"";
+
+        String result = (String)JSON.parse(test,false);
+        assertEquals("abc123|\"|\\|/|\b|\f|\n|\r|\t|\uaaaa|",result);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Test
+    public void testBigDecimal()
+    {
+        Object obj = JSON.parse("1.0E7");
+        assertTrue(obj instanceof Double);
+        BigDecimal bd = BigDecimal.valueOf(10000000d);
+        String string = JSON.toString(new Object[]{bd});
+        obj = Array.get(JSON.parse(string),0);
+        assertTrue(obj instanceof Double);
+    }
+
+
+    /* ------------------------------------------------------------ */
+    @Test
+    public void testZeroByte()
+    {
+        String withzero="\u0000";
+        JSON.toString(withzero);
+    }
+
+    /* ------------------------------------------------------------ */
+    public static class Gadget
+    {
+        private boolean modulated;
+        private long shields;
+        private Woggle[] woggles;
+        /* ------------------------------------------------------------ */
+        /**
+         * @return the modulated
+         */
+        public boolean isModulated()
+        {
+            return modulated;
+        }
+        /* ------------------------------------------------------------ */
+        /**
+         * @param modulated the modulated to set
+         */
+        public void setModulated(boolean modulated)
+        {
+            this.modulated=modulated;
+        }
+        /* ------------------------------------------------------------ */
+        /**
+         * @return the shields
+         */
+        public long getShields()
+        {
+            return shields;
+        }
+        /* ------------------------------------------------------------ */
+        /**
+         * @param shields the shields to set
+         */
+        public void setShields(long shields)
+        {
+            this.shields=shields;
+        }
+        /* ------------------------------------------------------------ */
+        /**
+         * @return the woggles
+         */
+        public Woggle[] getWoggles()
+        {
+            return woggles;
+        }
+        /* ------------------------------------------------------------ */
+        /**
+         * @param woggles the woggles to set
+         */
+        public void setWoggles(Woggle[] woggles)
+        {
+            this.woggles=woggles;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Test
+    public void testConvertor()
+    {
+        // test case#1 - force timezone to GMT
+        JSON json = new JSON();
+        json.addConvertor(Date.class, new JSONDateConvertor("MM/dd/yyyy HH:mm:ss zzz", TimeZone.getTimeZone("GMT"),false));
+        json.addConvertor(Object.class,new JSONObjectConvertor());
+
+        Woggle w0 = new Woggle();
+        Gizmo g0 = new Gizmo();
+
+        w0.name="woggle0";
+        w0.nested=g0;
+        w0.number=100;
+        g0.name="woggle1";
+        g0.nested=null;
+        g0.number=-101;
+        g0.tested=true;
+
+        HashMap map = new HashMap();
+        Date dummyDate = new Date(1);
+        map.put("date", dummyDate);
+        map.put("w0",w0);
+
+        StringBuffer buf = new StringBuffer();
+        json.append(buf,map);
+        String js=buf.toString();
+
+        assertTrue(js.indexOf("\"date\":\"01/01/1970 00:00:00 GMT\"")>=0);
+        assertTrue(js.indexOf("org.eclipse.jetty.util.ajax.JSONTest$Woggle")>=0);
+        assertTrue(js.indexOf("org.eclipse.jetty.util.ajax.JSONTest$Gizmo")<0);
+        assertTrue(js.indexOf("\"tested\":true")>=0);
+
+        // test case#3
+        TimeZone tzone = TimeZone.getTimeZone("JST");
+        String tzone3Letter = tzone.getDisplayName(false, TimeZone.SHORT);
+        String format = "EEE MMMMM dd HH:mm:ss zzz yyyy";
+
+        Locale l = new Locale("ja", "JP");
+        if (l!=null)
+        {
+            json.addConvertor(Date.class, new JSONDateConvertor(format, tzone, false, l));
+            buf = new StringBuffer();
+            json.append(buf,map);
+            js=buf.toString();
+            //assertTrue(js.indexOf("\"date\":\"\u6728 1\u6708 01 09:00:00 JST 1970\"")>=0);
+            assertTrue(js.indexOf(" 01 09:00:00 JST 1970\"")>=0);
+            assertTrue(js.indexOf("org.eclipse.jetty.util.ajax.JSONTest$Woggle")>=0);
+            assertTrue(js.indexOf("org.eclipse.jetty.util.ajax.JSONTest$Gizmo")<0);
+            assertTrue(js.indexOf("\"tested\":true")>=0);
+        }
+
+        // test case#4
+        json.addConvertor(Date.class,new JSONDateConvertor(true));
+        w0.nested=null;
+        buf = new StringBuffer();
+        json.append(buf,map);
+        js=buf.toString();
+        assertTrue(js.indexOf("\"date\":\"Thu Jan 01 00:00:00 GMT 1970\"")<0);
+        assertTrue(js.indexOf("org.eclipse.jetty.util.ajax.JSONTest$Woggle")>=0);
+        assertTrue(js.indexOf("org.eclipse.jetty.util.ajax.JSONTest$Gizmo")<0);
+
+        map=(HashMap)json.parse(new JSON.StringSource(js));
+
+        assertTrue(map.get("date") instanceof Date);
+        assertTrue(map.get("w0") instanceof Woggle);
+    }
+
+    enum Color { Red, Green, Blue };
+
+    @Test
+    public void testEnumConvertor()
+    {
+        JSON json = new JSON();
+        Locale l = new Locale("en", "US");
+        json.addConvertor(Date.class,new JSONDateConvertor(DateCache.DEFAULT_FORMAT,TimeZone.getTimeZone("GMT"),false,l));
+        json.addConvertor(Enum.class,new JSONEnumConvertor(false));
+        json.addConvertor(Object.class,new JSONObjectConvertor());
+
+        Woggle w0 = new Woggle();
+        Gizmo g0 = new Gizmo();
+
+        w0.name="woggle0";
+        w0.nested=g0;
+        w0.number=100;
+        w0.other=Color.Blue;
+        g0.name="woggle1";
+        g0.nested=null;
+        g0.number=-101;
+        g0.tested=true;
+        g0.other=Color.Green;
+
+        HashMap map = new HashMap();
+        map.put("date",new Date(1));
+        map.put("w0",w0);
+        map.put("g0",g0);
+
+        StringBuffer buf = new StringBuffer();
+        json.append((Appendable)buf,map);
+        String js=buf.toString();
+
+        assertTrue(js.indexOf("\"date\":\"Thu Jan 01 00:00:00 GMT 1970\"")>=0);
+        assertTrue(js.indexOf("org.eclipse.jetty.util.ajax.JSONTest$Woggle")>=0);
+        assertTrue(js.indexOf("org.eclipse.jetty.util.ajax.JSONTest$Gizmo")<0);
+        assertTrue(js.indexOf("\"tested\":true")>=0);
+        assertTrue(js.indexOf("\"Green\"")>=0);
+        assertTrue(js.indexOf("\"Blue\"")<0);
+
+        json.addConvertor(Date.class,new JSONDateConvertor(DateCache.DEFAULT_FORMAT,TimeZone.getTimeZone("GMT"),true,l));
+        json.addConvertor(Enum.class,new JSONEnumConvertor(false));
+        w0.nested=null;
+        buf = new StringBuffer();
+        json.append((Appendable)buf,map);
+        js=buf.toString();
+        assertTrue(js.indexOf("\"date\":\"Thu Jan 01 00:00:00 GMT 1970\"")<0);
+        assertTrue(js.indexOf("org.eclipse.jetty.util.ajax.JSONTest$Woggle")>=0);
+        assertTrue(js.indexOf("org.eclipse.jetty.util.ajax.JSONTest$Gizmo")<0);
+
+        Map map2=(HashMap)json.parse(new JSON.StringSource(js));
+
+        assertTrue(map2.get("date") instanceof Date);
+        assertTrue(map2.get("w0") instanceof Woggle);
+        assertEquals(null, ((Woggle)map2.get("w0")).getOther() );
+        assertEquals(Color.Green.toString(), ((Map)map2.get("g0")).get("other"));
+
+
+
+        json.addConvertor(Date.class,new JSONDateConvertor(DateCache.DEFAULT_FORMAT,TimeZone.getTimeZone("GMT"),true,l));
+        json.addConvertor(Enum.class,new JSONEnumConvertor(true));
+        buf = new StringBuffer();
+        json.append((Appendable)buf,map);
+        js=buf.toString();
+        map2=(HashMap)json.parse(new JSON.StringSource(js));
+
+        assertTrue(map2.get("date") instanceof Date);
+        assertTrue(map2.get("w0") instanceof Woggle);
+        assertEquals(null, ((Woggle)map2.get("w0")).getOther() );
+        Object o=((Map)map2.get("g0")).get("other");
+        assertEquals(Color.Green, o);
+
+    }
+
+    /* ------------------------------------------------------------ */
+    public static class Gizmo
+    {
+        String name;
+        Gizmo nested;
+        long number;
+        boolean tested;
+        Object other;
+
+        public String getName()
+        {
+            return name;
+        }
+        public Gizmo getNested()
+        {
+            return nested;
+        }
+        public long getNumber()
+        {
+            return number;
+        }
+        public boolean isTested()
+        {
+            return tested;
+        }
+        public Object getOther()
+        {
+            return other;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public static class Woggle extends Gizmo implements JSON.Convertible
+    {
+
+        public Woggle()
+        {
+        }
+
+        public void fromJSON(Map object)
+        {
+            name=(String)object.get("name");
+            nested=(Gizmo)object.get("nested");
+            number=((Number)object.get("number")).intValue();
+        }
+
+        public void toJSON(Output out)
+        {
+            out.addClass(Woggle.class);
+            out.add("name",name);
+            out.add("nested",nested);
+            out.add("number",number);
+        }
+
+        public String toString()
+        {
+            return name+"<<"+nested+">>"+number;
+        }
+
+    }
+}
diff --git a/jetty-util/pom.xml b/jetty-util/pom.xml
index e966cf3..f2ced84 100644
--- a/jetty-util/pom.xml
+++ b/jetty-util/pom.xml
@@ -2,7 +2,7 @@
   <parent>
     <groupId>org.eclipse.jetty</groupId>
     <artifactId>jetty-project</artifactId>
-    <version>8.1.17.v20150415</version>
+    <version>9.2.13.v20150730</version>
   </parent>
   <modelVersion>4.0.0</modelVersion>
   <artifactId>jetty-util</artifactId>
@@ -25,7 +25,7 @@
             </goals>
             <configuration>
               <instructions>
-                <Import-Package>javax.servlet.*;version="2.6.0",org.slf4j;version="[1.5,2.0)";resolution:=optional,org.slf4j.impl;version="[1.5,2.0)";resolution:=optional,*</Import-Package>
+                <Import-Package>javax.servlet.*;version="[2.6.0,3.2)",org.slf4j;version="[1.6,2.0)";resolution:=optional,org.slf4j.impl;version="[1.6,2.0)";resolution:=optional,*</Import-Package>
               </instructions>
             </configuration>
            </execution>
@@ -71,12 +71,17 @@
   </build>
   <dependencies>
     <dependency>
-      <groupId>org.eclipse.jetty.orbit</groupId>
-      <artifactId>javax.servlet</artifactId>
+      <groupId>javax.servlet</groupId>
+      <artifactId>javax.servlet-api</artifactId>
       <scope>provided</scope>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jetty.toolchain</groupId>
+      <artifactId>jetty-perf-helper</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.toolchain</groupId>
       <artifactId>jetty-test-helper</artifactId>
       <scope>test</scope>
     </dependency>
diff --git a/jetty-util/src/main/config/etc/jetty-logging.xml b/jetty-util/src/main/config/etc/jetty-logging.xml
index 2060a22..52589ee 100644
--- a/jetty-util/src/main/config/etc/jetty-logging.xml
+++ b/jetty-util/src/main/config/etc/jetty-logging.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0"?>
-<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
 
 
 <!-- =============================================================== -->
@@ -8,7 +8,7 @@
 <!-- other configuration files.  e.g.                                -->
 <!--    java -jar start.jar etc/jetty-logging.xml                    -->
 <!-- =============================================================== -->
-<Configure id="Server" class="org.eclipse.jetty.server.Server">
+<Configure id="logging" class="org.eclipse.jetty.util.log.Log">
 
     <New id="ServerLog" class="java.io.PrintStream">
       <Arg>
@@ -22,9 +22,11 @@
       </Arg>
     </New>
 
-    <Call class="org.eclipse.jetty.util.log.Log" name="info"><Arg>Redirecting stderr/stdout to <Ref id="ServerLogName"/></Arg></Call>
-    <Call class="java.lang.System" name="setErr"><Arg><Ref id="ServerLog"/></Arg></Call>
-    <Call class="java.lang.System" name="setOut"><Arg><Ref id="ServerLog"/></Arg></Call>
+    <Get name="rootLogger">
+      <Call name="info"><Arg>Redirecting stderr/stdout to <Ref refid="ServerLogName"/></Arg></Call>
+    </Get>
+    <Call class="java.lang.System" name="setErr"><Arg><Ref refid="ServerLog"/></Arg></Call>
+    <Call class="java.lang.System" name="setOut"><Arg><Ref refid="ServerLog"/></Arg></Call>
 
 </Configure>
 
diff --git a/jetty-util/src/main/config/modules/logging.mod b/jetty-util/src/main/config/modules/logging.mod
new file mode 100644
index 0000000..a39bfe4
--- /dev/null
+++ b/jetty-util/src/main/config/modules/logging.mod
@@ -0,0 +1,31 @@
+#
+# Jetty std err/out logging
+#
+
+[xml]
+etc/jetty-logging.xml
+
+[files]
+logs/
+
+[lib]
+lib/logging/**.jar
+resources/
+
+[ini-template]
+## Logging Configuration
+# Configure jetty logging for default internal behavior STDERR output
+# -Dorg.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
+
+# Configure jetty logging for slf4j
+# -Dorg.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.Slf4jLog
+
+# Configure jetty logging for java.util.logging
+# -Dorg.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.JavaUtilLog
+
+# STDERR / STDOUT Logging
+# Number of days to retain logs
+# jetty.log.retain=90
+# Directory for logging output
+# Either a path relative to ${jetty.base} or an absolute path
+# jetty.logs=logs
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/AbstractTrie.java b/jetty-util/src/main/java/org/eclipse/jetty/util/AbstractTrie.java
new file mode 100644
index 0000000..d4ff8b6
--- /dev/null
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/AbstractTrie.java
@@ -0,0 +1,85 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+
+
+/* ------------------------------------------------------------ */
+/** Abstract Trie implementation.
+ * <p>Provides some common implementations, which may not be the most
+ * efficient. For byte operations, the assumption is made that the charset
+ * is ISO-8859-1</p>
+ * @param <V>
+ */
+public abstract class AbstractTrie<V> implements Trie<V>
+{
+    final boolean _caseInsensitive;
+    
+    protected AbstractTrie(boolean insensitive)
+    {
+        _caseInsensitive=insensitive;
+    }
+
+    @Override
+    public boolean put(V v)
+    {
+        return put(v.toString(),v);
+    }
+
+    @Override
+    public V remove(String s)
+    {
+        V o=get(s);
+        put(s,null);
+        return o;
+    }
+
+    @Override
+    public V get(String s)
+    {
+        return get(s,0,s.length());
+    }
+
+    @Override
+    public V get(ByteBuffer b)
+    {
+        return get(b,0,b.remaining());
+    }
+
+    @Override
+    public V getBest(String s)
+    {
+        return getBest(s,0,s.length());
+    }
+    
+    @Override
+    public V getBest(byte[] b, int offset, int len)
+    {
+        return getBest(new String(b,offset,len,StandardCharsets.ISO_8859_1));
+    }
+
+    @Override
+    public boolean isCaseInsensitive()
+    {
+        return _caseInsensitive;
+    }
+
+}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/ArrayQueue.java b/jetty-util/src/main/java/org/eclipse/jetty/util/ArrayQueue.java
index 178dafd..572cb76 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/ArrayQueue.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/ArrayQueue.java
@@ -48,6 +48,12 @@ public class ArrayQueue<E> extends AbstractList<E> implements Queue<E>
     {
         this(DEFAULT_CAPACITY, -1);
     }
+    
+    /* ------------------------------------------------------------ */
+    public ArrayQueue(Object lock)
+    {
+        this(DEFAULT_CAPACITY, -1,lock);
+    }
 
     /* ------------------------------------------------------------ */
     public ArrayQueue(int capacity)
@@ -68,7 +74,13 @@ public class ArrayQueue<E> extends AbstractList<E> implements Queue<E>
         _growCapacity = growBy;
         _elements = new Object[initCapacity];
     }
-
+    
+    /* ------------------------------------------------------------ */
+    public Object lock()
+    {
+        return _lock;
+    }
+    
     /* ------------------------------------------------------------ */
     public int getCapacity()
     {
@@ -134,6 +146,7 @@ public class ArrayQueue<E> extends AbstractList<E> implements Queue<E>
         }
     }
 
+    /* ------------------------------------------------------------ */
     @SuppressWarnings("unchecked")
     private E at(int index)
     {
@@ -145,13 +158,21 @@ public class ArrayQueue<E> extends AbstractList<E> implements Queue<E>
     {
         synchronized (_lock)
         {
-            if (isEmpty())
+            if (_size == 0)
                 return null;
             return at(_nextE);
         }
     }
 
     /* ------------------------------------------------------------ */
+    public E peekUnsafe()
+    {
+        if (_size == 0)
+            return null;
+        return at(_nextE);
+    }
+
+    /* ------------------------------------------------------------ */
     public E poll()
     {
         synchronized (_lock)
@@ -161,6 +182,14 @@ public class ArrayQueue<E> extends AbstractList<E> implements Queue<E>
             return dequeue();
         }
     }
+    
+    /* ------------------------------------------------------------ */
+    public E pollUnsafe()
+    {
+        if (_size == 0)
+            return null;
+        return dequeue();
+    }
 
     /* ------------------------------------------------------------ */
     private E dequeue()
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/ArrayTernaryTrie.java b/jetty-util/src/main/java/org/eclipse/jetty/util/ArrayTernaryTrie.java
new file mode 100644
index 0000000..e289aeb
--- /dev/null
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/ArrayTernaryTrie.java
@@ -0,0 +1,511 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+
+/* ------------------------------------------------------------ */
+/** 
+ * <p>A Ternary Trie String lookup data structure.</p>
+ * This Trie is of a fixed size and cannot grow (which can be a good thing with regards to DOS when used as a cache).
+ * <p>
+ * The Trie is stored in 3 arrays:<dl>
+ * <dt>char[] _tree</dt><dd>This is semantically 2 dimensional array flattened into a 1 dimensional char array. The second dimension
+ * is that every 4 sequential elements represents a row of: character; hi index; eq index; low index, used to build a
+ * ternary trie of key strings.</dd>
+ * <dt>String[] _key<dt><dd>An array of key values where each element matches a row in the _tree array. A non zero key element 
+ * indicates that the _tree row is a complete key rather than an intermediate character of a longer key.</dd>
+ * <dt>V[] _value</dt><dd>An array of values corresponding to the _key array</dd>
+ * </dl>
+ * </p>
+ * <p>The lookup of a value will iterate through the _tree array matching characters. If the equal tree branch is followed,
+ * then the _key array is looked up to see if this is a complete match.  If a match is found then the _value array is looked up
+ * to return the matching value.
+ * </p>
+ * <p>
+ * This Trie may be instantiated either as case sensitive or insensitive.
+ * </p>
+ * <p>This Trie is not Threadsafe and contains no mutual exclusion 
+ * or deliberate memory barriers.  It is intended for an ArrayTrie to be
+ * built by a single thread and then used concurrently by multiple threads
+ * and not mutated during that access.  If concurrent mutations of the
+ * Trie is required external locks need to be applied.
+ * </p>
+ * 
+ * @param <V>
+ */
+public class ArrayTernaryTrie<V> extends AbstractTrie<V>
+{
+    private static int LO=1;
+    private static int EQ=2;
+    private static int HI=3;
+    
+    /**
+     * The Size of a Trie row is the char, and the low, equal and high
+     * child pointers
+     */
+    private static final int ROW_SIZE = 4;
+    
+    /**
+     * The Trie rows in a single array which allows a lookup of row,character
+     * to the next row in the Trie.  This is actually a 2 dimensional
+     * array that has been flattened to achieve locality of reference.
+     */
+    private final char[] _tree;
+    
+    /**
+     * The key (if any) for a Trie row. 
+     * A row may be a leaf, a node or both in the Trie tree.
+     */
+    private final String[] _key;
+    
+    /**
+     * The value (if any) for a Trie row. 
+     * A row may be a leaf, a node or both in the Trie tree.
+     */
+    private final V[] _value;
+    
+    /**
+     * The number of rows allocated
+     */
+    private char _rows;
+
+    /* ------------------------------------------------------------ */
+    /** Create a case insensitive Trie of default capacity.
+     */
+    public ArrayTernaryTrie()
+    {
+        this(128);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Create a Trie of default capacity
+     * @param insensitive true if the Trie is insensitive to the case of the key.
+     */
+    public ArrayTernaryTrie(boolean insensitive)
+    {
+        this(insensitive,128);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Create a case insensitive Trie
+     * @param capacity  The capacity of the Trie, which is in the worst case
+     * is the total number of characters of all keys stored in the Trie.
+     * The capacity needed is dependent of the shared prefixes of the keys.
+     * For example, a capacity of 6 nodes is required to store keys "foo" 
+     * and "bar", but a capacity of only 4 is required to
+     * store "bar" and "bat".
+     */
+    public ArrayTernaryTrie(int capacity)
+    {
+        this(true,capacity);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Create a Trie
+     * @param insensitive true if the Trie is insensitive to the case of the key.
+     * @param capacity The capacity of the Trie, which is in the worst case
+     * is the total number of characters of all keys stored in the Trie.
+     * The capacity needed is dependent of the shared prefixes of the keys.
+     * For example, a capacity of 6 nodes is required to store keys "foo" 
+     * and "bar", but a capacity of only 4 is required to
+     * store "bar" and "bat".
+     */
+    public ArrayTernaryTrie(boolean insensitive, int capacity)
+    {
+        super(insensitive);
+        _value=(V[])new Object[capacity];
+        _tree=new char[capacity*ROW_SIZE];
+        _key=new String[capacity];
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Copy Trie and change capacity by a factor
+     * @param trie
+     * @param factor
+     */
+    public ArrayTernaryTrie(ArrayTernaryTrie<V> trie, double factor)
+    {
+        super(trie.isCaseInsensitive());
+        int capacity=(int)(trie._value.length*factor);
+        _rows=trie._rows;
+        _value=Arrays.copyOf(trie._value, capacity);
+        _tree=Arrays.copyOf(trie._tree, capacity*ROW_SIZE);
+        _key=Arrays.copyOf(trie._key, capacity);
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean put(String s, V v)
+    {
+        int t=0;
+        int limit = s.length();
+        int last=0;
+        for(int k=0; k < limit; k++)
+        {
+            char c=s.charAt(k);
+            if(isCaseInsensitive() && c<128)
+                c=StringUtil.lowercases[c];
+            
+            while (true)
+            {
+                int row=ROW_SIZE*t;
+                
+                // Do we need to create the new row?
+                if (t==_rows)
+                {
+                    _rows++;
+                    if (_rows>=_key.length)
+                    {
+                        _rows--;
+                        return false;
+                    }
+                    _tree[row]=c;
+                }
+
+                char n=_tree[row];
+                int diff=n-c;
+                if (diff==0)
+                    t=_tree[last=(row+EQ)];
+                else if (diff<0)
+                    t=_tree[last=(row+LO)];
+                else
+                    t=_tree[last=(row+HI)];
+                
+                // do we need a new row?
+                if (t==0)
+                {
+                    t=_rows;
+                    _tree[last]=(char)t;
+                }
+                
+                if (diff==0)
+                    break;
+            }
+        }
+
+        // Do we need to create the new row?
+        if (t==_rows)
+        {
+            _rows++;
+            if (_rows>=_key.length)
+            {
+                _rows--;
+                return false;
+            }
+        }
+
+        // Put the key and value
+        _key[t]=v==null?null:s;
+        _value[t] = v;
+                
+        return true;
+    }
+    
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public V get(String s,int offset, int len)
+    {
+        int t = 0;
+        for(int i=0; i < len;)
+        {
+            char c=s.charAt(offset+i++);
+            if(isCaseInsensitive() && c<128)
+                c=StringUtil.lowercases[c];
+            
+            while (true)
+            {
+                int row = ROW_SIZE*t;
+                char n=_tree[row];
+                int diff=n-c;
+                
+                if (diff==0)
+                {
+                    t=_tree[row+EQ];
+                    if (t==0)
+                        return null;
+                    break;
+                }
+
+                t=_tree[row+hilo(diff)];
+                if (t==0)
+                    return null;
+            }
+        }
+        
+        return _value[t];
+    }
+
+    
+    @Override
+    public V get(ByteBuffer b, int offset, int len)
+    {
+        int t = 0;
+        offset+=b.position();
+        
+        for(int i=0; i < len;)
+        {
+            byte c=(byte)(b.get(offset+i++)&0x7f);
+            if(isCaseInsensitive())
+                c=(byte)StringUtil.lowercases[c];
+            
+            while (true)
+            {
+                int row = ROW_SIZE*t;
+                char n=_tree[row];
+                int diff=n-c;
+                
+                if (diff==0)
+                {
+                    t=_tree[row+EQ];
+                    if (t==0)
+                        return null;
+                    break;
+                }
+
+                t=_tree[row+hilo(diff)];
+                if (t==0)
+                    return null;
+            }
+        }
+
+        return (V)_value[t];
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public V getBest(String s)
+    {
+        return getBest(0,s,0,s.length());
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public V getBest(String s, int offset, int length)
+    {
+        return getBest(0,s,offset,length);
+    }
+
+    /* ------------------------------------------------------------ */
+    private V getBest(int t,String s,int offset,int len)
+    {
+        int node=t;
+        loop: for(int i=0; i<len; i++)
+        {
+            char c=s.charAt(offset+i);
+            if(isCaseInsensitive() && c<128)
+                c=StringUtil.lowercases[c];
+
+            while (true)
+            {
+                int row = ROW_SIZE*t;
+                char n=_tree[row];
+                int diff=n-c;
+                
+                if (diff==0)
+                {
+                    t=_tree[row+EQ];
+                    if (t==0)
+                        break loop;
+                    
+                    // if this node is a match, recurse to remember 
+                    if (_key[t]!=null)
+                    {
+                        node=t;
+                        V best=getBest(t,s,offset+i+1,len-i-1);
+                        if (best!=null)
+                            return best;
+                    }
+                    break;
+                }
+
+                t=_tree[row+hilo(diff)];
+                if (t==0)
+                    break loop;
+            }
+        }
+        return (V)_value[node];
+    }
+
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public V getBest(ByteBuffer b, int offset, int len)
+    {
+        if (b.hasArray())
+            return getBest(0,b.array(),b.arrayOffset()+b.position()+offset,len);
+        return getBest(0,b,offset,len);
+    }
+
+    /* ------------------------------------------------------------ */
+    private V getBest(int t,byte[] b, int offset, int len)
+    {
+        int node=t;
+        loop: for(int i=0; i<len; i++)
+        {
+            byte c=(byte)(b[offset+i]&0x7f);
+            if(isCaseInsensitive())
+                c=(byte)StringUtil.lowercases[c];
+
+            while (true)
+            {
+                int row = ROW_SIZE*t;
+                char n=_tree[row];
+                int diff=n-c;
+                
+                if (diff==0)
+                {
+                    t=_tree[row+EQ];
+                    if (t==0)
+                        break loop;
+                    
+                    // if this node is a match, recurse to remember 
+                    if (_key[t]!=null)
+                    {
+                        node=t;
+                        V best=getBest(t,b,offset+i+1,len-i-1);
+                        if (best!=null)
+                            return best;
+                    }
+                    break;
+                }
+
+                t=_tree[row+hilo(diff)];
+                if (t==0)
+                    break loop;
+            }
+        }
+        return (V)_value[node];
+    }
+
+    /* ------------------------------------------------------------ */
+    private V getBest(int t,ByteBuffer b, int offset, int len)
+    {
+        int node=t;
+        int o= offset+b.position();
+        
+        loop: for(int i=0; i<len; i++)
+        {
+            byte c=(byte)(b.get(o+i)&0x7f);
+            if(isCaseInsensitive())
+                c=(byte)StringUtil.lowercases[c];
+
+            while (true)
+            {
+                int row = ROW_SIZE*t;
+                char n=_tree[row];
+                int diff=n-c;
+                
+                if (diff==0)
+                {
+                    t=_tree[row+EQ];
+                    if (t==0)
+                        break loop;
+                    
+                    // if this node is a match, recurse to remember 
+                    if (_key[t]!=null)
+                    {
+                        node=t;
+                        V best=getBest(t,b,offset+i+1,len-i-1);
+                        if (best!=null)
+                            return best;
+                    }
+                    break;
+                }
+
+                t=_tree[row+hilo(diff)];
+                if (t==0)
+                    break loop;
+            }
+        }
+        return (V)_value[node];
+    }
+
+    @Override
+    public String toString()
+    {
+        StringBuilder buf = new StringBuilder();
+        for (int r=0;r<=_rows;r++)
+        {
+            if (_key[r]!=null && _value[r]!=null)
+            {
+                buf.append(',');
+                buf.append(_key[r]);
+                buf.append('=');
+                buf.append(_value[r].toString());
+            }
+        }
+        if (buf.length()==0)
+            return "{}";
+        
+        buf.setCharAt(0,'{');
+        buf.append('}');
+        return buf.toString();
+    }
+
+
+
+    @Override
+    public Set<String> keySet()
+    {
+        Set<String> keys = new HashSet<>();
+
+        for (int r=0;r<=_rows;r++)
+        {
+            if (_key[r]!=null && _value[r]!=null)
+                keys.add(_key[r]);
+        }
+        return keys;
+    }
+
+    @Override
+    public boolean isFull()
+    {
+        return _rows+1==_key.length;
+    }
+    
+    public static int hilo(int diff)
+    {
+        // branchless equivalent to return ((diff<0)?LO:HI);
+        // return 3+2*((diff&Integer.MIN_VALUE)>>Integer.SIZE-1);
+        return 1+(diff|Integer.MAX_VALUE)/(Integer.MAX_VALUE/2);
+    }
+    
+    public void dump()
+    {
+        for (int r=0;r<_rows;r++)
+        {
+            char c=_tree[r*ROW_SIZE+0];
+            System.err.printf("%4d [%s,%d,%d,%d] '%s':%s%n",
+                r,
+                (c<' '||c>127)?(""+(int)c):"'"+c+"'",
+                (int)_tree[r*ROW_SIZE+LO],
+                (int)_tree[r*ROW_SIZE+EQ],
+                (int)_tree[r*ROW_SIZE+HI],
+                _key[r],
+                _value[r]);
+        }
+        
+    }
+}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/ArrayTrie.java b/jetty-util/src/main/java/org/eclipse/jetty/util/ArrayTrie.java
new file mode 100644
index 0000000..172503e
--- /dev/null
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/ArrayTrie.java
@@ -0,0 +1,477 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.HashSet;
+import java.util.Set;
+
+/* ------------------------------------------------------------ */
+/** 
+ * <p>A Trie String lookup data structure using a fixed size array.</p>
+ * <p>This implementation is always case insensitive and is optimal for
+ * a small number of fixed strings with few special characters.  The
+ * Trie is stored in an array of lookup tables, each indexed by the 
+ * next character of the key.   Frequently used characters are directly
+ * indexed in each lookup table, whilst infrequently used characters
+ * must use a big character table.
+ * </p>
+ * <p>This Trie is very space efficient if the key characters are 
+ * from ' ', '+', '-', ':', ';', '.', 'A' to 'Z' or 'a' to 'z'. 
+ * Other ISO-8859-1 characters can be used by the key, but less space
+ * efficiently.
+ * </p>
+ * <p>This Trie is not Threadsafe and contains no mutual exclusion 
+ * or deliberate memory barriers.  It is intended for an ArrayTrie to be
+ * built by a single thread and then used concurrently by multiple threads
+ * and not mutated during that access.  If concurrent mutations of the
+ * Trie is required external locks need to be applied.
+ * </p>
+ * @param <V>
+ */
+public class ArrayTrie<V> extends AbstractTrie<V>
+{
+    /**
+     * The Size of a Trie row is how many characters can be looked
+     * up directly without going to a big index.  This is set at 
+     * 32 to cover case insensitive alphabet and a few other common
+     * characters. 
+     */
+    private static final int ROW_SIZE = 32;
+    
+    /**
+     * The index lookup table, this maps a character as a byte 
+     * (ISO-8859-1 or UTF8) to an index within a Trie row
+     */
+    private static final int[] __lookup = 
+    { // 0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F
+   /*0*/-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, 
+   /*2*/31, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 26, -1, 27, 30, -1,
+   /*3*/-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 28, 29, -1, -1, -1, -1,
+   /*4*/-1,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,
+   /*5*/15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
+   /*6*/-1,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,
+   /*7*/15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
+    };
+    
+    /**
+     * The Trie rows in a single array which allows a lookup of row,character
+     * to the next row in the Trie.  This is actually a 2 dimensional
+     * array that has been flattened to achieve locality of reference.
+     * The first ROW_SIZE entries are for row 0, then next ROW_SIZE 
+     * entries are for row 1 etc.   So in general instead of using
+     * _rows[row][index], we use _rows[row*ROW_SIZE+index] to look up
+     * the next row for a given character.
+     * 
+     * The array is of characters rather than integers to save space. 
+     */
+    private final char[] _rowIndex;
+    
+    /**
+     * The key (if any) for a Trie row. 
+     * A row may be a leaf, a node or both in the Trie tree.
+     */
+    private final String[] _key;
+    
+    /**
+     * The value (if any) for a Trie row. 
+     * A row may be a leaf, a node or both in the Trie tree.
+     */
+    private final V[] _value;
+    
+    /**
+     * A big index for each row.
+     * If a character outside of the lookup map is needed,
+     * then a big index will be created for the row, with
+     * 256 entries, one for each possible byte.
+     */
+    private char[][] _bigIndex;
+    
+    /**
+     * The number of rows allocated
+     */
+    private char _rows;
+
+    public ArrayTrie()
+    {
+        this(128);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param capacity  The capacity of the trie, which at the worst case
+     * is the total number of characters of all keys stored in the Trie.
+     * The capacity needed is dependent of the shared prefixes of the keys.
+     * For example, a capacity of 6 nodes is required to store keys "foo" 
+     * and "bar", but a capacity of only 4 is required to
+     * store "bar" and "bat".
+     */
+    @SuppressWarnings("unchecked")
+    public ArrayTrie(int capacity)
+    {
+        super(true);
+        _value=(V[])new Object[capacity];
+        _rowIndex=new char[capacity*32];
+        _key=new String[capacity];
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean put(String s, V v)
+    {
+        int t=0;
+        int k;
+        int limit = s.length();
+        for(k=0; k < limit; k++)
+        {
+            char c=s.charAt(k);
+            
+            int index=__lookup[c&0x7f];
+            if (index>=0)
+            {
+                int idx=t*ROW_SIZE+index;
+                t=_rowIndex[idx];
+                if (t==0)
+                {
+                    if (++_rows>=_value.length)
+                        return false;
+                    t=_rowIndex[idx]=_rows;
+                }
+            }
+            else if (c>127)
+                throw new IllegalArgumentException("non ascii character");
+            else
+            {
+                if (_bigIndex==null)
+                    _bigIndex=new char[_value.length][];
+                if (t>=_bigIndex.length)
+                    return false;
+                char[] big=_bigIndex[t];
+                if (big==null)
+                    big=_bigIndex[t]=new char[128];
+                t=big[c];
+                if (t==0)
+                {
+                    if (_rows==_value.length)
+                        return false;
+                    t=big[c]=++_rows;
+                }
+            }
+        }
+        
+        if (t>=_key.length)
+        {
+            _rows=(char)_key.length;
+            return false;
+        }
+        
+        _key[t]=v==null?null:s;
+        _value[t] = v;
+        return true;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public V get(String s, int offset, int len)
+    {
+        int t = 0;
+        for(int i=0; i < len; i++)
+        {
+            char c=s.charAt(offset+i);
+            int index=__lookup[c&0x7f];
+            if (index>=0)
+            {
+                int idx=t*ROW_SIZE+index;
+                t=_rowIndex[idx];
+                if (t==0)
+                    return null;
+            }
+            else
+            {
+                char[] big = _bigIndex==null?null:_bigIndex[t];
+                if (big==null)
+                    return null;
+                t=big[c];
+                if (t==0)
+                    return null;
+            }
+        }
+        return _value[t];
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public V get(ByteBuffer b,int offset,int len)
+    {
+        int t = 0;
+        for(int i=0; i < len; i++)
+        {
+            byte c=b.get(offset+i);
+            int index=__lookup[c&0x7f];
+            if (index>=0)
+            {
+                int idx=t*ROW_SIZE+index;
+                t=_rowIndex[idx];
+                if (t==0)
+                    return null;
+            }
+            else
+            {
+                char[] big = _bigIndex==null?null:_bigIndex[t];
+                if (big==null)
+                    return null;
+                t=big[c];
+                if (t==0)
+                    return null;
+            }
+        }
+        return (V)_value[t];
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public V getBest(byte[] b,int offset,int len)
+    {
+        return getBest(0,b,offset,len);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public V getBest(ByteBuffer b,int offset,int len)
+    {
+        if (b.hasArray())
+            return getBest(0,b.array(),b.arrayOffset()+b.position()+offset,len);
+        return getBest(0,b,offset,len);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public V getBest(String s, int offset, int len)
+    {
+        return getBest(0,s,offset,len);
+    }
+    
+    /* ------------------------------------------------------------ */
+    private V getBest(int t, String s, int offset, int len)
+    {
+        int pos=offset;
+        for(int i=0; i < len; i++)
+        {
+            char c=s.charAt(pos++);
+            int index=__lookup[c&0x7f];
+            if (index>=0)
+            {
+                int idx=t*ROW_SIZE+index;
+                int nt=_rowIndex[idx];
+                if (nt==0)
+                    break;
+                t=nt;
+            }
+            else
+            {
+                char[] big = _bigIndex==null?null:_bigIndex[t];
+                if (big==null)
+                    return null;
+                int nt=big[c];
+                if (nt==0)
+                    break;
+                t=nt;
+            }
+            
+            // Is the next Trie is a match
+            if (_key[t]!=null)
+            {
+                // Recurse so we can remember this possibility
+                V best=getBest(t,s,offset+i+1,len-i-1);
+                if (best!=null)
+                    return best;
+                return (V)_value[t];
+            }
+        }
+        return (V)_value[t];
+    }
+
+    /* ------------------------------------------------------------ */
+    private V getBest(int t,byte[] b,int offset,int len)
+    {
+        for(int i=0; i < len; i++)
+        {
+            byte c=b[offset+i];
+            int index=__lookup[c&0x7f];
+            if (index>=0)
+            {
+                int idx=t*ROW_SIZE+index;
+                int nt=_rowIndex[idx];
+                if (nt==0)
+                    break;
+                t=nt;
+            }
+            else
+            {
+                char[] big = _bigIndex==null?null:_bigIndex[t];
+                if (big==null)
+                    return null;
+                int nt=big[c];
+                if (nt==0)
+                    break;
+                t=nt;
+            }
+            
+            // Is the next Trie is a match
+            if (_key[t]!=null)
+            {
+                // Recurse so we can remember this possibility
+                V best=getBest(t,b,offset+i+1,len-i-1);
+                if (best!=null)
+                    return best;
+                break;
+            }
+        }
+        return (V)_value[t];
+    }
+    
+    private V getBest(int t,ByteBuffer b,int offset,int len)
+    {
+        int pos=b.position()+offset;
+        for(int i=0; i < len; i++)
+        {
+            byte c=b.get(pos++);
+            int index=__lookup[c&0x7f];
+            if (index>=0)
+            {
+                int idx=t*ROW_SIZE+index;
+                int nt=_rowIndex[idx];
+                if (nt==0)
+                    break;
+                t=nt;
+            }
+            else
+            {
+                char[] big = _bigIndex==null?null:_bigIndex[t];
+                if (big==null)
+                    return null;
+                int nt=big[c];
+                if (nt==0)
+                    break;
+                t=nt;
+            }
+            
+            // Is the next Trie is a match
+            if (_key[t]!=null)
+            {
+                // Recurse so we can remember this possibility
+                V best=getBest(t,b,offset+i+1,len-i-1);
+                if (best!=null)
+                    return best;
+                break;
+            }
+        }
+        return (V)_value[t];
+    }
+    
+    
+    
+
+    @Override
+    public String toString()
+    {
+        StringBuilder buf = new StringBuilder();
+        toString(buf,0);
+        
+        if (buf.length()==0)
+            return "{}";
+        
+        buf.setCharAt(0,'{');
+        buf.append('}');
+        return buf.toString();
+    }
+
+
+    private void toString(Appendable out, int t)
+    {
+        if (_value[t]!=null)
+        {
+            try
+            {
+                out.append(',');
+                out.append(_key[t]);
+                out.append('=');
+                out.append(_value[t].toString());
+            }
+            catch (IOException e)
+            {
+                throw new RuntimeException(e);
+            }
+        }
+
+        for(int i=0; i < ROW_SIZE; i++)
+        {
+            int idx=t*ROW_SIZE+i;
+            if (_rowIndex[idx] != 0)
+                toString(out,_rowIndex[idx]);
+        }
+
+        char[] big = _bigIndex==null?null:_bigIndex[t];
+        if (big!=null)
+        {
+            for (int i:big)
+                if (i!=0)
+                    toString(out,i);
+        }
+
+    }
+
+    @Override
+    public Set<String> keySet()
+    {
+        Set<String> keys = new HashSet<>();
+        keySet(keys,0);
+        return keys;
+    }
+    
+    private void keySet(Set<String> set, int t)
+    {
+        if (t<_value.length&&_value[t]!=null)
+            set.add(_key[t]);
+
+        for(int i=0; i < ROW_SIZE; i++)
+        {
+            int idx=t*ROW_SIZE+i;
+            if (idx<_rowIndex.length && _rowIndex[idx] != 0)
+                keySet(set,_rowIndex[idx]);
+        }
+        
+        char[] big = _bigIndex==null||t>=_bigIndex.length?null:_bigIndex[t];
+        if (big!=null)
+        {
+            for (int i:big)
+                if (i!=0)
+                    keySet(set,i);
+        }
+    }
+    
+    @Override
+    public boolean isFull()
+    {
+        return _rows+1>=_key.length;
+    }
+}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/ArrayUtil.java b/jetty-util/src/main/java/org/eclipse/jetty/util/ArrayUtil.java
new file mode 100644
index 0000000..903b9a4
--- /dev/null
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/ArrayUtil.java
@@ -0,0 +1,142 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.io.Serializable;
+import java.lang.reflect.Array;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+
+/* ------------------------------------------------------------ */
+/**
+ */
+public class ArrayUtil
+    implements Cloneable, Serializable
+{
+
+    /* ------------------------------------------------------------ */
+    public static<T> T[] removeFromArray(T[] array, Object item)
+    {
+        if (item==null || array==null)
+            return array;
+        for (int i=array.length;i-->0;)
+        {
+            if (item.equals(array[i]))
+            {
+                Class<?> c = array==null?item.getClass():array.getClass().getComponentType();
+                @SuppressWarnings("unchecked")
+                T[] na = (T[])Array.newInstance(c, Array.getLength(array)-1);
+                if (i>0)
+                    System.arraycopy(array, 0, na, 0, i);
+                if (i+1<array.length)
+                    System.arraycopy(array, i+1, na, i, array.length-(i+1));
+                return na;
+            }
+        }
+        return array;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Add element to an array
+     * @param array The array to add to (or null)
+     * @param item The item to add
+     * @param type The type of the array (in case of null array)
+     * @return new array with contents of array plus item
+     */
+    public static<T> T[] addToArray(T[] array, T item, Class<?> type)
+    {
+        if (array==null)
+        {
+            if (type==null && item!=null)
+                type= item.getClass();
+            @SuppressWarnings("unchecked")
+            T[] na = (T[])Array.newInstance(type, 1);
+            na[0]=item;
+            return na;
+        }
+        else
+        {
+            T[] na = Arrays.copyOf(array,array.length+1);
+            na[array.length]=item;
+            return na;
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Add element to the start of an array
+     * @param array The array to add to (or null)
+     * @param item The item to add
+     * @param type The type of the array (in case of null array)
+     * @return new array with contents of array plus item
+     */
+    public static<T> T[] prependToArray(T item, T[] array, Class<?> type)
+    {
+        if (array==null)
+        {
+            if (type==null && item!=null)
+                type= item.getClass();
+            @SuppressWarnings("unchecked")
+            T[] na = (T[])Array.newInstance(type, 1);
+            na[0]=item;
+            return na;
+        }
+        else
+        {
+            Class<?> c = array.getClass().getComponentType();
+            @SuppressWarnings("unchecked")
+            T[] na = (T[])Array.newInstance(c, Array.getLength(array)+1);
+            System.arraycopy(array, 0, na, 1, array.length);
+            na[0]=item;
+            return na;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param array Any array of object
+     * @return A new <i>modifiable</i> list initialised with the elements from <code>array</code>.
+     */
+    public static<E> List<E> asMutableList(E[] array)
+    {	
+        if (array==null || array.length==0)
+            return new ArrayList<E>();
+        return new ArrayList<E>(Arrays.asList(array));
+    }
+
+    /* ------------------------------------------------------------ */
+    public static <T> T[] removeNulls(T[] array)
+    {
+        for (T t : array)
+        {
+            if (t==null)
+            {
+                List<T> list = new ArrayList<>();
+                for (T t2:array)
+                    if (t2!=null)
+                        list.add(t2);
+                return list.toArray(Arrays.copyOf(array,list.size()));
+            }
+        }
+        return array;
+    }
+    
+}
+
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/Atomics.java b/jetty-util/src/main/java/org/eclipse/jetty/util/Atomics.java
index e3c664a..d7d6550 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/Atomics.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/Atomics.java
@@ -27,47 +27,51 @@ public class Atomics
     {
     }
 
-    public static void updateMin(AtomicLong currentMin, long newValue)
+    public static boolean updateMin(AtomicLong currentMin, long newValue)
     {
         long oldValue = currentMin.get();
         while (newValue < oldValue)
         {
             if (currentMin.compareAndSet(oldValue, newValue))
-                break;
+                return true;
             oldValue = currentMin.get();
         }
+        return false;
     }
 
-    public static void updateMax(AtomicLong currentMax, long newValue)
+    public static boolean updateMax(AtomicLong currentMax, long newValue)
     {
         long oldValue = currentMax.get();
         while (newValue > oldValue)
         {
             if (currentMax.compareAndSet(oldValue, newValue))
-                break;
+                return true;
             oldValue = currentMax.get();
         }
+        return false;
     }
 
-    public static void updateMin(AtomicInteger currentMin, int newValue)
+    public static boolean updateMin(AtomicInteger currentMin, int newValue)
     {
         int oldValue = currentMin.get();
         while (newValue < oldValue)
         {
             if (currentMin.compareAndSet(oldValue, newValue))
-                break;
+                return true;
             oldValue = currentMin.get();
         }
+        return false;
     }
 
-    public static void updateMax(AtomicInteger currentMax, int newValue)
+    public static boolean updateMax(AtomicInteger currentMax, int newValue)
     {
         int oldValue = currentMax.get();
         while (newValue > oldValue)
         {
             if (currentMax.compareAndSet(oldValue, newValue))
-                break;
+                return true;
             oldValue = currentMax.get();
         }
+        return false;
     }
 }
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/AttributesMap.java b/jetty-util/src/main/java/org/eclipse/jetty/util/AttributesMap.java
index e3e2feb..064e78b 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/AttributesMap.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/AttributesMap.java
@@ -21,143 +21,131 @@ package org.eclipse.jetty.util;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Enumeration;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicReference;
 
-/* ------------------------------------------------------------ */
-/** AttributesMap.
- * 
- *
- */
 public class AttributesMap implements Attributes
 {
-    protected final Map<String,Object> _map;
+    private final AtomicReference<ConcurrentMap<String, Object>> _map = new AtomicReference<>();
 
-    /* ------------------------------------------------------------ */
     public AttributesMap()
     {
-        _map=new HashMap<String,Object>();
     }
-    
-    /* ------------------------------------------------------------ */
-    public AttributesMap(Map<String,Object> map)
+
+    public AttributesMap(AttributesMap attributes)
     {
-        _map=map;
+        ConcurrentMap<String, Object> map = attributes.map();
+        if (map != null)
+            _map.set(new ConcurrentHashMap<>(map));
     }
 
-    /* ------------------------------------------------------------ */
-    public AttributesMap(AttributesMap map)
+    private ConcurrentMap<String, Object> map()
     {
-        _map=new HashMap<String,Object>(map._map);
+        return _map.get();
+    }
+
+    private ConcurrentMap<String, Object> ensureMap()
+    {
+        while (true)
+        {
+            ConcurrentMap<String, Object> map = map();
+            if (map != null)
+                return map;
+            map = new ConcurrentHashMap<>();
+            if (_map.compareAndSet(null, map))
+                return map;
+        }
     }
-    
-    /* ------------------------------------------------------------ */
-    /* 
-     * @see org.eclipse.jetty.util.Attributes#removeAttribute(java.lang.String)
-     */
+
+    @Override
     public void removeAttribute(String name)
     {
-        _map.remove(name);
+        Map<String, Object> map = map();
+        if (map != null)
+            map.remove(name);
     }
 
-    /* ------------------------------------------------------------ */
-    /* 
-     * @see org.eclipse.jetty.util.Attributes#setAttribute(java.lang.String, java.lang.Object)
-     */
+    @Override
     public void setAttribute(String name, Object attribute)
     {
-        if (attribute==null)
-            _map.remove(name);
+        if (attribute == null)
+            removeAttribute(name);
         else
-            _map.put(name, attribute);
+            ensureMap().put(name, attribute);
     }
 
-    /* ------------------------------------------------------------ */
-    /* 
-     * @see org.eclipse.jetty.util.Attributes#getAttribute(java.lang.String)
-     */
+    @Override
     public Object getAttribute(String name)
     {
-        return _map.get(name);
+        Map<String, Object> map = map();
+        return map == null ? null : map.get(name);
     }
 
-    /* ------------------------------------------------------------ */
-    /* 
-     * @see org.eclipse.jetty.util.Attributes#getAttributeNames()
-     */
+    @Override
     public Enumeration<String> getAttributeNames()
     {
-        return Collections.enumeration(_map.keySet());
+        return Collections.enumeration(getAttributeNameSet());
     }
 
-    /* ------------------------------------------------------------ */
-    /* 
-     * @see org.eclipse.jetty.util.Attributes#getAttributeNames()
-     */
     public Set<String> getAttributeNameSet()
     {
-        return _map.keySet();
+        return keySet();
     }
-    
-    /* ------------------------------------------------------------ */
+
     public Set<Map.Entry<String, Object>> getAttributeEntrySet()
     {
-        return _map.entrySet();
+        Map<String, Object> map = map();
+        return map == null ? Collections.<Map.Entry<String, Object>>emptySet() : map.entrySet();
     }
-    
-    /* ------------------------------------------------------------ */
-    /* 
-     * @see org.eclipse.jetty.util.Attributes#getAttributeNames()
-     */
+
     public static Enumeration<String> getAttributeNamesCopy(Attributes attrs)
     {
         if (attrs instanceof AttributesMap)
-            return Collections.enumeration(((AttributesMap)attrs)._map.keySet());
-        
-        List<String> names = new ArrayList<String>();
+            return Collections.enumeration(((AttributesMap)attrs).keySet());
+
+        List<String> names = new ArrayList<>();
         names.addAll(Collections.list(attrs.getAttributeNames()));
         return Collections.enumeration(names);
     }
 
-    /* ------------------------------------------------------------ */
-    /* 
-     * @see org.eclipse.jetty.util.Attributes#clear()
-     */
+    @Override
     public void clearAttributes()
     {
-        _map.clear();
+        Map<String, Object> map = map();
+        if (map != null)
+            map.clear();
     }
-    
-    /* ------------------------------------------------------------ */
+
     public int size()
     {
-        return _map.size();
+        Map<String, Object> map = map();
+        return map == null ? 0 : map.size();
     }
-    
-    /* ------------------------------------------------------------ */
+
     @Override
     public String toString()
     {
-        return _map.toString();
+        Map<String, Object> map = map();
+        return map == null ? "{}" : map.toString();
     }
-    
-    /* ------------------------------------------------------------ */
-    public Set<String> keySet()
+
+    private Set<String> keySet()
     {
-        return _map.keySet();
+        Map<String, Object> map = map();
+        return map == null ? Collections.<String>emptySet() : map.keySet();
     }
-    
-    /* ------------------------------------------------------------ */
+
     public void addAll(Attributes attributes)
     {
         Enumeration<String> e = attributes.getAttributeNames();
         while (e.hasMoreElements())
         {
-            String name=e.nextElement();
-            setAttribute(name,attributes.getAttribute(name));
+            String name = e.nextElement();
+            setAttribute(name, attributes.getAttribute(name));
         }
     }
-
 }
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/B64Code.java b/jetty-util/src/main/java/org/eclipse/jetty/util/B64Code.java
index 785867c..f828642 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/B64Code.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/B64Code.java
@@ -20,22 +20,21 @@ package org.eclipse.jetty.util;
 
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
-import java.io.UnsupportedEncodingException;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
 
 
-/* ------------------------------------------------------------ */
 /** Fast B64 Encoder/Decoder as described in RFC 1421.
  * <p>Does not insert or interpret whitespace as described in RFC
  * 1521. If you require this you must pre/post process your data.
  * <p> Note that in a web context the usual case is to not want
  * linebreaks or other white space in the encoded output.
- * 
+ *
  */
 public class B64Code
 {
-    // ------------------------------------------------------------------
-    static final char __pad='=';
-    static final char[] __rfc1421alphabet=
+    private static final char __pad='=';
+    private static final char[] __rfc1421alphabet=
             {
                 'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P',
                 'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f',
@@ -43,8 +42,7 @@ public class B64Code
                 'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/'
             };
 
-    static final byte[] __rfc1421nibbles;
-
+    private static final byte[] __rfc1421nibbles;
     static
     {
         __rfc1421nibbles=new byte[256];
@@ -55,26 +53,21 @@ public class B64Code
         __rfc1421nibbles[(byte)__pad]=0;
     }
 
-    // ------------------------------------------------------------------
+    private B64Code()
+    {
+    }
+
     /**
      * Base 64 encode as described in RFC 1421.
      * <p>Does not insert whitespace as described in RFC 1521.
      * @param s String to encode.
      * @return String containing the encoded form of the input.
      */
-    static public String encode(String s)
+    public static String encode(String s)
     {
-        try
-        {
-            return encode(s,null);
-        }
-        catch (UnsupportedEncodingException e)
-        {
-            throw new IllegalArgumentException(e.toString());
-        }
+        return encode(s, (Charset)null);
     }
 
-    // ------------------------------------------------------------------
     /**
      * Base 64 encode as described in RFC 1421.
      * <p>Does not insert whitespace as described in RFC 1521.
@@ -83,19 +76,29 @@ public class B64Code
      *        the character encoding of the provided input String.
      * @return String containing the encoded form of the input.
      */
-    static public String encode(String s,String charEncoding)
-            throws UnsupportedEncodingException
+    public static String encode(String s,String charEncoding)
     {
         byte[] bytes;
         if (charEncoding==null)
-            bytes=s.getBytes(StringUtil.__ISO_8859_1);
+            bytes=s.getBytes(StandardCharsets.ISO_8859_1);
         else
-            bytes=s.getBytes(charEncoding);
+            bytes=s.getBytes(Charset.forName(charEncoding));
+        return new String(encode(bytes));
+    }
 
+    /**
+     * Base 64 encode as described in RFC 1421.
+     * <p>Does not insert whitespace as described in RFC 1521.
+     * @param s String to encode.
+     * @param charEncoding The character encoding of the provided input String.
+     * @return String containing the encoded form of the input.
+     */
+    public static String encode(String s, Charset charEncoding)
+    {
+        byte[] bytes=s.getBytes(charEncoding==null ? StandardCharsets.ISO_8859_1 : charEncoding);
         return new String(encode(bytes));
     }
-    
-    // ------------------------------------------------------------------
+
     /**
      * Fast Base 64 encode as described in RFC 1421.
      * <p>Does not insert whitespace as described in RFC 1521.
@@ -103,7 +106,7 @@ public class B64Code
      * @param b byte array to encode.
      * @return char array containing the encoded form of the input.
      */
-    static public char[] encode(byte[] b)
+    public static char[] encode(byte[] b)
     {
         if (b==null)
             return null;
@@ -123,7 +126,7 @@ public class B64Code
             c[ci++]=__rfc1421alphabet[(b0>>>2)&0x3f];
             c[ci++]=__rfc1421alphabet[(b0<<4)&0x3f|(b1>>>4)&0x0f];
             c[ci++]=__rfc1421alphabet[(b1<<2)&0x3f|(b2>>>6)&0x03];
-            c[ci++]=__rfc1421alphabet[b2&077];
+            c[ci++]=__rfc1421alphabet[b2&0x3f];
         }
 
         if (bLen!=bi)
@@ -154,8 +157,7 @@ public class B64Code
 
         return c;
     }
-    
-    // ------------------------------------------------------------------
+
     /**
      * Fast Base 64 encode as described in RFC 1421 and RFC2045
      * <p>Does not insert whitespace as described in RFC 1521, unless rfc2045 is passed as true.
@@ -164,7 +166,7 @@ public class B64Code
      * @param rfc2045 If true, break lines at 76 characters with CRLF
      * @return char array containing the encoded form of the input.
      */
-    static public char[] encode(byte[] b, boolean rfc2045)
+    public static char[] encode(byte[] b, boolean rfc2045)
     {
         if (b==null)
             return null;
@@ -188,7 +190,7 @@ public class B64Code
             c[ci++]=__rfc1421alphabet[(b0>>>2)&0x3f];
             c[ci++]=__rfc1421alphabet[(b0<<4)&0x3f|(b1>>>4)&0x0f];
             c[ci++]=__rfc1421alphabet[(b1<<2)&0x3f|(b2>>>6)&0x03];
-            c[ci++]=__rfc1421alphabet[b2&077];
+            c[ci++]=__rfc1421alphabet[b2&0x3f];
             l+=4;
             if (l%76==0)
             {
@@ -228,7 +230,6 @@ public class B64Code
         return c;
     }
 
-    // ------------------------------------------------------------------
     /**
      * Base 64 decode as described in RFC 2045.
      * <p>Unlike {@link #decode(char[])}, extra whitespace is ignored.
@@ -236,24 +237,40 @@ public class B64Code
      * @param charEncoding String representing the character encoding
      *        used to map the decoded bytes into a String.
      * @return String decoded byte array.
-     * @throws UnsupportedEncodingException if the encoding is not supported
+     * @throws UnsupportedCharsetException if the encoding is not supported
      * @throws IllegalArgumentException if the input is not a valid
      *         B64 encoding.
      */
-    static public String decode(String encoded,String charEncoding)
-            throws UnsupportedEncodingException
+    public static String decode(String encoded,String charEncoding)
     {
         byte[] decoded=decode(encoded);
         if (charEncoding==null)
             return new String(decoded);
-        return new String(decoded,charEncoding);
+        return new String(decoded,Charset.forName(charEncoding));
+    }
+
+    /**
+     * Base 64 decode as described in RFC 2045.
+     * <p>Unlike {@link #decode(char[])}, extra whitespace is ignored.
+     * @param encoded String to decode.
+     * @param charEncoding Character encoding
+     *        used to map the decoded bytes into a String.
+     * @return String decoded byte array.
+     * @throws IllegalArgumentException if the input is not a valid
+     *         B64 encoding.
+     */
+    public static String decode(String encoded, Charset charEncoding)
+    {
+        byte[] decoded=decode(encoded);
+        if (charEncoding==null)
+            return new String(decoded);
+        return new String(decoded, charEncoding);
     }
 
-    /* ------------------------------------------------------------ */
     /**
      * Fast Base 64 decode as described in RFC 1421.
-     * 
-     * <p>Unlike other decode methods, this does not attempt to 
+     *
+     * <p>Unlike other decode methods, this does not attempt to
      * cope with extra whitespace as described in RFC 1521/2045.
      * <p> Avoids creating extra copies of the input/output.
      * <p> Note this code has been flattened for performance.
@@ -262,7 +279,7 @@ public class B64Code
      * @throws IllegalArgumentException if the input is not a valid
      *         B64 encoding.
      */
-    static public byte[] decode(char[] b)
+    public static byte[] decode(char[] b)
     {
         if (b==null)
             return null;
@@ -336,8 +353,7 @@ public class B64Code
 
         return r;
     }
-    
-    /* ------------------------------------------------------------ */
+
     /**
      * Base 64 decode as described in RFC 2045.
      * <p>Unlike {@link #decode(char[])}, extra whitespace is ignored.
@@ -346,7 +362,7 @@ public class B64Code
      * @throws IllegalArgumentException if the input is not a valid
      *         B64 encoding.
      */
-    static public byte[] decode(String encoded)
+    public static byte[] decode(String encoded)
     {
         if (encoded==null)
             return null;
@@ -361,8 +377,7 @@ public class B64Code
      * Base 64 decode as described in RFC 2045.
      * <p>Unlike {@link #decode(char[])}, extra whitespace is ignored.
      * @param encoded String to decode.
-     * @param output stream for decoded bytes
-     * @return byte array containing the decoded form of the input.
+     * @param bout stream for decoded bytes
      * @throws IllegalArgumentException if the input is not a valid
      *         B64 encoding.
      */
@@ -384,7 +399,7 @@ public class B64Code
 
             if (c==__pad)
                 break;
-            
+
             if (Character.isWhitespace(c))
                 continue;
 
@@ -415,8 +430,7 @@ public class B64Code
         return;
     }
     
-    
-    /* ------------------------------------------------------------ */
+
     public static void encode(int value,Appendable buf) throws IOException
     {
         buf.append(__rfc1421alphabet[0x3f&((0xFC000000&value)>>26)]);
@@ -427,8 +441,7 @@ public class B64Code
         buf.append(__rfc1421alphabet[0x3f&((0x00000003&value)<<4)]);
         buf.append('=');
     }
-    
-    /* ------------------------------------------------------------ */
+
     public static void encode(long lvalue,Appendable buf) throws IOException
     {
         int value=(int)(0xFFFFFFFC&(lvalue>>32));
@@ -437,9 +450,9 @@ public class B64Code
         buf.append(__rfc1421alphabet[0x3f&((0x000FC000&value)>>14)]);
         buf.append(__rfc1421alphabet[0x3f&((0x00003F00&value)>>8)]);
         buf.append(__rfc1421alphabet[0x3f&((0x000000FC&value)>>2)]);
-        
+
         buf.append(__rfc1421alphabet[0x3f&((0x00000003&value)<<4) + (0xf&(int)(lvalue>>28))]);
-        
+
         value=0x0FFFFFFF&(int)lvalue;
         buf.append(__rfc1421alphabet[0x3f&((0x0FC00000&value)>>22)]);
         buf.append(__rfc1421alphabet[0x3f&((0x003F0000&value)>>16)]);
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/BlockingArrayQueue.java b/jetty-util/src/main/java/org/eclipse/jetty/util/BlockingArrayQueue.java
index 7c2080a..bb1e2dd 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/BlockingArrayQueue.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/BlockingArrayQueue.java
@@ -20,182 +20,258 @@ package org.eclipse.jetty.util;
 
 import java.util.AbstractList;
 import java.util.Collection;
+import java.util.Iterator;
+import java.util.ListIterator;
 import java.util.NoSuchElementException;
+import java.util.Objects;
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;
 
-
-/* ------------------------------------------------------------ */
-/** Queue backed by a circular array.
- * 
- * This queue is uses  a variant of the two lock queue algorithm to
- * provide an efficient queue or list backed by a growable circular
- * array.  This queue also has a partial implementation of 
- * {@link java.util.concurrent.BlockingQueue}, specifically the {@link #take()} and 
- * {@link #poll(long, TimeUnit)} methods.  
- * Unlike {@link java.util.concurrent.ArrayBlockingQueue}, this class is
- * able to grow and provides a blocking put call.
- * <p>
- * The queue has both a capacity (the size of the array currently allocated)
- * and a limit (the maximum size that may be allocated), which defaults to 
+/**
+ * A BlockingQueue backed by a circular array capable or growing.
+ * <p/>
+ * This queue is uses a variant of the two lock queue algorithm to provide an efficient queue or list backed by a growable circular array.
+ * <p/>
+ * Unlike {@link java.util.concurrent.ArrayBlockingQueue}, this class is able to grow and provides a blocking put call.
+ * <p/>
+ * The queue has both a capacity (the size of the array currently allocated) and a max capacity (the maximum size that may be allocated), which defaults to
  * {@link Integer#MAX_VALUE}.
  * 
- * @param <E> The element type
+ * @param <E>
+ *            The element type
  */
 public class BlockingArrayQueue<E> extends AbstractList<E> implements BlockingQueue<E>
 {
-    public final int DEFAULT_CAPACITY=128;
-    public final int DEFAULT_GROWTH=64;
-    private final int _limit;
-    private final AtomicInteger _size=new AtomicInteger();
+    /**
+     * The head offset in the {@link #_indexes} array, displaced by 15 slots to avoid false sharing with the array length (stored before the first element of
+     * the array itself).
+     */
+    private static final int HEAD_OFFSET = MemoryUtils.getIntegersPerCacheLine() - 1;
+    /**
+     * The tail offset in the {@link #_indexes} array, displaced by 16 slots from the head to avoid false sharing with it.
+     */
+    private static final int TAIL_OFFSET = HEAD_OFFSET + MemoryUtils.getIntegersPerCacheLine();
+    /**
+     * Default initial capacity, 128.
+     */
+    public static final int DEFAULT_CAPACITY = 128;
+    /**
+     * Default growth factor, 64.
+     */
+    public static final int DEFAULT_GROWTH = 64;
+
+    private final int _maxCapacity;
     private final int _growCapacity;
-    
-    private volatile int _capacity;
-    private Object[] _elements;
-    
-    private final ReentrantLock _headLock = new ReentrantLock();
+    /**
+     * Array that holds the head and tail indexes, separated by a cache line to avoid false sharing
+     */
+    private final int[] _indexes = new int[TAIL_OFFSET + 1];
+    private final Lock _tailLock = new ReentrantLock();
+    private final AtomicInteger _size = new AtomicInteger();
+    private final Lock _headLock = new ReentrantLock();
     private final Condition _notEmpty = _headLock.newCondition();
-    private int _head;
-
-    // spacers created to prevent false sharing between head and tail http://en.wikipedia.org/wiki/False_sharing
-    // TODO verify this has benefits
-    private long _space0;
-    private long _space1;
-    private long _space2;
-    private long _space3;
-    private long _space4;
-    private long _space5;
-    private long _space6;
-    private long _space7;
-    
-    private final ReentrantLock _tailLock = new ReentrantLock();
-    private int _tail;
-    
-
-    /* ------------------------------------------------------------ */
-    /** Create a growing partially blocking Queue
+    private Object[] _elements;
+
+    /**
+     * Creates an unbounded {@link BlockingArrayQueue} with default initial capacity and grow factor.
      * 
+     * @see #DEFAULT_CAPACITY
+     * @see #DEFAULT_GROWTH
      */
     public BlockingArrayQueue()
     {
-        _elements=new Object[DEFAULT_CAPACITY];
-        _growCapacity=DEFAULT_GROWTH;
-        _capacity=_elements.length;
-        _limit=Integer.MAX_VALUE;
+        _elements = new Object[DEFAULT_CAPACITY];
+        _growCapacity = DEFAULT_GROWTH;
+        _maxCapacity = Integer.MAX_VALUE;
     }
 
-    /* ------------------------------------------------------------ */
-    /** Create a fixed size partially blocking Queue
-     * @param limit The initial capacity and the limit.
+    /**
+     * Creates a bounded {@link BlockingArrayQueue} that does not grow. The capacity of the queue is fixed and equal to the given parameter.
+     * 
+     * @param maxCapacity
+     *            the maximum capacity
      */
-    public BlockingArrayQueue(int limit)
+    public BlockingArrayQueue(int maxCapacity)
     {
-        _elements=new Object[limit];
-        _capacity=_elements.length;
-        _growCapacity=-1;
-        _limit=limit;
+        _elements = new Object[maxCapacity];
+        _growCapacity = -1;
+        _maxCapacity = maxCapacity;
     }
 
-    /* ------------------------------------------------------------ */
-    /** Create a growing partially blocking Queue.
-     * @param capacity Initial capacity
-     * @param growBy Incremental capacity.
+    /**
+     * Creates an unbounded {@link BlockingArrayQueue} that grows by the given parameter.
+     * 
+     * @param capacity
+     *            the initial capacity
+     * @param growBy
+     *            the growth factor
      */
-    public BlockingArrayQueue(int capacity,int growBy)
+    public BlockingArrayQueue(int capacity, int growBy)
     {
-        _elements=new Object[capacity];
-        _capacity=_elements.length;
-        _growCapacity=growBy;
-        _limit=Integer.MAX_VALUE;
+        _elements = new Object[capacity];
+        _growCapacity = growBy;
+        _maxCapacity = Integer.MAX_VALUE;
     }
 
-    /* ------------------------------------------------------------ */
-    /** Create a growing limited partially blocking Queue.
-     * @param capacity Initial capacity
-     * @param growBy Incremental capacity.
-     * @param limit maximum capacity.
+    /**
+     * Create a bounded {@link BlockingArrayQueue} that grows by the given parameter.
+     * 
+     * @param capacity
+     *            the initial capacity
+     * @param growBy
+     *            the growth factor
+     * @param maxCapacity
+     *            the maximum capacity
      */
-    public BlockingArrayQueue(int capacity,int growBy,int limit)
+    public BlockingArrayQueue(int capacity, int growBy, int maxCapacity)
     {
-        if (capacity>limit)
+        if (capacity > maxCapacity)
             throw new IllegalArgumentException();
-        
-        _elements=new Object[capacity];
-        _capacity=_elements.length;
-        _growCapacity=growBy;
-        _limit=limit;
+        _elements = new Object[capacity];
+        _growCapacity = growBy;
+        _maxCapacity = maxCapacity;
     }
 
-    /* ------------------------------------------------------------ */
-    public int getCapacity()
+    /*----------------------------------------------------------------------------*/
+    /* Collection methods */
+    /*----------------------------------------------------------------------------*/
+
+    @Override
+    public void clear()
     {
-        return _capacity;
+
+        _tailLock.lock();
+        try
+        {
+
+            _headLock.lock();
+            try
+            {
+                _indexes[HEAD_OFFSET] = 0;
+                _indexes[TAIL_OFFSET] = 0;
+                _size.set(0);
+            }
+            finally
+            {
+                _headLock.unlock();
+            }
+        }
+        finally
+        {
+            _tailLock.unlock();
+        }
     }
 
-    /* ------------------------------------------------------------ */
-    public int getLimit()
+    @Override
+    public int size()
     {
-        return _limit;
+        return _size.get();
     }
-    
-    /* ------------------------------------------------------------ */
+
     @Override
-    public boolean add(E e)
+    public Iterator<E> iterator()
     {
-        return offer(e);
+        return listIterator();
     }
-    
-    /* ------------------------------------------------------------ */
-    public E element()
+
+    /*----------------------------------------------------------------------------*/
+    /* Queue methods */
+    /*----------------------------------------------------------------------------*/
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public E poll()
     {
-        E e = peek();
-        if (e==null)
-            throw new NoSuchElementException();
+        if (_size.get() == 0)
+            return null;
+
+        E e = null;
+
+        _headLock.lock(); // Size cannot shrink
+        try
+        {
+            if (_size.get() > 0)
+            {
+                final int head = _indexes[HEAD_OFFSET];
+                e = (E)_elements[head];
+                _elements[head] = null;
+                _indexes[HEAD_OFFSET] = (head + 1) % _elements.length;
+                if (_size.decrementAndGet() > 0)
+                    _notEmpty.signal();
+            }
+        }
+        finally
+        {
+            _headLock.unlock();
+        }
         return e;
     }
-    
-    /* ------------------------------------------------------------ */
+
     @SuppressWarnings("unchecked")
+    @Override
     public E peek()
     {
         if (_size.get() == 0)
             return null;
-        
+
         E e = null;
+
         _headLock.lock(); // Size cannot shrink
-        try 
+        try
         {
-            if (_size.get() > 0) 
-                e = (E)_elements[_head];
-        } 
-        finally 
+            if (_size.get() > 0)
+                e = (E)_elements[_indexes[HEAD_OFFSET]];
+        }
+        finally
         {
             _headLock.unlock();
         }
-        
         return e;
     }
 
-    /* ------------------------------------------------------------ */
+    @Override
+    public E remove()
+    {
+        E e = poll();
+        if (e == null)
+            throw new NoSuchElementException();
+        return e;
+    }
+
+    @Override
+    public E element()
+    {
+        E e = peek();
+        if (e == null)
+            throw new NoSuchElementException();
+        return e;
+    }
+
+    /*----------------------------------------------------------------------------*/
+    /* BlockingQueue methods */
+    /*----------------------------------------------------------------------------*/
+
+    @Override
     public boolean offer(E e)
     {
-        if (e == null) 
-            throw new NullPointerException();
-        
-        boolean not_empty=false;
-        _tailLock.lock();  // size cannot grow... only shrink
-        try 
+        Objects.requireNonNull(e);
+
+        boolean notEmpty = false;
+        _tailLock.lock(); // Size cannot grow... only shrink
+        try
         {
-            if (_size.get() >= _limit) 
+            int size = _size.get();
+            if (size >= _maxCapacity)
                 return false;
-            
-            // should we expand array?
-            if (_size.get()==_capacity)
+
+            // Should we expand array?
+            if (size == _elements.length)
             {
-                _headLock.lock();   // Need to grow array
+                _headLock.lock();
                 try
                 {
                     if (!grow())
@@ -207,19 +283,18 @@ public class BlockingArrayQueue<E> extends AbstractList<E> implements BlockingQu
                 }
             }
 
-            // add the element
-            _elements[_tail]=e;
-            _tail=(_tail+1)%_capacity;
-
-            not_empty=0==_size.getAndIncrement();
-            
-        } 
-        finally 
+            // Re-read head and tail after a possible grow
+            int tail = _indexes[TAIL_OFFSET];
+            _elements[tail] = e;
+            _indexes[TAIL_OFFSET] = (tail + 1) % _elements.length;
+            notEmpty = _size.getAndIncrement() == 0;
+        }
+        finally
         {
             _tailLock.unlock();
         }
-        
-        if (not_empty)
+
+        if (notEmpty)
         {
             _headLock.lock();
             try
@@ -230,161 +305,167 @@ public class BlockingArrayQueue<E> extends AbstractList<E> implements BlockingQu
             {
                 _headLock.unlock();
             }
-        }  
+        }
 
         return true;
     }
 
+    @Override
+    public boolean add(E e)
+    {
+        if (offer(e))
+            return true;
+        else
+            throw new IllegalStateException();
+    }
 
-    /* ------------------------------------------------------------ */
-    @SuppressWarnings("unchecked")
-    public E poll()
+    @Override
+    public void put(E o) throws InterruptedException
     {
-        if (_size.get() == 0)
-            return null;
-        
-        E e = null;
-        _headLock.lock(); // Size cannot shrink
-        try 
-        {
-            if (_size.get() > 0) 
-            {
-                final int head=_head;
-                e = (E)_elements[head];
-                _elements[head]=null;
-                _head=(head+1)%_capacity;
-                
-                if (_size.decrementAndGet()>0)
-                    _notEmpty.signal();
-            }
-        } 
-        finally 
-        {
-            _headLock.unlock();
-        }
-        
-        return e;
+        // The mechanism to await and signal when the queue is full is not implemented
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean offer(E o, long timeout, TimeUnit unit) throws InterruptedException
+    {
+        // The mechanism to await and signal when the queue is full is not implemented
+        throw new UnsupportedOperationException();
     }
 
-    /* ------------------------------------------------------------ */
-    /**
-     * Retrieves and removes the head of this queue, waiting
-     * if no elements are present on this queue.
-     * @return the head of this queue
-     * @throws InterruptedException if interrupted while waiting.
-     */
     @SuppressWarnings("unchecked")
+    @Override
     public E take() throws InterruptedException
     {
         E e = null;
-        _headLock.lockInterruptibly();  // Size cannot shrink
-        try 
+
+        _headLock.lockInterruptibly(); // Size cannot shrink
+        try
         {
-            try 
+            try
             {
                 while (_size.get() == 0)
                 {
                     _notEmpty.await();
                 }
-            } 
-            catch (InterruptedException ie) 
+            }
+            catch (InterruptedException ie)
             {
                 _notEmpty.signal();
                 throw ie;
             }
 
-            final int head=_head;
+            final int head = _indexes[HEAD_OFFSET];
             e = (E)_elements[head];
-            _elements[head]=null;
-            _head=(head+1)%_capacity;
+            _elements[head] = null;
+            _indexes[HEAD_OFFSET] = (head + 1) % _elements.length;
 
-            if (_size.decrementAndGet()>0)
+            if (_size.decrementAndGet() > 0)
                 _notEmpty.signal();
-        } 
-        finally 
+        }
+        finally
         {
             _headLock.unlock();
         }
-        
         return e;
     }
 
-    /* ------------------------------------------------------------ */
-    /**
-     * Retrieves and removes the head of this queue, waiting
-     * if necessary up to the specified wait time if no elements are
-     * present on this queue.
-     * @param time how long to wait before giving up, in units of
-     * <tt>unit</tt>
-     * @param unit a <tt>TimeUnit</tt> determining how to interpret the
-     * <tt>timeout</tt> parameter
-     * @return the head of this queue, or <tt>null</tt> if the
-     * specified waiting time elapses before an element is present.
-     * @throws InterruptedException if interrupted while waiting.
-     */
     @SuppressWarnings("unchecked")
+    @Override
     public E poll(long time, TimeUnit unit) throws InterruptedException
     {
-        
+        long nanos = unit.toNanos(time);
         E e = null;
 
-        long nanos = unit.toNanos(time);
-        
         _headLock.lockInterruptibly(); // Size cannot shrink
-        try 
-        {    
-            try 
+        try
+        {
+            try
             {
                 while (_size.get() == 0)
                 {
-                    if (nanos<=0)
+                    if (nanos <= 0)
                         return null;
                     nanos = _notEmpty.awaitNanos(nanos);
                 }
-            } 
-            catch (InterruptedException ie) 
+            }
+            catch (InterruptedException x)
             {
                 _notEmpty.signal();
-                throw ie;
+                throw x;
             }
 
-            e = (E)_elements[_head];
-            _elements[_head]=null;
-            _head=(_head+1)%_capacity;
+            int head = _indexes[HEAD_OFFSET];
+            e = (E)_elements[head];
+            _elements[head] = null;
+            _indexes[HEAD_OFFSET] = (head + 1) % _elements.length;
 
-            if (_size.decrementAndGet()>0)
+            if (_size.decrementAndGet() > 0)
                 _notEmpty.signal();
-        } 
-        finally 
+        }
+        finally
         {
             _headLock.unlock();
         }
-        
         return e;
     }
 
-    /* ------------------------------------------------------------ */
-    public E remove()
+    @Override
+    public boolean remove(Object o)
     {
-        E e=poll();
-        if (e==null)
-            throw new NoSuchElementException();
-        return e;
+
+        _tailLock.lock();
+        try
+        {
+
+            _headLock.lock();
+            try
+            {
+                if (isEmpty())
+                    return false;
+
+                final int head = _indexes[HEAD_OFFSET];
+                final int tail = _indexes[TAIL_OFFSET];
+                final int capacity = _elements.length;
+
+                int i = head;
+                while (true)
+                {
+                    if (Objects.equals(_elements[i],o))
+                    {
+                        remove(i >= head?i - head:capacity - head + i);
+                        return true;
+                    }
+                    ++i;
+                    if (i == capacity)
+                        i = 0;
+                    if (i == tail)
+                        return false;
+                }
+            }
+            finally
+            {
+                _headLock.unlock();
+            }
+        }
+        finally
+        {
+            _tailLock.unlock();
+        }
     }
 
-    /* ------------------------------------------------------------ */
     @Override
-    public void clear()
+    public int remainingCapacity()
     {
+
         _tailLock.lock();
         try
         {
+
             _headLock.lock();
             try
             {
-                _head=0;
-                _tail=0;
-                _size.set(0);
+                return getCapacity() - size();
             }
             finally
             {
@@ -397,36 +478,40 @@ public class BlockingArrayQueue<E> extends AbstractList<E> implements BlockingQu
         }
     }
 
-    /* ------------------------------------------------------------ */
     @Override
-    public boolean isEmpty()
+    public int drainTo(Collection<? super E> c)
     {
-        return _size.get()==0;
+        throw new UnsupportedOperationException();
     }
 
-    /* ------------------------------------------------------------ */
     @Override
-    public int size()
+    public int drainTo(Collection<? super E> c, int maxElements)
     {
-        return _size.get();
+        throw new UnsupportedOperationException();
     }
 
-    /* ------------------------------------------------------------ */
+    /*----------------------------------------------------------------------------*/
+    /* List methods */
+    /*----------------------------------------------------------------------------*/
+
     @SuppressWarnings("unchecked")
     @Override
     public E get(int index)
     {
+
         _tailLock.lock();
         try
         {
+
             _headLock.lock();
             try
             {
-                if (index<0 || index>=_size.get())
-                    throw new IndexOutOfBoundsException("!("+0+"<"+index+"<="+_size+")");
-                int i = _head+index;
-                if (i>=_capacity)
-                    i-=_capacity;
+                if (index < 0 || index >= _size.get())
+                    throw new IndexOutOfBoundsException("!(" + 0 + "<" + index + "<=" + _size + ")");
+                int i = _indexes[HEAD_OFFSET] + index;
+                int capacity = _elements.length;
+                if (i >= capacity)
+                    i -= capacity;
                 return (E)_elements[i];
             }
             finally
@@ -439,49 +524,63 @@ public class BlockingArrayQueue<E> extends AbstractList<E> implements BlockingQu
             _tailLock.unlock();
         }
     }
-    
-    /* ------------------------------------------------------------ */
+
     @Override
-    public E remove(int index)
+    public void add(int index, E e)
     {
+        if (e == null)
+            throw new NullPointerException();
+
         _tailLock.lock();
         try
         {
+
             _headLock.lock();
             try
             {
+                final int size = _size.get();
 
-                if (index<0 || index>=_size.get())
-                    throw new IndexOutOfBoundsException("!("+0+"<"+index+"<="+_size+")");
+                if (index < 0 || index > size)
+                    throw new IndexOutOfBoundsException("!(" + 0 + "<" + index + "<=" + _size + ")");
 
-                int i = _head+index;
-                if (i>=_capacity)
-                    i-=_capacity;
-                @SuppressWarnings("unchecked")
-                E old=(E)_elements[i];
-
-                if (i<_tail)
+                if (index == size)
                 {
-                    System.arraycopy(_elements,i+1,_elements,i,_tail-i);
-                    _tail--;
-                    _size.decrementAndGet();
+                    add(e);
                 }
                 else
                 {
-                    System.arraycopy(_elements,i+1,_elements,i,_capacity-i-1);
-                    if (_tail>0)
+                    if (_indexes[TAIL_OFFSET] == _indexes[HEAD_OFFSET])
+                        if (!grow())
+                            throw new IllegalStateException("full");
+
+                    // Re-read head and tail after a possible grow
+                    int i = _indexes[HEAD_OFFSET] + index;
+                    int capacity = _elements.length;
+
+                    if (i >= capacity)
+                        i -= capacity;
+
+                    _size.incrementAndGet();
+                    int tail = _indexes[TAIL_OFFSET];
+                    _indexes[TAIL_OFFSET] = tail = (tail + 1) % capacity;
+
+                    if (i < tail)
                     {
-                        _elements[_capacity]=_elements[0];
-                        System.arraycopy(_elements,1,_elements,0,_tail-1);
-                        _tail--;
+                        System.arraycopy(_elements,i,_elements,i + 1,tail - i);
+                        _elements[i] = e;
                     }
                     else
-                        _tail=_capacity-1;
+                    {
+                        if (tail > 0)
+                        {
+                            System.arraycopy(_elements,0,_elements,1,tail);
+                            _elements[0] = _elements[capacity - 1];
+                        }
 
-                    _size.decrementAndGet();
+                        System.arraycopy(_elements,i,_elements,i + 1,capacity - i - 1);
+                        _elements[i] = e;
+                    }
                 }
-
-                return old;
             }
             finally
             {
@@ -494,29 +593,28 @@ public class BlockingArrayQueue<E> extends AbstractList<E> implements BlockingQu
         }
     }
 
-    /* ------------------------------------------------------------ */
+    @SuppressWarnings("unchecked")
     @Override
     public E set(int index, E e)
     {
-        if (e == null) 
-            throw new NullPointerException();
+        Objects.requireNonNull(e);
 
         _tailLock.lock();
         try
         {
+
             _headLock.lock();
             try
             {
-
-                if (index<0 || index>=_size.get())
-                    throw new IndexOutOfBoundsException("!("+0+"<"+index+"<="+_size+")");
-
-                int i = _head+index;
-                if (i>=_capacity)
-                    i-=_capacity;
-                @SuppressWarnings("unchecked")
-                E old=(E)_elements[i];
-                _elements[i]=e;
+                if (index < 0 || index >= _size.get())
+                    throw new IndexOutOfBoundsException("!(" + 0 + "<" + index + "<=" + _size + ")");
+
+                int i = _indexes[HEAD_OFFSET] + index;
+                int capacity = _elements.length;
+                if (i >= capacity)
+                    i -= capacity;
+                E old = (E)_elements[i];
+                _elements[i] = e;
                 return old;
             }
             finally
@@ -529,59 +627,53 @@ public class BlockingArrayQueue<E> extends AbstractList<E> implements BlockingQu
             _tailLock.unlock();
         }
     }
-    
-    /* ------------------------------------------------------------ */
+
+    @SuppressWarnings("unchecked")
     @Override
-    public void add(int index, E e)
+    public E remove(int index)
     {
-        if (e == null) 
-            throw new NullPointerException();
 
         _tailLock.lock();
         try
         {
+
             _headLock.lock();
             try
             {
+                if (index < 0 || index >= _size.get())
+                    throw new IndexOutOfBoundsException("!(" + 0 + "<" + index + "<=" + _size + ")");
 
-                if (index<0 || index>_size.get())
-                    throw new IndexOutOfBoundsException("!("+0+"<"+index+"<="+_size+")");
+                int i = _indexes[HEAD_OFFSET] + index;
+                int capacity = _elements.length;
+                if (i >= capacity)
+                    i -= capacity;
+                E old = (E)_elements[i];
 
-                if (index==_size.get())
+                int tail = _indexes[TAIL_OFFSET];
+                if (i < tail)
                 {
-                    add(e);
+                    System.arraycopy(_elements,i + 1,_elements,i,tail - i);
+                    --_indexes[TAIL_OFFSET];
                 }
                 else
                 {
-                    if (_tail==_head)
-                        if (!grow())
-                            throw new IllegalStateException("full");
-
-                    int i = _head+index;
-                    if (i>=_capacity)
-                        i-=_capacity;
-
-                    _size.incrementAndGet();
-                    _tail=(_tail+1)%_capacity;
-
-
-                    if (i<_tail)
+                    System.arraycopy(_elements,i + 1,_elements,i,capacity - i - 1);
+                    _elements[capacity - 1] = _elements[0];
+                    if (tail > 0)
                     {
-                        System.arraycopy(_elements,i,_elements,i+1,_tail-i);
-                        _elements[i]=e;
+                        System.arraycopy(_elements,1,_elements,0,tail);
+                        --_indexes[TAIL_OFFSET];
                     }
                     else
                     {
-                        if (_tail>0)
-                        {
-                            System.arraycopy(_elements,0,_elements,1,_tail);
-                            _elements[0]=_elements[_capacity-1];
-                        }
-
-                        System.arraycopy(_elements,i,_elements,i+1,_capacity-i-1);
-                        _elements[i]=e;
+                        _indexes[TAIL_OFFSET] = capacity - 1;
                     }
+                    _elements[_indexes[TAIL_OFFSET]] = null;
                 }
+
+                _size.decrementAndGet();
+
+                return old;
             }
             finally
             {
@@ -594,46 +686,34 @@ public class BlockingArrayQueue<E> extends AbstractList<E> implements BlockingQu
         }
     }
 
-    /* ------------------------------------------------------------ */
-    private boolean grow()
+    @Override
+    public ListIterator<E> listIterator(int index)
     {
-        if (_growCapacity<=0)
-            return false;
 
         _tailLock.lock();
         try
         {
+
             _headLock.lock();
             try
             {
-                final int head=_head;
-                final int tail=_tail;
-                final int new_tail;
-
-                Object[] elements=new Object[_capacity+_growCapacity];
-
-                if (head<tail)
-                {
-                    new_tail=tail-head;
-                    System.arraycopy(_elements,head,elements,0,new_tail);
-                }
-                else if (head>tail || _size.get()>0)
+                Object[] elements = new Object[size()];
+                if (size() > 0)
                 {
-                    new_tail=_capacity+tail-head;
-                    int cut=_capacity-head;
-                    System.arraycopy(_elements,head,elements,0,cut);
-                    System.arraycopy(_elements,0,elements,cut,tail);
-                }
-                else
-                {
-                    new_tail=0;
+                    int head = _indexes[HEAD_OFFSET];
+                    int tail = _indexes[TAIL_OFFSET];
+                    if (head < tail)
+                    {
+                        System.arraycopy(_elements,head,elements,0,tail - head);
+                    }
+                    else
+                    {
+                        int chunk = _elements.length - head;
+                        System.arraycopy(_elements,head,elements,0,chunk);
+                        System.arraycopy(_elements,0,elements,chunk,tail);
+                    }
                 }
-
-                _elements=elements;
-                _capacity=_elements.length;
-                _head=0;
-                _tail=new_tail; 
-                return true;
+                return new Itr(elements,index);
             }
             finally
             {
@@ -644,44 +724,80 @@ public class BlockingArrayQueue<E> extends AbstractList<E> implements BlockingQu
         {
             _tailLock.unlock();
         }
-
     }
 
-    /* ------------------------------------------------------------ */
-    public int drainTo(Collection<? super E> c)
-    {
-        throw new UnsupportedOperationException();
-    }
+    /*----------------------------------------------------------------------------*/
+    /* Additional methods */
+    /*----------------------------------------------------------------------------*/
 
-    /* ------------------------------------------------------------ */
-    public int drainTo(Collection<? super E> c, int maxElements)
+    /**
+     * @return the current capacity of this queue
+     */
+    public int getCapacity()
     {
-        throw new UnsupportedOperationException();
+        _tailLock.lock();
+        try
+        {
+            return _elements.length;
+        }
+        finally
+        {
+            _tailLock.unlock();
+        }
     }
 
-    /* ------------------------------------------------------------ */
-    public boolean offer(E o, long timeout, TimeUnit unit) throws InterruptedException
+    /**
+     * @return the max capacity of this queue, or -1 if this queue is unbounded
+     */
+    public int getMaxCapacity()
     {
-        throw new UnsupportedOperationException();
+        return _maxCapacity;
     }
 
-    /* ------------------------------------------------------------ */
-    public void put(E o) throws InterruptedException
-    {
-        if (!add(o))
-            throw new IllegalStateException("full");
-    }
+    /*----------------------------------------------------------------------------*/
+    /* Implementation methods */
+    /*----------------------------------------------------------------------------*/
 
-    /* ------------------------------------------------------------ */
-    public int remainingCapacity()
+    private boolean grow()
     {
+        if (_growCapacity <= 0)
+            return false;
+
         _tailLock.lock();
         try
         {
+
             _headLock.lock();
             try
             {
-                return getCapacity()-size();
+                final int head = _indexes[HEAD_OFFSET];
+                final int tail = _indexes[TAIL_OFFSET];
+                final int newTail;
+                final int capacity = _elements.length;
+
+                Object[] elements = new Object[capacity + _growCapacity];
+
+                if (head < tail)
+                {
+                    newTail = tail - head;
+                    System.arraycopy(_elements,head,elements,0,newTail);
+                }
+                else if (head > tail || _size.get() > 0)
+                {
+                    newTail = capacity + tail - head;
+                    int cut = capacity - head;
+                    System.arraycopy(_elements,head,elements,0,cut);
+                    System.arraycopy(_elements,0,elements,cut,tail);
+                }
+                else
+                {
+                    newTail = 0;
+                }
+
+                _elements = elements;
+                _indexes[HEAD_OFFSET] = 0;
+                _indexes[TAIL_OFFSET] = newTail;
+                return true;
             }
             finally
             {
@@ -693,12 +809,72 @@ public class BlockingArrayQueue<E> extends AbstractList<E> implements BlockingQu
             _tailLock.unlock();
         }
     }
-    
 
-    /* ------------------------------------------------------------ */
-    long sumOfSpace()
+    private class Itr implements ListIterator<E>
     {
-        // this method exists to stop clever optimisers removing the spacers
-        return _space0++ +_space1++ +_space2++ +_space3++ +_space4++ +_space5++ +_space6++ +_space7++; 
+        private final Object[] _elements;
+        private int _cursor;
+
+        public Itr(Object[] elements, int offset)
+        {
+            _elements = elements;
+            _cursor = offset;
+        }
+
+        @Override
+        public boolean hasNext()
+        {
+            return _cursor < _elements.length;
+        }
+
+        @SuppressWarnings("unchecked")
+        @Override
+        public E next()
+        {
+            return (E)_elements[_cursor++];
+        }
+
+        @Override
+        public boolean hasPrevious()
+        {
+            return _cursor > 0;
+        }
+
+        @SuppressWarnings("unchecked")
+        @Override
+        public E previous()
+        {
+            return (E)_elements[--_cursor];
+        }
+
+        @Override
+        public int nextIndex()
+        {
+            return _cursor + 1;
+        }
+
+        @Override
+        public int previousIndex()
+        {
+            return _cursor - 1;
+        }
+
+        @Override
+        public void remove()
+        {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void set(E e)
+        {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void add(E e)
+        {
+            throw new UnsupportedOperationException();
+        }
     }
 }
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/BlockingCallback.java b/jetty-util/src/main/java/org/eclipse/jetty/util/BlockingCallback.java
new file mode 100644
index 0000000..84b2002
--- /dev/null
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/BlockingCallback.java
@@ -0,0 +1,105 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.NonBlockingThread;
+
+/* ------------------------------------------------------------ */
+/**
+ * An implementation of Callback that blocks until success or failure.
+ */
+public class BlockingCallback implements Callback
+{
+    private static final Logger LOG = Log.getLogger(BlockingCallback.class);
+    
+    private static Throwable SUCCEEDED=new Throwable()
+    {
+        @Override
+        public String toString() { return "SUCCEEDED"; }
+    };
+    
+    private final CountDownLatch _latch = new CountDownLatch(1);
+    private final AtomicReference<Throwable> _state = new AtomicReference<>();
+    
+    public BlockingCallback()
+    {}
+
+    @Override
+    public void succeeded()
+    {
+        if (_state.compareAndSet(null,SUCCEEDED))
+            _latch.countDown();
+    }
+
+    @Override
+    public void failed(Throwable cause)
+    {
+        if (_state.compareAndSet(null,cause))
+            _latch.countDown();
+    }
+
+    /** Block until the Callback has succeeded or failed and 
+     * after the return leave in the state to allow reuse.
+     * This is useful for code that wants to repeatable use a FutureCallback to convert
+     * an asynchronous API to a blocking API. 
+     * @throws IOException if exception was caught during blocking, or callback was cancelled 
+     */
+    public void block() throws IOException
+    {
+        if (NonBlockingThread.isNonBlockingThread())
+            LOG.warn("Blocking a NonBlockingThread: ",new Throwable());
+        
+        try
+        {
+            _latch.await();
+            Throwable state=_state.get();
+            if (state==SUCCEEDED)
+                return;
+            if (state instanceof IOException)
+                throw (IOException) state;
+            if (state instanceof CancellationException)
+                throw (CancellationException) state;
+            throw new IOException(state);
+        }
+        catch (final InterruptedException e)
+        {
+            throw new InterruptedIOException(){{initCause(e);}};
+        }
+        finally
+        {
+            _state.set(null);
+        }
+    }
+    
+    
+    @Override
+    public String toString()
+    {
+        return String.format("%s@%x{%s}",BlockingCallback.class.getSimpleName(),hashCode(),_state.get());
+    }
+
+}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/BufferUtil.java b/jetty-util/src/main/java/org/eclipse/jetty/util/BufferUtil.java
new file mode 100644
index 0000000..8823e00
--- /dev/null
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/BufferUtil.java
@@ -0,0 +1,1088 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.RandomAccessFile;
+import java.nio.BufferOverflowException;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileChannel.MapMode;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+
+import org.eclipse.jetty.util.resource.Resource;
+
+
+/* ------------------------------------------------------------------------------- */
+/**
+ * Buffer utility methods.
+ * <p>The standard JVM {@link ByteBuffer} can exist in two modes: In fill mode the valid
+ * data is between 0 and pos; In flush mode the valid data is between the pos and the limit.
+ * The various ByteBuffer methods assume a mode and some of them will switch or enforce a mode:
+ * Allocate and clear set fill mode; flip and compact switch modes; read and write assume fill 
+ * and flush modes.    This duality can result in confusing code such as:
+ * <pre>
+ *     buffer.clear();
+ *     channel.write(buffer);
+ * </pre>
+ * Which looks as if it should write no data, but in fact writes the buffer worth of garbage.
+ * </p>
+ * <p>
+ * The BufferUtil class provides a set of utilities that operate on the convention that ByteBuffers
+ * will always be left, passed in an API or returned from a method in the flush mode - ie with
+ * valid data between the pos and limit.    This convention is adopted so as to avoid confusion as to
+ * what state a buffer is in and to avoid excessive copying of data that can result with the usage 
+ * of compress.</p> 
+ * <p>
+ * Thus this class provides alternate implementations of {@link #allocate(int)}, 
+ * {@link #allocateDirect(int)} and {@link #clear(ByteBuffer)} that leave the buffer
+ * in flush mode.   Thus the following tests will pass:<pre>
+ *     ByteBuffer buffer = BufferUtil.allocate(1024);
+ *     assert(buffer.remaining()==0);
+ *     BufferUtil.clear(buffer);
+ *     assert(buffer.remaining()==0);
+ * </pre>
+ * </p>
+ * <p>If the BufferUtil methods {@link #fill(ByteBuffer, byte[], int, int)}, 
+ * {@link #append(ByteBuffer, byte[], int, int)} or {@link #put(ByteBuffer, ByteBuffer)} are used,
+ * then the caller does not need to explicitly switch the buffer to fill mode.    
+ * If the caller wishes to use other ByteBuffer bases libraries to fill a buffer, 
+ * then they can use explicit calls of #flipToFill(ByteBuffer) and #flipToFlush(ByteBuffer, int)
+ * to change modes.  Note because this convention attempts to avoid the copies of compact, the position
+ * is not set to zero on each fill cycle and so its value must be remembered:
+ * <pre>
+ *      int pos = BufferUtil.flipToFill(buffer);
+ *      try
+ *      {
+ *          buffer.put(data);
+ *      }
+ *      finally
+ *      {
+ *          flipToFlush(buffer, pos);
+ *      }
+ * </pre>
+ * The flipToFill method will effectively clear the buffer if it is emtpy and will compact the buffer if there is no space.
+ * 
+ */
+public class BufferUtil
+{
+    static final int TEMP_BUFFER_SIZE = 4096;
+    static final byte SPACE = 0x20;
+    static final byte MINUS = '-';
+    static final byte[] DIGIT =
+            {(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'};
+
+    public static final ByteBuffer EMPTY_BUFFER = ByteBuffer.wrap(new byte[0]);
+
+    /* ------------------------------------------------------------ */
+    /** Allocate ByteBuffer in flush mode.
+     * The position and limit will both be zero, indicating that the buffer is
+     * empty and must be flipped before any data is put to it.
+     * @param capacity capacity of the allocated ByteBuffer
+     * @return Buffer
+     */
+    public static ByteBuffer allocate(int capacity)
+    {
+        ByteBuffer buf = ByteBuffer.allocate(capacity);
+        buf.limit(0);
+        return buf;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Allocate ByteBuffer in flush mode.
+     * The position and limit will both be zero, indicating that the buffer is
+     * empty and in flush mode.
+     * @param capacity capacity of the allocated ByteBuffer
+     * @return Buffer
+     */
+    public static ByteBuffer allocateDirect(int capacity)
+    {
+        ByteBuffer buf = ByteBuffer.allocateDirect(capacity);
+        buf.limit(0);
+        return buf;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /** Clear the buffer to be empty in flush mode.
+     * The position and limit are set to 0;
+     * @param buffer The buffer to clear.
+     */
+    public static void clear(ByteBuffer buffer)
+    {
+        if (buffer != null)
+        {
+            buffer.position(0);
+            buffer.limit(0);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Clear the buffer to be empty in fill mode.
+     * The position is set to 0 and the limit is set to the capacity.
+     * @param buffer The buffer to clear.
+     */
+    public static void clearToFill(ByteBuffer buffer)
+    {
+        if (buffer != null)
+        {
+            buffer.position(0);
+            buffer.limit(buffer.capacity());
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Flip the buffer to fill mode.
+     * The position is set to the first unused position in the buffer
+     * (the old limit) and the limit is set to the capacity.
+     * If the buffer is empty, then this call is effectively {@link #clearToFill(ByteBuffer)}.
+     * If there is no unused space to fill, a {@link ByteBuffer#compact()} is done to attempt
+     * to create space.
+     * <p>
+     * This method is used as a replacement to {@link ByteBuffer#compact()}.
+     *
+     * @param buffer The buffer to flip
+     * @return The position of the valid data before the flipped position. This value should be
+     * passed to a subsequent call to {@link #flipToFlush(ByteBuffer, int)}
+     */
+    public static int flipToFill(ByteBuffer buffer)
+    {
+        int position = buffer.position();
+        int limit = buffer.limit();
+        if (position == limit)
+        {
+            buffer.position(0);
+            buffer.limit(buffer.capacity());
+            return 0;
+        }
+
+        int capacity = buffer.capacity();
+        if (limit == capacity)
+        {
+            buffer.compact();
+            return 0;
+        }
+
+        buffer.position(limit);
+        buffer.limit(capacity);
+        return position;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /** Flip the buffer to Flush mode.
+     * The limit is set to the first unused byte(the old position) and
+     * the position is set to the passed position.
+     * <p>
+     * This method is used as a replacement of {@link Buffer#flip()}.
+     * @param buffer   the buffer to be flipped
+     * @param position The position of valid data to flip to. This should
+     * be the return value of the previous call to {@link #flipToFill(ByteBuffer)}
+     */
+    public static void flipToFlush(ByteBuffer buffer, int position)
+    {
+        buffer.limit(buffer.position());
+        buffer.position(position);
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /** Convert a ByteBuffer to a byte array.
+     * @param buffer The buffer to convert in flush mode. The buffer is not altered.
+     * @return An array of bytes duplicated from the buffer.
+     */
+    public static byte[] toArray(ByteBuffer buffer)
+    {
+        if (buffer.hasArray())
+        {
+            byte[] array = buffer.array();
+            int from=buffer.arrayOffset() + buffer.position();
+            return Arrays.copyOfRange(array,from,from+buffer.remaining());
+        }
+        else
+        {
+            byte[] to = new byte[buffer.remaining()];
+            buffer.slice().get(to);
+            return to;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Check for an empty or null buffer.
+     * @param buf the buffer to check
+     * @return true if the buffer is null or empty.
+     */
+    public static boolean isEmpty(ByteBuffer buf)
+    {
+        return buf == null || buf.remaining() == 0;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Check for a non null and non empty buffer.
+     * @param buf the buffer to check
+     * @return true if the buffer is not null and not empty.
+     */
+    public static boolean hasContent(ByteBuffer buf)
+    {
+        return buf != null && buf.remaining() > 0;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Check for a non null and full buffer.
+     * @param buf the buffer to check
+     * @return true if the buffer is not null and the limit equals the capacity.
+     */
+    public static boolean isFull(ByteBuffer buf)
+    {
+        return buf != null && buf.limit() == buf.capacity();
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Get remaining from null checked buffer
+     * @param buffer The buffer to get the remaining from, in flush mode.
+     * @return 0 if the buffer is null, else the bytes remaining in the buffer.
+     */
+    public static int length(ByteBuffer buffer)
+    {
+        return buffer == null ? 0 : buffer.remaining();
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Get the space from the limit to the capacity
+     * @param buffer the buffer to get the space from
+     * @return space
+     */
+    public static int space(ByteBuffer buffer)
+    {
+        if (buffer == null)
+            return 0;
+        return buffer.capacity() - buffer.limit();
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Compact the buffer
+     * @param buffer the buffer to compact
+     * @return true if the compact made a full buffer have space
+     */
+    public static boolean compact(ByteBuffer buffer)
+    {
+        if (buffer.position()==0)
+            return false;
+        boolean full = buffer.limit() == buffer.capacity();
+        buffer.compact().flip();
+        return full && buffer.limit() < buffer.capacity();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Put data from one buffer into another, avoiding over/under flows
+     * @param from Buffer to take bytes from in flush mode
+     * @param to   Buffer to put bytes to in fill mode.
+     * @return number of bytes moved
+     */
+    public static int put(ByteBuffer from, ByteBuffer to)
+    {
+        int put;
+        int remaining = from.remaining();
+        if (remaining > 0)
+        {
+            if (remaining <= to.remaining())
+            {
+                to.put(from);
+                put = remaining;
+                from.position(0);
+                from.limit(0);
+            }
+            else if (from.hasArray())
+            {
+                put = to.remaining();
+                to.put(from.array(), from.arrayOffset() + from.position(), put);
+                from.position(from.position() + put);
+            }
+            else
+            {
+                put = to.remaining();
+                ByteBuffer slice = from.slice();
+                slice.limit(put);
+                to.put(slice);
+                from.position(from.position() + put);
+            }
+        }
+        else
+            put = 0;
+
+        return put;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Put data from one buffer into another, avoiding over/under flows
+     * @param from Buffer to take bytes from in flush mode
+     * @param to   Buffer to put bytes to in flush mode. The buffer is flipToFill before the put and flipToFlush after.
+     * @return number of bytes moved
+     * @deprecated use {@link #append(ByteBuffer, ByteBuffer)}
+     */
+    public static int flipPutFlip(ByteBuffer from, ByteBuffer to)
+    {
+        return append(to,from);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Append bytes to a buffer.
+     * @param to Buffer is flush mode
+     * @param b bytes to append
+     * @param off offset into byte
+     * @param len length to append
+     * @throws BufferOverflowException
+     */
+    public static void append(ByteBuffer to, byte[] b, int off, int len) throws BufferOverflowException
+    {
+        int pos = flipToFill(to);
+        try
+        {
+            to.put(b, off, len);
+        }
+        finally
+        {
+            flipToFlush(to, pos);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Appends a byte to a buffer
+     * @param to Buffer is flush mode
+     * @param b byte to append
+     */
+    public static void append(ByteBuffer to, byte b)
+    {
+        int pos = flipToFill(to);
+        try
+        {
+            to.put(b);
+        }
+        finally
+        {
+            flipToFlush(to, pos);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Appends a buffer to a buffer
+     * @param to Buffer is flush mode
+     * @param b buffer to append
+     */
+    public static int append(ByteBuffer to, ByteBuffer b)
+    {
+        int pos = flipToFill(to);
+        try
+        {
+            return put(b, to);
+        }
+        finally
+        {
+            flipToFlush(to, pos);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Like append, but does not throw {@link BufferOverflowException}
+     * @param to Buffer is flush mode
+     * @param b bytes to fill
+     * @param off offset into byte
+     * @param len length to fill
+     */
+    public static int fill(ByteBuffer to, byte[] b, int off, int len)
+    {
+        int pos = flipToFill(to);
+        try
+        {
+            int remaining = to.remaining();
+            int take = remaining < len ? remaining : len;
+            to.put(b, off, take);
+            return take;
+        }
+        finally
+        {
+            flipToFlush(to, pos);
+        }
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public static void readFrom(File file, ByteBuffer buffer) throws IOException
+    {
+        try(RandomAccessFile raf = new RandomAccessFile(file,"r"))
+        {
+            FileChannel channel = raf.getChannel();
+            long needed=raf.length();
+
+            while (needed>0 && buffer.hasRemaining())
+                needed=needed-channel.read(buffer);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public static void readFrom(InputStream is, int needed, ByteBuffer buffer) throws IOException
+    {
+        ByteBuffer tmp = allocate(8192);
+
+        while (needed > 0 && buffer.hasRemaining())
+        {
+            int l = is.read(tmp.array(), 0, 8192);
+            if (l < 0)
+                break;
+            tmp.position(0);
+            tmp.limit(l);
+            buffer.put(tmp);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public static void writeTo(ByteBuffer buffer, OutputStream out) throws IOException
+    {
+        if (buffer.hasArray())
+        {
+            out.write(buffer.array(),buffer.arrayOffset() + buffer.position(),buffer.remaining());
+            // update buffer position, in way similar to non-array version of writeTo
+            buffer.position(buffer.position() + buffer.remaining());
+        }
+        else
+        {
+            byte[] bytes = new byte[TEMP_BUFFER_SIZE];
+            while(buffer.hasRemaining()){
+                int byteCountToWrite = Math.min(buffer.remaining(), TEMP_BUFFER_SIZE);
+                buffer.get(bytes, 0, byteCountToWrite);
+                out.write(bytes,0 , byteCountToWrite);
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Convert the buffer to an ISO-8859-1 String
+     * @param buffer The buffer to convert in flush mode. The buffer is unchanged
+     * @return The buffer as a string.
+     */
+    public static String toString(ByteBuffer buffer)
+    {
+        return toString(buffer, StandardCharsets.ISO_8859_1);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Convert the buffer to an UTF-8 String
+     * @param buffer The buffer to convert in flush mode. The buffer is unchanged
+     * @return The buffer as a string.
+     */
+    public static String toUTF8String(ByteBuffer buffer)
+    {
+        return toString(buffer, StandardCharsets.UTF_8);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Convert the buffer to an ISO-8859-1 String
+     * @param buffer  The buffer to convert in flush mode. The buffer is unchanged
+     * @param charset The {@link Charset} to use to convert the bytes
+     * @return The buffer as a string.
+     */
+    public static String toString(ByteBuffer buffer, Charset charset)
+    {
+        if (buffer == null)
+            return null;
+        byte[] array = buffer.hasArray() ? buffer.array() : null;
+        if (array == null)
+        {
+            byte[] to = new byte[buffer.remaining()];
+            buffer.slice().get(to);
+            return new String(to, 0, to.length, charset);
+        }
+        return new String(array, buffer.arrayOffset() + buffer.position(), buffer.remaining(), charset);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Convert a partial buffer to an ISO-8859-1 String
+     * @param buffer  The buffer to convert in flush mode. The buffer is unchanged
+     * @param charset The {@link Charset} to use to convert the bytes
+     * @return The buffer as a string.
+     */
+    public static String toString(ByteBuffer buffer, int position, int length, Charset charset)
+    {
+        if (buffer == null)
+            return null;
+        byte[] array = buffer.hasArray() ? buffer.array() : null;
+        if (array == null)
+        {
+            ByteBuffer ro = buffer.asReadOnlyBuffer();
+            ro.position(position);
+            ro.limit(position + length);
+            byte[] to = new byte[length];
+            ro.get(to);
+            return new String(to, 0, to.length, charset);
+        }
+        return new String(array, buffer.arrayOffset() + position, length, charset);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Convert buffer to an integer. Parses up to the first non-numeric character. If no number is found an IllegalArgumentException is thrown
+     *
+     * @param buffer
+     *            A buffer containing an integer in flush mode. The position is not changed.
+     * @return an int
+     */
+    public static int toInt(ByteBuffer buffer)
+    {
+        int val = 0;
+        boolean started = false;
+        boolean minus = false;
+
+        for (int i = buffer.position(); i < buffer.limit(); i++)
+        {
+            byte b = buffer.get(i);
+            if (b <= SPACE)
+            {
+                if (started)
+                    break;
+            }
+            else if (b >= '0' && b <= '9')
+            {
+                val = val * 10 + (b - '0');
+                started = true;
+            }
+            else if (b == MINUS && !started)
+            {
+                minus = true;
+            }
+            else
+                break;
+        }
+
+        if (started)
+            return minus ? (-val) : val;
+        throw new NumberFormatException(toString(buffer));
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Convert buffer to an integer. Parses up to the first non-numeric character. If no number is found an IllegalArgumentException is thrown
+     *
+     * @param buffer
+     *            A buffer containing an integer in flush mode. The position is updated.
+     * @return an int
+     */
+    public static int takeInt(ByteBuffer buffer)
+    {
+        int val = 0;
+        boolean started = false;
+        boolean minus = false;
+        int i;
+        for (i = buffer.position(); i < buffer.limit(); i++)
+        {
+            byte b = buffer.get(i);
+            if (b <= SPACE)
+            {
+                if (started)
+                    break;
+            }
+            else if (b >= '0' && b <= '9')
+            {
+                val = val * 10 + (b - '0');
+                started = true;
+            }
+            else if (b == MINUS && !started)
+            {
+                minus = true;
+            }
+            else
+                break;
+        }
+
+        if (started)
+        {
+            buffer.position(i);
+            return minus ? (-val) : val;
+        }
+        throw new NumberFormatException(toString(buffer));
+    }
+
+    /**
+     * Convert buffer to an long. Parses up to the first non-numeric character. If no number is found an IllegalArgumentException is thrown
+     *
+     * @param buffer
+     *            A buffer containing an integer in flush mode. The position is not changed.
+     * @return an int
+     */
+    public static long toLong(ByteBuffer buffer)
+    {
+        long val = 0;
+        boolean started = false;
+        boolean minus = false;
+
+        for (int i = buffer.position(); i < buffer.limit(); i++)
+        {
+            byte b = buffer.get(i);
+            if (b <= SPACE)
+            {
+                if (started)
+                    break;
+            }
+            else if (b >= '0' && b <= '9')
+            {
+                val = val * 10L + (b - '0');
+                started = true;
+            }
+            else if (b == MINUS && !started)
+            {
+                minus = true;
+            }
+            else
+                break;
+        }
+
+        if (started)
+            return minus ? (-val) : val;
+        throw new NumberFormatException(toString(buffer));
+    }
+
+    public static void putHexInt(ByteBuffer buffer, int n)
+    {
+        if (n < 0)
+        {
+            buffer.put((byte)'-');
+
+            if (n == Integer.MIN_VALUE)
+            {
+                buffer.put((byte)(0x7f & '8'));
+                buffer.put((byte)(0x7f & '0'));
+                buffer.put((byte)(0x7f & '0'));
+                buffer.put((byte)(0x7f & '0'));
+                buffer.put((byte)(0x7f & '0'));
+                buffer.put((byte)(0x7f & '0'));
+                buffer.put((byte)(0x7f & '0'));
+                buffer.put((byte)(0x7f & '0'));
+
+                return;
+            }
+            n = -n;
+        }
+
+        if (n < 0x10)
+        {
+            buffer.put(DIGIT[n]);
+        }
+        else
+        {
+            boolean started = false;
+            // This assumes constant time int arithmatic
+            for (int hexDivisor : hexDivisors)
+            {
+                if (n < hexDivisor)
+                {
+                    if (started)
+                        buffer.put((byte)'0');
+                    continue;
+                }
+
+                started = true;
+                int d = n / hexDivisor;
+                buffer.put(DIGIT[d]);
+                n = n - d * hexDivisor;
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public static void putDecInt(ByteBuffer buffer, int n)
+    {
+        if (n < 0)
+        {
+            buffer.put((byte)'-');
+
+            if (n == Integer.MIN_VALUE)
+            {
+                buffer.put((byte)'2');
+                n = 147483648;
+            }
+            else
+                n = -n;
+        }
+
+        if (n < 10)
+        {
+            buffer.put(DIGIT[n]);
+        }
+        else
+        {
+            boolean started = false;
+            // This assumes constant time int arithmatic
+            for (int decDivisor : decDivisors)
+            {
+                if (n < decDivisor)
+                {
+                    if (started)
+                        buffer.put((byte)'0');
+                    continue;
+                }
+
+                started = true;
+                int d = n / decDivisor;
+                buffer.put(DIGIT[d]);
+                n = n - d * decDivisor;
+            }
+        }
+    }
+
+    public static void putDecLong(ByteBuffer buffer, long n)
+    {
+        if (n < 0)
+        {
+            buffer.put((byte)'-');
+
+            if (n == Long.MIN_VALUE)
+            {
+                buffer.put((byte)'9');
+                n = 223372036854775808L;
+            }
+            else
+                n = -n;
+        }
+
+        if (n < 10)
+        {
+            buffer.put(DIGIT[(int)n]);
+        }
+        else
+        {
+            boolean started = false;
+            // This assumes constant time int arithmatic
+            for (long aDecDivisorsL : decDivisorsL)
+            {
+                if (n < aDecDivisorsL)
+                {
+                    if (started)
+                        buffer.put((byte)'0');
+                    continue;
+                }
+
+                started = true;
+                long d = n / aDecDivisorsL;
+                buffer.put(DIGIT[(int)d]);
+                n = n - d * aDecDivisorsL;
+            }
+        }
+    }
+
+    public static ByteBuffer toBuffer(int value)
+    {
+        ByteBuffer buf = ByteBuffer.allocate(32);
+        putDecInt(buf, value);
+        return buf;
+    }
+
+    public static ByteBuffer toBuffer(long value)
+    {
+        ByteBuffer buf = ByteBuffer.allocate(32);
+        putDecLong(buf, value);
+        return buf;
+    }
+
+    public static ByteBuffer toBuffer(String s)
+    {
+        return ByteBuffer.wrap(s.getBytes(StandardCharsets.ISO_8859_1));
+    }
+
+    public static ByteBuffer toDirectBuffer(String s)
+    {
+        byte[] bytes = s.getBytes(StandardCharsets.ISO_8859_1);
+        ByteBuffer buf = ByteBuffer.allocateDirect(bytes.length);
+        buf.put(bytes);
+        buf.flip();
+        return buf;
+    }
+
+    public static ByteBuffer toBuffer(String s, Charset charset)
+    {
+        return ByteBuffer.wrap(s.getBytes(charset));
+    }
+
+    public static ByteBuffer toDirectBuffer(String s, Charset charset)
+    {
+        byte[] bytes = s.getBytes(charset);
+        ByteBuffer buf = ByteBuffer.allocateDirect(bytes.length);
+        buf.put(bytes);
+        buf.flip();
+        return buf;
+    }
+
+    /**
+     * Create a new ByteBuffer using provided byte array.
+     *
+     * @param array
+     *            the byte array to back buffer with.
+     * @return ByteBuffer with provided byte array, in flush mode
+     */
+    public static ByteBuffer toBuffer(byte array[])
+    {
+        return ByteBuffer.wrap(array);
+    }
+
+    /**
+     * Create a new ByteBuffer using the provided byte array.
+     *
+     * @param array
+     *            the byte array to use.
+     * @param offset
+     *            the offset within the byte array to use from
+     * @param length
+     *            the length in bytes of the array to use
+     * @return ByteBuffer with provided byte array, in flush mode
+     */
+    public static ByteBuffer toBuffer(byte array[], int offset, int length)
+    {
+        return ByteBuffer.wrap(array, offset, length);
+    }
+
+    public static ByteBuffer toMappedBuffer(File file) throws IOException
+    {
+        try (RandomAccessFile raf = new RandomAccessFile(file, "r"))
+        {
+            return raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length());
+        }
+    }
+
+    public static ByteBuffer toBuffer(Resource resource,boolean direct) throws IOException
+    {
+        int len=(int)resource.length();
+        if (len<0)
+            throw new IllegalArgumentException("invalid resource: "+String.valueOf(resource)+" len="+len);
+        
+        ByteBuffer buffer = direct?BufferUtil.allocateDirect(len):BufferUtil.allocate(len);
+
+        int pos=BufferUtil.flipToFill(buffer);
+        if (resource.getFile()!=null)
+            BufferUtil.readFrom(resource.getFile(),buffer);
+        else
+        {
+            try (InputStream is = resource.getInputStream();)
+            {
+                BufferUtil.readFrom(is,len,buffer);
+            }
+        }
+        BufferUtil.flipToFlush(buffer,pos);
+        
+        return buffer;
+    }
+
+    public static String toSummaryString(ByteBuffer buffer)
+    {
+        if (buffer == null)
+            return "null";
+        StringBuilder buf = new StringBuilder();
+        buf.append("[p=");
+        buf.append(buffer.position());
+        buf.append(",l=");
+        buf.append(buffer.limit());
+        buf.append(",c=");
+        buf.append(buffer.capacity());
+        buf.append(",r=");
+        buf.append(buffer.remaining());
+        buf.append("]");
+        return buf.toString();
+    }
+
+    public static String toDetailString(ByteBuffer[] buffer)
+    {
+        StringBuilder builder = new StringBuilder();
+        builder.append('[');
+        for (int i = 0; i < buffer.length; i++)
+        {
+            if (i > 0) builder.append(',');
+            builder.append(toDetailString(buffer[i]));
+        }
+        builder.append(']');
+        return builder.toString();
+    }
+
+
+    
+    /* ------------------------------------------------------------ */
+    /** Convert Buffer to string ID independent of content
+     * @param buffer
+     * @return A string showing the buffer ID
+     */
+    private static void idString(ByteBuffer buffer, StringBuilder out) 
+    {
+        out.append(buffer.getClass().getSimpleName());
+        out.append("@");
+        if (buffer.hasArray() && buffer.arrayOffset()==4)
+        {
+            out.append('T');
+            byte[] array = buffer.array();
+            TypeUtil.toHex(array[0],out);
+            TypeUtil.toHex(array[1],out);
+            TypeUtil.toHex(array[2],out);
+            TypeUtil.toHex(array[3],out);
+        }
+        else
+            out.append(Integer.toHexString(System.identityHashCode(buffer)));
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Convert Buffer to string ID independent of content
+     * @param buffer
+     * @return A string showing the buffer ID
+     */
+    public static String toIDString(ByteBuffer buffer)
+    {
+        StringBuilder buf = new StringBuilder();
+        idString(buffer,buf);
+        return buf.toString();
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    /** Convert Buffer to a detail debug string of pointers and content
+     * @param buffer
+     * @return A string showing the pointers and content of the buffer
+     */
+    public static String toDetailString(ByteBuffer buffer)
+    {
+        if (buffer == null)
+            return "null";
+
+        StringBuilder buf = new StringBuilder();
+        idString(buffer,buf);
+        buf.append("[p=");
+        buf.append(buffer.position());
+        buf.append(",l=");
+        buf.append(buffer.limit());
+        buf.append(",c=");
+        buf.append(buffer.capacity());
+        buf.append(",r=");
+        buf.append(buffer.remaining());
+        buf.append("]={");
+
+        appendDebugString(buf,buffer);
+
+        buf.append("}");
+
+        return buf.toString();
+    }
+
+    private static void appendDebugString(StringBuilder buf,ByteBuffer buffer)
+    {
+        for (int i = 0; i < buffer.position(); i++)
+        {
+            appendContentChar(buf,buffer.get(i));
+            if (i == 16 && buffer.position() > 32)
+            {
+                buf.append("...");
+                i = buffer.position() - 16;
+            }
+        }
+        buf.append("<<<");
+        for (int i = buffer.position(); i < buffer.limit(); i++)
+        {
+            appendContentChar(buf,buffer.get(i));
+            if (i == buffer.position() + 16 && buffer.limit() > buffer.position() + 32)
+            {
+                buf.append("...");
+                i = buffer.limit() - 16;
+            }
+        }
+        buf.append(">>>");
+        int limit = buffer.limit();
+        buffer.limit(buffer.capacity());
+        for (int i = limit; i < buffer.capacity(); i++)
+        {
+            appendContentChar(buf,buffer.get(i));
+            if (i == limit + 16 && buffer.capacity() > limit + 32)
+            {
+                buf.append("...");
+                i = buffer.capacity() - 16;
+            }
+        }
+        buffer.limit(limit);
+    }
+
+    private static void appendContentChar(StringBuilder buf, byte b)
+    {
+        if (b == '\\')
+            buf.append("\\\\");   
+        else if (b >= ' ')
+            buf.append((char)b);
+        else if (b == '\r')
+            buf.append("\\r");
+        else if (b == '\n')
+            buf.append("\\n");
+        else if (b == '\t')
+            buf.append("\\t");
+        else
+            buf.append("\\x").append(TypeUtil.toHexString(b));
+    }
+
+    private final static int[] decDivisors =
+            {1000000000, 100000000, 10000000, 1000000, 100000, 10000, 1000, 100, 10, 1};
+
+    private final static int[] hexDivisors =
+            {0x10000000, 0x1000000, 0x100000, 0x10000, 0x1000, 0x100, 0x10, 0x1};
+
+    private final static long[] decDivisorsL =
+            {1000000000000000000L, 100000000000000000L, 10000000000000000L, 1000000000000000L, 100000000000000L, 10000000000000L, 1000000000000L, 100000000000L,
+                    10000000000L, 1000000000L, 100000000L, 10000000L, 1000000L, 100000L, 10000L, 1000L, 100L, 10L, 1L};
+
+    public static void putCRLF(ByteBuffer buffer)
+    {
+        buffer.put((byte)13);
+        buffer.put((byte)10);
+    }
+
+    public static boolean isPrefix(ByteBuffer prefix, ByteBuffer buffer)
+    {
+        if (prefix.remaining() > buffer.remaining())
+            return false;
+        int bi = buffer.position();
+        for (int i = prefix.position(); i < prefix.limit(); i++)
+            if (prefix.get(i) != buffer.get(bi++))
+                return false;
+        return true;
+    }
+
+    public static ByteBuffer ensureCapacity(ByteBuffer buffer, int capacity)
+    {
+        if (buffer==null)
+            return allocate(capacity);
+        
+        if (buffer.capacity()>=capacity)
+            return buffer;
+        
+        if (buffer.hasArray())
+            return ByteBuffer.wrap(Arrays.copyOfRange(buffer.array(), buffer.arrayOffset(), buffer.arrayOffset()+capacity),buffer.position(),buffer.remaining());
+        
+        throw new UnsupportedOperationException();
+    }
+
+
+
+}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/ByteArrayISO8859Writer.java b/jetty-util/src/main/java/org/eclipse/jetty/util/ByteArrayISO8859Writer.java
index 989db7b..08bcd6e 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/ByteArrayISO8859Writer.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/ByteArrayISO8859Writer.java
@@ -21,6 +21,8 @@ import java.io.IOException;
 import java.io.OutputStream;
 import java.io.OutputStreamWriter;
 import java.io.Writer;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
 
 
 /* ------------------------------------------------------------ */
@@ -211,7 +213,7 @@ public class ByteArrayISO8859Writer extends Writer
         if (_bout==null)
         {
             _bout = new ByteArrayOutputStream2(2*length);
-            _writer = new OutputStreamWriter(_bout,StringUtil.__ISO_8859_1);
+            _writer = new OutputStreamWriter(_bout,StandardCharsets.ISO_8859_1);
         }
         else
             _bout.reset();
@@ -252,19 +254,14 @@ public class ByteArrayISO8859Writer extends Writer
         {
             if (_fixed)
                 throw new IOException("Buffer overflow: "+_buf.length);
-            byte[] buf = new byte[(_buf.length+n)*4/3];
-            System.arraycopy(_buf,0,buf,0,_size);
-            _buf=buf;
+            _buf=Arrays.copyOf(_buf,(_buf.length+n)*4/3);
         }
     }
 
-
     /* ------------------------------------------------------------ */
     public byte[] getByteArray()
     {
-        byte[] data=new byte[_size];
-        System.arraycopy(_buf,0,data,0,_size);
-        return data;
+        return Arrays.copyOf(_buf,_size);
     }
     
 }
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/ByteArrayOutputStream2.java b/jetty-util/src/main/java/org/eclipse/jetty/util/ByteArrayOutputStream2.java
index 6523e4d..cce475d 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/ByteArrayOutputStream2.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/ByteArrayOutputStream2.java
@@ -18,6 +18,7 @@
 
 package org.eclipse.jetty.util;
 import java.io.ByteArrayOutputStream;
+import java.nio.charset.Charset;
 
 /* ------------------------------------------------------------ */
 /** ByteArrayOutputStream with public internals
@@ -46,4 +47,8 @@ public class ByteArrayOutputStream2 extends ByteArrayOutputStream
         buf[count++]=(byte)b;
     }
     
+    public String toString(Charset charset)
+    {
+        return new String(buf, 0, count, charset);
+    }
 }
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/Callback.java b/jetty-util/src/main/java/org/eclipse/jetty/util/Callback.java
new file mode 100644
index 0000000..8b10128
--- /dev/null
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/Callback.java
@@ -0,0 +1,79 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/*
+ * Copyright (c) 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.eclipse.jetty.util;
+
+/**
+ * <p>A callback abstraction that handles completed/failed events of asynchronous operations.</p>
+ *
+ * <p>Semantically this is equivalent to an optimise Promise<Void>, but callback is a more meaningful 
+ * name than EmptyPromise</p>
+ */
+public interface Callback
+{
+    /**
+     * <p>Callback invoked when the operation completes.</p>
+     *
+     * @see #failed(Throwable)
+     */
+    public abstract void succeeded();
+
+    /**
+     * <p>Callback invoked when the operation fails.</p>
+     * @param x the reason for the operation failure
+     */
+    public void failed(Throwable x);
+
+    /**
+     * <p>Empty implementation of {@link Callback}</p>
+     */
+    public static class Adapter implements Callback
+    {
+        /**
+         * Instance of Adapter that can be used when the callback methods need an empty
+         * implementation without incurring in the cost of allocating a new Adapter object.
+         */
+        public static final Adapter INSTANCE = new Adapter();
+
+        @Override
+        public void succeeded()
+        {
+        }
+
+        @Override
+        public void failed(Throwable x)
+        {
+        }
+    }
+}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/ClassLoadingObjectInputStream.java b/jetty-util/src/main/java/org/eclipse/jetty/util/ClassLoadingObjectInputStream.java
new file mode 100644
index 0000000..ae97381
--- /dev/null
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/ClassLoadingObjectInputStream.java
@@ -0,0 +1,105 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.util;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.Proxy;
+
+
+/**
+ * ClassLoadingObjectInputStream
+ *
+ * For re-inflating serialized objects, this class uses the thread context classloader
+ * rather than the jvm's default classloader selection.
+ * 
+ */
+public class ClassLoadingObjectInputStream extends ObjectInputStream
+{
+    /* ------------------------------------------------------------ */
+    public ClassLoadingObjectInputStream(java.io.InputStream in) throws IOException
+    {
+        super(in);
+    }
+
+    /* ------------------------------------------------------------ */
+    public ClassLoadingObjectInputStream () throws IOException
+    {
+        super();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public Class<?> resolveClass (java.io.ObjectStreamClass cl) throws IOException, ClassNotFoundException
+    {
+        try
+        {
+            return Class.forName(cl.getName(), false, Thread.currentThread().getContextClassLoader());
+        }
+        catch (ClassNotFoundException e)
+        {
+            return super.resolveClass(cl);
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    protected Class<?> resolveProxyClass(String[] interfaces)
+            throws IOException, ClassNotFoundException
+    {
+        ClassLoader loader = Thread.currentThread().getContextClassLoader();
+
+        ClassLoader nonPublicLoader = null;
+        boolean hasNonPublicInterface = false;
+
+        // define proxy in class loader of non-public interface(s), if any
+        Class<?>[] classObjs = new Class[interfaces.length];
+        for (int i = 0; i < interfaces.length; i++) 
+        {
+            Class<?> cl = Class.forName(interfaces[i], false, loader);
+            if ((cl.getModifiers() & Modifier.PUBLIC) == 0) 
+            {
+                if (hasNonPublicInterface) 
+                {
+                    if (nonPublicLoader != cl.getClassLoader()) 
+                    {
+                        throw new IllegalAccessError(
+                                "conflicting non-public interface class loaders");
+                    }
+                } 
+                else 
+                {
+                    nonPublicLoader = cl.getClassLoader();
+                    hasNonPublicInterface = true;
+                }
+            }
+            classObjs[i] = cl;
+        }
+        try 
+        {
+            return Proxy.getProxyClass(hasNonPublicInterface ? nonPublicLoader : loader,classObjs);
+        } 
+        catch (IllegalArgumentException e) 
+        {
+            throw new ClassNotFoundException(null, e);
+        }    
+    }
+}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/CompletableCallback.java b/jetty-util/src/main/java/org/eclipse/jetty/util/CompletableCallback.java
new file mode 100644
index 0000000..1020bb6
--- /dev/null
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/CompletableCallback.java
@@ -0,0 +1,168 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * A callback to be used by driver code that needs to know whether the callback has been
+ * succeeded or failed (that is, completed) just after the asynchronous operation or not,
+ * typically because further processing depends on the callback being completed.
+ * The driver code competes with the asynchronous operation to complete the callback.
+ * <p />
+ * If the callback is already completed, the driver code continues the processing,
+ * otherwise it suspends it. If it is suspended, the callback will be completed some time
+ * later, and {@link #resume()} or {@link #abort(Throwable)} will be called to allow the
+ * application to resume the processing.
+ * <p />
+ * Typical usage:
+ * <pre>
+ * CompletableCallback callback = new CompletableCallback()
+ * {
+ *     @Override
+ *     public void resume()
+ *     {
+ *         // continue processing
+ *     }
+ *
+ *     @Override
+ *     public void abort(Throwable failure)
+ *     {
+ *         // abort processing
+ *     }
+ * }
+ * asyncOperation(callback);
+ * boolean completed = callback.tryComplete();
+ * if (completed)
+ *     // suspend processing, async operation not done yet
+ * else
+ *     // continue processing, async operation already done
+ * </pre>
+ */
+public abstract class CompletableCallback implements Callback
+{
+    private final AtomicReference<State> state = new AtomicReference<>(State.IDLE);
+
+    @Override
+    public void succeeded()
+    {
+        while (true)
+        {
+            State current = state.get();
+            switch (current)
+            {
+                case IDLE:
+                {
+                    if (state.compareAndSet(current, State.SUCCEEDED))
+                        return;
+                    break;
+                }
+                case COMPLETED:
+                {
+                    if (state.compareAndSet(current, State.SUCCEEDED))
+                    {
+                        resume();
+                        return;
+                    }
+                    break;
+                }
+                default:
+                {
+                    throw new IllegalStateException(current.toString());
+                }
+            }
+        }
+    }
+
+    @Override
+    public void failed(Throwable x)
+    {
+        while (true)
+        {
+            State current = state.get();
+            switch (current)
+            {
+                case IDLE:
+                case COMPLETED:
+                {
+                    if (state.compareAndSet(current, State.FAILED))
+                    {
+                        abort(x);
+                        return;
+                    }
+                    break;
+                }
+                default:
+                {
+                    throw new IllegalStateException(current.toString());
+                }
+            }
+        }
+    }
+
+    /**
+     * Callback method invoked when this callback is succeeded
+     * <em>after</em> a first call to {@link #tryComplete()}.
+     */
+    public abstract void resume();
+
+    /**
+     * Callback method invoked when this callback is failed.
+     */
+    public abstract void abort(Throwable failure);
+
+    /**
+     * Tries to complete this callback; driver code should call
+     * this method once <em>after</em> the asynchronous operation
+     * to detect whether the asynchronous operation has already
+     * completed or not.
+     *
+     * @return whether the attempt to complete was successful.
+     */
+    public boolean tryComplete()
+    {
+        while (true)
+        {
+            State current = state.get();
+            switch (current)
+            {
+                case IDLE:
+                {
+                    if (state.compareAndSet(current, State.COMPLETED))
+                        return true;
+                    break;
+                }
+                case SUCCEEDED:
+                case FAILED:
+                {
+                    return false;
+                }
+                default:
+                {
+                    throw new IllegalStateException(current.toString());
+                }
+            }
+        }
+    }
+
+    private enum State
+    {
+        IDLE, SUCCEEDED, FAILED, COMPLETED
+    }
+}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/ConcurrentArrayQueue.java b/jetty-util/src/main/java/org/eclipse/jetty/util/ConcurrentArrayQueue.java
new file mode 100644
index 0000000..74aa255
--- /dev/null
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/ConcurrentArrayQueue.java
@@ -0,0 +1,573 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.util.AbstractQueue;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicIntegerArray;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.atomic.AtomicReferenceArray;
+
+/**
+ * A concurrent, unbounded implementation of {@link Queue} that uses singly-linked array blocks
+ * to store elements.
+ * <p/>
+ * This class is a drop-in replacement for {@link ConcurrentLinkedQueue}, with similar performance
+ * but producing less garbage because arrays are used to store elements rather than nodes.
+ * <p/>
+ * The algorithm used is a variation of the algorithm from Gidenstam, Sundell and Tsigas
+ * (http://www.adm.hb.se/~AGD/Presentations/CacheAwareQueue_OPODIS.pdf).
+ *
+ * @param <T>
+ */
+public class ConcurrentArrayQueue<T> extends AbstractQueue<T>
+{
+    public static final int DEFAULT_BLOCK_SIZE = 512;
+    public static final Object REMOVED_ELEMENT = new Object()
+    {
+        @Override
+        public String toString()
+        {
+            return "X";
+        }
+    };
+
+    private static final int HEAD_OFFSET = MemoryUtils.getIntegersPerCacheLine() - 1;
+    private static final int TAIL_OFFSET = MemoryUtils.getIntegersPerCacheLine()*2 -1;
+
+    private final AtomicReferenceArray<Block<T>> _blocks = new AtomicReferenceArray<>(TAIL_OFFSET + 1);
+    private final int _blockSize;
+
+    public ConcurrentArrayQueue()
+    {
+        this(DEFAULT_BLOCK_SIZE);
+    }
+
+    public ConcurrentArrayQueue(int blockSize)
+    {
+        _blockSize = blockSize;
+        Block<T> block = newBlock();
+        _blocks.set(HEAD_OFFSET,block);
+        _blocks.set(TAIL_OFFSET,block);
+    }
+
+    public int getBlockSize()
+    {
+        return _blockSize;
+    }
+
+    protected Block<T> getHeadBlock()
+    {
+        return _blocks.get(HEAD_OFFSET);
+    }
+
+    protected Block<T> getTailBlock()
+    {
+        return _blocks.get(TAIL_OFFSET);
+    }
+
+    @Override
+    public boolean offer(T item)
+    {
+        item = Objects.requireNonNull(item);
+
+        final Block<T> initialTailBlock = getTailBlock();
+        Block<T> currentTailBlock = initialTailBlock;
+        int tail = currentTailBlock.tail();
+        while (true)
+        {
+            if (tail == getBlockSize())
+            {
+                Block<T> nextTailBlock = currentTailBlock.next();
+                if (nextTailBlock == null)
+                {
+                    nextTailBlock = newBlock();
+                    if (currentTailBlock.link(nextTailBlock))
+                    {
+                        // Linking succeeded, loop
+                        currentTailBlock = nextTailBlock;
+                    }
+                    else
+                    {
+                        // Concurrent linking, use other block and loop
+                        currentTailBlock = currentTailBlock.next();
+                    }
+                }
+                else
+                {
+                    // Not at last block, loop
+                    currentTailBlock = nextTailBlock;
+                }
+                tail = currentTailBlock.tail();
+            }
+            else
+            {
+                if (currentTailBlock.peek(tail) == null)
+                {
+                    if (currentTailBlock.store(tail, item))
+                    {
+                        // Item stored
+                        break;
+                    }
+                    else
+                    {
+                        // Concurrent store, try next index
+                        ++tail;
+                    }
+                }
+                else
+                {
+                    // Not free, try next index
+                    ++tail;
+                }
+            }
+        }
+
+        updateTailBlock(initialTailBlock, currentTailBlock);
+
+        return true;
+    }
+
+    private void updateTailBlock(Block<T> oldTailBlock, Block<T> newTailBlock)
+    {
+        // Update the tail block pointer if needs to
+        if (oldTailBlock != newTailBlock)
+        {
+            // The tail block pointer is allowed to lag behind.
+            // If this update fails, it means that other threads
+            // have filled this block and installed a new one.
+            casTailBlock(oldTailBlock, newTailBlock);
+        }
+    }
+
+    protected boolean casTailBlock(Block<T> current, Block<T> update)
+    {
+        return _blocks.compareAndSet(TAIL_OFFSET,current,update);
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public T poll()
+    {
+        final Block<T> initialHeadBlock = getHeadBlock();
+        Block<T> currentHeadBlock = initialHeadBlock;
+        int head = currentHeadBlock.head();
+        T result = null;
+        while (true)
+        {
+            if (head == getBlockSize())
+            {
+                Block<T> nextHeadBlock = currentHeadBlock.next();
+                if (nextHeadBlock == null)
+                {
+                    // We could have read that the next head block was null
+                    // but another thread allocated a new block and stored a
+                    // new item. This thread could not detect this, but that
+                    // is ok, otherwise we would not be able to exit this loop.
+
+                    // Queue is empty
+                    break;
+                }
+                else
+                {
+                    // Use next block and loop
+                    currentHeadBlock = nextHeadBlock;
+                    head = currentHeadBlock.head();
+                }
+            }
+            else
+            {
+                Object element = currentHeadBlock.peek(head);
+                if (element == REMOVED_ELEMENT)
+                {
+                    // Already removed, try next index
+                    ++head;
+                }
+                else
+                {
+                    result = (T)element;
+                    if (result != null)
+                    {
+                        if (currentHeadBlock.remove(head, result, true))
+                        {
+                            // Item removed
+                            break;
+                        }
+                        else
+                        {
+                            // Concurrent remove, try next index
+                            ++head;
+                        }
+                    }
+                    else
+                    {
+                        // Queue is empty
+                        break;
+                    }
+                }
+            }
+        }
+
+        updateHeadBlock(initialHeadBlock, currentHeadBlock);
+
+        return result;
+    }
+
+    private void updateHeadBlock(Block<T> oldHeadBlock, Block<T> newHeadBlock)
+    {
+        // Update the head block pointer if needs to
+        if (oldHeadBlock != newHeadBlock)
+        {
+            // The head block pointer lagged behind.
+            // If this update fails, it means that other threads
+            // have emptied this block and pointed to a new one.
+            casHeadBlock(oldHeadBlock, newHeadBlock);
+        }
+    }
+
+    protected boolean casHeadBlock(Block<T> current, Block<T> update)
+    {
+        return _blocks.compareAndSet(HEAD_OFFSET,current,update);
+    }
+
+    @Override
+    public T peek()
+    {
+        Block<T> currentHeadBlock = getHeadBlock();
+        int head = currentHeadBlock.head();
+        while (true)
+        {
+            if (head == getBlockSize())
+            {
+                Block<T> nextHeadBlock = currentHeadBlock.next();
+                if (nextHeadBlock == null)
+                {
+                    // Queue is empty
+                    return null;
+                }
+                else
+                {
+                    // Use next block and loop
+                    currentHeadBlock = nextHeadBlock;
+                    head = currentHeadBlock.head();
+                }
+            }
+            else
+            {
+                T element = currentHeadBlock.peek(head);
+                if (element == REMOVED_ELEMENT)
+                {
+                    // Already removed, try next index
+                    ++head;
+                }
+                else
+                {
+                    return element;
+                }
+            }
+        }
+    }
+
+    @Override
+    public boolean remove(Object o)
+    {
+        Block<T> currentHeadBlock = getHeadBlock();
+        int head = currentHeadBlock.head();
+        boolean result = false;
+        while (true)
+        {
+            if (head == getBlockSize())
+            {
+                Block<T> nextHeadBlock = currentHeadBlock.next();
+                if (nextHeadBlock == null)
+                {
+                    // Not found
+                    break;
+                }
+                else
+                {
+                    // Use next block and loop
+                    currentHeadBlock = nextHeadBlock;
+                    head = currentHeadBlock.head();
+                }
+            }
+            else
+            {
+                Object element = currentHeadBlock.peek(head);
+                if (element == REMOVED_ELEMENT)
+                {
+                    // Removed, try next index
+                    ++head;
+                }
+                else
+                {
+                    if (element == null)
+                    {
+                        // Not found
+                        break;
+                    }
+                    else
+                    {
+                        if (element.equals(o))
+                        {
+                            // Found
+                            if (currentHeadBlock.remove(head, o, false))
+                            {
+                                result = true;
+                                break;
+                            }
+                            else
+                            {
+                                ++head;
+                            }
+                        }
+                        else
+                        {
+                            // Not the one we're looking for
+                            ++head;
+                        }
+                    }
+                }
+            }
+        }
+
+        return result;
+    }
+
+    @Override
+    public boolean removeAll(Collection<?> c)
+    {
+        // TODO: super invocations are based on iterator.remove(), which throws
+        return super.removeAll(c);
+    }
+
+    @Override
+    public boolean retainAll(Collection<?> c)
+    {
+        // TODO: super invocations are based on iterator.remove(), which throws
+        return super.retainAll(c);
+    }
+
+    @Override
+    public Iterator<T> iterator()
+    {
+        final List<Object[]> blocks = new ArrayList<>();
+        Block<T> currentHeadBlock = getHeadBlock();
+        while (currentHeadBlock != null)
+        {
+            Object[] elements = currentHeadBlock.arrayCopy();
+            blocks.add(elements);
+            currentHeadBlock = currentHeadBlock.next();
+        }
+        return new Iterator<T>()
+        {
+            private int blockIndex;
+            private int index;
+
+            @Override
+            public boolean hasNext()
+            {
+                while (true)
+                {
+                    if (blockIndex == blocks.size())
+                        return false;
+
+                    Object element = blocks.get(blockIndex)[index];
+
+                    if (element == null)
+                        return false;
+
+                    if (element != REMOVED_ELEMENT)
+                        return true;
+
+                    advance();
+                }
+            }
+
+            @Override
+            public T next()
+            {
+                while (true)
+                {
+                    if (blockIndex == blocks.size())
+                        throw new NoSuchElementException();
+
+                    Object element = blocks.get(blockIndex)[index];
+
+                    if (element == null)
+                        throw new NoSuchElementException();
+
+                    advance();
+
+                    if (element != REMOVED_ELEMENT) {
+                        @SuppressWarnings("unchecked")
+                        T e = (T)element;
+                        return e;
+                    }
+                }
+            }
+
+            private void advance()
+            {
+                if (++index == getBlockSize())
+                {
+                    index = 0;
+                    ++blockIndex;
+                }
+            }
+
+            @Override
+            public void remove()
+            {
+                throw new UnsupportedOperationException();
+            }
+        };
+    }
+
+    @Override
+    public int size()
+    {
+        Block<T> currentHeadBlock = getHeadBlock();
+        int head = currentHeadBlock.head();
+        int size = 0;
+        while (true)
+        {
+            if (head == getBlockSize())
+            {
+                Block<T> nextHeadBlock = currentHeadBlock.next();
+                if (nextHeadBlock == null)
+                {
+                    break;
+                }
+                else
+                {
+                    // Use next block and loop
+                    currentHeadBlock = nextHeadBlock;
+                    head = currentHeadBlock.head();
+                }
+            }
+            else
+            {
+                Object element = currentHeadBlock.peek(head);
+                if (element == REMOVED_ELEMENT)
+                {
+                    // Already removed, try next index
+                    ++head;
+                }
+                else if (element != null)
+                {
+                    ++size;
+                    ++head;
+                }
+                else
+                {
+                    break;
+                }
+            }
+        }
+        return size;
+    }
+
+    protected Block<T> newBlock()
+    {
+        return new Block<>(getBlockSize());
+    }
+
+    protected int getBlockCount()
+    {
+        int result = 0;
+        Block<T> headBlock = getHeadBlock();
+        while (headBlock != null)
+        {
+            ++result;
+            headBlock = headBlock.next();
+        }
+        return result;
+    }
+
+    protected static final class Block<E>
+    {
+        private static final int headOffset = MemoryUtils.getIntegersPerCacheLine()-1;
+        private static final int tailOffset = MemoryUtils.getIntegersPerCacheLine()*2-1;
+
+        private final AtomicReferenceArray<Object> elements;
+        private final AtomicReference<Block<E>> next = new AtomicReference<>();
+        private final AtomicIntegerArray indexes = new AtomicIntegerArray(TAIL_OFFSET+1);
+
+        protected Block(int blockSize)
+        {
+            elements = new AtomicReferenceArray<>(blockSize);
+        }
+
+        @SuppressWarnings("unchecked")
+        public E peek(int index)
+        {
+            return (E)elements.get(index);
+        }
+
+        public boolean store(int index, E item)
+        {
+            boolean result = elements.compareAndSet(index, null, item);
+            if (result)
+                indexes.incrementAndGet(tailOffset);
+            return result;
+        }
+
+        public boolean remove(int index, Object item, boolean updateHead)
+        {
+            boolean result = elements.compareAndSet(index, item, REMOVED_ELEMENT);
+            if (result && updateHead)
+                indexes.incrementAndGet(headOffset);
+            return result;
+        }
+
+        public Block<E> next()
+        {
+            return next.get();
+        }
+
+        public boolean link(Block<E> nextBlock)
+        {
+            return next.compareAndSet(null, nextBlock);
+        }
+
+        public int head()
+        {
+            return indexes.get(headOffset);
+        }
+
+        public int tail()
+        {
+            return indexes.get(tailOffset);
+        }
+
+        public Object[] arrayCopy()
+        {
+            Object[] result = new Object[elements.length()];
+            for (int i = 0; i < result.length; ++i)
+                result[i] = elements.get(i);
+            return result;
+        }
+    }
+}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/CountingCallback.java b/jetty-util/src/main/java/org/eclipse/jetty/util/CountingCallback.java
new file mode 100644
index 0000000..5cedfe6
--- /dev/null
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/CountingCallback.java
@@ -0,0 +1,98 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * <p>A callback wrapper that succeeds the wrapped callback when the count is
+ * reached, or on first failure.</p>
+ * <p>This callback is particularly useful when an async operation is split
+ * into multiple parts, for example when an original byte buffer that needs
+ * to be written, along with a callback, is split into multiple byte buffers,
+ * since it allows the original callback to be wrapped and notified only when
+ * the last part has been processed.</p>
+ * <p>Example:</p>
+ * <pre>
+ * public void process(EndPoint endPoint, ByteBuffer buffer, Callback callback)
+ * {
+ *     ByteBuffer[] buffers = split(buffer);
+ *     CountCallback countCallback = new CountCallback(callback, buffers.length);
+ *     endPoint.write(countCallback, buffers);
+ * }
+ * </pre>
+ */
+public class CountingCallback implements Callback
+{
+    private final Callback callback;
+    private final AtomicInteger count;
+
+    public CountingCallback(Callback callback, int count)
+    {
+        this.callback = callback;
+        this.count = new AtomicInteger(count);
+    }
+
+    @Override
+    public void succeeded()
+    {
+        // Forward success on the last success.
+        while (true)
+        {
+            int current = count.get();
+
+            // Already completed ?
+            if (current == 0)
+                return;
+
+            if (count.compareAndSet(current, current - 1))
+            {
+                if (current == 1)
+                    callback.succeeded();
+                return;
+            }
+        }
+    }
+
+    @Override
+    public void failed(Throwable failure)
+    {
+        // Forward failure on the first failure.
+        while (true)
+        {
+            int current = count.get();
+
+            // Already completed ?
+            if (current == 0)
+                return;
+
+            if (count.compareAndSet(current, 0))
+            {
+                callback.failed(failure);
+                return;
+            }
+        }
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s@%x", getClass().getSimpleName(), hashCode());
+    }
+}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/DateCache.java b/jetty-util/src/main/java/org/eclipse/jetty/util/DateCache.java
index ff708d0..1b5784f 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/DateCache.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/DateCache.java
@@ -18,7 +18,6 @@
 
 package org.eclipse.jetty.util;
 
-import java.text.DateFormatSymbols;
 import java.text.SimpleDateFormat;
 import java.util.Date;
 import java.util.Locale;
@@ -27,11 +26,11 @@ import java.util.TimeZone;
 /* ------------------------------------------------------------ */
 /**  Date Format Cache.
  * Computes String representations of Dates and caches
- * the results so that subsequent requests within the same minute
+ * the results so that subsequent requests within the same second
  * will be fast.
  *
- * Only format strings that contain either "ss" or "ss.SSS" are
- * handled.
+ * Only format strings that contain either "ss".  Sub second formatting is 
+ * not handled.
  *
  * The timezone of the date may be included as an ID with the "zzz"
  * format string or as an offset with the "ZZZ" format string.
@@ -39,33 +38,31 @@ import java.util.TimeZone;
  * If consecutive calls are frequently very different, then this
  * may be a little slower than a normal DateFormat.
  *
- * 
- * 
  */
 
-public class DateCache  
+public class DateCache
 {
-    public static String DEFAULT_FORMAT="EEE MMM dd HH:mm:ss zzz yyyy";
-    private static long __hitWindow=60*60;
+    public static final String DEFAULT_FORMAT="EEE MMM dd HH:mm:ss zzz yyyy";
     
-    private String _formatString;
-    private String _tzFormatString;
-    private SimpleDateFormat _tzFormat;
+    private final String _formatString;
+    private final String _tzFormatString;
+    private final SimpleDateFormat _tzFormat;
+    private final Locale _locale ;
     
-    private String _minFormatString;
-    private SimpleDateFormat _minFormat;
+    private volatile Tick _tick;
 
-    private String _secFormatString;
-    private String _secFormatString0;
-    private String _secFormatString1;
-
-    private long _lastMinutes = -1;
-    private long _lastSeconds = -1;
-    private int _lastMs = -1;
-    private String _lastResult = null;
-
-    private Locale _locale	= null;
-    private DateFormatSymbols	_dfs	= null;
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    public static class Tick
+    {
+        final long _seconds;
+        final String _string;
+        public Tick(long seconds, String string)
+        {
+            _seconds = seconds;
+            _string = string;
+        }
+    }
 
     /* ------------------------------------------------------------ */
     /** Constructor.
@@ -75,7 +72,6 @@ public class DateCache
     public DateCache()
     {
         this(DEFAULT_FORMAT);
-        getFormat().setTimeZone(TimeZone.getDefault());
     }
     
     /* ------------------------------------------------------------ */
@@ -84,74 +80,28 @@ public class DateCache
      */
     public DateCache(String format)
     {
-        _formatString=format;
-        setTimeZone(TimeZone.getDefault());
-        
+        this(format,null,TimeZone.getDefault());
     }
     
     /* ------------------------------------------------------------ */
     public DateCache(String format,Locale l)
     {
-        _formatString=format;
-        _locale = l;
-        setTimeZone(TimeZone.getDefault());       
-    }
-    
-    /* ------------------------------------------------------------ */
-    public DateCache(String format,DateFormatSymbols s)
-    {
-        _formatString=format;
-        _dfs = s;
-        setTimeZone(TimeZone.getDefault());
-    }
-
-    /* ------------------------------------------------------------ */
-    /** Set the timezone.
-     * @param tz TimeZone
-     */
-    public synchronized void setTimeZone(TimeZone tz)
-    {
-        setTzFormatString(tz);        
-        if( _locale != null ) 
-        {
-            _tzFormat=new SimpleDateFormat(_tzFormatString,_locale);
-            _minFormat=new SimpleDateFormat(_minFormatString,_locale);
-        }
-        else if( _dfs != null ) 
-        {
-            _tzFormat=new SimpleDateFormat(_tzFormatString,_dfs);
-            _minFormat=new SimpleDateFormat(_minFormatString,_dfs);
-        }
-        else 
-        {
-            _tzFormat=new SimpleDateFormat(_tzFormatString);
-            _minFormat=new SimpleDateFormat(_minFormatString);
-        }
-        _tzFormat.setTimeZone(tz);
-        _minFormat.setTimeZone(tz);
-        _lastSeconds=-1;
-        _lastMinutes=-1;        
+        this(format,l,TimeZone.getDefault());
     }
 
     /* ------------------------------------------------------------ */
-    public TimeZone getTimeZone()
-    {
-        return _tzFormat.getTimeZone();
-    }
-    
-    /* ------------------------------------------------------------ */
-    /** Set the timezone.
-     * @param timeZoneId TimeZoneId the ID of the zone as used by
-     * TimeZone.getTimeZone(id)
-     */
-    public void setTimeZoneID(String timeZoneId)
+    public DateCache(String format,Locale l,String tz)
     {
-        setTimeZone(TimeZone.getTimeZone(timeZoneId));
+        this(format,l,TimeZone.getTimeZone(tz));
     }
     
     /* ------------------------------------------------------------ */
-    private synchronized void setTzFormatString(final  TimeZone tz )
+    public DateCache(String format,Locale l,TimeZone tz)
     {
+        _formatString=format;
+        _locale = l;
+        
+
         int zIndex = _formatString.indexOf( "ZZZ" );
         if( zIndex >= 0 )
         {
@@ -170,7 +120,7 @@ public class DateCache
                 sb.append( '-' );
             }
             
-            int raw = tzOffset / (1000*60);		// Convert to seconds
+            int raw = tzOffset / (1000*60);             // Convert to seconds
             int hr = raw / 60;
             int min = raw % 60;
             
@@ -187,125 +137,132 @@ public class DateCache
         }
         else
             _tzFormatString=_formatString;
-        setMinFormatString();
+   
+        if( _locale != null ) 
+        {
+            _tzFormat=new SimpleDateFormat(_tzFormatString,_locale);
+        }
+        else 
+        {
+            _tzFormat=new SimpleDateFormat(_tzFormatString);
+        }
+        _tzFormat.setTimeZone(tz);
+        
+        _tick=null;
     }
-
     
+
     /* ------------------------------------------------------------ */
-    private void setMinFormatString()
+    public TimeZone getTimeZone()
     {
-        int i = _tzFormatString.indexOf("ss.SSS");
-        int l = 6;
-        if (i>=0)
-            throw new IllegalStateException("ms not supported");
-        i = _tzFormatString.indexOf("ss");
-        l=2;
-        
-        // Build a formatter that formats a second format string
-        String ss1=_tzFormatString.substring(0,i);
-        String ss2=_tzFormatString.substring(i+l);
-        _minFormatString =ss1+"'ss'"+ss2;
+        return _tzFormat.getTimeZone();
     }
 
+
     /* ------------------------------------------------------------ */
     /** Format a date according to our stored formatter.
      * @param inDate 
      * @return Formatted date
      */
-    public synchronized String format(Date inDate)
+    public String format(Date inDate)
     {
-        return format(inDate.getTime());
+        long seconds = inDate.getTime() / 1000;
+
+        Tick tick=_tick;
+        
+        // Is this the cached time
+        if (tick==null || seconds!=tick._seconds)
+        {
+            // It's a cache miss
+            synchronized (this)
+            {
+                return _tzFormat.format(inDate);
+            }
+        }
+        
+        return tick._string;
     }
     
     /* ------------------------------------------------------------ */
     /** Format a date according to our stored formatter.
+     * If it happens to be in the same second as the last formatNow
+     * call, then the format is reused.
      * @param inDate 
      * @return Formatted date
      */
-    public synchronized String format(long inDate)
+    public String format(long inDate)
     {
         long seconds = inDate / 1000;
 
-        // Is it not suitable to cache?
-        if (seconds<_lastSeconds ||
-            _lastSeconds>0 && seconds>_lastSeconds+__hitWindow)
+        Tick tick=_tick;
+        
+        // Is this the cached time
+        if (tick==null || seconds!=tick._seconds)
         {
             // It's a cache miss
             Date d = new Date(inDate);
-            return _tzFormat.format(d);
-            
+            synchronized (this)
+            {
+                return _tzFormat.format(d);
+            }
         }
-                                          
-        // Check if we are in the same second
-        // and don't care about millis
-        if (_lastSeconds==seconds )
-            return _lastResult;
-
-        Date d = new Date(inDate);
         
-        // Check if we need a new format string
-        long minutes = seconds/60;
-        if (_lastMinutes != minutes)
-        {
-            _lastMinutes = minutes;
-            _secFormatString=_minFormat.format(d);
-
-            int i=_secFormatString.indexOf("ss");
-            int l=2;
-            _secFormatString0=_secFormatString.substring(0,i);
-            _secFormatString1=_secFormatString.substring(i+l);
-        }
-
-        // Always format if we get here
-        _lastSeconds = seconds;
-        StringBuilder sb=new StringBuilder(_secFormatString.length());
-        sb.append(_secFormatString0);
-        int s=(int)(seconds%60);
-        if (s<10)
-            sb.append('0');
-        sb.append(s);
-        sb.append(_secFormatString1);
-        _lastResult=sb.toString();
-
-                
-        return _lastResult;
+        return tick._string;
     }
-
+    
     /* ------------------------------------------------------------ */
-    /** Format to string buffer. 
-     * @param inDate Date the format
-     * @param buffer StringBuilder
+    /** Format a date according to our stored formatter.
+     * The passed time is expected to be close to the current time, so it is 
+     * compared to the last value passed and if it is within the same second,
+     * the format is reused.  Otherwise a new cached format is created.
+     * @param now 
+     * @return Formatted date
      */
-    public void format(long inDate, StringBuilder buffer)
+    public String formatNow(long now)
     {
-        buffer.append(format(inDate));
+        long seconds = now / 1000;
+
+        Tick tick=_tick;
+        
+        // Is this the cached time
+        if (tick!=null && tick._seconds==seconds)
+            return tick._string;
+        return formatTick(now)._string;
     }
     
     /* ------------------------------------------------------------ */
-    /** Get the format.
-     */
-    public SimpleDateFormat getFormat()
+    public String now()
     {
-        return _minFormat;
+        return formatNow(System.currentTimeMillis());
     }
-
+    
     /* ------------------------------------------------------------ */
-    public String getFormatString()
+    public Tick tick()
     {
-        return _formatString;
-    }    
-
+        return formatTick(System.currentTimeMillis());
+    }
+    
     /* ------------------------------------------------------------ */
-    public String now()
+    protected Tick formatTick(long now)
     {
-        long now=System.currentTimeMillis();
-        _lastMs=(int)(now%1000);
-        return format(now);
+        long seconds = now / 1000;
+
+        // Synchronize to protect _tzFormat
+        synchronized (this)
+        {
+            // recheck the tick, to save multiple formats
+            if (_tick==null || _tick._seconds!=seconds)
+            {
+                String s= _tzFormat.format(new Date(now));
+                return _tick=new Tick(seconds,s);
+            }
+            return _tick;
+        }
     }
 
     /* ------------------------------------------------------------ */
-    public int lastMs()
+    public String getFormatString()
     {
-        return _lastMs;
-    }
+        return _formatString;
+    }    
 }
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/Fields.java b/jetty-util/src/main/java/org/eclipse/jetty/util/Fields.java
new file mode 100644
index 0000000..602a46b
--- /dev/null
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/Fields.java
@@ -0,0 +1,336 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * <p>A container for name/value pairs, known as fields.</p>
+ * <p>A {@link Field} is composed of a name string that can be case-sensitive
+ * or case-insensitive (by specifying the option at the constructor) and
+ * of a case-sensitive set of value strings.</p>
+ * <p>The implementation of this class is not thread safe.</p>
+ */
+public class Fields implements Iterable<Fields.Field>
+{
+    private final boolean caseSensitive;
+    private final Map<String, Field> fields;
+
+    /**
+     * <p>Creates an empty, modifiable, case insensitive {@link Fields} instance.</p>
+     * @see #Fields(Fields, boolean)
+     */
+    public Fields()
+    {
+        this(false);
+    }
+
+    /**
+     * <p>Creates an empty, modifiable, case insensitive {@link Fields} instance.</p>
+     * @param caseSensitive whether this {@link Fields} instance must be case sensitive
+     * @see #Fields(Fields, boolean)
+     */
+    public Fields(boolean caseSensitive)
+    {
+        this.caseSensitive = caseSensitive;
+        fields = new LinkedHashMap<>();
+    }
+
+    /**
+     * <p>Creates a {@link Fields} instance by copying the fields from the given
+     * {@link Fields} and making it (im)mutable depending on the given {@code immutable} parameter</p>
+     *
+     * @param original the {@link Fields} to copy fields from
+     * @param immutable whether this instance is immutable
+     */
+    public Fields(Fields original, boolean immutable)
+    {
+        this.caseSensitive = original.caseSensitive;
+        Map<String, Field> copy = new LinkedHashMap<>();
+        copy.putAll(original.fields);
+        fields = immutable ? Collections.unmodifiableMap(copy) : copy;
+    }
+
+    @Override
+    public boolean equals(Object obj)
+    {
+        if (this == obj)
+            return true;
+        if (obj == null || getClass() != obj.getClass())
+            return false;
+        Fields that = (Fields)obj;
+        if (getSize() != that.getSize())
+            return false;
+        if (caseSensitive != that.caseSensitive)
+            return false;
+        for (Map.Entry<String, Field> entry : fields.entrySet())
+        {
+            String name = entry.getKey();
+            Field value = entry.getValue();
+            if (!value.equals(that.get(name), caseSensitive))
+                return false;
+        }
+        return true;
+    }
+
+    @Override
+    public int hashCode()
+    {
+        return fields.hashCode();
+    }
+
+    /**
+     * @return a set of field names
+     */
+    public Set<String> getNames()
+    {
+        Set<String> result = new LinkedHashSet<>();
+        for (Field field : fields.values())
+            result.add(field.getName());
+        return result;
+    }
+
+    private String normalizeName(String name)
+    {
+        return caseSensitive ? name : name.toLowerCase(Locale.ENGLISH);
+    }
+
+    /**
+     * @param name the field name
+     * @return the {@link Field} with the given name, or null if no such field exists
+     */
+    public Field get(String name)
+    {
+        return fields.get(normalizeName(name));
+    }
+
+    /**
+     * <p>Inserts or replaces the given name/value pair as a single-valued {@link Field}.</p>
+     *
+     * @param name the field name
+     * @param value the field value
+     */
+    public void put(String name, String value)
+    {
+        // Preserve the case for the field name
+        Field field = new Field(name, value);
+        fields.put(normalizeName(name), field);
+    }
+
+    /**
+     * <p>Inserts or replaces the given {@link Field}, mapped to the {@link Field#getName() field's name}</p>
+     *
+     * @param field the field to put
+     */
+    public void put(Field field)
+    {
+        if (field != null)
+            fields.put(normalizeName(field.getName()), field);
+    }
+
+    /**
+     * <p>Adds the given value to a field with the given name,
+     * creating a {@link Field} is none exists for the given name.</p>
+     *
+     * @param name the field name
+     * @param value the field value to add
+     */
+    public void add(String name, String value)
+    {
+        String key = normalizeName(name);
+        Field field = fields.get(key);
+        if (field == null)
+        {
+            // Preserve the case for the field name
+            field = new Field(name, value);
+            fields.put(key, field);
+        }
+        else
+        {
+            field = new Field(field.getName(), field.getValues(), value);
+            fields.put(key, field);
+        }
+    }
+
+    /**
+     * <p>Removes the {@link Field} with the given name</p>
+     *
+     * @param name the name of the field to remove
+     * @return the removed field, or null if no such field existed
+     */
+    public Field remove(String name)
+    {
+        return fields.remove(normalizeName(name));
+    }
+
+    /**
+     * <p>Empties this {@link Fields} instance from all fields</p>
+     * @see #isEmpty()
+     */
+    public void clear()
+    {
+        fields.clear();
+    }
+
+    /**
+     * @return whether this {@link Fields} instance is empty
+     */
+    public boolean isEmpty()
+    {
+        return fields.isEmpty();
+    }
+
+    /**
+     * @return the number of fields
+     */
+    public int getSize()
+    {
+        return fields.size();
+    }
+
+    /**
+     * @return an iterator over the {@link Field}s present in this instance
+     */
+    @Override
+    public Iterator<Field> iterator()
+    {
+        return fields.values().iterator();
+    }
+
+    @Override
+    public String toString()
+    {
+        return fields.toString();
+    }
+
+    /**
+     * <p>A named list of string values.</p>
+     * <p>The name is case-sensitive and there must be at least one value.</p>
+     */
+    public static class Field
+    {
+        private final String name;
+        private final List<String> values;
+
+        public Field(String name, String value)
+        {
+            this(name, Collections.singletonList(value));
+        }
+
+        private Field(String name, List<String> values, String... moreValues)
+        {
+            this.name = name;
+            List<String> list = new ArrayList<>(values.size() + moreValues.length);
+            list.addAll(values);
+            list.addAll(Arrays.asList(moreValues));
+            this.values = Collections.unmodifiableList(list);
+        }
+
+        public boolean equals(Field that, boolean caseSensitive)
+        {
+            if (this == that)
+                return true;
+            if (that == null)
+                return false;
+            if (caseSensitive)
+                return equals(that);
+            return name.equalsIgnoreCase(that.name) && values.equals(that.values);
+        }
+
+        @Override
+        public boolean equals(Object obj)
+        {
+            if (this == obj)
+                return true;
+            if (obj == null || getClass() != obj.getClass())
+                return false;
+            Field that = (Field)obj;
+            return name.equals(that.name) && values.equals(that.values);
+        }
+
+        @Override
+        public int hashCode()
+        {
+            int result = name.hashCode();
+            result = 31 * result + values.hashCode();
+            return result;
+        }
+
+        /**
+         * @return the field's name
+         */
+        public String getName()
+        {
+            return name;
+        }
+
+        /**
+         * @return the first field's value
+         */
+        public String getValue()
+        {
+            return values.get(0);
+        }
+
+        /**
+         * <p>Attempts to convert the result of {@link #getValue()} to an integer,
+         * returning it if the conversion is successful; returns null if the
+         * result of {@link #getValue()} is null.</p>
+         *
+         * @return the result of {@link #getValue()} converted to an integer, or null
+         * @throws NumberFormatException if the conversion fails
+         */
+        public Integer getValueAsInt()
+        {
+            final String value = getValue();
+            return value == null ? null : Integer.valueOf(value);
+        }
+
+        /**
+         * @return the field's values
+         */
+        public List<String> getValues()
+        {
+            return values;
+        }
+
+        /**
+         * @return whether the field has multiple values
+         */
+        public boolean hasMultipleValues()
+        {
+            return values.size() > 1;
+        }
+
+        @Override
+        public String toString()
+        {
+            return String.format("%s=%s", name, values);
+        }
+    }
+}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/FutureCallback.java b/jetty-util/src/main/java/org/eclipse/jetty/util/FutureCallback.java
new file mode 100644
index 0000000..276dcd2
--- /dev/null
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/FutureCallback.java
@@ -0,0 +1,157 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.io.IOException;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public class FutureCallback implements Future<Void>,Callback
+{
+    private static Throwable COMPLETED=new Throwable();
+    private final AtomicBoolean _done=new AtomicBoolean(false);
+    private final CountDownLatch _latch=new CountDownLatch(1);
+    private Throwable _cause;
+    
+    public FutureCallback()
+    {}
+
+    public FutureCallback(boolean completed)
+    {
+        if (completed)
+        {
+            _cause=COMPLETED;
+            _done.set(true);
+            _latch.countDown();
+        }
+    }
+
+    public FutureCallback(Throwable failed)
+    {
+        _cause=failed;
+        _done.set(true);
+        _latch.countDown();
+    }
+
+    @Override
+    public void succeeded()
+    {
+        if (_done.compareAndSet(false,true))
+        {
+            _cause=COMPLETED;
+            _latch.countDown();
+        }
+    }
+
+    @Override
+    public void failed(Throwable cause)
+    {
+        if (_done.compareAndSet(false,true))
+        {
+            _cause=cause;
+            _latch.countDown();
+        }
+    }
+
+    @Override
+    public boolean cancel(boolean mayInterruptIfRunning)
+    {
+        if (_done.compareAndSet(false,true))
+        {
+            _cause=new CancellationException();
+            _latch.countDown();
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public boolean isCancelled()
+    {
+        if (_done.get())
+        {
+            try
+            {
+                _latch.await();
+            }
+            catch (InterruptedException e)
+            {
+                throw new RuntimeException(e);
+            }
+            return _cause instanceof CancellationException;
+        }
+        return false;
+    }
+
+    @Override
+    public boolean isDone()
+    {
+        return _done.get() && _latch.getCount()==0;
+    }
+
+    @Override
+    public Void get() throws InterruptedException, ExecutionException
+    {
+        _latch.await();
+        if (_cause==COMPLETED)
+            return null;
+        if (_cause instanceof CancellationException)
+            throw (CancellationException) new CancellationException().initCause(_cause);
+        throw new ExecutionException(_cause);
+    }
+
+    @Override
+    public Void get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException
+    {
+        if (!_latch.await(timeout,unit))
+            throw new TimeoutException();
+
+        if (_cause==COMPLETED)
+            return null;
+        if (_cause instanceof TimeoutException)
+            throw (TimeoutException)_cause;
+        if (_cause instanceof CancellationException)
+            throw (CancellationException) new CancellationException().initCause(_cause);
+        throw new ExecutionException(_cause);
+    }
+
+    public static void rethrow(ExecutionException e) throws IOException
+    {
+        Throwable cause=e.getCause();
+        if (cause instanceof IOException)
+            throw (IOException)cause;
+        if (cause instanceof Error)
+            throw (Error)cause;
+        if (cause instanceof RuntimeException)
+            throw (RuntimeException)cause;
+        throw new RuntimeException(cause);
+    }
+    
+    @Override
+    public String toString()
+    {
+        return String.format("FutureCallback@%x{%b,%b}",hashCode(),_done.get(),_cause==COMPLETED);
+    }
+    
+}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/FuturePromise.java b/jetty-util/src/main/java/org/eclipse/jetty/util/FuturePromise.java
new file mode 100644
index 0000000..c3e0766
--- /dev/null
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/FuturePromise.java
@@ -0,0 +1,159 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.io.IOException;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public class FuturePromise<C> implements Future<C>,Promise<C>
+{
+    private static Throwable COMPLETED=new Throwable();
+    private final AtomicBoolean _done=new AtomicBoolean(false);
+    private final CountDownLatch _latch=new CountDownLatch(1);
+    private Throwable _cause;
+    private C _result;
+    
+    public FuturePromise()
+    {}
+
+    public FuturePromise(C result)
+    {
+        _cause=COMPLETED;
+        _result=result;
+        _done.set(true);
+        _latch.countDown();
+    }
+
+    public FuturePromise(C ctx, Throwable failed)
+    {
+        _result=ctx;
+        _cause=failed;
+        _done.set(true);
+        _latch.countDown();
+    }
+
+    @Override
+    public void succeeded(C result)
+    {
+        if (_done.compareAndSet(false,true))
+        {
+            _result=result;
+            _cause=COMPLETED;
+            _latch.countDown();
+        }
+    }
+
+    @Override
+    public void failed(Throwable cause)
+    {
+        if (_done.compareAndSet(false,true))
+        {
+            _cause=cause;
+            _latch.countDown();
+        }
+    }
+
+    @Override
+    public boolean cancel(boolean mayInterruptIfRunning)
+    {
+        if (_done.compareAndSet(false,true))
+        {
+            _result=null;
+            _cause=new CancellationException();
+            _latch.countDown();
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public boolean isCancelled()
+    {
+        if (_done.get())
+        {
+            try
+            {
+                _latch.await();
+            }
+            catch (InterruptedException e)
+            {
+                throw new RuntimeException(e);
+            }
+            return _cause instanceof CancellationException;
+        }
+        return false;
+    }
+
+    @Override
+    public boolean isDone()
+    {
+        return _done.get() && _latch.getCount()==0;
+    }
+
+    @Override
+    public C get() throws InterruptedException, ExecutionException
+    {
+        _latch.await();
+        if (_cause==COMPLETED)
+            return _result;
+        if (_cause instanceof CancellationException)
+            throw (CancellationException) new CancellationException().initCause(_cause);
+        throw new ExecutionException(_cause);
+    }
+
+    @Override
+    public C get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException
+    {
+        if (!_latch.await(timeout,unit))
+            throw new TimeoutException();
+
+        if (_cause==COMPLETED)
+            return _result;
+        if (_cause instanceof TimeoutException)
+            throw (TimeoutException)_cause;
+        if (_cause instanceof CancellationException)
+            throw (CancellationException) new CancellationException().initCause(_cause);
+        throw new ExecutionException(_cause);
+    }
+
+    public static void rethrow(ExecutionException e) throws IOException
+    {
+        Throwable cause=e.getCause();
+        if (cause instanceof IOException)
+            throw (IOException)cause;
+        if (cause instanceof Error)
+            throw (Error)cause;
+        if (cause instanceof RuntimeException)
+            throw (RuntimeException)cause;
+        throw new RuntimeException(cause);
+    }
+    
+    @Override
+    public String toString()
+    {
+        return String.format("FutureCallback@%x{%b,%b,%s}",hashCode(),_done.get(),_cause==COMPLETED,_result);
+    }
+    
+}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/HttpCookieStore.java b/jetty-util/src/main/java/org/eclipse/jetty/util/HttpCookieStore.java
new file mode 100644
index 0000000..7d57b75
--- /dev/null
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/HttpCookieStore.java
@@ -0,0 +1,114 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.net.CookieManager;
+import java.net.CookieStore;
+import java.net.HttpCookie;
+import java.net.URI;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Implementation of {@link CookieStore} that delegates to an instance created by {@link CookieManager}
+ * via {@link CookieManager#getCookieStore()}.
+ */
+public class HttpCookieStore implements CookieStore
+{
+    private final CookieStore delegate;
+
+    public HttpCookieStore()
+    {
+        delegate = new CookieManager().getCookieStore();
+    }
+
+    @Override
+    public void add(URI uri, HttpCookie cookie)
+    {
+        delegate.add(uri, cookie);
+    }
+
+    @Override
+    public List<HttpCookie> get(URI uri)
+    {
+        return delegate.get(uri);
+    }
+
+    @Override
+    public List<HttpCookie> getCookies()
+    {
+        return delegate.getCookies();
+    }
+
+    @Override
+    public List<URI> getURIs()
+    {
+        return delegate.getURIs();
+    }
+
+    @Override
+    public boolean remove(URI uri, HttpCookie cookie)
+    {
+        return delegate.remove(uri, cookie);
+    }
+
+    @Override
+    public boolean removeAll()
+    {
+        return delegate.removeAll();
+    }
+
+    public static class Empty implements CookieStore
+    {
+        @Override
+        public void add(URI uri, HttpCookie cookie)
+        {
+        }
+
+        @Override
+        public List<HttpCookie> get(URI uri)
+        {
+            return Collections.emptyList();
+        }
+
+        @Override
+        public List<HttpCookie> getCookies()
+        {
+            return Collections.emptyList();
+        }
+
+        @Override
+        public List<URI> getURIs()
+        {
+            return Collections.emptyList();
+        }
+
+        @Override
+        public boolean remove(URI uri, HttpCookie cookie)
+        {
+            return false;
+        }
+
+        @Override
+        public boolean removeAll()
+        {
+            return false;
+        }
+    }
+}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/IO.java b/jetty-util/src/main/java/org/eclipse/jetty/util/IO.java
index f797177..ead3793 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/IO.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/IO.java
@@ -18,7 +18,6 @@
 
 package org.eclipse.jetty.util;
 import java.io.ByteArrayOutputStream;
-import java.io.Closeable;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
@@ -30,10 +29,13 @@ import java.io.PrintWriter;
 import java.io.Reader;
 import java.io.StringWriter;
 import java.io.Writer;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.channels.GatheringByteChannel;
+import java.nio.charset.Charset;
 
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.util.thread.QueuedThreadPool;
 
 /* ======================================================================== */
 /** IO Utilities.
@@ -53,18 +55,7 @@ public class IO
         CRLF_BYTES    = {(byte)'\015',(byte)'\012'};
 
     /* ------------------------------------------------------------------- */
-    public static int bufferSize = 64*1024;
-    
-    /* ------------------------------------------------------------------- */
-    // TODO get rid of this singleton!
-    private static class Singleton {
-        static final QueuedThreadPool __pool=new QueuedThreadPool();
-        static
-        {
-            try{__pool.start();}
-            catch(Exception e){LOG.warn(e); System.exit(1);}
-        }
-    }
+    public static final int bufferSize = 64*1024;
 
     /* ------------------------------------------------------------------- */
     static class Job implements Runnable
@@ -120,23 +111,6 @@ public class IO
     
     /* ------------------------------------------------------------------- */
     /** Copy Stream in to Stream out until EOF or exception.
-     * in own thread
-     */
-    public static void copyThread(InputStream in, OutputStream out)
-    {
-        try{
-            Job job=new Job(in,out);
-            if (!Singleton.__pool.dispatch(job))
-                job.run();
-        }
-        catch(Exception e)
-        {
-            LOG.warn(e);
-        }
-    }
-    
-    /* ------------------------------------------------------------------- */
-    /** Copy Stream in to Stream out until EOF or exception.
      */
     public static void copy(InputStream in, OutputStream out)
          throws IOException
@@ -145,24 +119,6 @@ public class IO
     }
     
     /* ------------------------------------------------------------------- */
-    /** Copy Stream in to Stream out until EOF or exception
-     * in own thread
-     */
-    public static void copyThread(Reader in, Writer out)
-    {
-        try
-        {
-            Job job=new Job(in,out);
-            if (!Singleton.__pool.dispatch(job))
-                job.run();
-        }
-        catch(Exception e)
-        {
-            LOG.warn(e);
-        }
-    }
-    
-    /* ------------------------------------------------------------------- */
     /** Copy Reader to Writer out until EOF or exception.
      */
     public static void copy(Reader in, Writer out)
@@ -299,11 +255,11 @@ public class IO
     /* ------------------------------------------------------------ */
     public static void copyFile(File from,File to) throws IOException
     {
-        FileInputStream in=new FileInputStream(from);
-        FileOutputStream out=new FileOutputStream(to);
-        copy(in,out);
-        in.close();
-        out.close();
+        try (InputStream in=new FileInputStream(from);
+                OutputStream out=new FileOutputStream(to))
+        {
+            copy(in,out);
+        }
     }
     
     /* ------------------------------------------------------------ */
@@ -312,7 +268,7 @@ public class IO
     public static String toString(InputStream in)
         throws IOException
     {
-        return toString(in,null);
+        return toString(in,(Charset)null);
     }
     
     /* ------------------------------------------------------------ */
@@ -321,13 +277,21 @@ public class IO
     public static String toString(InputStream in,String encoding)
         throws IOException
     {
+        return toString(in, encoding==null?null:Charset.forName(encoding));
+    }
+
+    /** Read input stream to string.
+     */
+    public static String toString(InputStream in, Charset encoding)
+            throws IOException
+    {
         StringWriter writer=new StringWriter();
         InputStreamReader reader = encoding==null?new InputStreamReader(in):new InputStreamReader(in,encoding);
-        
+
         copy(reader,writer);
         return writer.toString();
     }
-    
+
     /* ------------------------------------------------------------ */
     /** Read input stream to string.
      */
@@ -360,24 +324,6 @@ public class IO
 
     /* ------------------------------------------------------------ */
     /**
-     * closes any {@link Closeable}
-     *
-     * @param c the closeable to close
-     */
-    public static void close(Closeable c)
-    {
-        try
-        {
-            if (c != null)
-                c.close();
-        }
-        catch (IOException e)
-        {
-            LOG.ignore(e);
-        }
-    }
-    
-    /**
      * closes an input stream, and logs exceptions
      *
      * @param is the input stream to close
@@ -423,7 +369,8 @@ public class IO
         {
             if (writer != null)
                 writer.close();
-        } catch (IOException e)
+        } 
+        catch (IOException e)
         {
             LOG.ignore(e);
         }
@@ -437,6 +384,52 @@ public class IO
         copy(in,bout);
         return bout.toByteArray();
     }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * A gathering write utility wrapper.
+     * <p>This method wraps a gather write with a loop that handles the limitations of some operating systems that
+     * have a limit on the number of buffers written.  The method loops on the write until either all the content
+     * is written or no progress is made.
+     * @param out The GatheringgByteChannel to write to
+     * @param buffers The buffers to write
+     * @param offset The offset into the buffers array
+     * @param length The length in buffers to write
+     * @return The total bytes written
+     * @throws IOException
+     */
+    public static long write(GatheringByteChannel out, ByteBuffer[] buffers, int offset, int length) throws IOException
+    {
+        long total=0;
+        write: while (length>0)
+        {
+            // Write as much as we can
+            long wrote=out.write(buffers,offset,length);
+            
+            // If we can't write any more, give up
+            if (wrote==0)
+                break;
+            
+            // count the total
+            total+=wrote;
+            
+            // Look for unwritten content
+            for (int i=offset;i<buffers.length;i++)
+            {
+                if (buffers[i].hasRemaining())
+                {
+                    // loop with new offset and length;
+                    length=length-(i-offset);
+                    offset=i;
+                    continue write;
+                }
+            }
+            length=0;
+        }
+        
+        return total;
+    }
+    
     
     /* ------------------------------------------------------------ */
     /**
@@ -544,6 +537,7 @@ public class IO
     }
     private static NullWrite __nullWriter = new NullWrite();
     private static PrintWriter __nullPrintWriter = new PrintWriter(__nullWriter);
+
 }
 
 
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/IteratingCallback.java b/jetty-util/src/main/java/org/eclipse/jetty/util/IteratingCallback.java
new file mode 100644
index 0000000..365dc50
--- /dev/null
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/IteratingCallback.java
@@ -0,0 +1,543 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.nio.channels.ClosedChannelException;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * This specialized callback implements a pattern that allows
+ * a large job to be broken into smaller tasks using iteration
+ * rather than recursion.
+ * <p/>
+ * A typical example is the write of a large content to a socket,
+ * divided in chunks. Chunk C1 is written by thread T1, which
+ * also invokes the callback, which writes chunk C2, which invokes
+ * the callback again, which writes chunk C3, and so forth.
+ * <p/>
+ * The problem with the example is that if the callback thread
+ * is the same that performs the I/O operation, then the process
+ * is recursive and may result in a stack overflow.
+ * To avoid the stack overflow, a thread dispatch must be performed,
+ * causing context switching and cache misses, affecting performance.
+ * <p/>
+ * To avoid this issue, this callback uses an AtomicReference to
+ * record whether success callback has been called during the processing
+ * of a sub task, and if so then the processing iterates rather than
+ * recurring.
+ * <p/>
+ * Subclasses must implement method {@link #process()} where the sub
+ * task is executed and a suitable {@link IteratingCallback.Action} is
+ * returned to this callback to indicate the overall progress of the job.
+ * This callback is passed to the asynchronous execution of each sub
+ * task and a call the {@link #succeeded()} on this callback represents
+ * the completion of the sub task.
+ */
+public abstract class IteratingCallback implements Callback
+{
+    /**
+     * The internal states of this callback
+     */
+    private enum State
+    {
+        /**
+         * This callback is IDLE, ready to iterate.
+         */
+        IDLE,
+
+        /**
+         * This callback is iterating calls to {@link #process()} and is dealing with
+         * the returns.  To get into processing state, it much of held the lock state
+         * and set iterating to true.
+         */
+        PROCESSING,
+        
+        /**
+         * Waiting for a schedule callback
+         */
+        PENDING,
+        
+        /**
+         * Called by a schedule callback
+         */
+        CALLED,
+        
+        /**
+         * The overall job has succeeded as indicated by a {@link Action#SUCCEEDED} return 
+         * from {@link IteratingCallback#process()}
+         */
+        SUCCEEDED,
+        
+        /**
+         * The overall job has failed as indicated by a call to {@link IteratingCallback#failed(Throwable)}
+         */
+        FAILED,
+        
+        /**
+         * This callback has been closed and cannot be reset.
+         */ 
+        CLOSED,
+        
+        /**
+         * State is locked while leaving processing state to check the iterate boolean
+         */
+        LOCKED
+    }
+
+    /**
+     * The indication of the overall progress of the overall job that
+     * implementations of {@link #process()} must return.
+     */
+    protected enum Action
+    {
+        /**
+         * Indicates that {@link #process()} has no more work to do,
+         * but the overall job is not completed yet, probably waiting
+         * for additional events to trigger more work.
+         */
+        IDLE,
+        /**
+         * Indicates that {@link #process()} is executing asynchronously
+         * a sub task, where the execution has started but the callback
+         * may have not yet been invoked.
+         */
+        SCHEDULED,
+        
+        /**
+         * Indicates that {@link #process()} has completed the overall job.
+         */
+        SUCCEEDED
+    }
+
+    private final AtomicReference<State> _state;
+    private boolean _iterate;
+    
+    
+    protected IteratingCallback()
+    {
+        _state = new AtomicReference<>(State.IDLE);
+    }
+    
+    protected IteratingCallback(boolean needReset)
+    {
+        _state = new AtomicReference<>(needReset ? State.SUCCEEDED : State.IDLE);
+    }
+    
+    /**
+     * Method called by {@link #iterate()} to process the sub task.
+     * <p/>
+     * Implementations must start the asynchronous execution of the sub task
+     * (if any) and return an appropriate action:
+     * <ul>
+     * <li>{@link Action#IDLE} when no sub tasks are available for execution
+     * but the overall job is not completed yet</li>
+     * <li>{@link Action#SCHEDULED} when the sub task asynchronous execution
+     * has been started</li>
+     * <li>{@link Action#SUCCEEDED} when the overall job is completed</li>
+     * </ul>
+     *
+     * @throws Exception if the sub task processing throws
+     */
+    protected abstract Action process() throws Exception;
+
+    /**
+     * @deprecated Use {@link #onCompleteSuccess()} instead.
+     */
+    @Deprecated
+    protected void completed()
+    {
+    }
+
+    /**
+     * Invoked when the overall task has completed successfully.
+     *
+     * @see #onCompleteFailure(Throwable)
+     */
+    protected void onCompleteSuccess()
+    {
+        completed();
+    }
+    
+    /**
+     * Invoked when the overall task has completed with a failure.
+     *
+     * @see #onCompleteSuccess()
+     */
+    protected void onCompleteFailure(Throwable x)
+    {
+    }
+
+    /**
+     * This method must be invoked by applications to start the processing
+     * of sub tasks.  It can be called at any time by any thread, and it's 
+     * contract is that when called, then the {@link #process()} method will
+     * be called during or soon after, either by the calling thread or by 
+     * another thread.
+     */
+    public void iterate()
+    {
+        loop: while (true)
+        {
+            State state=_state.get();
+            switch (state)
+            {
+                case PENDING:
+                case CALLED:
+                    // process will be called when callback is handled
+                    break loop;
+                    
+                case IDLE:
+                    if (!_state.compareAndSet(state,State.PROCESSING))
+                        continue;
+                    processing();
+                    break loop;
+                    
+                case PROCESSING:
+                    if (!_state.compareAndSet(state,State.LOCKED))
+                        continue;
+                    // Tell the thread that is processing that it must iterate again
+                    _iterate=true;
+                    _state.set(State.PROCESSING);
+                    break loop;
+                    
+                case LOCKED:
+                    Thread.yield();
+                    continue loop;
+
+                case FAILED:
+                case SUCCEEDED:
+                    break loop;
+
+                case CLOSED:
+                default:
+                    throw new IllegalStateException("state="+state);
+            }
+        }
+    }
+
+    private void processing() 
+    {
+        // This should only ever be called when in processing state, however a failed or close call
+        // may happen concurrently, so state is not assumed.
+        
+        // While we are processing
+        processing: while (true)
+        {
+            // Call process to get the action that we have to take.
+            Action action;
+            try
+            {
+                action = process();
+            }
+            catch (Throwable x)
+            {
+                failed(x);
+                break processing;
+            }
+
+            // loop until we have successfully acted on the action we have just received
+            acting: while(true)
+            {
+                // action handling needs to know the state
+                State state=_state.get();
+                
+                switch (state)
+                {
+                    case PROCESSING:
+                    {
+                        switch (action)
+                        {
+                            case IDLE:
+                            {
+                                // lock the state
+                                if (!_state.compareAndSet(state,State.LOCKED))
+                                    continue acting;
+
+                                // Has iterate been called while we were processing?
+                                if (_iterate)
+                                {
+                                    // yes, so skip idle and keep processing
+                                    _iterate=false;
+                                    _state.set(State.PROCESSING);
+                                    continue processing;
+                                }
+
+                                // No, so we can go idle
+                                _state.set(State.IDLE);
+                                break processing;
+                            }
+                            
+                            case SCHEDULED:
+                            {
+                                if (!_state.compareAndSet(state, State.PENDING))
+                                    continue acting;
+                                // we won the race against the callback, so the callback has to process and we can break processing
+                                break processing;
+                            }
+                            
+                            case SUCCEEDED:
+                            {
+                                if (!_state.compareAndSet(state, State.LOCKED))
+                                    continue acting;
+                                _iterate=false;
+                                _state.set(State.SUCCEEDED);
+                                onCompleteSuccess();
+                                break processing;
+                            }
+
+                            default:
+                                throw new IllegalStateException("state="+state+" action="+action); 
+                        }
+                    }
+                    
+                    case CALLED:
+                    {
+                        switch (action)
+                        {
+                            case SCHEDULED:
+                            {
+                                if (!_state.compareAndSet(state, State.PROCESSING))
+                                    continue acting;
+                                // we lost the race, so we have to keep processing
+                                continue processing;
+                            }
+
+                            default:
+                                throw new IllegalStateException("state="+state+" action="+action); 
+                        }
+                    }
+                        
+                    case LOCKED:
+                        Thread.yield();
+                        continue acting;
+
+                    case SUCCEEDED:
+                    case FAILED:
+                    case CLOSED:
+                        break processing;
+
+                    case IDLE:
+                    case PENDING:
+                    default:
+                        throw new IllegalStateException("state="+state+" action="+action); 
+                }
+            }
+        }
+    }
+    
+    /**
+     * Invoked when the sub task succeeds.
+     * Subclasses that override this method must always remember to call
+     * {@code super.succeeded()}.
+     */
+    @Override
+    public void succeeded()
+    {
+        loop: while (true)
+        {
+            State state = _state.get();
+            switch (state)
+            {
+                case PROCESSING:
+                {
+                    if (!_state.compareAndSet(state, State.CALLED))
+                        continue loop;
+                    break loop;
+                }
+                case PENDING:
+                {
+                    if (!_state.compareAndSet(state, State.PROCESSING))
+                        continue loop;
+                    processing();
+                    break loop;
+                }
+                case CLOSED:
+                case FAILED:
+                {
+                    // Too late!
+                    break loop;
+                }
+                case LOCKED:
+                {
+                    Thread.yield();
+                    continue loop;
+                }       
+                default:
+                {
+                    throw new IllegalStateException("state="+state);
+                }
+            }
+        }
+    }
+
+    /**
+     * Invoked when the sub task fails.
+     * Subclasses that override this method must always remember to call
+     * {@code super.failed(Throwable)}.
+     */
+    @Override
+    public void failed(Throwable x)
+    {
+        loop: while (true)
+        {
+            State state = _state.get();
+            switch (state)
+            {
+                case SUCCEEDED:
+                case FAILED:
+                case IDLE:
+                case CLOSED:
+                case CALLED:
+                {
+                    // too late!.
+                    break loop;
+                }
+                case LOCKED:
+                {
+                    Thread.yield();
+                    continue loop;
+                }  
+                case PENDING: 
+                case PROCESSING: 
+                {
+                    if (!_state.compareAndSet(state, State.FAILED))
+                        continue loop;
+
+                    onCompleteFailure(x);
+                    break loop;
+                }
+                default:
+                    throw new IllegalStateException("state="+state);
+            }
+        }
+    }
+
+    public void close()
+    {
+        loop: while (true)
+        {
+            State state = _state.get();
+            switch (state)
+            {
+                case IDLE:
+                case SUCCEEDED:
+                case FAILED:
+                {
+                    if (!_state.compareAndSet(state, State.CLOSED))
+                        continue loop;
+                    break loop;
+                }
+                case CLOSED:
+                {
+                    break loop;
+                }
+                case LOCKED:
+                {
+                    Thread.yield();
+                    continue loop;
+                }    
+                default:
+                {
+                    if (!_state.compareAndSet(state, State.CLOSED))
+                        continue loop;
+                    onCompleteFailure(new ClosedChannelException());
+                    break loop;
+                }
+            }
+        }
+    }
+
+    /*
+     * only for testing
+     * @return whether this callback is idle and {@link #iterate()} needs to be called
+     */
+    boolean isIdle()
+    {
+        return _state.get() == State.IDLE;
+    }
+
+    public boolean isClosed()
+    {
+        return _state.get() == State.CLOSED;
+    }
+    
+    /**
+     * @return whether this callback has failed
+     */
+    public boolean isFailed()
+    {
+        return _state.get() == State.FAILED;
+    }
+
+    /**
+     * @return whether this callback has succeeded
+     */
+    public boolean isSucceeded()
+    {
+        return _state.get() == State.SUCCEEDED;
+    }
+
+    /**
+     * Resets this callback.
+     * <p/>
+     * A callback can only be reset to IDLE from the
+     * SUCCEEDED or FAILED states or if it is already IDLE.
+     *
+     * @return true if the reset was successful
+     */
+    public boolean reset()
+    {
+        while (true)
+        {
+            State state=_state.get();
+            switch(state)
+            {
+                case IDLE:
+                    return true;
+                    
+                case SUCCEEDED:
+                    if (!_state.compareAndSet(state, State.LOCKED))
+                        continue;
+                    _iterate=false;
+                    _state.set(State.IDLE);
+                    return true;
+                    
+                case FAILED:
+                    if (!_state.compareAndSet(state, State.LOCKED))
+                        continue;
+                    _iterate=false;
+                    _state.set(State.IDLE);
+                    return true;
+
+                case LOCKED:
+                    Thread.yield();
+                    continue;
+                    
+                default:
+                    return false;
+            }
+        }
+    }
+    
+    @Override
+    public String toString()
+    {
+        return String.format("%s[%s]", super.toString(), _state);
+    }
+}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/IteratingNestedCallback.java b/jetty-util/src/main/java/org/eclipse/jetty/util/IteratingNestedCallback.java
new file mode 100644
index 0000000..ac6c99a
--- /dev/null
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/IteratingNestedCallback.java
@@ -0,0 +1,67 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+
+/* ------------------------------------------------------------ */
+/** Iterating Nested Callback.
+ * <p>This specialized callback is used when breaking up an
+ * asynchronous task into smaller asynchronous tasks.  A typical pattern
+ * is that a successful callback is used to schedule the next sub task, but 
+ * if that task completes quickly and uses the calling thread to callback
+ * the success notification, this can result in a growing stack depth.
+ * </p>
+ * <p>To avoid this issue, this callback uses an AtomicBoolean to note 
+ * if the success callback has been called during the processing of a 
+ * sub task, and if so then the processing iterates rather than recurses.
+ * </p>
+ * <p>This callback is passed to the asynchronous handling of each sub
+ * task and a call the {@link #succeeded()} on this call back represents
+ * completion of the subtask.  Only once all the subtasks are completed is 
+ * the {@link Callback#succeeded()} method called on the {@link Callback} instance
+ * passed the the {@link #IteratingNestedCallback(Callback)} constructor.</p>
+ *  
+ */
+public abstract class IteratingNestedCallback extends IteratingCallback
+{
+    final Callback _callback;
+    
+    public IteratingNestedCallback(Callback callback)
+    {
+        _callback=callback;
+    }
+    
+    @Override
+    protected void onCompleteSuccess()
+    {
+        _callback.succeeded();
+    }
+    
+    @Override
+    protected void onCompleteFailure(Throwable x)
+    {
+        _callback.failed(x);
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s@%x",getClass().getSimpleName(),hashCode());
+    }
+}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/Jetty.java b/jetty-util/src/main/java/org/eclipse/jetty/util/Jetty.java
new file mode 100644
index 0000000..6a0aa53
--- /dev/null
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/Jetty.java
@@ -0,0 +1,39 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+public class Jetty
+{
+    public static final String VERSION;
+
+    static
+    {
+        Package pkg = Jetty.class.getPackage();
+        if (pkg != null &&
+                "Eclipse.org - Jetty".equals(pkg.getImplementationVendor()) &&
+                pkg.getImplementationVersion() != null)
+            VERSION = pkg.getImplementationVersion();
+        else
+            VERSION = System.getProperty("jetty.version", "9.2.z-SNAPSHOT");
+    }
+
+    private Jetty()
+    {
+    }
+}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/LazyList.java b/jetty-util/src/main/java/org/eclipse/jetty/util/LazyList.java
index 1391d35..2013a68 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/LazyList.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/LazyList.java
@@ -21,7 +21,6 @@ package org.eclipse.jetty.util;
 import java.io.Serializable;
 import java.lang.reflect.Array;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Iterator;
@@ -58,6 +57,7 @@ import java.util.ListIterator;
  *
  * @see java.util.List
  */
+ at SuppressWarnings("serial")
 public class LazyList
     implements Cloneable, Serializable
 {
@@ -260,6 +260,38 @@ public class LazyList
         
         return (List<E>)Collections.singletonList(list);
     }
+    
+    /**
+     * Simple utility method to test if List has at least 1 entry.
+     * 
+     * @param list
+     *            a LazyList, {@link List} or {@link Object}
+     * @return true if not-null and is not empty
+     */
+    public static boolean hasEntry(Object list)
+    {
+        if (list == null)
+            return false;
+        if (list instanceof List)
+            return !((List<?>)list).isEmpty();
+        return true;
+    }
+    
+    /**
+     * Simple utility method to test if List is empty
+     * 
+     * @param list
+     *            a LazyList, {@link List} or {@link Object}
+     * @return true if null or is empty
+     */
+    public static boolean isEmpty(Object list)
+    {
+        if (list == null)
+            return true;
+        if (list instanceof List)
+            return ((List<?>)list).isEmpty();
+        return false;
+    }
 
     
     /* ------------------------------------------------------------ */
@@ -414,70 +446,6 @@ public class LazyList
         List<E> l=getList(list);
         return l.listIterator();
     }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param array Any array of object
-     * @return A new <i>modifiable</i> list initialised with the elements from <code>array</code>.
-     */
-    public static<E> List<E> array2List(E[] array)
-    {	
-        if (array==null || array.length==0)
-            return new ArrayList<E>();
-        return new ArrayList<E>(Arrays.asList(array));
-    }
-
-    /* ------------------------------------------------------------ */
-    /** Add element to an array
-     * @param array The array to add to (or null)
-     * @param item The item to add
-     * @param type The type of the array (in case of null array)
-     * @return new array with contents of array plus item
-     */
-    public static<T> T[] addToArray(T[] array, T item, Class<?> type)
-    {
-        if (array==null)
-        {
-            if (type==null && item!=null)
-                type= item.getClass();
-            @SuppressWarnings("unchecked")
-            T[] na = (T[])Array.newInstance(type, 1);
-            na[0]=item;
-            return na;
-        }
-        else
-        {
-            // TODO: Replace with Arrays.copyOf(T[] original, int newLength) from Java 1.6+
-            Class<?> c = array.getClass().getComponentType();
-            @SuppressWarnings("unchecked")
-            T[] na = (T[])Array.newInstance(c, Array.getLength(array)+1);
-            System.arraycopy(array, 0, na, 0, array.length);
-            na[array.length]=item;
-            return na;
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    public static<T> T[] removeFromArray(T[] array, Object item)
-    {
-        if (item==null || array==null)
-            return array;
-        for (int i=array.length;i-->0;)
-        {
-            if (item.equals(array[i]))
-            {
-                Class<?> c = array==null?item.getClass():array.getClass().getComponentType();
-                @SuppressWarnings("unchecked")
-                T[] na = (T[])Array.newInstance(c, Array.getLength(array)-1);
-                if (i>0)
-                    System.arraycopy(array, 0, na, 0, i);
-                if (i+1<array.length)
-                    System.arraycopy(array, i+1, na, i, array.length-(i+1));
-                return na;
-            }
-        }
-        return array;
-    }
     
 }
 
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/LeakDetector.java b/jetty-util/src/main/java/org/eclipse/jetty/util/LeakDetector.java
new file mode 100644
index 0000000..e1c7ec0
--- /dev/null
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/LeakDetector.java
@@ -0,0 +1,208 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.lang.ref.PhantomReference;
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.WeakReference;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * A facility to detect improper usage of resource pools.
+ * <p>
+ * Resource pools usually have a method to acquire a pooled resource and a method to released it back to the pool.
+ * <p>
+ * To detect if client code acquires a resource but never releases it, the resource pool can be modified to use a
+ * {@link LeakDetector}. The modified resource pool should call {@link #acquired(Object)} every time the method to
+ * acquire a resource is called, and {@link #released(Object)} every time the method to release the resource is called.
+ * {@link LeakDetector} keeps track of these resources and invokes method
+ * {@link #leaked(org.eclipse.jetty.util.LeakDetector.LeakInfo)} when it detects that a resource has been leaked (that
+ * is, acquired but never released).
+ * <p>
+ * To detect whether client code releases a resource without having acquired it, the resource pool can be modified to
+ * check the return value of {@link #released(Object)}: if false, it means that the resource was not acquired.
+ * <p>
+ * IMPLEMENTATION NOTES
+ * <p>
+ * This class relies on {@link System#identityHashCode(Object)} to create a unique id for each resource passed to
+ * {@link #acquired(Object)} and {@link #released(Object)}. {@link System#identityHashCode(Object)} does not guarantee
+ * that it will not generate the same number for different objects, but in practice the chance of collision is rare.
+ * <p>
+ * {@link LeakDetector} uses {@link PhantomReference}s to detect leaks. {@link PhantomReference}s are enqueued in their
+ * {@link ReferenceQueue} <em>after</em> they have been garbage collected (differently from {@link WeakReference}s that
+ * are enqueued <em>before</em>). Since the resource is now garbage collected, {@link LeakDetector} checks whether it
+ * has been released and if not, it reports a leak. Using {@link PhantomReference}s is better than overriding
+ * {@link #finalize()} and works also in those cases where {@link #finalize()} is not overridable.
+ *
+ * @param <T> the resource type.
+ */
+public class LeakDetector<T> extends AbstractLifeCycle implements Runnable
+{
+    private static final Logger LOG = Log.getLogger(LeakDetector.class);
+
+    private final ReferenceQueue<T> queue = new ReferenceQueue<>();
+    private final ConcurrentMap<String, LeakInfo> resources = new ConcurrentHashMap<>();
+    private Thread thread;
+
+    /**
+     * Tracks the resource as been acquired.
+     *
+     * @param resource the resource that has been acquired
+     * @return true whether the resource has been acquired normally, false if the resource has detected a leak (meaning
+     *         that another acquire occurred before a release of the same resource)
+     * @see #released(Object)
+     */
+    public boolean acquired(T resource)
+    {
+        String id = id(resource);
+        LeakInfo info = resources.putIfAbsent(id, new LeakInfo(resource,id));
+        if (info != null)
+        {
+            // Leak detected, prior acquire exists (not released) or id clash.
+            return false;
+        }
+        // Normal behavior.
+        return true;
+    }
+
+    /**
+     * Tracks the resource as been released.
+     *
+     * @param resource the resource that has been released
+     * @return true whether the resource has been released normally (based on a previous acquire). false if the resource
+     *         has been released without a prior acquire (such as a double release scenario)
+     * @see #acquired(Object)
+     */
+    public boolean released(T resource)
+    {
+        String id = id(resource);
+        LeakInfo info = resources.remove(id);
+        if (info != null)
+        {
+            // Normal behavior.
+            return true;
+        }
+
+        // Leak detected (released without acquire).
+        return false;
+    }
+
+    /**
+     * Generates a unique ID for the given resource.
+     *
+     * @param resource the resource to generate the unique ID for
+     * @return the unique ID of the given resource
+     */
+    public String id(T resource)
+    {
+        return String.valueOf(System.identityHashCode(resource));
+    }
+
+    @Override
+    protected void doStart() throws Exception
+    {
+        super.doStart();
+        thread = new Thread(this,getClass().getSimpleName());
+        thread.setDaemon(true);
+        thread.start();
+    }
+
+    @Override
+    protected void doStop() throws Exception
+    {
+        super.doStop();
+        thread.interrupt();
+    }
+
+    @Override
+    public void run()
+    {
+        try
+        {
+            while (isRunning())
+            {
+                @SuppressWarnings("unchecked")
+                LeakInfo leakInfo = (LeakInfo)queue.remove();
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Resource GC'ed: {}",leakInfo);
+                if (resources.remove(leakInfo.id) != null)
+                    leaked(leakInfo);
+            }
+        }
+        catch (InterruptedException x)
+        {
+            // Exit
+        }
+    }
+
+    /**
+     * Callback method invoked by {@link LeakDetector} when it detects that a resource has been leaked.
+     *
+     * @param leakInfo the information about the leak
+     */
+    protected void leaked(LeakInfo leakInfo)
+    {
+        LOG.warn("Resource leaked: " + leakInfo.description,leakInfo.stackFrames);
+    }
+
+    /**
+     * Information about the leak of a resource.
+     */
+    public class LeakInfo extends PhantomReference<T>
+    {
+        private final String id;
+        private final String description;
+        private final Throwable stackFrames;
+
+        private LeakInfo(T referent, String id)
+        {
+            super(referent,queue);
+            this.id = id;
+            this.description = referent.toString();
+            this.stackFrames = new Throwable();
+        }
+
+        /**
+         * @return the resource description as provided by the resource's {@link Object#toString()} method.
+         */
+        public String getResourceDescription()
+        {
+            return description;
+        }
+
+        /**
+         * @return a Throwable instance that contains the stack frames at the time of resource acquisition.
+         */
+        public Throwable getStackFrames()
+        {
+            return stackFrames;
+        }
+
+        @Override
+        public String toString()
+        {
+            return description;
+        }
+    }
+}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/Loader.java b/jetty-util/src/main/java/org/eclipse/jetty/util/Loader.java
index cf2da65..500f9f9 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/Loader.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/Loader.java
@@ -46,79 +46,69 @@ import org.eclipse.jetty.util.resource.Resource;
 public class Loader
 {
     /* ------------------------------------------------------------ */
-    public static URL getResource(Class<?> loadClass,String name, boolean checkParents)
+    public static URL getResource(Class<?> loadClass,String name)
     {
         URL url =null;
-        ClassLoader loader=Thread.currentThread().getContextClassLoader();
-        while (url==null && loader!=null )
-        {
-            url=loader.getResource(name); 
-            loader=(url==null&&checkParents)?loader.getParent():null;
-        }      
+        ClassLoader context_loader=Thread.currentThread().getContextClassLoader();
+        if (context_loader!=null)
+            url=context_loader.getResource(name); 
         
-        loader=loadClass==null?null:loadClass.getClassLoader();
-        while (url==null && loader!=null )
+        if (url==null && loadClass!=null)
         {
-            url=loader.getResource(name); 
-            loader=(url==null&&checkParents)?loader.getParent():null;
-        }       
+            ClassLoader load_loader=loadClass.getClassLoader();
+            if (load_loader!=null && load_loader!=context_loader)
+                url=load_loader.getResource(name);
+        }
 
         if (url==null)
-        {
             url=ClassLoader.getSystemResource(name);
-        }   
 
         return url;
     }
 
     /* ------------------------------------------------------------ */
-    @SuppressWarnings("rawtypes")
-    public static Class loadClass(Class loadClass,String name)
-        throws ClassNotFoundException
-    {
-        return loadClass(loadClass,name,false);
-    }
-    
-    /* ------------------------------------------------------------ */
     /** Load a class.
      * 
      * @param loadClass
      * @param name
-     * @param checkParents If true, try loading directly from parent classloaders.
      * @return Class
      * @throws ClassNotFoundException
      */
     @SuppressWarnings("rawtypes")
-    public static Class loadClass(Class loadClass,String name,boolean checkParents)
+    public static Class loadClass(Class loadClass,String name)
         throws ClassNotFoundException
     {
         ClassNotFoundException ex=null;
         Class<?> c =null;
-        ClassLoader loader=Thread.currentThread().getContextClassLoader();
-        while (c==null && loader!=null )
+        ClassLoader context_loader=Thread.currentThread().getContextClassLoader();
+        if (context_loader!=null )
         {
-            try { c=loader.loadClass(name); }
-            catch (ClassNotFoundException e) {if(ex==null)ex=e;}
-            loader=(c==null&&checkParents)?loader.getParent():null;
-        }      
+            try { c=context_loader.loadClass(name); }
+            catch (ClassNotFoundException e) {ex=e;}
+        }    
         
-        loader=loadClass==null?null:loadClass.getClassLoader();
-        while (c==null && loader!=null )
+        if (c==null && loadClass!=null)
         {
-            try { c=loader.loadClass(name); }
-            catch (ClassNotFoundException e) {if(ex==null)ex=e;}
-            loader=(c==null&&checkParents)?loader.getParent():null;
-        }       
+            ClassLoader load_loader=loadClass.getClassLoader();
+            if (load_loader!=null && load_loader!=context_loader)
+            {
+                try { c=load_loader.loadClass(name); }
+                catch (ClassNotFoundException e) {if(ex==null)ex=e;}
+            }
+        }
 
         if (c==null)
         {
             try { c=Class.forName(name); }
-            catch (ClassNotFoundException e) {if(ex==null)ex=e;}
+            catch (ClassNotFoundException e) 
+            {
+                if(ex!=null)
+                    throw ex;
+                throw e;
+            }
         }   
 
-        if (c!=null)
-            return c;
-        throw ex;
+        return c;
     }
     
     
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/MemoryUtils.java b/jetty-util/src/main/java/org/eclipse/jetty/util/MemoryUtils.java
new file mode 100644
index 0000000..a0e50f4
--- /dev/null
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/MemoryUtils.java
@@ -0,0 +1,71 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+
+/**
+ * {@link MemoryUtils} provides an abstraction over memory properties and operations.
+ * <p />
+ */
+public class MemoryUtils
+{
+    private static final int cacheLineBytes;
+    static
+    {
+        final int defaultValue = 64;
+        int value = defaultValue;
+        try
+        {
+            value = Integer.parseInt(AccessController.doPrivileged(new PrivilegedAction<String>()
+            {
+                @Override
+                public String run()
+                {
+                    return System.getProperty("org.eclipse.jetty.util.cacheLineBytes", String.valueOf(defaultValue));
+                }
+            }));
+        }
+        catch (Exception ignored)
+        {
+        }
+        cacheLineBytes = value;
+    }
+
+    private MemoryUtils()
+    {
+    }
+
+    public static int getCacheLineBytes()
+    {
+        return cacheLineBytes;
+    }
+
+    public static int getIntegersPerCacheLine()
+    {
+        return getCacheLineBytes() >> 2;
+    }
+
+    public static int getLongsPerCacheLine()
+    {
+        return getCacheLineBytes() >> 3;
+    }
+
+}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/MultiException.java b/jetty-util/src/main/java/org/eclipse/jetty/util/MultiException.java
index 4a18a4d..f3e7d39 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/MultiException.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/MultiException.java
@@ -17,22 +17,20 @@
 //
 
 package org.eclipse.jetty.util;
-import java.io.PrintStream;
-import java.io.PrintWriter;
-import java.util.List;
 
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
 
-/* ------------------------------------------------------------ */
-/** Wraps multiple exceptions.
+/** 
+ * Wraps multiple exceptions.
  *
  * Allows multiple exceptions to be thrown as a single exception.
- *
- * 
  */
 @SuppressWarnings("serial")
 public class MultiException extends Exception
 {
-    private Object nested;
+    private List<Throwable> nested;
 
     /* ------------------------------------------------------------ */
     public MultiException()
@@ -43,32 +41,44 @@ public class MultiException extends Exception
     /* ------------------------------------------------------------ */
     public void add(Throwable e)
     {
+        if (e==null)
+            throw new IllegalArgumentException();
+
+        if(nested == null)
+        {
+            initCause(e);
+            nested = new ArrayList<>();
+        }
+        else
+            addSuppressed(e);
+        
         if (e instanceof MultiException)
         {
             MultiException me = (MultiException)e;
-            for (int i=0;i<LazyList.size(me.nested);i++)
-                nested=LazyList.add(nested,LazyList.get(me.nested,i));
+            nested.addAll(me.nested);
         }
         else
-            nested=LazyList.add(nested,e);
+            nested.add(e);
     }
 
     /* ------------------------------------------------------------ */
     public int size()
     {
-        return LazyList.size(nested);
+        return (nested ==null)?0:nested.size();
     }
     
     /* ------------------------------------------------------------ */
     public List<Throwable> getThrowables()
     {
-        return LazyList.getList(nested);
+        if(nested == null)
+            return Collections.emptyList();
+        return nested;
     }
     
     /* ------------------------------------------------------------ */
     public Throwable getThrowable(int i)
     {
-        return (Throwable) LazyList.get(nested,i);
+        return nested.get(i);
     }
 
     /* ------------------------------------------------------------ */
@@ -81,12 +91,15 @@ public class MultiException extends Exception
     public void ifExceptionThrow()
         throws Exception
     {
-        switch (LazyList.size(nested))
+        if(nested == null)
+            return;
+        
+        switch (nested.size())
         {
           case 0:
               break;
           case 1:
-              Throwable th=(Throwable)LazyList.get(nested,0);
+              Throwable th=nested.get(0);
               if (th instanceof Error)
                   throw (Error)th;
               if (th instanceof Exception)
@@ -108,12 +121,15 @@ public class MultiException extends Exception
     public void ifExceptionThrowRuntime()
         throws Error
     {
-        switch (LazyList.size(nested))
+        if(nested == null)
+            return;
+        
+        switch (nested.size())
         {
           case 0:
               break;
           case 1:
-              Throwable th=(Throwable)LazyList.get(nested,0);
+              Throwable th=nested.get(0);
               if (th instanceof Error)
                   throw (Error)th;
               else if (th instanceof RuntimeException)
@@ -134,7 +150,10 @@ public class MultiException extends Exception
     public void ifExceptionThrowMulti()
         throws MultiException
     {
-        if (LazyList.size(nested)>0)
+        if(nested == null)
+            return;
+        
+        if (nested.size()>0)
             throw this;
     }
 
@@ -142,44 +161,14 @@ public class MultiException extends Exception
     @Override
     public String toString()
     {
-        if (LazyList.size(nested)>0)
-            return MultiException.class.getSimpleName()+
-                LazyList.getList(nested);
-        return MultiException.class.getSimpleName()+"[]";
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public void printStackTrace()
-    {
-        super.printStackTrace();
-        for (int i=0;i<LazyList.size(nested);i++)
-            ((Throwable)LazyList.get(nested,i)).printStackTrace();
-    }
-   
-
-    /* ------------------------------------------------------------------------------- */
-    /**
-     * @see java.lang.Throwable#printStackTrace(java.io.PrintStream)
-     */
-    @Override
-    public void printStackTrace(PrintStream out)
-    {
-        super.printStackTrace(out);
-        for (int i=0;i<LazyList.size(nested);i++)
-            ((Throwable)LazyList.get(nested,i)).printStackTrace(out);
-    }
-
-    /* ------------------------------------------------------------------------------- */
-    /**
-     * @see java.lang.Throwable#printStackTrace(java.io.PrintWriter)
-     */
-    @Override
-    public void printStackTrace(PrintWriter out)
-    {
-        super.printStackTrace(out);
-        for (int i=0;i<LazyList.size(nested);i++)
-            ((Throwable)LazyList.get(nested,i)).printStackTrace(out);
+        StringBuilder str = new StringBuilder();
+        str.append(MultiException.class.getSimpleName());
+        if((nested == null) || (nested.size()<=0)) {
+            str.append("[]");
+        } else {
+            str.append(nested);
+        }
+        return str.toString();
     }
 
 }
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/MultiMap.java b/jetty-util/src/main/java/org/eclipse/jetty/util/MultiMap.java
index fb34c8e..286889b 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/MultiMap.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/MultiMap.java
@@ -18,67 +18,35 @@
 
 package org.eclipse.jetty.util;
 
-import java.io.Serializable;
+import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collection;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
+import java.util.Map.Entry;
 
-/* ------------------------------------------------------------ */
-/** A multi valued Map.
- * This Map specializes HashMap and provides methods
- * that operate on multi valued items. 
- * <P>
- * Implemented as a map of LazyList values
- * @param <K> The key type of the map.
- *
- * @see LazyList
- * 
+/** 
+ * A multi valued Map.
  */
-public class MultiMap<K> implements ConcurrentMap<K,Object>, Serializable
+ at SuppressWarnings("serial")
+public class MultiMap<V> extends HashMap<String,List<V>>
 {
-    private static final long serialVersionUID = -6878723138353851005L;
-    Map<K,Object> _map;
-    ConcurrentMap<K, Object> _cmap;
-
     public MultiMap()
     {
-        _map=new HashMap<K, Object>();
+        super();
     }
-    
-    public MultiMap(Map<K,Object> map)
-    {
-        if (map instanceof ConcurrentMap)
-            _map=_cmap=new ConcurrentHashMap<K, Object>(map);
-        else
-            _map=new HashMap<K, Object>(map);
-    }
-    
-    public MultiMap(MultiMap<K> map)
-    {
-        if (map._cmap!=null)
-            _map=_cmap=new ConcurrentHashMap<K, Object>(map._cmap);
-        else
-            _map=new HashMap<K,Object>(map._map);
-    }
-    
-    public MultiMap(int capacity)
+
+    public MultiMap(Map<String,List<V>> map)
     {
-        _map=new HashMap<K, Object>(capacity);
+        super(map);
     }
-    
-    public MultiMap(boolean concurrent)
+
+    public MultiMap(MultiMap<V> map)
     {
-        if (concurrent)
-            _map=_cmap=new ConcurrentHashMap<K, Object>();
-        else
-            _map=new HashMap<K, Object>();
+        super(map);
     }
-    
+
 
     /* ------------------------------------------------------------ */
     /** Get multiple values.
@@ -86,9 +54,13 @@ public class MultiMap<K> implements ConcurrentMap<K,Object>, Serializable
      * @param name The entry key. 
      * @return Unmodifieable List of values.
      */
-    public List getValues(Object name)
+    public List<V> getValues(String name)
     {
-        return LazyList.getList(_map.get(name),true);
+        List<V> vals = super.get(name);
+        if((vals == null) || vals.isEmpty()) {
+            return null;
+        }
+        return vals;
     }
     
     /* ------------------------------------------------------------ */
@@ -99,12 +71,16 @@ public class MultiMap<K> implements ConcurrentMap<K,Object>, Serializable
      * @param i Index of element to get.
      * @return Unmodifieable List of values.
      */
-    public Object getValue(Object name,int i)
+    public V getValue(String name,int i)
     {
-        Object l=_map.get(name);
-        if (i==0 && LazyList.size(l)==0)
+        List<V> vals = getValues(name);
+        if(vals == null) {
+            return null;
+        }
+        if (i==0 && vals.isEmpty()) {
             return null;
-        return LazyList.get(l,i);
+        }
+        return vals.get(i);
     }
     
     
@@ -116,84 +92,85 @@ public class MultiMap<K> implements ConcurrentMap<K,Object>, Serializable
      * @param name The entry key. 
      * @return String value.
      */
-    public String getString(Object name)
+    public String getString(String name)
     {
-        Object l=_map.get(name);
-        switch(LazyList.size(l))
+        List<V> vals =get(name);
+        if ((vals == null) || (vals.isEmpty()))
         {
-          case 0:
-              return null;
-          case 1:
-              Object o=LazyList.get(l,0);
-              return o==null?null:o.toString();
-          default:
-          {
-              StringBuilder values=new StringBuilder(128);
-              for (int i=0; i<LazyList.size(l); i++)              
-              {
-                  Object e=LazyList.get(l,i);
-                  if (e!=null)
-                  {
-                      if (values.length()>0)
-                          values.append(',');
-                      values.append(e.toString());
-                  }
-              }   
-              return values.toString();
-          }
+            return null;
         }
-    }
-    
-    /* ------------------------------------------------------------ */
-    public Object get(Object name) 
-    {
-        Object l=_map.get(name);
-        switch(LazyList.size(l))
+        
+        if (vals.size() == 1)
         {
-          case 0:
-              return null;
-          case 1:
-              Object o=LazyList.get(l,0);
-              return o;
-          default:
-              return LazyList.getList(l,true);
+            // simple form.
+            return vals.get(0).toString();
         }
+        
+        // delimited form
+        StringBuilder values=new StringBuilder(128);
+        for (V e : vals)
+        {
+            if (e != null)
+            {
+                if (values.length() > 0)
+                    values.append(',');
+                values.append(e.toString());
+            }
+        }   
+        return values.toString();
     }
     
-    /* ------------------------------------------------------------ */
-    /** Put and entry into the map.
+    /** 
+     * Put multi valued entry.
      * @param name The entry key. 
-     * @param value The entry value.
+     * @param value The simple value
      * @return The previous value or null.
      */
-    public Object put(K name, Object value) 
+    public List<V> put(String name, V value) 
     {
-        return _map.put(name,LazyList.add(null,value));
+        if(value == null) {
+            return super.put(name, null);
+        }
+        List<V> vals = new ArrayList<>();
+        vals.add(value);
+        return put(name,vals);
     }
 
+    /**
+     * Shorthand version of putAll
+     * @param input the input map
+     */
+    public void putAllValues(Map<String, V> input)
+    {
+        for(Map.Entry<String,V> entry: input.entrySet())
+        {
+            put(entry.getKey(), entry.getValue());
+        }
+    }
+    
     /* ------------------------------------------------------------ */
     /** Put multi valued entry.
      * @param name The entry key. 
      * @param values The List of multiple values.
      * @return The previous value or null.
      */
-    public Object putValues(K name, List<? extends Object> values) 
+    public List<V> putValues(String name, List<V> values) 
     {
-        return _map.put(name,values);
+        return super.put(name,values);
     }
     
     /* ------------------------------------------------------------ */
     /** Put multi valued entry.
      * @param name The entry key. 
-     * @param values The String array of multiple values.
+     * @param values The array of multiple values.
      * @return The previous value or null.
      */
-    public Object putValues(K name, String... values) 
+    @SafeVarargs
+    public final List<V> putValues(String name, V... values) 
     {
-        Object list=null;
-        for (int i=0;i<values.length;i++)
-            list=LazyList.add(list,values[i]);
-        return _map.put(name,list);
+        List<V> list = new ArrayList<>();
+        list.addAll(Arrays.asList(values));
+        return super.put(name,list);
     }
     
     
@@ -204,12 +181,14 @@ public class MultiMap<K> implements ConcurrentMap<K,Object>, Serializable
      * @param name The entry key. 
      * @param value The entry value.
      */
-    public void add(K name, Object value) 
+    public void add(String name, V value) 
     {
-        Object lo = _map.get(name);
-        Object ln = LazyList.add(lo,value);
-        if (lo!=ln)
-            _map.put(name,ln);
+        List<V> lo = get(name);
+        if(lo == null) {
+            lo = new ArrayList<>();
+        }
+        lo.add(value);
+        super.put(name,lo);
     }
 
     /* ------------------------------------------------------------ */
@@ -219,12 +198,14 @@ public class MultiMap<K> implements ConcurrentMap<K,Object>, Serializable
      * @param name The entry key. 
      * @param values The List of multiple values.
      */
-    public void addValues(K name, List<? extends Object> values) 
+    public void addValues(String name, List<V> values) 
     {
-        Object lo = _map.get(name);
-        Object ln = LazyList.addCollection(lo,values);
-        if (lo!=ln)
-            _map.put(name,ln);
+        List<V> lo = get(name);
+        if(lo == null) {
+            lo = new ArrayList<>();
+        }
+        lo.addAll(values);
+        put(name,lo);
     }
     
     /* ------------------------------------------------------------ */
@@ -234,12 +215,47 @@ public class MultiMap<K> implements ConcurrentMap<K,Object>, Serializable
      * @param name The entry key. 
      * @param values The String array of multiple values.
      */
-    public void addValues(K name, String[] values) 
+    public void addValues(String name, V[] values) 
     {
-        Object lo = _map.get(name);
-        Object ln = LazyList.addCollection(lo,Arrays.asList(values));
-        if (lo!=ln)
-            _map.put(name,ln);
+        List<V> lo = get(name);
+        if(lo == null) {
+            lo = new ArrayList<>();
+        }
+        lo.addAll(Arrays.asList(values));
+        put(name,lo);
+    }
+    
+    /**
+     * Merge values.
+     * 
+     * @param map
+     *            the map to overlay on top of this one, merging together values if needed.
+     * @return true if an existing key was merged with potentially new values, false if either no change was made, or there were only new keys.
+     */
+    public boolean addAllValues(MultiMap<V> map)
+    {
+        boolean merged = false;
+
+        if ((map == null) || (map.isEmpty()))
+        {
+            // done
+            return merged;
+        }
+
+        for (Map.Entry<String, List<V>> entry : map.entrySet())
+        {
+            String name = entry.getKey();
+            List<V> values = entry.getValue();
+
+            if (this.containsKey(name))
+            {
+                merged = true;
+            }
+
+            this.addValues(name,values);
+        }
+
+        return merged;
     }
     
     /* ------------------------------------------------------------ */
@@ -248,63 +264,92 @@ public class MultiMap<K> implements ConcurrentMap<K,Object>, Serializable
      * @param value The entry value. 
      * @return true if it was removed.
      */
-    public boolean removeValue(K name,Object value)
+    public boolean removeValue(String name,V value)
     {
-        Object lo = _map.get(name);
-        Object ln=lo;
-        int s=LazyList.size(lo);
-        if (s>0)
-        {
-            ln=LazyList.remove(lo,value);
-            if (ln==null)
-                _map.remove(name);
-            else
-                _map.put(name, ln);
+        List<V> lo = get(name);
+        if((lo == null)||(lo.isEmpty())) {
+            return false;
+        }
+        boolean ret = lo.remove(value);
+        if(lo.isEmpty()) {
+            remove(name);
+        } else {
+            put(name,lo);
         }
-        return LazyList.size(ln)!=s;
+        return ret;
     }
     
-    
-    /* ------------------------------------------------------------ */
-    /** Put all contents of map.
-     * @param m Map
+    /**
+     * Test for a specific single value in the map.
+     * <p>
+     * NOTE: This is a SLOW operation, and is actively discouraged.
+     * @param value
+     * @return true if contains simple value
      */
-    public void putAll(Map<? extends K, ? extends Object> m)
+    public boolean containsSimpleValue(V value)
     {
-        boolean multi = (m instanceof MultiMap);
-
-        if (multi)
+        for (List<V> vals : values())
         {
-            for (Map.Entry<? extends K, ? extends Object> entry : m.entrySet())
+            if ((vals.size() == 1) && vals.contains(value))
             {
-                _map.put(entry.getKey(),LazyList.clone(entry.getValue()));
+                return true;
             }
         }
-        else
+        return false;
+    }
+    
+    @Override
+    public String toString()
+    {
+        Iterator<Entry<String, List<V>>> iter = entrySet().iterator();
+        StringBuilder sb = new StringBuilder();
+        sb.append('{');
+        boolean delim = false;
+        while (iter.hasNext())
         {
-            _map.putAll(m);
+            Entry<String, List<V>> e = iter.next();
+            if (delim)
+            {
+                sb.append(", ");
+            }
+            String key = e.getKey();
+            List<V> vals = e.getValue();
+            sb.append(key);
+            sb.append('=');
+            if (vals.size() == 1)
+            {
+                sb.append(vals.get(0));
+            }
+            else
+            {
+                sb.append(vals);
+            }
+            delim = true;
         }
+        sb.append('}');
+        return sb.toString();
     }
-
+    
     /* ------------------------------------------------------------ */
     /** 
      * @return Map of String arrays
      */
-    public Map<K,String[]> toStringArrayMap()
+    public Map<String,String[]> toStringArrayMap()
     {
-        HashMap<K,String[]> map = new HashMap<K,String[]>(_map.size()*3/2)
+        HashMap<String,String[]> map = new HashMap<String,String[]>(size()*3/2)
         {
+            @Override
             public String toString()
             {
                 StringBuilder b=new StringBuilder();
                 b.append('{');
-                for (K k:keySet())
+                for (String k:super.keySet())
                 {
                     if(b.length()>1)
                         b.append(',');
                     b.append(k);
                     b.append('=');
-                    b.append(Arrays.asList(get(k)));
+                    b.append(Arrays.asList(super.get(k)));
                 }
 
                 b.append('}');
@@ -312,104 +357,17 @@ public class MultiMap<K> implements ConcurrentMap<K,Object>, Serializable
             }
         };
         
-        for(Map.Entry<K,Object> entry: _map.entrySet())
+        for(Map.Entry<String,List<V>> entry: entrySet())
         {
-            String[] a = LazyList.toStringArray(entry.getValue());
+            String[] a = null;
+            if (entry.getValue() != null)
+            {
+                a = new String[entry.getValue().size()];
+                a = entry.getValue().toArray(a);
+            }
             map.put(entry.getKey(),a);
         }
         return map;
     }
 
-    @Override
-    public String toString()
-    {
-        return _cmap==null?_map.toString():_cmap.toString();
-    }
-    
-    public void clear()
-    {
-        _map.clear();
-    }
-
-    public boolean containsKey(Object key)
-    {
-        return _map.containsKey(key);
-    }
-
-    public boolean containsValue(Object value)
-    {
-        return _map.containsValue(value);
-    }
-
-    public Set<Entry<K, Object>> entrySet()
-    {
-        return _map.entrySet();
-    }
-
-    @Override
-    public boolean equals(Object o)
-    {
-        return _map.equals(o);
-    }
-
-    @Override
-    public int hashCode()
-    {
-        return _map.hashCode();
-    }
-
-    public boolean isEmpty()
-    {
-        return _map.isEmpty();
-    }
-
-    public Set<K> keySet()
-    {
-        return _map.keySet();
-    }
-
-    public Object remove(Object key)
-    {
-        return _map.remove(key);
-    }
-
-    public int size()
-    {
-        return _map.size();
-    }
-
-    public Collection<Object> values()
-    {
-        return _map.values();
-    }
-
-    
-    
-    public Object putIfAbsent(K key, Object value)
-    {
-        if (_cmap==null)
-            throw new UnsupportedOperationException();
-        return _cmap.putIfAbsent(key,value);
-    }
-
-    public boolean remove(Object key, Object value)
-    {
-        if (_cmap==null)
-            throw new UnsupportedOperationException();
-        return _cmap.remove(key,value);
-    }
-
-    public boolean replace(K key, Object oldValue, Object newValue)
-    {
-        if (_cmap==null)
-            throw new UnsupportedOperationException();
-        return _cmap.replace(key,oldValue,newValue);
-    }
-
-    public Object replace(K key, Object value)
-    {
-        if (_cmap==null)
-            throw new UnsupportedOperationException();
-        return _cmap.replace(key,value);
-    }
 }
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/MultiPartInputStream.java b/jetty-util/src/main/java/org/eclipse/jetty/util/MultiPartInputStream.java
deleted file mode 100644
index 8cb3b2f..0000000
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/MultiPartInputStream.java
+++ /dev/null
@@ -1,851 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.util;
-
-import java.io.BufferedInputStream;
-import java.io.BufferedOutputStream;
-import java.io.BufferedReader;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.FilterInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.lang.reflect.Array;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.StringTokenizer;
-
-import javax.servlet.MultipartConfigElement;
-import javax.servlet.ServletException;
-import javax.servlet.http.Part;
-
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-
-
-
-/**
- * MultiPartInputStream
- *
- * Handle a MultiPart Mime input stream, breaking it up on the boundary into files and strings.
- */
-public class MultiPartInputStream
-{
-    private static final Logger LOG = Log.getLogger(MultiPartInputStream.class);
-
-    public static final MultipartConfigElement  __DEFAULT_MULTIPART_CONFIG = new MultipartConfigElement(System.getProperty("java.io.tmpdir"));
-    protected InputStream _in;
-    protected MultipartConfigElement _config;
-    protected String _contentType;
-    protected MultiMap<String> _parts;
-    protected File _tmpDir;
-    protected File _contextTmpDir;
-    protected boolean _deleteOnExit;
-    
-    
-    
-    public class MultiPart implements Part
-    {
-        protected String _name;
-        protected String _filename;
-        protected File _file;
-        protected OutputStream _out;
-        protected ByteArrayOutputStream2 _bout;
-        protected String _contentType;
-        protected MultiMap<String> _headers;
-        protected long _size = 0;
-        protected boolean _temporary = true;
-
-        public MultiPart (String name, String filename) 
-        throws IOException
-        {
-            _name = name;
-            _filename = filename;
-        }
-
-        protected void setContentType (String contentType)
-        {
-            _contentType = contentType;
-        }
-        
-        
-        protected void open() 
-        throws IOException
-        {
-            //We will either be writing to a file, if it has a filename on the content-disposition
-            //and otherwise a byte-array-input-stream, OR if we exceed the getFileSizeThreshold, we
-            //will need to change to write to a file.           
-            if (_filename != null && _filename.trim().length() > 0)
-            {
-                createFile();            
-            }
-            else
-            {
-                //Write to a buffer in memory until we discover we've exceed the 
-                //MultipartConfig fileSizeThreshold
-                _out = _bout= new ByteArrayOutputStream2();
-            }
-        }
-        
-        protected void close() 
-        throws IOException
-        {
-            _out.close();
-        }
-        
-      
-        protected void write (int b)
-        throws IOException
-        {      
-            if (MultiPartInputStream.this._config.getMaxFileSize() > 0 && _size + 1 > MultiPartInputStream.this._config.getMaxFileSize())
-                throw new IllegalStateException ("Multipart Mime part "+_name+" exceeds max filesize");
-            
-            if (MultiPartInputStream.this._config.getFileSizeThreshold() > 0 && _size + 1 > MultiPartInputStream.this._config.getFileSizeThreshold() && _file==null)
-                createFile();
-            _out.write(b);   
-            _size ++;
-        }
-        
-        protected void write (byte[] bytes, int offset, int length) 
-        throws IOException
-        { 
-            if (MultiPartInputStream.this._config.getMaxFileSize() > 0 && _size + length > MultiPartInputStream.this._config.getMaxFileSize())
-                throw new IllegalStateException ("Multipart Mime part "+_name+" exceeds max filesize");
-            
-            if (MultiPartInputStream.this._config.getFileSizeThreshold() > 0 && _size + length > MultiPartInputStream.this._config.getFileSizeThreshold() && _file==null)
-                createFile();
-            
-            _out.write(bytes, offset, length);
-            _size += length;
-        }
-        
-        protected void createFile ()
-        throws IOException
-        {
-            _file = File.createTempFile("MultiPart", "", MultiPartInputStream.this._tmpDir);
-            if (_deleteOnExit)
-                _file.deleteOnExit();
-            FileOutputStream fos = new FileOutputStream(_file);
-            BufferedOutputStream bos = new BufferedOutputStream(fos);
-            
-            if (_size > 0 && _out != null)
-            {
-                //already written some bytes, so need to copy them into the file
-                _out.flush();
-                _bout.writeTo(bos);
-                _out.close();
-                _bout = null;
-            }
-            _out = bos;
-        }
-        
-
-        
-        protected void setHeaders(MultiMap<String> headers)
-        {
-            _headers = headers;
-        }
-        
-        /** 
-         * @see javax.servlet.http.Part#getContentType()
-         */
-        public String getContentType()
-        {
-            return _contentType;
-        }
-
-        /** 
-         * @see javax.servlet.http.Part#getHeader(java.lang.String)
-         */
-        public String getHeader(String name)
-        {
-            if (name == null)
-                return null;
-            return (String)_headers.getValue(name.toLowerCase(Locale.ENGLISH), 0);
-        }
-
-        /** 
-         * @see javax.servlet.http.Part#getHeaderNames()
-         */
-        public Collection<String> getHeaderNames()
-        {
-            return _headers.keySet();
-        }
-
-        /** 
-         * @see javax.servlet.http.Part#getHeaders(java.lang.String)
-         */
-        public Collection<String> getHeaders(String name)
-        {
-           return _headers.getValues(name);
-        }
-
-        /** 
-         * @see javax.servlet.http.Part#getInputStream()
-         */
-        public InputStream getInputStream() throws IOException
-        {
-           if (_file != null)
-           {
-               //written to a file, whether temporary or not
-               return new BufferedInputStream (new FileInputStream(_file));
-           }
-           else
-           {
-               //part content is in memory
-               return new ByteArrayInputStream(_bout.getBuf(),0,_bout.size());
-           }
-        }
-
-        public byte[] getBytes()
-        {
-            if (_bout!=null)
-                return _bout.toByteArray();
-            return null;
-        }
-        
-        /** 
-         * @see javax.servlet.http.Part#getName()
-         */
-        public String getName()
-        {
-           return _name;
-        }
-
-        /** 
-         * @see javax.servlet.http.Part#getSize()
-         */
-        public long getSize()
-        {
-            return _size;         
-        }
-
-        /** 
-         * @see javax.servlet.http.Part#write(java.lang.String)
-         */
-        public void write(String fileName) throws IOException
-        {
-            if (_file == null)
-            {
-                _temporary = false;
-                
-                //part data is only in the ByteArrayOutputStream and never been written to disk
-                _file = new File (_tmpDir, fileName);
-
-                BufferedOutputStream bos = null;
-                try
-                {
-                    bos = new BufferedOutputStream(new FileOutputStream(_file));
-                    _bout.writeTo(bos);
-                    bos.flush();
-                }
-                finally
-                {
-                    if (bos != null)
-                        bos.close();
-                    _bout = null;
-                }
-            }
-            else
-            {
-                //the part data is already written to a temporary file, just rename it
-                _temporary = false;
-                
-                File f = new File(_tmpDir, fileName);
-                if (_file.renameTo(f))
-                    _file = f;
-            }
-        }
-        
-        /** 
-         * Remove the file, whether or not Part.write() was called on it
-         * (ie no longer temporary)
-         * @see javax.servlet.http.Part#delete()
-         */
-        public void delete() throws IOException
-        {
-            if (_file != null && _file.exists())
-                _file.delete();     
-        }
-        
-        /**
-         * Only remove tmp files.
-         * 
-         * @throws IOException
-         */
-        public void cleanUp() throws IOException
-        {
-            if (_temporary && _file != null && _file.exists())
-                _file.delete();
-        }
-        
-        
-        /**
-         * Get the file, if any, the data has been written to.
-         * @return
-         */
-        public File getFile ()
-        {
-            return _file;
-        }  
-        
-        
-        /**
-         * Get the filename from the content-disposition.
-         * @return null or the filename
-         */
-        public String getContentDispositionFilename ()
-        {
-            return _filename;
-        }
-    }
-    
-    
-    
-    
-    /**
-     * @param in Request input stream 
-     * @param contentType Content-Type header
-     * @param config MultipartConfigElement 
-     * @param contextTmpDir javax.servlet.context.tempdir
-     */
-    public MultiPartInputStream (InputStream in, String contentType, MultipartConfigElement config, File contextTmpDir)
-    {
-        _in = new ReadLineInputStream(in);
-       _contentType = contentType;
-       _config = config;
-       _contextTmpDir = contextTmpDir;
-       if (_contextTmpDir == null)
-           _contextTmpDir = new File (System.getProperty("java.io.tmpdir"));
-       
-       if (_config == null)
-           _config = new MultipartConfigElement(_contextTmpDir.getAbsolutePath());
-    }
-
-    /**
-     * Get the already parsed parts.
-     * 
-     * @return
-     */
-    public Collection<Part> getParsedParts()
-    {
-        if (_parts == null)
-            return Collections.emptyList();
-
-        Collection<Object> values = _parts.values();
-        List<Part> parts = new ArrayList<Part>();
-        for (Object o: values)
-        {
-            List<Part> asList = LazyList.getList(o, false);
-            parts.addAll(asList);
-        }
-        return parts;
-    }
-    
-    /**
-     * Delete any tmp storage for parts, and clear out the parts list.
-     * 
-     * @throws MultiException
-     */
-    public void deleteParts ()
-    throws MultiException
-    {
-        Collection<Part> parts = getParsedParts();
-        MultiException err = new MultiException();
-        for (Part p:parts)
-        {
-            try
-            {
-                ((MultiPartInputStream.MultiPart)p).cleanUp();
-            } 
-            catch(Exception e)
-            {     
-                err.add(e); 
-            }
-        }
-        _parts.clear();
-        
-        err.ifExceptionThrowMulti();
-    }
-
-   
-    /**
-     * Parse, if necessary, the multipart data and return the list of Parts.
-     * 
-     * @return
-     * @throws IOException
-     * @throws ServletException
-     */
-    public Collection<Part> getParts()
-    throws IOException, ServletException
-    {
-        parse();
-        Collection<Object> values = _parts.values();
-        List<Part> parts = new ArrayList<Part>();
-        for (Object o: values)
-        {
-            List<Part> asList = LazyList.getList(o, false);
-            parts.addAll(asList);
-        }
-        return parts;
-    }
-    
-    
-    /**
-     * Get the named Part.
-     * 
-     * @param name
-     * @return
-     * @throws IOException
-     * @throws ServletException
-     */
-    public Part getPart(String name)
-    throws IOException, ServletException
-    {
-        parse();
-        return (Part)_parts.getValue(name, 0);
-    }
-    
-    
-    /**
-     * Parse, if necessary, the multipart stream.
-     * 
-     * @throws IOException
-     * @throws ServletException
-     */
-    protected void parse ()
-    throws IOException, ServletException
-    {
-        //have we already parsed the input?
-        if (_parts != null)
-            return;
-        
-        //initialize
-        long total = 0; //keep running total of size of bytes read from input and throw an exception if exceeds MultipartConfigElement._maxRequestSize              
-        _parts = new MultiMap<String>();
-
-        //if its not a multipart request, don't parse it
-        if (_contentType == null || !_contentType.startsWith("multipart/form-data"))
-            return;
- 
-        //sort out the location to which to write the files
-        
-        if (_config.getLocation() == null)
-            _tmpDir = _contextTmpDir;
-        else if ("".equals(_config.getLocation()))
-            _tmpDir = _contextTmpDir;
-        else
-        {
-            File f = new File (_config.getLocation());
-            if (f.isAbsolute())
-                _tmpDir = f;
-            else
-                _tmpDir = new File (_contextTmpDir, _config.getLocation());
-        }
-      
-        if (!_tmpDir.exists())
-            _tmpDir.mkdirs();
-
-        String contentTypeBoundary = "";
-        int bstart = _contentType.indexOf("boundary=");
-        if (bstart >= 0)
-        {
-            int bend = _contentType.indexOf(";", bstart);
-            bend = (bend < 0? _contentType.length(): bend);
-            contentTypeBoundary = QuotedStringTokenizer.unquote(value(_contentType.substring(bstart,bend), true).trim());
-        }
-        
-        String boundary="--"+contentTypeBoundary;
-        byte[] byteBoundary=(boundary+"--").getBytes(StringUtil.__ISO_8859_1);
-
-        // Get first boundary
-        String line = null;
-        try
-        {
-            line=((ReadLineInputStream)_in).readLine();  
-        }
-        catch (IOException e)
-        {
-            LOG.warn("Badly formatted multipart request");
-            throw e;
-        }
-
-        if (line == null)
-            throw new IOException("Missing content for multipart request");
-
-        boolean badFormatLogged = false;
-        line=line.trim();
-        while (line != null && !line.equals(boundary))
-        {
-            if (!badFormatLogged)
-            {
-                LOG.warn("Badly formatted multipart request");
-                badFormatLogged = true;
-            }
-            line=((ReadLineInputStream)_in).readLine();
-            line=(line==null?line:line.trim());
-        }
-
-        if (line == null)
-            throw new IOException("Missing initial multi part boundary");
-
-        // Read each part
-        boolean lastPart=false;
-
-        outer:while(!lastPart)
-        {
-            String contentDisposition=null;
-            String contentType=null;
-            String contentTransferEncoding=null;
-            
-            MultiMap<String> headers = new MultiMap<String>();
-            while(true)
-            {
-                line=((ReadLineInputStream)_in).readLine();
-                
-                //No more input
-                if(line==null)
-                    break outer;
-
-                // If blank line, end of part headers
-                if("".equals(line))
-                    break;
-                
-                total += line.length();
-                if (_config.getMaxRequestSize() > 0 && total > _config.getMaxRequestSize())
-                    throw new IllegalStateException ("Request exceeds maxRequestSize ("+_config.getMaxRequestSize()+")");
-
-                //get content-disposition and content-type
-                int c=line.indexOf(':',0);
-                if(c>0)
-                {
-                    String key=line.substring(0,c).trim().toLowerCase(Locale.ENGLISH);
-                    String value=line.substring(c+1,line.length()).trim();
-                    headers.put(key, value);
-                    if (key.equalsIgnoreCase("content-disposition"))
-                        contentDisposition=value;
-                    if (key.equalsIgnoreCase("content-type"))
-                        contentType = value;
-                    if(key.equals("content-transfer-encoding"))
-                        contentTransferEncoding=value;
-
-                }
-            }
-
-            // Extract content-disposition
-            boolean form_data=false;
-            if(contentDisposition==null)
-            {
-                throw new IOException("Missing content-disposition");
-            }
-
-            QuotedStringTokenizer tok=new QuotedStringTokenizer(contentDisposition,";", false, true);
-            String name=null;
-            String filename=null;
-            while(tok.hasMoreTokens())
-            {
-                String t=tok.nextToken().trim();
-                String tl=t.toLowerCase(Locale.ENGLISH);
-                if(t.startsWith("form-data"))
-                    form_data=true;
-                else if(tl.startsWith("name="))
-                    name=value(t, true);
-                else if(tl.startsWith("filename="))
-                    filename=filenameValue(t);
-            }
-
-            // Check disposition
-            if(!form_data)
-            {
-                continue;
-            }
-            //It is valid for reset and submit buttons to have an empty name.
-            //If no name is supplied, the browser skips sending the info for that field.
-            //However, if you supply the empty string as the name, the browser sends the
-            //field, with name as the empty string. So, only continue this loop if we
-            //have not yet seen a name field.
-            if(name==null)
-            {
-                continue;
-            }
-
-            //Have a new Part
-            MultiPart part = new MultiPart(name, filename);
-            part.setHeaders(headers);
-            part.setContentType(contentType);
-            _parts.add(name, part);
-            part.open();
-            
-            InputStream partInput = null;
-            if ("base64".equalsIgnoreCase(contentTransferEncoding))
-            {
-                partInput = new Base64InputStream((ReadLineInputStream)_in);
-            }
-            else if ("quoted-printable".equalsIgnoreCase(contentTransferEncoding))
-            {
-                partInput = new FilterInputStream(_in)
-                {
-                    @Override
-                    public int read() throws IOException
-                    {
-                        int c = in.read();
-                        if (c >= 0 && c == '=')
-                        {
-                            int hi = in.read();
-                            int lo = in.read();
-                            if (hi < 0 || lo < 0)
-                            {
-                                throw new IOException("Unexpected end to quoted-printable byte");
-                            }
-                            char[] chars = new char[] { (char)hi, (char)lo };
-                            c = Integer.parseInt(new String(chars),16);
-                        }
-                        return c;
-                    }
-                };
-            }
-            else
-                partInput = _in;
-            
-            try
-            { 
-                int state=-2;
-                int c;
-                boolean cr=false;
-                boolean lf=false;
-
-                // loop for all lines
-                while(true)
-                {
-                    int b=0;
-                    while((c=(state!=-2)?state:partInput.read())!=-1)
-                    {
-                        total ++;
-                        if (_config.getMaxRequestSize() > 0 && total > _config.getMaxRequestSize())
-                            throw new IllegalStateException("Request exceeds maxRequestSize ("+_config.getMaxRequestSize()+")");
-                        
-                        state=-2;
-                        
-                        // look for CR and/or LF
-                        if(c==13||c==10)
-                        {
-                            if(c==13)
-                            {
-                                partInput.mark(1);
-                                int tmp=partInput.read();
-                                if (tmp!=10)
-                                    partInput.reset();
-                                else
-                                    state=tmp;
-                            }
-                            break;
-                        }
-                        
-                        // Look for boundary
-                        if(b>=0&&b<byteBoundary.length&&c==byteBoundary[b])
-                        {
-                            b++;
-                        }
-                        else
-                        {
-                            // Got a character not part of the boundary, so we don't have the boundary marker.
-                            // Write out as many chars as we matched, then the char we're looking at.
-                            if(cr)
-                                part.write(13);
-                    
-                            if(lf)
-                                part.write(10); 
-                            
-                            cr=lf=false;
-                            if(b>0)
-                                part.write(byteBoundary,0,b);
-                              
-                            b=-1;
-                            part.write(c);
-                        }
-                    }
-                    
-                    // Check for incomplete boundary match, writing out the chars we matched along the way
-                    if((b>0&&b<byteBoundary.length-2)||(b==byteBoundary.length-1))
-                    {
-                        if(cr)
-                            part.write(13);
-
-                        if(lf)
-                            part.write(10);
-
-                        cr=lf=false;
-                        part.write(byteBoundary,0,b);
-                        b=-1;
-                    }
-                    
-                    // Boundary match. If we've run out of input or we matched the entire final boundary marker, then this is the last part.
-                    if(b>0||c==-1)
-                    {
-                       
-                        if(b==byteBoundary.length)
-                            lastPart=true;
-                        if(state==10)
-                            state=-2;
-                        break;
-                    }
-                    
-                    // handle CR LF
-                    if(cr)
-                        part.write(13); 
-
-                    if(lf)
-                        part.write(10);
-
-                    cr=(c==13);
-                    lf=(c==10||state==10);
-                    if(state==10)
-                        state=-2;
-                }
-            }
-            finally
-            {
-                part.close();
-            }
-        }
-        if (!lastPart)
-            throw new IOException("Incomplete parts");
-    }
-    
-    public void setDeleteOnExit(boolean deleteOnExit)
-    {
-        _deleteOnExit = deleteOnExit;
-    }
-
-
-    public boolean isDeleteOnExit()
-    {
-        return _deleteOnExit;
-    }
-
-
-    /* ------------------------------------------------------------ */
-    private String value(String nameEqualsValue, boolean splitAfterSpace)
-    {
-        /*
-        String value=nameEqualsValue.substring(nameEqualsValue.indexOf('=')+1).trim();
-        int i=value.indexOf(';');
-        if(i>0)
-            value=value.substring(0,i);
-        if(value.startsWith("\""))
-        {
-            value=value.substring(1,value.indexOf('"',1));
-        }
-        else if (splitAfterSpace)
-        {
-            i=value.indexOf(' ');
-            if(i>0)
-                value=value.substring(0,i);
-        }
-        return value;
-        */
-         int idx = nameEqualsValue.indexOf('=');
-         String value = nameEqualsValue.substring(idx+1).trim();
-         return QuotedStringTokenizer.unquoteOnly(value);
-    }
-    
-    
-    /* ------------------------------------------------------------ */
-    private String filenameValue(String nameEqualsValue)
-    {
-        int idx = nameEqualsValue.indexOf('=');
-        String value = nameEqualsValue.substring(idx+1).trim();   
-
-        if (value.matches(".??[a-z,A-Z]\\:\\\\[^\\\\].*"))
-        {
-            //incorrectly escaped IE filenames that have the whole path
-            //we just strip any leading & trailing quotes and leave it as is
-            char first=value.charAt(0);
-            if (first=='"' || first=='\'')
-                value=value.substring(1);
-            char last=value.charAt(value.length()-1);
-            if (last=='"' || last=='\'')
-                value = value.substring(0,value.length()-1);
-
-            return value;
-        }
-        else
-            //unquote the string, but allow any backslashes that don't
-            //form a valid escape sequence to remain as many browsers
-            //even on *nix systems will not escape a filename containing
-            //backslashes
-            return QuotedStringTokenizer.unquoteOnly(value, true);
-    }
-    
-    private static class Base64InputStream extends InputStream
-    {
-        ReadLineInputStream _in;
-        String _line;
-        byte[] _buffer;
-        int _pos;
-
-    
-        public Base64InputStream(ReadLineInputStream rlis)
-        {
-            _in = rlis;
-        }
-
-        @Override
-        public int read() throws IOException
-        {
-            if (_buffer==null || _pos>= _buffer.length)
-            {
-                //Any CR and LF will be consumed by the readLine() call.
-                //We need to put them back into the bytes returned from this
-                //method because the parsing of the multipart content uses them
-                //as markers to determine when we've reached the end of a part.
-                _line = _in.readLine(); 
-                if (_line==null)
-                    return -1;  //nothing left
-                if (_line.startsWith("--"))
-                    _buffer=(_line+"\r\n").getBytes(); //boundary marking end of part
-                else if (_line.length()==0)
-                    _buffer="\r\n".getBytes(); //blank line
-                else
-                {
-                    ByteArrayOutputStream baos = new ByteArrayOutputStream((4*_line.length()/3)+2);
-                    B64Code.decode(_line, baos);
-                    baos.write(13);
-                    baos.write(10);
-                    _buffer = baos.toByteArray();
-                }
-
-                _pos=0;
-            }
-            
-            return _buffer[_pos++];
-        }
-    }
-}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/MultiPartInputStreamParser.java b/jetty-util/src/main/java/org/eclipse/jetty/util/MultiPartInputStreamParser.java
new file mode 100644
index 0000000..a8607ea
--- /dev/null
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/MultiPartInputStreamParser.java
@@ -0,0 +1,827 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+
+import javax.servlet.MultipartConfigElement;
+import javax.servlet.ServletException;
+import javax.servlet.http.Part;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+
+/**
+ * MultiPartInputStream
+ *
+ * Handle a MultiPart Mime input stream, breaking it up on the boundary into files and strings.
+ */
+public class MultiPartInputStreamParser
+{
+    private static final Logger LOG = Log.getLogger(MultiPartInputStreamParser.class);
+    public static final MultipartConfigElement  __DEFAULT_MULTIPART_CONFIG = new MultipartConfigElement(System.getProperty("java.io.tmpdir"));
+    protected InputStream _in;
+    protected MultipartConfigElement _config;
+    protected String _contentType;
+    protected MultiMap<Part> _parts;
+    protected File _tmpDir;
+    protected File _contextTmpDir;
+    protected boolean _deleteOnExit;
+
+
+
+    public class MultiPart implements Part
+    {
+        protected String _name;
+        protected String _filename;
+        protected File _file;
+        protected OutputStream _out;
+        protected ByteArrayOutputStream2 _bout;
+        protected String _contentType;
+        protected MultiMap<String> _headers;
+        protected long _size = 0;
+        protected boolean _temporary = true;
+
+        public MultiPart (String name, String filename)
+        throws IOException
+        {
+            _name = name;
+            _filename = filename;
+        }
+
+        protected void setContentType (String contentType)
+        {
+            _contentType = contentType;
+        }
+
+
+        protected void open()
+        throws IOException
+        {
+            //Write to a buffer in memory until we discover we've exceed the
+            //MultipartConfig fileSizeThreshold
+            _out = _bout= new ByteArrayOutputStream2();
+        }
+
+        protected void close()
+        throws IOException
+        {
+            _out.close();
+        }
+
+
+        protected void write (int b)
+        throws IOException
+        {
+            if (MultiPartInputStreamParser.this._config.getMaxFileSize() > 0 && _size + 1 > MultiPartInputStreamParser.this._config.getMaxFileSize())
+                throw new IllegalStateException ("Multipart Mime part "+_name+" exceeds max filesize");
+
+            if (MultiPartInputStreamParser.this._config.getFileSizeThreshold() > 0 && _size + 1 > MultiPartInputStreamParser.this._config.getFileSizeThreshold() && _file==null)
+                createFile();
+
+            _out.write(b);
+            _size ++;
+        }
+
+        protected void write (byte[] bytes, int offset, int length)
+        throws IOException
+        {
+            if (MultiPartInputStreamParser.this._config.getMaxFileSize() > 0 && _size + length > MultiPartInputStreamParser.this._config.getMaxFileSize())
+                throw new IllegalStateException ("Multipart Mime part "+_name+" exceeds max filesize");
+
+            if (MultiPartInputStreamParser.this._config.getFileSizeThreshold() > 0 && _size + length > MultiPartInputStreamParser.this._config.getFileSizeThreshold() && _file==null)
+                createFile();
+           
+            _out.write(bytes, offset, length);
+            _size += length;
+        }
+
+        protected void createFile ()
+        throws IOException
+        {
+            _file = File.createTempFile("MultiPart", "", MultiPartInputStreamParser.this._tmpDir);
+            
+            if (_deleteOnExit)
+                _file.deleteOnExit();
+            FileOutputStream fos = new FileOutputStream(_file);
+            BufferedOutputStream bos = new BufferedOutputStream(fos);
+
+            if (_size > 0 && _out != null)
+            {
+                //already written some bytes, so need to copy them into the file
+                _out.flush();
+                _bout.writeTo(bos);
+                _out.close();
+                _bout = null;
+            }
+            _out = bos;
+        }
+
+
+
+        protected void setHeaders(MultiMap<String> headers)
+        {
+            _headers = headers;
+        }
+
+        /**
+         * @see javax.servlet.http.Part#getContentType()
+         */
+        public String getContentType()
+        {
+            return _contentType;
+        }
+
+        /**
+         * @see javax.servlet.http.Part#getHeader(java.lang.String)
+         */
+        public String getHeader(String name)
+        {
+            if (name == null)
+                return null;
+            return (String)_headers.getValue(name.toLowerCase(Locale.ENGLISH), 0);
+        }
+
+        /**
+         * @see javax.servlet.http.Part#getHeaderNames()
+         */
+        public Collection<String> getHeaderNames()
+        {
+            return _headers.keySet();
+        }
+
+        /**
+         * @see javax.servlet.http.Part#getHeaders(java.lang.String)
+         */
+        public Collection<String> getHeaders(String name)
+        {
+           return _headers.getValues(name);
+        }
+
+        /**
+         * @see javax.servlet.http.Part#getInputStream()
+         */
+        public InputStream getInputStream() throws IOException
+        {
+           if (_file != null)
+           {
+               //written to a file, whether temporary or not
+               return new BufferedInputStream (new FileInputStream(_file));
+           }
+           else
+           {
+               //part content is in memory
+               return new ByteArrayInputStream(_bout.getBuf(),0,_bout.size());
+           }
+        }
+
+        
+        /** 
+         * @see javax.servlet.http.Part#getSubmittedFileName()
+         */
+        @Override
+        public String getSubmittedFileName()
+        {
+            return getContentDispositionFilename();
+        }
+
+        public byte[] getBytes()
+        {
+            if (_bout!=null)
+                return _bout.toByteArray();
+            return null;
+        }
+
+        /**
+         * @see javax.servlet.http.Part#getName()
+         */
+        public String getName()
+        {
+           return _name;
+        }
+
+        /**
+         * @see javax.servlet.http.Part#getSize()
+         */
+        public long getSize()
+        {
+            return _size;         
+        }
+
+        /**
+         * @see javax.servlet.http.Part#write(java.lang.String)
+         */
+        public void write(String fileName) throws IOException
+        {
+            if (_file == null)
+            {
+                _temporary = false;
+                
+                //part data is only in the ByteArrayOutputStream and never been written to disk
+                _file = new File (_tmpDir, fileName);
+
+                BufferedOutputStream bos = null;
+                try
+                {
+                    bos = new BufferedOutputStream(new FileOutputStream(_file));
+                    _bout.writeTo(bos);
+                    bos.flush();
+                }
+                finally
+                {
+                    if (bos != null)
+                        bos.close();
+                    _bout = null;
+                }
+            }
+            else
+            {
+                //the part data is already written to a temporary file, just rename it
+                _temporary = false;
+                
+                File f = new File(_tmpDir, fileName);
+                if (_file.renameTo(f))
+                    _file = f;
+            }
+        }
+
+        /**
+         * Remove the file, whether or not Part.write() was called on it
+         * (ie no longer temporary)
+         * @see javax.servlet.http.Part#delete()
+         */
+        public void delete() throws IOException
+        {
+            if (_file != null && _file.exists())
+                _file.delete();     
+        }
+        
+        /**
+         * Only remove tmp files.
+         * 
+         * @throws IOException
+         */
+        public void cleanUp() throws IOException
+        {
+            if (_temporary && _file != null && _file.exists())
+                _file.delete();
+        }
+
+
+        /**
+         * Get the file, if any, the data has been written to.
+         */
+        public File getFile ()
+        {
+            return _file;
+        }
+
+
+        /**
+         * Get the filename from the content-disposition.
+         * @return null or the filename
+         */
+        public String getContentDispositionFilename ()
+        {
+            return _filename;
+        }
+    }
+
+
+
+
+    /**
+     * @param in Request input stream
+     * @param contentType Content-Type header
+     * @param config MultipartConfigElement
+     * @param contextTmpDir javax.servlet.context.tempdir
+     */
+    public MultiPartInputStreamParser (InputStream in, String contentType, MultipartConfigElement config, File contextTmpDir)
+    {
+        _in = new ReadLineInputStream(in);
+       _contentType = contentType;
+       _config = config;
+       _contextTmpDir = contextTmpDir;
+       if (_contextTmpDir == null)
+           _contextTmpDir = new File (System.getProperty("java.io.tmpdir"));
+       
+       if (_config == null)
+           _config = new MultipartConfigElement(_contextTmpDir.getAbsolutePath());
+    }
+
+    /**
+     * Get the already parsed parts.
+     */
+    public Collection<Part> getParsedParts()
+    {
+        if (_parts == null)
+            return Collections.emptyList();
+
+        Collection<List<Part>> values = _parts.values();
+        List<Part> parts = new ArrayList<Part>();
+        for (List<Part> o: values)
+        {
+            List<Part> asList = LazyList.getList(o, false);
+            parts.addAll(asList);
+        }
+        return parts;
+    }
+
+    /**
+     * Delete any tmp storage for parts, and clear out the parts list.
+     * 
+     * @throws MultiException
+     */
+    public void deleteParts ()
+    throws MultiException
+    {
+        Collection<Part> parts = getParsedParts();
+        MultiException err = new MultiException();
+        for (Part p:parts)
+        {
+            try
+            {
+                ((MultiPartInputStreamParser.MultiPart)p).cleanUp();
+            } 
+            catch(Exception e)
+            {     
+                err.add(e); 
+            }
+        }
+        _parts.clear();
+        
+        err.ifExceptionThrowMulti();
+    }
+
+   
+    /**
+     * Parse, if necessary, the multipart data and return the list of Parts.
+     * 
+     * @throws IOException
+     * @throws ServletException
+     */
+    public Collection<Part> getParts()
+    throws IOException, ServletException
+    {
+        parse();
+        Collection<List<Part>> values = _parts.values();
+        List<Part> parts = new ArrayList<Part>();
+        for (List<Part> o: values)
+        {
+            List<Part> asList = LazyList.getList(o, false);
+            parts.addAll(asList);
+        }
+        return parts;
+    }
+
+
+    /**
+     * Get the named Part.
+     * 
+     * @param name
+     * @throws IOException
+     * @throws ServletException
+     */
+    public Part getPart(String name)
+    throws IOException, ServletException
+    {
+        parse();
+        return (Part)_parts.getValue(name, 0);
+    }
+
+
+    /**
+     * Parse, if necessary, the multipart stream.
+     * 
+     * @throws IOException
+     * @throws ServletException
+     */
+    protected void parse ()
+    throws IOException, ServletException
+    {
+        //have we already parsed the input?
+        if (_parts != null)
+            return;
+
+        //initialize
+        long total = 0; //keep running total of size of bytes read from input and throw an exception if exceeds MultipartConfigElement._maxRequestSize
+        _parts = new MultiMap<Part>();
+
+        //if its not a multipart request, don't parse it
+        if (_contentType == null || !_contentType.startsWith("multipart/form-data"))
+            return;
+
+        //sort out the location to which to write the files
+
+        if (_config.getLocation() == null)
+            _tmpDir = _contextTmpDir;
+        else if ("".equals(_config.getLocation()))
+            _tmpDir = _contextTmpDir;
+        else
+        {
+            File f = new File (_config.getLocation());
+            if (f.isAbsolute())
+                _tmpDir = f;
+            else
+                _tmpDir = new File (_contextTmpDir, _config.getLocation());
+        }
+
+        if (!_tmpDir.exists())
+            _tmpDir.mkdirs();
+
+        String contentTypeBoundary = "";
+        int bstart = _contentType.indexOf("boundary=");
+        if (bstart >= 0)
+        {
+            int bend = _contentType.indexOf(";", bstart);
+            bend = (bend < 0? _contentType.length(): bend);
+            contentTypeBoundary = QuotedStringTokenizer.unquote(value(_contentType.substring(bstart,bend)).trim());
+        }
+        
+        String boundary="--"+contentTypeBoundary;
+        byte[] byteBoundary=(boundary+"--").getBytes(StandardCharsets.ISO_8859_1);
+
+        // Get first boundary
+        String line = null;
+        try
+        {
+            line=((ReadLineInputStream)_in).readLine();  
+        }
+        catch (IOException e)
+        {
+            LOG.warn("Badly formatted multipart request");
+            throw e;
+        }
+        
+        if (line == null)
+            throw new IOException("Missing content for multipart request");
+        
+        boolean badFormatLogged = false;
+        line=line.trim();
+        while (line != null && !line.equals(boundary))
+        {
+            if (!badFormatLogged)
+            {
+                LOG.warn("Badly formatted multipart request");
+                badFormatLogged = true;
+            }
+            line=((ReadLineInputStream)_in).readLine();
+            line=(line==null?line:line.trim());
+        }
+
+        if (line == null)
+            throw new IOException("Missing initial multi part boundary");
+
+        // Read each part
+        boolean lastPart=false;
+
+        outer:while(!lastPart)
+        {
+            String contentDisposition=null;
+            String contentType=null;
+            String contentTransferEncoding=null;
+            
+            MultiMap<String> headers = new MultiMap<String>();
+            while(true)
+            {
+                line=((ReadLineInputStream)_in).readLine();
+                
+                //No more input
+                if(line==null)
+                    break outer;
+                
+                //end of headers:
+                if("".equals(line))
+                    break;
+           
+                total += line.length();
+                if (_config.getMaxRequestSize() > 0 && total > _config.getMaxRequestSize())
+                    throw new IllegalStateException ("Request exceeds maxRequestSize ("+_config.getMaxRequestSize()+")");
+
+                //get content-disposition and content-type
+                int c=line.indexOf(':',0);
+                if(c>0)
+                {
+                    String key=line.substring(0,c).trim().toLowerCase(Locale.ENGLISH);
+                    String value=line.substring(c+1,line.length()).trim();
+                    headers.put(key, value);
+                    if (key.equalsIgnoreCase("content-disposition"))
+                        contentDisposition=value;
+                    if (key.equalsIgnoreCase("content-type"))
+                        contentType = value;
+                    if(key.equals("content-transfer-encoding"))
+                        contentTransferEncoding=value;
+                }
+            }
+
+            // Extract content-disposition
+            boolean form_data=false;
+            if(contentDisposition==null)
+            {
+                throw new IOException("Missing content-disposition");
+            }
+
+            QuotedStringTokenizer tok=new QuotedStringTokenizer(contentDisposition,";", false, true);
+            String name=null;
+            String filename=null;
+            while(tok.hasMoreTokens())
+            {
+                String t=tok.nextToken().trim();
+                String tl=t.toLowerCase(Locale.ENGLISH);
+                if(t.startsWith("form-data"))
+                    form_data=true;
+                else if(tl.startsWith("name="))
+                    name=value(t);
+                else if(tl.startsWith("filename="))
+                    filename=filenameValue(t);
+            }
+
+            // Check disposition
+            if(!form_data)
+            {
+                continue;
+            }
+            //It is valid for reset and submit buttons to have an empty name.
+            //If no name is supplied, the browser skips sending the info for that field.
+            //However, if you supply the empty string as the name, the browser sends the
+            //field, with name as the empty string. So, only continue this loop if we
+            //have not yet seen a name field.
+            if(name==null)
+            {
+                continue;
+            }
+
+            //Have a new Part
+            MultiPart part = new MultiPart(name, filename);
+            part.setHeaders(headers);
+            part.setContentType(contentType);
+            _parts.add(name, part);
+            part.open();
+            
+            InputStream partInput = null;
+            if ("base64".equalsIgnoreCase(contentTransferEncoding))
+            {
+                partInput = new Base64InputStream((ReadLineInputStream)_in);
+            }
+            else if ("quoted-printable".equalsIgnoreCase(contentTransferEncoding))
+            {
+                partInput = new FilterInputStream(_in)
+                {
+                    @Override
+                    public int read() throws IOException
+                    {
+                        int c = in.read();
+                        if (c >= 0 && c == '=')
+                        {
+                            int hi = in.read();
+                            int lo = in.read();
+                            if (hi < 0 || lo < 0)
+                            {
+                                throw new IOException("Unexpected end to quoted-printable byte");
+                            }
+                            char[] chars = new char[] { (char)hi, (char)lo };
+                            c = Integer.parseInt(new String(chars),16);
+                        }
+                        return c;
+                    }
+                };
+            }
+            else
+                partInput = _in;
+
+            
+            try
+            {
+                int state=-2;
+                int c;
+                boolean cr=false;
+                boolean lf=false;
+
+                // loop for all lines
+                while(true)
+                {
+                    int b=0;
+                    while((c=(state!=-2)?state:partInput.read())!=-1)
+                    {
+                        total ++;
+                        if (_config.getMaxRequestSize() > 0 && total > _config.getMaxRequestSize())
+                            throw new IllegalStateException("Request exceeds maxRequestSize ("+_config.getMaxRequestSize()+")");
+
+                        state=-2;
+                        
+                        // look for CR and/or LF
+                        if(c==13||c==10)
+                        {
+                            if(c==13)
+                            {
+                                partInput.mark(1);
+                                int tmp=partInput.read();
+                                if (tmp!=10)
+                                    partInput.reset();
+                                else
+                                    state=tmp;
+                            }
+                            break;
+                        }
+                        
+                        // Look for boundary
+                        if(b>=0&&b<byteBoundary.length&&c==byteBoundary[b])
+                        {
+                            b++;
+                        }
+                        else
+                        {
+                            // Got a character not part of the boundary, so we don't have the boundary marker.
+                            // Write out as many chars as we matched, then the char we're looking at.
+                            if(cr)
+                                part.write(13);
+
+                            if(lf)
+                                part.write(10);
+
+                            cr=lf=false;
+                            if(b>0)
+                                part.write(byteBoundary,0,b);
+
+                            b=-1;
+                            part.write(c);
+                        }
+                    }
+                    
+                    // Check for incomplete boundary match, writing out the chars we matched along the way
+                    if((b>0&&b<byteBoundary.length-2)||(b==byteBoundary.length-1))
+                    {
+                        if(cr)
+                            part.write(13);
+
+                        if(lf)
+                            part.write(10);
+
+                        cr=lf=false;
+                        part.write(byteBoundary,0,b);
+                        b=-1;
+                    }
+                    
+                    // Boundary match. If we've run out of input or we matched the entire final boundary marker, then this is the last part.
+                    if(b>0||c==-1)
+                    {
+                       
+                        if(b==byteBoundary.length)
+                            lastPart=true;
+                        if(state==10)
+                            state=-2;
+                        break;
+                    }
+                    
+                    // handle CR LF
+                    if(cr)
+                        part.write(13);
+
+                    if(lf)
+                        part.write(10);
+
+                    cr=(c==13);
+                    lf=(c==10||state==10);
+                    if(state==10)
+                        state=-2;
+                }
+            }
+            finally
+            {
+
+                part.close();
+            }
+        }
+        if (!lastPart)
+            throw new IOException("Incomplete parts");
+    }
+    
+    public void setDeleteOnExit(boolean deleteOnExit)
+    {
+        _deleteOnExit = deleteOnExit;
+    }
+
+
+    public boolean isDeleteOnExit()
+    {
+        return _deleteOnExit;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    private String value(String nameEqualsValue)
+    {
+        int idx = nameEqualsValue.indexOf('=');
+        String value = nameEqualsValue.substring(idx+1).trim();
+        return QuotedStringTokenizer.unquoteOnly(value);
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    private String filenameValue(String nameEqualsValue)
+    {
+        int idx = nameEqualsValue.indexOf('=');
+        String value = nameEqualsValue.substring(idx+1).trim();
+
+        if (value.matches(".??[a-z,A-Z]\\:\\\\[^\\\\].*"))
+        {
+            //incorrectly escaped IE filenames that have the whole path
+            //we just strip any leading & trailing quotes and leave it as is
+            char first=value.charAt(0);
+            if (first=='"' || first=='\'')
+                value=value.substring(1);
+            char last=value.charAt(value.length()-1);
+            if (last=='"' || last=='\'')
+                value = value.substring(0,value.length()-1);
+
+            return value;
+        }
+        else
+            //unquote the string, but allow any backslashes that don't
+            //form a valid escape sequence to remain as many browsers
+            //even on *nix systems will not escape a filename containing
+            //backslashes
+            return QuotedStringTokenizer.unquoteOnly(value, true);
+    }
+
+    
+
+    private static class Base64InputStream extends InputStream
+    {
+        ReadLineInputStream _in;
+        String _line;
+        byte[] _buffer;
+        int _pos;
+
+    
+        public Base64InputStream(ReadLineInputStream rlis)
+        {
+            _in = rlis;
+        }
+
+        @Override
+        public int read() throws IOException
+        {
+            if (_buffer==null || _pos>= _buffer.length)
+            {
+                //Any CR and LF will be consumed by the readLine() call.
+                //We need to put them back into the bytes returned from this
+                //method because the parsing of the multipart content uses them
+                //as markers to determine when we've reached the end of a part.
+                _line = _in.readLine(); 
+                if (_line==null)
+                    return -1;  //nothing left
+                if (_line.startsWith("--"))
+                    _buffer=(_line+"\r\n").getBytes(); //boundary marking end of part
+                else if (_line.length()==0)
+                    _buffer="\r\n".getBytes(); //blank line
+                else
+                {
+                    ByteArrayOutputStream baos = new ByteArrayOutputStream((4*_line.length()/3)+2);
+                    B64Code.decode(_line, baos);
+                    baos.write(13);
+                    baos.write(10);
+                    _buffer = baos.toByteArray();
+                }
+
+                _pos=0;
+            }
+            
+            return _buffer[_pos++];
+        }
+    }
+}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/MultiPartOutputStream.java b/jetty-util/src/main/java/org/eclipse/jetty/util/MultiPartOutputStream.java
index 85bfa34..ddf9850 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/MultiPartOutputStream.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/MultiPartOutputStream.java
@@ -21,6 +21,7 @@ package org.eclipse.jetty.util;
 import java.io.FilterOutputStream;
 import java.io.IOException;
 import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
 
 
 /* ================================================================ */
@@ -35,12 +36,12 @@ public class MultiPartOutputStream extends FilterOutputStream
     private static final byte[] __CRLF={'\r','\n'};
     private static final byte[] __DASHDASH={'-','-'};
     
-    public static String MULTIPART_MIXED="multipart/mixed";
-    public static String MULTIPART_X_MIXED_REPLACE="multipart/x-mixed-replace";
+    public static final String MULTIPART_MIXED="multipart/mixed";
+    public static final String MULTIPART_X_MIXED_REPLACE="multipart/x-mixed-replace";
     
     /* ------------------------------------------------------------ */
-    private String boundary;
-    private byte[] boundaryBytes;
+    private final String boundary;
+    private final byte[] boundaryBytes;
 
     /* ------------------------------------------------------------ */
     private boolean inPart=false;    
@@ -53,12 +54,17 @@ public class MultiPartOutputStream extends FilterOutputStream
 
         boundary = "jetty"+System.identityHashCode(this)+
         Long.toString(System.currentTimeMillis(),36);
-        boundaryBytes=boundary.getBytes(StringUtil.__ISO_8859_1);
-
-        inPart=false;
+        boundaryBytes=boundary.getBytes(StandardCharsets.ISO_8859_1);
     }
 
-    
+    public MultiPartOutputStream(OutputStream out, String boundary)
+         throws IOException
+    {
+        super(out);
+
+        this.boundary = boundary;
+        boundaryBytes=boundary.getBytes(StandardCharsets.ISO_8859_1);
+    }
 
     /* ------------------------------------------------------------ */
     /** End the current part.
@@ -68,14 +74,20 @@ public class MultiPartOutputStream extends FilterOutputStream
     public void close()
          throws IOException
     {
-        if (inPart)
+        try
+        {
+            if (inPart)
+                out.write(__CRLF);
+            out.write(__DASHDASH);
+            out.write(boundaryBytes);
+            out.write(__DASHDASH);
             out.write(__CRLF);
-        out.write(__DASHDASH);
-        out.write(boundaryBytes);
-        out.write(__DASHDASH);
-        out.write(__CRLF);
-        inPart=false;
-        super.close();
+            inPart=false;
+        }
+        finally
+        {
+            super.close();
+        }
     }
     
     /* ------------------------------------------------------------ */
@@ -99,7 +111,7 @@ public class MultiPartOutputStream extends FilterOutputStream
         out.write(boundaryBytes);
         out.write(__CRLF);
         if (contentType != null)
-            out.write(("Content-Type: "+contentType).getBytes(StringUtil.__ISO_8859_1));
+            out.write(("Content-Type: "+contentType).getBytes(StandardCharsets.ISO_8859_1));
         out.write(__CRLF);
         out.write(__CRLF);
     }
@@ -117,16 +129,16 @@ public class MultiPartOutputStream extends FilterOutputStream
         out.write(boundaryBytes);
         out.write(__CRLF);
         if (contentType != null)
-            out.write(("Content-Type: "+contentType).getBytes(StringUtil.__ISO_8859_1));
+            out.write(("Content-Type: "+contentType).getBytes(StandardCharsets.ISO_8859_1));
         out.write(__CRLF);
         for (int i=0;headers!=null && i<headers.length;i++)
         {
-            out.write(headers[i].getBytes(StringUtil.__ISO_8859_1));
+            out.write(headers[i].getBytes(StandardCharsets.ISO_8859_1));
             out.write(__CRLF);
         }
         out.write(__CRLF);
     }
-    
+
     /* ------------------------------------------------------------ */
     @Override
     public void write(byte[] b, int off, int len) throws IOException
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/MultiPartWriter.java b/jetty-util/src/main/java/org/eclipse/jetty/util/MultiPartWriter.java
index ee1d5f7..f0654f5 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/MultiPartWriter.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/MultiPartWriter.java
@@ -35,8 +35,8 @@ public class MultiPartWriter extends FilterWriter
     private final static String __CRLF="\015\012";
     private final static String __DASHDASH="--";
     
-    public static String MULTIPART_MIXED=MultiPartOutputStream.MULTIPART_MIXED;
-    public static String MULTIPART_X_MIXED_REPLACE=MultiPartOutputStream.MULTIPART_X_MIXED_REPLACE;
+    public static final String MULTIPART_MIXED=MultiPartOutputStream.MULTIPART_MIXED;
+    public static final String MULTIPART_X_MIXED_REPLACE=MultiPartOutputStream.MULTIPART_X_MIXED_REPLACE;
     
     /* ------------------------------------------------------------ */
     private String boundary;
@@ -63,14 +63,20 @@ public class MultiPartWriter extends FilterWriter
     public void close()
          throws IOException
     {
-        if (inPart)
+        try
+        {
+            if (inPart)
+                out.write(__CRLF);
+            out.write(__DASHDASH);
+            out.write(boundary);
+            out.write(__DASHDASH);
             out.write(__CRLF);
-        out.write(__DASHDASH);
-        out.write(boundary);
-        out.write(__DASHDASH);
-        out.write(__CRLF);
-        inPart=false;
-        super.close();
+            inPart=false;
+        }
+        finally
+        {
+            super.close();
+        }
     }
     
     /* ------------------------------------------------------------ */
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/Promise.java b/jetty-util/src/main/java/org/eclipse/jetty/util/Promise.java
new file mode 100644
index 0000000..30ed7f1
--- /dev/null
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/Promise.java
@@ -0,0 +1,65 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import org.eclipse.jetty.util.log.Log;
+
+/**
+ * <p>A callback abstraction that handles completed/failed events of asynchronous operations.</p>
+ *
+ * @param <C> the type of the context object
+ */
+public interface Promise<C>
+{
+    /**
+     * <p>Callback invoked when the operation completes.</p>
+     *
+     * @param result the context
+     * @see #failed(Throwable)
+     */
+    public abstract void succeeded(C result);
+
+    /**
+     * <p>Callback invoked when the operation fails.</p>
+     *
+     * @param x the reason for the operation failure
+     */
+    public void failed(Throwable x);
+    
+
+    /**
+     * <p>Empty implementation of {@link Promise}</p>
+     *
+     * @param <C> the type of the context object
+     */
+    public static class Adapter<C> implements Promise<C>
+    {
+        @Override
+        public void succeeded(C result)
+        {
+        }
+
+        @Override
+        public void failed(Throwable x)
+        {
+            Log.getLogger(this.getClass()).warn(x);
+        }
+    }
+
+}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/QuotedStringTokenizer.java b/jetty-util/src/main/java/org/eclipse/jetty/util/QuotedStringTokenizer.java
index e03f230..7436c60 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/QuotedStringTokenizer.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/QuotedStringTokenizer.java
@@ -334,12 +334,44 @@ public class QuotedStringTokenizer
 
     /* ------------------------------------------------------------ */
     /** Quote a string into an Appendable.
+     * Only quotes and backslash are escaped.
+     * @param buffer The Appendable
+     * @param input The String to quote.
+     */
+    public static void quoteOnly(Appendable buffer, String input)
+    {
+        if(input==null)
+            return;
+
+        try
+        {
+            buffer.append('"');
+            for (int i = 0; i < input.length(); ++i)
+            {
+                char c = input.charAt(i);
+                if (c == '"' || c == '\\')
+                    buffer.append('\\');
+                buffer.append(c);
+            }
+            buffer.append('"');
+        }
+        catch (IOException x)
+        {
+            throw new RuntimeException(x);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Quote a string into an Appendable.
      * The characters ", \, \n, \r, \t, \f and \b are escaped
      * @param buffer The Appendable
      * @param input The String to quote.
      */
     public static void quote(Appendable buffer, String input)
     {
+        if(input==null)
+            return;
+
         try
         {
             buffer.append('"');
@@ -377,46 +409,14 @@ public class QuotedStringTokenizer
         }
     }
 
-    /* ------------------------------------------------------------ */
-    /** Quote a string into a StringBuffer only if needed.
-     * Quotes are forced if any delim characters are present.
-     *
-     * @param buf The StringBuffer
-     * @param s The String to quote.
-     * @param delim String of characters that must be quoted.
-     * @return true if quoted;
-     */
-    public static boolean quoteIfNeeded(Appendable buf, String s,String delim)
-    {
-        for (int i=0;i<s.length();i++)
-        {
-            char c = s.charAt(i);
-            if (delim.indexOf(c)>=0)
-            {
-            	quote(buf,s);
-            	return true;
-            }
-        }
 
-        try
-        {
-            buf.append(s);
-            return false;
-        }
-        catch(IOException e)
-        {
-            throw new RuntimeException(e);
-        }
-    }
-    
-    
     /* ------------------------------------------------------------ */
     public static String unquoteOnly(String s)
     {
         return unquoteOnly(s, false);
     }
-    
-    
+
+
     /* ------------------------------------------------------------ */
     /** Unquote a string, NOT converting unicode sequences
      * @param s The string to unquote.
@@ -460,15 +460,15 @@ public class QuotedStringTokenizer
             }
         }
 
-        return b.toString(); 
+        return b.toString();
     }
-    
+
     /* ------------------------------------------------------------ */
     public static String unquote(String s)
     {
         return unquote(s,false);
     }
-    
+
     /* ------------------------------------------------------------ */
     /** Unquote a string.
      * @param s The string to unquote.
@@ -550,8 +550,8 @@ public class QuotedStringTokenizer
 
         return b.toString();
     }
-    
-    
+
+
     /* ------------------------------------------------------------ */
     /** Check that char c (which is preceded by a backslash) is a valid
      * escape sequence.
@@ -560,12 +560,18 @@ public class QuotedStringTokenizer
      */
     private static boolean isValidEscaping(char c)
     {
-        return ((c == 'n') || (c == 'r') || (c == 't') || 
-                 (c == 'f') || (c == 'b') || (c == '\\') || 
+        return ((c == 'n') || (c == 'r') || (c == 't') ||
+                 (c == 'f') || (c == 'b') || (c == '\\') ||
                  (c == '/') || (c == '"') || (c == 'u'));
     }
 
     /* ------------------------------------------------------------ */
+    public static boolean isQuoted(String s)
+    {
+        return s!=null && s.length()>0 && s.charAt(0)=='"' && s.charAt(s.length()-1)=='"';
+    }
+
+    /* ------------------------------------------------------------ */
     /**
      * @return handle double quotes if true
      */
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/ReadLineInputStream.java b/jetty-util/src/main/java/org/eclipse/jetty/util/ReadLineInputStream.java
index 5c042d9..ee23a63 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/ReadLineInputStream.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/ReadLineInputStream.java
@@ -21,6 +21,7 @@ package org.eclipse.jetty.util;
 import java.io.BufferedInputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
 
 /**
  * ReadLineInputStream
@@ -58,7 +59,7 @@ public class ReadLineInputStream extends BufferedInputStream
                 int m=markpos;
                 markpos=-1;
                 if (pos>m)
-                    return new String(buf,m,pos-m,StringUtil.__UTF8_CHARSET);
+                    return new String(buf,m,pos-m, StandardCharsets.UTF_8);
 
                 return null;
             }
@@ -77,7 +78,7 @@ public class ReadLineInputStream extends BufferedInputStream
                     _skipLF=true;
                 int m=markpos;
                 markpos=-1;
-                return new String(buf,m,p-m-1,StringUtil.__UTF8_CHARSET);
+                return new String(buf,m,p-m-1,StandardCharsets.UTF_8);
             }
             
             if (b=='\n')
@@ -91,7 +92,7 @@ public class ReadLineInputStream extends BufferedInputStream
                 }
                 int m=markpos;
                 markpos=-1;
-                return new String(buf,m,pos-m-1,StringUtil.__UTF8_CHARSET);
+                return new String(buf,m,pos-m-1,StandardCharsets.UTF_8);
             }
         }
     }
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/Scanner.java b/jetty-util/src/main/java/org/eclipse/jetty/util/Scanner.java
index 42bce62..fe974ea 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/Scanner.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/Scanner.java
@@ -149,7 +149,7 @@ public class Scanner extends AbstractLifeCycle
      * Get the scan interval
      * @return interval between scans in seconds
      */
-    public int getScanInterval()
+    public synchronized int getScanInterval()
     {
         return _scanInterval;
     }
@@ -164,29 +164,6 @@ public class Scanner extends AbstractLifeCycle
         schedule();
     }
 
-    /**
-     * Set the location of the directory to scan.
-     * @param dir
-     * @deprecated use setScanDirs(List dirs) instead
-     */
-    @Deprecated
-    public void setScanDir (File dir)
-    {
-        _scanDirs.clear(); 
-        _scanDirs.add(dir);
-    }
-
-    /**
-     * Get the location of the directory to scan
-     * @return the first directory (of {@link #getScanDirs()} being scanned)
-     * @deprecated use getScanDirs() instead
-     */
-    @Deprecated
-    public File getScanDir ()
-    {
-        return (_scanDirs==null?null:(File)_scanDirs.get(0));
-    }
-
     public void setScanDirs (List<File> dirs)
     {
         _scanDirs.clear(); 
@@ -305,8 +282,7 @@ public class Scanner extends AbstractLifeCycle
         _listeners.add(listener);   
     }
 
-
-
+    /* ------------------------------------------------------------ */
     /**
      * Remove a registered listener
      * @param listener the Listener to be removed
@@ -394,6 +370,18 @@ public class Scanner extends AbstractLifeCycle
     }
 
     /**
+     * @return true if the path exists in one of the scandirs
+     */
+    public boolean exists(String path)
+    {
+        for (File dir : _scanDirs)
+            if (new File(dir,path).exists())
+                return true;
+        return false;
+    }
+    
+    
+    /**
      * Perform a pass of the scanner and report changes
      */
     public synchronized void scan ()
@@ -569,8 +557,15 @@ public class Scanner extends AbstractLifeCycle
             {
                 if ((_filter == null) || ((_filter != null) && _filter.accept(f.getParentFile(), f.getName())))
                 {
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("scan accepted {}",f);
                     String name = f.getCanonicalPath();
-                    scanInfoMap.put(name, new TimeNSize(f.lastModified(),f.length()));
+                    scanInfoMap.put(name, new TimeNSize(f.lastModified(),f.isDirectory()?0:f.length()));
+                }
+                else
+                {
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("scan rejected {}",f);
                 }
             }
             
@@ -585,7 +580,6 @@ public class Scanner extends AbstractLifeCycle
                 }
                 else
                     LOG.warn("Error listing files in directory {}", f);
-                    
             }
         }
         catch (IOException e)
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/SharedBlockingCallback.java b/jetty-util/src/main/java/org/eclipse/jetty/util/SharedBlockingCallback.java
new file mode 100644
index 0000000..01525b7
--- /dev/null
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/SharedBlockingCallback.java
@@ -0,0 +1,302 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.ReentrantLock;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.NonBlockingThread;
+
+
+/* ------------------------------------------------------------ */
+/** Provides a reusable BlockingCallback.
+ * A typical usage pattern is:
+ * <pre>
+ * void someBlockingCall(Object... args) throws IOException
+ * {
+ *   try(Blocker blocker=sharedBlockingCallback.acquire())
+ *   {
+ *     someAsyncCall(args,blocker);
+ *     blocker.block();
+ *   }
+ * }
+ * </pre>
+ */
+public class SharedBlockingCallback
+{
+    static final Logger LOG = Log.getLogger(SharedBlockingCallback.class);
+
+    final ReentrantLock _lock = new ReentrantLock();
+    final Condition _idle = _lock.newCondition();
+    final Condition _complete = _lock.newCondition();
+
+    
+    private static Throwable IDLE = new Throwable()
+    {
+        @Override
+        public String toString()
+        {
+            return "IDLE";
+        }
+    };
+
+    private static Throwable SUCCEEDED = new Throwable()
+    {
+        @Override
+        public String toString()
+        {
+            return "SUCCEEDED";
+        }
+    };
+    
+    private static Throwable FAILED = new Throwable()
+    {
+        @Override
+        public String toString()
+        {
+            return "FAILED";
+        }
+    };
+
+    Blocker _blocker;
+    
+    public SharedBlockingCallback()
+    {
+        _blocker=new Blocker();
+    }
+    
+    protected long getIdleTimeout()
+    {
+        return -1;
+    }
+    
+    public Blocker acquire() throws IOException
+    {
+        _lock.lock();
+        long idle = getIdleTimeout();
+        try
+        {
+            while (_blocker._state != IDLE)
+            {
+                if (idle>0 && (idle < Long.MAX_VALUE/2))
+                {
+                    // Wait a little bit longer than the blocker might block
+                    if (!_idle.await(idle*2,TimeUnit.MILLISECONDS))
+                        throw new IOException(new TimeoutException());
+                }
+                else
+                    _idle.await();
+            }
+            _blocker._state = null;
+        }
+        catch (final InterruptedException e)
+        {
+            throw new InterruptedIOException();
+        }
+        finally
+        {
+            _lock.unlock();
+        }
+        return _blocker;
+    }
+
+    protected void notComplete(Blocker blocker)
+    {
+        LOG.warn("Blocker not complete {}",blocker);
+        if (LOG.isDebugEnabled())
+            LOG.debug(new Throwable());
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** A Closeable Callback.
+     * Uses the auto close mechanism to check block has been called OK.
+     */
+    public class Blocker implements Callback, Closeable
+    {
+        Throwable _state = IDLE;
+        
+        protected Blocker()
+        {
+        }
+
+        @Override
+        public void succeeded()
+        {
+            _lock.lock();
+            try
+            {
+                if (_state == null)
+                {
+                    _state = SUCCEEDED;
+                    _complete.signalAll();
+                }
+                else
+                    throw new IllegalStateException(_state);
+            }
+            finally
+            {
+                _lock.unlock();
+            }
+        }
+
+        @Override
+        public void failed(Throwable cause)
+        {
+            _lock.lock();
+            try
+            {
+                if (_state == null)
+                {
+                    if (cause==null)
+                        _state=FAILED;
+                    else if (cause instanceof BlockerTimeoutException)
+                        // Not this blockers timeout
+                        _state=new IOException(cause);
+                    else 
+                        _state=cause;
+                    _complete.signalAll();
+                }
+                else 
+                    throw new IllegalStateException(_state);
+            }
+            finally
+            {
+                _lock.unlock();
+            }
+        }
+
+        /**
+         * Block until the Callback has succeeded or failed and after the return leave in the state to allow reuse. This is useful for code that wants to
+         * repeatable use a FutureCallback to convert an asynchronous API to a blocking API.
+         * 
+         * @throws IOException
+         *             if exception was caught during blocking, or callback was cancelled
+         */
+        public void block() throws IOException
+        {
+            if (NonBlockingThread.isNonBlockingThread())
+                LOG.warn("Blocking a NonBlockingThread: ",new Throwable());
+            
+            _lock.lock();
+            long idle = getIdleTimeout();
+            try
+            {
+                while (_state == null)
+                {
+                    if (idle>0 && (idle < Long.MAX_VALUE/2))
+                    {
+                        // Wait a little bit longer than expected callback idle timeout
+                        if (!_complete.await(idle+idle/2,TimeUnit.MILLISECONDS))
+                            // The callback has not arrived in sufficient time.
+                            // We will synthesize a TimeoutException 
+                            _state=new BlockerTimeoutException();
+                    }
+                    else
+                        _complete.await();
+                }
+
+                if (_state == SUCCEEDED)
+                    return;
+                if (_state == IDLE)
+                    throw new IllegalStateException("IDLE");
+                if (_state instanceof IOException)
+                    throw (IOException)_state;
+                if (_state instanceof CancellationException)
+                    throw (CancellationException)_state;
+                if (_state instanceof RuntimeException)
+                    throw (RuntimeException)_state;
+                if (_state instanceof Error)
+                    throw (Error)_state;
+                throw new IOException(_state);
+            }
+            catch (final InterruptedException e)
+            {
+                throw new InterruptedIOException();
+            }
+            finally
+            {
+                _lock.unlock();
+            }
+        }
+        
+        /**
+         * Check the Callback has succeeded or failed and after the return leave in the state to allow reuse.
+         * 
+         * @throws IOException
+         *             if exception was caught during blocking, or callback was cancelled
+         */
+        @Override
+        public void close() throws IOException
+        {
+            _lock.lock();
+            try
+            {
+                if (_state == IDLE)
+                    throw new IllegalStateException("IDLE");
+                if (_state == null)
+                    notComplete(this);
+            }
+            finally
+            {
+                try 
+                {
+                    // If the blocker timed itself out, remember the state
+                    if (_state instanceof BlockerTimeoutException)
+                        // and create a new Blocker
+                        _blocker=new Blocker();
+                    else
+                        // else reuse Blocker
+                        _state = IDLE;
+                    _idle.signalAll();
+                    _complete.signalAll();
+                } 
+                finally 
+                {
+                    _lock.unlock();
+                }
+            }
+        }
+
+        @Override
+        public String toString()
+        {
+            _lock.lock();
+            try
+            {
+                return String.format("%s@%x{%s}",Blocker.class.getSimpleName(),hashCode(),_state);
+            }
+            finally
+            {
+                _lock.unlock();
+            }
+        }
+    }
+    
+    private static class BlockerTimeoutException extends TimeoutException
+    { 
+    }
+}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/SocketAddressResolver.java b/jetty-util/src/main/java/org/eclipse/jetty/util/SocketAddressResolver.java
new file mode 100644
index 0000000..62ad1cf
--- /dev/null
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/SocketAddressResolver.java
@@ -0,0 +1,176 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.nio.channels.UnresolvedAddressException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+/**
+ * Creates asynchronously {@link SocketAddress} instances, returning them through a {@link Promise},
+ * in order to avoid blocking on DNS lookup.
+ * <p />
+ * {@link InetSocketAddress#InetSocketAddress(String, int)} attempts to perform a DNS resolution of
+ * the host name, and this may block for several seconds.
+ * This class creates the {@link InetSocketAddress} in a separate thread and provides the result
+ * through a {@link Promise}, with the possibility to specify a timeout for the operation.
+ * <p />
+ * Example usage:
+ * <pre>
+ * SocketAddressResolver resolver = new SocketAddressResolver(executor, scheduler);
+ * resolver.resolve("www.google.com", 80, new Promise<SocketAddress>()
+ * {
+ *     public void succeeded(SocketAddress result)
+ *     {
+ *         // The address was resolved
+ *     }
+ *
+ *     public void failed(Throwable failure)
+ *     {
+ *         // The address resolution failed
+ *     }
+ * });
+ * </pre>
+ */
+public class SocketAddressResolver
+{
+    private static final Logger LOG = Log.getLogger(SocketAddressResolver.class);
+
+    private final Executor executor;
+    private final Scheduler scheduler;
+    private final long timeout;
+
+    /**
+     * Creates a new instance with the given executor (to perform DNS resolution in a separate thread),
+     * the given scheduler (to cancel the operation if it takes too long) and the given timeout, in milliseconds.
+     *
+     * @param executor the thread pool to use to perform DNS resolution in pooled threads
+     * @param scheduler the scheduler to schedule tasks to cancel DNS resolution if it takes too long
+     * @param timeout the timeout, in milliseconds, for the DNS resolution to complete
+     */
+    public SocketAddressResolver(Executor executor, Scheduler scheduler, long timeout)
+    {
+        this.executor = executor;
+        this.scheduler = scheduler;
+        this.timeout = timeout;
+    }
+
+    public Executor getExecutor()
+    {
+        return executor;
+    }
+
+    public Scheduler getScheduler()
+    {
+        return scheduler;
+    }
+
+    public long getTimeout()
+    {
+        return timeout;
+    }
+
+    /**
+     * Resolves the given host and port, returning a {@link SocketAddress} through the given {@link Promise}
+     * with the default timeout.
+     *
+     * @param host the host to resolve
+     * @param port the port of the resulting socket address
+     * @param promise the callback invoked when the resolution succeeds or fails
+     * @see #resolve(String, int, long, Promise)
+     */
+    public void resolve(String host, int port, Promise<SocketAddress> promise)
+    {
+        resolve(host, port, timeout, promise);
+    }
+
+    /**
+     * Resolves the given host and port, returning a {@link SocketAddress} through the given {@link Promise}
+     * with the given timeout.
+     *
+     * @param host the host to resolve
+     * @param port the port of the resulting socket address
+     * @param timeout the timeout, in milliseconds, for the DNS resolution to complete
+     * @param promise the callback invoked when the resolution succeeds or fails
+     */
+    protected void resolve(final String host, final int port, final long timeout, final Promise<SocketAddress> promise)
+    {
+        executor.execute(new Runnable()
+        {
+            @Override
+            public void run()
+            {
+                Scheduler.Task task = null;
+                final AtomicBoolean complete = new AtomicBoolean();
+                if (timeout > 0)
+                {
+                    final Thread thread = Thread.currentThread();
+                    task = scheduler.schedule(new Runnable()
+                    {
+                        @Override
+                        public void run()
+                        {
+                            if (complete.compareAndSet(false, true))
+                            {
+                                promise.failed(new TimeoutException());
+                                thread.interrupt();
+                            }
+                        }
+                    }, timeout, TimeUnit.MILLISECONDS);
+                }
+
+                try
+                {
+                    long start = System.nanoTime();
+                    InetSocketAddress result = new InetSocketAddress(host, port);
+                    long elapsed = System.nanoTime() - start;
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("Resolved {} in {} ms", host, TimeUnit.NANOSECONDS.toMillis(elapsed));
+                    if (complete.compareAndSet(false, true))
+                    {
+                        if (result.isUnresolved())
+                            promise.failed(new UnresolvedAddressException());
+                        else
+                            promise.succeeded(result);
+                    }
+                }
+                catch (Throwable x)
+                {
+                    if (complete.compareAndSet(false, true))
+                        promise.failed(x);
+                }
+                finally
+                {
+                    if (task != null)
+                        task.cancel();
+                    // Reset the interrupted status before releasing the thread to the pool
+                    Thread.interrupted();
+                }
+            }
+        });
+    }
+}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/StringMap.java b/jetty-util/src/main/java/org/eclipse/jetty/util/StringMap.java
deleted file mode 100644
index 2fd9538..0000000
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/StringMap.java
+++ /dev/null
@@ -1,695 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.util;
-
-import java.io.Externalizable;
-import java.util.AbstractMap;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
-/* ------------------------------------------------------------ */
-/** Map implementation Optimized for Strings keys..
- * This String Map has been optimized for mapping small sets of
- * Strings where the most frequently accessed Strings have been put to
- * the map first.
- *
- * It also has the benefit that it can look up entries by substring or
- * sections of char and byte arrays.  This can prevent many String
- * objects from being created just to look up in the map.
- *
- * This map is NOT synchronized.
- */
-public class StringMap extends AbstractMap implements Externalizable
-{
-    public static final boolean CASE_INSENSTIVE=true;
-    protected static final int __HASH_WIDTH=17;
-    
-    /* ------------------------------------------------------------ */
-    protected int _width=__HASH_WIDTH;
-    protected Node _root=new Node();
-    protected boolean _ignoreCase=false;
-    protected NullEntry _nullEntry=null;
-    protected Object _nullValue=null;
-    protected HashSet _entrySet=new HashSet(3);
-    protected Set _umEntrySet=Collections.unmodifiableSet(_entrySet);
-    
-    /* ------------------------------------------------------------ */
-    /** Constructor. 
-     */
-    public StringMap()
-    {}
-    
-    /* ------------------------------------------------------------ */
-    /** Constructor. 
-     * @param ignoreCase 
-     */
-    public StringMap(boolean ignoreCase)
-    {
-        this();
-        _ignoreCase=ignoreCase;
-    }
-    
-    /* ------------------------------------------------------------ */
-    /** Constructor. 
-     * @param ignoreCase 
-     * @param width Width of hash tables, larger values are faster but
-     * use more memory.
-     */
-    public StringMap(boolean ignoreCase,int width)
-    {
-        this();
-        _ignoreCase=ignoreCase;
-        _width=width;
-    }
-    
-    /* ------------------------------------------------------------ */
-    /** Set the ignoreCase attribute.
-     * @param ic If true, the map is case insensitive for keys.
-     */
-    public void setIgnoreCase(boolean ic)
-    {
-        if (_root._children!=null)
-            throw new IllegalStateException("Must be set before first put");
-        _ignoreCase=ic;
-    }
-
-    /* ------------------------------------------------------------ */
-    public boolean isIgnoreCase()
-    {
-        return _ignoreCase;
-    }
-
-    /* ------------------------------------------------------------ */
-    /** Set the hash width.
-     * @param width Width of hash tables, larger values are faster but
-     * use more memory.
-     */
-    public void setWidth(int width)
-    {
-        _width=width;
-    }
-
-    /* ------------------------------------------------------------ */
-    public int getWidth()
-    {
-        return _width;
-    }
-    
-    /* ------------------------------------------------------------ */
-    @Override
-    public Object put(Object key, Object value)
-    {
-        if (key==null)
-            return put(null,value);
-        return put(key.toString(),value);
-    }
-        
-    /* ------------------------------------------------------------ */
-    public Object put(String key, Object value)
-    {
-        if (key==null)
-        {
-            Object oldValue=_nullValue;
-            _nullValue=value;
-            if (_nullEntry==null)
-            {   
-                _nullEntry=new NullEntry();
-                _entrySet.add(_nullEntry);
-            }
-            return oldValue;
-        }
-        
-        Node node = _root;
-        int ni=-1;
-        Node prev = null;
-        Node parent = null;
-
-        // look for best match
-    charLoop:
-        for (int i=0;i<key.length();i++)
-        {
-            char c=key.charAt(i);
-            
-            // Advance node
-            if (ni==-1)
-            {
-                parent=node;
-                prev=null;
-                ni=0;
-                node=(node._children==null)?null:node._children[c%_width];
-            }
-            
-            // Loop through a node chain at the same level
-            while (node!=null) 
-            {
-                // If it is a matching node, goto next char
-                if (node._char[ni]==c || _ignoreCase&&node._ochar[ni]==c)
-                {
-                    prev=null;
-                    ni++;
-                    if (ni==node._char.length)
-                        ni=-1;
-                    continue charLoop;
-                }
-
-                // no char match
-                // if the first char,
-                if (ni==0)
-                {
-                    // look along the chain for a char match
-                    prev=node;
-                    node=node._next;
-                }
-                else
-                {
-                    // Split the current node!
-                    node.split(this,ni);
-                    i--;
-                    ni=-1;
-                    continue charLoop;
-                }
-            }
-
-            // We have run out of nodes, so as this is a put, make one
-            node = new Node(_ignoreCase,key,i);
-
-            if (prev!=null) // add to end of chain
-                prev._next=node;
-            else if (parent!=null) // add new child
-            {
-                if (parent._children==null)
-                    parent._children=new Node[_width];
-                parent._children[c%_width]=node;
-                int oi=node._ochar[0]%_width;
-                if (node._ochar!=null && node._char[0]%_width!=oi)
-                {
-                    if (parent._children[oi]==null)
-                        parent._children[oi]=node;
-                    else
-                    {
-                        Node n=parent._children[oi];
-                        while(n._next!=null)
-                            n=n._next;
-                        n._next=node;
-                    }
-                }
-            }
-            else // this is the root.
-                _root=node;
-            break;
-        }
-        
-        // Do we have a node
-        if (node!=null)
-        {
-            // Split it if we are in the middle
-            if(ni>0)
-                node.split(this,ni);
-        
-            Object old = node._value;
-            node._key=key;
-            node._value=value;
-            _entrySet.add(node);
-            return old;
-        }
-        return null;
-    }
-    
-    /* ------------------------------------------------------------ */
-    @Override
-    public Object get(Object key)
-    {
-        if (key==null)
-            return _nullValue;
-        if (key instanceof String)
-            return get((String)key);
-        return get(key.toString());
-    }
-    
-    /* ------------------------------------------------------------ */
-    public Object get(String key)
-    {
-        if (key==null)
-            return _nullValue;
-        
-        Map.Entry entry = getEntry(key,0,key.length());
-        if (entry==null)
-            return null;
-        return entry.getValue();
-    }
-    
-    /* ------------------------------------------------------------ */
-    /** Get a map entry by substring key.
-     * @param key String containing the key
-     * @param offset Offset of the key within the String.
-     * @param length The length of the key 
-     * @return The Map.Entry for the key or null if the key is not in
-     * the map.
-     */
-    public Map.Entry getEntry(String key,int offset, int length)
-    {
-        if (key==null)
-            return _nullEntry;
-        
-        Node node = _root;
-        int ni=-1;
-
-        // look for best match
-    charLoop:
-        for (int i=0;i<length;i++)
-        {
-            char c=key.charAt(offset+i);
-
-            // Advance node
-            if (ni==-1)
-            {
-                ni=0;
-                node=(node._children==null)?null:node._children[c%_width];
-            }
-            
-            // Look through the node chain
-            while (node!=null) 
-            {
-                // If it is a matching node, goto next char
-                if (node._char[ni]==c || _ignoreCase&&node._ochar[ni]==c)
-                {
-                    ni++;
-                    if (ni==node._char.length)
-                        ni=-1;
-                    continue charLoop;
-                }
-
-                // No char match, so if mid node then no match at all.
-                if (ni>0) return null;
-
-                // try next in chain
-                node=node._next;                
-            }
-            return null;
-        }
-        
-        if (ni>0) return null;
-        if (node!=null && node._key==null)
-            return null;
-        return node;
-    }
-    
-    /* ------------------------------------------------------------ */
-    /** Get a map entry by char array key.
-     * @param key char array containing the key
-     * @param offset Offset of the key within the array.
-     * @param length The length of the key 
-     * @return The Map.Entry for the key or null if the key is not in
-     * the map.
-     */
-    public Map.Entry getEntry(char[] key,int offset, int length)
-    {
-        if (key==null)
-            return _nullEntry;
-        
-        Node node = _root;
-        int ni=-1;
-
-        // look for best match
-    charLoop:
-        for (int i=0;i<length;i++)
-        {
-            char c=key[offset+i];
-
-            // Advance node
-            if (ni==-1)
-            {
-                ni=0;
-                node=(node._children==null)?null:node._children[c%_width];
-            }
-            
-            // While we have a node to try
-            while (node!=null) 
-            {
-                // If it is a matching node, goto next char
-                if (node._char[ni]==c || _ignoreCase&&node._ochar[ni]==c)
-                {
-                    ni++;
-                    if (ni==node._char.length)
-                        ni=-1;
-                    continue charLoop;
-                }
-
-                // No char match, so if mid node then no match at all.
-                if (ni>0) return null;
-
-                // try next in chain
-                node=node._next;                
-            }
-            return null;
-        }
-        
-        if (ni>0) return null;
-        if (node!=null && node._key==null)
-            return null;
-        return node;
-    }
-
-    /* ------------------------------------------------------------ */
-    /** Get a map entry by byte array key, using as much of the passed key as needed for a match.
-     * A simple 8859-1 byte to char mapping is assumed.
-     * @param key char array containing the key
-     * @param offset Offset of the key within the array.
-     * @param maxLength The length of the key 
-     * @return The Map.Entry for the key or null if the key is not in
-     * the map.
-     */
-    public Map.Entry getBestEntry(byte[] key,int offset, int maxLength)
-    {
-        if (key==null)
-            return _nullEntry;
-        
-        Node node = _root;
-        int ni=-1;
-
-        // look for best match
-    charLoop:
-        for (int i=0;i<maxLength;i++)
-        {
-            char c=(char)key[offset+i];
-
-            // Advance node
-            if (ni==-1)
-            {
-                ni=0;
-                
-                Node child = (node._children==null)?null:node._children[c%_width];
-                
-                if (child==null && i>0)
-                    return node; // This is the best match
-                node=child;           
-            }
-            
-            // While we have a node to try
-            while (node!=null) 
-            {
-                // If it is a matching node, goto next char
-                if (node._char[ni]==c || _ignoreCase&&node._ochar[ni]==c)
-                {
-                    ni++;
-                    if (ni==node._char.length)
-                        ni=-1;
-                    continue charLoop;
-                }
-
-                // No char match, so if mid node then no match at all.
-                if (ni>0) return null;
-
-                // try next in chain
-                node=node._next;                
-            }
-            return null;
-        }
-        
-        if (ni>0) return null;
-        if (node!=null && node._key==null)
-            return null;
-        return node;
-    }
-    
-    
-    /* ------------------------------------------------------------ */
-    @Override
-    public Object remove(Object key)
-    {
-        if (key==null)
-            return remove(null);
-        return remove(key.toString());
-    }
-    
-    /* ------------------------------------------------------------ */
-    public Object remove(String key)
-    {
-        if (key==null)
-        {
-            Object oldValue=_nullValue;
-            if (_nullEntry!=null)
-            {
-                _entrySet.remove(_nullEntry);   
-                _nullEntry=null;
-                _nullValue=null;
-            }
-            return oldValue;
-        }
-        
-        Node node = _root;
-        int ni=-1;
-
-        // look for best match
-    charLoop:
-        for (int i=0;i<key.length();i++)
-        {
-            char c=key.charAt(i);
-
-            // Advance node
-            if (ni==-1)
-            {
-                ni=0;
-                node=(node._children==null)?null:node._children[c%_width];
-            }
-            
-            // While we have a node to try
-            while (node!=null) 
-            {
-                // If it is a matching node, goto next char
-                if (node._char[ni]==c || _ignoreCase&&node._ochar[ni]==c)
-                {
-                    ni++;
-                    if (ni==node._char.length)
-                        ni=-1;
-                    continue charLoop;
-                }
-
-                // No char match, so if mid node then no match at all.
-                if (ni>0) return null;
-
-                // try next in chain
-                node=node._next;         
-            }
-            return null;
-        }
-
-        if (ni>0) return null;
-        if (node!=null && node._key==null)
-            return null;
-        
-        Object old = node._value;
-        _entrySet.remove(node);
-        node._value=null;
-        node._key=null;
-        
-        return old; 
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public Set entrySet()
-    {
-        return _umEntrySet;
-    }
-    
-    /* ------------------------------------------------------------ */
-    @Override
-    public int size()
-    {
-        return _entrySet.size();
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public boolean isEmpty()
-    {
-        return _entrySet.isEmpty();
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public boolean containsKey(Object key)
-    {
-        if (key==null)
-            return _nullEntry!=null;
-        return
-            getEntry(key.toString(),0,key==null?0:key.toString().length())!=null;
-    }
-    
-    /* ------------------------------------------------------------ */
-    @Override
-    public void clear()
-    {
-        _root=new Node();
-        _nullEntry=null;
-        _nullValue=null;
-        _entrySet.clear();
-    }
-
-    
-    /* ------------------------------------------------------------ */
-    /* ------------------------------------------------------------ */
-    /* ------------------------------------------------------------ */
-    private static class Node implements Map.Entry
-    {
-        char[] _char;
-        char[] _ochar;
-        Node _next;
-        Node[] _children;
-        String _key;
-        Object _value;
-        
-        Node(){}
-        
-        Node(boolean ignoreCase,String s, int offset)
-        {
-            int l=s.length()-offset;
-            _char=new char[l];
-            _ochar=new char[l];
-            for (int i=0;i<l;i++)
-            {
-                char c=s.charAt(offset+i);
-                _char[i]=c;
-                if (ignoreCase)
-                {
-                    char o=c;
-                    if (Character.isUpperCase(c))
-                        o=Character.toLowerCase(c);
-                    else if (Character.isLowerCase(c))
-                        o=Character.toUpperCase(c);
-                    _ochar[i]=o;
-                }
-            }
-        }
-
-        Node split(StringMap map,int offset)
-        {
-            Node split = new Node();
-            int sl=_char.length-offset;
-            
-            char[] tmp=this._char;
-            this._char=new char[offset];
-            split._char = new char[sl];
-            System.arraycopy(tmp,0,this._char,0,offset);
-            System.arraycopy(tmp,offset,split._char,0,sl);
-
-            if (this._ochar!=null)
-            {
-                tmp=this._ochar;
-                this._ochar=new char[offset];
-                split._ochar = new char[sl];
-                System.arraycopy(tmp,0,this._ochar,0,offset);
-                System.arraycopy(tmp,offset,split._ochar,0,sl);
-            }
-            
-            split._key=this._key;
-            split._value=this._value;
-            this._key=null;
-            this._value=null;
-            if (map._entrySet.remove(this))
-                map._entrySet.add(split);
-
-            split._children=this._children;            
-            this._children=new Node[map._width];
-            this._children[split._char[0]%map._width]=split;
-            if (split._ochar!=null && this._children[split._ochar[0]%map._width]!=split)
-                this._children[split._ochar[0]%map._width]=split;
-
-            return split;
-        }
-        
-        public Object getKey(){return _key;}
-        public Object getValue(){return _value;}
-        public Object setValue(Object o){Object old=_value;_value=o;return old;}
-        @Override
-        public String toString()
-        {
-            StringBuilder buf=new StringBuilder();
-            toString(buf);
-            return buf.toString();
-        }
-
-        private void toString(StringBuilder buf)
-        {
-            buf.append("{[");
-            if (_char==null)
-                buf.append('-');
-            else
-                for (int i=0;i<_char.length;i++)
-                    buf.append(_char[i]);
-            buf.append(':');
-            buf.append(_key);
-            buf.append('=');
-            buf.append(_value);
-            buf.append(']');
-            if (_children!=null)
-            {
-                for (int i=0;i<_children.length;i++)
-                {
-                    buf.append('|');
-                    if (_children[i]!=null)
-                        _children[i].toString(buf);
-                    else
-                        buf.append("-");
-                }
-            }
-            buf.append('}');
-            if (_next!=null)
-            {
-                buf.append(",\n");
-                _next.toString(buf);
-            }
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    /* ------------------------------------------------------------ */
-    private class NullEntry implements Map.Entry
-    {
-        public Object getKey(){return null;}
-        public Object getValue(){return _nullValue;}
-        public Object setValue(Object o)
-            {Object old=_nullValue;_nullValue=o;return old;}
-        @Override
-        public String toString(){return "[:null="+_nullValue+"]";}
-    }
-
-    /* ------------------------------------------------------------ */
-    public void writeExternal(java.io.ObjectOutput out)
-        throws java.io.IOException
-    {
-        HashMap map = new HashMap(this);
-        out.writeBoolean(_ignoreCase);
-        out.writeObject(map);
-    }
-    
-    /* ------------------------------------------------------------ */
-    public void readExternal(java.io.ObjectInput in)
-        throws java.io.IOException, ClassNotFoundException
-    {
-        boolean ic=in.readBoolean();
-        HashMap map = (HashMap)in.readObject();
-        setIgnoreCase(ic);
-        this.putAll(map);
-    }
-}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/StringUtil.java b/jetty-util/src/main/java/org/eclipse/jetty/util/StringUtil.java
index c49b0d1..e82b8c9 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/StringUtil.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/StringUtil.java
@@ -20,13 +20,14 @@ package org.eclipse.jetty.util;
 
 import java.io.UnsupportedEncodingException;
 import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
 
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
 
 /** Fast String Utilities.
  *
- * These string utilities provide both conveniance methods and
+ * These string utilities provide both convenience methods and
  * performance improvements over most standard library versions. The
  * main aim of the optimizations is to avoid object creation unless
  * absolutely required.
@@ -37,26 +38,74 @@ public class StringUtil
 {
     private static final Logger LOG = Log.getLogger(StringUtil.class);
     
+    
+    private final static Trie<String> CHARSETS= new ArrayTrie<>(256);
+    
     public static final String ALL_INTERFACES="0.0.0.0";
     public static final String CRLF="\015\012";
-    public static final String __LINE_SEPARATOR=
-        System.getProperty("line.separator","\n");
+    
+    /** @deprecated use {@link System#lineSeparator()} instead */
+    @Deprecated
+    public static final String __LINE_SEPARATOR = System.lineSeparator();
        
     public static final String __ISO_8859_1="ISO-8859-1";
     public final static String __UTF8="UTF-8";
-    public final static String __UTF8Alt="UTF8";
     public final static String __UTF16="UTF-16";
-    
-    public final static Charset __UTF8_CHARSET;
-    public final static Charset __ISO_8859_1_CHARSET;
+
+    /**
+     * @deprecated Use {@link StandardCharsets#UTF_8}
+     */
+    @Deprecated
+    public final static Charset __UTF8_CHARSET=StandardCharsets.UTF_8;
+    /**
+     * @deprecated Use {@link StandardCharsets#ISO_8859_1}
+     */
+    @Deprecated
+    public final static Charset __ISO_8859_1_CHARSET=StandardCharsets.ISO_8859_1;
+    /**
+     * @deprecated Use {@link StandardCharsets#UTF_16}
+     */
+    @Deprecated
+    public final static Charset __UTF16_CHARSET=StandardCharsets.UTF_16;
+    /**
+     * @deprecated Use {@link StandardCharsets#US_ASCII}
+     */
+    @Deprecated
+    public final static Charset __US_ASCII_CHARSET=StandardCharsets.US_ASCII;
     
     static
     {
-        __UTF8_CHARSET=Charset.forName(__UTF8);
-        __ISO_8859_1_CHARSET=Charset.forName(__ISO_8859_1);
+        CHARSETS.put("UTF-8",__UTF8);
+        CHARSETS.put("UTF8",__UTF8);
+        CHARSETS.put("UTF-16",__UTF16);
+        CHARSETS.put("UTF16",__UTF16);
+        CHARSETS.put("ISO-8859-1",__ISO_8859_1);
+        CHARSETS.put("ISO_8859_1",__ISO_8859_1);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Convert alternate charset names (eg utf8) to normalized
+     * name (eg UTF-8).
+     */
+    public static String normalizeCharset(String s)
+    {
+        String n=CHARSETS.get(s);
+        return (n==null)?s:n;
     }
     
-    private static char[] lowercases = {
+    /* ------------------------------------------------------------ */
+    /** Convert alternate charset names (eg utf8) to normalized
+     * name (eg UTF-8).
+     */
+    public static String normalizeCharset(String s,int offset,int length)
+    {
+        String n=CHARSETS.get(s,offset,length);       
+        return (n==null)?s.substring(offset,offset+length):n;
+    }
+    
+
+    /* ------------------------------------------------------------ */
+    public static final char[] lowercases = {
           '\000','\001','\002','\003','\004','\005','\006','\007',
           '\010','\011','\012','\013','\014','\015','\016','\017',
           '\020','\021','\022','\023','\024','\025','\026','\027',
@@ -307,9 +356,15 @@ public class StringUtil
     /* ------------------------------------------------------------ */
     public static String toUTF8String(byte[] b,int offset,int length)
     {
+        return new String(b,offset,length,StandardCharsets.UTF_8);
+    }
+
+    /* ------------------------------------------------------------ */
+    public static String toString(byte[] b,int offset,int length,String charset)
+    {
         try
         {
-            return new String(b,offset,length,__UTF8);
+            return new String(b,offset,length,charset);
         }
         catch (UnsupportedEncodingException e)
         {
@@ -318,23 +373,89 @@ public class StringUtil
     }
 
     /* ------------------------------------------------------------ */
-    public static String toString(byte[] b,int offset,int length,String charset)
+    /**
+     * Test if a string is null or only has whitespace characters in it.
+     * <p>
+     * Note: uses codepoint version of {@link Character#isWhitespace(int)} to support Unicode better.
+     * 
+     * <pre>
+     *   isBlank(null)   == true
+     *   isBlank("")     == true
+     *   isBlank("\r\n") == true
+     *   isBlank("\t")   == true
+     *   isBlank("   ")  == true
+     *   isBlank("a")    == false
+     *   isBlank(".")    == false
+     *   isBlank(";\n")  == false
+     * </pre>
+     * 
+     * @param str
+     *            the string to test.
+     * @return true if string is null or only whitespace characters, false if non-whitespace characters encountered.
+     */
+    public static boolean isBlank(String str)
     {
-        try
+        if (str == null)
         {
-            return new String(b,offset,length,charset);
+            return true;
         }
-        catch (UnsupportedEncodingException e)
+        int len = str.length();
+        for (int i = 0; i < len; i++)
         {
-            throw new IllegalArgumentException(e);
+            if (!Character.isWhitespace(str.codePointAt(i)))
+            {
+                // found a non-whitespace, we can stop searching  now
+                return false;
+            }
         }
+        // only whitespace
+        return true;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Test if a string is not null and contains at least 1 non-whitespace characters in it.
+     * <p>
+     * Note: uses codepoint version of {@link Character#isWhitespace(int)} to support Unicode better.
+     * 
+     * <pre>
+     *   isNotBlank(null)   == false
+     *   isNotBlank("")     == false
+     *   isNotBlank("\r\n") == false
+     *   isNotBlank("\t")   == false
+     *   isNotBlank("   ")  == false
+     *   isNotBlank("a")    == true
+     *   isNotBlank(".")    == true
+     *   isNotBlank(";\n")  == true
+     * </pre>
+     * 
+     * @param str
+     *            the string to test.
+     * @return true if string is not null and has at least 1 non-whitespace character, false if null or all-whitespace characters.
+     */
+    public static boolean isNotBlank(String str)
+    {
+        if (str == null)
+        {
+            return false;
+        }
+        int len = str.length();
+        for (int i = 0; i < len; i++)
+        {
+            if (!Character.isWhitespace(str.codePointAt(i)))
+            {
+                // found a non-whitespace, we can stop searching  now
+                return true;
+            }
+        }
+        // only whitespace
+        return false;
     }
-
 
     /* ------------------------------------------------------------ */
     public static boolean isUTF8(String charset)
     {
-        return __UTF8.equalsIgnoreCase(charset)||__UTF8Alt.equalsIgnoreCase(charset);
+        return __UTF8.equalsIgnoreCase(charset)||__UTF8.equalsIgnoreCase(normalizeCharset(charset));
     }
 
 
@@ -373,15 +494,12 @@ public class StringUtil
     
     public static byte[] getBytes(String s)
     {
-        try
-        {
-            return s.getBytes(__ISO_8859_1);
-        }
-        catch(Exception e)
-        {
-            LOG.warn(e);
-            return s.getBytes();
-        }
+        return s.getBytes(StandardCharsets.ISO_8859_1);
+    }
+    
+    public static byte[] getUtf8Bytes(String s)
+    {
+        return s.getBytes(StandardCharsets.UTF_8);
     }
     
     public static byte[] getBytes(String s,String charset)
@@ -501,4 +619,187 @@ public class StringUtil
       
         return sidBytes;
     }
+    
+
+    /**
+     * Convert String to an integer. Parses up to the first non-numeric character. If no number is found an IllegalArgumentException is thrown
+     * 
+     * @param string
+     *            A String containing an integer.
+     * @return an int
+     */
+    public static int toInt(String string)
+    {
+        int val = 0;
+        boolean started = false;
+        boolean minus = false;
+
+        for (int i = 0; i < string.length(); i++)
+        {
+            char b = string.charAt(i);
+            if (b <= ' ')
+            {
+                if (started)
+                    break;
+            }
+            else if (b >= '0' && b <= '9')
+            {
+                val = val * 10 + (b - '0');
+                started = true;
+            }
+            else if (b == '-' && !started)
+            {
+                minus = true;
+            }
+            else
+                break;
+        }
+
+        if (started)
+            return minus?(-val):val;
+        throw new NumberFormatException(string);
+    }
+
+    /**
+     * Convert String to an long. Parses up to the first non-numeric character. If no number is found an IllegalArgumentException is thrown
+     * 
+     * @param string
+     *            A String containing an integer.
+     * @return an int
+     */
+    public static long toLong(String string)
+    {
+        long val = 0;
+        boolean started = false;
+        boolean minus = false;
+
+        for (int i = 0; i < string.length(); i++)
+        {
+            char b = string.charAt(i);
+            if (b <= ' ')
+            {
+                if (started)
+                    break;
+            }
+            else if (b >= '0' && b <= '9')
+            {
+                val = val * 10L + (b - '0');
+                started = true;
+            }
+            else if (b == '-' && !started)
+            {
+                minus = true;
+            }
+            else
+                break;
+        }
+
+        if (started)
+            return minus?(-val):val;
+        throw new NumberFormatException(string);
+    }
+    
+    /**
+     * Truncate a string to a max size.
+     * 
+     * @param str the string to possibly truncate
+     * @param maxSize the maximum size of the string
+     * @return the truncated string.  if <code>str</code> param is null, then the returned string will also be null.
+     */
+    public static String truncate(String str, int maxSize)
+    {
+        if (str == null)
+        {
+            return null;
+        }
+
+        if (str.length() <= maxSize)
+        {
+            return str;
+        }
+
+        return str.substring(0,maxSize);
+    }
+
+    public static String[] arrayFromString(String s) 
+    {
+        if (s==null)
+            return new String[]{};
+
+        if (!s.startsWith("[") || !s.endsWith("]"))
+            throw new IllegalArgumentException();
+        if (s.length()==2)
+            return new String[]{};
+
+        return s.substring(1,s.length()-1).split(" *, *");
+    }
+    
+    public static String sanitizeXmlString(String html)
+    {
+        if (html==null)
+            return null;
+        
+        int i=0;
+        
+        // Are there any characters that need sanitizing?
+        loop: for (;i<html.length();i++)
+        {
+            char c=html.charAt(i);
+
+            switch(c)
+            {
+                case '&' :
+                case '<' :
+                case '>' :
+                case '\'':
+                case '"':
+                    break loop;
+
+                default:
+                    if (Character.isISOControl(c) && !Character.isWhitespace(c))
+                        break loop;
+            }
+        }
+
+        // No characters need sanitizing, so return original string
+        if (i==html.length())
+            return html;
+        
+        // Create builder with OK content so far 
+        StringBuilder out = new StringBuilder(html.length()*4/3);
+        out.append(html,0,i);
+        
+        // sanitize remaining content
+        for (;i<html.length();i++)
+        {
+            char c=html.charAt(i);
+
+            switch(c)
+            {
+                case '&' :
+                    out.append("&");
+                    break;
+                case '<' :
+                    out.append("<");
+                    break;
+                case '>' :
+                    out.append(">");
+                    break;
+                case '\'':
+                    out.append("'");
+                    break;
+                case '"':
+                    out.append(""");
+                    break;
+
+                default:
+                    if (Character.isISOControl(c) && !Character.isWhitespace(c))
+                        out.append('?');
+                    else
+                        out.append(c);
+            }
+        }
+        return out.toString();
+    }
+
 }
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/TreeTrie.java b/jetty-util/src/main/java/org/eclipse/jetty/util/TreeTrie.java
new file mode 100644
index 0000000..9114f33
--- /dev/null
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/TreeTrie.java
@@ -0,0 +1,358 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/* ------------------------------------------------------------ */
+/** A Trie String lookup data structure using a tree
+ * <p>This implementation is always case insensitive and is optimal for
+ * a variable number of fixed strings with few special characters.
+ * </p>
+ * <p>This Trie is stored in a Tree and is unlimited in capacity</p>
+ * 
+ * <p>This Trie is not Threadsafe and contains no mutual exclusion 
+ * or deliberate memory barriers.  It is intended for an ArrayTrie to be
+ * built by a single thread and then used concurrently by multiple threads
+ * and not mutated during that access.  If concurrent mutations of the
+ * Trie is required external locks need to be applied.
+ * </p>
+ * 
+ * @param <V>
+ */
+public class TreeTrie<V> extends AbstractTrie<V>
+{
+    private static final int[] __lookup = 
+    { // 0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F
+   /*0*/-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, 
+   /*2*/31, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 26, -1, 27, 30, -1,
+   /*3*/-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 28, 29, -1, -1, -1, -1,
+   /*4*/-1,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,
+   /*5*/15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
+   /*6*/-1,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,
+   /*7*/15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
+    };
+    private static final int INDEX = 32;
+    private final TreeTrie<V>[]  _nextIndex;
+    private final List<TreeTrie<V>> _nextOther=new ArrayList<>();
+    private final char _c;
+    private String _key;
+    private V _value;
+
+    public TreeTrie()
+    {
+        super(true);
+        _nextIndex = new TreeTrie[INDEX];
+        _c=0;
+    }
+    
+    private TreeTrie(char c)
+    {
+        super(true);
+        _nextIndex = new TreeTrie[INDEX];
+        this._c=c;
+    }
+
+    @Override
+    public boolean put(String s, V v)
+    {
+        TreeTrie<V> t = this;
+        int limit = s.length();
+        for(int k=0; k < limit; k++)
+        {
+            char c=s.charAt(k);
+            
+            int index=c>=0&&c<0x7f?__lookup[c]:-1;
+            if (index>=0)
+            {
+                if (t._nextIndex[index] == null)
+                    t._nextIndex[index] = new TreeTrie<V>(c);
+                t = t._nextIndex[index];
+            }
+            else
+            {
+                TreeTrie<V> n=null;
+                for (int i=t._nextOther.size();i-->0;)
+                {
+                    n=t._nextOther.get(i);
+                    if (n._c==c)
+                        break;
+                    n=null;
+                }
+                if (n==null)
+                {
+                    n=new TreeTrie<V>(c);
+                    t._nextOther.add(n);
+                }
+                t=n;
+            }
+        }
+        t._key=v==null?null:s;
+        t._value = v;
+        return true;
+    }
+
+    @Override
+    public V get(String s,int offset, int len)
+    {
+        TreeTrie<V> t = this;
+        for(int i=0; i < len; i++)
+        {
+            char c=s.charAt(offset+i);
+            int index=c>=0&&c<0x7f?__lookup[c]:-1;
+            if (index>=0)
+            {
+                if (t._nextIndex[index] == null) 
+                    return null;
+                t = t._nextIndex[index];
+            }
+            else
+            {
+                TreeTrie<V> n=null;
+                for (int j=t._nextOther.size();j-->0;)
+                {
+                    n=t._nextOther.get(j);
+                    if (n._c==c)
+                        break;
+                    n=null;
+                }
+                if (n==null)
+                    return null;
+                t=n;
+            }
+        }
+        return t._value;
+    }
+
+    @Override
+    public V get(ByteBuffer b,int offset,int len)
+    {
+        TreeTrie<V> t = this;
+        for(int i=0; i < len; i++)
+        {
+            byte c=b.get(offset+i);
+            int index=c>=0&&c<0x7f?__lookup[c]:-1;
+            if (index>=0)
+            {
+                if (t._nextIndex[index] == null) 
+                    return null;
+                t = t._nextIndex[index];
+            }
+            else
+            {
+                TreeTrie<V> n=null;
+                for (int j=t._nextOther.size();j-->0;)
+                {
+                    n=t._nextOther.get(j);
+                    if (n._c==c)
+                        break;
+                    n=null;
+                }
+                if (n==null)
+                    return null;
+                t=n;
+            }
+        }
+        return t._value;
+    }
+
+    @Override
+    public V getBest(byte[] b,int offset,int len)
+    {
+        TreeTrie<V> t = this;
+        for(int i=0; i < len; i++)
+        {
+            byte c=b[offset+i];
+            int index=c>=0&&c<0x7f?__lookup[c]:-1;
+            if (index>=0)
+            {
+                if (t._nextIndex[index] == null) 
+                    break;
+                t = t._nextIndex[index];
+            }
+            else
+            {
+                TreeTrie<V> n=null;
+                for (int j=t._nextOther.size();j-->0;)
+                {
+                    n=t._nextOther.get(j);
+                    if (n._c==c)
+                        break;
+                    n=null;
+                }
+                if (n==null)
+                    break;
+                t=n;
+            }
+            
+            // Is the next Trie is a match
+            if (t._key!=null)
+            {
+                // Recurse so we can remember this possibility
+                V best=t.getBest(b,offset+i+1,len-i-1);
+                if (best!=null)
+                    return best;
+                break;
+            }
+        }
+        return t._value;
+    }
+
+    @Override
+    public V getBest(String s, int offset, int len)
+    {
+        // TODO inefficient
+        byte[] b=s.substring(offset,offset+len).getBytes(StandardCharsets.ISO_8859_1);
+        return getBest(b,0,b.length);
+    }
+    
+    @Override
+    public V getBest(ByteBuffer b,int offset,int len)
+    {
+        if (b.hasArray())
+            return getBest(b.array(),b.arrayOffset()+b.position()+offset,len);
+        return getBestByteBuffer(b,offset,len);
+    }
+    
+    private V getBestByteBuffer(ByteBuffer b,int offset,int len)
+    {
+        TreeTrie<V> t = this;
+        int pos=b.position()+offset;
+        for(int i=0; i < len; i++)
+        {
+            byte c=b.get(pos++);
+            int index=c>=0&&c<0x7f?__lookup[c]:-1;
+            if (index>=0)
+            {
+                if (t._nextIndex[index] == null) 
+                    break;
+                t = t._nextIndex[index];
+            }
+            else
+            {
+                TreeTrie<V> n=null;
+                for (int j=t._nextOther.size();j-->0;)
+                {
+                    n=t._nextOther.get(j);
+                    if (n._c==c)
+                        break;
+                    n=null;
+                }
+                if (n==null)
+                    break;
+                t=n;
+            }
+            
+            // Is the next Trie is a match
+            if (t._key!=null)
+            {
+                // Recurse so we can remember this possibility
+                V best=t.getBest(b,offset+i+1,len-i-1);
+                if (best!=null)
+                    return best;
+                break;
+            }
+        }
+        return t._value;
+    }
+    
+
+    @Override
+    public String toString()
+    {
+        StringBuilder buf = new StringBuilder();
+        toString(buf,this);
+        
+        if (buf.length()==0)
+            return "{}";
+        
+        buf.setCharAt(0,'{');
+        buf.append('}');
+        return buf.toString();
+    }
+
+    private static <V> void toString(Appendable out, TreeTrie<V> t)
+    {
+        if (t != null)
+        {
+            if (t._value!=null)
+            {
+                try
+                {
+                    out.append(',');
+                    out.append(t._key);
+                    out.append('=');
+                    out.append(t._value.toString());
+                }
+                catch (IOException e)
+                {
+                    throw new RuntimeException(e);
+                }
+            }
+           
+            for(int i=0; i < INDEX; i++)
+            {
+                if (t._nextIndex[i] != null)
+                    toString(out,t._nextIndex[i]);
+            }
+            for (int i=t._nextOther.size();i-->0;)
+                toString(out,t._nextOther.get(i));
+        }           
+    }
+
+    @Override
+    public Set<String> keySet()
+    {
+        Set<String> keys = new HashSet<>();
+        keySet(keys,this);
+        return keys;
+    }
+    
+    private static <V> void keySet(Set<String> set, TreeTrie<V> t)
+    {
+        if (t != null)
+        {
+            if (t._key!=null)
+                set.add(t._key);
+           
+            for(int i=0; i < INDEX; i++)
+            {
+                if (t._nextIndex[i] != null)
+                    keySet(set,t._nextIndex[i]);
+            }
+            for (int i=t._nextOther.size();i-->0;)
+                keySet(set,t._nextOther.get(i));
+        }           
+    }
+    
+    @Override
+    public boolean isFull()
+    {
+        return false;
+    }
+
+
+}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/Trie.java b/jetty-util/src/main/java/org/eclipse/jetty/util/Trie.java
new file mode 100644
index 0000000..131e610
--- /dev/null
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/Trie.java
@@ -0,0 +1,124 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.nio.ByteBuffer;
+import java.util.Set;
+
+
+/* ------------------------------------------------------------ */
+/** A Trie String lookup data structure.
+ * @param <V>
+ */
+public interface Trie<V>
+{
+    /* ------------------------------------------------------------ */
+    /** Put and entry into the Trie
+     * @param s The key for the entry
+     * @param v The value of the entry
+     * @return True if the Trie had capacity to add the field.
+     */
+    public boolean put(String s, V v);
+    
+    /* ------------------------------------------------------------ */
+    /** Put a value as both a key and a value.
+     * @param v The value and key
+     * @return True if the Trie had capacity to add the field.
+     */
+    public boolean put(V v);
+
+    /* ------------------------------------------------------------ */
+    public V remove(String s);
+
+    /* ------------------------------------------------------------ */
+    /** Get and exact match from a String key
+     * @param s The key
+     */
+    public V get(String s);
+
+    /* ------------------------------------------------------------ */
+    /** Get and exact match from a String key
+     * @param s The key
+     * @param offset The offset within the string of the key
+     * @param len the length of the key
+     */
+    public V get(String s,int offset,int len);
+
+    /* ------------------------------------------------------------ */
+    /** Get and exact match from a segment of a ByteBuufer as key
+     * @param b The buffer
+     * @return The value or null if not found
+     */
+    public V get(ByteBuffer b);
+
+    /* ------------------------------------------------------------ */
+    /** Get and exact match from a segment of a ByteBuufer as key
+     * @param b The buffer
+     * @param offset The offset within the buffer of the key
+     * @param len the length of the key
+     * @return The value or null if not found
+     */
+    public V get(ByteBuffer b,int offset,int len);
+    
+    /* ------------------------------------------------------------ */
+    /** Get the best match from key in a String.
+     * @param s The string
+     * @return The value or null if not found
+     */
+    public V getBest(String s);
+    
+    /* ------------------------------------------------------------ */
+    /** Get the best match from key in a String.
+     * @param s The string
+     * @param offset The offset within the string of the key
+     * @param len the length of the key
+     * @return The value or null if not found
+     */
+    public V getBest(String s,int offset,int len); 
+
+    /* ------------------------------------------------------------ */
+    /** Get the best match from key in a byte array.
+     * The key is assumed to by ISO_8859_1 characters.
+     * @param b The buffer
+     * @param offset The offset within the array of the key
+     * @param len the length of the key
+     * @return The value or null if not found
+     */
+    public V getBest(byte[] b,int offset,int len);
+
+    /* ------------------------------------------------------------ */
+    /** Get the best match from key in a byte buffer.
+     * The key is assumed to by ISO_8859_1 characters.
+     * @param b The buffer
+     * @param offset The offset within the buffer of the key
+     * @param len the length of the key
+     * @return The value or null if not found
+     */
+    public V getBest(ByteBuffer b,int offset,int len);
+    
+    /* ------------------------------------------------------------ */
+    public Set<String> keySet();
+
+    /* ------------------------------------------------------------ */
+    public boolean isFull();
+
+    /* ------------------------------------------------------------ */
+    public boolean isCaseInsensitive();
+
+}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/TypeUtil.java b/jetty-util/src/main/java/org/eclipse/jetty/util/TypeUtil.java
index 6b774f3..fc7a1a3 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/TypeUtil.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/TypeUtil.java
@@ -19,17 +19,18 @@
 package org.eclipse.jetty.util;
 
 import java.io.IOException;
-import java.io.InputStream;
+import java.lang.annotation.Annotation;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
-import java.net.URL;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
+import org.eclipse.jetty.util.annotation.Name;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
 
@@ -45,11 +46,12 @@ import org.eclipse.jetty.util.log.Logger;
 public class TypeUtil
 {
     private static final Logger LOG = Log.getLogger(TypeUtil.class);
-    public static int CR = '\015';
-    public static int LF = '\012';
+    public static final Class<?>[] NO_ARGS = new Class[]{};
+    public static final int CR = '\015';
+    public static final int LF = '\012';
 
     /* ------------------------------------------------------------ */
-    private static final HashMap<String, Class<?>> name2Class=new HashMap<String, Class<?>>();
+    private static final HashMap<String, Class<?>> name2Class=new HashMap<>();
     static
     {
         name2Class.put("boolean",java.lang.Boolean.TYPE);
@@ -97,7 +99,7 @@ public class TypeUtil
     }
 
     /* ------------------------------------------------------------ */
-    private static final HashMap<Class<?>, String> class2Name=new HashMap<Class<?>, String>();
+    private static final HashMap<Class<?>, String> class2Name=new HashMap<>();
     static
     {
         class2Name.put(java.lang.Boolean.TYPE,"boolean");
@@ -124,7 +126,7 @@ public class TypeUtil
     }
 
     /* ------------------------------------------------------------ */
-    private static final HashMap<Class<?>, Method> class2Value=new HashMap<Class<?>, Method>();
+    private static final HashMap<Class<?>, Method> class2Value=new HashMap<>();
     static
     {
         try
@@ -173,13 +175,13 @@ public class TypeUtil
      * Works like {@link Arrays#asList(Object...)}, but handles null arrays.
      * @return a list backed by the array.
      */
-    public static <T> List<T> asList(T[] a) 
+    public static <T> List<T> asList(T[] a)
     {
         if (a==null)
             return Collections.emptyList();
         return Arrays.asList(a);
     }
-    
+
     /* ------------------------------------------------------------ */
     /** Class from a canonical name for a type.
      * @param name A class or type name.
@@ -219,28 +221,20 @@ public class TypeUtil
 
             if (type.equals(java.lang.Character.TYPE) ||
                 type.equals(java.lang.Character.class))
-                return new Character(value.charAt(0));
+                return value.charAt(0);
 
             Constructor<?> c = type.getConstructor(java.lang.String.class);
             return c.newInstance(value);
         }
-        catch(NoSuchMethodException e)
+        catch (NoSuchMethodException | IllegalAccessException | InstantiationException x)
         {
-            // LogSupport.ignore(log,e);
+            LOG.ignore(x);
         }
-        catch(IllegalAccessException e)
+        catch (InvocationTargetException x)
         {
-            // LogSupport.ignore(log,e);
-        }
-        catch(InstantiationException e)
-        {
-            // LogSupport.ignore(log,e);
-        }
-        catch(InvocationTargetException e)
-        {
-            if (e.getTargetException() instanceof Error)
-                throw (Error)(e.getTargetException());
-            // LogSupport.ignore(log,e);
+            if (x.getTargetException() instanceof Error)
+                throw (Error)x.getTargetException();
+            LOG.ignore(x);
         }
         return null;
     }
@@ -359,7 +353,7 @@ public class TypeUtil
     {
         byte b = (byte)((c & 0x1f) + ((c >> 6) * 0x19) - 0x10);
         if (b<0 || b>15)
-            throw new IllegalArgumentException("!hex "+c);
+            throw new NumberFormatException("!hex "+c);
         return b;
     }
     
@@ -428,7 +422,7 @@ public class TypeUtil
     {
         return toHexString(new byte[]{b}, 0, 1);
     }
-    
+
     /* ------------------------------------------------------------ */
     public static String toHexString(byte[] b)
     {
@@ -486,108 +480,172 @@ public class TypeUtil
     }
 
 
-    /* ------------------------------------------------------------ */
-    /**
-     * @deprecated
-     */
-    public static byte[] readLine(InputStream in) throws IOException
+    public static Object call(Class<?> oClass, String methodName, Object obj, Object[] arg)
+       throws InvocationTargetException, NoSuchMethodException
     {
-        byte[] buf = new byte[256];
-
-        int i=0;
-        int loops=0;
-        int ch=0;
-
-        while (true)
+        // Lets just try all methods for now
+        for (Method method : oClass.getMethods())
         {
-            ch=in.read();
-            if (ch<0)
-                break;
-            loops++;
-
-            // skip a leading LF's
-            if (loops==1 && ch==LF)
+            if (!method.getName().equals(methodName))
+                continue;            
+            if (method.getParameterTypes().length != arg.length)
+                continue;
+            if (Modifier.isStatic(method.getModifiers()) != (obj == null))
+                continue;
+            if ((obj == null) && method.getDeclaringClass() != oClass)
                 continue;
 
-            if (ch==CR || ch==LF)
-                break;
-
-            if (i>=buf.length)
+            try
             {
-                byte[] old_buf=buf;
-                buf=new byte[old_buf.length+256];
-                System.arraycopy(old_buf, 0, buf, 0, old_buf.length);
+                return method.invoke(obj, arg);
+            }
+            catch (IllegalAccessException | IllegalArgumentException e)
+            {
+                LOG.ignore(e);
             }
-            buf[i++]=(byte)ch;
         }
-
-        if (ch==-1 && i==0)
-            return null;
-
-        // skip a trailing LF if it exists
-        if (ch==CR && in.available()>=1 && in.markSupported())
+        
+        // Lets look for a method with optional arguments
+        Object[] args_with_opts=null;
+        
+        for (Method method : oClass.getMethods())
         {
-            in.mark(1);
-            ch=in.read();
-            if (ch!=LF)
-                in.reset();
-        }
-
-        byte[] old_buf=buf;
-        buf=new byte[i];
-        System.arraycopy(old_buf, 0, buf, 0, i);
+            if (!method.getName().equals(methodName))
+                continue;            
+            if (method.getParameterTypes().length != arg.length+1)
+                continue;
+            if (!method.getParameterTypes()[arg.length].isArray())
+                continue;
+            if (Modifier.isStatic(method.getModifiers()) != (obj == null))
+                continue;
+            if ((obj == null) && method.getDeclaringClass() != oClass)
+                continue;
 
-        return buf;
+            if (args_with_opts==null)
+                args_with_opts=ArrayUtil.addToArray(arg,new Object[]{},Object.class);
+            try
+            {
+                return method.invoke(obj, args_with_opts);
+            }
+            catch (IllegalAccessException | IllegalArgumentException e)
+            {
+                LOG.ignore(e);
+            }
+        }
+        
+        
+        throw new NoSuchMethodException(methodName);
     }
 
-    public static URL jarFor(String className)
+    public static Object construct(Class<?> klass, Object[] arguments) throws InvocationTargetException, NoSuchMethodException
     {
-        try
+        for (Constructor<?> constructor : klass.getConstructors())
         {
-            className=className.replace('.','/')+".class";
-            // hack to discover jstl libraries
-            URL url = Loader.getResource(null,className,false);
-            String s=url.toString();
-            if (s.startsWith("jar:file:"))
-                return new URL(s.substring(4,s.indexOf("!/")));
-        }
-        catch(Exception e)
-        {
-            LOG.ignore(e);
+            if (constructor.getParameterTypes().length != arguments.length)
+                continue;
+
+            try
+            {
+                return constructor.newInstance(arguments);
+            }
+            catch (InstantiationException | IllegalAccessException | IllegalArgumentException e)
+            {
+                LOG.ignore(e);
+            }
         }
-        return null;
+        throw new NoSuchMethodException("<init>");
     }
     
-    public static Object call(Class<?> oClass, String method, Object obj, Object[] arg) 
-       throws InvocationTargetException, NoSuchMethodException
+    public static Object construct(Class<?> klass, Object[] arguments, Map<String, Object> namedArgMap) throws InvocationTargetException, NoSuchMethodException
     {
-        // Lets just try all methods for now
-        Method[] methods = oClass.getMethods();
-        for (int c = 0; methods != null && c < methods.length; c++)
+        for (Constructor<?> constructor : klass.getConstructors())
         {
-            if (!methods[c].getName().equals(method))
-                continue;
-            if (methods[c].getParameterTypes().length != arg.length)
-                continue;
-            if (Modifier.isStatic(methods[c].getModifiers()) != (obj == null))
-                continue;
-            if ((obj == null) && methods[c].getDeclaringClass() != oClass)
+            if (constructor.getParameterTypes().length != arguments.length)
                 continue;
 
             try
             {
-                return methods[c].invoke(obj,arg);
+                Annotation[][] parameterAnnotations = constructor.getParameterAnnotations();
+                
+                // target has no annotations
+                if ( parameterAnnotations == null || parameterAnnotations.length == 0 )
+                {
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("Target has no parameter annotations");
+                    return constructor.newInstance(arguments);
+                }
+                else
+                {
+                   Object[] swizzled = new Object[arguments.length];
+                   
+                   int count = 0;
+                   for ( Annotation[] annotations : parameterAnnotations )
+                   {
+                       for ( Annotation annotation : annotations)
+                       {
+                           if ( annotation instanceof Name )
+                           {
+                               Name param = (Name)annotation;
+                               
+                               if (namedArgMap.containsKey(param.value()))
+                               {
+                                   if (LOG.isDebugEnabled())
+                                       LOG.debug("placing named {} in position {}", param.value(), count);
+                                   swizzled[count] = namedArgMap.get(param.value());
+                               }
+                               else
+                               {
+                                   if (LOG.isDebugEnabled())
+                                       LOG.debug("placing {} in position {}", arguments[count], count);
+                                   swizzled[count] = arguments[count];
+                               }
+                               ++count;
+                           }
+                           else
+                           {
+                               if (LOG.isDebugEnabled())
+                                   LOG.debug("passing on annotation {}", annotation);
+                           }
+                       }
+                   }
+                   
+                   return constructor.newInstance(swizzled);
+                }
+                
             }
-            catch (IllegalAccessException e)
-            {
-                LOG.ignore(e);
-            }
-            catch (IllegalArgumentException e)
+            catch (InstantiationException | IllegalAccessException | IllegalArgumentException e)
             {
                 LOG.ignore(e);
             }
         }
+        throw new NoSuchMethodException("<init>");
+    }
 
-        throw new NoSuchMethodException(method);
+    /* ------------------------------------------------------------ */
+    /** 
+     * @param o Object to test for true
+     * @return True if passed object is not null and is either a Boolean with value true or evaluates to a string that evaluates to true.
+     */
+    public static boolean isTrue(Object o)
+    {
+        if (o==null)
+            return false;
+        if (o instanceof Boolean)
+            return ((Boolean)o).booleanValue();
+        return Boolean.parseBoolean(o.toString());
+    }
+
+    /* ------------------------------------------------------------ */
+    /** 
+     * @param o Object to test for false
+     * @return True if passed object is not null and is either a Boolean with value false or evaluates to a string that evaluates to false.
+     */
+    public static boolean isFalse(Object o)
+    {
+        if (o==null)
+            return false;
+        if (o instanceof Boolean)
+            return !((Boolean)o).booleanValue();
+        return "false".equalsIgnoreCase(o.toString());
     }
 }
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/URIUtil.java b/jetty-util/src/main/java/org/eclipse/jetty/util/URIUtil.java
index 32292b1..ca5260f 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/URIUtil.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/URIUtil.java
@@ -18,11 +18,8 @@
 
 package org.eclipse.jetty.util;
 
-import java.io.UnsupportedEncodingException;
-import java.net.URI;
-import java.net.URLEncoder;
-
-import org.eclipse.jetty.util.log.Log;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
 
 
 
@@ -47,8 +44,14 @@ public class URIUtil
     public static final String HTTPS_COLON="https:";
 
     // Use UTF-8 as per http://www.w3.org/TR/html40/appendix/notes.html#non-ascii-chars
-    public static final String __CHARSET=System.getProperty("org.eclipse.jetty.util.URI.charset",StringUtil.__UTF8);
-    
+    public static final Charset __CHARSET;
+
+    static
+    {
+        String charset = System.getProperty("org.eclipse.jetty.util.URI.charset");
+        __CHARSET = charset == null ? StandardCharsets.UTF_8 : Charset.forName(charset);
+    }
+
     private URIUtil()
     {}
     
@@ -99,14 +102,7 @@ public class URIUtil
                     default:
                         if (c>127)
                         {
-                            try
-                            {
-                                bytes=path.getBytes(URIUtil.__CHARSET);
-                            }
-                            catch (UnsupportedEncodingException e)
-                            {
-                                throw new IllegalStateException(e);
-                            }
+                            bytes=path.getBytes(URIUtil.__CHARSET);
                             buf=new StringBuilder(path.length()*2);
                             break loop;
                         }
@@ -309,16 +305,7 @@ public class URIUtil
             // Do we have some bytes to convert?
             if (b>0)
             {
-                // convert series of bytes and add to chars
-                String s;
-                try
-                {
-                    s=new String(bytes,0,b,__CHARSET);
-                }
-                catch (UnsupportedEncodingException e)
-                {       
-                    s=new String(bytes,0,b);
-                }
+                String s=new String(bytes,0,b,__CHARSET);
                 s.getChars(0,s.length(),chars,n);
                 n+=s.length();
                 b=0;
@@ -333,16 +320,7 @@ public class URIUtil
         // if we have a remaining sequence of bytes
         if (b>0)
         {
-            // convert series of bytes and add to chars
-            String s;
-            try
-            {
-                s=new String(bytes,0,b,__CHARSET);
-            }
-            catch (UnsupportedEncodingException e)
-            {       
-                s=new String(bytes,0,b);
-            }
+            String s=new String(bytes,0,b,__CHARSET);
             s.getChars(0,s.length(),chars,n);
             n+=s.length();
         }
@@ -391,8 +369,8 @@ public class URIUtil
         }
 
         if (bytes==null)
-            return StringUtil.toString(buf,offset,length,__CHARSET);
-        return StringUtil.toString(bytes,0,n,__CHARSET);
+            return new String(buf,offset,length,__CHARSET);
+        return new String(bytes,0,n,__CHARSET);
     }
 
     
@@ -685,7 +663,141 @@ public class URIUtil
         }
         return false;
     }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Create a new URI from the arguments, handling IPv6 host encoding and default ports
+     * @param scheme
+     * @param server
+     * @param port
+     * @param path
+     * @param query
+     * @return A String URI
+     */
+    public static String newURI(String scheme,String server, int port,String path,String query)
+    {
+        StringBuilder builder = newURIBuilder(scheme, server, port);
+        builder.append(path);
+        if (query!=null && query.length()>0)
+            builder.append('?').append(query);
+        return builder.toString();
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Create a new URI StringBuilder from the arguments, handling IPv6 host encoding and default ports
+     * @param scheme
+     * @param server
+     * @param port
+     * @return a StringBuilder containing URI prefix
+     */
+    public static StringBuilder newURIBuilder(String scheme,String server, int port)
+    {
+        StringBuilder builder = new StringBuilder();
+        appendSchemeHostPort(builder, scheme, server, port);
+        return builder;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** 
+     * Append scheme, host and port URI prefix, handling IPv6 address encoding and default ports</p>
+     * @param url StringBuilder to append to
+     * @param scheme
+     * @param server
+     * @param port
+     */
+    public static void appendSchemeHostPort(StringBuilder url,String scheme,String server, int port)
+    {
+        if (server.indexOf(':')>=0&&server.charAt(0)!='[')
+            url.append(scheme).append("://").append('[').append(server).append(']');
+        else
+            url.append(scheme).append("://").append(server);
+
+        if (port > 0)
+        {
+            switch(scheme)
+            {
+                case "http":
+                    if (port!=80) 
+                        url.append(':').append(port);
+                    break;
+                    
+                case "https":
+                    if (port!=443) 
+                        url.append(':').append(port);
+                    break;
+
+                default:
+                    url.append(':').append(port);
+            }
+        }
+    }
     
+    /* ------------------------------------------------------------ */
+    /** 
+     * Append scheme, host and port URI prefix, handling IPv6 address encoding and default ports</p>
+     * @param url StringBuffer to append to
+     * @param scheme
+     * @param server
+     * @param port
+     */
+    public static void appendSchemeHostPort(StringBuffer url,String scheme,String server, int port)
+    {
+        synchronized (url)
+        {
+            if (server.indexOf(':')>=0&&server.charAt(0)!='[')
+                url.append(scheme).append("://").append('[').append(server).append(']');
+            else
+                url.append(scheme).append("://").append(server);
+
+            if (port > 0)
+            {
+                switch(scheme)
+                {
+                    case "http":
+                        if (port!=80) 
+                            url.append(':').append(port);
+                        break;
+                        
+                    case "https":
+                        if (port!=443) 
+                            url.append(':').append(port);
+                        break;
+
+                    default:
+                        url.append(':').append(port);
+                }
+            }
+        }
+    }
+
+    public static boolean equalsIgnoreEncodings(String uriA, String uriB)
+    {
+        int lenA=uriA.length();
+        int lenB=uriB.length();
+        int a=0;
+        int b=0;
+        
+        while (a<lenA && b<lenB)
+        {
+            int oa=uriA.charAt(a++);
+            int ca=oa;
+            if (ca=='%')
+                ca=TypeUtil.convertHexDigit(uriA.charAt(a++))*16+TypeUtil.convertHexDigit(uriA.charAt(a++));
+            
+            int ob=uriB.charAt(b++);
+            int cb=ob;
+            if (cb=='%')
+                cb=TypeUtil.convertHexDigit(uriB.charAt(b++))*16+TypeUtil.convertHexDigit(uriB.charAt(b++));
+            
+            if (ca=='/' && oa!=ob)
+                return false;
+            
+            if (ca!=cb )
+                return URIUtil.decodePath(uriA).equals(URIUtil.decodePath(uriB));
+        }
+        return a==lenA && b==lenB;
+    }
 }
 
 
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/Uptime.java b/jetty-util/src/main/java/org/eclipse/jetty/util/Uptime.java
new file mode 100644
index 0000000..7de1f4c
--- /dev/null
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/Uptime.java
@@ -0,0 +1,132 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * Provide for a Uptime class that is compatible with Android, GAE, and the new Java 8 compact profiles
+ */
+public class Uptime
+{
+    public static final int NOIMPL = -1;
+
+    public static interface Impl
+    {
+        public long getUptime();
+    }
+
+    public static class DefaultImpl implements Impl
+    {
+        public Object mxBean;
+        public Method uptimeMethod;
+
+        public DefaultImpl()
+        {
+            ClassLoader cl = Thread.currentThread().getContextClassLoader();
+            try
+            {
+                Class<?> mgmtFactory = Class.forName("java.lang.management.ManagementFactory",true,cl);
+                Class<?> runtimeClass = Class.forName("java.lang.management.RuntimeMXBean",true,cl);
+                Class<?> noparams[] = new Class<?>[0];
+                Method mxBeanMethod = mgmtFactory.getMethod("getRuntimeMXBean",noparams);
+                if (mxBeanMethod == null)
+                {
+                    throw new UnsupportedOperationException("method getRuntimeMXBean() not found");
+                }
+                mxBean = mxBeanMethod.invoke(mgmtFactory);
+                if (mxBean == null)
+                {
+                    throw new UnsupportedOperationException("getRuntimeMXBean() method returned null");
+                }
+                uptimeMethod = runtimeClass.getMethod("getUptime",noparams);
+                if (mxBean == null)
+                {
+                    throw new UnsupportedOperationException("method getUptime() not found");
+                }
+            }
+            catch (ClassNotFoundException | 
+                   NoClassDefFoundError | 
+                   NoSuchMethodException | 
+                   SecurityException | 
+                   IllegalAccessException | 
+                   IllegalArgumentException | 
+                   InvocationTargetException e)
+            {
+                throw new UnsupportedOperationException("Implementation not available in this environment",e);
+            }
+        }
+
+        @Override
+        public long getUptime()
+        {
+            try
+            {
+                return (long)uptimeMethod.invoke(mxBean);
+            }
+            catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e)
+            {
+                return NOIMPL;
+            }
+        }
+    }
+
+    private static final Uptime INSTANCE = new Uptime();
+
+    public static Uptime getInstance()
+    {
+        return INSTANCE;
+    }
+
+    private Impl impl;
+
+    private Uptime()
+    {
+        try
+        {
+            impl = new DefaultImpl();
+        }
+        catch (UnsupportedOperationException e)
+        {
+            System.err.printf("Defaulting Uptime to NOIMPL due to (%s) %s%n",e.getClass().getName(),e.getMessage());
+            impl = null;
+        }
+    }
+
+    public Impl getImpl()
+    {
+        return impl;
+    }
+
+    public void setImpl(Impl impl)
+    {
+        this.impl = impl;
+    }
+
+    public static long getUptime()
+    {
+        Uptime u = getInstance();
+        if (u == null || u.impl == null)
+        {
+            return NOIMPL;
+        }
+        return u.impl.getUptime();
+    }
+}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/UrlEncoded.java b/jetty-util/src/main/java/org/eclipse/jetty/util/UrlEncoded.java
index d2d13d2..bad3775 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/UrlEncoded.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/UrlEncoded.java
@@ -24,8 +24,9 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.StringWriter;
-import java.io.UnsupportedEncodingException;
-import java.util.Iterator;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
 import java.util.Map;
 
 import org.eclipse.jetty.util.Utf8Appendable.NotUtf8Exception;
@@ -51,38 +52,44 @@ import org.eclipse.jetty.util.log.Logger;
  *
  * @see java.net.URLEncoder
  */
-public class UrlEncoded extends MultiMap implements Cloneable
+ at SuppressWarnings("serial")
+public class UrlEncoded extends MultiMap<String> implements Cloneable
 {
-    private static final Logger LOG = Log.getLogger(UrlEncoded.class);
+    static final Logger LOG = Log.getLogger(UrlEncoded.class);
 
-    public static final String ENCODING = System.getProperty("org.eclipse.jetty.util.UrlEncoding.charset",StringUtil.__UTF8);
-
-    /* ----------------------------------------------------------------- */
-    public UrlEncoded(UrlEncoded url)
+    public static final Charset ENCODING;
+    static
     {
-        super(url);
+        Charset encoding;
+        try
+        {
+            String charset = System.getProperty("org.eclipse.jetty.util.UrlEncoding.charset");
+            encoding = charset == null ? StandardCharsets.UTF_8 : Charset.forName(charset);
+        }
+        catch(Exception e)
+        {
+            LOG.warn(e);
+            encoding=StandardCharsets.UTF_8;
+        }
+        ENCODING=encoding;
     }
     
     /* ----------------------------------------------------------------- */
-    public UrlEncoded()
+    public UrlEncoded(UrlEncoded url)
     {
-        super(6);
+        super(url);
     }
     
     /* ----------------------------------------------------------------- */
-    public UrlEncoded(String s)
+    public UrlEncoded()
     {
-        super(6);
-        decode(s,ENCODING);
     }
     
-    /* ----------------------------------------------------------------- */
-    public UrlEncoded(String s, String charset)
+    public UrlEncoded(String query)
     {
-        super(6);
-        decode(s,charset);
+        decodeTo(query,this,ENCODING,-1);
     }
-    
+
     /* ----------------------------------------------------------------- */
     public void decode(String query)
     {
@@ -90,7 +97,7 @@ public class UrlEncoded extends MultiMap implements Cloneable
     }
     
     /* ----------------------------------------------------------------- */
-    public void decode(String query,String charset)
+    public void decode(String query,Charset charset)
     {
         decodeTo(query,this,charset,-1);
     }
@@ -106,7 +113,7 @@ public class UrlEncoded extends MultiMap implements Cloneable
     /* -------------------------------------------------------------- */
     /** Encode Hashtable with % encoding.
      */
-    public String encode(String charset)
+    public String encode(Charset charset)
     {
         return encode(charset,false);
     }
@@ -116,7 +123,7 @@ public class UrlEncoded extends MultiMap implements Cloneable
      * @param equalsForNullValue if True, then an '=' is always used, even
      * for parameters without a value. e.g. "blah?a=&b=&c=".
      */
-    public synchronized String encode(String charset, boolean equalsForNullValue)
+    public synchronized String encode(Charset charset, boolean equalsForNullValue)
     {
         return encode(this,charset,equalsForNullValue);
     }
@@ -126,21 +133,24 @@ public class UrlEncoded extends MultiMap implements Cloneable
      * @param equalsForNullValue if True, then an '=' is always used, even
      * for parameters without a value. e.g. "blah?a=&b=&c=".
      */
-    public static String encode(MultiMap map, String charset, boolean equalsForNullValue)
+    public static String encode(MultiMap<String> map, Charset charset, boolean equalsForNullValue)
     {
         if (charset==null)
             charset=ENCODING;
 
         StringBuilder result = new StringBuilder(128);
 
-        Iterator iter = map.entrySet().iterator();
-        while(iter.hasNext())
+        boolean delim = false;
+        for(Map.Entry<String, List<String>> entry: map.entrySet())
         {
-            Map.Entry entry = (Map.Entry)iter.next();
-
             String key = entry.getKey().toString();
-            Object list = entry.getValue();
-            int s=LazyList.size(list);
+            List<String> list = entry.getValue();
+            int s=list.size();
+            
+            if (delim)
+            {
+                result.append('&');
+            }
 
             if (s==0)
             {
@@ -154,7 +164,7 @@ public class UrlEncoded extends MultiMap implements Cloneable
                 {
                     if (i>0)
                         result.append('&');
-                    Object val=LazyList.get(list,i);
+                    String val=list.get(i);
                     result.append(encodeString(key,charset));
 
                     if (val!=null)
@@ -172,28 +182,25 @@ public class UrlEncoded extends MultiMap implements Cloneable
                         result.append('=');
                 }
             }
-            if (iter.hasNext())
-                result.append('&');
+            delim = true;
         }
         return result.toString();
     }
 
-
-
     /* -------------------------------------------------------------- */
     /** Decoded parameters to Map.
      * @param content the string containing the encoded parameters
      */
-    public static void decodeTo(String content, MultiMap map, String charset)
+    public static void decodeTo(String content, MultiMap<String> map, String charset, int maxKeys)
     {
-        decodeTo(content,map,charset,-1);
+        decodeTo(content,map,charset==null?null:Charset.forName(charset),maxKeys);
     }
     
     /* -------------------------------------------------------------- */
     /** Decoded parameters to Map.
      * @param content the string containing the encoded parameters
      */
-    public static void decodeTo(String content, MultiMap map, String charset, int maxKeys)
+    public static void decodeTo(String content, MultiMap<String> map, Charset charset, int maxKeys)
     {
         if (charset==null)
             charset=ENCODING;
@@ -270,27 +277,14 @@ public class UrlEncoded extends MultiMap implements Cloneable
      * @param length the length of the section to decode
      * @param map the {@link MultiMap} to populate
      */
-    public static void decodeUtf8To(byte[] raw,int offset, int length, MultiMap map)
-    {
-        decodeUtf8To(raw,offset,length,map,new Utf8StringBuilder());
-    }
-
-    /* -------------------------------------------------------------- */
-    /** Decoded parameters to Map.
-     * @param raw the byte[] containing the encoded parameters
-     * @param offset the offset within raw to decode from
-     * @param length the length of the section to decode
-     * @param map the {@link MultiMap} to populate
-     * @param buffer the buffer to decode into
-     */
-    public static void decodeUtf8To(byte[] raw,int offset, int length, MultiMap map,Utf8StringBuilder buffer)
+    public static void decodeUtf8To(byte[] raw,int offset, int length, MultiMap<String> map)
     {
+        Utf8StringBuilder buffer = new Utf8StringBuilder();
         synchronized(map)
         {
             String key = null;
             String value = null;
 
-            // TODO cache of parameter names ???
             int end=offset+length;
             for (int i=offset;i<end;i++)
             {
@@ -300,7 +294,7 @@ public class UrlEncoded extends MultiMap implements Cloneable
                     switch ((char)(0xff&b))
                     {
                         case '&':
-                            value = buffer.length()==0?"":buffer.toString();
+                            value = buffer.toReplacedString();
                             buffer.reset();
                             if (key != null)
                             {
@@ -320,7 +314,7 @@ public class UrlEncoded extends MultiMap implements Cloneable
                                 buffer.append(b);
                                 break;
                             }
-                            key = buffer.toString();
+                            key = buffer.toReplacedString();
                             buffer.reset();
                             break;
 
@@ -335,7 +329,13 @@ public class UrlEncoded extends MultiMap implements Cloneable
                                 {
                                     i++;
                                     if (i+4<end)
-                                        buffer.getStringBuilder().append(Character.toChars((convertHexDigit(raw[++i])<<12) +(convertHexDigit(raw[++i])<<8) + (convertHexDigit(raw[++i])<<4) +convertHexDigit(raw[++i])));
+                                    {
+                                        byte top=raw[++i];
+                                        byte hi=raw[++i];
+                                        byte lo=raw[++i];
+                                        byte bot=raw[++i];
+                                        buffer.getStringBuilder().append(Character.toChars((convertHexDigit(top)<<12) +(convertHexDigit(hi)<<8) + (convertHexDigit(lo)<<4) +convertHexDigit(bot)));
+                                    }
                                     else
                                     {
                                         buffer.getStringBuilder().append(Utf8Appendable.REPLACEMENT);
@@ -343,7 +343,11 @@ public class UrlEncoded extends MultiMap implements Cloneable
                                     }
                                 }
                                 else
-                                    buffer.append((byte)((convertHexDigit(raw[++i])<<4) + convertHexDigit(raw[++i])));
+                                {
+                                    byte hi=raw[++i];
+                                    byte lo=raw[++i];
+                                    buffer.append((byte)((convertHexDigit(hi)<<4) + convertHexDigit(lo)));
+                                }
                             }
                             else
                             {
@@ -362,11 +366,17 @@ public class UrlEncoded extends MultiMap implements Cloneable
                     LOG.warn(e.toString());
                     LOG.debug(e);
                 }
+                catch(NumberFormatException e)
+                {
+                    buffer.append(Utf8Appendable.REPLACEMENT_UTF8,0,3);
+                    LOG.warn(e.toString());
+                    LOG.debug(e);
+                }
             }
             
             if (key != null)
             {
-                value = buffer.length()==0?"":buffer.toReplacedString();
+                value = buffer.toReplacedString();
                 buffer.reset();
                 map.add(key,value);
             }
@@ -383,7 +393,7 @@ public class UrlEncoded extends MultiMap implements Cloneable
      * @param map MultiMap to add parameters to
      * @param maxLength maximum number of keys to read or -1 for no limit
      */
-    public static void decode88591To(InputStream in, MultiMap map, int maxLength, int maxKeys)
+    public static void decode88591To(InputStream in, MultiMap<String> map, int maxLength, int maxKeys)
     throws IOException
     {
         synchronized(map)
@@ -394,7 +404,6 @@ public class UrlEncoded extends MultiMap implements Cloneable
             
             int b;
 
-            // TODO cache of parameter names ???
             int totalLength=0;
             while ((b=in.read())>=0)
             {
@@ -482,7 +491,7 @@ public class UrlEncoded extends MultiMap implements Cloneable
      * @param map MultiMap to add parameters to
      * @param maxLength maximum number of keys to read or -1 for no limit
      */
-    public static void decodeUtf8To(InputStream in, MultiMap map, int maxLength, int maxKeys)
+    public static void decodeUtf8To(InputStream in, MultiMap<String> map, int maxLength, int maxKeys)
     throws IOException
     {
         synchronized(map)
@@ -493,7 +502,6 @@ public class UrlEncoded extends MultiMap implements Cloneable
             
             int b;
             
-            // TODO cache of parameter names ???
             int totalLength=0;
             while ((b=in.read())>=0)
             {
@@ -502,7 +510,7 @@ public class UrlEncoded extends MultiMap implements Cloneable
                     switch ((char) b)
                     {
                         case '&':
-                            value = buffer.length()==0?"":buffer.toString();
+                            value = buffer.toReplacedString();
                             buffer.reset();
                             if (key != null)
                             {
@@ -524,7 +532,7 @@ public class UrlEncoded extends MultiMap implements Cloneable
                                 buffer.append((byte)b);
                                 break;
                             }
-                            key = buffer.toString();
+                            key = buffer.toReplacedString(); 
                             buffer.reset();
                             break;
 
@@ -534,17 +542,26 @@ public class UrlEncoded extends MultiMap implements Cloneable
 
                         case '%':
                             int code0=in.read();
+                            boolean decoded=false;
                             if ('u'==code0)
                             {
-                                int code1=in.read();
-                                if (code1>=0)
+                                code0=in.read(); // XXX: we have to read the next byte, otherwise code0 is always 'u'
+                                if (code0>=0)
                                 {
-                                    int code2=in.read();
-                                    if (code2>=0)
+                                    int code1=in.read();
+                                    if (code1>=0)
                                     {
-                                        int code3=in.read();
-                                        if (code3>=0)
-                                            buffer.getStringBuilder().append(Character.toChars((convertHexDigit(code0)<<12)+(convertHexDigit(code1)<<8)+(convertHexDigit(code2)<<4)+convertHexDigit(code3)));
+                                        int code2=in.read();
+                                        if (code2>=0)
+                                        {
+                                            int code3=in.read();
+                                            if (code3>=0)
+                                            {
+                                                buffer.getStringBuilder().append(Character.toChars
+                                                    ((convertHexDigit(code0)<<12)+(convertHexDigit(code1)<<8)+(convertHexDigit(code2)<<4)+convertHexDigit(code3)));
+                                                decoded=true;
+                                            }
+                                        }
                                     }
                                 }
                             }
@@ -552,8 +569,15 @@ public class UrlEncoded extends MultiMap implements Cloneable
                             {
                                 int code1=in.read();
                                 if (code1>=0)
+                                {
                                     buffer.append((byte)((convertHexDigit(code0)<<4)+convertHexDigit(code1)));
+                                    decoded=true;
+                                }
                             }
+                            
+                            if (!decoded)
+                                buffer.getStringBuilder().append(Utf8Appendable.REPLACEMENT);
+
                             break;
                           
                         default:
@@ -566,64 +590,91 @@ public class UrlEncoded extends MultiMap implements Cloneable
                     LOG.warn(e.toString());
                     LOG.debug(e);
                 }
+                catch(NumberFormatException e)
+                {
+                    buffer.append(Utf8Appendable.REPLACEMENT_UTF8,0,3);
+                    LOG.warn(e.toString());
+                    LOG.debug(e);
+                }
                 if (maxLength>=0 && (++totalLength > maxLength))
                     throw new IllegalStateException("Form too large");
             }
             
             if (key != null)
             {
-                value = buffer.length()==0?"":buffer.toString();
+                value = buffer.toReplacedString();
                 buffer.reset();
                 map.add(key,value);
             }
             else if (buffer.length()>0)
             {
-                map.add(buffer.toString(), "");
+                map.add(buffer.toReplacedString(), "");
             }
         }
     }
     
     /* -------------------------------------------------------------- */
-    public static void decodeUtf16To(InputStream in, MultiMap map, int maxLength, int maxKeys) throws IOException
+    public static void decodeUtf16To(InputStream in, MultiMap<String> map, int maxLength, int maxKeys) throws IOException
     {
-        InputStreamReader input = new InputStreamReader(in,StringUtil.__UTF16);
+        InputStreamReader input = new InputStreamReader(in,StandardCharsets.UTF_16);
         StringWriter buf = new StringWriter(8192);
         IO.copy(input,buf,maxLength);
         
-        decodeTo(buf.getBuffer().toString(),map,StringUtil.__UTF16,maxKeys);
+        decodeTo(buf.getBuffer().toString(),map,StandardCharsets.UTF_16,maxKeys);
+    }
+
+    /* -------------------------------------------------------------- */
+    /** Decoded parameters to Map.
+     * @param in the stream containing the encoded parameters
+     */
+    public static void decodeTo(InputStream in, MultiMap<String> map, String charset, int maxLength, int maxKeys)
+    throws IOException
+    {
+        if (charset==null)
+        {
+            if (ENCODING.equals(StandardCharsets.UTF_8))
+                decodeUtf8To(in,map,maxLength,maxKeys);
+            else
+                decodeTo(in,map,ENCODING,maxLength,maxKeys);
+        }
+        else if (StringUtil.__UTF8.equalsIgnoreCase(charset))
+            decodeUtf8To(in,map,maxLength,maxKeys);
+        else if (StringUtil.__ISO_8859_1.equalsIgnoreCase(charset))
+            decode88591To(in,map,maxLength,maxKeys);
+        else if (StringUtil.__UTF16.equalsIgnoreCase(charset))
+            decodeUtf16To(in,map,maxLength,maxKeys);
+        else
+            decodeTo(in,map,Charset.forName(charset),maxLength,maxKeys);
     }
     
     /* -------------------------------------------------------------- */
     /** Decoded parameters to Map.
      * @param in the stream containing the encoded parameters
      */
-    public static void decodeTo(InputStream in, MultiMap map, String charset, int maxLength, int maxKeys)
+    public static void decodeTo(InputStream in, MultiMap<String> map, Charset charset, int maxLength, int maxKeys)
     throws IOException
     {
         //no charset present, use the configured default
         if (charset==null) 
-        {
            charset=ENCODING;
-        }
             
-        if (StringUtil.__UTF8.equalsIgnoreCase(charset))
+        if (StandardCharsets.UTF_8.equals(charset))
         {
             decodeUtf8To(in,map,maxLength,maxKeys);
             return;
         }
         
-        if (StringUtil.__ISO_8859_1.equals(charset))
+        if (StandardCharsets.ISO_8859_1.equals(charset))
         {
             decode88591To(in,map,maxLength,maxKeys);
             return;
         }
 
-        if (StringUtil.__UTF16.equalsIgnoreCase(charset)) // Should be all 2 byte encodings
+        if (StandardCharsets.UTF_16.equals(charset)) // Should be all 2 byte encodings
         {
             decodeUtf16To(in,map,maxLength,maxKeys);
             return;
         }
-        
 
         synchronized(map)
         {
@@ -633,87 +684,89 @@ public class UrlEncoded extends MultiMap implements Cloneable
             int c;
             
             int totalLength = 0;
-            ByteArrayOutputStream2 output = new ByteArrayOutputStream2();
-            
-            int size=0;
             
-            while ((c=in.read())>0)
+            try(ByteArrayOutputStream2 output = new ByteArrayOutputStream2();)
             {
-                switch ((char) c)
+                int size=0;
+
+                while ((c=in.read())>0)
                 {
-                    case '&':
-                        size=output.size();
-                        value = size==0?"":output.toString(charset);
-                        output.setCount(0);
-                        if (key != null)
-                        {
-                            map.add(key,value);
-                        }
-                        else if (value!=null&&value.length()>0)
-                        {
-                            map.add(value,"");
-                        }
-                        key = null;
-                        value=null;
-                        if (maxKeys>0 && map.size()>maxKeys)
-                            throw new IllegalStateException("Form too many keys");
-                        break;
-                    case '=':
-                        if (key!=null)
-                        {
-                            output.write(c);
+                    switch ((char) c)
+                    {
+                        case '&':
+                            size=output.size();
+                            value = size==0?"":output.toString(charset);
+                            output.setCount(0);
+                            if (key != null)
+                            {
+                                map.add(key,value);
+                            }
+                            else if (value!=null&&value.length()>0)
+                            {
+                                map.add(value,"");
+                            }
+                            key = null;
+                            value=null;
+                            if (maxKeys>0 && map.size()>maxKeys)
+                                throw new IllegalStateException("Form too many keys");
                             break;
-                        }
-                        size=output.size();
-                        key = size==0?"":output.toString(charset);
-                        output.setCount(0);
-                        break;
-                    case '+':
-                        output.write(' ');
-                        break;
-                    case '%':
-                        int code0=in.read();
-                        if ('u'==code0)
-                        {
-                            int code1=in.read();
-                            if (code1>=0)
+                        case '=':
+                            if (key!=null)
                             {
-                                int code2=in.read();
-                                if (code2>=0)
+                                output.write(c);
+                                break;
+                            }
+                            size=output.size();
+                            key = size==0?"":output.toString(charset);
+                            output.setCount(0);
+                            break;
+                        case '+':
+                            output.write(' ');
+                            break;
+                        case '%':
+                            int code0=in.read();
+                            if ('u'==code0)
+                            {
+                                int code1=in.read();
+                                if (code1>=0)
                                 {
-                                    int code3=in.read();
-                                    if (code3>=0)
-                                        output.write(new String(Character.toChars((convertHexDigit(code0)<<12)+(convertHexDigit(code1)<<8)+(convertHexDigit(code2)<<4)+convertHexDigit(code3))).getBytes(charset));
+                                    int code2=in.read();
+                                    if (code2>=0)
+                                    {
+                                        int code3=in.read();
+                                        if (code3>=0)
+                                            output.write(new String(Character.toChars((convertHexDigit(code0)<<12)+(convertHexDigit(code1)<<8)+(convertHexDigit(code2)<<4)+convertHexDigit(code3))).getBytes(charset));
+                                    }
                                 }
+
                             }
-                            
-                        }
-                        else if (code0>=0)
-                        {
-                            int code1=in.read();
-                            if (code1>=0)
-                                output.write((convertHexDigit(code0)<<4)+convertHexDigit(code1));
-                        }
-                        break;
-                    default:
-                        output.write(c);
-                    break;
+                            else if (code0>=0)
+                            {
+                                int code1=in.read();
+                                if (code1>=0)
+                                    output.write((convertHexDigit(code0)<<4)+convertHexDigit(code1));
+                            }
+                            break;
+                        default:
+                            output.write(c);
+                            break;
+                    }
+
+                    totalLength++;
+                    if (maxLength>=0 && totalLength > maxLength)
+                        throw new IllegalStateException("Form too large");
                 }
-                
-                totalLength++;
-                if (maxLength>=0 && totalLength > maxLength)
-                    throw new IllegalStateException("Form too large");
-            }
 
-            size=output.size();
-            if (key != null)
-            {
-                value = size==0?"":output.toString(charset);
-                output.setCount(0);
-                map.add(key,value);
+                size=output.size();
+                if (key != null)
+                {
+                    value = size==0?"":output.toString(charset);
+                    output.setCount(0);
+                    map.add(key,value);
+                }
+                else if (size>0)
+                    map.add(output.toString(charset),"");
             }
-            else if (size>0)
-                map.add(output.toString(charset),"");
         }
     }
     
@@ -722,9 +775,9 @@ public class UrlEncoded extends MultiMap implements Cloneable
      * This method makes the assumption that the majority of calls
      * will need no decoding.
      */
-    public static String decodeString(String encoded,int offset,int length,String charset)
+    public static String decodeString(String encoded,int offset,int length,Charset charset)
     {
-        if (charset==null || StringUtil.isUTF8(charset))
+        if (charset==null || StandardCharsets.UTF_8.equals(charset))
         {
             Utf8StringBuffer buffer=null;
 
@@ -791,9 +844,10 @@ public class UrlEncoded extends MultiMap implements Cloneable
                             LOG.warn(e.toString());
                             LOG.debug(e);
                         }
-                        catch(NumberFormatException nfe)
+                        catch(NumberFormatException e)
                         {
-                            LOG.debug(nfe);
+                            LOG.warn(e.toString());
+                            LOG.debug(e);
                             buffer.getStringBuffer().append(Utf8Appendable.REPLACEMENT);  
                         }
                     }
@@ -820,125 +874,119 @@ public class UrlEncoded extends MultiMap implements Cloneable
         {
             StringBuffer buffer=null;
 
-            try
+            for (int i=0;i<length;i++)
             {
-                for (int i=0;i<length;i++)
+                char c = encoded.charAt(offset+i);
+                if (c<0||c>0xff)
                 {
-                    char c = encoded.charAt(offset+i);
-                    if (c<0||c>0xff)
+                    if (buffer==null)
                     {
-                        if (buffer==null)
-                        {
-                            buffer=new StringBuffer(length);
-                            buffer.append(encoded,offset,offset+i+1);
-                        }
-                        else
-                            buffer.append(c);
+                        buffer=new StringBuffer(length);
+                        buffer.append(encoded,offset,offset+i+1);
                     }
-                    else if (c=='+')
+                    else
+                        buffer.append(c);
+                }
+                else if (c=='+')
+                {
+                    if (buffer==null)
                     {
-                        if (buffer==null)
-                        {
-                            buffer=new StringBuffer(length);
-                            buffer.append(encoded,offset,offset+i);
-                        }
-                        
-                        buffer.append(' ');
+                        buffer=new StringBuffer(length);
+                        buffer.append(encoded,offset,offset+i);
                     }
-                    else if (c=='%')
+
+                    buffer.append(' ');
+                }
+                else if (c=='%')
+                {
+                    if (buffer==null)
                     {
-                        if (buffer==null)
-                        {
-                            buffer=new StringBuffer(length);
-                            buffer.append(encoded,offset,offset+i);
-                        }
+                        buffer=new StringBuffer(length);
+                        buffer.append(encoded,offset,offset+i);
+                    }
 
-                        byte[] ba=new byte[length];
-                        int n=0;
-                        while(c>=0 && c<=0xff)
-                        {
-                            if (c=='%')
-                            {   
-                                if(i+2<length)
+                    byte[] ba=new byte[length];
+                    int n=0;
+                    while(c>=0 && c<=0xff)
+                    {
+                        if (c=='%')
+                        {   
+                            if(i+2<length)
+                            {
+                                try
                                 {
-                                    try
+                                    if ('u'==encoded.charAt(offset+i+1))
                                     {
-                                        if ('u'==encoded.charAt(offset+i+1))
+                                        if (i+6<length)
                                         {
-                                            if (i+6<length)
-                                            {
-                                                int o=offset+i+2;
-                                                i+=6;
-                                                String unicode = new String(Character.toChars(TypeUtil.parseInt(encoded,o,4,16)));
-                                                byte[] reencoded = unicode.getBytes(charset);
-                                                System.arraycopy(reencoded,0,ba,n,reencoded.length);
-                                                n+=reencoded.length;
-                                            }
-                                            else
-                                            {
-                                                ba[n++] = (byte)'?';
-                                                i=length;
-                                            }
+                                            int o=offset+i+2;
+                                            i+=6;
+                                            String unicode = new String(Character.toChars(TypeUtil.parseInt(encoded,o,4,16)));
+                                            byte[] reencoded = unicode.getBytes(charset);
+                                            System.arraycopy(reencoded,0,ba,n,reencoded.length);
+                                            n+=reencoded.length;
                                         }
                                         else
                                         {
-                                            int o=offset+i+1;
-                                            i+=3;
-                                            ba[n]=(byte)TypeUtil.parseInt(encoded,o,2,16);
-                                            n++;
+                                            ba[n++] = (byte)'?';
+                                            i=length;
                                         }
                                     }
-                                    catch(NumberFormatException nfe)
-                                    {   
-                                        LOG.ignore(nfe);
-                                        ba[n++] = (byte)'?';
+                                    else
+                                    {
+                                        int o=offset+i+1;
+                                        i+=3;
+                                        ba[n]=(byte)TypeUtil.parseInt(encoded,o,2,16);
+                                        n++;
                                     }
                                 }
-                                else
-                                {
+                                catch(Exception e)
+                                {   
+                                    LOG.warn(e.toString());
+                                    LOG.debug(e);
                                     ba[n++] = (byte)'?';
-                                    i=length;
                                 }
                             }
-                            else if (c=='+')
-                            {
-                                ba[n++]=(byte)' ';
-                                i++;
-                            }
                             else
                             {
-                                ba[n++]=(byte)c;
-                                i++;
+                                    ba[n++] = (byte)'?';
+                                    i=length;
                             }
-                            
-                            if (i>=length)
-                                break;
-                            c = encoded.charAt(offset+i);
+                        }
+                        else if (c=='+')
+                        {
+                            ba[n++]=(byte)' ';
+                            i++;
+                        }
+                        else
+                        {
+                            ba[n++]=(byte)c;
+                            i++;
                         }
 
-                        i--;
-                        buffer.append(new String(ba,0,n,charset));
-
+                        if (i>=length)
+                            break;
+                        c = encoded.charAt(offset+i);
                     }
-                    else if (buffer!=null)
-                        buffer.append(c);
-                }
 
-                if (buffer==null)
-                {
-                    if (offset==0 && encoded.length()==length)
-                        return encoded;
-                    return encoded.substring(offset,offset+length);
-                }
+                    i--;
+                    buffer.append(new String(ba,0,n,charset));
 
-                return buffer.toString();
+                }
+                else if (buffer!=null)
+                    buffer.append(c);
             }
-            catch (UnsupportedEncodingException e)
+
+            if (buffer==null)
             {
-                throw new RuntimeException(e);
+                if (offset==0 && encoded.length()==length)
+                    return encoded;
+                return encoded.substring(offset,offset+length);
             }
+
+            return buffer.toString();
         }
-        
+
     }
     
     /* ------------------------------------------------------------ */
@@ -956,20 +1004,12 @@ public class UrlEncoded extends MultiMap implements Cloneable
      * @param string 
      * @return encoded string.
      */
-    public static String encodeString(String string,String charset)
+    public static String encodeString(String string,Charset charset)
     {
         if (charset==null)
             charset=ENCODING;
         byte[] bytes=null;
-        try
-        {
-            bytes=string.getBytes(charset);
-        }
-        catch(UnsupportedEncodingException e)
-        {
-            // LOG.warn(LogSupport.EXCEPTION,e);
-            bytes=string.getBytes();
-        }
+        bytes=string.getBytes(charset);
         
         int len=bytes.length;
         byte[] encoded= new byte[bytes.length*3];
@@ -1011,15 +1051,7 @@ public class UrlEncoded extends MultiMap implements Cloneable
         if (noEncode)
             return string;
         
-        try
-        {    
-            return new String(encoded,0,n,charset);
-        }
-        catch(UnsupportedEncodingException e)
-        {
-            // LOG.warn(LogSupport.EXCEPTION,e);
-            return new String(encoded,0,n);
-        }
+        return new String(encoded,0,n,charset);
     }
 
 
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/Utf8Appendable.java b/jetty-util/src/main/java/org/eclipse/jetty/util/Utf8Appendable.java
index 4e2f0be..7a321ec 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/Utf8Appendable.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/Utf8Appendable.java
@@ -19,6 +19,7 @@
 package org.eclipse.jetty.util;
 
 import java.io.IOException;
+import java.nio.ByteBuffer;
 
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
@@ -51,6 +52,7 @@ public abstract class Utf8Appendable
 {
     protected static final Logger LOG = Log.getLogger(Utf8Appendable.class);
     public static final char REPLACEMENT = '\ufffd';
+    public static final byte[] REPLACEMENT_UTF8 = new byte[] {(byte)0xEF,(byte)0xBF,(byte)0xBD };
     private static final int UTF8_ACCEPT = 0;
     private static final int UTF8_REJECT = 12;
 
@@ -107,6 +109,21 @@ public abstract class Utf8Appendable
             throw new RuntimeException(e);
         }
     }
+    
+    public void append(ByteBuffer buf)
+    {
+        try
+        {
+            while (buf.remaining() > 0)
+            {
+                appendByte(buf.get());
+            }
+        }
+        catch (IOException e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
 
     public void append(byte[] b, int offset, int length)
     {
@@ -189,6 +206,7 @@ public abstract class Utf8Appendable
         return _state == UTF8_ACCEPT;
     }
 
+    @SuppressWarnings("serial")
     public static class NotUtf8Exception extends IllegalArgumentException
     {
         public NotUtf8Exception(String reason)
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/Utf8LineParser.java b/jetty-util/src/main/java/org/eclipse/jetty/util/Utf8LineParser.java
new file mode 100644
index 0000000..a3da9f5
--- /dev/null
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/Utf8LineParser.java
@@ -0,0 +1,99 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Stateful parser for lines of UTF8 formatted text, looking for <code>"\n"</code> as a line termination character.
+ * <p>
+ * For use with new IO framework that is based on ByteBuffer parsing.
+ */
+public class Utf8LineParser
+{
+    private enum State
+    {
+        START,
+        PARSE,
+        END;
+    }
+
+    private State state;
+    private Utf8StringBuilder utf;
+
+    public Utf8LineParser()
+    {
+        this.state = State.START;
+    }
+
+    /**
+     * Parse a ByteBuffer (could be a partial buffer), and return once a complete line of UTF8 parsed text has been reached.
+     *
+     * @param buf
+     *            the buffer to parse (could be an incomplete buffer)
+     * @return the line of UTF8 parsed text, or null if no line end termination has been reached within the {@link ByteBuffer#remaining() remaining} bytes of
+     *         the provided ByteBuffer. (In the case of a null, a subsequent ByteBuffer with a line end termination should be provided)
+     * @throws NotUtf8Exception
+     *             if the input buffer has bytes that do not conform to UTF8 validation (validation performed by {@link Utf8StringBuilder}
+     */
+    public String parse(ByteBuffer buf)
+    {
+        byte b;
+        while (buf.remaining() > 0)
+        {
+            b = buf.get();
+            if (parseByte(b))
+            {
+                state = State.START;
+                return utf.toString();
+            }
+        }
+        // have not reached end of line (yet)
+        return null;
+    }
+
+    private boolean parseByte(byte b)
+    {
+        switch (state)
+        {
+            case START:
+                utf = new Utf8StringBuilder();
+                state = State.PARSE;
+                return parseByte(b);
+            case PARSE:
+                // not waiting on more UTF sequence parts.
+                if (utf.isUtf8SequenceComplete() && ((b == '\r') || (b == '\n')))
+                {
+                    state = State.END;
+                    return parseByte(b);
+                }
+                utf.append(b);
+                break;
+            case END:
+                if (b == '\n')
+                {
+                    // we've reached the end
+                    state = State.START;
+                    return true;
+                }
+                break;
+        }
+        return false;
+    }
+}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/ajax/JSON.java b/jetty-util/src/main/java/org/eclipse/jetty/util/ajax/JSON.java
deleted file mode 100644
index 1691296..0000000
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/ajax/JSON.java
+++ /dev/null
@@ -1,1640 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.util.ajax;
-
-import java.io.Externalizable;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.Reader;
-import java.lang.reflect.Array;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
-import org.eclipse.jetty.util.IO;
-import org.eclipse.jetty.util.Loader;
-import org.eclipse.jetty.util.QuotedStringTokenizer;
-import org.eclipse.jetty.util.TypeUtil;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-
-/**
- * JSON Parser and Generator.
- * <p />
- * This class provides some static methods to convert POJOs to and from JSON
- * notation. The mapping from JSON to java is:
- *
- * <pre>
- *   object ==> Map
- *   array  ==> Object[]
- *   number ==> Double or Long
- *   string ==> String
- *   null   ==> null
- *   bool   ==> Boolean
- * </pre>
-
- * The java to JSON mapping is:
- *
- * <pre>
- *   String --> string
- *   Number --> number
- *   Map    --> object
- *   List   --> array
- *   Array  --> array
- *   null   --> null
- *   Boolean--> boolean
- *   Object --> string (dubious!)
- * </pre>
- *
- * The interface {@link JSON.Convertible} may be implemented by classes that
- * wish to externalize and initialize specific fields to and from JSON objects.
- * Only directed acyclic graphs of objects are supported.
- * <p />
- * The interface {@link JSON.Generator} may be implemented by classes that know
- * how to render themselves as JSON and the {@link #toString(Object)} method
- * will use {@link JSON.Generator#addJSON(Appendable)} to generate the JSON.
- * The class {@link JSON.Literal} may be used to hold pre-generated JSON object.
- * <p />
- * The interface {@link JSON.Convertor} may be implemented to provide static
- * converters for objects that may be registered with
- * {@link #registerConvertor(Class, Convertor)}.
- * These converters are looked up by class, interface and super class by
- * {@link #getConvertor(Class)}.
- * <p />
- * If a JSON object has a "class" field, then a java class for that name is
- * loaded and the method {@link #convertTo(Class,Map)} is used to find a
- * {@link JSON.Convertor} for that class.
- * <p />
- * If a JSON object has a "x-class" field then a direct lookup for a
- * {@link JSON.Convertor} for that class name is done (without loading the class).
- */
-public class JSON
-{
-    static final Logger LOG = Log.getLogger(JSON.class);
-    public final static JSON DEFAULT = new JSON();
-
-    private Map<String, Convertor> _convertors = new ConcurrentHashMap<String, Convertor>();
-    private int _stringBufferSize = 1024;
-
-    public JSON()
-    {
-    }
-
-    /**
-     * @return the initial stringBuffer size to use when creating JSON strings
-     *         (default 1024)
-     */
-    public int getStringBufferSize()
-    {
-        return _stringBufferSize;
-    }
-
-    /**
-     * @param stringBufferSize
-     *            the initial stringBuffer size to use when creating JSON
-     *            strings (default 1024)
-     */
-    public void setStringBufferSize(int stringBufferSize)
-    {
-        _stringBufferSize = stringBufferSize;
-    }
-
-    /**
-     * Register a {@link Convertor} for a class or interface.
-     *
-     * @param forClass
-     *            The class or interface that the convertor applies to
-     * @param convertor
-     *            the convertor
-     */
-    public static void registerConvertor(Class forClass, Convertor convertor)
-    {
-        DEFAULT.addConvertor(forClass,convertor);
-    }
-
-    public static JSON getDefault()
-    {
-        return DEFAULT;
-    }
-
-    @Deprecated
-    public static void setDefault(JSON json)
-    {
-    }
-
-    public static String toString(Object object)
-    {
-        StringBuilder buffer = new StringBuilder(DEFAULT.getStringBufferSize());
-        DEFAULT.append(buffer,object);
-        return buffer.toString();
-    }
-
-    public static String toString(Map object)
-    {
-        StringBuilder buffer = new StringBuilder(DEFAULT.getStringBufferSize());
-        DEFAULT.appendMap(buffer,object);
-        return buffer.toString();
-    }
-
-    public static String toString(Object[] array)
-    {
-        StringBuilder buffer = new StringBuilder(DEFAULT.getStringBufferSize());
-        DEFAULT.appendArray(buffer,array);
-        return buffer.toString();
-    }
-
-    /**
-     * @param s
-     *            String containing JSON object or array.
-     * @return A Map, Object array or primitive array parsed from the JSON.
-     */
-    public static Object parse(String s)
-    {
-        return DEFAULT.parse(new StringSource(s),false);
-    }
-
-    /**
-     * @param s
-     *            String containing JSON object or array.
-     * @param stripOuterComment
-     *            If true, an outer comment around the JSON is ignored.
-     * @return A Map, Object array or primitive array parsed from the JSON.
-     */
-    public static Object parse(String s, boolean stripOuterComment)
-    {
-        return DEFAULT.parse(new StringSource(s),stripOuterComment);
-    }
-
-    /**
-     * @param in
-     *            Reader containing JSON object or array.
-     * @return A Map, Object array or primitive array parsed from the JSON.
-     */
-    public static Object parse(Reader in) throws IOException
-    {
-        return DEFAULT.parse(new ReaderSource(in),false);
-    }
-
-    /**
-     * @param in
-     *            Reader containing JSON object or array.
-     * @param stripOuterComment
-     *            If true, an outer comment around the JSON is ignored.
-     * @return A Map, Object array or primitive array parsed from the JSON.
-     */
-    public static Object parse(Reader in, boolean stripOuterComment) throws IOException
-    {
-        return DEFAULT.parse(new ReaderSource(in),stripOuterComment);
-    }
-
-    /**
-     * @deprecated use {@link #parse(Reader)}
-     * @param in
-     *            Reader containing JSON object or array.
-     * @return A Map, Object array or primitive array parsed from the JSON.
-     */
-    @Deprecated
-    public static Object parse(InputStream in) throws IOException
-    {
-        return DEFAULT.parse(new StringSource(IO.toString(in)),false);
-    }
-
-    /**
-     * @deprecated use {@link #parse(Reader, boolean)}
-     * @param in
-     *            Stream containing JSON object or array.
-     * @param stripOuterComment
-     *            If true, an outer comment around the JSON is ignored.
-     * @return A Map, Object array or primitive array parsed from the JSON.
-     */
-    @Deprecated
-    public static Object parse(InputStream in, boolean stripOuterComment) throws IOException
-    {
-        return DEFAULT.parse(new StringSource(IO.toString(in)),stripOuterComment);
-    }
-
-    /**
-     * Convert Object to JSON
-     *
-     * @param object
-     *            The object to convert
-     * @return The JSON String
-     */
-    public String toJSON(Object object)
-    {
-        StringBuilder buffer = new StringBuilder(getStringBufferSize());
-        append(buffer,object);
-        return buffer.toString();
-    }
-
-    /**
-     * Convert JSON to Object
-     *
-     * @param json
-     *            The json to convert
-     * @return The object
-     */
-    public Object fromJSON(String json)
-    {
-        Source source = new StringSource(json);
-        return parse(source);
-    }
-
-    @Deprecated
-    public void append(StringBuffer buffer, Object object)
-    {
-        append((Appendable)buffer,object);
-    }
-
-    /**
-     * Append object as JSON to string buffer.
-     *
-     * @param buffer
-     *            the buffer to append to
-     * @param object
-     *            the object to append
-     */
-    public void append(Appendable buffer, Object object)
-    {
-        try
-        {
-            if (object == null)
-            {
-                buffer.append("null");
-            }
-            // Most likely first
-            else if (object instanceof Map)
-            {
-                appendMap(buffer,(Map)object);
-            }
-            else if (object instanceof String)
-            {
-                appendString(buffer,(String)object);
-            }
-            else if (object instanceof Number)
-            {
-                appendNumber(buffer,(Number)object);
-            }
-            else if (object instanceof Boolean)
-            {
-                appendBoolean(buffer,(Boolean)object);
-            }
-            else if (object.getClass().isArray())
-            {
-                appendArray(buffer,object);
-            }
-            else if (object instanceof Character)
-            {
-                appendString(buffer,object.toString());
-            }
-            else if (object instanceof Convertible)
-            {
-                appendJSON(buffer,(Convertible)object);
-            }
-            else if (object instanceof Generator)
-            {
-                appendJSON(buffer,(Generator)object);
-            }
-            else
-            {
-                // Check Convertor before Collection to support JSONCollectionConvertor
-                Convertor convertor = getConvertor(object.getClass());
-                if (convertor != null)
-                {
-                    appendJSON(buffer,convertor,object);
-                }
-                else if (object instanceof Collection)
-                {
-                    appendArray(buffer,(Collection)object);
-                }
-                else
-                {
-                    appendString(buffer,object.toString());
-                }
-            }
-        }
-        catch (IOException e)
-        {
-            throw new RuntimeException(e);
-        }
-    }
-
-    @Deprecated
-    public void appendNull(StringBuffer buffer)
-    {
-        appendNull((Appendable)buffer);
-    }
-
-    public void appendNull(Appendable buffer)
-    {
-        try
-        {
-            buffer.append("null");
-        }
-        catch (IOException e)
-        {
-            throw new RuntimeException(e);
-        }
-    }
-
-    @Deprecated
-    public void appendJSON(final StringBuffer buffer, final Convertor convertor, final Object object)
-    {
-        appendJSON((Appendable)buffer,convertor,object);
-    }
-
-    public void appendJSON(final Appendable buffer, final Convertor convertor, final Object object)
-    {
-        appendJSON(buffer,new Convertible()
-        {
-            public void fromJSON(Map object)
-            {
-            }
-
-            public void toJSON(Output out)
-            {
-                convertor.toJSON(object,out);
-            }
-        });
-    }
-
-    @Deprecated
-    public void appendJSON(final StringBuffer buffer, Convertible converter)
-    {
-        appendJSON((Appendable)buffer,converter);
-    }
-
-    public void appendJSON(final Appendable buffer, Convertible converter)
-    {
-        ConvertableOutput out=new ConvertableOutput(buffer);
-        converter.toJSON(out);
-        out.complete();
-    }
-
-    @Deprecated
-    public void appendJSON(StringBuffer buffer, Generator generator)
-    {
-        generator.addJSON(buffer);
-    }
-
-    public void appendJSON(Appendable buffer, Generator generator)
-    {
-        generator.addJSON(buffer);
-    }
-
-    @Deprecated
-    public void appendMap(StringBuffer buffer, Map<?,?> map)
-    {
-        appendMap((Appendable)buffer,map);
-    }
-
-    public void appendMap(Appendable buffer, Map<?,?> map)
-    {
-        try
-        {
-            if (map == null)
-            {
-                appendNull(buffer);
-                return;
-            }
-
-            buffer.append('{');
-            Iterator<?> iter = map.entrySet().iterator();
-            while (iter.hasNext())
-            {
-                Map.Entry<?,?> entry = (Map.Entry<?,?>)iter.next();
-                QuotedStringTokenizer.quote(buffer,entry.getKey().toString());
-                buffer.append(':');
-                append(buffer,entry.getValue());
-                if (iter.hasNext())
-                    buffer.append(',');
-            }
-
-            buffer.append('}');
-        }
-        catch (IOException e)
-        {
-            throw new RuntimeException(e);
-        }
-    }
-
-    @Deprecated
-    public void appendArray(StringBuffer buffer, Collection collection)
-    {
-        appendArray((Appendable)buffer,collection);
-    }
-
-    public void appendArray(Appendable buffer, Collection collection)
-    {
-        try
-        {
-            if (collection == null)
-            {
-                appendNull(buffer);
-                return;
-            }
-
-            buffer.append('[');
-            Iterator iter = collection.iterator();
-            boolean first = true;
-            while (iter.hasNext())
-            {
-                if (!first)
-                    buffer.append(',');
-
-                first = false;
-                append(buffer,iter.next());
-            }
-
-            buffer.append(']');
-        }
-        catch (IOException e)
-        {
-            throw new RuntimeException(e);
-        }
-    }
-
-    @Deprecated
-    public void appendArray(StringBuffer buffer, Object array)
-    {
-    appendArray((Appendable)buffer,array);
-    }
-
-    public void appendArray(Appendable buffer, Object array)
-    {
-        try
-        {
-            if (array == null)
-            {
-                appendNull(buffer);
-                return;
-            }
-
-            buffer.append('[');
-            int length = Array.getLength(array);
-
-            for (int i = 0; i < length; i++)
-            {
-                if (i != 0)
-                    buffer.append(',');
-                append(buffer,Array.get(array,i));
-            }
-
-            buffer.append(']');
-        }
-        catch (IOException e)
-        {
-            throw new RuntimeException(e);
-        }
-    }
-
-    @Deprecated
-    public void appendBoolean(StringBuffer buffer, Boolean b)
-    {
-        appendBoolean((Appendable)buffer,b);
-    }
-
-    public void appendBoolean(Appendable buffer, Boolean b)
-    {
-        try
-        {
-            if (b == null)
-            {
-                appendNull(buffer);
-                return;
-            }
-            buffer.append(b?"true":"false");
-        }
-        catch (IOException e)
-        {
-            throw new RuntimeException(e);
-        }
-    }
-
-    @Deprecated
-    public void appendNumber(StringBuffer buffer, Number number)
-    {
-        appendNumber((Appendable)buffer,number);
-    }
-
-    public void appendNumber(Appendable buffer, Number number)
-    {
-        try
-        {
-            if (number == null)
-            {
-                appendNull(buffer);
-                return;
-            }
-            buffer.append(String.valueOf(number));
-        }
-        catch (IOException e)
-        {
-            throw new RuntimeException(e);
-        }
-    }
-
-    @Deprecated
-    public void appendString(StringBuffer buffer, String string)
-    {
-        appendString((Appendable)buffer,string);
-    }
-
-    public void appendString(Appendable buffer, String string)
-    {
-        if (string == null)
-        {
-            appendNull(buffer);
-            return;
-        }
-
-        QuotedStringTokenizer.quote(buffer,string);
-    }
-
-    // Parsing utilities
-
-    protected String toString(char[] buffer, int offset, int length)
-    {
-        return new String(buffer,offset,length);
-    }
-
-    protected Map<String, Object> newMap()
-    {
-        return new HashMap<String, Object>();
-    }
-
-    protected Object[] newArray(int size)
-    {
-        return new Object[size];
-    }
-
-    protected JSON contextForArray()
-    {
-        return this;
-    }
-
-    protected JSON contextFor(String field)
-    {
-        return this;
-    }
-
-    protected Object convertTo(Class type, Map map)
-    {
-        if (type != null && Convertible.class.isAssignableFrom(type))
-        {
-            try
-            {
-                Convertible conv = (Convertible)type.newInstance();
-                conv.fromJSON(map);
-                return conv;
-            }
-            catch (Exception e)
-            {
-                throw new RuntimeException(e);
-            }
-        }
-
-        Convertor convertor = getConvertor(type);
-        if (convertor != null)
-        {
-            return convertor.fromJSON(map);
-        }
-        return map;
-    }
-
-    /**
-     * Register a {@link Convertor} for a class or interface.
-     *
-     * @param forClass
-     *            The class or interface that the convertor applies to
-     * @param convertor
-     *            the convertor
-     */
-    public void addConvertor(Class forClass, Convertor convertor)
-    {
-        _convertors.put(forClass.getName(),convertor);
-    }
-
-    /**
-     * Lookup a convertor for a class.
-     * <p>
-     * If no match is found for the class, then the interfaces for the class are
-     * tried. If still no match is found, then the super class and it's
-     * interfaces are tried recursively.
-     *
-     * @param forClass
-     *            The class
-     * @return a {@link JSON.Convertor} or null if none were found.
-     */
-    protected Convertor getConvertor(Class forClass)
-    {
-        Class cls = forClass;
-        Convertor convertor = _convertors.get(cls.getName());
-        if (convertor == null && this != DEFAULT)
-            convertor = DEFAULT.getConvertor(cls);
-
-        while (convertor == null && cls != Object.class)
-        {
-            Class[] ifs = cls.getInterfaces();
-            int i = 0;
-            while (convertor == null && ifs != null && i < ifs.length)
-                convertor = _convertors.get(ifs[i++].getName());
-            if (convertor == null)
-            {
-                cls = cls.getSuperclass();
-                convertor = _convertors.get(cls.getName());
-            }
-        }
-        return convertor;
-    }
-
-    /**
-     * Register a {@link JSON.Convertor} for a named class or interface.
-     *
-     * @param name
-     *            name of a class or an interface that the convertor applies to
-     * @param convertor
-     *            the convertor
-     */
-    public void addConvertorFor(String name, Convertor convertor)
-    {
-        _convertors.put(name,convertor);
-    }
-
-    /**
-     * Lookup a convertor for a named class.
-     *
-     * @param name
-     *            name of the class
-     * @return a {@link JSON.Convertor} or null if none were found.
-     */
-    public Convertor getConvertorFor(String name)
-    {
-        Convertor convertor = _convertors.get(name);
-        if (convertor == null && this != DEFAULT)
-            convertor = DEFAULT.getConvertorFor(name);
-        return convertor;
-    }
-
-    public Object parse(Source source, boolean stripOuterComment)
-    {
-        int comment_state = 0; // 0=no comment, 1="/", 2="/*", 3="/* *" -1="//"
-        if (!stripOuterComment)
-            return parse(source);
-
-        int strip_state = 1; // 0=no strip, 1=wait for /*, 2= wait for */
-
-        Object o = null;
-        while (source.hasNext())
-        {
-            char c = source.peek();
-
-            // handle // or /* comment
-            if (comment_state == 1)
-            {
-                switch (c)
-                {
-                    case '/':
-                        comment_state = -1;
-                        break;
-                    case '*':
-                        comment_state = 2;
-                        if (strip_state == 1)
-                        {
-                            comment_state = 0;
-                            strip_state = 2;
-                        }
-                }
-            }
-            // handle /* */ comment
-            else if (comment_state > 1)
-            {
-                switch (c)
-                {
-                    case '*':
-                        comment_state = 3;
-                        break;
-                    case '/':
-                        if (comment_state == 3)
-                        {
-                            comment_state = 0;
-                            if (strip_state == 2)
-                                return o;
-                        }
-                        else
-                            comment_state = 2;
-                        break;
-                    default:
-                        comment_state = 2;
-                }
-            }
-            // handle // comment
-            else if (comment_state < 0)
-            {
-                switch (c)
-                {
-                    case '\r':
-                    case '\n':
-                        comment_state = 0;
-                    default:
-                        break;
-                }
-            }
-            // handle unknown
-            else
-            {
-                if (!Character.isWhitespace(c))
-                {
-                    if (c == '/')
-                        comment_state = 1;
-                    else if (c == '*')
-                        comment_state = 3;
-                    else if (o == null)
-                    {
-                        o = parse(source);
-                        continue;
-                    }
-                }
-            }
-
-            source.next();
-        }
-
-        return o;
-    }
-
-    public Object parse(Source source)
-    {
-        int comment_state = 0; // 0=no comment, 1="/", 2="/*", 3="/* *" -1="//"
-
-        while (source.hasNext())
-        {
-            char c = source.peek();
-
-            // handle // or /* comment
-            if (comment_state == 1)
-            {
-                switch (c)
-                {
-                    case '/':
-                        comment_state = -1;
-                        break;
-                    case '*':
-                        comment_state = 2;
-                }
-            }
-            // handle /* */ comment
-            else if (comment_state > 1)
-            {
-                switch (c)
-                {
-                    case '*':
-                        comment_state = 3;
-                        break;
-                    case '/':
-                        if (comment_state == 3)
-                            comment_state = 0;
-                        else
-                            comment_state = 2;
-                        break;
-                    default:
-                        comment_state = 2;
-                }
-            }
-            // handle // comment
-            else if (comment_state < 0)
-            {
-                switch (c)
-                {
-                    case '\r':
-                    case '\n':
-                        comment_state = 0;
-                        break;
-                    default:
-                        break;
-                }
-            }
-            // handle unknown
-            else
-            {
-                switch (c)
-                {
-                    case '{':
-                        return parseObject(source);
-                    case '[':
-                        return parseArray(source);
-                    case '"':
-                        return parseString(source);
-                    case '-':
-                        return parseNumber(source);
-
-                    case 'n':
-                        complete("null",source);
-                        return null;
-                    case 't':
-                        complete("true",source);
-                        return Boolean.TRUE;
-                    case 'f':
-                        complete("false",source);
-                        return Boolean.FALSE;
-                    case 'u':
-                        complete("undefined",source);
-                        return null;
-                    case 'N':
-                        complete("NaN",source);
-                        return null;
-
-                    case '/':
-                        comment_state = 1;
-                        break;
-
-                    default:
-                        if (Character.isDigit(c))
-                            return parseNumber(source);
-                        else if (Character.isWhitespace(c))
-                            break;
-                        return handleUnknown(source,c);
-                }
-            }
-            source.next();
-        }
-
-        return null;
-    }
-
-    protected Object handleUnknown(Source source, char c)
-    {
-        throw new IllegalStateException("unknown char '" + c + "'(" + (int)c + ") in " + source);
-    }
-
-    protected Object parseObject(Source source)
-    {
-        if (source.next() != '{')
-            throw new IllegalStateException();
-        Map<String, Object> map = newMap();
-
-        char next = seekTo("\"}",source);
-
-        while (source.hasNext())
-        {
-            if (next == '}')
-            {
-                source.next();
-                break;
-            }
-
-            String name = parseString(source);
-            seekTo(':',source);
-            source.next();
-
-            Object value = contextFor(name).parse(source);
-            map.put(name,value);
-
-            seekTo(",}",source);
-            next = source.next();
-            if (next == '}')
-                break;
-            else
-                next = seekTo("\"}",source);
-        }
-
-        String xclassname = (String)map.get("x-class");
-        if (xclassname != null)
-        {
-            Convertor c = getConvertorFor(xclassname);
-            if (c != null)
-                return c.fromJSON(map);
-            LOG.warn("No Convertor for x-class '{}'", xclassname);
-        }
-
-        String classname = (String)map.get("class");
-        if (classname != null)
-        {
-            try
-            {
-                Class c = Loader.loadClass(JSON.class,classname);
-                return convertTo(c,map);
-            }
-            catch (ClassNotFoundException e)
-            {
-                LOG.warn("No Class for '{}'", classname);
-            }
-        }
-
-        return map;
-    }
-
-    protected Object parseArray(Source source)
-    {
-        if (source.next() != '[')
-            throw new IllegalStateException();
-
-        int size = 0;
-        ArrayList list = null;
-        Object item = null;
-        boolean coma = true;
-
-        while (source.hasNext())
-        {
-            char c = source.peek();
-            switch (c)
-            {
-                case ']':
-                    source.next();
-                    switch (size)
-                    {
-                        case 0:
-                            return newArray(0);
-                        case 1:
-                            Object array = newArray(1);
-                            Array.set(array,0,item);
-                            return array;
-                        default:
-                            return list.toArray(newArray(list.size()));
-                    }
-
-                case ',':
-                    if (coma)
-                        throw new IllegalStateException();
-                    coma = true;
-                    source.next();
-                    break;
-
-                default:
-                    if (Character.isWhitespace(c))
-                        source.next();
-                    else
-                    {
-                        coma = false;
-                        if (size++ == 0)
-                            item = contextForArray().parse(source);
-                        else if (list == null)
-                        {
-                            list = new ArrayList();
-                            list.add(item);
-                            item = contextForArray().parse(source);
-                            list.add(item);
-                            item = null;
-                        }
-                        else
-                        {
-                            item = contextForArray().parse(source);
-                            list.add(item);
-                            item = null;
-                        }
-                    }
-            }
-
-        }
-
-        throw new IllegalStateException("unexpected end of array");
-    }
-
-    protected String parseString(Source source)
-    {
-        if (source.next() != '"')
-            throw new IllegalStateException();
-
-        boolean escape = false;
-
-        StringBuilder b = null;
-        final char[] scratch = source.scratchBuffer();
-
-        if (scratch != null)
-        {
-            int i = 0;
-            while (source.hasNext())
-            {
-                if (i >= scratch.length)
-                {
-                    // we have filled the scratch buffer, so we must
-                    // use the StringBuffer for a large string
-                    b = new StringBuilder(scratch.length * 2);
-                    b.append(scratch,0,i);
-                    break;
-                }
-
-                char c = source.next();
-
-                if (escape)
-                {
-                    escape = false;
-                    switch (c)
-                    {
-                        case '"':
-                            scratch[i++] = '"';
-                            break;
-                        case '\\':
-                            scratch[i++] = '\\';
-                            break;
-                        case '/':
-                            scratch[i++] = '/';
-                            break;
-                        case 'b':
-                            scratch[i++] = '\b';
-                            break;
-                        case 'f':
-                            scratch[i++] = '\f';
-                            break;
-                        case 'n':
-                            scratch[i++] = '\n';
-                            break;
-                        case 'r':
-                            scratch[i++] = '\r';
-                            break;
-                        case 't':
-                            scratch[i++] = '\t';
-                            break;
-                        case 'u':
-                            char uc = (char)((TypeUtil.convertHexDigit((byte)source.next()) << 12) + (TypeUtil.convertHexDigit((byte)source.next()) << 8)
-                                    + (TypeUtil.convertHexDigit((byte)source.next()) << 4) + (TypeUtil.convertHexDigit((byte)source.next())));
-                            scratch[i++] = uc;
-                            break;
-                        default:
-                            scratch[i++] = c;
-                    }
-                }
-                else if (c == '\\')
-                {
-                    escape = true;
-                }
-                else if (c == '\"')
-                {
-                    // Return string that fits within scratch buffer
-                    return toString(scratch,0,i);
-                }
-                else
-                {
-                    scratch[i++] = c;
-                }
-            }
-
-            // Missing end quote, but return string anyway ?
-            if (b == null)
-                return toString(scratch,0,i);
-        }
-        else
-            b = new StringBuilder(getStringBufferSize());
-
-        // parse large string into string buffer
-        final StringBuilder builder=b;
-        while (source.hasNext())
-        {
-            char c = source.next();
-
-            if (escape)
-            {
-                escape = false;
-                switch (c)
-                {
-                    case '"':
-                        builder.append('"');
-                        break;
-                    case '\\':
-                        builder.append('\\');
-                        break;
-                    case '/':
-                        builder.append('/');
-                        break;
-                    case 'b':
-                        builder.append('\b');
-                        break;
-                    case 'f':
-                        builder.append('\f');
-                        break;
-                    case 'n':
-                        builder.append('\n');
-                        break;
-                    case 'r':
-                        builder.append('\r');
-                        break;
-                    case 't':
-                        builder.append('\t');
-                        break;
-                    case 'u':
-                        char uc = (char)((TypeUtil.convertHexDigit((byte)source.next()) << 12) + (TypeUtil.convertHexDigit((byte)source.next()) << 8)
-                                + (TypeUtil.convertHexDigit((byte)source.next()) << 4) + (TypeUtil.convertHexDigit((byte)source.next())));
-                        builder.append(uc);
-                        break;
-                    default:
-                        builder.append(c);
-                }
-            }
-            else if (c == '\\')
-            {
-                escape = true;
-            }
-            else if (c == '\"')
-            {
-                break;
-            }
-            else
-            {
-                builder.append(c);
-            }
-        }
-        return builder.toString();
-    }
-
-    public Number parseNumber(Source source)
-    {
-        boolean minus = false;
-        long number = 0;
-        StringBuilder buffer = null;
-
-        longLoop: while (source.hasNext())
-        {
-            char c = source.peek();
-            switch (c)
-            {
-                case '0':
-                case '1':
-                case '2':
-                case '3':
-                case '4':
-                case '5':
-                case '6':
-                case '7':
-                case '8':
-                case '9':
-                    number = number * 10 + (c - '0');
-                    source.next();
-                    break;
-
-                case '-':
-                case '+':
-                    if (number != 0)
-                        throw new IllegalStateException("bad number");
-                    minus = true;
-                    source.next();
-                    break;
-
-                case '.':
-                case 'e':
-                case 'E':
-                    buffer = new StringBuilder(16);
-                    if (minus)
-                        buffer.append('-');
-                    buffer.append(number);
-                    buffer.append(c);
-                    source.next();
-                    break longLoop;
-
-                default:
-                    break longLoop;
-            }
-        }
-
-        if (buffer == null)
-            return minus ? -1 * number : number;
-
-        doubleLoop: while (source.hasNext())
-        {
-            char c = source.peek();
-            switch (c)
-            {
-                case '0':
-                case '1':
-                case '2':
-                case '3':
-                case '4':
-                case '5':
-                case '6':
-                case '7':
-                case '8':
-                case '9':
-                case '-':
-                case '.':
-                case '+':
-                case 'e':
-                case 'E':
-                    buffer.append(c);
-                    source.next();
-                    break;
-
-                default:
-                    break doubleLoop;
-            }
-        }
-        return new Double(buffer.toString());
-
-    }
-
-    protected void seekTo(char seek, Source source)
-    {
-        while (source.hasNext())
-        {
-            char c = source.peek();
-            if (c == seek)
-                return;
-
-            if (!Character.isWhitespace(c))
-                throw new IllegalStateException("Unexpected '" + c + " while seeking '" + seek + "'");
-            source.next();
-        }
-
-        throw new IllegalStateException("Expected '" + seek + "'");
-    }
-
-    protected char seekTo(String seek, Source source)
-    {
-        while (source.hasNext())
-        {
-            char c = source.peek();
-            if (seek.indexOf(c) >= 0)
-            {
-                return c;
-            }
-
-            if (!Character.isWhitespace(c))
-                throw new IllegalStateException("Unexpected '" + c + "' while seeking one of '" + seek + "'");
-            source.next();
-        }
-
-        throw new IllegalStateException("Expected one of '" + seek + "'");
-    }
-
-    protected static void complete(String seek, Source source)
-    {
-        int i = 0;
-        while (source.hasNext() && i < seek.length())
-        {
-            char c = source.next();
-            if (c != seek.charAt(i++))
-                throw new IllegalStateException("Unexpected '" + c + " while seeking  \"" + seek + "\"");
-        }
-
-        if (i < seek.length())
-            throw new IllegalStateException("Expected \"" + seek + "\"");
-    }
-
-    private final class ConvertableOutput implements Output
-    {
-        private final Appendable _buffer;
-        char c = '{';
-
-        private ConvertableOutput(Appendable buffer)
-        {
-            _buffer = buffer;
-        }
-
-        public void complete()
-        {
-            try
-            {
-                if (c == '{')
-                    _buffer.append("{}");
-                else if (c != 0)
-                    _buffer.append("}");
-            }
-            catch (IOException e)
-            {
-                throw new RuntimeException(e);
-            }
-        }
-
-        public void add(Object obj)
-        {
-            if (c == 0)
-                throw new IllegalStateException();
-            append(_buffer,obj);
-            c = 0;
-        }
-
-        public void addClass(Class type)
-        {
-            try
-            {
-                if (c == 0)
-                    throw new IllegalStateException();
-                _buffer.append(c);
-                _buffer.append("\"class\":");
-                append(_buffer,type.getName());
-                c = ',';
-            }
-            catch (IOException e)
-            {
-                throw new RuntimeException(e);
-            }
-        }
-
-        public void add(String name, Object value)
-        {
-            try
-            {
-                if (c == 0)
-                    throw new IllegalStateException();
-                _buffer.append(c);
-                QuotedStringTokenizer.quote(_buffer,name);
-                _buffer.append(':');
-                append(_buffer,value);
-                c = ',';
-            }
-            catch (IOException e)
-            {
-                throw new RuntimeException(e);
-            }
-        }
-
-        public void add(String name, double value)
-        {
-            try
-            {
-                if (c == 0)
-                    throw new IllegalStateException();
-                _buffer.append(c);
-                QuotedStringTokenizer.quote(_buffer,name);
-                _buffer.append(':');
-                appendNumber(_buffer, value);
-                c = ',';
-            }
-            catch (IOException e)
-            {
-                throw new RuntimeException(e);
-            }
-        }
-
-        public void add(String name, long value)
-        {
-            try
-            {
-                if (c == 0)
-                    throw new IllegalStateException();
-                _buffer.append(c);
-                QuotedStringTokenizer.quote(_buffer,name);
-                _buffer.append(':');
-                appendNumber(_buffer, value);
-                c = ',';
-            }
-            catch (IOException e)
-            {
-                throw new RuntimeException(e);
-            }
-        }
-
-        public void add(String name, boolean value)
-        {
-            try
-            {
-                if (c == 0)
-                    throw new IllegalStateException();
-                _buffer.append(c);
-                QuotedStringTokenizer.quote(_buffer,name);
-                _buffer.append(':');
-                appendBoolean(_buffer,value?Boolean.TRUE:Boolean.FALSE);
-                c = ',';
-            }
-            catch (IOException e)
-            {
-                throw new RuntimeException(e);
-            }
-        }
-    }
-
-    public interface Source
-    {
-        boolean hasNext();
-
-        char next();
-
-        char peek();
-
-        char[] scratchBuffer();
-    }
-
-    public static class StringSource implements Source
-    {
-        private final String string;
-        private int index;
-        private char[] scratch;
-
-        public StringSource(String s)
-        {
-            string = s;
-        }
-
-        public boolean hasNext()
-        {
-            if (index < string.length())
-                return true;
-            scratch = null;
-            return false;
-        }
-
-        public char next()
-        {
-            return string.charAt(index++);
-        }
-
-        public char peek()
-        {
-            return string.charAt(index);
-        }
-
-        @Override
-        public String toString()
-        {
-            return string.substring(0,index) + "|||" + string.substring(index);
-        }
-
-        public char[] scratchBuffer()
-        {
-            if (scratch == null)
-                scratch = new char[string.length()];
-            return scratch;
-        }
-    }
-
-    public static class ReaderSource implements Source
-    {
-        private Reader _reader;
-        private int _next = -1;
-        private char[] scratch;
-
-        public ReaderSource(Reader r)
-        {
-            _reader = r;
-        }
-
-        public void setReader(Reader reader)
-        {
-            _reader = reader;
-            _next = -1;
-        }
-
-        public boolean hasNext()
-        {
-            getNext();
-            if (_next < 0)
-            {
-                scratch = null;
-                return false;
-            }
-            return true;
-        }
-
-        public char next()
-        {
-            getNext();
-            char c = (char)_next;
-            _next = -1;
-            return c;
-        }
-
-        public char peek()
-        {
-            getNext();
-            return (char)_next;
-        }
-
-        private void getNext()
-        {
-            if (_next < 0)
-            {
-                try
-                {
-                    _next = _reader.read();
-                }
-                catch (IOException e)
-                {
-                    throw new RuntimeException(e);
-                }
-            }
-        }
-
-        public char[] scratchBuffer()
-        {
-            if (scratch == null)
-                scratch = new char[1024];
-            return scratch;
-        }
-
-    }
-
-    /**
-     * JSON Output class for use by {@link Convertible}.
-     */
-    public interface Output
-    {
-        public void addClass(Class c);
-
-        public void add(Object obj);
-
-        public void add(String name, Object value);
-
-        public void add(String name, double value);
-
-        public void add(String name, long value);
-
-        public void add(String name, boolean value);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * JSON Convertible object. Object can implement this interface in a similar
-     * way to the {@link Externalizable} interface is used to allow classes to
-     * provide their own serialization mechanism.
-     * <p>
-     * A JSON.Convertible object may be written to a JSONObject or initialized
-     * from a Map of field names to values.
-     * <p>
-     * If the JSON is to be convertible back to an Object, then the method
-     * {@link Output#addClass(Class)} must be called from within toJSON()
-     *
-     */
-    public interface Convertible
-    {
-        public void toJSON(Output out);
-
-        public void fromJSON(Map object);
-    }
-
-    /**
-     * Static JSON Convertor.
-     * <p>
-     * may be implemented to provide static convertors for objects that may be
-     * registered with
-     * {@link JSON#registerConvertor(Class, org.eclipse.jetty.util.ajax.JSON.Convertor)}
-     * . These convertors are looked up by class, interface and super class by
-     * {@link JSON#getConvertor(Class)}. Convertors should be used when the
-     * classes to be converted cannot implement {@link Convertible} or
-     * {@link Generator}.
-     */
-    public interface Convertor
-    {
-        public void toJSON(Object obj, Output out);
-
-        public Object fromJSON(Map object);
-    }
-
-    /**
-     * JSON Generator. A class that can add it's JSON representation directly to
-     * a StringBuffer. This is useful for object instances that are frequently
-     * converted and wish to avoid multiple Conversions
-     */
-    public interface Generator
-    {
-        public void addJSON(Appendable buffer);
-    }
-
-    /**
-     * A Literal JSON generator A utility instance of {@link JSON.Generator}
-     * that holds a pre-generated string on JSON text.
-     */
-    public static class Literal implements Generator
-    {
-        private String _json;
-
-        /**
-         * Construct a literal JSON instance for use by
-         * {@link JSON#toString(Object)}. If {@link Log#isDebugEnabled()} is
-         * true, the JSON will be parsed to check validity
-         *
-         * @param json
-         *            A literal JSON string.
-         */
-        public Literal(String json)
-        {
-            if (LOG.isDebugEnabled()) // TODO: Make this a configurable option on JSON instead!
-                parse(json);
-            _json = json;
-        }
-
-        @Override
-        public String toString()
-        {
-            return _json;
-        }
-
-        public void addJSON(Appendable buffer)
-        {
-            try
-            {
-                buffer.append(_json);
-            }
-            catch(IOException e)
-            {
-                throw new RuntimeException(e);
-            }
-        }
-    }
-}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/ajax/JSONDateConvertor.java b/jetty-util/src/main/java/org/eclipse/jetty/util/ajax/JSONDateConvertor.java
deleted file mode 100644
index d624d0b..0000000
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/ajax/JSONDateConvertor.java
+++ /dev/null
@@ -1,107 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.util.ajax;
-
-import java.text.DateFormatSymbols;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.Locale;
-import java.util.Map;
-import java.util.TimeZone;
-
-import org.eclipse.jetty.util.DateCache;
-import org.eclipse.jetty.util.ajax.JSON.Output;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-
-/* ------------------------------------------------------------ */
-/**
-* Convert a {@link Date} to JSON.
-* If fromJSON is true in the constructor, the JSON generated will
-* be of the form {class="java.util.Date",value="1/1/1970 12:00 GMT"}
-* If fromJSON is false, then only the string value of the date is generated.
-*/
-public class JSONDateConvertor implements JSON.Convertor
-{
-    private static final Logger LOG = Log.getLogger(JSONDateConvertor.class);
-
-    private final boolean _fromJSON;
-    private final DateCache _dateCache;
-    private final SimpleDateFormat _format;
-
-    public JSONDateConvertor()
-    {
-        this(false);
-    }
-
-    public JSONDateConvertor(boolean fromJSON)
-    {
-        this(DateCache.DEFAULT_FORMAT,TimeZone.getTimeZone("GMT"),fromJSON);
-    }
-
-    public JSONDateConvertor(String format,TimeZone zone,boolean fromJSON)
-    {
-        _dateCache=new DateCache(format);
-        _dateCache.setTimeZone(zone);
-        _fromJSON=fromJSON;
-        _format=new SimpleDateFormat(format);
-        _format.setTimeZone(zone);
-    }
-
-    public JSONDateConvertor(String format, TimeZone zone, boolean fromJSON, Locale locale)
-    {
-        _dateCache = new DateCache(format, locale);
-        _dateCache.setTimeZone(zone);
-        _fromJSON = fromJSON;
-        _format = new SimpleDateFormat(format, new DateFormatSymbols(locale));
-        _format.setTimeZone(zone);
-    }
-
-    public Object fromJSON(Map map)
-    {
-        if (!_fromJSON)
-            throw new UnsupportedOperationException();
-        try
-        {
-            synchronized(_format)
-            {
-                return _format.parseObject((String)map.get("value"));
-            }
-        }
-        catch(Exception e)
-        {
-            LOG.warn(e);
-        }
-        return null;
-    }
-
-    public void toJSON(Object obj, Output out)
-    {
-        String date = _dateCache.format((Date)obj);
-        if (_fromJSON)
-        {
-            out.addClass(obj.getClass());
-            out.add("value",date);
-        }
-        else
-        {
-            out.add(date);
-        }
-    }
-}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/annotation/ManagedAttribute.java b/jetty-util/src/main/java/org/eclipse/jetty/util/annotation/ManagedAttribute.java
new file mode 100644
index 0000000..1422d7b
--- /dev/null
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/annotation/ManagedAttribute.java
@@ -0,0 +1,82 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * The @ManagedAttribute annotation is used to indicate that a given method 
+ * exposes a JMX attribute. This annotation is placed always on the reader 
+ * method of a given attribute. Unless it is marked as read-only in the 
+ * configuration of the annotation a corresponding setter is looked for 
+ * following normal naming conventions. For example if this annotation is 
+ * on a method called getFoo() then a method called setFoo() would be looked 
+ * for and if found wired automatically into the jmx attribute.
+ *
+ */
+ at Retention(RetentionPolicy.RUNTIME)
+ at Documented
+ at Target( { ElementType.METHOD } )
+public @interface ManagedAttribute
+{
+    /**
+     * Description of the Managed Attribute
+     * 
+     * @returngit checkout
+     */
+    String value() default "Not Specified";
+    
+    /**
+     * name to use for the attribute
+     * 
+     * @return the name of the attribute
+     */
+    String name() default "";
+    
+    /**
+     * Is the managed field read-only?
+     * 
+     * Required only when a setter exists but should not be exposed via JMX
+     * 
+     * @return true if readonly
+     */
+    boolean readonly() default false;
+  
+    /**
+     * Does the managed field exist on a proxy object?
+     * 
+     * 
+     * @return true if a proxy object is involved
+     */
+    boolean proxied() default false;
+    
+    
+    /**
+     * If is a field references a setter that doesn't conform to standards for discovery
+     * it can be set here.
+     * 
+     * @return the full name of the setter in question
+     */
+    String setter() default "";
+    
+}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/annotation/ManagedObject.java b/jetty-util/src/main/java/org/eclipse/jetty/util/annotation/ManagedObject.java
new file mode 100644
index 0000000..07d9688
--- /dev/null
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/annotation/ManagedObject.java
@@ -0,0 +1,45 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * The @ManagedObject annotation is used on a class at the top level to 
+ * indicate that it should be exposed as an mbean. It has only one attribute 
+ * to it which is used as the description of the MBean. Should multiple 
+ * @ManagedObject annotations be found in the chain of influence then the 
+ * first description is used.
+ *
+ */
+ at Retention(RetentionPolicy.RUNTIME)
+ at Documented
+ at Target( { ElementType.TYPE } )
+public @interface ManagedObject
+{
+    /**
+     * Description of the Managed Object
+     */
+    String value() default "Not Specified";
+  
+}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/annotation/ManagedOperation.java b/jetty-util/src/main/java/org/eclipse/jetty/util/annotation/ManagedOperation.java
new file mode 100644
index 0000000..ae3eb40
--- /dev/null
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/annotation/ManagedOperation.java
@@ -0,0 +1,60 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * The @ManagedOperation annotation is used to indicate that a given method 
+ * should be considered a JMX operation.
+ *
+ */
+ at Retention(RetentionPolicy.RUNTIME)
+ at Documented
+ at Target( { ElementType.METHOD } )
+public @interface ManagedOperation
+{
+    /**
+     * Description of the Managed Object
+     */
+    String value() default "Not Specified";
+    
+    /**
+     * The impact of an operation. 
+     * 
+     * NOTE: Valid values are UNKNOWN, ACTION, INFO, ACTION_INFO
+     * 
+     * NOTE: applies to METHOD
+     * 
+     * @return String representing the impact of the operation
+     */
+    String impact() default "UNKNOWN";
+    
+    /**
+     * Does the managed field exist on a proxy object?
+     * 
+     * 
+     * @return true if a proxy object is involved
+     */
+    boolean proxied() default false;
+}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/annotation/Name.java b/jetty-util/src/main/java/org/eclipse/jetty/util/annotation/Name.java
new file mode 100644
index 0000000..c4c6787
--- /dev/null
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/annotation/Name.java
@@ -0,0 +1,49 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * This annotation is used to describe variables in method 
+ * signatures so that when rendered into tools like JConsole 
+ * it is clear what the parameters are. For example:
+ *
+ * public void doodle(@Name(value="doodle", description="A description of the argument") String doodle)
+ * 
+ */
+ at Retention(RetentionPolicy.RUNTIME)
+ at Documented
+ at Target( { ElementType.PARAMETER } )
+public @interface Name
+{
+    /**
+     * the name of the parameter
+     */
+    String value();
+    
+    /**
+     * the description of the parameter
+     */
+    String description() default "";
+}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/annotation/package-info.java b/jetty-util/src/main/java/org/eclipse/jetty/util/annotation/package-info.java
new file mode 100644
index 0000000..5fa17c7
--- /dev/null
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/annotation/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Util : Common Utility Annotations
+ */
+package org.eclipse.jetty.util.annotation;
+
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/component/AbstractLifeCycle.java b/jetty-util/src/main/java/org/eclipse/jetty/util/component/AbstractLifeCycle.java
index 57b40dc..591b28b 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/component/AbstractLifeCycle.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/component/AbstractLifeCycle.java
@@ -20,29 +20,32 @@ package org.eclipse.jetty.util.component;
 
 import java.util.concurrent.CopyOnWriteArrayList;
 
+import org.eclipse.jetty.util.Uptime;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
 
 /**
  * Basic implementation of the life cycle interface for components.
- * 
- * 
  */
+ at ManagedObject("Abstract Implementation of LifeCycle")
 public abstract class AbstractLifeCycle implements LifeCycle
 {
     private static final Logger LOG = Log.getLogger(AbstractLifeCycle.class);
+    
     public static final String STOPPED="STOPPED";
     public static final String FAILED="FAILED";
     public static final String STARTING="STARTING";
     public static final String STARTED="STARTED";
     public static final String STOPPING="STOPPING";
     public static final String RUNNING="RUNNING";
-    
+
+    private final CopyOnWriteArrayList<LifeCycle.Listener> _listeners=new CopyOnWriteArrayList<LifeCycle.Listener>();
     private final Object _lock = new Object();
     private final int __FAILED = -1, __STOPPED = 0, __STARTING = 1, __STARTED = 2, __STOPPING = 3;
     private volatile int _state = __STOPPED;
-    
-    protected final CopyOnWriteArrayList<LifeCycle.Listener> _listeners=new CopyOnWriteArrayList<LifeCycle.Listener>();
+    private long _stopTimeout = 30000;
 
     protected void doStart() throws Exception
     {
@@ -51,7 +54,8 @@ public abstract class AbstractLifeCycle implements LifeCycle
     protected void doStop() throws Exception
     {
     }
-
+    
+    @Override
     public final void start() throws Exception
     {
         synchronized (_lock)
@@ -64,12 +68,7 @@ public abstract class AbstractLifeCycle implements LifeCycle
                 doStart();
                 setStarted();
             }
-            catch (Exception e)
-            {
-                setFailed(e);
-                throw e;
-            }
-            catch (Error e)
+            catch (Throwable e)
             {
                 setFailed(e);
                 throw e;
@@ -77,6 +76,7 @@ public abstract class AbstractLifeCycle implements LifeCycle
         }
     }
 
+    @Override
     public final void stop() throws Exception
     {
         synchronized (_lock)
@@ -89,12 +89,7 @@ public abstract class AbstractLifeCycle implements LifeCycle
                 doStop();
                 setStopped();
             }
-            catch (Exception e)
-            {
-                setFailed(e);
-                throw e;
-            }
-            catch (Error e)
+            catch (Throwable e)
             {
                 setFailed(e);
                 throw e;
@@ -102,48 +97,57 @@ public abstract class AbstractLifeCycle implements LifeCycle
         }
     }
 
+    @Override
     public boolean isRunning()
     {
         final int state = _state;
-        
+
         return state == __STARTED || state == __STARTING;
     }
 
+    @Override
     public boolean isStarted()
     {
         return _state == __STARTED;
     }
 
+    @Override
     public boolean isStarting()
     {
         return _state == __STARTING;
     }
 
+    @Override
     public boolean isStopping()
     {
         return _state == __STOPPING;
     }
 
+    @Override
     public boolean isStopped()
     {
         return _state == __STOPPED;
     }
 
+    @Override
     public boolean isFailed()
     {
         return _state == __FAILED;
     }
 
+    @Override
     public void addLifeCycleListener(LifeCycle.Listener listener)
     {
         _listeners.add(listener);
     }
 
+    @Override
     public void removeLifeCycleListener(LifeCycle.Listener listener)
     {
         _listeners.remove(listener);
     }
-    
+
+    @ManagedAttribute(value="Lifecycle State for this instance", readonly=true)
     public String getState()
     {
         switch(_state)
@@ -156,7 +160,7 @@ public abstract class AbstractLifeCycle implements LifeCycle
         }
         return null;
     }
-    
+
     public static String getState(LifeCycle lc)
     {
         if (lc.isStarting()) return STARTING;
@@ -169,14 +173,16 @@ public abstract class AbstractLifeCycle implements LifeCycle
     private void setStarted()
     {
         _state = __STARTED;
-        LOG.debug(STARTED+" {}",this);
+        if (LOG.isDebugEnabled())
+            LOG.debug(STARTED+" @{}ms {}",Uptime.getUptime(),this);
         for (Listener listener : _listeners)
             listener.lifeCycleStarted(this);
     }
 
     private void setStarting()
     {
-        LOG.debug("starting {}",this);
+        if (LOG.isDebugEnabled())
+            LOG.debug("starting {}",this);
         _state = __STARTING;
         for (Listener listener : _listeners)
             listener.lifeCycleStarting(this);
@@ -184,7 +190,8 @@ public abstract class AbstractLifeCycle implements LifeCycle
 
     private void setStopping()
     {
-        LOG.debug("stopping {}",this);
+        if (LOG.isDebugEnabled())
+            LOG.debug("stopping {}",this);
         _state = __STOPPING;
         for (Listener listener : _listeners)
             listener.lifeCycleStopping(this);
@@ -193,7 +200,8 @@ public abstract class AbstractLifeCycle implements LifeCycle
     private void setStopped()
     {
         _state = __STOPPED;
-        LOG.debug("{} {}",STOPPED,this);
+        if (LOG.isDebugEnabled())
+            LOG.debug("{} {}",STOPPED,this);
         for (Listener listener : _listeners)
             listener.lifeCycleStopped(this);
     }
@@ -206,12 +214,23 @@ public abstract class AbstractLifeCycle implements LifeCycle
             listener.lifeCycleFailure(this,th);
     }
 
+    @ManagedAttribute(value="The stop timeout in milliseconds")
+    public long getStopTimeout()
+    {
+        return _stopTimeout;
+    }
+
+    public void setStopTimeout(long stopTimeout)
+    {
+        this._stopTimeout = stopTimeout;
+    }
+
     public static abstract class AbstractLifeCycleListener implements LifeCycle.Listener
     {
-        public void lifeCycleFailure(LifeCycle event, Throwable cause) {}
-        public void lifeCycleStarted(LifeCycle event) {}
-        public void lifeCycleStarting(LifeCycle event) {}
-        public void lifeCycleStopped(LifeCycle event) {}
-        public void lifeCycleStopping(LifeCycle event) {}
+        @Override public void lifeCycleFailure(LifeCycle event, Throwable cause) {}
+        @Override public void lifeCycleStarted(LifeCycle event) {}
+        @Override public void lifeCycleStarting(LifeCycle event) {}
+        @Override public void lifeCycleStopped(LifeCycle event) {}
+        @Override public void lifeCycleStopping(LifeCycle event) {}
     }
 }
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/component/AggregateLifeCycle.java b/jetty-util/src/main/java/org/eclipse/jetty/util/component/AggregateLifeCycle.java
deleted file mode 100644
index c8852be..0000000
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/component/AggregateLifeCycle.java
+++ /dev/null
@@ -1,441 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.util.component;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-import java.util.concurrent.CopyOnWriteArrayList;
-
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-
-/**
- * An AggregateLifeCycle is an {@link LifeCycle} implementation for a collection of contained beans.
- * <p>
- * Beans can be added the AggregateLifeCycle either as managed beans or as unmanaged beans.  A managed bean is started, stopped and destroyed with the aggregate.  
- * An unmanaged bean is associated with the aggregate for the purposes of {@link #dump()}, but it's lifecycle must be managed externally.
- * <p>
- * When a bean is added, if it is a {@link LifeCycle} and it is already started, then it is assumed to be an unmanaged bean.  
- * Otherwise the methods {@link #addBean(Object, boolean)}, {@link #manage(Object)} and {@link #unmanage(Object)} can be used to 
- * explicitly control the life cycle relationship.
- * <p>
- * If adding a bean that is shared between multiple {@link AggregateLifeCycle} instances, then it should be started before being added, so it is unmanaged, or 
- * the API must be used to explicitly set it as unmanaged.
- * <p>
- */
-public class AggregateLifeCycle extends AbstractLifeCycle implements Destroyable, Dumpable
-{
-    private static final Logger LOG = Log.getLogger(AggregateLifeCycle.class);
-    private final List<Bean> _beans=new CopyOnWriteArrayList<Bean>();
-    private boolean _started=false;
-
-    private class Bean
-    {
-        Bean(Object b) 
-        {
-            _bean=b;
-        }
-        final Object _bean;
-        volatile boolean _managed=true;
-        
-        public String toString()
-        {
-            return "{"+_bean+","+_managed+"}";
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Start the managed lifecycle beans in the order they were added.
-     * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart()
-     */
-    @Override
-    protected void doStart() throws Exception
-    {
-        for (Bean b:_beans)
-        {
-            if (b._managed && b._bean instanceof LifeCycle)
-            {
-                LifeCycle l=(LifeCycle)b._bean;
-                if (!l.isRunning())
-                    l.start();
-            }
-        }
-        // indicate that we are started, so that addBean will start other beans added.
-        _started=true;
-        super.doStart();
-    }
-    
-    /* ------------------------------------------------------------ */
-    /**
-     * Stop the joined lifecycle beans in the reverse order they were added.
-     * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart()
-     */
-    @Override
-    protected void doStop() throws Exception
-    {
-        _started=false;
-        super.doStop();
-        List<Bean> reverse = new ArrayList<Bean>(_beans);
-        Collections.reverse(reverse);
-        for (Bean b:reverse)
-        {
-            if (b._managed && b._bean instanceof LifeCycle)
-            {
-                LifeCycle l=(LifeCycle)b._bean;
-                if (l.isRunning())
-                    l.stop();
-            }
-        }
-    }
-
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Destroy the joined Destroyable beans in the reverse order they were added.
-     * @see org.eclipse.jetty.util.component.Destroyable#destroy()
-     */
-    public void destroy()
-    {
-        List<Bean> reverse = new ArrayList<Bean>(_beans);
-        Collections.reverse(reverse);
-        for (Bean b:reverse)
-        {
-            if (b._bean instanceof Destroyable && b._managed)
-            {
-                Destroyable d=(Destroyable)b._bean;
-                d.destroy();
-            }
-        }
-        _beans.clear();
-    }
-
-
-    /* ------------------------------------------------------------ */
-    /** Is the bean contained in the aggregate.
-     * @param bean
-     * @return True if the aggregate contains the bean
-     */
-    public boolean contains(Object bean)
-    {
-        for (Bean b:_beans)
-            if (b._bean==bean)
-                return true;
-        return false;
-    }
-    
-    /* ------------------------------------------------------------ */
-    /** Is the bean joined to the aggregate.
-     * @param bean
-     * @return True if the aggregate contains the bean and it is joined
-     */
-    public boolean isManaged(Object bean)
-    {
-        for (Bean b:_beans)
-            if (b._bean==bean)
-                return b._managed;
-        return false;
-    }
-    
-    /* ------------------------------------------------------------ */
-    /**
-     * Add an associated bean.
-     * If the bean is a {@link LifeCycle}, then it will be managed if it is not 
-     * already started and umanaged if it is already started. The {@link #addBean(Object, boolean)}
-     * method should be used if this is not correct, or the {@link #manage(Object)} and {@link #unmanage(Object)}
-     * methods may be used after an add to change the status.
-     * @param o the bean object to add
-     * @return true if the bean was added or false if it has already been added.
-     */
-    public boolean addBean(Object o)
-    {
-        // beans are joined unless they are started lifecycles
-        return addBean(o,!((o instanceof LifeCycle)&&((LifeCycle)o).isStarted()));
-    }
-    
-    /* ------------------------------------------------------------ */
-    /** Add an associated lifecycle.
-     * @param o The lifecycle to add
-     * @param managed True if the LifeCycle is to be joined, otherwise it will be disjoint.
-     * @return true if bean was added, false if already present.
-     */
-    public boolean addBean(Object o, boolean managed)
-    {
-        if (contains(o))
-            return false;
-        
-        Bean b = new Bean(o);
-        b._managed=managed;
-        _beans.add(b);
-        
-        if (o instanceof LifeCycle)
-        {
-            LifeCycle l=(LifeCycle)o;
-
-            // Start the bean if we are started
-            if (managed && _started)
-            {
-                try
-                {
-                    l.start();
-                }
-                catch(Exception e)
-                {
-                    throw new RuntimeException (e);
-                }
-            }
-        }
-        return true;
-    }
-    
-    /* ------------------------------------------------------------ */
-    /**
-     * Manage a bean by this aggregate, so that it is started/stopped/destroyed with the 
-     * aggregate lifecycle.  
-     * @param bean The bean to manage (must already have been added).
-     */
-    public void manage(Object bean)
-    {    
-        for (Bean b :_beans)
-        {
-            if (b._bean==bean)
-            {
-                b._managed=true;
-                return;
-            }
-        }
-        throw new IllegalArgumentException();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Unmanage a bean by this aggregate, so that it is not started/stopped/destroyed with the 
-     * aggregate lifecycle.  
-     * @param bean The bean to manage (must already have been added).
-     */
-    public void unmanage(Object bean)
-    {
-        for (Bean b :_beans)
-        {
-            if (b._bean==bean)
-            {
-                b._managed=false;
-                return;
-            }
-        }
-        throw new IllegalArgumentException();
-    }
-    
-    /* ------------------------------------------------------------ */
-    /** Get dependent beans 
-     * @return List of beans.
-     */
-    public Collection<Object> getBeans()
-    {
-        return getBeans(Object.class);
-    }
-    
-    /* ------------------------------------------------------------ */
-    /** Get dependent beans of a specific class
-     * @see #addBean(Object)
-     * @param clazz
-     * @return List of beans.
-     */
-    public <T> List<T> getBeans(Class<T> clazz)
-    {
-        ArrayList<T> beans = new ArrayList<T>();
-        for (Bean b:_beans)
-        {
-            if (clazz.isInstance(b._bean))
-                beans.add((T)(b._bean));
-        }
-        return beans;
-    }
-
-    
-    /* ------------------------------------------------------------ */
-    /** Get dependent beans of a specific class.
-     * If more than one bean of the type exist, the first is returned.
-     * @see #addBean(Object)
-     * @param clazz
-     * @return bean or null
-     */
-    public <T> T getBean(Class<T> clazz)
-    {
-        for (Bean b:_beans)
-        {
-            if (clazz.isInstance(b._bean))
-                return (T)b._bean;
-        }
-        
-        return null;
-    }
-    
-    /* ------------------------------------------------------------ */
-    /**
-     * Remove all associated bean.
-     */
-    public void removeBeans ()
-    {
-        _beans.clear();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Remove an associated bean.
-     */
-    public boolean removeBean (Object o)
-    {
-        Iterator<Bean> i = _beans.iterator();
-        while(i.hasNext())
-        {
-            Bean b=i.next();
-            if (b._bean==o)
-            {
-                _beans.remove(b);
-                return true;
-            }
-        }
-        return false;
-    }
-
-    /* ------------------------------------------------------------ */
-    public void dumpStdErr()
-    {
-        try
-        {
-            dump(System.err,"");
-        }
-        catch (IOException e)
-        {
-            LOG.warn(e);
-        }
-    }
-    
-    /* ------------------------------------------------------------ */
-    public String dump()
-    {
-        return dump(this);
-    }    
-    
-    /* ------------------------------------------------------------ */
-    public static String dump(Dumpable dumpable)
-    {
-        StringBuilder b = new StringBuilder();
-        try
-        {
-            dumpable.dump(b,"");
-        }
-        catch (IOException e)
-        {
-            LOG.warn(e);
-        }
-        return b.toString();
-    }    
-
-    /* ------------------------------------------------------------ */
-    public void dump(Appendable out) throws IOException
-    {
-        dump(out,"");
-    }
-
-    /* ------------------------------------------------------------ */
-    protected void dumpThis(Appendable out) throws IOException
-    {
-        out.append(String.valueOf(this)).append(" - ").append(getState()).append("\n");
-    }
-
-    /* ------------------------------------------------------------ */
-    public static void dumpObject(Appendable out,Object o) throws IOException
-    {
-        try
-        {
-            if (o instanceof LifeCycle)
-                out.append(String.valueOf(o)).append(" - ").append((AbstractLifeCycle.getState((LifeCycle)o))).append("\n");
-            else
-                out.append(String.valueOf(o)).append("\n");
-        }
-        catch(Throwable th)
-        {
-            out.append(" => ").append(th.toString()).append('\n');
-        }
-    }
-    
-    /* ------------------------------------------------------------ */
-    public void dump(Appendable out,String indent) throws IOException
-    {
-        dumpThis(out);
-        int size=_beans.size();
-        if (size==0)
-            return;
-        int i=0;
-        for (Bean b : _beans)
-        {
-            i++;
-
-            out.append(indent).append(" +- ");
-            if (b._managed)
-            {
-                if (b._bean instanceof Dumpable)
-                    ((Dumpable)b._bean).dump(out,indent+(i==size?"    ":" |  "));
-                else 
-                    dumpObject(out,b._bean);
-            }
-            else 
-                dumpObject(out,b._bean);
-        }
-
-        if (i!=size)
-            out.append(indent).append(" |\n");
-    }
-    
-    /* ------------------------------------------------------------ */
-    public static void dump(Appendable out,String indent,Collection<?>... collections) throws IOException
-    {
-        if (collections.length==0)
-            return;
-        int size=0;
-        for (Collection<?> c : collections)
-            size+=c.size();    
-        if (size==0)
-            return;
-
-        int i=0;
-        for (Collection<?> c : collections)
-        {
-            for (Object o : c)
-            {
-                i++;
-                out.append(indent).append(" +- ");
-
-                if (o instanceof Dumpable)
-                    ((Dumpable)o).dump(out,indent+(i==size?"    ":" |  "));
-                else 
-                    dumpObject(out,o);
-            }
-            
-            if (i!=size)
-                out.append(indent).append(" |\n");          
-        }
-    }
-}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/component/Container.java b/jetty-util/src/main/java/org/eclipse/jetty/util/component/Container.java
index bdf3b4d..bbb95a6 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/component/Container.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/component/Container.java
@@ -17,289 +17,76 @@
 //
 
 package org.eclipse.jetty.util.component;
-import java.lang.ref.WeakReference;
-import java.util.EventListener;
-import java.util.concurrent.CopyOnWriteArrayList;
 
-import org.eclipse.jetty.util.LazyList;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
+import java.util.Collection;
 
-/* ------------------------------------------------------------ */
-/** Container.
- * This class allows a containment events to be generated from update methods.
- * 
- * The style of usage is: <pre>
- *   public void setFoo(Foo foo)
- *   {
- *       getContainer().update(this,this.foo,foo,"foo");
- *       this.foo=foo;
- *   }
- *   
- *   public void setBars(Bar[] bars)
- *   {
- *       getContainer().update(this,this.bars,bars,"bar");
- *       this.bars=bars;
- *   }
- * </pre>
- */
-public class Container
+public interface Container
 {
-    private static final Logger LOG = Log.getLogger(Container.class);
-    private final CopyOnWriteArrayList<Container.Listener> _listeners=new CopyOnWriteArrayList<Container.Listener>();
-    
-    public void addEventListener(Container.Listener listener)
-    {
-        _listeners.add(listener);
-    }
-    
-    public void removeEventListener(Container.Listener listener)
-    {
-        _listeners.remove(listener);
-    }
-    
     /* ------------------------------------------------------------ */
-    /** Update single parent to child relationship.
-     * @param parent The parent of the child.
-     * @param oldChild The previous value of the child.  If this is non null and differs from <code>child</code>, then a remove event is generated.
-     * @param child The current child. If this is non null and differs from <code>oldChild</code>, then an add event is generated.
-     * @param relationship The name of the relationship
+    /**
+     * Add a bean.  If the bean is-a {@link Listener}, then also do an implicit {@link #addEventListener(Listener)}.
+     * @param o the bean object to add
+     * @return true if the bean was added, false if it was already present
      */
-    public void update(Object parent, Object oldChild, final Object child, String relationship)
-    {
-        if (oldChild!=null && !oldChild.equals(child))
-            remove(parent,oldChild,relationship);
-        if (child!=null && !child.equals(oldChild))
-            add(parent,child,relationship);
-    }
-    
-    /* ------------------------------------------------------------ */
-    /** Update single parent to child relationship.
-     * @param parent The parent of the child.
-     * @param oldChild The previous value of the child.  If this is non null and differs from <code>child</code>, then a remove event is generated.
-     * @param child The current child. If this is non null and differs from <code>oldChild</code>, then an add event is generated.
-     * @param relationship The name of the relationship
-     * @param addRemove If true add/remove is called for the new/old children as well as the relationships
-     */
-    public void update(Object parent, Object oldChild, final Object child, String relationship,boolean addRemove)
-    {
-        if (oldChild!=null && !oldChild.equals(child))
-        {
-            remove(parent,oldChild,relationship);
-            if (addRemove)
-                removeBean(oldChild);
-        }
-        
-        if (child!=null && !child.equals(oldChild))
-        {
-            if (addRemove)
-                addBean(child);
-            add(parent,child,relationship);
-        }
-    }
+    public boolean addBean(Object o);
 
-    /* ------------------------------------------------------------ */
-    /** Update multiple parent to child relationship.
-     * @param parent The parent of the child.
-     * @param oldChildren The previous array of children.  A remove event is generated for any child in this array but not in the  <code>children</code> array.
-     * This array is modified and children that remain in the new children array are nulled out of the old children array.
-     * @param children The current array of children. An add event is generated for any child in this array but not in the <code>oldChildren</code> array.
-     * @param relationship The name of the relationship
+    /**
+     * @return the list of beans known to this aggregate
+     * @see #getBean(Class)
      */
-    public void update(Object parent, Object[] oldChildren, final Object[] children, String relationship)
-    {
-        update(parent,oldChildren,children,relationship,false);
-    }
-    
-    /* ------------------------------------------------------------ */
-    /** Update multiple parent to child relationship.
-     * @param parent The parent of the child.
-     * @param oldChildren The previous array of children.  A remove event is generated for any child in this array but not in the  <code>children</code> array.
-     * This array is modified and children that remain in the new children array are nulled out of the old children array.
-     * @param children The current array of children. An add event is generated for any child in this array but not in the <code>oldChildren</code> array.
-     * @param relationship The name of the relationship
-     * @param addRemove If true add/remove is called for the new/old children as well as the relationships
+    public Collection<Object> getBeans();
+
+    /**
+     * @param clazz the class of the beans
+     * @return the list of beans of the given class (or subclass)
+     * @see #getBeans()
      */
-    public void update(Object parent, Object[] oldChildren, final Object[] children, String relationship, boolean addRemove)
-    {
-        Object[] newChildren = null;
-        if (children!=null)
-        {
-            newChildren = new Object[children.length];
-        
-            for (int i=children.length;i-->0;)
-            {
-                boolean new_child=true;
-                if (oldChildren!=null)
-                {
-                    for (int j=oldChildren.length;j-->0;)
-                    {
-                        if (children[i]!=null && children[i].equals(oldChildren[j]))
-                        {
-                            oldChildren[j]=null;
-                            new_child=false;
-                        }
-                    }
-                }
-                if (new_child)
-                    newChildren[i]=children[i];
-            }
-        }
-        
-        if (oldChildren!=null)
-        {
-            for (int i=oldChildren.length;i-->0;)
-            {
-                if (oldChildren[i]!=null)
-                {
-                    remove(parent,oldChildren[i],relationship);
-                    if (addRemove)
-                        removeBean(oldChildren[i]);
-                }
-            }
-        }
-        
-        if (newChildren!=null)
-        {
-            for (int i=0;i<newChildren.length;i++)
-                if (newChildren[i]!=null)
-                {
-                    if (addRemove)
-                        addBean(newChildren[i]);
-                    add(parent,newChildren[i],relationship);
-                }
-        }
-    }
+    public <T> Collection<T> getBeans(Class<T> clazz);
 
-    /* ------------------------------------------------------------ */
-    public void addBean(Object obj)
-    {
-        if (_listeners!=null)
-        {
-            for (int i=0; i<LazyList.size(_listeners); i++)
-            {
-                Listener listener=(Listener)LazyList.get(_listeners, i);
-                listener.addBean(obj);
-            }
-        }
-    }
+    /**
+     * @param clazz the class of the bean
+     * @return the first bean of a specific class (or subclass), or null if no such bean exist
+     */
+    public <T> T getBean(Class<T> clazz);
 
-    /* ------------------------------------------------------------ */
-    public void removeBean(Object obj)
-    {
-        if (_listeners!=null)
-        {
-            for (int i=0; i<LazyList.size(_listeners); i++)
-                ((Listener)LazyList.get(_listeners, i)).removeBean(obj);
-        }
-    }
-    
-    /* ------------------------------------------------------------ */
-    /** Add a parent child relationship
-     * @param parent
-     * @param child
-     * @param relationship
+    /**
+     * Removes the given bean.
+     * If the bean is-a {@link Listener}, then also do an implicit {@link #removeEventListener(Listener)}.
+     * @return whether the bean was removed
      */
-    private void add(Object parent, Object child, String relationship)
-    {
-        if (LOG.isDebugEnabled())
-            LOG.debug("Container "+parent+" + "+child+" as "+relationship);
-        if (_listeners!=null)
-        {
-            Relationship event=new Relationship(this,parent,child,relationship);
-            for (int i=0; i<LazyList.size(_listeners); i++)
-                ((Listener)LazyList.get(_listeners, i)).add(event);
-        }
-    }
+    public boolean removeBean(Object o);
     
-    /* ------------------------------------------------------------ */
-    /** remove a parent child relationship
-     * @param parent
-     * @param child
-     * @param relationship
+    /**
+     * Add an event listener. 
+     * @see Container#addBean(Object)
+     * @param listener
      */
-    private void remove(Object parent, Object child, String relationship)
-    {
-        if (LOG.isDebugEnabled())
-            LOG.debug("Container "+parent+" - "+child+" as "+relationship);
-        if (_listeners!=null)
-        {
-            Relationship event=new Relationship(this,parent,child,relationship);
-            for (int i=0; i<LazyList.size(_listeners); i++)
-                ((Listener)LazyList.get(_listeners, i)).remove(event);
-        }
-    }
+    public void addEventListener(Listener listener);
     
-    /* ------------------------------------------------------------ */
-    /** A Container event.
-     * @see Listener
+    /**
+     * Remove an event listener. 
+     * @see Container#removeBean(Object)
+     * @param listener
      */
-    public static class Relationship
+    public void removeEventListener(Listener listener);
+
+    /**
+     * A listener for Container events.
+     * If an added bean implements this interface it will receive the events
+     * for this container.
+     */
+    public interface Listener
     {
-        private final WeakReference<Object> _parent;
-        private final WeakReference<Object> _child;
-        private String _relationship;
-        private Container _container;
-        
-        private Relationship(Container container, Object parent,Object child, String relationship)
-        {
-            _container=container;
-            _parent=new WeakReference<Object>(parent);
-            _child=new WeakReference<Object>(child);
-            _relationship=relationship;
-        }
-        
-        public Container getContainer()
-        {
-            return _container;
-        }
-        
-        public Object getChild()
-        {
-            return _child.get();
-        }
-        
-        public Object getParent()
-        {
-            return _parent.get();
-        }
-        
-        public String getRelationship()
-        {
-            return _relationship;
-        }
-        
-        @Override
-        public String toString()
-        {
-            return _parent+"---"+_relationship+"-->"+_child;
-        }
-        
-        @Override
-        public int hashCode()
-        {
-            return _parent.hashCode()+_child.hashCode()+_relationship.hashCode();
-        }
-        
-        @Override
-        public boolean equals(Object o)
-        {
-            if (o==null || !(o instanceof Relationship))
-                return false;
-            Relationship r = (Relationship)o;
-            return r._parent.get()==_parent.get() && r._child.get()==_child.get() && r._relationship.equals(_relationship);
-        }
+        void beanAdded(Container parent,Object child);
+        void beanRemoved(Container parent,Object child);
     }
     
-    /* ------------------------------------------------------------ */
-    /** Listener.
-     * A listener for Container events.
+    /**
+     * Inherited Listener.
+     * If an added bean implements this interface, then it will 
+     * be added to all contained beans that are themselves Containers
      */
-    public interface Listener extends EventListener
+    public interface InheritedListener extends Listener
     {
-        public void addBean(Object bean);
-        public void removeBean(Object bean);
-        public void add(Container.Relationship relationship);
-        public void remove(Container.Relationship relationship);
     }
 }
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/component/ContainerLifeCycle.java b/jetty-util/src/main/java/org/eclipse/jetty/util/component/ContainerLifeCycle.java
new file mode 100644
index 0000000..772c03f
--- /dev/null
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/component/ContainerLifeCycle.java
@@ -0,0 +1,813 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.component;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.annotation.ManagedOperation;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * An ContainerLifeCycle is an {@link LifeCycle} implementation for a collection of contained beans.
+ * <p>
+ * Beans can be added the ContainerLifeCycle either as managed beans or as unmanaged beans.  A managed bean is started, stopped and destroyed with the aggregate.
+ * An unmanaged bean is associated with the aggregate for the purposes of {@link #dump()}, but it's lifecycle must be managed externally.
+ * <p>
+ * When a {@link LifeCycle} bean is added without a managed state being specified the state is determined heuristically:
+ * <ul>
+ *   <li>If the added bean is running, it will be added as an unmanaged bean.
+ *   <li>If the added bean is !running and the container is !running, it will be added as an AUTO bean (see below).
+ *   <li>If the added bean is !running and the container is starting, it will be added as an managed bean and will be started (this handles the frequent case of 
+ *   new beans added during calls to doStart).
+ *   <li>If the added bean is !running and the container is started, it will be added as an unmanaged bean.
+ * </ul>
+ * When the container is started, then all contained managed beans will also be started.  Any contained Auto beans 
+ * will be check for their status and if already started will be switched unmanaged beans, else they will be 
+ * started and switched to managed beans.  Beans added after a container is started are not started and their state needs to
+ * be explicitly managed.
+ * <p>
+ * When stopping the container, a contained bean will be stopped by this aggregate only if it
+ * is started by this aggregate.
+ * <p>
+ * The methods {@link #addBean(Object, boolean)}, {@link #manage(Object)} and {@link #unmanage(Object)} can be used to
+ * explicitly control the life cycle relationship.
+ * <p>
+ * If adding a bean that is shared between multiple {@link ContainerLifeCycle} instances, then it should be started before being added, so it is unmanaged, or
+ * the API must be used to explicitly set it as unmanaged.
+ * <p>
+ * This class also provides utility methods to dump deep structures of objects.  It the dump, the following symbols are used to indicate the type of contained object:
+ * <pre>
+ * SomeContainerLifeCycleInstance
+ *   +- contained POJO instance
+ *   += contained MANAGED object, started and stopped with this instance
+ *   +~ referenced UNMANAGED object, with separate lifecycle
+ *   +? referenced AUTO object that could become MANAGED or UNMANAGED.
+ * </pre>
+ */
+
+/* ------------------------------------------------------------ */
+/**
+ */
+ at ManagedObject("Implementation of Container and LifeCycle")
+public class ContainerLifeCycle extends AbstractLifeCycle implements Container, Destroyable, Dumpable
+{
+    private static final Logger LOG = Log.getLogger(ContainerLifeCycle.class);
+    private final List<Bean> _beans = new CopyOnWriteArrayList<>();
+    private final List<Container.Listener> _listeners = new CopyOnWriteArrayList<>();
+    private boolean _doStarted = false;
+
+
+    public ContainerLifeCycle()
+    {
+    }
+
+    /**
+     * Starts the managed lifecycle beans in the order they were added.
+     */
+    @Override
+    protected void doStart() throws Exception
+    {
+        // indicate that we are started, so that addBean will start other beans added.
+        _doStarted = true;
+
+        // start our managed and auto beans
+        for (Bean b : _beans)
+        {
+            if (b._bean instanceof LifeCycle)
+            {
+                LifeCycle l = (LifeCycle)b._bean;
+                switch(b._managed)
+                {
+                    case MANAGED:
+                        if (!l.isRunning())
+                            start(l);
+                        break;
+                    case AUTO:
+                        if (l.isRunning())
+                            unmanage(b);
+                        else
+                        {
+                            manage(b);
+                            start(l);
+                        }
+                        break;
+                }
+            }
+        }
+
+        super.doStart();
+    }
+
+    /**
+     * Starts the given lifecycle.
+     *
+     * @param l
+     * @throws Exception
+     */
+    protected void start(LifeCycle l) throws Exception
+    {
+        l.start();
+    }
+    
+    /**
+     * Stops the given lifecycle.
+     *
+     * @param l
+     * @throws Exception
+     */
+    protected void stop(LifeCycle l) throws Exception
+    {
+        l.stop();
+    }
+
+    /**
+     * Stops the managed lifecycle beans in the reverse order they were added.
+     */
+    @Override
+    protected void doStop() throws Exception
+    {
+        _doStarted = false;
+        super.doStop();
+        List<Bean> reverse = new ArrayList<>(_beans);
+        Collections.reverse(reverse);
+        for (Bean b : reverse)
+        {
+            if (b._managed==Managed.MANAGED && b._bean instanceof LifeCycle)
+            {
+                LifeCycle l = (LifeCycle)b._bean;
+                if (l.isRunning())
+                    stop(l);
+            }
+        }
+    }
+
+    /**
+     * Destroys the managed Destroyable beans in the reverse order they were added.
+     */
+    @Override
+    public void destroy()
+    {
+        List<Bean> reverse = new ArrayList<>(_beans);
+        Collections.reverse(reverse);
+        for (Bean b : reverse)
+        {
+            if (b._bean instanceof Destroyable && (b._managed==Managed.MANAGED || b._managed==Managed.POJO))
+            {
+                Destroyable d = (Destroyable)b._bean;
+                d.destroy();
+            }
+        }
+        _beans.clear();
+    }
+
+
+    /**
+     * @param bean the bean to test
+     * @return whether this aggregate contains the bean
+     */
+    public boolean contains(Object bean)
+    {
+        for (Bean b : _beans)
+            if (b._bean == bean)
+                return true;
+        return false;
+    }
+
+    /**
+     * @param bean the bean to test
+     * @return whether this aggregate contains and manages the bean
+     */
+    public boolean isManaged(Object bean)
+    {
+        for (Bean b : _beans)
+            if (b._bean == bean)
+                return b.isManaged();
+        return false;
+    }
+
+    /**
+     * Adds the given bean, detecting whether to manage it or not.
+     * If the bean is a {@link LifeCycle}, then it will be managed if it is not
+     * already started and not managed if it is already started.
+     * The {@link #addBean(Object, boolean)}
+     * method should be used if this is not correct, or the {@link #manage(Object)} and {@link #unmanage(Object)}
+     * methods may be used after an add to change the status.
+     *
+     * @param o the bean object to add
+     * @return true if the bean was added, false if it was already present
+     */
+    @Override
+    public boolean addBean(Object o)
+    {
+        if (o instanceof LifeCycle)
+        {
+            LifeCycle l = (LifeCycle)o;
+            return addBean(o,l.isRunning()?Managed.UNMANAGED:Managed.AUTO);
+        }
+
+        return addBean(o,Managed.POJO);
+    }
+
+    /**
+     * Adds the given bean, explicitly managing it or not.
+     *
+     * @param o       The bean object to add
+     * @param managed whether to managed the lifecycle of the bean
+     * @return true if the bean was added, false if it was already present
+     */
+    public boolean addBean(Object o, boolean managed)
+    {
+        if (o instanceof LifeCycle)
+            return addBean(o,managed?Managed.MANAGED:Managed.UNMANAGED);
+        return addBean(o,managed?Managed.POJO:Managed.UNMANAGED);
+    }
+
+    public boolean addBean(Object o, Managed managed)
+    {
+        if (contains(o))
+            return false;
+
+        Bean new_bean = new Bean(o);
+
+        // if the bean is a Listener
+        if (o instanceof Container.Listener)
+            addEventListener((Container.Listener)o);
+
+        // Add the bean
+        _beans.add(new_bean);
+
+        // Tell existing listeners about the new bean
+        for (Container.Listener l:_listeners)
+            l.beanAdded(this,o);
+
+        try
+        {
+            switch (managed)
+            {
+                case UNMANAGED:
+                    unmanage(new_bean);
+                    break;
+
+                case MANAGED:
+                    manage(new_bean);
+
+                    if (isStarting() && _doStarted)
+                    {
+                        LifeCycle l = (LifeCycle)o;
+                        if (!l.isRunning())
+                            start(l);
+                    }
+                    break;
+
+                case AUTO:
+                    if (o instanceof LifeCycle)
+                    {
+                        LifeCycle l = (LifeCycle)o;
+                        if (isStarting())
+                        {
+                            if (l.isRunning())
+                                unmanage(new_bean);
+                            else if (_doStarted)
+                            {
+                                manage(new_bean);
+                                start(l);
+                            }
+                            else
+                                new_bean._managed=Managed.AUTO;      
+                        }
+                        else if (isStarted())
+                            unmanage(new_bean);
+                        else
+                            new_bean._managed=Managed.AUTO;
+                    }
+                    else
+                        new_bean._managed=Managed.POJO;
+                    break;
+
+                case POJO:
+                    new_bean._managed=Managed.POJO;
+            }
+        }
+        catch (RuntimeException | Error e)
+        {
+            throw e;
+        }
+        catch (Exception e)
+        {
+            throw new RuntimeException(e);
+        }
+
+        if (LOG.isDebugEnabled())
+            LOG.debug("{} added {}",this,new_bean);
+
+        return true;
+    }
+
+    
+    /* ------------------------------------------------------------ */
+    /** Add a managed lifecycle.
+     * <p>This is a conveniance method that uses addBean(lifecycle,true)
+     * and then ensures that the added bean is started iff this container
+     * is running.  Exception from nested calls to start are caught and 
+     * wrapped as RuntimeExceptions
+     * @param lifecycle
+     */
+    public void addManaged(LifeCycle lifecycle)
+    {
+        addBean(lifecycle,true);
+        try
+        {
+            if (isRunning() && !lifecycle.isRunning())
+                start(lifecycle);
+        }
+        catch (RuntimeException | Error e)
+        {
+            throw e;
+        }
+        catch (Exception e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public void addEventListener(Container.Listener listener)
+    {
+        if (_listeners.contains(listener))
+            return;
+        
+        _listeners.add(listener);
+
+        // tell it about existing beans
+        for (Bean b:_beans)
+        {
+            listener.beanAdded(this,b._bean);
+
+            // handle inheritance
+            if (listener instanceof InheritedListener && b.isManaged() && b._bean instanceof Container)
+            {
+                if (b._bean instanceof ContainerLifeCycle)
+                     ((ContainerLifeCycle)b._bean).addBean(listener, false);
+                 else
+                     ((Container)b._bean).addBean(listener);
+            }
+        }
+    }
+
+    /**
+     * Manages a bean already contained by this aggregate, so that it is started/stopped/destroyed with this
+     * aggregate.
+     *
+     * @param bean The bean to manage (must already have been added).
+     */
+    public void manage(Object bean)
+    {
+        for (Bean b : _beans)
+        {
+            if (b._bean == bean)
+            {
+                manage(b);
+                return;
+            }
+        }
+        throw new IllegalArgumentException("Unknown bean " + bean);
+    }
+
+    private void manage(Bean bean)
+    {
+        if (bean._managed!=Managed.MANAGED)
+        {
+            bean._managed=Managed.MANAGED;
+
+            if (bean._bean instanceof Container)
+            {
+                for (Container.Listener l:_listeners)
+                {
+                    if (l instanceof InheritedListener)
+                    {
+                        if (bean._bean instanceof ContainerLifeCycle)
+                            ((ContainerLifeCycle)bean._bean).addBean(l,false);
+                        else
+                            ((Container)bean._bean).addBean(l);
+                    }
+                }
+            }
+
+            if (bean._bean instanceof AbstractLifeCycle)
+            {
+                ((AbstractLifeCycle)bean._bean).setStopTimeout(getStopTimeout());
+            }
+        }
+    }
+
+    /**
+     * Unmanages a bean already contained by this aggregate, so that it is not started/stopped/destroyed with this
+     * aggregate.
+     *
+     * @param bean The bean to unmanage (must already have been added).
+     */
+    public void unmanage(Object bean)
+    {
+        for (Bean b : _beans)
+        {
+            if (b._bean == bean)
+            {
+                unmanage(b);
+                return;
+            }
+        }
+        throw new IllegalArgumentException("Unknown bean " + bean);
+    }
+
+    private void unmanage(Bean bean)
+    {
+        if (bean._managed!=Managed.UNMANAGED)
+        {
+            if (bean._managed==Managed.MANAGED && bean._bean instanceof Container)
+            {
+                for (Container.Listener l:_listeners)
+                {
+                    if (l instanceof InheritedListener)
+                        ((Container)bean._bean).removeBean(l);
+                }
+            }
+            bean._managed=Managed.UNMANAGED;
+        }
+    }
+
+    @Override
+    public Collection<Object> getBeans()
+    {
+        return getBeans(Object.class);
+    }
+
+    public void setBeans(Collection<Object> beans)
+    {
+        for (Object bean : beans)
+            addBean(bean);
+    }
+
+    @Override
+    public <T> Collection<T> getBeans(Class<T> clazz)
+    {
+        ArrayList<T> beans = new ArrayList<>();
+        for (Bean b : _beans)
+        {
+            if (clazz.isInstance(b._bean))
+                beans.add(clazz.cast(b._bean));
+        }
+        return beans;
+    }
+
+    @Override
+    public <T> T getBean(Class<T> clazz)
+    {
+        for (Bean b : _beans)
+        {
+            if (clazz.isInstance(b._bean))
+                return clazz.cast(b._bean);
+        }
+        return null;
+    }
+
+    /**
+     * Removes all bean
+     */
+    public void removeBeans()
+    {
+        ArrayList<Bean> beans= new ArrayList<>(_beans);
+        for (Bean b : beans)
+            remove(b);
+    }
+
+    private Bean getBean(Object o)
+    {
+        for (Bean b : _beans)
+        {
+            if (b._bean == o)
+                return b;
+        }
+        return null;
+    }
+
+    @Override
+    public boolean removeBean(Object o)
+    {
+        Bean b=getBean(o);
+        return b!=null && remove(b);
+    }
+
+    private boolean remove(Bean bean)
+    {
+        if (_beans.remove(bean))
+        {
+            boolean wasManaged = bean.isManaged();
+            
+            unmanage(bean);
+
+            for (Container.Listener l:_listeners)
+                l.beanRemoved(this,bean._bean);
+
+            if (bean._bean instanceof Container.Listener)
+                removeEventListener((Container.Listener)bean._bean);
+
+            // stop managed beans
+            if (wasManaged && bean._bean instanceof LifeCycle)
+            {
+                try
+                {
+                    stop((LifeCycle)bean._bean);
+                }
+                catch(RuntimeException | Error e)
+                {
+                    throw e;
+                }
+                catch (Exception e)
+                {
+                    throw new RuntimeException(e);
+                }
+            }
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public void removeEventListener(Container.Listener listener)
+    {
+        if (_listeners.remove(listener))
+        {
+            // remove existing beans
+            for (Bean b:_beans)
+            {
+                listener.beanRemoved(this,b._bean);
+
+                if (listener instanceof InheritedListener && b.isManaged() && b._bean instanceof Container)
+                    ((Container)b._bean).removeBean(listener);
+            }
+        }
+    }
+
+    @Override
+    public void setStopTimeout(long stopTimeout)
+    {
+        super.setStopTimeout(stopTimeout);
+        for (Bean bean : _beans)
+        {
+            if (bean.isManaged() && bean._bean instanceof AbstractLifeCycle)
+                ((AbstractLifeCycle)bean._bean).setStopTimeout(stopTimeout);
+        }
+    }
+
+    /**
+     * Dumps to {@link System#err}.
+     * @see #dump()
+     */
+    @ManagedOperation("Dump the object to stderr")
+    public void dumpStdErr()
+    {
+        try
+        {
+            dump(System.err, "");
+        }
+        catch (IOException e)
+        {
+            LOG.warn(e);
+        }
+    }
+
+    @Override
+    @ManagedOperation("Dump the object to a string")
+    public String dump()
+    {
+        return dump(this);
+    }
+
+    public static String dump(Dumpable dumpable)
+    {
+        StringBuilder b = new StringBuilder();
+        try
+        {
+            dumpable.dump(b, "");
+        }
+        catch (IOException e)
+        {
+            LOG.warn(e);
+        }
+        return b.toString();
+    }
+
+    public void dump(Appendable out) throws IOException
+    {
+        dump(out, "");
+    }
+
+    protected void dumpThis(Appendable out) throws IOException
+    {
+        out.append(String.valueOf(this)).append(" - ").append(getState()).append("\n");
+    }
+
+    public static void dumpObject(Appendable out, Object o) throws IOException
+    {
+        try
+        {
+            if (o instanceof LifeCycle)
+                out.append(String.valueOf(o)).append(" - ").append((AbstractLifeCycle.getState((LifeCycle)o))).append("\n");
+            else
+                out.append(String.valueOf(o)).append("\n");
+        }
+        catch (Throwable th)
+        {
+            out.append(" => ").append(th.toString()).append('\n');
+        }
+    }
+
+    @Override
+    public void dump(Appendable out, String indent) throws IOException
+    {
+        dumpBeans(out,indent);
+    }
+
+    protected void dumpBeans(Appendable out, String indent, Collection<?>... collections) throws IOException
+    {
+        dumpThis(out);
+        int size = _beans.size();
+        for (Collection<?> c : collections)
+            size += c.size();
+        if (size == 0)
+            return;
+        int i = 0;
+        for (Bean b : _beans)
+        {
+            i++;
+
+            switch(b._managed)
+            {
+                case POJO:
+                    out.append(indent).append(" +- ");
+                    if (b._bean instanceof Dumpable)
+                        ((Dumpable)b._bean).dump(out, indent + (i == size ? "    " : " |  "));
+                    else
+                        dumpObject(out, b._bean);
+                    break;
+
+                case MANAGED:
+                    out.append(indent).append(" += ");
+                    if (b._bean instanceof Dumpable)
+                        ((Dumpable)b._bean).dump(out, indent + (i == size ? "    " : " |  "));
+                    else
+                        dumpObject(out, b._bean);
+                    break;
+
+                case UNMANAGED:
+                    out.append(indent).append(" +~ ");
+                    dumpObject(out, b._bean);
+                    break;
+
+                case AUTO:
+                    out.append(indent).append(" +? ");
+                    if (b._bean instanceof Dumpable)
+                        ((Dumpable)b._bean).dump(out, indent + (i == size ? "    " : " |  "));
+                    else
+                        dumpObject(out, b._bean);
+                    break;
+
+            }
+        }
+
+        if (i<size)
+            out.append(indent).append(" |\n");
+
+        for (Collection<?> c : collections)
+        {
+            for (Object o : c)
+            {
+                i++;
+                out.append(indent).append(" +> ");
+
+                if (o instanceof Dumpable)
+                    ((Dumpable)o).dump(out, indent + (i == size ? "    " : " |  "));
+                else
+                    dumpObject(out, o);
+            }
+        }
+    }
+
+    public static void dump(Appendable out, String indent, Collection<?>... collections) throws IOException
+    {
+        if (collections.length == 0)
+            return;
+        int size = 0;
+        for (Collection<?> c : collections)
+            size += c.size();
+        if (size == 0)
+            return;
+
+        int i = 0;
+        for (Collection<?> c : collections)
+        {
+            for (Object o : c)
+            {
+                i++;
+                out.append(indent).append(" +- ");
+
+                if (o instanceof Dumpable)
+                    ((Dumpable)o).dump(out, indent + (i == size ? "    " : " |  "));
+                else
+                    dumpObject(out, o);
+            }
+        }
+    }
+
+
+    enum Managed { POJO, MANAGED, UNMANAGED, AUTO };
+
+    private static class Bean
+    {
+        private final Object _bean;
+        private volatile Managed _managed = Managed.POJO;
+
+        private Bean(Object b)
+        {
+            _bean = b;
+        }
+
+        public boolean isManaged()
+        {
+            return _managed==Managed.MANAGED;
+        }
+
+        @Override
+        public String toString()
+        {
+            return String.format("{%s,%s}", _bean, _managed);
+        }
+    }
+
+    public void updateBean(Object oldBean, final Object newBean)
+    {
+        if (newBean!=oldBean)
+        {
+            if (oldBean!=null)
+                removeBean(oldBean);
+            if (newBean!=null)
+                addBean(newBean);
+        }
+    }
+
+    public void updateBeans(Object[] oldBeans, final Object[] newBeans)
+    {
+        // remove oldChildren not in newChildren
+        if (oldBeans!=null)
+        {
+            loop: for (Object o:oldBeans)
+            {
+                if (newBeans!=null)
+                {
+                    for (Object n:newBeans)
+                        if (o==n)
+                            continue loop;
+                }
+                removeBean(o);
+            }
+        }
+
+        // add new beans not in old
+        if (newBeans!=null)
+        {
+            loop: for (Object n:newBeans)
+            {
+                if (oldBeans!=null)
+                {
+                    for (Object o:oldBeans)
+                        if (o==n)
+                            continue loop;
+                }
+                addBean(n);
+            }
+        }
+    }
+}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/component/Dumpable.java b/jetty-util/src/main/java/org/eclipse/jetty/util/component/Dumpable.java
index 872f1a6..451d67e 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/component/Dumpable.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/component/Dumpable.java
@@ -20,8 +20,14 @@ package org.eclipse.jetty.util.component;
 
 import java.io.IOException;
 
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.annotation.ManagedOperation;
+
+ at ManagedObject("Dumpable Object")
 public interface Dumpable
 {
+    @ManagedOperation(value="Dump the nested Object state as a String", impact="INFO")
     String dump();
+    
     void dump(Appendable out,String indent) throws IOException;
 }
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/component/FileDestroyable.java b/jetty-util/src/main/java/org/eclipse/jetty/util/component/FileDestroyable.java
index d5e3f90..79457d6 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/component/FileDestroyable.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/component/FileDestroyable.java
@@ -50,7 +50,10 @@ public class FileDestroyable implements Destroyable
     
     public void addFile(String file) throws IOException
     {
-        _files.add(Resource.newResource(file).getFile());
+        try(Resource r = Resource.newResource(file);)
+        {
+            _files.add(r.getFile());
+        }
     }
     
     public void addFile(File file)
@@ -65,7 +68,10 @@ public class FileDestroyable implements Destroyable
     
     public void removeFile(String file) throws IOException
     {
-        _files.remove(Resource.newResource(file).getFile());
+        try(Resource r = Resource.newResource(file);)
+        {
+            _files.remove(r.getFile());
+        }
     }
     
     public void removeFile(File file)
@@ -73,13 +79,15 @@ public class FileDestroyable implements Destroyable
         _files.remove(file);
     }
     
+    @Override
     public void destroy()
     {
         for (File file : _files)
         {
             if (file.exists())
             {
-                LOG.debug("Destroy {}",file);
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Destroy {}",file);
                 IO.delete(file);
             }
         }
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/component/FileNoticeLifeCycleListener.java b/jetty-util/src/main/java/org/eclipse/jetty/util/component/FileNoticeLifeCycleListener.java
index b811196..9e83170 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/component/FileNoticeLifeCycleListener.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/component/FileNoticeLifeCycleListener.java
@@ -19,6 +19,7 @@
 package org.eclipse.jetty.util.component;
 
 import java.io.FileWriter;
+import java.io.Writer;
 
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
@@ -30,7 +31,7 @@ import org.eclipse.jetty.util.log.Logger;
  */
 public class FileNoticeLifeCycleListener implements LifeCycle.Listener
 {
-    Logger LOG = Log.getLogger(FileNoticeLifeCycleListener.class);
+    private static final Logger LOG = Log.getLogger(FileNoticeLifeCycleListener.class);
     
     private final String _filename;
     
@@ -41,11 +42,9 @@ public class FileNoticeLifeCycleListener implements LifeCycle.Listener
 
     private void writeState(String action, LifeCycle lifecycle)
     {
-        try
+        try (Writer out = new FileWriter(_filename,true))
         {
-            FileWriter out = new FileWriter(_filename,true);
             out.append(action).append(" ").append(lifecycle.toString()).append("\n");
-            out.close();
         }
         catch(Exception e)
         {
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/component/Graceful.java b/jetty-util/src/main/java/org/eclipse/jetty/util/component/Graceful.java
new file mode 100644
index 0000000..d4421ba
--- /dev/null
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/component/Graceful.java
@@ -0,0 +1,29 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.component;
+
+import java.util.concurrent.Future;
+
+/* ------------------------------------------------------------ */
+/* A Lifecycle that can be gracefully shutdown.
+ */
+public interface Graceful
+{
+    public Future<Void> shutdown();
+}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/component/LifeCycle.java b/jetty-util/src/main/java/org/eclipse/jetty/util/component/LifeCycle.java
index 6f83abb..0713c6e 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/component/LifeCycle.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/component/LifeCycle.java
@@ -20,6 +20,9 @@ package org.eclipse.jetty.util.component;
 
 import java.util.EventListener;
 
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.annotation.ManagedOperation;
+
 /* ------------------------------------------------------------ */
 /**
  * The lifecycle interface for generic components.
@@ -29,6 +32,7 @@ import java.util.EventListener;
  *
  * 
  */
+ at ManagedObject("Lifecycle Interface for startable components")
 public interface LifeCycle
 {
     /* ------------------------------------------------------------ */
@@ -39,6 +43,7 @@ public interface LifeCycle
      * @see #stop()
      * @see #isFailed()
      */
+    @ManagedOperation(value="Starts the instance", impact="ACTION")
     public void start()
         throws Exception;
 
@@ -52,6 +57,7 @@ public interface LifeCycle
      * @see #start()
      * @see #isFailed()
      */
+    @ManagedOperation(value="Stops the instance", impact="ACTION")
     public void stop()
         throws Exception;
 
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/component/package-info.java b/jetty-util/src/main/java/org/eclipse/jetty/util/component/package-info.java
new file mode 100644
index 0000000..77b020d
--- /dev/null
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/component/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Util : Jetty Lifecycle Management
+ */
+package org.eclipse.jetty.util.component;
+
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/log/AbstractLogger.java b/jetty-util/src/main/java/org/eclipse/jetty/util/log/AbstractLogger.java
index 82498dc..4ea4771 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/log/AbstractLogger.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/log/AbstractLogger.java
@@ -25,6 +25,7 @@ package org.eclipse.jetty.util.log;
  */
 public abstract class AbstractLogger implements Logger
 {
+    @Override
     public final Logger getLogger(String name)
     {
         if (isBlank(name))
@@ -74,4 +75,12 @@ public abstract class AbstractLogger implements Logger
         }
         return true;
     }
+    
+    public void debug(String msg, long arg)
+    {
+        if (isDebugEnabled())
+        {
+            debug(msg,new Object[] { new Long(arg) });
+        }
+    }
 }
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/log/JavaUtilLog.java b/jetty-util/src/main/java/org/eclipse/jetty/util/log/JavaUtilLog.java
index 0b22bc2..c079fd4 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/log/JavaUtilLog.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/log/JavaUtilLog.java
@@ -57,7 +57,8 @@ public class JavaUtilLog extends AbstractLogger
 
     public void warn(String msg, Object... args)
     {
-        _logger.log(Level.WARNING, format(msg, args));
+        if (_logger.isLoggable(Level.WARNING))
+            _logger.log(Level.WARNING,format(msg,args));
     }
 
     public void warn(Throwable thrown)
@@ -72,7 +73,8 @@ public class JavaUtilLog extends AbstractLogger
 
     public void info(String msg, Object... args)
     {
-        _logger.log(Level.INFO, format(msg, args));
+        if (_logger.isLoggable(Level.INFO))
+            _logger.log(Level.INFO, format(msg, args));
     }
 
     public void info(Throwable thrown)
@@ -105,7 +107,14 @@ public class JavaUtilLog extends AbstractLogger
 
     public void debug(String msg, Object... args)
     {
-        _logger.log(Level.FINE, format(msg, args));
+        if (_logger.isLoggable(Level.FINE))
+            _logger.log(Level.FINE,format(msg, args));
+    }
+
+    public void debug(String msg, long arg)
+    {
+        if (_logger.isLoggable(Level.FINE))
+            _logger.log(Level.FINE,format(msg, arg));
     }
 
     public void debug(Throwable thrown)
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/log/Log.java b/jetty-util/src/main/java/org/eclipse/jetty/util/log/Log.java
index a99195d..b1163e6 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/log/Log.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/log/Log.java
@@ -24,17 +24,17 @@ import java.lang.reflect.Method;
 import java.net.URL;
 import java.security.AccessController;
 import java.security.PrivilegedAction;
-import java.util.Collection;
 import java.util.Collections;
 import java.util.Enumeration;
-import java.util.HashMap;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Properties;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 
-import org.eclipse.jetty.util.IO;
 import org.eclipse.jetty.util.Loader;
+import org.eclipse.jetty.util.Uptime;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
 
 /**
  * Logging.
@@ -60,20 +60,20 @@ public class Log
     /**
      * Logging Configuration Properties
      */
-    protected static Properties __props;
+    protected static final Properties __props;
     /**
      * The {@link Logger} implementation class name
      */
     public static String __logClass;
     /**
-     * Legacy flag indicating if {@link Log#ignore(Throwable)} methods produce any output in the {@link Logger}s
+     * Legacy flag indicating if {@link Logger#ignore(Throwable)} methods produce any output in the {@link Logger}s
      */
     public static boolean __ignored;
 
     /**
      * Hold loggers only.
      */
-    private final static ConcurrentMap<String, Logger> __loggers = new ConcurrentHashMap<String, Logger>();
+    private final static ConcurrentMap<String, Logger> __loggers = new ConcurrentHashMap<>();
 
 
     static
@@ -91,24 +91,18 @@ public class Log
                  * configuration of the Log class in situations where access to the System.properties are
                  * either too late or just impossible.
                  */
-                URL testProps = Loader.getResource(Log.class,"jetty-logging.properties",true);
-                if (testProps != null)
+                loadProperties("jetty-logging.properties",__props);
+
+                /*
+                 * Next see if an OS specific jetty-logging.properties object exists in the classpath. 
+                 * This really for setting up test specific logging behavior based on OS.
+                 */
+                String osName = System.getProperty("os.name");
+                // NOTE: cannot use jetty-util's StringUtil as that initializes logging itself.
+                if (osName != null && osName.length() > 0)
                 {
-                    InputStream in = null;
-                    try
-                    {
-                        in = testProps.openStream();
-                        __props.load(in);
-                    }
-                    catch (IOException e)
-                    {
-                        System.err.println("Unable to load " + testProps);
-                        e.printStackTrace(System.err);
-                    }
-                    finally
-                    {
-                        IO.close(in);
-                    }
+                    osName = osName.toLowerCase(Locale.ENGLISH).replace(' ','-');
+                    loadProperties("jetty-logging-" + osName + ".properties",__props);
                 }
 
                 /* Now load the System.properties as-is into the __props, these values will override
@@ -120,9 +114,11 @@ public class Log
                 {
                     String key = systemKeyEnum.nextElement();
                     String val = System.getProperty(key);
-                    //protect against application code insertion of non-String values (returned as null)
+                    // protect against application code insertion of non-String values (returned as null)
                     if (val != null)
+                    {
                         __props.setProperty(key,val);
+                    }
                 }
 
                 /* Now use the configuration properties to configure the Log statics
@@ -133,42 +129,62 @@ public class Log
             }
         });
     }
-
-    private static Logger LOG;
-    private static boolean __initialized;
-
-    public static boolean initialized()
+    
+    private static void loadProperties(String resourceName, Properties props)
     {
-        if (LOG != null)
+        URL testProps = Loader.getResource(Log.class,resourceName);
+        if (testProps != null)
         {
-            return true;
+            try (InputStream in = testProps.openStream())
+            {
+                Properties p = new Properties();
+                p.load(in);
+                for (Object key : p.keySet())
+                {
+                    Object value = p.get(key);
+                    if (value != null)
+                    {
+                        props.put(key,value);
+                    }
+                }
+            }
+            catch (IOException e)
+            {
+                System.err.println("[WARN] Error loading logging config: " + testProps);
+                e.printStackTrace(System.err);
+            }
         }
+    }
+
+    private static Logger LOG;
+    private static boolean __initialized=false;
 
+    public static void initialized()
+    {   
         synchronized (Log.class)
         {
             if (__initialized)
-            {
-                return LOG != null;
-            }
+                return;
             __initialized = true;
-        }
 
-        try
-        {
-            Class<?> log_class = Loader.loadClass(Log.class, __logClass);
-            if (LOG == null || !LOG.getClass().equals(log_class))
+            try
             {
-                LOG = (Logger)log_class.newInstance();
-                LOG.debug("Logging to {} via {}", LOG, log_class.getName());
+                Class<?> log_class = Loader.loadClass(Log.class, __logClass);
+                if (LOG == null || !LOG.getClass().equals(log_class))
+                {
+                    LOG = (Logger)log_class.newInstance();
+                    LOG.debug("Logging to {} via {}", LOG, log_class.getName());
+                }
+            }
+            catch(Throwable e)
+            {
+                // Unable to load specified Logger implementation, default to standard logging.
+                initStandardLogging(e);
             }
-        }
-        catch(Throwable e)
-        {
-            // Unable to load specified Logger implementation, default to standard logging.
-            initStandardLogging(e);
-        }
 
-        return LOG != null;
+            if (LOG!=null)
+                LOG.info(String.format("Logging initialized @%dms",Uptime.getUptime()));
+        }
     }
 
     private static void initStandardLogging(Throwable e)
@@ -176,7 +192,7 @@ public class Log
         Class<?> log_class;
         if(e != null && __ignored)
         {
-            e.printStackTrace();
+            e.printStackTrace(System.err);
         }
 
         if (LOG == null)
@@ -186,22 +202,18 @@ public class Log
             LOG.debug("Logging to {} via {}", LOG, log_class.getName());
         }
     }
-
-    public static void setLog(Logger log)
-    {
-        Log.LOG = log;
-    }
-
-    /**
-     * @deprecated anonymous logging is deprecated, use a named {@link Logger} obtained from {@link #getLogger(String)}
-     */
-    @Deprecated
+    
     public static Logger getLog()
     {
         initialized();
         return LOG;
     }
 
+    public static void setLog(Logger log)
+    {
+        Log.LOG = log;
+    }
+
     /**
      * Get the root logger.
      * @return the root logger
@@ -255,165 +267,6 @@ public class Log
     }
 
     /**
-     * @deprecated anonymous logging is deprecated, use a named {@link Logger} obtained from {@link #getLogger(String)}
-     */
-    @Deprecated
-    public static void debug(Throwable th)
-    {
-        if (!isDebugEnabled())
-            return;
-        LOG.debug(EXCEPTION, th);
-    }
-
-    /**
-     * @deprecated anonymous logging is deprecated, use a named {@link Logger} obtained from {@link #getLogger(String)}
-     */
-    @Deprecated
-    public static void debug(String msg)
-    {
-        if (!initialized())
-            return;
-        LOG.debug(msg);
-    }
-
-    /**
-     * @deprecated anonymous logging is deprecated, use a named {@link Logger} obtained from {@link #getLogger(String)}
-     */
-    @Deprecated
-    public static void debug(String msg, Object arg)
-    {
-        if (!initialized())
-            return;
-        LOG.debug(msg, arg);
-    }
-
-    /**
-     * @deprecated anonymous logging is deprecated, use a named {@link Logger} obtained from {@link #getLogger(String)}
-     */
-    @Deprecated
-    public static void debug(String msg, Object arg0, Object arg1)
-    {
-        if (!initialized())
-            return;
-        LOG.debug(msg, arg0, arg1);
-    }
-
-    /**
-     * Ignore an exception unless trace is enabled.
-     * This works around the problem that log4j does not support the trace level.
-     * @param thrown the Throwable to ignore
-     */
-    /**
-     * @deprecated anonymous logging is deprecated, use a named {@link Logger} obtained from {@link #getLogger(String)}
-     */
-    @Deprecated
-    public static void ignore(Throwable thrown)
-    {
-        if (!initialized())
-            return;
-        LOG.ignore(thrown);
-    }
-
-    /**
-     * @deprecated anonymous logging is deprecated, use a named {@link Logger} obtained from {@link #getLogger(String)}
-     */
-    @Deprecated
-    public static void info(String msg)
-    {
-        if (!initialized())
-            return;
-        LOG.info(msg);
-    }
-
-    /**
-     * @deprecated anonymous logging is deprecated, use a named {@link Logger} obtained from {@link #getLogger(String)}
-     */
-    @Deprecated
-    public static void info(String msg, Object arg)
-    {
-        if (!initialized())
-            return;
-        LOG.info(msg, arg);
-    }
-
-    /**
-     * @deprecated anonymous logging is deprecated, use a named {@link Logger} obtained from {@link #getLogger(String)}
-     */
-    @Deprecated
-    public static void info(String msg, Object arg0, Object arg1)
-    {
-        if (!initialized())
-            return;
-        LOG.info(msg, arg0, arg1);
-    }
-
-    /**
-     * @deprecated anonymous logging is deprecated, use a named {@link Logger} obtained from {@link #getLogger(String)}
-     */
-    @Deprecated
-    public static boolean isDebugEnabled()
-    {
-        if (!initialized())
-            return false;
-        return LOG.isDebugEnabled();
-    }
-
-    /**
-     * @deprecated anonymous logging is deprecated, use a named {@link Logger} obtained from {@link #getLogger(String)}
-     */
-    @Deprecated
-    public static void warn(String msg)
-    {
-        if (!initialized())
-            return;
-        LOG.warn(msg);
-    }
-
-    /**
-     * @deprecated anonymous logging is deprecated, use a named {@link Logger} obtained from {@link #getLogger(String)}
-     */
-    @Deprecated
-    public static void warn(String msg, Object arg)
-    {
-        if (!initialized())
-            return;
-        LOG.warn(msg, arg);
-    }
-
-    /**
-     * @deprecated anonymous logging is deprecated, use a named {@link Logger} obtained from {@link #getLogger(String)}
-     */
-    @Deprecated
-    public static void warn(String msg, Object arg0, Object arg1)
-    {
-        if (!initialized())
-            return;
-        LOG.warn(msg, arg0, arg1);
-    }
-
-    /**
-     * @deprecated anonymous logging is deprecated, use a named {@link Logger} obtained from {@link #getLogger(String)}
-     */
-    @Deprecated
-    public static void warn(String msg, Throwable th)
-    {
-        if (!initialized())
-            return;
-        LOG.warn(msg, th);
-    }
-
-    /**
-     * @deprecated anonymous logging is deprecated, use a named {@link Logger} obtained from {@link #getLogger(String)}
-     */
-    @Deprecated
-    public static void warn(Throwable th)
-    {
-        if (!initialized())
-            return;
-        LOG.warn(EXCEPTION, th);
-    }
-
-    /**
      * Obtain a named Logger based on the fully qualified class name.
      *
      * @param clazz
@@ -432,8 +285,7 @@ public class Log
      */
     public static Logger getLogger(String name)
     {
-        if (!initialized())
-            return null;
+        initialized();
 
         if(name==null)
             return LOG;
@@ -455,6 +307,7 @@ public class Log
      *
      * @return a map of all configured {@link Logger} instances
      */
+    @ManagedAttribute("list of all instantiated loggers")
     public static Map<String, Logger> getLoggers()
     {
         return Collections.unmodifiableMap(__loggers);
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/log/Logger.java b/jetty-util/src/main/java/org/eclipse/jetty/util/log/Logger.java
index 40f70ff..9da7788 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/log/Logger.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/log/Logger.java
@@ -85,6 +85,15 @@ public interface Logger
      * @param args the optional arguments
      */
     public void debug(String msg, Object... args);
+    
+
+    /**
+     * Formats and logs at debug level.
+     * avoids autoboxing of integers
+     * @param msg the formatting string
+     * @param value long value
+     */
+    public void debug(String msg, long value);
 
     /**
      * Logs the given Throwable information at debug level
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/log/LoggerLog.java b/jetty-util/src/main/java/org/eclipse/jetty/util/log/LoggerLog.java
index fb5396c..8b436c6 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/log/LoggerLog.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/log/LoggerLog.java
@@ -151,6 +151,7 @@ public class LoggerLog extends AbstractLogger
         }
     }
 
+    
     public void debug(String msg, Object... args)
     {
         if (!_debug)
@@ -186,6 +187,21 @@ public class LoggerLog extends AbstractLogger
         }
     }
 
+    public void debug(String msg, long value)
+    {
+        if (!_debug)
+            return;
+
+        try
+        {
+            _debugMAA.invoke(_logger, new Object[]{new Long(value)});
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+    
     public void ignore(Throwable ignored)
     {
         if (Log.isIgnored())
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/log/Slf4jLog.java b/jetty-util/src/main/java/org/eclipse/jetty/util/log/Slf4jLog.java
index d322801..104e958 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/log/Slf4jLog.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/log/Slf4jLog.java
@@ -88,6 +88,12 @@ public class Slf4jLog extends AbstractLogger
     {
         _logger.debug(msg, args);
     }
+    
+    public void debug(String msg, long arg)
+    {
+        if (isDebugEnabled())
+            _logger.debug(msg, new Object[]{new Long(arg)});
+    }
 
     public void debug(Throwable thrown)
     {
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/log/StacklessLogging.java b/jetty-util/src/main/java/org/eclipse/jetty/util/log/StacklessLogging.java
new file mode 100644
index 0000000..82cb8f0
--- /dev/null
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/log/StacklessLogging.java
@@ -0,0 +1,69 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.log;
+
+/**
+ * A try-with-resources compatible layer for {@link StdErrLog#setHideStacks(boolean) hiding stacktraces} within the scope of the <code>try</code> block when
+ * logging with {@link StdErrLog} implementation.
+ * <p>
+ * Use of other logging implementation cause no effect when using this class
+ * <p>
+ * Example:
+ * 
+ * <pre>
+ * try (StacklessLogging scope = new StacklessLogging(EventDriver.class,Noisy.class))
+ * {
+ *     doActionThatCausesStackTraces();
+ * }
+ * </pre>
+ */
+public class StacklessLogging implements AutoCloseable
+{
+    private final Class<?> clazzes[];
+
+    public StacklessLogging(Class<?>... classesToSquelch)
+    {
+        this.clazzes = classesToSquelch;
+        hideStacks(true);
+    }
+
+    @Override
+    public void close() throws Exception
+    {
+        hideStacks(false);
+    }
+
+    private void hideStacks(boolean hide)
+    {
+        for (Class<?> clazz : clazzes)
+        {
+            Logger log = Log.getLogger(clazz);
+            if (log == null)
+            {
+                // not interested in classes without loggers
+                continue;
+            }
+            if (log instanceof StdErrLog)
+            {
+                // only operate on loggers that are of type StdErrLog
+                ((StdErrLog)log).setHideStacks(hide);
+            }
+        }
+    }
+}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/log/StdErrLog.java b/jetty-util/src/main/java/org/eclipse/jetty/util/log/StdErrLog.java
index 685c8fb..dfc9168 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/log/StdErrLog.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/log/StdErrLog.java
@@ -23,20 +23,73 @@ import java.security.AccessControlException;
 import java.util.Properties;
 
 import org.eclipse.jetty.util.DateCache;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
 
 /**
- * StdErr Logging. This implementation of the Logging facade sends all logs to StdErr with minimal formatting.
+ * StdErr Logging implementation. 
  * <p>
- * If the system property "org.eclipse.jetty.LEVEL" is set to one of the following (ALL, DEBUG, INFO, WARN), then set
- * the eclipse jetty root level logger level to that specified level. (Default level is INFO)
+ * A Jetty {@link Logger} that sends all logs to STDERR ({@link System#err}) with basic formatting.
  * <p>
- * If the system property "org.eclipse.jetty.util.log.SOURCE" is set, then the source method/file of a log is logged.
- * For named debuggers, the system property name+".SOURCE" is checked, eg "org.eclipse.jetty.util.log.stderr.SOURCE". 
- * If it is not not set, then "org.eclipse.jetty.util.log.SOURCE" is used as the default.
+ * Supports named loggers, and properties based configuration. 
  * <p>
- * If the system property "org.eclipse.jetty.util.log.stderr.LONG" is set, then the full, unabbreviated name of the logger is
- * used for logging.
+ * Configuration Properties:
+ * <dl>
+ *   <dt>${name|hierarchy}.LEVEL=(ALL|DEBUG|INFO|WARN|OFF)</dt>
+ *   <dd>
+ *   Sets the level that the Logger should log at.<br/>
+ *   Names can be a package name, or a fully qualified class name.<br/>
+ *   Default: INFO<br/>
+ *   <br/>
+ *   Examples:
+ *   <dl>
+ *   <dt>org.eclipse.jetty.LEVEL=WARN</dt>
+ *   <dd>indicates that all of the jetty specific classes, in any package that 
+ *   starts with <code>org.eclipse.jetty</code> should log at level WARN.</dd>
+ *   <dt>org.eclipse.jetty.io.ChannelEndPoint.LEVEL=ALL</dt>
+ *   <dd>indicates that the specific class, ChannelEndPoint, should log all
+ *   logging events that it can generate, including DEBUG, INFO, WARN (and even special
+ *   internally ignored exception cases).</dd>
+ *   </dl>  
+ *   </dd>
+ *   
+ *   <dt>${name}.SOURCE=(true|false)</dt>
+ *   <dd>
+ *   Logger specific, attempt to print the java source file name and line number
+ *   where the logging event originated from.<br/>
+ *   Name must be a fully qualified class name (package name hierarchy is not supported
+ *   by this configurable)<br/>
+ *   Warning: this is a slow operation and will have an impact on performance!<br/>
+ *   Default: false
+ *   </dd>
+ *   
+ *   <dt>${name}.STACKS=(true|false)</dt>
+ *   <dd>
+ *   Logger specific, control the display of stacktraces.<br/>
+ *   Name must be a fully qualified class name (package name hierarchy is not supported
+ *   by this configurable)<br/>
+ *   Default: true
+ *   </dd>
+ *   
+ *   <dt>org.eclipse.jetty.util.log.stderr.SOURCE=(true|false)</dt>
+ *   <dd>Special Global Configuration, attempt to print the java source file name and line number
+ *   where the logging event originated from.<br/>
+ *   Default: false
+ *   </dd>
+ *   
+ *   <dt>org.eclipse.jetty.util.log.stderr.LONG=(true|false)</dt>
+ *   <dd>Special Global Configuration, when true, output logging events to STDERR using
+ *   long form, fully qualified class names.  when false, use abbreviated package names<br/>
+ *   Default: false
+ *   </dd>
+ *   <dt>org.eclipse.jetty.util.log.stderr.ESCAPE=(true|false)</dt>
+ *   <dd>Global Configuration, when true output logging events to STDERR are always
+ *   escaped so that control characters are replaced with '?";  '\r' with '<' and '\n' replaced '|'<br/>
+ *   Default: true
+ *   </dd>
+ * </dl>
  */
+ at ManagedObject("Jetty StdErr Logging Implementation")
 public class StdErrLog extends AbstractLogger
 {
     private static final String EOL = System.getProperty("line.separator");
@@ -46,11 +99,12 @@ public class StdErrLog extends AbstractLogger
     private final static boolean __source = Boolean.parseBoolean(Log.__props.getProperty("org.eclipse.jetty.util.log.SOURCE",
             Log.__props.getProperty("org.eclipse.jetty.util.log.stderr.SOURCE","false")));
     private final static boolean __long = Boolean.parseBoolean(Log.__props.getProperty("org.eclipse.jetty.util.log.stderr.LONG","false"));
+    private final static boolean __escape = Boolean.parseBoolean(Log.__props.getProperty("org.eclipse.jetty.util.log.stderr.ESCAPE","true"));
 
     static
     {
         __props.putAll(Log.__props);
-        
+
         String deprecatedProperties[] =
         { "DEBUG", "org.eclipse.jetty.util.log.DEBUG", "org.eclipse.jetty.util.log.stderr.DEBUG" };
 
@@ -77,6 +131,7 @@ public class StdErrLog extends AbstractLogger
     public static final int LEVEL_DEBUG = 1;
     public static final int LEVEL_INFO = 2;
     public static final int LEVEL_WARN = 3;
+    public static final int LEVEL_OFF = 10;
 
     private int _level = LEVEL_INFO;
     // Level that this Logger was configured as (remembered in special case of .setDebugEnabled())
@@ -91,16 +146,56 @@ public class StdErrLog extends AbstractLogger
     private final String _abbrevname;
     private boolean _hideStacks = false;
 
+    /**
+     * Obtain a StdErrLog reference for the specified class, a convenience method used most often during testing to allow for control over a specific logger.
+     * <p>
+     * Must be actively using StdErrLog as the Logger implementation.
+     * 
+     * @param clazz
+     *            the Class reference for the logger to use.
+     * @return the StdErrLog logger
+     * @throws RuntimeException
+     *             if StdErrLog is not the active Logger implementation.
+     */
+    public static StdErrLog getLogger(Class<?> clazz)
+    {
+        Logger log = Log.getLogger(clazz);
+        if (log instanceof StdErrLog)
+        {
+            return (StdErrLog)log;
+        }
+        throw new RuntimeException("Logger for " + clazz + " is not of type StdErrLog");
+    }
+
+    /**
+     * Construct an anonymous StdErrLog (no name).
+     * <p>
+     * NOTE: Discouraged usage!
+     */
     public StdErrLog()
     {
         this(null);
     }
 
+    /**
+     * Construct a named StdErrLog using the {@link Log} defined properties
+     * 
+     * @param name
+     *            the name of the logger
+     */
     public StdErrLog(String name)
     {
         this(name,__props);
     }
 
+    /**
+     * Construct a named Logger using the provided properties to configure logger.
+     * 
+     * @param name
+     *            the name of the logger
+     * @param props
+     *            the configuration properties
+     */
     public StdErrLog(String name, Properties props)
     {
         if (props!=null && props!=__props)
@@ -112,12 +207,24 @@ public class StdErrLog extends AbstractLogger
 
         try
         {
-            _source = Boolean.parseBoolean(props.getProperty(_name + ".SOURCE",Boolean.toString(_source)));
+            String source = getLoggingProperty(props,_name,"SOURCE");
+            _source = source==null?__source:Boolean.parseBoolean(source);
         }
         catch (AccessControlException ace)
         {
             _source = __source;
         }
+
+        try
+        {
+            // allow stacktrace display to be controlled by properties as well
+            String stacks = getLoggingProperty(props,_name,"STACKS");
+            _hideStacks = stacks==null?false:!Boolean.parseBoolean(stacks);
+        }
+        catch (AccessControlException ignore)
+        {
+            /* ignore */
+        }        
     }
 
     /**
@@ -132,6 +239,12 @@ public class StdErrLog extends AbstractLogger
      */
     public static int getLoggingLevel(Properties props, final String name)
     {
+        if ((props == null) || (props.isEmpty()))
+        {
+            // Default Logging Level
+            return getLevelId("log.LEVEL","INFO");
+        }
+        
         // Calculate the level this named logger should operate under.
         // Checking with FQCN first, then each package segment from longest to shortest.
         String nameSegment = name;
@@ -161,6 +274,26 @@ public class StdErrLog extends AbstractLogger
         // Default Logging Level
         return getLevelId("log.LEVEL",props.getProperty("log.LEVEL","INFO"));
     }
+    
+    public static String getLoggingProperty(Properties props, String name, String property)
+    {
+        // Calculate the level this named logger should operate under.
+        // Checking with FQCN first, then each package segment from longest to shortest.
+        String nameSegment = name;
+
+        while ((nameSegment != null) && (nameSegment.length() > 0))
+        {
+            String s = props.getProperty(nameSegment+"."+property);
+            if (s!=null)
+                return s;
+
+            // Trim and try again.
+            int idx = nameSegment.lastIndexOf('.');
+            nameSegment = (idx >= 0)?nameSegment.substring(0,idx):null;
+        }
+
+        return null;
+    }
 
     protected static int getLevelId(String levelSegment, String levelName)
     {
@@ -185,8 +318,12 @@ public class StdErrLog extends AbstractLogger
         {
             return LEVEL_WARN;
         }
+        else if ("OFF".equalsIgnoreCase(levelStr))
+        {
+            return LEVEL_OFF;
+        }
 
-        System.err.println("Unknown StdErrLog level [" + levelSegment + "]=[" + levelStr + "], expecting only [ALL, DEBUG, INFO, WARN] as values.");
+        System.err.println("Unknown StdErrLog level [" + levelSegment + "]=[" + levelStr + "], expecting only [ALL, DEBUG, INFO, WARN, OFF] as values.");
         return -1;
     }
 
@@ -319,6 +456,7 @@ public class StdErrLog extends AbstractLogger
         }
     }
 
+    @ManagedAttribute("is debug enabled for root logger Log.LOG")
     public boolean isDebugEnabled()
     {
         return (_level <= LEVEL_DEBUG);
@@ -328,6 +466,7 @@ public class StdErrLog extends AbstractLogger
      * Legacy interface where a programmatic configuration of the logger level
      * is done as a wholesale approach.
      */
+    @Override
     public void setDebugEnabled(boolean enabled)
     {
         if (enabled)
@@ -343,7 +482,7 @@ public class StdErrLog extends AbstractLogger
         else
         {
             this._level = this._configuredLevel;
-            
+
             for (Logger log : Log.getLoggers().values())
             {
                 if (log.getName().startsWith(getName()) && log instanceof StdErrLog)
@@ -386,6 +525,16 @@ public class StdErrLog extends AbstractLogger
         }
     }
 
+    public void debug(String msg, long arg)
+    {
+        if (isDebugEnabled())
+        {
+            StringBuilder buffer = new StringBuilder(64);
+            format(buffer,":DBUG:",msg,arg);
+            (_stderr==null?System.err:_stderr).println(buffer);
+        }
+    }
+    
     public void debug(Throwable thrown)
     {
         debug("",thrown);
@@ -403,8 +552,9 @@ public class StdErrLog extends AbstractLogger
 
     private void format(StringBuilder buffer, String level, String msg, Object... args)
     {
-        String d = _dateCache.now();
-        int ms = _dateCache.lastMs();
+        long now = System.currentTimeMillis();
+        int ms=(int)(now%1000);
+        String d = _dateCache.formatNow(now);
         tag(buffer,d,ms,level);
         format(buffer,msg,args);
     }
@@ -414,7 +564,7 @@ public class StdErrLog extends AbstractLogger
         format(buffer,level,msg);
         if (isHideStacks())
         {
-            format(buffer,String.valueOf(thrown));
+            format(buffer,": "+String.valueOf(thrown));
         }
         else
         {
@@ -448,6 +598,7 @@ public class StdErrLog extends AbstractLogger
             buffer.append(_abbrevname);
         }
         buffer.append(':');
+        buffer.append(Thread.currentThread().getName()).append(": ");
         if (_source)
         {
             Throwable source = new Throwable();
@@ -513,32 +664,42 @@ public class StdErrLog extends AbstractLogger
 
     private void escape(StringBuilder builder, String string)
     {
-        for (int i = 0; i < string.length(); ++i)
+        if (__escape)
         {
-            char c = string.charAt(i);
-            if (Character.isISOControl(c))
+            for (int i = 0; i < string.length(); ++i)
             {
-                if (c == '\n')
-                {
-                    builder.append('|');
-                }
-                else if (c == '\r')
+                char c = string.charAt(i);
+                if (Character.isISOControl(c))
                 {
-                    builder.append('<');
+                    if (c == '\n')
+                    {
+                        builder.append('|');
+                    }
+                    else if (c == '\r')
+                    {
+                        builder.append('<');
+                    }
+                    else
+                    {
+                        builder.append('?');
+                    }
                 }
                 else
                 {
-                    builder.append('?');
+                    builder.append(c);
                 }
             }
-            else
-            {
-                builder.append(c);
-            }
         }
+        else
+            builder.append(string);
     }
 
-    private void format(StringBuilder buffer, Throwable thrown)
+    protected void format(StringBuilder buffer, Throwable thrown)
+    {
+        format(buffer,thrown,"");
+    }
+    
+    protected void format(StringBuilder buffer, Throwable thrown, String indent)
     {
         if (thrown == null)
         {
@@ -546,20 +707,26 @@ public class StdErrLog extends AbstractLogger
         }
         else
         {
-            buffer.append(EOL);
+            buffer.append(EOL).append(indent);
             format(buffer,thrown.toString());
             StackTraceElement[] elements = thrown.getStackTrace();
             for (int i = 0; elements != null && i < elements.length; i++)
             {
-                buffer.append(EOL).append("\tat ");
+                buffer.append(EOL).append(indent).append("\tat ");
                 format(buffer,elements[i].toString());
             }
 
+            for (Throwable suppressed:thrown.getSuppressed())
+            {
+                buffer.append(EOL).append(indent).append("Suppressed: ");
+                format(buffer,suppressed,"\t|"+indent);
+            }
+            
             Throwable cause = thrown.getCause();
             if (cause != null && cause != thrown)
             {
-                buffer.append(EOL).append("Caused by: ");
-                format(buffer,cause);
+                buffer.append(EOL).append(indent).append("Caused by: ");
+                format(buffer,cause,indent);
             }
         }
     }
@@ -568,15 +735,14 @@ public class StdErrLog extends AbstractLogger
     /**
      * Create a Child Logger of this Logger.
      */
+    @Override
     protected Logger newLogger(String fullname)
     {
         StdErrLog logger = new StdErrLog(fullname);
         // Preserve configuration for new loggers configuration
         logger.setPrintLongNames(_printLongNames);
-        // Let Level come from configured Properties instead - sel.setLevel(_level);
-        logger.setSource(_source);
         logger._stderr = this._stderr;
-        
+
         // Force the child to have any programmatic configuration
         if (_level!=_configuredLevel)
             logger._level=_level;
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/log/package-info.java b/jetty-util/src/main/java/org/eclipse/jetty/util/log/package-info.java
new file mode 100644
index 0000000..d8cf1f0
--- /dev/null
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/log/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Util : Common Logging Integrations
+ */
+package org.eclipse.jetty.util.log;
+
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/package-info.java b/jetty-util/src/main/java/org/eclipse/jetty/util/package-info.java
new file mode 100644
index 0000000..1567c5b
--- /dev/null
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Util : Common Utility Classes
+ */
+package org.eclipse.jetty.util;
+
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/preventers/AWTLeakPreventer.java b/jetty-util/src/main/java/org/eclipse/jetty/util/preventers/AWTLeakPreventer.java
index de6135a..4285ed3 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/preventers/AWTLeakPreventer.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/preventers/AWTLeakPreventer.java
@@ -40,7 +40,8 @@ public class AWTLeakPreventer extends AbstractLeakPreventer
     @Override
     public void prevent(ClassLoader loader)
     {
-        LOG.debug("Pinning classloader for java.awt.EventQueue using "+loader);
+        if (LOG.isDebugEnabled())
+            LOG.debug("Pinning classloader for java.awt.EventQueue using "+loader);
         Toolkit.getDefaultToolkit();
     }
 
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/preventers/AppContextLeakPreventer.java b/jetty-util/src/main/java/org/eclipse/jetty/util/preventers/AppContextLeakPreventer.java
index 03ba52e..ad3698b 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/preventers/AppContextLeakPreventer.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/preventers/AppContextLeakPreventer.java
@@ -34,7 +34,8 @@ public class AppContextLeakPreventer extends AbstractLeakPreventer
     @Override
     public void prevent(ClassLoader loader)
     {
-        LOG.debug("Pinning classloader for AppContext.getContext() with "+loader);
+        if (LOG.isDebugEnabled())
+            LOG.debug("Pinning classloader for AppContext.getContext() with "+loader);
         ImageIO.getUseCache();
     }
 
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/preventers/DriverManagerLeakPreventer.java b/jetty-util/src/main/java/org/eclipse/jetty/util/preventers/DriverManagerLeakPreventer.java
index 8e06d48..77ee20d 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/preventers/DriverManagerLeakPreventer.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/preventers/DriverManagerLeakPreventer.java
@@ -35,7 +35,8 @@ public class DriverManagerLeakPreventer extends AbstractLeakPreventer
     @Override
     public void prevent(ClassLoader loader)
     {
-        LOG.debug("Pinning DriverManager classloader with "+loader);
+        if (LOG.isDebugEnabled())
+            LOG.debug("Pinning DriverManager classloader with "+loader);
         DriverManager.getDrivers();
     }
 
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/preventers/GCThreadLeakPreventer.java b/jetty-util/src/main/java/org/eclipse/jetty/util/preventers/GCThreadLeakPreventer.java
index 5e52691..f4594c4 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/preventers/GCThreadLeakPreventer.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/preventers/GCThreadLeakPreventer.java
@@ -47,7 +47,7 @@ public class GCThreadLeakPreventer extends AbstractLeakPreventer
     {
         try
         {
-            Class clazz = Class.forName("sun.misc.GC");
+            Class<?> clazz = Class.forName("sun.misc.GC");
             Method requestLatency = clazz.getMethod("requestLatency", new Class[] {long.class});
             requestLatency.invoke(null, Long.valueOf(Long.MAX_VALUE-1));
         }
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/preventers/package-info.java b/jetty-util/src/main/java/org/eclipse/jetty/util/preventers/package-info.java
new file mode 100644
index 0000000..2f9ad32
--- /dev/null
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/preventers/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Util : Common Memory Leak Prevention Tooling
+ */
+package org.eclipse.jetty.util.preventers;
+
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/resource/BadResource.java b/jetty-util/src/main/java/org/eclipse/jetty/util/resource/BadResource.java
index 76ad49b..6e0da5f 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/resource/BadResource.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/resource/BadResource.java
@@ -22,7 +22,6 @@ import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
-import java.io.OutputStream;
 import java.net.URL;
 
 
@@ -92,14 +91,6 @@ class BadResource extends URLResource
         
     /* --------------------------------------------------------- */
     @Override
-    public OutputStream getOutputStream()
-        throws java.io.IOException, SecurityException
-    {
-        throw new FileNotFoundException(_message);
-    }
-        
-    /* --------------------------------------------------------- */
-    @Override
     public boolean delete()
         throws SecurityException
     {
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/resource/EmptyResource.java b/jetty-util/src/main/java/org/eclipse/jetty/util/resource/EmptyResource.java
new file mode 100644
index 0000000..140db5b
--- /dev/null
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/resource/EmptyResource.java
@@ -0,0 +1,130 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.resource;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.nio.channels.ReadableByteChannel;
+
+/**
+ * EmptyResource
+ *
+ * Represents a resource that does does not refer to any file, url, jar etc. 
+ */
+public class EmptyResource extends Resource
+{
+    public static final Resource INSTANCE = new EmptyResource();
+    
+    private EmptyResource()
+    {
+    }
+
+    @Override
+    public boolean isContainedIn(Resource r) throws MalformedURLException
+    {
+        return false;
+    }
+
+    @Override
+    public void close()
+    {
+    }
+
+    @Override
+    public boolean exists()
+    {
+        return false;
+    }
+
+    @Override
+    public boolean isDirectory()
+    {
+        return false;
+    }
+
+    @Override
+    public long lastModified()
+    {
+        return 0;
+    }
+
+    @Override
+    public long length()
+    {
+        return 0;
+    }
+
+    @Override
+    public URL getURL()
+    {
+        return null;
+    }
+
+    @Override
+    public File getFile() throws IOException
+    {
+        return null;
+    }
+
+    @Override
+    public String getName()
+    {
+        return null;
+    }
+
+    @Override
+    public InputStream getInputStream() throws IOException
+    {
+        return null;
+    }
+
+    @Override
+    public ReadableByteChannel getReadableByteChannel() throws IOException
+    {
+        return null;
+    }
+
+    @Override
+    public boolean delete() throws SecurityException
+    {
+        return false;
+    }
+
+    @Override
+    public boolean renameTo(Resource dest) throws SecurityException
+    {
+        return false;
+    }
+
+    @Override
+    public String[] list()
+    {
+        return null;
+    }
+
+    @Override
+    public Resource addPath(String path) throws IOException, MalformedURLException
+    {
+        return null;
+    }
+
+}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/resource/FileResource.java b/jetty-util/src/main/java/org/eclipse/jetty/util/resource/FileResource.java
index f1960c9..1949f7a 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/resource/FileResource.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/resource/FileResource.java
@@ -20,15 +20,16 @@ package org.eclipse.jetty.util.resource;
 
 import java.io.File;
 import java.io.FileInputStream;
-import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
-import java.io.OutputStream;
 import java.net.MalformedURLException;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.net.URL;
 import java.net.URLConnection;
+import java.nio.channels.FileChannel;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.file.StandardOpenOption;
 import java.security.Permission;
 
 import org.eclipse.jetty.util.IO;
@@ -46,44 +47,24 @@ import org.eclipse.jetty.util.log.Logger;
  * by calling the static method @see FileResource#setCheckAliases(boolean)
  * 
  */
-public class FileResource extends URLResource
+public class FileResource extends Resource
 {
     private static final Logger LOG = Log.getLogger(FileResource.class);
-    private static boolean __checkAliases = true;
 
     /* ------------------------------------------------------------ */
-    private File _file;
-    private transient URL _alias=null;
-    private transient boolean _aliasChecked=false;
-
-    /* ------------------------------------------------------------------------------- */
-    /** setCheckAliases.
-     * @param checkAliases True of resource aliases are to be checked for (eg case insensitivity or 8.3 short names) and treated as not found.
-     */
-    public static void setCheckAliases(boolean checkAliases)
-    {
-        __checkAliases=checkAliases;
-    }
-
-    /* ------------------------------------------------------------------------------- */
-    /** getCheckAliases.
-     * @return True of resource aliases are to be checked for (eg case insensitivity or 8.3 short names) and treated as not found.
-     */
-    public static boolean getCheckAliases()
-    {
-        return __checkAliases;
-    }
+    private final File _file;
+    private final String _uri;
+    private final URI _alias;
     
     /* -------------------------------------------------------- */
     public FileResource(URL url)
         throws IOException, URISyntaxException
     {
-        super(url,null);
-
+        File file;
         try
         {
             // Try standard API to convert URL to file.
-            _file =new File(new URI(url.toString()));
+            file =new File(url.toURI());
         }
         catch (URISyntaxException e) 
         {
@@ -91,48 +72,106 @@ public class FileResource extends URLResource
         }
         catch (Exception e)
         {
+            if (!url.toString().startsWith("file:"))
+                throw new IllegalArgumentException("!file:");
+            
             LOG.ignore(e);
             try
             {
-                // Assume that File.toURL produced unencoded chars. So try
-                // encoding them.
+                // Assume that File.toURL produced unencoded chars. So try encoding them.
                 String file_url="file:"+URIUtil.encodePath(url.toString().substring(5));           
                 URI uri = new URI(file_url);
                 if (uri.getAuthority()==null) 
-                    _file = new File(uri);
+                    file = new File(uri);
                 else
-                    _file = new File("//"+uri.getAuthority()+URIUtil.decodePath(url.getFile()));
+                    file = new File("//"+uri.getAuthority()+URIUtil.decodePath(url.getFile()));
             }
             catch (Exception e2)
             {
                 LOG.ignore(e2);
-
                 // Still can't get the file.  Doh! try good old hack!
-                checkConnection();
-                Permission perm = _connection.getPermission();
-                _file = new File(perm==null?url.getFile():perm.getName());
+                URLConnection connection=url.openConnection();
+                Permission perm = connection.getPermission();
+                file = new File(perm==null?url.getFile():perm.getName());
             }
         }
-        if (_file.isDirectory())
-        {
-            if (!_urlString.endsWith("/"))
-                _urlString=_urlString+"/";
-        }
-        else
-        {
-            if (_urlString.endsWith("/"))
-                _urlString=_urlString.substring(0,_urlString.length()-1);
-        }
+        
+        _file=file;
+        _uri=normalizeURI(_file,url.toURI());
+        _alias=checkFileAlias(_file);
+    }
 
+    /* -------------------------------------------------------- */
+    public FileResource(URI uri)
+    {
+        File file=new File(uri);
+        _file=file;
+        URI file_uri=_file.toURI();
+        _uri=normalizeURI(_file,uri);
+
+        // Is it a URI alias?
+        if (!URIUtil.equalsIgnoreEncodings(_uri,file_uri.toString()))
+            _alias=_file.toURI();
+        else
+            _alias=checkFileAlias(_file);
     }
 
     /* -------------------------------------------------------- */
-    FileResource(URL url, URLConnection connection, File file)
+    FileResource(File file)
     {
-        super(url,connection);
         _file=file;
-        if (_file.isDirectory() && !_urlString.endsWith("/"))
-            _urlString=_urlString+"/";
+        _uri=normalizeURI(_file,_file.toURI());
+        _alias=checkFileAlias(_file);
+    }
+
+    /* -------------------------------------------------------- */
+    private static String normalizeURI(File file, URI uri)
+    {
+        String u =uri.toASCIIString();
+        if (file.isDirectory())
+        {
+            if(!u.endsWith("/"))
+                u+="/";
+        } 
+        else if (file.exists() && u.endsWith("/"))
+            u=u.substring(0,u.length()-1);
+        return u;
+    }
+
+    /* -------------------------------------------------------- */
+    private static URI checkFileAlias(File file)
+    {
+        try
+        {
+            String abs=file.getAbsolutePath();
+            String can=file.getCanonicalPath();
+
+            if (!abs.equals(can))
+            {
+                if (LOG.isDebugEnabled())
+                    LOG.debug("ALIAS abs={} can={}",abs,can);
+
+                URI alias=new File(can).toURI();
+                // Have to encode the path as File.toURI does not!
+                return new URI("file://"+URIUtil.encodePath(alias.getPath()));  
+            }
+        }
+        catch(Exception e)
+        {
+            LOG.warn("bad alias for {}: {}",file,e.toString());
+            LOG.debug(e);
+            try
+            {
+                return new URI("http://eclipse.org/bad/canonical/alias");
+            }
+            catch(Exception e2)
+            {
+                LOG.ignore(e2);
+                throw new RuntimeException(e);
+            }
+        }
+
+        return null;
     }
     
     /* -------------------------------------------------------- */
@@ -140,77 +179,42 @@ public class FileResource extends URLResource
     public Resource addPath(String path)
         throws IOException,MalformedURLException
     {
-        URLResource r=null;
-        String url=null;
-
         path = org.eclipse.jetty.util.URIUtil.canonicalPath(path);
-       
+
+        if (path==null)
+            throw new MalformedURLException();   
+        
         if ("/".equals(path))
             return this;
-        else if (!isDirectory())
-        {
-            r=(FileResource)super.addPath(path);
-            url=r._urlString;
-        }
-        else
-        {
-            if (path==null)
-                throw new MalformedURLException();   
-            
-            // treat all paths being added as relative
-            String rel=path;
-            if (path.startsWith("/"))
-                rel = path.substring(1);
-            
-            url=URIUtil.addPaths(_urlString,URIUtil.encodePath(rel));
-            r=(URLResource)Resource.newResource(url);
-        }
         
-        // Check for encoding aliases
+        path=URIUtil.encodePath(path);
         // The encoded path should be a suffix of the resource (give or take a directory / )
-        String encoded=URIUtil.encodePath(path);
-        int expected=r.toString().length()-encoded.length();
-        int index = r._urlString.lastIndexOf(encoded, expected);
-        if (expected!=index && ((expected-1)!=index || path.endsWith("/") || !r.isDirectory()))
+        URI uri;
+        try
         {
-            if (r instanceof FileResource)
+            if (_file.isDirectory())
+            {
+                // treat all paths being added as relative
+                uri=new URI(URIUtil.addPaths(_uri,path));
+            }
+            else
             {
-                ((FileResource)r)._alias=((FileResource)r)._file.getCanonicalFile().toURI().toURL();
-                ((FileResource)r)._aliasChecked=true;
+                uri=new URI(_uri+path);
             }
-        }                             
-        return r;
+        }
+        catch(final URISyntaxException e)
+        {
+            throw new MalformedURLException(){{initCause(e);}};
+        }
+
+        return new FileResource(uri);
     }
    
     
     /* ------------------------------------------------------------ */
     @Override
-    public URL getAlias()
+    public URI getAlias()
     {
-        if (__checkAliases && !_aliasChecked)
-        {
-            try
-            {    
-                String abs=_file.getAbsolutePath();
-                String can=_file.getCanonicalPath();
-                
-                if (abs.length()!=can.length() || !abs.equals(can))
-                    _alias=Resource.toURL(new File(can));
-                
-                _aliasChecked=true;
-                
-                if (_alias!=null && LOG.isDebugEnabled())
-                {
-                    LOG.debug("ALIAS abs="+abs);
-                    LOG.debug("ALIAS can="+can);
-                }
-            }
-            catch(Exception e)
-            {
-                LOG.warn(Log.EXCEPTION,e);
-                return getURL();
-            }                
-        }
         return _alias;
     }
     
@@ -236,12 +240,12 @@ public class FileResource extends URLResource
 
     /* -------------------------------------------------------- */
     /**
-     * Returns true if the respresenetd resource is a container/directory.
+     * Returns true if the resource is a container/directory.
      */
     @Override
     public boolean isDirectory()
     {
-        return _file.isDirectory();
+        return _file.exists() && _file.isDirectory() || _uri.endsWith("/");
     }
 
     /* --------------------------------------------------------- */
@@ -285,16 +289,12 @@ public class FileResource extends URLResource
     {
         return new FileInputStream(_file);
     }
-        
-    /* --------------------------------------------------------- */
-    /**
-     * Returns an output stream to the resource
-     */
+
+    /* ------------------------------------------------------------ */
     @Override
-    public OutputStream getOutputStream()
-        throws java.io.IOException, SecurityException
+    public ReadableByteChannel getReadableByteChannel() throws IOException
     {
-        return new FileOutputStream(_file);
+        return FileChannel.open(_file.toPath(),StandardOpenOption.READ);
     }
         
     /* --------------------------------------------------------- */
@@ -340,18 +340,6 @@ public class FileResource extends URLResource
         }
         return list;
     }
-         
-    /* ------------------------------------------------------------ */
-    /** Encode according to this resource type.
-     * File URIs are encoded.
-     * @param uri URI to encode.
-     * @return The uri unchanged.
-     */
-    @Override
-    public String encode(String uri)
-    {
-        return uri;
-    }
     
     /* ------------------------------------------------------------ */
     /** 
@@ -397,4 +385,41 @@ public class FileResource extends URLResource
             IO.copy(getFile(),destination);
         }
     }
+
+    @Override
+    public boolean isContainedIn(Resource r) throws MalformedURLException
+    {
+        return false;
+    }
+
+    @Override
+    public void close()
+    {
+    }
+
+    @Override
+    public URL getURL()
+    {
+        try
+        {
+            return new URL(_uri);
+        }
+        catch (MalformedURLException e)
+        {
+            throw new IllegalStateException(e);
+        }
+    }
+    
+    @Override
+    public URI getURI()
+    {
+        return _file.toURI();
+    }
+
+    @Override
+    public String toString()
+    {
+        return _uri;
+    }
+
 }
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/resource/JarFileResource.java b/jetty-util/src/main/java/org/eclipse/jetty/util/resource/JarFileResource.java
index 56572c9..7bb738a 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/resource/JarFileResource.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/resource/JarFileResource.java
@@ -46,13 +46,13 @@ class JarFileResource extends JarResource
     private boolean _exists;
     
     /* -------------------------------------------------------- */
-    JarFileResource(URL url)
+    protected JarFileResource(URL url)
     {
         super(url);
     }
     
     /* ------------------------------------------------------------ */
-    JarFileResource(URL url, boolean useCaches)
+    protected JarFileResource(URL url, boolean useCaches)
     {
         super(url, useCaches);
     }
@@ -60,7 +60,7 @@ class JarFileResource extends JarResource
 
     /* ------------------------------------------------------------ */
     @Override
-    public synchronized void release()
+    public synchronized void close()
     {
         _list=null;
         _entry=null;
@@ -73,7 +73,8 @@ class JarFileResource extends JarResource
             {
                 try
                 {
-                    LOG.debug("Closing JarFile "+_jarFile.getName());
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("Closing JarFile "+_jarFile.getName());
                     _jarFile.close();
                 }
                 catch ( IOException ioe )
@@ -83,12 +84,12 @@ class JarFileResource extends JarResource
             }
         }
         _jarFile=null;
-        super.release();
+        super.close();
     }
     
     /* ------------------------------------------------------------ */
     @Override
-    protected boolean checkConnection()
+    protected synchronized boolean checkConnection()
     {
         try
         {
@@ -135,6 +136,7 @@ class JarFileResource extends JarResource
      * Returns true if the represented resource exists.
      */
     @Override
+
     public boolean exists()
     {
         if (_exists)
@@ -160,10 +162,11 @@ class JarFileResource extends JarResource
         else 
         {
             // Can we find a file for it?
-            JarFile jarFile=null;
+            boolean close_jar_file= false;
+            JarFile jar_file=null;
             if (check)
                 // Yes
-                jarFile=_jarFile;
+                jar_file=_jarFile;
             else
             {
                 // No - so lets look if the root entry exists.
@@ -171,7 +174,8 @@ class JarFileResource extends JarResource
                 {
                     JarURLConnection c=(JarURLConnection)((new URL(_jarUrl)).openConnection());
                     c.setUseCaches(getUseCaches());
-                    jarFile=c.getJarFile();
+                    jar_file=c.getJarFile();
+                    close_jar_file = !getUseCaches();
                 }
                 catch(Exception e)
                 {
@@ -180,13 +184,13 @@ class JarFileResource extends JarResource
             }
 
             // Do we need to look more closely?
-            if (jarFile!=null && _entry==null && !_directory)
+            if (jar_file!=null && _entry==null && !_directory)
             {
                 // OK - we have a JarFile, lets look at the entries for our path
-                Enumeration<JarEntry> e=jarFile.entries();
+                Enumeration<JarEntry> e=jar_file.entries();
                 while(e.hasMoreElements())
                 {
-                    JarEntry entry = (JarEntry) e.nextElement();
+                    JarEntry entry = e.nextElement();
                     String name=entry.getName().replace('\\','/');
                     
                     // Do we have a match
@@ -211,18 +215,17 @@ class JarFileResource extends JarResource
                         break;
                     }
                 }
+            }
 
-                if (_directory && !_urlString.endsWith("/"))
+            if(close_jar_file && jar_file!=null) 
+            {
+                try 
                 {
-                    _urlString+="/";
-                    try
-                    {
-                        _url=new URL(_urlString);
-                    }
-                    catch(MalformedURLException ex)
-                    {
-                        LOG.warn(ex);
-                    }
+                    jar_file.close();
+                } 
+                catch (IOException ioe) 
+                {
+                    LOG.ignore(ioe);
                 }
             }
         }    
@@ -316,6 +319,8 @@ class JarFileResource extends JarResource
                 e.printStackTrace();
                  LOG.ignore(e);
             }
+                if(jarFile==null)
+                    throw new IllegalStateException();
         }
         
         Enumeration<JarEntry> e=jarFile.entries();
@@ -372,18 +377,6 @@ class JarFileResource extends JarResource
         
         return -1;
     }
-    
-    /* ------------------------------------------------------------ */
-    /** Encode according to this resource type.
-     * File URIs are not encoded.
-     * @param uri URI to encode.
-     * @return The uri unchanged.
-     */
-    @Override
-    public String encode(String uri)
-    {
-        return uri;
-    }
 
     
     /**
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/resource/JarResource.java b/jetty-util/src/main/java/org/eclipse/jetty/util/resource/JarResource.java
index 7c16fe3..301ade8 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/resource/JarResource.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/resource/JarResource.java
@@ -23,6 +23,7 @@ import java.io.FileOutputStream;
 import java.io.FilterInputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.OutputStream;
 import java.net.JarURLConnection;
 import java.net.URL;
 import java.util.jar.JarEntry;
@@ -42,23 +43,23 @@ public class JarResource extends URLResource
     protected JarURLConnection _jarConnection;
     
     /* -------------------------------------------------------- */
-    JarResource(URL url)
+    protected JarResource(URL url)
     {
         super(url,null);
     }
 
     /* ------------------------------------------------------------ */
-    JarResource(URL url, boolean useCaches)
+    protected JarResource(URL url, boolean useCaches)
     {
         super(url, null, useCaches);
     }
     
     /* ------------------------------------------------------------ */
     @Override
-    public synchronized void release()
+    public synchronized void close()
     {
         _jarConnection=null;
-        super.release();
+        super.close();
     }
     
     /* ------------------------------------------------------------ */
@@ -91,7 +92,7 @@ public class JarResource extends URLResource
     
     /* ------------------------------------------------------------ */
     /**
-     * Returns true if the respresenetd resource exists.
+     * Returns true if the represented resource exists.
      */
     @Override
     public boolean exists()
@@ -117,7 +118,7 @@ public class JarResource extends URLResource
     {     
         checkConnection();
         if (!_urlString.endsWith("!/"))
-            return new FilterInputStream(super.getInputStream()) 
+            return new FilterInputStream(getInputStream(false)) 
             {
                 @Override
                 public void close() throws IOException {this.in=IO.getClosedStream();}
@@ -128,6 +129,9 @@ public class JarResource extends URLResource
         return is;
     }
     
+ 
+    
+    
     /* ------------------------------------------------------------ */
     @Override
     public void copyTo(File directory)
@@ -153,115 +157,110 @@ public class JarResource extends URLResource
         if (LOG.isDebugEnabled()) 
             LOG.debug("Extracting entry = "+subEntryName+" from jar "+jarFileURL);
         
-        InputStream is = jarFileURL.openConnection().getInputStream();
-        JarInputStream jin = new JarInputStream(is);
-        JarEntry entry;
-        boolean shouldExtract;
-        while((entry=jin.getNextJarEntry())!=null)
+        try (InputStream is = jarFileURL.openConnection().getInputStream();
+                JarInputStream jin = new JarInputStream(is))
         {
-            String entryName = entry.getName();
-            if ((subEntryName != null) && (entryName.startsWith(subEntryName)))
-            { 
-                // is the subentry really a dir?
-                if (!subEntryIsDir && subEntryName.length()+1==entryName.length() && entryName.endsWith("/"))
-                        subEntryIsDir=true;
-                
-                //if there is a particular subEntry that we are looking for, only
-                //extract it.
-                if (subEntryIsDir)
+            JarEntry entry;
+            boolean shouldExtract;
+            while((entry=jin.getNextJarEntry())!=null)
+            {
+                String entryName = entry.getName();
+                if ((subEntryName != null) && (entryName.startsWith(subEntryName)))
                 {
-                    //if it is a subdirectory we are looking for, then we
-                    //are looking to extract its contents into the target
-                    //directory. Remove the name of the subdirectory so
-                    //that we don't wind up creating it too.
-                    entryName = entryName.substring(subEntryName.length());
-                    if (!entryName.equals(""))
+                    // is the subentry really a dir?
+                    if (!subEntryIsDir && subEntryName.length()+1==entryName.length() && entryName.endsWith("/"))
+                            subEntryIsDir=true;
+
+                    //if there is a particular subEntry that we are looking for, only
+                    //extract it.
+                    if (subEntryIsDir)
                     {
-                        //the entry is 
-                        shouldExtract = true;                   
+                        //if it is a subdirectory we are looking for, then we
+                        //are looking to extract its contents into the target
+                        //directory. Remove the name of the subdirectory so
+                        //that we don't wind up creating it too.
+                        entryName = entryName.substring(subEntryName.length());
+                        if (!entryName.equals(""))
+                        {
+                            //the entry is
+                            shouldExtract = true;
+                        }
+                        else
+                            shouldExtract = false;
                     }
                     else
-                        shouldExtract = false;
+                        shouldExtract = true;
+                }
+                else if ((subEntryName != null) && (!entryName.startsWith(subEntryName)))
+                {
+                    //there is a particular entry we are looking for, and this one
+                    //isn't it
+                    shouldExtract = false;
                 }
                 else
-                    shouldExtract = true;
-            }
-            else if ((subEntryName != null) && (!entryName.startsWith(subEntryName)))
-            {
-                //there is a particular entry we are looking for, and this one
-                //isn't it
-                shouldExtract = false;
-            }
-            else
-            {
-                //we are extracting everything
-                shouldExtract =  true;
-            }
-                
-            
-            if (!shouldExtract)
-            {
-                if (LOG.isDebugEnabled()) 
-                    LOG.debug("Skipping entry: "+entryName);
-                continue;
-            }
-                
-            String dotCheck = entryName.replace('\\', '/');   
-            dotCheck = URIUtil.canonicalPath(dotCheck);
-            if (dotCheck == null)
-            {
-                if (LOG.isDebugEnabled()) 
-                    LOG.debug("Invalid entry: "+entryName);
-                continue;
-            }
+                {
+                    //we are extracting everything
+                    shouldExtract =  true;
+                }
 
-            File file=new File(directory,entryName);
-     
-            if (entry.isDirectory())
-            {
-                // Make directory
-                if (!file.exists())
-                    file.mkdirs();
-            }
-            else
-            {
-                // make directory (some jars don't list dirs)
-                File dir = new File(file.getParent());
-                if (!dir.exists())
-                    dir.mkdirs();
+                if (!shouldExtract)
+                {
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("Skipping entry: "+entryName);
+                    continue;
+                }
 
-                // Make file
-                FileOutputStream fout = null;
-                try
+                String dotCheck = entryName.replace('\\', '/');
+                dotCheck = URIUtil.canonicalPath(dotCheck);
+                if (dotCheck == null)
                 {
-                    fout = new FileOutputStream(file);
-                    IO.copy(jin,fout);
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("Invalid entry: "+entryName);
+                    continue;
                 }
-                finally
+
+                File file=new File(directory,entryName);
+
+                if (entry.isDirectory())
                 {
-                    IO.close(fout);
+                    // Make directory
+                    if (!file.exists())
+                        file.mkdirs();
                 }
+                else
+                {
+                    // make directory (some jars don't list dirs)
+                    File dir = new File(file.getParent());
+                    if (!dir.exists())
+                        dir.mkdirs();
+
+                    // Make file
+                    try (OutputStream fout = new FileOutputStream(file))
+                    {
+                        IO.copy(jin,fout);
+                    }
 
-                // touch the file.
-                if (entry.getTime()>=0)
-                    file.setLastModified(entry.getTime());
+                    // touch the file.
+                    if (entry.getTime()>=0)
+                        file.setLastModified(entry.getTime());
+                }
             }
-        }
-        
-        if ((subEntryName == null) || (subEntryName != null && subEntryName.equalsIgnoreCase("META-INF/MANIFEST.MF")))
-        {
-            Manifest manifest = jin.getManifest();
-            if (manifest != null)
+
+            if ((subEntryName == null) || (subEntryName != null && subEntryName.equalsIgnoreCase("META-INF/MANIFEST.MF")))
             {
-                File metaInf = new File (directory, "META-INF");
-                metaInf.mkdir();
-                File f = new File(metaInf, "MANIFEST.MF");
-                FileOutputStream fout = new FileOutputStream(f);
-                manifest.write(fout);
-                fout.close();   
+                Manifest manifest = jin.getManifest();
+                if (manifest != null)
+                {
+                    File metaInf = new File (directory, "META-INF");
+                    metaInf.mkdir();
+                    File f = new File(metaInf, "MANIFEST.MF");
+                    try (OutputStream fout = new FileOutputStream(f))
+                    {
+                        manifest.write(fout);
+                    }
+                }
             }
         }
-        IO.close(jin);
     }   
     
     public static Resource newJarResource(Resource resource) throws IOException
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/resource/PathResource.java b/jetty-util/src/main/java/org/eclipse/jetty/util/resource/PathResource.java
new file mode 100644
index 0000000..19c2f3c
--- /dev/null
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/resource/PathResource.java
@@ -0,0 +1,354 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.resource;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.nio.channels.FileChannel;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.file.DirectoryIteratorException;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.InvalidPathException;
+import java.nio.file.LinkOption;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.StandardOpenOption;
+import java.nio.file.attribute.FileTime;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * Java NIO Path equivalent of FileResource.
+ */
+public class PathResource extends Resource
+{
+    private static final Logger LOG = Log.getLogger(PathResource.class);
+
+    private final Path path;
+    private final URI uri;
+    private LinkOption linkOptions[] = new LinkOption[] { LinkOption.NOFOLLOW_LINKS };
+
+    public PathResource(File file)
+    {
+        this(file.toPath());
+    }
+
+    public PathResource(Path path)
+    {
+        this.path = path;
+        this.uri = this.path.toUri();
+    }
+
+    public PathResource(URI uri) throws IOException
+    {
+        if (!uri.isAbsolute())
+        {
+            throw new IllegalArgumentException("not an absolute uri");
+        }
+
+        if (!uri.getScheme().equalsIgnoreCase("file"))
+        {
+            throw new IllegalArgumentException("not file: scheme");
+        }
+
+        Path path;
+        try
+        {
+            path = new File(uri).toPath();
+        }
+        catch (InvalidPathException e)
+        {
+            throw e;
+        }
+        catch (IllegalArgumentException e)
+        {
+            throw e;
+        }
+        catch (Exception e)
+        {
+            LOG.ignore(e);
+            throw new IOException("Unable to build Path from: " + uri,e);
+        }
+
+        this.path = path;
+        this.uri = path.toUri();
+    }
+
+    public PathResource(URL url) throws IOException, URISyntaxException
+    {
+        this(url.toURI());
+    }
+
+    @Override
+    public Resource addPath(String apath) throws IOException, MalformedURLException
+    {
+        return new PathResource(this.path.getFileSystem().getPath(path.toString(), apath));
+    }
+
+    @Override
+    public void close()
+    {
+        // not applicable for FileSytem / Path
+    }
+
+    @Override
+    public boolean delete() throws SecurityException
+    {
+        try
+        {
+            return Files.deleteIfExists(path);
+        }
+        catch (IOException e)
+        {
+            LOG.ignore(e);
+            return false;
+        }
+    }
+
+    @Override
+    public boolean equals(Object obj)
+    {
+        if (this == obj)
+        {
+            return true;
+        }
+        if (obj == null)
+        {
+            return false;
+        }
+        if (getClass() != obj.getClass())
+        {
+            return false;
+        }
+        PathResource other = (PathResource)obj;
+        if (path == null)
+        {
+            if (other.path != null)
+            {
+                return false;
+            }
+        }
+        else if (!path.equals(other.path))
+        {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public boolean exists()
+    {
+        return Files.exists(path,linkOptions);
+    }
+
+    @Override
+    public File getFile() throws IOException
+    {
+        return path.toFile();
+    }
+
+    public boolean getFollowLinks()
+    {
+        return (linkOptions != null) && (linkOptions.length > 0) && (linkOptions[0] == LinkOption.NOFOLLOW_LINKS);
+    }
+
+    @Override
+    public InputStream getInputStream() throws IOException
+    {
+        return Files.newInputStream(path,StandardOpenOption.READ);
+    }
+
+    @Override
+    public String getName()
+    {
+        return path.toAbsolutePath().toString();
+    }
+
+    @Override
+    public ReadableByteChannel getReadableByteChannel() throws IOException
+    {
+        return FileChannel.open(path,StandardOpenOption.READ);
+    }
+
+    @Override
+    public URI getURI()
+    {
+        return this.uri;
+    }
+
+    @Override
+    public URL getURL()
+    {
+        try
+        {
+            return path.toUri().toURL();
+        }
+        catch (MalformedURLException e)
+        {
+            return null;
+        }
+    }
+
+    @Override
+    public int hashCode()
+    {
+        final int prime = 31;
+        int result = 1;
+        result = (prime * result) + ((path == null)?0:path.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean isContainedIn(Resource r) throws MalformedURLException
+    {
+        // not applicable for FileSystem / path
+        return false;
+    }
+
+    @Override
+    public boolean isDirectory()
+    {
+        return Files.isDirectory(path,linkOptions);
+    }
+
+    @Override
+    public long lastModified()
+    {
+        try
+        {
+            FileTime ft = Files.getLastModifiedTime(path,linkOptions);
+            return ft.toMillis();
+        }
+        catch (IOException e)
+        {
+            LOG.ignore(e);
+            return 0;
+        }
+    }
+
+    @Override
+    public long length()
+    {
+        try
+        {
+            return Files.size(path);
+        }
+        catch (IOException e)
+        {
+            // in case of error, use File.length logic of 0L
+            return 0L;
+        }
+    }
+
+    @Override
+    public URI getAlias()
+    {
+        if (Files.isSymbolicLink(path))
+        {
+            try
+            {
+                return path.toRealPath().toUri();
+            }
+            catch (IOException e)
+            {
+                LOG.debug(e);
+                return null;
+            }
+        }
+        else
+        {
+            return null;
+        }
+    }
+
+    @Override
+    public String[] list()
+    {
+        try (DirectoryStream<Path> dir = Files.newDirectoryStream(path))
+        {
+            List<String> entries = new ArrayList<>();
+            for (Path entry : dir)
+            {
+                String name = entry.getFileName().toString();
+
+                if (Files.isDirectory(entry))
+                {
+                    name += "/";
+                }
+
+                entries.add(name);
+            }
+            int size = entries.size();
+            return entries.toArray(new String[size]);
+        }
+        catch (DirectoryIteratorException e)
+        {
+            LOG.debug(e);
+        }
+        catch (IOException e)
+        {
+            LOG.debug(e);
+        }
+        return null;
+    }
+
+    @Override
+    public boolean renameTo(Resource dest) throws SecurityException
+    {
+        if (dest instanceof PathResource)
+        {
+            PathResource destRes = (PathResource)dest;
+            try
+            {
+                Path result = Files.move(path,destRes.path,StandardCopyOption.ATOMIC_MOVE);
+                return Files.exists(result,linkOptions);
+            }
+            catch (IOException e)
+            {
+                LOG.ignore(e);
+                return false;
+            }
+        }
+        else
+        {
+            return false;
+        }
+    }
+
+    public void setFollowLinks(boolean followLinks)
+    {
+        if (followLinks)
+        {
+            linkOptions = new LinkOption[0];
+        }
+        else
+        {
+            linkOptions = new LinkOption[] { LinkOption.NOFOLLOW_LINKS };
+        }
+    }
+}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/resource/Resource.java b/jetty-util/src/main/java/org/eclipse/jetty/util/resource/Resource.java
index da83a13..0730720 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/resource/Resource.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/resource/Resource.java
@@ -18,6 +18,7 @@
 
 package org.eclipse.jetty.util.resource;
 
+import java.io.Closeable;
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
@@ -26,9 +27,11 @@ import java.io.OutputStream;
 import java.net.MalformedURLException;
 import java.net.URI;
 import java.net.URL;
-import java.net.URLConnection;
+import java.nio.channels.ReadableByteChannel;
 import java.text.DateFormat;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Date;
 
 import org.eclipse.jetty.util.B64Code;
@@ -43,8 +46,12 @@ import org.eclipse.jetty.util.log.Logger;
 /* ------------------------------------------------------------ */
 /** 
  * Abstract resource class.
+ * <p>
+ * This class provides a resource abstraction, where a resource may be
+ * a file, a URL or an entry in a jar file.
+ * </p>
  */
-public abstract class Resource implements ResourceFactory
+public abstract class Resource implements ResourceFactory, Closeable
 {
     private static final Logger LOG = Log.getLogger(Resource.class);
     public static boolean __defaultUseCaches = true;
@@ -71,10 +78,10 @@ public abstract class Resource implements ResourceFactory
     /** Construct a resource from a uri.
      * @param uri A URI.
      * @return A Resource object.
-     * @throws IOException Problem accessing URI
+     * @throws MalformedURLException Problem accessing URI
      */
     public static Resource newResource(URI uri)
-        throws IOException
+        throws MalformedURLException
     {
         return newResource(uri.toURL());
     }
@@ -83,10 +90,8 @@ public abstract class Resource implements ResourceFactory
     /** Construct a resource from a url.
      * @param url A URL.
      * @return A Resource object.
-     * @throws IOException Problem accessing URL
      */
     public static Resource newResource(URL url)
-        throws IOException
     {
         return newResource(url, __defaultUseCaches);
     }
@@ -113,6 +118,7 @@ public abstract class Resource implements ResourceFactory
             }
             catch(Exception e)
             {
+                LOG.warn(e.toString());
                 LOG.debug(Log.EXCEPTION,e);
                 return new BadResource(url,e.toString());
             }
@@ -134,10 +140,11 @@ public abstract class Resource implements ResourceFactory
     /* ------------------------------------------------------------ */
     /** Construct a resource from a string.
      * @param resource A URL or filename.
+     * @throws MalformedURLException Problem accessing URI
      * @return A Resource object.
      */
     public static Resource newResource(String resource)
-        throws MalformedURLException, IOException
+        throws MalformedURLException
     {
         return newResource(resource, __defaultUseCaches);
     }
@@ -147,9 +154,10 @@ public abstract class Resource implements ResourceFactory
      * @param resource A URL or filename.
      * @param useCaches controls URLConnection caching
      * @return A Resource object.
+     * @throws MalformedURLException Problem accessing URI
      */
-    public static Resource newResource (String resource, boolean useCaches)       
-    throws MalformedURLException, IOException
+    public static Resource newResource(String resource, boolean useCaches)       
+        throws MalformedURLException
     {
         URL url=null;
         try
@@ -170,11 +178,7 @@ public abstract class Resource implements ResourceFactory
                         resource=resource.substring(2);
                     
                     File file=new File(resource).getCanonicalFile();
-                    url=Resource.toURL(file);            
-                    
-                    URLConnection connection=url.openConnection();
-                    connection.setUseCaches(useCaches);
-                    return new FileResource(url,connection,file);
+                    return new FileResource(file);
                 }
                 catch(Exception e2)
                 {
@@ -189,19 +193,13 @@ public abstract class Resource implements ResourceFactory
             }
         }
 
-        return newResource(url);
+        return newResource(url, useCaches);
     }
 
     /* ------------------------------------------------------------ */
-    public static Resource newResource (File file)
-    throws MalformedURLException, IOException
+    public static Resource newResource(File file)
     {
-        file = file.getCanonicalFile();
-        URL url = Resource.toURL(file);
-
-        URLConnection connection = url.openConnection();
-        FileResource fileResource = new FileResource(url, connection, file);
-        return fileResource;
+        return new FileResource(file);
     }
 
     /* ------------------------------------------------------------ */
@@ -269,7 +267,7 @@ public abstract class Resource implements ResourceFactory
     /* ------------------------------------------------------------ */
     /** Find a classpath resource.
      * The {@link java.lang.Class#getResource(String)} method is used to lookup the resource. If it is not
-     * found, then the {@link Loader#getResource(Class, String, boolean)} method is used.
+     * found, then the {@link Loader#getResource(Class, String)} method is used.
      * If it is still not found, then {@link ClassLoader#getSystemResource(String)} is used.
      * Unlike {@link ClassLoader#getSystemResource(String)} this method does not check for normal resources.
      * @param name The relative name of the resource
@@ -283,7 +281,7 @@ public abstract class Resource implements ResourceFactory
         URL url=Resource.class.getResource(name);
         
         if (url==null)
-            url=Loader.getResource(Resource.class,name,checkParents);
+            url=Loader.getResource(Resource.class,name);
         if (url==null)
             return null;
         return newResource(url,useCaches);
@@ -299,7 +297,7 @@ public abstract class Resource implements ResourceFactory
     @Override
     protected void finalize()
     {
-        release();
+        close();
     }
     
     /* ------------------------------------------------------------ */
@@ -308,9 +306,18 @@ public abstract class Resource implements ResourceFactory
     
     /* ------------------------------------------------------------ */
     /** Release any temporary resources held by the resource.
+     * @deprecated use {@link #close()}
      */
-    public abstract void release();
-    
+    public final void release()
+    {
+        close();
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Release any temporary resources held by the resource.
+     */
+    @Override
+    public abstract void close();
 
     /* ------------------------------------------------------------ */
     /**
@@ -345,6 +352,7 @@ public abstract class Resource implements ResourceFactory
     /**
      * Returns an URL representing the given resource
      */
+    // TODO: should deprecate this one and only use getURI()
     public abstract URL getURL();
 
     /* ------------------------------------------------------------ */
@@ -386,18 +394,19 @@ public abstract class Resource implements ResourceFactory
      */
     public abstract InputStream getInputStream()
         throws java.io.IOException;
-
+    
     /* ------------------------------------------------------------ */
     /**
-     * Returns an output stream to the resource
+     * Returns an readable bytechannel to the resource or null if one is not available.
      */
-    public abstract OutputStream getOutputStream()
-        throws java.io.IOException, SecurityException;
-    
+    public abstract ReadableByteChannel getReadableByteChannel()
+        throws java.io.IOException;
+
     /* ------------------------------------------------------------ */
     /**
      * Deletes the given resource
      */
+    // TODO: can throw IOException
     public abstract boolean delete()
         throws SecurityException;
     
@@ -405,6 +414,7 @@ public abstract class Resource implements ResourceFactory
     /**
      * Rename the given resource
      */
+    // TODO: can throw IOException
     public abstract boolean renameTo( Resource dest)
         throws SecurityException;
     
@@ -413,25 +423,26 @@ public abstract class Resource implements ResourceFactory
      * Returns a list of resource names contained in the given resource
      * The resource names are not URL encoded.
      */
+    // TODO: can throw IOException
     public abstract String[] list();
 
     /* ------------------------------------------------------------ */
     /**
      * Returns the resource contained inside the current resource with the
      * given name.
-     * @param path The path segment to add, which should be encoded by the
-     * encode method. 
+     * @param path The path segment to add, which is not encoded
      */
     public abstract Resource addPath(String path)
         throws IOException,MalformedURLException;
 
     /* ------------------------------------------------------------ */
-    /** Get a resource from withing this resource.
+    /** Get a resource from within this resource.
      * <p>
      * This method is essentially an alias for {@link #addPath(String)}, but without checked exceptions.
      * This method satisfied the {@link ResourceFactory} interface.
      * @see org.eclipse.jetty.util.resource.ResourceFactory#getResource(java.lang.String)
      */
+    @Override
     public Resource getResource(String path)
     {
         try
@@ -446,14 +457,12 @@ public abstract class Resource implements ResourceFactory
     }
 
     /* ------------------------------------------------------------ */
-    /** Encode according to this resource type.
-     * The default implementation calls URI.encodePath(uri)
-     * @param uri 
-     * @return String encoded for this resource type.
+    /** 
+     * @deprecated
      */
     public String encode(String uri)
     {
-        return URIUtil.encodePath(uri);
+        return null;
     }
         
     /* ------------------------------------------------------------ */
@@ -472,7 +481,7 @@ public abstract class Resource implements ResourceFactory
     /**
      * @return The canonical Alias of this resource or null if none.
      */
-    public URL getAlias()
+    public URI getAlias()
     {
         return null;
     }
@@ -540,7 +549,7 @@ public abstract class Resource implements ResourceFactory
             buf.append("</TD></TR>");
         }
         buf.append("</TABLE>\n");
-	buf.append("</BODY></HTML>\n");
+        buf.append("</BODY></HTML>\n");
         
         return buf.toString();
     }
@@ -604,7 +613,7 @@ public abstract class Resource implements ResourceFactory
     
     private static String deTag(String raw) 
     {
-        return StringUtil.replace( StringUtil.replace(raw,"<","<"), ">", ">");
+        return StringUtil.sanitizeXmlString(raw);
     }
     
     /* ------------------------------------------------------------ */
@@ -616,8 +625,7 @@ public abstract class Resource implements ResourceFactory
     public void writeTo(OutputStream out,long start,long count)
         throws IOException
     {
-        InputStream in = getInputStream();
-        try
+        try (InputStream in = getInputStream())
         {
             in.skip(start);
             if (count<0)
@@ -625,10 +633,6 @@ public abstract class Resource implements ResourceFactory
             else
                 IO.copy(in,out,count);
         }
-        finally
-        {
-            in.close();
-        }
     }    
     
     /* ------------------------------------------------------------ */
@@ -637,7 +641,10 @@ public abstract class Resource implements ResourceFactory
     {
         if (destination.exists())
             throw new IllegalArgumentException(destination+" exists");
-        writeTo(new FileOutputStream(destination),0,-1);
+        try (OutputStream out = new FileOutputStream(destination))
+        {
+            writeTo(out,0,-1);
+        }
     }
 
     /* ------------------------------------------------------------ */
@@ -666,6 +673,34 @@ public abstract class Resource implements ResourceFactory
     }
     
     /* ------------------------------------------------------------ */
+    public Collection<Resource> getAllResources()
+    {
+        try
+        {
+            ArrayList<Resource> deep=new ArrayList<>();
+            {
+                String[] list=list();
+                if (list!=null)
+                {
+                    for (String i:list)
+                    {
+                        Resource r=addPath(i);
+                        if (r.isDirectory())
+                            deep.addAll(r.getAllResources());
+                        else
+                            deep.add(r);
+                    }
+                }
+            }
+            return deep;
+        }
+        catch(Exception e)
+        {
+            throw new IllegalStateException(e);
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
     /** Generate a properly encoded URL from a {@link File} instance.
      * @param file Target file. 
      * @return URL of the target file.
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceCollection.java b/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceCollection.java
index 451acc9..960956f 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceCollection.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceCollection.java
@@ -21,9 +21,9 @@ package org.eclipse.jetty.util.resource;
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
-import java.io.OutputStream;
 import java.net.MalformedURLException;
 import java.net.URL;
+import java.nio.channels.ReadableByteChannel;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashSet;
@@ -31,6 +31,8 @@ import java.util.List;
 import java.util.StringTokenizer;
 
 import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
 
 /**
  * A collection of resources (dirs).
@@ -44,6 +46,7 @@ import org.eclipse.jetty.util.URIUtil;
  */
 public class ResourceCollection extends Resource
 {
+    private static final Logger LOG = Log.getLogger(ResourceCollection.class);
     private Resource[] _resources;
 
     /* ------------------------------------------------------------ */
@@ -166,20 +169,25 @@ public class ResourceCollection extends Resource
                     " argument must be a string containing one or more comma-separated resource strings.");
         }
         
-        _resources = new Resource[len];
+        List<Resource> resources = new ArrayList<>();
+        
         try
         {            
-            for(int i=0; tokenizer.hasMoreTokens(); i++)
+            while(tokenizer.hasMoreTokens())
             {
-                _resources[i] = Resource.newResource(tokenizer.nextToken().trim());
-                if(!_resources[i].exists() || !_resources[i].isDirectory())
-                    throw new IllegalArgumentException(_resources[i] + " is not an existing directory.");
+                Resource resource = Resource.newResource(tokenizer.nextToken().trim());
+                if(!resource.exists() || !resource.isDirectory())
+                    LOG.warn(" !exist "+resource);
+                else
+                    resources.add(resource);
             }
         }
         catch(Exception e)
         {
             throw new RuntimeException(e);
         }
+
+        _resources = resources.toArray(new Resource[resources.size()]);
     }
     
     /* ------------------------------------------------------------ */
@@ -218,12 +226,15 @@ public class ResourceCollection extends Resource
             Resource r = _resources[i].addPath(path); 
             if (r.exists() && r.isDirectory())
             {
+                if (resources==null)
+                    resources = new ArrayList<Resource>();
+                    
                 if (resource!=null)
                 {
-                    resources = new ArrayList<Resource>();
                     resources.add(resource);
                     resource=null;
                 }
+                
                 resources.add(r);
             }
         }
@@ -328,35 +339,35 @@ public class ResourceCollection extends Resource
         }
         return null;
     }
-    
+
     /* ------------------------------------------------------------ */
-    @Override
-    public String getName()
+    @Override 
+    public ReadableByteChannel getReadableByteChannel() throws IOException
     {
         if(_resources==null)
             throw new IllegalStateException("*resources* not set.");
         
         for(Resource r : _resources)
         {
-            String name = r.getName();
-            if(name!=null)
-                return name;
+            ReadableByteChannel channel = r.getReadableByteChannel();
+            if(channel!=null)
+                return channel;
         }
         return null;
     }
     
     /* ------------------------------------------------------------ */
     @Override
-    public OutputStream getOutputStream() throws IOException, SecurityException
+    public String getName()
     {
         if(_resources==null)
             throw new IllegalStateException("*resources* not set.");
         
         for(Resource r : _resources)
         {
-            OutputStream os = r.getOutputStream();
-            if(os!=null)
-                return os;
+            String name = r.getName();
+            if(name!=null)
+                return name;
         }
         return null;
     }
@@ -433,13 +444,13 @@ public class ResourceCollection extends Resource
     
     /* ------------------------------------------------------------ */
     @Override
-    public void release()
+    public void close()
     {
         if(_resources==null)
             throw new IllegalStateException("*resources* not set.");
         
         for(Resource r : _resources)
-            r.release();
+            r.close();
     }
     
     /* ------------------------------------------------------------ */
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/resource/URLResource.java b/jetty-util/src/main/java/org/eclipse/jetty/util/resource/URLResource.java
index ecba6b2..793b4e0 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/resource/URLResource.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/resource/URLResource.java
@@ -21,10 +21,10 @@ package org.eclipse.jetty.util.resource;
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
-import java.io.OutputStream;
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.net.URLConnection;
+import java.nio.channels.ReadableByteChannel;
 import java.security.Permission;
 
 import org.eclipse.jetty.util.URIUtil;
@@ -37,8 +37,8 @@ import org.eclipse.jetty.util.log.Logger;
 public class URLResource extends Resource
 {
     private static final Logger LOG = Log.getLogger(URLResource.class);
-    protected URL _url;
-    protected String _urlString;
+    protected final URL _url;
+    protected final String _urlString;
     
     protected URLConnection _connection;
     protected InputStream _in=null;
@@ -48,7 +48,7 @@ public class URLResource extends Resource
     protected URLResource(URL url, URLConnection connection)
     {
         _url = url;
-        _urlString=_url.toString();
+        _urlString=_url.toExternalForm();
         _connection=connection;
     }
     
@@ -80,7 +80,7 @@ public class URLResource extends Resource
     /** Release any resources held by the resource.
      */
     @Override
-    public synchronized void release()
+    public synchronized void close()
     {
         if (_in!=null)
         {
@@ -116,14 +116,14 @@ public class URLResource extends Resource
 
     /* ------------------------------------------------------------ */
     /**
-     * Returns true if the respresenetd resource is a container/directory.
+     * Returns true if the represented resource is a container/directory.
      * If the resource is not a file, resources ending with "/" are
      * considered directories.
      */
     @Override
     public boolean isDirectory()
     {
-        return exists() && _url.toString().endsWith("/");
+        return exists() && _urlString.endsWith("/");
     }
 
 
@@ -197,14 +197,34 @@ public class URLResource extends Resource
         return _url.toExternalForm();
     }
 
+    
     /* ------------------------------------------------------------ */
     /**
-     * Returns an input stream to the resource
+     * Returns an input stream to the resource. The underlying 
+     * url connection will be nulled out to prevent re-use.
      */
     @Override
     public synchronized InputStream getInputStream()
         throws java.io.IOException
     {
+        return getInputStream (true); //backwards compatibility
+    }
+    
+    
+ 
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns an input stream to the resource, optionally nulling
+     * out the underlying url connection. If the connection is not
+     * nulled out, a subsequent call to getInputStream() may return
+     * an existing and already in-use input stream - this depends on
+     * the url protocol. Eg JarURLConnection does not reuse inputstreams.
+     * 
+     * @param resetConnection if true the connection field is set to null
+     */
+    protected synchronized InputStream getInputStream(boolean resetConnection)
+        throws java.io.IOException
+    {
         if (!checkConnection())
             throw new IOException( "Invalid resource");
 
@@ -220,20 +240,19 @@ public class URLResource extends Resource
         }
         finally
         {
-            _connection=null;
+            if (resetConnection)
+            {
+                _connection=null;
+                if (LOG.isDebugEnabled()) LOG.debug("Connection nulled");
+            }
         }
     }
 
-
     /* ------------------------------------------------------------ */
-    /**
-     * Returns an output stream to the resource
-     */
     @Override
-    public OutputStream getOutputStream()
-        throws java.io.IOException, SecurityException
+    public ReadableByteChannel getReadableByteChannel() throws IOException
     {
-        throw new IOException( "Output not supported");
+        return null;
     }
 
     /* ------------------------------------------------------------ */
@@ -282,7 +301,7 @@ public class URLResource extends Resource
 
         path = URIUtil.canonicalPath(path);
 
-        return newResource(URIUtil.addPaths(_url.toExternalForm(),path));
+        return newResource(URIUtil.addPaths(_url.toExternalForm(),URIUtil.encodePath(path)), _useCaches);
     }
 
     /* ------------------------------------------------------------ */
@@ -316,6 +335,6 @@ public class URLResource extends Resource
     @Override
     public boolean isContainedIn (Resource containingResource) throws MalformedURLException
     {
-        return false; //TODO check this!
+        return false;
     }
 }
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/resource/package-info.java b/jetty-util/src/main/java/org/eclipse/jetty/util/resource/package-info.java
new file mode 100644
index 0000000..6c77ce5
--- /dev/null
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/resource/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Util : Common Resource Utilities
+ */
+package org.eclipse.jetty.util.resource;
+
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/security/B64Code.java b/jetty-util/src/main/java/org/eclipse/jetty/util/security/B64Code.java
deleted file mode 100644
index 4cebecb..0000000
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/security/B64Code.java
+++ /dev/null
@@ -1,33 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-
-package org.eclipse.jetty.util.security;
-
-
-/* ------------------------------------------------------------ */
-/**
- * @deprecated use {@link org.eclipse.jetty.util.B64Code}
- */
- at Deprecated 
-public class B64Code extends org.eclipse.jetty.util.B64Code
-{
-    public B64Code()
-    {
-    }
-}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/security/Constraint.java b/jetty-util/src/main/java/org/eclipse/jetty/util/security/Constraint.java
index 1640c1e..f159ed5 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/security/Constraint.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/security/Constraint.java
@@ -23,6 +23,8 @@ import java.util.Arrays;
 
 /* ------------------------------------------------------------ */
 /**
+ * Constraint
+ * 
  * Describe an auth and/or data constraint.
  * 
  * 
@@ -65,6 +67,8 @@ public class Constraint implements Cloneable, Serializable
     public final static String NONE = "NONE";
 
     public final static String ANY_ROLE = "*";
+    
+    public final static String ANY_AUTH = "**"; //Servlet Spec 3.1 pg 140
 
     /* ------------------------------------------------------------ */
     private String _name;
@@ -74,6 +78,8 @@ public class Constraint implements Cloneable, Serializable
     private int _dataConstraint = DC_UNSET;
 
     private boolean _anyRole = false;
+    
+    private boolean _anyAuth = false;
 
     private boolean _authenticate = false;
 
@@ -115,13 +121,25 @@ public class Constraint implements Cloneable, Serializable
     }
 
     /* ------------------------------------------------------------ */
+    public String getName()
+    {
+        return _name;
+    }
+
+    /* ------------------------------------------------------------ */
     public void setRoles(String[] roles)
     {
         _roles = roles;
         _anyRole = false;
+        _anyAuth = false;
         if (roles != null) 
-            for (int i = roles.length; !_anyRole && i-- > 0;)
+        {
+            for (int i = roles.length; i-- > 0;)
+            {
                 _anyRole |= ANY_ROLE.equals(roles[i]);
+                _anyAuth |= ANY_AUTH.equals(roles[i]);
+            }
+        }
     }
 
     /* ------------------------------------------------------------ */
@@ -132,6 +150,16 @@ public class Constraint implements Cloneable, Serializable
     {
         return _anyRole;
     }
+    
+    
+    /* ------------------------------------------------------------ */
+    /** Servlet Spec 3.1, pg 140
+     * @return True if any authenticated user is permitted (ie a role "**" was specified in the constraint).
+     */
+    public boolean isAnyAuth()
+    {
+        return _anyAuth;
+    }
 
     /* ------------------------------------------------------------ */
     /**
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/security/Credential.java b/jetty-util/src/main/java/org/eclipse/jetty/util/security/Credential.java
index 82ef113..c763f94 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/security/Credential.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/security/Credential.java
@@ -19,9 +19,9 @@
 package org.eclipse.jetty.util.security;
 
 import java.io.Serializable;
+import java.nio.charset.StandardCharsets;
 import java.security.MessageDigest;
 
-import org.eclipse.jetty.util.StringUtil;
 import org.eclipse.jetty.util.TypeUtil;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
@@ -156,21 +156,23 @@ public abstract class Credential implements Serializable
                     {
                         if (__md == null) __md = MessageDigest.getInstance("MD5");
                         __md.reset();
-                        __md.update(credentials.toString().getBytes(StringUtil.__ISO_8859_1));
+                        __md.update(credentials.toString().getBytes(StandardCharsets.ISO_8859_1));
                         digest = __md.digest();
                     }
                     if (digest == null || digest.length != _digest.length) return false;
+                    boolean digestMismatch = false;
                     for (int i = 0; i < digest.length; i++)
-                        if (digest[i] != _digest[i]) return false;
-                    return true;
+                        digestMismatch |= (digest[i] != _digest[i]);
+                    return !digestMismatch;
                 }
                 else if (credentials instanceof MD5)
                 {
                     MD5 md5 = (MD5) credentials;
                     if (_digest.length != md5._digest.length) return false;
+                    boolean digestMismatch = false;
                     for (int i = 0; i < _digest.length; i++)
-                        if (_digest[i] != md5._digest[i]) return false;
-                    return true;
+                        digestMismatch |= (_digest[i] != md5._digest[i]);
+                    return !digestMismatch;
                 }
                 else if (credentials instanceof Credential)
                 {
@@ -213,7 +215,7 @@ public abstract class Credential implements Serializable
                     }
 
                     __md.reset();
-                    __md.update(password.getBytes(StringUtil.__ISO_8859_1));
+                    __md.update(password.getBytes(StandardCharsets.ISO_8859_1));
                     digest = __md.digest();
                 }
 
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/security/Password.java b/jetty-util/src/main/java/org/eclipse/jetty/util/security/Password.java
index 6b3916c..a443849 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/security/Password.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/security/Password.java
@@ -19,6 +19,7 @@
 package org.eclipse.jetty.util.security;
 
 import java.io.IOException;
+import java.nio.charset.StandardCharsets;
 import java.util.Arrays;
 
 import org.eclipse.jetty.util.log.Log;
@@ -141,39 +142,36 @@ public class Password extends Credential
     public static String obfuscate(String s)
     {
         StringBuilder buf = new StringBuilder();
-        byte[] b = s.getBytes();
+        byte[] b = s.getBytes(StandardCharsets.UTF_8);
 
         buf.append(__OBFUSCATE);
         for (int i = 0; i < b.length; i++)
         {
             byte b1 = b[i];
-            byte b2 = b[s.length() - (i + 1)];
-            int i1 = 127 + b1 + b2;
-            int i2 = 127 + b1 - b2;
-            int i0 = i1 * 256 + i2;
-            String x = Integer.toString(i0, 36);
-
-            switch (x.length())
+            byte b2 = b[b.length - (i + 1)];
+            if (b1<0 || b2<0)
+            {
+                int i0 = (0xff&b1)*256 + (0xff&b2); 
+                String x = Integer.toString(i0, 36).toLowerCase();
+                buf.append("U0000",0,5-x.length());
+                buf.append(x);
+            }
+            else
             {
-                case 1:
-                    buf.append('0');
-                    buf.append('0');
-                    buf.append('0');
-                    buf.append(x);
-                    break;
-                case 2:
-                    buf.append('0');
-                    buf.append('0');
-                    buf.append(x);
-                    break;
-                case 3:
-                    buf.append('0');
-                    buf.append(x);
-                    break;
-                default:
-                    buf.append(x);
-                    break;
+                int i1 = 127 + b1 + b2;
+                int i2 = 127 + b1 - b2;
+                int i0 = i1 * 256 + i2;
+                String x = Integer.toString(i0, 36).toLowerCase();
+
+                int j0 = Integer.parseInt(x, 36);
+                int j1 = (i0 / 256);
+                int j2 = (i0 % 256);
+                byte bx = (byte) ((j1 + j2 - 254) / 2);
+                
+                buf.append("000",0,4-x.length());
+                buf.append(x);
             }
+
         }
         return buf.toString();
 
@@ -188,14 +186,26 @@ public class Password extends Credential
         int l = 0;
         for (int i = 0; i < s.length(); i += 4)
         {
-            String x = s.substring(i, i + 4);
-            int i0 = Integer.parseInt(x, 36);
-            int i1 = (i0 / 256);
-            int i2 = (i0 % 256);
-            b[l++] = (byte) ((i1 + i2 - 254) / 2);
+            if (s.charAt(i)=='U')
+            {
+                i++;
+                String x = s.substring(i, i + 4);
+                int i0 = Integer.parseInt(x, 36);
+                byte bx = (byte)(i0>>8);
+                b[l++] = bx;
+            }
+            else
+            {
+                String x = s.substring(i, i + 4);
+                int i0 = Integer.parseInt(x, 36);
+                int i1 = (i0 / 256);
+                int i2 = (i0 % 256);
+                byte bx = (byte) ((i1 + i2 - 254) / 2);
+                b[l++] = bx;
+            }
         }
 
-        return new String(b, 0, l);
+        return new String(b, 0, l,StandardCharsets.UTF_8);
     }
 
     /* ------------------------------------------------------------ */
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/security/package-info.java b/jetty-util/src/main/java/org/eclipse/jetty/util/security/package-info.java
new file mode 100644
index 0000000..9a5e9bb
--- /dev/null
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/security/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Util : Common Security Utilities
+ */
+package org.eclipse.jetty.util.security;
+
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/ssl/AliasedX509ExtendedKeyManager.java b/jetty-util/src/main/java/org/eclipse/jetty/util/ssl/AliasedX509ExtendedKeyManager.java
index 3e698e3..d56b123 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/ssl/AliasedX509ExtendedKeyManager.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/ssl/AliasedX509ExtendedKeyManager.java
@@ -66,7 +66,7 @@ public class AliasedX509ExtendedKeyManager extends X509ExtendedKeyManager
      * @see javax.net.ssl.X509KeyManager#chooseServerAlias(java.lang.String, java.security.Principal[], java.net.Socket)
      */
     public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket)
-    {   
+    {
         return _keyAlias == null ? _keyManager.chooseServerAlias(keyType, issuers, socket) : _keyAlias;
     }
 
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/ssl/AliasedX509KeyManager.java b/jetty-util/src/main/java/org/eclipse/jetty/util/ssl/AliasedX509KeyManager.java
index 3403c09..509f53e 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/ssl/AliasedX509KeyManager.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/ssl/AliasedX509KeyManager.java
@@ -64,7 +64,7 @@ public class AliasedX509KeyManager implements X509KeyManager
      * @see javax.net.ssl.X509KeyManager#chooseServerAlias(java.lang.String, java.security.Principal[], java.net.Socket)
      */
     public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket)
-    {   
+    {
         return _keyAlias == null ?_keyManager.chooseServerAlias(keyType, issuers, socket) : _keyAlias;
     }
 
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/ssl/SslContextFactory.java b/jetty-util/src/main/java/org/eclipse/jetty/util/ssl/SslContextFactory.java
index 17470dd..cf2e2a7 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/ssl/SslContextFactory.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/ssl/SslContextFactory.java
@@ -20,10 +20,10 @@ package org.eclipse.jetty.util.ssl;
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
-import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.InetAddress;
+import java.net.InetSocketAddress;
 import java.security.InvalidParameterException;
 import java.security.KeyStore;
 import java.security.SecureRandom;
@@ -34,19 +34,27 @@ import java.security.cert.Certificate;
 import java.security.cert.CollectionCertStoreParameters;
 import java.security.cert.PKIXBuilderParameters;
 import java.security.cert.X509CertSelector;
+import java.security.cert.X509Certificate;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
 import javax.net.ssl.CertPathTrustManagerParameters;
 import javax.net.ssl.KeyManager;
 import javax.net.ssl.KeyManagerFactory;
 import javax.net.ssl.SSLContext;
 import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLParameters;
+import javax.net.ssl.SSLPeerUnverifiedException;
 import javax.net.ssl.SSLServerSocket;
 import javax.net.ssl.SSLServerSocketFactory;
+import javax.net.ssl.SSLSession;
 import javax.net.ssl.SSLSocket;
 import javax.net.ssl.SSLSocketFactory;
 import javax.net.ssl.TrustManager;
@@ -64,7 +72,6 @@ import org.eclipse.jetty.util.security.CertificateValidator;
 import org.eclipse.jetty.util.security.Password;
 
 
-/* ------------------------------------------------------------ */
 /**
  * SslContextFactory is used to configure SSL connectors
  * as well as HttpClient. It holds all SSL parameters and
@@ -89,18 +96,15 @@ public class SslContextFactory extends AbstractLifeCycle
         }
     }};
 
-    private static final Logger LOG = Log.getLogger(SslContextFactory.class);
+    static final Logger LOG = Log.getLogger(SslContextFactory.class);
 
     public static final String DEFAULT_KEYMANAGERFACTORY_ALGORITHM =
         (Security.getProperty("ssl.KeyManagerFactory.algorithm") == null ?
-                "SunX509" : Security.getProperty("ssl.KeyManagerFactory.algorithm"));
+                KeyManagerFactory.getDefaultAlgorithm() : Security.getProperty("ssl.KeyManagerFactory.algorithm"));
+
     public static final String DEFAULT_TRUSTMANAGERFACTORY_ALGORITHM =
         (Security.getProperty("ssl.TrustManagerFactory.algorithm") == null ?
-                "SunX509" : Security.getProperty("ssl.TrustManagerFactory.algorithm"));
-
-    /** Default value for the keystore location path. */
-    public static final String DEFAULT_KEYSTORE_PATH =
-        System.getProperty("user.home") + File.separator + ".keystore";
+                TrustManagerFactory.getDefaultAlgorithm() : Security.getProperty("ssl.TrustManagerFactory.algorithm"));
 
     /** String name of key password property. */
     public static final String KEYPASSWORD_PROPERTY = "org.eclipse.jetty.ssl.keypassword";
@@ -109,14 +113,16 @@ public class SslContextFactory extends AbstractLifeCycle
     public static final String PASSWORD_PROPERTY = "org.eclipse.jetty.ssl.password";
 
     /** Excluded protocols. */
-    private final Set<String> _excludeProtocols = new LinkedHashSet<String>();
+    private final Set<String> _excludeProtocols = new LinkedHashSet<>();
+
     /** Included protocols. */
-    private Set<String> _includeProtocols = null;
+    private final Set<String> _includeProtocols = new LinkedHashSet<>();
 
     /** Excluded cipher suites. */
-    private final Set<String> _excludeCipherSuites = new LinkedHashSet<String>();
+    private final Set<String> _excludeCipherSuites = new LinkedHashSet<>();
+    
     /** Included cipher suites. */
-    private Set<String> _includeCipherSuites = null;
+    private final Set<String> _includeCipherSuites = new LinkedHashSet<>();
 
     /** Keystore path. */
     private String _keyStorePath;
@@ -144,9 +150,6 @@ public class SslContextFactory extends AbstractLifeCycle
     /** Set to true if client certificate authentication is desired */
     private boolean _wantClientAuth = false;
 
-    /** Set to true if renegotiation is allowed */
-    private boolean _allowRenegotiate = true;
-
     /** Keystore password */
     private transient Password _keyStorePassword;
     /** Key manager password */
@@ -195,19 +198,24 @@ public class SslContextFactory extends AbstractLifeCycle
     /** SSL context */
     private SSLContext _context;
 
+    /** EndpointIdentificationAlgorithm - when set to "HTTPS" hostname verification will be enabled */
+    private String _endpointIdentificationAlgorithm = null;
+
+    /** Whether to blindly trust certificates */
     private boolean _trustAll;
 
-    /* ------------------------------------------------------------ */
+    /** Whether TLS renegotiation is allowed */
+    private boolean _renegotiationAllowed = true;
+
     /**
      * Construct an instance of SslContextFactory
      * Default constructor for use in XmlConfiguration files
      */
     public SslContextFactory()
     {
-        _trustAll=true;
+        this(false);
     }
 
-    /* ------------------------------------------------------------ */
     /**
      * Construct an instance of SslContextFactory
      * Default constructor for use in XmlConfiguration files
@@ -216,10 +224,10 @@ public class SslContextFactory extends AbstractLifeCycle
      */
     public SslContextFactory(boolean trustAll)
     {
-        _trustAll=trustAll;
+        setTrustAll(trustAll);
+        addExcludeProtocols("SSL", "SSLv2", "SSLv2Hello", "SSLv3");
     }
 
-    /* ------------------------------------------------------------ */
     /**
      * Construct an instance of SslContextFactory
      * @param keyStorePath default keystore location
@@ -229,7 +237,6 @@ public class SslContextFactory extends AbstractLifeCycle
         _keyStorePath = keyStorePath;
     }
 
-    /* ------------------------------------------------------------ */
     /**
      * Create the SSLContext object and start the lifecycle
      * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart()
@@ -246,14 +253,16 @@ public class SslContextFactory extends AbstractLifeCycle
 
                 if (_trustAll)
                 {
-                    LOG.debug("No keystore or trust store configured.  ACCEPTING UNTRUSTED CERTIFICATES!!!!!");
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("No keystore or trust store configured.  ACCEPTING UNTRUSTED CERTIFICATES!!!!!");
                     // Create a trust manager that does not validate certificate chains
                     trust_managers = TRUST_ALL_CERTS;
                 }
 
                 SecureRandom secureRandom = (_secureRandomAlgorithm == null)?null:SecureRandom.getInstance(_secureRandomAlgorithm);
-                _context = (_sslProvider == null)?SSLContext.getInstance(_sslProtocol):SSLContext.getInstance(_sslProtocol,_sslProvider);
-                _context.init(null, trust_managers, secureRandom);
+                SSLContext context = _sslProvider == null ? SSLContext.getInstance(_sslProtocol) : SSLContext.getInstance(_sslProtocol, _sslProvider);
+                context.init(null, trust_managers, secureRandom);
+                _context = context;
             }
             else
             {
@@ -292,19 +301,27 @@ public class SslContextFactory extends AbstractLifeCycle
                 TrustManager[] trustManagers = getTrustManagers(trustStore,crls);
 
                 SecureRandom secureRandom = (_secureRandomAlgorithm == null)?null:SecureRandom.getInstance(_secureRandomAlgorithm);
-                _context = (_sslProvider == null)?SSLContext.getInstance(_sslProtocol):SSLContext.getInstance(_sslProtocol,_sslProvider);
-                _context.init(keyManagers,trustManagers,secureRandom);
-
-                SSLEngine engine=newSslEngine();
+                SSLContext context = _sslProvider == null ? SSLContext.getInstance(_sslProtocol) : SSLContext.getInstance(_sslProtocol, _sslProvider);
+                context.init(keyManagers,trustManagers,secureRandom);
+                _context = context;
+            }
 
-                LOG.info("Enabled Protocols {} of {}",Arrays.asList(engine.getEnabledProtocols()),Arrays.asList(engine.getSupportedProtocols()));
-                if (LOG.isDebugEnabled())
-                    LOG.debug("Enabled Ciphers   {} of {}",Arrays.asList(engine.getEnabledCipherSuites()),Arrays.asList(engine.getSupportedCipherSuites()));
+            SSLEngine engine = newSSLEngine();
+            if (LOG.isDebugEnabled())
+            {
+                LOG.debug("Enabled Protocols {} of {}",Arrays.asList(engine.getEnabledProtocols()),Arrays.asList(engine.getSupportedProtocols()));
+                LOG.debug("Enabled Ciphers   {} of {}",Arrays.asList(engine.getEnabledCipherSuites()),Arrays.asList(engine.getSupportedCipherSuites()));
             }
         }
     }
 
-    /* ------------------------------------------------------------ */
+    @Override
+    protected void doStop() throws Exception
+    {
+        _context = null;
+        super.doStop();
+    }
+
     /**
      * @return The array of protocol names to exclude from
      * {@link SSLEngine#setEnabledProtocols(String[])}
@@ -314,7 +331,6 @@ public class SslContextFactory extends AbstractLifeCycle
         return _excludeProtocols.toArray(new String[_excludeProtocols.size()]);
     }
 
-    /* ------------------------------------------------------------ */
     /**
      * @param protocols
      *            The array of protocol names to exclude from
@@ -323,12 +339,10 @@ public class SslContextFactory extends AbstractLifeCycle
     public void setExcludeProtocols(String... protocols)
     {
         checkNotStarted();
-
         _excludeProtocols.clear();
         _excludeProtocols.addAll(Arrays.asList(protocols));
     }
 
-    /* ------------------------------------------------------------ */
     /**
      * @param protocol Protocol names to add to {@link SSLEngine#setEnabledProtocols(String[])}
      */
@@ -338,7 +352,6 @@ public class SslContextFactory extends AbstractLifeCycle
         _excludeProtocols.addAll(Arrays.asList(protocol));
     }
 
-    /* ------------------------------------------------------------ */
     /**
      * @return The array of protocol names to include in
      * {@link SSLEngine#setEnabledProtocols(String[])}
@@ -348,7 +361,6 @@ public class SslContextFactory extends AbstractLifeCycle
         return _includeProtocols.toArray(new String[_includeProtocols.size()]);
     }
 
-    /* ------------------------------------------------------------ */
     /**
      * @param protocols
      *            The array of protocol names to include in
@@ -357,11 +369,10 @@ public class SslContextFactory extends AbstractLifeCycle
     public void setIncludeProtocols(String... protocols)
     {
         checkNotStarted();
-
-        _includeProtocols = new LinkedHashSet<String>(Arrays.asList(protocols));
+        _includeProtocols.clear();
+        _includeProtocols.addAll(Arrays.asList(protocols));
     }
 
-    /* ------------------------------------------------------------ */
     /**
      * @return The array of cipher suite names to exclude from
      * {@link SSLEngine#setEnabledCipherSuites(String[])}
@@ -371,8 +382,8 @@ public class SslContextFactory extends AbstractLifeCycle
         return _excludeCipherSuites.toArray(new String[_excludeCipherSuites.size()]);
     }
 
-    /* ------------------------------------------------------------ */
     /**
+     * You can either use the exact cipher suite name or a a regular expression.
      * @param cipherSuites
      *            The array of cipher suite names to exclude from
      *            {@link SSLEngine#setEnabledCipherSuites(String[])}
@@ -384,7 +395,6 @@ public class SslContextFactory extends AbstractLifeCycle
         _excludeCipherSuites.addAll(Arrays.asList(cipherSuites));
     }
 
-    /* ------------------------------------------------------------ */
     /**
      * @param cipher Cipher names to add to {@link SSLEngine#setEnabledCipherSuites(String[])}
      */
@@ -394,7 +404,6 @@ public class SslContextFactory extends AbstractLifeCycle
         _excludeCipherSuites.addAll(Arrays.asList(cipher));
     }
 
-    /* ------------------------------------------------------------ */
     /**
      * @return The array of cipher suite names to include in
      * {@link SSLEngine#setEnabledCipherSuites(String[])}
@@ -404,8 +413,8 @@ public class SslContextFactory extends AbstractLifeCycle
         return _includeCipherSuites.toArray(new String[_includeCipherSuites.size()]);
     }
 
-    /* ------------------------------------------------------------ */
     /**
+     * You can either use the exact cipher suite name or a a regular expression.
      * @param cipherSuites
      *            The array of cipher suite names to include in
      *            {@link SSLEngine#setEnabledCipherSuites(String[])}
@@ -413,11 +422,10 @@ public class SslContextFactory extends AbstractLifeCycle
     public void setIncludeCipherSuites(String... cipherSuites)
     {
         checkNotStarted();
-
-        _includeCipherSuites = new LinkedHashSet<String>(Arrays.asList(cipherSuites));
+        _includeCipherSuites.clear();
+        _includeCipherSuites.addAll(Arrays.asList(cipherSuites));
     }
 
-    /* ------------------------------------------------------------ */
     /**
      * @return The file or URL of the SSL Key store.
      */
@@ -426,14 +434,6 @@ public class SslContextFactory extends AbstractLifeCycle
         return _keyStorePath;
     }
 
-    /* ------------------------------------------------------------ */
-    @Deprecated
-    public String getKeyStore()
-    {
-        return _keyStorePath;
-    }
-
-    /* ------------------------------------------------------------ */
     /**
      * @param keyStorePath
      *            The file or URL of the SSL Key store.
@@ -441,24 +441,9 @@ public class SslContextFactory extends AbstractLifeCycle
     public void setKeyStorePath(String keyStorePath)
     {
         checkNotStarted();
-
-        _keyStorePath = keyStorePath;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param keyStorePath the file system path or URL of the keystore
-     * @deprecated Use {@link #setKeyStorePath(String)}
-     */
-    @Deprecated
-    public void setKeyStore(String keyStorePath)
-    {
-        checkNotStarted();
-
         _keyStorePath = keyStorePath;
     }
 
-    /* ------------------------------------------------------------ */
     /**
      * @return The provider of the key store
      */
@@ -467,7 +452,6 @@ public class SslContextFactory extends AbstractLifeCycle
         return _keyStoreProvider;
     }
 
-    /* ------------------------------------------------------------ */
     /**
      * @param keyStoreProvider
      *            The provider of the key store
@@ -475,11 +459,9 @@ public class SslContextFactory extends AbstractLifeCycle
     public void setKeyStoreProvider(String keyStoreProvider)
     {
         checkNotStarted();
-
         _keyStoreProvider = keyStoreProvider;
     }
 
-    /* ------------------------------------------------------------ */
     /**
      * @return The type of the key store (default "JKS")
      */
@@ -488,7 +470,6 @@ public class SslContextFactory extends AbstractLifeCycle
         return (_keyStoreType);
     }
 
-    /* ------------------------------------------------------------ */
     /**
      * @param keyStoreType
      *            The type of the key store (default "JKS")
@@ -496,39 +477,9 @@ public class SslContextFactory extends AbstractLifeCycle
     public void setKeyStoreType(String keyStoreType)
     {
         checkNotStarted();
-
         _keyStoreType = keyStoreType;
     }
 
-    /* ------------------------------------------------------------ */
-    /** Get the _keyStoreInputStream.
-     * @return the _keyStoreInputStream
-     *
-     * @deprecated
-     */
-    @Deprecated
-    public InputStream getKeyStoreInputStream()
-    {
-        checkKeyStore();
-
-        return _keyStoreInputStream;
-    }
-
-    /* ------------------------------------------------------------ */
-    /** Set the keyStoreInputStream.
-     * @param keyStoreInputStream the InputStream to the KeyStore
-     *
-     * @deprecated Use {@link #setKeyStore(KeyStore)}
-     */
-    @Deprecated
-    public void setKeyStoreInputStream(InputStream keyStoreInputStream)
-    {
-        checkNotStarted();
-
-        _keyStoreInputStream = keyStoreInputStream;
-    }
-
-    /* ------------------------------------------------------------ */
     /**
      * @return Alias of SSL certificate for the connector
      */
@@ -537,7 +488,6 @@ public class SslContextFactory extends AbstractLifeCycle
         return _certAlias;
     }
 
-    /* ------------------------------------------------------------ */
     /**
      * @param certAlias
      *            Alias of SSL certificate for the connector
@@ -545,11 +495,9 @@ public class SslContextFactory extends AbstractLifeCycle
     public void setCertAlias(String certAlias)
     {
         checkNotStarted();
-
         _certAlias = certAlias;
     }
 
-    /* ------------------------------------------------------------ */
     /**
      * @return The file name or URL of the trust store location
      */
@@ -558,19 +506,16 @@ public class SslContextFactory extends AbstractLifeCycle
         return _trustStorePath;
     }
 
-    /* ------------------------------------------------------------ */
     /**
      * @param trustStorePath
      *            The file name or URL of the trust store location
      */
-    public void setTrustStore(String trustStorePath)
+    public void setTrustStorePath(String trustStorePath)
     {
         checkNotStarted();
-
         _trustStorePath = trustStorePath;
     }
 
-    /* ------------------------------------------------------------ */
     /**
      * @return The provider of the trust store
      */
@@ -579,7 +524,6 @@ public class SslContextFactory extends AbstractLifeCycle
         return _trustStoreProvider;
     }
 
-    /* ------------------------------------------------------------ */
     /**
      * @param trustStoreProvider
      *            The provider of the trust store
@@ -587,11 +531,9 @@ public class SslContextFactory extends AbstractLifeCycle
     public void setTrustStoreProvider(String trustStoreProvider)
     {
         checkNotStarted();
-
         _trustStoreProvider = trustStoreProvider;
     }
 
-    /* ------------------------------------------------------------ */
     /**
      * @return The type of the trust store (default "JKS")
      */
@@ -600,7 +542,6 @@ public class SslContextFactory extends AbstractLifeCycle
         return _trustStoreType;
     }
 
-    /* ------------------------------------------------------------ */
     /**
      * @param trustStoreType
      *            The type of the trust store (default "JKS")
@@ -608,39 +549,9 @@ public class SslContextFactory extends AbstractLifeCycle
     public void setTrustStoreType(String trustStoreType)
     {
         checkNotStarted();
-
         _trustStoreType = trustStoreType;
     }
 
-    /* ------------------------------------------------------------ */
-    /** Get the _trustStoreInputStream.
-     * @return the _trustStoreInputStream
-     *
-     * @deprecated
-     */
-    @Deprecated
-    public InputStream getTrustStoreInputStream()
-    {
-        checkKeyStore();
-
-        return _trustStoreInputStream;
-    }
-
-    /* ------------------------------------------------------------ */
-    /** Set the _trustStoreInputStream.
-     * @param trustStoreInputStream the InputStream to the TrustStore
-     *
-     * @deprecated
-     */
-    @Deprecated
-    public void setTrustStoreInputStream(InputStream trustStoreInputStream)
-    {
-        checkNotStarted();
-
-        _trustStoreInputStream = trustStoreInputStream;
-    }
-
-    /* ------------------------------------------------------------ */
     /**
      * @return True if SSL needs client authentication.
      * @see SSLEngine#getNeedClientAuth()
@@ -650,7 +561,6 @@ public class SslContextFactory extends AbstractLifeCycle
         return _needClientAuth;
     }
 
-    /* ------------------------------------------------------------ */
     /**
      * @param needClientAuth
      *            True if SSL needs client authentication.
@@ -659,11 +569,9 @@ public class SslContextFactory extends AbstractLifeCycle
     public void setNeedClientAuth(boolean needClientAuth)
     {
         checkNotStarted();
-
         _needClientAuth = needClientAuth;
     }
 
-    /* ------------------------------------------------------------ */
     /**
      * @return True if SSL wants client authentication.
      * @see SSLEngine#getWantClientAuth()
@@ -673,7 +581,6 @@ public class SslContextFactory extends AbstractLifeCycle
         return _wantClientAuth;
     }
 
-    /* ------------------------------------------------------------ */
     /**
      * @param wantClientAuth
      *            True if SSL wants client authentication.
@@ -682,22 +589,9 @@ public class SslContextFactory extends AbstractLifeCycle
     public void setWantClientAuth(boolean wantClientAuth)
     {
         checkNotStarted();
-
         _wantClientAuth = wantClientAuth;
     }
 
-    /* ------------------------------------------------------------ */
-    /**
-     * @return true if SSL certificate has to be validated
-     * @deprecated
-     */
-    @Deprecated
-    public boolean getValidateCerts()
-    {
-        return _validateCerts;
-    }
-
-    /* ------------------------------------------------------------ */
     /**
      * @return true if SSL certificate has to be validated
      */
@@ -706,7 +600,6 @@ public class SslContextFactory extends AbstractLifeCycle
         return _validateCerts;
     }
 
-    /* ------------------------------------------------------------ */
     /**
      * @param validateCerts
      *            true if SSL certificates have to be validated
@@ -714,11 +607,9 @@ public class SslContextFactory extends AbstractLifeCycle
     public void setValidateCerts(boolean validateCerts)
     {
         checkNotStarted();
-
         _validateCerts = validateCerts;
     }
 
-    /* ------------------------------------------------------------ */
     /**
      * @return true if SSL certificates of the peer have to be validated
      */
@@ -727,7 +618,6 @@ public class SslContextFactory extends AbstractLifeCycle
         return _validatePeerCerts;
     }
 
-    /* ------------------------------------------------------------ */
     /**
      * @param validatePeerCerts
      *            true if SSL certificates of the peer have to be validated
@@ -735,38 +625,10 @@ public class SslContextFactory extends AbstractLifeCycle
     public void setValidatePeerCerts(boolean validatePeerCerts)
     {
         checkNotStarted();
-
         _validatePeerCerts = validatePeerCerts;
     }
 
-    /* ------------------------------------------------------------ */
-    /**
-     * @return True if SSL re-negotiation is allowed (default false)
-     */
-    public boolean isAllowRenegotiate()
-    {
-        return _allowRenegotiate;
-    }
 
-    /* ------------------------------------------------------------ */
-    /**
-     * Set if SSL re-negotiation is allowed. CVE-2009-3555 discovered
-     * a vulnerability in SSL/TLS with re-negotiation.  If your JVM
-     * does not have CVE-2009-3555 fixed, then re-negotiation should
-     * not be allowed.  CVE-2009-3555 was fixed in Sun java 1.6 with a ban
-     * of renegotiates in u19 and with RFC5746 in u22.
-     *
-     * @param allowRenegotiate
-     *            true if re-negotiation is allowed (default false)
-     */
-    public void setAllowRenegotiate(boolean allowRenegotiate)
-    {
-        checkNotStarted();
-
-        _allowRenegotiate = allowRenegotiate;
-    }
-
-    /* ------------------------------------------------------------ */
     /**
      * @param password
      *            The password for the key store
@@ -774,11 +636,9 @@ public class SslContextFactory extends AbstractLifeCycle
     public void setKeyStorePassword(String password)
     {
         checkNotStarted();
-
         _keyStorePassword = Password.getPassword(PASSWORD_PROPERTY,password,null);
     }
 
-    /* ------------------------------------------------------------ */
     /**
      * @param password
      *            The password (if any) for the specific key within the key store
@@ -786,11 +646,9 @@ public class SslContextFactory extends AbstractLifeCycle
     public void setKeyManagerPassword(String password)
     {
         checkNotStarted();
-
         _keyManagerPassword = Password.getPassword(KEYPASSWORD_PROPERTY,password,null);
     }
 
-    /* ------------------------------------------------------------ */
     /**
      * @param password
      *            The password for the trust store
@@ -798,11 +656,9 @@ public class SslContextFactory extends AbstractLifeCycle
     public void setTrustStorePassword(String password)
     {
         checkNotStarted();
-
         _trustStorePassword = Password.getPassword(PASSWORD_PROPERTY,password,null);
     }
 
-    /* ------------------------------------------------------------ */
     /**
      * @return The SSL provider name, which if set is passed to
      * {@link SSLContext#getInstance(String, String)}
@@ -812,7 +668,6 @@ public class SslContextFactory extends AbstractLifeCycle
         return _sslProvider;
     }
 
-    /* ------------------------------------------------------------ */
     /**
      * @param provider
      *            The SSL provider name, which if set is passed to
@@ -821,11 +676,9 @@ public class SslContextFactory extends AbstractLifeCycle
     public void setProvider(String provider)
     {
         checkNotStarted();
-
         _sslProvider = provider;
     }
 
-    /* ------------------------------------------------------------ */
     /**
      * @return The SSL protocol (default "TLS") passed to
      * {@link SSLContext#getInstance(String, String)}
@@ -835,7 +688,6 @@ public class SslContextFactory extends AbstractLifeCycle
         return _sslProtocol;
     }
 
-    /* ------------------------------------------------------------ */
     /**
      * @param protocol
      *            The SSL protocol (default "TLS") passed to
@@ -844,11 +696,9 @@ public class SslContextFactory extends AbstractLifeCycle
     public void setProtocol(String protocol)
     {
         checkNotStarted();
-
         _sslProtocol = protocol;
     }
 
-    /* ------------------------------------------------------------ */
     /**
      * @return The algorithm name, which if set is passed to
      * {@link SecureRandom#getInstance(String)} to obtain the {@link SecureRandom} instance passed to
@@ -859,7 +709,6 @@ public class SslContextFactory extends AbstractLifeCycle
         return _secureRandomAlgorithm;
     }
 
-    /* ------------------------------------------------------------ */
     /**
      * @param algorithm
      *            The algorithm name, which if set is passed to
@@ -869,11 +718,9 @@ public class SslContextFactory extends AbstractLifeCycle
     public void setSecureRandomAlgorithm(String algorithm)
     {
         checkNotStarted();
-
         _secureRandomAlgorithm = algorithm;
     }
 
-    /* ------------------------------------------------------------ */
     /**
      * @return The algorithm name (default "SunX509") used by the {@link KeyManagerFactory}
      */
@@ -882,7 +729,6 @@ public class SslContextFactory extends AbstractLifeCycle
         return (_keyManagerFactoryAlgorithm);
     }
 
-    /* ------------------------------------------------------------ */
     /**
      * @param algorithm
      *            The algorithm name (default "SunX509") used by the {@link KeyManagerFactory}
@@ -890,11 +736,9 @@ public class SslContextFactory extends AbstractLifeCycle
     public void setSslKeyManagerFactoryAlgorithm(String algorithm)
     {
         checkNotStarted();
-
         _keyManagerFactoryAlgorithm = algorithm;
     }
 
-    /* ------------------------------------------------------------ */
     /**
      * @return The algorithm name (default "SunX509") used by the {@link TrustManagerFactory}
      */
@@ -903,7 +747,6 @@ public class SslContextFactory extends AbstractLifeCycle
         return (_trustManagerFactoryAlgorithm);
     }
 
-    /* ------------------------------------------------------------ */
     /**
      * @return True if all certificates should be trusted if there is no KeyStore or TrustStore
      */
@@ -912,16 +755,16 @@ public class SslContextFactory extends AbstractLifeCycle
         return _trustAll;
     }
 
-    /* ------------------------------------------------------------ */
     /**
      * @param trustAll True if all certificates should be trusted if there is no KeyStore or TrustStore
      */
     public void setTrustAll(boolean trustAll)
     {
         _trustAll = trustAll;
+        if(trustAll)
+            setEndpointIdentificationAlgorithm(null);
     }
 
-    /* ------------------------------------------------------------ */
     /**
      * @param algorithm
      *            The algorithm name (default "SunX509") used by the {@link TrustManagerFactory}
@@ -930,11 +773,25 @@ public class SslContextFactory extends AbstractLifeCycle
     public void setTrustManagerFactoryAlgorithm(String algorithm)
     {
         checkNotStarted();
-
         _trustManagerFactoryAlgorithm = algorithm;
     }
 
-    /* ------------------------------------------------------------ */
+    /**
+     * @return whether TLS renegotiation is allowed (true by default)
+     */
+    public boolean isRenegotiationAllowed()
+    {
+        return _renegotiationAllowed;
+    }
+
+    /**
+     * @param renegotiationAllowed whether TLS renegotiation is allowed
+     */
+    public void setRenegotiationAllowed(boolean renegotiationAllowed)
+    {
+        _renegotiationAllowed = renegotiationAllowed;
+    }
+
     /**
      * @return Path to file that contains Certificate Revocation List
      */
@@ -943,7 +800,6 @@ public class SslContextFactory extends AbstractLifeCycle
         return _crlPath;
     }
 
-    /* ------------------------------------------------------------ */
     /**
      * @param crlPath
      *            Path to file that contains Certificate Revocation List
@@ -951,11 +807,9 @@ public class SslContextFactory extends AbstractLifeCycle
     public void setCrlPath(String crlPath)
     {
         checkNotStarted();
-
         _crlPath = crlPath;
     }
 
-    /* ------------------------------------------------------------ */
     /**
      * @return Maximum number of intermediate certificates in
      * the certification path (-1 for unlimited)
@@ -965,7 +819,6 @@ public class SslContextFactory extends AbstractLifeCycle
         return _maxCertPathLength;
     }
 
-    /* ------------------------------------------------------------ */
     /**
      * @param maxCertPathLength
      *            maximum number of intermediate certificates in
@@ -974,11 +827,9 @@ public class SslContextFactory extends AbstractLifeCycle
     public void setMaxCertPathLength(int maxCertPathLength)
     {
         checkNotStarted();
-
         _maxCertPathLength = maxCertPathLength;
     }
 
-    /* ------------------------------------------------------------ */
     /**
      * @return The SSLContext
      */
@@ -989,7 +840,6 @@ public class SslContextFactory extends AbstractLifeCycle
         return _context;
     }
 
-    /* ------------------------------------------------------------ */
     /**
      * @param sslContext
      *            Set a preconfigured SSLContext
@@ -997,11 +847,19 @@ public class SslContextFactory extends AbstractLifeCycle
     public void setSslContext(SSLContext sslContext)
     {
         checkNotStarted();
-
         _context = sslContext;
     }
 
-    /* ------------------------------------------------------------ */
+    /**
+     * When set to "HTTPS" hostname verification will be enabled
+     *
+     * @param endpointIdentificationAlgorithm Set the endpointIdentificationAlgorithm
+     */
+    public void setEndpointIdentificationAlgorithm(String endpointIdentificationAlgorithm)
+    {
+        this._endpointIdentificationAlgorithm = endpointIdentificationAlgorithm;
+    }
+
     /**
      * Override this method to provide alternate way to load a keystore.
      *
@@ -1010,12 +868,11 @@ public class SslContextFactory extends AbstractLifeCycle
      */
     protected KeyStore loadKeyStore() throws Exception
     {
-        return _keyStore != null ? _keyStore : getKeyStore(_keyStoreInputStream,
+        return _keyStore != null ? _keyStore : CertificateUtils.getKeyStore(_keyStoreInputStream,
                 _keyStorePath, _keyStoreType, _keyStoreProvider,
                 _keyStorePassword==null? null: _keyStorePassword.toString());
     }
 
-    /* ------------------------------------------------------------ */
     /**
      * Override this method to provide alternate way to load a truststore.
      *
@@ -1024,36 +881,11 @@ public class SslContextFactory extends AbstractLifeCycle
      */
     protected KeyStore loadTrustStore() throws Exception
     {
-        return _trustStore != null ? _trustStore : getKeyStore(_trustStoreInputStream,
+        return _trustStore != null ? _trustStore : CertificateUtils.getKeyStore(_trustStoreInputStream,
                 _trustStorePath, _trustStoreType,  _trustStoreProvider,
                 _trustStorePassword==null? null: _trustStorePassword.toString());
     }
 
-    /* ------------------------------------------------------------ */
-    /**
-     * Loads keystore using an input stream or a file path in the same
-     * order of precedence.
-     *
-     * Required for integrations to be able to override the mechanism
-     * used to load a keystore in order to provide their own implementation.
-     *
-     * @param storeStream keystore input stream
-     * @param storePath path of keystore file
-     * @param storeType keystore type
-     * @param storeProvider keystore provider
-     * @param storePassword keystore password
-     * @return created keystore
-     * @throws Exception if the keystore cannot be obtained
-     *
-     * @deprecated
-     */
-    @Deprecated
-    protected KeyStore getKeyStore(InputStream storeStream, String storePath, String storeType, String storeProvider, String storePassword) throws Exception
-    {
-        return CertificateUtils.getKeyStore(storeStream, storePath, storeType, storeProvider, storePassword);
-    }
-
-    /* ------------------------------------------------------------ */
     /**
      * Loads certificate revocation list (CRL) from a file.
      *
@@ -1069,7 +901,6 @@ public class SslContextFactory extends AbstractLifeCycle
         return CertificateUtils.loadCRL(crlPath);
     }
 
-    /* ------------------------------------------------------------ */
     protected KeyManager[] getKeyManagers(KeyStore keyStore) throws Exception
     {
         KeyManager[] managers = null;
@@ -1095,7 +926,6 @@ public class SslContextFactory extends AbstractLifeCycle
         return managers;
     }
 
-    /* ------------------------------------------------------------ */
     protected TrustManager[] getTrustManagers(KeyStore trustStore, Collection<? extends CRL> crls) throws Exception
     {
         TrustManager[] managers = null;
@@ -1152,7 +982,6 @@ public class SslContextFactory extends AbstractLifeCycle
         return managers;
     }
 
-    /* ------------------------------------------------------------ */
     /**
      * Check KeyStore Configuration. Ensures that if keystore has been
      * configured but there's no truststore, that keystore is
@@ -1162,8 +991,7 @@ public class SslContextFactory extends AbstractLifeCycle
     public void checkKeyStore()
     {
         if (_context != null)
-            return; //nothing to check if using preconfigured context
-
+            return;
 
         if (_keyStore == null && _keyStoreInputStream == null && _keyStorePath == null)
             throw new IllegalStateException("SSL doesn't have a valid keystore");
@@ -1200,7 +1028,6 @@ public class SslContextFactory extends AbstractLifeCycle
         }
     }
 
-    /* ------------------------------------------------------------ */
     /**
      * Select protocols to be used by the connector
      * based on configured inclusion and exclusion lists
@@ -1211,10 +1038,10 @@ public class SslContextFactory extends AbstractLifeCycle
      */
     public String[] selectProtocols(String[] enabledProtocols, String[] supportedProtocols)
     {
-        Set<String> selected_protocols = new LinkedHashSet<String>();
+        Set<String> selected_protocols = new LinkedHashSet<>();
 
         // Set the starting protocols - either from the included or enabled list
-        if (_includeProtocols!=null)
+        if (!_includeProtocols.isEmpty())
         {
             // Use only the supported included protocols
             for (String protocol : _includeProtocols)
@@ -1226,13 +1053,11 @@ public class SslContextFactory extends AbstractLifeCycle
 
 
         // Remove any excluded protocols
-        if (_excludeProtocols != null)
-            selected_protocols.removeAll(_excludeProtocols);
+        selected_protocols.removeAll(_excludeProtocols);
 
         return selected_protocols.toArray(new String[selected_protocols.size()]);
     }
 
-    /* ------------------------------------------------------------ */
     /**
      * Select cipher suites to be used by the connector
      * based on configured inclusion and exclusion lists
@@ -1243,27 +1068,47 @@ public class SslContextFactory extends AbstractLifeCycle
      */
     public String[] selectCipherSuites(String[] enabledCipherSuites, String[] supportedCipherSuites)
     {
-        Set<String> selected_ciphers = new LinkedHashSet<String>();
+        Set<String> selected_ciphers = new CopyOnWriteArraySet<>();
 
         // Set the starting ciphers - either from the included or enabled list
-        if (_includeCipherSuites!=null)
-        {
-            // Use only the supported included ciphers
-            for (String cipherSuite : _includeCipherSuites)
-                if(Arrays.asList(supportedCipherSuites).contains(cipherSuite))
-                    selected_ciphers.add(cipherSuite);
-        }
-        else
+        if (_includeCipherSuites.isEmpty())
             selected_ciphers.addAll(Arrays.asList(enabledCipherSuites));
+        else
+            processIncludeCipherSuites(supportedCipherSuites, selected_ciphers);
 
+        removeExcludedCipherSuites(selected_ciphers);
 
-        // Remove any excluded ciphers
-        if (_excludeCipherSuites != null)
-            selected_ciphers.removeAll(_excludeCipherSuites);
         return selected_ciphers.toArray(new String[selected_ciphers.size()]);
     }
 
-    /* ------------------------------------------------------------ */
+    protected void processIncludeCipherSuites(String[] supportedCipherSuites, Set<String> selected_ciphers)
+    {
+        for (String cipherSuite : _includeCipherSuites)
+        {
+            Pattern p = Pattern.compile(cipherSuite);
+            for (String supportedCipherSuite : supportedCipherSuites)
+            {
+                Matcher m = p.matcher(supportedCipherSuite);
+                if (m.matches())
+                    selected_ciphers.add(supportedCipherSuite);
+            }
+        }
+    }
+
+    protected void removeExcludedCipherSuites(Set<String> selected_ciphers)
+    {
+        for (String excludeCipherSuite : _excludeCipherSuites)
+        {
+            Pattern excludeCipherPattern = Pattern.compile(excludeCipherSuite);
+            for (String selectedCipherSuite : selected_ciphers)
+            {
+                Matcher m = excludeCipherPattern.matcher(selectedCipherSuite);
+                if (m.matches())
+                    selected_ciphers.remove(selectedCipherSuite);
+            }
+        }
+    }
+
     /**
      * Check if the lifecycle has been started and throw runtime exception
      */
@@ -1273,7 +1118,6 @@ public class SslContextFactory extends AbstractLifeCycle
             throw new IllegalStateException("Cannot modify configuration when "+getState());
     }
 
-    /* ------------------------------------------------------------ */
     /**
      * @return true if CRL Distribution Points support is enabled
      */
@@ -1282,18 +1126,15 @@ public class SslContextFactory extends AbstractLifeCycle
         return _enableCRLDP;
     }
 
-    /* ------------------------------------------------------------ */
     /** Enables CRL Distribution Points Support
      * @param enableCRLDP true - turn on, false - turns off
      */
     public void setEnableCRLDP(boolean enableCRLDP)
     {
         checkNotStarted();
-
         _enableCRLDP = enableCRLDP;
     }
 
-    /* ------------------------------------------------------------ */
     /**
      * @return true if On-Line Certificate Status Protocol support is enabled
      */
@@ -1302,18 +1143,15 @@ public class SslContextFactory extends AbstractLifeCycle
         return _enableOCSP;
     }
 
-    /* ------------------------------------------------------------ */
     /** Enables On-Line Certificate Status Protocol support
      * @param enableOCSP true - turn on, false - turn off
      */
     public void setEnableOCSP(boolean enableOCSP)
     {
         checkNotStarted();
-
         _enableOCSP = enableOCSP;
     }
 
-    /* ------------------------------------------------------------ */
     /**
      * @return Location of the OCSP Responder
      */
@@ -1322,47 +1160,39 @@ public class SslContextFactory extends AbstractLifeCycle
         return _ocspResponderURL;
     }
 
-    /* ------------------------------------------------------------ */
     /** Set the location of the OCSP Responder.
      * @param ocspResponderURL location of the OCSP Responder
      */
     public void setOcspResponderURL(String ocspResponderURL)
     {
         checkNotStarted();
-
         _ocspResponderURL = ocspResponderURL;
     }
 
-    /* ------------------------------------------------------------ */
     /** Set the key store.
      * @param keyStore the key store to set
      */
     public void setKeyStore(KeyStore keyStore)
     {
         checkNotStarted();
-
         _keyStore = keyStore;
     }
 
-    /* ------------------------------------------------------------ */
     /** Set the trust store.
      * @param trustStore the trust store to set
      */
     public void setTrustStore(KeyStore trustStore)
     {
         checkNotStarted();
-
         _trustStore = trustStore;
     }
 
-    /* ------------------------------------------------------------ */
     /** Set the key store resource.
      * @param resource the key store resource to set
      */
     public void setKeyStoreResource(Resource resource)
     {
         checkNotStarted();
-
         try
         {
             _keyStoreInputStream = resource.getInputStream();
@@ -1374,14 +1204,12 @@ public class SslContextFactory extends AbstractLifeCycle
         }
     }
 
-    /* ------------------------------------------------------------ */
     /** Set the trust store resource.
      * @param resource the trust store resource to set
      */
     public void setTrustStoreResource(Resource resource)
     {
         checkNotStarted();
-
         try
         {
             _trustStoreInputStream = resource.getInputStream();
@@ -1393,7 +1221,6 @@ public class SslContextFactory extends AbstractLifeCycle
         }
     }
 
-    /* ------------------------------------------------------------ */
     /**
     * @return true if SSL Session caching is enabled
     */
@@ -1402,7 +1229,6 @@ public class SslContextFactory extends AbstractLifeCycle
         return _sessionCachingEnabled;
     }
 
-    /* ------------------------------------------------------------ */
     /** Set the flag to enable SSL Session caching.
     * @param enableSessionCaching the value of the flag
     */
@@ -1411,7 +1237,6 @@ public class SslContextFactory extends AbstractLifeCycle
         _sessionCachingEnabled = enableSessionCaching;
     }
 
-    /* ------------------------------------------------------------ */
     /** Get SSL session cache size.
      * @return SSL session cache size
      */
@@ -1420,7 +1245,6 @@ public class SslContextFactory extends AbstractLifeCycle
         return _sslSessionCacheSize;
     }
 
-    /* ------------------------------------------------------------ */
     /** SEt SSL session cache size.
      * @param sslSessionCacheSize SSL session cache size to set
      */
@@ -1429,7 +1253,6 @@ public class SslContextFactory extends AbstractLifeCycle
         _sslSessionCacheSize = sslSessionCacheSize;
     }
 
-    /* ------------------------------------------------------------ */
     /** Get SSL session timeout.
      * @return SSL session timeout
      */
@@ -1438,7 +1261,6 @@ public class SslContextFactory extends AbstractLifeCycle
         return _sslSessionTimeout;
     }
 
-    /* ------------------------------------------------------------ */
     /** Set SSL session timeout.
      * @param sslSessionTimeout SSL session timeout to set
      */
@@ -1448,7 +1270,6 @@ public class SslContextFactory extends AbstractLifeCycle
     }
 
 
-    /* ------------------------------------------------------------ */
     public SSLServerSocket newSslServerSocket(String host,int port,int backlog) throws IOException
     {
         SSLServerSocketFactory factory = _context.getServerSocketFactory();
@@ -1471,7 +1292,6 @@ public class SslContextFactory extends AbstractLifeCycle
         return socket;
     }
 
-    /* ------------------------------------------------------------ */
     public SSLSocket newSslSocket() throws IOException
     {
         SSLSocketFactory factory = _context.getSocketFactory();
@@ -1491,28 +1311,77 @@ public class SslContextFactory extends AbstractLifeCycle
         return socket;
     }
 
-    /* ------------------------------------------------------------ */
-    public SSLEngine newSslEngine(String host,int port)
+    /**
+     * Factory method for "scratch" {@link SSLEngine}s, usually only used for retrieving configuration
+     * information such as the application buffer size or the list of protocols/ciphers.
+     * <p />
+     * This method should not be used for creating {@link SSLEngine}s that are used in actual socket
+     * communication.
+     *
+     * @return a new, "scratch" {@link SSLEngine}
+     */
+    public SSLEngine newSSLEngine()
     {
-        SSLEngine sslEngine=isSessionCachingEnabled()
-            ?_context.createSSLEngine(host, port)
-            :_context.createSSLEngine();
-
+        if (!isRunning())
+            throw new IllegalStateException("!STARTED");
+        SSLEngine sslEngine=_context.createSSLEngine();
         customize(sslEngine);
         return sslEngine;
     }
 
-    /* ------------------------------------------------------------ */
-    public SSLEngine newSslEngine()
+    /**
+     * General purpose factory method for creating {@link SSLEngine}s, although creation of
+     * {@link SSLEngine}s on the server-side should prefer {@link #newSSLEngine(InetSocketAddress)}.
+     *
+     * @param host the remote host
+     * @param port the remote port
+     * @return a new {@link SSLEngine}
+     */
+    public SSLEngine newSSLEngine(String host, int port)
     {
-        SSLEngine sslEngine=_context.createSSLEngine();
+        if (!isRunning())
+            throw new IllegalStateException("!STARTED");
+        SSLEngine sslEngine=isSessionCachingEnabled()
+            ? _context.createSSLEngine(host, port)
+            : _context.createSSLEngine();
         customize(sslEngine);
         return sslEngine;
     }
 
-    /* ------------------------------------------------------------ */
+    /**
+     * Server-side only factory method for creating {@link SSLEngine}s.
+     * <p />
+     * If the given {@code address} is null, it is equivalent to {@link #newSSLEngine()}, otherwise
+     * {@link #newSSLEngine(String, int)} is called.
+     * <p />
+     * If {@link #getNeedClientAuth()} is {@code true}, then the host name is passed to
+     * {@link #newSSLEngine(String, int)}, possibly incurring in a reverse DNS lookup, which takes time
+     * and may hang the selector (since this method is usually called by the selector thread).
+     * <p />
+     * Otherwise, the host address is passed to {@link #newSSLEngine(String, int)} without DNS lookup
+     * penalties.
+     * <p />
+     * Clients that wish to create {@link SSLEngine} instances must use {@link #newSSLEngine(String, int)}.
+     *
+     * @param address the remote peer address
+     * @return a new {@link SSLEngine}
+     */
+    public SSLEngine newSSLEngine(InetSocketAddress address)
+    {
+        if (address == null)
+            return newSSLEngine();
+
+        boolean useHostName = getNeedClientAuth();
+        String hostName = useHostName ? address.getHostName() : address.getAddress().getHostAddress();
+        return newSSLEngine(hostName, address.getPort());
+    }
+
     public void customize(SSLEngine sslEngine)
     {
+        SSLParameters sslParams = sslEngine.getSSLParameters();
+        sslParams.setEndpointIdentificationAlgorithm(_endpointIdentificationAlgorithm);
+        sslEngine.setSSLParameters(sslParams);
+
         if (getWantClientAuth())
             sslEngine.setWantClientAuth(getWantClientAuth());
         if (getNeedClientAuth())
@@ -1525,7 +1394,92 @@ public class SslContextFactory extends AbstractLifeCycle
         sslEngine.setEnabledProtocols(selectProtocols(sslEngine.getEnabledProtocols(),sslEngine.getSupportedProtocols()));
     }
 
-    /* ------------------------------------------------------------ */
+    public static X509Certificate[] getCertChain(SSLSession sslSession)
+    {
+        try
+        {
+            Certificate[] javaxCerts=sslSession.getPeerCertificates();
+            if (javaxCerts==null||javaxCerts.length==0)
+                return null;
+
+            int length=javaxCerts.length;
+            X509Certificate[] javaCerts=new X509Certificate[length];
+
+            java.security.cert.CertificateFactory cf=java.security.cert.CertificateFactory.getInstance("X.509");
+            for (int i=0; i<length; i++)
+            {
+                byte bytes[]=javaxCerts[i].getEncoded();
+                ByteArrayInputStream stream=new ByteArrayInputStream(bytes);
+                javaCerts[i]=(X509Certificate)cf.generateCertificate(stream);
+            }
+
+            return javaCerts;
+        }
+        catch (SSLPeerUnverifiedException pue)
+        {
+            return null;
+        }
+        catch (Exception e)
+        {
+            LOG.warn(Log.EXCEPTION,e);
+            return null;
+        }
+    }
+
+    /**
+     * Given the name of a TLS/SSL cipher suite, return an int representing it effective stream
+     * cipher key strength. i.e. How much entropy material is in the key material being fed into the
+     * encryption routines.
+     *
+     * <p>
+     * This is based on the information on effective key lengths in RFC 2246 - The TLS Protocol
+     * Version 1.0, Appendix C. CipherSuite definitions:
+     *
+     * <pre>
+     *                         Effective
+     *     Cipher       Type    Key Bits
+     *
+     *     NULL       * Stream     0
+     *     IDEA_CBC     Block    128
+     *     RC2_CBC_40 * Block     40
+     *     RC4_40     * Stream    40
+     *     RC4_128      Stream   128
+     *     DES40_CBC  * Block     40
+     *     DES_CBC      Block     56
+     *     3DES_EDE_CBC Block    168
+     * </pre>
+     *
+     * @param cipherSuite String name of the TLS cipher suite.
+     * @return int indicating the effective key entropy bit-length.
+     */
+    public static int deduceKeyLength(String cipherSuite)
+    {
+        // Roughly ordered from most common to least common.
+        if (cipherSuite == null)
+            return 0;
+        else if (cipherSuite.contains("WITH_AES_256_"))
+            return 256;
+        else if (cipherSuite.contains("WITH_RC4_128_"))
+            return 128;
+        else if (cipherSuite.contains("WITH_AES_128_"))
+            return 128;
+        else if (cipherSuite.contains("WITH_RC4_40_"))
+            return 40;
+        else if (cipherSuite.contains("WITH_3DES_EDE_CBC_"))
+            return 168;
+        else if (cipherSuite.contains("WITH_IDEA_CBC_"))
+            return 128;
+        else if (cipherSuite.contains("WITH_RC2_CBC_40_"))
+            return 40;
+        else if (cipherSuite.contains("WITH_DES40_CBC_"))
+            return 40;
+        else if (cipherSuite.contains("WITH_DES_CBC_"))
+            return 56;
+        else
+            return 0;
+    }
+
+    @Override
     public String toString()
     {
         return String.format("%s@%x(%s,%s)",
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/ssl/package-info.java b/jetty-util/src/main/java/org/eclipse/jetty/util/ssl/package-info.java
new file mode 100644
index 0000000..049f354
--- /dev/null
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/ssl/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Util : Common SSL Utility Classes
+ */
+package org.eclipse.jetty.util.ssl;
+
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/statistic/CounterStatistic.java b/jetty-util/src/main/java/org/eclipse/jetty/util/statistic/CounterStatistic.java
index 91f606d..23ce97e 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/statistic/CounterStatistic.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/statistic/CounterStatistic.java
@@ -55,37 +55,31 @@ public class CounterStatistic
     /**
      * @param delta the amount to add to the count
      */
-    public void add(final long delta)
+    public long add(final long delta)
     {
         long value=_curr.addAndGet(delta);
         if (delta > 0)
+        {
             _total.addAndGet(delta);
-        Atomics.updateMax(_max,value);
+            Atomics.updateMax(_max,value);
+        }
+        return value;
     }
 
     /* ------------------------------------------------------------ */
     /**
-     * @param delta the amount to subtract the count by.
      */
-    public void subtract(final long delta)
+    public long increment()
     {
-        add(-delta);
+        return add(1);
     }
 
     /* ------------------------------------------------------------ */
     /**
      */
-    public void increment()
+    public long decrement()
     {
-        add(1);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     */
-    public void decrement()
-    {
-        add(-1);
+        return add(-1);
     }
 
     /* ------------------------------------------------------------ */
@@ -114,4 +108,11 @@ public class CounterStatistic
     {
         return _total.get();
     }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        return String.format("%s@%x{c=%d,m=%d,t=%d}",this.getClass().getSimpleName(),hashCode(),_curr.get(),_max.get(),_total.get());
+    }
 }
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/statistic/SampleStatistic.java b/jetty-util/src/main/java/org/eclipse/jetty/util/statistic/SampleStatistic.java
index e7b2acf..2f106f7 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/statistic/SampleStatistic.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/statistic/SampleStatistic.java
@@ -106,4 +106,11 @@ public class SampleStatistic
     {
         return Math.sqrt(getVariance());
     }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        return String.format("%s@%x{c=%d,m=%d,t=%d,v100=%d}",this.getClass().getSimpleName(),hashCode(),_count.get(),_max.get(),_total.get(),_totalVariance100.get());
+    }
 }
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/statistic/package-info.java b/jetty-util/src/main/java/org/eclipse/jetty/util/statistic/package-info.java
new file mode 100644
index 0000000..1c20ec5
--- /dev/null
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/statistic/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Util : Common Statistics Utility classes
+ */
+package org.eclipse.jetty.util.statistic;
+
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/ExecutorThreadPool.java b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/ExecutorThreadPool.java
index 9ec7d46..6f46db2 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/ExecutorThreadPool.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/ExecutorThreadPool.java
@@ -118,6 +118,14 @@ public class ExecutorThreadPool extends AbstractLifeCycle implements ThreadPool,
         this(new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue));
     }
 
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void execute(Runnable job)
+    {
+        _executor.execute(job);
+    }
+
     /* ------------------------------------------------------------ */
     public boolean dispatch(Runnable job)
     {
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/NonBlockingThread.java b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/NonBlockingThread.java
new file mode 100644
index 0000000..a8552ad
--- /dev/null
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/NonBlockingThread.java
@@ -0,0 +1,59 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.thread;
+
+/**
+ * Marker that wraps a Runnable, indicating that it is running in a thread that must not be blocked.
+ * <p />
+ * Client code can use the thread-local {@link #isNonBlockingThread()} to detect whether they are
+ * in the context of a non-blocking thread, and perform different actions if that's the case.
+ */
+public class NonBlockingThread implements Runnable
+{
+    private final static ThreadLocal<Boolean> __nonBlockingThread = new ThreadLocal<>();
+
+    /**
+     * @return whether the current thread is a thread that must not block.
+     */
+    public static boolean isNonBlockingThread()
+    {
+        return Boolean.TRUE.equals(__nonBlockingThread.get());
+    }
+
+    private final Runnable delegate;
+
+    public NonBlockingThread(Runnable delegate)
+    {
+        this.delegate = delegate;
+    }
+
+    @Override
+    public void run()
+    {
+        try
+        {
+            __nonBlockingThread.set(Boolean.TRUE);
+            delegate.run();
+        }
+        finally
+        {
+            __nonBlockingThread.set(Boolean.FALSE);
+        }
+    }
+}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/QueuedThreadPool.java b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/QueuedThreadPool.java
index a4562d2..5a71f7b 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/QueuedThreadPool.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/QueuedThreadPool.java
@@ -23,138 +23,160 @@ import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
-import java.util.concurrent.ArrayBlockingQueue;
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.concurrent.Executor;
 import java.util.concurrent.RejectedExecutionException;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicLong;
 
 import org.eclipse.jetty.util.BlockingArrayQueue;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.annotation.ManagedOperation;
+import org.eclipse.jetty.util.annotation.Name;
 import org.eclipse.jetty.util.component.AbstractLifeCycle;
-import org.eclipse.jetty.util.component.AggregateLifeCycle;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
 import org.eclipse.jetty.util.component.Dumpable;
 import org.eclipse.jetty.util.component.LifeCycle;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
 import org.eclipse.jetty.util.thread.ThreadPool.SizedThreadPool;
 
-public class QueuedThreadPool extends AbstractLifeCycle implements SizedThreadPool, Executor, Dumpable
+ at ManagedObject("A thread pool with no max bound by default")
+public class QueuedThreadPool extends AbstractLifeCycle implements SizedThreadPool, Dumpable
 {
     private static final Logger LOG = Log.getLogger(QueuedThreadPool.class);
 
     private final AtomicInteger _threadsStarted = new AtomicInteger();
     private final AtomicInteger _threadsIdle = new AtomicInteger();
     private final AtomicLong _lastShrink = new AtomicLong();
-    private final ConcurrentLinkedQueue<Thread> _threads=new ConcurrentLinkedQueue<Thread>();
+    private final ConcurrentLinkedQueue<Thread> _threads = new ConcurrentLinkedQueue<>();
     private final Object _joinLock = new Object();
-    private BlockingQueue<Runnable> _jobs;
-    private String _name;
-    private int _maxIdleTimeMs=60000;
-    private int _maxThreads=254;
-    private int _minThreads=8;
-    private int _maxQueued=-1;
-    private int _priority=Thread.NORM_PRIORITY;
-    private boolean _daemon=false;
-    private int _maxStopTime=100;
-    private boolean _detailedDump=false;
-
-    /* ------------------------------------------------------------------- */
-    /** Construct
-     */
+    private final BlockingQueue<Runnable> _jobs;
+    private String _name = "qtp" + hashCode();
+    private int _idleTimeout;
+    private int _maxThreads;
+    private int _minThreads;
+    private int _priority = Thread.NORM_PRIORITY;
+    private boolean _daemon = false;
+    private boolean _detailedDump = false;
+
     public QueuedThreadPool()
     {
-        _name="qtp"+super.hashCode();
+        this(200);
     }
 
-    /* ------------------------------------------------------------------- */
-    /** Construct
-     */
-    public QueuedThreadPool(int maxThreads)
+    public QueuedThreadPool(@Name("maxThreads") int maxThreads)
     {
-        this();
-        setMaxThreads(maxThreads);
+        this(maxThreads, 8);
     }
 
-    /* ------------------------------------------------------------------- */
-    /** Construct
-     */
-    public QueuedThreadPool(BlockingQueue<Runnable> jobQ)
+    public QueuedThreadPool(@Name("maxThreads") int maxThreads,  @Name("minThreads") int minThreads)
     {
-        this();
-        _jobs=jobQ;
-        _jobs.clear();
+        this(maxThreads, minThreads, 60000);
     }
 
+    public QueuedThreadPool(@Name("maxThreads") int maxThreads,  @Name("minThreads") int minThreads, @Name("idleTimeout")int idleTimeout)
+    {
+        this(maxThreads, minThreads, idleTimeout, null);
+    }
+
+    public QueuedThreadPool(@Name("maxThreads") int maxThreads, @Name("minThreads") int minThreads, @Name("idleTimeout") int idleTimeout, @Name("queue") BlockingQueue<Runnable> queue)
+    {
+        setMinThreads(minThreads);
+        setMaxThreads(maxThreads);
+        setIdleTimeout(idleTimeout);
+        setStopTimeout(5000);
+
+        if (queue==null)
+        {
+            int capacity=Math.max(_minThreads, 8);
+            queue=new BlockingArrayQueue<>(capacity, capacity);
+        }
+        _jobs=queue;
+    }
 
-    /* ------------------------------------------------------------ */
     @Override
     protected void doStart() throws Exception
     {
         super.doStart();
         _threadsStarted.set(0);
 
-        if (_jobs==null)
-        {
-            _jobs=_maxQueued>0 ?new ArrayBlockingQueue<Runnable>(_maxQueued)
-                :new BlockingArrayQueue<Runnable>(_minThreads,_minThreads);
-        }
-
-        int threads=_threadsStarted.get();
-        while (isRunning() && threads<_minThreads)
-        {
-            startThread(threads);
-            threads=_threadsStarted.get();
-        }
+        startThreads(_minThreads);
     }
 
-    /* ------------------------------------------------------------ */
     @Override
     protected void doStop() throws Exception
     {
         super.doStop();
-        long start=System.currentTimeMillis();
 
-        // let jobs complete naturally for a while
-        while (_threadsStarted.get()>0 && (System.currentTimeMillis()-start) < (_maxStopTime/2))
-            Thread.sleep(1);
+        long timeout = getStopTimeout();
+        BlockingQueue<Runnable> jobs = getQueue();
 
-        // kill queued jobs and flush out idle jobs
-        _jobs.clear();
-        Runnable noop = new Runnable(){public void run(){}};
-        for  (int i=_threadsIdle.get();i-->0;)
-            _jobs.offer(noop);
-        Thread.yield();
+        // If no stop timeout, clear job queue
+        if (timeout <= 0)
+            jobs.clear();
+
+        // Fill job Q with noop jobs to wakeup idle
+        Runnable noop = new Runnable()
+        {
+            @Override
+            public void run()
+            {
+            }
+        };
+        for (int i = _threadsStarted.get(); i-- > 0; )
+            jobs.offer(noop);
+
+        // try to jobs complete naturally for half our stop time
+        long stopby = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(timeout) / 2;
+        for (Thread thread : _threads)
+        {
+            long canwait = TimeUnit.NANOSECONDS.toMillis(stopby - System.nanoTime());
+            if (canwait > 0)
+                thread.join(canwait);
+        }
+
+        // If we still have threads running, get a bit more aggressive
 
         // interrupt remaining threads
-        if (_threadsStarted.get()>0)
+        if (_threadsStarted.get() > 0)
             for (Thread thread : _threads)
                 thread.interrupt();
 
-        // wait for remaining threads to die
-        while (_threadsStarted.get()>0 && (System.currentTimeMillis()-start) < _maxStopTime)
+        // wait again for the other half of our stop time
+        stopby = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(timeout) / 2;
+        for (Thread thread : _threads)
         {
-            Thread.sleep(1);
+            long canwait = TimeUnit.NANOSECONDS.toMillis(stopby - System.nanoTime());
+            if (canwait > 0)
+                thread.join(canwait);
         }
+
         Thread.yield();
-        int size=_threads.size();
-        if (size>0)
+        int size = _threads.size();
+        if (size > 0)
         {
-            LOG.warn(size+" threads could not be stopped");
-
-            if (size==1 || LOG.isDebugEnabled())
+            Thread.yield();
+            
+            if (LOG.isDebugEnabled())
             {
                 for (Thread unstopped : _threads)
                 {
-                    LOG.info("Couldn't stop "+unstopped);
+                    StringBuilder dmp = new StringBuilder();
                     for (StackTraceElement element : unstopped.getStackTrace())
                     {
-                        LOG.info(" at "+element);
+                        dmp.append(System.lineSeparator()).append("\tat ").append(element);
                     }
+                    LOG.warn("Couldn't stop {}{}", unstopped, dmp.toString());
                 }
             }
+            else
+            {
+                for (Thread unstopped : _threads)
+                    LOG.warn("{} Couldn't stop {}",this,unstopped);
+            }
         }
 
         synchronized (_joinLock)
@@ -163,226 +185,194 @@ public class QueuedThreadPool extends AbstractLifeCycle implements SizedThreadPo
         }
     }
 
-    /* ------------------------------------------------------------ */
     /**
      * Delegated to the named or anonymous Pool.
      */
     public void setDaemon(boolean daemon)
     {
-        _daemon=daemon;
+        _daemon = daemon;
     }
 
-    /* ------------------------------------------------------------ */
-    /** Set the maximum thread idle time.
+    /**
+     * Set the maximum thread idle time.
      * Threads that are idle for longer than this period may be
      * stopped.
      * Delegated to the named or anonymous Pool.
-     * @see #getMaxIdleTimeMs
-     * @param maxIdleTimeMs Max idle time in ms.
+     *
+     * @param idleTimeout Max idle time in ms.
+     * @see #getIdleTimeout
      */
-    public void setMaxIdleTimeMs(int maxIdleTimeMs)
+    public void setIdleTimeout(int idleTimeout)
     {
-        _maxIdleTimeMs=maxIdleTimeMs;
+        _idleTimeout = idleTimeout;
     }
 
-    /* ------------------------------------------------------------ */
     /**
-     * @param stopTimeMs maximum total time that stop() will wait for threads to die.
-     */
-    public void setMaxStopTimeMs(int stopTimeMs)
-    {
-        _maxStopTime = stopTimeMs;
-    }
-
-    /* ------------------------------------------------------------ */
-    /** Set the maximum number of threads.
+     * Set the maximum number of threads.
      * Delegated to the named or anonymous Pool.
-     * @see #getMaxThreads
+     *
      * @param maxThreads maximum number of threads.
+     * @see #getMaxThreads
      */
+    @Override
     public void setMaxThreads(int maxThreads)
     {
-        _maxThreads=maxThreads;
-        if (_minThreads>_maxThreads)
-            _minThreads=_maxThreads;
+        _maxThreads = maxThreads;
+        if (_minThreads > _maxThreads)
+            _minThreads = _maxThreads;
     }
 
-    /* ------------------------------------------------------------ */
-    /** Set the minimum number of threads.
+    /**
+     * Set the minimum number of threads.
      * Delegated to the named or anonymous Pool.
-     * @see #getMinThreads
+     *
      * @param minThreads minimum number of threads
+     * @see #getMinThreads
      */
+    @Override
     public void setMinThreads(int minThreads)
     {
-        _minThreads=minThreads;
+        _minThreads = minThreads;
 
-        if (_minThreads>_maxThreads)
-            _maxThreads=_minThreads;
+        if (_minThreads > _maxThreads)
+            _maxThreads = _minThreads;
 
-        int threads=_threadsStarted.get();
-        while (isStarted() && threads<_minThreads)
-        {
-            startThread(threads);
-            threads=_threadsStarted.get();
-        }
+        int threads = _threadsStarted.get();
+        if (isStarted() && threads < _minThreads)
+            startThreads(_minThreads - threads);
     }
 
-    /* ------------------------------------------------------------ */
     /**
-     * @param name Name of the BoundedThreadPool to use when naming Threads.
+     * @param name Name of this thread pool to use when naming threads.
      */
     public void setName(String name)
     {
         if (isRunning())
             throw new IllegalStateException("started");
-        _name= name;
-    }
-
-    /* ------------------------------------------------------------ */
-    /** Set the priority of the pool threads.
-     *  @param priority the new thread priority.
-     */
-    public void setThreadsPriority(int priority)
-    {
-        _priority=priority;
+        _name = name;
     }
 
-    /* ------------------------------------------------------------ */
     /**
-     * @return maximum queue size
+     * Set the priority of the pool threads.
+     *
+     * @param priority the new thread priority.
      */
-    public int getMaxQueued()
+    public void setThreadsPriority(int priority)
     {
-        return _maxQueued;
+        _priority = priority;
     }
 
-    /* ------------------------------------------------------------ */
     /**
-     * @param max job queue size
-     */
-    public void setMaxQueued(int max)
-    {
-        if (isRunning())
-            throw new IllegalStateException("started");
-        _maxQueued=max;
-    }
-
-    /* ------------------------------------------------------------ */
-    /** Get the maximum thread idle time.
+     * Get the maximum thread idle time.
      * Delegated to the named or anonymous Pool.
-     * @see #setMaxIdleTimeMs
+     *
      * @return Max idle time in ms.
+     * @see #setIdleTimeout
      */
-    public int getMaxIdleTimeMs()
+    @ManagedAttribute("maximum time a thread may be idle in ms")
+    public int getIdleTimeout()
     {
-        return _maxIdleTimeMs;
+        return _idleTimeout;
     }
 
-    /* ------------------------------------------------------------ */
     /**
-     * @return maximum total time that stop() will wait for threads to die.
-     */
-    public int getMaxStopTimeMs()
-    {
-        return _maxStopTime;
-    }
-
-    /* ------------------------------------------------------------ */
-    /** Set the maximum number of threads.
+     * Set the maximum number of threads.
      * Delegated to the named or anonymous Pool.
-     * @see #setMaxThreads
+     *
      * @return maximum number of threads.
+     * @see #setMaxThreads
      */
+    @Override
+    @ManagedAttribute("maximum number of threads in the pool")
     public int getMaxThreads()
     {
         return _maxThreads;
     }
 
-    /* ------------------------------------------------------------ */
-    /** Get the minimum number of threads.
+    /**
+     * Get the minimum number of threads.
      * Delegated to the named or anonymous Pool.
-     * @see #setMinThreads
+     *
      * @return minimum number of threads.
+     * @see #setMinThreads
      */
+    @Override
+    @ManagedAttribute("minimum number of threads in the pool")
     public int getMinThreads()
     {
         return _minThreads;
     }
 
-    /* ------------------------------------------------------------ */
     /**
-     * @return The name of the BoundedThreadPool.
+     * @return The name of the this thread pool
      */
+    @ManagedAttribute("name of the thread pool")
     public String getName()
     {
         return _name;
     }
 
-    /* ------------------------------------------------------------ */
-    /** Get the priority of the pool threads.
-     *  @return the priority of the pool threads.
+    /**
+     * Get the priority of the pool threads.
+     *
+     * @return the priority of the pool threads.
      */
+    @ManagedAttribute("priority of threads in the pool")
     public int getThreadsPriority()
     {
         return _priority;
     }
+    
+    /**
+     * Get the size of the job queue.
+     * 
+     * @return Number of jobs queued waiting for a thread
+     */
+    @ManagedAttribute("Size of the job queue")
+    public int getQueueSize()
+    {
+        return _jobs.size();
+    }
 
-    /* ------------------------------------------------------------ */
     /**
      * Delegated to the named or anonymous Pool.
      */
+    @ManagedAttribute("thead pool using a daemon thread")
     public boolean isDaemon()
     {
         return _daemon;
     }
 
-    /* ------------------------------------------------------------ */
     public boolean isDetailedDump()
     {
         return _detailedDump;
     }
 
-    /* ------------------------------------------------------------ */
     public void setDetailedDump(boolean detailedDump)
     {
         _detailedDump = detailedDump;
     }
-
-    /* ------------------------------------------------------------ */
-    public boolean dispatch(Runnable job)
+    
+    @Override
+    public void execute(Runnable job)
     {
-        if (isRunning())
+        if (!isRunning() || !_jobs.offer(job))
         {
-            final int jobQ = _jobs.size();
-            final int idle = getIdleThreads();
-            if(_jobs.offer(job))
-            {
-                // If we had no idle threads or the jobQ is greater than the idle threads
-                if (idle==0 || jobQ>idle)
-                {
-                    int threads=_threadsStarted.get();
-                    if (threads<_maxThreads)
-                        startThread(threads);
-                }
-                return true;
-            }
+            LOG.warn("{} rejected {}", this, job);
+            throw new RejectedExecutionException(job.toString());
+        }
+        else
+        {
+            // Make sure there is at least one thread executing the job.
+            if (getThreads() == 0)
+                startThreads(1);
         }
-        LOG.debug("Dispatched {} to stopped {}",job,this);
-        return false;
-    }
-
-    /* ------------------------------------------------------------ */
-    public void execute(Runnable job)
-    {
-        if (!dispatch(job))
-            throw new RejectedExecutionException();
     }
 
-    /* ------------------------------------------------------------ */
     /**
      * Blocks until the thread pool is {@link LifeCycle#stop stopped}.
      */
+    @Override
     public void join() throws InterruptedException
     {
         synchronized (_joinLock)
@@ -395,106 +385,124 @@ public class QueuedThreadPool extends AbstractLifeCycle implements SizedThreadPo
             Thread.sleep(1);
     }
 
-    /* ------------------------------------------------------------ */
     /**
      * @return The total number of threads currently in the pool
      */
+    @Override
+    @ManagedAttribute("total number of threads currently in the pool")
     public int getThreads()
     {
         return _threadsStarted.get();
     }
 
-    /* ------------------------------------------------------------ */
     /**
      * @return The number of idle threads in the pool
      */
+    @Override
+    @ManagedAttribute("total number of idle threads in the pool")
     public int getIdleThreads()
     {
         return _threadsIdle.get();
     }
 
-    /* ------------------------------------------------------------ */
+    /**
+     * @return The number of busy threads in the pool
+     */
+    @ManagedAttribute("total number of busy threads in the pool")
+    public int getBusyThreads()
+    {
+        return getThreads() - getIdleThreads();
+    }
+    
     /**
      * @return True if the pool is at maxThreads and there are not more idle threads than queued jobs
      */
+    @Override
+    @ManagedAttribute("True if the pools is at maxThreads and there are not idle threads than queued jobs")
     public boolean isLowOnThreads()
     {
-        return _threadsStarted.get()==_maxThreads && _jobs.size()>=_threadsIdle.get();
+        return _threadsStarted.get() == _maxThreads && _jobs.size() >= _threadsIdle.get();
     }
 
-    /* ------------------------------------------------------------ */
-    private boolean startThread(int threads)
+    private boolean startThreads(int threadsToStart)
     {
-        final int next=threads+1;
-        if (!_threadsStarted.compareAndSet(threads,next))
-            return false;
-
-        boolean started=false;
-        try
-        {
-            Thread thread=newThread(_runnable);
-            thread.setDaemon(_daemon);
-            thread.setPriority(_priority);
-            thread.setName(_name+"-"+thread.getId());
-            _threads.add(thread);
-
-            thread.start();
-            started=true;
-        }
-        finally
+        while (threadsToStart > 0 && isRunning())
         {
-            if (!started)
-                _threadsStarted.decrementAndGet();
+            int threads = _threadsStarted.get();
+            if (threads >= _maxThreads)
+                return false;
+
+            if (!_threadsStarted.compareAndSet(threads, threads + 1))
+                continue;
+
+            boolean started = false;
+            try
+            {
+                Thread thread = newThread(_runnable);
+                thread.setDaemon(isDaemon());
+                thread.setPriority(getThreadsPriority());
+                thread.setName(_name + "-" + thread.getId());
+                _threads.add(thread);
+
+                thread.start();
+                started = true;
+                --threadsToStart;
+            }
+            finally
+            {
+                if (!started)
+                    _threadsStarted.decrementAndGet();
+            }
         }
-        return started;
+        return true;
     }
 
-    /* ------------------------------------------------------------ */
     protected Thread newThread(Runnable runnable)
     {
         return new Thread(runnable);
     }
 
-
-    /* ------------------------------------------------------------ */
+    @Override
+    @ManagedOperation("dump thread state")
     public String dump()
     {
-        return AggregateLifeCycle.dump(this);
+        return ContainerLifeCycle.dump(this);
     }
 
-    /* ------------------------------------------------------------ */
+    @Override
     public void dump(Appendable out, String indent) throws IOException
     {
-        List<Object> dump = new ArrayList<Object>(getMaxThreads());
-        for (final Thread thread: _threads)
+        List<Object> dump = new ArrayList<>(getMaxThreads());
+        for (final Thread thread : _threads)
         {
-            final StackTraceElement[] trace=thread.getStackTrace();
-            boolean inIdleJobPoll=false;
-            // trace can be null on early java 6 jvms
-            if (trace != null)
+            final StackTraceElement[] trace = thread.getStackTrace();
+            boolean inIdleJobPoll = false;
+            for (StackTraceElement t : trace)
             {
-                for (StackTraceElement t : trace)
+                if ("idleJobPoll".equals(t.getMethodName()))
                 {
-                    if ("idleJobPoll".equals(t.getMethodName()))
-                    {
-                        inIdleJobPoll = true;
-                        break;
-                    }
+                    inIdleJobPoll = true;
+                    break;
                 }
             }
-            final boolean idle=inIdleJobPoll;
+            final boolean idle = inIdleJobPoll;
 
-            if (_detailedDump)
+            if (isDetailedDump())
             {
                 dump.add(new Dumpable()
                 {
+                    @Override
                     public void dump(Appendable out, String indent) throws IOException
                     {
-                        out.append(String.valueOf(thread.getId())).append(' ').append(thread.getName()).append(' ').append(thread.getState().toString()).append(idle?" IDLE":"").append('\n');
+                        out.append(String.valueOf(thread.getId())).append(' ').append(thread.getName()).append(' ').append(thread.getState().toString()).append(idle ? " IDLE" : "");
+                        if (thread.getPriority()!=Thread.NORM_PRIORITY)
+                            out.append(" prio=").append(String.valueOf(thread.getPriority()));
+                        out.append(System.lineSeparator());
                         if (!idle)
-                            AggregateLifeCycle.dump(out,indent,Arrays.asList(trace));
+                            ContainerLifeCycle.dump(out, indent, Arrays.asList(trace));
                     }
 
+                    @Override
                     public String dump()
                     {
                         return null;
@@ -503,45 +511,54 @@ public class QueuedThreadPool extends AbstractLifeCycle implements SizedThreadPo
             }
             else
             {
-                dump.add(thread.getId()+" "+thread.getName()+" "+thread.getState()+" @ "+(trace.length>0?trace[0]:"???")+(idle?" IDLE":""));
+                int p=thread.getPriority();
+                dump.add(thread.getId() + " " + thread.getName() + " " + thread.getState() + " @ " + (trace.length > 0 ? trace[0] : "???") + (idle ? " IDLE" : "")+ (p==Thread.NORM_PRIORITY?"":(" prio="+p)));
             }
         }
 
-        AggregateLifeCycle.dumpObject(out,this);
-        AggregateLifeCycle.dump(out,indent,dump);
-
+        ContainerLifeCycle.dumpObject(out, this);
+        ContainerLifeCycle.dump(out, indent, dump);
     }
 
-
-    /* ------------------------------------------------------------ */
     @Override
     public String toString()
     {
-        return _name+"{"+getMinThreads()+"<="+getIdleThreads()+"<="+getThreads()+"/"+getMaxThreads()+","+(_jobs==null?-1:_jobs.size())+"}";
+        return String.format("%s{%s,%d<=%d<=%d,i=%d,q=%d}", _name, getState(), getMinThreads(), getThreads(), getMaxThreads(), getIdleThreads(), (_jobs == null ? -1 : _jobs.size()));
     }
 
-    /* ------------------------------------------------------------ */
     private Runnable idleJobPoll() throws InterruptedException
     {
-        return _jobs.poll(_maxIdleTimeMs,TimeUnit.MILLISECONDS);
+        return _jobs.poll(_idleTimeout, TimeUnit.MILLISECONDS);
     }
 
-    /* ------------------------------------------------------------ */
     private Runnable _runnable = new Runnable()
     {
+        @Override
         public void run()
         {
-            boolean shrink=false;
+            boolean shrink = false;
+            boolean ignore = false;
             try
             {
-                Runnable job=_jobs.poll();
-                while (isRunning())
+                Runnable job = _jobs.poll();
+
+                if (job != null && _threadsIdle.get() == 0)
+                {
+                    startThreads(1);
+                }
+
+                loop: while (isRunning())
                 {
                     // Job loop
-                    while (job!=null && isRunning())
+                    while (job != null && isRunning())
                     {
                         runJob(job);
-                        job=_jobs.poll();
+                        if (Thread.interrupted())
+                        {
+                            ignore=true;
+                            break loop;
+                        }
+                        job = _jobs.poll();
                     }
 
                     // Idle loop
@@ -549,54 +566,64 @@ public class QueuedThreadPool extends AbstractLifeCycle implements SizedThreadPo
                     {
                         _threadsIdle.incrementAndGet();
 
-                        while (isRunning() && job==null)
+                        while (isRunning() && job == null)
                         {
-                            if (_maxIdleTimeMs<=0)
-                                job=_jobs.take();
+                            if (_idleTimeout <= 0)
+                                job = _jobs.take();
                             else
                             {
                                 // maybe we should shrink?
-                                final int size=_threadsStarted.get();
-                                if (size>_minThreads)
+                                final int size = _threadsStarted.get();
+                                if (size > _minThreads)
                                 {
-                                    long last=_lastShrink.get();
-                                    long now=System.currentTimeMillis();
-                                    if (last==0 || (now-last)>_maxIdleTimeMs)
+                                    long last = _lastShrink.get();
+                                    long now = System.nanoTime();
+                                    if (last == 0 || (now - last) > TimeUnit.MILLISECONDS.toNanos(_idleTimeout))
                                     {
-                                        shrink=_lastShrink.compareAndSet(last,now) &&
-                                        _threadsStarted.compareAndSet(size,size-1);
-                                        if (shrink)
-                                            return;
+                                        if (_lastShrink.compareAndSet(last, now) && _threadsStarted.compareAndSet(size, size - 1))
+                                        {
+                                            shrink=true;
+                                            break loop;
+                                        }
                                     }
                                 }
-                                job=idleJobPoll();
+                                job = idleJobPoll();
                             }
                         }
                     }
                     finally
                     {
-                        _threadsIdle.decrementAndGet();
+                        if (_threadsIdle.decrementAndGet() == 0)
+                        {
+                            startThreads(1);
+                        }
                     }
                 }
             }
-            catch(InterruptedException e)
+            catch (InterruptedException e)
             {
+                ignore=true;
                 LOG.ignore(e);
             }
-            catch(Exception e)
+            catch (Throwable e)
             {
                 LOG.warn(e);
             }
             finally
             {
-                if (!shrink)
-                    _threadsStarted.decrementAndGet();
+                if (!shrink && isRunning())
+                {
+                    if (!ignore)
+                        LOG.warn("Unexpected thread death: {} in {}",this,QueuedThreadPool.this);
+                    // This is an unexpected thread death!
+                    if (_threadsStarted.decrementAndGet()<getMaxThreads())
+                        startThreads(1);
+                }
                 _threads.remove(Thread.currentThread());
             }
         }
     };
 
-    /* ------------------------------------------------------------ */
     /**
      * <p>Runs the given job in the {@link Thread#currentThread() current thread}.</p>
      * <p>Subclasses may override to perform pre/post actions before/after the job is run.</p>
@@ -608,7 +635,6 @@ public class QueuedThreadPool extends AbstractLifeCycle implements SizedThreadPo
         job.run();
     }
 
-    /* ------------------------------------------------------------ */
     /**
      * @return the job queue
      */
@@ -617,36 +643,24 @@ public class QueuedThreadPool extends AbstractLifeCycle implements SizedThreadPo
         return _jobs;
     }
 
-    /* ------------------------------------------------------------ */
     /**
-     * @param id The thread ID to stop.
-     * @return true if the thread was found and stopped.
-     * @deprecated Use {@link #interruptThread(long)} in preference
+     * @param queue the job queue
      */
-    @Deprecated
-    public boolean stopThread(long id)
+    public void setQueue(BlockingQueue<Runnable> queue)
     {
-        for (Thread thread: _threads)
-        {
-            if (thread.getId()==id)
-            {
-                thread.stop();
-                return true;
-            }
-        }
-        return false;
+        throw new UnsupportedOperationException("Use constructor injection");
     }
 
-    /* ------------------------------------------------------------ */
     /**
      * @param id The thread ID to interrupt.
      * @return true if the thread was found and interrupted.
      */
-    public boolean interruptThread(long id)
+    @ManagedOperation("interrupt a pool thread")
+    public boolean interruptThread(@Name("id") long id)
     {
-        for (Thread thread: _threads)
+        for (Thread thread : _threads)
         {
-            if (thread.getId()==id)
+            if (thread.getId() == id)
             {
                 thread.interrupt();
                 return true;
@@ -655,21 +669,22 @@ public class QueuedThreadPool extends AbstractLifeCycle implements SizedThreadPo
         return false;
     }
 
-    /* ------------------------------------------------------------ */
     /**
      * @param id The thread ID to interrupt.
      * @return true if the thread was found and interrupted.
      */
-    public String dumpThread(long id)
+    @ManagedOperation("dump a pool thread stack")
+    public String dumpThread(@Name("id") long id)
     {
-        for (Thread thread: _threads)
+        for (Thread thread : _threads)
         {
-            if (thread.getId()==id)
+            if (thread.getId() == id)
             {
                 StringBuilder buf = new StringBuilder();
-                buf.append(thread.getId()).append(" ").append(thread.getName()).append(" ").append(thread.getState()).append(":\n");
+                buf.append(thread.getId()).append(" ").append(thread.getName()).append(" ");
+                buf.append(thread.getState()).append(":").append(System.lineSeparator());
                 for (StackTraceElement element : thread.getStackTrace())
-                    buf.append("  at ").append(element.toString()).append('\n');
+                    buf.append("  at ").append(element.toString()).append(System.lineSeparator());
                 return buf.toString();
             }
         }
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/ScheduledExecutorScheduler.java b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/ScheduledExecutorScheduler.java
new file mode 100644
index 0000000..55496f4
--- /dev/null
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/ScheduledExecutorScheduler.java
@@ -0,0 +1,141 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.thread;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.component.Dumpable;
+
+/**
+ * Implementation of {@link Scheduler} based on JDK's {@link ScheduledThreadPoolExecutor}.
+ * <p />
+ * While use of {@link ScheduledThreadPoolExecutor} creates futures that will not be used,
+ * it has the advantage of allowing to set a property to remove cancelled tasks from its
+ * queue even if the task did not fire, which provides a huge benefit in the performance
+ * of garbage collection in young generation.
+ */
+public class ScheduledExecutorScheduler extends AbstractLifeCycle implements Scheduler, Dumpable
+{
+    private final String name;
+    private final boolean daemon;
+    private final ClassLoader classloader;
+    private volatile ScheduledThreadPoolExecutor scheduler;
+    private volatile Thread thread;
+
+    public ScheduledExecutorScheduler()
+    {
+        this(null, false);
+    }  
+
+    public ScheduledExecutorScheduler(String name, boolean daemon)
+    {
+        this (name,daemon, Thread.currentThread().getContextClassLoader());
+    }
+    
+    public ScheduledExecutorScheduler(String name, boolean daemon, ClassLoader threadFactoryClassLoader)
+    {
+        this.name = name == null ? "Scheduler-" + hashCode() : name;
+        this.daemon = daemon;
+        this.classloader = threadFactoryClassLoader;
+    }
+
+    @Override
+    protected void doStart() throws Exception
+    {
+        scheduler = new ScheduledThreadPoolExecutor(1, new ThreadFactory()
+        {
+            @Override
+            public Thread newThread(Runnable r)
+            {
+                Thread thread = ScheduledExecutorScheduler.this.thread = new Thread(r, name);
+                thread.setDaemon(daemon);
+                thread.setContextClassLoader(classloader);
+                return thread;
+            }
+        });
+        scheduler.setRemoveOnCancelPolicy(true);
+        super.doStart();
+    }
+
+    @Override
+    protected void doStop() throws Exception
+    {
+        scheduler.shutdownNow();
+        super.doStop();
+        scheduler = null;
+    }
+
+    @Override
+    public Task schedule(Runnable task, long delay, TimeUnit unit)
+    {
+        ScheduledThreadPoolExecutor s = scheduler;
+        if (s==null)
+            return new Task(){
+                @Override
+                public boolean cancel()
+                {
+                    return false;
+                }};
+
+        ScheduledFuture<?> result = s.schedule(task, delay, unit);
+        return new ScheduledFutureTask(result);
+    }
+
+    @Override
+    public String dump()
+    {
+        return ContainerLifeCycle.dump(this);
+    }
+
+    @Override
+    public void dump(Appendable out, String indent) throws IOException
+    {
+        ContainerLifeCycle.dumpObject(out, this);
+        Thread thread = this.thread;
+        if (thread != null)
+        {
+            List<StackTraceElement> frames = Arrays.asList(thread.getStackTrace());
+            ContainerLifeCycle.dump(out, indent, frames);
+        }
+    }
+
+    private class ScheduledFutureTask implements Task
+    {
+        private final ScheduledFuture<?> scheduledFuture;
+
+        public ScheduledFutureTask(ScheduledFuture<?> scheduledFuture)
+        {
+            this.scheduledFuture = scheduledFuture;
+        }
+
+        @Override
+        public boolean cancel()
+        {
+            return scheduledFuture.cancel(false);
+        }
+    }
+}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/Scheduler.java b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/Scheduler.java
new file mode 100644
index 0000000..006ae8a
--- /dev/null
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/Scheduler.java
@@ -0,0 +1,33 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.thread;
+
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.util.component.LifeCycle;
+
+public interface Scheduler extends LifeCycle
+{
+    interface Task
+    {
+        boolean cancel();
+    }
+
+    Task schedule(Runnable task, long delay, TimeUnit units);
+}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/ShutdownThread.java b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/ShutdownThread.java
index e55d861..fd22212 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/ShutdownThread.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/ShutdownThread.java
@@ -120,6 +120,12 @@ public class ShutdownThread extends Thread
     }
 
     /* ------------------------------------------------------------ */
+    public static synchronized boolean isRegistered(LifeCycle lifeCycle)
+    {
+        return _thread._lifeCycles.contains(lifeCycle);
+    }
+
+    /* ------------------------------------------------------------ */
     @Override
     public void run()
     {
@@ -132,7 +138,7 @@ public class ShutdownThread extends Thread
                     lifeCycle.stop();
                     LOG.debug("Stopped {}",lifeCycle);
                 }
-                
+
                 if (lifeCycle instanceof Destroyable)
                 {
                     ((Destroyable)lifeCycle).destroy();
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/Sweeper.java b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/Sweeper.java
new file mode 100644
index 0000000..1767eb0
--- /dev/null
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/Sweeper.java
@@ -0,0 +1,194 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.thread;
+
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * <p>A utility class to perform periodic sweeping of resources.</p>
+ * <p>{@link Sweepable} resources may be added to or removed from a
+ * {@link Sweeper} and the resource implementation decides whether
+ * it should be swept or not.</p>
+ * <p>If a {@link Sweepable} resources is itself a container of
+ * other sweepable resources, it will forward the sweep operation
+ * to children resources, and so on recursively.</p>
+ * <p>Typical usage is to add {@link Sweeper} as a bean to an existing
+ * container:</p>
+ * <pre>
+ * Server server = new Server();
+ * server.addBean(new Sweeper(), true);
+ * server.start();
+ * </pre>
+ * Code that knows it has sweepable resources can then lookup the
+ * {@link Sweeper} and offer the sweepable resources to it:
+ * <pre>
+ * class MyComponent implements Sweeper.Sweepable
+ * {
+ *     private final long creation;
+ *     private volatile destroyed;
+ *
+ *     MyComponent(Server server)
+ *     {
+ *         this.creation = System.nanoTime();
+ *         Sweeper sweeper = server.getBean(Sweeper.class);
+ *         sweeper.offer(this);
+ *     }
+ *
+ *     void destroy()
+ *     {
+ *         destroyed = true;
+ *     }
+ *
+ *     @Override
+ *     public boolean sweep()
+ *     {
+ *         return destroyed;
+ *     }
+ * }
+ * </pre>
+ */
+public class Sweeper extends AbstractLifeCycle implements Runnable
+{
+    private static final Logger LOG = Log.getLogger(Sweeper.class);
+
+    private final AtomicReference<List<Sweepable>> items = new AtomicReference<>();
+    private final AtomicReference<Scheduler.Task> task = new AtomicReference<>();
+    private final Scheduler scheduler;
+    private final long period;
+
+    public Sweeper(Scheduler scheduler, long period)
+    {
+        this.scheduler = scheduler;
+        this.period = period;
+    }
+
+    @Override
+    protected void doStart() throws Exception
+    {
+        super.doStart();
+        items.set(new CopyOnWriteArrayList<Sweepable>());
+        activate();
+    }
+
+    @Override
+    protected void doStop() throws Exception
+    {
+        deactivate();
+        items.set(null);
+        super.doStop();
+    }
+
+    public int getSize()
+    {
+        List<Sweepable> refs = items.get();
+        return refs == null ? 0 : refs.size();
+    }
+
+    public boolean offer(Sweepable sweepable)
+    {
+        List<Sweepable> refs = items.get();
+        if (refs == null)
+            return false;
+        refs.add(sweepable);
+        if (LOG.isDebugEnabled())
+            LOG.debug("Resource offered {}", sweepable);
+        return true;
+    }
+
+    public boolean remove(Sweepable sweepable)
+    {
+        List<Sweepable> refs = items.get();
+        return refs != null && refs.remove(sweepable);
+    }
+
+    @Override
+    public void run()
+    {
+        List<Sweepable> refs = items.get();
+        if (refs == null)
+            return;
+        for (Sweepable sweepable : refs)
+        {
+            try
+            {
+                if (sweepable.sweep())
+                {
+                    refs.remove(sweepable);
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("Resource swept {}", sweepable);
+                }
+            }
+            catch (Throwable x)
+            {
+                LOG.info("Exception while sweeping " + sweepable, x);
+            }
+        }
+        activate();
+    }
+
+    private void activate()
+    {
+        if (isRunning())
+        {
+            Scheduler.Task t = scheduler.schedule(this, period, TimeUnit.MILLISECONDS);
+            if (LOG.isDebugEnabled())
+                LOG.debug("Scheduled in {} ms sweep task {}", period, t);
+            task.set(t);
+        }
+        else
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("Skipping sweep task scheduling");
+        }
+    }
+
+    private void deactivate()
+    {
+        Scheduler.Task t = task.getAndSet(null);
+        if (t != null)
+        {
+            boolean cancelled = t.cancel();
+            if (LOG.isDebugEnabled())
+                LOG.debug("Cancelled ({}) sweep task {}", cancelled, t);
+        }
+    }
+
+    /**
+     * <p>A {@link Sweepable} resource implements this interface to
+     * signal to a {@link Sweeper} or to a parent container if it
+     * needs to be swept or not.</p>
+     * <p>Typical implementations will check their own internal state
+     * and return true or false from {@link #sweep()} to indicate
+     * whether they should be swept.</p>
+     */
+    public interface Sweepable
+    {
+        /**
+         * @return whether this resource should be swept
+         */
+        public boolean sweep();
+    }
+}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/ThreadPool.java b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/ThreadPool.java
index 802e9ca..f7b1275 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/ThreadPool.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/ThreadPool.java
@@ -18,19 +18,22 @@
 
 package org.eclipse.jetty.util.thread;
 
-import org.eclipse.jetty.util.component.LifeCycle;
+import java.util.concurrent.Executor;
+
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
 
 /* ------------------------------------------------------------ */
 /** ThreadPool.
  * 
+ * A specialization of Executor interface that provides reporting methods (eg {@link #getThreads()})
+ * and the option of configuration methods (e.g. @link {@link SizedThreadPool#setMaxThreads(int)}). 
  *
  */
-public interface ThreadPool
+ at ManagedObject("Pool of Threads")
+public interface ThreadPool extends Executor
 {
     /* ------------------------------------------------------------ */
-    public abstract boolean dispatch(Runnable job);
-
-    /* ------------------------------------------------------------ */
     /**
      * Blocks until the thread pool is {@link LifeCycle#stop stopped}.
      */
@@ -40,18 +43,21 @@ public interface ThreadPool
     /**
      * @return The total number of threads currently in the pool
      */
+    @ManagedAttribute("number of threads in pool")
     public int getThreads();
 
     /* ------------------------------------------------------------ */
     /**
      * @return The number of idle threads in the pool
      */
+    @ManagedAttribute("number of idle threads in pool")
     public int getIdleThreads();
     
     /* ------------------------------------------------------------ */
     /**
      * @return True if the pool is low on threads
      */
+    @ManagedAttribute("indicates the pool is low on available threads")
     public boolean isLowOnThreads();
     
 
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/Timeout.java b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/Timeout.java
deleted file mode 100644
index b39fda6..0000000
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/Timeout.java
+++ /dev/null
@@ -1,380 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.util.thread;
-
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-
-
-/* ------------------------------------------------------------ */
-/** Timeout queue.
- * This class implements a timeout queue for timers that are at least as likely to be cancelled as they are to expire.
- * Unlike the util timeout class, the duration of the timeouts is shared by all scheduled tasks and if the duration 
- * is changed, this affects all scheduled tasks.
- * <p>
- * The nested class Task should be extended by users of this class to obtain call back notification of 
- * expires. 
- */
-public class Timeout
-{
-    private static final Logger LOG = Log.getLogger(Timeout.class);
-    private Object _lock;
-    private long _duration;
-    private volatile long _now=System.currentTimeMillis();
-    private Task _head=new Task();
-
-    /* ------------------------------------------------------------ */
-    public Timeout()
-    {
-        _lock=new Object();
-        _head._timeout=this;
-    }
-
-    /* ------------------------------------------------------------ */
-    public Timeout(Object lock)
-    {
-        _lock=lock;
-        _head._timeout=this;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return Returns the duration.
-     */
-    public long getDuration()
-    {
-        return _duration;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param duration The duration to set.
-     */
-    public void setDuration(long duration)
-    {
-        _duration = duration;
-    }
-
-    /* ------------------------------------------------------------ */
-    public long setNow()
-    {
-        return _now=System.currentTimeMillis();
-    }
-    
-    /* ------------------------------------------------------------ */
-    public long getNow()
-    {
-        return _now;
-    }
-
-    /* ------------------------------------------------------------ */
-    public void setNow(long now)
-    {
-        _now=now;
-    }
-
-    /* ------------------------------------------------------------ */
-    /** Get an expired tasks.
-     * This is called instead of {@link #tick()} to obtain the next
-     * expired Task, but without calling it's {@link Task#expire()} or
-     * {@link Task#expired()} methods.
-     * 
-     * @return the next expired task or null.
-     */
-    public Task expired()
-    {
-        synchronized (_lock)
-        {
-            long _expiry = _now-_duration;
-
-            if (_head._next!=_head)
-            {
-                Task task = _head._next;
-                if (task._timestamp>_expiry)
-                    return null;
-
-                task.unlink();
-                task._expired=true;
-                return task;
-            }
-            return null;
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    public void tick()
-    {
-        final long expiry = _now-_duration;
-
-        Task task=null;
-        while (true)
-        {
-            try
-            {
-                synchronized (_lock)
-                {
-                    task= _head._next;
-                    if (task==_head || task._timestamp>expiry)
-                        break;
-                    task.unlink();
-                    task._expired=true;
-                    task.expire();
-                }
-                
-                task.expired();
-            }
-            catch(Throwable th)
-            {
-                LOG.warn(Log.EXCEPTION,th);
-            }
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    public void tick(long now)
-    {
-        _now=now;
-        tick();
-    }
-
-    /* ------------------------------------------------------------ */
-    public void schedule(Task task)
-    {
-        schedule(task,0L);
-    }
-    
-    /* ------------------------------------------------------------ */
-    /**
-     * @param task
-     * @param delay A delay in addition to the default duration of the timeout
-     */
-    public void schedule(Task task,long delay)
-    {
-        synchronized (_lock)
-        {
-            if (task._timestamp!=0)
-            {
-                task.unlink();
-                task._timestamp=0;
-            }
-            task._timeout=this;
-            task._expired=false;
-            task._delay=delay;
-            task._timestamp = _now+delay;
-
-            Task last=_head._prev;
-            while (last!=_head)
-            {
-                if (last._timestamp <= task._timestamp)
-                    break;
-                last=last._prev;
-            }
-            last.link(task);
-        }
-    }
-
-
-    /* ------------------------------------------------------------ */
-    public void cancelAll()
-    {
-        synchronized (_lock)
-        {
-            _head._next=_head._prev=_head;
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    public boolean isEmpty()
-    {
-        synchronized (_lock)
-        {
-            return _head._next==_head;
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    public long getTimeToNext()
-    {
-        synchronized (_lock)
-        {
-            if (_head._next==_head)
-                return -1;
-            long to_next = _duration+_head._next._timestamp-_now;
-            return to_next<0?0:to_next;
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public String toString()
-    {
-        StringBuffer buf = new StringBuffer();
-        buf.append(super.toString());
-        
-        Task task = _head._next;
-        while (task!=_head)
-        {
-            buf.append("-->");
-            buf.append(task);
-            task=task._next;
-        }
-        
-        return buf.toString();
-    }
-
-    /* ------------------------------------------------------------ */
-    /* ------------------------------------------------------------ */
-    /* ------------------------------------------------------------ */
-    /* ------------------------------------------------------------ */
-    /** Task.
-     * The base class for scheduled timeouts.  This class should be
-     * extended to implement the expire() method, which is called if the
-     * timeout expires.
-     * 
-     * 
-     *
-     */
-    public static class Task
-    {
-        Task _next;
-        Task _prev;
-        Timeout _timeout;
-        long _delay;
-        long _timestamp=0;
-        boolean _expired=false;
-
-        /* ------------------------------------------------------------ */
-        protected Task()
-        {
-            _next=_prev=this;
-        }
-
-        /* ------------------------------------------------------------ */
-        public long getTimestamp()
-        {
-            return _timestamp;
-        }
-
-        /* ------------------------------------------------------------ */
-        public long getAge()
-        {
-            final Timeout t = _timeout;
-            if (t!=null)
-            {
-                final long now=t._now;
-                if (now!=0 && _timestamp!=0)
-                    return now-_timestamp;
-            }
-            return 0;
-        }
-
-        /* ------------------------------------------------------------ */
-        private void unlink()
-        {
-            _next._prev=_prev;
-            _prev._next=_next;
-            _next=_prev=this;
-            _expired=false;
-        }
-
-        /* ------------------------------------------------------------ */
-        private void link(Task task)
-        {
-            Task next_next = _next;
-            _next._prev=task;
-            _next=task;
-            _next._next=next_next;
-            _next._prev=this;   
-        }
-        
-        /* ------------------------------------------------------------ */
-        /** Schedule the task on the given timeout.
-         * The task exiry will be called after the timeout duration.
-         * @param timer
-         */
-        public void schedule(Timeout timer)
-        {
-            timer.schedule(this);
-        }
-        
-        /* ------------------------------------------------------------ */
-        /** Schedule the task on the given timeout.
-         * The task exiry will be called after the timeout duration.
-         * @param timer
-         */
-        public void schedule(Timeout timer, long delay)
-        {
-            timer.schedule(this,delay);
-        }
-        
-        /* ------------------------------------------------------------ */
-        /** Reschedule the task on the current timeout.
-         * The task timeout is rescheduled as if it had been cancelled and
-         * scheduled on the current timeout.
-         */
-        public void reschedule()
-        {
-            Timeout timeout = _timeout;
-            if (timeout!=null)
-                timeout.schedule(this,_delay);
-        }
-        
-        /* ------------------------------------------------------------ */
-        /** Cancel the task.
-         * Remove the task from the timeout.
-         */
-        public void cancel()
-        {
-            Timeout timeout = _timeout;
-            if (timeout!=null)
-            {
-                synchronized (timeout._lock)
-                {
-                    unlink();
-                    _timestamp=0;
-                }
-            }
-        }
-        
-        /* ------------------------------------------------------------ */
-        public boolean isExpired() { return _expired; }
-
-        /* ------------------------------------------------------------ */
-	public boolean isScheduled() { return _next!=this; }
-        
-        /* ------------------------------------------------------------ */
-        /** Expire task.
-         * This method is called when the timeout expires. It is called
-         * in the scope of the synchronize block (on this) that sets 
-         * the {@link #isExpired()} state to true.
-         * @see #expired() For an unsynchronized callback.
-         */
-        protected void expire(){}
-
-        /* ------------------------------------------------------------ */
-        /** Expire task.
-         * This method is called when the timeout expires. It is called 
-         * outside of any synchronization scope and may be delayed. 
-         * 
-         */
-        public void expired(){}
-
-    }
-
-}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/TimerScheduler.java b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/TimerScheduler.java
new file mode 100644
index 0000000..021218c
--- /dev/null
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/TimerScheduler.java
@@ -0,0 +1,129 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.thread;
+
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/* ------------------------------------------------------------ */
+/** A scheduler based on the the JVM Timer class
+ */
+public class TimerScheduler extends AbstractLifeCycle implements Scheduler, Runnable
+{
+    private static final Logger LOG = Log.getLogger(TimerScheduler.class);
+
+    /*
+     * This class uses the Timer class rather than an ScheduledExecutionService because
+     * it uses the same algorithm internally and the signature is cheaper to use as there are no
+     * Futures involved (which we do not need).
+     * However, Timer is still locking and a concurrent queue would be better.
+     */
+
+    private final String _name;
+    private final boolean _daemon;
+    private Timer _timer;
+
+    public TimerScheduler()
+    {
+        this(null, false);
+    }
+
+    public TimerScheduler(String name, boolean daemon)
+    {
+        _name = name;
+        _daemon = daemon;
+    }
+
+    @Override
+    protected void doStart() throws Exception
+    {
+        _timer = _name == null ? new Timer() : new Timer(_name, _daemon);
+        run();
+        super.doStart();
+    }
+
+    @Override
+    protected void doStop() throws Exception
+    {
+        _timer.cancel();
+        super.doStop();
+        _timer = null;
+    }
+
+    @Override
+    public Task schedule(final Runnable task, final long delay, final TimeUnit units)
+    {
+        Timer timer = _timer;
+        if (timer == null)
+            throw new RejectedExecutionException("STOPPED: " + this);
+        SimpleTask t = new SimpleTask(task);
+        timer.schedule(t, units.toMillis(delay));
+        return t;
+    }
+
+    @Override
+    public void run()
+    {
+        Timer timer = _timer;
+        if (timer != null)
+        {
+            timer.purge();
+            schedule(this, 1, TimeUnit.SECONDS);
+        }
+    }
+
+    private static class SimpleTask extends TimerTask implements Task
+    {
+        private final Runnable _task;
+
+        private SimpleTask(Runnable runnable)
+        {
+            _task = runnable;
+        }
+
+        @Override
+        public void run()
+        {
+            try
+            {
+                _task.run();
+            }
+            catch (Throwable x)
+            {
+                LOG.debug("Exception while executing task " + _task, x);
+            }
+        }
+
+        @Override
+        public String toString()
+        {
+            return String.format("%s.%s@%x",
+                    TimerScheduler.class.getSimpleName(),
+                    SimpleTask.class.getSimpleName(),
+                    hashCode());
+        }
+    }
+}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/package-info.java b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/package-info.java
new file mode 100644
index 0000000..afbb965
--- /dev/null
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Util : Common ThreadPool Utilities
+ */
+package org.eclipse.jetty.util.thread;
+
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/ArrayQueueTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/ArrayQueueTest.java
index e55ebc5..a3f21c6 100644
--- a/jetty-util/src/test/java/org/eclipse/jetty/util/ArrayQueueTest.java
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/ArrayQueueTest.java
@@ -31,7 +31,7 @@ public class ArrayQueueTest
     public void testWrap() throws Exception
     {
         ArrayQueue<String> queue = new ArrayQueue<String>(3,3);
-        
+
         assertEquals(0,queue.size());
 
         for (int i=0;i<10;i++)
@@ -53,13 +53,13 @@ public class ArrayQueueTest
 
             assertEquals("two",queue.remove(1));
             assertEquals(2,queue.size());
-            
+
             assertEquals("one",queue.remove());
             assertEquals(1,queue.size());
 
             assertEquals("three",queue.poll());
             assertEquals(0,queue.size());
-            
+
             assertEquals(null,queue.poll());
 
             queue.offer("xxx");
@@ -77,10 +77,10 @@ public class ArrayQueueTest
     public void testRemove() throws Exception
     {
         ArrayQueue<String> queue = new ArrayQueue<String>(3,3);
-       
+
         queue.add("0");
         queue.add("x");
-        
+
         for (int i=1;i<100;i++)
         {
             queue.add(""+i);
@@ -88,7 +88,7 @@ public class ArrayQueueTest
             queue.remove(queue.size()-3);
             queue.set(queue.size()-3,queue.get(queue.size()-3)+"!");
         }
-        
+
         for (int i=0;i<99;i++)
             assertEquals(i+"!",queue.get(i));
     }
@@ -105,7 +105,7 @@ public class ArrayQueueTest
         assertEquals(3,queue.getCapacity());
         queue.add("c");
         assertEquals(8,queue.getCapacity());
-        
+
         for (int i=0;i<4;i++)
             queue.add(""+('d'+i));
         assertEquals(8,queue.getCapacity());
@@ -124,7 +124,7 @@ public class ArrayQueueTest
 
         queue.add("z");
         assertEquals(13,queue.getCapacity());
-        
+
         queue.clear();
         assertEquals(13,queue.getCapacity());
         for (int i=0;i<12;i++)
@@ -136,7 +136,7 @@ public class ArrayQueueTest
             queue.add(""+('a'+i));
         assertEquals(13,queue.getCapacity());
     }
-    
+
     @Test
     public void testFullEmpty() throws Exception
     {
@@ -144,7 +144,7 @@ public class ArrayQueueTest
         assertTrue(queue.offer("one"));
         assertTrue(queue.offer("two"));
         assertFalse(queue.offer("three"));
-        
+
         try
         {
             queue.add("four");
@@ -152,7 +152,7 @@ public class ArrayQueueTest
         }
         catch(Exception e)
         {
-            
+
         }
 
         assertEquals("one",queue.peek());
@@ -165,10 +165,10 @@ public class ArrayQueueTest
         }
         catch(Exception e)
         {
-            
+
         }
 
         assertEquals(null,queue.poll());
     }
-        
+
 }
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/B64CodeTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/B64CodeTest.java
index 91cf34c..576541a 100644
--- a/jetty-util/src/test/java/org/eclipse/jetty/util/B64CodeTest.java
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/B64CodeTest.java
@@ -18,9 +18,9 @@
 
 package org.eclipse.jetty.util;
 
+import java.nio.charset.StandardCharsets;
 
-import junit.framework.Assert;
-
+import org.junit.Assert;
 import org.junit.Test;
 
 public class B64CodeTest
@@ -30,14 +30,14 @@ public class B64CodeTest
     @Test
     public void testRFC1421() throws Exception
     {
-        String b64 = B64Code.encode(text,StringUtil.__ISO_8859_1);
+        String b64 = B64Code.encode(text, StandardCharsets.ISO_8859_1);
         Assert.assertEquals("TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlz"+
                 "IHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2Yg"+
                 "dGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGlu"+
                 "dWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRzIHRo"+
                 "ZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4=",b64);
         
-        char[] chars = B64Code.encode(text.getBytes(StringUtil.__ISO_8859_1),false);
+        char[] chars = B64Code.encode(text.getBytes(StandardCharsets.ISO_8859_1),false);
         b64 = new String(chars,0,chars.length);
         Assert.assertEquals("TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlz"+
                 "IHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2Yg"+
@@ -50,7 +50,7 @@ public class B64CodeTest
     @Test
     public void testRFC2045() throws Exception
     {
-        char[] chars = B64Code.encode(text.getBytes(StringUtil.__ISO_8859_1),true);
+        char[] chars = B64Code.encode(text.getBytes(StandardCharsets.ISO_8859_1),true);
         String b64 = new String(chars,0,chars.length);
         Assert.assertEquals("TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlz\r\n"+
                 "IHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2Yg\r\n"+
@@ -64,7 +64,7 @@ public class B64CodeTest
     @Test
     public void testInteger() throws Exception
     {
-        byte[] bytes = text.getBytes(StringUtil.__ISO_8859_1);
+        byte[] bytes = text.getBytes(StandardCharsets.ISO_8859_1);
         int value=(bytes[0]<<24)+(bytes[1]<<16)+(bytes[2]<<8)+(bytes[3]);
         
         StringBuilder b = new StringBuilder();
@@ -74,7 +74,7 @@ public class B64CodeTest
     @Test
     public void testLong() throws Exception
     {
-        byte[] bytes = text.getBytes(StringUtil.__ISO_8859_1);
+        byte[] bytes = text.getBytes(StandardCharsets.ISO_8859_1);
         long value=((0xffL&bytes[0])<<56)+((0xffL&bytes[1])<<48)+((0xffL&bytes[2])<<40)+((0xffL&bytes[3])<<32)+
                 ((0xffL&bytes[4])<<24)+((0xffL&bytes[5])<<16)+((0xffL&bytes[6])<<8)+(0xffL&bytes[7]);
         
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/BlockingArrayQueueTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/BlockingArrayQueueTest.java
index 1cd2ba5..f8f6f0b 100644
--- a/jetty-util/src/test/java/org/eclipse/jetty/util/BlockingArrayQueueTest.java
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/BlockingArrayQueueTest.java
@@ -18,72 +18,72 @@
 
 package org.eclipse.jetty.util;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
 import java.util.HashSet;
+import java.util.ListIterator;
 import java.util.Random;
 import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.concurrent.CyclicBarrier;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
 
+import org.eclipse.jetty.toolchain.test.AdvancedRunner;
+import org.eclipse.jetty.toolchain.test.annotation.Slow;
+import org.junit.Assert;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 
-
+ at RunWith(AdvancedRunner.class)
 public class BlockingArrayQueueTest
 {
-    
     @Test
     public void testWrap() throws Exception
     {
-        BlockingArrayQueue<String> queue = new BlockingArrayQueue<String>(3);
-        
-        assertEquals(0,queue.size());
+        BlockingArrayQueue<String> queue = new BlockingArrayQueue<>(3);
+
+        Assert.assertEquals(0, queue.size());
 
-        for (int i=0;i<3;i++)
+        for (int i=0;i<queue.getMaxCapacity();i++)
         {
             queue.offer("one");
-            assertEquals(1,queue.size());
+            Assert.assertEquals(1, queue.size());
 
             queue.offer("two");
-            assertEquals(2,queue.size());
+            Assert.assertEquals(2, queue.size());
 
             queue.offer("three");
-            assertEquals(3,queue.size());
+            Assert.assertEquals(3, queue.size());
 
-            assertEquals("one",queue.get(0));
-            assertEquals("two",queue.get(1));
-            assertEquals("three",queue.get(2));
+            Assert.assertEquals("one", queue.get(0));
+            Assert.assertEquals("two", queue.get(1));
+            Assert.assertEquals("three", queue.get(2));
 
-            assertEquals("[one, two, three]",queue.toString());
+            Assert.assertEquals("[one, two, three]", queue.toString());
 
-            assertEquals("one",queue.poll());
-            assertEquals(2,queue.size());
+            Assert.assertEquals("one", queue.poll());
+            Assert.assertEquals(2, queue.size());
 
-            assertEquals("two",queue.poll());
-            assertEquals(1,queue.size());
+            Assert.assertEquals("two", queue.poll());
+            Assert.assertEquals(1, queue.size());
 
-            assertEquals("three",queue.poll());
-            assertEquals(0,queue.size());
+            Assert.assertEquals("three", queue.poll());
+            Assert.assertEquals(0, queue.size());
 
 
             queue.offer("xxx");
-            assertEquals(1,queue.size());
-            assertEquals("xxx",queue.poll());
-            assertEquals(0,queue.size());
-
+            Assert.assertEquals(1, queue.size());
+            Assert.assertEquals("xxx", queue.poll());
+            Assert.assertEquals(0, queue.size());
         }
-
     }
 
     @Test
     public void testRemove() throws Exception
     {
-        BlockingArrayQueue<String> queue = new BlockingArrayQueue<String>(3,3);
-       
+        BlockingArrayQueue<String> queue = new BlockingArrayQueue<>(3,3);
+
         queue.add("0");
         queue.add("x");
-        
+
         for (int i=1;i<100;i++)
         {
             queue.add(""+i);
@@ -91,64 +91,78 @@ public class BlockingArrayQueueTest
             queue.remove(queue.size()-3);
             queue.set(queue.size()-3,queue.get(queue.size()-3)+"!");
         }
-        
+
         for (int i=0;i<99;i++)
-            assertEquals(i+"!",queue.get(i));
+            Assert.assertEquals(i + "!", queue.get(i));
+    }
+
+    @Test
+    public void testLimit() throws Exception
+    {
+        BlockingArrayQueue<String> queue = new BlockingArrayQueue<>(1,0,1);
+
+        String element = "0";
+        Assert.assertTrue(queue.add(element));
+        Assert.assertFalse(queue.offer("1"));
+
+        Assert.assertEquals(element, queue.poll());
+        Assert.assertTrue(queue.add(element));
     }
 
     @Test
     public void testGrow() throws Exception
     {
-        BlockingArrayQueue<String> queue = new BlockingArrayQueue<String>(3,2);
-        assertEquals(3,queue.getCapacity());
-        
+        BlockingArrayQueue<String> queue = new BlockingArrayQueue<>(3,2);
+        Assert.assertEquals(3, queue.getCapacity());
+
         queue.add("a");
         queue.add("a");
-        assertEquals(2,queue.size());
-        assertEquals(3,queue.getCapacity());
+        Assert.assertEquals(2, queue.size());
+        Assert.assertEquals(3, queue.getCapacity());
         queue.add("a");
         queue.add("a");
-        assertEquals(4,queue.size());
-        assertEquals(5,queue.getCapacity());
+        Assert.assertEquals(4, queue.size());
+        Assert.assertEquals(5, queue.getCapacity());
 
         int s=5;
         int c=5;
         queue.add("a");
-        
+
         for (int t=0;t<100;t++)
         {
-            assertEquals(s,queue.size());
-            assertEquals(c,queue.getCapacity());
+            Assert.assertEquals(s, queue.size());
+            Assert.assertEquals(c, queue.getCapacity());
 
             for (int i=queue.size();i-->0;)
                 queue.poll();
-            assertEquals(0,queue.size());
-            assertEquals(c,queue.getCapacity());
+            Assert.assertEquals(0, queue.size());
+            Assert.assertEquals(c, queue.getCapacity());
 
             for (int i=queue.getCapacity();i-->0;)
                 queue.add("a");
             queue.add("a");
-            assertEquals(s+1,queue.size());
-            assertEquals(c+2,queue.getCapacity());
+            Assert.assertEquals(s + 1, queue.size());
+            Assert.assertEquals(c + 2, queue.getCapacity());
 
             queue.poll();
             queue.add("a");
             queue.add("a");
-            assertEquals(s+2,queue.size());
-            assertEquals(c+2,queue.getCapacity());
+            Assert.assertEquals(s + 2, queue.size());
+            Assert.assertEquals(c + 2, queue.getCapacity());
 
             s+=2;
             c+=2;
         }
     }
-    
+
     @Test
+    @Slow
     public void testTake() throws Exception
     {
         final String[] data=new String[4];
 
-        final BlockingArrayQueue<String> queue = new BlockingArrayQueue<String>();
-        
+        final BlockingArrayQueue<String> queue = new BlockingArrayQueue<>();
+
         Thread thread = new Thread()
         {
             @Override
@@ -164,14 +178,14 @@ public class BlockingArrayQueueTest
                 }
                 catch(Exception e)
                 {
-                    assertTrue(false);
                     e.printStackTrace();
+                    Assert.fail();
                 }
             }
         };
-        
+
         thread.start();
-        
+
         Thread.sleep(1000);
 
         queue.offer("zero");
@@ -179,45 +193,41 @@ public class BlockingArrayQueueTest
         queue.offer("two");
         thread.join();
 
-        assertEquals("zero",data[0]);
-        assertEquals("one",data[1]);
-        assertEquals("two",data[2]);
-        assertEquals(null,data[3]);
-        
+        Assert.assertEquals("zero", data[0]);
+        Assert.assertEquals("one", data[1]);
+        Assert.assertEquals("two", data[2]);
+        Assert.assertEquals(null, data[3]);
     }
-    
-    volatile boolean _running;
-    
+
     @Test
+    @Slow
     public void testConcurrentAccess() throws Exception
     {
         final int THREADS=50;
         final int LOOPS=1000;
 
-        final BlockingArrayQueue<Integer> queue = new BlockingArrayQueue<Integer>(1+THREADS*LOOPS);
-        
-        final ConcurrentLinkedQueue<Integer> produced=new ConcurrentLinkedQueue<Integer>();
-        final ConcurrentLinkedQueue<Integer> consumed=new ConcurrentLinkedQueue<Integer>();
-        
+        final BlockingArrayQueue<Integer> queue = new BlockingArrayQueue<>(1+THREADS*LOOPS);
+
+        final ConcurrentLinkedQueue<Integer> produced=new ConcurrentLinkedQueue<>();
+        final ConcurrentLinkedQueue<Integer> consumed=new ConcurrentLinkedQueue<>();
+
+        final AtomicBoolean running = new AtomicBoolean(true);
 
-        _running=true;
-        
         // start consumers
         final CyclicBarrier barrier0 = new CyclicBarrier(THREADS+1);
         for (int i=0;i<THREADS;i++)
         {
-            final Integer id = new Integer(i);
             new Thread()
             {
                 @Override
                 public void run()
                 {
                     final Random random = new Random();
-                    
+
                     setPriority(getPriority()-1);
                     try
                     {
-                        while(_running)
+                        while(running.get())
                         {
                             int r=1+random.nextInt(10);
                             if (r%2==0)
@@ -251,7 +261,6 @@ public class BlockingArrayQueueTest
                         }
                         catch (Exception e)
                         {
-                            // TODO Auto-generated catch block
                             e.printStackTrace();
                         }
                     }
@@ -263,7 +272,7 @@ public class BlockingArrayQueueTest
         final CyclicBarrier barrier1 = new CyclicBarrier(THREADS+1);
         for (int i=0;i<THREADS;i++)
         {
-            final Integer id = new Integer(i);
+            final int id = i;
             new Thread()
             {
                 @Override
@@ -274,7 +283,7 @@ public class BlockingArrayQueueTest
                     {
                         for (int j=0;j<LOOPS;j++)
                         {
-                            Integer msg = new Integer(random.nextInt());
+                            Integer msg = random.nextInt();
                             produced.add(msg);
                             if (!queue.offer(msg))
                                 throw new Exception(id+" FULL! "+queue.size());
@@ -293,14 +302,13 @@ public class BlockingArrayQueueTest
                         }
                         catch (Exception e)
                         {
-                            // TODO Auto-generated catch block
                             e.printStackTrace();
                         }
                     }
                 }
             }.start();
         }
-        
+
         barrier1.await();
         int size=queue.size();
         int last=size-1;
@@ -309,13 +317,179 @@ public class BlockingArrayQueueTest
             last=size;
             Thread.sleep(500);
             size=queue.size();
-        }   
-        _running=false;
+        }
+        running.set(false);
         barrier0.await();
-        
-        HashSet<Integer> prodSet = new HashSet<Integer>(produced);
-        HashSet<Integer> consSet = new HashSet<Integer>(consumed);
-        
-        assertEquals(prodSet,consSet);
+
+        HashSet<Integer> prodSet = new HashSet<>(produced);
+        HashSet<Integer> consSet = new HashSet<>(consumed);
+
+        Assert.assertEquals(prodSet, consSet);
+    }
+
+    @Test
+    public void testRemoveObjectFromEmptyQueue()
+    {
+        BlockingArrayQueue<String> queue = new BlockingArrayQueue<>(4,0,4);
+        Assert.assertFalse(queue.remove("SOMETHING"));
+    }
+
+    @Test
+    public void testRemoveObjectWithWrappedTail() throws Exception
+    {
+        BlockingArrayQueue<String> queue = new BlockingArrayQueue<>(6);
+        // Wrap the tail
+        for (int i = 0; i < queue.getMaxCapacity(); ++i)
+            queue.offer("" + i);
+        // Advance the head
+        queue.poll();
+        // Remove from the middle
+        Assert.assertTrue(queue.remove("2"));
+
+        // Advance the tail
+        Assert.assertTrue(queue.offer("A"));
+        Assert.assertTrue(queue.offer("B"));
+        queue.poll();
+        // Remove from the middle
+        Assert.assertTrue(queue.remove("3"));
+    }
+
+    @Test
+    public void testRemoveObject() throws Exception
+    {
+        BlockingArrayQueue<String> queue = new BlockingArrayQueue<>(4,0,4);
+
+        String element1 = "A";
+        Assert.assertTrue(queue.offer(element1));
+        Assert.assertTrue(queue.remove(element1));
+
+        for (int i = 0; i < queue.getMaxCapacity() - 1; ++i)
+        {
+            queue.offer("" + i);
+            queue.poll();
+        }
+        String element2 = "B";
+        Assert.assertTrue(queue.offer(element2));
+        Assert.assertTrue(queue.offer(element1));
+        Assert.assertTrue(queue.remove(element1));
+
+        Assert.assertFalse(queue.remove("NOT_PRESENT"));
+
+        Assert.assertTrue(queue.remove(element2));
+        Assert.assertFalse(queue.remove("NOT_PRESENT"));
+
+        queue.clear();
+
+        for (int i = 0; i < queue.getMaxCapacity(); ++i)
+            queue.offer("" + i);
+
+        Assert.assertTrue(queue.remove("" + (queue.getMaxCapacity() - 1)));
+    }
+
+    @Test
+    public void testRemoveWithMaxCapacityOne() throws Exception
+    {
+        BlockingArrayQueue<String> queue = new BlockingArrayQueue<>(1);
+
+        String element = "A";
+        Assert.assertTrue(queue.offer(element));
+        Assert.assertTrue(queue.remove(element));
+
+        Assert.assertTrue(queue.offer(element));
+        Assert.assertEquals(element, queue.remove(0));
+    }
+
+    @Test
+    public void testIteratorWithModification() throws Exception
+    {
+        BlockingArrayQueue<String> queue = new BlockingArrayQueue<>(4,0,4);
+        int count = queue.getMaxCapacity() - 1;
+        for (int i = 0; i < count; ++i)
+            queue.offer("" + i);
+
+        int sum = 0;
+        for (String element : queue)
+        {
+            ++sum;
+            // Concurrent modification, must not change the iterator
+            queue.remove(element);
+        }
+
+        Assert.assertEquals(count, sum);
+        Assert.assertTrue(queue.isEmpty());
+    }
+
+    @Test
+    public void testListIterator() throws Exception
+    {
+        BlockingArrayQueue<String> queue = new BlockingArrayQueue<>(4,0,4);
+        String element1 = "A";
+        String element2 = "B";
+        queue.offer(element1);
+        queue.offer(element2);
+
+        ListIterator<String> iterator = queue.listIterator();
+        Assert.assertTrue(iterator.hasNext());
+        Assert.assertFalse(iterator.hasPrevious());
+
+        String element = iterator.next();
+        Assert.assertEquals(element1, element);
+        Assert.assertTrue(iterator.hasNext());
+        Assert.assertTrue(iterator.hasPrevious());
+
+        element = iterator.next();
+        Assert.assertEquals(element2, element);
+        Assert.assertFalse(iterator.hasNext());
+        Assert.assertTrue(iterator.hasPrevious());
+
+        element = iterator.previous();
+        Assert.assertEquals(element2, element);
+        Assert.assertTrue(iterator.hasNext());
+        Assert.assertTrue(iterator.hasPrevious());
+
+        element = iterator.previous();
+        Assert.assertEquals(element1, element);
+        Assert.assertTrue(iterator.hasNext());
+        Assert.assertFalse(iterator.hasPrevious());
+    }
+
+    @Test
+    public void testListIteratorWithWrappedHead() throws Exception
+    {
+        BlockingArrayQueue<String> queue = new BlockingArrayQueue<>(4,0,4);
+        // This sequence of offers and polls wraps the head around the array
+        queue.offer("0");
+        queue.offer("1");
+        queue.offer("2");
+        queue.offer("3");
+        queue.poll();
+        queue.poll();
+
+        String element1 = queue.get(0);
+        String element2 = queue.get(1);
+
+        ListIterator<String> iterator = queue.listIterator();
+        Assert.assertTrue(iterator.hasNext());
+        Assert.assertFalse(iterator.hasPrevious());
+
+        String element = iterator.next();
+        Assert.assertEquals(element1, element);
+        Assert.assertTrue(iterator.hasNext());
+        Assert.assertTrue(iterator.hasPrevious());
+
+        element = iterator.next();
+        Assert.assertEquals(element2, element);
+        Assert.assertFalse(iterator.hasNext());
+        Assert.assertTrue(iterator.hasPrevious());
+
+        element = iterator.previous();
+        Assert.assertEquals(element2, element);
+        Assert.assertTrue(iterator.hasNext());
+        Assert.assertTrue(iterator.hasPrevious());
+
+        element = iterator.previous();
+        Assert.assertEquals(element1, element);
+        Assert.assertTrue(iterator.hasNext());
+        Assert.assertFalse(iterator.hasPrevious());
     }
 }
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/BlockingCallbackTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/BlockingCallbackTest.java
new file mode 100644
index 0000000..54462df
--- /dev/null
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/BlockingCallbackTest.java
@@ -0,0 +1,123 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.io.IOException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.hamcrest.Matchers;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class BlockingCallbackTest
+{
+    public BlockingCallbackTest()
+    {
+    }
+    
+    @Test
+    public void testDone() throws Exception
+    {
+        final BlockingCallback fcb= new BlockingCallback();
+        fcb.succeeded();
+        long start=System.currentTimeMillis();
+        fcb.block();
+        Assert.assertThat(System.currentTimeMillis()-start,Matchers.lessThan(500L));     
+    }
+    
+    @Test
+    public void testGetDone() throws Exception
+    {
+        final BlockingCallback fcb= new BlockingCallback();
+        final CountDownLatch latch = new CountDownLatch(1);
+        
+        new Thread(new Runnable()
+        {
+            @Override
+            public void run()
+            {
+                latch.countDown();
+                try{TimeUnit.MILLISECONDS.sleep(100);}catch(Exception e){e.printStackTrace();}
+                fcb.succeeded();
+            }
+        }).start();
+        
+        latch.await();
+        long start=System.currentTimeMillis();
+        fcb.block();
+        Assert.assertThat(System.currentTimeMillis()-start,Matchers.greaterThan(10L)); 
+        Assert.assertThat(System.currentTimeMillis()-start,Matchers.lessThan(1000L)); 
+    }
+    
+    @Test
+    public void testFailed() throws Exception
+    {
+        final BlockingCallback fcb= new BlockingCallback();
+        Exception ex=new Exception("FAILED");
+        fcb.failed(ex);
+        
+        long start=System.currentTimeMillis();
+        try
+        {
+            fcb.block();
+            Assert.fail();
+        }
+        catch(IOException ee)
+        {
+            Assert.assertEquals(ex,ee.getCause());
+        }
+        Assert.assertThat(System.currentTimeMillis()-start,Matchers.lessThan(100L));     
+    }
+    
+    @Test
+    public void testGetFailed() throws Exception
+    {
+        final BlockingCallback fcb= new BlockingCallback();
+        final Exception ex=new Exception("FAILED");
+        final CountDownLatch latch = new CountDownLatch(1);
+        
+        new Thread(new Runnable()
+        {
+            @Override
+            public void run()
+            {
+                latch.countDown();
+                try{TimeUnit.MILLISECONDS.sleep(100);}catch(Exception e){e.printStackTrace();}
+                fcb.failed(ex);
+            }
+        }).start();
+        
+        latch.await();
+        long start=System.currentTimeMillis();
+        try
+        {
+            fcb.block();
+            Assert.fail();
+        }
+        catch(IOException ee)
+        {
+            Assert.assertEquals(ex,ee.getCause());
+        }
+        Assert.assertThat(System.currentTimeMillis()-start,Matchers.greaterThan(10L)); 
+        Assert.assertThat(System.currentTimeMillis()-start,Matchers.lessThan(1000L));
+    }
+        
+    
+}
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/BufferUtilTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/BufferUtilTest.java
new file mode 100644
index 0000000..d2f8b24
--- /dev/null
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/BufferUtilTest.java
@@ -0,0 +1,338 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.BufferOverflowException;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.concurrent.ThreadLocalRandom;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.junit.Assert;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class BufferUtilTest
+{
+    @Test
+    public void testToInt() throws Exception
+    {
+        ByteBuffer buf[] =
+        {
+            BufferUtil.toBuffer("0"),
+            BufferUtil.toBuffer(" 42 "),
+            BufferUtil.toBuffer("   43abc"),
+            BufferUtil.toBuffer("-44"),
+            BufferUtil.toBuffer(" - 45;"),
+            BufferUtil.toBuffer("-2147483648"),
+            BufferUtil.toBuffer("2147483647"),
+        };
+
+        int val[] =
+        {
+            0,42,43,-44,-45,-2147483648,2147483647
+        };
+
+        for (int i=0;i<buf.length;i++)
+            assertEquals("t"+i, val[i], BufferUtil.toInt(buf[i]));
+    }
+
+    @Test
+    public void testPutInt() throws Exception
+    {
+        int val[] =
+        {
+            0,42,43,-44,-45,Integer.MIN_VALUE,Integer.MAX_VALUE
+        };
+
+        String str[] =
+        {
+            "0","42","43","-44","-45",""+Integer.MIN_VALUE,""+Integer.MAX_VALUE
+        };
+
+        ByteBuffer buffer = ByteBuffer.allocate(24);
+
+        for (int i=0;i<val.length;i++)
+        {
+            BufferUtil.clearToFill(buffer);
+            BufferUtil.putDecInt(buffer,val[i]);
+            BufferUtil.flipToFlush(buffer,0);
+            assertEquals("t"+i,str[i],BufferUtil.toString(buffer));
+        }
+    }
+
+    @Test
+    public void testPutLong() throws Exception
+    {
+        long val[] =
+        {
+                0L,42L,43L,-44L,-45L,Long.MIN_VALUE,Long.MAX_VALUE
+        };
+
+        String str[] =
+        {
+                "0","42","43","-44","-45",""+Long.MIN_VALUE,""+Long.MAX_VALUE
+        };
+
+        ByteBuffer buffer = ByteBuffer.allocate(50);
+
+        for (int i=0;i<val.length;i++)
+        {
+            BufferUtil.clearToFill(buffer);
+            BufferUtil.putDecLong(buffer,val[i]);
+            BufferUtil.flipToFlush(buffer,0);
+            assertEquals("t"+i,str[i],BufferUtil.toString(buffer));
+        }
+    }
+
+    @Test
+    public void testPutHexInt() throws Exception
+    {
+        int val[] =
+        {
+            0,42,43,-44,-45,-2147483648,2147483647
+        };
+
+        String str[] =
+        {
+            "0","2A","2B","-2C","-2D","-80000000","7FFFFFFF"
+        };
+
+        ByteBuffer buffer = ByteBuffer.allocate(50);
+
+        for (int i=0;i<val.length;i++)
+        {
+            BufferUtil.clearToFill(buffer);
+            BufferUtil.putHexInt(buffer,val[i]);
+            BufferUtil.flipToFlush(buffer,0);
+            assertEquals("t"+i,str[i],BufferUtil.toString(buffer));
+        }
+    }
+
+    @Test
+    public void testPut() throws Exception
+    {
+        ByteBuffer to = BufferUtil.allocate(10);
+        ByteBuffer from=BufferUtil.toBuffer("12345");
+
+        BufferUtil.clear(to);
+        assertEquals(5,BufferUtil.append(to,from));
+        assertTrue(BufferUtil.isEmpty(from));
+        assertEquals("12345",BufferUtil.toString(to));
+
+        from=BufferUtil.toBuffer("XX67890ZZ");
+        from.position(2);
+
+        assertEquals(5,BufferUtil.append(to,from));
+        assertEquals(2,from.remaining());
+        assertEquals("1234567890",BufferUtil.toString(to));
+    }
+    
+
+
+    @Test
+    public void testAppend() throws Exception
+    {
+        ByteBuffer to = BufferUtil.allocate(8);
+        ByteBuffer from=BufferUtil.toBuffer("12345");
+
+        BufferUtil.append(to,from.array(),0,3);
+        assertEquals("123",BufferUtil.toString(to));
+        BufferUtil.append(to,from.array(),3,2);
+        assertEquals("12345",BufferUtil.toString(to));
+        
+        try
+        {
+            BufferUtil.append(to,from.array(),0,5);
+            Assert.fail();
+        }
+        catch(BufferOverflowException e)
+        {}
+    }
+    
+
+    @Test
+    public void testPutDirect() throws Exception
+    {
+        ByteBuffer to = BufferUtil.allocateDirect(10);
+        ByteBuffer from=BufferUtil.toBuffer("12345");
+
+        BufferUtil.clear(to);
+        assertEquals(5,BufferUtil.append(to,from));
+        assertTrue(BufferUtil.isEmpty(from));
+        assertEquals("12345",BufferUtil.toString(to));
+
+        from=BufferUtil.toBuffer("XX67890ZZ");
+        from.position(2);
+
+        assertEquals(5,BufferUtil.append(to,from));
+        assertEquals(2,from.remaining());
+        assertEquals("1234567890",BufferUtil.toString(to));
+    }
+
+    @Test
+    public void testToBuffer_Array()
+    {
+        byte arr[] = new byte[128];
+        Arrays.fill(arr,(byte)0x44);
+        ByteBuffer buf = BufferUtil.toBuffer(arr);
+
+        int count = 0;
+        while (buf.remaining() > 0)
+        {
+            byte b = buf.get();
+            Assert.assertEquals(b,0x44);
+            count++;
+        }
+
+        Assert.assertEquals("Count of bytes",arr.length,count);
+    }
+
+    @Test
+    public void testToBuffer_ArrayOffsetLength()
+    {
+        byte arr[] = new byte[128];
+        Arrays.fill(arr,(byte)0xFF); // fill whole thing with FF
+        int offset = 10;
+        int length = 100;
+        Arrays.fill(arr,offset,offset + length,(byte)0x77); // fill partial with 0x77
+        ByteBuffer buf = BufferUtil.toBuffer(arr,offset,length);
+
+        int count = 0;
+        while (buf.remaining() > 0)
+        {
+            byte b = buf.get();
+            Assert.assertEquals(b,0x77);
+            count++;
+        }
+
+        Assert.assertEquals("Count of bytes",length,count);
+    }
+
+    private static final Logger LOG = Log.getLogger(BufferUtilTest.class);
+
+    @Test
+    @Ignore("Very simple microbenchmark to compare different writeTo implementations. Only for development thus " +
+            "ignored.")
+    public void testWriteToMicrobenchmark() throws IOException
+    {
+        int capacity = 1024 * 128;
+        int iterations = 100;
+        int testRuns = 10;
+        byte[] bytes = new byte[capacity];
+        ThreadLocalRandom.current().nextBytes(bytes);
+        ByteBuffer buffer = BufferUtil.allocate(capacity);
+        BufferUtil.append(buffer, bytes, 0, capacity);
+        long startTest = System.nanoTime();
+        for (int i = 0; i < testRuns; i++)
+        {
+            long start = System.nanoTime();
+            for (int j = 0; j < iterations; j++)
+            {
+                ByteArrayOutputStream out = new ByteArrayOutputStream();
+                long startRun = System.nanoTime();
+                BufferUtil.writeTo(buffer.asReadOnlyBuffer(), out);
+                long elapsedRun = System.nanoTime() - startRun;
+//                LOG.warn("run elapsed={}ms", elapsedRun / 1000);
+                assertThat("Bytes in out equal bytes in buffer", Arrays.equals(bytes, out.toByteArray()), is(true));
+            }
+            long elapsed = System.nanoTime() - start;
+            LOG.warn("elapsed={}ms average={}ms", elapsed / 1000, elapsed/iterations/1000);
+        }
+        LOG.warn("overall average: {}ms", (System.nanoTime() - startTest) / testRuns / iterations / 1000);
+    }
+
+    @Test
+    public void testWriteToWithBufferThatDoesNotExposeArrayAndSmallContent() throws IOException
+    {
+        int capacity = BufferUtil.TEMP_BUFFER_SIZE/4;
+        testWriteToWithBufferThatDoesNotExposeArray(capacity);
+    }
+
+    @Test
+    public void testWriteToWithBufferThatDoesNotExposeArrayAndContentLengthMatchingTempBufferSize() throws IOException
+    {
+        int capacity = BufferUtil.TEMP_BUFFER_SIZE;
+        testWriteToWithBufferThatDoesNotExposeArray(capacity);
+    }
+
+    @Test
+    public void testWriteToWithBufferThatDoesNotExposeArrayAndContentSlightlyBiggerThanTwoTimesTempBufferSize()
+            throws
+            IOException
+    {
+        int capacity = BufferUtil.TEMP_BUFFER_SIZE*2+1024;
+        testWriteToWithBufferThatDoesNotExposeArray(capacity);
+    }
+    
+
+    @Test
+    public void testEnsureCapacity() throws Exception
+    {
+        ByteBuffer b = BufferUtil.toBuffer("Goodbye Cruel World");
+        assertTrue(b==BufferUtil.ensureCapacity(b, 0));
+        assertTrue(b==BufferUtil.ensureCapacity(b, 10));
+        assertTrue(b==BufferUtil.ensureCapacity(b, b.capacity()));
+        
+
+        ByteBuffer b1 = BufferUtil.ensureCapacity(b, 64);
+        assertTrue(b!=b1);
+        assertEquals(64, b1.capacity());
+        assertEquals("Goodbye Cruel World", BufferUtil.toString(b1));
+        
+        b1.position(8);
+        b1.limit(13);
+        assertEquals("Cruel", BufferUtil.toString(b1));
+        ByteBuffer b2 = b1.slice();
+        assertEquals("Cruel", BufferUtil.toString(b2));
+        System.err.println(BufferUtil.toDetailString(b2));
+        assertEquals(8, b2.arrayOffset());
+        assertEquals(5, b2.capacity());
+
+        assertTrue(b2==BufferUtil.ensureCapacity(b2, 5));
+
+        ByteBuffer b3 = BufferUtil.ensureCapacity(b2, 64);
+        assertTrue(b2!=b3);
+        assertEquals(64, b3.capacity());
+        assertEquals("Cruel", BufferUtil.toString(b3));
+        assertEquals(0, b3.arrayOffset());
+        
+    }
+    
+
+    private void testWriteToWithBufferThatDoesNotExposeArray(int capacity) throws IOException
+    {
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        byte[] bytes = new byte[capacity];
+        ThreadLocalRandom.current().nextBytes(bytes);
+        ByteBuffer buffer = BufferUtil.allocate(capacity);
+        BufferUtil.append(buffer, bytes, 0, capacity);
+        BufferUtil.writeTo(buffer.asReadOnlyBuffer(), out);
+        assertThat("Bytes in out equal bytes in buffer", Arrays.equals(bytes, out.toByteArray()), is(true));
+    }
+}
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/CollectionAssert.java b/jetty-util/src/test/java/org/eclipse/jetty/util/CollectionAssert.java
new file mode 100644
index 0000000..0e841f8
--- /dev/null
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/CollectionAssert.java
@@ -0,0 +1,137 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.junit.Assert;
+
+public class CollectionAssert
+{
+    public static void assertContainsUnordered(String msg, Collection<String> expectedSet, Collection<String> actualSet)
+    {
+        // same size?
+        boolean mismatch = expectedSet.size() != actualSet.size();
+
+        // test content
+        Set<String> missing = new HashSet<>();
+        for (String expected : expectedSet)
+        {
+            if (!actualSet.contains(expected))
+            {
+                missing.add(expected);
+            }
+        }
+
+        if (mismatch || missing.size() > 0)
+        {
+            // build up detailed error message
+            StringWriter message = new StringWriter();
+            PrintWriter err = new PrintWriter(message);
+
+            err.printf("%s: Assert Contains (Unordered)",msg);
+            if (mismatch)
+            {
+                err.print(" [size mismatch]");
+            }
+            if (missing.size() >= 0)
+            {
+                err.printf(" [%d entries missing]",missing.size());
+            }
+            err.println();
+            err.printf("Actual Entries (size: %d)%n",actualSet.size());
+            for (String actual : actualSet)
+            {
+                char indicator = expectedSet.contains(actual)?' ':'>';
+                err.printf("%s| %s%n",indicator,actual);
+            }
+            err.printf("Expected Entries (size: %d)%n",expectedSet.size());
+            for (String expected : expectedSet)
+            {
+                char indicator = actualSet.contains(expected)?' ':'>';
+                err.printf("%s| %s%n",indicator,expected);
+            }
+            err.flush();
+            Assert.fail(message.toString());
+        }
+    }
+    
+    public static void assertOrdered(String msg, List<String> expectedList, List<String> actualList)
+    {
+        // same size?
+        boolean mismatch = expectedList.size() != actualList.size();
+
+        // test content
+        List<Integer> badEntries = new ArrayList<>();
+        int min = Math.min(expectedList.size(),actualList.size());
+        int max = Math.max(expectedList.size(),actualList.size());
+        for (int i = 0; i < min; i++)
+        {
+            if (!expectedList.get(i).equals(actualList.get(i)))
+            {
+                badEntries.add(i);
+            }
+        }
+        for (int i = min; i < max; i++)
+        {
+            badEntries.add(i);
+        }
+
+        if (mismatch || badEntries.size() > 0)
+        {
+            // build up detailed error message
+            StringWriter message = new StringWriter();
+            PrintWriter err = new PrintWriter(message);
+
+            err.printf("%s: Assert Contains (Unordered)",msg);
+            if (mismatch)
+            {
+                err.print(" [size mismatch]");
+            }
+            if (badEntries.size() >= 0)
+            {
+                err.printf(" [%d entries not matched]",badEntries.size());
+            }
+            err.println();
+            err.printf("Actual Entries (size: %d)%n",actualList.size());
+            for (int i = 0; i < actualList.size(); i++)
+            {
+                String actual = actualList.get(i);
+                char indicator = badEntries.contains(i)?'>':' ';
+                err.printf("%s[%d] %s%n",indicator,i,actual);
+            }
+
+            err.printf("Expected Entries (size: %d)%n",expectedList.size());
+            for (int i = 0; i < expectedList.size(); i++)
+            {
+                String expected = expectedList.get(i);
+                char indicator = badEntries.contains(i)?'>':' ';
+                err.printf("%s[%d] %s%n",indicator,i,expected);
+            }
+            err.flush();
+            Assert.fail(message.toString());
+        }
+    }
+}
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/ConcurrentArrayQueueTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/ConcurrentArrayQueueTest.java
new file mode 100644
index 0000000..a3ad744
--- /dev/null
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/ConcurrentArrayQueueTest.java
@@ -0,0 +1,172 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class ConcurrentArrayQueueTest
+{
+    protected ConcurrentArrayQueue<Integer> newConcurrentArrayQueue(int blockSize)
+    {
+        return new ConcurrentArrayQueue<>(blockSize);
+    }
+
+    @Test
+    public void testOfferCreatesBlock()
+    {
+        int blockSize = 2;
+        ConcurrentArrayQueue<Integer> queue = newConcurrentArrayQueue(blockSize);
+        int blocks = 3;
+        for (int i = 0; i < blocks * blockSize + 1; ++i)
+            queue.offer(i);
+        Assert.assertEquals(blocks + 1, queue.getBlockCount());
+    }
+
+    @Test
+    public void testPeekRemove() throws Exception
+    {
+        int blockSize = 2;
+        ConcurrentArrayQueue<Integer> queue = newConcurrentArrayQueue(blockSize);
+
+        Assert.assertNull(queue.peek());
+
+        queue.offer(1);
+        queue.remove(1);
+        Assert.assertNull(queue.peek());
+
+        int blocks = 3;
+        int size = blocks * blockSize + 1;
+        for (int i = 0; i < size; ++i)
+            queue.offer(i);
+        for (int i = 0; i < size; ++i)
+        {
+            Assert.assertEquals(i, (int)queue.peek());
+            Assert.assertEquals(i, (int)queue.remove());
+        }
+    }
+
+    @Test
+    public void testRemoveObject() throws Exception
+    {
+        int blockSize = 2;
+        ConcurrentArrayQueue<Integer> queue = newConcurrentArrayQueue(blockSize);
+        queue.add(1);
+        queue.add(2);
+        queue.add(3);
+
+        Assert.assertFalse(queue.remove(4));
+
+        int size = queue.size();
+
+        Assert.assertTrue(queue.remove(2));
+        --size;
+        Assert.assertEquals(size, queue.size());
+
+        Iterator<Integer> iterator = queue.iterator();
+        Assert.assertTrue(iterator.hasNext());
+        Assert.assertEquals(1, (int)iterator.next());
+        Assert.assertTrue(iterator.hasNext());
+        Assert.assertEquals(3, (int)iterator.next());
+
+        queue.offer(4);
+        ++size;
+
+        Assert.assertTrue(queue.remove(3));
+        --size;
+        Assert.assertEquals(size, queue.size());
+
+        iterator = queue.iterator();
+        Assert.assertTrue(iterator.hasNext());
+        Assert.assertEquals(1, (int)iterator.next());
+        Assert.assertTrue(iterator.hasNext());
+        Assert.assertEquals(4, (int)iterator.next());
+
+        Assert.assertTrue(queue.remove(1));
+        --size;
+        Assert.assertTrue(queue.remove(4));
+        --size;
+
+        iterator = queue.iterator();
+        Assert.assertFalse(iterator.hasNext());
+    }
+
+    @Test
+    public void testSize() throws Exception
+    {
+        int blockSize = 2;
+        ConcurrentArrayQueue<Integer> queue = newConcurrentArrayQueue(blockSize);
+        queue.offer(1);
+        Assert.assertEquals(1, queue.size());
+
+        queue = newConcurrentArrayQueue(blockSize);
+        for (int i = 0; i < 2 * blockSize; ++i)
+            queue.offer(i);
+        for (int i = 0; i < blockSize; ++i)
+            queue.poll();
+        Assert.assertEquals(blockSize, queue.size());
+    }
+
+    @Test
+    public void testIterator() throws Exception
+    {
+        int blockSize = 2;
+        ConcurrentArrayQueue<Integer> queue = newConcurrentArrayQueue(blockSize);
+        queue.offer(1);
+        Iterator<Integer> iterator = queue.iterator();
+        Assert.assertTrue(iterator.hasNext());
+        Assert.assertEquals(1, (int)iterator.next());
+        Assert.assertFalse(iterator.hasNext());
+
+        try
+        {
+            iterator.next();
+            Assert.fail();
+        }
+        catch (NoSuchElementException ignored)
+        {
+        }
+
+        // Test block edge
+        queue = newConcurrentArrayQueue(blockSize);
+        for (int i = 0; i < blockSize * 2; ++i)
+            queue.offer(i);
+        queue.poll();
+        iterator = queue.iterator();
+        Assert.assertTrue(iterator.hasNext());
+        Assert.assertEquals(1, (int)iterator.next());
+        Assert.assertTrue(iterator.hasNext());
+        Assert.assertEquals(2, (int)iterator.next());
+        Assert.assertTrue(iterator.hasNext());
+        Assert.assertEquals(3, (int)iterator.next());
+        Assert.assertFalse(iterator.hasNext());
+
+        try
+        {
+            iterator.next();
+            Assert.fail();
+        }
+        catch (NoSuchElementException ignored)
+        {
+        }
+    }
+}
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/DateCacheTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/DateCacheTest.java
index c302657..eb9dc5b 100644
--- a/jetty-util/src/test/java/org/eclipse/jetty/util/DateCacheTest.java
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/DateCacheTest.java
@@ -18,83 +18,57 @@
 
 package org.eclipse.jetty.util;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.assertFalse;
 
 import java.util.Locale;
 import java.util.TimeZone;
+import java.util.concurrent.TimeUnit;
 
+import org.eclipse.jetty.toolchain.test.AdvancedRunner;
+import org.eclipse.jetty.toolchain.test.annotation.Slow;
+import org.hamcrest.Matchers;
+import org.junit.Assert;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 
 
-/* ------------------------------------------------------------ */
-/** Util meta Tests.
- * 
+/**
+ * Util meta Tests.
  */
+ at RunWith(AdvancedRunner.class)
 public class DateCacheTest
 {
     /* ------------------------------------------------------------ */
     @Test
+    @Slow
     public void testDateCache() throws Exception
     {
         //@WAS: Test t = new Test("org.eclipse.jetty.util.DateCache");
         //                            012345678901234567890123456789
-        DateCache dc = new DateCache("EEE, dd MMM yyyy HH:mm:ss zzz ZZZ",
-                                     Locale.US);
-            dc.setTimeZone(TimeZone.getTimeZone("GMT"));
-            String last=dc.format(System.currentTimeMillis());
-            boolean change=false;
-            for (int i=0;i<15;i++)
-            {
-                Thread.sleep(100);
-                String date=dc.format(System.currentTimeMillis());
-                
-                assertEquals( "Same Date",
-                              last.substring(0,17),
-                              date.substring(0,17));
-                
-                if (!last.substring(17).equals(date.substring(17)))
-                    change=true;
-                else
-                {
-                    int lh=Integer.parseInt(last.substring(17,19));
-                    int dh=Integer.parseInt(date.substring(17,19));
-                    int lm=Integer.parseInt(last.substring(20,22));
-                    int dm=Integer.parseInt(date.substring(20,22));
-                    int ls=Integer.parseInt(last.substring(23,25));
-                    int ds=Integer.parseInt(date.substring(23,25));
+        DateCache dc = new DateCache("EEE, dd MMM yyyy HH:mm:ss zzz ZZZ",Locale.US,TimeZone.getTimeZone("GMT"));
 
-                    // This won't work at midnight!
-                    change|= ds!=ls || dm!=lm || dh!=lh;
-                }
-                last=date;
-            }
-            assertTrue("time changed", change);
+        Thread.sleep(2000);
 
+        long now=System.currentTimeMillis();
+        long end=now+3000;
+        String f=dc.formatNow(now);
+        String last=f;
 
-            // Test string is cached
-            dc = new DateCache();
-            long now = 1000L*(System.currentTimeMillis()%1000L)+123;
-            // format a time for now
-            String s1=dc.format(now);
-            
-            // format a  time in the past (this should not reset cached date)
-            dc.format(now-2000);
-            
-            // format a time a little later than now 
-            String s2=dc.format(now+10);
-            
-            // format a time  in future (this should reset cached data)
-            dc.format(now+2000);
-            
-            // format time a little later than now
-            String s3=dc.format(now+20);
-            
-            assertEquals(s1,s2);
-            assertEquals(s2,s3);
-            assertTrue(s1==s2);
-            assertFalse(s2==s3);
-    }
+        int hits=0;
+        int misses=0;
+
+        while (now<end)
+        {
+            last=f;
+            f=dc.formatNow(now);
+            // System.err.printf("%s %s%n",f,last==f);
+            if (last==f)
+                hits++;
+            else
+                misses++;
 
+            TimeUnit.MILLISECONDS.sleep(100);
+            now=System.currentTimeMillis();
+        }
+        Assert.assertThat(hits,Matchers.greaterThan(misses));
+    }
 }
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/FutureCallbackTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/FutureCallbackTest.java
new file mode 100644
index 0000000..f9af5f2
--- /dev/null
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/FutureCallbackTest.java
@@ -0,0 +1,212 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.hamcrest.Matchers;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class FutureCallbackTest
+{
+    @Test
+    public void testNotDone()
+    {
+        FutureCallback fcb= new FutureCallback();
+        Assert.assertFalse(fcb.isDone());
+        Assert.assertFalse(fcb.isCancelled());
+    }
+    
+    @Test
+    public void testGetNotDone() throws Exception
+    {
+        FutureCallback fcb= new FutureCallback();
+        
+        long start=System.currentTimeMillis();
+        try
+        {
+            fcb.get(500,TimeUnit.MILLISECONDS);
+            Assert.fail();
+        }
+        catch(TimeoutException e)
+        {
+        }
+        Assert.assertThat(System.currentTimeMillis()-start,Matchers.greaterThan(50L));
+    }
+
+    @Test
+    public void testDone() throws Exception
+    {
+        FutureCallback fcb= new FutureCallback();
+        fcb.succeeded();
+        Assert.assertTrue(fcb.isDone());
+        Assert.assertFalse(fcb.isCancelled());
+
+        long start=System.currentTimeMillis();
+        Assert.assertEquals(null,fcb.get());
+        Assert.assertThat(System.currentTimeMillis()-start,Matchers.lessThan(500L));     
+    }
+    
+    @Test
+    public void testGetDone() throws Exception
+    {
+        final FutureCallback fcb= new FutureCallback();
+        final CountDownLatch latch = new CountDownLatch(1);
+        
+        new Thread(new Runnable(){
+            public void run()
+            {
+                latch.countDown();
+                try{TimeUnit.MILLISECONDS.sleep(100);}catch(Exception e){e.printStackTrace();}
+                fcb.succeeded();
+            }
+        }).start();
+        
+        latch.await();
+        long start=System.currentTimeMillis();
+        Assert.assertEquals(null,fcb.get(10000,TimeUnit.MILLISECONDS));
+        Assert.assertThat(System.currentTimeMillis()-start,Matchers.greaterThan(10L)); 
+        Assert.assertThat(System.currentTimeMillis()-start,Matchers.lessThan(1000L)); 
+        
+        Assert.assertTrue(fcb.isDone());
+        Assert.assertFalse(fcb.isCancelled());   
+    }
+    
+
+
+    @Test
+    public void testFailed() throws Exception
+    {
+        FutureCallback fcb= new FutureCallback();
+        Exception ex=new Exception("FAILED");
+        fcb.failed(ex);
+        Assert.assertTrue(fcb.isDone());
+        Assert.assertFalse(fcb.isCancelled());
+
+        long start=System.currentTimeMillis();
+        try
+        {
+            fcb.get();
+            Assert.fail();
+        }
+        catch(ExecutionException ee)
+        {
+            Assert.assertEquals(ex,ee.getCause());
+        }
+        Assert.assertThat(System.currentTimeMillis()-start,Matchers.lessThan(100L));     
+    }
+    
+    @Test
+    public void testGetFailed() throws Exception
+    {
+        final FutureCallback fcb= new FutureCallback();
+        final Exception ex=new Exception("FAILED");
+        final CountDownLatch latch = new CountDownLatch(1);
+        
+        new Thread(new Runnable(){
+            public void run()
+            {
+                latch.countDown();
+                try{TimeUnit.MILLISECONDS.sleep(100);}catch(Exception e){e.printStackTrace();}
+                fcb.failed(ex);
+            }
+        }).start();
+        
+        latch.await();
+        long start=System.currentTimeMillis();
+        try
+        {
+            fcb.get(10000,TimeUnit.MILLISECONDS);
+            Assert.fail();
+        }
+        catch(ExecutionException ee)
+        {
+            Assert.assertEquals(ex,ee.getCause());
+        }
+        Assert.assertThat(System.currentTimeMillis()-start,Matchers.greaterThan(10L)); 
+        Assert.assertThat(System.currentTimeMillis()-start,Matchers.lessThan(1000L));
+
+        Assert.assertTrue(fcb.isDone());
+        Assert.assertFalse(fcb.isCancelled());
+    }
+    
+
+
+    @Test
+    public void testCancelled() throws Exception
+    {
+        FutureCallback fcb= new FutureCallback();
+        fcb.cancel(true);
+        Assert.assertTrue(fcb.isDone());
+        Assert.assertTrue(fcb.isCancelled());
+
+        long start=System.currentTimeMillis();
+        try
+        {
+            fcb.get();
+            Assert.fail();
+        }
+        catch(CancellationException e)
+        {
+            Assert.assertThat(e.getCause(),Matchers.instanceOf(CancellationException.class));
+        }
+        Assert.assertThat(System.currentTimeMillis()-start,Matchers.lessThan(100L));     
+    }
+    
+    @Test
+    public void testGetCancelled() throws Exception
+    {
+        final FutureCallback fcb= new FutureCallback();
+        final CountDownLatch latch = new CountDownLatch(1);
+        
+        new Thread(new Runnable(){
+            public void run()
+            {
+                latch.countDown();
+                try{TimeUnit.MILLISECONDS.sleep(100);}catch(Exception e){e.printStackTrace();}
+                fcb.cancel(true);
+            }
+        }).start();
+        
+        latch.await();
+        long start=System.currentTimeMillis();
+        try
+        {
+            fcb.get(10000,TimeUnit.MILLISECONDS);
+            Assert.fail();
+        }
+        catch(CancellationException e)
+        {
+            Assert.assertThat(e.getCause(),Matchers.instanceOf(CancellationException.class));
+        }
+        Assert.assertThat(System.currentTimeMillis()-start,Matchers.greaterThan(10L)); 
+        Assert.assertThat(System.currentTimeMillis()-start,Matchers.lessThan(1000L));
+
+        Assert.assertTrue(fcb.isDone());
+        Assert.assertTrue(fcb.isCancelled());
+           
+    }
+    
+    
+}
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/IPAddressMapTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/IPAddressMapTest.java
index eed6ddd..91493e5 100644
--- a/jetty-util/src/test/java/org/eclipse/jetty/util/IPAddressMapTest.java
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/IPAddressMapTest.java
@@ -30,56 +30,56 @@ public class IPAddressMapTest
     @Test
     public void testOneAddress()
     {
-        IPAddressMap<String> map = new IPAddressMap();
-     
+        IPAddressMap<String> map = new IPAddressMap<>();
+
         map.put("10.5.2.1","1");
-        
+
         assertNotNull(map.match("10.5.2.1"));
-       
+
         assertNull(map.match("101.5.2.1"));
         assertNull(map.match("10.15.2.1"));
         assertNull(map.match("10.5.22.1"));
         assertNull(map.match("10.5.2.0"));
     }
-    
+
     /* ------------------------------------------------------------ */
     @Test
     public void testOneRange()
     {
-        IPAddressMap<String> map = new IPAddressMap();
-        
+        IPAddressMap<String> map = new IPAddressMap<>();
+
         map.put("1-15.16-31.32-63.64-127","1");
-        
+
         assertNotNull(map.match("7.23.39.71"));
         assertNotNull(map.match("1.16.32.64"));
         assertNotNull(map.match("15.31.63.127"));
-        
+
         assertNull(map.match("16.32.64.128"));
         assertNull(map.match("1.16.32.63"));
         assertNull(map.match("1.16.31.64"));
         assertNull(map.match("1.15.32.64"));
         assertNull(map.match("0.16.32.64"));
     }
-    
+
     /* ------------------------------------------------------------ */
     @Test
     public void testOneMissing()
     {
-        IPAddressMap<String> map = new IPAddressMap();
-        
+        IPAddressMap<String> map = new IPAddressMap<>();
+
         map.put("10.5.2.","1");
 
         assertNotNull(map.match("10.5.2.0"));
         assertNotNull(map.match("10.5.2.128"));
         assertNotNull(map.match("10.5.2.255"));
     }
-    
+
     /* ------------------------------------------------------------ */
     @Test
     public void testTwoMissing()
     {
-        IPAddressMap<String> map = new IPAddressMap();
-        
+        IPAddressMap<String> map = new IPAddressMap<>();
+
         map.put("10.5.","1");
 
         assertNotNull(map.match("10.5.2.0"));
@@ -89,13 +89,13 @@ public class IPAddressMapTest
         assertNotNull(map.match("10.5.128.1"));
         assertNotNull(map.match("10.5.255.1"));
     }
-    
+
     /* ------------------------------------------------------------ */
     @Test
     public void testThreeMissing()
     {
-        IPAddressMap<String> map = new IPAddressMap();
-        
+        IPAddressMap<String> map = new IPAddressMap<>();
+
         map.put("10.","1");
 
         assertNotNull(map.match("10.5.2.0"));
@@ -108,46 +108,46 @@ public class IPAddressMapTest
         assertNotNull(map.match("10.128.1.1"));
         assertNotNull(map.match("10.255.1.1"));
     }
-    
+
     /* ------------------------------------------------------------ */
     @Test
     public void testOneMixed()
     {
-        IPAddressMap<String> map = new IPAddressMap();
-        
+        IPAddressMap<String> map = new IPAddressMap<>();
+
         map.put("0-15,21.10,16-31.0-15,32-63.-95,128-","1");
-        
+
         assertNotNull(map.match("7.23.39.46"));
         assertNotNull(map.match("10.20.10.150"));
         assertNotNull(map.match("21.10.32.255"));
         assertNotNull(map.match("21.10.15.0"));
-        
+
         assertNull(map.match("16.15.20.100"));
         assertNull(map.match("15.10.63.100"));
         assertNull(map.match("15.10.64.128"));
         assertNull(map.match("15.11.32.95"));
         assertNull(map.match("16.31.63.128"));
     }
-    
+
     /* ------------------------------------------------------------ */
     @Test
     public void testManyMixed()
     {
-        IPAddressMap<String> map = new IPAddressMap();
-        
+        IPAddressMap<String> map = new IPAddressMap<>();
+
         map.put("10.5.2.1","1");
         map.put("1-15.16-31.32-63.64-127","2");
         map.put("1-15,21.10,16-31.0-15,32-63.-55,195-","3");
         map.put("44.99.99.","4");
         map.put("55.99.","5");
         map.put("66.","6");
-        
+
         assertEquals("1", map.match("10.5.2.1"));
-        
+
         assertEquals("2", map.match("7.23.39.71"));
         assertEquals("2", map.match("1.16.32.64"));
         assertEquals("2", map.match("15.31.63.127"));
-        
+
         assertEquals("3", map.match("7.23.39.46"));
         assertEquals("3", map.match("10.20.10.200"));
         assertEquals("3", map.match("21.10.32.255"));
@@ -161,13 +161,13 @@ public class IPAddressMapTest
         assertNull(map.match("10.15.2.1"));
         assertNull(map.match("10.5.22.1"));
         assertNull(map.match("10.5.2.0"));
-        
+
         assertNull(map.match("16.32.64.96"));
         assertNull(map.match("1.16.32.194"));
         assertNull(map.match("1.16.31.64"));
         assertNull(map.match("1.15.32.64"));
         assertNull(map.match("0.16.32.64"));
-        
+
         assertNull(map.match("16.15.20.100"));
         assertNull(map.match("15.10.63.100"));
         assertNull(map.match("15.10.64.128"));
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/IteratingCallbackTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/IteratingCallbackTest.java
new file mode 100644
index 0000000..cc57ea5
--- /dev/null
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/IteratingCallbackTest.java
@@ -0,0 +1,300 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
+import org.eclipse.jetty.util.thread.Scheduler;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class IteratingCallbackTest
+{
+    static Scheduler scheduler = new ScheduledExecutorScheduler();
+   
+    @BeforeClass
+    public static void beforeClass() throws Exception
+    {
+        scheduler.start();
+    }
+    
+    @AfterClass
+    public static void afterClass() throws Exception
+    {
+        scheduler.stop();
+    }
+    
+    @Test
+    public void testNonWaitingProcess() throws Exception
+    {
+        
+        TestCB cb=new TestCB()
+        {
+            int i=10;
+            
+            @Override
+            protected Action process() throws Exception
+            {
+                processed++;
+                if (i-->1)      
+                {
+                    succeeded(); // fake a completed IO operation
+                    return Action.SCHEDULED;
+                }
+                return Action.SUCCEEDED;
+            }
+        };
+        
+        cb.iterate();
+        Assert.assertTrue(cb.waitForComplete()); 
+        Assert.assertEquals(10,cb.processed);
+    }
+
+
+
+    @Test
+    public void testWaitingProcess() throws Exception
+    {
+        TestCB cb=new TestCB()
+        {
+            int i=4;
+            
+            @Override
+            protected Action process() throws Exception
+            {
+                processed++;
+                if (i-->1)      
+                {
+                    scheduler.schedule(successTask,50,TimeUnit.MILLISECONDS);
+                    return Action.SCHEDULED;
+                }
+                return Action.SUCCEEDED;
+            }
+        };
+        
+        cb.iterate();
+        
+        Assert.assertTrue(cb.waitForComplete());
+         
+        Assert.assertEquals(4,cb.processed);
+    }
+
+    @Test
+    public void testWaitingProcessSpuriousInterate() throws Exception
+    {
+        final TestCB cb=new TestCB()
+        {
+            int i=4;
+            
+            @Override
+            protected Action process() throws Exception
+            {
+                processed++;
+                if (i-->1)      
+                {
+                    scheduler.schedule(successTask,50,TimeUnit.MILLISECONDS);
+                    return Action.SCHEDULED;
+                }
+                return Action.SUCCEEDED;
+            }
+        };
+        
+        cb.iterate();
+        scheduler.schedule(new Runnable()
+        {
+            @Override
+            public void run()
+            {
+                cb.iterate();
+                if (!cb.isSucceeded())
+                    scheduler.schedule(this,50,TimeUnit.MILLISECONDS);
+            }
+        },49,TimeUnit.MILLISECONDS);
+        
+        Assert.assertTrue(cb.waitForComplete());
+         
+        Assert.assertEquals(4,cb.processed);
+    }
+
+    @Test
+    public void testNonWaitingProcessFailure() throws Exception
+    {
+        TestCB cb=new TestCB()
+        {
+            int i=10;
+            
+            @Override
+            protected Action process() throws Exception
+            {
+                processed++;
+                if (i-->1)      
+                {
+                    if (i>5)
+                        succeeded(); // fake a completed IO operation
+                    else
+                        failed(new Exception("testing"));
+                    return Action.SCHEDULED;
+                }
+                return Action.SUCCEEDED;
+            }
+        };
+        
+        cb.iterate();
+        Assert.assertFalse(cb.waitForComplete()); 
+        Assert.assertEquals(5,cb.processed);
+    }
+
+    @Test
+    public void testWaitingProcessFailure() throws Exception
+    {
+        TestCB cb=new TestCB()
+        {
+            int i=4;
+            
+            @Override
+            protected Action process() throws Exception
+            {
+                processed++;
+                if (i-->1)      
+                {
+                    scheduler.schedule(i>2?successTask:failTask,50,TimeUnit.MILLISECONDS);
+                    return Action.SCHEDULED;
+                }
+                return Action.SUCCEEDED;
+            }
+        };
+        
+        cb.iterate();
+        
+        Assert.assertFalse(cb.waitForComplete());
+        Assert.assertEquals(2,cb.processed);
+    }
+    
+
+    @Test
+    public void testIdleWaiting() throws Exception
+    {
+        final CountDownLatch idle = new CountDownLatch(1);
+        
+        TestCB cb=new TestCB()
+        {
+            int i=5;
+            
+            @Override
+            protected Action process()
+            {
+                processed++;
+                
+                switch(i--)
+                {
+                    case 5:
+                        succeeded();
+                        return Action.SCHEDULED;
+                        
+                    case 4:
+                        scheduler.schedule(successTask,5,TimeUnit.MILLISECONDS);
+                        return Action.SCHEDULED;
+                        
+                    case 3:
+                        scheduler.schedule(new Runnable()
+                        {
+                            @Override
+                            public void run()
+                            {
+                                idle.countDown();
+                            }
+                        },5,TimeUnit.MILLISECONDS);
+                        return Action.IDLE;
+
+                    case 2:
+                        succeeded();
+                        return Action.SCHEDULED;
+                        
+                    case 1:
+                        scheduler.schedule(successTask,5,TimeUnit.MILLISECONDS);
+                        return Action.SCHEDULED;
+                        
+                    case 0:
+                        return Action.SUCCEEDED;
+                        
+                    default: 
+                        throw new IllegalStateException();
+                    
+                }
+            }
+        };
+        
+        cb.iterate();
+        idle.await(10,TimeUnit.SECONDS);
+        Assert.assertTrue(cb.isIdle());
+        
+        cb.iterate();
+        Assert.assertTrue(cb.waitForComplete());
+        Assert.assertEquals(6,cb.processed);
+    }
+    
+    
+    
+    private abstract static class TestCB extends IteratingCallback
+    {
+        CountDownLatch completed = new CountDownLatch(1);
+        int processed=0;
+
+        @Override
+        protected void onCompleteSuccess()
+        {
+            completed.countDown();
+        }
+        
+        @Override
+        public void onCompleteFailure(Throwable x)
+        {
+            completed.countDown();
+        }
+
+        boolean waitForComplete() throws InterruptedException
+        {
+            completed.await(10,TimeUnit.SECONDS);
+            return isSucceeded();
+        }
+        
+        Runnable successTask = new Runnable()
+        {
+            @Override
+            public void run()
+            {
+                succeeded();
+            }
+        };
+        Runnable failTask = new Runnable()
+        {
+            @Override
+            public void run()
+            {
+                failed(new Exception("testing failure"));
+            }
+        };
+    }
+
+}
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/LazyListTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/LazyListTest.java
index 7f766ea..ad31674 100644
--- a/jetty-util/src/test/java/org/eclipse/jetty/util/LazyListTest.java
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/LazyListTest.java
@@ -42,7 +42,7 @@ import org.junit.Test;
 public class LazyListTest
 {
     public static final boolean STRICT = false;
-    
+
     /**
      * Tests for {@link LazyList#add(Object, Object)}
      */
@@ -54,7 +54,7 @@ public class LazyListTest
         assertTrue(list instanceof List);
         assertEquals(1,LazyList.size(list));
     }
-    
+
     /**
      * Tests for {@link LazyList#add(Object, Object)}
      */
@@ -79,12 +79,12 @@ public class LazyListTest
         Object item = LazyList.add(null, "x");
         item = LazyList.add(item,"y");
         item = LazyList.add(item,"z");
-        
+
         Object list = LazyList.add(null, item);
         assertNotNull(list);
         assertTrue(list instanceof List);
         assertEquals(1,LazyList.size(list));
-        
+
         Object val = LazyList.get(list, 0);
         assertTrue(val instanceof List);
     }
@@ -96,7 +96,7 @@ public class LazyListTest
     public void testAddObjectObject_NonListInput()
     {
         String input = "a";
-        
+
         Object list = LazyList.add(input, "b");
         assertNotNull(list);
         assertTrue(list instanceof List);
@@ -110,18 +110,18 @@ public class LazyListTest
     public void testAddObjectObject_LazyListInput()
     {
         Object input = LazyList.add(null, "a");
-        
+
         Object list = LazyList.add(input, "b");
         assertEquals(2,LazyList.size(list));
         assertEquals("a",LazyList.get(list,0));
-        
+
         list=LazyList.add(list, "c");
         assertEquals(3,LazyList.size(list));
         assertEquals("a",LazyList.get(list,0));
         assertEquals("b",LazyList.get(list,1));
         assertEquals("c",LazyList.get(list,2));
     }
-    
+
     /**
      * Tests for {@link LazyList#add(Object, Object)}
      */
@@ -130,11 +130,11 @@ public class LazyListTest
     {
         List<String> input = new ArrayList<String>();
         input.add("a");
-        
+
         Object list = LazyList.add(input, "b");
         assertEquals(2,LazyList.size(list));
         assertEquals("a",LazyList.get(list,0));
-        
+
         list=LazyList.add(list, "c");
         assertEquals(3,LazyList.size(list));
         assertEquals("a",LazyList.get(list,0));
@@ -152,18 +152,18 @@ public class LazyListTest
         list=LazyList.add(list, null);
         assertEquals(1,LazyList.size(list));
         assertEquals(null,LazyList.get(list,0));
-        
+
         list="a";
         list=LazyList.add(list, null);
         assertEquals(2,LazyList.size(list));
         assertEquals("a",LazyList.get(list,0));
         assertEquals(null,LazyList.get(list,1));
-        
+
         list=LazyList.add(list, null);
         assertEquals(3,LazyList.size(list));
         assertEquals("a",LazyList.get(list,0));
         assertEquals(null,LazyList.get(list,1));
-        assertEquals(null,LazyList.get(list,2)); 
+        assertEquals(null,LazyList.get(list,2));
     }
 
     /**
@@ -200,7 +200,7 @@ public class LazyListTest
     public void testAddObjectIntObject_NullInput_NonListItem2()
     {
         Assume.assumeTrue(STRICT); // Only run in STRICT mode.
-        
+
         String item = "a";
         // Test branch of logic "index>0"
         Object list = LazyList.add(null, 1, item); // Always throws exception?
@@ -218,15 +218,15 @@ public class LazyListTest
         Object item = LazyList.add(null, "x");
         item = LazyList.add(item,"y");
         item = LazyList.add(item,"z");
-        
+
         Object list = LazyList.add(null, 0, item);
         assertNotNull(list);
         assertEquals(1,LazyList.size(list));
-        
+
         Object val = LazyList.get(list, 0);
         assertTrue(val instanceof List);
     }
-    
+
     /**
      * Test for {@link LazyList#add(Object, int, Object)}
      */
@@ -235,7 +235,7 @@ public class LazyListTest
     {
         List<String> item = new ArrayList<String>();
         item.add("a");
-        
+
         Object list = LazyList.add(null, 0, item);
         assertNotNull(list);
         assertTrue(list instanceof List);
@@ -249,7 +249,7 @@ public class LazyListTest
     public void testAddObjectIntObject_NonListInput_NullItem()
     {
         String input = "a";
-        
+
         Object list = LazyList.add(input, 0, null);
         assertNotNull(list);
         assertEquals(2,LazyList.size(list));
@@ -265,7 +265,7 @@ public class LazyListTest
     {
         String input = "a";
         String item = "b";
-        
+
         Object list = LazyList.add(input, 0, item);
         assertNotNull(list);
         assertEquals(2, LazyList.size(list));
@@ -283,14 +283,14 @@ public class LazyListTest
         list=LazyList.add(list,0,"a"); // [a, c]
         list=LazyList.add(list,1,"b"); // [a, b, c]
         list=LazyList.add(list,3,"d"); // [a, b, c, d]
-        
+
         assertEquals(4,LazyList.size(list));
         assertEquals("a",LazyList.get(list,0));
         assertEquals("b",LazyList.get(list,1));
         assertEquals("c",LazyList.get(list,2));
         assertEquals("d",LazyList.get(list,3));
     }
-    
+
     /**
      * Test for {@link LazyList#addCollection(Object, java.util.Collection)}
      */
@@ -298,7 +298,7 @@ public class LazyListTest
     public void testAddCollection_NullInput()
     {
         Collection<?> coll = Arrays.asList("a","b","c");
-        
+
         Object list = LazyList.addCollection(null,coll);
         assertTrue(list instanceof List);
         assertEquals(3, LazyList.size(list));
@@ -306,7 +306,7 @@ public class LazyListTest
         assertEquals("b",LazyList.get(list,1));
         assertEquals("c",LazyList.get(list,2));
     }
-    
+
     /**
      * Test for {@link LazyList#addCollection(Object, java.util.Collection)}
      */
@@ -315,7 +315,7 @@ public class LazyListTest
     {
         Collection<?> coll = Arrays.asList("a","b","c");
         String input = "z";
-        
+
         Object list = LazyList.addCollection(input,coll);
         assertTrue(list instanceof List);
         assertEquals(4, LazyList.size(list));
@@ -332,11 +332,11 @@ public class LazyListTest
     public void testAddCollection_LazyListInput()
     {
         Collection<?> coll = Arrays.asList("a","b","c");
-        
+
         Object input = LazyList.add(null, "x");
         input = LazyList.add(input, "y");
         input = LazyList.add(input, "z");
-        
+
         Object list = LazyList.addCollection(input,coll);
         assertTrue(list instanceof List);
         assertEquals(6, LazyList.size(list));
@@ -355,12 +355,12 @@ public class LazyListTest
     public void testAddCollection_GenricListInput()
     {
         Collection<?> coll = Arrays.asList("a","b","c");
-        
+
         List<String> input = new ArrayList<String>();
         input.add("x");
         input.add("y");
         input.add("z");
-        
+
         Object list = LazyList.addCollection(input,coll);
         assertTrue(list instanceof List);
         assertEquals(6, LazyList.size(list));
@@ -383,7 +383,7 @@ public class LazyListTest
         Object list = null;
         list = LazyList.addCollection(list,coll);
         list = LazyList.addCollection(list,coll);
-        
+
         assertEquals(4,LazyList.size(list));
         assertEquals("a",LazyList.get(list,0));
         assertEquals("b",LazyList.get(list,1));
@@ -404,14 +404,14 @@ public class LazyListTest
         Object list=null;
         list=LazyList.addCollection(list,l);
         list=LazyList.addCollection(list,l);
-        
+
         assertEquals(4,LazyList.size(list));
         assertEquals("a",LazyList.get(list,0));
         assertEquals("b",LazyList.get(list,1));
         assertEquals("a",LazyList.get(list,2));
         assertEquals("b",LazyList.get(list,3));
     }
-    
+
     /**
      * Tests for {@link LazyList#addArray(Object, Object[])}
      */
@@ -546,7 +546,7 @@ public class LazyListTest
         Object input = LazyList.add(null,"x");
         input = LazyList.add(input,"y");
         input = LazyList.add(input,"z");
-        
+
         String arr[] = null;
         Object list = LazyList.addArray(input,arr);
         assertNotNull(list);
@@ -631,7 +631,7 @@ public class LazyListTest
         input.add("x");
         input.add("y");
         input.add("z");
-        
+
         String arr[] = null;
         Object list = LazyList.addArray(input,arr);
         assertNotNull(list);
@@ -720,7 +720,7 @@ public class LazyListTest
         assertTrue(list instanceof List);
         // Not possible to test for List capacity value.
     }
-    
+
     /**
      * Tests for {@link LazyList#ensureSize(Object, int)}
      */
@@ -744,7 +744,7 @@ public class LazyListTest
     {
         Object input = LazyList.add(null, "a");
         input = LazyList.add(input,"b");
-        
+
         Object list = LazyList.ensureSize(input,10);
         assertNotNull(list);
         assertTrue(list instanceof List);
@@ -753,7 +753,7 @@ public class LazyListTest
         assertEquals("a", LazyList.get(list,0));
         assertEquals("b", LazyList.get(list,1));
     }
-    
+
     /**
      * Tests for {@link LazyList#ensureSize(Object, int)}
      */
@@ -763,7 +763,7 @@ public class LazyListTest
         List<String> input = new ArrayList<String>();
         input.add("a");
         input.add("b");
-        
+
         Object list = LazyList.ensureSize(input,10);
         assertNotNull(list);
         assertTrue(list instanceof List);
@@ -780,13 +780,13 @@ public class LazyListTest
     public void testEnsureSize_GenericListInput_LinkedList()
     {
         Assume.assumeTrue(STRICT); // Only run in STRICT mode.
-        
-        // Using LinkedList concrete type as LazyList internal 
+
+        // Using LinkedList concrete type as LazyList internal
         // implementation does not look for this specifically.
         List<String> input = new LinkedList<String>();
         input.add("a");
         input.add("b");
-        
+
         Object list = LazyList.ensureSize(input,10);
         assertNotNull(list);
         assertTrue(list instanceof List);
@@ -806,17 +806,17 @@ public class LazyListTest
         l.add("a");
         l.add("b");
         l.add("c");
-        
+
         // NOTE: Testing for object equality might be viewed as
         //       fragile by most developers, however, for this
         //       specific implementation, we don't want the
         //       provided list to change if the size requirements
         //       have been met.
-        
+
         // Trigger growth
         Object ret = LazyList.ensureSize(l,10);
         assertTrue("Should have returned a new list object", ret != l);
-        
+
         // Growth not neeed.
         ret = LazyList.ensureSize(l,1);
         assertTrue("Should have returned same list object", ret == l);
@@ -829,25 +829,25 @@ public class LazyListTest
     public void testEnsureSize_Growth_LinkedList()
     {
         Assume.assumeTrue(STRICT); // Only run in STRICT mode.
-        
-        // Using LinkedList concrete type as LazyList internal 
-        // implementation has not historically looked for this 
+
+        // Using LinkedList concrete type as LazyList internal
+        // implementation has not historically looked for this
         // specifically.
         List<String> l = new LinkedList<String>();
         l.add("a");
         l.add("b");
         l.add("c");
-        
+
         // NOTE: Testing for object equality might be viewed as
         //       fragile by most developers, however, for this
         //       specific implementation, we don't want the
         //       provided list to change if the size requirements
         //       have been met.
-        
+
         // Trigger growth
         Object ret = LazyList.ensureSize(l,10);
         assertTrue("Should have returned a new list object", ret != l);
-        
+
         // Growth not neeed.
         ret = LazyList.ensureSize(l,1);
         assertTrue("Should have returned same list object", ret == l);
@@ -860,13 +860,13 @@ public class LazyListTest
     public void testRemoveObjectObject_NullInput()
     {
         Object input = null;
-        
+
         assertNull(LazyList.remove(input,null));
         assertNull(LazyList.remove(input,"a"));
         assertNull(LazyList.remove(input,new ArrayList<Object>()));
         assertNull(LazyList.remove(input,Integer.valueOf(42)));
     }
-    
+
     /**
      * Test for {@link LazyList#remove(Object, Object)}
      */
@@ -874,7 +874,7 @@ public class LazyListTest
     public void testRemoveObjectObject_NonListInput()
     {
         String input = "a";
-        
+
         // Remove null item
         Object list = LazyList.remove(input, null);
         assertNotNull(list);
@@ -882,7 +882,7 @@ public class LazyListTest
             assertTrue(list instanceof List);
         }
         assertEquals(1, LazyList.size(list));
-        
+
         // Remove item that doesn't exist
         list = LazyList.remove(input, "b");
         assertNotNull(list);
@@ -907,13 +907,13 @@ public class LazyListTest
         Object input = LazyList.add(null, "a");
         input = LazyList.add(input, "b");
         input = LazyList.add(input, "c");
-        
+
         // Remove null item
         Object list = LazyList.remove(input, null);
         assertNotNull(list);
         assertTrue(list instanceof List);
         assertEquals(3, LazyList.size(list));
-        
+
         // Attempt to remove something that doesn't exist
         list = LazyList.remove(input, "z");
         assertNotNull(list);
@@ -928,7 +928,7 @@ public class LazyListTest
         assertEquals("a", LazyList.get(list, 0));
         assertEquals("c", LazyList.get(list, 1));
     }
-    
+
     /**
      * Test for {@link LazyList#remove(Object, Object)}
      */
@@ -939,14 +939,14 @@ public class LazyListTest
         input.add("a");
         input.add("b");
         input.add("c");
-        
+
         // Remove null item
         Object list = LazyList.remove(input, null);
         assertNotNull(list);
         assertTrue(list instanceof List);
         assertTrue("Should not have recreated list obj", input == list);
         assertEquals(3, LazyList.size(list));
-        
+
         // Attempt to remove something that doesn't exist
         list = LazyList.remove(input, "z");
         assertNotNull(list);
@@ -962,13 +962,13 @@ public class LazyListTest
         assertEquals(2, LazyList.size(list));
         assertEquals("a", LazyList.get(list, 0));
         assertEquals("c", LazyList.get(list, 1));
-        
+
         // Try to remove the rest.
         list = LazyList.remove(list,"a");
         list = LazyList.remove(list,"c");
         assertNull(list);
     }
-    
+
     /**
      * Test for {@link LazyList#remove(Object, Object)}
      */
@@ -980,14 +980,14 @@ public class LazyListTest
         input.add("a");
         input.add("b");
         input.add("c");
-        
+
         // Remove null item
         Object list = LazyList.remove(input, null);
         assertNotNull(list);
         assertTrue(list instanceof List);
         assertTrue("Should not have recreated list obj", input == list);
         assertEquals(3, LazyList.size(list));
-        
+
         // Attempt to remove something that doesn't exist
         list = LazyList.remove(input, "z");
         assertNotNull(list);
@@ -1012,12 +1012,12 @@ public class LazyListTest
     public void testRemoveObjectInt_NullInput()
     {
         Object input = null;
-        
+
         assertNull(LazyList.remove(input,0));
         assertNull(LazyList.remove(input,2));
         assertNull(LazyList.remove(input,-2));
     }
-    
+
     /**
      * Tests for {@link LazyList#remove(Object, int)}
      */
@@ -1025,7 +1025,7 @@ public class LazyListTest
     public void testRemoveObjectInt_NonListInput()
     {
         String input = "a";
-        
+
         // Invalid index
         Object list = LazyList.remove(input, 1);
         assertNotNull(list);
@@ -1033,7 +1033,7 @@ public class LazyListTest
             assertTrue(list instanceof List);
         }
         assertEquals(1, LazyList.size(list));
-        
+
         // Valid index
         list = LazyList.remove(input, 0);
         // TODO: should this be null? or an empty list?
@@ -1050,9 +1050,9 @@ public class LazyListTest
         Object input = LazyList.add(null, "a");
         input = LazyList.add(input, "b");
         input = LazyList.add(input, "c");
-        
+
         Object list = null;
-        
+
         if (STRICT)
         {
             // Invalid index
@@ -1064,7 +1064,7 @@ public class LazyListTest
             assertTrue(list instanceof List);
             assertEquals(3, LazyList.size(list));
         }
-        
+
         // Valid index
         list = LazyList.remove(input, 1); // remove the 'b'
         assertNotNull(list);
@@ -1084,9 +1084,9 @@ public class LazyListTest
         input.add("a");
         input.add("b");
         input.add("c");
-        
+
         Object list = null;
-        
+
         if (STRICT)
         {
             // Invalid index
@@ -1098,7 +1098,7 @@ public class LazyListTest
             assertTrue(list instanceof List);
             assertEquals(3, LazyList.size(list));
         }
-        
+
         // Valid index
         list = LazyList.remove(input, 1); // remove the 'b'
         assertNotNull(list);
@@ -1106,7 +1106,7 @@ public class LazyListTest
         assertEquals(2, LazyList.size(list));
         assertEquals("a", LazyList.get(list, 0));
         assertEquals("c", LazyList.get(list, 1));
-        
+
         // Remove the rest
         list = LazyList.remove(list, 0); // the 'a'
         list = LazyList.remove(list, 0); // the 'c'
@@ -1120,13 +1120,13 @@ public class LazyListTest
     public void testGetListObject_NullInput()
     {
         Object input = null;
-        
+
         Object list = LazyList.getList(input);
         assertNotNull(list);
         assertTrue(list instanceof List);
         assertEquals(0, LazyList.size(list));
     }
-    
+
     /**
      * Test for {@link LazyList#getList(Object)}
      */
@@ -1134,7 +1134,7 @@ public class LazyListTest
     public void testGetListObject_NonListInput()
     {
         String input = "a";
-        
+
         Object list = LazyList.getList(input);
         assertNotNull(list);
         assertTrue(list instanceof List);
@@ -1150,7 +1150,7 @@ public class LazyListTest
         Object input = LazyList.add(null, "a");
         input = LazyList.add(input, "b");
         input = LazyList.add(input, "c");
-        
+
         Object list = LazyList.getList(input);
         assertNotNull(list);
         assertTrue(list instanceof List);
@@ -1170,7 +1170,7 @@ public class LazyListTest
         input.add("a");
         input.add("b");
         input.add("c");
-        
+
         Object list = LazyList.getList(input);
         assertNotNull(list);
         assertTrue(list instanceof List);
@@ -1190,7 +1190,7 @@ public class LazyListTest
         input.add("a");
         input.add("b");
         input.add("c");
-        
+
         Object list = LazyList.getList(input);
         assertNotNull(list);
         assertTrue(list instanceof List);
@@ -1210,7 +1210,7 @@ public class LazyListTest
         assertNull(LazyList.getList(null, true));
         assertNotNull(LazyList.getList(null, false));
     }
-    
+
     /**
      * Tests for {@link LazyList#toStringArray(Object)}
      */
@@ -1219,31 +1219,31 @@ public class LazyListTest
     public void testToStringArray()
     {
         assertEquals(0,LazyList.toStringArray(null).length);
-        
+
         assertEquals(1,LazyList.toStringArray("a").length);
         assertEquals("a",LazyList.toStringArray("a")[0]);
-        
+
         @SuppressWarnings("rawtypes")
         ArrayList l=new ArrayList();
         l.add("a");
         l.add(null);
         l.add(new Integer(2));
         String[] a=LazyList.toStringArray(l);
-        
+
         assertEquals(3,a.length);
         assertEquals("a",a[0]);
         assertEquals(null,a[1]);
         assertEquals("2",a[2]);
-        
+
     }
-    
+
     /**
      * Tests for {@link LazyList#toArray(Object, Class)}
      */
     @Test
     public void testToArray_NullInput_Object() {
         Object input = null;
-        
+
         Object arr = LazyList.toArray(input,Object.class);
         assertNotNull(arr);
         assertTrue(arr.getClass().isArray());
@@ -1255,7 +1255,7 @@ public class LazyListTest
     @Test
     public void testToArray_NullInput_String() {
         String input = null;
-        
+
         Object arr = LazyList.toArray(input,String.class);
         assertNotNull(arr);
         assertTrue(arr.getClass().isArray());
@@ -1268,12 +1268,12 @@ public class LazyListTest
     @Test
     public void testToArray_NonListInput() {
         String input = "a";
-        
+
         Object arr = LazyList.toArray(input,String.class);
         assertNotNull(arr);
         assertTrue(arr.getClass().isArray());
         assertTrue(arr instanceof String[]);
-        
+
         String strs[] = (String[])arr;
         assertEquals(1, strs.length);
         assertEquals("a", strs[0]);
@@ -1287,12 +1287,12 @@ public class LazyListTest
         Object input = LazyList.add(null, "a");
         input = LazyList.add(input, "b");
         input = LazyList.add(input, "c");
-        
+
         Object arr = LazyList.toArray(input,String.class);
         assertNotNull(arr);
         assertTrue(arr.getClass().isArray());
         assertTrue(arr instanceof String[]);
-        
+
         String strs[] = (String[])arr;
         assertEquals(3, strs.length);
         assertEquals("a", strs[0]);
@@ -1309,12 +1309,12 @@ public class LazyListTest
         input = LazyList.add(input, 333);
         input = LazyList.add(input, 4444);
         input = LazyList.add(input, 55555);
-        
+
         Object arr = LazyList.toArray(input,int.class);
         assertNotNull(arr);
         assertTrue(arr.getClass().isArray());
         assertTrue(arr instanceof int[]);
-        
+
         int nums[] = (int[])arr;
         assertEquals(4, nums.length);
         assertEquals(22, nums[0]);
@@ -1332,12 +1332,12 @@ public class LazyListTest
         input.add("a");
         input.add("b");
         input.add("c");
-        
+
         Object arr = LazyList.toArray(input,String.class);
         assertNotNull(arr);
         assertTrue(arr.getClass().isArray());
         assertTrue(arr instanceof String[]);
-        
+
         String strs[] = (String[])arr;
         assertEquals(3, strs.length);
         assertEquals("a", strs[0]);
@@ -1353,7 +1353,7 @@ public class LazyListTest
     {
         assertEquals(0, LazyList.size(null));
     }
-    
+
     /**
      * Tests for {@link LazyList#size(Object)}
      */
@@ -1372,11 +1372,11 @@ public class LazyListTest
     {
         Object input = LazyList.add(null,"a");
         input = LazyList.add(input,"b");
-        
+
         assertEquals(2, LazyList.size(input));
-        
+
         input = LazyList.add(input,"c");
-    
+
         assertEquals(3, LazyList.size(input));
     }
 
@@ -1392,11 +1392,11 @@ public class LazyListTest
 
         input.add("a");
         input.add("b");
-        
+
         assertEquals(2, LazyList.size(input));
-        
+
         input.add("c");
-    
+
         assertEquals(3, LazyList.size(input));
     }
 
@@ -1469,13 +1469,13 @@ public class LazyListTest
         List<String> input = new ArrayList<String>();
         input.add("a");
         assertEquals("a",LazyList.get(input,0));
-        
+
         List<URI> uris = new ArrayList<URI>();
         uris.add(URI.create("http://www.mortbay.org/"));
         uris.add(URI.create("http://jetty.codehaus.org/jetty/"));
         uris.add(URI.create("http://www.intalio.com/jetty/"));
         uris.add(URI.create("http://www.eclipse.org/jetty/"));
-        
+
         // Make sure that Generics pass through the 'get' routine safely.
         // We should be able to call this without casting the result to URI
         URI eclipseUri = LazyList.get(uris, 3);
@@ -1490,7 +1490,7 @@ public class LazyListTest
     {
         assertFalse(LazyList.contains(null, "z"));
     }
-    
+
     /**
      * Tests for {@link LazyList#contains(Object, Object)}
      */
@@ -1511,7 +1511,7 @@ public class LazyListTest
         Object input = LazyList.add(null,"a");
         input = LazyList.add(input,"b");
         input = LazyList.add(input,"c");
-        
+
         assertFalse(LazyList.contains(input, "z"));
         assertTrue(LazyList.contains(input, "a"));
         assertTrue(LazyList.contains(input, "b"));
@@ -1527,12 +1527,12 @@ public class LazyListTest
         input.add("a");
         input.add("b");
         input.add("c");
-        
+
         assertFalse(LazyList.contains(input, "z"));
         assertTrue(LazyList.contains(input, "a"));
         assertTrue(LazyList.contains(input, "b"));
     }
-    
+
     /**
      * Tests for {@link LazyList#clone(Object)}
      */
@@ -1540,7 +1540,7 @@ public class LazyListTest
     public void testClone_NullInput()
     {
         Object input = null;
-        
+
         Object list = LazyList.clone(input);
         assertNull(list);
     }
@@ -1552,12 +1552,12 @@ public class LazyListTest
     public void testClone_NonListInput()
     {
         String input = "a";
-        
+
         Object list = LazyList.clone(input);
         assertNotNull(list);
         assertTrue("Should be the same object", input == list);
     }
-    
+
     /**
      * Tests for {@link LazyList#clone(Object)}
      */
@@ -1567,7 +1567,7 @@ public class LazyListTest
         Object input = LazyList.add(null,"a");
         input = LazyList.add(input,"b");
         input = LazyList.add(input,"c");
-        
+
         Object list = LazyList.clone(input);
         assertNotNull(list);
         assertTrue("Should be a List object", list instanceof List);
@@ -1588,7 +1588,7 @@ public class LazyListTest
         input.add("a");
         input.add("b");
         input.add("c");
-        
+
         // TODO: decorate the .clone(Object) method to return
         //       the same generic object element type
         Object list = LazyList.clone(input);
@@ -1600,7 +1600,7 @@ public class LazyListTest
         assertEquals("b", LazyList.get(list,1));
         assertEquals("c", LazyList.get(list,2));
     }
-    
+
     /**
      * Tests for {@link LazyList#toString(Object)}
      */
@@ -1610,7 +1610,7 @@ public class LazyListTest
         Object input = null;
         assertEquals("[]", LazyList.toString(input));
     }
-    
+
     /**
      * Tests for {@link LazyList#toString(Object)}
      */
@@ -1637,7 +1637,7 @@ public class LazyListTest
         assertEquals("[a, b, c]", LazyList.toString(input));
     }
 
-    
+
     /**
      * Tests for {@link LazyList#toString(Object)}
      */
@@ -1654,7 +1654,7 @@ public class LazyListTest
 
         assertEquals("[a, b, c]", LazyList.toString(input));
     }
-    
+
     /**
      * Tests for {@link LazyList#iterator(Object)}
      */
@@ -1665,7 +1665,7 @@ public class LazyListTest
         assertNotNull(iter);
         assertFalse(iter.hasNext());
     }
-    
+
     /**
      * Tests for {@link LazyList#iterator(Object)}
      */
@@ -1673,7 +1673,7 @@ public class LazyListTest
     public void testIterator_NonListInput()
     {
         String input = "a";
-        
+
         Iterator<?> iter = LazyList.iterator(input);
         assertNotNull(iter);
         assertTrue(iter.hasNext());
@@ -1690,7 +1690,7 @@ public class LazyListTest
         Object input = LazyList.add(null,"a");
         input = LazyList.add(input,"b");
         input = LazyList.add(input,"c");
-        
+
         Iterator<?> iter = LazyList.iterator(input);
         assertNotNull(iter);
         assertTrue(iter.hasNext());
@@ -1710,7 +1710,7 @@ public class LazyListTest
         input.add("a");
         input.add("b");
         input.add("c");
-        
+
         Iterator<String> iter = LazyList.iterator(input);
         assertNotNull(iter);
         assertTrue(iter.hasNext());
@@ -1731,7 +1731,7 @@ public class LazyListTest
         assertFalse(iter.hasNext());
         assertFalse(iter.hasPrevious());
     }
-    
+
     /**
      * Tests for {@link LazyList#listIterator(Object)}
      */
@@ -1739,7 +1739,7 @@ public class LazyListTest
     public void testListIterator_NonListInput()
     {
         String input = "a";
-        
+
         ListIterator<?> iter = LazyList.listIterator(input);
         assertNotNull(iter);
         assertTrue(iter.hasNext());
@@ -1758,7 +1758,7 @@ public class LazyListTest
         Object input = LazyList.add(null,"a");
         input = LazyList.add(input,"b");
         input = LazyList.add(input,"c");
-        
+
         ListIterator<?> iter = LazyList.listIterator(input);
         assertNotNull(iter);
         assertTrue(iter.hasNext());
@@ -1773,7 +1773,7 @@ public class LazyListTest
         assertEquals("a", iter.previous());
         assertFalse(iter.hasPrevious());
     }
-    
+
     /**
      * Tests for {@link LazyList#listIterator(Object)}
      */
@@ -1784,7 +1784,7 @@ public class LazyListTest
         input.add("a");
         input.add("b");
         input.add("c");
-        
+
         ListIterator<?> iter = LazyList.listIterator(input);
         assertNotNull(iter);
         assertTrue(iter.hasNext());
@@ -1799,45 +1799,45 @@ public class LazyListTest
         assertEquals("a", iter.previous());
         assertFalse(iter.hasPrevious());
     }
-    
-    
+
+
     /**
-     * Tests for {@link LazyList#array2List(Object[])}
+     * Tests for {@link ArrayUtil#asMutableList(Object[])}
      */
     @Test
     public void testArray2List_NullInput()
     {
         Object input[] = null;
-        
-        Object list = LazyList.array2List(input);
+
+        Object list = ArrayUtil.asMutableList(input);
         assertNotNull(list);
         assertTrue("Should be a List object", list instanceof List);
         assertEquals(0, LazyList.size(list));
     }
 
     /**
-     * Tests for {@link LazyList#array2List(Object[])}
+     * Tests for {@link ArrayUtil#asMutableList(Object[])}
      */
     @Test
     public void testArray2List_EmptyInput()
     {
         String input[] = new String[0];
-        
-        Object list = LazyList.array2List(input);
+
+        Object list = ArrayUtil.asMutableList(input);
         assertNotNull(list);
         assertTrue("Should be a List object", list instanceof List);
         assertEquals(0, LazyList.size(list));
     }
 
     /**
-     * Tests for {@link LazyList#array2List(Object[])}
+     * Tests for {@link ArrayUtil#asMutableList(Object[])}
      */
     @Test
     public void testArray2List_SingleInput()
     {
         String input[] = new String[] { "a" };
-        
-        Object list = LazyList.array2List(input);
+
+        Object list = ArrayUtil.asMutableList(input);
         assertNotNull(list);
         assertTrue("Should be a List object", list instanceof List);
         assertEquals(1, LazyList.size(list));
@@ -1845,14 +1845,14 @@ public class LazyListTest
     }
 
     /**
-     * Tests for {@link LazyList#array2List(Object[])}
+     * Tests for {@link ArrayUtil#asMutableList(Object[])}
      */
     @Test
     public void testArray2List_MultiInput()
     {
         String input[] = new String[] { "a", "b", "c" };
-        
-        Object list = LazyList.array2List(input);
+
+        Object list = ArrayUtil.asMutableList(input);
         assertNotNull(list);
         assertTrue("Should be a List object", list instanceof List);
         assertEquals(3, LazyList.size(list));
@@ -1860,17 +1860,17 @@ public class LazyListTest
         assertEquals("b", LazyList.get(list, 1));
         assertEquals("c", LazyList.get(list, 2));
     }
-    
+
     /**
-     * Tests for {@link LazyList#array2List(Object[])}
+     * Tests for {@link ArrayUtil#asMutableList(Object[])}
      */
     @Test
     public void testArray2List_GenericsInput()
     {
         String input[] = new String[] { "a", "b", "c" };
-        
+
         // Test the Generics definitions for array2List
-        List<String> list = LazyList.array2List(input);
+        List<String> list = ArrayUtil.asMutableList(input);
         assertNotNull(list);
         assertTrue("Should be a List object", list instanceof List);
         assertEquals(3, LazyList.size(list));
@@ -1880,14 +1880,14 @@ public class LazyListTest
     }
 
     /**
-     * Tests for {@link LazyList#addToArray(Object[], Object, Class)}
+     * Tests for {@link ArrayUtil#addToArray(Object[], Object, Class)}
      */
     @Test
     public void testAddToArray_NullInput_NullItem()
     {
         Object input[] = null;
-        
-        Object arr[] = LazyList.addToArray(input,null,Object.class);
+
+        Object arr[] = ArrayUtil.addToArray(input,null,Object.class);
         assertNotNull(arr);
         if(STRICT) {
             // Adding null item to array should result in nothing added?
@@ -1898,18 +1898,18 @@ public class LazyListTest
     }
 
     /**
-     * Tests for {@link LazyList#addToArray(Object[], Object, Class)}
+     * Tests for {@link ArrayUtil#addToArray(Object[], Object, Class)}
      */
     @Test
     public void testAddToArray_NullNullNull()
     {
         // NPE if item && type are both null.
         Assume.assumeTrue(STRICT);
-        
+
         // Harsh test case.
         Object input[] = null;
-        
-        Object arr[] = LazyList.addToArray(input,null,null);
+
+        Object arr[] = ArrayUtil.addToArray(input,null,null);
         assertNotNull(arr);
         if(STRICT) {
             // Adding null item to array should result in nothing added?
@@ -1920,34 +1920,34 @@ public class LazyListTest
     }
 
     /**
-     * Tests for {@link LazyList#addToArray(Object[], Object, Class)}
+     * Tests for {@link ArrayUtil#addToArray(Object[], Object, Class)}
      */
     @Test
     public void testAddToArray_NullInput_SimpleItem()
     {
         Object input[] = null;
-        
-        Object arr[] = LazyList.addToArray(input,"a",String.class);
+
+        Object arr[] = ArrayUtil.addToArray(input,"a",String.class);
         assertNotNull(arr);
         assertEquals(1, arr.length);
         assertEquals("a", arr[0]);
-        
+
         // Same test, but with an undefined type
-        arr = LazyList.addToArray(input,"b",null);
+        arr = ArrayUtil.addToArray(input,"b",null);
         assertNotNull(arr);
         assertEquals(1, arr.length);
         assertEquals("b", arr[0]);
     }
 
     /**
-     * Tests for {@link LazyList#addToArray(Object[], Object, Class)}
+     * Tests for {@link ArrayUtil#addToArray(Object[], Object, Class)}
      */
     @Test
     public void testAddToArray_EmptyInput_NullItem()
     {
         String input[] = new String[0];
-        
-        String arr[] = LazyList.addToArray(input,null,Object.class);
+
+        String arr[] = ArrayUtil.addToArray(input,null,Object.class);
         assertNotNull(arr);
         if(STRICT) {
             // Adding null item to array should result in nothing added?
@@ -1958,28 +1958,28 @@ public class LazyListTest
     }
 
     /**
-     * Tests for {@link LazyList#addToArray(Object[], Object, Class)}
+     * Tests for {@link ArrayUtil#addToArray(Object[], Object, Class)}
      */
     @Test
     public void testAddToArray_EmptyInput_SimpleItem()
     {
         String input[] = new String[0];
-        
-        String arr[] = LazyList.addToArray(input,"a",String.class);
+
+        String arr[] = ArrayUtil.addToArray(input,"a",String.class);
         assertNotNull(arr);
         assertEquals(1, arr.length);
         assertEquals("a", arr[0]);
     }
 
     /**
-     * Tests for {@link LazyList#addToArray(Object[], Object, Class)}
+     * Tests for {@link ArrayUtil#addToArray(Object[], Object, Class)}
      */
     @Test
     public void testAddToArray_SingleInput_NullItem()
     {
         String input[] = new String[] { "z" };
-        
-        String arr[] = LazyList.addToArray(input,null,Object.class);
+
+        String arr[] = ArrayUtil.addToArray(input,null,Object.class);
         assertNotNull(arr);
         if(STRICT) {
             // Should a null item be added to an array?
@@ -1992,14 +1992,14 @@ public class LazyListTest
     }
 
     /**
-     * Tests for {@link LazyList#addToArray(Object[], Object, Class)}
+     * Tests for {@link ArrayUtil#addToArray(Object[], Object, Class)}
      */
     @Test
     public void testAddToArray_SingleInput_SimpleItem()
     {
         String input[] = new String[] { "z" };
-        
-        String arr[] = LazyList.addToArray(input,"a",String.class);
+
+        String arr[] = ArrayUtil.addToArray(input,"a",String.class);
         assertNotNull(arr);
         assertEquals(2, arr.length);
         assertEquals("z", arr[0]);
@@ -2007,85 +2007,85 @@ public class LazyListTest
     }
 
     /**
-     * Tests for {@link LazyList#removeFromArray(Object[], Object)}
+     * Tests for {@link ArrayUtil#removeFromArray(Object[], Object)}
      */
     @Test
     public void testRemoveFromArray_NullInput_NullItem() {
         Object input[] = null;
-        
-        Object arr[] = LazyList.removeFromArray(input,null);
+
+        Object arr[] = ArrayUtil.removeFromArray(input,null);
         assertNull(arr);
     }
-    
+
     /**
-     * Tests for {@link LazyList#removeFromArray(Object[], Object)}
+     * Tests for {@link ArrayUtil#removeFromArray(Object[], Object)}
      */
     @Test
     public void testRemoveFromArray_NullInput_SimpleItem() {
         Object input[] = null;
-        
-        Object arr[] = LazyList.removeFromArray(input,"a");
+
+        Object arr[] = ArrayUtil.removeFromArray(input,"a");
         assertNull(arr);
     }
 
     /**
-     * Tests for {@link LazyList#removeFromArray(Object[], Object)}
+     * Tests for {@link ArrayUtil#removeFromArray(Object[], Object)}
      */
     @Test
     public void testRemoveFromArray_EmptyInput_NullItem() {
         String input[] = new String[0];
-        
-        String arr[] = LazyList.removeFromArray(input,null);
+
+        String arr[] = ArrayUtil.removeFromArray(input,null);
         assertNotNull("Should not be null", arr);
         assertEquals(0, arr.length);
     }
 
     /**
-     * Tests for {@link LazyList#removeFromArray(Object[], Object)}
+     * Tests for {@link ArrayUtil#removeFromArray(Object[], Object)}
      */
     @Test
     public void testRemoveFromArray_EmptyInput_SimpleItem() {
         String input[] = new String[0];
-        
-        String arr[] = LazyList.removeFromArray(input,"a");
+
+        String arr[] = ArrayUtil.removeFromArray(input,"a");
         assertNotNull("Should not be null", arr);
         assertEquals(0, arr.length);
     }
 
     /**
-     * Tests for {@link LazyList#removeFromArray(Object[], Object)}
+     * Tests for {@link ArrayUtil#removeFromArray(Object[], Object)}
      */
     @Test
     public void testRemoveFromArray_SingleInput() {
         String input[] = new String[] { "a" };
-        
-        String arr[] = LazyList.removeFromArray(input,null);
+
+        String arr[] = ArrayUtil.removeFromArray(input,null);
         assertNotNull("Should not be null", arr);
         assertEquals(1, arr.length);
         assertEquals("a", arr[0]);
-        
+
         // Remove actual item
-        arr = LazyList.removeFromArray(input,"a");
+        arr = ArrayUtil.removeFromArray(input,"a");
         assertNotNull("Should not be null", arr);
         assertEquals(0, arr.length);
     }
 
     /**
-     * Tests for {@link LazyList#removeFromArray(Object[], Object)}
+     * Tests for {@link ArrayUtil#removeFromArray(Object[], Object)}
      */
     @Test
     public void testRemoveFromArray_MultiInput() {
         String input[] = new String[] { "a", "b", "c" };
-        
-        String arr[] = LazyList.removeFromArray(input,null);
+
+        String arr[] = ArrayUtil.removeFromArray(input,null);
         assertNotNull("Should not be null", arr);
         assertEquals(3, arr.length);
         assertEquals("a", arr[0]);
         assertEquals("b", arr[1]);
         assertEquals("c", arr[2]);
-        
+
         // Remove an actual item
-        arr = LazyList.removeFromArray(input,"b");
+        arr = ArrayUtil.removeFromArray(input,"b");
         assertNotNull("Should not be null", arr);
         assertEquals(2, arr.length);
         assertEquals("a", arr[0]);
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/LeakDetectorTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/LeakDetectorTest.java
new file mode 100644
index 0000000..da60591
--- /dev/null
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/LeakDetectorTest.java
@@ -0,0 +1,92 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class LeakDetectorTest
+{
+    private LeakDetector<Object> leakDetector;
+
+    public void prepare(LeakDetector<Object> leakDetector) throws Exception
+    {
+        this.leakDetector = leakDetector;
+        leakDetector.start();
+    }
+
+    public void dispose() throws Exception
+    {
+        leakDetector.stop();
+    }
+
+    private void gc()
+    {
+        for (int i = 0; i < 3; ++i)
+            System.gc();
+    }
+
+    @Test
+    public void testResourceAcquiredAndReleased() throws Exception
+    {
+        final CountDownLatch latch = new CountDownLatch(1);
+        prepare(new LeakDetector<Object>()
+        {
+            @Override
+            protected void leaked(LeakInfo leakInfo)
+            {
+                latch.countDown();
+            }
+        });
+
+        // Block to make sure "resource" goes out of scope
+        {
+            Object resource = new Object();
+            leakDetector.acquired(resource);
+            leakDetector.released(resource);
+        }
+
+        gc();
+
+        Assert.assertFalse(latch.await(1, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testResourceAcquiredAndNotReleased() throws Exception
+    {
+        final CountDownLatch latch = new CountDownLatch(1);
+        prepare(new LeakDetector<Object>()
+        {
+            @Override
+            protected void leaked(LeakInfo leakInfo)
+            {
+                latch.countDown();
+            }
+        });
+
+        leakDetector.acquired(new Object());
+
+        gc();
+
+        Assert.assertTrue(latch.await(1, TimeUnit.SECONDS));
+    }
+}
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/MultiExceptionTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/MultiExceptionTest.java
index 6c27eb2..1c60293 100644
--- a/jetty-util/src/test/java/org/eclipse/jetty/util/MultiExceptionTest.java
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/MultiExceptionTest.java
@@ -38,16 +38,16 @@ public class MultiExceptionTest
         me.ifExceptionThrowMulti();
         me.ifExceptionThrowRuntime();
     }
-    
+
     @Test
     public void testOne() throws Exception
     {
         MultiException me = new MultiException();
         IOException io = new IOException("one");
         me.add(io);
-        
+
         assertEquals(1,me.size());
-        
+
         try
         {
             me.ifExceptionThrow();
@@ -57,7 +57,7 @@ public class MultiExceptionTest
         {
             assertTrue(e==io);
         }
-        
+
         try
         {
             me.ifExceptionThrowMulti();
@@ -67,7 +67,7 @@ public class MultiExceptionTest
         {
             assertTrue(e==me);
         }
-        
+
         try
         {
             me.ifExceptionThrowRuntime();
@@ -77,7 +77,7 @@ public class MultiExceptionTest
         {
             assertTrue(e.getCause()==io);
         }
-        
+
         me = new MultiException();
         RuntimeException run = new RuntimeException("one");
         me.add(run);
@@ -92,7 +92,7 @@ public class MultiExceptionTest
             assertTrue(run==e);
         }
     }
-    
+
     @Test
     public void testTwo() throws Exception
     {
@@ -101,9 +101,9 @@ public class MultiExceptionTest
         RuntimeException run = new RuntimeException("one");
         me.add(io);
         me.add(run);
-        
+
         assertEquals(2,me.size());
-        
+
         try
         {
             me.ifExceptionThrow();
@@ -113,7 +113,7 @@ public class MultiExceptionTest
         {
             assertTrue(e==me);
         }
-        
+
         try
         {
             me.ifExceptionThrowMulti();
@@ -123,7 +123,7 @@ public class MultiExceptionTest
         {
             assertTrue(e==me);
         }
-        
+
         try
         {
             me.ifExceptionThrowRuntime();
@@ -133,7 +133,7 @@ public class MultiExceptionTest
         {
             assertTrue(e.getCause()==me);
         }
-        
+
         me = new MultiException();
         me.add(run);
         me.add(run);
@@ -148,4 +148,17 @@ public class MultiExceptionTest
             assertTrue(e.getCause()==me);
         }
     }
+    
+    @Test
+    public void testCause() throws Exception
+    {
+        MultiException me = new MultiException();
+        IOException io = new IOException("one");
+        RuntimeException run = new RuntimeException("two");
+        me.add(io);
+        me.add(run);
+
+        assertEquals(2,me.size());
+        assertEquals(io,me.getCause());        
+    }
 }
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/MultiMapTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/MultiMapTest.java
index 16aa78b..8bc8149 100644
--- a/jetty-util/src/test/java/org/eclipse/jetty/util/MultiMapTest.java
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/MultiMapTest.java
@@ -18,6 +18,8 @@
 
 package org.eclipse.jetty.util;
 
+import static org.hamcrest.Matchers.nullValue;
+
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -34,7 +36,7 @@ public class MultiMapTest
     @Test
     public void testPut()
     {
-        MultiMap<String> mm = new MultiMap<String>();
+        MultiMap<String> mm = new MultiMap<>();
 
         String key = "formats";
 
@@ -47,16 +49,32 @@ public class MultiMapTest
      * Tests {@link MultiMap#put(Object, Object)}
      */
     @Test
-    public void testPut_Null()
+    public void testPut_Null_String()
+    {
+        MultiMap<String> mm = new MultiMap<>();
+
+        String key = "formats";
+        String val = null;
+
+        mm.put(key,val);
+        assertMapSize(mm,1);
+        assertNullValues(mm,key);
+    }
+
+    /**
+     * Tests {@link MultiMap#put(Object, Object)}
+     */
+    @Test
+    public void testPut_Null_List()
     {
-        MultiMap<String> mm = new MultiMap<String>();
+        MultiMap<String> mm = new MultiMap<>();
 
         String key = "formats";
+        List<String> vals = null;
 
-        mm.put(key,null);
+        mm.put(key,vals);
         assertMapSize(mm,1);
-        assertValues(mm,key,new Object[]
-        { null });
+        assertNullValues(mm,key);
     }
 
     /**
@@ -65,7 +83,7 @@ public class MultiMapTest
     @Test
     public void testPut_Replace()
     {
-        MultiMap<String> mm = new MultiMap<String>();
+        MultiMap<String> mm = new MultiMap<>();
 
         String key = "formats";
         Object ret;
@@ -84,12 +102,12 @@ public class MultiMapTest
     }
 
     /**
-     * Tests {@link MultiMap#putValues(Object, List)}
+     * Tests {@link MultiMap#putValues(String, List)}
      */
     @Test
     public void testPutValues_List()
     {
-        MultiMap<String> mm = new MultiMap<String>();
+        MultiMap<String> mm = new MultiMap<>();
 
         String key = "formats";
 
@@ -104,12 +122,12 @@ public class MultiMapTest
     }
 
     /**
-     * Tests {@link MultiMap#putValues(Object, String...)}
+     * Tests {@link MultiMap#putValues(String, String...)}
      */
     @Test
     public void testPutValues_StringArray()
     {
-        MultiMap<String> mm = new MultiMap<String>();
+        MultiMap<String> mm = new MultiMap<>();
 
         String key = "formats";
 
@@ -120,12 +138,12 @@ public class MultiMapTest
     }
 
     /**
-     * Tests {@link MultiMap#putValues(Object, String...)}
+     * Tests {@link MultiMap#putValues(String, String...)}
      */
     @Test
     public void testPutValues_VarArgs()
     {
-        MultiMap<String> mm = new MultiMap<String>();
+        MultiMap<String> mm = new MultiMap<>();
 
         String key = "formats";
 
@@ -135,12 +153,12 @@ public class MultiMapTest
     }
 
     /**
-     * Tests {@link MultiMap#add(Object, Object)}
+     * Tests {@link MultiMap#add(String, Object)}
      */
     @Test
     public void testAdd()
     {
-        MultiMap<String> mm = new MultiMap<String>();
+        MultiMap<String> mm = new MultiMap<>();
 
         String key = "formats";
 
@@ -148,7 +166,7 @@ public class MultiMapTest
         mm.put(key,"gzip");
         assertMapSize(mm,1);
         assertValues(mm,key,"gzip");
-        
+
         // Add to the key
         mm.add(key,"jar");
         mm.add(key,"pack200");
@@ -158,12 +176,12 @@ public class MultiMapTest
     }
 
     /**
-     * Tests {@link MultiMap#addValues(Object, List)}
+     * Tests {@link MultiMap#addValues(String, List)}
      */
     @Test
     public void testAddValues_List()
     {
-        MultiMap<String> mm = new MultiMap<String>();
+        MultiMap<String> mm = new MultiMap<>();
 
         String key = "formats";
 
@@ -171,7 +189,7 @@ public class MultiMapTest
         mm.put(key,"gzip");
         assertMapSize(mm,1);
         assertValues(mm,key,"gzip");
-        
+
         // Add to the key
         List<String> extras = new ArrayList<String>();
         extras.add("jar");
@@ -182,14 +200,14 @@ public class MultiMapTest
         assertMapSize(mm,1);
         assertValues(mm,key,"gzip","jar","pack200","zip");
     }
-    
+
     /**
-     * Tests {@link MultiMap#addValues(Object, List)}
+     * Tests {@link MultiMap#addValues(String, List)}
      */
     @Test
     public void testAddValues_List_Empty()
     {
-        MultiMap<String> mm = new MultiMap<String>();
+        MultiMap<String> mm = new MultiMap<>();
 
         String key = "formats";
 
@@ -197,7 +215,7 @@ public class MultiMapTest
         mm.put(key,"gzip");
         assertMapSize(mm,1);
         assertValues(mm,key,"gzip");
-        
+
         // Add to the key
         List<String> extras = new ArrayList<String>();
         mm.addValues(key,extras);
@@ -207,12 +225,12 @@ public class MultiMapTest
     }
 
     /**
-     * Tests {@link MultiMap#addValues(Object, String[])}
+     * Tests {@link MultiMap#addValues(String, Object[])}
      */
     @Test
     public void testAddValues_StringArray()
     {
-        MultiMap<String> mm = new MultiMap<String>();
+        MultiMap<String> mm = new MultiMap<>();
 
         String key = "formats";
 
@@ -220,7 +238,7 @@ public class MultiMapTest
         mm.put(key,"gzip");
         assertMapSize(mm,1);
         assertValues(mm,key,"gzip");
-        
+
         // Add to the key
         String extras[] = { "jar", "pack200", "zip" };
         mm.addValues(key,extras);
@@ -230,12 +248,12 @@ public class MultiMapTest
     }
 
     /**
-     * Tests {@link MultiMap#addValues(Object, String[])}
+     * Tests {@link MultiMap#addValues(String, Object[])}
      */
     @Test
     public void testAddValues_StringArray_Empty()
     {
-        MultiMap<String> mm = new MultiMap<String>();
+        MultiMap<String> mm = new MultiMap<>();
 
         String key = "formats";
 
@@ -243,7 +261,7 @@ public class MultiMapTest
         mm.put(key,"gzip");
         assertMapSize(mm,1);
         assertValues(mm,key,"gzip");
-        
+
         // Add to the key
         String extras[] = new String[0];
         mm.addValues(key,extras);
@@ -253,12 +271,12 @@ public class MultiMapTest
     }
 
     /**
-     * Tests {@link MultiMap#removeValue(Object, Object)}
+     * Tests {@link MultiMap#removeValue(String, Object)}
      */
     @Test
     public void testRemoveValue()
     {
-        MultiMap<String> mm = new MultiMap<String>();
+        MultiMap<String> mm = new MultiMap<>();
 
         String key = "formats";
 
@@ -266,21 +284,21 @@ public class MultiMapTest
         mm.putValues(key,"gzip","jar","pack200");
         assertMapSize(mm,1);
         assertValues(mm,key,"gzip","jar","pack200");
-        
+
         // Remove a value
         mm.removeValue(key,"jar");
         assertMapSize(mm,1);
         assertValues(mm,key,"gzip","pack200");
-        
+
     }
-    
+
     /**
-     * Tests {@link MultiMap#removeValue(Object, Object)}
+     * Tests {@link MultiMap#removeValue(String, Object)}
      */
     @Test
     public void testRemoveValue_InvalidItem()
     {
-        MultiMap<String> mm = new MultiMap<String>();
+        MultiMap<String> mm = new MultiMap<>();
 
         String key = "formats";
 
@@ -288,20 +306,20 @@ public class MultiMapTest
         mm.putValues(key,"gzip","jar","pack200");
         assertMapSize(mm,1);
         assertValues(mm,key,"gzip","jar","pack200");
-        
+
         // Remove a value that isn't there
         mm.removeValue(key,"msi");
         assertMapSize(mm,1);
         assertValues(mm,key,"gzip","jar","pack200");
     }
-    
+
     /**
-     * Tests {@link MultiMap#removeValue(Object, Object)}
+     * Tests {@link MultiMap#removeValue(String, Object)}
      */
     @Test
     public void testRemoveValue_AllItems()
     {
-        MultiMap<String> mm = new MultiMap<String>();
+        MultiMap<String> mm = new MultiMap<>();
 
         String key = "formats";
 
@@ -309,7 +327,7 @@ public class MultiMapTest
         mm.putValues(key,"gzip","jar","pack200");
         assertMapSize(mm,1);
         assertValues(mm,key,"gzip","jar","pack200");
-        
+
         // Remove a value
         mm.removeValue(key,"jar");
         assertMapSize(mm,1);
@@ -324,14 +342,14 @@ public class MultiMapTest
         mm.removeValue(key,"pack200");
         assertMapSize(mm,0);  // should be empty now
     }
-    
+
     /**
-     * Tests {@link MultiMap#removeValue(Object, Object)}
+     * Tests {@link MultiMap#removeValue(String, Object)}
      */
     @Test
     public void testRemoveValue_FromEmpty()
     {
-        MultiMap<String> mm = new MultiMap<String>();
+        MultiMap<String> mm = new MultiMap<>();
 
         String key = "formats";
 
@@ -339,76 +357,76 @@ public class MultiMapTest
         mm.putValues(key,new String[0]);
         assertMapSize(mm,1);
         assertEmptyValues(mm,key);
-        
+
         // Remove a value that isn't in the underlying values
         mm.removeValue(key,"jar");
         assertMapSize(mm,1);
         assertEmptyValues(mm,key);
     }
-    
+
     /**
      * Tests {@link MultiMap#putAll(java.util.Map)}
      */
     @Test
     public void testPutAll_Map()
     {
-        MultiMap<String> mm = new MultiMap<String>();
-        
+        MultiMap<String> mm = new MultiMap<>();
+
         assertMapSize(mm,0); // Shouldn't have anything yet.
-        
+
         Map<String,String> input = new HashMap<String,String>();
         input.put("food","apple");
         input.put("color","red");
         input.put("amount","bushel");
-        
-        mm.putAll(input);
-        
+
+        mm.putAllValues(input);
+
         assertMapSize(mm,3);
         assertValues(mm,"food","apple");
         assertValues(mm,"color","red");
         assertValues(mm,"amount","bushel");
     }
-    
+
     /**
      * Tests {@link MultiMap#putAll(java.util.Map)}
      */
     @Test
     public void testPutAll_MultiMap_Simple()
     {
-        MultiMap<String> mm = new MultiMap<String>();
-        
+        MultiMap<String> mm = new MultiMap<>();
+
         assertMapSize(mm,0); // Shouldn't have anything yet.
-        
-        MultiMap<String> input = new MultiMap<String>();
+
+        MultiMap<String> input = new MultiMap<>();
         input.put("food","apple");
         input.put("color","red");
         input.put("amount","bushel");
-        
+
         mm.putAll(input);
-        
+
         assertMapSize(mm,3);
         assertValues(mm,"food","apple");
         assertValues(mm,"color","red");
         assertValues(mm,"amount","bushel");
     }
-    
+
     /**
      * Tests {@link MultiMap#putAll(java.util.Map)}
      */
     @Test
     public void testPutAll_MultiMapComplex()
     {
-        MultiMap<String> mm = new MultiMap<String>();
-        
+        MultiMap<String> mm = new MultiMap<>();
+
         assertMapSize(mm,0); // Shouldn't have anything yet.
-        
-        MultiMap<String> input = new MultiMap<String>();
+
+        MultiMap<String> input = new MultiMap<>();
         input.putValues("food","apple","cherry","raspberry");
         input.put("color","red");
         input.putValues("amount","bushel","pint");
-        
+
         mm.putAll(input);
-        
+
         assertMapSize(mm,3);
         assertValues(mm,"food","apple","cherry","raspberry");
         assertValues(mm,"color","red");
@@ -421,52 +439,52 @@ public class MultiMapTest
     @Test
     public void testToStringArrayMap()
     {
-        MultiMap<String> mm = new MultiMap<String>();
+        MultiMap<String> mm = new MultiMap<>();
         mm.putValues("food","apple","cherry","raspberry");
         mm.put("color","red");
         mm.putValues("amount","bushel","pint");
-        
+
         assertMapSize(mm,3);
 
         Map<String,String[]> sam = mm.toStringArrayMap();
         Assert.assertEquals("String Array Map.size",3,sam.size());
-        
+
         assertArray("toStringArrayMap(food)", sam.get("food"), "apple","cherry","raspberry");
         assertArray("toStringArrayMap(color)", sam.get("color"), "red");
         assertArray("toStringArrayMap(amount)", sam.get("amount"), "bushel","pint");
     }
-    
+
     /**
      * Tests {@link MultiMap#toString()}
      */
     @Test
     public void testToString()
     {
-        MultiMap<String> mm = new MultiMap<String>();
+        MultiMap<String> mm = new MultiMap<>();
         mm.put("color","red");
 
         Assert.assertEquals("{color=red}", mm.toString());
-        
+
         mm.putValues("food","apple","cherry","raspberry");
-        
+
         Assert.assertEquals("{color=red, food=[apple, cherry, raspberry]}", mm.toString());
     }
-    
+
     /**
      * Tests {@link MultiMap#clear()}
      */
     @Test
     public void testClear()
     {
-        MultiMap<String> mm = new MultiMap<String>();
+        MultiMap<String> mm = new MultiMap<>();
         mm.putValues("food","apple","cherry","raspberry");
         mm.put("color","red");
         mm.putValues("amount","bushel","pint");
-        
+
         assertMapSize(mm,3);
 
         mm.clear();
-        
+
         assertMapSize(mm,0);
     }
 
@@ -476,7 +494,7 @@ public class MultiMapTest
     @Test
     public void testContainsKey()
     {
-        MultiMap<String> mm = new MultiMap<String>();
+        MultiMap<String> mm = new MultiMap<>();
         mm.putValues("food","apple","cherry","raspberry");
         mm.put("color","red");
         mm.putValues("amount","bushel","pint");
@@ -484,19 +502,38 @@ public class MultiMapTest
         Assert.assertTrue("Contains Key [color]", mm.containsKey("color"));
         Assert.assertFalse("Contains Key [nutrition]", mm.containsKey("nutrition"));
     }
-    
+
+    /**
+     * Tests {@link MultiMap#containsSimpleValue(Object)}
+     */
+    @Test
+    public void testContainsSimpleValue()
+    {
+        MultiMap<String> mm = new MultiMap<>();
+        mm.putValues("food","apple","cherry","raspberry");
+        mm.put("color","red");
+        mm.putValues("amount","bushel","pint");
+
+        Assert.assertTrue("Contains Value [red]", mm.containsSimpleValue("red"));
+        Assert.assertFalse("Contains Value [nutrition]", mm.containsValue("nutrition"));
+    }
+
     /**
      * Tests {@link MultiMap#containsValue(Object)}
      */
     @Test
     public void testContainsValue()
     {
-        MultiMap<String> mm = new MultiMap<String>();
+        MultiMap<String> mm = new MultiMap<>();
         mm.putValues("food","apple","cherry","raspberry");
         mm.put("color","red");
         mm.putValues("amount","bushel","pint");
 
-        Assert.assertTrue("Contains Value [red]", mm.containsValue("red"));
+        List<String> acr = new ArrayList<>();
+        acr.add("apple");
+        acr.add("cherry");
+        acr.add("raspberry");
+        Assert.assertTrue("Contains Value [apple,cherry,raspberry]", mm.containsValue(acr));
         Assert.assertFalse("Contains Value [nutrition]", mm.containsValue("nutrition"));
     }
 
@@ -506,14 +543,14 @@ public class MultiMapTest
     @Test
     public void testContainsValue_LazyList()
     {
-        MultiMap<String> mm = new MultiMap<String>();
+        MultiMap<String> mm = new MultiMap<>();
         mm.putValues("food","apple","cherry","raspberry");
         mm.put("color","red");
         mm.putValues("amount","bushel","pint");
 
         Object list = LazyList.add(null, "bushel");
         list = LazyList.add(list, "pint");
-        
+
         Assert.assertTrue("Contains Value [" + list + "]", mm.containsValue(list));
     }
 
@@ -529,22 +566,34 @@ public class MultiMapTest
 
     private void assertValues(MultiMap<String> mm, String key, Object... expectedValues)
     {
-        List<Object> values = mm.getValues(key);
+        List<String> values = mm.getValues(key);
 
         String prefix = "MultiMap.getValues(" + key + ")";
 
-        Assert.assertNotNull(prefix,values);
         Assert.assertEquals(prefix + ".size",expectedValues.length,values.size());
-        int len = values.size();
+        int len = expectedValues.length;
         for (int i = 0; i < len; i++)
         {
-            Assert.assertEquals(prefix + "[" + i + "]",expectedValues[i],values.get(i));
+            if(expectedValues[i] == null) {
+                Assert.assertThat(prefix + "[" + i + "]",values.get(i),nullValue());
+            } else {
+                Assert.assertEquals(prefix + "[" + i + "]",expectedValues[i],values.get(i));
+            }
         }
     }
 
+    private void assertNullValues(MultiMap<String> mm, String key)
+    {
+        List<String> values = mm.getValues(key);
+
+        String prefix = "MultiMap.getValues(" + key + ")";
+
+        Assert.assertThat(prefix + ".size",values,nullValue());
+    }
+
     private void assertEmptyValues(MultiMap<String> mm, String key)
     {
-        List<Object> values = mm.getValues(key);
+        List<String> values = mm.getValues(key);
 
         String prefix = "MultiMap.getValues(" + key + ")";
 
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/MultiPartInputStreamTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/MultiPartInputStreamTest.java
index 42a5869..b915d2f 100644
--- a/jetty-util/src/test/java/org/eclipse/jetty/util/MultiPartInputStreamTest.java
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/MultiPartInputStreamTest.java
@@ -22,33 +22,34 @@ import static org.hamcrest.Matchers.is;
 import static org.hamcrest.Matchers.not;
 import static org.hamcrest.Matchers.notNullValue;
 import static org.hamcrest.Matchers.nullValue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.File;
-import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.StringReader;
 import java.util.Collection;
 
 import javax.servlet.MultipartConfigElement;
 import javax.servlet.ServletException;
 import javax.servlet.http.Part;
 
-import junit.framework.TestCase;
-
-import org.eclipse.jetty.util.MultiPartInputStream.MultiPart;
-import org.hamcrest.core.IsNot;
+import org.eclipse.jetty.util.MultiPartInputStreamParser.MultiPart;
+import org.junit.Test;
 
 /**
  * MultiPartInputStreamTest
  *
  *
  */
-public class MultiPartInputStreamTest extends TestCase
+public class MultiPartInputStreamTest
 {
     private static final String FILENAME = "stuff.txt";
     protected String _contentType = "multipart/form-data, boundary=AaB03x";
@@ -72,7 +73,7 @@ public class MultiPartInputStreamTest extends TestCase
         "\r\n--" + boundary + "-\r\n\r\n";
         
         MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
-        MultiPartInputStream mpis = new MultiPartInputStream(new ByteArrayInputStream(str.getBytes()), 
+        MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(str.getBytes()), 
                                                              "multipart/form-data, boundary="+boundary,
                                                              config,
                                                              _tmpDir);
@@ -119,7 +120,7 @@ public class MultiPartInputStreamTest extends TestCase
             "----\r\n";
         
         MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
-        MultiPartInputStream mpis = new MultiPartInputStream(new ByteArrayInputStream(str.getBytes()),
+        MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(str.getBytes()),
                                                              "multipart/form-data",
                                                              config,
                                                              _tmpDir);
@@ -149,16 +150,30 @@ public class MultiPartInputStreamTest extends TestCase
         assertThat(baos.toString("US-ASCII"), is("ttt"));  
     }
 
-    public void testNoBody()
+    @Test
+    public void testNonMultiPartRequest()
     throws Exception
     {
-        String body = "";
-        
         MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
-        MultiPartInputStream mpis = new MultiPartInputStream(new ByteArrayInputStream(body.getBytes()), 
-                                                             _contentType,
+        MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(_multi.getBytes()),
+                                                            "Content-type: text/plain",
                                                              config,
-                                                             _tmpDir);
+                                                            _tmpDir);
+        mpis.setDeleteOnExit(true);
+        assertTrue(mpis.getParts().isEmpty());
+    }
+
+    @Test
+    public void testNoBody()
+            throws Exception
+            {
+        String body = "";
+
+        MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
+        MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(body.getBytes()), 
+                                                                         _contentType,
+                                                                         config,
+                                                                         _tmpDir);
         mpis.setDeleteOnExit(true);
         try
         {
@@ -171,17 +186,18 @@ public class MultiPartInputStreamTest extends TestCase
         }
     }
     
+    @Test
     public void testWhitespaceBodyWithCRLF()
-    throws Exception
-    {
+            throws Exception
+            {
         String whitespace = "              \n\n\n\r\n\r\n\r\n\r\n";
- 
-        
+
+
         MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
-        MultiPartInputStream mpis = new MultiPartInputStream(new ByteArrayInputStream(whitespace.getBytes()), 
-                                                             _contentType,
-                                                             config,
-                                                             _tmpDir);
+        MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(whitespace.getBytes()), 
+                                                                         _contentType,
+                                                                         config,
+                                                                         _tmpDir);
         mpis.setDeleteOnExit(true);
         try
         {
@@ -193,17 +209,18 @@ public class MultiPartInputStreamTest extends TestCase
             assertTrue(e.getMessage().startsWith("Missing initial"));
         }
     }
-    
+
+    @Test
     public void testWhitespaceBody()
-    throws Exception
-    {
+            throws Exception
+            {
         String whitespace = " ";
-        
+
         MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
-        MultiPartInputStream mpis = new MultiPartInputStream(new ByteArrayInputStream(whitespace.getBytes()), 
-                                                             _contentType,
-                                                             config,
-                                                             _tmpDir);
+        MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(whitespace.getBytes()), 
+                                                                         _contentType,
+                                                                         config,
+                                                                         _tmpDir);
         mpis.setDeleteOnExit(true);
         try
         {
@@ -216,30 +233,30 @@ public class MultiPartInputStreamTest extends TestCase
         }
     }
 
-
+    @Test
     public void testLeadingWhitespaceBodyWithCRLF()
     throws Exception
     {
         String body = "              \n\n\n\r\n\r\n\r\n\r\n"+
-                      "--AaB03x\r\n"+
-                      "content-disposition: form-data; name=\"field1\"\r\n"+
-                      "\r\n"+
-                      "Joe Blow\r\n"+
-                      "--AaB03x\r\n"+
-                      "content-disposition: form-data; name=\"stuff\"; filename=\"" + "foo.txt" + "\"\r\n"+
-                      "Content-Type: text/plain\r\n"+
-                      "\r\n"+"aaaa"+
-                      "bbbbb"+"\r\n" +
-                      "--AaB03x--\r\n";
+                "--AaB03x\r\n"+
+                "content-disposition: form-data; name=\"field1\"\r\n"+
+                "\r\n"+
+                "Joe Blow\r\n"+
+                "--AaB03x\r\n"+
+                "content-disposition: form-data; name=\"stuff\"; filename=\"" + "foo.txt" + "\"\r\n"+
+                "Content-Type: text/plain\r\n"+
+                "\r\n"+"aaaa"+
+                "bbbbb"+"\r\n" +
+                "--AaB03x--\r\n";
 
 
         MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
-        MultiPartInputStream mpis = new MultiPartInputStream(new ByteArrayInputStream(body.getBytes()),
-                                                             _contentType,
-                                                             config,
-                                                             _tmpDir);
+        MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(body.getBytes()),
+                                                                         _contentType,
+                                                                         config,
+                                                                         _tmpDir);
         mpis.setDeleteOnExit(true);
- 
+
         Collection<Part> parts =    mpis.getParts();
         assertThat(parts, notNullValue());
         assertThat(parts.size(), is(2));
@@ -258,29 +275,29 @@ public class MultiPartInputStreamTest extends TestCase
     
 
 
-
+    @Test
     public void testLeadingWhitespaceBodyWithoutCRLF()
-    throws Exception
-    {
+            throws Exception
+            {
         String body = "            "+
-        "--AaB03x\r\n"+
-        "content-disposition: form-data; name=\"field1\"\r\n"+
-        "\r\n"+
-        "Joe Blow\r\n"+
-        "--AaB03x\r\n"+
-        "content-disposition: form-data; name=\"stuff\"; filename=\"" + "foo.txt" + "\"\r\n"+
-        "Content-Type: text/plain\r\n"+
-        "\r\n"+"aaaa"+
-        "bbbbb"+"\r\n" +
-        "--AaB03x--\r\n";
+                "--AaB03x\r\n"+
+                "content-disposition: form-data; name=\"field1\"\r\n"+
+                "\r\n"+
+                "Joe Blow\r\n"+
+                "--AaB03x\r\n"+
+                "content-disposition: form-data; name=\"stuff\"; filename=\"" + "foo.txt" + "\"\r\n"+
+                "Content-Type: text/plain\r\n"+
+                "\r\n"+"aaaa"+
+                "bbbbb"+"\r\n" +
+                "--AaB03x--\r\n";
 
         MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
-        MultiPartInputStream mpis = new MultiPartInputStream(new ByteArrayInputStream(body.getBytes()),
-                                                             _contentType,
-                                                             config,
-                                                             _tmpDir);
+        MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(body.getBytes()),
+                                                                         _contentType,
+                                                                         config,
+                                                                         _tmpDir);
         mpis.setDeleteOnExit(true);
- 
+
         Collection<Part> parts =    mpis.getParts();
         assertThat(parts, notNullValue());
         assertThat(parts.size(), is(2));
@@ -302,24 +319,12 @@ public class MultiPartInputStreamTest extends TestCase
     
     
 
-    public void testNonMultiPartRequest()
-    throws Exception
-    {
-
-        MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);  
-        MultiPartInputStream mpis = new MultiPartInputStream(new ByteArrayInputStream(_multi.getBytes()), 
-                                                             "Content-type: text/plain",
-                                                             config,
-                                                            _tmpDir);
-        mpis.setDeleteOnExit(true);
-        assertTrue(mpis.getParts().isEmpty());   
-    }
-    
+    @Test
     public void testNoLimits()
     throws Exception
     {
         MultipartConfigElement config = new MultipartConfigElement(_dirname);
-        MultiPartInputStream mpis = new MultiPartInputStream(new ByteArrayInputStream(_multi.getBytes()), 
+        MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(_multi.getBytes()),
                                                              _contentType,
                                                              config,
                                                              _tmpDir);
@@ -328,11 +333,12 @@ public class MultiPartInputStreamTest extends TestCase
         assertFalse(parts.isEmpty());
     }
 
+    @Test
     public void testRequestTooBig ()
     throws Exception
     {
-        MultipartConfigElement config = new MultipartConfigElement(_dirname, 60, 100, 50);  
-        MultiPartInputStream mpis = new MultiPartInputStream(new ByteArrayInputStream(_multi.getBytes()), 
+        MultipartConfigElement config = new MultipartConfigElement(_dirname, 60, 100, 50);
+        MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(_multi.getBytes()),
                                                             _contentType,
                                                              config,
                                                              _tmpDir);
@@ -348,12 +354,13 @@ public class MultiPartInputStreamTest extends TestCase
             assertTrue(e.getMessage().startsWith("Request exceeds maxRequestSize"));
         }
     }
-    
+
+    @Test
     public void testFileTooBig()
     throws Exception
     {
-        MultipartConfigElement config = new MultipartConfigElement(_dirname, 40, 1024, 30);  
-        MultiPartInputStream mpis = new MultiPartInputStream(new ByteArrayInputStream(_multi.getBytes()), 
+        MultipartConfigElement config = new MultipartConfigElement(_dirname, 40, 1024, 30);
+        MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(_multi.getBytes()),
                                                             _contentType,
                                                              config,
                                                              _tmpDir);
@@ -369,11 +376,12 @@ public class MultiPartInputStreamTest extends TestCase
             assertTrue(e.getMessage().startsWith("Multipart Mime part"));
         }
     }
-    
+
+    @Test
     public void testPartFileNotDeleted () throws Exception
     {
         MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);  
-        MultiPartInputStream mpis = new MultiPartInputStream(new ByteArrayInputStream(createMultipartRequestString("tptfd").getBytes()),
+        MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(createMultipartRequestString("tptfd").getBytes()),
                 _contentType,
                 config,
                 _tmpDir);
@@ -381,7 +389,7 @@ public class MultiPartInputStreamTest extends TestCase
         Collection<Part> parts = mpis.getParts();
         
         MultiPart part = (MultiPart)mpis.getPart("stuff");
-        File stuff = ((MultiPartInputStream.MultiPart)part).getFile();
+        File stuff = ((MultiPartInputStreamParser.MultiPart)part).getFile();
         assertThat(stuff,notNullValue()); // longer than 100 bytes, should already be a tmp file
         part.write("tptfd.txt");
         File tptfd = new File (_dirname+File.separator+"tptfd.txt");
@@ -392,26 +400,26 @@ public class MultiPartInputStreamTest extends TestCase
         tptfd.deleteOnExit(); //clean up test
     }
     
-    
+    @Test
     public void testPartTmpFileDeletion () throws Exception
     {
         MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);  
-        MultiPartInputStream mpis = new MultiPartInputStream(new ByteArrayInputStream(createMultipartRequestString("tptfd").getBytes()),
-                _contentType,
-                config,
-                _tmpDir);
+        MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(createMultipartRequestString("tptfd").getBytes()),
+                                                                         _contentType,
+                                                                         config,
+                                                                         _tmpDir);
         mpis.setDeleteOnExit(true);
         Collection<Part> parts = mpis.getParts();
-        
+
         MultiPart part = (MultiPart)mpis.getPart("stuff");
-        File stuff = ((MultiPartInputStream.MultiPart)part).getFile();
+        File stuff = ((MultiPartInputStreamParser.MultiPart)part).getFile();
         assertThat(stuff,notNullValue()); // longer than 100 bytes, should already be a tmp file
         assertThat (stuff.exists(), is(true));
         part.cleanUp();
         assertThat(stuff.exists(), is(false));  //tmp file was removed after cleanup
     }
     
- 
+    @Test
     public void testLFOnlyRequest()
     throws Exception
     {
@@ -426,7 +434,7 @@ public class MultiPartInputStreamTest extends TestCase
                 "--AaB03x--\n";
 
         MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
-        MultiPartInputStream mpis = new MultiPartInputStream(new ByteArrayInputStream(str.getBytes()),
+        MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(str.getBytes()),
                                                                          _contentType,
                                                                          config,
                                                                          _tmpDir);
@@ -446,7 +454,7 @@ public class MultiPartInputStreamTest extends TestCase
         assertThat(baos.toString("UTF-8"), is("Other"));
     }
     
- 
+    @Test
     public void testCROnlyRequest()
     throws Exception
     {
@@ -461,7 +469,7 @@ public class MultiPartInputStreamTest extends TestCase
         "--AaB03x--\r";
 
         MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
-        MultiPartInputStream mpis = new MultiPartInputStream(new ByteArrayInputStream(str.getBytes()),
+        MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(str.getBytes()),
                                                                          _contentType,
                                                                          config,
                                                                          _tmpDir);
@@ -484,7 +492,7 @@ public class MultiPartInputStreamTest extends TestCase
         assertThat(baos.toString("UTF-8"), is("Other"));
     }
 
-   
+    @Test
     public void testCRandLFMixRequest()
     throws Exception
     {
@@ -500,7 +508,7 @@ public class MultiPartInputStreamTest extends TestCase
                 "--AaB03x--\r";
         
         MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
-        MultiPartInputStream mpis = new MultiPartInputStream(new ByteArrayInputStream(str.getBytes()),
+        MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(str.getBytes()),
                                                                          _contentType,
                                                                          config,
                                                                          _tmpDir);
@@ -521,7 +529,7 @@ public class MultiPartInputStreamTest extends TestCase
         assertThat(baos.toString("UTF-8"), is("Other")); 
     }
     
-
+    @Test
     public void testBufferOverflowNoCRLF () throws Exception
     {
         ByteArrayOutputStream baos = new ByteArrayOutputStream();
@@ -532,7 +540,7 @@ public class MultiPartInputStreamTest extends TestCase
         }
         
         MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
-        MultiPartInputStream mpis = new MultiPartInputStream(new ByteArrayInputStream(baos.toByteArray()), 
+        MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(baos.toByteArray()), 
                                                              _contentType,
                                                              config,
                                                              _tmpDir);
@@ -561,7 +569,7 @@ public class MultiPartInputStreamTest extends TestCase
                 "--TheBoundary--\r";
         
         MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
-        MultiPartInputStream mpis = new MultiPartInputStream(new ByteArrayInputStream(str.getBytes()),
+        MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(str.getBytes()),
                                                                          contentType,
                                                                          config,
                                                                          _tmpDir);
@@ -571,6 +579,7 @@ public class MultiPartInputStreamTest extends TestCase
     }
     
     
+    @Test
     public void testBadlyEncodedFilename() throws Exception
     {
         
@@ -582,16 +591,17 @@ public class MultiPartInputStreamTest extends TestCase
         "--AaB03x--\r\n";
         
         MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
-        MultiPartInputStream mpis = new MultiPartInputStream(new ByteArrayInputStream(contents.getBytes()),
+        MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(contents.getBytes()),
                                                                          _contentType,
                                                                          config,
                                                                          _tmpDir);
         mpis.setDeleteOnExit(true);
         Collection<Part> parts = mpis.getParts();
         assertThat(parts.size(), is(1));
-        assertThat(((MultiPartInputStream.MultiPart)parts.iterator().next()).getContentDispositionFilename(), is("Taken on Aug 22 \\ 2012.jpg"));
+        assertThat(((MultiPartInputStreamParser.MultiPart)parts.iterator().next()).getSubmittedFileName(), is("Taken on Aug 22 \\ 2012.jpg"));
     }
     
+    @Test
     public void testBadlyEncodedMSFilename() throws Exception
     {
         
@@ -603,16 +613,17 @@ public class MultiPartInputStreamTest extends TestCase
         "--AaB03x--\r\n";
         
         MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
-        MultiPartInputStream mpis = new MultiPartInputStream(new ByteArrayInputStream(contents.getBytes()),
+        MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(contents.getBytes()),
                                                                          _contentType,
                                                                          config,
                                                                          _tmpDir);
         mpis.setDeleteOnExit(true);
         Collection<Part> parts = mpis.getParts();
         assertThat(parts.size(), is(1));
-        assertThat(((MultiPartInputStream.MultiPart)parts.iterator().next()).getContentDispositionFilename(), is("c:\\this\\really\\is\\some\\path\\to\\a\\file.txt"));
+        assertThat(((MultiPartInputStreamParser.MultiPart)parts.iterator().next()).getSubmittedFileName(), is("c:\\this\\really\\is\\some\\path\\to\\a\\file.txt"));
     }
 
+    @Test
     public void testCorrectlyEncodedMSFilename() throws Exception
     {
         String contents =  "--AaB03x\r\n"+
@@ -623,14 +634,14 @@ public class MultiPartInputStreamTest extends TestCase
         "--AaB03x--\r\n";
         
         MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
-        MultiPartInputStream mpis = new MultiPartInputStream(new ByteArrayInputStream(contents.getBytes()),
+        MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(contents.getBytes()),
                                                                          _contentType,
                                                                          config,
                                                                          _tmpDir);
         mpis.setDeleteOnExit(true);
         Collection<Part> parts = mpis.getParts();
         assertThat(parts.size(), is(1));
-        assertThat(((MultiPartInputStream.MultiPart)parts.iterator().next()).getContentDispositionFilename(), is("c:\\this\\really\\is\\some\\path\\to\\a\\file.txt"));
+        assertThat(((MultiPartInputStreamParser.MultiPart)parts.iterator().next()).getSubmittedFileName(), is("c:\\this\\really\\is\\some\\path\\to\\a\\file.txt"));
     }
     
     public void testMulti ()
@@ -639,6 +650,7 @@ public class MultiPartInputStreamTest extends TestCase
         testMulti(FILENAME);
     }
 
+    @Test
     public void testMultiWithSpaceInFilename() throws Exception
     {
         testMulti("stuff with spaces.txt");
@@ -647,10 +659,10 @@ public class MultiPartInputStreamTest extends TestCase
     
     
     
-    private void testMulti(String filename) throws IOException, ServletException
+    private void testMulti(String filename) throws IOException, ServletException, InterruptedException
     {
-        MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);  
-        MultiPartInputStream mpis = new MultiPartInputStream(new ByteArrayInputStream(createMultipartRequestString(filename).getBytes()),
+        MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
+        MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(createMultipartRequestString(filename).getBytes()),
                 _contentType,
                 config,
                 _tmpDir);
@@ -665,10 +677,10 @@ public class MultiPartInputStreamTest extends TestCase
         IO.copy(is, os);
         assertEquals("Joe Blow", new String(os.toByteArray()));
         assertEquals(8, field1.getSize());
-        
-        assertNotNull(((MultiPartInputStream.MultiPart)field1).getBytes());//in internal buffer
+
+        assertNotNull(((MultiPartInputStreamParser.MultiPart)field1).getBytes());//in internal buffer
         field1.write("field1.txt");
-        assertNull(((MultiPartInputStream.MultiPart)field1).getBytes());//no longer in internal buffer
+        assertNull(((MultiPartInputStreamParser.MultiPart)field1).getBytes());//no longer in internal buffer
         File f = new File (_dirname+File.separator+"field1.txt");
         assertTrue(f.exists());
         field1.write("another_field1.txt"); //write after having already written
@@ -678,18 +690,19 @@ public class MultiPartInputStreamTest extends TestCase
         field1.delete();  //file should be deleted
         assertFalse(f.exists()); //original file was renamed
         assertFalse(f2.exists()); //2nd written file was explicitly deleted
-        
+
         MultiPart stuff = (MultiPart)mpis.getPart("stuff");
-        assertThat(stuff.getContentDispositionFilename(), is(filename));
+        assertThat(stuff.getSubmittedFileName(), is(filename));
         assertThat(stuff.getContentType(),is("text/plain"));
         assertThat(stuff.getHeader("Content-Type"),is("text/plain"));
         assertThat(stuff.getHeaders("content-type").size(),is(1));
         assertThat(stuff.getHeader("content-disposition"),is("form-data; name=\"stuff\"; filename=\"" + filename + "\""));
         assertThat(stuff.getHeaderNames().size(),is(2));
         assertThat(stuff.getSize(),is(51L));
-        File tmpfile = ((MultiPartInputStream.MultiPart)stuff).getFile();
+        
+        File tmpfile = ((MultiPartInputStreamParser.MultiPart)stuff).getFile();
         assertThat(tmpfile,notNullValue()); // longer than 100 bytes, should already be a tmp file
-        assertThat(((MultiPartInputStream.MultiPart)stuff).getBytes(),nullValue()); //not in an internal buffer
+        assertThat(((MultiPartInputStreamParser.MultiPart)stuff).getBytes(),nullValue()); //not in an internal buffer
         assertThat(tmpfile.exists(),is(true));
         assertThat(tmpfile.getName(),is(not("stuff with space.txt")));
         stuff.write(filename);
@@ -707,6 +720,7 @@ public class MultiPartInputStreamTest extends TestCase
         f.deleteOnExit(); //clean up after test
     }
 
+    @Test
     public void testMultiSameNames ()
     throws Exception
     {
@@ -721,9 +735,9 @@ public class MultiPartInputStreamTest extends TestCase
         "\r\n"+
         "110000000000000000000000000000000000000000000000000\r\n"+
         "--AaB03x--\r\n";
-        
-        MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);          
-        MultiPartInputStream mpis = new MultiPartInputStream(new ByteArrayInputStream(sameNames.getBytes()),
+
+        MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
+        MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(sameNames.getBytes()),
                                                              _contentType,
                                                              config,
                                                              _tmpDir);
@@ -732,14 +746,14 @@ public class MultiPartInputStreamTest extends TestCase
         assertEquals(2, parts.size());
         for (Part p:parts)
             assertEquals("stuff", p.getName());
-        
+
         //if they all have the name name, then only retrieve the first one
         Part p = mpis.getPart("stuff");
         assertNotNull(p);
         assertEquals(5, p.getSize());
     }
 
-
+    @Test
     public void testBase64EncodedContent () throws Exception
     {
         String contentWithEncodedPart =
@@ -762,14 +776,14 @@ public class MultiPartInputStreamTest extends TestCase
                         "--AaB03x--\r\n";
 
         MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
-        MultiPartInputStream mpis = new MultiPartInputStream(new ByteArrayInputStream(contentWithEncodedPart.getBytes()),
-                                                             _contentType,
-                                                             config,
-                                                             _tmpDir);
+        MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(contentWithEncodedPart.getBytes()),
+                                                                         _contentType,
+                                                                         config,
+                                                                         _tmpDir);
         mpis.setDeleteOnExit(true);
         Collection<Part> parts = mpis.getParts();
         assertEquals(3, parts.size());
-       
+
         Part p1 = mpis.getPart("other");
         assertNotNull(p1);
         ByteArrayOutputStream baos = new ByteArrayOutputStream();
@@ -789,6 +803,7 @@ public class MultiPartInputStreamTest extends TestCase
         assertEquals("the end", baos.toString("US-ASCII"));
     }
     
+    @Test
     public void testQuotedPrintableEncoding () throws Exception
     {
         String contentWithEncodedPart = 
@@ -805,10 +820,10 @@ public class MultiPartInputStreamTest extends TestCase
                         "truth=3Dbeauty" + "\r\n"+
                         "--AaB03x--\r\n";  
         MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
-        MultiPartInputStream mpis = new MultiPartInputStream(new ByteArrayInputStream(contentWithEncodedPart.getBytes()),
-                                                             _contentType,
-                                                             config,
-                                                             _tmpDir);
+        MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(contentWithEncodedPart.getBytes()),
+                                                                         _contentType,
+                                                                         config,
+                                                                         _tmpDir);
         mpis.setDeleteOnExit(true);
         Collection<Part> parts = mpis.getParts();
         assertEquals(2, parts.size());
@@ -845,7 +860,7 @@ public class MultiPartInputStreamTest extends TestCase
         }
         
         return "--AaB03x\r\n"+
-        "content-disposition: form-data; name=\"field1\"\r\n"+
+        "content-disposition: form-data; name=\"field1\"; filename=\"frooble.txt\"\r\n"+
         "\r\n"+
         "Joe Blow\r\n"+
         "--AaB03x\r\n"+
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/QueueBenchmarkTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/QueueBenchmarkTest.java
new file mode 100644
index 0000000..2971e6c
--- /dev/null
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/QueueBenchmarkTest.java
@@ -0,0 +1,222 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Queue;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.CyclicBarrier;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.toolchain.test.AdvancedRunner;
+import org.eclipse.jetty.toolchain.test.annotation.Stress;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+ at RunWith(AdvancedRunner.class)
+public class QueueBenchmarkTest
+{
+    private static final Logger logger = Log.getLogger(QueueBenchmarkTest.class);
+    private static final Runnable ELEMENT = new Runnable()
+    {
+        @Override
+        public void run()
+        {
+        }
+    };
+    private static final Runnable END = new Runnable()
+    {
+        @Override
+        public void run()
+        {
+        }
+    };
+
+    @Stress("High CPU")
+    @Test
+    public void testQueues() throws Exception
+    {
+        int cores = Runtime.getRuntime().availableProcessors();
+        Assume.assumeTrue(cores > 1);
+
+        final int readers = cores / 2;
+        final int writers = readers;
+        final int iterations = 16 * 1024 * 1024;
+
+        final List<Queue<Runnable>> queues = new ArrayList<>();
+        queues.add(new ConcurrentArrayQueue<Runnable>()); // Jetty lock-free queue, allocating array blocks
+        queues.add(new ConcurrentLinkedQueue<Runnable>()); // JDK lock-free queue, allocating nodes
+        queues.add(new ArrayBlockingQueue<Runnable>(iterations * writers)); // JDK lock-based, circular array queue
+        queues.add(new BlockingArrayQueue<Runnable>(iterations * writers)); // Jetty lock-based, circular array queue
+
+        testQueues(readers, writers, iterations, queues, false);
+    }
+
+    @Stress("High CPU")
+    @Test
+    public void testBlockingQueues() throws Exception
+    {
+        int cores = Runtime.getRuntime().availableProcessors();
+        Assume.assumeTrue(cores > 1);
+
+        final int readers = cores / 2;
+        final int writers = readers;
+        final int iterations = 16 * 1024 * 1024;
+
+        final List<Queue<Runnable>> queues = new ArrayList<>();
+        queues.add(new LinkedBlockingQueue<Runnable>());
+        queues.add(new ArrayBlockingQueue<Runnable>(iterations * writers));
+        queues.add(new BlockingArrayQueue<Runnable>(iterations * writers));
+
+        testQueues(readers, writers, iterations, queues, true);
+    }
+
+    private void testQueues(final int readers, final int writers, final int iterations, List<Queue<Runnable>> queues, final boolean blocking) throws Exception
+    {
+        final int runs = 8;
+        int threads = readers + writers;
+        final CyclicBarrier barrier = new CyclicBarrier(threads + 1);
+
+        for (final Queue<Runnable> queue : queues)
+        {
+            for (int r = 0; r < runs; ++r)
+            {
+                for (int i = 0; i < readers; ++i)
+                {
+                    Thread thread = new Thread()
+                    {
+                        @Override
+                        public void run()
+                        {
+                            await(barrier);
+                            consume(queue, writers, blocking);
+                            await(barrier);
+                        }
+                    };
+                    thread.start();
+                }
+                for (int i = 0; i < writers; ++i)
+                {
+                    Thread thread = new Thread()
+                    {
+                        @Override
+                        public void run()
+                        {
+                            await(barrier);
+                            produce(queue, readers, iterations);
+                            await(barrier);
+                        }
+                    };
+                    thread.start();
+                }
+
+                await(barrier);
+                long begin = System.nanoTime();
+                await(barrier);
+                long end = System.nanoTime();
+                long elapsed = TimeUnit.NANOSECONDS.toMillis(end - begin);
+                logger.info("{} Readers/Writers: {}/{} => {} ms", queue.getClass().getSimpleName(), readers, writers, elapsed);
+            }
+        }
+    }
+
+    private static void consume(Queue<Runnable> queue, int writers, boolean blocking)
+    {
+        while (true)
+        {
+            Runnable element = blocking ? take(queue) : poll(queue);
+            if (element == END)
+                if (--writers == 0)
+                    break;
+        }
+    }
+
+    private static void produce(Queue<Runnable> queue, int readers, int iterations)
+    {
+        for (int i = 0; i < iterations; ++i)
+            append(queue, ELEMENT);
+        for (int i = 0; i < readers; ++i)
+            append(queue, END);
+    }
+
+    private static void append(Queue<Runnable> queue, Runnable element)
+    {
+        if (!queue.offer(element))
+            logger.warn("Queue {} capacity is too small", queue);
+    }
+
+    private static Runnable take(Queue<Runnable> queue)
+    {
+        try
+        {
+            return ((BlockingQueue<Runnable>)queue).take();
+        }
+        catch (InterruptedException x)
+        {
+            throw new RuntimeException(x);
+        }
+    }
+
+    private static Runnable poll(Queue<Runnable> queue)
+    {
+        int loops = 0;
+        while (true)
+        {
+            Runnable element = queue.poll();
+            if (element != null)
+                return element;
+            // Busy loop
+            sleepMicros(1);
+            ++loops;
+            if (loops % 16 == 0)
+                logger.warn("Spin looping while polling empty queue: {} spins: ", loops);
+        }
+    }
+
+    private static void sleepMicros(long sleep)
+    {
+        try
+        {
+            TimeUnit.MICROSECONDS.sleep(sleep);
+        }
+        catch (InterruptedException x)
+        {
+            throw new RuntimeException(x);
+        }
+    }
+
+    private static void await(CyclicBarrier barrier)
+    {
+        try
+        {
+            barrier.await();
+        }
+        catch (Exception x)
+        {
+            throw new RuntimeException(x);
+        }
+    }
+}
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/QuotedStringTokenizerTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/QuotedStringTokenizerTest.java
index 55cf489..665c8ee 100644
--- a/jetty-util/src/test/java/org/eclipse/jetty/util/QuotedStringTokenizerTest.java
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/QuotedStringTokenizerTest.java
@@ -18,13 +18,15 @@
 
 package org.eclipse.jetty.util;
 
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 
 import org.junit.Test;
 
 
 /**
- * 
+ *
  *
  */
 public class QuotedStringTokenizerTest
@@ -35,7 +37,7 @@ public class QuotedStringTokenizerTest
     @Test
     public void testTokenizer0()
     {
-        QuotedStringTokenizer tok = 
+        QuotedStringTokenizer tok =
             new QuotedStringTokenizer("abc\n\"d\\\"'\"\n'p\\',y'\nz");
         checkTok(tok,false,false);
     }
@@ -46,8 +48,8 @@ public class QuotedStringTokenizerTest
     @Test
     public void testTokenizer1()
     {
-        QuotedStringTokenizer tok = 
-            new QuotedStringTokenizer("abc, \"d\\\"'\",'p\\',y' z", 
+        QuotedStringTokenizer tok =
+            new QuotedStringTokenizer("abc, \"d\\\"'\",'p\\',y' z",
                                       " ,");
         checkTok(tok,false,false);
     }
@@ -58,16 +60,16 @@ public class QuotedStringTokenizerTest
     @Test
     public void testTokenizer2()
     {
-        QuotedStringTokenizer tok = 
+        QuotedStringTokenizer tok =
             new QuotedStringTokenizer("abc, \"d\\\"'\",'p\\',y' z", " ,",
             false);
         checkTok(tok,false,false);
-        
+
         tok = new QuotedStringTokenizer("abc, \"d\\\"'\",'p\\',y' z", " ,",
                                         true);
         checkTok(tok,true,false);
     }
-    
+
     /*
      * Test for String nextToken()
      */
@@ -75,29 +77,29 @@ public class QuotedStringTokenizerTest
     public void testTokenizer3()
     {
         QuotedStringTokenizer tok;
-        
+
         tok = new QuotedStringTokenizer("abc, \"d\\\"'\",'p\\',y' z", " ,",
                                         false,false);
         checkTok(tok,false,false);
-        
+
         tok = new QuotedStringTokenizer("abc, \"d\\\"'\",'p\\',y' z", " ,",
                                         false,true);
         checkTok(tok,false,true);
-        
+
         tok = new QuotedStringTokenizer("abc, \"d\\\"'\",'p\\',y' z", " ,",
                                         true,false);
         checkTok(tok,true,false);
-        
+
         tok = new QuotedStringTokenizer("abc, \"d\\\"'\",'p\\',y' z", " ,",
                                         true,true);
         checkTok(tok,true,true);
     }
-    
+
     @Test
     public void testQuote()
     {
         StringBuffer buf = new StringBuffer();
-        
+
         buf.setLength(0);
         QuotedStringTokenizer.quote(buf,"abc \n efg");
         assertEquals("\"abc \\n efg\"",buf.toString());
@@ -105,19 +107,11 @@ public class QuotedStringTokenizerTest
         buf.setLength(0);
         QuotedStringTokenizer.quote(buf,"abcefg");
         assertEquals("\"abcefg\"",buf.toString());
-        
+
         buf.setLength(0);
         QuotedStringTokenizer.quote(buf,"abcefg\"");
         assertEquals("\"abcefg\\\"\"",buf.toString());
-        
-        buf.setLength(0);
-        QuotedStringTokenizer.quoteIfNeeded(buf,"abc \n efg","\"\\\n\r\t\f\b%+ ;=");
-        assertEquals("\"abc \\n efg\"",buf.toString());
-        
-        buf.setLength(0);
-        QuotedStringTokenizer.quoteIfNeeded(buf,"abcefg","\"\\\n\r\t\f\b%+ ;=");
-        assertEquals("abcefg",buf.toString());
-        
+
     }
 
     /*
@@ -134,7 +128,7 @@ public class QuotedStringTokenizerTest
         tok.setSingle(true);
         assertEquals("abcdef,ghijkl",tok.nextToken());
     }
-    
+
     private void checkTok(QuotedStringTokenizer tok,boolean delim,boolean quotes)
     {
         assertTrue(tok.hasMoreElements());
@@ -142,7 +136,7 @@ public class QuotedStringTokenizerTest
         assertEquals("abc",tok.nextToken());
         if (delim)assertEquals(",",tok.nextToken());
         if (delim)assertEquals(" ",tok.nextToken());
-            
+
         assertEquals(quotes?"\"d\\\"'\"":"d\"'",tok.nextElement());
         if (delim)assertEquals(",",tok.nextToken());
         assertEquals(quotes?"'p\\',y'":"p',y",tok.nextToken());
@@ -159,9 +153,9 @@ public class QuotedStringTokenizerTest
     {
         assertEquals("abc",QuotedStringTokenizer.quoteIfNeeded("abc", " ,"));
         assertEquals("\"a c\"",QuotedStringTokenizer.quoteIfNeeded("a c", " ,"));
-        assertEquals("\"a'c\"",QuotedStringTokenizer.quoteIfNeeded("a'c", " ,"));  
-        assertEquals("\"a\\n\\r\\t\"",QuotedStringTokenizer.quote("a\n\r\t"));  
-        assertEquals("\"\\u0000\\u001f\"",QuotedStringTokenizer.quote("\u0000\u001f")); 
+        assertEquals("\"a'c\"",QuotedStringTokenizer.quoteIfNeeded("a'c", " ,"));
+        assertEquals("\"a\\n\\r\\t\"",QuotedStringTokenizer.quote("a\n\r\t"));
+        assertEquals("\"\\u0000\\u001f\"",QuotedStringTokenizer.quote("\u0000\u001f"));
     }
 
     @Test
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/ReadLineInputStreamTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/ReadLineInputStreamTest.java
new file mode 100644
index 0000000..17df2b7
--- /dev/null
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/ReadLineInputStreamTest.java
@@ -0,0 +1,247 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.io.OutputStream;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class ReadLineInputStreamTest
+{
+    BlockingArrayQueue<String> _queue = new BlockingArrayQueue<>();
+    PipedInputStream _pin;
+    volatile PipedOutputStream _pout;
+    ReadLineInputStream _in;
+    volatile Thread _writer;
+    
+    @Before
+    public void before() throws Exception
+    {
+        _queue.clear();
+        _pin=new PipedInputStream();
+        _pout=new PipedOutputStream(_pin);
+        _in=new ReadLineInputStream(_pin);
+        _writer=new Thread()
+        {
+            @Override
+            public void run()
+            {
+                try
+                {
+                    OutputStream out=_pout;
+                    while (out!=null)
+                    {
+                        String s = _queue.poll(100,TimeUnit.MILLISECONDS);
+                        if (s!=null)
+                        {
+                            if ("__CLOSE__".equals(s))
+                                _pout.close();
+                            else
+                            {
+                                _pout.write(s.getBytes(StandardCharsets.UTF_8));
+                                Thread.sleep(50);
+                            }
+                        }
+                        out=_pout;
+                    }
+                }
+                catch (Exception e)
+                {
+                    e.printStackTrace();
+                }
+                finally
+                {
+                    _writer=null;
+                }
+              
+            }
+        };
+        _writer.start();
+    }
+    
+    @After
+    public void after()  throws Exception
+    {
+        _pout=null;
+        while (_writer!=null)
+            Thread.sleep(10);
+    }
+    
+    @Test
+    public void testCR() throws Exception
+    {
+        _queue.add("\rHello\rWorld\r\r");
+        _queue.add("__CLOSE__");
+        
+        Assert.assertEquals("",_in.readLine());
+        Assert.assertEquals("Hello",_in.readLine());
+        Assert.assertEquals("World",_in.readLine());
+        Assert.assertEquals("",_in.readLine());
+        Assert.assertEquals(null,_in.readLine());
+    }
+    
+    @Test
+    public void testLF() throws Exception
+    {
+        _queue.add("\nHello\nWorld\n\n");
+        _queue.add("__CLOSE__");
+        
+        Assert.assertEquals("",_in.readLine());
+        Assert.assertEquals("Hello",_in.readLine());
+        Assert.assertEquals("World",_in.readLine());
+        Assert.assertEquals("",_in.readLine());
+        Assert.assertEquals(null,_in.readLine());
+    }
+    
+    @Test
+    public void testCRLF() throws Exception
+    {
+        _queue.add("\r\nHello\r\nWorld\r\n\r\n");
+        _queue.add("__CLOSE__");
+        
+        Assert.assertEquals("",_in.readLine());
+        Assert.assertEquals("Hello",_in.readLine());
+        Assert.assertEquals("World",_in.readLine());
+        Assert.assertEquals("",_in.readLine());
+        Assert.assertEquals(null,_in.readLine());
+    }
+    
+
+    @Test
+    public void testCRBlocking() throws Exception
+    {
+        _queue.add("");
+        _queue.add("\r");
+        _queue.add("Hello");
+        _queue.add("\rWorld\r");
+        _queue.add("\r");
+        _queue.add("__CLOSE__");
+        
+        Assert.assertEquals("",_in.readLine());
+        Assert.assertEquals("Hello",_in.readLine());
+        Assert.assertEquals("World",_in.readLine());
+        Assert.assertEquals("",_in.readLine());
+        Assert.assertEquals(null,_in.readLine());
+    }
+    
+    @Test
+    public void testLFBlocking() throws Exception
+    {
+        _queue.add("");
+        _queue.add("\n");
+        _queue.add("Hello");
+        _queue.add("\nWorld\n");
+        _queue.add("\n");
+        _queue.add("__CLOSE__");
+        
+        Assert.assertEquals("",_in.readLine());
+        Assert.assertEquals("Hello",_in.readLine());
+        Assert.assertEquals("World",_in.readLine());
+        Assert.assertEquals("",_in.readLine());
+        Assert.assertEquals(null,_in.readLine());
+    }
+    
+    @Test
+    public void testCRLFBlocking() throws Exception
+    {
+        _queue.add("\r");
+        _queue.add("\nHello");
+        _queue.add("\r\nWorld\r");
+        _queue.add("\n\r");
+        _queue.add("\n");
+        _queue.add("");
+        _queue.add("__CLOSE__");
+        
+        Assert.assertEquals("",_in.readLine());
+        Assert.assertEquals("Hello",_in.readLine());
+        Assert.assertEquals("World",_in.readLine());
+        Assert.assertEquals("",_in.readLine());
+        Assert.assertEquals(null,_in.readLine());
+    }
+
+
+    @Test
+    public void testHeaderLFBodyLF() throws Exception
+    {
+        _queue.add("Header\n");
+        _queue.add("\n");
+        _queue.add("\nBody\n");
+        _queue.add("\n");
+        _queue.add("__CLOSE__");
+
+        Assert.assertEquals("Header",_in.readLine());
+        Assert.assertEquals("",_in.readLine());
+
+        byte[] body = new byte[6];
+        _in.read(body);
+        Assert.assertEquals("\nBody\n",new String(body,0,6,StandardCharsets.UTF_8));
+        
+        Assert.assertEquals("",_in.readLine());
+        Assert.assertEquals(null,_in.readLine());
+    }
+    
+    @Test
+    public void testHeaderCRBodyLF() throws Exception
+    {
+        _queue.add("Header\r");
+        _queue.add("\r");
+        _queue.add("\nBody\n");
+        _queue.add("\r");
+        _queue.add("__CLOSE__");
+
+        Assert.assertEquals("Header",_in.readLine());
+        Assert.assertEquals("",_in.readLine());
+
+        byte[] body = new byte[6];
+        _in.read(body);
+        Assert.assertEquals("\nBody\n",new String(body,0,6,StandardCharsets.UTF_8));
+        
+        Assert.assertEquals("",_in.readLine());
+        Assert.assertEquals(null,_in.readLine());
+    }
+    
+    @Test
+    public void testHeaderCRLFBodyLF() throws Exception
+    {
+        _queue.add("Header\r\n");
+        _queue.add("\r\n");
+        _queue.add("\nBody\n");
+        _queue.add("\r\n");
+        _queue.add("__CLOSE__");
+
+        Assert.assertEquals("Header",_in.readLine());
+        Assert.assertEquals("",_in.readLine());
+
+        byte[] body = new byte[6];
+        _in.read(body);
+        Assert.assertEquals("\nBody\n",new String(body,0,6,StandardCharsets.UTF_8));
+        
+        Assert.assertEquals("",_in.readLine());
+        Assert.assertEquals(null,_in.readLine());
+    }
+
+    
+}
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/ScannerTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/ScannerTest.java
index b0d8c7d..db0a4cb 100644
--- a/jetty-util/src/test/java/org/eclipse/jetty/util/ScannerTest.java
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/ScannerTest.java
@@ -21,20 +21,25 @@ package org.eclipse.jetty.util;
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.io.OutputStream;
 import java.util.List;
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.LinkedBlockingQueue;
 
+import org.eclipse.jetty.toolchain.test.AdvancedRunner;
 import org.eclipse.jetty.toolchain.test.FS;
 import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
 import org.eclipse.jetty.toolchain.test.OS;
+import org.eclipse.jetty.toolchain.test.annotation.Slow;
 import org.eclipse.jetty.util.Scanner.Notification;
 import org.junit.AfterClass;
 import org.junit.Assert;
 import org.junit.Assume;
 import org.junit.BeforeClass;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 
+ at RunWith(AdvancedRunner.class)
 public class ScannerTest
 {
     static File _directory;
@@ -103,6 +108,7 @@ public class ScannerTest
     }
 
     @Test
+    @Slow
     public void testAddedChangeRemove() throws Exception
     {
         // TODO needs to be further investigated
@@ -231,51 +237,52 @@ public class ScannerTest
         // Create a new file by writing to it.
         long now = System.currentTimeMillis();
         File file = new File(_directory,"st");
-        FileOutputStream out = new FileOutputStream(file,true);
-        out.write('x');
-        out.flush();
-        file.setLastModified(now);
-
-        // Not stable yet so no notification.
-        _scanner.scan();
-        event = _queue.poll();
-        Assert.assertTrue(event==null);
-
-        // Modify size only
-        out.write('x');
-        out.flush();
-        file.setLastModified(now);
-
-        // Still not stable yet so no notification.
-        _scanner.scan();
-        event = _queue.poll();
-        Assert.assertTrue(event==null);
-
-        // now stable so finally see the ADDED
-        _scanner.scan();
-        event = _queue.poll();
-        Assert.assertTrue(event!=null);
-        Assert.assertEquals(_directory+"/st",event._filename);
-        Assert.assertEquals(Notification.ADDED,event._notification);
-
-        // Modify size only
-        out.write('x');
-        out.flush();
-        file.setLastModified(now);
-
-
-        // Still not stable yet so no notification.
-        _scanner.scan();
-        event = _queue.poll();
-        Assert.assertTrue(event==null);
-
-        // now stable so finally see the ADDED
-        _scanner.scan();
-        event = _queue.poll();
-        Assert.assertTrue(event!=null);
-        Assert.assertEquals(_directory+"/st",event._filename);
-        Assert.assertEquals(Notification.CHANGED,event._notification);
-
+        try (OutputStream out = new FileOutputStream(file,true))
+        {
+            out.write('x');
+            out.flush();
+            file.setLastModified(now);
+
+            // Not stable yet so no notification.
+            _scanner.scan();
+            event = _queue.poll();
+            Assert.assertTrue(event==null);
+
+            // Modify size only
+            out.write('x');
+            out.flush();
+            file.setLastModified(now);
+
+            // Still not stable yet so no notification.
+            _scanner.scan();
+            event = _queue.poll();
+            Assert.assertTrue(event==null);
+
+            // now stable so finally see the ADDED
+            _scanner.scan();
+            event = _queue.poll();
+            Assert.assertTrue(event!=null);
+            Assert.assertEquals(_directory+"/st",event._filename);
+            Assert.assertEquals(Notification.ADDED,event._notification);
+
+            // Modify size only
+            out.write('x');
+            out.flush();
+            file.setLastModified(now);
+
+
+            // Still not stable yet so no notification.
+            _scanner.scan();
+            event = _queue.poll();
+            Assert.assertTrue(event==null);
+
+            // now stable so finally see the ADDED
+            _scanner.scan();
+            event = _queue.poll();
+            Assert.assertTrue(event!=null);
+            Assert.assertEquals(_directory+"/st",event._filename);
+            Assert.assertEquals(Notification.CHANGED,event._notification);
+        }
     }
 
     private void delete(String string) throws IOException
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/SharedBlockingCallbackTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/SharedBlockingCallbackTest.java
new file mode 100644
index 0000000..eba2e22
--- /dev/null
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/SharedBlockingCallbackTest.java
@@ -0,0 +1,259 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.instanceOf;
+import static org.hamcrest.Matchers.not;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+
+import java.io.IOException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.eclipse.jetty.util.SharedBlockingCallback.Blocker;
+import org.hamcrest.Matchers;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class SharedBlockingCallbackTest
+{
+    final AtomicInteger notComplete = new AtomicInteger();
+    final SharedBlockingCallback sbcb= new SharedBlockingCallback()
+    {
+        @Override
+        protected long getIdleTimeout()
+        {
+            return 150;
+        }
+
+        @Override
+        protected void notComplete(Blocker blocker)
+        {
+            super.notComplete(blocker);
+            notComplete.incrementAndGet();
+        }
+
+    };
+    
+    public SharedBlockingCallbackTest()
+    {
+    }
+    
+    
+    @Test
+    public void testDone() throws Exception
+    { 
+        long start;
+        try (Blocker blocker=sbcb.acquire())
+        {
+            blocker.succeeded();
+            start=System.currentTimeMillis();
+            blocker.block();
+        }
+        Assert.assertThat(System.currentTimeMillis()-start,Matchers.lessThan(500L));  
+        Assert.assertEquals(0,notComplete.get());   
+    }
+    
+    @Test
+    public void testGetDone() throws Exception
+    {
+        long start;
+        try (final Blocker blocker=sbcb.acquire())
+        {
+            final CountDownLatch latch = new CountDownLatch(1);
+
+            new Thread(new Runnable()
+            {
+                @Override
+                public void run()
+                {
+                    latch.countDown();
+                    try{TimeUnit.MILLISECONDS.sleep(100);}catch(Exception e){e.printStackTrace();}
+                    blocker.succeeded();
+                }
+            }).start();
+
+            latch.await();
+            start=System.currentTimeMillis();
+            blocker.block();
+        }
+        Assert.assertThat(System.currentTimeMillis()-start,Matchers.greaterThan(10L)); 
+        Assert.assertThat(System.currentTimeMillis()-start,Matchers.lessThan(1000L)); 
+        Assert.assertEquals(0,notComplete.get());   
+    }
+    
+    @Test
+    public void testFailed() throws Exception
+    {
+        final Exception ex = new Exception("FAILED");
+        long start=Long.MIN_VALUE;
+        try
+        {
+            try (final Blocker blocker=sbcb.acquire())
+            {
+                blocker.failed(ex);
+                blocker.block();
+            }
+            Assert.fail();
+        }
+        catch(IOException ee)
+        {
+            start=System.currentTimeMillis();
+            Assert.assertEquals(ex,ee.getCause());
+        }
+        Assert.assertThat(System.currentTimeMillis()-start,Matchers.lessThan(100L));    
+        Assert.assertEquals(0,notComplete.get());    
+    }
+    
+    @Test
+    public void testGetFailed() throws Exception
+    {
+        final Exception ex = new Exception("FAILED");
+        long start=Long.MIN_VALUE;
+        final CountDownLatch latch = new CountDownLatch(1);
+
+        try
+        {
+            try (final Blocker blocker=sbcb.acquire())
+            {
+
+                new Thread(new Runnable()
+                {
+                    @Override
+                    public void run()
+                    {
+                        latch.countDown();
+                        try{TimeUnit.MILLISECONDS.sleep(100);}catch(Exception e){e.printStackTrace();}
+                        blocker.failed(ex);
+                    }
+                }).start();
+
+                latch.await();
+                start=System.currentTimeMillis();
+                blocker.block();
+            }
+            Assert.fail();
+        }
+        catch(IOException ee)
+        {
+            Assert.assertEquals(ex,ee.getCause());
+        }
+        Assert.assertThat(System.currentTimeMillis()-start,Matchers.greaterThan(10L)); 
+        Assert.assertThat(System.currentTimeMillis()-start,Matchers.lessThan(1000L));
+        Assert.assertEquals(0,notComplete.get());   
+    }
+
+
+    @Test
+    public void testAcquireBlocked() throws Exception
+    {
+        final CountDownLatch latch = new CountDownLatch(1);
+        
+        new Thread(new Runnable()
+        {
+            @Override
+            public void run()
+            {
+                try
+                {
+                    try (Blocker blocker=sbcb.acquire())
+                    {
+                        latch.countDown();
+                        TimeUnit.MILLISECONDS.sleep(100);
+                        blocker.succeeded();
+                        blocker.block();
+                    }
+                }
+                catch(Exception e)
+                {
+                    e.printStackTrace();
+                }
+            }
+        }).start();
+        
+        
+        latch.await();
+        long start=System.currentTimeMillis();
+        try (Blocker blocker=sbcb.acquire())
+        {
+            Assert.assertThat(System.currentTimeMillis()-start,Matchers.greaterThan(10L)); 
+            Assert.assertThat(System.currentTimeMillis()-start,Matchers.lessThan(500L)); 
+
+            blocker.succeeded();
+            blocker.block();
+        };
+        Assert.assertThat(System.currentTimeMillis()-start,Matchers.lessThan(600L)); 
+        Assert.assertEquals(0,notComplete.get());     
+    }
+
+    @Test
+    public void testBlockerClose() throws Exception
+    {
+        try (Blocker blocker=sbcb.acquire())
+        {
+            SharedBlockingCallback.LOG.info("Blocker not complete "+blocker+" warning is expected...");
+        }
+        
+        Assert.assertEquals(1,notComplete.get());
+    }
+    
+    @Test
+    public void testBlockerTimeout() throws Exception
+    {
+        Blocker b0=null;
+        try
+        {
+            try (Blocker blocker=sbcb.acquire())
+            {
+                b0=blocker;
+                Thread.sleep(400);
+                blocker.block();
+            }
+            fail();
+        }
+        catch(IOException e)
+        {
+            Throwable cause = e.getCause();
+            assertThat(cause,instanceOf(TimeoutException.class));
+        }
+        
+        Assert.assertEquals(0,notComplete.get());
+        
+
+        try (Blocker blocker=sbcb.acquire())
+        {
+            assertThat(blocker,not(equalTo(b0)));
+            try
+            {
+                b0.succeeded();
+                fail();
+            }
+            catch(Exception e)
+            {
+                assertThat(e,instanceOf(IllegalStateException.class));
+                assertThat(e.getCause(),instanceOf(TimeoutException.class));
+            }
+            blocker.succeeded();
+        }
+    }
+}
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/StringMapTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/StringMapTest.java
deleted file mode 100644
index c486eff..0000000
--- a/jetty-util/src/test/java/org/eclipse/jetty/util/StringMapTest.java
+++ /dev/null
@@ -1,320 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.util;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
-import java.util.Map;
-import java.util.Set;
-
-import org.junit.Before;
-import org.junit.Test;
-
-
-/**
- * 
- *
- */
-public class StringMapTest
-{
-    StringMap m0;
-    StringMap m1;
-    StringMap m5;
-    StringMap m5i;
-
-    /*
-     * @see TestCase#setUp()
-     */
-    
-    @Before
-    public void setUp() throws Exception
-    {
-        m0=new StringMap();
-        m1=new StringMap(false);
-        m1.put("abc", "0");
-        
-        m5=new StringMap(false);
-        m5.put("a", "0");
-        m5.put("ab", "1");
-        m5.put("abc", "2");
-        m5.put("abb", "3");
-        m5.put("bbb", "4");
-        
-        m5i=new StringMap(true); 
-        m5i.put(null, "0");
-        m5i.put("ab", "1");
-        m5i.put("abc", "2");
-        m5i.put("abb", "3");
-        m5i.put("bbb", null);
-    }
-
-    @Test
-    public void testSize()
-    {
-        assertEquals(0, m0.size());
-        assertEquals(1, m1.size());
-        assertEquals(5, m5.size());
-        assertEquals(5, m5i.size());
-        
-        m1.remove("abc");
-        m5.remove("abc");
-        m5.put("bbb","x");
-        m5i.put("ABC", "x");
-        assertEquals(0, m0.size());
-        assertEquals(0, m1.size());
-        assertEquals(4, m5.size());
-        assertEquals(5, m5i.size());
-    }
-
-    @Test
-    public void testIsEmpty()
-    {
-        assertTrue(m0.isEmpty());
-        assertFalse(m1.isEmpty());
-        assertFalse(m5.isEmpty());
-        assertFalse(m5i.isEmpty());
-    }
-
-    @Test
-    public void testClear()
-    {
-        m0.clear();
-        m1.clear();
-        m5.clear();
-        m5i.clear();
-        assertTrue(m0.isEmpty());
-        assertTrue(m1.isEmpty());
-        assertTrue(m5.isEmpty());
-        assertTrue(m5i.isEmpty());
-        assertEquals(null,m1.get("abc"));
-        assertEquals(null,m5.get("abc"));
-        assertEquals(null,m5i.get("abc"));
-    }
-
-
-    /*
-     * Test for Object put(Object, Object)
-     */
-    @Test
-    public void testPutGet()
-    {
-        assertEquals("2",m5.get("abc"));
-        assertEquals(null,m5.get("aBc"));
-        assertEquals("2",m5i.get("abc"));
-        assertEquals("2",m5i.get("aBc"));
-        
-        m5.put(null,"x");
-        m5.put("aBc", "x");
-        m5i.put("AbC", "x");
-
-        StringBuilder buffer=new StringBuilder();
-        buffer.append("aBc");
-        assertEquals("2",m5.get("abc"));
-        assertEquals("x",m5.get(buffer));
-        assertEquals("x",m5i.get((Object)"abc"));
-        assertEquals("x",m5i.get("aBc"));
-        
-        assertEquals("x",m5.get(null));
-        assertEquals("0",m5i.get(null));
-        
-    }
-
-    /*
-     * Test for Map.Entry getEntry(String, int, int)
-     */
-    @Test
-    public void testGetEntryStringintint()
-    {
-        Map.Entry entry;
-        
-        entry=m5.getEntry("xabcyz",1,3);
-        assertTrue(entry!=null);
-        assertEquals("abc",entry.getKey());
-        assertEquals("2",entry.getValue());
-        
-        entry=m5.getBestEntry("xabcyz".getBytes(),1,5);
-        assertTrue(entry!=null);
-        assertEquals("abc",entry.getKey());
-        assertEquals("2",entry.getValue());
-        
-        entry=m5.getEntry("xaBcyz",1,3);
-        assertTrue(entry==null);
-        
-        entry=m5i.getEntry("xaBcyz",1,3);
-        assertTrue(entry!=null);
-        assertEquals("abc",entry.getKey());
-        assertEquals("2",entry.getValue());
-        entry.setValue("x");
-        assertEquals("{[c:abc=x]}",entry.toString());
-        
-        entry=m5i.getEntry((String)null,0,0);
-        assertTrue(entry!=null);
-        assertEquals(null,entry.getKey());
-        assertEquals("0",entry.getValue());
-        entry.setValue("x");
-        assertEquals("[:null=x]",entry.toString());
-
-    }
-
-    /*
-     * Test for Map.Entry getEntry(char[], int, int)
-     */
-    @Test
-    public void testGetEntrycharArrayintint()
-    {
-        char[] xabcyz = {'x','a','b','c','y','z'};
-        char[] xaBcyz = {'x','a','B','c','y','z'};
-        Map.Entry entry;
-        
-        entry=m5.getEntry(xabcyz,1,3);
-        assertTrue(entry!=null);
-        assertEquals("abc",entry.getKey());
-        assertEquals("2",entry.getValue());
-        
-        entry=m5.getEntry(xaBcyz,1,3);
-        assertTrue(entry==null);
-        
-        entry=m5i.getEntry(xaBcyz,1,3);
-        assertTrue(entry!=null);
-        assertEquals("abc",entry.getKey());
-        assertEquals("2",entry.getValue());
-    }
-
-    /*
-     * Test for Object remove(Object)
-     */
-    @Test
-    public void testRemove()
-    {
-        m0.remove("abc");
-        m1.remove("abc");
-        m5.remove("aBc");
-        m5.remove("bbb");
-        m5i.remove("aBc");
-        m5i.remove(null);
-
-        assertEquals(0, m0.size());
-        assertEquals(0, m1.size());
-        assertEquals(4, m5.size());
-        assertEquals(3, m5i.size());
-
-        assertEquals("2",m5.get("abc"));
-        assertEquals(null,m5.get("bbb"));
-        assertEquals(null,m5i.get("AbC"));
-        assertEquals(null,m5i.get(null));
-    }
-
-    /*
-     * Test for Set entrySet()
-     */
-    @Test
-    public void testEntrySet()
-    {
-        Set es0=m0.entrySet();
-        Set es1=m1.entrySet();
-        Set es5=m5.entrySet();
-        assertEquals(0, es0.size());
-        assertEquals(1, es1.size());
-        assertEquals(5, es5.size());
-    }
-
-    /*
-     * Test for boolean containsKey(Object)
-     */
-    @Test
-    public void testContainsKey()
-    {
-        assertTrue(m5.containsKey("abc"));
-        assertTrue(!m5.containsKey("aBc"));
-        assertTrue(m5.containsKey("bbb"));
-        assertTrue(!m5.containsKey("xyz"));
-        
-        assertTrue(m5i.containsKey(null));
-        assertTrue(m5i.containsKey("abc"));
-        assertTrue(m5i.containsKey("aBc"));
-        assertTrue(m5i.containsKey("ABC"));
-    }
-
-    @Test
-    public void testWriteExternal()
-        throws Exception
-    {
-        ByteArrayOutputStream bout= new ByteArrayOutputStream();
-        ObjectOutputStream oo=new ObjectOutputStream(bout);
-        ObjectInputStream oi;
-        
-        oo.writeObject(m0);
-        oo.writeObject(m1);
-        oo.writeObject(m5);
-        oo.writeObject(m5i);
-        
-        oi=new ObjectInputStream(new ByteArrayInputStream(bout.toByteArray()));
-        m0=(StringMap)oi.readObject();
-        m1=(StringMap)oi.readObject();
-        m5=(StringMap)oi.readObject();
-        m5i=(StringMap)oi.readObject();
-        testSize();
-        
-        oi=new ObjectInputStream(new ByteArrayInputStream(bout.toByteArray()));
-        m0=(StringMap)oi.readObject();
-        m1=(StringMap)oi.readObject();
-        m5=(StringMap)oi.readObject();
-        m5i=(StringMap)oi.readObject();
-        testPutGet();
-        
-    }
-    
-    @Test
-    public void testToString()
-    {
-        assertEquals("{}",m0.toString());
-        assertEquals("{abc=0}",m1.toString());
-        assertTrue(m5.toString().indexOf("abc=2")>0);
-    }
-    
-    @Test
-    public void testIgnoreCase()
-    {
-        StringMap map = new StringMap(true);
-        map.put("POST","1");
-        map.put("HEAD","2");
-        map.put("PUT","3");
-        map.put("OPTIONS","4");
-        map.put("DELETE","5");
-        map.put("TRACE","6");
-        map.put("CONNECT","7");
-        map.put("Upgrade","8");
-        
-        assertEquals("1",map.get("POST"));
-        assertEquals("1",map.get("pOST"));
-        assertEquals("1",map.get("Post"));
-        
-        assertEquals("8",map.get("UPGRADE"));
-        assertEquals("8",map.get("Upgrade"));
-        assertEquals("8",map.get("upgrade"));
-        
-    }
-
-}
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/StringUtilTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/StringUtilTest.java
index 6b325ec..8c4b05f 100644
--- a/jetty-util/src/test/java/org/eclipse/jetty/util/StringUtilTest.java
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/StringUtilTest.java
@@ -21,15 +21,12 @@ package org.eclipse.jetty.util;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
-import junit.framework.Assert;
 
-import org.junit.Test;
+import java.nio.charset.StandardCharsets;
 
+import org.junit.Assert;
+import org.junit.Test;
 
-/**
- * 
- *
- */
 public class StringUtilTest
 {
     @Test
@@ -43,7 +40,7 @@ public class StringUtilTest
     @Test
     public void testStartsWithIgnoreCase()
     {
-        
+
         assertTrue(StringUtil.startsWithIgnoreCase("\u0690b\u0690defg", "\u0690b\u0690"));
         assertTrue(StringUtil.startsWithIgnoreCase("\u0690bcdefg", "\u0690bc"));
         assertTrue(StringUtil.startsWithIgnoreCase("\u0690bcdefg", "\u0690Bc"));
@@ -53,9 +50,9 @@ public class StringUtilTest
         assertTrue(StringUtil.startsWithIgnoreCase("\u0690bcdefg", null));
         assertTrue(StringUtil.startsWithIgnoreCase("\u0690bcdefg", "\u0690bcdefg"));
 
-        assertFalse(StringUtil.startsWithIgnoreCase(null, "xyz")); 
+        assertFalse(StringUtil.startsWithIgnoreCase(null, "xyz"));
         assertFalse(StringUtil.startsWithIgnoreCase("\u0690bcdefg", "xyz"));
-        assertFalse(StringUtil.startsWithIgnoreCase("\u0690", "xyz")); 
+        assertFalse(StringUtil.startsWithIgnoreCase("\u0690", "xyz"));
     }
 
     @Test
@@ -70,9 +67,9 @@ public class StringUtilTest
         assertTrue(StringUtil.endsWithIgnoreCase("\u0690bcdefg", null));
         assertTrue(StringUtil.endsWithIgnoreCase("\u0690bcdefg", "\u0690bcdefg"));
 
-        assertFalse(StringUtil.endsWithIgnoreCase(null, "xyz")); 
+        assertFalse(StringUtil.endsWithIgnoreCase(null, "xyz"));
         assertFalse(StringUtil.endsWithIgnoreCase("\u0690bcdefg", "xyz"));
-        assertFalse(StringUtil.endsWithIgnoreCase("\u0690", "xyz"));  
+        assertFalse(StringUtil.endsWithIgnoreCase("\u0690", "xyz"));
     }
 
     @Test
@@ -90,10 +87,10 @@ public class StringUtilTest
         String s="\u0690bc \u0690bc \u0690bc";
         assertEquals(StringUtil.replace(s, "\u0690bc", "xyz"),"xyz xyz xyz");
         assertTrue(StringUtil.replace(s,"xyz","pqy")==s);
-        
+
         s=" \u0690bc ";
         assertEquals(StringUtil.replace(s, "\u0690bc", "xyz")," xyz ");
-        
+
     }
 
     @Test
@@ -137,9 +134,9 @@ public class StringUtilTest
         StringUtil.append(buf, (byte)-1, 16);
         StringUtil.append(buf, (byte)-16, 16);
         assertEquals("ab0c10fff0",buf.toString());
-        
+
     }
-    
+
     @Test
     public void testSidConversion() throws Exception
     {
@@ -159,13 +156,13 @@ public class StringUtilTest
         Assert.assertEquals(sid12, StringUtil.sidBytesToString(sid12Bytes));
 
     }
-    
-    
+
+
     public static void main(String[] arg) throws Exception
     {
         String string = "Now \u0690xxxxxxxx";
         System.err.println(string);
-        byte[] bytes=string.getBytes("UTF-8");
+        byte[] bytes=string.getBytes(StandardCharsets.UTF_8);
         System.err.println(new String(bytes));
         System.err.println(bytes.length);
         long calc=0;
@@ -175,7 +172,7 @@ public class StringUtilTest
             long s1=System.currentTimeMillis();
             for (int j=1000000; j-->0;)
             {
-                calc+=new String(bytes,0,bytes.length,"UTF-8").hashCode();
+                calc+=new String(bytes,0,bytes.length,StandardCharsets.UTF_8).hashCode();
             }
             long s2=System.currentTimeMillis();
             for (int j=1000000; j-->0;)
@@ -197,9 +194,51 @@ public class StringUtilTest
                 calc+=strbuf.toString().hashCode();
             }
             long s5=System.currentTimeMillis();
-            
+
             System.err.println((s2-s1)+", "+(s3-s2)+", "+(s4-s3)+", "+(s5-s4));
         }
         System.err.println(calc);
     }
+
+    @Test
+    public void testIsBlank() 
+    {
+        Assert.assertTrue(StringUtil.isBlank(null));
+        Assert.assertTrue(StringUtil.isBlank(""));
+        Assert.assertTrue(StringUtil.isBlank("\r\n"));
+        Assert.assertTrue(StringUtil.isBlank("\t"));
+        Assert.assertTrue(StringUtil.isBlank("   "));
+
+        Assert.assertFalse(StringUtil.isBlank("a"));
+        Assert.assertFalse(StringUtil.isBlank("  a"));
+        Assert.assertFalse(StringUtil.isBlank("a  "));
+        Assert.assertFalse(StringUtil.isBlank("."));
+        Assert.assertFalse(StringUtil.isBlank(";\n"));
+    }
+
+    @Test
+    public void testIsNotBlank() 
+    {
+        Assert.assertFalse(StringUtil.isNotBlank(null));
+        Assert.assertFalse(StringUtil.isNotBlank(""));
+        Assert.assertFalse(StringUtil.isNotBlank("\r\n"));
+        Assert.assertFalse(StringUtil.isNotBlank("\t"));
+        Assert.assertFalse(StringUtil.isNotBlank("   "));
+
+        Assert.assertTrue(StringUtil.isNotBlank("a"));
+        Assert.assertTrue(StringUtil.isNotBlank("  a"));
+        Assert.assertTrue(StringUtil.isNotBlank("a  "));
+        Assert.assertTrue(StringUtil.isNotBlank("."));
+        Assert.assertTrue(StringUtil.isNotBlank(";\n"));
+    }
+    
+    @Test
+    public void testSanitizeHTML()
+    {
+        assertEquals(null,StringUtil.sanitizeXmlString(null));
+        assertEquals("",StringUtil.sanitizeXmlString(""));
+        assertEquals("<&>",StringUtil.sanitizeXmlString("<&>"));
+        assertEquals("Hello <Cruel> World",StringUtil.sanitizeXmlString("Hello <Cruel> World"));
+        assertEquals("Hello ? World",StringUtil.sanitizeXmlString("Hello \u0000 World"));
+    }
 }
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/TestIntrospectionUtil.java b/jetty-util/src/test/java/org/eclipse/jetty/util/TestIntrospectionUtil.java
index 4652b75..07f8cc7 100644
--- a/jetty-util/src/test/java/org/eclipse/jetty/util/TestIntrospectionUtil.java
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/TestIntrospectionUtil.java
@@ -35,7 +35,7 @@ import org.junit.Test;
  */
 public class TestIntrospectionUtil
 {
-    public final static Class[] __INTEGER_ARG = new Class[] {Integer.class};
+    public final static Class<?>[] __INTEGER_ARG = new Class[] {Integer.class};
     static Field privateAField;
     static Field protectedAField;
     static Field publicAField;
@@ -52,15 +52,15 @@ public class TestIntrospectionUtil
     static Method protectedDMethod;
     static Method publicDMethod;
     static Method defaultDMethod;
-    
-    public class ServletA 
+
+    public class ServletA
     {
-        private Integer privateA; 
+        private Integer privateA;
         protected Integer protectedA;
         Integer defaultA;
         public Integer publicA;
     }
-    
+
     public class ServletB extends ServletA
     {
         private String privateB;
@@ -68,15 +68,15 @@ public class TestIntrospectionUtil
         public String publicB;
         String defaultB;
     }
-    
+
     public class ServletC
     {
-        private void setPrivateC (Integer c) {}      
+        private void setPrivateC (Integer c) {}
         protected void setProtectedC (Integer c) {}
         public void setPublicC(Integer c) {}
         void setDefaultC(Integer c) {}
     }
-    
+
     public class ServletD extends ServletC
     {
         private void setPrivateD(Integer d) {}
@@ -84,7 +84,7 @@ public class TestIntrospectionUtil
         public void setPublicD(Integer d) {}
         void setDefaultD(Integer d) {}
     }
-    
+
     @BeforeClass
     public static void setUp()
     throws Exception
@@ -101,12 +101,12 @@ public class TestIntrospectionUtil
         protectedCMethod = ServletC.class.getDeclaredMethod("setProtectedC", __INTEGER_ARG);
         publicCMethod = ServletC.class.getDeclaredMethod("setPublicC", __INTEGER_ARG);
         defaultCMethod = ServletC.class.getDeclaredMethod("setDefaultC", __INTEGER_ARG);
-        privateDMethod = ServletD.class.getDeclaredMethod("setPrivateD", __INTEGER_ARG); 
+        privateDMethod = ServletD.class.getDeclaredMethod("setPrivateD", __INTEGER_ARG);
         protectedDMethod = ServletD.class.getDeclaredMethod("setProtectedD", __INTEGER_ARG);
         publicDMethod = ServletD.class.getDeclaredMethod("setPublicD", __INTEGER_ARG);
         defaultDMethod = ServletD.class.getDeclaredMethod("setDefaultD", __INTEGER_ARG);
     }
-   
+
     @Test
     public void testFieldPrivate ()
     throws Exception
@@ -126,20 +126,20 @@ public class TestIntrospectionUtil
             //expected
         }
     }
-    
+
     @Test
-    public void testFieldProtected()    
+    public void testFieldProtected()
     throws Exception
     {
         //direct
         Field f = IntrospectionUtil.findField(ServletA.class, "protectedA", Integer.class, true, false);
         assertEquals(f, protectedAField);
-        
+
         //inheritance
         f = IntrospectionUtil.findField(ServletB.class, "protectedA", Integer.class, true, false);
         assertEquals(f, protectedAField);
     }
-    
+
     @Test
     public void testFieldPublic()
     throws Exception
@@ -147,12 +147,12 @@ public class TestIntrospectionUtil
         //direct
         Field f = IntrospectionUtil.findField(ServletA.class, "publicA", Integer.class, true, false);
         assertEquals(f, publicAField);
-        
+
         //inheritance
         f = IntrospectionUtil.findField(ServletB.class, "publicA", Integer.class, true, false);
         assertEquals(f, publicAField);
     }
-    
+
     @Test
     public void testFieldDefault()
     throws Exception
@@ -160,12 +160,12 @@ public class TestIntrospectionUtil
         //direct
         Field f = IntrospectionUtil.findField(ServletA.class, "defaultA", Integer.class, true, false);
         assertEquals(f, defaultAField);
-        
+
         //inheritance
         f = IntrospectionUtil.findField(ServletB.class, "defaultA", Integer.class, true, false);
         assertEquals(f, defaultAField);
     }
-    
+
     @Test
     public void testMethodPrivate ()
     throws Exception
@@ -173,7 +173,7 @@ public class TestIntrospectionUtil
         //direct
         Method m = IntrospectionUtil.findMethod(ServletC.class, "setPrivateC", __INTEGER_ARG, true, false);
         assertEquals(m, privateCMethod);
-        
+
         //inheritance
         try
         {
@@ -185,7 +185,7 @@ public class TestIntrospectionUtil
             //expected
         }
     }
-    
+
     @Test
     public void testMethodProtected ()
     throws Exception
@@ -193,12 +193,12 @@ public class TestIntrospectionUtil
         // direct
         Method m = IntrospectionUtil.findMethod(ServletC.class, "setProtectedC", __INTEGER_ARG, true, false);
         assertEquals(m, protectedCMethod);
-        
+
         //inherited
         m = IntrospectionUtil.findMethod(ServletD.class, "setProtectedC", __INTEGER_ARG, true, false);
         assertEquals(m, protectedCMethod);
     }
-    
+
     @Test
     public void testMethodPublic ()
     throws Exception
@@ -206,12 +206,12 @@ public class TestIntrospectionUtil
         // direct
         Method m = IntrospectionUtil.findMethod(ServletC.class, "setPublicC",  __INTEGER_ARG, true, false);
         assertEquals(m, publicCMethod);
-        
+
         //inherited
        m = IntrospectionUtil.findMethod(ServletD.class, "setPublicC",  __INTEGER_ARG, true, false);
        assertEquals(m, publicCMethod);
     }
-    
+
     @Test
     public void testMethodDefault ()
     throws Exception
@@ -219,7 +219,7 @@ public class TestIntrospectionUtil
         // direct
         Method m = IntrospectionUtil.findMethod(ServletC.class, "setDefaultC", __INTEGER_ARG, true, false);
         assertEquals(m, defaultCMethod);
-        
+
         //inherited
         m = IntrospectionUtil.findMethod(ServletD.class, "setDefaultC", __INTEGER_ARG, true, false);
         assertEquals(m, defaultCMethod);
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/TrieTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/TrieTest.java
new file mode 100644
index 0000000..f7e924c
--- /dev/null
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/TrieTest.java
@@ -0,0 +1,245 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.Collection;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+
+ at RunWith(value = Parameterized.class)
+public class TrieTest
+{
+    @Parameterized.Parameters
+    public static Collection<Object[]> data()
+    {
+        Object[][] data = new Object[][]{
+            {new ArrayTrie<Integer>(128)},
+            {new TreeTrie<Integer>()},
+            {new ArrayTernaryTrie<Integer>(128)}
+        };
+        return Arrays.asList(data);
+    }
+
+    Trie<Integer> trie;
+    
+    public TrieTest(Trie<Integer> t)
+    {
+        trie=t;
+    }
+    
+    @Before
+    public void before()
+    {
+        trie.put("hello",1);
+        trie.put("He",2);
+        trie.put("HELL",3);
+        trie.put("wibble",4);
+        trie.put("Wobble",5);
+        trie.put("foo-bar",6);
+        trie.put("foo+bar",7);
+        trie.put("HELL4",8);
+        trie.put("",9);
+    }
+
+    @Test
+    public void testOverflow() throws Exception
+    {
+        int i=0;
+        while (true) 
+        {
+            if (++i>10000)
+                break; // must not be fixed size
+            if (!trie.put("prefix" + i, i))
+            {
+                Assert.assertTrue(trie.isFull());
+                break;
+            }
+        }
+        
+        Assert.assertTrue(!trie.isFull() || !trie.put("overflow", 0));
+    }
+    
+    @Test
+    public void testKeySet() throws Exception
+    {
+        Assert.assertTrue(trie.keySet().contains("hello"));
+        Assert.assertTrue(trie.keySet().contains("He"));
+        Assert.assertTrue(trie.keySet().contains("HELL"));
+        Assert.assertTrue(trie.keySet().contains("wibble"));
+        Assert.assertTrue(trie.keySet().contains("Wobble"));
+        Assert.assertTrue(trie.keySet().contains("foo-bar"));
+        Assert.assertTrue(trie.keySet().contains("foo+bar"));
+        Assert.assertTrue(trie.keySet().contains("HELL4"));
+        Assert.assertTrue(trie.keySet().contains(""));        
+    }
+    
+    @Test
+    public void testGetString() throws Exception
+    {
+        Assert.assertEquals(1,trie.get("hello").intValue());
+        Assert.assertEquals(2,trie.get("He").intValue());
+        Assert.assertEquals(3,trie.get("HELL").intValue());
+        Assert.assertEquals(4,trie.get("wibble").intValue());
+        Assert.assertEquals(5,trie.get("Wobble").intValue());
+        Assert.assertEquals(6,trie.get("foo-bar").intValue());
+        Assert.assertEquals(7,trie.get("foo+bar").intValue());
+        
+        Assert.assertEquals(1,trie.get("Hello").intValue());
+        Assert.assertEquals(2,trie.get("HE").intValue());
+        Assert.assertEquals(3,trie.get("heLL").intValue());
+        Assert.assertEquals(4,trie.get("Wibble").intValue());
+        Assert.assertEquals(5,trie.get("wobble").intValue());
+        Assert.assertEquals(6,trie.get("Foo-bar").intValue());
+        Assert.assertEquals(7,trie.get("FOO+bar").intValue());
+        Assert.assertEquals(8,trie.get("HELL4").intValue());
+        Assert.assertEquals(9,trie.get("").intValue());
+        
+        Assert.assertEquals(null,trie.get("helloworld"));
+        Assert.assertEquals(null,trie.get("Help"));
+        Assert.assertEquals(null,trie.get("Blah"));
+    }
+
+    @Test
+    public void testGetBuffer() throws Exception
+    {
+        Assert.assertEquals(1,trie.get(BufferUtil.toBuffer("xhellox"),1,5).intValue());
+        Assert.assertEquals(2,trie.get(BufferUtil.toBuffer("xhellox"),1,2).intValue());
+        Assert.assertEquals(3,trie.get(BufferUtil.toBuffer("xhellox"),1,4).intValue());
+        Assert.assertEquals(4,trie.get(BufferUtil.toBuffer("wibble"),0,6).intValue());
+        Assert.assertEquals(5,trie.get(BufferUtil.toBuffer("xWobble"),1,6).intValue());
+        Assert.assertEquals(6,trie.get(BufferUtil.toBuffer("xfoo-barx"),1,7).intValue());
+        Assert.assertEquals(7,trie.get(BufferUtil.toBuffer("xfoo+barx"),1,7).intValue());
+        
+        Assert.assertEquals(1,trie.get(BufferUtil.toBuffer("xhellox"),1,5).intValue());
+        Assert.assertEquals(2,trie.get(BufferUtil.toBuffer("xHELLox"),1,2).intValue());
+        Assert.assertEquals(3,trie.get(BufferUtil.toBuffer("xhellox"),1,4).intValue());
+        Assert.assertEquals(4,trie.get(BufferUtil.toBuffer("Wibble"),0,6).intValue());
+        Assert.assertEquals(5,trie.get(BufferUtil.toBuffer("xwobble"),1,6).intValue());
+        Assert.assertEquals(6,trie.get(BufferUtil.toBuffer("xFOO-barx"),1,7).intValue());
+        Assert.assertEquals(7,trie.get(BufferUtil.toBuffer("xFOO+barx"),1,7).intValue());
+
+        Assert.assertEquals(null,trie.get(BufferUtil.toBuffer("xHelloworldx"),1,10));
+        Assert.assertEquals(null,trie.get(BufferUtil.toBuffer("xHelpx"),1,4));
+        Assert.assertEquals(null,trie.get(BufferUtil.toBuffer("xBlahx"),1,4));
+    }
+    
+    @Test
+    public void testGetDirectBuffer() throws Exception
+    {
+        Assert.assertEquals(1,trie.get(BufferUtil.toDirectBuffer("xhellox"),1,5).intValue());
+        Assert.assertEquals(2,trie.get(BufferUtil.toDirectBuffer("xhellox"),1,2).intValue());
+        Assert.assertEquals(3,trie.get(BufferUtil.toDirectBuffer("xhellox"),1,4).intValue());
+        Assert.assertEquals(4,trie.get(BufferUtil.toDirectBuffer("wibble"),0,6).intValue());
+        Assert.assertEquals(5,trie.get(BufferUtil.toDirectBuffer("xWobble"),1,6).intValue());
+        Assert.assertEquals(6,trie.get(BufferUtil.toDirectBuffer("xfoo-barx"),1,7).intValue());
+        Assert.assertEquals(7,trie.get(BufferUtil.toDirectBuffer("xfoo+barx"),1,7).intValue());
+        
+        Assert.assertEquals(1,trie.get(BufferUtil.toDirectBuffer("xhellox"),1,5).intValue());
+        Assert.assertEquals(2,trie.get(BufferUtil.toDirectBuffer("xHELLox"),1,2).intValue());
+        Assert.assertEquals(3,trie.get(BufferUtil.toDirectBuffer("xhellox"),1,4).intValue());
+        Assert.assertEquals(4,trie.get(BufferUtil.toDirectBuffer("Wibble"),0,6).intValue());
+        Assert.assertEquals(5,trie.get(BufferUtil.toDirectBuffer("xwobble"),1,6).intValue());
+        Assert.assertEquals(6,trie.get(BufferUtil.toDirectBuffer("xFOO-barx"),1,7).intValue());
+        Assert.assertEquals(7,trie.get(BufferUtil.toDirectBuffer("xFOO+barx"),1,7).intValue());
+
+        Assert.assertEquals(null,trie.get(BufferUtil.toDirectBuffer("xHelloworldx"),1,10));
+        Assert.assertEquals(null,trie.get(BufferUtil.toDirectBuffer("xHelpx"),1,4));
+        Assert.assertEquals(null,trie.get(BufferUtil.toDirectBuffer("xBlahx"),1,4));
+    }
+    
+
+    @Test
+    public void testGetBestArray() throws Exception
+    {
+        Assert.assertEquals(1,trie.getBest(StringUtil.getUtf8Bytes("xhelloxxxx"),1,8).intValue());
+        Assert.assertEquals(2,trie.getBest(StringUtil.getUtf8Bytes("xhelxoxxxx"),1,8).intValue());
+        Assert.assertEquals(3,trie.getBest(StringUtil.getUtf8Bytes("xhellxxxxx"),1,8).intValue()); 
+        Assert.assertEquals(6,trie.getBest(StringUtil.getUtf8Bytes("xfoo-barxx"),1,8).intValue()); 
+        Assert.assertEquals(8,trie.getBest(StringUtil.getUtf8Bytes("xhell4xxxx"),1,8).intValue()); 
+        
+        Assert.assertEquals(1,trie.getBest(StringUtil.getUtf8Bytes("xHELLOxxxx"),1,8).intValue());
+        Assert.assertEquals(2,trie.getBest(StringUtil.getUtf8Bytes("xHELxoxxxx"),1,8).intValue());
+        Assert.assertEquals(3,trie.getBest(StringUtil.getUtf8Bytes("xHELLxxxxx"),1,8).intValue()); 
+        Assert.assertEquals(6,trie.getBest(StringUtil.getUtf8Bytes("xfoo-BARxx"),1,8).intValue()); 
+        Assert.assertEquals(8,trie.getBest(StringUtil.getUtf8Bytes("xHELL4xxxx"),1,8).intValue());  
+        Assert.assertEquals(9,trie.getBest(StringUtil.getUtf8Bytes("xZZZZZxxxx"),1,8).intValue());  
+    }
+
+    @Test
+    public void testGetBestBuffer() throws Exception
+    {
+        Assert.assertEquals(1,trie.getBest(BufferUtil.toBuffer("xhelloxxxx"),1,8).intValue());
+        Assert.assertEquals(2,trie.getBest(BufferUtil.toBuffer("xhelxoxxxx"),1,8).intValue());
+        Assert.assertEquals(3,trie.getBest(BufferUtil.toBuffer("xhellxxxxx"),1,8).intValue()); 
+        Assert.assertEquals(6,trie.getBest(BufferUtil.toBuffer("xfoo-barxx"),1,8).intValue()); 
+        Assert.assertEquals(8,trie.getBest(BufferUtil.toBuffer("xhell4xxxx"),1,8).intValue()); 
+        
+        Assert.assertEquals(1,trie.getBest(BufferUtil.toBuffer("xHELLOxxxx"),1,8).intValue());
+        Assert.assertEquals(2,trie.getBest(BufferUtil.toBuffer("xHELxoxxxx"),1,8).intValue());
+        Assert.assertEquals(3,trie.getBest(BufferUtil.toBuffer("xHELLxxxxx"),1,8).intValue()); 
+        Assert.assertEquals(6,trie.getBest(BufferUtil.toBuffer("xfoo-BARxx"),1,8).intValue()); 
+        Assert.assertEquals(8,trie.getBest(BufferUtil.toBuffer("xHELL4xxxx"),1,8).intValue());  
+        Assert.assertEquals(9,trie.getBest(BufferUtil.toBuffer("xZZZZZxxxx"),1,8).intValue());  
+        
+        ByteBuffer buffer = (ByteBuffer)BufferUtil.toBuffer("xhelloxxxxxxx").position(2);
+        Assert.assertEquals(1,trie.getBest(buffer,-1,10).intValue());
+    }
+
+    @Test
+    public void testGetBestDirectBuffer() throws Exception
+    {
+        Assert.assertEquals(1,trie.getBest(BufferUtil.toDirectBuffer("xhelloxxxx"),1,8).intValue());
+        Assert.assertEquals(2,trie.getBest(BufferUtil.toDirectBuffer("xhelxoxxxx"),1,8).intValue());
+        Assert.assertEquals(3,trie.getBest(BufferUtil.toDirectBuffer("xhellxxxxx"),1,8).intValue()); 
+        Assert.assertEquals(6,trie.getBest(BufferUtil.toDirectBuffer("xfoo-barxx"),1,8).intValue()); 
+        Assert.assertEquals(8,trie.getBest(BufferUtil.toDirectBuffer("xhell4xxxx"),1,8).intValue()); 
+        
+        Assert.assertEquals(1,trie.getBest(BufferUtil.toDirectBuffer("xHELLOxxxx"),1,8).intValue());
+        Assert.assertEquals(2,trie.getBest(BufferUtil.toDirectBuffer("xHELxoxxxx"),1,8).intValue());
+        Assert.assertEquals(3,trie.getBest(BufferUtil.toDirectBuffer("xHELLxxxxx"),1,8).intValue()); 
+        Assert.assertEquals(6,trie.getBest(BufferUtil.toDirectBuffer("xfoo-BARxx"),1,8).intValue()); 
+        Assert.assertEquals(8,trie.getBest(BufferUtil.toDirectBuffer("xHELL4xxxx"),1,8).intValue());  
+        Assert.assertEquals(9,trie.getBest(BufferUtil.toDirectBuffer("xZZZZZxxxx"),1,8).intValue());  
+        
+        ByteBuffer buffer = (ByteBuffer)BufferUtil.toDirectBuffer("xhelloxxxxxxx").position(2);
+        Assert.assertEquals(1,trie.getBest(buffer,-1,10).intValue());
+    }
+    
+    @Test 
+    public void testFull() throws Exception
+    {
+       if (!(trie instanceof ArrayTrie<?> || trie instanceof ArrayTernaryTrie<?>))
+           return;
+       
+       Assert.assertFalse(trie.put("Large: This is a really large key and should blow the maximum size of the array trie as lots of nodes should already be used.",99));
+       testGetString();
+       testGetBestArray();
+       testGetBestBuffer();
+    }
+    
+    
+}
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/TypeUtilTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/TypeUtilTest.java
index fc7da6a..93bed66 100644
--- a/jetty-util/src/test/java/org/eclipse/jetty/util/TypeUtilTest.java
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/TypeUtilTest.java
@@ -18,8 +18,8 @@
 
 package org.eclipse.jetty.util;
 
-import junit.framework.Assert;
 
+import org.junit.Assert;
 import org.junit.Test;
 
 public class TypeUtilTest
@@ -90,4 +90,33 @@ public class TypeUtilTest
         Assert.assertEquals("123456789ABCDEF0",b.toString());
     }
 
+    @Test
+    public void testIsTrue() throws Exception
+    {
+        Assert.assertTrue(TypeUtil.isTrue(Boolean.TRUE));
+        Assert.assertTrue(TypeUtil.isTrue(true));
+        Assert.assertTrue(TypeUtil.isTrue("true"));
+        Assert.assertTrue(TypeUtil.isTrue(new Object(){@Override public String toString(){return "true";}}));
+        
+        Assert.assertFalse(TypeUtil.isTrue(Boolean.FALSE));
+        Assert.assertFalse(TypeUtil.isTrue(false));
+        Assert.assertFalse(TypeUtil.isTrue("false"));
+        Assert.assertFalse(TypeUtil.isTrue("blargle"));
+        Assert.assertFalse(TypeUtil.isTrue(new Object(){@Override public String toString(){return "false";}}));
+    }
+
+    @Test
+    public void testIsFalse() throws Exception
+    {
+        Assert.assertTrue(TypeUtil.isFalse(Boolean.FALSE));
+        Assert.assertTrue(TypeUtil.isFalse(false));
+        Assert.assertTrue(TypeUtil.isFalse("false"));
+        Assert.assertTrue(TypeUtil.isFalse(new Object(){@Override public String toString(){return "false";}}));
+        
+        Assert.assertFalse(TypeUtil.isFalse(Boolean.TRUE));
+        Assert.assertFalse(TypeUtil.isFalse(true));
+        Assert.assertFalse(TypeUtil.isFalse("true"));
+        Assert.assertFalse(TypeUtil.isFalse("blargle"));
+        Assert.assertFalse(TypeUtil.isFalse(new Object(){@Override public String toString(){return "true";}}));
+    }
 }
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/URITest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/URITest.java
deleted file mode 100644
index 9230995..0000000
--- a/jetty-util/src/test/java/org/eclipse/jetty/util/URITest.java
+++ /dev/null
@@ -1,244 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.util;
-
-import static org.junit.Assert.assertEquals;
-
-import org.junit.Test;
-
-
-/* ------------------------------------------------------------ */
-/** Util meta Tests.
- * 
- */
-public class URITest
-{
-    /* ------------------------------------------------------------ */
-    @Test
-    public void testEncodePath()
-    {
-        // test basic encode/decode
-        StringBuilder buf = new StringBuilder();
-        
-        
-        buf.setLength(0);
-        URIUtil.encodePath(buf,"/foo%23+;,:=/b a r/?info ");
-        assertEquals("/foo%2523+%3B,:=/b%20a%20r/%3Finfo%20",buf.toString());
-
-        assertEquals("/foo%2523+%3B,:=/b%20a%20r/%3Finfo%20",URIUtil.encodePath("/foo%23+;,:=/b a r/?info "));
-                
-        buf.setLength(0);
-        URIUtil.encodeString(buf,"foo%23;,:=b a r",";,= ");
-        assertEquals("foo%2523%3b%2c:%3db%20a%20r",buf.toString());
-        
-        buf.setLength(0);
-        URIUtil.encodePath(buf,"/context/'list'/\"me\"/;<script>window.alert('xss');</script>");
-        assertEquals("/context/%27list%27/%22me%22/%3B%3Cscript%3Ewindow.alert(%27xss%27)%3B%3C/script%3E", buf.toString());
-    }    
-    
-    /* ------------------------------------------------------------ */
-    @Test
-    public void testDecodePath()
-    {
-        assertEquals("foo%23;,:=b a r",URIUtil.decodePath("foo%2523%3b%2c:%3db%20a%20r;rubbish")); 
-        assertEquals("foo%23;,:=b a r=",URIUtil.decodePath("xxxfoo%2523%3b%2c:%3db%20a%20r%3Dxxx;rubbish".getBytes(),3,30));
-        assertEquals("fää%23;,:=b a r=",URIUtil.decodePath("fää%2523%3b%2c:%3db%20a%20r%3D"));   
-        assertEquals("f\u0629\u0629%23;,:=b a r",URIUtil.decodePath("f%d8%a9%d8%a9%2523%3b%2c:%3db%20a%20r"));   
-    }
-    
-    /* ------------------------------------------------------------ */
-    @Test
-    public void testAddPaths()
-    {
-        assertEquals("null+null", URIUtil.addPaths(null,null),null);
-        assertEquals("null+", URIUtil.addPaths(null,""),"");
-        assertEquals("null+bbb", URIUtil.addPaths(null,"bbb"),"bbb");
-        assertEquals("null+/", URIUtil.addPaths(null,"/"),"/");
-        assertEquals("null+/bbb", URIUtil.addPaths(null,"/bbb"),"/bbb");
-        
-        assertEquals("+null", URIUtil.addPaths("",null),"");
-        assertEquals("+", URIUtil.addPaths("",""),"");
-        assertEquals("+bbb", URIUtil.addPaths("","bbb"),"bbb");
-        assertEquals("+/", URIUtil.addPaths("","/"),"/");
-        assertEquals("+/bbb", URIUtil.addPaths("","/bbb"),"/bbb");
-        
-        assertEquals("aaa+null", URIUtil.addPaths("aaa",null),"aaa");
-        assertEquals("aaa+", URIUtil.addPaths("aaa",""),"aaa");
-        assertEquals("aaa+bbb", URIUtil.addPaths("aaa","bbb"),"aaa/bbb");
-        assertEquals("aaa+/", URIUtil.addPaths("aaa","/"),"aaa/");
-        assertEquals("aaa+/bbb", URIUtil.addPaths("aaa","/bbb"),"aaa/bbb");
-        
-        assertEquals("/+null", URIUtil.addPaths("/",null),"/");
-        assertEquals("/+", URIUtil.addPaths("/",""),"/");
-        assertEquals("/+bbb", URIUtil.addPaths("/","bbb"),"/bbb");
-        assertEquals("/+/", URIUtil.addPaths("/","/"),"/");
-        assertEquals("/+/bbb", URIUtil.addPaths("/","/bbb"),"/bbb");
-        
-        assertEquals("aaa/+null", URIUtil.addPaths("aaa/",null),"aaa/");
-        assertEquals("aaa/+", URIUtil.addPaths("aaa/",""),"aaa/");
-        assertEquals("aaa/+bbb", URIUtil.addPaths("aaa/","bbb"),"aaa/bbb");
-        assertEquals("aaa/+/", URIUtil.addPaths("aaa/","/"),"aaa/");
-        assertEquals("aaa/+/bbb", URIUtil.addPaths("aaa/","/bbb"),"aaa/bbb");
-        
-        assertEquals(";JS+null", URIUtil.addPaths(";JS",null),";JS");
-        assertEquals(";JS+", URIUtil.addPaths(";JS",""),";JS");
-        assertEquals(";JS+bbb", URIUtil.addPaths(";JS","bbb"),"bbb;JS");
-        assertEquals(";JS+/", URIUtil.addPaths(";JS","/"),"/;JS");
-        assertEquals(";JS+/bbb", URIUtil.addPaths(";JS","/bbb"),"/bbb;JS");
-        
-        assertEquals("aaa;JS+null", URIUtil.addPaths("aaa;JS",null),"aaa;JS");
-        assertEquals("aaa;JS+", URIUtil.addPaths("aaa;JS",""),"aaa;JS");
-        assertEquals("aaa;JS+bbb", URIUtil.addPaths("aaa;JS","bbb"),"aaa/bbb;JS");
-        assertEquals("aaa;JS+/", URIUtil.addPaths("aaa;JS","/"),"aaa/;JS");
-        assertEquals("aaa;JS+/bbb", URIUtil.addPaths("aaa;JS","/bbb"),"aaa/bbb;JS");
-        
-        assertEquals("aaa;JS+null", URIUtil.addPaths("aaa/;JS",null),"aaa/;JS");
-        assertEquals("aaa;JS+", URIUtil.addPaths("aaa/;JS",""),"aaa/;JS");
-        assertEquals("aaa;JS+bbb", URIUtil.addPaths("aaa/;JS","bbb"),"aaa/bbb;JS");
-        assertEquals("aaa;JS+/", URIUtil.addPaths("aaa/;JS","/"),"aaa/;JS");
-        assertEquals("aaa;JS+/bbb", URIUtil.addPaths("aaa/;JS","/bbb"),"aaa/bbb;JS");
-        
-        assertEquals("?A=1+null", URIUtil.addPaths("?A=1",null),"?A=1");
-        assertEquals("?A=1+", URIUtil.addPaths("?A=1",""),"?A=1");
-        assertEquals("?A=1+bbb", URIUtil.addPaths("?A=1","bbb"),"bbb?A=1");
-        assertEquals("?A=1+/", URIUtil.addPaths("?A=1","/"),"/?A=1");
-        assertEquals("?A=1+/bbb", URIUtil.addPaths("?A=1","/bbb"),"/bbb?A=1");
-        
-        assertEquals("aaa?A=1+null", URIUtil.addPaths("aaa?A=1",null),"aaa?A=1");
-        assertEquals("aaa?A=1+", URIUtil.addPaths("aaa?A=1",""),"aaa?A=1");
-        assertEquals("aaa?A=1+bbb", URIUtil.addPaths("aaa?A=1","bbb"),"aaa/bbb?A=1");
-        assertEquals("aaa?A=1+/", URIUtil.addPaths("aaa?A=1","/"),"aaa/?A=1");
-        assertEquals("aaa?A=1+/bbb", URIUtil.addPaths("aaa?A=1","/bbb"),"aaa/bbb?A=1");
-        
-        assertEquals("aaa?A=1+null", URIUtil.addPaths("aaa/?A=1",null),"aaa/?A=1");
-        assertEquals("aaa?A=1+", URIUtil.addPaths("aaa/?A=1",""),"aaa/?A=1");
-        assertEquals("aaa?A=1+bbb", URIUtil.addPaths("aaa/?A=1","bbb"),"aaa/bbb?A=1");
-        assertEquals("aaa?A=1+/", URIUtil.addPaths("aaa/?A=1","/"),"aaa/?A=1");
-        assertEquals("aaa?A=1+/bbb", URIUtil.addPaths("aaa/?A=1","/bbb"),"aaa/bbb?A=1");
-        
-        assertEquals(";JS?A=1+null", URIUtil.addPaths(";JS?A=1",null),";JS?A=1");
-        assertEquals(";JS?A=1+", URIUtil.addPaths(";JS?A=1",""),";JS?A=1");
-        assertEquals(";JS?A=1+bbb", URIUtil.addPaths(";JS?A=1","bbb"),"bbb;JS?A=1");
-        assertEquals(";JS?A=1+/", URIUtil.addPaths(";JS?A=1","/"),"/;JS?A=1");
-        assertEquals(";JS?A=1+/bbb", URIUtil.addPaths(";JS?A=1","/bbb"),"/bbb;JS?A=1");
-        
-        assertEquals("aaa;JS?A=1+null", URIUtil.addPaths("aaa;JS?A=1",null),"aaa;JS?A=1");
-        assertEquals("aaa;JS?A=1+", URIUtil.addPaths("aaa;JS?A=1",""),"aaa;JS?A=1");
-        assertEquals("aaa;JS?A=1+bbb", URIUtil.addPaths("aaa;JS?A=1","bbb"),"aaa/bbb;JS?A=1");
-        assertEquals("aaa;JS?A=1+/", URIUtil.addPaths("aaa;JS?A=1","/"),"aaa/;JS?A=1");
-        assertEquals("aaa;JS?A=1+/bbb", URIUtil.addPaths("aaa;JS?A=1","/bbb"),"aaa/bbb;JS?A=1");
-        
-        assertEquals("aaa;JS?A=1+null", URIUtil.addPaths("aaa/;JS?A=1",null),"aaa/;JS?A=1");
-        assertEquals("aaa;JS?A=1+", URIUtil.addPaths("aaa/;JS?A=1",""),"aaa/;JS?A=1");
-        assertEquals("aaa;JS?A=1+bbb", URIUtil.addPaths("aaa/;JS?A=1","bbb"),"aaa/bbb;JS?A=1");
-        assertEquals("aaa;JS?A=1+/", URIUtil.addPaths("aaa/;JS?A=1","/"),"aaa/;JS?A=1");
-        assertEquals("aaa;JS?A=1+/bbb", URIUtil.addPaths("aaa/;JS?A=1","/bbb"),"aaa/bbb;JS?A=1");
-
-    }
-
-    /* ------------------------------------------------------------ */
-    @Test
-    public void testCompactPath()
-    {
-        assertEquals("/foo/bar", URIUtil.compactPath("/foo/bar"));
-        assertEquals("/foo/bar?a=b//c", URIUtil.compactPath("/foo/bar?a=b//c"));
-
-        assertEquals("/foo/bar", URIUtil.compactPath("//foo//bar"));
-        assertEquals("/foo/bar?a=b//c", URIUtil.compactPath("//foo//bar?a=b//c"));
-        
-        assertEquals("/foo/bar", URIUtil.compactPath("/foo///bar"));
-        assertEquals("/foo/bar?a=b//c", URIUtil.compactPath("/foo///bar?a=b//c"));
-    }
-    
-    /* ------------------------------------------------------------ */
-    @Test
-    public void testParentPath()
-    {
-        assertEquals("parent /aaa/bbb/","/aaa/", URIUtil.parentPath("/aaa/bbb/"));
-        assertEquals("parent /aaa/bbb","/aaa/", URIUtil.parentPath("/aaa/bbb"));
-        assertEquals("parent /aaa/","/", URIUtil.parentPath("/aaa/"));
-        assertEquals("parent /aaa","/", URIUtil.parentPath("/aaa"));
-        assertEquals("parent /",null, URIUtil.parentPath("/"));
-        assertEquals("parent null",null, URIUtil.parentPath(null));
-
-    }
-    
-    /* ------------------------------------------------------------ */
-    @Test
-    public void testCanonicalPath()
-    {
-        String[][] canonical = 
-        {
-            {"/aaa/bbb/","/aaa/bbb/"},
-            {"/aaa//bbb/","/aaa//bbb/"},
-            {"/aaa///bbb/","/aaa///bbb/"},
-            {"/aaa/./bbb/","/aaa/bbb/"},
-            {"/aaa/../bbb/","/bbb/"},
-            {"/aaa/./../bbb/","/bbb/"},
-            {"/aaa/bbb/ccc/../../ddd/","/aaa/ddd/"},
-            {"./bbb/","bbb/"},
-            {"./aaa/../bbb/","bbb/"},
-            {"./",""},
-            {".//",".//"},
-            {".///",".///"},
-            {"/.","/"},
-            {"//.","//"},
-            {"///.","///"},
-            {"/","/"},
-            {"aaa/bbb","aaa/bbb"},
-            {"aaa/","aaa/"},
-            {"aaa","aaa"},
-            {"/aaa/bbb","/aaa/bbb"},
-            {"/aaa//bbb","/aaa//bbb"},
-            {"/aaa/./bbb","/aaa/bbb"},
-            {"/aaa/../bbb","/bbb"},
-            {"/aaa/./../bbb","/bbb"},
-            {"./bbb","bbb"},
-            {"./aaa/../bbb","bbb"},
-            {"aaa/bbb/..","aaa/"},
-            {"aaa/bbb/../","aaa/"},
-            {"/aaa//../bbb","/aaa/bbb"},
-            {"/aaa/./../bbb","/bbb"},
-            {"./",""},
-            {".",""},
-            {"",""},
-            {"..",null},
-            {"./..",null},
-            {"aaa/../..",null},
-            {"/foo/bar/../../..",null},
-            {"/../foo",null},
-            {"/foo/.","/foo/"},
-            {"a","a"},
-            {"a/","a/"},
-            {"a/.","a/"},
-            {"a/..",""},
-            {"a/../..",null},
-            {"/foo/../bar//","/bar//"},
-        };
-
-        for (int t=0;t<canonical.length;t++)
-            assertEquals( "canonical "+canonical[t][0],
-                          canonical[t][1],
-                          URIUtil.canonicalPath(canonical[t][0])
-                          );
-        
-    }
-    
-
-}
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/URIUtilTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/URIUtilTest.java
new file mode 100644
index 0000000..68409b9
--- /dev/null
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/URIUtilTest.java
@@ -0,0 +1,274 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.nio.charset.StandardCharsets;
+
+import org.junit.Test;
+
+
+/* ------------------------------------------------------------ */
+/** Util meta Tests.
+ *
+ */
+public class URIUtilTest
+{
+    /* ------------------------------------------------------------ */
+    @Test
+    public void testEncodePath()
+    {
+        // test basic encode/decode
+        StringBuilder buf = new StringBuilder();
+
+
+        buf.setLength(0);
+        URIUtil.encodePath(buf,"/foo%23+;,:=/b a r/?info ");
+        assertEquals("/foo%2523+%3B,:=/b%20a%20r/%3Finfo%20",buf.toString());
+
+        assertEquals("/foo%2523+%3B,:=/b%20a%20r/%3Finfo%20",URIUtil.encodePath("/foo%23+;,:=/b a r/?info "));
+
+        buf.setLength(0);
+        URIUtil.encodeString(buf,"foo%23;,:=b a r",";,= ");
+        assertEquals("foo%2523%3b%2c:%3db%20a%20r",buf.toString());
+
+        buf.setLength(0);
+        URIUtil.encodePath(buf,"/context/'list'/\"me\"/;<script>window.alert('xss');</script>");
+        assertEquals("/context/%27list%27/%22me%22/%3B%3Cscript%3Ewindow.alert(%27xss%27)%3B%3C/script%3E", buf.toString());
+    }
+
+    /* ------------------------------------------------------------ */
+    @Test
+    public void testDecodePath()
+    {
+        assertEquals("foo%23;,:=b a r",URIUtil.decodePath("foo%2523%3b%2c:%3db%20a%20r;rubbish"));
+        assertEquals("foo%23;,:=b a r=",URIUtil.decodePath("xxxfoo%2523%3b%2c:%3db%20a%20r%3Dxxx;rubbish".getBytes(),3,30));
+        assertEquals("fää%23;,:=b a r=",URIUtil.decodePath("fää%2523%3b%2c:%3db%20a%20r%3D"));
+        assertEquals("f\u0629\u0629%23;,:=b a r",URIUtil.decodePath("f%d8%a9%d8%a9%2523%3b%2c:%3db%20a%20r"));
+        
+        // Test for null character (real world ugly test case)
+        byte oddBytes[] = { '/', 0x00, '/' };
+        String odd = new String(oddBytes, StandardCharsets.ISO_8859_1);
+        assertEquals(odd,URIUtil.decodePath("/%00/"));
+    }
+
+    /* ------------------------------------------------------------ */
+    @Test
+    public void testAddPaths()
+    {
+        assertEquals("null+null", URIUtil.addPaths(null,null),null);
+        assertEquals("null+", URIUtil.addPaths(null,""),"");
+        assertEquals("null+bbb", URIUtil.addPaths(null,"bbb"),"bbb");
+        assertEquals("null+/", URIUtil.addPaths(null,"/"),"/");
+        assertEquals("null+/bbb", URIUtil.addPaths(null,"/bbb"),"/bbb");
+
+        assertEquals("+null", URIUtil.addPaths("",null),"");
+        assertEquals("+", URIUtil.addPaths("",""),"");
+        assertEquals("+bbb", URIUtil.addPaths("","bbb"),"bbb");
+        assertEquals("+/", URIUtil.addPaths("","/"),"/");
+        assertEquals("+/bbb", URIUtil.addPaths("","/bbb"),"/bbb");
+
+        assertEquals("aaa+null", URIUtil.addPaths("aaa",null),"aaa");
+        assertEquals("aaa+", URIUtil.addPaths("aaa",""),"aaa");
+        assertEquals("aaa+bbb", URIUtil.addPaths("aaa","bbb"),"aaa/bbb");
+        assertEquals("aaa+/", URIUtil.addPaths("aaa","/"),"aaa/");
+        assertEquals("aaa+/bbb", URIUtil.addPaths("aaa","/bbb"),"aaa/bbb");
+
+        assertEquals("/+null", URIUtil.addPaths("/",null),"/");
+        assertEquals("/+", URIUtil.addPaths("/",""),"/");
+        assertEquals("/+bbb", URIUtil.addPaths("/","bbb"),"/bbb");
+        assertEquals("/+/", URIUtil.addPaths("/","/"),"/");
+        assertEquals("/+/bbb", URIUtil.addPaths("/","/bbb"),"/bbb");
+
+        assertEquals("aaa/+null", URIUtil.addPaths("aaa/",null),"aaa/");
+        assertEquals("aaa/+", URIUtil.addPaths("aaa/",""),"aaa/");
+        assertEquals("aaa/+bbb", URIUtil.addPaths("aaa/","bbb"),"aaa/bbb");
+        assertEquals("aaa/+/", URIUtil.addPaths("aaa/","/"),"aaa/");
+        assertEquals("aaa/+/bbb", URIUtil.addPaths("aaa/","/bbb"),"aaa/bbb");
+
+        assertEquals(";JS+null", URIUtil.addPaths(";JS",null),";JS");
+        assertEquals(";JS+", URIUtil.addPaths(";JS",""),";JS");
+        assertEquals(";JS+bbb", URIUtil.addPaths(";JS","bbb"),"bbb;JS");
+        assertEquals(";JS+/", URIUtil.addPaths(";JS","/"),"/;JS");
+        assertEquals(";JS+/bbb", URIUtil.addPaths(";JS","/bbb"),"/bbb;JS");
+
+        assertEquals("aaa;JS+null", URIUtil.addPaths("aaa;JS",null),"aaa;JS");
+        assertEquals("aaa;JS+", URIUtil.addPaths("aaa;JS",""),"aaa;JS");
+        assertEquals("aaa;JS+bbb", URIUtil.addPaths("aaa;JS","bbb"),"aaa/bbb;JS");
+        assertEquals("aaa;JS+/", URIUtil.addPaths("aaa;JS","/"),"aaa/;JS");
+        assertEquals("aaa;JS+/bbb", URIUtil.addPaths("aaa;JS","/bbb"),"aaa/bbb;JS");
+
+        assertEquals("aaa;JS+null", URIUtil.addPaths("aaa/;JS",null),"aaa/;JS");
+        assertEquals("aaa;JS+", URIUtil.addPaths("aaa/;JS",""),"aaa/;JS");
+        assertEquals("aaa;JS+bbb", URIUtil.addPaths("aaa/;JS","bbb"),"aaa/bbb;JS");
+        assertEquals("aaa;JS+/", URIUtil.addPaths("aaa/;JS","/"),"aaa/;JS");
+        assertEquals("aaa;JS+/bbb", URIUtil.addPaths("aaa/;JS","/bbb"),"aaa/bbb;JS");
+
+        assertEquals("?A=1+null", URIUtil.addPaths("?A=1",null),"?A=1");
+        assertEquals("?A=1+", URIUtil.addPaths("?A=1",""),"?A=1");
+        assertEquals("?A=1+bbb", URIUtil.addPaths("?A=1","bbb"),"bbb?A=1");
+        assertEquals("?A=1+/", URIUtil.addPaths("?A=1","/"),"/?A=1");
+        assertEquals("?A=1+/bbb", URIUtil.addPaths("?A=1","/bbb"),"/bbb?A=1");
+
+        assertEquals("aaa?A=1+null", URIUtil.addPaths("aaa?A=1",null),"aaa?A=1");
+        assertEquals("aaa?A=1+", URIUtil.addPaths("aaa?A=1",""),"aaa?A=1");
+        assertEquals("aaa?A=1+bbb", URIUtil.addPaths("aaa?A=1","bbb"),"aaa/bbb?A=1");
+        assertEquals("aaa?A=1+/", URIUtil.addPaths("aaa?A=1","/"),"aaa/?A=1");
+        assertEquals("aaa?A=1+/bbb", URIUtil.addPaths("aaa?A=1","/bbb"),"aaa/bbb?A=1");
+
+        assertEquals("aaa?A=1+null", URIUtil.addPaths("aaa/?A=1",null),"aaa/?A=1");
+        assertEquals("aaa?A=1+", URIUtil.addPaths("aaa/?A=1",""),"aaa/?A=1");
+        assertEquals("aaa?A=1+bbb", URIUtil.addPaths("aaa/?A=1","bbb"),"aaa/bbb?A=1");
+        assertEquals("aaa?A=1+/", URIUtil.addPaths("aaa/?A=1","/"),"aaa/?A=1");
+        assertEquals("aaa?A=1+/bbb", URIUtil.addPaths("aaa/?A=1","/bbb"),"aaa/bbb?A=1");
+
+        assertEquals(";JS?A=1+null", URIUtil.addPaths(";JS?A=1",null),";JS?A=1");
+        assertEquals(";JS?A=1+", URIUtil.addPaths(";JS?A=1",""),";JS?A=1");
+        assertEquals(";JS?A=1+bbb", URIUtil.addPaths(";JS?A=1","bbb"),"bbb;JS?A=1");
+        assertEquals(";JS?A=1+/", URIUtil.addPaths(";JS?A=1","/"),"/;JS?A=1");
+        assertEquals(";JS?A=1+/bbb", URIUtil.addPaths(";JS?A=1","/bbb"),"/bbb;JS?A=1");
+
+        assertEquals("aaa;JS?A=1+null", URIUtil.addPaths("aaa;JS?A=1",null),"aaa;JS?A=1");
+        assertEquals("aaa;JS?A=1+", URIUtil.addPaths("aaa;JS?A=1",""),"aaa;JS?A=1");
+        assertEquals("aaa;JS?A=1+bbb", URIUtil.addPaths("aaa;JS?A=1","bbb"),"aaa/bbb;JS?A=1");
+        assertEquals("aaa;JS?A=1+/", URIUtil.addPaths("aaa;JS?A=1","/"),"aaa/;JS?A=1");
+        assertEquals("aaa;JS?A=1+/bbb", URIUtil.addPaths("aaa;JS?A=1","/bbb"),"aaa/bbb;JS?A=1");
+
+        assertEquals("aaa;JS?A=1+null", URIUtil.addPaths("aaa/;JS?A=1",null),"aaa/;JS?A=1");
+        assertEquals("aaa;JS?A=1+", URIUtil.addPaths("aaa/;JS?A=1",""),"aaa/;JS?A=1");
+        assertEquals("aaa;JS?A=1+bbb", URIUtil.addPaths("aaa/;JS?A=1","bbb"),"aaa/bbb;JS?A=1");
+        assertEquals("aaa;JS?A=1+/", URIUtil.addPaths("aaa/;JS?A=1","/"),"aaa/;JS?A=1");
+        assertEquals("aaa;JS?A=1+/bbb", URIUtil.addPaths("aaa/;JS?A=1","/bbb"),"aaa/bbb;JS?A=1");
+
+    }
+
+    /* ------------------------------------------------------------ */
+    @Test
+    public void testCompactPath()
+    {
+        assertEquals("/foo/bar", URIUtil.compactPath("/foo/bar"));
+        assertEquals("/foo/bar?a=b//c", URIUtil.compactPath("/foo/bar?a=b//c"));
+
+        assertEquals("/foo/bar", URIUtil.compactPath("//foo//bar"));
+        assertEquals("/foo/bar?a=b//c", URIUtil.compactPath("//foo//bar?a=b//c"));
+
+        assertEquals("/foo/bar", URIUtil.compactPath("/foo///bar"));
+        assertEquals("/foo/bar?a=b//c", URIUtil.compactPath("/foo///bar?a=b//c"));
+    }
+
+    /* ------------------------------------------------------------ */
+    @Test
+    public void testParentPath()
+    {
+        assertEquals("parent /aaa/bbb/","/aaa/", URIUtil.parentPath("/aaa/bbb/"));
+        assertEquals("parent /aaa/bbb","/aaa/", URIUtil.parentPath("/aaa/bbb"));
+        assertEquals("parent /aaa/","/", URIUtil.parentPath("/aaa/"));
+        assertEquals("parent /aaa","/", URIUtil.parentPath("/aaa"));
+        assertEquals("parent /",null, URIUtil.parentPath("/"));
+        assertEquals("parent null",null, URIUtil.parentPath(null));
+
+    }
+    /* ------------------------------------------------------------ */
+    @Test
+    public void testEqualsIgnoreEncoding()
+    {
+        assertTrue(URIUtil.equalsIgnoreEncodings("http://example.com/foo/bar","http://example.com/foo/bar" ));
+        assertTrue(URIUtil.equalsIgnoreEncodings("/barry's","/barry%27s"));
+        assertTrue(URIUtil.equalsIgnoreEncodings("/barry%27s","/barry's"));
+        assertTrue(URIUtil.equalsIgnoreEncodings("/barry%27s","/barry%27s"));
+        assertTrue(URIUtil.equalsIgnoreEncodings("/b rry's","/b%20rry%27s"));
+        assertTrue(URIUtil.equalsIgnoreEncodings("/b rry%27s","/b%20rry's"));
+        assertTrue(URIUtil.equalsIgnoreEncodings("/b rry%27s","/b%20rry%27s"));
+        
+        assertTrue(URIUtil.equalsIgnoreEncodings("/foo%2fbar","/foo%2fbar"));
+        assertTrue(URIUtil.equalsIgnoreEncodings("/foo%2fbar","/foo%2Fbar"));
+        
+        assertFalse(URIUtil.equalsIgnoreEncodings("ABC", "abc"));
+        assertFalse(URIUtil.equalsIgnoreEncodings("/barry's","/barry%26s"));
+        
+        assertFalse(URIUtil.equalsIgnoreEncodings("/foo/bar","/foo%2fbar"));
+        assertFalse(URIUtil.equalsIgnoreEncodings("/foo2fbar","/foo/bar"));
+    }
+
+    /* ------------------------------------------------------------ */
+    @Test
+    public void testCanonicalPath()
+    {
+        String[][] canonical =
+        {
+            {"/aaa/bbb/","/aaa/bbb/"},
+            {"/aaa//bbb/","/aaa//bbb/"},
+            {"/aaa///bbb/","/aaa///bbb/"},
+            {"/aaa/./bbb/","/aaa/bbb/"},
+            {"/aaa/../bbb/","/bbb/"},
+            {"/aaa/./../bbb/","/bbb/"},
+            {"/aaa/bbb/ccc/../../ddd/","/aaa/ddd/"},
+            {"./bbb/","bbb/"},
+            {"./aaa/../bbb/","bbb/"},
+            {"./",""},
+            {".//",".//"},
+            {".///",".///"},
+            {"/.","/"},
+            {"//.","//"},
+            {"///.","///"},
+            {"/","/"},
+            {"aaa/bbb","aaa/bbb"},
+            {"aaa/","aaa/"},
+            {"aaa","aaa"},
+            {"/aaa/bbb","/aaa/bbb"},
+            {"/aaa//bbb","/aaa//bbb"},
+            {"/aaa/./bbb","/aaa/bbb"},
+            {"/aaa/../bbb","/bbb"},
+            {"/aaa/./../bbb","/bbb"},
+            {"./bbb","bbb"},
+            {"./aaa/../bbb","bbb"},
+            {"aaa/bbb/..","aaa/"},
+            {"aaa/bbb/../","aaa/"},
+            {"/aaa//../bbb","/aaa/bbb"},
+            {"/aaa/./../bbb","/bbb"},
+            {"./",""},
+            {".",""},
+            {"",""},
+            {"..",null},
+            {"./..",null},
+            {"aaa/../..",null},
+            {"/foo/bar/../../..",null},
+            {"/../foo",null},
+            {"/foo/.","/foo/"},
+            {"a","a"},
+            {"a/","a/"},
+            {"a/.","a/"},
+            {"a/..",""},
+            {"a/../..",null},
+            {"/foo/../bar//","/bar//"},
+        };
+
+        for (int t=0;t<canonical.length;t++)
+            assertEquals( "canonical "+canonical[t][0],
+                          canonical[t][1],
+                          URIUtil.canonicalPath(canonical[t][0])
+                          );
+
+    }
+
+
+}
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/URLEncodedTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/URLEncodedTest.java
index abc270c..3cf9f0b 100644
--- a/jetty-util/src/test/java/org/eclipse/jetty/util/URLEncodedTest.java
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/URLEncodedTest.java
@@ -22,38 +22,40 @@ import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
 import java.io.ByteArrayInputStream;
-import java.io.UnsupportedEncodingException;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
 
+import org.junit.Assert;
 import org.junit.Test;
 
 
 /* ------------------------------------------------------------ */
 /** Util meta Tests.
- * 
+ *
  */
 public class URLEncodedTest
 {
-    
+
     /* -------------------------------------------------------------- */
-    static 
+    static
     {
         /*
          * Uncomment to set setting the System property to something other than the default of UTF-8.
          * Beware however that you will have to @Ignore all the other tests other than testUrlEncodedStream!
-         
+
             System.setProperty("org.eclipse.jetty.util.UrlEncoding.charset", StringUtil.__ISO_8859_1);
          */
     }
 
-    
+
     /* -------------------------------------------------------------- */
     @Test
-    public void testUrlEncoded() throws UnsupportedEncodingException
+    public void testUrlEncoded()
     {
-          
+
         UrlEncoded url_encoded = new UrlEncoded();
         assertEquals("Initially not empty",0, url_encoded.size());
-        
+
         url_encoded.clear();
         url_encoded.decode("");
         assertEquals("Not empty after decode(\"\")",0, url_encoded.size());
@@ -63,19 +65,19 @@ public class URLEncodedTest
         assertEquals("simple param size",1, url_encoded.size());
         assertEquals("simple encode","Name1=Value1", url_encoded.encode());
         assertEquals("simple get","Value1", url_encoded.getString("Name1"));
-        
+
         url_encoded.clear();
         url_encoded.decode("Name2=");
         assertEquals("dangling param size",1, url_encoded.size());
         assertEquals("dangling encode","Name2", url_encoded.encode());
         assertEquals("dangling get","", url_encoded.getString("Name2"));
-    
+
         url_encoded.clear();
         url_encoded.decode("Name3");
         assertEquals("noValue param size",1, url_encoded.size());
         assertEquals("noValue encode","Name3", url_encoded.encode());
         assertEquals("noValue get","", url_encoded.getString("Name3"));
-    
+
         url_encoded.clear();
         url_encoded.decode("Name4=V\u0629lue+4%21");
         assertEquals("encoded param size",1, url_encoded.size());
@@ -87,14 +89,14 @@ public class URLEncodedTest
         assertEquals("encoded param size",1, url_encoded.size());
         assertEquals("encoded encode","Name4=Value%2B4%21", url_encoded.encode());
         assertEquals("encoded get","Value+4!", url_encoded.getString("Name4"));
-        
+
         url_encoded.clear();
         url_encoded.decode("Name4=Value+4%21%20%214");
         assertEquals("encoded param size",1, url_encoded.size());
         assertEquals("encoded encode","Name4=Value+4%21+%214", url_encoded.encode());
         assertEquals("encoded get","Value 4! !4", url_encoded.getString("Name4"));
 
-        
+
         url_encoded.clear();
         url_encoded.decode("Name5=aaa&Name6=bbb");
         assertEquals("multi param size",2, url_encoded.size());
@@ -104,7 +106,7 @@ public class URLEncodedTest
                    );
         assertEquals("multi get","aaa", url_encoded.getString("Name5"));
         assertEquals("multi get","bbb", url_encoded.getString("Name6"));
-    
+
         url_encoded.clear();
         url_encoded.decode("Name7=aaa&Name7=b%2Cb&Name7=ccc");
         assertEquals("multi encode","Name7=aaa&Name7=b%2Cb&Name7=ccc",url_encoded.encode());
@@ -118,86 +120,86 @@ public class URLEncodedTest
         assertEquals("encoded param size",1, url_encoded.size());
         assertEquals("encoded encode","Name8=xx%2C++yy++%2Czz", url_encoded.encode());
         assertEquals("encoded get", url_encoded.getString("Name8"),"xx,  yy  ,zz");
-        
+
         url_encoded.clear();
-        url_encoded.decode("Name11=%u30EDxxVerdi+%C6+og+2zz", "ISO-8859-1");
+        url_encoded.decode("Name11=%u30EDxxVerdi+%C6+og+2zz", StandardCharsets.ISO_8859_1);
         assertEquals("encoded param size",1, url_encoded.size());
         assertEquals("encoded get", "?xxVerdi \u00c6 og 2zz",url_encoded.getString("Name11"));
-        
+
         url_encoded.clear();
-        url_encoded.decode("Name12=%u30EDxxVerdi+%2F+og+2zz", "UTF-8");
+        url_encoded.decode("Name12=%u30EDxxVerdi+%2F+og+2zz", StandardCharsets.UTF_8);
         assertEquals("encoded param size",1, url_encoded.size());
         assertEquals("encoded get", url_encoded.getString("Name12"),"\u30edxxVerdi / og 2zz");
-        
+
         url_encoded.clear();
-        url_encoded.decode("Name14=%uXXXXa%GGb%+%c%+%d", "ISO-8859-1");
+        url_encoded.decode("Name14=%uXXXXa%GGb%+%c%+%d", StandardCharsets.ISO_8859_1);
         assertEquals("encoded param size",1, url_encoded.size());
         assertEquals("encoded get","?a?b?c?d", url_encoded.getString("Name14"));
-        
+
         url_encoded.clear();
-        url_encoded.decode("Name14=%uXXXX%GG%+%%+%", "UTF-8");
+        url_encoded.decode("Name14=%uXXXX%GG%+%%+%", StandardCharsets.UTF_8);
         assertEquals("encoded param size",1, url_encoded.size());
-        assertEquals("encoded get", url_encoded.getString("Name14"),"\ufffd\ufffd\ufffd\ufffd");
+        assertEquals("encoded get", "\ufffd\ufffd\ufffd\ufffd",url_encoded.getString("Name14"));
 
         
         /* Not every jvm supports this encoding */
-        
+
         if (java.nio.charset.Charset.isSupported("SJIS"))
         {
             url_encoded.clear();
-            url_encoded.decode("Name9=%u30ED%83e%83X%83g", "SJIS"); // "Test" in Japanese Katakana
+            url_encoded.decode("Name9=%u30ED%83e%83X%83g", Charset.forName("SJIS")); // "Test" in Japanese Katakana
             assertEquals("encoded param size",1, url_encoded.size());
             assertEquals("encoded get", "\u30ed\u30c6\u30b9\u30c8", url_encoded.getString("Name9"));   
         }
         else
             assertTrue("Charset SJIS not supported by jvm", true);
     }
-    
+
 
     /* -------------------------------------------------------------- */
     @Test
-    public void testBadEncoding() throws UnsupportedEncodingException
+    public void testBadEncoding()
     {
         UrlEncoded url_encoded = new UrlEncoded();
-        url_encoded.decode("Name15=xx%zzyy", "UTF-8");
+        url_encoded.decode("Name15=xx%zzyy", StandardCharsets.UTF_8);
         assertEquals("encoded param size",1, url_encoded.size());
         assertEquals("encoded get", "xx\ufffdyy", url_encoded.getString("Name15"));
 
-        byte[] bad="Name=%FF%FF%FF".getBytes("UTF-8");
+        byte[] bad="Name=%FF%FF%FF".getBytes(StandardCharsets.UTF_8);
         MultiMap<String> map = new MultiMap<String>();
         UrlEncoded.decodeUtf8To(bad,0,bad.length,map);
         assertEquals("encoded param size",1, map.size());
         assertEquals("encoded get", "\ufffd\ufffd\ufffd", map.getString("Name"));
         
         url_encoded.clear();
-        url_encoded.decode("Name=%FF%FF%FF", "UTF-8");
+        url_encoded.decode("Name=%FF%FF%FF", StandardCharsets.UTF_8);
         assertEquals("encoded param size",1, url_encoded.size());
         assertEquals("encoded get", "\ufffd\ufffd\ufffd", url_encoded.getString("Name"));
         
         url_encoded.clear();
-        url_encoded.decode("Name=%EF%EF%EF", "UTF-8");
+        url_encoded.decode("Name=%EF%EF%EF", StandardCharsets.UTF_8);
         assertEquals("encoded param size",1, url_encoded.size());
         assertEquals("encoded get", "\ufffd\ufffd", url_encoded.getString("Name"));
 
-        assertEquals("x",UrlEncoded.decodeString("x",0,1,"UTF-8"));
-        assertEquals("x\ufffd",UrlEncoded.decodeString("x%",0,2,"UTF-8"));
-        assertEquals("x\ufffd",UrlEncoded.decodeString("x%2",0,3,"UTF-8"));
-        assertEquals("x ",UrlEncoded.decodeString("x%20",0,4,"UTF-8"));
-
-        assertEquals("xxx",UrlEncoded.decodeString("xxx",0,3,"UTF-8"));
-        assertEquals("xxx\ufffd",UrlEncoded.decodeString("xxx%",0,4,"UTF-8"));
-        assertEquals("xxx\ufffd",UrlEncoded.decodeString("xxx%u",0,5,"UTF-8"));
-        assertEquals("xxx\ufffd",UrlEncoded.decodeString("xxx%u1",0,6,"UTF-8"));
-        assertEquals("xxx\ufffd",UrlEncoded.decodeString("xxx%u12",0,7,"UTF-8"));
-        assertEquals("xxx\ufffd",UrlEncoded.decodeString("xxx%u123",0,8,"UTF-8"));
-        assertEquals("xxx\u1234",UrlEncoded.decodeString("xxx%u1234",0,9,"UTF-8"));
+        assertEquals("x",UrlEncoded.decodeString("x",0,1,StandardCharsets.UTF_8));
+        assertEquals("x\ufffd",UrlEncoded.decodeString("x%",0,2,StandardCharsets.UTF_8));
+        assertEquals("x\ufffd",UrlEncoded.decodeString("x%2",0,3,StandardCharsets.UTF_8));
+        assertEquals("x ",UrlEncoded.decodeString("x%20",0,4,StandardCharsets.UTF_8));
+
+        assertEquals("xxx",UrlEncoded.decodeString("xxx",0,3,StandardCharsets.UTF_8));
+        assertEquals("xxx\ufffd",UrlEncoded.decodeString("xxx%",0,4,StandardCharsets.UTF_8));
+        assertEquals("xxx\ufffd",UrlEncoded.decodeString("xxx%u",0,5,StandardCharsets.UTF_8));
+        assertEquals("xxx\ufffd",UrlEncoded.decodeString("xxx%u1",0,6,StandardCharsets.UTF_8));
+        assertEquals("xxx\ufffd",UrlEncoded.decodeString("xxx%u12",0,7,StandardCharsets.UTF_8));
+        assertEquals("xxx\ufffd",UrlEncoded.decodeString("xxx%u123",0,8,StandardCharsets.UTF_8));
+        assertEquals("xxx\u1234",UrlEncoded.decodeString("xxx%u1234",0,9,StandardCharsets.UTF_8));
     }
-    
-    
+
+
     /* -------------------------------------------------------------- */
     @Test
     public void testUrlEncodedStream()
-    	throws Exception
+        throws Exception
     {
         String [][] charsets = new String[][]
         {
@@ -206,54 +208,53 @@ public class URLEncodedTest
            {StringUtil.__UTF8,StringUtil.__UTF8,"%30"},
            {StringUtil.__UTF16,StringUtil.__UTF16,"%00%30"},
         };
-        
+
 
         for (int i=0;i<charsets.length;i++)
         {
             ByteArrayInputStream in = new ByteArrayInputStream(("name\n=value+"+charsets[i][2]+"&name1=&name2&n\u00e3me3=value+3").getBytes(charsets[i][0]));
-            MultiMap m = new MultiMap();
-            UrlEncoded.decodeTo(in, m, charsets[i][1], -1,-1);
-            System.err.println(m);
-            assertEquals(i+" stream length",4,m.size());
-            assertEquals(i+" stream name\\n","value 0",m.getString("name\n"));
-            assertEquals(i+" stream name1","",m.getString("name1"));
-            assertEquals(i+" stream name2","",m.getString("name2"));
-            assertEquals(i+" stream n\u00e3me3","value 3",m.getString("n\u00e3me3"));
+            MultiMap<String> m = new MultiMap<>();
+            UrlEncoded.decodeTo(in, m, charsets[i][1]==null?null:Charset.forName(charsets[i][1]), -1,-1);
+            assertEquals(charsets[i][1]+" stream length",4,m.size());
+            assertEquals(charsets[i][1]+" stream name\\n","value 0",m.getString("name\n"));
+            assertEquals(charsets[i][1]+" stream name1","",m.getString("name1"));
+            assertEquals(charsets[i][1]+" stream name2","",m.getString("name2"));
+            assertEquals(charsets[i][1]+" stream n\u00e3me3","value 3",m.getString("n\u00e3me3"));
         }
-        
-        
+
+
         if (java.nio.charset.Charset.isSupported("Shift_JIS"))
         {
-            ByteArrayInputStream in2 = new ByteArrayInputStream ("name=%83e%83X%83g".getBytes(StringUtil.__ISO_8859_1));
-            MultiMap<String> m2 = new MultiMap<String>();
-            UrlEncoded.decodeTo(in2, m2, "Shift_JIS", -1,-1);
+            ByteArrayInputStream in2 = new ByteArrayInputStream("name=%83e%83X%83g".getBytes(StandardCharsets.ISO_8859_1));
+            MultiMap<String> m2 = new MultiMap<>();
+            UrlEncoded.decodeTo(in2, m2, Charset.forName("Shift_JIS"), -1,-1);
             assertEquals("stream length",1,m2.size());
             assertEquals("stream name","\u30c6\u30b9\u30c8",m2.getString("name"));
         }
         else
             assertTrue("Charset Shift_JIS not supported by jvm", true);
-     
+
     }
-    
-    
+
+
     /* -------------------------------------------------------------- */
     @Test
     public void testCharsetViaSystemProperty ()
-    throws Exception 
-    {        
+    throws Exception
+    {
         /*
          * Uncomment to test setting a non-UTF-8 default character encoding using the SystemProperty org.eclipse.jetty.util.UrlEncoding.charset.
          * You will also need to uncomment the static initializer that sets this SystemProperty near the top of this file.
-  
+
 
         ByteArrayInputStream in3 = new ByteArrayInputStream("name=libell%E9".getBytes(StringUtil.__ISO_8859_1));
         MultiMap m3 = new MultiMap();
         UrlEncoded.decodeTo(in3, m3, null, -1);
         assertEquals("stream name", "libell\u00E9", m3.getString("name"));
-        
-        */ 
+
+        */
     }
-    
+
     /* -------------------------------------------------------------- */
     @Test
     public void testUtf8()
@@ -264,26 +265,26 @@ public class URLEncodedTest
 
         url_encoded.clear();
         url_encoded.decode("text=%E0%B8%9F%E0%B8%AB%E0%B8%81%E0%B8%A7%E0%B8%94%E0%B8%B2%E0%B9%88%E0%B8%81%E0%B8%9F%E0%B8%A7%E0%B8%AB%E0%B8%AA%E0%B8%94%E0%B8%B2%E0%B9%88%E0%B8%AB%E0%B8%9F%E0%B8%81%E0%B8%A7%E0%B8%94%E0%B8%AA%E0%B8%B2%E0%B8%9F%E0%B8%81%E0%B8%AB%E0%B8%A3%E0%B8%94%E0%B9%89%E0%B8%9F%E0%B8%AB%E0%B8%99%E0%B8%81%E0%B8%A3%E0%B8%94%E0%B8%B5&Action=Submit");
-        
+
         String hex ="E0B89FE0B8ABE0B881E0B8A7E0B894E0B8B2E0B988E0B881E0B89FE0B8A7E0B8ABE0B8AAE0B894E0B8B2E0B988E0B8ABE0B89FE0B881E0B8A7E0B894E0B8AAE0B8B2E0B89FE0B881E0B8ABE0B8A3E0B894E0B989E0B89FE0B8ABE0B899E0B881E0B8A3E0B894E0B8B5";
         String expected = new String(TypeUtil.fromHexString(hex),"utf-8");
-        assertEquals(expected,url_encoded.get("text"));
+        Assert.assertEquals(expected,url_encoded.getString("text"));
     }
-    
+
     /* -------------------------------------------------------------- */
     @Test
     public void testNotUtf8() throws Exception
-    {   
+    {
         String query="name=X%c0%afZ";
-        
-        MultiMap<String> map = new MultiMap<String>();
-        
-        UrlEncoded.decodeUtf8To(query.getBytes(StringUtil.__ISO_8859_1),0,query.length(),map);
+
+        MultiMap<String> map = new MultiMap<>();
+        UrlEncoded.LOG.info("EXPECT 4 Not Valid UTF8 warnings...");
+        UrlEncoded.decodeUtf8To(query.getBytes(StandardCharsets.ISO_8859_1),0,query.length(),map);
         assertEquals("X"+Utf8Appendable.REPLACEMENT+Utf8Appendable.REPLACEMENT+"Z",map.getValue("name",0));
 
         map.clear();
 
-        UrlEncoded.decodeUtf8To(new ByteArrayInputStream(query.getBytes(StringUtil.__ISO_8859_1)),map,100,2);
+        UrlEncoded.decodeUtf8To(new ByteArrayInputStream(query.getBytes(StandardCharsets.ISO_8859_1)),map,100,2);
         assertEquals("X"+Utf8Appendable.REPLACEMENT+Utf8Appendable.REPLACEMENT+"Z",map.getValue("name",0));
     }
 }
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/UptimeTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/UptimeTest.java
new file mode 100644
index 0000000..6ddf83c
--- /dev/null
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/UptimeTest.java
@@ -0,0 +1,31 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import org.junit.Test;
+
+public class UptimeTest
+{
+    @Test
+    public void testUptime()
+    {
+        // should not throw an exception (if it does, the exception flows out and fails the testcase)
+        System.err.printf("Uptime = %,d%n",Uptime.getUptime());
+    }
+}
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/UrlEncodedUtf8Test.java b/jetty-util/src/test/java/org/eclipse/jetty/util/UrlEncodedUtf8Test.java
new file mode 100644
index 0000000..9dee7fe
--- /dev/null
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/UrlEncodedUtf8Test.java
@@ -0,0 +1,165 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class UrlEncodedUtf8Test
+{
+
+    static final Logger LOG=Log.getLogger(UrlEncodedUtf8Test.class);
+
+    
+    @Test
+    public void testIncompleteSequestAtTheEnd() throws Exception
+    {
+        byte[] bytes= { 97, 98, 61, 99, -50 };
+        String test=new String(bytes,StandardCharsets.UTF_8);
+        String expected = "c"+Utf8Appendable.REPLACEMENT;
+
+        fromByteArray(test,bytes,"ab",expected,false);
+        fromInputStream(test,bytes,"ab",expected,false);
+    }
+
+    @Test
+    public void testIncompleteSequestAtTheEnd2() throws Exception
+    {
+        byte[] bytes={ 97, 98, 61, -50 };
+        String test=new String(bytes,StandardCharsets.UTF_8);
+        String expected = ""+Utf8Appendable.REPLACEMENT;
+
+        fromByteArray(test,bytes,"ab",expected,false);
+        fromInputStream(test,bytes,"ab",expected,false);
+        
+    }
+    
+    @Test
+    public void testIncompleteSequestInName() throws Exception
+    {
+        byte[] bytes= { 101, -50, 61, 102, 103, 38, 97, 98, 61, 99, 100 };
+        String test=new String(bytes,StandardCharsets.UTF_8);
+        String name = "e"+Utf8Appendable.REPLACEMENT;
+        String value = "fg";
+
+        fromByteArray(test,bytes,name,value,false);
+        fromInputStream(test,bytes,name,value,false);
+    }
+    
+    @Test
+    public void testIncompleteSequestInValue() throws Exception
+    {
+        byte[] bytes= { 101, 102, 61, 103, -50, 38, 97, 98, 61, 99, 100 };
+        String test=new String(bytes,StandardCharsets.UTF_8);
+        String name = "ef";
+        String value = "g"+Utf8Appendable.REPLACEMENT;
+
+        fromByteArray(test,bytes,name,value,false);
+        fromInputStream(test,bytes,name,value,false);
+        
+    }
+
+    @Test
+    public void testCorrectUnicode() throws Exception
+    {
+        String chars="a=%u0061";
+        byte[] bytes= chars.getBytes(StandardCharsets.UTF_8);
+        String test=new String(bytes,StandardCharsets.UTF_8);
+        String name = "a";
+        String value = "a";
+
+        fromByteArray(test,bytes,name,value,false);
+        fromInputStream(test,bytes,name,value,false);
+        
+    }
+    
+    @Test
+    public void testIncompleteUnicode() throws Exception
+    {
+        String chars="a=%u0";
+        byte[] bytes= chars.getBytes(StandardCharsets.UTF_8);
+        String test=new String(bytes,StandardCharsets.UTF_8);
+        String name = "a";
+        String value = ""+Utf8Appendable.REPLACEMENT;
+
+        fromByteArray(test,bytes,name,value,false);
+        fromInputStream(test,bytes,name,value,false);
+        
+    }
+    
+    @Test
+    public void testIncompletePercent() throws Exception
+    {
+        String chars="a=%A";
+        byte[] bytes= chars.getBytes(StandardCharsets.UTF_8);
+        String test=new String(bytes,StandardCharsets.UTF_8);
+        String name = "a";
+        String value = ""+Utf8Appendable.REPLACEMENT;
+
+        fromByteArray(test,bytes,name,value,false);
+        fromInputStream(test,bytes,name,value,false);
+        
+    }
+
+    static void fromByteArray(String test,byte[] b,String field,String expected,boolean thrown) throws Exception
+    {
+        MultiMap<String> values=new MultiMap<>();
+        try
+        {
+            //safeDecodeUtf8To(b, 0, b.length, values);
+            UrlEncoded.decodeUtf8To(b, 0, b.length, values);
+            if (thrown)
+                Assert.fail();
+            Assert.assertEquals(test, expected, values.getString(field));
+        }
+        catch (Exception e)
+        {
+            if (!thrown)
+                throw e;
+            LOG.ignore(e);
+        }
+    }
+
+    static void fromInputStream(String test, byte[] b,String field, String expected,boolean thrown) throws Exception
+    {
+        InputStream is=new ByteArrayInputStream(b);
+        MultiMap<String> values=new MultiMap<>();
+        try
+        {
+            //safeDecodeUtf8To(is, values, 1000000, 10000000);
+            UrlEncoded.decodeUtf8To(is, values, 1000000, 10000000);
+            if (thrown)
+                Assert.fail();
+            Assert.assertEquals(test, expected, values.getString(field));
+        }
+        catch (Exception e)
+        {
+            if (!thrown)
+                throw e;
+            LOG.ignore(e);
+        }
+    }
+    
+}
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/Utf8LineParserTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/Utf8LineParserTest.java
new file mode 100644
index 0000000..f038d63
--- /dev/null
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/Utf8LineParserTest.java
@@ -0,0 +1,191 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.lessThan;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class Utf8LineParserTest
+{
+    private void appendUtf8(ByteBuffer buf, String line)
+    {
+        buf.put(ByteBuffer.wrap(StringUtil.getUtf8Bytes(line)));
+    }
+
+    private void assertEquals(List<String> expected, List<String> actual)
+    {
+        Assert.assertThat("Expected Line Count",actual.size(),is(expected.size()));
+        int len = expected.size();
+        for (int i = 0; i < len; i++)
+        {
+            String expectedLine = expected.get(i);
+            String actualLine = actual.get(i);
+
+            Assert.assertThat("Line[" + i + "]",actualLine,is(expectedLine));
+        }
+    }
+
+    /**
+     * Parse a basic line, with UNIX style line endings <code>"\n"</code>
+     */
+    @Test
+    public void testBasicParse()
+    {
+        ByteBuffer buf = ByteBuffer.allocate(64);
+        appendUtf8(buf,"Hello World\n");
+        BufferUtil.flipToFlush(buf,0);
+
+        Utf8LineParser utfparser = new Utf8LineParser();
+
+        String line = utfparser.parse(buf);
+        Assert.assertThat("Line",line,is("Hello World"));
+    }
+
+    /**
+     * Parsing of a single line of HTTP header style line ending <code>"\r\n"</code>
+     */
+    @Test
+    public void testHttpLineParse()
+    {
+        ByteBuffer buf = ByteBuffer.allocate(64);
+        appendUtf8(buf,"Hello World\r\n");
+        BufferUtil.flipToFlush(buf,0);
+
+        Utf8LineParser utfparser = new Utf8LineParser();
+
+        String line = utfparser.parse(buf);
+        Assert.assertThat("Line",line,is("Hello World"));
+    }
+
+    /**
+     * Parsing of an "in the wild" set HTTP response header lines.
+     */
+    @Test
+    public void testWildHttpRequestParse()
+    {
+        // Arbitrary Http Response Headers seen in the wild.
+        // Request URI -> http://www.eclipse.org/jetty/
+        List<String> expected = new ArrayList<>();
+        expected.add("HEAD /jetty/ HTTP/1.0");
+        expected.add("User-Agent: \"Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1.6) Gecko/20060601 Firefox/2.0.0.6 (Ubuntu-feisty)\"");
+        expected.add("Accept: */*");
+        expected.add("Host: www.eclipse.org");
+        expected.add("Connection: Keep-Alive");
+        expected.add("");
+
+        // Prepare Buffer
+        ByteBuffer buf = ByteBuffer.allocate(512);
+        for (String line : expected)
+        {
+            appendUtf8(buf,line + "\r\n");
+        }
+
+        BufferUtil.flipToFlush(buf,0);
+
+        // Parse Buffer
+        Utf8LineParser utfparser = new Utf8LineParser();
+
+        List<String> actual = new ArrayList<>();
+        int count = 0;
+        int excessive = expected.size() + 10; // fail-safe for bad code
+        boolean done = false;
+        while (!done)
+        {
+            String line = utfparser.parse(buf);
+            if (line != null)
+            {
+                actual.add(line);
+            }
+            else
+            {
+                done = true;
+            }
+            count++;
+            Assert.assertThat("Parse Count is excessive (bug in code!)",count,lessThan(excessive));
+        }
+
+        // Validate Results
+        assertEquals(expected,actual);
+    }
+
+    /**
+     * Parsing of an "in the wild" set HTTP response header lines.
+     */
+    @Test
+    public void testWildHttpResponseParse()
+    {
+        // Arbitrary Http Response Headers seen in the wild.
+        // Request URI -> https://ssl.google-analytics.com/__utm.gif
+        List<String> expected = new ArrayList<>();
+        expected.add("HTTP/1.0 200 OK");
+        expected.add("Date: Thu, 09 Aug 2012 16:16:39 GMT");
+        expected.add("Content-Length: 35");
+        expected.add("X-Content-Type-Options: nosniff");
+        expected.add("Pragma: no-cache");
+        expected.add("Expires: Wed, 19 Apr 2000 11:43:00 GMT");
+        expected.add("Last-Modified: Wed, 21 Jan 2004 19:51:30 GMT");
+        expected.add("Content-Type: image/gif");
+        expected.add("Cache-Control: private, no-cache, no-cache=Set-Cookie, proxy-revalidate");
+        expected.add("Age: 518097");
+        expected.add("Server: GFE/2.0");
+        expected.add("Connection: Keep-Alive");
+        expected.add("");
+
+        // Prepare Buffer
+        ByteBuffer buf = ByteBuffer.allocate(512);
+        for (String line : expected)
+        {
+            appendUtf8(buf,line + "\r\n");
+        }
+
+        BufferUtil.flipToFlush(buf,0);
+
+        // Parse Buffer
+        Utf8LineParser utfparser = new Utf8LineParser();
+
+        List<String> actual = new ArrayList<>();
+        int count = 0;
+        int excessive = expected.size() + 10; // fail-safe for bad code
+        boolean done = false;
+        while (!done)
+        {
+            String line = utfparser.parse(buf);
+            if (line != null)
+            {
+                actual.add(line);
+            }
+            else
+            {
+                done = true;
+            }
+            count++;
+            Assert.assertThat("Parse Count is excessive (bug in code!)",count,lessThan(excessive));
+        }
+
+        // Validate Results
+        assertEquals(expected,actual);
+    }
+}
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/Utf8StringBufferTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/Utf8StringBufferTest.java
index 2ea0f5a..2ff9dc5 100644
--- a/jetty-util/src/test/java/org/eclipse/jetty/util/Utf8StringBufferTest.java
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/Utf8StringBufferTest.java
@@ -22,6 +22,7 @@ import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
 import java.io.UnsupportedEncodingException;
+import java.nio.charset.StandardCharsets;
 
 import org.junit.Test;
 
@@ -31,7 +32,7 @@ public class Utf8StringBufferTest
     public void testUtfStringBuffer() throws Exception
     {
         String source = "abcd012345\n\r\u0000\u00a4\u10fb\ufffdjetty";
-        byte[] bytes = source.getBytes(StringUtil.__UTF8);
+        byte[] bytes = source.getBytes(StandardCharsets.UTF_8);
         Utf8StringBuffer buffer = new Utf8StringBuffer();
         for (byte aByte : bytes)
             buffer.append(aByte);
@@ -43,7 +44,7 @@ public class Utf8StringBufferTest
     public void testUtf8WithMissingByte() throws Exception
     {
         String source = "abc\u10fb";
-        byte[] bytes = source.getBytes(StringUtil.__UTF8);
+        byte[] bytes = source.getBytes(StandardCharsets.UTF_8);
         Utf8StringBuffer buffer = new Utf8StringBuffer();
         for (int i = 0; i < bytes.length - 1; i++)
             buffer.append(bytes[i]);
@@ -54,7 +55,7 @@ public class Utf8StringBufferTest
     public void testUtf8WithAdditionalByte() throws Exception
     {
         String source = "abcXX";
-        byte[] bytes = source.getBytes(StringUtil.__UTF8);
+        byte[] bytes = source.getBytes(StandardCharsets.UTF_8);
         bytes[3] = (byte)0xc0;
         bytes[4] = (byte)0x00;
 
@@ -68,9 +69,9 @@ public class Utf8StringBufferTest
     public void testUTF32codes() throws Exception
     {
         String source = "\uD842\uDF9F";
-        byte[] bytes = source.getBytes("UTF-8");
+        byte[] bytes = source.getBytes(StandardCharsets.UTF_8);
 
-        String jvmcheck = new String(bytes,0,bytes.length,"UTF-8");
+        String jvmcheck = new String(bytes,0,bytes.length,StandardCharsets.UTF_8);
         assertEquals(source,jvmcheck);
 
         Utf8StringBuffer buffer = new Utf8StringBuffer();
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/Utf8StringBuilderInvalidUtfTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/Utf8StringBuilderInvalidUtfTest.java
new file mode 100644
index 0000000..7b9a751
--- /dev/null
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/Utf8StringBuilderInvalidUtfTest.java
@@ -0,0 +1,71 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.eclipse.jetty.util.Utf8Appendable.NotUtf8Exception;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Test various invalid UTF8 byte sequences.
+ */
+ at RunWith(Parameterized.class)
+public class Utf8StringBuilderInvalidUtfTest
+{
+    @Parameters
+    public static Collection<Object[]> data() {
+        List<Object[]> data = new ArrayList<>();
+        data.add(new String[]{"c0af"});
+        data.add(new String[]{"EDA080"});
+        data.add(new String[]{"f08080af"});
+        data.add(new String[]{"f8808080af"});
+        data.add(new String[]{"e080af"});
+        data.add(new String[]{"F4908080"});
+        data.add(new String[]{"fbbfbfbfbf"});
+        data.add(new String[]{"10FFFF"});
+        data.add(new String[]{"CeBaE1BdB9Cf83CeBcCeB5EdA080656469746564"});
+        // use of UTF-16 High Surrogates (in codepoint form)
+        data.add(new String[]{"da07"});
+        data.add(new String[]{"d807"});
+        // decoded UTF-16 High Surrogate "\ud807" (in UTF-8 form)
+        data.add(new String[]{"EDA087"});
+        return data;
+    }
+    
+    private byte[] bytes;
+    
+    public Utf8StringBuilderInvalidUtfTest(String rawhex)
+    {
+        bytes = TypeUtil.fromHexString(rawhex);
+        System.out.printf("Utf8StringBuilderInvalidUtfTest[] (%s)%n", TypeUtil.toHexString(bytes));
+    }
+    
+    @Test(expected=NotUtf8Exception.class)
+    public void testInvalidUTF8()
+    {
+        Utf8StringBuilder buffer = new Utf8StringBuilder();
+        buffer.append(bytes,0,bytes.length);
+    }
+}
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/Utf8StringBuilderTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/Utf8StringBuilderTest.java
index 2c09eac..2108c5d 100644
--- a/jetty-util/src/test/java/org/eclipse/jetty/util/Utf8StringBuilderTest.java
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/Utf8StringBuilderTest.java
@@ -21,31 +21,56 @@ package org.eclipse.jetty.util;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
+import java.nio.charset.StandardCharsets;
+
+import org.junit.Assert;
 import org.junit.Test;
 
 public class Utf8StringBuilderTest
 {
     @Test
-    public void testInvalid() throws Exception
+    public void testFastFail_1() throws Exception
     {
-        String[] invalids =
-        { "c0af", "EDA080", "f08080af", "f8808080af", "e080af", "F4908080", "fbbfbfbfbf", "10FFFF",
-          "CeBaE1BdB9Cf83CeBcCeB5EdA080656469746564" };
+        byte[] part1 = TypeUtil.fromHexString("cebae1bdb9cf83cebcceb5");
+        byte[] part2 = TypeUtil.fromHexString("f4908080"); // INVALID
+        // Here for test tracking reasons, not needed to satisfy test
+        // byte[] part3 = TypeUtil.fromHexString("656469746564");
 
-        for (String i : invalids)
+        Utf8StringBuilder buffer = new Utf8StringBuilder();
+        // Part 1 is valid
+        buffer.append(part1,0,part1.length);
+        try
+        {
+            // Part 2 is invalid
+            buffer.append(part2,0,part2.length);
+            Assert.fail("Should have thrown a NotUtf8Exception");
+        }
+        catch (Utf8Appendable.NotUtf8Exception e)
         {
-            byte[] bytes = TypeUtil.fromHexString(i);
-            try
-            {
-                Utf8StringBuilder buffer = new Utf8StringBuilder();
-                buffer.append(bytes,0,bytes.length);
+            // expected path
+        }
+    }
 
-                assertEquals(i,"not expected",buffer.toString());
-            }
-            catch (Utf8Appendable.NotUtf8Exception e)
-            {
-                assertTrue(i,true);
-            }
+    @Test
+    public void testFastFail_2() throws Exception
+    {
+        byte[] part1 = TypeUtil.fromHexString("cebae1bdb9cf83cebcceb5f4");
+        byte[] part2 = TypeUtil.fromHexString("90"); // INVALID
+        // Here for test search/tracking reasons, not needed to satisfy test
+        // byte[] part3 = TypeUtil.fromHexString("8080656469746564");
+
+        Utf8StringBuilder buffer = new Utf8StringBuilder();
+        // Part 1 is valid
+        buffer.append(part1,0,part1.length);
+        try
+        {
+            // Part 2 is invalid
+            buffer.append(part2,0,part2.length);
+            Assert.fail("Should have thrown a NotUtf8Exception");
+        }
+        catch (Utf8Appendable.NotUtf8Exception e)
+        {
+            // expected path
         }
     }
 
@@ -53,7 +78,7 @@ public class Utf8StringBuilderTest
     public void testUtfStringBuilder() throws Exception
     {
         String source = "abcd012345\n\r\u0000\u00a4\u10fb\ufffdjetty";
-        byte[] bytes = source.getBytes(StringUtil.__UTF8);
+        byte[] bytes = source.getBytes(StandardCharsets.UTF_8);
         Utf8StringBuilder buffer = new Utf8StringBuilder();
         for (byte aByte : bytes)
             buffer.append(aByte);
@@ -65,7 +90,7 @@ public class Utf8StringBuilderTest
     public void testShort() throws Exception
     {
         String source = "abc\u10fb";
-        byte[] bytes = source.getBytes(StringUtil.__UTF8);
+        byte[] bytes = source.getBytes(StandardCharsets.UTF_8);
         Utf8StringBuilder buffer = new Utf8StringBuilder();
         for (int i = 0; i < bytes.length - 1; i++)
             buffer.append(bytes[i]);
@@ -76,20 +101,21 @@ public class Utf8StringBuilderTest
     public void testLong() throws Exception
     {
         String source = "abcXX";
-        byte[] bytes = source.getBytes(StringUtil.__UTF8);
+        byte[] bytes = source.getBytes(StandardCharsets.UTF_8);
         bytes[3] = (byte)0xc0;
         bytes[4] = (byte)0x00;
 
         Utf8StringBuilder buffer = new Utf8StringBuilder();
         try
         {
-            for (byte aByte : bytes)
+            for (byte aByte : bytes) {
                 buffer.append(aByte);
-            assertTrue(false);
+            }
+            Assert.fail("Should have resulted in an Utf8Appendable.NotUtf8Exception");
         }
-        catch (IllegalArgumentException e)
+        catch (Utf8Appendable.NotUtf8Exception e)
         {
-            assertTrue(true);
+            // expected path
         }
         assertEquals("abc\ufffd",buffer.toString());
     }
@@ -98,9 +124,9 @@ public class Utf8StringBuilderTest
     public void testUTF32codes() throws Exception
     {
         String source = "\uD842\uDF9F";
-        byte[] bytes = source.getBytes("UTF-8");
+        byte[] bytes = source.getBytes(StandardCharsets.UTF_8);
 
-        String jvmcheck = new String(bytes,0,bytes.length,"UTF-8");
+        String jvmcheck = new String(bytes,0,bytes.length,StandardCharsets.UTF_8);
         assertEquals(source,jvmcheck);
 
         Utf8StringBuilder buffer = new Utf8StringBuilder();
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/ajax/JSONPojoConvertorFactoryTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/ajax/JSONPojoConvertorFactoryTest.java
deleted file mode 100644
index 836266e..0000000
--- a/jetty-util/src/test/java/org/eclipse/jetty/util/ajax/JSONPojoConvertorFactoryTest.java
+++ /dev/null
@@ -1,418 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.util.ajax;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import java.util.Map;
-
-import org.junit.Test;
-
-
-/**
- * Test to convert POJOs to JSON and vice versa with automatic convertor creation.
- */
-public class JSONPojoConvertorFactoryTest
-{    
-    @Test
-    public void testFoo()
-    {
-        JSON jsonOut = new JSON();
-        JSON jsonIn = new JSON();
-        
-        jsonOut.addConvertor(Object.class, new JSONPojoConvertorFactory(jsonOut));
-        jsonOut.addConvertor(Enum.class, new JSONEnumConvertor());
-        jsonIn.addConvertor(Object.class, new JSONPojoConvertorFactory(jsonIn));
-        jsonIn.addConvertor(Enum.class, new JSONEnumConvertor());
-        
-        Foo foo = new Foo();
-        foo._name = "Foo @ " + System.currentTimeMillis();
-        foo._int1 = 1;
-        foo._int2 = new Integer(2);
-        foo._long1 = 1000001l;
-        foo._long2 = new Long(1000002l);
-        foo._float1 = 10.11f;
-        foo._float2 = new Float(10.22f);
-        foo._double1 = 10000.11111d;
-        foo._double2 = new Double(10000.22222d);
-        
-        Bar bar = new Bar("Hello", true, new Baz("World", Boolean.FALSE, foo), new Baz[]{
-            new Baz("baz0", Boolean.TRUE, null), new Baz("baz1", Boolean.FALSE, null)
-        });
-        bar.setColor(Color.Green);
-        
-        String s = jsonOut.toJSON(bar);
-        
-        Object obj = jsonIn.parse(new JSON.StringSource(s));
-        
-        assertTrue(obj instanceof Bar);
-        
-        Bar br = (Bar)obj;        
-        
-        Baz bz = br.getBaz();
-        
-        Foo f = bz.getFoo();
-        
-        assertEquals(f, foo);
-        assertTrue(br.getBazs().length==2);
-        assertEquals(br.getBazs()[0].getMessage(), "baz0");
-        assertEquals(br.getBazs()[1].getMessage(), "baz1");
-        assertEquals(Color.Green,br.getColor());
-    }
-    
-    @Test
-    public void testFoo2Map()
-    {
-        JSON jsonOut = new JSON();
-        JSON jsonIn = new JSON();
-        
-        jsonOut.addConvertor(Object.class, new JSONPojoConvertorFactory(jsonOut,false));
-        jsonOut.addConvertor(Enum.class, new JSONEnumConvertor());
-        jsonIn.addConvertor(Object.class, new JSONPojoConvertorFactory(jsonIn,false));
-        jsonIn.addConvertor(Enum.class, new JSONEnumConvertor());
-        
-        Foo foo = new Foo();
-        foo._name = "Foo @ " + System.currentTimeMillis();
-        foo._int1 = 1;
-        foo._int2 = new Integer(2);
-        foo._long1 = 1000001l;
-        foo._long2 = new Long(1000002l);
-        foo._float1 = 10.11f;
-        foo._float2 = new Float(10.22f);
-        foo._double1 = 10000.11111d;
-        foo._double2 = new Double(10000.22222d);
-        
-        Bar bar = new Bar("Hello", true, new Baz("World", Boolean.FALSE, foo), new Baz[]{
-            new Baz("baz0", Boolean.TRUE, null), new Baz("baz1", Boolean.FALSE, null)
-        });
-        bar.setColor(Color.Green);
-        
-        String s = jsonOut.toJSON(bar);
-        System.err.println(s);
-        
-        assertTrue(s.indexOf("class")<0);
-        
-        Object obj = jsonIn.parse(new JSON.StringSource(s));
-        
-        assertTrue(obj instanceof Map);
-        
-        Map<String,Object> br = (Map<String,Object>)obj;        
-        
-        Map<String,Object> bz = (Map<String,Object>)br.get("baz");
-        
-        Map<String,Object> f = (Map<String,Object>)bz.get("foo");
-        assertTrue(f != null);
-        Object[] bazs = (Object[])br.get("bazs");
-        assertTrue(bazs.length==2);
-        assertEquals(((Map)bazs[0]).get("message"), "baz0");
-        assertEquals(((Map)bazs[1]).get("message"), "baz1");
-        assertEquals("Green",br.get("color"));
-    }
-
-
-    enum Color { Red, Green, Blue };
-    
-    public static class Bar
-    {
-        private String _title, _nullTest;
-        private Baz _baz;
-        private boolean _boolean1;
-        private Baz[] _bazs;
-        private Color _color;
-        
-        public Bar()
-        {
-            
-        }
-        
-        public Bar(String title, boolean boolean1, Baz baz)
-        {
-            setTitle(title);
-            setBoolean1(boolean1);
-            setBaz(baz);
-        }
-        
-        public Bar(String title, boolean boolean1, Baz baz, Baz[] bazs)
-        {
-            this(title, boolean1, baz);
-            setBazs(bazs);
-        }
-        
-        @Override
-        public String toString()
-        {
-            return new StringBuffer().append("\n=== ").append(getClass().getSimpleName()).append(" ===")
-                .append("\ntitle: ").append(getTitle())
-                .append("\nboolean1: ").append(isBoolean1())
-                .append("\nnullTest: ").append(getNullTest())
-                .append("\nbaz: ").append(getBaz())
-                .append("\ncolor: ").append(_color).toString();
-        }
-        
-        public void setTitle(String title)
-        {
-            _title = title;
-        }
-        
-        public String getTitle()
-        {
-            return _title;
-        }
-        
-        public void setNullTest(String nullTest)
-        {
-            assert(nullTest==null);            
-            _nullTest = nullTest;
-        }
-        
-        public String getNullTest()
-        {
-            return _nullTest;
-        }
-        
-        public void setBaz(Baz baz)
-        {
-            _baz = baz;
-        }
-        
-        public Baz getBaz()
-        {
-            return _baz;
-        }
-        
-        public void setBoolean1(boolean boolean1)
-        {
-            _boolean1 = boolean1;
-        }
-        
-        public boolean isBoolean1()
-        {
-            return _boolean1;
-        }
-        
-        public void setBazs(Baz[] bazs)
-        {
-            _bazs = bazs;
-        }
-        
-        public Baz[] getBazs()
-        {
-            return _bazs;
-        }
-
-        public Color getColor()
-        {
-            return _color;
-        }
-        
-        public void setColor(Color color)
-        {
-            _color = color;
-        }
-        
-    }
-    
-    public static class Baz
-    {
-        private String _message;
-        private Foo _foo;
-        private Boolean _boolean2;
-        
-        public Baz()
-        {
-            
-        }
-        
-        public Baz(String message, Boolean boolean2, Foo foo)
-        {
-            setMessage(message);
-            setBoolean2(boolean2);
-            setFoo(foo);
-        }
-        
-        @Override
-        public String toString()
-        {
-            return new StringBuffer().append("\n=== ").append(getClass().getSimpleName()).append(" ===")
-                .append("\nmessage: ").append(getMessage())
-                .append("\nboolean2: ").append(isBoolean2())
-                .append("\nfoo: ").append(getFoo()).toString();
-        }
-        
-        public void setMessage(String message)
-        {
-            _message = message;            
-        }
-        
-        public String getMessage()
-        {
-            return _message;
-        }
-        
-        public void setFoo(Foo foo)
-        {
-            _foo = foo;
-        }
-        
-        public Foo getFoo()
-        {
-            return _foo;
-        }
-        
-        public void setBoolean2(Boolean boolean2)
-        {
-            _boolean2 = boolean2;
-        }
-        
-        public Boolean isBoolean2()
-        {
-            return _boolean2;
-        }
-        
-    }
-    
-    public static class Foo
-    {
-        private String _name;
-        private int _int1;
-        private Integer _int2;
-        private long _long1;
-        private Long _long2;        
-        private float _float1;
-        private Float _float2;
-        private double _double1;
-        private Double _double2;
-        
-        public Foo()
-        {
-            
-        }
-        
-        @Override
-        public String toString()
-        {
-            return new StringBuffer().append("\n=== ").append(getClass().getSimpleName()).append(" ===")
-                .append("\nname: ").append(_name)
-                .append("\nint1: ").append(_int1)
-                .append("\nint2: ").append(_int2)
-                .append("\nlong1: ").append(_long1)
-                .append("\nlong2: ").append(_long2)
-                .append("\nfloat1: ").append(_float1)
-                .append("\nfloat2: ").append(_float2)
-                .append("\ndouble1: ").append(_double1)
-                .append("\ndouble2: ").append(_double2)                
-                .toString();                
-        }
-        
-        @Override
-        public boolean equals(Object another)
-        {
-            if(another instanceof Foo)
-            {
-                Foo foo = (Foo)another;                
-                return getName().equals(foo.getName()) 
-                    && getInt1()==foo.getInt1()
-                    && getInt2().equals(foo.getInt2())
-                    && getLong1()==foo.getLong1()
-                    && getLong2().equals(foo.getLong2())
-                    && getFloat1()==foo.getFloat1()
-                    && getFloat2().equals(foo.getFloat2())
-                    && getDouble1()==foo.getDouble1()                    
-                    && getDouble2().equals(foo.getDouble2());
-            }
-            
-            return false;
-        }
-        
-        public String getName()
-        {
-            return _name;
-        }
-        public void setName(String name)
-        {
-            _name = name;
-        }
-        public int getInt1()
-        {
-            return _int1;
-        }
-        public void setInt1(int int1)
-        {
-            _int1 = int1;
-        }
-        public Integer getInt2()
-        {
-            return _int2;
-        }
-        public void setInt2(Integer int2)
-        {
-            _int2 = int2;
-        }
-        public long getLong1()
-        {
-            return _long1;
-        }
-        public void setLong1(long long1)
-        {
-            _long1 = long1;
-        }
-        public Long getLong2()
-        {
-            return _long2;
-        }
-        public void setLong2(Long long2)
-        {
-            _long2 = long2;
-        }
-        public float getFloat1()
-        {
-            return _float1;
-        }
-        public void setFloat1(float float1)
-        {
-            _float1 = float1;
-        }
-        public Float getFloat2()
-        {
-            return _float2;
-        }
-        public void setFloat2(Float float2)
-        {
-            _float2 = float2;
-        }
-        public double getDouble1()
-        {
-            return _double1;
-        }
-        public void setDouble1(double double1)
-        {
-            _double1 = double1;
-        }
-        public Double getDouble2()
-        {
-            return _double2;
-        }
-        public void setDouble2(Double double2)
-        {
-            _double2 = double2;
-        }
-       
-    }    
-}
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/ajax/JSONPojoConvertorTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/ajax/JSONPojoConvertorTest.java
deleted file mode 100644
index 4774dd4..0000000
--- a/jetty-util/src/test/java/org/eclipse/jetty/util/ajax/JSONPojoConvertorTest.java
+++ /dev/null
@@ -1,441 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.util.ajax;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-
-import org.junit.Test;
-
-
-/**
- * Test to converts POJOs to JSON and vice versa.
- */
-public class JSONPojoConvertorTest
-{
-    @Test
-    public void testFoo()
-    {
-        JSON json = new JSON();
-        json.addConvertor(Foo.class, new JSONPojoConvertor(Foo.class));
-        json.addConvertor(Bar.class, new JSONPojoConvertor(Bar.class));
-        json.addConvertor(Baz.class, new JSONPojoConvertor(Baz.class));
-        // json.addConvertor(Enum.class, new JSONEnumConvertor(true));
-        
-        Foo foo = new Foo();
-        foo._name = "Foo @ " + System.currentTimeMillis();
-        foo._int1 = 1;
-        foo._int2 = new Integer(2);
-        foo._long1 = 1000001l;
-        foo._long2 = new Long(1000002l);
-        foo._float1 = 10.11f;
-        foo._float2 = new Float(10.22f);
-        foo._double1 = 10000.11111d;
-        foo._double2 = new Double(10000.22222d);
-        foo._char1='a';
-        foo._char2=new Character('b');
-        
-        Bar bar = new Bar("Hello", true, new Baz("World", Boolean.FALSE, foo), new Baz[]{
-            new Baz("baz0", Boolean.TRUE, null), new Baz("baz1", Boolean.FALSE, null)
-        });
-        bar.setColor(Color.Green);
-        
-        String s = json.toJSON(bar);
-        
-        Object obj = json.parse(new JSON.StringSource(s));
-        
-        assertTrue(obj instanceof Bar);
-        
-        Bar br = (Bar)obj;        
-        
-        Baz bz = br.getBaz();
-        
-        Foo f = bz.getFoo();
-        
-        assertEquals(foo, f);
-        assertTrue(br.getBazs().length==2);
-        assertEquals(br.getBazs()[0].getMessage(), "baz0");
-        assertEquals(br.getBazs()[1].getMessage(), "baz1");
-        assertEquals(Color.Green,br.getColor());
-    }
-    
-    @Test
-    public void testExclude()
-    {
-        JSON json = new JSON();
-        json.addConvertor(Foo.class, new JSONPojoConvertor(Foo.class, 
-                new String[]{"name", "long1", "int2"}));
-        json.addConvertor(Bar.class, new JSONPojoConvertor(Bar.class, 
-                new String[]{"title", "boolean1"}));
-        json.addConvertor(Baz.class, new JSONPojoConvertor(Baz.class, 
-                new String[]{"boolean2"}));
-        
-        Foo foo = new Foo();
-        foo._name = "Foo @ " + System.currentTimeMillis();
-        foo._int1 = 1;
-        foo._int2 = new Integer(2);
-        foo._long1 = 1000001l;
-        foo._long2 = new Long(1000002l);
-        foo._float1 = 10.11f;
-        foo._float2 = new Float(10.22f);
-        foo._double1 = 10000.11111d;
-        foo._double2 = new Double(10000.22222d);
-        foo._char1='a';
-        foo._char2=new Character('b');
-        
-        Bar bar = new Bar("Hello", true, new Baz("World", Boolean.FALSE, foo));
-        // bar.setColor(Color.Blue);
-        
-        String s = json.toJSON(bar);
-        Object obj = json.parse(new JSON.StringSource(s));
-        
-        assertTrue(obj instanceof Bar);
-        
-        Bar br = (Bar)obj;        
-        Baz bz = br.getBaz();
-        Foo f = bz.getFoo();
-        
-        assertNull(br.getTitle());
-        assertFalse(bar.getTitle().equals(br.getTitle()));
-        assertFalse(br.isBoolean1()==bar.isBoolean1());
-        assertNull(bz.isBoolean2());
-        assertFalse(bar.getBaz().isBoolean2().equals(bz.isBoolean2()));
-        assertFalse(f.getLong1()==foo.getLong1());
-        assertNull(f.getInt2());
-        assertFalse(foo.getInt2().equals(f.getInt2()));
-        assertNull(f.getName());   
-        assertEquals(null,br.getColor());
-    }
-    
-    enum Color { Red, Green, Blue };
-    
-    public static class Bar
-    {
-        private String _title, _nullTest;
-        private Baz _baz;
-        private boolean _boolean1;
-        private Baz[] _bazs;
-        private Color _color;
-        
-        public Bar()
-        {
-            
-        }
-        
-        public Bar(String title, boolean boolean1, Baz baz)
-        {
-            setTitle(title);
-            setBoolean1(boolean1);
-            setBaz(baz);
-        }
-        
-        public Bar(String title, boolean boolean1, Baz baz, Baz[] bazs)
-        {
-            this(title, boolean1, baz);
-            setBazs(bazs);
-        }
-        
-        @Override
-        public String toString()
-        {
-            return new StringBuffer()
-                .append("\n=== ").append(getClass().getSimpleName()).append(" ===")
-                .append("\ntitle: ").append(getTitle())
-                .append("\nboolean1: ").append(isBoolean1())
-                .append("\nnullTest: ").append(getNullTest())
-                .append("\nbaz: ").append(getBaz())
-                .append("\ncolor: ").append(_color).toString();
-        }
-        
-        public void setTitle(String title)
-        {
-            _title = title;
-        }
-        
-        public String getTitle()
-        {
-            return _title;
-        }
-        
-        public void setNullTest(String nullTest)
-        {
-            assert(nullTest==null);            
-            _nullTest = nullTest;
-        }
-        
-        public String getNullTest()
-        {
-            return _nullTest;
-        }
-        
-        public void setBaz(Baz baz)
-        {
-            _baz = baz;
-        }
-        
-        public Baz getBaz()
-        {
-            return _baz;
-        }
-        
-        public void setBoolean1(boolean boolean1)
-        {
-            _boolean1 = boolean1;
-        }
-        
-        public boolean isBoolean1()
-        {
-            return _boolean1;
-        }
-        
-        public void setBazs(Baz[] bazs)
-        {
-            _bazs = bazs;
-        }
-        
-        public Baz[] getBazs()
-        {
-            return _bazs;
-        }
-
-        public Color getColor()
-        {
-            return _color;
-        }
-        
-        public void setColor(Color color)
-        {
-            _color = color;
-        }
-        
-        
-    }
-    
-    public static class Baz
-    {
-        private String _message;
-        private Foo _foo;
-        private Boolean _boolean2;
-        
-        public Baz()
-        {
-            
-        }
-        
-        public Baz(String message, Boolean boolean2, Foo foo)
-        {
-            setMessage(message);
-            setBoolean2(boolean2);
-            setFoo(foo);
-        }
-        
-        @Override
-        public String toString()
-        {
-            return new StringBuffer().append("\n=== ").append(getClass().getSimpleName()).append(" ===")
-                .append("\nmessage: ").append(getMessage())
-                .append("\nboolean2: ").append(isBoolean2())
-                .append("\nfoo: ").append(getFoo()).toString();
-        }
-        
-        public void setMessage(String message)
-        {
-            _message = message;            
-        }
-        
-        public String getMessage()
-        {
-            return _message;
-        }
-        
-        public void setFoo(Foo foo)
-        {
-            _foo = foo;
-        }
-        
-        public Foo getFoo()
-        {
-            return _foo;
-        }
-        
-        public void setBoolean2(Boolean boolean2)
-        {
-            _boolean2 = boolean2;
-        }
-        
-        public Boolean isBoolean2()
-        {
-            return _boolean2;
-        }
-        
-    }
-    
-    public static class Foo
-    {
-        private String _name;
-        private int _int1;
-        private Integer _int2;
-        private long _long1;
-        private Long _long2;        
-        private float _float1;
-        private Float _float2;
-        private double _double1;
-        private Double _double2;
-        private char _char1;
-        private Character _char2;
-        
-        public Foo()
-        {
-            
-        }
-        
-        @Override
-        public String toString()
-        {
-            return new StringBuffer().append("\n=== ").append(getClass().getSimpleName()).append(" ===")
-                .append("\nname: ").append(_name)
-                .append("\nint1: ").append(_int1)
-                .append("\nint2: ").append(_int2)
-                .append("\nlong1: ").append(_long1)
-                .append("\nlong2: ").append(_long2)
-                .append("\nfloat1: ").append(_float1)
-                .append("\nfloat2: ").append(_float2)
-                .append("\ndouble1: ").append(_double1)
-                .append("\ndouble2: ").append(_double2)  
-                .append("\nchar1: ").append(_char1)
-                .append("\nchar2: ").append(_char2)                
-                .toString();                
-        }
-        
-        @Override
-        public boolean equals(Object another)
-        {
-            if(another instanceof Foo)
-            {
-                Foo foo = (Foo)another;                
-                return getName().equals(foo.getName()) 
-                    && getInt1()==foo.getInt1()
-                    && getInt2().equals(foo.getInt2())
-                    && getLong1()==foo.getLong1()
-                    && getLong2().equals(foo.getLong2())
-                    && getFloat1()==foo.getFloat1()
-                    && getFloat2().equals(foo.getFloat2())
-                    && getDouble1()==foo.getDouble1()                    
-                    && getDouble2().equals(foo.getDouble2())
-                    && getChar1()==foo.getChar1()                    
-                    && getChar2().equals(foo.getChar2());
-            }
-            
-            return false;
-        }
-        
-        public String getName()
-        {
-            return _name;
-        }
-        public void setName(String name)
-        {
-            _name = name;
-        }
-        public int getInt1()
-        {
-            return _int1;
-        }
-        public void setInt1(int int1)
-        {
-            _int1 = int1;
-        }
-        public Integer getInt2()
-        {
-            return _int2;
-        }
-        public void setInt2(Integer int2)
-        {
-            _int2 = int2;
-        }
-        public long getLong1()
-        {
-            return _long1;
-        }
-        public void setLong1(long long1)
-        {
-            _long1 = long1;
-        }
-        public Long getLong2()
-        {
-            return _long2;
-        }
-        public void setLong2(Long long2)
-        {
-            _long2 = long2;
-        }
-        public float getFloat1()
-        {
-            return _float1;
-        }
-        public void setFloat1(float float1)
-        {
-            _float1 = float1;
-        }
-        public Float getFloat2()
-        {
-            return _float2;
-        }
-        public void setFloat2(Float float2)
-        {
-            _float2 = float2;
-        }
-        public double getDouble1()
-        {
-            return _double1;
-        }
-        public void setDouble1(double double1)
-        {
-            _double1 = double1;
-        }
-        public Double getDouble2()
-        {
-            return _double2;
-        }
-        public void setDouble2(Double double2)
-        {
-            _double2 = double2;
-        }
-        public char getChar1()
-        {
-            return _char1;
-        }
-        public void setChar1(char char1)
-        {
-            _char1 = char1;
-        }
-        public Character getChar2()
-        {
-            return _char2;
-        }
-        public void setChar2(Character char2)
-        {
-            _char2 = char2;
-        }
-       
-    }    
-
-}
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/ajax/JSONTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/ajax/JSONTest.java
deleted file mode 100644
index 473b8eb..0000000
--- a/jetty-util/src/test/java/org/eclipse/jetty/util/ajax/JSONTest.java
+++ /dev/null
@@ -1,473 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.util.ajax;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import java.io.StringReader;
-import java.lang.reflect.Array;
-import java.math.BigDecimal;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.Locale;
-import java.util.Map;
-import java.util.TimeZone;
-
-import org.eclipse.jetty.util.DateCache;
-import org.eclipse.jetty.util.ajax.JSON.Output;
-import org.junit.BeforeClass;
-import org.junit.Test;
-
-
-public class JSONTest
-{   
-    String test="\n\n\n\t\t    "+
-    "// ignore this ,a [ \" \n"+
-    "/* and this \n" +
-    "/* and * // this \n" +
-    "*/" +
-    "{ "+
-    "\"onehundred\" : 100  ,"+
-    "\"small\":-0.2,"+
-    "\"name\" : \"fred\"  ," +
-    "\"empty\" : {}  ," +
-    "\"map\" : {\"a\":-1.0e2}  ," +
-    "\"array\" : [\"a\",-1.0e2,[],null,true,false]  ," +
-    "\"w0\":{\"class\":\"org.eclipse.jetty.util.ajax.JSONTest$Woggle\",\"name\":\"woggle0\",\"nested\":{\"class\":\"org.eclipse.jetty.util.ajax.JSONTest$Woggle\",\"name\":\"woggle1\",\"nested\":null,\"number\":-101},\"number\":100}," +
-    "\"NaN\": NaN," + 
-    "\"undefined\": undefined," +
-    "}";
-    
-    /* ------------------------------------------------------------ */
-    /* (non-Javadoc)
-     * @see junit.framework.TestCase#setUp()
-     */
-    @BeforeClass
-    public static void setUp() throws Exception
-    {
-        JSON.registerConvertor(Gadget.class,new JSONObjectConvertor(false));
-    }
-
-    @Test
-    public void testToString()
-    {
-        HashMap map = new HashMap();
-        HashMap obj6 = new HashMap();
-        HashMap obj7 = new HashMap();
-        
-        Woggle w0 = new Woggle();
-        Woggle w1 = new Woggle();
-        
-        w0.name="woggle0";
-        w0.nested=w1;
-        w0.number=100;
-        w1.name="woggle1";
-        w1.nested=null;
-        w1.number=-101;
-        
-        map.put("n1",null);
-        map.put("n2",new Integer(2));
-        map.put("n3",new Double(-0.00000000003));
-        map.put("n4","4\n\r\t\"4");
-        map.put("n5",new Object[]{"a",new Character('b'),new Integer(3),new String[]{},null,Boolean.TRUE,Boolean.FALSE});
-        map.put("n6",obj6);
-        map.put("n7",obj7);
-        map.put("n8",new int[]{1,2,3,4});
-        map.put("n9",new JSON.Literal("[{},  [],  {}]"));
-        map.put("w0",w0);
-        
-        obj7.put("x","value");
-
-
-        String s = JSON.toString(map);
-        assertTrue(s.indexOf("\"n1\":null")>=0);
-        assertTrue(s.indexOf("\"n2\":2")>=0);
-        assertTrue(s.indexOf("\"n3\":-3.0E-11")>=0);
-        assertTrue(s.indexOf("\"n4\":\"4\\n")>=0);
-        assertTrue(s.indexOf("\"n5\":[\"a\",\"b\",")>=0);
-        assertTrue(s.indexOf("\"n6\":{}")>=0);
-        assertTrue(s.indexOf("\"n7\":{\"x\":\"value\"}")>=0);
-        assertTrue(s.indexOf("\"n8\":[1,2,3,4]")>=0);
-        assertTrue(s.indexOf("\"n9\":[{},  [],  {}]")>=0);
-        assertTrue(s.indexOf("\"w0\":{\"class\":\"org.eclipse.jetty.util.ajax.JSONTest$Woggle\",\"name\":\"woggle0\",\"nested\":{\"class\":\"org.eclipse.jetty.util.ajax.JSONTest$Woggle\",\"name\":\"woggle1\",\"nested\":null,\"number\":-101},\"number\":100}")>=0);
-        
-        Gadget gadget = new Gadget();
-        gadget.setShields(42);
-        gadget.setWoggles(new Woggle[]{w0,w1});
-        
-        s = JSON.toString(new Gadget[]{gadget});
-        assertTrue(s.startsWith("["));
-        assertTrue(s.indexOf("\"modulated\":false")>=0);
-        assertTrue(s.indexOf("\"shields\":42")>=0);
-        assertTrue(s.indexOf("\"name\":\"woggle0\"")>=0);
-        assertTrue(s.indexOf("\"name\":\"woggle1\"")>=0);
-    }
-
-    /* ------------------------------------------------------------ */
-    @Test
-    public void testParse()
-    {
-        Map map = (Map)JSON.parse(test);
-        assertEquals(new Long(100),map.get("onehundred"));
-        assertEquals("fred",map.get("name"));
-        assertEquals(-0.2,map.get("small"));
-        assertTrue(map.get("array").getClass().isArray());
-        assertTrue(map.get("w0") instanceof Woggle);
-        assertTrue(((Woggle)map.get("w0")).nested instanceof Woggle);
-        assertEquals(-101,((Woggle)((Woggle)map.get("w0")).nested).number);
-        assertTrue(map.containsKey("NaN"));
-        assertEquals(null,map.get("NaN"));
-        assertTrue(map.containsKey("undefined"));
-        assertEquals(null,map.get("undefined"));
-        
-        test="{\"data\":{\"source\":\"15831407eqdaawf7\",\"widgetId\":\"Magnet_8\"},\"channel\":\"/magnets/moveStart\",\"connectionId\":null,\"clientId\":\"15831407eqdaawf7\"}";
-        map = (Map)JSON.parse(test);
-    }
-
-    /* ------------------------------------------------------------ */
-    @Test
-    public void testParseReader() throws Exception
-    {
-        Map map = (Map)JSON.parse(new StringReader(test));
-   
-        assertEquals(new Long(100),map.get("onehundred"));
-        assertEquals("fred",map.get("name"));
-        assertTrue(map.get("array").getClass().isArray());
-        assertTrue(map.get("w0") instanceof Woggle);
-        assertTrue(((Woggle)map.get("w0")).nested instanceof Woggle);
-        
-        test="{\"data\":{\"source\":\"15831407eqdaawf7\",\"widgetId\":\"Magnet_8\"},\"channel\":\"/magnets/moveStart\",\"connectionId\":null,\"clientId\":\"15831407eqdaawf7\"}";
-        map = (Map)JSON.parse(test);
-    }
-    
-    /* ------------------------------------------------------------ */
-    @Test
-    public void testStripComment()
-    {
-        String test="\n\n\n\t\t    "+
-        "// ignore this ,a [ \" \n"+
-        "/* "+
-        "{ "+
-        "\"onehundred\" : 100  ,"+
-        "\"name\" : \"fred\"  ," +
-        "\"empty\" : {}  ," +
-        "\"map\" : {\"a\":-1.0e2}  ," +
-        "\"array\" : [\"a\",-1.0e2,[],null,true,false]  ," +
-        "} */";
-        
-        Object o = JSON.parse(test,false);
-        assertTrue(o==null);
-        o = JSON.parse(test,true);
-        assertTrue(o instanceof Map);
-        assertEquals("fred",((Map)o).get("name"));
-        
-    }
-    
-    /* ------------------------------------------------------------ */
-    @Test
-    public void testQuote()
-    {
-        String test="\"abc123|\\\"|\\\\|\\/|\\b|\\f|\\n|\\r|\\t|\\uaaaa|\"";
-        
-        String result = (String)JSON.parse(test,false);
-        assertEquals("abc123|\"|\\|/|\b|\f|\n|\r|\t|\uaaaa|",result);
-    }
-
-    /* ------------------------------------------------------------ */
-    @Test
-    public void testBigDecimal()
-    {
-        Object obj = JSON.parse("1.0E7");
-        assertTrue(obj instanceof Double);
-        BigDecimal bd = BigDecimal.valueOf(10000000d);
-        String string = JSON.toString(new Object[]{bd}); 
-        obj = Array.get(JSON.parse(string),0);
-        assertTrue(obj instanceof Double);
-    }
-
-
-    /* ------------------------------------------------------------ */
-    @Test
-    public void testZeroByte()
-    {
-        String withzero="\u0000";
-        JSON.toString(withzero);
-    }
-    
-    /* ------------------------------------------------------------ */
-    public static class Gadget 
-    {
-        private boolean modulated;
-        private long shields;
-        private Woggle[] woggles;
-        /* ------------------------------------------------------------ */
-        /**
-         * @return the modulated
-         */
-        public boolean isModulated()
-        {
-            return modulated;
-        }
-        /* ------------------------------------------------------------ */
-        /**
-         * @param modulated the modulated to set
-         */
-        public void setModulated(boolean modulated)
-        {
-            this.modulated=modulated;
-        }
-        /* ------------------------------------------------------------ */
-        /**
-         * @return the shields
-         */
-        public long getShields()
-        {
-            return shields;
-        }
-        /* ------------------------------------------------------------ */
-        /**
-         * @param shields the shields to set
-         */
-        public void setShields(long shields)
-        {
-            this.shields=shields;
-        }
-        /* ------------------------------------------------------------ */
-        /**
-         * @return the woggles
-         */
-        public Woggle[] getWoggles()
-        {
-            return woggles;
-        }
-        /* ------------------------------------------------------------ */
-        /**
-         * @param woggles the woggles to set
-         */
-        public void setWoggles(Woggle[] woggles)
-        {
-            this.woggles=woggles;
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    @Test
-    public void testConvertor()
-    {
-        // test case#1 - force timezone to GMT
-        JSON json = new JSON();
-        json.addConvertor(Date.class, new JSONDateConvertor("MM/dd/yyyy HH:mm:ss zzz", TimeZone.getTimeZone("GMT"),false));
-        json.addConvertor(Object.class,new JSONObjectConvertor());
-
-        Woggle w0 = new Woggle();
-        Gizmo g0 = new Gizmo();
-        
-        w0.name="woggle0";
-        w0.nested=g0;
-        w0.number=100;
-        g0.name="woggle1";
-        g0.nested=null;
-        g0.number=-101;
-        g0.tested=true;
-        
-        HashMap map = new HashMap();
-        Date dummyDate = new Date(1);
-        map.put("date", dummyDate);
-        map.put("w0",w0);
-
-        StringBuffer buf = new StringBuffer();
-        json.append(buf,map);
-        String js=buf.toString();
-        
-        assertTrue(js.indexOf("\"date\":\"01/01/1970 00:00:00 GMT\"")>=0);
-        assertTrue(js.indexOf("org.eclipse.jetty.util.ajax.JSONTest$Woggle")>=0);
-        assertTrue(js.indexOf("org.eclipse.jetty.util.ajax.JSONTest$Gizmo")<0);
-        assertTrue(js.indexOf("\"tested\":true")>=0);
-
-        // test case#3
-        TimeZone tzone = TimeZone.getTimeZone("JST");
-        String tzone3Letter = tzone.getDisplayName(false, TimeZone.SHORT);
-        String format = "EEE MMMMM dd HH:mm:ss zzz yyyy";
-
-        Locale l = new Locale("ja", "JP");
-        if (l!=null)
-        {
-            json.addConvertor(Date.class, new JSONDateConvertor(format, tzone, false, l));
-            buf = new StringBuffer();
-            json.append(buf,map);
-            js=buf.toString();
-            //assertTrue(js.indexOf("\"date\":\"\u6728 1\u6708 01 09:00:00 JST 1970\"")>=0);
-            assertTrue(js.indexOf(" 01 09:00:00 JST 1970\"")>=0);
-            assertTrue(js.indexOf("org.eclipse.jetty.util.ajax.JSONTest$Woggle")>=0);
-            assertTrue(js.indexOf("org.eclipse.jetty.util.ajax.JSONTest$Gizmo")<0);
-            assertTrue(js.indexOf("\"tested\":true")>=0);
-        }
-        
-        // test case#4 
-        json.addConvertor(Date.class,new JSONDateConvertor(true));
-        w0.nested=null;
-        buf = new StringBuffer();
-        json.append(buf,map);
-        js=buf.toString();
-        assertTrue(js.indexOf("\"date\":\"Thu Jan 01 00:00:00 GMT 1970\"")<0);
-        assertTrue(js.indexOf("org.eclipse.jetty.util.ajax.JSONTest$Woggle")>=0);
-        assertTrue(js.indexOf("org.eclipse.jetty.util.ajax.JSONTest$Gizmo")<0);
-        
-        map=(HashMap)json.parse(new JSON.StringSource(js));
-        
-        assertTrue(map.get("date") instanceof Date);
-        assertTrue(map.get("w0") instanceof Woggle);
-    }
-    
-    enum Color { Red, Green, Blue };
-
-    @Test
-    public void testEnumConvertor()
-    {
-        JSON json = new JSON();
-        Locale l = new Locale("en", "US");
-        json.addConvertor(Date.class,new JSONDateConvertor(DateCache.DEFAULT_FORMAT,TimeZone.getTimeZone("GMT"),false,l));
-        json.addConvertor(Enum.class,new JSONEnumConvertor(false));
-        json.addConvertor(Object.class,new JSONObjectConvertor());
-
-        Woggle w0 = new Woggle();
-        Gizmo g0 = new Gizmo();
-        
-        w0.name="woggle0";
-        w0.nested=g0;
-        w0.number=100;
-        w0.other=Color.Blue;
-        g0.name="woggle1";
-        g0.nested=null;
-        g0.number=-101;
-        g0.tested=true;
-        g0.other=Color.Green;
-        
-        HashMap map = new HashMap();
-        map.put("date",new Date(1));
-        map.put("w0",w0);
-        map.put("g0",g0);
-
-        StringBuffer buf = new StringBuffer();
-        json.append((Appendable)buf,map);
-        String js=buf.toString();
-        
-        assertTrue(js.indexOf("\"date\":\"Thu Jan 01 00:00:00 GMT 1970\"")>=0);
-        assertTrue(js.indexOf("org.eclipse.jetty.util.ajax.JSONTest$Woggle")>=0);
-        assertTrue(js.indexOf("org.eclipse.jetty.util.ajax.JSONTest$Gizmo")<0);
-        assertTrue(js.indexOf("\"tested\":true")>=0);
-        assertTrue(js.indexOf("\"Green\"")>=0);
-        assertTrue(js.indexOf("\"Blue\"")<0);
-
-        json.addConvertor(Date.class,new JSONDateConvertor(DateCache.DEFAULT_FORMAT,TimeZone.getTimeZone("GMT"),true,l));
-        json.addConvertor(Enum.class,new JSONEnumConvertor(false));
-        w0.nested=null;
-        buf = new StringBuffer();
-        json.append((Appendable)buf,map);
-        js=buf.toString();
-        assertTrue(js.indexOf("\"date\":\"Thu Jan 01 00:00:00 GMT 1970\"")<0);
-        assertTrue(js.indexOf("org.eclipse.jetty.util.ajax.JSONTest$Woggle")>=0);
-        assertTrue(js.indexOf("org.eclipse.jetty.util.ajax.JSONTest$Gizmo")<0);
-        
-        Map map2=(HashMap)json.parse(new JSON.StringSource(js));
-        
-        assertTrue(map2.get("date") instanceof Date);
-        assertTrue(map2.get("w0") instanceof Woggle);
-        assertEquals(null, ((Woggle)map2.get("w0")).getOther() );
-        assertEquals(Color.Green.toString(), ((Map)map2.get("g0")).get("other"));
-        
-        
-
-        json.addConvertor(Date.class,new JSONDateConvertor(DateCache.DEFAULT_FORMAT,TimeZone.getTimeZone("GMT"),true,l));
-        json.addConvertor(Enum.class,new JSONEnumConvertor(true));
-        buf = new StringBuffer();
-        json.append((Appendable)buf,map);
-        js=buf.toString();
-        map2=(HashMap)json.parse(new JSON.StringSource(js));
-        
-        assertTrue(map2.get("date") instanceof Date);
-        assertTrue(map2.get("w0") instanceof Woggle);
-        assertEquals(null, ((Woggle)map2.get("w0")).getOther() );
-        Object o=((Map)map2.get("g0")).get("other");
-        assertEquals(Color.Green, o);
-           
-    }
-
-    /* ------------------------------------------------------------ */
-    public static class Gizmo
-    {
-        String name;
-        Gizmo nested;
-        long number;
-        boolean tested;
-        Object other;
-        
-        public String getName()
-        {
-            return name;
-        }
-        public Gizmo getNested()
-        {
-            return nested;
-        }
-        public long getNumber()
-        {
-            return number;
-        }
-        public boolean isTested()
-        {
-            return tested;
-        }
-        public Object getOther()
-        {
-            return other;
-        }
-    }
-    
-    /* ------------------------------------------------------------ */
-    public static class Woggle extends Gizmo implements JSON.Convertible
-    {
-        
-        public Woggle()
-        {
-        }
-        
-        public void fromJSON(Map object)
-        {
-            name=(String)object.get("name");
-            nested=(Gizmo)object.get("nested");
-            number=((Number)object.get("number")).intValue();
-        }
-
-        public void toJSON(Output out)
-        {
-            out.addClass(Woggle.class);
-            out.add("name",name);
-            out.add("nested",nested);
-            out.add("number",number);
-        }
-        
-        public String toString()
-        {
-            return name+"<<"+nested+">>"+number;
-        }
-        
-    }
-}
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/component/AggregateLifeCycleTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/component/AggregateLifeCycleTest.java
deleted file mode 100644
index cc4ed52..0000000
--- a/jetty-util/src/test/java/org/eclipse/jetty/util/component/AggregateLifeCycleTest.java
+++ /dev/null
@@ -1,288 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.util.component;
-
-import java.io.IOException;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import junit.framework.Assert;
-
-import org.eclipse.jetty.util.TypeUtil;
-import org.junit.Test;
-
-
-public class AggregateLifeCycleTest
-{
-
-    @Test
-    public void testStartStopDestroy() throws Exception
-    {
-        final AtomicInteger destroyed=new AtomicInteger();
-        final AtomicInteger started=new AtomicInteger();
-        final AtomicInteger stopped=new AtomicInteger();
-
-        AggregateLifeCycle a0=new AggregateLifeCycle();
-        
-        AggregateLifeCycle a1=new AggregateLifeCycle()
-        {
-            @Override
-            protected void doStart() throws Exception
-            {
-                started.incrementAndGet();
-                super.doStart();
-            }
-
-            @Override
-            protected void doStop() throws Exception
-            {
-                stopped.incrementAndGet();
-                super.doStop();
-            }
-            
-            @Override
-            public void destroy()
-            {
-                destroyed.incrementAndGet();
-                super.destroy();
-            }
-
-        };
-        
-        
-        a0.addBean(a1);
-        
-        a0.start();
-        Assert.assertEquals(1,started.get());
-        Assert.assertEquals(0,stopped.get());
-        Assert.assertEquals(0,destroyed.get());
-        
-        a0.start();
-        Assert.assertEquals(1,started.get());
-        Assert.assertEquals(0,stopped.get());
-        Assert.assertEquals(0,destroyed.get());
-        
-        a0.stop();
-        Assert.assertEquals(1,started.get());
-        Assert.assertEquals(1,stopped.get());
-        Assert.assertEquals(0,destroyed.get());
-        
-        a0.start();
-        Assert.assertEquals(2,started.get());
-        Assert.assertEquals(1,stopped.get());
-        Assert.assertEquals(0,destroyed.get());
-        
-        a0.stop();
-        Assert.assertEquals(2,started.get());
-        Assert.assertEquals(2,stopped.get());
-        Assert.assertEquals(0,destroyed.get());
-        
-        a0.destroy();
-        
-        Assert.assertEquals(2,started.get());
-        Assert.assertEquals(2,stopped.get());
-        Assert.assertEquals(1,destroyed.get());
-        
-        a0.start();
-        Assert.assertEquals(2,started.get());
-        Assert.assertEquals(2,stopped.get());
-        Assert.assertEquals(1,destroyed.get());
-        
-        a0.addBean(a1);
-        a0.start();
-        Assert.assertEquals(3,started.get());
-        Assert.assertEquals(2,stopped.get());
-        Assert.assertEquals(1,destroyed.get());
-        
-        a0.removeBean(a1);
-        a0.stop();
-        a0.destroy();
-        Assert.assertEquals(3,started.get());
-        Assert.assertEquals(2,stopped.get());
-        Assert.assertEquals(1,destroyed.get());
-        
-        a1.stop();
-        Assert.assertEquals(3,started.get());
-        Assert.assertEquals(3,stopped.get());
-        Assert.assertEquals(1,destroyed.get());
-        
-        a1.destroy();
-        Assert.assertEquals(3,started.get());
-        Assert.assertEquals(3,stopped.get());
-        Assert.assertEquals(2,destroyed.get());
-        
-    }
-    
-    @Test
-    public void testDisJoint() throws Exception
-    {
-        final AtomicInteger destroyed=new AtomicInteger();
-        final AtomicInteger started=new AtomicInteger();
-        final AtomicInteger stopped=new AtomicInteger();
-
-        AggregateLifeCycle a0=new AggregateLifeCycle();
-        
-        AggregateLifeCycle a1=new AggregateLifeCycle()
-        {
-            @Override
-            protected void doStart() throws Exception
-            {
-                started.incrementAndGet();
-                super.doStart();
-            }
-
-            @Override
-            protected void doStop() throws Exception
-            {
-                stopped.incrementAndGet();
-                super.doStop();
-            }
-            
-            @Override
-            public void destroy()
-            {
-                destroyed.incrementAndGet();
-                super.destroy();
-            }
-
-        };
-        
-        // Start the a1 bean before adding, makes it auto disjoint
-        a1.start();
-        
-        // Now add it
-        a0.addBean(a1);
-        Assert.assertFalse(a0.isManaged(a1));
-        
-        a0.start();
-        Assert.assertEquals(1,started.get());
-        Assert.assertEquals(0,stopped.get());
-        Assert.assertEquals(0,destroyed.get());
-        
-        a0.start();
-        Assert.assertEquals(1,started.get());
-        Assert.assertEquals(0,stopped.get());
-        Assert.assertEquals(0,destroyed.get());
-        
-        a0.stop();
-        Assert.assertEquals(1,started.get());
-        Assert.assertEquals(0,stopped.get());
-        Assert.assertEquals(0,destroyed.get());
-        
-        a1.stop();
-        Assert.assertEquals(1,started.get());
-        Assert.assertEquals(1,stopped.get());
-        Assert.assertEquals(0,destroyed.get());
-        
-        a0.start();
-        Assert.assertEquals(1,started.get());
-        Assert.assertEquals(1,stopped.get());
-        Assert.assertEquals(0,destroyed.get());
-        
-        a0.manage(a1);
-        Assert.assertTrue(a0.isManaged(a1));
-
-        a0.stop();
-        Assert.assertEquals(1,started.get());
-        Assert.assertEquals(1,stopped.get());
-        Assert.assertEquals(0,destroyed.get());
-        
-
-        a0.start();
-        Assert.assertEquals(2,started.get());
-        Assert.assertEquals(1,stopped.get());
-        Assert.assertEquals(0,destroyed.get());
-
-        a0.stop();
-        Assert.assertEquals(2,started.get());
-        Assert.assertEquals(2,stopped.get());
-        Assert.assertEquals(0,destroyed.get());
-        
-
-        a0.unmanage(a1);
-        Assert.assertFalse(a0.isManaged(a1));
-        
-        a0.destroy();
-        Assert.assertEquals(2,started.get());
-        Assert.assertEquals(2,stopped.get());
-        Assert.assertEquals(0,destroyed.get());
-        
-        a1.destroy();
-        Assert.assertEquals(2,started.get());
-        Assert.assertEquals(2,stopped.get());
-        Assert.assertEquals(1,destroyed.get());
-        
-    }
-    
-    @Test
-    public void testDumpable()
-    {
-        AggregateLifeCycle a0 = new AggregateLifeCycle();
-        a0.dumpStdErr();
-        
-        System.err.println("--");
-        AggregateLifeCycle aa0 = new AggregateLifeCycle();
-        a0.addBean(aa0);
-        a0.dumpStdErr();
-        
-        System.err.println("--");
-        AggregateLifeCycle aa1 = new AggregateLifeCycle();
-        a0.addBean(aa1);
-        a0.dumpStdErr();
-        
-        System.err.println("--");
-        AggregateLifeCycle aaa0 = new AggregateLifeCycle();
-        aa0.addBean(aaa0);
-        a0.dumpStdErr();   
-        
-        System.err.println("--");
-        AggregateLifeCycle aa10 = new AggregateLifeCycle();
-        aa1.addBean(aa10);
-        a0.dumpStdErr();   
-        
-
-        System.err.println("--");
-        final AggregateLifeCycle a1 = new AggregateLifeCycle();
-        final AggregateLifeCycle a2 = new AggregateLifeCycle();
-        final AggregateLifeCycle a3 = new AggregateLifeCycle();
-        final AggregateLifeCycle a4 = new AggregateLifeCycle();
-        
-
-        AggregateLifeCycle aa = new AggregateLifeCycle()
-        {
-            @Override
-            public void dump(Appendable out, String indent) throws IOException
-            {
-                out.append(this.toString()).append("\n");
-                dump(out,indent,TypeUtil.asList(new Object[]{a1,a2}),TypeUtil.asList(new Object[]{a3,a4}));
-            }
-        };
-        a0.addBean(aa);
-        a0.dumpStdErr();   
-
-        System.err.println("--");
-        a2.addBean(aa0);
-        a0.dumpStdErr(); 
-
-        System.err.println("--");
-        a0.unmanage(aa);
-        a2.unmanage(aa0);
-        a0.dumpStdErr(); 
-        
-    }
-}
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/component/ContainerLifeCycleTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/component/ContainerLifeCycleTest.java
new file mode 100644
index 0000000..e31fb7a
--- /dev/null
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/component/ContainerLifeCycleTest.java
@@ -0,0 +1,581 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.component;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.eclipse.jetty.util.TypeUtil;
+import org.junit.Assert;
+import org.junit.Test;
+
+
+public class ContainerLifeCycleTest
+{
+
+
+    @Test
+    public void testStartStopDestroy() throws Exception
+    {
+        final AtomicInteger destroyed=new AtomicInteger();
+        final AtomicInteger started=new AtomicInteger();
+        final AtomicInteger stopped=new AtomicInteger();
+
+        ContainerLifeCycle a0=new ContainerLifeCycle();
+
+        ContainerLifeCycle a1=new ContainerLifeCycle()
+        {
+            @Override
+            protected void doStart() throws Exception
+            {
+                started.incrementAndGet();
+                super.doStart();
+            }
+
+            @Override
+            protected void doStop() throws Exception
+            {
+                stopped.incrementAndGet();
+                super.doStop();
+            }
+
+            @Override
+            public void destroy()
+            {
+                destroyed.incrementAndGet();
+                super.destroy();
+            }
+
+        };
+
+
+        a0.addBean(a1);
+
+        a0.start();
+        Assert.assertEquals(1,started.get());
+        Assert.assertEquals(0,stopped.get());
+        Assert.assertEquals(0,destroyed.get());
+
+        a0.start();
+        Assert.assertEquals(1,started.get());
+        Assert.assertEquals(0,stopped.get());
+        Assert.assertEquals(0,destroyed.get());
+
+        a0.stop();
+        Assert.assertEquals(1,started.get());
+        Assert.assertEquals(1,stopped.get());
+        Assert.assertEquals(0,destroyed.get());
+
+        a0.start();
+        Assert.assertEquals(2,started.get());
+        Assert.assertEquals(1,stopped.get());
+        Assert.assertEquals(0,destroyed.get());
+
+        a0.stop();
+        Assert.assertEquals(2,started.get());
+        Assert.assertEquals(2,stopped.get());
+        Assert.assertEquals(0,destroyed.get());
+
+        a0.destroy();
+
+        Assert.assertEquals(2,started.get());
+        Assert.assertEquals(2,stopped.get());
+        Assert.assertEquals(1,destroyed.get());
+
+        a0.start();
+        Assert.assertEquals(2,started.get());
+        Assert.assertEquals(2,stopped.get());
+        Assert.assertEquals(1,destroyed.get());
+
+        a0.addBean(a1);
+        Assert.assertEquals(2,started.get());
+        Assert.assertEquals(2,stopped.get());
+        Assert.assertEquals(1,destroyed.get());
+        Assert.assertFalse(a0.isManaged(a1));
+        a0.start();
+        Assert.assertEquals(2,started.get());
+        Assert.assertEquals(2,stopped.get());
+        Assert.assertEquals(1,destroyed.get());
+        a1.start();
+        a0.manage(a1);
+        Assert.assertEquals(3,started.get());
+        Assert.assertEquals(2,stopped.get());
+        Assert.assertEquals(1,destroyed.get());
+
+        a0.removeBean(a1);
+        Assert.assertEquals(3,started.get());
+        Assert.assertEquals(3,stopped.get());
+        Assert.assertEquals(1,destroyed.get());
+        
+        a0.stop();
+        a0.destroy();
+        Assert.assertEquals(3,started.get());
+        Assert.assertEquals(3,stopped.get());
+        Assert.assertEquals(1,destroyed.get());
+
+        a1.stop();
+        Assert.assertEquals(3,started.get());
+        Assert.assertEquals(3,stopped.get());
+        Assert.assertEquals(1,destroyed.get());
+
+        a1.destroy();
+        Assert.assertEquals(3,started.get());
+        Assert.assertEquals(3,stopped.get());
+        Assert.assertEquals(2,destroyed.get());
+
+    }
+
+    @Test
+    public void testDisJoint() throws Exception
+    {
+        final AtomicInteger destroyed=new AtomicInteger();
+        final AtomicInteger started=new AtomicInteger();
+        final AtomicInteger stopped=new AtomicInteger();
+
+        ContainerLifeCycle a0=new ContainerLifeCycle();
+
+        ContainerLifeCycle a1=new ContainerLifeCycle()
+        {
+            @Override
+            protected void doStart() throws Exception
+            {
+                started.incrementAndGet();
+                super.doStart();
+            }
+
+            @Override
+            protected void doStop() throws Exception
+            {
+                stopped.incrementAndGet();
+                super.doStop();
+            }
+
+            @Override
+            public void destroy()
+            {
+                destroyed.incrementAndGet();
+                super.destroy();
+            }
+
+        };
+
+        // Start the a1 bean before adding, makes it auto disjoint
+        a1.start();
+
+        // Now add it
+        a0.addBean(a1);
+        Assert.assertFalse(a0.isManaged(a1));
+
+        a0.start();
+        Assert.assertEquals(1,started.get());
+        Assert.assertEquals(0,stopped.get());
+        Assert.assertEquals(0,destroyed.get());
+
+        a0.start();
+        Assert.assertEquals(1,started.get());
+        Assert.assertEquals(0,stopped.get());
+        Assert.assertEquals(0,destroyed.get());
+
+        a0.stop();
+        Assert.assertEquals(1,started.get());
+        Assert.assertEquals(0,stopped.get());
+        Assert.assertEquals(0,destroyed.get());
+
+        a1.stop();
+        Assert.assertEquals(1,started.get());
+        Assert.assertEquals(1,stopped.get());
+        Assert.assertEquals(0,destroyed.get());
+
+        a0.start();
+        Assert.assertEquals(1,started.get());
+        Assert.assertEquals(1,stopped.get());
+        Assert.assertEquals(0,destroyed.get());
+
+        a0.manage(a1);
+        Assert.assertTrue(a0.isManaged(a1));
+
+        a0.stop();
+        Assert.assertEquals(1,started.get());
+        Assert.assertEquals(1,stopped.get());
+        Assert.assertEquals(0,destroyed.get());
+
+
+        a0.start();
+        Assert.assertEquals(2,started.get());
+        Assert.assertEquals(1,stopped.get());
+        Assert.assertEquals(0,destroyed.get());
+
+        a0.stop();
+        Assert.assertEquals(2,started.get());
+        Assert.assertEquals(2,stopped.get());
+        Assert.assertEquals(0,destroyed.get());
+
+
+        a0.unmanage(a1);
+        Assert.assertFalse(a0.isManaged(a1));
+
+        a0.destroy();
+        Assert.assertEquals(2,started.get());
+        Assert.assertEquals(2,stopped.get());
+        Assert.assertEquals(0,destroyed.get());
+
+        a1.destroy();
+        Assert.assertEquals(2,started.get());
+        Assert.assertEquals(2,stopped.get());
+        Assert.assertEquals(1,destroyed.get());
+
+    }
+
+    @Test
+    public void testDumpable() throws Exception
+    {
+        ContainerLifeCycle a0 = new ContainerLifeCycle();
+        String dump=trim(a0.dump());
+        dump=check(dump,"org.eclipse.jetty.util.component.ContainerLifeCycl");
+
+        ContainerLifeCycle aa0 = new ContainerLifeCycle();
+        a0.addBean(aa0);
+        dump=trim(a0.dump());
+        dump=check(dump,"org.eclipse.jetty.util.component.ContainerLifeCycl");
+        dump=check(dump," +? org.eclipse.jetty.util.component.ContainerLife");
+
+        ContainerLifeCycle aa1 = new ContainerLifeCycle();
+        a0.addBean(aa1);
+        dump=trim(a0.dump());
+        dump=check(dump,"org.eclipse.jetty.util.component.ContainerLifeCycl");
+        dump=check(dump," +? org.eclipse.jetty.util.component.ContainerLife");
+        dump=check(dump," +? org.eclipse.jetty.util.component.ContainerLife");
+        dump=check(dump,"");
+        
+        ContainerLifeCycle aa2 = new ContainerLifeCycle();
+        a0.addBean(aa2,false);
+        dump=trim(a0.dump());
+        dump=check(dump,"org.eclipse.jetty.util.component.ContainerLifeCycl");
+        dump=check(dump," +? org.eclipse.jetty.util.component.ContainerLife");
+        dump=check(dump," +? org.eclipse.jetty.util.component.ContainerLife");
+        dump=check(dump," +~ org.eclipse.jetty.util.component.ContainerLife");
+        dump=check(dump,"");
+        
+        aa1.start();
+        a0.start();
+        dump=trim(a0.dump());
+        dump=check(dump,"org.eclipse.jetty.util.component.ContainerLifeCycl");
+        dump=check(dump," += org.eclipse.jetty.util.component.ContainerLife");
+        dump=check(dump," +~ org.eclipse.jetty.util.component.ContainerLife");
+        dump=check(dump," +~ org.eclipse.jetty.util.component.ContainerLife");
+        dump=check(dump,"");
+        
+        a0.manage(aa1);
+        a0.removeBean(aa2);
+        dump=trim(a0.dump());
+        dump=check(dump,"org.eclipse.jetty.util.component.ContainerLifeCycl");
+        dump=check(dump," += org.eclipse.jetty.util.component.ContainerLife");
+        dump=check(dump," += org.eclipse.jetty.util.component.ContainerLife");
+        dump=check(dump,"");        
+
+        ContainerLifeCycle aaa0 = new ContainerLifeCycle();
+        aa0.addBean(aaa0);
+        dump=trim(a0.dump());
+        dump=check(dump,"org.eclipse.jetty.util.component.ContainerLifeCycl");
+        dump=check(dump," += org.eclipse.jetty.util.component.ContainerLife");
+        dump=check(dump," |   +~ org.eclipse.jetty.util.component.Container");
+        dump=check(dump," += org.eclipse.jetty.util.component.ContainerLife");
+        dump=check(dump,"");
+
+        ContainerLifeCycle aa10 = new ContainerLifeCycle();
+        aa1.addBean(aa10,true);
+        dump=trim(a0.dump());
+        dump=check(dump,"org.eclipse.jetty.util.component.ContainerLifeCycl");
+        dump=check(dump," += org.eclipse.jetty.util.component.ContainerLife");
+        dump=check(dump," |   +~ org.eclipse.jetty.util.component.Container");
+        dump=check(dump," += org.eclipse.jetty.util.component.ContainerLife");
+        dump=check(dump,"     += org.eclipse.jetty.util.component.Container");
+        dump=check(dump,"");
+
+        final ContainerLifeCycle a1 = new ContainerLifeCycle();
+        final ContainerLifeCycle a2 = new ContainerLifeCycle();
+        final ContainerLifeCycle a3 = new ContainerLifeCycle();
+        final ContainerLifeCycle a4 = new ContainerLifeCycle();
+
+
+        ContainerLifeCycle aa = new ContainerLifeCycle()
+        {
+            @Override
+            public void dump(Appendable out, String indent) throws IOException
+            {
+                out.append(this.toString()).append("\n");
+                dump(out,indent,TypeUtil.asList(new Object[]{a1,a2}),TypeUtil.asList(new Object[]{a3,a4}));
+            }
+        };
+        a0.addBean(aa,true);
+        dump=trim(a0.dump());
+        dump=check(dump,"org.eclipse.jetty.util.component.ContainerLifeCycl");
+        dump=check(dump," += org.eclipse.jetty.util.component.ContainerLife");
+        dump=check(dump," |   +~ org.eclipse.jetty.util.component.Container");
+        dump=check(dump," += org.eclipse.jetty.util.component.ContainerLife");
+        dump=check(dump," |   += org.eclipse.jetty.util.component.Container");
+        dump=check(dump," += org.eclipse.jetty.util.component.ContainerLife");
+        dump=check(dump,"     +- org.eclipse.jetty.util.component.Container");
+        dump=check(dump,"     +- org.eclipse.jetty.util.component.Container");
+        dump=check(dump,"     +- org.eclipse.jetty.util.component.Container");
+        dump=check(dump,"     +- org.eclipse.jetty.util.component.Container");
+        dump=check(dump,"");
+
+        a2.addBean(aa0,true);
+        dump=trim(a0.dump());
+        dump=check(dump,"org.eclipse.jetty.util.component.ContainerLifeCycl");
+        dump=check(dump," += org.eclipse.jetty.util.component.ContainerLife");
+        dump=check(dump," |   +~ org.eclipse.jetty.util.component.Container");
+        dump=check(dump," += org.eclipse.jetty.util.component.ContainerLife");
+        dump=check(dump," |   += org.eclipse.jetty.util.component.Container");
+        dump=check(dump," += org.eclipse.jetty.util.component.ContainerLife");
+        dump=check(dump,"     +- org.eclipse.jetty.util.component.Container");
+        dump=check(dump,"     +- org.eclipse.jetty.util.component.Container");
+        dump=check(dump,"     |   += org.eclipse.jetty.util.component.Conta");
+        dump=check(dump,"     |       +~ org.eclipse.jetty.util.component.C");
+        dump=check(dump,"     +- org.eclipse.jetty.util.component.Container");
+        dump=check(dump,"     +- org.eclipse.jetty.util.component.Container");
+        dump=check(dump,"");
+
+        a2.unmanage(aa0);
+        dump=trim(a0.dump());
+        dump=check(dump,"org.eclipse.jetty.util.component.ContainerLifeCycl");
+        dump=check(dump," += org.eclipse.jetty.util.component.ContainerLife");
+        dump=check(dump," |   +~ org.eclipse.jetty.util.component.Container");
+        dump=check(dump," += org.eclipse.jetty.util.component.ContainerLife");
+        dump=check(dump," |   += org.eclipse.jetty.util.component.Container");
+        dump=check(dump," += org.eclipse.jetty.util.component.ContainerLife");
+        dump=check(dump,"     +- org.eclipse.jetty.util.component.Container");
+        dump=check(dump,"     +- org.eclipse.jetty.util.component.Container");
+        dump=check(dump,"     |   +~ org.eclipse.jetty.util.component.Conta");
+        dump=check(dump,"     +- org.eclipse.jetty.util.component.Container");
+        dump=check(dump,"     +- org.eclipse.jetty.util.component.Container");
+        dump=check(dump,"");
+
+        a0.unmanage(aa);
+        dump=trim(a0.dump());
+        dump=check(dump,"org.eclipse.jetty.util.component.ContainerLifeCycl");
+        dump=check(dump," += org.eclipse.jetty.util.component.ContainerLife");
+        dump=check(dump," |   +~ org.eclipse.jetty.util.component.Container");
+        dump=check(dump," += org.eclipse.jetty.util.component.ContainerLife");
+        dump=check(dump," |   += org.eclipse.jetty.util.component.Container");
+        dump=check(dump," +~ org.eclipse.jetty.util.component.ContainerLife");
+        dump=check(dump,"");
+
+    }
+    
+    @Test
+    public void listenerTest() throws Exception
+    {
+        final Queue<String> handled = new ConcurrentLinkedQueue<>();
+        final Queue<String> operation = new ConcurrentLinkedQueue<>();
+        final Queue<Container> parent = new ConcurrentLinkedQueue<>();
+        final Queue<Object> child = new ConcurrentLinkedQueue<>();
+        
+        Container.Listener listener= new Container.Listener()
+        { 
+            @Override
+            public void beanRemoved(Container p, Object c)
+            {
+                handled.add(toString());
+                operation.add("removed");
+                parent.add(p);
+                child.add(c);
+            }
+            
+            @Override
+            public void beanAdded(Container p, Object c)
+            {
+                handled.add(toString());
+                operation.add("added");
+                parent.add(p);
+                child.add(c);
+            }
+            
+            public @Override String toString() {return "listener";}
+        };
+        
+        
+        ContainerLifeCycle c0 = new ContainerLifeCycle() { public @Override String toString() {return "c0";}};
+        ContainerLifeCycle c00 = new ContainerLifeCycle() { public @Override String toString() {return "c00";}};
+        c0.addBean(c00);
+        String b000="b000";
+        c00.addBean(b000);
+        
+        c0.addBean(listener);
+
+        Assert.assertEquals("listener",handled.poll());
+        Assert.assertEquals("added",operation.poll());
+        Assert.assertEquals(c0,parent.poll());
+        Assert.assertEquals(c00,child.poll());
+
+        Assert.assertEquals("listener",handled.poll());
+        Assert.assertEquals("added",operation.poll());
+        Assert.assertEquals(c0,parent.poll());
+        Assert.assertEquals(listener,child.poll());
+
+        
+        Container.InheritedListener inherited= new Container.InheritedListener()
+        { 
+            @Override
+            public void beanRemoved(Container p, Object c)
+            {
+                handled.add(toString());
+                operation.add("removed");
+                parent.add(p);
+                child.add(c);
+            }
+            
+            @Override
+            public void beanAdded(Container p, Object c)
+            {
+                handled.add(toString());
+                operation.add("added");
+                parent.add(p);
+                child.add(c);
+            }
+            
+            public @Override String toString() {return "inherited";}
+        };
+
+        c0.addBean(inherited);
+
+        Assert.assertEquals("inherited",handled.poll());
+        Assert.assertEquals("added",operation.poll());
+        Assert.assertEquals(c0,parent.poll());
+        Assert.assertEquals(c00,child.poll());
+
+        Assert.assertEquals("inherited",handled.poll());
+        Assert.assertEquals("added",operation.poll());
+        Assert.assertEquals(c0,parent.poll());
+        Assert.assertEquals(listener,child.poll());
+
+        Assert.assertEquals("listener",handled.poll());
+        Assert.assertEquals("added",operation.poll());
+        Assert.assertEquals(c0,parent.poll());
+        Assert.assertEquals(inherited,child.poll());
+
+        Assert.assertEquals("inherited",handled.poll());
+        Assert.assertEquals("added",operation.poll());
+        Assert.assertEquals(c0,parent.poll());
+        Assert.assertEquals(inherited,child.poll());
+        
+        c0.start();
+        
+        Assert.assertEquals("inherited",handled.poll());
+        Assert.assertEquals("added",operation.poll());
+        Assert.assertEquals(c00,parent.poll());
+        Assert.assertEquals(b000,child.poll());
+        
+        Assert.assertEquals("inherited",handled.poll());
+        Assert.assertEquals("added",operation.poll());
+        Assert.assertEquals(c00,parent.poll());
+        Assert.assertEquals(inherited,child.poll());
+        
+        c0.removeBean(c00);
+        
+        Assert.assertEquals("inherited",handled.poll());
+        Assert.assertEquals("removed",operation.poll());
+        Assert.assertEquals(c00,parent.poll());
+        Assert.assertEquals(inherited,child.poll());
+        
+        Assert.assertEquals("inherited",handled.poll());
+        Assert.assertEquals("removed",operation.poll());
+        Assert.assertEquals(c00,parent.poll());
+        Assert.assertEquals(b000,child.poll());
+
+        Assert.assertEquals("listener",handled.poll());
+        Assert.assertEquals("removed",operation.poll());
+        Assert.assertEquals(c0,parent.poll());
+        Assert.assertEquals(c00,child.poll());
+
+        Assert.assertEquals("inherited",handled.poll());
+        Assert.assertEquals("removed",operation.poll());
+        Assert.assertEquals(c0,parent.poll());
+        Assert.assertEquals(c00,child.poll());
+        
+    }
+
+    private final class InheritedListenerLifeCycle extends AbstractLifeCycle implements Container.InheritedListener
+    {
+        public @Override void beanRemoved(Container p, Object c){}
+
+        public @Override void beanAdded(Container p, Object c) {}
+
+        public @Override String toString() {return "inherited";}
+    }
+    
+    @Test
+    public void testInheritedListener() throws Exception
+    {
+        ContainerLifeCycle c0 = new ContainerLifeCycle() { public @Override String toString() {return "c0";}};
+        ContainerLifeCycle c00 = new ContainerLifeCycle() { public @Override String toString() {return "c00";}};
+        ContainerLifeCycle c01 = new ContainerLifeCycle() { public @Override String toString() {return "c01";}};
+        Container.InheritedListener inherited= new InheritedListenerLifeCycle();
+
+        c0.addBean(c00);
+        c0.start();
+        c0.addBean(inherited);
+        c0.manage(inherited);
+        c0.addBean(c01);
+        c01.start();
+        c0.manage(c01);
+
+        Assert.assertTrue(c0.isManaged(inherited));
+        Assert.assertFalse(c00.isManaged(inherited));
+        Assert.assertFalse(c01.isManaged(inherited));
+    }
+
+    String trim(String s) throws IOException
+    {
+        StringBuilder b=new StringBuilder();
+        BufferedReader reader=new BufferedReader(new StringReader(s));
+
+        for (String line=reader.readLine();line!=null;line=reader.readLine())
+        {
+            if (line.length()>50)
+                line=line.substring(0,50);
+            b.append(line).append('\n');
+        }
+
+        return b.toString();
+    }
+
+    String check(String s,String x)
+    {
+        String r=s;
+        int nl = s.indexOf('\n');
+        if (nl>0)
+        {
+            r=s.substring(nl+1);
+            s=s.substring(0,nl);
+        }
+
+        Assert.assertEquals(x,s);
+
+        return r;
+    }
+
+
+
+}
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/component/LifeCycleListenerNestedTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/component/LifeCycleListenerNestedTest.java
new file mode 100644
index 0000000..40e4b34
--- /dev/null
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/component/LifeCycleListenerNestedTest.java
@@ -0,0 +1,280 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.component;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.hamcrest.Matcher;
+import org.junit.Test;
+
+/**
+ * Testing for LifeCycleListener events on nested components
+ * during runtime.
+ */
+public class LifeCycleListenerNestedTest
+{
+    public static class Foo extends ContainerLifeCycle
+    {
+        @Override
+        public String toString()
+        {
+            return Foo.class.getSimpleName();
+        }
+    }
+
+    public static class Bar extends ContainerLifeCycle
+    {
+        private final String id;
+
+        public Bar(String id)
+        {
+            this.id = id;
+        }
+
+        public String getId()
+        {
+            return id;
+        }
+
+        @Override
+        public int hashCode()
+        {
+            final int prime = 31;
+            int result = 1;
+            result = prime * result + ((id == null) ? 0 : id.hashCode());
+            return result;
+        }
+
+        @Override
+        public boolean equals(Object obj)
+        {
+            if (this == obj)
+                return true;
+            if (obj == null)
+                return false;
+            if (getClass() != obj.getClass())
+                return false;
+            Bar other = (Bar)obj;
+            if (id == null)
+            {
+                if (other.id != null)
+                    return false;
+            }
+            else if (!id.equals(other.id))
+                return false;
+            return true;
+        }
+
+        @Override
+        public String toString()
+        {
+            return Bar.class.getSimpleName() + "(" + id + ")";
+        }
+    }
+
+    public static enum LifeCycleEvent
+    {
+        STARTING,
+        STARTED,
+        FAILURE,
+        STOPPING,
+        STOPPED
+    }
+
+    public static class CapturingListener implements LifeCycle.Listener, Container.InheritedListener
+    {
+        private List<String> events = new ArrayList<>();
+
+        private void addEvent(Object obj, LifeCycleEvent event)
+        {
+            events.add(String.format("%s - %s",obj.toString(),event.name()));
+        }
+
+        @Override
+        public void lifeCycleStarting(LifeCycle event)
+        {
+            addEvent(event,LifeCycleEvent.STARTING);
+        }
+
+        @Override
+        public void lifeCycleStarted(LifeCycle event)
+        {
+            addEvent(event,LifeCycleEvent.STARTED);
+        }
+
+        @Override
+        public void lifeCycleFailure(LifeCycle event, Throwable cause)
+        {
+            addEvent(event,LifeCycleEvent.FAILURE);
+        }
+
+        @Override
+        public void lifeCycleStopping(LifeCycle event)
+        {
+            addEvent(event,LifeCycleEvent.STOPPING);
+        }
+
+        @Override
+        public void lifeCycleStopped(LifeCycle event)
+        {
+            addEvent(event,LifeCycleEvent.STOPPED);
+        }
+
+        public List<String> getEvents()
+        {
+            return events;
+        }
+
+        public void assertEvents(Matcher<Iterable<? super String>> matcher)
+        {
+            assertThat(events,matcher);
+        }
+
+        @Override
+        public void beanAdded(Container parent, Object child)
+        {
+            if(child instanceof ContainerLifeCycle)
+            {
+                ((ContainerLifeCycle)child).addLifeCycleListener(this);
+            }
+        }
+
+        @Override
+        public void beanRemoved(Container parent, Object child)
+        {
+            if(child instanceof ContainerLifeCycle)
+            {
+                ((ContainerLifeCycle)child).removeLifeCycleListener(this);
+            }
+        }
+    }
+
+    @Test
+    public void testAddBean_AddListener_Start() throws Exception
+    {
+        Foo foo = new Foo();
+        Bar bara = new Bar("a");
+        Bar barb = new Bar("b");
+        foo.addBean(bara);
+        foo.addBean(barb);
+
+        CapturingListener listener = new CapturingListener();
+        foo.addLifeCycleListener(listener);
+        foo.addEventListener(listener);
+
+        try
+        {
+            foo.start();
+
+            assertThat("Foo.started",foo.isStarted(),is(true));
+            assertThat("Bar(a).started",bara.isStarted(),is(true));
+            assertThat("Bar(b).started",barb.isStarted(),is(true));
+
+            listener.assertEvents(hasItem("Foo - STARTING"));
+            listener.assertEvents(hasItem("Foo - STARTED"));
+            listener.assertEvents(hasItem("Bar(a) - STARTING"));
+            listener.assertEvents(hasItem("Bar(a) - STARTED"));
+            listener.assertEvents(hasItem("Bar(b) - STARTING"));
+            listener.assertEvents(hasItem("Bar(b) - STARTED"));
+        }
+        finally
+        {
+            foo.stop();
+        }
+    }
+
+    @Test
+    public void testAddListener_AddBean_Start() throws Exception
+    {
+        Foo foo = new Foo();
+
+        CapturingListener listener = new CapturingListener();
+        foo.addLifeCycleListener(listener);
+        foo.addEventListener(listener);
+
+        Bar bara = new Bar("a");
+        Bar barb = new Bar("b");
+        foo.addBean(bara);
+        foo.addBean(barb);
+
+        try
+        {
+            foo.start();
+
+            assertThat("Foo.started",foo.isStarted(),is(true));
+            assertThat("Bar(a).started",bara.isStarted(),is(true));
+            assertThat("Bar(b).started",barb.isStarted(),is(true));
+
+            listener.assertEvents(hasItem("Foo - STARTING"));
+            listener.assertEvents(hasItem("Foo - STARTED"));
+            listener.assertEvents(hasItem("Bar(a) - STARTING"));
+            listener.assertEvents(hasItem("Bar(a) - STARTED"));
+            listener.assertEvents(hasItem("Bar(b) - STARTING"));
+            listener.assertEvents(hasItem("Bar(b) - STARTED"));
+        }
+        finally
+        {
+            foo.stop();
+        }
+    }
+
+    @Test
+    public void testAddListener_Start_AddBean() throws Exception
+    {
+        Foo foo = new Foo();
+        Bar bara = new Bar("a");
+        Bar barb = new Bar("b");
+
+        CapturingListener listener = new CapturingListener();
+        foo.addLifeCycleListener(listener);
+        foo.addEventListener(listener);
+
+        try
+        {
+            foo.start();
+
+            listener.assertEvents(hasItem("Foo - STARTING"));
+            listener.assertEvents(hasItem("Foo - STARTED"));
+
+            foo.addBean(bara);
+            foo.addBean(barb);
+
+            bara.start();
+            barb.start();
+
+            assertThat("Bar(a).started",bara.isStarted(),is(true));
+            assertThat("Bar(b).started",barb.isStarted(),is(true));
+
+            listener.assertEvents(hasItem("Bar(a) - STARTING"));
+            listener.assertEvents(hasItem("Bar(a) - STARTED"));
+            listener.assertEvents(hasItem("Bar(b) - STARTING"));
+            listener.assertEvents(hasItem("Bar(b) - STARTED"));
+        }
+        finally
+        {
+            barb.stop();
+            bara.stop();
+            foo.stop();
+        }
+    }
+}
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/component/LifeCycleListenerTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/component/LifeCycleListenerTest.java
index 7d9474f..e772e01 100644
--- a/jetty-util/src/test/java/org/eclipse/jetty/util/component/LifeCycleListenerTest.java
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/component/LifeCycleListenerTest.java
@@ -43,7 +43,7 @@ public class LifeCycleListenerTest
 
         try
         {
-            ((StdErrLog)Log.getLogger(AbstractLifeCycle.class)).setHideStacks(true);
+            StdErrLog.getLogger(AbstractLifeCycle.class).setHideStacks(true);
             lifecycle.start();
             assertTrue(false);
         }
@@ -54,11 +54,9 @@ public class LifeCycleListenerTest
         }
         finally
         {
-            ((StdErrLog)Log.getLogger(AbstractLifeCycle.class)).setHideStacks(false);
+            StdErrLog.getLogger(AbstractLifeCycle.class).setHideStacks(false);
         }
         lifecycle.setCause(null);
-        ((StdErrLog)Log.getLog()).setHideStacks(false);
-
 
         lifecycle.start();
 
@@ -73,7 +71,6 @@ public class LifeCycleListenerTest
 
         // check that the lifecycle's state is started
         assertTrue("The lifecycle state is not started",lifecycle.isStarted());
-        
     }
 
     @Test
@@ -83,14 +80,14 @@ public class LifeCycleListenerTest
         TestListener listener = new TestListener();
         lifecycle.addLifeCycleListener(listener);
 
-        
+
         // need to set the state to something other than stopped or stopping or
         // else
         // stop() will return without doing anything
 
         lifecycle.start();
         lifecycle.setCause(cause);
-        
+
         try
         {
             ((StdErrLog)Log.getLogger(AbstractLifeCycle.class)).setHideStacks(true);
@@ -108,7 +105,7 @@ public class LifeCycleListenerTest
         }
 
         lifecycle.setCause(null);
-        
+
         lifecycle.stop();
 
         // check that the stopping event has been thrown
@@ -125,7 +122,7 @@ public class LifeCycleListenerTest
         assertTrue("The lifecycle state is not stooped",lifecycle.isStopped());
     }
 
-    
+
     @Test
     public void testRemoveLifecycleListener ()
     throws Exception
@@ -140,14 +137,15 @@ public class LifeCycleListenerTest
         lifecycle.stop();
         assertFalse("The stopping event occurred", listener.stopping);
     }
+    
     private static class TestLifeCycle extends AbstractLifeCycle
     {
         Exception cause;
-        
+
         private TestLifeCycle()
         {
         }
-        
+
         @Override
         protected void doStart() throws Exception
         {
@@ -155,7 +153,7 @@ public class LifeCycleListenerTest
                 throw cause;
             super.doStart();
         }
-        
+
         @Override
         protected void doStop() throws Exception
         {
@@ -163,18 +161,16 @@ public class LifeCycleListenerTest
                 throw cause;
             super.doStop();
         }
-        
+
         public void setCause(Exception e)
         {
             cause=e;
         }
     }
-    
-   
 
     private class TestListener extends AbstractLifeCycle.AbstractLifeCycleListener
     {
-
+        @SuppressWarnings("unused")
         private boolean failure = false;
         private boolean started = false;
         private boolean starting = false;
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/log/JavaUtilLogTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/log/JavaUtilLogTest.java
index 830ef97..c4cbedc 100644
--- a/jetty-util/src/test/java/org/eclipse/jetty/util/log/JavaUtilLogTest.java
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/log/JavaUtilLogTest.java
@@ -78,10 +78,10 @@ public class JavaUtilLogTest
         log.info("Info test");
 
         jul.assertContainsLine("INFO|test|Info test");
-        
+
         JavaUtilLog loglong = new JavaUtilLog("test.a.long.name");
         loglong.info("Long test");
-        
+
         jul.assertContainsLine("INFO|test.a.long.name|Long test");
     }
 
@@ -89,27 +89,27 @@ public class JavaUtilLogTest
     public void testDebugOutput()
     {
         jul.clear();
-        
+
         // Common Throwable (for test)
         Throwable th = new Throwable("Message");
-        
+
         // Capture raw string form
         StringWriter tout = new StringWriter();
         th.printStackTrace(new PrintWriter(tout));
         String ths = tout.toString();
-        
+
         // Tests
         JavaUtilLog log = new JavaUtilLog("test.de.bug");
         setJulLevel("test.de.bug",Level.FINE);
-        
+
         log.debug("Simple debug");
         log.debug("Debug with {} parameter",1);
         log.debug("Debug with {} {} parameters", 2, "spiffy");
         log.debug("Debug with throwable", th);
         log.debug(th);
-        
+
         // jul.dump();
-        
+
         jul.assertContainsLine("FINE|test.de.bug|Simple debug");
         jul.assertContainsLine("FINE|test.de.bug|Debug with 1 parameter");
         jul.assertContainsLine("FINE|test.de.bug|Debug with 2 spiffy parameters");
@@ -121,27 +121,27 @@ public class JavaUtilLogTest
     public void testInfoOutput()
     {
         jul.clear();
-        
+
         // Common Throwable (for test)
         Throwable th = new Throwable("Message");
-        
+
         // Capture raw string form
         StringWriter tout = new StringWriter();
         th.printStackTrace(new PrintWriter(tout));
         String ths = tout.toString();
-        
+
         // Tests
         JavaUtilLog log = new JavaUtilLog("test.in.fo");
         setJulLevel("test.in.fo",Level.INFO);
-        
+
         log.info("Simple info");
         log.info("Info with {} parameter",1);
         log.info("Info with {} {} parameters", 2, "spiffy");
         log.info("Info with throwable", th);
         log.info(th);
-        
+
         // jul.dump();
-        
+
         jul.assertContainsLine("INFO|test.in.fo|Simple info");
         jul.assertContainsLine("INFO|test.in.fo|Info with 1 parameter");
         jul.assertContainsLine("INFO|test.in.fo|Info with 2 spiffy parameters");
@@ -153,62 +153,62 @@ public class JavaUtilLogTest
     public void testWarnOutput()
     {
         jul.clear();
-        
+
         // Common Throwable (for test)
         Throwable th = new Throwable("Message");
-        
+
         // Capture raw string form
         StringWriter tout = new StringWriter();
         th.printStackTrace(new PrintWriter(tout));
         String ths = tout.toString();
-        
+
         // Tests
         JavaUtilLog log = new JavaUtilLog("test.wa.rn");
         setJulLevel("test.wa.rn",Level.WARNING);
-        
+
         log.warn("Simple warn");
         log.warn("Warn with {} parameter",1);
         log.warn("Warn with {} {} parameters", 2, "spiffy");
         log.warn("Warn with throwable", th);
         log.warn(th);
-        
+
         // jul.dump();
-        
+
         jul.assertContainsLine("WARNING|test.wa.rn|Simple warn");
         jul.assertContainsLine("WARNING|test.wa.rn|Warn with 1 parameter");
         jul.assertContainsLine("WARNING|test.wa.rn|Warn with 2 spiffy parameters");
         jul.assertContainsLine("WARNING|test.wa.rn|Warn with throwable");
         jul.assertContainsLine(ths);
     }
-    
+
     @Test
     public void testFormattingWithNulls()
     {
         jul.clear();
-        
+
         JavaUtilLog log = new JavaUtilLog("test.nu.ll");
         setJulLevel("test.nu.ll",Level.INFO);
-        
+
         log.info("Testing info(msg,null,null) - {} {}","arg0","arg1");
         log.info("Testing info(msg,null,null) - {}/{}",null,null);
         log.info("Testing info(msg,null,null) > {}",null,null);
         log.info("Testing info(msg,null,null)",null,null);
         log.info(null,"Testing","info(null,arg0,arg1)");
         log.info(null,null,null);
-        
-        jul.dump();
-        
+
+        //jul.dump();
+
         jul.assertContainsLine("INFO|test.nu.ll|Testing info(msg,null,null) - null/null");
         jul.assertContainsLine("INFO|test.nu.ll|Testing info(msg,null,null) > null null");
         jul.assertContainsLine("INFO|test.nu.ll|Testing info(msg,null,null) null null");
         jul.assertContainsLine("INFO|test.nu.ll|null Testing info(null,arg0,arg1)");
         jul.assertContainsLine("INFO|test.nu.ll|null null null");
     }
-    
+
     @Test
     public void testIsDebugEnabled() {
         JavaUtilLog log = new JavaUtilLog("test.legacy");
-        
+
         setJulLevel("test.legacy",Level.ALL);
         Assert.assertThat("log.level(all).isDebugEnabled", log.isDebugEnabled(), is(true));
 
@@ -217,7 +217,7 @@ public class JavaUtilLogTest
 
         setJulLevel("test.legacy",Level.FINER);
         Assert.assertThat("log.level(finer).isDebugEnabled", log.isDebugEnabled(), is(true));
-        
+
         setJulLevel("test.legacy",Level.FINE);
         Assert.assertThat("log.level(fine).isDebugEnabled", log.isDebugEnabled(), is(true));
 
@@ -226,7 +226,7 @@ public class JavaUtilLogTest
 
         setJulLevel("test.legacy",Level.WARNING);
         Assert.assertThat("log.level(warn).isDebugEnabled", log.isDebugEnabled(), is(false));
-        
+
         log.setDebugEnabled(true);
         Assert.assertThat("log.isDebugEnabled", log.isDebugEnabled(), is(true));
 
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/log/LogTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/log/LogTest.java
index fef993b..999cdfb 100644
--- a/jetty-util/src/test/java/org/eclipse/jetty/util/log/LogTest.java
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/log/LogTest.java
@@ -18,7 +18,6 @@
 
 package org.eclipse.jetty.util.log;
 
-import static org.hamcrest.Matchers.is;
 
 import java.util.HashMap;
 import java.util.Map;
@@ -33,7 +32,6 @@ public class LogTest
     private static Logger originalLogger;
     private static Map<String,Logger> originalLoggers;
 
-    @SuppressWarnings("deprecation")
     @BeforeClass
     public static void rememberOriginalLogger()
     {
@@ -87,10 +85,9 @@ public class LogTest
         assertNamedLogging(Green.class);
     }
 
-    @SuppressWarnings("deprecation")
     private void assertNamedLogging(Class<?> clazz)
     {
         Logger lc = Log.getLogger(clazz);
-        Assert.assertThat("Named logging (impl=" + Log.getLog().getClass().getName() + ")",lc.getName(),is(clazz.getName()));
+        Assert.assertEquals("Named logging (impl=" + Log.getLog().getClass().getName() + ")",lc.getName(),clazz.getName());
     }
 }
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/log/StdErrCapture.java b/jetty-util/src/test/java/org/eclipse/jetty/util/log/StdErrCapture.java
index 8d61aa1..48a2f4d 100644
--- a/jetty-util/src/test/java/org/eclipse/jetty/util/log/StdErrCapture.java
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/log/StdErrCapture.java
@@ -61,7 +61,7 @@ public class StdErrCapture
         String output = new String(test.toByteArray());
         Assert.assertThat(output,not(containsString(unexpectedString)));
     }
-    
+
     public String toString()
     {
         err.flush();
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/log/StdErrLogTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/log/StdErrLogTest.java
index 68d3704..38fd37e 100644
--- a/jetty-util/src/test/java/org/eclipse/jetty/util/log/StdErrLogTest.java
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/log/StdErrLogTest.java
@@ -22,13 +22,16 @@ import static org.hamcrest.Matchers.containsString;
 import static org.hamcrest.Matchers.is;
 
 import java.io.ByteArrayOutputStream;
+import java.io.IOException;
 import java.io.PrintStream;
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.io.UnsupportedEncodingException;
+import java.nio.charset.StandardCharsets;
 import java.util.Properties;
 
 import org.junit.Assert;
+import org.junit.Before;
 import org.junit.Test;
 
 /**
@@ -36,6 +39,12 @@ import org.junit.Test;
  */
 public class StdErrLogTest
 {
+    @Before
+    public void before()
+    {
+        Thread.currentThread().setName("tname");
+    }
+
     @Test
     public void testStdErrLogFormat() throws UnsupportedEncodingException
     {
@@ -50,14 +59,15 @@ public class StdErrLogTest
         log.info("testing:{}",null,null);
         log.info("testing",null,null);
 
-        output.assertContains("INFO:oejul.LogTest:testing:test,format1");
-        output.assertContains("INFO:oejul.LogTest:testing:test,format1");
-        output.assertContains("INFO:oejul.LogTest:testing:test format2");
-        output.assertContains("INFO:oejul.LogTest:testing test format3");
-        output.assertContains("INFO:oejul.LogTest:testing:test,null");
-        output.assertContains("INFO:oejul.LogTest:testing null null");
-        output.assertContains("INFO:oejul.LogTest:testing:null");
-        output.assertContains("INFO:oejul.LogTest:testing");
+        System.err.println(output);
+        output.assertContains("INFO:oejul.LogTest:tname: testing:test,format1");
+        output.assertContains("INFO:oejul.LogTest:tname: testing:test,format1");
+        output.assertContains("INFO:oejul.LogTest:tname: testing:test format2");
+        output.assertContains("INFO:oejul.LogTest:tname: testing test format3");
+        output.assertContains("INFO:oejul.LogTest:tname: testing:test,null");
+        output.assertContains("INFO:oejul.LogTest:tname: testing null null");
+        output.assertContains("INFO:oejul.LogTest:tname: testing:null");
+        output.assertContains("INFO:oejul.LogTest:tname: testing");
     }
 
     @Test
@@ -65,14 +75,14 @@ public class StdErrLogTest
     {
         StdErrLog log = new StdErrLog("xxx",new Properties());
         StdErrCapture output = new StdErrCapture(log);
-        
+
         log.setLevel(StdErrLog.LEVEL_DEBUG);
         log.debug("testing {} {}","test","debug");
         log.info("testing {} {}","test","info");
         log.warn("testing {} {}","test","warn");
         log.setLevel(StdErrLog.LEVEL_INFO);
         log.debug("YOU SHOULD NOT SEE THIS!",null,null);
-        
+
         // Test for backward compat with old (now deprecated) method
         Logger before = log.getLogger("before");
         log.setDebugEnabled(true);
@@ -85,59 +95,59 @@ public class StdErrLogTest
         before.debug("testing {} {}","test","debug-before-false");
         log.debug("testing {} {}","test","debug-deprecated-false");
         after.debug("testing {} {}","test","debug-after-false");
-        
-        output.assertContains("DBUG:xxx:testing test debug");
-        output.assertContains("INFO:xxx:testing test info");
-        output.assertContains("WARN:xxx:testing test warn");
+
+        output.assertContains("DBUG:xxx:tname: testing test debug");
+        output.assertContains("INFO:xxx:tname: testing test info");
+        output.assertContains("WARN:xxx:tname: testing test warn");
         output.assertNotContains("YOU SHOULD NOT SEE THIS!");
-        output.assertContains("DBUG:x.before:testing test debug-before");
-        output.assertContains("DBUG:xxx:testing test debug-deprecated");
-        output.assertContains("DBUG:x.after:testing test debug-after");
-        output.assertNotContains("DBUG:x.before:testing test debug-before-false");
-        output.assertNotContains("DBUG:xxx:testing test debug-deprecated-false");
-        output.assertNotContains("DBUG:x.after:testing test debug-after-false");
+        output.assertContains("DBUG:x.before:tname: testing test debug-before");
+        output.assertContains("DBUG:xxx:tname: testing test debug-deprecated");
+        output.assertContains("DBUG:x.after:tname: testing test debug-after");
+        output.assertNotContains("DBUG:x.before:tname: testing test debug-before-false");
+        output.assertNotContains("DBUG:xxx:tname: testing test debug-deprecated-false");
+        output.assertNotContains("DBUG:x.after:tname: testing test debug-after-false");
     }
-    
+
     @Test
     public void testStdErrLogName()
     {
         StdErrLog log = new StdErrLog("test",new Properties());
         log.setPrintLongNames(true);
         StdErrCapture output = new StdErrCapture(log);
-        
+
         Assert.assertThat("Log.name", log.getName(), is("test"));
         Logger next=log.getLogger("next");
         Assert.assertThat("Log.name(child)", next.getName(), is("test.next"));
         next.info("testing {} {}","next","info");
-        
-        output.assertContains(":test.next:testing next info");
+
+        output.assertContains(":test.next:tname: testing next info");
     }
-    
+
     @Test
     public void testStdErrThrowable()
     {
         // Common Throwable (for test)
         Throwable th = new Throwable("Message");
-        
+
         // Capture raw string form
         StringWriter tout = new StringWriter();
         th.printStackTrace(new PrintWriter(tout));
         String ths = tout.toString();
-        
+
         // Start test
         StdErrLog log = new StdErrLog("test",new Properties());
         StdErrCapture output = new StdErrCapture(log);
 
         log.warn("ex",th);
         output.assertContains(ths);
-        
+
         th = new Throwable("Message with \033 escape");
 
         log.warn("ex",th);
         output.assertNotContains("Message with \033 escape");
         log.info(th.toString());
         output.assertNotContains("Message with \033 escape");
-        
+
         log.warn("ex",th);
         output.assertContains("Message with ? escape");
         log.info(th.toString());
@@ -186,7 +196,7 @@ public class StdErrLogTest
     public void testGetLoggingLevel_Default()
     {
         Properties props = new Properties();
-        
+
         // Default Levels
         Assert.assertEquals("Default Logging Level",StdErrLog.LEVEL_INFO,StdErrLog.getLoggingLevel(props,null));
         Assert.assertEquals("Default Logging Level",StdErrLog.LEVEL_INFO,StdErrLog.getLoggingLevel(props,""));
@@ -199,8 +209,8 @@ public class StdErrLogTest
     {
         Properties props = new Properties();
         props.setProperty("log.LEVEL", "WARN");
-        props.setProperty("org.eclipse.jetty.bad.LEVEL","FRUIT");
-        
+        props.setProperty("org.eclipse.jetty.bad.LEVEL","EXPECTED_BAD_LEVEL");
+
         // Default Level (because of bad level value)
         Assert.assertEquals("Bad Logging Level",StdErrLog.LEVEL_WARN,StdErrLog.getLoggingLevel(props,"org.eclipse.jetty.bad"));
     }
@@ -211,7 +221,7 @@ public class StdErrLogTest
         Properties props = new Properties();
         props.setProperty("log.LEVEL", "warn");
         props.setProperty("org.eclipse.jetty.util.LEVEL","info");
-        
+
         // Default Level
         Assert.assertEquals("Lowercase Level",StdErrLog.LEVEL_WARN,StdErrLog.getLoggingLevel(props,"org.eclipse.jetty"));
         // Specific Level
@@ -223,7 +233,7 @@ public class StdErrLogTest
     {
         Properties props = new Properties();
         props.setProperty("log.LEVEL","DEBUG");
-        
+
         // Default Levels
         Assert.assertEquals("Default Logging Level",StdErrLog.LEVEL_DEBUG,StdErrLog.getLoggingLevel(props,null));
         Assert.assertEquals("Default Logging Level",StdErrLog.LEVEL_DEBUG,StdErrLog.getLoggingLevel(props,""));
@@ -242,7 +252,7 @@ public class StdErrLogTest
         Assert.assertEquals(StdErrLog.LEVEL_INFO,StdErrLog.getLoggingLevel(props,null));
         Assert.assertEquals(StdErrLog.LEVEL_INFO,StdErrLog.getLoggingLevel(props,""));
         Assert.assertEquals(StdErrLog.LEVEL_INFO,StdErrLog.getLoggingLevel(props,"org.eclipse.jetty"));
-        
+
         // Specified Level
         Assert.assertEquals(StdErrLog.LEVEL_ALL,StdErrLog.getLoggingLevel(props,name));
     }
@@ -258,7 +268,7 @@ public class StdErrLogTest
         Assert.assertEquals(StdErrLog.LEVEL_INFO,StdErrLog.getLoggingLevel(props,""));
         Assert.assertEquals(StdErrLog.LEVEL_INFO,StdErrLog.getLoggingLevel(props,"org.eclipse.jetty"));
         Assert.assertEquals(StdErrLog.LEVEL_INFO,StdErrLog.getLoggingLevel(props,"org.eclipse.jetty.server.BogusObject"));
-        
+
         // Configured Level
         Assert.assertEquals(StdErrLog.LEVEL_DEBUG,StdErrLog.getLoggingLevel(props,StdErrLogTest.class.getName()));
         Assert.assertEquals(StdErrLog.LEVEL_DEBUG,StdErrLog.getLoggingLevel(props,"org.eclipse.jetty.util.Bogus"));
@@ -279,7 +289,7 @@ public class StdErrLogTest
         Assert.assertEquals(StdErrLog.LEVEL_DEBUG,StdErrLog.getLoggingLevel(props,""));
         Assert.assertEquals(StdErrLog.LEVEL_DEBUG,StdErrLog.getLoggingLevel(props,"org.eclipse.jetty"));
         Assert.assertEquals(StdErrLog.LEVEL_DEBUG,StdErrLog.getLoggingLevel(props,"org.eclipse.jetty.server.ServerObject"));
-        
+
         // Configured Level
         Assert.assertEquals(StdErrLog.LEVEL_WARN,StdErrLog.getLoggingLevel(props,StdErrLogTest.class.getName()));
         Assert.assertEquals(StdErrLog.LEVEL_WARN,StdErrLog.getLoggingLevel(props,"org.eclipse.jetty.util.MagicUtil"));
@@ -298,7 +308,7 @@ public class StdErrLogTest
     {
         StdErrLog log = new StdErrLog(StdErrLogTest.class.getName(),new Properties());
         log.setHideStacks(false);
-        
+
         StdErrCapture output = new StdErrCapture(log);
 
         // Start with default level
@@ -322,7 +332,7 @@ public class StdErrLogTest
         output.assertContains("Cheer Me");
 
         // Validate Stack Traces
-        output.assertContains(".StdErrLogTest:<zoom>");
+        output.assertContains(".StdErrLogTest:tname: <zoom>");
         output.assertContains("java.lang.Throwable: out of focus");
         output.assertContains("java.lang.Throwable: scene lost");
     }
@@ -364,17 +374,42 @@ public class StdErrLogTest
         output.assertContains("this record");
         output.assertContains("it is scratched.");
         output.assertNotContains("sorry?");
-        
+
         // Validate Stack Traces
         output.assertNotContains("<spoken line>");
         output.assertNotContains("on editing room floor");
 
-        output.assertContains(".StdErrLogTest:<zoom>");
+        output.assertContains(".StdErrLogTest:tname: <zoom>");
         output.assertContains("java.lang.Throwable: out of focus");
         output.assertContains("java.lang.Throwable: scene lost");
     }
 
     /**
+     * Tests {@link StdErrLog#LEVEL_OFF} filtering.
+     */
+    @Test
+    public void testOffFiltering() throws UnsupportedEncodingException
+    {
+        StdErrLog log = new StdErrLog(StdErrLogTest.class.getName(),new Properties());
+        log.setHideStacks(false);
+        log.setLevel(StdErrLog.LEVEL_OFF);
+
+        StdErrCapture output = new StdErrCapture(log);
+
+        // Various logging events
+        log.debug("Squelch");
+        log.debug("Squelch", new RuntimeException("Squelch"));
+        log.info("Squelch");
+        log.info("Squelch", new IllegalStateException("Squelch"));
+        log.warn("Squelch");
+        log.warn("Squelch", new Exception("Squelch"));
+        log.ignore(new Throwable("Squelch"));
+
+        // Validate Output
+        output.assertNotContains("Squelch");
+    }
+
+    /**
      * Tests StdErrLog.debug() methods with level filtering.
      * <p>
      * Should only see DEBUG level messages when level is set to {@link StdErrLog#LEVEL_DEBUG} and below.
@@ -394,7 +429,7 @@ public class StdErrLogTest
         // Level Debug
         log.setLevel(StdErrLog.LEVEL_DEBUG);
         log.debug("my hovercraft is");
-        
+
         log.debug("<zoom>", new Throwable("out of focus"));
         log.debug(new Throwable("scene lost"));
 
@@ -416,8 +451,8 @@ public class StdErrLogTest
         // Validate Stack Traces
         output.assertNotContains("<spoken line>");
         output.assertNotContains("on editing room floor");
-        
-        output.assertContains(".StdErrLogTest:<zoom>");
+
+        output.assertContains(".StdErrLogTest:tname: <zoom>");
         output.assertContains("java.lang.Throwable: out of focus");
         output.assertContains("java.lang.Throwable: scene lost");
     }
@@ -441,7 +476,7 @@ public class StdErrLogTest
         // Show Ignored
         log.setLevel(StdErrLog.LEVEL_ALL);
         log.ignore(new Throwable("Don't ignore me"));
-        
+
         // Set to Debug level
         log.setLevel(StdErrLog.LEVEL_DEBUG);
         log.ignore(new Throwable("Debug me"));
@@ -452,12 +487,12 @@ public class StdErrLogTest
         output.assertContains("Don't ignore me");
         output.assertNotContains("Debug me");
     }
-    
+
     @Test
     public void testIsDebugEnabled() {
         StdErrLog log = new StdErrLog(StdErrLogTest.class.getName(),new Properties());
         log.setHideStacks(true);
-        
+
         log.setLevel(StdErrLog.LEVEL_ALL);
         Assert.assertThat("log.level(all).isDebugEnabled", log.isDebugEnabled(), is(true));
 
@@ -469,14 +504,17 @@ public class StdErrLogTest
 
         log.setLevel(StdErrLog.LEVEL_WARN);
         Assert.assertThat("log.level(warn).isDebugEnabled", log.isDebugEnabled(), is(false));
+
+        log.setLevel(StdErrLog.LEVEL_OFF);
+        Assert.assertThat("log.level(off).isDebugEnabled", log.isDebugEnabled(), is(false));
     }
-    
+
     @Test
     public void testSetGetLevel()
     {
         StdErrLog log = new StdErrLog(StdErrLogTest.class.getName(),new Properties());
         log.setHideStacks(true);
-        
+
         log.setLevel(StdErrLog.LEVEL_ALL);
         Assert.assertThat("log.level(all).getLevel()", log.getLevel(), is(StdErrLog.LEVEL_ALL));
 
@@ -488,30 +526,33 @@ public class StdErrLogTest
 
         log.setLevel(StdErrLog.LEVEL_WARN);
         Assert.assertThat("log.level(warn).getLevel()", log.getLevel(), is(StdErrLog.LEVEL_WARN));
+
+        log.setLevel(StdErrLog.LEVEL_OFF);
+        Assert.assertThat("log.level(off).getLevel()", log.getLevel(), is(StdErrLog.LEVEL_OFF));
     }
-    
+
     @Test
     public void testGetChildLogger_Simple()
     {
         String baseName = "jetty";
         StdErrLog log = new StdErrLog(baseName,new Properties());
         log.setHideStacks(true);
-        
+
         Assert.assertThat("Logger.name", log.getName(), is("jetty"));
-        
+
         Logger log2 = log.getLogger("child");
         Assert.assertThat("Logger.child.name", log2.getName(), is("jetty.child"));
     }
-    
+
     @Test
     public void testGetChildLogger_Deep()
     {
         String baseName = "jetty";
         StdErrLog log = new StdErrLog(baseName,new Properties());
         log.setHideStacks(true);
-        
+
         Assert.assertThat("Logger.name", log.getName(), is("jetty"));
-        
+
         Logger log2 = log.getLogger("child.of.the.sixties");
         Assert.assertThat("Logger.child.name", log2.getName(), is("jetty.child.of.the.sixties"));
     }
@@ -522,11 +563,11 @@ public class StdErrLogTest
         String baseName = "jetty";
         StdErrLog log = new StdErrLog(baseName,new Properties());
         log.setHideStacks(true);
-        
+
         Assert.assertThat("Logger.name", log.getName(), is("jetty"));
-        
+
         // Pass null as child reference, should return parent logger
-        Logger log2 = log.getLogger(null);
+        Logger log2 = log.getLogger((String)null);
         Assert.assertThat("Logger.child.name", log2.getName(), is("jetty"));
         Assert.assertSame("Should have returned same logger", log2, log);
     }
@@ -537,9 +578,9 @@ public class StdErrLogTest
         String baseName = "jetty";
         StdErrLog log = new StdErrLog(baseName,new Properties());
         log.setHideStacks(true);
-        
+
         Assert.assertThat("Logger.name", log.getName(), is("jetty"));
-        
+
         // Pass empty name as child reference, should return parent logger
         Logger log2 = log.getLogger("");
         Assert.assertThat("Logger.child.name", log2.getName(), is("jetty"));
@@ -552,9 +593,9 @@ public class StdErrLogTest
         String baseName = "jetty";
         StdErrLog log = new StdErrLog(baseName,new Properties());
         log.setHideStacks(true);
-        
+
         Assert.assertThat("Logger.name", log.getName(), is("jetty"));
-        
+
         // Pass empty name as child reference, should return parent logger
         Logger log2 = log.getLogger("      ");
         Assert.assertThat("Logger.child.name", log2.getName(), is("jetty"));
@@ -565,14 +606,14 @@ public class StdErrLogTest
     public void testGetChildLogger_NullParent()
     {
         StdErrLog log = new StdErrLog(null,new Properties());
-        
+
         Assert.assertThat("Logger.name", log.getName(), is(""));
-        
+
         Logger log2 = log.getLogger("jetty");
         Assert.assertThat("Logger.child.name", log2.getName(), is("jetty"));
         Assert.assertNotSame("Should have returned same logger", log2, log);
     }
-    
+
     @Test
     public void testToString()
     {
@@ -583,34 +624,39 @@ public class StdErrLogTest
 
         log.setLevel(StdErrLog.LEVEL_DEBUG);
         Assert.assertThat("Logger.toString", log.toString(), is("StdErrLog:jetty:LEVEL=DEBUG"));
-        
+
         log.setLevel(StdErrLog.LEVEL_INFO);
         Assert.assertThat("Logger.toString", log.toString(), is("StdErrLog:jetty:LEVEL=INFO"));
-        
+
         log.setLevel(StdErrLog.LEVEL_WARN);
         Assert.assertThat("Logger.toString", log.toString(), is("StdErrLog:jetty:LEVEL=WARN"));
-        
+
         log.setLevel(99); // intentionally bogus level
         Assert.assertThat("Logger.toString", log.toString(), is("StdErrLog:jetty:LEVEL=?"));
     }
-    
+
     @Test
     public void testPrintSource() throws UnsupportedEncodingException
     {
-        StdErrLog log = new StdErrLog("test",new Properties());
+        Properties props=new Properties();
+        props.put("test.SOURCE","true");
+        StdErrLog log = new StdErrLog("test",props);
         log.setLevel(StdErrLog.LEVEL_DEBUG);
-        log.setSource(true);
 
         ByteArrayOutputStream test = new ByteArrayOutputStream();
         PrintStream err = new PrintStream(test);
         log.setStdErrStream(err);
-        
+
         log.debug("Show me the source!");
-        
-        String output = new String(test.toByteArray(),"UTF-8");
-        // System.err.print(output);   
-        
+
+        String output = new String(test.toByteArray(), StandardCharsets.UTF_8);
+        // System.err.print(output);
+
         Assert.assertThat(output, containsString(".StdErrLogTest#testPrintSource(StdErrLogTest.java:"));
+        
+
+        props.put("test.SOURCE","false");
+        log=new StdErrLog("other",props);
     }
 
     @Test
@@ -619,14 +665,14 @@ public class StdErrLogTest
         Properties props = new Properties();
         props.setProperty("org.eclipse.jetty.util.LEVEL","WARN");
         props.setProperty("org.eclipse.jetty.io.LEVEL", "WARN");
-        
+
         StdErrLog root = new StdErrLog("", props);
         assertLevel(root,StdErrLog.LEVEL_INFO); // default
 
         StdErrLog log = (StdErrLog)root.getLogger(StdErrLogTest.class.getName());
         Assert.assertThat("Log.isDebugEnabled()", log.isDebugEnabled(), is(false));
         assertLevel(log,StdErrLog.LEVEL_WARN); // as configured
-        
+
         // Boot stomp it all to debug
         root.setDebugEnabled(true);
         Assert.assertThat("Log.isDebugEnabled()", log.isDebugEnabled(), is(true));
@@ -637,12 +683,32 @@ public class StdErrLogTest
         Assert.assertThat("Log.isDebugEnabled()", log.isDebugEnabled(), is(false));
         assertLevel(log,StdErrLog.LEVEL_WARN); // as configured
     }
+
+    @Test
+    public void testSuppressed()
+    {
+        StdErrLog log = new StdErrLog("xxx",new Properties());
+        StdErrCapture output = new StdErrCapture(log);
+
+        Exception inner = new Exception("inner");
+        inner.addSuppressed( new IllegalStateException(){{addSuppressed(new Exception("branch0"));}});
+        IOException outer = new IOException("outer",inner);
+        
+        outer.addSuppressed( new IllegalStateException(){{addSuppressed(new Exception("branch1"));}});
+        outer.addSuppressed( new IllegalArgumentException(){{addSuppressed(new Exception("branch2"));}});
+        
+        log.warn("problem",outer);
+
+        output.assertContains("\t|\t|java.lang.Exception: branch2");
+        output.assertContains("\t|\t|java.lang.Exception: branch1");
+        output.assertContains("\t|\t|java.lang.Exception: branch0");
+    }
     
     private void assertLevel(StdErrLog log, int expectedLevel)
     {
         Assert.assertThat("Log[" + log.getName() + "].level",levelToString(log.getLevel()),is(levelToString(expectedLevel)));
     }
-    
+
     private String levelToString(int level)
     {
         switch (level)
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/resource/AbstractFSResourceTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/resource/AbstractFSResourceTest.java
new file mode 100644
index 0000000..67cafac
--- /dev/null
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/resource/AbstractFSResourceTest.java
@@ -0,0 +1,535 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.resource;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+import static org.junit.Assume.*;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.net.URI;
+import java.net.URL;
+import java.nio.ByteBuffer;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.file.FileSystemException;
+import java.nio.file.Files;
+import java.nio.file.InvalidPathException;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.eclipse.jetty.toolchain.test.FS;
+import org.eclipse.jetty.toolchain.test.IO;
+import org.eclipse.jetty.toolchain.test.OS;
+import org.eclipse.jetty.toolchain.test.TestingDir;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.CollectionAssert;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+
+public abstract class AbstractFSResourceTest
+{
+    @Rule
+    public TestingDir testdir = new TestingDir();
+
+    public abstract Resource newResource(URI uri) throws IOException;
+
+    public abstract Resource newResource(File file) throws IOException;
+
+    private URI createEmptyFile(String name) throws IOException
+    {
+        File file = testdir.getFile(name);
+        file.createNewFile();
+        return file.toURI();
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testNonAbsoluteURI() throws Exception
+    {
+        newResource(new URI("path/to/resource"));
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testNotFileURI() throws Exception
+    {
+        newResource(new URI("http://www.eclipse.org/jetty/"));
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testBogusFilename() throws Exception
+    {
+        if (OS.IS_UNIX)
+        {
+            // A windows path is invalid under unix
+            newResource(new URI("file://Z:/:"));
+        }
+        else if (OS.IS_WINDOWS)
+        {
+            // "CON" is a reserved name under windows
+            newResource(new URI("file://CON"));
+        }
+        else
+        {
+            assumeFalse("Unknown OS type",false);
+        }   
+    }
+
+    @Test
+    public void testIsContainedIn() throws Exception
+    {
+        createEmptyFile("foo");
+
+        try (Resource base = newResource(testdir.getDir()))
+        {
+            Resource res = base.addPath("foo");
+            assertThat("is contained in",res.isContainedIn(base),is(false));
+        }
+    }
+
+    @Test
+    public void testAddPath() throws Exception
+    {
+        File dir = testdir.getDir();
+        File subdir = new File(dir,"sub");
+        FS.ensureDirExists(subdir);
+
+        try (Resource base = newResource(testdir.getDir()))
+        {
+            Resource sub = base.addPath("sub");
+            assertThat("sub/.isDirectory",sub.isDirectory(),is(true));
+            
+            Resource tmp = sub.addPath("/tmp");
+            assertThat("No root",tmp.exists(),is(false));
+        }
+    }
+    
+    @Test
+    public void testIsDirectory() throws Exception
+    {
+        File dir = testdir.getDir();
+        createEmptyFile("foo");
+
+        File subdir = new File(dir,"sub");
+        FS.ensureDirExists(subdir);
+
+        try (Resource base = newResource(testdir.getDir()))
+        {
+            Resource res = base.addPath("foo");
+            assertThat("foo.isDirectory",res.isDirectory(),is(false));
+
+            Resource sub = base.addPath("sub");
+            assertThat("sub/.isDirectory",sub.isDirectory(),is(true));
+        }
+    }
+
+    @Test
+    public void testLastModified() throws Exception
+    {
+        File file = testdir.getFile("foo");
+        file.createNewFile();
+
+        long expected = file.lastModified();
+
+        try (Resource base = newResource(testdir.getDir()))
+        {
+            Resource res = base.addPath("foo");
+            assertThat("foo.lastModified",res.lastModified(),is(expected));
+        }
+    }
+
+    @Test
+    public void testLastModified_NotExists() throws Exception
+    {
+        try (Resource base = newResource(testdir.getDir()))
+        {
+            Resource res = base.addPath("foo");
+            assertThat("foo.lastModified",res.lastModified(),is(0L));
+        }
+    }
+
+    @Test
+    public void testLength() throws Exception
+    {
+        File file = testdir.getFile("foo");
+        file.createNewFile();
+
+        try (StringReader reader = new StringReader("foo"); FileWriter writer = new FileWriter(file))
+        {
+            IO.copy(reader,writer);
+        }
+
+        long expected = file.length();
+
+        try (Resource base = newResource(testdir.getDir()))
+        {
+            Resource res = base.addPath("foo");
+            assertThat("foo.length",res.length(),is(expected));
+        }
+    }
+
+    @Test
+    public void testLength_NotExists() throws Exception
+    {
+        try (Resource base = newResource(testdir.getDir()))
+        {
+            Resource res = base.addPath("foo");
+            assertThat("foo.length",res.length(),is(0L));
+        }
+    }
+
+    @Test
+    public void testDelete() throws Exception
+    {
+        File file = testdir.getFile("foo");
+        file.createNewFile();
+
+        try (Resource base = newResource(testdir.getDir()))
+        {
+            // Is it there?
+            Resource res = base.addPath("foo");
+            assertThat("foo.exists",res.exists(),is(true));
+            // delete it
+            assertThat("foo.delete",res.delete(),is(true));
+            // is it there?
+            assertThat("foo.exists",res.exists(),is(false));
+        }
+    }
+
+    @Test
+    public void testDelete_NotExists() throws Exception
+    {
+        try (Resource base = newResource(testdir.getDir()))
+        {
+            // Is it there?
+            Resource res = base.addPath("foo");
+            assertThat("foo.exists",res.exists(),is(false));
+            // delete it
+            assertThat("foo.delete",res.delete(),is(false));
+            // is it there?
+            assertThat("foo.exists",res.exists(),is(false));
+        }
+    }
+
+    @Test
+    public void testName() throws Exception
+    {
+        String expected = testdir.getDir().getAbsolutePath();
+
+        try (Resource base = newResource(testdir.getDir()))
+        {
+            assertThat("base.name",base.getName(),is(expected));
+        }
+    }
+
+    @Test
+    public void testInputStream() throws Exception
+    {
+        File file = testdir.getFile("foo");
+        file.createNewFile();
+
+        String content = "Foo is here";
+
+        try (StringReader reader = new StringReader(content); FileWriter writer = new FileWriter(file))
+        {
+            IO.copy(reader,writer);
+        }
+
+        try (Resource base = newResource(testdir.getDir()))
+        {
+            Resource foo = base.addPath("foo");
+            try (InputStream stream = foo.getInputStream(); InputStreamReader reader = new InputStreamReader(stream); StringWriter writer = new StringWriter())
+            {
+                IO.copy(reader,writer);
+                assertThat("Stream",writer.toString(),is(content));
+            }
+        }
+    }
+
+    @Test
+    public void testReadableByteChannel() throws Exception
+    {
+        File file = testdir.getFile("foo");
+        file.createNewFile();
+
+        String content = "Foo is here";
+
+        try (StringReader reader = new StringReader(content); FileWriter writer = new FileWriter(file))
+        {
+            IO.copy(reader,writer);
+        }
+
+        try (Resource base = newResource(testdir.getDir()))
+        {
+            Resource foo = base.addPath("foo");
+            try (ReadableByteChannel channel = foo.getReadableByteChannel())
+            {
+                ByteBuffer buf = ByteBuffer.allocate(256);
+                channel.read(buf);
+                buf.flip();
+                String actual = BufferUtil.toUTF8String(buf);
+                assertThat("ReadableByteChannel content",actual,is(content));
+            }
+        }
+    }
+    
+    @Test
+    public void testGetURI() throws Exception
+    {
+        File file = testdir.getFile("foo");
+        file.createNewFile();
+
+        URI expected = file.toURI();
+
+        try (Resource base = newResource(testdir.getDir()))
+        {
+            Resource foo = base.addPath("foo");
+            assertThat("getURI",foo.getURI(),is(expected));
+        }
+    }
+
+    @Test
+    public void testGetURL() throws Exception
+    {
+        File file = testdir.getFile("foo");
+        file.createNewFile();
+
+        URL expected = file.toURI().toURL();
+
+        try (Resource base = newResource(testdir.getDir()))
+        {
+            Resource foo = base.addPath("foo");
+            assertThat("getURL",foo.getURL(),is(expected));
+        }
+    }
+    
+    @Test
+    public void testList() throws Exception
+    {
+        File dir = testdir.getDir();
+        FS.touch(new File(dir, "foo"));
+        FS.touch(new File(dir, "bar"));
+        FS.ensureDirExists(new File(dir, "tick"));
+        FS.ensureDirExists(new File(dir, "tock"));
+        
+        List<String> expected = new ArrayList<>();
+        expected.add("foo");
+        expected.add("bar");
+        expected.add("tick/");
+        expected.add("tock/");
+
+        try (Resource base = newResource(testdir.getDir()))
+        {
+            String list[] = base.list();
+            List<String> actual = Arrays.asList(list);
+            
+            CollectionAssert.assertContainsUnordered("Resource Directory Listing",
+                    expected,actual);
+        }
+    }
+    
+    @Test
+    public void testSymlink() throws Exception
+    {
+        File dir = testdir.getDir();
+        
+        Path foo = new File(dir, "foo").toPath();
+        Path bar = new File(dir, "bar").toPath();
+        
+        try
+        {
+            Files.createFile(foo);
+            Files.createSymbolicLink(bar,foo);
+        }
+        catch (UnsupportedOperationException | FileSystemException e)
+        {
+            // if unable to create symlink, no point testing the rest
+            // this is the path that Microsoft Windows takes.
+            assumeNoException(e);
+        }
+        
+        try (Resource base = newResource(testdir.getDir()))
+        {
+            Resource resFoo = base.addPath("foo");
+            Resource resBar = base.addPath("bar");
+            
+            // Access to the same resource, but via a symlink means that they are not equivalent
+            assertThat("foo.equals(bar)", resFoo.equals(resBar), is(false));
+            
+            assertThat("foo.alias", resFoo.getAlias(), nullValue());
+            assertThat("bar.alias", resBar.getAlias(), is(foo.toUri()));
+        }
+    }
+    
+    @Test
+    public void testSemicolon() throws Exception
+    {
+        File dir = testdir.getDir();
+        
+        try
+        {
+            // attempt to create file
+            Path foo = new File(dir, "foo;").toPath();
+            Files.createFile(foo);
+        }
+        catch (Exception e)
+        {
+            // if unable to create file, no point testing the rest
+            // this is the path that Microsoft Windows takes.
+            assumeNoException(e);
+        }
+
+        try (Resource base = newResource(testdir.getDir()))
+        {
+            Resource res = base.addPath("foo;");
+            assertThat("Alias: " + res,res.getAlias(),nullValue());
+        }
+    }
+
+    @Test
+    public void testExist_Normal() throws Exception
+    {
+        createEmptyFile("a.jsp");
+
+        URI ref = testdir.getDir().toURI().resolve("a.jsp");
+        try (Resource fileres = newResource(ref))
+        {
+            assertThat("Resource: " + fileres,fileres.exists(),is(true));
+        }
+    }
+
+    @Test
+    public void testSingleQuoteInFileName() throws Exception
+    {
+        createEmptyFile("foo's.txt");
+        createEmptyFile("f o's.txt");
+
+        URI refQuoted = testdir.getDir().toURI().resolve("foo's.txt");
+
+        try (Resource fileres = newResource(refQuoted))
+        {
+            assertThat("Exists: " + refQuoted,fileres.exists(),is(true));
+            assertThat("Alias: " + refQuoted,fileres.getAlias(),nullValue());
+        }
+
+        URI refEncoded = testdir.getDir().toURI().resolve("foo%27s.txt");
+
+        try (Resource fileres = newResource(refEncoded))
+        {
+            assertThat("Exists: " + refEncoded,fileres.exists(),is(true));
+            assertThat("Alias: " + refEncoded,fileres.getAlias(),nullValue());
+        }
+
+        URI refQuoteSpace = testdir.getDir().toURI().resolve("f%20o's.txt");
+
+        try (Resource fileres = newResource(refQuoteSpace))
+        {
+            assertThat("Exists: " + refQuoteSpace,fileres.exists(),is(true));
+            assertThat("Alias: " + refQuoteSpace,fileres.getAlias(),nullValue());
+        }
+
+        URI refEncodedSpace = testdir.getDir().toURI().resolve("f%20o%27s.txt");
+
+        try (Resource fileres = newResource(refEncodedSpace))
+        {
+            assertThat("Exists: " + refEncodedSpace,fileres.exists(),is(true));
+            assertThat("Alias: " + refEncodedSpace,fileres.getAlias(),nullValue());
+        }
+
+        URI refA = testdir.getDir().toURI().resolve("foo's.txt");
+        URI refB = testdir.getDir().toURI().resolve("foo%27s.txt");
+
+        StringBuilder msg = new StringBuilder();
+        msg.append("URI[a].equals(URI[b])").append(System.lineSeparator());
+        msg.append("URI[a] = ").append(refA).append(System.lineSeparator());
+        msg.append("URI[b] = ").append(refB);
+
+        // show that simple URI.equals() doesn't work
+        assertThat(msg.toString(),refA.equals(refB),is(false));
+
+        // now show that Resource.equals() does work
+        try (Resource a = newResource(refA); Resource b = newResource(refB);)
+        {
+            assertThat("A.equals(B)",a.equals(b),is(true));
+        }
+    }
+
+    @Test
+    public void testExist_BadNull() throws Exception
+    {
+        createEmptyFile("a.jsp");
+
+        try
+        {
+            // request with null at end
+            URI ref = testdir.getDir().toURI().resolve("a.jsp%00");
+            assertThat("Null URI",ref,notNullValue());
+
+            newResource(ref);
+            fail("Should have thrown " + InvalidPathException.class);
+        }
+        catch (InvalidPathException e)
+        {
+            // Expected path
+        }
+    }
+
+    @Test
+    public void testExist_BadNullX() throws Exception
+    {
+        createEmptyFile("a.jsp");
+
+        try
+        {
+            // request with null and x at end
+            URI ref = testdir.getDir().toURI().resolve("a.jsp%00x");
+            assertThat("NullX URI",ref,notNullValue());
+
+            newResource(ref);
+            fail("Should have thrown " + InvalidPathException.class);
+        }
+        catch (InvalidPathException e)
+        {
+            // Expected path
+        }
+    }
+    
+    @Test
+    public void testUtf8Dir() throws Exception
+    {
+        File dir=new File(testdir.getDir(),"bãm");
+        dir.mkdir();
+        File file = new File(dir,"file.txt");
+        file.createNewFile();
+        
+        Resource base = newResource(dir);
+        Assert.assertNull(base.getAlias());
+        
+        Resource r = base.addPath("file.txt");
+        Assert.assertNull(r.getAlias());
+        
+    }
+}
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/resource/FileResourceTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/resource/FileResourceTest.java
index cb43cad..df5b159 100644
--- a/jetty-util/src/test/java/org/eclipse/jetty/util/resource/FileResourceTest.java
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/resource/FileResourceTest.java
@@ -18,81 +18,36 @@
 
 package org.eclipse.jetty.util.resource;
 
-import static org.hamcrest.Matchers.*;
-
 import java.io.File;
 import java.io.IOException;
-import java.net.MalformedURLException;
 import java.net.URI;
-import java.net.URISyntaxException;
-import java.net.URL;
 
-import org.eclipse.jetty.toolchain.test.TestingDir;
-import org.eclipse.jetty.util.StringUtil;
-import org.eclipse.jetty.util.UrlEncoded;
-import org.junit.Assert;
 import org.junit.Ignore;
-import org.junit.Rule;
 import org.junit.Test;
 
-public class FileResourceTest
+public class FileResourceTest extends AbstractFSResourceTest
 {
-    @Rule
-    public TestingDir testdir = new TestingDir();
-
-    private URI createDummyFile(String name) throws IOException
+    @Override
+    public Resource newResource(URI uri) throws IOException
     {
-        File file = testdir.getFile(name);
-        file.createNewFile();
-        return file.toURI();
+        return new FileResource(uri);
     }
     
-    private URL decode(URL url) throws MalformedURLException
+    @Override
+    public Resource newResource(File file) throws IOException
     {
-        String raw = url.toExternalForm();
-        String decoded = UrlEncoded.decodeString(raw,0,raw.length(),StringUtil.__UTF8);
-        return new URL(decoded);
+        return new FileResource(file);
     }
-
-    @Test
-    public void testExist_Normal() throws Exception
-    {
-        createDummyFile("a.jsp");
-
-        URI ref = testdir.getDir().toURI().resolve("a.jsp");
-        FileResource fileres = new FileResource(decode(ref.toURL()));
-        Assert.assertThat("FileResource: " + fileres,fileres.exists(),is(true));
-    }
-
+    
     @Ignore("Cannot get null to be seen by FileResource")
     @Test
     public void testExist_BadNull() throws Exception
     {
-        createDummyFile("a.jsp");
-
-        try {
-            // request with null at end
-            URI ref = testdir.getDir().toURI().resolve("a.jsp%00");
-            FileResource fileres = new FileResource(decode(ref.toURL()));
-            Assert.assertThat("FileResource: " + fileres,fileres.exists(),is(false));
-        } catch(URISyntaxException e) {
-            // Valid path
-        }
     }
 
     @Ignore("Validation shouldn't be done in FileResource")
     @Test
     public void testExist_BadNullX() throws Exception
     {
-        createDummyFile("a.jsp");
-
-        try {
-            // request with null and x at end
-            URI ref = testdir.getDir().toURI().resolve("a.jsp%00x");
-            FileResource fileres = new FileResource(decode(ref.toURL()));
-            Assert.assertThat("FileResource: " + fileres,fileres.exists(),is(false));
-        } catch(URISyntaxException e) {
-            // Valid path
-        }
     }
 }
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/resource/PathResourceTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/resource/PathResourceTest.java
new file mode 100644
index 0000000..7027cae
--- /dev/null
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/resource/PathResourceTest.java
@@ -0,0 +1,38 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.resource;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+
+public class PathResourceTest extends AbstractFSResourceTest
+{
+    @Override
+    public Resource newResource(URI uri) throws IOException
+    {
+        return new PathResource(uri);
+    }
+
+    @Override
+    public Resource newResource(File file) throws IOException
+    {
+        return new PathResource(file);
+    }
+}
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/resource/ResourceAliasTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/resource/ResourceAliasTest.java
new file mode 100644
index 0000000..733fd1c
--- /dev/null
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/resource/ResourceAliasTest.java
@@ -0,0 +1,109 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.resource;
+
+import java.io.File;
+import java.net.MalformedURLException;
+
+import org.eclipse.jetty.toolchain.test.FS;
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class ResourceAliasTest
+{
+    static File __dir;
+    
+    @BeforeClass
+    public static void beforeClass()
+    {
+        __dir=MavenTestingUtils.getTargetTestingDir("RAT");
+    }
+    
+    @Before
+    public void before()
+    {
+        FS.ensureDirExists(__dir);
+        FS.ensureEmpty(__dir);
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    @Test
+    public void testNullCharEndingFilename() throws Exception
+    {
+        File file=new File(__dir,"test.txt");
+        Assert.assertFalse(file.exists());
+        Assert.assertTrue(file.createNewFile());
+        Assert.assertTrue(file.exists());
+
+        File file0=new File(__dir,"test.txt\0");
+        if (!file0.exists())
+            return;  // this file system does not suffer this problem
+        
+        Assert.assertTrue(file0.exists()); // This is an alias!
+
+        Resource dir = Resource.newResource(__dir); 
+        
+        // Test not alias paths
+        Resource resource = Resource.newResource(file);
+        Assert.assertTrue(resource.exists());
+        Assert.assertNull(resource.getAlias());
+        resource = Resource.newResource(file.getAbsoluteFile());
+        Assert.assertTrue(resource.exists());
+        Assert.assertNull(resource.getAlias());
+        resource = Resource.newResource(file.toURI());
+        Assert.assertTrue(resource.exists());
+        Assert.assertNull(resource.getAlias());
+        resource = Resource.newResource(file.toURI().toString());
+        Assert.assertTrue(resource.exists());
+        Assert.assertNull(resource.getAlias());
+        resource = dir.addPath("test.txt");
+        Assert.assertTrue(resource.exists());
+        Assert.assertNull(resource.getAlias());
+        
+        
+        // Test alias paths
+        resource = Resource.newResource(file0);
+        Assert.assertTrue(resource.exists());
+        Assert.assertNotNull(resource.getAlias());
+        resource = Resource.newResource(file0.getAbsoluteFile());
+        Assert.assertTrue(resource.exists());
+        Assert.assertNotNull(resource.getAlias());
+        resource = Resource.newResource(file0.toURI());
+        Assert.assertTrue(resource.exists());
+        Assert.assertNotNull(resource.getAlias());
+        resource = Resource.newResource(file0.toURI().toString());
+        Assert.assertTrue(resource.exists());
+        Assert.assertNotNull(resource.getAlias());
+        
+        try
+        {
+            resource = dir.addPath("test.txt\0");
+            Assert.assertTrue(resource.exists());
+            Assert.assertNotNull(resource.getAlias());
+        }
+        catch(MalformedURLException e)
+        {
+            Assert.assertTrue(true);
+        }
+    }
+}
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/resource/ResourceCollectionTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/resource/ResourceCollectionTest.java
index fabe878..205f842 100644
--- a/jetty-util/src/test/java/org/eclipse/jetty/util/resource/ResourceCollectionTest.java
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/resource/ResourceCollectionTest.java
@@ -30,7 +30,7 @@ import org.junit.Test;
 
 public class ResourceCollectionTest
 {
-    
+
     @Test
     public void testMutlipleSources1() throws Exception
     {
@@ -41,9 +41,9 @@ public class ResourceCollectionTest
         });
         assertEquals("1 - one", getContent(rc1, "1.txt"));
         assertEquals("2 - two", getContent(rc1, "2.txt"));
-        assertEquals("3 - three", getContent(rc1, "3.txt"));        
-        
-        
+        assertEquals("3 - three", getContent(rc1, "3.txt"));
+
+
         ResourceCollection rc2 = new ResourceCollection(
                 "src/test/resources/org/eclipse/jetty/util/resource/one/," +
                 "src/test/resources/org/eclipse/jetty/util/resource/two/," +
@@ -52,9 +52,9 @@ public class ResourceCollectionTest
         assertEquals("1 - one", getContent(rc2, "1.txt"));
         assertEquals("2 - two", getContent(rc2, "2.txt"));
         assertEquals("3 - three", getContent(rc2, "3.txt"));
-             
+
     }
-    
+
     @Test
     public void testMergedDir() throws Exception
     {
@@ -63,15 +63,15 @@ public class ResourceCollectionTest
                 "src/test/resources/org/eclipse/jetty/util/resource/two/",
                 "src/test/resources/org/eclipse/jetty/util/resource/three/"
         });
-        
+
         Resource r = rc.addPath("dir");
         assertTrue(r instanceof ResourceCollection);
         rc=(ResourceCollection)r;
         assertEquals("1 - one", getContent(rc, "1.txt"));
         assertEquals("2 - two", getContent(rc, "2.txt"));
-        assertEquals("3 - three", getContent(rc, "3.txt"));  
+        assertEquals("3 - three", getContent(rc, "3.txt"));
     }
-    
+
     @Test
     public void testCopyTo() throws Exception
     {
@@ -80,35 +80,36 @@ public class ResourceCollectionTest
                 "src/test/resources/org/eclipse/jetty/util/resource/two/",
                 "src/test/resources/org/eclipse/jetty/util/resource/three/"
         });
-        
+
         File dest = File.createTempFile("copyto",null);
         if (dest.exists())
             dest.delete();
         dest.mkdir();
         dest.deleteOnExit();
         rc.copyTo(dest);
-        
+
         Resource r = Resource.newResource(dest.toURI());
         assertEquals("1 - one", getContent(r, "1.txt"));
         assertEquals("2 - two", getContent(r, "2.txt"));
-        assertEquals("3 - three", getContent(r, "3.txt"));  
+        assertEquals("3 - three", getContent(r, "3.txt"));
         r = r.addPath("dir");
         assertEquals("1 - one", getContent(r, "1.txt"));
         assertEquals("2 - two", getContent(r, "2.txt"));
-        assertEquals("3 - three", getContent(r, "3.txt")); 
-        
+        assertEquals("3 - three", getContent(r, "3.txt"));
+
         IO.delete(dest);
     }
-    
+
     static String getContent(Resource r, String path) throws Exception
     {
         StringBuilder buffer = new StringBuilder();
         String line = null;
-        BufferedReader br = new BufferedReader(new InputStreamReader(r.addPath(path).getURL().openStream()));
-        while((line=br.readLine())!=null)
-            buffer.append(line);
-        br.close();        
+        try (BufferedReader br = new BufferedReader(new InputStreamReader(r.addPath(path).getURL().openStream())))
+        {
+            while((line=br.readLine())!=null)
+                buffer.append(line);
+        }
         return buffer.toString();
     }
-    
+
 }
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/resource/ResourceTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/resource/ResourceTest.java
index ed7413f..121e314 100644
--- a/jetty-util/src/test/java/org/eclipse/jetty/util/resource/ResourceTest.java
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/resource/ResourceTest.java
@@ -29,20 +29,17 @@ import java.io.FilenameFilter;
 import java.io.InputStream;
 import java.net.URI;
 import java.net.URL;
-import java.sql.Time;
 import java.util.Arrays;
-import java.util.Date;
+import java.util.Collection;
 import java.util.HashSet;
 import java.util.Set;
-import java.util.TimeZone;
-import java.util.jar.JarFile;
 import java.util.zip.ZipFile;
 
-import junit.framework.Assert;
-
 import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
 import org.eclipse.jetty.toolchain.test.OS;
 import org.eclipse.jetty.util.IO;
+import org.hamcrest.Matchers;
+import org.junit.Assert;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
@@ -56,7 +53,7 @@ public class ResourceTest
 
     private static final boolean DIR=true;
     private static final boolean EXISTS=true;
-    
+
     static class Data
     {
         Resource resource;
@@ -64,7 +61,7 @@ public class ResourceTest
         boolean exists;
         boolean dir;
         String content;
-        
+
         Data(Data data,String path,boolean exists, boolean dir)
             throws Exception
         {
@@ -73,7 +70,7 @@ public class ResourceTest
             this.exists=exists;
             this.dir=dir;
         }
-        
+
         Data(Data data,String path,boolean exists, boolean dir, String content)
             throws Exception
         {
@@ -83,7 +80,7 @@ public class ResourceTest
             this.dir=dir;
             this.content=content;
         }
-        
+
         Data(URL url,boolean exists, boolean dir)
             throws Exception
         {
@@ -92,7 +89,7 @@ public class ResourceTest
             this.dir=dir;
             resource=Resource.newResource(url);
         }
-        
+
         Data(String url,boolean exists, boolean dir)
             throws Exception
         {
@@ -101,7 +98,7 @@ public class ResourceTest
             this.dir=dir;
             resource=Resource.newResource(url);
         }
-        
+
         Data(String url,boolean exists, boolean dir, String content)
             throws Exception
         {
@@ -129,9 +126,9 @@ public class ResourceTest
         __userURL=uri.toURL();
         
         __userURL = MavenTestingUtils.getTestResourcesDir().toURI().toURL();
-	FilePermission perm = (FilePermission) __userURL.openConnection().getPermission();
-	__userDir = new File(perm.getName()).getCanonicalPath() + File.separatorChar;
-	__relDir = "src/test/resources/".replace('/', File.separatorChar);  
+        FilePermission perm = (FilePermission) __userURL.openConnection().getPermission();
+        __userDir = new File(perm.getName()).getCanonicalPath() + File.separatorChar;
+        __relDir = "src/test/resources/".replace('/', File.separatorChar);  
         
         //System.err.println("User Dir="+__userDir);
         //System.err.println("Rel  Dir="+__relDir);
@@ -139,33 +136,33 @@ public class ResourceTest
 
         tmpFile=File.createTempFile("test",null).getCanonicalFile();
         tmpFile.deleteOnExit();
-        
+
         data = new Data[50];
         int i=0;
 
         data[i++]=new Data(tmpFile.toString(),EXISTS,!DIR);
-        
+
         int rt=i;
         data[i++]=new Data(__userURL,EXISTS,DIR);
         data[i++]=new Data(__userDir,EXISTS,DIR);
         data[i++]=new Data(__relDir,EXISTS,DIR);
-        data[i++]=new Data(__userURL+"jetty-logging.properties",EXISTS,!DIR);
-        data[i++]=new Data(__userDir+"jetty-logging.properties",EXISTS,!DIR);
-        data[i++]=new Data(__relDir+"jetty-logging.properties",EXISTS,!DIR);
+        data[i++]=new Data(__userURL+"resource.txt",EXISTS,!DIR);
+        data[i++]=new Data(__userDir+"resource.txt",EXISTS,!DIR);
+        data[i++]=new Data(__relDir+"resource.txt",EXISTS,!DIR);
         data[i++]=new Data(__userURL+"NoName.txt",!EXISTS,!DIR);
         data[i++]=new Data(__userDir+"NoName.txt",!EXISTS,!DIR);
         data[i++]=new Data(__relDir+"NoName.txt",!EXISTS,!DIR);
 
-        data[i++]=new Data(data[rt],"jetty-logging.properties",EXISTS,!DIR);
-        data[i++]=new Data(data[rt],"/jetty-logging.properties",EXISTS,!DIR);
+        data[i++]=new Data(data[rt],"resource.txt",EXISTS,!DIR);
+        data[i++]=new Data(data[rt],"/resource.txt",EXISTS,!DIR);
         data[i++]=new Data(data[rt],"NoName.txt",!EXISTS,!DIR);
         data[i++]=new Data(data[rt],"/NoName.txt",!EXISTS,!DIR);
-        
+
         int td=i;
         data[i++]=new Data(data[rt],"TestData",EXISTS,DIR);
         data[i++]=new Data(data[rt],"TestData/",EXISTS,DIR);
         data[i++]=new Data(data[td],"alphabet.txt",EXISTS,!DIR,"ABCDEFGHIJKLMNOPQRSTUVWXYZ");
-        
+
         data[i++]=new Data("jar:file:/somejar.jar!/content/",!EXISTS,DIR);
         data[i++]=new Data("jar:file:/somejar.jar!/",!EXISTS,DIR);
 
@@ -173,14 +170,14 @@ public class ResourceTest
         data[i++]=new Data("jar:"+__userURL+"TestData/test.zip!/",EXISTS,DIR);
         data[i++]=new Data(data[tj],"Unkown",!EXISTS,!DIR);
         data[i++]=new Data(data[tj],"/Unkown/",!EXISTS,DIR);
-        
+
         data[i++]=new Data(data[tj],"subdir",EXISTS,DIR);
         data[i++]=new Data(data[tj],"/subdir/",EXISTS,DIR);
         data[i++]=new Data(data[tj],"alphabet",EXISTS,!DIR,
                            "ABCDEFGHIJKLMNOPQRSTUVWXYZ");
         data[i++]=new Data(data[tj],"/subdir/alphabet",EXISTS,!DIR,
                            "ABCDEFGHIJKLMNOPQRSTUVWXYZ");
-        
+
         Resource base = Resource.newResource(__userDir);
         Resource dir0 = base.addPath("TestData");
         assertTrue(dir0.isDirectory());
@@ -190,8 +187,8 @@ public class ResourceTest
         assertTrue(dir1.isDirectory());
         assertTrue(dir1.toString().endsWith("/"));
         assertTrue(dir1.getAlias()==null);
-        
-        
+
+
     }
 
     /* ------------------------------------------------------------ */
@@ -206,7 +203,7 @@ public class ResourceTest
             assertEquals(""+i+":"+data[i].test,data[i].exists,data[i].resource.exists());
         }
     }
-    
+
     /* ------------------------------------------------------------ */
     @Test
     public void testResourceDir()
@@ -219,7 +216,7 @@ public class ResourceTest
             assertEquals(""+i+":"+data[i].test,data[i].dir,data[i].resource.isDirectory());
         }
     }
-    
+
     /* ------------------------------------------------------------ */
     @Test
     public void testResourceContent()
@@ -229,7 +226,7 @@ public class ResourceTest
         {
             if (data[i]==null || data[i].content==null)
                 continue;
-          
+
             InputStream in = data[i].resource.getInputStream();
             String c=IO.toString(in);
             assertTrue(""+i+":"+data[i].test,c.startsWith(data[i].content));
@@ -254,8 +251,8 @@ public class ResourceTest
     {
         String s = "jar:"+__userURL+"TestData/test.zip!/subdir/";
         Resource r = Resource.newResource(s);
-        
-        Set entries = new HashSet(Arrays.asList(r.list()));
+
+        Set<String> entries = new HashSet<>(Arrays.asList(r.list()));
         assertEquals(3,entries.size());
         assertTrue(entries.contains("alphabet"));
         assertTrue(entries.contains("numbers"));
@@ -268,10 +265,10 @@ public class ResourceTest
         extract.deleteOnExit();
 
         r.copyTo(extract);
-        
+
         Resource e = Resource.newResource(extract.getAbsolutePath());
-        
-        entries = new HashSet(Arrays.asList(e.list()));
+
+        entries = new HashSet<>(Arrays.asList(e.list()));
         assertEquals(3,entries.size());
         assertTrue(entries.contains("alphabet"));
         assertTrue(entries.contains("numbers"));
@@ -280,8 +277,8 @@ public class ResourceTest
 
         s = "jar:"+__userURL+"TestData/test.zip!/subdir/subsubdir/";
         r = Resource.newResource(s);
-        
-        entries = new HashSet(Arrays.asList(r.list()));
+
+        entries = new HashSet<>(Arrays.asList(r.list()));
         assertEquals(2,entries.size());
         assertTrue(entries.contains("alphabet"));
         assertTrue(entries.contains("numbers"));
@@ -293,17 +290,27 @@ public class ResourceTest
         extract.deleteOnExit();
 
         r.copyTo(extract);
-        
+
         e = Resource.newResource(extract.getAbsolutePath());
-        
-        entries = new HashSet(Arrays.asList(e.list()));
+
+        entries = new HashSet<>(Arrays.asList(e.list()));
         assertEquals(2,entries.size());
         assertTrue(entries.contains("alphabet"));
         assertTrue(entries.contains("numbers"));
         IO.delete(extract);
+
+    }
+
+    /* ------------------------------------------------------------ */
+    @Test
+    public void testJarFileGetAllResoures()
+    throws Exception
+    {
+        String s = "jar:"+__userURL+"TestData/test.zip!/subdir/";
+        Resource r = Resource.newResource(s);
+        Collection<Resource> deep=r.getAllResources();
         
-        
-        
+        assertEquals(4, deep.size());
     }
     
     @Test
@@ -313,12 +320,12 @@ public class ResourceTest
         String s = "jar:"+__userURL+"TestData/test.zip!/subdir/";
         Resource r = Resource.newResource(s);
         Resource container = Resource.newResource(__userURL+"TestData/test.zip");
-        
+
         assertTrue(r instanceof JarFileResource);
         JarFileResource jarFileResource = (JarFileResource)r;
-        
+
         assertTrue(jarFileResource.isContainedIn(container));
-        
+
         container = Resource.newResource(__userURL+"TestData");
         assertFalse(jarFileResource.isContainedIn(container));
     }
@@ -329,15 +336,31 @@ public class ResourceTest
     throws Exception
     {
         String s = "jar:"+__userURL+"TestData/test.zip!/subdir/numbers";
+
+        try(ZipFile zf = new ZipFile(MavenTestingUtils.getTestResourceFile("TestData/test.zip")))
+        {
+            long last = zf.getEntry("subdir/numbers").getTime();
+
+            Resource r = Resource.newResource(s);
+            assertEquals(last,r.lastModified());
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Test
+    public void testEncodeAddPath ()
+    throws Exception
+    {
+        Resource r;
+
+        r = Resource.newResource(__userURL+"TestData/").addPath("foo%/b r");
+        Assert.assertThat(r.getURI().toString(),Matchers.endsWith("/foo%25/b%20r"));
         
-        ZipFile zf = new ZipFile(MavenTestingUtils.getTestResourceFile("TestData/test.zip"));
-        
-        long last = zf.getEntry("subdir/numbers").getTime();
-        
-        Resource r = Resource.newResource(s);
-        assertEquals(last,r.lastModified()); // Known date value inside zip
+        r = Resource.newResource("jar:"+__userURL+"TestData/test.zip!/subdir/").addPath("foo%/b r");
+        Assert.assertThat(r.getURI().toString(),Matchers.endsWith("/foo%25/b%20r"));
     }
     
+    
     /* ------------------------------------------------------------ */
     @Test
     public void testJarFileCopyToDirectoryTraversal () throws Exception
@@ -353,7 +376,7 @@ public class ResourceTest
             destParent.delete();
         destParent.mkdir();
         destParent.deleteOnExit();
-        
+
         File dest = new File(destParent.getCanonicalPath()+"/extract");
         if(dest.exists())
             dest.delete();
@@ -367,15 +390,17 @@ public class ResourceTest
         assertEquals(1, dest.getParentFile().listFiles().length);
 
         FilenameFilter dotdotFilenameFilter = new FilenameFilter() {
+            @Override
             public boolean accept(File directory, String name)
             {
                 return name.equals("dotdot.txt");
             }
-        };        
+        };
         assertEquals(0, dest.listFiles(dotdotFilenameFilter).length);
         assertEquals(0, dest.getParentFile().listFiles(dotdotFilenameFilter).length);
 
         FilenameFilter extractfileFilenameFilter = new FilenameFilter() {
+            @Override
             public boolean accept(File directory, String name)
             {
                 return name.equals("extract-filenotdir");
@@ -385,6 +410,7 @@ public class ResourceTest
         assertEquals(0, dest.getParentFile().listFiles(extractfileFilenameFilter).length);
 
         FilenameFilter currentDirectoryFilenameFilter = new FilenameFilter() {
+            @Override
             public boolean accept(File directory, String name)
             {
                 return name.equals("current.txt");
@@ -392,11 +418,11 @@ public class ResourceTest
         };
         assertEquals(1, dest.listFiles(currentDirectoryFilenameFilter).length);
         assertEquals(0, dest.getParentFile().listFiles(currentDirectoryFilenameFilter).length);
-        
+
         IO.delete(dest);
         assertFalse(dest.exists());
     }
-    
+
     /**
      * Test a class path resource for existence.
      */
@@ -405,15 +431,14 @@ public class ResourceTest
     {
         final String classPathName="Resource.class";
 
-        Resource resource=Resource.newClassPathResource(classPathName);
-
-        assertTrue(resource!=null);
-
-        // A class path cannot be a directory
-        assertFalse("Class path cannot be a directory.",resource.isDirectory());
+        try(Resource resource=Resource.newClassPathResource(classPathName);)
+        {
+            // A class path cannot be a directory
+            assertFalse("Class path cannot be a directory.",resource.isDirectory());
 
-        // A class path must exist
-        assertTrue("Class path resource does not exist.",resource.exists());
+            // A class path must exist
+            assertTrue("Class path resource does not exist.",resource.exists());
+        }
     }
 
     /**
@@ -426,8 +451,6 @@ public class ResourceTest
 
         Resource resource=Resource.newClassPathResource(classPathName);
 
-        assertTrue(resource!=null);
-
         // A class path cannot be a directory
         assertFalse("Class path cannot be a directory.",resource.isDirectory());
 
@@ -445,9 +468,6 @@ public class ResourceTest
 
         Resource resource=Resource.newClassPathResource(classPathName);
 
-        
-        assertTrue(resource!=null);
-        
         // A class path must be a directory
         assertTrue("Class path must be a directory.",resource.isDirectory());
 
@@ -469,20 +489,42 @@ public class ResourceTest
         // Will locate a resource in the class path
         Resource resource=Resource.newClassPathResource(classPathName);
 
-        assertTrue(resource!=null);
-        
         // A class path cannot be a directory
         assertFalse("Class path must be a directory.",resource.isDirectory());
 
         assertTrue(resource!=null);
-        
+
         File file=resource.getFile();
 
-        assertTrue("File returned from class path should not be null.",file!=null);
         assertEquals("File name from class path is not equal.",fileName,file.getName());
         assertTrue("File returned from class path should be a file.",file.isFile());
 
         // A class path must exist
         assertTrue("Class path resource does not exist.",resource.exists());
     }
+
+    @Test
+    public void testUncPathResourceFile() throws Exception
+    {
+        // This test is intended to run only on Windows platform
+        assumeTrue(OS.IS_WINDOWS);
+
+        String path = __userURL.toURI().getPath().replace('/','\\')+"resource.txt";
+        //System.err.println(path);
+
+        Resource resource = Resource.newResource(path, false);
+        //System.err.println(resource);
+        assertTrue(resource.exists());
+
+        /*
+
+        String uncPath = "\\\\127.0.0.1"+__userURL.toURI().getPath().replace('/','\\').replace(':','$')+"ResourceTest.java";
+        System.err.println(uncPath);
+
+        Resource uncResource = Resource.newResource(uncPath, false);
+        System.err.println(uncResource);
+        assertTrue(uncResource.exists());
+
+        */
+    }
 }
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/security/PasswordTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/security/PasswordTest.java
new file mode 100644
index 0000000..1bd3996
--- /dev/null
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/security/PasswordTest.java
@@ -0,0 +1,52 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.security;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+public class PasswordTest
+{
+    @Test
+    public void testDeobfuscate()
+    {
+        // check any changes do not break already encoded strings
+        String password = "secret password !# ";
+        String obfuscate = "OBF:1iaa1g3l1fb51i351sw01ym91hdc1yt41v1p1ym71v2p1yti1hhq1ym51svy1hyl1f7h1fzx1i5o";
+        assertEquals(password,Password.deobfuscate(obfuscate));
+    }
+
+    @Test
+    public void testObfuscate()
+    {
+        String password = "secret password !# ";
+        String obfuscate = Password.obfuscate(password);
+        assertEquals(password,Password.deobfuscate(obfuscate));
+    }
+    
+    @Test
+    public void testObfuscateUnicode()
+    {
+        String password = "secret password !#\u20ac ";
+        String obfuscate = Password.obfuscate(password);
+        assertEquals(password,Password.deobfuscate(obfuscate));
+    }
+
+}
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/ssl/SslContextFactoryTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/ssl/SslContextFactoryTest.java
index 1beb0c8..a59a4b3 100644
--- a/jetty-util/src/test/java/org/eclipse/jetty/util/ssl/SslContextFactoryTest.java
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/ssl/SslContextFactoryTest.java
@@ -18,10 +18,19 @@
 
 package org.eclipse.jetty.util.ssl;
 
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.greaterThan;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
 import java.io.IOException;
 import java.io.InputStream;
 import java.security.KeyStore;
 
+import javax.net.ssl.SSLEngine;
+
 import org.eclipse.jetty.util.component.AbstractLifeCycle;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.StdErrLog;
@@ -30,11 +39,6 @@ import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 
-import static junit.framework.Assert.assertTrue;
-import static org.hamcrest.Matchers.equalTo;
-import static org.hamcrest.Matchers.is;
-import static org.junit.Assert.assertThat;
-
 
 public class SslContextFactoryTest
 {
@@ -53,49 +57,35 @@ public class SslContextFactoryTest
         String keystorePath = System.getProperty("basedir",".") + "/src/test/resources/keystore";
         cf.setKeyStorePassword("storepwd");
         cf.setKeyManagerPassword("keypwd");
-        
-        cf.start();
-        
-        assertTrue(cf.getSslContext()!=null);
-    }
-    
-    @Test
-    public void testNoTsStreamKs() throws Exception
-    {
-        InputStream keystoreInputStream = this.getClass().getResourceAsStream("keystore");
 
-        cf.setKeyStoreInputStream(keystoreInputStream);
-        cf.setKeyStorePassword("storepwd");
-        cf.setKeyManagerPassword("keypwd");
-        
         cf.start();
-        
+
         assertTrue(cf.getSslContext()!=null);
     }
-    
+
     @Test
     public void testNoTsSetKs() throws Exception
     {
-        InputStream keystoreInputStream = this.getClass().getResourceAsStream("keystore");
-
         KeyStore ks = KeyStore.getInstance("JKS");
-        ks.load(keystoreInputStream, "storepwd".toCharArray());
-
+        try (InputStream keystoreInputStream = this.getClass().getResourceAsStream("keystore"))
+        {
+            ks.load(keystoreInputStream, "storepwd".toCharArray());
+        }
         cf.setKeyStore(ks);
         cf.setKeyManagerPassword("keypwd");
-        
+
         cf.start();
-        
+
         assertTrue(cf.getSslContext()!=null);
     }
-    
+
     @Test
     public void testNoTsNoKs() throws Exception
     {
         cf.start();
         assertTrue(cf.getSslContext()!=null);
     }
-    
+
     @Test
     public void testTrustAll() throws Exception
     {
@@ -137,6 +127,7 @@ public class SslContextFactoryTest
     @Test
     public void testResourceTsResourceKsWrongPW() throws Exception
     {
+        SslContextFactory.LOG.info("EXPECT SslContextFactory@????????(null,null): java.security.UnrecoverableKeyException: Cannot recover key...");
         Resource keystoreResource = Resource.newSystemResource("keystore");
         Resource truststoreResource = Resource.newSystemResource("keystore");
 
@@ -160,6 +151,7 @@ public class SslContextFactoryTest
     @Test
     public void testResourceTsWrongPWResourceKs() throws Exception
     {
+        SslContextFactory.LOG.info("EXPECT SslContextFactory@????????(null,null): java.io.IOException: Keystore was tampered with ...");
         Resource keystoreResource = Resource.newSystemResource("keystore");
         Resource truststoreResource = Resource.newSystemResource("keystore");
 
@@ -179,20 +171,21 @@ public class SslContextFactoryTest
         {
         }
     }
-    
+
     @Test
     public void testNoKeyConfig() throws Exception
     {
         try
         {
+            SslContextFactory.LOG.info("EXPECT SslContextFactory@????????(null,/foo): java.lang.IllegalStateException: SSL doesn't have a valid keystore...");
             ((StdErrLog)Log.getLogger(AbstractLifeCycle.class)).setHideStacks(true);
-            cf.setTrustStore("/foo");
+            cf.setTrustStorePath("/foo");
             cf.start();
             Assert.fail();
         }
         catch (IllegalStateException e)
         {
-            
+
         }
         catch (Exception e)
         {
@@ -201,6 +194,30 @@ public class SslContextFactoryTest
     }
 
     @Test
+    public void testSetExcludeCipherSuitesRegex() throws Exception
+    {
+        cf.setExcludeCipherSuites(".*RC4.*");
+        cf.start();
+        SSLEngine sslEngine = cf.newSSLEngine();
+        String[] enabledCipherSuites = sslEngine.getEnabledCipherSuites();
+        assertThat("At least 1 cipherSuite is enabled", enabledCipherSuites.length, greaterThan(0));
+        for (String enabledCipherSuite : enabledCipherSuites)
+            assertThat("CipherSuite does not contain RC4", enabledCipherSuite.contains("RC4"), is(false));
+    }
+
+    @Test
+    public void testSetIncludeCipherSuitesRegex() throws Exception
+    {
+        cf.setIncludeCipherSuites(".*RC4.*");
+        cf.start();
+        SSLEngine sslEngine = cf.newSSLEngine();
+        String[] enabledCipherSuites = sslEngine.getEnabledCipherSuites();
+        assertThat("At least 1 cipherSuite is enabled", enabledCipherSuites.length, greaterThan(0));
+        for (String enabledCipherSuite : enabledCipherSuites)
+            assertThat("CipherSuite contains RC4", enabledCipherSuite.contains("RC4"), is(true));
+    }
+
+    @Test
     public void testSetIncludeCipherSuitesPreservesOrder()
     {
         String[] supportedCipherSuites = new String[]{"cipher4", "cipher2", "cipher1", "cipher3"};
@@ -224,6 +241,15 @@ public class SslContextFactoryTest
         assertSelectedMatchesIncluded(includeProtocol, selectedProtocol);
     }
 
+    @Test
+    public void testProtocolAndCipherSettingsAreNPESafe()
+    {
+    	assertNotNull(cf.getExcludeProtocols());
+    	assertNotNull(cf.getIncludeProtocols());
+    	assertNotNull(cf.getExcludeCipherSuites());
+    	assertNotNull(cf.getIncludeCipherSuites());
+    }
+    
     private void assertSelectedMatchesIncluded(String[] includeStrings, String[] selectedStrings)
     {
         assertThat(includeStrings.length + " strings are selected", selectedStrings.length, is(includeStrings.length));
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/statistic/SampleStatisticTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/statistic/SampleStatisticTest.java
index b996b7d..b8ce3cb 100644
--- a/jetty-util/src/test/java/org/eclipse/jetty/util/statistic/SampleStatisticTest.java
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/statistic/SampleStatisticTest.java
@@ -19,22 +19,23 @@
 package org.eclipse.jetty.util.statistic;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
 
+import org.hamcrest.Matchers;
+import org.junit.Assert;
 import org.junit.Test;
 
 
 /* ------------------------------------------------------------ */
 public class SampleStatisticTest
 {
-    private static long[][] data = 
+    private static long[][] data =
     {
         {100,100,100,100,100,100,100,100,100,100},
         {100,100,100,100,100,100,100,100,100,100,90,110},
         {100,100,100,100,100,100,100,100,90,110,95,105,97,103},
         {100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,90,110,95,105,97,103},
     };
-    
+
     private static double[][] results =
     { /* {mean,stddev}*/
         {100.0,0.0},
@@ -43,8 +44,8 @@ public class SampleStatisticTest
         {100.0,Math.sqrt((10*10+10*10+5*5+5*5+3*3+3*3)/24.0)},
         {100.0,Math.sqrt((10*10+10*10+5*5+5*5+3*3+3*3)/104.0)}
     };
-    
-    
+
+
     @Test
     public void testData()
         throws Exception
@@ -64,15 +65,8 @@ public class SampleStatisticTest
 
     private void assertNearEnough(String test,double expected, double actual)
     {
-        double diff = Math.abs(expected-actual);
-        if (diff<0.1)
-        {
-            System.out.println("Near enough "+test+" diff="+diff);
-            return;
-        }
-        String failed = "Not near enough "+test+" expected="+expected+" actual="+actual+" diff="+diff;
-        System.err.println(failed);
-        assertTrue(failed,false);
+        Assert.assertThat(actual,Matchers.greaterThan(expected-0.1D));
+        Assert.assertThat(actual,Matchers.lessThan(expected+0.1D));
     }
-    
+
 }
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/thread/QueuedThreadPoolTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/thread/QueuedThreadPoolTest.java
index 4b4e836..43d9e5b 100644
--- a/jetty-util/src/test/java/org/eclipse/jetty/util/thread/QueuedThreadPoolTest.java
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/thread/QueuedThreadPoolTest.java
@@ -18,21 +18,28 @@
 
 package org.eclipse.jetty.util.thread;
 
-import static org.junit.Assert.assertTrue;
-
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
 
-import junit.framework.Assert;
-
+import org.eclipse.jetty.toolchain.test.AdvancedRunner;
+import org.eclipse.jetty.toolchain.test.annotation.Slow;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.StdErrLog;
+import org.junit.Assert;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 
+import static org.hamcrest.Matchers.greaterThanOrEqualTo;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
 
+ at RunWith(AdvancedRunner.class)
 public class QueuedThreadPoolTest
 {
     final AtomicInteger _jobs=new AtomicInteger();
-    
+
     class RunningJob implements Runnable
     {
         private final CountDownLatch _run = new CountDownLatch(1);
@@ -40,7 +47,7 @@ public class QueuedThreadPoolTest
         private final CountDownLatch _stopped = new CountDownLatch(1);
         public void run()
         {
-            try 
+            try
             {
                 _run.countDown();
                 _stopping.await();
@@ -55,44 +62,45 @@ public class QueuedThreadPoolTest
                 _stopped.countDown();
             }
         }
-        
+
         public void stop() throws InterruptedException
         {
-            _run.await(10,TimeUnit.SECONDS);
-            _stopping.countDown();
+            if (_run.await(10,TimeUnit.SECONDS))
+                _stopping.countDown();
             if (!_stopped.await(10,TimeUnit.SECONDS))
-                throw new IllegalStateException(); 
+                throw new IllegalStateException();
         }
-    };   
-    
-    
+    };
+
+
     @Test
+    @Slow
     public void testThreadPool() throws Exception
-    {        
+    {
         QueuedThreadPool tp= new QueuedThreadPool();
         tp.setMinThreads(5);
         tp.setMaxThreads(10);
-        tp.setMaxIdleTimeMs(1000);
+        tp.setIdleTimeout(1000);
         tp.setThreadsPriority(Thread.NORM_PRIORITY-1);
 
         tp.start();
-        
+
         waitForThreads(tp,5);
         waitForIdle(tp,5);
-        
+
         Thread.sleep(1000);
         waitForThreads(tp,5);
         waitForIdle(tp,5);
-        
+
         RunningJob job=new RunningJob();
-        tp.dispatch(job);
+        tp.execute(job);
         waitForIdle(tp,4);
         waitForThreads(tp,5);
 
         job.stop();
         waitForIdle(tp,5);
         waitForThreads(tp,5);
-        
+
         Thread.sleep(200);
         waitForIdle(tp,5);
         waitForThreads(tp,5);
@@ -101,40 +109,46 @@ public class QueuedThreadPoolTest
         for (int i=0;i<jobs.length;i++)
         {
             jobs[i]=new RunningJob();
-            tp.dispatch(jobs[i]);
+            tp.execute(jobs[i]);
         }
-        waitForIdle(tp,0);
-        waitForThreads(tp,5);
-        
-        job=new RunningJob();
-        tp.dispatch(job);
+
+        waitForIdle(tp,1);
         waitForThreads(tp,6);
-        
+
+        job=new RunningJob();
+        tp.execute(job);
+        waitForIdle(tp,1);
+        waitForThreads(tp,7);
+
         job.stop();
-        waitForThreads(tp,5);
-        
+        waitForIdle(tp,2);
+        waitForThreads(tp,7);
+        waitForThreads(tp,6);
+        waitForIdle(tp,1);
+
         jobs[0].stop();
         waitForIdle(tp,1);
         waitForThreads(tp,5);
-        
+
         for (int i=1;i<jobs.length;i++)
             jobs[i].stop();
 
         waitForIdle(tp,5);
         waitForThreads(tp,5);
-        
+
         jobs = new RunningJob[15];
         for (int i=0;i<jobs.length;i++)
         {
             jobs[i]=new RunningJob();
-            tp.dispatch(jobs[i]);
+            tp.execute(jobs[i]);
         }
+
         waitForIdle(tp,0);
         waitForThreads(tp,10);
         for (int i=0;i<9;i++)
             jobs[i].stop();
         waitForThreads(tp,9);
-        
+
         for (int i=9;i<jobs.length;i++)
             jobs[i].stop();
         waitForIdle(tp,5);
@@ -142,6 +156,7 @@ public class QueuedThreadPoolTest
     }
 
     @Test
+    @Slow
     public void testShrink() throws Exception
     {
         final AtomicInteger sleep = new AtomicInteger(100);
@@ -149,7 +164,7 @@ public class QueuedThreadPoolTest
         {
             public void run()
             {
-                try 
+                try
                 {
                     Thread.sleep(sleep.get());
                 }
@@ -158,32 +173,32 @@ public class QueuedThreadPoolTest
                     e.printStackTrace();
                 }
             }
-            
+
         };
-        
+
         QueuedThreadPool tp= new QueuedThreadPool();
         tp.setMinThreads(2);
         tp.setMaxThreads(10);
-        tp.setMaxIdleTimeMs(400);
+        tp.setIdleTimeout(400);
         tp.setThreadsPriority(Thread.NORM_PRIORITY-1);
-        
+
         tp.start();
         waitForIdle(tp,2);
         waitForThreads(tp,2);
-        
+
         sleep.set(200);
-        tp.dispatch(job);
-        tp.dispatch(job);
+        tp.execute(job);
+        tp.execute(job);
         for (int i=0;i<20;i++)
-            tp.dispatch(job);
+            tp.execute(job);
 
         waitForThreads(tp,10);
         waitForIdle(tp,0);
-        
+
         sleep.set(5);
         for (int i=0;i<500;i++)
         {
-            tp.dispatch(job);
+            tp.execute(job);
             Thread.sleep(10);
         }
         waitForThreads(tp,2);
@@ -194,9 +209,9 @@ public class QueuedThreadPoolTest
     public void testMaxStopTime() throws Exception
     {
         QueuedThreadPool tp= new QueuedThreadPool();
-        tp.setMaxStopTimeMs(500);
+        tp.setStopTimeout(500);
         tp.start();
-        tp.dispatch(new Runnable(){
+        tp.execute(new Runnable(){
             public void run () {
                 while (true) {
                     try {
@@ -226,8 +241,9 @@ public class QueuedThreadPoolTest
             }
             catch(InterruptedException e)
             {}
+            now=System.currentTimeMillis();
         }
-        Assert.assertEquals(idle,tp.getIdleThreads());
+        Assert.assertEquals(idle, tp.getIdleThreads());
     }
 
     private void waitForThreads(QueuedThreadPool tp, int threads)
@@ -241,10 +257,55 @@ public class QueuedThreadPoolTest
                 Thread.sleep(10);
             }
             catch(InterruptedException e)
-            {} 
+            {}
             now=System.currentTimeMillis();
         }
-        Assert.assertEquals(threads,tp.getThreads());
+        assertEquals(threads,tp.getThreads());
     }
 
+    @Test
+    public void testException() throws Exception
+    {
+        QueuedThreadPool tp= new QueuedThreadPool();
+        tp.setMinThreads(5);
+        tp.setMaxThreads(10);
+        tp.setIdleTimeout(1000);
+        tp.start();
+        try
+        {
+            ((StdErrLog)Log.getLogger(QueuedThreadPool.class)).setHideStacks(true);
+            tp.execute(new Runnable(){ public void run () { throw new IllegalStateException(); } });
+            tp.execute(new Runnable(){ public void run () { throw new Error(); } });
+            tp.execute(new Runnable(){ public void run () { throw new RuntimeException(); } });
+            tp.execute(new Runnable(){ public void run () { throw new ThreadDeath(); } });
+            
+            Thread.sleep(100);
+            assertThat(tp.getThreads(),greaterThanOrEqualTo(5));
+        }
+        finally
+        {
+            ((StdErrLog)Log.getLogger(QueuedThreadPool.class)).setHideStacks(false);
+        }
+    }
+
+    @Test
+    public void testZeroMinThreads() throws Exception
+    {
+        int maxThreads = 10;
+        int minThreads = 0;
+        QueuedThreadPool pool = new QueuedThreadPool(maxThreads, minThreads);
+        pool.start();
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        pool.execute(new Runnable()
+        {
+            @Override
+            public void run()
+            {
+                latch.countDown();
+            }
+        });
+
+        assertTrue(latch.await(5, TimeUnit.SECONDS));
+    }
 }
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/thread/SchedulerTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/thread/SchedulerTest.java
new file mode 100644
index 0000000..6813c64
--- /dev/null
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/thread/SchedulerTest.java
@@ -0,0 +1,334 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.thread;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Random;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.eclipse.jetty.toolchain.perf.PlatformMonitor;
+import org.eclipse.jetty.toolchain.test.annotation.Slow;
+import org.eclipse.jetty.util.statistic.SampleStatistic;
+import org.hamcrest.Matchers;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+
+ at RunWith(value = Parameterized.class)
+public class SchedulerTest
+{
+    @Parameterized.Parameters
+    public static Collection<Object[]> data()
+    {
+        Object[][] data = new Object[][]{
+            {new TimerScheduler()},
+            {new ScheduledExecutorScheduler()}/*,
+            {new ConcurrentScheduler(0)},
+            {new ConcurrentScheduler(1500)},
+            {new ConcurrentScheduler(executor,1500)}*/
+        };
+        return Arrays.asList(data);
+    }
+
+    private Scheduler _scheduler;
+
+    public SchedulerTest(Scheduler scheduler)
+    {
+        _scheduler=scheduler;
+    }
+
+    @Before
+    public void before() throws Exception
+    {
+        _scheduler.start();
+    }
+
+    @After
+    public void after() throws Exception
+    {
+        _scheduler.stop();
+    }
+
+    @Test
+    public void testExecution() throws Exception
+    {
+        final AtomicLong executed = new AtomicLong();
+        long expected=System.currentTimeMillis()+1000;
+        Scheduler.Task task=_scheduler.schedule(new Runnable()
+        {
+            @Override
+            public void run()
+            {
+                executed.set(System.currentTimeMillis());
+            }
+        },1000,TimeUnit.MILLISECONDS);
+
+        Thread.sleep(1500);
+        Assert.assertFalse(task.cancel());
+        Assert.assertThat(executed.get(),Matchers.greaterThanOrEqualTo(expected));
+        Assert.assertThat(expected-executed.get(),Matchers.lessThan(1000L));
+    }
+
+    @Test
+    public void testTwoExecution() throws Exception
+    {
+        final AtomicLong executed = new AtomicLong();
+        long expected=System.currentTimeMillis()+1000;
+        Scheduler.Task task=_scheduler.schedule(new Runnable()
+        {
+            @Override
+            public void run()
+            {
+                executed.set(System.currentTimeMillis());
+            }
+        },1000,TimeUnit.MILLISECONDS);
+
+        Thread.sleep(1500);
+        Assert.assertFalse(task.cancel());
+        Assert.assertThat(executed.get(),Matchers.greaterThanOrEqualTo(expected));
+        Assert.assertThat(expected-executed.get(),Matchers.lessThan(1000L));
+
+        final AtomicLong executed1 = new AtomicLong();
+        long expected1=System.currentTimeMillis()+1000;
+        Scheduler.Task task1=_scheduler.schedule(new Runnable()
+        {
+            @Override
+            public void run()
+            {
+                executed1.set(System.currentTimeMillis());
+            }
+        },1000,TimeUnit.MILLISECONDS);
+
+        Thread.sleep(1500);
+        Assert.assertFalse(task1.cancel());
+        Assert.assertThat(executed1.get(),Matchers.greaterThanOrEqualTo(expected1));
+        Assert.assertThat(expected1-executed1.get(),Matchers.lessThan(1000L));
+    }
+
+    @Test
+    public void testQuickCancel() throws Exception
+    {
+        final AtomicLong executed = new AtomicLong();
+        Scheduler.Task task=_scheduler.schedule(new Runnable()
+        {
+            @Override
+            public void run()
+            {
+                executed.set(System.currentTimeMillis());
+            }
+        },2000,TimeUnit.MILLISECONDS);
+
+        Thread.sleep(100);
+        Assert.assertTrue(task.cancel());
+        Thread.sleep(2500);
+        Assert.assertEquals(0,executed.get());
+    }
+
+    @Test
+    public void testLongCancel() throws Exception
+    {
+        final AtomicLong executed = new AtomicLong();
+        Scheduler.Task task=_scheduler.schedule(new Runnable()
+        {
+            @Override
+            public void run()
+            {
+                executed.set(System.currentTimeMillis());
+            }
+        },2000,TimeUnit.MILLISECONDS);
+
+        Thread.sleep(1600);
+        Assert.assertTrue(task.cancel());
+        Thread.sleep(1000);
+        Assert.assertEquals(0,executed.get());
+    }
+
+    @Test
+    public void testTaskThrowsException() throws Exception
+    {
+        long delay = 500;
+        _scheduler.schedule(new Runnable()
+        {
+            @Override
+            public void run()
+            {
+                throw new RuntimeException();
+            }
+        }, delay, TimeUnit.MILLISECONDS);
+
+        TimeUnit.MILLISECONDS.sleep(2 * delay);
+
+        // Check whether after a task throwing an exception, the scheduler is still working
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        _scheduler.schedule(new Runnable()
+        {
+            @Override
+            public void run()
+            {
+                latch.countDown();
+            }
+        }, delay, TimeUnit.MILLISECONDS);
+
+        Assert.assertTrue(latch.await(2 * delay, TimeUnit.MILLISECONDS));
+    }
+
+    @Test
+    @Slow
+    @Ignore
+    public void testManySchedulesAndCancels() throws Exception
+    {
+        schedule(100,5000,3800,200);
+    }
+    
+    @Test
+    public void testFewSchedulesAndCancels() throws Exception
+    {
+        schedule(10,500,380,20);
+    }
+
+    @Test
+    @Slow
+    @Ignore
+    public void testBenchmark() throws Exception
+    {
+        schedule(2000,10000,2000,50);
+        PlatformMonitor benchmark = new PlatformMonitor();
+        PlatformMonitor.Start start = benchmark.start();
+        System.err.println(start);
+        System.err.println(_scheduler);
+        schedule(2000,30000,2000,50);
+        PlatformMonitor.Stop stop = benchmark.stop();
+        System.err.println(stop);
+    }
+
+    private void schedule(int threads,final int duration, final int delay, final int interval) throws Exception
+    {
+        Thread[] test = new Thread[threads];
+
+        final AtomicInteger schedules = new AtomicInteger();
+        final SampleStatistic executions = new SampleStatistic();
+        final SampleStatistic cancellations = new SampleStatistic();
+
+        for (int i=test.length;i-->0;)
+        {
+            test[i]=new Thread()
+            {
+                @Override
+                public void run()
+                {
+                    try
+                    {
+                        Random random = new Random();
+                        long now = System.currentTimeMillis();
+                        long start=now;
+                        long end=start+duration;
+                        boolean last=false;
+                        while (!last)
+                        {
+                            final long expected=now+delay;
+                            int cancel=random.nextInt(interval);
+                            final boolean expected_to_execute;
+
+                            last=now+2*interval>end;
+                            if (cancel==0 || last)
+                            {
+                                expected_to_execute=true;
+                                cancel=delay+1000;
+                            }
+                            else
+                                expected_to_execute=false;
+
+                            schedules.incrementAndGet();
+                            Scheduler.Task task=_scheduler.schedule(new Runnable()
+                            {
+                                @Override
+                                public void run()
+                                {
+                                    long lateness=System.currentTimeMillis()-expected;
+                                    if (expected_to_execute)
+                                        executions.set(lateness);
+                                    else
+                                        executions.set(6666);
+
+                                }
+                            },delay,TimeUnit.MILLISECONDS);
+
+                            Thread.sleep(cancel);
+                            now = System.currentTimeMillis();
+                            if (task.cancel())
+                            {
+                                long lateness=now-expected;
+                                if (expected_to_execute)
+                                    cancellations.set(lateness);
+                                else
+                                    cancellations.set(0);
+                            }
+                            else
+                            {
+                                if (!expected_to_execute)
+                                {
+                                    cancellations.set(9999);
+                                }
+                            }
+
+                            Thread.yield();
+                        }
+                    }
+                    catch (InterruptedException e)
+                    {
+                        e.printStackTrace();
+                    }
+                }
+            };
+        }
+
+        for (Thread thread : test)
+            thread.start();
+
+        for (Thread thread : test)
+            thread.join();
+
+        // there were some executions and cancellations
+        Assert.assertThat(executions.getCount(),Matchers.greaterThan(0L));
+        Assert.assertThat(cancellations.getCount(),Matchers.greaterThan(0L));
+
+        // All executed or cancelled
+        // Not that SimpleScheduler can execute and cancel an event!
+        Assert.assertThat(0L+schedules.get(),Matchers.lessThanOrEqualTo(executions.getCount()+cancellations.getCount()));
+
+        // No really late executions
+        Assert.assertThat(executions.getMax(),Matchers.lessThan(500L));
+
+        // Executions on average are close to the expected time
+        Assert.assertThat(executions.getMean(),Matchers.lessThan(500.0));
+
+        // No cancellations long after expected executions
+        Assert.assertThat(cancellations.getMax(),Matchers.lessThan(500L));
+    }
+}
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/thread/SweeperTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/thread/SweeperTest.java
new file mode 100644
index 0000000..113246d
--- /dev/null
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/thread/SweeperTest.java
@@ -0,0 +1,124 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.thread;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class SweeperTest
+{
+    private Scheduler scheduler;
+
+    @Before
+    public void prepare() throws Exception
+    {
+        scheduler = new ScheduledExecutorScheduler();
+        scheduler.start();
+    }
+
+    @After
+    public void dispose() throws Exception
+    {
+        scheduler.stop();
+    }
+
+    @Test
+    public void testResourceNotSweptIsNotRemoved() throws Exception
+    {
+        testResourceSweepRemove(false);
+    }
+
+    @Test
+    public void testResourceSweptIsRemoved() throws Exception
+    {
+        testResourceSweepRemove(true);
+    }
+
+    private void testResourceSweepRemove(final boolean sweep) throws Exception
+    {
+        long period = 1000;
+        final CountDownLatch taskLatch = new CountDownLatch(1);
+        Sweeper sweeper = new Sweeper(scheduler, period)
+        {
+            @Override
+            public void run()
+            {
+                super.run();
+                taskLatch.countDown();
+            }
+        };
+        sweeper.start();
+
+        final CountDownLatch sweepLatch = new CountDownLatch(1);
+        sweeper.offer(new Sweeper.Sweepable()
+        {
+            @Override
+            public boolean sweep()
+            {
+                sweepLatch.countDown();
+                return sweep;
+            }
+        });
+
+        Assert.assertTrue(sweepLatch.await(2 * period, TimeUnit.MILLISECONDS));
+        Assert.assertTrue(taskLatch.await(2 * period, TimeUnit.MILLISECONDS));
+        Assert.assertEquals(sweep ? 0 : 1, sweeper.getSize());
+
+        sweeper.stop();
+    }
+
+    @Test
+    public void testSweepThrows() throws Exception
+    {
+        long period = 500;
+        final CountDownLatch taskLatch = new CountDownLatch(2);
+        Sweeper sweeper = new Sweeper(scheduler, period)
+        {
+            @Override
+            public void run()
+            {
+                super.run();
+                taskLatch.countDown();
+            }
+        };
+        sweeper.start();
+
+        final CountDownLatch sweepLatch = new CountDownLatch(2);
+        sweeper.offer(new Sweeper.Sweepable()
+        {
+            @Override
+            public boolean sweep()
+            {
+                sweepLatch.countDown();
+                throw new NullPointerException();
+            }
+        });
+
+        Assert.assertTrue(sweepLatch.await(4 * period, TimeUnit.MILLISECONDS));
+        Assert.assertTrue(taskLatch.await(4 * period, TimeUnit.MILLISECONDS));
+        Assert.assertEquals(1, sweeper.getSize());
+
+        sweeper.stop();
+    }
+}
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/thread/TimeoutTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/thread/TimeoutTest.java
deleted file mode 100644
index 2ebfc01..0000000
--- a/jetty-util/src/test/java/org/eclipse/jetty/util/thread/TimeoutTest.java
+++ /dev/null
@@ -1,270 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.util.thread;
-
-import static org.junit.Assert.assertEquals;
-
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicIntegerArray;
-
-import org.junit.Before;
-import org.junit.Test;
-
-
-public class TimeoutTest
-{
-	private boolean _stress=Boolean.getBoolean("STRESS");
-	
-    Object lock = new Object();
-    Timeout timeout = new Timeout(null);
-    Timeout.Task[] tasks;
-
-    /* ------------------------------------------------------------ */
-    /* 
-     * @see junit.framework.TestCase#setUp()
-     */
-    @Before
-    public void setUp() throws Exception
-    {
-        timeout=new Timeout(lock);
-        tasks= new Timeout.Task[10]; 
-        
-        for (int i=0;i<tasks.length;i++)
-        {
-            tasks[i]=new Timeout.Task();
-            timeout.setNow(1000+i*100);
-            timeout.schedule(tasks[i]);
-        }
-        timeout.setNow(100);
-    }
-
-    /* ------------------------------------------------------------ */
-    @Test
-    public void testExpiry()
-    {
-        timeout.setDuration(200);
-        timeout.setNow(1500);
-        timeout.tick();
-        
-        for (int i=0;i<tasks.length;i++)
-        {
-            assertEquals("isExpired "+i,i<4, tasks[i].isExpired());
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    @Test
-    public void testCancel()
-    {
-        timeout.setDuration(200);
-        timeout.setNow(1700);
-
-        for (int i=0;i<tasks.length;i++)
-            if (i%2==1)
-                tasks[i].cancel();
-
-        timeout.tick();
-        
-        for (int i=0;i<tasks.length;i++)
-        {
-            assertEquals("isExpired "+i,i%2==0 && i<6, tasks[i].isExpired());
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    @Test
-    public void testTouch()
-    {
-        timeout.setDuration(200);
-        timeout.setNow(1350);
-        timeout.schedule(tasks[2]);
-        
-        timeout.setNow(1500);
-        timeout.tick();
-        for (int i=0;i<tasks.length;i++)
-        {
-            assertEquals("isExpired "+i,i!=2 && i<4, tasks[i].isExpired());
-        }
-        
-        timeout.setNow(1550);
-        timeout.tick();
-        for (int i=0;i<tasks.length;i++)
-        {
-            assertEquals("isExpired "+i, i<4, tasks[i].isExpired());
-        }  
-    }
-
-
-    /* ------------------------------------------------------------ */
-    @Test
-    public void testDelay()
-    {
-        Timeout.Task task = new Timeout.Task();
-
-        timeout.setNow(1100);
-        timeout.schedule(task, 300);
-        timeout.setDuration(200);
-        
-        timeout.setNow(1300);
-        timeout.tick();
-        assertEquals("delay", false, task.isExpired());
-        
-        timeout.setNow(1500);
-        timeout.tick();
-        assertEquals("delay", false, task.isExpired());
-        
-        timeout.setNow(1700);
-        timeout.tick();
-        assertEquals("delay", true, task.isExpired());
-    }
-
-    /* ------------------------------------------------------------ */
-    @Test
-    public void testStress() throws Exception
-    {
-    	if ( !_stress )
-    		return;
-    	
-        final int LOOP=250;
-        final AtomicBoolean running=new AtomicBoolean(true);
-        final AtomicIntegerArray count = new AtomicIntegerArray( 4 );
-
-
-        timeout.setNow(System.currentTimeMillis());
-        timeout.setDuration(500);
-        
-        // Start a ticker thread that will tick over the timer frequently.
-        Thread ticker = new Thread()
-        {
-            @Override
-            public void run()
-            {
-                while (running.get())
-                {
-                    try
-                    {
-                        // use lock.wait so we have a memory barrier and
-                        // have no funny optimisation issues.
-                        synchronized (lock)
-                        {
-                            lock.wait(30);
-                        }
-                        Thread.sleep(30);
-                        timeout.tick(System.currentTimeMillis());
-                    }
-                    catch(Exception e)
-                    {
-                        e.printStackTrace();
-                    }
-                }
-            }
-        };
-        ticker.start();
-
-        // start lots of test threads
-        for (int i=0;i<LOOP;i++)
-        {
-            // 
-            Thread th = new Thread()
-            { 
-                @Override
-                public void run()
-                {
-                    // count how many threads were started (should == LOOP)
-                    int once = (int) 10 + count.incrementAndGet( 0 )%50;
-                    
-                    // create a task for this thread
-                    Timeout.Task task = new Timeout.Task()
-                    {
-                        @Override
-                        public void expired()
-                        {       
-                            // count the number of expires                           
-                            count.incrementAndGet( 2 );                          
-                        }
-                    };
-                    
-                    // this thread will loop and each loop with schedule a 
-                    // task with a delay  on top of the timeouts duration
-                    // mostly this thread will then cancel the task
-                    // But once it will wait and the task will expire
-                    
-                    
-                    // do the looping until we are stopped
-                    int loop=0;
-                    while (running.get())
-                    {
-                        try
-                        {
-                            long delay=1000;
-                            long wait=100-once;
-                            
-                            if (loop++==once)
-                            { 
-                                // THIS loop is the one time we wait longer than the delay
-                                count.incrementAndGet( 1 );  
-                                delay=200;
-                                wait=1000;
-                            }
-                            
-                            timeout.schedule(task,delay);
-                            
-                            // do the wait
-                            Thread.sleep(wait);
-                            
-                            // cancel task (which may have expired)
-                            task.cancel();
-                        }
-                        catch(Exception e)
-                        {
-                            e.printStackTrace();
-                        }
-                    }
-                    count.incrementAndGet(3);
-                }
-            };
-            th.start();
-        }
-        
-        long start=System.currentTimeMillis();
-        
-        // run test until all threads are started
-        while (count.get(0)<LOOP && (System.currentTimeMillis()-start)<20000)
-            Thread.sleep(50);
-        // run test until all expires initiated
-        while (count.get(1)<LOOP && (System.currentTimeMillis()-start)<20000)
-            Thread.sleep(50);
-        
-        // run test until all expires initiated
-        while (count.get(2)<LOOP && (System.currentTimeMillis()-start)<20000)
-            Thread.sleep(50);
-        
-        running.set(false);
-
-        // run test until all threads complete
-        while (count.get(3)<LOOP && (System.currentTimeMillis()-start)<20000)
-            Thread.sleep(50);
-        
-        // check the counts
-        assertEquals("count threads", LOOP,count.get( 0 ));
-        assertEquals("count once waits",LOOP,count.get(1 ));
-        assertEquals("count expires",LOOP,count.get(2));
-        assertEquals("done",LOOP,count.get(3));
-    }
-}
diff --git a/jetty-util/src/test/resources/TestData/WindowsDir.zip b/jetty-util/src/test/resources/TestData/WindowsDir.zip
new file mode 100644
index 0000000..26f2357
Binary files /dev/null and b/jetty-util/src/test/resources/TestData/WindowsDir.zip differ
diff --git a/jetty-util/src/test/resources/TestData/test/META-INF/MANIFEST.MF b/jetty-util/src/test/resources/TestData/test/META-INF/MANIFEST.MF
old mode 100755
new mode 100644
diff --git a/jetty-util/src/test/resources/TestData/test/alphabet b/jetty-util/src/test/resources/TestData/test/alphabet
old mode 100755
new mode 100644
diff --git a/jetty-util/src/test/resources/TestData/test/numbers b/jetty-util/src/test/resources/TestData/test/numbers
old mode 100755
new mode 100644
diff --git a/jetty-util/src/test/resources/TestData/test/subdir/alphabet b/jetty-util/src/test/resources/TestData/test/subdir/alphabet
old mode 100755
new mode 100644
diff --git a/jetty-util/src/test/resources/TestData/test/subdir/numbers b/jetty-util/src/test/resources/TestData/test/subdir/numbers
old mode 100755
new mode 100644
diff --git a/jetty-util/src/test/resources/TestData/test/subdir/subsubdir/alphabet b/jetty-util/src/test/resources/TestData/test/subdir/subsubdir/alphabet
old mode 100755
new mode 100644
diff --git a/jetty-util/src/test/resources/TestData/test/subdir/subsubdir/numbers b/jetty-util/src/test/resources/TestData/test/subdir/subsubdir/numbers
old mode 100755
new mode 100644
diff --git a/jetty-util/src/test/resources/jetty-logging.properties b/jetty-util/src/test/resources/jetty-logging.properties
index 1fdedf4..3a0ce97 100644
--- a/jetty-util/src/test/resources/jetty-logging.properties
+++ b/jetty-util/src/test/resources/jetty-logging.properties
@@ -1,2 +1,3 @@
 # Setup default logging implementation for during testing
-org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
\ No newline at end of file
+org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
+#org.eclipse.jetty.util.LEVEL=DEBUG
diff --git a/jetty-util/src/test/resources/org/eclipse/jetty/util/resource/four/four b/jetty-util/src/test/resources/org/eclipse/jetty/util/resource/four/four
new file mode 100644
index 0000000..02bf84b
--- /dev/null
+++ b/jetty-util/src/test/resources/org/eclipse/jetty/util/resource/four/four
@@ -0,0 +1 @@
+4 - four (no extension)
\ No newline at end of file
diff --git a/jetty-util/src/test/resources/org/eclipse/jetty/util/resource/four/four.txt b/jetty-util/src/test/resources/org/eclipse/jetty/util/resource/four/four.txt
new file mode 100644
index 0000000..05a6c6f
--- /dev/null
+++ b/jetty-util/src/test/resources/org/eclipse/jetty/util/resource/four/four.txt
@@ -0,0 +1 @@
+4 - four
\ No newline at end of file
diff --git a/jetty-util/src/test/resources/org/eclipse/jetty/util/resource/resource.txt b/jetty-util/src/test/resources/org/eclipse/jetty/util/resource/resource.txt
new file mode 100644
index 0000000..016e97f
--- /dev/null
+++ b/jetty-util/src/test/resources/org/eclipse/jetty/util/resource/resource.txt
@@ -0,0 +1 @@
+this is test data
diff --git a/jetty-webapp/pom.xml b/jetty-webapp/pom.xml
index 0f0222f..e3e066f 100644
--- a/jetty-webapp/pom.xml
+++ b/jetty-webapp/pom.xml
@@ -2,7 +2,7 @@
   <parent>
     <groupId>org.eclipse.jetty</groupId>
     <artifactId>jetty-project</artifactId>
-    <version>8.1.17.v20150415</version>
+    <version>9.2.13.v20150730</version>
   </parent>
   <modelVersion>4.0.0</modelVersion>
   <artifactId>jetty-webapp</artifactId>
@@ -55,7 +55,7 @@
             </goals>
             <configuration>
               <instructions>
-                <Import-Package>javax.servlet.*;version="2.6.0",*</Import-Package>
+                <Import-Package>javax.servlet.*;version="[2.6.0,3.2]",*</Import-Package>
               </instructions>
             </configuration>
           </execution>
@@ -91,8 +91,8 @@
       <version>${project.version}</version>
     </dependency>
     <dependency>
-      <groupId>junit</groupId>
-      <artifactId>junit</artifactId>
+      <groupId>org.eclipse.jetty.toolchain</groupId>
+      <artifactId>jetty-test-helper</artifactId>
       <scope>test</scope>
     </dependency>
     <dependency>
diff --git a/jetty-webapp/src/main/config/etc/webdefault.xml b/jetty-webapp/src/main/config/etc/webdefault.xml
index 213138b..d54d76f 100644
--- a/jetty-webapp/src/main/config/etc/webdefault.xml
+++ b/jetty-webapp/src/main/config/etc/webdefault.xml
@@ -1,29 +1,28 @@
-<?xml version="1.0" encoding="ISO-8859-1"?>
+<?xml version="1.0" encoding="UTF-8"?>
+<web-app 
+   xmlns="http://xmlns.jcp.org/xml/ns/javaee" 
+   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+   xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
+   metadata-complete="false"
+   version="3.1"> 
 
   <!-- ===================================================================== -->
   <!-- This file contains the default descriptor for web applications.       -->
   <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
   <!-- The intent of this descriptor is to include jetty specific or common  -->
   <!-- configuration for all webapps.   If a context has a webdefault.xml    -->
-  <!-- descriptor, it is applied before the contexts own web.xml file        -->
+  <!-- descriptor, it is applied before the context's own web.xml file       -->
   <!--                                                                       -->
-  <!-- A context may be assigned a default descriptor by:                    -->
-  <!--  + Calling WebApplicationContext.setDefaultsDescriptor                -->
-  <!--  + Passed an arg to addWebApplications                                -->
+  <!-- A context may be assigned a default descriptor by calling             -->
+  <!-- WebAppContext.setDefaultsDescriptor(String).                          -->
   <!--                                                                       -->
-  <!-- This file is used both as the resource within the jetty.jar (which is -->
-  <!-- used as the default if no explicit defaults descriptor is set) and it -->
-  <!-- is copied to the etc directory of the Jetty distro and explicitly     -->
-  <!-- by the jetty.xml file.                                                -->
+  <!-- This file is present in the jetty-webapp.jar, and is used as the      -->
+  <!-- defaults descriptor if no other is explicitly set on a context.       -->
   <!--                                                                       -->
+  <!-- A copy of this file is also placed into the $JETTY_HOME/etc dir of    -->
+  <!-- the  distribution, and is referenced by some of the other xml files,  -->
+  <!-- eg the jetty-deploy.xml file.                                         -->
   <!-- ===================================================================== -->
-<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"
-  metadata-complete="true"
-  version="2.5"
->
 
   <description>
     Default web.xml file.  
@@ -51,9 +50,19 @@
   <!-- Context params to control Session Cookies                            -->
   <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -->
   <!--
-    UNCOMMENT TO ACTIVATE <context-param> <param-name>org.eclipse.jetty.servlet.SessionDomain</param-name> <param-value>127.0.0.1</param-value> </context-param> <context-param>
-    <param-name>org.eclipse.jetty.servlet.SessionPath</param-name> <param-value>/</param-value> </context-param> <context-param> <param-name>org.eclipse.jetty.servlet.MaxAge</param-name>
-    <param-value>-1</param-value> </context-param>
+    UNCOMMENT TO ACTIVATE 
+    <context-param> 
+      <param-name>org.eclipse.jetty.servlet.SessionDomain</param-name> 
+      <param-value>127.0.0.1</param-value> 
+    </context-param> 
+    <context-param>
+      <param-name>org.eclipse.jetty.servlet.SessionPath</param-name>
+      <param-value>/</param-value>
+    </context-param>
+    <context-param>
+      <param-name>org.eclipse.jetty.servlet.MaxAge</param-name>
+      <param-value>-1</param-value>
+    </context-param>
   -->
 
   <!-- ==================================================================== -->
@@ -87,33 +96,39 @@
  *
  *  resourceBase      Set to replace the context resource base
  *
- *  resourceCache     If set, this is a context attribute name, which the servlet 
- *                    will use to look for a shared ResourceCache instance. 
- *                        
+ *  resourceCache     If set, this is a context attribute name, which the servlet
+ *                    will use to look for a shared ResourceCache instance.
+ *
  *  relativeResourceBase
  *                    Set with a pathname relative to the base of the
  *                    servlet context root. Useful for only serving static content out
  *                    of only specific subdirectories.
  *
+ *  pathInfoOnly      If true, only the path info will be applied to the resourceBase
+ *
+ *  stylesheet        Set with the location of an optional stylesheet that will be used
+ *                    to decorate the directory listing html.
+ *
  *  aliases           If True, aliases of resources are allowed (eg. symbolic
  *                    links and caps variations). May bypass security constraints.
+ *                    
+ *  etags             If True, weak etags will be generated and handled.
  *
  *  maxCacheSize      The maximum total size of the cache or 0 for no cache.
  *  maxCachedFileSize The maximum size of a file to cache
  *  maxCachedFiles    The maximum number of files to cache
  *
  *  useFileMappedBuffer
- *                    If set to true, it will use mapped file buffer to serve static content
- *                    when using NIO connector. Setting this value to false means that
+ *                    If set to true, it will use mapped file buffers to serve static content
+ *                    when using an NIO connector. Setting this value to false means that
  *                    a direct buffer will be used instead of a mapped file buffer.
- *                    By default, this is set to true.
+ *                    This file sets the value to true.
  *
  *  cacheControl      If set, all static content will have this value set as the cache-control
  *                    header.
+ *
  -->
- 
- 
-  <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -->
+ <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -->
   <servlet>
     <servlet-name>default</servlet-name>
     <servlet-class>org.eclipse.jetty.servlet.DefaultServlet</servlet-class>
@@ -151,7 +166,11 @@
     </init-param>
     <init-param>
       <param-name>gzip</param-name>
-      <param-value>true</param-value>
+      <param-value>false</param-value>
+    </init-param>
+    <init-param>
+      <param-name>etags</param-name>
+      <param-value>false</param-value>
     </init-param>
     <init-param>
       <param-name>useFileMappedBuffer</param-name>
@@ -180,13 +199,13 @@
 
   <!-- ==================================================================== -->
   <!-- JSP Servlet                                                          -->
-  <!-- This is the jasper JSP servlet from the jakarta project              -->
+  <!-- This is the jasper JSP servlet.                                      -->
   <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -->
   <!-- The JSP page compiler and execution servlet, which is the mechanism  -->
-  <!-- used by Glassfish to support JSP pages.  Traditionally, this servlet -->
-  <!-- is mapped to URL patterh "*.jsp".  This servlet supports the         -->
-  <!-- following initialization parameters (default values are in square    -->
-  <!-- brackets):                                                           -->
+  <!-- used by the jsp container to support JSP pages.  Traditionally,      -->
+  <!-- this servlet is mapped to URL pattern "*.jsp".  This servlet         -->
+  <!-- supports the following initialization parameters (default values     -->
+  <!-- are in square brackets):                                             -->
   <!--                                                                      -->
   <!--   checkInterval       If development is false and reloading is true, -->
   <!--                       background compiles are enabled. checkInterval -->
@@ -194,7 +213,7 @@
   <!--                       if a JSP page needs to be recompiled. [300]    -->
   <!--                                                                      -->
   <!--   compiler            Which compiler Ant should use to compile JSP   -->
-  <!--                       pages.  See the Ant documenation for more      -->
+  <!--                       pages.  See the Ant documentation for more     -->
   <!--                       information. [javac]                           -->
   <!--                                                                      -->
   <!--   classdebuginfo      Should the class file be compiled with         -->
@@ -255,19 +274,10 @@
   <!--   xpoweredBy          Determines whether X-Powered-By response       -->
   <!--                       header is added by generated servlet  [false]  -->
   <!--                                                                      -->
-  <!-- If you wish to use Jikes to compile JSP pages:                       -->
-  <!--   Set the init parameter "compiler" to "jikes".  Define              -->
-  <!--   the property "-Dbuild.compiler.emacs=true" when starting Jetty     -->
-  <!--   to cause Jikes to emit error messages in a format compatible with  -->
-  <!--   Jasper.                                                            -->
-  <!--   If you get an error reporting that jikes can't use UTF-8 encoding, -->
-  <!--   try setting the init parameter "javaEncoding" to "ISO-8859-1".     -->
   <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -->
-  <servlet
-    id="jsp"
-  >
+  <servlet id="jsp">
     <servlet-name>jsp</servlet-name>
-    <servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
+    <servlet-class>org.eclipse.jetty.jsp.JettyJspServlet</servlet-class>
     <init-param>
       <param-name>logVerbosityLevel</param-name>
       <param-value>DEBUG</param-value>
@@ -280,6 +290,14 @@
       <param-name>xpoweredBy</param-name>
       <param-value>false</param-value>
     </init-param>
+    <init-param>
+      <param-name>compilerTargetVM</param-name>
+      <param-value>1.7</param-value>
+    </init-param>
+    <init-param>
+      <param-name>compilerSourceVM</param-name>
+      <param-value>1.7</param-value>
+    </init-param>
     <!--  
     <init-param>
         <param-name>classpath</param-name>
@@ -301,35 +319,10 @@
     <url-pattern>*.XSP</url-pattern>
   </servlet-mapping>
 
-  <!-- ==================================================================== -->
-  <!-- Dynamic Servlet Invoker.                                             -->
-  <!-- This servlet invokes anonymous servlets that have not been defined   -->
-  <!-- in the web.xml or by other means. The first element of the pathInfo  -->
-  <!-- of a request passed to the envoker is treated as a servlet name for  -->
-  <!-- an existing servlet, or as a class name of a new servlet.            -->
-  <!-- This servlet is normally mapped to /servlet/*                        -->
-  <!-- This servlet support the following initParams:                       -->
-  <!--                                                                      -->
-  <!--  nonContextServlets       If false, the invoker can only load        -->
-  <!--                           servlets from the contexts classloader.    -->
-  <!--                           This is false by default and setting this  -->
-  <!--                           to true may have security implications.    -->
-  <!--                                                                      -->
-  <!--  verbose                  If true, log dynamic loads                 -->
-  <!--                                                                      -->
-  <!--  *                        All other parameters are copied to the     -->
-  <!--                           each dynamic servlet as init parameters    -->
-  <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -->
-  <!--
-    Uncomment for dynamic invocation <servlet> <servlet-name>invoker</servlet-name> <servlet-class>org.eclipse.jetty.servlet.Invoker</servlet-class> <init-param> <param-name>verbose</param-name>
-    <param-value>false</param-value> </init-param> <init-param> <param-name>nonContextServlets</param-name> <param-value>false</param-value> </init-param> <init-param>
-    <param-name>dynamicParam</param-name> <param-value>anyValue</param-value> </init-param> <load-on-startup>0</load-on-startup> </servlet> <servlet-mapping> <servlet-name>invoker</servlet-name>
-    <url-pattern>/servlet/*</url-pattern> </servlet-mapping>
-  -->
-
-
 
   <!-- ==================================================================== -->
+  <!-- Default session configuration                                        -->
+  <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -->
   <session-config>
     <session-timeout>30</session-timeout>
   </session-config>
@@ -337,7 +330,7 @@
   <!-- ==================================================================== -->
   <!-- Default MIME mappings                                                -->
   <!-- The default MIME mappings are provided by the mime.properties        -->
-  <!-- resource in the org.eclipse.jetty.server.jar file.  Additional or modified  -->
+  <!-- resource in the jetty-http.jar file.  Additional or modified         -->
   <!-- mappings may be specified here                                       -->
   <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -->
   <!-- UNCOMMENT TO ACTIVATE
@@ -348,6 +341,8 @@
   -->
 
   <!-- ==================================================================== -->
+  <!-- Default welcome files                                                -->
+  <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -->
   <welcome-file-list>
     <welcome-file>index.html</welcome-file>
     <welcome-file>index.htm</welcome-file>
@@ -355,6 +350,8 @@
   </welcome-file-list>
 
   <!-- ==================================================================== -->
+  <!-- Default locale encodings                                             -->
+  <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -->
   <locale-encoding-mapping-list>
     <locale-encoding-mapping>
       <locale>ar</locale>
@@ -514,6 +511,9 @@
     </locale-encoding-mapping>
   </locale-encoding-mapping-list>
 
+  <!-- ==================================================================== -->
+  <!-- Disable TRACE method with security constraint                        -->
+  <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -->
   <security-constraint>
     <web-resource-collection>
       <web-resource-name>Disable TRACE</web-resource-name>
@@ -522,6 +522,13 @@
     </web-resource-collection>
     <auth-constraint/>
   </security-constraint>
+  <security-constraint>
+    <web-resource-collection>
+      <web-resource-name>Enable everything but TRACE</web-resource-name>
+      <url-pattern>/</url-pattern>
+      <http-method-omission>TRACE</http-method-omission>
+    </web-resource-collection>
+  </security-constraint>
 
 </web-app>
 
diff --git a/jetty-webapp/src/main/config/modules/webapp.mod b/jetty-webapp/src/main/config/modules/webapp.mod
new file mode 100644
index 0000000..6bb37ef
--- /dev/null
+++ b/jetty-webapp/src/main/config/modules/webapp.mod
@@ -0,0 +1,10 @@
+#
+# WebApp Support Module
+#
+
+[depend]
+servlet
+security
+
+[lib]
+lib/jetty-webapp-${jetty.version}.jar
diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/ClasspathPattern.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/ClasspathPattern.java
index 724a754..7389f7f 100644
--- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/ClasspathPattern.java
+++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/ClasspathPattern.java
@@ -81,8 +81,6 @@ public class ClasspathPattern
     
     /* ------------------------------------------------------------ */
     /**
-     * Initialize the matcher by parsing each classpath pattern in an array
-     * 
      * @param patterns array of classpath patterns
      */
     private void addPatterns(String[] patterns)
@@ -93,7 +91,8 @@ public class ClasspathPattern
             for (String pattern : patterns)
             {
                 entry = createEntry(pattern);
-                if (entry != null) {
+                if (entry != null) 
+                {
                     _patterns.add(pattern);
                     _entries.add(entry);
                 }
@@ -103,6 +102,29 @@ public class ClasspathPattern
     
     /* ------------------------------------------------------------ */
     /**
+     * @param patterns array of classpath patterns
+     */
+    private void prependPatterns(String[] patterns)
+    {
+        if (patterns != null)
+        {
+            Entry entry = null;
+            int i=0;
+            for (String pattern : patterns)
+            {
+                entry = createEntry(pattern);
+                if (entry != null) 
+                {
+                    _patterns.add(i,pattern);
+                    _entries.add(i,entry);
+                    i++;
+                }
+            }
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
      * Create an entry object containing information about 
      * a single classpath pattern
      * 
@@ -156,9 +178,24 @@ public class ClasspathPattern
             patterns.add(entries.nextToken());
         }
         
-        addPatterns((String[])patterns.toArray(new String[patterns.size()]));
+        addPatterns(patterns.toArray(new String[patterns.size()]));
     }   
     
+
+    /* ------------------------------------------------------------ */
+    public void prependPattern(String classOrPackage)
+    {
+        ArrayList<String> patterns = new ArrayList<String>();
+        StringTokenizer entries = new StringTokenizer(classOrPackage, ":,");
+        while (entries.hasMoreTokens())
+        {
+            patterns.add(entries.nextToken());
+        }
+        
+        prependPatterns(patterns.toArray(new String[patterns.size()]));
+    }
+    
+    
     /* ------------------------------------------------------------ */
     /**
      * @return array of classpath patterns
@@ -227,4 +264,5 @@ public class ClasspathPattern
         }
         return result;
     }
+
 }
diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/Configuration.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/Configuration.java
index 4b5ea4b..e67ff28 100644
--- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/Configuration.java
+++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/Configuration.java
@@ -18,6 +18,14 @@
 
 package org.eclipse.jetty.webapp;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.ListIterator;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.util.annotation.Name;
+
 
 /* ------------------------------------------------------------------------------- */
 /** Base Class for WebApplicationContext Configuration.
@@ -26,7 +34,8 @@ package org.eclipse.jetty.webapp;
  */
 public interface Configuration 
 {
-
+    public final static String ATTR="org.eclipse.jetty.webapp.configuration";
+    
     /* ------------------------------------------------------------------------------- */
     /** Set up for configuration.
      * <p>
@@ -84,4 +93,105 @@ public interface Configuration
      * @throws Exception
      */
     public void cloneConfigure (WebAppContext template, WebAppContext context) throws Exception;
+    
+    
+    public class ClassList extends ArrayList<String>
+    {        
+        /* ------------------------------------------------------------ */
+        /** Get/Set/Create the server default Configuration ClassList.
+         * <p>Get the class list from: a Server bean; or the attribute (which can
+         * either be a ClassList instance or an String[] of class names); or a new instance
+         * with default configuration classes.</p>
+         * <p>This method also adds the obtained ClassList instance as a dependent bean
+         * on the server and clears the attribute</p>
+         * @param server The server the default is for
+         * @return the server default ClassList instance of the configuration classes for this server. Changes to this list will change the server default instance.
+         */
+        public static ClassList setServerDefault(Server server)
+        {
+            ClassList cl=server.getBean(ClassList.class);
+            if (cl!=null)
+                return cl;
+            cl=serverDefault(server);
+            server.addBean(cl);
+            server.setAttribute(ATTR,null);
+            return cl;
+        }
+
+        /* ------------------------------------------------------------ */
+        /** Get/Create the server default Configuration ClassList.
+         * <p>Get the class list from: a Server bean; or the attribute (which can
+         * either be a ClassList instance or an String[] of class names); or a new instance
+         * with default configuration classes.
+         * @param server The server the default is for
+         * @return A copy of the server default ClassList instance of the configuration classes for this server. Changes to the returned list will not change the server default.
+         */
+        public static ClassList serverDefault(Server server)
+        {
+            ClassList cl=server.getBean(ClassList.class);
+            if (cl!=null)
+                return new ClassList(cl);
+            Object attr = server.getAttribute(ATTR);
+            if (attr instanceof ClassList)
+                return new ClassList((ClassList)attr);
+            if (attr instanceof String[])
+                return new ClassList((String[])attr);
+            return new ClassList();
+        }
+        
+        public ClassList()
+        {
+            this(WebAppContext.DEFAULT_CONFIGURATION_CLASSES);
+        }
+        
+        public ClassList(String[] classes)
+        {
+            addAll(Arrays.asList(classes));
+        }
+
+        public ClassList(List<String> classes)
+        {
+            addAll(classes);
+        }
+        
+        public void addAfter(@Name("afterClass") String afterClass, at Name("configClass")String... configClass)
+        {
+            if (configClass!=null && afterClass!=null)
+            {
+                ListIterator<String> iter = listIterator();
+                while (iter.hasNext())
+                {
+                    String cc=iter.next();
+                    if (afterClass.equals(cc))
+                    {
+                        for (int i=0;i<configClass.length;i++)
+                            iter.add(configClass[i]);
+                        return;
+                    }
+                }
+            }
+            throw new IllegalArgumentException("afterClass '"+afterClass+"' not found in "+this);
+        }
+
+        public void addBefore(@Name("beforeClass") String beforeClass, at Name("configClass")String... configClass)
+        {
+            if (configClass!=null && beforeClass!=null)
+            {
+                ListIterator<String> iter = listIterator();
+                while (iter.hasNext())
+                {
+                    String cc=iter.next();
+                    if (beforeClass.equals(cc))
+                    {
+                        iter.previous();
+                        for (int i=0;i<configClass.length;i++)
+                            iter.add(configClass[i]);
+                        return;
+                    }
+                }
+            }
+            throw new IllegalArgumentException("beforeClass '"+beforeClass+"' not found in "+this);
+        }
+        
+    }
 }
diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/Descriptor.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/Descriptor.java
index 723f7d8..226897f 100644
--- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/Descriptor.java
+++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/Descriptor.java
@@ -18,8 +18,6 @@
 
 package org.eclipse.jetty.webapp;
 
-import java.net.URL;
-
 import org.eclipse.jetty.util.resource.Resource;
 import org.eclipse.jetty.xml.XmlParser;
 
@@ -35,18 +33,9 @@ public abstract class Descriptor
         _xml = xml;
     }
     
-    public abstract XmlParser newParser()
-    throws ClassNotFoundException;
-    
     public abstract void ensureParser()
     throws ClassNotFoundException;
     
-    protected void redirect(XmlParser parser, String resource, URL source)
-    {
-        if (source != null) parser.redirectEntity(resource, source);
-    }
-    
-    
     public void setValidating (boolean validating)
     {
        _validating = validating;
@@ -66,7 +55,7 @@ public abstract class Descriptor
             }
             finally
             {
-                _xml.release();
+                _xml.close();
             }
         }
     }
diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/FragmentConfiguration.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/FragmentConfiguration.java
index 2b5c047..cf1b3ce 100644
--- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/FragmentConfiguration.java
+++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/FragmentConfiguration.java
@@ -19,7 +19,7 @@
 
 package org.eclipse.jetty.webapp;
 
-import java.util.List;
+import java.util.Map;
 
 import org.eclipse.jetty.util.resource.Resource;
 
@@ -45,15 +45,6 @@ public class FragmentConfiguration extends AbstractConfiguration
         
     }
 
-    @Override
-    public void configure(WebAppContext context) throws Exception
-    { 
-        if (!context.isConfigurationDiscovered())
-            return;
-        
-        //order the fragments
-        context.getMetaData().orderFragments(); 
-    }
 
     @Override
     public void postConfigure(WebAppContext context) throws Exception
@@ -70,18 +61,18 @@ public class FragmentConfiguration extends AbstractConfiguration
     public void findWebFragments (final WebAppContext context, final MetaData metaData) throws Exception
     {
         @SuppressWarnings("unchecked")
-        List<Resource> frags = (List<Resource>)context.getAttribute(FRAGMENT_RESOURCES);
+        Map<Resource, Resource> frags = (Map<Resource,Resource>)context.getAttribute(FRAGMENT_RESOURCES);
         if (frags!=null)
         {
-            for (Resource frag : frags)
+            for (Resource key : frags.keySet())
             {
-            	if (frag.isDirectory()) //tolerate the case where the library is a directory, not a jar. useful for OSGi for example
-            	{
-                    metaData.addFragment(frag, Resource.newResource(frag.getURL()+"/META-INF/web-fragment.xml"));            		
-            	}
+                if (key.isDirectory()) //tolerate the case where the library is a directory, not a jar. useful for OSGi for example
+                {
+                    metaData.addFragment(key, frags.get(key));                          
+                }
                 else //the standard case: a jar most likely inside WEB-INF/lib
                 {
-                    metaData.addFragment(frag, Resource.newResource("jar:"+frag.getURL()+"!/META-INF/web-fragment.xml"));
+                    metaData.addFragment(key, frags.get(key));
                 }
             }
         }
diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/JettyWebXmlConfiguration.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/JettyWebXmlConfiguration.java
index f6d56cf..c6f5ce8 100644
--- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/JettyWebXmlConfiguration.java
+++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/JettyWebXmlConfiguration.java
@@ -18,7 +18,6 @@
 
 package org.eclipse.jetty.webapp;
 
-import java.util.HashMap;
 import java.util.Map;
 
 import org.eclipse.jetty.util.log.Log;
@@ -28,12 +27,12 @@ import org.eclipse.jetty.xml.XmlConfiguration;
 
 
 /**
- * 
+ *
  * JettyWebConfiguration.
- * 
- * Looks for Xmlconfiguration files in WEB-INF.  Searches in order for the first of jetty6-web.xml, jetty-web.xml or web-jetty.xml
  *
- * 
+ * Looks for XmlConfiguration files in WEB-INF.  Searches in order for the first of jetty6-web.xml, jetty-web.xml or web-jetty.xml
+ *
+ *
  *
  */
 public class JettyWebXmlConfiguration extends AbstractConfiguration
@@ -48,8 +47,8 @@ public class JettyWebXmlConfiguration extends AbstractConfiguration
 
     public static final String XML_CONFIGURATION = "org.eclipse.jetty.webapp.JettyWebXmlConfiguration";
     public static final String JETTY_WEB_XML = "jetty-web.xml";
-    
-    /** 
+
+    /**
      * Configure
      * Apply web-jetty.xml configuration
      * @see Configuration#configure(WebAppContext)
@@ -63,9 +62,9 @@ public class JettyWebXmlConfiguration extends AbstractConfiguration
             LOG.debug("Cannot configure webapp after it is started");
             return;
         }
-        
+
         LOG.debug("Configuring web-jetty.xml");
-        
+
         Resource web_inf = context.getWebInf();
         // handle any WEB-INF descriptors
         if(web_inf!=null&&web_inf.isDirectory())
@@ -79,16 +78,16 @@ public class JettyWebXmlConfiguration extends AbstractConfiguration
 
             if(jetty.exists())
             {
-                // No server classes while configuring 
+                // No server classes while configuring
                 String[] old_server_classes = context.getServerClasses();
                 try
                 {
                     context.setServerClasses(null);
                     if(LOG.isDebugEnabled())
                         LOG.debug("Configure: "+jetty);
-                    
+
                     XmlConfiguration jetty_config = (XmlConfiguration)context.getAttribute(XML_CONFIGURATION);
-                    
+
                     if (jetty_config==null)
                     {
                         jetty_config=new XmlConfiguration(jetty.getURL());
@@ -97,7 +96,7 @@ public class JettyWebXmlConfiguration extends AbstractConfiguration
                     {
                         context.removeAttribute(XML_CONFIGURATION);
                     }
-                    setupXmlConfiguration(context,jetty_config, web_inf);
+                    setupXmlConfiguration(jetty_config, web_inf);
                     try
                     {
                         jetty_config.configure(context);
@@ -109,7 +108,7 @@ public class JettyWebXmlConfiguration extends AbstractConfiguration
                 }
                 finally
                 {
-                    if (context.getServerClasses()==null)
+                    if (old_server_classes != null)
                         context.setServerClasses(old_server_classes);
                 }
             }
@@ -120,27 +119,12 @@ public class JettyWebXmlConfiguration extends AbstractConfiguration
      * Configures some well-known properties before the XmlConfiguration reads
      * the configuration.
      * @param jetty_config The configuration object.
-     */
-    private void setupXmlConfiguration(WebAppContext context, XmlConfiguration jetty_config, Resource web_inf)
-    {
-        setupXmlConfiguration(jetty_config,web_inf);
-    }
-    
-    /**
-     * Configures some well-known properties before the XmlConfiguration reads
-     * the configuration.
-     * @param jetty_config The configuration object.
+     * @param web_inf the WEB-INF location
      */
     private void setupXmlConfiguration(XmlConfiguration jetty_config, Resource web_inf)
     {
-    	Map<String,String> props = jetty_config.getProperties();
-    	if (props == null)
-    	{
-    		props = new HashMap<String, String>();
-    		jetty_config.setProperties(props);
-    	}
-    	
-    	// TODO - should this be an id rather than a property?
-    	props.put(PROPERTY_THIS_WEB_INF_URL, String.valueOf(web_inf.getURL()));
+        Map<String,String> props = jetty_config.getProperties();
+        // TODO - should this be an id rather than a property?
+        props.put(PROPERTY_THIS_WEB_INF_URL, String.valueOf(web_inf.getURL()));
     }
 }
diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/MetaData.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/MetaData.java
index 3c127af..166c421 100644
--- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/MetaData.java
+++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/MetaData.java
@@ -18,6 +18,7 @@
 
 package org.eclipse.jetty.webapp;
 
+import java.lang.annotation.Annotation;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
@@ -28,6 +29,7 @@ import javax.servlet.ServletContext;
 
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.EmptyResource;
 import org.eclipse.jetty.util.resource.Resource;
 
 
@@ -41,40 +43,54 @@ import org.eclipse.jetty.util.resource.Resource;
 public class MetaData
 {
     private static final Logger LOG = Log.getLogger(MetaData.class);
-        
+
     public static final String ORDERED_LIBS = "javax.servlet.context.orderedLibs";
+    public static final Resource NON_FRAG_RESOURCE = EmptyResource.INSTANCE;
 
     protected Map<String, OriginInfo> _origins  =new HashMap<String,OriginInfo>();
     protected WebDescriptor _webDefaultsRoot;
     protected WebDescriptor _webXmlRoot;
     protected final List<WebDescriptor> _webOverrideRoots=new ArrayList<WebDescriptor>();
-    protected boolean _metaDataComplete;
-    protected final List<DiscoveredAnnotation> _annotations = new ArrayList<DiscoveredAnnotation>();
+    protected boolean _metaDataComplete;  
     protected final List<DescriptorProcessor> _descriptorProcessors = new ArrayList<DescriptorProcessor>();
     protected final List<FragmentDescriptor> _webFragmentRoots = new ArrayList<FragmentDescriptor>();
     protected final Map<String,FragmentDescriptor> _webFragmentNameMap = new HashMap<String,FragmentDescriptor>();
     protected final Map<Resource, FragmentDescriptor> _webFragmentResourceMap = new HashMap<Resource, FragmentDescriptor>();
-    protected final Map<Resource, List<DiscoveredAnnotation>> _webFragmentAnnotations = new HashMap<Resource, List<DiscoveredAnnotation>>();
+    protected final Map<Resource, List<DiscoveredAnnotation>> _annotations = new HashMap<Resource, List<DiscoveredAnnotation>>();
+    protected final List<Resource> _webInfClasses = new ArrayList<Resource>();
     protected final List<Resource> _webInfJars = new ArrayList<Resource>();
-    protected final List<Resource> _orderedWebInfJars = new ArrayList<Resource>(); 
-    protected final List<Resource> _orderedContainerJars = new ArrayList<Resource>();
+    protected final List<Resource> _orderedContainerResources = new ArrayList<Resource>();
+    protected final List<Resource> _orderedWebInfResources = new ArrayList<Resource>();
     protected Ordering _ordering;//can be set to RelativeOrdering by web-default.xml, web.xml, web-override.xml
     protected boolean allowDuplicateFragmentNames = false;
-   
- 
-    
-  
+
+
+
+
 
     public static class OriginInfo
-    {   
-        protected String name;
-        protected Origin origin;
-        protected Descriptor descriptor;
+    {
+        private final String name;
+        private final Origin origin;
+        private final Descriptor descriptor;
+        private final Annotation annotation;
+        private final Class<?> annotated;
+
+        public OriginInfo (String n, Annotation a,Class<?> ac)
+        {
+            name=n;
+            origin=Origin.Annotation;
+            descriptor=null;
+            annotation=a;
+            annotated=ac;
+        }
         
         public OriginInfo (String n, Descriptor d)
         {
             name = n;
-            descriptor = d;           
+            descriptor = d;
+            annotation=null;
+            annotated=null;
             if (d == null)
                 throw new IllegalArgumentException("No descriptor");
             if (d instanceof FragmentDescriptor)
@@ -86,39 +102,45 @@ public class MetaData
             else
                 origin = Origin.WebXml;
         }
-        
-        public OriginInfo (String n)
-        {
-            name = n;
-            origin = Origin.Annotation;
-        }
-        
-        public OriginInfo(String n, Origin o)
+
+        public OriginInfo(String n)
         {
             name = n;
-            origin = o;
+            origin = Origin.API;
+            annotation=null;
+            descriptor=null;
+            annotated=null;
         }
-        
+
         public String getName()
         {
             return name;
         }
-        
+
         public Origin getOriginType()
         {
             return origin;
         }
-        
+
         public Descriptor getDescriptor()
         {
             return descriptor;
         }
+        
+        public String toString()
+        {
+            if (descriptor!=null)
+                return descriptor.toString();
+            if (annotation!=null)
+                return "@"+annotation.annotationType().getSimpleName()+" on "+annotated.getName();
+            return origin.toString();
+        }
     }
-   
+
     public MetaData ()
     {
     }
-    
+
     /**
      * Empty ready for reuse
      */
@@ -134,67 +156,73 @@ public class MetaData
         _webFragmentRoots.clear();
         _webFragmentNameMap.clear();
         _webFragmentResourceMap.clear();
-        _webFragmentAnnotations.clear();
+        _annotations.clear();
         _webInfJars.clear();
-        _orderedWebInfJars.clear();
-        _orderedContainerJars.clear();
+        _orderedWebInfResources.clear();
+        _orderedContainerResources.clear();
         _ordering = null;
         allowDuplicateFragmentNames = false;
     }
-    
+
     public void setDefaults (Resource webDefaults)
     throws Exception
     {
-        _webDefaultsRoot =  new DefaultsDescriptor(webDefaults); 
+        _webDefaultsRoot =  new DefaultsDescriptor(webDefaults);
         _webDefaultsRoot.parse();
         if (_webDefaultsRoot.isOrdered())
         {
-            if (_ordering == null)
-                _ordering = new Ordering.AbsoluteOrdering(this);
+            Ordering ordering = getOrdering();
+            if (ordering == null)
+               ordering = new Ordering.AbsoluteOrdering(this);
 
             List<String> order = _webDefaultsRoot.getOrdering();
             for (String s:order)
             {
                 if (s.equalsIgnoreCase("others"))
-                    ((Ordering.AbsoluteOrdering)_ordering).addOthers();
-                else 
-                    ((Ordering.AbsoluteOrdering)_ordering).add(s);
+                    ((Ordering.AbsoluteOrdering)ordering).addOthers();
+                else
+                    ((Ordering.AbsoluteOrdering)ordering).add(s);
             }
-        }    
+            
+            //(re)set the ordering to cause webinf jar order to be recalculated
+            setOrdering(ordering);
+        }
     }
-    
+
     public void setWebXml (Resource webXml)
     throws Exception
     {
         _webXmlRoot = new WebDescriptor(webXml);
         _webXmlRoot.parse();
         _metaDataComplete=_webXmlRoot.getMetaDataComplete() == MetaDataComplete.True;
-        
-        
-        
+
         if (_webXmlRoot.isOrdered())
         {
-            if (_ordering == null)
-                _ordering = new Ordering.AbsoluteOrdering(this);
+            Ordering ordering = getOrdering();
+            if (ordering == null)
+                ordering = new Ordering.AbsoluteOrdering(this);
 
             List<String> order = _webXmlRoot.getOrdering();
             for (String s:order)
             {
                 if (s.equalsIgnoreCase("others"))
-                    ((Ordering.AbsoluteOrdering)_ordering).addOthers();
-                else 
-                    ((Ordering.AbsoluteOrdering)_ordering).add(s);
+                    ((Ordering.AbsoluteOrdering)ordering).addOthers();
+                else
+                    ((Ordering.AbsoluteOrdering)ordering).add(s);
             }
-        }    
+            
+            //(re)set the ordering to cause webinf jar order to be recalculated
+            setOrdering(ordering);
+        }
     }
-    
+
     public void addOverride (Resource override)
     throws Exception
     {
         OverrideDescriptor webOverrideRoot = new OverrideDescriptor(override);
         webOverrideRoot.setValidating(false);
         webOverrideRoot.parse();
-        
+
         switch(webOverrideRoot.getMetaDataComplete())
         {
             case True:
@@ -206,45 +234,50 @@ public class MetaData
             case NotSet:
                 break;
         }
-        
+
         if (webOverrideRoot.isOrdered())
         {
-            if (_ordering == null)
-                _ordering = new Ordering.AbsoluteOrdering(this);
+            Ordering ordering = getOrdering();
+            
+            if (ordering == null)
+               ordering = new Ordering.AbsoluteOrdering(this);
 
             List<String> order = webOverrideRoot.getOrdering();
             for (String s:order)
             {
                 if (s.equalsIgnoreCase("others"))
-                    ((Ordering.AbsoluteOrdering)_ordering).addOthers();
-                else 
-                    ((Ordering.AbsoluteOrdering)_ordering).add(s);
+                    ((Ordering.AbsoluteOrdering)ordering).addOthers();
+                else
+                    ((Ordering.AbsoluteOrdering)ordering).add(s);
             }
-        }   
+            
+            //set or reset the ordering to cause the webinf jar ordering to be recomputed
+            setOrdering(ordering);
+        }
         _webOverrideRoots.add(webOverrideRoot);
     }
-    
-    
+
+
     /**
      * Add a web-fragment.xml
-     * 
+     *
      * @param jarResource the jar the fragment is contained in
      * @param xmlResource the resource representing the xml file
      * @throws Exception
      */
     public void addFragment (Resource jarResource, Resource xmlResource)
     throws Exception
-    { 
+    {
         if (_metaDataComplete)
             return; //do not process anything else if web.xml/web-override.xml set metadata-complete
-        
+
         //Metadata-complete is not set, or there is no web.xml
         FragmentDescriptor descriptor = new FragmentDescriptor(xmlResource);
         _webFragmentResourceMap.put(jarResource, descriptor);
         _webFragmentRoots.add(descriptor);
-        
+
         descriptor.parse();
-        
+
         if (descriptor.getName() != null)
         {
             Descriptor existing = _webFragmentNameMap.get(descriptor.getName());
@@ -256,12 +289,16 @@ public class MetaData
                 _webFragmentNameMap.put(descriptor.getName(), descriptor);
         }
 
-        //If web.xml has specified an absolute ordering, ignore any relative ordering in the fragment
-        if (_ordering != null && _ordering.isAbsolute())
+
+        //only accept an ordering from the fragment if there is no ordering already established
+        if (_ordering == null && descriptor.isOrdered())
+        {
+            setOrdering(new Ordering.RelativeOrdering(this));
             return;
+        }
         
-        if (_ordering == null && descriptor.isOrdered())
-            _ordering = new Ordering.RelativeOrdering(this);
+        //recompute the ordering with the new fragment name
+        orderFragments();
     }
 
     /**
@@ -275,79 +312,83 @@ public class MetaData
             return;
         for (DiscoveredAnnotation a:annotations)
         {
-            Resource r = a.getResource();
-            if (r == null || !_webInfJars.contains(r))
-                _annotations.add(a);
-            else 
-                addDiscoveredAnnotation(a.getResource(), a);
-                
+            addDiscoveredAnnotation(a);       
         }
     }
+
     
-    
-    public void addDiscoveredAnnotation(Resource resource, DiscoveredAnnotation annotation)
+    /**
+     * Add an annotation that has been discovered on a class, method or field within a resource
+     * eg a jar or dir.
+     * 
+     * This method is synchronized as it is anticipated that it may be called by many threads
+     * during the annotation scanning phase.
+     * 
+     * @param annotation
+     */
+    public synchronized void addDiscoveredAnnotation (DiscoveredAnnotation annotation)
     {
-        List<DiscoveredAnnotation> list = _webFragmentAnnotations.get(resource);
+        if (annotation == null)
+            return;
+        
+        //if no resource associated with an annotation or the resource is not one of the WEB-INF/lib jars,
+        //map it to empty resource
+        Resource resource = annotation.getResource();
+        if (resource == null ||  !_webInfJars.contains(resource))
+            resource = EmptyResource.INSTANCE;
+
+        List<DiscoveredAnnotation> list = _annotations.get(resource);
         if (list == null)
         {
             list = new ArrayList<DiscoveredAnnotation>();
-            _webFragmentAnnotations.put(resource, list);
+            _annotations.put(resource, list);
         }
-        list.add(annotation);
+        list.add(annotation);           
     }
-    
 
-    public void addDiscoveredAnnotations(Resource resource, List<DiscoveredAnnotation> annotations)
+
+    public void addDescriptorProcessor(DescriptorProcessor p)
     {
-        List<DiscoveredAnnotation> list = _webFragmentAnnotations.get(resource);
-        if (list == null)
-        {
-            list = new ArrayList<DiscoveredAnnotation>();
-            _webFragmentAnnotations.put(resource, list);
-        }
-            
-        list.addAll(annotations);
+        _descriptorProcessors.add(p);
     }
     
-    public void addDescriptorProcessor(DescriptorProcessor p)
+    public void removeDescriptorProcessor(DescriptorProcessor p)
     {
-        _descriptorProcessors.add(p);
+        _descriptorProcessors.remove(p);
     }
+
     
     public void orderFragments ()
     {
-        //if we have already ordered them don't do it again
-        if (_orderedWebInfJars.size()==_webInfJars.size())
-            return;
-        
-        if (_ordering != null)
-            _orderedWebInfJars.addAll(_ordering.order(_webInfJars));
-        else
-            _orderedWebInfJars.addAll(_webInfJars);
+        _orderedWebInfResources.clear();
+        if (getOrdering() != null)
+            _orderedWebInfResources.addAll(getOrdering().order(_webInfJars));
     }
-    
-    
+
+
     /**
      * Resolve all servlet/filter/listener metadata from all sources: descriptors and annotations.
-     * 
+     *
      */
     public void resolve (WebAppContext context)
     throws Exception
     {
         LOG.debug("metadata resolve {}",context);
-        
+
         //Ensure origins is fresh
         _origins.clear();
-        
+
         // Set the ordered lib attribute
-        if (_ordering != null)
+        List<Resource> orderedWebInfJars = null;
+        if (getOrdering() != null)
         {
+            orderedWebInfJars = getOrderedWebInfJars();
             List<String> orderedLibs = new ArrayList<String>();
-            for (Resource webInfJar:_orderedWebInfJars)
+            for (Resource webInfJar:orderedWebInfJars)
             {
                 //get just the name of the jar file
                 String fullname = webInfJar.getName();
-                int i = fullname.indexOf(".jar");          
+                int i = fullname.indexOf(".jar");
                 int j = fullname.lastIndexOf("/", i);
                 orderedLibs.add(fullname.substring(j+1,i+4));
             }
@@ -365,21 +406,34 @@ public class MetaData
         {
             p.process(context,getWebDefault());
             p.process(context,getWebXml());
-            for (WebDescriptor wd : getOverrideWebs())   
+            for (WebDescriptor wd : getOverrideWebs())
             {
                 LOG.debug("process {} {}",context,wd);
                 p.process(context,wd);
             }
         }
-        
-        for (DiscoveredAnnotation a:_annotations)
+
+        //get an apply the annotations that are not associated with a fragment (and hence for
+        //which no ordering applies
+        List<DiscoveredAnnotation> nonFragAnnotations = _annotations.get(NON_FRAG_RESOURCE);
+        if (nonFragAnnotations != null)
         {
-            LOG.debug("apply {}",a);
-            a.apply();
+            for (DiscoveredAnnotation a:nonFragAnnotations)
+            {
+                LOG.debug("apply {}",a);
+                a.apply();
+            }
         }
-    
+      
+        //apply the annotations that are associated with a fragment, according to the 
+        //established ordering
+        List<Resource> resources = null;
+        
+        if (getOrdering() != null)
+            resources = orderedWebInfJars;
+        else
+            resources = getWebInfJars();
         
-        List<Resource> resources = getOrderedWebInfJars();
         for (Resource r:resources)
         {
             FragmentDescriptor fd = _webFragmentResourceMap.get(r);
@@ -391,8 +445,8 @@ public class MetaData
                     p.process(context,fd);
                 }
             }
-            
-            List<DiscoveredAnnotation> fragAnnotations = _webFragmentAnnotations.get(r);
+
+            List<DiscoveredAnnotation> fragAnnotations = _annotations.get(r);
             if (fragAnnotations != null)
             {
                 for (DiscoveredAnnotation a:fragAnnotations)
@@ -402,61 +456,64 @@ public class MetaData
                 }
             }
         }
-        
+
     }
-    
+
     public boolean isDistributable ()
     {
         boolean distributable = (
-                (_webDefaultsRoot != null && _webDefaultsRoot.isDistributable()) 
+                (_webDefaultsRoot != null && _webDefaultsRoot.isDistributable())
                 || (_webXmlRoot != null && _webXmlRoot.isDistributable()));
-        
+
         for (WebDescriptor d : _webOverrideRoots)
             distributable&=d.isDistributable();
-        
-        List<Resource> orderedResources = getOrderedWebInfJars();
-        for (Resource r: orderedResources)
-        {  
-            FragmentDescriptor d = _webFragmentResourceMap.get(r);
-            if (d!=null)
-                distributable = distributable && d.isDistributable();
+
+        if (getOrdering() != null)
+        {
+            List<Resource> orderedResources = getOrderedWebInfJars();
+            for (Resource r: orderedResources)
+            {
+                FragmentDescriptor d = _webFragmentResourceMap.get(r);
+                if (d!=null)
+                    distributable = distributable && d.isDistributable();
+            }
         }
         return distributable;
     }
-   
-    
+
+
     public WebDescriptor getWebXml ()
     {
         return _webXmlRoot;
     }
-    
+
     public List<WebDescriptor> getOverrideWebs ()
     {
         return _webOverrideRoots;
     }
-    
+
     public WebDescriptor getWebDefault ()
     {
         return _webDefaultsRoot;
     }
-    
+
     public List<FragmentDescriptor> getFragments ()
     {
         return _webFragmentRoots;
     }
-    
+
     public List<Resource> getOrderedWebInfJars()
     {
-        return _orderedWebInfJars == null? new ArrayList<Resource>(): _orderedWebInfJars;
+        return _orderedWebInfResources;
     }
-    
+
     public List<FragmentDescriptor> getOrderedFragments ()
     {
         List<FragmentDescriptor> list = new ArrayList<FragmentDescriptor>();
-        if (_orderedWebInfJars == null)
+        if (getOrdering() == null)
             return list;
 
-        for (Resource r:_orderedWebInfJars)
+        for (Resource r:getOrderedWebInfJars())
         {
             FragmentDescriptor fd = _webFragmentResourceMap.get(r);
             if (fd != null)
@@ -464,33 +521,34 @@ public class MetaData
         }
         return list;
     }
-    
+
     public Ordering getOrdering()
     {
         return _ordering;
     }
-    
+
     public void setOrdering (Ordering o)
-    {
+    {    
         _ordering = o;
+        orderFragments();
     }
-    
+
     public FragmentDescriptor getFragment (Resource jar)
     {
         return _webFragmentResourceMap.get(jar);
     }
-    
+
     public FragmentDescriptor getFragment(String name)
     {
         return _webFragmentNameMap.get(name);
     }
-    
+
     public Resource getJarForFragment (String name)
     {
         FragmentDescriptor f = getFragment(name);
         if (f == null)
             return null;
-        
+
         Resource jar = null;
         for (Resource r: _webFragmentResourceMap.keySet())
         {
@@ -499,23 +557,31 @@ public class MetaData
         }
         return jar;
     }
-    
+
     public Map<String,FragmentDescriptor> getNamedFragments ()
     {
         return Collections.unmodifiableMap(_webFragmentNameMap);
     }
-    
-    
+
+
     public Origin getOrigin (String name)
     {
         OriginInfo x =  _origins.get(name);
         if (x == null)
             return Origin.NotSet;
-        
+
         return x.getOriginType();
     }
-  
- 
+
+    public OriginInfo getOriginInfo (String name)
+    {
+        OriginInfo x =  _origins.get(name);
+        if (x == null)
+            return null;
+
+        return x;
+    }
+
     public Descriptor getOriginDescriptor (String name)
     {
         OriginInfo o = _origins.get(name);
@@ -523,28 +589,28 @@ public class MetaData
             return null;
         return o.getDescriptor();
     }
-    
+
     public void setOrigin (String name, Descriptor d)
     {
         OriginInfo x = new OriginInfo (name, d);
         _origins.put(name, x);
     }
-    
-    public void setOrigin (String name)
+
+    public void setOrigin (String name, Annotation annotation, Class<?> annotated)
     {
         if (name == null)
             return;
-       
-        OriginInfo x = new OriginInfo (name, Origin.Annotation);
+
+        OriginInfo x = new OriginInfo (name, annotation, annotated);
         _origins.put(name, x);
     }
     
-    public void setOrigin(String name, Origin origin)
+    public void setOriginAPI(String name)
     {
         if (name == null)
             return;
        
-        OriginInfo x = new OriginInfo (name, origin);
+        OriginInfo x = new OriginInfo (name);
         _origins.put(name, x);
     }
 
@@ -553,7 +619,7 @@ public class MetaData
         return _metaDataComplete;
     }
 
-    
+
     public void addWebInfJar(Resource newResource)
     {
         _webInfJars.add(newResource);
@@ -563,16 +629,27 @@ public class MetaData
     {
         return Collections.unmodifiableList(_webInfJars);
     }
+
+    public List<Resource> getContainerResources()
+    {
+        return _orderedContainerResources;
+    }
+
+    public void addContainerResource(Resource jar)
+    {
+        _orderedContainerResources.add(jar);
+    }
     
-    public List<Resource> getOrderedContainerJars()
+    public void setWebInfClassesDirs (List<Resource> dirs)
     {
-        return _orderedContainerJars;
+        _webInfClasses.addAll(dirs);
     }
     
-    public void addContainerJar(Resource jar)
+    public List<Resource> getWebInfClassesDirs ()
     {
-        _orderedContainerJars.add(jar);
+        return _webInfClasses;
     }
+    
     public boolean isAllowDuplicateFragmentNames()
     {
         return allowDuplicateFragmentNames;
@@ -582,4 +659,9 @@ public class MetaData
     {
         this.allowDuplicateFragmentNames = allowDuplicateFragmentNames;
     }
+
+    public Map<String,OriginInfo> getOrigins()
+    {
+        return Collections.unmodifiableMap(_origins);
+    }
 }
diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/MetaInfConfiguration.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/MetaInfConfiguration.java
index 0ba9263..0c0d80e 100644
--- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/MetaInfConfiguration.java
+++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/MetaInfConfiguration.java
@@ -20,133 +20,335 @@ package org.eclipse.jetty.webapp;
 
 
 import java.net.URI;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Locale;
-import java.util.jar.JarEntry;
+import java.net.URL;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.EmptyResource;
 import org.eclipse.jetty.util.resource.Resource;
 
 /**
  * MetaInfConfiguration
  *
- * Scan META-INF of all jars in WEB-INF/lib to find:
+ * Scan META-INF of jars to find:
  * <ul>
  * <li>tlds
  * <li>web-fragment.xml
  * <li>resources
  * </ul>
+ * 
+ * The jars which are scanned are:
+ * <ol>
+ * <li>those from the container classpath whose pattern matched the WebInfConfiguration.CONTAINER_JAR_PATTERN</li>
+ * <li>those from WEB-INF/lib</li>
+ * </ol>
  */
 public class MetaInfConfiguration extends AbstractConfiguration
 {
     private static final Logger LOG = Log.getLogger(MetaInfConfiguration.class);
 
-    public static final String METAINF_TLDS = TagLibConfiguration.TLD_RESOURCES;
+    public static final String USE_CONTAINER_METAINF_CACHE = "org.eclipse.jetty.metainf.useCache";
+    public static final boolean DEFAULT_USE_CONTAINER_METAINF_CACHE = true;
+    public static final String CACHED_CONTAINER_TLDS = "org.eclipse.jetty.tlds.cache";
+    public static final String CACHED_CONTAINER_FRAGMENTS = FragmentConfiguration.FRAGMENT_RESOURCES+".cache";
+    public static final String CACHED_CONTAINER_RESOURCES = WebInfConfiguration.RESOURCE_DIRS+".cache";
+    public static final String METAINF_TLDS = "org.eclipse.jetty.tlds";
     public static final String METAINF_FRAGMENTS = FragmentConfiguration.FRAGMENT_RESOURCES;
-    public static final String METAINF_RESOURCES = WebInfConfiguration.RESOURCE_URLS;
-  
+    public static final String METAINF_RESOURCES = WebInfConfiguration.RESOURCE_DIRS;
+
     @Override
     public void preConfigure(final WebAppContext context) throws Exception
-    {
-       //Merge all container and webinf lib jars to look for META-INF resources
-      
-        ArrayList<Resource> jars = new ArrayList<Resource>();
-        jars.addAll(context.getMetaData().getOrderedContainerJars());
-        jars.addAll(context.getMetaData().getWebInfJars());
+    {        
+        boolean useContainerCache = DEFAULT_USE_CONTAINER_METAINF_CACHE;
+        Boolean attr = (Boolean)context.getServer().getAttribute(USE_CONTAINER_METAINF_CACHE);
+        if (attr != null)
+            useContainerCache = attr.booleanValue();
+        
+        if (LOG.isDebugEnabled()) LOG.debug("{} = {}", USE_CONTAINER_METAINF_CACHE, useContainerCache);
         
-        JarScanner scanner = new JarScanner()
+        //pre-emptively create empty lists for tlds, fragments and resources as context attributes
+        //this signals that this class has been called. This differentiates the case where this class
+        //has been called but finds no META-INF data from the case where this class was never called
+        if (context.getAttribute(METAINF_TLDS) == null)
+            context.setAttribute(METAINF_TLDS, new HashSet<URL>());
+        if (context.getAttribute(METAINF_RESOURCES) == null)
+            context.setAttribute(METAINF_RESOURCES, new HashSet<Resource>());
+        if (context.getAttribute(METAINF_FRAGMENTS) == null)
+            context.setAttribute(METAINF_FRAGMENTS, new HashMap<Resource, Resource>());
+       
+        scanJars(context, context.getMetaData().getContainerResources(), useContainerCache);
+        scanJars(context, context.getMetaData().getWebInfJars(), false);
+    }
+
+    /**
+     * Look into the jars to discover info in META-INF. If useCaches == true, then we will
+     * cache the info discovered indexed by the jar in which it was discovered: this speeds
+     * up subsequent context deployments.
+     * 
+     * @param context
+     * @param jars
+     * @param useCaches
+     * @throws Exception
+     */
+    public void scanJars (final WebAppContext context, Collection<Resource> jars, boolean useCaches)
+    throws Exception
+    {
+        ConcurrentHashMap<Resource, Resource> metaInfResourceCache = null;       
+        ConcurrentHashMap<Resource, Resource> metaInfFragmentCache = null;
+        ConcurrentHashMap<Resource, Collection<URL>> metaInfTldCache = null;
+        if (useCaches)
         {
-            public void processEntry(URI jarUri, JarEntry entry)
+            metaInfResourceCache = (ConcurrentHashMap<Resource, Resource>)context.getServer().getAttribute(CACHED_CONTAINER_RESOURCES);
+            if (metaInfResourceCache == null)
             {
-                try
-                {
-                    MetaInfConfiguration.this.processEntry(context,jarUri,entry);
-                }
-                catch (Exception e)
-                {
-                    LOG.warn("Problem processing jar entry " + entry, e);
-                }
+                metaInfResourceCache = new ConcurrentHashMap<Resource,Resource>();
+                context.getServer().setAttribute(CACHED_CONTAINER_RESOURCES, metaInfResourceCache);
             }
-        };
-        
+            metaInfFragmentCache = (ConcurrentHashMap<Resource, Resource>)context.getServer().getAttribute(CACHED_CONTAINER_FRAGMENTS);
+            if (metaInfFragmentCache == null)
+            {
+                metaInfFragmentCache = new ConcurrentHashMap<Resource,Resource>();
+                context.getServer().setAttribute(CACHED_CONTAINER_FRAGMENTS, metaInfFragmentCache);
+            }
+            metaInfTldCache = (ConcurrentHashMap<Resource, Collection<URL>>)context.getServer().getAttribute(CACHED_CONTAINER_TLDS);
+            if (metaInfTldCache == null)
+            {
+                metaInfTldCache = new ConcurrentHashMap<Resource,Collection<URL>>(); 
+                context.getServer().setAttribute(CACHED_CONTAINER_TLDS, metaInfTldCache);
+            }
+        }
         
         //Scan jars for META-INF information
         if (jars != null)
         {
-            URI[] uris = new URI[jars.size()];
-            int i=0;
             for (Resource r : jars)
             {
-                uris[i++] = r.getURI();
+                
+               scanForResources(context, r, metaInfResourceCache);
+               scanForFragment(context, r, metaInfFragmentCache);
+               scanForTlds(context, r, metaInfTldCache);
             }
-            scanner.scan(null, uris, true);
         }
     }
-    @Override
-    public void configure(WebAppContext context) throws Exception
+    
+    /**
+     * Scan for META-INF/resources dir in the given jar.
+     * 
+     * @param context
+     * @param target
+     * @param cache
+     * @throws Exception
+     */
+    public void scanForResources (WebAppContext context, Resource target, ConcurrentHashMap<Resource,Resource> cache)
+    throws Exception
     {
-        
-    }
+        Resource resourcesDir = null;
+        if (cache != null && cache.containsKey(target))
+        {
+            resourcesDir = cache.get(target);  
+            if (resourcesDir == EmptyResource.INSTANCE)
+            {
+                if (LOG.isDebugEnabled()) LOG.debug(target+" cached as containing no META-INF/resources");
+                return;    
+            }
+            else
+                if (LOG.isDebugEnabled()) LOG.debug(target+" META-INF/resources found in cache ");
+        }
+        else
+        {
+            //not using caches or not in the cache so check for the resources dir
+            if (LOG.isDebugEnabled()) LOG.debug(target+" META-INF/resources checked");
+            if (target.isDirectory())
+            {
+                //TODO think  how to handle an unpacked jar file (eg for osgi)
+                resourcesDir = target.addPath("/META-INF/resources");
+            }
+            else
+            {
+                //Resource represents a packed jar
+                URI uri = target.getURI();
+                resourcesDir = Resource.newResource("jar:"+uri+"!/META-INF/resources");
+            }
+            if (!resourcesDir.exists() || !resourcesDir.isDirectory())
+                resourcesDir = EmptyResource.INSTANCE;
 
-    @Override
-    public void deconfigure(WebAppContext context) throws Exception
-    {
- 
-    }
+            if (cache != null)
+            {               
+                Resource old  = cache.putIfAbsent(target, resourcesDir);
+                if (old != null)
+                    resourcesDir = old;
+                else
+                    if (LOG.isDebugEnabled()) LOG.debug(target+" META-INF/resources cache updated");
+            }
 
-    @Override
-    public void postConfigure(WebAppContext context) throws Exception
-    {
-        context.setAttribute(METAINF_FRAGMENTS, null); 
-        context.setAttribute(METAINF_RESOURCES, null);
-        context.setAttribute(METAINF_TLDS, null);
-    }
+            if (resourcesDir == EmptyResource.INSTANCE)
+                return;
+        }
 
-    public void addResource (WebAppContext context, String attribute, Resource jar)
+        //add it to the meta inf resources for this context
+        Set<Resource> dirs = (Set<Resource>)context.getAttribute(METAINF_RESOURCES);
+        if (dirs == null)
+        {
+            dirs = new HashSet<Resource>();
+            context.setAttribute(METAINF_RESOURCES, dirs);
+        }
+        if (LOG.isDebugEnabled()) LOG.debug(resourcesDir+" added to context");
+        dirs.add(resourcesDir);
+    }
+    
+    /**
+     * Scan for META-INF/web-fragment.xml file in the given jar.
+     * 
+     * @param context
+     * @param jar
+     * @param cache
+     * @throws Exception
+     */
+    public void scanForFragment (WebAppContext context, Resource jar, ConcurrentHashMap<Resource,Resource> cache)
+    throws Exception
     {
-        @SuppressWarnings("unchecked")
-        List<Resource> list = (List<Resource>)context.getAttribute(attribute);
-        if (list==null)
+        Resource webFrag = null;
+        if (cache != null && cache.containsKey(jar))
+        {
+            webFrag = cache.get(jar);  
+            if (webFrag == EmptyResource.INSTANCE)
+            {
+                if (LOG.isDebugEnabled()) LOG.debug(jar+" cached as containing no META-INF/web-fragment.xml");
+                return;     
+            }
+            else
+                if (LOG.isDebugEnabled()) LOG.debug(jar+" META-INF/web-fragment.xml found in cache ");
+        }
+        else
+        {
+            //not using caches or not in the cache so check for the web-fragment.xml
+            if (LOG.isDebugEnabled()) LOG.debug(jar+" META-INF/web-fragment.xml checked");
+            if (jar.isDirectory())
+            {
+                //TODO   ????
+                webFrag = jar.addPath("/META-INF/web-fragment.xml");
+            }
+            else
+            {
+                URI uri = jar.getURI();
+                webFrag = Resource.newResource("jar:"+uri+"!/META-INF/web-fragment.xml");
+            }
+            if (!webFrag.exists() || webFrag.isDirectory())
+                webFrag = EmptyResource.INSTANCE;
+            
+            if (cache != null)
+            {
+                //web-fragment.xml doesn't exist: put token in cache to signal we've seen the jar               
+                Resource old = cache.putIfAbsent(jar, webFrag);
+                if (old != null)
+                    webFrag = old;
+                else
+                    if (LOG.isDebugEnabled()) LOG.debug(jar+" META-INF/web-fragment.xml cache updated");
+            }
+            
+            if (webFrag == EmptyResource.INSTANCE)
+                return;
+        }
+
+        Map<Resource, Resource> fragments = (Map<Resource,Resource>)context.getAttribute(METAINF_FRAGMENTS);
+        if (fragments == null)
         {
-            list=new ArrayList<Resource>();
-            context.setAttribute(attribute,list);
+            fragments = new HashMap<Resource, Resource>();
+            context.setAttribute(METAINF_FRAGMENTS, fragments);
         }
-        if (!list.contains(jar))
-            list.add(jar);
+        fragments.put(jar, webFrag);   
+        if (LOG.isDebugEnabled()) LOG.debug(webFrag+" added to context");
     }
     
     
-    protected void processEntry(WebAppContext context, URI jarUri, JarEntry entry)
+    /**
+     * Discover META-INF/*.tld files in the given jar
+     * 
+     * @param context
+     * @param jar
+     * @param cache
+     * @throws Exception
+     */
+    public void scanForTlds (WebAppContext context, Resource jar, ConcurrentHashMap<Resource, Collection<URL>> cache)
+    throws Exception
     {
-        String name = entry.getName();
-
-        if (!name.startsWith("META-INF/"))
-            return;
+        Collection<URL> tlds = null;
         
-        try
+        if (cache != null && cache.containsKey(jar))
         {
-            if (name.equals("META-INF/web-fragment.xml") && context.isConfigurationDiscovered())
+            Collection<URL> tmp = cache.get(jar);
+            if (tmp.isEmpty())
+            {
+                if (LOG.isDebugEnabled()) LOG.debug(jar+" cached as containing no tlds");
+                return;
+            }
+            else
             {
-                addResource(context,METAINF_FRAGMENTS,Resource.newResource(jarUri));     
+                tlds = tmp;
+                if (LOG.isDebugEnabled()) LOG.debug(jar+" tlds found in cache ");
             }
-            else if (name.equals("META-INF/resources/") && context.isConfigurationDiscovered())
+        }
+        else
+        {
+            //not using caches or not in the cache so find all tlds
+            Resource metaInfDir = null;
+            if (jar.isDirectory())
             {
-                addResource(context,METAINF_RESOURCES,Resource.newResource("jar:"+jarUri+"!/META-INF/resources"));
+                //TODO ??????
+                metaInfDir = jar.addPath("/META-INF/");
             }
             else
             {
-                String lcname = name.toLowerCase(Locale.ENGLISH);
-                if (lcname.endsWith(".tld"))
+                URI uri = jar.getURI();
+                metaInfDir = Resource.newResource("jar:"+uri+"!/META-INF/");
+            }
+
+            //find any *.tld files inside META-INF or subdirs
+            tlds = new HashSet<URL>();      
+            Collection<Resource> resources = metaInfDir.getAllResources();
+            for (Resource t:resources)
+            {
+                String name = t.toString();
+                if (name.endsWith(".tld"))
                 {
-                    addResource(context,METAINF_TLDS,Resource.newResource("jar:"+jarUri+"!/"+name));
+                    if (LOG.isDebugEnabled()) LOG.debug(t+" tld discovered");
+                    tlds.add(t.getURL());
                 }
             }
+            if (cache != null)
+            {  
+                if (LOG.isDebugEnabled()) LOG.debug(jar+" tld cache updated");
+                Collection<URL> old = (Collection<URL>)cache.putIfAbsent(jar, tlds);
+                if (old != null)
+                    tlds = old;
+            }
+            
+            if (tlds.isEmpty())
+                return;
         }
-        catch(Exception e)
+
+        Collection<URL> tld_resources=(Collection<URL>)context.getAttribute(METAINF_TLDS);
+        if (tld_resources == null)
         {
-            context.getServletContext().log(jarUri+"!/"+name,e);
+            tld_resources = new HashSet<URL>();
+            context.setAttribute(METAINF_TLDS, tld_resources);
         }
+        tld_resources.addAll(tlds);  
+        if (LOG.isDebugEnabled()) LOG.debug("tlds added to context");
+    }
+    
+   
+    @Override
+    public void postConfigure(WebAppContext context) throws Exception
+    {
+        context.setAttribute(METAINF_FRAGMENTS, null); 
+        context.setAttribute(METAINF_RESOURCES, null);
+        context.setAttribute(METAINF_TLDS, null);
     }
 }
diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/StandardDescriptorProcessor.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/StandardDescriptorProcessor.java
index b405822..6c42b7e 100644
--- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/StandardDescriptorProcessor.java
+++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/StandardDescriptorProcessor.java
@@ -18,17 +18,15 @@
 
 package org.eclipse.jetty.webapp;
 
-import java.io.File;
-import java.io.IOException;
-import java.net.URL;
-import java.net.URLClassLoader;
 import java.util.ArrayList;
-import java.util.Collection;
+import java.util.Arrays;
 import java.util.EnumSet;
 import java.util.EventListener;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
+import java.util.ListIterator;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
@@ -36,34 +34,28 @@ import java.util.Set;
 import javax.servlet.DispatcherType;
 import javax.servlet.MultipartConfigElement;
 import javax.servlet.ServletException;
-import javax.servlet.ServletRegistration;
 import javax.servlet.SessionTrackingMode;
-import javax.servlet.descriptor.JspConfigDescriptor;
-import javax.servlet.descriptor.JspPropertyGroupDescriptor;
-import javax.servlet.descriptor.TaglibDescriptor;
 
 import org.eclipse.jetty.security.ConstraintAware;
 import org.eclipse.jetty.security.ConstraintMapping;
 import org.eclipse.jetty.security.authentication.FormAuthenticator;
+import org.eclipse.jetty.servlet.BaseHolder.Source;
 import org.eclipse.jetty.servlet.ErrorPageErrorHandler;
 import org.eclipse.jetty.servlet.FilterHolder;
 import org.eclipse.jetty.servlet.FilterMapping;
-import org.eclipse.jetty.servlet.Holder;
-import org.eclipse.jetty.servlet.JspPropertyGroupServlet;
-import org.eclipse.jetty.servlet.ServletContextHandler;
-import org.eclipse.jetty.servlet.ServletHandler;
+import org.eclipse.jetty.servlet.ListenerHolder;
 import org.eclipse.jetty.servlet.ServletContextHandler.JspConfig;
 import org.eclipse.jetty.servlet.ServletContextHandler.JspPropertyGroup;
 import org.eclipse.jetty.servlet.ServletContextHandler.TagLib;
 import org.eclipse.jetty.servlet.ServletHolder;
 import org.eclipse.jetty.servlet.ServletMapping;
-import org.eclipse.jetty.util.LazyList;
+import org.eclipse.jetty.util.ArrayUtil;
 import org.eclipse.jetty.util.Loader;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.util.resource.Resource;
 import org.eclipse.jetty.util.security.Constraint;
 import org.eclipse.jetty.xml.XmlParser;
+import org.eclipse.jetty.xml.XmlParser.Node;
 
 /**
  * StandardDescriptorProcessor
@@ -75,32 +67,37 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
     private static final Logger LOG = Log.getLogger(StandardDescriptorProcessor.class);
 
     public static final String STANDARD_PROCESSOR = "org.eclipse.jetty.standardDescriptorProcessor";
-    
-    
-    
+
+    final Map<String,FilterHolder> _filterHolderMap = new HashMap<>();
+    final List<FilterHolder> _filterHolders = new ArrayList<>();
+    final List<FilterMapping> _filterMappings = new ArrayList<>();
+    final Map<String,ServletHolder> _servletHolderMap = new HashMap<>();
+    final List<ServletHolder> _servletHolders = new ArrayList<>();
+    final List<ServletMapping> _servletMappings = new ArrayList<>();
+
     public StandardDescriptorProcessor ()
     {
- 
         try
         {
-            registerVisitor("context-param", this.getClass().getDeclaredMethod("visitContextParam", __signature));
-            registerVisitor("display-name", this.getClass().getDeclaredMethod("visitDisplayName", __signature));
-            registerVisitor("servlet", this.getClass().getDeclaredMethod("visitServlet",  __signature));
-            registerVisitor("servlet-mapping", this.getClass().getDeclaredMethod("visitServletMapping",  __signature));
-            registerVisitor("session-config", this.getClass().getDeclaredMethod("visitSessionConfig",  __signature));
-            registerVisitor("mime-mapping", this.getClass().getDeclaredMethod("visitMimeMapping",  __signature)); 
-            registerVisitor("welcome-file-list", this.getClass().getDeclaredMethod("visitWelcomeFileList",  __signature));
-            registerVisitor("locale-encoding-mapping-list", this.getClass().getDeclaredMethod("visitLocaleEncodingList",  __signature));
-            registerVisitor("error-page", this.getClass().getDeclaredMethod("visitErrorPage",  __signature));
-            registerVisitor("taglib", this.getClass().getDeclaredMethod("visitTagLib",  __signature));
-            registerVisitor("jsp-config", this.getClass().getDeclaredMethod("visitJspConfig",  __signature));
-            registerVisitor("security-constraint", this.getClass().getDeclaredMethod("visitSecurityConstraint",  __signature));
-            registerVisitor("login-config", this.getClass().getDeclaredMethod("visitLoginConfig",  __signature));
-            registerVisitor("security-role", this.getClass().getDeclaredMethod("visitSecurityRole",  __signature));
-            registerVisitor("filter", this.getClass().getDeclaredMethod("visitFilter",  __signature));
-            registerVisitor("filter-mapping", this.getClass().getDeclaredMethod("visitFilterMapping",  __signature));
-            registerVisitor("listener", this.getClass().getDeclaredMethod("visitListener",  __signature));
-            registerVisitor("distributable", this.getClass().getDeclaredMethod("visitDistributable",  __signature));
+            registerVisitor("context-param", this.getClass().getMethod("visitContextParam", __signature));
+            registerVisitor("display-name", this.getClass().getMethod("visitDisplayName", __signature));
+            registerVisitor("servlet", this.getClass().getMethod("visitServlet",  __signature));
+            registerVisitor("servlet-mapping", this.getClass().getMethod("visitServletMapping",  __signature));
+            registerVisitor("session-config", this.getClass().getMethod("visitSessionConfig",  __signature));
+            registerVisitor("mime-mapping", this.getClass().getMethod("visitMimeMapping",  __signature));
+            registerVisitor("welcome-file-list", this.getClass().getMethod("visitWelcomeFileList",  __signature));
+            registerVisitor("locale-encoding-mapping-list", this.getClass().getMethod("visitLocaleEncodingList",  __signature));
+            registerVisitor("error-page", this.getClass().getMethod("visitErrorPage",  __signature));
+            registerVisitor("taglib", this.getClass().getMethod("visitTagLib",  __signature));
+            registerVisitor("jsp-config", this.getClass().getMethod("visitJspConfig",  __signature));
+            registerVisitor("security-constraint", this.getClass().getMethod("visitSecurityConstraint",  __signature));
+            registerVisitor("login-config", this.getClass().getMethod("visitLoginConfig",  __signature));
+            registerVisitor("security-role", this.getClass().getMethod("visitSecurityRole",  __signature));
+            registerVisitor("filter", this.getClass().getMethod("visitFilter",  __signature));
+            registerVisitor("filter-mapping", this.getClass().getMethod("visitFilterMapping",  __signature));
+            registerVisitor("listener", this.getClass().getMethod("visitListener",  __signature));
+            registerVisitor("distributable", this.getClass().getMethod("visitDistributable",  __signature));
+            registerVisitor("deny-uncovered-http-methods", this.getClass().getMethod("visitDenyUncoveredHttpMethods", __signature));
         }
         catch (Exception e)
         {
@@ -108,24 +105,49 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
         }
     }
 
-    
-    
+
+
     /**
      * {@inheritDoc}
      */
     public void start(WebAppContext context, Descriptor descriptor)
-    { 
+    {
+        for (FilterHolder h : context.getServletHandler().getFilters())
+        {
+            _filterHolderMap.put(h.getName(),h);
+            _filterHolders.add(h);
+        }
+        if (context.getServletHandler().getFilterMappings()!=null)
+            _filterMappings.addAll(Arrays.asList(context.getServletHandler().getFilterMappings()));
+        for (ServletHolder h : context.getServletHandler().getServlets())
+        {
+            _servletHolderMap.put(h.getName(),h);
+            _servletHolders.add(h);
+        }
+        if (context.getServletHandler().getServletMappings()!=null)
+            _servletMappings.addAll(Arrays.asList(context.getServletHandler().getServletMappings()));
     }
-    
-    
-    
-    /** 
+
+
+    /**
      * {@inheritDoc}
      */
     public void end(WebAppContext context, Descriptor descriptor)
     {
+        context.getServletHandler().setFilters(_filterHolders.toArray(new FilterHolder[_filterHolderMap.size()]));
+        context.getServletHandler().setServlets(_servletHolders.toArray(new ServletHolder[_servletHolderMap.size()]));
+
+        context.getServletHandler().setFilterMappings(_filterMappings.toArray(new FilterMapping[_filterMappings.size()]));
+        context.getServletHandler().setServletMappings(_servletMappings.toArray(new ServletMapping[_servletMappings.size()]));
+
+        _filterHolderMap.clear();
+        _filterHolders.clear();
+        _filterMappings.clear();
+        _servletHolderMap.clear();
+        _servletHolders.clear();
+        _servletMappings.clear();
     }
-    
+
     /**
      * @param context
      * @param descriptor
@@ -135,8 +157,7 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
     {
         String name = node.getString("param-name", false, true);
         String value = node.getString("param-value", false, true);
-        Origin o = context.getMetaData().getOrigin("context-param."+name);
-        switch (o)
+        switch (context.getMetaData().getOrigin("context-param."+name))
         {
             case NotSet:
             {
@@ -153,7 +174,7 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
                 if (!(descriptor instanceof FragmentDescriptor))
                 {
                     context.getInitParams().put(name, value);
-                    context.getMetaData().setOrigin("context-param."+name, descriptor); 
+                    context.getMetaData().setOrigin("context-param."+name, descriptor);
                 }
                 break;
             }
@@ -167,12 +188,14 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
                 }
                 break;
             }
+            default:
+                LOG.warn(new Throwable()); // TODO throw ISE?
         }
-        if (LOG.isDebugEnabled()) 
+        if (LOG.isDebugEnabled())
             LOG.debug("ContextParam: " + name + "=" + value);
 
     }
-    
+
 
     /* ------------------------------------------------------------ */
     /**
@@ -180,7 +203,7 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
      * @param descriptor
      * @param node
      */
-    protected void visitDisplayName(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
+    public void visitDisplayName(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
     {
         //Servlet Spec 3.0 p. 74 Ignore from web-fragments
         if (!(descriptor instanceof FragmentDescriptor))
@@ -189,117 +212,105 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
             context.getMetaData().setOrigin("display-name", descriptor);
         }
     }
-    
-    
+
+
     /**
      * @param context
      * @param descriptor
      * @param node
      */
-    protected void visitServlet(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
+    public void visitServlet(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
     {
         String id = node.getAttribute("id");
 
         // initialize holder
-        String servlet_name = node.getString("servlet-name", false, true);
-        ServletHolder holder = context.getServletHandler().getServlet(servlet_name);
-          
-        /*
-         * If servlet of that name does not already exist, create it.
-         */
+        String name = node.getString("servlet-name", false, true);
+        ServletHolder holder = _servletHolderMap.get(name);
+
+        //If servlet of that name does not already exist, create it.
         if (holder == null)
         {
-            holder = context.getServletHandler().newServletHolder(Holder.Source.DESCRIPTOR);
-            holder.setName(servlet_name);
-            context.getServletHandler().addServlet(holder);
+            holder = context.getServletHandler().newServletHolder(Source.DESCRIPTOR);
+            holder.setName(name);
+            _servletHolderMap.put(name,holder);
+            _servletHolders.add(holder);
         }
 
-        // init params  
+        // init params
         Iterator<?> iParamsIter = node.iterator("init-param");
         while (iParamsIter.hasNext())
         {
             XmlParser.Node paramNode = (XmlParser.Node) iParamsIter.next();
             String pname = paramNode.getString("param-name", false, true);
             String pvalue = paramNode.getString("param-value", false, true);
-            
-            Origin origin = context.getMetaData().getOrigin(servlet_name+".servlet.init-param."+pname);
-            
-            switch (origin)
+            String originName = name+".servlet.init-param."+pname;
+
+            Descriptor originDescriptor = context.getMetaData().getOriginDescriptor(originName);
+            switch (context.getMetaData().getOrigin(originName))
             {
                 case NotSet:
                 {
                     //init-param not already set, so set it
-                    
-                    holder.setInitParameter(pname, pvalue); 
-                    context.getMetaData().setOrigin(servlet_name+".servlet.init-param."+pname, descriptor);
+                    holder.setInitParameter(pname, pvalue);
+                    context.getMetaData().setOrigin(originName, descriptor);
                     break;
                 }
                 case WebXml:
                 case WebDefaults:
                 case WebOverride:
                 {
-                    //previously set by a web xml descriptor, if we're parsing another web xml descriptor allow override
+                    //previously set by a web xml descriptor, if we're parsing another web xml descriptor allow override as long as it is from a different descriptor
+                    //ie ignore setting more than once within the same descriptor
                     //otherwise just ignore it
-                    if (!(descriptor instanceof FragmentDescriptor))
+                    if (!(descriptor instanceof FragmentDescriptor) && (descriptor!=originDescriptor))
                     {
-                        holder.setInitParameter(pname, pvalue); 
-                        context.getMetaData().setOrigin(servlet_name+".servlet.init-param."+pname, descriptor);
+                        holder.setInitParameter(pname, pvalue);
+                        context.getMetaData().setOrigin(originName, descriptor);
                     }
                     break;
                 }
                 case WebFragment:
                 {
                     //previously set by a web-fragment, make sure that the value matches, otherwise its an error
-                    if (!holder.getInitParameter(pname).equals(pvalue))
+                    if ((descriptor != originDescriptor) && !holder.getInitParameter(pname).equals(pvalue))
                         throw new IllegalStateException("Mismatching init-param "+pname+"="+pvalue+" in "+descriptor.getResource());
                     break;
                 }
-            }  
+                default:
+                    LOG.warn(new Throwable()); // TODO throw ISE?
+            }
         }
 
         String servlet_class = node.getString("servlet-class", false, true);
-
-        // Handle JSP
-        String jspServletClass=null;;
+        if ("".equals(servlet_class))
+            servlet_class = null;
 
         //Handle the default jsp servlet instance
-        if (id != null && id.equals("jsp"))
+        if (id != null && id.equals("jsp") && servlet_class != null)
         {
-            jspServletClass = servlet_class;
             try
             {
                 Loader.loadClass(this.getClass(), servlet_class);
-                
-                //Ensure there is a scratch dir
-                if (holder.getInitParameter("scratchdir") == null)
-                {
-                    File tmp = context.getTempDirectory();
-                    File scratch = new File(tmp, "jsp");
-                    if (!scratch.exists()) scratch.mkdir();
-                    holder.setInitParameter("scratchdir", scratch.getAbsolutePath());
-                }
             }
             catch (ClassNotFoundException e)
             {
                 LOG.info("NO JSP Support for {}, did not find {}", context.getContextPath(), servlet_class);
-                jspServletClass = servlet_class = "org.eclipse.jetty.servlet.NoJspServlet";
+                servlet_class = "org.eclipse.jetty.servlet.NoJspServlet";
             }
         }
-        
-       
+
+
         //Set the servlet-class
-        if (servlet_class != null) 
+        if (servlet_class != null)
         {
             ((WebDescriptor)descriptor).addClassName(servlet_class);
-            
-            Origin o = context.getMetaData().getOrigin(servlet_name+".servlet.servlet-class");
-            switch (o)
+            switch (context.getMetaData().getOrigin(name+".servlet.servlet-class"))
             {
                 case NotSet:
                 {
                     //the class of the servlet has not previously been set, so set it
                     holder.setClassName(servlet_class);
-                    context.getMetaData().setOrigin(servlet_name+".servlet.servlet-class", descriptor);
+                    context.getMetaData().setOrigin(name+".servlet.servlet-class", descriptor);
                     break;
                 }
                 case WebXml:
@@ -310,7 +321,7 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
                     if (!(descriptor instanceof FragmentDescriptor))
                     {
                         holder.setClassName(servlet_class);
-                        context.getMetaData().setOrigin(servlet_name+".servlet.servlet-class", descriptor);
+                        context.getMetaData().setOrigin(name+".servlet.servlet-class", descriptor);
                     }
                     break;
                 }
@@ -321,20 +332,17 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
                         throw new IllegalStateException("Conflicting servlet-class "+servlet_class+" in "+descriptor.getResource());
                     break;
                 }
-            }          
+                default:
+                    LOG.warn(new Throwable()); // TODO throw ISE?
+            }
         }
 
         // Handle JSP file
         String jsp_file = node.getString("jsp-file", false, true);
         if (jsp_file != null)
-        {
             holder.setForcedPath(jsp_file);
-            ServletHolder jsp=context.getServletHandler().getServlet("jsp");
-            if (jsp!=null)
-                holder.setClassName(jsp.getClassName());
-        }
 
-        // handle load-on-startup 
+        // handle load-on-startup
         XmlParser.Node startup = node.get("load-on-startup");
         if (startup != null)
         {
@@ -343,7 +351,7 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
             if (s.startsWith("t"))
             {
                 LOG.warn("Deprecated boolean load-on-startup.  Please use integer");
-                order = 1; 
+                order = 1;
             }
             else
             {
@@ -358,14 +366,13 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
                 }
             }
 
-            Origin o = context.getMetaData().getOrigin(servlet_name+".servlet.load-on-startup");
-            switch (o)
+            switch (context.getMetaData().getOrigin(name+".servlet.load-on-startup"))
             {
                 case NotSet:
                 {
                     //not already set, so set it now
                     holder.setInitOrder(order);
-                    context.getMetaData().setOrigin(servlet_name+".servlet.load-on-startup", descriptor);
+                    context.getMetaData().setOrigin(name+".servlet.load-on-startup", descriptor);
                     break;
                 }
                 case WebXml:
@@ -376,7 +383,7 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
                     if (!(descriptor instanceof FragmentDescriptor))
                     {
                         holder.setInitOrder(order);
-                        context.getMetaData().setOrigin(servlet_name+".servlet.load-on-startup", descriptor);
+                        context.getMetaData().setOrigin(name+".servlet.load-on-startup", descriptor);
                     }
                     break;
                 }
@@ -387,10 +394,12 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
                         throw new IllegalStateException("Conflicting load-on-startup value in "+descriptor.getResource());
                     break;
                 }
-            } 
+                default:
+                    LOG.warn(new Throwable()); // TODO throw ISE?
+            }
         }
 
-        Iterator sRefsIter = node.iterator("security-role-ref");
+        Iterator<Node> sRefsIter = node.iterator("security-role-ref");
         while (sRefsIter.hasNext())
         {
             XmlParser.Node securityRef = (XmlParser.Node) sRefsIter.next();
@@ -399,14 +408,13 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
             if (roleName != null && roleName.length() > 0 && roleLink != null && roleLink.length() > 0)
             {
                 if (LOG.isDebugEnabled()) LOG.debug("link role " + roleName + " to " + roleLink + " for " + this);
-                Origin o = context.getMetaData().getOrigin(servlet_name+".servlet.role-name."+roleName);
-                switch (o)
+                switch (context.getMetaData().getOrigin(name+".servlet.role-name."+roleName))
                 {
                     case NotSet:
                     {
                         //set it
                         holder.setUserRoleLink(roleName, roleLink);
-                        context.getMetaData().setOrigin(servlet_name+".servlet.role-name."+roleName, descriptor);
+                        context.getMetaData().setOrigin(name+".servlet.role-name."+roleName, descriptor);
                         break;
                     }
                     case WebXml:
@@ -417,16 +425,18 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
                         if (!(descriptor instanceof FragmentDescriptor))
                         {
                             holder.setUserRoleLink(roleName, roleLink);
-                            context.getMetaData().setOrigin(servlet_name+".servlet.role-name."+roleName, descriptor);
+                            context.getMetaData().setOrigin(name+".servlet.role-name."+roleName, descriptor);
                         }
                         break;
                     }
                     case WebFragment:
                     {
                         if (!holder.getUserRoleLink(roleName).equals(roleLink))
-                            throw new IllegalStateException("Conflicting role-link for role-name "+roleName+" for servlet "+servlet_name+" in "+descriptor.getResource());
+                            throw new IllegalStateException("Conflicting role-link for role-name "+roleName+" for servlet "+name+" in "+descriptor.getResource());
                         break;
                     }
+                    default:
+                        LOG.warn(new Throwable()); // TODO throw ISE?
                 }
             }
             else
@@ -435,22 +445,21 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
             }
         }
 
-        
+
         XmlParser.Node run_as = node.get("run-as");
         if (run_as != null)
-        { 
+        {
             String roleName = run_as.getString("role-name", false, true);
 
             if (roleName != null)
             {
-                Origin o = context.getMetaData().getOrigin(servlet_name+".servlet.run-as");
-                switch (o)
+                switch (context.getMetaData().getOrigin(name+".servlet.run-as"))
                 {
                     case NotSet:
                     {
                         //run-as not set, so set it
                         holder.setRunAsRole(roleName);
-                        context.getMetaData().setOrigin(servlet_name+".servlet.run-as", descriptor);
+                        context.getMetaData().setOrigin(name+".servlet.run-as", descriptor);
                         break;
                     }
                     case WebXml:
@@ -461,7 +470,7 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
                         if (!(descriptor instanceof FragmentDescriptor))
                         {
                             holder.setRunAsRole(roleName);
-                            context.getMetaData().setOrigin(servlet_name+".servlet.run-as", descriptor);
+                            context.getMetaData().setOrigin(name+".servlet.run-as", descriptor);
                         }
                         break;
                     }
@@ -469,9 +478,11 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
                     {
                         //run-as was set by another fragment, this fragment must show the same value
                         if (!holder.getRunAsRole().equals(roleName))
-                            throw new IllegalStateException("Conflicting run-as role "+roleName+" for servlet "+servlet_name+" in "+descriptor.getResource());
+                            throw new IllegalStateException("Conflicting run-as role "+roleName+" for servlet "+name+" in "+descriptor.getResource());
                         break;
-                    }    
+                    }
+                    default:
+                        LOG.warn(new Throwable()); // TODO throw ISE?
                 }
             }
         }
@@ -480,14 +491,13 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
         if (async!=null)
         {
             boolean val = async.length()==0||Boolean.valueOf(async);
-            Origin o =context.getMetaData().getOrigin(servlet_name+".servlet.async-supported");
-            switch (o)
+            switch (context.getMetaData().getOrigin(name+".servlet.async-supported"))
             {
                 case NotSet:
                 {
                     //set it
                     holder.setAsyncSupported(val);
-                    context.getMetaData().setOrigin(servlet_name+".servlet.async-supported", descriptor);
+                    context.getMetaData().setOrigin(name+".servlet.async-supported", descriptor);
                     break;
                 }
                 case WebXml:
@@ -498,32 +508,33 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
                     if (!(descriptor instanceof FragmentDescriptor))
                     {
                         holder.setAsyncSupported(val);
-                        context.getMetaData().setOrigin(servlet_name+".servlet.async-supported", descriptor);  
-                    }             
+                        context.getMetaData().setOrigin(name+".servlet.async-supported", descriptor);
+                    }
                     break;
                 }
                 case WebFragment:
                 {
                     //async-supported set by another fragment, this fragment's value must match
                     if (holder.isAsyncSupported() != val)
-                        throw new IllegalStateException("Conflicting async-supported="+async+" for servlet "+servlet_name+" in "+descriptor.getResource());
+                        throw new IllegalStateException("Conflicting async-supported="+async+" for servlet "+name+" in "+descriptor.getResource());
                     break;
                 }
+                default:
+                    LOG.warn(new Throwable()); // TODO throw ISE?
             }
         }
 
         String enabled = node.getString("enabled", false, true);
         if (enabled!=null)
         {
-            boolean is_enabled = enabled.length()==0||Boolean.valueOf(enabled);     
-            Origin o = context.getMetaData().getOrigin(servlet_name+".servlet.enabled");
-            switch (o)
+            boolean is_enabled = enabled.length()==0||Boolean.valueOf(enabled);
+            switch (context.getMetaData().getOrigin(name+".servlet.enabled"))
             {
                 case NotSet:
                 {
-                    //hasn't been set yet, so set it                
+                    //hasn't been set yet, so set it
                     holder.setEnabled(is_enabled);
-                    context.getMetaData().setOrigin(servlet_name+".servlet.enabled", descriptor);
+                    context.getMetaData().setOrigin(name+".servlet.enabled", descriptor);
                     break;
                 }
                 case WebXml:
@@ -533,8 +544,8 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
                     //was set in a web xml descriptor, only allow override from another web xml descriptor
                     if (!(descriptor instanceof FragmentDescriptor))
                     {
-                        holder.setEnabled(is_enabled);   
-                        context.getMetaData().setOrigin(servlet_name+".servlet.enabled", descriptor);
+                        holder.setEnabled(is_enabled);
+                        context.getMetaData().setOrigin(name+".servlet.enabled", descriptor);
                     }
                     break;
                 }
@@ -542,12 +553,14 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
                 {
                     //was set by another fragment, this fragment's value must match
                     if (holder.isEnabled() != is_enabled)
-                        throw new IllegalStateException("Conflicting value of servlet enabled for servlet "+servlet_name+" in "+descriptor.getResource());
+                        throw new IllegalStateException("Conflicting value of servlet enabled for servlet "+name+" in "+descriptor.getResource());
                     break;
                 }
+                default:
+                    LOG.warn(new Throwable()); // TODO throw ISE?
             }
         }
-        
+
         /*
          * If multipart config not set, then set it and record it was by the web.xml or fragment.
          * If it was set by web.xml then if this is a fragment, ignore the settings.
@@ -564,15 +577,14 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
                                                                         (maxFile==null||"".equals(maxFile)?-1L:Long.parseLong(maxFile)),
                                                                         (maxRequest==null||"".equals(maxRequest)?-1L:Long.parseLong(maxRequest)),
                                                                         (threshold==null||"".equals(threshold)?0:Integer.parseInt(threshold)));
-            
-            Origin o = context.getMetaData().getOrigin(servlet_name+".servlet.multipart-config");
-            switch (o)
+
+            switch (context.getMetaData().getOrigin(name+".servlet.multipart-config"))
             {
                 case NotSet:
                 {
                     //hasn't been set, so set it
                     holder.getRegistration().setMultipartConfig(element);
-                    context.getMetaData().setOrigin(servlet_name+".servlet.multipart-config", descriptor);
+                    context.getMetaData().setOrigin(name+".servlet.multipart-config", descriptor);
                     break;
                 }
                 case WebXml:
@@ -583,7 +595,7 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
                     if (!(descriptor instanceof FragmentDescriptor))
                     {
                         holder.getRegistration().setMultipartConfig(element);
-                        context.getMetaData().setOrigin(servlet_name+".servlet.multipart-config", descriptor);  
+                        context.getMetaData().setOrigin(name+".servlet.multipart-config", descriptor);
                     }
                     break;
                 }
@@ -591,56 +603,55 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
                 {
                     //another fragment set the value, this fragment's values must match exactly or it is an error
                     MultipartConfigElement cfg = ((ServletHolder.Registration)holder.getRegistration()).getMultipartConfig();
-                    
+
                     if (cfg.getMaxFileSize() != element.getMaxFileSize())
-                        throw new IllegalStateException("Conflicting multipart-config max-file-size for servlet "+servlet_name+" in "+descriptor.getResource());
+                        throw new IllegalStateException("Conflicting multipart-config max-file-size for servlet "+name+" in "+descriptor.getResource());
                     if (cfg.getMaxRequestSize() != element.getMaxRequestSize())
-                        throw new IllegalStateException("Conflicting multipart-config max-request-size for servlet "+servlet_name+" in "+descriptor.getResource());
+                        throw new IllegalStateException("Conflicting multipart-config max-request-size for servlet "+name+" in "+descriptor.getResource());
                     if (cfg.getFileSizeThreshold() != element.getFileSizeThreshold())
-                        throw new IllegalStateException("Conflicting multipart-config file-size-threshold for servlet "+servlet_name+" in "+descriptor.getResource());
+                        throw new IllegalStateException("Conflicting multipart-config file-size-threshold for servlet "+name+" in "+descriptor.getResource());
                     if ((cfg.getLocation() != null && (element.getLocation() == null || element.getLocation().length()==0))
                             || (cfg.getLocation() == null && (element.getLocation()!=null || element.getLocation().length() > 0)))
-                        throw new IllegalStateException("Conflicting multipart-config location for servlet "+servlet_name+" in "+descriptor.getResource());
+                        throw new IllegalStateException("Conflicting multipart-config location for servlet "+name+" in "+descriptor.getResource());
                     break;
                 }
-            } 
+                default:
+                    LOG.warn(new Throwable()); // TODO throw ISE?
+            }
         }
     }
-    
-    
+
+
 
     /**
      * @param context
      * @param descriptor
      * @param node
      */
-    protected void visitServletMapping(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
+    public void visitServletMapping(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
     {
         //Servlet Spec 3.0, p74
         //servlet-mappings are always additive, whether from web xml descriptors (web.xml/web-default.xml/web-override.xml) or web-fragments.
         //Maintenance update 3.0a to spec:
-        //  Updated 8.2.3.g.v to say <servlet-mapping> elements are additive across web-fragments. 
+        //  Updated 8.2.3.g.v to say <servlet-mapping> elements are additive across web-fragments.
         //  <servlet-mapping> declared in web.xml overrides the mapping for the servlet specified in the web-fragment.xml
 
-        String servlet_name = node.getString("servlet-name", false, true); 
-        Origin origin = context.getMetaData().getOrigin(servlet_name+".servlet.mappings");
-        
-        switch (origin)
+        String servlet_name = node.getString("servlet-name", false, true);
+        switch (context.getMetaData().getOrigin(servlet_name+".servlet.mappings"))
         {
             case NotSet:
             {
                 //no servlet mappings
                 context.getMetaData().setOrigin(servlet_name+".servlet.mappings", descriptor);
-                ServletMapping mapping = addServletMapping(servlet_name, node, context, descriptor);
-                mapping.setDefault(context.getMetaData().getOrigin(servlet_name+".servlet.mappings") == Origin.WebDefaults);
+                addServletMapping(servlet_name, node, context, descriptor);               
                 break;
             }
-            case WebXml:
             case WebDefaults:
+            case WebXml:
             case WebOverride:
             {
                 //previously set by a web xml descriptor, if we're parsing another web xml descriptor allow override
-                //otherwise just ignore it
+                //otherwise just ignore it as web.xml takes precedence (pg 8-81 5.g.vi)
                 if (!(descriptor instanceof FragmentDescriptor))
                 {
                    addServletMapping(servlet_name, node, context, descriptor);
@@ -653,16 +664,18 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
                 addServletMapping(servlet_name, node, context, descriptor);
                 break;
             }
-        }        
+            default:
+                LOG.warn(new Throwable()); // TODO throw ISE?
+        }
     }
-    
-    
+
+
     /**
      * @param context
      * @param descriptor
      * @param node
      */
-    protected void visitSessionConfig(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
+    public void visitSessionConfig(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
     {
         XmlParser.Node tNode = node.get("session-timeout");
         if (tNode != null)
@@ -670,11 +683,11 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
             int timeout = Integer.parseInt(tNode.toString(false, true));
             context.getSessionHandler().getSessionManager().setMaxInactiveInterval(timeout * 60);
         }
-        
-        //Servlet Spec 3.0 
+
+        //Servlet Spec 3.0
         // <tracking-mode>
         // this is additive across web-fragments
-        Iterator iter = node.iterator("tracking-mode");
+        Iterator<Node> iter = node.iterator("tracking-mode");
         if (iter.hasNext())
         { 
             Set<SessionTrackingMode> modes = null;
@@ -700,7 +713,9 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
                         modes = new HashSet<SessionTrackingMode>(context.getSessionHandler().getSessionManager().getEffectiveSessionTrackingModes());
                     context.getMetaData().setOrigin("session.tracking-mode", descriptor);
                     break;
-                }       
+                }    
+                default:
+                    LOG.warn(new Throwable()); // TODO throw ISE?   
             }
             
             while (iter.hasNext())
@@ -712,8 +727,8 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
             context.getSessionHandler().getSessionManager().setSessionTrackingModes(modes);   
         }
        
-        
-        //Servlet Spec 3.0 
+
+        //Servlet Spec 3.0
         //<cookie-config>
         XmlParser.Node cookieConfig = node.get("cookie-config");
         if (cookieConfig != null)
@@ -722,8 +737,7 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
             String name = cookieConfig.getString("name", false, true);
             if (name != null)
             {
-                Origin o = context.getMetaData().getOrigin("cookie-config.name");
-                switch (o)
+                switch (context.getMetaData().getOrigin("cookie-config.name"))
                 {
                     case NotSet:
                     {
@@ -747,19 +761,20 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
                     case WebFragment:
                     {
                         //a web-fragment set the value, all web-fragments must have the same value
-                        if (!context.getSessionHandler().getSessionManager().getSessionCookieConfig().getName().equals(name))                  
+                        if (!context.getSessionHandler().getSessionManager().getSessionCookieConfig().getName().equals(name))
                             throw new IllegalStateException("Conflicting cookie-config name "+name+" in "+descriptor.getResource());
                         break;
                     }
+                    default:
+                        LOG.warn(new Throwable()); // TODO throw ISE?
                 }
             }
-            
+
             //  <domain>
             String domain = cookieConfig.getString("domain", false, true);
             if (domain != null)
             {
-                Origin o = context.getMetaData().getOrigin("cookie-config.domain");
-                switch (o)
+                switch (context.getMetaData().getOrigin("cookie-config.domain"))
                 {
                     case NotSet:
                     {
@@ -783,19 +798,20 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
                     case WebFragment:
                     {
                         //a web-fragment set the value, all web-fragments must have the same value
-                        if (!context.getSessionHandler().getSessionManager().getSessionCookieConfig().getDomain().equals(domain))                  
+                        if (!context.getSessionHandler().getSessionManager().getSessionCookieConfig().getDomain().equals(domain))
                             throw new IllegalStateException("Conflicting cookie-config domain "+domain+" in "+descriptor.getResource());
                         break;
                     }
+                    default:
+                        LOG.warn(new Throwable()); // TODO throw ISE?
                 }
             }
-            
+
             //  <path>
             String path = cookieConfig.getString("path", false, true);
             if (path != null)
             {
-                Origin o = context.getMetaData().getOrigin("cookie-config.path");
-                switch (o)
+                switch (context.getMetaData().getOrigin("cookie-config.path"))
                 {
                     case NotSet:
                     {
@@ -819,19 +835,20 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
                     case WebFragment:
                     {
                         //a web-fragment set the value, all web-fragments must have the same value
-                        if (!context.getSessionHandler().getSessionManager().getSessionCookieConfig().getPath().equals(path))                  
+                        if (!context.getSessionHandler().getSessionManager().getSessionCookieConfig().getPath().equals(path))
                             throw new IllegalStateException("Conflicting cookie-config path "+path+" in "+descriptor.getResource());
                         break;
                     }
+                    default:
+                        LOG.warn(new Throwable()); // TODO throw ISE?
                 }
             }
-            
+
             //  <comment>
             String comment = cookieConfig.getString("comment", false, true);
             if (comment != null)
             {
-                Origin o = context.getMetaData().getOrigin("cookie-config.comment");
-                switch (o)
+                switch (context.getMetaData().getOrigin("cookie-config.comment"))
                 {
                     case NotSet:
                     {
@@ -855,20 +872,21 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
                     case WebFragment:
                     {
                         //a web-fragment set the value, all web-fragments must have the same value
-                        if (!context.getSessionHandler().getSessionManager().getSessionCookieConfig().getComment().equals(comment))                  
+                        if (!context.getSessionHandler().getSessionManager().getSessionCookieConfig().getComment().equals(comment))
                             throw new IllegalStateException("Conflicting cookie-config comment "+comment+" in "+descriptor.getResource());
                         break;
                     }
+                    default:
+                        LOG.warn(new Throwable()); // TODO throw ISE?
                 }
             }
-            
+
             //  <http-only>true/false
             tNode = cookieConfig.get("http-only");
             if (tNode != null)
             {
-                boolean httpOnly = Boolean.parseBoolean(tNode.toString(false,true));           
-                Origin o = context.getMetaData().getOrigin("cookie-config.http-only");
-                switch (o)
+                boolean httpOnly = Boolean.parseBoolean(tNode.toString(false,true));
+                switch (context.getMetaData().getOrigin("cookie-config.http-only"))
                 {
                     case NotSet:
                     {
@@ -892,20 +910,21 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
                     case WebFragment:
                     {
                         //a web-fragment set the value, all web-fragments must have the same value
-                        if (context.getSessionHandler().getSessionManager().getSessionCookieConfig().isHttpOnly() != httpOnly)       
+                        if (context.getSessionHandler().getSessionManager().getSessionCookieConfig().isHttpOnly() != httpOnly)
                             throw new IllegalStateException("Conflicting cookie-config http-only "+httpOnly+" in "+descriptor.getResource());
                         break;
                     }
+                    default:
+                        LOG.warn(new Throwable()); // TODO throw ISE?
                 }
             }
-            
+
             //  <secure>true/false
             tNode = cookieConfig.get("secure");
             if (tNode != null)
             {
                 boolean secure = Boolean.parseBoolean(tNode.toString(false,true));
-                Origin o = context.getMetaData().getOrigin("cookie-config.secure");
-                switch (o)
+                switch (context.getMetaData().getOrigin("cookie-config.secure"))
                 {
                     case NotSet:
                     {
@@ -913,7 +932,7 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
                         context.getSessionHandler().getSessionManager().getSessionCookieConfig().setSecure(secure);
                         context.getMetaData().setOrigin("cookie-config.secure", descriptor);
                         break;
-                    }                   
+                    }
                     case WebXml:
                     case WebDefaults:
                     case WebOverride:
@@ -929,23 +948,24 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
                     case WebFragment:
                     {
                         //a web-fragment set the value, all web-fragments must have the same value
-                        if (context.getSessionHandler().getSessionManager().getSessionCookieConfig().isSecure() != secure)       
+                        if (context.getSessionHandler().getSessionManager().getSessionCookieConfig().isSecure() != secure)
                             throw new IllegalStateException("Conflicting cookie-config secure "+secure+" in "+descriptor.getResource());
                         break;
                     }
+                    default:
+                        LOG.warn(new Throwable()); // TODO throw ISE?
                 }
             }
-            
+
             //  <max-age>
             tNode = cookieConfig.get("max-age");
             if (tNode != null)
             {
                 int maxAge = Integer.parseInt(tNode.toString(false,true));
-                Origin o = context.getMetaData().getOrigin("cookie-config.max-age");
-                switch (o)
+                switch (context.getMetaData().getOrigin("cookie-config.max-age"))
                 {
                     case NotSet:
-                    { 
+                    {
                         //no <cookie-config><max-age> set yet, accept it
                         context.getSessionHandler().getSessionManager().getSessionCookieConfig().setMaxAge(maxAge);
                         context.getMetaData().setOrigin("cookie-config.max-age", descriptor);
@@ -954,7 +974,7 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
                     case WebXml:
                     case WebDefaults:
                     case WebOverride:
-                    {   
+                    {
                         //<cookie-config><max-age> set in a web xml, only allow web-default/web-override to change
                         if (!(descriptor instanceof FragmentDescriptor))
                         {
@@ -970,28 +990,29 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
                             throw new IllegalStateException("Conflicting cookie-config max-age "+maxAge+" in "+descriptor.getResource());
                         break;
                     }
+                    default:
+                        LOG.warn(new Throwable()); // TODO throw ISE?
                 }
             }
         }
     }
-    
-    
-    
+
+
+
     /**
      * @param context
      * @param descriptor
      * @param node
      */
-    protected void visitMimeMapping(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
+    public void visitMimeMapping(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
     {
         String extension = node.getString("extension", false, true);
-        if (extension != null && extension.startsWith(".")) 
+        if (extension != null && extension.startsWith("."))
             extension = extension.substring(1);
         String mimeType = node.getString("mime-type", false, true);
         if (extension != null)
         {
-            Origin o = context.getMetaData().getOrigin("extension."+extension);
-            switch (o)
+            switch (context.getMetaData().getOrigin("extension."+extension))
             {
                 case NotSet:
                 {
@@ -1015,23 +1036,24 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
                 case WebFragment:
                 {
                     //a web-fragment set the value, all web-fragments must have the same value
-                    if (!context.getMimeTypes().getMimeByExtension("."+extension).equals(context.getMimeTypes().CACHE.lookup(mimeType)))
+                    if (!context.getMimeTypes().getMimeByExtension("."+extension).equals(mimeType))
                         throw new IllegalStateException("Conflicting mime-type "+mimeType+" for extension "+extension+" in "+descriptor.getResource());
                     break;
                 }
+                default:
+                    LOG.warn(new Throwable()); // TODO throw ISE?
             }
         }
     }
-    
+
     /**
      * @param context
      * @param descriptor
      * @param node
      */
-    protected void visitWelcomeFileList(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
+    public void visitWelcomeFileList(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
     {
-        Origin o = context.getMetaData().getOrigin("welcome-file-list");
-        switch (o)
+        switch (context.getMetaData().getOrigin("welcome-file-list"))
         {
             case NotSet:
             {
@@ -1064,19 +1086,21 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
             }
             case WebFragment:
             {
-                //A web-fragment first set the welcome-file-list. Other descriptors just add. 
+                //A web-fragment first set the welcome-file-list. Other descriptors just add.
                 addWelcomeFiles(context,node);
                 break;
             }
+            default:
+                LOG.warn(new Throwable()); // TODO throw ISE?
         }
     }
-    
+
     /**
      * @param context
      * @param descriptor
      * @param node
      */
-    protected void visitLocaleEncodingList(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
+    public void visitLocaleEncodingList(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
     {
         Iterator<XmlParser.Node> iter = node.iterator("locale-encoding-mapping");
         while (iter.hasNext())
@@ -1084,11 +1108,10 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
             XmlParser.Node mapping = iter.next();
             String locale = mapping.getString("locale", false, true);
             String encoding = mapping.getString("encoding", false, true);
-            
+
             if (encoding != null)
             {
-                Origin o = context.getMetaData().getOrigin("locale-encoding."+locale);
-                switch (o)
+                switch (context.getMetaData().getOrigin("locale-encoding."+locale))
                 {
                     case NotSet:
                     {
@@ -1114,8 +1137,10 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
                         //a value was set by a web-fragment, all fragments must have the same value
                         if (!encoding.equals(context.getLocaleEncoding(locale)))
                             throw new IllegalStateException("Conflicting loacle-encoding mapping for locale "+locale+" in "+descriptor.getResource());
-                        break;                    
+                        break;
                     }
+                    default:
+                        LOG.warn(new Throwable()); // TODO throw ISE?
                 }
             }
         }
@@ -1126,11 +1151,11 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
      * @param descriptor
      * @param node
      */
-    protected void visitErrorPage(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
+    public void visitErrorPage(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
     {
         String error = node.getString("error-code", false, true);
         int code=0;
-        if (error == null || error.length() == 0) 
+        if (error == null || error.length() == 0)
         {
             error = node.getString("exception-type", false, true);
             if (error == null || error.length() == 0)
@@ -1138,12 +1163,11 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
         }
         else
             code=Integer.valueOf(error);
-        
+
         String location = node.getString("location", false, true);
         ErrorPageErrorHandler handler = (ErrorPageErrorHandler)context.getErrorHandler();
-        Origin o = context.getMetaData().getOrigin("error."+error);
-        
-        switch (o)
+        String originName = "error."+error;
+        switch (context.getMetaData().getOrigin(originName))
         {
             case NotSet:
             {
@@ -1159,19 +1183,19 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
             case WebDefaults:
             case WebOverride:
             {
-                //an error page setup was set in web.xml, only allow other web xml descriptors to override it
+                //an error page setup was set in web.xml/webdefault.xml/web-override.xml, only allow other web xml descriptors to override it
                 if (!(descriptor instanceof FragmentDescriptor))
                 {
-                    if (descriptor instanceof OverrideDescriptor || descriptor instanceof DefaultsDescriptor)
-                    {
-                        if (code>0)
-                            handler.addErrorPage(code,location);
-                        else
-                            handler.addErrorPage(error,location);
-                        context.getMetaData().setOrigin("error."+error, descriptor);
-                    }
+                    //if set twice in the same descriptor, its an error
+                    Descriptor originDescriptor = context.getMetaData().getOriginDescriptor(originName);
+                    if (descriptor == originDescriptor)
+                        throw new IllegalStateException("Duplicate error-page "+error+" at "+location);
+
+                    if (code>0)
+                        handler.addErrorPage(code,location);
                     else
-                        throw new IllegalStateException("Duplicate global error-page "+location);
+                        handler.addErrorPage(error,location);
+                    context.getMetaData().setOrigin("error."+error, descriptor);
                 }
                 break;
             }
@@ -1182,38 +1206,41 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
                     throw new IllegalStateException("Conflicting error-code or exception-type "+error+" in "+descriptor.getResource());
                 break;
             }
+            default:
+                LOG.warn(new Throwable()); // TODO throw ISE?
         }
-       
+
     }
-    
+
     /**
      * @param context
      * @param node
      */
-    protected void addWelcomeFiles(WebAppContext context, XmlParser.Node node)
+    public void addWelcomeFiles(WebAppContext context, XmlParser.Node node)
     {
         Iterator<XmlParser.Node> iter = node.iterator("welcome-file");
         while (iter.hasNext())
         {
             XmlParser.Node indexNode = (XmlParser.Node) iter.next();
             String welcome = indexNode.toString(false, true);
-            
+
             //Servlet Spec 3.0 p. 74 welcome files are additive
             if (welcome != null && welcome.trim().length() > 0)
-                context.setWelcomeFiles((String[])LazyList.addToArray(context.getWelcomeFiles(),welcome,String.class));
+               context.setWelcomeFiles((String[])ArrayUtil.addToArray(context.getWelcomeFiles(),welcome,String.class));
         }
     }
-    
-    
+
+
     /**
      * @param servletName
      * @param node
      * @param context
      */
-    protected ServletMapping addServletMapping (String servletName, XmlParser.Node node, WebAppContext context, Descriptor descriptor)
+    public ServletMapping addServletMapping (String servletName, XmlParser.Node node, WebAppContext context, Descriptor descriptor)
     {
         ServletMapping mapping = new ServletMapping();
         mapping.setServletName(servletName);
+        mapping.setDefault(descriptor instanceof DefaultsDescriptor);
         
         List<String> paths = new ArrayList<String>();
         Iterator<XmlParser.Node> iter = node.iterator("url-pattern");
@@ -1221,20 +1248,59 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
         {
             String p = iter.next().toString(false, true);
             p = normalizePattern(p);
+            
+            //check if there is already a mapping for this path, and if there is && it is from a defaultdescriptor
+            //remove it in favour of the new one      
+            ListIterator<ServletMapping> listItor = _servletMappings.listIterator();
+            boolean found = false;
+            while (listItor.hasNext() && !found)
+            {
+                ServletMapping sm = listItor.next();
+                if (sm.getPathSpecs() != null)
+                {
+                    for (String ps:sm.getPathSpecs())
+                    {
+                        if (p.equals(ps) && sm.isDefault())
+                        {
+                            if (LOG.isDebugEnabled()) LOG.debug("{} in mapping {} from defaults descriptor is overridden by ",ps,sm,servletName);
+                            //remove ps from the path specs on the existing mapping
+                            //if the mapping now has no pathspecs, remove it
+                            String[] updatedPaths = ArrayUtil.removeFromArray(sm.getPathSpecs(), ps);
+                            if (updatedPaths == null || updatedPaths.length == 0)
+                            { 
+                                if (LOG.isDebugEnabled()) LOG.debug("Removed mapping {} from defaults descriptor",sm);
+                                listItor.remove();
+                            }
+                            else 
+                            {
+                                sm.setPathSpecs(updatedPaths);
+                                if (LOG.isDebugEnabled()) LOG.debug("Removed path {} from mapping {} from defaults descriptor ", p,sm);
+                            }
+                            found = true;
+                            break;
+                        }
+                    }
+                }
+            }
+            
             paths.add(p);
             context.getMetaData().setOrigin(servletName+".servlet.mapping."+p, descriptor);
         }
         mapping.setPathSpecs((String[]) paths.toArray(new String[paths.size()]));
-        context.getServletHandler().addServletMapping(mapping);
+        if (LOG.isDebugEnabled()) LOG.debug("Added mapping {} ",mapping);
+        
+      
+      
+        _servletMappings.add(mapping);
         return mapping;
     }
-    
+
     /**
      * @param filterName
      * @param node
      * @param context
      */
-    protected void addFilterMapping (String filterName, XmlParser.Node node, WebAppContext context, Descriptor descriptor)
+    public void addFilterMapping (String filterName, XmlParser.Node node, WebAppContext context, Descriptor descriptor)
     {
         FilterMapping mapping = new FilterMapping();
         mapping.setFilterName(filterName);
@@ -1259,7 +1325,7 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
         }
         mapping.setServletNames((String[]) names.toArray(new String[names.size()]));
 
-        
+
         List<DispatcherType> dispatches = new ArrayList<DispatcherType>();
         iter=node.iterator("dispatcher");
         while(iter.hasNext())
@@ -1267,47 +1333,47 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
             String d=((XmlParser.Node)iter.next()).toString(false,true);
             dispatches.add(FilterMapping.dispatch(d));
         }
-        
+
         if (dispatches.size()>0)
             mapping.setDispatcherTypes(EnumSet.copyOf(dispatches));
 
-        context.getServletHandler().addFilterMapping(mapping);
+        _filterMappings.add(mapping);
     }
-    
-    
+
+
     /**
      * @param context
      * @param descriptor
      * @param node
      */
-    protected void visitTagLib(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
+    public void visitTagLib(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
     {
         //Additive across web.xml and web-fragment.xml
         String uri = node.getString("taglib-uri", false, true);
         String location = node.getString("taglib-location", false, true);
 
         context.setResourceAlias(uri, location);
-        
+
         JspConfig config = (JspConfig)context.getServletContext().getJspConfigDescriptor();
         if (config == null)
         {
             config = new JspConfig();
             context.getServletContext().setJspConfigDescriptor(config);
         }
-        
+
         TagLib tl = new TagLib();
         tl.setTaglibLocation(location);
         tl.setTaglibURI(uri);
         config.addTaglibDescriptor(tl);
     }
-    
+
     /**
      * @param context
      * @param descriptor
      * @param node
      */
-    protected void visitJspConfig(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
-    {   
+    public void visitJspConfig(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
+    {
         //Additive across web.xml and web-fragment.xml
         JspConfig config = (JspConfig)context.getServletContext().getJspConfigDescriptor();
         if (config == null)
@@ -1315,12 +1381,12 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
            config = new JspConfig();
            context.getServletContext().setJspConfigDescriptor(config);
         }
-        
-        
+
+
         for (int i = 0; i < node.size(); i++)
         {
             Object o = node.get(i);
-            if (o instanceof XmlParser.Node && "taglib".equals(((XmlParser.Node) o).getTag())) 
+            if (o instanceof XmlParser.Node && "taglib".equals(((XmlParser.Node) o).getTag()))
                 visitTagLib(context,descriptor, (XmlParser.Node) o);
         }
 
@@ -1330,10 +1396,10 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
         List<String> paths = new ArrayList<String>();
         while (iter.hasNext())
         {
+
             JspPropertyGroup jpg = new JspPropertyGroup();
             config.addJspPropertyGroup(jpg);
             XmlParser.Node group = iter.next();
-            
             //url-patterns
             Iterator<XmlParser.Node> iter2 = group.iterator("url-pattern");
             while (iter2.hasNext())
@@ -1343,7 +1409,7 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
                 paths.add( url);
                 jpg.addUrlPattern(url);
             }
-            
+
             jpg.setElIgnored(group.getString("el-ignored", false, true));
             jpg.setPageEncoding(group.getString("page-encoding", false, true));
             jpg.setScriptingInvalid(group.getString("scripting-invalid", false, true));
@@ -1353,7 +1419,7 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
             jpg.setDefaultContentType(group.getString("default-content-type", false, true));
             jpg.setBuffer(group.getString("buffer", false, true));
             jpg.setErrorOnUndeclaredNamespace(group.getString("error-on-undeclared-namespace", false, true));
-            
+
             //preludes
             Iterator<XmlParser.Node> preludes = group.iterator("include-prelude");
             while (preludes.hasNext())
@@ -1368,37 +1434,30 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
                 String coda = codas.next().toString(false, true);
                 jpg.addIncludeCoda(coda);
             }
-            
+
             if (LOG.isDebugEnabled()) LOG.debug(config.toString());
         }
 
+        //add mappings to the jsp servlet from the property-group mappings
         if (paths.size() > 0)
         {
-            ServletHandler handler = context.getServletHandler();
-            ServletHolder jsp_pg_servlet = handler.getServlet(JspPropertyGroupServlet.NAME);
-            if (jsp_pg_servlet==null)
-            {
-                jsp_pg_servlet=new ServletHolder(JspPropertyGroupServlet.NAME,new JspPropertyGroupServlet(context,handler));
-                handler.addServlet(jsp_pg_servlet);
-            }
-
             ServletMapping mapping = new ServletMapping();
-            mapping.setServletName(JspPropertyGroupServlet.NAME);
+            mapping.setServletName("jsp");
             mapping.setPathSpecs(paths.toArray(new String[paths.size()]));
-            context.getServletHandler().addServletMapping(mapping);
+            _servletMappings.add(mapping);
         }
     }
-    
+
     /**
      * @param context
      * @param descriptor
      * @param node
      */
-    protected void visitSecurityConstraint(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
+    public void visitSecurityConstraint(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
     {
         Constraint scBase = new Constraint();
 
-        //ServletSpec 3.0, p74 security-constraints, as minOccurs > 1, are additive 
+        //ServletSpec 3.0, p74 security-constraints, as minOccurs > 1, are additive
         //across fragments
         
         //TODO: need to remember origin of the constraints
@@ -1453,18 +1512,18 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
                     //remember origin so we can process ServletRegistration.Dynamic.setServletSecurityElement() correctly
                     context.getMetaData().setOrigin("constraint.url."+url, descriptor);
                     
-                    Iterator<XmlParser.Node> iter3 = collection.iterator("http-method");
-                    Iterator<XmlParser.Node> iter4 = collection.iterator("http-method-omission");
+                    Iterator<XmlParser.Node> methods = collection.iterator("http-method");
+                    Iterator<XmlParser.Node> ommissions = collection.iterator("http-method-omission");
                    
-                    if (iter3.hasNext())
+                    if (methods.hasNext())
                     {
-                        if (iter4.hasNext())
+                        if (ommissions.hasNext())
                             throw new IllegalStateException ("web-resource-collection cannot contain both http-method and http-method-omission");
                         
                         //configure all the http-method elements for each url
-                        while (iter3.hasNext())
+                        while (methods.hasNext())
                         {
-                            String method = ((XmlParser.Node) iter3.next()).toString(false, true);
+                            String method = ((XmlParser.Node) methods.next()).toString(false, true);
                             ConstraintMapping mapping = new ConstraintMapping();
                             mapping.setMethod(method);
                             mapping.setPathSpec(url);
@@ -1472,12 +1531,13 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
                             ((ConstraintAware)context.getSecurityHandler()).addConstraintMapping(mapping);
                         }
                     }
-                    else if (iter4.hasNext())
+                    else if (ommissions.hasNext())
                     {
                         //configure all the http-method-omission elements for each url
-                        while (iter4.hasNext())
+                        // TODO use the array
+                        while (ommissions.hasNext())
                         {
-                            String method = ((XmlParser.Node)iter4.next()).toString(false, true);
+                            String method = ((XmlParser.Node)ommissions.next()).toString(false, true);
                             ConstraintMapping mapping = new ConstraintMapping();
                             mapping.setMethodOmissions(new String[]{method});
                             mapping.setPathSpec(url);
@@ -1501,14 +1561,14 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
             LOG.warn(e);
         }
     }
-    
+
     /**
      * @param context
      * @param descriptor
      * @param node
      * @throws Exception
      */
-    protected void visitLoginConfig(WebAppContext context, Descriptor descriptor, XmlParser.Node node) throws Exception
+    public void visitLoginConfig(WebAppContext context, Descriptor descriptor, XmlParser.Node node) throws Exception
     {
         //ServletSpec 3.0 p74 says elements present 0/1 time if specified in web.xml take
         //precendece over any web-fragment. If not specified in web.xml, then if specified
@@ -1517,8 +1577,7 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
         if (method != null)
         {
             //handle auth-method merge
-            Origin o = context.getMetaData().getOrigin("auth-method");
-            switch (o)
+            switch (context.getMetaData().getOrigin("auth-method"))
             {
                 case NotSet:
                 {
@@ -1546,13 +1605,14 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
                         throw new IllegalStateException("Conflicting auth-method value in "+descriptor.getResource());
                     break;
                 }
-            } 
-            
+                default:
+                    LOG.warn(new Throwable()); // TODO throw ISE?
+            }
+
             //handle realm-name merge
             XmlParser.Node name = node.get("realm-name");
             String nameStr = (name == null ? "default" : name.toString(false, true));
-            o = context.getMetaData().getOrigin("realm-name");
-            switch (o)
+            switch (context.getMetaData().getOrigin("realm-name"))
             {
                 case NotSet:
                 {
@@ -1569,7 +1629,7 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
                     if (!(descriptor instanceof FragmentDescriptor))
                     {
                         context.getSecurityHandler().setRealmName(nameStr);
-                        context.getMetaData().setOrigin("realm-name", descriptor); 
+                        context.getMetaData().setOrigin("realm-name", descriptor);
                     }
                     break;
                 }
@@ -1580,25 +1640,26 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
                         throw new IllegalStateException("Conflicting realm-name value in "+descriptor.getResource());
                     break;
                 }
+                default:
+                    LOG.warn(new Throwable()); // TODO throw ISE?
             }
- 
-            if (Constraint.__FORM_AUTH.equals(context.getSecurityHandler().getAuthMethod()))
-            {  
+
+            if (Constraint.__FORM_AUTH.equalsIgnoreCase(context.getSecurityHandler().getAuthMethod()))
+            {
                 XmlParser.Node formConfig = node.get("form-login-config");
                 if (formConfig != null)
                 {
                     String loginPageName = null;
                     XmlParser.Node loginPage = formConfig.get("form-login-page");
-                    if (loginPage != null) 
+                    if (loginPage != null)
                         loginPageName = loginPage.toString(false, true);
                     String errorPageName = null;
                     XmlParser.Node errorPage = formConfig.get("form-error-page");
-                    if (errorPage != null) 
+                    if (errorPage != null)
                         errorPageName = errorPage.toString(false, true);
-                    
+
                     //handle form-login-page
-                    o = context.getMetaData().getOrigin("form-login-page");
-                    switch (o)
+                    switch (context.getMetaData().getOrigin("form-login-page"))
                     {
                         case NotSet:
                         {
@@ -1626,11 +1687,12 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
                                 throw new IllegalStateException("Conflicting form-login-page value in "+descriptor.getResource());
                             break;
                         }
+                        default:
+                            LOG.warn(new Throwable()); // TODO throw ISE?
                     }
-                    
+
                     //handle form-error-page
-                    o = context.getMetaData().getOrigin("form-error-page");
-                    switch (o)
+                    switch (context.getMetaData().getOrigin("form-error-page"))
                     {
                         case NotSet:
                         {
@@ -1658,7 +1720,9 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
                                 throw new IllegalStateException("Conflicting form-error-page value in "+descriptor.getResource());
                             break;
                         }
-                    }              
+                        default:
+                            LOG.warn(new Throwable()); // TODO throw ISE?
+                    }
                 }
                 else
                 {
@@ -1667,44 +1731,44 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
             }
         }
     }
-    
+
     /**
      * @param context
      * @param descriptor
      * @param node
      */
-    protected void visitSecurityRole(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
+    public void visitSecurityRole(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
     {
         //ServletSpec 3.0, p74 elements with multiplicity >1 are additive when merged
         XmlParser.Node roleNode = node.get("role-name");
         String role = roleNode.toString(false, true);
         ((ConstraintAware)context.getSecurityHandler()).addRole(role);
     }
-    
-    
+
+
     /**
      * @param context
      * @param descriptor
      * @param node
      */
-    protected void visitFilter(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
+    public void visitFilter(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
     {
         String name = node.getString("filter-name", false, true);
-        FilterHolder holder = context.getServletHandler().getFilter(name);
+        FilterHolder holder = _filterHolderMap.get(name);
         if (holder == null)
         {
-            holder = context.getServletHandler().newFilterHolder(Holder.Source.DESCRIPTOR);
+            holder = context.getServletHandler().newFilterHolder(Source.DESCRIPTOR);
             holder.setName(name);
-            context.getServletHandler().addFilter(holder);
+            _filterHolderMap.put(name,holder);
+            _filterHolders.add(holder);
         }
 
         String filter_class = node.getString("filter-class", false, true);
-        if (filter_class != null) 
+        if (filter_class != null)
         {
             ((WebDescriptor)descriptor).addClassName(filter_class);
-            
-            Origin o = context.getMetaData().getOrigin(name+".filter.filter-class");
-            switch (o)
+
+            switch (context.getMetaData().getOrigin(name+".filter.filter-class"))
             {
                 case NotSet:
                 {
@@ -1721,7 +1785,7 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
                     if (!(descriptor instanceof FragmentDescriptor))
                     {
                         holder.setClassName(filter_class);
-                        context.getMetaData().setOrigin(name+".filter.filter-class", descriptor); 
+                        context.getMetaData().setOrigin(name+".filter.filter-class", descriptor);
                     }
                     break;
                 }
@@ -1732,8 +1796,9 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
                         throw new IllegalStateException("Conflicting filter-class for filter "+name+" in "+descriptor.getResource());
                     break;
                 }
+                default:
+                    LOG.warn(new Throwable()); // TODO throw ISE?
             }
-           
         }
 
         Iterator<XmlParser.Node>  iter = node.iterator("init-param");
@@ -1742,14 +1807,13 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
             XmlParser.Node paramNode = iter.next();
             String pname = paramNode.getString("param-name", false, true);
             String pvalue = paramNode.getString("param-value", false, true);
-            
-            Origin origin = context.getMetaData().getOrigin(name+".filter.init-param."+pname);
-            switch (origin)
+
+            switch (context.getMetaData().getOrigin(name+".filter.init-param."+pname))
             {
                 case NotSet:
                 {
                     //init-param not already set, so set it
-                    holder.setInitParameter(pname, pvalue); 
+                    holder.setInitParameter(pname, pvalue);
                     context.getMetaData().setOrigin(name+".filter.init-param."+pname, descriptor);
                     break;
                 }
@@ -1761,7 +1825,7 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
                     //otherwise just ignore it
                     if (!(descriptor instanceof FragmentDescriptor))
                     {
-                        holder.setInitParameter(pname, pvalue); 
+                        holder.setInitParameter(pname, pvalue);
                         context.getMetaData().setOrigin(name+".filter.init-param."+pname, descriptor);
                     }
                     break;
@@ -1773,7 +1837,9 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
                         throw new IllegalStateException("Mismatching init-param "+pname+"="+pvalue+" in "+descriptor.getResource());
                     break;
                 }
-            }  
+                default:
+                    LOG.warn(new Throwable()); // TODO throw ISE?
+            }
         }
 
         String async=node.getString("async-supported",false,true);
@@ -1782,8 +1848,7 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
         if (async!=null)
         {
             boolean val = async.length()==0||Boolean.valueOf(async);
-            Origin o = context.getMetaData().getOrigin(name+".filter.async-supported");
-            switch (o)
+            switch (context.getMetaData().getOrigin(name+".filter.async-supported"))
             {
                 case NotSet:
                 {
@@ -1800,8 +1865,8 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
                     if (!(descriptor instanceof FragmentDescriptor))
                     {
                         holder.setAsyncSupported(val);
-                        context.getMetaData().setOrigin(name+".filter.async-supported", descriptor);  
-                    }             
+                        context.getMetaData().setOrigin(name+".filter.async-supported", descriptor);
+                    }
                     break;
                 }
                 case WebFragment:
@@ -1811,9 +1876,10 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
                         throw new IllegalStateException("Conflicting async-supported="+async+" for filter "+name+" in "+descriptor.getResource());
                     break;
                 }
+                default:
+                    LOG.warn(new Throwable()); // TODO throw ISE?
             }
         }
-        
     }
 
     /**
@@ -1821,19 +1887,14 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
      * @param descriptor
      * @param node
      */
-    protected void visitFilterMapping(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
+    public void visitFilterMapping(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
     {
         //Servlet Spec 3.0, p74
         //filter-mappings are always additive, whether from web xml descriptors (web.xml/web-default.xml/web-override.xml) or web-fragments.
         //Maintenance update 3.0a to spec:
-        //  Updated 8.2.3.g.v to say <servlet-mapping> elements are additive across web-fragments. 
-      
-        
+        //  Updated 8.2.3.g.v to say <servlet-mapping> elements are additive across web-fragments.
         String filter_name = node.getString("filter-name", false, true);
-        
-        Origin origin = context.getMetaData().getOrigin(filter_name+".filter.mappings");
-        
-        switch (origin)
+        switch (context.getMetaData().getOrigin(filter_name+".filter.mappings"))
         {
             case NotSet:
             {
@@ -1859,16 +1920,18 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
                 addFilterMapping(filter_name, node, context, descriptor);
                 break;
             }
+            default:
+                LOG.warn(new Throwable()); // TODO throw ISE?
         }
     }
 
-    
+
     /**
      * @param context
      * @param descriptor
      * @param node
      */
-    protected void visitListener(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
+    public void visitListener(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
     {
         String className = node.getString("listener-class", false, true);
         EventListener listener = null;
@@ -1887,7 +1950,7 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
                             return;
                     }
                 }
-                
+
                 ((WebDescriptor)descriptor).addClassName(className);
 
                 Class<? extends EventListener> listenerClass = (Class<? extends EventListener>)context.loadClass(className);
@@ -1899,7 +1962,7 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
                 }
                 context.addEventListener(listener);
                 context.getMetaData().setOrigin(className+".listener", descriptor);
-                
+
             }
         }
         catch (Exception e)
@@ -1908,13 +1971,13 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
             return;
         }
     }
-    
+
     /**
      * @param context
      * @param descriptor
      * @param node
      */
-    protected void visitDistributable(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
+    public void visitDistributable(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
     {
         // the element has no content, so its simple presence
         // indicates that the webapp is distributable...
@@ -1922,6 +1985,21 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
         ((WebDescriptor)descriptor).setDistributable(true);
     }
     
+    
+    /**
+     * Servlet spec 3.1. When present in web.xml, this means that http methods that are
+     * not covered by security constraints should have access denied.
+     * 
+     * See section 13.8.4, pg 145
+     * @param context
+     * @param descriptor
+     * @param node
+     */
+    public void visitDenyUncoveredHttpMethods(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
+    {
+        ((ConstraintAware)context.getSecurityHandler()).setDenyUncoveredHttpMethods(true);
+    }
+
     /**
      * @param context
      * @param clazz
@@ -1930,32 +2008,25 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
      * @throws InstantiationException
      * @throws IllegalAccessException
      */
-    protected EventListener newListenerInstance(WebAppContext context,Class<? extends EventListener> clazz) throws ServletException, InstantiationException, IllegalAccessException
+    public EventListener newListenerInstance(WebAppContext context,Class<? extends EventListener> clazz) throws Exception
     {
-        try
-        {
-            return context.getServletContext().createListener(clazz);
-        }
-        catch (ServletException se)
-        {
-            Throwable cause = se.getRootCause();
-            if (cause instanceof InstantiationException)
-                throw (InstantiationException)cause;
-            if (cause instanceof IllegalAccessException)
-                throw (IllegalAccessException)cause;
-            throw se;
-        }
+        ListenerHolder h = context.getServletHandler().newListenerHolder(Source.DESCRIPTOR);
+        EventListener l = context.getServletContext().createInstance(clazz);
+        h.setListener(l);
+        context.getServletHandler().addListener(h);
+        return l;
+
     }
-    
+
     /**
      * @param p
      * @return the normalized pattern
      */
-    protected String normalizePattern(String p)
+    public String normalizePattern(String p)
     {
         if (p != null && p.length() > 0 && !p.startsWith("/") && !p.startsWith("*")) return "/" + p;
         return p;
     }
 
-  
+
 }
diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/TagLibConfiguration.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/TagLibConfiguration.java
deleted file mode 100644
index 97e37e5..0000000
--- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/TagLibConfiguration.java
+++ /dev/null
@@ -1,530 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.webapp;
-
-import java.io.IOException;
-import java.net.URI;
-import java.net.URL;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.EventListener;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Set;
-
-import javax.servlet.Servlet;
-import javax.servlet.ServletContextEvent;
-import javax.servlet.ServletContextListener;
-
-import org.eclipse.jetty.util.Loader;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.util.resource.Resource;
-import org.eclipse.jetty.xml.XmlParser;
-
-/* ------------------------------------------------------------ */
-/** TagLibConfiguration.
- * 
- * The class searches for TLD descriptors found in web.xml, in WEB-INF/*.tld files of the web app
- * or *.tld files within jars found in WEB-INF/lib of the webapp.   Any listeners defined in these
- * tld's are added to the context.
- * 
- * <bile>This is total rubbish special case for JSPs! If there was a general use-case for web app
- * frameworks to register listeners directly, then a generic mechanism could have been added to the servlet
- * spec.  Instead some special purpose JSP support is required that breaks all sorts of encapsulation rules as
- * the servlet container must go searching for and then parsing the descriptors for one particular framework.
- * It only appears to be used by JSF, which is being developed by the same developer who implemented this
- * feature in the first place!
- * </bile>
- * 
- * 
- * Note- this has been superceded by the new TldScanner in jasper which uses ServletContainerInitializer to
- * find all the listeners in tag libs and register them.
- */
-public class TagLibConfiguration extends AbstractConfiguration
-{
-    private static final Logger LOG = Log.getLogger(TagLibConfiguration.class);
-
-    public static final String TLD_RESOURCES = "org.eclipse.jetty.tlds";
-    
-  
-    /**
-     * TagLibListener
-     *
-     * A listener that does the job of finding .tld files that contain
-     * (other) listeners that need to be called by the servlet container.
-     * 
-     * This implementation is necessitated by the fact that it is only
-     * after all the Configuration classes have run that we will
-     * parse web.xml/fragments etc and thus find tlds mentioned therein.
-     * 
-     * Note: TagLibConfiguration is not used in jetty-8 as jasper (JSP engine)
-     * uses the new TldScanner class - a ServletContainerInitializer from
-     * Servlet Spec 3 - to find all listeners in taglibs and register them
-     * with the servlet container.
-     */
-    public  class TagLibListener implements ServletContextListener {
-        private List<EventListener> _tldListeners;
-        private WebAppContext _context;       
-        
-        public TagLibListener (WebAppContext context) {
-            _context = context;
-        }
-
-        public void contextDestroyed(ServletContextEvent sce)
-        {
-            if (_tldListeners == null)
-                return;
-            
-            for (int i=_tldListeners.size()-1; i>=0; i--) {
-                EventListener l = _tldListeners.get(i);
-                if (l instanceof ServletContextListener) {
-                    ((ServletContextListener)l).contextDestroyed(sce);
-                }
-            }
-        }
-
-        public void contextInitialized(ServletContextEvent sce)
-        {
-            try 
-            {
-                //For jasper 2.1: 
-                //Get the system classpath tlds and tell jasper about them, if jasper is on the classpath
-                try
-                {
-
-                    ClassLoader loader = _context.getClassLoader();
-                    if (loader == null || loader.getParent() == null)
-                        loader = getClass().getClassLoader();
-                    else
-                        loader = loader.getParent();
-                    Class<?> clazz = loader.loadClass("org.apache.jasper.compiler.TldLocationsCache");
-                    assert clazz!=null;
-                    Collection<Resource> tld_resources = (Collection<Resource>)_context.getAttribute(TLD_RESOURCES);
-                   
-                    Map<URI, List<String>> tldMap = new HashMap<URI, List<String>>();
-                    
-                    if (tld_resources != null)
-                    {
-                        //get the jar file names of the files
-                        for (Resource r:tld_resources)
-                        {
-                            Resource jarResource = extractJarResource(r);
-                            //jasper is happy with an empty list of tlds
-                            if (!tldMap.containsKey(jarResource.getURI()))
-                                tldMap.put(jarResource.getURI(), null);
-
-                        }
-                        //set the magic context attribute that tells jasper about the system tlds
-                        sce.getServletContext().setAttribute("com.sun.appserv.tld.map", tldMap);
-                    }
-                }
-                catch (ClassNotFoundException e)
-                {
-                    LOG.ignore(e);
-                }
-               
-                //find the tld files and parse them to get out their
-                //listeners
-                Set<Resource> tlds = findTldResources();
-                List<TldDescriptor> descriptors = parseTlds(tlds);
-                processTlds(descriptors);
-                
-                if (_tldListeners == null)
-                    return;
-                
-                //call the listeners that are ServletContextListeners, put the
-                //rest into the context's list of listeners to call at the appropriate
-                //moment
-                for (EventListener l:_tldListeners) {
-                    if (l instanceof ServletContextListener) {
-                        ((ServletContextListener)l).contextInitialized(sce);
-                    } else {
-                        _context.addEventListener(l);
-                    }
-                }
-                
-            } 
-            catch (Exception e) {
-                LOG.warn(e);
-            }
-        }
-
-
-        
-        
-        private Resource extractJarResource (Resource r)
-        {
-            if (r == null)
-                return null;
-            
-            try
-            {
-                String url = r.getURI().toURL().toString();
-                int idx = url.lastIndexOf("!/");
-                if (idx >= 0)
-                    url = url.substring(0, idx);
-                if (url.startsWith("jar:"))
-                    url = url.substring(4);
-                return Resource.newResource(url);
-            }
-            catch (IOException e)
-            {
-                LOG.warn(e);
-                return null;
-            }
-        }
-    
-        /**
-         * Find all the locations that can harbour tld files that may contain
-         * a listener which the web container is supposed to instantiate and
-         * call.
-         * 
-         * @return
-         * @throws IOException
-         */
-        private Set<Resource> findTldResources () throws IOException {
-            
-            Set<Resource> tlds = new HashSet<Resource>();
-            
-            // Find tld's from web.xml
-            // When web.xml was processed, it should have created aliases for all TLDs.  So search resources aliases
-            // for aliases ending in tld
-            if (_context.getResourceAliases()!=null && 
-                    _context.getBaseResource()!=null && 
-                    _context.getBaseResource().exists())
-            {
-                Iterator<String> iter=_context.getResourceAliases().values().iterator();
-                while(iter.hasNext())
-                {
-                    String location = iter.next();
-                    if (location!=null && location.toLowerCase(Locale.ENGLISH).endsWith(".tld"))
-                    {
-                        if (!location.startsWith("/"))
-                            location="/WEB-INF/"+location;
-                        Resource l=_context.getBaseResource().addPath(location);
-                        tlds.add(l);
-                    }
-                }
-            }
-            
-            // Look for any tlds in WEB-INF directly.
-            Resource web_inf = _context.getWebInf();
-            if (web_inf!=null)
-            {
-                String[] contents = web_inf.list();
-                for (int i=0;contents!=null && i<contents.length;i++)
-                {
-                    if (contents[i]!=null && contents[i].toLowerCase(Locale.ENGLISH).endsWith(".tld"))
-                    {
-                        Resource l=web_inf.addPath(contents[i]);
-                        tlds.add(l);
-                    }
-                }
-            }
-            
-            //Look for tlds in common location of WEB-INF/tlds
-            if (web_inf != null) {
-                Resource web_inf_tlds = _context.getWebInf().addPath("/tlds/");
-                if (web_inf_tlds.exists() && web_inf_tlds.isDirectory()) {
-                    String[] contents = web_inf_tlds.list();
-                    for (int i=0;contents!=null && i<contents.length;i++)
-                    {
-                        if (contents[i]!=null && contents[i].toLowerCase(Locale.ENGLISH).endsWith(".tld"))
-                        {
-                            Resource l=web_inf_tlds.addPath(contents[i]);
-                            tlds.add(l);
-                        }
-                    }
-                } 
-            }
-
-            // Add in tlds found in META-INF of jars. The jars that will be scanned are controlled by
-            // the patterns defined in the context attributes: org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern,
-            // and org.eclipse.jetty.server.webapp.WebInfIncludeJarPattern
-            @SuppressWarnings("unchecked")
-            Collection<Resource> tld_resources=(Collection<Resource>)_context.getAttribute(TLD_RESOURCES);
-            if (tld_resources!=null)
-                tlds.addAll(tld_resources);
-            
-            return tlds;
-        }
-        
-        
-        /**
-         * Parse xml into in-memory tree
-         * @param tlds
-         * @return
-         */
-        private List<TldDescriptor> parseTlds (Set<Resource> tlds) {         
-            List<TldDescriptor> descriptors = new ArrayList<TldDescriptor>();
-            
-            Resource tld = null;
-            Iterator<Resource> iter = tlds.iterator();
-            while (iter.hasNext())
-            {
-                try
-                {
-                    tld = iter.next();
-                    if (LOG.isDebugEnabled()) LOG.debug("TLD="+tld);
-                   
-                    TldDescriptor d = new TldDescriptor(tld);
-                    d.parse();
-                    descriptors.add(d);
-                }
-                catch(Exception e)
-                {
-                    LOG.warn("Unable to parse TLD: " + tld,e);
-                }
-            }
-            return descriptors;
-        }
-        
-        
-        /**
-         * Create listeners from the parsed tld trees
-         * @param descriptors
-         * @throws Exception
-         */
-        private void processTlds (List<TldDescriptor> descriptors) throws Exception {
-
-            TldProcessor processor = new TldProcessor();
-            for (TldDescriptor d:descriptors)
-                processor.process(_context, d); 
-            
-            _tldListeners = new ArrayList<EventListener>(processor.getListeners());
-        }
-    }
-    
-    
-    
-    
-    /**
-     * TldDescriptor
-     *
-     *
-     */
-    public static class TldDescriptor extends Descriptor
-    {
-        protected static XmlParser __parserSingleton;
-
-        public TldDescriptor(Resource xml)
-        {
-            super(xml);
-        }
-
-        @Override
-        public void ensureParser() throws ClassNotFoundException
-        {
-           if (__parserSingleton == null)
-               __parserSingleton = newParser();
-            _parser = __parserSingleton;
-        }
-
-        @Override
-        public XmlParser newParser() throws ClassNotFoundException
-        {
-            // Create a TLD parser
-            XmlParser parser = new XmlParser(false);
-            
-            URL taglib11=null;
-            URL taglib12=null;
-            URL taglib20=null;
-            URL taglib21=null;
-
-            try
-            {
-                Class<?> jsp_page = Loader.loadClass(WebXmlConfiguration.class,"javax.servlet.jsp.JspPage");
-                taglib11=jsp_page.getResource("javax/servlet/jsp/resources/web-jsptaglibrary_1_1.dtd");
-                taglib12=jsp_page.getResource("javax/servlet/jsp/resources/web-jsptaglibrary_1_2.dtd");
-                taglib20=jsp_page.getResource("javax/servlet/jsp/resources/web-jsptaglibrary_2_0.xsd");
-                taglib21=jsp_page.getResource("javax/servlet/jsp/resources/web-jsptaglibrary_2_1.xsd");
-            }
-            catch(Exception e)
-            {
-                LOG.ignore(e);
-            }
-            finally
-            {
-                if(taglib11==null)
-                    taglib11=Loader.getResource(Servlet.class,"javax/servlet/jsp/resources/web-jsptaglibrary_1_1.dtd",true);
-                if(taglib12==null)
-                    taglib12=Loader.getResource(Servlet.class,"javax/servlet/jsp/resources/web-jsptaglibrary_1_2.dtd",true);
-                if(taglib20==null)
-                    taglib20=Loader.getResource(Servlet.class,"javax/servlet/jsp/resources/web-jsptaglibrary_2_0.xsd",true);
-                if(taglib21==null)
-                    taglib21=Loader.getResource(Servlet.class,"javax/servlet/jsp/resources/web-jsptaglibrary_2_1.xsd",true);
-            }
-            
-
-            if(taglib11!=null)
-            {
-                redirect(parser, "web-jsptaglib_1_1.dtd",taglib11);  
-                redirect(parser, "web-jsptaglibrary_1_1.dtd",taglib11);
-            }
-            if(taglib12!=null)
-            {
-                redirect(parser, "web-jsptaglib_1_2.dtd",taglib12);
-                redirect(parser, "web-jsptaglibrary_1_2.dtd",taglib12);
-            }
-            if(taglib20!=null)
-            {
-                redirect(parser, "web-jsptaglib_2_0.xsd",taglib20);
-                redirect(parser, "web-jsptaglibrary_2_0.xsd",taglib20);
-            }
-            if(taglib21!=null)
-            {
-                redirect(parser, "web-jsptaglib_2_1.xsd",taglib21);
-                redirect(parser, "web-jsptaglibrary_2_1.xsd",taglib21);
-            }
-            
-            parser.setXpath("/taglib/listener/listener-class");
-            return parser;
-        }
-        
-        public void parse ()
-        throws Exception
-        {
-            ensureParser();
-            try
-            {
-                //xerces on apple appears to sometimes close the zip file instead
-                //of the inputstream, so try opening the input stream, but if
-                //that doesn't work, fallback to opening a new url
-                _root = _parser.parse(_xml.getInputStream());
-            }
-            catch (Exception e)
-            {
-                _root = _parser.parse(_xml.getURL().toString());
-            }
-
-            if (_root==null)
-            {
-                LOG.warn("No TLD root in {}",_xml);
-            }
-        }
-    }
-    
-    
-    /**
-     * TldProcessor
-     *
-     * Process TldDescriptors representing tag libs to find listeners.
-     */
-    public class TldProcessor extends IterativeDescriptorProcessor
-    {
-        public static final String TAGLIB_PROCESSOR = "org.eclipse.jetty.tagLibProcessor";
-        XmlParser _parser;
-        List<XmlParser.Node> _roots = new ArrayList<XmlParser.Node>();
-        List<EventListener> _listeners;
-        
-        
-        public TldProcessor ()
-        throws Exception
-        {  
-            _listeners = new ArrayList<EventListener>();
-            registerVisitor("listener", this.getClass().getDeclaredMethod("visitListener", __signature));
-        }
-      
-
-        public void visitListener (WebAppContext context, Descriptor descriptor, XmlParser.Node node)
-        {     
-            String className=node.getString("listener-class",false,true);
-            if (LOG.isDebugEnabled()) 
-                LOG.debug("listener="+className);
-
-            try
-            {
-                Class<?> listenerClass = context.loadClass(className);
-                EventListener l = (EventListener)listenerClass.newInstance();
-                _listeners.add(l);
-            }
-            catch(Exception e)
-            {
-                LOG.warn("Could not instantiate listener "+className+": "+e);
-                LOG.debug(e);
-            }
-            catch(Error e)
-            {
-                LOG.warn("Could not instantiate listener "+className+": "+e);
-                LOG.debug(e);
-            }
-
-        }
-
-        @Override
-        public void end(WebAppContext context, Descriptor descriptor)
-        {
-        }
-
-        @Override
-        public void start(WebAppContext context, Descriptor descriptor)
-        {  
-        }
-        
-        public List<EventListener> getListeners() {
-            return _listeners;
-        }
-    }
-
-
-    @Override
-    public void preConfigure(WebAppContext context) throws Exception
-    {
-        try
-        {
-            Class<?> jsp_page = Loader.loadClass(WebXmlConfiguration.class,"javax.servlet.jsp.JspPage");
-        }
-        catch (Exception e)
-        {
-            //no jsp available, don't parse TLDs
-            return;
-        }
-
-        TagLibListener tagLibListener = new TagLibListener(context);
-        context.addEventListener(tagLibListener);
-    }
-    
-
-    @Override
-    public void configure (WebAppContext context) throws Exception
-    {         
-    }
-
-    @Override
-    public void postConfigure(WebAppContext context) throws Exception
-    {     
-    }
-
-
-    @Override
-    public void cloneConfigure(WebAppContext template, WebAppContext context) throws Exception
-    {
-    }
-
-
-    @Override
-    public void deconfigure(WebAppContext context) throws Exception
-    {
-    } 
-}
diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebAppClassLoader.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebAppClassLoader.java
index ef7a6b4..fa30de5 100644
--- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebAppClassLoader.java
+++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebAppClassLoader.java
@@ -20,6 +20,10 @@ package org.eclipse.jetty.webapp;
 
 import java.io.File;
 import java.io.IOException;
+import java.io.InputStream;
+import java.lang.instrument.ClassFileTransformer;
+import java.lang.instrument.IllegalClassFormatException;
+import java.lang.instrument.Instrumentation;
 import java.net.URL;
 import java.net.URLClassLoader;
 import java.security.CodeSource;
@@ -32,7 +36,9 @@ import java.util.List;
 import java.util.Locale;
 import java.util.Set;
 import java.util.StringTokenizer;
+import java.util.concurrent.CopyOnWriteArrayList;
 
+import org.eclipse.jetty.util.IO;
 import org.eclipse.jetty.util.StringUtil;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
@@ -66,6 +72,7 @@ public class WebAppClassLoader extends URLClassLoader
     private final ClassLoader _parent;
     private final Set<String> _extensions=new HashSet<String>();
     private String _name=String.valueOf(hashCode());
+    private final List<ClassFileTransformer> _transformers = new CopyOnWriteArrayList<>();
     
     /* ------------------------------------------------------------ */
     /** The Context in which the classloader operates.
@@ -215,7 +222,7 @@ public class WebAppClassLoader extends URLClassLoader
      * with '/'.
      */
     public void addClassPath(String classPath)
-    	throws IOException
+        throws IOException
     {
         if (classPath == null)
             return;
@@ -242,7 +249,11 @@ public class WebAppClassLoader extends URLClassLoader
                 else if (resource.isDirectory())
                     addURL(resource.getURL());
                 else
-                    throw new IllegalArgumentException("!file: "+resource);
+                {
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("Check file exists and is not nested jar: "+resource);
+                    throw new IllegalArgumentException("File not resolvable or incompatible with URLClassloader: "+resource);
+                }
             }
         }
     }
@@ -291,15 +302,16 @@ public class WebAppClassLoader extends URLClassLoader
     }
 
     /* ------------------------------------------------------------ */
+    @Override
     public PermissionCollection getPermissions(CodeSource cs)
     {
-        // TODO check CodeSource
         PermissionCollection permissions=_context.getPermissions();
         PermissionCollection pc= (permissions == null) ? super.getPermissions(cs) : permissions;
         return pc;
     }
 
     /* ------------------------------------------------------------ */
+    @Override
     public Enumeration<URL> getResources(String name) throws IOException
     {
         boolean system_class=_context.isSystemClass(name);
@@ -333,12 +345,20 @@ public class WebAppClassLoader extends URLClassLoader
      * should one be present. This is non-standard and it is recommended 
      * to not rely on this behavior
      */
+    @Override
     public URL getResource(String name)
     {
         URL url= null;
         boolean tried_parent= false;
-        boolean system_class=_context.isSystemClass(name);
-        boolean server_class=_context.isServerClass(name);
+
+        //If the resource is a class name with .class suffix, strip it off before comparison
+        //as the server and system patterns are specified without a .class suffix
+        String tmp = name;
+        if (tmp != null && tmp.endsWith(".class"))
+            tmp = tmp.substring(0, tmp.length()-6);
+      
+        boolean system_class=_context.isSystemClass(tmp);
+        boolean server_class=_context.isServerClass(tmp);
         
         if (system_class && server_class)
             return null;
@@ -429,19 +449,116 @@ public class WebAppClassLoader extends URLClassLoader
         if (c == null && _parent!=null && !tried_parent && !server_class )
             c= _parent.loadClass(name);
 
-        if (c == null)
+        if (c == null && ex!=null)
             throw ex;
 
         if (resolve)
             resolveClass(c);
 
         if (LOG.isDebugEnabled())
-            LOG.debug("loaded " + c+ " from "+c.getClassLoader());
+            LOG.debug("loaded {} from {}",c,c==null?null:c.getClassLoader());
         
         return c;
     }
 
     /* ------------------------------------------------------------ */
+    /**
+     * @see addTransformer
+     * @deprecated
+     */
+    public void addClassFileTransformer(ClassFileTransformer transformer)
+    {
+        _transformers.add(transformer);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @see removeTransformer
+     * @deprecated
+     */
+    public boolean removeClassFileTransformer(ClassFileTransformer transformer)
+    {
+        return _transformers.remove(transformer);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see addClassFileTransformer
+     */
+    public void addTransformer(ClassFileTransformer transformer)
+    {
+        _transformers.add(transformer);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @see removeClassFileTransformer
+     */
+    public boolean removeTransformer(ClassFileTransformer transformer)
+    {
+        return _transformers.remove(transformer);
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    protected Class<?> findClass(final String name) throws ClassNotFoundException
+    {
+        Class<?> clazz=null;
+
+        if (_transformers.isEmpty())
+            clazz = super.findClass(name);
+        else
+        {
+            String path = name.replace('.', '/').concat(".class");
+            URL url = getResource(path);
+            if (url==null)
+                throw new ClassNotFoundException(name);
+
+            InputStream content=null;
+            try
+            {
+                content = url.openStream();
+                byte[] bytes = IO.readBytes(content);
+                    
+                for (ClassFileTransformer transformer : _transformers)
+                {
+                    byte[] tmp = transformer.transform(this,name,null,null,bytes);
+                    if (tmp != null)
+                        bytes = tmp;
+                }
+                
+                clazz=defineClass(name,bytes,0,bytes.length);
+            }
+            catch (IOException e)
+            {
+                throw new ClassNotFoundException(name,e);
+            }
+            catch (IllegalClassFormatException e)
+            {
+                throw new ClassNotFoundException(name,e);
+            }
+            finally
+            {
+                if (content!=null)
+                {
+                    try
+                    {
+                        content.close(); 
+                    }
+                    catch (IOException e)
+                    {
+                        throw new ClassNotFoundException(name,e);
+                    }
+                }
+            }
+        }
+
+        return clazz;
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
     public String toString()
     {
         return "WebAppClassLoader=" + _name+"@"+Long.toHexString(hashCode());
diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebAppContext.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebAppContext.java
index 547aca5..15bae85 100644
--- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebAppContext.java
+++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebAppContext.java
@@ -34,15 +34,13 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
-import javax.servlet.HttpMethodConstraintElement;
 import javax.servlet.ServletContext;
 import javax.servlet.ServletRegistration.Dynamic;
 import javax.servlet.ServletSecurityElement;
-import javax.servlet.annotation.ServletSecurity.EmptyRoleSemantic;
-import javax.servlet.annotation.ServletSecurity.TransportGuarantee;
 import javax.servlet.http.HttpSessionActivationListener;
 import javax.servlet.http.HttpSessionAttributeListener;
 import javax.servlet.http.HttpSessionBindingListener;
+import javax.servlet.http.HttpSessionIdListener;
 import javax.servlet.http.HttpSessionListener;
 
 import org.eclipse.jetty.security.ConstraintAware;
@@ -58,16 +56,15 @@ import org.eclipse.jetty.server.session.SessionHandler;
 import org.eclipse.jetty.servlet.ErrorPageErrorHandler;
 import org.eclipse.jetty.servlet.ServletContextHandler;
 import org.eclipse.jetty.servlet.ServletHandler;
-import org.eclipse.jetty.util.LazyList;
 import org.eclipse.jetty.util.Loader;
 import org.eclipse.jetty.util.MultiException;
-import org.eclipse.jetty.util.StringUtil;
 import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
 import org.eclipse.jetty.util.resource.Resource;
 import org.eclipse.jetty.util.resource.ResourceCollection;
-import org.eclipse.jetty.util.security.Constraint;
 
 /* ------------------------------------------------------------ */
 /** Web Application Context Handler.
@@ -82,6 +79,7 @@ import org.eclipse.jetty.util.security.Constraint;
  * @org.apache.xbean.XBean description="Creates a servlet web application at a given context from a resource base"
  *
  */
+ at ManagedObject("Web Application ContextHandler")
 public class WebAppContext extends ServletContextHandler implements WebAppClassLoader.Context
 {
     private static final Logger LOG = Log.getLogger(WebAppContext.class);
@@ -90,20 +88,18 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
     public static final String BASETEMPDIR = "org.eclipse.jetty.webapp.basetempdir";
     public final static String WEB_DEFAULTS_XML="org/eclipse/jetty/webapp/webdefault.xml";
     public final static String ERROR_PAGE="org.eclipse.jetty.server.error_page";
-    public final static String SERVER_CONFIG = "org.eclipse.jetty.webapp.configuration";
     public final static String SERVER_SYS_CLASSES = "org.eclipse.jetty.webapp.systemClasses";
     public final static String SERVER_SRV_CLASSES = "org.eclipse.jetty.webapp.serverClasses";
-    
+
     private String[] __dftProtectedTargets = {"/web-inf", "/meta-inf"};
-    
-    private static String[] __dftConfigurationClasses =
+
+    public static final String[] DEFAULT_CONFIGURATION_CLASSES =
     {
         "org.eclipse.jetty.webapp.WebInfConfiguration",
         "org.eclipse.jetty.webapp.WebXmlConfiguration",
         "org.eclipse.jetty.webapp.MetaInfConfiguration",
         "org.eclipse.jetty.webapp.FragmentConfiguration",
-        "org.eclipse.jetty.webapp.JettyWebXmlConfiguration"//,
-        //"org.eclipse.jetty.webapp.TagLibConfiguration"
+        "org.eclipse.jetty.webapp.JettyWebXmlConfiguration"
     } ;
 
     // System classes are classes that cannot be replaced by
@@ -115,14 +111,17 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
         "javax.",                           // Java SE classes (per servlet spec v2.5 / SRV.9.7.2)
         "org.xml.",                         // needed by javax.xml
         "org.w3c.",                         // needed by javax.xml
-        "org.apache.commons.logging.",      // TODO: review if special case still needed
+        "org.eclipse.jetty.jmx.",           // webapp cannot change jmx classes
+        "org.eclipse.jetty.util.annotation.",  // webapp cannot change jmx annotations
         "org.eclipse.jetty.continuation.",  // webapp cannot change continuation classes
         "org.eclipse.jetty.jndi.",          // webapp cannot change naming classes
-        "org.eclipse.jetty.plus.jaas.",     // webapp cannot change jaas classes
-        "org.eclipse.jetty.websocket.WebSocket", // WebSocket is a jetty extension
-        "org.eclipse.jetty.websocket.WebSocketFactory", // WebSocket is a jetty extension
-        "org.eclipse.jetty.websocket.WebSocketServlet", // webapp cannot change WebSocketServlet
-        "org.eclipse.jetty.servlet.DefaultServlet" // webapp cannot change default servlets
+        "org.eclipse.jetty.jaas.",          // webapp cannot change jaas classes
+        "org.eclipse.jetty.websocket.",     // webapp cannot change / replace websocket classes
+        "org.eclipse.jetty.util.log.",      // webapp should use server log
+        "org.eclipse.jetty.servlet.ServletContextHandler.Decorator", // for CDI / weld use
+        "org.eclipse.jetty.servlet.DefaultServlet", // webapp cannot change default servlets
+        "org.eclipse.jetty.jsp.JettyJspServlet", //webapp cannot change jetty jsp servlet
+        "org.eclipse.jetty.servlets.AsyncGzipFilter" // special case for AsyncGzipFilter
     } ;
 
     // Server classes are classes that are hidden from being
@@ -131,29 +130,36 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
     // it has to include them in its distribution.
     public final static String[] __dftServerClasses =
     {
+        "-org.eclipse.jetty.jmx.",          // don't hide jmx classes
+        "-org.eclipse.jetty.util.annotation.", // don't hide jmx annotation
         "-org.eclipse.jetty.continuation.", // don't hide continuation classes
         "-org.eclipse.jetty.jndi.",         // don't hide naming classes
-        "-org.eclipse.jetty.plus.jaas.",    // don't hide jaas classes
-        "-org.eclipse.jetty.websocket.WebSocket", // WebSocket is a jetty extension
-        "-org.eclipse.jetty.websocket.WebSocketFactory", // WebSocket is a jetty extension
-        "-org.eclipse.jetty.websocket.WebSocketServlet", // don't hide WebSocketServlet
+        "-org.eclipse.jetty.jaas.",         // don't hide jaas classes
+        "-org.eclipse.jetty.servlets.",     // don't hide jetty servlets
         "-org.eclipse.jetty.servlet.DefaultServlet", // don't hide default servlet
+        "-org.eclipse.jetty.jsp.",          //don't hide jsp servlet
         "-org.eclipse.jetty.servlet.listener.", // don't hide useful listeners
+        "-org.eclipse.jetty.websocket.",    // don't hide websocket classes from webapps (allow webapp to use ones from system classloader)
+        "-org.eclipse.jetty.apache.",       // don't hide jetty apache impls
+        "-org.eclipse.jetty.util.log.",     // don't hide server log 
+        "-org.eclipse.jetty.servlet.ServletContextHandler.Decorator", // don't hide CDI / weld interface  
+        "org.objectweb.asm.",               // hide asm used by jetty
+        "org.eclipse.jdt.",                 // hide jdt used by jetty
         "org.eclipse.jetty."                // hide other jetty classes
     } ;
 
-    private String[] _configurationClasses = __dftConfigurationClasses;
+    private final List<String> _configurationClasses = new ArrayList<>();
     private ClasspathPattern _systemClasses = null;
     private ClasspathPattern _serverClasses = null;
 
-    private Configuration[] _configurations;
+    private final List<Configuration> _configurations = new ArrayList<>();
     private String _defaultsDescriptor=WEB_DEFAULTS_XML;
     private String _descriptor=null;
-    private final List<String> _overrideDescriptors = new ArrayList<String>();
+    private final List<String> _overrideDescriptors = new ArrayList<>();
     private boolean _distributable=false;
     private boolean _extractWAR=true;
     private boolean _copyDir=false;
-    private boolean _copyWebInf=false; // TODO change to true?
+    private boolean _copyWebInf=false;
     private boolean _logUrlOnStart =false;
     private boolean _parentLoaderPriority= Boolean.getBoolean("org.eclipse.jetty.server.webapp.parentLoaderPriority");
     private PermissionCollection _permissions;
@@ -161,6 +167,8 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
     private String[] _contextWhiteList = null;
 
     private File _tmpDir;
+    private boolean _persistTmpDir = false;
+ 
     private String _war;
     private String _extraClasspath;
     private Throwable _unavailableException;
@@ -168,12 +176,10 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
     private Map<String, String> _resourceAliases;
     private boolean _ownClassLoader=false;
     private boolean _configurationDiscovered=true;
-    private boolean _configurationClassesSet=false;
-    private boolean _configurationsSet=false;
     private boolean _allowDuplicateFragmentNames = false;
     private boolean _throwUnavailableOnStartupException = false;
-    
-    
+
+
 
     private MetaData _metadata=new MetaData();
 
@@ -192,10 +198,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
     /* ------------------------------------------------------------ */
     public WebAppContext()
     {
-        super(SESSIONS|SECURITY);
-        _scontext=new Context();
-        setErrorHandler(new ErrorPageErrorHandler());
-        setProtectedTargets(__dftProtectedTargets);
+        this(null,null,null,null,null,new ErrorPageErrorHandler(),SESSIONS|SECURITY);
     }
 
     /* ------------------------------------------------------------ */
@@ -205,12 +208,8 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
      */
     public WebAppContext(String webApp,String contextPath)
     {
-        super(null,contextPath,SESSIONS|SECURITY);
-        _scontext=new Context();
-        setContextPath(contextPath);
+        this(null,contextPath,null,null,null,new ErrorPageErrorHandler(),SESSIONS|SECURITY);
         setWar(webApp);
-        setErrorHandler(new ErrorPageErrorHandler());
-        setProtectedTargets(__dftProtectedTargets);
     }
 
     /* ------------------------------------------------------------ */
@@ -221,11 +220,8 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
      */
     public WebAppContext(HandlerContainer parent, String webApp, String contextPath)
     {
-        super(parent,contextPath,SESSIONS|SECURITY);
-        _scontext=new Context();
+        this(parent,contextPath,null,null,null,new ErrorPageErrorHandler(),SESSIONS|SECURITY);
         setWar(webApp);
-        setErrorHandler(new ErrorPageErrorHandler());
-        setProtectedTargets(__dftProtectedTargets);
     }
 
     /* ------------------------------------------------------------ */
@@ -238,8 +234,23 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
      * @param servletHandler ServletHandler for this web app
      * @param errorHandler ErrorHandler for this web app
      */
-    public WebAppContext(SessionHandler sessionHandler, SecurityHandler securityHandler, ServletHandler servletHandler, ErrorHandler errorHandler) {
-        super(null, sessionHandler, securityHandler, servletHandler, errorHandler);
+    public WebAppContext(SessionHandler sessionHandler, SecurityHandler securityHandler, ServletHandler servletHandler, ErrorHandler errorHandler) 
+    {
+        this(null, null, sessionHandler, securityHandler, servletHandler, errorHandler,0);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * This constructor is used in the geronimo integration.
+     *
+     * @param sessionHandler SessionHandler for this web app
+     * @param securityHandler SecurityHandler for this web app
+     * @param servletHandler ServletHandler for this web app
+     * @param errorHandler ErrorHandler for this web app
+     */
+    public WebAppContext(HandlerContainer parent, String contextPath, SessionHandler sessionHandler, SecurityHandler securityHandler, ServletHandler servletHandler, ErrorHandler errorHandler,int options) 
+    {
+        super(parent, contextPath,sessionHandler, securityHandler, servletHandler, errorHandler,options);
         _scontext = new Context();
         setErrorHandler(errorHandler != null ? errorHandler : new ErrorPageErrorHandler());
         setProtectedTargets(__dftProtectedTargets);
@@ -303,7 +314,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
         if (_resourceAliases == null)
             return null;
         String alias = _resourceAliases.get(path);
-        
+
         int slash=path.length();
         while (alias==null)
         {
@@ -312,7 +323,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
                 break;
             String match=_resourceAliases.get(path.substring(0,slash+1));
             if (match!=null)
-                alias=match+path.substring(slash+1);            
+                alias=match+path.substring(slash+1);
         }
         return alias;
     }
@@ -451,10 +462,10 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
         }
 
         // Prepare for configuration
-        for (int i=0;i<_configurations.length;i++)
+        for (Configuration configuration : _configurations)
         {
-            LOG.debug("preConfigure {} with {}",this,_configurations[i]);
-            _configurations[i].preConfigure(this);
+            LOG.debug("preConfigure {} with {}",this,configuration);
+            configuration.preConfigure(this);
         }
     }
 
@@ -462,10 +473,10 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
     public void configure() throws Exception
     {
         // Configure webapp
-        for (int i=0;i<_configurations.length;i++)
+        for (Configuration configuration : _configurations)
         {
-            LOG.debug("configure {} with {}",this,_configurations[i]);
-            _configurations[i].configure(this);
+            LOG.debug("configure {} with {}",this,configuration);
+            configuration.configure(this);
         }
     }
 
@@ -473,10 +484,10 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
     public void postConfigure() throws Exception
     {
         // Clean up after configuration
-        for (int i=0;i<_configurations.length;i++)
+        for (Configuration configuration : _configurations)
         {
-            LOG.debug("postConfigure {} with {}",this,_configurations[i]);
-            _configurations[i].postConfigure(this);
+            LOG.debug("postConfigure {} with {}",this,configuration);
+            configuration.postConfigure(this);
         }
     }
 
@@ -519,8 +530,8 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
 
         try
         {
-            for (int i=_configurations.length;i-->0;)
-                _configurations[i].deconfigure(this);
+            for (int i=_configurations.size();i-->0;)
+                _configurations.get(i).deconfigure(this);
 
             if (_metadata != null)
                 _metadata.clear();
@@ -545,11 +556,11 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
         MultiException mx=new MultiException();
         if (_configurations!=null)
         {
-            for (int i=_configurations.length;i-->0;)
+            for (int i=_configurations.size();i-->0;)
             {
                 try
                 {
-                    _configurations[i].destroy(this);
+                    _configurations.get(i).destroy(this);
                 }
                 catch(Exception e)
                 {
@@ -557,7 +568,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
                 }
             }
         }
-        _configurations=null;
+        _configurations.clear();
         super.destroy();
         mx.ifExceptionThrowRuntime();
     }
@@ -572,12 +583,11 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
         Connector[] connectors = getServer().getConnectors();
         for (int i=0;i<connectors.length;i++)
         {
-            String connectorName = connectors[i].getName();
             String displayName = getDisplayName();
             if (displayName == null)
                 displayName = "WebApp@"+connectors.hashCode();
 
-            LOG.info(displayName + " at http://" + connectorName + getContextPath());
+            LOG.info(displayName + " at http://" + connectors[i].toString() + getContextPath());
         }
     }
 
@@ -585,9 +595,10 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
     /**
      * @return Returns the configurations.
      */
+    @ManagedAttribute(value="configuration classes used to configure webapp", readonly=true)
     public String[] getConfigurationClasses()
     {
-        return _configurationClasses;
+        return _configurationClasses.toArray(new String[_configurationClasses.size()]);
     }
 
     /* ------------------------------------------------------------ */
@@ -596,7 +607,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
      */
     public Configuration[] getConfigurations()
     {
-        return _configurations;
+        return _configurations.toArray(new Configuration[_configurations.size()]);
     }
 
     /* ------------------------------------------------------------ */
@@ -604,6 +615,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
      * The default descriptor is a web.xml format file that is applied to the context before the standard WEB-INF/web.xml
      * @return Returns the defaultsDescriptor.
      */
+    @ManagedAttribute(value="default web.xml deascriptor applied before standard web.xml", readonly=true)
     public String getDefaultsDescriptor()
     {
         return _defaultsDescriptor;
@@ -613,9 +625,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
     /**
      * The override descriptor is a web.xml format file that is applied to the context after the standard WEB-INF/web.xml
      * @return Returns the Override Descriptor.
-     * @deprecated use {@link #getOverrideDescriptors()}
      */
-    @Deprecated
     public String getOverrideDescriptor()
     {
         if (_overrideDescriptors.size()!=1)
@@ -628,6 +638,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
      * An override descriptor is a web.xml format file that is applied to the context after the standard WEB-INF/web.xml
      * @return Returns the Override Descriptor list
      */
+    @ManagedAttribute(value="web.xml deascriptors applied after standard web.xml", readonly=true)
     public List<String> getOverrideDescriptors()
     {
         return Collections.unmodifiableList(_overrideDescriptors);
@@ -637,6 +648,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
     /**
      * @return Returns the permissions.
      */
+    @Override
     public PermissionCollection getPermissions()
     {
         return _permissions;
@@ -647,6 +659,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
      * @see #setServerClasses(String[])
      * @return Returns the serverClasses.
      */
+    @ManagedAttribute(value="classes and packages hidden by the context classloader", readonly=true)
     public String[] getServerClasses()
     {
         if (_serverClasses == null)
@@ -655,12 +668,40 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
         return _serverClasses.getPatterns();
     }
 
-    public void addServerClass(String classname)
+    /* ------------------------------------------------------------ */
+    /** Add to the list of Server classes.
+     * @param classOrPackage A fully qualified class name (eg com.foo.MyClass) 
+     * or a qualified package name ending with '.' (eg com.foo.).  If the class 
+     * or package has '-' it is excluded from the server classes and order is thus
+     * important when added system class patterns. This argument may also be a comma 
+     * separated list of classOrPackage patterns.
+     * @see #setServerClasses(String[])
+     * @see <a href="http://www.eclipse.org/jetty/documentation/current/jetty-classloading.html">Jetty Documentation: Classloading</a>
+     */
+    public void addServerClass(String classOrPackage)
+    {
+        if (_serverClasses == null)
+            loadServerClasses();
+
+        _serverClasses.addPattern(classOrPackage);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Prepend to the list of Server classes.
+     * @param classOrPackage A fully qualified class name (eg com.foo.MyClass) 
+     * or a qualified package name ending with '.' (eg com.foo.).  If the class 
+     * or package has '-' it is excluded from the server classes and order is thus
+     * important when added system class patterns. This argument may also be a comma 
+     * separated list of classOrPackage patterns.
+     * @see #setServerClasses(String[])
+     * @see <a href="http://www.eclipse.org/jetty/documentation/current/jetty-classloading.html">Jetty Documentation: Classloading</a>
+     */
+    public void prependServerClass(String classOrPackage)
     {
         if (_serverClasses == null)
             loadServerClasses();
 
-        _serverClasses.addPattern(classname);
+        _serverClasses.prependPattern(classOrPackage);
     }
 
     /* ------------------------------------------------------------ */
@@ -668,6 +709,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
      * @see #setSystemClasses(String[])
      * @return Returns the systemClasses.
      */
+    @ManagedAttribute(value="classes and packages given priority by context classloader", readonly=true)
     public String[] getSystemClasses()
     {
         if (_systemClasses == null)
@@ -677,15 +719,44 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
     }
 
     /* ------------------------------------------------------------ */
-    public void addSystemClass(String classname)
+    /** Add to the list of System classes.
+     * @param classOrPackage A fully qualified class name (eg com.foo.MyClass) 
+     * or a qualified package name ending with '.' (eg com.foo.).  If the class 
+     * or package has '-' it is excluded from the system classes and order is thus
+     * important when added system class patterns.  This argument may also be a comma 
+     * separated list of classOrPackage patterns.
+     * @see #setSystemClasses(String[])
+     * @see <a href="http://www.eclipse.org/jetty/documentation/current/jetty-classloading.html">Jetty Documentation: Classloading</a>
+     */
+    public void addSystemClass(String classOrPackage)
+    {
+        if (_systemClasses == null)
+            loadSystemClasses();
+
+        _systemClasses.addPattern(classOrPackage);
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /** Prepend to the list of System classes.
+     * @param classOrPackage A fully qualified class name (eg com.foo.MyClass) 
+     * or a qualified package name ending with '.' (eg com.foo.).  If the class 
+     * or package has '-' it is excluded from the system classes and order is thus
+     * important when added system class patterns.This argument may also be a comma 
+     * separated list of classOrPackage patterns.
+     * @see #setSystemClasses(String[])
+     * @see <a href="http://www.eclipse.org/jetty/documentation/current/jetty-classloading.html">Jetty Documentation: Classloading</a>
+     */
+    public void prependSystemClass(String classOrPackage)
     {
         if (_systemClasses == null)
             loadSystemClasses();
 
-        _systemClasses.addPattern(classname);
+        _systemClasses.prependPattern(classOrPackage);
     }
 
     /* ------------------------------------------------------------ */
+    @Override
     public boolean isServerClass(String name)
     {
         if (_serverClasses == null)
@@ -695,6 +766,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
     }
 
     /* ------------------------------------------------------------ */
+    @Override
     public boolean isSystemClass(String name)
     {
         if (_systemClasses == null)
@@ -753,6 +825,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
     /**
      * @return Returns the war as a file or URL string (Resource)
      */
+    @ManagedAttribute(value="war file location", readonly=true)
     public String getWar()
     {
         if (_war==null)
@@ -778,6 +851,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
     /**
      * @return Returns the distributable.
      */
+    @ManagedAttribute("web application distributable")
     public boolean isDistributable()
     {
         return _distributable;
@@ -787,6 +861,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
     /**
      * @return Returns the extractWAR.
      */
+    @ManagedAttribute(value="extract war", readonly=true)
     public boolean isExtractWAR()
     {
         return _extractWAR;
@@ -796,6 +871,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
     /**
      * @return True if the webdir is copied (to allow hot replacement of jars on windows)
      */
+    @ManagedAttribute(value="webdir copied on deploy (allows hot replacement on windows)", readonly=true)
     public boolean isCopyWebDir()
     {
         return _copyDir;
@@ -815,8 +891,11 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
      * @return True if the classloader should delegate first to the parent
      * classloader (standard java behaviour) or false if the classloader
      * should first try to load from WEB-INF/lib or WEB-INF/classes (servlet
-     * spec recommendation).
+     * spec recommendation). Default is false or can be set by the system 
+     * property org.eclipse.jetty.server.webapp.parentLoaderPriority
      */
+    @Override
+    @ManagedAttribute(value="parent classloader given priority", readonly=true)
     public boolean isParentLoaderPriority()
     {
         return _parentLoaderPriority;
@@ -824,9 +903,9 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
 
 
     /* ------------------------------------------------------------ */
-    public String[] getDefaultConfigurationClasses ()
+    public static String[] getDefaultConfigurationClasses ()
     {
-        return __dftConfigurationClasses;
+        return DEFAULT_CONFIGURATION_CLASSES;
     }
 
     /* ------------------------------------------------------------ */
@@ -843,30 +922,30 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
 
     /* ------------------------------------------------------------ */
     protected void loadConfigurations()
-    	throws Exception
+        throws Exception
     {
         //if the configuration instances have been set explicitly, use them
-        if (_configurations!=null)
+        if (_configurations.size()>0)
             return;
-
-        //if the configuration classnames have been set explicitly use them
-        if (!_configurationClassesSet)
-            _configurationClasses=__dftConfigurationClasses;
-
-        _configurations = new Configuration[_configurationClasses.length];
-        for (int i = 0; i < _configurationClasses.length; i++)
-        {
-            _configurations[i]=(Configuration)Loader.loadClass(this.getClass(), _configurationClasses[i]).newInstance();
-        }
+        
+        if (_configurationClasses.size()==0)
+            _configurationClasses.addAll(Configuration.ClassList.serverDefault(getServer()));
+        for (String configClass : _configurationClasses)
+            _configurations.add((Configuration)Loader.loadClass(this.getClass(), configClass).newInstance());
     }
 
-  
-
     /* ------------------------------------------------------------ */
     @Override
     public String toString()
     {
-        return super.toString()+(_war==null?"":(","+_war));
+        if (_war!=null)
+        {
+            String war=_war;
+            if (war.indexOf("/webapps/")>=0)
+                war=war.substring(war.indexOf("/webapps/")+8);
+            return super.toString()+"{"+war+"}";
+        }
+        return super.toString();
     }
 
     /* ------------------------------------------------------------ */
@@ -876,23 +955,30 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
      */
     public void setConfigurationClasses(String[] configurations)
     {
-        if (isRunning())
+        if (isStarted())
             throw new IllegalStateException();
-        _configurationClasses = configurations==null?null:(String[])configurations.clone();
-        _configurationClassesSet = true;
-        _configurations=null;
+        _configurationClasses.clear();
+        if (configurations!=null)
+            _configurationClasses.addAll(Arrays.asList(configurations));
+        _configurations.clear();
     }
 
+    public void setConfigurationClasses(List<String> configurations)
+    {
+        setConfigurationClasses(configurations.toArray(new String[configurations.size()]));
+    }
+    
     /* ------------------------------------------------------------ */
     /**
      * @param configurations The configurations to set.
      */
     public void setConfigurations(Configuration[] configurations)
     {
-        if (isRunning())
+        if (isStarted())
             throw new IllegalStateException();
-        _configurations = configurations==null?null:(Configuration[])configurations.clone();
-        _configurationsSet = true;
+        _configurations.clear();
+        if (configurations!=null)
+            _configurations.addAll(Arrays.asList(configurations));
     }
 
     /* ------------------------------------------------------------ */
@@ -909,9 +995,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
     /**
      * The override descriptor is a web.xml format file that is applied to the context after the standard WEB-INF/web.xml
      * @param overrideDescriptor The overrideDescritpor to set.
-     * @deprecated use {@link #setOverrideDescriptors(List)}
      */
-    @Deprecated
     public void setOverrideDescriptor(String overrideDescriptor)
     {
         _overrideDescriptors.clear();
@@ -943,6 +1027,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
     /**
      * @return the web.xml descriptor to use. If set to null, WEB-INF/web.xml is used if it exists.
      */
+    @ManagedAttribute(value="standard web.xml descriptor", readonly=true)
     public String getDescriptor()
     {
         return _descriptor;
@@ -974,23 +1059,43 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
             _sessionHandler.clearEventListeners();
 
         super.setEventListeners(eventListeners);
+    }
 
-        for (int i=0; eventListeners!=null && i<eventListeners.length;i ++)
+    /* ------------------------------------------------------------ */
+    /** Add EventListener
+     * Convenience method that calls {@link #setEventListeners(EventListener[])}
+     * @param listener
+     */
+    @Override
+    public void addEventListener(EventListener listener)
+    {
+        super.addEventListener(listener);
+        if ((listener instanceof HttpSessionActivationListener)
+            || (listener instanceof HttpSessionAttributeListener)
+            || (listener instanceof HttpSessionBindingListener)
+            || (listener instanceof HttpSessionListener)
+            || (listener instanceof HttpSessionIdListener))
         {
-            EventListener listener = eventListeners[i];
-
-            if ((listener instanceof HttpSessionActivationListener)
-                            || (listener instanceof HttpSessionAttributeListener)
-                            || (listener instanceof HttpSessionBindingListener)
-                            || (listener instanceof HttpSessionListener))
-            {
-                if (_sessionHandler!=null)
-                    _sessionHandler.addEventListener(listener);
-            }
-
+            if (_sessionHandler!=null)
+                _sessionHandler.addEventListener(listener);
         }
     }
-
+    
+    @Override
+    public void removeEventListener(EventListener listener)
+    {
+        super.removeEventListener(listener);
+        if ((listener instanceof HttpSessionActivationListener)
+            || (listener instanceof HttpSessionAttributeListener)
+            || (listener instanceof HttpSessionBindingListener)
+            || (listener instanceof HttpSessionListener)
+            || (listener instanceof HttpSessionIdListener))
+        {
+            if (_sessionHandler!=null)
+                _sessionHandler.removeEventListener(listener);
+        }
+        
+    }
 
 
     /* ------------------------------------------------------------ */
@@ -1022,7 +1127,11 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
 
     /* ------------------------------------------------------------ */
     /**
-     * @param java2compliant The java2compliant to set.
+     * @param java2compliant True if the classloader should delegate first to the parent
+     * classloader (standard java behaviour) or false if the classloader
+     * should first try to load from WEB-INF/lib or WEB-INF/classes (servlet
+     * spec recommendation).  Default is false or can be set by the system 
+     * property org.eclipse.jetty.server.webapp.parentLoaderPriority
      */
     public void setParentLoaderPriority(boolean java2compliant)
     {
@@ -1109,34 +1218,38 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
             catch (IOException e){LOG.warn(Log.EXCEPTION,e);}
         }
 
-        if (dir!=null && !dir.exists())
-        {
-            dir.mkdir();
-            dir.deleteOnExit();
-        }
-
-        if (dir!=null && ( !dir.exists() || !dir.isDirectory() || !dir.canWrite()))
-            throw new IllegalArgumentException("Bad temp directory: "+dir);
-
-        try
-        {
-            if (dir!=null)
-                dir=dir.getCanonicalFile();
-        }
-        catch(Exception e)
-        {
-            LOG.warn(e);
-        }
         _tmpDir=dir;
-        setAttribute(TEMPDIR,_tmpDir);
+        setAttribute(TEMPDIR,_tmpDir);            
     }
 
     /* ------------------------------------------------------------ */
+    @ManagedAttribute(value="temporary directory location", readonly=true)
     public File getTempDirectory ()
     {
         return _tmpDir;
     }
 
+    /**
+     * If true the temp directory for this 
+     * webapp will be kept when the webapp stops. Otherwise,
+     * it will be deleted.
+     * 
+     * @param delete
+     */
+    public void setPersistTempDirectory(boolean persist)
+    {
+        _persistTmpDir = persist;
+    }
+    
+    /**
+     * @return
+     */
+    public boolean isPersistTempDirectory()
+    {
+        return _persistTmpDir;
+    }
+    
+    
     /* ------------------------------------------------------------ */
     /**
      * @param war The war to set as a file name or URL
@@ -1152,6 +1265,8 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
      * pointing to directories or jar files. Directories should end
      * with '/'.
      */
+    @Override
+    @ManagedAttribute(value="extra classpath for context classloader", readonly=true)
     public String getExtraClasspath()
     {
         return _extraClasspath;
@@ -1185,45 +1300,30 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
         this._logUrlOnStart = logOnStart;
     }
 
-
     /* ------------------------------------------------------------ */
     @Override
     public void setServer(Server server)
     {
         super.setServer(server);
-        //if we haven't been given a set of configuration instances to
-        //use, and we haven't been given a set of configuration classes
-        //to use, use the configuration classes that came from the
-        //Server (if there are any)
-        if (!_configurationsSet && !_configurationClassesSet && server != null)
-        {
-            String[] serverConfigs = (String[])server.getAttribute(SERVER_CONFIG);
-            if (serverConfigs != null)
-                setConfigurationClasses(serverConfigs);
-        }
     }
 
-
     /* ------------------------------------------------------------ */
     public boolean isAllowDuplicateFragmentNames()
     {
         return _allowDuplicateFragmentNames;
     }
 
-
     /* ------------------------------------------------------------ */
     public void setAllowDuplicateFragmentNames(boolean allowDuplicateFragmentNames)
     {
         _allowDuplicateFragmentNames = allowDuplicateFragmentNames;
     }
 
-
     /* ------------------------------------------------------------ */
     public void setThrowUnavailableOnStartupException (boolean throwIfStartupException) {
         _throwUnavailableOnStartupException = throwIfStartupException;
     }
 
-
     /* ------------------------------------------------------------ */
     public boolean isThrowUnavailableOnStartupException () {
         return _throwUnavailableOnStartupException;
@@ -1239,14 +1339,20 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
         //resolve the metadata
         _metadata.resolve(this);
 
+        startWebapp();
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void startWebapp()
+        throws Exception
+    {
         super.startContext();
     }
-       
+    
     /* ------------------------------------------------------------ */    
     @Override
     public Set<String> setServletSecurity(Dynamic registration, ServletSecurityElement servletSecurityElement)
     {
-     
         Set<String> unchangedURLMappings = new HashSet<String>();
         //From javadoc for ServletSecurityElement:
         /*
@@ -1267,7 +1373,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
         Collection<String> pathMappings = registration.getMappings();
         if (pathMappings != null)
         {
-            Constraint constraint = ConstraintSecurityHandler.createConstraint(registration.getName(), servletSecurityElement);
+            ConstraintSecurityHandler.createConstraint(registration.getName(), servletSecurityElement);
 
             for (String pathSpec:pathMappings)
             {
@@ -1281,7 +1387,8 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
                         List<ConstraintMapping> mappings = ConstraintSecurityHandler.createConstraintsWithMappingsForPath(registration.getName(), pathSpec, servletSecurityElement);
                         for (ConstraintMapping m:mappings)
                             ((ConstraintAware)getSecurityHandler()).addConstraintMapping(m);
-                        getMetaData().setOrigin("constraint.url."+pathSpec, Origin.API);
+                        ((ConstraintAware)getSecurityHandler()).checkPathsWithUncoveredHttpMethods();
+                        getMetaData().setOriginAPI("constraint.url."+pathSpec);
                         break;
                     }
                     case WebXml:
@@ -1304,6 +1411,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
                         constraintMappings.addAll(freshMappings);
                            
                         ((ConstraintSecurityHandler)getSecurityHandler()).setConstraintMappings(constraintMappings);
+                        ((ConstraintAware)getSecurityHandler()).checkPathsWithUncoveredHttpMethods();
                         break;
                     }
                 }
@@ -1318,6 +1426,32 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
     /* ------------------------------------------------------------ */
     public class Context extends ServletContextHandler.Context
     {
+       
+        /* ------------------------------------------------------------ */
+        @Override
+        public void checkListener(Class<? extends EventListener> listener) throws IllegalStateException
+        {
+            try
+            {
+                super.checkListener(listener);
+            }
+            catch (IllegalArgumentException e)
+            {
+                //not one of the standard servlet listeners, check our extended session listener types
+                boolean ok = false;
+                for (Class l:SessionHandler.SESSION_LISTENER_TYPES)
+                {
+                    if (l.isAssignableFrom(listener))
+                    {
+                        ok = true;
+                        break;
+                    }
+                }
+                if (!ok)
+                    throw new IllegalArgumentException("Inappropriate listener type "+listener.getName());
+            }
+        }
+
         /* ------------------------------------------------------------ */
         @Override
         public URL getResource(String path) throws MalformedURLException
@@ -1363,8 +1497,6 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
                 return servletContext;
             }
         }
-
-        
         
     }
 
@@ -1373,5 +1505,4 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
     {
         return _metadata;
     }
-
 }
diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebDescriptor.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebDescriptor.java
index fe00f58..1a439d9 100644
--- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebDescriptor.java
+++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebDescriptor.java
@@ -30,6 +30,7 @@ import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
 import org.eclipse.jetty.util.resource.Resource;
 import org.eclipse.jetty.xml.XmlParser;
+import org.xml.sax.InputSource;
 
 
 
@@ -41,7 +42,7 @@ import org.eclipse.jetty.xml.XmlParser;
 public class WebDescriptor extends Descriptor
 {
     private static final Logger LOG = Log.getLogger(WebDescriptor.class);
- 
+
     protected static XmlParser _parserSingleton;
     protected MetaDataComplete _metaDataComplete;
     protected int _majorVersion = 3; //default to container version
@@ -51,97 +52,155 @@ public class WebDescriptor extends Descriptor
 
     protected boolean _isOrdered = false;
     protected List<String> _ordering = new ArrayList<String>();
-    
+
     @Override
-    public void ensureParser()
-    throws ClassNotFoundException
+    public void ensureParser() throws ClassNotFoundException
     {
-        if (_parserSingleton == null)
+        synchronized (WebDescriptor.class)
         {
-            _parserSingleton = newParser();
+            if (_parserSingleton == null)
+                _parserSingleton = newParser(isValidating());
         }
-        _parser = _parserSingleton;
+        
+        if (_parserSingleton.isValidating()==isValidating())
+            _parser = _parserSingleton;
+        else
+            _parser = newParser(isValidating());
     }
 
-    
-    public XmlParser newParser()
-    throws ClassNotFoundException
+    public static XmlParser newParser(boolean validating) throws ClassNotFoundException
     {
-        XmlParser xmlParser=new XmlParser();
-        //set up cache of DTDs and schemas locally        
-        URL dtd22=Loader.getResource(Servlet.class,"javax/servlet/resources/web-app_2_2.dtd",true);
-        URL dtd23=Loader.getResource(Servlet.class,"javax/servlet/resources/web-app_2_3.dtd",true);
-        URL j2ee14xsd=Loader.getResource(Servlet.class,"javax/servlet/resources/j2ee_1_4.xsd",true);
-        URL webapp24xsd=Loader.getResource(Servlet.class,"javax/servlet/resources/web-app_2_4.xsd",true);
-        URL webapp25xsd=Loader.getResource(Servlet.class,"javax/servlet/resources/web-app_2_5.xsd",true);
-        URL webapp30xsd=Loader.getResource(Servlet.class,"javax/servlet/resources/web-app_3_0.xsd",true);
-        URL webcommon30xsd=Loader.getResource(Servlet.class,"javax/servlet/resources/web-common_3_0.xsd",true);
-        URL webfragment30xsd=Loader.getResource(Servlet.class,"javax/servlet/resources/web-fragment_3_0.xsd",true);
-        URL schemadtd=Loader.getResource(Servlet.class,"javax/servlet/resources/XMLSchema.dtd",true);
-        URL xmlxsd=Loader.getResource(Servlet.class,"javax/servlet/resources/xml.xsd",true);
-        URL webservice11xsd=Loader.getResource(Servlet.class,"javax/servlet/resources/j2ee_web_services_client_1_1.xsd",true);
-        URL webservice12xsd=Loader.getResource(Servlet.class,"javax/servlet/resources/javaee_web_services_client_1_2.xsd",true);
-        URL datatypesdtd=Loader.getResource(Servlet.class,"javax/servlet/resources/datatypes.dtd",true);
-
-        URL jsp20xsd = null;
-        URL jsp21xsd = null;
-
-        try
-        {
-            Class<?> jsp_page = Loader.loadClass(WebXmlConfiguration.class, "javax.servlet.jsp.JspPage");
-            jsp20xsd = jsp_page.getResource("/javax/servlet/resources/jsp_2_0.xsd");
-            jsp21xsd = jsp_page.getResource("/javax/servlet/resources/jsp_2_1.xsd");
-        }
-        catch (Exception e)
+        XmlParser xmlParser=new XmlParser(validating)
         {
-            LOG.ignore(e);
-        }
-        finally
-        {
-            if (jsp20xsd == null) jsp20xsd = Loader.getResource(Servlet.class, "javax/servlet/resources/jsp_2_0.xsd", true);
-            if (jsp21xsd == null) jsp21xsd = Loader.getResource(Servlet.class, "javax/servlet/resources/jsp_2_1.xsd", true);
-        }
+            boolean mapped=false;
+            
+            @Override
+            protected InputSource resolveEntity(String pid, String sid)
+            {
+                if (!mapped)
+                {
+                    mapResources();
+                    mapped=true;
+                }
+                InputSource is = super.resolveEntity(pid,sid);
+                return is;
+            }
+            
+            void mapResources()
+            {
+                //set up cache of DTDs and schemas locally
+                URL dtd22=Loader.getResource(Servlet.class,"javax/servlet/resources/web-app_2_2.dtd");
+                URL dtd23=Loader.getResource(Servlet.class,"javax/servlet/resources/web-app_2_3.dtd");
+                URL j2ee14xsd=Loader.getResource(Servlet.class,"javax/servlet/resources/j2ee_1_4.xsd");
+                URL javaee5=Loader.getResource(Servlet.class,"javax/servlet/resources/javaee_5.xsd");
+                URL javaee6=Loader.getResource(Servlet.class,"javax/servlet/resources/javaee_6.xsd");
+                URL javaee7=Loader.getResource(Servlet.class,"javax/servlet/resources/javaee_7.xsd");
+
+                URL webapp24xsd=Loader.getResource(Servlet.class,"javax/servlet/resources/web-app_2_4.xsd");
+                URL webapp25xsd=Loader.getResource(Servlet.class,"javax/servlet/resources/web-app_2_5.xsd");
+                URL webapp30xsd=Loader.getResource(Servlet.class,"javax/servlet/resources/web-app_3_0.xsd");
+                URL webapp31xsd=Loader.getResource(Servlet.class,"javax/servlet/resources/web-app_3_1.xsd");
+                
+                URL webcommon30xsd=Loader.getResource(Servlet.class,"javax/servlet/resources/web-common_3_0.xsd");
+                URL webcommon31xsd=Loader.getResource(Servlet.class,"javax/servlet/resources/web-common_3_1.xsd");
+            
+                URL webfragment30xsd=Loader.getResource(Servlet.class,"javax/servlet/resources/web-fragment_3_0.xsd");
+                URL webfragment31xsd=Loader.getResource(Servlet.class,"javax/servlet/resources/web-fragment_3_1.xsd");
+                
+                URL schemadtd=Loader.getResource(Servlet.class,"javax/servlet/resources/XMLSchema.dtd");
+                URL xmlxsd=Loader.getResource(Servlet.class,"javax/servlet/resources/xml.xsd");
+                URL webservice11xsd=Loader.getResource(Servlet.class,"javax/servlet/resources/j2ee_web_services_client_1_1.xsd");
+                URL webservice12xsd=Loader.getResource(Servlet.class,"javax/servlet/resources/javaee_web_services_client_1_2.xsd");
+                URL webservice13xsd=Loader.getResource(Servlet.class,"javax/servlet/resources/javaee_web_services_client_1_3.xsd");
+                URL webservice14xsd=Loader.getResource(Servlet.class,"javax/servlet/resources/javaee_web_services_client_1_4.xsd");
+                URL datatypesdtd=Loader.getResource(Servlet.class,"javax/servlet/resources/datatypes.dtd");
+                
+                URL jsp20xsd = null;
+                URL jsp21xsd = null;
+                URL jsp22xsd = null;
+                URL jsp23xsd = null;
+
+                try
+                {
+                    //try both javax/servlet/resources and javax/servlet/jsp/resources to load 
+                    jsp20xsd = Loader.getResource(Servlet.class, "javax/servlet/resources/jsp_2_0.xsd");
+                    jsp21xsd = Loader.getResource(Servlet.class, "javax/servlet/resources/jsp_2_1.xsd");
+                    jsp22xsd = Loader.getResource(Servlet.class, "javax/servlet/resources/jsp_2_2.xsd");
+                    jsp23xsd = Loader.getResource(Servlet.class, "javax/servlet/resources/jsp_2_3.xsd");
+                }
+                catch (Exception e)
+                {
+                    LOG.ignore(e);
+                }
+                finally
+                {
+                    if (jsp20xsd == null) jsp20xsd = Loader.getResource(Servlet.class, "javax/servlet/jsp/resources/jsp_2_0.xsd");
+                    if (jsp21xsd == null) jsp21xsd = Loader.getResource(Servlet.class, "javax/servlet/jsp/resources/jsp_2_1.xsd");
+                    if (jsp22xsd == null) jsp22xsd = Loader.getResource(Servlet.class, "javax/servlet/jsp/resources/jsp_2_2.xsd");
+                    if (jsp23xsd == null) jsp23xsd = Loader.getResource(Servlet.class, "javax/servlet/jsp/resources/jsp_2_3.xsd");
+                }
+                
+                redirectEntity("web-app_2_2.dtd",dtd22);
+                redirectEntity("-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN",dtd22);
+                redirectEntity("web.dtd",dtd23);
+                redirectEntity("web-app_2_3.dtd",dtd23);
+                redirectEntity("-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN",dtd23);
+                redirectEntity("XMLSchema.dtd",schemadtd);
+                redirectEntity("http://www.w3.org/2001/XMLSchema.dtd",schemadtd);
+                redirectEntity("-//W3C//DTD XMLSCHEMA 200102//EN",schemadtd);
+                redirectEntity("jsp_2_0.xsd",jsp20xsd);
+                redirectEntity("http://java.sun.com/xml/ns/j2ee/jsp_2_0.xsd",jsp20xsd);
+                redirectEntity("http://java.sun.com/xml/ns/javaee/jsp_2_1.xsd",jsp21xsd);
+                redirectEntity("jsp_2_2.xsd",jsp22xsd);
+                redirectEntity("http://java.sun.com/xml/ns/javaee/jsp_2_2.xsd",jsp22xsd);
+                redirectEntity("jsp_2_3.xsd",jsp23xsd);
+                redirectEntity("http://xmlns.jcp.org/xml/ns/javaee/jsp_2_3.xsd",jsp23xsd);
+                redirectEntity("j2ee_1_4.xsd",j2ee14xsd);
+                redirectEntity("http://java.sun.com/xml/ns/j2ee/j2ee_1_4.xsd",j2ee14xsd);
+                redirectEntity( "http://java.sun.com/xml/ns/javaee/javaee_5.xsd",javaee5);
+                redirectEntity( "http://java.sun.com/xml/ns/javaee/javaee_6.xsd",javaee6);
+                redirectEntity( "http://xmlns.jcp.org/xml/ns/javaee/javaee_7.xsd",javaee7);
+                redirectEntity("web-app_2_4.xsd",webapp24xsd);
+                redirectEntity("http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd",webapp24xsd);
+                redirectEntity("web-app_2_5.xsd",webapp25xsd);
+                redirectEntity("http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd",webapp25xsd);
+                redirectEntity("web-app_3_0.xsd",webapp30xsd);
+                redirectEntity("http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd",webapp30xsd);
+                redirectEntity("web-common_3_0.xsd",webcommon30xsd);
+                redirectEntity("http://java.sun.com/xml/ns/javaee/web-common_3_0.xsd",webcommon30xsd);
+                redirectEntity("web-fragment_3_0.xsd",webfragment30xsd);
+                redirectEntity("http://java.sun.com/xml/ns/javaee/web-fragment_3_0.xsd",webfragment30xsd);
+                redirectEntity("web-app_3_1.xsd",webapp31xsd);
+                redirectEntity("http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd",webapp31xsd);
+                
+                redirectEntity("web-common_3_1.xsd",webcommon30xsd);
+                redirectEntity("http://xmlns.jcp.org/xml/ns/javaee/web-common_3_1.xsd",webcommon31xsd);
+                redirectEntity("web-fragment_3_1.xsd",webfragment30xsd);
+                redirectEntity("http://xmlns.jcp.org/xml/ns/javaee/web-fragment_3_1.xsd",webfragment31xsd);
+                redirectEntity("xml.xsd",xmlxsd);
+                redirectEntity("http://www.w3.org/2001/xml.xsd",xmlxsd);
+                redirectEntity("datatypes.dtd",datatypesdtd);
+                redirectEntity("http://www.w3.org/2001/datatypes.dtd",datatypesdtd);
+                redirectEntity("j2ee_web_services_client_1_1.xsd",webservice11xsd);
+                redirectEntity("http://www.ibm.com/webservices/xsd/j2ee_web_services_client_1_1.xsd",webservice11xsd);
+                redirectEntity("javaee_web_services_client_1_2.xsd",webservice12xsd);   
+                redirectEntity("http://www.ibm.com/webservices/xsd/javaee_web_services_client_1_2.xsd",webservice12xsd);
+                redirectEntity("javaee_web_services_client_1_3.xsd",webservice13xsd);
+                redirectEntity("http://java.sun.com/xml/ns/javaee/javaee_web_services_client_1_3.xsd",webservice13xsd);
+                redirectEntity("javaee_web_services_client_1_4.xsd",webservice14xsd);
+                redirectEntity("http://xmlns.jcp.org/xml/ns/javaee/javaee_web_services_client_1_4.xsd",webservice14xsd);
+            }
+        };
         
-        redirect(xmlParser,"web-app_2_2.dtd",dtd22);
-        redirect(xmlParser,"-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN",dtd22);
-        redirect(xmlParser,"web.dtd",dtd23);
-        redirect(xmlParser,"web-app_2_3.dtd",dtd23);
-        redirect(xmlParser,"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN",dtd23);
-        redirect(xmlParser,"XMLSchema.dtd",schemadtd);
-        redirect(xmlParser,"http://www.w3.org/2001/XMLSchema.dtd",schemadtd);
-        redirect(xmlParser,"-//W3C//DTD XMLSCHEMA 200102//EN",schemadtd);
-        redirect(xmlParser,"jsp_2_0.xsd",jsp20xsd);
-        redirect(xmlParser,"http://java.sun.com/xml/ns/j2ee/jsp_2_0.xsd",jsp20xsd);
-        redirect(xmlParser,"http://java.sun.com/xml/ns/javaee/jsp_2_1.xsd",jsp21xsd);
-        redirect(xmlParser,"j2ee_1_4.xsd",j2ee14xsd);
-        redirect(xmlParser,"http://java.sun.com/xml/ns/j2ee/j2ee_1_4.xsd",j2ee14xsd);
-        redirect(xmlParser,"web-app_2_4.xsd",webapp24xsd);
-        redirect(xmlParser,"http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd",webapp24xsd);
-        redirect(xmlParser,"web-app_2_5.xsd",webapp25xsd);
-        redirect(xmlParser,"http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd",webapp25xsd);
-        redirect(xmlParser,"web-app_3_0.xsd",webapp30xsd);
-        redirect(xmlParser,"http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd",webapp30xsd);
-        redirect(xmlParser,"web-common_3_0.xsd",webcommon30xsd);
-        redirect(xmlParser,"http://java.sun.com/xml/ns/javaee/web-common_3_0.xsd",webcommon30xsd);
-        redirect(xmlParser,"web-fragment_3_0.xsd",webfragment30xsd);
-        redirect(xmlParser,"http://java.sun.com/xml/ns/javaee/web-fragment_3_0.xsd",webfragment30xsd);
-        redirect(xmlParser,"xml.xsd",xmlxsd);
-        redirect(xmlParser,"http://www.w3.org/2001/xml.xsd",xmlxsd);
-        redirect(xmlParser,"datatypes.dtd",datatypesdtd);
-        redirect(xmlParser,"http://www.w3.org/2001/datatypes.dtd",datatypesdtd);
-        redirect(xmlParser,"j2ee_web_services_client_1_1.xsd",webservice11xsd);
-        redirect(xmlParser,"http://www.ibm.com/webservices/xsd/j2ee_web_services_client_1_1.xsd",webservice11xsd);
-        redirect(xmlParser,"javaee_web_services_client_1_2.xsd",webservice12xsd);
-        redirect(xmlParser,"http://www.ibm.com/webservices/xsd/javaee_web_services_client_1_2.xsd",webservice12xsd);
         return xmlParser;
     }
-    
-    
+
+
     public WebDescriptor (Resource xml)
     {
         super(xml);
     }
-    
+
     public void parse ()
     throws Exception
     {
@@ -149,25 +208,25 @@ public class WebDescriptor extends Descriptor
         processVersion();
         processOrdering();
     }
-    
+
     public MetaDataComplete getMetaDataComplete()
     {
         return _metaDataComplete;
     }
-    
- 
-    
+
+
+
     public int getMajorVersion ()
     {
         return _majorVersion;
     }
-    
+
     public int getMinorVersion()
     {
         return _minorVersion;
     }
-  
-    
+
+
     public void processVersion ()
     {
         String version = _root.getAttribute("version", "DTD");
@@ -182,7 +241,7 @@ public class WebDescriptor extends Descriptor
                 _minorVersion = 2;
             }
         }
-        else 
+        else
         {
            int dot = version.indexOf(".");
            if (dot > 0)
@@ -191,8 +250,8 @@ public class WebDescriptor extends Descriptor
                _minorVersion = Integer.parseInt(version.substring(dot+1));
            }
         }
-     
-        if (_majorVersion < 2 && _minorVersion < 5)
+
+        if (_majorVersion <= 2 && _minorVersion < 5)
             _metaDataComplete = MetaDataComplete.True; // does not apply before 2.5
         else
         {
@@ -202,22 +261,22 @@ public class WebDescriptor extends Descriptor
             else
                 _metaDataComplete = Boolean.valueOf(s).booleanValue()?MetaDataComplete.True:MetaDataComplete.False;
         }
-            
+
         if (LOG.isDebugEnabled())
-            LOG.debug(_xml.toString()+": Calculated metadatacomplete = " + _metaDataComplete + " with version=" + version);     
+            LOG.debug(_xml.toString()+": Calculated metadatacomplete = " + _metaDataComplete + " with version=" + version);
     }
-    
+
     public void processOrdering ()
     {
-        //Process the web.xml's optional <absolute-ordering> element              
+        //Process the web.xml's optional <absolute-ordering> element
         XmlParser.Node ordering = _root.get("absolute-ordering");
         if (ordering == null)
            return;
-        
+
         _isOrdered = true;
         //If an absolute-ordering was already set, then ignore it in favor of this new one
        // _processor.setOrdering(new AbsoluteOrdering());
-   
+
         Iterator<Object> iter = ordering.iterator();
         XmlParser.Node node = null;
         while (iter.hasNext())
@@ -234,43 +293,47 @@ public class WebDescriptor extends Descriptor
                 _ordering.add(node.toString(false,true));
         }
     }
-  
+
     public void addClassName (String className)
     {
         if (!_classNames.contains(className))
             _classNames.add(className);
     }
-    
+
     public ArrayList<String> getClassNames ()
     {
         return _classNames;
     }
-    
+
     public void setDistributable (boolean distributable)
     {
         _distributable = distributable;
     }
-    
+
     public boolean isDistributable()
     {
         return _distributable;
     }
-    
+
     public void setValidating (boolean validating)
     {
        _validating = validating;
     }
     
-    
+    public boolean isValidating ()
+    {
+       return _validating;
+    }
+
     public boolean isOrdered()
     {
         return _isOrdered;
     }
-    
+
     public List<String> getOrdering()
     {
         return _ordering;
     }
 
-  
+
 }
diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebInfConfiguration.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebInfConfiguration.java
index 3e109b1..74056ba 100644
--- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebInfConfiguration.java
+++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebInfConfiguration.java
@@ -27,9 +27,12 @@ import java.net.URLClassLoader;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Locale;
+import java.util.Set;
+import java.util.StringTokenizer;
 import java.util.regex.Pattern;
 
 import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.NetworkConnector;
 import org.eclipse.jetty.server.Server;
 import org.eclipse.jetty.util.IO;
 import org.eclipse.jetty.util.PatternMatcher;
@@ -47,32 +50,30 @@ public class WebInfConfiguration extends AbstractConfiguration
     public static final String TEMPDIR_CONFIGURED = "org.eclipse.jetty.tmpdirConfigured";
     public static final String CONTAINER_JAR_PATTERN = "org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern";
     public static final String WEBINF_JAR_PATTERN = "org.eclipse.jetty.server.webapp.WebInfIncludeJarPattern";
-    
+
     /**
      * If set, to a list of URLs, these resources are added to the context
-     * resource base as a resource collection. 
+     * resource base as a resource collection.
      */
-    public static final String RESOURCE_URLS = "org.eclipse.jetty.resources";
+    public static final String RESOURCE_DIRS = "org.eclipse.jetty.resources";
     
+
     protected Resource _preUnpackBaseResource;
     
+
+
     @Override
     public void preConfigure(final WebAppContext context) throws Exception
     {
-        // Look for a work directory
-        File work = findWorkDirectory(context);
-        if (work != null)
-            makeTempDirectory(work, context, false);
-        
         //Make a temp directory for the webapp if one is not already set
         resolveTempDirectory(context);
-        
+
         //Extract webapp if necessary
         unpack (context);
 
-        
+
         //Apply an initial ordering to the jars which governs which will be scanned for META-INF
-        //info and annotations. The ordering is based on inclusion patterns.       
+        //info and annotations. The ordering is based on inclusion patterns.
         String tmp = (String)context.getAttribute(WEBINF_JAR_PATTERN);
         Pattern webInfPattern = (tmp==null?null:Pattern.compile(tmp));
         tmp = (String)context.getAttribute(CONTAINER_JAR_PATTERN);
@@ -84,8 +85,8 @@ public class WebInfConfiguration extends AbstractConfiguration
         {
             public void matched(URI uri) throws Exception
             {
-                context.getMetaData().addContainerJar(Resource.newResource(uri));
-            }      
+                context.getMetaData().addContainerResource(Resource.newResource(uri));
+            }
         };
         ClassLoader loader = null;
         if (context.getClassLoader() != null)
@@ -100,21 +101,21 @@ public class WebInfConfiguration extends AbstractConfiguration
                 int i=0;
                 for (URL u : urls)
                 {
-                    try 
+                    try
                     {
                         containerUris[i] = u.toURI();
                     }
                     catch (URISyntaxException e)
                     {
                         containerUris[i] = new URI(u.toString().replaceAll(" ", "%20"));
-                    }  
+                    }
                     i++;
                 }
                 containerJarNameMatcher.match(containerPattern, containerUris, false);
             }
             loader = loader.getParent();
         }
-        
+
         //Apply ordering to WEB-INF/lib jars
         PatternMatcher webInfJarNameMatcher = new PatternMatcher ()
         {
@@ -122,10 +123,10 @@ public class WebInfConfiguration extends AbstractConfiguration
             public void matched(URI uri) throws Exception
             {
                 context.getMetaData().addWebInfJar(Resource.newResource(uri));
-            }      
+            }
         };
         List<Resource> jars = findJars(context);
-       
+
         //Convert to uris for matching
         URI[] uris = null;
         if (jars != null)
@@ -137,9 +138,12 @@ public class WebInfConfiguration extends AbstractConfiguration
                 uris[i++] = r.getURI();
             }
         }
-        webInfJarNameMatcher.match(webInfPattern, uris, true); //null is inclusive, no pattern == all jars match 
+        webInfJarNameMatcher.match(webInfPattern, uris, true); //null is inclusive, no pattern == all jars match
+       
+        //No pattern to appy to classes, just add to metadata
+        context.getMetaData().setWebInfClassesDirs(findClassDirs(context));
     }
-    
+
 
     @Override
     public void configure(WebAppContext context) throws Exception
@@ -167,11 +171,11 @@ public class WebInfConfiguration extends AbstractConfiguration
             if (lib.exists() || lib.isDirectory())
                 ((WebAppClassLoader)context.getClassLoader()).addJars(lib);
         }
-        
+
         // Look for extra resource
         @SuppressWarnings("unchecked")
-        List<Resource> resources = (List<Resource>)context.getAttribute(RESOURCE_URLS);
-        if (resources!=null)
+        Set<Resource> resources = (Set<Resource>)context.getAttribute(RESOURCE_DIRS);
+        if (resources!=null && !resources.isEmpty())
         {
             Resource[] collection=new Resource[resources.size()+1];
             int i=0;
@@ -185,25 +189,21 @@ public class WebInfConfiguration extends AbstractConfiguration
     @Override
     public void deconfigure(WebAppContext context) throws Exception
     {
-        // delete temp directory if we had to create it or if it isn't called work
-        Boolean tmpdirConfigured = (Boolean)context.getAttribute(TEMPDIR_CONFIGURED);
-        
-        if (context.getTempDirectory()!=null && (tmpdirConfigured == null || !tmpdirConfigured.booleanValue()) && !isTempWorkDirectory(context.getTempDirectory()))
+        //if we're not persisting the temp dir contents delete it
+        if (!context.isPersistTempDirectory())
         {
             IO.delete(context.getTempDirectory());
-            context.setTempDirectory(null);
-            
-            //clear out the context attributes for the tmp dir only if we had to
-            //create the tmp dir
-            context.setAttribute(TEMPDIR_CONFIGURED, null);
-            context.setAttribute(WebAppContext.TEMPDIR, null);
         }
-
         
+        //if it wasn't explicitly configured by the user, then unset it
+        Boolean tmpdirConfigured = (Boolean)context.getAttribute(TEMPDIR_CONFIGURED);
+        if (tmpdirConfigured != null && !tmpdirConfigured) 
+            context.setTempDirectory(null);
+
         //reset the base resource back to what it was before we did any unpacking of resources
         context.setBaseResource(_preUnpackBaseResource);
     }
-    
+
     /* ------------------------------------------------------------ */
     /**
      * @see org.eclipse.jetty.webapp.AbstractConfiguration#cloneConfigure(org.eclipse.jetty.webapp.WebAppContext, org.eclipse.jetty.webapp.WebAppContext)
@@ -227,55 +227,48 @@ public class WebInfConfiguration extends AbstractConfiguration
      * Get a temporary directory in which to unpack the war etc etc.
      * The algorithm for determining this is to check these alternatives
      * in the order shown:
-     * 
+     *
      * <p>A. Try to use an explicit directory specifically for this webapp:</p>
      * <ol>
      * <li>
-     * Iff an explicit directory is set for this webapp, use it. Do NOT set
-     * delete on exit.
+     * Iff an explicit directory is set for this webapp, use it. Set delete on
+     * exit depends on value of persistTempDirectory.
      * </li>
      * <li>
      * Iff javax.servlet.context.tempdir context attribute is set for
-     * this webapp && exists && writeable, then use it. Do NOT set delete on exit.
-     * </li>
-     * </ol>
-     * 
-     * <p>B. Create a directory based on global settings. The new directory 
-     * will be called "Jetty_"+host+"_"+port+"__"+context+"_"+virtualhost
-     * Work out where to create this directory:
-     * <ol>
-     * <li>
-     * Iff $(jetty.home)/work exists create the directory there. Do NOT
-     * set delete on exit. Do NOT delete contents if dir already exists.
-     * </li>
-     * <li>
-     * Iff WEB-INF/work exists create the directory there. Do NOT set
-     * delete on exit. Do NOT delete contents if dir already exists.
-     * </li>
-     * <li>
-     * Else create dir in $(java.io.tmpdir). Set delete on exit. Delete
-     * contents if dir already exists.
+     * this webapp && exists && writeable, then use it. Set delete on exit depends on
+     * value of persistTempDirectory.
      * </li>
      * </ol>
+     *
+     * <p>B. Create a directory based on global settings. The new directory
+     * will be called "Jetty-"+host+"-"+port+"__"+context+"-"+virtualhost+"-"+randomdigits+".dir"
+     * </p>
+     * <p>
+     * If the user has specified the context attribute org.eclipse.jetty.webapp.basetempdir, the
+     * directory specified by this attribute will be the parent of the temp dir created. Otherwise,
+     * the parent dir is $(java.io.tmpdir). Set delete on exit depends on value of persistTempDirectory. 
+     * </p>
      */
     public void resolveTempDirectory (WebAppContext context)
+    throws Exception
     {
-        //If a tmp directory is already set, we're done
+        //If a tmp directory is already set we should use it
         File tmpDir = context.getTempDirectory();
-        if (tmpDir != null && tmpDir.isDirectory() && tmpDir.canWrite())
+        if (tmpDir != null)
         {
-            context.setAttribute(TEMPDIR_CONFIGURED, Boolean.TRUE);
-            return; // Already have a suitable tmp dir configured
+            configureTempDirectory(tmpDir, context);
+            context.setAttribute(TEMPDIR_CONFIGURED, Boolean.TRUE); //the tmp dir was set explicitly
+            return;
         }
-        
 
-        // No temp directory configured, try to establish one.
-        // First we check the context specific, javax.servlet specified, temp directory attribute
+        // No temp directory configured, try to establish one via the javax.servlet.context.tempdir.
         File servletTmpDir = asFile(context.getAttribute(WebAppContext.TEMPDIR));
-        if (servletTmpDir != null && servletTmpDir.isDirectory() && servletTmpDir.canWrite())
+        if (servletTmpDir != null)
         {
             // Use as tmpDir
             tmpDir = servletTmpDir;
+            configureTempDirectory(tmpDir, context);
             // Ensure Attribute has File object
             context.setAttribute(WebAppContext.TEMPDIR,tmpDir);
             // Set as TempDir in context.
@@ -283,60 +276,38 @@ public class WebInfConfiguration extends AbstractConfiguration
             return;
         }
 
-        try
-        {
-            // Put the tmp dir in the work directory if we had one
-            File work =  new File(System.getProperty("jetty.home"),"work");
-            if (work.exists() && work.canWrite() && work.isDirectory())
-            {
-                makeTempDirectory(work, context, false); //make a tmp dir inside work, don't delete if it exists
-            }
-            else
-            {
-                File baseTemp = asFile(context.getAttribute(WebAppContext.BASETEMPDIR));
-                if (baseTemp != null && baseTemp.isDirectory() && baseTemp.canWrite())
-                {
-                    // Use baseTemp directory (allow the funky Jetty_0_0_0_0.. subdirectory logic to kick in
-                    makeTempDirectory(baseTemp,context,false);
-                }
-                else
-                {
-                    makeTempDirectory(new File(System.getProperty("java.io.tmpdir")),context,true); //make a tmpdir, delete if it already exists
-                }
-            }
-        }
-        catch(Exception e)
+        //We need to make a temp dir. Check if the user has set a directory to use instead
+        //of java.io.tmpdir as the parent of the dir
+        File baseTemp = asFile(context.getAttribute(WebAppContext.BASETEMPDIR));
+        if (baseTemp != null && baseTemp.isDirectory() && baseTemp.canWrite())
         {
-            tmpDir=null;
-            LOG.ignore(e);
+            //Make a temp directory as a child of the given base dir
+            makeTempDirectory(baseTemp,context);
+            return;
         }
 
-        //Third ... Something went wrong trying to make the tmp directory, just make
-        //a jvm managed tmp directory
-        if (context.getTempDirectory() == null)
+        //Look for a directory named "work" in ${jetty.base} and
+        //treat it as parent of a new temp dir (which we will persist)
+        File jettyBase = asFile(System.getProperty("jetty.base"));
+        if (jettyBase != null)
         {
-            try
+            File work = new File (jettyBase, "work");
+            if (work.exists() && work.isDirectory() && work.canWrite())
             {
-                // Last resort
-                tmpDir=File.createTempFile("JettyContext","");
-                if (tmpDir.exists())
-                    IO.delete(tmpDir);
-                tmpDir.mkdir();
-                tmpDir.deleteOnExit();
-                context.setTempDirectory(tmpDir);
-            }
-            catch(IOException e)
-            {
-                tmpDir = null;
-                throw new IllegalStateException("Cannot create tmp dir in "+System.getProperty("java.io.tmpdir")+ " for context "+context,e);
+                context.setPersistTempDirectory(true);
+                makeTempDirectory(work,context);
+                return;
             }
         }
+
+        //Make a temp directory in java.io.tmpdir
+        makeTempDirectory(new File(System.getProperty("java.io.tmpdir")),context);
     }
-    
+
     /**
      * Given an Object, return File reference for object.
      * Typically used to convert anonymous Object from getAttribute() calls to a File object.
-     * @param fileattr the file attribute to analyze and return from (supports type File and type String, all others return null)
+     * @param fileattr the file attribute to analyze and return from (supports type File and type String, all others return null
      * @return the File object, null if null, or null if not a File or String
      */
     private File asFile(Object fileattr)
@@ -358,53 +329,67 @@ public class WebInfConfiguration extends AbstractConfiguration
 
 
 
-    public void makeTempDirectory (File parent, WebAppContext context, boolean deleteExisting)
-    throws IOException
+    public void makeTempDirectory (File parent, WebAppContext context)
+            throws Exception
     {
-        if (parent != null && parent.exists() && parent.canWrite() && parent.isDirectory())
+        if (parent == null || !parent.exists() || !parent.canWrite() || !parent.isDirectory())
+            throw new IllegalStateException("Parent for temp dir not configured correctly: "+(parent==null?"null":"writeable="+parent.canWrite()));
+
+        //Create a name for the webapp     
+        String temp = getCanonicalNameForWebAppTmpDir(context);
+        File tmpDir = null;
+        if (context.isPersistTempDirectory())
         {
-            String temp = getCanonicalNameForWebAppTmpDir(context);                    
-            File tmpDir = new File(parent,temp);
+            //if it is to be persisted, make sure it will be the same name
+            //by not using File.createTempFile, which appends random digits
+            tmpDir = new File (parent, temp);
+        }
+        else
+        {
+            //ensure file will always be unique by appending random digits
+            tmpDir = File.createTempFile(temp, ".dir", parent);
+            //delete the file that was created
+            tmpDir.delete();
+            //and make a directory of the same name
+            tmpDir.mkdirs();
+        }
+        configureTempDirectory(tmpDir, context);
 
-            if (deleteExisting && tmpDir.exists())
-            {
-                if (!IO.delete(tmpDir))
-                {
-                    if(LOG.isDebugEnabled())LOG.debug("Failed to delete temp dir "+tmpDir);
-                }
-            
-                //If we can't delete the existing tmp dir, create a new one
-                if (tmpDir.exists())
-                {
-                    String old=tmpDir.toString();
-                    tmpDir=File.createTempFile(temp+"_","");
-                    if (tmpDir.exists())
-                        IO.delete(tmpDir);
-                    LOG.warn("Can't reuse "+old+", using "+tmpDir);
-                } 
-            }
-            
-            if (!tmpDir.exists())
-                tmpDir.mkdir();
+        if(LOG.isDebugEnabled())
+            LOG.debug("Set temp dir "+tmpDir);
+        context.setTempDirectory(tmpDir);
+    }
 
-            //If the parent is not a work directory
-            if (!isTempWorkDirectory(tmpDir))
-            {
-                tmpDir.deleteOnExit();
-            }
+    public void configureTempDirectory (File dir, WebAppContext context)
+    {
+        if (dir == null)
+            throw new IllegalArgumentException("Null temp dir");
 
-            if(LOG.isDebugEnabled())
-                LOG.debug("Set temp dir "+tmpDir);
-            context.setTempDirectory(tmpDir);
+        //if dir exists and we don't want it persisted, delete it
+        if (dir.exists() && !context.isPersistTempDirectory())
+        {
+            if (!IO.delete(dir))
+                throw new IllegalStateException("Failed to delete temp dir "+dir);
         }
+
+        //if it doesn't exist make it
+        if (!dir.exists())
+            dir.mkdirs();
+
+        if (!context.isPersistTempDirectory())
+            dir.deleteOnExit();
+
+        //is it useable
+        if (!dir.canWrite() || !dir.isDirectory())   
+            throw new IllegalStateException("Temp dir "+dir+" not useable: writeable="+dir.canWrite()+", dir="+dir.isDirectory());
     }
-    
-    
+
+
     public void unpack (WebAppContext context) throws IOException
     {
         Resource web_app = context.getBaseResource();
         _preUnpackBaseResource = context.getBaseResource();
-        
+
         if (web_app == null)
         {
             String war = context.getWar();
@@ -412,6 +397,9 @@ public class WebInfConfiguration extends AbstractConfiguration
                 web_app = context.newResource(war);
             else
                 web_app=context.getBaseResource();
+            
+            if (web_app == null)
+                throw new IllegalStateException("No resourceBase or war set for context");
 
             // Accept aliases for WAR files
             if (web_app.getAlias() != null)
@@ -435,7 +423,7 @@ public class WebInfConfiguration extends AbstractConfiguration
             if (web_app.exists()  && (
                     (context.isCopyWebDir() && web_app.getFile() != null && web_app.getFile().isDirectory()) ||
                     (context.isExtractWAR() && web_app.getFile() != null && !web_app.getFile().isDirectory()) ||
-                    (context.isExtractWAR() && web_app.getFile() == null) || 
+                    (context.isExtractWAR() && web_app.getFile() == null) ||
                     !web_app.isDirectory())
                             )
             {
@@ -453,7 +441,7 @@ public class WebInfConfiguration extends AbstractConfiguration
                             extractedWebAppDir=sibling;
                     }
                 }
-                
+
                 if (extractedWebAppDir==null)
                     // Then extract it if necessary to the temporary location
                     extractedWebAppDir= new File(context.getTempDirectory(), "webapp");
@@ -461,7 +449,7 @@ public class WebInfConfiguration extends AbstractConfiguration
                 if (web_app.getFile()!=null && web_app.getFile().isDirectory())
                 {
                     // Copy directory
-                    LOG.info("Copy " + web_app + " to " + extractedWebAppDir);
+                    LOG.debug("Copy " + web_app + " to " + extractedWebAppDir);
                     web_app.copyTo(extractedWebAppDir);
                 }
                 else
@@ -469,13 +457,13 @@ public class WebInfConfiguration extends AbstractConfiguration
                     //Use a sentinel file that will exist only whilst the extraction is taking place.
                     //This will help us detect interrupted extractions.
                     File extractionLock = new File (context.getTempDirectory(), ".extract_lock");
-                   
+
                     if (!extractedWebAppDir.exists())
                     {
                         //it hasn't been extracted before so extract it
-                        extractionLock.createNewFile();  
+                        extractionLock.createNewFile();
                         extractedWebAppDir.mkdir();
-                        LOG.info("Extract " + web_app + " to " + extractedWebAppDir);                                     
+                        LOG.debug("Extract " + web_app + " to " + extractedWebAppDir);
                         Resource jar_web_app = JarResource.newJarResource(web_app);
                         jar_web_app.copyTo(extractedWebAppDir);
                         extractionLock.delete();
@@ -488,13 +476,13 @@ public class WebInfConfiguration extends AbstractConfiguration
                             extractionLock.createNewFile();
                             IO.delete(extractedWebAppDir);
                             extractedWebAppDir.mkdir();
-                            LOG.info("Extract " + web_app + " to " + extractedWebAppDir);
+                            LOG.debug("Extract " + web_app + " to " + extractedWebAppDir);
                             Resource jar_web_app = JarResource.newJarResource(web_app);
                             jar_web_app.copyTo(extractedWebAppDir);
                             extractionLock.delete();
                         }
                     }
-                } 
+                }
                 web_app = Resource.newResource(extractedWebAppDir.getCanonicalPath());
             }
 
@@ -504,13 +492,13 @@ public class WebInfConfiguration extends AbstractConfiguration
                 LOG.warn("Web application not found " + war);
                 throw new java.io.FileNotFoundException(war);
             }
-        
+
             context.setBaseResource(web_app);
-            
+
             if (LOG.isDebugEnabled())
                 LOG.debug("webapp=" + web_app);
         }
-        
+
 
         // Do we need to extract WEB-INF/lib?
         if (context.isCopyWebInf() && !context.isCopyWebDir())
@@ -532,7 +520,7 @@ public class WebInfConfiguration extends AbstractConfiguration
                     IO.delete(webInfLibDir);
                 webInfLibDir.mkdir();
 
-                LOG.info("Copying WEB-INF/lib " + web_inf_lib + " to " + webInfLibDir);
+                LOG.debug("Copying WEB-INF/lib " + web_inf_lib + " to " + webInfLibDir);
                 web_inf_lib.copyTo(webInfLibDir);
             }
 
@@ -543,7 +531,7 @@ public class WebInfConfiguration extends AbstractConfiguration
                 if (webInfClassesDir.exists())
                     IO.delete(webInfClassesDir);
                 webInfClassesDir.mkdir();
-                LOG.info("Copying WEB-INF/classes from "+web_inf_classes+" to "+webInfClassesDir.getAbsolutePath());
+                LOG.debug("Copying WEB-INF/classes from "+web_inf_classes+" to "+webInfClassesDir.getAbsolutePath());
                 web_inf_classes.copyTo(webInfClassesDir);
             }
 
@@ -554,58 +542,30 @@ public class WebInfConfiguration extends AbstractConfiguration
             if (LOG.isDebugEnabled())
                 LOG.debug("context.resourcebase = "+rc);
 
-            context.setBaseResource(rc);   
-        }
-    }
-    
-    
-    public File findWorkDirectory (WebAppContext context) throws IOException
-    {
-        if (context.getBaseResource() != null)
-        {
-            Resource web_inf = context.getWebInf();
-            if (web_inf !=null && web_inf.exists())
-            {
-               return new File(web_inf.getFile(),"work");
-            }
+            context.setBaseResource(rc);
         }
-        return null;
     }
-    
-    
-    /**
-     * Check if the tmpDir itself is called "work", or if the tmpDir
-     * is in a directory called "work".
-     * @return true if File is a temporary or work directory
-     */
-    public boolean isTempWorkDirectory (File tmpDir)
-    {
-        if (tmpDir == null)
-            return false;
-        if (tmpDir.getName().equalsIgnoreCase("work"))
-            return true;
-        File t = tmpDir.getParentFile();
-        if (t == null)
-            return false;
-        return (t.getName().equalsIgnoreCase("work"));
-    }
-    
-    
+
+
+
+
     /**
      * Create a canonical name for a webapp temp directory.
      * The form of the name is:
-     *  <code>"Jetty_"+host+"_"+port+"__"+resourceBase+"_"+context+"_"+virtualhost+base36_hashcode_of_whole_string</code>
-     *  
+     *  <code>"jetty-"+host+"-"+port+"-"+resourceBase+"-_"+context+"-"+virtualhost+"-"+randomdigits+".dir"</code>
+     *
      *  host and port uniquely identify the server
      *  context and virtual host uniquely identify the webapp
+     *  randomdigits ensure every tmp directory is unique
+     *  
      * @return the canonical name for the webapp temp directory
      */
     public static String getCanonicalNameForWebAppTmpDir (WebAppContext context)
     {
         StringBuffer canonicalName = new StringBuffer();
         canonicalName.append("jetty-");
-       
-        //get the host and the port from the first connector 
+
+        //get the host and the port from the first connector
         Server server=context.getServer();
         if (server!=null)
         {
@@ -614,25 +574,31 @@ public class WebInfConfiguration extends AbstractConfiguration
             if (connectors.length>0)
             {
                 //Get the host
-                String host = (connectors==null||connectors[0]==null?"":connectors[0].getHost());
+                String host=null;
+                int port=0;
+                if (connectors!=null && (connectors[0] instanceof NetworkConnector))
+                {
+                    NetworkConnector connector = (NetworkConnector)connectors[0];
+                    host=connector.getHost();
+                    port=connector.getLocalPort();
+                    if (port < 0)
+                        port = connector.getPort();
+                }
                 if (host == null)
                     host = "0.0.0.0";
                 canonicalName.append(host);
-                
+
                 //Get the port
                 canonicalName.append("-");
-                //try getting the real port being listened on
-                int port = (connectors==null||connectors[0]==null?0:connectors[0].getLocalPort());
-                //if not available (eg no connectors or connector not started), 
+
+                //if not available (eg no connectors or connector not started),
                 //try getting one that was configured.
-                if (port < 0)
-                    port = connectors[0].getPort();
                 canonicalName.append(port);
                 canonicalName.append("-");
             }
         }
 
-       
+
         //Resource  base
         try
         {
@@ -640,12 +606,12 @@ public class WebInfConfiguration extends AbstractConfiguration
             if (resource == null)
             {
                 if (context.getWar()==null || context.getWar().length()==0)
-                    resource=context.newResource(context.getResourceBase());
-                
+                   throw new IllegalStateException("No resourceBase or war set for context");
+
                 // Set dir or WAR
                 resource = context.newResource(context.getWar());
             }
-                
+
             String tmp = URIUtil.decodePath(resource.getURL().getPath());
             if (tmp.endsWith("/"))
                 tmp = tmp.substring(0, tmp.length()-1);
@@ -658,15 +624,16 @@ public class WebInfConfiguration extends AbstractConfiguration
         }
         catch (Exception e)
         {
-            LOG.warn("Can't generate resourceBase as part of webapp tmp dir name", e);
+            LOG.warn("Can't generate resourceBase as part of webapp tmp dir name: " + e);
+            LOG.debug(e);
         }
-            
+
         //Context name
         String contextPath = context.getContextPath();
         contextPath=contextPath.replace('/','_');
         contextPath=contextPath.replace('\\','_');
         canonicalName.append(contextPath);
-        
+
         //Virtual host (if there is one)
         canonicalName.append("-");
         String[] vhosts = context.getVirtualHosts();
@@ -674,43 +641,82 @@ public class WebInfConfiguration extends AbstractConfiguration
             canonicalName.append("any");
         else
             canonicalName.append(vhosts[0]);
-        
+
         // sanitize
         for (int i=0;i<canonicalName.length();i++)
         {
             char c=canonicalName.charAt(i);
             if (!Character.isJavaIdentifierPart(c) && "-.".indexOf(c)<0)
                 canonicalName.setCharAt(i,'.');
-        }        
+        }
 
         canonicalName.append("-");
+
         return canonicalName.toString();
     }
+
+    
+    protected List<Resource> findClassDirs (WebAppContext context)
+    throws Exception
+    {
+        if (context == null)
+            return null;
+        
+        List<Resource> classDirs = new ArrayList<Resource>();
+
+        Resource webInfClasses = findWebInfClassesDir(context);
+        if (webInfClasses != null)
+            classDirs.add(webInfClasses);
+        List<Resource> extraClassDirs = findExtraClasspathDirs(context);
+        if (extraClassDirs != null)
+            classDirs.addAll(extraClassDirs);
+        
+        return classDirs;
+    }
+    
     
     /**
-     * Look for jars in WEB-INF/lib
+     * Look for jars that should be treated as if they are in WEB-INF/lib
+     * 
      * @param context
-     * @return the list of jar resources found within context 
+     * @return the list of jar resources found within context
      * @throws Exception
      */
-    protected List<Resource> findJars (WebAppContext context) 
+    protected List<Resource> findJars (WebAppContext context)
     throws Exception
     {
         List<Resource> jarResources = new ArrayList<Resource>();
-        
+        List<Resource> webInfLibJars = findWebInfLibJars(context);
+        if (webInfLibJars != null)
+            jarResources.addAll(webInfLibJars);
+        List<Resource> extraClasspathJars = findExtraClasspathJars(context);
+        if (extraClasspathJars != null)
+            jarResources.addAll(extraClasspathJars);
+        return jarResources;
+    }
+    
+    /**
+     *  Look for jars in WEB-INF/lib
+     *  
+     * @param context
+     * @return
+     * @throws Exception
+     */
+    protected List<Resource> findWebInfLibJars(WebAppContext context)
+    throws Exception
+    {
         Resource web_inf = context.getWebInf();
         if (web_inf==null || !web_inf.exists())
             return null;
-        
+
+        List<Resource> jarResources = new ArrayList<Resource>();
         Resource web_inf_lib = web_inf.addPath("/lib");
-       
-        
         if (web_inf_lib.exists() && web_inf_lib.isDirectory())
         {
             String[] files=web_inf_lib.list();
             for (int f=0;files!=null && f<files.length;f++)
             {
-                try 
+                try
                 {
                     Resource file = web_inf_lib.addPath(files[f]);
                     String fnlc = file.getName().toLowerCase(Locale.ENGLISH);
@@ -729,4 +735,90 @@ public class WebInfConfiguration extends AbstractConfiguration
         }
         return jarResources;
     }
+    
+    
+    
+    /**
+     * Get jars from WebAppContext.getExtraClasspath as resources
+     * 
+     * @param context
+     * @return
+     * @throws Exception
+     */
+    protected List<Resource>  findExtraClasspathJars(WebAppContext context)
+    throws Exception
+    { 
+        if (context == null || context.getExtraClasspath() == null)
+            return null;
+        
+        List<Resource> jarResources = new ArrayList<Resource>();
+        StringTokenizer tokenizer = new StringTokenizer(context.getExtraClasspath(), ",;");
+        while (tokenizer.hasMoreTokens())
+        {
+            Resource resource = context.newResource(tokenizer.nextToken().trim());
+            String fnlc = resource.getName().toLowerCase(Locale.ENGLISH);
+            int dot = fnlc.lastIndexOf('.');
+            String extension = (dot < 0 ? null : fnlc.substring(dot));
+            if (extension != null && (extension.equals(".jar") || extension.equals(".zip")))
+            {
+                jarResources.add(resource);
+            }
+        }
+        
+        return jarResources;
+    }
+    
+    /**
+     * Get WEB-INF/classes dir
+     * 
+     * @param context
+     * @return
+     * @throws Exception
+     */
+    protected Resource findWebInfClassesDir (WebAppContext context)
+    throws Exception
+    {
+        if (context == null)
+            return null;
+        
+        Resource web_inf = context.getWebInf();
+
+        // Find WEB-INF/classes
+        if (web_inf != null && web_inf.isDirectory())
+        {
+            // Look for classes directory
+            Resource classes= web_inf.addPath("classes/");
+            if (classes.exists())
+                return classes;
+        }
+        return null;
+    }
+    
+    
+    /**
+     * Get class dirs from WebAppContext.getExtraClasspath as resources
+     * 
+     * @param context
+     * @return
+     * @throws Exception
+     */
+    protected List<Resource>  findExtraClasspathDirs(WebAppContext context)
+    throws Exception
+    { 
+        if (context == null || context.getExtraClasspath() == null)
+            return null;
+        
+        List<Resource> dirResources = new ArrayList<Resource>();
+        StringTokenizer tokenizer = new StringTokenizer(context.getExtraClasspath(), ",;");
+        while (tokenizer.hasMoreTokens())
+        {
+            Resource resource = context.newResource(tokenizer.nextToken().trim());
+            if (resource.exists() && resource.isDirectory())
+                dirResources.add(resource);
+        }
+        
+        return dirResources;
+    }
+    
+    
 }
diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebXmlConfiguration.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebXmlConfiguration.java
index cb7b641..42ec276 100644
--- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebXmlConfiguration.java
+++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebXmlConfiguration.java
@@ -22,7 +22,6 @@ import java.io.IOException;
 import java.net.MalformedURLException;
 
 import org.eclipse.jetty.servlet.ErrorPageErrorHandler;
-import org.eclipse.jetty.servlet.ServletHandler;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
 import org.eclipse.jetty.util.resource.Resource;
@@ -120,17 +119,13 @@ public class WebXmlConfiguration extends AbstractConfiguration
     /* ------------------------------------------------------------------------------- */
     @Override
     public void deconfigure (WebAppContext context) throws Exception
-    {
-        ServletHandler _servletHandler = context.getServletHandler();
-       
+    {      
         context.setWelcomeFiles(null);
 
         if (context.getErrorHandler() instanceof ErrorPageErrorHandler)
             ((ErrorPageErrorHandler) 
                     context.getErrorHandler()).setErrorPages(null);
 
-
         // TODO remove classpaths from classloader
-
     }
 }
diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/package-info.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/package-info.java
new file mode 100644
index 0000000..cf7bedc
--- /dev/null
+++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Util : Modular Web Application Support
+ */
+package org.eclipse.jetty.webapp;
+
diff --git a/jetty-webapp/src/main/resources/org/eclipse/jetty/webapp/jmx/WebAppContext-mbean.properties b/jetty-webapp/src/main/resources/org/eclipse/jetty/webapp/jmx/WebAppContext-mbean.properties
deleted file mode 100644
index 5a09672..0000000
--- a/jetty-webapp/src/main/resources/org/eclipse/jetty/webapp/jmx/WebAppContext-mbean.properties
+++ /dev/null
@@ -1,14 +0,0 @@
-WebAppContext: Web Application ContextHandler
-configurationClasses: Array of names of configuration classes 
-defaultsDescriptor: Default web.xml descriptor applied before standard web.xml
-overrideDescriptor: Override web.xml descriptor applied after standard web.xml
-serverClasses: Classes and packages hidden by the context classloader
-systemClasses: Classes and packages given priority by context classloader
-war: WAR file location
-distributable: Is the web application distributable
-extractWAR: Is the war file extraced on deploy
-copyWebDir: Is the web application directory copied on deploy
-parentLoaderPriority: Is the parent classloader given priority
-descriptor: The standard web.xml descriptor
-extraClasspath: Extra classpath for the context classloader
-tempDirectory: Temporary directory location
\ No newline at end of file
diff --git a/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/OrderingTest.java b/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/OrderingTest.java
index 704ca4f..e54a4a2 100644
--- a/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/OrderingTest.java
+++ b/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/OrderingTest.java
@@ -24,9 +24,9 @@ import static org.junit.Assert.fail;
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
-import java.io.OutputStream;
 import java.net.MalformedURLException;
 import java.net.URL;
+import java.nio.channels.ReadableByteChannel;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -45,13 +45,13 @@ public class OrderingTest
     public class TestResource extends Resource
     {
         public String _name;
-        
+
         public TestResource (String name)
         {
             _name =name;
         }
 
-        /** 
+        /**
          * @see org.eclipse.jetty.util.resource.Resource#addPath(java.lang.String)
          */
         @Override
@@ -60,7 +60,7 @@ public class OrderingTest
             return null;
         }
 
-        /** 
+        /**
          * @see org.eclipse.jetty.util.resource.Resource#delete()
          */
         @Override
@@ -69,7 +69,7 @@ public class OrderingTest
             return false;
         }
 
-        /** 
+        /**
          * @see org.eclipse.jetty.util.resource.Resource#exists()
          */
         @Override
@@ -78,7 +78,7 @@ public class OrderingTest
             return false;
         }
 
-        /** 
+        /**
          * @see org.eclipse.jetty.util.resource.Resource#getFile()
          */
         @Override
@@ -87,7 +87,7 @@ public class OrderingTest
             return null;
         }
 
-        /** 
+        /**
          * @see org.eclipse.jetty.util.resource.Resource#getInputStream()
          */
         @Override
@@ -96,25 +96,22 @@ public class OrderingTest
             return null;
         }
 
-        /** 
-         * @see org.eclipse.jetty.util.resource.Resource#getName()
-         */
         @Override
-        public String getName()
+        public ReadableByteChannel getReadableByteChannel() throws IOException
         {
-            return _name;
+            return null;
         }
 
-        /** 
-         * @see org.eclipse.jetty.util.resource.Resource#getOutputStream()
+        /**
+         * @see org.eclipse.jetty.util.resource.Resource#getName()
          */
         @Override
-        public OutputStream getOutputStream() throws IOException, SecurityException
+        public String getName()
         {
-            return null;
+            return _name;
         }
-
-        /** 
+        
+        /**
          * @see org.eclipse.jetty.util.resource.Resource#getURL()
          */
         @Override
@@ -123,7 +120,7 @@ public class OrderingTest
             return null;
         }
 
-        /** 
+        /**
          * @see org.eclipse.jetty.util.resource.Resource#isContainedIn(org.eclipse.jetty.util.resource.Resource)
          */
         @Override
@@ -132,7 +129,7 @@ public class OrderingTest
             return false;
         }
 
-        /** 
+        /**
          * @see org.eclipse.jetty.util.resource.Resource#isDirectory()
          */
         @Override
@@ -141,7 +138,7 @@ public class OrderingTest
             return false;
         }
 
-        /** 
+        /**
          * @see org.eclipse.jetty.util.resource.Resource#lastModified()
          */
         @Override
@@ -150,7 +147,7 @@ public class OrderingTest
             return 0;
         }
 
-        /** 
+        /**
          * @see org.eclipse.jetty.util.resource.Resource#length()
          */
         @Override
@@ -159,7 +156,7 @@ public class OrderingTest
             return 0;
         }
 
-        /** 
+        /**
          * @see org.eclipse.jetty.util.resource.Resource#list()
          */
         @Override
@@ -168,15 +165,15 @@ public class OrderingTest
             return null;
         }
 
-        /** 
-         * @see org.eclipse.jetty.util.resource.Resource#release()
+        /**
+         * @see org.eclipse.jetty.util.resource.Resource#close()
          */
         @Override
-        public void release()
+        public void close()
         {
         }
 
-        /** 
+        /**
          * @see org.eclipse.jetty.util.resource.Resource#renameTo(org.eclipse.jetty.util.resource.Resource)
          */
         @Override
@@ -184,9 +181,9 @@ public class OrderingTest
         {
             return false;
         }
-        
+
     }
-    
+
     @Test
     public void testRelativeOrdering0 ()
     throws Exception
@@ -196,7 +193,7 @@ public class OrderingTest
         MetaData metaData = new MetaData();
         List<Resource> resources = new ArrayList<Resource>();
         metaData._ordering = new RelativeOrdering(metaData);
-        
+
         //A: after others, after C
         TestResource jar1 = new TestResource("A");
         resources.add(jar1);
@@ -208,7 +205,7 @@ public class OrderingTest
         f1._otherType = FragmentDescriptor.OtherType.After;
         //((RelativeOrdering)metaData._ordering).addAfterOthers(r1);
         f1._afters.add("C");
-        
+
         //B: before others
         TestResource jar2 = new TestResource("B");
         resources.add(jar2);
@@ -219,7 +216,7 @@ public class OrderingTest
         metaData._webFragmentResourceMap.put(jar2, f2);
         f2._otherType = FragmentDescriptor.OtherType.Before;
         //((RelativeOrdering)metaData._ordering).addBeforeOthers(r2);
-        
+
         //C: after others
         TestResource jar3 = new TestResource("C");
         resources.add(jar3);
@@ -230,29 +227,29 @@ public class OrderingTest
         metaData._webFragmentResourceMap.put(jar3, f3);
         f3._otherType = FragmentDescriptor.OtherType.After;
         //((RelativeOrdering)metaData._ordering).addAfterOthers(r3);
-        
+
         //D: no ordering
         TestResource jar4 = new TestResource("D");
         resources.add(jar4);
         TestResource r4 = new TestResource("D/web-fragment.xml");
         FragmentDescriptor f4 = new FragmentDescriptor(r4);
-        f4._name="D"; 
+        f4._name="D";
         metaData._webFragmentNameMap.put(f4._name, f4);
         metaData._webFragmentResourceMap.put(jar4, f4);
         f4._otherType = FragmentDescriptor.OtherType.None;
         //((RelativeOrdering)metaData._ordering).addNoOthers(r4);
-        
-        //E: no ordering   
-        TestResource jar5 = new TestResource("E");     
+
+        //E: no ordering
+        TestResource jar5 = new TestResource("E");
         resources.add(jar5);
         TestResource r5 = new TestResource("E/web-fragment.xml");
         FragmentDescriptor f5 = new FragmentDescriptor(r5);
-        f5._name="E"; 
+        f5._name="E";
         metaData._webFragmentNameMap.put(f5._name, f5);
         metaData._webFragmentResourceMap.put(jar5, f5);
         f5._otherType = FragmentDescriptor.OtherType.None;
         //((RelativeOrdering)metaData._ordering).addNoOthers(r5);
-        
+
         //F: before others, before B
         TestResource jar6 = new TestResource("F");
         resources.add(jar6);
@@ -264,22 +261,22 @@ public class OrderingTest
         f6._otherType = FragmentDescriptor.OtherType.Before;
         //((RelativeOrdering)metaData._ordering).addBeforeOthers(r6);
         f6._befores.add("B");
-        
+
         //
         // p.70 outcome: F, B, D, E, C, A
         //
         String[] outcomes = {"FBDECA"};
         List<Resource> orderedList = metaData._ordering.order(resources);
-        
+
         String result = "";
         for (Resource r:orderedList)
             result+=(((TestResource)r)._name);
-        
+
         if (!checkResult(result, outcomes))
             fail("No outcome matched "+result);
     }
- 
-  
+
+
 
     @Test
     public void testRelativeOrdering1 ()
@@ -289,7 +286,7 @@ public class OrderingTest
         WebAppContext wac = new WebAppContext();
         MetaData metaData = new MetaData();
         metaData._ordering = new RelativeOrdering(metaData);
-        
+
         //Example from ServletSpec p.70-71
         //No name: after others, before C
         TestResource jar1 = new TestResource("plain");
@@ -297,12 +294,12 @@ public class OrderingTest
         TestResource r1 = new TestResource("plain/web-fragment.xml");
         FragmentDescriptor f1 = new FragmentDescriptor(r1);
         f1._name = FragmentDescriptor.NAMELESS+"1";
-        metaData._webFragmentNameMap.put(f1._name, f1); 
+        metaData._webFragmentNameMap.put(f1._name, f1);
         metaData._webFragmentResourceMap.put(jar1,f1);
         f1._otherType = FragmentDescriptor.OtherType.After;
         //((RelativeOrdering)metaData._ordering).addAfterOthers(f1);
         f1._befores.add("C");
-        
+
         //B: before others
         TestResource jar2 = new TestResource("B");
         resources.add(jar2);
@@ -313,7 +310,7 @@ public class OrderingTest
         metaData._webFragmentResourceMap.put(jar2,f2);
         f2._otherType = FragmentDescriptor.OtherType.Before;
         //((RelativeOrdering)metaData._ordering).addBeforeOthers(f2);
-               
+
         //C: no ordering
         TestResource jar3 = new TestResource("C");
         resources.add(jar3);
@@ -324,18 +321,18 @@ public class OrderingTest
         metaData._webFragmentResourceMap.put(jar3,f3);
         //((RelativeOrdering)metaData._ordering).addNoOthers(f3);
         f3._otherType = FragmentDescriptor.OtherType.None;
-        
+
         //D: after others
         TestResource jar4 = new TestResource("D");
         resources.add(jar4);
         TestResource r4 = new TestResource("D/web-fragment.xml");
         FragmentDescriptor f4 = new FragmentDescriptor(r4);
-        f4._name="D"; 
+        f4._name="D";
         metaData._webFragmentNameMap.put(f4._name, f4);
         metaData._webFragmentResourceMap.put(jar4,f4);
         //((RelativeOrdering)metaData._ordering).addAfterOthers(f4);
         f4._otherType = FragmentDescriptor.OtherType.After;
-        
+
         //E: before others
         TestResource jar5 = new TestResource("E");
         resources.add(jar5);
@@ -346,7 +343,7 @@ public class OrderingTest
         metaData._webFragmentResourceMap.put(jar5,f5);
         //((RelativeOrdering)metaData._ordering).addBeforeOthers(f5);
         f5._otherType = FragmentDescriptor.OtherType.Before;
-        
+
         //F: no ordering
         TestResource jar6 = new TestResource("F");
         resources.add(jar6);
@@ -357,7 +354,7 @@ public class OrderingTest
         metaData._webFragmentResourceMap.put(jar6,f6);
         //((RelativeOrdering)metaData._ordering).addNoOthers(f6);
         f6._otherType = FragmentDescriptor.OtherType.None;
-        
+
         List<Resource> orderedList = metaData._ordering.order(resources);
 
         // p.70-71 Possible outcomes are:
@@ -367,20 +364,20 @@ public class OrderingTest
         // E, B, F, noname, D, C
         // E, B, F, D, noname, C
         //
-        String[] outcomes = {"BEFplainCD", 
+        String[] outcomes = {"BEFplainCD",
                              "BEFplainDC",
                              "EBFplainCD",
                              "EBFplainDC",
                              "EBFDplain"};
-        
+
         String orderedNames = "";
         for (Resource r:orderedList)
             orderedNames+=(((TestResource)r)._name);
-        
+
         if (!checkResult(orderedNames, outcomes))
             fail("No outcome matched "+orderedNames);
     }
-    
+
     @Test
     public void testRelativeOrdering2 ()
     throws Exception
@@ -389,9 +386,9 @@ public class OrderingTest
         WebAppContext wac = new WebAppContext();
         MetaData metaData = new MetaData();
         metaData._ordering = new RelativeOrdering(metaData);
-        
+
         //Example from Spec p. 71-72
-        
+
         //A: after B
         TestResource jar1 = new TestResource("A");
         resources.add(jar1);
@@ -403,7 +400,7 @@ public class OrderingTest
         //((RelativeOrdering)metaData._ordering).addNoOthers(f1);
         f1._otherType = FragmentDescriptor.OtherType.None;
         f1._afters.add("B");
-        
+
         //B: no order
         TestResource jar2 = new TestResource("B");
         resources.add(jar2);
@@ -414,7 +411,7 @@ public class OrderingTest
         metaData._webFragmentResourceMap.put(jar2, f2);
         //((RelativeOrdering)metaData._ordering).addNoOthers(f2);
         f2._otherType = FragmentDescriptor.OtherType.None;
-        
+
         //C: before others
         TestResource jar3 = new TestResource("C");
         resources.add(jar3);
@@ -431,7 +428,7 @@ public class OrderingTest
         resources.add(jar4);
         TestResource r4 = new TestResource("D/web-fragment.xml");
         FragmentDescriptor f4 = new FragmentDescriptor(r4);
-        f4._name="D"; 
+        f4._name="D";
         metaData._webFragmentNameMap.put(f4._name, f4);
         metaData._webFragmentResourceMap.put(jar4, f4);
         //((RelativeOrdering)metaData._ordering).addNoOthers(f4);
@@ -445,21 +442,21 @@ public class OrderingTest
         String[] outcomes = {"CBDA",
                              "CDBA",
                              "CBAD"};
-       
- 
+
+
         List<Resource> orderedList = metaData._ordering.order(resources);
         String result = "";
         for (Resource r:orderedList)
            result+=(((TestResource)r)._name);
-        
+
         if (!checkResult(result, outcomes))
             fail ("No outcome matched "+result);
     }
-    
+
     @Test
     public void testRelativeOrdering3 ()
     throws Exception
-    { 
+    {
         List<Resource> resources = new ArrayList<Resource>();
         WebAppContext wac = new WebAppContext();
         MetaData metaData = new MetaData();
@@ -489,7 +486,7 @@ public class OrderingTest
         f2._otherType = FragmentDescriptor.OtherType.Before;
         f2._befores.add("C");
 
-        //C: no ordering   
+        //C: no ordering
         TestResource jar3 = new TestResource("C");
         resources.add(jar3);
         TestResource r3 = new TestResource("C/web-fragment.xml");
@@ -499,31 +496,31 @@ public class OrderingTest
         metaData._webFragmentResourceMap.put(jar3,f3);
         //((RelativeOrdering)metaData._ordering).addNoOthers(f3);
         f3._otherType = FragmentDescriptor.OtherType.None;
-        
+
         //result: BAC
         String[] outcomes = {"BAC"};
-        
+
         List<Resource> orderedList = metaData._ordering.order(resources);
         String result = "";
         for (Resource r:orderedList)
            result+=(((TestResource)r)._name);
-        
+
         if (!checkResult(result, outcomes))
             fail ("No outcome matched "+result);
     }
-    
+
     @Test
     public void testCircular1 ()
     throws Exception
     {
-        
+
         //A: after B
         //B: after A
         List<Resource> resources = new ArrayList<Resource>();
         WebAppContext wac = new WebAppContext();
         MetaData metaData = new MetaData();
         metaData._ordering = new RelativeOrdering(metaData);
-        
+
         //A: after B
         TestResource jar1 = new TestResource("A");
         resources.add(jar1);
@@ -535,7 +532,7 @@ public class OrderingTest
         //((RelativeOrdering)metaData._ordering).addNoOthers(f1);
         f1._otherType = FragmentDescriptor.OtherType.None;
         f1._afters.add("B");
-        
+
         //B: after A
         TestResource jar2 = new TestResource("B");
         resources.add(jar2);
@@ -558,18 +555,18 @@ public class OrderingTest
             assertTrue (e instanceof IllegalStateException);
         }
     }
- 
-    
-    
+
+
+
     @Test
     public void testInvalid1 ()
     throws Exception
-    {      
+    {
         List<Resource> resources = new ArrayList<Resource>();
         WebAppContext wac = new WebAppContext();
         MetaData metaData = new MetaData();
         metaData._ordering = new RelativeOrdering(metaData);
-        
+
         //A: after others, before C
         TestResource jar1 = new TestResource("A");
         resources.add(jar1);
@@ -581,7 +578,7 @@ public class OrderingTest
         //((RelativeOrdering)metaData._ordering).addAfterOthers(r1);
         f1._otherType = FragmentDescriptor.OtherType.After;
         f1._befores.add("C");
-        
+
         //B: before others, after C
         TestResource jar2 = new TestResource("B");
         resources.add(jar2);
@@ -593,7 +590,7 @@ public class OrderingTest
         //((RelativeOrdering)metaData._ordering).addBeforeOthers(r2);
         f2._otherType = FragmentDescriptor.OtherType.Before;
         f2._afters.add("C");
-        
+
         //C: no ordering
         TestResource jar3 = new TestResource("C");
         resources.add(jar3);
@@ -604,7 +601,7 @@ public class OrderingTest
         metaData._webFragmentResourceMap.put(jar3,f3);
         //((RelativeOrdering)metaData._ordering).addNoOthers(r3);
         f3._otherType = FragmentDescriptor.OtherType.None;
-        
+
         try
         {
             List<Resource> orderedList = metaData._ordering.order(resources);
@@ -635,7 +632,7 @@ public class OrderingTest
         ((AbsoluteOrdering)metaData._ordering).add("B");
         ((AbsoluteOrdering)metaData._ordering).add("C");
         ((AbsoluteOrdering)metaData._ordering).addOthers();
-        
+
         TestResource jar1 = new TestResource("A");
         resources.add(jar1);
         TestResource r1 = new TestResource("A/web-fragment.xml");
@@ -643,7 +640,7 @@ public class OrderingTest
         f1._name = "A";
         metaData._webFragmentNameMap.put(f1._name, f1);
         metaData._webFragmentResourceMap.put(jar1,f1);
-        
+
         TestResource jar2 = new TestResource("B");
         resources.add(jar2);
         TestResource r2 = new TestResource("B/web-fragment.xml");
@@ -651,7 +648,7 @@ public class OrderingTest
         f2._name="B";
         metaData._webFragmentNameMap.put(f2._name, f2);
         metaData._webFragmentResourceMap.put(jar2, f2);
-        
+
         TestResource jar3 = new TestResource("C");
         resources.add(jar3);
         TestResource r3 = new TestResource("C/web-fragment.xml");
@@ -659,15 +656,15 @@ public class OrderingTest
         f3._name="C";
         metaData._webFragmentNameMap.put(f3._name, f3);
         metaData._webFragmentResourceMap.put(jar3, f3);
-        
+
         TestResource jar4 = new TestResource("D");
         resources.add(jar4);
         TestResource r4 = new TestResource("D/web-fragment.xml");
         FragmentDescriptor f4 = new FragmentDescriptor((Resource)null);
-        f4._name="D"; 
+        f4._name="D";
         metaData._webFragmentNameMap.put(f4._name, f4);
         metaData._webFragmentResourceMap.put(jar4, f4);
-        
+
         TestResource jar5 = new TestResource("E");
         resources.add(jar5);
         TestResource r5 = new TestResource("E/web-fragment.xml");
@@ -675,7 +672,7 @@ public class OrderingTest
         f5._name="E";
         metaData._webFragmentNameMap.put(f5._name, f5);
         metaData._webFragmentResourceMap.put(jar5, f5);
-        
+
         TestResource jar6 = new TestResource("plain");
         resources.add(jar6);
         TestResource r6 = new TestResource ("plain/web-fragment.xml");
@@ -683,32 +680,32 @@ public class OrderingTest
         f6._name=FragmentDescriptor.NAMELESS+"1";
         metaData._webFragmentNameMap.put(f6._name, f6);
         metaData._webFragmentResourceMap.put(jar6, f6);
-        
+
         List<Resource> list = metaData._ordering.order(resources);
-        
+
         String[] outcomes = {"ABCDEplain"};
         String result = "";
         for (Resource r:list)
             result += ((TestResource)r)._name;
-        
+
         if (!checkResult(result, outcomes))
             fail("No outcome matched "+result);
     }
-   
+
     @Test
     public void testAbsoluteOrdering2 ()
     throws Exception
     {
-        // C,B,A 
+        // C,B,A
         List<Resource> resources = new ArrayList<Resource>();
-        
+
         WebAppContext wac = new WebAppContext();
         MetaData metaData = new MetaData();
         metaData._ordering = new AbsoluteOrdering(metaData);
         ((AbsoluteOrdering)metaData._ordering).add("C");
         ((AbsoluteOrdering)metaData._ordering).add("B");
         ((AbsoluteOrdering)metaData._ordering).add("A");
-        
+
         TestResource jar1 = new TestResource("A");
         resources.add(jar1);
         TestResource r1 = new TestResource("A/web-fragment.xml");
@@ -716,15 +713,15 @@ public class OrderingTest
         f1._name = "A";
         metaData._webFragmentNameMap.put(f1._name, f1);
         metaData._webFragmentResourceMap.put(jar1,f1);
-        
-        TestResource jar2 = new TestResource("B");  
+
+        TestResource jar2 = new TestResource("B");
         resources.add(jar2);
         TestResource r2 = new TestResource("B/web-fragment.xml");
         FragmentDescriptor f2 = new FragmentDescriptor(r2);
         f2._name="B";
         metaData._webFragmentNameMap.put(f2._name, f2);
         metaData._webFragmentResourceMap.put(jar2,f2);
-        
+
         TestResource jar3 = new TestResource("C");
         resources.add(jar3);
         TestResource r3 = new TestResource("C/web-fragment.xml");
@@ -732,15 +729,15 @@ public class OrderingTest
         f3._name="C";
         metaData._webFragmentNameMap.put(f3._name, f3);
         metaData._webFragmentResourceMap.put(jar3,f3);
-        
+
         TestResource jar4 = new TestResource("D");
         resources.add(jar4);
         TestResource r4 = new TestResource("D/web-fragment.xml");
         FragmentDescriptor f4 = new FragmentDescriptor(r4);
-        f4._name="D"; 
+        f4._name="D";
         metaData._webFragmentNameMap.put(f4._name, f4);
         metaData._webFragmentResourceMap.put(jar4,f4);
-        
+
         TestResource jar5 = new TestResource("E");
         resources.add(jar5);
         TestResource r5 = new TestResource("E/web-fragment.xml");
@@ -748,7 +745,7 @@ public class OrderingTest
         f5._name="E";
         metaData._webFragmentNameMap.put(f5._name, f5);
         metaData._webFragmentResourceMap.put(jar5,f5);
-        
+
         TestResource jar6 = new TestResource("plain");
         resources.add(jar6);
         TestResource r6 = new TestResource("plain/web-fragment.xml");
@@ -756,36 +753,36 @@ public class OrderingTest
         f6._name=FragmentDescriptor.NAMELESS+"1";
         metaData._webFragmentNameMap.put(f6._name, f6);
         metaData._webFragmentResourceMap.put(jar6,f6);
-        
+
         List<Resource> list = metaData._ordering.order(resources);
         String[] outcomes = {"CBA"};
         String result = "";
         for (Resource r:list)
             result += ((TestResource)r)._name;
-        
-        
+
+
         if (!checkResult(result, outcomes))
             fail("No outcome matched "+result);
     }
-    
+
     @Test
     public void testAbsoluteOrdering3 ()
     throws Exception
     {
         //empty <absolute-ordering>
-        
+
         WebAppContext wac = new WebAppContext();
         MetaData metaData = new MetaData();
         metaData._ordering = new AbsoluteOrdering(metaData);
         List<Resource> resources = new ArrayList<Resource>();
-        
+
         resources.add(new TestResource("A"));
         resources.add(new TestResource("B"));
-        
+
         List<Resource> list = metaData._ordering.order(resources);
         assertTrue(list.isEmpty());
     }
-    
+
     @Test
     public void testRelativeOrderingWithPlainJars ()
     throws Exception
@@ -820,7 +817,7 @@ public class OrderingTest
         f2._otherType = FragmentDescriptor.OtherType.Before;
         f2._befores.add("C");
 
-        //C: after A   
+        //C: after A
         TestResource jar3 = new TestResource("C");
         resources.add(jar3);
         TestResource r3 = new TestResource("C/web-fragment.xml");
@@ -831,15 +828,15 @@ public class OrderingTest
         //((RelativeOrdering)metaData._ordering).addNoOthers(f3);
         f3._otherType = FragmentDescriptor.OtherType.None;
         f3._afters.add("A");
-        
+
         //No fragment jar 1
         TestResource r4 = new TestResource("plain1");
         resources.add(r4);
-        
+
         //No fragment jar 2
         TestResource r5 = new TestResource("plain2");
         resources.add(r5);
-        
+
         //result: BAC
         String[] outcomes = {"Bplain1plain2AC"};
 
@@ -847,11 +844,11 @@ public class OrderingTest
         String result = "";
         for (Resource r:orderedList)
            result+=(((TestResource)r)._name);
-        
+
         if (!checkResult(result, outcomes))
             fail ("No outcome matched "+result);
     }
-    
+
     @Test
     public void testRelativeOrderingWithPlainJars2 ()
     throws Exception
@@ -905,7 +902,7 @@ public class OrderingTest
         ((AbsoluteOrdering)metaData._ordering).add("B");
         ((AbsoluteOrdering)metaData._ordering).add("C");
         ((AbsoluteOrdering)metaData._ordering).addOthers();
-        
+
         TestResource jar1 = new TestResource("A");
         resources.add(jar1);
         TestResource r1 = new TestResource("A/web-fragment.xml");
@@ -913,7 +910,7 @@ public class OrderingTest
         f1._name = "A";
         metaData._webFragmentNameMap.put(f1._name, f1);
         metaData._webFragmentResourceMap.put(jar1,f1);
-        
+
         TestResource jar2 = new TestResource("B");
         resources.add(jar2);
         TestResource r2 = new TestResource("B/web-fragment.xml");
@@ -921,7 +918,7 @@ public class OrderingTest
         f2._name="B";
         metaData._webFragmentNameMap.put(f2._name, f2);
         metaData._webFragmentResourceMap.put(jar2, f2);
-        
+
         TestResource jar3 = new TestResource("C");
         resources.add(jar3);
         TestResource r3 = new TestResource("C/web-fragment.xml");
@@ -929,15 +926,15 @@ public class OrderingTest
         f3._name="C";
         metaData._webFragmentNameMap.put(f3._name, f3);
         metaData._webFragmentResourceMap.put(jar3, f3);
-        
+
         TestResource jar4 = new TestResource("D");
         resources.add(jar4);
         TestResource r4 = new TestResource("D/web-fragment.xml");
         FragmentDescriptor f4 = new FragmentDescriptor((Resource)null);
-        f4._name="D"; 
+        f4._name="D";
         metaData._webFragmentNameMap.put(f4._name, f4);
         metaData._webFragmentResourceMap.put(jar4, f4);
-        
+
         TestResource jar5 = new TestResource("E");
         resources.add(jar5);
         TestResource r5 = new TestResource("E/web-fragment.xml");
@@ -945,7 +942,7 @@ public class OrderingTest
         f5._name="E";
         metaData._webFragmentNameMap.put(f5._name, f5);
         metaData._webFragmentResourceMap.put(jar5, f5);
-        
+
         TestResource jar6 = new TestResource("plain");
         resources.add(jar6);
         TestResource r6 = new TestResource("plain/web-fragment.xml");
@@ -953,27 +950,27 @@ public class OrderingTest
         f6._name=FragmentDescriptor.NAMELESS+"1";
         metaData._webFragmentNameMap.put(f6._name, f6);
         metaData._webFragmentResourceMap.put(jar6, f6);
-        
+
         //plain jar
         TestResource r7 = new TestResource("plain1");
         resources.add(r7);
-        
+
         TestResource r8 = new TestResource("plain2");
         resources.add(r8);
-        
+
         List<Resource> list = metaData._ordering.order(resources);
-        
+
         String[] outcomes = {"ABCDEplainplain1plain2"};
         String result = "";
         for (Resource r:list)
             result += ((TestResource)r)._name;
-        
+
         if (!checkResult(result, outcomes))
             fail("No outcome matched "+result);
     }
-    
-    
-    
+
+
+
     public boolean checkResult (String result, String[] outcomes)
     {
         boolean matched = false;
diff --git a/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/WebAppClassLoaderTest.java b/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/WebAppClassLoaderTest.java
index 564fa8e..49011ab 100644
--- a/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/WebAppClassLoaderTest.java
+++ b/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/WebAppClassLoaderTest.java
@@ -19,11 +19,17 @@
 package org.eclipse.jetty.webapp;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
+import java.lang.instrument.ClassFileTransformer;
+import java.lang.instrument.IllegalClassFormatException;
 import java.net.URL;
+import java.security.ProtectionDomain;
 import java.util.ArrayList;
 import java.util.Enumeration;
+import java.util.Iterator;
 import java.util.List;
 
 import org.eclipse.jetty.util.resource.Resource;
@@ -60,7 +66,7 @@ public class WebAppClassLoaderTest
 
         assertTrue(cantLoadClass("org.eclipse.jetty.webapp.Configuration"));
 
-        Class clazzA = _loader.loadClass("org.acme.webapp.ClassInJarA");
+        Class<?> clazzA = _loader.loadClass("org.acme.webapp.ClassInJarA");
         assertTrue(clazzA.getField("FROM_PARENT")!=null);
     }
 
@@ -85,6 +91,69 @@ public class WebAppClassLoaderTest
             assertTrue(true);
         }
     }
+    
+    @Test
+    public void testClassFileTranslations() throws Exception
+    {
+        final List<Object> results=new ArrayList<Object>();
+        
+        _loader.addTransformer(new ClassFileTransformer()
+        {
+            public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer)
+                    throws IllegalClassFormatException
+            {
+                results.add(loader);
+                byte[] b = new byte[classfileBuffer.length];
+                for (int i=0;i<classfileBuffer.length;i++)
+                    b[i]=(byte)(classfileBuffer[i]^0xff);
+                return b;
+            }
+        });
+        _loader.addTransformer(new ClassFileTransformer()
+        {
+            public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer)
+                    throws IllegalClassFormatException
+            {
+                results.add(className);
+                byte[] b = new byte[classfileBuffer.length];
+                for (int i=0;i<classfileBuffer.length;i++)
+                    b[i]=(byte)(classfileBuffer[i]^0xff);
+                return b;
+            }
+        });
+        
+        _context.setParentLoaderPriority(false);
+        assertTrue(canLoadClass("org.acme.webapp.ClassInJarA"));
+        assertTrue(canLoadClass("org.acme.webapp.ClassInJarB"));
+        assertTrue(canLoadClass("org.acme.other.ClassInClassesC"));
+        assertTrue(canLoadClass("java.lang.String"));
+        assertTrue(cantLoadClass("org.eclipse.jetty.webapp.Configuration"));
+        
+        Iterator<Object> iter = results.iterator();
+        assertEquals(_loader,iter.next());
+        assertEquals("org.acme.webapp.ClassInJarA",iter.next());
+        assertEquals(_loader,iter.next());
+        assertEquals("org.acme.webapp.ClassInJarB",iter.next());
+        assertEquals(_loader,iter.next());
+        assertEquals("org.acme.other.ClassInClassesC",iter.next());
+        assertFalse(iter.hasNext());
+    }
+    
+    @Test
+    public void testNullClassFileTransformer () throws Exception
+    {
+        _loader.addTransformer(new ClassFileTransformer()
+        {
+            public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer)
+                    throws IllegalClassFormatException
+            {
+                return null;
+            }
+        });
+        
+        
+        assertTrue(canLoadClass("org.acme.webapp.ClassInJarA"));
+    }
 
     @Test
     public void testExposedClass() throws Exception
@@ -121,9 +190,24 @@ public class WebAppClassLoaderTest
         assertTrue(canLoadClass("org.acme.webapp.ClassInJarA"));
         assertTrue(canLoadClass("org.acme.webapp.ClassInJarB"));
         assertTrue(canLoadClass("org.acme.other.ClassInClassesC"));
-
         assertTrue(cantLoadClass("org.eclipse.jetty.webapp.Configuration"));
         assertTrue(cantLoadClass("org.eclipse.jetty.webapp.JarScanner"));
+
+        oldSysC=_context.getSystemClasses();
+        newSysC=new String[oldSysC.length+1];
+        newSysC[0]="org.acme.webapp.ClassInJarA";
+        System.arraycopy(oldSysC,0,newSysC,1,oldSysC.length);
+        _context.setSystemClasses(newSysC);
+
+        assertNotNull(_loader.getResource("org/acme/webapp/ClassInJarA.class"));
+        _context.setSystemClasses(oldSysC);
+
+        oldServC=_context.getServerClasses();
+        newServC=new String[oldServC.length+1];
+        newServC[0]="org.acme.webapp.ClassInJarA";
+        System.arraycopy(oldServC,0,newServC,1,oldServC.length);
+        _context.setServerClasses(newServC);
+        assertNotNull(_loader.getResource("org/acme/webapp/ClassInJarA.class"));
     }
 
     @Test
@@ -182,7 +266,7 @@ public class WebAppClassLoaderTest
     {
         return _loader.loadClass(clazz)!=null;
     }
-    
+
     private boolean cantLoadClass(String clazz)
     {
         try
diff --git a/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/WebAppContextTest.java b/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/WebAppContextTest.java
index 5dc5428..d19fae9 100644
--- a/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/WebAppContextTest.java
+++ b/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/WebAppContextTest.java
@@ -20,7 +20,6 @@ package org.eclipse.jetty.webapp;
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
 import java.io.File;
@@ -39,6 +38,7 @@ import org.eclipse.jetty.server.LocalConnector;
 import org.eclipse.jetty.server.Server;
 import org.eclipse.jetty.server.handler.ContextHandlerCollection;
 import org.eclipse.jetty.server.handler.HandlerList;
+import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.resource.Resource;
 import org.eclipse.jetty.util.resource.ResourceCollection;
 import org.junit.Test;
@@ -51,7 +51,7 @@ public class WebAppContextTest
         Server server = new Server();
         //test if no classnames set, its the defaults
         WebAppContext wac = new WebAppContext();
-        assertNull(wac.getConfigurations());
+        Assert.assertEquals(0,wac.getConfigurations().length);
         String[] classNames = wac.getConfigurationClasses();
         assertNotNull(classNames);
 
@@ -66,7 +66,7 @@ public class WebAppContextTest
         String[] classNames = {"x.y.z"};
 
         Server server = new Server();
-        server.setAttribute(WebAppContext.SERVER_CONFIG, classNames);
+        server.setAttribute(Configuration.ATTR, classNames);
 
         //test an explicitly set classnames list overrides that from the server
         WebAppContext wac = new WebAppContext();
@@ -80,6 +80,14 @@ public class WebAppContextTest
         //test if no explicit classnames, they come from the server
         WebAppContext wac2 = new WebAppContext();
         wac2.setServer(server);
+        try
+        {
+            wac2.loadConfigurations();
+        }
+        catch(Exception e)
+        {
+            Log.getRootLogger().ignore(e);
+        }
         assertTrue(Arrays.equals(classNames, wac2.getConfigurationClasses()));
     }
 
@@ -94,11 +102,11 @@ public class WebAppContextTest
         //test that explicit config instances override any from server
         String[] classNames = {"x.y.z"};
         Server server = new Server();
-        server.setAttribute(WebAppContext.SERVER_CONFIG, classNames);
+        server.setAttribute(Configuration.ATTR, classNames);
         wac.setServer(server);
         assertTrue(Arrays.equals(configs,wac.getConfigurations()));
     }
-    
+
     @Test
     public void testRealPathDoesNotExist() throws Exception
     {
@@ -111,30 +119,30 @@ public class WebAppContextTest
         assertNotNull(ctx.getRealPath("/doesnotexist"));
         assertNotNull(ctx.getRealPath("/doesnotexist/"));
     }
-    
+
     /**
      * tests that the servlet context white list works
-     * 
+     *
      * @throws Exception
      */
-    @Test 
+    @Test
     public void testContextWhiteList() throws Exception
     {
         Server server = new Server(0);
         HandlerList handlers = new HandlerList();
-        WebAppContext contextA = new WebAppContext(".", "/A"); 
-        
+        WebAppContext contextA = new WebAppContext(".", "/A");
+
         contextA.addServlet( ServletA.class, "/s");
         handlers.addHandler(contextA);
         WebAppContext contextB = new WebAppContext(".", "/B");
-        
+
         contextB.addServlet(ServletB.class, "/s");
         contextB.setContextWhiteList(new String [] { "/doesnotexist", "/B/s" } );
         handlers.addHandler(contextB);
-        
+
         server.setHandler(handlers);
         server.start();
-        
+
         // context A should be able to get both A and B servlet contexts
         Assert.assertNotNull(contextA.getServletHandler().getServletContext().getContext("/A/s"));
         Assert.assertNotNull(contextA.getServletHandler().getServletContext().getContext("/B/s"));
@@ -143,36 +151,36 @@ public class WebAppContextTest
         Assert.assertNull(contextB.getServletHandler().getServletContext().getContext("/A/s"));
         Assert.assertNotNull(contextB.getServletHandler().getServletContext().getContext("/B/s"));
     }
-    
 
-    @Test 
+
+    @Test
     public void testAlias() throws Exception
     {
         File dir = File.createTempFile("dir",null);
         dir.delete();
         dir.mkdir();
         dir.deleteOnExit();
-        
+
         File webinf = new File(dir,"WEB-INF");
         webinf.mkdir();
-        
+
         File classes = new File(dir,"classes");
         classes.mkdir();
-        
+
         File someclass = new File(classes,"SomeClass.class");
         someclass.createNewFile();
-        
+
         WebAppContext context = new WebAppContext();
         context.setBaseResource(new ResourceCollection(dir.getAbsolutePath()));
-        
+
         context.setResourceAlias("/WEB-INF/classes/", "/classes/");
 
         assertTrue(Resource.newResource(context.getServletContext().getResource("/WEB-INF/classes/SomeClass.class")).exists());
         assertTrue(Resource.newResource(context.getServletContext().getResource("/classes/SomeClass.class")).exists());
 
     }
-    
-    
+
+
     @Test
     public void testIsProtected() throws Exception
     {
@@ -196,7 +204,7 @@ public class WebAppContextTest
         handlers.addHandler(contexts);
         contexts.addHandler(context);
         
-        LocalConnector connector = new LocalConnector();
+        LocalConnector connector = new LocalConnector(server);
         server.addConnector(connector);
         
         server.start();
@@ -218,15 +226,15 @@ public class WebAppContextTest
         public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException
         {
             this.getServletContext().getContext("/A/s");
-        }      
+        }
     }
-    
+
     class ServletB extends GenericServlet
     {
         @Override
         public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException
         {
             this.getServletContext().getContext("/B/s");
-        }      
+        }
     }
 }
diff --git a/jetty-websocket/README.txt b/jetty-websocket/README.TXT
similarity index 100%
rename from jetty-websocket/README.txt
rename to jetty-websocket/README.TXT
diff --git a/jetty-websocket/javax-websocket-client-impl/pom.xml b/jetty-websocket/javax-websocket-client-impl/pom.xml
new file mode 100644
index 0000000..9c8b25e
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/pom.xml
@@ -0,0 +1,100 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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">
+  <parent>
+    <groupId>org.eclipse.jetty.websocket</groupId>
+    <artifactId>websocket-parent</artifactId>
+    <version>9.2.13.v20150730</version>
+  </parent>
+
+  <modelVersion>4.0.0</modelVersion>
+  <artifactId>javax-websocket-client-impl</artifactId>
+  <name>Jetty :: Websocket :: javax.websocket :: Client Implementation</name>
+
+  <properties>
+    <bundle-symbolic-name>${project.groupId}.javax.websocket</bundle-symbolic-name>
+  </properties>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.eclipse.jetty.websocket</groupId>
+      <artifactId>websocket-client</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>javax.websocket</groupId>
+      <artifactId>javax.websocket-api</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-server</artifactId>
+      <version>${project.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.websocket</groupId>
+      <artifactId>websocket-server</artifactId>
+      <version>${project.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.websocket</groupId>
+      <artifactId>websocket-common</artifactId>
+      <version>${project.version}</version>
+      <classifier>tests</classifier>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.toolchain</groupId>
+      <artifactId>jetty-test-helper</artifactId>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+          <groupId>org.apache.felix</groupId>
+          <artifactId>maven-bundle-plugin</artifactId>
+          <extensions>true</extensions>
+          <executions>
+              <execution>
+                  <goals>
+                      <goal>manifest</goal>
+                  </goals>
+                  <configuration>
+                      <instructions>
+                        <Bundle-Description>javax.websocket.client Implementation</Bundle-Description>
+                        <Export-Package>org.eclipse.jetty.websocket.jsr356.*;version="${parsedVersion.majorVersion}.${parsedVersion.minorVersion}.${parsedVersion.incrementalVersion}"</Export-Package>
+                      </instructions>
+                  </configuration>
+              </execution>
+          </executions>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-enforcer-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>ban-java-servlet-api</id>
+            <goals>
+              <goal>enforce</goal>
+            </goals>
+            <configuration>
+              <rules>
+                <bannedDependencies>
+                  <includes>
+                    <include>javax.servlet</include>
+                    <include>servletapi</include>
+                    <include>org.eclipse.jetty.orbit:javax.servlet</include>
+                    <include>org.mortbay.jetty:servlet-api</include>
+                    <include>jetty:servlet-api</include>
+                  </includes>
+                </bannedDependencies>
+              </rules>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+</project>
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/AbstractJsrRemote.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/AbstractJsrRemote.java
new file mode 100644
index 0000000..aed0f25
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/AbstractJsrRemote.java
@@ -0,0 +1,195 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.concurrent.Future;
+import javax.websocket.EncodeException;
+import javax.websocket.Encoder;
+import javax.websocket.RemoteEndpoint;
+import javax.websocket.SendHandler;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.BatchMode;
+import org.eclipse.jetty.websocket.common.WebSocketRemoteEndpoint;
+import org.eclipse.jetty.websocket.common.io.FutureWriteCallback;
+import org.eclipse.jetty.websocket.common.message.MessageOutputStream;
+import org.eclipse.jetty.websocket.common.message.MessageWriter;
+import org.eclipse.jetty.websocket.jsr356.encoders.EncodeFailedFuture;
+
+public abstract class AbstractJsrRemote implements RemoteEndpoint
+{
+    private static final Logger LOG = Log.getLogger(AbstractJsrRemote.class);
+
+    protected final JsrSession session;
+    protected final WebSocketRemoteEndpoint jettyRemote;
+    protected final EncoderFactory encoders;
+
+    protected AbstractJsrRemote(JsrSession session)
+    {
+        this.session = session;
+        if (!(session.getRemote() instanceof WebSocketRemoteEndpoint))
+        {
+            StringBuilder err = new StringBuilder();
+            err.append("Unexpected implementation [");
+            err.append(session.getRemote().getClass().getName());
+            err.append("].  Expected an instanceof [");
+            err.append(WebSocketRemoteEndpoint.class.getName());
+            err.append("]");
+            throw new IllegalStateException(err.toString());
+        }
+        this.jettyRemote = (WebSocketRemoteEndpoint)session.getRemote();
+        this.encoders = session.getEncoderFactory();
+    }
+
+    protected void assertMessageNotNull(Object data)
+    {
+        if (data == null)
+        {
+            throw new IllegalArgumentException("message cannot be null");
+        }
+    }
+
+    protected void assertSendHandlerNotNull(SendHandler handler)
+    {
+        if (handler == null)
+        {
+            throw new IllegalArgumentException("SendHandler cannot be null");
+        }
+    }
+
+    @Override
+    public void flushBatch() throws IOException
+    {
+        jettyRemote.flush();
+    }
+
+    @Override
+    public boolean getBatchingAllowed()
+    {
+        return jettyRemote.getBatchMode() == BatchMode.ON;
+    }
+
+    @Override
+    public void setBatchingAllowed(boolean allowed) throws IOException
+    {
+        if (jettyRemote.getBatchMode() == BatchMode.ON && !allowed)
+            jettyRemote.flush();
+        jettyRemote.setBatchMode(allowed ? BatchMode.ON : BatchMode.OFF);
+    }
+
+    @SuppressWarnings(
+            {"rawtypes", "unchecked"})
+    public Future<Void> sendObjectViaFuture(Object data)
+    {
+        assertMessageNotNull(data);
+        if (LOG.isDebugEnabled())
+        {
+            LOG.debug("sendObject({})", data);
+        }
+
+        Encoder encoder = encoders.getEncoderFor(data.getClass());
+        if (encoder == null)
+        {
+            throw new IllegalArgumentException("No encoder for type: " + data.getClass());
+        }
+
+        if (encoder instanceof Encoder.Text)
+        {
+            Encoder.Text text = (Encoder.Text)encoder;
+            try
+            {
+                String msg = text.encode(data);
+                return jettyRemote.sendStringByFuture(msg);
+            }
+            catch (EncodeException e)
+            {
+                return new EncodeFailedFuture(data, text, Encoder.Text.class, e);
+            }
+        }
+        else if (encoder instanceof Encoder.TextStream)
+        {
+            Encoder.TextStream etxt = (Encoder.TextStream)encoder;
+            FutureWriteCallback callback = new FutureWriteCallback();
+            try (MessageWriter writer = new MessageWriter(session))
+            {
+                writer.setCallback(callback);
+                etxt.encode(data, writer);
+                return callback;
+            }
+            catch (EncodeException | IOException e)
+            {
+                return new EncodeFailedFuture(data, etxt, Encoder.Text.class, e);
+            }
+        }
+        else if (encoder instanceof Encoder.Binary)
+        {
+            Encoder.Binary ebin = (Encoder.Binary)encoder;
+            try
+            {
+                ByteBuffer buf = ebin.encode(data);
+                return jettyRemote.sendBytesByFuture(buf);
+            }
+            catch (EncodeException e)
+            {
+                return new EncodeFailedFuture(data, ebin, Encoder.Binary.class, e);
+            }
+        }
+        else if (encoder instanceof Encoder.BinaryStream)
+        {
+            Encoder.BinaryStream ebin = (Encoder.BinaryStream)encoder;
+            FutureWriteCallback callback = new FutureWriteCallback();
+            try (MessageOutputStream out = new MessageOutputStream(session))
+            {
+                out.setCallback(callback);
+                ebin.encode(data, out);
+                return callback;
+            }
+            catch (EncodeException | IOException e)
+            {
+                return new EncodeFailedFuture(data, ebin, Encoder.Binary.class, e);
+            }
+        }
+
+        throw new IllegalArgumentException("Unknown encoder type: " + encoder);
+    }
+
+    @Override
+    public void sendPing(ByteBuffer data) throws IOException, IllegalArgumentException
+    {
+        if (LOG.isDebugEnabled())
+        {
+            LOG.debug("sendPing({})", BufferUtil.toDetailString(data));
+        }
+        jettyRemote.sendPing(data);
+    }
+
+    @Override
+    public void sendPong(ByteBuffer data) throws IOException, IllegalArgumentException
+    {
+        if (LOG.isDebugEnabled())
+        {
+            LOG.debug("sendPong({})", BufferUtil.toDetailString(data));
+        }
+        jettyRemote.sendPong(data);
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/BasicEndpointConfig.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/BasicEndpointConfig.java
new file mode 100644
index 0000000..ebde681
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/BasicEndpointConfig.java
@@ -0,0 +1,63 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.websocket.Decoder;
+import javax.websocket.Encoder;
+import javax.websocket.EndpointConfig;
+
+/**
+ * Basic EndpointConfig (used when no EndpointConfig is provided or discovered)
+ */
+public class BasicEndpointConfig implements EndpointConfig
+{
+    private List<Class<? extends Decoder>> decoders;
+    private List<Class<? extends Encoder>> encoders;
+    private Map<String, Object> userProperties;
+
+    public BasicEndpointConfig()
+    {
+        decoders = Collections.emptyList();
+        encoders = Collections.emptyList();
+        userProperties = new HashMap<>();
+    }
+
+    @Override
+    public List<Class<? extends Decoder>> getDecoders()
+    {
+        return decoders;
+    }
+
+    @Override
+    public List<Class<? extends Encoder>> getEncoders()
+    {
+        return encoders;
+    }
+
+    @Override
+    public Map<String, Object> getUserProperties()
+    {
+        return userProperties;
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/ClientContainer.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/ClientContainer.java
new file mode 100644
index 0000000..b6366c3
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/ClientContainer.java
@@ -0,0 +1,390 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Future;
+import javax.websocket.ClientEndpoint;
+import javax.websocket.ClientEndpointConfig;
+import javax.websocket.DeploymentException;
+import javax.websocket.Endpoint;
+import javax.websocket.EndpointConfig;
+import javax.websocket.Extension;
+import javax.websocket.Session;
+import javax.websocket.WebSocketContainer;
+
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.eclipse.jetty.util.thread.ShutdownThread;
+import org.eclipse.jetty.websocket.api.InvalidWebSocketException;
+import org.eclipse.jetty.websocket.api.extensions.ExtensionFactory;
+import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
+import org.eclipse.jetty.websocket.client.WebSocketClient;
+import org.eclipse.jetty.websocket.client.io.UpgradeListener;
+import org.eclipse.jetty.websocket.common.SessionListener;
+import org.eclipse.jetty.websocket.common.WebSocketSession;
+import org.eclipse.jetty.websocket.jsr356.annotations.AnnotatedEndpointScanner;
+import org.eclipse.jetty.websocket.jsr356.client.AnnotatedClientEndpointMetadata;
+import org.eclipse.jetty.websocket.jsr356.client.EmptyClientEndpointConfig;
+import org.eclipse.jetty.websocket.jsr356.client.SimpleEndpointMetadata;
+import org.eclipse.jetty.websocket.jsr356.decoders.PrimitiveDecoderMetadataSet;
+import org.eclipse.jetty.websocket.jsr356.encoders.PrimitiveEncoderMetadataSet;
+import org.eclipse.jetty.websocket.jsr356.endpoints.EndpointInstance;
+import org.eclipse.jetty.websocket.jsr356.endpoints.JsrEventDriverFactory;
+import org.eclipse.jetty.websocket.jsr356.metadata.EndpointMetadata;
+
+/**
+ * Container for Client use of the javax.websocket API.
+ * <p>
+ * This should be specific to a JVM if run in a standalone mode. or specific to a WebAppContext if running on the Jetty server.
+ */
+public class ClientContainer extends ContainerLifeCycle implements WebSocketContainer, SessionListener
+{
+    private static final Logger LOG = Log.getLogger(ClientContainer.class);
+    
+    /** Tracking all primitive decoders for the container */
+    private final DecoderFactory decoderFactory;
+    /** Tracking all primitive encoders for the container */
+    private final EncoderFactory encoderFactory;
+
+    /** Tracking for all declared Client endpoints */
+    private final Map<Class<?>, EndpointMetadata> endpointClientMetadataCache;
+    /** Tracking for all open Sessions */
+    private Set<Session> openSessions = new CopyOnWriteArraySet<>();
+    /** The jetty websocket client in use for this container */
+    private WebSocketClient client;
+
+    public ClientContainer()
+    {
+        // This constructor is used with Standalone JSR Client usage.
+        this(null);
+        client.setDaemon(true);
+    }
+    
+    public ClientContainer(Executor executor)
+    {
+        endpointClientMetadataCache = new ConcurrentHashMap<>();
+        decoderFactory = new DecoderFactory(PrimitiveDecoderMetadataSet.INSTANCE);
+        encoderFactory = new EncoderFactory(PrimitiveEncoderMetadataSet.INSTANCE);
+
+        EmptyClientEndpointConfig empty = new EmptyClientEndpointConfig();
+        decoderFactory.init(empty);
+        encoderFactory.init(empty);
+
+        boolean trustAll = Boolean.getBoolean("org.eclipse.jetty.websocket.jsr356.ssl-trust-all");
+        
+        client = new WebSocketClient(new SslContextFactory(trustAll), executor);
+        client.setEventDriverFactory(new JsrEventDriverFactory(client.getPolicy()));
+        client.setSessionFactory(new JsrSessionFactory(this,this,client));
+        addBean(client);
+
+        ShutdownThread.register(this);
+    }
+
+    private Session connect(EndpointInstance instance, URI path) throws IOException
+    {
+        Objects.requireNonNull(instance,"EndpointInstance cannot be null");
+        Objects.requireNonNull(path,"Path cannot be null");
+
+        ClientEndpointConfig config = (ClientEndpointConfig)instance.getConfig();
+        ClientUpgradeRequest req = new ClientUpgradeRequest();
+        UpgradeListener upgradeListener = null;
+
+        for (Extension ext : config.getExtensions())
+        {
+            req.addExtensions(new JsrExtensionConfig(ext));
+        }
+
+        if (config.getPreferredSubprotocols().size() > 0)
+        {
+            req.setSubProtocols(config.getPreferredSubprotocols());
+        }
+
+        if (config.getConfigurator() != null)
+        {
+            upgradeListener = new JsrUpgradeListener(config.getConfigurator());
+        }
+
+        Future<org.eclipse.jetty.websocket.api.Session> futSess = client.connect(instance,path,req,upgradeListener);
+        try
+        {
+            return (JsrSession)futSess.get();
+        }
+        catch (InterruptedException e)
+        {
+            throw new IOException("Connect failure",e);
+        }
+        catch (ExecutionException e)
+        {
+            // Unwrap Actual Cause
+            Throwable cause = e.getCause();
+
+            if (cause instanceof IOException)
+            {
+                // Just rethrow
+                throw (IOException)cause;
+            }
+            else
+            {
+                throw new IOException("Connect failure",cause);
+            }
+        }
+    }
+
+    @Override
+    public Session connectToServer(Class<? extends Endpoint> endpointClass, ClientEndpointConfig config, URI path) throws DeploymentException, IOException
+    {
+        EndpointInstance instance = newClientEndpointInstance(endpointClass,config);
+        return connect(instance,path);
+    }
+
+    @Override
+    public Session connectToServer(Class<?> annotatedEndpointClass, URI path) throws DeploymentException, IOException
+    {
+        EndpointInstance instance = newClientEndpointInstance(annotatedEndpointClass,null);
+        return connect(instance,path);
+    }
+
+    @Override
+    public Session connectToServer(Endpoint endpoint, ClientEndpointConfig config, URI path) throws DeploymentException, IOException
+    {
+        EndpointInstance instance = newClientEndpointInstance(endpoint,config);
+        return connect(instance,path);
+    }
+
+    @Override
+    public Session connectToServer(Object endpoint, URI path) throws DeploymentException, IOException
+    {
+        EndpointInstance instance = newClientEndpointInstance(endpoint,null);
+        return connect(instance,path);
+    }
+
+    @Override
+    protected void doStop() throws Exception
+    {
+        ShutdownThread.deregister(this);
+        endpointClientMetadataCache.clear();
+        super.doStop();
+    }
+
+    public WebSocketClient getClient()
+    {
+        return client;
+    }
+
+    public EndpointMetadata getClientEndpointMetadata(Class<?> endpoint, EndpointConfig config)
+    {
+        EndpointMetadata metadata = null;
+
+        synchronized (endpointClientMetadataCache)
+        {
+            metadata = endpointClientMetadataCache.get(endpoint);
+
+            if (metadata != null)
+            {
+                return metadata;
+            }
+
+            ClientEndpoint anno = endpoint.getAnnotation(ClientEndpoint.class);
+            if (anno != null)
+            {
+                // Annotated takes precedence here
+                AnnotatedClientEndpointMetadata annoMetadata = new AnnotatedClientEndpointMetadata(this,endpoint);
+                AnnotatedEndpointScanner<ClientEndpoint, ClientEndpointConfig> scanner = new AnnotatedEndpointScanner<>(annoMetadata);
+                scanner.scan();
+                metadata = annoMetadata;
+            }
+            else if (Endpoint.class.isAssignableFrom(endpoint))
+            {
+                // extends Endpoint
+                @SuppressWarnings("unchecked")
+                Class<? extends Endpoint> eendpoint = (Class<? extends Endpoint>)endpoint;
+                metadata = new SimpleEndpointMetadata(eendpoint,config);
+            }
+            else
+            {
+                StringBuilder err = new StringBuilder();
+                err.append("Not a recognized websocket [");
+                err.append(endpoint.getName());
+                err.append("] does not extend @").append(ClientEndpoint.class.getName());
+                err.append(" or extend from ").append(Endpoint.class.getName());
+                throw new InvalidWebSocketException("Unable to identify as valid Endpoint: " + endpoint);
+            }
+
+            endpointClientMetadataCache.put(endpoint,metadata);
+            return metadata;
+        }
+    }
+
+    public DecoderFactory getDecoderFactory()
+    {
+        return decoderFactory;
+    }
+
+    @Override
+    public long getDefaultAsyncSendTimeout()
+    {
+        return client.getAsyncWriteTimeout();
+    }
+
+    @Override
+    public int getDefaultMaxBinaryMessageBufferSize()
+    {
+        return client.getMaxBinaryMessageBufferSize();
+    }
+
+    @Override
+    public long getDefaultMaxSessionIdleTimeout()
+    {
+        return client.getMaxIdleTimeout();
+    }
+
+    @Override
+    public int getDefaultMaxTextMessageBufferSize()
+    {
+        return client.getMaxTextMessageBufferSize();
+    }
+
+    public EncoderFactory getEncoderFactory()
+    {
+        return encoderFactory;
+    }
+
+    @Override
+    public Set<Extension> getInstalledExtensions()
+    {
+        Set<Extension> ret = new HashSet<>();
+        ExtensionFactory extensions = client.getExtensionFactory();
+
+        for (String name : extensions.getExtensionNames())
+        {
+            ret.add(new JsrExtension(name));
+        }
+
+        return ret;
+    }
+
+    /**
+     * Used in {@link Session#getOpenSessions()}
+     */
+    public Set<Session> getOpenSessions()
+    {
+        return Collections.unmodifiableSet(this.openSessions);
+    }
+
+    private EndpointInstance newClientEndpointInstance(Class<?> endpointClass, ClientEndpointConfig config)
+    {
+        try
+        {
+            return newClientEndpointInstance(endpointClass.newInstance(),config);
+        }
+        catch (InstantiationException | IllegalAccessException e)
+        {
+            throw new InvalidWebSocketException("Unable to instantiate websocket: " + endpointClass.getClass());
+        }
+    }
+
+    public EndpointInstance newClientEndpointInstance(Object endpoint, ClientEndpointConfig config)
+    {
+        EndpointMetadata metadata = getClientEndpointMetadata(endpoint.getClass(),config);
+        ClientEndpointConfig cec = config;
+        if (config == null)
+        {
+            if (metadata instanceof AnnotatedClientEndpointMetadata)
+            {
+                cec = ((AnnotatedClientEndpointMetadata)metadata).getConfig();
+            }
+            else
+            {
+                cec = new EmptyClientEndpointConfig();
+            }
+        }
+        return new EndpointInstance(endpoint,cec,metadata);
+    }
+
+    @Override
+    public void onSessionClosed(WebSocketSession session)
+    {
+        if (session instanceof Session)
+        {
+            this.openSessions.remove((Session)session);
+        }
+        else
+        {
+            LOG.warn("JSR356 Implementation should not be mixed with native implementation: Expected {} to implement {}",session.getClass().getName(),
+                    Session.class.getName());
+        }
+    }
+
+    @Override
+    public void onSessionOpened(WebSocketSession session)
+    {
+        if (session instanceof Session)
+        {
+            this.openSessions.add((Session)session);
+        }
+        else
+        {
+            LOG.warn("JSR356 Implementation should not be mixed with native implementation: Expected {} to implement {}",session.getClass().getName(),
+                    Session.class.getName());
+        }
+    }
+
+    @Override
+    public void setAsyncSendTimeout(long ms)
+    {
+        client.setAsyncWriteTimeout(ms);
+    }
+
+    @Override
+    public void setDefaultMaxBinaryMessageBufferSize(int max)
+    {
+        // overall message limit (used in non-streaming)
+        client.getPolicy().setMaxBinaryMessageSize(max);
+        // incoming streaming buffer size
+        client.setMaxBinaryMessageBufferSize(max);
+    }
+
+    @Override
+    public void setDefaultMaxSessionIdleTimeout(long ms)
+    {
+        client.setMaxIdleTimeout(ms);
+    }
+
+    @Override
+    public void setDefaultMaxTextMessageBufferSize(int max)
+    {
+        // overall message limit (used in non-streaming)
+        client.getPolicy().setMaxTextMessageSize(max);
+        // incoming streaming buffer size
+        client.setMaxTextMessageBufferSize(max);
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/Configurable.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/Configurable.java
new file mode 100644
index 0000000..aec4f97
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/Configurable.java
@@ -0,0 +1,29 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356;
+
+import javax.websocket.EndpointConfig;
+
+/**
+ * Tag indicating a component that needs to be configured.
+ */
+public interface Configurable
+{
+    public void init(EndpointConfig config);
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/ConfigurationException.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/ConfigurationException.java
new file mode 100644
index 0000000..cd86f81
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/ConfigurationException.java
@@ -0,0 +1,36 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356;
+
+import org.eclipse.jetty.websocket.api.WebSocketException;
+
+public class ConfigurationException extends WebSocketException
+{
+    private static final long serialVersionUID = 3026803845657799372L;
+
+    public ConfigurationException(String message)
+    {
+        super(message);
+    }
+
+    public ConfigurationException(String message, Throwable cause)
+    {
+        super(message,cause);
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/DecoderFactory.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/DecoderFactory.java
new file mode 100644
index 0000000..4554b14
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/DecoderFactory.java
@@ -0,0 +1,183 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import javax.websocket.Decoder;
+import javax.websocket.EndpointConfig;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.jsr356.metadata.DecoderMetadata;
+import org.eclipse.jetty.websocket.jsr356.metadata.DecoderMetadataSet;
+
+/**
+ * Factory for {@link DecoderMetadata}
+ * <p>
+ * Relies on search order of parent {@link DecoderFactory} instances as such.
+ * <ul>
+ * <li>From Static DecoderMetadataSet (based on data in annotations and static EndpointConfig)</li>
+ * <li>From Composite DecoderMetadataSet (based static and instance specific EndpointConfig)</li>
+ * <li>Container declared DecoderMetadataSet (primitives)</li>
+ * </ul>
+ */
+public class DecoderFactory implements Configurable
+{
+    public static class Wrapper implements Configurable
+    {
+        private final Decoder decoder;
+        private final DecoderMetadata metadata;
+
+        private Wrapper(Decoder decoder, DecoderMetadata metadata)
+        {
+            this.decoder = decoder;
+            this.metadata = metadata;
+        }
+
+        public Decoder getDecoder()
+        {
+            return decoder;
+        }
+
+        public DecoderMetadata getMetadata()
+        {
+            return metadata;
+        }
+
+        @Override
+        public void init(EndpointConfig config)
+        {
+            this.decoder.init(config);
+        }
+    }
+
+    private static final Logger LOG = Log.getLogger(DecoderFactory.class);
+
+    private final DecoderMetadataSet metadatas;
+    private DecoderFactory parentFactory;
+    private Map<Class<?>, Wrapper> activeWrappers;
+
+    public DecoderFactory(DecoderMetadataSet metadatas)
+    {
+        this.metadatas = metadatas;
+        this.activeWrappers = new ConcurrentHashMap<>();
+    }
+
+    public DecoderFactory(DecoderMetadataSet metadatas, DecoderFactory parentFactory)
+    {
+        this(metadatas);
+        this.parentFactory = parentFactory;
+    }
+
+    public Decoder getDecoderFor(Class<?> type)
+    {
+        Wrapper wrapper = getWrapperFor(type);
+        if (wrapper == null)
+        {
+            return null;
+        }
+        return wrapper.decoder;
+    }
+
+    public DecoderMetadata getMetadataFor(Class<?> type)
+    {
+        if (LOG.isDebugEnabled())
+        {
+            LOG.debug("getMetadataFor({})",type);
+        }
+        DecoderMetadata metadata = metadatas.getMetadataByType(type);
+
+        if (metadata != null)
+        {
+            return metadata;
+        }
+
+        if (parentFactory != null)
+        {
+            return parentFactory.getMetadataFor(type);
+        }
+
+        return null;
+    }
+
+    public Wrapper getWrapperFor(Class<?> type)
+    {
+        synchronized (activeWrappers)
+        {
+            Wrapper wrapper = activeWrappers.get(type);
+
+            // Try parent (if needed)
+            if ((wrapper == null) && (parentFactory != null))
+            {
+                wrapper = parentFactory.getWrapperFor(type);
+            }
+
+            if (wrapper == null)
+            {
+                // Attempt to create Wrapper on demand
+                DecoderMetadata metadata = metadatas.getMetadataByType(type);
+                if (metadata == null)
+                {
+                    return null;
+                }
+                wrapper = newWrapper(metadata);
+                // track wrapper
+                activeWrappers.put(type,wrapper);
+            }
+
+            return wrapper;
+        }
+    }
+
+    @Override
+    public void init(EndpointConfig config)
+    {
+        if (LOG.isDebugEnabled())
+        {
+            LOG.debug("init({})",config);
+        }
+        // Instantiate all declared decoders
+        for (DecoderMetadata metadata : metadatas)
+        {
+            Wrapper wrapper = newWrapper(metadata);
+            activeWrappers.put(metadata.getObjectType(),wrapper);
+        }
+
+        // Initialize all decoders
+        for (Wrapper wrapper : activeWrappers.values())
+        {
+            wrapper.decoder.init(config);
+        }
+    }
+
+    public Wrapper newWrapper(DecoderMetadata metadata)
+    {
+        Class<? extends Decoder> decoderClass = metadata.getCoderClass();
+        try
+        {
+            Decoder decoder = decoderClass.newInstance();
+            return new Wrapper(decoder,metadata);
+        }
+        catch (InstantiationException | IllegalAccessException e)
+        {
+            throw new IllegalStateException("Unable to instantiate Decoder: " + decoderClass.getName());
+        }
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/EncoderFactory.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/EncoderFactory.java
new file mode 100644
index 0000000..29fedec
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/EncoderFactory.java
@@ -0,0 +1,177 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import javax.websocket.Encoder;
+import javax.websocket.EndpointConfig;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.jsr356.metadata.EncoderMetadata;
+import org.eclipse.jetty.websocket.jsr356.metadata.EncoderMetadataSet;
+
+/**
+ * Represents all of the declared {@link Encoder}s that the Container is aware of.
+ */
+public class EncoderFactory implements Configurable
+{
+    public static class Wrapper implements Configurable
+    {
+        private final Encoder encoder;
+        private final EncoderMetadata metadata;
+
+        private Wrapper(Encoder encoder, EncoderMetadata metadata)
+        {
+            this.encoder = encoder;
+            this.metadata = metadata;
+        }
+
+        public Encoder getEncoder()
+        {
+            return encoder;
+        }
+
+        public EncoderMetadata getMetadata()
+        {
+            return metadata;
+        }
+
+        @Override
+        public void init(EndpointConfig config)
+        {
+            this.encoder.init(config);
+        }
+    }
+
+    private static final Logger LOG = Log.getLogger(EncoderFactory.class);
+
+    private final EncoderMetadataSet metadatas;
+    private EncoderFactory parentFactory;
+    private Map<Class<?>, Wrapper> activeWrappers;
+
+    public EncoderFactory(EncoderMetadataSet metadatas)
+    {
+        this.metadatas = metadatas;
+        this.activeWrappers = new ConcurrentHashMap<>();
+    }
+
+    public EncoderFactory(EncoderMetadataSet metadatas, EncoderFactory parentFactory)
+    {
+        this(metadatas);
+        this.parentFactory = parentFactory;
+    }
+
+    public Encoder getEncoderFor(Class<?> type)
+    {
+        Wrapper wrapper = getWrapperFor(type);
+        if (wrapper == null)
+        {
+            return null;
+        }
+        return wrapper.encoder;
+    }
+
+    public EncoderMetadata getMetadataFor(Class<?> type)
+    {
+        if (LOG.isDebugEnabled())
+        {
+            LOG.debug("getMetadataFor({})",type);
+        }
+        EncoderMetadata metadata = metadatas.getMetadataByType(type);
+
+        if (metadata != null)
+        {
+            return metadata;
+        }
+
+        if (parentFactory != null)
+        {
+            return parentFactory.getMetadataFor(type);
+        }
+
+        return null;
+    }
+
+    public Wrapper getWrapperFor(Class<?> type)
+    {
+        synchronized (activeWrappers)
+        {
+            Wrapper wrapper = activeWrappers.get(type);
+
+            // Try parent (if needed)
+            if ((wrapper == null) && (parentFactory != null))
+            {
+                wrapper = parentFactory.getWrapperFor(type);
+            }
+
+            if (wrapper == null)
+            {
+                // Attempt to create Wrapper on demand
+                EncoderMetadata metadata = metadatas.getMetadataByType(type);
+                if (metadata == null)
+                {
+                    return null;
+                }
+                wrapper = newWrapper(metadata);
+                // track wrapper
+                activeWrappers.put(type,wrapper);
+            }
+
+            return wrapper;
+        }
+    }
+
+    @Override
+    public void init(EndpointConfig config)
+    {
+        if (LOG.isDebugEnabled())
+        {
+            LOG.debug("init({})",config);
+        }
+
+        // Instantiate all declared encoders
+        for (EncoderMetadata metadata : metadatas)
+        {
+            Wrapper wrapper = newWrapper(metadata);
+            activeWrappers.put(metadata.getObjectType(),wrapper);
+        }
+
+        // Initialize all encoders
+        for (Wrapper wrapper : activeWrappers.values())
+        {
+            wrapper.encoder.init(config);
+        }
+    }
+
+    private Wrapper newWrapper(EncoderMetadata metadata)
+    {
+        Class<? extends Encoder> encoderClass = metadata.getCoderClass();
+        try
+        {
+            Encoder encoder = encoderClass.newInstance();
+            return new Wrapper(encoder,metadata);
+        }
+        catch (InstantiationException | IllegalAccessException e)
+        {
+            throw new IllegalStateException("Unable to instantiate Encoder: " + encoderClass.getName());
+        }
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/InitException.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/InitException.java
new file mode 100644
index 0000000..cf1cf06
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/InitException.java
@@ -0,0 +1,42 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356;
+
+/**
+ * Exception during initialization of the Endpoint
+ */
+public class InitException extends IllegalStateException
+{
+    private static final long serialVersionUID = -4691138423037387558L;
+
+    public InitException(String s)
+    {
+        super(s);
+    }
+
+    public InitException(String message, Throwable cause)
+    {
+        super(message,cause);
+    }
+
+    public InitException(Throwable cause)
+    {
+        super(cause);
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JettyClientContainerProvider.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JettyClientContainerProvider.java
new file mode 100644
index 0000000..eb043bc
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JettyClientContainerProvider.java
@@ -0,0 +1,43 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356;
+
+import javax.websocket.ContainerProvider;
+import javax.websocket.WebSocketContainer;
+
+/**
+ * Client {@link ContainerProvider} implementation
+ */
+public class JettyClientContainerProvider extends ContainerProvider
+{
+    @Override
+    protected WebSocketContainer getContainer()
+    {
+        ClientContainer container = new ClientContainer();
+        try
+        {
+            container.start();
+            return container;
+        }
+        catch (Exception e)
+        {
+            throw new RuntimeException("Unable to start Client Container",e);
+        }
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JsrAsyncRemote.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JsrAsyncRemote.java
new file mode 100644
index 0000000..cfb5a1f
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JsrAsyncRemote.java
@@ -0,0 +1,197 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.concurrent.Future;
+
+import javax.websocket.EncodeException;
+import javax.websocket.Encoder;
+import javax.websocket.RemoteEndpoint;
+import javax.websocket.SendHandler;
+import javax.websocket.SendResult;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.frames.BinaryFrame;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
+import org.eclipse.jetty.websocket.common.message.MessageOutputStream;
+import org.eclipse.jetty.websocket.common.message.MessageWriter;
+import org.eclipse.jetty.websocket.common.util.TextUtil;
+import org.eclipse.jetty.websocket.jsr356.messages.SendHandlerWriteCallback;
+
+public class JsrAsyncRemote extends AbstractJsrRemote implements RemoteEndpoint.Async
+{
+    static final Logger LOG = Log.getLogger(JsrAsyncRemote.class);
+
+    protected JsrAsyncRemote(JsrSession session)
+    {
+        super(session);
+    }
+
+    @Override
+    public long getSendTimeout()
+    {
+        // TODO Auto-generated method stub
+        return 0;
+    }
+
+    @Override
+    public Future<Void> sendBinary(ByteBuffer data)
+    {
+        assertMessageNotNull(data);
+        if (LOG.isDebugEnabled())
+        {
+            LOG.debug("sendBinary({})",BufferUtil.toDetailString(data));
+        }
+        return jettyRemote.sendBytesByFuture(data);
+    }
+
+    @Override
+    public void sendBinary(ByteBuffer data, SendHandler handler)
+    {
+        assertMessageNotNull(data);
+        assertSendHandlerNotNull(handler);
+        if (LOG.isDebugEnabled())
+        {
+            LOG.debug("sendBinary({},{})",BufferUtil.toDetailString(data),handler);
+        }
+        WebSocketFrame frame = new BinaryFrame().setPayload(data).setFin(true);
+        jettyRemote.uncheckedSendFrame(frame,new SendHandlerWriteCallback(handler));
+    }
+
+    @Override
+    public Future<Void> sendObject(Object data)
+    {
+        return sendObjectViaFuture(data);
+    }
+
+    @SuppressWarnings(
+    { "rawtypes", "unchecked" })
+    @Override
+    public void sendObject(Object data, SendHandler handler)
+    {
+        assertMessageNotNull(data);
+        assertSendHandlerNotNull(handler);
+        if (LOG.isDebugEnabled())
+        {
+            LOG.debug("sendObject({},{})",data,handler);
+        }
+
+        Encoder encoder = encoders.getEncoderFor(data.getClass());
+        if (encoder == null)
+        {
+            throw new IllegalArgumentException("No encoder for type: " + data.getClass());
+        }
+
+        if (encoder instanceof Encoder.Text)
+        {
+            Encoder.Text etxt = (Encoder.Text)encoder;
+            try
+            {
+                String msg = etxt.encode(data);
+                sendText(msg,handler);
+                return;
+            }
+            catch (EncodeException e)
+            {
+                handler.onResult(new SendResult(e));
+            }
+        }
+        else if (encoder instanceof Encoder.TextStream)
+        {
+            Encoder.TextStream etxt = (Encoder.TextStream)encoder;
+            SendHandlerWriteCallback callback = new SendHandlerWriteCallback(handler);
+            try (MessageWriter writer = new MessageWriter(session))
+            {
+                writer.setCallback(callback);
+                etxt.encode(data,writer);
+                return;
+            }
+            catch (EncodeException | IOException e)
+            {
+                handler.onResult(new SendResult(e));
+            }
+        }
+        else if (encoder instanceof Encoder.Binary)
+        {
+            Encoder.Binary ebin = (Encoder.Binary)encoder;
+            try
+            {
+                ByteBuffer buf = ebin.encode(data);
+                sendBinary(buf,handler);
+                return;
+            }
+            catch (EncodeException e)
+            {
+                handler.onResult(new SendResult(e));
+            }
+        }
+        else if (encoder instanceof Encoder.BinaryStream)
+        {
+            Encoder.BinaryStream ebin = (Encoder.BinaryStream)encoder;
+            SendHandlerWriteCallback callback = new SendHandlerWriteCallback(handler);
+            try (MessageOutputStream out = new MessageOutputStream(session))
+            {
+                out.setCallback(callback);
+                ebin.encode(data,out);
+                return;
+            }
+            catch (EncodeException | IOException e)
+            {
+                handler.onResult(new SendResult(e));
+            }
+        }
+
+        throw new IllegalArgumentException("Unknown encoder type: " + encoder);
+    }
+
+    @Override
+    public Future<Void> sendText(String text)
+    {
+        assertMessageNotNull(text);
+        if (LOG.isDebugEnabled())
+        {
+            LOG.debug("sendText({})",TextUtil.hint(text));
+        }
+        return jettyRemote.sendStringByFuture(text);
+    }
+
+    @Override
+    public void sendText(String text, SendHandler handler)
+    {
+        assertMessageNotNull(text);
+        assertSendHandlerNotNull(handler);
+        if (LOG.isDebugEnabled())
+        {
+            LOG.debug("sendText({},{})",TextUtil.hint(text),handler);
+        }
+        WebSocketFrame frame = new TextFrame().setPayload(text).setFin(true);
+        jettyRemote.uncheckedSendFrame(frame,new SendHandlerWriteCallback(handler));
+    }
+
+    @Override
+    public void setSendTimeout(long timeoutmillis)
+    {
+        // TODO Auto-generated method stub
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JsrBasicRemote.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JsrBasicRemote.java
new file mode 100644
index 0000000..9548ee5
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JsrBasicRemote.java
@@ -0,0 +1,121 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.Writer;
+import java.nio.ByteBuffer;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+
+import javax.websocket.EncodeException;
+import javax.websocket.RemoteEndpoint;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.common.message.MessageOutputStream;
+import org.eclipse.jetty.websocket.common.message.MessageWriter;
+import org.eclipse.jetty.websocket.common.util.TextUtil;
+
+public class JsrBasicRemote extends AbstractJsrRemote implements RemoteEndpoint.Basic
+{
+    private static final Logger LOG = Log.getLogger(JsrBasicRemote.class);
+
+    protected JsrBasicRemote(JsrSession session)
+    {
+        super(session);
+    }
+
+    @Override
+    public OutputStream getSendStream() throws IOException
+    {
+        return new MessageOutputStream(session);
+    }
+
+    @Override
+    public Writer getSendWriter() throws IOException
+    {
+        return new MessageWriter(session);
+    }
+
+    @Override
+    public void sendBinary(ByteBuffer data) throws IOException
+    {
+        assertMessageNotNull(data);
+        if (LOG.isDebugEnabled())
+        {
+            LOG.debug("sendBinary({})",BufferUtil.toDetailString(data));
+        }
+        jettyRemote.sendBytes(data);
+    }
+
+    @Override
+    public void sendBinary(ByteBuffer partialByte, boolean isLast) throws IOException
+    {
+        assertMessageNotNull(partialByte);
+        if (LOG.isDebugEnabled())
+        {
+            LOG.debug("sendBinary({},{})",BufferUtil.toDetailString(partialByte),isLast);
+        }
+        jettyRemote.sendPartialBytes(partialByte,isLast);
+    }
+
+    @Override
+    public void sendObject(Object data) throws IOException, EncodeException
+    {
+        // TODO avoid the use of a Future
+        Future<Void> fut = sendObjectViaFuture(data);
+        try
+        {
+            fut.get(); // block till done
+        }
+        catch (ExecutionException e)
+        {
+            throw new IOException("Failed to write object",e.getCause());
+        }
+        catch (InterruptedException e)
+        {
+            throw new IOException("Failed to write object",e);
+        }
+    }
+
+    @Override
+    public void sendText(String text) throws IOException
+    {
+        assertMessageNotNull(text);
+        if (LOG.isDebugEnabled())
+        {
+            LOG.debug("sendText({})",TextUtil.hint(text));
+        }
+        jettyRemote.sendString(text);
+    }
+
+    @Override
+    public void sendText(String partialMessage, boolean isLast) throws IOException
+    {
+        assertMessageNotNull(partialMessage);
+        if (LOG.isDebugEnabled())
+        {
+            LOG.debug("sendText({},{})",TextUtil.hint(partialMessage),isLast);
+        }
+        jettyRemote.sendPartialString(partialMessage,isLast);
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JsrExtension.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JsrExtension.java
new file mode 100644
index 0000000..49814ab
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JsrExtension.java
@@ -0,0 +1,112 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import javax.websocket.Extension;
+
+import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
+import org.eclipse.jetty.websocket.api.util.QuoteUtil;
+
+public class JsrExtension implements Extension
+{
+    private static class JsrParameter implements Extension.Parameter
+    {
+        private String name;
+        private String value;
+
+        private JsrParameter(String key, String value)
+        {
+            this.name = key;
+            this.value = value;
+        }
+
+        @Override
+        public String getName()
+        {
+            return this.name;
+        }
+
+        @Override
+        public String getValue()
+        {
+            return this.value;
+        }
+    }
+
+    private final String name;
+    private List<Parameter> parameters = new ArrayList<>();
+
+    /**
+     * A configured extension
+     */
+    public JsrExtension(ExtensionConfig cfg)
+    {
+        this.name = cfg.getName();
+        if (cfg.getParameters() != null)
+        {
+            for (Map.Entry<String, String> entry : cfg.getParameters().entrySet())
+            {
+                parameters.add(new JsrParameter(entry.getKey(),entry.getValue()));
+            }
+        }
+    }
+
+    /**
+     * A potential (unconfigured) extension
+     */
+    public JsrExtension(String name)
+    {
+        this.name = name;
+    }
+
+    @Override
+    public String getName()
+    {
+        return name;
+    }
+
+    @Override
+    public List<Parameter> getParameters()
+    {
+        return parameters;
+    }
+
+    @Override
+    public String toString()
+    {
+        StringBuilder str = new StringBuilder();
+        str.append(name);
+        for (Parameter param : parameters)
+        {
+            str.append(';');
+            str.append(param.getName());
+            String value = param.getValue();
+            if (value != null)
+            {
+                str.append('=');
+                QuoteUtil.quoteIfNeeded(str,value,";=");
+            }
+        }
+        return str.toString();
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JsrExtensionConfig.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JsrExtensionConfig.java
new file mode 100644
index 0000000..55764d0
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JsrExtensionConfig.java
@@ -0,0 +1,35 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356;
+
+import javax.websocket.Extension;
+
+import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
+
+public class JsrExtensionConfig extends ExtensionConfig
+{
+    public JsrExtensionConfig(Extension ext)
+    {
+        super(ext.getName());
+        for (Extension.Parameter param : ext.getParameters())
+        {
+            this.setParameter(param.getName(),param.getValue());
+        }
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JsrHandshakeResponse.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JsrHandshakeResponse.java
new file mode 100644
index 0000000..a8192e0
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JsrHandshakeResponse.java
@@ -0,0 +1,42 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356;
+
+import java.util.List;
+import java.util.Map;
+
+import javax.websocket.HandshakeResponse;
+
+import org.eclipse.jetty.websocket.api.UpgradeResponse;
+
+public class JsrHandshakeResponse implements HandshakeResponse
+{
+    private final Map<String, List<String>> headers;
+
+    public JsrHandshakeResponse(UpgradeResponse response)
+    {
+        this.headers = response.getHeaders();
+    }
+
+    @Override
+    public Map<String, List<String>> getHeaders()
+    {
+        return this.headers;
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JsrPongMessage.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JsrPongMessage.java
new file mode 100644
index 0000000..961329a
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JsrPongMessage.java
@@ -0,0 +1,39 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356;
+
+import java.nio.ByteBuffer;
+
+import javax.websocket.PongMessage;
+
+public class JsrPongMessage implements PongMessage
+{
+    private final ByteBuffer data;
+
+    public JsrPongMessage(ByteBuffer buf)
+    {
+        this.data = buf;
+    }
+
+    @Override
+    public ByteBuffer getApplicationData()
+    {
+        return data.slice();
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JsrSession.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JsrSession.java
new file mode 100644
index 0000000..a1988cf
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JsrSession.java
@@ -0,0 +1,383 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356;
+
+import java.io.IOException;
+import java.net.URI;
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import javax.websocket.CloseReason;
+import javax.websocket.EndpointConfig;
+import javax.websocket.Extension;
+import javax.websocket.MessageHandler;
+import javax.websocket.RemoteEndpoint.Async;
+import javax.websocket.RemoteEndpoint.Basic;
+import javax.websocket.Session;
+import javax.websocket.WebSocketContainer;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.BatchMode;
+import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
+import org.eclipse.jetty.websocket.common.LogicalConnection;
+import org.eclipse.jetty.websocket.common.SessionListener;
+import org.eclipse.jetty.websocket.common.WebSocketSession;
+import org.eclipse.jetty.websocket.common.events.EventDriver;
+import org.eclipse.jetty.websocket.jsr356.endpoints.AbstractJsrEventDriver;
+import org.eclipse.jetty.websocket.jsr356.metadata.DecoderMetadata;
+import org.eclipse.jetty.websocket.jsr356.metadata.EndpointMetadata;
+import org.eclipse.jetty.websocket.jsr356.metadata.MessageHandlerMetadata;
+
+/**
+ * Session for the JSR.
+ */
+public class JsrSession extends WebSocketSession implements javax.websocket.Session, Configurable
+{
+    private static final Logger LOG = Log.getLogger(JsrSession.class);
+    private final ClientContainer container;
+    private final String id;
+    private final EndpointConfig config;
+    private final EndpointMetadata metadata;
+    private final DecoderFactory decoderFactory;
+    private final EncoderFactory encoderFactory;
+    /** Factory for MessageHandlers */
+    private final MessageHandlerFactory messageHandlerFactory;
+    /** Array of MessageHandlerWrappers, indexed by {@link MessageType#ordinal()} */
+    private final MessageHandlerWrapper wrappers[];
+    private Set<MessageHandler> messageHandlerSet;
+    private List<Extension> negotiatedExtensions;
+    private Map<String, String> pathParameters = new HashMap<>();
+    private JsrAsyncRemote asyncRemote;
+    private JsrBasicRemote basicRemote;
+
+    public JsrSession(URI requestURI, EventDriver websocket, LogicalConnection connection, ClientContainer container, String id, SessionListener... sessionListeners)
+    {
+        super(requestURI, websocket, connection, sessionListeners);
+        if (!(websocket instanceof AbstractJsrEventDriver))
+        {
+            throw new IllegalArgumentException("Cannot use, not a JSR WebSocket: " + websocket);
+        }
+        AbstractJsrEventDriver jsr = (AbstractJsrEventDriver)websocket;
+        this.config = jsr.getConfig();
+        this.metadata = jsr.getMetadata();
+        this.container = container;
+        this.id = id;
+        this.decoderFactory = new DecoderFactory(metadata.getDecoders(),container.getDecoderFactory());
+        this.encoderFactory = new EncoderFactory(metadata.getEncoders(),container.getEncoderFactory());
+        this.messageHandlerFactory = new MessageHandlerFactory();
+        this.wrappers = new MessageHandlerWrapper[MessageType.values().length];
+        this.messageHandlerSet = new HashSet<>();
+    }
+
+    @Override
+    public void addMessageHandler(MessageHandler handler) throws IllegalStateException
+    {
+        Objects.requireNonNull(handler, "MessageHandler cannot be null");
+
+        synchronized (wrappers)
+        {
+            for (MessageHandlerMetadata metadata : messageHandlerFactory.getMetadata(handler.getClass()))
+            {
+                DecoderFactory.Wrapper wrapper = decoderFactory.getWrapperFor(metadata.getMessageClass());
+                if (wrapper == null)
+                {
+                    StringBuilder err = new StringBuilder();
+                    err.append("Unable to find decoder for type <");
+                    err.append(metadata.getMessageClass().getName());
+                    err.append("> used in <");
+                    err.append(metadata.getHandlerClass().getName());
+                    err.append(">");
+                    throw new IllegalStateException(err.toString());
+                }
+
+                MessageType key = wrapper.getMetadata().getMessageType();
+                MessageHandlerWrapper other = wrappers[key.ordinal()];
+                if (other != null)
+                {
+                    StringBuilder err = new StringBuilder();
+                    err.append("Encountered duplicate MessageHandler handling message type <");
+                    err.append(wrapper.getMetadata().getObjectType().getName());
+                    err.append(">, ").append(metadata.getHandlerClass().getName());
+                    err.append("<");
+                    err.append(metadata.getMessageClass().getName());
+                    err.append("> and ");
+                    err.append(other.getMetadata().getHandlerClass().getName());
+                    err.append("<");
+                    err.append(other.getMetadata().getMessageClass().getName());
+                    err.append("> both implement this message type");
+                    throw new IllegalStateException(err.toString());
+                }
+                else
+                {
+                    MessageHandlerWrapper handlerWrapper = new MessageHandlerWrapper(handler,metadata,wrapper);
+                    wrappers[key.ordinal()] = handlerWrapper;
+                }
+            }
+
+            // Update handlerSet
+            updateMessageHandlerSet();
+        }
+    }
+
+    @Override
+    public void close(CloseReason closeReason) throws IOException
+    {
+        close(closeReason.getCloseCode().getCode(),closeReason.getReasonPhrase());
+    }
+
+    @Override
+    public Async getAsyncRemote()
+    {
+        if (asyncRemote == null)
+        {
+            asyncRemote = new JsrAsyncRemote(this);
+        }
+        return asyncRemote;
+    }
+
+    @Override
+    public Basic getBasicRemote()
+    {
+        if (basicRemote == null)
+        {
+            basicRemote = new JsrBasicRemote(this);
+        }
+        return basicRemote;
+    }
+
+    @Override
+    public WebSocketContainer getContainer()
+    {
+        return this.container;
+    }
+
+    public DecoderFactory getDecoderFactory()
+    {
+        return decoderFactory;
+    }
+
+    public EncoderFactory getEncoderFactory()
+    {
+        return encoderFactory;
+    }
+
+    public EndpointConfig getEndpointConfig()
+    {
+        return config;
+    }
+
+    public EndpointMetadata getEndpointMetadata()
+    {
+        return metadata;
+    }
+
+    @Override
+    public String getId()
+    {
+        return this.id;
+    }
+
+    @Override
+    public int getMaxBinaryMessageBufferSize()
+    {
+        return getPolicy().getMaxBinaryMessageSize();
+    }
+
+    @Override
+    public long getMaxIdleTimeout()
+    {
+        return getPolicy().getIdleTimeout();
+    }
+
+    @Override
+    public int getMaxTextMessageBufferSize()
+    {
+        return getPolicy().getMaxTextMessageSize();
+    }
+
+    public MessageHandlerFactory getMessageHandlerFactory()
+    {
+        return messageHandlerFactory;
+    }
+
+    @Override
+    public Set<MessageHandler> getMessageHandlers()
+    {
+        // Always return copy of set, as it is common to iterate and remove from the real set.
+        return new HashSet<MessageHandler>(messageHandlerSet);
+    }
+
+    public MessageHandlerWrapper getMessageHandlerWrapper(MessageType type)
+    {
+        synchronized (wrappers)
+        {
+            return wrappers[type.ordinal()];
+        }
+    }
+
+    @Override
+    public List<Extension> getNegotiatedExtensions()
+    {
+        if (negotiatedExtensions == null)
+        {
+            negotiatedExtensions = new ArrayList<Extension>();
+            for (ExtensionConfig cfg : getUpgradeResponse().getExtensions())
+            {
+                negotiatedExtensions.add(new JsrExtension(cfg));
+            }
+        }
+        return negotiatedExtensions;
+    }
+
+    @Override
+    public String getNegotiatedSubprotocol()
+    {
+        String acceptedSubProtocol = getUpgradeResponse().getAcceptedSubProtocol();
+        if (acceptedSubProtocol == null)
+        {
+            return "";
+        }
+        return acceptedSubProtocol;
+    }
+
+    @Override
+    public Set<Session> getOpenSessions()
+    {
+        return container.getOpenSessions();
+    }
+
+    @Override
+    public Map<String, String> getPathParameters()
+    {
+        return Collections.unmodifiableMap(pathParameters);
+    }
+
+    @Override
+    public String getQueryString()
+    {
+        return getUpgradeRequest().getRequestURI().getQuery();
+    }
+
+    @Override
+    public Map<String, List<String>> getRequestParameterMap()
+    {
+        return getUpgradeRequest().getParameterMap();
+    }
+
+    @Override
+    public Principal getUserPrincipal()
+    {
+        return getUpgradeRequest().getUserPrincipal();
+    }
+
+    @Override
+    public Map<String, Object> getUserProperties()
+    {
+        return config.getUserProperties();
+    }
+
+    @Override
+    public void init(EndpointConfig config)
+    {
+        // Initialize encoders
+        encoderFactory.init(config);
+        // Initialize decoders
+        decoderFactory.init(config);
+    }
+
+    @Override
+    public void removeMessageHandler(MessageHandler handler)
+    {
+        synchronized (wrappers)
+        {
+            try
+            {
+                for (MessageHandlerMetadata metadata : messageHandlerFactory.getMetadata(handler.getClass()))
+                {
+                    DecoderMetadata decoder = decoderFactory.getMetadataFor(metadata.getMessageClass());
+                    MessageType key = decoder.getMessageType();
+                    wrappers[key.ordinal()] = null;
+                }
+                updateMessageHandlerSet();
+            }
+            catch (IllegalStateException e)
+            {
+                LOG.warn("Unable to identify MessageHandler: " + handler.getClass().getName(),e);
+            }
+        }
+    }
+
+    @Override
+    public void setMaxBinaryMessageBufferSize(int length)
+    {
+        getPolicy().setMaxBinaryMessageSize(length);
+        getPolicy().setMaxBinaryMessageBufferSize(length);
+    }
+
+    @Override
+    public void setMaxIdleTimeout(long milliseconds)
+    {
+        getPolicy().setIdleTimeout(milliseconds);
+        super.setIdleTimeout(milliseconds);
+    }
+
+    @Override
+    public void setMaxTextMessageBufferSize(int length)
+    {
+        getPolicy().setMaxTextMessageSize(length);
+        getPolicy().setMaxTextMessageBufferSize(length);
+    }
+
+    public void setPathParameters(Map<String, String> pathParams)
+    {
+        this.pathParameters.clear();
+        if (pathParams != null)
+        {
+            this.pathParameters.putAll(pathParams);
+        }
+    }
+
+    private void updateMessageHandlerSet()
+    {
+        messageHandlerSet.clear();
+        for (MessageHandlerWrapper wrapper : wrappers)
+        {
+            if (wrapper == null)
+            {
+                // skip empty
+                continue;
+            }
+            messageHandlerSet.add(wrapper.getHandler());
+        }
+    }
+
+    @Override
+    public BatchMode getBatchMode()
+    {
+        // JSR 356 specification mandates default batch mode to be off.
+        return BatchMode.OFF;
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JsrSessionFactory.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JsrSessionFactory.java
new file mode 100644
index 0000000..3893a4c
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JsrSessionFactory.java
@@ -0,0 +1,59 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356;
+
+import java.net.URI;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.eclipse.jetty.websocket.common.LogicalConnection;
+import org.eclipse.jetty.websocket.common.SessionFactory;
+import org.eclipse.jetty.websocket.common.SessionListener;
+import org.eclipse.jetty.websocket.common.WebSocketSession;
+import org.eclipse.jetty.websocket.common.events.EventDriver;
+import org.eclipse.jetty.websocket.jsr356.endpoints.AbstractJsrEventDriver;
+
+public class JsrSessionFactory implements SessionFactory
+{
+    private AtomicLong idgen = new AtomicLong(0);
+    private final ClientContainer container;
+    private final SessionListener[] listeners;
+
+    public JsrSessionFactory(ClientContainer container, SessionListener... sessionListeners)
+    {
+        this.container = container;
+        this.listeners = sessionListeners;
+    }
+
+    @Override
+    public WebSocketSession createSession(URI requestURI, EventDriver websocket, LogicalConnection connection)
+    {
+        return new JsrSession(requestURI,websocket,connection,container,getNextId(),listeners);
+    }
+
+    public String getNextId()
+    {
+        return String.format("websocket-%d",idgen.incrementAndGet());
+    }
+
+    @Override
+    public boolean supports(EventDriver websocket)
+    {
+        return (websocket instanceof AbstractJsrEventDriver);
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JsrUpgradeListener.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JsrUpgradeListener.java
new file mode 100644
index 0000000..e711a3a
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JsrUpgradeListener.java
@@ -0,0 +1,79 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356;
+
+import java.net.HttpCookie;
+import java.util.List;
+import java.util.Map;
+import javax.websocket.ClientEndpointConfig.Configurator;
+
+import org.eclipse.jetty.websocket.api.UpgradeRequest;
+import org.eclipse.jetty.websocket.api.UpgradeResponse;
+import org.eclipse.jetty.websocket.client.io.UpgradeListener;
+
+public class JsrUpgradeListener implements UpgradeListener
+{
+    private Configurator configurator;
+
+    public JsrUpgradeListener(Configurator configurator)
+    {
+        this.configurator = configurator;
+    }
+
+    @Override
+    public void onHandshakeRequest(UpgradeRequest request)
+    {
+        if (configurator == null)
+        {
+            return;
+        }
+
+        Map<String, List<String>> headers = request.getHeaders();
+        configurator.beforeRequest(headers);
+
+        // Handle cookies
+        for (String name : headers.keySet())
+        {
+            if ("cookie".equalsIgnoreCase(name))
+            {
+                List<String> values = headers.get(name);
+                if (values != null)
+                {
+                    for (String cookie : values)
+                    {
+                        List<HttpCookie> cookies = HttpCookie.parse(cookie);
+                        request.getCookies().addAll(cookies);
+                    }
+                }
+            }
+        }
+    }
+
+    @Override
+    public void onHandshakeResponse(UpgradeResponse response)
+    {
+        if (configurator == null)
+        {
+            return;
+        }
+
+        JsrHandshakeResponse hr = new JsrHandshakeResponse(response);
+        configurator.afterResponse(hr);
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/MessageHandlerFactory.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/MessageHandlerFactory.java
new file mode 100644
index 0000000..f715c1b
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/MessageHandlerFactory.java
@@ -0,0 +1,100 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import javax.websocket.MessageHandler;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.common.util.ReflectUtils;
+import org.eclipse.jetty.websocket.jsr356.metadata.MessageHandlerMetadata;
+
+/**
+ * Factory for {@link MessageHandlerMetadata}
+ */
+public class MessageHandlerFactory
+{
+    private static final Logger LOG = Log.getLogger(MessageHandlerFactory.class);
+    /** Registered MessageHandlers at this level */
+    private Map<Class<? extends MessageHandler>, List<MessageHandlerMetadata>> registered;
+
+    public MessageHandlerFactory()
+    {
+        registered = new ConcurrentHashMap<>();
+    }
+
+    public List<MessageHandlerMetadata> getMetadata(Class<? extends MessageHandler> handler) throws IllegalStateException
+    {
+        if (LOG.isDebugEnabled())
+        {
+            LOG.debug("getMetadata({})",handler);
+        }
+        List<MessageHandlerMetadata> ret = registered.get(handler);
+        if (ret != null)
+        {
+            return ret;
+        }
+
+        return register(handler);
+    }
+
+    public List<MessageHandlerMetadata> register(Class<? extends MessageHandler> handler)
+    {
+        List<MessageHandlerMetadata> metadatas = new ArrayList<>();
+
+        boolean partial = false;
+
+        if (MessageHandler.Partial.class.isAssignableFrom(handler))
+        {
+            if (LOG.isDebugEnabled())
+            {
+                LOG.debug("supports Partial: {}",handler);
+            }
+            partial = true;
+            Class<?> onMessageClass = ReflectUtils.findGenericClassFor(handler,MessageHandler.Partial.class);
+            if (LOG.isDebugEnabled())
+            {
+                LOG.debug("Partial message class: {}",onMessageClass);
+            }
+            metadatas.add(new MessageHandlerMetadata(handler,onMessageClass,partial));
+        }
+
+        if (MessageHandler.Whole.class.isAssignableFrom(handler))
+        {
+            if (LOG.isDebugEnabled())
+            {
+                LOG.debug("supports Whole: {}",handler.getName());
+            }
+            partial = false;
+            Class<?> onMessageClass = ReflectUtils.findGenericClassFor(handler,MessageHandler.Whole.class);
+            if (LOG.isDebugEnabled())
+            {
+                LOG.debug("Whole message class: {}",onMessageClass);
+            }
+            metadatas.add(new MessageHandlerMetadata(handler,onMessageClass,partial));
+        }
+
+        registered.put(handler,metadatas);
+        return metadatas;
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/MessageHandlerWrapper.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/MessageHandlerWrapper.java
new file mode 100644
index 0000000..d0e0ff5
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/MessageHandlerWrapper.java
@@ -0,0 +1,82 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356;
+
+import javax.websocket.MessageHandler;
+
+import org.eclipse.jetty.websocket.jsr356.metadata.MessageHandlerMetadata;
+
+/**
+ * Expose a {@link MessageHandler} instance along with its associated {@link MessageHandlerMetadata} and {@link DecoderFactory.Wrapper}
+ */
+public class MessageHandlerWrapper
+{
+    private final MessageHandler handler;
+    private final MessageHandlerMetadata metadata;
+    private final DecoderFactory.Wrapper decoder;
+
+    public MessageHandlerWrapper(MessageHandler handler, MessageHandlerMetadata metadata, DecoderFactory.Wrapper decoder)
+    {
+        this.handler = handler;
+        this.metadata = metadata;
+        this.decoder = decoder;
+    }
+
+    public DecoderFactory.Wrapper getDecoder()
+    {
+        return decoder;
+    }
+
+    public MessageHandler getHandler()
+    {
+        return handler;
+    }
+
+    public MessageHandlerMetadata getMetadata()
+    {
+        return metadata;
+    }
+
+    public boolean isMessageType(Class<?> msgType)
+    {
+        return msgType.isAssignableFrom(metadata.getMessageClass());
+    }
+
+    /**
+     * Flag for a onMessage() that wants partial messages.
+     * <p>
+     * This indicates the use of MessageHandler.{@link Partial}.
+     * 
+     * @return true for use of MessageHandler.{@link Partial}, false for use of MessageHandler.{@link Whole}
+     */
+    public boolean wantsPartialMessages()
+    {
+        return metadata.isPartialSupported();
+    }
+
+    /**
+     * Flag for a onMessage() method that wants MessageHandler.{@link Whole} with a Decoder that is based on {@link TextStream} or {@link BinaryStream}
+     * 
+     * @return true for Streaming based Decoder, false for normal decoder for whole messages.
+     */
+    public boolean wantsStreams()
+    {
+        return decoder.getMetadata().isStreamed();
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/MessageType.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/MessageType.java
new file mode 100644
index 0000000..3a3ff1b
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/MessageType.java
@@ -0,0 +1,31 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356;
+
+/**
+ * Basic Message Type enum.
+ * <p>
+ * The list of options mirrors the registration limits for "websocket message type" defined in JSR-356 / PFD1 section 2.1.3 "Receiving Messages".
+ */
+public enum MessageType
+{
+    TEXT,
+    BINARY,
+    PONG;
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/AnnotatedEndpointMetadata.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/AnnotatedEndpointMetadata.java
new file mode 100644
index 0000000..253f88c
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/AnnotatedEndpointMetadata.java
@@ -0,0 +1,134 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.annotations;
+
+import java.lang.annotation.Annotation;
+import java.util.LinkedList;
+
+import javax.websocket.EndpointConfig;
+
+import org.eclipse.jetty.websocket.jsr356.metadata.DecoderMetadata;
+import org.eclipse.jetty.websocket.jsr356.metadata.DecoderMetadataSet;
+import org.eclipse.jetty.websocket.jsr356.metadata.EncoderMetadataSet;
+import org.eclipse.jetty.websocket.jsr356.metadata.EndpointMetadata;
+
+/**
+ * Static reference to a specific annotated classes metadata.
+ * 
+ * @param <T>
+ *            the annotation this metadata is based off of
+ */
+public abstract class AnnotatedEndpointMetadata<T extends Annotation, C extends EndpointConfig> implements EndpointMetadata
+{
+    /**
+     * Callable for @{@link OnOpen} annotation.
+     */
+    public OnOpenCallable onOpen;
+
+    /**
+     * Callable for @{@link OnClose} annotation
+     */
+    public OnCloseCallable onClose;
+
+    /**
+     * Callable for @{@link OnError} annotation
+     */
+    public OnErrorCallable onError;
+
+    /**
+     * Callable for @{@link OnMessage} annotation dealing with Text Message Format
+     */
+    public OnMessageTextCallable onText;
+
+    /**
+     * Callable for @{@link OnMessage} annotation dealing with Text Streaming Message Format
+     */
+    public OnMessageTextStreamCallable onTextStream;
+
+    /**
+     * Callable for @{@link OnMessage} annotation dealing with Binary Message Format
+     */
+    public OnMessageBinaryCallable onBinary;
+
+    /**
+     * Callable for @{@link OnMessage} annotation dealing with Binary Streaming Message Format
+     */
+    public OnMessageBinaryStreamCallable onBinaryStream;
+
+    /**
+     * Callable for @{@link OnMessage} annotation dealing with Pong Message Format
+     */
+    public OnMessagePongCallable onPong;
+
+    private final Class<?> endpointClass;
+    private DecoderMetadataSet decoders;
+    private EncoderMetadataSet encoders;
+
+    protected AnnotatedEndpointMetadata(Class<?> endpointClass)
+    {
+        this.endpointClass = endpointClass;
+        this.decoders = new DecoderMetadataSet();
+        this.encoders = new EncoderMetadataSet();
+    }
+
+    public void customizeParamsOnClose(LinkedList<IJsrParamId> params)
+    {
+        /* do nothing */
+    }
+
+    public void customizeParamsOnError(LinkedList<IJsrParamId> params)
+    {
+        /* do nothing */
+    }
+
+    public void customizeParamsOnMessage(LinkedList<IJsrParamId> params)
+    {
+        for (DecoderMetadata metadata : decoders)
+        {
+            params.add(new JsrParamIdDecoder(metadata));
+        }
+    }
+
+    public void customizeParamsOnOpen(LinkedList<IJsrParamId> params)
+    {
+        /* do nothing */
+    }
+
+    public abstract T getAnnotation();
+
+    public abstract C getConfig();
+
+    @Override
+    public DecoderMetadataSet getDecoders()
+    {
+        return decoders;
+    }
+
+    @Override
+    public EncoderMetadataSet getEncoders()
+    {
+        return encoders;
+    }
+
+    @Override
+    public Class<?> getEndpointClass()
+    {
+        return endpointClass;
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/AnnotatedEndpointScanner.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/AnnotatedEndpointScanner.java
new file mode 100644
index 0000000..f710ed2
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/AnnotatedEndpointScanner.java
@@ -0,0 +1,213 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.annotations;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.util.LinkedList;
+import java.util.List;
+import javax.websocket.EndpointConfig;
+import javax.websocket.OnClose;
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.common.events.annotated.AbstractMethodAnnotationScanner;
+import org.eclipse.jetty.websocket.common.events.annotated.InvalidSignatureException;
+import org.eclipse.jetty.websocket.common.util.ReflectUtils;
+
+public class AnnotatedEndpointScanner<T extends Annotation, C extends EndpointConfig> extends AbstractMethodAnnotationScanner<AnnotatedEndpointMetadata<T, C>>
+{
+    private static final Logger LOG = Log.getLogger(AnnotatedEndpointScanner.class);
+
+    private final LinkedList<IJsrParamId> paramsOnOpen;
+    private final LinkedList<IJsrParamId> paramsOnClose;
+    private final LinkedList<IJsrParamId> paramsOnError;
+    private final LinkedList<IJsrParamId> paramsOnMessage;
+    private final AnnotatedEndpointMetadata<T, C> metadata;
+
+    public AnnotatedEndpointScanner(AnnotatedEndpointMetadata<T, C> metadata)
+    {
+        this.metadata = metadata;
+
+        paramsOnOpen = new LinkedList<>();
+        paramsOnClose = new LinkedList<>();
+        paramsOnError = new LinkedList<>();
+        paramsOnMessage = new LinkedList<>();
+
+        metadata.customizeParamsOnOpen(paramsOnOpen);
+        paramsOnOpen.add(JsrParamIdOnOpen.INSTANCE);
+
+        metadata.customizeParamsOnClose(paramsOnClose);
+        paramsOnClose.add(JsrParamIdOnClose.INSTANCE);
+
+        metadata.customizeParamsOnError(paramsOnError);
+        paramsOnError.add(JsrParamIdOnError.INSTANCE);
+
+        metadata.customizeParamsOnMessage(paramsOnMessage);
+        paramsOnMessage.add(JsrParamIdText.INSTANCE);
+        paramsOnMessage.add(JsrParamIdBinary.INSTANCE);
+        paramsOnMessage.add(JsrParamIdPong.INSTANCE);
+    }
+
+    private void assertNotDuplicate(JsrCallable callable, Class<? extends Annotation> methodAnnotationClass, Class<?> pojo, Method method)
+    {
+        if (callable != null)
+        {
+            // Duplicate annotation detected
+            StringBuilder err = new StringBuilder();
+            err.append("Encountered duplicate method annotations @");
+            err.append(methodAnnotationClass.getSimpleName());
+            err.append(" on ");
+            err.append(ReflectUtils.toString(pojo,callable.getMethod()));
+            err.append(" and ");
+            err.append(ReflectUtils.toString(pojo,method));
+
+            throw new InvalidSignatureException(err.toString());
+        }
+    }
+
+    @Override
+    public void onMethodAnnotation(AnnotatedEndpointMetadata<T, C> metadata, Class<?> pojo, Method method, Annotation annotation)
+    {
+        if (LOG.isDebugEnabled())
+        {
+            LOG.debug("onMethodAnnotation({}, {}, {}, {})",metadata,pojo,method,annotation);
+        }
+
+        if (isAnnotation(annotation,OnOpen.class))
+        {
+            assertIsPublicNonStatic(method);
+            assertIsReturn(method,Void.TYPE);
+            assertNotDuplicate(metadata.onOpen,OnOpen.class,pojo,method);
+            OnOpenCallable onopen = new OnOpenCallable(pojo,method);
+            visitMethod(onopen,pojo,method,paramsOnOpen,OnOpen.class);
+            metadata.onOpen = onopen;
+            return;
+        }
+
+        if (isAnnotation(annotation,OnClose.class))
+        {
+            assertIsPublicNonStatic(method);
+            assertIsReturn(method,Void.TYPE);
+            assertNotDuplicate(metadata.onClose,OnClose.class,pojo,method);
+            OnCloseCallable onclose = new OnCloseCallable(pojo,method);
+            visitMethod(onclose,pojo,method,paramsOnClose,OnClose.class);
+            metadata.onClose = onclose;
+            return;
+        }
+
+        if (isAnnotation(annotation,OnError.class))
+        {
+            assertIsPublicNonStatic(method);
+            assertIsReturn(method,Void.TYPE);
+            assertNotDuplicate(metadata.onError,OnError.class,pojo,method);
+            OnErrorCallable onerror = new OnErrorCallable(pojo,method);
+            visitMethod(onerror,pojo,method,paramsOnError,OnError.class);
+            metadata.onError = onerror;
+            return;
+        }
+
+        if (isAnnotation(annotation,OnMessage.class))
+        {
+            assertIsPublicNonStatic(method);
+            // assertIsReturn(method,Void.TYPE); // no validation, it can be any return type
+            OnMessageCallable onmessage = new OnMessageCallable(pojo,method);
+            visitMethod(onmessage,pojo,method,paramsOnMessage,OnMessage.class);
+
+            Param param = onmessage.getMessageObjectParam();
+            switch (param.role)
+            {
+                case MESSAGE_BINARY:
+                    metadata.onBinary = new OnMessageBinaryCallable(onmessage);
+                    break;
+                case MESSAGE_BINARY_STREAM:
+                    metadata.onBinaryStream = new OnMessageBinaryStreamCallable(onmessage);
+                    break;
+                case MESSAGE_TEXT:
+                    metadata.onText = new OnMessageTextCallable(onmessage);
+                    break;
+                case MESSAGE_TEXT_STREAM:
+                    metadata.onTextStream = new OnMessageTextStreamCallable(onmessage);
+                    break;
+                case MESSAGE_PONG:
+                    metadata.onPong = new OnMessagePongCallable(onmessage);
+                    break;
+                default:
+                    StringBuilder err = new StringBuilder();
+                    err.append("An unrecognized message type <");
+                    err.append(param.type);
+                    err.append(">: does not meet specified type categories of [TEXT, BINARY, DECODER, or PONG]");
+                    throw new InvalidSignatureException(err.toString());
+            }
+        }
+    }
+
+    public AnnotatedEndpointMetadata<T, C> scan()
+    {
+        scanMethodAnnotations(metadata,metadata.getEndpointClass());
+        return metadata;
+    }
+
+    private void visitMethod(JsrCallable callable, Class<?> pojo, Method method, LinkedList<IJsrParamId> paramIds,
+            Class<? extends Annotation> methodAnnotationClass)
+    {
+        // Identify all of the parameters
+        for (Param param : callable.getParams())
+        {
+            if (!visitParam(callable,param,paramIds))
+            {
+                StringBuilder err = new StringBuilder();
+                err.append("Encountered unknown parameter type <");
+                err.append(param.type.getName());
+                err.append("> on @");
+                err.append(methodAnnotationClass.getSimpleName());
+                err.append(" annotated method: ");
+                err.append(ReflectUtils.toString(pojo,method));
+
+                throw new InvalidSignatureException(err.toString());
+            }
+        }
+    }
+
+    private boolean visitParam(JsrCallable callable, Param param, List<IJsrParamId> paramIds)
+    {
+        for (IJsrParamId paramId : paramIds)
+        {
+            if (LOG.isDebugEnabled())
+            {
+                LOG.debug("{}.process()",paramId);
+            }
+            if (paramId.process(param,callable))
+            {
+                // Successfully identified
+                if (LOG.isDebugEnabled())
+                {
+                    LOG.debug("Identified: {}",param);
+                }
+                return true;
+            }
+        }
+
+        // Failed identification as a known parameter
+        return false;
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/IJsrMethod.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/IJsrMethod.java
new file mode 100644
index 0000000..2e03113
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/IJsrMethod.java
@@ -0,0 +1,83 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.annotations;
+
+import java.lang.reflect.Method;
+
+import javax.websocket.Decoder;
+
+import org.eclipse.jetty.websocket.jsr356.MessageType;
+
+public interface IJsrMethod
+{
+    /**
+     * Indicate that partial message support is desired
+     */
+    void enablePartialMessageSupport();
+
+    /**
+     * Get the fully qualifed method name {classname}.{methodname}({params}) suitable for using in error messages.
+     * 
+     * @return the fully qualified method name for end users
+     */
+    String getFullyQualifiedMethodName();
+
+    /**
+     * Get the Decoder to use for message decoding
+     * 
+     * @return the decoder class to use for message decoding
+     */
+    Class<? extends Decoder> getMessageDecoder();
+
+    /**
+     * The type of message this method can handle
+     * 
+     * @return the message type if @{@link OnMessage} annotated, null if unknown/unspecified
+     */
+    MessageType getMessageType();
+
+    /**
+     * The reflected method
+     * 
+     * @return the method itself
+     */
+    Method getMethod();
+
+    /**
+     * Indicator that partial message support is enabled
+     * 
+     * @return true if enabled
+     */
+    boolean isPartialMessageSupportEnabled();
+
+    /**
+     * The message decoder class to use.
+     * 
+     * @param decoderClass
+     */
+    void setMessageDecoder(Class<? extends Decoder> decoderClass);
+
+    /**
+     * The type of message this method can handle
+     * 
+     * @param type
+     *            the type of message
+     */
+    void setMessageType(MessageType type);
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/IJsrParamId.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/IJsrParamId.java
new file mode 100644
index 0000000..48cea50
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/IJsrParamId.java
@@ -0,0 +1,43 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.annotations;
+
+import org.eclipse.jetty.websocket.common.events.annotated.InvalidSignatureException;
+
+/**
+ * JSR-356 Parameter Identification processing.
+ */
+public interface IJsrParamId
+{
+    /**
+     * Process the potential parameter.
+     * <p>
+     * If known to be a valid parameter, bind a role to it.
+     * 
+     * @param param
+     *            the parameter being processed
+     * @param callable
+     *            the callable this param belongs to (used to obtain extra state about the callable that might impact decision making)
+     * 
+     * @return true if processed, false if not processed
+     * @throws InvalidSignatureException
+     *             if a violation of the signature rules occurred
+     */
+    boolean process(Param param, JsrCallable callable) throws InvalidSignatureException;
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrCallable.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrCallable.java
new file mode 100644
index 0000000..c22af6d
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrCallable.java
@@ -0,0 +1,175 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.annotations;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.util.Map;
+
+import javax.websocket.DecodeException;
+import javax.websocket.Decoder;
+
+import org.eclipse.jetty.websocket.api.InvalidWebSocketException;
+import org.eclipse.jetty.websocket.common.events.annotated.CallableMethod;
+import org.eclipse.jetty.websocket.common.events.annotated.InvalidSignatureException;
+import org.eclipse.jetty.websocket.common.util.ReflectUtils;
+import org.eclipse.jetty.websocket.jsr356.JsrSession;
+import org.eclipse.jetty.websocket.jsr356.annotations.Param.Role;
+
+public abstract class JsrCallable extends CallableMethod
+{
+    protected final Param[] params;
+    protected final Object[] args;
+    protected int idxSession = -1;
+    protected int idxConfig = -1;
+
+    public JsrCallable(Class<?> pojo, Method method)
+    {
+        super(pojo,method);
+
+        Class<?> ptypes[] = method.getParameterTypes();
+        Annotation pannos[][] = method.getParameterAnnotations();
+        int len = ptypes.length;
+        params = new Param[len];
+        for (int i = 0; i < len; i++)
+        {
+            params[i] = new Param(i,ptypes[i],pannos[i]);
+        }
+
+        args = new Object[len];
+    }
+
+    /**
+     * Copy Constructor
+     */
+    public JsrCallable(JsrCallable copy)
+    {
+        this(copy.getPojo(),copy.getMethod());
+        this.idxSession = copy.idxSession;
+        this.idxConfig = copy.idxConfig;
+        System.arraycopy(copy.params,0,this.params,0,params.length);
+        System.arraycopy(copy.args,0,this.args,0,args.length);
+    }
+
+    protected void assertRoleRequired(int index, String description)
+    {
+        if (index < 0)
+        {
+            StringBuilder err = new StringBuilder();
+            err.append("Unable to find parameter with role [");
+            err.append(description).append("] in method: ");
+            err.append(ReflectUtils.toString(pojo,method));
+            throw new InvalidSignatureException(err.toString());
+        }
+    }
+
+    /**
+     * Search the list of parameters for first one matching the role specified.
+     * 
+     * @param role
+     *            the role to look for
+     * @return the index for the role specified (or -1 if not found)
+     */
+    protected int findIndexForRole(Role role)
+    {
+        Param param = findParamForRole(role);
+        if (param != null)
+        {
+            return param.index;
+        }
+        return -1;
+    }
+
+    /**
+     * Find first param for specified role.
+     * 
+     * @param role
+     *            the role specified
+     * @return the param (or null if not found)
+     */
+    protected Param findParamForRole(Role role)
+    {
+        for (Param param : params)
+        {
+            if (param.role == role)
+            {
+                return param;
+            }
+        }
+        return null;
+    }
+
+    public Param[] getParams()
+    {
+        return params;
+    }
+
+    public void init(JsrSession session)
+    {
+        // Default for the session.
+        // Session is an optional parameter (always)
+        idxSession = findIndexForRole(Param.Role.SESSION);
+        if (idxSession >= 0)
+        {
+            args[idxSession] = session;
+        }
+
+        // Optional EndpointConfig
+        idxConfig = findIndexForRole(Param.Role.ENDPOINT_CONFIG);
+        if (idxConfig >= 0)
+        {
+            args[idxConfig] = session.getEndpointConfig();
+        }
+
+        // Default for the path parameters
+        // PathParam's are optional parameters (always)
+        Map<String, String> pathParams = session.getPathParameters();
+        if ((pathParams != null) && (pathParams.size() > 0))
+        {
+            for (Param param : params)
+            {
+                if (param.role == Role.PATH_PARAM)
+                {
+                    int idx = param.index;
+                    String rawvalue = pathParams.get(param.getPathParamName());
+
+                    Decoder decoder = session.getDecoderFactory().getDecoderFor(param.type);
+                    if (decoder instanceof Decoder.Text<?>)
+                    {
+                        Decoder.Text<?> textDecoder = (Decoder.Text<?>)decoder;
+                        try
+                        {
+                            args[idx] = textDecoder.decode(rawvalue);
+                        }
+                        catch (DecodeException e)
+                        {
+                            session.notifyError(e);
+                        }
+                    }
+                    else
+                    {
+                        throw new InvalidWebSocketException("PathParam decoders must use Decoder.Text");
+                    }
+                }
+            }
+        }
+    }
+
+    public abstract void setDecoderClass(Class<? extends Decoder> decoderClass);
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrEvents.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrEvents.java
new file mode 100644
index 0000000..745daab
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrEvents.java
@@ -0,0 +1,302 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.annotations;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.lang.annotation.Annotation;
+import java.nio.ByteBuffer;
+import java.util.Map;
+import javax.websocket.CloseReason;
+import javax.websocket.DecodeException;
+import javax.websocket.EndpointConfig;
+import javax.websocket.RemoteEndpoint;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.jsr356.JsrSession;
+
+/**
+ * The live event methods found for a specific Annotated Endpoint
+ */
+public class JsrEvents<T extends Annotation, C extends EndpointConfig>
+{
+    private static final Logger LOG = Log.getLogger(JsrEvents.class);
+    private final AnnotatedEndpointMetadata<T, C> metadata;
+
+    /**
+     * Callable for @{@link OnOpen} annotation.
+     */
+    private final OnOpenCallable onOpen;
+
+    /**
+     * Callable for @{@link OnClose} annotation
+     */
+    private final OnCloseCallable onClose;
+
+    /**
+     * Callable for @{@link OnError} annotation
+     */
+    private final OnErrorCallable onError;
+
+    /**
+     * Callable for @{@link OnMessage} annotation dealing with Text Message Format
+     */
+    private final OnMessageTextCallable onText;
+
+    /**
+     * Callable for @{@link OnMessage} annotation dealing with Text Streaming Message Format
+     */
+    private final OnMessageTextStreamCallable onTextStream;
+
+    /**
+     * Callable for @{@link OnMessage} annotation dealing with Binary Message Format
+     */
+    private final OnMessageBinaryCallable onBinary;
+
+    /**
+     * Callable for @{@link OnMessage} annotation dealing with Binary Streaming Message Format
+     */
+    private final OnMessageBinaryStreamCallable onBinaryStream;
+
+    /**
+     * Callable for @{@link OnMessage} annotation dealing with Pong Message Format
+     */
+    private OnMessagePongCallable onPong;
+
+    /**
+     * The Request Parameters (from resolved javax.websocket.server.PathParam entries)
+     */
+    private Map<String, String> pathParameters;
+
+    public JsrEvents(AnnotatedEndpointMetadata<T, C> metadata)
+    {
+        this.metadata = metadata;
+        this.onOpen = (metadata.onOpen == null)?null:new OnOpenCallable(metadata.onOpen);
+        this.onClose = (metadata.onClose == null)?null:new OnCloseCallable(metadata.onClose);
+        this.onError = (metadata.onError == null)?null:new OnErrorCallable(metadata.onError);
+        this.onBinary = (metadata.onBinary == null)?null:new OnMessageBinaryCallable(metadata.onBinary);
+        this.onBinaryStream = (metadata.onBinaryStream == null)?null:new OnMessageBinaryStreamCallable(metadata.onBinaryStream);
+        this.onText = (metadata.onText == null)?null:new OnMessageTextCallable(metadata.onText);
+        this.onTextStream = (metadata.onTextStream == null)?null:new OnMessageTextStreamCallable(metadata.onTextStream);
+        this.onPong = (metadata.onPong == null)?null:new OnMessagePongCallable(metadata.onPong);
+    }
+
+    public void callBinary(RemoteEndpoint.Async endpoint, Object websocket, ByteBuffer buf, boolean fin) throws DecodeException
+    {
+        if (onBinary == null)
+        {
+            return;
+        }
+
+        Object ret = onBinary.call(websocket,buf,fin);
+        if (ret != null)
+        {
+            if (LOG.isDebugEnabled())
+            {
+                LOG.debug("returning: {}",ret);
+            }
+            endpoint.sendObject(ret);
+        }
+    }
+
+    public void callBinaryStream(RemoteEndpoint.Async endpoint, Object websocket, InputStream stream) throws DecodeException, IOException
+    {
+        if (onBinaryStream == null)
+        {
+            return;
+        }
+
+        Object ret = onBinaryStream.call(websocket,stream);
+        if (ret != null)
+        {
+            if (LOG.isDebugEnabled())
+            {
+                LOG.debug("returning: {}",ret);
+            }
+            endpoint.sendObject(ret);
+        }
+    }
+
+    public void callClose(Object websocket, CloseReason close)
+    {
+        if (onClose == null)
+        {
+            return;
+        }
+        onClose.call(websocket,close);
+    }
+
+    public void callError(Object websocket, Throwable cause)
+    {
+        if (onError == null)
+        {
+            return;
+        }
+        onError.call(websocket,cause);
+    }
+
+    public void callOpen(Object websocket, EndpointConfig config)
+    {
+        if (onOpen == null)
+        {
+            return;
+        }
+        onOpen.call(websocket,config);
+    }
+
+    public void callPong(RemoteEndpoint.Async endpoint, Object websocket, ByteBuffer pong)
+    {
+        if (onPong == null)
+        {
+            return;
+        }
+
+        Object ret = onPong.call(websocket,pong);
+        if (ret != null)
+        {
+            if (LOG.isDebugEnabled())
+            {
+                LOG.debug("returning: {}",ret);
+            }
+            endpoint.sendObject(ret);
+        }
+    }
+
+    public void callText(RemoteEndpoint.Async endpoint, Object websocket, String text, boolean fin) throws DecodeException
+    {
+        if (onText == null)
+        {
+            return;
+        }
+        Object ret = onText.call(websocket,text,fin);
+        if (ret != null)
+        {
+            if (LOG.isDebugEnabled())
+            {
+                LOG.debug("returning: {}",ret);
+            }
+            endpoint.sendObject(ret);
+        }
+    }
+
+    public void callTextStream(RemoteEndpoint.Async endpoint, Object websocket, Reader reader) throws DecodeException, IOException
+    {
+        if (onTextStream == null)
+        {
+            return;
+        }
+        Object ret = onTextStream.call(websocket,reader);
+        if (ret != null)
+        {
+            if (LOG.isDebugEnabled())
+            {
+                LOG.debug("returning: {}",ret);
+            }
+            endpoint.sendObject(ret);
+        }
+    }
+
+    public AnnotatedEndpointMetadata<T, C> getMetadata()
+    {
+        return metadata;
+    }
+
+    public boolean hasBinary()
+    {
+        return (onBinary != null);
+    }
+
+    public boolean hasBinaryStream()
+    {
+        return (onBinaryStream != null);
+    }
+
+    public boolean hasText()
+    {
+        return (onText != null);
+    }
+
+    public boolean hasTextStream()
+    {
+        return (onTextStream != null);
+    }
+
+    public void init(JsrSession session)
+    {
+        session.setPathParameters(pathParameters);
+
+        if (onOpen != null)
+        {
+            onOpen.init(session);
+        }
+        if (onClose != null)
+        {
+            onClose.init(session);
+        }
+        if (onError != null)
+        {
+            onError.init(session);
+        }
+        if (onText != null)
+        {
+            onText.init(session);
+        }
+        if (onTextStream != null)
+        {
+            onTextStream.init(session);
+        }
+        if (onBinary != null)
+        {
+            onBinary.init(session);
+        }
+        if (onBinaryStream != null)
+        {
+            onBinaryStream.init(session);
+        }
+        if (onPong != null)
+        {
+            onPong.init(session);
+        }
+    }
+
+    public boolean isBinaryPartialSupported()
+    {
+        if (onBinary == null)
+        {
+            return false;
+        }
+        return onBinary.isPartialMessageSupported();
+    }
+
+    public boolean isTextPartialSupported()
+    {
+        if (onText == null)
+        {
+            return false;
+        }
+        return onText.isPartialMessageSupported();
+    }
+
+    public void setPathParameters(Map<String, String> pathParameters)
+    {
+        this.pathParameters = pathParameters;
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdBase.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdBase.java
new file mode 100644
index 0000000..e727f42
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdBase.java
@@ -0,0 +1,51 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.annotations;
+
+import javax.websocket.EndpointConfig;
+import javax.websocket.Session;
+
+import org.eclipse.jetty.websocket.common.events.annotated.InvalidSignatureException;
+import org.eclipse.jetty.websocket.jsr356.annotations.Param.Role;
+
+/**
+ * Common base for Parameter Identification in JSR Callable methods
+ */
+public abstract class JsrParamIdBase implements IJsrParamId
+{
+    @Override
+    public boolean process(Param param, JsrCallable callable) throws InvalidSignatureException
+    {
+        // Session parameter (optional)
+        if (param.type.isAssignableFrom(Session.class))
+        {
+            param.bind(Role.SESSION);
+            return true;
+        }
+
+        // Endpoint Config (optional)
+        if (param.type.isAssignableFrom(EndpointConfig.class))
+        {
+            param.bind(Role.ENDPOINT_CONFIG);
+            return true;
+        }
+
+        return false;
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdBinary.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdBinary.java
new file mode 100644
index 0000000..42c7597
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdBinary.java
@@ -0,0 +1,71 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.annotations;
+
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.websocket.common.events.annotated.InvalidSignatureException;
+import org.eclipse.jetty.websocket.jsr356.annotations.Param.Role;
+import org.eclipse.jetty.websocket.jsr356.decoders.ByteArrayDecoder;
+import org.eclipse.jetty.websocket.jsr356.decoders.ByteBufferDecoder;
+import org.eclipse.jetty.websocket.jsr356.decoders.InputStreamDecoder;
+
+/**
+ * Param handling for static Binary @{@link OnMessage} parameters.
+ */
+public class JsrParamIdBinary extends JsrParamIdOnMessage implements IJsrParamId
+{
+    public static final IJsrParamId INSTANCE = new JsrParamIdBinary();
+
+    @Override
+    public boolean process(Param param, JsrCallable callable) throws InvalidSignatureException
+    {
+        if (super.process(param,callable))
+        {
+            // Found common roles
+            return true;
+        }
+
+        if (param.type.isAssignableFrom(ByteBuffer.class))
+        {
+            param.bind(Role.MESSAGE_BINARY);
+            callable.setDecoderClass(ByteBufferDecoder.class);
+            return true;
+        }
+
+        if (param.type.isAssignableFrom(byte[].class))
+        {
+            param.bind(Role.MESSAGE_BINARY);
+            callable.setDecoderClass(ByteArrayDecoder.class);
+            return true;
+        }
+
+        // Streaming
+        if (param.type.isAssignableFrom(InputStream.class))
+        {
+            assertPartialMessageSupportDisabled(param,callable);
+            param.bind(Role.MESSAGE_BINARY_STREAM);
+            callable.setDecoderClass(InputStreamDecoder.class);
+            return true;
+        }
+
+        return false;
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdDecoder.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdDecoder.java
new file mode 100644
index 0000000..81d237f
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdDecoder.java
@@ -0,0 +1,75 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.annotations;
+
+import org.eclipse.jetty.websocket.common.events.annotated.InvalidSignatureException;
+import org.eclipse.jetty.websocket.jsr356.annotations.Param.Role;
+import org.eclipse.jetty.websocket.jsr356.metadata.DecoderMetadata;
+
+/**
+ * Param handling for Text or Binary @{@link OnMessage} parameters declared as {@link Decoder}s
+ */
+public class JsrParamIdDecoder extends JsrParamIdOnMessage implements IJsrParamId
+{
+    private final DecoderMetadata metadata;
+
+    public JsrParamIdDecoder(DecoderMetadata metadata)
+    {
+        this.metadata = metadata;
+    }
+
+    @Override
+    public boolean process(Param param, JsrCallable callable) throws InvalidSignatureException
+    {
+        if (param.type.isAssignableFrom(metadata.getObjectType()))
+        {
+            assertPartialMessageSupportDisabled(param,callable);
+
+            switch (metadata.getMessageType())
+            {
+                case TEXT:
+                    if (metadata.isStreamed())
+                    {
+                        param.bind(Role.MESSAGE_TEXT_STREAM);
+                    }
+                    else
+                    {
+                        param.bind(Role.MESSAGE_TEXT);
+                    }
+                    break;
+                case BINARY:
+                    if (metadata.isStreamed())
+                    {
+                        param.bind(Role.MESSAGE_BINARY_STREAM);
+                    }
+                    else
+                    {
+                        param.bind(Role.MESSAGE_BINARY);
+                    }
+                    break;
+                case PONG:
+                    param.bind(Role.MESSAGE_PONG);
+                    break;
+            }
+            callable.setDecoderClass(metadata.getCoderClass());
+            return true;
+        }
+        return false;
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdOnClose.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdOnClose.java
new file mode 100644
index 0000000..e52e8d1
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdOnClose.java
@@ -0,0 +1,49 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.annotations;
+
+import javax.websocket.CloseReason;
+
+import org.eclipse.jetty.websocket.common.events.annotated.InvalidSignatureException;
+import org.eclipse.jetty.websocket.jsr356.annotations.Param.Role;
+
+/**
+ * Param handling for @{@link OnClose} parameters.
+ */
+public class JsrParamIdOnClose extends JsrParamIdBase implements IJsrParamId
+{
+    public static final IJsrParamId INSTANCE = new JsrParamIdOnClose();
+
+    @Override
+    public boolean process(Param param, JsrCallable callable) throws InvalidSignatureException
+    {
+        if (super.process(param,callable))
+        {
+            // Found common roles
+            return true;
+        }
+
+        if (param.type.isAssignableFrom(CloseReason.class))
+        {
+            param.bind(Role.CLOSE_REASON);
+            return true;
+        }
+        return false;
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdOnError.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdOnError.java
new file mode 100644
index 0000000..db9d200
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdOnError.java
@@ -0,0 +1,47 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.annotations;
+
+import org.eclipse.jetty.websocket.common.events.annotated.InvalidSignatureException;
+import org.eclipse.jetty.websocket.jsr356.annotations.Param.Role;
+
+/**
+ * Param handling for @{@link OnError} parameters.
+ */
+public class JsrParamIdOnError extends JsrParamIdBase implements IJsrParamId
+{
+    public static final IJsrParamId INSTANCE = new JsrParamIdOnError();
+
+    @Override
+    public boolean process(Param param, JsrCallable callable) throws InvalidSignatureException
+    {
+        if (super.process(param,callable))
+        {
+            // Found common roles
+            return true;
+        }
+
+        if (param.type.isAssignableFrom(Throwable.class))
+        {
+            param.bind(Role.ERROR_CAUSE);
+            return true;
+        }
+        return false;
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdOnMessage.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdOnMessage.java
new file mode 100644
index 0000000..852c55d
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdOnMessage.java
@@ -0,0 +1,40 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.annotations;
+
+import org.eclipse.jetty.websocket.common.events.annotated.InvalidSignatureException;
+
+public abstract class JsrParamIdOnMessage extends JsrParamIdBase implements IJsrParamId
+{
+    protected void assertPartialMessageSupportDisabled(Param param, JsrCallable callable)
+    {
+        if (callable instanceof OnMessageCallable)
+        {
+            OnMessageCallable onmessage = (OnMessageCallable)callable;
+            if (onmessage.isPartialMessageSupported())
+            {
+                StringBuilder err = new StringBuilder();
+                err.append("Unable to support parameter type <");
+                err.append(param.type.getName()).append("> in conjunction with the partial message indicator boolean.");
+                err.append(" Only type <String> is supported with partial message boolean indicator.");
+                throw new InvalidSignatureException(err.toString());
+            }
+        }
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdOnOpen.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdOnOpen.java
new file mode 100644
index 0000000..d0b096f
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdOnOpen.java
@@ -0,0 +1,49 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.annotations;
+
+import javax.websocket.EndpointConfig;
+
+import org.eclipse.jetty.websocket.common.events.annotated.InvalidSignatureException;
+import org.eclipse.jetty.websocket.jsr356.annotations.Param.Role;
+
+/**
+ * Param handling for @{@link OnOpen} parameters.
+ */
+public class JsrParamIdOnOpen extends JsrParamIdBase implements IJsrParamId
+{
+    public static final IJsrParamId INSTANCE = new JsrParamIdOnOpen();
+
+    @Override
+    public boolean process(Param param, JsrCallable callable) throws InvalidSignatureException
+    {
+        if (super.process(param,callable))
+        {
+            // Found common roles
+            return true;
+        }
+
+        if (param.type.isAssignableFrom(EndpointConfig.class))
+        {
+            param.bind(Role.ENDPOINT_CONFIG);
+            return true;
+        }
+        return false;
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdPong.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdPong.java
new file mode 100644
index 0000000..b176064
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdPong.java
@@ -0,0 +1,49 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.annotations;
+
+import javax.websocket.PongMessage;
+
+import org.eclipse.jetty.websocket.common.events.annotated.InvalidSignatureException;
+import org.eclipse.jetty.websocket.jsr356.annotations.Param.Role;
+import org.eclipse.jetty.websocket.jsr356.decoders.PongMessageDecoder;
+
+public class JsrParamIdPong extends JsrParamIdOnMessage implements IJsrParamId
+{
+    public static final IJsrParamId INSTANCE = new JsrParamIdPong();
+
+    @Override
+    public boolean process(Param param, JsrCallable callable) throws InvalidSignatureException
+    {
+        if (super.process(param,callable))
+        {
+            // Found common roles
+            return true;
+        }
+
+        if (param.type.isAssignableFrom(PongMessage.class))
+        {
+            assertPartialMessageSupportDisabled(param,callable);
+            param.bind(Role.MESSAGE_PONG);
+            callable.setDecoderClass(PongMessageDecoder.class);
+            return true;
+        }
+        return false;
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdText.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdText.java
new file mode 100644
index 0000000..9973cc8
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdText.java
@@ -0,0 +1,158 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.annotations;
+
+import java.io.Reader;
+
+import org.eclipse.jetty.websocket.common.events.annotated.InvalidSignatureException;
+import org.eclipse.jetty.websocket.jsr356.annotations.Param.Role;
+import org.eclipse.jetty.websocket.jsr356.decoders.BooleanDecoder;
+import org.eclipse.jetty.websocket.jsr356.decoders.ByteDecoder;
+import org.eclipse.jetty.websocket.jsr356.decoders.CharacterDecoder;
+import org.eclipse.jetty.websocket.jsr356.decoders.DoubleDecoder;
+import org.eclipse.jetty.websocket.jsr356.decoders.FloatDecoder;
+import org.eclipse.jetty.websocket.jsr356.decoders.IntegerDecoder;
+import org.eclipse.jetty.websocket.jsr356.decoders.LongDecoder;
+import org.eclipse.jetty.websocket.jsr356.decoders.ReaderDecoder;
+import org.eclipse.jetty.websocket.jsr356.decoders.ShortDecoder;
+import org.eclipse.jetty.websocket.jsr356.decoders.StringDecoder;
+
+/**
+ * Param handling for static Text @{@link OnMessage} parameters
+ */
+public class JsrParamIdText extends JsrParamIdOnMessage implements IJsrParamId
+{
+    public static final IJsrParamId INSTANCE = new JsrParamIdText();
+
+    private boolean isMessageRoleAssigned(JsrCallable callable)
+    {
+        if (callable instanceof OnMessageCallable)
+        {
+            OnMessageCallable onmessage = (OnMessageCallable)callable;
+            return onmessage.isMessageRoleAssigned();
+        }
+        return false;
+    }
+
+    @Override
+    public boolean process(Param param, JsrCallable callable) throws InvalidSignatureException
+    {
+        if (super.process(param,callable))
+        {
+            // Found common roles
+            return true;
+        }
+
+        // String for whole message
+        if (param.type.isAssignableFrom(String.class))
+        {
+            param.bind(Role.MESSAGE_TEXT);
+            callable.setDecoderClass(StringDecoder.class);
+            return true;
+        }
+
+        // Java primitive or class equivalent to receive the whole message converted to that type
+        if (param.type.isAssignableFrom(Boolean.class))
+        {
+            assertPartialMessageSupportDisabled(param,callable);
+            param.bind(Role.MESSAGE_TEXT);
+            callable.setDecoderClass(BooleanDecoder.class);
+            return true;
+        }
+        if (param.type.isAssignableFrom(Byte.class) || (param.type == Byte.TYPE))
+        {
+            assertPartialMessageSupportDisabled(param,callable);
+            param.bind(Role.MESSAGE_TEXT);
+            callable.setDecoderClass(ByteDecoder.class);
+            return true;
+        }
+        if (param.type.isAssignableFrom(Character.class) || (param.type == Character.TYPE))
+        {
+            assertPartialMessageSupportDisabled(param,callable);
+            param.bind(Role.MESSAGE_TEXT);
+            callable.setDecoderClass(CharacterDecoder.class);
+            return true;
+        }
+        if (param.type.isAssignableFrom(Double.class) || (param.type == Double.TYPE))
+        {
+            assertPartialMessageSupportDisabled(param,callable);
+            param.bind(Role.MESSAGE_TEXT);
+            callable.setDecoderClass(DoubleDecoder.class);
+            return true;
+        }
+        if (param.type.isAssignableFrom(Float.class) || (param.type == Float.TYPE))
+        {
+            assertPartialMessageSupportDisabled(param,callable);
+            param.bind(Role.MESSAGE_TEXT);
+            callable.setDecoderClass(FloatDecoder.class);
+            return true;
+        }
+        if (param.type.isAssignableFrom(Integer.class) || (param.type == Integer.TYPE))
+        {
+            assertPartialMessageSupportDisabled(param,callable);
+            param.bind(Role.MESSAGE_TEXT);
+            callable.setDecoderClass(IntegerDecoder.class);
+            return true;
+        }
+        if (param.type.isAssignableFrom(Long.class) || (param.type == Long.TYPE))
+        {
+            assertPartialMessageSupportDisabled(param,callable);
+            param.bind(Role.MESSAGE_TEXT);
+            callable.setDecoderClass(LongDecoder.class);
+            return true;
+        }
+        if (param.type.isAssignableFrom(Short.class) || (param.type == Short.TYPE))
+        {
+            assertPartialMessageSupportDisabled(param,callable);
+            param.bind(Role.MESSAGE_TEXT);
+            callable.setDecoderClass(ShortDecoder.class);
+            return true;
+        }
+
+        // Streaming
+        if (param.type.isAssignableFrom(Reader.class))
+        {
+            assertPartialMessageSupportDisabled(param,callable);
+            param.bind(Role.MESSAGE_TEXT_STREAM);
+            callable.setDecoderClass(ReaderDecoder.class);
+            return true;
+        }
+
+        /*
+         * boolean primitive.
+         * 
+         * can be used for either: 1) a boolean message type 2) a partial message indicator flag
+         */
+        if (param.type == Boolean.TYPE)
+        {
+            if (isMessageRoleAssigned(callable))
+            {
+                param.bind(Role.MESSAGE_PARTIAL_FLAG);
+            }
+            else
+            {
+                param.bind(Role.MESSAGE_TEXT);
+                callable.setDecoderClass(BooleanDecoder.class);
+            }
+            return true;
+        }
+
+        return false;
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/OnCloseCallable.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/OnCloseCallable.java
new file mode 100644
index 0000000..9f989fd
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/OnCloseCallable.java
@@ -0,0 +1,89 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.annotations;
+
+import java.lang.reflect.Method;
+
+import javax.websocket.CloseReason;
+import javax.websocket.CloseReason.CloseCodes;
+import javax.websocket.Decoder;
+
+import org.eclipse.jetty.websocket.common.CloseInfo;
+import org.eclipse.jetty.websocket.jsr356.JsrSession;
+import org.eclipse.jetty.websocket.jsr356.annotations.Param.Role;
+
+/**
+ * Callable for {@link OnClose} annotated methods
+ */
+public class OnCloseCallable extends JsrCallable
+{
+    private int idxCloseReason = -1;
+
+    public OnCloseCallable(Class<?> pojo, Method method)
+    {
+        super(pojo,method);
+    }
+
+    public OnCloseCallable(OnCloseCallable copy)
+    {
+        super(copy);
+        this.idxCloseReason = copy.idxCloseReason;
+    }
+
+    public void call(Object endpoint, CloseInfo close)
+    {
+        this.call(endpoint,close.getStatusCode(),close.getReason());
+    }
+
+    public void call(Object endpoint, CloseReason closeReason)
+    {
+        // Close Reason is an optional parameter
+        if (idxCloseReason >= 0)
+        {
+            // convert to javax.websocket.CloseReason
+            super.args[idxCloseReason] = closeReason;
+        }
+        super.call(endpoint,super.args);
+    }
+
+    public void call(Object endpoint, int statusCode, String reason)
+    {
+        // Close Reason is an optional parameter
+        if (idxCloseReason >= 0)
+        {
+            // convert to javax.websocket.CloseReason
+            CloseReason jsrclose = new CloseReason(CloseCodes.getCloseCode(statusCode),reason);
+            super.args[idxCloseReason] = jsrclose;
+        }
+        super.call(endpoint,super.args);
+    }
+
+    @Override
+    public void init(JsrSession session)
+    {
+        idxCloseReason = findIndexForRole(Role.CLOSE_REASON);
+        super.init(session);
+    }
+
+    @Override
+    public void setDecoderClass(Class<? extends Decoder> decoderClass)
+    {
+        /* ignore, not relevant for onClose */
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/OnErrorCallable.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/OnErrorCallable.java
new file mode 100644
index 0000000..9b9a109
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/OnErrorCallable.java
@@ -0,0 +1,74 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.annotations;
+
+import java.lang.reflect.Method;
+
+import javax.websocket.Decoder;
+
+import org.eclipse.jetty.websocket.jsr356.JsrSession;
+
+/**
+ * Callable for {@link OnError} annotated methods
+ */
+public class OnErrorCallable extends JsrCallable
+{
+    private int idxThrowable = -1;
+
+    public OnErrorCallable(Class<?> pojo, Method method)
+    {
+        super(pojo,method);
+    }
+
+    public OnErrorCallable(OnErrorCallable copy)
+    {
+        super(copy);
+        this.idxThrowable = copy.idxThrowable;
+    }
+
+    public void call(Object endpoint, Throwable cause)
+    {
+        if (idxThrowable == (-1))
+        {
+            idxThrowable = findIndexForRole(Param.Role.ERROR_CAUSE);
+            assertRoleRequired(idxThrowable,"Throwable");
+        }
+
+        if (idxThrowable >= 0)
+        {
+            super.args[idxThrowable] = cause;
+        }
+        super.call(endpoint,super.args);
+    }
+
+    @Override
+    public void init(JsrSession session)
+    {
+        idxThrowable = findIndexForRole(Param.Role.ERROR_CAUSE);
+        assertRoleRequired(idxThrowable,"Throwable");
+        super.init(session);
+    }
+
+    @Override
+    public void setDecoderClass(Class<? extends Decoder> decoderClass)
+    {
+        /* ignore, not relevant for onClose */
+    }
+
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/OnMessageBinaryCallable.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/OnMessageBinaryCallable.java
new file mode 100644
index 0000000..d6dba84
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/OnMessageBinaryCallable.java
@@ -0,0 +1,73 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.annotations;
+
+import java.lang.reflect.Method;
+import java.nio.ByteBuffer;
+
+import javax.websocket.DecodeException;
+import javax.websocket.Decoder;
+
+import org.eclipse.jetty.websocket.jsr356.JsrSession;
+import org.eclipse.jetty.websocket.jsr356.annotations.Param.Role;
+
+/**
+ * Callable for {@link OnMessage} annotated methods with a whole or partial binary messages.
+ * <p>
+ * Not for use with {@link InputStream} based {@link OnMessage} method objects.
+ * 
+ * @see Binary
+ */
+public class OnMessageBinaryCallable extends OnMessageCallable
+{
+    private Decoder.Binary<?> binaryDecoder;
+
+    public OnMessageBinaryCallable(Class<?> pojo, Method method)
+    {
+        super(pojo,method);
+    }
+
+    /**
+     * Copy Constructor
+     */
+    public OnMessageBinaryCallable(OnMessageCallable copy)
+    {
+        super(copy);
+    }
+
+    public Object call(Object endpoint, ByteBuffer buf, boolean partialFlag) throws DecodeException
+    {
+        super.args[idxMessageObject] = binaryDecoder.decode(buf);
+        if (idxPartialMessageFlag >= 0)
+        {
+            super.args[idxPartialMessageFlag] = partialFlag;
+        }
+        return super.call(endpoint,super.args);
+    }
+
+    @Override
+    public void init(JsrSession session)
+    {
+        idxMessageObject = findIndexForRole(Role.MESSAGE_BINARY);
+        assertRoleRequired(idxMessageObject,"Binary Message Object");
+        super.init(session);
+        assertDecoderRequired();
+        binaryDecoder = (Decoder.Binary<?>)getDecoder();
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/OnMessageBinaryStreamCallable.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/OnMessageBinaryStreamCallable.java
new file mode 100644
index 0000000..428c179
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/OnMessageBinaryStreamCallable.java
@@ -0,0 +1,73 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.annotations;
+
+//import java.io.IOException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Method;
+
+import javax.websocket.DecodeException;
+import javax.websocket.Decoder;
+
+import org.eclipse.jetty.websocket.jsr356.JsrSession;
+import org.eclipse.jetty.websocket.jsr356.annotations.Param.Role;
+
+/**
+ * Callable for {@link OnMessage} annotated methods for {@link InputStream} based binary message objects
+ * 
+ * @see BinaryStream
+ */
+public class OnMessageBinaryStreamCallable extends OnMessageCallable
+{
+    private Decoder.BinaryStream<?> binaryDecoder;
+
+    public OnMessageBinaryStreamCallable(Class<?> pojo, Method method)
+    {
+        super(pojo,method);
+    }
+
+    /**
+     * Copy Constructor
+     */
+    public OnMessageBinaryStreamCallable(OnMessageCallable copy)
+    {
+        super(copy);
+    }
+
+    public Object call(Object endpoint, InputStream stream) throws DecodeException, IOException
+    {
+        // Bug-430088 - streaming based calls are dispatched.
+        // create a copy of the calling args array to prevent concurrency problems.
+        Object copy[] = new Object[super.args.length];
+        System.arraycopy(super.args,0,copy,0,super.args.length);
+        copy[idxMessageObject] = binaryDecoder.decode(stream);
+        return super.call(endpoint,copy);
+    }
+
+    @Override
+    public void init(JsrSession session)
+    {
+        idxMessageObject = findIndexForRole(Role.MESSAGE_BINARY_STREAM);
+        assertRoleRequired(idxMessageObject,"Binary InputStream Message Object");
+        super.init(session);
+        assertDecoderRequired();
+        binaryDecoder = (Decoder.BinaryStream<?>)getDecoder();
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/OnMessageCallable.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/OnMessageCallable.java
new file mode 100644
index 0000000..9797e36
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/OnMessageCallable.java
@@ -0,0 +1,175 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.annotations;
+
+import java.lang.reflect.Method;
+
+import javax.websocket.Decoder;
+import javax.websocket.Encoder;
+
+import org.eclipse.jetty.websocket.common.events.annotated.InvalidSignatureException;
+import org.eclipse.jetty.websocket.common.util.ReflectUtils;
+import org.eclipse.jetty.websocket.jsr356.EncoderFactory;
+import org.eclipse.jetty.websocket.jsr356.InitException;
+import org.eclipse.jetty.websocket.jsr356.JsrSession;
+import org.eclipse.jetty.websocket.jsr356.annotations.Param.Role;
+
+public class OnMessageCallable extends JsrCallable
+{
+    protected final Class<?> returnType;
+    protected Encoder returnEncoder;
+    protected Class<? extends Decoder> decoderClass;
+    protected Decoder decoder;
+    protected int idxPartialMessageFlag = -1;
+    protected int idxMessageObject = -1;
+    protected boolean messageRoleAssigned = false;
+
+    public OnMessageCallable(Class<?> pojo, Method method)
+    {
+        super(pojo,method);
+        this.returnType = method.getReturnType();
+    }
+
+    public OnMessageCallable(OnMessageCallable copy)
+    {
+        super(copy);
+        this.returnType = copy.returnType;
+        this.decoderClass = copy.decoderClass;
+        this.decoder = copy.decoder;
+        this.idxPartialMessageFlag = copy.idxPartialMessageFlag;
+        this.idxMessageObject = copy.idxMessageObject;
+    }
+
+    protected void assertDecoderRequired()
+    {
+        if (getDecoder() == null)
+        {
+            StringBuilder err = new StringBuilder();
+            err.append("Unable to find a valid ");
+            err.append(Decoder.class.getName());
+            err.append(" for parameter #");
+            Param param = params[idxMessageObject];
+            err.append(param.index);
+            err.append(" [").append(param.type).append("] in method: ");
+            err.append(ReflectUtils.toString(pojo,method));
+            throw new InvalidSignatureException(err.toString());
+        }
+    }
+
+    private int findMessageObjectIndex()
+    {
+        int index = -1;
+
+        for (Param.Role role : Param.Role.getMessageRoles())
+        {
+            index = findIndexForRole(role);
+            if (index >= 0)
+            {
+                return index;
+            }
+        }
+
+        return -1;
+    }
+
+    public Decoder getDecoder()
+    {
+        return decoder;
+    }
+
+    public Class<? extends Decoder> getDecoderClass()
+    {
+        return decoderClass;
+    }
+
+    public Param getMessageObjectParam()
+    {
+        if (idxMessageObject < 0)
+        {
+            idxMessageObject = findMessageObjectIndex();
+
+            if (idxMessageObject < 0)
+            {
+                StringBuilder err = new StringBuilder();
+                err.append("A message type must be specified [TEXT, BINARY, DECODER, or PONG] : ");
+                err.append(ReflectUtils.toString(pojo,method));
+                throw new InvalidSignatureException(err.toString());
+            }
+        }
+
+        return super.params[idxMessageObject];
+    }
+
+    public Encoder getReturnEncoder()
+    {
+        return returnEncoder;
+    }
+
+    public Class<?> getReturnType()
+    {
+        return returnType;
+    }
+
+    @Override
+    public void init(JsrSession session)
+    {
+        super.init(session);
+        idxPartialMessageFlag = findIndexForRole(Role.MESSAGE_PARTIAL_FLAG);
+
+        EncoderFactory.Wrapper encoderWrapper = session.getEncoderFactory().getWrapperFor(returnType);
+        if (encoderWrapper != null)
+        {
+            this.returnEncoder = encoderWrapper.getEncoder();
+        }
+
+        if (decoderClass != null)
+        {
+            try
+            {
+                this.decoder = decoderClass.newInstance();
+            }
+            catch (InstantiationException | IllegalAccessException e)
+            {
+                throw new InitException("Unable to create decoder: " + decoderClass.getName(),e);
+            }
+        }
+    }
+
+    public boolean isMessageRoleAssigned()
+    {
+        return messageRoleAssigned;
+    }
+
+    public boolean isPartialMessageSupported()
+    {
+        return (idxPartialMessageFlag >= 0);
+    }
+
+    @Override
+    public void setDecoderClass(Class<? extends Decoder> decoderClass)
+    {
+        this.decoderClass = decoderClass;
+        messageRoleAssigned = true;
+    }
+
+    public void setPartialMessageFlag(Param param)
+    {
+        idxPartialMessageFlag = param.index;
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/OnMessagePongCallable.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/OnMessagePongCallable.java
new file mode 100644
index 0000000..8da5597
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/OnMessagePongCallable.java
@@ -0,0 +1,59 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.annotations;
+
+import java.lang.reflect.Method;
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.websocket.jsr356.JsrPongMessage;
+import org.eclipse.jetty.websocket.jsr356.JsrSession;
+import org.eclipse.jetty.websocket.jsr356.annotations.Param.Role;
+
+/**
+ * Callable for {@link OnMessage} annotated methods with a {@link PongMessage} message object.
+ */
+public class OnMessagePongCallable extends OnMessageCallable
+{
+    public OnMessagePongCallable(Class<?> pojo, Method method)
+    {
+        super(pojo,method);
+    }
+
+    /**
+     * Copy Constructor
+     */
+    public OnMessagePongCallable(OnMessageCallable copy)
+    {
+        super(copy);
+    }
+
+    public Object call(Object endpoint, ByteBuffer buf)
+    {
+        super.args[idxMessageObject] = new JsrPongMessage(buf);
+        return super.call(endpoint,super.args);
+    }
+
+    @Override
+    public void init(JsrSession session)
+    {
+        idxMessageObject = findIndexForRole(Role.MESSAGE_PONG);
+        assertRoleRequired(idxMessageObject,"Pong Message Object");
+        super.init(session);
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/OnMessageTextCallable.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/OnMessageTextCallable.java
new file mode 100644
index 0000000..a9bfb92
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/OnMessageTextCallable.java
@@ -0,0 +1,72 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.annotations;
+
+import java.lang.reflect.Method;
+
+import javax.websocket.DecodeException;
+import javax.websocket.Decoder;
+
+import org.eclipse.jetty.websocket.jsr356.JsrSession;
+import org.eclipse.jetty.websocket.jsr356.annotations.Param.Role;
+
+/**
+ * Callable for {@link OnMessage} annotated methods with a whole or partial text messages.
+ * <p>
+ * Not for use with {@link Reader} based {@link OnMessage} method objects.
+ * 
+ * @see Text
+ */
+public class OnMessageTextCallable extends OnMessageCallable
+{
+    private Decoder.Text<?> textDecoder;
+
+    public OnMessageTextCallable(Class<?> pojo, Method method)
+    {
+        super(pojo,method);
+    }
+
+    /**
+     * Copy Constructor
+     */
+    public OnMessageTextCallable(OnMessageCallable copy)
+    {
+        super(copy);
+    }
+
+    public Object call(Object endpoint, String str, boolean partialFlag) throws DecodeException
+    {
+        super.args[idxMessageObject] = textDecoder.decode(str);
+        if (idxPartialMessageFlag >= 0)
+        {
+            super.args[idxPartialMessageFlag] = partialFlag;
+        }
+        return super.call(endpoint,super.args);
+    }
+
+    @Override
+    public void init(JsrSession session)
+    {
+        idxMessageObject = findIndexForRole(Role.MESSAGE_TEXT);
+        assertRoleRequired(idxMessageObject,"Text Message Object");
+        super.init(session);
+        assertDecoderRequired();
+        textDecoder = (Decoder.Text<?>)getDecoder();
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/OnMessageTextStreamCallable.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/OnMessageTextStreamCallable.java
new file mode 100644
index 0000000..67312d7
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/OnMessageTextStreamCallable.java
@@ -0,0 +1,72 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.annotations;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.lang.reflect.Method;
+
+import javax.websocket.DecodeException;
+import javax.websocket.Decoder;
+
+import org.eclipse.jetty.websocket.jsr356.JsrSession;
+import org.eclipse.jetty.websocket.jsr356.annotations.Param.Role;
+
+/**
+ * Callable for {@link OnMessage} annotated methods for {@link Reader} based text message objects
+ * 
+ * @see TextStream
+ */
+public class OnMessageTextStreamCallable extends OnMessageCallable
+{
+    private Decoder.TextStream<?> textDecoder;
+
+    public OnMessageTextStreamCallable(Class<?> pojo, Method method)
+    {
+        super(pojo,method);
+    }
+
+    /**
+     * Copy Constructor
+     */
+    public OnMessageTextStreamCallable(OnMessageCallable copy)
+    {
+        super(copy);
+    }
+
+    public Object call(Object endpoint, Reader reader) throws DecodeException, IOException
+    {
+        // Bug-430088 - streaming based calls are dispatched.
+        // create a copy of the calling args array to prevent concurrency problems.
+        Object copy[] = new Object[super.args.length];
+        System.arraycopy(super.args,0,copy,0,super.args.length);
+        copy[idxMessageObject] = textDecoder.decode(reader);
+        return super.call(endpoint,copy);
+    }
+
+    @Override
+    public void init(JsrSession session)
+    {
+        idxMessageObject = findIndexForRole(Role.MESSAGE_TEXT_STREAM);
+        assertRoleRequired(idxMessageObject,"Text Reader Message Object");
+        super.init(session);
+        assertDecoderRequired();
+        textDecoder = (Decoder.TextStream<?>)getDecoder();
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/OnOpenCallable.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/OnOpenCallable.java
new file mode 100644
index 0000000..9c9414a
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/OnOpenCallable.java
@@ -0,0 +1,69 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.annotations;
+
+import java.lang.reflect.Method;
+
+import javax.websocket.Decoder;
+import javax.websocket.EndpointConfig;
+
+import org.eclipse.jetty.websocket.jsr356.JsrSession;
+import org.eclipse.jetty.websocket.jsr356.annotations.Param.Role;
+
+/**
+ * Callable for {@link OnOpen} annotated methods
+ */
+public class OnOpenCallable extends JsrCallable
+{
+    private int idxEndpointConfig = -1;
+
+    public OnOpenCallable(Class<?> pojo, Method method)
+    {
+        super(pojo,method);
+    }
+
+    public OnOpenCallable(OnOpenCallable copy)
+    {
+        super(copy);
+        this.idxEndpointConfig = copy.idxEndpointConfig;
+    }
+
+    public void call(Object endpoint, EndpointConfig config)
+    {
+        // EndpointConfig is an optional parameter
+        if (idxEndpointConfig >= 0)
+        {
+            super.args[idxEndpointConfig] = config;
+        }
+        super.call(endpoint,super.args);
+    }
+
+    @Override
+    public void init(JsrSession session)
+    {
+        idxEndpointConfig = findIndexForRole(Role.ENDPOINT_CONFIG);
+        super.init(session);
+    }
+
+    @Override
+    public void setDecoderClass(Class<? extends Decoder> decoderClass)
+    {
+        /* ignore, not relevant for onClose */
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/Param.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/Param.java
new file mode 100644
index 0000000..b7731dc
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/Param.java
@@ -0,0 +1,135 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.annotations;
+
+import java.lang.annotation.Annotation;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.jetty.websocket.common.util.ReflectUtils;
+
+public class Param
+{
+    /**
+     * The various roles of the known parameters.
+     */
+    public static enum Role
+    {
+        SESSION,
+        ENDPOINT_CONFIG,
+        CLOSE_REASON,
+        ERROR_CAUSE,
+        MESSAGE_TEXT,
+        MESSAGE_TEXT_STREAM,
+        MESSAGE_BINARY,
+        MESSAGE_BINARY_STREAM,
+        MESSAGE_PONG,
+        MESSAGE_PARTIAL_FLAG,
+        PATH_PARAM;
+
+        private static Role[] messageRoles;
+
+        static
+        {
+            messageRoles = new Role[]
+            { MESSAGE_TEXT, MESSAGE_TEXT_STREAM, MESSAGE_BINARY, MESSAGE_BINARY_STREAM, MESSAGE_PONG, };
+        }
+
+        public static Role[] getMessageRoles()
+        {
+            return messageRoles;
+        }
+    }
+
+    public int index;
+    public Class<?> type;
+    private transient Map<Class<? extends Annotation>, Annotation> annotations;
+
+    /*
+     * The bound role for this parameter.
+     */
+    public Role role = null;
+    private String pathParamName = null;
+
+    public Param(int idx, Class<?> type, Annotation[] annos)
+    {
+        this.index = idx;
+        this.type = type;
+        if (annos != null)
+        {
+            this.annotations = new HashMap<>();
+            for (Annotation anno : annos)
+            {
+                this.annotations.put(anno.annotationType(),anno);
+            }
+        }
+    }
+
+    public void bind(Role role)
+    {
+        this.role = role;
+    }
+
+    @SuppressWarnings("unchecked")
+    public <A extends Annotation> A getAnnotation(Class<A> annotationClass)
+    {
+        if (this.annotations == null)
+        {
+            return null;
+        }
+
+        return (A)this.annotations.get(annotationClass);
+    }
+
+    public String getPathParamName()
+    {
+        return this.pathParamName;
+    }
+
+    public boolean isValid()
+    {
+        return this.role != null;
+    }
+
+    public void setPathParamName(String name)
+    {
+        this.pathParamName = name;
+    }
+
+    @Override
+    public String toString()
+    {
+        StringBuilder str = new StringBuilder();
+        str.append("Param[");
+        str.append("index=").append(index);
+        str.append(",type=").append(ReflectUtils.toShortName(type));
+        str.append(",role=").append(role);
+        if (pathParamName != null)
+        {
+            str.append(",pathParamName=").append(pathParamName);
+        }
+        str.append(']');
+        return str.toString();
+    }
+
+    public void unbind()
+    {
+        this.role = null;
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/client/AnnotatedClientEndpointConfig.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/client/AnnotatedClientEndpointConfig.java
new file mode 100644
index 0000000..daa9c8e
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/client/AnnotatedClientEndpointConfig.java
@@ -0,0 +1,112 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.client;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.ClientEndpointConfig;
+import javax.websocket.Decoder;
+import javax.websocket.Encoder;
+import javax.websocket.Extension;
+
+import org.eclipse.jetty.websocket.api.InvalidWebSocketException;
+
+public class AnnotatedClientEndpointConfig implements ClientEndpointConfig
+{
+    private final List<Class<? extends Decoder>> decoders;
+    private final List<Class<? extends Encoder>> encoders;
+    private final List<Extension> extensions;
+    private final List<String> preferredSubprotocols;
+    private final Configurator configurator;
+    private Map<String, Object> userProperties;
+
+    public AnnotatedClientEndpointConfig(ClientEndpoint anno)
+    {
+        this.decoders = Collections.unmodifiableList(Arrays.asList(anno.decoders()));
+        this.encoders = Collections.unmodifiableList(Arrays.asList(anno.encoders()));
+        this.preferredSubprotocols = Collections.unmodifiableList(Arrays.asList(anno.subprotocols()));
+
+        // no extensions declared in annotation
+        this.extensions = Collections.emptyList();
+        // no userProperties in annotation
+        this.userProperties = new HashMap<>();
+
+        if (anno.configurator() == null)
+        {
+            this.configurator = EmptyConfigurator.INSTANCE;
+        }
+        else
+        {
+            try
+            {
+                this.configurator = anno.configurator().newInstance();
+            }
+            catch (InstantiationException | IllegalAccessException e)
+            {
+                StringBuilder err = new StringBuilder();
+                err.append("Unable to instantiate ClientEndpoint.configurator() of ");
+                err.append(anno.configurator().getName());
+                err.append(" defined as annotation in ");
+                err.append(anno.getClass().getName());
+                throw new InvalidWebSocketException(err.toString(),e);
+            }
+        }
+    }
+
+    @Override
+    public Configurator getConfigurator()
+    {
+        return configurator;
+    }
+
+    @Override
+    public List<Class<? extends Decoder>> getDecoders()
+    {
+        return decoders;
+    }
+
+    @Override
+    public List<Class<? extends Encoder>> getEncoders()
+    {
+        return encoders;
+    }
+
+    @Override
+    public List<Extension> getExtensions()
+    {
+        return extensions;
+    }
+
+    @Override
+    public List<String> getPreferredSubprotocols()
+    {
+        return preferredSubprotocols;
+    }
+
+    @Override
+    public Map<String, Object> getUserProperties()
+    {
+        return userProperties;
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/client/AnnotatedClientEndpointMetadata.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/client/AnnotatedClientEndpointMetadata.java
new file mode 100644
index 0000000..0c9c83a
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/client/AnnotatedClientEndpointMetadata.java
@@ -0,0 +1,62 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.client;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.ClientEndpointConfig;
+
+import org.eclipse.jetty.websocket.api.InvalidWebSocketException;
+import org.eclipse.jetty.websocket.jsr356.ClientContainer;
+import org.eclipse.jetty.websocket.jsr356.annotations.AnnotatedEndpointMetadata;
+
+public class AnnotatedClientEndpointMetadata extends AnnotatedEndpointMetadata<ClientEndpoint, ClientEndpointConfig>
+{
+    private final ClientEndpoint endpoint;
+    private final AnnotatedClientEndpointConfig config;
+
+    public AnnotatedClientEndpointMetadata(ClientContainer container, Class<?> websocket)
+    {
+        super(websocket);
+
+        ClientEndpoint anno = websocket.getAnnotation(ClientEndpoint.class);
+        if (anno == null)
+        {
+            throw new InvalidWebSocketException(String.format("Unsupported WebSocket object [%s], missing @%s annotation",websocket.getName(),
+                    ClientEndpoint.class.getName()));
+        }
+
+        this.endpoint = anno;
+        this.config = new AnnotatedClientEndpointConfig(anno);
+
+        getDecoders().addAll(anno.decoders());
+        getEncoders().addAll(anno.encoders());
+    }
+
+    @Override
+    public ClientEndpoint getAnnotation()
+    {
+        return endpoint;
+    }
+
+    @Override
+    public ClientEndpointConfig getConfig()
+    {
+        return config;
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/client/EmptyClientEndpointConfig.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/client/EmptyClientEndpointConfig.java
new file mode 100644
index 0000000..f212236
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/client/EmptyClientEndpointConfig.java
@@ -0,0 +1,85 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.client;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.websocket.ClientEndpointConfig;
+import javax.websocket.Decoder;
+import javax.websocket.Encoder;
+import javax.websocket.Extension;
+
+public class EmptyClientEndpointConfig implements ClientEndpointConfig
+{
+    private final List<Class<? extends Decoder>> decoders;
+    private final List<Class<? extends Encoder>> encoders;
+    private final List<Extension> extensions;
+    private final List<String> preferredSubprotocols;
+    private final Configurator configurator;
+    private Map<String, Object> userProperties;
+
+    public EmptyClientEndpointConfig()
+    {
+        this.decoders = new ArrayList<>();
+        this.encoders = new ArrayList<>();
+        this.preferredSubprotocols = new ArrayList<>();
+        this.extensions = new ArrayList<>();
+        this.userProperties = new HashMap<>();
+        this.configurator = EmptyConfigurator.INSTANCE;
+    }
+
+    @Override
+    public Configurator getConfigurator()
+    {
+        return configurator;
+    }
+
+    @Override
+    public List<Class<? extends Decoder>> getDecoders()
+    {
+        return decoders;
+    }
+
+    @Override
+    public List<Class<? extends Encoder>> getEncoders()
+    {
+        return encoders;
+    }
+
+    @Override
+    public List<Extension> getExtensions()
+    {
+        return extensions;
+    }
+
+    @Override
+    public List<String> getPreferredSubprotocols()
+    {
+        return preferredSubprotocols;
+    }
+
+    @Override
+    public Map<String, Object> getUserProperties()
+    {
+        return userProperties;
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/client/EmptyConfigurator.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/client/EmptyConfigurator.java
new file mode 100644
index 0000000..f6f375e
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/client/EmptyConfigurator.java
@@ -0,0 +1,42 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.client;
+
+import java.util.List;
+import java.util.Map;
+
+import javax.websocket.ClientEndpointConfig;
+import javax.websocket.HandshakeResponse;
+
+public class EmptyConfigurator extends ClientEndpointConfig.Configurator
+{
+    public static final EmptyConfigurator INSTANCE = new EmptyConfigurator();
+
+    @Override
+    public void afterResponse(HandshakeResponse hr)
+    {
+        // do nothing
+    }
+
+    @Override
+    public void beforeRequest(Map<String, List<String>> headers)
+    {
+        // do nothing
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/client/JsrClientEndpointImpl.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/client/JsrClientEndpointImpl.java
new file mode 100644
index 0000000..04b282d
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/client/JsrClientEndpointImpl.java
@@ -0,0 +1,102 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.client;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.ClientEndpointConfig;
+import javax.websocket.DeploymentException;
+import javax.websocket.OnMessage;
+
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.common.events.EventDriver;
+import org.eclipse.jetty.websocket.common.events.EventDriverImpl;
+import org.eclipse.jetty.websocket.jsr356.annotations.JsrEvents;
+import org.eclipse.jetty.websocket.jsr356.annotations.OnMessageCallable;
+import org.eclipse.jetty.websocket.jsr356.endpoints.EndpointInstance;
+import org.eclipse.jetty.websocket.jsr356.endpoints.JsrAnnotatedEventDriver;
+
+/**
+ * Event Driver for classes annotated with @{@link ClientEndpoint}
+ */
+public class JsrClientEndpointImpl implements EventDriverImpl
+{
+    @Override
+    public EventDriver create(Object websocket, WebSocketPolicy policy) throws DeploymentException
+    {
+        if (!(websocket instanceof EndpointInstance))
+        {
+            throw new IllegalStateException(String.format("Websocket %s must be an %s",websocket.getClass().getName(),EndpointInstance.class.getName()));
+        }
+
+        EndpointInstance ei = (EndpointInstance)websocket;
+        AnnotatedClientEndpointMetadata metadata = (AnnotatedClientEndpointMetadata)ei.getMetadata();
+        JsrEvents<ClientEndpoint, ClientEndpointConfig> events = new JsrEvents<>(metadata);
+
+        // Handle @OnMessage maxMessageSizes
+        int maxBinaryMessage = getMaxMessageSize(policy.getMaxBinaryMessageSize(),metadata.onBinary,metadata.onBinaryStream);
+        int maxTextMessage = getMaxMessageSize(policy.getMaxTextMessageSize(),metadata.onText,metadata.onTextStream);
+
+        policy.setMaxBinaryMessageSize(maxBinaryMessage);
+        policy.setMaxTextMessageSize(maxTextMessage);
+
+        return new JsrAnnotatedEventDriver(policy,ei,events);
+    }
+
+    @Override
+    public String describeRule()
+    {
+        return "class is annotated with @" + ClientEndpoint.class.getName();
+    }
+
+    private int getMaxMessageSize(int defaultMaxMessageSize, OnMessageCallable... onMessages)
+    {
+        for (OnMessageCallable callable : onMessages)
+        {
+            if (callable == null)
+            {
+                continue;
+            }
+            OnMessage onMsg = callable.getMethod().getAnnotation(OnMessage.class);
+            if (onMsg == null)
+            {
+                continue;
+            }
+            if (onMsg.maxMessageSize() > 0)
+            {
+                return (int)onMsg.maxMessageSize();
+            }
+        }
+        return defaultMaxMessageSize;
+    }
+
+    @Override
+    public boolean supports(Object websocket)
+    {
+        if (!(websocket instanceof EndpointInstance))
+        {
+            return false;
+        }
+
+        EndpointInstance ei = (EndpointInstance)websocket;
+        Object endpoint = ei.getEndpoint();
+
+        ClientEndpoint anno = endpoint.getClass().getAnnotation(ClientEndpoint.class);
+        return (anno != null);
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/client/SimpleEndpointMetadata.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/client/SimpleEndpointMetadata.java
new file mode 100644
index 0000000..0df2d73
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/client/SimpleEndpointMetadata.java
@@ -0,0 +1,72 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.client;
+
+import javax.websocket.Endpoint;
+import javax.websocket.EndpointConfig;
+
+import org.eclipse.jetty.websocket.jsr356.metadata.DecoderMetadataSet;
+import org.eclipse.jetty.websocket.jsr356.metadata.EncoderMetadataSet;
+import org.eclipse.jetty.websocket.jsr356.metadata.EndpointMetadata;
+
+/**
+ * Basic {@link EndpointMetadata} for an WebSocket that extends from {@link Endpoint}
+ */
+public class SimpleEndpointMetadata implements EndpointMetadata
+{
+    private final Class<?> endpointClass;
+    private DecoderMetadataSet decoders;
+    private EncoderMetadataSet encoders;
+
+    public SimpleEndpointMetadata(Class<? extends Endpoint> endpointClass)
+    {
+        this(endpointClass, null);
+    }
+    
+    public SimpleEndpointMetadata(Class<? extends Endpoint> endpointClass, EndpointConfig config)
+    {
+        this.endpointClass = endpointClass;
+        this.decoders = new DecoderMetadataSet();
+        this.encoders = new EncoderMetadataSet();
+
+        if (config != null)
+        {
+            this.decoders.addAll(config.getDecoders());
+            this.encoders.addAll(config.getEncoders());
+        }
+    }
+
+    @Override
+    public DecoderMetadataSet getDecoders()
+    {
+        return decoders;
+    }
+
+    @Override
+    public EncoderMetadataSet getEncoders()
+    {
+        return encoders;
+    }
+
+    @Override
+    public Class<?> getEndpointClass()
+    {
+        return endpointClass;
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/AbstractDecoder.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/AbstractDecoder.java
new file mode 100644
index 0000000..58ddc84
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/AbstractDecoder.java
@@ -0,0 +1,35 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.decoders;
+
+import javax.websocket.Decoder;
+import javax.websocket.EndpointConfig;
+
+public abstract class AbstractDecoder implements Decoder
+{
+    @Override
+    public void destroy()
+    {
+    }
+
+    @Override
+    public void init(EndpointConfig config)
+    {
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/BooleanDecoder.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/BooleanDecoder.java
new file mode 100644
index 0000000..c18c566
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/BooleanDecoder.java
@@ -0,0 +1,49 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.decoders;
+
+import javax.websocket.DecodeException;
+import javax.websocket.Decoder;
+
+
+/**
+ * Default implementation of the {@link Text} Message to {@link Boolean} decoder.
+ * <p>
+ * Note: delegates to {@link Boolean#parseBoolean(String)} and will only support "true" and "false" as boolean values.
+ */
+public class BooleanDecoder extends AbstractDecoder implements Decoder.Text<Boolean>
+{
+    public static final BooleanDecoder INSTANCE = new BooleanDecoder();
+
+    @Override
+    public Boolean decode(String s) throws DecodeException
+    {
+        return Boolean.parseBoolean(s);
+    }
+
+    @Override
+    public boolean willDecode(String s)
+    {
+        if (s == null)
+        {
+            return false;
+        }
+        return (s.equalsIgnoreCase("true") || s.equalsIgnoreCase("false"));
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/ByteArrayDecoder.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/ByteArrayDecoder.java
new file mode 100644
index 0000000..490a63d
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/ByteArrayDecoder.java
@@ -0,0 +1,43 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.decoders;
+
+import java.nio.ByteBuffer;
+
+import javax.websocket.DecodeException;
+import javax.websocket.Decoder;
+
+import org.eclipse.jetty.util.BufferUtil;
+
+public class ByteArrayDecoder extends AbstractDecoder implements Decoder.Binary<byte[]>
+{
+    public static final ByteArrayDecoder INSTANCE = new ByteArrayDecoder();
+
+    @Override
+    public byte[] decode(ByteBuffer bytes) throws DecodeException
+    {
+        return BufferUtil.toArray(bytes);
+    }
+
+    @Override
+    public boolean willDecode(ByteBuffer bytes)
+    {
+        return true;
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/ByteBufferDecoder.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/ByteBufferDecoder.java
new file mode 100644
index 0000000..69f5fbb
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/ByteBufferDecoder.java
@@ -0,0 +1,41 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.decoders;
+
+import java.nio.ByteBuffer;
+
+import javax.websocket.DecodeException;
+import javax.websocket.Decoder;
+
+public class ByteBufferDecoder extends AbstractDecoder implements Decoder.Binary<ByteBuffer>
+{
+    public static final ByteBufferDecoder INSTANCE = new ByteBufferDecoder();
+
+    @Override
+    public ByteBuffer decode(ByteBuffer bytes) throws DecodeException
+    {
+        return bytes;
+    }
+
+    @Override
+    public boolean willDecode(ByteBuffer bytes)
+    {
+        return true;
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/ByteDecoder.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/ByteDecoder.java
new file mode 100644
index 0000000..5bdd8d6
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/ByteDecoder.java
@@ -0,0 +1,62 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.decoders;
+
+import javax.websocket.DecodeException;
+import javax.websocket.Decoder;
+
+
+/**
+ * Default implementation of the {@link Text} Message to {@link Byte} decoder
+ */
+public class ByteDecoder extends AbstractDecoder implements Decoder.Text<Byte>
+{
+    public static final ByteDecoder INSTANCE = new ByteDecoder();
+
+    @Override
+    public Byte decode(String s) throws DecodeException
+    {
+        try
+        {
+            return Byte.parseByte(s);
+        }
+        catch (NumberFormatException e)
+        {
+            throw new DecodeException(s,"Unable to parse Byte",e);
+        }
+    }
+
+    @Override
+    public boolean willDecode(String s)
+    {
+        if (s == null)
+        {
+            return false;
+        }
+        try
+        {
+            Byte.parseByte(s);
+            return true;
+        }
+        catch (NumberFormatException e)
+        {
+            return false;
+        }
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/CharacterDecoder.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/CharacterDecoder.java
new file mode 100644
index 0000000..b092fcb
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/CharacterDecoder.java
@@ -0,0 +1,52 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.decoders;
+
+import javax.websocket.DecodeException;
+import javax.websocket.Decoder;
+
+
+/**
+ * Default implementation of the {@link Text} Message to {@link Character} decoder
+ */
+public class CharacterDecoder extends AbstractDecoder implements Decoder.Text<Character>
+{
+    public static final CharacterDecoder INSTANCE = new CharacterDecoder();
+
+    @Override
+    public Character decode(String s) throws DecodeException
+    {
+        return Character.valueOf(s.charAt(0));
+    }
+
+    @Override
+    public boolean willDecode(String s)
+    {
+        if (s == null)
+        {
+            return false;
+        }
+        if (s.length() == 1)
+        {
+            return true;
+        }
+        // can only parse 1 character
+        return false;
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/DoubleDecoder.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/DoubleDecoder.java
new file mode 100644
index 0000000..1bb9c6f
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/DoubleDecoder.java
@@ -0,0 +1,62 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.decoders;
+
+import javax.websocket.DecodeException;
+import javax.websocket.Decoder;
+
+
+/**
+ * Default implementation of the {@link Text} Message to {@link Double} to decoder
+ */
+public class DoubleDecoder extends AbstractDecoder implements Decoder.Text<Double>
+{
+    public static final DoubleDecoder INSTANCE = new DoubleDecoder();
+
+    @Override
+    public Double decode(String s) throws DecodeException
+    {
+        try
+        {
+            return Double.parseDouble(s);
+        }
+        catch (NumberFormatException e)
+        {
+            throw new DecodeException(s,"Unable to parse double",e);
+        }
+    }
+
+    @Override
+    public boolean willDecode(String s)
+    {
+        if (s == null)
+        {
+            return false;
+        }
+        try
+        {
+            Double.parseDouble(s);
+            return true;
+        }
+        catch (NumberFormatException e)
+        {
+            return false;
+        }
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/FloatDecoder.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/FloatDecoder.java
new file mode 100644
index 0000000..4a940d4
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/FloatDecoder.java
@@ -0,0 +1,66 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.decoders;
+
+import javax.websocket.DecodeException;
+import javax.websocket.Decoder;
+
+/**
+ * Default implementation of the Text Message to {@link Float} decoder
+ */
+public class FloatDecoder extends AbstractDecoder implements Decoder.Text<Float>
+{
+    public static final FloatDecoder INSTANCE = new FloatDecoder();
+
+    @Override
+    public Float decode(String s) throws DecodeException
+    {
+        try
+        {
+            Float val = Float.parseFloat(s);
+            if (val.isNaN())
+            {
+                throw new DecodeException(s,"NaN");
+            }
+            return val;
+        }
+        catch (NumberFormatException e)
+        {
+            throw new DecodeException(s,"Unable to parse float",e);
+        }
+    }
+
+    @Override
+    public boolean willDecode(String s)
+    {
+        if (s == null)
+        {
+            return false;
+        }
+        try
+        {
+            Float val = Float.parseFloat(s);
+            return (!val.isNaN());
+        }
+        catch (NumberFormatException e)
+        {
+            return false;
+        }
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/InputStreamDecoder.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/InputStreamDecoder.java
new file mode 100644
index 0000000..09b773d
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/InputStreamDecoder.java
@@ -0,0 +1,45 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.decoders;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.websocket.DecodeException;
+import javax.websocket.Decoder;
+import javax.websocket.EndpointConfig;
+
+public class InputStreamDecoder implements Decoder.BinaryStream<InputStream>
+{
+    @Override
+    public InputStream decode(InputStream is) throws DecodeException, IOException
+    {
+        return is;
+    }
+
+    @Override
+    public void destroy()
+    {
+    }
+
+    @Override
+    public void init(EndpointConfig config)
+    {
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/IntegerDecoder.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/IntegerDecoder.java
new file mode 100644
index 0000000..0cee891
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/IntegerDecoder.java
@@ -0,0 +1,63 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.decoders;
+
+import javax.websocket.DecodeException;
+import javax.websocket.Decoder;
+
+
+/**
+ * Default implementation of the {@link Text} Message to {@link Integer} decoder
+ */
+public class IntegerDecoder extends AbstractDecoder implements Decoder.Text<Integer>
+{
+    public static final IntegerDecoder INSTANCE = new IntegerDecoder();
+
+    @Override
+    public Integer decode(String s) throws DecodeException
+    {
+        try
+        {
+            return Integer.parseInt(s);
+        }
+        catch (NumberFormatException e)
+        {
+            throw new DecodeException(s,"Unable to parse Integer",e);
+        }
+    }
+
+    @Override
+    public boolean willDecode(String s)
+    {
+        if (s == null)
+        {
+            return false;
+        }
+
+        try
+        {
+            Integer.parseInt(s);
+            return true;
+        }
+        catch (NumberFormatException e)
+        {
+            return false;
+        }
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/LongDecoder.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/LongDecoder.java
new file mode 100644
index 0000000..3843e3b
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/LongDecoder.java
@@ -0,0 +1,61 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.decoders;
+
+import javax.websocket.DecodeException;
+import javax.websocket.Decoder;
+
+/**
+ * Default implementation of the Text Message to {@link Long} decoder
+ */
+public class LongDecoder extends AbstractDecoder implements Decoder.Text<Long>
+{
+    public static final LongDecoder INSTANCE = new LongDecoder();
+
+    @Override
+    public Long decode(String s) throws DecodeException
+    {
+        try
+        {
+            return Long.parseLong(s);
+        }
+        catch (NumberFormatException e)
+        {
+            throw new DecodeException(s,"Unable to parse Long",e);
+        }
+    }
+
+    @Override
+    public boolean willDecode(String s)
+    {
+        if (s == null)
+        {
+            return false;
+        }
+        try
+        {
+            Long.parseLong(s);
+            return true;
+        }
+        catch (NumberFormatException e)
+        {
+            return false;
+        }
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/PongMessageDecoder.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/PongMessageDecoder.java
new file mode 100644
index 0000000..acd9ec1
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/PongMessageDecoder.java
@@ -0,0 +1,61 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.decoders;
+
+import java.nio.ByteBuffer;
+
+import javax.websocket.DecodeException;
+import javax.websocket.Decoder;
+import javax.websocket.PongMessage;
+
+import org.eclipse.jetty.util.BufferUtil;
+
+public class PongMessageDecoder extends AbstractDecoder implements Decoder.Binary<PongMessage>
+{
+    private static class PongMsg implements PongMessage
+    {
+        private final ByteBuffer bytes;
+
+        public PongMsg(ByteBuffer buf)
+        {
+            int len = buf.remaining();
+            this.bytes = ByteBuffer.allocate(len);
+            BufferUtil.put(buf,this.bytes);
+            BufferUtil.flipToFlush(this.bytes,0);
+        }
+
+        @Override
+        public ByteBuffer getApplicationData()
+        {
+            return this.bytes;
+        }
+    }
+
+    @Override
+    public PongMessage decode(ByteBuffer bytes) throws DecodeException
+    {
+        return new PongMsg(bytes);
+    }
+
+    @Override
+    public boolean willDecode(ByteBuffer bytes)
+    {
+        return true;
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/PrimitiveDecoderMetadataSet.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/PrimitiveDecoderMetadataSet.java
new file mode 100644
index 0000000..7c32793
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/PrimitiveDecoderMetadataSet.java
@@ -0,0 +1,76 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.decoders;
+
+import java.io.InputStream;
+import java.io.Reader;
+import java.nio.ByteBuffer;
+
+import javax.websocket.PongMessage;
+
+import org.eclipse.jetty.websocket.jsr356.MessageType;
+import org.eclipse.jetty.websocket.jsr356.metadata.DecoderMetadataSet;
+
+public class PrimitiveDecoderMetadataSet extends DecoderMetadataSet
+{
+    public static final DecoderMetadataSet INSTANCE = new PrimitiveDecoderMetadataSet();
+
+    public PrimitiveDecoderMetadataSet()
+    {
+        boolean streamed = false;
+        // TEXT based - Classes Based
+        MessageType msgType = MessageType.TEXT;
+        register(Boolean.class,BooleanDecoder.class,msgType,streamed);
+        register(Byte.class,ByteDecoder.class,msgType,streamed);
+        register(Character.class,CharacterDecoder.class,msgType,streamed);
+        register(Double.class,DoubleDecoder.class,msgType,streamed);
+        register(Float.class,FloatDecoder.class,msgType,streamed);
+        register(Integer.class,IntegerDecoder.class,msgType,streamed);
+        register(Long.class,LongDecoder.class,msgType,streamed);
+        register(Short.class,ShortDecoder.class,msgType,streamed);
+        register(String.class,StringDecoder.class,msgType,streamed);
+
+        // TEXT based - Primitive Types
+        msgType = MessageType.TEXT;
+        register(Boolean.TYPE,BooleanDecoder.class,msgType,streamed);
+        register(Byte.TYPE,ByteDecoder.class,msgType,streamed);
+        register(Character.TYPE,CharacterDecoder.class,msgType,streamed);
+        register(Double.TYPE,DoubleDecoder.class,msgType,streamed);
+        register(Float.TYPE,FloatDecoder.class,msgType,streamed);
+        register(Integer.TYPE,IntegerDecoder.class,msgType,streamed);
+        register(Long.TYPE,LongDecoder.class,msgType,streamed);
+        register(Short.TYPE,ShortDecoder.class,msgType,streamed);
+
+        // BINARY based
+        msgType = MessageType.BINARY;
+        register(ByteBuffer.class,ByteBufferDecoder.class,msgType,streamed);
+        register(byte[].class,ByteArrayDecoder.class,msgType,streamed);
+        
+        // PONG based
+        msgType = MessageType.PONG;
+        register(PongMessage.class,PongMessageDecoder.class,msgType,streamed);
+
+        // STREAMING based
+        streamed = true;
+        msgType = MessageType.TEXT;
+        register(Reader.class,ReaderDecoder.class,msgType,streamed);
+        msgType = MessageType.BINARY;
+        register(InputStream.class,InputStreamDecoder.class,msgType,streamed);
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/ReaderDecoder.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/ReaderDecoder.java
new file mode 100644
index 0000000..f3e1722
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/ReaderDecoder.java
@@ -0,0 +1,45 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.decoders;
+
+import java.io.IOException;
+import java.io.Reader;
+
+import javax.websocket.DecodeException;
+import javax.websocket.Decoder;
+import javax.websocket.EndpointConfig;
+
+public class ReaderDecoder implements Decoder.TextStream<Reader>
+{
+    @Override
+    public Reader decode(Reader reader) throws DecodeException, IOException
+    {
+        return reader;
+    }
+
+    @Override
+    public void destroy()
+    {
+    }
+
+    @Override
+    public void init(EndpointConfig config)
+    {
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/ShortDecoder.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/ShortDecoder.java
new file mode 100644
index 0000000..95133a3
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/ShortDecoder.java
@@ -0,0 +1,62 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.decoders;
+
+import javax.websocket.DecodeException;
+import javax.websocket.Decoder;
+
+
+/**
+ * Default implementation of the {@link Text} Message to {@link Short} decoder
+ */
+public class ShortDecoder extends AbstractDecoder implements Decoder.Text<Short>
+{
+    public static final ShortDecoder INSTANCE = new ShortDecoder();
+
+    @Override
+    public Short decode(String s) throws DecodeException
+    {
+        try
+        {
+            return Short.parseShort(s);
+        }
+        catch (NumberFormatException e)
+        {
+            throw new DecodeException(s,"Unable to parse Short",e);
+        }
+    }
+
+    @Override
+    public boolean willDecode(String s)
+    {
+        if (s == null)
+        {
+            return false;
+        }
+        try
+        {
+            Short.parseShort(s);
+            return true;
+        }
+        catch (NumberFormatException e)
+        {
+            return false;
+        }
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/StringDecoder.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/StringDecoder.java
new file mode 100644
index 0000000..94bf773
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/decoders/StringDecoder.java
@@ -0,0 +1,43 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.decoders;
+
+import javax.websocket.DecodeException;
+import javax.websocket.Decoder;
+
+
+/**
+ * Default implementation of the {@link Text} Message to {@link String} decoder
+ */
+public class StringDecoder extends AbstractDecoder implements Decoder.Text<String>
+{
+    public static final StringDecoder INSTANCE = new StringDecoder();
+
+    @Override
+    public String decode(String s) throws DecodeException
+    {
+        return s;
+    }
+
+    @Override
+    public boolean willDecode(String s)
+    {
+        return true;
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/AbstractEncoder.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/AbstractEncoder.java
new file mode 100644
index 0000000..3fec319
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/AbstractEncoder.java
@@ -0,0 +1,35 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.encoders;
+
+import javax.websocket.Encoder;
+import javax.websocket.EndpointConfig;
+
+public abstract class AbstractEncoder implements Encoder
+{
+    @Override
+    public void destroy()
+    {
+    }
+
+    @Override
+    public void init(EndpointConfig config)
+    {
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/BooleanEncoder.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/BooleanEncoder.java
new file mode 100644
index 0000000..c4f22d2
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/BooleanEncoder.java
@@ -0,0 +1,38 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.encoders;
+
+import javax.websocket.EncodeException;
+import javax.websocket.Encoder;
+
+/**
+ * Default encoder for {@link Boolean} to {@link Text} Message encoder
+ */
+public class BooleanEncoder extends AbstractEncoder implements Encoder.Text<Boolean>
+{
+    @Override
+    public String encode(Boolean object) throws EncodeException
+    {
+        if (object == null)
+        {
+            return null;
+        }
+        return object.toString();
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/ByteArrayEncoder.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/ByteArrayEncoder.java
new file mode 100644
index 0000000..552f5f0
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/ByteArrayEncoder.java
@@ -0,0 +1,46 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.encoders;
+
+import java.nio.ByteBuffer;
+
+import javax.websocket.EncodeException;
+import javax.websocket.Encoder;
+import javax.websocket.EndpointConfig;
+
+public class ByteArrayEncoder implements Encoder.Binary<byte[]>
+{
+    @Override
+    public void destroy()
+    {
+        /* do nothing */
+    }
+
+    @Override
+    public ByteBuffer encode(byte[] object) throws EncodeException
+    {
+        return ByteBuffer.wrap(object);
+    }
+
+    @Override
+    public void init(EndpointConfig config)
+    {
+        /* do nothing */
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/ByteBufferEncoder.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/ByteBufferEncoder.java
new file mode 100644
index 0000000..4920b0f
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/ByteBufferEncoder.java
@@ -0,0 +1,46 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.encoders;
+
+import java.nio.ByteBuffer;
+
+import javax.websocket.EncodeException;
+import javax.websocket.Encoder;
+import javax.websocket.EndpointConfig;
+
+public class ByteBufferEncoder implements Encoder.Binary<ByteBuffer>
+{
+    @Override
+    public void destroy()
+    {
+        /* do nothing */
+    }
+
+    @Override
+    public ByteBuffer encode(ByteBuffer object) throws EncodeException
+    {
+        return object;
+    }
+
+    @Override
+    public void init(EndpointConfig config)
+    {
+        /* do nothing */
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/ByteEncoder.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/ByteEncoder.java
new file mode 100644
index 0000000..3963d05
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/ByteEncoder.java
@@ -0,0 +1,38 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.encoders;
+
+import javax.websocket.EncodeException;
+import javax.websocket.Encoder;
+
+/**
+ * Default encoder for {@link Byte} to {@link Text} Message encoder
+ */
+public class ByteEncoder extends AbstractEncoder implements Encoder.Text<Byte>
+{
+    @Override
+    public String encode(Byte object) throws EncodeException
+    {
+        if (object == null)
+        {
+            return null;
+        }
+        return object.toString();
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/CharacterEncoder.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/CharacterEncoder.java
new file mode 100644
index 0000000..87d1fa3
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/CharacterEncoder.java
@@ -0,0 +1,38 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.encoders;
+
+import javax.websocket.EncodeException;
+import javax.websocket.Encoder;
+
+/**
+ * Default encoder for {@link Character} to {@link Text} Message encoder
+ */
+public class CharacterEncoder extends AbstractEncoder implements Encoder.Text<Character>
+{
+    @Override
+    public String encode(Character object) throws EncodeException
+    {
+        if (object == null)
+        {
+            return null;
+        }
+        return object.toString();
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/DefaultBinaryEncoder.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/DefaultBinaryEncoder.java
new file mode 100644
index 0000000..21d30a4
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/DefaultBinaryEncoder.java
@@ -0,0 +1,33 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.encoders;
+
+import java.nio.ByteBuffer;
+
+import javax.websocket.EncodeException;
+import javax.websocket.Encoder;
+
+public class DefaultBinaryEncoder extends AbstractEncoder implements Encoder.Binary<ByteBuffer>
+{
+    @Override
+    public ByteBuffer encode(ByteBuffer message) throws EncodeException
+    {
+        return message;
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/DefaultBinaryStreamEncoder.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/DefaultBinaryStreamEncoder.java
new file mode 100644
index 0000000..6f48326
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/DefaultBinaryStreamEncoder.java
@@ -0,0 +1,37 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.encoders;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+
+import javax.websocket.EncodeException;
+import javax.websocket.Encoder;
+
+import org.eclipse.jetty.util.BufferUtil;
+
+public class DefaultBinaryStreamEncoder extends AbstractEncoder implements Encoder.BinaryStream<ByteBuffer>
+{
+    @Override
+    public void encode(ByteBuffer message, OutputStream out) throws EncodeException, IOException
+    {
+        BufferUtil.writeTo(message,out);
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/DefaultTextEncoder.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/DefaultTextEncoder.java
new file mode 100644
index 0000000..79dc0c6
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/DefaultTextEncoder.java
@@ -0,0 +1,31 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.encoders;
+
+import javax.websocket.EncodeException;
+import javax.websocket.Encoder;
+
+public class DefaultTextEncoder extends AbstractEncoder implements Encoder.Text<String>
+{
+    @Override
+    public String encode(String message) throws EncodeException
+    {
+        return message;
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/DefaultTextStreamEncoder.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/DefaultTextStreamEncoder.java
new file mode 100644
index 0000000..606e573
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/DefaultTextStreamEncoder.java
@@ -0,0 +1,35 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.encoders;
+
+import java.io.IOException;
+import java.io.Writer;
+
+import javax.websocket.EncodeException;
+import javax.websocket.Encoder;
+
+public class DefaultTextStreamEncoder extends AbstractEncoder implements Encoder.TextStream<String>
+{
+    @Override
+    public void encode(String message, Writer writer) throws EncodeException, IOException
+    {
+        writer.append(message);
+        writer.flush();
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/DoubleEncoder.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/DoubleEncoder.java
new file mode 100644
index 0000000..bc97d22
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/DoubleEncoder.java
@@ -0,0 +1,38 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.encoders;
+
+import javax.websocket.EncodeException;
+import javax.websocket.Encoder;
+
+/**
+ * Default encoder for {@link Double} to {@link Text} Message encoder
+ */
+public class DoubleEncoder extends AbstractEncoder implements Encoder.Text<Double>
+{
+    @Override
+    public String encode(Double object) throws EncodeException
+    {
+        if (object == null)
+        {
+            return null;
+        }
+        return object.toString();
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/EncodeFailedFuture.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/EncodeFailedFuture.java
new file mode 100644
index 0000000..acf9492
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/EncodeFailedFuture.java
@@ -0,0 +1,71 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.encoders;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import javax.websocket.Encoder;
+
+/**
+ * A <code>Future<Void></code> that is already failed as a result of an Encode error
+ */
+public class EncodeFailedFuture implements Future<Void>
+{
+    private final String msg;
+    private final Throwable cause;
+
+    public EncodeFailedFuture(Object data, Encoder encoder, Class<?> encoderType, Throwable cause)
+    {
+        this.msg = String.format("Unable to encode %s using %s as %s",data.getClass().getName(),encoder.getClass().getName(),encoderType.getName());
+        this.cause = cause;
+    }
+
+    @Override
+    public boolean cancel(boolean mayInterruptIfRunning)
+    {
+        return false;
+    }
+
+    @Override
+    public Void get() throws InterruptedException, ExecutionException
+    {
+        throw new ExecutionException(msg,cause);
+    }
+
+    @Override
+    public Void get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException
+    {
+        throw new ExecutionException(msg,cause);
+    }
+
+    @Override
+    public boolean isCancelled()
+    {
+        return false;
+    }
+
+    @Override
+    public boolean isDone()
+    {
+        return true;
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/FloatEncoder.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/FloatEncoder.java
new file mode 100644
index 0000000..a9df32a
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/FloatEncoder.java
@@ -0,0 +1,37 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.encoders;
+
+import javax.websocket.EncodeException;
+import javax.websocket.Encoder;
+/**
+ * Default encoder for {@link Float} to {@link Text} Message encoder
+ */
+public class FloatEncoder extends AbstractEncoder implements Encoder.Text<Float>
+{
+    @Override
+    public String encode(Float object) throws EncodeException
+    {
+        if (object == null)
+        {
+            return null;
+        }
+        return object.toString();
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/IntegerEncoder.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/IntegerEncoder.java
new file mode 100644
index 0000000..124c840
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/IntegerEncoder.java
@@ -0,0 +1,38 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.encoders;
+
+import javax.websocket.EncodeException;
+import javax.websocket.Encoder;
+
+/**
+ * Default encoder for {@link Integer} to {@link Text} Message encoder
+ */
+public class IntegerEncoder extends AbstractEncoder implements Encoder.Text<Integer>
+{
+    @Override
+    public String encode(Integer object) throws EncodeException
+    {
+        if (object == null)
+        {
+            return null;
+        }
+        return object.toString();
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/LongEncoder.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/LongEncoder.java
new file mode 100644
index 0000000..f41a570
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/LongEncoder.java
@@ -0,0 +1,38 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.encoders;
+
+import javax.websocket.EncodeException;
+import javax.websocket.Encoder;
+
+/**
+ * Default encoder for {@link Long} to {@link Text} Message encoder
+ */
+public class LongEncoder extends AbstractEncoder implements Encoder.Text<Long>
+{
+    @Override
+    public String encode(Long object) throws EncodeException
+    {
+        if (object == null)
+        {
+            return null;
+        }
+        return object.toString();
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/PrimitiveEncoderMetadataSet.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/PrimitiveEncoderMetadataSet.java
new file mode 100644
index 0000000..70c7201
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/PrimitiveEncoderMetadataSet.java
@@ -0,0 +1,62 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.encoders;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.websocket.jsr356.MessageType;
+import org.eclipse.jetty.websocket.jsr356.metadata.EncoderMetadataSet;
+
+public class PrimitiveEncoderMetadataSet extends EncoderMetadataSet
+{
+    public static final EncoderMetadataSet INSTANCE = new PrimitiveEncoderMetadataSet();
+
+    public PrimitiveEncoderMetadataSet()
+    {
+        boolean streamed = false;
+        // TEXT based - Classes Based
+        MessageType msgType = MessageType.TEXT;
+        register(Boolean.class,BooleanEncoder.class,msgType,streamed);
+        register(Byte.class,ByteEncoder.class,msgType,streamed);
+        register(Character.class,CharacterEncoder.class,msgType,streamed);
+        register(Double.class,DoubleEncoder.class,msgType,streamed);
+        register(Float.class,FloatEncoder.class,msgType,streamed);
+        register(Integer.class,IntegerEncoder.class,msgType,streamed);
+        register(Long.class,LongEncoder.class,msgType,streamed);
+        register(Short.class,ShortEncoder.class,msgType,streamed);
+        register(String.class,StringEncoder.class,msgType,streamed);
+
+        // TEXT based - Primitive Types
+        msgType = MessageType.TEXT;
+        register(Boolean.TYPE,BooleanEncoder.class,msgType,streamed);
+        register(Byte.TYPE,ByteEncoder.class,msgType,streamed);
+        register(Character.TYPE,CharacterEncoder.class,msgType,streamed);
+        register(Double.TYPE,DoubleEncoder.class,msgType,streamed);
+        register(Float.TYPE,FloatEncoder.class,msgType,streamed);
+        register(Integer.TYPE,IntegerEncoder.class,msgType,streamed);
+        register(Long.TYPE,LongEncoder.class,msgType,streamed);
+        register(Short.TYPE,ShortEncoder.class,msgType,streamed);
+
+        // BINARY based
+        msgType = MessageType.BINARY;
+        register(ByteBuffer.class,ByteBufferEncoder.class,msgType,streamed);
+        register(byte[].class,ByteArrayEncoder.class,msgType,streamed);
+
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/ShortEncoder.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/ShortEncoder.java
new file mode 100644
index 0000000..c2b55af
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/ShortEncoder.java
@@ -0,0 +1,38 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.encoders;
+
+import javax.websocket.EncodeException;
+import javax.websocket.Encoder;
+
+/**
+ * Default encoder for {@link Short} to {@link Text} Message encoder
+ */
+public class ShortEncoder extends AbstractEncoder implements Encoder.Text<Short>
+{
+    @Override
+    public String encode(Short object) throws EncodeException
+    {
+        if (object == null)
+        {
+            return null;
+        }
+        return object.toString();
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/StringEncoder.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/StringEncoder.java
new file mode 100644
index 0000000..701d53b
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/StringEncoder.java
@@ -0,0 +1,34 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.encoders;
+
+import javax.websocket.EncodeException;
+import javax.websocket.Encoder;
+
+/**
+ * Default encoder for {@link String} to {@link Text} Message encoder
+ */
+public class StringEncoder extends AbstractEncoder implements Encoder.Text<String>
+{
+    @Override
+    public String encode(String object) throws EncodeException
+    {
+        return object;
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/AbstractJsrEventDriver.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/AbstractJsrEventDriver.java
new file mode 100644
index 0000000..1e782cc
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/AbstractJsrEventDriver.java
@@ -0,0 +1,114 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.endpoints;
+
+import java.util.Map;
+
+import javax.websocket.CloseReason;
+import javax.websocket.CloseReason.CloseCode;
+import javax.websocket.CloseReason.CloseCodes;
+import javax.websocket.EndpointConfig;
+import javax.websocket.Session;
+
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.common.CloseInfo;
+import org.eclipse.jetty.websocket.common.WebSocketSession;
+import org.eclipse.jetty.websocket.common.events.AbstractEventDriver;
+import org.eclipse.jetty.websocket.jsr356.JsrSession;
+import org.eclipse.jetty.websocket.jsr356.metadata.EndpointMetadata;
+
+public abstract class AbstractJsrEventDriver extends AbstractEventDriver
+{
+    protected final EndpointMetadata metadata;
+    protected final EndpointConfig config;
+    protected JsrSession jsrsession;
+    private boolean hasCloseBeenCalled = false;
+
+    public AbstractJsrEventDriver(WebSocketPolicy policy, EndpointInstance endpointInstance)
+    {
+        super(policy,endpointInstance.getEndpoint());
+        this.config = endpointInstance.getConfig();
+        this.metadata = endpointInstance.getMetadata();
+    }
+
+    public EndpointConfig getConfig()
+    {
+        return config;
+    }
+
+    public Session getJsrSession()
+    {
+        return this.jsrsession;
+    }
+
+    public EndpointMetadata getMetadata()
+    {
+        return metadata;
+    }
+
+    public abstract void init(JsrSession jsrsession);
+
+    @Override
+    public final void onClose(CloseInfo close)
+    {
+        if (hasCloseBeenCalled)
+        {
+            // avoid duplicate close events (possible when using harsh Session.disconnect())
+            return;
+        }
+        hasCloseBeenCalled = true;
+
+        CloseCode closecode = CloseCodes.getCloseCode(close.getStatusCode());
+        CloseReason closereason = new CloseReason(closecode,close.getReason());
+        onClose(closereason);
+    }
+
+    protected abstract void onClose(CloseReason closereason);
+
+    @Override
+    public void onFrame(Frame frame)
+    {
+        /* Ignored, not supported by JSR-356 */
+    }
+
+    @Override
+    public final void openSession(WebSocketSession session)
+    {
+        // Cast should be safe, as it was created by JsrSessionFactory
+        this.jsrsession = (JsrSession)session;
+
+        // Allow jsr session to init
+        this.jsrsession.init(config);
+
+        // Allow event driver to init itself
+        init(jsrsession);
+
+        // Allow end-user socket to adjust configuration
+        super.openSession(session);
+    }
+
+    public void setEndpointconfig(EndpointConfig endpointconfig)
+    {
+        throw new RuntimeException("Why are you reconfiguring the endpoint?");
+        // this.config = endpointconfig;
+    }
+
+    public abstract void setPathParameters(Map<String, String> pathParameters);
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/EndpointInstance.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/EndpointInstance.java
new file mode 100644
index 0000000..8c6dd01
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/EndpointInstance.java
@@ -0,0 +1,58 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.endpoints;
+
+import javax.websocket.EndpointConfig;
+
+import org.eclipse.jetty.websocket.jsr356.metadata.EndpointMetadata;
+
+/**
+ * Associate a JSR Endpoint with its optional {@link EndpointConfig}
+ */
+public class EndpointInstance
+{
+    /** The instance of the Endpoint */
+    private final Object endpoint;
+    /** The instance specific configuration for the Endpoint */
+    private final EndpointConfig config;
+    /** The metadata for this endpoint */
+    private final EndpointMetadata metadata;
+
+    public EndpointInstance(Object endpoint, EndpointConfig config, EndpointMetadata metadata)
+    {
+        this.endpoint = endpoint;
+        this.config = config;
+        this.metadata = metadata;
+    }
+
+    public EndpointConfig getConfig()
+    {
+        return config;
+    }
+
+    public Object getEndpoint()
+    {
+        return endpoint;
+    }
+
+    public EndpointMetadata getMetadata()
+    {
+        return metadata;
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/JsrAnnotatedEventDriver.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/JsrAnnotatedEventDriver.java
new file mode 100644
index 0000000..8ec9efb
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/JsrAnnotatedEventDriver.java
@@ -0,0 +1,396 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.endpoints;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.nio.ByteBuffer;
+import java.util.Map;
+
+import javax.websocket.CloseReason;
+import javax.websocket.DecodeException;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.common.message.MessageInputStream;
+import org.eclipse.jetty.websocket.common.message.MessageReader;
+import org.eclipse.jetty.websocket.common.message.SimpleBinaryMessage;
+import org.eclipse.jetty.websocket.common.message.SimpleTextMessage;
+import org.eclipse.jetty.websocket.jsr356.JsrSession;
+import org.eclipse.jetty.websocket.jsr356.annotations.JsrEvents;
+import org.eclipse.jetty.websocket.jsr356.messages.BinaryPartialOnMessage;
+import org.eclipse.jetty.websocket.jsr356.messages.TextPartialOnMessage;
+
+/**
+ * Base implementation for JSR-356 Annotated event drivers.
+ */
+public class JsrAnnotatedEventDriver extends AbstractJsrEventDriver
+{
+    private static final Logger LOG = Log.getLogger(JsrAnnotatedEventDriver.class);
+    private final JsrEvents<?, ?> events;
+
+    public JsrAnnotatedEventDriver(WebSocketPolicy policy, EndpointInstance endpointInstance, JsrEvents<?, ?> events)
+    {
+        super(policy,endpointInstance);
+        this.events = events;
+    }
+
+    @Override
+    public void init(JsrSession jsrsession)
+    {
+        this.events.init(jsrsession);
+    }
+
+    /**
+     * Entry point for all incoming binary frames.
+     */
+    @Override
+    public void onBinaryFrame(ByteBuffer buffer, boolean fin) throws IOException
+    {
+        if (LOG.isDebugEnabled())
+        {
+            LOG.debug("onBinaryFrame({}, {})",BufferUtil.toDetailString(buffer),fin);
+            LOG.debug("events.onBinary={}",events.hasBinary());
+            LOG.debug("events.onBinaryStream={}",events.hasBinaryStream());
+        }
+        boolean handled = false;
+
+        if (events.hasBinary())
+        {
+            handled = true;
+            if (activeMessage == null)
+            {
+                if (events.isBinaryPartialSupported())
+                {
+                    // Partial Message Support (does not use messageAppender)
+                    if (LOG.isDebugEnabled())
+                    {
+                        LOG.debug("Partial Binary Message: fin={}",fin);
+                    }
+                    activeMessage = new BinaryPartialOnMessage(this);
+                }
+                else
+                {
+                    // Whole Message Support
+                    if (LOG.isDebugEnabled())
+                    {
+                        LOG.debug("Whole Binary Message");
+                    }
+                    activeMessage = new SimpleBinaryMessage(this);
+                }
+            }
+        }
+
+        if (events.hasBinaryStream())
+        {
+            handled = true;
+            // Streaming Message Support
+            if (activeMessage == null)
+            {
+                if (LOG.isDebugEnabled())
+                {
+                    LOG.debug("Binary Message InputStream");
+                }
+                final MessageInputStream stream = new MessageInputStream();
+                activeMessage = stream;
+
+                // Always dispatch streaming read to another thread.
+                dispatch(new Runnable()
+                {
+                    @Override
+                    public void run()
+                    {
+                        try
+                        {
+                            events.callBinaryStream(jsrsession.getAsyncRemote(),websocket,stream);
+                        }
+                        catch (Throwable e)
+                        {
+                            onFatalError(e);
+                        }
+                    }
+                });
+            }
+        }
+
+        if (LOG.isDebugEnabled())
+        {
+            LOG.debug("handled = {}",handled);
+        }
+
+        // Process any active MessageAppender
+        if (handled && (activeMessage != null))
+        {
+            appendMessage(buffer,fin);
+        }
+    }
+
+    /**
+     * Entry point for binary frames destined for {@link Whole}
+     */
+    @Override
+    public void onBinaryMessage(byte[] data)
+    {
+        if (data == null)
+        {
+            return;
+        }
+
+        ByteBuffer buf = ByteBuffer.wrap(data);
+
+        if (LOG.isDebugEnabled())
+        {
+            LOG.debug("onBinaryMessage({})",BufferUtil.toDetailString(buf));
+        }
+
+        try
+        {
+            // FIN is always true here
+            events.callBinary(jsrsession.getAsyncRemote(),websocket,buf,true);
+        }
+        catch (Throwable e)
+        {
+            onFatalError(e);
+        }
+    }
+
+    @Override
+    protected void onClose(CloseReason closereason)
+    {
+        events.callClose(websocket,closereason);
+    }
+
+    @Override
+    public void onConnect()
+    {
+        events.callOpen(websocket,config);
+    }
+
+    @Override
+    public void onError(Throwable cause)
+    {
+        try
+        {
+            events.callError(websocket,cause);
+        }
+        catch (Throwable e)
+        {
+            LOG.warn("Unable to call onError with cause", cause);
+            LOG.warn("Call to onError resulted in exception", e);
+        }
+    }
+
+    private void onFatalError(Throwable t)
+    {
+        onError(t);
+    }
+
+    @Override
+    public void onFrame(Frame frame)
+    {
+        /* Ignored in JSR-356 */
+    }
+
+    @Override
+    public void onInputStream(InputStream stream) throws IOException
+    {
+        try
+        {
+            events.callBinaryStream(jsrsession.getAsyncRemote(),websocket,stream);
+        }
+        catch (DecodeException e)
+        {
+            throw new RuntimeException("Unable decode input stream", e);
+        }
+    }
+
+    public void onPartialBinaryMessage(ByteBuffer buffer, boolean fin)
+    {
+        try
+        {
+            events.callBinary(jsrsession.getAsyncRemote(),websocket,buffer,fin);
+        }
+        catch (DecodeException e)
+        {
+            throw new RuntimeException("Unable decode partial binary message", e);
+        }
+    }
+
+    public void onPartialTextMessage(String message, boolean fin)
+    {
+        try
+        {
+            events.callText(jsrsession.getAsyncRemote(),websocket,message,fin);
+        }
+        catch (DecodeException e)
+        {
+            throw new RuntimeException("Unable decode partial text message", e);
+        }
+    }
+
+    @Override
+    public void onPing(ByteBuffer buffer)
+    {
+        // Call pong, as there is no "onPing" method in the JSR
+        events.callPong(jsrsession.getAsyncRemote(),websocket,buffer);
+    }
+    
+    @Override
+    public void onPong(ByteBuffer buffer)
+    {
+        events.callPong(jsrsession.getAsyncRemote(),websocket,buffer);
+    }
+
+    @Override
+    public void onReader(Reader reader) throws IOException
+    {
+        try
+        {
+            events.callTextStream(jsrsession.getAsyncRemote(),websocket,reader);
+        }
+        catch (DecodeException e)
+        {
+            throw new RuntimeException("Unable decode reader", e);
+        }
+    }
+
+    /**
+     * Entry point for all incoming text frames.
+     */
+    @Override
+    public void onTextFrame(ByteBuffer buffer, boolean fin) throws IOException
+    {
+        if (LOG.isDebugEnabled())
+        {
+            LOG.debug("onTextFrame({}, {})",BufferUtil.toDetailString(buffer),fin);
+            LOG.debug("events.hasText={}",events.hasText());
+            LOG.debug("events.hasTextStream={}",events.hasTextStream());
+        }
+
+        boolean handled = false;
+
+        if (events.hasText())
+        {
+            handled = true;
+            if (activeMessage == null)
+            {
+                if (events.isTextPartialSupported())
+                {
+                    // Partial Message Support
+                    if (LOG.isDebugEnabled())
+                    {
+                        LOG.debug("Partial Text Message: fin={}",fin);
+                    }
+                    activeMessage = new TextPartialOnMessage(this);
+                }
+                else
+                {
+                    // Whole Message Support
+                    if (LOG.isDebugEnabled())
+                    {
+                        LOG.debug("Whole Text Message");
+                    }
+                    activeMessage = new SimpleTextMessage(this);
+                }
+            }
+        }
+
+        if (events.hasTextStream())
+        {
+            handled = true;
+            // Streaming Message Support
+            if (activeMessage == null)
+            {
+                if (LOG.isDebugEnabled())
+                {
+                    LOG.debug("Text Message Writer");
+                }
+
+                final MessageReader stream = new MessageReader(new MessageInputStream());
+                activeMessage = stream;
+
+                // Always dispatch streaming read to another thread.
+                dispatch(new Runnable()
+                {
+                    @Override
+                    public void run()
+                    {
+                        try
+                        {
+                            events.callTextStream(jsrsession.getAsyncRemote(),websocket,stream);
+                        }
+                        catch (Throwable e)
+                        {
+                            onFatalError(e);
+                        }
+                    }
+                });
+            }
+        }
+
+        if (LOG.isDebugEnabled())
+        {
+            LOG.debug("handled = {}", handled);
+        }
+
+        // Process any active MessageAppender
+        if (handled && (activeMessage != null))
+        {
+            appendMessage(buffer,fin);
+        }
+    }
+
+    /**
+     * Entry point for whole text messages
+     */
+    @Override
+    public void onTextMessage(String message)
+    {
+        if (LOG.isDebugEnabled())
+        {
+            LOG.debug("onText({})",message);
+        }
+
+        try
+        {
+            // FIN is always true here
+            events.callText(jsrsession.getAsyncRemote(),websocket,message,true);
+        }
+        catch (Throwable e)
+        {
+            onFatalError(e);
+        }
+    }
+
+    @Override
+    public void setPathParameters(Map<String, String> pathParameters)
+    {
+        events.setPathParameters(pathParameters);
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s[websocket=%s]",this.getClass().getSimpleName(),websocket);
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/JsrEndpointEventDriver.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/JsrEndpointEventDriver.java
new file mode 100644
index 0000000..c96ffaa
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/JsrEndpointEventDriver.java
@@ -0,0 +1,283 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.endpoints;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.nio.ByteBuffer;
+import java.util.Map;
+
+import javax.websocket.CloseReason;
+import javax.websocket.Endpoint;
+import javax.websocket.MessageHandler;
+import javax.websocket.MessageHandler.Whole;
+import javax.websocket.PongMessage;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.common.message.MessageInputStream;
+import org.eclipse.jetty.websocket.common.message.MessageReader;
+import org.eclipse.jetty.websocket.jsr356.JsrPongMessage;
+import org.eclipse.jetty.websocket.jsr356.JsrSession;
+import org.eclipse.jetty.websocket.jsr356.MessageHandlerWrapper;
+import org.eclipse.jetty.websocket.jsr356.MessageType;
+import org.eclipse.jetty.websocket.jsr356.messages.BinaryPartialMessage;
+import org.eclipse.jetty.websocket.jsr356.messages.BinaryWholeMessage;
+import org.eclipse.jetty.websocket.jsr356.messages.TextPartialMessage;
+import org.eclipse.jetty.websocket.jsr356.messages.TextWholeMessage;
+
+/**
+ * EventDriver for websocket that extend from {@link javax.websocket.Endpoint}
+ */
+public class JsrEndpointEventDriver extends AbstractJsrEventDriver
+{
+    private static final Logger LOG = Log.getLogger(JsrEndpointEventDriver.class);
+
+    private final Endpoint endpoint;
+    private Map<String, String> pathParameters;
+
+    public JsrEndpointEventDriver(WebSocketPolicy policy, EndpointInstance endpointInstance)
+    {
+        super(policy,endpointInstance);
+        this.endpoint = (Endpoint)endpointInstance.getEndpoint();
+    }
+
+    @Override
+    public void init(JsrSession jsrsession)
+    {
+        jsrsession.setPathParameters(pathParameters);
+    }
+
+    @Override
+    public void onBinaryFrame(ByteBuffer buffer, boolean fin) throws IOException
+    {
+        if (activeMessage == null)
+        {
+            final MessageHandlerWrapper wrapper = jsrsession.getMessageHandlerWrapper(MessageType.BINARY);
+            if (wrapper == null)
+            {
+                if (LOG.isDebugEnabled())
+                {
+                    LOG.debug("No BINARY MessageHandler declared");
+                }
+                return;
+            }
+            if (wrapper.wantsPartialMessages())
+            {
+                activeMessage = new BinaryPartialMessage(wrapper);
+            }
+            else if (wrapper.wantsStreams())
+            {
+                final MessageInputStream stream = new MessageInputStream();
+                activeMessage = stream;
+                dispatch(new Runnable()
+                {
+                    @SuppressWarnings("unchecked")
+                    @Override
+                    public void run()
+                    {
+                        MessageHandler.Whole<InputStream> handler = (Whole<InputStream>)wrapper.getHandler();
+                        handler.onMessage(stream);
+                    }
+                });
+            }
+            else
+            {
+                activeMessage = new BinaryWholeMessage(this,wrapper);
+            }
+        }
+
+        activeMessage.appendFrame(buffer,fin);
+
+        if (fin)
+        {
+            activeMessage.messageComplete();
+            activeMessage = null;
+        }
+    }
+
+    @Override
+    public void onBinaryMessage(byte[] data)
+    {
+        /* Ignored, handled by BinaryWholeMessage */
+    }
+
+    @Override
+    protected void onClose(CloseReason closereason)
+    {
+        endpoint.onClose(this.jsrsession,closereason);
+    }
+
+    @Override
+    public void onConnect()
+    {
+        if (LOG.isDebugEnabled())
+        {
+            LOG.debug("onConnect({}, {})",jsrsession,config);
+        }
+
+        // Let unhandled exceptions flow out
+        endpoint.onOpen(jsrsession,config);
+    }
+
+    @Override
+    public void onError(Throwable cause)
+    {
+        LOG.warn(cause);
+        try
+        {
+            endpoint.onError(jsrsession,cause);
+        }
+        catch (Throwable t)
+        {
+            LOG.warn("Unable to report to onError due to exception",t);
+        }
+    }
+
+    @Override
+    public void onFrame(Frame frame)
+    {
+        /* Ignored, not supported by JSR-356 */
+    }
+
+    @Override
+    public void onInputStream(InputStream stream)
+    {
+        /* Ignored, handled by BinaryStreamMessage */
+    }
+
+    @Override
+    public void onReader(Reader reader)
+    {
+        /* Ignored, handled by TextStreamMessage */
+    }
+
+    @Override
+    public void onTextFrame(ByteBuffer buffer, boolean fin) throws IOException
+    {
+        if (activeMessage == null)
+        {
+            final MessageHandlerWrapper wrapper = jsrsession.getMessageHandlerWrapper(MessageType.TEXT);
+            if (wrapper == null)
+            {
+                if (LOG.isDebugEnabled())
+                {
+                    LOG.debug("No TEXT MessageHandler declared");
+                }
+                return;
+            }
+            if (wrapper.wantsPartialMessages())
+            {
+                activeMessage = new TextPartialMessage(wrapper);
+            }
+            else if (wrapper.wantsStreams())
+            {
+                final MessageReader stream = new MessageReader(new MessageInputStream());
+                activeMessage = stream;
+
+                dispatch(new Runnable()
+                {
+                    @SuppressWarnings("unchecked")
+                    @Override
+                    public void run()
+                    {
+                        MessageHandler.Whole<Reader> handler = (Whole<Reader>)wrapper.getHandler();
+                        handler.onMessage(stream);
+                    }
+                });
+            }
+            else
+            {
+                activeMessage = new TextWholeMessage(this,wrapper);
+            }
+        }
+
+        activeMessage.appendFrame(buffer,fin);
+
+        if (fin)
+        {
+            activeMessage.messageComplete();
+            activeMessage = null;
+        }
+    }
+
+    @Override
+    public void onTextMessage(String message)
+    {
+        /* Ignored, handled by TextWholeMessage */
+    }
+    
+    @Override
+    public void onPing(ByteBuffer buffer)
+    {
+        onPongMessage(buffer);
+    }
+
+    @Override
+    public void onPong(ByteBuffer buffer)
+    {
+        onPongMessage(buffer);
+    }
+
+    private void onPongMessage(ByteBuffer buffer)
+    {
+        final MessageHandlerWrapper wrapper = jsrsession.getMessageHandlerWrapper(MessageType.PONG);
+        if (wrapper == null)
+        {
+            if (LOG.isDebugEnabled())
+            {
+                LOG.debug("No PONG MessageHandler declared");
+            }
+            return;
+        }
+        
+        ByteBuffer pongBuf = null;
+        
+        if (BufferUtil.isEmpty(buffer))
+        {
+            pongBuf = BufferUtil.EMPTY_BUFFER;
+        }
+        else
+        {
+            pongBuf = ByteBuffer.allocate(buffer.remaining());
+            BufferUtil.put(buffer,pongBuf);
+            BufferUtil.flipToFlush(pongBuf,0);
+        }
+
+        @SuppressWarnings("unchecked")
+        Whole<PongMessage> pongHandler = (Whole<PongMessage>)wrapper.getHandler();
+        pongHandler.onMessage(new JsrPongMessage(pongBuf));
+    }
+    
+    @Override
+    public void setPathParameters(Map<String, String> pathParameters)
+    {
+        this.pathParameters = pathParameters;
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s[%s]",JsrEndpointEventDriver.class.getSimpleName(),endpoint.getClass().getName());
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/JsrEndpointImpl.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/JsrEndpointImpl.java
new file mode 100644
index 0000000..a51ad1a
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/JsrEndpointImpl.java
@@ -0,0 +1,57 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.endpoints;
+
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.common.events.EventDriver;
+import org.eclipse.jetty.websocket.common.events.EventDriverImpl;
+
+public class JsrEndpointImpl implements EventDriverImpl
+{
+    @Override
+    public EventDriver create(Object websocket, WebSocketPolicy policy)
+    {
+        if (!(websocket instanceof EndpointInstance))
+        {
+            throw new IllegalStateException(String.format("Websocket %s must be an %s",websocket.getClass().getName(),EndpointInstance.class.getName()));
+        }
+
+        return new JsrEndpointEventDriver(policy,(EndpointInstance)websocket);
+    }
+
+    @Override
+    public String describeRule()
+    {
+        return "class extends " + javax.websocket.Endpoint.class.getName();
+    }
+
+    @Override
+    public boolean supports(Object websocket)
+    {
+        if (!(websocket instanceof EndpointInstance))
+        {
+            return false;
+        }
+
+        EndpointInstance ei = (EndpointInstance)websocket;
+        Object endpoint = ei.getEndpoint();
+
+        return (endpoint instanceof javax.websocket.Endpoint);
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/JsrEventDriverFactory.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/JsrEventDriverFactory.java
new file mode 100644
index 0000000..f6a1744
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/JsrEventDriverFactory.java
@@ -0,0 +1,52 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.endpoints;
+
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.common.events.EventDriverFactory;
+import org.eclipse.jetty.websocket.jsr356.client.JsrClientEndpointImpl;
+
+public class JsrEventDriverFactory extends EventDriverFactory
+{
+    public JsrEventDriverFactory(WebSocketPolicy policy)
+    {
+        super(policy);
+
+        clearImplementations();
+        // Classes that extend javax.websocket.Endpoint
+        addImplementation(new JsrEndpointImpl());
+        // Classes annotated with @javax.websocket.ClientEndpoint
+        addImplementation(new JsrClientEndpointImpl());
+    }
+
+    /**
+     * Unwrap ConfiguredEndpoint for end-user.
+     */
+    @Override
+    protected String getClassName(Object websocket)
+    {
+        if (websocket instanceof EndpointInstance)
+        {
+            EndpointInstance ce = (EndpointInstance)websocket;
+            return ce.getEndpoint().getClass().getName();
+        }
+
+        return websocket.getClass().getName();
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/BinaryPartialMessage.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/BinaryPartialMessage.java
new file mode 100644
index 0000000..003afe7
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/BinaryPartialMessage.java
@@ -0,0 +1,80 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.messages;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+import javax.websocket.MessageHandler;
+import javax.websocket.MessageHandler.Partial;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.websocket.common.message.MessageAppender;
+import org.eclipse.jetty.websocket.jsr356.MessageHandlerWrapper;
+
+/**
+ * Partial BINARY MessageAppender for MessageHandler.Partial interface
+ */
+public class BinaryPartialMessage implements MessageAppender
+{
+    private final MessageHandlerWrapper msgWrapper;
+    private final MessageHandler.Partial<Object> partialHandler;
+
+    @SuppressWarnings("unchecked")
+    public BinaryPartialMessage(MessageHandlerWrapper wrapper)
+    {
+        this.msgWrapper = wrapper;
+        this.partialHandler = (Partial<Object>)wrapper.getHandler();
+    }
+
+    @Override
+    public void appendFrame(ByteBuffer payload, boolean isLast) throws IOException
+    {
+        // No decoders for Partial messages per JSR-356 (PFD1 spec)
+
+        // Supported Partial<> Type #1: ByteBuffer
+        if (msgWrapper.isMessageType(ByteBuffer.class))
+        {
+            partialHandler.onMessage(payload.slice(),isLast);
+            return;
+        }
+
+        // Supported Partial<> Type #2: byte[]
+        if (msgWrapper.isMessageType(byte[].class))
+        {
+            partialHandler.onMessage(BufferUtil.toArray(payload),isLast);
+            return;
+        }
+
+        StringBuilder err = new StringBuilder();
+        err.append(msgWrapper.getHandler().getClass());
+        err.append(" does not implement an expected ");
+        err.append(MessageHandler.Partial.class.getName());
+        err.append(" of type ");
+        err.append(ByteBuffer.class.getName());
+        err.append(" or byte[]");
+        throw new IllegalStateException(err.toString());
+    }
+
+    @Override
+    public void messageComplete()
+    {
+        /* nothing to do here */
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/BinaryPartialOnMessage.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/BinaryPartialOnMessage.java
new file mode 100644
index 0000000..d738242
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/BinaryPartialOnMessage.java
@@ -0,0 +1,64 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.messages;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.websocket.common.message.MessageAppender;
+import org.eclipse.jetty.websocket.jsr356.endpoints.JsrAnnotatedEventDriver;
+
+/**
+ * Partial BINARY MessageAppender for @{@link OnMessage} annotated methods
+ */
+public class BinaryPartialOnMessage implements MessageAppender
+{
+    private final JsrAnnotatedEventDriver driver;
+    private boolean finished;
+
+    public BinaryPartialOnMessage(JsrAnnotatedEventDriver driver)
+    {
+        this.driver = driver;
+        this.finished = false;
+    }
+
+    @Override
+    public void appendFrame(ByteBuffer payload, boolean isLast) throws IOException
+    {
+        if (finished)
+        {
+            throw new IOException("Cannot append to finished buffer");
+        }
+        if (payload == null)
+        {
+            driver.onPartialBinaryMessage(BufferUtil.EMPTY_BUFFER,isLast);
+        }
+        else
+        {
+            driver.onPartialBinaryMessage(payload,isLast);
+        }
+    }
+
+    @Override
+    public void messageComplete()
+    {
+        finished = true;
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/BinaryWholeMessage.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/BinaryWholeMessage.java
new file mode 100644
index 0000000..e660f79
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/BinaryWholeMessage.java
@@ -0,0 +1,67 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.messages;
+
+import javax.websocket.DecodeException;
+import javax.websocket.Decoder;
+import javax.websocket.Decoder.Binary;
+import javax.websocket.MessageHandler;
+import javax.websocket.MessageHandler.Whole;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.websocket.api.WebSocketException;
+import org.eclipse.jetty.websocket.common.events.EventDriver;
+import org.eclipse.jetty.websocket.common.message.SimpleBinaryMessage;
+import org.eclipse.jetty.websocket.jsr356.DecoderFactory;
+import org.eclipse.jetty.websocket.jsr356.MessageHandlerWrapper;
+
+public class BinaryWholeMessage extends SimpleBinaryMessage
+{
+    private final MessageHandlerWrapper msgWrapper;
+    private final MessageHandler.Whole<Object> wholeHandler;
+
+    @SuppressWarnings("unchecked")
+    public BinaryWholeMessage(EventDriver onEvent, MessageHandlerWrapper wrapper)
+    {
+        super(onEvent);
+        this.msgWrapper = wrapper;
+        this.wholeHandler = (Whole<Object>)wrapper.getHandler();
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public void messageComplete()
+    {
+        super.finished = true;
+
+        byte data[] = out.toByteArray();
+
+        DecoderFactory.Wrapper decoder = msgWrapper.getDecoder();
+        Decoder.Binary<Object> binaryDecoder = (Binary<Object>)decoder.getDecoder();
+        try
+        {
+            Object obj = binaryDecoder.decode(BufferUtil.toBuffer(data));
+            wholeHandler.onMessage(obj);
+        }
+        catch (DecodeException e)
+        {
+            throw new WebSocketException("Unable to decode binary data",e);
+        }
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/SendHandlerWriteCallback.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/SendHandlerWriteCallback.java
new file mode 100644
index 0000000..dfb60c8
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/SendHandlerWriteCallback.java
@@ -0,0 +1,46 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.messages;
+
+import javax.websocket.SendHandler;
+import javax.websocket.SendResult;
+
+import org.eclipse.jetty.websocket.api.WriteCallback;
+
+public class SendHandlerWriteCallback implements WriteCallback
+{
+    private final SendHandler sendHandler;
+
+    public SendHandlerWriteCallback(SendHandler sendHandler)
+    {
+        this.sendHandler = sendHandler;
+    }
+
+    @Override
+    public void writeFailed(Throwable x)
+    {
+        sendHandler.onResult(new SendResult(x));
+    }
+
+    @Override
+    public void writeSuccess()
+    {
+        sendHandler.onResult(new SendResult());
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/TextPartialMessage.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/TextPartialMessage.java
new file mode 100644
index 0000000..21d06b5
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/TextPartialMessage.java
@@ -0,0 +1,59 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.messages;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+import javax.websocket.MessageHandler;
+import javax.websocket.MessageHandler.Partial;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.websocket.common.message.MessageAppender;
+import org.eclipse.jetty.websocket.jsr356.MessageHandlerWrapper;
+
+/**
+ * Partial TEXT MessageAppender for MessageHandler.Partial interface
+ */
+public class TextPartialMessage implements MessageAppender
+{
+    @SuppressWarnings("unused")
+    private final MessageHandlerWrapper msgWrapper;
+    private final MessageHandler.Partial<String> partialHandler;
+
+    @SuppressWarnings("unchecked")
+    public TextPartialMessage(MessageHandlerWrapper wrapper)
+    {
+        this.msgWrapper = wrapper;
+        this.partialHandler = (Partial<String>)wrapper.getHandler();
+    }
+
+    @Override
+    public void appendFrame(ByteBuffer payload, boolean isLast) throws IOException
+    {
+        // No decoders for Partial messages per JSR-356 (PFD1 spec)
+        partialHandler.onMessage(BufferUtil.toUTF8String(payload.slice()),isLast);
+    }
+
+    @Override
+    public void messageComplete()
+    {
+        /* nothing to do here */
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/TextPartialOnMessage.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/TextPartialOnMessage.java
new file mode 100644
index 0000000..69d830e
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/TextPartialOnMessage.java
@@ -0,0 +1,65 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.messages;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.websocket.common.message.MessageAppender;
+import org.eclipse.jetty.websocket.jsr356.endpoints.JsrAnnotatedEventDriver;
+
+/**
+ * Partial TEXT MessageAppender for @{@link OnMessage} annotated methods
+ */
+public class TextPartialOnMessage implements MessageAppender
+{
+    private final JsrAnnotatedEventDriver driver;
+    private boolean finished;
+
+    public TextPartialOnMessage(JsrAnnotatedEventDriver driver)
+    {
+        this.driver = driver;
+        this.finished = false;
+    }
+
+    @Override
+    public void appendFrame(ByteBuffer payload, boolean isLast) throws IOException
+    {
+        if (finished)
+        {
+            throw new IOException("Cannot append to finished buffer");
+        }
+        if (payload == null)
+        {
+            driver.onPartialTextMessage("",isLast);
+        }
+        else
+        {
+            String text = BufferUtil.toUTF8String(payload);
+            driver.onPartialTextMessage(text,isLast);
+        }
+    }
+
+    @Override
+    public void messageComplete()
+    {
+        finished = true;
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/TextWholeMessage.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/TextWholeMessage.java
new file mode 100644
index 0000000..f20ed18
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/TextWholeMessage.java
@@ -0,0 +1,63 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.messages;
+
+import javax.websocket.DecodeException;
+import javax.websocket.Decoder;
+import javax.websocket.MessageHandler;
+import javax.websocket.MessageHandler.Whole;
+
+import org.eclipse.jetty.websocket.api.WebSocketException;
+import org.eclipse.jetty.websocket.common.events.EventDriver;
+import org.eclipse.jetty.websocket.common.message.SimpleTextMessage;
+import org.eclipse.jetty.websocket.jsr356.DecoderFactory;
+import org.eclipse.jetty.websocket.jsr356.MessageHandlerWrapper;
+
+public class TextWholeMessage extends SimpleTextMessage
+{
+    private final MessageHandlerWrapper msgWrapper;
+    private final MessageHandler.Whole<Object> wholeHandler;
+
+    @SuppressWarnings("unchecked")
+    public TextWholeMessage(EventDriver onEvent, MessageHandlerWrapper wrapper)
+    {
+        super(onEvent);
+        this.msgWrapper = wrapper;
+        this.wholeHandler = (Whole<Object>)wrapper.getHandler();
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public void messageComplete()
+    {
+        finished = true;
+
+        DecoderFactory.Wrapper decoder = msgWrapper.getDecoder();
+        Decoder.Text<Object> textDecoder = (Decoder.Text<Object>)decoder.getDecoder();
+        try
+        {
+            Object obj = textDecoder.decode(utf.toString());
+            wholeHandler.onMessage(obj);
+        }
+        catch (DecodeException e)
+        {
+            throw new WebSocketException("Unable to decode text data",e);
+        }
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/metadata/CoderMetadata.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/metadata/CoderMetadata.java
new file mode 100644
index 0000000..6333315
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/metadata/CoderMetadata.java
@@ -0,0 +1,67 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.metadata;
+
+import org.eclipse.jetty.websocket.jsr356.MessageType;
+
+/**
+ * The immutable base metadata for a coder ({@link Decoder} or {@link Encoder}
+ * 
+ * @param <T>
+ *            the specific type of coder ({@link Decoder} or {@link Encoder}
+ */
+public abstract class CoderMetadata<T>
+{
+    /** The class for the Coder */
+    private final Class<? extends T> coderClass;
+    /** The Class that the Decoder declares it decodes */
+    private final Class<?> objType;
+    /** The Basic type of message the decoder handles */
+    private final MessageType messageType;
+    /** Flag indicating if Decoder is for streaming (or not) */
+    private final boolean streamed;
+
+    public CoderMetadata(Class<? extends T> coderClass, Class<?> objType, MessageType messageType, boolean streamed)
+    {
+        this.objType = objType;
+        this.coderClass = coderClass;
+        this.messageType = messageType;
+        this.streamed = streamed;
+    }
+
+    public Class<? extends T> getCoderClass()
+    {
+        return this.coderClass;
+    }
+
+    public MessageType getMessageType()
+    {
+        return messageType;
+    }
+
+    public Class<?> getObjectType()
+    {
+        return objType;
+    }
+
+    public boolean isStreamed()
+    {
+        return streamed;
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/metadata/CoderMetadataSet.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/metadata/CoderMetadataSet.java
new file mode 100644
index 0000000..38a962d
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/metadata/CoderMetadataSet.java
@@ -0,0 +1,255 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.metadata;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * An durable collection of {@link CoderMetadata}.
+ * <p>
+ * This is a write-only collection, and cannot be modified once initialized.
+ * 
+ * @param <T>
+ *            The type of coder ({@link Decoder} or {@link Encoder}
+ * @param <M>
+ *            The metadata for the coder
+ */
+public abstract class CoderMetadataSet<T, M extends CoderMetadata<T>> implements Iterable<M>
+{
+    /**
+     * Collection of metadatas
+     */
+    private final List<M> metadatas;
+    /**
+     * Collection of declared Coder classes
+     */
+    private final List<Class<? extends T>> coders;
+    /**
+     * Mapping of supported Type to metadata list index
+     */
+    private final Map<Class<?>, Integer> typeMap;
+    /**
+     * Mapping of Coder class to list of supported metadata
+     */
+    private final Map<Class<? extends T>, List<Integer>> implMap;
+
+    protected CoderMetadataSet()
+    {
+        metadatas = new ArrayList<>();
+        coders = new ArrayList<>();
+        typeMap = new ConcurrentHashMap<>();
+        implMap = new ConcurrentHashMap<>();
+    }
+
+    public void add(Class<? extends T> coder)
+    {
+        List<M> metadatas = discover(coder);
+        trackMetadata(metadatas);
+    }
+
+    public List<M> addAll(Class<? extends T>[] coders)
+    {
+        List<M> metadatas = new ArrayList<>();
+
+        for (Class<? extends T> coder : coders)
+        {
+            metadatas.addAll(discover(coder));
+        }
+
+        trackMetadata(metadatas);
+        return metadatas;
+    }
+
+    public List<M> addAll(List<Class<? extends T>> coders)
+    {
+        List<M> metadatas = new ArrayList<>();
+
+        for (Class<? extends T> coder : coders)
+        {
+            metadatas.addAll(discover(coder));
+        }
+
+        trackMetadata(metadatas);
+        return metadatas;
+    }
+
+    /**
+     * Coder Specific discovery of Metadata for a specific coder.
+     * 
+     * @param coder
+     *            the coder to discover metadata in.
+     * @return the list of metadata discovered
+     * @throws InvalidWebSocketException
+     *             if unable to discover some metadata. Sucha as: a duplicate {@link CoderMetadata#getObjectType()} encountered, , or if unable to find the
+     *             concrete generic class reference for the coder, or if the provided coder is not valid per spec.
+     */
+    protected abstract List<M> discover(Class<? extends T> coder);
+
+    public Class<? extends T> getCoder(Class<?> type)
+    {
+        M metadata = getMetadataByType(type);
+        if (metadata == null)
+        {
+            return null;
+        }
+        return metadata.getCoderClass();
+    }
+
+    public List<Class<? extends T>> getList()
+    {
+        return coders;
+    }
+
+    public List<M> getMetadataByImplementation(Class<? extends T> clazz)
+    {
+        List<Integer> indexes = implMap.get(clazz);
+        if (indexes == null)
+        {
+            return null;
+        }
+        List<M> ret = new ArrayList<>();
+        for (Integer idx : indexes)
+        {
+            ret.add(metadatas.get(idx));
+        }
+        return ret;
+    }
+
+    public M getMetadataByType(Class<?> type)
+    {
+        Integer idx = typeMap.get(type);
+        if (idx == null)
+        {
+            // Quick lookup failed, try slower lookup via isAssignable instead
+            idx = getMetadataByAssignableType(type);
+            if (idx != null)
+            {
+                // add new entry map
+                typeMap.put(type,idx);
+            }
+        }
+
+        // If idx is STILL null, we've got no match
+        if (idx == null)
+        {
+            return null;
+        }
+        return metadatas.get(idx);
+    }
+
+    private Integer getMetadataByAssignableType(Class<?> type)
+    {
+        for (Map.Entry<Class<?>, Integer> entry : typeMap.entrySet())
+        {
+            if (entry.getKey().isAssignableFrom(type))
+            {
+                return entry.getValue();
+            }
+        }
+
+        return null;
+    }
+
+    @Override
+    public Iterator<M> iterator()
+    {
+        return metadatas.iterator();
+    }
+
+    @Override
+    public String toString()
+    {
+        StringBuilder builder = new StringBuilder();
+        builder.append(this.getClass().getSimpleName());
+        builder.append("[metadatas=");
+        builder.append(metadatas.size());
+        builder.append(",coders=");
+        builder.append(coders.size());
+        builder.append("]");
+        return builder.toString();
+    }
+
+    protected void trackMetadata(List<M> metadatas)
+    {
+        for (M metadata : metadatas)
+        {
+            trackMetadata(metadata);
+        }
+    }
+
+    protected void trackMetadata(M metadata)
+    {
+        synchronized (metadatas)
+        {
+            // Validate
+            boolean duplicate = false;
+
+            // Is this metadata already declared?
+            if (metadatas.contains(metadata))
+            {
+                duplicate = true;
+            }
+
+            // Is this type already declared?
+            Class<?> type = metadata.getObjectType();
+            if (typeMap.containsKey(type))
+            {
+                duplicate = true;
+            }
+
+            if (duplicate)
+            {
+                StringBuilder err = new StringBuilder();
+                err.append("Duplicate decoder for type: ");
+                err.append(type);
+                err.append(" (class ").append(metadata.getCoderClass().getName());
+
+                // Get prior one
+                M dup = getMetadataByType(type);
+                err.append(" duplicates ");
+                err.append(dup.getCoderClass().getName());
+                err.append(")");
+                throw new IllegalStateException(err.toString());
+            }
+
+            // Track
+            Class<? extends T> coderClass = metadata.getCoderClass();
+            int newidx = metadatas.size();
+            metadatas.add(metadata);
+            coders.add(coderClass);
+            typeMap.put(type,newidx);
+
+            List<Integer> indexes = implMap.get(coderClass);
+            if (indexes == null)
+            {
+                indexes = new ArrayList<>();
+            }
+            if (indexes.contains(newidx))
+            {
+                // possible duplicate, TODO: how?
+            }
+            indexes.add(newidx);
+            implMap.put(coderClass,indexes);
+        }
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/metadata/DecoderMetadata.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/metadata/DecoderMetadata.java
new file mode 100644
index 0000000..bd80616
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/metadata/DecoderMetadata.java
@@ -0,0 +1,34 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.metadata;
+
+import javax.websocket.Decoder;
+
+import org.eclipse.jetty.websocket.jsr356.MessageType;
+
+/**
+ * Immutable Metadata for a {@link Decoder}
+ */
+public class DecoderMetadata extends CoderMetadata<Decoder>
+{
+    public DecoderMetadata(Class<? extends Decoder> coderClass, Class<?> objType, MessageType messageType, boolean streamed)
+    {
+        super(coderClass,objType,messageType,streamed);
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/metadata/DecoderMetadataSet.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/metadata/DecoderMetadataSet.java
new file mode 100644
index 0000000..5cf2390
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/metadata/DecoderMetadataSet.java
@@ -0,0 +1,92 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.metadata;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.websocket.Decoder;
+
+import org.eclipse.jetty.websocket.api.InvalidWebSocketException;
+import org.eclipse.jetty.websocket.common.events.annotated.InvalidSignatureException;
+import org.eclipse.jetty.websocket.common.util.ReflectUtils;
+import org.eclipse.jetty.websocket.jsr356.MessageType;
+
+public class DecoderMetadataSet extends CoderMetadataSet<Decoder, DecoderMetadata>
+{
+    @Override
+    protected List<DecoderMetadata> discover(Class<? extends Decoder> decoder)
+    {
+        List<DecoderMetadata> metadatas = new ArrayList<>();
+
+        if (Decoder.Binary.class.isAssignableFrom(decoder))
+        {
+            Class<?> objType = getDecoderType(decoder,Decoder.Binary.class);
+            metadatas.add(new DecoderMetadata(decoder,objType,MessageType.BINARY,false));
+        }
+        if (Decoder.BinaryStream.class.isAssignableFrom(decoder))
+        {
+            Class<?> objType = getDecoderType(decoder,Decoder.BinaryStream.class);
+            metadatas.add(new DecoderMetadata(decoder,objType,MessageType.BINARY,true));
+        }
+        if (Decoder.Text.class.isAssignableFrom(decoder))
+        {
+            Class<?> objType = getDecoderType(decoder,Decoder.Text.class);
+            metadatas.add(new DecoderMetadata(decoder,objType,MessageType.TEXT,false));
+        }
+        if (Decoder.TextStream.class.isAssignableFrom(decoder))
+        {
+            Class<?> objType = getDecoderType(decoder,Decoder.TextStream.class);
+            metadatas.add(new DecoderMetadata(decoder,objType,MessageType.TEXT,true));
+        }
+
+        if (!ReflectUtils.isDefaultConstructable(decoder))
+        {
+            throw new InvalidSignatureException("Decoder must have public, no-args constructor: " + decoder.getName());
+        }
+
+        if (metadatas.size() <= 0)
+        {
+            throw new InvalidSignatureException("Not a valid Decoder class: " + decoder.getName());
+        }
+
+        return metadatas;
+    }
+
+    private Class<?> getDecoderType(Class<? extends Decoder> decoder, Class<?> interfaceClass)
+    {
+        Class<?> decoderClass = ReflectUtils.findGenericClassFor(decoder,interfaceClass);
+        if (decoderClass == null)
+        {
+            StringBuilder err = new StringBuilder();
+            err.append("Invalid type declared for interface ");
+            err.append(interfaceClass.getName());
+            err.append(" on class ");
+            err.append(decoder);
+            throw new InvalidWebSocketException(err.toString());
+        }
+        return decoderClass;
+    }
+
+    protected final void register(Class<?> type, Class<? extends Decoder> decoder, MessageType msgType, boolean streamed)
+    {
+        DecoderMetadata metadata = new DecoderMetadata(decoder,type,msgType,streamed);
+        trackMetadata(metadata);
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/metadata/DuplicateCoderException.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/metadata/DuplicateCoderException.java
new file mode 100644
index 0000000..331cc31
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/metadata/DuplicateCoderException.java
@@ -0,0 +1,39 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.metadata;
+
+import org.eclipse.jetty.websocket.api.InvalidWebSocketException;
+
+/**
+ * Thrown when a duplicate coder is encountered when attempting to identify a Endpoint's metadata ( {@link Decoder} or {@link Encoder})
+ */
+public class DuplicateCoderException extends InvalidWebSocketException
+{
+    private static final long serialVersionUID = -3049181444035417170L;
+
+    public DuplicateCoderException(String message)
+    {
+        super(message);
+    }
+
+    public DuplicateCoderException(String message, Throwable cause)
+    {
+        super(message,cause);
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/metadata/EncoderMetadata.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/metadata/EncoderMetadata.java
new file mode 100644
index 0000000..10019d4
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/metadata/EncoderMetadata.java
@@ -0,0 +1,34 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.metadata;
+
+import javax.websocket.Encoder;
+
+import org.eclipse.jetty.websocket.jsr356.MessageType;
+
+/**
+ * Immutable Metadata for a {@link Encoder}
+ */
+public class EncoderMetadata extends CoderMetadata<Encoder>
+{
+    public EncoderMetadata(Class<? extends Encoder> coderClass, Class<?> objType, MessageType messageType, boolean streamed)
+    {
+        super(coderClass,objType,messageType,streamed);
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/metadata/EncoderMetadataSet.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/metadata/EncoderMetadataSet.java
new file mode 100644
index 0000000..f5df029
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/metadata/EncoderMetadataSet.java
@@ -0,0 +1,92 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.metadata;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.websocket.Encoder;
+
+import org.eclipse.jetty.websocket.api.InvalidWebSocketException;
+import org.eclipse.jetty.websocket.common.events.annotated.InvalidSignatureException;
+import org.eclipse.jetty.websocket.common.util.ReflectUtils;
+import org.eclipse.jetty.websocket.jsr356.MessageType;
+
+public class EncoderMetadataSet extends CoderMetadataSet<Encoder, EncoderMetadata>
+{
+    @Override
+    protected List<EncoderMetadata> discover(Class<? extends Encoder> encoder)
+    {
+        List<EncoderMetadata> metadatas = new ArrayList<>();
+
+        if (Encoder.Binary.class.isAssignableFrom(encoder))
+        {
+            Class<?> objType = getEncoderType(encoder,Encoder.Binary.class);
+            metadatas.add(new EncoderMetadata(encoder,objType,MessageType.BINARY,false));
+        }
+        if (Encoder.BinaryStream.class.isAssignableFrom(encoder))
+        {
+            Class<?> objType = getEncoderType(encoder,Encoder.BinaryStream.class);
+            metadatas.add(new EncoderMetadata(encoder,objType,MessageType.BINARY,true));
+        }
+        if (Encoder.Text.class.isAssignableFrom(encoder))
+        {
+            Class<?> objType = getEncoderType(encoder,Encoder.Text.class);
+            metadatas.add(new EncoderMetadata(encoder,objType,MessageType.TEXT,false));
+        }
+        if (Encoder.TextStream.class.isAssignableFrom(encoder))
+        {
+            Class<?> objType = getEncoderType(encoder,Encoder.TextStream.class);
+            metadatas.add(new EncoderMetadata(encoder,objType,MessageType.TEXT,true));
+        }
+
+        if (!ReflectUtils.isDefaultConstructable(encoder))
+        {
+            throw new InvalidSignatureException("Encoder must have public, no-args constructor: " + encoder.getName());
+        }
+
+        if (metadatas.size() <= 0)
+        {
+            throw new InvalidSignatureException("Not a valid Encoder class: " + encoder.getName() + " implements no " + Encoder.class.getName() + " interfaces");
+        }
+
+        return metadatas;
+    }
+
+    private Class<?> getEncoderType(Class<? extends Encoder> encoder, Class<?> interfaceClass)
+    {
+        Class<?> decoderClass = ReflectUtils.findGenericClassFor(encoder,interfaceClass);
+        if (decoderClass == null)
+        {
+            StringBuilder err = new StringBuilder();
+            err.append("Invalid type declared for interface ");
+            err.append(interfaceClass.getName());
+            err.append(" on class ");
+            err.append(encoder);
+            throw new InvalidWebSocketException(err.toString());
+        }
+        return decoderClass;
+    }
+
+    protected final void register(Class<?> type, Class<? extends Encoder> encoder, MessageType msgType, boolean streamed)
+    {
+        EncoderMetadata metadata = new EncoderMetadata(encoder,type,msgType,streamed);
+        trackMetadata(metadata);
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/metadata/EndpointMetadata.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/metadata/EndpointMetadata.java
new file mode 100644
index 0000000..c42b59e
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/metadata/EndpointMetadata.java
@@ -0,0 +1,28 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.metadata;
+
+public interface EndpointMetadata
+{
+    public DecoderMetadataSet getDecoders();
+
+    public EncoderMetadataSet getEncoders();
+
+    public Class<?> getEndpointClass();
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/metadata/MessageHandlerMetadata.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/metadata/MessageHandlerMetadata.java
new file mode 100644
index 0000000..3d4efd5
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/metadata/MessageHandlerMetadata.java
@@ -0,0 +1,71 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.metadata;
+
+import javax.websocket.MessageHandler;
+
+/**
+ * An immutable metadata for a {@link MessageHandler}, representing a single interface on a message handling class.
+ * <p>
+ * A message handling class can contain more than 1 valid {@link MessageHandler} interface, this will result in multiple {@link MessageHandlerMetadata}
+ * instances, each tracking one of the {@link MessageHandler} interfaces declared.
+ */
+public class MessageHandlerMetadata
+{
+    /**
+     * The implemented MessageHandler class.
+     * <p>
+     * Commonly a end-user provided class, with 1 or more implemented {@link MessageHandler} interfaces
+     */
+    private final Class<? extends MessageHandler> handlerClass;
+    /**
+     * Indicator if this is a {@link MessageHandler.Partial} or {@link MessageHandler.Whole} interface.
+     * <p>
+     * True for MessageHandler.Partial, other wise its a MessageHandler.Whole
+     */
+    private final boolean isPartialSupported;
+    /**
+     * The class type that this specific interface's generic implements.
+     * <p>
+     * Or said another way, the first parameter type on this interface's onMessage() method.
+     */
+    private final Class<?> messageClass;
+
+    public MessageHandlerMetadata(Class<? extends MessageHandler> handlerClass, Class<?> messageClass, boolean partial)
+    {
+        this.handlerClass = handlerClass;
+        this.isPartialSupported = partial;
+        this.messageClass = messageClass;
+    }
+
+    public Class<? extends MessageHandler> getHandlerClass()
+    {
+        return handlerClass;
+    }
+
+    public Class<?> getMessageClass()
+    {
+        return messageClass;
+    }
+
+    public boolean isPartialSupported()
+    {
+        return isPartialSupported;
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/utils/Primitives.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/utils/Primitives.java
new file mode 100644
index 0000000..51737bf
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/utils/Primitives.java
@@ -0,0 +1,77 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.utils;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+public class Primitives
+{
+    private static final Map<Class<?>, Class<?>> PRIMITIVE_CLASS_MAP;
+    private static final Map<Class<?>, Class<?>> CLASS_PRIMITIVE_MAP;
+
+    static
+    {
+        Map<Class<?>, Class<?>> primitives = new HashMap<>();
+
+        // Map of classes to primitive types
+        primitives.put(Boolean.class,Boolean.TYPE);
+        primitives.put(Byte.class,Byte.TYPE);
+        primitives.put(Character.class,Character.TYPE);
+        primitives.put(Double.class,Double.TYPE);
+        primitives.put(Float.class,Float.TYPE);
+        primitives.put(Integer.class,Integer.TYPE);
+        primitives.put(Long.class,Long.TYPE);
+        primitives.put(Short.class,Short.TYPE);
+        primitives.put(Void.class,Void.TYPE);
+
+        CLASS_PRIMITIVE_MAP = Collections.unmodifiableMap(primitives);
+
+        // Map of primitive types to classes
+        Map<Class<?>, Class<?>> types = new HashMap<>();
+        for (Map.Entry<Class<?>, Class<?>> classEntry : primitives.entrySet())
+        {
+            types.put(classEntry.getValue(),classEntry.getKey());
+        }
+
+        PRIMITIVE_CLASS_MAP = Collections.unmodifiableMap(types);
+    }
+
+    public static Class<?> getPrimitiveClass(Class<?> primitiveType)
+    {
+        return PRIMITIVE_CLASS_MAP.get(primitiveType);
+    }
+
+    public static Set<Class<?>> getPrimitiveClasses()
+    {
+        return CLASS_PRIMITIVE_MAP.keySet();
+    }
+
+    public static Set<Class<?>> getPrimitives()
+    {
+        return PRIMITIVE_CLASS_MAP.keySet();
+    }
+
+    public static Class<?> getPrimitiveType(Class<?> primitiveClass)
+    {
+        return CLASS_PRIMITIVE_MAP.get(primitiveClass);
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/resources/META-INF/services/javax.websocket.ContainerProvider b/jetty-websocket/javax-websocket-client-impl/src/main/resources/META-INF/services/javax.websocket.ContainerProvider
new file mode 100644
index 0000000..e6dffe7
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/resources/META-INF/services/javax.websocket.ContainerProvider
@@ -0,0 +1 @@
+org.eclipse.jetty.websocket.jsr356.JettyClientContainerProvider
\ No newline at end of file
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/AnnotatedEchoClient.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/AnnotatedEchoClient.java
new file mode 100644
index 0000000..c7a194c
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/AnnotatedEchoClient.java
@@ -0,0 +1,60 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356;
+
+import java.io.IOException;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.CloseReason;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+
+ at ClientEndpoint
+public class AnnotatedEchoClient
+{
+    private Session session = null;
+    public CloseReason close = null;
+    public MessageQueue messageQueue = new MessageQueue();
+
+    public void onClose(CloseReason close)
+    {
+        this.close = close;
+    }
+
+    @OnMessage
+    public void onMessage(String message)
+    {
+        this.messageQueue.offer(message);
+    }
+
+    @OnOpen
+    public void onOpen(Session session)
+    {
+        this.session = session;
+    }
+
+    public void sendText(String text) throws IOException
+    {
+        if (session != null)
+        {
+            session.getBasicRemote().sendText(text);
+        }
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/AnnotatedEchoTest.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/AnnotatedEchoTest.java
new file mode 100644
index 0000000..140bda5
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/AnnotatedEchoTest.java
@@ -0,0 +1,89 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356;
+
+import java.net.URI;
+import java.util.concurrent.TimeUnit;
+
+import javax.websocket.ContainerProvider;
+import javax.websocket.Session;
+import javax.websocket.WebSocketContainer;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class AnnotatedEchoTest
+{
+    private static Server server;
+    private static EchoHandler handler;
+    private static URI serverUri;
+
+    @BeforeClass
+    public static void startServer() throws Exception
+    {
+        server = new Server();
+        ServerConnector connector = new ServerConnector(server);
+        server.addConnector(connector);
+
+        handler = new EchoHandler();
+
+        ContextHandler context = new ContextHandler();
+        context.setContextPath("/");
+        context.setHandler(handler);
+        server.setHandler(context);
+
+        // Start Server
+        server.start();
+
+        String host = connector.getHost();
+        if (host == null)
+        {
+            host = "localhost";
+        }
+        int port = connector.getLocalPort();
+        serverUri = new URI(String.format("ws://%s:%d/",host,port));
+    }
+
+    @AfterClass
+    public static void stopServer()
+    {
+        try
+        {
+            server.stop();
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace(System.err);
+        }
+    }
+
+    @Test
+    public void testEcho() throws Exception
+    {
+        WebSocketContainer container = ContainerProvider.getWebSocketContainer();
+        AnnotatedEchoClient echoer = new AnnotatedEchoClient();
+        Session session = container.connectToServer(echoer,serverUri);
+        session.getBasicRemote().sendText("Echo");
+        echoer.messageQueue.awaitMessages(1,1000,TimeUnit.MILLISECONDS);
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/AnnotatedEndpointClient.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/AnnotatedEndpointClient.java
new file mode 100644
index 0000000..38a6681
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/AnnotatedEndpointClient.java
@@ -0,0 +1,60 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356;
+
+import java.nio.ByteBuffer;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.EndpointConfig;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+
+import org.eclipse.jetty.websocket.jsr356.decoders.DateDecoder;
+import org.eclipse.jetty.websocket.jsr356.encoders.TimeEncoder;
+
+ at ClientEndpoint(
+        subprotocols = { "chat", "echo" },
+        decoders = { DateDecoder.class },
+        encoders = { TimeEncoder.class },
+        configurator = AnnotatedEndpointConfigurator.class)
+public class AnnotatedEndpointClient
+{
+    public Session session;
+    public EndpointConfig config;
+
+    @OnOpen
+    public void onOpen(Session session, EndpointConfig config)
+    {
+        this.session = session;
+        this.config = config;
+    }
+
+    @OnMessage(maxMessageSize = 111222)
+    public void onText(String msg)
+    {
+        /* do nothing */
+    }
+
+    @OnMessage(maxMessageSize = 333444)
+    public void onBinary(ByteBuffer buf)
+    {
+        /* do nothing */
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/AnnotatedEndpointConfigTest.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/AnnotatedEndpointConfigTest.java
new file mode 100644
index 0000000..bd3fd2c
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/AnnotatedEndpointConfigTest.java
@@ -0,0 +1,189 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356;
+
+import static org.hamcrest.Matchers.*;
+
+import java.net.URI;
+import java.util.List;
+
+import javax.websocket.ClientEndpointConfig;
+import javax.websocket.ContainerProvider;
+import javax.websocket.Decoder;
+import javax.websocket.Encoder;
+import javax.websocket.EndpointConfig;
+import javax.websocket.Session;
+import javax.websocket.WebSocketContainer;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.websocket.jsr356.decoders.DateDecoder;
+import org.eclipse.jetty.websocket.jsr356.encoders.TimeEncoder;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class AnnotatedEndpointConfigTest
+{
+    private static Server server;
+    private static EchoHandler handler;
+    private static URI serverUri;
+    private static ClientEndpointConfig ceconfig;
+    private static EndpointConfig config;
+    private static Session session;
+    private static AnnotatedEndpointClient socket;
+
+    @BeforeClass
+    public static void startEnv() throws Exception
+    {
+        // Server
+
+        server = new Server();
+        ServerConnector connector = new ServerConnector(server);
+        server.addConnector(connector);
+
+        handler = new EchoHandler();
+
+        ContextHandler context = new ContextHandler();
+        context.setContextPath("/");
+        context.setHandler(handler);
+        server.setHandler(context);
+
+        // Start Server
+        server.start();
+
+        String host = connector.getHost();
+        if (host == null)
+        {
+            host = "localhost";
+        }
+        int port = connector.getLocalPort();
+        serverUri = new URI(String.format("ws://%s:%d/",host,port));
+
+        // Connect client
+
+        WebSocketContainer container = ContainerProvider.getWebSocketContainer();
+        socket = new AnnotatedEndpointClient();
+
+        session = container.connectToServer(socket,serverUri);
+        Assert.assertThat("Session",session,notNullValue());
+
+        config = socket.config;
+        Assert.assertThat("EndpointConfig",config,notNullValue());
+        Assert.assertThat("EndpointConfig",config,instanceOf(ClientEndpointConfig.class));
+
+        ceconfig = (ClientEndpointConfig)config;
+        Assert.assertThat("EndpointConfig",ceconfig,notNullValue());
+    }
+
+    @AfterClass
+    public static void stopEnv()
+    {
+        // Disconnect client
+        try
+        {
+            session.close();
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace(System.err);
+        }
+
+        // Stop server
+        try
+        {
+            server.stop();
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace(System.err);
+        }
+    }
+
+    @Test
+    public void testTextMax() throws Exception
+    {
+        Assert.assertThat("Client Text Max",
+                socket.session.getMaxTextMessageBufferSize(),
+                is(111222));
+    }
+    
+    @Test
+    public void testBinaryMax() throws Exception
+    {
+        Assert.assertThat("Client Binary Max",
+                socket.session.getMaxBinaryMessageBufferSize(),
+                is(333444));
+    }
+    
+    @Test
+    public void testSubProtocols() throws Exception
+    {
+        List<String> subprotocols = ceconfig.getPreferredSubprotocols();
+        Assert.assertThat("Client Preferred SubProtocols",subprotocols,contains("chat","echo"));
+    }
+
+    @Test
+    public void testDecoders() throws Exception
+    {
+        List<Class<? extends Decoder>> decoders = config.getDecoders();
+        Assert.assertThat("Decoders",decoders,notNullValue());
+
+        Class<?> expectedClass = DateDecoder.class;
+        boolean hasExpectedDecoder = false;
+        for (Class<? extends Decoder> decoder : decoders)
+        {
+            if (expectedClass.isAssignableFrom(decoder))
+            {
+                hasExpectedDecoder = true;
+            }
+        }
+
+        Assert.assertTrue("Client Decoders has " + expectedClass.getName(),hasExpectedDecoder);
+    }
+
+    @Test
+    public void testEncoders() throws Exception
+    {
+        List<Class<? extends Encoder>> encoders = config.getEncoders();
+        Assert.assertThat("Encoders",encoders,notNullValue());
+
+        Class<?> expectedClass = TimeEncoder.class;
+        boolean hasExpectedEncoder = false;
+        for (Class<? extends Encoder> encoder : encoders)
+        {
+            if (expectedClass.isAssignableFrom(encoder))
+            {
+                hasExpectedEncoder = true;
+            }
+        }
+
+        Assert.assertTrue("Client Encoders has " + expectedClass.getName(),hasExpectedEncoder);
+    }
+
+    @Test
+    public void testConfigurator() throws Exception
+    {
+        ClientEndpointConfig ceconfig = (ClientEndpointConfig)config;
+
+        Assert.assertThat("Client Configurator",ceconfig.getConfigurator(),instanceOf(AnnotatedEndpointConfigurator.class));
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/AnnotatedEndpointConfigurator.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/AnnotatedEndpointConfigurator.java
new file mode 100644
index 0000000..1864479
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/AnnotatedEndpointConfigurator.java
@@ -0,0 +1,34 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356;
+
+import java.util.Collections;
+
+import javax.websocket.ClientEndpointConfig;
+import javax.websocket.HandshakeResponse;
+
+public class AnnotatedEndpointConfigurator extends ClientEndpointConfig.Configurator
+{
+    @Override
+    public void afterResponse(HandshakeResponse hr)
+    {
+        hr.getHeaders().put("X-Test",Collections.singletonList("Extra"));
+        super.afterResponse(hr);
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/ConfiguratorTest.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/ConfiguratorTest.java
new file mode 100644
index 0000000..bbea2d8
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/ConfiguratorTest.java
@@ -0,0 +1,133 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356;
+
+import static org.hamcrest.Matchers.*;
+
+import java.net.URI;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import javax.websocket.ClientEndpointConfig;
+import javax.websocket.ContainerProvider;
+import javax.websocket.HandshakeResponse;
+import javax.websocket.Session;
+import javax.websocket.WebSocketContainer;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * Tests of {@link Configurator}
+ */
+public class ConfiguratorTest
+{
+    public class TrackingConfigurator extends ClientEndpointConfig.Configurator
+    {
+        public HandshakeResponse response;
+        public Map<String, List<String>> request;
+
+        @Override
+        public void afterResponse(HandshakeResponse hr)
+        {
+            this.response = hr;
+        }
+
+        @Override
+        public void beforeRequest(Map<String, List<String>> headers)
+        {
+            this.request = headers;
+        }
+    }
+
+    private static Server server;
+    private static EchoHandler handler;
+    private static URI serverUri;
+
+    @BeforeClass
+    public static void startServer() throws Exception
+    {
+        server = new Server();
+        ServerConnector connector = new ServerConnector(server);
+        server.addConnector(connector);
+
+        handler = new EchoHandler();
+
+        ContextHandler context = new ContextHandler();
+        context.setContextPath("/");
+        context.setHandler(handler);
+        server.setHandler(context);
+
+        // Start Server
+        server.start();
+
+        String host = connector.getHost();
+        if (host == null)
+        {
+            host = "localhost";
+        }
+        int port = connector.getLocalPort();
+        serverUri = new URI(String.format("ws://%s:%d/",host,port));
+    }
+
+    @AfterClass
+    public static void stopServer()
+    {
+        try
+        {
+            server.stop();
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace(System.err);
+        }
+    }
+
+    @Test
+    public void testEndpointHandshakeInfo() throws Exception
+    {
+        WebSocketContainer container = ContainerProvider.getWebSocketContainer();
+        EndpointEchoClient echoer = new EndpointEchoClient();
+
+        // Build Config
+        ClientEndpointConfig.Builder cfgbldr = ClientEndpointConfig.Builder.create();
+        TrackingConfigurator configurator = new TrackingConfigurator();
+        cfgbldr.configurator(configurator);
+        ClientEndpointConfig config = cfgbldr.build();
+
+        // Connect
+        Session session = container.connectToServer(echoer,config,serverUri);
+
+        // Send Simple Message
+        session.getBasicRemote().sendText("Echo");
+
+        // Wait for echo
+        echoer.textCapture.messageQueue.awaitMessages(1,1000,TimeUnit.MILLISECONDS);
+
+        // Validate client side configurator use
+        Assert.assertThat("configurator.request",configurator.request,notNullValue());
+        Assert.assertThat("configurator.response",configurator.response,notNullValue());
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/CookiesTest.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/CookiesTest.java
new file mode 100644
index 0000000..3597cca
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/CookiesTest.java
@@ -0,0 +1,179 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356;
+
+import java.net.HttpCookie;
+import java.net.URI;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import javax.websocket.ClientEndpointConfig;
+import javax.websocket.ContainerProvider;
+import javax.websocket.Endpoint;
+import javax.websocket.EndpointConfig;
+import javax.websocket.HandshakeResponse;
+import javax.websocket.Session;
+import javax.websocket.WebSocketContainer;
+
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
+import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class CookiesTest
+{
+    private Server server;
+    private ServerConnector connector;
+
+    protected void startServer(Handler handler) throws Exception
+    {
+        server = new Server();
+        connector = new ServerConnector(server);
+        server.addConnector(connector);
+
+        ContextHandler context = new ContextHandler();
+        context.setContextPath("/");
+        context.setHandler(handler);
+        server.setHandler(context);
+
+        server.start();
+    }
+
+    @After
+    public void stopServer() throws Exception
+    {
+        server.stop();
+    }
+
+    @Test
+    public void testCookiesAreSentToServer() throws Exception
+    {
+        final String cookieName = "name";
+        final String cookieValue = "value";
+        final String cookieString = cookieName + "=" + cookieValue;
+        startServer(new EchoHandler()
+        {
+            @Override
+            public Object createWebSocket(ServletUpgradeRequest request, ServletUpgradeResponse response)
+            {
+                List<HttpCookie> cookies = request.getCookies();
+                Assert.assertNotNull(cookies);
+                Assert.assertEquals(1, cookies.size());
+                HttpCookie cookie = cookies.get(0);
+                Assert.assertEquals(cookieName, cookie.getName());
+                Assert.assertEquals(cookieValue, cookie.getValue());
+
+                Map<String, List<String>> headers = request.getHeaders();
+                // Test case insensitivity
+                Assert.assertTrue(headers.containsKey("cookie"));
+                List<String> values = headers.get("Cookie");
+                Assert.assertNotNull(values);
+                Assert.assertEquals(1, values.size());
+                Assert.assertEquals(cookieString, values.get(0));
+
+                return super.createWebSocket(request, response);
+            }
+        });
+
+        WebSocketContainer container = ContainerProvider.getWebSocketContainer();
+
+        ClientEndpointConfig.Builder builder = ClientEndpointConfig.Builder.create();
+        builder.configurator(new ClientEndpointConfig.Configurator()
+        {
+            @Override
+            public void beforeRequest(Map<String, List<String>> headers)
+            {
+                headers.put("Cookie", Collections.singletonList(cookieString));
+            }
+        });
+        ClientEndpointConfig config = builder.build();
+
+        Endpoint endPoint = new Endpoint()
+        {
+            @Override
+            public void onOpen(Session session, EndpointConfig config)
+            {
+            }
+        };
+
+        Session session = container.connectToServer(endPoint, config, URI.create("ws://localhost:" + connector.getLocalPort()));
+        session.close();
+    }
+
+    @Test
+    public void testCookiesAreSentToClient() throws Exception
+    {
+        final String cookieName = "name";
+        final String cookieValue = "value";
+        final String cookieDomain = "domain";
+        final String cookiePath = "/path";
+        startServer(new EchoHandler()
+        {
+            @Override
+            public Object createWebSocket(ServletUpgradeRequest request, ServletUpgradeResponse response)
+            {
+                String cookieString = cookieName + "=" + cookieValue + ";Domain=" + cookieDomain + ";Path=" + cookiePath;
+                response.getHeaders().put("Set-Cookie", Collections.singletonList(cookieString));
+                return super.createWebSocket(request, response);
+            }
+        });
+
+        WebSocketContainer container = ContainerProvider.getWebSocketContainer();
+
+        ClientEndpointConfig.Builder builder = ClientEndpointConfig.Builder.create();
+        builder.configurator(new ClientEndpointConfig.Configurator()
+        {
+            @Override
+            public void afterResponse(HandshakeResponse response)
+            {
+                Map<String, List<String>> headers = response.getHeaders();
+                // Test case insensitivity
+                Assert.assertTrue(headers.containsKey("set-cookie"));
+                List<String> values = headers.get("Set-Cookie");
+                Assert.assertNotNull(values);
+                Assert.assertEquals(1, values.size());
+
+                List<HttpCookie> cookies = HttpCookie.parse(values.get(0));
+                Assert.assertEquals(1, cookies.size());
+                HttpCookie cookie = cookies.get(0);
+                Assert.assertEquals(cookieName, cookie.getName());
+                Assert.assertEquals(cookieValue, cookie.getValue());
+                Assert.assertEquals(cookieDomain, cookie.getDomain());
+                Assert.assertEquals(cookiePath, cookie.getPath());
+            }
+        });
+        ClientEndpointConfig config = builder.build();
+
+        Endpoint endPoint = new Endpoint()
+        {
+            @Override
+            public void onOpen(Session session, EndpointConfig config)
+            {
+            }
+        };
+
+        Session session = container.connectToServer(endPoint, config, URI.create("ws://localhost:" + connector.getLocalPort()));
+        session.close();
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/DecoderFactoryTest.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/DecoderFactoryTest.java
new file mode 100644
index 0000000..07bc9ab
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/DecoderFactoryTest.java
@@ -0,0 +1,107 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356;
+
+import static org.hamcrest.Matchers.is;
+
+import java.nio.ByteBuffer;
+import java.util.Date;
+
+import javax.websocket.Decoder;
+
+import org.eclipse.jetty.websocket.jsr356.decoders.ByteArrayDecoder;
+import org.eclipse.jetty.websocket.jsr356.decoders.ByteBufferDecoder;
+import org.eclipse.jetty.websocket.jsr356.decoders.DateDecoder;
+import org.eclipse.jetty.websocket.jsr356.decoders.IntegerDecoder;
+import org.eclipse.jetty.websocket.jsr356.decoders.LongDecoder;
+import org.eclipse.jetty.websocket.jsr356.decoders.PrimitiveDecoderMetadataSet;
+import org.eclipse.jetty.websocket.jsr356.decoders.StringDecoder;
+import org.eclipse.jetty.websocket.jsr356.metadata.DecoderMetadata;
+import org.eclipse.jetty.websocket.jsr356.metadata.DecoderMetadataSet;
+import org.eclipse.jetty.websocket.jsr356.samples.Fruit;
+import org.eclipse.jetty.websocket.jsr356.samples.FruitDecoder;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class DecoderFactoryTest
+{
+    private DecoderMetadataSet metadatas;
+    private DecoderFactory factory;
+
+    private void assertMetadataFor(Class<?> type, Class<? extends Decoder> expectedDecoderClass, MessageType expectedType)
+    {
+        DecoderMetadata metadata = factory.getMetadataFor(type);
+        Assert.assertEquals("metadata.coderClass",metadata.getCoderClass(),expectedDecoderClass);
+        Assert.assertThat("metadata.messageType",metadata.getMessageType(),is(expectedType));
+        Assert.assertEquals("metadata.objectType",metadata.getObjectType(),type);
+    }
+
+    @Before
+    public void initDecoderFactory()
+    {
+        DecoderFactory primitivesFactory = new DecoderFactory(PrimitiveDecoderMetadataSet.INSTANCE);
+        metadatas = new DecoderMetadataSet();
+        factory = new DecoderFactory(metadatas,primitivesFactory);
+    }
+
+    @Test
+    public void testGetMetadataForByteArray()
+    {
+        assertMetadataFor(byte[].class,ByteArrayDecoder.class,MessageType.BINARY);
+    }
+
+    @Test
+    public void testGetMetadataForByteBuffer()
+    {
+        assertMetadataFor(ByteBuffer.class,ByteBufferDecoder.class,MessageType.BINARY);
+    }
+
+    @Test
+    public void testGetMetadataForDate()
+    {
+        metadatas.add(DateDecoder.class);
+        assertMetadataFor(Date.class,DateDecoder.class,MessageType.TEXT);
+    }
+
+    @Test
+    public void testGetMetadataForFruit()
+    {
+        metadatas.add(FruitDecoder.class);
+        assertMetadataFor(Fruit.class,FruitDecoder.class,MessageType.TEXT);
+    }
+
+    @Test
+    public void testGetMetadataForInteger()
+    {
+        assertMetadataFor(Integer.TYPE,IntegerDecoder.class,MessageType.TEXT);
+    }
+
+    @Test
+    public void testGetMetadataForLong()
+    {
+        assertMetadataFor(Long.TYPE,LongDecoder.class,MessageType.TEXT);
+    }
+
+    @Test
+    public void testGetStringDecoder()
+    {
+        assertMetadataFor(String.class,StringDecoder.class,MessageType.TEXT);
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/DecoderReaderManySmallTest.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/DecoderReaderManySmallTest.java
new file mode 100644
index 0000000..ec1d09c
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/DecoderReaderManySmallTest.java
@@ -0,0 +1,219 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.CloseReason;
+import javax.websocket.ContainerProvider;
+import javax.websocket.DecodeException;
+import javax.websocket.Decoder;
+import javax.websocket.EndpointConfig;
+import javax.websocket.OnClose;
+import javax.websocket.OnMessage;
+import javax.websocket.WebSocketContainer;
+
+import org.eclipse.jetty.toolchain.test.EventQueue;
+import org.eclipse.jetty.toolchain.test.TestTracker;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
+import org.eclipse.jetty.websocket.common.test.BlockheadServer;
+import org.eclipse.jetty.websocket.common.test.BlockheadServer.ServerConnection;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class DecoderReaderManySmallTest
+{
+    public static class EventId
+    {
+        public int eventId;
+    }
+
+    public static class EventIdDecoder implements Decoder.TextStream<EventId>
+    {
+        @Override
+        public void init(EndpointConfig config)
+        {
+        }
+
+        @Override
+        public void destroy()
+        {
+        }
+
+        @Override
+        public EventId decode(Reader reader) throws DecodeException, IOException
+        {
+            EventId id = new EventId();
+            try (BufferedReader buf = new BufferedReader(reader))
+            {
+                String line;
+                while ((line = buf.readLine()) != null)
+                {
+                    id.eventId = Integer.parseInt(line);
+                }
+            }
+            return id;
+        }
+    }
+
+    @ClientEndpoint(decoders = { EventIdDecoder.class })
+    public static class EventIdSocket
+    {
+        public EventQueue<EventId> messageQueue = new EventQueue<>();
+        private CountDownLatch closeLatch = new CountDownLatch(1);
+
+        @OnClose
+        public void onClose(CloseReason close)
+        {
+            closeLatch.countDown();
+        }
+
+        @OnMessage
+        public void onMessage(EventId msg)
+        {
+            messageQueue.add(msg);
+        }
+
+        public void awaitClose() throws InterruptedException
+        {
+            closeLatch.await(4,TimeUnit.SECONDS);
+        }
+    }
+
+    private static class EventIdServer implements Runnable
+    {
+        private BlockheadServer server;
+        private ServerConnection sconnection;
+        private CountDownLatch connectLatch = new CountDownLatch(1);
+
+        public EventIdServer(BlockheadServer server)
+        {
+            this.server = server;
+        }
+
+        @Override
+        public void run()
+        {
+            try
+            {
+                sconnection = server.accept();
+                sconnection.setSoTimeout(60000);
+                sconnection.upgrade();
+            }
+            catch (Exception e)
+            {
+                LOG.warn(e);
+            }
+            finally
+            {
+                connectLatch.countDown();
+            }
+        }
+
+        public void writeSequentialIds(int from, int to) throws IOException
+        {
+            for (int id = from; id < to; id++)
+            {
+                TextFrame frame = new TextFrame();
+                frame.setPayload(Integer.toString(id));
+                sconnection.write(frame);
+            }
+        }
+
+        public void close() throws IOException
+        {
+            sconnection.close();
+        }
+
+        public void awaitConnect() throws InterruptedException
+        {
+            connectLatch.await(1,TimeUnit.SECONDS);
+        }
+    }
+
+    private static final Logger LOG = Log.getLogger(DecoderReaderManySmallTest.class);
+
+    @Rule
+    public TestTracker tt = new TestTracker();
+
+    private BlockheadServer server;
+    private WebSocketContainer client;
+
+    @Before
+    public void initClient()
+    {
+        client = ContainerProvider.getWebSocketContainer();
+    }
+
+    @Before
+    public void startServer() throws Exception
+    {
+        server = new BlockheadServer();
+        server.start();
+    }
+
+    @After
+    public void stopServer() throws Exception
+    {
+        server.stop();
+    }
+
+    @Test
+    public void testManyIds() throws Exception
+    {
+        EventIdSocket ids = new EventIdSocket();
+        EventIdServer idserver = new EventIdServer(server);
+        new Thread(idserver).start();
+        client.connectToServer(ids,server.getWsUri());
+        idserver.awaitConnect();
+        int from = 1000;
+        int to = 2000;
+        idserver.writeSequentialIds(from,to);
+        idserver.close();
+        int count = from - to;
+        ids.messageQueue.awaitEventCount(count,4,TimeUnit.SECONDS);
+        ids.awaitClose();
+        // collect seen ids
+        List<Integer> seen = new ArrayList<>();
+        for(EventId id: ids.messageQueue)
+        {
+            // validate that ids don't repeat.
+            Assert.assertFalse("Already saw ID: " + id.eventId, seen.contains(id.eventId));
+            seen.add(id.eventId);
+        }
+        
+        // validate that all expected ids have been seen (order is irrelevant here)
+        for(int expected=from; expected<to; expected++)
+        {
+            Assert.assertTrue("Has expected id:"+expected,seen.contains(expected));
+        }
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/DecoderReaderTest.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/DecoderReaderTest.java
new file mode 100644
index 0000000..c031b99
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/DecoderReaderTest.java
@@ -0,0 +1,292 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356;
+
+import static org.hamcrest.Matchers.*;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.CloseReason;
+import javax.websocket.ContainerProvider;
+import javax.websocket.DecodeException;
+import javax.websocket.Decoder;
+import javax.websocket.EndpointConfig;
+import javax.websocket.OnClose;
+import javax.websocket.OnMessage;
+import javax.websocket.WebSocketContainer;
+
+import org.eclipse.jetty.toolchain.test.EventQueue;
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.eclipse.jetty.toolchain.test.TestTracker;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.frames.ContinuationFrame;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
+import org.eclipse.jetty.websocket.common.test.BlockheadServer;
+import org.eclipse.jetty.websocket.common.test.BlockheadServer.ServerConnection;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class DecoderReaderTest
+{
+    public static class Quotes
+    {
+        private String author;
+        private List<String> quotes = new ArrayList<>();
+
+        public String getAuthor()
+        {
+            return author;
+        }
+
+        public void setAuthor(String author)
+        {
+            this.author = author;
+        }
+
+        public List<String> getQuotes()
+        {
+            return quotes;
+        }
+
+        public void addQuote(String quote)
+        {
+            quotes.add(quote);
+        }
+    }
+
+    public static class QuotesDecoder implements Decoder.TextStream<Quotes>
+    {
+        @Override
+        public void init(EndpointConfig config)
+        {
+        }
+
+        @Override
+        public void destroy()
+        {
+        }
+
+        @Override
+        public Quotes decode(Reader reader) throws DecodeException, IOException
+        {
+            Quotes quotes = new Quotes();
+            try (BufferedReader buf = new BufferedReader(reader))
+            {
+                String line;
+                while ((line = buf.readLine()) != null)
+                {
+                    switch (line.charAt(0))
+                    {
+                        case 'a':
+                            quotes.setAuthor(line.substring(2));
+                            break;
+                        case 'q':
+                            quotes.addQuote(line.substring(2));
+                            break;
+                    }
+                }
+            }
+            return quotes;
+        }
+    }
+
+    @ClientEndpoint(decoders = { QuotesDecoder.class })
+    public static class QuotesSocket
+    {
+        public EventQueue<Quotes> messageQueue = new EventQueue<>();
+        private CountDownLatch closeLatch = new CountDownLatch(1);
+
+        @OnClose
+        public void onClose(CloseReason close)
+        {
+            closeLatch.countDown();
+        }
+
+        @OnMessage
+        public void onMessage(Quotes msg)
+        {
+            messageQueue.add(msg);
+            System.out.printf("Quotes from: %s%n",msg.author);
+            for (String quote : msg.quotes)
+            {
+                System.out.printf(" - %s%n",quote);
+            }
+        }
+
+        public void awaitClose() throws InterruptedException
+        {
+            closeLatch.await(4,TimeUnit.SECONDS);
+        }
+    }
+
+    private static class QuoteServer implements Runnable
+    {
+        private BlockheadServer server;
+        private ServerConnection sconnection;
+        private CountDownLatch connectLatch = new CountDownLatch(1);
+
+        public QuoteServer(BlockheadServer server)
+        {
+            this.server = server;
+        }
+
+        @Override
+        public void run()
+        {
+            try
+            {
+                sconnection = server.accept();
+                sconnection.setSoTimeout(60000);
+                sconnection.upgrade();
+            }
+            catch (Exception e)
+            {
+                LOG.warn(e);
+            }
+            finally
+            {
+                connectLatch.countDown();
+            }
+        }
+
+        public void writeQuotes(String filename) throws IOException
+        {
+            // read file
+            File qfile = MavenTestingUtils.getTestResourceFile(filename);
+            List<String> lines = new ArrayList<>();
+            try (FileReader reader = new FileReader(qfile); BufferedReader buf = new BufferedReader(reader))
+            {
+                String line;
+                while ((line = buf.readLine()) != null)
+                {
+                    lines.add(line);
+                }
+            }
+            // write file out, each line on a separate frame, but as
+            // 1 whole message
+            for (int i = 0; i < lines.size(); i++)
+            {
+                WebSocketFrame frame;
+                if (i == 0)
+                {
+                    frame = new TextFrame();
+                }
+                else
+                {
+                    frame = new ContinuationFrame();
+                }
+                frame.setFin((i >= (lines.size() - 1)));
+                frame.setPayload(BufferUtil.toBuffer(lines.get(i) + "\n"));
+                sconnection.write(frame);
+            }
+        }
+
+        public void close() throws IOException
+        {
+            sconnection.close();
+        }
+
+        public void awaitConnect() throws InterruptedException
+        {
+            connectLatch.await(1,TimeUnit.SECONDS);
+        }
+    }
+
+    private static final Logger LOG = Log.getLogger(DecoderReaderTest.class);
+
+    @Rule
+    public TestTracker tt = new TestTracker();
+
+    private BlockheadServer server;
+    private WebSocketContainer client;
+
+    @Before
+    public void initClient()
+    {
+        client = ContainerProvider.getWebSocketContainer();
+    }
+
+    @Before
+    public void startServer() throws Exception
+    {
+        server = new BlockheadServer();
+        server.start();
+    }
+
+    @After
+    public void stopServer() throws Exception
+    {
+        server.stop();
+    }
+
+    // TODO analyse and fix 
+    @Ignore
+    @Test
+    public void testSingleQuotes() throws Exception
+    {
+        QuotesSocket quoter = new QuotesSocket();
+        QuoteServer qserver = new QuoteServer(server);
+        new Thread(qserver).start();
+        client.connectToServer(quoter,server.getWsUri());
+        qserver.awaitConnect();
+        qserver.writeQuotes("quotes-ben.txt");
+        quoter.messageQueue.awaitEventCount(1,1000,TimeUnit.MILLISECONDS);
+        qserver.close();
+        quoter.awaitClose();
+        Quotes quotes = quoter.messageQueue.poll();
+        Assert.assertThat("Quotes Author",quotes.author,is("Benjamin Franklin"));
+        Assert.assertThat("Quotes Count",quotes.quotes.size(),is(3));
+    }
+
+    // TODO analyse and fix 
+    @Ignore
+    @Test
+    public void testTwoQuotes() throws Exception
+    {
+        QuotesSocket quoter = new QuotesSocket();
+        QuoteServer qserver = new QuoteServer(server);
+        new Thread(qserver).start();
+        client.connectToServer(quoter,server.getWsUri());
+        qserver.awaitConnect();
+        qserver.writeQuotes("quotes-ben.txt");
+        qserver.writeQuotes("quotes-twain.txt");
+        quoter.messageQueue.awaitEventCount(2,1000,TimeUnit.MILLISECONDS);
+        qserver.close();
+        quoter.awaitClose();
+        Quotes quotes = quoter.messageQueue.poll();
+        Assert.assertThat("Quotes Author",quotes.author,is("Benjamin Franklin"));
+        Assert.assertThat("Quotes Count",quotes.quotes.size(),is(3));
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/EchoCaptureHandler.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/EchoCaptureHandler.java
new file mode 100644
index 0000000..58285e1
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/EchoCaptureHandler.java
@@ -0,0 +1,32 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356;
+
+import javax.websocket.MessageHandler;
+
+public class EchoCaptureHandler implements MessageHandler.Whole<String>
+{
+    public MessageQueue messageQueue = new MessageQueue();
+
+    @Override
+    public void onMessage(String message)
+    {
+        messageQueue.offer(message);
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/EchoHandler.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/EchoHandler.java
new file mode 100644
index 0000000..f7a6816
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/EchoHandler.java
@@ -0,0 +1,42 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356;
+
+import org.eclipse.jetty.websocket.server.WebSocketHandler;
+import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
+import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
+import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
+import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
+
+public class EchoHandler extends WebSocketHandler implements WebSocketCreator
+{
+    public JettyEchoSocket socket = new JettyEchoSocket();
+
+    @Override
+    public void configure(WebSocketServletFactory factory)
+    {
+        factory.setCreator(this);
+    }
+
+    @Override
+    public Object createWebSocket(ServletUpgradeRequest req, ServletUpgradeResponse resp)
+    {
+        return socket;
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/EncoderFactoryTest.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/EncoderFactoryTest.java
new file mode 100644
index 0000000..dca75c2
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/EncoderFactoryTest.java
@@ -0,0 +1,86 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356;
+
+import static org.hamcrest.Matchers.is;
+
+import javax.websocket.Encoder;
+
+import org.eclipse.jetty.websocket.jsr356.encoders.IntegerEncoder;
+import org.eclipse.jetty.websocket.jsr356.encoders.LongEncoder;
+import org.eclipse.jetty.websocket.jsr356.encoders.PrimitiveEncoderMetadataSet;
+import org.eclipse.jetty.websocket.jsr356.metadata.EncoderMetadata;
+import org.eclipse.jetty.websocket.jsr356.metadata.EncoderMetadataSet;
+import org.eclipse.jetty.websocket.jsr356.samples.Fruit;
+import org.eclipse.jetty.websocket.jsr356.samples.FruitBinaryEncoder;
+import org.eclipse.jetty.websocket.jsr356.samples.FruitTextEncoder;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Tests against the Encoders class
+ */
+public class EncoderFactoryTest
+{
+    private EncoderMetadataSet metadatas;
+    private EncoderFactory factory;
+
+    private void assertMetadataFor(Class<?> type, Class<? extends Encoder> expectedEncoderClass, MessageType expectedType)
+    {
+        EncoderMetadata metadata = factory.getMetadataFor(type);
+        Assert.assertEquals("metadata.coderClass",metadata.getCoderClass(),expectedEncoderClass);
+        Assert.assertThat("metadata.messageType",metadata.getMessageType(),is(expectedType));
+        Assert.assertEquals("metadata.objectType",metadata.getObjectType(),type);
+    }
+
+    @Before
+    public void initEncoderFactory()
+    {
+        EncoderFactory primitivesFactory = new EncoderFactory(PrimitiveEncoderMetadataSet.INSTANCE);
+        metadatas = new EncoderMetadataSet();
+        factory = new EncoderFactory(metadatas,primitivesFactory);
+    }
+
+    @Test
+    public void testGetMetadataForFruitBinary()
+    {
+        metadatas.add(FruitBinaryEncoder.class);
+        assertMetadataFor(Fruit.class,FruitBinaryEncoder.class,MessageType.BINARY);
+    }
+
+    @Test
+    public void testGetMetadataForFruitText()
+    {
+        metadatas.add(FruitTextEncoder.class);
+        assertMetadataFor(Fruit.class,FruitTextEncoder.class,MessageType.TEXT);
+    }
+
+    @Test
+    public void testGetMetadataForInteger()
+    {
+        assertMetadataFor(Integer.TYPE,IntegerEncoder.class,MessageType.TEXT);
+    }
+
+    @Test
+    public void testGetMetadataForLong()
+    {
+        assertMetadataFor(Long.TYPE,LongEncoder.class,MessageType.TEXT);
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/EncoderTest.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/EncoderTest.java
new file mode 100644
index 0000000..7ce1381
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/EncoderTest.java
@@ -0,0 +1,316 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import javax.websocket.ClientEndpointConfig;
+import javax.websocket.ContainerProvider;
+import javax.websocket.EncodeException;
+import javax.websocket.Encoder;
+import javax.websocket.Endpoint;
+import javax.websocket.EndpointConfig;
+import javax.websocket.MessageHandler;
+import javax.websocket.Session;
+import javax.websocket.WebSocketContainer;
+
+import org.eclipse.jetty.toolchain.test.EventQueue;
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.eclipse.jetty.toolchain.test.TestTracker;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.common.test.BlockheadServer;
+import org.eclipse.jetty.websocket.common.test.BlockheadServer.ServerConnection;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import static org.hamcrest.Matchers.containsString;
+
+public class EncoderTest
+{
+    private static class EchoServer implements Runnable
+    {
+        private Thread thread;
+        private BlockheadServer server;
+        private ServerConnection sconnection;
+        private CountDownLatch connectLatch = new CountDownLatch(1);
+
+        public EchoServer(BlockheadServer server)
+        {
+            this.server = server;
+        }
+
+        @Override
+        public void run()
+        {
+            try
+            {
+                sconnection = server.accept();
+                sconnection.setSoTimeout(60000);
+                sconnection.upgrade();
+                sconnection.startEcho();
+            }
+            catch (Exception e)
+            {
+                LOG.warn(e);
+            }
+            finally
+            {
+                connectLatch.countDown();
+            }
+        }
+
+        public void start()
+        {
+            this.thread = new Thread(this,"EchoServer");
+            this.thread.start();
+        }
+
+        public void stop()
+        {
+            if (this.sconnection != null)
+            {
+                this.sconnection.stopEcho();
+                try
+                {
+                    this.sconnection.close();
+                }
+                catch (IOException ignore)
+                {
+                    /* ignore */
+                }
+            }
+        }
+    }
+
+    public static class Quotes
+    {
+        private String author;
+        private List<String> quotes = new ArrayList<>();
+
+        public void addQuote(String quote)
+        {
+            quotes.add(quote);
+        }
+
+        public String getAuthor()
+        {
+            return author;
+        }
+
+        public List<String> getQuotes()
+        {
+            return quotes;
+        }
+
+        public void setAuthor(String author)
+        {
+            this.author = author;
+        }
+    }
+
+    public static class QuotesEncoder implements Encoder.Text<Quotes>
+    {
+        @Override
+        public void destroy()
+        {
+        }
+
+        @Override
+        public String encode(Quotes q) throws EncodeException
+        {
+            StringBuilder buf = new StringBuilder();
+            buf.append("Author: ").append(q.getAuthor());
+            buf.append(System.lineSeparator());
+            for (String quote : q.quotes)
+            {
+                buf.append("Quote: ").append(quote);
+                buf.append(System.lineSeparator());
+            }
+            return buf.toString();
+        }
+
+        @Override
+        public void init(EndpointConfig config)
+        {
+        }
+    }
+
+    public static class QuotesSocket extends Endpoint implements MessageHandler.Whole<String>
+    {
+        private Session session;
+        private EventQueue<String> messageQueue = new EventQueue<>();
+
+        @Override
+        public void onMessage(String message)
+        {
+            messageQueue.add(message);
+        }
+
+        @Override
+        public void onOpen(Session session, EndpointConfig config)
+        {
+            this.session = session;
+            this.session.addMessageHandler(this);
+        }
+
+        public void write(Quotes quotes) throws IOException, EncodeException
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("Writing Quotes: {}",quotes);
+            this.session.getBasicRemote().sendObject(quotes);
+        }
+    }
+
+    private static final Logger LOG = Log.getLogger(EncoderTest.class);
+
+    @Rule
+    public TestTracker tt = new TestTracker();
+    private BlockheadServer server;
+
+    private WebSocketContainer client;
+
+    private void assertReceivedQuotes(String result, Quotes quotes)
+    {
+        Assert.assertThat("Quote Author",result,containsString("Author: " + quotes.getAuthor()));
+        for (String quote : quotes.quotes)
+        {
+            Assert.assertThat("Quote",result,containsString("Quote: " + quote));
+        }
+    }
+
+    private Quotes getQuotes(String filename) throws IOException
+    {
+        Quotes quotes = new Quotes();
+
+        // read file
+        File qfile = MavenTestingUtils.getTestResourceFile(filename);
+        try (FileReader reader = new FileReader(qfile); BufferedReader buf = new BufferedReader(reader))
+        {
+            String line;
+            while ((line = buf.readLine()) != null)
+            {
+                switch (line.charAt(0))
+                {
+                    case 'a':
+                        quotes.setAuthor(line.substring(2));
+                        break;
+                    case 'q':
+                        quotes.addQuote(line.substring(2));
+                        break;
+                }
+            }
+        }
+
+        return quotes;
+    }
+
+    @Before
+    public void initClient()
+    {
+        client = ContainerProvider.getWebSocketContainer();
+    }
+
+    @Before
+    public void startServer() throws Exception
+    {
+        server = new BlockheadServer();
+        server.start();
+    }
+
+    @After
+    public void stopServer() throws Exception
+    {
+        server.stop();
+    }
+
+    @Test
+    public void testSingleQuotes() throws Exception
+    {
+        EchoServer eserver = new EchoServer(server);
+        try
+        {
+            eserver.start();
+
+            QuotesSocket quoter = new QuotesSocket();
+
+            ClientEndpointConfig.Builder builder = ClientEndpointConfig.Builder.create();
+            List<Class<? extends Encoder>> encoders = new ArrayList<>();
+            encoders.add(QuotesEncoder.class);
+            builder.encoders(encoders);
+            ClientEndpointConfig cec = builder.build();
+            client.connectToServer(quoter,cec,server.getWsUri());
+
+            Quotes ben = getQuotes("quotes-ben.txt");
+            quoter.write(ben);
+
+            quoter.messageQueue.awaitEventCount(1,1000,TimeUnit.MILLISECONDS);
+
+            String result = quoter.messageQueue.poll();
+            assertReceivedQuotes(result,ben);
+        }
+        finally
+        {
+            eserver.stop();
+        }
+    }
+
+    @Test
+    public void testTwoQuotes() throws Exception
+    {
+        EchoServer eserver = new EchoServer(server);
+        try
+        {
+            eserver.start();
+
+            QuotesSocket quoter = new QuotesSocket();
+            ClientEndpointConfig.Builder builder = ClientEndpointConfig.Builder.create();
+            List<Class<? extends Encoder>> encoders = new ArrayList<>();
+            encoders.add(QuotesEncoder.class);
+            builder.encoders(encoders);
+            ClientEndpointConfig cec = builder.build();
+            client.connectToServer(quoter,cec,server.getWsUri());
+
+            Quotes ben = getQuotes("quotes-ben.txt");
+            Quotes twain = getQuotes("quotes-twain.txt");
+            quoter.write(ben);
+            quoter.write(twain);
+
+            quoter.messageQueue.awaitEventCount(2,1000,TimeUnit.MILLISECONDS);
+
+            String result = quoter.messageQueue.poll();
+            assertReceivedQuotes(result,ben);
+            result = quoter.messageQueue.poll();
+            assertReceivedQuotes(result,twain);
+        }
+        finally
+        {
+            eserver.stop();
+        }
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/EndpointEchoClient.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/EndpointEchoClient.java
new file mode 100644
index 0000000..0728b42
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/EndpointEchoClient.java
@@ -0,0 +1,66 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356;
+
+import java.io.IOException;
+import javax.websocket.CloseReason;
+import javax.websocket.Endpoint;
+import javax.websocket.EndpointConfig;
+import javax.websocket.Session;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.junit.Assert;
+
+import static org.hamcrest.Matchers.notNullValue;
+
+/**
+ * Basic Echo Client from extended Endpoint
+ */
+public class EndpointEchoClient extends Endpoint
+{
+    private static final Logger LOG = Log.getLogger(EndpointEchoClient.class);
+    private Session session = null;
+    private CloseReason close = null;
+    public EchoCaptureHandler textCapture = new EchoCaptureHandler();
+
+    public CloseReason getClose()
+    {
+        return close;
+    }
+
+    @Override
+    public void onOpen(Session session, EndpointConfig config)
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("onOpen({}, {})",session,config);
+        this.session = session;
+        Assert.assertThat("Session is required",session,notNullValue());
+        Assert.assertThat("EndpointConfig is required",config,notNullValue());
+        this.session.addMessageHandler(textCapture);
+    }
+
+    public void sendText(String text) throws IOException
+    {
+        if (session != null)
+        {
+            session.getBasicRemote().sendText(text);
+        }
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/EndpointEchoTest.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/EndpointEchoTest.java
new file mode 100644
index 0000000..82b47b2
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/EndpointEchoTest.java
@@ -0,0 +1,147 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356;
+
+import java.net.URI;
+import java.util.concurrent.TimeUnit;
+import javax.websocket.ContainerProvider;
+import javax.websocket.Session;
+import javax.websocket.WebSocketContainer;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.jsr356.samples.EchoStringEndpoint;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import static org.hamcrest.Matchers.instanceOf;
+
+public class EndpointEchoTest
+{
+    private static final Logger LOG = Log.getLogger(EndpointEchoTest.class);
+    private static Server server;
+    private static EchoHandler handler;
+    private static URI serverUri;
+
+    @BeforeClass
+    public static void startServer() throws Exception
+    {
+        server = new Server();
+        ServerConnector connector = new ServerConnector(server);
+        server.addConnector(connector);
+
+        handler = new EchoHandler();
+
+        ContextHandler context = new ContextHandler();
+        context.setContextPath("/");
+        context.setHandler(handler);
+        server.setHandler(context);
+
+        // Start Server
+        server.start();
+
+        String host = connector.getHost();
+        if (host == null)
+        {
+            host = "localhost";
+        }
+        int port = connector.getLocalPort();
+        serverUri = new URI(String.format("ws://%s:%d/",host,port));
+    }
+
+    @AfterClass
+    public static void stopServer()
+    {
+        try
+        {
+            server.stop();
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace(System.err);
+        }
+    }
+
+    @Test
+    public void testBasicEchoInstance() throws Exception
+    {
+        WebSocketContainer container = ContainerProvider.getWebSocketContainer();
+        EndpointEchoClient echoer = new EndpointEchoClient();
+        Assert.assertThat(echoer,instanceOf(javax.websocket.Endpoint.class));
+        // Issue connect using instance of class that extends Endpoint
+        Session session = container.connectToServer(echoer,serverUri);
+        if (LOG.isDebugEnabled())
+            LOG.debug("Client Connected: {}",session);
+        session.getBasicRemote().sendText("Echo");
+        if (LOG.isDebugEnabled())
+            LOG.debug("Client Message Sent");
+        echoer.textCapture.messageQueue.awaitMessages(1,1000,TimeUnit.MILLISECONDS);
+    }
+
+    @Test
+    public void testBasicEchoClassref() throws Exception
+    {
+        WebSocketContainer container = ContainerProvider.getWebSocketContainer();
+        // Issue connect using class reference (class extends Endpoint)
+        Session session = container.connectToServer(EndpointEchoClient.class,serverUri);
+        if (LOG.isDebugEnabled())
+            LOG.debug("Client Connected: {}",session);
+        session.getBasicRemote().sendText("Echo");
+        if (LOG.isDebugEnabled())
+            LOG.debug("Client Message Sent");
+        // TODO: figure out echo verification.
+        // echoer.textCapture.messageQueue.awaitMessages(1,1000,TimeUnit.MILLISECONDS);
+    }
+
+    @Test
+    public void testAbstractEchoInstance() throws Exception
+    {
+        WebSocketContainer container = ContainerProvider.getWebSocketContainer();
+        EchoStringEndpoint echoer = new EchoStringEndpoint();
+        Assert.assertThat(echoer,instanceOf(javax.websocket.Endpoint.class));
+        // Issue connect using instance of class that extends abstract that extends Endpoint
+        Session session = container.connectToServer(echoer,serverUri);
+        if (LOG.isDebugEnabled())
+            LOG.debug("Client Connected: {}",session);
+        session.getBasicRemote().sendText("Echo");
+        if (LOG.isDebugEnabled())
+            LOG.debug("Client Message Sent");
+        echoer.messageQueue.awaitMessages(1,1000,TimeUnit.MILLISECONDS);
+    }
+
+    @Test
+    public void testAbstractEchoClassref() throws Exception
+    {
+        WebSocketContainer container = ContainerProvider.getWebSocketContainer();
+        // Issue connect using class reference (class that extends abstract that extends Endpoint)
+        Session session = container.connectToServer(EchoStringEndpoint.class,serverUri);
+        if (LOG.isDebugEnabled())
+            LOG.debug("Client Connected: {}",session);
+        session.getBasicRemote().sendText("Echo");
+        if (LOG.isDebugEnabled())
+            LOG.debug("Client Message Sent");
+        // TODO: figure out echo verification.
+        // echoer.messageQueue.awaitMessages(1,1000,TimeUnit.MILLISECONDS);
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/JettyEchoSocket.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/JettyEchoSocket.java
new file mode 100644
index 0000000..99f46f2
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/JettyEchoSocket.java
@@ -0,0 +1,75 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356;
+
+import java.io.IOException;
+
+import org.eclipse.jetty.io.RuntimeIOException;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.BatchMode;
+import org.eclipse.jetty.websocket.api.RemoteEndpoint;
+import org.eclipse.jetty.websocket.api.WebSocketAdapter;
+
+/**
+ * Jetty Echo Socket. using Jetty techniques.
+ */
+public class JettyEchoSocket extends WebSocketAdapter
+{
+    private static final Logger LOG = Log.getLogger(JettyEchoSocket.class);
+
+    @Override
+    public void onWebSocketBinary(byte[] payload, int offset, int len)
+    {
+        try
+        {
+            RemoteEndpoint remote = getRemote();
+            remote.sendBytes(BufferUtil.toBuffer(payload, offset, len), null);
+            if (remote.getBatchMode() == BatchMode.ON)
+                remote.flush();
+        }
+        catch (IOException x)
+        {
+            throw new RuntimeIOException(x);
+        }
+    }
+
+    @Override
+    public void onWebSocketError(Throwable cause)
+    {
+        LOG.warn(cause);
+    }
+
+    @Override
+    public void onWebSocketText(String message)
+    {
+        try
+        {
+            RemoteEndpoint remote = getRemote();
+            remote.sendString(message, null);
+            if (remote.getBatchMode() == BatchMode.ON)
+                remote.flush();
+        }
+        catch (IOException x)
+        {
+            throw new RuntimeIOException(x);
+        }
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/JsrSessionTest.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/JsrSessionTest.java
new file mode 100644
index 0000000..a342ca1
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/JsrSessionTest.java
@@ -0,0 +1,115 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356;
+
+import java.net.URI;
+import java.nio.ByteBuffer;
+import javax.websocket.ClientEndpointConfig;
+import javax.websocket.DeploymentException;
+import javax.websocket.MessageHandler;
+
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.common.events.EventDriver;
+import org.eclipse.jetty.websocket.jsr356.client.EmptyClientEndpointConfig;
+import org.eclipse.jetty.websocket.jsr356.client.SimpleEndpointMetadata;
+import org.eclipse.jetty.websocket.jsr356.endpoints.EndpointInstance;
+import org.eclipse.jetty.websocket.jsr356.endpoints.JsrEndpointEventDriver;
+import org.eclipse.jetty.websocket.jsr356.handlers.ByteArrayWholeHandler;
+import org.eclipse.jetty.websocket.jsr356.handlers.ByteBufferPartialHandler;
+import org.eclipse.jetty.websocket.jsr356.handlers.LongMessageHandler;
+import org.eclipse.jetty.websocket.jsr356.handlers.StringWholeHandler;
+import org.eclipse.jetty.websocket.jsr356.samples.DummyConnection;
+import org.eclipse.jetty.websocket.jsr356.samples.DummyEndpoint;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.hamcrest.Matchers.instanceOf;
+
+public class JsrSessionTest
+{
+    private ClientContainer container;
+    private JsrSession session;
+
+    @Before
+    public void initSession()
+    {
+        container = new ClientContainer();
+        String id = JsrSessionTest.class.getSimpleName();
+        URI requestURI = URI.create("ws://localhost/" + id);
+        WebSocketPolicy policy = WebSocketPolicy.newClientPolicy();
+        ClientEndpointConfig config = new EmptyClientEndpointConfig();
+        DummyEndpoint websocket = new DummyEndpoint();
+        SimpleEndpointMetadata metadata = new SimpleEndpointMetadata(websocket.getClass());
+        // Executor executor = null;
+
+        EndpointInstance ei = new EndpointInstance(websocket,config,metadata);
+
+        EventDriver driver = new JsrEndpointEventDriver(policy,ei);
+        DummyConnection connection = new DummyConnection();
+        session = new JsrSession(requestURI,driver,connection,container,id);
+    }
+
+    @Test
+    public void testMessageHandlerBinary() throws DeploymentException
+    {
+        session.addMessageHandler(new ByteBufferPartialHandler());
+        MessageHandlerWrapper wrapper = session.getMessageHandlerWrapper(MessageType.BINARY);
+        Assert.assertThat("Binary Handler",wrapper.getHandler(),instanceOf(ByteBufferPartialHandler.class));
+        Assert.assertEquals("Message Class",wrapper.getMetadata().getMessageClass(),ByteBuffer.class);
+    }
+
+    @Test
+    public void testMessageHandlerBoth() throws DeploymentException
+    {
+        session.addMessageHandler(new StringWholeHandler());
+        session.addMessageHandler(new ByteArrayWholeHandler());
+        MessageHandlerWrapper wrapper = session.getMessageHandlerWrapper(MessageType.TEXT);
+        Assert.assertThat("Text Handler",wrapper.getHandler(),instanceOf(StringWholeHandler.class));
+        Assert.assertEquals("Message Class",wrapper.getMetadata().getMessageClass(),String.class);
+        wrapper = session.getMessageHandlerWrapper(MessageType.BINARY);
+        Assert.assertThat("Binary Handler",wrapper.getHandler(),instanceOf(ByteArrayWholeHandler.class));
+        Assert.assertEquals("Message Class",wrapper.getMetadata().getMessageClass(),byte[].class);
+    }
+
+    @Test
+    public void testMessageHandlerReplaceTextHandler() throws DeploymentException
+    {
+        MessageHandler oldText = new StringWholeHandler();
+        session.addMessageHandler(oldText); // add a TEXT handler
+        session.addMessageHandler(new ByteArrayWholeHandler()); // add BINARY handler
+        session.removeMessageHandler(oldText); // remove original TEXT handler
+        session.addMessageHandler(new LongMessageHandler()); // add new TEXT handler
+        MessageHandlerWrapper wrapper = session.getMessageHandlerWrapper(MessageType.BINARY);
+        Assert.assertThat("Binary Handler",wrapper.getHandler(),instanceOf(ByteArrayWholeHandler.class));
+        Assert.assertEquals("Message Class",wrapper.getMetadata().getMessageClass(),byte[].class);
+        wrapper = session.getMessageHandlerWrapper(MessageType.TEXT);
+        Assert.assertThat("Text Handler",wrapper.getHandler(),instanceOf(LongMessageHandler.class));
+        Assert.assertEquals("Message Class",wrapper.getMetadata().getMessageClass(),Long.class);
+    }
+
+    @Test
+    public void testMessageHandlerText() throws DeploymentException
+    {
+        session.addMessageHandler(new StringWholeHandler());
+        MessageHandlerWrapper wrapper = session.getMessageHandlerWrapper(MessageType.TEXT);
+        Assert.assertThat("Text Handler",wrapper.getHandler(),instanceOf(StringWholeHandler.class));
+        Assert.assertEquals("Message Class",wrapper.getMetadata().getMessageClass(),String.class);
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/MessageHandlerFactoryTest.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/MessageHandlerFactoryTest.java
new file mode 100644
index 0000000..5ec7b31
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/MessageHandlerFactoryTest.java
@@ -0,0 +1,76 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356;
+
+import static org.hamcrest.Matchers.is;
+
+import java.lang.reflect.Type;
+import java.util.List;
+
+import javax.websocket.DeploymentException;
+
+import org.eclipse.jetty.websocket.jsr356.decoders.PrimitiveDecoderMetadataSet;
+import org.eclipse.jetty.websocket.jsr356.handlers.ByteArrayPartialHandler;
+import org.eclipse.jetty.websocket.jsr356.handlers.StringPartialHandler;
+import org.eclipse.jetty.websocket.jsr356.metadata.DecoderMetadata;
+import org.eclipse.jetty.websocket.jsr356.metadata.DecoderMetadataSet;
+import org.eclipse.jetty.websocket.jsr356.metadata.MessageHandlerMetadata;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class MessageHandlerFactoryTest
+{
+    private MessageHandlerFactory factory;
+    private DecoderMetadataSet metadatas;
+    private DecoderFactory decoders;
+
+    @Before
+    public void init() throws DeploymentException
+    {
+        DecoderFactory primitivesFactory = new DecoderFactory(PrimitiveDecoderMetadataSet.INSTANCE);
+        metadatas = new DecoderMetadataSet();
+        decoders = new DecoderFactory(metadatas,primitivesFactory);
+        factory = new MessageHandlerFactory();
+    }
+
+    @Test
+    public void testByteArrayPartial() throws DeploymentException
+    {
+        List<MessageHandlerMetadata> metadatas = factory.getMetadata(ByteArrayPartialHandler.class);
+        Assert.assertThat("Metadata.list.size",metadatas.size(),is(1));
+
+        MessageHandlerMetadata handlerMetadata = metadatas.get(0);
+        DecoderMetadata decoderMetadata = decoders.getMetadataFor(handlerMetadata.getMessageClass());
+        Assert.assertThat("Message Type",decoderMetadata.getMessageType(),is(MessageType.BINARY));
+        Assert.assertThat("Message Class",handlerMetadata.getMessageClass(),is((Type)byte[].class));
+    }
+
+    @Test
+    public void testStringPartial() throws DeploymentException
+    {
+        List<MessageHandlerMetadata> metadatas = factory.getMetadata(StringPartialHandler.class);
+        Assert.assertThat("Metadata.list.size",metadatas.size(),is(1));
+
+        MessageHandlerMetadata handlerMetadata = metadatas.get(0);
+        DecoderMetadata decoderMetadata = decoders.getMetadataFor(handlerMetadata.getMessageClass());
+        Assert.assertThat("Message Type",decoderMetadata.getMessageType(),is(MessageType.TEXT));
+        Assert.assertThat("Message Class",handlerMetadata.getMessageClass(),is((Type)String.class));
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/MessageQueue.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/MessageQueue.java
new file mode 100644
index 0000000..ee81c03
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/MessageQueue.java
@@ -0,0 +1,59 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.eclipse.jetty.util.BlockingArrayQueue;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public class MessageQueue extends BlockingArrayQueue<String>
+{
+    private static final Logger LOG = Log.getLogger(MessageQueue.class);
+
+    public void awaitMessages(int expectedMessageCount, int timeoutDuration, TimeUnit timeoutUnit) throws TimeoutException
+    {
+        long msDur = TimeUnit.MILLISECONDS.convert(timeoutDuration,timeoutUnit);
+        long now = System.currentTimeMillis();
+        long expireOn = now + msDur;
+        if (LOG.isDebugEnabled())
+        {
+            LOG.debug("Await Message.. Now: {} - expireOn: {} ({} ms)",now,expireOn,msDur);
+        }
+
+        while (this.size() < expectedMessageCount)
+        {
+            try
+            {
+                TimeUnit.MILLISECONDS.sleep(20);
+            }
+            catch (InterruptedException gnore)
+            {
+                /* ignore */
+            }
+            if (!LOG.isDebugEnabled() && (System.currentTimeMillis() > expireOn))
+            {
+                throw new TimeoutException(String.format("Timeout reading all %d expected messages. (managed to only read %d messages)",expectedMessageCount,
+                        this.size()));
+            }
+        }
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/annotations/DateTextSocket.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/annotations/DateTextSocket.java
new file mode 100644
index 0000000..088ab54
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/annotations/DateTextSocket.java
@@ -0,0 +1,57 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.annotations;
+
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+
+import org.eclipse.jetty.websocket.jsr356.decoders.DateDecoder;
+
+ at ClientEndpoint(decoders =
+{ DateDecoder.class })
+public class DateTextSocket
+{
+    private Session session;
+
+    @OnMessage
+    public void onMessage(Date d) throws IOException
+    {
+        if (d == null)
+        {
+            session.getAsyncRemote().sendText("Error: Date is null");
+        }
+        else
+        {
+            String msg = SimpleDateFormat.getDateInstance(SimpleDateFormat.SHORT).format(d);
+            session.getAsyncRemote().sendText(msg);
+        }
+    }
+
+    @OnOpen
+    public void onOpen(Session session)
+    {
+        this.session = session;
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdDecoderTest.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdDecoderTest.java
new file mode 100644
index 0000000..544b3c7
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdDecoderTest.java
@@ -0,0 +1,57 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.annotations;
+
+import static org.hamcrest.Matchers.is;
+
+import java.lang.reflect.Method;
+import java.util.Date;
+
+import org.eclipse.jetty.websocket.jsr356.MessageType;
+import org.eclipse.jetty.websocket.jsr356.decoders.DateDecoder;
+import org.eclipse.jetty.websocket.jsr356.metadata.DecoderMetadata;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class JsrParamIdDecoderTest
+{
+    private JsrCallable getOnMessageCallableFrom(Class<?> clazz, String methodName)
+    {
+        for (Method method : clazz.getMethods())
+        {
+            if (method.getName().equals(methodName))
+            {
+                return new OnMessageCallable(clazz,method);
+            }
+        }
+        return null;
+    }
+
+    @Test
+    public void testMatchDateDecoder()
+    {
+        DecoderMetadata metadata = new DecoderMetadata(DateDecoder.class,Date.class,MessageType.TEXT,false);
+        JsrParamIdDecoder paramId = new JsrParamIdDecoder(metadata);
+
+        JsrCallable callable = getOnMessageCallableFrom(DateTextSocket.class,"onMessage");
+        Param param = new Param(0,Date.class,null);
+
+        Assert.assertThat("Match for Decoder",paramId.process(param,callable),is(true));
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/decoders/BadDualDecoder.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/decoders/BadDualDecoder.java
new file mode 100644
index 0000000..0971eb2
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/decoders/BadDualDecoder.java
@@ -0,0 +1,122 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.decoders;
+
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.websocket.DecodeException;
+import javax.websocket.Decoder;
+import javax.websocket.EndpointConfig;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.websocket.jsr356.samples.Fruit;
+import org.eclipse.jetty.websocket.jsr356.samples.FruitBinaryEncoder;
+
+/**
+ * Intentionally bad example of attempting to decode the same object to different message formats.
+ */
+public class BadDualDecoder implements Decoder.Text<Fruit>, Decoder.Binary<Fruit>
+{
+    @Override
+    public Fruit decode(ByteBuffer bytes) throws DecodeException
+    {
+        try
+        {
+            int id = bytes.get(bytes.position());
+            if (id != FruitBinaryEncoder.FRUIT_ID_BYTE)
+            {
+                // not a binary fruit object
+                throw new DecodeException(bytes,"Not an encoded Binary Fruit object");
+            }
+
+            Fruit fruit = new Fruit();
+            fruit.name = getUTF8String(bytes);
+            fruit.color = getUTF8String(bytes);
+            return fruit;
+        }
+        catch (BufferUnderflowException e)
+        {
+            throw new DecodeException(bytes,"Unable to read Fruit from binary message",e);
+        }
+    }
+
+    @Override
+    public Fruit decode(String s) throws DecodeException
+    {
+        Pattern pat = Pattern.compile("([^|]*)|([^|]*)");
+        Matcher mat = pat.matcher(s);
+        if (!mat.find())
+        {
+            throw new DecodeException(s,"Unable to find Fruit reference encoded in text message");
+        }
+
+        Fruit fruit = new Fruit();
+        fruit.name = mat.group(1);
+        fruit.color = mat.group(2);
+
+        return fruit;
+    }
+
+    @Override
+    public void destroy()
+    {
+    }
+
+    private String getUTF8String(ByteBuffer buf)
+    {
+        int strLen = buf.getInt();
+        ByteBuffer slice = buf.slice();
+        slice.limit(slice.position() + strLen);
+        String str = BufferUtil.toUTF8String(slice);
+        buf.position(buf.position() + strLen);
+        return str;
+    }
+
+    @Override
+    public void init(EndpointConfig config)
+    {
+    }
+
+    @Override
+    public boolean willDecode(ByteBuffer bytes)
+    {
+        if (bytes == null)
+        {
+            return false;
+        }
+        int id = bytes.get(bytes.position());
+        return (id != FruitBinaryEncoder.FRUIT_ID_BYTE);
+    }
+
+    @Override
+    public boolean willDecode(String s)
+    {
+        if (s == null)
+        {
+            return false;
+        }
+
+        Pattern pat = Pattern.compile("([^|]*)|([^|]*)");
+        Matcher mat = pat.matcher(s);
+        return (mat.find());
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/decoders/DateDecoder.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/decoders/DateDecoder.java
new file mode 100644
index 0000000..ee446bd
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/decoders/DateDecoder.java
@@ -0,0 +1,62 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.decoders;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import javax.websocket.DecodeException;
+import javax.websocket.Decoder;
+import javax.websocket.EndpointConfig;
+
+/**
+ * Decode Date
+ */
+public class DateDecoder implements Decoder.Text<Date>
+{
+    @Override
+    public Date decode(String s) throws DecodeException
+    {
+        try
+        {
+            return new SimpleDateFormat("yyyy.MM.dd").parse(s);
+        }
+        catch (ParseException e)
+        {
+            throw new DecodeException(s,e.getMessage(),e);
+        }
+    }
+
+    @Override
+    public void destroy()
+    {
+    }
+
+    @Override
+    public void init(EndpointConfig config)
+    {
+    }
+
+    @Override
+    public boolean willDecode(String s)
+    {
+        return true;
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/decoders/DateTimeDecoder.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/decoders/DateTimeDecoder.java
new file mode 100644
index 0000000..efea05e
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/decoders/DateTimeDecoder.java
@@ -0,0 +1,62 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.decoders;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import javax.websocket.DecodeException;
+import javax.websocket.Decoder;
+import javax.websocket.EndpointConfig;
+
+/**
+ * Decode Date and Time
+ */
+public class DateTimeDecoder implements Decoder.Text<Date>
+{
+    @Override
+    public Date decode(String s) throws DecodeException
+    {
+        try
+        {
+            return new SimpleDateFormat("yyyy.MM.dd G 'at' HH:mm:ss z").parse(s);
+        }
+        catch (ParseException e)
+        {
+            throw new DecodeException(s,e.getMessage(),e);
+        }
+    }
+
+    @Override
+    public void destroy()
+    {
+    }
+
+    @Override
+    public void init(EndpointConfig config)
+    {
+    }
+
+    @Override
+    public boolean willDecode(String s)
+    {
+        return true;
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/decoders/IntegerDecoderTest.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/decoders/IntegerDecoderTest.java
new file mode 100644
index 0000000..bfa8923
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/decoders/IntegerDecoderTest.java
@@ -0,0 +1,37 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.decoders;
+
+import static org.hamcrest.Matchers.is;
+
+import javax.websocket.DecodeException;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class IntegerDecoderTest
+{
+    @Test
+    public void testDecode() throws DecodeException
+    {
+        IntegerDecoder decoder = new IntegerDecoder();
+        Integer val = decoder.decode("123");
+        Assert.assertThat("Decoded value",val,is(123));
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/decoders/PrimitiveDecoderMetadataSetTest.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/decoders/PrimitiveDecoderMetadataSetTest.java
new file mode 100644
index 0000000..a732ce8
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/decoders/PrimitiveDecoderMetadataSetTest.java
@@ -0,0 +1,54 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.decoders;
+
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+
+import javax.websocket.Decoder;
+
+import org.eclipse.jetty.websocket.jsr356.MessageType;
+import org.eclipse.jetty.websocket.jsr356.metadata.DecoderMetadata;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class PrimitiveDecoderMetadataSetTest
+{
+    private void assertClassEquals(String msg, Class<?> actual, Class<?> expected)
+    {
+        Assert.assertThat(msg,actual.getName(),is(expected.getName()));
+    }
+
+    private void assertDecoderType(Class<? extends Decoder> expectedDecoder, MessageType expectedMsgType, Class<?> type)
+    {
+        PrimitiveDecoderMetadataSet primitives = new PrimitiveDecoderMetadataSet();
+        DecoderMetadata metadata = primitives.getMetadataByType(type);
+        String prefix = String.format("Metadata By Type [%s]",type.getName());
+        Assert.assertThat(prefix,metadata,notNullValue());
+
+        assertClassEquals(prefix + ".coderClass",metadata.getCoderClass(),expectedDecoder);
+        Assert.assertThat(prefix + ".messageType",metadata.getMessageType(),is(expectedMsgType));
+    }
+
+    @Test
+    public void testGetByteArray()
+    {
+        assertDecoderType(ByteArrayDecoder.class,MessageType.BINARY,byte[].class);
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/decoders/TimeDecoder.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/decoders/TimeDecoder.java
new file mode 100644
index 0000000..5bced00
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/decoders/TimeDecoder.java
@@ -0,0 +1,62 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.decoders;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import javax.websocket.DecodeException;
+import javax.websocket.Decoder;
+import javax.websocket.EndpointConfig;
+
+/**
+ * Decode Time
+ */
+public class TimeDecoder implements Decoder.Text<Date>
+{
+    @Override
+    public Date decode(String s) throws DecodeException
+    {
+        try
+        {
+            return new SimpleDateFormat("HH:mm:ss z").parse(s);
+        }
+        catch (ParseException e)
+        {
+            throw new DecodeException(s,e.getMessage(),e);
+        }
+    }
+
+    @Override
+    public void destroy()
+    {
+    }
+
+    @Override
+    public void init(EndpointConfig config)
+    {
+    }
+
+    @Override
+    public boolean willDecode(String s)
+    {
+        return true;
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/decoders/ValidDualDecoder.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/decoders/ValidDualDecoder.java
new file mode 100644
index 0000000..25b8020
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/decoders/ValidDualDecoder.java
@@ -0,0 +1,65 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.decoders;
+
+import java.nio.ByteBuffer;
+
+import javax.websocket.DecodeException;
+import javax.websocket.Decoder;
+import javax.websocket.EndpointConfig;
+
+/**
+ * Example of a valid decoder impl declaring 2 decoders.
+ */
+public class ValidDualDecoder implements Decoder.Text<Integer>, Decoder.Binary<Long>
+{
+    @Override
+    public Long decode(ByteBuffer bytes) throws DecodeException
+    {
+        return bytes.getLong();
+    }
+
+    @Override
+    public Integer decode(String s) throws DecodeException
+    {
+        return Integer.parseInt(s);
+    }
+
+    @Override
+    public void destroy()
+    {
+    }
+
+    @Override
+    public void init(EndpointConfig config)
+    {
+    }
+
+    @Override
+    public boolean willDecode(ByteBuffer bytes)
+    {
+        return true;
+    }
+
+    @Override
+    public boolean willDecode(String s)
+    {
+        return true;
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/demo/ExampleClient.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/demo/ExampleClient.java
new file mode 100644
index 0000000..c11ac0a
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/demo/ExampleClient.java
@@ -0,0 +1,99 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.demo;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Objects;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.CloseReason;
+import javax.websocket.ContainerProvider;
+import javax.websocket.DeploymentException;
+import javax.websocket.OnClose;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.RemoteEndpoint;
+import javax.websocket.Session;
+import javax.websocket.WebSocketContainer;
+
+public class ExampleClient
+{
+    @ClientEndpoint
+    public class ExampleSocket
+    {
+        public String message;
+        public CountDownLatch messageLatch = new CountDownLatch(1);
+        public CountDownLatch closeLatch = new CountDownLatch(1);
+
+        @OnOpen
+        public void onOpen(Session session)
+        {
+            System.out.println("Opened");
+        }
+
+        @OnMessage
+        public void onMessage(String msg)
+        {
+            System.out.printf("Received: %s%n",Objects.toString(msg));
+            this.messageLatch.countDown();
+        }
+
+        @OnClose
+        public void onClose(CloseReason close)
+        {
+            System.out.printf("Closed: %d, %s%n",close.getCloseCode().getCode(),Objects.toString(close.getReasonPhrase()));
+            this.closeLatch.countDown();
+        }
+    }
+
+    public static void main(String[] args)
+    {
+        try
+        {
+            new ExampleClient().run();
+        }
+        catch (Throwable t)
+        {
+            t.printStackTrace();
+        }
+    }
+
+    private void run() throws DeploymentException, IOException, URISyntaxException, InterruptedException
+    {
+        WebSocketContainer container = ContainerProvider.getWebSocketContainer();
+
+        System.out.printf("WebSocketContainer Impl: %s%n",container.getClass().getName());
+
+        ExampleSocket socket = new ExampleSocket();
+        URI uri = new URI("ws://echo.websocket.org/");
+        Session session = container.connectToServer(socket,uri);
+        RemoteEndpoint.Basic remote = session.getBasicRemote();
+        String msg = "Hello world";
+        System.out.printf("Sending: %s%n",Objects.toString(msg));
+        remote.sendText(msg);
+        socket.messageLatch.await(1,TimeUnit.SECONDS); // give remote 1 second to respond
+        session.close();
+        socket.closeLatch.await(1,TimeUnit.SECONDS); // give remote 1 second to acknowledge response
+        System.out.println("Socket is closed");
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/demo/ExampleSocket.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/demo/ExampleSocket.java
new file mode 100644
index 0000000..192e04b
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/demo/ExampleSocket.java
@@ -0,0 +1,71 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.demo;
+
+import java.io.IOException;
+import java.util.concurrent.CountDownLatch;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.CloseReason;
+import javax.websocket.OnClose;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+
+ at ClientEndpoint
+public class ExampleSocket
+{
+    private Session session;
+    public CountDownLatch messageLatch = new CountDownLatch(1);
+    public CountDownLatch closeLatch = new CountDownLatch(1);
+
+    @OnClose
+    public void onClose(CloseReason close)
+    {
+        System.out.printf("Closed: %d, \"%s\"%n",close.getCloseCode().getCode(),close.getReasonPhrase());
+        closeLatch.countDown();
+    }
+
+    @OnMessage
+    public void onMessage(String message)
+    {
+        System.out.printf("Received: \"%s\"%n",message);
+        messageLatch.countDown();
+    }
+
+    @OnOpen
+    public void onOpen(Session session)
+    {
+        System.out.printf("Opened%n");
+        this.session = session;
+    }
+
+    public void writeMessage(String message)
+    {
+        System.out.printf("Writing: \"%s\"%n",message);
+        try
+        {
+            session.getBasicRemote().sendText(message);
+        }
+        catch (IOException e)
+        {
+            e.printStackTrace();
+        }
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/encoders/BadDualEncoder.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/encoders/BadDualEncoder.java
new file mode 100644
index 0000000..5a7651f
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/encoders/BadDualEncoder.java
@@ -0,0 +1,54 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.encoders;
+
+import java.io.IOException;
+import java.io.Writer;
+
+import javax.websocket.EncodeException;
+import javax.websocket.Encoder;
+import javax.websocket.EndpointConfig;
+
+/**
+ * Intentionally bad example of attempting to encode the same object for different message types.
+ */
+public class BadDualEncoder implements Encoder.Text<Integer>, Encoder.TextStream<Integer>
+{
+    @Override
+    public void destroy()
+    {
+    }
+
+    @Override
+    public String encode(Integer object) throws EncodeException
+    {
+        return Integer.toString(object);
+    }
+
+    @Override
+    public void encode(Integer object, Writer writer) throws EncodeException, IOException
+    {
+        writer.write(Integer.toString(object));
+    }
+
+    @Override
+    public void init(EndpointConfig config)
+    {
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/encoders/DateEncoder.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/encoders/DateEncoder.java
new file mode 100644
index 0000000..c9f8247
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/encoders/DateEncoder.java
@@ -0,0 +1,48 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.encoders;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import javax.websocket.EncodeException;
+import javax.websocket.Encoder;
+import javax.websocket.EndpointConfig;
+
+/**
+ * Encode Date
+ */
+public class DateEncoder implements Encoder.Text<Date>
+{
+    @Override
+    public void destroy()
+    {
+    }
+
+    @Override
+    public String encode(Date object) throws EncodeException
+    {
+        return new SimpleDateFormat("yyyy.MM.dd").format(object);
+    }
+
+    @Override
+    public void init(EndpointConfig config)
+    {
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/encoders/DateTimeEncoder.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/encoders/DateTimeEncoder.java
new file mode 100644
index 0000000..5c3106d
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/encoders/DateTimeEncoder.java
@@ -0,0 +1,48 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.encoders;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import javax.websocket.EncodeException;
+import javax.websocket.Encoder;
+import javax.websocket.EndpointConfig;
+
+/**
+ * Encode Date
+ */
+public class DateTimeEncoder implements Encoder.Text<Date>
+{
+    @Override
+    public void destroy()
+    {
+    }
+
+    @Override
+    public String encode(Date object) throws EncodeException
+    {
+        return new SimpleDateFormat("yyyy.MM.dd G 'at' HH:mm:ss z").format(object);
+    }
+
+    @Override
+    public void init(EndpointConfig config)
+    {
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/encoders/DualEncoder.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/encoders/DualEncoder.java
new file mode 100644
index 0000000..0033187
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/encoders/DualEncoder.java
@@ -0,0 +1,58 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.encoders;
+
+import java.io.IOException;
+import java.io.Writer;
+
+import javax.websocket.EncodeException;
+import javax.websocket.Encoder;
+import javax.websocket.EndpointConfig;
+
+import org.eclipse.jetty.websocket.jsr356.samples.Fruit;
+
+/**
+ * Intentionally bad example of attempting to decode the same object to different message formats.
+ */
+public class DualEncoder implements Encoder.Text<Fruit>, Encoder.TextStream<Fruit>
+{
+    @Override
+    public void destroy()
+    {
+    }
+
+    @Override
+    public String encode(Fruit fruit) throws EncodeException
+    {
+        return String.format("%s|%s",fruit.name,fruit.color);
+    }
+
+    @Override
+    public void encode(Fruit fruit, Writer writer) throws EncodeException, IOException
+    {
+        writer.write(fruit.name);
+        writer.write('|');
+        writer.write(fruit.color);
+    }
+
+    @Override
+    public void init(EndpointConfig config)
+    {
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/encoders/TimeEncoder.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/encoders/TimeEncoder.java
new file mode 100644
index 0000000..dbfb8d9
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/encoders/TimeEncoder.java
@@ -0,0 +1,48 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.encoders;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import javax.websocket.EncodeException;
+import javax.websocket.Encoder;
+import javax.websocket.EndpointConfig;
+
+/**
+ * Encode Time
+ */
+public class TimeEncoder implements Encoder.Text<Date>
+{
+    @Override
+    public void destroy()
+    {
+    }
+
+    @Override
+    public String encode(Date object) throws EncodeException
+    {
+        return new SimpleDateFormat("HH:mm:ss z").format(object);
+    }
+
+    @Override
+    public void init(EndpointConfig config)
+    {
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/encoders/ValidDualEncoder.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/encoders/ValidDualEncoder.java
new file mode 100644
index 0000000..e839331
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/encoders/ValidDualEncoder.java
@@ -0,0 +1,64 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.encoders;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import javax.websocket.EncodeException;
+import javax.websocket.Encoder;
+import javax.websocket.EndpointConfig;
+
+/**
+ * Example of a valid encoder impl declaring 2 encoders.
+ */
+public class ValidDualEncoder implements Encoder.Text<Integer>, Encoder.BinaryStream<Long>
+{
+    @Override
+    public void destroy()
+    {
+    }
+
+    @Override
+    public String encode(Integer object) throws EncodeException
+    {
+        return Integer.toString(object);
+    }
+
+    @Override
+    public void encode(Long object, OutputStream os) throws EncodeException, IOException
+    {
+        byte b[] = new byte[8];
+        long v = object;
+        b[0] = (byte)(v >>> 56);
+        b[1] = (byte)(v >>> 48);
+        b[2] = (byte)(v >>> 40);
+        b[3] = (byte)(v >>> 32);
+        b[4] = (byte)(v >>> 24);
+        b[5] = (byte)(v >>> 16);
+        b[6] = (byte)(v >>> 8);
+        b[7] = (byte)(v >>> 0);
+        os.write(b,0,8);
+    }
+
+    @Override
+    public void init(EndpointConfig config)
+    {
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/ClientAnnotatedEndpointScanner_GoodSignaturesTest.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/ClientAnnotatedEndpointScanner_GoodSignaturesTest.java
new file mode 100644
index 0000000..dabae5d
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/ClientAnnotatedEndpointScanner_GoodSignaturesTest.java
@@ -0,0 +1,169 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.endpoints;
+
+import static org.hamcrest.Matchers.notNullValue;
+
+import java.io.InputStream;
+import java.lang.reflect.Field;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.ClientEndpointConfig;
+import javax.websocket.CloseReason;
+import javax.websocket.PongMessage;
+import javax.websocket.Session;
+
+import org.eclipse.jetty.websocket.jsr356.ClientContainer;
+import org.eclipse.jetty.websocket.jsr356.annotations.AnnotatedEndpointMetadata;
+import org.eclipse.jetty.websocket.jsr356.annotations.AnnotatedEndpointScanner;
+import org.eclipse.jetty.websocket.jsr356.annotations.JsrCallable;
+import org.eclipse.jetty.websocket.jsr356.client.AnnotatedClientEndpointMetadata;
+import org.eclipse.jetty.websocket.jsr356.endpoints.samples.BasicBinaryMessageByteBufferSocket;
+import org.eclipse.jetty.websocket.jsr356.endpoints.samples.BasicErrorSessionSocket;
+import org.eclipse.jetty.websocket.jsr356.endpoints.samples.BasicErrorSessionThrowableSocket;
+import org.eclipse.jetty.websocket.jsr356.endpoints.samples.BasicErrorSocket;
+import org.eclipse.jetty.websocket.jsr356.endpoints.samples.BasicErrorThrowableSessionSocket;
+import org.eclipse.jetty.websocket.jsr356.endpoints.samples.BasicErrorThrowableSocket;
+import org.eclipse.jetty.websocket.jsr356.endpoints.samples.BasicInputStreamSocket;
+import org.eclipse.jetty.websocket.jsr356.endpoints.samples.BasicInputStreamWithThrowableSocket;
+import org.eclipse.jetty.websocket.jsr356.endpoints.samples.BasicOpenSessionSocket;
+import org.eclipse.jetty.websocket.jsr356.endpoints.samples.BasicOpenSocket;
+import org.eclipse.jetty.websocket.jsr356.endpoints.samples.BasicPongMessageSocket;
+import org.eclipse.jetty.websocket.jsr356.endpoints.samples.BasicTextMessageStringSocket;
+import org.eclipse.jetty.websocket.jsr356.endpoints.samples.close.CloseReasonSessionSocket;
+import org.eclipse.jetty.websocket.jsr356.endpoints.samples.close.CloseReasonSocket;
+import org.eclipse.jetty.websocket.jsr356.endpoints.samples.close.CloseSessionReasonSocket;
+import org.eclipse.jetty.websocket.jsr356.endpoints.samples.close.CloseSocket;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Test {@link AnnotatedEndpointScanner} against various valid, simple, 1 method {@link ClientEndpoint} annotated classes with valid signatures.
+ */
+ at RunWith(Parameterized.class)
+public class ClientAnnotatedEndpointScanner_GoodSignaturesTest
+{
+    public static class Case
+    {
+        public static void add(List<Case[]> data, Class<?> pojo, Field metadataField, Class<?>... expectedParams)
+        {
+            data.add(new Case[]
+            { new Case(pojo,metadataField,expectedParams) });
+        }
+
+        // The websocket pojo to test against
+        Class<?> pojo;
+        // The JsrAnnotatedMetadata field that should be populated
+        Field metadataField;
+        // The expected parameters for the Callable found by the scanner
+        Class<?> expectedParameters[];
+
+        public Case(Class<?> pojo, Field metadataField, Class<?>... expectedParams)
+        {
+            this.pojo = pojo;
+            this.metadataField = metadataField;
+            this.expectedParameters = expectedParams;
+        }
+    }
+
+    private static ClientContainer container = new ClientContainer();
+
+    @Parameters
+    public static Collection<Case[]> data() throws Exception
+    {
+        List<Case[]> data = new ArrayList<>();
+        Field fOpen = findFieldRef(AnnotatedEndpointMetadata.class,"onOpen");
+        Field fClose = findFieldRef(AnnotatedEndpointMetadata.class,"onClose");
+        Field fError = findFieldRef(AnnotatedEndpointMetadata.class,"onError");
+        Field fText = findFieldRef(AnnotatedEndpointMetadata.class,"onText");
+        Field fBinary = findFieldRef(AnnotatedEndpointMetadata.class,"onBinary");
+        Field fBinaryStream = findFieldRef(AnnotatedEndpointMetadata.class,"onBinaryStream");
+        Field fPong = findFieldRef(AnnotatedEndpointMetadata.class,"onPong");
+
+        // @formatter:off
+        // -- Open Events
+        Case.add(data, BasicOpenSocket.class, fOpen);
+        Case.add(data, BasicOpenSessionSocket.class, fOpen, Session.class);
+        // -- Close Events
+        Case.add(data, CloseSocket.class, fClose);
+        Case.add(data, CloseReasonSocket.class, fClose, CloseReason.class);
+        Case.add(data, CloseReasonSessionSocket.class, fClose, CloseReason.class, Session.class);
+        Case.add(data, CloseSessionReasonSocket.class, fClose, Session.class, CloseReason.class);
+        // -- Error Events
+        Case.add(data, BasicErrorSocket.class, fError);
+        Case.add(data, BasicErrorSessionSocket.class, fError, Session.class);
+        Case.add(data, BasicErrorSessionThrowableSocket.class, fError, Session.class, Throwable.class);
+        Case.add(data, BasicErrorThrowableSocket.class, fError, Throwable.class);
+        Case.add(data, BasicErrorThrowableSessionSocket.class, fError, Throwable.class, Session.class);
+        // -- Text Events
+        Case.add(data, BasicTextMessageStringSocket.class, fText, String.class);
+        // -- Binary Events
+        Case.add(data, BasicBinaryMessageByteBufferSocket.class, fBinary, ByteBuffer.class);
+        // -- Pong Events
+        Case.add(data, BasicPongMessageSocket.class, fPong, PongMessage.class);
+        // -- InputStream Events
+        Case.add(data, BasicInputStreamSocket.class, fBinaryStream, InputStream.class);
+        Case.add(data, BasicInputStreamWithThrowableSocket.class, fBinaryStream, InputStream.class);
+        // @formatter:on
+
+        // TODO: validate return types
+
+        return data;
+    }
+
+    private static Field findFieldRef(Class<?> clazz, String fldName) throws Exception
+    {
+        return clazz.getField(fldName);
+    }
+
+    private Case testcase;
+
+    public ClientAnnotatedEndpointScanner_GoodSignaturesTest(Case testcase)
+    {
+        this.testcase = testcase;
+    }
+
+    @Test
+    public void testScan_Basic() throws Exception
+    {
+        AnnotatedClientEndpointMetadata metadata = new AnnotatedClientEndpointMetadata(container,testcase.pojo);
+        AnnotatedEndpointScanner<ClientEndpoint, ClientEndpointConfig> scanner = new AnnotatedEndpointScanner<>(metadata);
+        scanner.scan();
+
+        Assert.assertThat("Metadata",metadata,notNullValue());
+
+        JsrCallable cm = (JsrCallable)testcase.metadataField.get(metadata);
+        Assert.assertThat(testcase.metadataField.toString(),cm,notNullValue());
+        int len = testcase.expectedParameters.length;
+        for (int i = 0; i < len; i++)
+        {
+            Class<?> expectedParam = testcase.expectedParameters[i];
+            Class<?> actualParam = cm.getParamTypes()[i];
+
+            Assert.assertTrue("Parameter[" + i + "] - expected:[" + expectedParam + "], actual:[" + actualParam + "]",actualParam.equals(expectedParam));
+        }
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/ClientAnnotatedEndpointScanner_InvalidSignaturesTest.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/ClientAnnotatedEndpointScanner_InvalidSignaturesTest.java
new file mode 100644
index 0000000..b5e8fb1
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/ClientAnnotatedEndpointScanner_InvalidSignaturesTest.java
@@ -0,0 +1,113 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.endpoints;
+
+import java.lang.annotation.Annotation;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import javax.websocket.ClientEndpoint;
+import javax.websocket.ClientEndpointConfig;
+import javax.websocket.DeploymentException;
+import javax.websocket.OnClose;
+import javax.websocket.OnError;
+import javax.websocket.OnOpen;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.common.events.annotated.InvalidSignatureException;
+import org.eclipse.jetty.websocket.jsr356.ClientContainer;
+import org.eclipse.jetty.websocket.jsr356.annotations.AnnotatedEndpointScanner;
+import org.eclipse.jetty.websocket.jsr356.client.AnnotatedClientEndpointMetadata;
+import org.eclipse.jetty.websocket.jsr356.endpoints.samples.InvalidCloseIntSocket;
+import org.eclipse.jetty.websocket.jsr356.endpoints.samples.InvalidErrorErrorSocket;
+import org.eclipse.jetty.websocket.jsr356.endpoints.samples.InvalidErrorExceptionSocket;
+import org.eclipse.jetty.websocket.jsr356.endpoints.samples.InvalidErrorIntSocket;
+import org.eclipse.jetty.websocket.jsr356.endpoints.samples.InvalidOpenCloseReasonSocket;
+import org.eclipse.jetty.websocket.jsr356.endpoints.samples.InvalidOpenIntSocket;
+import org.eclipse.jetty.websocket.jsr356.endpoints.samples.InvalidOpenSessionIntSocket;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+import static org.hamcrest.Matchers.containsString;
+
+/**
+ * Test {@link AnnotatedEndpointScanner} against various simple, 1 method, {@link ClientEndpoint} annotated classes with invalid signatures.
+ */
+ at RunWith(Parameterized.class)
+public class ClientAnnotatedEndpointScanner_InvalidSignaturesTest
+{
+    private static final Logger LOG = Log.getLogger(ClientAnnotatedEndpointScanner_InvalidSignaturesTest.class);
+    private static ClientContainer container = new ClientContainer();
+
+    @Parameters
+    public static Collection<Class<?>[]> data()
+    {
+        List<Class<?>[]> data = new ArrayList<>();
+
+        // @formatter:off
+        data.add(new Class<?>[]{ InvalidCloseIntSocket.class, OnClose.class });
+        data.add(new Class<?>[]{ InvalidErrorErrorSocket.class, OnError.class });
+        data.add(new Class<?>[]{ InvalidErrorExceptionSocket.class, OnError.class });
+        data.add(new Class<?>[]{ InvalidErrorIntSocket.class, OnError.class });
+        data.add(new Class<?>[]{ InvalidOpenCloseReasonSocket.class, OnOpen.class });
+        data.add(new Class<?>[]{ InvalidOpenIntSocket.class, OnOpen.class });
+        data.add(new Class<?>[]{ InvalidOpenSessionIntSocket.class, OnOpen.class });
+        // @formatter:on
+
+        // TODO: invalid return types
+        // TODO: static methods
+        // TODO: private or protected methods
+        // TODO: abstract methods
+
+        return data;
+    }
+
+    // The pojo to test
+    private Class<?> pojo;
+    // The annotation class expected to be mentioned in the error message
+    private Class<? extends Annotation> expectedAnnoClass;
+
+    public ClientAnnotatedEndpointScanner_InvalidSignaturesTest(Class<?> pojo, Class<? extends Annotation> expectedAnnotation)
+    {
+        this.pojo = pojo;
+        this.expectedAnnoClass = expectedAnnotation;
+    }
+
+    @Test
+    public void testScan_InvalidSignature() throws DeploymentException
+    {
+        AnnotatedClientEndpointMetadata metadata = new AnnotatedClientEndpointMetadata(container,pojo);
+        AnnotatedEndpointScanner<ClientEndpoint, ClientEndpointConfig> scanner = new AnnotatedEndpointScanner<>(metadata);
+        try
+        {
+            scanner.scan();
+            Assert.fail("Expected " + InvalidSignatureException.class + " with message that references " + expectedAnnoClass + " annotation");
+        }
+        catch (InvalidSignatureException e)
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("{}:{}",e.getClass(),e.getMessage());
+            Assert.assertThat("Message",e.getMessage(),containsString(expectedAnnoClass.getSimpleName()));
+        }
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/OnCloseTest.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/OnCloseTest.java
new file mode 100644
index 0000000..7533384
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/OnCloseTest.java
@@ -0,0 +1,126 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.endpoints;
+
+import static org.hamcrest.Matchers.is;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.ClientEndpointConfig;
+
+import org.eclipse.jetty.toolchain.test.EventQueue;
+import org.eclipse.jetty.websocket.api.StatusCode;
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.common.CloseInfo;
+import org.eclipse.jetty.websocket.common.events.EventDriver;
+import org.eclipse.jetty.websocket.jsr356.ClientContainer;
+import org.eclipse.jetty.websocket.jsr356.annotations.AnnotatedEndpointScanner;
+import org.eclipse.jetty.websocket.jsr356.annotations.JsrEvents;
+import org.eclipse.jetty.websocket.jsr356.client.AnnotatedClientEndpointMetadata;
+import org.eclipse.jetty.websocket.jsr356.endpoints.samples.close.CloseEndpointConfigSocket;
+import org.eclipse.jetty.websocket.jsr356.endpoints.samples.close.CloseReasonSessionSocket;
+import org.eclipse.jetty.websocket.jsr356.endpoints.samples.close.CloseReasonSocket;
+import org.eclipse.jetty.websocket.jsr356.endpoints.samples.close.CloseSessionReasonSocket;
+import org.eclipse.jetty.websocket.jsr356.endpoints.samples.close.CloseSessionSocket;
+import org.eclipse.jetty.websocket.jsr356.endpoints.samples.close.CloseSocket;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+ at RunWith(Parameterized.class)
+public class OnCloseTest
+{
+    private static class Case
+    {
+        public static Case add(List<Case[]> data, Class<?> closeClass)
+        {
+            Case tcase = new Case();
+            tcase.closeClass = closeClass;
+            data.add(new Case[]
+            { tcase });
+            return tcase;
+        }
+
+        Class<?> closeClass;
+        String expectedCloseEvent;
+
+        public Case expect(String expectedEvent)
+        {
+            this.expectedCloseEvent = expectedEvent;
+            return this;
+        }
+    }
+
+    private static ClientContainer container = new ClientContainer();
+
+    @Parameters
+    public static Collection<Case[]> data() throws Exception
+    {
+        List<Case[]> data = new ArrayList<>();
+
+        Case.add(data,CloseSocket.class).expect("onClose()");
+        Case.add(data,CloseReasonSocket.class).expect("onClose(CloseReason)");
+        Case.add(data,CloseSessionSocket.class).expect("onClose(Session)");
+        Case.add(data,CloseReasonSessionSocket.class).expect("onClose(CloseReason,Session)");
+        Case.add(data,CloseSessionReasonSocket.class).expect("onClose(Session,CloseReason)");
+        Case.add(data,CloseEndpointConfigSocket.class).expect("onClose(EndpointConfig)");
+
+        return data;
+    }
+
+    private final Case testcase;
+
+    public OnCloseTest(Case testcase)
+    {
+        this.testcase = testcase;
+        System.err.printf("Testing @OnClose for %s%n",testcase.closeClass.getName());
+    }
+
+    @Test
+    public void testOnCloseCall() throws Exception
+    {
+        // Scan annotations
+        AnnotatedClientEndpointMetadata metadata = new AnnotatedClientEndpointMetadata(container,testcase.closeClass);
+        AnnotatedEndpointScanner<ClientEndpoint, ClientEndpointConfig> scanner = new AnnotatedEndpointScanner<>(metadata);
+        scanner.scan();
+
+        // Build up EventDriver
+        WebSocketPolicy policy = WebSocketPolicy.newClientPolicy();
+        ClientEndpointConfig config = metadata.getConfig();
+        TrackingSocket endpoint = (TrackingSocket)testcase.closeClass.newInstance();
+        EndpointInstance ei = new EndpointInstance(endpoint,config,metadata);
+        JsrEvents<ClientEndpoint, ClientEndpointConfig> jsrevents = new JsrEvents<>(metadata);
+
+        EventDriver driver = new JsrAnnotatedEventDriver(policy,ei,jsrevents);
+
+        // Execute onClose call
+        driver.onClose(new CloseInfo(StatusCode.NORMAL,"normal"));
+
+        // Test captured event
+        EventQueue<String> events = endpoint.eventQueue;
+        Assert.assertThat("Number of Events Captured",events.size(),is(1));
+        String closeEvent = events.poll();
+        Assert.assertThat("Close Event",closeEvent,is(testcase.expectedCloseEvent));
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/TrackingSocket.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/TrackingSocket.java
new file mode 100644
index 0000000..c6f13c1
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/TrackingSocket.java
@@ -0,0 +1,127 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.endpoints;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import javax.websocket.CloseReason;
+import javax.websocket.CloseReason.CloseCode;
+
+import org.eclipse.jetty.toolchain.test.EventQueue;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.junit.Assert;
+
+import static org.hamcrest.Matchers.greaterThanOrEqualTo;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+
+/**
+ * Abstract base socket used for tracking state and events within the socket for testing reasons.
+ */
+public abstract class TrackingSocket
+{
+    private static final Logger LOG = Log.getLogger(TrackingSocket.class);
+
+    public CloseReason closeReason;
+    public EventQueue<String> eventQueue = new EventQueue<String>();
+    public EventQueue<Throwable> errorQueue = new EventQueue<>();
+    public CountDownLatch openLatch = new CountDownLatch(1);
+    public CountDownLatch closeLatch = new CountDownLatch(1);
+    public CountDownLatch dataLatch = new CountDownLatch(1);
+
+    protected void addError(Throwable t)
+    {
+        LOG.warn(t);
+        errorQueue.add(t);
+    }
+
+    protected void addEvent(String format, Object... args)
+    {
+        eventQueue.add(String.format(format,args));
+    }
+
+    public void assertClose(CloseCode expectedCode, String expectedReason) throws InterruptedException
+    {
+        assertCloseCode(expectedCode);
+        assertCloseReason(expectedReason);
+    }
+
+    public void assertCloseCode(CloseCode expectedCode) throws InterruptedException
+    {
+        Assert.assertThat("Was Closed",closeLatch.await(50,TimeUnit.MILLISECONDS),is(true));
+        Assert.assertThat("CloseReason",closeReason,notNullValue());
+        Assert.assertThat("Close Code",closeReason.getCloseCode(),is(expectedCode));
+    }
+
+    private void assertCloseReason(String expectedReason)
+    {
+        Assert.assertThat("Close Reason",closeReason.getReasonPhrase(),is(expectedReason));
+    }
+
+    public void assertEvent(String expected)
+    {
+        String actual = eventQueue.poll();
+        Assert.assertEquals("Event",expected,actual);
+    }
+
+    public void assertIsOpen() throws InterruptedException
+    {
+        assertWasOpened();
+        assertNotClosed();
+    }
+
+    public void assertNotClosed()
+    {
+        Assert.assertThat("Closed Latch",closeLatch.getCount(),greaterThanOrEqualTo(1L));
+    }
+
+    public void assertNotOpened()
+    {
+        Assert.assertThat("Open Latch",openLatch.getCount(),greaterThanOrEqualTo(1L));
+    }
+
+    public void assertWasOpened() throws InterruptedException
+    {
+        Assert.assertThat("Was Opened",openLatch.await(500,TimeUnit.MILLISECONDS),is(true));
+    }
+
+    public void clear()
+    {
+        eventQueue.clear();
+        errorQueue.clear();
+    }
+
+    public void waitForClose(int timeoutDuration, TimeUnit timeoutUnit) throws InterruptedException
+    {
+        Assert.assertThat("Client Socket Closed",closeLatch.await(timeoutDuration,timeoutUnit),is(true));
+    }
+
+    public void waitForConnected(int timeoutDuration, TimeUnit timeoutUnit) throws InterruptedException
+    {
+        Assert.assertThat("Client Socket Connected",openLatch.await(timeoutDuration,timeoutUnit),is(true));
+    }
+
+    public void waitForData(int timeoutDuration, TimeUnit timeoutUnit) throws InterruptedException
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("Waiting for message");
+        Assert.assertThat("Data Received",dataLatch.await(timeoutDuration,timeoutUnit),is(true));
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicBinaryMessageByteBufferSocket.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicBinaryMessageByteBufferSocket.java
new file mode 100644
index 0000000..535bccb
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicBinaryMessageByteBufferSocket.java
@@ -0,0 +1,37 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.endpoints.samples;
+
+import java.nio.ByteBuffer;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.OnMessage;
+
+import org.eclipse.jetty.websocket.jsr356.endpoints.TrackingSocket;
+
+ at ClientEndpoint
+public class BasicBinaryMessageByteBufferSocket extends TrackingSocket
+{
+    @OnMessage
+    public void onBinary(ByteBuffer data)
+    {
+        addEvent("onBinary(%s)",data);
+        dataLatch.countDown();
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicErrorSessionSocket.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicErrorSessionSocket.java
new file mode 100644
index 0000000..546703f
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicErrorSessionSocket.java
@@ -0,0 +1,35 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.endpoints.samples;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.OnError;
+import javax.websocket.Session;
+
+import org.eclipse.jetty.websocket.jsr356.endpoints.TrackingSocket;
+
+ at ClientEndpoint
+public class BasicErrorSessionSocket extends TrackingSocket
+{
+    @OnError
+    public void onError(Session session)
+    {
+        addEvent("onError(%s)",session);
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicErrorSessionThrowableSocket.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicErrorSessionThrowableSocket.java
new file mode 100644
index 0000000..51a7946
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicErrorSessionThrowableSocket.java
@@ -0,0 +1,36 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.endpoints.samples;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.OnError;
+import javax.websocket.Session;
+
+import org.eclipse.jetty.websocket.jsr356.endpoints.TrackingSocket;
+
+ at ClientEndpoint
+public class BasicErrorSessionThrowableSocket extends TrackingSocket
+{
+    @OnError
+    public void onError(Session session, Throwable t)
+    {
+        addEvent("onError(%s,%s)",session,t);
+        addError(t);
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicErrorSocket.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicErrorSocket.java
new file mode 100644
index 0000000..fbb167c
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicErrorSocket.java
@@ -0,0 +1,34 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.endpoints.samples;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.OnError;
+
+import org.eclipse.jetty.websocket.jsr356.endpoints.TrackingSocket;
+
+ at ClientEndpoint
+public class BasicErrorSocket extends TrackingSocket
+{
+    @OnError
+    public void onError()
+    {
+        addEvent("onError()");
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicErrorThrowableSessionSocket.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicErrorThrowableSessionSocket.java
new file mode 100644
index 0000000..4227f96
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicErrorThrowableSessionSocket.java
@@ -0,0 +1,36 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.endpoints.samples;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.OnError;
+import javax.websocket.Session;
+
+import org.eclipse.jetty.websocket.jsr356.endpoints.TrackingSocket;
+
+ at ClientEndpoint
+public class BasicErrorThrowableSessionSocket extends TrackingSocket
+{
+    @OnError
+    public void onError(Throwable t, Session session)
+    {
+        addEvent("onError(%s,%s)",t,session);
+        addError(t);
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicErrorThrowableSocket.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicErrorThrowableSocket.java
new file mode 100644
index 0000000..ade6ad7
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicErrorThrowableSocket.java
@@ -0,0 +1,35 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.endpoints.samples;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.OnError;
+
+import org.eclipse.jetty.websocket.jsr356.endpoints.TrackingSocket;
+
+ at ClientEndpoint
+public class BasicErrorThrowableSocket extends TrackingSocket
+{
+    @OnError
+    public void onError(Throwable t)
+    {
+        addEvent("onError(%s)",t);
+        addError(t);
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicInputStreamSocket.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicInputStreamSocket.java
new file mode 100644
index 0000000..9bf888c
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicInputStreamSocket.java
@@ -0,0 +1,47 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.endpoints.samples;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.OnMessage;
+
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.websocket.jsr356.endpoints.TrackingSocket;
+
+ at ClientEndpoint
+public class BasicInputStreamSocket extends TrackingSocket
+{
+    @OnMessage
+    public void onBinary(InputStream stream)
+    {
+        try
+        {
+            String msg = IO.toString(stream);
+            addEvent("onBinary(%s)",msg);
+        }
+        catch (IOException e)
+        {
+            super.errorQueue.add(e);
+        }
+        dataLatch.countDown();
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicInputStreamWithThrowableSocket.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicInputStreamWithThrowableSocket.java
new file mode 100644
index 0000000..19cc23e
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicInputStreamWithThrowableSocket.java
@@ -0,0 +1,39 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.endpoints.samples;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.OnMessage;
+
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.websocket.jsr356.endpoints.TrackingSocket;
+
+ at ClientEndpoint
+public class BasicInputStreamWithThrowableSocket extends TrackingSocket
+{
+    @OnMessage
+    public void onBinary(InputStream stream) throws IOException
+    {
+        String msg = IO.toString(stream);
+        addEvent("onBinary(%s)",msg);
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicOpenCloseSessionSocket.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicOpenCloseSessionSocket.java
new file mode 100644
index 0000000..d62ffbb
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicOpenCloseSessionSocket.java
@@ -0,0 +1,46 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.endpoints.samples;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.CloseReason;
+import javax.websocket.OnClose;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+
+import org.eclipse.jetty.websocket.jsr356.endpoints.TrackingSocket;
+
+ at ClientEndpoint
+public class BasicOpenCloseSessionSocket extends TrackingSocket
+{
+    @OnClose
+    public void onClose(CloseReason close, Session session)
+    {
+        addEvent("onClose(%s, %s)",close,session);
+        this.closeReason = close;
+        closeLatch.countDown();
+    }
+
+    @OnOpen
+    public void onOpen(Session session)
+    {
+        addEvent("onOpen(%s)",session);
+        openLatch.countDown();
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicOpenCloseSocket.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicOpenCloseSocket.java
new file mode 100644
index 0000000..2e95563
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicOpenCloseSocket.java
@@ -0,0 +1,41 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.endpoints.samples;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.CloseReason;
+import javax.websocket.OnClose;
+import javax.websocket.OnOpen;
+
+import org.eclipse.jetty.websocket.jsr356.endpoints.TrackingSocket;
+
+ at ClientEndpoint
+public class BasicOpenCloseSocket extends TrackingSocket
+{
+    @OnOpen
+    public void onOpen() {
+        openLatch.countDown();
+    }
+    
+    @OnClose
+    public void onClose(CloseReason close) {
+        this.closeReason = close;
+        closeLatch.countDown();
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicOpenSessionSocket.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicOpenSessionSocket.java
new file mode 100644
index 0000000..6a39113
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicOpenSessionSocket.java
@@ -0,0 +1,35 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.endpoints.samples;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+
+import org.eclipse.jetty.websocket.jsr356.endpoints.TrackingSocket;
+
+ at ClientEndpoint
+public class BasicOpenSessionSocket extends TrackingSocket
+{
+    @OnOpen
+    public void onOpen(Session session)
+    {
+        openLatch.countDown();
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicOpenSocket.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicOpenSocket.java
new file mode 100644
index 0000000..947a8cc
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicOpenSocket.java
@@ -0,0 +1,34 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.endpoints.samples;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.OnOpen;
+
+import org.eclipse.jetty.websocket.jsr356.endpoints.TrackingSocket;
+
+ at ClientEndpoint
+public class BasicOpenSocket extends TrackingSocket
+{
+    @OnOpen
+    public void onOpen()
+    {
+        openLatch.countDown();
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicPongMessageSocket.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicPongMessageSocket.java
new file mode 100644
index 0000000..504d2d5
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicPongMessageSocket.java
@@ -0,0 +1,36 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.endpoints.samples;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.OnMessage;
+import javax.websocket.PongMessage;
+
+import org.eclipse.jetty.websocket.jsr356.endpoints.TrackingSocket;
+
+ at ClientEndpoint
+public class BasicPongMessageSocket extends TrackingSocket
+{
+    @OnMessage
+    public void onPong(PongMessage pong)
+    {
+        addEvent("onPong(%s)",pong);
+        dataLatch.countDown();
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicTextMessageStringSocket.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicTextMessageStringSocket.java
new file mode 100644
index 0000000..1e9d9ed
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/BasicTextMessageStringSocket.java
@@ -0,0 +1,35 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.endpoints.samples;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.OnMessage;
+
+import org.eclipse.jetty.websocket.jsr356.endpoints.TrackingSocket;
+
+ at ClientEndpoint
+public class BasicTextMessageStringSocket extends TrackingSocket
+{
+    @OnMessage
+    public void onText(String message)
+    {
+        addEvent("onText(%s)",message);
+        dataLatch.countDown();
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/InvalidCloseIntSocket.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/InvalidCloseIntSocket.java
new file mode 100644
index 0000000..63faaec
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/InvalidCloseIntSocket.java
@@ -0,0 +1,37 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.endpoints.samples;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.OnClose;
+
+import org.eclipse.jetty.websocket.jsr356.endpoints.TrackingSocket;
+
+ at ClientEndpoint
+public class InvalidCloseIntSocket extends TrackingSocket
+{
+    /**
+     * Invalid Close Method Declaration (parameter type int)
+     */
+    @OnClose
+    public void onClose(int statusCode)
+    {
+        closeLatch.countDown();
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/InvalidErrorErrorSocket.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/InvalidErrorErrorSocket.java
new file mode 100644
index 0000000..65550ea
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/InvalidErrorErrorSocket.java
@@ -0,0 +1,37 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.endpoints.samples;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.OnError;
+
+import org.eclipse.jetty.websocket.jsr356.endpoints.TrackingSocket;
+
+ at ClientEndpoint
+public class InvalidErrorErrorSocket extends TrackingSocket
+{
+    /**
+     * Invalid Error Method Declaration (parameter type Error)
+     */
+    @OnError
+    public void onError(Error error)
+    {
+        /* no impl */
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/InvalidErrorExceptionSocket.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/InvalidErrorExceptionSocket.java
new file mode 100644
index 0000000..8899e5b
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/InvalidErrorExceptionSocket.java
@@ -0,0 +1,37 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.endpoints.samples;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.OnError;
+
+import org.eclipse.jetty.websocket.jsr356.endpoints.TrackingSocket;
+
+ at ClientEndpoint
+public class InvalidErrorExceptionSocket extends TrackingSocket
+{
+    /**
+     * Invalid Error Method Declaration (parameter type Exception)
+     */
+    @OnError
+    public void onError(Exception e)
+    {
+        /* no impl */
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/InvalidErrorIntSocket.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/InvalidErrorIntSocket.java
new file mode 100644
index 0000000..ab29ef4
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/InvalidErrorIntSocket.java
@@ -0,0 +1,37 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.endpoints.samples;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.OnError;
+
+import org.eclipse.jetty.websocket.jsr356.endpoints.TrackingSocket;
+
+ at ClientEndpoint
+public class InvalidErrorIntSocket extends TrackingSocket
+{
+    /**
+     * Invalid Error Method Declaration (parameter type int)
+     */
+    @OnError
+    public void onError(int errorCount)
+    {
+        /* no impl */
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/InvalidOpenCloseReasonSocket.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/InvalidOpenCloseReasonSocket.java
new file mode 100644
index 0000000..702a221
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/InvalidOpenCloseReasonSocket.java
@@ -0,0 +1,38 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.endpoints.samples;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.CloseReason;
+import javax.websocket.OnOpen;
+
+import org.eclipse.jetty.websocket.jsr356.endpoints.TrackingSocket;
+
+ at ClientEndpoint
+public class InvalidOpenCloseReasonSocket extends TrackingSocket
+{
+    /**
+     * Invalid Open Method Declaration (parameter type CloseReason)
+     */
+    @OnOpen
+    public void onOpen(CloseReason reason)
+    {
+        openLatch.countDown();
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/InvalidOpenIntSocket.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/InvalidOpenIntSocket.java
new file mode 100644
index 0000000..c528074
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/InvalidOpenIntSocket.java
@@ -0,0 +1,37 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.endpoints.samples;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.OnOpen;
+
+import org.eclipse.jetty.websocket.jsr356.endpoints.TrackingSocket;
+
+ at ClientEndpoint
+public class InvalidOpenIntSocket extends TrackingSocket
+{
+    /**
+     * Invalid Open Method Declaration (parameter type int)
+     */
+    @OnOpen
+    public void onOpen(int value)
+    {
+        openLatch.countDown();
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/InvalidOpenSessionIntSocket.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/InvalidOpenSessionIntSocket.java
new file mode 100644
index 0000000..bb73ed4
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/InvalidOpenSessionIntSocket.java
@@ -0,0 +1,38 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.endpoints.samples;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+
+import org.eclipse.jetty.websocket.jsr356.endpoints.TrackingSocket;
+
+ at ClientEndpoint
+public class InvalidOpenSessionIntSocket extends TrackingSocket
+{
+    /**
+     * Invalid Open Method Declaration (parameter of type int)
+     */
+    @OnOpen
+    public void onOpen(Session session, int count)
+    {
+        openLatch.countDown();
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/close/CloseEndpointConfigSocket.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/close/CloseEndpointConfigSocket.java
new file mode 100644
index 0000000..985087a
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/close/CloseEndpointConfigSocket.java
@@ -0,0 +1,36 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.endpoints.samples.close;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.EndpointConfig;
+import javax.websocket.OnClose;
+
+import org.eclipse.jetty.websocket.jsr356.endpoints.TrackingSocket;
+
+ at ClientEndpoint
+public class CloseEndpointConfigSocket extends TrackingSocket
+{
+    @OnClose
+    public void onClose(EndpointConfig config)
+    {
+        addEvent("onClose(EndpointConfig)");
+        closeLatch.countDown();
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/close/CloseReasonSessionSocket.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/close/CloseReasonSessionSocket.java
new file mode 100644
index 0000000..fabf583
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/close/CloseReasonSessionSocket.java
@@ -0,0 +1,37 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.endpoints.samples.close;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.CloseReason;
+import javax.websocket.OnClose;
+import javax.websocket.Session;
+
+import org.eclipse.jetty.websocket.jsr356.endpoints.TrackingSocket;
+
+ at ClientEndpoint
+public class CloseReasonSessionSocket extends TrackingSocket
+{
+    @OnClose
+    public void onClose(CloseReason reason, Session session)
+    {
+        addEvent("onClose(CloseReason,Session)");
+        closeLatch.countDown();
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/close/CloseReasonSocket.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/close/CloseReasonSocket.java
new file mode 100644
index 0000000..4a9fbc8
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/close/CloseReasonSocket.java
@@ -0,0 +1,36 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.endpoints.samples.close;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.CloseReason;
+import javax.websocket.OnClose;
+
+import org.eclipse.jetty.websocket.jsr356.endpoints.TrackingSocket;
+
+ at ClientEndpoint
+public class CloseReasonSocket extends TrackingSocket
+{
+    @OnClose
+    public void onClose(CloseReason reason)
+    {
+        addEvent("onClose(CloseReason)");
+        closeLatch.countDown();
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/close/CloseSessionReasonSocket.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/close/CloseSessionReasonSocket.java
new file mode 100644
index 0000000..f367d59
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/close/CloseSessionReasonSocket.java
@@ -0,0 +1,37 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.endpoints.samples.close;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.CloseReason;
+import javax.websocket.OnClose;
+import javax.websocket.Session;
+
+import org.eclipse.jetty.websocket.jsr356.endpoints.TrackingSocket;
+
+ at ClientEndpoint
+public class CloseSessionReasonSocket extends TrackingSocket
+{
+    @OnClose
+    public void onClose(Session session, CloseReason reason)
+    {
+        addEvent("onClose(Session,CloseReason)");
+        closeLatch.countDown();
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/close/CloseSessionSocket.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/close/CloseSessionSocket.java
new file mode 100644
index 0000000..24614ab
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/close/CloseSessionSocket.java
@@ -0,0 +1,36 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.endpoints.samples.close;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.OnClose;
+import javax.websocket.Session;
+
+import org.eclipse.jetty.websocket.jsr356.endpoints.TrackingSocket;
+
+ at ClientEndpoint
+public class CloseSessionSocket extends TrackingSocket
+{
+    @OnClose
+    public void onClose(Session session)
+    {
+        addEvent("onClose(Session)");
+        closeLatch.countDown();
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/close/CloseSocket.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/close/CloseSocket.java
new file mode 100644
index 0000000..cf58449
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/samples/close/CloseSocket.java
@@ -0,0 +1,35 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.endpoints.samples.close;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.OnClose;
+
+import org.eclipse.jetty.websocket.jsr356.endpoints.TrackingSocket;
+
+ at ClientEndpoint
+public class CloseSocket extends TrackingSocket
+{
+    @OnClose
+    public void onClose()
+    {
+        addEvent("onClose()");
+        closeLatch.countDown();
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/handlers/BaseMessageHandler.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/handlers/BaseMessageHandler.java
new file mode 100644
index 0000000..7453ed8
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/handlers/BaseMessageHandler.java
@@ -0,0 +1,30 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.handlers;
+
+import javax.websocket.MessageHandler;
+
+public class BaseMessageHandler implements MessageHandler.Whole<String>
+{
+    @Override
+    public void onMessage(String message)
+    {
+        // TODO Auto-generated method stub
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/handlers/ByteArrayPartialHandler.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/handlers/ByteArrayPartialHandler.java
new file mode 100644
index 0000000..80065cf
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/handlers/ByteArrayPartialHandler.java
@@ -0,0 +1,30 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.handlers;
+
+import javax.websocket.MessageHandler;
+
+public class ByteArrayPartialHandler implements MessageHandler.Partial<byte[]>
+{
+    @Override
+    public void onMessage(byte[] partialMessage, boolean last)
+    {
+        // TODO Auto-generated method stub
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/handlers/ByteArrayWholeHandler.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/handlers/ByteArrayWholeHandler.java
new file mode 100644
index 0000000..67c9847
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/handlers/ByteArrayWholeHandler.java
@@ -0,0 +1,30 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.handlers;
+
+import javax.websocket.MessageHandler;
+
+public class ByteArrayWholeHandler implements MessageHandler.Whole<byte[]>
+{
+    @Override
+    public void onMessage(byte[] message)
+    {
+        // TODO Auto-generated method stub
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/handlers/ByteBufferPartialHandler.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/handlers/ByteBufferPartialHandler.java
new file mode 100644
index 0000000..00c0a7d
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/handlers/ByteBufferPartialHandler.java
@@ -0,0 +1,32 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.handlers;
+
+import java.nio.ByteBuffer;
+
+import javax.websocket.MessageHandler;
+
+public class ByteBufferPartialHandler implements MessageHandler.Partial<ByteBuffer>
+{
+    @Override
+    public void onMessage(ByteBuffer partialMessage, boolean last)
+    {
+        // TODO Auto-generated method stub
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/handlers/ByteBufferWholeHandler.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/handlers/ByteBufferWholeHandler.java
new file mode 100644
index 0000000..5b2e628
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/handlers/ByteBufferWholeHandler.java
@@ -0,0 +1,32 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.handlers;
+
+import java.nio.ByteBuffer;
+
+import javax.websocket.MessageHandler;
+
+public class ByteBufferWholeHandler implements MessageHandler.Whole<ByteBuffer>
+{
+    @Override
+    public void onMessage(ByteBuffer message)
+    {
+        // TODO Auto-generated method stub
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/handlers/ComboMessageHandler.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/handlers/ComboMessageHandler.java
new file mode 100644
index 0000000..135e8e2
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/handlers/ComboMessageHandler.java
@@ -0,0 +1,41 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.handlers;
+
+import java.nio.ByteBuffer;
+
+import javax.websocket.MessageHandler;
+
+/**
+ * A particularly annoying type of MessageHandler. One defining 2 implementations.
+ */
+public class ComboMessageHandler implements MessageHandler.Whole<String>, MessageHandler.Partial<ByteBuffer>
+{
+    @Override
+    public void onMessage(ByteBuffer partialMessage, boolean last)
+    {
+        // TODO Auto-generated method stub
+    }
+
+    @Override
+    public void onMessage(String message)
+    {
+        // TODO Auto-generated method stub
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/handlers/ExtendedMessageHandler.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/handlers/ExtendedMessageHandler.java
new file mode 100644
index 0000000..75f5901
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/handlers/ExtendedMessageHandler.java
@@ -0,0 +1,32 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.handlers;
+
+import java.nio.ByteBuffer;
+
+import javax.websocket.MessageHandler;
+
+public class ExtendedMessageHandler extends BaseMessageHandler implements MessageHandler.Partial<ByteBuffer>
+{
+    @Override
+    public void onMessage(ByteBuffer partialMessage, boolean last)
+    {
+        // TODO Auto-generated method stub
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/handlers/InputStreamWholeHandler.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/handlers/InputStreamWholeHandler.java
new file mode 100644
index 0000000..9b89269
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/handlers/InputStreamWholeHandler.java
@@ -0,0 +1,32 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.handlers;
+
+import java.io.InputStream;
+
+import javax.websocket.MessageHandler;
+
+public class InputStreamWholeHandler implements MessageHandler.Whole<InputStream>
+{
+    @Override
+    public void onMessage(InputStream stream)
+    {
+        // TODO Auto-generated method stub
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/handlers/LongMessageHandler.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/handlers/LongMessageHandler.java
new file mode 100644
index 0000000..889a96e
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/handlers/LongMessageHandler.java
@@ -0,0 +1,29 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.handlers;
+
+import javax.websocket.MessageHandler;
+
+public class LongMessageHandler implements MessageHandler.Whole<Long>
+{
+    @Override
+    public void onMessage(Long message)
+    {
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/handlers/ReaderWholeHandler.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/handlers/ReaderWholeHandler.java
new file mode 100644
index 0000000..245cd71
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/handlers/ReaderWholeHandler.java
@@ -0,0 +1,32 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.handlers;
+
+import java.io.Reader;
+
+import javax.websocket.MessageHandler;
+
+public class ReaderWholeHandler implements MessageHandler.Whole<Reader>
+{
+    @Override
+    public void onMessage(Reader reader)
+    {
+        // TODO Auto-generated method stub
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/handlers/StringPartialHandler.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/handlers/StringPartialHandler.java
new file mode 100644
index 0000000..d3ded03
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/handlers/StringPartialHandler.java
@@ -0,0 +1,30 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.handlers;
+
+import javax.websocket.MessageHandler;
+
+public class StringPartialHandler implements MessageHandler.Partial<String>
+{
+    @Override
+    public void onMessage(String partialMessage, boolean last)
+    {
+        // TODO Auto-generated method stub
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/handlers/StringWholeHandler.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/handlers/StringWholeHandler.java
new file mode 100644
index 0000000..74c16b1
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/handlers/StringWholeHandler.java
@@ -0,0 +1,30 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.handlers;
+
+import javax.websocket.MessageHandler;
+
+public class StringWholeHandler implements MessageHandler.Whole<String>
+{
+    @Override
+    public void onMessage(String message)
+    {
+        // TODO Auto-generated method stub
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/metadata/DecoderMetadataSetTest.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/metadata/DecoderMetadataSetTest.java
new file mode 100644
index 0000000..b33157f
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/metadata/DecoderMetadataSetTest.java
@@ -0,0 +1,134 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.metadata;
+
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+
+import java.util.List;
+
+import javax.websocket.Decoder;
+
+import org.eclipse.jetty.websocket.jsr356.MessageType;
+import org.eclipse.jetty.websocket.jsr356.decoders.BadDualDecoder;
+import org.eclipse.jetty.websocket.jsr356.decoders.DateDecoder;
+import org.eclipse.jetty.websocket.jsr356.decoders.IntegerDecoder;
+import org.eclipse.jetty.websocket.jsr356.decoders.TimeDecoder;
+import org.eclipse.jetty.websocket.jsr356.decoders.ValidDualDecoder;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class DecoderMetadataSetTest
+{
+    private void assertMetadata(CoderMetadata<?> metadata, Class<?> expectedType, Class<?> expectedCoder, MessageType expectedMessageType)
+    {
+        Assert.assertEquals("metadata.coderClass",expectedCoder,metadata.getCoderClass());
+        Assert.assertThat("metadata.messageType",metadata.getMessageType(),is(expectedMessageType));
+        Assert.assertEquals("metadata.objectType",expectedType,metadata.getObjectType());
+    }
+
+    @Test
+    public void testAddBadDualDecoders()
+    {
+        try
+        {
+            DecoderMetadataSet coders = new DecoderMetadataSet();
+
+            // has duplicated support for the same target Type
+            coders.add(BadDualDecoder.class);
+            Assert.fail("Should have thrown IllegalStateException for attempting to register Decoders with duplicate implementation");
+        }
+        catch (IllegalStateException e)
+        {
+            Assert.assertThat(e.getMessage(),containsString("Duplicate"));
+        }
+    }
+
+    @Test
+    public void testAddDuplicate()
+    {
+        DecoderMetadataSet coders = new DecoderMetadataSet();
+
+        // Add DateDecoder (decodes java.util.Date)
+        coders.add(DateDecoder.class);
+
+        try
+        {
+            // Add TimeDecoder (which also wants to decode java.util.Date)
+            coders.add(TimeDecoder.class);
+            Assert.fail("Should have thrown IllegalStateException for attempting to register Decoders with duplicate implementation");
+        }
+        catch (IllegalStateException e)
+        {
+            Assert.assertThat(e.getMessage(),containsString("Duplicate"));
+        }
+    }
+
+    @Test
+    public void testAddGetCoder()
+    {
+        DecoderMetadataSet coders = new DecoderMetadataSet();
+
+        coders.add(IntegerDecoder.class);
+        Class<? extends Decoder> actualClazz = coders.getCoder(Integer.class);
+        Assert.assertEquals("Coder Class",IntegerDecoder.class,actualClazz);
+    }
+
+    @Test
+    public void testAddGetMetadataByImpl()
+    {
+        DecoderMetadataSet coders = new DecoderMetadataSet();
+
+        coders.add(IntegerDecoder.class);
+        List<DecoderMetadata> metadatas = coders.getMetadataByImplementation(IntegerDecoder.class);
+        Assert.assertThat("Metadatas (by impl) count",metadatas.size(),is(1));
+        DecoderMetadata metadata = metadatas.get(0);
+        assertMetadata(metadata,Integer.class,IntegerDecoder.class,MessageType.TEXT);
+    }
+
+    @Test
+    public void testAddGetMetadataByType()
+    {
+        DecoderMetadataSet coders = new DecoderMetadataSet();
+
+        coders.add(IntegerDecoder.class);
+        DecoderMetadata metadata = coders.getMetadataByType(Integer.class);
+        assertMetadata(metadata,Integer.class,IntegerDecoder.class,MessageType.TEXT);
+    }
+
+    @Test
+    public void testAddValidDualDecoders()
+    {
+        DecoderMetadataSet coders = new DecoderMetadataSet();
+
+        coders.add(ValidDualDecoder.class);
+
+        List<Class<? extends Decoder>> decodersList = coders.getList();
+        Assert.assertThat("Decoder List",decodersList,notNullValue());
+        Assert.assertThat("Decoder List count",decodersList.size(),is(2));
+
+        DecoderMetadata metadata;
+        metadata = coders.getMetadataByType(Integer.class);
+        assertMetadata(metadata,Integer.class,ValidDualDecoder.class,MessageType.TEXT);
+
+        metadata = coders.getMetadataByType(Long.class);
+        assertMetadata(metadata,Long.class,ValidDualDecoder.class,MessageType.BINARY);
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/metadata/EncoderMetadataSetTest.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/metadata/EncoderMetadataSetTest.java
new file mode 100644
index 0000000..7c84141
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/metadata/EncoderMetadataSetTest.java
@@ -0,0 +1,134 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.metadata;
+
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+
+import java.util.List;
+
+import javax.websocket.Encoder;
+
+import org.eclipse.jetty.websocket.jsr356.MessageType;
+import org.eclipse.jetty.websocket.jsr356.encoders.BadDualEncoder;
+import org.eclipse.jetty.websocket.jsr356.encoders.DateEncoder;
+import org.eclipse.jetty.websocket.jsr356.encoders.IntegerEncoder;
+import org.eclipse.jetty.websocket.jsr356.encoders.TimeEncoder;
+import org.eclipse.jetty.websocket.jsr356.encoders.ValidDualEncoder;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class EncoderMetadataSetTest
+{
+    private void assertMetadata(CoderMetadata<?> metadata, Class<?> expectedType, Class<?> expectedCoder, MessageType expectedMessageType)
+    {
+        Assert.assertEquals("metadata.coderClass",expectedCoder,metadata.getCoderClass());
+        Assert.assertThat("metadata.messageType",metadata.getMessageType(),is(expectedMessageType));
+        Assert.assertEquals("metadata.objectType",expectedType,metadata.getObjectType());
+    }
+
+    @Test
+    public void testAddBadDualEncoders()
+    {
+        try
+        {
+            EncoderMetadataSet coders = new EncoderMetadataSet();
+
+            // has duplicated support for the same target Type
+            coders.add(BadDualEncoder.class);
+            Assert.fail("Should have thrown IllegalStateException for attempting to register Encoders with duplicate implementation");
+        }
+        catch (IllegalStateException e)
+        {
+            Assert.assertThat(e.getMessage(),containsString("Duplicate"));
+        }
+    }
+
+    @Test
+    public void testAddDuplicate()
+    {
+        EncoderMetadataSet coders = new EncoderMetadataSet();
+
+        // Add DateEncoder (decodes java.util.Date)
+        coders.add(DateEncoder.class);
+
+        try
+        {
+            // Add TimeEncoder (which also wants to decode java.util.Date)
+            coders.add(TimeEncoder.class);
+            Assert.fail("Should have thrown IllegalStateException for attempting to register Encoders with duplicate implementation");
+        }
+        catch (IllegalStateException e)
+        {
+            Assert.assertThat(e.getMessage(),containsString("Duplicate"));
+        }
+    }
+
+    @Test
+    public void testAddGetCoder()
+    {
+        EncoderMetadataSet coders = new EncoderMetadataSet();
+
+        coders.add(IntegerEncoder.class);
+        Class<? extends Encoder> actualClazz = coders.getCoder(Integer.class);
+        Assert.assertEquals("Coder Class",IntegerEncoder.class,actualClazz);
+    }
+
+    @Test
+    public void testAddGetMetadataByImpl()
+    {
+        EncoderMetadataSet coders = new EncoderMetadataSet();
+
+        coders.add(IntegerEncoder.class);
+        List<EncoderMetadata> metadatas = coders.getMetadataByImplementation(IntegerEncoder.class);
+        Assert.assertThat("Metadatas (by impl) count",metadatas.size(),is(1));
+        EncoderMetadata metadata = metadatas.get(0);
+        assertMetadata(metadata,Integer.class,IntegerEncoder.class,MessageType.TEXT);
+    }
+
+    @Test
+    public void testAddGetMetadataByType()
+    {
+        EncoderMetadataSet coders = new EncoderMetadataSet();
+
+        coders.add(IntegerEncoder.class);
+        EncoderMetadata metadata = coders.getMetadataByType(Integer.class);
+        assertMetadata(metadata,Integer.class,IntegerEncoder.class,MessageType.TEXT);
+    }
+
+    @Test
+    public void testAddValidDualEncoders()
+    {
+        EncoderMetadataSet coders = new EncoderMetadataSet();
+
+        coders.add(ValidDualEncoder.class);
+
+        List<Class<? extends Encoder>> EncodersList = coders.getList();
+        Assert.assertThat("Encoder List",EncodersList,notNullValue());
+        Assert.assertThat("Encoder List count",EncodersList.size(),is(2));
+
+        EncoderMetadata metadata;
+        metadata = coders.getMetadataByType(Integer.class);
+        assertMetadata(metadata,Integer.class,ValidDualEncoder.class,MessageType.TEXT);
+
+        metadata = coders.getMetadataByType(Long.class);
+        assertMetadata(metadata,Long.class,ValidDualEncoder.class,MessageType.BINARY);
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/misbehaving/AnnotatedRuntimeOnOpen.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/misbehaving/AnnotatedRuntimeOnOpen.java
new file mode 100644
index 0000000..e841e7d
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/misbehaving/AnnotatedRuntimeOnOpen.java
@@ -0,0 +1,65 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.misbehaving;
+
+import java.util.LinkedList;
+import java.util.concurrent.CountDownLatch;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.CloseReason;
+import javax.websocket.EndpointConfig;
+import javax.websocket.OnClose;
+import javax.websocket.OnError;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+
+/**
+ * A JSR-356 Annotated that tosses a RuntimeException during its onOpen call
+ */
+ at ClientEndpoint
+public class AnnotatedRuntimeOnOpen
+{
+    public CountDownLatch closeLatch = new CountDownLatch(1);
+    public CloseReason closeReason;
+    public LinkedList<Throwable> errors = new LinkedList<>();
+
+    @OnOpen
+    public void onOpen(Session session, EndpointConfig config)
+    {
+        // Intentional runtime exception.
+        int[] arr = new int[5];
+        for (int i = 0; i < 10; i++)
+        {
+            arr[i] = 222;
+        }
+    }
+
+    @OnClose
+    public void onClose(Session session, CloseReason closeReason)
+    {
+        this.closeReason = closeReason;
+        closeLatch.countDown();
+    }
+
+    @OnError
+    public void onError(Session session, Throwable thr)
+    {
+        errors.add(thr);
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/misbehaving/EndpointRuntimeOnOpen.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/misbehaving/EndpointRuntimeOnOpen.java
new file mode 100644
index 0000000..b9a9eea
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/misbehaving/EndpointRuntimeOnOpen.java
@@ -0,0 +1,63 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.misbehaving;
+
+import java.util.LinkedList;
+import java.util.concurrent.CountDownLatch;
+
+import javax.websocket.CloseReason;
+import javax.websocket.Endpoint;
+import javax.websocket.EndpointConfig;
+import javax.websocket.Session;
+
+/**
+ * A JSR-356 Endpoint that tosses a RuntimeException during its onOpen call
+ */
+public class EndpointRuntimeOnOpen extends Endpoint
+{
+    public CountDownLatch closeLatch = new CountDownLatch(1);
+    public CloseReason closeReason;
+    public LinkedList<Throwable> errors = new LinkedList<>();
+
+    @Override
+    public void onOpen(Session session, EndpointConfig config)
+    {
+        // Intentional runtime exception.
+        int[] arr = new int[5];
+        for (int i = 0; i < 10; i++)
+        {
+            arr[i] = 222;
+        }
+    }
+
+    @Override
+    public void onClose(Session session, CloseReason closeReason)
+    {
+        super.onClose(session,closeReason);
+        this.closeReason = closeReason;
+        closeLatch.countDown();
+    }
+
+    @Override
+    public void onError(Session session, Throwable thr)
+    {
+        super.onError(session,thr);
+        errors.add(thr);
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/misbehaving/MisbehavingClassTest.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/misbehaving/MisbehavingClassTest.java
new file mode 100644
index 0000000..ce00648
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/misbehaving/MisbehavingClassTest.java
@@ -0,0 +1,127 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.misbehaving;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+import java.net.URI;
+import java.util.concurrent.TimeUnit;
+
+import javax.websocket.ContainerProvider;
+import javax.websocket.Session;
+import javax.websocket.WebSocketContainer;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.util.log.StacklessLogging;
+import org.eclipse.jetty.websocket.jsr356.EchoHandler;
+import org.eclipse.jetty.websocket.jsr356.endpoints.JsrEndpointEventDriver;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class MisbehavingClassTest
+{
+    private static Server server;
+    private static EchoHandler handler;
+    private static URI serverUri;
+
+    @BeforeClass
+    public static void startServer() throws Exception
+    {
+        server = new Server();
+        ServerConnector connector = new ServerConnector(server);
+        server.addConnector(connector);
+
+        handler = new EchoHandler();
+
+        ContextHandler context = new ContextHandler();
+        context.setContextPath("/");
+        context.setHandler(handler);
+        server.setHandler(context);
+
+        // Start Server
+        server.start();
+
+        String host = connector.getHost();
+        if (host == null)
+        {
+            host = "localhost";
+        }
+        int port = connector.getLocalPort();
+        serverUri = new URI(String.format("ws://%s:%d/",host,port));
+    }
+
+    @AfterClass
+    public static void stopServer()
+    {
+        try
+        {
+            server.stop();
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace(System.err);
+        }
+    }
+
+    @Test
+    public void testEndpointRuntimeOnOpen() throws Exception
+    {
+        WebSocketContainer container = ContainerProvider.getWebSocketContainer();
+        EndpointRuntimeOnOpen socket = new EndpointRuntimeOnOpen();
+
+        try (StacklessLogging logging = new StacklessLogging(EndpointRuntimeOnOpen.class,JsrEndpointEventDriver.class))
+        {
+            // expecting ArrayIndexOutOfBoundsException during onOpen
+            Session session = container.connectToServer(socket,serverUri);
+            assertThat("Close should have occurred",socket.closeLatch.await(1,TimeUnit.SECONDS),is(true));
+
+            // technically, the session object isn't invalid here.
+            assertThat("Session.isOpen",session.isOpen(),is(false));
+            assertThat("Should have only had 1 error",socket.errors.size(),is(1));
+
+            Throwable cause = socket.errors.pop();
+            assertThat("Error",cause,instanceOf(ArrayIndexOutOfBoundsException.class));
+        }
+    }
+
+    @Test
+    public void testAnnotatedRuntimeOnOpen() throws Exception
+    {
+        WebSocketContainer container = ContainerProvider.getWebSocketContainer();
+        AnnotatedRuntimeOnOpen socket = new AnnotatedRuntimeOnOpen();
+
+        try (StacklessLogging logging = new StacklessLogging(AnnotatedRuntimeOnOpen.class))
+        {
+            // expecting ArrayIndexOutOfBoundsException during onOpen
+            Session session = container.connectToServer(socket,serverUri);
+            assertThat("Close should have occurred",socket.closeLatch.await(1,TimeUnit.SECONDS),is(true));
+
+            // technically, the session object isn't invalid here.
+            assertThat("Session.isOpen",session.isOpen(),is(false));
+            assertThat("Should have only had 1 error",socket.errors.size(),is(1));
+
+            Throwable cause = socket.errors.pop();
+            assertThat("Error",cause,instanceOf(ArrayIndexOutOfBoundsException.class));
+        }
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/samples/AbstractStringEndpoint.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/samples/AbstractStringEndpoint.java
new file mode 100644
index 0000000..e2ecf6a
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/samples/AbstractStringEndpoint.java
@@ -0,0 +1,60 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.samples;
+
+import javax.websocket.CloseReason;
+import javax.websocket.Endpoint;
+import javax.websocket.EndpointConfig;
+import javax.websocket.MessageHandler;
+import javax.websocket.Session;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * Base Abstract Class.
+ */
+public abstract class AbstractStringEndpoint extends Endpoint implements MessageHandler.Whole<String>
+{
+    private static final Logger LOG = Log.getLogger(AbstractStringEndpoint.class);
+    protected Session session;
+    protected EndpointConfig config;
+    
+    @Override
+    public void onOpen(Session session, EndpointConfig config)
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("onOpen({}, {})",session,config);
+        session.addMessageHandler(this);
+        this.session = session;
+        this.config = config;
+    }
+
+    public void onClose(Session session, CloseReason closeReason)
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("onClose({}, {})",session,closeReason);
+        this.session = null;
+    }
+
+    public void onError(Session session, Throwable thr)
+    {
+        LOG.warn("onError()",thr);
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/samples/DummyConnection.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/samples/DummyConnection.java
new file mode 100644
index 0000000..9a18951
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/samples/DummyConnection.java
@@ -0,0 +1,156 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.samples;
+
+import java.net.InetSocketAddress;
+import java.util.concurrent.Executor;
+
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.websocket.api.BatchMode;
+import org.eclipse.jetty.websocket.api.SuspendToken;
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.api.WriteCallback;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.api.extensions.IncomingFrames;
+import org.eclipse.jetty.websocket.common.LogicalConnection;
+import org.eclipse.jetty.websocket.common.WebSocketSession;
+import org.eclipse.jetty.websocket.common.io.IOState;
+
+public class DummyConnection implements LogicalConnection
+{
+    private IOState iostate;
+
+    public DummyConnection()
+    {
+        this.iostate = new IOState();
+    }
+
+    @Override
+    public void close()
+    {
+    }
+
+    @Override
+    public void close(int statusCode, String reason)
+    {
+    }
+
+    @Override
+    public void disconnect()
+    {
+    }
+
+    @Override
+    public ByteBufferPool getBufferPool()
+    {
+        return null;
+    }
+
+    @Override
+    public Executor getExecutor()
+    {
+        return null;
+    }
+
+    @Override
+    public long getIdleTimeout()
+    {
+        return 0;
+    }
+
+    @Override
+    public IOState getIOState()
+    {
+        return this.iostate;
+    }
+
+    @Override
+    public InetSocketAddress getLocalAddress()
+    {
+        return null;
+    }
+
+    @Override
+    public long getMaxIdleTimeout()
+    {
+        return 0;
+    }
+
+    @Override
+    public WebSocketPolicy getPolicy()
+    {
+        return null;
+    }
+
+    @Override
+    public InetSocketAddress getRemoteAddress()
+    {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public WebSocketSession getSession()
+    {
+        return null;
+    }
+
+    @Override
+    public boolean isOpen()
+    {
+        return false;
+    }
+
+    @Override
+    public boolean isReading()
+    {
+        return false;
+    }
+
+    @Override
+    public void outgoingFrame(Frame frame, WriteCallback callback, BatchMode batchMode)
+    {
+    }
+
+    @Override
+    public void resume()
+    {
+    }
+
+    @Override
+    public void setMaxIdleTimeout(long ms)
+    {
+    }
+
+    @Override
+    public void setNextIncomingFrames(IncomingFrames incoming)
+    {
+    }
+
+    @Override
+    public void setSession(WebSocketSession session)
+    {
+    }
+
+    @Override
+    public SuspendToken suspend()
+    {
+        return null;
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/samples/DummyEndpoint.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/samples/DummyEndpoint.java
new file mode 100644
index 0000000..6bc2673
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/samples/DummyEndpoint.java
@@ -0,0 +1,32 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.samples;
+
+import javax.websocket.Endpoint;
+import javax.websocket.EndpointConfig;
+import javax.websocket.Session;
+
+public class DummyEndpoint extends Endpoint
+{
+    @Override
+    public void onOpen(Session session, EndpointConfig config)
+    {
+        /* do nothing */
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/samples/EchoStringEndpoint.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/samples/EchoStringEndpoint.java
new file mode 100644
index 0000000..aa27225
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/samples/EchoStringEndpoint.java
@@ -0,0 +1,36 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.samples;
+
+import org.eclipse.jetty.websocket.jsr356.MessageQueue;
+
+/**
+ * Legitimate structure for an Endpoint
+ */
+public class EchoStringEndpoint extends AbstractStringEndpoint
+{
+    public MessageQueue messageQueue = new MessageQueue();
+    
+    @Override
+    public void onMessage(String message)
+    {
+        messageQueue.offer(message);
+        session.getAsyncRemote().sendText(message);
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/samples/ExtDecoder.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/samples/ExtDecoder.java
new file mode 100644
index 0000000..65b9426
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/samples/ExtDecoder.java
@@ -0,0 +1,29 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.samples;
+
+import javax.websocket.Decoder;
+
+/**
+ * Testing scenario of an extended Decoder interface
+ */
+public interface ExtDecoder<T> extends Decoder.Text<T>
+{
+    void setId(String id);
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/samples/Fruit.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/samples/Fruit.java
new file mode 100644
index 0000000..b2877ae
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/samples/Fruit.java
@@ -0,0 +1,25 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.samples;
+
+public class Fruit
+{
+    public String name;
+    public String color;
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/samples/FruitBinaryEncoder.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/samples/FruitBinaryEncoder.java
new file mode 100644
index 0000000..2abbb55
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/samples/FruitBinaryEncoder.java
@@ -0,0 +1,68 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.samples;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+
+import javax.websocket.EncodeException;
+import javax.websocket.Encoder;
+import javax.websocket.EndpointConfig;
+
+import org.eclipse.jetty.util.BufferUtil;
+
+public class FruitBinaryEncoder implements Encoder.Binary<Fruit>
+{
+    public static final byte FRUIT_ID_BYTE = (byte)0xAF;
+    // the number of bytes to store a string (1 int)
+    public static final int STRLEN_STORAGE = 4;
+
+    @Override
+    public void destroy()
+    {
+    }
+
+    @Override
+    public ByteBuffer encode(Fruit fruit) throws EncodeException
+    {
+        int len = 1; // id byte
+        len += STRLEN_STORAGE + fruit.name.length();
+        len += STRLEN_STORAGE + fruit.color.length();
+
+        ByteBuffer buf = ByteBuffer.allocate(len + 64);
+        buf.flip();
+        buf.put(FRUIT_ID_BYTE);
+        putString(buf,fruit.name);
+        putString(buf,fruit.color);
+        buf.flip();
+
+        return buf;
+    }
+
+    @Override
+    public void init(EndpointConfig config)
+    {
+    }
+
+    private void putString(ByteBuffer buf, String str)
+    {
+        buf.putInt(str.length());
+        BufferUtil.toBuffer(str,Charset.forName("UTF-8"));
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/samples/FruitDecoder.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/samples/FruitDecoder.java
new file mode 100644
index 0000000..2ea1cfa
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/samples/FruitDecoder.java
@@ -0,0 +1,82 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.samples;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.websocket.DecodeException;
+import javax.websocket.EndpointConfig;
+
+public class FruitDecoder implements ExtDecoder<Fruit>
+{
+    private String id;
+
+    @Override
+    public Fruit decode(String s) throws DecodeException
+    {
+        Pattern pat = Pattern.compile("([^|]*)|([^|]*)");
+        Matcher mat = pat.matcher(s);
+        if (!mat.find())
+        {
+            throw new DecodeException(s,"Unable to find Fruit reference encoded in text message");
+        }
+
+        Fruit fruit = new Fruit();
+        fruit.name = mat.group(1);
+        fruit.color = mat.group(2);
+
+        return fruit;
+    }
+
+    @Override
+    public void destroy()
+    {
+    }
+
+    @Override
+    public void init(EndpointConfig config)
+    {
+    }
+
+    @Override
+    public void setId(String id)
+    {
+        this.id = id;
+    }
+
+    @Override
+    public String toString()
+    {
+        return "SecondDecoder[id=" + id + "]";
+    }
+
+    @Override
+    public boolean willDecode(String s)
+    {
+        if (s == null)
+        {
+            return false;
+        }
+
+        Pattern pat = Pattern.compile("([^|]*)|([^|]*)");
+        Matcher mat = pat.matcher(s);
+        return (mat.find());
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/samples/FruitTextEncoder.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/samples/FruitTextEncoder.java
new file mode 100644
index 0000000..9196139
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/samples/FruitTextEncoder.java
@@ -0,0 +1,42 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.samples;
+
+import javax.websocket.EncodeException;
+import javax.websocket.Encoder;
+import javax.websocket.EndpointConfig;
+
+public class FruitTextEncoder implements Encoder.Text<Fruit>
+{
+    @Override
+    public void destroy()
+    {
+    }
+
+    @Override
+    public String encode(Fruit fruit) throws EncodeException
+    {
+        return String.format("%s|%s",fruit.name,fruit.color);
+    }
+
+    @Override
+    public void init(EndpointConfig config)
+    {
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/samples/IntSocket.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/samples/IntSocket.java
new file mode 100644
index 0000000..ea55c8b
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/samples/IntSocket.java
@@ -0,0 +1,46 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.samples;
+
+import java.io.IOException;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.EncodeException;
+import javax.websocket.OnMessage;
+import javax.websocket.Session;
+
+import org.eclipse.jetty.websocket.jsr356.decoders.BadDualDecoder;
+
+ at ClientEndpoint(decoders =
+{ BadDualDecoder.class })
+public class IntSocket
+{
+    @OnMessage
+    public void onInt(Session session, int value)
+    {
+        try
+        {
+            session.getBasicRemote().sendObject(value);
+        }
+        catch (IOException | EncodeException e)
+        {
+            e.printStackTrace();
+        }
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/utils/ReflectUtilsTest.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/utils/ReflectUtilsTest.java
new file mode 100644
index 0000000..a0fc159
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/utils/ReflectUtilsTest.java
@@ -0,0 +1,138 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.utils;
+
+import static org.hamcrest.Matchers.nullValue;
+
+import org.eclipse.jetty.websocket.common.util.ReflectUtils;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class ReflectUtilsTest
+{
+    public static interface Fruit<T>
+    {
+    }
+
+    public static interface Color<T>
+    {
+    }
+
+    public static interface Food<T> extends Fruit<T>
+    {
+    }
+
+    public static abstract class Apple<T extends Object> implements Fruit<T>, Color<String>
+    {
+    }
+
+    public static abstract class Cherry<A extends Object, B extends Number> implements Fruit<A>, Color<B>
+    {
+    }
+
+    public static abstract class Banana implements Fruit<String>, Color<String>
+    {
+    }
+
+    public static class Washington<Z extends Number, X extends Object> extends Cherry<X, Z>
+    {
+    }
+
+    public static class Rainier extends Washington<Float, Short>
+    {
+    }
+
+    public static class Pizza implements Food<Integer>
+    {
+    }
+
+    public static class Cavendish extends Banana
+    {
+    }
+
+    public static class GrannySmith extends Apple<Long>
+    {
+    }
+
+    public static class Pear implements Fruit<String>, Color<Double>
+    {
+    }
+
+    public static class Kiwi implements Fruit<Character>
+    {
+    }
+
+    @Test
+    public void testFindGeneric_PearFruit()
+    {
+        assertFindGenericClass(Pear.class,Fruit.class,String.class);
+    }
+
+    @Test
+    public void testFindGeneric_PizzaFruit()
+    {
+        assertFindGenericClass(Pizza.class,Fruit.class,Integer.class);
+    }
+
+    @Test
+    public void testFindGeneric_KiwiFruit()
+    {
+        assertFindGenericClass(Kiwi.class,Fruit.class,Character.class);
+    }
+
+    @Test
+    public void testFindGeneric_PearColor()
+    {
+        assertFindGenericClass(Pear.class,Color.class,Double.class);
+    }
+
+    @Test
+    public void testFindGeneric_GrannySmithFruit()
+    {
+        assertFindGenericClass(GrannySmith.class,Fruit.class,Long.class);
+    }
+
+    @Test
+    public void testFindGeneric_CavendishFruit()
+    {
+        assertFindGenericClass(Cavendish.class,Fruit.class,String.class);
+    }
+
+    @Test
+    public void testFindGeneric_RainierFruit()
+    {
+        assertFindGenericClass(Rainier.class,Fruit.class,Short.class);
+    }
+
+    @Test
+    public void testFindGeneric_WashingtonFruit()
+    {
+        // Washington does not have a concrete implementation
+        // of the Fruit interface, this should return null
+        Class<?> impl = ReflectUtils.findGenericClassFor(Washington.class,Fruit.class);
+        Assert.assertThat("Washington -> Fruit implementation",impl,nullValue());
+    }
+
+    private void assertFindGenericClass(Class<?> baseClass, Class<?> ifaceClass, Class<?> expectedClass)
+    {
+        Class<?> foundClass = ReflectUtils.findGenericClassFor(baseClass,ifaceClass);
+        String msg = String.format("Expecting %s<%s> found on %s",ifaceClass.getName(),expectedClass.getName(),baseClass.getName());
+        Assert.assertEquals(msg,expectedClass,foundClass);
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/utils/TypeTree.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/utils/TypeTree.java
new file mode 100644
index 0000000..6c06966
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/utils/TypeTree.java
@@ -0,0 +1,120 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.utils;
+
+import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.lang.reflect.WildcardType;
+
+import org.eclipse.jetty.websocket.common.util.ReflectUtils;
+
+public class TypeTree
+{
+    public static void dumpTree(String indent, Type type)
+    {
+        if ((type == null) || (type == Object.class))
+        {
+            return;
+        }
+
+        if (type instanceof Class<?>)
+        {
+            Class<?> ctype = (Class<?>)type;
+            System.out.printf("%s (Class) = %s%n",indent,ctype.getName());
+
+            String name = ctype.getName();
+            if (name.startsWith("java.lang.") || name.startsWith("java.io."))
+            {
+                // filter away standard classes from tree (otherwise it will go on infinitely)
+                return;
+            }
+
+            Type superType = ctype.getGenericSuperclass();
+            dumpTree(indent + ".genericSuperClass()",superType);
+
+            Type[] ifaces = ctype.getGenericInterfaces();
+            if ((ifaces != null) && (ifaces.length > 0))
+            {
+                // System.out.printf("%s.genericInterfaces[].length = %d%n",indent,ifaces.length);
+                for (int i = 0; i < ifaces.length; i++)
+                {
+                    Type iface = ifaces[i];
+                    dumpTree(indent + ".genericInterfaces[" + i + "]",iface);
+                }
+            }
+
+            TypeVariable<?>[] typeParams = ctype.getTypeParameters();
+            if ((typeParams != null) && (typeParams.length > 0))
+            {
+                // System.out.printf("%s.typeParameters[].length = %d%n",indent,typeParams.length);
+                for (int i = 0; i < typeParams.length; i++)
+                {
+                    TypeVariable<?> typeParam = typeParams[i];
+                    dumpTree(indent + ".typeParameters[" + i + "]",typeParam);
+                }
+            }
+            return;
+        }
+
+        if (type instanceof ParameterizedType)
+        {
+            ParameterizedType ptype = (ParameterizedType)type;
+            System.out.printf("%s (ParameterizedType) = %s%n",indent,ReflectUtils.toShortName(ptype));
+            // dumpTree(indent + ".ownerType()",ptype.getOwnerType());
+            dumpTree(indent + ".rawType(" + ReflectUtils.toShortName(ptype.getRawType()) + ")",ptype.getRawType());
+            Type args[] = ptype.getActualTypeArguments();
+            if (args != null)
+            {
+                System.out.printf("%s.actualTypeArguments[].length = %d%n",indent,args.length);
+                for (int i = 0; i < args.length; i++)
+                {
+                    Type arg = args[i];
+                    dumpTree(indent + ".actualTypeArguments[" + i + "]",arg);
+                }
+            }
+            return;
+        }
+
+        if (type instanceof GenericArrayType)
+        {
+            GenericArrayType gtype = (GenericArrayType)type;
+            System.out.printf("%s (GenericArrayType) = %s%n",indent,gtype);
+            return;
+        }
+
+        if (type instanceof TypeVariable<?>)
+        {
+            TypeVariable<?> tvar = (TypeVariable<?>)type;
+            System.out.printf("%s (TypeVariable) = %s%n",indent,tvar);
+            System.out.printf("%s.getName() = %s%n",indent,tvar.getName());
+            System.out.printf("%s.getGenericDeclaration() = %s%n",indent,tvar.getGenericDeclaration());
+            return;
+        }
+
+        if (type instanceof WildcardType)
+        {
+            System.out.printf("%s (WildcardType) = %s%n",indent,type);
+            return;
+        }
+
+        System.out.printf("%s (?) = %s%n",indent,type);
+    }
+}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/resources/jetty-logging.properties b/jetty-websocket/javax-websocket-client-impl/src/test/resources/jetty-logging.properties
new file mode 100644
index 0000000..6c5baae
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/resources/jetty-logging.properties
@@ -0,0 +1,5 @@
+org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
+org.eclipse.jetty.LEVEL=WARN
+
+# org.eclipse.jetty.websocket.LEVEL=WARN
+# org.eclipse.jetty.websocket.jsr356.LEVEL=DEBUG
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/resources/quotes-ben.txt b/jetty-websocket/javax-websocket-client-impl/src/test/resources/quotes-ben.txt
new file mode 100644
index 0000000..6b9ae8d
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/resources/quotes-ben.txt
@@ -0,0 +1,4 @@
+a|Benjamin Franklin
+q|There never was a good war or a bad peace.
+q|We must, indeed, all hang together, or assuredly we shall all hang separately.
+q|Our new Constitution is now established, and has an appearance that promises permanency; but in this world nothing can be said to be certain, except death and taxes.
\ No newline at end of file
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/resources/quotes-twain.txt b/jetty-websocket/javax-websocket-client-impl/src/test/resources/quotes-twain.txt
new file mode 100644
index 0000000..4a0d6bc
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/resources/quotes-twain.txt
@@ -0,0 +1,5 @@
+a|Mark Twain
+q|He is now fast rising from affluence to poverty.
+q|A baby is an inestimable blessing and bother.
+q|As I slowly grow wise I briskly grow cautious.
+q|A circle is a round straight line with a hole in the middle.
\ No newline at end of file
diff --git a/jetty-websocket/javax-websocket-server-impl/pom.xml b/jetty-websocket/javax-websocket-server-impl/pom.xml
new file mode 100644
index 0000000..b9ae2df
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/pom.xml
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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">
+  <parent>
+    <groupId>org.eclipse.jetty.websocket</groupId>
+    <artifactId>websocket-parent</artifactId>
+    <version>9.2.13.v20150730</version>
+  </parent>
+
+  <modelVersion>4.0.0</modelVersion>
+  <artifactId>javax-websocket-server-impl</artifactId>
+  <name>Jetty :: Websocket :: javax.websocket.server :: Server Implementation</name>
+
+  <properties>
+    <bundle-symbolic-name>${project.groupId}.javax.websocket.server</bundle-symbolic-name>
+  </properties>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-annotations</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.websocket</groupId>
+      <artifactId>javax-websocket-client-impl</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.websocket</groupId>
+      <artifactId>websocket-server</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>javax.websocket</groupId>
+      <artifactId>javax.websocket-api</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.websocket</groupId>
+      <artifactId>websocket-common</artifactId>
+      <version>${project.version}</version>
+      <classifier>tests</classifier>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.toolchain</groupId>
+      <artifactId>jetty-test-helper</artifactId>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+          <groupId>org.apache.felix</groupId>
+          <artifactId>maven-bundle-plugin</artifactId>
+          <extensions>true</extensions>
+          <executions>
+              <execution>
+                  <goals>
+                      <goal>manifest</goal>
+                  </goals>
+                  <configuration>
+                      <instructions>
+                        <Bundle-Description>javax.websocket.server Implementation</Bundle-Description>
+                        <Export-Package>org.eclipse.jetty.websocket.jsr356.server.*;version="${parsedVersion.majorVersion}.${parsedVersion.minorVersion}.${parsedVersion.incrementalVersion}"</Export-Package>
+                        <Require-Capability>osgi.extender; filter:="(osgi.extender=osgi.serviceloader.registrar)";resolution:=optional</Require-Capability>
+                        <Provide-Capability>osgi.serviceloader; osgi.serviceloader=javax.servlet.ServletContainerInitializer</Provide-Capability>
+                      </instructions>
+                  </configuration>
+              </execution>
+          </executions>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-assembly-plugin</artifactId>
+        <executions>
+          <execution>
+            <phase>package</phase>
+            <goals>
+              <goal>single</goal>
+            </goals>
+            <configuration>
+              <descriptorRefs>
+                <descriptorRef>config</descriptorRef>
+              </descriptorRefs>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+</project>
diff --git a/jetty-websocket/javax-websocket-server-impl/src/main/config/modules/websocket.mod b/jetty-websocket/javax-websocket-server-impl/src/main/config/modules/websocket.mod
new file mode 100644
index 0000000..e866b17
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/main/config/modules/websocket.mod
@@ -0,0 +1,12 @@
+#
+# WebSocket Module
+#
+
+[depend]
+# javax.websocket needs annotations
+annotations
+
+[lib]
+lib/websocket/*.jar
+
+
diff --git a/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/AnnotatedServerEndpointConfig.java b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/AnnotatedServerEndpointConfig.java
new file mode 100644
index 0000000..ebc6ff9
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/AnnotatedServerEndpointConfig.java
@@ -0,0 +1,209 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.websocket.Decoder;
+import javax.websocket.DeploymentException;
+import javax.websocket.Encoder;
+import javax.websocket.Extension;
+import javax.websocket.server.ServerEndpoint;
+import javax.websocket.server.ServerEndpointConfig;
+
+public class AnnotatedServerEndpointConfig implements ServerEndpointConfig
+{
+    private final Class<?> endpointClass;
+    private final String path;
+    private final List<Class<? extends Decoder>> decoders;
+    private final List<Class<? extends Encoder>> encoders;
+    private final ServerEndpointConfig.Configurator configurator;
+    private final List<String> subprotocols;
+
+    private Map<String, Object> userProperties;
+    private List<Extension> extensions;
+
+    public AnnotatedServerEndpointConfig(Class<?> endpointClass, ServerEndpoint anno) throws DeploymentException
+    {
+        this(endpointClass,anno,null);
+    }
+
+    public AnnotatedServerEndpointConfig(Class<?> endpointClass, ServerEndpoint anno, ServerEndpointConfig baseConfig) throws DeploymentException
+    {
+        ServerEndpointConfig.Configurator configr = null;
+
+        // Copy from base config
+        if (baseConfig != null)
+        {
+            configr = baseConfig.getConfigurator();
+        }
+
+        // Decoders (favor provided config over annotation)
+        if (baseConfig != null && baseConfig.getDecoders() != null && baseConfig.getDecoders().size() > 0)
+        {
+            this.decoders = Collections.unmodifiableList(baseConfig.getDecoders());
+        }
+        else
+        {
+            this.decoders = Collections.unmodifiableList(Arrays.asList(anno.decoders()));
+        }
+
+        // Encoders (favor provided config over annotation)
+        if (baseConfig != null && baseConfig.getEncoders() != null && baseConfig.getEncoders().size() > 0)
+        {
+            this.encoders = Collections.unmodifiableList(baseConfig.getEncoders());
+        }
+        else
+        {
+            this.encoders = Collections.unmodifiableList(Arrays.asList(anno.encoders()));
+        }
+
+        // Sub Protocols (favor provided config over annotation)
+        if (baseConfig != null && baseConfig.getSubprotocols() != null && baseConfig.getSubprotocols().size() > 0)
+        {
+            this.subprotocols = Collections.unmodifiableList(baseConfig.getSubprotocols());
+        }
+        else
+        {
+            this.subprotocols = Collections.unmodifiableList(Arrays.asList(anno.subprotocols()));
+        }
+
+        // Path (favor provided config over annotation)
+        if (baseConfig != null && baseConfig.getPath() != null && baseConfig.getPath().length() > 0)
+        {
+            this.path = baseConfig.getPath();
+        }
+        else
+        {
+            this.path = anno.value();
+        }
+
+        // supplied by init lifecycle
+        this.extensions = new ArrayList<>();
+        // always what is passed in
+        this.endpointClass = endpointClass;
+        // UserProperties in annotation
+        this.userProperties = new HashMap<>();
+        if (baseConfig != null && baseConfig.getUserProperties() != null && baseConfig.getUserProperties().size() > 0)
+        {
+            userProperties.putAll(baseConfig.getUserProperties());
+        }
+
+        if (anno.configurator() == ServerEndpointConfig.Configurator.class)
+        {
+            if (configr != null)
+            {
+                this.configurator = configr;
+            }
+            else
+            {
+                this.configurator = BasicServerEndpointConfigurator.INSTANCE;
+            }
+        }
+        else
+        {
+            try
+            {
+                this.configurator = anno.configurator().newInstance();
+            }
+            catch (InstantiationException | IllegalAccessException e)
+            {
+                StringBuilder err = new StringBuilder();
+                err.append("Unable to instantiate ClientEndpoint.configurator() of ");
+                err.append(anno.configurator().getName());
+                err.append(" defined as annotation in ");
+                err.append(anno.getClass().getName());
+                throw new DeploymentException(err.toString(),e);
+            }
+        }
+    }
+
+    @Override
+    public ServerEndpointConfig.Configurator getConfigurator()
+    {
+        return configurator;
+    }
+
+    @Override
+    public List<Class<? extends Decoder>> getDecoders()
+    {
+        return decoders;
+    }
+
+    @Override
+    public List<Class<? extends Encoder>> getEncoders()
+    {
+        return encoders;
+    }
+
+    @Override
+    public Class<?> getEndpointClass()
+    {
+        return endpointClass;
+    }
+
+    @Override
+    public List<Extension> getExtensions()
+    {
+        return extensions;
+    }
+
+    @Override
+    public String getPath()
+    {
+        return path;
+    }
+
+    @Override
+    public List<String> getSubprotocols()
+    {
+        return subprotocols;
+    }
+
+    @Override
+    public Map<String, Object> getUserProperties()
+    {
+        return userProperties;
+    }
+
+    @Override
+    public String toString()
+    {
+        StringBuilder builder = new StringBuilder();
+        builder.append("AnnotatedServerEndpointConfig[endpointClass=");
+        builder.append(endpointClass);
+        builder.append(",path=");
+        builder.append(path);
+        builder.append(",decoders=");
+        builder.append(decoders);
+        builder.append(",encoders=");
+        builder.append(encoders);
+        builder.append(",subprotocols=");
+        builder.append(subprotocols);
+        builder.append(",extensions=");
+        builder.append(extensions);
+        builder.append("]");
+        return builder.toString();
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/AnnotatedServerEndpointMetadata.java b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/AnnotatedServerEndpointMetadata.java
new file mode 100644
index 0000000..b5deb90
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/AnnotatedServerEndpointMetadata.java
@@ -0,0 +1,108 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import java.util.LinkedList;
+
+import javax.websocket.DeploymentException;
+import javax.websocket.server.ServerEndpoint;
+import javax.websocket.server.ServerEndpointConfig;
+
+import org.eclipse.jetty.websocket.api.InvalidWebSocketException;
+import org.eclipse.jetty.websocket.jsr356.annotations.AnnotatedEndpointMetadata;
+import org.eclipse.jetty.websocket.jsr356.annotations.IJsrParamId;
+
+public class AnnotatedServerEndpointMetadata extends AnnotatedEndpointMetadata<ServerEndpoint,ServerEndpointConfig> implements ServerEndpointMetadata
+{
+    private final ServerEndpoint endpoint;
+    private final AnnotatedServerEndpointConfig config;
+
+    protected AnnotatedServerEndpointMetadata(Class<?> websocket, ServerEndpointConfig baseConfig) throws DeploymentException
+    {
+        super(websocket);
+
+        ServerEndpoint anno = websocket.getAnnotation(ServerEndpoint.class);
+        if (anno == null)
+        {
+            throw new InvalidWebSocketException("Unsupported WebSocket object, missing @" + ServerEndpoint.class + " annotation");
+        }
+
+        this.endpoint = anno;
+        this.config = new AnnotatedServerEndpointConfig(websocket,anno,baseConfig);
+        
+        getDecoders().addAll(anno.decoders());
+        getEncoders().addAll(anno.encoders());
+    }
+
+    @Override
+    public void customizeParamsOnClose(LinkedList<IJsrParamId> params)
+    {
+        super.customizeParamsOnClose(params);
+        params.addFirst(JsrPathParamId.INSTANCE);
+    }
+
+    @Override
+    public void customizeParamsOnError(LinkedList<IJsrParamId> params)
+    {
+        super.customizeParamsOnError(params);
+        params.addFirst(JsrPathParamId.INSTANCE);
+    }
+    
+    @Override
+    public void customizeParamsOnOpen(LinkedList<IJsrParamId> params)
+    {
+        super.customizeParamsOnOpen(params);
+        params.addFirst(JsrPathParamId.INSTANCE);
+    }
+    
+    @Override
+    public void customizeParamsOnMessage(LinkedList<IJsrParamId> params)
+    {
+        super.customizeParamsOnMessage(params);
+        params.addFirst(JsrPathParamId.INSTANCE);
+    }
+
+    @Override
+    public ServerEndpoint getAnnotation()
+    {
+        return endpoint;
+    }
+
+    public AnnotatedServerEndpointConfig getConfig()
+    {
+        return config;
+    }
+
+    public String getPath()
+    {
+        return config.getPath();
+    }
+
+    @Override
+    public String toString()
+    {
+        StringBuilder builder = new StringBuilder();
+        builder.append("AnnotatedServerEndpointMetadata[endpoint=");
+        builder.append(endpoint);
+        builder.append(",config=");
+        builder.append(config);
+        builder.append("]");
+        return builder.toString();
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/BasicServerEndpointConfig.java b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/BasicServerEndpointConfig.java
new file mode 100644
index 0000000..5b9bb70
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/BasicServerEndpointConfig.java
@@ -0,0 +1,125 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.websocket.Decoder;
+import javax.websocket.Encoder;
+import javax.websocket.Extension;
+import javax.websocket.server.ServerEndpointConfig;
+
+public class BasicServerEndpointConfig implements ServerEndpointConfig
+{
+    private final List<Class<? extends Decoder>> decoders;
+    private final List<Class<? extends Encoder>> encoders;
+    private final List<Extension> extensions;
+    private final List<String> subprotocols;
+    private final ServerEndpointConfig.Configurator configurator;
+    private final Class<?> endpointClass;
+    private final String path;
+    private Map<String, Object> userProperties;
+
+    public BasicServerEndpointConfig(Class<?> endpointClass, String path)
+    {
+        this.endpointClass = endpointClass;
+        this.path = path;
+
+        this.decoders = new ArrayList<>();
+        this.encoders = new ArrayList<>();
+        this.subprotocols = new ArrayList<>();
+        this.extensions = new ArrayList<>();
+        this.userProperties = new HashMap<>();
+        this.configurator = BasicServerEndpointConfigurator.INSTANCE;
+    }
+
+    public BasicServerEndpointConfig(ServerEndpointConfig copy)
+    {
+        // immutable concepts
+        this.endpointClass = copy.getEndpointClass();
+        this.path = copy.getPath();
+
+        this.decoders = copy.getDecoders();
+        this.encoders = copy.getEncoders();
+        this.subprotocols = copy.getSubprotocols();
+        this.extensions = copy.getExtensions();
+        if (copy.getConfigurator() != null)
+        {
+            this.configurator = copy.getConfigurator();
+        }
+        else
+        {
+            this.configurator = BasicServerEndpointConfigurator.INSTANCE;
+        }
+
+        // mutable concepts
+        this.userProperties = new HashMap<>(copy.getUserProperties());
+    }
+
+    @Override
+    public List<Class<? extends Encoder>> getEncoders()
+    {
+        return encoders;
+    }
+
+    @Override
+    public List<Class<? extends Decoder>> getDecoders()
+    {
+        return decoders;
+    }
+
+    @Override
+    public Map<String, Object> getUserProperties()
+    {
+        return userProperties;
+    }
+
+    @Override
+    public Class<?> getEndpointClass()
+    {
+        return endpointClass;
+    }
+
+    @Override
+    public String getPath()
+    {
+        return path;
+    }
+
+    @Override
+    public List<String> getSubprotocols()
+    {
+        return subprotocols;
+    }
+
+    @Override
+    public List<Extension> getExtensions()
+    {
+        return extensions;
+    }
+
+    @Override
+    public ServerEndpointConfig.Configurator getConfigurator()
+    {
+        return configurator;
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/BasicServerEndpointConfigurator.java b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/BasicServerEndpointConfigurator.java
new file mode 100644
index 0000000..fc7d7fd
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/BasicServerEndpointConfigurator.java
@@ -0,0 +1,108 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import java.util.List;
+import javax.websocket.Extension;
+import javax.websocket.HandshakeResponse;
+import javax.websocket.server.HandshakeRequest;
+import javax.websocket.server.ServerEndpointConfig;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.util.QuoteUtil;
+
+public class BasicServerEndpointConfigurator extends ServerEndpointConfig.Configurator
+{
+    private static final Logger LOG = Log.getLogger(BasicServerEndpointConfigurator.class);
+    private static final String NO_SUBPROTOCOL = "";
+    public static final ServerEndpointConfig.Configurator INSTANCE = new BasicServerEndpointConfigurator();
+
+    @Override
+    public boolean checkOrigin(String originHeaderValue)
+    {
+        return true;
+    }
+
+    @Override
+    public <T> T getEndpointInstance(Class<T> endpointClass) throws InstantiationException
+    {
+        if (LOG.isDebugEnabled())
+        {
+            LOG.debug(".getEndpointInstance({})",endpointClass);
+        }
+        try
+        {
+            return endpointClass.newInstance();
+        }
+        catch (IllegalAccessException e)
+        {
+            throw new InstantiationException(String.format("%s: %s",e.getClass().getName(),e.getMessage()));
+        }
+    }
+
+    @Override
+    public List<Extension> getNegotiatedExtensions(List<Extension> installed, List<Extension> requested)
+    {
+        return requested;
+    }
+
+    @Override
+    public String getNegotiatedSubprotocol(List<String> supported, List<String> requested)
+    {
+        if ((requested == null) || (requested.size() == 0))
+        {
+            // nothing requested, don't return anything
+            return NO_SUBPROTOCOL;
+        }
+
+        // Nothing specifically called out as being supported by the endpoint
+        if ((supported == null) || (supported.isEmpty()))
+        {
+            // Just return the first hit in this case
+            LOG.warn("Client requested Subprotocols on endpoint with none supported: {}",QuoteUtil.join(requested,","));
+            return NO_SUBPROTOCOL;
+        }
+
+        // Return the first matching hit from the list of supported protocols.
+        for (String possible : requested)
+        {
+            if (possible == null)
+            {
+                // skip null
+                continue;
+            }
+
+            if (supported.contains(possible))
+            {
+                return possible;
+            }
+        }
+
+        LOG.warn("Client requested subprotocols {} do not match any endpoint supported subprotocols {}",QuoteUtil.join(requested,","),
+                QuoteUtil.join(supported,","));
+        return NO_SUBPROTOCOL;
+    }
+
+    @Override
+    public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response)
+    {
+        /* do nothing */
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/JsrCreator.java b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/JsrCreator.java
new file mode 100644
index 0000000..1c43be3
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/JsrCreator.java
@@ -0,0 +1,162 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.websocket.Extension;
+import javax.websocket.Extension.Parameter;
+import javax.websocket.server.ServerEndpointConfig;
+
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
+import org.eclipse.jetty.websocket.api.extensions.ExtensionFactory;
+import org.eclipse.jetty.websocket.jsr356.JsrExtension;
+import org.eclipse.jetty.websocket.jsr356.endpoints.EndpointInstance;
+import org.eclipse.jetty.websocket.jsr356.server.pathmap.WebSocketPathSpec;
+import org.eclipse.jetty.websocket.server.pathmap.PathSpec;
+import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
+import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
+import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
+
+public class JsrCreator implements WebSocketCreator
+{
+    public static final String PROP_REMOTE_ADDRESS = "javax.websocket.endpoint.remoteAddress";
+    public static final String PROP_LOCAL_ADDRESS = "javax.websocket.endpoint.localAddress";
+    private static final Logger LOG = Log.getLogger(JsrCreator.class);
+    private final ServerEndpointMetadata metadata;
+    private final ExtensionFactory extensionFactory;
+
+    public JsrCreator(ServerEndpointMetadata metadata, ExtensionFactory extensionFactory)
+    {
+        this.metadata = metadata;
+        this.extensionFactory = extensionFactory;
+    }
+
+    @Override
+    public Object createWebSocket(ServletUpgradeRequest req, ServletUpgradeResponse resp)
+    {
+        JsrHandshakeRequest hsreq = new JsrHandshakeRequest(req);
+        JsrHandshakeResponse hsresp = new JsrHandshakeResponse(resp);
+
+        // Get raw config, as defined when the endpoint was added to the container
+        ServerEndpointConfig config = metadata.getConfig();
+        
+        // Establish a copy of the config, so that the UserProperties are unique
+        // per upgrade request.
+        config = new BasicServerEndpointConfig(config);
+        
+        // Bug 444617 - Expose localAddress and remoteAddress for jsr modify handshake to use
+        // This is being implemented as an optional set of userProperties so that
+        // it is not JSR api breaking.  A few users on #jetty and a few from cometd
+        // have asked for access to this information.
+        config.getUserProperties().put(PROP_LOCAL_ADDRESS,req.getLocalSocketAddress());
+        config.getUserProperties().put(PROP_REMOTE_ADDRESS,req.getRemoteSocketAddress());
+
+        // Get Configurator from config object (not guaranteed to be unique per endpoint upgrade)
+        ServerEndpointConfig.Configurator configurator = config.getConfigurator();
+
+        // modify handshake
+        configurator.modifyHandshake(config,hsreq,hsresp);
+
+        // check origin
+        if (!configurator.checkOrigin(req.getOrigin()))
+        {
+            try
+            {
+                resp.sendForbidden("Origin mismatch");
+            }
+            catch (IOException e)
+            {
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Unable to send error response",e);
+            }
+            return null;
+        }
+
+        // deal with sub protocols
+        List<String> supported = config.getSubprotocols();
+        List<String> requested = req.getSubProtocols();
+        String subprotocol = configurator.getNegotiatedSubprotocol(supported,requested);
+        if (StringUtil.isNotBlank(subprotocol))
+        {
+            resp.setAcceptedSubProtocol(subprotocol);
+        }
+
+        // deal with extensions
+        List<Extension> installedExts = new ArrayList<>();
+        for (String extName : extensionFactory.getAvailableExtensions().keySet())
+        {
+            installedExts.add(new JsrExtension(extName));
+        }
+        List<Extension> requestedExts = new ArrayList<>();
+        for (ExtensionConfig reqCfg : req.getExtensions())
+        {
+            requestedExts.add(new JsrExtension(reqCfg));
+        }
+        List<Extension> usedExts = configurator.getNegotiatedExtensions(installedExts,requestedExts);
+        List<ExtensionConfig> configs = new ArrayList<>();
+        if (usedExts != null)
+        {
+            for (Extension used : usedExts)
+            {
+                ExtensionConfig ecfg = new ExtensionConfig(used.getName());
+                for (Parameter param : used.getParameters())
+                {
+                    ecfg.setParameter(param.getName(),param.getValue());
+                }
+                configs.add(ecfg);
+            }
+        }
+        resp.setExtensions(configs);
+
+        // create endpoint class
+        try
+        {
+            Class<?> endpointClass = config.getEndpointClass();
+            Object endpoint = config.getConfigurator().getEndpointInstance(endpointClass);
+            PathSpec pathSpec = hsreq.getRequestPathSpec();
+            if (pathSpec instanceof WebSocketPathSpec)
+            {
+                // We have a PathParam path spec
+                WebSocketPathSpec wspathSpec = (WebSocketPathSpec)pathSpec;
+                String requestPath = req.getRequestPath();
+                // Wrap the config with the path spec information
+                config = new PathParamServerEndpointConfig(config,wspathSpec,requestPath);
+            }
+            return new EndpointInstance(endpoint,config,metadata);
+        }
+        catch (InstantiationException e)
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("Unable to create websocket: " + config.getEndpointClass().getName(),e);
+            return null;
+        }
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s[metadata=%s]",this.getClass().getName(),metadata);
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/JsrHandshakeRequest.java b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/JsrHandshakeRequest.java
new file mode 100644
index 0000000..6db246d
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/JsrHandshakeRequest.java
@@ -0,0 +1,86 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import java.net.URI;
+import java.security.Principal;
+import java.util.List;
+import java.util.Map;
+
+import javax.websocket.server.HandshakeRequest;
+
+import org.eclipse.jetty.websocket.server.pathmap.PathSpec;
+import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
+
+public class JsrHandshakeRequest implements HandshakeRequest
+{
+    private final ServletUpgradeRequest request;
+
+    public JsrHandshakeRequest(ServletUpgradeRequest req)
+    {
+        this.request = req;
+    }
+
+    @Override
+    public Map<String, List<String>> getHeaders()
+    {
+        return request.getHeaders();
+    }
+
+    @Override
+    public Object getHttpSession()
+    {
+        return request.getSession();
+    }
+
+    @Override
+    public Map<String, List<String>> getParameterMap()
+    {
+        return request.getParameterMap();
+    }
+
+    @Override
+    public String getQueryString()
+    {
+        return request.getQueryString();
+    }
+
+    public PathSpec getRequestPathSpec()
+    {
+        return (PathSpec)request.getServletAttribute(PathSpec.class.getName());
+    }
+
+    @Override
+    public URI getRequestURI()
+    {
+        return request.getRequestURI();
+    }
+
+    @Override
+    public Principal getUserPrincipal()
+    {
+        return request.getUserPrincipal();
+    }
+
+    @Override
+    public boolean isUserInRole(String role)
+    {
+        return request.isUserInRole(role);
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/JsrHandshakeResponse.java b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/JsrHandshakeResponse.java
new file mode 100644
index 0000000..7a66344
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/JsrHandshakeResponse.java
@@ -0,0 +1,42 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import java.util.List;
+import java.util.Map;
+
+import javax.websocket.HandshakeResponse;
+
+import org.eclipse.jetty.websocket.api.UpgradeResponse;
+
+public class JsrHandshakeResponse implements HandshakeResponse
+{
+    private final UpgradeResponse response;
+
+    public JsrHandshakeResponse(UpgradeResponse resp)
+    {
+        this.response = resp;
+    }
+
+    @Override
+    public Map<String, List<String>> getHeaders()
+    {
+        return response.getHeaders();
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/JsrPathParamId.java b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/JsrPathParamId.java
new file mode 100644
index 0000000..9839821
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/JsrPathParamId.java
@@ -0,0 +1,49 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import javax.websocket.server.PathParam;
+
+import org.eclipse.jetty.websocket.common.events.annotated.InvalidSignatureException;
+import org.eclipse.jetty.websocket.jsr356.annotations.IJsrParamId;
+import org.eclipse.jetty.websocket.jsr356.annotations.JsrCallable;
+import org.eclipse.jetty.websocket.jsr356.annotations.Param;
+import org.eclipse.jetty.websocket.jsr356.annotations.Param.Role;
+
+/**
+ * Param handling for static parameters annotated with @{@link PathParam}
+ */
+public class JsrPathParamId implements IJsrParamId
+{
+    public static final IJsrParamId INSTANCE = new JsrPathParamId();
+
+    @Override
+    public boolean process(Param param, JsrCallable callable) throws InvalidSignatureException
+    {
+        PathParam pathparam = param.getAnnotation(PathParam.class);
+        if(pathparam != null)
+        {
+            param.bind(Role.PATH_PARAM);
+            param.setPathParamName(pathparam.value());
+            return true;
+        }
+
+        return false;
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/JsrServerEndpointImpl.java b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/JsrServerEndpointImpl.java
new file mode 100644
index 0000000..fb5612e
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/JsrServerEndpointImpl.java
@@ -0,0 +1,110 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import javax.websocket.OnMessage;
+import javax.websocket.server.ServerEndpoint;
+import javax.websocket.server.ServerEndpointConfig;
+
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.common.events.EventDriver;
+import org.eclipse.jetty.websocket.common.events.EventDriverImpl;
+import org.eclipse.jetty.websocket.jsr356.annotations.JsrEvents;
+import org.eclipse.jetty.websocket.jsr356.annotations.OnMessageCallable;
+import org.eclipse.jetty.websocket.jsr356.endpoints.EndpointInstance;
+import org.eclipse.jetty.websocket.jsr356.endpoints.JsrAnnotatedEventDriver;
+
+/**
+ * Event Driver for classes annotated with @{@link ServerEndpoint}
+ */
+public class JsrServerEndpointImpl implements EventDriverImpl
+{
+    @Override
+    public EventDriver create(Object websocket, WebSocketPolicy policy) throws Throwable
+    {
+        if (!(websocket instanceof EndpointInstance))
+        {
+            throw new IllegalStateException(String.format("Websocket %s must be an %s",websocket.getClass().getName(),EndpointInstance.class.getName()));
+        }
+
+        EndpointInstance ei = (EndpointInstance)websocket;
+        AnnotatedServerEndpointMetadata metadata = (AnnotatedServerEndpointMetadata)ei.getMetadata();
+        JsrEvents<ServerEndpoint, ServerEndpointConfig> events = new JsrEvents<>(metadata);
+
+        // Handle @OnMessage maxMessageSizes
+        int maxBinaryMessage = getMaxMessageSize(policy.getMaxBinaryMessageSize(),metadata.onBinary,metadata.onBinaryStream);
+        int maxTextMessage = getMaxMessageSize(policy.getMaxTextMessageSize(),metadata.onText,metadata.onTextStream);
+
+        policy.setMaxBinaryMessageSize(maxBinaryMessage);
+        policy.setMaxTextMessageSize(maxTextMessage);
+
+        JsrAnnotatedEventDriver driver = new JsrAnnotatedEventDriver(policy,ei,events);
+        // Handle @PathParam values
+        ServerEndpointConfig config = (ServerEndpointConfig)ei.getConfig();
+        if (config instanceof PathParamServerEndpointConfig)
+        {
+            PathParamServerEndpointConfig ppconfig = (PathParamServerEndpointConfig)config;
+            driver.setPathParameters(ppconfig.getPathParamMap());
+        }
+
+        return driver;
+    }
+
+    @Override
+    public String describeRule()
+    {
+        return "class is annotated with @" + ServerEndpoint.class.getName();
+    }
+
+    private int getMaxMessageSize(int defaultMaxMessageSize, OnMessageCallable... onMessages)
+    {
+        for (OnMessageCallable callable : onMessages)
+        {
+            if (callable == null)
+            {
+                continue;
+            }
+            OnMessage onMsg = callable.getMethod().getAnnotation(OnMessage.class);
+            if (onMsg == null)
+            {
+                continue;
+            }
+            if (onMsg.maxMessageSize() > 0)
+            {
+                return (int)onMsg.maxMessageSize();
+            }
+        }
+        return defaultMaxMessageSize;
+    }
+
+    @Override
+    public boolean supports(Object websocket)
+    {
+        if (!(websocket instanceof EndpointInstance))
+        {
+            return false;
+        }
+
+        EndpointInstance ei = (EndpointInstance)websocket;
+        Object endpoint = ei.getEndpoint();
+
+        ServerEndpoint anno = endpoint.getClass().getAnnotation(ServerEndpoint.class);
+        return (anno != null);
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/JsrServerExtendsEndpointImpl.java b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/JsrServerExtendsEndpointImpl.java
new file mode 100644
index 0000000..3ba578c
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/JsrServerExtendsEndpointImpl.java
@@ -0,0 +1,71 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import javax.websocket.server.ServerEndpointConfig;
+
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.common.events.EventDriver;
+import org.eclipse.jetty.websocket.common.events.EventDriverImpl;
+import org.eclipse.jetty.websocket.jsr356.endpoints.EndpointInstance;
+import org.eclipse.jetty.websocket.jsr356.endpoints.JsrEndpointEventDriver;
+
+public class JsrServerExtendsEndpointImpl implements EventDriverImpl
+{
+    @Override
+    public EventDriver create(Object websocket, WebSocketPolicy policy)
+    {
+        if (!(websocket instanceof EndpointInstance))
+        {
+            throw new IllegalStateException(String.format("Websocket %s must be an %s",websocket.getClass().getName(),EndpointInstance.class.getName()));
+        }
+        
+        EndpointInstance ei = (EndpointInstance)websocket;
+        JsrEndpointEventDriver driver = new JsrEndpointEventDriver(policy, ei);
+        
+        ServerEndpointConfig config = (ServerEndpointConfig)ei.getConfig();
+        if (config instanceof PathParamServerEndpointConfig)
+        {
+            PathParamServerEndpointConfig ppconfig = (PathParamServerEndpointConfig)config;
+            driver.setPathParameters(ppconfig.getPathParamMap());
+        }
+
+        return driver;
+    }
+
+    @Override
+    public String describeRule()
+    {
+        return "class extends " + javax.websocket.Endpoint.class.getName();
+    }
+
+    @Override
+    public boolean supports(Object websocket)
+    {
+        if (!(websocket instanceof EndpointInstance))
+        {
+            return false;
+        }
+
+        EndpointInstance ei = (EndpointInstance)websocket;
+        Object endpoint = ei.getEndpoint();
+
+        return (endpoint instanceof javax.websocket.Endpoint);
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/PathParamServerEndpointConfig.java b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/PathParamServerEndpointConfig.java
new file mode 100644
index 0000000..d5a906d
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/PathParamServerEndpointConfig.java
@@ -0,0 +1,51 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.websocket.server.ServerEndpointConfig;
+
+import org.eclipse.jetty.websocket.jsr356.server.pathmap.WebSocketPathSpec;
+
+/**
+ * Wrapper for a {@link ServerEndpointConfig} where there PathParm information from the incoming request.
+ */
+public class PathParamServerEndpointConfig extends BasicServerEndpointConfig implements ServerEndpointConfig
+{
+    private final Map<String, String> pathParamMap;
+
+    public PathParamServerEndpointConfig(ServerEndpointConfig config, WebSocketPathSpec pathSpec, String requestPath)
+    {
+        super(config);
+
+        Map<String, String> pathMap = pathSpec.getPathParams(requestPath);
+        pathParamMap = new HashMap<String, String>();
+        if (pathMap != null)
+        {
+            pathParamMap.putAll(pathMap);
+        }
+    }
+
+    public Map<String, String> getPathParamMap()
+    {
+        return pathParamMap;
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/ServerContainer.java b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/ServerContainer.java
new file mode 100644
index 0000000..f8829ee
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/ServerContainer.java
@@ -0,0 +1,190 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import java.util.concurrent.Executor;
+
+import javax.websocket.DeploymentException;
+import javax.websocket.Endpoint;
+import javax.websocket.server.ServerEndpoint;
+import javax.websocket.server.ServerEndpointConfig;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.common.events.EventDriverFactory;
+import org.eclipse.jetty.websocket.jsr356.ClientContainer;
+import org.eclipse.jetty.websocket.jsr356.JsrSessionFactory;
+import org.eclipse.jetty.websocket.jsr356.annotations.AnnotatedEndpointScanner;
+import org.eclipse.jetty.websocket.jsr356.endpoints.EndpointInstance;
+import org.eclipse.jetty.websocket.jsr356.metadata.EndpointMetadata;
+import org.eclipse.jetty.websocket.jsr356.server.pathmap.WebSocketPathSpec;
+import org.eclipse.jetty.websocket.server.MappedWebSocketCreator;
+import org.eclipse.jetty.websocket.server.WebSocketServerFactory;
+
+public class ServerContainer extends ClientContainer implements javax.websocket.server.ServerContainer
+{
+    private static final Logger LOG = Log.getLogger(ServerContainer.class);
+
+    private final MappedWebSocketCreator mappedCreator;
+    private final WebSocketServerFactory webSocketServerFactory;
+
+    public ServerContainer(MappedWebSocketCreator creator, WebSocketServerFactory factory, Executor executor)
+    {
+        super(executor);
+        this.mappedCreator = creator;
+        this.webSocketServerFactory = factory;
+        EventDriverFactory eventDriverFactory = this.webSocketServerFactory.getEventDriverFactory();
+        eventDriverFactory.addImplementation(new JsrServerEndpointImpl());
+        eventDriverFactory.addImplementation(new JsrServerExtendsEndpointImpl());
+        this.webSocketServerFactory.addSessionFactory(new JsrSessionFactory(this,this));
+    }
+    
+    public EndpointInstance newClientEndpointInstance(Object endpoint, ServerEndpointConfig config, String path)
+    {
+        EndpointMetadata metadata = getClientEndpointMetadata(endpoint.getClass(),config);
+        ServerEndpointConfig cec = config;
+        if (config == null)
+        {
+            if (metadata instanceof AnnotatedServerEndpointMetadata)
+            {
+                cec = ((AnnotatedServerEndpointMetadata)metadata).getConfig();
+            }
+            else
+            {
+                cec = new BasicServerEndpointConfig(endpoint.getClass(),path);
+            }
+        }
+        return new EndpointInstance(endpoint,cec,metadata);
+    }
+
+    @Override
+    public void addEndpoint(Class<?> endpointClass) throws DeploymentException
+    {
+        ServerEndpointMetadata metadata = getServerEndpointMetadata(endpointClass,null);
+        addEndpoint(metadata);
+    }
+
+    public void addEndpoint(ServerEndpointMetadata metadata) throws DeploymentException
+    {
+        JsrCreator creator = new JsrCreator(metadata,webSocketServerFactory.getExtensionFactory());
+        mappedCreator.addMapping(new WebSocketPathSpec(metadata.getPath()),creator);
+    }
+
+    @Override
+    public void addEndpoint(ServerEndpointConfig config) throws DeploymentException
+    {
+        if (LOG.isDebugEnabled())
+        {
+            LOG.debug("addEndpoint({}) path={} endpoint={}",config,config.getPath(),config.getEndpointClass());
+        }
+        ServerEndpointMetadata metadata = getServerEndpointMetadata(config.getEndpointClass(),config);
+        addEndpoint(metadata);
+    }
+
+    public ServerEndpointMetadata getServerEndpointMetadata(final Class<?> endpoint, final ServerEndpointConfig config) throws DeploymentException
+    {
+        ServerEndpointMetadata metadata = null;
+
+        ServerEndpoint anno = endpoint.getAnnotation(ServerEndpoint.class);
+        if (anno != null)
+        {
+            // Annotated takes precedence here
+            AnnotatedServerEndpointMetadata ametadata = new AnnotatedServerEndpointMetadata(endpoint,config);
+            AnnotatedEndpointScanner<ServerEndpoint, ServerEndpointConfig> scanner = new AnnotatedEndpointScanner<>(ametadata);
+            metadata = ametadata;
+            scanner.scan();
+        }
+        else if (Endpoint.class.isAssignableFrom(endpoint))
+        {
+            // extends Endpoint
+            @SuppressWarnings("unchecked")
+            Class<? extends Endpoint> eendpoint = (Class<? extends Endpoint>)endpoint;
+            metadata = new SimpleServerEndpointMetadata(eendpoint,config);
+        }
+        else
+        {
+            StringBuilder err = new StringBuilder();
+            err.append("Not a recognized websocket [");
+            err.append(endpoint.getName());
+            err.append("] does not extend @").append(ServerEndpoint.class.getName());
+            err.append(" or extend from ").append(Endpoint.class.getName());
+            throw new DeploymentException("Unable to identify as valid Endpoint: " + endpoint);
+        }
+
+        return metadata;
+    }
+
+    @Override
+    public long getDefaultAsyncSendTimeout()
+    {
+        return webSocketServerFactory.getPolicy().getAsyncWriteTimeout();
+    }
+
+    @Override
+    public int getDefaultMaxBinaryMessageBufferSize()
+    {
+        return webSocketServerFactory.getPolicy().getMaxBinaryMessageSize();
+    }
+
+    @Override
+    public long getDefaultMaxSessionIdleTimeout()
+    {
+        return webSocketServerFactory.getPolicy().getIdleTimeout();
+    }
+
+    @Override
+    public int getDefaultMaxTextMessageBufferSize()
+    {
+        return webSocketServerFactory.getPolicy().getMaxTextMessageSize();
+    }
+
+    @Override
+    public void setAsyncSendTimeout(long ms)
+    {
+        super.setAsyncSendTimeout(ms);
+        webSocketServerFactory.getPolicy().setAsyncWriteTimeout(ms);
+    }
+
+    @Override
+    public void setDefaultMaxBinaryMessageBufferSize(int max)
+    {
+        super.setDefaultMaxBinaryMessageBufferSize(max);
+        // overall message limit (used in non-streaming)
+        webSocketServerFactory.getPolicy().setMaxBinaryMessageSize(max);
+        // incoming streaming buffer size
+        webSocketServerFactory.getPolicy().setMaxBinaryMessageBufferSize(max);
+    }
+
+    @Override
+    public void setDefaultMaxSessionIdleTimeout(long ms)
+    {
+        super.setDefaultMaxSessionIdleTimeout(ms);
+        webSocketServerFactory.getPolicy().setIdleTimeout(ms);
+    }
+
+    @Override
+    public void setDefaultMaxTextMessageBufferSize(int max)
+    {
+        super.setDefaultMaxTextMessageBufferSize(max);
+        // overall message limit (used in non-streaming)
+        webSocketServerFactory.getPolicy().setMaxTextMessageSize(max);
+        // incoming streaming buffer size
+        webSocketServerFactory.getPolicy().setMaxTextMessageBufferSize(max);
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/ServerEndpointMetadata.java b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/ServerEndpointMetadata.java
new file mode 100644
index 0000000..d27b25e
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/ServerEndpointMetadata.java
@@ -0,0 +1,30 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import javax.websocket.server.ServerEndpointConfig;
+
+import org.eclipse.jetty.websocket.jsr356.metadata.EndpointMetadata;
+
+public interface ServerEndpointMetadata extends EndpointMetadata
+{
+    ServerEndpointConfig getConfig();
+    
+    public String getPath();
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/SimpleServerEndpointMetadata.java b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/SimpleServerEndpointMetadata.java
new file mode 100644
index 0000000..ebd8d26
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/SimpleServerEndpointMetadata.java
@@ -0,0 +1,61 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import javax.websocket.Endpoint;
+import javax.websocket.server.ServerEndpointConfig;
+
+import org.eclipse.jetty.websocket.jsr356.client.SimpleEndpointMetadata;
+
+public class SimpleServerEndpointMetadata extends SimpleEndpointMetadata implements ServerEndpointMetadata
+{
+    private final ServerEndpointConfig config;
+
+    public SimpleServerEndpointMetadata(Class<? extends Endpoint> endpointClass, ServerEndpointConfig config)
+    {
+        super(endpointClass,config);
+        this.config = config;
+    }
+
+    @Override
+    public ServerEndpointConfig getConfig()
+    {
+        return config;
+    }
+
+    @Override
+    public String getPath()
+    {
+        return config.getPath();
+    }
+
+    @Override
+    public String toString()
+    {
+        StringBuilder builder = new StringBuilder();
+        builder.append("SimpleServerEndpointMetadata [");
+        builder.append("config=").append(config.getClass().getName());
+        builder.append(",path=").append(config.getPath());
+        builder.append(",endpoint=").append(config.getEndpointClass());
+        builder.append(",decoders=").append(config.getDecoders());
+        builder.append(",encoders=").append(config.getEncoders());
+        builder.append("]");
+        return builder.toString();
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/deploy/WebSocketServerContainerInitializer.java b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/deploy/WebSocketServerContainerInitializer.java
new file mode 100644
index 0000000..708d946
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/deploy/WebSocketServerContainerInitializer.java
@@ -0,0 +1,290 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.deploy;
+
+import java.util.HashSet;
+import java.util.Set;
+import javax.servlet.ServletContainerInitializer;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.annotation.HandlesTypes;
+import javax.websocket.DeploymentException;
+import javax.websocket.Endpoint;
+import javax.websocket.server.ServerApplicationConfig;
+import javax.websocket.server.ServerEndpoint;
+import javax.websocket.server.ServerEndpointConfig;
+
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.jsr356.server.ServerContainer;
+import org.eclipse.jetty.websocket.server.WebSocketUpgradeFilter;
+
+ at HandlesTypes(
+{ ServerApplicationConfig.class, ServerEndpoint.class, Endpoint.class })
+public class WebSocketServerContainerInitializer implements ServletContainerInitializer
+{
+    public static final String ENABLE_KEY = "org.eclipse.jetty.websocket.jsr356";
+    private static final Logger LOG = Log.getLogger(WebSocketServerContainerInitializer.class);
+
+    /**
+     * Jetty Native approach.
+     * <p>
+     * Note: this will add the Upgrade filter to the existing list, with no regard for order.  It will just be tacked onto the end of the list.
+     */
+    public static ServerContainer configureContext(ServletContextHandler context) throws ServletException
+    {
+        // Create Filter
+        WebSocketUpgradeFilter filter = WebSocketUpgradeFilter.configureContext(context);
+
+        // Create the Jetty ServerContainer implementation
+        ServerContainer jettyContainer = new ServerContainer(filter,filter.getFactory(),context.getServer().getThreadPool());
+        context.addBean(jettyContainer);
+
+        // Store a reference to the ServerContainer per javax.websocket spec 1.0 final section 6.4 Programmatic Server Deployment
+        context.setAttribute(javax.websocket.server.ServerContainer.class.getName(),jettyContainer);
+
+        return jettyContainer;
+    }
+
+    /**
+     * Servlet 3.1 approach.
+     * <p>
+     * This will use Servlet 3.1 techniques on the {@link ServletContext} to add a filter at the start of the filter chain.
+     */
+    public static ServerContainer configureContext(ServletContext context, ServletContextHandler jettyContext) throws ServletException
+    {
+        // Create Filter
+        WebSocketUpgradeFilter filter = WebSocketUpgradeFilter.configureContext(context);
+
+        // Create the Jetty ServerContainer implementation
+        ServerContainer jettyContainer = new ServerContainer(filter,filter.getFactory(),jettyContext.getServer().getThreadPool());
+        jettyContext.addBean(jettyContainer);
+
+        // Store a reference to the ServerContainer per javax.websocket spec 1.0 final section 6.4 Programmatic Server Deployment
+        context.setAttribute(javax.websocket.server.ServerContainer.class.getName(),jettyContainer);
+
+        return jettyContainer;
+    }
+    
+    private boolean isEnabled(Set<Class<?>> c, ServletContext context)
+    {
+        // Try context parameters first
+        String cp = context.getInitParameter(ENABLE_KEY);
+        if(TypeUtil.isTrue(cp))
+        {
+            // forced on
+            return true;
+        }
+        
+        if(TypeUtil.isFalse(cp))
+        {
+            // forced off
+            LOG.warn("JSR-356 support disabled via parameter on context {} - {}",context.getContextPath(),context);
+            return false;
+        }
+        
+        // Next, try attribute on context
+        Object enable = context.getAttribute(ENABLE_KEY);
+        
+        if(TypeUtil.isTrue(enable))
+        {
+            // forced on
+            return true;
+        }
+        
+        if (TypeUtil.isFalse(enable))
+        {
+            // forced off
+            LOG.warn("JSR-356 support disabled via attribute on context {} - {}",context.getContextPath(),context);
+            return false;
+        }
+        
+        // if not forced on or off, determine behavior based on annotations.
+        if (c.isEmpty())
+        {
+            if (LOG.isDebugEnabled())
+            {
+                LOG.debug("No JSR-356 annotations or interfaces discovered. JSR-356 support disabled",context.getContextPath(),context);
+            }
+            return false;
+        }
+        
+        return true;
+    }
+
+    @Override
+    public void onStartup(Set<Class<?>> c, ServletContext context) throws ServletException
+    {
+        if(!isEnabled(c,context))
+        {
+            return;
+        }
+        
+        ContextHandler handler = ContextHandler.getContextHandler(context);
+
+        if (handler == null)
+        {
+            throw new ServletException("Not running on Jetty, JSR-356 support unavailable");
+        }
+
+        if (!(handler instanceof ServletContextHandler))
+        {
+            throw new ServletException("Not running in Jetty ServletContextHandler, JSR-356 support unavailable");
+        }
+
+        ServletContextHandler jettyContext = (ServletContextHandler)handler;
+
+        ClassLoader old = Thread.currentThread().getContextClassLoader();
+        try
+        {
+            Thread.currentThread().setContextClassLoader(context.getClassLoader());
+
+            // Create the Jetty ServerContainer implementation
+            ServerContainer jettyContainer = configureContext(context,jettyContext);
+
+            // Store a reference to the ServerContainer per javax.websocket spec 1.0 final section 6.4 Programmatic Server Deployment
+            context.setAttribute(javax.websocket.server.ServerContainer.class.getName(),jettyContainer);
+
+            if (LOG.isDebugEnabled())
+            {
+                LOG.debug("Found {} classes",c.size());
+            }
+
+            // Now process the incoming classes
+            Set<Class<? extends Endpoint>> discoveredExtendedEndpoints = new HashSet<>();
+            Set<Class<?>> discoveredAnnotatedEndpoints = new HashSet<>();
+            Set<Class<? extends ServerApplicationConfig>> serverAppConfigs = new HashSet<>();
+
+            filterClasses(c,discoveredExtendedEndpoints,discoveredAnnotatedEndpoints,serverAppConfigs);
+
+            if (LOG.isDebugEnabled())
+            {
+                LOG.debug("Discovered {} extends Endpoint classes",discoveredExtendedEndpoints.size());
+                LOG.debug("Discovered {} @ServerEndpoint classes",discoveredAnnotatedEndpoints.size());
+                LOG.debug("Discovered {} ServerApplicationConfig classes",serverAppConfigs.size());
+            }
+
+            // Process the server app configs to determine endpoint filtering
+            boolean wasFiltered = false;
+            Set<ServerEndpointConfig> deployableExtendedEndpointConfigs = new HashSet<>();
+            Set<Class<?>> deployableAnnotatedEndpoints = new HashSet<>();
+
+            for (Class<? extends ServerApplicationConfig> clazz : serverAppConfigs)
+            {
+                if (LOG.isDebugEnabled())
+                {
+                    LOG.debug("Found ServerApplicationConfig: {}",clazz);
+                }
+                try
+                {
+                    ServerApplicationConfig config = clazz.newInstance();
+
+                    Set<ServerEndpointConfig> seconfigs = config.getEndpointConfigs(discoveredExtendedEndpoints);
+                    if (seconfigs != null)
+                    {
+                        wasFiltered = true;
+                        deployableExtendedEndpointConfigs.addAll(seconfigs);
+                    }
+
+                    Set<Class<?>> annotatedClasses = config.getAnnotatedEndpointClasses(discoveredAnnotatedEndpoints);
+                    if (annotatedClasses != null)
+                    {
+                        wasFiltered = true;
+                        deployableAnnotatedEndpoints.addAll(annotatedClasses);
+                    }
+                }
+                catch (InstantiationException | IllegalAccessException e)
+                {
+                    throw new ServletException("Unable to instantiate: " + clazz.getName(),e);
+                }
+            }
+
+            // Default behavior if nothing filtered
+            if (!wasFiltered)
+            {
+                deployableAnnotatedEndpoints.addAll(discoveredAnnotatedEndpoints);
+                // Note: it is impossible to determine path of "extends Endpoint" discovered classes
+                deployableExtendedEndpointConfigs = new HashSet<>();
+            }
+
+            if (LOG.isDebugEnabled())
+            {
+                LOG.debug("Deploying {} ServerEndpointConfig(s)",deployableExtendedEndpointConfigs.size());
+            }
+            // Deploy what should be deployed.
+            for (ServerEndpointConfig config : deployableExtendedEndpointConfigs)
+            {
+                try
+                {
+                    jettyContainer.addEndpoint(config);
+                }
+                catch (DeploymentException e)
+                {
+                    throw new ServletException(e);
+                }
+            }
+
+            if (LOG.isDebugEnabled())
+            {
+                LOG.debug("Deploying {} @ServerEndpoint(s)",deployableAnnotatedEndpoints.size());
+            }
+            for (Class<?> annotatedClass : deployableAnnotatedEndpoints)
+            {
+                try
+                {
+                    jettyContainer.addEndpoint(annotatedClass);
+                }
+                catch (DeploymentException e)
+                {
+                    throw new ServletException(e);
+                }
+            }
+        } finally {
+            Thread.currentThread().setContextClassLoader(old);
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private void filterClasses(Set<Class<?>> c, Set<Class<? extends Endpoint>> discoveredExtendedEndpoints, Set<Class<?>> discoveredAnnotatedEndpoints,
+            Set<Class<? extends ServerApplicationConfig>> serverAppConfigs)
+    {
+        for (Class<?> clazz : c)
+        {
+            if (ServerApplicationConfig.class.isAssignableFrom(clazz))
+            {
+                serverAppConfigs.add((Class<? extends ServerApplicationConfig>)clazz);
+            }
+
+            if (Endpoint.class.isAssignableFrom(clazz))
+            {
+                discoveredExtendedEndpoints.add((Class<? extends Endpoint>)clazz);
+            }
+            
+            ServerEndpoint endpoint = clazz.getAnnotation(ServerEndpoint.class);
+
+            if (endpoint != null)
+            {
+                discoveredAnnotatedEndpoints.add(clazz);
+            }
+        }
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/pathmap/WebSocketPathSpec.java b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/pathmap/WebSocketPathSpec.java
new file mode 100644
index 0000000..bddf581
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/pathmap/WebSocketPathSpec.java
@@ -0,0 +1,344 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.pathmap;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.server.pathmap.PathSpecGroup;
+import org.eclipse.jetty.websocket.server.pathmap.RegexPathSpec;
+
+/**
+ * PathSpec for WebSocket @{@link ServerEndpoint} declarations with support for URI templates and @{@link PathParam} annotations
+ * 
+ * @see javax.websocket spec (JSR-356) Section 3.1.1 URI Mapping
+ * @see <a href="https://tools.ietf.org/html/rfc6570">URI Templates (Level 1)</a>
+ */
+public class WebSocketPathSpec extends RegexPathSpec
+{
+    private static final Logger LOG = Log.getLogger(WebSocketPathSpec.class);
+    
+    private static final Pattern VARIABLE_PATTERN = Pattern.compile("\\{(.*)\\}");
+    /** Reserved Symbols in URI Template variable */
+    private static final String VARIABLE_RESERVED = ":/?#[]@" + // gen-delims
+                                                    "!$&'()*+,;="; // sub-delims
+    /** Allowed Symboles in a URI Template variable */
+    private static final String VARIABLE_SYMBOLS="-._";
+    private static final Set<String> FORBIDDEN_SEGMENTS;
+
+    static
+    {
+        FORBIDDEN_SEGMENTS = new HashSet<>();
+        FORBIDDEN_SEGMENTS.add("/./");
+        FORBIDDEN_SEGMENTS.add("/../");
+        FORBIDDEN_SEGMENTS.add("//");
+    }
+
+    private String variables[];
+
+    public WebSocketPathSpec(String pathParamSpec)
+    {
+        super();
+        Objects.requireNonNull(pathParamSpec,"Path Param Spec cannot be null");
+
+        if ("".equals(pathParamSpec) || "/".equals(pathParamSpec))
+        {
+            super.pathSpec = "/";
+            super.pattern = Pattern.compile("^/$");
+            super.pathDepth = 1;
+            this.specLength = 1;
+            this.variables = new String[0];
+            this.group = PathSpecGroup.EXACT;
+            return;
+        }
+
+        if (pathParamSpec.charAt(0) != '/')
+        {
+            // path specs must start with '/'
+            StringBuilder err = new StringBuilder();
+            err.append("Syntax Error: path spec \"");
+            err.append(pathParamSpec);
+            err.append("\" must start with '/'");
+            throw new IllegalArgumentException(err.toString());
+        }
+
+        for (String forbidden : FORBIDDEN_SEGMENTS)
+        {
+            if (pathParamSpec.contains(forbidden))
+            {
+                StringBuilder err = new StringBuilder();
+                err.append("Syntax Error: segment ");
+                err.append(forbidden);
+                err.append(" is forbidden in path spec: ");
+                err.append(pathParamSpec);
+                throw new IllegalArgumentException(err.toString());
+            }
+        }
+
+        this.pathSpec = pathParamSpec;
+
+        StringBuilder regex = new StringBuilder();
+        regex.append('^');
+
+        List<String> varNames = new ArrayList<>();
+        // split up into path segments (ignoring the first slash that will always be empty)
+        String segments[] = pathParamSpec.substring(1).split("/");
+        char segmentSignature[] = new char[segments.length];
+        this.pathDepth = segments.length;
+        for (int i = 0; i < segments.length; i++)
+        {
+            String segment = segments[i];
+            Matcher mat = VARIABLE_PATTERN.matcher(segment);
+
+            if (mat.matches())
+            {
+                // entire path segment is a variable.
+                String variable = mat.group(1);
+                if (varNames.contains(variable))
+                {
+                    // duplicate variable names
+                    StringBuilder err = new StringBuilder();
+                    err.append("Syntax Error: variable ");
+                    err.append(variable);
+                    err.append(" is duplicated in path spec: ");
+                    err.append(pathParamSpec);
+                    throw new IllegalArgumentException(err.toString());
+                }
+
+                assertIsValidVariableLiteral(variable);
+
+                segmentSignature[i] = 'v'; // variable
+                // valid variable name
+                varNames.add(variable);
+                // build regex
+                regex.append("/([^/]+)");
+            }
+            else if (mat.find(0))
+            {
+                // variable exists as partial segment
+                StringBuilder err = new StringBuilder();
+                err.append("Syntax Error: variable ");
+                err.append(mat.group());
+                err.append(" must exist as entire path segment: ");
+                err.append(pathParamSpec);
+                throw new IllegalArgumentException(err.toString());
+            }
+            else if ((segment.indexOf('{') >= 0) || (segment.indexOf('}') >= 0))
+            {
+                // variable is split with a path separator
+                StringBuilder err = new StringBuilder();
+                err.append("Syntax Error: invalid path segment /");
+                err.append(segment);
+                err.append("/ variable declaration incomplete: ");
+                err.append(pathParamSpec);
+                throw new IllegalArgumentException(err.toString());
+            }
+            else if (segment.indexOf('*') >= 0)
+            {
+                // glob segment
+                StringBuilder err = new StringBuilder();
+                err.append("Syntax Error: path segment /");
+                err.append(segment);
+                err.append("/ contains a wildcard symbol (not supported by javax.websocket): ");
+                err.append(pathParamSpec);
+                throw new IllegalArgumentException(err.toString());
+            }
+            else
+            {
+                // valid path segment
+                segmentSignature[i] = 'e'; // exact
+                // build regex
+                regex.append('/');
+                // escape regex special characters
+                for (char c : segment.toCharArray())
+                {
+                    if ((c == '.') || (c == '[') || (c == ']') || (c == '\\'))
+                    {
+                        regex.append('\\');
+                    }
+                    regex.append(c);
+                }
+            }
+        }
+        
+        // Handle trailing slash (which is not picked up during split)
+        if(pathParamSpec.charAt(pathParamSpec.length()-1) == '/')
+        {
+            regex.append('/');
+        }
+
+        regex.append('$');
+
+        this.pattern = Pattern.compile(regex.toString());
+
+        int varcount = varNames.size();
+        this.variables = varNames.toArray(new String[varcount]);
+
+        // Convert signature to group
+        String sig = String.valueOf(segmentSignature);
+
+        if (Pattern.matches("^e*$",sig))
+        {
+            this.group = PathSpecGroup.EXACT;
+        }
+        else if (Pattern.matches("^e*v+",sig))
+        {
+            this.group = PathSpecGroup.PREFIX_GLOB;
+        }
+        else if (Pattern.matches("^v+e+",sig))
+        {
+            this.group = PathSpecGroup.SUFFIX_GLOB;
+        }
+        else
+        {
+            this.group = PathSpecGroup.MIDDLE_GLOB;
+        }
+    }
+
+    /**
+     * Validate variable literal name, per RFC6570, Section 2.1 Literals
+     * @param variable
+     * @param pathParamSpec
+     */
+    private void assertIsValidVariableLiteral(String variable)
+    {
+        int len = variable.length();
+        
+        int i = 0;
+        int codepoint;
+        boolean valid = (len > 0); // must not be zero length
+        
+        while (valid && i < len)
+        {
+            codepoint = variable.codePointAt(i);
+            i += Character.charCount(codepoint);
+
+            // basic letters, digits, or symbols
+            if (isValidBasicLiteralCodepoint(codepoint))
+            {
+                continue;
+            }
+
+            // The ucschar and iprivate pieces
+            if (Character.isSupplementaryCodePoint(codepoint))
+            {
+                continue;
+            }
+
+            // pct-encoded
+            if (codepoint == '%')
+            {
+                if (i + 2 > len)
+                {
+                    // invalid percent encoding, missing extra 2 chars
+                    valid = false;
+                    continue;
+                }
+                codepoint = TypeUtil.convertHexDigit(variable.codePointAt(i++)) << 4;
+                codepoint |= TypeUtil.convertHexDigit(variable.codePointAt(i++));
+
+                // validate basic literal
+                if (isValidBasicLiteralCodepoint(codepoint))
+                {
+                    continue;
+                }
+            }
+            
+            valid = false;
+        }
+
+        if (!valid)
+        {
+            // invalid variable name
+            StringBuilder err = new StringBuilder();
+            err.append("Syntax Error: variable {");
+            err.append(variable);
+            err.append("} an invalid variable name: ");
+            err.append(pathSpec);
+            throw new IllegalArgumentException(err.toString());
+        }
+    }
+    
+    private boolean isValidBasicLiteralCodepoint(int codepoint)
+    {
+        // basic letters or digits
+        if((codepoint >= 'a' && codepoint <= 'z') ||
+           (codepoint >= 'A' && codepoint <= 'Z') ||
+           (codepoint >= '0' && codepoint <= '9'))
+        {
+            return true;
+        }
+        
+        // basic allowed symbols
+        if(VARIABLE_SYMBOLS.indexOf(codepoint) >= 0)
+        {
+            return true; // valid simple value
+        }
+        
+        // basic reserved symbols
+        if(VARIABLE_RESERVED.indexOf(codepoint) >= 0)
+        {
+            LOG.warn("Detected URI Template reserved symbol [{}] in path spec \"{}\"",(char)codepoint,pathSpec);
+            return false; // valid simple value
+        }
+
+        return false;
+    }
+
+    public Map<String, String> getPathParams(String path)
+    {
+        Matcher matcher = getMatcher(path);
+        if (matcher.matches())
+        {
+            if (group == PathSpecGroup.EXACT)
+            {
+                return Collections.emptyMap();
+            }
+            Map<String, String> ret = new HashMap<>();
+            int groupCount = matcher.groupCount();
+            for (int i = 1; i <= groupCount; i++)
+            {
+                ret.put(this.variables[i - 1],matcher.group(i));
+            }
+            return ret;
+        }
+        return null;
+    }
+
+    public int getVariableCount()
+    {
+        return variables.length;
+    }
+
+    public String[] getVariables()
+    {
+        return this.variables;
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer b/jetty-websocket/javax-websocket-server-impl/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer
new file mode 100644
index 0000000..0ee5657
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer
@@ -0,0 +1 @@
+org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer
\ No newline at end of file
diff --git a/jetty-websocket/javax-websocket-server-impl/src/main/resources/META-INF/services/javax.websocket.server.ServerEndpointConfig$Configurator b/jetty-websocket/javax-websocket-server-impl/src/main/resources/META-INF/services/javax.websocket.server.ServerEndpointConfig$Configurator
new file mode 100644
index 0000000..57e3ef3
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/main/resources/META-INF/services/javax.websocket.server.ServerEndpointConfig$Configurator
@@ -0,0 +1 @@
+org.eclipse.jetty.websocket.jsr356.server.BasicServerEndpointConfigurator
\ No newline at end of file
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/examples/GetHttpSessionConfigurator.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/examples/GetHttpSessionConfigurator.java
new file mode 100644
index 0000000..b9ec2a4
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/examples/GetHttpSessionConfigurator.java
@@ -0,0 +1,34 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package examples;
+
+import javax.servlet.http.HttpSession;
+import javax.websocket.HandshakeResponse;
+import javax.websocket.server.HandshakeRequest;
+import javax.websocket.server.ServerEndpointConfig;
+
+public class GetHttpSessionConfigurator extends ServerEndpointConfig.Configurator
+{
+    @Override
+    public void modifyHandshake(ServerEndpointConfig config, HandshakeRequest request, HandshakeResponse response)
+    {
+        HttpSession httpSession = (HttpSession)request.getHttpSession();
+        config.getUserProperties().put(HttpSession.class.getName(),httpSession);
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/examples/GetHttpSessionSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/examples/GetHttpSessionSocket.java
new file mode 100644
index 0000000..93e1207
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/examples/GetHttpSessionSocket.java
@@ -0,0 +1,47 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package examples;
+
+import java.io.IOException;
+
+import javax.servlet.http.HttpSession;
+import javax.websocket.EndpointConfig;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+ at ServerEndpoint(value = "/example", configurator = GetHttpSessionConfigurator.class)
+public class GetHttpSessionSocket
+{
+    private Session wsSession;
+    @SuppressWarnings("unused")
+    private HttpSession httpSession;
+    
+    @OnOpen
+    public void open(Session session, EndpointConfig config) {
+        this.wsSession = session;
+        this.httpSession = (HttpSession)config.getUserProperties().get(HttpSession.class.getName());
+    }
+    
+    @OnMessage
+    public void echo(String msg) throws IOException {
+        wsSession.getBasicRemote().sendText(msg);
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/examples/MyAuthedConfigurator.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/examples/MyAuthedConfigurator.java
new file mode 100644
index 0000000..431cbd2
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/examples/MyAuthedConfigurator.java
@@ -0,0 +1,48 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package examples;
+
+import java.security.Principal;
+
+import javax.websocket.HandshakeResponse;
+import javax.websocket.server.HandshakeRequest;
+import javax.websocket.server.ServerEndpointConfig;
+
+public class MyAuthedConfigurator extends ServerEndpointConfig.Configurator
+{
+    @Override
+    public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response)
+    {
+        // Is Authenticated?
+        Principal principal = request.getUserPrincipal();
+        if (principal == null)
+        { 
+            throw new RuntimeException("Not authenticated");
+        }
+
+        // Is Authorized?
+        if (!request.isUserInRole("websocket"))
+        {
+            throw new RuntimeException("Not authorized");
+        }
+        
+        // normal operation
+        super.modifyHandshake(sec,request,response);
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/examples/MyAuthedSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/examples/MyAuthedSocket.java
new file mode 100644
index 0000000..bb87f24
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/examples/MyAuthedSocket.java
@@ -0,0 +1,33 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package examples;
+
+import javax.websocket.OnMessage;
+import javax.websocket.server.ServerEndpoint;
+
+ at ServerEndpoint(value = "/secured/socket", configurator = MyAuthedConfigurator.class)
+public class MyAuthedSocket
+{
+    @OnMessage
+    public String onMessage(String msg)
+    {
+        // echo the message back to the remote
+        return msg;
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/examples/StreamingEchoSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/examples/StreamingEchoSocket.java
new file mode 100644
index 0000000..4ac19ce
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/examples/StreamingEchoSocket.java
@@ -0,0 +1,46 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package examples;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.io.Writer;
+
+import javax.websocket.OnMessage;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.toolchain.test.IO;
+
+ at ServerEndpoint("/echo")
+public class StreamingEchoSocket
+{
+    @OnMessage
+    public void onMessage(Session session, Reader reader)
+    {
+        try (Writer writer = session.getBasicRemote().getSendWriter())
+        {
+            IO.copy(reader,writer);
+        }
+        catch (IOException e)
+        {
+            e.printStackTrace();
+        }
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/AnnotatedServerEndpointTest.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/AnnotatedServerEndpointTest.java
new file mode 100644
index 0000000..2c510e5
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/AnnotatedServerEndpointTest.java
@@ -0,0 +1,142 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import static org.hamcrest.Matchers.*;
+
+import java.io.File;
+import java.net.URI;
+import java.util.Queue;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.eclipse.jetty.webapp.WebAppContext;
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
+import org.eclipse.jetty.websocket.client.WebSocketClient;
+import org.eclipse.jetty.websocket.common.test.LeakTrackingBufferPoolRule;
+import org.eclipse.jetty.websocket.jsr356.server.samples.beans.DateDecoder;
+import org.eclipse.jetty.websocket.jsr356.server.samples.beans.TimeEncoder;
+import org.eclipse.jetty.websocket.jsr356.server.samples.echo.ConfiguredEchoSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.echo.EchoSocketConfigurator;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+
+/**
+ * Example of an annotated echo server discovered via annotation scanning.
+ */
+public class AnnotatedServerEndpointTest
+{
+    @Rule
+    public LeakTrackingBufferPoolRule bufferPool = new LeakTrackingBufferPoolRule("Test");
+
+    private static WSServer server;
+
+    @BeforeClass
+    public static void startServer() throws Exception
+    {
+        File testdir = MavenTestingUtils.getTargetTestingDir(AnnotatedServerEndpointTest.class.getName());
+        server = new WSServer(testdir,"app");
+        server.createWebInf();
+        server.copyEndpoint(ConfiguredEchoSocket.class);
+        server.copyClass(EchoSocketConfigurator.class);
+        server.copyClass(DateDecoder.class);
+        server.copyClass(TimeEncoder.class);
+
+        server.start();
+
+        WebAppContext webapp = server.createWebAppContext();
+        server.deployWebapp(webapp);
+    }
+
+    @AfterClass
+    public static void stopServer()
+    {
+        server.stop();
+    }
+
+    private void assertResponse(String message, String... expectedTexts) throws Exception
+    {
+        WebSocketClient client = new WebSocketClient(bufferPool);
+        try
+        {
+            client.start();
+            JettyEchoSocket clientEcho = new JettyEchoSocket();
+            URI uri = server.getServerBaseURI().resolve("echo");
+            ClientUpgradeRequest req = new ClientUpgradeRequest();
+            req.setSubProtocols("echo");
+            Future<Session> foo = client.connect(clientEcho,uri,req);
+            // wait for connect
+            foo.get(1,TimeUnit.SECONDS);
+
+            clientEcho.sendMessage(message);
+            Queue<String> msgs = clientEcho.awaitMessages(1);
+
+            String response = msgs.poll();
+            for (String expected : expectedTexts)
+            {
+                Assert.assertThat("Expected message",response,containsString(expected));
+            }
+        }
+        finally
+        {
+            client.stop();
+        }
+    }
+
+    @Test
+    public void testConfigurator() throws Exception
+    {
+        assertResponse("configurator",EchoSocketConfigurator.class.getName());
+    }
+    
+    @Test
+    public void testTextMax() throws Exception
+    {
+        assertResponse("text-max","111,222");
+    }
+    
+    @Test
+    public void testBinaryMax() throws Exception
+    {
+        assertResponse("binary-max","333,444");
+    }
+
+    @Test
+    public void testDecoders() throws Exception
+    {
+        assertResponse("decoders",DateDecoder.class.getName());
+    }
+
+    @Test
+    public void testEncoders() throws Exception
+    {
+        assertResponse("encoders",TimeEncoder.class.getName());
+    }
+
+    @Test
+    public void testSubProtocols() throws Exception
+    {
+        assertResponse("subprotocols","chat, echo, test");
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/BasicEndpointTest.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/BasicEndpointTest.java
new file mode 100644
index 0000000..05f7284
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/BasicEndpointTest.java
@@ -0,0 +1,89 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import java.net.URI;
+import java.util.Queue;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.toolchain.test.TestingDir;
+import org.eclipse.jetty.webapp.WebAppContext;
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.client.WebSocketClient;
+import org.eclipse.jetty.websocket.common.test.LeakTrackingBufferPoolRule;
+import org.eclipse.jetty.websocket.jsr356.server.samples.echo.BasicEchoEndpoint;
+import org.eclipse.jetty.websocket.jsr356.server.samples.echo.BasicEchoEndpointConfigContextListener;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+
+/**
+ * Example of an {@link Endpoint} extended echo server added programmatically via the
+ * {@link ServerContainer#addEndpoint(javax.websocket.server.ServerEndpointConfig)}
+ */
+public class BasicEndpointTest
+{
+    @Rule
+    public TestingDir testdir = new TestingDir();
+
+    @Rule
+    public LeakTrackingBufferPoolRule bufferPool = new LeakTrackingBufferPoolRule("Test");
+
+    @Test
+    public void testEcho() throws Exception
+    {
+        WSServer wsb = new WSServer(testdir,"app");
+        wsb.copyWebInf("basic-echo-endpoint-config-web.xml");
+        // the endpoint (extends javax.websocket.Endpoint)
+        wsb.copyClass(BasicEchoEndpoint.class);
+        // the configuration (adds the endpoint)
+        wsb.copyClass(BasicEchoEndpointConfigContextListener.class);
+
+        try
+        {
+            wsb.start();
+            URI uri = wsb.getServerBaseURI();
+
+            WebAppContext webapp = wsb.createWebAppContext();
+            wsb.deployWebapp(webapp);
+
+            WebSocketClient client = new WebSocketClient(bufferPool);
+            try
+            {
+                client.start();
+                JettyEchoSocket clientEcho = new JettyEchoSocket();
+                Future<Session> future = client.connect(clientEcho,uri.resolve("echo"));
+                // wait for connect
+                future.get(1,TimeUnit.SECONDS);
+                clientEcho.sendMessage("Hello World");
+                Queue<String> msgs = clientEcho.awaitMessages(1);
+                Assert.assertEquals("Expected message","Hello World",msgs.poll());
+            }
+            finally
+            {
+                client.stop();
+            }
+        }
+        finally
+        {
+            wsb.stop();
+        }
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/BinaryStreamTest.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/BinaryStreamTest.java
new file mode 100644
index 0000000..790c0cf
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/BinaryStreamTest.java
@@ -0,0 +1,177 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URI;
+import java.util.Random;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import javax.websocket.ClientEndpoint;
+import javax.websocket.ContainerProvider;
+import javax.websocket.OnMessage;
+import javax.websocket.Session;
+import javax.websocket.WebSocketContainer;
+import javax.websocket.server.ServerEndpoint;
+import javax.websocket.server.ServerEndpointConfig;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class BinaryStreamTest
+{
+    private static final String PATH = "/echo";
+
+    private Server server;
+    private ServerConnector connector;
+    private WebSocketContainer wsClient;
+
+    @Before
+    public void prepare() throws Exception
+    {
+        server = new Server();
+        connector = new ServerConnector(server);
+        server.addConnector(connector);
+
+        ServletContextHandler context = new ServletContextHandler(server, "/", true, false);
+        ServerContainer container = WebSocketServerContainerInitializer.configureContext(context);
+        ServerEndpointConfig config = ServerEndpointConfig.Builder.create(ServerBinaryStreamer.class, PATH).build();
+        container.addEndpoint(config);
+
+        server.start();
+
+        wsClient = ContainerProvider.getWebSocketContainer();
+        server.addBean(wsClient, true);
+    }
+
+    @After
+    public void dispose() throws Exception
+    {
+        server.stop();
+    }
+
+    @Test
+    public void testEchoWithMediumMessage() throws Exception
+    {
+        testEcho(1024);
+    }
+
+    @Test
+    public void testLargestMessage() throws Exception
+    {
+        testEcho(wsClient.getDefaultMaxBinaryMessageBufferSize());
+    }
+
+    private void testEcho(int size) throws Exception
+    {
+        byte[] data = randomBytes(size);
+        URI uri = URI.create("ws://localhost:" + connector.getLocalPort() + PATH);
+        ClientBinaryStreamer client = new ClientBinaryStreamer();
+        Session session = wsClient.connectToServer(client, uri);
+
+        try (OutputStream output = session.getBasicRemote().getSendStream())
+        {
+             output.write(data);
+        }
+
+        Assert.assertTrue(client.await(5, TimeUnit.SECONDS));
+        Assert.assertArrayEquals(data, client.getEcho());
+    }
+
+    @Test
+    public void testMoreThanLargestMessageOneByteAtATime() throws Exception
+    {
+        int size = wsClient.getDefaultMaxBinaryMessageBufferSize() + 16;
+        byte[] data = randomBytes(size);
+        URI uri = URI.create("ws://localhost:" + connector.getLocalPort() + PATH);
+        ClientBinaryStreamer client = new ClientBinaryStreamer();
+        Session session = wsClient.connectToServer(client, uri);
+
+        try (OutputStream output = session.getBasicRemote().getSendStream())
+        {
+            for (int i = 0; i < size; ++i)
+                output.write(data[i]);
+        }
+
+        Assert.assertTrue(client.await(5, TimeUnit.SECONDS));
+        Assert.assertArrayEquals(data, client.getEcho());
+    }
+
+    private byte[] randomBytes(int size)
+    {
+        byte[] data = new byte[size];
+        new Random().nextBytes(data);
+        return data;
+    }
+
+    @ClientEndpoint
+    public static class ClientBinaryStreamer
+    {
+        private final CountDownLatch latch = new CountDownLatch(1);
+        private final ByteArrayOutputStream output = new ByteArrayOutputStream();
+
+        @OnMessage
+        public void echoed(InputStream input) throws IOException
+        {
+            while (true)
+            {
+                int read = input.read();
+                if (read < 0)
+                    break;
+                output.write(read);
+            }
+            latch.countDown();
+        }
+
+        public byte[] getEcho()
+        {
+            return output.toByteArray();
+        }
+
+        public boolean await(long timeout, TimeUnit unit) throws InterruptedException
+        {
+            return latch.await(timeout, unit);
+        }
+    }
+
+    @ServerEndpoint(PATH)
+    public static class ServerBinaryStreamer
+    {
+        @OnMessage
+        public void echo(Session session, InputStream input) throws IOException
+        {
+            byte[] buffer = new byte[128];
+            try (OutputStream output = session.getBasicRemote().getSendStream())
+            {
+                int read;
+                while ((read = input.read(buffer)) >= 0)
+                    output.write(buffer, 0, read);
+            }
+        }
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/ConfiguratorTest.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/ConfiguratorTest.java
new file mode 100644
index 0000000..0678367
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/ConfiguratorTest.java
@@ -0,0 +1,508 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import static org.hamcrest.Matchers.*;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.net.InetSocketAddress;
+import java.net.URI;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+
+import javax.websocket.Extension;
+import javax.websocket.HandshakeResponse;
+import javax.websocket.OnMessage;
+import javax.websocket.Session;
+import javax.websocket.server.HandshakeRequest;
+import javax.websocket.server.ServerEndpoint;
+import javax.websocket.server.ServerEndpointConfig;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.toolchain.test.EventQueue;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.util.QuoteUtil;
+import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
+import org.eclipse.jetty.websocket.common.test.BlockheadClient;
+import org.eclipse.jetty.websocket.common.test.HttpResponse;
+import org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class ConfiguratorTest
+{
+    private static final Logger LOG = Log.getLogger(ConfiguratorTest.class);
+
+    public static class EmptyConfigurator extends ServerEndpointConfig.Configurator
+    {
+    }
+
+    @ServerEndpoint(value = "/empty", configurator = EmptyConfigurator.class)
+    public static class EmptySocket
+    {
+        @OnMessage
+        public String echo(String message)
+        {
+            return message;
+        }
+    }
+
+    public static class NoExtensionsConfigurator extends ServerEndpointConfig.Configurator
+    {
+        @Override
+        public List<Extension> getNegotiatedExtensions(List<Extension> installed, List<Extension> requested)
+        {
+            return Collections.emptyList();
+        }
+    }
+
+    @ServerEndpoint(value = "/no-extensions", configurator = NoExtensionsConfigurator.class)
+    public static class NoExtensionsSocket
+    {
+        @OnMessage
+        public String echo(String message)
+        {
+            return message;
+        }
+    }
+
+    public static class CaptureHeadersConfigurator extends ServerEndpointConfig.Configurator
+    {
+        @Override
+        public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response)
+        {
+            super.modifyHandshake(sec,request,response);
+            sec.getUserProperties().put("request-headers",request.getHeaders());
+        }
+    }
+
+    @ServerEndpoint(value = "/capture-request-headers", configurator = CaptureHeadersConfigurator.class)
+    public static class CaptureHeadersSocket
+    {
+        @OnMessage
+        public String getHeaders(Session session, String headerKey)
+        {
+            StringBuilder response = new StringBuilder();
+
+            response.append("Request Header [").append(headerKey).append("]: ");
+            @SuppressWarnings("unchecked")
+            Map<String, List<String>> headers = (Map<String, List<String>>)session.getUserProperties().get("request-headers");
+            if (headers == null)
+            {
+                response.append("<no headers found in session.getUserProperties()>");
+            }
+            else
+            {
+                List<String> values = headers.get(headerKey);
+                if (values == null)
+                {
+                    response.append("<header not found>");
+                }
+                else
+                {
+                    response.append(QuoteUtil.join(values,","));
+                }
+            }
+
+            return response.toString();
+        }
+    }
+
+    public static class ProtocolsConfigurator extends ServerEndpointConfig.Configurator
+    {
+        public static AtomicReference<String> seenProtocols = new AtomicReference<>();
+        
+        @Override
+        public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response)
+        {
+            super.modifyHandshake(sec,request,response);
+        }
+        
+        @Override
+        public String getNegotiatedSubprotocol(List<String> supported, List<String> requested)
+        {
+            String seen = QuoteUtil.join(requested,",");
+            seenProtocols.compareAndSet(null,seen);
+            return super.getNegotiatedSubprotocol(supported,requested);
+        }
+    }
+
+    @ServerEndpoint(value = "/protocols", configurator = ProtocolsConfigurator.class)
+    public static class ProtocolsSocket
+    {
+        @OnMessage
+        public String onMessage(Session session, String msg)
+        {
+            StringBuilder response = new StringBuilder();
+            response.append("Requested Protocols: [").append(ProtocolsConfigurator.seenProtocols.get()).append("]");
+            return response.toString();
+        }
+    }
+    
+    public static class UniqueUserPropsConfigurator extends ServerEndpointConfig.Configurator
+    {
+        private AtomicInteger upgradeCount = new AtomicInteger(0);
+        
+        @Override
+        public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response)
+        {
+            int upgradeNum = upgradeCount.addAndGet(1);
+            LOG.debug("Upgrade Num: {}", upgradeNum);
+            sec.getUserProperties().put("upgradeNum",Integer.toString(upgradeNum));
+            switch(upgradeNum) {
+                case 1: sec.getUserProperties().put("apple", "fruit from tree"); break;
+                case 2: sec.getUserProperties().put("blueberry", "fruit from bush"); break;
+                case 3: sec.getUserProperties().put("strawberry", "fruit from annual"); break;
+                default: sec.getUserProperties().put("fruit"+upgradeNum, "placeholder"); break;
+            }
+            
+            super.modifyHandshake(sec,request,response);
+        }
+    }
+    
+    @ServerEndpoint(value = "/unique-user-props", configurator = UniqueUserPropsConfigurator.class)
+    public static class UniqueUserPropsSocket
+    {
+        @OnMessage
+        public String onMessage(Session session, String msg)
+        {
+            String value = (String)session.getUserProperties().get(msg);
+            StringBuilder response = new StringBuilder();
+            response.append("Requested User Property: [").append(msg).append("] = ");
+            if (value == null)
+            {
+                response.append("<null>");
+            }
+            else
+            {
+                response.append('"').append(value).append('"');
+            }
+            return response.toString();
+        }
+    }
+    
+    public static class AddrConfigurator extends ServerEndpointConfig.Configurator
+    {
+        @Override
+        public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response)
+        {
+            InetSocketAddress local = (InetSocketAddress)sec.getUserProperties().get(JsrCreator.PROP_LOCAL_ADDRESS);
+            InetSocketAddress remote = (InetSocketAddress)sec.getUserProperties().get(JsrCreator.PROP_REMOTE_ADDRESS);
+            
+            sec.getUserProperties().put("found.local", local);
+            sec.getUserProperties().put("found.remote", remote);
+            
+            super.modifyHandshake(sec,request,response);
+        }
+    }
+    
+    @ServerEndpoint(value = "/addr", configurator = AddrConfigurator.class)
+    public static class AddressSocket
+    {
+        @OnMessage
+        public String onMessage(Session session, String msg)
+        {
+            StringBuilder response = new StringBuilder();
+            appendPropValue(session,response,"javax.websocket.endpoint.localAddress");
+            appendPropValue(session,response,"javax.websocket.endpoint.remoteAddress");
+            appendPropValue(session,response,"found.local");
+            appendPropValue(session,response,"found.remote");
+            return response.toString();
+        }
+
+        private void appendPropValue(Session session, StringBuilder response, String key)
+        {
+            InetSocketAddress value = (InetSocketAddress)session.getUserProperties().get(key);
+
+            response.append("[").append(key).append("] = ");
+            response.append(toSafeAddr(value));
+            response.append(System.lineSeparator());
+        }
+    }
+    
+    private static Server server;
+    private static URI baseServerUri;
+
+    @BeforeClass
+    public static void startServer() throws Exception
+    {
+        server = new Server();
+        ServerConnector connector = new ServerConnector(server);
+        connector.setPort(0);
+        server.addConnector(connector);
+
+        ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
+        context.setContextPath("/");
+        server.setHandler(context);
+
+        ServerContainer container = WebSocketServerContainerInitializer.configureContext(context);
+        container.addEndpoint(CaptureHeadersSocket.class);
+        container.addEndpoint(EmptySocket.class);
+        container.addEndpoint(NoExtensionsSocket.class);
+        container.addEndpoint(ProtocolsSocket.class);
+        container.addEndpoint(UniqueUserPropsSocket.class);
+        container.addEndpoint(AddressSocket.class);
+
+        server.start();
+        String host = connector.getHost();
+        if (host == null)
+        {
+            host = "localhost";
+        }
+        int port = connector.getLocalPort();
+        baseServerUri = new URI(String.format("ws://%s:%d/",host,port));
+        if (LOG.isDebugEnabled())
+            LOG.debug("Server started on {}",baseServerUri);
+    }
+
+    public static String toSafeAddr(InetSocketAddress addr)
+    {
+        if (addr == null)
+        {
+            return "<null>";
+        }
+        return String.format("%s:%d",addr.getAddress().getHostAddress(),addr.getPort());
+    }
+
+    @AfterClass
+    public static void stopServer() throws Exception
+    {
+        server.stop();
+    }
+
+    @Test
+    public void testEmptyConfigurator() throws Exception
+    {
+        URI uri = baseServerUri.resolve("/empty");
+
+        try (BlockheadClient client = new BlockheadClient(uri))
+        {
+            client.addExtensions("identity");
+            client.connect();
+            client.sendStandardRequest();
+            HttpResponse response = client.readResponseHeader();
+            Assert.assertThat("response.extensions",response.getExtensionsHeader(),is("identity"));
+        }
+    }
+
+    @Test
+    public void testNoExtensionsConfigurator() throws Exception
+    {
+        URI uri = baseServerUri.resolve("/no-extensions");
+
+        try (BlockheadClient client = new BlockheadClient(uri))
+        {
+            client.addExtensions("identity");
+            client.connect();
+            client.sendStandardRequest();
+            HttpResponse response = client.readResponseHeader();
+            Assert.assertThat("response.extensions",response.getExtensionsHeader(),nullValue());
+        }
+    }
+
+    @Test
+    public void testCaptureRequestHeadersConfigurator() throws Exception
+    {
+        URI uri = baseServerUri.resolve("/capture-request-headers");
+
+        try (BlockheadClient client = new BlockheadClient(uri))
+        {
+            client.addHeader("X-Dummy: Bogus\r\n");
+            client.connect();
+            client.sendStandardRequest();
+            client.expectUpgradeResponse();
+
+            client.write(new TextFrame().setPayload("X-Dummy"));
+            EventQueue<WebSocketFrame> frames = client.readFrames(1,1,TimeUnit.SECONDS);
+            WebSocketFrame frame = frames.poll();
+            Assert.assertThat("Frame Response", frame.getPayloadAsUTF8(), is("Request Header [X-Dummy]: \"Bogus\""));
+        }
+    }
+    
+    @Test
+    public void testUniqueUserPropsConfigurator() throws Exception
+    {
+        URI uri = baseServerUri.resolve("/unique-user-props");
+
+        // First request
+        try (BlockheadClient client = new BlockheadClient(uri))
+        {
+            client.connect();
+            client.sendStandardRequest();
+            client.expectUpgradeResponse();
+
+            client.write(new TextFrame().setPayload("apple"));
+            EventQueue<WebSocketFrame> frames = client.readFrames(1,1,TimeUnit.SECONDS);
+            WebSocketFrame frame = frames.poll();
+            Assert.assertThat("Frame Response", frame.getPayloadAsUTF8(), is("Requested User Property: [apple] = \"fruit from tree\""));
+        }
+        
+        // Second request
+        try (BlockheadClient client = new BlockheadClient(uri))
+        {
+            client.connect();
+            client.sendStandardRequest();
+            client.expectUpgradeResponse();
+
+            client.write(new TextFrame().setPayload("apple"));
+            client.write(new TextFrame().setPayload("blueberry"));
+            EventQueue<WebSocketFrame> frames = client.readFrames(2,1,TimeUnit.SECONDS);
+            WebSocketFrame frame = frames.poll();
+            // should have no value
+            Assert.assertThat("Frame Response", frame.getPayloadAsUTF8(), is("Requested User Property: [apple] = <null>"));
+            
+            frame = frames.poll();
+            Assert.assertThat("Frame Response", frame.getPayloadAsUTF8(), is("Requested User Property: [blueberry] = \"fruit from bush\""));
+        }
+    }
+    
+    @Test
+    public void testUserPropsAddress() throws Exception
+    {
+        URI uri = baseServerUri.resolve("/addr");
+
+        // First request
+        try (BlockheadClient client = new BlockheadClient(uri))
+        {
+            client.connect();
+            client.sendStandardRequest();
+            client.expectUpgradeResponse();
+            
+            InetSocketAddress expectedLocal = client.getLocalSocketAddress();
+            InetSocketAddress expectedRemote = client.getRemoteSocketAddress();
+
+            client.write(new TextFrame().setPayload("addr"));
+            EventQueue<WebSocketFrame> frames = client.readFrames(1,1,TimeUnit.SECONDS);
+            WebSocketFrame frame = frames.poll();
+            
+            StringWriter expected = new StringWriter();
+            PrintWriter out = new PrintWriter(expected);
+            // local <-> remote are opposite on server (duh)
+            out.printf("[javax.websocket.endpoint.localAddress] = %s%n", toSafeAddr(expectedRemote));
+            out.printf("[javax.websocket.endpoint.remoteAddress] = %s%n", toSafeAddr(expectedLocal));
+            out.printf("[found.local] = %s%n",toSafeAddr(expectedRemote));
+            out.printf("[found.remote] = %s%n",toSafeAddr(expectedLocal));
+            
+            Assert.assertThat("Frame Response", frame.getPayloadAsUTF8(), is(expected.toString()));
+        }
+    }
+    
+    /**
+     * Test of Sec-WebSocket-Protocol, as seen in RFC-6455, 1 protocol
+     */
+    @Test
+    public void testProtocol_Single() throws Exception
+    {
+        URI uri = baseServerUri.resolve("/protocols");
+        ProtocolsConfigurator.seenProtocols.set(null);
+
+        try (BlockheadClient client = new BlockheadClient(uri))
+        {
+            client.addHeader("Sec-WebSocket-Protocol: echo\r\n");
+            client.connect();
+            client.sendStandardRequest();
+            client.expectUpgradeResponse();
+
+            client.write(new TextFrame().setPayload("getProtocols"));
+            EventQueue<WebSocketFrame> frames = client.readFrames(1,1,TimeUnit.SECONDS);
+            WebSocketFrame frame = frames.poll();
+            Assert.assertThat("Frame Response", frame.getPayloadAsUTF8(), is("Requested Protocols: [\"echo\"]"));
+        }
+    }
+    
+    /**
+     * Test of Sec-WebSocket-Protocol, as seen in RFC-6455, 3 protocols
+     */
+    @Test
+    public void testProtocol_Triple() throws Exception
+    {
+        URI uri = baseServerUri.resolve("/protocols");
+        ProtocolsConfigurator.seenProtocols.set(null);
+
+        try (BlockheadClient client = new BlockheadClient(uri))
+        {
+            client.addHeader("Sec-WebSocket-Protocol: echo, chat, status\r\n");
+            client.connect();
+            client.sendStandardRequest();
+            client.expectUpgradeResponse();
+
+            client.write(new TextFrame().setPayload("getProtocols"));
+            EventQueue<WebSocketFrame> frames = client.readFrames(1,1,TimeUnit.SECONDS);
+            WebSocketFrame frame = frames.poll();
+            Assert.assertThat("Frame Response", frame.getPayloadAsUTF8(), is("Requested Protocols: [\"echo\",\"chat\",\"status\"]"));
+        }
+    }
+    
+    /**
+     * Test of Sec-WebSocket-Protocol, using all lowercase header
+     */
+    @Test
+    public void testProtocol_LowercaseHeader() throws Exception
+    {
+        URI uri = baseServerUri.resolve("/protocols");
+        ProtocolsConfigurator.seenProtocols.set(null);
+
+        try (BlockheadClient client = new BlockheadClient(uri))
+        {
+            client.addHeader("sec-websocket-protocol: echo, chat, status\r\n");
+            client.connect();
+            client.sendStandardRequest();
+            client.expectUpgradeResponse();
+
+            client.write(new TextFrame().setPayload("getProtocols"));
+            EventQueue<WebSocketFrame> frames = client.readFrames(1,1,TimeUnit.SECONDS);
+            WebSocketFrame frame = frames.poll();
+            Assert.assertThat("Frame Response", frame.getPayloadAsUTF8(), is("Requested Protocols: [\"echo\",\"chat\",\"status\"]"));
+        }
+    }
+    
+    /**
+     * Test of Sec-WebSocket-Protocol, using non-spec case header
+     */
+    @Test
+    public void testProtocol_AltHeaderCase() throws Exception
+    {
+        URI uri = baseServerUri.resolve("/protocols");
+        ProtocolsConfigurator.seenProtocols.set(null);
+
+        try (BlockheadClient client = new BlockheadClient(uri))
+        {
+            client.addHeader("Sec-Websocket-Protocol: echo, chat, status\r\n");
+            client.connect();
+            client.sendStandardRequest();
+            client.expectUpgradeResponse();
+
+            client.write(new TextFrame().setPayload("getProtocols"));
+            EventQueue<WebSocketFrame> frames = client.readFrames(1,1,TimeUnit.SECONDS);
+            WebSocketFrame frame = frames.poll();
+            Assert.assertThat("Frame Response", frame.getPayloadAsUTF8(), is("Requested Protocols: [\"echo\",\"chat\",\"status\"]"));
+        }
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/DummyConnection.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/DummyConnection.java
new file mode 100644
index 0000000..dc03d2a
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/DummyConnection.java
@@ -0,0 +1,162 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import java.net.InetSocketAddress;
+import java.util.concurrent.Executor;
+
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.BatchMode;
+import org.eclipse.jetty.websocket.api.SuspendToken;
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.api.WriteCallback;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.api.extensions.IncomingFrames;
+import org.eclipse.jetty.websocket.common.LogicalConnection;
+import org.eclipse.jetty.websocket.common.WebSocketSession;
+import org.eclipse.jetty.websocket.common.io.IOState;
+
+public class DummyConnection implements LogicalConnection
+{
+    private static final Logger LOG = Log.getLogger(DummyConnection.class);
+    private IOState iostate;
+
+    public DummyConnection()
+    {
+        this.iostate = new IOState();
+    }
+
+    @Override
+    public void close()
+    {
+    }
+
+    @Override
+    public void close(int statusCode, String reason)
+    {
+    }
+
+    @Override
+    public void disconnect()
+    {
+    }
+
+    @Override
+    public ByteBufferPool getBufferPool()
+    {
+        return null;
+    }
+
+    @Override
+    public Executor getExecutor()
+    {
+        return null;
+    }
+
+    @Override
+    public long getIdleTimeout()
+    {
+        return 0;
+    }
+
+    @Override
+    public IOState getIOState()
+    {
+        return this.iostate;
+    }
+
+    @Override
+    public InetSocketAddress getLocalAddress()
+    {
+        return null;
+    }
+
+    @Override
+    public long getMaxIdleTimeout()
+    {
+        return 0;
+    }
+
+    @Override
+    public WebSocketPolicy getPolicy()
+    {
+        return null;
+    }
+
+    @Override
+    public InetSocketAddress getRemoteAddress()
+    {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public WebSocketSession getSession()
+    {
+        return null;
+    }
+
+    @Override
+    public boolean isOpen()
+    {
+        return false;
+    }
+
+    @Override
+    public boolean isReading()
+    {
+        return false;
+    }
+
+    @Override
+    public void outgoingFrame(Frame frame, WriteCallback callback, BatchMode batchMode)
+    {
+        callback.writeSuccess();
+    }
+
+    @Override
+    public void resume()
+    {
+    }
+
+    @Override
+    public void setMaxIdleTimeout(long ms)
+    {
+    }
+
+    @Override
+    public void setNextIncomingFrames(IncomingFrames incoming)
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("setNextIncomingFrames({})",incoming);
+    }
+
+    @Override
+    public void setSession(WebSocketSession session)
+    {
+    }
+
+    @Override
+    public SuspendToken suspend()
+    {
+        return null;
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/DummyCreator.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/DummyCreator.java
new file mode 100644
index 0000000..d3e5553
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/DummyCreator.java
@@ -0,0 +1,39 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import org.eclipse.jetty.websocket.server.MappedWebSocketCreator;
+import org.eclipse.jetty.websocket.server.pathmap.PathMappings;
+import org.eclipse.jetty.websocket.server.pathmap.PathSpec;
+import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
+
+public class DummyCreator implements MappedWebSocketCreator
+{
+    @Override
+    public void addMapping(PathSpec spec, WebSocketCreator creator)
+    {
+        /* do nothing */
+    }
+
+    @Override
+    public PathMappings<WebSocketCreator> getMappings()
+    {
+        return null;
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/EchoCase.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/EchoCase.java
new file mode 100644
index 0000000..a215c18
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/EchoCase.java
@@ -0,0 +1,181 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.websocket.server.ServerEndpoint;
+
+public class EchoCase
+{
+    public static class PartialBinary
+    {
+        ByteBuffer part;
+
+        boolean fin;
+        public PartialBinary(ByteBuffer part, boolean fin)
+        {
+            this.part = part;
+            this.fin = fin;
+        }
+    }
+
+    public static class PartialText
+    {
+        String part;
+
+        boolean fin;
+        public PartialText(String part, boolean fin)
+        {
+            this.part = part;
+            this.fin = fin;
+        }
+    }
+
+    public static EchoCase add(List<EchoCase[]> data, Class<?> serverPojo)
+    {
+        EchoCase ecase = new EchoCase();
+        ecase.serverPojo = serverPojo;
+        data.add(new EchoCase[]
+        { ecase });
+        ServerEndpoint endpoint = serverPojo.getAnnotation(ServerEndpoint.class);
+        ecase.path = endpoint.value();
+        return ecase;
+    }
+
+    public static EchoCase add(List<EchoCase[]> data, Class<?> serverPojo, String path)
+    {
+        EchoCase ecase = new EchoCase();
+        ecase.serverPojo = serverPojo;
+        ecase.path = path;
+        data.add(new EchoCase[]
+        { ecase });
+        return ecase;
+    }
+
+    // The websocket server pojo to test against
+    public Class<?> serverPojo;
+    // The (relative) URL path to hit
+    public String path;
+    // The messages to transmit
+    public List<Object> messages = new ArrayList<>();
+    // The expected Strings (that are echoed back)
+    public List<String> expectedStrings = new ArrayList<>();
+
+    public EchoCase addMessage(Object msg)
+    {
+        messages.add(msg);
+        return this;
+    }
+
+    public EchoCase addSplitMessage(ByteBuffer... parts)
+    {
+        int len = parts.length;
+        for (int i = 0; i < len; i++)
+        {
+            addMessage(new PartialBinary(parts[i],(i == (len-1))));
+        }
+        return this;
+    }
+
+    public EchoCase addSplitMessage(String... parts)
+    {
+        int len = parts.length;
+        for (int i = 0; i < len; i++)
+        {
+            addMessage(new PartialText(parts[i],(i == (len-1))));
+        }
+        return this;
+    }
+
+    public EchoCase expect(String message)
+    {
+        expectedStrings.add(message);
+        return this;
+    }
+
+    public EchoCase requestPath(String path)
+    {
+        this.path = path;
+        return this;
+    }
+
+    @Override
+    public String toString()
+    {
+        StringBuilder str = new StringBuilder();
+        str.append("EchoCase['");
+        str.append(path);
+        str.append("',").append(serverPojo.getName());
+        str.append(",messages[").append(messages.size());
+        str.append("]=");
+        boolean delim = false;
+        for (Object msg : messages)
+        {
+            if (delim)
+            {
+                str.append(",");
+            }
+            if (msg instanceof String)
+            {
+                str.append("'").append(msg).append("'");
+            }
+            else
+            {
+                str.append("(").append(msg.getClass().getName()).append(")");
+                str.append(msg);
+            }
+            delim = true;
+        }
+        str.append("]");
+        return str.toString();
+    }
+
+    public int getMessageCount()
+    {
+        int messageCount = 0;
+        for (Object msg : messages)
+        {
+            if (msg instanceof PartialText)
+            {
+                PartialText pt = (PartialText)msg;
+                if (pt.fin)
+                {
+                    messageCount++;
+                }
+            }
+            else if (msg instanceof PartialBinary)
+            {
+                PartialBinary pb = (PartialBinary)msg;
+                if (pb.fin)
+                {
+                    messageCount++;
+                }
+            }
+            else
+            {
+                messageCount++;
+            }
+        }
+        
+        return messageCount;
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/EchoClientSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/EchoClientSocket.java
new file mode 100644
index 0000000..88ee696
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/EchoClientSocket.java
@@ -0,0 +1,123 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.CloseReason;
+import javax.websocket.EncodeException;
+import javax.websocket.OnClose;
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.RemoteEndpoint.Basic;
+import javax.websocket.Session;
+
+import org.eclipse.jetty.util.BufferUtil;
+
+ at ClientEndpoint
+public class EchoClientSocket extends TrackingSocket
+{
+    public final CountDownLatch eventCountLatch;
+    private Session session;
+    private Basic remote;
+
+    public EchoClientSocket(int expectedEventCount)
+    {
+        this.eventCountLatch = new CountDownLatch(expectedEventCount);
+    }
+
+    public void close() throws IOException
+    {
+        if (session != null)
+        {
+            this.session.close(new CloseReason(CloseReason.CloseCodes.NORMAL_CLOSURE,"Test Complete"));
+        }
+    }
+
+    @OnClose
+    public void onClose(CloseReason close)
+    {
+        this.session = null;
+        super.closeReason = close;
+        super.closeLatch.countDown();
+    }
+
+    @OnError
+    public void onError(Throwable t)
+    {
+        if (t == null)
+        {
+            addError(new NullPointerException("Throwable should not be null"));
+        }
+        else
+        {
+            addError(t);
+        }
+    }
+
+    @OnOpen
+    public void onOpen(Session session)
+    {
+        this.session = session;
+        this.remote = session.getBasicRemote();
+        openLatch.countDown();
+    }
+
+    @OnMessage
+    public void onText(String text)
+    {
+        addEvent(text);
+        eventCountLatch.countDown();
+    }
+
+    public boolean awaitAllEvents(long timeout, TimeUnit unit) throws InterruptedException
+    {
+        return eventCountLatch.await(timeout,unit);
+    }
+
+    public void sendObject(Object obj) throws IOException, EncodeException
+    {
+        remote.sendObject(obj);
+    }
+
+    public void sendPartialBinary(ByteBuffer part, boolean fin) throws IOException
+    {
+        remote.sendBinary(part,fin);
+    }
+
+    public void sendPartialText(String part, boolean fin) throws IOException
+    {
+        remote.sendText(part,fin);
+    }
+    
+    public void sendPing(String message) throws IOException
+    {
+        remote.sendPing(BufferUtil.toBuffer(message));
+    }
+    
+    public void sendPong(String message) throws IOException
+    {
+        remote.sendPong(BufferUtil.toBuffer(message));
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/EchoTest.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/EchoTest.java
new file mode 100644
index 0000000..fcdbea4
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/EchoTest.java
@@ -0,0 +1,302 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import static org.hamcrest.Matchers.contains;
+
+import java.io.File;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import javax.websocket.ContainerProvider;
+import javax.websocket.WebSocketContainer;
+
+import org.eclipse.jetty.toolchain.test.EventQueue;
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.webapp.WebAppContext;
+import org.eclipse.jetty.websocket.jsr356.server.EchoCase.PartialBinary;
+import org.eclipse.jetty.websocket.jsr356.server.EchoCase.PartialText;
+import org.eclipse.jetty.websocket.jsr356.server.samples.binary.ByteBufferSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.partial.PartialTextSessionSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.partial.PartialTextSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.primitives.BooleanObjectTextSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.primitives.BooleanTextSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.primitives.ByteObjectTextSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.primitives.ByteTextSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.primitives.CharTextSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.primitives.CharacterObjectTextSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.primitives.DoubleObjectTextSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.primitives.DoubleTextSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.primitives.FloatObjectTextSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.primitives.FloatTextSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.primitives.IntParamTextSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.primitives.IntTextSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.primitives.IntegerObjectTextSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.primitives.LongObjectTextSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.primitives.LongTextSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.primitives.ShortObjectTextSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.primitives.ShortTextSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.streaming.InputStreamSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.streaming.ReaderParamSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.streaming.ReaderSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.streaming.StringReturnReaderParamSocket;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+ at RunWith(Parameterized.class)
+public class EchoTest
+{
+    private static final List<EchoCase[]> TESTCASES = new ArrayList<>();
+
+    private static WSServer server;
+    private static URI serverUri;
+    private static WebSocketContainer client;
+
+    static
+    {
+        EchoCase.add(TESTCASES,BooleanTextSocket.class).addMessage(true).expect("true");
+        EchoCase.add(TESTCASES,BooleanTextSocket.class).addMessage(false).expect("false");
+        EchoCase.add(TESTCASES,BooleanTextSocket.class).addMessage(Boolean.TRUE).expect("true");
+        EchoCase.add(TESTCASES,BooleanTextSocket.class).addMessage(Boolean.FALSE).expect("false");
+        EchoCase.add(TESTCASES,BooleanTextSocket.class).addMessage("true").expect("true");
+        EchoCase.add(TESTCASES,BooleanTextSocket.class).addMessage("TRUe").expect("true");
+        EchoCase.add(TESTCASES,BooleanTextSocket.class).addMessage("Apple").expect("false");
+        EchoCase.add(TESTCASES,BooleanTextSocket.class).addMessage("false").expect("false");
+
+        EchoCase.add(TESTCASES,BooleanObjectTextSocket.class).addMessage(true).expect("true");
+        EchoCase.add(TESTCASES,BooleanObjectTextSocket.class).addMessage(false).expect("false");
+        EchoCase.add(TESTCASES,BooleanObjectTextSocket.class).addMessage(Boolean.TRUE).expect("true");
+        EchoCase.add(TESTCASES,BooleanObjectTextSocket.class).addMessage(Boolean.FALSE).expect("false");
+        EchoCase.add(TESTCASES,BooleanObjectTextSocket.class).addMessage("true").expect("true");
+        EchoCase.add(TESTCASES,BooleanObjectTextSocket.class).addMessage("false").expect("false");
+        EchoCase.add(TESTCASES,BooleanObjectTextSocket.class).addMessage("FaLsE").expect("false");
+
+        EchoCase.add(TESTCASES,ByteTextSocket.class).addMessage((byte)88).expect("0x58");
+        EchoCase.add(TESTCASES,ByteTextSocket.class).addMessage((byte)101).expect("0x65");
+        EchoCase.add(TESTCASES,ByteTextSocket.class).addMessage((byte)202).expect("0xCA");
+        EchoCase.add(TESTCASES,ByteTextSocket.class).addMessage(Byte.valueOf((byte)33)).expect("0x21");
+        EchoCase.add(TESTCASES,ByteTextSocket.class).addMessage(Byte.valueOf((byte)131)).expect("0x83");
+        EchoCase.add(TESTCASES,ByteTextSocket.class).addMessage(Byte.valueOf((byte)232)).expect("0xE8");
+
+        EchoCase.add(TESTCASES,ByteObjectTextSocket.class).addMessage((byte)88).expect("0x58");
+        EchoCase.add(TESTCASES,ByteObjectTextSocket.class).addMessage((byte)101).expect("0x65");
+        EchoCase.add(TESTCASES,ByteObjectTextSocket.class).addMessage((byte)202).expect("0xCA");
+        EchoCase.add(TESTCASES,ByteObjectTextSocket.class).addMessage(Byte.valueOf((byte)33)).expect("0x21");
+        EchoCase.add(TESTCASES,ByteObjectTextSocket.class).addMessage(Byte.valueOf((byte)131)).expect("0x83");
+        EchoCase.add(TESTCASES,ByteObjectTextSocket.class).addMessage(Byte.valueOf((byte)232)).expect("0xE8");
+
+        EchoCase.add(TESTCASES,CharTextSocket.class).addMessage((char)40).expect("(");
+        EchoCase.add(TESTCASES,CharTextSocket.class).addMessage((char)106).expect("j");
+        EchoCase.add(TESTCASES,CharTextSocket.class).addMessage((char)126).expect("~");
+        EchoCase.add(TESTCASES,CharTextSocket.class).addMessage(Character.valueOf((char)41)).expect(")");
+        EchoCase.add(TESTCASES,CharTextSocket.class).addMessage(Character.valueOf((char)74)).expect("J");
+        EchoCase.add(TESTCASES,CharTextSocket.class).addMessage(Character.valueOf((char)64)).expect("@");
+
+        EchoCase.add(TESTCASES,CharacterObjectTextSocket.class).addMessage((char)40).expect("(");
+        EchoCase.add(TESTCASES,CharacterObjectTextSocket.class).addMessage((char)106).expect("j");
+        EchoCase.add(TESTCASES,CharacterObjectTextSocket.class).addMessage((char)126).expect("~");
+        EchoCase.add(TESTCASES,CharacterObjectTextSocket.class).addMessage("E").expect("E");
+        EchoCase.add(TESTCASES,CharacterObjectTextSocket.class).addMessage(Character.valueOf((char)41)).expect(")");
+        EchoCase.add(TESTCASES,CharacterObjectTextSocket.class).addMessage(Character.valueOf((char)74)).expect("J");
+        EchoCase.add(TESTCASES,CharacterObjectTextSocket.class).addMessage(Character.valueOf((char)64)).expect("@");
+
+        EchoCase.add(TESTCASES,DoubleTextSocket.class).addMessage((double)3.1459).expect("3.1459");
+        EchoCase.add(TESTCASES,DoubleTextSocket.class).addMessage((double)123.456).expect("123.4560");
+        EchoCase.add(TESTCASES,DoubleTextSocket.class).addMessage(Double.valueOf(55)).expect("55.0000");
+        EchoCase.add(TESTCASES,DoubleTextSocket.class).addMessage(Double.valueOf(1.0E8)).expect("100000000.0000");
+        EchoCase.add(TESTCASES,DoubleTextSocket.class).addMessage("42").expect("42.0000");
+        EchoCase.add(TESTCASES,DoubleTextSocket.class).addMessage(".123").expect("0.1230");
+
+        EchoCase.add(TESTCASES,DoubleObjectTextSocket.class).addMessage((double)3.1459).expect("3.1459");
+        EchoCase.add(TESTCASES,DoubleObjectTextSocket.class).addMessage((double)123.456).expect("123.4560");
+        EchoCase.add(TESTCASES,DoubleObjectTextSocket.class).addMessage(Double.valueOf(55)).expect("55.0000");
+        EchoCase.add(TESTCASES,DoubleObjectTextSocket.class).addMessage(Double.valueOf(1.0E8)).expect("100000000.0000");
+        EchoCase.add(TESTCASES,DoubleObjectTextSocket.class).addMessage("42").expect("42.0000");
+        EchoCase.add(TESTCASES,DoubleObjectTextSocket.class).addMessage(".123").expect("0.1230");
+
+        EchoCase.add(TESTCASES,FloatTextSocket.class).addMessage((float)3.1459).expect("3.1459");
+        EchoCase.add(TESTCASES,FloatTextSocket.class).addMessage((float)123.456).expect("123.4560");
+        EchoCase.add(TESTCASES,FloatTextSocket.class).addMessage(Float.valueOf(55)).expect("55.0000");
+        EchoCase.add(TESTCASES,FloatTextSocket.class).addMessage(Float.valueOf(1.0E8f)).expect("100000000.0000");
+        EchoCase.add(TESTCASES,FloatTextSocket.class).addMessage("42").expect("42.0000");
+        EchoCase.add(TESTCASES,FloatTextSocket.class).addMessage(".123").expect("0.1230");
+        EchoCase.add(TESTCASES,FloatTextSocket.class).addMessage("50505E-6").expect("0.0505");
+
+        EchoCase.add(TESTCASES,FloatObjectTextSocket.class).addMessage((float)3.1459).expect("3.1459");
+        EchoCase.add(TESTCASES,FloatObjectTextSocket.class).addMessage((float)123.456).expect("123.4560");
+        EchoCase.add(TESTCASES,FloatObjectTextSocket.class).addMessage(Float.valueOf(55)).expect("55.0000");
+        EchoCase.add(TESTCASES,FloatObjectTextSocket.class).addMessage(Float.valueOf(1.0E8f)).expect("100000000.0000");
+        EchoCase.add(TESTCASES,FloatObjectTextSocket.class).addMessage("42").expect("42.0000");
+        EchoCase.add(TESTCASES,FloatObjectTextSocket.class).addMessage(".123").expect("0.1230");
+        EchoCase.add(TESTCASES,FloatObjectTextSocket.class).addMessage("50505E-6").expect("0.0505");
+
+        EchoCase.add(TESTCASES,IntTextSocket.class).addMessage((int)8).expect("8");
+        EchoCase.add(TESTCASES,IntTextSocket.class).addMessage((int)22).expect("22");
+        EchoCase.add(TESTCASES,IntTextSocket.class).addMessage("12345678").expect("12345678");
+
+        EchoCase.add(TESTCASES,IntegerObjectTextSocket.class).addMessage((int)8).expect("8");
+        EchoCase.add(TESTCASES,IntegerObjectTextSocket.class).addMessage((int)22).expect("22");
+        EchoCase.add(TESTCASES,IntegerObjectTextSocket.class).addMessage("12345678").expect("12345678");
+
+        EchoCase.add(TESTCASES,LongTextSocket.class).addMessage((int)789).expect("789");
+        EchoCase.add(TESTCASES,LongTextSocket.class).addMessage((long)123456L).expect("123456");
+        EchoCase.add(TESTCASES,LongTextSocket.class).addMessage(-456).expect("-456");
+
+        EchoCase.add(TESTCASES,LongObjectTextSocket.class).addMessage((int)789).expect("789");
+        EchoCase.add(TESTCASES,LongObjectTextSocket.class).addMessage((long)123456L).expect("123456");
+        EchoCase.add(TESTCASES,LongObjectTextSocket.class).addMessage(-234).expect("-234");
+
+        EchoCase.add(TESTCASES,ShortTextSocket.class).addMessage((int)4).expect("4");
+        EchoCase.add(TESTCASES,ShortTextSocket.class).addMessage((long)987).expect("987");
+        EchoCase.add(TESTCASES,ShortTextSocket.class).addMessage("32001").expect("32001");
+
+        EchoCase.add(TESTCASES,ShortObjectTextSocket.class).addMessage((int)4).expect("4");
+        EchoCase.add(TESTCASES,ShortObjectTextSocket.class).addMessage((int)987).expect("987");
+        EchoCase.add(TESTCASES,ShortObjectTextSocket.class).addMessage(-32001L).expect("-32001");
+
+        // PathParam based
+        EchoCase.add(TESTCASES,IntParamTextSocket.class).requestPath("/echo/primitives/integer/params/5678").addMessage(1234).expect("1234|5678");
+        
+        // ByteBuffer based
+        EchoCase.add(TESTCASES,ByteBufferSocket.class).addMessage(BufferUtil.toBuffer("Hello World")).expect("Hello World");
+
+        // InputStream based
+        EchoCase.add(TESTCASES,InputStreamSocket.class).addMessage(BufferUtil.toBuffer("Hello World")).expect("Hello World");
+
+        // Reader based
+        EchoCase.add(TESTCASES,ReaderSocket.class).addMessage("Hello World").expect("Hello World");
+        EchoCase.add(TESTCASES,ReaderParamSocket.class).requestPath("/echo/streaming/readerparam/OhNo").addMessage("Hello World").expect("Hello World|OhNo");
+        EchoCase.add(TESTCASES,StringReturnReaderParamSocket.class).requestPath("/echo/streaming/readerparam2/OhMy").addMessage("Hello World")
+                .expect("Hello World|OhMy");
+
+        // Partial message based
+        EchoCase.add(TESTCASES,PartialTextSocket.class)
+          .addSplitMessage("Saved"," by ","zero")
+          .expect("('Saved',false)(' by ',false)('zero',true)");
+        EchoCase.add(TESTCASES,PartialTextSessionSocket.class)
+          .addSplitMessage("Built"," for"," the"," future")
+          .expect("('Built',false)(' for',false)(' the',false)(' future',true)");
+    }
+
+    @BeforeClass
+    public static void startServer() throws Exception
+    {
+        File testdir = MavenTestingUtils.getTargetTestingDir(EchoTest.class.getName());
+        server = new WSServer(testdir,"app");
+        server.copyWebInf("empty-web.xml");
+
+        for (EchoCase cases[] : TESTCASES)
+        {
+            for (EchoCase ecase : cases)
+            {
+                server.copyClass(ecase.serverPojo);
+            }
+        }
+
+        server.start();
+        serverUri = server.getServerBaseURI();
+
+        WebAppContext webapp = server.createWebAppContext();
+        server.deployWebapp(webapp);
+    }
+
+    @BeforeClass
+    public static void startClient() throws Exception
+    {
+        client = ContainerProvider.getWebSocketContainer();
+    }
+
+    @AfterClass
+    public static void stopServer()
+    {
+        server.stop();
+    }
+
+    @Parameters
+    public static Collection<EchoCase[]> data() throws Exception
+    {
+        return TESTCASES;
+    }
+
+    private EchoCase testcase;
+
+    public EchoTest(EchoCase testcase)
+    {
+        this.testcase = testcase;
+        System.err.println(testcase);
+    }
+
+    @Test(timeout=2000)
+    public void testEcho() throws Exception
+    {
+        int messageCount = testcase.getMessageCount();
+        EchoClientSocket socket = new EchoClientSocket(messageCount);
+        URI toUri = serverUri.resolve(testcase.path.substring(1));
+
+        try
+        {
+            // Connect
+            client.connectToServer(socket,toUri);
+            socket.waitForConnected(2,TimeUnit.SECONDS);
+
+            // Send Messages
+            for (Object msg : testcase.messages)
+            {
+                if (msg instanceof PartialText)
+                {
+                    PartialText pt = (PartialText)msg;
+                    socket.sendPartialText(pt.part,pt.fin);
+                }
+                else if (msg instanceof PartialBinary)
+                {
+                    PartialBinary pb = (PartialBinary)msg;
+                    socket.sendPartialBinary(pb.part,pb.fin);
+                }
+                else
+                {
+                    socket.sendObject(msg);
+                }
+            }
+
+            // Collect Responses
+            socket.awaitAllEvents(1,TimeUnit.SECONDS);
+            EventQueue<String> received = socket.eventQueue;
+
+            // Validate Responses
+            for (String expected : testcase.expectedStrings)
+            {
+                Assert.assertThat("Received Echo Responses",received,contains(expected));
+            }
+        }
+        finally
+        {
+            // Close
+            socket.close();
+        }
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/ExtensionStackProcessingTest.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/ExtensionStackProcessingTest.java
new file mode 100644
index 0000000..3096f47
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/ExtensionStackProcessingTest.java
@@ -0,0 +1,184 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import java.net.URI;
+import java.util.Arrays;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import javax.websocket.ClientEndpointConfig;
+import javax.websocket.ContainerProvider;
+import javax.websocket.Endpoint;
+import javax.websocket.EndpointConfig;
+import javax.websocket.Extension;
+import javax.websocket.MessageHandler;
+import javax.websocket.SendHandler;
+import javax.websocket.SendResult;
+import javax.websocket.Session;
+import javax.websocket.WebSocketContainer;
+import javax.websocket.server.ServerEndpointConfig;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.websocket.api.extensions.ExtensionFactory;
+import org.eclipse.jetty.websocket.api.extensions.OutgoingFrames;
+import org.eclipse.jetty.websocket.client.io.WebSocketClientConnection;
+import org.eclipse.jetty.websocket.common.extensions.ExtensionStack;
+import org.eclipse.jetty.websocket.common.extensions.compress.DeflateFrameExtension;
+import org.eclipse.jetty.websocket.jsr356.JsrExtension;
+import org.eclipse.jetty.websocket.jsr356.JsrSession;
+import org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer;
+import org.eclipse.jetty.websocket.jsr356.server.samples.echo.BasicEchoEndpoint;
+import org.eclipse.jetty.websocket.server.WebSocketUpgradeFilter;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Test;
+
+public class ExtensionStackProcessingTest
+{
+    private Server server;
+    private ServerConnector connector;
+    private ExtensionFactory serverExtensionFactory;
+    private WebSocketContainer client;
+
+    @Before
+    public void prepare() throws Exception
+    {
+        server = new Server();
+        connector = new ServerConnector(server);
+        server.addConnector(connector);
+
+        ServletContextHandler context = new ServletContextHandler(server, "/", true, false);
+        ServerContainer container = WebSocketServerContainerInitializer.configureContext(context);
+        
+        WebSocketUpgradeFilter filter = (WebSocketUpgradeFilter)context.getAttribute(WebSocketUpgradeFilter.class.getName());
+        serverExtensionFactory = filter.getFactory().getExtensionFactory();
+        
+        ServerEndpointConfig config = ServerEndpointConfig.Builder.create(BasicEchoEndpoint.class, "/").build();
+        container.addEndpoint(config);
+
+        client = ContainerProvider.getWebSocketContainer();
+        server.addBean(client, true);
+        
+        server.start();
+    }
+
+    @After
+    public void dispose() throws Exception
+    {
+        server.stop();
+    }
+
+    @Test
+    public void testDeflateFrameExtension() throws Exception
+    {
+        Assume.assumeTrue("Server has deflate-frame extension registered",serverExtensionFactory.isAvailable("deflate-frame"));
+        
+        ClientEndpointConfig config = ClientEndpointConfig.Builder.create()
+                .extensions(Arrays.<Extension>asList(new JsrExtension("deflate-frame")))
+                .build();
+
+        final String content = "deflate_me";
+        final CountDownLatch messageLatch = new CountDownLatch(1);
+        URI uri = URI.create("ws://localhost:" + connector.getLocalPort());
+        Session session = client.connectToServer(new EndpointAdapter()
+        {
+            @Override
+            public void onMessage(String message)
+            {
+                Assert.assertEquals(content, message);
+                messageLatch.countDown();
+            }
+        }, config, uri);
+
+        // Make sure everything is wired properly.
+        OutgoingFrames firstOut = ((JsrSession)session).getOutgoingHandler();
+        Assert.assertTrue(firstOut instanceof ExtensionStack);
+        ExtensionStack extensionStack = (ExtensionStack)firstOut;
+        Assert.assertTrue(extensionStack.isRunning());
+        OutgoingFrames secondOut = extensionStack.getNextOutgoing();
+        Assert.assertTrue(secondOut instanceof DeflateFrameExtension);
+        DeflateFrameExtension deflateExtension = (DeflateFrameExtension)secondOut;
+        Assert.assertTrue(deflateExtension.isRunning());
+        OutgoingFrames thirdOut = deflateExtension.getNextOutgoing();
+        Assert.assertTrue(thirdOut instanceof WebSocketClientConnection);
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        session.getAsyncRemote().sendText(content, new SendHandler()
+        {
+            @Override
+            public void onResult(SendResult result)
+            {
+                latch.countDown();
+            }
+        });
+
+        Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+        Assert.assertTrue(messageLatch.await(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testPerMessageDeflateExtension() throws Exception
+    {
+        Assume.assumeTrue("Server has permessage-deflate extension registered",serverExtensionFactory.isAvailable("permessage-deflate"));
+        
+        ClientEndpointConfig config = ClientEndpointConfig.Builder.create()
+                .extensions(Arrays.<Extension>asList(new JsrExtension("permessage-deflate")))
+                .build();
+
+        final String content = "deflate_me";
+        final CountDownLatch messageLatch = new CountDownLatch(1);
+        URI uri = URI.create("ws://localhost:" + connector.getLocalPort());
+        Session session = client.connectToServer(new EndpointAdapter()
+        {
+            @Override
+            public void onMessage(String message)
+            {
+                Assert.assertEquals(content, message);
+                messageLatch.countDown();
+            }
+        }, config, uri);
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        session.getAsyncRemote().sendText(content, new SendHandler()
+        {
+            @Override
+            public void onResult(SendResult result)
+            {
+                latch.countDown();
+            }
+        });
+
+        Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+        Assert.assertTrue(messageLatch.await(5, TimeUnit.SECONDS));
+    }
+
+    private static abstract class EndpointAdapter extends Endpoint implements MessageHandler.Whole<String>
+    {
+        @Override
+        public void onOpen(Session session, EndpointConfig config)
+        {
+            session.addMessageHandler(this);
+        }
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/IdleTimeoutTest.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/IdleTimeoutTest.java
new file mode 100644
index 0000000..101ddfb
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/IdleTimeoutTest.java
@@ -0,0 +1,140 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.Queue;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.eclipse.jetty.toolchain.test.TestingDir;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.webapp.WebAppContext;
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.client.WebSocketClient;
+import org.eclipse.jetty.websocket.common.test.LeakTrackingBufferPoolRule;
+import org.eclipse.jetty.websocket.jsr356.server.samples.idletimeout.IdleTimeoutContextListener;
+import org.eclipse.jetty.websocket.jsr356.server.samples.idletimeout.OnOpenIdleTimeoutEndpoint;
+import org.eclipse.jetty.websocket.jsr356.server.samples.idletimeout.OnOpenIdleTimeoutSocket;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+
+import static org.hamcrest.Matchers.empty;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
+public class IdleTimeoutTest
+{
+    private static final Logger LOG = Log.getLogger(IdleTimeoutTest.class);
+
+    @Rule
+    public TestingDir testdir = new TestingDir();
+
+    @Rule
+    public LeakTrackingBufferPoolRule bufferPool = new LeakTrackingBufferPoolRule("Test");
+
+    private static WSServer server;
+
+    @BeforeClass
+    public static void setupServer() throws Exception
+    {
+        server = new WSServer(MavenTestingUtils.getTargetTestingDir(IdleTimeoutTest.class.getName()),"app");
+        server.copyWebInf("idle-timeout-config-web.xml");
+        // the endpoint (extends javax.websocket.Endpoint)
+        server.copyClass(OnOpenIdleTimeoutEndpoint.class);
+        // the configuration that adds the endpoint
+        server.copyClass(IdleTimeoutContextListener.class);
+        // the annotated socket
+        server.copyClass(OnOpenIdleTimeoutSocket.class);
+
+        server.start();
+
+        WebAppContext webapp = server.createWebAppContext();
+        server.deployWebapp(webapp);
+        // wsb.dump();
+    }
+
+    @AfterClass
+    public static void stopServer()
+    {
+        server.stop();
+    }
+
+    private void assertConnectionTimeout(URI uri) throws Exception, IOException, InterruptedException, ExecutionException, TimeoutException
+    {
+        WebSocketClient client = new WebSocketClient(bufferPool);
+        try
+        {
+            client.start();
+            JettyEchoSocket clientEcho = new JettyEchoSocket();
+            if (LOG.isDebugEnabled())
+                LOG.debug("Client Attempting to connnect");
+            Future<Session> future = client.connect(clientEcho,uri);
+            // wait for connect
+            future.get(1,TimeUnit.SECONDS);
+            if (LOG.isDebugEnabled())
+                LOG.debug("Client Connected");
+            // wait 1 second
+            if (LOG.isDebugEnabled())
+                LOG.debug("Waiting 1 second");
+            TimeUnit.SECONDS.sleep(1);
+            if (LOG.isDebugEnabled())
+                LOG.debug("Waited 1 second");
+            if (clientEcho.getClosed() == false)
+            {
+                // Try to write
+                clientEcho.sendMessage("You shouldn't be there");
+                try
+                {
+                    Queue<String> msgs = clientEcho.awaitMessages(1);
+                    assertThat("Should not have received messages echoed back",msgs,is(empty()));
+                }
+                catch (TimeoutException | InterruptedException e)
+                {
+                    // valid success path
+                }
+            }
+        }
+        finally
+        {
+            client.stop();
+        }
+    }
+
+    @Test
+    public void testAnnotated() throws Exception
+    {
+        URI uri = server.getServerBaseURI();
+        assertConnectionTimeout(uri.resolve("idle-onopen-socket"));
+    }
+
+    @Test
+    public void testEndpoint() throws Exception
+    {
+        URI uri = server.getServerBaseURI();
+        assertConnectionTimeout(uri.resolve("idle-onopen-endpoint"));
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/JettyEchoSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/JettyEchoSocket.java
new file mode 100644
index 0000000..8b51951
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/JettyEchoSocket.java
@@ -0,0 +1,137 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import java.io.IOException;
+import java.util.Queue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+import org.eclipse.jetty.toolchain.test.EventQueue;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.BatchMode;
+import org.eclipse.jetty.websocket.api.RemoteEndpoint;
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
+import org.eclipse.jetty.websocket.api.annotations.WebSocket;
+
+/**
+ * This is a Jetty API version of a websocket.
+ * <p>
+ * This is used a a client socket during the server tests.
+ */
+ at WebSocket
+public class JettyEchoSocket
+{
+    private static final Logger LOG = Log.getLogger(JettyEchoSocket.class);
+    @SuppressWarnings("unused")
+    private Session session;
+    private Lock remoteLock = new ReentrantLock();
+    private RemoteEndpoint remote;
+    private EventQueue<String> incomingMessages = new EventQueue<>();
+
+    public Queue<String> awaitMessages(int expected) throws TimeoutException, InterruptedException
+    {
+        incomingMessages.awaitEventCount(expected,2,TimeUnit.SECONDS);
+        return incomingMessages;
+    }
+
+    public boolean getClosed()
+    {
+        remoteLock.lock();
+        try
+        {
+            return (remote == null);
+        }
+        finally
+        {
+            remoteLock.unlock();
+        }
+    }
+
+    @OnWebSocketClose
+    public void onClose(int code, String reason)
+    {
+        session = null;
+        remoteLock.lock();
+        try
+        {
+            remote = null;
+        }
+        finally
+        {
+            remoteLock.unlock();
+        }
+    }
+
+    @OnWebSocketError
+    public void onError(Throwable t)
+    {
+        LOG.warn(t);
+    }
+
+    @OnWebSocketMessage
+    public void onMessage(String msg) throws IOException
+    {
+        incomingMessages.add(msg);
+        sendMessage(msg);
+    }
+
+    @OnWebSocketConnect
+    public void onOpen(Session session)
+    {
+        this.session = session;
+        remoteLock.lock();
+        try
+        {
+            this.remote = session.getRemote();
+        }
+        finally
+        {
+            remoteLock.unlock();
+        }
+    }
+
+    public void sendMessage(String msg) throws IOException
+    {
+        remoteLock.lock();
+        try
+        {
+            RemoteEndpoint r = remote;
+            if (r == null)
+            {
+                return;
+            }
+
+            r.sendStringByFuture(msg);
+            if (r.getBatchMode() == BatchMode.ON)
+                r.flush();
+        }
+        finally
+        {
+            remoteLock.unlock();
+        }
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/JettyServerEndpointConfiguratorTest.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/JettyServerEndpointConfiguratorTest.java
new file mode 100644
index 0000000..2f1dba9
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/JettyServerEndpointConfiguratorTest.java
@@ -0,0 +1,53 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import static org.hamcrest.Matchers.instanceOf;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.junit.Assert.assertThat;
+
+import java.util.Iterator;
+import java.util.ServiceLoader;
+
+import javax.websocket.server.ServerEndpointConfig;
+
+import org.junit.Test;
+
+/**
+ * Test the JettyServerEndpointConfigurator impl.
+ */
+public class JettyServerEndpointConfiguratorTest
+{
+    @Test
+    public void testServiceLoader()
+    {
+        System.out.printf("Service Name: %s%n",ServerEndpointConfig.Configurator.class.getName());
+
+        ServiceLoader<ServerEndpointConfig.Configurator> loader = ServiceLoader.load(javax.websocket.server.ServerEndpointConfig.Configurator.class);
+        assertThat("loader",loader,notNullValue());
+        Iterator<ServerEndpointConfig.Configurator> iter = loader.iterator();
+        assertThat("loader.iterator",iter,notNullValue());
+        assertThat("loader.iterator.hasNext",iter.hasNext(),is(true));
+
+        ServerEndpointConfig.Configurator configr = iter.next();
+        assertThat("Configurator",configr,notNullValue());
+        assertThat("COnfigurator type",configr,instanceOf(BasicServerEndpointConfigurator.class));
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/JsrBatchModeTest.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/JsrBatchModeTest.java
new file mode 100644
index 0000000..bf92e70
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/JsrBatchModeTest.java
@@ -0,0 +1,181 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import java.net.URI;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import javax.websocket.ClientEndpointConfig;
+import javax.websocket.ContainerProvider;
+import javax.websocket.Endpoint;
+import javax.websocket.EndpointConfig;
+import javax.websocket.MessageHandler;
+import javax.websocket.RemoteEndpoint;
+import javax.websocket.Session;
+import javax.websocket.WebSocketContainer;
+import javax.websocket.server.ServerEndpointConfig;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer;
+import org.eclipse.jetty.websocket.jsr356.server.samples.echo.BasicEchoEndpoint;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class JsrBatchModeTest
+{
+    private Server server;
+    private ServerConnector connector;
+    private WebSocketContainer client;
+
+    @Before
+    public void prepare() throws Exception
+    {
+        server = new Server();
+        connector = new ServerConnector(server);
+        server.addConnector(connector);
+
+        ServletContextHandler context = new ServletContextHandler(server, "/", true, false);
+        ServerContainer container = WebSocketServerContainerInitializer.configureContext(context);
+        ServerEndpointConfig config = ServerEndpointConfig.Builder.create(BasicEchoEndpoint.class, "/").build();
+        container.addEndpoint(config);
+
+        server.start();
+
+        client = ContainerProvider.getWebSocketContainer();
+        server.addBean(client, true);
+    }
+
+    @After
+    public void dispose() throws Exception
+    {
+        server.stop();
+    }
+
+    @Test
+    public void testBatchModeOn() throws Exception
+    {
+        ClientEndpointConfig config = ClientEndpointConfig.Builder.create().build();
+
+        URI uri = URI.create("ws://localhost:" + connector.getLocalPort());
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        EndpointAdapter endpoint = new EndpointAdapter()
+        {
+            @Override
+            public void onMessage(String message)
+            {
+                latch.countDown();
+            }
+        };
+
+        try (Session session = client.connectToServer(endpoint, config, uri))
+        {
+            RemoteEndpoint.Async remote = session.getAsyncRemote();
+            remote.setBatchingAllowed(true);
+
+            Future<Void> future = remote.sendText("batch_mode_on");
+            // The write is aggregated and therefore completes immediately.
+            future.get(1, TimeUnit.MICROSECONDS);
+
+            // Did not flush explicitly, so the message should not be back yet.
+            Assert.assertFalse(latch.await(1, TimeUnit.SECONDS));
+
+            // Explicitly flush.
+            remote.flushBatch();
+
+            // Wait for the echo.
+            Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+        }
+    }
+
+    @Test
+    public void testBatchModeOff() throws Exception
+    {
+        ClientEndpointConfig config = ClientEndpointConfig.Builder.create().build();
+
+        URI uri = URI.create("ws://localhost:" + connector.getLocalPort());
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        EndpointAdapter endpoint = new EndpointAdapter()
+        {
+            @Override
+            public void onMessage(String message)
+            {
+                latch.countDown();
+            }
+        };
+
+        try (Session session = client.connectToServer(endpoint, config, uri))
+        {
+            RemoteEndpoint.Async remote = session.getAsyncRemote();
+            remote.setBatchingAllowed(false);
+
+            Future<Void> future = remote.sendText("batch_mode_off");
+            // The write is immediate.
+            future.get(1, TimeUnit.SECONDS);
+
+            // Wait for the echo.
+            Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+        }
+    }
+
+    @Test
+    public void testBatchModeAuto() throws Exception
+    {
+        ClientEndpointConfig config = ClientEndpointConfig.Builder.create().build();
+
+        URI uri = URI.create("ws://localhost:" + connector.getLocalPort());
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        EndpointAdapter endpoint = new EndpointAdapter()
+        {
+            @Override
+            public void onMessage(String message)
+            {
+                latch.countDown();
+            }
+        };
+
+        try (Session session = client.connectToServer(endpoint, config, uri))
+        {
+            RemoteEndpoint.Async remote = session.getAsyncRemote();
+
+            Future<Void> future = remote.sendText("batch_mode_auto");
+            // The write is immediate, as per the specification.
+            future.get(1, TimeUnit.SECONDS);
+
+            // Wait for the echo.
+            Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+        }
+    }
+
+    private static abstract class EndpointAdapter extends Endpoint implements MessageHandler.Whole<String>
+    {
+        @Override
+        public void onOpen(Session session, EndpointConfig config)
+        {
+            session.addMessageHandler(this);
+        }
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/LargeAnnotatedTest.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/LargeAnnotatedTest.java
new file mode 100644
index 0000000..8208c00
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/LargeAnnotatedTest.java
@@ -0,0 +1,92 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Queue;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.toolchain.test.TestingDir;
+import org.eclipse.jetty.webapp.WebAppContext;
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.client.WebSocketClient;
+import org.eclipse.jetty.websocket.common.test.LeakTrackingBufferPoolRule;
+import org.eclipse.jetty.websocket.jsr356.server.samples.echo.LargeEchoConfiguredSocket;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+
+/**
+ * Test Echo of Large messages, targeting the {@link javax.websocket.Session#setMaxTextMessageBufferSize(int)} functionality
+ */
+public class LargeAnnotatedTest
+{
+    @Rule
+    public TestingDir testdir = new TestingDir();
+
+    @Rule
+    public LeakTrackingBufferPoolRule bufferPool = new LeakTrackingBufferPoolRule("Test");
+
+    @Test
+    public void testEcho() throws Exception
+    {
+        WSServer wsb = new WSServer(testdir,"app");
+        wsb.createWebInf();
+        wsb.copyEndpoint(LargeEchoConfiguredSocket.class);
+
+        try
+        {
+            wsb.start();
+            URI uri = wsb.getServerBaseURI();
+
+            WebAppContext webapp = wsb.createWebAppContext();
+            wsb.deployWebapp(webapp);
+            // wsb.dump();
+
+            WebSocketClient client = new WebSocketClient(bufferPool);
+            try
+            {
+                client.getPolicy().setMaxTextMessageSize(128*1024);
+                client.start();
+                JettyEchoSocket clientEcho = new JettyEchoSocket();
+                Future<Session> foo = client.connect(clientEcho,uri.resolve("echo/large"));
+                // wait for connect
+                foo.get(1,TimeUnit.SECONDS);
+                // The message size should be bigger than default, but smaller than the limit that LargeEchoSocket specifies
+                byte txt[] = new byte[100 * 1024];
+                Arrays.fill(txt,(byte)'o');
+                String msg = new String(txt,StandardCharsets.UTF_8);
+                clientEcho.sendMessage(msg);
+                Queue<String> msgs = clientEcho.awaitMessages(1);
+                Assert.assertEquals("Expected message",msg,msgs.poll());
+            }
+            finally
+            {
+                client.stop();
+            }
+        }
+        finally
+        {
+            wsb.stop();
+        }
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/LargeContainerTest.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/LargeContainerTest.java
new file mode 100644
index 0000000..97d2888
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/LargeContainerTest.java
@@ -0,0 +1,92 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Queue;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.toolchain.test.TestingDir;
+import org.eclipse.jetty.webapp.WebAppContext;
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.client.WebSocketClient;
+import org.eclipse.jetty.websocket.common.test.LeakTrackingBufferPoolRule;
+import org.eclipse.jetty.websocket.jsr356.server.samples.echo.LargeEchoDefaultSocket;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+
+/**
+ * Test Echo of Large messages, targeting the {@link javax.websocket.WebSocketContainer#setDefaultMaxTextMessageBufferSize(int)} functionality
+ */
+public class LargeContainerTest
+{
+    @Rule
+    public TestingDir testdir = new TestingDir();
+
+    @Rule
+    public LeakTrackingBufferPoolRule bufferPool = new LeakTrackingBufferPoolRule("Test");
+
+    @Test
+    public void testEcho() throws Exception
+    {
+        WSServer wsb = new WSServer(testdir,"app");
+        wsb.copyWebInf("large-echo-config-web.xml");
+        wsb.copyEndpoint(LargeEchoDefaultSocket.class);
+
+        try
+        {
+            wsb.start();
+            URI uri = wsb.getServerBaseURI();
+
+            WebAppContext webapp = wsb.createWebAppContext();
+            wsb.deployWebapp(webapp);
+            // wsb.dump();
+
+            WebSocketClient client = new WebSocketClient(bufferPool);
+            try
+            {
+                client.getPolicy().setMaxTextMessageSize(128*1024);
+                client.start();
+                JettyEchoSocket clientEcho = new JettyEchoSocket();
+                Future<Session> foo = client.connect(clientEcho,uri.resolve("echo/large"));
+                // wait for connect
+                foo.get(1,TimeUnit.SECONDS);
+                // The message size should be bigger than default, but smaller than the limit that LargeEchoSocket specifies
+                byte txt[] = new byte[100 * 1024];
+                Arrays.fill(txt,(byte)'o');
+                String msg = new String(txt,StandardCharsets.UTF_8);
+                clientEcho.sendMessage(msg);
+                Queue<String> msgs = clientEcho.awaitMessages(1);
+                Assert.assertEquals("Expected message",msg,msgs.poll());
+            }
+            finally
+            {
+                client.stop();
+            }
+        }
+        finally
+        {
+            wsb.stop();
+        }
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/MemoryUsageTest.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/MemoryUsageTest.java
new file mode 100644
index 0000000..854f28e
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/MemoryUsageTest.java
@@ -0,0 +1,134 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import static org.hamcrest.Matchers.*;
+
+import java.lang.management.ManagementFactory;
+import java.lang.management.MemoryMXBean;
+import java.lang.management.MemoryUsage;
+import java.net.URI;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import javax.websocket.ContainerProvider;
+import javax.websocket.Endpoint;
+import javax.websocket.EndpointConfig;
+import javax.websocket.MessageHandler;
+import javax.websocket.Session;
+import javax.websocket.WebSocketContainer;
+import javax.websocket.server.ServerEndpointConfig;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer;
+import org.eclipse.jetty.websocket.jsr356.server.samples.echo.BasicEchoEndpoint;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class MemoryUsageTest
+{
+    private Server server;
+    private ServerConnector connector;
+    private WebSocketContainer client;
+
+    @Before
+    public void prepare() throws Exception
+    {
+        server = new Server();
+        connector = new ServerConnector(server);
+        server.addConnector(connector);
+
+        ServletContextHandler context = new ServletContextHandler(server, "/", true, false);
+        ServerContainer container = WebSocketServerContainerInitializer.configureContext(context);
+        ServerEndpointConfig config = ServerEndpointConfig.Builder.create(BasicEchoEndpoint.class, "/").build();
+        container.addEndpoint(config);
+
+        server.start();
+
+        client = ContainerProvider.getWebSocketContainer();
+        server.addBean(client, true);
+    }
+
+    @After
+    public void dispose() throws Exception
+    {
+        server.stop();
+    }
+
+    @SuppressWarnings("unused")
+    @Test
+    public void testMemoryUsage() throws Exception
+    {
+        int sessionCount = 1000;
+        Session[] sessions = new Session[sessionCount];
+
+        MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean();
+
+        System.gc();
+        MemoryUsage heapBefore = memoryMXBean.getHeapMemoryUsage();
+        MemoryUsage nonHeapBefore = memoryMXBean.getNonHeapMemoryUsage();
+
+        URI uri = URI.create("ws://localhost:" + connector.getLocalPort());
+        final CountDownLatch latch = new CountDownLatch(sessionCount);
+        for (int i = 0; i < sessionCount; ++i)
+        {
+            sessions[i] = client.connectToServer(new EndpointAdapter()
+            {
+                @Override
+                public void onMessage(String message)
+                {
+                    latch.countDown();
+                }
+            }, uri);
+        }
+        for (int i = 0; i < sessionCount; ++i)
+        {
+            sessions[i].getBasicRemote().sendText("OK");
+        }
+        latch.await(5 * sessionCount, TimeUnit.MILLISECONDS);
+
+        System.gc();
+        MemoryUsage heapAfter = memoryMXBean.getHeapMemoryUsage();
+        MemoryUsage nonHeapAfter = memoryMXBean.getNonHeapMemoryUsage();
+
+        long heapUsed = heapAfter.getUsed() - heapBefore.getUsed();
+        long nonHeapUsed = nonHeapAfter.getUsed() - nonHeapBefore.getUsed();
+
+//        System.out.println("heapUsed = " + heapUsed);
+//        System.out.println("nonHeapUsed = " + nonHeapUsed);
+//        new CountDownLatch(1).await();
+
+        // Assume no more than 25 KiB per session pair (client and server).
+        long expected = 25 * 1024 * sessionCount;
+        Assert.assertThat("heap used", heapUsed,lessThan(expected));
+    }
+
+    private static abstract class EndpointAdapter extends Endpoint implements MessageHandler.Whole<String>
+    {
+        @Override
+        public void onOpen(Session session, EndpointConfig config)
+        {
+            session.addMessageHandler(this);
+        }
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/OnMessageReturnTest.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/OnMessageReturnTest.java
new file mode 100644
index 0000000..d63bb84
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/OnMessageReturnTest.java
@@ -0,0 +1,82 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import java.net.URI;
+import java.util.Queue;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.toolchain.test.TestingDir;
+import org.eclipse.jetty.webapp.WebAppContext;
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.client.WebSocketClient;
+import org.eclipse.jetty.websocket.common.test.LeakTrackingBufferPoolRule;
+import org.eclipse.jetty.websocket.jsr356.server.samples.echo.EchoReturnEndpoint;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class OnMessageReturnTest
+{
+    @Rule
+    public TestingDir testdir = new TestingDir();
+
+    @Rule
+    public LeakTrackingBufferPoolRule bufferPool = new LeakTrackingBufferPoolRule("Test");
+
+    @Test
+    public void testEchoReturn() throws Exception
+    {
+        WSServer wsb = new WSServer(testdir,"app");
+        wsb.copyWebInf("empty-web.xml");
+        wsb.copyClass(EchoReturnEndpoint.class);
+
+        try
+        {
+            wsb.start();
+            URI uri = wsb.getServerBaseURI();
+
+            WebAppContext webapp = wsb.createWebAppContext();
+            wsb.deployWebapp(webapp);
+
+            WebSocketClient client = new WebSocketClient(bufferPool);
+            try
+            {
+                client.start();
+                JettyEchoSocket clientEcho = new JettyEchoSocket();
+                Future<Session> future = client.connect(clientEcho,uri.resolve("echoreturn"));
+                // wait for connect
+                future.get(1,TimeUnit.SECONDS);
+                clientEcho.sendMessage("Hello World");
+                Queue<String> msgs = clientEcho.awaitMessages(1);
+                Assert.assertEquals("Expected message","Hello World",msgs.poll());
+            }
+            finally
+            {
+                client.stop();
+            }
+        }
+        finally
+        {
+            wsb.stop();
+        }
+    }
+
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/OnPartialTest.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/OnPartialTest.java
new file mode 100644
index 0000000..3f1affd
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/OnPartialTest.java
@@ -0,0 +1,111 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
+import javax.websocket.server.ServerEndpoint;
+import javax.websocket.server.ServerEndpointConfig;
+
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.events.EventDriver;
+import org.eclipse.jetty.websocket.common.events.EventDriverFactory;
+import org.eclipse.jetty.websocket.common.events.EventDriverImpl;
+import org.eclipse.jetty.websocket.common.frames.ContinuationFrame;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
+import org.eclipse.jetty.websocket.jsr356.ClientContainer;
+import org.eclipse.jetty.websocket.jsr356.JsrSession;
+import org.eclipse.jetty.websocket.jsr356.annotations.AnnotatedEndpointScanner;
+import org.eclipse.jetty.websocket.jsr356.endpoints.EndpointInstance;
+import org.eclipse.jetty.websocket.jsr356.server.samples.partial.PartialTrackingSocket;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+
+public class OnPartialTest
+{
+    @Rule
+    public TestName testname = new TestName();
+
+    public EventDriver toEventDriver(Object websocket) throws Throwable
+    {
+        WebSocketPolicy policy = WebSocketPolicy.newServerPolicy();
+        policy.setInputBufferSize(1024);
+        policy.setMaxBinaryMessageBufferSize(1024);
+        policy.setMaxTextMessageBufferSize(1024);
+
+        // Event Driver Factory
+        EventDriverFactory factory = new EventDriverFactory(policy);
+        factory.addImplementation(new JsrServerEndpointImpl());
+
+        // Create EventDriver
+        EventDriverImpl driverImpl = new JsrServerEndpointImpl();
+        Class<?> endpoint = websocket.getClass();
+        ServerEndpoint anno = endpoint.getAnnotation(ServerEndpoint.class);
+        Assert.assertThat("Endpoint: " + endpoint + " should be annotated with @ServerEndpoint",anno,notNullValue());
+        ServerEndpointConfig config = new BasicServerEndpointConfig(endpoint,"/");
+        AnnotatedServerEndpointMetadata metadata = new AnnotatedServerEndpointMetadata(endpoint,config);
+        AnnotatedEndpointScanner<ServerEndpoint, ServerEndpointConfig> scanner = new AnnotatedEndpointScanner<>(metadata);
+        scanner.scan();
+        EndpointInstance ei = new EndpointInstance(websocket,config,metadata);
+        EventDriver driver = driverImpl.create(ei,policy);
+        Assert.assertThat("EventDriver",driver,notNullValue());
+
+        // Create Local JsrSession
+        String id = testname.getMethodName();
+        URI requestURI = URI.create("ws://localhost/" + id);
+        DummyConnection connection = new DummyConnection();
+        ClientContainer container = new ClientContainer();
+        @SuppressWarnings("resource")
+        JsrSession session = new JsrSession(requestURI,driver,connection,container,id);
+        session.setPolicy(policy);
+        session.open();
+        return driver;
+    }
+
+    @Test
+    public void testOnTextPartial() throws Throwable
+    {
+        List<WebSocketFrame> frames = new ArrayList<>();
+        frames.add(new TextFrame().setPayload("Saved").setFin(false));
+        frames.add(new ContinuationFrame().setPayload(" by ").setFin(false));
+        frames.add(new ContinuationFrame().setPayload("zero").setFin(true));
+
+        PartialTrackingSocket socket = new PartialTrackingSocket();
+
+        EventDriver driver = toEventDriver(socket);
+        driver.onConnect();
+
+        for (WebSocketFrame frame : frames)
+        {
+            driver.incomingFrame(frame);
+        }
+
+        Assert.assertThat("Captured Event Queue size",socket.eventQueue.size(),is(3));
+        Assert.assertThat("Event[0]",socket.eventQueue.poll(),is("onPartial(\"Saved\",false)"));
+        Assert.assertThat("Event[1]",socket.eventQueue.poll(),is("onPartial(\" by \",false)"));
+        Assert.assertThat("Event[2]",socket.eventQueue.poll(),is("onPartial(\"zero\",true)"));
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/PingPongTest.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/PingPongTest.java
new file mode 100644
index 0000000..5ed1cbb
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/PingPongTest.java
@@ -0,0 +1,199 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import static org.hamcrest.Matchers.*;
+
+import java.io.File;
+import java.net.URI;
+import java.util.concurrent.TimeUnit;
+
+import javax.websocket.ContainerProvider;
+import javax.websocket.WebSocketContainer;
+
+import org.eclipse.jetty.toolchain.test.EventQueue;
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.eclipse.jetty.webapp.WebAppContext;
+import org.eclipse.jetty.websocket.jsr356.server.samples.pong.PongContextListener;
+import org.eclipse.jetty.websocket.jsr356.server.samples.pong.PongMessageEndpoint;
+import org.eclipse.jetty.websocket.jsr356.server.samples.pong.PongSocket;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class PingPongTest
+{
+    private static WSServer server;
+    private static URI serverUri;
+    private static WebSocketContainer client;
+
+    @BeforeClass
+    public static void startServer() throws Exception
+    {
+        File testdir = MavenTestingUtils.getTargetTestingDir(PingPongTest.class.getName());
+        server = new WSServer(testdir,"app");
+        server.copyWebInf("pong-config-web.xml");
+
+        server.copyClass(PongContextListener.class);
+        server.copyClass(PongMessageEndpoint.class);
+        server.copyClass(PongSocket.class);
+
+        server.start();
+        serverUri = server.getServerBaseURI();
+
+        WebAppContext webapp = server.createWebAppContext();
+        server.deployWebapp(webapp);
+    }
+
+    @BeforeClass
+    public static void startClient() throws Exception
+    {
+        client = ContainerProvider.getWebSocketContainer();
+    }
+
+    @AfterClass
+    public static void stopServer()
+    {
+        server.stop();
+    }
+
+    @Test(timeout = 2000)
+    public void testPingEndpoint() throws Exception
+    {
+        EchoClientSocket socket = new EchoClientSocket(1);
+        URI toUri = serverUri.resolve("ping");
+
+        try
+        {
+            // Connect
+            client.connectToServer(socket,toUri);
+            socket.waitForConnected(1,TimeUnit.SECONDS);
+
+            // Send Ping
+            String msg = "hello";
+            socket.sendPing(msg);
+
+            // Collect Responses
+            socket.awaitAllEvents(1,TimeUnit.SECONDS);
+            EventQueue<String> received = socket.eventQueue;
+
+            // Validate Responses
+            String actual = received.poll();
+            Assert.assertThat("Received Ping Response",actual,containsString("PongMessage[/ping]:" + msg));
+        }
+        finally
+        {
+            // Close
+            socket.close();
+        }
+    }
+    
+    @Test(timeout = 2000)
+    public void testPongEndpoint() throws Exception
+    {
+        EchoClientSocket socket = new EchoClientSocket(1);
+        URI toUri = serverUri.resolve("pong");
+
+        try
+        {
+            // Connect
+            client.connectToServer(socket,toUri);
+            socket.waitForConnected(1,TimeUnit.SECONDS);
+
+            // Send Ping
+            String msg = "hello";
+            socket.sendPong(msg);
+
+            // Collect Responses
+            socket.awaitAllEvents(1,TimeUnit.SECONDS);
+            EventQueue<String> received = socket.eventQueue;
+
+            // Validate Responses
+            Assert.assertThat("Received Ping Responses",received,contains("PongMessage[/pong]:" + msg));
+        }
+        finally
+        {
+            // Close
+            socket.close();
+        }
+    }
+    
+    @Test(timeout = 2000)
+    public void testPingSocket() throws Exception
+    {
+        EchoClientSocket socket = new EchoClientSocket(1);
+        URI toUri = serverUri.resolve("ping-socket");
+
+        try
+        {
+            // Connect
+            client.connectToServer(socket,toUri);
+            socket.waitForConnected(1,TimeUnit.SECONDS);
+
+            // Send Ping
+            String msg = "hello";
+            socket.sendPing(msg);
+
+            // Collect Responses
+            socket.awaitAllEvents(1,TimeUnit.SECONDS);
+            EventQueue<String> received = socket.eventQueue;
+
+            // Validate Responses
+            String actual = received.poll();
+            Assert.assertThat("Received Ping Response",actual,containsString("@OnMessage(PongMessage)[/ping-socket]:" + msg));
+        }
+        finally
+        {
+            // Close
+            socket.close();
+        }
+    }
+    
+    @Test(timeout = 2000)
+    public void testPongSocket() throws Exception
+    {
+        EchoClientSocket socket = new EchoClientSocket(1);
+        URI toUri = serverUri.resolve("pong-socket");
+
+        try
+        {
+            // Connect
+            client.connectToServer(socket,toUri);
+            socket.waitForConnected(1,TimeUnit.SECONDS);
+
+            // Send Ping
+            String msg = "hello";
+            socket.sendPong(msg);
+
+            // Collect Responses
+            socket.awaitAllEvents(1,TimeUnit.SECONDS);
+            EventQueue<String> received = socket.eventQueue;
+
+            // Validate Responses
+            Assert.assertThat("Received Ping Responses",received,contains("@OnMessage(PongMessage)[/pong-socket]:" + msg));
+        }
+        finally
+        {
+            // Close
+            socket.close();
+        }
+    }
+
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/ServerAnnotatedEndpointScanner_GoodSignaturesTest.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/ServerAnnotatedEndpointScanner_GoodSignaturesTest.java
new file mode 100644
index 0000000..2718f42
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/ServerAnnotatedEndpointScanner_GoodSignaturesTest.java
@@ -0,0 +1,202 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import static org.hamcrest.Matchers.notNullValue;
+
+import java.io.Reader;
+import java.lang.reflect.Field;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+
+import javax.websocket.CloseReason;
+import javax.websocket.PongMessage;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+import javax.websocket.server.ServerEndpointConfig;
+
+import org.eclipse.jetty.websocket.jsr356.annotations.AnnotatedEndpointScanner;
+import org.eclipse.jetty.websocket.jsr356.annotations.JsrCallable;
+import org.eclipse.jetty.websocket.jsr356.server.samples.BasicBinaryMessageByteBufferSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.BasicCloseReasonSessionSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.BasicCloseReasonSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.BasicCloseSessionReasonSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.BasicCloseSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.BasicErrorSessionSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.BasicErrorSessionThrowableSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.BasicErrorSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.BasicErrorThrowableSessionSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.BasicErrorThrowableSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.BasicOpenSessionSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.BasicOpenSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.BasicPongMessageSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.BasicTextMessageStringSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.StatelessTextMessageStringSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.beans.DateTextSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.primitives.BooleanObjectTextSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.primitives.BooleanTextSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.primitives.ByteObjectTextSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.primitives.ByteTextSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.primitives.CharTextSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.primitives.CharacterObjectTextSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.primitives.DoubleObjectTextSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.primitives.DoubleTextSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.primitives.FloatObjectTextSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.primitives.FloatTextSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.primitives.IntTextSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.primitives.IntegerObjectTextSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.primitives.ShortObjectTextSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.primitives.ShortTextSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.streaming.ReaderParamSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.streaming.StringReturnReaderParamSocket;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Test {@link AnnotatedEndpointScanner} against various simple, 1 method {@link ServerEndpoint} annotated classes with valid signatures.
+ */
+ at RunWith(Parameterized.class)
+public class ServerAnnotatedEndpointScanner_GoodSignaturesTest
+{
+    public static class Case
+    {
+        public static void add(List<Case[]> data, Class<?> pojo, Field metadataField, Class<?>... expectedParams)
+        {
+            data.add(new Case[]
+            { new Case(pojo,metadataField,expectedParams) });
+        }
+
+        // The websocket pojo to test against
+        Class<?> pojo;
+        // The JsrAnnotatedMetadata field that should be populated
+        Field metadataField;
+        // The expected parameters for the Callable found by the scanner
+        Class<?> expectedParameters[];
+
+        public Case(Class<?> pojo, Field metadataField, Class<?>... expectedParams)
+        {
+            this.pojo = pojo;
+            this.metadataField = metadataField;
+            this.expectedParameters = expectedParams;
+        }
+    }
+
+    @Parameters
+    public static Collection<Case[]> data() throws Exception
+    {
+        List<Case[]> data = new ArrayList<>();
+        Field fOpen = findFieldRef(AnnotatedServerEndpointMetadata.class,"onOpen");
+        Field fClose = findFieldRef(AnnotatedServerEndpointMetadata.class,"onClose");
+        Field fError = findFieldRef(AnnotatedServerEndpointMetadata.class,"onError");
+        Field fText = findFieldRef(AnnotatedServerEndpointMetadata.class,"onText");
+        Field fTextStream = findFieldRef(AnnotatedServerEndpointMetadata.class,"onTextStream");
+        Field fBinary = findFieldRef(AnnotatedServerEndpointMetadata.class,"onBinary");
+        @SuppressWarnings("unused")
+        Field fBinaryStream = findFieldRef(AnnotatedServerEndpointMetadata.class,"onBinaryStream");
+        Field fPong = findFieldRef(AnnotatedServerEndpointMetadata.class,"onPong");
+
+        // @formatter:off
+        // -- Open Events
+        Case.add(data, BasicOpenSocket.class, fOpen);
+        Case.add(data, BasicOpenSessionSocket.class, fOpen, Session.class);
+        // -- Close Events
+        Case.add(data, BasicCloseSocket.class, fClose);
+        Case.add(data, BasicCloseReasonSocket.class, fClose, CloseReason.class);
+        Case.add(data, BasicCloseReasonSessionSocket.class, fClose, CloseReason.class, Session.class);
+        Case.add(data, BasicCloseSessionReasonSocket.class, fClose, Session.class, CloseReason.class);
+        // -- Error Events
+        Case.add(data, BasicErrorSocket.class, fError);
+        Case.add(data, BasicErrorSessionSocket.class, fError, Session.class);
+        Case.add(data, BasicErrorSessionThrowableSocket.class, fError, Session.class, Throwable.class);
+        Case.add(data, BasicErrorThrowableSocket.class, fError, Throwable.class);
+        Case.add(data, BasicErrorThrowableSessionSocket.class, fError, Throwable.class, Session.class);
+        // -- Text Events
+        Case.add(data, BasicTextMessageStringSocket.class, fText, String.class);
+        Case.add(data, StatelessTextMessageStringSocket.class, fText, Session.class, String.class);
+        // -- Primitives
+        Case.add(data, BooleanTextSocket.class, fText, Boolean.TYPE);
+        Case.add(data, BooleanObjectTextSocket.class, fText, Boolean.class);
+        Case.add(data, ByteTextSocket.class, fText, Byte.TYPE);
+        Case.add(data, ByteObjectTextSocket.class, fText, Byte.class);
+        Case.add(data, CharTextSocket.class, fText, Character.TYPE);
+        Case.add(data, CharacterObjectTextSocket.class, fText, Character.class);
+        Case.add(data, DoubleTextSocket.class, fText, Double.TYPE);
+        Case.add(data, DoubleObjectTextSocket.class, fText, Double.class);
+        Case.add(data, FloatTextSocket.class, fText, Float.TYPE);
+        Case.add(data, FloatObjectTextSocket.class, fText, Float.class);
+        Case.add(data, IntTextSocket.class, fText, Integer.TYPE);
+        Case.add(data, IntegerObjectTextSocket.class, fText, Integer.class);
+        Case.add(data, ShortTextSocket.class, fText, Short.TYPE);
+        Case.add(data, ShortObjectTextSocket.class, fText, Short.class);
+        // -- Beans
+        Case.add(data, DateTextSocket.class, fText, Date.class);
+        // -- Reader Events
+        Case.add(data, ReaderParamSocket.class, fTextStream, Reader.class, String.class);
+        Case.add(data, StringReturnReaderParamSocket.class, fTextStream, Reader.class, String.class);
+        // -- Binary Events
+        Case.add(data, BasicBinaryMessageByteBufferSocket.class, fBinary, ByteBuffer.class);
+        // -- Pong Events
+        Case.add(data, BasicPongMessageSocket.class, fPong, PongMessage.class);
+        // @formatter:on
+
+        // TODO: validate return types
+
+        return data;
+    }
+
+    private static Field findFieldRef(Class<?> clazz, String fldName) throws Exception
+    {
+        return clazz.getField(fldName);
+    }
+
+    private Case testcase;
+
+    public ServerAnnotatedEndpointScanner_GoodSignaturesTest(Case testcase)
+    {
+        this.testcase = testcase;
+        System.err.printf("Testing signature of %s%n",testcase.pojo.getName());
+    }
+
+    @Test
+    public void testScan_Basic() throws Exception
+    {
+        AnnotatedServerEndpointMetadata metadata = new AnnotatedServerEndpointMetadata(testcase.pojo,null);
+        AnnotatedEndpointScanner<ServerEndpoint, ServerEndpointConfig> scanner = new AnnotatedEndpointScanner<>(metadata);
+        scanner.scan();
+
+        Assert.assertThat("Metadata",metadata,notNullValue());
+
+        JsrCallable method = (JsrCallable)testcase.metadataField.get(metadata);
+        Assert.assertThat(testcase.metadataField.toString(),method,notNullValue());
+        int len = testcase.expectedParameters.length;
+        for (int i = 0; i < len; i++)
+        {
+            Class<?> expectedParam = testcase.expectedParameters[i];
+            Class<?> actualParam = method.getParamTypes()[i];
+
+            Assert.assertTrue("Parameter[" + i + "] - expected:[" + expectedParam + "], actual:[" + actualParam + "]",actualParam.equals(expectedParam));
+        }
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/ServerAnnotatedEndpointScanner_InvalidSignaturesTest.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/ServerAnnotatedEndpointScanner_InvalidSignaturesTest.java
new file mode 100644
index 0000000..c01f316
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/ServerAnnotatedEndpointScanner_InvalidSignaturesTest.java
@@ -0,0 +1,111 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import java.lang.annotation.Annotation;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import javax.websocket.DeploymentException;
+import javax.websocket.OnClose;
+import javax.websocket.OnError;
+import javax.websocket.OnOpen;
+import javax.websocket.server.ServerEndpoint;
+import javax.websocket.server.ServerEndpointConfig;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.common.events.annotated.InvalidSignatureException;
+import org.eclipse.jetty.websocket.jsr356.annotations.AnnotatedEndpointScanner;
+import org.eclipse.jetty.websocket.jsr356.server.samples.InvalidCloseIntSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.InvalidErrorErrorSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.InvalidErrorExceptionSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.InvalidErrorIntSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.InvalidOpenCloseReasonSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.InvalidOpenIntSocket;
+import org.eclipse.jetty.websocket.jsr356.server.samples.InvalidOpenSessionIntSocket;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+import static org.hamcrest.Matchers.containsString;
+
+/**
+ * Test {@link AnnotatedEndpointScanner} against various simple, 1 method {@link ServerEndpoint} annotated classes with invalid signatures.
+ */
+ at RunWith(Parameterized.class)
+public class ServerAnnotatedEndpointScanner_InvalidSignaturesTest
+{
+    private static final Logger LOG = Log.getLogger(ServerAnnotatedEndpointScanner_InvalidSignaturesTest.class);
+
+    @Parameters
+    public static Collection<Class<?>[]> data()
+    {
+        List<Class<?>[]> data = new ArrayList<>();
+
+        // @formatter:off
+        data.add(new Class<?>[]{ InvalidCloseIntSocket.class, OnClose.class });
+        data.add(new Class<?>[]{ InvalidErrorErrorSocket.class, OnError.class });
+        data.add(new Class<?>[]{ InvalidErrorExceptionSocket.class, OnError.class });
+        data.add(new Class<?>[]{ InvalidErrorIntSocket.class, OnError.class });
+        data.add(new Class<?>[]{ InvalidOpenCloseReasonSocket.class, OnOpen.class });
+        data.add(new Class<?>[]{ InvalidOpenIntSocket.class, OnOpen.class });
+        data.add(new Class<?>[]{ InvalidOpenSessionIntSocket.class, OnOpen.class });
+        // @formatter:on
+
+        // TODO: invalid return types
+        // TODO: static methods
+        // TODO: private or protected methods
+        // TODO: abstract methods
+
+        return data;
+    }
+
+    // The pojo to test
+    private Class<?> pojo;
+    // The annotation class expected to be mentioned in the error message
+    private Class<? extends Annotation> expectedAnnoClass;
+
+    public ServerAnnotatedEndpointScanner_InvalidSignaturesTest(Class<?> pojo, Class<? extends Annotation> expectedAnnotation)
+    {
+        this.pojo = pojo;
+        this.expectedAnnoClass = expectedAnnotation;
+    }
+
+    @Test
+    public void testScan_InvalidSignature() throws DeploymentException
+    {
+        AnnotatedServerEndpointMetadata metadata = new AnnotatedServerEndpointMetadata(pojo,null);
+        AnnotatedEndpointScanner<ServerEndpoint,ServerEndpointConfig> scanner = new AnnotatedEndpointScanner<>(metadata);
+
+        try
+        {
+            scanner.scan();
+            Assert.fail("Expected " + InvalidSignatureException.class + " with message that references " + expectedAnnoClass + " annotation");
+        }
+        catch (InvalidSignatureException e)
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("{}:{}",e.getClass(),e.getMessage());
+            Assert.assertThat("Message",e.getMessage(),containsString(expectedAnnoClass.getSimpleName()));
+        }
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/SessionAltConfig.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/SessionAltConfig.java
new file mode 100644
index 0000000..76b230e
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/SessionAltConfig.java
@@ -0,0 +1,55 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.websocket.Endpoint;
+import javax.websocket.server.ServerApplicationConfig;
+import javax.websocket.server.ServerEndpointConfig;
+
+public class SessionAltConfig implements ServerApplicationConfig
+{
+    @Override
+    public Set<ServerEndpointConfig> getEndpointConfigs(Set<Class<? extends Endpoint>> endpointClasses)
+    {
+        Set<ServerEndpointConfig> configs = new HashSet<>();
+        Class<?> endpointClass = SessionInfoSocket.class;
+        configs.add(ServerEndpointConfig.Builder.create(endpointClass,"/info/{a}/").build());
+        configs.add(ServerEndpointConfig.Builder.create(endpointClass,"/info/{a}/{b}/").build());
+        configs.add(ServerEndpointConfig.Builder.create(endpointClass,"/info/{a}/{b}/{c}/").build());
+        configs.add(ServerEndpointConfig.Builder.create(endpointClass,"/info/{a}/{b}/{c}/{d}/").build());
+        endpointClass = SessionInfoEndpoint.class;
+        configs.add(ServerEndpointConfig.Builder.create(endpointClass,"/einfo/").build());
+        configs.add(ServerEndpointConfig.Builder.create(endpointClass,"/einfo/{a}/").build());
+        configs.add(ServerEndpointConfig.Builder.create(endpointClass,"/einfo/{a}/{b}/").build());
+        configs.add(ServerEndpointConfig.Builder.create(endpointClass,"/einfo/{a}/{b}/{c}/").build());
+        configs.add(ServerEndpointConfig.Builder.create(endpointClass,"/einfo/{a}/{b}/{c}/{d}/").build());
+        return configs;
+    }
+
+    @Override
+    public Set<Class<?>> getAnnotatedEndpointClasses(Set<Class<?>> scanned)
+    {
+        Set<Class<?>> annotated = new HashSet<>();
+        annotated.add(SessionInfoSocket.class);
+        return annotated;
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/SessionInfoEndpoint.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/SessionInfoEndpoint.java
new file mode 100644
index 0000000..660bd47
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/SessionInfoEndpoint.java
@@ -0,0 +1,102 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import javax.websocket.Endpoint;
+import javax.websocket.EndpointConfig;
+import javax.websocket.MessageHandler;
+import javax.websocket.Session;
+
+public class SessionInfoEndpoint extends Endpoint implements MessageHandler.Whole<String>
+{
+    private Session session;
+
+    @Override
+    public void onOpen(Session session, EndpointConfig config)
+    {
+        this.session = session;
+        this.session.addMessageHandler(this);
+    }
+
+    @Override
+    public void onMessage(String message)
+    {
+        try
+        {
+            if ("pathParams".equalsIgnoreCase(message))
+            {
+                StringBuilder ret = new StringBuilder();
+                ret.append("pathParams");
+                Map<String, String> pathParams = session.getPathParameters();
+                if (pathParams == null)
+                {
+                    ret.append("=<null>");
+                }
+                else
+                {
+                    ret.append('[').append(pathParams.size()).append(']');
+                    List<String> keys = new ArrayList<>();
+                    for (String key : pathParams.keySet())
+                    {
+                        keys.add(key);
+                    }
+                    Collections.sort(keys);
+                    for (String key : keys)
+                    {
+                        String value = pathParams.get(key);
+                        ret.append(": '").append(key).append("'=").append(value);
+                    }
+                }
+                session.getBasicRemote().sendText(ret.toString());
+                return;
+            }
+
+            if ("requestUri".equalsIgnoreCase(message))
+            {
+                StringBuilder ret = new StringBuilder();
+                ret.append("requestUri=");
+                URI uri = session.getRequestURI();
+                if (uri == null)
+                {
+                    ret.append("=<null>");
+                }
+                else
+                {
+                    ret.append(uri.toASCIIString());
+                }
+                session.getBasicRemote().sendText(ret.toString());
+                return;
+            }
+
+            // simple echo
+            session.getBasicRemote().sendText("echo:'" + message + "'");
+        }
+        catch (IOException e)
+        {
+            e.printStackTrace(System.err);
+        }
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/SessionInfoSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/SessionInfoSocket.java
new file mode 100644
index 0000000..cde772c
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/SessionInfoSocket.java
@@ -0,0 +1,83 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import javax.websocket.OnMessage;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+ at ServerEndpoint(value = "/info/")
+public class SessionInfoSocket
+{
+    @OnMessage
+    public String onMessage(Session session, String message)
+    {
+        if ("pathParams".equalsIgnoreCase(message))
+        {
+            StringBuilder ret = new StringBuilder();
+            ret.append("pathParams");
+            Map<String, String> pathParams = session.getPathParameters();
+            if (pathParams == null)
+            {
+                ret.append("=<null>");
+            }
+            else
+            {
+                ret.append('[').append(pathParams.size()).append(']');
+                List<String> keys = new ArrayList<>();
+                for (String key : pathParams.keySet())
+                {
+                    keys.add(key);
+                }
+                Collections.sort(keys);
+                for (String key : keys)
+                {
+                    String value = pathParams.get(key);
+                    ret.append(": '").append(key).append("'=").append(value);
+                }
+            }
+            return ret.toString();
+        }
+
+        if ("requestUri".equalsIgnoreCase(message))
+        {
+            StringBuilder ret = new StringBuilder();
+            ret.append("requestUri=");
+            URI uri = session.getRequestURI();
+            if (uri == null)
+            {
+                ret.append("=<null>");
+            }
+            else
+            {
+                ret.append(uri.toASCIIString());
+            }
+            return ret.toString();
+        }
+
+        // simple echo
+        return "echo:'" + message + "'";
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/SessionTest.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/SessionTest.java
new file mode 100644
index 0000000..06a4600
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/SessionTest.java
@@ -0,0 +1,244 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import static org.hamcrest.Matchers.*;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Queue;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.eclipse.jetty.servlet.DefaultServlet;
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.eclipse.jetty.webapp.WebAppContext;
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.client.WebSocketClient;
+import org.eclipse.jetty.websocket.common.test.LeakTrackingBufferPoolRule;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+ at RunWith(Parameterized.class)
+public class SessionTest
+{
+    private static interface Case
+    {
+        public void customize(WebAppContext context);
+    }
+
+    @Parameters
+    public static Collection<Case[]> data()
+    {
+        List<Case[]> cases = new ArrayList<>();
+        cases.add(new Case[]
+        { new Case()
+        {
+            @Override
+            public void customize(WebAppContext context)
+            {
+                // no customization
+            }
+        } });
+        cases.add(new Case[]
+        { new Case()
+        {
+            @Override
+            public void customize(WebAppContext context)
+            {
+                // Test with DefaultServlet only
+                context.addServlet(DefaultServlet.class,"/");
+            }
+        } });
+        cases.add(new Case[]
+        { new Case()
+        {
+            @Override
+            public void customize(WebAppContext context)
+            {
+                // Test with Servlet mapped to "/*"
+                context.addServlet(DefaultServlet.class,"/*");
+            }
+        } });
+        cases.add(new Case[]
+        { new Case()
+        {
+            @Override
+            public void customize(WebAppContext context)
+            {
+                // Test with Servlet mapped to "/info/*"
+                context.addServlet(DefaultServlet.class,"/info/*");
+            }
+        } });
+        return cases;
+    }
+
+    @Rule
+    public LeakTrackingBufferPoolRule bufferPool = new LeakTrackingBufferPoolRule("Test");
+
+    private final Case testcase;
+    private final static AtomicInteger ID = new AtomicInteger(0);
+    private WSServer server;
+    private URI serverUri;
+
+    public SessionTest(Case testcase)
+    {
+        this.testcase = testcase;
+    }
+
+    @Before
+    public void startServer() throws Exception
+    {
+        server = new WSServer(MavenTestingUtils.getTargetTestingDir(SessionTest.class.getSimpleName() + "-" + ID.incrementAndGet()),"app");
+        server.copyWebInf("empty-web.xml");
+        server.copyClass(SessionInfoSocket.class);
+        server.copyClass(SessionAltConfig.class);
+        server.start();
+        serverUri = server.getServerBaseURI();
+
+        WebAppContext webapp = server.createWebAppContext();
+        testcase.customize(webapp);
+        server.deployWebapp(webapp);
+    }
+
+    @After
+    public void stopServer()
+    {
+        server.stop();
+    }
+
+    private void assertResponse(String requestPath, String requestMessage, String expectedResponse) throws Exception
+    {
+        WebSocketClient client = new WebSocketClient(bufferPool);
+        try
+        {
+            client.start();
+            JettyEchoSocket clientEcho = new JettyEchoSocket();
+            Future<Session> future = client.connect(clientEcho,serverUri.resolve(requestPath));
+            // wait for connect
+            future.get(1,TimeUnit.SECONDS);
+            clientEcho.sendMessage(requestMessage);
+            Queue<String> msgs = clientEcho.awaitMessages(1);
+            Assert.assertThat("Expected message",msgs.poll(),is(expectedResponse));
+        }
+        finally
+        {
+            client.stop();
+        }
+    }
+
+    @Test
+    public void testPathParams_Annotated_Empty() throws Exception
+    {
+        assertResponse("info/","pathParams","pathParams[0]");
+    }
+
+    @Test
+    public void testPathParams_Annotated_Single() throws Exception
+    {
+        assertResponse("info/apple/","pathParams","pathParams[1]: 'a'=apple");
+    }
+
+    @Test
+    public void testPathParams_Annotated_Double() throws Exception
+    {
+        assertResponse("info/apple/pear/","pathParams","pathParams[2]: 'a'=apple: 'b'=pear");
+    }
+
+    @Test
+    public void testPathParams_Annotated_Triple() throws Exception
+    {
+        assertResponse("info/apple/pear/cherry/","pathParams","pathParams[3]: 'a'=apple: 'b'=pear: 'c'=cherry");
+    }
+
+    @Test
+    public void testPathParams_Endpoint_Empty() throws Exception
+    {
+        assertResponse("einfo/","pathParams","pathParams[0]");
+    }
+
+    @Test
+    public void testPathParams_Endpoint_Single() throws Exception
+    {
+        assertResponse("einfo/apple/","pathParams","pathParams[1]: 'a'=apple");
+    }
+
+    @Test
+    public void testPathParams_Endpoint_Double() throws Exception
+    {
+        assertResponse("einfo/apple/pear/","pathParams","pathParams[2]: 'a'=apple: 'b'=pear");
+    }
+
+    @Test
+    public void testPathParams_Endpoint_Triple() throws Exception
+    {
+        assertResponse("einfo/apple/pear/cherry/","pathParams","pathParams[3]: 'a'=apple: 'b'=pear: 'c'=cherry");
+    }
+
+    @Test
+    public void testRequestUri_Annotated_Basic() throws Exception
+    {
+        URI expectedUri = serverUri.resolve("info/");
+        assertResponse("info/","requestUri","requestUri=" + expectedUri.toASCIIString());
+    }
+
+    @Test
+    public void testRequestUri_Annotated_WithPathParam() throws Exception
+    {
+        URI expectedUri = serverUri.resolve("info/apple/banana/");
+        assertResponse("info/apple/banana/","requestUri","requestUri=" + expectedUri.toASCIIString());
+    }
+
+    @Test
+    public void testRequestUri_Annotated_WithPathParam_WithQuery() throws Exception
+    {
+        URI expectedUri = serverUri.resolve("info/apple/banana/?fruit=fresh&store=grandmasfarm");
+        assertResponse("info/apple/banana/?fruit=fresh&store=grandmasfarm","requestUri","requestUri=" + expectedUri.toASCIIString());
+    }
+
+    @Test
+    public void testRequestUri_Endpoint_Basic() throws Exception
+    {
+        URI expectedUri = serverUri.resolve("einfo/");
+        assertResponse("einfo/","requestUri","requestUri=" + expectedUri.toASCIIString());
+    }
+
+    @Test
+    public void testRequestUri_Endpoint_WithPathParam() throws Exception
+    {
+        URI expectedUri = serverUri.resolve("einfo/apple/banana/");
+        assertResponse("einfo/apple/banana/","requestUri","requestUri=" + expectedUri.toASCIIString());
+    }
+
+    @Test
+    public void testRequestUri_Endpoint_WithPathParam_WithQuery() throws Exception
+    {
+        URI expectedUri = serverUri.resolve("einfo/apple/banana/?fruit=fresh&store=grandmasfarm");
+        assertResponse("einfo/apple/banana/?fruit=fresh&store=grandmasfarm","requestUri","requestUri=" + expectedUri.toASCIIString());
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/StreamTest.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/StreamTest.java
new file mode 100644
index 0000000..ff96561
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/StreamTest.java
@@ -0,0 +1,348 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URI;
+import java.security.DigestOutputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.CloseReason;
+import javax.websocket.CloseReason.CloseCode;
+import javax.websocket.CloseReason.CloseCodes;
+import javax.websocket.ContainerProvider;
+import javax.websocket.EndpointConfig;
+import javax.websocket.HandshakeResponse;
+import javax.websocket.OnClose;
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.WebSocketContainer;
+import javax.websocket.server.HandshakeRequest;
+import javax.websocket.server.PathParam;
+import javax.websocket.server.ServerEndpoint;
+import javax.websocket.server.ServerEndpointConfig;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.toolchain.test.FS;
+import org.eclipse.jetty.toolchain.test.IO;
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.common.test.LeakTrackingBufferPoolRule;
+import org.eclipse.jetty.websocket.common.util.Hex;
+import org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+
+import static org.hamcrest.Matchers.equalToIgnoringCase;
+import static org.hamcrest.Matchers.is;
+
+public class StreamTest
+{
+    private static final Logger LOG = Log.getLogger(StreamTest.class);
+
+    @Rule
+    public LeakTrackingBufferPoolRule bufferPool = new LeakTrackingBufferPoolRule("Test");
+
+    private static File outputDir;
+    private static Server server;
+    private static URI serverUri;
+
+    @BeforeClass
+    public static void startServer() throws Exception
+    {
+        server = new Server();
+        ServerConnector connector = new ServerConnector(server);
+        connector.setPort(0);
+        server.addConnector(connector);
+
+        ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
+        context.setContextPath("/");
+        server.setHandler(context);
+
+        ServerContainer wsContainer = WebSocketServerContainerInitializer.configureContext(context);
+
+        // Prepare Server Side Output directory for uploaded files
+        outputDir = MavenTestingUtils.getTargetTestingDir(StreamTest.class.getName());
+        FS.ensureEmpty(outputDir);
+
+        // Create Server Endpoint with output directory configuration
+        ServerEndpointConfig config = ServerEndpointConfig.Builder.create(UploadSocket.class,"/upload/{filename}")
+                .configurator(new ServerUploadConfigurator(outputDir)).build();
+        wsContainer.addEndpoint(config);
+
+        server.start();
+
+        String host = connector.getHost();
+        if (host == null)
+        {
+            host = "localhost";
+        }
+        int port = connector.getLocalPort();
+        serverUri = new URI(String.format("ws://%s:%d/",host,port));
+        if (LOG.isDebugEnabled())
+            LOG.debug("Server started on {}",serverUri);
+    }
+
+    @AfterClass
+    public static void stopServer() throws Exception
+    {
+        server.stop();
+    }
+
+    @Test
+    public void testUploadSmall() throws Exception
+    {
+        upload("small.png");
+    }
+
+    @Test
+    public void testUploadMedium() throws Exception
+    {
+        upload("medium.png");
+    }
+
+    @Test
+    public void testUploadLarger() throws Exception
+    {
+        upload("larger.png");
+    }
+
+    @Test
+    public void testUploadLargest() throws Exception
+    {
+        upload("largest.jpg");
+    }
+
+    private void upload(String filename) throws Exception
+    {
+        File inputFile = MavenTestingUtils.getTestResourceFile("data/" + filename);
+
+        WebSocketContainer client = ContainerProvider.getWebSocketContainer();
+        ClientSocket socket = new ClientSocket();
+        URI uri = serverUri.resolve("/upload/" + filename);
+        client.connectToServer(socket,uri);
+        socket.uploadFile(inputFile);
+        socket.awaitClose();
+
+        File sha1File = MavenTestingUtils.getTestResourceFile("data/" + filename + ".sha");
+        assertFileUpload(new File(outputDir,filename),sha1File);
+    }
+
+    /**
+     * Verify that the file sha1sum matches the previously calculated sha1sum
+     * 
+     * @param file
+     *            the file to validate
+     * @param shaFile
+     *            the sha1sum file to verify against
+     */
+    private void assertFileUpload(File file, File sha1File) throws IOException, NoSuchAlgorithmException
+    {
+        Assert.assertThat("Path should exist: " + file,file.exists(),is(true));
+        Assert.assertThat("Path should not be a directory:" + file,file.isDirectory(),is(false));
+
+        String expectedSha1 = loadExpectedSha1Sum(sha1File);
+        String actualSha1 = calculateSha1Sum(file);
+
+        Assert.assertThat("SHA1Sum of content: " + file,expectedSha1,equalToIgnoringCase(actualSha1));
+    }
+
+    private String calculateSha1Sum(File file) throws IOException, NoSuchAlgorithmException
+    {
+        MessageDigest digest = MessageDigest.getInstance("SHA1");
+        try (FileInputStream fis = new FileInputStream(file);
+                NoOpOutputStream noop = new NoOpOutputStream();
+                DigestOutputStream digester = new DigestOutputStream(noop,digest))
+        {
+            IO.copy(fis,digester);
+            return Hex.asHex(digest.digest());
+        }
+    }
+
+    private String loadExpectedSha1Sum(File sha1File) throws IOException
+    {
+        String contents = IO.readToString(sha1File);
+        Pattern pat = Pattern.compile("^[0-9A-Fa-f]*");
+        Matcher mat = pat.matcher(contents);
+        Assert.assertTrue("Should have found HEX code in SHA1 file: " + sha1File,mat.find());
+        return mat.group();
+    }
+
+    @ClientEndpoint
+    public static class ClientSocket
+    {
+        private Session session;
+        private CountDownLatch closeLatch = new CountDownLatch(1);
+
+        @OnOpen
+        public void onOpen(Session session)
+        {
+            this.session = session;
+        }
+
+        public void close() throws IOException
+        {
+            this.session.close();
+        }
+
+        @OnClose
+        public void onClose(CloseReason close)
+        {
+            closeLatch.countDown();
+        }
+
+        public void awaitClose() throws InterruptedException
+        {
+            Assert.assertThat("Wait for ClientSocket close success",closeLatch.await(5,TimeUnit.SECONDS),is(true));
+        }
+
+        @OnError
+        public void onError(Throwable t)
+        {
+            t.printStackTrace(System.err);
+        }
+
+        public void uploadFile(File inputFile) throws IOException
+        {
+            try (OutputStream out = session.getBasicRemote().getSendStream(); FileInputStream in = new FileInputStream(inputFile))
+            {
+                IO.copy(in,out);
+            }
+        }
+    }
+
+    public static class ServerUploadConfigurator extends ServerEndpointConfig.Configurator
+    {
+        public static final String OUTPUT_DIR = "outputDir";
+        private final File outputDir;
+
+        public ServerUploadConfigurator(File outputDir)
+        {
+            this.outputDir = outputDir;
+        }
+
+        @Override
+        public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response)
+        {
+            sec.getUserProperties().put(OUTPUT_DIR,this.outputDir);
+            super.modifyHandshake(sec,request,response);
+        }
+    }
+
+    @ServerEndpoint("/upload/{filename}")
+    public static class UploadSocket
+    {
+        private File outputDir;
+        private Session session;
+
+        @OnOpen
+        public void onOpen(Session session, EndpointConfig config)
+        {
+            this.session = session;
+            // not setting max message here, as streaming handling
+            // should allow any sized message.
+            this.outputDir = (File)config.getUserProperties().get(ServerUploadConfigurator.OUTPUT_DIR);
+        }
+
+        @OnMessage
+        public void onMessage(InputStream stream, @PathParam("filename") String filename) throws IOException
+        {
+            File outputFile = new File(outputDir,filename);
+            CloseCode closeCode = CloseCodes.NORMAL_CLOSURE;
+            String closeReason = "";
+            try (FileOutputStream out = new FileOutputStream(outputFile))
+            {
+                IO.copy(stream,out);
+                if (outputFile.exists())
+                {
+                    closeReason = String.format("Received %,d bytes",outputFile.length());
+                    if (LOG.isDebugEnabled())
+                        LOG.debug(closeReason);
+                }
+                else
+                {
+                    LOG.warn("Uploaded file does not exist: " + outputFile);
+                }
+            }
+            catch (IOException e)
+            {
+                e.printStackTrace(System.err);
+                closeReason = "Error writing file";
+                closeCode = CloseCodes.UNEXPECTED_CONDITION;
+            }
+            finally
+            {
+                session.close(new CloseReason(closeCode,closeReason));
+            }
+        }
+
+        @OnError
+        public void onError(Throwable t)
+        {
+            t.printStackTrace(System.err);
+        }
+    }
+
+    private static class NoOpOutputStream extends OutputStream
+    {
+        @Override
+        public void write(byte[] b) throws IOException
+        {
+        }
+
+        @Override
+        public void write(byte[] b, int off, int len) throws IOException
+        {
+        }
+
+        @Override
+        public void flush() throws IOException
+        {
+        }
+
+        @Override
+        public void close() throws IOException
+        {
+        }
+
+        @Override
+        public void write(int b) throws IOException
+        {
+        }
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/TextStreamTest.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/TextStreamTest.java
new file mode 100644
index 0000000..ecc948a
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/TextStreamTest.java
@@ -0,0 +1,179 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.io.Writer;
+import java.net.URI;
+import java.util.Random;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import javax.websocket.ClientEndpoint;
+import javax.websocket.ContainerProvider;
+import javax.websocket.OnMessage;
+import javax.websocket.Session;
+import javax.websocket.WebSocketContainer;
+import javax.websocket.server.ServerEndpoint;
+import javax.websocket.server.ServerEndpointConfig;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class TextStreamTest
+{
+    private static final String PATH = "/echo";
+    private static final String CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
+
+    private Server server;
+    private ServerConnector connector;
+    private WebSocketContainer wsClient;
+
+    @Before
+    public void prepare() throws Exception
+    {
+        server = new Server();
+        connector = new ServerConnector(server);
+        server.addConnector(connector);
+
+        ServletContextHandler context = new ServletContextHandler(server, "/", true, false);
+        ServerContainer container = WebSocketServerContainerInitializer.configureContext(context);
+        ServerEndpointConfig config = ServerEndpointConfig.Builder.create(ServerTextStreamer.class, PATH).build();
+        container.addEndpoint(config);
+
+        server.start();
+
+        wsClient = ContainerProvider.getWebSocketContainer();
+        server.addBean(wsClient, true);
+    }
+
+    @After
+    public void dispose() throws Exception
+    {
+        server.stop();
+    }
+
+    @Test
+    public void testEchoWithMediumMessage() throws Exception
+    {
+        testEcho(1024);
+    }
+
+    @Test
+    public void testLargestMessage() throws Exception
+    {
+        testEcho(wsClient.getDefaultMaxBinaryMessageBufferSize());
+    }
+
+    private void testEcho(int size) throws Exception
+    {
+        char[] data = randomChars(size);
+        URI uri = URI.create("ws://localhost:" + connector.getLocalPort() + PATH);
+        ClientTextStreamer client = new ClientTextStreamer();
+        Session session = wsClient.connectToServer(client, uri);
+
+        try (Writer output = session.getBasicRemote().getSendWriter())
+        {
+             output.write(data);
+        }
+
+        Assert.assertTrue(client.await(5, TimeUnit.SECONDS));
+        Assert.assertArrayEquals(data, client.getEcho());
+    }
+
+    @Test
+    public void testMoreThanLargestMessageOneByteAtATime() throws Exception
+    {
+        int size = wsClient.getDefaultMaxBinaryMessageBufferSize() + 16;
+        char[] data = randomChars(size);
+        URI uri = URI.create("ws://localhost:" + connector.getLocalPort() + PATH);
+        ClientTextStreamer client = new ClientTextStreamer();
+        Session session = wsClient.connectToServer(client, uri);
+
+        try (Writer output = session.getBasicRemote().getSendWriter())
+        {
+            for (int i = 0; i < size; ++i)
+                output.write(data[i]);
+        }
+
+        Assert.assertTrue(client.await(5, TimeUnit.SECONDS));
+        Assert.assertArrayEquals(data, client.getEcho());
+    }
+
+    private char[] randomChars(int size)
+    {
+        char[] data = new char[size];
+        Random random = new Random();
+        for (int i = 0; i < data.length; ++i)
+            data[i] = CHARS.charAt(random.nextInt(CHARS.length()));
+        return data;
+    }
+
+    @ClientEndpoint
+    public static class ClientTextStreamer
+    {
+        private final CountDownLatch latch = new CountDownLatch(1);
+        private final StringBuilder output = new StringBuilder();
+
+        @OnMessage
+        public void echoed(Reader input) throws IOException
+        {
+            while (true)
+            {
+                int read = input.read();
+                if (read < 0)
+                    break;
+                output.append((char)read);
+            }
+            latch.countDown();
+        }
+
+        public char[] getEcho()
+        {
+            return output.toString().toCharArray();
+        }
+
+        public boolean await(long timeout, TimeUnit unit) throws InterruptedException
+        {
+            return latch.await(timeout, unit);
+        }
+    }
+
+    @ServerEndpoint(PATH)
+    public static class ServerTextStreamer
+    {
+        @OnMessage
+        public void echo(Session session, Reader input) throws IOException
+        {
+            char[] buffer = new char[128];
+            try (Writer output = session.getBasicRemote().getSendWriter())
+            {
+                int read;
+                while ((read = input.read(buffer)) >= 0)
+                    output.write(buffer, 0, read);
+            }
+        }
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/TrackingSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/TrackingSocket.java
new file mode 100644
index 0000000..981884c
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/TrackingSocket.java
@@ -0,0 +1,127 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import javax.websocket.CloseReason;
+import javax.websocket.CloseReason.CloseCode;
+
+import org.eclipse.jetty.toolchain.test.EventQueue;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.junit.Assert;
+
+import static org.hamcrest.Matchers.greaterThanOrEqualTo;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+
+/**
+ * Abstract base socket used for tracking state and events within the socket for testing reasons.
+ */
+public abstract class TrackingSocket
+{
+    private static final Logger LOG = Log.getLogger(TrackingSocket.class);
+    
+    public CloseReason closeReason;
+    public EventQueue<String> eventQueue = new EventQueue<String>();
+    public EventQueue<Throwable> errorQueue = new EventQueue<>();
+    public CountDownLatch openLatch = new CountDownLatch(1);
+    public CountDownLatch closeLatch = new CountDownLatch(1);
+    public CountDownLatch dataLatch = new CountDownLatch(1);
+    
+    protected void addError(Throwable t)
+    {
+        LOG.warn(t);
+        errorQueue.add(t);
+    }
+
+    protected void addEvent(String format, Object... args)
+    {
+        eventQueue.add(String.format(format,args));
+    }
+
+    public void assertClose(CloseCode expectedCode, String expectedReason) throws InterruptedException
+    {
+        assertCloseCode(expectedCode);
+        assertCloseReason(expectedReason);
+    }
+
+    public void assertCloseCode(CloseCode expectedCode) throws InterruptedException
+    {
+        Assert.assertThat("Was Closed",closeLatch.await(50,TimeUnit.MILLISECONDS),is(true));
+        Assert.assertThat("CloseReason",closeReason,notNullValue());
+        Assert.assertThat("Close Code",closeReason.getCloseCode(),is(expectedCode));
+    }
+
+    private void assertCloseReason(String expectedReason)
+    {
+        Assert.assertThat("Close Reason",closeReason.getReasonPhrase(),is(expectedReason));
+    }
+
+    public void assertEvent(String expected)
+    {
+        String actual = eventQueue.poll();
+        Assert.assertEquals("Event",expected,actual);
+    }
+
+    public void assertIsOpen() throws InterruptedException
+    {
+        assertWasOpened();
+        assertNotClosed();
+    }
+
+    public void assertNotClosed()
+    {
+        Assert.assertThat("Closed Latch",closeLatch.getCount(),greaterThanOrEqualTo(1L));
+    }
+
+    public void assertNotOpened()
+    {
+        Assert.assertThat("Open Latch",openLatch.getCount(),greaterThanOrEqualTo(1L));
+    }
+
+    public void assertWasOpened() throws InterruptedException
+    {
+        Assert.assertThat("Was Opened",openLatch.await(500,TimeUnit.MILLISECONDS),is(true));
+    }
+
+    public void clear()
+    {
+        eventQueue.clear();
+        errorQueue.clear();
+    }
+
+    public void waitForClose(int timeoutDuration, TimeUnit timeoutUnit) throws InterruptedException
+    {
+        Assert.assertThat("Client Socket Closed",closeLatch.await(timeoutDuration,timeoutUnit),is(true));
+    }
+
+    public void waitForConnected(int timeoutDuration, TimeUnit timeoutUnit) throws InterruptedException
+    {
+        Assert.assertThat("Client Socket Connected",openLatch.await(timeoutDuration,timeoutUnit),is(true));
+    }
+
+    public void waitForData(int timeoutDuration, TimeUnit timeoutUnit) throws InterruptedException
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("Waiting for message");
+        Assert.assertThat("Data Received",dataLatch.await(timeoutDuration,timeoutUnit),is(true));
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/WSServer.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/WSServer.java
new file mode 100644
index 0000000..dedd13f
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/WSServer.java
@@ -0,0 +1,199 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URL;
+
+import org.eclipse.jetty.annotations.AnnotationConfiguration;
+import org.eclipse.jetty.plus.webapp.EnvConfiguration;
+import org.eclipse.jetty.plus.webapp.PlusConfiguration;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.handler.ContextHandlerCollection;
+import org.eclipse.jetty.server.handler.HandlerCollection;
+import org.eclipse.jetty.toolchain.test.FS;
+import org.eclipse.jetty.toolchain.test.IO;
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.eclipse.jetty.toolchain.test.OS;
+import org.eclipse.jetty.toolchain.test.TestingDir;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.webapp.Configuration;
+import org.eclipse.jetty.webapp.FragmentConfiguration;
+import org.eclipse.jetty.webapp.MetaInfConfiguration;
+import org.eclipse.jetty.webapp.WebAppContext;
+import org.eclipse.jetty.webapp.WebInfConfiguration;
+import org.eclipse.jetty.webapp.WebXmlConfiguration;
+import org.junit.Assert;
+
+import static org.hamcrest.Matchers.notNullValue;
+
+/**
+ * Utility to build out exploded directory WebApps, in the /target/tests/ directory, for testing out servers that use javax.websocket endpoints.
+ * <p>
+ * This is particularly useful when the WebSocket endpoints are discovered via the javax.websocket annotation scanning.
+ */
+public class WSServer
+{
+    private static final Logger LOG = Log.getLogger(WSServer.class);
+    private final File contextDir;
+    private final String contextPath;
+    private Server server;
+    private URI serverUri;
+    private ContextHandlerCollection contexts;
+    private File webinf;
+    private File classesDir;
+
+    public WSServer(TestingDir testdir, String contextName)
+    {
+        this(testdir.getDir(),contextName);
+    }
+
+    public WSServer(File testdir, String contextName)
+    {
+        this.contextDir = new File(testdir,contextName);
+        this.contextPath = "/" + contextName;
+        FS.ensureEmpty(contextDir);
+    }
+
+    public void copyClass(Class<?> clazz) throws Exception
+    {
+        ClassLoader cl = Thread.currentThread().getContextClassLoader();
+        String endpointPath = clazz.getName().replace('.','/') + ".class";
+        URL classUrl = cl.getResource(endpointPath);
+        Assert.assertThat("Class URL for: " + clazz,classUrl,notNullValue());
+        File destFile = new File(classesDir,OS.separators(endpointPath));
+        FS.ensureDirExists(destFile.getParentFile());
+        File srcFile = new File(classUrl.toURI());
+        IO.copy(srcFile,destFile);
+    }
+
+    public void copyEndpoint(Class<?> endpointClass) throws Exception
+    {
+        copyClass(endpointClass);
+    }
+
+    public void copyWebInf(String testResourceName) throws IOException
+    {
+        webinf = new File(contextDir,"WEB-INF");
+        FS.ensureDirExists(webinf);
+        classesDir = new File(webinf,"classes");
+        FS.ensureDirExists(classesDir);
+        File webxml = new File(webinf,"web.xml");
+        File testWebXml = MavenTestingUtils.getTestResourceFile(testResourceName);
+        IO.copy(testWebXml,webxml);
+    }
+
+    public WebAppContext createWebAppContext() throws MalformedURLException, IOException
+    {
+        WebAppContext context = new WebAppContext();
+        context.setContextPath(this.contextPath);
+        context.setBaseResource(Resource.newResource(this.contextDir));
+        context.setAttribute("org.eclipse.jetty.websocket.jsr356",Boolean.TRUE);
+
+        // @formatter:off
+        context.setConfigurations(new Configuration[] {
+                new AnnotationConfiguration(),
+                new WebXmlConfiguration(),
+                new WebInfConfiguration(),
+                new PlusConfiguration(), 
+                new MetaInfConfiguration(),
+                new FragmentConfiguration(), 
+                new EnvConfiguration()});
+        // @formatter:on
+
+        return context;
+    }
+
+    public void createWebInf() throws IOException
+    {
+        copyWebInf("empty-web.xml");
+    }
+
+    public void deployWebapp(WebAppContext webapp) throws Exception
+    {
+        contexts.addHandler(webapp);
+        contexts.manage(webapp);
+        webapp.start();
+        if (LOG.isDebugEnabled())
+        {
+            webapp.dump(System.err);
+        }
+    }
+
+    public void dump()
+    {
+        server.dumpStdErr();
+    }
+
+    public URI getServerBaseURI()
+    {
+        return serverUri;
+    }
+
+    public File getWebAppDir()
+    {
+        return this.contextDir;
+    }
+
+    public void start() throws Exception
+    {
+        server = new Server();
+        ServerConnector connector = new ServerConnector(server);
+        connector.setPort(0);
+        server.addConnector(connector);
+
+        HandlerCollection handlers = new HandlerCollection();
+        contexts = new ContextHandlerCollection();
+        handlers.addHandler(contexts);
+        server.setHandler(handlers);
+
+        server.start();
+
+        String host = connector.getHost();
+        if (host == null)
+        {
+            host = "localhost";
+        }
+        int port = connector.getLocalPort();
+        serverUri = new URI(String.format("ws://%s:%d%s/",host,port,contextPath));
+        if (LOG.isDebugEnabled())
+            LOG.debug("Server started on {}",serverUri);
+    }
+
+    public void stop()
+    {
+        if (server != null)
+        {
+            try
+            {
+                server.stop();
+            }
+            catch (Exception e)
+            {
+                e.printStackTrace(System.err);
+            }
+        }
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/browser/JsrBrowserConfigurator.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/browser/JsrBrowserConfigurator.java
new file mode 100644
index 0000000..6c2baac
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/browser/JsrBrowserConfigurator.java
@@ -0,0 +1,52 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.browser;
+
+import java.util.Collections;
+import java.util.List;
+
+import javax.websocket.Extension;
+import javax.websocket.HandshakeResponse;
+import javax.websocket.server.HandshakeRequest;
+import javax.websocket.server.ServerEndpointConfig;
+
+import org.eclipse.jetty.websocket.api.util.QuoteUtil;
+
+public class JsrBrowserConfigurator extends ServerEndpointConfig.Configurator
+{
+    @Override
+    public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response)
+    {
+        super.modifyHandshake(sec,request,response);
+        sec.getUserProperties().put("userAgent",getHeaderValue(request,"User-Agent"));
+        sec.getUserProperties().put("requestedExtensions",getHeaderValue(request,"Sec-WebSocket-Extensions"));
+    }
+
+    private String getHeaderValue(HandshakeRequest request, String key)
+    {
+        List<String> value = request.getHeaders().get(key);
+        return QuoteUtil.join(value,",");
+    }
+
+    @Override
+    public List<Extension> getNegotiatedExtensions(List<Extension> installed, List<Extension> requested)
+    {
+        return Collections.emptyList();
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/browser/JsrBrowserDebugTool.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/browser/JsrBrowserDebugTool.java
new file mode 100644
index 0000000..473f39b
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/browser/JsrBrowserDebugTool.java
@@ -0,0 +1,98 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.browser;
+
+import javax.servlet.ServletException;
+import javax.websocket.DeploymentException;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.servlet.DefaultServlet;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.jsr356.server.ServerContainer;
+import org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer;
+
+/**
+ * Tool to help debug JSR based websocket circumstances reported around browsers.
+ * <p>
+ * Provides a server, with a few simple websocket's that can be twiddled from a browser. This helps with setting up breakpoints and whatnot to help debug our
+ * websocket implementation from the context of a browser client.
+ */
+public class JsrBrowserDebugTool
+{
+    private static final Logger LOG = Log.getLogger(JsrBrowserDebugTool.class);
+
+    public static void main(String[] args)
+    {
+        int port = 8080;
+
+        for (int i = 0; i < args.length; i++)
+        {
+            String a = args[i];
+            if ("-p".equals(a) || "--port".equals(a))
+            {
+                port = Integer.parseInt(args[++i]);
+            }
+        }
+
+        try
+        {
+            JsrBrowserDebugTool tool = new JsrBrowserDebugTool();
+            tool.setupServer(port);
+            tool.runForever();
+        }
+        catch (Throwable t)
+        {
+            LOG.warn(t);
+        }
+    }
+
+    private Server server;
+
+    private void runForever() throws Exception
+    {
+        server.start();
+        server.dumpStdErr();
+        LOG.info("Server available.");
+        server.join();
+    }
+
+    private void setupServer(int port) throws DeploymentException, ServletException
+    {
+        server = new Server();
+        ServerConnector connector = new ServerConnector(server);
+        connector.setPort(port);
+        server.addConnector(connector);
+
+        ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
+        context.setContextPath("/");
+        ServletHolder holder = context.addServlet(DefaultServlet.class,"/");
+        holder.setInitParameter("resourceBase","src/test/resources/jsr-browser-debug-tool");
+        holder.setInitParameter("dirAllowed","true");
+        server.setHandler(context);
+
+        ServerContainer container = WebSocketServerContainerInitializer.configureContext(context);
+        container.addEndpoint(JsrBrowserSocket.class);
+
+        LOG.info("{} setup on port {}",this.getClass().getName(),port);
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/browser/JsrBrowserSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/browser/JsrBrowserSocket.java
new file mode 100644
index 0000000..ace05d8
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/browser/JsrBrowserSocket.java
@@ -0,0 +1,228 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.browser;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Locale;
+import java.util.Random;
+import javax.websocket.CloseReason;
+import javax.websocket.OnClose;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.RemoteEndpoint.Async;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+ at ServerEndpoint(value = "/", subprotocols = { "tool" }, configurator = JsrBrowserConfigurator.class)
+public class JsrBrowserSocket
+{
+    private static class WriteMany implements Runnable
+    {
+        private Async remote;
+        private int size;
+        private int count;
+
+        public WriteMany(Async remote, int size, int count)
+        {
+            this.remote = remote;
+            this.size = size;
+            this.count = count;
+        }
+
+        @Override
+        public void run()
+        {
+            char letters[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-|{}[]():".toCharArray();
+            int lettersLen = letters.length;
+            char randomText[] = new char[size];
+            Random rand = new Random(42);
+            String msg;
+
+            for (int n = 0; n < count; n++)
+            {
+                // create random text
+                for (int i = 0; i < size; i++)
+                {
+                    randomText[i] = letters[rand.nextInt(lettersLen)];
+                }
+                msg = String.format("ManyThreads [%s]",String.valueOf(randomText));
+                remote.sendText(msg);
+            }
+        }
+    }
+
+    private static final Logger LOG = Log.getLogger(JsrBrowserSocket.class);
+    private Session session;
+    private Async remote;
+    private String userAgent;
+    private String requestedExtensions;
+
+    @OnOpen
+    public void onOpen(Session session)
+    {
+        LOG.info("Open: {}",session);
+        this.session = session;
+        this.remote = session.getAsyncRemote();
+        this.userAgent = (String)session.getUserProperties().get("userAgent");
+        this.requestedExtensions = (String)session.getUserProperties().get("requestedExtensions");
+    }
+
+    @OnClose
+    public void onClose(CloseReason close)
+    {
+        LOG.info("Close: {}: {}",close.getCloseCode(),close.getReasonPhrase());
+        this.session = null;
+    }
+
+    @OnMessage
+    public void onMessage(String message)
+    {
+        LOG.info("onTextMessage({})",message);
+
+        int idx = message.indexOf(':');
+        if (idx > 0)
+        {
+            String key = message.substring(0,idx).toLowerCase(Locale.ENGLISH);
+            String val = message.substring(idx + 1);
+            switch (key)
+            {
+                case "info":
+                {
+                    writeMessage("Using javax.websocket");
+                    if (StringUtil.isBlank(userAgent))
+                    {
+                        writeMessage("Client has no User-Agent");
+                    }
+                    else
+                    {
+                        writeMessage("Client User-Agent: " + this.userAgent);
+                    }
+
+                    if (StringUtil.isBlank(requestedExtensions))
+                    {
+                        writeMessage("Client requested no Sec-WebSocket-Extensions");
+                    }
+                    else
+                    {
+                        writeMessage("Client Sec-WebSocket-Extensions: " + this.requestedExtensions);
+                    }
+                    break;
+                }
+                case "many":
+                {
+                    String parts[] = val.split(",");
+                    int size = Integer.parseInt(parts[0]);
+                    int count = Integer.parseInt(parts[1]);
+
+                    writeManyAsync(size,count);
+                    break;
+                }
+                case "manythreads":
+                {
+                    String parts[] = val.split(",");
+                    int threadCount = Integer.parseInt(parts[0]);
+                    int size = Integer.parseInt(parts[1]);
+                    int count = Integer.parseInt(parts[2]);
+
+                    Thread threads[] = new Thread[threadCount];
+
+                    // Setup threads
+                    for (int n = 0; n < threadCount; n++)
+                    {
+                        threads[n] = new Thread(new WriteMany(remote,size,count),"WriteMany[" + n + "]");
+                    }
+
+                    // Execute threads
+                    for (Thread thread : threads)
+                    {
+                        thread.start();
+                    }
+
+                    // Drop out of this thread
+                    break;
+                }
+                case "time":
+                {
+                    Calendar now = Calendar.getInstance();
+                    DateFormat sdf = SimpleDateFormat.getDateTimeInstance(SimpleDateFormat.FULL,SimpleDateFormat.FULL);
+                    writeMessage("Server time: %s",sdf.format(now.getTime()));
+                    break;
+                }
+                default:
+                {
+                    writeMessage("key[%s] val[%s]",key,val);
+                }
+            }
+        }
+        else
+        {
+            // Not parameterized, echo it back
+            writeMessage(message);
+        }
+    }
+
+    private void writeManyAsync(int size, int count)
+    {
+        char letters[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-|{}[]():".toCharArray();
+        int lettersLen = letters.length;
+        char randomText[] = new char[size];
+        Random rand = new Random(42);
+
+        for (int n = 0; n < count; n++)
+        {
+            // create random text
+            for (int i = 0; i < size; i++)
+            {
+                randomText[i] = letters[rand.nextInt(lettersLen)];
+            }
+            writeMessage("Many [%s]",String.valueOf(randomText));
+        }
+    }
+
+    private void writeMessage(String message)
+    {
+        if (this.session == null)
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("Not connected");
+            return;
+        }
+
+        if (session.isOpen() == false)
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("Not open");
+            return;
+        }
+
+        // Async write
+        remote.sendText(message);
+    }
+
+    private void writeMessage(String format, Object... args)
+    {
+        writeMessage(String.format(format,args));
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/pathmap/PathMappingsTest.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/pathmap/PathMappingsTest.java
new file mode 100644
index 0000000..3196b0c
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/pathmap/PathMappingsTest.java
@@ -0,0 +1,110 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.pathmap;
+
+import static org.hamcrest.Matchers.notNullValue;
+
+import org.eclipse.jetty.websocket.server.pathmap.PathMappings;
+import org.eclipse.jetty.websocket.server.pathmap.PathMappings.MappedResource;
+import org.eclipse.jetty.websocket.server.pathmap.ServletPathSpec;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class PathMappingsTest
+{
+    private void assertMatch(PathMappings<String> pathmap, String path, String expectedValue)
+    {
+        String msg = String.format(".getMatch(\"%s\")",path);
+        MappedResource<String> match = pathmap.getMatch(path);
+        Assert.assertThat(msg,match,notNullValue());
+        String actualMatch = match.getResource();
+        Assert.assertEquals(msg,expectedValue,actualMatch);
+    }
+
+    public void dumpMappings(PathMappings<String> p)
+    {
+        for (MappedResource<String> res : p)
+        {
+            System.out.printf("  %s%n",res);
+        }
+    }
+
+    /**
+     * Test the match order rules with a mixed Servlet and WebSocket path specs
+     * <p>
+     * <ul>
+     * <li>Exact match</li>
+     * <li>Longest prefix match</li>
+     * <li>Longest suffix match</li>
+     * </ul>
+     */
+    @Test
+    public void testMixedMatchOrder()
+    {
+        PathMappings<String> p = new PathMappings<>();
+
+        p.put(new ServletPathSpec("/"),"default");
+        p.put(new ServletPathSpec("/animal/bird/*"),"birds");
+        p.put(new ServletPathSpec("/animal/fish/*"),"fishes");
+        p.put(new ServletPathSpec("/animal/*"),"animals");
+        p.put(new WebSocketPathSpec("/animal/{type}/{name}/chat"),"animalChat");
+        p.put(new WebSocketPathSpec("/animal/{type}/{name}/cam"),"animalCam");
+        p.put(new WebSocketPathSpec("/entrance/cam"),"entranceCam");
+
+        // dumpMappings(p);
+
+        assertMatch(p,"/animal/bird/eagle","birds");
+        assertMatch(p,"/animal/fish/bass/sea","fishes");
+        assertMatch(p,"/animal/peccary/javalina/evolution","animals");
+        assertMatch(p,"/","default");
+        assertMatch(p,"/animal/bird/eagle/chat","animalChat");
+        assertMatch(p,"/animal/bird/penguin/chat","animalChat");
+        assertMatch(p,"/animal/fish/trout/cam","animalCam");
+        assertMatch(p,"/entrance/cam","entranceCam");
+    }
+
+    /**
+     * Test the match order rules imposed by the WebSocket API (JSR-356)
+     * <p>
+     * <ul>
+     * <li>Exact match</li>
+     * <li>Longest prefix match</li>
+     * <li>Longest suffix match</li>
+     * </ul>
+     */
+    @Test
+    public void testWebsocketMatchOrder()
+    {
+        PathMappings<String> p = new PathMappings<>();
+
+        p.put(new WebSocketPathSpec("/a/{var}/c"),"endpointA");
+        p.put(new WebSocketPathSpec("/a/b/c"),"endpointB");
+        p.put(new WebSocketPathSpec("/a/{var1}/{var2}"),"endpointC");
+        p.put(new WebSocketPathSpec("/{var1}/d"),"endpointD");
+        p.put(new WebSocketPathSpec("/b/{var2}"),"endpointE");
+
+        // dumpMappings(p);
+
+        assertMatch(p,"/a/b/c","endpointB");
+        assertMatch(p,"/a/d/c","endpointA");
+        assertMatch(p,"/a/x/y","endpointC");
+
+        assertMatch(p,"/b/d","endpointE");
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/pathmap/WebSocketPathSpecBadSpecsTest.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/pathmap/WebSocketPathSpecBadSpecsTest.java
new file mode 100644
index 0000000..efc6af2
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/pathmap/WebSocketPathSpecBadSpecsTest.java
@@ -0,0 +1,87 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.pathmap;
+
+import static org.junit.Assert.fail;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Tests for bad path specs on ServerEndpoint Path Param / URI Template
+ */
+ at RunWith(Parameterized.class)
+public class WebSocketPathSpecBadSpecsTest
+{
+    private static void bad(List<String[]> data, String str)
+    {
+        data.add(new String[]
+        { str });
+    }
+
+    @Parameters
+    public static Collection<String[]> data()
+    {
+        List<String[]> data = new ArrayList<>();
+        bad(data,"/a/b{var}"); // bad syntax - variable does not encompass whole path segment
+        bad(data,"a/{var}"); // bad syntax - no start slash
+        bad(data,"/a/{var/b}"); // path segment separator in variable name
+        bad(data,"/{var}/*"); // bad syntax - no globs allowed
+        bad(data,"/{var}.do"); // bad syntax - variable does not encompass whole path segment
+        bad(data,"/a/{var*}"); // use of glob character not allowed in variable name
+        bad(data,"/a/{}"); // bad syntax - no variable name
+        // MIGHT BE ALLOWED bad(data,"/a/{---}"); // no alpha in variable name
+        bad(data,"{var}"); // bad syntax - no start slash
+        bad(data,"/a/{my special variable}"); // bad syntax - space in variable name
+        bad(data,"/a/{var}/{var}"); // variable name duplicate
+        // MIGHT BE ALLOWED bad(data,"/a/{var}/{Var}/{vAR}"); // variable name duplicated (diff case)
+        bad(data,"/a/../../../{var}"); // path navigation not allowed
+        bad(data,"/a/./{var}"); // path navigation not allowed
+        bad(data,"/a//{var}"); // bad syntax - double path slash (no path segment)
+        return data;
+    }
+
+    private String pathSpec;
+
+    public WebSocketPathSpecBadSpecsTest(String pathSpec)
+    {
+        this.pathSpec = pathSpec;
+    }
+
+    @Test
+    public void testBadPathSpec()
+    {
+        try
+        {
+            new WebSocketPathSpec(this.pathSpec);
+            fail("Expected IllegalArgumentException for a bad PathParam pathspec on: " + pathSpec);
+        }
+        catch (IllegalArgumentException e)
+        {
+            // expected path
+            System.out.println(e.getMessage());
+        }
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/pathmap/WebSocketPathSpecTest.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/pathmap/WebSocketPathSpecTest.java
new file mode 100644
index 0000000..23b87e7
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/pathmap/WebSocketPathSpecTest.java
@@ -0,0 +1,286 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.pathmap;
+
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import java.util.Map;
+
+import org.eclipse.jetty.websocket.server.pathmap.PathSpec;
+import org.eclipse.jetty.websocket.server.pathmap.PathSpecGroup;
+import org.junit.Test;
+
+/**
+ * Tests for ServerEndpoint Path Param / URI Template Path Specs
+ */
+public class WebSocketPathSpecTest
+{
+    private void assertDetectedVars(WebSocketPathSpec spec, String... expectedVars)
+    {
+        String prefix = String.format("Spec(\"%s\")",spec.getPathSpec());
+        assertEquals(prefix + ".variableCount",expectedVars.length,spec.getVariableCount());
+        assertEquals(prefix + ".variable.length",expectedVars.length,spec.getVariables().length);
+        for (int i = 0; i < expectedVars.length; i++)
+        {
+            assertEquals(String.format("%s.variable[%d]",prefix,i),expectedVars[i],spec.getVariables()[i]);
+        }
+    }
+
+    private void assertMatches(PathSpec spec, String path)
+    {
+        String msg = String.format("Spec(\"%s\").matches(\"%s\")",spec.getPathSpec(),path);
+        assertThat(msg,spec.matches(path),is(true));
+    }
+
+    private void assertNotMatches(PathSpec spec, String path)
+    {
+        String msg = String.format("!Spec(\"%s\").matches(\"%s\")",spec.getPathSpec(),path);
+        assertThat(msg,spec.matches(path),is(false));
+    }
+
+    @Test
+    public void testDefaultPathSpec()
+    {
+        WebSocketPathSpec spec = new WebSocketPathSpec("/");
+        assertEquals("Spec.pathSpec","/",spec.getPathSpec());
+        assertEquals("Spec.pattern","^/$",spec.getPattern().pattern());
+        assertEquals("Spec.pathDepth",1,spec.getPathDepth());
+        assertEquals("Spec.group",PathSpecGroup.EXACT,spec.getGroup());
+
+        assertEquals("Spec.variableCount",0,spec.getVariableCount());
+        assertEquals("Spec.variable.length",0,spec.getVariables().length);
+    }
+
+    @Test
+    public void testExactOnePathSpec()
+    {
+        WebSocketPathSpec spec = new WebSocketPathSpec("/a");
+        assertEquals("Spec.pathSpec","/a",spec.getPathSpec());
+        assertEquals("Spec.pattern","^/a$",spec.getPattern().pattern());
+        assertEquals("Spec.pathDepth",1,spec.getPathDepth());
+        assertEquals("Spec.group",PathSpecGroup.EXACT,spec.getGroup());
+        
+        assertMatches(spec,"/a");
+        assertMatches(spec,"/a?type=other");
+        assertNotMatches(spec,"/a/b");
+        assertNotMatches(spec,"/a/");
+
+        assertEquals("Spec.variableCount",0,spec.getVariableCount());
+        assertEquals("Spec.variable.length",0,spec.getVariables().length);
+    }
+    
+    @Test
+    public void testExactPathSpec_TestWebapp()
+    {
+        WebSocketPathSpec spec = new WebSocketPathSpec("/javax.websocket/");
+        assertEquals("Spec.pathSpec","/javax.websocket/",spec.getPathSpec());
+        assertEquals("Spec.pattern","^/javax\\.websocket/$",spec.getPattern().pattern());
+        assertEquals("Spec.pathDepth",1,spec.getPathDepth());
+        assertEquals("Spec.group",PathSpecGroup.EXACT,spec.getGroup());
+        
+        assertMatches(spec,"/javax.websocket/");
+        assertNotMatches(spec,"/javax.websocket");
+
+        assertEquals("Spec.variableCount",0,spec.getVariableCount());
+        assertEquals("Spec.variable.length",0,spec.getVariables().length);
+    }
+    
+    @Test
+    public void testExactTwoPathSpec()
+    {
+        WebSocketPathSpec spec = new WebSocketPathSpec("/a/b");
+        assertEquals("Spec.pathSpec","/a/b",spec.getPathSpec());
+        assertEquals("Spec.pattern","^/a/b$",spec.getPattern().pattern());
+        assertEquals("Spec.pathDepth",2,spec.getPathDepth());
+        assertEquals("Spec.group",PathSpecGroup.EXACT,spec.getGroup());
+
+        assertEquals("Spec.variableCount",0,spec.getVariableCount());
+        assertEquals("Spec.variable.length",0,spec.getVariables().length);
+
+        assertMatches(spec,"/a/b");
+
+        assertNotMatches(spec,"/a/b/");
+        assertNotMatches(spec,"/a/");
+        assertNotMatches(spec,"/a/bb");
+    }
+
+    @Test
+    public void testMiddleVarPathSpec()
+    {
+        WebSocketPathSpec spec = new WebSocketPathSpec("/a/{var}/c");
+        assertEquals("Spec.pathSpec","/a/{var}/c",spec.getPathSpec());
+        assertEquals("Spec.pattern","^/a/([^/]+)/c$",spec.getPattern().pattern());
+        assertEquals("Spec.pathDepth",3,spec.getPathDepth());
+        assertEquals("Spec.group",PathSpecGroup.MIDDLE_GLOB,spec.getGroup());
+
+        assertDetectedVars(spec,"var");
+
+        assertMatches(spec,"/a/b/c");
+        assertMatches(spec,"/a/zz/c");
+        assertMatches(spec,"/a/hello+world/c");
+        assertNotMatches(spec,"/a/bc");
+        assertNotMatches(spec,"/a/b/");
+        assertNotMatches(spec,"/a/b");
+
+        Map<String, String> mapped = spec.getPathParams("/a/b/c");
+        assertThat("Spec.pathParams",mapped,notNullValue());
+        assertThat("Spec.pathParams.size",mapped.size(),is(1));
+        assertEquals("Spec.pathParams[var]","b",mapped.get("var"));
+    }
+
+    @Test
+    public void testOneVarPathSpec()
+    {
+        WebSocketPathSpec spec = new WebSocketPathSpec("/a/{foo}");
+        assertEquals("Spec.pathSpec","/a/{foo}",spec.getPathSpec());
+        assertEquals("Spec.pattern","^/a/([^/]+)$",spec.getPattern().pattern());
+        assertEquals("Spec.pathDepth",2,spec.getPathDepth());
+        assertEquals("Spec.group",PathSpecGroup.PREFIX_GLOB,spec.getGroup());
+
+        assertDetectedVars(spec,"foo");
+
+        assertMatches(spec,"/a/b");
+        assertNotMatches(spec,"/a/");
+        assertNotMatches(spec,"/a");
+
+        Map<String, String> mapped = spec.getPathParams("/a/b");
+        assertThat("Spec.pathParams",mapped,notNullValue());
+        assertThat("Spec.pathParams.size",mapped.size(),is(1));
+        assertEquals("Spec.pathParams[foo]","b",mapped.get("foo"));
+    }
+
+    @Test
+    public void testOneVarSuffixPathSpec()
+    {
+        WebSocketPathSpec spec = new WebSocketPathSpec("/{var}/b/c");
+        assertEquals("Spec.pathSpec","/{var}/b/c",spec.getPathSpec());
+        assertEquals("Spec.pattern","^/([^/]+)/b/c$",spec.getPattern().pattern());
+        assertEquals("Spec.pathDepth",3,spec.getPathDepth());
+        assertEquals("Spec.group",PathSpecGroup.SUFFIX_GLOB,spec.getGroup());
+
+        assertDetectedVars(spec,"var");
+
+        assertMatches(spec,"/a/b/c");
+        assertMatches(spec,"/az/b/c");
+        assertMatches(spec,"/hello+world/b/c");
+        assertNotMatches(spec,"/a/bc");
+        assertNotMatches(spec,"/a/b/");
+        assertNotMatches(spec,"/a/b");
+
+        Map<String, String> mapped = spec.getPathParams("/a/b/c");
+        assertThat("Spec.pathParams",mapped,notNullValue());
+        assertThat("Spec.pathParams.size",mapped.size(),is(1));
+        assertEquals("Spec.pathParams[var]","a",mapped.get("var"));
+    }
+
+    @Test
+    public void testTwoVarComplexInnerPathSpec()
+    {
+        WebSocketPathSpec spec = new WebSocketPathSpec("/a/{var1}/c/{var2}/e");
+        assertEquals("Spec.pathSpec","/a/{var1}/c/{var2}/e",spec.getPathSpec());
+        assertEquals("Spec.pattern","^/a/([^/]+)/c/([^/]+)/e$",spec.getPattern().pattern());
+        assertEquals("Spec.pathDepth",5,spec.getPathDepth());
+        assertEquals("Spec.group",PathSpecGroup.MIDDLE_GLOB,spec.getGroup());
+
+        assertDetectedVars(spec,"var1","var2");
+
+        assertMatches(spec,"/a/b/c/d/e");
+        assertNotMatches(spec,"/a/bc/d/e");
+        assertNotMatches(spec,"/a/b/d/e");
+        assertNotMatches(spec,"/a/b//d/e");
+
+        Map<String, String> mapped = spec.getPathParams("/a/b/c/d/e");
+        assertThat("Spec.pathParams",mapped,notNullValue());
+        assertThat("Spec.pathParams.size",mapped.size(),is(2));
+        assertEquals("Spec.pathParams[var1]","b",mapped.get("var1"));
+        assertEquals("Spec.pathParams[var2]","d",mapped.get("var2"));
+    }
+
+    @Test
+    public void testTwoVarComplexOuterPathSpec()
+    {
+        WebSocketPathSpec spec = new WebSocketPathSpec("/{var1}/b/{var2}/{var3}");
+        assertEquals("Spec.pathSpec","/{var1}/b/{var2}/{var3}",spec.getPathSpec());
+        assertEquals("Spec.pattern","^/([^/]+)/b/([^/]+)/([^/]+)$",spec.getPattern().pattern());
+        assertEquals("Spec.pathDepth",4,spec.getPathDepth());
+        assertEquals("Spec.group",PathSpecGroup.MIDDLE_GLOB,spec.getGroup());
+
+        assertDetectedVars(spec,"var1","var2","var3");
+
+        assertMatches(spec,"/a/b/c/d");
+        assertNotMatches(spec,"/a/bc/d/e");
+        assertNotMatches(spec,"/a/c/d/e");
+        assertNotMatches(spec,"/a//d/e");
+
+        Map<String, String> mapped = spec.getPathParams("/a/b/c/d");
+        assertThat("Spec.pathParams",mapped,notNullValue());
+        assertThat("Spec.pathParams.size",mapped.size(),is(3));
+        assertEquals("Spec.pathParams[var1]","a",mapped.get("var1"));
+        assertEquals("Spec.pathParams[var2]","c",mapped.get("var2"));
+        assertEquals("Spec.pathParams[var3]","d",mapped.get("var3"));
+    }
+
+    @Test
+    public void testTwoVarPrefixPathSpec()
+    {
+        WebSocketPathSpec spec = new WebSocketPathSpec("/a/{var1}/{var2}");
+        assertEquals("Spec.pathSpec","/a/{var1}/{var2}",spec.getPathSpec());
+        assertEquals("Spec.pattern","^/a/([^/]+)/([^/]+)$",spec.getPattern().pattern());
+        assertEquals("Spec.pathDepth",3,spec.getPathDepth());
+        assertEquals("Spec.group",PathSpecGroup.PREFIX_GLOB,spec.getGroup());
+
+        assertDetectedVars(spec,"var1","var2");
+
+        assertMatches(spec,"/a/b/c");
+        assertNotMatches(spec,"/a/bc");
+        assertNotMatches(spec,"/a/b/");
+        assertNotMatches(spec,"/a/b");
+
+        Map<String, String> mapped = spec.getPathParams("/a/b/c");
+        assertThat("Spec.pathParams",mapped,notNullValue());
+        assertThat("Spec.pathParams.size",mapped.size(),is(2));
+        assertEquals("Spec.pathParams[var1]","b",mapped.get("var1"));
+        assertEquals("Spec.pathParams[var2]","c",mapped.get("var2"));
+    }
+
+    @Test
+    public void testVarOnlyPathSpec()
+    {
+        WebSocketPathSpec spec = new WebSocketPathSpec("/{var1}");
+        assertEquals("Spec.pathSpec","/{var1}",spec.getPathSpec());
+        assertEquals("Spec.pattern","^/([^/]+)$",spec.getPattern().pattern());
+        assertEquals("Spec.pathDepth",1,spec.getPathDepth());
+        assertEquals("Spec.group",PathSpecGroup.PREFIX_GLOB,spec.getGroup());
+
+        assertDetectedVars(spec,"var1");
+
+        assertMatches(spec,"/a");
+        assertNotMatches(spec,"/");
+        assertNotMatches(spec,"/a/b");
+        assertNotMatches(spec,"/a/b/c");
+
+        Map<String, String> mapped = spec.getPathParams("/a");
+        assertThat("Spec.pathParams",mapped,notNullValue());
+        assertThat("Spec.pathParams.size",mapped.size(),is(1));
+        assertEquals("Spec.pathParams[var1]","a",mapped.get("var1"));
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicBinaryMessageByteBufferSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicBinaryMessageByteBufferSocket.java
new file mode 100644
index 0000000..78b21be
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicBinaryMessageByteBufferSocket.java
@@ -0,0 +1,37 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples;
+
+import java.nio.ByteBuffer;
+
+import javax.websocket.OnMessage;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.websocket.jsr356.server.TrackingSocket;
+
+ at ServerEndpoint(value="/basic")
+public class BasicBinaryMessageByteBufferSocket extends TrackingSocket
+{
+    @OnMessage
+    public void onBinary(ByteBuffer data)
+    {
+        addEvent("onBinary(%s)",data);
+        dataLatch.countDown();
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicCloseReasonSessionSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicCloseReasonSessionSocket.java
new file mode 100644
index 0000000..2ce72b8
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicCloseReasonSessionSocket.java
@@ -0,0 +1,37 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples;
+
+import javax.websocket.CloseReason;
+import javax.websocket.OnClose;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.websocket.jsr356.server.TrackingSocket;
+
+ at ServerEndpoint(value="/basic")
+public class BasicCloseReasonSessionSocket extends TrackingSocket
+{
+    @OnClose
+    public void onClose(CloseReason reason, Session session)
+    {
+        addEvent("onClose(%s,%s)",reason,session);
+        closeLatch.countDown();
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicCloseReasonSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicCloseReasonSocket.java
new file mode 100644
index 0000000..42a1195
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicCloseReasonSocket.java
@@ -0,0 +1,36 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples;
+
+import javax.websocket.CloseReason;
+import javax.websocket.OnClose;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.websocket.jsr356.server.TrackingSocket;
+
+ at ServerEndpoint(value="/basic")
+public class BasicCloseReasonSocket extends TrackingSocket
+{
+    @OnClose
+    public void onClose(CloseReason reason)
+    {
+        addEvent("onClose(%s)", reason);
+        closeLatch.countDown();
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicCloseSessionReasonSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicCloseSessionReasonSocket.java
new file mode 100644
index 0000000..5c61335
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicCloseSessionReasonSocket.java
@@ -0,0 +1,37 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples;
+
+import javax.websocket.CloseReason;
+import javax.websocket.OnClose;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.websocket.jsr356.server.TrackingSocket;
+
+ at ServerEndpoint(value="/basic")
+public class BasicCloseSessionReasonSocket extends TrackingSocket
+{
+    @OnClose
+    public void onClose(Session session, CloseReason reason)
+    {
+        addEvent("onClose(%s,%s)",session,reason);
+        closeLatch.countDown();
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicCloseSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicCloseSocket.java
new file mode 100644
index 0000000..28a8594
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicCloseSocket.java
@@ -0,0 +1,35 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples;
+
+import javax.websocket.OnClose;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.websocket.jsr356.server.TrackingSocket;
+
+ at ServerEndpoint(value = "/basic")
+public class BasicCloseSocket extends TrackingSocket
+{
+    @OnClose
+    public void onClose()
+    {
+        addEvent("onClose()");
+        closeLatch.countDown();
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicErrorSessionSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicErrorSessionSocket.java
new file mode 100644
index 0000000..f5c450d
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicErrorSessionSocket.java
@@ -0,0 +1,35 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples;
+
+import javax.websocket.OnError;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.websocket.jsr356.server.TrackingSocket;
+
+ at ServerEndpoint(value="/basic")
+public class BasicErrorSessionSocket extends TrackingSocket
+{
+    @OnError
+    public void onError(Session session)
+    {
+        addEvent("onError(%s)",session);
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicErrorSessionThrowableSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicErrorSessionThrowableSocket.java
new file mode 100644
index 0000000..4ba1e42
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicErrorSessionThrowableSocket.java
@@ -0,0 +1,36 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples;
+
+import javax.websocket.OnError;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.websocket.jsr356.server.TrackingSocket;
+
+ at ServerEndpoint(value="/basic")
+public class BasicErrorSessionThrowableSocket extends TrackingSocket
+{
+    @OnError
+    public void onError(Session session, Throwable t)
+    {
+        addEvent("onError(%s,%s)",session,t);
+        addError(t);
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicErrorSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicErrorSocket.java
new file mode 100644
index 0000000..4c48012
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicErrorSocket.java
@@ -0,0 +1,34 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples;
+
+import javax.websocket.OnError;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.websocket.jsr356.server.TrackingSocket;
+
+ at ServerEndpoint(value="/basic")
+public class BasicErrorSocket extends TrackingSocket
+{
+    @OnError
+    public void onError()
+    {
+        addEvent("onError()");
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicErrorThrowableSessionSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicErrorThrowableSessionSocket.java
new file mode 100644
index 0000000..c656542
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicErrorThrowableSessionSocket.java
@@ -0,0 +1,36 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples;
+
+import javax.websocket.OnError;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.websocket.jsr356.server.TrackingSocket;
+
+ at ServerEndpoint(value="/basic")
+public class BasicErrorThrowableSessionSocket extends TrackingSocket
+{
+    @OnError
+    public void onError(Throwable t, Session session)
+    {
+        addEvent("onError(%s,%s)",t,session);
+        addError(t);
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicErrorThrowableSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicErrorThrowableSocket.java
new file mode 100644
index 0000000..198437d
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicErrorThrowableSocket.java
@@ -0,0 +1,35 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples;
+
+import javax.websocket.OnError;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.websocket.jsr356.server.TrackingSocket;
+
+ at ServerEndpoint(value="/basic")
+public class BasicErrorThrowableSocket extends TrackingSocket
+{
+    @OnError
+    public void onError(Throwable t)
+    {
+        addEvent("onError(%s)",t);
+        addError(t);
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicOpenCloseSessionSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicOpenCloseSessionSocket.java
new file mode 100644
index 0000000..a79a642
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicOpenCloseSessionSocket.java
@@ -0,0 +1,46 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples;
+
+import javax.websocket.CloseReason;
+import javax.websocket.OnClose;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.websocket.jsr356.server.TrackingSocket;
+
+ at ServerEndpoint(value="/basic")
+public class BasicOpenCloseSessionSocket extends TrackingSocket
+{
+    @OnClose
+    public void onClose(CloseReason close, Session session)
+    {
+        addEvent("onClose(%s, %s)",close,session);
+        this.closeReason = close;
+        closeLatch.countDown();
+    }
+
+    @OnOpen
+    public void onOpen(Session session)
+    {
+        addEvent("onOpen(%s)",session);
+        openLatch.countDown();
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicOpenCloseSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicOpenCloseSocket.java
new file mode 100644
index 0000000..5635bef
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicOpenCloseSocket.java
@@ -0,0 +1,41 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples;
+
+import javax.websocket.CloseReason;
+import javax.websocket.OnClose;
+import javax.websocket.OnOpen;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.websocket.jsr356.server.TrackingSocket;
+
+ at ServerEndpoint(value="/basic")
+public class BasicOpenCloseSocket extends TrackingSocket
+{
+    @OnOpen
+    public void onOpen() {
+        openLatch.countDown();
+    }
+    
+    @OnClose
+    public void onClose(CloseReason close) {
+        this.closeReason = close;
+        closeLatch.countDown();
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicOpenSessionSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicOpenSessionSocket.java
new file mode 100644
index 0000000..96aef9d
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicOpenSessionSocket.java
@@ -0,0 +1,35 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples;
+
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.websocket.jsr356.server.TrackingSocket;
+
+ at ServerEndpoint(value="/basic")
+public class BasicOpenSessionSocket extends TrackingSocket
+{
+    @OnOpen
+    public void onOpen(Session session)
+    {
+        openLatch.countDown();
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicOpenSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicOpenSocket.java
new file mode 100644
index 0000000..8a1739d
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicOpenSocket.java
@@ -0,0 +1,34 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples;
+
+import javax.websocket.OnOpen;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.websocket.jsr356.server.TrackingSocket;
+
+ at ServerEndpoint(value="/basic")
+public class BasicOpenSocket extends TrackingSocket
+{
+    @OnOpen
+    public void onOpen()
+    {
+        openLatch.countDown();
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicPongMessageSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicPongMessageSocket.java
new file mode 100644
index 0000000..260251d
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicPongMessageSocket.java
@@ -0,0 +1,36 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples;
+
+import javax.websocket.OnMessage;
+import javax.websocket.PongMessage;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.websocket.jsr356.server.TrackingSocket;
+
+ at ServerEndpoint(value="/basic")
+public class BasicPongMessageSocket extends TrackingSocket
+{
+    @OnMessage
+    public void onPong(PongMessage pong)
+    {
+        addEvent("onPong(%s)",pong);
+        dataLatch.countDown();
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicTextMessageStringSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicTextMessageStringSocket.java
new file mode 100644
index 0000000..de7978e
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/BasicTextMessageStringSocket.java
@@ -0,0 +1,35 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples;
+
+import javax.websocket.OnMessage;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.websocket.jsr356.server.TrackingSocket;
+
+ at ServerEndpoint(value="/basic")
+public class BasicTextMessageStringSocket extends TrackingSocket
+{
+    @OnMessage
+    public void onText(String message)
+    {
+        addEvent("onText(%s)",message);
+        dataLatch.countDown();
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/InvalidCloseIntSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/InvalidCloseIntSocket.java
new file mode 100644
index 0000000..956d3f4
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/InvalidCloseIntSocket.java
@@ -0,0 +1,37 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples;
+
+import javax.websocket.OnClose;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.websocket.jsr356.server.TrackingSocket;
+
+ at ServerEndpoint(value="/invalid")
+public class InvalidCloseIntSocket extends TrackingSocket
+{
+    /**
+     * Invalid Close Method Declaration (parameter type int)
+     */
+    @OnClose
+    public void onClose(int statusCode)
+    {
+        closeLatch.countDown();
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/InvalidErrorErrorSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/InvalidErrorErrorSocket.java
new file mode 100644
index 0000000..1f38f40
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/InvalidErrorErrorSocket.java
@@ -0,0 +1,37 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples;
+
+import javax.websocket.OnError;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.websocket.jsr356.server.TrackingSocket;
+
+ at ServerEndpoint(value="/invalid")
+public class InvalidErrorErrorSocket extends TrackingSocket
+{
+    /**
+     * Invalid Error Method Declaration (parameter type Error)
+     */
+    @OnError
+    public void onError(Error error)
+    {
+        /* no impl */
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/InvalidErrorExceptionSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/InvalidErrorExceptionSocket.java
new file mode 100644
index 0000000..c2ec17a
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/InvalidErrorExceptionSocket.java
@@ -0,0 +1,37 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples;
+
+import javax.websocket.OnError;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.websocket.jsr356.server.TrackingSocket;
+
+ at ServerEndpoint(value="/invalid")
+public class InvalidErrorExceptionSocket extends TrackingSocket
+{
+    /**
+     * Invalid Error Method Declaration (parameter type Exception)
+     */
+    @OnError
+    public void onError(Exception e)
+    {
+        /* no impl */
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/InvalidErrorIntSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/InvalidErrorIntSocket.java
new file mode 100644
index 0000000..f6c2a34
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/InvalidErrorIntSocket.java
@@ -0,0 +1,37 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples;
+
+import javax.websocket.OnError;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.websocket.jsr356.server.TrackingSocket;
+
+ at ServerEndpoint(value="/invalid")
+public class InvalidErrorIntSocket extends TrackingSocket
+{
+    /**
+     * Invalid Error Method Declaration (parameter type int)
+     */
+    @OnError
+    public void onError(int errorCount)
+    {
+        /* no impl */
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/InvalidOpenCloseReasonSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/InvalidOpenCloseReasonSocket.java
new file mode 100644
index 0000000..4181359
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/InvalidOpenCloseReasonSocket.java
@@ -0,0 +1,38 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples;
+
+import javax.websocket.CloseReason;
+import javax.websocket.OnOpen;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.websocket.jsr356.server.TrackingSocket;
+
+ at ServerEndpoint(value="/invalid")
+public class InvalidOpenCloseReasonSocket extends TrackingSocket
+{
+    /**
+     * Invalid Open Method Declaration (parameter type CloseReason)
+     */
+    @OnOpen
+    public void onOpen(CloseReason reason)
+    {
+        openLatch.countDown();
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/InvalidOpenIntSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/InvalidOpenIntSocket.java
new file mode 100644
index 0000000..fa87b65
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/InvalidOpenIntSocket.java
@@ -0,0 +1,37 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples;
+
+import javax.websocket.OnOpen;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.websocket.jsr356.server.TrackingSocket;
+
+ at ServerEndpoint(value="/invalid")
+public class InvalidOpenIntSocket extends TrackingSocket
+{
+    /**
+     * Invalid Open Method Declaration (parameter type int)
+     */
+    @OnOpen
+    public void onOpen(int value)
+    {
+        openLatch.countDown();
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/InvalidOpenSessionIntSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/InvalidOpenSessionIntSocket.java
new file mode 100644
index 0000000..9739b97
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/InvalidOpenSessionIntSocket.java
@@ -0,0 +1,38 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples;
+
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.websocket.jsr356.server.TrackingSocket;
+
+ at ServerEndpoint(value="/invalid")
+public class InvalidOpenSessionIntSocket extends TrackingSocket
+{
+    /**
+     * Invalid Open Method Declaration (parameter of type int)
+     */
+    @OnOpen
+    public void onOpen(Session session, int count)
+    {
+        openLatch.countDown();
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/StatelessTextMessageStringSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/StatelessTextMessageStringSocket.java
new file mode 100644
index 0000000..63880d8
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/StatelessTextMessageStringSocket.java
@@ -0,0 +1,36 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples;
+
+import javax.websocket.OnMessage;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.websocket.jsr356.server.TrackingSocket;
+
+ at ServerEndpoint(value = "/stateless")
+public class StatelessTextMessageStringSocket extends TrackingSocket
+{
+    @OnMessage
+    public void onText(Session session, String message)
+    {
+        addEvent("onText(%s,%s)",session,message);
+        dataLatch.countDown();
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/beans/DateDecoder.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/beans/DateDecoder.java
new file mode 100644
index 0000000..c4e8b42
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/beans/DateDecoder.java
@@ -0,0 +1,62 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.beans;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import javax.websocket.DecodeException;
+import javax.websocket.Decoder;
+import javax.websocket.EndpointConfig;
+
+/**
+ * Decode Date
+ */
+public class DateDecoder implements Decoder.Text<Date>
+{
+    @Override
+    public Date decode(String s) throws DecodeException
+    {
+        try
+        {
+            return new SimpleDateFormat("yyyy.MM.dd").parse(s);
+        }
+        catch (ParseException e)
+        {
+            throw new DecodeException(s,e.getMessage(),e);
+        }
+    }
+
+    @Override
+    public void destroy()
+    {
+    }
+
+    @Override
+    public void init(EndpointConfig config)
+    {
+    }
+
+    @Override
+    public boolean willDecode(String s)
+    {
+        return true;
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/beans/DateEncoder.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/beans/DateEncoder.java
new file mode 100644
index 0000000..b1c7f1a
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/beans/DateEncoder.java
@@ -0,0 +1,48 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.beans;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import javax.websocket.EncodeException;
+import javax.websocket.Encoder;
+import javax.websocket.EndpointConfig;
+
+/**
+ * Encode Date
+ */
+public class DateEncoder implements Encoder.Text<Date>
+{
+    @Override
+    public void destroy()
+    {
+    }
+
+    @Override
+    public String encode(Date object) throws EncodeException
+    {
+        return new SimpleDateFormat("yyyy.MM.dd").format(object);
+    }
+
+    @Override
+    public void init(EndpointConfig config)
+    {
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/beans/DateTextSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/beans/DateTextSocket.java
new file mode 100644
index 0000000..780fd38
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/beans/DateTextSocket.java
@@ -0,0 +1,70 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.beans;
+
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.common.util.StackUtil;
+
+ at ServerEndpoint(value = "/echo/beans/date", decoders = { DateDecoder.class }, encoders = { DateEncoder.class })
+public class DateTextSocket
+{
+    private static final Logger LOG = Log.getLogger(DateTextSocket.class);
+
+    private Session session;
+
+    @OnOpen
+    public void onOpen(Session session)
+    {
+        this.session = session;
+    }
+
+    // The decoder declared in the @ServerEndpoint will be used
+    @OnMessage
+    public void onMessage(Date d) throws IOException
+    {
+        if (d == null)
+        {
+            session.getAsyncRemote().sendText("Error: Date is null");
+        }
+        else
+        {
+            String msg = SimpleDateFormat.getDateInstance(SimpleDateFormat.SHORT).format(d);
+            // The encoder declared in the @ServerEndpoint will be used
+            session.getAsyncRemote().sendText(msg);
+        }
+    }
+
+    @OnError
+    public void onError(Throwable cause) throws IOException
+    {
+        LOG.warn("Error",cause);
+        session.getBasicRemote().sendText("Exception: " + StackUtil.toString(cause));
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/beans/DateTimeDecoder.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/beans/DateTimeDecoder.java
new file mode 100644
index 0000000..58c84af
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/beans/DateTimeDecoder.java
@@ -0,0 +1,62 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.beans;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import javax.websocket.DecodeException;
+import javax.websocket.Decoder;
+import javax.websocket.EndpointConfig;
+
+/**
+ * Decode Date and Time
+ */
+public class DateTimeDecoder implements Decoder.Text<Date>
+{
+    @Override
+    public Date decode(String s) throws DecodeException
+    {
+        try
+        {
+            return new SimpleDateFormat("yyyy.MM.dd G 'at' HH:mm:ss z").parse(s);
+        }
+        catch (ParseException e)
+        {
+            throw new DecodeException(s,e.getMessage(),e);
+        }
+    }
+
+    @Override
+    public void destroy()
+    {
+    }
+
+    @Override
+    public void init(EndpointConfig config)
+    {
+    }
+
+    @Override
+    public boolean willDecode(String s)
+    {
+        return true;
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/beans/DateTimeEncoder.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/beans/DateTimeEncoder.java
new file mode 100644
index 0000000..e5061e0
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/beans/DateTimeEncoder.java
@@ -0,0 +1,48 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.beans;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import javax.websocket.EncodeException;
+import javax.websocket.Encoder;
+import javax.websocket.EndpointConfig;
+
+/**
+ * Encode Date
+ */
+public class DateTimeEncoder implements Encoder.Text<Date>
+{
+    @Override
+    public void destroy()
+    {
+    }
+
+    @Override
+    public String encode(Date object) throws EncodeException
+    {
+        return new SimpleDateFormat("yyyy.MM.dd G 'at' HH:mm:ss z").format(object);
+    }
+
+    @Override
+    public void init(EndpointConfig config)
+    {
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/beans/TimeDecoder.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/beans/TimeDecoder.java
new file mode 100644
index 0000000..fc2b3fd
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/beans/TimeDecoder.java
@@ -0,0 +1,62 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.beans;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import javax.websocket.DecodeException;
+import javax.websocket.Decoder;
+import javax.websocket.EndpointConfig;
+
+/**
+ * Decode Time
+ */
+public class TimeDecoder implements Decoder.Text<Date>
+{
+    @Override
+    public Date decode(String s) throws DecodeException
+    {
+        try
+        {
+            return new SimpleDateFormat("HH:mm:ss z").parse(s);
+        }
+        catch (ParseException e)
+        {
+            throw new DecodeException(s,e.getMessage(),e);
+        }
+    }
+
+    @Override
+    public void destroy()
+    {
+    }
+
+    @Override
+    public void init(EndpointConfig config)
+    {
+    }
+
+    @Override
+    public boolean willDecode(String s)
+    {
+        return true;
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/beans/TimeEncoder.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/beans/TimeEncoder.java
new file mode 100644
index 0000000..f4c6075
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/beans/TimeEncoder.java
@@ -0,0 +1,48 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.beans;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import javax.websocket.EncodeException;
+import javax.websocket.Encoder;
+import javax.websocket.EndpointConfig;
+
+/**
+ * Encode Time
+ */
+public class TimeEncoder implements Encoder.Text<Date>
+{
+    @Override
+    public void destroy()
+    {
+    }
+
+    @Override
+    public String encode(Date object) throws EncodeException
+    {
+        return new SimpleDateFormat("HH:mm:ss z").format(object);
+    }
+
+    @Override
+    public void init(EndpointConfig config)
+    {
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/binary/ByteBufferSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/binary/ByteBufferSocket.java
new file mode 100644
index 0000000..41232fd
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/binary/ByteBufferSocket.java
@@ -0,0 +1,51 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.binary;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.common.util.StackUtil;
+
+ at ServerEndpoint("/echo/binary/bytebuffer")
+public class ByteBufferSocket
+{
+    private static final Logger LOG = Log.getLogger(ByteBufferSocket.class);
+
+    @OnMessage
+    public String onByteBuffer(ByteBuffer bbuf)
+    {
+        return BufferUtil.toUTF8String(bbuf);
+    }
+
+    @OnError
+    public void onError(Session session, Throwable cause) throws IOException
+    {
+        LOG.warn("Error",cause);
+        session.getBasicRemote().sendText("Exception: " + StackUtil.toString(cause));
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/echo/BasicEchoEndpoint.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/echo/BasicEchoEndpoint.java
new file mode 100644
index 0000000..6b498be
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/echo/BasicEchoEndpoint.java
@@ -0,0 +1,46 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.echo;
+
+import javax.websocket.Endpoint;
+import javax.websocket.EndpointConfig;
+import javax.websocket.MessageHandler;
+import javax.websocket.Session;
+
+/**
+ * Example of websocket endpoint based on extending {@link Endpoint}
+ */
+public class BasicEchoEndpoint extends Endpoint implements MessageHandler.Whole<String>
+{
+    private Session session;
+
+    @Override
+    public void onMessage(String msg)
+    {
+        // reply with echo
+        session.getAsyncRemote().sendText(msg);
+    }
+
+    @Override
+    public void onOpen(Session session, EndpointConfig config)
+    {
+        this.session = session;
+        this.session.addMessageHandler(this);
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/echo/BasicEchoEndpointConfigContextListener.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/echo/BasicEchoEndpointConfigContextListener.java
new file mode 100644
index 0000000..fac2bff
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/echo/BasicEchoEndpointConfigContextListener.java
@@ -0,0 +1,57 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.echo;
+
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+import javax.websocket.DeploymentException;
+import javax.websocket.server.ServerContainer;
+import javax.websocket.server.ServerEndpointConfig;
+
+/**
+ * Example of adding a server WebSocket (extending {@link Endpoint}) programmatically via config
+ */
+public class BasicEchoEndpointConfigContextListener implements ServletContextListener
+{
+    @Override
+    public void contextDestroyed(ServletContextEvent sce)
+    {
+        /* do nothing */
+    }
+
+    @Override
+    public void contextInitialized(ServletContextEvent sce)
+    {
+        ServerContainer container = (ServerContainer)sce.getServletContext().getAttribute(ServerContainer.class.getName());
+        if (container==null)
+            throw new IllegalStateException("No Websocket ServerContainer in "+sce.getServletContext());
+        
+        // Build up a configuration with a specific path
+        String path = "/echo";
+        ServerEndpointConfig.Builder builder = ServerEndpointConfig.Builder.create(BasicEchoEndpoint.class,path);
+        try
+        {
+            container.addEndpoint(builder.build());
+        }
+        catch (DeploymentException e)
+        {
+            throw new RuntimeException("Unable to add endpoint via config file",e);
+        }
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/echo/BasicEchoEndpointContextListener.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/echo/BasicEchoEndpointContextListener.java
new file mode 100644
index 0000000..89281e9
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/echo/BasicEchoEndpointContextListener.java
@@ -0,0 +1,57 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.echo;
+
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+import javax.websocket.DeploymentException;
+import javax.websocket.server.ServerContainer;
+import javax.websocket.server.ServerEndpointConfig;
+
+import org.eclipse.jetty.websocket.jsr356.server.samples.pong.PongMessageEndpoint;
+
+/**
+ * Example of adding a server WebSocket (extending {@link Endpoint}) programmatically directly.
+ * <p>
+ * NOTE: this shouldn't work as the endpoint has no path associated with it.
+ */
+public class BasicEchoEndpointContextListener implements ServletContextListener
+{
+    @Override
+    public void contextDestroyed(ServletContextEvent sce)
+    {
+        /* do nothing */
+    }
+
+    @Override
+    public void contextInitialized(ServletContextEvent sce)
+    {
+        ServerContainer container = (ServerContainer)sce.getServletContext().getAttribute(ServerContainer.class.getName());
+        
+        try
+        {
+            container.addEndpoint(ServerEndpointConfig.Builder.create(PongMessageEndpoint.class,"/ping").build());
+            container.addEndpoint(ServerEndpointConfig.Builder.create(PongMessageEndpoint.class,"/pong").build());
+        }
+        catch (DeploymentException e)
+        {
+            throw new RuntimeException("Unable to add endpoint via config file",e);
+        }
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/echo/BasicEchoSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/echo/BasicEchoSocket.java
new file mode 100644
index 0000000..76088e4
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/echo/BasicEchoSocket.java
@@ -0,0 +1,37 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.echo;
+
+import javax.websocket.OnMessage;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+/**
+ * Annotated echo socket
+ */
+ at ServerEndpoint("/echo")
+public class BasicEchoSocket
+{
+    @OnMessage
+    public void echo(Session session, String msg)
+    {
+        // reply with echo
+        session.getAsyncRemote().sendText(msg);
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/echo/BasicEchoSocketConfigContextListener.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/echo/BasicEchoSocketConfigContextListener.java
new file mode 100644
index 0000000..13185bc
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/echo/BasicEchoSocketConfigContextListener.java
@@ -0,0 +1,55 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.echo;
+
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+import javax.websocket.DeploymentException;
+import javax.websocket.server.ServerContainer;
+import javax.websocket.server.ServerEndpointConfig;
+
+/**
+ * Example of adding a server socket (which extends {@link Endpoint}) programmatically via the {@link ServerContainer#addEndpoint(ServerEndpointConfig)}
+ */
+public class BasicEchoSocketConfigContextListener implements ServletContextListener
+{
+    @Override
+    public void contextDestroyed(ServletContextEvent sce)
+    {
+        /* do nothing */
+    }
+
+    @Override
+    public void contextInitialized(ServletContextEvent sce)
+    {
+        ServerContainer container = (ServerContainer)sce.getServletContext().getAttribute(ServerContainer.class.getName());
+        // Build up a configuration with a specific path
+        // Intentionally using alternate path in config (which differs from @ServerEndpoint declaration)
+        String path = "/echo-alt";
+        ServerEndpointConfig.Builder builder = ServerEndpointConfig.Builder.create(BasicEchoSocket.class,path);
+        try
+        {
+            container.addEndpoint(builder.build());
+        }
+        catch (DeploymentException e)
+        {
+            throw new RuntimeException("Unable to add endpoint via config file",e);
+        }
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/echo/BasicEchoSocketContextListener.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/echo/BasicEchoSocketContextListener.java
new file mode 100644
index 0000000..9a83ce5
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/echo/BasicEchoSocketContextListener.java
@@ -0,0 +1,50 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.echo;
+
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+import javax.websocket.DeploymentException;
+import javax.websocket.server.ServerContainer;
+
+/**
+ * Example of adding a server socket (annotated) programmatically directly with no config
+ */
+public class BasicEchoSocketContextListener implements ServletContextListener
+{
+    @Override
+    public void contextDestroyed(ServletContextEvent sce)
+    {
+        /* do nothing */
+    }
+
+    @Override
+    public void contextInitialized(ServletContextEvent sce)
+    {
+        ServerContainer container = (ServerContainer)sce.getServletContext().getAttribute(ServerContainer.class.getName());
+        try
+        {
+            container.addEndpoint(BasicEchoSocket.class);
+        }
+        catch (DeploymentException e)
+        {
+            throw new RuntimeException("Unable to add endpoint directly",e);
+        }
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/echo/ConfiguredEchoSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/echo/ConfiguredEchoSocket.java
new file mode 100644
index 0000000..85d9985
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/echo/ConfiguredEchoSocket.java
@@ -0,0 +1,131 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.echo;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.Objects;
+
+import javax.websocket.EndpointConfig;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+import javax.websocket.server.ServerEndpointConfig;
+
+import org.eclipse.jetty.websocket.jsr356.server.samples.beans.DateDecoder;
+import org.eclipse.jetty.websocket.jsr356.server.samples.beans.TimeEncoder;
+
+/**
+ * Annotated echo socket, using all of the annotation configurations
+ */
+ at ServerEndpoint(
+        value = "/echo",
+        decoders = { DateDecoder.class },
+        encoders = { TimeEncoder.class },
+        subprotocols = { "test", "echo", "chat" },
+        configurator = EchoSocketConfigurator.class)
+public class ConfiguredEchoSocket
+{
+    private Session session;
+    private EndpointConfig config;
+    private ServerEndpointConfig serverConfig;
+
+    @OnOpen
+    public void onOpen(Session session, EndpointConfig config)
+    {
+        this.session = session;
+        this.config = config;
+        if (config instanceof ServerEndpointConfig)
+        {
+            this.serverConfig = (ServerEndpointConfig)config;
+        }
+    }
+
+    @OnMessage(maxMessageSize = 111222)
+    public String echoText(String msg)
+    {
+        switch (msg)
+        {
+            case "text-max":
+                return String.format(Locale.US, "%,d",session.getMaxTextMessageBufferSize());
+            case "binary-max":
+                return String.format(Locale.US, "%,d",session.getMaxBinaryMessageBufferSize());
+            case "decoders":
+                return join(config.getDecoders(),", ");
+            case "encoders":
+                return join(config.getEncoders(),", ");
+            case "subprotocols":
+                if (serverConfig == null)
+                {
+                    return "<not a ServerEndpointConfig>";
+                }
+                else
+                {
+                    List<String> protocols = new ArrayList<>();
+                    protocols.addAll(serverConfig.getSubprotocols());
+                    Collections.sort(protocols);
+                    return join(protocols,", ");
+                }
+            case "configurator":
+                if (serverConfig == null)
+                {
+                    return "<not a ServerEndpointConfig>";
+                }
+                else
+                {
+                    return serverConfig.getConfigurator().getClass().getName();
+                }
+            default:
+                // normal echo
+                return msg;
+        }
+    }
+
+    private String join(Collection<?> coll, String delim)
+    {
+        StringBuilder buf = new StringBuilder();
+        boolean needDelim = false;
+        for (Object obj : coll)
+        {
+            if (needDelim)
+            {
+                buf.append(delim);
+            }
+            buf.append(Objects.toString(obj));
+            needDelim = true;
+        }
+
+        return buf.toString();
+    }
+
+    @OnMessage(maxMessageSize = 333444)
+    public ByteBuffer echoBinary(ByteBuffer buf)
+    {
+        // this one isn't that important, just here to satisfy the @OnMessage
+        // settings that we actually test for via "binary-max" TEXT message
+        ByteBuffer ret = buf.slice();
+        ret.flip();
+        return ret;
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/echo/EchoReturnEndpoint.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/echo/EchoReturnEndpoint.java
new file mode 100644
index 0000000..f0f9efe
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/echo/EchoReturnEndpoint.java
@@ -0,0 +1,64 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.echo;
+
+import java.io.IOException;
+
+import javax.websocket.CloseReason;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.toolchain.test.EventQueue;
+
+ at ServerEndpoint(value = "/echoreturn")
+public class EchoReturnEndpoint
+{
+    private Session session = null;
+    public CloseReason close = null;
+    public EventQueue<String> messageQueue = new EventQueue<>();
+
+    public void onClose(CloseReason close)
+    {
+        this.close = close;
+    }
+
+    @OnMessage
+    public String onMessage(String message)
+    {
+        this.messageQueue.offer(message);
+        // Return the message
+        return message;
+    }
+
+    @OnOpen
+    public void onOpen(Session session)
+    {
+        this.session = session;
+    }
+
+    public void sendText(String text) throws IOException
+    {
+        if (session != null)
+        {
+            session.getBasicRemote().sendText(text);
+        }
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/echo/EchoSocketConfigurator.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/echo/EchoSocketConfigurator.java
new file mode 100644
index 0000000..dd11cac
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/echo/EchoSocketConfigurator.java
@@ -0,0 +1,35 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.echo;
+
+import java.util.Collections;
+
+import javax.websocket.HandshakeResponse;
+import javax.websocket.server.HandshakeRequest;
+import javax.websocket.server.ServerEndpointConfig;
+
+public class EchoSocketConfigurator extends ServerEndpointConfig.Configurator
+{
+    @Override
+    public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response)
+    {
+        response.getHeaders().put("X-Test",Collections.singletonList("Extra"));
+        super.modifyHandshake(sec,request,response);
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/echo/LargeEchoConfiguredSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/echo/LargeEchoConfiguredSocket.java
new file mode 100644
index 0000000..a0802e4
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/echo/LargeEchoConfiguredSocket.java
@@ -0,0 +1,47 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.echo;
+
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+/**
+ * Annotated echo socket
+ */
+ at ServerEndpoint(value = "/echo/large")
+public class LargeEchoConfiguredSocket
+{
+    private Session session;
+
+    @OnOpen
+    public void open(Session session)
+    {
+        this.session = session;
+        this.session.setMaxTextMessageBufferSize(128 * 1024);
+    }
+
+    @OnMessage
+    public void echo(String msg)
+    {
+        // reply with echo
+        session.getAsyncRemote().sendText(msg);
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/echo/LargeEchoContextListener.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/echo/LargeEchoContextListener.java
new file mode 100644
index 0000000..89d3972
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/echo/LargeEchoContextListener.java
@@ -0,0 +1,42 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.echo;
+
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+import javax.websocket.server.ServerContainer;
+
+/**
+ * Configure the Large Text Message Size via the Container
+ */
+public class LargeEchoContextListener implements ServletContextListener
+{
+    @Override
+    public void contextDestroyed(ServletContextEvent sce)
+    {
+        /* do nothing */
+    }
+
+    @Override
+    public void contextInitialized(ServletContextEvent sce)
+    {
+        ServerContainer container = (ServerContainer)sce.getServletContext().getAttribute(ServerContainer.class.getName());
+        container.setDefaultMaxTextMessageBufferSize(128 * 1024);
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/echo/LargeEchoDefaultSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/echo/LargeEchoDefaultSocket.java
new file mode 100644
index 0000000..6364b6c
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/echo/LargeEchoDefaultSocket.java
@@ -0,0 +1,37 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.echo;
+
+import javax.websocket.OnMessage;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+/**
+ * Annotated echo socket (default behavior as defined from {@link WebSocketContainer#setDefaultMaxTextMessageBufferSize(int)})
+ */
+ at ServerEndpoint(value = "/echo/large")
+public class LargeEchoDefaultSocket
+{
+    @OnMessage
+    public void echo(Session session, String msg)
+    {
+        // reply with echo
+        session.getAsyncRemote().sendText(msg);
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/idletimeout/IdleTimeoutContextListener.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/idletimeout/IdleTimeoutContextListener.java
new file mode 100644
index 0000000..dd2194a
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/idletimeout/IdleTimeoutContextListener.java
@@ -0,0 +1,54 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.idletimeout;
+
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+import javax.websocket.DeploymentException;
+import javax.websocket.server.ServerContainer;
+import javax.websocket.server.ServerEndpointConfig;
+
+/**
+ * Example of adding a server WebSocket (extending {@link Endpoint}) programmatically via config
+ */
+public class IdleTimeoutContextListener implements ServletContextListener
+{
+    @Override
+    public void contextDestroyed(ServletContextEvent sce)
+    {
+        /* do nothing */
+    }
+
+    @Override
+    public void contextInitialized(ServletContextEvent sce)
+    {
+        ServerContainer container = (ServerContainer)sce.getServletContext().getAttribute(ServerContainer.class.getName());
+        // Build up a configuration with a specific path
+        String path = "/idle-onopen-endpoint";
+        ServerEndpointConfig.Builder builder = ServerEndpointConfig.Builder.create(OnOpenIdleTimeoutEndpoint.class,path);
+        try
+        {
+            container.addEndpoint(builder.build());
+        }
+        catch (DeploymentException e)
+        {
+            throw new RuntimeException("Unable to add endpoint via config file",e);
+        }
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/idletimeout/OnOpenIdleTimeoutEndpoint.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/idletimeout/OnOpenIdleTimeoutEndpoint.java
new file mode 100644
index 0000000..9c30ce4
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/idletimeout/OnOpenIdleTimeoutEndpoint.java
@@ -0,0 +1,44 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.idletimeout;
+
+import javax.websocket.Endpoint;
+import javax.websocket.EndpointConfig;
+import javax.websocket.MessageHandler;
+import javax.websocket.Session;
+
+public class OnOpenIdleTimeoutEndpoint extends Endpoint implements MessageHandler.Whole<String>
+{
+    private Session session;
+    
+    @Override
+    public void onOpen(Session session, EndpointConfig config)
+    {
+        this.session = session;
+        session.addMessageHandler(this);
+        session.setMaxIdleTimeout(500);
+    }
+
+    @Override
+    public void onMessage(String message)
+    {
+        // echo message back (this is an indication of timeout failure)
+        session.getAsyncRemote().sendText(message);
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/idletimeout/OnOpenIdleTimeoutSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/idletimeout/OnOpenIdleTimeoutSocket.java
new file mode 100644
index 0000000..7a0b526
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/idletimeout/OnOpenIdleTimeoutSocket.java
@@ -0,0 +1,40 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.idletimeout;
+
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+ at ServerEndpoint(value = "/idle-onopen-socket")
+public class OnOpenIdleTimeoutSocket
+{
+    @OnOpen
+    public void onOpen(Session session)
+    {
+        session.setMaxIdleTimeout(500);
+    }
+    
+    @OnMessage
+    public String onMessage(String msg)
+    {
+        return msg;
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/partial/PartialTextSessionSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/partial/PartialTextSessionSocket.java
new file mode 100644
index 0000000..f30b985
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/partial/PartialTextSessionSocket.java
@@ -0,0 +1,55 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.partial;
+
+import java.io.IOException;
+
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.common.util.StackUtil;
+
+ at ServerEndpoint("/echo/partial/textsession")
+public class PartialTextSessionSocket
+{
+    private static final Logger LOG = Log.getLogger(PartialTextSessionSocket.class);
+    private StringBuilder buf = new StringBuilder();
+
+    @OnMessage
+    public void onPartial(String msg, boolean fin, Session session) throws IOException
+    {
+        buf.append("('").append(msg).append("',").append(fin).append(')');
+        if (fin)
+        {
+            session.getBasicRemote().sendText(buf.toString());
+            buf.setLength(0);
+        }
+    }
+
+    @OnError
+    public void onError(Throwable cause, Session session) throws IOException
+    {
+        LOG.warn("Error",cause);
+        session.getBasicRemote().sendText("Exception: " + StackUtil.toString(cause));
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/partial/PartialTextSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/partial/PartialTextSocket.java
new file mode 100644
index 0000000..ba16f5a
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/partial/PartialTextSocket.java
@@ -0,0 +1,63 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.partial;
+
+import java.io.IOException;
+
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.common.util.StackUtil;
+
+ at ServerEndpoint("/echo/partial/text")
+public class PartialTextSocket
+{
+    private static final Logger LOG = Log.getLogger(PartialTextSocket.class);
+    private Session session;
+    private StringBuilder buf = new StringBuilder();
+
+    @OnOpen
+    public void onOpen(Session session)
+    {
+        this.session = session;
+    }
+
+    @OnMessage
+    public void onPartial(String msg, boolean fin) throws IOException
+    {
+        buf.append("('").append(msg).append("',").append(fin).append(')');
+        if (fin)
+        {
+            session.getBasicRemote().sendText(buf.toString());
+            buf.setLength(0);
+        }
+    }
+
+    @OnError
+    public void onError(Throwable cause) throws IOException
+    {
+        LOG.warn("Error",cause);
+        session.getBasicRemote().sendText("Exception: " + StackUtil.toString(cause));
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/partial/PartialTrackingSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/partial/PartialTrackingSocket.java
new file mode 100644
index 0000000..19b4b58
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/partial/PartialTrackingSocket.java
@@ -0,0 +1,36 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.partial;
+
+import java.io.IOException;
+
+import javax.websocket.OnMessage;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.websocket.jsr356.server.TrackingSocket;
+
+ at ServerEndpoint("/echo/partial/tracking")
+public class PartialTrackingSocket extends TrackingSocket
+{
+    @OnMessage
+    public void onPartial(String msg, boolean fin) throws IOException
+    {
+        addEvent("onPartial(\"%s\",%b)",msg,fin);
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/pong/PongContextListener.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/pong/PongContextListener.java
new file mode 100644
index 0000000..0a93a13
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/pong/PongContextListener.java
@@ -0,0 +1,66 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.pong;
+
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+import javax.websocket.DeploymentException;
+import javax.websocket.HandshakeResponse;
+import javax.websocket.server.HandshakeRequest;
+import javax.websocket.server.ServerContainer;
+import javax.websocket.server.ServerEndpointConfig;
+import javax.websocket.server.ServerEndpointConfig.Configurator;
+
+public class PongContextListener implements ServletContextListener
+{
+    public static class Config extends ServerEndpointConfig.Configurator
+    {
+        @Override
+        public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response)
+        {
+            sec.getUserProperties().put("path",sec.getPath());
+            super.modifyHandshake(sec,request,response);
+        }
+    }
+    
+    @Override
+    public void contextDestroyed(ServletContextEvent sce)
+    {
+        /* do nothing */
+    }
+
+    @Override
+    public void contextInitialized(ServletContextEvent sce)
+    {
+        ServerContainer container = (ServerContainer)sce.getServletContext().getAttribute(ServerContainer.class.getName());
+        try
+        {
+            Configurator config = new Config();
+            
+            container.addEndpoint(ServerEndpointConfig.Builder.create(PongMessageEndpoint.class,"/ping").configurator(config).build());
+            container.addEndpoint(ServerEndpointConfig.Builder.create(PongMessageEndpoint.class,"/pong").configurator(config).build());
+            container.addEndpoint(ServerEndpointConfig.Builder.create(PongSocket.class,"/ping-socket").build());
+            container.addEndpoint(ServerEndpointConfig.Builder.create(PongSocket.class,"/pong-socket").build());
+        }
+        catch (DeploymentException e)
+        {
+            throw new RuntimeException("Unable to add endpoint directly",e);
+        }
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/pong/PongMessageEndpoint.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/pong/PongMessageEndpoint.java
new file mode 100644
index 0000000..49a58e6
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/pong/PongMessageEndpoint.java
@@ -0,0 +1,51 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.pong;
+
+import java.nio.charset.StandardCharsets;
+
+import javax.websocket.Endpoint;
+import javax.websocket.EndpointConfig;
+import javax.websocket.MessageHandler;
+import javax.websocket.PongMessage;
+import javax.websocket.Session;
+
+import org.eclipse.jetty.util.BufferUtil;
+
+public class PongMessageEndpoint extends Endpoint implements MessageHandler.Whole<PongMessage>
+{
+    private String path = "?";
+    private Session session;
+
+    @Override
+    public void onOpen(Session session, EndpointConfig config)
+    {
+        this.session = session;
+        this.session.addMessageHandler(this);
+        this.path = (String)config.getUserProperties().get("path");
+    }
+
+    @Override
+    public void onMessage(PongMessage pong)
+    {
+        byte buf[] = BufferUtil.toArray(pong.getApplicationData());
+        String message = new String(buf,StandardCharsets.UTF_8);
+        this.session.getAsyncRemote().sendText("PongMessage[" + path + "]:" + message);
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/pong/PongSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/pong/PongSocket.java
new file mode 100644
index 0000000..013908f
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/pong/PongSocket.java
@@ -0,0 +1,56 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.pong;
+
+import java.nio.charset.StandardCharsets;
+import javax.websocket.EndpointConfig;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.PongMessage;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+ at ServerEndpoint(value="/pong-socket", configurator=PongContextListener.Config.class)
+public class PongSocket
+{
+    private static final Logger LOG = Log.getLogger(PongSocket.class);
+    private String path = "?";
+    private Session session;
+
+    @OnOpen
+    public void onOpen(Session session, EndpointConfig config)
+    {
+        this.session = session;
+        this.path = (String)config.getUserProperties().get("path");
+    }
+    
+    @OnMessage
+    public void onPong(PongMessage pong)
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("onPong(): PongMessage.appData={}",BufferUtil.toDetailString(pong.getApplicationData()));
+        byte buf[] = BufferUtil.toArray(pong.getApplicationData());
+        String message = new String(buf,StandardCharsets.UTF_8);
+        this.session.getAsyncRemote().sendText("@OnMessage(PongMessage)[" + path + "]:" + message);
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/BooleanObjectTextParamSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/BooleanObjectTextParamSocket.java
new file mode 100644
index 0000000..3a9bd4f
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/BooleanObjectTextParamSocket.java
@@ -0,0 +1,67 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.primitives;
+
+import java.io.IOException;
+
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.PathParam;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.common.util.StackUtil;
+
+ at ServerEndpoint("/echo/primitives/booleanobject/params/{a}")
+public class BooleanObjectTextParamSocket
+{
+    private static final Logger LOG = Log.getLogger(BooleanObjectTextParamSocket.class);
+
+    private Session session;
+
+    @OnOpen
+    public void onOpen(Session session)
+    {
+        this.session = session;
+    }
+
+    @OnMessage
+    public void onMessage(Boolean b, @PathParam("a") Boolean param) throws IOException
+    {
+        if (b == null)
+        {
+            session.getAsyncRemote().sendText("Error: Boolean is null");
+        }
+        else
+        {
+            String msg = String.format("%b|%b", b, param);
+            session.getAsyncRemote().sendText(msg);
+        }
+    }
+
+    @OnError
+    public void onError(Throwable cause) throws IOException
+    {
+        LOG.warn("Error",cause);
+        session.getBasicRemote().sendText("Exception: " + StackUtil.toString(cause));
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/BooleanObjectTextSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/BooleanObjectTextSocket.java
new file mode 100644
index 0000000..898f118
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/BooleanObjectTextSocket.java
@@ -0,0 +1,66 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.primitives;
+
+import java.io.IOException;
+
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.common.util.StackUtil;
+
+ at ServerEndpoint("/echo/primitives/booleanobject")
+public class BooleanObjectTextSocket
+{
+    private static final Logger LOG = Log.getLogger(BooleanObjectTextSocket.class);
+
+    private Session session;
+
+    @OnOpen
+    public void onOpen(Session session)
+    {
+        this.session = session;
+    }
+
+    @OnMessage
+    public void onMessage(Boolean b) throws IOException
+    {
+        if (b == null)
+        {
+            session.getAsyncRemote().sendText("Error: Boolean is null");
+        }
+        else
+        {
+            String msg = b.toString();
+            session.getAsyncRemote().sendText(msg);
+        }
+    }
+
+    @OnError
+    public void onError(Throwable cause) throws IOException
+    {
+        LOG.warn("Error",cause);
+        session.getBasicRemote().sendText("Exception: " + StackUtil.toString(cause));
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/BooleanTextParamSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/BooleanTextParamSocket.java
new file mode 100644
index 0000000..902bdae
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/BooleanTextParamSocket.java
@@ -0,0 +1,60 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.primitives;
+
+import java.io.IOException;
+
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.PathParam;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.common.util.StackUtil;
+
+ at ServerEndpoint("/echo/primitives/boolean/params/{a}")
+public class BooleanTextParamSocket
+{
+    private static final Logger LOG = Log.getLogger(BooleanTextParamSocket.class);
+    
+    private Session session;
+
+    @OnOpen
+    public void onOpen(Session session)
+    {
+        this.session = session;
+    }
+
+    @OnMessage
+    public void onMessage(boolean b, @PathParam("a") boolean param) throws IOException
+    {
+        String msg = String.format("%b|%b", b, param);
+        session.getAsyncRemote().sendText(msg);
+    }
+
+    @OnError
+    public void onError(Throwable cause) throws IOException
+    {
+        LOG.warn("Error",cause);
+        session.getBasicRemote().sendText("Exception: " + StackUtil.toString(cause));
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/BooleanTextSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/BooleanTextSocket.java
new file mode 100644
index 0000000..a05eba1
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/BooleanTextSocket.java
@@ -0,0 +1,59 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.primitives;
+
+import java.io.IOException;
+
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.common.util.StackUtil;
+
+ at ServerEndpoint("/echo/primitives/boolean")
+public class BooleanTextSocket
+{
+    private static final Logger LOG = Log.getLogger(BooleanTextSocket.class);
+    
+    private Session session;
+
+    @OnOpen
+    public void onOpen(Session session)
+    {
+        this.session = session;
+    }
+
+    @OnMessage
+    public void onMessage(boolean b) throws IOException
+    {
+        String msg = Boolean.toString(b);
+        session.getAsyncRemote().sendText(msg);
+    }
+
+    @OnError
+    public void onError(Throwable cause) throws IOException
+    {
+        LOG.warn("Error",cause);
+        session.getBasicRemote().sendText("Exception: " + StackUtil.toString(cause));
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/ByteObjectTextSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/ByteObjectTextSocket.java
new file mode 100644
index 0000000..5e7edc2
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/ByteObjectTextSocket.java
@@ -0,0 +1,66 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.primitives;
+
+import java.io.IOException;
+
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.common.util.StackUtil;
+
+ at ServerEndpoint("/echo/primitives/byteobject")
+public class ByteObjectTextSocket
+{
+    private static final Logger LOG = Log.getLogger(ByteObjectTextSocket.class);
+
+    private Session session;
+
+    @OnOpen
+    public void onOpen(Session session)
+    {
+        this.session = session;
+    }
+
+    @OnMessage
+    public void onMessage(Byte b) throws IOException
+    {
+        if (b == null)
+        {
+            session.getAsyncRemote().sendText("Error: Byte is null");
+        }
+        else
+        {
+            String msg = String.format("0x%02X",b);
+            session.getAsyncRemote().sendText(msg);
+        }
+    }
+
+    @OnError
+    public void onError(Throwable cause) throws IOException
+    {
+        LOG.warn("Error",cause);
+        session.getBasicRemote().sendText("Exception: " + StackUtil.toString(cause));
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/ByteTextSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/ByteTextSocket.java
new file mode 100644
index 0000000..26f4b30
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/ByteTextSocket.java
@@ -0,0 +1,59 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.primitives;
+
+import java.io.IOException;
+
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.common.util.StackUtil;
+
+ at ServerEndpoint("/echo/primitives/byte")
+public class ByteTextSocket
+{
+    private static final Logger LOG = Log.getLogger(ByteTextSocket.class);
+    
+    private Session session;
+
+    @OnOpen
+    public void onOpen(Session session)
+    {
+        this.session = session;
+    }
+
+    @OnMessage
+    public void onMessage(byte b) throws IOException
+    {
+        String msg = String.format("0x%02X",b);
+        session.getAsyncRemote().sendText(msg);
+    }
+
+    @OnError
+    public void onError(Throwable cause) throws IOException
+    {
+        LOG.warn("Error",cause);
+        session.getBasicRemote().sendText("Exception: " + StackUtil.toString(cause));
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/CharTextSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/CharTextSocket.java
new file mode 100644
index 0000000..2b85f66
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/CharTextSocket.java
@@ -0,0 +1,59 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.primitives;
+
+import java.io.IOException;
+
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.common.util.StackUtil;
+
+ at ServerEndpoint("/echo/primitives/char")
+public class CharTextSocket
+{
+    private static final Logger LOG = Log.getLogger(CharTextSocket.class);
+    
+    private Session session;
+
+    @OnOpen
+    public void onOpen(Session session)
+    {
+        this.session = session;
+    }
+
+    @OnMessage
+    public void onMessage(char c) throws IOException
+    {
+        String msg = Character.toString(c);
+        session.getAsyncRemote().sendText(msg);
+    }
+
+    @OnError
+    public void onError(Throwable cause) throws IOException
+    {
+        LOG.warn("Error",cause);
+        session.getBasicRemote().sendText("Exception: " + StackUtil.toString(cause));
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/CharacterObjectTextSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/CharacterObjectTextSocket.java
new file mode 100644
index 0000000..f5999f7
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/CharacterObjectTextSocket.java
@@ -0,0 +1,66 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.primitives;
+
+import java.io.IOException;
+
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.common.util.StackUtil;
+
+ at ServerEndpoint("/echo/primitives/characterobject")
+public class CharacterObjectTextSocket
+{
+    private static final Logger LOG = Log.getLogger(CharacterObjectTextSocket.class);
+
+    private Session session;
+
+    @OnOpen
+    public void onOpen(Session session)
+    {
+        this.session = session;
+    }
+
+    @OnMessage
+    public void onMessage(Character c) throws IOException
+    {
+        if (c == null)
+        {
+            session.getAsyncRemote().sendText("Error: Character is null");
+        }
+        else
+        {
+            String msg = c.toString();
+            session.getAsyncRemote().sendText(msg);
+        }
+    }
+
+    @OnError
+    public void onError(Throwable cause) throws IOException
+    {
+        LOG.warn("Error",cause);
+        session.getBasicRemote().sendText("Exception: " + StackUtil.toString(cause));
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/DoubleObjectTextSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/DoubleObjectTextSocket.java
new file mode 100644
index 0000000..eade593
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/DoubleObjectTextSocket.java
@@ -0,0 +1,67 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.primitives;
+
+import java.io.IOException;
+import java.util.Locale;
+
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.common.util.StackUtil;
+
+ at ServerEndpoint("/echo/primitives/doubleobject")
+public class DoubleObjectTextSocket
+{
+    private static final Logger LOG = Log.getLogger(DoubleObjectTextSocket.class);
+
+    private Session session;
+
+    @OnOpen
+    public void onOpen(Session session)
+    {
+        this.session = session;
+    }
+
+    @OnMessage
+    public void onMessage(Double d) throws IOException
+    {
+        if (d == null)
+        {
+            session.getAsyncRemote().sendText("Error: Double is null");
+        }
+        else
+        {
+            String msg = String.format(Locale.US, "%.4f",d);
+            session.getAsyncRemote().sendText(msg);
+        }
+    }
+
+    @OnError
+    public void onError(Throwable cause) throws IOException
+    {
+        LOG.warn("Error",cause);
+        session.getBasicRemote().sendText("Exception: " + StackUtil.toString(cause));
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/DoubleTextSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/DoubleTextSocket.java
new file mode 100644
index 0000000..71e5e4f
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/DoubleTextSocket.java
@@ -0,0 +1,60 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.primitives;
+
+import java.io.IOException;
+import java.util.Locale;
+
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.common.util.StackUtil;
+
+ at ServerEndpoint("/echo/primitives/double")
+public class DoubleTextSocket
+{
+    private static final Logger LOG = Log.getLogger(DoubleTextSocket.class);
+    
+    private Session session;
+
+    @OnOpen
+    public void onOpen(Session session)
+    {
+        this.session = session;
+    }
+
+    @OnMessage
+    public void onMessage(double d) throws IOException
+    {
+        String msg = String.format(Locale.US, "%.4f",d);
+        session.getAsyncRemote().sendText(msg);
+    }
+
+    @OnError
+    public void onError(Throwable cause) throws IOException
+    {
+        LOG.warn("Error",cause);
+        session.getBasicRemote().sendText("Exception: " + StackUtil.toString(cause));
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/FloatObjectTextSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/FloatObjectTextSocket.java
new file mode 100644
index 0000000..db82622
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/FloatObjectTextSocket.java
@@ -0,0 +1,67 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.primitives;
+
+import java.io.IOException;
+import java.util.Locale;
+
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.common.util.StackUtil;
+
+ at ServerEndpoint("/echo/primitives/floatobject")
+public class FloatObjectTextSocket
+{
+    private static final Logger LOG = Log.getLogger(FloatObjectTextSocket.class);
+
+    private Session session;
+
+    @OnOpen
+    public void onOpen(Session session)
+    {
+        this.session = session;
+    }
+
+    @OnMessage
+    public void onMessage(Float f) throws IOException
+    {
+        if (f == null)
+        {
+            session.getAsyncRemote().sendText("Error: Float is null");
+        }
+        else
+        {
+            String msg = String.format(Locale.US, "%.4f",f);
+            session.getAsyncRemote().sendText(msg);
+        }
+    }
+
+    @OnError
+    public void onError(Throwable cause) throws IOException
+    {
+        LOG.warn("Error",cause);
+        session.getBasicRemote().sendText("Exception: " + StackUtil.toString(cause));
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/FloatTextSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/FloatTextSocket.java
new file mode 100644
index 0000000..0b5423b
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/FloatTextSocket.java
@@ -0,0 +1,60 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.primitives;
+
+import java.io.IOException;
+import java.util.Locale;
+
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.common.util.StackUtil;
+
+ at ServerEndpoint("/echo/primitives/float")
+public class FloatTextSocket
+{
+    private static final Logger LOG = Log.getLogger(FloatTextSocket.class);
+    
+    private Session session;
+
+    @OnOpen
+    public void onOpen(Session session)
+    {
+        this.session = session;
+    }
+
+    @OnMessage
+    public void onMessage(float f) throws IOException
+    {
+        String msg = String.format(Locale.US, "%.4f",f);
+        session.getAsyncRemote().sendText(msg);
+    }
+
+    @OnError
+    public void onError(Throwable cause) throws IOException
+    {
+        LOG.warn("Error",cause);
+        session.getBasicRemote().sendText("Exception: " + StackUtil.toString(cause));
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/IntParamTextSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/IntParamTextSocket.java
new file mode 100644
index 0000000..9f5ae6a
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/IntParamTextSocket.java
@@ -0,0 +1,60 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.primitives;
+
+import java.io.IOException;
+
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.PathParam;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.common.util.StackUtil;
+
+ at ServerEndpoint("/echo/primitives/integer/params/{a}")
+public class IntParamTextSocket
+{
+    private static final Logger LOG = Log.getLogger(IntParamTextSocket.class);
+
+    private Session session;
+
+    @OnOpen
+    public void onOpen(Session session)
+    {
+        this.session = session;
+    }
+
+    @OnMessage
+    public void onMessage(int i, @PathParam("a") int param) throws IOException
+    {
+        String msg = String.format("%d|%d",i,param);
+        session.getAsyncRemote().sendText(msg);
+    }
+
+    @OnError
+    public void onError(Throwable cause) throws IOException
+    {
+        LOG.warn("Error",cause);
+        session.getBasicRemote().sendText("Exception: " + StackUtil.toString(cause));
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/IntTextSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/IntTextSocket.java
new file mode 100644
index 0000000..dc4c672
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/IntTextSocket.java
@@ -0,0 +1,59 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.primitives;
+
+import java.io.IOException;
+
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.common.util.StackUtil;
+
+ at ServerEndpoint("/echo/primitives/integer")
+public class IntTextSocket
+{
+    private static final Logger LOG = Log.getLogger(IntTextSocket.class);
+    
+    private Session session;
+
+    @OnOpen
+    public void onOpen(Session session)
+    {
+        this.session = session;
+    }
+
+    @OnMessage
+    public void onMessage(int i) throws IOException
+    {
+        String msg = Integer.toString(i);
+        session.getAsyncRemote().sendText(msg);
+    }
+
+    @OnError
+    public void onError(Throwable cause) throws IOException
+    {
+        LOG.warn("Error",cause);
+        session.getBasicRemote().sendText("Exception: " + StackUtil.toString(cause));
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/IntegerObjectParamTextSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/IntegerObjectParamTextSocket.java
new file mode 100644
index 0000000..02e6e19
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/IntegerObjectParamTextSocket.java
@@ -0,0 +1,67 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.primitives;
+
+import java.io.IOException;
+
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.PathParam;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.common.util.StackUtil;
+
+ at ServerEndpoint("/echo/primitives/integerobject/params/{a}")
+public class IntegerObjectParamTextSocket
+{
+    private static final Logger LOG = Log.getLogger(IntegerObjectParamTextSocket.class);
+
+    private Session session;
+
+    @OnOpen
+    public void onOpen(Session session)
+    {
+        this.session = session;
+    }
+
+    @OnMessage
+    public void onMessage(Integer i, @PathParam("a") int param) throws IOException
+    {
+        if (i == null)
+        {
+            session.getAsyncRemote().sendText("Error: Integer is null");
+        }
+        else
+        {
+            String msg = String.format("%d|%d",i,param);
+            session.getAsyncRemote().sendText(msg);
+        }
+    }
+
+    @OnError
+    public void onError(Throwable cause) throws IOException
+    {
+        LOG.warn("Error",cause);
+        session.getBasicRemote().sendText("Exception: " + StackUtil.toString(cause));
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/IntegerObjectTextSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/IntegerObjectTextSocket.java
new file mode 100644
index 0000000..53970e8
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/IntegerObjectTextSocket.java
@@ -0,0 +1,66 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.primitives;
+
+import java.io.IOException;
+
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.common.util.StackUtil;
+
+ at ServerEndpoint("/echo/primitives/integerobject")
+public class IntegerObjectTextSocket
+{
+    private static final Logger LOG = Log.getLogger(IntegerObjectTextSocket.class);
+
+    private Session session;
+
+    @OnOpen
+    public void onOpen(Session session)
+    {
+        this.session = session;
+    }
+
+    @OnMessage
+    public void onMessage(Integer i) throws IOException
+    {
+        if (i == null)
+        {
+            session.getAsyncRemote().sendText("Error: Integer is null");
+        }
+        else
+        {
+            String msg = i.toString();
+            session.getAsyncRemote().sendText(msg);
+        }
+    }
+
+    @OnError
+    public void onError(Throwable cause) throws IOException
+    {
+        LOG.warn("Error",cause);
+        session.getBasicRemote().sendText("Exception: " + StackUtil.toString(cause));
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/LongObjectTextSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/LongObjectTextSocket.java
new file mode 100644
index 0000000..c73e38b
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/LongObjectTextSocket.java
@@ -0,0 +1,66 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.primitives;
+
+import java.io.IOException;
+
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.common.util.StackUtil;
+
+ at ServerEndpoint("/echo/primitives/longobject")
+public class LongObjectTextSocket
+{
+    private static final Logger LOG = Log.getLogger(LongObjectTextSocket.class);
+
+    private Session session;
+
+    @OnOpen
+    public void onOpen(Session session)
+    {
+        this.session = session;
+    }
+
+    @OnMessage
+    public void onMessage(Long l) throws IOException
+    {
+        if (l == null)
+        {
+            session.getAsyncRemote().sendText("Error: Long is null");
+        }
+        else
+        {
+            String msg = l.toString();
+            session.getAsyncRemote().sendText(msg);
+        }
+    }
+
+    @OnError
+    public void onError(Throwable cause) throws IOException
+    {
+        LOG.warn("Error",cause);
+        session.getBasicRemote().sendText("Exception: " + StackUtil.toString(cause));
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/LongTextSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/LongTextSocket.java
new file mode 100644
index 0000000..fc2e949
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/LongTextSocket.java
@@ -0,0 +1,59 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.primitives;
+
+import java.io.IOException;
+
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.common.util.StackUtil;
+
+ at ServerEndpoint("/echo/primitives/long")
+public class LongTextSocket
+{
+    private static final Logger LOG = Log.getLogger(LongTextSocket.class);
+    
+    private Session session;
+
+    @OnOpen
+    public void onOpen(Session session)
+    {
+        this.session = session;
+    }
+
+    @OnMessage
+    public void onMessage(long l) throws IOException
+    {
+        String msg = Long.toString(l);
+        session.getAsyncRemote().sendText(msg);
+    }
+
+    @OnError
+    public void onError(Throwable cause) throws IOException
+    {
+        LOG.warn("Error",cause);
+        session.getBasicRemote().sendText("Exception: " + StackUtil.toString(cause));
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/ShortObjectTextSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/ShortObjectTextSocket.java
new file mode 100644
index 0000000..43e85e4
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/ShortObjectTextSocket.java
@@ -0,0 +1,66 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.primitives;
+
+import java.io.IOException;
+
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.common.util.StackUtil;
+
+ at ServerEndpoint("/echo/primitives/shortobject")
+public class ShortObjectTextSocket
+{
+    private static final Logger LOG = Log.getLogger(ShortObjectTextSocket.class);
+
+    private Session session;
+
+    @OnOpen
+    public void onOpen(Session session)
+    {
+        this.session = session;
+    }
+
+    @OnMessage
+    public void onMessage(Short s) throws IOException
+    {
+        if (s == null)
+        {
+            session.getAsyncRemote().sendText("Error: Short is null");
+        }
+        else
+        {
+            String msg = s.toString();
+            session.getAsyncRemote().sendText(msg);
+        }
+    }
+
+    @OnError
+    public void onError(Throwable cause) throws IOException
+    {
+        LOG.warn("Error",cause);
+        session.getBasicRemote().sendText("Exception: " + StackUtil.toString(cause));
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/ShortTextSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/ShortTextSocket.java
new file mode 100644
index 0000000..4710cc5
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/primitives/ShortTextSocket.java
@@ -0,0 +1,59 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.primitives;
+
+import java.io.IOException;
+
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.common.util.StackUtil;
+
+ at ServerEndpoint("/echo/primitives/short")
+public class ShortTextSocket
+{
+    private static final Logger LOG = Log.getLogger(ShortTextSocket.class);
+    
+    private Session session;
+
+    @OnOpen
+    public void onOpen(Session session)
+    {
+        this.session = session;
+    }
+
+    @OnMessage
+    public void onMessage(short s) throws IOException
+    {
+        String msg = Short.toString(s);
+        session.getAsyncRemote().sendText(msg);
+    }
+
+    @OnError
+    public void onError(Throwable cause) throws IOException
+    {
+        LOG.warn("Error",cause);
+        session.getBasicRemote().sendText("Exception: " + StackUtil.toString(cause));
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/streaming/InputStreamSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/streaming/InputStreamSocket.java
new file mode 100644
index 0000000..b6c177e
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/streaming/InputStreamSocket.java
@@ -0,0 +1,52 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.streaming;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.common.util.StackUtil;
+
+ at ServerEndpoint("/echo/streaming/inputstream")
+public class InputStreamSocket
+{
+    private static final Logger LOG = Log.getLogger(InputStreamSocket.class);
+
+    @OnMessage
+    public String onInputStream(InputStream stream) throws IOException
+    {
+        return IO.toString(stream, StringUtil.__UTF8);
+    }
+
+    @OnError
+    public void onError(Session session, Throwable cause) throws IOException
+    {
+        LOG.warn("Error",cause);
+        session.getBasicRemote().sendText("Exception: " + StackUtil.toString(cause));
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/streaming/ReaderParamSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/streaming/ReaderParamSocket.java
new file mode 100644
index 0000000..fbcf54a
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/streaming/ReaderParamSocket.java
@@ -0,0 +1,65 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.streaming;
+
+import java.io.IOException;
+import java.io.Reader;
+
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.PathParam;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.common.util.StackUtil;
+
+ at ServerEndpoint("/echo/streaming/readerparam/{param}")
+public class ReaderParamSocket
+{
+    private static final Logger LOG = Log.getLogger(ReaderParamSocket.class);
+
+    private Session session;
+
+    @OnOpen
+    public void onOpen(Session session)
+    {
+        this.session = session;
+    }
+
+    @OnMessage
+    public void onReader(Reader reader, @PathParam("param") String param) throws IOException
+    {
+        StringBuilder msg = new StringBuilder();
+        msg.append(IO.toString(reader));
+        msg.append('|');
+        msg.append(param);
+        session.getAsyncRemote().sendText(msg.toString());
+    }
+
+    @OnError
+    public void onError(Throwable cause) throws IOException
+    {
+        LOG.warn("Error",cause);
+        session.getBasicRemote().sendText("Exception: " + StackUtil.toString(cause));
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/streaming/ReaderSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/streaming/ReaderSocket.java
new file mode 100644
index 0000000..5df67ec
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/streaming/ReaderSocket.java
@@ -0,0 +1,51 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.streaming;
+
+import java.io.IOException;
+import java.io.Reader;
+
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.common.util.StackUtil;
+
+ at ServerEndpoint("/echo/streaming/reader")
+public class ReaderSocket
+{
+    private static final Logger LOG = Log.getLogger(ReaderSocket.class);
+
+    @OnMessage
+    public String onReader(Reader reader) throws IOException
+    {
+        return IO.toString(reader);
+    }
+
+    @OnError
+    public void onError(Session session, Throwable cause) throws IOException
+    {
+        LOG.warn("Error",cause);
+        session.getBasicRemote().sendText("Exception: " + StackUtil.toString(cause));
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/streaming/StringReturnReaderParamSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/streaming/StringReturnReaderParamSocket.java
new file mode 100644
index 0000000..6860356
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/streaming/StringReturnReaderParamSocket.java
@@ -0,0 +1,56 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356.server.samples.streaming;
+
+import java.io.IOException;
+import java.io.Reader;
+
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.Session;
+import javax.websocket.server.PathParam;
+import javax.websocket.server.ServerEndpoint;
+
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.common.util.StackUtil;
+
+ at ServerEndpoint("/echo/streaming/readerparam2/{param}")
+public class StringReturnReaderParamSocket
+{
+    private static final Logger LOG = Log.getLogger(StringReturnReaderParamSocket.class);
+
+    @OnMessage
+    public String onReader(Reader reader, @PathParam("param") String param) throws IOException
+    {
+        StringBuilder msg = new StringBuilder();
+        msg.append(IO.toString(reader));
+        msg.append('|');
+        msg.append(param);
+        return msg.toString();
+    }
+
+    @OnError
+    public void onError(Session session, Throwable cause) throws IOException
+    {
+        LOG.warn("Error",cause);
+        session.getBasicRemote().sendText("Exception: " + StackUtil.toString(cause));
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/resources/basic-echo-endpoint-config-web.xml b/jetty-websocket/javax-websocket-server-impl/src/test/resources/basic-echo-endpoint-config-web.xml
new file mode 100644
index 0000000..afd9cef
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/resources/basic-echo-endpoint-config-web.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<web-app 
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xmlns="http://java.sun.com/xml/ns/javaee" 
+    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
+    metadata-complete="false"
+    version="3.0">
+    
+    <listener>
+      <listener-class>org.eclipse.jetty.websocket.jsr356.server.samples.echo.BasicEchoEndpointConfigContextListener</listener-class>
+    </listener>
+</web-app>
\ No newline at end of file
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/resources/data/larger.png b/jetty-websocket/javax-websocket-server-impl/src/test/resources/data/larger.png
new file mode 100644
index 0000000..ad09b2f
Binary files /dev/null and b/jetty-websocket/javax-websocket-server-impl/src/test/resources/data/larger.png differ
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/resources/data/larger.png.sha b/jetty-websocket/javax-websocket-server-impl/src/test/resources/data/larger.png.sha
new file mode 100644
index 0000000..426a795
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/resources/data/larger.png.sha
@@ -0,0 +1 @@
+acaa0165afd6912984d96472fef9db9b72b45cd3  larger.png
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/resources/data/largest.jpg b/jetty-websocket/javax-websocket-server-impl/src/test/resources/data/largest.jpg
new file mode 100644
index 0000000..2130578
Binary files /dev/null and b/jetty-websocket/javax-websocket-server-impl/src/test/resources/data/largest.jpg differ
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/resources/data/largest.jpg.sha b/jetty-websocket/javax-websocket-server-impl/src/test/resources/data/largest.jpg.sha
new file mode 100644
index 0000000..d0ac8d5
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/resources/data/largest.jpg.sha
@@ -0,0 +1 @@
+96fadcd98ea76c0f7928dccd37f35c9a3518cde8  largest.jpg
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/resources/data/medium.png b/jetty-websocket/javax-websocket-server-impl/src/test/resources/data/medium.png
new file mode 100644
index 0000000..b66f4d9
Binary files /dev/null and b/jetty-websocket/javax-websocket-server-impl/src/test/resources/data/medium.png differ
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/resources/data/medium.png.sha b/jetty-websocket/javax-websocket-server-impl/src/test/resources/data/medium.png.sha
new file mode 100644
index 0000000..a414e0f
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/resources/data/medium.png.sha
@@ -0,0 +1 @@
+c99b6ea3b589be24c91fc3d09e1d384d190892ae  medium.png
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/resources/data/small.png b/jetty-websocket/javax-websocket-server-impl/src/test/resources/data/small.png
new file mode 100644
index 0000000..498e45a
Binary files /dev/null and b/jetty-websocket/javax-websocket-server-impl/src/test/resources/data/small.png differ
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/resources/data/small.png.sha b/jetty-websocket/javax-websocket-server-impl/src/test/resources/data/small.png.sha
new file mode 100644
index 0000000..d5dbacf
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/resources/data/small.png.sha
@@ -0,0 +1 @@
+7a1a94aff526a8271b67871c2a6461b8609ce5ae  small.png
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/resources/empty-web.xml b/jetty-websocket/javax-websocket-server-impl/src/test/resources/empty-web.xml
new file mode 100644
index 0000000..18aafa5
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/resources/empty-web.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<web-app 
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xmlns="http://java.sun.com/xml/ns/javaee" 
+    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
+    metadata-complete="false"
+    version="3.0">
+</web-app>
\ No newline at end of file
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/resources/idle-timeout-config-web.xml b/jetty-websocket/javax-websocket-server-impl/src/test/resources/idle-timeout-config-web.xml
new file mode 100644
index 0000000..a500095
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/resources/idle-timeout-config-web.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<web-app 
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xmlns="http://java.sun.com/xml/ns/javaee" 
+    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
+    metadata-complete="false"
+    version="3.0">
+    
+    <listener>
+      <listener-class>org.eclipse.jetty.websocket.jsr356.server.samples.idletimeout.IdleTimeoutContextListener</listener-class>
+    </listener>
+</web-app>
\ No newline at end of file
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/resources/jetty-logging.properties b/jetty-websocket/javax-websocket-server-impl/src/test/resources/jetty-logging.properties
new file mode 100644
index 0000000..c5a50f6
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/resources/jetty-logging.properties
@@ -0,0 +1,11 @@
+org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
+org.eclipse.jetty.LEVEL=WARN
+
+# org.eclipse.jetty.websocket.LEVEL=DEBUG
+# org.eclipse.jetty.websocket.LEVEL=INFO
+# org.eclipse.jetty.websocket.LEVEL=WARN
+# org.eclipse.jetty.websocket.common.io.LEVEL=DEBUG
+
+### Show state changes on BrowserDebugTool
+# -- LEAVE THIS AT DEBUG LEVEL --
+org.eclipse.jetty.websocket.jsr356.server.browser.LEVEL=DEBUG
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/resources/jsr-browser-debug-tool/index.html b/jetty-websocket/javax-websocket-server-impl/src/test/resources/jsr-browser-debug-tool/index.html
new file mode 100644
index 0000000..ee9ef00
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/resources/jsr-browser-debug-tool/index.html
@@ -0,0 +1,37 @@
+<html>
+  <head>
+    <title>Jetty WebSocket Browser -> Server Debug Tool</title>
+    <script type="text/javascript" src="websocket.js"></script>
+    <link rel="stylesheet" type="text/css" href="main.css" media="all" >
+  </head>
+  <body>
+    jetty websocket/browser/javascript -> server debug tool #console
+    <div id="console"></div>
+    <div id="buttons">
+      <input id="connect" class="button" type="submit" name="connect" value="connect"/>
+      <input id="close" class="button" type="submit" name="close" value="close" disabled="disabled"/>
+      <input id="info" class="button" type="submit" name="info" value="info" disabled="disabled"/>
+      <input id="time" class="button" type="submit" name="time" value="time" disabled="disabled"/>
+      <input id="many" class="button" type="submit" name="many" value="many" disabled="disabled"/>
+      <input id="manythreads" class="button" type="submit" name="many" value="manythreads" disabled="disabled"/>
+      <input id="hello" class="button" type="submit" name="hello" value="hello" disabled="disabled"/>
+      <input id="there" class="button" type="submit" name="there" value="there" disabled="disabled"/>
+      <input id="json" class="button" type="submit" name="json" value="json" disabled="disabled"/>
+    </div>
+    <script type="text/javascript">
+    $("connect").onclick = function(event) { wstool.connect(); return false; }
+    $("close").onclick = function(event) {wstool.close(); return false; }
+    $("info").onclick = function(event) {wstool.write("info:"); return false; }
+    $("time").onclick = function(event) {wstool.write("time:"); return false; }
+    $("many").onclick = function(event) {wstool.write("many:15,300"); return false; }
+    $("manythreads").onclick = function(event) {wstool.write("manythreads:20,25,60"); return false; }
+    $("hello").onclick = function(event) {wstool.write("Hello"); return false; }
+    $("there").onclick = function(event) {wstool.write("There"); return false; }
+    $("json").onclick = function(event) {wstool.write("[{\"channel\":\"/meta/subscribe\",\"subscription\":\"/chat/demo\",\"id\":\"2\",\"clientId\":\"81dwnxwbgs0h0bq8968b0a0gyl\",\"timestamp\":\"Thu,"
+            + " 12 Sep 2013 19:42:30 GMT\"},{\"channel\":\"/meta/subscribe\",\"subscription\":\"/members/demo\",\"id\":\"3\",\"clientId\":\"81dwnxwbgs0h0bq8968b0a0gyl\",\"timestamp\":\"Thu,"
+            + " 12 Sep 2013 19:42:30 GMT\"},{\"channel\":\"/chat/demo\",\"data\":{\"user\":\"ch\",\"membership\":\"join\",\"chat\":\"ch"
+            + " has joined\"},\"id\":\"4\",\"clientId\":\"81dwnxwbgs0h0bq8968b0a0gyl\",\"timestamp\":\"Thu,"
+            + " 12 Sep 2013 19:42:30 GMT\"}]"); return false; }
+    </script>
+  </body>
+</html>
\ No newline at end of file
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/resources/jsr-browser-debug-tool/main.css b/jetty-websocket/javax-websocket-server-impl/src/test/resources/jsr-browser-debug-tool/main.css
new file mode 100644
index 0000000..7a808dc
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/resources/jsr-browser-debug-tool/main.css
@@ -0,0 +1,33 @@
+body {
+	font-family: sans-serif;
+}
+
+div {
+    border: 0px solid black;	
+}
+
+div#console {
+    clear: both;
+    width: 40em;
+    height: 20em;
+    overflow: auto;
+    background-color: #f0f0f0;
+    padding: 4px;
+    border: 1px solid black;
+}
+
+div#console .info {
+	color: black;
+}
+
+div#console .error {
+    color: red;
+}
+
+div#console .client {
+	color: blue;
+}
+
+div#console .server {
+    color: magenta;
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/resources/jsr-browser-debug-tool/websocket.js b/jetty-websocket/javax-websocket-server-impl/src/test/resources/jsr-browser-debug-tool/websocket.js
new file mode 100644
index 0000000..3e1d5a9
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/resources/jsr-browser-debug-tool/websocket.js
@@ -0,0 +1,139 @@
+if (!window.WebSocket && window.MozWebSocket) {
+    window.WebSocket = window.MozWebSocket;
+}
+
+if (!window.WebSocket) {
+    alert("WebSocket not supported by this browser");
+}
+
+function $() {
+    return document.getElementById(arguments[0]);
+}
+function $F() {
+    return document.getElementById(arguments[0]).value;
+}
+
+function getKeyCode(ev) {
+    if (window.event)
+        return window.event.keyCode;
+    return ev.keyCode;
+}
+
+var wstool = {
+    connect : function() {
+        var location = document.location.toString().replace('http://', 'ws://')
+                .replace('https://', 'wss://');
+
+        wstool.info("Document URI: " + document.location);
+        wstool.info("WS URI: " + location);
+        
+        this._scount = 0;
+
+        try {
+            this._ws = new WebSocket(location, "tool");
+            this._ws.onopen = this._onopen;
+            this._ws.onmessage = this._onmessage;
+            this._ws.onclose = this._onclose;
+            this._ws.onerror = this._onerror;
+        } catch (exception) {
+            wstool.info("Connect Error: " + exception);
+        }
+    },
+
+    close : function() {
+        this._ws.close();
+    },
+    
+    _out : function(css, message) {
+        var console = $('console');
+        var spanText = document.createElement('span');
+        spanText.className = 'text ' + css;
+        spanText.innerHTML = message;
+        var lineBreak = document.createElement('br');
+        console.appendChild(spanText);
+        console.appendChild(lineBreak);
+        console.scrollTop = console.scrollHeight - console.clientHeight;
+    },
+
+    info : function(message) {
+        wstool._out("info", message);
+    },
+
+    error : function(message) {
+        wstool._out("error", message);
+    },
+
+    infoc : function(message) {
+        wstool._out("client", "[c] " + message);
+    },
+    
+    infos : function(message) {
+        this._scount++;
+        wstool._out("server", "[s" + this._scount + "] " + message);
+    },
+
+    setState : function(enabled) {
+        $('connect').disabled = enabled;
+        $('close').disabled = !enabled;
+        $('info').disabled = !enabled;
+        $('time').disabled = !enabled;
+        $('many').disabled = !enabled;
+        $('manythreads').disabled = !enabled;
+        $('hello').disabled = !enabled;
+        $('there').disabled = !enabled;
+        $('json').disabled = !enabled;
+    },
+    
+    _onopen : function() {
+        wstool.setState(true);
+        wstool.info("Websocket Connected");
+    },
+    
+    _onerror : function(evt) {
+        wstool.setState(false);
+        wstool.error("Websocket Error: " + evt.data);
+        wstool.error("See Javascript Console for possible detailed error message");
+    },
+
+    _send : function(message) {
+        if (this._ws) {
+            this._ws.send(message);
+            wstool.infoc(message);
+        }
+    },
+
+    write : function(text) {
+        wstool._send(text);
+    },
+
+    _onmessage : function(m) {
+        if (m.data) {
+            wstool.infos(m.data);
+        }
+    },
+
+    _onclose : function(closeEvent) {
+        this._ws = null;
+        wstool.setState(false);
+        wstool.info("Websocket Closed");
+        wstool.info("  .wasClean = " + closeEvent.wasClean);
+        
+        var codeMap = {};
+        codeMap[1000] = "(NORMAL)";
+        codeMap[1001] = "(ENDPOINT_GOING_AWAY)";
+        codeMap[1002] = "(PROTOCOL_ERROR)";
+        codeMap[1003] = "(UNSUPPORTED_DATA)";
+        codeMap[1004] = "(UNUSED/RESERVED)";
+        codeMap[1005] = "(INTERNAL/NO_CODE_PRESENT)";
+        codeMap[1006] = "(INTERNAL/ABNORMAL_CLOSE)";
+        codeMap[1007] = "(BAD_DATA)";
+        codeMap[1008] = "(POLICY_VIOLATION)";
+        codeMap[1009] = "(MESSAGE_TOO_BIG)";
+        codeMap[1010] = "(HANDSHAKE/EXT_FAILURE)";
+        codeMap[1011] = "(SERVER/UNEXPECTED_CONDITION)";
+        codeMap[1015] = "(INTERNAL/TLS_ERROR)";
+        var codeStr = codeMap[closeEvent.code];
+        wstool.info("  .code = " + closeEvent.code + "  " + codeStr);
+        wstool.info("  .reason = " + closeEvent.reason);
+    }
+};
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/resources/large-echo-config-web.xml b/jetty-websocket/javax-websocket-server-impl/src/test/resources/large-echo-config-web.xml
new file mode 100644
index 0000000..08e696f
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/resources/large-echo-config-web.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<web-app 
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xmlns="http://java.sun.com/xml/ns/javaee" 
+    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
+    metadata-complete="false"
+    version="3.0">
+    
+    <listener>
+      <listener-class>org.eclipse.jetty.websocket.jsr356.server.samples.echo.LargeEchoContextListener</listener-class>
+    </listener>
+</web-app>
\ No newline at end of file
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/resources/pong-config-web.xml b/jetty-websocket/javax-websocket-server-impl/src/test/resources/pong-config-web.xml
new file mode 100644
index 0000000..c4598cd
--- /dev/null
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/resources/pong-config-web.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<web-app 
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xmlns="http://java.sun.com/xml/ns/javaee" 
+    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
+    metadata-complete="false"
+    version="3.0">
+    
+    <listener>
+      <listener-class>org.eclipse.jetty.websocket.jsr356.server.samples.pong.PongContextListener</listener-class>
+    </listener>
+</web-app>
\ No newline at end of file
diff --git a/jetty-websocket/pom.xml b/jetty-websocket/pom.xml
index 4ec2ff5..14f25cc 100644
--- a/jetty-websocket/pom.xml
+++ b/jetty-websocket/pom.xml
@@ -3,60 +3,36 @@
     <parent>
         <artifactId>jetty-project</artifactId>
         <groupId>org.eclipse.jetty</groupId>
-        <version>8.1.17.v20150415</version>
+        <version>9.2.13.v20150730</version>
     </parent>
 
     <modelVersion>4.0.0</modelVersion>
-    <artifactId>jetty-websocket</artifactId>
-    <name>Jetty :: Websocket</name>
+    <groupId>org.eclipse.jetty.websocket</groupId>
+    <artifactId>websocket-parent</artifactId>
+    <name>Jetty :: Websocket :: Parent</name>
+    <packaging>pom</packaging>
     <url>http://www.eclipse.org/jetty</url>
-    <properties>
-        <bundle-symbolic-name>${project.groupId}.websocket</bundle-symbolic-name>
-    </properties>
 
-    <dependencies>
-        <dependency>
-            <groupId>org.eclipse.jetty.orbit</groupId>
-            <artifactId>javax.servlet</artifactId>
-            <scope>provided</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.eclipse.jetty</groupId>
-            <artifactId>jetty-server</artifactId>
-            <version>${project.version}</version>
-            <scope>provided</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.eclipse.jetty</groupId>
-            <artifactId>jetty-util</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-        <dependency>
-            <groupId>org.eclipse.jetty</groupId>
-            <artifactId>jetty-io</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-        <dependency>
-            <groupId>org.eclipse.jetty</groupId>
-            <artifactId>jetty-http</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-        <dependency>
-            <groupId>org.eclipse.jetty.toolchain</groupId>
-            <artifactId>jetty-test-helper</artifactId>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.eclipse.jetty</groupId>
-            <artifactId>jetty-servlet</artifactId>
-            <version>${project.version}</version>
-            <scope>test</scope>
-        </dependency>
-    </dependencies>
+    <modules>
+        <module>websocket-common</module>
+        <module>websocket-api</module>
+        <module>websocket-client</module>
+        <module>websocket-server</module>
+        <module>websocket-servlet</module>
+        <module>javax-websocket-client-impl</module>
+        <module>javax-websocket-server-impl</module>
+    </modules>
 
     <build>
         <plugins>
             <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>findbugs-maven-plugin</artifactId>
+                <configuration>
+                    <onlyAnalyze>org.eclipse.jetty.websocket.*</onlyAnalyze>
+                </configuration>
+            </plugin>
+            <plugin>
                 <groupId>org.apache.felix</groupId>
                 <artifactId>maven-bundle-plugin</artifactId>
                 <extensions>true</extensions>
@@ -67,7 +43,9 @@
                         </goals>
                         <configuration>
                             <instructions>
-                                <Import-Package>javax.servlet.*;version="2.6.0",*</Import-Package>
+                                <Export-Package>${bundle-symbolic-name}.*;version="${parsedVersion.majorVersion}.${parsedVersion.minorVersion}.${parsedVersion.incrementalVersion}"</Export-Package>
+                                <Import-Package>javax.servlet.*;version="[3.1,4.0)",org.eclipse.jetty.*;version="[9.0,10.0)",*</Import-Package>
+                                <_nouses>true</_nouses>
                             </instructions>
                         </configuration>
                     </execution>
@@ -76,20 +54,6 @@
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-jar-plugin</artifactId>
-                <executions>
-                    <execution>
-                        <id>artifact-jar</id>
-                        <goals>
-                            <goal>jar</goal>
-                        </goals>
-                    </execution>
-                    <execution>
-                        <id>test-jar</id>
-                        <goals>
-                            <goal>test-jar</goal>
-                        </goals>
-                    </execution>
-                </executions>
                 <configuration>
                     <archive>
                         <manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
@@ -98,9 +62,20 @@
             </plugin>
             <plugin>
                 <groupId>org.codehaus.mojo</groupId>
-                <artifactId>findbugs-maven-plugin</artifactId>
+                <artifactId>clirr-maven-plugin</artifactId>
+                <version>2.5</version>
+                <executions>
+                    <execution>
+                        <id>compare-api</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>clirr</goal>
+                        </goals>
+                    </execution>
+                </executions>
                 <configuration>
-                    <onlyAnalyze>org.eclipse.jetty.websocket.*</onlyAnalyze>
+                    <minSeverity>info</minSeverity>
+                    <comparisonVersion>9.1.0.v20131115</comparisonVersion>
                 </configuration>
             </plugin>
         </plugins>
diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/AbstractExtension.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/AbstractExtension.java
deleted file mode 100644
index 2bd4fd7..0000000
--- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/AbstractExtension.java
+++ /dev/null
@@ -1,149 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.websocket;
-
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.Map;
-
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.util.QuotedStringTokenizer;
-import org.eclipse.jetty.websocket.WebSocketParser.FrameHandler;
-
-public class AbstractExtension implements Extension
-{
-    private static final int[] __mask = { -1, 0x04, 0x02, 0x01};
-    private final String _name;
-    private final Map<String,String> _parameters=new HashMap<String, String>();
-    private FrameHandler _inbound;
-    private WebSocketGenerator _outbound;
-    private WebSocket.FrameConnection _connection;
-    
-    public AbstractExtension(String name)
-    {
-        _name = name;
-    }
-    
-    public WebSocket.FrameConnection getConnection()
-    {
-        return _connection;
-    }
-
-    public boolean init(Map<String, String> parameters)
-    {
-        _parameters.putAll(parameters);
-        return true;
-    }
-    
-    public String getInitParameter(String name)
-    {
-        return _parameters.get(name);
-    }
-
-    public String getInitParameter(String name,String dft)
-    {
-        if (!_parameters.containsKey(name))
-            return dft;
-        return _parameters.get(name);
-    }
-
-    public int getInitParameter(String name, int dft)
-    {
-        String v=_parameters.get(name);
-        if (v==null)
-            return dft;
-        return Integer.valueOf(v);
-    }
-    
-    
-    public void bind(WebSocket.FrameConnection connection, FrameHandler incoming, WebSocketGenerator outgoing)
-    {
-        _connection=connection;
-        _inbound=incoming;
-        _outbound=outgoing;
-    }
-
-    public String getName()
-    {
-        return _name;
-    }
-
-    public String getParameterizedName()
-    {
-        StringBuilder name = new StringBuilder();
-        name.append(_name);
-        for (String param : _parameters.keySet())
-            name.append(';').append(param).append('=').append(QuotedStringTokenizer.quoteIfNeeded(_parameters.get(param),";="));
-        return name.toString();
-    }
-
-    public void onFrame(byte flags, byte opcode, Buffer buffer)
-    {
-        // System.err.printf("onFrame %s %x %x %d\n",getExtensionName(),flags,opcode,buffer.length());
-        _inbound.onFrame(flags,opcode,buffer);
-    }
-
-    public void close(int code, String message)
-    {
-        _inbound.close(code,message);
-    }
-
-    public int flush() throws IOException
-    {
-        return _outbound.flush();
-    }
-
-    public boolean isBufferEmpty()
-    {
-        return _outbound.isBufferEmpty();
-    }
-
-    public void addFrame(byte flags, byte opcode, byte[] content, int offset, int length) throws IOException
-    {
-        // System.err.printf("addFrame %s %x %x %d\n",getExtensionName(),flags,opcode,length);
-        _outbound.addFrame(flags,opcode,content,offset,length);
-    }
-    
-    public byte setFlag(byte flags,int rsv)
-    {
-        if (rsv<1||rsv>3)
-            throw new IllegalArgumentException("rsv"+rsv);
-        byte b=(byte)(flags | __mask[rsv]);
-        return b;
-    }
-    
-    public byte clearFlag(byte flags,int rsv)
-    {
-        if (rsv<1||rsv>3)
-            throw new IllegalArgumentException("rsv"+rsv);
-        return (byte)(flags & ~__mask[rsv]);
-    }
-
-    public boolean isFlag(byte flags,int rsv)
-    {
-        if (rsv<1||rsv>3)
-            throw new IllegalArgumentException("rsv"+rsv);
-        return (flags & __mask[rsv])!=0;
-    }
-    
-    public String toString()
-    {
-        return getParameterizedName();
-    }
-}
diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/DeflateFrameExtension.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/DeflateFrameExtension.java
deleted file mode 100644
index f57f660..0000000
--- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/DeflateFrameExtension.java
+++ /dev/null
@@ -1,164 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.websocket;
-
-import java.io.IOException;
-import java.util.Map;
-import java.util.zip.DataFormatException;
-import java.util.zip.Deflater;
-import java.util.zip.Inflater;
-
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.ByteArrayBuffer;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-
-/**
- * TODO Implement proposed deflate frame draft
- */
-public class DeflateFrameExtension extends AbstractExtension
-{
-    private static final Logger LOG = Log.getLogger(DeflateFrameExtension.class);
-
-    private int _minLength=8;
-    private Deflater _deflater;
-    private Inflater _inflater;
-
-    public DeflateFrameExtension()
-    {
-        super("x-deflate-frame");
-    }
-
-    @Override
-    public boolean init(Map<String, String> parameters)
-    {
-        if (!parameters.containsKey("minLength"))
-            parameters.put("minLength",Integer.toString(_minLength));
-        if(super.init(parameters))
-        {
-            _minLength=getInitParameter("minLength",_minLength);
-
-            _deflater=new Deflater();
-            _inflater=new Inflater();
-
-            return true;
-        }
-        return false;
-    }
-
-    /* (non-Javadoc)
-     * @see org.eclipse.jetty.websocket.AbstractExtension#onFrame(byte, byte, org.eclipse.jetty.io.Buffer)
-     */
-    @Override
-    public void onFrame(byte flags, byte opcode, Buffer buffer)
-    {
-        if (getConnection().isControl(opcode) || !isFlag(flags,1))
-        {
-            super.onFrame(flags,opcode,buffer);
-            return;
-        }
-
-        if (buffer.array()==null)
-            buffer=buffer.asMutableBuffer();
-
-        int length=0xff&buffer.get();
-        if (length>=0x7e)
-        {
-            int b=(length==0x7f)?8:2;
-            length=0;
-            while(b-->0)
-                length=0x100*length+(0xff&buffer.get());
-        }
-
-        // TODO check a max framesize
-
-        _inflater.setInput(buffer.array(),buffer.getIndex(),buffer.length());
-        ByteArrayBuffer buf = new ByteArrayBuffer(length);
-        try
-        {
-            while(_inflater.getRemaining()>0)
-            {
-                int inflated=_inflater.inflate(buf.array(),buf.putIndex(),buf.space());
-                if (inflated==0)
-                    throw new DataFormatException("insufficient data");
-                buf.setPutIndex(buf.putIndex()+inflated);
-            }
-
-            super.onFrame(clearFlag(flags,1),opcode,buf);
-        }
-        catch(DataFormatException e)
-        {
-            LOG.warn(e);
-            getConnection().close(WebSocketConnectionRFC6455.CLOSE_BAD_PAYLOAD,e.toString());
-        }
-    }
-
-    /* (non-Javadoc)
-     * @see org.eclipse.jetty.websocket.AbstractExtension#addFrame(byte, byte, byte[], int, int)
-     */
-    @Override
-    public void addFrame(byte flags, byte opcode, byte[] content, int offset, int length) throws IOException
-    {
-        if (getConnection().isControl(opcode) || length<_minLength)
-        {
-            super.addFrame(clearFlag(flags,1),opcode,content,offset,length);
-            return;
-        }
-
-        // prepare the uncompressed input
-        _deflater.reset();
-        _deflater.setInput(content,offset,length);
-        _deflater.finish();
-
-        // prepare the output buffer
-        byte[] out= new byte[length];
-        int out_offset=0;
-
-        // write the uncompressed length
-        if (length>0xffff)
-        {
-            out[out_offset++]=0x7f;
-            out[out_offset++]=(byte)0;
-            out[out_offset++]=(byte)0;
-            out[out_offset++]=(byte)0;
-            out[out_offset++]=(byte)0;
-            out[out_offset++]=(byte)((length>>24)&0xff);
-            out[out_offset++]=(byte)((length>>16)&0xff);
-            out[out_offset++]=(byte)((length>>8)&0xff);
-            out[out_offset++]=(byte)(length&0xff);
-        }
-        else if (length >=0x7e)
-        {
-            out[out_offset++]=0x7e;
-            out[out_offset++]=(byte)(length>>8);
-            out[out_offset++]=(byte)(length&0xff);
-        }
-        else
-        {
-            out[out_offset++]=(byte)(length&0x7f);
-        }
-
-        int l = _deflater.deflate(out,out_offset,length-out_offset);
-
-        if (_deflater.finished())
-            super.addFrame(setFlag(flags,1),opcode,out,0,l+out_offset);
-        else
-            super.addFrame(clearFlag(flags,1),opcode,content,offset,length);
-    }
-}
diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/Extension.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/Extension.java
deleted file mode 100644
index 936aa5f..0000000
--- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/Extension.java
+++ /dev/null
@@ -1,31 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.websocket;
-
-import java.util.Map;
-
-public interface Extension extends WebSocketParser.FrameHandler, WebSocketGenerator
-{
-    public String getName();
-    public String getParameterizedName();
-    
-    public boolean init(Map<String,String> parameters);
-    public void bind(WebSocket.FrameConnection connection, WebSocketParser.FrameHandler inbound, WebSocketGenerator outbound);
-    
-}
diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/FixedMaskGen.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/FixedMaskGen.java
deleted file mode 100644
index a44b78a..0000000
--- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/FixedMaskGen.java
+++ /dev/null
@@ -1,44 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-
-package org.eclipse.jetty.websocket;
-
-
-public class FixedMaskGen implements MaskGen
-{
-    private final byte[] _mask;
-
-    public FixedMaskGen()
-    {
-        this(new byte[]{(byte)0xff,(byte)0xff,(byte)0xff,(byte)0xff});
-    }
-
-    public FixedMaskGen(byte[] mask)
-    {
-        _mask=new byte[4];
-        // Copy to avoid that external code keeps a reference
-        // to the array parameter to modify masking on-the-fly
-        System.arraycopy(mask, 0, _mask, 0, 4);
-    }
-
-    public void genMask(byte[] mask)
-    {
-        System.arraycopy(_mask, 0, mask, 0, 4);
-    }
-}
diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/FragmentExtension.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/FragmentExtension.java
deleted file mode 100644
index 1a985b4..0000000
--- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/FragmentExtension.java
+++ /dev/null
@@ -1,80 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.websocket;
-
-import java.io.IOException;
-import java.util.Map;
-
-public class FragmentExtension extends AbstractExtension
-{
-    private int _maxLength=-1;
-    private int _minFragments=1;
-    
-    public FragmentExtension()
-    {
-        super("fragment");
-    }
-
-    @Override
-    public boolean init(Map<String, String> parameters)
-    {
-        if(super.init(parameters))
-        {
-            _maxLength=getInitParameter("maxLength",_maxLength);
-            _minFragments=getInitParameter("minFragments",_minFragments);
-            return true;
-        }
-        return false;
-    }
-
-    @Override
-    public void addFrame(byte flags, byte opcode, byte[] content, int offset, int length) throws IOException
-    {
-        if (getConnection().isControl(opcode))
-        {
-            super.addFrame(flags,opcode,content,offset,length);
-            return;
-        }
-        
-        int fragments=1;
-        
-        while (_maxLength>0 && length>_maxLength)
-        {
-            fragments++;
-            super.addFrame((byte)(flags&~getConnection().finMask()),opcode,content,offset,_maxLength);
-            length-=_maxLength;
-            offset+=_maxLength;
-            opcode=getConnection().continuationOpcode();
-        }
-        
-        while (fragments<_minFragments)
-        {
-            int frag=length/2;
-            fragments++;
-            super.addFrame((byte)(flags&0x7),opcode,content,offset,frag);
-            length-=frag;
-            offset+=frag;
-            opcode=getConnection().continuationOpcode();
-        }
-
-        super.addFrame((byte)(flags|getConnection().finMask()),opcode,content,offset,length);
-    }
-    
-    
-}
diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/IdentityExtension.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/IdentityExtension.java
deleted file mode 100644
index a01db55..0000000
--- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/IdentityExtension.java
+++ /dev/null
@@ -1,27 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.websocket;
-
-public class IdentityExtension extends AbstractExtension
-{
-    public IdentityExtension()
-    {
-        super("identity");
-    }
-}
diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/MaskGen.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/MaskGen.java
deleted file mode 100644
index 9178d90..0000000
--- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/MaskGen.java
+++ /dev/null
@@ -1,24 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.websocket;
-
-public interface MaskGen
-{
-    void genMask(byte[] mask);
-}
diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/RandomMaskGen.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/RandomMaskGen.java
deleted file mode 100644
index a180c60..0000000
--- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/RandomMaskGen.java
+++ /dev/null
@@ -1,45 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.websocket;
-
-import java.util.Random;
-
-
-public class RandomMaskGen implements MaskGen
-{
-    private final Random _random;
-
-    public RandomMaskGen()
-    {
-        this(new Random());
-    }
-
-    public RandomMaskGen(Random random)
-    {
-        _random=random;
-    }
-
-    public void genMask(byte[] mask)
-    {
-        // The assumption is that this code is always called
-        // with an external lock held to prevent concurrent access
-        // Otherwise we need to synchronize on the _random.
-        _random.nextBytes(mask);
-    }
-}
diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocket.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocket.java
deleted file mode 100644
index 34bdf07..0000000
--- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocket.java
+++ /dev/null
@@ -1,275 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.websocket;
-
-import java.io.IOException;
-
-/**
- * WebSocket Interface.
- * <p>
- * This interface provides the signature for a server-side end point of a websocket connection.
- * The Interface has several nested interfaces, for each type of message that may be received.
- */
-public interface WebSocket
-{   
-    /**
-     * Called when a new websocket connection is accepted.
-     * @param connection The Connection object to use to send messages.
-     */
-    void onOpen(Connection connection);
-
-    /**
-     * Called when an established websocket connection closes
-     * @param closeCode
-     * @param message
-     */
-    void onClose(int closeCode, String message);
-
-    /**
-     * A nested WebSocket interface for receiving text messages
-     */
-    interface OnTextMessage extends WebSocket
-    {
-        /**
-         * Called with a complete text message when all fragments have been received.
-         * The maximum size of text message that may be aggregated from multiple frames is set with {@link Connection#setMaxTextMessageSize(int)}.
-         * @param data The message
-         */
-        void onMessage(String data);
-    }
-
-    /**
-     * A nested WebSocket interface for receiving binary messages
-     */
-    interface OnBinaryMessage extends WebSocket
-    {
-        /**
-         * Called with a complete binary message when all fragments have been received.
-         * The maximum size of binary message that may be aggregated from multiple frames is set with {@link Connection#setMaxBinaryMessageSize(int)}.
-         * @param data
-         * @param offset
-         * @param length
-         */
-        void onMessage(byte[] data, int offset, int length);
-    }
-    
-    /**
-     * A nested WebSocket interface for receiving control messages
-     */
-    interface OnControl extends WebSocket
-    {
-        /** 
-         * Called when a control message has been received.
-         * @param controlCode
-         * @param data
-         * @param offset
-         * @param length
-         * @return true if this call has completely handled the control message and no further processing is needed.
-         */
-        boolean onControl(byte controlCode,byte[] data, int offset, int length);
-    }
-    
-    /**
-     * A nested WebSocket interface for receiving any websocket frame
-     */
-    interface OnFrame extends WebSocket
-    {
-        /**
-         * Called when any websocket frame is received.
-         * @param flags
-         * @param opcode
-         * @param data
-         * @param offset
-         * @param length
-         * @return true if this call has completely handled the frame and no further processing is needed (including aggregation and/or message delivery)
-         */
-        boolean onFrame(byte flags,byte opcode,byte[] data, int offset, int length);
-        
-        void onHandshake(FrameConnection connection);
-    }
-    
-    /**
-     * A  Connection interface is passed to a WebSocket instance via the {@link WebSocket#onOpen(Connection)} to 
-     * give the application access to the specifics of the current connection.   This includes methods 
-     * for sending frames and messages as well as methods for interpreting the flags and opcodes of the connection.
-     */
-    public interface Connection
-    {
-        String getProtocol();
-        void sendMessage(String data) throws IOException;
-        void sendMessage(byte[] data, int offset, int length) throws IOException;
-        
-        /**
-         * @deprecated Use {@link #close()}
-         */
-        void disconnect();
-
-        /** 
-         * Close the connection with normal close code.
-         */
-        void close();
-        
-        /** Close the connection with specific closeCode and message.
-         * @param closeCode The close code to send, or -1 for no close code
-         * @param message The message to send or null for no message
-         */
-        void close(int closeCode,String message);
-        
-        boolean isOpen();
-
-        /**
-         * @param ms The time in ms that the connection can be idle before closing
-         */
-        void setMaxIdleTime(int ms);
-        
-        /**
-         * @param size size<0 No aggregation of frames to messages, >=0 max size of text frame aggregation buffer in characters
-         */
-        void setMaxTextMessageSize(int size);
-        
-        /**
-         * @param size size<0 no aggregation of binary frames, >=0 size of binary frame aggregation buffer
-         */
-        void setMaxBinaryMessageSize(int size);
-        
-        /**
-         * @return The time in ms that the connection can be idle before closing
-         */
-        int getMaxIdleTime();
-        
-        /**
-         * Size in characters of the maximum text message to be received
-         * @return size <0 No aggregation of frames to messages, >=0 max size of text frame aggregation buffer in characters
-         */
-        int getMaxTextMessageSize();
-        
-        /**
-         * Size in bytes of the maximum binary message to be received
-         * @return size <0 no aggregation of binary frames, >=0 size of binary frame aggregation buffer
-         */
-        int getMaxBinaryMessageSize();
-    }
-
-    /**
-     * Frame Level Connection
-     * <p>The Connection interface at the level of sending/receiving frames rather than messages.
-     * Also contains methods to decode/generate flags and opcodes without using constants, so that 
-     * code can be written to work with multiple drafts of the protocol.
-     *
-     */
-    public interface FrameConnection extends Connection
-    {
-        /**
-         * @return The opcode of a binary message
-         */
-        byte binaryOpcode();
-        
-        /**
-         * @return The opcode of a text message
-         */
-        byte textOpcode();
-        
-        /**
-         * @return The opcode of a continuation frame
-         */
-        byte continuationOpcode();
-        
-        /**
-         * @return Mask for the FIN bit.
-         */
-        byte finMask();
-        
-        /** Set if frames larger than the frame buffer are handled with local fragmentations
-         * @param allowFragmentation
-         */
-        void setAllowFrameFragmentation(boolean allowFragmentation);
-
-        /**
-         * @param flags The flags bytes of a frame
-         * @return True of the flags indicate a final frame.
-         */
-        boolean isMessageComplete(byte flags);
-
-        /**
-         * @param opcode
-         * @return True if the opcode is for a control frame
-         */
-        boolean isControl(byte opcode);
-
-        /**
-         * @param opcode
-         * @return True if the opcode is for a text frame
-         */
-        boolean isText(byte opcode);
-
-        /**
-         * @param opcode
-         * @return True if the opcode is for a binary frame
-         */
-        boolean isBinary(byte opcode);
-
-        /**
-         * @param opcode
-         * @return True if the opcode is for a continuation frame
-         */
-        boolean isContinuation(byte opcode);
-
-        /**
-         * @param opcode 
-         * @return True if the opcode is a close control
-         */
-        boolean isClose(byte opcode);
-
-        /**
-         * @param opcode
-         * @return True if the opcode is a ping control
-         */
-        boolean isPing(byte opcode);
-
-        /**
-         * @param opcode
-         * @return True if the opcode is a pong control
-         */
-        boolean isPong(byte opcode);
-        
-        /**
-         * @return True if frames larger than the frame buffer are fragmented.
-         */
-        boolean isAllowFrameFragmentation();
-        
-        /** Send a control frame
-         * @param control
-         * @param data
-         * @param offset
-         * @param length
-         * @throws IOException
-         */
-        void sendControl(byte control,byte[] data, int offset, int length) throws IOException;
-
-        /** Send an arbitrary frame
-         * @param flags
-         * @param opcode
-         * @param data
-         * @param offset
-         * @param length
-         * @throws IOException
-         */
-        void sendFrame(byte flags,byte opcode,byte[] data, int offset, int length) throws IOException;
-    }
-}
diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketBuffers.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketBuffers.java
deleted file mode 100644
index 910b800..0000000
--- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketBuffers.java
+++ /dev/null
@@ -1,65 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.websocket;
-
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.Buffers;
-import org.eclipse.jetty.io.Buffers.Type;
-import org.eclipse.jetty.io.BuffersFactory;
-
-
-/* ------------------------------------------------------------ */
-/** The WebSocket Buffer Pool.
- *
- * The normal buffers are byte array buffers so that user processes
- * can access directly.   However the generator uses direct buffers
- * for the final output stage as they are filled in bulk and are more
- * efficient to flush.
- */
-public class WebSocketBuffers
-{
-    final private int _bufferSize;
-    final private Buffers _buffers;
-
-    public WebSocketBuffers(final int bufferSize)
-    {
-        _bufferSize=bufferSize;
-        _buffers = BuffersFactory.newBuffers(Type.DIRECT,bufferSize,Type.INDIRECT,bufferSize,Type.INDIRECT,-1);
-    }
-
-    public Buffer getBuffer()
-    {
-        return _buffers.getBuffer();
-    }
-
-    public Buffer getDirectBuffer()
-    {
-        return _buffers.getHeader();
-    }
-
-    public void returnBuffer(Buffer buffer)
-    {
-        _buffers.returnBuffer(buffer);
-    }
-
-    public int getBufferSize()
-    {
-        return _bufferSize;
-    }
-}
diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketClient.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketClient.java
deleted file mode 100644
index 00e1547..0000000
--- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketClient.java
+++ /dev/null
@@ -1,620 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.websocket;
-
-import java.io.IOException;
-import java.net.InetSocketAddress;
-import java.net.ProtocolException;
-import java.net.SocketAddress;
-import java.net.URI;
-import java.nio.channels.ByteChannel;
-import java.nio.channels.SocketChannel;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
-import org.eclipse.jetty.util.IO;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-
-
-/* ------------------------------------------------------------ */
-/**
- * <p>{@link WebSocketClient} allows to create multiple connections to multiple destinations
- * that can speak the websocket protocol.</p>
- * <p>When creating websocket connections, {@link WebSocketClient} accepts a {@link WebSocket}
- * object (to receive events from the server), and returns a {@link WebSocket.Connection} to
- * send data to the server.</p>
- * <p>Example usage is as follows:</p>
- * <pre>
- *   WebSocketClientFactory factory = new WebSocketClientFactory();
- *   factory.start();
- *
- *   WebSocketClient client = factory.newWebSocketClient();
- *   // Configure the client
- *
- *   WebSocket.Connection connection = client.open(new URI("ws://127.0.0.1:8080/"), new WebSocket.OnTextMessage()
- *   {
- *     public void onOpen(Connection connection)
- *     {
- *       // open notification
- *     }
- *
- *     public void onClose(int closeCode, String message)
- *     {
- *       // close notification
- *     }
- *
- *     public void onMessage(String data)
- *     {
- *       // handle incoming message
- *     }
- *   }).get(5, TimeUnit.SECONDS);
- *
- *   connection.sendMessage("Hello World");
- * </pre>
- */
-public class WebSocketClient
-{
-    private final static Logger __log = org.eclipse.jetty.util.log.Log.getLogger(WebSocketClient.class.getName());
-
-    private final WebSocketClientFactory _factory;
-    private final Map<String,String> _cookies=new ConcurrentHashMap<String, String>();
-    private final List<String> _extensions=new CopyOnWriteArrayList<String>();
-    private String _origin;
-    private String _protocol;
-    private int _maxIdleTime=-1;
-    private int _maxTextMessageSize=16*1024;
-    private int _maxBinaryMessageSize=-1;
-    private MaskGen _maskGen;
-    private SocketAddress _bindAddress;
-
-    /* ------------------------------------------------------------ */
-    /**
-     * <p>Creates a WebSocketClient from a private WebSocketClientFactory.</p>
-     * <p>This can be wasteful of resources if many clients are created.</p>
-     *
-     * @deprecated Use {@link WebSocketClientFactory#newWebSocketClient()}
-     * @throws Exception if the private WebSocketClientFactory fails to start
-     */
-    @Deprecated
-    public WebSocketClient() throws Exception
-    {
-        _factory=new WebSocketClientFactory();
-        _factory.start();
-        _maskGen=_factory.getMaskGen();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * <p>Creates a WebSocketClient with shared WebSocketClientFactory.</p>
-     *
-     * @param factory the shared {@link WebSocketClientFactory}
-     */
-    public WebSocketClient(WebSocketClientFactory factory)
-    {
-        _factory=factory;
-        _maskGen=_factory.getMaskGen();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return The WebSocketClientFactory this client was created with.
-     */
-    public WebSocketClientFactory getFactory()
-    {
-        return _factory;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return the address to bind the socket channel to
-     * @see #setBindAddress(SocketAddress)
-     */
-    public SocketAddress getBindAddress()
-    {
-        return _bindAddress;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param bindAddress the address to bind the socket channel to
-     * @see #getBindAddress()
-     */
-    public void setBindAddress(SocketAddress bindAddress)
-    {
-        this._bindAddress = bindAddress;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return The maxIdleTime in ms for connections opened by this client,
-     * or -1 if the default from {@link WebSocketClientFactory#getSelectorManager()} is used.
-     * @see #setMaxIdleTime(int)
-     */
-    public int getMaxIdleTime()
-    {
-        return _maxIdleTime;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param maxIdleTime The max idle time in ms for connections opened by this client
-     * @see #getMaxIdleTime()
-     */
-    public void setMaxIdleTime(int maxIdleTime)
-    {
-        _maxIdleTime=maxIdleTime;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return The subprotocol string for connections opened by this client.
-     * @see #setProtocol(String)
-     */
-    public String getProtocol()
-    {
-        return _protocol;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param protocol The subprotocol string for connections opened by this client.
-     * @see #getProtocol()
-     */
-    public void setProtocol(String protocol)
-    {
-        _protocol = protocol;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return The origin URI of the client
-     * @see #setOrigin(String)
-     */
-    public String getOrigin()
-    {
-        return _origin;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param origin The origin URI of the client (eg "http://example.com")
-     * @see #getOrigin()
-     */
-    public void setOrigin(String origin)
-    {
-        _origin = origin;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * <p>Returns the map of the cookies that are sent during the initial HTTP handshake
-     * that upgrades to the websocket protocol.</p>
-     * @return The read-write cookie map
-     */
-    public Map<String,String> getCookies()
-    {
-        return _cookies;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return The list of websocket protocol extensions
-     */
-    public List<String> getExtensions()
-    {
-        return _extensions;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return the mask generator to use, or null if not mask generator should be used
-     * @see #setMaskGen(MaskGen)
-     */
-    public MaskGen getMaskGen()
-    {
-        return _maskGen;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param maskGen the mask generator to use, or null if not mask generator should be used
-     * @see #getMaskGen()
-     */
-    public void setMaskGen(MaskGen maskGen)
-    {
-        _maskGen = maskGen;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return The initial maximum text message size (in characters) for a connection
-     */
-    public int getMaxTextMessageSize()
-    {
-        return _maxTextMessageSize;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Set the initial maximum text message size for a connection. This can be changed by
-     * the application calling {@link WebSocket.Connection#setMaxTextMessageSize(int)}.
-     * @param maxTextMessageSize The default maximum text message size (in characters) for a connection
-     */
-    public void setMaxTextMessageSize(int maxTextMessageSize)
-    {
-        _maxTextMessageSize = maxTextMessageSize;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return The initial maximum binary message size (in bytes)  for a connection
-     */
-    public int getMaxBinaryMessageSize()
-    {
-        return _maxBinaryMessageSize;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Set the initial maximum binary message size for a connection. This can be changed by
-     * the application calling {@link WebSocket.Connection#setMaxBinaryMessageSize(int)}.
-     * @param maxBinaryMessageSize The default maximum binary message size (in bytes) for a connection
-     */
-    public void setMaxBinaryMessageSize(int maxBinaryMessageSize)
-    {
-        _maxBinaryMessageSize = maxBinaryMessageSize;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * <p>Opens a websocket connection to the URI and blocks until the connection is accepted or there is an error.</p>
-     *
-     * @param uri The URI to connect to.
-     * @param websocket The {@link WebSocket} instance to handle incoming events.
-     * @param maxConnectTime The interval to wait for a successful connection
-     * @param units the units of the maxConnectTime
-     * @return A {@link WebSocket.Connection}
-     * @throws IOException if the connection fails
-     * @throws InterruptedException if the thread is interrupted
-     * @throws TimeoutException if the timeout elapses before the connection is completed
-     * @see #open(URI, WebSocket)
-     */
-    public WebSocket.Connection open(URI uri, WebSocket websocket,long maxConnectTime,TimeUnit units) throws IOException, InterruptedException, TimeoutException
-    {
-        try
-        {
-            return open(uri,websocket).get(maxConnectTime,units);
-        }
-        catch (ExecutionException e)
-        {
-            Throwable cause = e.getCause();
-            if (cause instanceof IOException)
-                throw (IOException)cause;
-            if (cause instanceof Error)
-                throw (Error)cause;
-            if (cause instanceof RuntimeException)
-                throw (RuntimeException)cause;
-            throw new RuntimeException(cause);
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * <p>Asynchronously opens a websocket connection and returns a {@link Future} to obtain the connection.</p>
-     * <p>The caller must call {@link Future#get(long, TimeUnit)} if they wish to impose a connect timeout on the open.</p>
-     *
-     * @param uri The URI to connect to.
-     * @param websocket The {@link WebSocket} instance to handle incoming events.
-     * @return A {@link Future} to the {@link WebSocket.Connection}
-     * @throws IOException if the connection fails
-     * @see #open(URI, WebSocket, long, TimeUnit)
-     */
-    public Future<WebSocket.Connection> open(URI uri, WebSocket websocket) throws IOException
-    {
-        if (!_factory.isStarted())
-            throw new IllegalStateException("Factory !started");
-        
-        __log.debug("Connecting to: {}", uri);
-
-        InetSocketAddress address = toSocketAddress(uri);
-
-        SocketChannel channel = null;
-        try
-        {
-            channel = SocketChannel.open();
-            if (_bindAddress != null)
-                channel.socket().bind(_bindAddress);
-            channel.socket().setTcpNoDelay(true);
-
-            WebSocketFuture holder = new WebSocketFuture(websocket,uri,this,channel);
-
-            channel.configureBlocking(false);
-            channel.connect(address);
-            _factory.getSelectorManager().register(channel,holder);
-
-            return holder;
-        }
-        catch (RuntimeException e)
-        {
-            // close the channel (prevent connection leak)
-            IO.close(channel);
-            
-            // rethrow
-            throw e;
-        }
-        catch(IOException e)
-        {
-            // close the channel (prevent connection leak)
-            IO.close(channel);
-
-            // rethrow
-            throw e;
-        }
-    }
-
-    public static InetSocketAddress toSocketAddress(URI uri)
-    {
-        String scheme = uri.getScheme();
-        if (!("ws".equalsIgnoreCase(scheme) || "wss".equalsIgnoreCase(scheme)))
-            throw new IllegalArgumentException("Bad WebSocket scheme: " + scheme);
-        int port = uri.getPort();
-        if (port == 0)
-            throw new IllegalArgumentException("Bad WebSocket port: " + port);
-        if (port < 0)
-            port = "ws".equals(scheme) ? 80 : 443;
-
-        return new InetSocketAddress(uri.getHost(), port);
-    }
-
-    /* ------------------------------------------------------------ */
-    /** The Future Websocket Connection.
-     */
-    static class WebSocketFuture implements Future<WebSocket.Connection>
-    {
-        final WebSocket _websocket;
-        final URI _uri;
-        final WebSocketClient _client;
-        final CountDownLatch _done = new CountDownLatch(1);
-        ByteChannel _channel;
-        WebSocketConnection _connection;
-        Throwable _exception;
-
-        private WebSocketFuture(WebSocket websocket, URI uri, WebSocketClient client, ByteChannel channel)
-        {
-            _websocket=websocket;
-            _uri=uri;
-            _client=client;
-            _channel=channel;
-        }
-
-        public void onConnection(WebSocketConnection connection)
-        {
-            try
-            {
-                _client.getFactory().addConnection(connection);
-
-                connection.getConnection().setMaxTextMessageSize(_client.getMaxTextMessageSize());
-                connection.getConnection().setMaxBinaryMessageSize(_client.getMaxBinaryMessageSize());
-
-                WebSocketConnection con;
-                synchronized (this)
-                {
-                    if (_channel!=null)
-                        _connection=connection;
-                    con=_connection;
-                }
-
-                if (con!=null)
-                {
-                    if (_websocket instanceof WebSocket.OnFrame)
-                        ((WebSocket.OnFrame)_websocket).onHandshake((WebSocket.FrameConnection)con.getConnection());
-
-                    _websocket.onOpen(con.getConnection());
-                }
-            }
-            finally
-            {
-                _done.countDown();
-            }
-        }
-
-        public void handshakeFailed(Throwable ex)
-        {
-            try
-            {
-                ByteChannel channel=null;
-                synchronized (this)
-                {
-                    if (_channel!=null)
-                    {
-                        channel=_channel;
-                        _channel=null;
-                        _exception=ex;
-                    }
-                }
-
-                if (channel!=null)
-                {
-                    if (ex instanceof ProtocolException)
-                        closeChannel(channel,WebSocketConnectionRFC6455.CLOSE_PROTOCOL,ex.getMessage());
-                    else
-                        closeChannel(channel,WebSocketConnectionRFC6455.CLOSE_NO_CLOSE,ex.getMessage());
-                }
-            }
-            finally
-            {
-                _done.countDown();
-            }
-        }
-
-        public Map<String,String> getCookies()
-        {
-            return _client.getCookies();
-        }
-
-        public String getProtocol()
-        {
-            return _client.getProtocol();
-        }
-
-        public WebSocket getWebSocket()
-        {
-            return _websocket;
-        }
-
-        public URI getURI()
-        {
-            return _uri;
-        }
-
-        public int getMaxIdleTime()
-        {
-            return _client.getMaxIdleTime();
-        }
-
-        public String getOrigin()
-        {
-            return _client.getOrigin();
-        }
-
-        public MaskGen getMaskGen()
-        {
-            return _client.getMaskGen();
-        }
-
-        @Override
-        public String toString()
-        {
-            return "[" + _uri + ","+_websocket+"]@"+hashCode();
-        }
-
-        public boolean cancel(boolean mayInterruptIfRunning)
-        {
-            try
-            {
-                ByteChannel channel=null;
-                synchronized (this)
-                {
-                    if (_connection==null && _exception==null && _channel!=null)
-                    {
-                        channel=_channel;
-                        _channel=null;
-                    }
-                }
-
-                if (channel!=null)
-                {
-                    closeChannel(channel,WebSocketConnectionRFC6455.CLOSE_NO_CLOSE,"cancelled");
-                    return true;
-                }
-                return false;
-            }
-            finally
-            {
-                _done.countDown();
-            }
-        }
-
-        public boolean isCancelled()
-        {
-            synchronized (this)
-            {
-                return _channel==null && _connection==null;
-            }
-        }
-
-        public boolean isDone()
-        {
-            synchronized (this)
-            {
-                return _connection!=null && _exception==null;
-            }
-        }
-
-        public org.eclipse.jetty.websocket.WebSocket.Connection get() throws InterruptedException, ExecutionException
-        {
-            try
-            {
-                return get(Long.MAX_VALUE,TimeUnit.SECONDS);
-            }
-            catch(TimeoutException e)
-            {
-                throw new IllegalStateException("The universe has ended",e);
-            }
-        }
-
-        public org.eclipse.jetty.websocket.WebSocket.Connection get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException,
-                TimeoutException
-        {
-            _done.await(timeout,unit);
-            ByteChannel channel=null;
-            org.eclipse.jetty.websocket.WebSocket.Connection connection=null;
-            Throwable exception;
-            synchronized (this)
-            {
-                exception=_exception;
-                if (_connection==null)
-                {
-                    exception=_exception;
-                    channel=_channel;
-                    _channel=null;
-                }
-                else
-                    connection=_connection.getConnection();
-            }
-
-            if (channel!=null)
-                closeChannel(channel,WebSocketConnectionRFC6455.CLOSE_NO_CLOSE,"timeout");
-            if (exception!=null)
-                throw new ExecutionException(exception);
-            if (connection!=null)
-                return connection;
-            throw new TimeoutException();
-        }
-
-        private void closeChannel(ByteChannel channel,int code, String message)
-        {
-            try
-            {
-                _websocket.onClose(code,message);
-            }
-            catch(Exception e)
-            {
-                __log.warn(e);
-            }
-
-            try
-            {
-                channel.close();
-            }
-            catch(IOException e)
-            {
-                __log.debug(e);
-            }
-        }
-    }
-}
diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketClientFactory.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketClientFactory.java
deleted file mode 100644
index db10778..0000000
--- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketClientFactory.java
+++ /dev/null
@@ -1,596 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.websocket;
-
-import java.io.EOFException;
-import java.io.IOException;
-import java.net.ProtocolException;
-import java.nio.channels.SelectionKey;
-import java.nio.channels.SocketChannel;
-import java.util.List;
-import java.util.Map;
-import java.util.Queue;
-import java.util.Random;
-import java.util.concurrent.ConcurrentLinkedQueue;
-
-import javax.net.ssl.SSLEngine;
-
-import org.eclipse.jetty.http.HttpFields;
-import org.eclipse.jetty.http.HttpParser;
-import org.eclipse.jetty.io.AbstractConnection;
-import org.eclipse.jetty.io.AsyncEndPoint;
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.Buffers;
-import org.eclipse.jetty.io.ByteArrayBuffer;
-import org.eclipse.jetty.io.ConnectedEndPoint;
-import org.eclipse.jetty.io.Connection;
-import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.io.SimpleBuffers;
-import org.eclipse.jetty.io.nio.AsyncConnection;
-import org.eclipse.jetty.io.nio.SelectChannelEndPoint;
-import org.eclipse.jetty.io.nio.SelectorManager;
-import org.eclipse.jetty.io.nio.SslConnection;
-import org.eclipse.jetty.util.B64Code;
-import org.eclipse.jetty.util.QuotedStringTokenizer;
-import org.eclipse.jetty.util.component.AggregateLifeCycle;
-import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.util.ssl.SslContextFactory;
-import org.eclipse.jetty.util.thread.QueuedThreadPool;
-import org.eclipse.jetty.util.thread.ThreadPool;
-
-/* ------------------------------------------------------------ */
-/**
- * <p>WebSocketClientFactory contains the common components needed by multiple {@link WebSocketClient} instances
- * (for example, a {@link ThreadPool}, a {@link SelectorManager NIO selector}, etc).</p>
- * <p>WebSocketClients with different configurations should share the same factory to avoid to waste resources.</p>
- * <p>If a ThreadPool or MaskGen is passed in the constructor, then it is not added with {@link AggregateLifeCycle#addBean(Object)},
- * so it's lifecycle must be controlled externally.
- *
- * @see WebSocketClient
- */
-public class WebSocketClientFactory extends AggregateLifeCycle
-{
-    private final static Logger __log = org.eclipse.jetty.util.log.Log.getLogger(WebSocketClientFactory.class.getName());
-    private final static ByteArrayBuffer __ACCEPT = new ByteArrayBuffer.CaseInsensitive("Sec-WebSocket-Accept");
-    private final Queue<WebSocketConnection> connections = new ConcurrentLinkedQueue<WebSocketConnection>();
-    private final SslContextFactory _sslContextFactory = new SslContextFactory();
-    private final ThreadPool _threadPool;
-    private final WebSocketClientSelector _selector;
-    private MaskGen _maskGen;
-    private WebSocketBuffers _buffers;
-
-    /* ------------------------------------------------------------ */
-    /**
-     * <p>Creates a WebSocketClientFactory with the default configuration.</p>
-     */
-    public WebSocketClientFactory()
-    {
-        this(null);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * <p>Creates a WebSocketClientFactory with the given ThreadPool and the default configuration.</p>
-     *
-     * @param threadPool the ThreadPool instance to use
-     */
-    public WebSocketClientFactory(ThreadPool threadPool)
-    {
-        this(threadPool, new RandomMaskGen());
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * <p>Creates a WebSocketClientFactory with the given ThreadPool and the given MaskGen.</p>
-     *
-     * @param threadPool the ThreadPool instance to use
-     * @param maskGen    the MaskGen instance to use
-     */
-    public WebSocketClientFactory(ThreadPool threadPool, MaskGen maskGen)
-    {
-        this(threadPool, maskGen, 8192);
-    }
-
-    /* ------------------------------------------------------------ */
-
-    /**
-     * <p>Creates a WebSocketClientFactory with the specified configuration.</p>
-     *
-     * @param threadPool the ThreadPool instance to use
-     * @param maskGen    the mask generator to use
-     * @param bufferSize the read buffer size
-     */
-    public WebSocketClientFactory(ThreadPool threadPool, MaskGen maskGen, int bufferSize)
-    {
-        if (threadPool == null)
-            threadPool = new QueuedThreadPool();
-        _threadPool = threadPool;
-        addBean(_threadPool);
-
-        _buffers = new WebSocketBuffers(bufferSize);
-        addBean(_buffers);
-
-        _maskGen = maskGen;
-        addBean(_maskGen);
-
-        _selector = new WebSocketClientSelector();
-        addBean(_selector);
-
-        addBean(_sslContextFactory);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return the SslContextFactory used to configure SSL parameters
-     */
-    public SslContextFactory getSslContextFactory()
-    {
-        return _sslContextFactory;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Get the selectorManager. Used to configure the manager.
-     *
-     * @return The {@link SelectorManager} instance.
-     */
-    public SelectorManager getSelectorManager()
-    {
-        return _selector;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Get the ThreadPool.
-     * Used to set/query the thread pool configuration.
-     *
-     * @return The {@link ThreadPool}
-     */
-    public ThreadPool getThreadPool()
-    {
-        return _threadPool;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return the shared mask generator, or null if no shared mask generator is used
-     * @see WebSocketClient#getMaskGen()
-     */
-    public MaskGen getMaskGen()
-    {
-        return _maskGen;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param maskGen the shared mask generator, or null if no shared mask generator is used
-     * @see WebSocketClient#setMaskGen(MaskGen)
-     */
-    public void setMaskGen(MaskGen maskGen)
-    {
-        if (isRunning())
-            throw new IllegalStateException(getState());
-        removeBean(_maskGen);
-        _maskGen = maskGen;
-        addBean(maskGen);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param bufferSize the read buffer size
-     * @see #getBufferSize()
-     */
-    public void setBufferSize(int bufferSize)
-    {
-        if (isRunning())
-            throw new IllegalStateException(getState());
-        removeBean(_buffers);
-        _buffers = new WebSocketBuffers(bufferSize);
-        addBean(_buffers);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return the read buffer size
-     */
-    public int getBufferSize()
-    {
-        return _buffers.getBufferSize();
-    }
-
-    @Override
-    protected void doStop() throws Exception
-    {
-        closeConnections();
-        super.doStop();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * <p>Creates and returns a new instance of a {@link WebSocketClient}, configured with this
-     * WebSocketClientFactory instance.</p>
-     *
-     * @return a new {@link WebSocketClient} instance
-     */
-    public WebSocketClient newWebSocketClient()
-    {
-        return new WebSocketClient(this);
-    }
-
-    protected SSLEngine newSslEngine(SocketChannel channel) throws IOException
-    {
-        SSLEngine sslEngine;
-        if (channel != null)
-        {
-            String peerHost = channel.socket().getInetAddress().getHostAddress();
-            int peerPort = channel.socket().getPort();
-            sslEngine = _sslContextFactory.newSslEngine(peerHost, peerPort);
-        }
-        else
-        {
-            sslEngine = _sslContextFactory.newSslEngine();
-        }
-        sslEngine.setUseClientMode(true);
-        sslEngine.beginHandshake();
-
-        return sslEngine;
-    }
-
-    protected boolean addConnection(WebSocketConnection connection)
-    {
-        return isRunning() && connections.add(connection);
-    }
-
-    protected boolean removeConnection(WebSocketConnection connection)
-    {
-        return connections.remove(connection);
-    }
-
-    protected void closeConnections()
-    {
-        for (WebSocketConnection connection : connections)
-            connection.shutdown();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * WebSocket Client Selector Manager
-     */
-    class WebSocketClientSelector extends SelectorManager
-    {
-        @Override
-        public boolean dispatch(Runnable task)
-        {
-            return _threadPool.dispatch(task);
-        }
-
-        @Override
-        protected SelectChannelEndPoint newEndPoint(SocketChannel channel, SelectSet selectSet, final SelectionKey key) throws IOException
-        {
-            WebSocketClient.WebSocketFuture holder = (WebSocketClient.WebSocketFuture)key.attachment();
-            int maxIdleTime = holder.getMaxIdleTime();
-            if (maxIdleTime < 0)
-                maxIdleTime = (int)getMaxIdleTime();
-            SelectChannelEndPoint result = new SelectChannelEndPoint(channel, selectSet, key, maxIdleTime);
-            AsyncEndPoint endPoint = result;
-
-            // Detect if it is SSL, and wrap the connection if so
-            if ("wss".equals(holder.getURI().getScheme()))
-            {
-                SSLEngine sslEngine = newSslEngine(channel);
-                SslConnection sslConnection = new SslConnection(sslEngine, endPoint);
-                endPoint.setConnection(sslConnection);
-                endPoint = sslConnection.getSslEndPoint();
-            }
-
-            AsyncConnection connection = selectSet.getManager().newConnection(channel, endPoint, holder);
-            endPoint.setConnection(connection);
-
-            return result;
-        }
-
-        @Override
-        public AsyncConnection newConnection(SocketChannel channel, AsyncEndPoint endpoint, Object attachment)
-        {
-            WebSocketClient.WebSocketFuture holder = (WebSocketClient.WebSocketFuture)attachment;
-            return new HandshakeConnection(endpoint, holder);
-        }
-
-        @Override
-        protected void endPointOpened(SelectChannelEndPoint endpoint)
-        {
-            // TODO expose on outer class ??
-        }
-
-        @Override
-        protected void endPointUpgraded(ConnectedEndPoint endpoint, Connection oldConnection)
-        {
-            LOG.debug("upgrade {} -> {}", oldConnection, endpoint.getConnection());
-        }
-
-        @Override
-        protected void endPointClosed(SelectChannelEndPoint endpoint)
-        {
-            endpoint.getConnection().onClose();
-        }
-
-        @Override
-        protected void connectionFailed(SocketChannel channel, Throwable ex, Object attachment)
-        {
-            if (!(attachment instanceof WebSocketClient.WebSocketFuture))
-                super.connectionFailed(channel, ex, attachment);
-            else
-            {
-                __log.debug(ex);
-                WebSocketClient.WebSocketFuture future = (WebSocketClient.WebSocketFuture)attachment;
-
-                future.handshakeFailed(ex);
-            }
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Handshake Connection.
-     * Handles the connection until the handshake succeeds or fails.
-     */
-    class HandshakeConnection extends AbstractConnection implements AsyncConnection
-    {
-        private final AsyncEndPoint _endp;
-        private final WebSocketClient.WebSocketFuture _future;
-        private final String _key;
-        private final HttpParser _parser;
-        private String _accept;
-        private String _error;
-        private ByteArrayBuffer _handshake;
-
-        public HandshakeConnection(AsyncEndPoint endpoint, WebSocketClient.WebSocketFuture future)
-        {
-            super(endpoint, System.currentTimeMillis());
-            _endp = endpoint;
-            _future = future;
-
-            byte[] bytes = new byte[16];
-            new Random().nextBytes(bytes);
-            _key = new String(B64Code.encode(bytes));
-
-            Buffers buffers = new SimpleBuffers(_buffers.getBuffer(), null);
-            _parser = new HttpParser(buffers, _endp, new HttpParser.EventHandler()
-            {
-                @Override
-                public void startResponse(Buffer version, int status, Buffer reason) throws IOException
-                {
-                    if (status != 101)
-                    {
-                        _error = "Bad response status " + status + " " + reason;
-                        _endp.close();
-                    }
-                }
-
-                @Override
-                public void parsedHeader(Buffer name, Buffer value) throws IOException
-                {
-                    if (__ACCEPT.equals(name))
-                        _accept = value.toString();
-                }
-
-                @Override // TODO simone says shouldn't be needed
-                public void startRequest(Buffer method, Buffer url, Buffer version) throws IOException
-                {
-                    if (_error == null)
-                        _error = "Bad response: " + method + " " + url + " " + version;
-                    _endp.close();
-                }
-
-                @Override // TODO simone says shouldn't be needed
-                public void content(Buffer ref) throws IOException
-                {
-                    if (_error == null)
-                        _error = "Bad response. " + ref.length() + "B of content?";
-                    _endp.close();
-                }
-            });
-        }
-
-        private boolean handshake()
-        {
-            if (_handshake==null)
-            {
-                String path = _future.getURI().getPath();
-                if (path == null || path.length() == 0)
-                    path = "/";
-
-                if (_future.getURI().getRawQuery() != null)
-                    path += "?" + _future.getURI().getRawQuery();
-
-                String origin = _future.getOrigin();
-
-                StringBuilder request = new StringBuilder(512);
-                request.append("GET ").append(path).append(" HTTP/1.1\r\n")
-                .append("Host: ").append(_future.getURI().getHost()).append(":");
-                
-                int port = _future.getURI().getPort();
-                if (port <= 0)
-                {
-                    // fix it
-                    String scheme = _future.getURI().getScheme();
-
-                    if ("ws".equalsIgnoreCase(scheme))
-                    {
-                        port = 80;
-                    }
-                    else if ("wss".equalsIgnoreCase(scheme))
-                    {
-                        port = 443;
-                    }
-                    else
-                    {
-                        throw new RuntimeException("No valid port provided for scheme [" + scheme + "]");
-                    }
-                }
-
-                request.append(port).append("\r\n");
-                
-                request.append("Upgrade: websocket\r\n")
-                .append("Connection: Upgrade\r\n")
-                .append("Sec-WebSocket-Key: ")
-                .append(_key).append("\r\n");
-
-                if (origin != null)
-                    request.append("Origin: ").append(origin).append("\r\n");
-
-                request.append("Sec-WebSocket-Version: ").append(WebSocketConnectionRFC6455.VERSION).append("\r\n");
-
-                if (_future.getProtocol() != null)
-                    request.append("Sec-WebSocket-Protocol: ").append(_future.getProtocol()).append("\r\n");
-
-                Map<String, String> cookies = _future.getCookies();
-                if (cookies != null && cookies.size() > 0)
-                {
-                    for (String cookie : cookies.keySet())
-                        request.append("Cookie: ")
-                        .append(QuotedStringTokenizer.quoteIfNeeded(cookie, HttpFields.__COOKIE_DELIM))
-                        .append("=")
-                        .append(QuotedStringTokenizer.quoteIfNeeded(cookies.get(cookie), HttpFields.__COOKIE_DELIM))
-                        .append("\r\n");
-                }
-
-                request.append("\r\n");
-
-                _handshake=new ByteArrayBuffer(request.toString(), false);
-            }
-            
-            // TODO extensions
-
-            try
-            {
-                int len = _handshake.length();
-                int flushed = _endp.flush(_handshake);
-                if (flushed<0)
-                    throw new IOException("incomplete handshake");
-            }
-            catch (IOException e)
-            {
-                _future.handshakeFailed(e);
-            }
-            return _handshake.length()==0;
-        }
-
-        public Connection handle() throws IOException
-        {
-            while (_endp.isOpen() && !_parser.isComplete())
-            {
-                if (_handshake==null || _handshake.length()>0)
-                    if (!handshake())
-                        return this;
-
-                if (!_parser.parseAvailable())
-                {
-                    if (_endp.isInputShutdown())
-                        _future.handshakeFailed(new IOException("Incomplete handshake response"));
-                    return this;
-                }
-            }
-            if (_error == null)
-            {
-                if (_accept == null)
-                {
-                    _error = "No Sec-WebSocket-Accept";
-                }
-                else if (!WebSocketConnectionRFC6455.hashKey(_key).equals(_accept))
-                {
-                    _error = "Bad Sec-WebSocket-Accept";
-                }
-                else
-                {
-                    WebSocketConnection connection = newWebSocketConnection();
-
-                    Buffer header = _parser.getHeaderBuffer();
-                    if (header.hasContent())
-                        connection.fillBuffersFrom(header);
-                    _buffers.returnBuffer(header);
-
-                    _future.onConnection(connection);
-
-                    return connection;
-                }
-            }
-
-            _endp.close();
-            return this;
-        }
-
-        private WebSocketConnection newWebSocketConnection() throws IOException
-        {
-            __log.debug("newWebSocketConnection()");
-            return new WebSocketClientConnection(
-                    _future._client.getFactory(),
-                    _future.getWebSocket(),
-                    _endp,
-                    _buffers,
-                    System.currentTimeMillis(),
-                    _future.getMaxIdleTime(),
-                    _future.getProtocol(),
-                    null,
-                    WebSocketConnectionRFC6455.VERSION,
-                    _future.getMaskGen());
-        }
-
-        public void onInputShutdown() throws IOException
-        {
-            _endp.close();
-        }
-
-        public boolean isIdle()
-        {
-            return false;
-        }
-
-        public boolean isSuspended()
-        {
-            return false;
-        }
-
-        public void onClose()
-        {
-            if (_error != null)
-                _future.handshakeFailed(new ProtocolException(_error));
-            else
-                _future.handshakeFailed(new EOFException());
-        }
-    }
-
-    private static class WebSocketClientConnection extends WebSocketConnectionRFC6455
-    {
-        private final WebSocketClientFactory factory;
-
-        public WebSocketClientConnection(WebSocketClientFactory factory, WebSocket webSocket, EndPoint endPoint, WebSocketBuffers buffers, long timeStamp, int maxIdleTime, String protocol, List<Extension> extensions, int draftVersion, MaskGen maskGen) throws IOException
-        {
-            super(webSocket, endPoint, buffers, timeStamp, maxIdleTime, protocol, extensions, draftVersion, maskGen);
-            this.factory = factory;
-        }
-
-        @Override
-        public void onClose()
-        {
-            super.onClose();
-            factory.removeConnection(this);
-        }
-    }
-}
diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketConnection.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketConnection.java
deleted file mode 100644
index 76385f7..0000000
--- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketConnection.java
+++ /dev/null
@@ -1,36 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.websocket;
-
-
-import java.util.List;
-
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.nio.AsyncConnection;
-
-public interface WebSocketConnection extends AsyncConnection
-{
-    void fillBuffersFrom(Buffer buffer);
-
-    List<Extension> getExtensions();
-
-    WebSocket.Connection getConnection();
-
-    void shutdown();
-}
diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketConnectionD00.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketConnectionD00.java
deleted file mode 100644
index 4858bab..0000000
--- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketConnectionD00.java
+++ /dev/null
@@ -1,514 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.websocket;
-
-import java.io.IOException;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.util.Collections;
-import java.util.List;
-
-import org.eclipse.jetty.io.AbstractConnection;
-import org.eclipse.jetty.io.AsyncEndPoint;
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.ByteArrayBuffer;
-import org.eclipse.jetty.io.Connection;
-import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.io.nio.IndirectNIOBuffer;
-import org.eclipse.jetty.util.StringUtil;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.websocket.WebSocket.OnFrame;
-
-public class WebSocketConnectionD00 extends AbstractConnection implements WebSocketConnection, WebSocket.FrameConnection
-{
-    private static final Logger LOG = Log.getLogger(WebSocketConnectionD00.class);
-
-    public final static byte LENGTH_FRAME=(byte)0x80;
-    public final static byte SENTINEL_FRAME=(byte)0x00;
-
-    private final WebSocketParser _parser;
-    private final WebSocketGenerator _generator;
-    private final WebSocket _websocket;
-    private final String _protocol;
-    private String _key1;
-    private String _key2;
-    private ByteArrayBuffer _hixieBytes;
-
-    public WebSocketConnectionD00(WebSocket websocket, EndPoint endpoint, WebSocketBuffers buffers, long timestamp, int maxIdleTime, String protocol)
-        throws IOException
-    {
-        super(endpoint,timestamp);
-
-        _endp.setMaxIdleTime(maxIdleTime);
-
-        _websocket = websocket;
-        _protocol=protocol;
-
-        _generator = new WebSocketGeneratorD00(buffers, _endp);
-        _parser = new WebSocketParserD00(buffers, endpoint, new FrameHandlerD00(_websocket));
-    }
-
-    /* ------------------------------------------------------------ */
-    public org.eclipse.jetty.websocket.WebSocket.Connection getConnection()
-    {
-        return this;
-    }
-
-
-    /* ------------------------------------------------------------ */
-    public void setHixieKeys(String key1,String key2)
-    {
-        _key1=key1;
-        _key2=key2;
-        _hixieBytes=new IndirectNIOBuffer(16);
-    }
-
-    /* ------------------------------------------------------------ */
-    public Connection handle() throws IOException
-    {
-        try
-        {
-            // handle stupid hixie random bytes
-            if (_hixieBytes!=null)
-            {
-
-                // take any available bytes from the parser buffer, which may have already been read
-                Buffer buffer=_parser.getBuffer();
-                if (buffer!=null && buffer.length()>0)
-                {
-                    int l=buffer.length();
-                    if (l>(8-_hixieBytes.length()))
-                        l=8-_hixieBytes.length();
-                    _hixieBytes.put(buffer.peek(buffer.getIndex(),l));
-                    buffer.skip(l);
-                }
-
-                // while we are not blocked
-                while(_endp.isOpen())
-                {
-                    // do we now have enough
-                    if (_hixieBytes.length()==8)
-                    {
-                        // we have the silly random bytes
-                        // so let's work out the stupid 16 byte reply.
-                        doTheHixieHixieShake();
-                        _endp.flush(_hixieBytes);
-                        _hixieBytes=null;
-                        _endp.flush();
-                        break;
-                    }
-
-                    // no, then let's fill
-                    int filled=_endp.fill(_hixieBytes);
-                    if (filled<0)
-                    {
-                        _endp.flush();
-                        _endp.close();
-                        break;
-                    }
-                    else if (filled==0)
-                        return this;
-                }
-
-                if (_websocket instanceof OnFrame)
-                    ((OnFrame)_websocket).onHandshake(this);
-                _websocket.onOpen(this);
-                return this;
-            }
-
-            // handle the framing protocol
-            boolean progress=true;
-
-            while (progress)
-            {
-                int flushed=_generator.flush();
-                int filled=_parser.parseNext();
-
-                progress = flushed>0 || filled>0;
-
-                _endp.flush();
-
-                if (_endp instanceof AsyncEndPoint && ((AsyncEndPoint)_endp).hasProgressed())
-                    progress=true;
-            }
-        }
-        catch(IOException e)
-        {
-            LOG.debug(e);
-            try
-            {
-                if (_endp.isOpen())
-                    _endp.close();
-            }
-            catch(IOException e2)
-            {
-                LOG.ignore(e2);
-            }
-            throw e;
-        }
-        finally
-        {
-            if (_endp.isOpen())
-            {
-                if (_endp.isInputShutdown() && _generator.isBufferEmpty())
-                    _endp.close();
-                else
-                    checkWriteable();
-
-                checkWriteable();
-            }
-        }
-        return this;
-    }
-
-    /* ------------------------------------------------------------ */
-    public void onInputShutdown() throws IOException
-    {
-        // TODO
-    }
-
-    /* ------------------------------------------------------------ */
-    private void doTheHixieHixieShake()
-    {
-        byte[] result=WebSocketConnectionD00.doTheHixieHixieShake(
-                WebSocketConnectionD00.hixieCrypt(_key1),
-                WebSocketConnectionD00.hixieCrypt(_key2),
-                _hixieBytes.asArray());
-        _hixieBytes.clear();
-        _hixieBytes.put(result);
-    }
-
-    /* ------------------------------------------------------------ */
-    public boolean isOpen()
-    {
-        return _endp!=null&&_endp.isOpen();
-    }
-
-    /* ------------------------------------------------------------ */
-    public boolean isIdle()
-    {
-        return _parser.isBufferEmpty() && _generator.isBufferEmpty();
-    }
-
-    /* ------------------------------------------------------------ */
-    public boolean isSuspended()
-    {
-        return false;
-    }
-
-    /* ------------------------------------------------------------ */
-    public void onClose()
-    {
-        _websocket.onClose(WebSocketConnectionD06.CLOSE_NORMAL,"");
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     */
-    public void sendMessage(String content) throws IOException
-    {
-        byte[] data = content.getBytes(StringUtil.__UTF8);
-        _generator.addFrame((byte)0,SENTINEL_FRAME,data,0,data.length);
-        _generator.flush();
-        checkWriteable();
-    }
-
-    /* ------------------------------------------------------------ */
-    public void sendMessage(byte[] data, int offset, int length) throws IOException
-    {
-        _generator.addFrame((byte)0,LENGTH_FRAME,data,offset,length);
-        _generator.flush();
-        checkWriteable();
-    }
-
-    /* ------------------------------------------------------------ */
-    public boolean isMore(byte flags)
-    {
-        return (flags&0x8) != 0;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * {@inheritDoc}
-     */
-    public void sendControl(byte code, byte[] content, int offset, int length) throws IOException
-    {
-    }
-
-    /* ------------------------------------------------------------ */
-    public void sendFrame(byte flags,byte opcode, byte[] content, int offset, int length) throws IOException
-    {
-        _generator.addFrame((byte)0,opcode,content,offset,length);
-        _generator.flush();
-        checkWriteable();
-    }
-
-    /* ------------------------------------------------------------ */
-    public void close(int code, String message)
-    {
-        throw new UnsupportedOperationException();
-    }
-
-    /* ------------------------------------------------------------ */
-    public void disconnect()
-    {
-        close();
-    }
-
-    /* ------------------------------------------------------------ */
-    public void close()
-    {
-        try
-        {
-            _generator.flush();
-            _endp.close();
-        }
-        catch(IOException e)
-        {
-            LOG.ignore(e);
-        }
-    }
-
-    public void shutdown()
-    {
-        close();
-    }
-
-    /* ------------------------------------------------------------ */
-    public void fillBuffersFrom(Buffer buffer)
-    {
-        _parser.fill(buffer);
-    }
-
-
-    /* ------------------------------------------------------------ */
-    private void checkWriteable()
-    {
-        if (!_generator.isBufferEmpty() && _endp instanceof AsyncEndPoint)
-            ((AsyncEndPoint)_endp).scheduleWrite();
-    }
-
-    /* ------------------------------------------------------------ */
-    static long hixieCrypt(String key)
-    {
-        // Don't ask me what all this is about.
-        // I think it's pretend secret stuff, kind of
-        // like talking in pig latin!
-        long number=0;
-        int spaces=0;
-        for (char c : key.toCharArray())
-        {
-            if (Character.isDigit(c))
-                number=number*10+(c-'0');
-            else if (c==' ')
-                spaces++;
-        }
-        return number/spaces;
-    }
-
-    public static byte[] doTheHixieHixieShake(long key1,long key2,byte[] key3)
-    {
-        try
-        {
-            MessageDigest md = MessageDigest.getInstance("MD5");
-            byte [] fodder = new byte[16];
-
-            fodder[0]=(byte)(0xff&(key1>>24));
-            fodder[1]=(byte)(0xff&(key1>>16));
-            fodder[2]=(byte)(0xff&(key1>>8));
-            fodder[3]=(byte)(0xff&key1);
-            fodder[4]=(byte)(0xff&(key2>>24));
-            fodder[5]=(byte)(0xff&(key2>>16));
-            fodder[6]=(byte)(0xff&(key2>>8));
-            fodder[7]=(byte)(0xff&key2);
-            System.arraycopy(key3, 0, fodder, 8, 8);
-            md.update(fodder);
-            return md.digest();
-        }
-        catch (NoSuchAlgorithmException e)
-        {
-            throw new IllegalStateException(e);
-        }
-    }
-
-    public void setMaxTextMessageSize(int size)
-    {
-    }
-
-    public void setMaxIdleTime(int ms)
-    {
-        try
-        {
-            _endp.setMaxIdleTime(ms);
-        }
-        catch(IOException e)
-        {
-            LOG.warn(e);
-        }
-    }
-
-    public void setMaxBinaryMessageSize(int size)
-    {
-    }
-
-    public int getMaxTextMessageSize()
-    {
-        return -1;
-    }
-
-    public int getMaxIdleTime()
-    {
-        return _endp.getMaxIdleTime();
-    }
-
-    public int getMaxBinaryMessageSize()
-    {
-        return -1;
-    }
-
-    public String getProtocol()
-    {
-        return _protocol;
-    }
-
-    protected void onFrameHandshake()
-    {
-        if (_websocket instanceof OnFrame)
-        {
-            ((OnFrame)_websocket).onHandshake(this);
-        }
-    }
-
-    protected void onWebsocketOpen()
-    {
-        _websocket.onOpen(this);
-    }
-
-    static class FrameHandlerD00 implements WebSocketParser.FrameHandler
-    {
-        final WebSocket _websocket;
-
-        FrameHandlerD00(WebSocket websocket)
-        {
-            _websocket=websocket;
-        }
-
-        public void onFrame(byte flags, byte opcode, Buffer buffer)
-        {
-            try
-            {
-                byte[] array=buffer.array();
-
-                if (opcode==0)
-                {
-                    if (_websocket instanceof WebSocket.OnTextMessage)
-                        ((WebSocket.OnTextMessage)_websocket).onMessage(buffer.toString(StringUtil.__UTF8));
-                }
-                else
-                {
-                    if (_websocket instanceof WebSocket.OnBinaryMessage)
-                        ((WebSocket.OnBinaryMessage)_websocket).onMessage(array,buffer.getIndex(),buffer.length());
-                }
-            }
-            catch(Throwable th)
-            {
-                LOG.warn(th);
-            }
-        }
-
-        public void close(int code,String message)
-        {
-        }
-    }
-
-    public boolean isMessageComplete(byte flags)
-    {
-        return true;
-    }
-
-    public byte binaryOpcode()
-    {
-        return LENGTH_FRAME;
-    }
-
-    public byte textOpcode()
-    {
-        return SENTINEL_FRAME;
-    }
-
-    public boolean isControl(byte opcode)
-    {
-        return false;
-    }
-
-    public boolean isText(byte opcode)
-    {
-        return (opcode&LENGTH_FRAME)==0;
-    }
-
-    public boolean isBinary(byte opcode)
-    {
-        return (opcode&LENGTH_FRAME)!=0;
-    }
-
-    public boolean isContinuation(byte opcode)
-    {
-        return false;
-    }
-
-    public boolean isClose(byte opcode)
-    {
-        return false;
-    }
-
-    public boolean isPing(byte opcode)
-    {
-        return false;
-    }
-
-    public boolean isPong(byte opcode)
-    {
-        return false;
-    }
-
-    public List<Extension> getExtensions()
-    {
-        return Collections.emptyList();
-    }
-
-    public byte continuationOpcode()
-    {
-        return 0;
-    }
-
-    public byte finMask()
-    {
-        return 0;
-    }
-
-    public void setAllowFrameFragmentation(boolean allowFragmentation)
-    {
-    }
-
-    public boolean isAllowFrameFragmentation()
-    {
-        return false;
-    }
-}
diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketConnectionD06.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketConnectionD06.java
deleted file mode 100644
index d981e1a..0000000
--- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketConnectionD06.java
+++ /dev/null
@@ -1,734 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.websocket;
-
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.security.MessageDigest;
-import java.util.Collections;
-import java.util.List;
-
-import org.eclipse.jetty.io.AbstractConnection;
-import org.eclipse.jetty.io.AsyncEndPoint;
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.ByteArrayBuffer;
-import org.eclipse.jetty.io.Connection;
-import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.util.B64Code;
-import org.eclipse.jetty.util.StringUtil;
-import org.eclipse.jetty.util.Utf8StringBuilder;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.websocket.WebSocket.OnBinaryMessage;
-import org.eclipse.jetty.websocket.WebSocket.OnControl;
-import org.eclipse.jetty.websocket.WebSocket.OnFrame;
-import org.eclipse.jetty.websocket.WebSocket.OnTextMessage;
-
-public class WebSocketConnectionD06 extends AbstractConnection implements WebSocketConnection
-{
-    private static final Logger LOG = Log.getLogger(WebSocketConnectionD06.class);
-
-    final static byte OP_CONTINUATION = 0x00;
-    final static byte OP_CLOSE = 0x01;
-    final static byte OP_PING = 0x02;
-    final static byte OP_PONG = 0x03;
-    final static byte OP_TEXT = 0x04;
-    final static byte OP_BINARY = 0x05;
-
-    final static int CLOSE_NORMAL=1000;
-    final static int CLOSE_SHUTDOWN=1001;
-    final static int CLOSE_PROTOCOL=1002;
-    final static int CLOSE_BADDATA=1003;
-    final static int CLOSE_LARGE=1004;
-
-    static boolean isLastFrame(int flags)
-    {
-        return (flags&0x8)!=0;
-    }
-
-    static boolean isControlFrame(int opcode)
-    {
-        switch(opcode)
-        {
-            case OP_CLOSE:
-            case OP_PING:
-            case OP_PONG:
-                return true;
-            default:
-                return false;
-        }
-    }
-
-    private final static byte[] MAGIC;
-    private final WebSocketParser _parser;
-    private final WebSocketGenerator _generator;
-    private final WebSocket _webSocket;
-    private final OnFrame _onFrame;
-    private final OnBinaryMessage _onBinaryMessage;
-    private final OnTextMessage _onTextMessage;
-    private final OnControl _onControl;
-    private final String _protocol;
-    private volatile boolean _closedIn;
-    private volatile boolean _closedOut;
-    private int _maxTextMessageSize;
-    private int _maxBinaryMessageSize=-1;
-
-    static
-    {
-        try
-        {
-            MAGIC="258EAFA5-E914-47DA-95CA-C5AB0DC85B11".getBytes(StringUtil.__ISO_8859_1);
-        }
-        catch (UnsupportedEncodingException e)
-        {
-            throw new RuntimeException(e);
-        }
-    }
-
-    private final WebSocketParser.FrameHandler _frameHandler= new FrameHandlerD06();
-    private final WebSocket.FrameConnection _connection = new FrameConnectionD06();
-
-
-    /* ------------------------------------------------------------ */
-    public WebSocketConnectionD06(WebSocket websocket, EndPoint endpoint, WebSocketBuffers buffers, long timestamp, int maxIdleTime, String protocol)
-        throws IOException
-    {
-        super(endpoint,timestamp);
-
-        _endp.setMaxIdleTime(maxIdleTime);
-
-        _webSocket = websocket;
-        _onFrame=_webSocket instanceof OnFrame ? (OnFrame)_webSocket : null;
-        _onTextMessage=_webSocket instanceof OnTextMessage ? (OnTextMessage)_webSocket : null;
-        _onBinaryMessage=_webSocket instanceof OnBinaryMessage ? (OnBinaryMessage)_webSocket : null;
-        _onControl=_webSocket instanceof OnControl ? (OnControl)_webSocket : null;
-        _generator = new WebSocketGeneratorD06(buffers, _endp,null);
-        _parser = new WebSocketParserD06(buffers, endpoint, _frameHandler,true);
-        _protocol=protocol;
-
-        _maxTextMessageSize=buffers.getBufferSize();
-        _maxBinaryMessageSize=-1;
-    }
-
-    /* ------------------------------------------------------------ */
-    public WebSocket.Connection getConnection()
-    {
-        return _connection;
-    }
-
-    /* ------------------------------------------------------------ */
-    public Connection handle() throws IOException
-    {
-        try
-        {
-            // handle the framing protocol
-            boolean progress=true;
-
-            while (progress)
-            {
-                int flushed=_generator.flush();
-                int filled=_parser.parseNext();
-
-                progress = flushed>0 || filled>0;
-
-                if (filled<0 || flushed<0)
-                {
-                    _endp.close();
-                    break;
-                }
-            }
-        }
-        catch(IOException e)
-        {
-            try
-            {
-                _endp.close();
-            }
-            catch(IOException e2)
-            {
-                LOG.ignore(e2);
-            }
-            throw e;
-        }
-        finally
-        {
-            if (_endp.isOpen())
-            {
-                if (_closedIn && _closedOut && _generator.isBufferEmpty())
-                    _endp.close();
-                else if (_endp.isInputShutdown() && !_closedIn)
-                    closeIn(CLOSE_PROTOCOL,null);
-                else
-                    checkWriteable();
-            }
-
-        }
-        return this;
-    }
-
-    /* ------------------------------------------------------------ */
-    public void onInputShutdown() throws IOException
-    {
-        // TODO
-    }
-
-    /* ------------------------------------------------------------ */
-    public boolean isIdle()
-    {
-        return _parser.isBufferEmpty() && _generator.isBufferEmpty();
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public void onIdleExpired(long idleForMs)
-    {
-        closeOut(WebSocketConnectionD06.CLOSE_NORMAL,"Idle");
-    }
-
-    /* ------------------------------------------------------------ */
-    public boolean isSuspended()
-    {
-        return false;
-    }
-
-    /* ------------------------------------------------------------ */
-    public void onClose()
-    {
-        _webSocket.onClose(WebSocketConnectionD06.CLOSE_NORMAL,"");
-    }
-
-    /* ------------------------------------------------------------ */
-    public synchronized void closeIn(int code,String message)
-    {
-        LOG.debug("ClosedIn {} {}",this,message);
-        try
-        {
-            if (_closedOut)
-                _endp.close();
-            else
-                closeOut(code,message);
-        }
-        catch(IOException e)
-        {
-            LOG.ignore(e);
-        }
-        finally
-        {
-            _closedIn=true;
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    public synchronized void closeOut(int code,String message)
-    {
-        LOG.debug("ClosedOut {} {}",this,message);
-        try
-        {
-            if (_closedIn || _closedOut)
-                _endp.close();
-            else
-            {
-                if (code<=0)
-                    code=WebSocketConnectionD06.CLOSE_NORMAL;
-                byte[] bytes = ("xx"+(message==null?"":message)).getBytes(StringUtil.__ISO_8859_1);
-                bytes[0]=(byte)(code/0x100);
-                bytes[1]=(byte)(code%0x100);
-                _generator.addFrame((byte)0x8,WebSocketConnectionD06.OP_CLOSE,bytes,0,bytes.length);
-            }
-            _generator.flush();
-
-        }
-        catch(IOException e)
-        {
-            LOG.ignore(e);
-        }
-        finally
-        {
-            _closedOut=true;
-        }
-    }
-
-    public void shutdown()
-    {
-        final WebSocket.Connection connection = _connection;
-        if (connection != null)
-            connection.close(CLOSE_SHUTDOWN, null);
-    }
-
-    /* ------------------------------------------------------------ */
-    public void fillBuffersFrom(Buffer buffer)
-    {
-        _parser.fill(buffer);
-    }
-
-    /* ------------------------------------------------------------ */
-    private void checkWriteable()
-    {
-        if (!_generator.isBufferEmpty() && _endp instanceof AsyncEndPoint)
-        {
-            ((AsyncEndPoint)_endp).scheduleWrite();
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    public List<Extension> getExtensions()
-    {
-        return Collections.emptyList();
-    }
-
-    protected void onFrameHandshake()
-    {
-        if (_onFrame!=null)
-        {
-            _onFrame.onHandshake(_connection);
-        }
-    }
-
-    protected void onWebSocketOpen()
-    {
-        _webSocket.onOpen(_connection);
-    }
-
-    /* ------------------------------------------------------------ */
-    private class FrameConnectionD06 implements WebSocket.FrameConnection
-    {
-        volatile boolean _disconnecting;
-        int _maxTextMessage=WebSocketConnectionD06.this._maxTextMessageSize;
-        int _maxBinaryMessage=WebSocketConnectionD06.this._maxBinaryMessageSize;
-
-        /* ------------------------------------------------------------ */
-        public synchronized void sendMessage(String content) throws IOException
-        {
-            if (_closedOut)
-                throw new IOException("closing");
-            byte[] data = content.getBytes(StringUtil.__UTF8);
-            _generator.addFrame((byte)0x8,WebSocketConnectionD06.OP_TEXT,data,0,data.length);
-            _generator.flush();
-            checkWriteable();
-        }
-
-        /* ------------------------------------------------------------ */
-        public synchronized void sendMessage(byte[] content, int offset, int length) throws IOException
-        {
-            if (_closedOut)
-                throw new IOException("closing");
-            _generator.addFrame((byte)0x8,WebSocketConnectionD06.OP_BINARY,content,offset,length);
-            _generator.flush();
-            checkWriteable();
-        }
-
-        /* ------------------------------------------------------------ */
-        public void sendFrame(byte flags,byte opcode, byte[] content, int offset, int length) throws IOException
-        {
-            if (_closedOut)
-                throw new IOException("closing");
-            _generator.addFrame(flags,opcode,content,offset,length);
-            _generator.flush();
-            checkWriteable();
-        }
-
-        /* ------------------------------------------------------------ */
-        public void sendControl(byte control, byte[] data, int offset, int length) throws IOException
-        {
-            if (_closedOut)
-                throw new IOException("closing");
-            _generator.addFrame((byte)0x8,control,data,offset,length);
-            _generator.flush();
-            checkWriteable();
-        }
-
-        /* ------------------------------------------------------------ */
-        public boolean isMessageComplete(byte flags)
-        {
-            return isLastFrame(flags);
-        }
-
-        /* ------------------------------------------------------------ */
-        public boolean isOpen()
-        {
-            return _endp!=null&&_endp.isOpen();
-        }
-
-        /* ------------------------------------------------------------ */
-        public void close(int code, String message)
-        {
-            if (_disconnecting)
-                return;
-            _disconnecting=true;
-            WebSocketConnectionD06.this.closeOut(code,message);
-        }
-
-        /* ------------------------------------------------------------ */
-        public void setMaxIdleTime(int ms)
-        {
-            try
-            {
-                _endp.setMaxIdleTime(ms);
-            }
-            catch(IOException e)
-            {
-                LOG.warn(e);
-            }
-        }
-
-        /* ------------------------------------------------------------ */
-        public void setMaxTextMessageSize(int size)
-        {
-            _maxTextMessage=size;
-        }
-
-        /* ------------------------------------------------------------ */
-        public void setMaxBinaryMessageSize(int size)
-        {
-            _maxBinaryMessage=size;
-        }
-
-        /* ------------------------------------------------------------ */
-        public int getMaxTextMessageSize()
-        {
-            return _maxTextMessage;
-        }
-
-        /* ------------------------------------------------------------ */
-        public int getMaxIdleTime()
-        {
-            return _endp.getMaxIdleTime();
-        }
-
-        /* ------------------------------------------------------------ */
-        public int getMaxBinaryMessageSize()
-        {
-            return _maxBinaryMessage;
-        }
-
-        /* ------------------------------------------------------------ */
-        public String getProtocol()
-        {
-            return _protocol;
-        }
-
-        /* ------------------------------------------------------------ */
-        public byte binaryOpcode()
-        {
-            return OP_BINARY;
-        }
-
-        /* ------------------------------------------------------------ */
-        public byte textOpcode()
-        {
-            return OP_TEXT;
-        }
-
-        /* ------------------------------------------------------------ */
-        public byte continuationOpcode()
-        {
-            return OP_CONTINUATION;
-        }
-
-        /* ------------------------------------------------------------ */
-        public byte finMask()
-        {
-            return 0x8;
-        }
-
-        /* ------------------------------------------------------------ */
-        public boolean isControl(byte opcode)
-        {
-            return isControlFrame(opcode);
-        }
-
-        /* ------------------------------------------------------------ */
-        public boolean isText(byte opcode)
-        {
-            return opcode==OP_TEXT;
-        }
-
-        /* ------------------------------------------------------------ */
-        public boolean isBinary(byte opcode)
-        {
-            return opcode==OP_BINARY;
-        }
-
-        /* ------------------------------------------------------------ */
-        public boolean isContinuation(byte opcode)
-        {
-            return opcode==OP_CONTINUATION;
-        }
-
-        /* ------------------------------------------------------------ */
-        public boolean isClose(byte opcode)
-        {
-            return opcode==OP_CLOSE;
-        }
-
-        /* ------------------------------------------------------------ */
-        public boolean isPing(byte opcode)
-        {
-            return opcode==OP_PING;
-        }
-
-        /* ------------------------------------------------------------ */
-        public boolean isPong(byte opcode)
-        {
-            return opcode==OP_PONG;
-        }
-
-        /* ------------------------------------------------------------ */
-        public void disconnect()
-        {
-            close();
-        }
-
-        /* ------------------------------------------------------------ */
-        public void close()
-        {
-            close(CLOSE_NORMAL,null);
-        }
-
-        /* ------------------------------------------------------------ */
-        @Override
-        public String toString()
-        {
-            return this.getClass().getSimpleName()+"@"+_endp.getLocalAddr()+":"+_endp.getLocalPort()+"<->"+_endp.getRemoteAddr()+":"+_endp.getRemotePort();
-        }
-
-        public void setAllowFrameFragmentation(boolean allowFragmentation)
-        {
-        }
-
-        public boolean isAllowFrameFragmentation()
-        {
-            return false;
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    /* ------------------------------------------------------------ */
-    /* ------------------------------------------------------------ */
-    private class FrameHandlerD06 implements WebSocketParser.FrameHandler
-    {
-        private final Utf8StringBuilder _utf8 = new Utf8StringBuilder();
-        private ByteArrayBuffer _aggregate;
-        private byte _opcode=-1;
-
-        public void onFrame(byte flags, byte opcode, Buffer buffer)
-        {
-            boolean lastFrame = isLastFrame(flags);
-
-            synchronized(WebSocketConnectionD06.this)
-            {
-                // Ignore incoming after a close
-                if (_closedIn)
-                    return;
-
-                try
-                {
-                    byte[] array=buffer.array();
-
-                    // Deliver frame if websocket is a FrameWebSocket
-                    if (_onFrame!=null)
-                    {
-                        if (_onFrame.onFrame(flags,opcode,array,buffer.getIndex(),buffer.length()))
-                            return;
-                    }
-
-                    if (_onControl!=null && isControlFrame(opcode))
-                    {
-                        if (_onControl.onControl(opcode,array,buffer.getIndex(),buffer.length()))
-                            return;
-                    }
-
-                    switch(opcode)
-                    {
-                        case WebSocketConnectionD06.OP_CONTINUATION:
-                        {
-                            // If text, append to the message buffer
-                            if (_opcode==WebSocketConnectionD06.OP_TEXT && _connection.getMaxTextMessageSize()>=0)
-                            {
-                                if (_utf8.append(buffer.array(),buffer.getIndex(),buffer.length(),_connection.getMaxTextMessageSize()))
-                                {
-                                    // If this is the last fragment, deliver the text buffer
-                                    if (lastFrame && _onTextMessage!=null)
-                                    {
-                                        _opcode=-1;
-                                        String msg =_utf8.toString();
-                                        _utf8.reset();
-                                        _onTextMessage.onMessage(msg);
-                                    }
-                                }
-                                else
-                                {
-                                    _connection.close(WebSocketConnectionD06.CLOSE_LARGE,"Text message size > "+_connection.getMaxTextMessageSize()+" chars");
-                                    _utf8.reset();
-                                    _opcode=-1;
-                                }
-                            }
-                            else if (_opcode>=0 && _connection.getMaxBinaryMessageSize()>=0)
-                            {
-                                if (_aggregate.space()<_aggregate.length())
-                                {
-                                    _connection.close(WebSocketConnectionD06.CLOSE_LARGE,"Message size > "+_connection.getMaxBinaryMessageSize());
-                                    _aggregate.clear();
-                                    _opcode=-1;
-                                }
-                                else
-                                {
-                                    _aggregate.put(buffer);
-
-                                    // If this is the last fragment, deliver
-                                    if (lastFrame && _onBinaryMessage!=null)
-                                    {
-                                        try
-                                        {
-                                            _onBinaryMessage.onMessage(_aggregate.array(),_aggregate.getIndex(),_aggregate.length());
-                                        }
-                                        finally
-                                        {
-                                            _opcode=-1;
-                                            _aggregate.clear();
-                                        }
-                                    }
-                                }
-                            }
-                            break;
-                        }
-                        case WebSocketConnectionD06.OP_PING:
-                        {
-                            LOG.debug("PING {}",this);
-                            if (!_closedOut)
-                                _connection.sendControl(WebSocketConnectionD06.OP_PONG,buffer.array(),buffer.getIndex(),buffer.length());
-                            break;
-                        }
-
-                        case WebSocketConnectionD06.OP_PONG:
-                        {
-                            LOG.debug("PONG {}",this);
-                            break;
-                        }
-
-                        case WebSocketConnectionD06.OP_CLOSE:
-                        {
-                            int code=-1;
-                            String message=null;
-                            if (buffer.length()>=2)
-                            {
-                                code=buffer.array()[buffer.getIndex()]*0xff+buffer.array()[buffer.getIndex()+1];
-                                if (buffer.length()>2)
-                                    message=new String(buffer.array(),buffer.getIndex()+2,buffer.length()-2,StringUtil.__UTF8);
-                            }
-                            closeIn(code,message);
-                            break;
-                        }
-
-
-                        case WebSocketConnectionD06.OP_TEXT:
-                        {
-                            if(_onTextMessage!=null)
-                            {
-                                if (lastFrame)
-                                {
-                                    // Deliver the message
-                                    _onTextMessage.onMessage(buffer.toString(StringUtil.__UTF8));
-                                }
-                                else
-                                {
-                                    if (_connection.getMaxTextMessageSize()>=0)
-                                    {
-                                        // If this is a text fragment, append to buffer
-                                        if (_utf8.append(buffer.array(),buffer.getIndex(),buffer.length(),_connection.getMaxTextMessageSize()))
-                                            _opcode=WebSocketConnectionD06.OP_TEXT;
-                                        else
-                                        {
-                                            _utf8.reset();
-                                            _opcode=-1;
-                                            _connection.close(WebSocketConnectionD06.CLOSE_LARGE,"Text message size > "+_connection.getMaxTextMessageSize()+" chars");
-                                        }
-                                    }
-                                }
-                            }
-                            break;
-                        }
-
-                        default:
-                        {
-                            if (_onBinaryMessage!=null)
-                            {
-                                if (lastFrame)
-                                {
-                                    _onBinaryMessage.onMessage(array,buffer.getIndex(),buffer.length());
-                                }
-                                else
-                                {
-                                    if (_connection.getMaxBinaryMessageSize()>=0)
-                                    {
-                                        if (buffer.length()>_connection.getMaxBinaryMessageSize())
-                                        {
-                                            _connection.close(WebSocketConnectionD06.CLOSE_LARGE,"Message size > "+_connection.getMaxBinaryMessageSize());
-                                            if (_aggregate!=null)
-                                                _aggregate.clear();
-                                            _opcode=-1;
-                                        }
-                                        else
-                                        {
-                                            _opcode=opcode;
-                                            if (_aggregate==null)
-                                                _aggregate=new ByteArrayBuffer(_connection.getMaxBinaryMessageSize());
-                                            _aggregate.put(buffer);
-                                        }
-                                    }
-                                }
-                            }
-                        }
-                    }
-                }
-                catch(Throwable th)
-                {
-                    LOG.warn(th);
-                }
-            }
-        }
-
-        public void close(int code,String message)
-        {
-            _connection.close(code,message);
-        }
-
-        @Override
-        public String toString()
-        {
-            return WebSocketConnectionD06.this.toString()+"FH";
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    public static String hashKey(String key)
-    {
-        try
-        {
-            MessageDigest md = MessageDigest.getInstance("SHA1");
-            md.update(key.getBytes("UTF-8"));
-            md.update(MAGIC);
-            return new String(B64Code.encode(md.digest()));
-        }
-        catch (Exception e)
-        {
-            throw new RuntimeException(e);
-        }
-    }
-}
diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketConnectionD08.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketConnectionD08.java
deleted file mode 100644
index 1d2514c..0000000
--- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketConnectionD08.java
+++ /dev/null
@@ -1,854 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.websocket;
-
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.security.MessageDigest;
-import java.util.Collections;
-import java.util.List;
-
-import org.eclipse.jetty.io.AbstractConnection;
-import org.eclipse.jetty.io.AsyncEndPoint;
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.ByteArrayBuffer;
-import org.eclipse.jetty.io.Connection;
-import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.util.B64Code;
-import org.eclipse.jetty.util.StringUtil;
-import org.eclipse.jetty.util.Utf8StringBuilder;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.websocket.WebSocket.OnBinaryMessage;
-import org.eclipse.jetty.websocket.WebSocket.OnControl;
-import org.eclipse.jetty.websocket.WebSocket.OnFrame;
-import org.eclipse.jetty.websocket.WebSocket.OnTextMessage;
-
-public class WebSocketConnectionD08 extends AbstractConnection implements WebSocketConnection
-{
-    private static final Logger LOG = Log.getLogger(WebSocketConnectionD08.class);
-
-    final static byte OP_CONTINUATION = 0x00;
-    final static byte OP_TEXT = 0x01;
-    final static byte OP_BINARY = 0x02;
-    final static byte OP_EXT_DATA = 0x03;
-
-    final static byte OP_CONTROL = 0x08;
-    final static byte OP_CLOSE = 0x08;
-    final static byte OP_PING = 0x09;
-    final static byte OP_PONG = 0x0A;
-    final static byte OP_EXT_CTRL = 0x0B;
-
-    final static int CLOSE_NORMAL=1000;
-    final static int CLOSE_SHUTDOWN=1001;
-    final static int CLOSE_PROTOCOL=1002;
-    final static int CLOSE_BADDATA=1003;
-    final static int CLOSE_NOCODE=1005;
-    final static int CLOSE_NOCLOSE=1006;
-    final static int CLOSE_NOTUTF8=1007;
-
-    final static int FLAG_FIN=0x8;
-
-    final static int VERSION=8;
-
-    static boolean isLastFrame(byte flags)
-    {
-        return (flags&FLAG_FIN)!=0;
-    }
-
-    static boolean isControlFrame(byte opcode)
-    {
-        return (opcode&OP_CONTROL)!=0;
-    }
-
-    private final static byte[] MAGIC;
-    private final List<Extension> _extensions;
-    private final WebSocketParserD08 _parser;
-    private final WebSocketGeneratorD08 _generator;
-    private final WebSocketGenerator _outbound;
-    private final WebSocket _webSocket;
-    private final OnFrame _onFrame;
-    private final OnBinaryMessage _onBinaryMessage;
-    private final OnTextMessage _onTextMessage;
-    private final OnControl _onControl;
-    private final String _protocol;
-    private final int _draft;
-    private final ClassLoader _context;
-    private volatile int _closeCode;
-    private volatile String _closeMessage;
-    private volatile boolean _closedIn;
-    private volatile boolean _closedOut;
-    private int _maxTextMessageSize=-1;
-    private int _maxBinaryMessageSize=-1;
-
-    static
-    {
-        try
-        {
-            MAGIC="258EAFA5-E914-47DA-95CA-C5AB0DC85B11".getBytes(StringUtil.__ISO_8859_1);
-        }
-        catch (UnsupportedEncodingException e)
-        {
-            throw new RuntimeException(e);
-        }
-    }
-
-    private final WebSocket.FrameConnection _connection = new WSFrameConnection();
-
-
-    /* ------------------------------------------------------------ */
-    public WebSocketConnectionD08(WebSocket websocket, EndPoint endpoint, WebSocketBuffers buffers, long timestamp, int maxIdleTime, String protocol, List<Extension> extensions,int draft)
-        throws IOException
-    {
-        this(websocket,endpoint,buffers,timestamp,maxIdleTime,protocol,extensions,draft,null);
-    }
-
-    /* ------------------------------------------------------------ */
-    public WebSocketConnectionD08(WebSocket websocket, EndPoint endpoint, WebSocketBuffers buffers, long timestamp, int maxIdleTime, String protocol, List<Extension> extensions,int draft, MaskGen maskgen)
-        throws IOException
-    {
-        super(endpoint,timestamp);
-
-        _context=Thread.currentThread().getContextClassLoader();
-
-        _draft=draft;
-        _endp.setMaxIdleTime(maxIdleTime);
-
-        _webSocket = websocket;
-        _onFrame=_webSocket instanceof OnFrame ? (OnFrame)_webSocket : null;
-        _onTextMessage=_webSocket instanceof OnTextMessage ? (OnTextMessage)_webSocket : null;
-        _onBinaryMessage=_webSocket instanceof OnBinaryMessage ? (OnBinaryMessage)_webSocket : null;
-        _onControl=_webSocket instanceof OnControl ? (OnControl)_webSocket : null;
-        _generator = new WebSocketGeneratorD08(buffers, _endp,maskgen);
-
-        _extensions=extensions;
-        WebSocketParser.FrameHandler _frameHandler= new WSFrameHandler();
-        if (_extensions!=null)
-        {
-            int e=0;
-            for (Extension extension : _extensions)
-            {
-                extension.bind(
-                        _connection,
-                        e==extensions.size()-1?_frameHandler:extensions.get(e+1),
-                        e==0?_generator:extensions.get(e-1));
-                e++;
-            }
-        }
-
-        _outbound=(_extensions==null||_extensions.size()==0)?_generator:extensions.get(extensions.size()-1);
-        WebSocketParser.FrameHandler _inbound=(_extensions==null||_extensions.size()==0)?_frameHandler:extensions.get(0);
-
-        _parser = new WebSocketParserD08(buffers, endpoint,_inbound,maskgen==null);
-
-        _protocol=protocol;
-
-    }
-
-    /* ------------------------------------------------------------ */
-    public WebSocket.Connection getConnection()
-    {
-        return _connection;
-    }
-
-    /* ------------------------------------------------------------ */
-    public List<Extension> getExtensions()
-    {
-        if (_extensions==null)
-            return Collections.emptyList();
-
-        return _extensions;
-    }
-
-    /* ------------------------------------------------------------ */
-    public Connection handle() throws IOException
-    {
-        Thread current = Thread.currentThread();
-        ClassLoader oldcontext = current.getContextClassLoader();
-        current.setContextClassLoader(_context);
-        try
-        {
-            // handle the framing protocol
-            boolean progress=true;
-
-            while (progress)
-            {
-                int flushed=_generator.flushBuffer();
-                int filled=_parser.parseNext();
-
-                progress = flushed>0 || filled>0;
-                _endp.flush();
-
-                if (_endp instanceof AsyncEndPoint && ((AsyncEndPoint)_endp).hasProgressed())
-                    progress=true;
-            }
-        }
-        catch(IOException e)
-        {
-            try
-            {
-                if (_endp.isOpen())
-                    _endp.close();
-            }
-            catch(IOException e2)
-            {
-                LOG.ignore(e2);
-            }
-            throw e;
-        }
-        finally
-        {
-            current.setContextClassLoader(oldcontext);
-            _parser.returnBuffer();
-            _generator.returnBuffer();
-            if (_endp.isOpen())
-            {
-                if (_closedIn && _closedOut && _outbound.isBufferEmpty())
-                    _endp.close();
-                else if (_endp.isInputShutdown() && !_closedIn)
-                    closeIn(CLOSE_NOCLOSE,null);
-                else
-                    checkWriteable();
-            }
-        }
-        return this;
-    }
-
-    /* ------------------------------------------------------------ */
-    public void onInputShutdown() throws IOException
-    {
-        if (!_closedIn)
-            _endp.close();
-    }
-
-    /* ------------------------------------------------------------ */
-    public boolean isIdle()
-    {
-        return _parser.isBufferEmpty() && _outbound.isBufferEmpty();
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public void onIdleExpired(long idleForMs)
-    {
-        closeOut(WebSocketConnectionD08.CLOSE_NORMAL,"Idle for "+idleForMs+"ms > "+_endp.getMaxIdleTime()+"ms");
-    }
-
-    /* ------------------------------------------------------------ */
-    public boolean isSuspended()
-    {
-        return false;
-    }
-
-    /* ------------------------------------------------------------ */
-    public void onClose()
-    {
-        final boolean closed;
-        synchronized (this)
-        {
-            closed=_closeCode==0;
-            if (closed)
-                _closeCode=WebSocketConnectionD08.CLOSE_NOCLOSE;
-        }
-        if (closed)
-            _webSocket.onClose(WebSocketConnectionD08.CLOSE_NOCLOSE,"closed");
-    }
-
-    /* ------------------------------------------------------------ */
-    public void closeIn(int code,String message)
-    {
-        LOG.debug("ClosedIn {} {} {}",this,code,message);
-
-        final boolean closed_out;
-        final boolean tell_app;
-        synchronized (this)
-        {
-            closed_out=_closedOut;
-            _closedIn=true;
-            tell_app=_closeCode==0;
-            if (tell_app)
-            {
-                _closeCode=code;
-                _closeMessage=message;
-            }
-        }
-
-        try
-        {
-            if (!closed_out)
-                closeOut(code,message);
-        }
-        finally
-        {
-            if  (tell_app)
-                _webSocket.onClose(code,message);
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    public void closeOut(int code,String message)
-    {
-        LOG.debug("ClosedOut {} {} {}",this,code,message);
-
-        final boolean closed_out;
-        final boolean tell_app;
-        synchronized (this)
-        {
-            closed_out=_closedOut;
-            _closedOut=true;
-            tell_app=_closeCode==0;
-            if (tell_app)
-            {
-                _closeCode=code;
-                _closeMessage=message;
-            }
-        }
-
-        try
-        {                    
-            if (tell_app)
-                _webSocket.onClose(code,message);
-        }
-        finally
-        {
-            try
-            {
-                if (!closed_out)
-                {
-                    if (code<=0)
-                        code=WebSocketConnectionD08.CLOSE_NORMAL;
-                    byte[] bytes = ("xx"+(message==null?"":message)).getBytes(StringUtil.__ISO_8859_1);
-                    bytes[0]=(byte)(code/0x100);
-                    bytes[1]=(byte)(code%0x100);
-                    _outbound.addFrame((byte)FLAG_FIN,WebSocketConnectionD08.OP_CLOSE,bytes,0,bytes.length);
-                }
-                _outbound.flush();
-
-            }
-            catch(IOException e)
-            {
-                LOG.ignore(e);
-            }
-        }
-    }
-
-    public void shutdown()
-    {
-        final WebSocket.Connection connection = _connection;
-        if (connection != null)
-            connection.close(CLOSE_SHUTDOWN, null);
-    }
-
-    /* ------------------------------------------------------------ */
-    public void fillBuffersFrom(Buffer buffer)
-    {
-        _parser.fill(buffer);
-    }
-
-    /* ------------------------------------------------------------ */
-    private void checkWriteable()
-    {
-        if (!_outbound.isBufferEmpty() && _endp instanceof AsyncEndPoint)
-        {
-            ((AsyncEndPoint)_endp).scheduleWrite();
-        }
-    }
-
-    protected void onFrameHandshake()
-    {
-        if (_onFrame != null)
-        {
-            _onFrame.onHandshake(_connection);
-        }
-    }
-
-    protected void onWebSocketOpen()
-    {
-        _webSocket.onOpen(_connection);
-    }
-
-    /* ------------------------------------------------------------ */
-    private class WSFrameConnection implements WebSocket.FrameConnection
-    {
-        private volatile boolean _disconnecting;
-
-        /* ------------------------------------------------------------ */
-        public void sendMessage(String content) throws IOException
-        {
-            if (_closedOut)
-                throw new IOException("closedOut "+_closeCode+":"+_closeMessage);
-            byte[] data = content.getBytes(StringUtil.__UTF8);
-            _outbound.addFrame((byte)FLAG_FIN,WebSocketConnectionD08.OP_TEXT,data,0,data.length);
-            checkWriteable();
-        }
-
-        /* ------------------------------------------------------------ */
-        public void sendMessage(byte[] content, int offset, int length) throws IOException
-        {
-            if (_closedOut)
-                throw new IOException("closedOut "+_closeCode+":"+_closeMessage);
-            _outbound.addFrame((byte)FLAG_FIN,WebSocketConnectionD08.OP_BINARY,content,offset,length);
-            checkWriteable();
-        }
-
-        /* ------------------------------------------------------------ */
-        public void sendFrame(byte flags,byte opcode, byte[] content, int offset, int length) throws IOException
-        {
-            if (_closedOut)
-                throw new IOException("closedOut "+_closeCode+":"+_closeMessage);
-            _outbound.addFrame(flags,opcode,content,offset,length);
-            checkWriteable();
-        }
-
-        /* ------------------------------------------------------------ */
-        public void sendControl(byte ctrl, byte[] data, int offset, int length) throws IOException
-        {
-            if (_closedOut)
-                throw new IOException("closedOut "+_closeCode+":"+_closeMessage);
-            _outbound.addFrame((byte)FLAG_FIN,ctrl,data,offset,length);
-            checkWriteable();
-        }
-
-        /* ------------------------------------------------------------ */
-        public boolean isMessageComplete(byte flags)
-        {
-            return isLastFrame(flags);
-        }
-
-        /* ------------------------------------------------------------ */
-        public boolean isOpen()
-        {
-            return _endp!=null&&_endp.isOpen();
-        }
-
-        /* ------------------------------------------------------------ */
-        public void close(int code, String message)
-        {
-            if (_disconnecting)
-                return;
-            _disconnecting=true;
-            WebSocketConnectionD08.this.closeOut(code,message);
-        }
-
-        /* ------------------------------------------------------------ */
-        public void setMaxIdleTime(int ms)
-        {
-            try
-            {
-                _endp.setMaxIdleTime(ms);
-            }
-            catch(IOException e)
-            {
-                LOG.warn(e);
-            }
-        }
-
-        /* ------------------------------------------------------------ */
-        public void setMaxTextMessageSize(int size)
-        {
-            _maxTextMessageSize=size;
-        }
-
-        /* ------------------------------------------------------------ */
-        public void setMaxBinaryMessageSize(int size)
-        {
-            _maxBinaryMessageSize=size;
-        }
-
-        /* ------------------------------------------------------------ */
-        public int getMaxIdleTime()
-        {
-            return _endp.getMaxIdleTime();
-        }
-
-        /* ------------------------------------------------------------ */
-        public int getMaxTextMessageSize()
-        {
-            return _maxTextMessageSize;
-        }
-
-        /* ------------------------------------------------------------ */
-        public int getMaxBinaryMessageSize()
-        {
-            return _maxBinaryMessageSize;
-        }
-
-        /* ------------------------------------------------------------ */
-        public String getProtocol()
-        {
-            return _protocol;
-        }
-
-        /* ------------------------------------------------------------ */
-        public byte binaryOpcode()
-        {
-            return OP_BINARY;
-        }
-
-        /* ------------------------------------------------------------ */
-        public byte textOpcode()
-        {
-            return OP_TEXT;
-        }
-
-        /* ------------------------------------------------------------ */
-        public byte continuationOpcode()
-        {
-            return OP_CONTINUATION;
-        }
-
-        /* ------------------------------------------------------------ */
-        public byte finMask()
-        {
-            return FLAG_FIN;
-        }
-
-        /* ------------------------------------------------------------ */
-        public boolean isControl(byte opcode)
-        {
-            return isControlFrame(opcode);
-        }
-
-        /* ------------------------------------------------------------ */
-        public boolean isText(byte opcode)
-        {
-            return opcode==OP_TEXT;
-        }
-
-        /* ------------------------------------------------------------ */
-        public boolean isBinary(byte opcode)
-        {
-            return opcode==OP_BINARY;
-        }
-
-        /* ------------------------------------------------------------ */
-        public boolean isContinuation(byte opcode)
-        {
-            return opcode==OP_CONTINUATION;
-        }
-
-        /* ------------------------------------------------------------ */
-        public boolean isClose(byte opcode)
-        {
-            return opcode==OP_CLOSE;
-        }
-
-        /* ------------------------------------------------------------ */
-        public boolean isPing(byte opcode)
-        {
-            return opcode==OP_PING;
-        }
-
-        /* ------------------------------------------------------------ */
-        public boolean isPong(byte opcode)
-        {
-            return opcode==OP_PONG;
-        }
-
-        /* ------------------------------------------------------------ */
-        public void disconnect()
-        {
-            close(CLOSE_NORMAL,null);
-        }
-
-        /* ------------------------------------------------------------ */
-        public void close()
-        {
-            close(CLOSE_NORMAL,null);
-        }
-
-        /* ------------------------------------------------------------ */
-        public void setAllowFrameFragmentation(boolean allowFragmentation)
-        {
-            _parser.setFakeFragments(allowFragmentation);
-        }
-
-        /* ------------------------------------------------------------ */
-        public boolean isAllowFrameFragmentation()
-        {
-            return _parser.isFakeFragments();
-        }
-
-        /* ------------------------------------------------------------ */
-        @Override
-        public String toString()
-        {
-            return String.format("%s[D08]@%x l(%s:%d)<->r(%s:%d)",
-                    getClass().getSimpleName(),
-                    hashCode(),
-                    _endp.getLocalAddr(),
-                    _endp.getLocalPort(),
-                    _endp.getRemoteAddr(),
-                    _endp.getRemotePort());
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    /* ------------------------------------------------------------ */
-    /* ------------------------------------------------------------ */
-    private class WSFrameHandler implements WebSocketParser.FrameHandler
-    {
-        private final Utf8StringBuilder _utf8 = new Utf8StringBuilder();
-        private ByteArrayBuffer _aggregate;
-        private byte _opcode=-1;
-
-        public void onFrame(final byte flags, final byte opcode, final Buffer buffer)
-        {
-            boolean lastFrame = isLastFrame(flags);
-
-            synchronized(WebSocketConnectionD08.this)
-            {
-                // Ignore incoming after a close
-                if (_closedIn)
-                    return;
-            }
-            try
-            {
-                byte[] array=buffer.array();
-
-                // Deliver frame if websocket is a FrameWebSocket
-                if (_onFrame!=null)
-                {
-                    if (_onFrame.onFrame(flags,opcode,array,buffer.getIndex(),buffer.length()))
-                        return;
-                }
-
-                if (_onControl!=null && isControlFrame(opcode))
-                {
-                    if (_onControl.onControl(opcode,array,buffer.getIndex(),buffer.length()))
-                        return;
-                }
-
-                switch(opcode)
-                {
-                    case WebSocketConnectionD08.OP_CONTINUATION:
-                    {
-                        // If text, append to the message buffer
-                        if (_onTextMessage!=null && _opcode==WebSocketConnectionD08.OP_TEXT)
-                        {
-                            if (_utf8.append(buffer.array(),buffer.getIndex(),buffer.length(),_connection.getMaxTextMessageSize()))
-                            {
-                                // If this is the last fragment, deliver the text buffer
-                                if (lastFrame)
-                                {
-                                    _opcode=-1;
-                                    String msg =_utf8.toString();
-                                    _utf8.reset();
-                                    _onTextMessage.onMessage(msg);
-                                }
-                            }
-                            else
-                                textMessageTooLarge();
-                        }
-
-                        if (_opcode>=0 && _connection.getMaxBinaryMessageSize()>=0)
-                        {
-                            if (checkBinaryMessageSize(_aggregate.length(),buffer.length()))
-                            {
-                                _aggregate.put(buffer);
-
-                                // If this is the last fragment, deliver
-                                if (lastFrame && _onBinaryMessage!=null)
-                                {
-                                    try
-                                    {
-                                        _onBinaryMessage.onMessage(_aggregate.array(),_aggregate.getIndex(),_aggregate.length());
-                                    }
-                                    finally
-                                    {
-                                        _opcode=-1;
-                                        _aggregate.clear();
-                                    }
-                                }
-                            }
-                        }
-                        break;
-                    }
-                    case WebSocketConnectionD08.OP_PING:
-                    {
-                        LOG.debug("PING {}",this);
-                        if (!_closedOut)
-                            _connection.sendControl(WebSocketConnectionD08.OP_PONG,buffer.array(),buffer.getIndex(),buffer.length());
-                        break;
-                    }
-
-                    case WebSocketConnectionD08.OP_PONG:
-                    {
-                        LOG.debug("PONG {}",this);
-                        break;
-                    }
-
-                    case WebSocketConnectionD08.OP_CLOSE:
-                    {
-                        int code=WebSocketConnectionD08.CLOSE_NOCODE;
-                        String message=null;
-                        if (buffer.length()>=2)
-                        {
-                            code=buffer.array()[buffer.getIndex()]*0x100+buffer.array()[buffer.getIndex()+1];
-                            if (buffer.length()>2)
-                                message=new String(buffer.array(),buffer.getIndex()+2,buffer.length()-2,StringUtil.__UTF8);
-                        }
-                        closeIn(code,message);
-                        break;
-                    }
-
-                    case WebSocketConnectionD08.OP_TEXT:
-                    {
-                        if(_onTextMessage!=null)
-                        {
-                            if (_connection.getMaxTextMessageSize()<=0)
-                            {
-                                // No size limit, so handle only final frames
-                                if (lastFrame)
-                                    _onTextMessage.onMessage(buffer.toString(StringUtil.__UTF8));
-                                else
-                                {
-                                    LOG.warn("Frame discarded. Text aggregation disabled for {}",_endp);
-                                    _connection.close(WebSocketConnectionD08.CLOSE_BADDATA,"Text frame aggregation disabled");
-                                }
-                            }
-                            // append bytes to message buffer (if they fit)
-                            else if (_utf8.append(buffer.array(),buffer.getIndex(),buffer.length(),_connection.getMaxTextMessageSize()))
-                            {
-                                if (lastFrame)
-                                {
-                                    String msg =_utf8.toString();
-                                    _utf8.reset();
-                                    _onTextMessage.onMessage(msg);
-                                }
-                                else
-                                {
-                                    _opcode=WebSocketConnectionD08.OP_TEXT;
-                                }
-                            }
-                            else
-                                textMessageTooLarge();
-                        }
-                        break;
-                    }
-
-                    default:
-                    {
-                        if (_onBinaryMessage!=null && checkBinaryMessageSize(0,buffer.length()))
-                        {
-                            if (lastFrame)
-                            {
-                                _onBinaryMessage.onMessage(array,buffer.getIndex(),buffer.length());
-                            }
-                            else if (_connection.getMaxBinaryMessageSize()>=0)
-                            {
-                                _opcode=opcode;
-                                if (_aggregate==null)
-                                    _aggregate=new ByteArrayBuffer(_connection.getMaxBinaryMessageSize());
-                                _aggregate.put(buffer);
-                            }
-                            else
-                            {
-                                LOG.warn("Frame discarded. Binary aggregation disabed for {}",_endp);
-                                _connection.close(WebSocketConnectionD08.CLOSE_BADDATA,"Binary frame aggregation disabled");
-                            }
-                        }
-                    }
-                }
-            }
-            catch(Throwable e)
-            {
-                LOG.warn("{} for {}",e,_endp, e);
-                LOG.debug(e);
-                errorClose(WebSocketConnectionRFC6455.CLOSE_SERVER_ERROR,"Internal Server Error: "+e);
-            }
-        }
-
-        private void errorClose(int code, String message)
-        {
-            _connection.close(code,message);
-
-            // Brutally drop the connection
-            try
-            {
-                _endp.close();
-            }
-            catch (IOException e)
-            {
-                LOG.warn(e.toString());
-                LOG.debug(e);
-            }
-        }
-
-        private boolean checkBinaryMessageSize(int bufferLen, int length)
-        {
-            int max = _connection.getMaxBinaryMessageSize();
-            if (max>0 && (bufferLen+length)>max)
-            {
-                LOG.warn("Binary message too large > {}B for {}",_connection.getMaxBinaryMessageSize(),_endp);
-                _connection.close(WebSocketConnectionD08.CLOSE_BADDATA,"Message size > "+_connection.getMaxBinaryMessageSize());
-                _opcode=-1;
-                if (_aggregate!=null)
-                    _aggregate.clear();
-                return false;
-            }
-            return true;
-        }
-
-        private void textMessageTooLarge()
-        {
-            LOG.warn("Text message too large > {} chars for {}",_connection.getMaxTextMessageSize(),_endp);
-            _connection.close(WebSocketConnectionD08.CLOSE_BADDATA,"Text message size > "+_connection.getMaxTextMessageSize()+" chars");
-
-            _opcode=-1;
-            _utf8.reset();
-        }
-
-        public void close(int code,String message)
-        {
-            if (code!=CLOSE_NORMAL)
-                LOG.warn("Close: "+code+" "+message);
-            _connection.close(code,message);
-        }
-
-        @Override
-        public String toString()
-        {
-            return WebSocketConnectionD08.this.toString()+"FH";
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    public static String hashKey(String key)
-    {
-        try
-        {
-            MessageDigest md = MessageDigest.getInstance("SHA1");
-            md.update(key.getBytes("UTF-8"));
-            md.update(MAGIC);
-            return new String(B64Code.encode(md.digest()));
-        }
-        catch (Exception e)
-        {
-            throw new RuntimeException(e);
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public String toString()
-    {
-        return String.format("WS/D%d p=%s g=%s", _draft, _parser, _generator);
-    }
-}
diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketConnectionRFC6455.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketConnectionRFC6455.java
deleted file mode 100644
index 183a680..0000000
--- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketConnectionRFC6455.java
+++ /dev/null
@@ -1,995 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.websocket;
-
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.security.MessageDigest;
-import java.util.Collections;
-import java.util.List;
-
-import org.eclipse.jetty.io.AbstractConnection;
-import org.eclipse.jetty.io.AsyncEndPoint;
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.ByteArrayBuffer;
-import org.eclipse.jetty.io.Connection;
-import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.util.B64Code;
-import org.eclipse.jetty.util.StringUtil;
-import org.eclipse.jetty.util.Utf8Appendable;
-import org.eclipse.jetty.util.Utf8StringBuilder;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.websocket.WebSocket.OnBinaryMessage;
-import org.eclipse.jetty.websocket.WebSocket.OnControl;
-import org.eclipse.jetty.websocket.WebSocket.OnFrame;
-import org.eclipse.jetty.websocket.WebSocket.OnTextMessage;
-
-
-/* ------------------------------------------------------------ */
-/**
- * <pre>
- *    0                   1                   2                   3
- *    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
- *   +-+-+-+-+-------+-+-------------+-------------------------------+
- *   |F|R|R|R| opcode|M| Payload len |    Extended payload length    |
- *   |I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
- *   |N|V|V|V|       |S|             |   (if payload len==126/127)   |
- *   | |1|2|3|       |K|             |                               |
- *   +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
- *   |     Extended payload length continued, if payload len == 127  |
- *   + - - - - - - - - - - - - - - - +-------------------------------+
- *   |                               |Masking-key, if MASK set to 1  |
- *   +-------------------------------+-------------------------------+
- *   | Masking-key (continued)       |          Payload Data         |
- *   +-------------------------------- - - - - - - - - - - - - - - - +
- *   :                     Payload Data continued ...                :
- *   + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
- *   |                     Payload Data continued ...                |
- *   +---------------------------------------------------------------+
- * </pre>
- */
-public class WebSocketConnectionRFC6455 extends AbstractConnection implements WebSocketConnection
-{
-    private static final Logger LOG = Log.getLogger(WebSocketConnectionRFC6455.class);
-
-    final static byte OP_CONTINUATION = 0x00;
-    final static byte OP_TEXT = 0x01;
-    final static byte OP_BINARY = 0x02;
-    final static byte OP_EXT_DATA = 0x03;
-
-    final static byte OP_CONTROL = 0x08;
-    final static byte OP_CLOSE = 0x08;
-    final static byte OP_PING = 0x09;
-    final static byte OP_PONG = 0x0A;
-    final static byte OP_EXT_CTRL = 0x0B;
-
-    final static int CLOSE_NORMAL=1000;
-    final static int CLOSE_SHUTDOWN=1001;
-    final static int CLOSE_PROTOCOL=1002;
-    final static int CLOSE_BAD_DATA=1003;
-    final static int CLOSE_UNDEFINED=1004;
-    final static int CLOSE_NO_CODE=1005;
-    final static int CLOSE_NO_CLOSE=1006;
-    final static int CLOSE_BAD_PAYLOAD=1007;
-    final static int CLOSE_POLICY_VIOLATION=1008;
-    final static int CLOSE_MESSAGE_TOO_LARGE=1009;
-    final static int CLOSE_REQUIRED_EXTENSION=1010;
-    final static int CLOSE_SERVER_ERROR=1011;
-    final static int CLOSE_FAILED_TLS_HANDSHAKE=1015;
-
-    final static int FLAG_FIN=0x8;
-
-    // Per RFC 6455, section 1.3 - Opening Handshake - this version is "13"
-    final static int VERSION=13;
-
-    static boolean isLastFrame(byte flags)
-    {
-        return (flags&FLAG_FIN)!=0;
-    }
-
-    static boolean isControlFrame(byte opcode)
-    {
-        return (opcode&OP_CONTROL)!=0;
-    }
-
-    private final static byte[] MAGIC;
-    private final List<Extension> _extensions;
-    private final WebSocketParserRFC6455 _parser;
-    private final WebSocketGeneratorRFC6455 _generator;
-    private final WebSocketGenerator _outbound;
-    private final WebSocket _webSocket;
-    private final OnFrame _onFrame;
-    private final OnBinaryMessage _onBinaryMessage;
-    private final OnTextMessage _onTextMessage;
-    private final OnControl _onControl;
-    private final String _protocol;
-    private final int _draft;
-    private final ClassLoader _context;
-    private volatile int _closeCode;
-    private volatile String _closeMessage;
-    private volatile boolean _closedIn;
-    private volatile boolean _closedOut;
-    private int _maxTextMessageSize=-1;
-    private int _maxBinaryMessageSize=-1;
-
-    static
-    {
-        try
-        {
-            MAGIC="258EAFA5-E914-47DA-95CA-C5AB0DC85B11".getBytes(StringUtil.__ISO_8859_1);
-        }
-        catch (UnsupportedEncodingException e)
-        {
-            throw new RuntimeException(e);
-        }
-    }
-
-    private final WebSocket.FrameConnection _connection = new WSFrameConnection();
-
-
-    /* ------------------------------------------------------------ */
-    public WebSocketConnectionRFC6455(WebSocket websocket, EndPoint endpoint, WebSocketBuffers buffers, long timestamp, int maxIdleTime, String protocol, List<Extension> extensions,int draft)
-        throws IOException
-    {
-        this(websocket,endpoint,buffers,timestamp,maxIdleTime,protocol,extensions,draft,null);
-    }
-
-    /* ------------------------------------------------------------ */
-    public WebSocketConnectionRFC6455(WebSocket websocket, EndPoint endpoint, WebSocketBuffers buffers, long timestamp, int maxIdleTime, String protocol, List<Extension> extensions,int draft, MaskGen maskgen)
-        throws IOException
-    {
-        super(endpoint,timestamp);
-
-        _context=Thread.currentThread().getContextClassLoader();
-
-        _draft=draft;
-        _endp.setMaxIdleTime(maxIdleTime);
-
-        _webSocket = websocket;
-        _onFrame=_webSocket instanceof OnFrame ? (OnFrame)_webSocket : null;
-        _onTextMessage=_webSocket instanceof OnTextMessage ? (OnTextMessage)_webSocket : null;
-        _onBinaryMessage=_webSocket instanceof OnBinaryMessage ? (OnBinaryMessage)_webSocket : null;
-        _onControl=_webSocket instanceof OnControl ? (OnControl)_webSocket : null;
-        _generator = new WebSocketGeneratorRFC6455(buffers, _endp,maskgen);
-
-        _extensions=extensions;
-        WebSocketParser.FrameHandler frameHandler = new WSFrameHandler();
-        if (_extensions!=null)
-        {
-            int e=0;
-            for (Extension extension : _extensions)
-            {
-                extension.bind(
-                        _connection,
-                        e==extensions.size()-1? frameHandler :extensions.get(e+1),
-                        e==0?_generator:extensions.get(e-1));
-                e++;
-            }
-        }
-
-        _outbound=(_extensions==null||_extensions.size()==0)?_generator:extensions.get(extensions.size()-1);
-        WebSocketParser.FrameHandler inbound = (_extensions == null || _extensions.size() == 0) ? frameHandler : extensions.get(0);
-
-        _parser = new WebSocketParserRFC6455(buffers, endpoint, inbound,maskgen==null);
-
-        _protocol=protocol;
-
-    }
-
-    /* ------------------------------------------------------------ */
-    public WebSocket.Connection getConnection()
-    {
-        return _connection;
-    }
-
-    /* ------------------------------------------------------------ */
-    public List<Extension> getExtensions()
-    {
-        if (_extensions==null)
-            return Collections.emptyList();
-
-        return _extensions;
-    }
-
-    /* ------------------------------------------------------------ */
-    public Connection handle() throws IOException
-    {
-        Thread current = Thread.currentThread();
-        ClassLoader oldcontext = current.getContextClassLoader();
-        current.setContextClassLoader(_context);
-        try
-        {
-            // handle the framing protocol
-            boolean progress=true;
-
-            while (progress)
-            {
-                int flushed=_generator.flushBuffer();
-                int filled=_parser.parseNext();
-
-                progress = flushed>0 || filled>0;
-                _endp.flush();
-
-                if (_endp instanceof AsyncEndPoint && ((AsyncEndPoint)_endp).hasProgressed())
-                    progress=true;
-            }
-        }
-        catch(IOException e)
-        {
-            try
-            {
-                if (_endp.isOpen())
-                    _endp.close();
-            }
-            catch(IOException e2)
-            {
-                LOG.ignore(e2);
-            }
-            throw e;
-        }
-        finally
-        {
-            current.setContextClassLoader(oldcontext);
-            _parser.returnBuffer();
-            _generator.returnBuffer();
-            if (_endp.isOpen())
-            {
-                if (_closedIn && _closedOut && _outbound.isBufferEmpty())
-                    _endp.close();
-                else if (_endp.isInputShutdown() && !_closedIn)
-                    closeIn(CLOSE_NO_CLOSE,null);
-                else
-                    checkWriteable();
-            }
-        }
-        return this;
-    }
-
-    /* ------------------------------------------------------------ */
-    public void onInputShutdown() throws IOException
-    {
-        if (!_closedIn)
-            _endp.close();
-    }
-
-    /* ------------------------------------------------------------ */
-    public boolean isIdle()
-    {
-        return _parser.isBufferEmpty() && _outbound.isBufferEmpty();
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public void onIdleExpired(long idleForMs)
-    {
-        closeOut(WebSocketConnectionRFC6455.CLOSE_NORMAL,"Idle for "+idleForMs+"ms > "+_endp.getMaxIdleTime()+"ms");
-    }
-
-    /* ------------------------------------------------------------ */
-    public boolean isSuspended()
-    {
-        return false;
-    }
-
-    /* ------------------------------------------------------------ */
-    public void onClose()
-    {
-        final boolean closed;
-        synchronized (this)
-        {
-            closed=_closeCode==0;
-            if (closed)
-                _closeCode=WebSocketConnectionRFC6455.CLOSE_NO_CLOSE;
-        }
-        if (closed)
-            _webSocket.onClose(WebSocketConnectionRFC6455.CLOSE_NO_CLOSE,"closed");
-    }
-
-    /* ------------------------------------------------------------ */
-    public void closeIn(int code,String message)
-    {
-        LOG.debug("ClosedIn {} {} {}",this,code,message);
-
-        final boolean closed_out;
-        final boolean tell_app;
-        synchronized (this)
-        {
-            closed_out=_closedOut;
-            _closedIn=true;
-            tell_app=_closeCode==0;
-            if (tell_app)
-            {
-                _closeCode=code;
-                _closeMessage=message;
-            }
-        }
-
-        try
-        {
-            if (!closed_out)
-                closeOut(code,message);
-        }
-        finally
-        {
-            if  (tell_app)
-                _webSocket.onClose(code,message);
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    public void closeOut(int code,String message)
-    {
-        LOG.debug("ClosedOut {} {} {}",this,code,message);
-
-        final boolean closed_out;
-        final boolean tell_app;
-        synchronized (this)
-        {
-            closed_out=_closedOut;
-            _closedOut=true;
-            tell_app=_closeCode==0;
-            if (tell_app)
-            {
-                _closeCode=code;
-                _closeMessage=message;
-            }
-        }
-
-        try
-        {                    
-            if (tell_app)
-                _webSocket.onClose(code,message);
-        }
-        finally
-        {
-            try
-            {
-                if (!closed_out)
-                {
-                    // Close code 1005/1006/1015 are never to be sent as a status over
-                    // a Close control frame. Code<-1 also means no node.
-
-                    if (code < 0 || (code == WebSocketConnectionRFC6455.CLOSE_NO_CODE) || (code == WebSocketConnectionRFC6455.CLOSE_NO_CLOSE)
-                            || (code == WebSocketConnectionRFC6455.CLOSE_FAILED_TLS_HANDSHAKE))
-                    {
-                        code = -1;
-                    }
-                    else if (code == 0)
-                    {
-                        code = WebSocketConnectionRFC6455.CLOSE_NORMAL;
-                    }
-
-                    byte[] bytes = ("xx"+(message==null?"":message)).getBytes(StringUtil.__ISO_8859_1);
-                    bytes[0]=(byte)(code/0x100);
-                    bytes[1]=(byte)(code%0x100);
-                    _outbound.addFrame((byte)FLAG_FIN,WebSocketConnectionRFC6455.OP_CLOSE,bytes,0,code>0?bytes.length:0);
-                    _outbound.flush();
-                }
-            }
-            catch(IOException e)
-            {
-                LOG.ignore(e);
-            }
-        }
-    }
-
-    public void shutdown()
-    {
-        final WebSocket.Connection connection = _connection;
-        if (connection != null)
-            connection.close(CLOSE_SHUTDOWN, null);
-    }
-
-    /* ------------------------------------------------------------ */
-    public void fillBuffersFrom(Buffer buffer)
-    {
-        _parser.fill(buffer);
-    }
-
-    /* ------------------------------------------------------------ */
-    private void checkWriteable()
-    {
-        if (!_outbound.isBufferEmpty() && _endp instanceof AsyncEndPoint)
-        {
-            ((AsyncEndPoint)_endp).scheduleWrite();
-        }
-    }
-
-    protected void onFrameHandshake()
-    {
-        if (_onFrame != null)
-        {
-            _onFrame.onHandshake(_connection);
-        }
-    }
-
-    protected void onWebSocketOpen()
-    {
-        _webSocket.onOpen(_connection);
-    }
-
-    /* ------------------------------------------------------------ */
-    private class WSFrameConnection implements WebSocket.FrameConnection
-    {
-        private volatile boolean _disconnecting;
-
-        /* ------------------------------------------------------------ */
-        public void sendMessage(String content) throws IOException
-        {
-            if (_closedOut)
-                throw new IOException("closedOut "+_closeCode+":"+_closeMessage);
-            byte[] data = content.getBytes(StringUtil.__UTF8);
-            _outbound.addFrame((byte)FLAG_FIN,WebSocketConnectionRFC6455.OP_TEXT,data,0,data.length);
-            checkWriteable();
-        }
-
-        /* ------------------------------------------------------------ */
-        public void sendMessage(byte[] content, int offset, int length) throws IOException
-        {
-            if (_closedOut)
-                throw new IOException("closedOut "+_closeCode+":"+_closeMessage);
-            _outbound.addFrame((byte)FLAG_FIN,WebSocketConnectionRFC6455.OP_BINARY,content,offset,length);
-            checkWriteable();
-        }
-
-        /* ------------------------------------------------------------ */
-        public void sendFrame(byte flags,byte opcode, byte[] content, int offset, int length) throws IOException
-        {
-            if (_closedOut)
-                throw new IOException("closedOut "+_closeCode+":"+_closeMessage);
-            _outbound.addFrame(flags,opcode,content,offset,length);
-            checkWriteable();
-        }
-
-        /* ------------------------------------------------------------ */
-        public void sendControl(byte ctrl, byte[] data, int offset, int length) throws IOException
-        {
-            // TODO: section 5.5 states that control frames MUST never be length > 125 bytes and MUST NOT be fragmented
-            if (_closedOut)
-                throw new IOException("closedOut "+_closeCode+":"+_closeMessage);
-            _outbound.addFrame((byte)FLAG_FIN,ctrl,data,offset,length);
-            checkWriteable();
-        }
-
-        /* ------------------------------------------------------------ */
-        public boolean isMessageComplete(byte flags)
-        {
-            return isLastFrame(flags);
-        }
-
-        /* ------------------------------------------------------------ */
-        public boolean isOpen()
-        {
-            return _endp!=null&&_endp.isOpen();
-        }
-
-        /* ------------------------------------------------------------ */
-        public void close(int code, String message)
-        {
-            if (_disconnecting)
-                return;
-            _disconnecting=true;
-            WebSocketConnectionRFC6455.this.closeOut(code,message);
-        }
-
-        /* ------------------------------------------------------------ */
-        public void setMaxIdleTime(int ms)
-        {
-            try
-            {
-                _endp.setMaxIdleTime(ms);
-            }
-            catch(IOException e)
-            {
-                LOG.warn(e);
-            }
-        }
-
-        /* ------------------------------------------------------------ */
-        public void setMaxTextMessageSize(int size)
-        {
-            _maxTextMessageSize=size;
-        }
-
-        /* ------------------------------------------------------------ */
-        public void setMaxBinaryMessageSize(int size)
-        {
-            _maxBinaryMessageSize=size;
-        }
-
-        /* ------------------------------------------------------------ */
-        public int getMaxIdleTime()
-        {
-            return _endp.getMaxIdleTime();
-        }
-
-        /* ------------------------------------------------------------ */
-        public int getMaxTextMessageSize()
-        {
-            return _maxTextMessageSize;
-        }
-
-        /* ------------------------------------------------------------ */
-        public int getMaxBinaryMessageSize()
-        {
-            return _maxBinaryMessageSize;
-        }
-
-        /* ------------------------------------------------------------ */
-        public String getProtocol()
-        {
-            return _protocol;
-        }
-
-        /* ------------------------------------------------------------ */
-        public byte binaryOpcode()
-        {
-            return OP_BINARY;
-        }
-
-        /* ------------------------------------------------------------ */
-        public byte textOpcode()
-        {
-            return OP_TEXT;
-        }
-
-        /* ------------------------------------------------------------ */
-        public byte continuationOpcode()
-        {
-            return OP_CONTINUATION;
-        }
-
-        /* ------------------------------------------------------------ */
-        public byte finMask()
-        {
-            return FLAG_FIN;
-        }
-
-        /* ------------------------------------------------------------ */
-        public boolean isControl(byte opcode)
-        {
-            return isControlFrame(opcode);
-        }
-
-        /* ------------------------------------------------------------ */
-        public boolean isText(byte opcode)
-        {
-            return opcode==OP_TEXT;
-        }
-
-        /* ------------------------------------------------------------ */
-        public boolean isBinary(byte opcode)
-        {
-            return opcode==OP_BINARY;
-        }
-
-        /* ------------------------------------------------------------ */
-        public boolean isContinuation(byte opcode)
-        {
-            return opcode==OP_CONTINUATION;
-        }
-
-        /* ------------------------------------------------------------ */
-        public boolean isClose(byte opcode)
-        {
-            return opcode==OP_CLOSE;
-        }
-
-        /* ------------------------------------------------------------ */
-        public boolean isPing(byte opcode)
-        {
-            return opcode==OP_PING;
-        }
-
-        /* ------------------------------------------------------------ */
-        public boolean isPong(byte opcode)
-        {
-            return opcode==OP_PONG;
-        }
-
-        /* ------------------------------------------------------------ */
-        public void disconnect()
-        {
-            close(CLOSE_NORMAL,null);
-        }
-
-        /* ------------------------------------------------------------ */
-        public void close()
-        {
-            close(CLOSE_NORMAL,null);
-        }
-
-        /* ------------------------------------------------------------ */
-        public void setAllowFrameFragmentation(boolean allowFragmentation)
-        {
-            _parser.setFakeFragments(allowFragmentation);
-        }
-
-        /* ------------------------------------------------------------ */
-        public boolean isAllowFrameFragmentation()
-        {
-            return _parser.isFakeFragments();
-        }
-
-        /* ------------------------------------------------------------ */
-        @Override
-        public String toString()
-        {
-            return String.format("%s@%x l(%s:%d)<->r(%s:%d)",
-                    getClass().getSimpleName(),
-                    hashCode(),
-                    _endp.getLocalAddr(),
-                    _endp.getLocalPort(),
-                    _endp.getRemoteAddr(),
-                    _endp.getRemotePort());
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    /* ------------------------------------------------------------ */
-    /* ------------------------------------------------------------ */
-    private class WSFrameHandler implements WebSocketParser.FrameHandler
-    {
-        private static final int MAX_CONTROL_FRAME_PAYLOAD = 125;
-        private final Utf8StringBuilder _utf8 = new Utf8StringBuilder(512); // TODO configure initial capacity
-        private ByteArrayBuffer _aggregate;
-        private byte _opcode=-1;
-
-        public void onFrame(final byte flags, final byte opcode, final Buffer buffer)
-        {
-            boolean lastFrame = isLastFrame(flags);
-            
-            
-
-            synchronized(WebSocketConnectionRFC6455.this)
-            {
-                // Ignore incoming after a close
-                if (_closedIn)
-                    return;
-            }
-            try
-            {
-                byte[] array=buffer.array();
-
-                if (isControlFrame(opcode) && buffer.length()>MAX_CONTROL_FRAME_PAYLOAD)
-                {
-                    errorClose(WebSocketConnectionRFC6455.CLOSE_PROTOCOL,"Control frame too large: " + buffer.length() + " > " + MAX_CONTROL_FRAME_PAYLOAD);
-                    return;
-                }
-
-                // TODO: check extensions for RSV bit(s) meanings
-                if ((flags&0x7)!=0)
-                {
-                    errorClose(WebSocketConnectionRFC6455.CLOSE_PROTOCOL,"RSV bits set 0x"+Integer.toHexString(flags));
-                    return;
-                }
-
-                // Ignore all frames after error close
-                if (_closeCode!=0 && _closeCode!=CLOSE_NORMAL && opcode!=OP_CLOSE)
-                {
-                    return;
-                }
-
-                // Deliver frame if websocket is a FrameWebSocket
-                if (_onFrame!=null)
-                {
-                    if (_onFrame.onFrame(flags,opcode,array,buffer.getIndex(),buffer.length()))
-                        return;
-                }
-
-                if (_onControl!=null && isControlFrame(opcode))
-                {
-                    if (_onControl.onControl(opcode,array,buffer.getIndex(),buffer.length()))
-                        return;
-                }
-
-                switch(opcode)
-                {
-                    case WebSocketConnectionRFC6455.OP_CONTINUATION:
-                    {
-                        // logic based on initial opcode (text | binary)
-                        switch (_opcode)
-                        {
-                            case WebSocketConnectionRFC6455.OP_TEXT:
-                                if (lastFrame)
-                                {
-                                    _opcode = -1;
-                                }
-                                if (_onTextMessage != null)
-                                {
-                                    if (_utf8.append(buffer.array(),buffer.getIndex(),buffer.length(),_connection.getMaxTextMessageSize()))
-                                    {
-                                        // If this is the last fragment, deliver the text buffer
-                                        if (lastFrame)
-                                        {
-                                            String msg = _utf8.toString();
-                                            _utf8.reset();
-                                            _onTextMessage.onMessage(msg);
-                                        }
-                                    }
-                                    else
-                                        textMessageTooLarge();
-                                }
-                                break;
-                            case WebSocketConnectionRFC6455.OP_BINARY:
-                                if (lastFrame)
-                                {
-                                    _opcode = -1;
-                                }
-                                if (_onBinaryMessage != null)
-                                {
-                                    if (_aggregate != null && checkBinaryMessageSize(_aggregate.length(),buffer.length()))
-                                    {
-                                        _aggregate.put(buffer);
-
-                                        // If this is the last fragment, deliver
-                                        if (lastFrame)
-                                        {
-                                            try
-                                            {
-                                                _onBinaryMessage.onMessage(_aggregate.array(),_aggregate.getIndex(),_aggregate.length());
-                                            }
-                                            finally
-                                            {
-                                                _aggregate.clear();
-                                            }
-                                        }
-                                    }
-                                }
-                                break;
-                            default:
-                                errorClose(WebSocketConnectionRFC6455.CLOSE_PROTOCOL,"Bad Continuation");
-                                return;
-                        }
-                        break;
-                    }
-                    case WebSocketConnectionRFC6455.OP_PING:
-                    {
-                        LOG.debug("PING {}",this);
-                        if (!_closedOut)
-                        {
-                            _connection.sendControl(WebSocketConnectionRFC6455.OP_PONG,buffer.array(),buffer.getIndex(),buffer.length());
-                        }
-                        break;
-                    }
-
-                    case WebSocketConnectionRFC6455.OP_PONG:
-                    {
-                        LOG.debug("PONG {}",this);
-                        break;
-                    }
-
-                    case WebSocketConnectionRFC6455.OP_CLOSE:
-                    {
-                        int code=WebSocketConnectionRFC6455.CLOSE_NO_CODE;
-                        String message=null;
-                        if (buffer.length()>=2)
-                        {
-                            code=(0xff&buffer.array()[buffer.getIndex()])*0x100+(0xff&buffer.array()[buffer.getIndex()+1]);
-
-                            // Validate close status codes.
-                            if (code < WebSocketConnectionRFC6455.CLOSE_NORMAL ||
-                                code == WebSocketConnectionRFC6455.CLOSE_UNDEFINED ||
-                                code == WebSocketConnectionRFC6455.CLOSE_NO_CLOSE ||
-                                code == WebSocketConnectionRFC6455.CLOSE_NO_CODE ||
-                                ( code > 1011 && code <= 2999 ) ||
-                                code >= 5000 )
-                            {
-                                errorClose(WebSocketConnectionRFC6455.CLOSE_PROTOCOL,"Invalid close code " + code);
-                                return;
-                            }
-
-                            if (buffer.length()>2)
-                            {
-                                if(_utf8.append(buffer.array(),buffer.getIndex()+2,buffer.length()-2,_connection.getMaxTextMessageSize()))
-                                {
-                                    message = _utf8.toString();
-                                    _utf8.reset();
-                                }
-                            }
-                        }
-                        else if(buffer.length() == 1)
-                        {
-                            // Invalid length. use status code 1002 (Protocol error)
-                            errorClose(WebSocketConnectionRFC6455.CLOSE_PROTOCOL,"Invalid payload length of 1");
-                            return;
-                        }
-                        closeIn(code,message);
-                        break;
-                    }
-
-                    case WebSocketConnectionRFC6455.OP_TEXT:
-                    {
-                        if (_opcode!=-1)
-                        {
-                            errorClose(WebSocketConnectionRFC6455.CLOSE_PROTOCOL,"Expected Continuation"+Integer.toHexString(opcode));
-                            return;
-                        }
-
-                        _opcode = lastFrame ? -1 : WebSocketConnectionRFC6455.OP_TEXT;
-
-                        if(_onTextMessage!=null)
-                        {
-                            if (_connection.getMaxTextMessageSize()<=0)
-                            {
-                                // No size limit, so handle only final frames
-                                if (lastFrame)
-                                {
-                                    _onTextMessage.onMessage(buffer.toString(StringUtil.__UTF8));
-                                }
-                                else
-                                {
-                                    LOG.warn("Frame discarded. Text aggregation disabled for {}",_endp);
-                                    errorClose(WebSocketConnectionRFC6455.CLOSE_POLICY_VIOLATION,"Text frame aggregation disabled");
-                                }
-                            }
-                            // append bytes to message buffer (if they fit)
-                            else if (_utf8.append(buffer.array(),buffer.getIndex(),buffer.length(),_connection.getMaxTextMessageSize()))
-                            {
-                                if (lastFrame)
-                                {
-                                    String msg =_utf8.toString();
-                                    _utf8.reset();
-                                    _onTextMessage.onMessage(msg);
-                                }
-                            }
-                            else
-                                textMessageTooLarge();
-                        }
-                        break;
-                    }
-
-                    case WebSocketConnectionRFC6455.OP_BINARY:
-                    {
-                        if (_opcode!=-1)
-                        {
-                            errorClose(WebSocketConnectionRFC6455.CLOSE_PROTOCOL,"Expected Continuation"+Integer.toHexString(opcode));
-                            return;
-                        }
-                        
-                        _opcode = lastFrame ? -1 : WebSocketConnectionRFC6455.OP_BINARY;
-
-                        if (_onBinaryMessage!=null)
-                        {
-                            if(!checkBinaryMessageSize(0,buffer.length()))
-                            {
-                                return;
-                            }
-                            
-                            if (lastFrame)
-                            {
-                                _onBinaryMessage.onMessage(array,buffer.getIndex(),buffer.length());
-                            }
-                            else if (_connection.getMaxBinaryMessageSize()>=0)
-                            {
-                                // TODO use a growing buffer rather than a fixed one.
-                                if (_aggregate == null)
-                                {
-                                    _aggregate = new ByteArrayBuffer(_connection.getMaxBinaryMessageSize());
-                                    _aggregate.clear();
-                                }
-
-                                _aggregate.put(buffer);
-                            }
-                            else
-                            {
-                                LOG.warn("Frame discarded. Binary aggregation disabed for {}",_endp);
-                                errorClose(WebSocketConnectionRFC6455.CLOSE_POLICY_VIOLATION,"Binary frame aggregation disabled");
-                            }
-                        }
-                        break;
-                    }
-
-                    default:
-                        errorClose(WebSocketConnectionRFC6455.CLOSE_PROTOCOL,"Bad opcode 0x"+Integer.toHexString(opcode));
-                        break;
-                }
-            }
-            catch(Utf8Appendable.NotUtf8Exception notUtf8)
-            {
-                LOG.warn("NOTUTF8 - {} for {}",notUtf8,_endp, notUtf8);
-                LOG.debug(notUtf8);
-                errorClose(WebSocketConnectionRFC6455.CLOSE_BAD_PAYLOAD,"Invalid UTF-8");
-            }
-            catch(Throwable e)
-            {
-                LOG.warn("{} for {}",e,_endp, e);
-                LOG.debug(e);
-                errorClose(WebSocketConnectionRFC6455.CLOSE_SERVER_ERROR,"Internal Server Error: "+e);
-            }
-        }
-
-        private void errorClose(int code, String message)
-        {
-            _connection.close(code,message);
-
-            // Brutally drop the connection
-            try
-            {
-                _endp.close();
-            }
-            catch (IOException e)
-            {
-                LOG.warn(e.toString());
-                LOG.debug(e);
-            }
-        }
-
-        private boolean checkBinaryMessageSize(int bufferLen, int length)
-        {
-            int max = _connection.getMaxBinaryMessageSize();
-            if (max>0 && (bufferLen+length)>max)
-            {
-                LOG.warn("Binary message too large > {}B for {}",_connection.getMaxBinaryMessageSize(),_endp);
-                _connection.close(WebSocketConnectionRFC6455.CLOSE_MESSAGE_TOO_LARGE,"Message size > "+_connection.getMaxBinaryMessageSize());
-                _opcode=-1;
-                if (_aggregate!=null)
-                    _aggregate.clear();
-                return false;
-            }
-            return true;
-        }
-
-        private void textMessageTooLarge()
-        {
-            LOG.warn("Text message too large > {} chars for {}",_connection.getMaxTextMessageSize(),_endp);
-            _connection.close(WebSocketConnectionRFC6455.CLOSE_MESSAGE_TOO_LARGE,"Text message size > "+_connection.getMaxTextMessageSize()+" chars");
-
-            _opcode=-1;
-            _utf8.reset();
-        }
-
-        public void close(int code,String message)
-        {
-            if (code!=CLOSE_NORMAL)
-                LOG.warn("Close: "+code+" "+message);
-            _connection.close(code,message);
-        }
-
-        @Override
-        public String toString()
-        {
-            return WebSocketConnectionRFC6455.this.toString()+"FH";
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    public static String hashKey(String key)
-    {
-        try
-        {
-            MessageDigest md = MessageDigest.getInstance("SHA1");
-            md.update(key.getBytes("UTF-8"));
-            md.update(MAGIC);
-            return new String(B64Code.encode(md.digest()));
-        }
-        catch (Exception e)
-        {
-            throw new RuntimeException(e);
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public String toString()
-    {
-        return String.format("%s p=%s g=%s", getClass().getSimpleName(), _parser, _generator);
-    }
-}
diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketFactory.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketFactory.java
deleted file mode 100644
index ac1225f..0000000
--- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketFactory.java
+++ /dev/null
@@ -1,465 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.websocket;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Queue;
-import java.util.concurrent.ConcurrentLinkedQueue;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jetty.http.HttpException;
-import org.eclipse.jetty.http.HttpParser;
-import org.eclipse.jetty.io.ConnectedEndPoint;
-import org.eclipse.jetty.server.AbstractHttpConnection;
-import org.eclipse.jetty.server.BlockingHttpConnection;
-import org.eclipse.jetty.util.QuotedStringTokenizer;
-import org.eclipse.jetty.util.component.AbstractLifeCycle;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-
-/**
- * Factory to create WebSocket connections
- */
-public class WebSocketFactory extends AbstractLifeCycle
-{
-    private static final Logger LOG = Log.getLogger(WebSocketFactory.class);
-    private final Queue<WebSocketServletConnection> connections = new ConcurrentLinkedQueue<WebSocketServletConnection>();
-
-    public interface Acceptor
-    {
-        /* ------------------------------------------------------------ */
-        /**
-         * <p>Factory method that applications needs to implement to return a
-         * {@link WebSocket} object.</p>
-         * @param request the incoming HTTP upgrade request
-         * @param protocol the websocket sub protocol
-         * @return a new {@link WebSocket} object that will handle websocket events.
-         */
-        WebSocket doWebSocketConnect(HttpServletRequest request, String protocol);
-
-        /* ------------------------------------------------------------ */
-        /**
-         * <p>Checks the origin of an incoming WebSocket handshake request.</p>
-         * @param request the incoming HTTP upgrade request
-         * @param origin the origin URI
-         * @return boolean to indicate that the origin is acceptable.
-         */
-        boolean checkOrigin(HttpServletRequest request, String origin);
-    }
-
-    private final Map<String,Class<? extends Extension>> _extensionClasses = new HashMap<String, Class<? extends Extension>>();
-    {
-        _extensionClasses.put("identity",IdentityExtension.class);
-        _extensionClasses.put("fragment",FragmentExtension.class);
-        _extensionClasses.put("x-deflate-frame",DeflateFrameExtension.class);
-    }
-
-    private final Acceptor _acceptor;
-    private WebSocketBuffers _buffers;
-    private int _maxIdleTime = 300000;
-    private int _maxTextMessageSize = 16 * 1024;
-    private int _maxBinaryMessageSize = -1;
-    private int _minVersion;
-
-    public WebSocketFactory(Acceptor acceptor)
-    {
-        this(acceptor, 64 * 1024, WebSocketConnectionRFC6455.VERSION);
-    }
-
-    public WebSocketFactory(Acceptor acceptor, int bufferSize)
-    {
-        this(acceptor, bufferSize, WebSocketConnectionRFC6455.VERSION);
-    }
-
-    public WebSocketFactory(Acceptor acceptor, int bufferSize, int minVersion)
-    {
-        _buffers = new WebSocketBuffers(bufferSize);
-        _acceptor = acceptor;
-        _minVersion=WebSocketConnectionRFC6455.VERSION;
-    }
-
-    public int getMinVersion()
-    {
-        return _minVersion;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param minVersion The minimum support version (default RCF6455.VERSION == 13 )
-     */
-    public void setMinVersion(int minVersion)
-    {
-        _minVersion = minVersion;
-    }
-
-    /**
-     * @return A modifiable map of extension name to extension class
-     */
-    public Map<String,Class<? extends Extension>> getExtensionClassesMap()
-    {
-        return _extensionClasses;
-    }
-
-    /**
-     * Get the maxIdleTime.
-     *
-     * @return the maxIdleTime
-     */
-    public long getMaxIdleTime()
-    {
-        return _maxIdleTime;
-    }
-
-    /**
-     * Set the maxIdleTime.
-     *
-     * @param maxIdleTime the maxIdleTime to set
-     */
-    public void setMaxIdleTime(int maxIdleTime)
-    {
-        _maxIdleTime = maxIdleTime;
-    }
-
-    /**
-     * Get the bufferSize.
-     *
-     * @return the bufferSize
-     */
-    public int getBufferSize()
-    {
-        return _buffers.getBufferSize();
-    }
-
-    /**
-     * Set the bufferSize.
-     *
-     * @param bufferSize the bufferSize to set
-     */
-    public void setBufferSize(int bufferSize)
-    {
-        if (bufferSize != getBufferSize())
-            _buffers = new WebSocketBuffers(bufferSize);
-    }
-
-    /**
-     * @return The initial maximum text message size (in characters) for a connection
-     */
-    public int getMaxTextMessageSize()
-    {
-        return _maxTextMessageSize;
-    }
-
-    /**
-     * Set the initial maximum text message size for a connection. This can be changed by
-     * the application calling {@link WebSocket.Connection#setMaxTextMessageSize(int)}.
-     * @param maxTextMessageSize The default maximum text message size (in characters) for a connection
-     */
-    public void setMaxTextMessageSize(int maxTextMessageSize)
-    {
-        _maxTextMessageSize = maxTextMessageSize;
-    }
-
-    /**
-     * @return The initial maximum binary message size (in bytes)  for a connection
-     */
-    public int getMaxBinaryMessageSize()
-    {
-        return _maxBinaryMessageSize;
-    }
-
-    /**
-     * Set the initial maximum binary message size for a connection. This can be changed by
-     * the application calling {@link WebSocket.Connection#setMaxBinaryMessageSize(int)}.
-     * @param maxBinaryMessageSize The default maximum binary message size (in bytes) for a connection
-     */
-    public void setMaxBinaryMessageSize(int maxBinaryMessageSize)
-    {
-        _maxBinaryMessageSize = maxBinaryMessageSize;
-    }
-
-    @Override
-    protected void doStop() throws Exception
-    {
-        closeConnections();
-    }
-
-    /**
-     * Upgrade the request/response to a WebSocket Connection.
-     * <p>This method will not normally return, but will instead throw a
-     * UpgradeConnectionException, to exit HTTP handling and initiate
-     * WebSocket handling of the connection.
-     *
-     * @param request   The request to upgrade
-     * @param response  The response to upgrade
-     * @param websocket The websocket handler implementation to use
-     * @param protocol  The websocket protocol
-     * @throws IOException in case of I/O errors
-     */
-    public void upgrade(HttpServletRequest request, HttpServletResponse response, WebSocket websocket, String protocol)
-            throws IOException
-    {
-        if (!"websocket".equalsIgnoreCase(request.getHeader("Upgrade")))
-            throw new IllegalStateException("!Upgrade:websocket");
-        if (!"HTTP/1.1".equals(request.getProtocol()))
-            throw new IllegalStateException("!HTTP/1.1");
-
-        int draft = request.getIntHeader("Sec-WebSocket-Version");
-        if (draft < 0) {
-            // Old pre-RFC version specifications (header not present in RFC-6455)
-            draft = request.getIntHeader("Sec-WebSocket-Draft");
-        }
-        // Remember requested version for possible error message later
-        int requestedVersion = draft;
-        AbstractHttpConnection http = AbstractHttpConnection.getCurrentConnection();
-        if (http instanceof BlockingHttpConnection)
-            throw new IllegalStateException("Websockets not supported on blocking connectors");
-        ConnectedEndPoint endp = (ConnectedEndPoint)http.getEndPoint();
-
-        List<String> extensions_requested = new ArrayList<String>();
-        @SuppressWarnings("unchecked")
-        Enumeration<String> e = request.getHeaders("Sec-WebSocket-Extensions");
-        while (e.hasMoreElements())
-        {
-            QuotedStringTokenizer tok = new QuotedStringTokenizer(e.nextElement(),",");
-            while (tok.hasMoreTokens())
-            {
-                extensions_requested.add(tok.nextToken());
-            }
-        }
-
-        final WebSocketServletConnection connection;
-        if (draft<_minVersion)
-            draft=Integer.MAX_VALUE;
-        switch (draft)
-        {
-            case -1: // unspecified draft/version (such as early OSX Safari 5.1 and iOS 5.x)
-            case 0: // Old school draft/version
-            {
-                connection = new WebSocketServletConnectionD00(this, websocket, endp, _buffers, http.getTimeStamp(), _maxIdleTime, protocol);
-                break;
-            }
-            case 1:
-            case 2:
-            case 3:
-            case 4:
-            case 5:
-            case 6:
-            {
-                connection = new WebSocketServletConnectionD06(this, websocket, endp, _buffers, http.getTimeStamp(), _maxIdleTime, protocol);
-                break;
-            }
-            case 7:
-            case 8:
-            {
-                List<Extension> extensions = initExtensions(extensions_requested, 8 - WebSocketConnectionD08.OP_EXT_DATA, 16 - WebSocketConnectionD08.OP_EXT_CTRL, 3);
-                connection = new WebSocketServletConnectionD08(this, websocket, endp, _buffers, http.getTimeStamp(), _maxIdleTime, protocol, extensions, draft);
-                break;
-            }
-            case WebSocketConnectionRFC6455.VERSION: // RFC 6455 Version
-            {
-                List<Extension> extensions = initExtensions(extensions_requested, 8 - WebSocketConnectionRFC6455.OP_EXT_DATA, 16 - WebSocketConnectionRFC6455.OP_EXT_CTRL, 3);
-                connection = new WebSocketServletConnectionRFC6455(this, websocket, endp, _buffers, http.getTimeStamp(), _maxIdleTime, protocol, extensions, draft);
-                break;
-            }
-            default:
-            {
-                // Per RFC 6455 - 4.4 - Supporting Multiple Versions of WebSocket Protocol
-                // Using the examples as outlined
-                String versions="13";
-                if (_minVersion<=8)
-                    versions+=", 8";
-                if (_minVersion<=6)
-                    versions+=", 6";
-                if (_minVersion<=0)
-                    versions+=", 0";
-                    
-                response.setHeader("Sec-WebSocket-Version", versions);
-
-                // Make error clear for developer / end-user
-                StringBuilder err = new StringBuilder();
-                err.append("Unsupported websocket client version specification ");
-                if(requestedVersion >= 0) {
-                    err.append("[").append(requestedVersion).append("]");
-                } else {
-                    err.append("<Unspecified, likely a pre-draft version of websocket>");
-                }
-                err.append(", configured minVersion [").append(_minVersion).append("]");
-                err.append(", reported supported versions [").append(versions).append("]");
-                LOG.warn(err.toString()); // Log it
-                // use spec language for unsupported versions
-                throw new HttpException(400, "Unsupported websocket version specification"); // Tell client
-            }
-        }
-
-        addConnection(connection);
-
-        // Set the defaults
-        connection.getConnection().setMaxBinaryMessageSize(_maxBinaryMessageSize);
-        connection.getConnection().setMaxTextMessageSize(_maxTextMessageSize);
-
-        // Let the connection finish processing the handshake
-        connection.handshake(request, response, protocol);
-        response.flushBuffer();
-
-        // Give the connection any unused data from the HTTP connection.
-        connection.fillBuffersFrom(((HttpParser)http.getParser()).getHeaderBuffer());
-        connection.fillBuffersFrom(((HttpParser)http.getParser()).getBodyBuffer());
-
-        // Tell jetty about the new connection
-        LOG.debug("Websocket upgrade {} {} {} {}",request.getRequestURI(),draft,protocol,connection);
-        request.setAttribute("org.eclipse.jetty.io.Connection", connection);
-    }
-
-    protected String[] parseProtocols(String protocol)
-    {
-        if (protocol == null)
-            return new String[]{null};
-        protocol = protocol.trim();
-        if (protocol == null || protocol.length() == 0)
-            return new String[]{null};
-        String[] passed = protocol.split("\\s*,\\s*");
-        String[] protocols = new String[passed.length + 1];
-        System.arraycopy(passed, 0, protocols, 0, passed.length);
-        return protocols;
-    }
-
-    public boolean acceptWebSocket(HttpServletRequest request, HttpServletResponse response)
-            throws IOException
-    {
-        if ("websocket".equalsIgnoreCase(request.getHeader("Upgrade")))
-        {
-            String origin = request.getHeader("Origin");
-            if (origin==null)
-                origin = request.getHeader("Sec-WebSocket-Origin");
-            if (!_acceptor.checkOrigin(request,origin))
-            {
-                response.sendError(HttpServletResponse.SC_FORBIDDEN);
-                return false;
-            }
-
-            // Try each requested protocol
-            WebSocket websocket = null;
-
-            @SuppressWarnings("unchecked")
-            Enumeration<String> protocols = request.getHeaders("Sec-WebSocket-Protocol");
-            String protocol=null;
-            while (protocol==null && protocols!=null && protocols.hasMoreElements())
-            {
-                String candidate = protocols.nextElement();
-                for (String p : parseProtocols(candidate))
-                {
-                    websocket = _acceptor.doWebSocketConnect(request, p);
-                    if (websocket != null)
-                    {
-                        protocol = p;
-                        break;
-                    }
-                }
-            }
-
-            // Did we get a websocket?
-            if (websocket == null)
-            {
-                // Try with no protocol
-                websocket = _acceptor.doWebSocketConnect(request, null);
-
-                if (websocket==null)
-                {
-                    response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
-                    return false;
-                }
-            }
-
-            // Send the upgrade
-            upgrade(request, response, websocket, protocol);
-            return true;
-        }
-
-        return false;
-    }
-
-    public List<Extension> initExtensions(List<String> requested,int maxDataOpcodes,int maxControlOpcodes,int maxReservedBits)
-    {
-        List<Extension> extensions = new ArrayList<Extension>();
-        for (String rExt : requested)
-        {
-            QuotedStringTokenizer tok = new QuotedStringTokenizer(rExt,";");
-            String extName=tok.nextToken().trim();
-            Map<String,String> parameters = new HashMap<String,String>();
-            while (tok.hasMoreTokens())
-            {
-                QuotedStringTokenizer nv = new QuotedStringTokenizer(tok.nextToken().trim(),"=");
-                String name=nv.nextToken().trim();
-                String value=nv.hasMoreTokens()?nv.nextToken().trim():null;
-                parameters.put(name,value);
-            }
-
-            Extension extension = newExtension(extName);
-
-            if (extension==null)
-                continue;
-
-            if (extension.init(parameters))
-            {
-                LOG.debug("add {} {}",extName,parameters);
-                extensions.add(extension);
-            }
-        }
-        LOG.debug("extensions={}",extensions);
-        return extensions;
-    }
-
-    private Extension newExtension(String name)
-    {
-        try
-        {
-            Class<? extends Extension> extClass = _extensionClasses.get(name);
-            if (extClass!=null)
-                return extClass.newInstance();
-        }
-        catch (Exception e)
-        {
-            LOG.warn(e);
-        }
-
-        return null;
-    }
-
-    protected boolean addConnection(WebSocketServletConnection connection)
-    {
-        return isRunning() && connections.add(connection);
-    }
-
-    protected boolean removeConnection(WebSocketServletConnection connection)
-    {
-        return connections.remove(connection);
-    }
-
-    protected void closeConnections()
-    {
-        for (WebSocketServletConnection connection : connections)
-            connection.shutdown();
-    }
-}
diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketGenerator.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketGenerator.java
deleted file mode 100644
index 10046ac..0000000
--- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketGenerator.java
+++ /dev/null
@@ -1,33 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.websocket;
-
-import java.io.IOException;
-
-
-
-/* ------------------------------------------------------------ */
-/** WebSocketGenerator.
- */
-public interface WebSocketGenerator
-{
-    int flush() throws IOException;
-    boolean isBufferEmpty();
-    void addFrame(byte flags,byte opcode, byte[] content, int offset, int length) throws IOException;
-}
diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketGeneratorD00.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketGeneratorD00.java
deleted file mode 100644
index 3ae82b6..0000000
--- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketGeneratorD00.java
+++ /dev/null
@@ -1,173 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.websocket;
-
-import java.io.IOException;
-import java.math.BigInteger;
-
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.io.EofException;
-
-
-/* ------------------------------------------------------------ */
-/** WebSocketGenerator.
- * This class generates websocket packets.
- * It is fully synchronized because it is likely that async
- * threads will call the addMessage methods while other
- * threads are flushing the generator.
- */
-public class WebSocketGeneratorD00 implements WebSocketGenerator
-{
-    final private WebSocketBuffers _buffers;
-    final private EndPoint _endp;
-    private Buffer _buffer;
-
-    public WebSocketGeneratorD00(WebSocketBuffers buffers, EndPoint endp)
-    {
-        _buffers=buffers;
-        _endp=endp;
-    }
-    
-    public synchronized void addFrame(byte flags, byte opcode,byte[] content, int offset, int length) throws IOException
-    {
-        long blockFor=_endp.getMaxIdleTime();
-        
-        if (_buffer==null)
-            _buffer=_buffers.getDirectBuffer();
-
-        if (_buffer.space() == 0)
-            expelBuffer(blockFor);
-
-        bufferPut(opcode, blockFor);
-
-        if (isLengthFrame(opcode))
-        {
-            // Send a length delimited frame
-
-            // How many bytes we need for the length ?
-            // We have 7 bits available, so log2(length) / 7 + 1
-            // For example, 50000 bytes is 2 8-bytes: 11000011 01010000
-            // but we need to write it in 3 7-bytes 0000011 0000110 1010000
-            // 65536 == 1 00000000 00000000 => 100 0000000 0000000
-            int lengthBytes = new BigInteger(String.valueOf(length)).bitLength() / 7 + 1;
-            for (int i = lengthBytes - 1; i > 0; --i)
-            {
-                byte lengthByte = (byte)(0x80 | (0x7F & (length >> 7 * i)));
-                bufferPut(lengthByte, blockFor);
-            }
-            bufferPut((byte)(0x7F & length), blockFor);
-        }
-
-        int remaining = length;
-        while (remaining > 0)
-        {
-            int chunk = remaining < _buffer.space() ? remaining : _buffer.space();
-            _buffer.put(content, offset + (length - remaining), chunk);
-            remaining -= chunk;
-            if (_buffer.space() > 0)
-            {
-                if (!isLengthFrame(opcode))
-                    _buffer.put((byte)0xFF);
-                // Gently flush the data, issuing a non-blocking write
-                flushBuffer();
-            }
-            else
-            {
-                // Forcibly flush the data, issuing a blocking write
-                expelBuffer(blockFor);
-                if (remaining == 0)
-                {
-                    if (!isLengthFrame(opcode))
-                        _buffer.put((byte)0xFF);
-                    // Gently flush the data, issuing a non-blocking write
-                    flushBuffer();
-                }
-            }
-        }
-    }
-
-    private synchronized boolean isLengthFrame(byte frame)
-    {
-        return (frame & WebSocketConnectionD00.LENGTH_FRAME) == WebSocketConnectionD00.LENGTH_FRAME;
-    }
-
-    private synchronized void bufferPut(byte datum, long blockFor) throws IOException
-    {
-        if (_buffer==null)
-            _buffer=_buffers.getDirectBuffer();
-        _buffer.put(datum);
-        if (_buffer.space() == 0)
-            expelBuffer(blockFor);
-    }
-
-    public synchronized int flush(int blockFor) throws IOException
-    {
-        return expelBuffer(blockFor);
-    }
-
-    public synchronized int flush() throws IOException
-    {
-        int flushed = flushBuffer();
-        if (_buffer!=null && _buffer.length()==0)
-        {
-            _buffers.returnBuffer(_buffer);
-            _buffer=null;
-        }
-        return flushed;
-    }
-
-    private synchronized int flushBuffer() throws IOException
-    {
-        if (!_endp.isOpen())
-            throw new EofException();
-
-        if (_buffer!=null && _buffer.hasContent())
-            return _endp.flush(_buffer);
-
-        return 0;
-    }
-
-    private synchronized int expelBuffer(long blockFor) throws IOException
-    {
-        if (_buffer==null)
-            return 0;
-        int result = flushBuffer();
-        _buffer.compact();
-        if (!_endp.isBlocking())
-        {
-            while (_buffer.space()==0)
-            {
-                boolean ready = _endp.blockWritable(blockFor);
-                if (!ready)
-                    throw new IOException("Write timeout");
-
-                result += flushBuffer();
-                _buffer.compact();
-            }
-        }
-        return result;
-    }
-
-    public synchronized boolean isBufferEmpty()
-    {
-        return _buffer==null || _buffer.length()==0;
-    }
-    
-}
diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketGeneratorD06.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketGeneratorD06.java
deleted file mode 100644
index a417670..0000000
--- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketGeneratorD06.java
+++ /dev/null
@@ -1,234 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.websocket;
-
-import java.io.IOException;
-
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.io.EofException;
-
-
-/* ------------------------------------------------------------ */
-/** WebSocketGenerator.
- * This class generates websocket packets.
- * It is fully synchronized because it is likely that async
- * threads will call the addMessage methods while other
- * threads are flushing the generator.
- */
-public class WebSocketGeneratorD06 implements WebSocketGenerator
-{
-    final private WebSocketBuffers _buffers;
-    final private EndPoint _endp;
-    private Buffer _buffer;
-    private final byte[] _mask=new byte[4];
-    private int _m;
-    private boolean _opsent;
-    private final MaskGen _maskGen;
-
-    public WebSocketGeneratorD06(WebSocketBuffers buffers, EndPoint endp)
-    {
-        _buffers=buffers;
-        _endp=endp;
-        _maskGen=null;
-    }
-
-    public WebSocketGeneratorD06(WebSocketBuffers buffers, EndPoint endp, MaskGen maskGen)
-    {
-        _buffers=buffers;
-        _endp=endp;
-        _maskGen=maskGen;
-    }
-
-    public synchronized void addFrame(byte flags, byte opcode, byte[] content, int offset, int length) throws IOException
-    {
-        // System.err.printf("<< %s %s %s\n",TypeUtil.toHexString(flags),TypeUtil.toHexString(opcode),length);
-
-        long blockFor=_endp.getMaxIdleTime();
-
-        if (_buffer==null)
-            _buffer=(_maskGen!=null)?_buffers.getBuffer():_buffers.getDirectBuffer();
-
-        boolean last=WebSocketConnectionD06.isLastFrame(flags);
-        opcode=(byte)(((0xf&flags)<<4)+0xf&opcode);
-
-        int space=(_maskGen!=null)?14:10;
-
-        do
-        {
-            opcode = _opsent?WebSocketConnectionD06.OP_CONTINUATION:opcode;
-            _opsent=true;
-
-            int payload=length;
-            if (payload+space>_buffer.capacity())
-            {
-                // We must fragement, so clear FIN bit
-                opcode&=(byte)0x7F; // Clear the FIN bit
-                payload=_buffer.capacity()-space;
-            }
-            else if (last)
-                opcode|=(byte)0x80; // Set the FIN bit
-
-            // ensure there is space for header
-            if (_buffer.space() <= space)
-                expelBuffer(blockFor);
-
-            // write mask
-            if ((_maskGen!=null))
-            {
-                _maskGen.genMask(_mask);
-                _m=0;
-                _buffer.put(_mask);
-            }
-
-            // write the opcode and length
-            if (payload>0xffff)
-            {
-                bufferPut(new byte[]{
-                        opcode,
-                        (byte)0x7f,
-                        (byte)0,
-                        (byte)0,
-                        (byte)0,
-                        (byte)0,
-                        (byte)((payload>>24)&0xff),
-                        (byte)((payload>>16)&0xff),
-                        (byte)((payload>>8)&0xff),
-                        (byte)(payload&0xff)});
-            }
-            else if (payload >=0x7e)
-            {
-                bufferPut(new byte[]{
-                        opcode,
-                        (byte)0x7e,
-                        (byte)(payload>>8),
-                        (byte)(payload&0xff)});
-            }
-            else
-            {
-                bufferPut(opcode);
-                bufferPut((byte)payload);
-            }
-
-            // write payload
-            int remaining = payload;
-            while (remaining > 0)
-            {
-                _buffer.compact();
-                int chunk = remaining < _buffer.space() ? remaining : _buffer.space();
-
-                if ((_maskGen!=null))
-                {
-                    for (int i=0;i<chunk;i++)
-                        bufferPut(content[offset+ (payload-remaining)+i]);
-                }
-                else
-                    _buffer.put(content, offset + (payload - remaining), chunk);
-
-                remaining -= chunk;
-                if (_buffer.space() > 0)
-                {
-                    // Gently flush the data, issuing a non-blocking write
-                    flushBuffer();
-                }
-                else
-                {
-                    // Forcibly flush the data, issuing a blocking write
-                    expelBuffer(blockFor);
-                    if (remaining == 0)
-                    {
-                        // Gently flush the data, issuing a non-blocking write
-                        flushBuffer();
-                    }
-                }
-            }
-            offset+=payload;
-            length-=payload;
-        }
-        while (length>0);
-        _opsent=!last;
-    }
-
-    private synchronized void bufferPut(byte[] data) throws IOException
-    {
-        if (_maskGen!=null)
-            for (int i=0;i<data.length;i++)
-                data[i]^=_mask[+_m++%4];
-        _buffer.put(data);
-    }
-
-    private synchronized void bufferPut(byte data) throws IOException
-    {
-        _buffer.put((byte)(data^_mask[+_m++%4]));
-    }
-
-    public synchronized int flush(int blockFor) throws IOException
-    {
-        return expelBuffer(blockFor);
-    }
-
-    public synchronized int flush() throws IOException
-    {
-        int flushed = flushBuffer();
-        if (_buffer!=null && _buffer.length()==0)
-        {
-            _buffers.returnBuffer(_buffer);
-            _buffer=null;
-        }
-        return flushed;
-    }
-
-    private synchronized int flushBuffer() throws IOException
-    {
-        if (!_endp.isOpen())
-            throw new EofException();
-
-        if (_buffer!=null)
-            return _endp.flush(_buffer);
-
-        return 0;
-    }
-
-    private synchronized int expelBuffer(long blockFor) throws IOException
-    {
-        if (_buffer==null)
-            return 0;
-        int result = flushBuffer();
-        _buffer.compact();
-        if (!_endp.isBlocking())
-        {
-            while (_buffer.space()==0)
-            {
-                boolean ready = _endp.blockWritable(blockFor);
-                if (!ready)
-                    throw new IOException("Write timeout");
-
-                result += flushBuffer();
-                _buffer.compact();
-            }
-        }
-        return result;
-    }
-
-    public synchronized boolean isBufferEmpty()
-    {
-        return _buffer==null || _buffer.length()==0;
-    }
-
-}
diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketGeneratorD08.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketGeneratorD08.java
deleted file mode 100644
index 59045c3..0000000
--- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketGeneratorD08.java
+++ /dev/null
@@ -1,308 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.websocket;
-
-import java.io.IOException;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.io.EofException;
-
-/**
- * WebSocketGenerator. This class generates websocket packets. It is fully synchronized because it is likely that async threads will call the addMessage methods
- * while other threads are flushing the generator.
- */
-public class WebSocketGeneratorD08 implements WebSocketGenerator
-{
-    private final Lock _lock = new ReentrantLock();
-    private final WebSocketBuffers _buffers;
-    private final EndPoint _endp;
-    private final byte[] _mask = new byte[4];
-    private final MaskGen _maskGen;
-    private Buffer _buffer;
-    private int _m;
-    private boolean _opsent;
-    private boolean _closed;
-
-    public WebSocketGeneratorD08(WebSocketBuffers buffers, EndPoint endp)
-    {
-        this(buffers, endp, null);
-    }
-
-    public WebSocketGeneratorD08(WebSocketBuffers buffers, EndPoint endp, MaskGen maskGen)
-    {
-        _buffers = buffers;
-        _endp = endp;
-        _maskGen = maskGen;
-    }
-
-    public Buffer getBuffer()
-    {
-        _lock.lock();
-        try
-        {
-            return _buffer;
-        }
-        finally
-        {
-            _lock.unlock();
-        }
-    }
-
-    public void addFrame(byte flags, byte opcode, byte[] content, int offset, int length) throws IOException
-    {
-        _lock.lock();
-        try
-        {
-            if (_closed)
-                throw new EofException("Closed");
-            if (opcode == WebSocketConnectionRFC6455.OP_CLOSE)
-                _closed = true;
-
-            boolean mask = _maskGen != null;
-
-            if (_buffer == null)
-                _buffer = mask ? _buffers.getBuffer() : _buffers.getDirectBuffer();
-
-            boolean last = WebSocketConnectionD08.isLastFrame(flags);
-
-            int space = mask ? 14 : 10;
-
-            do
-            {
-                opcode = _opsent ? WebSocketConnectionD08.OP_CONTINUATION : opcode;
-                opcode = (byte)(((0xf & flags) << 4) + (0xf & opcode));
-                _opsent = true;
-
-                int payload = length;
-                if (payload + space > _buffer.capacity())
-                {
-                    // We must fragement, so clear FIN bit
-                    opcode = (byte)(opcode & 0x7F); // Clear the FIN bit
-                    payload = _buffer.capacity() - space;
-                }
-                else if (last)
-                    opcode = (byte)(opcode | 0x80); // Set the FIN bit
-
-                // ensure there is space for header
-                if (_buffer.space() <= space)
-                {
-                    flushBuffer();
-                    if (_buffer.space() <= space)
-                        flush();
-                }
-
-                // write the opcode and length
-                if (payload > 0xffff)
-                {
-                    _buffer.put(new byte[]{
-                            opcode,
-                            mask ? (byte)0xff : (byte)0x7f,
-                            (byte)0,
-                            (byte)0,
-                            (byte)0,
-                            (byte)0,
-                            (byte)((payload >> 24) & 0xff),
-                            (byte)((payload >> 16) & 0xff),
-                            (byte)((payload >> 8) & 0xff),
-                            (byte)(payload & 0xff)});
-                }
-                else if (payload >= 0x7e)
-                {
-                    _buffer.put(new byte[]{
-                            opcode,
-                            mask ? (byte)0xfe : (byte)0x7e,
-                            (byte)(payload >> 8),
-                            (byte)(payload & 0xff)});
-                }
-                else
-                {
-                    _buffer.put(new byte[]{
-                            opcode,
-                            (byte)(mask ? (0x80 | payload) : payload)});
-                }
-
-                // write mask
-                if (mask)
-                {
-                    _maskGen.genMask(_mask);
-                    _m = 0;
-                    _buffer.put(_mask);
-                }
-
-                // write payload
-                int remaining = payload;
-                while (remaining > 0)
-                {
-                    _buffer.compact();
-                    int chunk = remaining < _buffer.space() ? remaining : _buffer.space();
-
-                    if (mask)
-                    {
-                        for (int i = 0; i < chunk; i++)
-                            _buffer.put((byte)(content[offset + (payload - remaining) + i] ^ _mask[+_m++ % 4]));
-                    }
-                    else
-                        _buffer.put(content, offset + (payload - remaining), chunk);
-
-                    remaining -= chunk;
-                    if (_buffer.space() > 0)
-                    {
-                        // Gently flush the data, issuing a non-blocking write
-                        flushBuffer();
-                    }
-                    else
-                    {
-                        // Forcibly flush the data, issuing a blocking write
-                        flush();
-                        if (remaining == 0)
-                        {
-                            // Gently flush the data, issuing a non-blocking write
-                            flushBuffer();
-                        }
-                    }
-                }
-                offset += payload;
-                length -= payload;
-            }
-            while (length > 0);
-            _opsent = !last;
-
-            if (_buffer != null && _buffer.length() == 0)
-            {
-                _buffers.returnBuffer(_buffer);
-                _buffer = null;
-            }
-        }
-        finally
-        {
-            _lock.unlock();
-        }
-    }
-
-    public int flushBuffer() throws IOException
-    {
-        if (!_lock.tryLock())
-            return 0;
-
-        try
-        {
-            if (!_endp.isOpen())
-                throw new EofException();
-
-            if (_buffer != null)
-            {
-                int flushed = _buffer.hasContent() ? _endp.flush(_buffer) : 0;
-                if (_closed && _buffer.length() == 0)
-                    _endp.shutdownOutput();
-                return flushed;
-            }
-
-            return 0;
-        }
-        finally
-        {
-            _lock.unlock();
-        }
-    }
-
-    public int flush() throws IOException
-    {
-        if (!_lock.tryLock())
-            return 0;
-
-        try
-        {
-            if (_buffer == null)
-                return 0;
-
-            int result = flushBuffer();
-            if (!_endp.isBlocking())
-            {
-                long now = System.currentTimeMillis();
-                long end = now + _endp.getMaxIdleTime();
-                while (_buffer.length() > 0)
-                {
-                    boolean ready = _endp.blockWritable(end - now);
-                    if (!ready)
-                    {
-                        now = System.currentTimeMillis();
-                        if (now < end)
-                            continue;
-                        throw new IOException("Write timeout");
-                    }
-
-                    result += flushBuffer();
-                }
-            }
-            _buffer.compact();
-            return result;
-        }
-        finally
-        {
-            _lock.unlock();
-        }
-    }
-
-    public boolean isBufferEmpty()
-    {
-        _lock.lock();
-        try
-        {
-            return _buffer == null || _buffer.length() == 0;
-        }
-        finally
-        {
-            _lock.unlock();
-        }
-    }
-
-    public void returnBuffer()
-    {
-        _lock.lock();
-        try
-        {
-            if (_buffer != null && _buffer.length() == 0)
-            {
-                _buffers.returnBuffer(_buffer);
-                _buffer = null;
-            }
-        }
-        finally
-        {
-            _lock.unlock();
-        }
-    }
-
-    @Override
-    public String toString()
-    {
-        // Do NOT use synchronized (this)
-        // because it's very easy to deadlock when debugging is enabled.
-        // We do a best effort to print the right toString() and that's it.
-        Buffer buffer = _buffer;
-        return String.format("%s@%x closed=%b buffer=%d",
-                getClass().getSimpleName(),
-                hashCode(),
-                _closed,
-                buffer == null ? -1 : buffer.length());
-    }
-}
diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketGeneratorRFC6455.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketGeneratorRFC6455.java
deleted file mode 100644
index 9e3e919..0000000
--- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketGeneratorRFC6455.java
+++ /dev/null
@@ -1,312 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.websocket;
-
-import java.io.IOException;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.io.EofException;
-
-
-/**
- * WebSocketGenerator.
- * This class generates websocket packets.
- * It is fully synchronized because it is likely that async
- * threads will call the addMessage methods while other
- * threads are flushing the generator.
- */
-public class WebSocketGeneratorRFC6455 implements WebSocketGenerator
-{
-    private final Lock _lock = new ReentrantLock();
-    private final WebSocketBuffers _buffers;
-    private final EndPoint _endp;
-    private final byte[] _mask = new byte[4];
-    private final MaskGen _maskGen;
-    private Buffer _buffer;
-    private int _m;
-    private boolean _opsent;
-    private boolean _closed;
-
-    public WebSocketGeneratorRFC6455(WebSocketBuffers buffers, EndPoint endp)
-    {
-        this(buffers, endp, null);
-    }
-
-    public WebSocketGeneratorRFC6455(WebSocketBuffers buffers, EndPoint endp, MaskGen maskGen)
-    {
-        _buffers = buffers;
-        _endp = endp;
-        _maskGen = maskGen;
-    }
-
-    public Buffer getBuffer()
-    {
-        _lock.lock();
-        try
-        {
-            return _buffer;
-        }
-        finally
-        {
-            _lock.unlock();
-        }
-    }
-
-    public void addFrame(byte flags, byte opcode, byte[] content, int offset, int length) throws IOException
-    {
-        _lock.lock();
-        try
-        {
-            if (_closed)
-                throw new EofException("Closed");
-            if (opcode == WebSocketConnectionRFC6455.OP_CLOSE)
-                _closed = true;
-
-            boolean mask = _maskGen != null;
-
-            if (_buffer == null)
-                _buffer = mask ? _buffers.getBuffer() : _buffers.getDirectBuffer();
-
-            boolean last = WebSocketConnectionRFC6455.isLastFrame(flags);
-
-            int space = mask ? 14 : 10;
-
-            do
-            {
-                opcode = _opsent ? WebSocketConnectionRFC6455.OP_CONTINUATION : opcode;
-                opcode = (byte)(((0xf & flags) << 4) + (0xf & opcode));
-                _opsent = true;
-
-                int payload = length;
-                if (payload + space > _buffer.capacity())
-                {
-                    // We must fragement, so clear FIN bit
-                    opcode = (byte)(opcode & 0x7F); // Clear the FIN bit
-                    payload = _buffer.capacity() - space;
-                }
-                else if (last)
-                    opcode = (byte)(opcode | 0x80); // Set the FIN bit
-
-                // ensure there is space for header
-                if (_buffer.space() <= space)
-                {
-                    flushBuffer();
-                    if (_buffer.space() <= space)
-                        flush();
-                }
-
-                // write the opcode and length
-                if (payload > 0xffff)
-                {
-                    _buffer.put(new byte[]{
-                            opcode,
-                            mask ? (byte)0xff : (byte)0x7f,
-                            (byte)0,
-                            (byte)0,
-                            (byte)0,
-                            (byte)0,
-                            (byte)((payload >> 24) & 0xff),
-                            (byte)((payload >> 16) & 0xff),
-                            (byte)((payload >> 8) & 0xff),
-                            (byte)(payload & 0xff)});
-                }
-                else if (payload >= 0x7e)
-                {
-                    _buffer.put(new byte[]{
-                            opcode,
-                            mask ? (byte)0xfe : (byte)0x7e,
-                            (byte)(payload >> 8),
-                            (byte)(payload & 0xff)});
-                }
-                else
-                {
-                    _buffer.put(new byte[]{
-                            opcode,
-                            (byte)(mask ? (0x80 | payload) : payload)});
-                }
-
-                // write mask
-                if (mask)
-                {
-                    _maskGen.genMask(_mask);
-                    _m = 0;
-                    _buffer.put(_mask);
-                }
-
-                // write payload
-                int remaining = payload;
-                while (remaining > 0)
-                {
-                    _buffer.compact();
-                    int chunk = remaining < _buffer.space() ? remaining : _buffer.space();
-
-                    if (mask)
-                    {
-                        for (int i = 0; i < chunk; i++)
-                            _buffer.put((byte)(content[offset + (payload - remaining) + i] ^ _mask[+_m++ % 4]));
-                    }
-                    else
-                        _buffer.put(content, offset + (payload - remaining), chunk);
-
-                    remaining -= chunk;
-                    if (_buffer.space() > 0)
-                    {
-                        // Gently flush the data, issuing a non-blocking write
-                        flushBuffer();
-                    }
-                    else
-                    {
-                        // Forcibly flush the data, issuing a blocking write
-                        flush();
-                        if (remaining == 0)
-                        {
-                            // Gently flush the data, issuing a non-blocking write
-                            flushBuffer();
-                        }
-                    }
-                }
-                offset += payload;
-                length -= payload;
-            }
-            while (length > 0);
-            _opsent = !last;
-
-            if (_buffer != null && _buffer.length() == 0)
-            {
-                _buffers.returnBuffer(_buffer);
-                _buffer = null;
-            }
-        }
-        finally
-        {
-            _lock.unlock();
-        }
-    }
-
-    public int flushBuffer() throws IOException
-    {
-        if (!_lock.tryLock())
-            return 0;
-
-        try
-        {
-            if (!_endp.isOpen())
-                throw new EofException();
-
-            if (_buffer != null)
-            {
-                int flushed = _buffer.hasContent() ? _endp.flush(_buffer) : 0;
-                if (_closed && _buffer.length() == 0)
-                    _endp.shutdownOutput();
-                return flushed;
-            }
-
-            return 0;
-        }
-        finally
-        {
-            _lock.unlock();
-        }
-    }
-
-    public int flush() throws IOException
-    {
-        if (!_lock.tryLock())
-            return 0;
-
-        try
-        {
-            if (_buffer == null)
-                return 0;
-
-            int result = flushBuffer();
-            if (!_endp.isBlocking())
-            {
-                long now = System.currentTimeMillis();
-                long end = now + _endp.getMaxIdleTime();
-                while (_buffer.length() > 0)
-                {
-                    boolean ready = _endp.blockWritable(end - now);
-                    if (!ready)
-                    {
-                        now = System.currentTimeMillis();
-                        if (now < end)
-                            continue;
-                        throw new IOException("Write timeout");
-                    }
-
-                    result += flushBuffer();
-                }
-            }
-            _buffer.compact();
-            return result;
-        }
-        finally
-        {
-            _lock.unlock();
-        }
-    }
-
-    public boolean isBufferEmpty()
-    {
-        _lock.lock();
-        try
-        {
-            return _buffer == null || _buffer.length() == 0;
-        }
-        finally
-        {
-            _lock.unlock();
-        }
-    }
-
-    public void returnBuffer()
-    {
-        _lock.lock();
-        try
-        {
-            if (_buffer != null && _buffer.length() == 0)
-            {
-                _buffers.returnBuffer(_buffer);
-                _buffer = null;
-            }
-        }
-        finally
-        {
-            _lock.unlock();
-        }
-    }
-
-    @Override
-    public String toString()
-    {
-        // Do NOT use synchronized (this)
-        // because it's very easy to deadlock when debugging is enabled.
-        // We do a best effort to print the right toString() and that's it.
-        Buffer buffer = _buffer;
-        return String.format("%s@%x closed=%b buffer=%d",
-                getClass().getSimpleName(),
-                hashCode(),
-                _closed,
-                buffer == null ? -1 : buffer.length());
-    }
-}
diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketHandler.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketHandler.java
deleted file mode 100644
index 8a4b345..0000000
--- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketHandler.java
+++ /dev/null
@@ -1,57 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.websocket;
-
-import java.io.IOException;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jetty.server.Request;
-import org.eclipse.jetty.server.handler.HandlerWrapper;
-
-public abstract class WebSocketHandler extends HandlerWrapper implements WebSocketFactory.Acceptor
-{
-    private final WebSocketFactory _webSocketFactory=new WebSocketFactory(this,32*1024);
-    
-    public WebSocketFactory getWebSocketFactory()
-    {
-        return _webSocketFactory;
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
-    {
-        if (_webSocketFactory.acceptWebSocket(request,response) || response.isCommitted())
-        {
-            baseRequest.setHandled(true);
-            return;
-        }
-        super.handle(target,baseRequest,request,response);
-    }
-    
-    /* ------------------------------------------------------------ */
-    public boolean checkOrigin(HttpServletRequest request, String origin)
-    {
-        return true;
-    }
-    
-}
diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketParser.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketParser.java
deleted file mode 100644
index 866b69e..0000000
--- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketParser.java
+++ /dev/null
@@ -1,53 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.websocket;
-
-import org.eclipse.jetty.io.Buffer;
-
-
-
-/* ------------------------------------------------------------ */
-/**
- * Parser the WebSocket protocol.
- *
- */
-public interface WebSocketParser
-{
-    
-    /* ------------------------------------------------------------ */
-    /* ------------------------------------------------------------ */
-    /* ------------------------------------------------------------ */
-    public interface FrameHandler
-    {
-        void onFrame(byte flags, byte opcode, Buffer buffer);
-        void close(int code,String message);
-    }
-
-    Buffer getBuffer();
-
-    /**
-     * @return an indication of progress, normally bytes filled plus events parsed, or -1 for EOF
-     */
-    int parseNext();
-
-    boolean isBufferEmpty();
-
-    void fill(Buffer buffer);
-
-}
diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketParserD00.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketParserD00.java
deleted file mode 100644
index cdf879d..0000000
--- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketParserD00.java
+++ /dev/null
@@ -1,212 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.websocket;
-
-import java.io.IOException;
-
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.Buffers;
-import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.util.TypeUtil;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-
-
-
-
-/* ------------------------------------------------------------ */
-/**
- * Parser the WebSocket protocol.
- *
- */
-public class WebSocketParserD00 implements WebSocketParser
-{
-    private static final Logger LOG = Log.getLogger(WebSocketParserD00.class);
-
-    public static final int STATE_START=0;
-    public static final int STATE_SENTINEL_DATA=1;
-    public static final int STATE_LENGTH=2;
-    public static final int STATE_DATA=3;
-
-    private final WebSocketBuffers _buffers;
-    private final EndPoint _endp;
-    private final FrameHandler _handler;
-    private int _state;
-    private Buffer _buffer;
-    private byte _opcode;
-    private int _length;
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param buffers The buffers to use for parsing.  Only the {@link Buffers#getBuffer()} is used.
-     * This should be a direct buffer if binary data is mostly used or an indirect buffer if utf-8 data
-     * is mostly used.
-     * @param endp the endpoint
-     * @param handler the handler to notify when a parse event occurs
-     */
-    public WebSocketParserD00(WebSocketBuffers buffers, EndPoint endp, FrameHandler handler)
-    {
-        _buffers=buffers;
-        _endp=endp;
-        _handler=handler;
-    }
-
-    /* ------------------------------------------------------------ */
-    public boolean isBufferEmpty()
-    {
-        return _buffer==null || _buffer.length()==0;
-    }
-
-    /* ------------------------------------------------------------ */
-    public Buffer getBuffer()
-    {
-        return _buffer;
-    }
-
-    /* ------------------------------------------------------------ */
-    /** Parse to next event.
-     * Parse to the next {@link WebSocketParser.FrameHandler} event or until no more data is
-     * available. Fill data from the {@link EndPoint} only as necessary.
-     * @return An indication of progress or otherwise. -1 indicates EOF, 0 indicates
-     * that no bytes were read and no messages parsed. A positive number indicates either
-     * the bytes filled or the messages parsed.
-     */
-    public int parseNext()
-    {
-        if (_buffer==null)
-            _buffer=_buffers.getBuffer();
-
-        int progress=0;
-
-        // Loop until an datagram call back or can't fill anymore
-        while(true)
-        {
-            int length=_buffer.length();
-
-            // Fill buffer if we need a byte or need length
-            if (length == 0 || _state==STATE_DATA && length<_length)
-            {
-                // compact to mark (set at start of data)
-                _buffer.compact();
-
-                // if no space, then the data is too big for buffer
-                if (_buffer.space() == 0)
-                    throw new IllegalStateException("FULL");
-
-                // catch IOExceptions (probably EOF) and try to parse what we have
-                try
-                {
-                    int filled=_endp.isOpen()?_endp.fill(_buffer):-1;
-                    if (filled<=0)
-                        return progress;
-                    progress+=filled;
-                    length=_buffer.length();
-                }
-                catch(IOException e)
-                {
-                    LOG.debug(e);
-                    return progress>0?progress:-1;
-                }
-            }
-
-
-            // Parse the buffer byte by byte (unless it is STATE_DATA)
-            byte b;
-            charloop: while (length-->0)
-            {
-                switch (_state)
-                {
-                    case STATE_START:
-                        b=_buffer.get();
-                        _opcode=b;
-                        if (_opcode<0)
-                        {
-                            _length=0;
-                            _state=STATE_LENGTH;
-                        }
-                        else
-                        {
-                            _state=STATE_SENTINEL_DATA;
-                            _buffer.mark(0);
-                        }
-                        continue;
-
-                    case STATE_SENTINEL_DATA:
-                        b=_buffer.get();
-                        if ((b&0xff)==0xff)
-                        {
-                            _state=STATE_START;
-                            int l=_buffer.getIndex()-_buffer.markIndex()-1;
-                            progress++;
-                            _handler.onFrame((byte)0,_opcode,_buffer.sliceFromMark(l));
-                            _buffer.setMarkIndex(-1);
-                            if (_buffer.length()==0)
-                            {
-                                _buffers.returnBuffer(_buffer);
-                                _buffer=null;
-                            }
-                            return progress;
-                        }
-                        continue;
-
-                    case STATE_LENGTH:
-                        b=_buffer.get();
-                        _length=_length<<7 | (0x7f&b);
-                        if (b>=0)
-                        {
-                            _state=STATE_DATA;
-                            _buffer.mark(0);
-                        }
-                        continue;
-
-                    case STATE_DATA:
-                        if (_buffer.markIndex()<0)
-                        if (_buffer.length()<_length)
-                            break charloop;
-                        Buffer data=_buffer.sliceFromMark(_length);
-                        _buffer.skip(_length);
-                        _state=STATE_START;
-                        progress++;
-                        _handler.onFrame((byte)0, _opcode, data);
-
-                        if (_buffer.length()==0)
-                        {
-                            _buffers.returnBuffer(_buffer);
-                            _buffer=null;
-                        }
-
-                        return progress;
-                }
-            }
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    public void fill(Buffer buffer)
-    {
-        if (buffer!=null && buffer.length()>0)
-        {
-            if (_buffer==null)
-                _buffer=_buffers.getBuffer();
-            _buffer.put(buffer);
-            buffer.clear();
-        }
-    }
-
-}
diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketParserD06.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketParserD06.java
deleted file mode 100644
index 7d110ae..0000000
--- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketParserD06.java
+++ /dev/null
@@ -1,310 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.websocket;
-
-import java.io.IOException;
-
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.Buffers;
-import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-
-
-
-/* ------------------------------------------------------------ */
-/**
- * Parser the WebSocket protocol.
- *
- */
-public class WebSocketParserD06 implements WebSocketParser
-{
-    private static final Logger LOG = Log.getLogger(WebSocketParserD06.class);
-
-    public enum State {
-
-        START(0), MASK(4), OPCODE(1), LENGTH_7(1), LENGTH_16(2), LENGTH_63(8), DATA(0), SKIP(1);
-
-        int _needs;
-
-        State(int needs)
-        {
-            _needs=needs;
-        }
-
-        int getNeeds()
-        {
-            return _needs;
-        }
-    }
-
-
-    private final WebSocketBuffers _buffers;
-    private final EndPoint _endp;
-    private final FrameHandler _handler;
-    private final boolean _masked;
-    private State _state;
-    private Buffer _buffer;
-    private byte _flags;
-    private byte _opcode;
-    private int _bytesNeeded;
-    private long _length;
-    private final byte[] _mask = new byte[4];
-    private int _m;
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param buffers The buffers to use for parsing.  Only the {@link Buffers#getBuffer()} is used.
-     * This should be a direct buffer if binary data is mostly used or an indirect buffer if utf-8 data
-     * is mostly used.
-     * @param endp the endpoint
-     * @param handler the handler to notify when a parse event occurs
-     * @param masked whether masking should be handled
-     */
-    public WebSocketParserD06(WebSocketBuffers buffers, EndPoint endp, FrameHandler handler, boolean masked)
-    {
-        _buffers=buffers;
-        _endp=endp;
-        _handler=handler;
-        _masked=masked;
-        _state=State.START;
-    }
-
-    /* ------------------------------------------------------------ */
-    public boolean isBufferEmpty()
-    {
-        return _buffer==null || _buffer.length()==0;
-    }
-
-    /* ------------------------------------------------------------ */
-    public Buffer getBuffer()
-    {
-        return _buffer;
-    }
-
-    /* ------------------------------------------------------------ */
-    /** Parse to next event.
-     * Parse to the next {@link WebSocketParser.FrameHandler} event or until no more data is
-     * available. Fill data from the {@link EndPoint} only as necessary.
-     * @return An indication of progress or otherwise. -1 indicates EOF, 0 indicates
-     * that no bytes were read and no messages parsed. A positive number indicates either
-     * the bytes filled or the messages parsed.
-     */
-    public int parseNext()
-    {
-        if (_buffer==null)
-            _buffer=_buffers.getBuffer();
-
-        int total_filled=0;
-        int events=0;
-
-        // Loop until an datagram call back or can't fill anymore
-        while(true)
-        {
-            int available=_buffer.length();
-
-            // Fill buffer if we need a byte or need length
-            while (available<(_state==State.SKIP?1:_bytesNeeded))
-            {
-                // compact to mark (set at start of data)
-                _buffer.compact();
-
-                // if no space, then the data is too big for buffer
-                if (_buffer.space() == 0)
-                    throw new IllegalStateException("FULL: "+_state+" "+_bytesNeeded+">"+_buffer.capacity());
-
-                // catch IOExceptions (probably EOF) and try to parse what we have
-                try
-                {
-                    int filled=_endp.isOpen()?_endp.fill(_buffer):-1;
-                    if (filled<=0)
-                        return (total_filled+events)>0?(total_filled+events):filled;
-                    total_filled+=filled;
-                    available=_buffer.length();
-                }
-                catch(IOException e)
-                {
-                    LOG.debug(e);
-                    return (total_filled+events)>0?(total_filled+events):-1;
-                }
-            }
-
-            // if we are here, then we have sufficient bytes to process the current state.
-
-            // Parse the buffer byte by byte (unless it is STATE_DATA)
-            byte b;
-            while (_state!=State.DATA && available>=(_state==State.SKIP?1:_bytesNeeded))
-            {
-                switch (_state)
-                {
-                    case START:
-                        _state=_masked?State.MASK:State.OPCODE;
-                        _bytesNeeded=_state.getNeeds();
-                        continue;
-
-                    case MASK:
-                        _buffer.get(_mask,0,4);
-                        available-=4;
-                        _state=State.OPCODE;
-                        _bytesNeeded=_state.getNeeds();
-                        _m=0;
-                        continue;
-
-                    case OPCODE:
-                        b=_buffer.get();
-                        available--;
-                        if (_masked)
-                            b^=_mask[_m++%4];
-                        _opcode=(byte)(b&0xf);
-                        _flags=(byte)(0xf&(b>>4));
-
-                        if (WebSocketConnectionD06.isControlFrame(_opcode)&&!WebSocketConnectionD06.isLastFrame(_flags))
-                        {
-                            _state=State.SKIP;
-                            events++;
-                            _handler.close(WebSocketConnectionD06.CLOSE_PROTOCOL,"fragmented control");
-                        }
-                        else
-                            _state=State.LENGTH_7;
-
-                        _bytesNeeded=_state.getNeeds();
-                        continue;
-
-                    case LENGTH_7:
-                        b=_buffer.get();
-                        available--;
-                        if (_masked)
-                            b^=_mask[_m++%4];
-                        switch(b)
-                        {
-                            case 127:
-                                _length=0;
-                                _state=State.LENGTH_63;
-                                _bytesNeeded=_state.getNeeds();
-                                break;
-                            case 126:
-                                _length=0;
-                                _state=State.LENGTH_16;
-                                _bytesNeeded=_state.getNeeds();
-                                break;
-                            default:
-                                _length=(0x7f&b);
-                                _bytesNeeded=(int)_length;
-                                _state=State.DATA;
-                        }
-                        continue;
-
-                    case LENGTH_16:
-                        b=_buffer.get();
-                        available--;
-                        if (_masked)
-                            b^=_mask[_m++%4];
-                        _length = _length*0x100 + (0xff&b);
-                        if (--_bytesNeeded==0)
-                        {
-                            _bytesNeeded=(int)_length;
-                            if (_length>_buffer.capacity())
-                            {
-                                _state=State.SKIP;
-                                events++;
-                                _handler.close(WebSocketConnectionD06.CLOSE_LARGE,"frame size "+_length+">"+_buffer.capacity());
-                            }
-                            else
-                            {
-                                _state=State.DATA;
-                            }
-                        }
-                        continue;
-
-                    case LENGTH_63:
-                        b=_buffer.get();
-                        available--;
-                        if (_masked)
-                            b^=_mask[_m++%4];
-                        _length = _length*0x100 + (0xff&b);
-                        if (--_bytesNeeded==0)
-                        {
-                            _bytesNeeded=(int)_length;
-                            if (_length>=_buffer.capacity())
-                            {
-                                _state=State.SKIP;
-                                events++;
-                                _handler.close(WebSocketConnectionD06.CLOSE_LARGE,"frame size "+_length+">"+_buffer.capacity());
-                            }
-                            else
-                            {
-                                _state=State.DATA;
-                            }
-                        }
-                        continue;
-
-                    case SKIP:
-                        int skip=Math.min(available,_bytesNeeded);
-                        _buffer.skip(skip);
-                        available-=skip;
-                        _bytesNeeded-=skip;
-                        if (_bytesNeeded==0)
-                            _state=State.START;
-
-                }
-            }
-
-            if (_state==State.DATA && available>=_bytesNeeded)
-            {
-                Buffer data =_buffer.get(_bytesNeeded);
-                if (_masked)
-                {
-                    if (data.array()==null)
-                        data=_buffer.asMutableBuffer();
-                    byte[] array = data.array();
-                    final int end=data.putIndex();
-                    for (int i=data.getIndex();i<end;i++)
-                        array[i]^=_mask[_m++%4];
-                }
-
-                // System.err.printf("%s %s %s >>\n",TypeUtil.toHexString(_flags),TypeUtil.toHexString(_opcode),data.length());
-                events++;
-                _handler.onFrame(_flags, _opcode, data);
-                _bytesNeeded=0;
-                _state=State.START;
-
-                if (_buffer.length()==0)
-                {
-                    _buffers.returnBuffer(_buffer);
-                    _buffer=null;
-                }
-
-                return total_filled+events;
-            }
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    public void fill(Buffer buffer)
-    {
-        if (buffer!=null && buffer.length()>0)
-        {
-            if (_buffer==null)
-                _buffer=_buffers.getBuffer();
-            _buffer.put(buffer);
-            buffer.clear();
-        }
-    }
-
-}
diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketParserD08.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketParserD08.java
deleted file mode 100644
index fa9d7d2..0000000
--- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketParserD08.java
+++ /dev/null
@@ -1,394 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.websocket;
-
-import java.io.IOException;
-
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.Buffers;
-import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-
-
-
-/* ------------------------------------------------------------ */
-/**
- * Parser the WebSocket protocol.
- *
- */
-public class WebSocketParserD08 implements WebSocketParser
-{
-    private static final Logger LOG = Log.getLogger(WebSocketParserD08.class);
-
-    public enum State {
-
-        START(0), OPCODE(1), LENGTH_7(1), LENGTH_16(2), LENGTH_63(8), MASK(4), PAYLOAD(0), DATA(0), SKIP(1), SEEK_EOF(1);
-
-        int _needs;
-
-        State(int needs)
-        {
-            _needs=needs;
-        }
-
-        int getNeeds()
-        {
-            return _needs;
-        }
-    }
-
-    private final WebSocketBuffers _buffers;
-    private final EndPoint _endp;
-    private final FrameHandler _handler;
-    private final boolean _shouldBeMasked;
-    private State _state;
-    private Buffer _buffer;
-    private byte _flags;
-    private byte _opcode;
-    private int _bytesNeeded;
-    private long _length;
-    private boolean _masked;
-    private final byte[] _mask = new byte[4];
-    private int _m;
-    private boolean _skip;
-    private boolean _fragmentFrames=true;
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param buffers The buffers to use for parsing.  Only the {@link Buffers#getBuffer()} is used.
-     * This should be a direct buffer if binary data is mostly used or an indirect buffer if utf-8 data
-     * is mostly used.
-     * @param endp the endpoint
-     * @param handler the handler to notify when a parse event occurs
-     * @param shouldBeMasked whether masking should be handled
-     */
-    public WebSocketParserD08(WebSocketBuffers buffers, EndPoint endp, FrameHandler handler, boolean shouldBeMasked)
-    {
-        _buffers=buffers;
-        _endp=endp;
-        _handler=handler;
-        _shouldBeMasked=shouldBeMasked;
-        _state=State.START;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return True if fake fragments should be created for frames larger than the buffer.
-     */
-    public boolean isFakeFragments()
-    {
-        return _fragmentFrames;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param fakeFragments True if fake fragments should be created for frames larger than the buffer.
-     */
-    public void setFakeFragments(boolean fakeFragments)
-    {
-        _fragmentFrames = fakeFragments;
-    }
-
-    /* ------------------------------------------------------------ */
-    public boolean isBufferEmpty()
-    {
-        return _buffer==null || _buffer.length()==0;
-    }
-
-    /* ------------------------------------------------------------ */
-    public Buffer getBuffer()
-    {
-        return _buffer;
-    }
-
-    /* ------------------------------------------------------------ */
-    /** Parse to next event.
-     * Parse to the next {@link WebSocketParser.FrameHandler} event or until no more data is
-     * available. Fill data from the {@link EndPoint} only as necessary.
-     * @return An indication of progress or otherwise. -1 indicates EOF, 0 indicates
-     * that no bytes were read and no messages parsed. A positive number indicates either
-     * the bytes filled or the messages parsed.
-     */
-    public int parseNext()
-    {
-        if (_buffer==null)
-            _buffer=_buffers.getBuffer();
-
-        boolean progress=false;
-        int filled=-1;
-
-        // Loop until a datagram call back or can't fill anymore
-        while(!progress && (!_endp.isInputShutdown()||_buffer.length()>0))
-        {
-            int available=_buffer.length();
-
-            // Fill buffer if we need a byte or need length
-            while (available<(_state==State.SKIP?1:_bytesNeeded))
-            {
-                // compact to mark (set at start of data)
-                _buffer.compact();
-
-                // if no space, then the data is too big for buffer
-                if (_buffer.space() == 0)
-                {
-                    // Can we send a fake frame?
-                    if (_fragmentFrames && _state==State.DATA)
-                    {
-                        Buffer data =_buffer.get(4*(available/4));
-                        _buffer.compact();
-                        if (_masked)
-                        {
-                            if (data.array()==null)
-                                data=_buffer.asMutableBuffer();
-                            byte[] array = data.array();
-                            final int end=data.putIndex();
-                            for (int i=data.getIndex();i<end;i++)
-                                array[i]^=_mask[_m++%4];
-                        }
-
-                        // System.err.printf("%s %s %s >>\n",TypeUtil.toHexString(_flags),TypeUtil.toHexString(_opcode),data.length());
-                        _bytesNeeded-=data.length();
-                        progress=true;
-                        _handler.onFrame((byte)(_flags&(0xff^WebSocketConnectionD08.FLAG_FIN)), _opcode, data);
-
-                        _opcode=WebSocketConnectionD08.OP_CONTINUATION;
-                    }
-
-                    if (_buffer.space() == 0)
-                        throw new IllegalStateException("FULL: "+_state+" "+_bytesNeeded+">"+_buffer.capacity());
-                }
-
-                // catch IOExceptions (probably EOF) and try to parse what we have
-                try
-                {
-                    filled=_endp.isInputShutdown()?-1:_endp.fill(_buffer);
-                    available=_buffer.length();
-                    // System.err.printf(">> filled %d/%d%n",filled,available);
-                    if (filled<=0)
-                        break;
-                }
-                catch(IOException e)
-                {
-                    LOG.debug(e);
-                    filled=-1;
-                    break;
-                }
-            }
-            // Did we get enough?
-            if (available<(_state==State.SKIP?1:_bytesNeeded))
-                break;
-
-            // if we are here, then we have sufficient bytes to process the current state.
-            // Parse the buffer byte by byte (unless it is STATE_DATA)
-            byte b;
-            while (_state!=State.DATA && available>=(_state==State.SKIP?1:_bytesNeeded))
-            {
-                switch (_state)
-                {
-                    case START:
-                        _skip=false;
-                        _state=_opcode==WebSocketConnectionD08.OP_CLOSE?State.SEEK_EOF:State.OPCODE;
-                        _bytesNeeded=_state.getNeeds();
-                        continue;
-
-                    case OPCODE:
-                        b=_buffer.get();
-                        available--;
-                        _opcode=(byte)(b&0xf);
-                        _flags=(byte)(0xf&(b>>4));
-
-                        if (WebSocketConnectionD08.isControlFrame(_opcode)&&!WebSocketConnectionD08.isLastFrame(_flags))
-                        {
-                            LOG.warn("Fragmented Control from "+_endp);
-                            _handler.close(WebSocketConnectionD08.CLOSE_PROTOCOL,"Fragmented control");
-                            progress=true;
-                            _skip=true;
-                        }
-
-                        _state=State.LENGTH_7;
-                        _bytesNeeded=_state.getNeeds();
-
-                        continue;
-
-                    case LENGTH_7:
-                        b=_buffer.get();
-                        available--;
-                        _masked=(b&0x80)!=0;
-                        b=(byte)(0x7f&b);
-
-                        switch(b)
-                        {
-                            case 0x7f:
-                                _length=0;
-                                _state=State.LENGTH_63;
-                                break;
-                            case 0x7e:
-                                _length=0;
-                                _state=State.LENGTH_16;
-                                break;
-                            default:
-                                _length=(0x7f&b);
-                                _state=_masked?State.MASK:State.PAYLOAD;
-                        }
-                        _bytesNeeded=_state.getNeeds();
-                        continue;
-
-                    case LENGTH_16:
-                        b=_buffer.get();
-                        available--;
-                        _length = _length*0x100 + (0xff&b);
-                        if (--_bytesNeeded==0)
-                        {
-                            if (_length>_buffer.capacity() && !_fragmentFrames)
-                            {
-                                progress=true;
-                                _handler.close(WebSocketConnectionD08.CLOSE_BADDATA,"frame size "+_length+">"+_buffer.capacity());
-                                _skip=true;
-                            }
-
-                            _state=_masked?State.MASK:State.PAYLOAD;
-                            _bytesNeeded=_state.getNeeds();
-                        }
-                        continue;
-
-                    case LENGTH_63:
-                        b=_buffer.get();
-                        available--;
-                        _length = _length*0x100 + (0xff&b);
-                        if (--_bytesNeeded==0)
-                        {
-                            _bytesNeeded=(int)_length;
-                            if (_length>=_buffer.capacity() && !_fragmentFrames)
-                            {
-                                progress=true;
-                                _handler.close(WebSocketConnectionD08.CLOSE_BADDATA,"frame size "+_length+">"+_buffer.capacity());
-                                _skip=true;
-                            }
-
-                            _state=_masked?State.MASK:State.PAYLOAD;
-                            _bytesNeeded=_state.getNeeds();
-                        }
-                        continue;
-
-                    case MASK:
-                        _buffer.get(_mask,0,4);
-                        _m=0;
-                        available-=4;
-                        _state=State.PAYLOAD;
-                        _bytesNeeded=_state.getNeeds();
-                        break;
-
-                    case PAYLOAD:
-                        _bytesNeeded=(int)_length;
-                        _state=_skip?State.SKIP:State.DATA;
-                        break;
-
-                    case DATA:
-                        break;
-
-                    case SKIP:
-                        int skip=Math.min(available,_bytesNeeded);
-                        progress=true;
-                        _buffer.skip(skip);
-                        available-=skip;
-                        _bytesNeeded-=skip;
-                        if (_bytesNeeded==0)
-                            _state=State.START;
-                        break;
-
-                    case SEEK_EOF:
-                        progress=true;
-                        _buffer.skip(available);
-                        available=0;
-                        break;
-                }
-            }
-
-            if (_state==State.DATA && available>=_bytesNeeded)
-            {
-                if ( _masked!=_shouldBeMasked)
-                {
-                    _buffer.skip(_bytesNeeded);
-                    _state=State.START;
-                    progress=true;
-                    _handler.close(WebSocketConnectionD08.CLOSE_PROTOCOL,"bad mask");
-                }
-                else
-                {
-                    Buffer data =_buffer.get(_bytesNeeded);
-                    if (_masked)
-                    {
-                        if (data.array()==null)
-                            data=_buffer.asMutableBuffer();
-                        byte[] array = data.array();
-                        final int end=data.putIndex();
-                        for (int i=data.getIndex();i<end;i++)
-                            array[i]^=_mask[_m++%4];
-                    }
-
-                    // System.err.printf("%s %s %s >>\n",TypeUtil.toHexString(_flags),TypeUtil.toHexString(_opcode),data.length());
-
-                    progress=true;
-                    _handler.onFrame(_flags, _opcode, data);
-                    _bytesNeeded=0;
-                    _state=State.START;
-                }
-
-                break;
-            }
-        }
-
-        return progress?1:filled;
-    }
-
-    /* ------------------------------------------------------------ */
-    public void fill(Buffer buffer)
-    {
-        if (buffer!=null && buffer.length()>0)
-        {
-            if (_buffer==null)
-                _buffer=_buffers.getBuffer();
-
-            _buffer.put(buffer);
-            buffer.clear();
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    public void returnBuffer()
-    {
-        if (_buffer!=null && _buffer.length()==0)
-        {
-            _buffers.returnBuffer(_buffer);
-            _buffer=null;
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public String toString()
-    {
-        return String.format("%s@%x state=%s buffer=%s",
-                getClass().getSimpleName(),
-                hashCode(),
-                _state,
-                _buffer);
-    }
-}
diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketParserRFC6455.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketParserRFC6455.java
deleted file mode 100644
index b57e234..0000000
--- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketParserRFC6455.java
+++ /dev/null
@@ -1,394 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.websocket;
-
-import java.io.IOException;
-
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.Buffers;
-import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-
-
-
-/* ------------------------------------------------------------ */
-/**
- * Parser the WebSocket protocol.
- *
- */
-public class WebSocketParserRFC6455 implements WebSocketParser
-{
-    private static final Logger LOG = Log.getLogger(WebSocketParserRFC6455.class);
-
-    public enum State {
-
-        START(0), OPCODE(1), LENGTH_7(1), LENGTH_16(2), LENGTH_63(8), MASK(4), PAYLOAD(0), DATA(0), SKIP(1), SEEK_EOF(1);
-
-        int _needs;
-
-        State(int needs)
-        {
-            _needs=needs;
-        }
-
-        int getNeeds()
-        {
-            return _needs;
-        }
-    }
-
-    private final WebSocketBuffers _buffers;
-    private final EndPoint _endp;
-    private final FrameHandler _handler;
-    private final boolean _shouldBeMasked;
-    private State _state;
-    private Buffer _buffer;
-    private byte _flags;
-    private byte _opcode;
-    private int _bytesNeeded;
-    private long _length;
-    private boolean _masked;
-    private final byte[] _mask = new byte[4];
-    private int _m;
-    private boolean _skip;
-    private boolean _fragmentFrames=true;
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param buffers The buffers to use for parsing.  Only the {@link Buffers#getBuffer()} is used.
-     * This should be a direct buffer if binary data is mostly used or an indirect buffer if utf-8 data
-     * is mostly used.
-     * @param endp the endpoint
-     * @param handler the handler to notify when a parse event occurs
-     * @param shouldBeMasked whether masking should be handled
-     */
-    public WebSocketParserRFC6455(WebSocketBuffers buffers, EndPoint endp, FrameHandler handler, boolean shouldBeMasked)
-    {
-        _buffers=buffers;
-        _endp=endp;
-        _handler=handler;
-        _shouldBeMasked=shouldBeMasked;
-        _state=State.START;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return True if fake fragments should be created for frames larger than the buffer.
-     */
-    public boolean isFakeFragments()
-    {
-        return _fragmentFrames;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param fakeFragments True if fake fragments should be created for frames larger than the buffer.
-     */
-    public void setFakeFragments(boolean fakeFragments)
-    {
-        _fragmentFrames = fakeFragments;
-    }
-
-    /* ------------------------------------------------------------ */
-    public boolean isBufferEmpty()
-    {
-        return _buffer==null || _buffer.length()==0;
-    }
-
-    /* ------------------------------------------------------------ */
-    public Buffer getBuffer()
-    {
-        return _buffer;
-    }
-
-    /* ------------------------------------------------------------ */
-    /** Parse to next event.
-     * Parse to the next {@link WebSocketParser.FrameHandler} event or until no more data is
-     * available. Fill data from the {@link EndPoint} only as necessary.
-     * @return An indication of progress or otherwise. -1 indicates EOF, 0 indicates
-     * that no bytes were read and no messages parsed. A positive number indicates either
-     * the bytes filled or the messages parsed.
-     */
-    public int parseNext()
-    {
-        if (_buffer==null)
-            _buffer=_buffers.getBuffer();
-
-        boolean progress=false;
-        int filled=-1;
-
-        // Loop until a datagram call back or can't fill anymore
-        while(!progress && (!_endp.isInputShutdown()||_buffer.length()>0))
-        {
-            int available=_buffer.length();
-
-            // Fill buffer if we need a byte or need length
-            while (available<(_state==State.SKIP?1:_bytesNeeded))
-            {
-                // compact to mark (set at start of data)
-                _buffer.compact();
-
-                // if no space, then the data is too big for buffer
-                if (_buffer.space() == 0)
-                {
-                    // Can we send a fake frame?
-                    if (_fragmentFrames && _state==State.DATA)
-                    {
-                        Buffer data =_buffer.get(4*(available/4));
-                        _buffer.compact();
-                        if (_masked)
-                        {
-                            if (data.array()==null)
-                                data=_buffer.asMutableBuffer();
-                            byte[] array = data.array();
-                            final int end=data.putIndex();
-                            for (int i=data.getIndex();i<end;i++)
-                                array[i]^=_mask[_m++%4];
-                        }
-
-                        // System.err.printf("%s %s %s >>\n",TypeUtil.toHexString(_flags),TypeUtil.toHexString(_opcode),data.length());
-                        _bytesNeeded-=data.length();
-                        progress=true;
-                        _handler.onFrame((byte)(_flags&(0xff^WebSocketConnectionRFC6455.FLAG_FIN)), _opcode, data);
-
-                        _opcode=WebSocketConnectionRFC6455.OP_CONTINUATION;
-                    }
-
-                    if (_buffer.space() == 0)
-                        throw new IllegalStateException("FULL: "+_state+" "+_bytesNeeded+">"+_buffer.capacity());
-                }
-
-                // catch IOExceptions (probably EOF) and try to parse what we have
-                try
-                {
-                    filled=_endp.isInputShutdown()?-1:_endp.fill(_buffer);
-                    available=_buffer.length();
-                    // System.err.printf(">> filled %d/%d%n",filled,available);
-                    if (filled<=0)
-                        break;
-                }
-                catch(IOException e)
-                {
-                    LOG.debug(e);
-                    filled=-1;
-                    break;
-                }
-            }
-            // Did we get enough?
-            if (available<(_state==State.SKIP?1:_bytesNeeded))
-                break;
-
-            // if we are here, then we have sufficient bytes to process the current state.
-            // Parse the buffer byte by byte (unless it is STATE_DATA)
-            byte b;
-            while (_state!=State.DATA && available>=(_state==State.SKIP?1:_bytesNeeded))
-            {
-                switch (_state)
-                {
-                    case START:
-                        _skip=false;
-                        _state=_opcode==WebSocketConnectionRFC6455.OP_CLOSE?State.SEEK_EOF:State.OPCODE;
-                        _bytesNeeded=_state.getNeeds();
-                        continue;
-
-                    case OPCODE:
-                        b=_buffer.get();
-                        available--;
-                        _opcode=(byte)(b&0xf);
-                        _flags=(byte)(0xf&(b>>4));
-
-                        if (WebSocketConnectionRFC6455.isControlFrame(_opcode)&&!WebSocketConnectionRFC6455.isLastFrame(_flags))
-                        {
-                            LOG.warn("Fragmented Control from "+_endp);
-                            _handler.close(WebSocketConnectionRFC6455.CLOSE_PROTOCOL,"Fragmented control");
-                            progress=true;
-                            _skip=true;
-                        }
-
-                        _state=State.LENGTH_7;
-                        _bytesNeeded=_state.getNeeds();
-
-                        continue;
-
-                    case LENGTH_7:
-                        b=_buffer.get();
-                        available--;
-                        _masked=(b&0x80)!=0;
-                        b=(byte)(0x7f&b);
-
-                        switch(b)
-                        {
-                            case 0x7f:
-                                _length=0;
-                                _state=State.LENGTH_63;
-                                break;
-                            case 0x7e:
-                                _length=0;
-                                _state=State.LENGTH_16;
-                                break;
-                            default:
-                                _length=(0x7f&b);
-                                _state=_masked?State.MASK:State.PAYLOAD;
-                        }
-                        _bytesNeeded=_state.getNeeds();
-                        continue;
-
-                    case LENGTH_16:
-                        b=_buffer.get();
-                        available--;
-                        _length = _length*0x100 + (0xff&b);
-                        if (--_bytesNeeded==0)
-                        {
-                            if (_length>_buffer.capacity() && !_fragmentFrames)
-                            {
-                                progress=true;
-                                _handler.close(WebSocketConnectionRFC6455.CLOSE_POLICY_VIOLATION,"frame size "+_length+">"+_buffer.capacity());
-                                _skip=true;
-                            }
-
-                            _state=_masked?State.MASK:State.PAYLOAD;
-                            _bytesNeeded=_state.getNeeds();
-                        }
-                        continue;
-
-                    case LENGTH_63:
-                        b=_buffer.get();
-                        available--;
-                        _length = _length*0x100 + (0xff&b);
-                        if (--_bytesNeeded==0)
-                        {
-                            _bytesNeeded=(int)_length;
-                            if (_length>=_buffer.capacity() && !_fragmentFrames)
-                            {
-                                progress=true;
-                                _handler.close(WebSocketConnectionRFC6455.CLOSE_POLICY_VIOLATION,"frame size "+_length+">"+_buffer.capacity());
-                                _skip=true;
-                            }
-
-                            _state=_masked?State.MASK:State.PAYLOAD;
-                            _bytesNeeded=_state.getNeeds();
-                        }
-                        continue;
-
-                    case MASK:
-                        _buffer.get(_mask,0,4);
-                        _m=0;
-                        available-=4;
-                        _state=State.PAYLOAD;
-                        _bytesNeeded=_state.getNeeds();
-                        break;
-
-                    case PAYLOAD:
-                        _bytesNeeded=(int)_length;
-                        _state=_skip?State.SKIP:State.DATA;
-                        break;
-
-                    case DATA:
-                        break;
-
-                    case SKIP:
-                        int skip=Math.min(available,_bytesNeeded);
-                        progress=true;
-                        _buffer.skip(skip);
-                        available-=skip;
-                        _bytesNeeded-=skip;
-                        if (_bytesNeeded==0)
-                            _state=State.START;
-                        break;
-
-                    case SEEK_EOF:
-                        progress=true;
-                        _buffer.skip(available);
-                        available=0;
-                        break;
-                }
-            }
-
-            if (_state==State.DATA && available>=_bytesNeeded)
-            {
-                if ( _masked!=_shouldBeMasked)
-                {
-                    _buffer.skip(_bytesNeeded);
-                    _state=State.START;
-                    progress=true;
-                    _handler.close(WebSocketConnectionRFC6455.CLOSE_PROTOCOL,"Not masked");
-                }
-                else
-                {
-                    Buffer data =_buffer.get(_bytesNeeded);
-                    if (_masked)
-                    {
-                        if (data.array()==null)
-                            data=_buffer.asMutableBuffer();
-                        byte[] array = data.array();
-                        final int end=data.putIndex();
-                        for (int i=data.getIndex();i<end;i++)
-                            array[i]^=_mask[_m++%4];
-                    }
-
-                    // System.err.printf("%s %s %s >>\n",TypeUtil.toHexString(_flags),TypeUtil.toHexString(_opcode),data.length());
-
-                    progress=true;
-                    _handler.onFrame(_flags, _opcode, data);
-                    _bytesNeeded=0;
-                    _state=State.START;
-                }
-
-                break;
-            }
-        }
-
-        return progress?1:filled;
-    }
-
-    /* ------------------------------------------------------------ */
-    public void fill(Buffer buffer)
-    {
-        if (buffer!=null && buffer.length()>0)
-        {
-            if (_buffer==null)
-                _buffer=_buffers.getBuffer();
-
-            _buffer.put(buffer);
-            buffer.clear();
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    public void returnBuffer()
-    {
-        if (_buffer!=null && _buffer.length()==0)
-        {
-            _buffers.returnBuffer(_buffer);
-            _buffer=null;
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public String toString()
-    {
-        return String.format("%s@%x state=%s buffer=%s",
-                getClass().getSimpleName(),
-                hashCode(),
-                _state,
-                _buffer);
-    }
-}
diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketServlet.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketServlet.java
deleted file mode 100644
index 45c97d2..0000000
--- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketServlet.java
+++ /dev/null
@@ -1,128 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.websocket;
-
-import java.io.IOException;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-
-/* ------------------------------------------------------------ */
-/**
- * Servlet to upgrade connections to WebSocket
- * <p/>
- * The request must have the correct upgrade headers, else it is
- * handled as a normal servlet request.
- * <p/>
- * The initParameter "bufferSize" can be used to set the buffer size,
- * which is also the max frame byte size (default 8192).
- * <p/>
- * The initParameter "maxIdleTime" can be used to set the time in ms
- * that a websocket may be idle before closing. (default is 300000)
- * <p/>
- * The initParameter "maxTextMessageSize" can be used to set the size in characters
- * that a websocket may be accept before closing. (Default is 16768)
- * <p/>
- * The initParameter "maxBinaryMessageSize" can be used to set the size in bytes
- * that a websocket may be accept before closing. (Default is -1 - or unlimited)
- * <p/>
- * The initParameter "minVersion" can be used to set the minimum protocol version
- * accepted. (Default 13 - the RFC6455 version)
- */
- at SuppressWarnings("serial")
-public abstract class WebSocketServlet extends HttpServlet implements WebSocketFactory.Acceptor
-{
-    private final Logger LOG = Log.getLogger(getClass());
-    private WebSocketFactory _webSocketFactory;
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see javax.servlet.GenericServlet#init()
-     */
-    @Override
-    public void init() throws ServletException
-    {
-        try
-        {
-            String bs = getInitParameter("bufferSize");
-            _webSocketFactory = new WebSocketFactory(this, bs == null ? 8192 : Integer.parseInt(bs));
-            _webSocketFactory.start();
-
-            String max = getInitParameter("maxIdleTime");
-            if (max != null)
-                _webSocketFactory.setMaxIdleTime(Integer.parseInt(max));
-
-            max = getInitParameter("maxTextMessageSize");
-            if (max != null)
-                _webSocketFactory.setMaxTextMessageSize(Integer.parseInt(max));
-
-            max = getInitParameter("maxBinaryMessageSize");
-            if (max != null)
-                _webSocketFactory.setMaxBinaryMessageSize(Integer.parseInt(max));
-            
-            String min = getInitParameter("minVersion");
-            if (min != null)
-                _webSocketFactory.setMinVersion(Integer.parseInt(min));
-        }
-        catch (ServletException x)
-        {
-            throw x;
-        }
-        catch (Exception x)
-        {
-            throw new ServletException(x);
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @see javax.servlet.http.HttpServlet#service(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
-     */
-    @Override
-    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
-    {
-        if (_webSocketFactory.acceptWebSocket(request, response) || response.isCommitted())
-            return;
-        super.service(request, response);
-    }
-
-    /* ------------------------------------------------------------ */
-    public boolean checkOrigin(HttpServletRequest request, String origin)
-    {
-        return true;
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public void destroy()
-    {
-        try
-        {
-            _webSocketFactory.stop();
-        }
-        catch (Exception x)
-        {
-            LOG.ignore(x);
-        }
-    }
-}
diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketServletConnection.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketServletConnection.java
deleted file mode 100644
index 0b39141..0000000
--- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketServletConnection.java
+++ /dev/null
@@ -1,28 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.websocket;
-
-import java.io.IOException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-public interface WebSocketServletConnection extends WebSocketConnection
-{
-    void handshake(HttpServletRequest request, HttpServletResponse response, String subprotocol) throws IOException;
-}
diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketServletConnectionD00.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketServletConnectionD00.java
deleted file mode 100644
index 5589e25..0000000
--- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketServletConnectionD00.java
+++ /dev/null
@@ -1,105 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.websocket;
-
-import java.io.IOException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jetty.http.HttpURI;
-import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.util.QuotedStringTokenizer;
-
-public class WebSocketServletConnectionD00 extends WebSocketConnectionD00 implements WebSocketServletConnection
-{
-    private final WebSocketFactory factory;
-
-    public WebSocketServletConnectionD00(WebSocketFactory factory, WebSocket websocket, EndPoint endpoint, WebSocketBuffers buffers, long timestamp, int maxIdleTime, String protocol)
-            throws IOException
-    {
-        super(websocket,endpoint,buffers,timestamp,maxIdleTime,protocol);
-        this.factory = factory;
-    }
-
-    public void handshake(HttpServletRequest request, HttpServletResponse response, String subprotocol) throws IOException
-    {
-        String uri = request.getRequestURI();
-        String query = request.getQueryString();
-        if (query != null && query.length() > 0)
-        {
-            uri += "?" + query;
-        }
-        uri = new HttpURI(uri).toString();
-        String host = request.getHeader("Host");
-
-        String origin = request.getHeader("Sec-WebSocket-Origin");
-        if (origin == null)
-        {
-            origin = request.getHeader("Origin");
-        }
-        if (origin != null)
-        {
-            origin = QuotedStringTokenizer.quoteIfNeeded(origin,"\r\n");
-        }
-
-        String key1 = request.getHeader("Sec-WebSocket-Key1");
-
-        if (key1 != null)
-        {
-            String key2 = request.getHeader("Sec-WebSocket-Key2");
-            setHixieKeys(key1,key2);
-
-            response.setHeader("Upgrade","WebSocket");
-            response.addHeader("Connection","Upgrade");
-            if (origin != null)
-            {
-                response.addHeader("Sec-WebSocket-Origin",origin);
-            }
-            response.addHeader("Sec-WebSocket-Location",(request.isSecure()?"wss://":"ws://") + host + uri);
-            if (subprotocol != null)
-            {
-                response.addHeader("Sec-WebSocket-Protocol",subprotocol);
-            }
-            response.sendError(101, "WebSocket Protocol Handshake");
-        }
-        else
-        {
-            response.setHeader("Upgrade","WebSocket");
-            response.addHeader("Connection","Upgrade");
-            response.addHeader("WebSocket-Origin",origin);
-            response.addHeader("WebSocket-Location",(request.isSecure()?"wss://":"ws://") + host + uri);
-            if (subprotocol != null)
-            {
-                response.addHeader("WebSocket-Protocol",subprotocol);
-            }
-            response.sendError(101,"Web Socket Protocol Handshake");
-            response.flushBuffer();
-
-            onFrameHandshake();
-            onWebsocketOpen();
-        }
-    }
-
-    @Override
-    public void onClose()
-    {
-        super.onClose();
-        factory.removeConnection(this);
-    }
-}
diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketServletConnectionD06.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketServletConnectionD06.java
deleted file mode 100644
index 79c1f60..0000000
--- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketServletConnectionD06.java
+++ /dev/null
@@ -1,63 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.websocket;
-
-import java.io.IOException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jetty.io.EndPoint;
-
-public class WebSocketServletConnectionD06 extends WebSocketConnectionD06 implements WebSocketServletConnection
-{
-    private final WebSocketFactory factory;
-
-    public WebSocketServletConnectionD06(WebSocketFactory factory, WebSocket websocket, EndPoint endpoint, WebSocketBuffers buffers, long timestamp, int maxIdleTime, String protocol)
-            throws IOException
-    {
-        super(websocket,endpoint,buffers,timestamp,maxIdleTime,protocol);
-        this.factory = factory;
-    }
-
-    /* ------------------------------------------------------------ */
-    public void handshake(HttpServletRequest request, HttpServletResponse response, String subprotocol) throws IOException
-    {
-        String key = request.getHeader("Sec-WebSocket-Key");
-
-        response.setHeader("Upgrade","WebSocket");
-        response.addHeader("Connection","Upgrade");
-        response.addHeader("Sec-WebSocket-Accept",hashKey(key));
-        if (subprotocol!=null)
-        {
-            response.addHeader("Sec-WebSocket-Protocol",subprotocol);
-        }
-
-        response.sendError(101);
-
-        onFrameHandshake();
-        onWebSocketOpen();
-    }
-
-    @Override
-    public void onClose()
-    {
-        super.onClose();
-        factory.removeConnection(this);
-    }
-}
diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketServletConnectionD08.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketServletConnectionD08.java
deleted file mode 100644
index 63383c9..0000000
--- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketServletConnectionD08.java
+++ /dev/null
@@ -1,69 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.websocket;
-
-import java.io.IOException;
-import java.util.List;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jetty.io.EndPoint;
-
-public class WebSocketServletConnectionD08 extends WebSocketConnectionD08 implements WebSocketServletConnection
-{
-    private final WebSocketFactory factory;
-
-    public WebSocketServletConnectionD08(WebSocketFactory factory, WebSocket websocket, EndPoint endpoint, WebSocketBuffers buffers, long timestamp, int maxIdleTime, String protocol,
-            List<Extension> extensions, int draft) throws IOException
-    {
-        super(websocket,endpoint,buffers,timestamp,maxIdleTime,protocol,extensions,draft);
-        this.factory = factory;
-    }
-
-    /* ------------------------------------------------------------ */
-    public void handshake(HttpServletRequest request, HttpServletResponse response, String subprotocol) throws IOException
-    {
-        String key = request.getHeader("Sec-WebSocket-Key");
-
-        response.setHeader("Upgrade","WebSocket");
-        response.addHeader("Connection","Upgrade");
-        response.addHeader("Sec-WebSocket-Accept",hashKey(key));
-        if (subprotocol != null)
-        {
-            response.addHeader("Sec-WebSocket-Protocol",subprotocol);
-        }
-
-        for (Extension ext : getExtensions())
-        {
-            response.addHeader("Sec-WebSocket-Extensions",ext.getParameterizedName());
-        }
-
-        response.sendError(101);
-
-        onFrameHandshake();
-        onWebSocketOpen();
-    }
-
-    @Override
-    public void onClose()
-    {
-        super.onClose();
-        factory.removeConnection(this);
-    }
-}
diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketServletConnectionRFC6455.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketServletConnectionRFC6455.java
deleted file mode 100644
index 72b5662..0000000
--- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketServletConnectionRFC6455.java
+++ /dev/null
@@ -1,69 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.websocket;
-
-import java.io.IOException;
-import java.util.List;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jetty.io.EndPoint;
-
-public class WebSocketServletConnectionRFC6455 extends WebSocketConnectionRFC6455 implements WebSocketServletConnection
-{
-    private final WebSocketFactory factory;
-
-    public WebSocketServletConnectionRFC6455(WebSocketFactory factory, WebSocket websocket, EndPoint endpoint, WebSocketBuffers buffers, long timestamp, int maxIdleTime, String protocol,
-            List<Extension> extensions, int draft) throws IOException
-    {
-        super(websocket,endpoint,buffers,timestamp,maxIdleTime,protocol,extensions,draft);
-        this.factory = factory;
-    }
-
-    /* ------------------------------------------------------------ */
-    public void handshake(HttpServletRequest request, HttpServletResponse response, String subprotocol) throws IOException
-    {
-        String key = request.getHeader("Sec-WebSocket-Key");
-
-        response.setHeader("Upgrade","WebSocket");
-        response.addHeader("Connection","Upgrade");
-        response.addHeader("Sec-WebSocket-Accept",hashKey(key));
-        if (subprotocol != null)
-        {
-            response.addHeader("Sec-WebSocket-Protocol",subprotocol);
-        }
-
-        for (Extension ext : getExtensions())
-        {
-            response.addHeader("Sec-WebSocket-Extensions",ext.getParameterizedName());
-        }
-
-        response.sendError(101);
-
-        onFrameHandshake();
-        onWebSocketOpen();
-    }
-
-    @Override
-    public void onClose()
-    {
-        super.onClose();
-        factory.removeConnection(this);
-    }
-}
diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/ZeroMaskGen.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/ZeroMaskGen.java
deleted file mode 100644
index 4ec8410..0000000
--- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/ZeroMaskGen.java
+++ /dev/null
@@ -1,28 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.websocket;
-
-
-public class ZeroMaskGen implements MaskGen
-{
-    public void genMask(byte[] mask)
-    {
-        mask[0]=mask[1]=mask[2]=mask[3]=0;
-    }
-}
diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/SafariWebsocketDraft0Test.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/SafariWebsocketDraft0Test.java
deleted file mode 100644
index 8298b68..0000000
--- a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/SafariWebsocketDraft0Test.java
+++ /dev/null
@@ -1,129 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.websocket;
-
-import static org.hamcrest.Matchers.*;
-
-import java.net.URI;
-import java.util.concurrent.TimeUnit;
-
-import org.eclipse.jetty.server.Connector;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.servlet.ServletContextHandler;
-import org.eclipse.jetty.servlet.ServletHolder;
-import org.eclipse.jetty.websocket.helper.CaptureSocket;
-import org.eclipse.jetty.websocket.helper.SafariD00;
-import org.eclipse.jetty.websocket.helper.WebSocketCaptureServlet;
-import org.junit.After;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.Test;
-
-public class SafariWebsocketDraft0Test
-{
-    private Server server;
-    private WebSocketCaptureServlet servlet;
-    private URI serverUri;
-
-    @BeforeClass
-    public static void initLogging()
-    {
-        // Configure Logging
-        // System.setProperty("org.eclipse.jetty.util.log.class",StdErrLog.class.getName());
-        System.setProperty("org.eclipse.jetty.websocket.helper.LEVEL","DEBUG");
-    }
-
-    @Before
-    public void startServer() throws Exception
-    {
-        // Configure Server
-        server = new Server(0);
-
-        ServletContextHandler context = new ServletContextHandler();
-        context.setContextPath("/");
-        server.setHandler(context);
-
-        // Serve capture servlet
-        servlet = new WebSocketCaptureServlet();
-        ServletHolder holder = new ServletHolder(servlet);
-        holder.setInitParameter("minVersion","-1");
-        context.addServlet(holder,"/");
-
-        // Start Server
-        server.start();
-
-        Connector conn = server.getConnectors()[0];
-        String host = conn.getHost();
-        if (host == null)
-        {
-            host = "localhost";
-        }
-        int port = conn.getLocalPort();
-        serverUri = new URI(String.format("ws://%s:%d/",host,port));
-        // System.out.printf("Server URI: %s%n",serverUri);
-    }
-
-    @Test
-    public void testSendTextMessages() throws Exception
-    {
-        SafariD00 safari = new SafariD00(serverUri);
-
-        try
-        {
-            safari.connect();
-            safari.issueHandshake();
-
-            // Send 5 short messages, using technique seen in Safari.
-            safari.sendMessage("aa-0"); // single msg
-            safari.sendMessage("aa-1", "aa-2", "aa-3", "aa-4");
-
-            // Servlet should show only 1 connection.
-            Assert.assertThat("Servlet.captureSockets.size",servlet.captures.size(),is(1));
-
-            CaptureSocket socket = servlet.captures.get(0);
-            Assert.assertThat("CaptureSocket",socket,notNullValue());
-            Assert.assertThat("CaptureSocket.isConnected", socket.awaitConnected(10000), is(true));
-
-            // Give servlet time to process messages
-            for (int i=0;i<100 && socket.messages.size()<5;i++)
-                threadSleep(100,TimeUnit.MILLISECONDS);
-
-            // Should have captured 5 messages.
-            Assert.assertThat("CaptureSocket.messages.size",socket.messages.size(),is(5));
-        }
-        finally
-        {
-            // System.out.println("Closing client socket");
-            safari.disconnect();
-        }
-    }
-
-    public static void threadSleep(int dur, TimeUnit unit) throws InterruptedException
-    {
-        long ms = TimeUnit.MILLISECONDS.convert(dur,unit);
-        Thread.sleep(ms);
-    }
-
-    @After
-    public void stopServer() throws Exception
-    {
-        server.stop();
-    }
-}
diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/TestClient.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/TestClient.java
deleted file mode 100644
index 388f0b9..0000000
--- a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/TestClient.java
+++ /dev/null
@@ -1,312 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.websocket;
-
-import java.net.InetSocketAddress;
-import java.net.URI;
-import java.util.Arrays;
-import java.util.Random;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicLong;
-
-import org.eclipse.jetty.util.StringUtil;
-import org.eclipse.jetty.util.TypeUtil;
-
-/**
- * This is not a general purpose websocket client.
- * It's only for testing the websocket server and is hardwired to a specific draft version of the protocol.
- */
-public class TestClient implements WebSocket.OnFrame
-{
-    private static WebSocketClientFactory __clientFactory = new WebSocketClientFactory();
-    private static boolean _verbose=false;
-
-    private static final Random __random = new Random();
-    
-    private final String _host;
-    private final int _port;
-    private final String _protocol;
-    private final int _timeout;
-    
-    private static boolean __quiet;
-    private static int __framesSent;
-    private static int __messagesSent;
-    private static AtomicInteger __framesReceived=new AtomicInteger();
-    private static AtomicInteger __messagesReceived=new AtomicInteger();
-    
-    private static AtomicLong __totalTime=new AtomicLong();
-    private static AtomicLong __minDuration=new AtomicLong(Long.MAX_VALUE);
-    private static AtomicLong __maxDuration=new AtomicLong(Long.MIN_VALUE);
-    private static long __start;
-    private BlockingQueue<Long> _starts = new LinkedBlockingQueue<Long>();
-    int _messageBytes;
-    int _frames;
-    byte _opcode=-1;
-    private volatile WebSocket.FrameConnection _connection;
-    private final CountDownLatch _handshook = new CountDownLatch(1);
-    
-    
-    public void onOpen(Connection connection)
-    {
-        if (_verbose)
-            System.err.printf("%s#onHandshake %s %s\n",this.getClass().getSimpleName(),connection,connection.getClass().getSimpleName());
-    }
-
-    public void onClose(int closeCode, String message)
-    {
-        _handshook.countDown();
-    }
-
-    public boolean onFrame(byte flags, byte opcode, byte[] data, int offset, int length)
-    {
-        try
-        {
-            if (_connection.isClose(opcode))
-                return false;
-            
-            __framesReceived.incrementAndGet();
-            _frames++;
-            _messageBytes+=length;
-            
-            if (_opcode==-1)
-                _opcode=opcode;
-            
-            if (_connection.isControl(opcode) || _connection.isMessageComplete(flags))
-            {  
-                int recv =__messagesReceived.incrementAndGet();
-                Long start=_starts.poll();
-
-                if (start!=null)
-                {
-                    long duration = System.nanoTime()-start.longValue();
-                    long max=__maxDuration.get();
-                    while(duration>max && !__maxDuration.compareAndSet(max,duration))
-                        max=__maxDuration.get();
-                    long min=__minDuration.get();
-                    while(duration<min && !__minDuration.compareAndSet(min,duration))
-                        min=__minDuration.get();
-                    __totalTime.addAndGet(duration);
-                    if (!__quiet)
-                        System.out.printf("%d bytes from %s: frames=%d req=%d time=%.1fms opcode=0x%s\n",_messageBytes,_host,_frames,recv,((double)duration/1000000.0),TypeUtil.toHexString(_opcode));
-                }
-                _frames=0;
-                _messageBytes=0;
-                _opcode=-1;   
-            }
-        }
-        catch(Exception e)
-        {
-            e.printStackTrace();
-        }
-        return false;
-    }
-
-    public void onHandshake(FrameConnection connection)
-    {
-        _connection=connection;
-        _handshook.countDown();
-    }
-    
-    public TestClient(String host, int port,String protocol, int timeoutMS) throws Exception
-    {
-        _host=host;
-        _port=port;
-        _protocol=protocol;
-        _timeout=timeoutMS;
-    }
-
-    private void open() throws Exception
-    {
-        WebSocketClient client = new WebSocketClient(__clientFactory);
-        client.setProtocol(_protocol);
-        client.setMaxIdleTime(_timeout);
-        client.open(new URI("ws://"+_host+":"+_port+"/"),this).get(10,TimeUnit.SECONDS);
-    }
-
-    public void ping(byte opcode,byte[] data,int fragment) throws Exception
-    {
-        _starts.add(System.nanoTime());
-
-        int off=0;
-        int len=data.length;
-        if (fragment>0&& len>fragment)
-            len=fragment;
-        __messagesSent++;
-        while(off<data.length)
-        {                    
-            __framesSent++;
-            byte flags= (byte)(off+len==data.length?0x8:0);
-            byte op=(byte)(off==0?opcode:WebSocketConnectionRFC6455.OP_CONTINUATION);
-
-            if (_verbose)                
-                System.err.printf("%s#sendFrame %s|%s %s\n",this.getClass().getSimpleName(),TypeUtil.toHexString(flags),TypeUtil.toHexString(op),TypeUtil.toHexString(data,off,len));
-
-            _connection.sendFrame(flags,op,data,off,len);
-
-            off+=len;
-            if(data.length-off>len)
-                len=data.length-off;
-            if (fragment>0&& len>fragment)
-                len=fragment;
-        }
-    }
-
-    public void disconnect() throws Exception
-    {
-        if (_connection!=null)
-        {
-            _connection.close();
-        }
-    }
-    
-
-    private static void usage(String[] args)
-    {
-        System.err.println("ERROR: "+Arrays.asList(args));
-        System.err.println("USAGE: java -cp CLASSPATH "+TestClient.class+" [ OPTIONS ]");
-        System.err.println("  -h|--host HOST  (default localhost)");
-        System.err.println("  -p|--port PORT  (default 8080)");
-        System.err.println("  -b|--binary");
-        System.err.println("  -v|--verbose");
-        System.err.println("  -q|--quiet");
-        System.err.println("  -c|--count n    (default 10)");
-        System.err.println("  -s|--size n     (default 64)");
-        System.err.println("  -f|--fragment n (default 4000) ");
-        System.err.println("  -P|--protocol echo|echo-assemble|echo-fragment|echo-broadcast");
-        System.err.println("  -C|--clients n  (default 1) ");
-        System.err.println("  -d|--delay n    (default 1000ms) ");
-        System.exit(1);
-    }
-    
-    public static void main(String[] args) throws Exception
-    {
-        __clientFactory.start();
-
-        String host="localhost";
-        int port=8080;
-        String protocol=null;
-        int count=10;
-        int size=64;
-        int fragment=4000;
-        boolean binary=false;
-        int clients=1;
-        int delay=1000;
-
-        for (int i=0;i<args.length;i++)
-        {
-            String a=args[i];
-            if ("-p".equals(a)||"--port".equals(a))
-                port=Integer.parseInt(args[++i]);
-            else if ("-h".equals(a)||"--host".equals(a))
-                host=args[++i];
-            else if ("-c".equals(a)||"--count".equals(a))
-                count=Integer.parseInt(args[++i]);
-            else if ("-s".equals(a)||"--size".equals(a))
-                size=Integer.parseInt(args[++i]);
-            else if ("-f".equals(a)||"--fragment".equals(a))
-                fragment=Integer.parseInt(args[++i]);
-            else if ("-P".equals(a)||"--protocol".equals(a))
-                protocol=args[++i];
-            else if ("-v".equals(a)||"--verbose".equals(a))
-                _verbose=true;
-            else if ("-b".equals(a)||"--binary".equals(a))
-                binary=true;
-            else if ("-C".equals(a)||"--clients".equals(a))
-                clients=Integer.parseInt(args[++i]);
-            else if ("-d".equals(a)||"--delay".equals(a))
-                delay=Integer.parseInt(args[++i]);
-            else if ("-q".equals(a)||"--quiet".equals(a))
-                __quiet=true;
-            else if (a.startsWith("-"))
-                usage(args);
-        }
-
-
-        TestClient[] client = new TestClient[clients];
-        
-        try
-        {
-            __start=System.currentTimeMillis();
-            protocol=protocol==null?"echo":protocol;
-            
-            for (int i=0;i<clients;i++)
-            {
-                client[i]=new TestClient(host,port,protocol==null?null:protocol,60000);
-                client[i].open();
-            }
-
-            System.out.println("Jetty WebSocket PING "+host+":"+port+
-                    " ("+ new InetSocketAddress(host,port)+") "+clients+" clients "+protocol);
-            
-            
-            for (int p=0;p<count;p++)
-            {
-                long next = System.currentTimeMillis()+delay;
-                
-                byte opcode=binary?WebSocketConnectionRFC6455.OP_BINARY:WebSocketConnectionRFC6455.OP_TEXT;
-                
-                byte data[]=null;
-
-                if (opcode==WebSocketConnectionRFC6455.OP_TEXT)
-                {
-                    StringBuilder b = new StringBuilder();
-                    while (b.length()<size)
-                        b.append('A'+__random.nextInt(26));
-                    data=b.toString().getBytes(StringUtil.__UTF8);
-                }
-                else
-                {             
-                    data= new byte[size];
-                    __random.nextBytes(data);
-                }
-
-                for (int i=0;i<clients;i++)
-                    client[i].ping(opcode,data,opcode==WebSocketConnectionRFC6455.OP_PING?-1:fragment);
-                
-                while(System.currentTimeMillis()<next)
-                    Thread.sleep(10);
-            }
-        }
-        finally
-        {
-            for (int i=0;i<clients;i++)
-                if (client[i]!=null)
-                    client[i].disconnect();
-            
-            long duration=System.currentTimeMillis()-__start;
-            System.out.println("--- "+host+" websocket ping statistics using "+clients+" connection"+(clients>1?"s":"")+" ---");
-            System.out.printf("%d/%d frames sent/recv, %d/%d mesg sent/recv, time %dms %dm/s %.2fbps%n",
-                    __framesSent,__framesReceived.get(),
-                    __messagesSent,__messagesReceived.get(),
-                    duration,(1000L*__messagesReceived.get()/duration),
-                    1000.0D*__messagesReceived.get()*8*size/duration/1024/1024);
-            System.out.printf("rtt min/ave/max = %.3f/%.3f/%.3f ms\n",__minDuration.get()/1000000.0,__messagesReceived.get()==0?0.0:(__totalTime.get()/__messagesReceived.get()/1000000.0),__maxDuration.get()/1000000.0);
-            
-            __clientFactory.stop();
-        }
-
-    }
-    
-    
-    
-}
diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/TestServer.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/TestServer.java
deleted file mode 100644
index aab6c82..0000000
--- a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/TestServer.java
+++ /dev/null
@@ -1,448 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.websocket;
-
-import java.io.IOException;
-import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-import javax.servlet.http.HttpServletRequest;
-
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.handler.ResourceHandler;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
-import org.eclipse.jetty.util.StringUtil;
-import org.eclipse.jetty.util.TypeUtil;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-
-public class TestServer extends Server
-{
-    private static final Logger LOG = Log.getLogger(TestServer.class);
-
-    boolean _verbose;
-
-    WebSocket _websocket;
-    SelectChannelConnector _connector;
-    WebSocketHandler _wsHandler;
-    ResourceHandler _rHandler;
-    ConcurrentLinkedQueue<TestWebSocket> _broadcast = new ConcurrentLinkedQueue<TestWebSocket>();
-
-    public TestServer(int port)
-    {
-        _connector = new SelectChannelConnector();
-        _connector.setPort(port);
-
-        addConnector(_connector);
-        _wsHandler = new WebSocketHandler()
-        {
-            public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol)
-            {
-                if ("org.ietf.websocket.test-echo".equals(protocol) || "echo".equals(protocol) || "lws-mirror-protocol".equals(protocol))
-                {
-                    _websocket = new TestEchoWebSocket();        
-                }
-                else if ("org.ietf.websocket.test-echo-broadcast".equals(protocol) || "echo-broadcast".equals(protocol))
-                {
-                    _websocket = new TestEchoBroadcastWebSocket(); 
-                }
-                else if ("echo-broadcast-ping".equals(protocol))
-                {
-                    _websocket = new TestEchoBroadcastPingWebSocket();        
-                }
-                else if ("org.ietf.websocket.test-echo-assemble".equals(protocol) || "echo-assemble".equals(protocol))
-                {
-                    _websocket = new TestEchoAssembleWebSocket();
-                }
-                else if ("org.ietf.websocket.test-echo-fragment".equals(protocol) || "echo-fragment".equals(protocol))
-                {
-                    _websocket = new TestEchoFragmentWebSocket();
-                }
-                else if (protocol==null)
-                {
-                    _websocket = new TestWebSocket(); 
-                }
-                return _websocket;
-            }
-        };
-
-        setHandler(_wsHandler);
-        
-        _rHandler=new ResourceHandler();
-        _rHandler.setDirectoriesListed(true);
-        _rHandler.setResourceBase("src/test/webapp");
-        _wsHandler.setHandler(_rHandler);
-   
-    }
-
-    /* ------------------------------------------------------------ */
-    public boolean isVerbose()
-    {
-        return _verbose;
-    }
-
-    /* ------------------------------------------------------------ */
-    public void setVerbose(boolean verbose)
-    {
-        _verbose = verbose;
-    }
-
-    /* ------------------------------------------------------------ */
-    public void setResourceBase(String dir)
-    {
-        _rHandler.setResourceBase(dir);
-    }
-
-    /* ------------------------------------------------------------ */
-    public String getResourceBase()
-    {
-        return _rHandler.getResourceBase();
-    }
-    
-    /* ------------------------------------------------------------ */
-    /* ------------------------------------------------------------ */
-    class TestWebSocket implements WebSocket, WebSocket.OnFrame, WebSocket.OnBinaryMessage, WebSocket.OnTextMessage, WebSocket.OnControl
-    {
-        protected FrameConnection _connection;
-        
-        public FrameConnection getConnection()
-        {
-            return _connection;
-        }
-        
-        public void onOpen(Connection connection)
-        {
-            if (_verbose)
-                System.err.printf("%s#onOpen %s %s\n",this.getClass().getSimpleName(),connection,connection.getProtocol());
-        }
-        
-        public void onHandshake(FrameConnection connection)
-        {
-            if (_verbose)
-                System.err.printf("%s#onHandshake %s %s\n",this.getClass().getSimpleName(),connection,connection.getClass().getSimpleName());
-            _connection = connection;
-        }
-
-        public void onClose(int code,String message)
-        {
-            if (_verbose)
-                System.err.printf("%s#onDisonnect %d %s\n",this.getClass().getSimpleName(),code,message);
-        }
-        
-        public boolean onFrame(byte flags, byte opcode, byte[] data, int offset, int length)
-        {            
-            if (_verbose)
-                System.err.printf("%s#onFrame %s|%s %s\n",this.getClass().getSimpleName(),TypeUtil.toHexString(flags),TypeUtil.toHexString(opcode),TypeUtil.toHexString(data,offset,length));
-            return false;
-        }
-
-        public boolean onControl(byte controlCode, byte[] data, int offset, int length)
-        {
-            if (_verbose)
-                System.err.printf("%s#onControl  %s %s\n",this.getClass().getSimpleName(),TypeUtil.toHexString(controlCode),TypeUtil.toHexString(data,offset,length));            
-            return false;
-        }
-
-        public void onMessage(String data)
-        {
-            if (_verbose)
-                System.err.printf("%s#onMessage     %s\n",this.getClass().getSimpleName(),data);
-        }
-
-        public void onMessage(byte[] data, int offset, int length)
-        {
-            if (_verbose)
-                System.err.printf("%s#onMessage     %s\n",this.getClass().getSimpleName(),TypeUtil.toHexString(data,offset,length));
-        }
-    }
-    
-    /* ------------------------------------------------------------ */
-    /* ------------------------------------------------------------ */
-    class TestEchoWebSocket extends TestWebSocket 
-    {
-        @Override
-        public void onOpen(Connection connection)
-        {
-            super.onOpen(connection);
-            connection.setMaxTextMessageSize(-1);
-            connection.setMaxBinaryMessageSize(-1);
-        }
-        
-        @Override
-        public boolean onFrame(byte flags, byte opcode, byte[] data, int offset, int length)
-        {
-            super.onFrame(flags,opcode,data,offset,length);
-            try
-            {
-                if (!getConnection().isControl(opcode))
-                    getConnection().sendFrame(flags,opcode,data,offset,length);             
-            }
-            catch (IOException e)
-            {
-                e.printStackTrace();
-            }
-            
-            return false;
-        }
-    }
-    
-    /* ------------------------------------------------------------ */
-    /* ------------------------------------------------------------ */
-    class TestEchoBroadcastPingWebSocket extends TestEchoBroadcastWebSocket 
-    {
-        Thread _keepAlive; // A dedicated thread is not a good way to do this
-        CountDownLatch _latch = new CountDownLatch(1);
-        
-        @Override
-        public void onHandshake(final FrameConnection connection)
-        {
-            super.onHandshake(connection);
-            _keepAlive=new Thread()
-            {
-                @Override
-                public void run()
-                {
-                    try
-                    {
-                        while(!_latch.await(10,TimeUnit.SECONDS))
-                        {
-                            System.err.println("Ping "+connection);
-                            byte[] data = { (byte)1, (byte) 2, (byte) 3 };
-                            connection.sendControl(WebSocketConnectionRFC6455.OP_PING,data,0,data.length);
-                        }
-                    }
-                    catch (Exception e)
-                    {
-                        e.printStackTrace();
-                    }
-                }
-            };
-            _keepAlive.start();
-        }
-
-
-        @Override
-        public boolean onControl(byte controlCode, byte[] data, int offset, int length)
-        {
-            if (controlCode==WebSocketConnectionRFC6455.OP_PONG)
-                System.err.println("Pong "+getConnection());
-            return super.onControl(controlCode,data,offset,length);
-        }
-
-
-        @Override
-        public void onClose(int code, String message)
-        {
-            _latch.countDown();
-            super.onClose(code,message);
-        }
-        
-        
-    }
-    
-    
-    /* ------------------------------------------------------------ */
-    /* ------------------------------------------------------------ */
-    class TestEchoBroadcastWebSocket extends TestWebSocket
-    {
-        @Override
-        public void onOpen(Connection connection)
-        {
-            super.onOpen(connection);
-            _broadcast.add(this);
-        }
-
-        @Override
-        public void onClose(int code,String message)
-        {
-            super.onClose(code,message);
-            _broadcast.remove(this);
-        }
-        
-        @Override
-        public void onMessage(byte[] data, int offset, int length)
-        {
-            super.onMessage(data,offset,length);
-            for (TestWebSocket ws : _broadcast)
-            {
-                try
-                {
-                    ws.getConnection().sendMessage(data,offset,length); 
-                }
-                catch (IOException e)
-                {
-                    _broadcast.remove(ws);
-                    e.printStackTrace();
-                }
-            }
-        }
-
-        @Override
-        public void onMessage(final String data)
-        {
-            super.onMessage(data);
-            for (TestWebSocket ws : _broadcast)
-            {
-                try
-                {
-                    ws.getConnection().sendMessage(data); 
-                }
-                catch (IOException e)
-                {
-                    _broadcast.remove(ws);
-                    e.printStackTrace();
-                }
-            }
-        }
-    }
-    
-    /* ------------------------------------------------------------ */
-    /* ------------------------------------------------------------ */
-    class TestEchoAssembleWebSocket extends TestWebSocket
-    {
-        
-        @Override
-        public void onOpen(Connection connection)
-        {
-            super.onOpen(connection);
-            connection.setMaxTextMessageSize(64*1024);
-            connection.setMaxBinaryMessageSize(64*1024);
-        }
-
-        @Override
-        public void onMessage(byte[] data, int offset, int length)
-        {
-            super.onMessage(data,offset,length);
-            try
-            {
-                getConnection().sendMessage(data,offset,length); 
-            }
-            catch (IOException e)
-            {
-                e.printStackTrace();
-            }
-        }
-
-        @Override
-        public void onMessage(final String data)
-        {
-            super.onMessage(data);
-            try
-            {
-                getConnection().sendMessage(data); 
-            }
-            catch (IOException e)
-            {
-                e.printStackTrace();
-            }
-        }
-    }
-    
-    /* ------------------------------------------------------------ */
-    /* ------------------------------------------------------------ */
-    class TestEchoFragmentWebSocket extends TestWebSocket
-    {
-        @Override
-        public void onOpen(Connection connection)
-        {
-            super.onOpen(connection);
-            connection.setMaxTextMessageSize(64*1024);
-            connection.setMaxBinaryMessageSize(64*1024);
-        }
-
-        @Override
-        public void onMessage(byte[] data, int offset, int length)
-        {
-            super.onMessage(data,offset,length);
-            try
-            {
-                getConnection().sendFrame((byte)0x0,getConnection().binaryOpcode(),data,offset,length/2); 
-                getConnection().sendFrame((byte)0x8,getConnection().binaryOpcode(),data,offset+length/2,length-length/2); 
-            }
-            catch (IOException e)
-            {
-                e.printStackTrace();
-            }
-        }
-
-        @Override
-        public void onMessage(final String message)
-        {
-            super.onMessage(message);
-            try
-            {
-                byte[] data = message.getBytes(StringUtil.__UTF8);
-                int offset=0;
-                int length=data.length;
-                getConnection().sendFrame((byte)0x0,getConnection().textOpcode(),data,offset,length/2); 
-                getConnection().sendFrame((byte)0x8,getConnection().textOpcode(),data,offset+length/2,length-length/2); 
-            }
-            catch (IOException e)
-            {
-                e.printStackTrace();
-            }
-        }
-    }
-
-    private static void usage()
-    {
-        System.err.println("java -cp CLASSPATH "+TestServer.class+" [ OPTIONS ]");
-        System.err.println("  -p|--port PORT    (default 8080)");
-        System.err.println("  -v|--verbose ");
-        System.err.println("  -d|--docroot file (default 'src/test/webapp')");
-        System.exit(1);
-    }
-    
-    public static void main(String... args)
-    {
-        try
-        {
-            int port=8080;
-            boolean verbose=false;
-            String docroot="src/test/webapp";
-            
-            for (int i=0;i<args.length;i++)
-            {
-                String a=args[i];
-                if ("-p".equals(a)||"--port".equals(a))
-                    port=Integer.parseInt(args[++i]);
-                else if ("-v".equals(a)||"--verbose".equals(a))
-                    verbose=true;
-                else if ("-d".equals(a)||"--docroot".equals(a))
-                    docroot=args[++i];
-                else if (a.startsWith("-"))
-                    usage();
-            }
-            
-            
-            TestServer server = new TestServer(port);
-            server.setVerbose(verbose);
-            server.setResourceBase(docroot);
-            server.start();
-            server.join();
-        }
-        catch (Exception e)
-        {
-            LOG.warn(e);
-        }
-    }
-
-
-}
diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/TomcatServerQuirksTest.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/TomcatServerQuirksTest.java
deleted file mode 100644
index 03e0fde..0000000
--- a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/TomcatServerQuirksTest.java
+++ /dev/null
@@ -1,125 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.websocket;
-
-import java.io.IOException;
-import java.net.URI;
-import java.nio.ByteBuffer;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-import org.eclipse.jetty.util.thread.QueuedThreadPool;
-import org.eclipse.jetty.websocket.dummy.DummyServer;
-import org.eclipse.jetty.websocket.dummy.DummyServer.ServerConnection;
-import org.junit.Assert;
-import org.junit.Ignore;
-import org.junit.Test;
-
-public class TomcatServerQuirksTest
-{
-    /**
-     * Test for when encountering a "Transfer-Encoding: chunked" on a Upgrade Response header.
-     * <ul>
-     * <li><a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=393075">Eclipse Jetty Bug #393075</a></li>
-     * <li><a href="https://issues.apache.org/bugzilla/show_bug.cgi?id=54067">Apache Tomcat Bug #54067</a></li>
-     * </ul>
-     * @throws IOException 
-     */
-    @Test
-    public void testTomcat7_0_32_WithTransferEncoding() throws Exception 
-    {
-        DummyServer server = new DummyServer();
-        int bufferSize = 512;
-        QueuedThreadPool threadPool = new QueuedThreadPool();
-        WebSocketClientFactory factory = new WebSocketClientFactory(threadPool, new ZeroMaskGen(), bufferSize);
-        
-        try 
-        {
-            server.start();
-            
-            // Setup Client Factory
-            threadPool.start();
-            factory.start();
-            
-            // Create Client
-            WebSocketClient client = new WebSocketClient(factory);
-
-            // Create End User WebSocket Class
-            final CountDownLatch openLatch = new CountDownLatch(1);
-            final CountDownLatch dataLatch = new CountDownLatch(1);
-            WebSocket.OnTextMessage websocket = new WebSocket.OnTextMessage()
-            {
-                public void onOpen(Connection connection)
-                {
-                    openLatch.countDown();
-                }
-
-                public void onMessage(String data)
-                {
-                    // System.out.println("data = " + data);
-                    dataLatch.countDown();
-                }
-
-                public void onClose(int closeCode, String message)
-                {
-                }
-            };
-            
-            // Open connection
-            URI wsURI = server.getWsUri();
-            client.open(wsURI, websocket);
-
-            // Accept incoming connection
-            ServerConnection socket = server.accept();
-            socket.setSoTimeout(2000); // timeout
-            
-            // Issue upgrade
-            Map<String,String> extraResponseHeaders = new HashMap<String, String>();
-            extraResponseHeaders.put("Transfer-Encoding", "chunked"); // !! The problem !!
-            socket.upgrade(extraResponseHeaders);
-            
-            // Wait for proper upgrade
-            Assert.assertTrue("Timed out waiting for Client side WebSocket open event", openLatch.await(1, TimeUnit.SECONDS));
-
-            // Have server write frame.
-            int length = bufferSize / 2;
-            ByteBuffer serverFrame = ByteBuffer.allocate(bufferSize);
-            serverFrame.put((byte)(0x80 | 0x01)); // FIN + TEXT
-            serverFrame.put((byte)0x7E); // No MASK and 2 bytes length
-            serverFrame.put((byte)(length >> 8)); // first length byte
-            serverFrame.put((byte)(length & 0xFF)); // second length byte
-            for (int i = 0; i < length; ++i)
-                serverFrame.put((byte)'x');
-            serverFrame.flip();
-            byte buf[] = serverFrame.array();
-            socket.write(buf,0,buf.length);
-            socket.flush();
-
-            Assert.assertTrue(dataLatch.await(1000, TimeUnit.SECONDS));
-        } 
-        finally 
-        {
-            factory.stop();
-            threadPool.stop();
-            server.stop();
-        }
-    }
-}
diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketClientTest.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketClientTest.java
deleted file mode 100644
index 1729b35..0000000
--- a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketClientTest.java
+++ /dev/null
@@ -1,790 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.websocket;
-
-import static org.hamcrest.Matchers.*;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.net.ConnectException;
-import java.net.InetSocketAddress;
-import java.net.ServerSocket;
-import java.net.Socket;
-import java.net.URI;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.Exchanger;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicLong;
-
-import org.eclipse.jetty.util.BlockingArrayQueue;
-import org.eclipse.jetty.util.IO;
-import org.eclipse.jetty.util.thread.QueuedThreadPool;
-import org.junit.After;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.Ignore;
-
- at Ignore("slow release machine")
-public class WebSocketClientTest
-{
-    private WebSocketClientFactory _factory = new WebSocketClientFactory();
-    private ServerSocket _server;
-    private int _serverPort;
-
-    @Before
-    public void startServer() throws Exception
-    {
-        _server = new ServerSocket();
-        _server.bind(null);
-        _serverPort = _server.getLocalPort();
-        _factory.start();
-    }
-
-    @After
-    public void stopServer() throws Exception
-    {
-        if(_server != null) {
-            _server.close();
-        }
-        _factory.stop();
-    }
-
-    @Test
-    public void testMessageBiggerThanBufferSize() throws Exception
-    {
-        QueuedThreadPool threadPool = new QueuedThreadPool();
-        int bufferSize = 512;
-        WebSocketClientFactory factory = new WebSocketClientFactory(threadPool, new ZeroMaskGen(), bufferSize);
-        threadPool.start();
-        factory.start();
-        WebSocketClient client = new WebSocketClient(factory);
-
-        final CountDownLatch openLatch = new CountDownLatch(1);
-        final CountDownLatch dataLatch = new CountDownLatch(1);
-        WebSocket.OnTextMessage websocket = new WebSocket.OnTextMessage()
-        {
-            public void onOpen(Connection connection)
-            {
-                openLatch.countDown();
-            }
-
-            public void onMessage(String data)
-            {
-                // System.out.println("data = " + data);
-                dataLatch.countDown();
-            }
-
-            public void onClose(int closeCode, String message)
-            {
-            }
-        };
-        
-        client.open(new URI("ws://127.0.0.1:" + _serverPort + "/"), websocket);
-
-        Socket socket = _server.accept();
-        accept(socket);
-
-        Assert.assertTrue(openLatch.await(1, TimeUnit.SECONDS));
-        OutputStream serverOutput = socket.getOutputStream();
-
-        int length = bufferSize + bufferSize / 2;
-        serverOutput.write(0x80 | 0x01); // FIN + TEXT
-        serverOutput.write(0x7E); // No MASK and 2 bytes length
-        serverOutput.write(length >> 8); // first length byte
-        serverOutput.write(length & 0xFF); // second length byte
-        for (int i = 0; i < length; ++i)
-            serverOutput.write('x');
-        serverOutput.flush();
-
-        Assert.assertTrue(dataLatch.await(1000, TimeUnit.SECONDS));
-
-        factory.stop();
-        threadPool.stop();
-    }
-
-    @Test
-    public void testBadURL() throws Exception
-    {
-        WebSocketClient client = new WebSocketClient(_factory);
-
-        boolean bad=false;
-        final AtomicBoolean open = new AtomicBoolean();
-        try
-        {
-            client.open(new URI("http://localhost:8080"),new WebSocket()
-            {
-                public void onOpen(Connection connection)
-                {
-                    open.set(true);
-                }
-
-                public void onClose(int closeCode, String message)
-                {}
-            });
-
-            Assert.fail();
-        }
-        catch(IllegalArgumentException e)
-        {
-            bad=true;
-        }
-        Assert.assertTrue(bad);
-        Assert.assertFalse(open.get());
-    }
-
-    @Test
-    public void testAsyncConnectionRefused() throws Exception
-    {
-        WebSocketClient client = new WebSocketClient(_factory);
-
-        final AtomicBoolean open = new AtomicBoolean();
-        final AtomicInteger close = new AtomicInteger();
-
-        Future<WebSocket.Connection> future=client.open(new URI("ws://127.0.0.1:1"),new WebSocket()
-        {
-            public void onOpen(Connection connection)
-            {
-                open.set(true);
-            }
-
-            public void onClose(int closeCode, String message)
-            {
-                close.set(closeCode);
-            }
-        });
-
-        Throwable error=null;
-        try
-        {
-            future.get(1,TimeUnit.SECONDS);
-            Assert.fail();
-        }
-        catch(ExecutionException e)
-        {
-            error=e.getCause();
-        }
-
-        Assert.assertFalse(open.get());
-        Assert.assertEquals(WebSocketConnectionRFC6455.CLOSE_NO_CLOSE,close.get());
-        Assert.assertTrue(error instanceof ConnectException);
-
-    }
-
-    @Test
-    public void testConnectionNotAccepted() throws Exception
-    {
-        WebSocketClient client = new WebSocketClient(_factory);
-
-        final AtomicBoolean open = new AtomicBoolean();
-        final AtomicInteger close = new AtomicInteger();
-        Future<WebSocket.Connection> future=client.open(new URI("ws://127.0.0.1:"+_serverPort),new WebSocket()
-        {
-            public void onOpen(Connection connection)
-            {
-                open.set(true);
-            }
-
-            public void onClose(int closeCode, String message)
-            {
-                close.set(closeCode);
-            }
-        });
-
-
-        Throwable error=null;
-        try
-        {
-            future.get(250,TimeUnit.MILLISECONDS);
-            Assert.fail();
-        }
-        catch(TimeoutException e)
-        {
-            error=e;
-        }
-
-        Assert.assertFalse(open.get());
-        Assert.assertEquals(WebSocketConnectionRFC6455.CLOSE_NO_CLOSE,close.get());
-        Assert.assertTrue(error instanceof TimeoutException);
-
-    }
-
-    @Test
-    public void testConnectionTimeout() throws Exception
-    {
-        WebSocketClient client = new WebSocketClient(_factory);
-
-        final AtomicBoolean open = new AtomicBoolean();
-        final AtomicInteger close = new AtomicInteger();
-        Future<WebSocket.Connection> future=client.open(new URI("ws://127.0.0.1:"+_serverPort),new WebSocket()
-        {
-            public void onOpen(Connection connection)
-            {
-                open.set(true);
-            }
-
-            public void onClose(int closeCode, String message)
-            {
-                close.set(closeCode);
-            }
-        });
-
-        Assert.assertNotNull(_server.accept());
-
-        Throwable error=null;
-        try
-        {
-            future.get(250,TimeUnit.MILLISECONDS);
-            Assert.fail();
-        }
-        catch(TimeoutException e)
-        {
-            error=e;
-        }
-
-        Assert.assertFalse(open.get());
-        Assert.assertEquals(WebSocketConnectionRFC6455.CLOSE_NO_CLOSE,close.get());
-        Assert.assertTrue(error instanceof TimeoutException);
-
-    }
-
-    @Test
-    public void testBadHandshake() throws Exception
-    {
-        WebSocketClient client = new WebSocketClient(_factory);
-
-        final AtomicBoolean open = new AtomicBoolean();
-        final AtomicInteger close = new AtomicInteger();
-        Future<WebSocket.Connection> future=client.open(new URI("ws://127.0.0.1:"+_serverPort+"/"),new WebSocket()
-        {
-            public void onOpen(Connection connection)
-            {
-                open.set(true);
-            }
-
-            public void onClose(int closeCode, String message)
-            {
-                close.set(closeCode);
-            }
-        });
-
-        Socket connection = _server.accept();
-        respondToClient(connection, "HTTP/1.1 404 NOT FOUND\r\n\r\n");
-
-        Throwable error=null;
-        try
-        {
-            future.get(250,TimeUnit.MILLISECONDS);
-            Assert.fail();
-        }
-        catch(ExecutionException e)
-        {
-            error=e.getCause();
-        }
-
-        Assert.assertFalse(open.get());
-        Assert.assertEquals(WebSocketConnectionRFC6455.CLOSE_PROTOCOL,close.get());
-        Assert.assertTrue(error instanceof IOException);
-        Assert.assertTrue(error.getMessage().indexOf("404 NOT FOUND")>0);
-
-    }
-
-    @Test
-    public void testBadUpgrade() throws Exception
-    {
-        WebSocketClient client = new WebSocketClient(_factory);
-
-        final AtomicBoolean open = new AtomicBoolean();
-        final AtomicInteger close = new AtomicInteger();
-        Future<WebSocket.Connection> future=client.open(new URI("ws://127.0.0.1:"+_serverPort+"/"),new WebSocket()
-        {
-            public void onOpen(Connection connection)
-            {
-                open.set(true);
-            }
-
-            public void onClose(int closeCode, String message)
-            {
-                close.set(closeCode);
-            }
-        });
-
-        Socket connection = _server.accept();
-        respondToClient(connection,
-                "HTTP/1.1 101 Upgrade\r\n" +
-                "Sec-WebSocket-Accept: rubbish\r\n" +
-                "\r\n" );
-
-        Throwable error=null;
-        try
-        {
-            future.get(250,TimeUnit.MILLISECONDS);
-            Assert.fail();
-        }
-        catch(ExecutionException e)
-        {
-            error=e.getCause();
-        }
-        Assert.assertFalse(open.get());
-        Assert.assertEquals(WebSocketConnectionRFC6455.CLOSE_PROTOCOL,close.get());
-        Assert.assertTrue(error instanceof IOException);
-        Assert.assertTrue(error.getMessage().indexOf("Bad Sec-WebSocket-Accept")>=0);
-    }
-
-    @Test
-    public void testUpgradeThenTCPClose() throws Exception
-    {
-        WebSocketClient client = new WebSocketClient(_factory);
-
-        final AtomicBoolean open = new AtomicBoolean();
-        final AtomicInteger close = new AtomicInteger();
-        final CountDownLatch _latch = new CountDownLatch(1);
-        Future<WebSocket.Connection> future=client.open(new URI("ws://127.0.0.1:"+_serverPort+"/"),new WebSocket()
-        {
-            public void onOpen(Connection connection)
-            {
-                open.set(true);
-            }
-
-            public void onClose(int closeCode, String message)
-            {
-                close.set(closeCode);
-                _latch.countDown();
-            }
-        });
-
-        Socket socket = _server.accept();
-        accept(socket);
-
-        WebSocket.Connection connection = future.get(250,TimeUnit.MILLISECONDS);
-        Assert.assertNotNull(connection);
-        Assert.assertTrue(open.get());
-        Assert.assertEquals(0,close.get());
-
-        socket.close();
-        _latch.await(10,TimeUnit.SECONDS);
-
-        Assert.assertEquals(WebSocketConnectionRFC6455.CLOSE_NO_CLOSE,close.get());
-
-    }
-
-    @Test
-    public void testIdle() throws Exception
-    {
-        WebSocketClient client = new WebSocketClient(_factory);
-        client.setMaxIdleTime(500);
-
-        final AtomicBoolean open = new AtomicBoolean();
-        final AtomicInteger close = new AtomicInteger();
-        final CountDownLatch _latch = new CountDownLatch(1);
-        Future<WebSocket.Connection> future=client.open(new URI("ws://127.0.0.1:"+_serverPort+"/"),new WebSocket()
-        {
-            public void onOpen(Connection connection)
-            {
-                open.set(true);
-            }
-
-            public void onClose(int closeCode, String message)
-            {
-                close.set(closeCode);
-                _latch.countDown();
-            }
-        });
-
-        Socket socket = _server.accept();
-        accept(socket);
-
-        WebSocket.Connection connection = future.get(250,TimeUnit.MILLISECONDS);
-        Assert.assertNotNull(connection);
-        Assert.assertTrue(open.get());
-        Assert.assertEquals(0,close.get());
-
-        long start=System.currentTimeMillis();
-        _latch.await(10,TimeUnit.SECONDS);
-        Assert.assertTrue(System.currentTimeMillis()-start<5000);
-        Assert.assertEquals(WebSocketConnectionRFC6455.CLOSE_NORMAL,close.get());
-    }
-
-    @Test
-    public void testNotIdle() throws Exception
-    {
-        WebSocketClient client = new WebSocketClient(_factory);
-        client.setMaxIdleTime(500);
-
-        final AtomicBoolean open = new AtomicBoolean();
-        final AtomicInteger close = new AtomicInteger();
-        final CountDownLatch _latch = new CountDownLatch(1);
-        final BlockingQueue<String> queue = new BlockingArrayQueue<String>();
-        final StringBuilder closeMessage = new StringBuilder();
-        Future<WebSocket.Connection> future=client.open(new URI("ws://127.0.0.1:"+_serverPort+"/"),new WebSocket.OnTextMessage()
-        {
-            public void onOpen(Connection connection)
-            {
-                open.set(true);
-            }
-
-            public void onClose(int closeCode, String message)
-            {
-                close.set(closeCode);
-                closeMessage.append(message);
-                _latch.countDown();
-            }
-
-            public void onMessage(String data)
-            {
-                queue.add(data);
-            }
-        });
-
-        Socket socket = _server.accept();
-        accept(socket);
-
-        WebSocket.Connection connection = future.get(250,TimeUnit.MILLISECONDS);
-        Assert.assertNotNull(connection);
-        Assert.assertTrue(open.get());
-        Assert.assertEquals(0,close.get());
-
-
-
-        // Send some messages client to server
-        byte[] recv = new byte[1024];
-        int len=-1;
-        for (int i=0;i<10;i++)
-        {
-            Thread.sleep(250);
-            connection.sendMessage("Hello");
-            len=socket.getInputStream().read(recv,0,recv.length);
-            Assert.assertTrue(len>0);
-        }
-
-        // Send some messages server to client
-        byte[] send = new byte[] { (byte)0x81, (byte) 0x02, (byte)'H', (byte)'i'};
-
-        for (int i=0;i<10;i++)
-        {
-            Thread.sleep(250);
-            socket.getOutputStream().write(send,0,send.length);
-            socket.getOutputStream().flush();
-            Assert.assertEquals("Hi",queue.poll(1,TimeUnit.SECONDS));
-        }
-
-        // Close with code
-        long start=System.currentTimeMillis();
-        socket.getOutputStream().write(new byte[]{(byte)0x88, (byte) 0x02, (byte)4, (byte)87 },0,4);
-        socket.getOutputStream().flush();
-
-        _latch.await(10,TimeUnit.SECONDS);
-        Assert.assertTrue(System.currentTimeMillis()-start<5000);
-        Assert.assertEquals(1002,close.get());
-        Assert.assertEquals("Invalid close code 1111", closeMessage.toString());
-    }
-
-    @Test
-    public void testBlockSending() throws Exception
-    {
-        WebSocketClient client = new WebSocketClient(_factory);
-        client.setMaxIdleTime(10000);
-
-        final AtomicBoolean open = new AtomicBoolean();
-        final AtomicInteger close = new AtomicInteger();
-        final CountDownLatch _latch = new CountDownLatch(1);
-        Future<WebSocket.Connection> future=client.open(new URI("ws://127.0.0.1:"+_serverPort+"/"),new WebSocket.OnTextMessage()
-        {
-            public void onOpen(Connection connection)
-            {
-                open.set(true);
-            }
-
-            public void onClose(int closeCode, String message)
-            {
-                close.set(closeCode);
-                _latch.countDown();
-            }
-
-            public void onMessage(String data)
-            {
-            }
-        });
-
-        final Socket socket = _server.accept();
-        accept(socket);
-
-        WebSocket.Connection connection = future.get(250,TimeUnit.MILLISECONDS);
-        Assert.assertNotNull(connection);
-        Assert.assertTrue(open.get());
-        Assert.assertEquals(0,close.get());
-
-        final int messages=200000;
-        final AtomicLong totalB=new AtomicLong();
-
-        Thread consumer = new Thread()
-        {
-            @Override
-            public void run()
-            {
-                // Thread.sleep is for artificially poor performance reader needed for this testcase.
-                try
-                {
-                    Thread.sleep(200);
-                    byte[] recv = new byte[32*1024];
-
-                    int len=0;
-                    while (len>=0)
-                    {
-                        totalB.addAndGet(len);
-                        len=socket.getInputStream().read(recv,0,recv.length);
-                        Thread.sleep(10);
-                    }
-                }
-                catch(InterruptedException e)
-                {
-                    return;
-                }
-                catch(Exception e)
-                {
-                    e.printStackTrace();
-                }
-            }
-        };
-        consumer.start();
-
-        // Send lots of messages client to server
-        long start=System.currentTimeMillis();
-        String mesg="This is a test message to send";
-        for (int i=0;i<messages;i++)
-        {
-            connection.sendMessage(mesg);
-        }
-
-        // Duration for the write phase
-        long writeDur = (System.currentTimeMillis() - start);
-
-        // wait for consumer to complete
-        while (totalB.get()<messages*(mesg.length()+6L))
-        {
-            Thread.sleep(10);
-        }
-
-        Assert.assertThat("write duration", writeDur, greaterThan(1000L)); // writing was blocked
-        Assert.assertEquals(messages*(mesg.length()+6L),totalB.get());
-
-        consumer.interrupt();
-    }
-
-    @Test
-    public void testBlockReceiving() throws Exception
-    {
-        WebSocketClient client = new WebSocketClient(_factory);
-        client.setMaxIdleTime(60000);
-
-        final AtomicBoolean open = new AtomicBoolean();
-        final AtomicInteger close = new AtomicInteger();
-        final CountDownLatch _latch = new CountDownLatch(1);
-        final StringBuilder closeMessage = new StringBuilder();
-        final Exchanger<String> exchanger = new Exchanger<String>();
-        Future<WebSocket.Connection> future=client.open(new URI("ws://127.0.0.1:"+_serverPort+"/"),new WebSocket.OnTextMessage()
-        {
-            public void onOpen(Connection connection)
-            {
-                open.set(true);
-            }
-
-            public void onClose(int closeCode, String message)
-            {
-                close.set(closeCode);
-                closeMessage.append(message);
-                _latch.countDown();
-            }
-
-            public void onMessage(String data)
-            {
-                try
-                {
-                    exchanger.exchange(data);
-                }
-                catch (InterruptedException e)
-                {
-                    // e.printStackTrace();
-                }
-            }
-        });
-
-        Socket socket = _server.accept();
-        socket.setSoTimeout(60000);
-        accept(socket);
-
-        WebSocket.Connection connection = future.get(250,TimeUnit.MILLISECONDS);
-        Assert.assertNotNull(connection);
-        Assert.assertTrue(open.get());
-        Assert.assertEquals(0,close.get());
-
-        // define some messages to send server to client
-        byte[] send = new byte[] { (byte)0x81, (byte) 0x05,
-                (byte)'H', (byte)'e', (byte)'l', (byte)'l',(byte)'o'  };
-        final int messages=100000;
-        final AtomicInteger m = new AtomicInteger();
-
-
-        // Set up a consumer of received messages that waits a while before consuming
-        Thread consumer = new Thread()
-        {
-            @Override
-            public void run()
-            {
-                try
-                {
-                    Thread.sleep(200);
-                    while (m.get() < messages)
-                    {
-                        String msg = exchanger.exchange(null);
-                        if ("Hello".equals(msg))
-                        {
-                            m.incrementAndGet();
-                        }
-                        else
-                        {
-                            throw new IllegalStateException("exchanged " + msg);
-                        }
-                        if (m.get() % 1000 == 0)
-                        {
-                            // Artificially slow reader
-                            Thread.sleep(10);
-                        }
-                    }
-                }
-                catch(InterruptedException e)
-                {
-                    return;
-                }
-                catch(Exception e)
-                {
-                    e.printStackTrace();
-                }
-            }
-        };
-        consumer.start();
-
-        long start=System.currentTimeMillis();
-        for (int i=0;i<messages;i++)
-        {
-            socket.getOutputStream().write(send,0,send.length);
-            socket.getOutputStream().flush();
-        }
-
-        while(consumer.isAlive())
-        {
-            Thread.sleep(10);
-        }
-
-        // Duration of the read operation.
-        long readDur = (System.currentTimeMillis() - start);
-
-        Assert.assertThat("read duration", readDur, greaterThan(1000L)); // reading was blocked
-        Assert.assertEquals(m.get(),messages);
-
-        // Close with code
-        start=System.currentTimeMillis();
-        socket.getOutputStream().write(new byte[]{(byte)0x88, (byte) 0x02, (byte)4, (byte)87 },0,4);
-        socket.getOutputStream().flush();
-
-        _latch.await(10,TimeUnit.SECONDS);
-        Assert.assertTrue(System.currentTimeMillis()-start<5000);
-        Assert.assertEquals(1002,close.get());
-        Assert.assertEquals("Invalid close code 1111", closeMessage.toString());
-    }
-
-    @Test
-    public void testURIWithDefaultPort() throws Exception
-    {
-        URI uri = new URI("ws://localhost");
-        InetSocketAddress addr = WebSocketClient.toSocketAddress(uri);
-        Assert.assertThat("URI (" + uri + ").host", addr.getHostName(), is("localhost"));
-        Assert.assertThat("URI (" + uri + ").port", addr.getPort(), is(80));
-    }
-
-    @Test
-    public void testURIWithDefaultWSSPort() throws Exception
-    {
-        URI uri = new URI("wss://localhost");
-        InetSocketAddress addr = WebSocketClient.toSocketAddress(uri);
-        Assert.assertThat("URI (" + uri + ").host", addr.getHostName(), is("localhost"));
-        Assert.assertThat("URI (" + uri + ").port", addr.getPort(), is(443));
-    }
-
-    private void respondToClient(Socket connection, String serverResponse) throws IOException
-    {
-        InputStream in = null;
-        InputStreamReader isr = null;
-        BufferedReader buf = null;
-        OutputStream out = null;
-        try {
-            in = connection.getInputStream();
-            isr = new InputStreamReader(in);
-            buf = new BufferedReader(isr);
-            String line;
-            while((line = buf.readLine())!=null)
-            {
-                // System.err.println(line);
-                if(line.length() == 0)
-                {
-                    // Got the "\r\n" line.
-                    break;
-                }
-            }
-
-            // System.out.println("[Server-Out] " + serverResponse);
-            out = connection.getOutputStream();
-            out.write(serverResponse.getBytes());
-            out.flush();
-        }
-        finally
-        {
-            IO.close(buf);
-            IO.close(isr);
-            IO.close(in);
-            IO.close(out);
-        }
-    }
-
-    private void accept(Socket connection) throws IOException
-    {
-        String key="not sent";
-        BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
-        for (String line=in.readLine();line!=null;line=in.readLine())
-        {
-            if (line.length()==0)
-                break;
-            if (line.startsWith("Sec-WebSocket-Key:"))
-                key=line.substring(18).trim();
-        }
-        connection.getOutputStream().write((
-                "HTTP/1.1 101 Upgrade\r\n" +
-                "Sec-WebSocket-Accept: "+ WebSocketConnectionRFC6455.hashKey(key) +"\r\n" +
-                "\r\n").getBytes());
-    }
-}
diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketCommTest.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketCommTest.java
deleted file mode 100644
index e71055e..0000000
--- a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketCommTest.java
+++ /dev/null
@@ -1,130 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.websocket;
-
-import java.net.URI;
-import java.util.concurrent.TimeUnit;
-
-import org.eclipse.jetty.server.Connector;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.servlet.ServletContextHandler;
-import org.eclipse.jetty.servlet.ServletHolder;
-import org.eclipse.jetty.websocket.helper.CaptureSocket;
-import org.eclipse.jetty.websocket.helper.MessageSender;
-import org.eclipse.jetty.websocket.helper.WebSocketCaptureServlet;
-import org.junit.After;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-
-import static org.hamcrest.Matchers.is;
-import static org.hamcrest.Matchers.notNullValue;
-
-/**
- * WebSocketCommTest - to test reported undelivered messages in bug <a
- * href="https://jira.codehaus.org/browse/JETTY-1463">JETTY-1463</a>
- */
-public class WebSocketCommTest
-{
-    private Server server;
-    private WebSocketCaptureServlet servlet;
-    private URI serverUri;
-
-    @Before
-    public void startServer() throws Exception
-    {
-        // Configure Server
-        server = new Server(0);
-
-        ServletContextHandler context = new ServletContextHandler();
-        context.setContextPath("/");
-        server.setHandler(context);
-
-        // Serve capture servlet
-        servlet = new WebSocketCaptureServlet();
-        context.addServlet(new ServletHolder(servlet),"/");
-
-        // Start Server
-        server.start();
-
-        Connector conn = server.getConnectors()[0];
-        String host = conn.getHost();
-        if (host == null)
-        {
-            host = "localhost";
-        }
-        int port = conn.getLocalPort();
-        serverUri = new URI(String.format("ws://%s:%d/",host,port));
-        System.out.printf("Server URI: %s%n",serverUri);
-    }
-
-    @After
-    public void stopServer()
-    {
-        try
-        {
-            server.stop();
-        }
-        catch (Exception e)
-        {
-            e.printStackTrace(System.err);
-        }
-    }
-
-    @Test
-    public void testSendTextMessages() throws Exception
-    {
-        WebSocketClientFactory clientFactory = new WebSocketClientFactory();
-        clientFactory.start();
-
-        WebSocketClient wsc = clientFactory.newWebSocketClient();
-        MessageSender sender = new MessageSender();
-        wsc.open(serverUri,sender);
-
-        try
-        {
-            sender.awaitConnect();
-
-            // Send 5 short messages
-            for (int i = 0; i < 5; i++)
-            {
-                System.out.printf("Sending msg-%d%n",i);
-                sender.sendMessage("msg-%d",i);
-            }
-
-            // Servlet should show only 1 connection.
-            Assert.assertThat("Servlet.captureSockets.size",servlet.captures.size(),is(1));
-
-            CaptureSocket socket = servlet.captures.get(0);
-            Assert.assertThat("CaptureSocket",socket,notNullValue());
-            Assert.assertThat("CaptureSocket.isConnected",socket.awaitConnected(1000),is(true));
-
-            // Give servlet time to process messages
-            TimeUnit.MILLISECONDS.sleep(500);
-
-            // Should have captured 5 messages.
-            Assert.assertThat("CaptureSocket.messages.size",socket.messages.size(),is(5));
-        }
-        finally
-        {
-            System.out.println("Closing client socket");
-            sender.close();
-        }
-    }
-}
diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketGeneratorD00Test.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketGeneratorD00Test.java
deleted file mode 100644
index a3d477c..0000000
--- a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketGeneratorD00Test.java
+++ /dev/null
@@ -1,94 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.websocket;
-
-import static junit.framework.Assert.*;
-
-import org.eclipse.jetty.io.ByteArrayBuffer;
-import org.eclipse.jetty.io.ByteArrayEndPoint;
-import org.eclipse.jetty.util.StringUtil;
-import org.junit.Before;
-import org.junit.Test;
-
-public class WebSocketGeneratorD00Test
-{
-    private ByteArrayBuffer _out;
-    private WebSocketGenerator _generator;
-
-    @Before
-    public void setUp() throws Exception
-    {
-        WebSocketBuffers buffers = new WebSocketBuffers(1024);
-        ByteArrayEndPoint endPoint = new ByteArrayEndPoint();
-        _generator = new WebSocketGeneratorD00(buffers, endPoint);
-        _out = new ByteArrayBuffer(4096);
-        endPoint.setOut(_out);
-    }
-
-    @Test
-    public void testOneString() throws Exception
-    {
-        byte[] data="Hell\uFF4F W\uFF4Frld".getBytes(StringUtil.__UTF8);
-        _generator.addFrame((byte)0x0,(byte)0x04,data,0,data.length);
-        _generator.flush();
-        assertEquals((byte)0x04,_out.get());
-        assertEquals('H',_out.get());
-        assertEquals('e',_out.get());
-        assertEquals('l',_out.get());
-        assertEquals('l',_out.get());
-        assertEquals(0xEF,0xff&_out.get());
-        assertEquals(0xBD,0xff&_out.get());
-        assertEquals(0x8F,0xff&_out.get());
-        assertEquals(' ',_out.get());
-        assertEquals('W',_out.get());
-        assertEquals(0xEF,0xff&_out.get());
-        assertEquals(0xBD,0xff&_out.get());
-        assertEquals(0x8F,0xff&_out.get());
-        assertEquals('r',_out.get());
-        assertEquals('l',_out.get());
-        assertEquals('d',_out.get());
-        assertEquals((byte)0xff,_out.get());
-    }
-
-    @Test
-    public void testOneBinaryString() throws Exception
-    {
-        byte[] data="Hell\uFF4F W\uFF4Frld".getBytes(StringUtil.__UTF8);
-        _generator.addFrame((byte)0x0,(byte)0x84,data,0,data.length);
-        _generator.flush();
-        assertEquals((byte)0x84,_out.get());
-        assertEquals(15,_out.get());
-        assertEquals('H',_out.get());
-        assertEquals('e',_out.get());
-        assertEquals('l',_out.get());
-        assertEquals('l',_out.get());
-        assertEquals(0xEF,0xff&_out.get());
-        assertEquals(0xBD,0xff&_out.get());
-        assertEquals(0x8F,0xff&_out.get());
-        assertEquals(' ',_out.get());
-        assertEquals('W',_out.get());
-        assertEquals(0xEF,0xff&_out.get());
-        assertEquals(0xBD,0xff&_out.get());
-        assertEquals(0x8F,0xff&_out.get());
-        assertEquals('r',_out.get());
-        assertEquals('l',_out.get());
-        assertEquals('d',_out.get());
-    }
-
-}
diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketGeneratorD06Test.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketGeneratorD06Test.java
deleted file mode 100644
index d403f61..0000000
--- a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketGeneratorD06Test.java
+++ /dev/null
@@ -1,216 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.websocket;
-
-import static junit.framework.Assert.*;
-
-import org.eclipse.jetty.io.ByteArrayBuffer;
-import org.eclipse.jetty.io.ByteArrayEndPoint;
-import org.eclipse.jetty.util.StringUtil;
-import org.junit.Before;
-import org.junit.Test;
-
-public class WebSocketGeneratorD06Test
-{
-    private ByteArrayBuffer _out;
-    private WebSocketGenerator _generator;
-    ByteArrayEndPoint _endPoint;
-    WebSocketBuffers _buffers;
-    byte[] _mask = new byte[4];
-    int _m;
-    
-    public MaskGen _maskGen = new FixedMaskGen(
-            new byte[]{(byte)0x00,(byte)0x00,(byte)0x0f,(byte)0xff});
-
-    @Before
-    public void setUp() throws Exception
-    {
-        _endPoint = new ByteArrayEndPoint();
-        _out = new ByteArrayBuffer(2048);
-        _endPoint.setOut(_out);
-        _buffers = new WebSocketBuffers(1024);
-        _m=0;
-    }
-
-    byte getMasked()
-    {
-        return (byte)(_out.get()^_mask[_m++%4]);
-    }
-    
-    
-    @Test
-    public void testOneString() throws Exception
-    {
-        _generator = new WebSocketGeneratorD06(_buffers, _endPoint,null);
-
-        byte[] data = "Hell\uFF4F W\uFF4Frld".getBytes(StringUtil.__UTF8);
-        _generator.addFrame((byte)0x8,(byte)0x04,data,0,data.length);
-        _generator.flush();
-        assertEquals((byte)0x84,_out.get());
-        assertEquals(15,0xff&_out.get());
-        assertEquals('H',_out.get());
-        assertEquals('e',_out.get());
-        assertEquals('l',_out.get());
-        assertEquals('l',_out.get());
-        assertEquals(0xEF,0xff&_out.get());
-        assertEquals(0xBD,0xff&_out.get());
-        assertEquals(0x8F,0xff&_out.get());
-        assertEquals(' ',_out.get());
-        assertEquals('W',_out.get());
-        assertEquals(0xEF,0xff&_out.get());
-        assertEquals(0xBD,0xff&_out.get());
-        assertEquals(0x8F,0xff&_out.get());
-        assertEquals('r',_out.get());
-        assertEquals('l',_out.get());
-        assertEquals('d',_out.get());
-    }
-
-    @Test
-    public void testOneBuffer() throws Exception
-    {
-        _generator = new WebSocketGeneratorD06(_buffers, _endPoint,null);
-        
-        String string = "Hell\uFF4F W\uFF4Frld";
-        byte[] bytes=string.getBytes(StringUtil.__UTF8);
-        _generator.addFrame((byte)0x8,(byte)0x04,bytes,0,bytes.length);
-        _generator.flush();
-        assertEquals((byte)0x84,_out.get());
-        assertEquals(15,0xff&_out.get());
-        assertEquals('H',_out.get());
-        assertEquals('e',_out.get());
-        assertEquals('l',_out.get());
-        assertEquals('l',_out.get());
-        assertEquals(0xEF,0xff&_out.get());
-        assertEquals(0xBD,0xff&_out.get());
-        assertEquals(0x8F,0xff&_out.get());
-        assertEquals(' ',_out.get());
-        assertEquals('W',_out.get());
-        assertEquals(0xEF,0xff&_out.get());
-        assertEquals(0xBD,0xff&_out.get());
-        assertEquals(0x8F,0xff&_out.get());
-        assertEquals('r',_out.get());
-        assertEquals('l',_out.get());
-        assertEquals('d',_out.get());
-    }
-
-    @Test
-    public void testOneLongBuffer() throws Exception
-    {
-        _generator = new WebSocketGeneratorD06(_buffers, _endPoint,null);
-        
-        byte[] b=new byte[150];
-        for (int i=0;i<b.length;i++)
-            b[i]=(byte)('0'+(i%10));
-
-        _generator.addFrame((byte)0x8,(byte)0x4,b,0,b.length);
-
-        _generator.flush();
-        assertEquals((byte)0x84,_out.get());
-        assertEquals((byte)126,_out.get());
-        assertEquals((byte)0,_out.get());
-        assertEquals((byte)b.length,_out.get());
-        
-        for (int i=0;i<b.length;i++)
-            assertEquals('0'+(i%10),0xff&_out.get());
-    }
-
-    @Test
-    public void testOneStringMasked() throws Exception
-    {
-        _generator = new WebSocketGeneratorD06(_buffers, _endPoint,_maskGen);
-
-        byte[] data = "Hell\uFF4F W\uFF4Frld".getBytes(StringUtil.__UTF8);
-        _generator.addFrame((byte)0x8,(byte)0x04,data,0,data.length);
-        _generator.flush();
-        
-        _out.get(_mask,0,4);
-        
-        assertEquals((byte)0x84,getMasked());
-        assertEquals(15,0xff&getMasked());
-        assertEquals('H',getMasked());
-        assertEquals('e',getMasked());
-        assertEquals('l',getMasked());
-        assertEquals('l',getMasked());
-        assertEquals(0xEF,0xff&getMasked());
-        assertEquals(0xBD,0xff&getMasked());
-        assertEquals(0x8F,0xff&getMasked());
-        assertEquals(' ',getMasked());
-        assertEquals('W',getMasked());
-        assertEquals(0xEF,0xff&getMasked());
-        assertEquals(0xBD,0xff&getMasked());
-        assertEquals(0x8F,0xff&getMasked());
-        assertEquals('r',getMasked());
-        assertEquals('l',getMasked());
-        assertEquals('d',getMasked());
-    }
-
-    @Test
-    public void testOneBufferMasked() throws Exception
-    {
-        _generator = new WebSocketGeneratorD06(_buffers, _endPoint,_maskGen);
-        
-        String string = "Hell\uFF4F W\uFF4Frld";
-        byte[] bytes=string.getBytes(StringUtil.__UTF8);
-        _generator.addFrame((byte)0x8,(byte)0x04,bytes,0,bytes.length);
-        _generator.flush();
-        
-        _out.get(_mask,0,4);
-        
-        assertEquals((byte)0x84,getMasked());
-        assertEquals(15,0xff&getMasked());
-        assertEquals('H',getMasked());
-        assertEquals('e',getMasked());
-        assertEquals('l',getMasked());
-        assertEquals('l',getMasked());
-        assertEquals(0xEF,0xff&getMasked());
-        assertEquals(0xBD,0xff&getMasked());
-        assertEquals(0x8F,0xff&getMasked());
-        assertEquals(' ',getMasked());
-        assertEquals('W',getMasked());
-        assertEquals(0xEF,0xff&getMasked());
-        assertEquals(0xBD,0xff&getMasked());
-        assertEquals(0x8F,0xff&getMasked());
-        assertEquals('r',getMasked());
-        assertEquals('l',getMasked());
-        assertEquals('d',getMasked());
-    }
-
-    @Test
-    public void testOneLongBufferMasked() throws Exception
-    {
-        _generator = new WebSocketGeneratorD06(_buffers, _endPoint,_maskGen);
-        
-        byte[] b=new byte[150];
-        for (int i=0;i<b.length;i++)
-            b[i]=(byte)('0'+(i%10));
-
-        _generator.addFrame((byte)0x8,(byte)0x04,b,0,b.length);
-        _generator.flush();
-        
-        _out.get(_mask,0,4);
-        assertEquals((byte)0x84,getMasked());
-        assertEquals((byte)126,getMasked());
-        assertEquals((byte)0,getMasked());
-        assertEquals((byte)b.length,getMasked());
-        
-        for (int i=0;i<b.length;i++)
-            assertEquals('0'+(i%10),0xff&getMasked());
-    }
-    
-}
diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketGeneratorD08Test.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketGeneratorD08Test.java
deleted file mode 100644
index 5de4571..0000000
--- a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketGeneratorD08Test.java
+++ /dev/null
@@ -1,215 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.websocket;
-
-import static junit.framework.Assert.*;
-
-import org.eclipse.jetty.io.ByteArrayBuffer;
-import org.eclipse.jetty.io.ByteArrayEndPoint;
-import org.eclipse.jetty.util.StringUtil;
-import org.junit.Before;
-import org.junit.Test;
-
-public class WebSocketGeneratorD08Test
-{
-    private ByteArrayBuffer _out;
-    private WebSocketGenerator _generator;
-    ByteArrayEndPoint _endPoint;
-    WebSocketBuffers _buffers;
-    byte[] _mask = new byte[4];
-    int _m;
-    
-    public MaskGen _maskGen = new FixedMaskGen(
-            new byte[]{(byte)0x00,(byte)0x00,(byte)0x0f,(byte)0xff});
-
-    @Before
-    public void setUp() throws Exception
-    {
-        _endPoint = new ByteArrayEndPoint();
-        _out = new ByteArrayBuffer(2048);
-        _endPoint.setOut(_out);
-        _buffers = new WebSocketBuffers(1024);
-        _m=0;
-    }
-
-    byte getMasked()
-    {
-        return (byte)(_out.get()^_mask[_m++%4]);
-    }
-    
-    
-    @Test
-    public void testOneString() throws Exception
-    {
-        _generator = new WebSocketGeneratorD08(_buffers, _endPoint,null);
-
-        byte[] data = "Hell\uFF4F W\uFF4Frld".getBytes(StringUtil.__UTF8);
-        _generator.addFrame((byte)0x8,(byte)0x04,data,0,data.length);
-        _generator.flush();
-        assertEquals((byte)0x84,_out.get());
-        assertEquals(15,0xff&_out.get());
-        assertEquals('H',_out.get());
-        assertEquals('e',_out.get());
-        assertEquals('l',_out.get());
-        assertEquals('l',_out.get());
-        assertEquals(0xEF,0xff&_out.get());
-        assertEquals(0xBD,0xff&_out.get());
-        assertEquals(0x8F,0xff&_out.get());
-        assertEquals(' ',_out.get());
-        assertEquals('W',_out.get());
-        assertEquals(0xEF,0xff&_out.get());
-        assertEquals(0xBD,0xff&_out.get());
-        assertEquals(0x8F,0xff&_out.get());
-        assertEquals('r',_out.get());
-        assertEquals('l',_out.get());
-        assertEquals('d',_out.get());
-    }
-
-    @Test
-    public void testOneBuffer() throws Exception
-    {
-        _generator = new WebSocketGeneratorD08(_buffers, _endPoint,null);
-        
-        String string = "Hell\uFF4F W\uFF4Frld";
-        byte[] bytes=string.getBytes(StringUtil.__UTF8);
-        _generator.addFrame((byte)0x8,(byte)0x04,bytes,0,bytes.length);
-        _generator.flush();
-        assertEquals((byte)0x84,_out.get());
-        assertEquals(15,0xff&_out.get());
-        assertEquals('H',_out.get());
-        assertEquals('e',_out.get());
-        assertEquals('l',_out.get());
-        assertEquals('l',_out.get());
-        assertEquals(0xEF,0xff&_out.get());
-        assertEquals(0xBD,0xff&_out.get());
-        assertEquals(0x8F,0xff&_out.get());
-        assertEquals(' ',_out.get());
-        assertEquals('W',_out.get());
-        assertEquals(0xEF,0xff&_out.get());
-        assertEquals(0xBD,0xff&_out.get());
-        assertEquals(0x8F,0xff&_out.get());
-        assertEquals('r',_out.get());
-        assertEquals('l',_out.get());
-        assertEquals('d',_out.get());
-    }
-
-    @Test
-    public void testOneLongBuffer() throws Exception
-    {
-        _generator = new WebSocketGeneratorD08(_buffers, _endPoint,null);
-        
-        byte[] b=new byte[150];
-        for (int i=0;i<b.length;i++)
-            b[i]=(byte)('0'+(i%10));
-
-        _generator.addFrame((byte)0x8,(byte)0x4,b,0,b.length);
-
-        _generator.flush();
-        assertEquals((byte)0x84,_out.get());
-        assertEquals((byte)126,_out.get());
-        assertEquals((byte)0,_out.get());
-        assertEquals((byte)b.length,_out.get());
-        
-        for (int i=0;i<b.length;i++)
-            assertEquals('0'+(i%10),0xff&_out.get());
-    }
-
-    @Test
-    public void testOneStringMasked() throws Exception
-    {
-        _generator = new WebSocketGeneratorD08(_buffers, _endPoint,_maskGen);
-
-        byte[] data = "Hell\uFF4F W\uFF4Frld".getBytes(StringUtil.__UTF8);
-        _generator.addFrame((byte)0x8,(byte)0x04,data,0,data.length);
-        _generator.flush();
-        
-        assertEquals((byte)0x84,_out.get());
-        assertEquals(15,0x7f&_out.get());
-        _out.get(_mask,0,4);
-        assertEquals('H',getMasked());
-        assertEquals('e',getMasked());
-        assertEquals('l',getMasked());
-        assertEquals('l',getMasked());
-        assertEquals(0xEF,0xff&getMasked());
-        assertEquals(0xBD,0xff&getMasked());
-        assertEquals(0x8F,0xff&getMasked());
-        assertEquals(' ',getMasked());
-        assertEquals('W',getMasked());
-        assertEquals(0xEF,0xff&getMasked());
-        assertEquals(0xBD,0xff&getMasked());
-        assertEquals(0x8F,0xff&getMasked());
-        assertEquals('r',getMasked());
-        assertEquals('l',getMasked());
-        assertEquals('d',getMasked());
-    }
-
-    @Test
-    public void testOneBufferMasked() throws Exception
-    {
-        _generator = new WebSocketGeneratorD08(_buffers, _endPoint,_maskGen);
-        
-        String string = "Hell\uFF4F W\uFF4Frld";
-        byte[] bytes=string.getBytes(StringUtil.__UTF8);
-        _generator.addFrame((byte)0x8,(byte)0x04,bytes,0,bytes.length);
-        _generator.flush();
-        
-        assertEquals((byte)0x84,_out.get());
-        assertEquals(15,0x7f&_out.get());
-        _out.get(_mask,0,4);
-        assertEquals('H',getMasked());
-        assertEquals('e',getMasked());
-        assertEquals('l',getMasked());
-        assertEquals('l',getMasked());
-        assertEquals(0xEF,0xff&getMasked());
-        assertEquals(0xBD,0xff&getMasked());
-        assertEquals(0x8F,0xff&getMasked());
-        assertEquals(' ',getMasked());
-        assertEquals('W',getMasked());
-        assertEquals(0xEF,0xff&getMasked());
-        assertEquals(0xBD,0xff&getMasked());
-        assertEquals(0x8F,0xff&getMasked());
-        assertEquals('r',getMasked());
-        assertEquals('l',getMasked());
-        assertEquals('d',getMasked());
-    }
-
-    @Test
-    public void testOneLongBufferMasked() throws Exception
-    {
-        _generator = new WebSocketGeneratorD08(_buffers, _endPoint,_maskGen);
-        
-        byte[] b=new byte[150];
-        for (int i=0;i<b.length;i++)
-            b[i]=(byte)('0'+(i%10));
-
-        _generator.addFrame((byte)0x8,(byte)0x04,b,0,b.length);
-        _generator.flush();
-        
-        assertEquals((byte)0x84,_out.get());
-        assertEquals((byte)126,0x7f&_out.get());
-        assertEquals((byte)0,_out.get());
-        assertEquals((byte)b.length,_out.get());
-
-        _out.get(_mask,0,4);
-        
-        for (int i=0;i<b.length;i++)
-            assertEquals('0'+(i%10),0xff&getMasked());
-    }
-    
-}
diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketGeneratorRFC6455Test.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketGeneratorRFC6455Test.java
deleted file mode 100644
index ced050d..0000000
--- a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketGeneratorRFC6455Test.java
+++ /dev/null
@@ -1,254 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.websocket;
-
-import static junit.framework.Assert.*;
-
-import org.eclipse.jetty.io.ByteArrayBuffer;
-import org.eclipse.jetty.io.ByteArrayEndPoint;
-import org.eclipse.jetty.io.EofException;
-import org.eclipse.jetty.util.StringUtil;
-import org.junit.Before;
-import org.junit.Test;
-
-/**
- */
-public class WebSocketGeneratorRFC6455Test
-{
-    private ByteArrayBuffer _out;
-    private WebSocketGenerator _generator;
-    ByteArrayEndPoint _endPoint;
-    WebSocketBuffers _buffers;
-    byte[] _mask = new byte[4];
-    int _m;
-    
-    public MaskGen _maskGen = new FixedMaskGen(
-            new byte[]{(byte)0x00,(byte)0x00,(byte)0x0f,(byte)0xff});
-
-    @Before
-    public void setUp() throws Exception
-    {
-        _endPoint = new ByteArrayEndPoint();
-        _out = new ByteArrayBuffer(2048);
-        _endPoint.setOut(_out);
-        _buffers = new WebSocketBuffers(1024);
-        _m=0;
-    }
-
-    byte getMasked()
-    {
-        return (byte)(_out.get()^_mask[_m++%4]);
-    }
-    
-    
-    @Test
-    public void testOneString() throws Exception
-    {
-        _generator = new WebSocketGeneratorRFC6455(_buffers, _endPoint,null);
-
-        byte[] data = "Hell\uFF4F W\uFF4Frld".getBytes(StringUtil.__UTF8);
-        _generator.addFrame((byte)0x8,(byte)0x04,data,0,data.length);
-        _generator.flush();
-        assertEquals((byte)0x84,_out.get());
-        assertEquals(15,0xff&_out.get());
-        assertEquals('H',_out.get());
-        assertEquals('e',_out.get());
-        assertEquals('l',_out.get());
-        assertEquals('l',_out.get());
-        assertEquals(0xEF,0xff&_out.get());
-        assertEquals(0xBD,0xff&_out.get());
-        assertEquals(0x8F,0xff&_out.get());
-        assertEquals(' ',_out.get());
-        assertEquals('W',_out.get());
-        assertEquals(0xEF,0xff&_out.get());
-        assertEquals(0xBD,0xff&_out.get());
-        assertEquals(0x8F,0xff&_out.get());
-        assertEquals('r',_out.get());
-        assertEquals('l',_out.get());
-        assertEquals('d',_out.get());
-    }
-
-    @Test
-    public void testOneBuffer() throws Exception
-    {
-        _generator = new WebSocketGeneratorRFC6455(_buffers, _endPoint,null);
-        
-        String string = "Hell\uFF4F W\uFF4Frld";
-        byte[] bytes=string.getBytes(StringUtil.__UTF8);
-        _generator.addFrame((byte)0x8,(byte)0x04,bytes,0,bytes.length);
-        _generator.flush();
-        assertEquals((byte)0x84,_out.get());
-        assertEquals(15,0xff&_out.get());
-        assertEquals('H',_out.get());
-        assertEquals('e',_out.get());
-        assertEquals('l',_out.get());
-        assertEquals('l',_out.get());
-        assertEquals(0xEF,0xff&_out.get());
-        assertEquals(0xBD,0xff&_out.get());
-        assertEquals(0x8F,0xff&_out.get());
-        assertEquals(' ',_out.get());
-        assertEquals('W',_out.get());
-        assertEquals(0xEF,0xff&_out.get());
-        assertEquals(0xBD,0xff&_out.get());
-        assertEquals(0x8F,0xff&_out.get());
-        assertEquals('r',_out.get());
-        assertEquals('l',_out.get());
-        assertEquals('d',_out.get());
-    }
-
-    @Test
-    public void testOneLongBuffer() throws Exception
-    {
-        _generator = new WebSocketGeneratorRFC6455(_buffers, _endPoint,null);
-        
-        byte[] b=new byte[150];
-        for (int i=0;i<b.length;i++)
-            b[i]=(byte)('0'+(i%10));
-
-        _generator.addFrame((byte)0x8,(byte)0x4,b,0,b.length);
-
-        _generator.flush();
-        assertEquals((byte)0x84,_out.get());
-        assertEquals((byte)126,_out.get());
-        assertEquals((byte)0,_out.get());
-        assertEquals((byte)b.length,_out.get());
-        
-        for (int i=0;i<b.length;i++)
-            assertEquals('0'+(i%10),0xff&_out.get());
-    }
-
-    @Test
-    public void testOneStringMasked() throws Exception
-    {
-        _generator = new WebSocketGeneratorRFC6455(_buffers, _endPoint,_maskGen);
-
-        byte[] data = "Hell\uFF4F W\uFF4Frld".getBytes(StringUtil.__UTF8);
-        _generator.addFrame((byte)0x8,(byte)0x04,data,0,data.length);
-        _generator.flush();
-        
-        assertEquals((byte)0x84,_out.get());
-        assertEquals(15,0x7f&_out.get());
-        _out.get(_mask,0,4);
-        assertEquals('H',getMasked());
-        assertEquals('e',getMasked());
-        assertEquals('l',getMasked());
-        assertEquals('l',getMasked());
-        assertEquals(0xEF,0xff&getMasked());
-        assertEquals(0xBD,0xff&getMasked());
-        assertEquals(0x8F,0xff&getMasked());
-        assertEquals(' ',getMasked());
-        assertEquals('W',getMasked());
-        assertEquals(0xEF,0xff&getMasked());
-        assertEquals(0xBD,0xff&getMasked());
-        assertEquals(0x8F,0xff&getMasked());
-        assertEquals('r',getMasked());
-        assertEquals('l',getMasked());
-        assertEquals('d',getMasked());
-    }
-
-    @Test
-    public void testOneBufferMasked() throws Exception
-    {
-        _generator = new WebSocketGeneratorRFC6455(_buffers, _endPoint,_maskGen);
-        
-        String string = "Hell\uFF4F W\uFF4Frld";
-        byte[] bytes=string.getBytes(StringUtil.__UTF8);
-        _generator.addFrame((byte)0x8,(byte)0x04,bytes,0,bytes.length);
-        _generator.flush();
-        
-        assertEquals((byte)0x84,_out.get());
-        assertEquals(15,0x7f&_out.get());
-        _out.get(_mask,0,4);
-        assertEquals('H',getMasked());
-        assertEquals('e',getMasked());
-        assertEquals('l',getMasked());
-        assertEquals('l',getMasked());
-        assertEquals(0xEF,0xff&getMasked());
-        assertEquals(0xBD,0xff&getMasked());
-        assertEquals(0x8F,0xff&getMasked());
-        assertEquals(' ',getMasked());
-        assertEquals('W',getMasked());
-        assertEquals(0xEF,0xff&getMasked());
-        assertEquals(0xBD,0xff&getMasked());
-        assertEquals(0x8F,0xff&getMasked());
-        assertEquals('r',getMasked());
-        assertEquals('l',getMasked());
-        assertEquals('d',getMasked());
-    }
-
-    @Test
-    public void testOneLongBufferMasked() throws Exception
-    {
-        _generator = new WebSocketGeneratorRFC6455(_buffers, _endPoint,_maskGen);
-        
-        byte[] b=new byte[150];
-        for (int i=0;i<b.length;i++)
-            b[i]=(byte)('0'+(i%10));
-
-        _generator.addFrame((byte)0x8,(byte)0x04,b,0,b.length);
-        _generator.flush();
-        
-        assertEquals((byte)0x84,_out.get());
-        assertEquals((byte)126,0x7f&_out.get());
-        assertEquals((byte)0,_out.get());
-        assertEquals((byte)b.length,_out.get());
-
-        _out.get(_mask,0,4);
-        
-        for (int i=0;i<b.length;i++)
-            assertEquals('0'+(i%10),0xff&getMasked());
-    }
-
-    
-    @Test
-    public void testClose() throws Exception
-    {
-        _generator = new WebSocketGeneratorRFC6455(_buffers, _endPoint,null);
-
-        byte[] data = "xxGame Over".getBytes(StringUtil.__UTF8);
-        data[0]=(byte)(1000/0x100);
-        data[1]=(byte)(1000%0x100);
-        _generator.addFrame((byte)0x8,(byte)0x08,data,0,data.length);
-        _generator.flush();
-        assertEquals((byte)0x88,_out.get());
-        assertEquals(11,0xff&_out.get());
-        _out.get();
-        _out.get();
-        assertEquals('G',_out.get());
-        assertEquals('a',_out.get());
-        assertEquals('m',_out.get());
-        assertEquals('e',_out.get());
-        assertEquals(' ',_out.get());
-        assertEquals('O',_out.get());
-        assertEquals('v',_out.get());
-        assertEquals('e',_out.get());
-        assertEquals('r',_out.get());
-
-        try
-        {
-            _generator.addFrame((byte)0x8,(byte)0x04,data,0,data.length);
-            assertTrue(false);
-        }
-        catch(EofException e)
-        {
-        }
-        
-        assertTrue(_endPoint.isOutputShutdown());
-    }
-}
diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketLoadD08Test.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketLoadD08Test.java
deleted file mode 100644
index 46e291e..0000000
--- a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketLoadD08Test.java
+++ /dev/null
@@ -1,252 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.websocket;
-
-import static org.junit.Assert.*;
-
-import java.io.BufferedReader;
-import java.io.BufferedWriter;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.OutputStreamWriter;
-import java.lang.management.ManagementFactory;
-import java.net.Socket;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
-
-import javax.servlet.http.HttpServletRequest;
-
-import junit.framework.Assert;
-
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.bio.SocketEndPoint;
-import org.eclipse.jetty.server.Connector;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.handler.DefaultHandler;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
-import org.eclipse.jetty.util.StringUtil;
-import org.eclipse.jetty.util.thread.QueuedThreadPool;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.Test;
-
-public class WebSocketLoadD08Test
-{
-    private static Server _server;
-    private static Connector _connector;
-
-    @BeforeClass
-    public static void startServer() throws Exception
-    {
-        _server = new Server();
-
-        _connector = new SelectChannelConnector();
-        _server.addConnector(_connector);
-
-        QueuedThreadPool threadPool = new QueuedThreadPool(200);
-        threadPool.setMaxStopTimeMs(1000);
-        _server.setThreadPool(threadPool);
-
-        WebSocketHandler wsHandler = new WebSocketHandler()
-        {
-            public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol)
-            {
-                return new EchoWebSocket();
-            }
-        };
-        wsHandler.getWebSocketFactory().setMinVersion(8);
-        wsHandler.setHandler(new DefaultHandler());
-        _server.setHandler(wsHandler);
-
-        _server.start();
-    }
-
-    @AfterClass
-    public static void stopServer() throws Exception
-    {
-        _server.stop();
-        _server.join();
-    }
-
-    @Test
-    public void testLoad() throws Exception
-    {
-        int count = 50;
-        int iterations = 100;
-
-        ExecutorService threadPool = Executors.newCachedThreadPool();
-        try
-        {
-            CountDownLatch latch = new CountDownLatch(count * iterations);
-            WebSocketClient[] clients = new WebSocketClient[count];
-            for (int i = 0; i < clients.length; ++i)
-            {
-                clients[i] = new WebSocketClient("localhost", _connector.getLocalPort(), 1000, latch, iterations);
-            }
-            
-            try {
-                for(WebSocketClient client: clients) {
-                    client.open();
-                }
-    
-                //long start = System.nanoTime();
-                for (WebSocketClient client : clients)
-                    threadPool.execute(client);
-    
-                int parallelism = ManagementFactory.getOperatingSystemMXBean().getAvailableProcessors();
-                long maxTimePerIteration = 5;
-                assertTrue(latch.await(iterations * (count / parallelism + 1) * maxTimePerIteration, TimeUnit.MILLISECONDS));
-                //long end = System.nanoTime();
-                // System.err.println("Elapsed: " + TimeUnit.NANOSECONDS.toMillis(end - start) + " ms");
-            } finally {
-                for (WebSocketClient client : clients) {
-                    client.close();
-                }
-            }
-        }
-        finally
-        {
-            threadPool.shutdown();
-            assertTrue(threadPool.awaitTermination(2, TimeUnit.SECONDS));
-        }
-    }
-
-    private static class EchoWebSocket implements WebSocket.OnTextMessage
-    {
-        private volatile Connection outbound;
-
-        public void onOpen(Connection outbound)
-        {
-            this.outbound = outbound;
-        }
-        
-        public void onMessage(String data)
-        {
-            try
-            {
-                // System.err.println(">> "+data);
-                outbound.sendMessage(data);
-            }
-            catch (IOException x)
-            {
-                outbound.disconnect();
-            }
-        }
-
-        public void onClose(int closeCode, String message)
-        {
-        }
-    }
-
-    private class WebSocketClient implements Runnable
-    {
-        private final Socket socket;
-        private final BufferedWriter output;
-        private final BufferedReader input;
-        private final int iterations;
-        private final CountDownLatch latch;
-        private final SocketEndPoint _endp;
-        private final WebSocketGeneratorD08 _generator;
-        private final WebSocketParserD08 _parser;
-        private final WebSocketParser.FrameHandler _handler = new WebSocketParser.FrameHandler()
-        {
-            public void onFrame(byte flags, byte opcode, Buffer buffer)
-            {
-                _response=buffer;
-            }
-
-            public void close(int code,String message)
-            {
-            }
-        };
-        private volatile Buffer _response;
-        
-        public WebSocketClient(String host, int port, int readTimeout, CountDownLatch latch, int iterations) throws IOException
-        {
-            this.latch = latch;
-            socket = new Socket(host, port);
-            socket.setSoTimeout(readTimeout);
-            output = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), "ISO-8859-1"));
-            input = new BufferedReader(new InputStreamReader(socket.getInputStream(), "ISO-8859-1"));
-            this.iterations = iterations;
-            
-            _endp=new SocketEndPoint(socket);
-            _generator = new WebSocketGeneratorD08(new WebSocketBuffers(32*1024),_endp,new FixedMaskGen());
-            _parser = new WebSocketParserD08(new WebSocketBuffers(32*1024),_endp,_handler,false);
-            
-        }
-
-        private void open() throws IOException
-        {
-            output.write("GET /chat HTTP/1.1\r\n"+
-                    "Host: server.example.com\r\n"+
-                    "Upgrade: websocket\r\n"+
-                    "Connection: Upgrade\r\n"+
-                    "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+
-                    "Sec-WebSocket-Origin: http://example.com\r\n"+
-                    "Sec-WebSocket-Protocol: onConnect\r\n" +
-                    "Sec-WebSocket-Version: 8\r\n"+
-                    "\r\n");
-            output.flush();
-
-            String responseLine = input.readLine();
-            assertTrue(responseLine.startsWith("HTTP/1.1 101 Switching Protocols"));
-            // Read until we find an empty line, which signals the end of the http response
-            String line;
-            while ((line = input.readLine()) != null)
-                if (line.length() == 0)
-                    break;
-        }
-
-        public void run()
-        {
-            try
-            {
-                String message = "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF";
-                for (int i = 0; i < iterations; ++i)
-                {
-                    byte[] data = message.getBytes(StringUtil.__UTF8);
-                    _generator.addFrame((byte)0x8,WebSocketConnectionD08.OP_TEXT,data,0,data.length);
-                    _generator.flush();
-                    
-                    //System.err.println("-> "+message);
-                    
-                    _response=null;
-                    while(_response==null) 
-                        _parser.parseNext();
-                    //System.err.println("<- "+_response);
-                    Assert.assertEquals(message,_response.toString());
-                    latch.countDown();
-                }
-            }
-            catch (IOException x)
-            {
-                throw new RuntimeException(x);
-            }
-        }
-
-
-        public void close() throws IOException
-        {
-            socket.close();
-        }
-    }
-}
diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketLoadRFC6455Test.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketLoadRFC6455Test.java
deleted file mode 100644
index 38704ef..0000000
--- a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketLoadRFC6455Test.java
+++ /dev/null
@@ -1,247 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.websocket;
-
-import java.io.BufferedReader;
-import java.io.BufferedWriter;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.OutputStreamWriter;
-import java.lang.management.ManagementFactory;
-import java.net.Socket;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
-import javax.servlet.http.HttpServletRequest;
-
-import junit.framework.Assert;
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.bio.SocketEndPoint;
-import org.eclipse.jetty.server.Connector;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.handler.DefaultHandler;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
-import org.eclipse.jetty.util.StringUtil;
-import org.eclipse.jetty.util.thread.QueuedThreadPool;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.Test;
-
-import static org.junit.Assert.assertTrue;
-
-public class WebSocketLoadRFC6455Test
-{
-    private static Server _server;
-    private static Connector _connector;
-
-    @BeforeClass
-    public static void startServer() throws Exception
-    {
-        _server = new Server();
-
-        _connector = new SelectChannelConnector();
-        _server.addConnector(_connector);
-
-        QueuedThreadPool threadPool = new QueuedThreadPool(200);
-        threadPool.setMaxStopTimeMs(1000);
-        _server.setThreadPool(threadPool);
-
-        WebSocketHandler wsHandler = new WebSocketHandler()
-        {
-            public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol)
-            {
-                return new EchoWebSocket();
-            }
-        };
-        wsHandler.setHandler(new DefaultHandler());
-        _server.setHandler(wsHandler);
-
-        _server.start();
-    }
-
-    @AfterClass
-    public static void stopServer() throws Exception
-    {
-        _server.stop();
-        _server.join();
-    }
-
-    @Test
-    public void testLoad() throws Exception
-    {
-        int count = 50;
-        int iterations = 100;
-
-        ExecutorService threadPool = Executors.newCachedThreadPool();
-        try
-        {
-            CountDownLatch latch = new CountDownLatch(count * iterations);
-            WebSocketClient[] clients = new WebSocketClient[count];
-            for (int i = 0; i < clients.length; ++i)
-            {
-                clients[i] = new WebSocketClient("localhost", _connector.getLocalPort(), 1000, latch, iterations);
-                clients[i].open();
-            }
-
-            //long start = System.nanoTime();
-            for (WebSocketClient client : clients)
-            {
-                threadPool.execute(client);
-            }
-
-            int parallelism = ManagementFactory.getOperatingSystemMXBean().getAvailableProcessors();
-            long maxTimePerIteration = 5;
-            assertTrue(latch.await(iterations * (count / parallelism + 1) * maxTimePerIteration, TimeUnit.MILLISECONDS));
-            //long end = System.nanoTime();
-            // System.err.println("Elapsed: " + TimeUnit.NANOSECONDS.toMillis(end - start) + " ms");
-
-            for (WebSocketClient client : clients)
-            {
-                client.close();
-            }
-        }
-        finally
-        {
-            threadPool.shutdown();
-            assertTrue(threadPool.awaitTermination(2, TimeUnit.SECONDS));
-        }
-    }
-
-    private static class EchoWebSocket implements WebSocket.OnTextMessage
-    {
-        private volatile Connection outbound;
-
-        public void onOpen(Connection outbound)
-        {
-            this.outbound = outbound;
-        }
-
-        public void onMessage(String data)
-        {
-            try
-            {
-                // System.err.println(">> "+data);
-                outbound.sendMessage(data);
-            }
-            catch (IOException x)
-            {
-                outbound.disconnect();
-            }
-        }
-
-        public void onClose(int closeCode, String message)
-        {
-        }
-    }
-
-    private class WebSocketClient implements Runnable
-    {
-        private final Socket socket;
-        private final BufferedWriter output;
-        private final BufferedReader input;
-        private final int iterations;
-        private final CountDownLatch latch;
-        private final SocketEndPoint _endp;
-        private final WebSocketGeneratorRFC6455 _generator;
-        private final WebSocketParserRFC6455 _parser;
-        private final WebSocketParser.FrameHandler _handler = new WebSocketParser.FrameHandler()
-        {
-            public void onFrame(byte flags, byte opcode, Buffer buffer)
-            {
-                _response=buffer;
-            }
-
-            public void close(int code,String message)
-            {
-            }
-        };
-        private volatile Buffer _response;
-
-        public WebSocketClient(String host, int port, int readTimeout, CountDownLatch latch, int iterations) throws IOException
-        {
-            this.latch = latch;
-            socket = new Socket(host, port);
-            socket.setSoTimeout(readTimeout);
-            output = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), "ISO-8859-1"));
-            input = new BufferedReader(new InputStreamReader(socket.getInputStream(), "ISO-8859-1"));
-            this.iterations = iterations;
-
-            _endp=new SocketEndPoint(socket);
-            _generator = new WebSocketGeneratorRFC6455(new WebSocketBuffers(32*1024),_endp,new FixedMaskGen());
-            _parser = new WebSocketParserRFC6455(new WebSocketBuffers(32*1024),_endp,_handler,false);
-
-        }
-
-        private void open() throws IOException
-        {
-            output.write("GET /chat HTTP/1.1\r\n"+
-                    "Host: server.example.com\r\n"+
-                    "Upgrade: websocket\r\n"+
-                    "Connection: Upgrade\r\n"+
-                    "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+
-                    "Sec-WebSocket-Origin: http://example.com\r\n"+
-                    "Sec-WebSocket-Protocol: onConnect\r\n" +
-                    "Sec-WebSocket-Version: 13\r\n"+
-                    "\r\n");
-            output.flush();
-
-            String responseLine = input.readLine();
-            assertTrue(responseLine.startsWith("HTTP/1.1 101 Switching Protocols"));
-            // Read until we find an empty line, which signals the end of the http response
-            String line;
-            while ((line = input.readLine()) != null)
-                if (line.length() == 0)
-                    break;
-        }
-
-        public void run()
-        {
-            try
-            {
-                String message = "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF";
-                for (int i = 0; i < iterations; ++i)
-                {
-                    byte[] data = message.getBytes(StringUtil.__UTF8);
-                    _generator.addFrame((byte)0x8,WebSocketConnectionRFC6455.OP_TEXT,data,0,data.length);
-                    _generator.flush();
-
-                    //System.err.println("-> "+message);
-
-                    _response=null;
-                    while(_response==null)
-                        _parser.parseNext();
-                    //System.err.println("<- "+_response);
-                    Assert.assertEquals(message,_response.toString());
-                    latch.countDown();
-                }
-            }
-            catch (IOException x)
-            {
-                throw new RuntimeException(x);
-            }
-        }
-
-
-        public void close() throws IOException
-        {
-            socket.close();
-        }
-    }
-}
diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketMessageD00Test.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketMessageD00Test.java
deleted file mode 100644
index ae2bdf2..0000000
--- a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketMessageD00Test.java
+++ /dev/null
@@ -1,816 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.websocket;
-
-import static org.junit.Assert.*;
-
-import java.io.BufferedReader;
-import java.io.ByteArrayOutputStream;
-import java.io.EOFException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.net.Socket;
-import java.net.SocketException;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicLong;
-
-import javax.servlet.http.HttpServletRequest;
-
-import junit.framework.Assert;
-
-import org.eclipse.jetty.server.Connector;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.handler.DefaultHandler;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
-import org.eclipse.jetty.util.StringUtil;
-import org.eclipse.jetty.util.TypeUtil;
-import org.eclipse.jetty.util.Utf8StringBuilder;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.StdErrLog;
-import org.junit.AfterClass;
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.Test;
-import org.junit.Ignore;
-
- at Ignore("slow release machine")
-public class WebSocketMessageD00Test
-{
-    private static Server __server;
-    private static Connector __connector;
-    private static TestWebSocket __serverWebSocket;
-    private static CountDownLatch __latch;
-    private static AtomicInteger __textCount = new AtomicInteger(0);
-
-
-    @BeforeClass
-    public static void startServer() throws Exception
-    {
-        __server = new Server();
-        __connector = new SelectChannelConnector();
-        __server.addConnector(__connector);
-        WebSocketHandler wsHandler = new WebSocketHandler()
-        {
-            public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol)
-            {
-                __serverWebSocket = new TestWebSocket();
-                __serverWebSocket._onConnect=("onConnect".equals(protocol)); 
-                __serverWebSocket._echo=("echo".equals(protocol));
-                __serverWebSocket._latch=("latch".equals(protocol));
-                if (__serverWebSocket._latch)
-                    __latch=new CountDownLatch(1);
-                return __serverWebSocket;
-            }
-        };
-        wsHandler.getWebSocketFactory().setMinVersion(-1);
-        wsHandler.setHandler(new DefaultHandler());
-        __server.setHandler(wsHandler);
-        __server.start();
-    }
-
-    @AfterClass
-    public static void stopServer() throws Exception
-    {
-        __server.stop();
-        __server.join();
-    }
-
-    @Before
-    public void reset()
-    {
-        __textCount.set(0);
-    }
-    
-    @Test
-    public void testServerSendBigStringMessage() throws Exception
-    {
-        Socket socket = new Socket("localhost", __connector.getLocalPort());
-        OutputStream output = socket.getOutputStream();
-        output.write(
-                ("GET /test HTTP/1.1\r\n" +
-                "Host: localhost\r\n" +
-                "Upgrade: WebSocket\r\n" +
-                "Connection: Upgrade\r\n" +
-                "Sec-WebSocket-Key1: 4 @1  46546xW%0l 1 5\r\n" +
-                "Sec-WebSocket-Key2: 12998 5 Y3 1  .P00\r\n" +
-                "\r\n"+
-                "^n:ds[4U").getBytes("ISO-8859-1"));
-        output.flush();
-
-        // Make sure the read times out if there are problems with the implementation
-        socket.setSoTimeout(1000);
-
-        InputStream input = socket.getInputStream();
-        BufferedReader reader = new BufferedReader(new InputStreamReader(input, "ISO-8859-1"));
-        String responseLine = reader.readLine();
-        assertTrue(responseLine.startsWith("HTTP/1.1 101 WebSocket Protocol Handshake"));
-        // Read until we find an empty line, which signals the end of the http response
-        String line;
-        while ((line = reader.readLine()) != null)
-            if (line.length() == 0)
-                break;
-        
-        assertTrue(__serverWebSocket.awaitConnected(1000));
-        assertNotNull(__serverWebSocket.outbound);
-        
-        // read the hixie bytes
-        char[] hixie=new char[16]; // should be bytes, but we know this example is all ascii
-        int h=16;
-        int o=0;
-        do 
-        {
-            int l=reader.read(hixie,o,h);
-            if (l<0)
-                break;
-            h-=l;
-            o+=l;
-        }
-        while (h>0);
-        assertEquals("8jKS'y:G*Co,Wxa-",new String(hixie,0,16));
-        
-        // Server sends a big message
-        StringBuilder message = new StringBuilder();
-        String text = "0123456789ABCDEF";
-        for (int i = 0; i < 64 * 1024 / text.length(); ++i)
-            message.append(text);
-        __serverWebSocket.outbound.sendMessage(message.toString());
-
-        // Read until we get 0xFF
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        while (true)
-        {
-            int read = input.read();
-            baos.write(read);
-            if (read == 0xFF)
-                break;
-        }
-        baos.close();
-        byte[] bytes = baos.toByteArray();
-        String result = StringUtil.printable(bytes);
-        assertTrue(result.startsWith("0x00"));
-        assertTrue(result.endsWith("0xFF"));
-        assertEquals(message.length() + "0x00".length() + "0xFF".length(), result.length());
-    }
-
-    @Test
-    public void testServerSendOnConnect() throws Exception
-    {
-        Socket socket = new Socket("localhost", __connector.getLocalPort());
-        OutputStream output = socket.getOutputStream();
-        output.write(
-                ("GET /test HTTP/1.1\r\n" +
-                "Host: localhost\r\n" +
-                "Upgrade: WebSocket\r\n" +
-                "Connection: Upgrade\r\n" +
-                "Sec-WebSocket-Protocol: onConnect\r\n" +
-                "Sec-WebSocket-Key1: 4 @1  46546xW%0l 1 5\r\n" +
-                "Sec-WebSocket-Key2: 12998 5 Y3 1  .P00\r\n" +
-                "\r\n"+
-                "^n:ds[4U").getBytes("ISO-8859-1"));
-        output.flush();
-
-        // Make sure the read times out if there are problems with the implementation
-        socket.setSoTimeout(1000);
-
-        InputStream input = socket.getInputStream();
-        
-        String looking_for="HTTP/1.1 101 WebSocket Protocol Handshake\r\n";
-
-        while(true)
-        {
-            int b = input.read();
-            if (b<0)
-                throw new EOFException();
-
-            assertEquals((int)looking_for.charAt(0),b);
-            if (looking_for.length()==1)
-                break;
-            looking_for=looking_for.substring(1);
-        }
-
-        String skipping_for="\r\n\r\n";
-        int state=0;
-
-        while(true)
-        {
-            int b = input.read();
-            if (b<0)
-                throw new EOFException();
-
-            if (b==skipping_for.charAt(state))
-            {
-                state++;
-                if (state==skipping_for.length())
-                    break;
-            }
-            else
-                state=0;
-        }
-        
-
-        assertTrue(__serverWebSocket.awaitConnected(1000));
-        assertNotNull(__serverWebSocket.outbound);
-        
-        looking_for="8jKS'y:G*Co,Wxa-";
-        while(true)
-        {
-            int b = input.read();
-            if (b<0)
-                throw new EOFException();
-
-            assertEquals((int)looking_for.charAt(0),b);
-            if (looking_for.length()==1)
-                break;
-            looking_for=looking_for.substring(1);
-        }
-        
-        assertEquals(0x00,input.read());
-        looking_for="sent on connect";
-        while(true)
-        {
-            int b = input.read();
-            if (b<0)
-                throw new EOFException();
-
-            assertEquals((int)looking_for.charAt(0),b);
-            if (looking_for.length()==1)
-                break;
-            looking_for=looking_for.substring(1);
-        }
-        assertEquals(0xff,input.read());
-    }
-
-    
-
-    @Test
-    public void testServerEcho() throws Exception
-    {
-        // Log.getLogger("org.eclipse.jetty.websocket").setDebugEnabled(true);
-        
-        Socket socket = new Socket("localhost", __connector.getLocalPort());
-        socket.setSoTimeout(1000); 
-        OutputStream output = socket.getOutputStream();
-        InputStream input = socket.getInputStream();
-        output.write(
-                ("GET /test HTTP/1.1\r\n" +
-                        "Host: localhost\r\n" +
-                        "Upgrade: WebSocket\r\n" +
-                        "Connection: Upgrade\r\n" +
-                        "Sec-WebSocket-Protocol: echo\r\n" +
-                        "Sec-WebSocket-Key1: 4 @1  46546xW%0l 1 5\r\n" +
-                        "Sec-WebSocket-Key2: 12998 5 Y3 1  .P00\r\n" +
-                        "\r\n"+
-                        "^n:ds[4U").getBytes("ISO-8859-1"));
-        output.flush();
-        
-        lookFor("HTTP/1.1 101 WebSocket Protocol Handshake\r\n",input);
-        skipTo("\r\n\r\n",input);
-        lookFor("8jKS'y:G*Co,Wxa-",input);
-        
-        assertTrue(__serverWebSocket.awaitConnected(1000));
-        assertNotNull(__serverWebSocket.connection);
-        
-        
-        output.write(0x00);
-        byte[] bytes="this is an echo".getBytes(StringUtil.__ISO_8859_1);
-        for (int i=0;i<bytes.length;i++)
-            output.write(bytes[i]);
-        output.write(0xff);
-        output.flush();
-        
-        assertEquals("00",TypeUtil.toHexString((byte)(0xff&input.read())));
-        lookFor("this is an echo",input);
-        assertEquals(0xff,input.read());
-    }
-    
-    @Test
-    public void testBlockedConsumer() throws Exception
-    {
-
-        // Log.getLogger("org.eclipse.jetty.websocket").setDebugEnabled(true);
-        
-        Socket socket = new Socket("localhost", __connector.getLocalPort());
-        socket.setSoTimeout(60000); 
-        OutputStream output = socket.getOutputStream();
-        InputStream input = socket.getInputStream();
-        output.write(
-                ("GET /test HTTP/1.1\r\n" +
-                        "Host: localhost\r\n" +
-                        "Upgrade: WebSocket\r\n" +
-                        "Connection: Upgrade\r\n" +
-                        "Sec-WebSocket-Protocol: latch\r\n" +
-                        "Sec-WebSocket-Key1: 4 @1  46546xW%0l 1 5\r\n" +
-                        "Sec-WebSocket-Key2: 12998 5 Y3 1  .P00\r\n" +
-                        "\r\n"+
-                        "^n:ds[4U").getBytes("ISO-8859-1"));
-        output.flush();
-        
-        lookFor("HTTP/1.1 101 WebSocket Protocol Handshake\r\n",input);
-        skipTo("\r\n\r\n",input);
-        lookFor("8jKS'y:G*Co,Wxa-",input);
-        
-        assertTrue(__serverWebSocket.awaitConnected(1000));
-        assertNotNull(__serverWebSocket.connection);
-        __serverWebSocket.connection.setMaxIdleTime(60000);
-        
-        
-        byte[] bytes="This is a long message of text that we will send again and again".getBytes(StringUtil.__ISO_8859_1);
-        byte[] mesg=new byte[bytes.length+2];
-        mesg[0]=(byte)0x00;
-        for (int i=0;i<bytes.length;i++)
-            mesg[i+1]=(byte)(bytes[i]);
-        mesg[mesg.length-1]=(byte)0xFF;
-        
-        final int count = 100000;
-
-
-        // Send and receive 1 message
-        output.write(mesg);
-        output.flush();
-        while(__textCount.get()==0)
-            Thread.sleep(10);
-
-        // unblock the latch in 4s
-        new Thread()
-        {
-            @Override
-            public void run()
-            {
-                try
-                {
-                    Thread.sleep(4000);
-                    __latch.countDown();
-                    //System.err.println("latched");
-                }
-                catch(Exception e)
-                {
-                    e.printStackTrace();
-                }
-            }
-        }.start();
-        
-        // Send enough messages to fill receive buffer
-        long max=0;
-        long start=System.currentTimeMillis();
-        for (int i=0;i<count;i++)
-        {
-            output.write(mesg);
-            if (i%100==0)
-            {
-                // System.err.println(">>> "+i);
-                output.flush();
-                
-                long now=System.currentTimeMillis();
-                long duration=now-start;
-                start=now;
-                if (max<duration)
-                    max=duration;
-            }
-        }
-
-        Thread.sleep(50);
-        while(__textCount.get()<count+1)
-        {
-            System.err.println(__textCount.get()+"<"+(count+1));
-            Thread.sleep(10);
-        }
-        assertEquals(count+1,__textCount.get()); // all messages
-        assertTrue(max>2000); // was blocked
-    }
-    
-    @Test
-    public void testBlockedProducer() throws Exception
-    {
-        // Log.getLogger("org.eclipse.jetty.websocket").setDebugEnabled(true);
-        
-        final Socket socket = new Socket("localhost", __connector.getLocalPort());
-        socket.setSoTimeout(60000); 
-        OutputStream output = socket.getOutputStream();
-        InputStream input = socket.getInputStream();
-        output.write(
-                ("GET /test HTTP/1.1\r\n" +
-                        "Host: localhost\r\n" +
-                        "Upgrade: WebSocket\r\n" +
-                        "Connection: Upgrade\r\n" +
-                        "Sec-WebSocket-Protocol: latch\r\n" +
-                        "Sec-WebSocket-Key1: 4 @1  46546xW%0l 1 5\r\n" +
-                        "Sec-WebSocket-Key2: 12998 5 Y3 1  .P00\r\n" +
-                        "\r\n"+
-                        "^n:ds[4U").getBytes("ISO-8859-1"));
-        output.flush();
-        
-        lookFor("HTTP/1.1 101 WebSocket Protocol Handshake\r\n",input);
-        skipTo("\r\n\r\n",input);
-        lookFor("8jKS'y:G*Co,Wxa-",input);
-        
-        assertTrue(__serverWebSocket.awaitConnected(1000));
-        assertNotNull(__serverWebSocket.connection);
-        
-        
-        final int count = 100000;
-
-        __serverWebSocket.connection.setMaxIdleTime(60000);
-        __latch.countDown();
-
-        // wait 2s and then consume messages
-        final AtomicLong totalB=new AtomicLong();
-        new Thread()
-        {
-            @Override
-            public void run()
-            {
-                try
-                {
-                    Thread.sleep(2000);
-
-                    byte[] recv = new byte[32*1024];
-
-                    int len=0;
-                    while (len>=0)
-                    {
-                        totalB.addAndGet(len);
-                        len=socket.getInputStream().read(recv,0,recv.length);
-                        Thread.sleep(10);
-                    }
-                }
-                catch(Exception e)
-                {
-                    e.printStackTrace();
-                }
-            }
-        }.start();
-        
-        
-        // Send enough messages to fill receive buffer
-        long max=0;
-        long start=System.currentTimeMillis();
-        String mesg="How Now Brown Cow";
-        for (int i=0;i<count;i++)
-        {
-            __serverWebSocket.connection.sendMessage(mesg);
-            if (i%100==0)
-            {
-                output.flush();
-                
-                long now=System.currentTimeMillis();
-                long duration=now-start;
-                start=now;
-                if (max<duration)
-                    max=duration;
-            }
-        }
-        
-        while(totalB.get()<(count*(mesg.length()+2)))
-            Thread.sleep(100);
-        
-        assertEquals(count*(mesg.length()+2),totalB.get()); // all messages
-        assertTrue(max>1000); // was blocked
-    }
-
-
-
-    @Test
-    public void testIdle() throws Exception
-    {
-        // Log.getLogger("org.eclipse.jetty.websocket").setDebugEnabled(true);
-        
-        final Socket socket = new Socket("localhost", __connector.getLocalPort());
-        socket.setSoTimeout(10000); 
-        OutputStream output = socket.getOutputStream();
-        InputStream input = socket.getInputStream();
-        output.write(
-                ("GET /test HTTP/1.1\r\n" +
-                        "Host: localhost\r\n" +
-                        "Upgrade: WebSocket\r\n" +
-                        "Connection: Upgrade\r\n" +
-                        "Sec-WebSocket-Protocol: onConnect\r\n" +
-                        "Sec-WebSocket-Key1: 4 @1  46546xW%0l 1 5\r\n" +
-                        "Sec-WebSocket-Key2: 12998 5 Y3 1  .P00\r\n" +
-                        "\r\n"+
-                        "^n:ds[4U").getBytes("ISO-8859-1"));
-        output.flush();
-        
-        lookFor("HTTP/1.1 101 WebSocket Protocol Handshake\r\n",input);
-        skipTo("\r\n\r\n",input);
-        lookFor("8jKS'y:G*Co,Wxa-",input);
-        
-        assertTrue(__serverWebSocket.awaitConnected(1000));
-        assertNotNull(__serverWebSocket.connection);
-        __serverWebSocket.connection.setMaxIdleTime(250);
-        assertEquals(0x00,input.read());
-        lookFor("sent on connect",input);
-        assertEquals(0xff,input.read());
-
-        assertEquals(-1,input.read());
-        socket.close();
-        
-        assertTrue(__serverWebSocket.awaitDisconnected(100));
-    }
-    
-    @Test
-    public void testIdleBadClient() throws Exception
-    {
-        // Log.getLogger("org.eclipse.jetty.websocket").setDebugEnabled(true);
-        
-        final Socket socket = new Socket("localhost", __connector.getLocalPort());
-        socket.setSoTimeout(10000); 
-        OutputStream output = socket.getOutputStream();
-        InputStream input = socket.getInputStream();
-        output.write(
-                ("GET /test HTTP/1.1\r\n" +
-                        "Host: localhost\r\n" +
-                        "Upgrade: WebSocket\r\n" +
-                        "Connection: Upgrade\r\n" +
-                        "Sec-WebSocket-Protocol: onConnect\r\n" +
-                        "Sec-WebSocket-Key1: 4 @1  46546xW%0l 1 5\r\n" +
-                        "Sec-WebSocket-Key2: 12998 5 Y3 1  .P00\r\n" +
-                        "\r\n"+
-                        "^n:ds[4U").getBytes("ISO-8859-1"));
-        output.flush();
-        
-        lookFor("HTTP/1.1 101 WebSocket Protocol Handshake\r\n",input);
-        skipTo("\r\n\r\n",input);
-        lookFor("8jKS'y:G*Co,Wxa-",input);
-        
-        assertTrue(__serverWebSocket.awaitConnected(1000));
-        assertNotNull(__serverWebSocket.connection);
-        __serverWebSocket.connection.setMaxIdleTime(250);
-        assertEquals(0x00,input.read());
-        lookFor("sent on connect",input);
-        assertEquals(0xff,input.read());
-
-        assertEquals(-1,input.read());
-        
-        assertTrue(__serverWebSocket.disconnected.getCount()>0);
-        assertTrue(__serverWebSocket.awaitDisconnected(1000));
-    }
-
-    @Test
-    public void testTCPClose() throws Exception
-    {
-        // Log.getLogger("org.eclipse.jetty.websocket").setDebugEnabled(true);
-        
-        final Socket socket = new Socket("localhost", __connector.getLocalPort());
-        socket.setSoTimeout(10000); 
-        OutputStream output = socket.getOutputStream();
-        InputStream input = socket.getInputStream();
-        output.write(
-                ("GET /test HTTP/1.1\r\n" +
-                        "Host: localhost\r\n" +
-                        "Upgrade: WebSocket\r\n" +
-                        "Connection: Upgrade\r\n" +
-                        "Sec-WebSocket-Protocol: onConnect\r\n" +
-                        "Sec-WebSocket-Key1: 4 @1  46546xW%0l 1 5\r\n" +
-                        "Sec-WebSocket-Key2: 12998 5 Y3 1  .P00\r\n" +
-                        "\r\n"+
-                        "^n:ds[4U").getBytes("ISO-8859-1"));
-        output.flush();
-        
-        lookFor("HTTP/1.1 101 WebSocket Protocol Handshake\r\n",input);
-        skipTo("\r\n\r\n",input);
-        lookFor("8jKS'y:G*Co,Wxa-",input);
-        
-        assertTrue(__serverWebSocket.awaitConnected(1000));
-        assertNotNull(__serverWebSocket.connection);
-        __serverWebSocket.connection.setMaxIdleTime(250);
-        assertEquals(0x00,input.read());
-        lookFor("sent on connect",input);
-        assertEquals(0xff,input.read());
-        
-        
-        
-        socket.close();
-        
-        assertTrue(__serverWebSocket.awaitDisconnected(500));
-
-        try
-        {
-            __serverWebSocket.connection.sendMessage("Don't send");
-            assertTrue(false);
-        }
-        catch(IOException e)
-        {
-            assertTrue(true);
-        }    
-    }
-
-    @Test
-    public void testTCPHalfClose() throws Exception
-    {
-        // Log.getLogger("org.eclipse.jetty.websocket").setDebugEnabled(true);
-        
-        final Socket socket = new Socket("localhost", __connector.getLocalPort());
-        socket.setSoTimeout(10000); 
-        OutputStream output = socket.getOutputStream();
-        InputStream input = socket.getInputStream();
-        output.write(
-                ("GET /test HTTP/1.1\r\n" +
-                        "Host: localhost\r\n" +
-                        "Upgrade: WebSocket\r\n" +
-                        "Connection: Upgrade\r\n" +
-                        "Sec-WebSocket-Protocol: onConnect\r\n" +
-                        "Sec-WebSocket-Key1: 4 @1  46546xW%0l 1 5\r\n" +
-                        "Sec-WebSocket-Key2: 12998 5 Y3 1  .P00\r\n" +
-                        "\r\n"+
-                        "^n:ds[4U").getBytes("ISO-8859-1"));
-        output.flush();
-        
-        lookFor("HTTP/1.1 101 WebSocket Protocol Handshake\r\n",input);
-        skipTo("\r\n\r\n",input);
-        lookFor("8jKS'y:G*Co,Wxa-",input);
-        
-        assertTrue(__serverWebSocket.awaitConnected(1000));
-        assertNotNull(__serverWebSocket.connection);
-        __serverWebSocket.connection.setMaxIdleTime(250);
-        assertEquals(0x00,input.read());
-        lookFor("sent on connect",input);
-        assertEquals(0xff,input.read());
-        
-        
-        socket.shutdownOutput();
-        
-        assertTrue(__serverWebSocket.awaitDisconnected(500));
-
-        assertEquals(-1,input.read());
-        
-        // look for broken pipe
-        try
-        {
-            for (int i=0;i<1000;i++)
-                output.write(0);
-            Assert.fail();
-        }
-        catch(SocketException e)
-        {
-            // expected
-        }
-    }
-    
-    
-    
-    
-    
-    
-    
-    
-    
-    
-    
-    
-
-    private void lookFor(String string,InputStream in)
-        throws IOException
-    {
-        String orig=string;
-        Utf8StringBuilder scanned=new Utf8StringBuilder();
-        try
-        {
-            while(true)
-            {
-                int b = in.read();
-                if (b<0)
-                    throw new EOFException();
-                scanned.append((byte)b);
-                assertEquals("looking for\""+orig+"\" in '"+scanned+"'",(int)string.charAt(0),b);
-                if (string.length()==1)
-                    break;
-                string=string.substring(1);
-            }
-        }
-        catch(IOException e)
-        {
-            System.err.println("IOE while looking for \""+orig+"\" in '"+scanned+"'");
-            throw e;
-        }
-    }
-
-    private void skipTo(String string,InputStream in)
-    throws IOException
-    {
-        int state=0;
-
-        while(true)
-        {
-            int b = in.read();
-            if (b<0)
-                throw new EOFException();
-
-            if (b==string.charAt(state))
-            {
-                state++;
-                if (state==string.length())
-                    break;
-            }
-            else
-                state=0;
-        }
-    }
-    
-    
-
-    private static class TestWebSocket implements WebSocket.OnFrame, WebSocket, WebSocket.OnTextMessage
-    {
-        protected boolean _latch;
-        boolean _echo=true;
-        boolean _onConnect=false;
-        private volatile Connection outbound;
-        private final CountDownLatch connected = new CountDownLatch(1);
-        private final CountDownLatch disconnected = new CountDownLatch(1);
-        private volatile FrameConnection connection;
-
-        public FrameConnection getConnection()
-        {
-            return connection;
-        }
-        
-        public void onHandshake(FrameConnection connection)
-        {
-            this.connection = connection;
-        }
-        
-        public void onOpen(Connection outbound)
-        {
-            this.outbound = outbound;
-            if (_onConnect)
-            {
-                try
-                {
-                    outbound.sendMessage("sent on connect");
-                }
-                catch(IOException e)
-                {
-                    e.printStackTrace();
-                }
-            }
-            connected.countDown();
-        }
-
-        private boolean awaitConnected(long time) throws InterruptedException
-        {
-            return connected.await(time, TimeUnit.MILLISECONDS);
-        }
-
-        private boolean awaitDisconnected(long time) throws InterruptedException
-        {
-            return disconnected.await(time, TimeUnit.MILLISECONDS);
-        }
-        
-        public void onClose(int code,String message)
-        {
-            disconnected.countDown();
-        }
-
-        public boolean onFrame(byte flags, byte opcode, byte[] data, int offset, int length)
-        {
-            return true;
-        }
-
-        public void onMessage(String data)
-        {
-            __textCount.incrementAndGet();
-            if (_latch)
-            {
-                try
-                {
-                    __latch.await();
-                }
-                catch(Exception e)
-                {
-                    e.printStackTrace();
-                }
-            }
-            
-            if (_echo)
-            {
-                try
-                {
-                    outbound.sendMessage(data); 
-                }
-                catch (IOException e)
-                {
-                    e.printStackTrace();
-                }
-            }
-        }
-    }
-}
diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketMessageD06Test.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketMessageD06Test.java
deleted file mode 100644
index 9000b24..0000000
--- a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketMessageD06Test.java
+++ /dev/null
@@ -1,862 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.websocket;
-
-import static org.junit.Assert.*;
-
-import java.io.EOFException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.Socket;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicReference;
-
-import javax.servlet.http.HttpServletRequest;
-
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.ByteArrayEndPoint;
-import org.eclipse.jetty.server.Connector;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.handler.DefaultHandler;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
-import org.eclipse.jetty.util.StringUtil;
-import org.eclipse.jetty.util.Utf8StringBuilder;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.Test;
-import org.junit.Ignore;
-
- at Ignore("slow release machine")
-public class WebSocketMessageD06Test
-{
-    private static Server _server;
-    private static Connector _connector;
-    private static TestWebSocket _serverWebSocket;
-
-    @BeforeClass
-    public static void startServer() throws Exception
-    {
-        _server = new Server();
-        _connector = new SelectChannelConnector();
-        _server.addConnector(_connector);
-        WebSocketHandler wsHandler = new WebSocketHandler()
-        {
-            public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol)
-            {
-                _serverWebSocket = new TestWebSocket();
-                _serverWebSocket.onConnect=("onConnect".equals(protocol));
-                _serverWebSocket.echo=("echo".equals(protocol));
-                _serverWebSocket.aggregate=("aggregate".equals(protocol));
-                return _serverWebSocket;
-            }
-        };
-        wsHandler.getWebSocketFactory().setMinVersion(6);
-        wsHandler.getWebSocketFactory().setBufferSize(8192);
-        wsHandler.getWebSocketFactory().setMaxIdleTime(1000);
-        wsHandler.setHandler(new DefaultHandler());
-        _server.setHandler(wsHandler);
-        _server.start();
-    }
-
-    @AfterClass
-    public static void stopServer() throws Exception
-    {
-        _server.stop();
-        _server.join();
-    }
-
-    
-    @Test
-    public void testHash()
-    {
-        assertEquals("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",WebSocketConnectionD06.hashKey("dGhlIHNhbXBsZSBub25jZQ=="));
-    }
-    
-    @Test
-    public void testServerSendBigStringMessage() throws Exception
-    {
-        Socket socket = new Socket("localhost", _connector.getLocalPort());
-        OutputStream output = socket.getOutputStream();
-        output.write(
-                ("GET /chat HTTP/1.1\r\n"+
-                 "Host: server.example.com\r\n"+
-                 "Upgrade: websocket\r\n"+
-                 "Connection: Upgrade\r\n"+
-                 "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+
-                 "Sec-WebSocket-Origin: http://example.com\r\n"+
-                 "Sec-WebSocket-Protocol: chat, superchat\r\n"+
-                 "Sec-WebSocket-Version: 6\r\n"+
-                 "\r\n").getBytes("ISO-8859-1"));
-        output.flush();
-
-        // Make sure the read times out if there are problems with the implementation
-        socket.setSoTimeout(1000);
-
-        InputStream input = socket.getInputStream();
-        
-        lookFor("HTTP/1.1 101 Switching Protocols\r\n",input);
-        skipTo("Sec-WebSocket-Accept: ",input);
-        lookFor("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",input);
-        skipTo("\r\n\r\n",input);
-
-        assertTrue(_serverWebSocket.awaitConnected(1000));
-        assertNotNull(_serverWebSocket.connection);
-        
-        // Server sends a big message
-        StringBuilder message = new StringBuilder();
-        String text = "0123456789ABCDEF";
-        for (int i = 0; i < (0x2000) / text.length(); i++)
-            message.append(text);
-        String data=message.toString();
-        _serverWebSocket.connection.sendMessage(data);
-
-        assertEquals(WebSocketConnectionD06.OP_TEXT,input.read());
-        assertEquals(0x7e,input.read());
-        assertEquals(0x1f,input.read());
-        assertEquals(0xf6,input.read());
-        lookFor(data.substring(0,0x1ff6),input);
-        assertEquals(0x80,input.read());
-        assertEquals(0x0A,input.read());
-        lookFor(data.substring(0x1ff6),input);
-    }
-
-    @Test
-    public void testServerSendOnConnect() throws Exception
-    {
-        Socket socket = new Socket("localhost", _connector.getLocalPort());
-        OutputStream output = socket.getOutputStream();
-        output.write(
-                ("GET /chat HTTP/1.1\r\n"+
-                 "Host: server.example.com\r\n"+
-                 "Upgrade: websocket\r\n"+
-                 "Connection: Upgrade\r\n"+
-                 "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+
-                 "Sec-WebSocket-Origin: http://example.com\r\n"+
-                 "Sec-WebSocket-Protocol: onConnect\r\n" +
-                 "Sec-WebSocket-Version: 6\r\n"+
-                 "\r\n").getBytes("ISO-8859-1"));
-        output.flush();
-
-        // Make sure the read times out if there are problems with the implementation
-        socket.setSoTimeout(1000);
-
-        InputStream input = socket.getInputStream();
-        
-        lookFor("HTTP/1.1 101 Switching Protocols\r\n",input);
-        skipTo("Sec-WebSocket-Accept: ",input);
-        lookFor("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",input);
-        skipTo("\r\n\r\n",input);
-
-        assertTrue(_serverWebSocket.awaitConnected(1000));
-        assertNotNull(_serverWebSocket.connection);
-        
-        assertEquals(0x84,input.read());
-        assertEquals(0x0f,input.read());
-        lookFor("sent on connect",input);
-    }
-
-    @Test
-    public void testServerEcho() throws Exception
-    {
-        Socket socket = new Socket("localhost", _connector.getLocalPort());
-        OutputStream output = socket.getOutputStream();
-        output.write(
-                ("GET /chat HTTP/1.1\r\n"+
-                 "Host: server.example.com\r\n"+
-                 "Upgrade: websocket\r\n"+
-                 "Connection: Upgrade\r\n"+
-                 "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+
-                 "Sec-WebSocket-Origin: http://example.com\r\n"+
-                 "Sec-WebSocket-Protocol: echo\r\n" +
-                 "Sec-WebSocket-Version: 6\r\n"+
-                 "\r\n").getBytes("ISO-8859-1"));
-        output.flush();
-        output.write(0xff);
-        output.write(0xff);
-        output.write(0xff);
-        output.write(0xff);
-        output.write(0x84^0xff);
-        output.write(0x0f^0xff);
-        byte[] bytes="this is an echo".getBytes(StringUtil.__ISO_8859_1);
-        for (int i=0;i<bytes.length;i++)
-            output.write(bytes[i]^0xff);
-        output.flush();
-        // Make sure the read times out if there are problems with the implementation
-        socket.setSoTimeout(1000);
-
-        InputStream input = socket.getInputStream();
-        
-        lookFor("HTTP/1.1 101 Switching Protocols\r\n",input);
-        skipTo("Sec-WebSocket-Accept: ",input);
-        lookFor("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",input);
-        skipTo("\r\n\r\n",input);
-
-        assertTrue(_serverWebSocket.awaitConnected(1000));
-        assertNotNull(_serverWebSocket.connection);
-        
-        assertEquals(0x84,input.read());
-        assertEquals(0x0f,input.read());
-        lookFor("this is an echo",input);
-    }
-
-    @Test
-    public void testServerPingPong() throws Exception
-    {
-        Socket socket = new Socket("localhost", _connector.getLocalPort());
-        // Make sure the read times out if there are problems with the implementation
-        OutputStream output = socket.getOutputStream();
-        output.write(
-                ("GET /chat HTTP/1.1\r\n"+
-                 "Host: server.example.com\r\n"+
-                 "Upgrade: websocket\r\n"+
-                 "Connection: Upgrade\r\n"+
-                 "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+
-                 "Sec-WebSocket-Origin: http://example.com\r\n"+
-                 "Sec-WebSocket-Protocol: echo\r\n" +
-                 "Sec-WebSocket-Version: 6\r\n"+
-                 "\r\n").getBytes("ISO-8859-1"));
-        output.flush();
-        output.write(0xff);
-        output.write(0xff);
-        output.write(0xff);
-        output.write(0xff);
-        output.write(0x82^0xff);
-        output.write(0x00^0xff);
-        output.flush();
-
-        InputStream input = socket.getInputStream();
-        
-        lookFor("HTTP/1.1 101 Switching Protocols\r\n",input);
-        skipTo("Sec-WebSocket-Accept: ",input);
-        lookFor("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",input);
-        skipTo("\r\n\r\n",input);
-
-        assertTrue(_serverWebSocket.awaitConnected(1000));
-        assertNotNull(_serverWebSocket.connection);
-
-        socket.setSoTimeout(1000);
-        assertEquals(0x83,input.read());
-        assertEquals(0x00,input.read());
-    }
-    
-    @Test
-    public void testMaxTextSize() throws Exception
-    {
-        Socket socket = new Socket("localhost", _connector.getLocalPort());
-        OutputStream output = socket.getOutputStream();
-        output.write(
-                ("GET /chat HTTP/1.1\r\n"+
-                 "Host: server.example.com\r\n"+
-                 "Upgrade: websocket\r\n"+
-                 "Connection: Upgrade\r\n"+
-                 "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+
-                 "Sec-WebSocket-Origin: http://example.com\r\n"+
-                 "Sec-WebSocket-Protocol: other\r\n" +
-                 "Sec-WebSocket-Version: 6\r\n"+
-                 "\r\n").getBytes("ISO-8859-1"));
-        output.flush();
-
-        socket.setSoTimeout(1000);
-        InputStream input = socket.getInputStream();
-        
-        lookFor("HTTP/1.1 101 Switching Protocols\r\n",input);
-        skipTo("Sec-WebSocket-Accept: ",input);
-        lookFor("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",input);
-        skipTo("\r\n\r\n",input);
-
-        assertTrue(_serverWebSocket.awaitConnected(1000));
-        assertNotNull(_serverWebSocket.connection);
-        
-        _serverWebSocket.getConnection().setMaxTextMessageSize(15);
-        
-        output.write(0xff);
-        output.write(0xff);
-        output.write(0xff);
-        output.write(0xff);
-        output.write(0x04^0xff);
-        output.write(0x0a^0xff);
-        byte[] bytes="0123456789".getBytes(StringUtil.__ISO_8859_1);
-        for (int i=0;i<bytes.length;i++)
-            output.write(bytes[i]^0xff);
-        output.flush();
-
-        output.write(0xff);
-        output.write(0xff);
-        output.write(0xff);
-        output.write(0xff);
-        output.write(0x80^0xff);
-        output.write(0x0a^0xff);
-        for (int i=0;i<bytes.length;i++)
-            output.write(bytes[i]^0xff);
-        output.flush();
-
-        assertEquals(0x80|WebSocketConnectionD06.OP_CLOSE,input.read());
-        assertEquals(30,input.read());
-        int code=(0xff&input.read())*0x100+(0xff&input.read());
-        assertEquals(1004,code);
-        lookFor("Text message size > 15 chars",input);
-    }
-
-
-    @Test
-    public void testMaxTextSize2() throws Exception
-    {
-        Socket socket = new Socket("localhost", _connector.getLocalPort());
-        OutputStream output = socket.getOutputStream();
-        output.write(
-                ("GET /chat HTTP/1.1\r\n"+
-                 "Host: server.example.com\r\n"+
-                 "Upgrade: websocket\r\n"+
-                 "Connection: Upgrade\r\n"+
-                 "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+
-                 "Sec-WebSocket-Origin: http://example.com\r\n"+
-                 "Sec-WebSocket-Protocol: other\r\n" +
-                 "Sec-WebSocket-Version: 6\r\n"+
-                 "\r\n").getBytes("ISO-8859-1"));
-        output.flush();
-
-        socket.setSoTimeout(100000);
-        InputStream input = socket.getInputStream();
-        
-        lookFor("HTTP/1.1 101 Switching Protocols\r\n",input);
-        skipTo("Sec-WebSocket-Accept: ",input);
-        lookFor("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",input);
-        skipTo("\r\n\r\n",input);
-
-        assertTrue(_serverWebSocket.awaitConnected(1000));
-        assertNotNull(_serverWebSocket.connection);
-        
-        _serverWebSocket.getConnection().setMaxTextMessageSize(15);
-        
-        output.write(0xff);
-        output.write(0xff);
-        output.write(0xff);
-        output.write(0xff);
-        output.write(0x04^0xff);
-        output.write(0x14^0xff);
-        byte[] bytes="01234567890123456789".getBytes(StringUtil.__ISO_8859_1);
-        for (int i=0;i<bytes.length;i++)
-            output.write(bytes[i]^0xff);
-        output.flush();
-        
-        assertEquals(0x80|WebSocketConnectionD06.OP_CLOSE,input.read());
-        assertEquals(30,input.read());
-        int code=(0xff&input.read())*0x100+(0xff&input.read());
-        assertEquals(1004,code);
-        lookFor("Text message size > 15 chars",input);
-    }
-
-    @Test
-    public void testBinaryAggregate() throws Exception
-    {
-        Socket socket = new Socket("localhost", _connector.getLocalPort());
-        OutputStream output = socket.getOutputStream();
-        output.write(
-                ("GET /chat HTTP/1.1\r\n"+
-                 "Host: server.example.com\r\n"+
-                 "Upgrade: websocket\r\n"+
-                 "Connection: Upgrade\r\n"+
-                 "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+
-                 "Sec-WebSocket-Origin: http://example.com\r\n"+
-                 "Sec-WebSocket-Protocol: aggregate\r\n" +
-                 "Sec-WebSocket-Version: 6\r\n"+
-                 "\r\n").getBytes("ISO-8859-1"));
-        output.flush();
-
-        socket.setSoTimeout(1000);
-        InputStream input = socket.getInputStream();
-        
-        lookFor("HTTP/1.1 101 Switching Protocols\r\n",input);
-        skipTo("Sec-WebSocket-Accept: ",input);
-        lookFor("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",input);
-        skipTo("\r\n\r\n",input);
-
-        assertTrue(_serverWebSocket.awaitConnected(1000));
-        assertNotNull(_serverWebSocket.connection);
-        _serverWebSocket.getConnection().setMaxBinaryMessageSize(1024);
-        
-        output.write(0xff);
-        output.write(0xff);
-        output.write(0xff);
-        output.write(0xff);
-        output.write(WebSocketConnectionD06.OP_BINARY^0xff);
-        output.write(0x0a^0xff);
-        byte[] bytes="0123456789".getBytes(StringUtil.__ISO_8859_1);
-        for (int i=0;i<bytes.length;i++)
-            output.write(bytes[i]^0xff);
-        output.flush();
-
-        output.write(0xff);
-        output.write(0xff);
-        output.write(0xff);
-        output.write(0xff);
-        output.write(0x80^0xff);
-        output.write(0x0a^0xff);
-        for (int i=0;i<bytes.length;i++)
-            output.write(bytes[i]^0xff);
-        output.flush();
-        
-        assertEquals(0x85,input.read());
-        assertEquals(20,input.read());
-        lookFor("01234567890123456789",input);
-    }
-    
-    @Test
-    public void testMaxBinarySize() throws Exception
-    {
-        Socket socket = new Socket("localhost", _connector.getLocalPort());
-        OutputStream output = socket.getOutputStream();
-        output.write(
-                ("GET /chat HTTP/1.1\r\n"+
-                 "Host: server.example.com\r\n"+
-                 "Upgrade: websocket\r\n"+
-                 "Connection: Upgrade\r\n"+
-                 "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+
-                 "Sec-WebSocket-Origin: http://example.com\r\n"+
-                 "Sec-WebSocket-Protocol: other\r\n" +
-                 "Sec-WebSocket-Version: 6\r\n"+
-                 "\r\n").getBytes("ISO-8859-1"));
-        output.flush();
-
-        socket.setSoTimeout(100000);
-        InputStream input = socket.getInputStream();
-        
-        lookFor("HTTP/1.1 101 Switching Protocols\r\n",input);
-        skipTo("Sec-WebSocket-Accept: ",input);
-        lookFor("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",input);
-        skipTo("\r\n\r\n",input);
-
-        assertTrue(_serverWebSocket.awaitConnected(1000));
-        assertNotNull(_serverWebSocket.connection);
-        
-        _serverWebSocket.getConnection().setMaxBinaryMessageSize(15);
-        
-        output.write(0xff);
-        output.write(0xff);
-        output.write(0xff);
-        output.write(0xff);
-        output.write(0x0f^0xff);
-        output.write(0x0a^0xff);
-        byte[] bytes="0123456789".getBytes(StringUtil.__ISO_8859_1);
-        for (int i=0;i<bytes.length;i++)
-            output.write(bytes[i]^0xff);
-        output.flush();
-
-        output.write(0xff);
-        output.write(0xff);
-        output.write(0xff);
-        output.write(0xff);
-        output.write(0x80^0xff);
-        output.write(0x0a^0xff);
-        for (int i=0;i<bytes.length;i++)
-            output.write(bytes[i]^0xff);
-        output.flush();
-
-        
-        assertEquals(0x80|WebSocketConnectionD06.OP_CLOSE,input.read());
-        assertEquals(19,input.read());
-        int code=(0xff&input.read())*0x100+(0xff&input.read());
-        assertEquals(1004,code);
-        lookFor("Message size > 15",input);
-    }
-
-
-    @Test
-    public void testMaxBinarySize2() throws Exception
-    {
-        Socket socket = new Socket("localhost", _connector.getLocalPort());
-        OutputStream output = socket.getOutputStream();
-        output.write(
-                ("GET /chat HTTP/1.1\r\n"+
-                 "Host: server.example.com\r\n"+
-                 "Upgrade: websocket\r\n"+
-                 "Connection: Upgrade\r\n"+
-                 "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+
-                 "Sec-WebSocket-Origin: http://example.com\r\n"+
-                 "Sec-WebSocket-Protocol: other\r\n" +
-                 "Sec-WebSocket-Version: 6\r\n"+
-                 "\r\n").getBytes("ISO-8859-1"));
-        output.flush();
-
-        socket.setSoTimeout(100000);
-        InputStream input = socket.getInputStream();
-        
-        lookFor("HTTP/1.1 101 Switching Protocols\r\n",input);
-        skipTo("Sec-WebSocket-Accept: ",input);
-        lookFor("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",input);
-        skipTo("\r\n\r\n",input);
-
-        assertTrue(_serverWebSocket.awaitConnected(1000));
-        assertNotNull(_serverWebSocket.connection);
-        
-        _serverWebSocket.getConnection().setMaxBinaryMessageSize(15);
-        
-        output.write(0xff);
-        output.write(0xff);
-        output.write(0xff);
-        output.write(0xff);
-        output.write(0x0f^0xff);
-        output.write(0x14^0xff);
-        byte[] bytes="01234567890123456789".getBytes(StringUtil.__ISO_8859_1);
-        for (int i=0;i<bytes.length;i++)
-            output.write(bytes[i]^0xff);
-        output.flush();
-        
-        assertEquals(0x80|WebSocketConnectionD06.OP_CLOSE,input.read());
-        assertEquals(19,input.read());
-        int code=(0xff&input.read())*0x100+(0xff&input.read());
-        assertEquals(1004,code);
-        lookFor("Message size > 15",input);
-    }
-
-    @Test
-    public void testIdle() throws Exception
-    {
-        Socket socket = new Socket("localhost", _connector.getLocalPort());
-        OutputStream output = socket.getOutputStream();
-        output.write(
-                ("GET /chat HTTP/1.1\r\n"+
-                 "Host: server.example.com\r\n"+
-                 "Upgrade: websocket\r\n"+
-                 "Connection: Upgrade\r\n"+
-                 "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+
-                 "Sec-WebSocket-Origin: http://example.com\r\n"+
-                 "Sec-WebSocket-Protocol: onConnect\r\n" +
-                 "Sec-WebSocket-Version: 6\r\n"+
-                 "\r\n").getBytes("ISO-8859-1"));
-        output.flush();
-
-        // Make sure the read times out if there are problems with the implementation
-        socket.setSoTimeout(10000);
-
-        InputStream input = socket.getInputStream();
-
-        lookFor("HTTP/1.1 101 Switching Protocols\r\n",input);
-        skipTo("Sec-WebSocket-Accept: ",input);
-        lookFor("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",input);
-        skipTo("\r\n\r\n",input);
-
-        assertTrue(_serverWebSocket.awaitConnected(1000));
-        assertNotNull(_serverWebSocket.connection);
-        
-        assertEquals(0x84,input.read());
-        assertEquals(0x0f,input.read());
-        lookFor("sent on connect",input);
-
-        assertEquals((byte)0x81,(byte)input.read());
-        assertEquals(0x06,input.read());
-        assertEquals(1000/0x100,input.read());
-        assertEquals(1000%0x100,input.read());
-        lookFor("Idle",input);
-
-        // respond to close
-        output.write(0xff);
-        output.write(0xff);
-        output.write(0xff);
-        output.write(0xff);
-        output.write(0x81^0xff);
-        output.write(0x00^0xff);
-        output.flush();
-        
-        
-        assertTrue(_serverWebSocket.awaitDisconnected(5000));
-        try
-        {
-            _serverWebSocket.connection.sendMessage("Don't send");
-            assertTrue(false);
-        }
-        catch(IOException e)
-        {
-            assertTrue(true);
-        }
-    }
-
-    @Test
-    public void testClose() throws Exception
-    {
-        Socket socket = new Socket("localhost", _connector.getLocalPort());
-        OutputStream output = socket.getOutputStream();
-        output.write(
-                ("GET /chat HTTP/1.1\r\n"+
-                        "Host: server.example.com\r\n"+
-                        "Upgrade: websocket\r\n"+
-                        "Connection: Upgrade\r\n"+
-                        "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+
-                        "Sec-WebSocket-Origin: http://example.com\r\n"+
-                        "Sec-WebSocket-Protocol: onConnect\r\n" +
-                        "Sec-WebSocket-Version: 6\r\n"+
-                "\r\n").getBytes("ISO-8859-1"));
-        output.flush();
-
-        // Make sure the read times out if there are problems with the implementation
-        socket.setSoTimeout(1000);
-
-        InputStream input = socket.getInputStream();
-
-        lookFor("HTTP/1.1 101 Switching Protocols\r\n",input);
-        skipTo("Sec-WebSocket-Accept: ",input);
-        lookFor("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",input);
-        skipTo("\r\n\r\n",input);
-
-
-        assertTrue(_serverWebSocket.awaitConnected(1000));
-        assertNotNull(_serverWebSocket.connection);
-        
-        assertEquals(0x84,input.read());
-        assertEquals(0x0f,input.read());
-        lookFor("sent on connect",input);
-        socket.close();
-        
-        assertTrue(_serverWebSocket.awaitDisconnected(500));
-        
-
-        try
-        {
-            _serverWebSocket.connection.sendMessage("Don't send");
-            assertTrue(false);
-        }
-        catch(IOException e)
-        {
-            assertTrue(true);
-        }    
-    }
-    
-    @Test
-    public void testParserAndGenerator() throws Exception
-    {
-        String message = "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF";
-        final AtomicReference<String> received = new AtomicReference<String>();
-        ByteArrayEndPoint endp = new ByteArrayEndPoint(new byte[0],4096);
-        
-        WebSocketGeneratorD06 gen = new WebSocketGeneratorD06(new WebSocketBuffers(8096),endp,null);
-        
-        byte[] data = message.getBytes(StringUtil.__UTF8);
-        gen.addFrame((byte)0x8,(byte)0x4,data,0,data.length);
-        
-        endp = new ByteArrayEndPoint(endp.getOut().asArray(),4096);
-                
-        WebSocketParserD06 parser = new WebSocketParserD06(new WebSocketBuffers(8096),endp,new WebSocketParser.FrameHandler()
-        {
-            public void onFrame(byte flags, byte opcode, Buffer buffer)
-            {
-                received.set(buffer.toString());
-            }
-
-            public void close(int code,String message)
-            {
-            }
-
-        },false);
-        
-        parser.parseNext();
-        
-        assertEquals(message,received.get());
-    }
-    
-    @Test
-    public void testParserAndGeneratorMasked() throws Exception
-    {
-        String message = "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF";
-        final AtomicReference<String> received = new AtomicReference<String>();
-        ByteArrayEndPoint endp = new ByteArrayEndPoint(new byte[0],4096);
-
-        MaskGen maskGen = new RandomMaskGen();
-        
-        WebSocketGeneratorD06 gen = new WebSocketGeneratorD06(new WebSocketBuffers(8096),endp,maskGen);
-        byte[] data = message.getBytes(StringUtil.__UTF8);
-        gen.addFrame((byte)0x8,(byte)0x4,data,0,data.length);
-        
-        endp = new ByteArrayEndPoint(endp.getOut().asArray(),4096);
-                
-        WebSocketParserD06 parser = new WebSocketParserD06(new WebSocketBuffers(8096),endp,new WebSocketParser.FrameHandler()
-        {
-            public void onFrame(byte flags, byte opcode, Buffer buffer)
-            {
-                received.set(buffer.toString());
-            }
-
-            public void close(int code,String message)
-            {
-            }
-        },true);
-        
-        parser.parseNext();
-        
-        assertEquals(message,received.get());
-    }
-    
-    
-    private void lookFor(String string,InputStream in)
-        throws IOException
-    {
-        String orig=string;
-        Utf8StringBuilder scanned=new Utf8StringBuilder();
-        try
-        {
-            while(true)
-            {
-                int b = in.read();
-                if (b<0)
-                    throw new EOFException();
-                scanned.append((byte)b);
-                assertEquals("looking for\""+orig+"\" in '"+scanned+"'",(int)string.charAt(0),b);
-                if (string.length()==1)
-                    break;
-                string=string.substring(1);
-            }
-        }
-        catch(IOException e)
-        {
-            System.err.println("IOE while looking for \""+orig+"\" in '"+scanned+"'");
-            throw e;
-        }
-    }
-
-    private void skipTo(String string,InputStream in)
-    throws IOException
-    {
-        int state=0;
-
-        while(true)
-        {
-            int b = in.read();
-            if (b<0)
-                throw new EOFException();
-
-            if (b==string.charAt(state))
-            {
-                state++;
-                if (state==string.length())
-                    break;
-            }
-            else
-                state=0;
-        }
-    }
-    
-
-    private static class TestWebSocket implements WebSocket.OnFrame, WebSocket.OnBinaryMessage, WebSocket.OnTextMessage
-    {
-        boolean onConnect=false;
-        boolean echo=true;
-        boolean aggregate=false;
-        private final CountDownLatch connected = new CountDownLatch(1);
-        private final CountDownLatch disconnected = new CountDownLatch(1);
-        private volatile FrameConnection connection;
-
-        public Connection getConnection()
-        {
-            return connection;
-        }
-
-        public void onHandshake(FrameConnection connection)
-        {
-            this.connection = connection;
-        }
-        
-        public void onOpen(Connection connection)
-        {
-            if (onConnect)
-            {
-                try
-                {
-                    connection.sendMessage("sent on connect");
-                }
-                catch(IOException e)
-                {
-                    e.printStackTrace();
-                }
-            }
-            connected.countDown();
-        }
-
-        private boolean awaitConnected(long time) throws InterruptedException
-        {
-            return connected.await(time, TimeUnit.MILLISECONDS);
-        }
-
-        private boolean awaitDisconnected(long time) throws InterruptedException
-        {
-            return disconnected.await(time, TimeUnit.MILLISECONDS);
-        }
-
-        public void onClose(int code,String message)
-        {
-            disconnected.countDown();
-        }
-
-        public boolean onFrame(byte flags, byte opcode, byte[] data, int offset, int length)
-        {
-            if (echo)
-            {
-                switch(opcode)
-                {
-                    case WebSocketConnectionD06.OP_CLOSE:
-                    case WebSocketConnectionD06.OP_PING:
-                    case WebSocketConnectionD06.OP_PONG:
-                        break;
-                        
-                    default:
-                        try
-                        {
-                            connection.sendFrame(flags,opcode,data,offset,length);
-                        }
-                        catch (IOException e)
-                        {
-                            e.printStackTrace();
-                        }
-                }
-            }
-            return false;
-        }
-
-        public void onMessage(byte[] data, int offset, int length)
-        {
-            if (aggregate)
-            {
-                try
-                {
-                    connection.sendMessage(data,offset,length);
-                }
-                catch (IOException e)
-                {
-                    e.printStackTrace();
-                }
-            }
-        }
-
-        public void onMessage(String data)
-        {
-            if (aggregate)
-            {
-                try
-                {
-                    connection.sendMessage(data);
-                }
-                catch (IOException e)
-                {
-                    e.printStackTrace();
-                }
-            }
-        }
-
-    }
-}
diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketMessageD08Test.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketMessageD08Test.java
deleted file mode 100644
index 2b9dab1..0000000
--- a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketMessageD08Test.java
+++ /dev/null
@@ -1,1319 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.websocket;
-
-import static org.junit.Assert.*;
-
-import java.io.EOFException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.Socket;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicLong;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.zip.Deflater;
-import java.util.zip.Inflater;
-
-import javax.servlet.http.HttpServletRequest;
-
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.ByteArrayEndPoint;
-import org.eclipse.jetty.server.Connector;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.handler.DefaultHandler;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
-import org.eclipse.jetty.util.StringUtil;
-import org.eclipse.jetty.util.TypeUtil;
-import org.eclipse.jetty.util.Utf8StringBuilder;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.Test;
-import org.junit.Ignore;
-
- at Ignore("slow release machine")
-public class WebSocketMessageD08Test
-{
-    private static Server __server;
-    private static Connector __connector;
-    private static TestWebSocket __serverWebSocket;
-    private static CountDownLatch __latch;
-    private static AtomicInteger __textCount = new AtomicInteger(0);
-
-    @BeforeClass
-    public static void startServer() throws Exception
-    {
-        __server = new Server();
-        __connector = new SelectChannelConnector();
-        __server.addConnector(__connector);
-        WebSocketHandler wsHandler = new WebSocketHandler()
-        {
-            public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol)
-            {
-                __textCount.set(0);
-                __serverWebSocket = new TestWebSocket();
-                __serverWebSocket._onConnect=("onConnect".equals(protocol));
-                __serverWebSocket._echo=("echo".equals(protocol));
-                __serverWebSocket._aggregate=("aggregate".equals(protocol));
-                __serverWebSocket._latch=("latch".equals(protocol));
-                if (__serverWebSocket._latch)
-                    __latch=new CountDownLatch(1);
-                return __serverWebSocket;
-            }
-        };
-        wsHandler.getWebSocketFactory().setMinVersion(8);
-        wsHandler.getWebSocketFactory().setBufferSize(8192);
-        wsHandler.getWebSocketFactory().setMaxIdleTime(1000);
-        wsHandler.setHandler(new DefaultHandler());
-        __server.setHandler(wsHandler);
-        __server.start();
-    }
-
-    @AfterClass
-    public static void stopServer() throws Exception
-    {
-        __server.stop();
-        __server.join();
-    }
-
-    
-    @Test
-    public void testHash()
-    {
-        assertEquals("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",WebSocketConnectionD08.hashKey("dGhlIHNhbXBsZSBub25jZQ=="));
-    }
-    
-    @Test
-    public void testServerSendBigStringMessage() throws Exception
-    {
-        Socket socket = new Socket("localhost", __connector.getLocalPort());
-        OutputStream output = socket.getOutputStream();
-        output.write(
-                ("GET /chat HTTP/1.1\r\n"+
-                 "Host: server.example.com\r\n"+
-                 "Upgrade: websocket\r\n"+
-                 "Connection: Upgrade\r\n"+
-                 "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+
-                 "Sec-WebSocket-Origin: http://example.com\r\n"+
-                 "Sec-WebSocket-Protocol: chat, superchat\r\n"+
-                 "Sec-WebSocket-Version: 8\r\n"+
-                 "\r\n").getBytes("ISO-8859-1"));
-        output.flush();
-
-        // Make sure the read times out if there are problems with the implementation
-        socket.setSoTimeout(1000);
-
-        InputStream input = socket.getInputStream();
-        
-        lookFor("HTTP/1.1 101 Switching Protocols\r\n",input);
-        skipTo("Sec-WebSocket-Accept: ",input);
-        lookFor("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",input);
-        skipTo("\r\n\r\n",input);
-
-        assertTrue(__serverWebSocket.awaitConnected(1000));
-        assertNotNull(__serverWebSocket.connection);
-        
-        // Server sends a big message
-        StringBuilder message = new StringBuilder();
-        String text = "0123456789ABCDEF";
-        for (int i = 0; i < (0x2000) / text.length(); i++)
-            message.append(text);
-        String data=message.toString();
-        __serverWebSocket.connection.sendMessage(data);
-
-        assertEquals(WebSocketConnectionD08.OP_TEXT,input.read());
-        assertEquals(0x7e,input.read());
-        assertEquals(0x1f,input.read());
-        assertEquals(0xf6,input.read());
-        lookFor(data.substring(0,0x1ff6),input);
-        assertEquals(0x80,input.read());
-        assertEquals(0x0A,input.read());
-        lookFor(data.substring(0x1ff6),input);
-    }
-
-    @Test
-    public void testServerSendOnConnect() throws Exception
-    {
-        Socket socket = new Socket("localhost", __connector.getLocalPort());
-        OutputStream output = socket.getOutputStream();
-        output.write(
-                ("GET /chat HTTP/1.1\r\n"+
-                 "Host: server.example.com\r\n"+
-                 "Upgrade: websocket\r\n"+
-                 "Connection: Upgrade\r\n"+
-                 "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+
-                 "Sec-WebSocket-Origin: http://example.com\r\n"+
-                 "Sec-WebSocket-Protocol: onConnect\r\n" +
-                 "Sec-WebSocket-Version: 8\r\n"+
-                 "\r\n").getBytes("ISO-8859-1"));
-        output.flush();
-
-        // Make sure the read times out if there are problems with the implementation
-        socket.setSoTimeout(1000);
-
-        InputStream input = socket.getInputStream();
-        
-        lookFor("HTTP/1.1 101 Switching Protocols\r\n",input);
-        skipTo("Sec-WebSocket-Accept: ",input);
-        lookFor("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",input);
-        skipTo("\r\n\r\n",input);
-
-        assertTrue(__serverWebSocket.awaitConnected(1000));
-        assertNotNull(__serverWebSocket.connection);
-        
-        assertEquals(0x81,input.read());
-        assertEquals(0x0f,input.read());
-        lookFor("sent on connect",input);
-    }
-
-    @Test
-    public void testIdentityExtension() throws Exception
-    {
-        Socket socket = new Socket("localhost", __connector.getLocalPort());
-        OutputStream output = socket.getOutputStream();
-        output.write(
-                ("GET /chat HTTP/1.1\r\n"+
-                 "Host: server.example.com\r\n"+
-                 "Upgrade: websocket\r\n"+
-                 "Connection: Upgrade\r\n"+
-                 "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+
-                 "Sec-WebSocket-Origin: http://example.com\r\n"+
-                 "Sec-WebSocket-Protocol: onConnect\r\n" +
-                 "Sec-WebSocket-Version: 8\r\n"+
-                 "Sec-WebSocket-Extensions: identity;param=0\r\n"+
-                 "Sec-WebSocket-Extensions: identity;param=1, identity ; param = '2' ; other = ' some = value ' \r\n"+
-                 "\r\n").getBytes("ISO-8859-1"));
-        output.flush();
-
-        // Make sure the read times out if there are problems with the implementation
-        socket.setSoTimeout(1000);
-
-        InputStream input = socket.getInputStream();
-        lookFor("HTTP/1.1 101 Switching Protocols\r\n",input);
-        skipTo("Sec-WebSocket-Accept: ",input);
-        lookFor("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",input);
-        skipTo("Sec-WebSocket-Extensions: ",input);
-        lookFor("identity;param=0",input);
-        skipTo("Sec-WebSocket-Extensions: ",input);
-        lookFor("identity;param=1",input);
-        skipTo("Sec-WebSocket-Extensions: ",input);
-        lookFor("identity;",input);
-        skipTo("\r\n\r\n",input);
-
-        assertTrue(__serverWebSocket.awaitConnected(1000));
-        assertNotNull(__serverWebSocket.connection);
-
-        assertEquals(0x81,input.read());
-        assertEquals(0x0f,input.read());
-        lookFor("sent on connect",input);
-    }
-
-
-    @Test
-    public void testFragmentExtension() throws Exception
-    {
-        Socket socket = new Socket("localhost", __connector.getLocalPort());
-        OutputStream output = socket.getOutputStream();
-        output.write(
-                ("GET /chat HTTP/1.1\r\n"+
-                 "Host: server.example.com\r\n"+
-                 "Upgrade: websocket\r\n"+
-                 "Connection: Upgrade\r\n"+
-                 "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+
-                 "Sec-WebSocket-Origin: http://example.com\r\n"+
-                 "Sec-WebSocket-Protocol: onConnect\r\n" +
-                 "Sec-WebSocket-Version: 8\r\n"+
-                 "Sec-WebSocket-Extensions: fragment;maxLength=4;minFragments=7\r\n"+
-                 "\r\n").getBytes("ISO-8859-1"));
-        output.flush();
-
-        // Make sure the read times out if there are problems with the implementation
-        socket.setSoTimeout(1000);
-
-        InputStream input = socket.getInputStream();
-        lookFor("HTTP/1.1 101 Switching Protocols\r\n",input);
-        skipTo("Sec-WebSocket-Accept: ",input);
-        lookFor("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",input);
-        skipTo("Sec-WebSocket-Extensions: ",input);
-        lookFor("fragment;",input);
-        skipTo("\r\n\r\n",input);
-
-        assertTrue(__serverWebSocket.awaitConnected(1000));
-        assertNotNull(__serverWebSocket.connection);
-
-        assertEquals(0x01,input.read());
-        assertEquals(0x04,input.read());
-        lookFor("sent",input);
-        assertEquals(0x00,input.read());
-        assertEquals(0x04,input.read());
-        lookFor(" on ",input);
-        assertEquals(0x00,input.read());
-        assertEquals(0x04,input.read());
-        lookFor("conn",input);
-        assertEquals(0x00,input.read());
-        assertEquals(0x01,input.read());
-        lookFor("e",input);
-        assertEquals(0x00,input.read());
-        assertEquals(0x01,input.read());
-        lookFor("c",input);
-        assertEquals(0x00,input.read());
-        assertEquals(0x00,input.read());
-        assertEquals(0x80,input.read());
-        assertEquals(0x01,input.read());
-        lookFor("t",input);
-    }
-
-    @Test
-    public void testDeflateFrameExtension() throws Exception
-    {
-        Socket socket = new Socket("localhost", __connector.getLocalPort());
-        OutputStream output = socket.getOutputStream();
-        output.write(
-                ("GET /chat HTTP/1.1\r\n"+
-                 "Host: server.example.com\r\n"+
-                 "Upgrade: websocket\r\n"+
-                 "Connection: Upgrade\r\n"+
-                 "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+
-                 "Sec-WebSocket-Origin: http://example.com\r\n"+
-                 "Sec-WebSocket-Protocol: echo\r\n" +
-                 "Sec-WebSocket-Version: 8\r\n"+
-                 "Sec-WebSocket-Extensions: x-deflate-frame;minLength=64\r\n"+
-                 "Sec-WebSocket-Extensions: fragment;minFragments=2\r\n"+
-                 "\r\n").getBytes("ISO-8859-1"));
-        output.flush();
-
-        // Make sure the read times out if there are problems with the implementation
-        socket.setSoTimeout(1000);
-
-        InputStream input = socket.getInputStream();
-        lookFor("HTTP/1.1 101 Switching Protocols\r\n",input);
-        skipTo("Sec-WebSocket-Accept: ",input);
-        lookFor("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",input);
-        skipTo("Sec-WebSocket-Extensions: ",input);
-        lookFor("x-deflate-frame;minLength=64",input);
-        skipTo("Sec-WebSocket-Extensions: ",input);
-        lookFor("fragment;",input);
-        skipTo("\r\n\r\n",input);
-
-        assertTrue(__serverWebSocket.awaitConnected(1000));
-        assertNotNull(__serverWebSocket.connection);
-        
-        
-        // Server sends a big message
-        String text = "0123456789ABCDEF ";
-        text=text+text+text+text;
-        text=text+text+text+text;
-        text=text+text+text+text+'X';
-        byte[] data=text.getBytes("utf-8");
-        Deflater deflater = new Deflater();
-        deflater.setInput(data);
-        deflater.finish();
-        byte[] buf=new byte[data.length];
-        
-        buf[0]=(byte)((byte)0x7e);
-        buf[1]=(byte)(data.length>>8);
-        buf[2]=(byte)(data.length&0xff);
-        
-        int l=deflater.deflate(buf,3,buf.length-3);
-
-        assertTrue(deflater.finished());
-        
-        output.write(0xC1);
-        output.write((byte)(0x80|(0xff&(l+3))));
-        output.write(0x00);
-        output.write(0x00);
-        output.write(0x00);
-        output.write(0x00);
-        output.write(buf,0,l+3);
-        output.flush();
-        
-        assertEquals(0x40+WebSocketConnectionD08.OP_TEXT,input.read());
-        assertEquals(0x20+3,input.read());
-        assertEquals(0x7e,input.read());
-        assertEquals(0x02,input.read());
-        assertEquals(0x20,input.read());
-        
-        byte[] raw = new byte[32];
-        assertEquals(32,input.read(raw));
-        
-        Inflater inflater = new Inflater();
-        inflater.setInput(raw);
-        
-        byte[] result = new byte[544];
-        assertEquals(544,inflater.inflate(result));
-        assertEquals(TypeUtil.toHexString(data,0,544),TypeUtil.toHexString(result));
-        
-
-        assertEquals((byte)0xC0,(byte)input.read());
-        assertEquals(0x21+3,input.read());
-        assertEquals(0x7e,input.read());
-        assertEquals(0x02,input.read());
-        assertEquals(0x21,input.read());
-        
-        assertEquals(32,input.read(raw));
-        
-        inflater.reset();
-        inflater.setInput(raw);
-        result = new byte[545];
-        assertEquals(545,inflater.inflate(result));
-        assertEquals(TypeUtil.toHexString(data,544,545),TypeUtil.toHexString(result));
-        
-
-    }
-    
-    @Test
-    public void testServerEcho() throws Exception
-    {
-        Socket socket = new Socket("localhost", __connector.getLocalPort());
-        OutputStream output = socket.getOutputStream();
-        output.write(
-                ("GET /chat HTTP/1.1\r\n"+
-                 "Host: server.example.com\r\n"+
-                 "Upgrade: websocket\r\n"+
-                 "Connection: Upgrade\r\n"+
-                 "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+
-                 "Sec-WebSocket-Origin: http://example.com\r\n"+
-                 "Sec-WebSocket-Protocol: echo\r\n" +
-                 "Sec-WebSocket-Version: 8\r\n"+
-                 "\r\n").getBytes("ISO-8859-1"));
-        output.flush();
-        output.write(0x84);
-        output.write(0x8f);
-        output.write(0xff);
-        output.write(0xff);
-        output.write(0xff);
-        output.write(0xff);
-        byte[] bytes="this is an echo".getBytes(StringUtil.__ISO_8859_1);
-        for (int i=0;i<bytes.length;i++)
-            output.write(bytes[i]^0xff);
-        output.flush();
-        // Make sure the read times out if there are problems with the implementation
-        socket.setSoTimeout(1000);
-
-        InputStream input = socket.getInputStream();
-        
-        lookFor("HTTP/1.1 101 Switching Protocols\r\n",input);
-        skipTo("Sec-WebSocket-Accept: ",input);
-        lookFor("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",input);
-        skipTo("\r\n\r\n",input);
-
-        assertTrue(__serverWebSocket.awaitConnected(1000));
-        assertNotNull(__serverWebSocket.connection);
-        
-        assertEquals(0x84,input.read());
-        assertEquals(0x0f,input.read());
-        lookFor("this is an echo",input);
-    }
-    
-    @Test
-    public void testBlockedConsumer() throws Exception
-    {
-        Socket socket = new Socket("localhost", __connector.getLocalPort());
-        OutputStream output = socket.getOutputStream();
-
-        byte[] bytes="This is a long message of text that we will send again and again".getBytes(StringUtil.__ISO_8859_1);
-        byte[] mesg=new byte[bytes.length+6];
-        mesg[0]=(byte)(0x80+WebSocketConnectionD08.OP_TEXT);
-        mesg[1]=(byte)(0x80+bytes.length);
-        mesg[2]=(byte)0xff;
-        mesg[3]=(byte)0xff;
-        mesg[4]=(byte)0xff;
-        mesg[5]=(byte)0xff;
-        for (int i=0;i<bytes.length;i++)
-            mesg[6+i]=(byte)(bytes[i]^0xff);
-        
-        final int count = 100000;
-
-        output.write(
-                ("GET /chat HTTP/1.1\r\n"+
-                        "Host: server.example.com\r\n"+
-                        "Upgrade: websocket\r\n"+
-                        "Connection: Upgrade\r\n"+
-                        "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+
-                        "Sec-WebSocket-Origin: http://example.com\r\n"+
-                        "Sec-WebSocket-Protocol: latch\r\n" +
-                        "Sec-WebSocket-Version: 8\r\n"+
-                "\r\n").getBytes("ISO-8859-1"));
-        output.flush();
-
-        // Make sure the read times out if there are problems with the implementation
-        socket.setSoTimeout(60000);
-
-        InputStream input = socket.getInputStream();
-
-        lookFor("HTTP/1.1 101 Switching Protocols\r\n",input);
-        skipTo("Sec-WebSocket-Accept: ",input);
-        lookFor("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",input);
-        skipTo("\r\n\r\n",input);
-
-        assertTrue(__serverWebSocket.awaitConnected(1000));
-        assertNotNull(__serverWebSocket.connection);
-        __serverWebSocket.connection.setMaxIdleTime(60000);
-
-        // Send and receive 1 message
-        output.write(mesg);
-        output.flush();
-        while(__textCount.get()==0)
-            Thread.sleep(10);
-
-        // unblock the latch in 4s
-        new Thread()
-        {
-            @Override
-            public void run()
-            {
-                try
-                {
-                    Thread.sleep(4000);
-                    __latch.countDown();
-                    //System.err.println("latched");
-                }
-                catch(Exception e)
-                {
-                    e.printStackTrace();
-                }
-            }
-        }.start();
-        
-        // Send enough messages to fill receive buffer
-        long max=0;
-        long start=System.currentTimeMillis();
-        for (int i=0;i<count;i++)
-        {
-            output.write(mesg);
-            if (i%100==0)
-            {
-                // System.err.println(">>> "+i);
-                output.flush();
-                
-                long now=System.currentTimeMillis();
-                long duration=now-start;
-                start=now;
-                if (max<duration)
-                    max=duration;
-            }
-        }
-
-        Thread.sleep(50);
-        while(__textCount.get()<count+1)
-        {
-            System.err.println(__textCount.get()+"<"+(count+1));
-            Thread.sleep(10);
-        }
-        assertEquals(count+1,__textCount.get()); // all messages
-        assertTrue(max>2000); // was blocked
-    }
-    
-    @Test
-    public void testBlockedProducer() throws Exception
-    {
-        final Socket socket = new Socket("localhost", __connector.getLocalPort());
-        OutputStream output = socket.getOutputStream();
-        
-        final int count = 100000;
-
-        output.write(
-                ("GET /chat HTTP/1.1\r\n"+
-                        "Host: server.example.com\r\n"+
-                        "Upgrade: websocket\r\n"+
-                        "Connection: Upgrade\r\n"+
-                        "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+
-                        "Sec-WebSocket-Origin: http://example.com\r\n"+
-                        "Sec-WebSocket-Protocol: latch\r\n" +
-                        "Sec-WebSocket-Version: 8\r\n"+
-                "\r\n").getBytes("ISO-8859-1"));
-        output.flush();
-
-        // Make sure the read times out if there are problems with the implementation
-        socket.setSoTimeout(60000);
-
-        InputStream input = socket.getInputStream();
-
-        lookFor("HTTP/1.1 101 Switching Protocols\r\n",input);
-        skipTo("Sec-WebSocket-Accept: ",input);
-        lookFor("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",input);
-        skipTo("\r\n\r\n",input);
-
-        assertTrue(__serverWebSocket.awaitConnected(1000));
-        assertNotNull(__serverWebSocket.connection);
-        __serverWebSocket.connection.setMaxIdleTime(60000);
-        __latch.countDown();
-
-        // wait 2s and then consume messages
-        final AtomicLong totalB=new AtomicLong();
-        new Thread()
-        {
-            @Override
-            public void run()
-            {
-                try
-                {
-                    Thread.sleep(2000);
-
-                    byte[] recv = new byte[32*1024];
-
-                    int len=0;
-                    while (len>=0)
-                    {
-                        totalB.addAndGet(len);
-                        len=socket.getInputStream().read(recv,0,recv.length);
-                        Thread.sleep(10);
-                    }
-                }
-                catch(Exception e)
-                {
-                    e.printStackTrace();
-                }
-            }
-        }.start();
-        
-        
-        // Send enough messages to fill receive buffer
-        long start=System.currentTimeMillis();
-        String mesg="How Now Brown Cow";
-        for (int i=0;i<count;i++)
-        {
-            __serverWebSocket.connection.sendMessage(mesg);
-            if (i%100==0)
-                output.flush();
-        }
-        long duration=System.currentTimeMillis()-start;
-        
-        while(totalB.get()<(count*(mesg.length()+2)))
-            Thread.sleep(100);
-        
-        assertEquals(count*(mesg.length()+2),totalB.get()); // all messages
-        // if (duration<1500)
-            System.err.println("max="+duration);
-        assertTrue(duration>1500); // was blocked
-    }
-
-    @Test
-    public void testServerPingPong() throws Exception
-    {
-        Socket socket = new Socket("localhost", __connector.getLocalPort());
-        // Make sure the read times out if there are problems with the implementation
-        OutputStream output = socket.getOutputStream();
-        output.write(
-                ("GET /chat HTTP/1.1\r\n"+
-                 "Host: server.example.com\r\n"+
-                 "Upgrade: websocket\r\n"+
-                 "Connection: Upgrade\r\n"+
-                 "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+
-                 "Sec-WebSocket-Origin: http://example.com\r\n"+
-                 "Sec-WebSocket-Protocol: echo\r\n" +
-                 "Sec-WebSocket-Version: 8\r\n"+
-                 "\r\n").getBytes("ISO-8859-1"));
-        output.flush();
-        output.write(0x89);
-        output.write(0x80);
-        output.write(0xff);
-        output.write(0xff);
-        output.write(0xff);
-        output.write(0xff);
-        output.flush();
-
-        InputStream input = socket.getInputStream();
-        
-        lookFor("HTTP/1.1 101 Switching Protocols\r\n",input);
-        skipTo("Sec-WebSocket-Accept: ",input);
-        lookFor("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",input);
-        skipTo("\r\n\r\n",input);
-
-        assertTrue(__serverWebSocket.awaitConnected(1000));
-        assertNotNull(__serverWebSocket.connection);
-
-        socket.setSoTimeout(1000);
-        assertEquals(0x8A,input.read());
-        assertEquals(0x00,input.read());
-    }
-
-    @Test
-    public void testMaxTextSizeFalseFrag() throws Exception
-    {
-        Socket socket = new Socket("localhost", __connector.getLocalPort());
-        OutputStream output = socket.getOutputStream();
-        output.write(
-                ("GET /chat HTTP/1.1\r\n"+
-                 "Host: server.example.com\r\n"+
-                 "Upgrade: websocket\r\n"+
-                 "Connection: Upgrade\r\n"+
-                 "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+
-                 "Sec-WebSocket-Origin: http://example.com\r\n"+
-                 "Sec-WebSocket-Protocol: other\r\n" +
-                 "Sec-WebSocket-Version: 8\r\n"+
-                 "\r\n").getBytes("ISO-8859-1"));
-        output.flush();
-
-        socket.setSoTimeout(1000);
-        InputStream input = socket.getInputStream();
-        
-        lookFor("HTTP/1.1 101 Switching Protocols\r\n",input);
-        skipTo("Sec-WebSocket-Accept: ",input);
-        lookFor("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",input);
-        skipTo("\r\n\r\n",input);
-
-        assertTrue(__serverWebSocket.awaitConnected(1000));
-        assertNotNull(__serverWebSocket.connection);
-        
-        __serverWebSocket.getConnection().setMaxTextMessageSize(10*1024);
-        __serverWebSocket.getConnection().setAllowFrameFragmentation(true);
-        
-        output.write(0x81);
-        output.write(0x80|0x7E);
-        output.write((byte)((16*1024)>>8));
-        output.write((byte)((16*1024)&0xff));
-        output.write(0x00);
-        output.write(0x00);
-        output.write(0x00);
-        output.write(0x00);
-
-        for (int i=0;i<(16*1024);i++)
-            output.write('X');
-        output.flush();
-        
-
-        assertEquals(0x80|WebSocketConnectionD08.OP_CLOSE,input.read());
-        assertEquals(33,input.read());
-        int code=(0xff&input.read())*0x100+(0xff&input.read());
-        assertEquals(WebSocketConnectionD08.CLOSE_BADDATA,code);
-        lookFor("Text message size > 10240 chars",input);
-    }
-
-    @Test
-    public void testMaxTextSize() throws Exception
-    {
-        Socket socket = new Socket("localhost", __connector.getLocalPort());
-        OutputStream output = socket.getOutputStream();
-        output.write(
-                ("GET /chat HTTP/1.1\r\n"+
-                 "Host: server.example.com\r\n"+
-                 "Upgrade: websocket\r\n"+
-                 "Connection: Upgrade\r\n"+
-                 "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+
-                 "Sec-WebSocket-Origin: http://example.com\r\n"+
-                 "Sec-WebSocket-Protocol: other\r\n" +
-                 "Sec-WebSocket-Version: 8\r\n"+
-                 "\r\n").getBytes("ISO-8859-1"));
-        output.flush();
-
-        socket.setSoTimeout(1000);
-        InputStream input = socket.getInputStream();
-        
-        lookFor("HTTP/1.1 101 Switching Protocols\r\n",input);
-        skipTo("Sec-WebSocket-Accept: ",input);
-        lookFor("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",input);
-        skipTo("\r\n\r\n",input);
-
-        assertTrue(__serverWebSocket.awaitConnected(1000));
-        assertNotNull(__serverWebSocket.connection);
-        
-        __serverWebSocket.getConnection().setMaxTextMessageSize(15);
-        
-        output.write(0x01);
-        output.write(0x8a);
-        output.write(0xff);
-        output.write(0xff);
-        output.write(0xff);
-        output.write(0xff);
-        byte[] bytes="0123456789".getBytes(StringUtil.__ISO_8859_1);
-        for (int i=0;i<bytes.length;i++)
-            output.write(bytes[i]^0xff);
-        output.flush();
-
-        output.write(0x80);
-        output.write(0x8a);
-        output.write(0xff);
-        output.write(0xff);
-        output.write(0xff);
-        output.write(0xff);
-        for (int i=0;i<bytes.length;i++)
-            output.write(bytes[i]^0xff);
-        output.flush();
-
-        assertEquals(0x80|WebSocketConnectionD08.OP_CLOSE,input.read());
-        assertEquals(30,input.read());
-        int code=(0xff&input.read())*0x100+(0xff&input.read());
-        assertEquals(WebSocketConnectionD08.CLOSE_BADDATA,code);
-        lookFor("Text message size > 15 chars",input);
-    }
-
-
-    @Test
-    public void testMaxTextSize2() throws Exception
-    {
-        Socket socket = new Socket("localhost", __connector.getLocalPort());
-        OutputStream output = socket.getOutputStream();
-        output.write(
-                ("GET /chat HTTP/1.1\r\n"+
-                 "Host: server.example.com\r\n"+
-                 "Upgrade: websocket\r\n"+
-                 "Connection: Upgrade\r\n"+
-                 "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+
-                 "Sec-WebSocket-Origin: http://example.com\r\n"+
-                 "Sec-WebSocket-Protocol: other\r\n" +
-                 "Sec-WebSocket-Version: 8\r\n"+
-                 "\r\n").getBytes("ISO-8859-1"));
-        output.flush();
-
-        socket.setSoTimeout(100000);
-        InputStream input = socket.getInputStream();
-        
-        lookFor("HTTP/1.1 101 Switching Protocols\r\n",input);
-        skipTo("Sec-WebSocket-Accept: ",input);
-        lookFor("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",input);
-        skipTo("\r\n\r\n",input);
-
-        assertTrue(__serverWebSocket.awaitConnected(1000));
-        assertNotNull(__serverWebSocket.connection);
-        
-        __serverWebSocket.getConnection().setMaxTextMessageSize(15);
-        
-        output.write(0x01);
-        output.write(0x94);
-        output.write(0xff);
-        output.write(0xff);
-        output.write(0xff);
-        output.write(0xff);
-        byte[] bytes="01234567890123456789".getBytes(StringUtil.__ISO_8859_1);
-        for (int i=0;i<bytes.length;i++)
-            output.write(bytes[i]^0xff);
-        output.flush();
-        
-        
-        
-        assertEquals(0x80|WebSocketConnectionD08.OP_CLOSE,input.read());
-        assertEquals(30,input.read());
-        int code=(0xff&input.read())*0x100+(0xff&input.read());
-        assertEquals(WebSocketConnectionD08.CLOSE_BADDATA,code);
-        lookFor("Text message size > 15 chars",input);
-    }
-
-    @Test
-    public void testBinaryAggregate() throws Exception
-    {
-        Socket socket = new Socket("localhost", __connector.getLocalPort());
-        OutputStream output = socket.getOutputStream();
-        output.write(
-                ("GET /chat HTTP/1.1\r\n"+
-                 "Host: server.example.com\r\n"+
-                 "Upgrade: websocket\r\n"+
-                 "Connection: Upgrade\r\n"+
-                 "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+
-                 "Sec-WebSocket-Origin: http://example.com\r\n"+
-                 "Sec-WebSocket-Protocol: aggregate\r\n" +
-                 "Sec-WebSocket-Version: 8\r\n"+
-                 "\r\n").getBytes("ISO-8859-1"));
-        output.flush();
-
-        socket.setSoTimeout(1000);
-        InputStream input = socket.getInputStream();
-        
-        lookFor("HTTP/1.1 101 Switching Protocols\r\n",input);
-        skipTo("Sec-WebSocket-Accept: ",input);
-        lookFor("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",input);
-        skipTo("\r\n\r\n",input);
-
-        assertTrue(__serverWebSocket.awaitConnected(1000));
-        assertNotNull(__serverWebSocket.connection);
-        __serverWebSocket.getConnection().setMaxBinaryMessageSize(1024);
-        
-        output.write(WebSocketConnectionD08.OP_BINARY);
-        output.write(0x8a);
-        output.write(0xff);
-        output.write(0xff);
-        output.write(0xff);
-        output.write(0xff);
-        byte[] bytes="0123456789".getBytes(StringUtil.__ISO_8859_1);
-        for (int i=0;i<bytes.length;i++)
-            output.write(bytes[i]^0xff);
-        output.flush();
-
-        output.write(0x80);
-        output.write(0x8a);
-        output.write(0xff);
-        output.write(0xff);
-        output.write(0xff);
-        output.write(0xff);
-        for (int i=0;i<bytes.length;i++)
-            output.write(bytes[i]^0xff);
-        output.flush();
-        
-        assertEquals(0x80+WebSocketConnectionD08.OP_BINARY,input.read());
-        assertEquals(20,input.read());
-        lookFor("01234567890123456789",input);
-    }
-    
-    @Test
-    public void testMaxBinarySize() throws Exception
-    {
-        Socket socket = new Socket("localhost", __connector.getLocalPort());
-        OutputStream output = socket.getOutputStream();
-        output.write(
-                ("GET /chat HTTP/1.1\r\n"+
-                 "Host: server.example.com\r\n"+
-                 "Upgrade: websocket\r\n"+
-                 "Connection: Upgrade\r\n"+
-                 "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+
-                 "Sec-WebSocket-Origin: http://example.com\r\n"+
-                 "Sec-WebSocket-Protocol: other\r\n" +
-                 "Sec-WebSocket-Version: 8\r\n"+
-                 "\r\n").getBytes("ISO-8859-1"));
-        output.flush();
-
-        socket.setSoTimeout(100000);
-        InputStream input = socket.getInputStream();
-        
-        lookFor("HTTP/1.1 101 Switching Protocols\r\n",input);
-        skipTo("Sec-WebSocket-Accept: ",input);
-        lookFor("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",input);
-        skipTo("\r\n\r\n",input);
-
-        assertTrue(__serverWebSocket.awaitConnected(1000));
-        assertNotNull(__serverWebSocket.connection);
-        
-        __serverWebSocket.getConnection().setMaxBinaryMessageSize(15);
-        
-        output.write(0x02);
-        output.write(0x8a);
-        output.write(0xff);
-        output.write(0xff);
-        output.write(0xff);
-        output.write(0xff);
-        byte[] bytes="0123456789".getBytes(StringUtil.__ISO_8859_1);
-        for (int i=0;i<bytes.length;i++)
-            output.write(bytes[i]^0xff);
-        output.flush();
-
-        output.write(0x80);
-        output.write(0x8a);
-        output.write(0xff);
-        output.write(0xff);
-        output.write(0xff);
-        output.write(0xff);
-        for (int i=0;i<bytes.length;i++)
-            output.write(bytes[i]^0xff);
-        output.flush();
-
-        
-        assertEquals(0x80|WebSocketConnectionD08.OP_CLOSE,input.read());
-        assertEquals(19,input.read());
-        int code=(0xff&input.read())*0x100+(0xff&input.read());
-        assertEquals(WebSocketConnectionD08.CLOSE_BADDATA,code);
-        lookFor("Message size > 15",input);
-    }
-
-
-    @Test
-    public void testMaxBinarySize2() throws Exception
-    {
-        Socket socket = new Socket("localhost", __connector.getLocalPort());
-        OutputStream output = socket.getOutputStream();
-        output.write(
-                ("GET /chat HTTP/1.1\r\n"+
-                 "Host: server.example.com\r\n"+
-                 "Upgrade: websocket\r\n"+
-                 "Connection: Upgrade\r\n"+
-                 "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+
-                 "Sec-WebSocket-Origin: http://example.com\r\n"+
-                 "Sec-WebSocket-Protocol: other\r\n" +
-                 "Sec-WebSocket-Version: 8\r\n"+
-                 "\r\n").getBytes("ISO-8859-1"));
-        output.flush();
-
-        socket.setSoTimeout(100000);
-        InputStream input = socket.getInputStream();
-        
-        lookFor("HTTP/1.1 101 Switching Protocols\r\n",input);
-        skipTo("Sec-WebSocket-Accept: ",input);
-        lookFor("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",input);
-        skipTo("\r\n\r\n",input);
-
-        assertTrue(__serverWebSocket.awaitConnected(1000));
-        assertNotNull(__serverWebSocket.connection);
-        
-        __serverWebSocket.getConnection().setMaxBinaryMessageSize(15);
-        
-        output.write(0x02);
-        output.write(0x94);
-        output.write(0xff);
-        output.write(0xff);
-        output.write(0xff);
-        output.write(0xff);
-        byte[] bytes="01234567890123456789".getBytes(StringUtil.__ISO_8859_1);
-        for (int i=0;i<bytes.length;i++)
-            output.write(bytes[i]^0xff);
-        output.flush();
-        
-        assertEquals(0x80|WebSocketConnectionD08.OP_CLOSE,input.read());
-        assertEquals(19,input.read());
-        int code=(0xff&input.read())*0x100+(0xff&input.read());
-        assertEquals(WebSocketConnectionD08.CLOSE_BADDATA,code);
-        lookFor("Message size > 15",input);
-    }
-
-    @Test
-    public void testIdle() throws Exception
-    {
-        Socket socket = new Socket("localhost", __connector.getLocalPort());
-        OutputStream output = socket.getOutputStream();
-        output.write(
-                ("GET /chat HTTP/1.1\r\n"+
-                 "Host: server.example.com\r\n"+
-                 "Upgrade: websocket\r\n"+
-                 "Connection: Upgrade\r\n"+
-                 "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+
-                 "Sec-WebSocket-Origin: http://example.com\r\n"+
-                 "Sec-WebSocket-Protocol: onConnect\r\n" +
-                 "Sec-WebSocket-Version: 8\r\n"+
-                 "\r\n").getBytes("ISO-8859-1"));
-        output.flush();
-
-        // Make sure the read times out if there are problems with the implementation
-        socket.setSoTimeout(10000);
-
-        InputStream input = socket.getInputStream();
-
-        lookFor("HTTP/1.1 101 Switching Protocols\r\n",input);
-        skipTo("Sec-WebSocket-Accept: ",input);
-        lookFor("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",input);
-        skipTo("\r\n\r\n",input);
-
-        assertTrue(__serverWebSocket.awaitConnected(1000));
-        assertNotNull(__serverWebSocket.connection);
-        
-        assertEquals(0x81,input.read());
-        assertEquals(0x0f,input.read());
-        lookFor("sent on connect",input);
-
-        assertEquals((byte)0x88,(byte)input.read());
-        assertEquals(26,input.read());
-        assertEquals(1000/0x100,input.read());
-        assertEquals(1000%0x100,input.read());
-        lookFor("Idle",input);
-
-        // respond to close
-        output.write(0x88^0xff);
-        output.write(0x80^0xff);
-        output.write(0xff);
-        output.write(0xff);
-        output.write(0xff);
-        output.write(0xff);
-        output.flush();
-        
-        
-        assertTrue(__serverWebSocket.awaitDisconnected(5000));
-        try
-        {
-            __serverWebSocket.connection.sendMessage("Don't send");
-            assertTrue(false);
-        }
-        catch(IOException e)
-        {
-            assertTrue(true);
-        }
-    }
-
-    @Test
-    public void testClose() throws Exception
-    {
-        Socket socket = new Socket("localhost", __connector.getLocalPort());
-        OutputStream output = socket.getOutputStream();
-        output.write(
-                ("GET /chat HTTP/1.1\r\n"+
-                        "Host: server.example.com\r\n"+
-                        "Upgrade: websocket\r\n"+
-                        "Connection: Upgrade\r\n"+
-                        "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+
-                        "Sec-WebSocket-Origin: http://example.com\r\n"+
-                        "Sec-WebSocket-Protocol: onConnect\r\n" +
-                        "Sec-WebSocket-Version: 8\r\n"+
-                "\r\n").getBytes("ISO-8859-1"));
-        output.flush();
-
-        // Make sure the read times out if there are problems with the implementation
-        socket.setSoTimeout(1000);
-
-        InputStream input = socket.getInputStream();
-
-        lookFor("HTTP/1.1 101 Switching Protocols\r\n",input);
-        skipTo("Sec-WebSocket-Accept: ",input);
-        lookFor("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",input);
-        skipTo("\r\n\r\n",input);
-
-
-        assertTrue(__serverWebSocket.awaitConnected(1000));
-        assertNotNull(__serverWebSocket.connection);
-        
-        assertEquals(0x81,input.read());
-        assertEquals(0x0f,input.read());
-        lookFor("sent on connect",input);
-        socket.close();
-        
-        assertTrue(__serverWebSocket.awaitDisconnected(500));
-        
-
-        try
-        {
-            __serverWebSocket.connection.sendMessage("Don't send");
-            assertTrue(false);
-        }
-        catch(IOException e)
-        {
-            assertTrue(true);
-        }    
-    }
-    
-    @Test
-    public void testParserAndGenerator() throws Exception
-    {
-        String message = "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF";
-        final AtomicReference<String> received = new AtomicReference<String>();
-        ByteArrayEndPoint endp = new ByteArrayEndPoint(new byte[0],4096);
-        
-        WebSocketGeneratorD08 gen = new WebSocketGeneratorD08(new WebSocketBuffers(8096),endp,null);
-        
-        byte[] data = message.getBytes(StringUtil.__UTF8);
-        gen.addFrame((byte)0x8,(byte)0x4,data,0,data.length);
-        
-        endp = new ByteArrayEndPoint(endp.getOut().asArray(),4096);
-                
-        WebSocketParserD08 parser = new WebSocketParserD08(new WebSocketBuffers(8096),endp,new WebSocketParser.FrameHandler()
-        {
-            public void onFrame(byte flags, byte opcode, Buffer buffer)
-            {
-                received.set(buffer.toString());
-            }
-
-            public void close(int code,String message)
-            {
-            }
-
-        },false);
-        
-        parser.parseNext();
-        
-        assertEquals(message,received.get());
-    }
-    
-    @Test
-    public void testParserAndGeneratorMasked() throws Exception
-    {
-        String message = "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF";
-        final AtomicReference<String> received = new AtomicReference<String>();
-        ByteArrayEndPoint endp = new ByteArrayEndPoint(new byte[0],4096);
-
-        MaskGen maskGen = new RandomMaskGen();
-        
-        WebSocketGeneratorD08 gen = new WebSocketGeneratorD08(new WebSocketBuffers(8096),endp,maskGen);
-        byte[] data = message.getBytes(StringUtil.__UTF8);
-        gen.addFrame((byte)0x8,(byte)0x1,data,0,data.length);
-        
-        endp = new ByteArrayEndPoint(endp.getOut().asArray(),4096);
-                
-        WebSocketParserD08 parser = new WebSocketParserD08(new WebSocketBuffers(8096),endp,new WebSocketParser.FrameHandler()
-        {
-            public void onFrame(byte flags, byte opcode, Buffer buffer)
-            {
-                received.set(buffer.toString());
-            }
-
-            public void close(int code,String message)
-            {
-            }
-        },true);
-        
-        parser.parseNext();
-        
-        assertEquals(message,received.get());
-    }
-    
-    
-    private void lookFor(String string,InputStream in)
-        throws IOException
-    {
-        String orig=string;
-        Utf8StringBuilder scanned=new Utf8StringBuilder();
-        try
-        {
-            while(true)
-            {
-                int b = in.read();
-                if (b<0)
-                    throw new EOFException();
-                scanned.append((byte)b);
-                assertEquals("looking for\""+orig+"\" in '"+scanned+"'",(int)string.charAt(0),b);
-                if (string.length()==1)
-                    break;
-                string=string.substring(1);
-            }
-        }
-        catch(IOException e)
-        {
-            System.err.println("IOE while looking for \""+orig+"\" in '"+scanned+"'");
-            throw e;
-        }
-    }
-
-    private void skipTo(String string,InputStream in)
-    throws IOException
-    {
-        int state=0;
-
-        while(true)
-        {
-            int b = in.read();
-            if (b<0)
-                throw new EOFException();
-
-            if (b==string.charAt(state))
-            {
-                state++;
-                if (state==string.length())
-                    break;
-            }
-            else
-                state=0;
-        }
-    }
-    
-
-    private static class TestWebSocket implements WebSocket.OnFrame, WebSocket.OnBinaryMessage, WebSocket.OnTextMessage
-    {
-        protected boolean _latch;
-        boolean _onConnect=false;
-        boolean _echo=true;
-        boolean _aggregate=false;
-        private final CountDownLatch connected = new CountDownLatch(1);
-        private final CountDownLatch disconnected = new CountDownLatch(1);
-        private volatile FrameConnection connection;
-
-        public FrameConnection getConnection()
-        {
-            return connection;
-        }
-
-        public void onHandshake(FrameConnection connection)
-        {
-            this.connection = connection;
-        }
-        
-        public void onOpen(Connection connection)
-        {
-            if (_onConnect)
-            {
-                try
-                {
-                    connection.sendMessage("sent on connect");
-                }
-                catch(IOException e)
-                {
-                    e.printStackTrace();
-                }
-            }
-            connected.countDown();
-        }
-
-        private boolean awaitConnected(long time) throws InterruptedException
-        {
-            return connected.await(time, TimeUnit.MILLISECONDS);
-        }
-
-        private boolean awaitDisconnected(long time) throws InterruptedException
-        {
-            return disconnected.await(time, TimeUnit.MILLISECONDS);
-        }
-        
-        public void onClose(int code,String message)
-        {
-            disconnected.countDown();
-        }
-
-        public boolean onFrame(byte flags, byte opcode, byte[] data, int offset, int length)
-        {            
-            if (_echo)
-            {
-                switch(opcode)
-                {
-                    case WebSocketConnectionD08.OP_CLOSE:
-                    case WebSocketConnectionD08.OP_PING:
-                    case WebSocketConnectionD08.OP_PONG:
-                        break;
-                        
-                    default:
-                        try
-                        {
-                            connection.sendFrame(flags,opcode,data,offset,length);
-                        }
-                        catch (IOException e)
-                        {
-                            e.printStackTrace();
-                        }
-                }
-            }
-            return false;
-        }
-
-        public void onMessage(byte[] data, int offset, int length)
-        {
-            if (_aggregate)
-            {
-                try
-                {
-                    connection.sendMessage(data,offset,length);
-                }
-                catch (IOException e)
-                {
-                    e.printStackTrace();
-                }
-            }
-        }
-
-        public void onMessage(String data)
-        {
-            __textCount.incrementAndGet();
-            if (_latch)
-            {
-                try
-                {
-                    __latch.await();
-                }
-                catch(Exception e)
-                {
-                    e.printStackTrace();
-                }
-            }
-            
-            if (_aggregate)
-            {
-                try
-                {
-                    connection.sendMessage(data);
-                }
-                catch (IOException e)
-                {
-                    e.printStackTrace();
-                }
-            }
-        }
-
-    }
-}
diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketMessageRFC6455Test.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketMessageRFC6455Test.java
deleted file mode 100644
index 98c93bb..0000000
--- a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketMessageRFC6455Test.java
+++ /dev/null
@@ -1,1665 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.websocket;
-
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
-
-import java.io.EOFException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.Socket;
-import java.net.SocketException;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicLong;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.zip.Deflater;
-import java.util.zip.Inflater;
-
-import javax.servlet.http.HttpServletRequest;
-
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.ByteArrayEndPoint;
-import org.eclipse.jetty.server.Connector;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.handler.DefaultHandler;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
-import org.eclipse.jetty.util.StringUtil;
-import org.eclipse.jetty.util.TypeUtil;
-import org.eclipse.jetty.util.Utf8StringBuilder;
-import org.junit.AfterClass;
-import org.junit.Assert;
-import org.junit.BeforeClass;
-import org.junit.Test;
-import org.junit.Ignore;
-
- at Ignore("slow release machine")
-public class WebSocketMessageRFC6455Test
-{
-    private static Server __server;
-    private static Connector __connector;
-    private static TestWebSocket __serverWebSocket;
-    private static CountDownLatch __latch;
-    private static AtomicInteger __textCount = new AtomicInteger(0);
-
-    @BeforeClass
-    public static void startServer() throws Exception
-    {
-        __server = new Server();
-        __connector = new SelectChannelConnector();
-        __server.addConnector(__connector);
-        WebSocketHandler wsHandler = new WebSocketHandler()
-        {
-            public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol)
-            {
-                __textCount.set(0);
-                __serverWebSocket = new TestWebSocket();
-                __serverWebSocket._onConnect=("onConnect".equals(protocol));
-                __serverWebSocket._echo=("echo".equals(protocol));
-                __serverWebSocket._aggregate=("aggregate".equals(protocol));
-                __serverWebSocket._latch=("latch".equals(protocol));
-                if (__serverWebSocket._latch)
-                    __latch=new CountDownLatch(1);
-                return __serverWebSocket;
-            }
-        };
-        wsHandler.getWebSocketFactory().setBufferSize(8192);
-        wsHandler.getWebSocketFactory().setMaxIdleTime(1000);
-        wsHandler.setHandler(new DefaultHandler());
-        __server.setHandler(wsHandler);
-        __server.start();
-    }
-
-    @AfterClass
-    public static void stopServer() throws Exception
-    {
-        __server.stop();
-        __server.join();
-    }
-
-
-    @Test
-    public void testHash()
-    {
-        assertEquals("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",WebSocketConnectionRFC6455.hashKey("dGhlIHNhbXBsZSBub25jZQ=="));
-    }
-
-    @Test
-    public void testServerSendBigStringMessage() throws Exception
-    {
-        Socket socket = new Socket("localhost", __connector.getLocalPort());
-        OutputStream output = socket.getOutputStream();
-        output.write(
-                ("GET /chat HTTP/1.1\r\n"+
-                 "Host: server.example.com\r\n"+
-                 "Upgrade: websocket\r\n"+
-                 "Connection: Upgrade\r\n"+
-                 "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+
-                 "Sec-WebSocket-Origin: http://example.com\r\n"+
-                 "Sec-WebSocket-Protocol: chat, superchat\r\n"+
-                 "Sec-WebSocket-Version: "+WebSocketConnectionRFC6455.VERSION+"\r\n"+
-                 "\r\n").getBytes("ISO-8859-1"));
-        output.flush();
-
-        // Make sure the read times out if there are problems with the implementation
-        socket.setSoTimeout(1000);
-
-        InputStream input = socket.getInputStream();
-
-        lookFor("HTTP/1.1 101 Switching Protocols\r\n",input);
-        skipTo("Sec-WebSocket-Accept: ",input);
-        lookFor("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",input);
-        skipTo("\r\n\r\n",input);
-
-        assertTrue(__serverWebSocket.awaitConnected(1000));
-        assertNotNull(__serverWebSocket.connection);
-
-        // Server sends a big message
-        StringBuilder message = new StringBuilder();
-        String text = "0123456789ABCDEF";
-        for (int i = 0; i < (0x2000) / text.length(); i++)
-            message.append(text);
-        String data=message.toString();
-        __serverWebSocket.connection.sendMessage(data);
-
-        assertEquals(WebSocketConnectionRFC6455.OP_TEXT,input.read());
-        assertEquals(0x7e,input.read());
-        assertEquals(0x1f,input.read());
-        assertEquals(0xf6,input.read());
-        lookFor(data.substring(0,0x1ff6),input);
-        assertEquals(0x80,input.read());
-        assertEquals(0x0A,input.read());
-        lookFor(data.substring(0x1ff6),input);
-    }
-
-    @Test
-    public void testServerSendOnConnect() throws Exception
-    {
-        Socket socket = new Socket("localhost", __connector.getLocalPort());
-        OutputStream output = socket.getOutputStream();
-        output.write(
-                ("GET /chat HTTP/1.1\r\n"+
-                 "Host: server.example.com\r\n"+
-                 "Upgrade: websocket\r\n"+
-                 "Connection: Upgrade\r\n"+
-                 "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+
-                 "Sec-WebSocket-Origin: http://example.com\r\n"+
-                 "Sec-WebSocket-Protocol: onConnect\r\n" +
-                 "Sec-WebSocket-Version: "+WebSocketConnectionRFC6455.VERSION+"\r\n"+
-                 "\r\n").getBytes("ISO-8859-1"));
-        output.flush();
-
-        // Make sure the read times out if there are problems with the implementation
-        socket.setSoTimeout(1000);
-
-        InputStream input = socket.getInputStream();
-
-        lookFor("HTTP/1.1 101 Switching Protocols\r\n",input);
-        skipTo("Sec-WebSocket-Accept: ",input);
-        lookFor("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",input);
-        skipTo("\r\n\r\n",input);
-
-        assertTrue(__serverWebSocket.awaitConnected(1000));
-        assertNotNull(__serverWebSocket.connection);
-
-        assertEquals(0x81,input.read());
-        assertEquals(0x0f,input.read());
-        lookFor("sent on connect",input);
-    }
-
-    @Test
-    public void testIdentityExtension() throws Exception
-    {
-        Socket socket = new Socket("localhost", __connector.getLocalPort());
-        OutputStream output = socket.getOutputStream();
-        output.write(
-                ("GET /chat HTTP/1.1\r\n"+
-                 "Host: server.example.com\r\n"+
-                 "Upgrade: websocket\r\n"+
-                 "Connection: Upgrade\r\n"+
-                 "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+
-                 "Sec-WebSocket-Origin: http://example.com\r\n"+
-                 "Sec-WebSocket-Protocol: onConnect\r\n" +
-                 "Sec-WebSocket-Version: "+WebSocketConnectionRFC6455.VERSION+"\r\n"+
-                 "Sec-WebSocket-Extensions: identity;param=0\r\n"+
-                 "Sec-WebSocket-Extensions: identity;param=1, identity ; param = '2' ; other = ' some = value ' \r\n"+
-                 "\r\n").getBytes("ISO-8859-1"));
-        output.flush();
-
-        // Make sure the read times out if there are problems with the implementation
-        socket.setSoTimeout(1000);
-
-        InputStream input = socket.getInputStream();
-        lookFor("HTTP/1.1 101 Switching Protocols\r\n",input);
-        skipTo("Sec-WebSocket-Accept: ",input);
-        lookFor("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",input);
-        skipTo("Sec-WebSocket-Extensions: ",input);
-        lookFor("identity;param=0",input);
-        skipTo("Sec-WebSocket-Extensions: ",input);
-        lookFor("identity;param=1",input);
-        skipTo("Sec-WebSocket-Extensions: ",input);
-        lookFor("identity;",input);
-        skipTo("\r\n\r\n",input);
-
-        assertTrue(__serverWebSocket.awaitConnected(1000));
-        assertNotNull(__serverWebSocket.connection);
-
-        assertEquals(0x81,input.read());
-        assertEquals(0x0f,input.read());
-        lookFor("sent on connect",input);
-    }
-
-
-    @Test
-    public void testFragmentExtension() throws Exception
-    {
-        Socket socket = new Socket("localhost", __connector.getLocalPort());
-        OutputStream output = socket.getOutputStream();
-        output.write(
-                ("GET /chat HTTP/1.1\r\n"+
-                 "Host: server.example.com\r\n"+
-                 "Upgrade: websocket\r\n"+
-                 "Connection: Upgrade\r\n"+
-                 "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+
-                 "Sec-WebSocket-Origin: http://example.com\r\n"+
-                 "Sec-WebSocket-Protocol: onConnect\r\n" +
-                 "Sec-WebSocket-Version: "+WebSocketConnectionRFC6455.VERSION+"\r\n"+
-                 "Sec-WebSocket-Extensions: fragment;maxLength=4;minFragments=7\r\n"+
-                 "\r\n").getBytes("ISO-8859-1"));
-        output.flush();
-
-        // Make sure the read times out if there are problems with the implementation
-        socket.setSoTimeout(1000);
-
-        InputStream input = socket.getInputStream();
-        lookFor("HTTP/1.1 101 Switching Protocols\r\n",input);
-        skipTo("Sec-WebSocket-Accept: ",input);
-        lookFor("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",input);
-        skipTo("Sec-WebSocket-Extensions: ",input);
-        lookFor("fragment;",input);
-        skipTo("\r\n\r\n",input);
-
-        assertTrue(__serverWebSocket.awaitConnected(1000));
-        assertNotNull(__serverWebSocket.connection);
-
-        assertEquals(0x01,input.read());
-        assertEquals(0x04,input.read());
-        lookFor("sent",input);
-        assertEquals(0x00,input.read());
-        assertEquals(0x04,input.read());
-        lookFor(" on ",input);
-        assertEquals(0x00,input.read());
-        assertEquals(0x04,input.read());
-        lookFor("conn",input);
-        assertEquals(0x00,input.read());
-        assertEquals(0x01,input.read());
-        lookFor("e",input);
-        assertEquals(0x00,input.read());
-        assertEquals(0x01,input.read());
-        lookFor("c",input);
-        assertEquals(0x00,input.read());
-        assertEquals(0x00,input.read());
-        assertEquals(0x80,input.read());
-        assertEquals(0x01,input.read());
-        lookFor("t",input);
-    }
-
-    @Test
-    public void testDeflateFrameExtension() throws Exception
-    {
-        Socket socket = new Socket("localhost", __connector.getLocalPort());
-        OutputStream output = socket.getOutputStream();
-        output.write(
-                ("GET /chat HTTP/1.1\r\n"+
-                 "Host: server.example.com\r\n"+
-                 "Upgrade: websocket\r\n"+
-                 "Connection: Upgrade\r\n"+
-                 "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+
-                 "Sec-WebSocket-Origin: http://example.com\r\n"+
-                 "Sec-WebSocket-Protocol: echo\r\n" +
-                 "Sec-WebSocket-Version: "+WebSocketConnectionRFC6455.VERSION+"\r\n"+
-                 "Sec-WebSocket-Extensions: x-deflate-frame;minLength=64\r\n"+
-                 "Sec-WebSocket-Extensions: fragment;minFragments=2\r\n"+
-                 "\r\n").getBytes("ISO-8859-1"));
-        output.flush();
-
-        // Make sure the read times out if there are problems with the implementation
-        socket.setSoTimeout(1000);
-
-        InputStream input = socket.getInputStream();
-        lookFor("HTTP/1.1 101 Switching Protocols\r\n",input);
-        skipTo("Sec-WebSocket-Accept: ",input);
-        lookFor("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",input);
-        skipTo("Sec-WebSocket-Extensions: ",input);
-        lookFor("x-deflate-frame;minLength=64",input);
-        skipTo("Sec-WebSocket-Extensions: ",input);
-        lookFor("fragment;",input);
-        skipTo("\r\n\r\n",input);
-
-        assertTrue(__serverWebSocket.awaitConnected(1000));
-        assertNotNull(__serverWebSocket.connection);
-
-
-        // Server sends a big message
-        String text = "0123456789ABCDEF ";
-        text=text+text+text+text;
-        text=text+text+text+text;
-        text=text+text+text+text+'X';
-        byte[] data=text.getBytes("utf-8");
-        Deflater deflater = new Deflater();
-        deflater.setInput(data);
-        deflater.finish();
-        byte[] buf=new byte[data.length];
-
-        buf[0]=(byte)((byte)0x7e);
-        buf[1]=(byte)(data.length>>8);
-        buf[2]=(byte)(data.length&0xff);
-
-        int l=deflater.deflate(buf,3,buf.length-3);
-
-        assertTrue(deflater.finished());
-
-        output.write(0xC1);
-        output.write((byte)(0x80|(0xff&(l+3))));
-        output.write(0x00);
-        output.write(0x00);
-        output.write(0x00);
-        output.write(0x00);
-        output.write(buf,0,l+3);
-        output.flush();
-
-        assertEquals(0x40+WebSocketConnectionRFC6455.OP_TEXT,input.read());
-        assertEquals(0x20+3,input.read());
-        assertEquals(0x7e,input.read());
-        assertEquals(0x02,input.read());
-        assertEquals(0x20,input.read());
-
-        byte[] raw = new byte[32];
-        assertEquals(32,input.read(raw));
-
-        Inflater inflater = new Inflater();
-        inflater.setInput(raw);
-
-        byte[] result = new byte[544];
-        assertEquals(544,inflater.inflate(result));
-        assertEquals(TypeUtil.toHexString(data,0,544),TypeUtil.toHexString(result));
-
-
-        assertEquals((byte)0xC0,(byte)input.read());
-        assertEquals(0x21+3,input.read());
-        assertEquals(0x7e,input.read());
-        assertEquals(0x02,input.read());
-        assertEquals(0x21,input.read());
-
-        assertEquals(32,input.read(raw));
-
-        inflater.reset();
-        inflater.setInput(raw);
-        result = new byte[545];
-        assertEquals(545,inflater.inflate(result));
-        assertEquals(TypeUtil.toHexString(data,544,545),TypeUtil.toHexString(result));
-
-
-    }
-
-    @Test
-    public void testServerEcho() throws Exception
-    {
-        Socket socket = new Socket("localhost", __connector.getLocalPort());
-        OutputStream output = socket.getOutputStream();
-        output.write(
-                ("GET /chat HTTP/1.1\r\n"+
-                 "Host: server.example.com\r\n"+
-                 "Upgrade: websocket\r\n"+
-                 "Connection: Upgrade\r\n"+
-                 "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+
-                 "Sec-WebSocket-Origin: http://example.com\r\n"+
-                 "Sec-WebSocket-Protocol: echo\r\n" +
-                 "Sec-WebSocket-Version: "+WebSocketConnectionRFC6455.VERSION+"\r\n"+
-                 "\r\n").getBytes("ISO-8859-1"));
-        output.flush();
-        output.write(0x84);
-        output.write(0x8f);
-        output.write(0xff);
-        output.write(0xff);
-        output.write(0xff);
-        output.write(0xff);
-        byte[] bytes="this is an echo".getBytes(StringUtil.__ISO_8859_1);
-        for (int i=0;i<bytes.length;i++)
-            output.write(bytes[i]^0xff);
-        output.flush();
-        // Make sure the read times out if there are problems with the implementation
-        socket.setSoTimeout(1000);
-
-        InputStream input = socket.getInputStream();
-
-        lookFor("HTTP/1.1 101 Switching Protocols\r\n",input);
-        skipTo("Sec-WebSocket-Accept: ",input);
-        lookFor("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",input);
-        skipTo("\r\n\r\n",input);
-
-        assertTrue(__serverWebSocket.awaitConnected(1000));
-        assertNotNull(__serverWebSocket.connection);
-
-        assertEquals(0x84,input.read());
-        assertEquals(0x0f,input.read());
-        lookFor("this is an echo",input);
-    }
-
-    @Test
-    public void testBlockedConsumer() throws Exception
-    {
-        Socket socket = new Socket("localhost", __connector.getLocalPort());
-        OutputStream output = socket.getOutputStream();
-
-        byte[] bytes="This is a long message of text that we will send again and again".getBytes(StringUtil.__ISO_8859_1);
-        byte[] mesg=new byte[bytes.length+6];
-        mesg[0]=(byte)(0x80+WebSocketConnectionRFC6455.OP_TEXT);
-        mesg[1]=(byte)(0x80+bytes.length);
-        mesg[2]=(byte)0xff;
-        mesg[3]=(byte)0xff;
-        mesg[4]=(byte)0xff;
-        mesg[5]=(byte)0xff;
-        for (int i=0;i<bytes.length;i++)
-            mesg[6+i]=(byte)(bytes[i]^0xff);
-
-        final int count = 100000;
-
-        output.write(
-                ("GET /chat HTTP/1.1\r\n"+
-                        "Host: server.example.com\r\n"+
-                        "Upgrade: websocket\r\n"+
-                        "Connection: Upgrade\r\n"+
-                        "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+
-                        "Sec-WebSocket-Origin: http://example.com\r\n"+
-                        "Sec-WebSocket-Protocol: latch\r\n" +
-                        "Sec-WebSocket-Version: "+WebSocketConnectionRFC6455.VERSION+"\r\n"+
-                "\r\n").getBytes("ISO-8859-1"));
-        output.flush();
-
-        // Make sure the read times out if there are problems with the implementation
-        socket.setSoTimeout(60000);
-
-        InputStream input = socket.getInputStream();
-
-        lookFor("HTTP/1.1 101 Switching Protocols\r\n",input);
-        skipTo("Sec-WebSocket-Accept: ",input);
-        lookFor("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",input);
-        skipTo("\r\n\r\n",input);
-
-        assertTrue(__serverWebSocket.awaitConnected(1000));
-        assertNotNull(__serverWebSocket.connection);
-        __serverWebSocket.connection.setMaxIdleTime(60000);
-
-        // Send and receive 1 message
-        output.write(mesg);
-        output.flush();
-        while(__textCount.get()==0)
-            Thread.sleep(10);
-
-        // unblock the latch in 4s
-        new Thread()
-        {
-            @Override
-            public void run()
-            {
-                try
-                {
-                    Thread.sleep(4000);
-                    __latch.countDown();
-                    //System.err.println("latched");
-                }
-                catch(Exception e)
-                {
-                    e.printStackTrace();
-                }
-            }
-        }.start();
-
-        // Send enough messages to fill receive buffer
-        long max=0;
-        long start=System.currentTimeMillis();
-        for (int i=0;i<count;i++)
-        {
-            output.write(mesg);
-            if (i%100==0)
-            {
-                // System.err.println(">>> "+i);
-                output.flush();
-
-                long now=System.currentTimeMillis();
-                long duration=now-start;
-                start=now;
-                if (max<duration)
-                    max=duration;
-            }
-        }
-
-        Thread.sleep(50);
-        while(__textCount.get()<count+1)
-        {
-            System.err.println(__textCount.get()+"<"+(count+1));
-            Thread.sleep(10);
-        }
-        assertEquals(count+1,__textCount.get()); // all messages
-        assertTrue(max>2000); // was blocked
-    }
-
-    @Test
-    public void testBlockedProducer() throws Exception
-    {
-        final Socket socket = new Socket("localhost", __connector.getLocalPort());
-        OutputStream output = socket.getOutputStream();
-
-        final int count = 100000;
-
-        output.write(
-                ("GET /chat HTTP/1.1\r\n"+
-                        "Host: server.example.com\r\n"+
-                        "Upgrade: websocket\r\n"+
-                        "Connection: Upgrade\r\n"+
-                        "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+
-                        "Sec-WebSocket-Origin: http://example.com\r\n"+
-                        "Sec-WebSocket-Protocol: latch\r\n" +
-                        "Sec-WebSocket-Version: "+WebSocketConnectionRFC6455.VERSION+"\r\n"+
-                "\r\n").getBytes("ISO-8859-1"));
-        output.flush();
-
-        // Make sure the read times out if there are problems with the implementation
-        socket.setSoTimeout(60000);
-
-        InputStream input = socket.getInputStream();
-
-        lookFor("HTTP/1.1 101 Switching Protocols\r\n",input);
-        skipTo("Sec-WebSocket-Accept: ",input);
-        lookFor("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",input);
-        skipTo("\r\n\r\n",input);
-
-        assertTrue(__serverWebSocket.awaitConnected(1000));
-        assertNotNull(__serverWebSocket.connection);
-        __serverWebSocket.connection.setMaxIdleTime(60000);
-        __latch.countDown();
-
-        // wait 2s and then consume messages
-        final AtomicLong totalB=new AtomicLong();
-        new Thread()
-        {
-            @Override
-            public void run()
-            {
-                try
-                {
-                    Thread.sleep(2000);
-
-                    byte[] recv = new byte[32*1024];
-
-                    int len=0;
-                    while (len>=0)
-                    {
-                        totalB.addAndGet(len);
-                        len=socket.getInputStream().read(recv,0,recv.length);
-                        Thread.sleep(10);
-                    }
-                }
-                catch(Exception e)
-                {
-                    e.printStackTrace();
-                }
-            }
-        }.start();
-
-
-        // Send enough messages to fill receive buffer
-        long max=0;
-        long start=System.currentTimeMillis();
-        String mesg="How Now Brown Cow";
-        for (int i=0;i<count;i++)
-        {
-            __serverWebSocket.connection.sendMessage(mesg);
-            if (i%100==0)
-            {
-                output.flush();
-
-                long now=System.currentTimeMillis();
-                long duration=now-start;
-                start=now;
-                if (max<duration)
-                    max=duration;
-            }
-        }
-
-        while(totalB.get()<(count*(mesg.length()+2)))
-            Thread.sleep(100);
-
-        assertEquals(count*(mesg.length()+2),totalB.get()); // all messages
-        Assert.assertThat("Was blocked (max time)", max, greaterThan(1000L)); // was blocked
-    }
-
-    @Test
-    public void testServerPingPong() throws Exception
-    {
-        Socket socket = new Socket("localhost", __connector.getLocalPort());
-        // Make sure the read times out if there are problems with the implementation
-        OutputStream output = socket.getOutputStream();
-        output.write(
-                ("GET /chat HTTP/1.1\r\n"+
-                 "Host: server.example.com\r\n"+
-                 "Upgrade: websocket\r\n"+
-                 "Connection: Upgrade\r\n"+
-                 "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+
-                 "Sec-WebSocket-Origin: http://example.com\r\n"+
-                 "Sec-WebSocket-Protocol: echo\r\n" +
-                 "Sec-WebSocket-Version: "+WebSocketConnectionRFC6455.VERSION+"\r\n"+
-                 "\r\n").getBytes("ISO-8859-1"));
-        output.flush();
-        output.write(0x89);
-        output.write(0x80);
-        output.write(0xff);
-        output.write(0xff);
-        output.write(0xff);
-        output.write(0xff);
-        output.flush();
-
-        InputStream input = socket.getInputStream();
-
-        lookFor("HTTP/1.1 101 Switching Protocols\r\n",input);
-        skipTo("Sec-WebSocket-Accept: ",input);
-        lookFor("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",input);
-        skipTo("\r\n\r\n",input);
-
-        assertTrue(__serverWebSocket.awaitConnected(1000));
-        assertNotNull(__serverWebSocket.connection);
-
-        socket.setSoTimeout(1000);
-        assertEquals(0x8A,input.read());
-        assertEquals(0x00,input.read());
-    }
-
-    @Test
-    public void testMaxTextSizeFalseFrag() throws Exception
-    {
-        Socket socket = new Socket("localhost", __connector.getLocalPort());
-        OutputStream output = socket.getOutputStream();
-        output.write(
-                ("GET /chat HTTP/1.1\r\n"+
-                 "Host: server.example.com\r\n"+
-                 "Upgrade: websocket\r\n"+
-                 "Connection: Upgrade\r\n"+
-                 "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+
-                 "Sec-WebSocket-Origin: http://example.com\r\n"+
-                 "Sec-WebSocket-Protocol: other\r\n" +
-                 "Sec-WebSocket-Version: "+WebSocketConnectionRFC6455.VERSION+"\r\n"+
-                 "\r\n").getBytes("ISO-8859-1"));
-        output.flush();
-
-        socket.setSoTimeout(1000);
-        InputStream input = socket.getInputStream();
-
-        lookFor("HTTP/1.1 101 Switching Protocols\r\n",input);
-        skipTo("Sec-WebSocket-Accept: ",input);
-        lookFor("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",input);
-        skipTo("\r\n\r\n",input);
-
-        assertTrue(__serverWebSocket.awaitConnected(1000));
-        assertNotNull(__serverWebSocket.connection);
-
-        __serverWebSocket.getConnection().setMaxTextMessageSize(10*1024);
-        __serverWebSocket.getConnection().setAllowFrameFragmentation(true);
-
-        output.write(0x81);
-        output.write(0x80|0x7E);
-        output.write((byte)((16*1024)>>8));
-        output.write((byte)((16*1024)&0xff));
-        output.write(0x00);
-        output.write(0x00);
-        output.write(0x00);
-        output.write(0x00);
-
-        for (int i=0;i<(16*1024);i++)
-            output.write('X');
-        output.flush();
-
-
-        assertEquals(0x80|WebSocketConnectionRFC6455.OP_CLOSE,input.read());
-        assertEquals(33,input.read());
-        int code=(0xff&input.read())*0x100+(0xff&input.read());
-        assertEquals(WebSocketConnectionRFC6455.CLOSE_MESSAGE_TOO_LARGE,code);
-        lookFor("Text message size > 10240 chars",input);
-    }
-
-    @Test
-    public void testMaxTextSize() throws Exception
-    {
-        Socket socket = new Socket("localhost", __connector.getLocalPort());
-        OutputStream output = socket.getOutputStream();
-        output.write(
-                ("GET /chat HTTP/1.1\r\n"+
-                 "Host: server.example.com\r\n"+
-                 "Upgrade: websocket\r\n"+
-                 "Connection: Upgrade\r\n"+
-                 "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+
-                 "Sec-WebSocket-Origin: http://example.com\r\n"+
-                 "Sec-WebSocket-Protocol: other\r\n" +
-                 "Sec-WebSocket-Version: "+WebSocketConnectionRFC6455.VERSION+"\r\n"+
-                 "\r\n").getBytes("ISO-8859-1"));
-        output.flush();
-
-        socket.setSoTimeout(1000);
-        InputStream input = socket.getInputStream();
-
-        lookFor("HTTP/1.1 101 Switching Protocols\r\n",input);
-        skipTo("Sec-WebSocket-Accept: ",input);
-        lookFor("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",input);
-        skipTo("\r\n\r\n",input);
-
-        assertTrue(__serverWebSocket.awaitConnected(1000));
-        assertNotNull(__serverWebSocket.connection);
-
-        __serverWebSocket.getConnection().setMaxTextMessageSize(15);
-
-        output.write(0x01);
-        output.write(0x8a);
-        output.write(0xff);
-        output.write(0xff);
-        output.write(0xff);
-        output.write(0xff);
-        byte[] bytes="0123456789".getBytes(StringUtil.__ISO_8859_1);
-        for (int i=0;i<bytes.length;i++)
-            output.write(bytes[i]^0xff);
-        output.flush();
-
-        output.write(0x80);
-        output.write(0x8a);
-        output.write(0xff);
-        output.write(0xff);
-        output.write(0xff);
-        output.write(0xff);
-        for (int i=0;i<bytes.length;i++)
-            output.write(bytes[i]^0xff);
-        output.flush();
-
-        assertEquals(0x80|WebSocketConnectionRFC6455.OP_CLOSE,input.read());
-        assertEquals(30,input.read());
-        int code=(0xff&input.read())*0x100+(0xff&input.read());
-        assertEquals(WebSocketConnectionRFC6455.CLOSE_MESSAGE_TOO_LARGE,code);
-        lookFor("Text message size > 15 chars",input);
-    }
-
-
-    @Test
-    public void testMaxTextSize2() throws Exception
-    {
-        Socket socket = new Socket("localhost", __connector.getLocalPort());
-        OutputStream output = socket.getOutputStream();
-        output.write(
-                ("GET /chat HTTP/1.1\r\n"+
-                 "Host: server.example.com\r\n"+
-                 "Upgrade: websocket\r\n"+
-                 "Connection: Upgrade\r\n"+
-                 "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+
-                 "Sec-WebSocket-Origin: http://example.com\r\n"+
-                 "Sec-WebSocket-Protocol: other\r\n" +
-                 "Sec-WebSocket-Version: "+WebSocketConnectionRFC6455.VERSION+"\r\n"+
-                 "\r\n").getBytes("ISO-8859-1"));
-        output.flush();
-
-        socket.setSoTimeout(100000);
-        InputStream input = socket.getInputStream();
-
-        lookFor("HTTP/1.1 101 Switching Protocols\r\n",input);
-        skipTo("Sec-WebSocket-Accept: ",input);
-        lookFor("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",input);
-        skipTo("\r\n\r\n",input);
-
-        assertTrue(__serverWebSocket.awaitConnected(1000));
-        assertNotNull(__serverWebSocket.connection);
-
-        __serverWebSocket.getConnection().setMaxTextMessageSize(15);
-
-        output.write(0x01);
-        output.write(0x94);
-        output.write(0xff);
-        output.write(0xff);
-        output.write(0xff);
-        output.write(0xff);
-        byte[] bytes="01234567890123456789".getBytes(StringUtil.__ISO_8859_1);
-        for (int i=0;i<bytes.length;i++)
-            output.write(bytes[i]^0xff);
-        output.flush();
-
-
-
-        assertEquals(0x80|WebSocketConnectionRFC6455.OP_CLOSE,input.read());
-        assertEquals(30,input.read());
-        int code=(0xff&input.read())*0x100+(0xff&input.read());
-        assertEquals(WebSocketConnectionRFC6455.CLOSE_MESSAGE_TOO_LARGE,code);
-        lookFor("Text message size > 15 chars",input);
-    }
-
-    @Test
-    public void testBinaryAggregate() throws Exception
-    {
-        Socket socket = new Socket("localhost", __connector.getLocalPort());
-        OutputStream output = socket.getOutputStream();
-        output.write(
-                ("GET /chat HTTP/1.1\r\n"+
-                 "Host: server.example.com\r\n"+
-                 "Upgrade: websocket\r\n"+
-                 "Connection: Upgrade\r\n"+
-                 "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+
-                 "Sec-WebSocket-Origin: http://example.com\r\n"+
-                 "Sec-WebSocket-Protocol: aggregate\r\n" +
-                 "Sec-WebSocket-Version: "+WebSocketConnectionRFC6455.VERSION+"\r\n"+
-                 "\r\n").getBytes("ISO-8859-1"));
-        output.flush();
-
-        socket.setSoTimeout(1000);
-        InputStream input = socket.getInputStream();
-
-        lookFor("HTTP/1.1 101 Switching Protocols\r\n",input);
-        skipTo("Sec-WebSocket-Accept: ",input);
-        lookFor("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",input);
-        skipTo("\r\n\r\n",input);
-
-        assertTrue(__serverWebSocket.awaitConnected(1000));
-        assertNotNull(__serverWebSocket.connection);
-        __serverWebSocket.getConnection().setMaxBinaryMessageSize(1024);
-
-        output.write(WebSocketConnectionRFC6455.OP_BINARY);
-        output.write(0x8a);
-        output.write(0xff);
-        output.write(0xff);
-        output.write(0xff);
-        output.write(0xff);
-        byte[] bytes="0123456789".getBytes(StringUtil.__ISO_8859_1);
-        for (int i=0;i<bytes.length;i++)
-            output.write(bytes[i]^0xff);
-        output.flush();
-
-        output.write(0x80);
-        output.write(0x8a);
-        output.write(0xff);
-        output.write(0xff);
-        output.write(0xff);
-        output.write(0xff);
-        for (int i=0;i<bytes.length;i++)
-            output.write(bytes[i]^0xff);
-        output.flush();
-
-        assertEquals(0x80+WebSocketConnectionRFC6455.OP_BINARY,input.read());
-        assertEquals(20,input.read());
-        lookFor("01234567890123456789",input);
-    }
-
-    @Test
-    public void testMaxBinarySize() throws Exception
-    {
-        Socket socket = new Socket("localhost", __connector.getLocalPort());
-        OutputStream output = socket.getOutputStream();
-        output.write(
-                ("GET /chat HTTP/1.1\r\n"+
-                 "Host: server.example.com\r\n"+
-                 "Upgrade: websocket\r\n"+
-                 "Connection: Upgrade\r\n"+
-                 "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+
-                 "Sec-WebSocket-Origin: http://example.com\r\n"+
-                 "Sec-WebSocket-Protocol: other\r\n" +
-                 "Sec-WebSocket-Version: "+WebSocketConnectionRFC6455.VERSION+"\r\n"+
-                 "\r\n").getBytes("ISO-8859-1"));
-        output.flush();
-
-        socket.setSoTimeout(100000);
-        InputStream input = socket.getInputStream();
-
-        lookFor("HTTP/1.1 101 Switching Protocols\r\n",input);
-        skipTo("Sec-WebSocket-Accept: ",input);
-        lookFor("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",input);
-        skipTo("\r\n\r\n",input);
-
-        assertTrue(__serverWebSocket.awaitConnected(1000));
-        assertNotNull(__serverWebSocket.connection);
-
-        __serverWebSocket.getConnection().setMaxBinaryMessageSize(15);
-
-        output.write(0x02);
-        output.write(0x8a);
-        output.write(0xff);
-        output.write(0xff);
-        output.write(0xff);
-        output.write(0xff);
-        byte[] bytes="0123456789".getBytes(StringUtil.__ISO_8859_1);
-        for (int i=0;i<bytes.length;i++)
-            output.write(bytes[i]^0xff);
-        output.flush();
-
-        output.write(0x80);
-        output.write(0x8a);
-        output.write(0xff);
-        output.write(0xff);
-        output.write(0xff);
-        output.write(0xff);
-        for (int i=0;i<bytes.length;i++)
-            output.write(bytes[i]^0xff);
-        output.flush();
-
-
-        assertEquals(0x80|WebSocketConnectionRFC6455.OP_CLOSE,input.read());
-        assertEquals(19,input.read());
-        int code=(0xff&input.read())*0x100+(0xff&input.read());
-        assertEquals(WebSocketConnectionRFC6455.CLOSE_MESSAGE_TOO_LARGE,code);
-        lookFor("Message size > 15",input);
-    }
-
-
-
-    @Test
-    public void testCloseIn() throws Exception
-    {
-        int[][] tests =
-        {
-                {-1,0,-1},
-                {-1,0,-1},
-                {1000,2,1000},
-                {1000,2+4,1000},
-                {1005,2+23,1002},
-                {1005,2+23,1002},
-                {1006,2+23,1002},
-                {1006,2+23,1002},
-                {4000,2,4000},
-                {4000,2+4,4000},
-                {9000,2+23,1002},
-                {9000,2+23,1002}
-        };
-
-        String[] mesg =
-        {
-                "",
-                "",
-                "",
-                "mesg",
-                "",
-                "mesg",
-                "",
-                "mesg",
-                "",
-                "mesg",
-                "",
-                "mesg"
-        };
-
-        String[] resp =
-        {
-                "",
-                "",
-                "",
-                "mesg",
-                "Invalid close code 1005",
-                "Invalid close code 1005",
-                "Invalid close code 1006",
-                "Invalid close code 1006",
-                "",
-                "mesg",
-                "Invalid close code 9000",
-                "Invalid close code 9000"
-        };
-
-        for (int t=0;t<tests.length;t++)
-        {
-            String tst=""+t;
-            Socket socket = new Socket("localhost", __connector.getLocalPort());
-            OutputStream output = socket.getOutputStream();
-            output.write(
-                    ("GET /chat HTTP/1.1\r\n"+
-                            "Host: server.example.com\r\n"+
-                            "Upgrade: websocket\r\n"+
-                            "Connection: Upgrade\r\n"+
-                            "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+
-                            "Sec-WebSocket-Origin: http://example.com\r\n"+
-                            "Sec-WebSocket-Protocol: chat\r\n" +
-                            "Sec-WebSocket-Version: "+WebSocketConnectionRFC6455.VERSION+"\r\n"+
-                    "\r\n").getBytes("ISO-8859-1"));
-            output.flush();
-
-            socket.setSoTimeout(100000);
-            InputStream input = socket.getInputStream();
-
-            lookFor("HTTP/1.1 101 Switching Protocols\r\n",input);
-            skipTo("Sec-WebSocket-Accept: ",input);
-            lookFor("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",input);
-            skipTo("\r\n\r\n",input);
-
-            assertTrue(__serverWebSocket.awaitConnected(1000));
-            assertNotNull(__serverWebSocket.connection);
-
-            int code=tests[t][0];
-            String m=mesg[t];
-
-            output.write(0x88);
-            output.write(0x80 + (code<=0?0:(2+m.length())));
-            output.write(0x00);
-            output.write(0x00);
-            output.write(0x00);
-            output.write(0x00);
-
-            if (code>0)
-            {
-                output.write(code/0x100);
-                output.write(code%0x100);
-                output.write(m.getBytes());
-            }
-            output.flush();
-
-            __serverWebSocket.awaitDisconnected(1000);
-
-            byte[] buf = new byte[128];
-            int len = input.read(buf);
-
-            assertEquals(tst,2+tests[t][1],len);
-            assertEquals(tst,(byte)0x88,buf[0]);
-
-            if (len>=4)
-            {
-                code=(0xff&buf[2])*0x100+(0xff&buf[3]);
-                assertEquals(tst,tests[t][2],code);
-
-                if (len>4)
-                {
-                    m = new String(buf,4,len-4,"UTF-8");
-                    assertEquals(tst,resp[t],m);
-                }
-            }
-            else
-                assertEquals(tst,tests[t][2],-1);
-
-
-            len = input.read(buf);
-            assertEquals(tst,-1,len);
-        }
-    }
-
-
-
-    @Test
-    public void testCloseOut() throws Exception
-    {
-        int[][] tests =
-        {
-                {-1,0,-1},
-                {-1,0,-1},
-                {0,2,1000},
-                {0,2+4,1000},
-                {1000,2,1000},
-                {1000,2+4,1000},
-                {1005,0,-1},
-                {1005,0,-1},
-                {1006,0,-1},
-                {1006,0,-1},
-                {9000,2,9000},
-                {9000,2+4,9000}
-        };
-
-        String[] mesg =
-        {
-                null,
-                "Not Sent",
-                null,
-                "mesg",
-                null,
-                "mesg",
-                null,
-                "mesg",
-                null,
-                "mesg",
-                null,
-                "mesg"
-        };
-
-        for (int t=0;t<tests.length;t++)
-        {
-            String tst=""+t;
-            Socket socket = new Socket("localhost", __connector.getLocalPort());
-            OutputStream output = socket.getOutputStream();
-            output.write(
-                    ("GET /chat HTTP/1.1\r\n"+
-                            "Host: server.example.com\r\n"+
-                            "Upgrade: websocket\r\n"+
-                            "Connection: Upgrade\r\n"+
-                            "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+
-                            "Sec-WebSocket-Origin: http://example.com\r\n"+
-                            "Sec-WebSocket-Protocol: chat\r\n" +
-                            "Sec-WebSocket-Version: "+WebSocketConnectionRFC6455.VERSION+"\r\n"+
-                    "\r\n").getBytes("ISO-8859-1"));
-            output.flush();
-
-            socket.setSoTimeout(100000);
-            InputStream input = socket.getInputStream();
-
-            lookFor("HTTP/1.1 101 Switching Protocols\r\n",input);
-            skipTo("Sec-WebSocket-Accept: ",input);
-            lookFor("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",input);
-            skipTo("\r\n\r\n",input);
-
-            assertTrue(__serverWebSocket.awaitConnected(1000));
-            assertNotNull(__serverWebSocket.connection);
-
-            __serverWebSocket.getConnection().close(tests[t][0],mesg[t]);
-
-            byte[] buf = new byte[128];
-            int len = input.read(buf);
-            assertEquals(tst,2+tests[t][1],len);
-            assertEquals(tst,(byte)0x88,buf[0]);
-
-            if (len>=4)
-            {
-                int code=(0xff&buf[2])*0x100+(0xff&buf[3]);
-                assertEquals(tst,tests[t][2],code);
-
-                if (len>4)
-                {
-                    String m = new String(buf,4,len-4,"UTF-8");
-                    assertEquals(tst,mesg[t],m);
-                }
-            }
-            else
-                assertEquals(tst,tests[t][2],-1);
-
-            try
-            {
-                output.write(0x88);
-                output.write(0x80);
-                output.write(0x00);
-                output.write(0x00);
-                output.write(0x00);
-                output.write(0x00);
-                output.flush();
-            }
-            catch(IOException e)
-            {
-                System.err.println("socket "+socket);
-                throw e;
-            }
-
-            len = input.read(buf);
-            assertEquals(tst,-1,len);
-        }
-    }
-
-
-    @Test
-    public void testNotUTF8() throws Exception
-    {
-        Socket socket = new Socket("localhost", __connector.getLocalPort());
-        OutputStream output = socket.getOutputStream();
-        output.write(
-                ("GET /chat HTTP/1.1\r\n"+
-                 "Host: server.example.com\r\n"+
-                 "Upgrade: websocket\r\n"+
-                 "Connection: Upgrade\r\n"+
-                 "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+
-                 "Sec-WebSocket-Origin: http://example.com\r\n"+
-                 "Sec-WebSocket-Protocol: chat\r\n" +
-                 "Sec-WebSocket-Version: "+WebSocketConnectionRFC6455.VERSION+"\r\n"+
-                 "\r\n").getBytes("ISO-8859-1"));
-        output.flush();
-
-        socket.setSoTimeout(100000);
-        InputStream input = socket.getInputStream();
-
-        lookFor("HTTP/1.1 101 Switching Protocols\r\n",input);
-        skipTo("Sec-WebSocket-Accept: ",input);
-        lookFor("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",input);
-        skipTo("\r\n\r\n",input);
-
-        assertTrue(__serverWebSocket.awaitConnected(1000));
-        assertNotNull(__serverWebSocket.connection);
-
-        __serverWebSocket.getConnection().setMaxBinaryMessageSize(15);
-
-        output.write(0x81);
-        output.write(0x82);
-        output.write(0x00);
-        output.write(0x00);
-        output.write(0x00);
-        output.write(0x00);
-        output.write(0xc3);
-        output.write(0x28);
-        output.flush();
-
-        assertEquals(0x80|WebSocketConnectionRFC6455.OP_CLOSE,input.read());
-        assertEquals(15,input.read());
-        int code=(0xff&input.read())*0x100+(0xff&input.read());
-        assertEquals(WebSocketConnectionRFC6455.CLOSE_BAD_PAYLOAD,code);
-        lookFor("Invalid UTF-8",input);
-    }
-
-    @Test
-    public void testMaxBinarySize2() throws Exception
-    {
-        Socket socket = new Socket("localhost", __connector.getLocalPort());
-        OutputStream output = socket.getOutputStream();
-        output.write(
-                ("GET /chat HTTP/1.1\r\n"+
-                 "Host: server.example.com\r\n"+
-                 "Upgrade: websocket\r\n"+
-                 "Connection: Upgrade\r\n"+
-                 "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+
-                 "Sec-WebSocket-Origin: http://example.com\r\n"+
-                 "Sec-WebSocket-Protocol: other\r\n" +
-                 "Sec-WebSocket-Version: "+WebSocketConnectionRFC6455.VERSION+"\r\n"+
-                 "\r\n").getBytes("ISO-8859-1"));
-        output.flush();
-
-        socket.setSoTimeout(100000);
-        InputStream input = socket.getInputStream();
-
-        lookFor("HTTP/1.1 101 Switching Protocols\r\n",input);
-        skipTo("Sec-WebSocket-Accept: ",input);
-        lookFor("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",input);
-        skipTo("\r\n\r\n",input);
-
-        assertTrue(__serverWebSocket.awaitConnected(1000));
-        assertNotNull(__serverWebSocket.connection);
-
-        __serverWebSocket.getConnection().setMaxBinaryMessageSize(15);
-
-        output.write(0x02);
-        output.write(0x94);
-        output.write(0xff);
-        output.write(0xff);
-        output.write(0xff);
-        output.write(0xff);
-        byte[] bytes="01234567890123456789".getBytes(StringUtil.__ISO_8859_1);
-        for (int i=0;i<bytes.length;i++)
-            output.write(bytes[i]^0xff);
-        output.flush();
-
-        assertEquals(0x80|WebSocketConnectionRFC6455.OP_CLOSE,input.read());
-        assertEquals(19,input.read());
-        int code=(0xff&input.read())*0x100+(0xff&input.read());
-        assertEquals(WebSocketConnectionRFC6455.CLOSE_MESSAGE_TOO_LARGE,code);
-        lookFor("Message size > 15",input);
-    }
-
-    @Test
-    public void testIdle() throws Exception
-    {
-        Socket socket = new Socket("localhost", __connector.getLocalPort());
-        OutputStream output = socket.getOutputStream();
-        output.write(
-                ("GET /chat HTTP/1.1\r\n"+
-                 "Host: server.example.com\r\n"+
-                 "Upgrade: websocket\r\n"+
-                 "Connection: Upgrade\r\n"+
-                 "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+
-                 "Sec-WebSocket-Origin: http://example.com\r\n"+
-                 "Sec-WebSocket-Protocol: onConnect\r\n" +
-                 "Sec-WebSocket-Version: "+WebSocketConnectionRFC6455.VERSION+"\r\n"+
-                 "\r\n").getBytes("ISO-8859-1"));
-        output.flush();
-
-        // Make sure the read times out if there are problems with the implementation
-        socket.setSoTimeout(10000);
-
-        InputStream input = socket.getInputStream();
-
-        lookFor("HTTP/1.1 101 Switching Protocols\r\n",input);
-        skipTo("Sec-WebSocket-Accept: ",input);
-        lookFor("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",input);
-        skipTo("\r\n\r\n",input);
-
-        assertTrue(__serverWebSocket.awaitConnected(1000));
-        assertNotNull(__serverWebSocket.connection);
-
-        assertEquals(0x81,input.read());
-        assertEquals(0x0f,input.read());
-        lookFor("sent on connect",input);
-
-        assertEquals((byte)0x88,(byte)input.read());
-        assertEquals(26,input.read());
-        assertEquals(1000/0x100,input.read());
-        assertEquals(1000%0x100,input.read());
-        lookFor("Idle",input);
-
-        // respond to close
-        output.write(0x88^0xff);
-        output.write(0x80^0xff);
-        output.write(0xff);
-        output.write(0xff);
-        output.write(0xff);
-        output.write(0xff);
-        output.flush();
-
-
-        assertTrue(__serverWebSocket.awaitDisconnected(5000));
-        try
-        {
-            __serverWebSocket.connection.sendMessage("Don't send");
-            assertTrue(false);
-        }
-        catch(IOException e)
-        {
-            assertTrue(true);
-        }
-    }
-
-    @Test
-    public void testTCPClose() throws Exception
-    {
-        Socket socket = new Socket("localhost", __connector.getLocalPort());
-        OutputStream output = socket.getOutputStream();
-        output.write(
-                ("GET /chat HTTP/1.1\r\n"+
-                        "Host: server.example.com\r\n"+
-                        "Upgrade: websocket\r\n"+
-                        "Connection: Upgrade\r\n"+
-                        "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+
-                        "Sec-WebSocket-Origin: http://example.com\r\n"+
-                        "Sec-WebSocket-Protocol: onConnect\r\n" +
-                        "Sec-WebSocket-Version: "+WebSocketConnectionRFC6455.VERSION+"\r\n"+
-                "\r\n").getBytes("ISO-8859-1"));
-        output.flush();
-
-        // Make sure the read times out if there are problems with the implementation
-        socket.setSoTimeout(10000);
-
-        InputStream input = socket.getInputStream();
-
-        lookFor("HTTP/1.1 101 Switching Protocols\r\n",input);
-        skipTo("Sec-WebSocket-Accept: ",input);
-        lookFor("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",input);
-        skipTo("\r\n\r\n",input);
-
-
-        assertTrue(__serverWebSocket.awaitConnected(10000));
-        assertNotNull(__serverWebSocket.connection);
-
-        assertEquals(0x81,input.read());
-        assertEquals(0x0f,input.read());
-        lookFor("sent on connect",input);
-        socket.close();
-
-        assertTrue(__serverWebSocket.awaitDisconnected(10000));
-
-        try
-        {
-            __serverWebSocket.connection.sendMessage("Don't send");
-            assertTrue(false);
-        }
-        catch(IOException e)
-        {
-            assertTrue(true);
-        }
-    }
-
-    @Test
-    public void testTCPHalfClose() throws Exception
-    {
-        Socket socket = new Socket("localhost", __connector.getLocalPort());
-        OutputStream output = socket.getOutputStream();
-        output.write(
-                ("GET /chat HTTP/1.1\r\n"+
-                        "Host: server.example.com\r\n"+
-                        "Upgrade: websocket\r\n"+
-                        "Connection: Upgrade\r\n"+
-                        "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+
-                        "Sec-WebSocket-Origin: http://example.com\r\n"+
-                        "Sec-WebSocket-Protocol: onConnect\r\n" +
-                        "Sec-WebSocket-Version: "+WebSocketConnectionRFC6455.VERSION+"\r\n"+
-                "\r\n").getBytes("ISO-8859-1"));
-        output.flush();
-
-        // Make sure the read times out if there are problems with the implementation
-        socket.setSoTimeout(1000);
-
-        InputStream input = socket.getInputStream();
-
-        lookFor("HTTP/1.1 101 Switching Protocols\r\n",input);
-        skipTo("Sec-WebSocket-Accept: ",input);
-        lookFor("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",input);
-        skipTo("\r\n\r\n",input);
-
-
-        assertTrue(__serverWebSocket.awaitConnected(1000));
-        assertNotNull(__serverWebSocket.connection);
-
-        assertEquals(0x81,input.read());
-        assertEquals(0x0f,input.read());
-        lookFor("sent on connect",input);
-
-        socket.shutdownOutput();
-
-        assertTrue(__serverWebSocket.awaitDisconnected(500));
-
-        assertEquals(0x88,input.read());
-        assertEquals(0x00,input.read());
-        assertEquals(-1,input.read());
-
-        // look for broken pipe
-        try
-        {
-            for (int i=0;i<1000;i++)
-                output.write(0);
-            Assert.fail();
-        }
-        catch(SocketException e)
-        {
-            // expected
-        }
-    }
-
-
-
-    @Test
-    public void testParserAndGenerator() throws Exception
-    {
-        String message = "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF";
-        final AtomicReference<String> received = new AtomicReference<String>();
-        ByteArrayEndPoint endp = new ByteArrayEndPoint(new byte[0],4096);
-
-        WebSocketGeneratorRFC6455 gen = new WebSocketGeneratorRFC6455(new WebSocketBuffers(8096),endp,null);
-
-        byte[] data = message.getBytes(StringUtil.__UTF8);
-        gen.addFrame((byte)0x8,(byte)0x4,data,0,data.length);
-
-        endp = new ByteArrayEndPoint(endp.getOut().asArray(),4096);
-
-        WebSocketParserRFC6455 parser = new WebSocketParserRFC6455(new WebSocketBuffers(8096),endp,new WebSocketParser.FrameHandler()
-        {
-            public void onFrame(byte flags, byte opcode, Buffer buffer)
-            {
-                received.set(buffer.toString());
-            }
-
-            public void close(int code,String message)
-            {
-            }
-
-        },false);
-
-        parser.parseNext();
-
-        assertEquals(message,received.get());
-    }
-
-    @Test
-    public void testParserAndGeneratorMasked() throws Exception
-    {
-        String message = "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF";
-        final AtomicReference<String> received = new AtomicReference<String>();
-        ByteArrayEndPoint endp = new ByteArrayEndPoint(new byte[0],4096);
-
-        MaskGen maskGen = new RandomMaskGen();
-
-        WebSocketGeneratorRFC6455 gen = new WebSocketGeneratorRFC6455(new WebSocketBuffers(8096),endp,maskGen);
-        byte[] data = message.getBytes(StringUtil.__UTF8);
-        gen.addFrame((byte)0x8,(byte)0x1,data,0,data.length);
-
-        endp = new ByteArrayEndPoint(endp.getOut().asArray(),4096);
-
-        WebSocketParserRFC6455 parser = new WebSocketParserRFC6455(new WebSocketBuffers(8096),endp,new WebSocketParser.FrameHandler()
-        {
-            public void onFrame(byte flags, byte opcode, Buffer buffer)
-            {
-                received.set(buffer.toString());
-            }
-
-            public void close(int code,String message)
-            {
-            }
-        },true);
-
-        parser.parseNext();
-
-        assertEquals(message,received.get());
-    }
-
-
-    private void lookFor(String string,InputStream in)
-        throws IOException
-    {
-        String orig=string;
-        Utf8StringBuilder scanned=new Utf8StringBuilder();
-        try
-        {
-            while(true)
-            {
-                int b = in.read();
-                if (b<0)
-                    throw new EOFException();
-                scanned.append((byte)b);
-                assertEquals("looking for\""+orig+"\" in '"+scanned+"'",(int)string.charAt(0),b);
-                if (string.length()==1)
-                    break;
-                string=string.substring(1);
-            }
-        }
-        catch(IOException e)
-        {
-            System.err.println("IOE while looking for \""+orig+"\" in '"+scanned+"'");
-            throw e;
-        }
-    }
-
-    private void skipTo(String string,InputStream in)
-    throws IOException
-    {
-        int state=0;
-
-        while(true)
-        {
-            int b = in.read();
-            if (b<0)
-                throw new EOFException();
-
-            if (b==string.charAt(state))
-            {
-                state++;
-                if (state==string.length())
-                    break;
-            }
-            else
-                state=0;
-        }
-    }
-
-
-    private static class TestWebSocket implements WebSocket.OnFrame, WebSocket.OnBinaryMessage, WebSocket.OnTextMessage
-    {
-        protected boolean _latch;
-        boolean _onConnect=false;
-        boolean _echo=true;
-        boolean _aggregate=false;
-        private final CountDownLatch connected = new CountDownLatch(1);
-        private final CountDownLatch disconnected = new CountDownLatch(1);
-        private volatile FrameConnection connection;
-
-        public FrameConnection getConnection()
-        {
-            return connection;
-        }
-
-        public void onHandshake(FrameConnection connection)
-        {
-            this.connection = connection;
-        }
-
-        public void onOpen(Connection connection)
-        {
-            if (_onConnect)
-            {
-                try
-                {
-                    connection.sendMessage("sent on connect");
-                }
-                catch(IOException e)
-                {
-                    e.printStackTrace();
-                }
-            }
-            connected.countDown();
-        }
-
-        private boolean awaitConnected(long time) throws InterruptedException
-        {
-            return connected.await(time, TimeUnit.MILLISECONDS);
-        }
-
-        private boolean awaitDisconnected(long time) throws InterruptedException
-        {
-            return disconnected.await(time, TimeUnit.MILLISECONDS);
-        }
-
-        public void onClose(int code,String message)
-        {
-            disconnected.countDown();
-        }
-
-        public boolean onFrame(byte flags, byte opcode, byte[] data, int offset, int length)
-        {
-            if (_echo)
-            {
-                switch(opcode)
-                {
-                    case WebSocketConnectionRFC6455.OP_CLOSE:
-                    case WebSocketConnectionRFC6455.OP_PING:
-                    case WebSocketConnectionRFC6455.OP_PONG:
-                        break;
-
-                    default:
-                        try
-                        {
-                            connection.sendFrame(flags,opcode,data,offset,length);
-                        }
-                        catch (IOException e)
-                        {
-                            e.printStackTrace();
-                        }
-                }
-            }
-            return false;
-        }
-
-        public void onMessage(byte[] data, int offset, int length)
-        {
-            if (_aggregate)
-            {
-                try
-                {
-                    connection.sendMessage(data,offset,length);
-                }
-                catch (IOException e)
-                {
-                    e.printStackTrace();
-                }
-            }
-        }
-
-        public void onMessage(String data)
-        {
-            __textCount.incrementAndGet();
-            if (_latch)
-            {
-                try
-                {
-                    __latch.await();
-                }
-                catch(Exception e)
-                {
-                    e.printStackTrace();
-                }
-            }
-
-            if (_aggregate)
-            {
-                try
-                {
-                    connection.sendMessage(data);
-                }
-                catch (IOException e)
-                {
-                    e.printStackTrace();
-                }
-            }
-        }
-
-    }
-}
diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketMinVersionTest.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketMinVersionTest.java
deleted file mode 100644
index f54eda0..0000000
--- a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketMinVersionTest.java
+++ /dev/null
@@ -1,116 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.websocket;
-
-import static org.hamcrest.Matchers.*;
-
-import java.net.URI;
-import java.util.concurrent.TimeUnit;
-
-import org.eclipse.jetty.server.Connector;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.servlet.ServletContextHandler;
-import org.eclipse.jetty.servlet.ServletHolder;
-import org.eclipse.jetty.websocket.helper.CaptureSocket;
-import org.eclipse.jetty.websocket.helper.SafariD00;
-import org.eclipse.jetty.websocket.helper.WebSocketCaptureServlet;
-import org.junit.After;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.Test;
-
-public class WebSocketMinVersionTest
-{
-    private Server server;
-    private WebSocketCaptureServlet servlet;
-    private URI serverUri;
-
-    @BeforeClass
-    public static void initLogging()
-    {
-        // Configure Logging
-        // System.setProperty("org.eclipse.jetty.util.log.class",StdErrLog.class.getName());
-        System.setProperty("org.eclipse.jetty.websocket.helper.LEVEL","DEBUG");
-    }
-
-    @Before
-    public void startServer() throws Exception
-    {
-        // Configure Server
-        server = new Server(0);
-
-        ServletContextHandler context = new ServletContextHandler();
-        context.setContextPath("/");
-        server.setHandler(context);
-
-        // Serve capture servlet
-        servlet = new WebSocketCaptureServlet();
-        ServletHolder holder = new ServletHolder(servlet);
-        holder.setInitParameter("minVersion","8");
-        context.addServlet(holder,"/");
-
-        // Start Server
-        server.start();
-
-        Connector conn = server.getConnectors()[0];
-        String host = conn.getHost();
-        if (host == null)
-        {
-            host = "localhost";
-        }
-        int port = conn.getLocalPort();
-        serverUri = new URI(String.format("ws://%s:%d/",host,port));
-        // System.out.printf("Server URI: %s%n",serverUri);
-    }
-
-    @Test
-    public void testAttemptUpgrade() throws Exception
-    {
-        SafariD00 safari = new SafariD00(serverUri);
-
-        try
-        {
-            safari.connect();
-            safari.issueHandshake();
-            Assert.fail("Expected upgrade failure");
-        }
-        catch(IllegalStateException e) {
-            String respHeader = e.getMessage();
-            Assert.assertThat("Response Header", respHeader, containsString("HTTP/1.1 400 Unsupported websocket version specification"));
-        }
-        finally
-        {
-            // System.out.println("Closing client socket");
-            safari.disconnect();
-        }
-    }
-
-    public static void threadSleep(int dur, TimeUnit unit) throws InterruptedException
-    {
-        long ms = TimeUnit.MILLISECONDS.convert(dur,unit);
-        Thread.sleep(ms);
-    }
-
-    @After
-    public void stopServer() throws Exception
-    {
-        server.stop();
-    }
-}
diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketOverSSLTest.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketOverSSLTest.java
deleted file mode 100644
index a7862a1..0000000
--- a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketOverSSLTest.java
+++ /dev/null
@@ -1,216 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.websocket;
-
-import java.io.IOException;
-import java.net.URI;
-import java.util.Arrays;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import javax.servlet.http.HttpServletRequest;
-
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.ssl.SslSelectChannelConnector;
-import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
-import org.eclipse.jetty.util.ssl.SslContextFactory;
-import org.eclipse.jetty.util.thread.QueuedThreadPool;
-import org.junit.After;
-import org.junit.Assert;
-import org.junit.Test;
-
-public class WebSocketOverSSLTest
-{
-    private Server _server;
-    private int _port;
-    private QueuedThreadPool _threadPool;
-    private WebSocketClientFactory _wsFactory;
-    private WebSocket.Connection _connection;
-
-    private void startServer(final WebSocket webSocket) throws Exception
-    {
-        _server = new Server();
-        SslSelectChannelConnector connector = new SslSelectChannelConnector();
-        _server.addConnector(connector);
-        SslContextFactory cf = connector.getSslContextFactory();
-        cf.setKeyStorePath(MavenTestingUtils.getTestResourceFile("keystore").getAbsolutePath());
-        cf.setKeyStorePassword("storepwd");
-        cf.setKeyManagerPassword("keypwd");
-        _server.setHandler(new WebSocketHandler()
-        {
-            public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol)
-            {
-                return webSocket;
-            }
-        });
-        _server.start();
-        _port = connector.getLocalPort();
-    }
-
-    private void startClient(final WebSocket webSocket) throws Exception
-    {
-        Assert.assertTrue(_server.isStarted());
-
-        _threadPool = new QueuedThreadPool();
-        _threadPool.setName("wsc-" + _threadPool.getName());
-        _threadPool.start();
-
-        _wsFactory = new WebSocketClientFactory(_threadPool, new ZeroMaskGen());
-        SslContextFactory cf = _wsFactory.getSslContextFactory();
-        cf.setKeyStorePath(MavenTestingUtils.getTestResourceFile("keystore").getAbsolutePath());
-        cf.setKeyStorePassword("storepwd");
-        cf.setKeyManagerPassword("keypwd");
-        _wsFactory.start();
-
-        WebSocketClient client = new WebSocketClient(_wsFactory);
-        _connection = client.open(new URI("wss://localhost:" + _port), webSocket).get(5, TimeUnit.SECONDS);
-    }
-
-    @After
-    public void destroy() throws Exception
-    {
-        if (_connection != null)
-            _connection.close();
-
-        if (_wsFactory != null)
-            _wsFactory.stop();
-
-        if (_threadPool != null)
-            _threadPool.stop();
-
-        if (_server != null)
-        {
-            _server.stop();
-            _server.join();
-        }
-    }
-
-    @Test
-    public void testWebSocketOverSSL() throws Exception
-    {
-        final String message = "message";
-        final CountDownLatch serverLatch = new CountDownLatch(1);
-        startServer(new WebSocket.OnTextMessage()
-        {
-            private Connection connection;
-
-            public void onOpen(Connection connection)
-            {
-                this.connection = connection;
-            }
-
-            public void onMessage(String data)
-            {
-                try
-                {
-                    Assert.assertEquals(message, data);
-                    connection.sendMessage(data);
-                    serverLatch.countDown();
-                }
-                catch (IOException x)
-                {
-                    x.printStackTrace();
-                }
-            }
-
-            public void onClose(int closeCode, String message)
-            {
-            }
-        });
-        final CountDownLatch clientLatch = new CountDownLatch(1);
-        startClient(new WebSocket.OnTextMessage()
-        {
-            public void onOpen(Connection connection)
-            {
-            }
-
-            public void onMessage(String data)
-            {
-                Assert.assertEquals(message, data);
-                clientLatch.countDown();
-            }
-
-            public void onClose(int closeCode, String message)
-            {
-            }
-        });
-        _connection.sendMessage(message);
-
-        Assert.assertTrue(serverLatch.await(5, TimeUnit.SECONDS));
-        Assert.assertTrue(clientLatch.await(5, TimeUnit.SECONDS));
-    }
-
-    @Test
-    public void testManyMessages() throws Exception
-    {
-        startServer(new WebSocket.OnTextMessage()
-        {
-            private Connection connection;
-
-            public void onOpen(Connection connection)
-            {
-                this.connection = connection;
-            }
-
-            public void onMessage(String data)
-            {
-                try
-                {
-                    connection.sendMessage(data);
-                }
-                catch (IOException x)
-                {
-                    x.printStackTrace();
-                }
-            }
-
-            public void onClose(int closeCode, String message)
-            {
-            }
-        });
-        int count = 1000;
-        final CountDownLatch clientLatch = new CountDownLatch(count);
-        startClient(new WebSocket.OnTextMessage()
-        {
-            public void onOpen(Connection connection)
-            {
-            }
-
-            public void onMessage(String data)
-            {
-                clientLatch.countDown();
-            }
-
-            public void onClose(int closeCode, String message)
-            {
-            }
-        });
-
-        char[] chars = new char[256];
-        Arrays.fill(chars, 'x');
-        String message = new String(chars);
-        for (int i = 0; i < count; ++i)
-            _connection.sendMessage(message);
-
-        Assert.assertTrue(clientLatch.await(20, TimeUnit.SECONDS));
-
-        // While messages may have all arrived, the SSL close alert
-        // may be in the way so give some time for it to be processed.
-        TimeUnit.SECONDS.sleep(1);
-    }
-}
diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketParserD00Test.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketParserD00Test.java
deleted file mode 100644
index b1c97b0..0000000
--- a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketParserD00Test.java
+++ /dev/null
@@ -1,159 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.websocket;
-
-import static org.hamcrest.Matchers.greaterThan;
-import static org.junit.Assert.*;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import org.eclipse.jetty.http.HttpHeaderValues;
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.BufferCache.CachedBuffer;
-import org.eclipse.jetty.io.ByteArrayBuffer;
-import org.eclipse.jetty.io.ByteArrayEndPoint;
-import org.eclipse.jetty.util.StringUtil;
-import org.junit.Before;
-import org.junit.Test;
-
-public class WebSocketParserD00Test
-{
-    private ByteArrayBuffer _in;
-    private Handler _handler;
-    private WebSocketParser _parser;
-
-    @Before
-    public void setUp() throws Exception
-    {
-        WebSocketBuffers buffers = new WebSocketBuffers(1024);
-        ByteArrayEndPoint endPoint = new ByteArrayEndPoint();
-        _handler = new Handler();
-        _parser=new WebSocketParserD00(buffers, endPoint,_handler);
-        _in = new ByteArrayBuffer(2048);
-        endPoint.setIn(_in);
-    }
-
-    @Test
-    public void testCache() throws Exception
-    {
-        assertEquals(HttpHeaderValues.UPGRADE_ORDINAL ,((CachedBuffer)HttpHeaderValues.CACHE.lookup("Upgrade")).getOrdinal());
-    }
-
-    @Test
-    public void testOneUtf8() throws Exception
-    {
-        _in.put((byte)0x00);
-        _in.put("Hello World".getBytes(StringUtil.__UTF8));
-        _in.put((byte)0xff);
-
-        int filled =_parser.parseNext();
-
-        assertThat(filled,greaterThan(0));
-        assertEquals("Hello World",_handler._data.get(0));
-        assertTrue(_parser.isBufferEmpty());
-        assertTrue(_parser.getBuffer()==null);
-    }
-
-    @Test
-    public void testTwoUtf8() throws Exception
-    {
-        _in.put((byte)0x00);
-        _in.put("Hello World".getBytes(StringUtil.__UTF8));
-        _in.put((byte)0xff);
-        _in.put((byte)0x00);
-        _in.put("Hell\uFF4F W\uFF4Frld".getBytes(StringUtil.__UTF8));
-        _in.put((byte)0xff);
-
-        int filled =_parser.parseNext();
-
-        assertThat(filled,greaterThan(0));
-        assertEquals("Hello World",_handler._data.get(0));
-        assertFalse(_parser.isBufferEmpty());
-        assertFalse(_parser.getBuffer()==null);
-
-        filled =_parser.parseNext();
-
-        assertThat(filled,greaterThan(0));
-        assertEquals("Hell\uFF4f W\uFF4Frld",_handler._data.get(1));
-        assertTrue(_parser.isBufferEmpty());
-        assertTrue(_parser.getBuffer()==null);
-    }
-
-    @Test
-    public void testOneBinary() throws Exception
-    {
-        _in.put((byte)0x80);
-        _in.put((byte)11);
-        _in.put("Hello World".getBytes(StringUtil.__UTF8));
-
-        int filled =_parser.parseNext();
-
-        assertThat(filled,greaterThan(0));
-        assertEquals("Hello World",_handler._data.get(0));
-        assertTrue(_parser.isBufferEmpty());
-        assertTrue(_parser.getBuffer()==null);
-    }
-
-    @Test
-    public void testTwoBinary() throws Exception
-    {
-        _in.put((byte)0x80);
-        _in.put((byte)11);
-        _in.put("Hello World".getBytes(StringUtil.__UTF8));
-
-        byte[] data = new byte[150];
-        for (int i=0;i<data.length;i++)
-            data[i]=(byte)('0'+(i%10));
-
-        _in.put((byte)0x80);
-        _in.put((byte)(0x80|(data.length>>7)));
-        _in.put((byte)(data.length&0x7f));
-        _in.put(data);
-
-        int filled =_parser.parseNext();
-        assertThat(filled,greaterThan(0));
-        assertEquals("Hello World",_handler._data.get(0));
-        assertFalse(_parser.isBufferEmpty());
-        assertFalse(_parser.getBuffer()==null);
-
-        filled =_parser.parseNext();
-        assertThat(filled,greaterThan(0));
-        String got=_handler._data.get(1);
-        assertEquals(data.length,got.length());
-        assertTrue(got.startsWith("012345678901234567890123"));
-        assertTrue(_parser.isBufferEmpty());
-        assertTrue(_parser.getBuffer()==null);
-    }
-
-
-    private class Handler implements WebSocketParser.FrameHandler
-    {
-        public List<String> _data = new ArrayList<String>();
-
-        public void onFrame(byte flags, byte opcode, Buffer buffer)
-        {
-            _data.add(buffer.toString(StringUtil.__UTF8));
-        }
-
-        public void close(int code,String message)
-        {
-        }
-    }
-}
diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketParserD06Test.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketParserD06Test.java
deleted file mode 100644
index a94f826..0000000
--- a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketParserD06Test.java
+++ /dev/null
@@ -1,330 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.websocket;
-
-import static org.junit.Assert.*;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import org.eclipse.jetty.http.HttpHeaderValues;
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.BufferCache.CachedBuffer;
-import org.eclipse.jetty.io.ByteArrayBuffer;
-import org.eclipse.jetty.io.ByteArrayEndPoint;
-import org.eclipse.jetty.util.StringUtil;
-import org.eclipse.jetty.util.TypeUtil;
-import org.eclipse.jetty.util.Utf8StringBuilder;
-import org.junit.Before;
-import org.junit.Test;
-
-public class WebSocketParserD06Test
-{
-    private MaskedByteArrayBuffer _in;
-    private Handler _handler;
-    private WebSocketParser _parser;
-    private byte[] _mask = new byte[] {(byte)0x00,(byte)0xF0,(byte)0x0F,(byte)0xFF};
-    private int _m;
-
-    class MaskedByteArrayBuffer extends ByteArrayBuffer
-    {
-        MaskedByteArrayBuffer()
-        {
-            super(4096);
-        }
-        
-        public void sendMask()
-        {
-            super.poke(putIndex(),_mask,0,4);
-            super.setPutIndex(putIndex()+4);
-            _m=0;
-        }
-
-        @Override
-        public int put(Buffer src)
-        {
-            return put(src.asArray(),0,src.length());
-        }
-
-        @Override
-        public void put(byte b)
-        {
-            super.put((byte)(b^_mask[_m++%4]));
-        }
-
-        @Override
-        public int put(byte[] b, int offset, int length)
-        {
-            byte[] mb = new byte[b.length];
-            final int end=offset+length;
-            for (int i=offset;i<end;i++)
-            {
-                mb[i]=(byte)(b[i]^_mask[_m++%4]);
-            }
-            return super.put(mb,offset,length);
-        }
-
-        @Override
-        public int put(byte[] b)
-        {
-            return put(b,0,b.length);
-        }
-        
-    };
-    
-    
-    @Before
-    public void setUp() throws Exception
-    {
-        WebSocketBuffers buffers = new WebSocketBuffers(1024);
-        ByteArrayEndPoint endPoint = new ByteArrayEndPoint();
-        endPoint.setNonBlocking(true);
-        _handler = new Handler();
-        _parser=new WebSocketParserD06(buffers, endPoint,_handler,true);
-        _in = new MaskedByteArrayBuffer();
-        
-        endPoint.setIn(_in);
-    }
-
-    @Test
-    public void testCache() throws Exception
-    {
-        assertEquals(HttpHeaderValues.UPGRADE_ORDINAL ,((CachedBuffer)HttpHeaderValues.CACHE.lookup("Upgrade")).getOrdinal());
-    }
-
-    @Test
-    public void testFlagsOppcode() throws Exception
-    {
-        _in.sendMask();
-        _in.put((byte)0xff);
-        _in.put((byte)0);
-
-        int filled =_parser.parseNext();
-
-        assertEquals(7,filled);
-        assertEquals(0xf,_handler._flags);
-        assertEquals(0xf,_handler._opcode);
-        assertTrue(_parser.isBufferEmpty());
-        assertTrue(_parser.getBuffer()==null);
-    }
-    
-    @Test
-    public void testShortText() throws Exception
-    {
-        _in.sendMask();
-        _in.put((byte)0x84);
-        _in.put((byte)11);
-        _in.put("Hello World".getBytes(StringUtil.__UTF8));
-        System.err.println("tosend="+TypeUtil.toHexString(_in.asArray()));
-
-        int filled =_parser.parseNext();
-
-        assertEquals(18,filled);
-        assertEquals("Hello World",_handler._data.get(0));
-        assertEquals(0x8,_handler._flags);
-        assertEquals(0x4,_handler._opcode);
-        assertTrue(_parser.isBufferEmpty());
-        assertTrue(_parser.getBuffer()==null);
-    }
-    
-    @Test
-    public void testShortUtf8() throws Exception
-    {
-        String string = "Hell\uFF4f W\uFF4Frld";
-        byte[] bytes = string.getBytes("UTF-8");
-
-        _in.sendMask();
-        _in.put((byte)0x84);
-        _in.put((byte)bytes.length);
-        _in.put(bytes);
-
-        int filled =_parser.parseNext();
-
-        assertEquals(bytes.length+7,filled);
-        assertEquals(string,_handler._data.get(0));
-        assertEquals(0x8,_handler._flags);
-        assertEquals(0x4,_handler._opcode);
-        assertTrue(_parser.isBufferEmpty());
-        assertTrue(_parser.getBuffer()==null);
-    }
-    
-    @Test
-    public void testMediumText() throws Exception
-    {
-        String string = "Hell\uFF4f Medium W\uFF4Frld ";
-        for (int i=0;i<4;i++)
-            string = string+string;
-        string += ". The end.";
-        
-        byte[] bytes = string.getBytes(StringUtil.__UTF8);
-        
-        _in.sendMask();
-        _in.put((byte)0x84);
-        _in.put((byte)0x7E);
-        _in.put((byte)(bytes.length>>8));
-        _in.put((byte)(bytes.length&0xff));
-        _in.put(bytes);
-
-        int filled =_parser.parseNext();
-
-        assertEquals(bytes.length+9,filled);
-        assertEquals(string,_handler._data.get(0));
-        assertEquals(0x8,_handler._flags);
-        assertEquals(0x4,_handler._opcode);
-        assertTrue(_parser.isBufferEmpty());
-        assertTrue(_parser.getBuffer()==null);
-    }
-    
-    @Test
-    public void testLongText() throws Exception
-    {
-        WebSocketBuffers buffers = new WebSocketBuffers(0x20000);
-        ByteArrayEndPoint endPoint = new ByteArrayEndPoint();
-        WebSocketParser parser=new WebSocketParserD06(buffers, endPoint,_handler,false);
-        ByteArrayBuffer in = new ByteArrayBuffer(0x20000);
-        endPoint.setIn(in);
-        
-        String string = "Hell\uFF4f Big W\uFF4Frld ";
-        for (int i=0;i<12;i++)
-            string = string+string;
-        string += ". The end.";
-        
-        byte[] bytes = string.getBytes("UTF-8");
-
-        _in.sendMask();
-        in.put((byte)0x84);
-        in.put((byte)0x7F);
-        in.put((byte)0x00);
-        in.put((byte)0x00);
-        in.put((byte)0x00);
-        in.put((byte)0x00);
-        in.put((byte)0x00);
-        in.put((byte)(bytes.length>>16));
-        in.put((byte)((bytes.length>>8)&0xff));
-        in.put((byte)(bytes.length&0xff));
-        in.put(bytes);
-
-        int filled =parser.parseNext();
-
-        assertEquals(bytes.length+11,filled);
-        assertEquals(string,_handler._data.get(0));
-        assertTrue(parser.isBufferEmpty());
-        assertTrue(parser.getBuffer()==null);
-    }
-
-    @Test
-    public void testShortFragmentTest() throws Exception
-    {
-        _in.sendMask();
-        _in.put((byte)0x04);
-        _in.put((byte)0x06);
-        _in.put("Hello ".getBytes(StringUtil.__UTF8));
-        _in.sendMask();
-        _in.put((byte)0x80);
-        _in.put((byte)0x05);
-        _in.put("World".getBytes(StringUtil.__UTF8));
-
-        int filled =_parser.parseNext();
-
-        assertEquals(24,filled);
-        assertEquals(0,_handler._data.size());
-        assertFalse(_parser.isBufferEmpty());
-        assertFalse(_parser.getBuffer()==null);
-
-        filled =_parser.parseNext();
-
-        assertEquals(1,filled);
-        assertEquals("Hello World",_handler._data.get(0));
-        assertTrue(_parser.isBufferEmpty());
-        assertTrue(_parser.getBuffer()==null);
-    }
-
-    @Test
-    public void testFrameTooLarge() throws Exception
-    {
-        // Buffers are only 1024, so this frame is too large
-        
-        _in.sendMask();
-        _in.put((byte)0x84);
-        _in.put((byte)0x7E);
-        _in.put((byte)(2048>>8));
-        _in.put((byte)(2048&0xff));
-
-        int filled =_parser.parseNext();
-
-        assertEquals(9,filled);
-       
-        assertEquals(WebSocketConnectionD06.CLOSE_LARGE,_handler._code);
-        for (int i=0;i<2048;i++)
-            _in.put((byte)'a');
-        filled =_parser.parseNext();
-
-        assertEquals(2048,filled);
-        assertEquals(0,_handler._data.size());
-        assertEquals(0,_handler._utf8.length());
-        
-        _handler._code=0;
-        _handler._message=null;
-
-        _in.sendMask();
-        _in.put((byte)0x84);
-        _in.put((byte)0x7E);
-        _in.put((byte)(1024>>8));
-        _in.put((byte)(1024&0xff));
-        for (int i=0;i<1024;i++)
-            _in.put((byte)'a');
-
-        filled =_parser.parseNext();
-        assertEquals(1024+8+1,filled);
-        assertEquals(1,_handler._data.size());
-        assertEquals(1024,_handler._data.get(0).length());
-    }
-
-    private class Handler implements WebSocketParser.FrameHandler
-    {
-        Utf8StringBuilder _utf8 = new Utf8StringBuilder();
-        public List<String> _data = new ArrayList<String>();
-        private byte _flags;
-        private byte _opcode;
-        int _code;
-        String _message;
-
-        public void onFrame(byte flags, byte opcode, Buffer buffer)
-        {
-            _flags=flags;
-            _opcode=opcode;
-            if ((flags&0x8)==0)
-                _utf8.append(buffer.array(),buffer.getIndex(),buffer.length());
-            else if (_utf8.length()==0)
-                _data.add(buffer.toString("utf-8"));
-            else
-            {
-                _utf8.append(buffer.array(),buffer.getIndex(),buffer.length());
-                _data.add(_utf8.toString());
-                _utf8.reset();
-            }
-        }
-
-        public void close(int code,String message)
-        {
-            _code=code;
-            _message=message;
-        }
-    }
-}
diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketParserD08Test.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketParserD08Test.java
deleted file mode 100644
index 225e84d..0000000
--- a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketParserD08Test.java
+++ /dev/null
@@ -1,370 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.websocket;
-
-import static org.junit.Assert.*;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import org.eclipse.jetty.http.HttpHeaderValues;
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.BufferCache.CachedBuffer;
-import org.eclipse.jetty.io.ByteArrayBuffer;
-import org.eclipse.jetty.io.ByteArrayEndPoint;
-import org.eclipse.jetty.util.StringUtil;
-import org.eclipse.jetty.util.Utf8StringBuilder;
-import org.junit.Before;
-import org.junit.Test;
-
-public class WebSocketParserD08Test
-{
-    private MaskedByteArrayBuffer _in;
-    private Handler _handler;
-    private WebSocketParserD08 _parser;
-    private byte[] _mask = new byte[] {(byte)0x00,(byte)0xF0,(byte)0x0F,(byte)0xFF};
-    private int _m;
-
-    class MaskedByteArrayBuffer extends ByteArrayBuffer
-    {
-        MaskedByteArrayBuffer()
-        {
-            super(4096);
-        }
-        
-        public void sendMask()
-        {
-            super.poke(putIndex(),_mask,0,4);
-            super.setPutIndex(putIndex()+4);
-            _m=0;
-        }
-
-        @Override
-        public int put(Buffer src)
-        {
-            return put(src.asArray(),0,src.length());
-        }
-
-        public void putUnmasked(byte b)
-        {
-            super.put(b);
-        }
-        
-        @Override
-        public void put(byte b)
-        {
-            super.put((byte)(b^_mask[_m++%4]));
-        }
-
-        @Override
-        public int put(byte[] b, int offset, int length)
-        {
-            byte[] mb = new byte[b.length];
-            final int end=offset+length;
-            for (int i=offset;i<end;i++)
-            {
-                mb[i]=(byte)(b[i]^_mask[_m++%4]);
-            }
-            return super.put(mb,offset,length);
-        }
-
-        @Override
-        public int put(byte[] b)
-        {
-            return put(b,0,b.length);
-        }
-        
-    };
-    
-    
-    @Before
-    public void setUp() throws Exception
-    {
-        WebSocketBuffers buffers = new WebSocketBuffers(1024);
-        ByteArrayEndPoint endPoint = new ByteArrayEndPoint();
-        endPoint.setNonBlocking(true);
-        _handler = new Handler();
-        _parser=new WebSocketParserD08(buffers, endPoint,_handler,true);
-        _parser.setFakeFragments(false);
-        _in = new MaskedByteArrayBuffer();
-        
-        endPoint.setIn(_in);
-    }
-
-    @Test
-    public void testCache() throws Exception
-    {
-        assertEquals(HttpHeaderValues.UPGRADE_ORDINAL ,((CachedBuffer)HttpHeaderValues.CACHE.lookup("Upgrade")).getOrdinal());
-    }
-
-    @Test
-    public void testFlagsOppcode() throws Exception
-    {
-        _in.putUnmasked((byte)0xff);
-        _in.putUnmasked((byte)0x80);
-        _in.sendMask();
-
-        int progress =_parser.parseNext();
-
-        assertTrue(progress>0);
-        assertEquals(0xf,_handler._flags);
-        assertEquals(0xf,_handler._opcode);
-        assertTrue(_parser.isBufferEmpty());
-        _parser.returnBuffer();
-        assertTrue(_parser.getBuffer()==null);
-    }
-
-    @Test
-    public void testShortText() throws Exception
-    {
-        _in.putUnmasked((byte)0x81);
-        _in.putUnmasked((byte)(0x80|11));
-        _in.sendMask();
-        _in.put("Hello World".getBytes(StringUtil.__UTF8));
-        // System.err.println("tosend="+TypeUtil.toHexString(_in.asArray()));
-
-        int progress =_parser.parseNext();
-
-        assertTrue(progress>0);
-        assertEquals("Hello World",_handler._data.get(0));
-        assertEquals(0x8,_handler._flags);
-        assertEquals(0x1,_handler._opcode);
-        assertTrue(_parser.isBufferEmpty());
-        _parser.returnBuffer();
-        assertTrue(_parser.getBuffer()==null);
-    }
-
-    @Test
-    public void testShortUtf8() throws Exception
-    {
-        String string = "Hell\uFF4f W\uFF4Frld";
-        byte[] bytes = string.getBytes("UTF-8");
-
-        _in.putUnmasked((byte)0x81);
-        _in.putUnmasked((byte)(0x80|bytes.length));
-        _in.sendMask();
-        _in.put(bytes);
-
-        int progress =_parser.parseNext();
-
-        assertTrue(progress>0);
-        assertEquals(string,_handler._data.get(0));
-        assertEquals(0x8,_handler._flags);
-        assertEquals(0x1,_handler._opcode);
-        _parser.returnBuffer();
-        assertTrue(_parser.isBufferEmpty());
-        assertTrue(_parser.getBuffer()==null);
-    }
-
-    @Test
-    public void testMediumText() throws Exception
-    {
-        String string = "Hell\uFF4f Medium W\uFF4Frld ";
-        for (int i=0;i<4;i++)
-            string = string+string;
-        string += ". The end.";
-
-        byte[] bytes = string.getBytes(StringUtil.__UTF8);
-
-        _in.putUnmasked((byte)0x81);
-        _in.putUnmasked((byte)(0x80|0x7E));
-        _in.putUnmasked((byte)(bytes.length>>8));
-        _in.putUnmasked((byte)(bytes.length&0xff));
-        _in.sendMask();
-        _in.put(bytes);
-
-        int progress =_parser.parseNext();
-
-        assertTrue(progress>0);
-        assertEquals(string,_handler._data.get(0));
-        assertEquals(0x8,_handler._flags);
-        assertEquals(0x1,_handler._opcode);
-        _parser.returnBuffer();
-        assertTrue(_parser.isBufferEmpty());
-        assertTrue(_parser.getBuffer()==null);
-    }
-
-    @Test
-    public void testLongText() throws Exception
-    {
-        WebSocketBuffers buffers = new WebSocketBuffers(0x20000);
-        ByteArrayEndPoint endPoint = new ByteArrayEndPoint();
-        WebSocketParserD08 parser=new WebSocketParserD08(buffers, endPoint,_handler,false);
-        ByteArrayBuffer in = new ByteArrayBuffer(0x20000);
-        endPoint.setIn(in);
-
-        String string = "Hell\uFF4f Big W\uFF4Frld ";
-        for (int i=0;i<12;i++)
-            string = string+string;
-        string += ". The end.";
-
-        byte[] bytes = string.getBytes("UTF-8");
-
-        _in.sendMask();
-        in.put((byte)0x84);
-        in.put((byte)0x7F);
-        in.put((byte)0x00);
-        in.put((byte)0x00);
-        in.put((byte)0x00);
-        in.put((byte)0x00);
-        in.put((byte)0x00);
-        in.put((byte)(bytes.length>>16));
-        in.put((byte)((bytes.length>>8)&0xff));
-        in.put((byte)(bytes.length&0xff));
-        in.put(bytes);
-
-        int progress =parser.parseNext();
-        parser.returnBuffer();
-
-        assertTrue(progress>0);
-        assertEquals(string,_handler._data.get(0));
-        assertTrue(parser.isBufferEmpty());
-        assertTrue(parser.getBuffer()==null);
-    }
-
-    @Test
-    public void testShortFragmentTest() throws Exception
-    {
-        _in.putUnmasked((byte)0x01);
-        _in.putUnmasked((byte)0x86);
-        _in.sendMask();
-        _in.put("Hello ".getBytes(StringUtil.__UTF8));
-        _in.putUnmasked((byte)0x80);
-        _in.putUnmasked((byte)0x85);
-        _in.sendMask();
-        _in.put("World".getBytes(StringUtil.__UTF8));
-
-        int progress =_parser.parseNext();
-
-        assertTrue(progress>0);
-        assertEquals(0,_handler._data.size());
-        assertFalse(_parser.isBufferEmpty());
-        assertFalse(_parser.getBuffer()==null);
-
-        progress =_parser.parseNext();
-        _parser.returnBuffer();
-
-        assertTrue(progress>0);
-        assertEquals("Hello World",_handler._data.get(0));
-        assertTrue(_parser.isBufferEmpty());
-        assertTrue(_parser.getBuffer()==null);
-    }
-
-    @Test
-    public void testFrameTooLarge() throws Exception
-    {
-        // Buffers are only 1024, so this frame is too large
-        _parser.setFakeFragments(false);
-        
-        _in.putUnmasked((byte)0x81);
-        _in.putUnmasked((byte)(0x80|0x7E));
-        _in.putUnmasked((byte)(2048>>8));
-        _in.putUnmasked((byte)(2048&0xff));
-        _in.sendMask();
-
-        int progress =_parser.parseNext();
-
-        assertTrue(progress>0);
-       
-        assertEquals(WebSocketConnectionD08.CLOSE_BADDATA,_handler._code);
-        for (int i=0;i<2048;i++)
-            _in.put((byte)'a');
-        progress =_parser.parseNext();
-
-        assertTrue(progress>0);
-        assertEquals(0,_handler._data.size());
-        assertEquals(0,_handler._utf8.length());
-        
-        _handler._code=0;
-
-        _in.putUnmasked((byte)0x81);
-        _in.putUnmasked((byte)0xFE);
-        _in.putUnmasked((byte)(1024>>8));
-        _in.putUnmasked((byte)(1024&0xff));
-        _in.sendMask();
-        for (int i=0;i<1024;i++)
-            _in.put((byte)'a');
-
-        progress =_parser.parseNext();
-        assertTrue(progress>0);
-        assertEquals(0,_handler._data.size());
-    }
-
-    @Test
-    public void testFakeFragement() throws Exception
-    {
-        // Buffers are only 1024, so this frame will be fake fragmented
-        _parser.setFakeFragments(true);
-        
-        _in.putUnmasked((byte)0x81);
-        _in.putUnmasked((byte)(0x80|0x7E));
-        _in.putUnmasked((byte)(2048>>8));
-        _in.putUnmasked((byte)(2048&0xff));
-        _in.sendMask();
-        for (int i=0;i<2048;i++)
-            _in.put((byte)('a'+i%26));
-        
-        int progress =_parser.parseNext();
-        assertTrue(progress>0);
-
-        assertEquals(2,_handler._frames);
-        assertEquals(WebSocketConnectionD08.OP_CONTINUATION,_handler._opcode);
-        assertEquals(1,_handler._data.size());
-        String mesg=_handler._data.remove(0);
-
-        assertEquals(2048,mesg.length());
-
-        for (int i=0;i<2048;i++)
-            assertEquals(('a'+i%26),mesg.charAt(i));
-    }
-
-    private class Handler implements WebSocketParser.FrameHandler
-    {
-        Utf8StringBuilder _utf8 = new Utf8StringBuilder();
-        public List<String> _data = new ArrayList<String>();
-        private byte _flags;
-        private byte _opcode;
-        int _code;
-        String _message;
-        int _frames;
-
-        public void onFrame(byte flags, byte opcode, Buffer buffer)
-        {
-            _frames++;
-            _flags=flags;
-            _opcode=opcode;
-            if ((flags&0x8)==0)
-                _utf8.append(buffer.array(),buffer.getIndex(),buffer.length());
-            else if (_utf8.length()==0)
-                _data.add(buffer.toString("utf-8"));
-            else
-            {
-                _utf8.append(buffer.array(),buffer.getIndex(),buffer.length());
-                _data.add(_utf8.toString());
-                _utf8.reset();
-            }
-        }
-
-        public void close(int code,String message)
-        {
-            _code=code;
-            _message=message;
-        }
-    }
-}
diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketParserRFC6455Test.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketParserRFC6455Test.java
deleted file mode 100644
index 53570c1..0000000
--- a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketParserRFC6455Test.java
+++ /dev/null
@@ -1,409 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.websocket;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import org.eclipse.jetty.http.HttpHeaderValues;
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.BufferCache.CachedBuffer;
-import org.eclipse.jetty.io.ByteArrayBuffer;
-import org.eclipse.jetty.io.ByteArrayEndPoint;
-import org.eclipse.jetty.util.StringUtil;
-import org.eclipse.jetty.util.Utf8StringBuilder;
-import org.junit.Before;
-import org.junit.Test;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-public class WebSocketParserRFC6455Test
-{
-    private ByteArrayEndPoint _endPoint;
-    private MaskedByteArrayBuffer _in;
-    private Handler _handler;
-    private WebSocketParserRFC6455 _parser;
-    private byte[] _mask = new byte[] {(byte)0x00,(byte)0xF0,(byte)0x0F,(byte)0xFF};
-    private int _m;
-
-    class MaskedByteArrayBuffer extends ByteArrayBuffer
-    {
-        MaskedByteArrayBuffer()
-        {
-            super(4096);
-        }
-
-        public void sendMask()
-        {
-            super.poke(putIndex(),_mask,0,4);
-            super.setPutIndex(putIndex()+4);
-            _m=0;
-        }
-
-        @Override
-        public int put(Buffer src)
-        {
-            return put(src.asArray(),0,src.length());
-        }
-
-        public void putUnmasked(byte b)
-        {
-            super.put(b);
-        }
-
-        @Override
-        public void put(byte b)
-        {
-            super.put((byte)(b^_mask[_m++%4]));
-        }
-
-        @Override
-        public int put(byte[] b, int offset, int length)
-        {
-            byte[] mb = new byte[b.length];
-            final int end=offset+length;
-            for (int i=offset;i<end;i++)
-            {
-                mb[i]=(byte)(b[i]^_mask[_m++%4]);
-            }
-            return super.put(mb,offset,length);
-        }
-
-        @Override
-        public int put(byte[] b)
-        {
-            return put(b,0,b.length);
-        }
-
-    };
-
-
-    @Before
-    public void setUp() throws Exception
-    {
-        WebSocketBuffers buffers = new WebSocketBuffers(1024);
-        _endPoint = new ByteArrayEndPoint();
-        _endPoint.setNonBlocking(true);
-        _handler = new Handler();
-        _parser=new WebSocketParserRFC6455(buffers, _endPoint,_handler,true);
-        _parser.setFakeFragments(false);
-        _in = new MaskedByteArrayBuffer();
-
-        _endPoint.setIn(_in);
-    }
-
-    @Test
-    public void testCache() throws Exception
-    {
-        assertEquals(HttpHeaderValues.UPGRADE_ORDINAL ,((CachedBuffer)HttpHeaderValues.CACHE.lookup("Upgrade")).getOrdinal());
-    }
-
-    @Test
-    public void testFlagsOppcode() throws Exception
-    {
-        _in.putUnmasked((byte)0xff);
-        _in.putUnmasked((byte)0x80);
-        _in.sendMask();
-
-        int progress =_parser.parseNext();
-
-        assertTrue(progress>0);
-        assertEquals(0xf,_handler._flags);
-        assertEquals(0xf,_handler._opcode);
-        assertTrue(_parser.isBufferEmpty());
-        _parser.returnBuffer();
-        assertTrue(_parser.getBuffer()==null);
-    }
-
-    @Test
-    public void testShortText() throws Exception
-    {
-        _in.putUnmasked((byte)0x81);
-        _in.putUnmasked((byte)(0x80|11));
-        _in.sendMask();
-        _in.put("Hello World".getBytes(StringUtil.__UTF8));
-        // System.err.println("tosend="+TypeUtil.toHexString(_in.asArray()));
-
-        int progress =_parser.parseNext();
-
-        assertTrue(progress>0);
-        assertEquals("Hello World",_handler._data.get(0));
-        assertEquals(0x8,_handler._flags);
-        assertEquals(0x1,_handler._opcode);
-        assertTrue(_parser.isBufferEmpty());
-        _parser.returnBuffer();
-        assertTrue(_parser.getBuffer()==null);
-    }
-
-    @Test
-    public void testShortUtf8() throws Exception
-    {
-        String string = "Hell\uFF4f W\uFF4Frld";
-        byte[] bytes = string.getBytes("UTF-8");
-
-        _in.putUnmasked((byte)0x81);
-        _in.putUnmasked((byte)(0x80|bytes.length));
-        _in.sendMask();
-        _in.put(bytes);
-
-        int progress =_parser.parseNext();
-
-        assertTrue(progress>0);
-        assertEquals(string,_handler._data.get(0));
-        assertEquals(0x8,_handler._flags);
-        assertEquals(0x1,_handler._opcode);
-        _parser.returnBuffer();
-        assertTrue(_parser.isBufferEmpty());
-        assertTrue(_parser.getBuffer()==null);
-    }
-
-    @Test
-    public void testMediumText() throws Exception
-    {
-        String string = "Hell\uFF4f Medium W\uFF4Frld ";
-        for (int i=0;i<4;i++)
-            string = string+string;
-        string += ". The end.";
-
-        byte[] bytes = string.getBytes(StringUtil.__UTF8);
-
-        _in.putUnmasked((byte)0x81);
-        _in.putUnmasked((byte)(0x80|0x7E));
-        _in.putUnmasked((byte)(bytes.length>>8));
-        _in.putUnmasked((byte)(bytes.length&0xff));
-        _in.sendMask();
-        _in.put(bytes);
-
-        int progress =_parser.parseNext();
-
-        assertTrue(progress>0);
-        assertEquals(string,_handler._data.get(0));
-        assertEquals(0x8,_handler._flags);
-        assertEquals(0x1,_handler._opcode);
-        _parser.returnBuffer();
-        assertTrue(_parser.isBufferEmpty());
-        assertTrue(_parser.getBuffer()==null);
-    }
-
-    @Test
-    public void testLongText() throws Exception
-    {
-        WebSocketBuffers buffers = new WebSocketBuffers(0x20000);
-        ByteArrayEndPoint endPoint = new ByteArrayEndPoint();
-        WebSocketParserRFC6455 parser=new WebSocketParserRFC6455(buffers, endPoint,_handler,false);
-        ByteArrayBuffer in = new ByteArrayBuffer(0x20000);
-        endPoint.setIn(in);
-
-        String string = "Hell\uFF4f Big W\uFF4Frld ";
-        for (int i=0;i<12;i++)
-            string = string+string;
-        string += ". The end.";
-
-        byte[] bytes = string.getBytes("UTF-8");
-
-        _in.sendMask();
-        in.put((byte)0x84);
-        in.put((byte)0x7F);
-        in.put((byte)0x00);
-        in.put((byte)0x00);
-        in.put((byte)0x00);
-        in.put((byte)0x00);
-        in.put((byte)0x00);
-        in.put((byte)(bytes.length>>16));
-        in.put((byte)((bytes.length>>8)&0xff));
-        in.put((byte)(bytes.length&0xff));
-        in.put(bytes);
-
-        int progress =parser.parseNext();
-        parser.returnBuffer();
-
-        assertTrue(progress>0);
-        assertEquals(string,_handler._data.get(0));
-        assertTrue(parser.isBufferEmpty());
-        assertTrue(parser.getBuffer()==null);
-    }
-
-    @Test
-    public void testShortFragmentTest() throws Exception
-    {
-        _in.putUnmasked((byte)0x01);
-        _in.putUnmasked((byte)0x86);
-        _in.sendMask();
-        _in.put("Hello ".getBytes(StringUtil.__UTF8));
-        _in.putUnmasked((byte)0x80);
-        _in.putUnmasked((byte)0x85);
-        _in.sendMask();
-        _in.put("World".getBytes(StringUtil.__UTF8));
-
-        int progress =_parser.parseNext();
-
-        assertTrue(progress>0);
-        assertEquals(0,_handler._data.size());
-        assertFalse(_parser.isBufferEmpty());
-        assertFalse(_parser.getBuffer()==null);
-
-        progress =_parser.parseNext();
-        _parser.returnBuffer();
-
-        assertTrue(progress>0);
-        assertEquals("Hello World",_handler._data.get(0));
-        assertTrue(_parser.isBufferEmpty());
-        assertTrue(_parser.getBuffer()==null);
-    }
-
-    @Test
-    public void testFrameTooLarge() throws Exception
-    {
-        // Buffers are only 1024, so this frame is too large
-        _parser.setFakeFragments(false);
-
-        _in.putUnmasked((byte)0x81);
-        _in.putUnmasked((byte)(0x80|0x7E));
-        _in.putUnmasked((byte)(2048>>8));
-        _in.putUnmasked((byte)(2048&0xff));
-        _in.sendMask();
-
-        int progress =_parser.parseNext();
-
-        assertTrue(progress>0);
-        assertEquals(WebSocketConnectionRFC6455.CLOSE_POLICY_VIOLATION,_handler._code);
-
-
-        for (int i=0;i<2048;i++)
-            _in.put((byte)'a');
-        progress =_parser.parseNext();
-
-        assertTrue(progress>0);
-        assertEquals(0,_handler._data.size());
-        assertEquals(0,_handler._utf8.length());
-
-        _handler._code=0;
-
-        _in.putUnmasked((byte)0x81);
-        _in.putUnmasked((byte)0xFE);
-        _in.putUnmasked((byte)(1024>>8));
-        _in.putUnmasked((byte)(1024&0xff));
-        _in.sendMask();
-        for (int i=0;i<1024;i++)
-            _in.put((byte)'a');
-
-        progress =_parser.parseNext();
-        assertTrue(progress>0);
-        assertEquals(0,_handler._data.size());
-    }
-
-    @Test
-    public void testFakeFragement() throws Exception
-    {
-        // Buffers are only 1024, so this frame will be fake fragmented
-        _parser.setFakeFragments(true);
-
-        _in.putUnmasked((byte)0x81);
-        _in.putUnmasked((byte)(0x80|0x7E));
-        _in.putUnmasked((byte)(2048>>8));
-        _in.putUnmasked((byte)(2048&0xff));
-        _in.sendMask();
-        for (int i=0;i<2048;i++)
-            _in.put((byte)('a'+i%26));
-
-        int progress =_parser.parseNext();
-        assertTrue(progress>0);
-
-        assertEquals(2,_handler._frames);
-        assertEquals(WebSocketConnectionRFC6455.OP_CONTINUATION,_handler._opcode);
-        assertEquals(1,_handler._data.size());
-        String mesg=_handler._data.remove(0);
-
-        assertEquals(2048,mesg.length());
-
-        for (int i=0;i<2048;i++)
-            assertEquals(('a'+i%26),mesg.charAt(i));
-    }
-
-    @Test
-    public void testClose() throws Exception
-    {
-        String string = "Game Over";
-        byte[] bytes = string.getBytes("UTF-8");
-
-        _in.putUnmasked((byte)(0x80|0x08));
-        _in.putUnmasked((byte)(0x80|(2+bytes.length)));
-        _in.sendMask();
-        _in.put((byte)(1000/0x100));
-        _in.put((byte)(1000%0x100));
-        _in.put(bytes);
-
-        int progress =_parser.parseNext();
-
-        assertTrue(progress>0);
-        assertEquals(string,_handler._data.get(0).substring(2));
-        assertEquals(0x8,_handler._flags);
-        assertEquals(0x8,_handler._opcode);
-        _parser.returnBuffer();
-        assertTrue(_parser.isBufferEmpty());
-        assertTrue(_parser.getBuffer()==null);
-
-        _in.clear();
-        _in.put(bytes);
-        _endPoint.setIn(_in);
-        progress =_parser.parseNext();
-        assertTrue(progress>0);
-
-        _endPoint.shutdownInput();
-
-        progress =_parser.parseNext();
-        assertEquals(-1,progress);
-
-    }
-
-
-    private class Handler implements WebSocketParser.FrameHandler
-    {
-        Utf8StringBuilder _utf8 = new Utf8StringBuilder();
-        public List<String> _data = new ArrayList<String>();
-        private byte _flags;
-        private byte _opcode;
-        int _code;
-        int _frames;
-
-        public void onFrame(byte flags, byte opcode, Buffer buffer)
-        {
-            _frames++;
-            _flags=flags;
-            _opcode=opcode;
-            if ((flags&0x8)==0)
-                _utf8.append(buffer.array(),buffer.getIndex(),buffer.length());
-            else if (_utf8.length()==0)
-                _data.add(buffer.toString("utf-8"));
-            else
-            {
-                _utf8.append(buffer.array(),buffer.getIndex(),buffer.length());
-                _data.add(_utf8.toString());
-                _utf8.reset();
-            }
-        }
-
-        public void close(int code,String message)
-        {
-            _code=code;
-        }
-    }
-}
diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketRedeployTest.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketRedeployTest.java
deleted file mode 100644
index b7a1fb9..0000000
--- a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketRedeployTest.java
+++ /dev/null
@@ -1,178 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.websocket;
-
-import java.net.URI;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import javax.servlet.http.HttpServletRequest;
-
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.handler.HandlerCollection;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
-import org.eclipse.jetty.servlet.ServletContextHandler;
-import org.eclipse.jetty.servlet.ServletHolder;
-import org.junit.After;
-import org.junit.Assert;
-import org.junit.Test;
-
-public class WebSocketRedeployTest
-{
-    private Server server;
-    private ServletContextHandler context;
-    private String uri;
-    private WebSocketClientFactory wsFactory;
-
-    public void init(final WebSocket webSocket) throws Exception
-    {
-        server = new Server();
-        SelectChannelConnector connector = new SelectChannelConnector();
-//        connector.setPort(8080);
-        server.addConnector(connector);
-
-        HandlerCollection handlers = new HandlerCollection();
-        server.setHandler(handlers);
-
-        String contextPath = "/test_context";
-        context = new ServletContextHandler(handlers, contextPath, ServletContextHandler.SESSIONS);
-
-        WebSocketServlet servlet = new WebSocketServlet()
-        {
-            public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol)
-            {
-                return webSocket;
-            }
-        };
-        String servletPath = "/websocket";
-        context.addServlet(new ServletHolder(servlet), servletPath);
-
-        server.start();
-
-        uri = "ws://localhost:" + connector.getLocalPort() + contextPath + servletPath;
-
-        wsFactory = new WebSocketClientFactory();
-        wsFactory.start();
-    }
-
-    @After
-    public void destroy() throws Exception
-    {
-        if (wsFactory != null)
-            wsFactory.stop();
-        if (server != null)
-        {
-            server.stop();
-            server.join();
-        }
-    }
-
-    @Test
-    public void testStoppingContextClosesConnections() throws Exception
-    {
-        final CountDownLatch openLatch = new CountDownLatch(2);
-        final CountDownLatch closeLatch = new CountDownLatch(2);
-        init(new WebSocket.OnTextMessage()
-        {
-            public void onOpen(Connection connection)
-            {
-                openLatch.countDown();
-            }
-
-            public void onMessage(String data)
-            {
-            }
-
-            public void onClose(int closeCode, String message)
-            {
-                closeLatch.countDown();
-            }
-        });
-
-        WebSocketClient client = wsFactory.newWebSocketClient();
-        client.open(new URI(uri), new WebSocket.OnTextMessage()
-        {
-            public void onOpen(Connection connection)
-            {
-                openLatch.countDown();
-            }
-
-            public void onMessage(String data)
-            {
-            }
-
-            public void onClose(int closeCode, String message)
-            {
-                closeLatch.countDown();
-            }
-        }, 5, TimeUnit.SECONDS);
-
-        Assert.assertTrue(openLatch.await(5, TimeUnit.SECONDS));
-
-        context.stop();
-
-        Assert.assertTrue(closeLatch.await(5, TimeUnit.SECONDS));
-    }
-
-    @Test
-    public void testStoppingClientFactoryClosesConnections() throws Exception
-    {
-        final CountDownLatch openLatch = new CountDownLatch(2);
-        final CountDownLatch closeLatch = new CountDownLatch(2);
-        init(new WebSocket.OnTextMessage()
-        {
-            public void onOpen(Connection connection)
-            {
-                openLatch.countDown();
-            }
-
-            public void onMessage(String data)
-            {
-            }
-
-            public void onClose(int closeCode, String message)
-            {
-                closeLatch.countDown();
-            }
-        });
-
-        WebSocketClient client = wsFactory.newWebSocketClient();
-        client.open(new URI(uri), new WebSocket.OnTextMessage()
-        {
-            public void onOpen(Connection connection)
-            {
-                openLatch.countDown();
-            }
-
-            public void onMessage(String data)
-            {
-            }
-
-            public void onClose(int closeCode, String message)
-            {
-                closeLatch.countDown();
-            }
-        }, 5, TimeUnit.SECONDS);
-
-        Assert.assertTrue(openLatch.await(5, TimeUnit.SECONDS));
-
-        wsFactory.stop();
-
-        Assert.assertTrue(closeLatch.await(5, TimeUnit.SECONDS));
-    }
-}
diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketServletRFCTest.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketServletRFCTest.java
deleted file mode 100644
index 7c00285..0000000
--- a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketServletRFCTest.java
+++ /dev/null
@@ -1,285 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.websocket;
-
-import static org.hamcrest.Matchers.*;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.net.InetSocketAddress;
-import java.net.Socket;
-import java.net.SocketAddress;
-import java.net.URI;
-import java.util.concurrent.TimeUnit;
-
-import javax.servlet.http.HttpServletRequest;
-
-import org.eclipse.jetty.server.Connector;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.servlet.ServletContextHandler;
-import org.eclipse.jetty.servlet.ServletHolder;
-import org.eclipse.jetty.util.IO;
-import org.eclipse.jetty.websocket.helper.MessageSender;
-import org.junit.AfterClass;
-import org.junit.Assert;
-import org.junit.BeforeClass;
-import org.junit.Test;
-
-/**
- * Test various <a href="http://tools.ietf.org/html/rfc6455">RFC 6455</a> specified requirements placed on
- * {@link WebSocketServlet}
- * <p>
- * This test serves a different purpose than than the {@link WebSocketGeneratorRFC6455Test},
- * {@link WebSocketMessageRFC6455Test}, and {@link WebSocketParserRFC6455Test} tests.
- */
-public class WebSocketServletRFCTest
-{
-    private static class RFCSocket implements WebSocket, WebSocket.OnTextMessage
-    {
-        private Connection conn;
-
-        public void onOpen(Connection connection)
-        {
-            this.conn = connection;
-        }
-
-        public void onClose(int closeCode, String message)
-        {
-            this.conn = null;
-        }
-
-        public void onMessage(String data)
-        {
-            // Test the RFC 6455 close code 1011 that should close
-            // trigger a WebSocket server terminated close.
-            if (data.equals("CRASH"))
-            {
-                throw new RuntimeException("Something bad happened");
-            }
-
-            // echo the message back.
-            try
-            {
-                conn.sendMessage(data);
-                
-                conn.close(1000, data);
-            }
-            catch (IOException e)
-            {
-                e.printStackTrace(System.err);
-            }
-        }
-
-    }
-
-    @SuppressWarnings("serial")
-    private static class RFCServlet extends WebSocketServlet
-    {
-        public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol)
-        {
-            return new RFCSocket();
-        }
-    }
-
-    private static Server server;
-    private static URI serverUri;
-
-    @BeforeClass
-    public static void startServer() throws Exception
-    {
-        // Configure Server
-        server = new Server(0);
-
-        ServletContextHandler context = new ServletContextHandler();
-        context.setContextPath("/");
-        server.setHandler(context);
-
-        // Serve capture servlet
-        context.addServlet(new ServletHolder(new RFCServlet()),"/*");
-
-        // Start Server
-        server.start();
-
-        Connector conn = server.getConnectors()[0];
-        String host = conn.getHost();
-        if (host == null)
-        {
-            host = "localhost";
-        }
-        int port = conn.getLocalPort();
-        serverUri = new URI(String.format("ws://%s:%d/",host,port));
-        System.out.printf("Server URI: %s%n",serverUri);
-    }
-
-    @AfterClass
-    public static void stopServer()
-    {
-        try
-        {
-            server.stop();
-        }
-        catch (Exception e)
-        {
-            e.printStackTrace(System.err);
-        }
-    }
-
-    /**
-     * Test the requirement of responding with an http 400 when using a Sec-WebSocket-Version that is unsupported.
-     */
-    @Test
-    public void testResponseOnInvalidVersion() throws Exception
-    {
-        // Using straight Socket to accomplish this as jetty's WebSocketClient
-        // doesn't allow the use of invalid versions. (obviously)
-
-        Socket socket = new Socket();
-        SocketAddress endpoint = new InetSocketAddress(serverUri.getHost(),serverUri.getPort());
-        socket.connect(endpoint);
-
-        StringBuilder req = new StringBuilder();
-        req.append("GET / HTTP/1.1\r\n");
-        req.append(String.format("Host: %s:%d\r\n",serverUri.getHost(),serverUri.getPort()));
-        req.append("Upgrade: WebSocket\r\n");
-        req.append("Connection: Upgrade\r\n");
-        req.append("Sec-WebSocket-Version: 29\r\n"); // bad version
-        req.append("\r\n");
-
-        OutputStream out = null;
-        InputStream in = null;
-        try
-        {
-            out = socket.getOutputStream();
-            in = socket.getInputStream();
-
-            // Write request
-            out.write(req.toString().getBytes());
-            out.flush();
-
-            // Read response
-            String respHeader = readResponseHeader(in);
-            // System.out.println("RESPONSE: " + respHeader);
-
-            Assert.assertThat("Response Code",respHeader,startsWith("HTTP/1.1 400 Unsupported websocket version specification"));
-            Assert.assertThat("Response Header Versions",respHeader,containsString("Sec-WebSocket-Version: 13\r\n"));
-        }
-        finally
-        {
-            IO.close(in);
-            IO.close(out);
-            socket.close();
-        }
-    }
-
-    private String readResponseHeader(InputStream in) throws IOException
-    {
-        InputStreamReader isr = new InputStreamReader(in);
-        BufferedReader reader = new BufferedReader(isr);
-        StringBuilder header = new StringBuilder();
-        // Read the response header
-        String line = reader.readLine();
-        Assert.assertNotNull(line);
-        Assert.assertThat(line,startsWith("HTTP/1.1 "));
-        header.append(line).append("\r\n");
-        while ((line = reader.readLine()) != null)
-        {
-            if (line.trim().length() == 0)
-            {
-                break;
-            }
-            header.append(line).append("\r\n");
-        }
-        return header.toString();
-    }
-
-    /**
-     * Test the requirement of responding with server terminated close code 1011 when there is an unhandled (internal
-     * server error) being produced by the extended WebSocketServlet.
-     */
-    @Test
-    public void testResponseOnInternalError() throws Exception
-    {
-        WebSocketClientFactory clientFactory = new WebSocketClientFactory();
-        clientFactory.start();
-
-        WebSocketClient wsc = clientFactory.newWebSocketClient();
-        MessageSender sender = new MessageSender();
-        wsc.open(serverUri,sender);
-
-        try
-        {
-            sender.awaitConnect();
-
-            sender.sendMessage("CRASH");
-
-            // Give servlet 500 millisecond to process messages
-            TimeUnit.MILLISECONDS.sleep(500);
-
-            Assert.assertThat("WebSocket should be closed",sender.isConnected(),is(false));
-            Assert.assertThat("WebSocket close clode",sender.getCloseCode(),is(1011));
-        }
-        finally
-        {
-            sender.close();
-        }
-    }
-    
-    /**
-     * Test the requirement of responding with server terminated close code 1011 when there is an unhandled (internal
-     * server error) being produced by the extended WebSocketServlet.
-     */
-    @Test
-    public void testResponseOfHttpKeyword() throws Exception
-    {
-        WebSocketClientFactory clientFactory = new WebSocketClientFactory();
-        clientFactory.start();
-
-        WebSocketClient wsc = clientFactory.newWebSocketClient();
-        MessageSender sender = new MessageSender();
-        wsc.open(serverUri,sender);
-
-        String message = "GET";
-        
-        try
-        {
-            sender.awaitConnect();
-
-            // echo back a http keyword
-            sender.sendMessage(message);
-
-            // Give servlet 500 millisecond to process messages
-            TimeUnit.MILLISECONDS.sleep(500);
-
-            sender.awaitMessage();
-            
-            Assert.assertEquals("Message should match",message, sender.getMessage());               
-            Assert.assertThat("WebSocket should be closed",sender.isConnected(),is(false));
-            Assert.assertThat("WebSocket close clode",sender.getCloseCode(),is(1000));
-            Assert.assertEquals("WebSocket close message",message, sender.getCloseMessage());
-
-        }
-        finally
-        {
-            sender.close();
-        }
-    }
-}
diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/dummy/DummyServer.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/dummy/DummyServer.java
deleted file mode 100644
index 74bf447..0000000
--- a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/dummy/DummyServer.java
+++ /dev/null
@@ -1,309 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.websocket.dummy;
-
-import static org.hamcrest.Matchers.*;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.net.InetAddress;
-import java.net.InetSocketAddress;
-import java.net.ServerSocket;
-import java.net.Socket;
-import java.net.SocketException;
-import java.net.URI;
-import java.nio.ByteBuffer;
-import java.util.Map;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import org.eclipse.jetty.util.IO;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.websocket.WebSocketConnectionRFC6455;
-import org.junit.Assert;
-
-/**
- * Simple ServerSocket server used to test oddball server scenarios encountered in the real world.
- */
-public class DummyServer
-{
-    public static class ServerConnection
-    {
-        private static final Logger LOG = Log.getLogger(ServerConnection.class);
-        private final Socket socket;
-        private InputStream in;
-        private OutputStream out;
-
-        public ServerConnection(Socket socket)
-        {
-            this.socket = socket;
-        }
-        
-        public int read(ByteBuffer buf) throws IOException
-        {
-            int len = 0;
-            while ((in.available() > 0) && (buf.remaining() > 0))
-            {
-                buf.put((byte)in.read());
-                len++;
-            }
-            return len;
-        }
-
-        public void disconnect()
-        {
-            LOG.debug("disconnect");
-            IO.close(in);
-            IO.close(out);
-            if (socket != null)
-            {
-                try
-                {
-                    socket.close();
-                }
-                catch (IOException ignore)
-                {
-                    /* ignore */
-                }
-            }
-        }
-
-        public InputStream getInputStream() throws IOException
-        {
-            if (in == null)
-            {
-                in = socket.getInputStream();
-            }
-            return in;
-        }
-
-        public OutputStream getOutputStream() throws IOException
-        {
-            if (out == null)
-            {
-                out = socket.getOutputStream();
-            }
-            return out;
-        }
-
-        public void flush() throws IOException
-        {
-            LOG.debug("flush()");
-            getOutputStream().flush();
-        }
-
-        public String readRequest() throws IOException
-        {
-            LOG.debug("Reading client request");
-            StringBuilder request = new StringBuilder();
-            BufferedReader in = new BufferedReader(new InputStreamReader(getInputStream()));
-            for (String line = in.readLine(); line != null; line = in.readLine())
-            {
-                if (line.length() == 0)
-                {
-                    break;
-                }
-                request.append(line).append("\r\n");
-                LOG.debug("read line: {}",line);
-            }
-
-            LOG.debug("Client Request:{}{}","\n",request);
-            return request.toString();
-        }
-
-        public void respond(String rawstr) throws IOException
-        {
-            LOG.debug("respond(){}{}","\n",rawstr);
-            getOutputStream().write(rawstr.getBytes());
-            flush();
-        }
-
-        public void setSoTimeout(int ms) throws SocketException
-        {
-            socket.setSoTimeout(ms);
-        }
-
-        public void upgrade(Map<String, String> extraResponseHeaders) throws IOException
-        {
-            @SuppressWarnings("unused")
-            Pattern patExts = Pattern.compile("^Sec-WebSocket-Extensions: (.*)$",Pattern.CASE_INSENSITIVE);
-            Pattern patKey = Pattern.compile("^Sec-WebSocket-Key: (.*)$",Pattern.CASE_INSENSITIVE);
-
-            LOG.debug("(Upgrade) Reading HTTP Request");
-            Matcher mat;
-            String key = "not sent";
-            BufferedReader in = new BufferedReader(new InputStreamReader(getInputStream()));
-            for (String line = in.readLine(); line != null; line = in.readLine())
-            {
-                if (line.length() == 0)
-                {
-                    break;
-                }
-
-                // TODO: Check for extensions
-                // mat = patExts.matcher(line);
-                // if (mat.matches())
-
-                // Check for Key
-                mat = patKey.matcher(line);
-                if (mat.matches())
-                {
-                    key = mat.group(1);
-                }
-            }
-
-            LOG.debug("(Upgrade) Writing HTTP Response");
-            // TODO: handle extensions?
-
-            // Setup Response
-            StringBuilder resp = new StringBuilder();
-            resp.append("HTTP/1.1 101 Upgrade\r\n");
-            resp.append("Upgrade: websocket\r\n");
-            resp.append("Connection: upgrade\r\n");
-            resp.append("Sec-WebSocket-Accept: ");
-            resp.append(WebSocketConnectionRFC6455.hashKey(key)).append("\r\n");
-            // extra response headers.
-            if (extraResponseHeaders != null)
-            {
-                for (Map.Entry<String,String> header : extraResponseHeaders.entrySet())
-                {
-                    resp.append(header.getKey());
-                    resp.append(": ");
-                    resp.append(header.getValue());
-                    resp.append("\r\n");
-                }
-            }
-            resp.append("\r\n");
-
-            // Write Response
-            getOutputStream().write(resp.toString().getBytes());
-            flush();
-        }
-
-        public void write(byte[] bytes) throws IOException
-        {
-            LOG.debug("Writing {} bytes", bytes.length);
-            getOutputStream().write(bytes);
-        }
-
-        public void write(byte[] buf, int offset, int length) throws IOException
-        {
-            LOG.debug("Writing bytes[{}], offset={}, length={}", buf.length, offset, length);
-            getOutputStream().write(buf,offset,length);
-        }
-
-        public void write(int b) throws IOException
-        {
-            LOG.debug("Writing int={}", b);
-            getOutputStream().write(b);
-        }
-    }
-
-    private static final Logger LOG = Log.getLogger(DummyServer.class);
-    private ServerSocket serverSocket;
-    private URI wsUri;
-
-    public ServerConnection accept() throws IOException
-    {
-        LOG.debug(".accept()");
-        assertIsStarted();
-        Socket socket = serverSocket.accept();
-        return new ServerConnection(socket);
-    }
-
-    private void assertIsStarted()
-    {
-        Assert.assertThat("ServerSocket",serverSocket,notNullValue());
-        Assert.assertThat("ServerSocket.isBound",serverSocket.isBound(),is(true));
-        Assert.assertThat("ServerSocket.isClosed",serverSocket.isClosed(),is(false));
-
-        Assert.assertThat("WsUri",wsUri,notNullValue());
-    }
-
-    public URI getWsUri()
-    {
-        return wsUri;
-    }
-
-    public void respondToClient(Socket connection, String serverResponse) throws IOException
-    {
-        InputStream in = null;
-        InputStreamReader isr = null;
-        BufferedReader buf = null;
-        OutputStream out = null;
-        try
-        {
-            in = connection.getInputStream();
-            isr = new InputStreamReader(in);
-            buf = new BufferedReader(isr);
-            String line;
-            while ((line = buf.readLine()) != null)
-            {
-                // System.err.println(line);
-                if (line.length() == 0)
-                {
-                    // Got the "\r\n" line.
-                    break;
-                }
-            }
-
-            // System.out.println("[Server-Out] " + serverResponse);
-            out = connection.getOutputStream();
-            out.write(serverResponse.getBytes());
-            out.flush();
-        }
-        finally
-        {
-            IO.close(buf);
-            IO.close(isr);
-            IO.close(in);
-            IO.close(out);
-        }
-    }
-
-    public void start() throws IOException
-    {
-        serverSocket = new ServerSocket();
-        InetAddress addr = InetAddress.getByName("localhost");
-        InetSocketAddress endpoint = new InetSocketAddress(addr,0);
-        serverSocket.bind(endpoint);
-        int port = serverSocket.getLocalPort();
-        String uri = String.format("ws://%s:%d/",addr.getHostAddress(),port);
-        wsUri = URI.create(uri);
-        LOG.debug("Server Started on {} -> {}",endpoint,wsUri);
-    }
-
-    public void stop()
-    {
-        LOG.debug("Stopping Server");
-        try
-        {
-            serverSocket.close();
-        }
-        catch (IOException ignore)
-        {
-            /* ignore */
-        }
-    }
-
-}
diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/examples/EchoClientSocketExample.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/examples/EchoClientSocketExample.java
deleted file mode 100644
index 1da6c69..0000000
--- a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/examples/EchoClientSocketExample.java
+++ /dev/null
@@ -1,145 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.websocket.examples;
-
-import java.io.IOException;
-import java.net.URI;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.util.thread.QueuedThreadPool;
-import org.eclipse.jetty.websocket.WebSocket;
-import org.eclipse.jetty.websocket.WebSocketClient;
-import org.eclipse.jetty.websocket.WebSocketClientFactory;
-
-public class EchoClientSocketExample
-{
-    /**
-     * A simple echo socket, on connect it sends a text message, then when it receives the message it tracks it and then closes the connection.
-     * <p>
-     * There are some fundamental latches provided that a WebSocketClient can use to wait till a socket is connected or even disconnected.
-     */
-    public static class EchoClientSocket implements WebSocket, WebSocket.OnTextMessage
-    {
-        private static final Logger LOG = Log.getLogger(EchoClientSocket.class);
-
-        public CountDownLatch connectLatch = new CountDownLatch(1);
-        public CountDownLatch disconnectLatch = new CountDownLatch(1);
-
-        public List<String> textMessages = new ArrayList<String>();
-        public List<Throwable> errors = new ArrayList<Throwable>();
-        private Connection connection;
-
-        @Override
-        public void onClose(int closeCode, String message)
-        {
-            LOG.info("Closed {} : {}",closeCode,message);
-            disconnectLatch.countDown();
-        }
-
-        @Override
-        public void onMessage(String message)
-        {
-            LOG.info("on Text : {}",message);
-            textMessages.add(message);
-            connection.close();
-        }
-
-        @Override
-        public void onOpen(Connection connection)
-        {
-            this.connection = connection;
-            LOG.info("Connection opened : {}",connection);
-            connectLatch.countDown();
-            try
-            {
-                connection.sendMessage("Hello WebSocket World");
-            }
-            catch (IOException e)
-            {
-                LOG.warn(e);
-            }
-        }
-    }
-    
-    private static final Logger LOG = Log.getLogger(EchoClientSocketExample.class);
-
-    public static void main(String[] args)
-    {
-        QueuedThreadPool threadPool = new QueuedThreadPool();
-        // Since this is a standalone client, lets make the threadpool
-        // be able to shut itself down at System.exit()
-        threadPool.setDaemon(true);
-
-        WebSocketClientFactory factory = new WebSocketClientFactory(threadPool);
-
-        factory.getSslContextFactory().setTrustAll(true);
-        
-        // disable vulnerable SSL protocols
-        factory.getSslContextFactory().addExcludeProtocols("SSL","SSLv2","SSLv2Hello","SSLv3");
-        // disable vulnerable SSL cipher suites
-        factory.getSslContextFactory().addExcludeCipherSuites(
-                "SSL_RSA_WITH_DES_CBC_SHA",
-                "SSL_DHE_RSA_WITH_DES_CBC_SHA",
-                "SSL_DHE_DSS_WITH_DES_CBC_SHA",
-                "SSL_RSA_EXPORT_WITH_RC4_40_MD5",
-                "SSL_RSA_EXPORT_WITH_DES40_CBC_SHA",
-                "SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA",
-                "SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA");
-
-        try
-        {
-            factory.start();
-
-            WebSocketClient client = factory.newWebSocketClient();
-            client.setMaxTextMessageSize(50000);
-
-            // Change to "wss://" for secure (SSL) version.
-            URI wsUri = new URI("ws://echo.websocket.org/");
-
-            // Our socket endpoint (the client side)
-            EchoClientSocket socket = new EchoClientSocket();
-
-            // Open the connection to the destination, wait 10 seconds for it to succeed (toss exception otherwise).
-            client.open(wsUri,socket).get(10,TimeUnit.SECONDS);
-
-            // wait till the socket is disconnected
-            socket.disconnectLatch.await(2L,TimeUnit.SECONDS);
-        }
-        catch (Throwable t)
-        {
-            LOG.warn(t);
-        }
-        finally
-        {
-            try
-            {
-                factory.stop();
-            }
-            catch (Exception e)
-            {
-                LOG.ignore(e);
-            }
-        }
-    }
-}
diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/examples/EchoServerSocketExample.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/examples/EchoServerSocketExample.java
deleted file mode 100644
index bcffbf7..0000000
--- a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/examples/EchoServerSocketExample.java
+++ /dev/null
@@ -1,140 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.websocket.examples;
-
-import java.io.IOException;
-import java.net.URI;
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.servlet.http.HttpServletRequest;
-
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.ByteArrayBuffer;
-import org.eclipse.jetty.server.Connector;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.servlet.ServletContextHandler;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.websocket.WebSocket;
-import org.eclipse.jetty.websocket.WebSocketServlet;
-
-public class EchoServerSocketExample
-{
-    public static class EchoSocket implements WebSocket, WebSocket.OnTextMessage, WebSocket.OnBinaryMessage
-    {
-        private static final Logger LOG = Log.getLogger(EchoSocket.class);
-
-        public List<Throwable> errors = new ArrayList<Throwable>();
-        private Connection connection;
-
-        @Override
-        public void onClose(int closeCode, String message)
-        {
-            LOG.info("Closed {} : {}",closeCode,message);
-        }
-
-        @Override
-        public void onMessage(byte[] data, int offset, int length)
-        {
-            // Wrap in Jetty Buffer (only for logging reasons)
-            Buffer buf = new ByteArrayBuffer(data,offset,length);
-            LOG.info("on Text : {}",buf.toDetailString());
-
-            try
-            {
-                // echo back this BINARY message
-                this.connection.sendMessage(data,offset,length);
-            }
-            catch (IOException e)
-            {
-                LOG.warn(e);
-            }
-        }
-
-        @Override
-        public void onMessage(String message)
-        {
-            LOG.info("on Text : {}",message);
-
-            try
-            {
-                // echo back this TEXT message
-                this.connection.sendMessage(message);
-            }
-            catch (IOException e)
-            {
-                LOG.warn(e);
-            }
-        }
-
-        @Override
-        public void onOpen(Connection connection)
-        {
-            this.connection = connection;
-            LOG.info("Connection opened : {}",connection);
-        }
-    }
-
-    @SuppressWarnings("serial")
-    public static class EchoSocketServlet extends WebSocketServlet
-    {
-        @Override
-        public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol)
-        {
-            return new EchoSocket();
-        }
-    }
-
-    private static final Logger LOG = Log.getLogger(EchoServerSocketExample.class);
-
-    public static void main(String[] args)
-    {
-        Server server = new Server(9090);
-
-        ServletContextHandler context = new ServletContextHandler();
-        context.setContextPath("/");
-        server.setHandler(context);
-
-        // Setup websocket echo socket, via servlet, on url pattern "/echo"
-        context.addServlet(EchoSocketServlet.class,"/echo");
-
-        try
-        {
-            // Start server
-            server.start();
-
-            Connector conn = server.getConnectors()[0];
-            String host = conn.getHost();
-            if (host == null)
-            {
-                host = "localhost";
-            }
-            int port = conn.getLocalPort();
-            URI serverUri = new URI(String.format("ws://%s:%d/echo",host,port));
-
-            LOG.info("WebSocket Echo Server started on {}",serverUri);
-            server.join();
-        }
-        catch (Throwable t)
-        {
-            LOG.warn(t);
-        }
-    }
-}
diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/helper/CaptureSocket.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/helper/CaptureSocket.java
deleted file mode 100644
index 245711d..0000000
--- a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/helper/CaptureSocket.java
+++ /dev/null
@@ -1,57 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.websocket.helper;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-import org.eclipse.jetty.websocket.WebSocket;
-
-public class CaptureSocket implements WebSocket.OnTextMessage
-{
-    private final CountDownLatch latch = new CountDownLatch(1);
-    public List<String> messages;
-
-    public CaptureSocket()
-    {
-        messages = new ArrayList<String>();
-    }
-
-    public boolean awaitConnected(long timeout) throws InterruptedException
-    {
-        return latch.await(timeout, TimeUnit.MILLISECONDS);
-    }
-
-    public void onMessage(String data)
-    {
-        // System.out.printf("Received Message \"%s\" [size %d]%n", data, data.length());
-        messages.add(data);
-    }
-
-    public void onOpen(Connection connection)
-    {
-        latch.countDown();
-    }
-
-    public void onClose(int closeCode, String message)
-    {
-    }
-}
diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/helper/MessageSender.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/helper/MessageSender.java
deleted file mode 100644
index 9459b54..0000000
--- a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/helper/MessageSender.java
+++ /dev/null
@@ -1,104 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.websocket.helper;
-
-import java.io.IOException;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-import org.eclipse.jetty.websocket.WebSocket;
-
-public class MessageSender implements WebSocket, WebSocket.OnTextMessage
-{
-    private Connection conn;
-    private CountDownLatch connectLatch = new CountDownLatch(1);
-    private CountDownLatch messageLatch = new CountDownLatch(1);
-
-    private int closeCode = -1;
-    private String closeMessage = null;
-    private String message = null;
-    
-    
-    public void onOpen(Connection connection)
-    {
-        this.conn = connection;
-        connectLatch.countDown();
-    }
-
-    public void onClose(int closeCode, String message)
-    {
-        this.conn = null;
-        this.closeCode = closeCode;
-        this.closeMessage = message;
-    }
-    
-    
-    public void onMessage(String data)
-    {
-        message = data;
-    }
-
-    public boolean isConnected()
-    {
-        if (this.conn == null)
-        {
-            return false;
-        }
-        return this.conn.isOpen();
-    }
-    
-    public int getCloseCode()
-    {
-        return closeCode;
-    }
-    
-    public String getCloseMessage()
-    {
-        return closeMessage;
-    }
-    
-    public String getMessage()
-    {
-        return message;
-    }
-
-    public void sendMessage(String format, Object... args) throws IOException
-    {
-        this.conn.sendMessage(String.format(format,args));
-    }
-
-    public void awaitConnect() throws InterruptedException
-    {
-        connectLatch.await(1,TimeUnit.SECONDS);
-    }
-    
-    public void awaitMessage() throws InterruptedException
-    {
-        messageLatch.await(1,TimeUnit.SECONDS);
-    }
-
-    public void close()
-    {
-        if (this.conn == null)
-        {
-            return;
-        }
-        this.conn.close();
-    }
-}
diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/helper/SafariD00.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/helper/SafariD00.java
deleted file mode 100644
index 6154056..0000000
--- a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/helper/SafariD00.java
+++ /dev/null
@@ -1,185 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.websocket.helper;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.io.UnsupportedEncodingException;
-import java.net.ConnectException;
-import java.net.InetSocketAddress;
-import java.net.Socket;
-import java.net.SocketAddress;
-import java.net.URI;
-
-import org.eclipse.jetty.io.ByteArrayBuffer;
-import org.eclipse.jetty.util.TypeUtil;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.util.log.StdErrLog;
-import org.junit.Assert;
-
-import static org.hamcrest.Matchers.*;
-import static org.hamcrest.Matchers.is;
-
-public class SafariD00
-{
-    private static final Logger LOG = Log.getLogger(SafariD00.class);
-    private URI uri;
-    private SocketAddress endpoint;
-    private Socket socket;
-    private OutputStream out;
-    private InputStream in;
-
-    public SafariD00(URI uri)
-    {
-        if (LOG instanceof StdErrLog)
-        {
-            ((StdErrLog)LOG).setLevel(StdErrLog.LEVEL_DEBUG);
-        }
-        this.uri = uri;
-        this.endpoint = new InetSocketAddress(uri.getHost(),uri.getPort());
-    }
-
-    /**
-     * Open the Socket to the destination endpoint and
-     *
-     * @return the open java Socket.
-     * @throws IOException
-     */
-    public Socket connect() throws IOException
-    {
-        LOG.info("Connecting to endpoint: " + endpoint);
-        socket = new Socket();
-        socket.setTcpNoDelay(true);
-        socket.connect(endpoint,1000);
-
-        out = socket.getOutputStream();
-        in = socket.getInputStream();
-
-        return socket;
-    }
-
-    /**
-     * Issue an Http websocket (Draft-0) upgrade request (using an example request captured from OSX/Safari)
-     *
-     * @throws UnsupportedEncodingException
-     */
-    public void issueHandshake() throws IOException
-    {
-        LOG.debug("Issuing Handshake");
-        StringBuilder req = new StringBuilder();
-        req.append("GET ").append(uri.getPath()).append(" HTTP/1.1\r\n");
-        req.append("Upgrade: WebSocket\r\n");
-        req.append("Connection: Upgrade\r\n");
-        req.append("Host: ").append(uri.getHost()).append(":").append(uri.getPort()).append("\r\n");
-        req.append("Origin: http://www.google.com/\r\n");
-        req.append("Sec-WebSocket-Key1: 15{ft  :6 at 87  0 M 5 c901\r\n");
-        req.append("Sec-WebSocket-Key2: 3? C;7~0 8   \" 3 2105 6  `_ {\r\n");
-        req.append("\r\n");
-
-        LOG.debug("Request:" + req);
-
-        byte reqBytes[] = req.toString().getBytes("UTF-8");
-        byte hixieBytes[] = TypeUtil.fromHexString("e739617916c9daf3");
-        byte buf[] = new byte[reqBytes.length + hixieBytes.length];
-        System.arraycopy(reqBytes,0,buf,0,reqBytes.length);
-        System.arraycopy(hixieBytes,0,buf,reqBytes.length,hixieBytes.length);
-
-        // Send HTTP GET Request (with hixie bytes)
-        out.write(buf,0,buf.length);
-        out.flush();
-
-        socket.setSoTimeout(10000);
-
-        // Read HTTP 101 Upgrade / Handshake Response
-        InputStreamReader reader = new InputStreamReader(in);
-        StringBuilder respHeaders = new StringBuilder();
-
-        LOG.debug("Reading http headers");
-        int crlfs = 0;
-        while (true)
-        {
-            int read = in.read();
-            respHeaders.append((char)read);
-            if (read == '\r' || read == '\n')
-                ++crlfs;
-            else
-                crlfs = 0;
-            if (crlfs == 4)
-                break;
-        }
-        
-        if(respHeaders.toString().startsWith("HTTP/1.1 101 ") == false) {
-            String respLine = respHeaders.toString();
-            int idx = respLine.indexOf('\r');
-            if(idx > 0) {
-                respLine = respLine.substring(0,idx);
-            }
-            LOG.debug("Response Headers: {}",respHeaders.toString());
-            throw new IllegalStateException(respLine);
-        }
-
-        // Read expected handshake hixie bytes
-        byte hixieHandshakeExpected[] = TypeUtil.fromHexString("c7438d956cf611a6af70603e6fa54809");
-        byte hixieHandshake[] = new byte[hixieHandshakeExpected.length];
-        Assert.assertThat("Hixie handshake buffer size",hixieHandshake.length,is(16));
-
-        LOG.debug("Reading hixie handshake bytes");
-        int bytesRead = 0;
-        while (bytesRead < hixieHandshake.length)
-        {
-            int val = in.read();
-            if (val >= 0)
-            {
-                hixieHandshake[bytesRead++] = (byte)val;
-            }
-        }
-        Assert.assertThat("Read hixie handshake bytes",bytesRead,is(hixieHandshake.length));
-    }
-
-    public void sendMessage(String... msgs) throws IOException
-    {
-        int len = 0;
-        for (String msg : msgs)
-        {
-            LOG.debug("sending message: " + msg);
-            len += (msg.length() + 2);
-        }
-
-        ByteArrayBuffer buf = new ByteArrayBuffer(len);
-
-        for (String msg : msgs)
-        {
-            buf.put((byte)0x00);
-            buf.put(msg.getBytes("UTF-8"));
-            buf.put((byte)0xFF);
-        }
-
-        out.write(buf.array());
-        out.flush();
-    }
-
-    public void disconnect() throws IOException
-    {
-        LOG.debug("disconnect");
-        socket.close();
-    }
-}
diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/helper/WebSocketCaptureServlet.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/helper/WebSocketCaptureServlet.java
deleted file mode 100644
index f6da091..0000000
--- a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/helper/WebSocketCaptureServlet.java
+++ /dev/null
@@ -1,49 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.websocket.helper;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jetty.websocket.WebSocket;
-import org.eclipse.jetty.websocket.WebSocketServlet;
-
- at SuppressWarnings("serial") 
-public class WebSocketCaptureServlet extends WebSocketServlet
-{
-    public List<CaptureSocket> captures = new ArrayList<CaptureSocket>();;
-
-    @Override
-    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
-    {
-        resp.sendError(404);
-    }
-
-    public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol)
-    {
-        CaptureSocket capture = new CaptureSocket();
-        captures.add(capture);
-        return capture;
-    }
-}
diff --git a/jetty-websocket/src/test/resources/jetty-logging.properties b/jetty-websocket/src/test/resources/jetty-logging.properties
deleted file mode 100644
index 7633c48..0000000
--- a/jetty-websocket/src/test/resources/jetty-logging.properties
+++ /dev/null
@@ -1,4 +0,0 @@
-# Setup default logging implementation for during testing
-org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
-org.eclipse.jetty.LEVEL=INFO
-#org.eclipse.jetty.websocket.LEVEL=DEBUG
diff --git a/jetty-websocket/websocket-api/pom.xml b/jetty-websocket/websocket-api/pom.xml
new file mode 100644
index 0000000..aec5c96
--- /dev/null
+++ b/jetty-websocket/websocket-api/pom.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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">
+    <parent>
+        <groupId>org.eclipse.jetty.websocket</groupId>
+        <artifactId>websocket-parent</artifactId>
+        <version>9.2.13.v20150730</version>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>websocket-api</artifactId>
+    <name>Jetty :: Websocket :: API</name>
+
+    <properties>
+        <bundle-symbolic-name>${project.groupId}.api</bundle-symbolic-name>
+    </properties>
+
+  <dependencies>
+    <!-- NOTE: It is important that this websocket-api module NOT depend on any other jetty modules,
+               for WebAppClassloader isolation reasons
+               - Joakim
+      -->
+    <dependency>
+      <groupId>org.eclipse.jetty.toolchain</groupId>
+      <artifactId>jetty-test-helper</artifactId>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-enforcer-plugin</artifactId>
+                <executions>
+                  <execution>
+                    <id>ban-java-servlet-api</id>
+                    <goals>
+                      <goal>enforce</goal>
+                    </goals>
+                    <configuration>
+                      <rules>
+                        <bannedDependencies>
+                          <includes>
+                            <include>javax.servlet</include>
+                            <include>servletapi</include>
+                            <include>org.eclipse.jetty.orbit:javax.servlet</include>
+                            <include>org.mortbay.jetty:servlet-api</include>
+                            <include>jetty:servlet-api</include>
+                          </includes>
+                        </bannedDependencies>
+                      </rules>
+                    </configuration>
+                  </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/BadPayloadException.java b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/BadPayloadException.java
new file mode 100644
index 0000000..83c239c
--- /dev/null
+++ b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/BadPayloadException.java
@@ -0,0 +1,44 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.api;
+
+/**
+ * Exception to terminate the connection because it has received data within a frame payload that was not consistent with the requirements of that frame
+ * payload. (eg: not UTF-8 in a text frame, or a unexpected data seen by an extension)
+ * 
+ * @see StatusCode#BAD_PAYLOAD
+ */
+ at SuppressWarnings("serial")
+public class BadPayloadException extends CloseException
+{
+    public BadPayloadException(String message)
+    {
+        super(StatusCode.BAD_PAYLOAD,message);
+    }
+
+    public BadPayloadException(String message, Throwable t)
+    {
+        super(StatusCode.BAD_PAYLOAD,message,t);
+    }
+
+    public BadPayloadException(Throwable t)
+    {
+        super(StatusCode.BAD_PAYLOAD,t);
+    }
+}
diff --git a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/BatchMode.java b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/BatchMode.java
new file mode 100644
index 0000000..c92c776
--- /dev/null
+++ b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/BatchMode.java
@@ -0,0 +1,47 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.api;
+
+/**
+ * The possible batch modes when invoking {@link OutgoingFrames#outgoingFrame(Frame, WriteCallback, BatchMode)}.
+ */
+public enum BatchMode
+{
+    /**
+     * Implementers are free to decide whether to send or not frames
+     * to the network layer.
+     */
+    AUTO,
+
+    /**
+     * Implementers must batch frames.
+     */
+    ON,
+
+    /**
+     * Implementers must send frames to the network layer.
+     */
+    OFF;
+
+    public static BatchMode max(BatchMode one, BatchMode two)
+    {
+        // Return the BatchMode that has the higher priority, where AUTO < ON < OFF.
+        return one.ordinal() < two.ordinal() ? two : one;
+    }
+}
diff --git a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/CloseException.java b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/CloseException.java
new file mode 100644
index 0000000..d0ecd12
--- /dev/null
+++ b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/CloseException.java
@@ -0,0 +1,49 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.api;
+
+ at SuppressWarnings("serial")
+public class CloseException extends WebSocketException
+{
+    private int statusCode;
+
+    public CloseException(int closeCode, String message)
+    {
+        super(message);
+        this.statusCode = closeCode;
+    }
+
+    public CloseException(int closeCode, String message, Throwable cause)
+    {
+        super(message,cause);
+        this.statusCode = closeCode;
+    }
+
+    public CloseException(int closeCode, Throwable cause)
+    {
+        super(cause);
+        this.statusCode = closeCode;
+    }
+
+    public int getStatusCode()
+    {
+        return statusCode;
+    }
+
+}
diff --git a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/CloseStatus.java b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/CloseStatus.java
new file mode 100644
index 0000000..417221e
--- /dev/null
+++ b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/CloseStatus.java
@@ -0,0 +1,81 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.api;
+
+public class CloseStatus
+{
+    private static final int MAX_CONTROL_PAYLOAD = 125;
+    private static final int MAX_REASON_PHRASE = MAX_CONTROL_PAYLOAD - 2;
+
+    /**
+     * Convenience method for trimming a long reason phrase at the maximum reason phrase length.
+     * 
+     * @param reason
+     *            the proposed reason phrase
+     * @return the reason phrase (trimmed if needed)
+     */
+    public static String trimMaxReasonLength(String reason)
+    {
+        if (reason == null)
+        {
+            return null;
+        }
+        
+        if (reason.length() > MAX_REASON_PHRASE)
+        {
+            return reason.substring(0,MAX_REASON_PHRASE);
+        }
+        else
+        {
+            return reason;
+        }
+    }
+
+    private int code;
+    private String phrase;
+
+    /**
+     * Creates a reason for closing a web socket connection with the given code and reason phrase.
+     * 
+     * @param closeCode
+     *            the close code
+     * @param reasonPhrase
+     *            the reason phrase
+     * @see StatusCode
+     */
+    public CloseStatus(int closeCode, String reasonPhrase)
+    {
+        this.code = closeCode;
+        this.phrase = reasonPhrase;
+        if (reasonPhrase.length() > MAX_REASON_PHRASE)
+        {
+            throw new IllegalArgumentException("Phrase exceeds maximum length of " + MAX_REASON_PHRASE);
+        }
+    }
+
+    public int getCode()
+    {
+        return code;
+    }
+
+    public String getPhrase()
+    {
+        return phrase;
+    }
+}
diff --git a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/InvalidWebSocketException.java b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/InvalidWebSocketException.java
new file mode 100644
index 0000000..2dfeb85
--- /dev/null
+++ b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/InvalidWebSocketException.java
@@ -0,0 +1,43 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.api;
+
+/**
+ * Indicating that the provided Class is not a valid WebSocket as defined by the API.
+ * <p>
+ * A valid WebSocket should do one of the following:
+ * <ul>
+ * <li>Implement {@link WebSocketListener}</li>
+ * <li>Extend {@link WebSocketAdapter}</li>
+ * <li>Declare the {@link WebSocket @WebSocket} annotation on the type</li>
+ * </ul>
+ */
+ at SuppressWarnings("serial")
+public class InvalidWebSocketException extends WebSocketException
+{
+    public InvalidWebSocketException(String message)
+    {
+        super(message);
+    }
+
+    public InvalidWebSocketException(String message, Throwable cause)
+    {
+        super(message,cause);
+    }
+}
diff --git a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/MessageTooLargeException.java b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/MessageTooLargeException.java
new file mode 100644
index 0000000..315b67b
--- /dev/null
+++ b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/MessageTooLargeException.java
@@ -0,0 +1,43 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.api;
+
+/**
+ * Exception when a message is too large for the internal buffers occurs and should trigger a connection close.
+ * 
+ * @see StatusCode#MESSAGE_TOO_LARGE
+ */
+ at SuppressWarnings("serial")
+public class MessageTooLargeException extends CloseException
+{
+    public MessageTooLargeException(String message)
+    {
+        super(StatusCode.MESSAGE_TOO_LARGE,message);
+    }
+
+    public MessageTooLargeException(String message, Throwable t)
+    {
+        super(StatusCode.MESSAGE_TOO_LARGE,message,t);
+    }
+
+    public MessageTooLargeException(Throwable t)
+    {
+        super(StatusCode.MESSAGE_TOO_LARGE,t);
+    }
+}
diff --git a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/PolicyViolationException.java b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/PolicyViolationException.java
new file mode 100644
index 0000000..3638777
--- /dev/null
+++ b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/PolicyViolationException.java
@@ -0,0 +1,43 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.api;
+
+/**
+ * Exception when a violation of policy occurs and should trigger a connection close.
+ * 
+ * @see StatusCode#POLICY_VIOLATION
+ */
+ at SuppressWarnings("serial")
+public class PolicyViolationException extends CloseException
+{
+    public PolicyViolationException(String message)
+    {
+        super(StatusCode.POLICY_VIOLATION,message);
+    }
+
+    public PolicyViolationException(String message, Throwable t)
+    {
+        super(StatusCode.POLICY_VIOLATION,message,t);
+    }
+
+    public PolicyViolationException(Throwable t)
+    {
+        super(StatusCode.POLICY_VIOLATION,t);
+    }
+}
diff --git a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/ProtocolException.java b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/ProtocolException.java
new file mode 100644
index 0000000..eef4817
--- /dev/null
+++ b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/ProtocolException.java
@@ -0,0 +1,41 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.api;
+
+/**
+ * Per spec, a protocol error should result in a Close frame of status code 1002 (PROTOCOL_ERROR)
+ */
+ at SuppressWarnings("serial")
+public class ProtocolException extends CloseException
+{
+    public ProtocolException(String message)
+    {
+        super(StatusCode.PROTOCOL,message);
+    }
+
+    public ProtocolException(String message, Throwable t)
+    {
+        super(StatusCode.PROTOCOL,message,t);
+    }
+
+    public ProtocolException(Throwable t)
+    {
+        super(StatusCode.PROTOCOL,t);
+    }
+}
diff --git a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/RemoteEndpoint.java b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/RemoteEndpoint.java
new file mode 100644
index 0000000..c2a81bd
--- /dev/null
+++ b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/RemoteEndpoint.java
@@ -0,0 +1,137 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.api;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.concurrent.Future;
+
+public interface RemoteEndpoint
+{
+    /**
+     * Send a binary message, returning when all bytes of the message has been transmitted.
+     * <p>
+     * Note: this is a blocking call
+     * 
+     * @param data
+     *            the message to be sent
+     */
+    void sendBytes(ByteBuffer data) throws IOException;
+
+    /**
+     * Initiates the asynchronous transmission of a binary message. This method returns before the message is transmitted. Developers may use the returned
+     * Future object to track progress of the transmission.
+     * 
+     * @param data
+     *            the data being sent
+     * @return the Future object representing the send operation.
+     */
+    Future<Void> sendBytesByFuture(ByteBuffer data);
+
+    /**
+     * Initiates the asynchronous transmission of a binary message. This method returns before the message is transmitted. Developers may provide a callback to
+     * be notified when the message has been transmitted or resulted in an error.
+     * 
+     * @param data
+     *            the data being sent
+     * @param callback
+     *            callback to notify of success or failure of the write operation
+     */
+    void sendBytes(ByteBuffer data, WriteCallback callback);
+
+    /**
+     * Send a binary message in pieces, blocking until all of the message has been transmitted. The runtime reads the message in order. Non-final pieces are
+     * sent with isLast set to false. The final piece must be sent with isLast set to true.
+     * 
+     * @param fragment
+     *            the piece of the message being sent
+     */
+    void sendPartialBytes(ByteBuffer fragment, boolean isLast) throws IOException;
+
+    /**
+     * Send a text message in pieces, blocking until all of the message has been transmitted. The runtime reads the message in order. Non-final pieces are sent
+     * with isLast set to false. The final piece must be sent with isLast set to true.
+     * 
+     * @param fragment
+     *            the piece of the message being sent
+     */
+    void sendPartialString(String fragment, boolean isLast) throws IOException;
+
+    /**
+     * Send a Ping message containing the given application data to the remote endpoint. The corresponding Pong message may be picked up using the
+     * MessageHandler.Pong handler.
+     * 
+     * @param applicationData
+     *            the data to be carried in the ping request
+     */
+    void sendPing(ByteBuffer applicationData) throws IOException;
+
+    /**
+     * Allows the developer to send an unsolicited Pong message containing the given application data in order to serve as a unidirectional heartbeat for the
+     * session.
+     * 
+     * @param applicationData
+     *            the application data to be carried in the pong response.
+     */
+    void sendPong(ByteBuffer applicationData) throws IOException;
+
+    /**
+     * Send a text message, blocking until all bytes of the message has been transmitted.
+     * <p>
+     * Note: this is a blocking call
+     * 
+     * @param text
+     *            the message to be sent
+     */
+    void sendString(String text) throws IOException;
+
+    /**
+     * Initiates the asynchronous transmission of a text message. This method may return before the message is transmitted. Developers may use the returned
+     * Future object to track progress of the transmission.
+     * 
+     * @param text
+     *            the text being sent
+     * @return the Future object representing the send operation.
+     */
+    Future<Void> sendStringByFuture(String text);
+
+    /**
+     * Initiates the asynchronous transmission of a text message. This method may return before the message is transmitted. Developers may provide a callback to
+     * be notified when the message has been transmitted or resulted in an error.
+     * 
+     * @param text
+     *            the text being sent
+     * @param callback
+     *            callback to notify of success or failure of the write operation
+     */
+    void sendString(String text, WriteCallback callback);
+
+    /**
+     * @return the batch mode with which messages are sent.
+     * @see #flush()
+     */
+    BatchMode getBatchMode();
+
+    /**
+     * Flushes messages that may have been batched by the implementation.
+     * @throws IOException if the flush fails
+     * @see #getBatchMode()
+     */
+    void flush() throws IOException;
+}
diff --git a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/Session.java b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/Session.java
new file mode 100644
index 0000000..f9daa0e
--- /dev/null
+++ b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/Session.java
@@ -0,0 +1,178 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.api;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+
+/**
+ * Session represents an active link of communications with a Remote WebSocket Endpoint.
+ * <p>
+ */
+public interface Session extends Closeable
+{
+    /**
+     * Request a close of the current conversation with a normal status code and no reason phrase.
+     * <p>
+     * This will enqueue a graceful close to the remote endpoint.
+     * 
+     * @see #close(CloseStatus)
+     * @see #close(int, String)
+     * @see #disconnect()
+     */
+    @Override
+    void close();
+
+    /**
+     * Request Close the current conversation, giving a reason for the closure. Note the websocket spec defines the acceptable uses of status codes and reason
+     * phrases.
+     * <p>
+     * This will enqueue a graceful close to the remote endpoint.
+     * 
+     * @param closeStatus
+     *            the reason for the closure
+     * 
+     * @see #close()
+     * @see #close(int, String)
+     * @see #disconnect()
+     */
+    void close(CloseStatus closeStatus);
+
+    /**
+     * Send a websocket Close frame, with status code.
+     * <p>
+     * This will enqueue a graceful close to the remote endpoint.
+     * 
+     * @param statusCode
+     *            the status code
+     * @param reason
+     *            the (optional) reason. (can be null for no reason)
+     * @see StatusCode
+     * 
+     * @see #close()
+     * @see #close(CloseStatus)
+     * @see #disconnect()
+     */
+    void close(int statusCode, String reason);
+
+    /**
+     * Issue a harsh disconnect of the underlying connection.
+     * <p>
+     * This will terminate the connection, without sending a websocket close frame.
+     * <p>
+     * Once called, any read/write activity on the websocket from this point will be indeterminate.
+     * <p>
+     * Once the underlying connection has been determined to be closed, the various onClose() events (either
+     * {@link WebSocketListener#onWebSocketClose(int, String)} or {@link OnWebSocketClose}) will be called on your websocket.
+     * 
+     * @see #close()
+     * @see #close(CloseStatus)
+     * @see #close(int, String)
+     * @see #disconnect()
+     */
+    void disconnect() throws IOException;
+
+    /**
+     * Return the number of milliseconds before this conversation will be closed by the container if it is inactive, ie no messages are either sent or received
+     * in that time.
+     * 
+     * @return the timeout in milliseconds.
+     */
+    long getIdleTimeout();
+
+    /**
+     * Get the address of the local side.
+     * 
+     * @return the local side address
+     */
+    public InetSocketAddress getLocalAddress();
+
+    /**
+     * Access the (now read-only) {@link WebSocketPolicy} in use for this connection.
+     * 
+     * @return the policy in use
+     */
+    WebSocketPolicy getPolicy();
+
+    /**
+     * Returns the version of the websocket protocol currently being used. This is taken as the value of the Sec-WebSocket-Version header used in the opening
+     * handshake. i.e. "13".
+     * 
+     * @return the protocol version
+     */
+    String getProtocolVersion();
+
+    /**
+     * Return a reference to the RemoteEndpoint object representing the other end of this conversation.
+     * 
+     * @return the remote endpoint
+     */
+    RemoteEndpoint getRemote();
+
+    /**
+     * Get the address of the remote side.
+     * 
+     * @return the remote side address
+     */
+    public InetSocketAddress getRemoteAddress();
+
+    /**
+     * Get the UpgradeRequest used to create this session
+     * 
+     * @return the UpgradeRequest used to create this session
+     */
+    UpgradeRequest getUpgradeRequest();
+
+    /**
+     * Get the UpgradeResponse used to create this session
+     * 
+     * @return the UpgradeResponse used to create this session
+     */
+    UpgradeResponse getUpgradeResponse();
+
+    /**
+     * Return true if and only if the underlying socket is open.
+     * 
+     * @return whether the session is open
+     */
+    abstract boolean isOpen();
+
+    /**
+     * Return true if and only if the underlying socket is using a secure transport.
+     * 
+     * @return whether its using a secure transport
+     */
+    boolean isSecure();
+
+    /**
+     * Set the number of milliseconds before this conversation will be closed by the container if it is inactive, ie no messages are either sent or received.
+     * 
+     * @param ms
+     *            the number of milliseconds.
+     */
+    void setIdleTimeout(long ms);
+
+    /**
+     * Suspend a the incoming read events on the connection.
+     * 
+     * @return the suspend token suitable for resuming the reading of data on the connection.
+     */
+    SuspendToken suspend();
+}
diff --git a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/StatusCode.java b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/StatusCode.java
new file mode 100644
index 0000000..5ff2c9a
--- /dev/null
+++ b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/StatusCode.java
@@ -0,0 +1,147 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.api;
+
+/**
+ * The <a href="https://tools.ietf.org/html/rfc6455#section-7.4">RFC 6455 specified status codes</a> and <a
+ * href="https://www.iana.org/assignments/websocket/websocket.xml#close-code-number-rules">IANA: WebSocket Close Code Number Registry</a>
+ */
+public class StatusCode
+{
+    /**
+     * 1000 indicates a normal closure, meaning that the purpose for which the connection was established has been fulfilled.
+     * <p>
+     * See <a href="https://tools.ietf.org/html/rfc6455#section-7.4.1">RFC 6455, Section 7.4.1 Defined Status Codes</a>.
+     */
+    public final static int NORMAL = 1000;
+
+    /**
+     * 1001 indicates that an endpoint is "going away", such as a server going down or a browser having navigated away from a page.
+     * <p>
+     * See <a href="https://tools.ietf.org/html/rfc6455#section-7.4.1">RFC 6455, Section 7.4.1 Defined Status Codes</a>.
+     */
+    public final static int SHUTDOWN = 1001;
+
+    /**
+     * 1002 indicates that an endpoint is terminating the connection due to a protocol error.
+     * <p>
+     * See <a href="https://tools.ietf.org/html/rfc6455#section-7.4.1">RFC 6455, Section 7.4.1 Defined Status Codes</a>.
+     */
+    public final static int PROTOCOL = 1002;
+
+    /**
+     * 1003 indicates that an endpoint is terminating the connection because it has received a type of data it cannot accept (e.g., an endpoint that understands
+     * only text data MAY send this if it receives a binary message).
+     * <p>
+     * See <a href="https://tools.ietf.org/html/rfc6455#section-7.4.1">RFC 6455, Section 7.4.1 Defined Status Codes</a>.
+     */
+    public final static int BAD_DATA = 1003;
+
+    /**
+     * Reserved. The specific meaning might be defined in the future.
+     * <p>
+     * See <a href="https://tools.ietf.org/html/rfc6455#section-7.4.1">RFC 6455, Section 7.4.1 Defined Status Codes</a>.
+     */
+    public final static int UNDEFINED = 1004;
+
+    /**
+     * 1005 is a reserved value and MUST NOT be set as a status code in a Close control frame by an endpoint. It is designated for use in applications expecting
+     * a status code to indicate that no status code was actually present.
+     * <p>
+     * See <a href="https://tools.ietf.org/html/rfc6455#section-7.4.1">RFC 6455, Section 7.4.1 Defined Status Codes</a>.
+     */
+    public final static int NO_CODE = 1005;
+
+    /**
+     * 1006 is a reserved value and MUST NOT be set as a status code in a Close control frame by an endpoint. It is designated for use in applications expecting
+     * a status code to indicate that the connection was closed abnormally, e.g., without sending or receiving a Close control frame.
+     * <p>
+     * See <a href="https://tools.ietf.org/html/rfc6455#section-7.4.1">RFC 6455, Section 7.4.1 Defined Status Codes</a>.
+     */
+    public final static int NO_CLOSE = 1006;
+    
+    /**
+     * Abnormal Close is a synonym for {@link #NO_CLOSE}, used to indicate a close
+     * condition where no close frame was processed from the remote side.
+     */
+    public final static int ABNORMAL = NO_CLOSE;
+
+    /**
+     * 1007 indicates that an endpoint is terminating the connection because it has received data within a message that was not consistent with the type of the
+     * message (e.g., non-UTF-8 [<a href="https://tools.ietf.org/html/rfc3629">RFC3629</a>] data within a text message).
+     * <p>
+     * See <a href="https://tools.ietf.org/html/rfc6455#section-7.4.1">RFC 6455, Section 7.4.1 Defined Status Codes</a>.
+     */
+    public final static int BAD_PAYLOAD = 1007;
+
+    /**
+     * 1008 indicates that an endpoint is terminating the connection because it has received a message that violates its policy. This is a generic status code
+     * that can be returned when there is no other more suitable status code (e.g., 1003 or 1009) or if there is a need to hide specific details about the
+     * policy.
+     * <p>
+     * See <a href="https://tools.ietf.org/html/rfc6455#section-7.4.1">RFC 6455, Section 7.4.1 Defined Status Codes</a>.
+     */
+    public final static int POLICY_VIOLATION = 1008;
+
+    /**
+     * 1009 indicates that an endpoint is terminating the connection because it has received a message that is too big for it to process.
+     * <p>
+     * See <a href="https://tools.ietf.org/html/rfc6455#section-7.4.1">RFC 6455, Section 7.4.1 Defined Status Codes</a>.
+     */
+    public final static int MESSAGE_TOO_LARGE = 1009;
+
+    /**
+     * 1010 indicates that an endpoint (client) is terminating the connection because it has expected the server to negotiate one or more extension, but the
+     * server didn't return them in the response message of the WebSocket handshake. The list of extensions that are needed SHOULD appear in the /reason/ part
+     * of the Close frame. Note that this status code is not used by the server, because it can fail the WebSocket handshake instead.
+     * <p>
+     * See <a href="https://tools.ietf.org/html/rfc6455#section-7.4.1">RFC 6455, Section 7.4.1 Defined Status Codes</a>.
+     */
+    public final static int REQUIRED_EXTENSION = 1010;
+
+    /**
+     * 1011 indicates that a server is terminating the connection because it encountered an unexpected condition that prevented it from fulfilling the request.
+     * <p>
+     * See <a href="https://tools.ietf.org/html/rfc6455#section-7.4.1">RFC 6455, Section 7.4.1 Defined Status Codes</a>.
+     */
+    public final static int SERVER_ERROR = 1011;
+
+    /**
+     * 1012 indicates that the service is restarted. a client may reconnect, and if it chooses to do, should reconnect using a randomized delay of 5 - 30s.
+     * <p>
+     * See <a href="https://www.ietf.org/mail-archive/web/hybi/current/msg09649.html">[hybi] Additional WebSocket Close Error Codes</a>
+     */
+    public final static int SERVICE_RESTART = 1012;
+
+    /**
+     * 1013 indicates that the service is experiencing overload. a client should only connect to a different IP (when there are multiple for the target) or
+     * reconnect to the same IP upon user action.
+     * <p>
+     * See <a href="https://www.ietf.org/mail-archive/web/hybi/current/msg09649.html">[hybi] Additional WebSocket Close Error Codes</a>
+     */
+    public final static int TRY_AGAIN_LATER = 1013;
+
+    /**
+     * 1015 is a reserved value and MUST NOT be set as a status code in a Close control frame by an endpoint. It is designated for use in applications expecting
+     * a status code to indicate that the connection was closed due to a failure to perform a TLS handshake (e.g., the server certificate can't be verified).
+     * <p>
+     * See <a href="https://tools.ietf.org/html/rfc6455#section-7.4.1">RFC 6455, Section 7.4.1 Defined Status Codes</a>.
+     */
+    public final static int FAILED_TLS_HANDSHAKE = 1015;
+}
diff --git a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/SuspendToken.java b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/SuspendToken.java
new file mode 100644
index 0000000..8e6bb09
--- /dev/null
+++ b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/SuspendToken.java
@@ -0,0 +1,30 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.api;
+
+/**
+ * Connection suspend token
+ */
+public interface SuspendToken
+{
+    /**
+     * Resume a previously suspended connection.
+     */
+    void resume();
+}
diff --git a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/UpgradeException.java b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/UpgradeException.java
new file mode 100644
index 0000000..4ad92c3
--- /dev/null
+++ b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/UpgradeException.java
@@ -0,0 +1,62 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.api;
+
+import java.net.URI;
+
+/**
+ * Exception during WebSocket Upgrade Handshake.
+ */
+ at SuppressWarnings("serial")
+public class UpgradeException extends WebSocketException
+{
+    private final URI requestURI;
+    private final int responseStatusCode;
+
+    public UpgradeException(URI requestURI, int responseStatusCode, String message)
+    {
+        super(message);
+        this.requestURI = requestURI;
+        this.responseStatusCode = responseStatusCode;
+    }
+
+    public UpgradeException(URI requestURI, int responseStatusCode, String message, Throwable cause)
+    {
+        super(message,cause);
+        this.requestURI = requestURI;
+        this.responseStatusCode = responseStatusCode;
+    }
+
+    public UpgradeException(URI requestURI, Throwable cause)
+    {
+        super(cause);
+        this.requestURI = requestURI;
+        this.responseStatusCode = -1;
+    }
+
+    public URI getRequestURI()
+    {
+        return requestURI;
+    }
+
+    public int getResponseStatusCode()
+    {
+        return responseStatusCode;
+    }
+}
diff --git a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/UpgradeRequest.java b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/UpgradeRequest.java
new file mode 100644
index 0000000..79da9ec
--- /dev/null
+++ b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/UpgradeRequest.java
@@ -0,0 +1,362 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.api;
+
+import java.net.HttpCookie;
+import java.net.URI;
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
+import org.eclipse.jetty.websocket.api.util.QuoteUtil;
+
+public class UpgradeRequest
+{
+    private URI requestURI;
+    private List<String> subProtocols = new ArrayList<>(1);
+    private List<ExtensionConfig> extensions = new ArrayList<>(1);
+    private List<HttpCookie> cookies = new ArrayList<>(1);
+    private Map<String, List<String>> headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+    private Map<String, List<String>> parameters = new HashMap<>(1);
+    private Object session;
+    private String httpVersion;
+    private String method;
+    private String host;
+    private boolean secure;
+
+    protected UpgradeRequest()
+    {
+        /* anonymous, no requestURI, upgrade request */
+    }
+
+    public UpgradeRequest(String requestURI)
+    {
+        this(URI.create(requestURI));
+    }
+
+    public UpgradeRequest(URI requestURI)
+    {
+        setRequestURI(requestURI);
+    }
+
+    public void addExtensions(ExtensionConfig... configs)
+    {
+        Collections.addAll(extensions, configs);
+    }
+
+    public void addExtensions(String... configs)
+    {
+        for (String config : configs)
+        {
+            extensions.add(ExtensionConfig.parse(config));
+        }
+    }
+
+    public void clearHeaders()
+    {
+        headers.clear();
+    }
+
+    public List<HttpCookie> getCookies()
+    {
+        return cookies;
+    }
+
+    public List<ExtensionConfig> getExtensions()
+    {
+        return extensions;
+    }
+
+    public String getHeader(String name)
+    {
+        List<String> values = headers.get(name);
+        // no value list
+        if (values == null)
+        {
+            return null;
+        }
+        int size = values.size();
+        // empty value list
+        if (size <= 0)
+        {
+            return null;
+        }
+        // simple return
+        if (size == 1)
+        {
+            return values.get(0);
+        }
+        // join it with commas
+        boolean needsDelim = false;
+        StringBuilder ret = new StringBuilder();
+        for (String value : values)
+        {
+            if (needsDelim)
+            {
+                ret.append(", ");
+            }
+            QuoteUtil.quoteIfNeeded(ret,value,QuoteUtil.ABNF_REQUIRED_QUOTING);
+            needsDelim = true;
+        }
+        return ret.toString();
+    }
+
+    public int getHeaderInt(String name)
+    {
+        List<String> values = headers.get(name);
+        // no value list
+        if (values == null)
+        {
+            return -1;
+        }
+        int size = values.size();
+        // empty value list
+        if (size <= 0)
+        {
+            return -1;
+        }
+        // simple return
+        if (size == 1)
+        {
+            return Integer.parseInt(values.get(0));
+        }
+        throw new NumberFormatException("Cannot convert multi-value header into int");
+    }
+
+    public Map<String, List<String>> getHeaders()
+    {
+        return headers;
+    }
+
+    public List<String> getHeaders(String name)
+    {
+        return headers.get(name);
+    }
+
+    public String getHost()
+    {
+        return host;
+    }
+
+    public String getHttpVersion()
+    {
+        return httpVersion;
+    }
+
+    public String getMethod()
+    {
+        return method;
+    }
+
+    public String getOrigin()
+    {
+        return getHeader("Origin");
+    }
+
+    /**
+     * Returns a map of the query parameters of the request.
+     * 
+     * @return a unmodifiable map of query parameters of the request.
+     */
+    public Map<String, List<String>> getParameterMap()
+    {
+        return Collections.unmodifiableMap(parameters);
+    }
+
+    public String getProtocolVersion()
+    {
+        String version = getHeader("Sec-WebSocket-Version");
+        if (version == null)
+        {
+            return "13"; // Default
+        }
+        return version;
+    }
+
+    public String getQueryString()
+    {
+        return requestURI.getQuery();
+    }
+
+    public URI getRequestURI()
+    {
+        return requestURI;
+    }
+
+    /**
+     * Access the Servlet HTTP Session (if present)
+     * <p>
+     * Note: Never present on a Client UpgradeRequest.
+     * 
+     * @return the Servlet HTTPSession on server side UpgradeRequests
+     */
+    public Object getSession()
+    {
+        return session;
+    }
+
+    public List<String> getSubProtocols()
+    {
+        return subProtocols;
+    }
+
+    /**
+     * Get the User Principal for this request.
+     * <p>
+     * Only applicable when using UpgradeRequest from server side.
+     * 
+     * @return the user principal
+     */
+    public Principal getUserPrincipal()
+    {
+        // Server side should override to implement
+        return null;
+    }
+
+    public boolean hasSubProtocol(String test)
+    {
+        for (String protocol : subProtocols)
+        {
+            if (protocol.equalsIgnoreCase(test))
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public boolean isOrigin(String test)
+    {
+        return test.equalsIgnoreCase(getOrigin());
+    }
+
+    public boolean isSecure()
+    {
+        return secure;
+    }
+
+    public void setCookies(List<HttpCookie> cookies)
+    {
+        this.cookies.clear();
+        if (cookies != null && !cookies.isEmpty())
+        {
+            this.cookies.addAll(cookies);
+        }
+    }
+    
+    public void setExtensions(List<ExtensionConfig> configs)
+    {
+        this.extensions.clear();
+        if (configs != null)
+        {
+            this.extensions.addAll(configs);
+        }
+    }
+
+    public void setHeader(String name, List<String> values)
+    {
+        headers.put(name,values);
+    }
+
+    public void setHeader(String name, String value)
+    {
+        List<String> values = new ArrayList<>();
+        values.add(value);
+        setHeader(name,values);
+    }
+
+    public void setHeaders(Map<String, List<String>> headers)
+    {
+        clearHeaders();
+
+        for (Map.Entry<String, List<String>> entry : headers.entrySet())
+        {
+            String name = entry.getKey();
+            List<String> values = entry.getValue();
+            setHeader(name,values);
+        }
+    }
+
+    public void setHttpVersion(String httpVersion)
+    {
+        this.httpVersion = httpVersion;
+    }
+
+    public void setMethod(String method)
+    {
+        this.method = method;
+    }
+
+    protected void setParameterMap(Map<String, List<String>> parameters)
+    {
+        this.parameters.clear();
+        this.parameters.putAll(parameters);
+    }
+
+    public void setRequestURI(URI uri)
+    {
+        this.requestURI = uri;
+        String scheme = uri.getScheme();
+        if ("ws".equalsIgnoreCase(scheme))
+        {
+            secure = false;
+        }
+        else if ("wss".equalsIgnoreCase(scheme))
+        {
+            secure = true;
+        }
+        else
+        {
+            throw new IllegalArgumentException("URI scheme must be 'ws' or 'wss'");
+        }
+        this.host = this.requestURI.getHost();
+        this.parameters.clear();
+    }
+
+    public void setSession(Object session)
+    {
+        this.session = session;
+    }
+
+    public void setSubProtocols(List<String> subProtocols)
+    {
+        this.subProtocols.clear();
+        if (subProtocols != null)
+        {
+            this.subProtocols.addAll(subProtocols);
+        }
+    }
+
+    /**
+     * Set Sub Protocol request list.
+     * 
+     * @param protocols
+     *            the sub protocols desired
+     */
+    public void setSubProtocols(String... protocols)
+    {
+        subProtocols.clear();
+        Collections.addAll(subProtocols, protocols);
+    }
+}
diff --git a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/UpgradeResponse.java b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/UpgradeResponse.java
new file mode 100644
index 0000000..a98a6f3
--- /dev/null
+++ b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/UpgradeResponse.java
@@ -0,0 +1,206 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.api;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+
+import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
+import org.eclipse.jetty.websocket.api.util.QuoteUtil;
+
+public class UpgradeResponse
+{
+    public static final String SEC_WEBSOCKET_PROTOCOL = "Sec-WebSocket-Protocol";
+    private int statusCode;
+    private String statusReason;
+    private Map<String, List<String>> headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+    private List<ExtensionConfig> extensions = new ArrayList<>();
+    private boolean success = false;
+
+    public void addHeader(String name, String value)
+    {
+        String key = name;
+        List<String> values = headers.get(key);
+        if (values == null)
+        {
+            values = new ArrayList<>();
+        }
+        values.add(value);
+        headers.put(key,values);
+    }
+
+    /**
+     * Get the accepted WebSocket protocol.
+     * 
+     * @return the accepted WebSocket protocol.
+     */
+    public String getAcceptedSubProtocol()
+    {
+        return getHeader(SEC_WEBSOCKET_PROTOCOL);
+    }
+
+    /**
+     * Get the list of extensions that should be used for the websocket.
+     * 
+     * @return the list of negotiated extensions to use.
+     */
+    public List<ExtensionConfig> getExtensions()
+    {
+        return extensions;
+    }
+
+    public String getHeader(String name)
+    {
+        List<String> values = getHeaders(name);
+        // no value list
+        if (values == null)
+        {
+            return null;
+        }
+        int size = values.size();
+        // empty value list
+        if (size <= 0)
+        {
+            return null;
+        }
+        // simple return
+        if (size == 1)
+        {
+            return values.get(0);
+        }
+        // join it with commas
+        boolean needsDelim = false;
+        StringBuilder ret = new StringBuilder();
+        for (String value : values)
+        {
+            if (needsDelim)
+            {
+                ret.append(", ");
+            }
+            QuoteUtil.quoteIfNeeded(ret,value,QuoteUtil.ABNF_REQUIRED_QUOTING);
+            needsDelim = true;
+        }
+        return ret.toString();
+    }
+
+    public Set<String> getHeaderNames()
+    {
+        return headers.keySet();
+    }
+
+    public Map<String, List<String>> getHeaders()
+    {
+        return headers;
+    }
+
+    public List<String> getHeaders(String name)
+    {
+        return headers.get(name);
+    }
+
+    public int getStatusCode()
+    {
+        return statusCode;
+    }
+
+    public String getStatusReason()
+    {
+        return statusReason;
+    }
+
+    public boolean isSuccess()
+    {
+        return success;
+    }
+
+    /**
+     * Issue a forbidden upgrade response.
+     * <p>
+     * This means that the websocket endpoint was valid, but the conditions to use a WebSocket resulted in a forbidden access.
+     * <p>
+     * Use this when the origin or authentication is invalid.
+     * 
+     * @param message
+     *            the short 1 line detail message about the forbidden response
+     * @throws IOException
+     */
+    public void sendForbidden(String message) throws IOException
+    {
+        throw new UnsupportedOperationException("Not supported");
+    }
+
+    /**
+     * Set the accepted WebSocket Protocol.
+     * 
+     * @param protocol
+     *            the protocol to list as accepted
+     */
+    public void setAcceptedSubProtocol(String protocol)
+    {
+        setHeader(SEC_WEBSOCKET_PROTOCOL,protocol);
+    }
+
+    /**
+     * Set the list of extensions that are approved for use with this websocket.
+     * <p>
+     * Notes:
+     * <ul>
+     * <li>Per the spec you cannot add extensions that have not been seen in the {@link UpgradeRequest}, just remove entries you don't want to use</li>
+     * <li>If this is unused, or a null is passed, then the list negotiation will follow default behavior and use the complete list of extensions that are
+     * available in this WebSocket server implementation.</li>
+     * </ul>
+     * 
+     * @param extensions
+     *            the list of extensions to use.
+     */
+    public void setExtensions(List<ExtensionConfig> extensions)
+    {
+        this.extensions.clear();
+        if (extensions != null)
+        {
+            this.extensions.addAll(extensions);
+        }
+    }
+
+    public void setHeader(String name, String value)
+    {
+        List<String> values = new ArrayList<>();
+        values.add(value);
+        headers.put(name,values);
+    }
+
+    public void setStatusCode(int statusCode)
+    {
+        this.statusCode = statusCode;
+    }
+
+    public void setStatusReason(String statusReason)
+    {
+        this.statusReason = statusReason;
+    }
+
+    public void setSuccess(boolean success)
+    {
+        this.success = success;
+    }
+}
diff --git a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketAdapter.java b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketAdapter.java
new file mode 100644
index 0000000..b3ee62b
--- /dev/null
+++ b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketAdapter.java
@@ -0,0 +1,82 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.api;
+
+/**
+ * Default implementation of the {@link WebSocketListener}.
+ * <p>
+ * Convenient abstract class to base standard WebSocket implementations off of.
+ */
+public class WebSocketAdapter implements WebSocketListener
+{
+    private volatile Session session;
+
+    public RemoteEndpoint getRemote()
+    {
+        Session sess = this.session;
+        return sess == null?null:session.getRemote();
+    }
+
+    public Session getSession()
+    {
+        return session;
+    }
+
+    public boolean isConnected()
+    {
+        Session sess = this.session;
+        return (sess != null) && (sess.isOpen());
+    }
+
+    public boolean isNotConnected()
+    {
+        Session sess = this.session;
+        return (sess == null) || (!sess.isOpen());
+    }
+
+    @Override
+    public void onWebSocketBinary(byte[] payload, int offset, int len)
+    {
+        /* do nothing */
+    }
+
+    @Override
+    public void onWebSocketClose(int statusCode, String reason)
+    {
+        this.session = null;
+    }
+
+    @Override
+    public void onWebSocketConnect(Session sess)
+    {
+        this.session = sess;
+    }
+
+    @Override
+    public void onWebSocketError(Throwable cause)
+    {
+        /* do nothing */
+    }
+
+    @Override
+    public void onWebSocketText(String message)
+    {
+        /* do nothing */
+    }
+}
diff --git a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketBehavior.java b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketBehavior.java
new file mode 100644
index 0000000..bd03126
--- /dev/null
+++ b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketBehavior.java
@@ -0,0 +1,31 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.api;
+
+/**
+ * Behavior for how the WebSocket should operate.
+ * <p>
+ * This dictated by the <a href="https://tools.ietf.org/html/rfc6455">RFC 6455</a> spec in various places, where certain behavior must be performed depending on
+ * operation as a <a href="https://tools.ietf.org/html/rfc6455#section-4.1">CLIENT</a> vs a <a href="https://tools.ietf.org/html/rfc6455#section-4.2">SERVER</a>
+ */
+public enum WebSocketBehavior
+{
+    CLIENT,
+    SERVER;
+}
diff --git a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketException.java b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketException.java
new file mode 100644
index 0000000..23a3b77
--- /dev/null
+++ b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketException.java
@@ -0,0 +1,46 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.api;
+
+/**
+ * A recoverable exception within the websocket framework.
+ */
+ at SuppressWarnings("serial")
+public class WebSocketException extends RuntimeException
+{
+    public WebSocketException()
+    {
+        super();
+    }
+
+    public WebSocketException(String message)
+    {
+        super(message);
+    }
+
+    public WebSocketException(String message, Throwable cause)
+    {
+        super(message,cause);
+    }
+
+    public WebSocketException(Throwable cause)
+    {
+        super(cause);
+    }
+}
diff --git a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketListener.java b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketListener.java
new file mode 100644
index 0000000..b860951
--- /dev/null
+++ b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketListener.java
@@ -0,0 +1,80 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.api;
+
+/**
+ * Basic WebSocket Listener interface for incoming WebSocket events.
+ */
+public interface WebSocketListener
+{
+    /**
+     * A WebSocket binary frame has been received.
+     * 
+     * @param payload
+     *            the raw payload array received
+     * @param offset
+     *            the offset in the payload array where the data starts
+     * @param len
+     *            the length of bytes in the payload
+     */
+    void onWebSocketBinary(byte payload[], int offset, int len);
+
+    /**
+     * A Close Event was received.
+     * <p>
+     * The underlying Connection will be considered closed at this point.
+     * 
+     * @param statusCode
+     *            the close status code. (See {@link StatusCode})
+     * @param reason
+     *            the optional reason for the close.
+     */
+    void onWebSocketClose(int statusCode, String reason);
+
+    /**
+     * A WebSocket {@link Session} has connected successfully and is ready to be used.
+     * <p>
+     * Note: It is a good idea to track this session as a field in your object so that you can write messages back via the {@link RemoteEndpoint}
+     * 
+     * @param session
+     *            the websocket session.
+     */
+    void onWebSocketConnect(Session session);
+
+    /**
+     * A WebSocket exception has occurred.
+     * <p>
+     * This is a way for the internal implementation to notify of exceptions occured during the processing of websocket.
+     * <p>
+     * Usually this occurs from bad / malformed incoming packets. (example: bad UTF8 data, frames that are too big, violations of the spec)
+     * <p>
+     * This will result in the {@link Session} being closed by the implementing side.
+     * 
+     * @param error
+     *            the error that occurred.
+     */
+    void onWebSocketError(Throwable cause);
+
+    /**
+     * A WebSocket Text frame was received.
+     * 
+     * @param message
+     */
+    void onWebSocketText(String message);
+}
diff --git a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketPolicy.java b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketPolicy.java
new file mode 100644
index 0000000..ec4a050
--- /dev/null
+++ b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketPolicy.java
@@ -0,0 +1,358 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.api;
+
+/**
+ * Settings for WebSocket operations.
+ */
+public class WebSocketPolicy
+{
+    private static final int KB = 1024;
+
+    public static WebSocketPolicy newClientPolicy()
+    {
+        return new WebSocketPolicy(WebSocketBehavior.CLIENT);
+    }
+
+    public static WebSocketPolicy newServerPolicy()
+    {
+        return new WebSocketPolicy(WebSocketBehavior.SERVER);
+    }
+
+    /**
+     * The maximum size of a text message during parsing/generating.
+     * <p>
+     * Text messages over this maximum will result in a close code 1009 {@link StatusCode#MESSAGE_TOO_LARGE}
+     * <p>
+     * Default: 65536 (64 K)
+     */
+    private int maxTextMessageSize = 64 * KB;
+
+    /**
+     * The maximum size of a text message buffer.
+     * <p>
+     * Used ONLY for stream based message writing.
+     * <p>
+     * Default: 32768 (32 K)
+     */
+    private int maxTextMessageBufferSize = 32 * KB;
+
+    /**
+     * The maximum size of a binary message during parsing/generating.
+     * <p>
+     * Binary messages over this maximum will result in a close code 1009 {@link StatusCode#MESSAGE_TOO_LARGE}
+     * <p>
+     * Default: 65536 (64 K)
+     */
+    private int maxBinaryMessageSize = 64 * KB;
+
+    /**
+     * The maximum size of a binary message buffer
+     * <p>
+     * Used ONLY for for stream based message writing
+     * <p>
+     * Default: 32768 (32 K)
+     */
+    private int maxBinaryMessageBufferSize = 32 * KB;
+
+    /**
+     * The timeout in ms (milliseconds) for async write operations.
+     * <p>
+     * Negative values indicate a disabled timeout.
+     */
+    private long asyncWriteTimeout = 60000;
+
+    /**
+     * The time in ms (milliseconds) that a websocket may be idle before closing.
+     * <p>
+     * Default: 300000 (ms)
+     */
+    private long idleTimeout = 300000;
+
+    /**
+     * The size of the input (read from network layer) buffer size.
+     * <p>
+     * Default: 4096 (4 K)
+     */
+    private int inputBufferSize = 4 * KB;
+
+    /**
+     * Behavior of the websockets
+     */
+    private final WebSocketBehavior behavior;
+
+    public WebSocketPolicy(WebSocketBehavior behavior)
+    {
+        this.behavior = behavior;
+    }
+
+    private void assertLessThan(String name, long size, String otherName, long otherSize)
+    {
+        if (size > otherSize)
+        {
+            throw new IllegalArgumentException(String.format("%s [%d] must be less than %s [%d]",name,size,otherName,otherSize));
+        }
+    }
+
+    private void assertGreaterThan(String name, long size, long minSize)
+    {
+        if (size < minSize)
+        {
+            throw new IllegalArgumentException(String.format("%s [%d] must be a greater than or equal to " + minSize,name,size));
+        }
+    }
+
+    public void assertValidBinaryMessageSize(int requestedSize)
+    {
+        if (maxBinaryMessageSize > 0)
+        {
+            // validate it
+            if (requestedSize > maxBinaryMessageSize)
+            {
+                throw new MessageTooLargeException("Binary message size [" + requestedSize + "] exceeds maximum size [" + maxBinaryMessageSize + "]");
+            }
+        }
+    }
+
+    public void assertValidTextMessageSize(int requestedSize)
+    {
+        if (maxTextMessageSize > 0)
+        {
+            // validate it
+            if (requestedSize > maxTextMessageSize)
+            {
+                throw new MessageTooLargeException("Text message size [" + requestedSize + "] exceeds maximum size [" + maxTextMessageSize + "]");
+            }
+        }
+    }
+
+    public WebSocketPolicy clonePolicy()
+    {
+        WebSocketPolicy clone = new WebSocketPolicy(this.behavior);
+        clone.idleTimeout = this.idleTimeout;
+        clone.maxTextMessageSize = this.maxTextMessageSize;
+        clone.maxTextMessageBufferSize = this.maxTextMessageBufferSize;
+        clone.maxBinaryMessageSize = this.maxBinaryMessageSize;
+        clone.maxBinaryMessageBufferSize = this.maxBinaryMessageBufferSize;
+        clone.inputBufferSize = this.inputBufferSize;
+        clone.asyncWriteTimeout = this.asyncWriteTimeout;
+        return clone;
+    }
+
+    /**
+     * The timeout in ms (milliseconds) for async write operations.
+     * <p>
+     * Negative values indicate a disabled timeout.
+     * 
+     * @return the timeout for async write operations. negative values indicate disabled timeout.
+     */
+    public long getAsyncWriteTimeout()
+    {
+        return asyncWriteTimeout;
+    }
+
+    public WebSocketBehavior getBehavior()
+    {
+        return behavior;
+    }
+
+    /**
+     * The time in ms (milliseconds) that a websocket connection mad by idle before being closed automatically.
+     * 
+     * @return the timeout in milliseconds for idle timeout.
+     */
+    public long getIdleTimeout()
+    {
+        return idleTimeout;
+    }
+
+    /**
+     * The size of the input (read from network layer) buffer size.
+     * <p>
+     * This is the raw read operation buffer size, before the parsing of the websocket frames.
+     * 
+     * @return the raw network bytes read operation buffer size.
+     */
+    public int getInputBufferSize()
+    {
+        return inputBufferSize;
+    }
+
+    /**
+     * Get the maximum size of a binary message buffer (for streaming writing)
+     * 
+     * @return the maximum size of a binary message buffer
+     */
+    public int getMaxBinaryMessageBufferSize()
+    {
+        return maxBinaryMessageBufferSize;
+    }
+
+    /**
+     * Get the maximum size of a binary message during parsing/generating.
+     * <p>
+     * Binary messages over this maximum will result in a close code 1009 {@link StatusCode#MESSAGE_TOO_LARGE}
+     * 
+     * @return the maximum size of a binary message
+     */
+    public int getMaxBinaryMessageSize()
+    {
+        return maxBinaryMessageSize;
+    }
+
+    /**
+     * Get the maximum size of a text message buffer (for streaming writing)
+     * 
+     * @return the maximum size of a text message buffer
+     */
+    public int getMaxTextMessageBufferSize()
+    {
+        return maxTextMessageBufferSize;
+    }
+
+    /**
+     * Get the maximum size of a text message during parsing/generating.
+     * <p>
+     * Text messages over this maximum will result in a close code 1009 {@link StatusCode#MESSAGE_TOO_LARGE}
+     * 
+     * @return the maximum size of a text message.
+     */
+    public int getMaxTextMessageSize()
+    {
+        return maxTextMessageSize;
+    }
+
+    /**
+     * The timeout in ms (milliseconds) for async write operations.
+     * <p>
+     * Negative values indicate a disabled timeout.
+     * 
+     * @param ms
+     *            the timeout in milliseconds
+     */
+    public void setAsyncWriteTimeout(long ms)
+    {
+        assertLessThan("AsyncWriteTimeout",ms,"IdleTimeout",idleTimeout);
+        this.asyncWriteTimeout = ms;
+    }
+
+    /**
+     * The time in ms (milliseconds) that a websocket may be idle before closing.
+     * 
+     * @param ms
+     *            the timeout in milliseconds
+     */
+    public void setIdleTimeout(long ms)
+    {
+        assertGreaterThan("IdleTimeout",ms,0);
+        this.idleTimeout = ms;
+    }
+
+    /**
+     * The size of the input (read from network layer) buffer size.
+     * 
+     * @param size
+     *            the size in bytes
+     */
+    public void setInputBufferSize(int size)
+    {
+        assertGreaterThan("InputBufferSize",size,1);
+        assertLessThan("InputBufferSize",size,"MaxTextMessageBufferSize",maxTextMessageBufferSize);
+        assertLessThan("InputBufferSize",size,"MaxBinaryMessageBufferSize",maxBinaryMessageBufferSize);
+
+        this.inputBufferSize = size;
+    }
+
+    /**
+     * The maximum size of a binary message buffer.
+     * <p>
+     * Used ONLY for stream based message writing.
+     * 
+     * @param size
+     *            the maximum size of the binary message buffer
+     */
+    public void setMaxBinaryMessageBufferSize(int size)
+    {
+        assertGreaterThan("MaxBinaryMessageBufferSize",size,1);
+
+        this.maxBinaryMessageBufferSize = size;
+    }
+
+    /**
+     * The maximum size of a binary message during parsing/generating.
+     * <p>
+     * Binary messages over this maximum will result in a close code 1009 {@link StatusCode#MESSAGE_TOO_LARGE}
+     * 
+     * @param size
+     *            the maximum allowed size of a binary message.
+     */
+    public void setMaxBinaryMessageSize(int size)
+    {
+        assertGreaterThan("MaxBinaryMessageSize",size,1);
+
+        this.maxBinaryMessageSize = size;
+    }
+
+    /**
+     * The maximum size of a text message buffer.
+     * <p>
+     * Used ONLY for stream based message writing.
+     * 
+     * @param size
+     *            the maximum size of the text message buffer
+     */
+    public void setMaxTextMessageBufferSize(int size)
+    {
+        assertGreaterThan("MaxTextMessageBufferSize",size,1);
+
+        this.maxTextMessageBufferSize = size;
+    }
+
+    /**
+     * The maximum size of a text message during parsing/generating.
+     * <p>
+     * Text messages over this maximum will result in a close code 1009 {@link StatusCode#MESSAGE_TOO_LARGE}
+     * 
+     * @param size
+     *            the maximum allowed size of a text message.
+     */
+    public void setMaxTextMessageSize(int size)
+    {
+        assertGreaterThan("MaxTextMessageSize",size,1);
+
+        this.maxTextMessageSize = size;
+    }
+
+    @Override
+    public String toString()
+    {
+        StringBuilder builder = new StringBuilder();
+        builder.append("WebSocketPolicy@").append(Integer.toHexString(hashCode()));
+        builder.append("[behavior=").append(behavior);
+        builder.append(",maxTextMessageSize=").append(maxTextMessageSize);
+        builder.append(",maxTextMessageBufferSize=").append(maxTextMessageBufferSize);
+        builder.append(",maxBinaryMessageSize=").append(maxBinaryMessageSize);
+        builder.append(",maxBinaryMessageBufferSize=").append(maxBinaryMessageBufferSize);
+        builder.append(",asyncWriteTimeout=").append(asyncWriteTimeout);
+        builder.append(",idleTimeout=").append(idleTimeout);
+        builder.append(",inputBufferSize=").append(inputBufferSize);
+        builder.append("]");
+        return builder.toString();
+    }
+}
diff --git a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketTimeoutException.java b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketTimeoutException.java
new file mode 100644
index 0000000..375b16e
--- /dev/null
+++ b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketTimeoutException.java
@@ -0,0 +1,42 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.api;
+
+/**
+ * Exception thrown to indicate a connection I/O timeout.
+ */
+public class WebSocketTimeoutException extends WebSocketException
+{
+    private static final long serialVersionUID = -6145098200250676673L;
+
+    public WebSocketTimeoutException(String message)
+    {
+        super(message);
+    }
+
+    public WebSocketTimeoutException(String message, Throwable cause)
+    {
+        super(message,cause);
+    }
+
+    public WebSocketTimeoutException(Throwable cause)
+    {
+        super(cause);
+    }
+}
diff --git a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/WriteCallback.java b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/WriteCallback.java
new file mode 100644
index 0000000..9f6f697
--- /dev/null
+++ b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/WriteCallback.java
@@ -0,0 +1,48 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.api;
+
+/**
+ * Callback for Write events.
+ */
+public interface WriteCallback
+{
+    /*
+     * NOTE: We don't expose org.eclipse.jetty.util.Callback here as that would complicate matters with the WebAppContext's classloader isolation.
+     */
+
+    /**
+     * <p>
+     * Callback invoked when the write fails.
+     * </p>
+     * 
+     * @param x
+     *            the reason for the write failure
+     */
+    public void writeFailed(Throwable x);
+
+    /**
+     * <p>
+     * Callback invoked when the write completes.
+     * </p>
+     * 
+     * @see #writeFailed(Throwable)
+     */
+    public abstract void writeSuccess();
+}
diff --git a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/annotations/OnWebSocketClose.java b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/annotations/OnWebSocketClose.java
new file mode 100644
index 0000000..a29fab3
--- /dev/null
+++ b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/annotations/OnWebSocketClose.java
@@ -0,0 +1,44 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.api.annotations;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation for tagging methods to receive connection close events.
+ * <p>
+ * Acceptable method patterns.<br>
+ * Note: <code>methodName</code> can be any name you want to use.
+ * <ol>
+ * <li><code>public void methodName(int statusCode, String reason)</code></li>
+ * <li><code>public void methodName({@link Session} session, int statusCode, String reason)</code></li>
+ * </ol>
+ */
+ at Documented
+ at Retention(RetentionPolicy.RUNTIME)
+ at Target(value =
+{ ElementType.METHOD })
+public @interface OnWebSocketClose
+{
+    /* no config */
+}
diff --git a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/annotations/OnWebSocketConnect.java b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/annotations/OnWebSocketConnect.java
new file mode 100644
index 0000000..89690f3
--- /dev/null
+++ b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/annotations/OnWebSocketConnect.java
@@ -0,0 +1,43 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.api.annotations;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation for tagging methods to receive connection open events.
+ * <p>
+ * Only 1 acceptable method pattern for this annotation.<br>
+ * Note: <code>methodName</code> can be any name you want to use.
+ * <ol>
+ * <li><code>public void methodName({@link Session} session)</code></li>
+ * </ol>
+ */
+ at Documented
+ at Retention(RetentionPolicy.RUNTIME)
+ at Target(value =
+{ ElementType.METHOD })
+public @interface OnWebSocketConnect
+{
+    /* no config */
+}
diff --git a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/annotations/OnWebSocketError.java b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/annotations/OnWebSocketError.java
new file mode 100644
index 0000000..1fb9e6b
--- /dev/null
+++ b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/annotations/OnWebSocketError.java
@@ -0,0 +1,45 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.api.annotations;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation for receiving websocket errors (exceptions) that have occurred internally in the websocket implementation.
+ * <p>
+ * Acceptable method patterns.<br>
+ * Note: <code>methodName</code> can be any name you want to use.
+ * <p>
+ * <ol>
+ * <li><code>public void methodName({@link Throwable} error)</code></li>
+ * <li><code>public void methodName({@link Session} session, {@link Throwable} error)</code></li>
+ * </ol>
+ */
+ at Documented
+ at Retention(RetentionPolicy.RUNTIME)
+ at Target(value =
+{ ElementType.METHOD })
+public @interface OnWebSocketError
+{
+    /* no config */
+}
diff --git a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/annotations/OnWebSocketFrame.java b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/annotations/OnWebSocketFrame.java
new file mode 100644
index 0000000..f3bcd0d
--- /dev/null
+++ b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/annotations/OnWebSocketFrame.java
@@ -0,0 +1,44 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.api.annotations;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * (ADVANCED) Annotation for tagging methods to receive frame events.
+ * <p>
+ * Acceptable method patterns.<br>
+ * Note: <code>methodName</code> can be any name you want to use.
+ * <ol>
+ * <li><code>public void methodName({@link Frame} frame)</code></li>
+ * <li><code>public void methodName({@link Session} session, {@link Frame} frame)</code></li>
+ * </ol>
+ */
+ at Documented
+ at Retention(RetentionPolicy.RUNTIME)
+ at Target(value =
+{ ElementType.METHOD })
+public @interface OnWebSocketFrame
+{
+    /* no config */
+}
diff --git a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/annotations/OnWebSocketMessage.java b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/annotations/OnWebSocketMessage.java
new file mode 100644
index 0000000..74f687b
--- /dev/null
+++ b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/annotations/OnWebSocketMessage.java
@@ -0,0 +1,58 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.api.annotations;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation for tagging methods to receive Binary or Text Message events.
+ * <p>
+ * Acceptable method patterns.<br>
+ * Note: <code>methodName</code> can be any name you want to use.
+ * <p>
+ * <u>Text Message Versions</u>
+ * <ol>
+ * <li><code>public void methodName(String text)</code></li>
+ * <li><code>public void methodName({@link Session} session, String text)</code></li>
+ * <li><code>public void methodName(Reader reader)</code></li>
+ * <li><code>public void methodName({@link Session} session, Reader reader)</code></li>
+ * </ol>
+ * Note: that the {@link Reader} in this case will always use UTF-8 encoding/charset (this is dictated by the RFC 6455 spec for Text Messages. If you need to
+ * use a non-UTF-8 encoding/charset, you are instructed to use the binary messaging techniques.
+ * <p>
+ * <u>Binary Message Versions</u>
+ * <ol>
+ * <li><code>public void methodName(byte buf[], int offset, int length)</code></li>
+ * <li><code>public void methodName({@link Session} session, byte buf[], int offset, int length)</code></li>
+ * <li><code>public void methodName(InputStream stream)</code></li>
+ * <li><code>public void methodName({@link Session} session, InputStream stream)</code></li>
+ * </ol>
+ */
+ at Documented
+ at Retention(RetentionPolicy.RUNTIME)
+ at Target(value =
+{ ElementType.METHOD })
+public @interface OnWebSocketMessage
+{
+    /* no config */
+}
diff --git a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/annotations/WebSocket.java b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/annotations/WebSocket.java
new file mode 100644
index 0000000..25d1001
--- /dev/null
+++ b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/annotations/WebSocket.java
@@ -0,0 +1,43 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.api.annotations;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Tags a POJO as being a WebSocket class.
+ */
+ at Documented
+ at Retention(RetentionPolicy.RUNTIME)
+ at Target(value =
+{ ElementType.TYPE })
+public @interface WebSocket
+{
+    int inputBufferSize() default -2;
+
+    int maxBinaryMessageSize() default -2;
+
+    int maxIdleTime() default -2;
+
+    int maxTextMessageSize() default -2;
+}
diff --git a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/annotations/package-info.java b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/annotations/package-info.java
new file mode 100644
index 0000000..19ca236
--- /dev/null
+++ b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/annotations/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty WebSocket API : WebSocket POJO Annotations
+ */
+package org.eclipse.jetty.websocket.api.annotations;
+
diff --git a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/extensions/Extension.java b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/extensions/Extension.java
new file mode 100644
index 0000000..ebcb7da
--- /dev/null
+++ b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/extensions/Extension.java
@@ -0,0 +1,89 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.api.extensions;
+
+/**
+ * Interface for WebSocket Extensions.
+ * <p>
+ * That {@link Frame}s are passed through the Extension via the {@link IncomingFrames} and {@link OutgoingFrames} interfaces
+ */
+public interface Extension extends IncomingFrames, OutgoingFrames
+{
+    /**
+     * The active configuration for this extension.
+     * 
+     * @return the configuration for this extension. never null.
+     */
+    public ExtensionConfig getConfig();
+
+    /**
+     * The <code>Sec-WebSocket-Extensions</code> name for this extension.
+     * <p>
+     * Also known as the <a href="https://tools.ietf.org/html/rfc6455#section-9.1"><code>extension-token</code> per Section 9.1. Negotiating Extensions</a>.
+     */
+    public String getName();
+
+    /**
+     * Used to indicate that the extension makes use of the RSV1 bit of the base websocket framing.
+     * <p>
+     * This is used to adjust validation during parsing, as well as a checkpoint against 2 or more extensions all simultaneously claiming ownership of RSV1.
+     * 
+     * @return true if extension uses RSV1 for its own purposes.
+     */
+    public abstract boolean isRsv1User();
+
+    /**
+     * Used to indicate that the extension makes use of the RSV2 bit of the base websocket framing.
+     * <p>
+     * This is used to adjust validation during parsing, as well as a checkpoint against 2 or more extensions all simultaneously claiming ownership of RSV2.
+     * 
+     * @return true if extension uses RSV2 for its own purposes.
+     */
+    public abstract boolean isRsv2User();
+
+    /**
+     * Used to indicate that the extension makes use of the RSV3 bit of the base websocket framing.
+     * <p>
+     * This is used to adjust validation during parsing, as well as a checkpoint against 2 or more extensions all simultaneously claiming ownership of RSV3.
+     * 
+     * @return true if extension uses RSV3 for its own purposes.
+     */
+    public abstract boolean isRsv3User();
+
+    /**
+     * Set the next {@link IncomingFrames} to call in the chain.
+     * 
+     * @param nextIncoming
+     *            the next incoming extension
+     */
+    public void setNextIncomingFrames(IncomingFrames nextIncoming);
+
+    /**
+     * Set the next {@link OutgoingFrames} to call in the chain.
+     * 
+     * @param nextOutgoing
+     *            the next outgoing extension
+     */
+    public void setNextOutgoingFrames(OutgoingFrames nextOutgoing);
+    
+    // TODO: Extension should indicate if it requires boundary of fragments to be preserved
+    
+    // TODO: Extension should indicate if it uses the Extension data field of frame for its own reasons.
+    
+}
diff --git a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/extensions/ExtensionConfig.java b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/extensions/ExtensionConfig.java
new file mode 100644
index 0000000..1041bc5
--- /dev/null
+++ b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/extensions/ExtensionConfig.java
@@ -0,0 +1,250 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.api.extensions;
+
+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 java.util.Set;
+
+import org.eclipse.jetty.websocket.api.util.QuoteUtil;
+
+/**
+ * Represents an Extension Configuration, as seen during the connection Handshake process.
+ */
+public class ExtensionConfig
+{
+    /**
+     * Parse a single parameterized name.
+     * 
+     * @param parameterizedName
+     *            the parameterized name
+     * @return the ExtensionConfig
+     */
+    public static ExtensionConfig parse(String parameterizedName)
+    {
+        return new ExtensionConfig(parameterizedName);
+    }
+
+    /**
+     * Parse enumeration of <code>Sec-WebSocket-Extensions</code> header values into a {@link ExtensionConfig} list
+     * 
+     * @param valuesEnum
+     *            the raw header values enum
+     * @return the list of extension configs
+     */
+    public static List<ExtensionConfig> parseEnum(Enumeration<String> valuesEnum)
+    {
+        List<ExtensionConfig> configs = new ArrayList<>();
+
+        if (valuesEnum != null)
+        {
+            while (valuesEnum.hasMoreElements())
+            {
+                Iterator<String> extTokenIter = QuoteUtil.splitAt(valuesEnum.nextElement(),",");
+                while (extTokenIter.hasNext())
+                {
+                    String extToken = extTokenIter.next();
+                    configs.add(ExtensionConfig.parse(extToken));
+                }
+            }
+        }
+
+        return configs;
+    }
+
+    /**
+     * Parse 1 or more raw <code>Sec-WebSocket-Extensions</code> header values into a {@link ExtensionConfig} list
+     * 
+     * @param rawSecWebSocketExtensions
+     *            the raw header values
+     * @return the list of extension configs
+     */
+    public static List<ExtensionConfig> parseList(String... rawSecWebSocketExtensions)
+    {
+        List<ExtensionConfig> configs = new ArrayList<>();
+
+        for (String rawValue : rawSecWebSocketExtensions)
+        {
+            Iterator<String> extTokenIter = QuoteUtil.splitAt(rawValue,",");
+            while (extTokenIter.hasNext())
+            {
+                String extToken = extTokenIter.next();
+                configs.add(ExtensionConfig.parse(extToken));
+            }
+        }
+
+        return configs;
+    }
+
+    /**
+     * Convert a list of {@link ExtensionConfig} to a header value
+     * 
+     * @param configs
+     *            the list of extension configs
+     * @return the header value (null if no configs present)
+     */
+    public static String toHeaderValue(List<ExtensionConfig> configs)
+    {
+        if ((configs == null) || (configs.isEmpty()))
+        {
+            return null;
+        }
+        StringBuilder parameters = new StringBuilder();
+        boolean needsDelim = false;
+        for (ExtensionConfig ext : configs)
+        {
+            if (needsDelim)
+            {
+                parameters.append(", ");
+            }
+            parameters.append(ext.getParameterizedName());
+            needsDelim = true;
+        }
+        return parameters.toString();
+    }
+
+    private final String name;
+    private final Map<String, String> parameters;
+
+    /**
+     * Copy constructor
+     */
+    public ExtensionConfig(ExtensionConfig copy)
+    {
+        this.name = copy.name;
+        this.parameters = new HashMap<>();
+        this.parameters.putAll(copy.parameters);
+    }
+
+    public ExtensionConfig(String parameterizedName)
+    {
+        Iterator<String> extListIter = QuoteUtil.splitAt(parameterizedName,";");
+        this.name = extListIter.next();
+        this.parameters = new HashMap<>();
+
+        // now for parameters
+        while (extListIter.hasNext())
+        {
+            String extParam = extListIter.next();
+            Iterator<String> extParamIter = QuoteUtil.splitAt(extParam,"=");
+            String key = extParamIter.next().trim();
+            String value = null;
+            if (extParamIter.hasNext())
+            {
+                value = extParamIter.next();
+            }
+            parameters.put(key,value);
+        }
+    }
+
+    public String getName()
+    {
+        return name;
+    }
+
+    public final int getParameter(String key, int defValue)
+    {
+        String val = parameters.get(key);
+        if (val == null)
+        {
+            return defValue;
+        }
+        return Integer.valueOf(val);
+    }
+
+    public final String getParameter(String key, String defValue)
+    {
+        String val = parameters.get(key);
+        if (val == null)
+        {
+            return defValue;
+        }
+        return val;
+    }
+
+    public final String getParameterizedName()
+    {
+        StringBuilder str = new StringBuilder();
+        str.append(name);
+        for (String param : parameters.keySet())
+        {
+            str.append(';');
+            str.append(param);
+            String value = parameters.get(param);
+            if (value != null)
+            {
+                str.append('=');
+                QuoteUtil.quoteIfNeeded(str,value,";=");
+            }
+        }
+        return str.toString();
+    }
+
+    public final Set<String> getParameterKeys()
+    {
+        return parameters.keySet();
+    }
+
+    /**
+     * Return parameters found in request URI.
+     * 
+     * @return the parameter map
+     */
+    public final Map<String, String> getParameters()
+    {
+        return parameters;
+    }
+
+    /**
+     * Initialize the parameters on this config from the other configuration.
+     * 
+     * @param other
+     *            the other configuration.
+     */
+    public final void init(ExtensionConfig other)
+    {
+        this.parameters.clear();
+        this.parameters.putAll(other.parameters);
+    }
+
+    public final void setParameter(String key)
+    {
+        parameters.put(key,null);
+    }
+
+    public final void setParameter(String key, int value)
+    {
+        parameters.put(key,Integer.toString(value));
+    }
+
+    public final void setParameter(String key, String value)
+    {
+        parameters.put(key,value);
+    }
+
+    @Override
+    public String toString()
+    {
+        return getParameterizedName();
+    }
+}
diff --git a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/extensions/ExtensionFactory.java b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/extensions/ExtensionFactory.java
new file mode 100644
index 0000000..336cb18
--- /dev/null
+++ b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/extensions/ExtensionFactory.java
@@ -0,0 +1,81 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.api.extensions;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.ServiceLoader;
+import java.util.Set;
+
+public abstract class ExtensionFactory implements Iterable<Class<? extends Extension>>
+{
+    private ServiceLoader<Extension> extensionLoader = ServiceLoader.load(Extension.class);
+    private Map<String, Class<? extends Extension>> availableExtensions;
+
+    public ExtensionFactory()
+    {
+        availableExtensions = new HashMap<>();
+        for (Extension ext : extensionLoader)
+        {
+            if (ext != null)
+            {
+                availableExtensions.put(ext.getName(),ext.getClass());
+            }
+        }
+    }
+
+    public Map<String, Class<? extends Extension>> getAvailableExtensions()
+    {
+        return availableExtensions;
+    }
+
+    public Class<? extends Extension> getExtension(String name)
+    {
+        return availableExtensions.get(name);
+    }
+
+    public Set<String> getExtensionNames()
+    {
+        return availableExtensions.keySet();
+    }
+
+    public boolean isAvailable(String name)
+    {
+        return availableExtensions.containsKey(name);
+    }
+
+    @Override
+    public Iterator<Class<? extends Extension>> iterator()
+    {
+        return availableExtensions.values().iterator();
+    }
+
+    public abstract Extension newInstance(ExtensionConfig config);
+
+    public void register(String name, Class<? extends Extension> extension)
+    {
+        availableExtensions.put(name,extension);
+    }
+
+    public void unregister(String name)
+    {
+        availableExtensions.remove(name);
+    }
+}
diff --git a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/extensions/Frame.java b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/extensions/Frame.java
new file mode 100644
index 0000000..3dec2fa
--- /dev/null
+++ b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/extensions/Frame.java
@@ -0,0 +1,118 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.api.extensions;
+
+import java.nio.ByteBuffer;
+
+/**
+ * An immutable websocket frame.
+ */
+public interface Frame
+{
+    public static enum Type
+    {
+        CONTINUATION((byte)0x00),
+        TEXT((byte)0x01),
+        BINARY((byte)0x02),
+        CLOSE((byte)0x08),
+        PING((byte)0x09),
+        PONG((byte)0x0A);
+
+        public static Type from(byte op)
+        {
+            for (Type type : values())
+            {
+                if (type.opcode == op)
+                {
+                    return type;
+                }
+            }
+            throw new IllegalArgumentException("OpCode " + op + " is not a valid Frame.Type");
+        }
+
+        private byte opcode;
+
+        private Type(byte code)
+        {
+            this.opcode = code;
+        }
+
+        public byte getOpCode()
+        {
+            return opcode;
+        }
+
+        public boolean isControl()
+        {
+            return (opcode >= CLOSE.getOpCode());
+        }
+
+        public boolean isData()
+        {
+            return (opcode == TEXT.getOpCode()) | (opcode == BINARY.getOpCode());
+        }
+
+        public boolean isContinuation()
+        {
+            return opcode == CONTINUATION.getOpCode();
+        }
+
+        @Override
+        public String toString()
+        {
+            return this.name();
+        }
+    }
+
+    public byte[] getMask();
+
+    public byte getOpCode();
+
+    public ByteBuffer getPayload();
+
+    /**
+     * The original payload length ({@link ByteBuffer#remaining()})
+     * 
+     * @return the original payload length ({@link ByteBuffer#remaining()})
+     */
+    public int getPayloadLength();
+
+    public Type getType();
+
+    public boolean hasPayload();
+
+    public boolean isFin();
+
+    /**
+     * Same as {@link #isFin()}
+     * 
+     * @return true if final frame.
+     */
+    // FIXME: remove
+    public boolean isLast();
+
+    public boolean isMasked();
+
+    public boolean isRsv1();
+
+    public boolean isRsv2();
+
+    public boolean isRsv3();
+
+}
diff --git a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/extensions/IncomingFrames.java b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/extensions/IncomingFrames.java
new file mode 100644
index 0000000..e79e702
--- /dev/null
+++ b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/extensions/IncomingFrames.java
@@ -0,0 +1,38 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.api.extensions;
+
+/**
+ * Interface for dealing with Incoming Frames.
+ */
+public interface IncomingFrames
+{
+    public void incomingError(Throwable t);
+
+    /**
+     * Process the incoming frame.
+     * <p>
+     * Note: if you need to hang onto any information from the frame, be sure
+     * to copy it, as the information contained in the Frame will be released
+     * and/or reused by the implementation.
+     * 
+     * @param frame the frame to process
+     */
+    public void incomingFrame(Frame frame);
+}
diff --git a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/extensions/OutgoingFrames.java b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/extensions/OutgoingFrames.java
new file mode 100644
index 0000000..2f6cea3
--- /dev/null
+++ b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/extensions/OutgoingFrames.java
@@ -0,0 +1,44 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.api.extensions;
+
+import org.eclipse.jetty.websocket.api.BatchMode;
+import org.eclipse.jetty.websocket.api.WriteCallback;
+
+/**
+ * Interface for dealing with frames outgoing to (eventually) the network layer.
+ */
+public interface OutgoingFrames
+{
+    /**
+     * A frame, and optional callback, intended for the network layer.
+     * <p/>
+     * Note: the frame can undergo many transformations in the various
+     * layers and extensions present in the implementation.
+     * <p/>
+     * If you are implementing a mutation, you are obliged to handle
+     * the incoming WriteCallback appropriately.
+     *
+     * @param frame     the frame to eventually write to the network layer.
+     * @param callback  the callback to notify when the frame is written.
+     * @param batchMode the batch mode requested by the sender.
+     */
+    void outgoingFrame(Frame frame, WriteCallback callback, BatchMode batchMode);
+
+}
diff --git a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/extensions/package-info.java b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/extensions/package-info.java
new file mode 100644
index 0000000..468cd09
--- /dev/null
+++ b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/extensions/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty WebSocket API : WebSocket Extension API
+ */
+package org.eclipse.jetty.websocket.api.extensions;
+
diff --git a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/package-info.java b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/package-info.java
new file mode 100644
index 0000000..99aeb9f
--- /dev/null
+++ b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty WebSocket API
+ */
+package org.eclipse.jetty.websocket.api;
+
diff --git a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/util/QuoteUtil.java b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/util/QuoteUtil.java
new file mode 100644
index 0000000..faf9674
--- /dev/null
+++ b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/util/QuoteUtil.java
@@ -0,0 +1,511 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.api.util;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * Provide some consistent Http header value and Extension configuration parameter quoting support.
+ * <p>
+ * While QuotedStringTokenizer exists in jetty-util, and works great with http header values, using it in websocket-api is undesired.
+ * <p>
+ * <ul>
+ * <li>Using QuotedStringTokenizer would introduce a dependency to jetty-util that would need to be exposed via the WebAppContext classloader</li>
+ * <li>ABNF defined extension parameter parsing requirements of RFC-6455 (WebSocket) ABNF, is slightly different than the ABNF parsing defined in RFC-2616
+ * (HTTP/1.1).</li>
+ * <li>Future HTTPbis ABNF changes for parsing will impact QuotedStringTokenizer</li>
+ * </ul>
+ * It was decided to keep this implementation separate for the above reasons.
+ */
+public class QuoteUtil
+{
+    private static class DeQuotingStringIterator implements Iterator<String>
+    {
+        private enum State
+        {
+            START,
+            TOKEN,
+            QUOTE_SINGLE,
+            QUOTE_DOUBLE
+        }
+
+        private static final boolean DEBUG = false;
+
+        private final String input;
+        private final String delims;
+        private StringBuilder token;
+        private boolean hasToken = false;
+        private int i = 0;
+
+        public DeQuotingStringIterator(String input, String delims)
+        {
+            this.input = input;
+            this.delims = delims;
+            int len = input.length();
+            token = new StringBuilder(len > 1024?512:len / 2);
+        }
+
+        private void appendToken(char c)
+        {
+            if (hasToken)
+            {
+                token.append(c);
+            }
+            else
+            {
+                if (Character.isWhitespace(c))
+                {
+                    return; // skip whitespace at start of token.
+                }
+                else
+                {
+                    token.append(c);
+                    hasToken = true;
+                }
+            }
+        }
+
+        private void debug(String format, Object... args)
+        {
+            if (DEBUG)
+            {
+                System.out.printf(format,args);
+            }
+        }
+
+        @Override
+        public boolean hasNext()
+        {
+            // already found a token
+            if (hasToken)
+            {
+                return true;
+            }
+
+            State state = State.START;
+            boolean escape = false;
+            int inputLen = input.length();
+
+            while (i < inputLen)
+            {
+                char c = input.charAt(i++);
+
+                switch (state)
+                {
+                    case START:
+                    {
+                        if (c == '\'')
+                        {
+                            state = State.QUOTE_SINGLE;
+                            appendToken(c);
+                        }
+                        else if (c == '\"')
+                        {
+                            state = State.QUOTE_DOUBLE;
+                            appendToken(c);
+                        }
+                        else
+                        {
+                            appendToken(c);
+                            state = State.TOKEN;
+                        }
+                        break;
+                    }
+                    case TOKEN:
+                    {
+                        if (delims.indexOf(c) >= 0)
+                        {
+                            debug("hasNext/t: %b [%s]%n",hasToken,token);
+                            return hasToken;
+                        }
+                        else if (c == '\'')
+                        {
+                            state = State.QUOTE_SINGLE;
+                        }
+                        else if (c == '\"')
+                        {
+                            state = State.QUOTE_DOUBLE;
+                        }
+                        appendToken(c);
+                        break;
+                    }
+                    case QUOTE_SINGLE:
+                    {
+                        if (escape)
+                        {
+                            escape = false;
+                            appendToken(c);
+                        }
+                        else if (c == '\'')
+                        {
+                            appendToken(c);
+                            state = State.TOKEN;
+                        }
+                        else if (c == '\\')
+                        {
+                            escape = true;
+                        }
+                        else
+                        {
+                            appendToken(c);
+                        }
+                        break;
+                    }
+                    case QUOTE_DOUBLE:
+                    {
+                        if (escape)
+                        {
+                            escape = false;
+                            appendToken(c);
+                        }
+                        else if (c == '\"')
+                        {
+                            appendToken(c);
+                            state = State.TOKEN;
+                        }
+                        else if (c == '\\')
+                        {
+                            escape = true;
+                        }
+                        else
+                        {
+                            appendToken(c);
+                        }
+                        break;
+                    }
+                }
+                debug("%s <%s> : [%s]%n",state,c,token);
+            }
+
+            debug("hasNext/e: %b [%s]%n",hasToken,token);
+            return hasToken;
+        }
+
+        @Override
+        public String next()
+        {
+            if (!hasNext())
+            {
+                throw new NoSuchElementException();
+            }
+            String ret = token.toString();
+            token.setLength(0);
+            hasToken = false;
+            return QuoteUtil.dequote(ret.trim());
+        }
+
+        @Override
+        public void remove()
+        {
+            throw new UnsupportedOperationException("Remove not supported with this iterator");
+        }
+    }
+
+    /**
+     * ABNF from RFC 2616, RFC 822, and RFC 6455 specified characters requiring quoting.
+     */
+    public static final String ABNF_REQUIRED_QUOTING = "\"'\\\n\r\t\f\b%+ ;=";
+
+    private static final char UNICODE_TAG = 0xFFFF;
+    private static final char[] escapes = new char[32];
+
+    static
+    {
+        Arrays.fill(escapes,UNICODE_TAG);
+        // non-unicode
+        escapes['\b'] = 'b';
+        escapes['\t'] = 't';
+        escapes['\n'] = 'n';
+        escapes['\f'] = 'f';
+        escapes['\r'] = 'r';
+    }
+
+    private static int dehex(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);
+        }
+        throw new IllegalArgumentException("!hex:" + Integer.toHexString(0xff & b));
+    }
+
+    /**
+     * Remove quotes from a string, only if the input string start with and end with the same quote character.
+     * 
+     * @param str
+     *            the string to remove surrounding quotes from
+     * @return the de-quoted string
+     */
+    public static String dequote(String str)
+    {
+        char start = str.charAt(0);
+        if ((start == '\'') || (start == '\"'))
+        {
+            // possibly quoted
+            char end = str.charAt(str.length() - 1);
+            if (start == end)
+            {
+                // dequote
+                return str.substring(1,str.length() - 1);
+            }
+        }
+        return str;
+    }
+
+    public static void escape(StringBuilder buf, String str)
+    {
+        for (char c : str.toCharArray())
+        {
+            if (c >= 32)
+            {
+                // non special character
+                if ((c == '"') || (c == '\\'))
+                {
+                    buf.append('\\');
+                }
+                buf.append(c);
+            }
+            else
+            {
+                // special characters, requiring escaping
+                char escaped = escapes[c];
+
+                // is this a unicode escape?
+                if (escaped == UNICODE_TAG)
+                {
+                    buf.append("\\u00");
+                    if (c < 0x10)
+                    {
+                        buf.append('0');
+                    }
+                    buf.append(Integer.toString(c,16)); // hex
+                }
+                else
+                {
+                    // normal escape
+                    buf.append('\\').append(escaped);
+                }
+            }
+        }
+    }
+
+    /**
+     * Simple quote of a string, escaping where needed.
+     * 
+     * @param buf
+     *            the StringBuilder to append to
+     * @param str
+     *            the string to quote
+     */
+    public static void quote(StringBuilder buf, String str)
+    {
+        buf.append('"');
+        escape(buf,str);
+        buf.append('"');
+    }
+
+    /**
+     * Append into buf the provided string, adding quotes if needed.
+     * <p>
+     * Quoting is determined if any of the characters in the <code>delim</code> are found in the input <code>str</code>.
+     * 
+     * @param buf
+     *            the buffer to append to
+     * @param str
+     *            the string to possibly quote
+     * @param delim
+     *            the delimiter characters that will trigger automatic quoting
+     */
+    public static void quoteIfNeeded(StringBuilder buf, String str, String delim)
+    {
+        if (str == null)
+        {
+            return;
+        }
+        // check for delimiters in input string
+        int len = str.length();
+        if (len == 0)
+        {
+            return;
+        }
+        int ch;
+        for (int i = 0; i < len; i++)
+        {
+            ch = str.codePointAt(i);
+            if (delim.indexOf(ch) >= 0)
+            {
+                // found a delimiter codepoint. we need to quote it.
+                quote(buf,str);
+                return;
+            }
+        }
+
+        // no special delimiters used, no quote needed.
+        buf.append(str);
+    }
+
+    /**
+     * Create an iterator of the input string, breaking apart the string at the provided delimiters, removing quotes and triming the parts of the string as
+     * needed.
+     * 
+     * @param str
+     *            the input string to split apart
+     * @param delims
+     *            the delimiter characters to split the string on
+     * @return the iterator of the parts of the string, trimmed, with quotes around the string part removed, and unescaped
+     */
+    public static Iterator<String> splitAt(String str, String delims)
+    {
+        return new DeQuotingStringIterator(str.trim(),delims);
+    }
+
+    public static String unescape(String str)
+    {
+        if (str == null)
+        {
+            // nothing there
+            return null;
+        }
+
+        int len = str.length();
+        if (len <= 1)
+        {
+            // impossible to be escaped
+            return str;
+        }
+
+        StringBuilder ret = new StringBuilder(len - 2);
+        boolean escaped = false;
+        char c;
+        for (int i = 0; i < len; i++)
+        {
+            c = str.charAt(i);
+            if (escaped)
+            {
+                escaped = false;
+                switch (c)
+                {
+                    case 'n':
+                        ret.append('\n');
+                        break;
+                    case 'r':
+                        ret.append('\r');
+                        break;
+                    case 't':
+                        ret.append('\t');
+                        break;
+                    case 'f':
+                        ret.append('\f');
+                        break;
+                    case 'b':
+                        ret.append('\b');
+                        break;
+                    case '\\':
+                        ret.append('\\');
+                        break;
+                    case '/':
+                        ret.append('/');
+                        break;
+                    case '"':
+                        ret.append('"');
+                        break;
+                    case 'u':
+                        ret.append((char)((dehex((byte)str.charAt(i++)) << 24) + (dehex((byte)str.charAt(i++)) << 16) + (dehex((byte)str.charAt(i++)) << 8) + (dehex((byte)str
+                                .charAt(i++)))));
+                        break;
+                    default:
+                        ret.append(c);
+                }
+            }
+            else if (c == '\\')
+            {
+                escaped = true;
+            }
+            else
+            {
+                ret.append(c);
+            }
+        }
+        return ret.toString();
+    }
+
+    public static String join(Object[] objs, String delim)
+    {
+        if (objs == null)
+        {
+            return "";
+        }
+        StringBuilder ret = new StringBuilder();
+        int len = objs.length;
+        for (int i = 0; i < len; i++)
+        {
+            if (i > 0)
+            {
+                ret.append(delim);
+            }
+            if (objs[i] instanceof String)
+            {
+                ret.append('"').append(objs[i]).append('"');
+            }
+            else
+            {
+                ret.append(objs[i]);
+            }
+        }
+        return ret.toString();
+    }
+
+    public static String join(Collection<?> objs, String delim)
+    {
+        if (objs == null)
+        {
+            return "";
+        }
+        StringBuilder ret = new StringBuilder();
+        boolean needDelim = false;
+        for (Object obj : objs)
+        {
+            if (needDelim)
+            {
+                ret.append(delim);
+            }
+            if (obj instanceof String)
+            {
+                ret.append('"').append(obj).append('"');
+            }
+            else
+            {
+                ret.append(obj);
+            }
+            needDelim = true;
+        }
+        return ret.toString();
+    }
+}
diff --git a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/util/WSURI.java b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/util/WSURI.java
new file mode 100644
index 0000000..e645396
--- /dev/null
+++ b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/util/WSURI.java
@@ -0,0 +1,145 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.api.util;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Objects;
+
+/**
+ * Utility methods for converting a {@link URI} between a HTTP(S) and WS(S) URI.
+ */
+public final class WSURI
+{
+    /**
+     * Convert to HTTP <code>http</code> or <code>https</code> scheme URIs.
+     * <p>
+     * Converting <code>ws</code> and <code>wss</code> URIs to their HTTP equivalent
+     * 
+     * @param inputUri
+     *            the input URI
+     * @return the HTTP scheme URI for the input URI.
+     * @throws URISyntaxException
+     *             if unable to convert the input URI
+     */
+    public static URI toHttp(final URI inputUri) throws URISyntaxException
+    {
+        Objects.requireNonNull(inputUri,"Input URI must not be null");
+        String wsScheme = inputUri.getScheme();
+        String httpScheme = null;
+        if ("http".equalsIgnoreCase(wsScheme) || "https".equalsIgnoreCase(wsScheme))
+        {
+            // leave alone
+            httpScheme = wsScheme;
+        }
+        else if ("ws".equalsIgnoreCase(wsScheme))
+        {
+            // convert to http
+            httpScheme = "http";
+        }
+        else if ("wss".equalsIgnoreCase(wsScheme))
+        {
+            // convert to https
+            httpScheme = "https";
+        }
+        else
+        {
+            throw new URISyntaxException(inputUri.toString(),"Unrecognized WebSocket scheme");
+        }
+
+        return new URI(httpScheme,inputUri.getUserInfo(),inputUri.getHost(),inputUri.getPort(),inputUri.getPath(),inputUri.getQuery(),inputUri.getFragment());
+    }
+
+    /**
+     * Convert to WebSocket <code>ws</code> or <code>wss</code> scheme URIs
+     * <p>
+     * Converting <code>http</code> and <code>https</code> URIs to their WebSocket equivalent
+     * 
+     * @param inputUrl
+     *            the input URI
+     * @return the WebSocket scheme URI for the input URI.
+     * @throws URISyntaxException
+     *             if unable to convert the input URI
+     */
+    public static URI toWebsocket(CharSequence inputUrl) throws URISyntaxException
+    {
+        return toWebsocket(new URI(inputUrl.toString()));
+    }
+
+    /**
+     * Convert to WebSocket <code>ws</code> or <code>wss</code> scheme URIs
+     * <p>
+     * Converting <code>http</code> and <code>https</code> URIs to their WebSocket equivalent
+     * 
+     * @param inputUrl
+     *            the input URI
+     * @param query
+     *            the optional query string
+     * @return the WebSocket scheme URI for the input URI.
+     * @throws URISyntaxException
+     *             if unable to convert the input URI
+     */
+    public static URI toWebsocket(CharSequence inputUrl, String query) throws URISyntaxException
+    {
+        if (query == null)
+        {
+            return toWebsocket(new URI(inputUrl.toString()));
+        }
+        return toWebsocket(new URI(inputUrl.toString() + '?' + query));
+    }
+
+    /**
+     * Convert to WebSocket <code>ws</code> or <code>wss</code> scheme URIs
+     * 
+     * <p>
+     * Converting <code>http</code> and <code>https</code> URIs to their WebSocket equivalent
+     * 
+     * @param inputUri
+     *            the input URI
+     * @return the WebSocket scheme URI for the input URI.
+     * @throws URISyntaxException
+     *             if unable to convert the input URI
+     */
+    public static URI toWebsocket(final URI inputUri) throws URISyntaxException
+    {
+        Objects.requireNonNull(inputUri,"Input URI must not be null");
+        String httpScheme = inputUri.getScheme();
+        String wsScheme = null;
+        if ("ws".equalsIgnoreCase(httpScheme) || "wss".equalsIgnoreCase(httpScheme))
+        {
+            // keep as-is
+            wsScheme = httpScheme;
+        }
+        else if ("http".equalsIgnoreCase(httpScheme))
+        {
+            // convert to ws
+            wsScheme = "ws";
+        }
+        else if ("https".equalsIgnoreCase(httpScheme))
+        {
+            // convert to wss
+            wsScheme = "wss";
+        }
+        else
+        {
+            throw new URISyntaxException(inputUri.toString(),"Unrecognized HTTP scheme");
+        }
+        return new URI(wsScheme,inputUri.getUserInfo(),inputUri.getHost(),inputUri.getPort(),inputUri.getPath(),inputUri.getQuery(),inputUri.getFragment());
+    }
+}
diff --git a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/util/package-info.java b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/util/package-info.java
new file mode 100644
index 0000000..3698764
--- /dev/null
+++ b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/util/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty WebSocket API : Utility Classes
+ */
+package org.eclipse.jetty.websocket.api.util;
+
diff --git a/jetty-websocket/websocket-api/src/test/java/org/eclipse/jetty/websocket/api/extensions/ExtensionConfigTest.java b/jetty-websocket/websocket-api/src/test/java/org/eclipse/jetty/websocket/api/extensions/ExtensionConfigTest.java
new file mode 100644
index 0000000..945dead
--- /dev/null
+++ b/jetty-websocket/websocket-api/src/test/java/org/eclipse/jetty/websocket/api/extensions/ExtensionConfigTest.java
@@ -0,0 +1,124 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.api.extensions;
+
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class ExtensionConfigTest
+{
+    private void assertConfig(ExtensionConfig cfg, String expectedName, Map<String, String> expectedParams)
+    {
+        String prefix = "ExtensionConfig";
+        Assert.assertThat(prefix + ".Name",cfg.getName(),is(expectedName));
+
+        prefix += ".getParameters()";
+        Map<String, String> actualParams = cfg.getParameters();
+        Assert.assertThat(prefix,actualParams,notNullValue());
+        Assert.assertThat(prefix + ".size",actualParams.size(),is(expectedParams.size()));
+
+        for (String expectedKey : expectedParams.keySet())
+        {
+            Assert.assertThat(prefix + ".containsKey(" + expectedKey + ")",actualParams.containsKey(expectedKey),is(true));
+
+            String expectedValue = expectedParams.get(expectedKey);
+            String actualValue = actualParams.get(expectedKey);
+
+            Assert.assertThat(prefix + ".containsKey(" + expectedKey + ")",actualValue,is(expectedValue));
+        }
+    }
+
+    @Test
+    public void testParseMuxExample()
+    {
+        ExtensionConfig cfg = ExtensionConfig.parse("mux; max-channels=4; flow-control");
+        Map<String, String> expectedParams = new HashMap<>();
+        expectedParams.put("max-channels","4");
+        expectedParams.put("flow-control",null);
+        assertConfig(cfg,"mux",expectedParams);
+    }
+
+    @Test
+    public void testParsePerMessageCompressExample1()
+    {
+        ExtensionConfig cfg = ExtensionConfig.parse("permessage-compress; method=foo");
+        Map<String, String> expectedParams = new HashMap<>();
+        expectedParams.put("method","foo");
+        assertConfig(cfg,"permessage-compress",expectedParams);
+    }
+
+    @Test
+    public void testParsePerMessageCompressExample2()
+    {
+        ExtensionConfig cfg = ExtensionConfig.parse("permessage-compress; method=\"foo; x=10\"");
+        Map<String, String> expectedParams = new HashMap<>();
+        expectedParams.put("method","foo; x=10");
+        assertConfig(cfg,"permessage-compress",expectedParams);
+    }
+
+    @Test
+    public void testParsePerMessageCompressExample3()
+    {
+        ExtensionConfig cfg = ExtensionConfig.parse("permessage-compress; method=\"foo, bar\"");
+        Map<String, String> expectedParams = new HashMap<>();
+        expectedParams.put("method","foo, bar");
+        assertConfig(cfg,"permessage-compress",expectedParams);
+    }
+
+    @Test
+    public void testParsePerMessageCompressExample4()
+    {
+        ExtensionConfig cfg = ExtensionConfig.parse("permessage-compress; method=\"foo; use_x, foo\"");
+        Map<String, String> expectedParams = new HashMap<>();
+        expectedParams.put("method","foo; use_x, foo");
+        assertConfig(cfg,"permessage-compress",expectedParams);
+    }
+
+    @Test
+    public void testParsePerMessageCompressExample5()
+    {
+        ExtensionConfig cfg = ExtensionConfig.parse("permessage-compress; method=\"foo; x=\\\"Hello World\\\", bar\"");
+        Map<String, String> expectedParams = new HashMap<>();
+        expectedParams.put("method","foo; x=\"Hello World\", bar");
+        assertConfig(cfg,"permessage-compress",expectedParams);
+    }
+
+    @Test
+    public void testParseSimple_BasicParameters()
+    {
+        ExtensionConfig cfg = ExtensionConfig.parse("bar; baz=2");
+        Map<String, String> expectedParams = new HashMap<>();
+        expectedParams.put("baz","2");
+        assertConfig(cfg,"bar",expectedParams);
+    }
+
+    @Test
+    public void testParseSimple_NoParameters()
+    {
+        ExtensionConfig cfg = ExtensionConfig.parse("foo");
+        Map<String, String> expectedParams = new HashMap<>();
+        assertConfig(cfg,"foo",expectedParams);
+    }
+}
diff --git a/jetty-websocket/websocket-api/src/test/java/org/eclipse/jetty/websocket/api/util/QuoteUtilTest.java b/jetty-websocket/websocket-api/src/test/java/org/eclipse/jetty/websocket/api/util/QuoteUtilTest.java
new file mode 100644
index 0000000..d475ec0
--- /dev/null
+++ b/jetty-websocket/websocket-api/src/test/java/org/eclipse/jetty/websocket/api/util/QuoteUtilTest.java
@@ -0,0 +1,156 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.api.util;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Test QuoteUtil
+ */
+public class QuoteUtilTest
+{
+    private void assertSplitAt(Iterator<String> iter, String... expectedParts)
+    {
+        int len = expectedParts.length;
+        for (int i = 0; i < len; i++)
+        {
+            String expected = expectedParts[i];
+            Assert.assertThat("Split[" + i + "].hasNext()",iter.hasNext(),is(true));
+            Assert.assertThat("Split[" + i + "].next()",iter.next(),is(expected));
+        }
+    }
+
+    @Test
+    public void testSplitAt_PreserveQuoting()
+    {
+        Iterator<String> iter = QuoteUtil.splitAt("permessage-compress; method=\"foo, bar\"",";");
+        assertSplitAt(iter,"permessage-compress","method=\"foo, bar\"");
+    }
+
+    @Test
+    public void testSplitAt_PreserveQuotingWithNestedDelim()
+    {
+        Iterator<String> iter = QuoteUtil.splitAt("permessage-compress; method=\"foo; x=10\"",";");
+        assertSplitAt(iter,"permessage-compress","method=\"foo; x=10\"");
+    }
+
+    @Test(expected = NoSuchElementException.class)
+    public void testSplitAtAllWhitespace()
+    {
+        Iterator<String> iter = QuoteUtil.splitAt("   ","=");
+        Assert.assertThat("Has Next",iter.hasNext(),is(false));
+        iter.next(); // should trigger NoSuchElementException
+    }
+
+    @Test(expected = NoSuchElementException.class)
+    public void testSplitAtEmpty()
+    {
+        Iterator<String> iter = QuoteUtil.splitAt("","=");
+        Assert.assertThat("Has Next",iter.hasNext(),is(false));
+        iter.next(); // should trigger NoSuchElementException
+    }
+
+    @Test
+    public void testSplitAtHelloWorld()
+    {
+        Iterator<String> iter = QuoteUtil.splitAt("Hello World"," =");
+        assertSplitAt(iter,"Hello","World");
+    }
+
+    @Test
+    public void testSplitAtKeyValue_Message()
+    {
+        Iterator<String> iter = QuoteUtil.splitAt("method=\"foo, bar\"","=");
+        assertSplitAt(iter,"method","foo, bar");
+    }
+
+    @Test
+    public void testSplitAtQuotedDelim()
+    {
+        // test that split ignores delimiters that occur within a quoted
+        // part of the sequence.
+        Iterator<String> iter = QuoteUtil.splitAt("A,\"B,C\",D",",");
+        assertSplitAt(iter,"A","B,C","D");
+    }
+
+    @Test
+    public void testSplitAtSimple()
+    {
+        Iterator<String> iter = QuoteUtil.splitAt("Hi","=");
+        assertSplitAt(iter,"Hi");
+    }
+
+    @Test
+    public void testSplitKeyValue_Quoted()
+    {
+        Iterator<String> iter = QuoteUtil.splitAt("Key = \"Value\"","=");
+        assertSplitAt(iter,"Key","Value");
+    }
+
+    @Test
+    public void testSplitKeyValue_QuotedValueList()
+    {
+        Iterator<String> iter = QuoteUtil.splitAt("Fruit = \"Apple, Banana, Cherry\"","=");
+        assertSplitAt(iter,"Fruit","Apple, Banana, Cherry");
+    }
+
+    @Test
+    public void testSplitKeyValue_QuotedWithDelim()
+    {
+        Iterator<String> iter = QuoteUtil.splitAt("Key = \"Option=Value\"","=");
+        assertSplitAt(iter,"Key","Option=Value");
+    }
+
+    @Test
+    public void testSplitKeyValue_Simple()
+    {
+        Iterator<String> iter = QuoteUtil.splitAt("Key=Value","=");
+        assertSplitAt(iter,"Key","Value");
+    }
+
+    @Test
+    public void testSplitKeyValue_WithWhitespace()
+    {
+        Iterator<String> iter = QuoteUtil.splitAt("Key = Value","=");
+        assertSplitAt(iter,"Key","Value");
+    }
+    
+    @Test
+    public void testQuoteIfNeeded()
+    {
+        StringBuilder buf = new StringBuilder();
+        QuoteUtil.quoteIfNeeded(buf, "key",",");
+        assertThat("key",buf.toString(),is("key"));
+    }
+    
+    @Test
+    public void testQuoteIfNeeded_null()
+    {
+        StringBuilder buf = new StringBuilder();
+        QuoteUtil.quoteIfNeeded(buf, null,";=");
+        assertThat("<null>",buf.toString(),is(""));
+    }
+}
diff --git a/jetty-websocket/websocket-api/src/test/java/org/eclipse/jetty/websocket/api/util/QuoteUtil_QuoteTest.java b/jetty-websocket/websocket-api/src/test/java/org/eclipse/jetty/websocket/api/util/QuoteUtil_QuoteTest.java
new file mode 100644
index 0000000..a2a6871
--- /dev/null
+++ b/jetty-websocket/websocket-api/src/test/java/org/eclipse/jetty/websocket/api/util/QuoteUtil_QuoteTest.java
@@ -0,0 +1,84 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.api.util;
+
+import static org.hamcrest.Matchers.is;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Test QuoteUtil.quote(), and QuoteUtil.dequote()
+ */
+ at RunWith(Parameterized.class)
+public class QuoteUtil_QuoteTest
+{
+    @Parameters
+    public static Collection<Object[]> data()
+    {
+        // The various quoting of a String
+        List<Object[]> data = new ArrayList<>();
+
+        // @formatter:off
+        data.add(new Object[] { "Hi", "\"Hi\"" });
+        data.add(new Object[] { "Hello World", "\"Hello World\"" });
+        data.add(new Object[] { "9.0.0", "\"9.0.0\"" });
+        data.add(new Object[] { "Something \"Special\"", 
+                                "\"Something \\\"Special\\\"\"" });
+        data.add(new Object[] { "A Few\n\"Good\"\tMen", 
+                                "\"A Few\\n\\\"Good\\\"\\tMen\"" });
+        // @formatter:on
+
+        return data;
+    }
+
+    private String unquoted;
+    private String quoted;
+
+    public QuoteUtil_QuoteTest(String unquoted, String quoted)
+    {
+        this.unquoted = unquoted;
+        this.quoted = quoted;
+    }
+
+    @Test
+    public void testDequoting()
+    {
+        String actual = QuoteUtil.dequote(quoted);
+        actual = QuoteUtil.unescape(actual);
+        Assert.assertThat(actual,is(unquoted));
+    }
+
+    @Test
+    public void testQuoting()
+    {
+        StringBuilder buf = new StringBuilder();
+        QuoteUtil.quote(buf,unquoted);
+
+        String actual = buf.toString();
+        Assert.assertThat(actual,is(quoted));
+    }
+}
diff --git a/jetty-websocket/websocket-api/src/test/java/org/eclipse/jetty/websocket/api/util/WSURITest.java b/jetty-websocket/websocket-api/src/test/java/org/eclipse/jetty/websocket/api/util/WSURITest.java
new file mode 100644
index 0000000..20b0528
--- /dev/null
+++ b/jetty-websocket/websocket-api/src/test/java/org/eclipse/jetty/websocket/api/util/WSURITest.java
@@ -0,0 +1,88 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.api.util;
+
+import static org.hamcrest.Matchers.is;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class WSURITest
+{
+    private void assertURI(URI actual, URI expected)
+    {
+        Assert.assertThat(actual.toASCIIString(),is(expected.toASCIIString()));
+    }
+
+    @Test
+    public void testHttpsToHttps() throws URISyntaxException
+    {
+        assertURI(WSURI.toHttp(URI.create("https://localhost/")),URI.create("https://localhost/"));
+    }
+
+    @Test
+    public void testHttpsToWss() throws URISyntaxException
+    {
+        assertURI(WSURI.toWebsocket(URI.create("https://localhost/")),URI.create("wss://localhost/"));
+    }
+
+    @Test
+    public void testHttpToHttp() throws URISyntaxException
+    {
+        assertURI(WSURI.toHttp(URI.create("http://localhost/")),URI.create("http://localhost/"));
+    }
+
+    @Test
+    public void testHttpToWs() throws URISyntaxException
+    {
+        assertURI(WSURI.toWebsocket(URI.create("http://localhost/")),URI.create("ws://localhost/"));
+        assertURI(WSURI.toWebsocket(URI.create("http://localhost:8080/deeper/")),URI.create("ws://localhost:8080/deeper/"));
+        assertURI(WSURI.toWebsocket("http://localhost/"),URI.create("ws://localhost/"));
+        assertURI(WSURI.toWebsocket("http://localhost/",null),URI.create("ws://localhost/"));
+        assertURI(WSURI.toWebsocket("http://localhost/","a=b"),URI.create("ws://localhost/?a=b"));
+    }
+
+    @Test
+    public void testWssToHttps() throws URISyntaxException
+    {
+        assertURI(WSURI.toHttp(URI.create("wss://localhost/")),URI.create("https://localhost/"));
+    }
+
+    @Test
+    public void testWssToWss() throws URISyntaxException
+    {
+        assertURI(WSURI.toWebsocket(URI.create("wss://localhost/")),URI.create("wss://localhost/"));
+    }
+
+    @Test
+    public void testWsToHttp() throws URISyntaxException
+    {
+        assertURI(WSURI.toHttp(URI.create("ws://localhost/")),URI.create("http://localhost/"));
+    }
+
+    @Test
+    public void testWsToWs() throws URISyntaxException
+    {
+        assertURI(WSURI.toWebsocket(URI.create("ws://localhost/")),URI.create("ws://localhost/"));
+    }
+
+}
diff --git a/jetty-websocket/websocket-client/pom.xml b/jetty-websocket/websocket-client/pom.xml
new file mode 100644
index 0000000..9b42d41
--- /dev/null
+++ b/jetty-websocket/websocket-client/pom.xml
@@ -0,0 +1,118 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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">
+    <parent>
+        <groupId>org.eclipse.jetty.websocket</groupId>
+        <artifactId>websocket-parent</artifactId>
+        <version>9.2.13.v20150730</version>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>websocket-client</artifactId>
+    <name>Jetty :: Websocket :: Client</name>
+
+    <properties>
+        <bundle-symbolic-name>${project.groupId}.client</bundle-symbolic-name>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-util</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-io</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty.websocket</groupId>
+            <artifactId>websocket-common</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty.websocket</groupId>
+            <artifactId>websocket-common</artifactId>
+            <version>${project.version}</version>
+            <classifier>tests</classifier>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty.toolchain</groupId>
+            <artifactId>jetty-test-helper</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-enforcer-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>ban-java-servlet-api</id>
+                        <goals>
+                            <goal>enforce</goal>
+                        </goals>
+                        <configuration>
+                            <rules>
+                                <bannedDependencies>
+                                    <includes>
+                                        <include>javax.servlet</include>
+                                        <include>servletapi</include>
+                                        <include>org.eclipse.jetty.orbit:javax.servlet</include>
+                                        <include>org.mortbay.jetty:servlet-api</include>
+                                        <include>jetty:servlet-api</include>
+                                    </includes>
+                                    <searchTransitive>true</searchTransitive>
+                                    <message>The servlet-api dependency is banned in websocket-client as it causes problems in apps that use client only.</message>
+                                </bannedDependencies>
+                            </rules>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-jar-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>tests-jar</id>
+                        <goals>
+                            <goal>test-jar</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-shade-plugin</artifactId>
+                <version>2.0</version>
+                <executions>
+                    <execution>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>shade</goal>
+                        </goals>
+                        <configuration>
+                            <shadedArtifactAttached>true</shadedArtifactAttached>
+                            <shadedClassifierName>hybrid</shadedClassifierName>
+                            <artifactSet>
+                                <includes>
+                                    <include>org.eclipse.jetty.websocket:websocket-common</include>
+                                </includes>
+                            </artifactSet>
+                            <relocations>
+                                <relocation>
+                                    <pattern>org.eclipse.jetty.websocket.common</pattern>
+                                    <shadedPattern>org.eclipse.jetty.websocket.client.common</shadedPattern>
+                                </relocation>
+                            </relocations>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/ClientUpgradeRequest.java b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/ClientUpgradeRequest.java
new file mode 100644
index 0000000..ac5873d
--- /dev/null
+++ b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/ClientUpgradeRequest.java
@@ -0,0 +1,273 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.client;
+
+import java.net.CookieStore;
+import java.net.HttpCookie;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.concurrent.ThreadLocalRandom;
+
+import org.eclipse.jetty.util.B64Code;
+import org.eclipse.jetty.util.LazyList;
+import org.eclipse.jetty.util.MultiMap;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.UrlEncoded;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.UpgradeRequest;
+import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
+
+/**
+ * Allowing a generate from a UpgradeRequest
+ */
+public class ClientUpgradeRequest extends UpgradeRequest
+{
+    private static final Logger LOG = Log.getLogger(ClientUpgradeRequest.class);
+    private static final int MAX_KEYS = -1; // maximum number of parameter keys to decode
+    private static final Set<String> FORBIDDEN_HEADERS;
+
+    static
+    {
+        // Headers not allowed to be set in ClientUpgradeRequest.headers.
+        FORBIDDEN_HEADERS = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
+        // Cookies are handled explicitly, avoid to add them twice.
+        FORBIDDEN_HEADERS.add("cookie");
+        // Headers that cannot be set by applications.
+        FORBIDDEN_HEADERS.add("upgrade");
+        FORBIDDEN_HEADERS.add("host");
+        FORBIDDEN_HEADERS.add("connection");
+        FORBIDDEN_HEADERS.add("sec-websocket-key");
+        FORBIDDEN_HEADERS.add("sec-websocket-extensions");
+        FORBIDDEN_HEADERS.add("sec-websocket-accept");
+        FORBIDDEN_HEADERS.add("sec-websocket-protocol");
+        FORBIDDEN_HEADERS.add("sec-websocket-version");
+        FORBIDDEN_HEADERS.add("pragma");
+        FORBIDDEN_HEADERS.add("cache-control");
+    }
+
+    private final String key;
+
+    public ClientUpgradeRequest()
+    {
+        super();
+        this.key = genRandomKey();
+    }
+
+    protected ClientUpgradeRequest(URI requestURI)
+    {
+        super(requestURI);
+        this.key = genRandomKey();
+    }
+
+    public String generate()
+    {
+        URI uri = getRequestURI();
+
+        StringBuilder request = new StringBuilder(512);
+        request.append("GET ");
+        if (StringUtil.isBlank(uri.getPath()))
+        {
+            request.append("/");
+        }
+        else
+        {
+            request.append(uri.getPath());
+        }
+        if (StringUtil.isNotBlank(uri.getRawQuery()))
+        {
+            request.append("?").append(uri.getRawQuery());
+        }
+        request.append(" HTTP/1.1\r\n");
+
+        request.append("Host: ").append(uri.getHost());
+        if (uri.getPort() > 0)
+        {
+            request.append(':').append(uri.getPort());
+        }
+        request.append("\r\n");
+
+        // WebSocket specifics
+        request.append("Upgrade: websocket\r\n");
+        request.append("Connection: Upgrade\r\n");
+        request.append("Sec-WebSocket-Key: ").append(key).append("\r\n");
+        request.append("Sec-WebSocket-Version: 13\r\n"); // RFC-6455 specified version
+
+        // (Per the hybi list): Add no-cache headers to avoid compatibility issue.
+        // There are some proxies that rewrite "Connection: upgrade"
+        // to "Connection: close" in the response if a request doesn't contain
+        // these headers.
+        request.append("Pragma: no-cache\r\n");
+        request.append("Cache-Control: no-cache\r\n");
+
+        // Extensions
+        if (!getExtensions().isEmpty())
+        {
+            request.append("Sec-WebSocket-Extensions: ");
+            boolean needDelim = false;
+            for (ExtensionConfig ext : getExtensions())
+            {
+                if (needDelim)
+                {
+                    request.append(", ");
+                }
+                request.append(ext.getParameterizedName());
+                needDelim = true;
+            }
+            request.append("\r\n");
+        }
+
+        // Sub Protocols
+        if (!getSubProtocols().isEmpty())
+        {
+            request.append("Sec-WebSocket-Protocol: ");
+            boolean needDelim = false;
+            for (String protocol : getSubProtocols())
+            {
+                if (needDelim)
+                {
+                    request.append(", ");
+                }
+                request.append(protocol);
+                needDelim = true;
+            }
+            request.append("\r\n");
+        }
+
+        // Cookies
+        List<HttpCookie> cookies = getCookies();
+        if ((cookies != null) && (cookies.size() > 0))
+        {
+            request.append("Cookie: ");
+            boolean needDelim = false;
+            for (HttpCookie cookie : cookies)
+            {
+                if (needDelim)
+                {
+                    request.append("; ");
+                }
+                
+                request.append(cookie.getName()).append("=");
+                if (cookie.getVersion() == 1)
+                {
+                    // must be enclosed with quotes
+                    request.append('"').append(cookie.getValue()).append('"');
+                }
+                else
+                {
+                    request.append(cookie.getValue());
+                }
+                needDelim = true;
+            }
+            request.append("\r\n");
+        }
+
+        // Other headers
+        for (String key : getHeaders().keySet())
+        {
+            if (FORBIDDEN_HEADERS.contains(key))
+            {
+                LOG.debug("Skipping forbidden header - {}",key);
+                continue; // skip
+            }
+            request.append(key).append(": ");
+            request.append(getHeader(key));
+            request.append("\r\n");
+        }
+
+        // request header end
+        request.append("\r\n");
+        return request.toString();
+    }
+
+    private final String genRandomKey()
+    {
+        byte[] bytes = new byte[16];
+        ThreadLocalRandom.current().nextBytes(bytes);
+        return new String(B64Code.encode(bytes));
+    }
+
+    public String getKey()
+    {
+        return key;
+    }
+
+    public void setCookiesFrom(CookieStore cookieStore)
+    {
+        if (cookieStore == null)
+        {
+            return;
+        }
+
+        List<HttpCookie> existing = getCookies();
+        List<HttpCookie> extra = cookieStore.get(getRequestURI());
+
+        List<HttpCookie> cookies = new ArrayList<>();
+        if (LazyList.hasEntry(existing))
+        {
+            cookies.addAll(existing);
+        }
+        if (LazyList.hasEntry(extra))
+        {
+            cookies.addAll(extra);
+        }
+        setCookies(cookies);
+    }
+
+    @Override
+    public void setRequestURI(URI uri)
+    {
+        super.setRequestURI(uri);
+
+        // parse parameter map
+        Map<String, List<String>> pmap = new HashMap<>();
+
+        String query = uri.getQuery();
+
+        if (StringUtil.isNotBlank(query))
+        {
+            MultiMap<String> params = new MultiMap<String>();
+            UrlEncoded.decodeTo(uri.getQuery(),params,StandardCharsets.UTF_8,MAX_KEYS);
+
+            for (String key : params.keySet())
+            {
+                List<String> values = params.getValues(key);
+                if (values == null)
+                {
+                    pmap.put(key,new ArrayList<String>());
+                }
+                else
+                {
+                    // break link to original
+                    List<String> copy = new ArrayList<>();
+                    copy.addAll(values);
+                    pmap.put(key,copy);
+                }
+            }
+
+            super.setParameterMap(pmap);
+        }
+    }
+}
diff --git a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/ClientUpgradeResponse.java b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/ClientUpgradeResponse.java
new file mode 100644
index 0000000..0c46876
--- /dev/null
+++ b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/ClientUpgradeResponse.java
@@ -0,0 +1,52 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.client;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.websocket.api.UpgradeResponse;
+import org.eclipse.jetty.websocket.common.io.http.HttpResponseHeaderParseListener;
+
+public class ClientUpgradeResponse extends UpgradeResponse implements HttpResponseHeaderParseListener
+{
+    private ByteBuffer remainingBuffer;
+
+    public ClientUpgradeResponse()
+    {
+        super();
+    }
+
+    public ByteBuffer getRemainingBuffer()
+    {
+        return remainingBuffer;
+    }
+
+    @Override
+    public void sendForbidden(String message) throws IOException
+    {
+        throw new UnsupportedOperationException("Not supported on client implementation");
+    }
+
+    @Override
+    public void setRemainingBuffer(ByteBuffer remainingBuffer)
+    {
+        this.remainingBuffer = remainingBuffer;
+    }
+}
diff --git a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/WebSocketClient.java b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/WebSocketClient.java
new file mode 100644
index 0000000..3320d57
--- /dev/null
+++ b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/WebSocketClient.java
@@ -0,0 +1,584 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.client;
+
+import java.io.IOException;
+import java.net.CookieStore;
+import java.net.SocketAddress;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Future;
+
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.MappedByteBufferPool;
+import org.eclipse.jetty.io.SelectorManager;
+import org.eclipse.jetty.util.HttpCookieStore;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
+import org.eclipse.jetty.util.thread.Scheduler;
+import org.eclipse.jetty.util.thread.ShutdownThread;
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.api.extensions.Extension;
+import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
+import org.eclipse.jetty.websocket.api.extensions.ExtensionFactory;
+import org.eclipse.jetty.websocket.client.io.ConnectPromise;
+import org.eclipse.jetty.websocket.client.io.ConnectionManager;
+import org.eclipse.jetty.websocket.client.io.UpgradeListener;
+import org.eclipse.jetty.websocket.client.masks.Masker;
+import org.eclipse.jetty.websocket.client.masks.RandomMasker;
+import org.eclipse.jetty.websocket.common.SessionFactory;
+import org.eclipse.jetty.websocket.common.SessionListener;
+import org.eclipse.jetty.websocket.common.WebSocketSession;
+import org.eclipse.jetty.websocket.common.WebSocketSessionFactory;
+import org.eclipse.jetty.websocket.common.events.EventDriver;
+import org.eclipse.jetty.websocket.common.events.EventDriverFactory;
+import org.eclipse.jetty.websocket.common.extensions.WebSocketExtensionFactory;
+
+/**
+ * WebSocketClient provides a means of establishing connections to remote websocket endpoints.
+ */
+public class WebSocketClient extends ContainerLifeCycle implements SessionListener
+{
+    private static final Logger LOG = Log.getLogger(WebSocketClient.class);
+
+    private final WebSocketPolicy policy;
+    private final SslContextFactory sslContextFactory;
+    private final WebSocketExtensionFactory extensionRegistry;
+    private boolean daemon = false;
+    private EventDriverFactory eventDriverFactory;
+    private SessionFactory sessionFactory;
+    private ByteBufferPool bufferPool;
+    private Executor executor;
+    private Scheduler scheduler;
+    private CookieStore cookieStore;
+    private ConnectionManager connectionManager;
+    private Masker masker;
+    private SocketAddress bindAddress;
+    private long connectTimeout = SelectorManager.DEFAULT_CONNECT_TIMEOUT;
+
+    public WebSocketClient()
+    {
+        this(null,null);
+    }
+
+    public WebSocketClient(Executor executor)
+    {
+        this(null,executor);
+    }
+    
+    public WebSocketClient(ByteBufferPool bufferPool)
+    {
+        this(null,null,bufferPool);
+    }
+
+    public WebSocketClient(SslContextFactory sslContextFactory)
+    {
+        this(sslContextFactory,null);
+    }
+
+    public WebSocketClient(SslContextFactory sslContextFactory, Executor executor)
+    {
+        this(sslContextFactory,executor,new MappedByteBufferPool());
+    }
+    
+    public WebSocketClient(SslContextFactory sslContextFactory, Executor executor, ByteBufferPool bufferPool)
+    {
+        this.executor = executor;
+        this.sslContextFactory = sslContextFactory;
+        this.policy = WebSocketPolicy.newClientPolicy();
+        this.bufferPool = bufferPool;
+        this.extensionRegistry = new WebSocketExtensionFactory(policy,bufferPool);
+        
+        // Bug #431459 - unregistering compression extensions till they are more stable
+        this.extensionRegistry.unregister("deflate-frame");
+        this.extensionRegistry.unregister("permessage-deflate");
+        this.extensionRegistry.unregister("x-webkit-deflate-frame");
+        
+        this.masker = new RandomMasker();
+        this.eventDriverFactory = new EventDriverFactory(policy);
+        this.sessionFactory = new WebSocketSessionFactory(this);
+        
+        addBean(this.executor);
+        addBean(this.sslContextFactory);
+        addBean(this.bufferPool);
+    }
+
+    public Future<Session> connect(Object websocket, URI toUri) throws IOException
+    {
+        ClientUpgradeRequest request = new ClientUpgradeRequest(toUri);
+        request.setRequestURI(toUri);
+        request.setCookiesFrom(this.cookieStore);
+
+        return connect(websocket,toUri,request);
+    }
+
+    public Future<Session> connect(Object websocket, URI toUri, ClientUpgradeRequest request) throws IOException
+    {
+        return connect(websocket,toUri,request,null);
+    }
+
+    public Future<Session> connect(Object websocket, URI toUri, ClientUpgradeRequest request, UpgradeListener upgradeListener) throws IOException
+    {
+        if (!isStarted())
+        {
+            throw new IllegalStateException(WebSocketClient.class.getSimpleName() + "@" + this.hashCode() + " is not started");
+        }
+
+        // Validate websocket URI
+        if (!toUri.isAbsolute())
+        {
+            throw new IllegalArgumentException("WebSocket URI must be absolute");
+        }
+
+        if (StringUtil.isBlank(toUri.getScheme()))
+        {
+            throw new IllegalArgumentException("WebSocket URI must include a scheme");
+        }
+
+        String scheme = toUri.getScheme().toLowerCase(Locale.ENGLISH);
+        if (("ws".equals(scheme) == false) && ("wss".equals(scheme) == false))
+        {
+            throw new IllegalArgumentException("WebSocket URI scheme only supports [ws] and [wss], not [" + scheme + "]");
+        }
+
+        request.setRequestURI(toUri);
+        request.setCookiesFrom(this.cookieStore);
+
+        // Validate Requested Extensions
+        for (ExtensionConfig reqExt : request.getExtensions())
+        {
+            if (!extensionRegistry.isAvailable(reqExt.getName()))
+            {
+                throw new IllegalArgumentException("Requested extension [" + reqExt.getName() + "] is not installed");
+            }
+        }
+
+        if (LOG.isDebugEnabled())
+            LOG.debug("connect websocket {} to {}",websocket,toUri);
+
+        // Grab Connection Manager
+        initialiseClient();
+        ConnectionManager manager = getConnectionManager();
+
+        // Setup Driver for user provided websocket
+        EventDriver driver = null;
+        if (websocket instanceof EventDriver)
+        {
+            // Use the EventDriver as-is
+            driver = (EventDriver)websocket;
+        }
+        else
+        {
+            // Wrap websocket with appropriate EventDriver
+            driver = eventDriverFactory.wrap(websocket);
+        }
+
+        if (driver == null)
+        {
+            throw new IllegalStateException("Unable to identify as websocket object: " + websocket.getClass().getName());
+        }
+
+        // Create the appropriate (physical vs virtual) connection task
+        ConnectPromise promise = manager.connect(this,driver,request);
+
+        if (upgradeListener != null)
+        {
+            promise.setUpgradeListener(upgradeListener);
+        }
+
+        if (LOG.isDebugEnabled())
+            LOG.debug("Connect Promise: {}",promise);
+
+        // Execute the connection on the executor thread
+        executor.execute(promise);
+
+        // Return the future
+        return promise;
+    }
+
+    @Override
+    protected void doStart() throws Exception
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("Starting {}",this);
+
+        if (sslContextFactory != null)
+        {
+            addBean(sslContextFactory);
+        }
+
+        String name = WebSocketClient.class.getSimpleName() + "@" + hashCode();
+
+        if (bufferPool == null)
+        {
+            bufferPool = new MappedByteBufferPool();
+        }
+        addBean(bufferPool);
+
+        if (scheduler == null)
+        {
+            scheduler = new ScheduledExecutorScheduler(name + "-scheduler",daemon);
+        }
+        addBean(scheduler);
+
+        if (cookieStore == null)
+        {
+            cookieStore = new HttpCookieStore.Empty();
+        }
+
+        super.doStart();
+
+        if (LOG.isDebugEnabled())
+            LOG.debug("Started {}",this);
+    }
+
+    @Override
+    protected void doStop() throws Exception
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("Stopping {}",this);
+        
+        if (ShutdownThread.isRegistered(this))
+        {
+            ShutdownThread.deregister(this);
+        }
+
+        if (cookieStore != null)
+        {
+            cookieStore.removeAll();
+            cookieStore = null;
+        }
+
+        super.doStop();
+
+        if (LOG.isDebugEnabled())
+            LOG.debug("Stopped {}",this);
+    }
+
+    /**
+     * Return the number of milliseconds for a timeout of an attempted write operation.
+     * 
+     * @return number of milliseconds for timeout of an attempted write operation
+     */
+    public long getAsyncWriteTimeout()
+    {
+        return this.policy.getAsyncWriteTimeout();
+    }
+
+    public SocketAddress getBindAddress()
+    {
+        return bindAddress;
+    }
+
+    public ByteBufferPool getBufferPool()
+    {
+        return bufferPool;
+    }
+
+    public ConnectionManager getConnectionManager()
+    {
+        return connectionManager;
+    }
+
+    public long getConnectTimeout()
+    {
+        return connectTimeout;
+    }
+
+    public CookieStore getCookieStore()
+    {
+        return cookieStore;
+    }
+    
+    public EventDriverFactory getEventDriverFactory()
+    {
+        return eventDriverFactory;
+    }
+
+    public Executor getExecutor()
+    {
+        return executor;
+    }
+
+    public ExtensionFactory getExtensionFactory()
+    {
+        return extensionRegistry;
+    }
+
+    public Masker getMasker()
+    {
+        return masker;
+    }
+
+    /**
+     * Get the maximum size for buffering of a binary message.
+     * 
+     * @return the maximum size of a binary message buffer.
+     */
+    public int getMaxBinaryMessageBufferSize()
+    {
+        return this.policy.getMaxBinaryMessageBufferSize();
+    }
+
+    /**
+     * Get the maximum size for a binary message.
+     * 
+     * @return the maximum size of a binary message.
+     */
+    public long getMaxBinaryMessageSize()
+    {
+        return this.policy.getMaxBinaryMessageSize();
+    }
+
+    /**
+     * Get the max idle timeout for new connections.
+     * 
+     * @return the max idle timeout in milliseconds for new connections.
+     */
+    public long getMaxIdleTimeout()
+    {
+        return this.policy.getIdleTimeout();
+    }
+
+    /**
+     * Get the maximum size for buffering of a text message.
+     * 
+     * @return the maximum size of a text message buffer.
+     */
+    public int getMaxTextMessageBufferSize()
+    {
+        return this.policy.getMaxTextMessageBufferSize();
+    }
+
+    /**
+     * Get the maximum size for a text message.
+     * 
+     * @return the maximum size of a text message.
+     */
+    public long getMaxTextMessageSize()
+    {
+        return this.policy.getMaxTextMessageSize();
+    }
+
+    public Set<WebSocketSession> getOpenSessions()
+    {
+        return new HashSet<>(getBeans(WebSocketSession.class));
+    }
+
+    public WebSocketPolicy getPolicy()
+    {
+        return this.policy;
+    }
+
+    public Scheduler getScheduler()
+    {
+        return scheduler;
+    }
+
+    public SessionFactory getSessionFactory()
+    {
+        return sessionFactory;
+    }
+
+    /**
+     * @return the {@link SslContextFactory} that manages TLS encryption
+     * @see #WebSocketClient(SslContextFactory)
+     */
+    public SslContextFactory getSslContextFactory()
+    {
+        return sslContextFactory;
+    }
+
+    public List<Extension> initExtensions(List<ExtensionConfig> requested)
+    {
+        List<Extension> extensions = new ArrayList<Extension>();
+
+        for (ExtensionConfig cfg : requested)
+        {
+            Extension extension = extensionRegistry.newInstance(cfg);
+
+            if (extension == null)
+            {
+                continue;
+            }
+
+            if (LOG.isDebugEnabled())
+                LOG.debug("added {}",extension);
+            extensions.add(extension);
+        }
+        if (LOG.isDebugEnabled())
+            LOG.debug("extensions={}",extensions);
+        return extensions;
+    }
+
+    private synchronized void initialiseClient() throws IOException
+    {
+        if (!ShutdownThread.isRegistered(this))
+        {
+            ShutdownThread.register(this);
+        }
+
+        if (executor == null)
+        {
+            QueuedThreadPool threadPool = new QueuedThreadPool();
+            String name = WebSocketClient.class.getSimpleName() + "@" + hashCode();
+            threadPool.setName(name);
+            threadPool.setDaemon(daemon);
+            executor = threadPool;
+            addManaged(threadPool);
+        }
+        else
+        {
+            addBean(executor,false);
+        }
+
+        if (connectionManager == null)
+        {
+            connectionManager = newConnectionManager();
+            addManaged(connectionManager);
+        }
+    }
+
+    /**
+     * Factory method for new ConnectionManager (used by other projects like cometd)
+     * 
+     * @return the ConnectionManager instance to use
+     */
+    protected ConnectionManager newConnectionManager()
+    {
+        return new ConnectionManager(this);
+    }
+
+    @Override
+    public void onSessionClosed(WebSocketSession session)
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("Session Closed: {}",session);
+        removeBean(session);
+    }
+
+    @Override
+    public void onSessionOpened(WebSocketSession session)
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("Session Opened: {}",session);
+    }
+
+    public void setAsyncWriteTimeout(long ms)
+    {
+        this.policy.setAsyncWriteTimeout(ms);
+    }
+
+    public void setBindAdddress(SocketAddress bindAddress)
+    {
+        this.bindAddress = bindAddress;
+    }
+
+    public void setBufferPool(ByteBufferPool bufferPool)
+    {
+        this.bufferPool = bufferPool;
+    }
+
+    /**
+     * Set the timeout for connecting to the remote server.
+     * 
+     * @param ms
+     *            the timeout in milliseconds
+     */
+    public void setConnectTimeout(long ms)
+    {
+        if (ms < 0)
+        {
+            throw new IllegalStateException("Connect Timeout cannot be negative");
+        }
+        this.connectTimeout = ms;
+    }
+
+    public void setCookieStore(CookieStore cookieStore)
+    {
+        this.cookieStore = cookieStore;
+    }
+    
+    public void setDaemon(boolean daemon)
+    {
+        this.daemon = daemon;
+    }
+
+    public void setEventDriverFactory(EventDriverFactory factory)
+    {
+        this.eventDriverFactory = factory;
+    }
+
+    public void setExecutor(Executor executor)
+    {
+        updateBean(this.executor,executor);
+        this.executor = executor;
+    }
+
+    public void setMasker(Masker masker)
+    {
+        this.masker = masker;
+    }
+
+    public void setMaxBinaryMessageBufferSize(int max)
+    {
+        this.policy.setMaxBinaryMessageBufferSize(max);
+    }
+    
+    /**
+     * Set the max idle timeout for new connections.
+     * <p>
+     * Existing connections will not have their max idle timeout adjusted.
+     * 
+     * @param ms
+     *            the timeout in milliseconds
+     */
+    public void setMaxIdleTimeout(long ms)
+    {
+        this.policy.setIdleTimeout(ms);
+    }
+
+    public void setMaxTextMessageBufferSize(int max)
+    {
+        this.policy.setMaxTextMessageBufferSize(max);
+    }
+
+    public void setSessionFactory(SessionFactory sessionFactory)
+    {
+        this.sessionFactory = sessionFactory;
+    }
+
+    @Override
+    public void dump(Appendable out, String indent) throws IOException
+    {
+        dumpThis(out);
+        dump(out, indent, getOpenSessions());
+    }
+}
diff --git a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/ConnectPromise.java b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/ConnectPromise.java
new file mode 100644
index 0000000..9230b6a
--- /dev/null
+++ b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/ConnectPromise.java
@@ -0,0 +1,107 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.client.io;
+
+import org.eclipse.jetty.util.FuturePromise;
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
+import org.eclipse.jetty.websocket.client.ClientUpgradeResponse;
+import org.eclipse.jetty.websocket.client.WebSocketClient;
+import org.eclipse.jetty.websocket.client.masks.Masker;
+import org.eclipse.jetty.websocket.common.WebSocketSession;
+import org.eclipse.jetty.websocket.common.events.EventDriver;
+
+/**
+ * Holder for the pending connect information.
+ */
+public abstract class ConnectPromise extends FuturePromise<Session> implements Runnable
+{
+    private final WebSocketClient client;
+    private final EventDriver driver;
+    private final ClientUpgradeRequest request;
+    private final Masker masker;
+    private UpgradeListener upgradeListener;
+    private ClientUpgradeResponse response;
+
+    public ConnectPromise(WebSocketClient client, EventDriver driver, ClientUpgradeRequest request)
+    {
+        this.client = client;
+        this.driver = driver;
+        this.request = request;
+        this.masker = client.getMasker();
+    }
+
+    @Override
+    public void failed(Throwable cause)
+    {
+        // Notify websocket of failure to connect
+        driver.onError(cause);
+
+        // Notify promise/future of failure to connect
+        super.failed(cause);
+    }
+
+    public WebSocketClient getClient()
+    {
+        return client;
+    }
+
+    public EventDriver getDriver()
+    {
+        return this.driver;
+    }
+
+    public Masker getMasker()
+    {
+        return masker;
+    }
+
+    public ClientUpgradeRequest getRequest()
+    {
+        return this.request;
+    }
+
+    public ClientUpgradeResponse getResponse()
+    {
+        return response;
+    }
+
+    public UpgradeListener getUpgradeListener()
+    {
+        return upgradeListener;
+    }
+
+    public void setResponse(ClientUpgradeResponse response)
+    {
+        this.response = response;
+    }
+
+    public void setUpgradeListener(UpgradeListener upgradeListener)
+    {
+        this.upgradeListener = upgradeListener;
+    }
+
+    public void succeeded(WebSocketSession session)
+    {
+        session.setUpgradeRequest(request);
+        session.setUpgradeResponse(response);
+        session.open();
+        super.succeeded(session);
+    }
+}
diff --git a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/ConnectionManager.java b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/ConnectionManager.java
new file mode 100644
index 0000000..1aae226
--- /dev/null
+++ b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/ConnectionManager.java
@@ -0,0 +1,253 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.client.io;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.net.URI;
+import java.nio.channels.SocketChannel;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Locale;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.StatusCode;
+import org.eclipse.jetty.websocket.api.WebSocketException;
+import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
+import org.eclipse.jetty.websocket.client.WebSocketClient;
+import org.eclipse.jetty.websocket.common.WebSocketSession;
+import org.eclipse.jetty.websocket.common.events.EventDriver;
+
+/**
+ * Internal Connection/Client Manager used to track active clients, their physical vs virtual connection information, and provide some means to create new
+ * physical or virtual connections.
+ */
+public class ConnectionManager extends ContainerLifeCycle
+{
+    private class PhysicalConnect extends ConnectPromise
+    {
+        private SocketAddress bindAddress;
+
+        public PhysicalConnect(WebSocketClient client, EventDriver driver, ClientUpgradeRequest request)
+        {
+            super(client,driver,request);
+            this.bindAddress = client.getBindAddress();
+        }
+
+        @Override
+        public void run()
+        {
+            SocketChannel channel = null;
+            try
+            {
+                channel = SocketChannel.open();
+                if (bindAddress != null)
+                {
+                    channel.bind(bindAddress);
+                }
+
+                URI wsUri = getRequest().getRequestURI();
+
+                channel.socket().setTcpNoDelay(true); // disable nagle
+                channel.configureBlocking(false); // async always
+
+                InetSocketAddress address = toSocketAddress(wsUri);
+
+                if (channel.connect(address))
+                {
+                    getSelector().accept(channel, this);
+                }
+                else
+                {
+                    getSelector().connect(channel, this);
+                }
+            }
+            catch (Throwable t)
+            {
+                // close the socket channel
+                if (channel != null)
+                {
+                    try
+                    {
+                        channel.close();
+                    }
+                    catch (IOException ignore)
+                    {
+                        LOG.ignore(ignore);
+                    }
+                }
+                
+                // notify the future
+                failed(t);
+            }
+        }
+    }
+
+    private class VirtualConnect extends ConnectPromise
+    {
+        public VirtualConnect(WebSocketClient client, EventDriver driver, ClientUpgradeRequest request)
+        {
+            super(client,driver,request);
+        }
+
+        @Override
+        public void run()
+        {
+            failed(new WebSocketException("MUX Not yet supported"));
+        }
+    }
+
+    private static final Logger LOG = Log.getLogger(ConnectionManager.class);
+
+    public static InetSocketAddress toSocketAddress(URI uri)
+    {
+        if (!uri.isAbsolute())
+        {
+            throw new IllegalArgumentException("Cannot get InetSocketAddress of non-absolute URIs");
+        }
+
+        int port = uri.getPort();
+        String scheme = uri.getScheme().toLowerCase(Locale.ENGLISH);
+        if ("ws".equals(scheme))
+        {
+            if (port == (-1))
+            {
+                port = 80;
+            }
+        }
+        else if ("wss".equals(scheme))
+        {
+            if (port == (-1))
+            {
+                port = 443;
+            }
+        }
+        else
+        {
+            throw new IllegalArgumentException("Only support ws:// and wss:// URIs");
+        }
+
+        return new InetSocketAddress(uri.getHost(),port);
+    }
+
+    private final Queue<WebSocketSession> sessions = new ConcurrentLinkedQueue<>();
+    private final WebSocketClient client;
+    private WebSocketClientSelectorManager selector;
+
+    public ConnectionManager(WebSocketClient client)
+    {
+        this.client = client;
+    }
+
+    public void addSession(WebSocketSession session)
+    {
+        sessions.add(session);
+    }
+
+    private void shutdownAllConnections()
+    {
+        for (WebSocketSession session : sessions)
+        {
+            if (session.getConnection() != null)
+            {
+                try
+                {
+                    session.getConnection().close(
+                            StatusCode.SHUTDOWN,
+                            "Shutdown");
+                }
+                catch (Throwable t)
+                {
+                    LOG.debug("During Shutdown All Connections",t);
+                }
+            }
+        }
+    }
+
+    public ConnectPromise connect(WebSocketClient client, EventDriver driver, ClientUpgradeRequest request)
+    {
+        URI toUri = request.getRequestURI();
+        String hostname = toUri.getHost();
+
+        if (isVirtualConnectionPossibleTo(hostname))
+        {
+            return new VirtualConnect(client,driver,request);
+        }
+
+        return new PhysicalConnect(client,driver,request);
+    }
+
+    @Override
+    protected void doStart() throws Exception
+    {
+        selector = newWebSocketClientSelectorManager(client);
+        selector.setSslContextFactory(client.getSslContextFactory());
+        selector.setConnectTimeout(client.getConnectTimeout());
+        addBean(selector);
+
+        super.doStart();
+    }
+
+    @Override
+    protected void doStop() throws Exception
+    {
+        shutdownAllConnections();
+        sessions.clear();
+        super.doStop();
+        removeBean(selector);
+    }
+
+    public WebSocketClientSelectorManager getSelector()
+    {
+        return selector;
+    }
+
+    public Collection<WebSocketSession> getSessions()
+    {
+        return Collections.unmodifiableCollection(sessions);
+    }
+
+    public boolean isVirtualConnectionPossibleTo(String hostname)
+    {
+        // TODO Auto-generated method stub
+        return false;
+    }
+
+    /**
+     * Factory method for new WebSocketClientSelectorManager (used by other projects like cometd)
+     * 
+     * @param client
+     *            the client used to create the WebSocketClientSelectorManager
+     * @return the new WebSocketClientSelectorManager
+     */
+    protected WebSocketClientSelectorManager newWebSocketClientSelectorManager(WebSocketClient client)
+    {
+        return new WebSocketClientSelectorManager(client);
+    }
+
+    public void removeSession(WebSocketSession session)
+    {
+        sessions.remove(session);
+    }
+}
diff --git a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/UpgradeConnection.java b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/UpgradeConnection.java
new file mode 100644
index 0000000..d4c4a75
--- /dev/null
+++ b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/UpgradeConnection.java
@@ -0,0 +1,352 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.client.io;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.net.URI;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+import org.eclipse.jetty.io.AbstractConnection;
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.FutureCallback;
+import org.eclipse.jetty.util.QuotedStringTokenizer;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.UpgradeException;
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
+import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
+import org.eclipse.jetty.websocket.client.ClientUpgradeResponse;
+import org.eclipse.jetty.websocket.common.AcceptHash;
+import org.eclipse.jetty.websocket.common.SessionFactory;
+import org.eclipse.jetty.websocket.common.WebSocketSession;
+import org.eclipse.jetty.websocket.common.events.EventDriver;
+import org.eclipse.jetty.websocket.common.extensions.ExtensionStack;
+import org.eclipse.jetty.websocket.common.io.http.HttpResponseHeaderParser;
+import org.eclipse.jetty.websocket.common.io.http.HttpResponseHeaderParser.ParseException;
+
+/**
+ * This is the initial connection handling that exists immediately after physical connection is established to destination server.
+ * <p>
+ * Eventually, upon successful Upgrade request/response, this connection swaps itself out for the WebSocektClientConnection handler.
+ */
+public class UpgradeConnection extends AbstractConnection
+{
+    public class SendUpgradeRequest extends FutureCallback implements Runnable
+    {
+        @Override
+        public void run()
+        {
+            URI uri = connectPromise.getRequest().getRequestURI();
+            request.setRequestURI(uri);
+
+            UpgradeListener handshakeListener = connectPromise.getUpgradeListener();
+            if (handshakeListener != null)
+            {
+                handshakeListener.onHandshakeRequest(request);
+            }
+
+            String rawRequest = request.generate();
+
+            ByteBuffer buf = BufferUtil.toBuffer(rawRequest, StandardCharsets.UTF_8);
+            getEndPoint().write(this,buf);
+        }
+
+        @Override
+        public void succeeded()
+        {
+            LOG.debug("Upgrade Request Write Success");
+            // Writing the request header is complete.
+            super.succeeded();
+            // start the interest in fill
+            fillInterested();
+        }
+
+        @Override
+        public void failed(Throwable cause)
+        {
+            LOG.warn("Upgrade Request Write Failure", cause);
+            super.failed(cause);
+            // Fail the connect promise when a fundamental exception during connect occurs.
+            connectPromise.failed(cause);
+        }
+    }
+
+    /** HTTP Response Code: 101 Switching Protocols */
+    private static final int SWITCHING_PROTOCOLS = 101;
+
+    private static final Logger LOG = Log.getLogger(UpgradeConnection.class);
+    private final ByteBufferPool bufferPool;
+    private final ConnectPromise connectPromise;
+    private final HttpResponseHeaderParser parser;
+    private ClientUpgradeRequest request;
+
+    public UpgradeConnection(EndPoint endp, Executor executor, ConnectPromise connectPromise)
+    {
+        super(endp,executor);
+        this.connectPromise = connectPromise;
+        this.bufferPool = connectPromise.getClient().getBufferPool();
+        this.request = connectPromise.getRequest();
+
+        // Setup the parser
+        this.parser = new HttpResponseHeaderParser(new ClientUpgradeResponse());
+    }
+
+    public void disconnect(boolean onlyOutput)
+    {
+        EndPoint endPoint = getEndPoint();
+        // We need to gently close first, to allow
+        // SSL close alerts to be sent by Jetty
+        if (LOG.isDebugEnabled())
+            LOG.debug("Shutting down output {}",endPoint);
+        endPoint.shutdownOutput();
+        if (!onlyOutput)
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("Closing {}",endPoint);
+            endPoint.close();
+        }
+    }
+
+    private void failUpgrade(Throwable cause)
+    {
+        close();
+        connectPromise.failed(cause);
+    }
+
+    private void notifyConnect(ClientUpgradeResponse response)
+    {
+        connectPromise.setResponse(response);
+
+        UpgradeListener handshakeListener = connectPromise.getUpgradeListener();
+        if (handshakeListener != null)
+        {
+            handshakeListener.onHandshakeResponse(response);
+        }
+    }
+
+    @Override
+    public void onFillable()
+    {
+        if (LOG.isDebugEnabled())
+        {
+            LOG.debug("onFillable");
+        }
+        ByteBuffer buffer = bufferPool.acquire(getInputBufferSize(),false);
+        BufferUtil.clear(buffer);
+        boolean readMore = false;
+        try
+        {
+            readMore = read(buffer);
+        }
+        finally
+        {
+            bufferPool.release(buffer);
+        }
+
+        if (readMore)
+        {
+            fillInterested();
+        }
+    }
+
+    @Override
+    public void onOpen()
+    {
+        super.onOpen();
+        getExecutor().execute(new SendUpgradeRequest());
+    }
+    
+    @Override
+    public void onClose()
+    {
+        if (LOG.isDebugEnabled())
+        {
+            LOG.warn("Closed connection {}",this);
+        }
+        super.onClose();
+    }
+    
+    @Override
+    protected boolean onReadTimeout()
+    {
+        if (LOG.isDebugEnabled())
+        {
+            LOG.warn("Timeout on connection {}",this);
+        }
+        
+        failUpgrade(new IOException("Timeout while performing WebSocket Upgrade"));
+        
+        return super.onReadTimeout();
+    }
+
+    /**
+     * Read / Parse the waiting read/fill buffer
+     * 
+     * @param buffer
+     *            the buffer to fill into from the endpoint
+     * @return true if there is more to read, false if reading should stop
+     */
+    private boolean read(ByteBuffer buffer)
+    {
+        EndPoint endPoint = getEndPoint();
+        try
+        {
+            while (true)
+            {
+                int filled = endPoint.fill(buffer);
+                if (filled == 0)
+                {
+                    return true;
+                }
+                else if (filled < 0)
+                {
+                    LOG.warn("read - EOF Reached");
+                    failUpgrade(new EOFException("Reading WebSocket Upgrade response"));
+                    return false;
+                }
+                else
+                {
+                    if (LOG.isDebugEnabled())
+                    {
+                        LOG.debug("Filled {} bytes - {}",filled,BufferUtil.toDetailString(buffer));
+                    }
+                    ClientUpgradeResponse resp = (ClientUpgradeResponse)parser.parse(buffer);
+                    if (resp != null)
+                    {
+                        // Got a response!
+                        validateResponse(resp);
+                        notifyConnect(resp);
+                        upgradeConnection(resp);
+                        if (buffer.hasRemaining())
+                        {
+                            LOG.debug("Has remaining client bytebuffer of {}",buffer.remaining());
+                        }
+                        return false; // do no more reading
+                    }
+                }
+            }
+        }
+        catch (IOException | ParseException e)
+        {
+            UpgradeException ue = new UpgradeException(request.getRequestURI(),e);
+            connectPromise.failed(ue);
+            disconnect(false);
+            return false;
+        }
+        catch (UpgradeException e)
+        {
+            connectPromise.failed(e);
+            disconnect(false);
+            return false;
+        }
+    }
+
+    private void upgradeConnection(ClientUpgradeResponse response)
+    {
+        EndPoint endp = getEndPoint();
+        Executor executor = getExecutor();
+        
+        EventDriver websocket = connectPromise.getDriver();
+        WebSocketPolicy policy = websocket.getPolicy();
+
+        WebSocketClientConnection connection = new WebSocketClientConnection(endp,executor,connectPromise,policy);
+
+        SessionFactory sessionFactory = connectPromise.getClient().getSessionFactory();
+        WebSocketSession session = sessionFactory.createSession(request.getRequestURI(),websocket,connection);
+        session.setPolicy(policy);
+        session.setUpgradeResponse(response);
+
+        connection.setSession(session);
+
+        // Initialize / Negotiate Extensions
+        ExtensionStack extensionStack = new ExtensionStack(connectPromise.getClient().getExtensionFactory());
+        extensionStack.negotiate(response.getExtensions());
+
+        extensionStack.configure(connection.getParser());
+        extensionStack.configure(connection.getGenerator());
+
+        // Setup Incoming Routing
+        connection.setNextIncomingFrames(extensionStack);
+        extensionStack.setNextIncoming(session);
+
+        // Setup Outgoing Routing
+        session.setOutgoingHandler(extensionStack);
+        extensionStack.setNextOutgoing(connection);
+
+        session.addBean(extensionStack);
+        connectPromise.getClient().addManaged(session);
+
+        // Now swap out the connection
+        // TODO use endp.upgrade ???
+        endp.setConnection(connection);
+        connection.onOpen();
+    }
+
+    private void validateResponse(ClientUpgradeResponse response)
+    {
+        // Validate Response Status Code
+        if (response.getStatusCode() != SWITCHING_PROTOCOLS)
+        {
+            throw new UpgradeException(request.getRequestURI(),response.getStatusCode(),"Didn't switch protocols");
+        }
+
+        // Validate Connection header
+        String connection = response.getHeader("Connection");
+        if (!"upgrade".equalsIgnoreCase(connection))
+        {
+            throw new UpgradeException(request.getRequestURI(),response.getStatusCode(),"Connection is " + connection + " (expected upgrade)");
+        }
+
+        // Check the Accept hash
+        String reqKey = request.getKey();
+        String expectedHash = AcceptHash.hashKey(reqKey);
+        String respHash = response.getHeader("Sec-WebSocket-Accept");
+
+        response.setSuccess(true);
+        if (expectedHash.equalsIgnoreCase(respHash) == false)
+        {
+            response.setSuccess(false);
+            throw new UpgradeException(request.getRequestURI(),response.getStatusCode(),"Invalid Sec-WebSocket-Accept hash");
+        }
+
+        // Parse extensions
+        List<ExtensionConfig> extensions = new ArrayList<>();
+        List<String> extValues = response.getHeaders("Sec-WebSocket-Extensions");
+        if (extValues != null)
+        {
+            for (String extVal : extValues)
+            {
+                QuotedStringTokenizer tok = new QuotedStringTokenizer(extVal,",");
+                while (tok.hasMoreTokens())
+                {
+                    extensions.add(ExtensionConfig.parse(tok.nextToken()));
+                }
+            }
+        }
+        response.setExtensions(extensions);
+    }
+}
diff --git a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/UpgradeListener.java b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/UpgradeListener.java
new file mode 100644
index 0000000..e5d7cb1
--- /dev/null
+++ b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/UpgradeListener.java
@@ -0,0 +1,32 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.client.io;
+
+import org.eclipse.jetty.websocket.api.UpgradeRequest;
+import org.eclipse.jetty.websocket.api.UpgradeResponse;
+
+/**
+ * Listener for Handshake/Upgrade events.
+ */
+public interface UpgradeListener
+{
+    public void onHandshakeRequest(UpgradeRequest request);
+
+    public void onHandshakeResponse(UpgradeResponse response);
+}
diff --git a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/WebSocketClientConnection.java b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/WebSocketClientConnection.java
new file mode 100644
index 0000000..4ee106c
--- /dev/null
+++ b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/WebSocketClientConnection.java
@@ -0,0 +1,116 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.client.io;
+
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.BatchMode;
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.api.WriteCallback;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.api.extensions.IncomingFrames;
+import org.eclipse.jetty.websocket.client.masks.Masker;
+import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.WebSocketSession;
+import org.eclipse.jetty.websocket.common.io.AbstractWebSocketConnection;
+
+/**
+ * Client side WebSocket physical connection.
+ */
+public class WebSocketClientConnection extends AbstractWebSocketConnection
+{
+    private static final Logger LOG = Log.getLogger(WebSocketClientConnection.class);
+    private final ConnectPromise connectPromise;
+    private final Masker masker;
+    private final AtomicBoolean opened = new AtomicBoolean(false);
+
+    public WebSocketClientConnection(EndPoint endp, Executor executor, ConnectPromise connectPromise, WebSocketPolicy policy)
+    {
+        super(endp,executor,connectPromise.getClient().getScheduler(),policy,connectPromise.getClient().getBufferPool());
+        this.connectPromise = connectPromise;
+        this.masker = connectPromise.getMasker();
+        assert (this.masker != null);
+    }
+
+    @Override
+    public InetSocketAddress getLocalAddress()
+    {
+        return getEndPoint().getLocalAddress();
+    }
+
+    @Override
+    public InetSocketAddress getRemoteAddress()
+    {
+        return getEndPoint().getRemoteAddress();
+    }
+
+    @Override
+    public void onClose()
+    {
+        super.onClose();
+        ConnectionManager connectionManager = connectPromise.getClient().getConnectionManager();
+        connectionManager.removeSession(getSession());
+    }
+
+    @Override
+    public void onOpen()
+    {
+        boolean beenOpened = opened.getAndSet(true);
+        if (!beenOpened)
+        {
+            WebSocketSession session = getSession();
+            ConnectionManager connectionManager = connectPromise.getClient().getConnectionManager();
+            connectionManager.addSession(session);
+            connectPromise.succeeded(session);
+
+            ByteBuffer extraBuf = connectPromise.getResponse().getRemainingBuffer();
+            if (extraBuf.hasRemaining())
+            {
+                LOG.debug("Parsing extra remaining buffer from UpgradeConnection");
+                getParser().parse(extraBuf);
+            }
+        }
+        super.onOpen();
+    }
+
+    /**
+     * Override to set the masker.
+     */
+    @Override
+    public void outgoingFrame(Frame frame, WriteCallback callback, BatchMode batchMode)
+    {
+        if (frame instanceof WebSocketFrame)
+        {
+            masker.setMask((WebSocketFrame)frame);
+        }
+        super.outgoingFrame(frame,callback, batchMode);
+    }
+
+    @Override
+    public void setNextIncomingFrames(IncomingFrames incoming)
+    {
+        getParser().setIncomingFramesHandler(incoming);
+    }
+}
diff --git a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/WebSocketClientSelectorManager.java b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/WebSocketClientSelectorManager.java
new file mode 100644
index 0000000..15597e3
--- /dev/null
+++ b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/WebSocketClientSelectorManager.java
@@ -0,0 +1,149 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.client.io;
+
+import java.io.IOException;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.SocketChannel;
+import java.util.concurrent.Executor;
+import javax.net.ssl.SSLEngine;
+
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.SelectChannelEndPoint;
+import org.eclipse.jetty.io.SelectorManager;
+import org.eclipse.jetty.io.ssl.SslConnection;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.client.WebSocketClient;
+
+public class WebSocketClientSelectorManager extends SelectorManager
+{
+    private static final Logger LOG = Log.getLogger(WebSocketClientSelectorManager.class);
+    private final WebSocketPolicy policy;
+    private final ByteBufferPool bufferPool;
+    private SslContextFactory sslContextFactory;
+
+    public WebSocketClientSelectorManager(WebSocketClient client)
+    {
+        super(client.getExecutor(),client.getScheduler());
+        this.bufferPool = client.getBufferPool();
+        this.policy = client.getPolicy();
+    }
+
+    @Override
+    protected void connectionFailed(SocketChannel channel, Throwable ex, Object attachment)
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("Connection Failed",ex);
+        ConnectPromise connect = (ConnectPromise)attachment;
+        connect.failed(ex);
+    }
+
+    public SslContextFactory getSslContextFactory()
+    {
+        return sslContextFactory;
+    }
+
+    @Override
+    public Connection newConnection(final SocketChannel channel, EndPoint endPoint, final Object attachment) throws IOException
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("newConnection({},{},{})",channel,endPoint,attachment);
+        ConnectPromise connectPromise = (ConnectPromise)attachment;
+
+        try
+        {
+            String scheme = connectPromise.getRequest().getRequestURI().getScheme();
+
+            if ("wss".equalsIgnoreCase(scheme))
+            {
+                // Encrypted "wss://"
+                SslContextFactory sslContextFactory = getSslContextFactory();
+                if (sslContextFactory != null)
+                {
+                    SSLEngine engine = newSSLEngine(sslContextFactory,channel);
+                    SslConnection sslConnection = new SslConnection(bufferPool,getExecutor(),endPoint,engine);
+                    sslConnection.setRenegotiationAllowed(sslContextFactory.isRenegotiationAllowed());
+                    EndPoint sslEndPoint = sslConnection.getDecryptedEndPoint();
+
+                    Connection connection = newUpgradeConnection(channel,sslEndPoint,connectPromise);
+                    sslEndPoint.setIdleTimeout(connectPromise.getClient().getMaxIdleTimeout());
+                    sslEndPoint.setConnection(connection);
+                    return sslConnection;
+                }
+                else
+                {
+                    throw new IOException("Cannot init SSL");
+                }
+            }
+            else
+            {
+                // Standard "ws://"
+                endPoint.setIdleTimeout(connectPromise.getDriver().getPolicy().getIdleTimeout());
+                return newUpgradeConnection(channel,endPoint,connectPromise);
+            }
+        }
+        catch (IOException e)
+        {
+            LOG.ignore(e);
+            connectPromise.failed(e);
+            // rethrow
+            throw e;
+        }
+    }
+
+    @Override
+    protected EndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey selectionKey) throws IOException
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("newEndPoint({}, {}, {})",channel,selectSet,selectionKey);
+        return new SelectChannelEndPoint(channel,selectSet,selectionKey,getScheduler(),policy.getIdleTimeout());
+    }
+
+    public SSLEngine newSSLEngine(SslContextFactory sslContextFactory, SocketChannel channel)
+    {
+        String peerHost = channel.socket().getInetAddress().getHostName();
+        int peerPort = channel.socket().getPort();
+        SSLEngine engine = sslContextFactory.newSSLEngine(peerHost,peerPort);
+        engine.setUseClientMode(true);
+        return engine;
+    }
+
+    public UpgradeConnection newUpgradeConnection(SocketChannel channel, EndPoint endPoint, ConnectPromise connectPromise)
+    {
+        WebSocketClient client = connectPromise.getClient();
+        Executor executor = client.getExecutor();
+        UpgradeConnection connection = new UpgradeConnection(endPoint,executor,connectPromise);
+        return connection;
+    }
+
+    public void setSslContextFactory(SslContextFactory sslContextFactory)
+    {
+        this.sslContextFactory = sslContextFactory;
+    }
+    
+    public WebSocketPolicy getPolicy()
+    {
+        return policy;
+    }
+}
diff --git a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/package-info.java b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/package-info.java
new file mode 100644
index 0000000..0958276
--- /dev/null
+++ b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty WebSocket Client : I/O Implementation [<em>Internal Use Only</em>]
+ */
+package org.eclipse.jetty.websocket.client.io;
+
diff --git a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/masks/FixedMasker.java b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/masks/FixedMasker.java
new file mode 100644
index 0000000..48a4a67
--- /dev/null
+++ b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/masks/FixedMasker.java
@@ -0,0 +1,47 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.client.masks;
+
+import java.util.Arrays;
+
+import org.eclipse.jetty.websocket.common.WebSocketFrame;
+
+public class FixedMasker implements Masker
+{
+    private final byte[] mask;
+
+    public FixedMasker()
+    {
+        this(new byte[]
+        { (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff });
+    }
+
+    public FixedMasker(byte[] mask)
+    {
+        // Copy to avoid that external code keeps a reference
+        // to the array parameter to modify masking on-the-fly
+        this.mask=Arrays.copyOf(mask, 4);
+    }
+
+    @Override
+    public void setMask(WebSocketFrame frame)
+    {
+        frame.setMask(mask);
+    }
+}
diff --git a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/masks/Masker.java b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/masks/Masker.java
new file mode 100644
index 0000000..47446f8
--- /dev/null
+++ b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/masks/Masker.java
@@ -0,0 +1,37 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.client.masks;
+
+import org.eclipse.jetty.websocket.common.WebSocketFrame;
+
+/**
+ * Interface for various Masker implementations.
+ */
+public interface Masker
+{
+    /**
+     * Set the mask on the provided {@link WebSocketFrame}.
+     * <p>
+     * Implementations MUST set the mask on the frame.
+     * 
+     * @param frame
+     *            the frame to set the mask on.
+     */
+    void setMask(WebSocketFrame frame);
+}
diff --git a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/masks/RandomMasker.java b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/masks/RandomMasker.java
new file mode 100644
index 0000000..4c17643
--- /dev/null
+++ b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/masks/RandomMasker.java
@@ -0,0 +1,46 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.client.masks;
+
+import java.util.Random;
+
+import org.eclipse.jetty.websocket.common.WebSocketFrame;
+
+public class RandomMasker implements Masker
+{
+    private final Random random;
+
+    public RandomMasker()
+    {
+        this(new Random());
+    }
+
+    public RandomMasker(Random random)
+    {
+        this.random = random;
+    }
+
+    @Override
+    public void setMask(WebSocketFrame frame)
+    {
+        byte mask[] = new byte[4];
+        random.nextBytes(mask);
+        frame.setMask(mask);
+    }
+}
diff --git a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/masks/ZeroMasker.java b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/masks/ZeroMasker.java
new file mode 100644
index 0000000..6f5c2d5
--- /dev/null
+++ b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/masks/ZeroMasker.java
@@ -0,0 +1,40 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.client.masks;
+
+import java.util.Arrays;
+
+import org.eclipse.jetty.websocket.common.WebSocketFrame;
+
+public class ZeroMasker implements Masker
+{
+    private final byte mask[];
+
+    public ZeroMasker()
+    {
+        this.mask = new byte[4];
+        Arrays.fill(mask,(byte)0);
+    }
+
+    @Override
+    public void setMask(WebSocketFrame frame)
+    {
+        frame.setMask(mask);
+    }
+}
diff --git a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/masks/package-info.java b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/masks/package-info.java
new file mode 100644
index 0000000..5ae4fe2
--- /dev/null
+++ b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/masks/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty WebSocket Client : Frame Masking Implementations
+ */
+package org.eclipse.jetty.websocket.client.masks;
+
diff --git a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/package-info.java b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/package-info.java
new file mode 100644
index 0000000..22d4366
--- /dev/null
+++ b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/package-info.java
@@ -0,0 +1,34 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty WebSocket Client API
+ * <p>
+ * The core class is {@link org.eclipse.jetty.websocket.client.WebSocketClient}, which acts as a central configuration object (for example
+ * for {@link org.eclipse.jetty.websocket.client.WebSocketClient#setConnectTimeout(int) connect timeouts}, {@link WebSocketClient#setCookieStore(CookieStore)
+ * request cookie store}, etc.) and as a factory for WebSocket {@link org.eclipse.jetty.websocket.api.Session} objects.
+ * <p>
+ * The <a href="https://tools.ietf.org/html/rfc6455">WebSocket protocol</a> is based on a framing protocol built
+ * around an upgraded HTTP connection.  It is primarily focused on the sending of messages (text or binary), with an
+ * occasional control frame (close, ping, pong) that this implementation uses.  
+ * <p />
+ * {@link org.eclipse.jetty.websocket.client.WebSocketClient} holds a number of {@link org.eclipse.jetty.websocket.api.Session Sessions}, which in turn
+ * is used to manage physical vs virtual connection handling (mux extension).
+ */
+package org.eclipse.jetty.websocket.client;
+
diff --git a/jetty-websocket/websocket-client/src/test/java/examples/SimpleEchoClient.java b/jetty-websocket/websocket-client/src/test/java/examples/SimpleEchoClient.java
new file mode 100644
index 0000000..6bcbeee
--- /dev/null
+++ b/jetty-websocket/websocket-client/src/test/java/examples/SimpleEchoClient.java
@@ -0,0 +1,70 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package examples;
+
+import java.net.URI;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
+import org.eclipse.jetty.websocket.client.WebSocketClient;
+
+/**
+ * Example of a simple Echo Client.
+ */
+public class SimpleEchoClient
+{
+    public static void main(String[] args)
+    {
+        String destUri = "ws://echo.websocket.org";
+        if (args.length > 0)
+        {
+            destUri = args[0];
+        }
+
+        WebSocketClient client = new WebSocketClient();
+        SimpleEchoSocket socket = new SimpleEchoSocket();
+        try
+        {
+            client.start();
+
+            URI echoUri = new URI(destUri);
+            ClientUpgradeRequest request = new ClientUpgradeRequest();
+            client.connect(socket,echoUri,request);
+            System.out.printf("Connecting to : %s%n",echoUri);
+
+            // wait for closed socket connection.
+            socket.awaitClose(5,TimeUnit.SECONDS);
+        }
+        catch (Throwable t)
+        {
+            t.printStackTrace();
+        }
+        finally
+        {
+            try
+            {
+                client.stop();
+            }
+            catch (Exception e)
+            {
+                e.printStackTrace();
+            }
+        }
+    }
+}
diff --git a/jetty-websocket/websocket-client/src/test/java/examples/SimpleEchoSocket.java b/jetty-websocket/websocket-client/src/test/java/examples/SimpleEchoSocket.java
new file mode 100644
index 0000000..b6049e7
--- /dev/null
+++ b/jetty-websocket/websocket-client/src/test/java/examples/SimpleEchoSocket.java
@@ -0,0 +1,87 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package examples;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.StatusCode;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
+import org.eclipse.jetty.websocket.api.annotations.WebSocket;
+
+/**
+ * Basic Echo Client Socket
+ */
+ at WebSocket(maxTextMessageSize = 64 * 1024)
+public class SimpleEchoSocket
+{
+    private final CountDownLatch closeLatch;
+    @SuppressWarnings("unused")
+    private Session session;
+
+    public SimpleEchoSocket()
+    {
+        this.closeLatch = new CountDownLatch(1);
+    }
+
+    public boolean awaitClose(int duration, TimeUnit unit) throws InterruptedException
+    {
+        return this.closeLatch.await(duration,unit);
+    }
+
+    @OnWebSocketClose
+    public void onClose(int statusCode, String reason)
+    {
+        System.out.printf("Connection closed: %d - %s%n",statusCode,reason);
+        this.session = null;
+        this.closeLatch.countDown(); // trigger latch
+    }
+
+    @OnWebSocketConnect
+    public void onConnect(Session session)
+    {
+        System.out.printf("Got connect: %s%n",session);
+        this.session = session;
+        try
+        {
+            Future<Void> fut;
+            fut = session.getRemote().sendStringByFuture("Hello");
+            fut.get(2,TimeUnit.SECONDS); // wait for send to complete.
+
+            fut = session.getRemote().sendStringByFuture("Thanks for the conversation.");
+            fut.get(2,TimeUnit.SECONDS); // wait for send to complete.
+
+            session.close(StatusCode.NORMAL,"I'm done");
+        }
+        catch (Throwable t)
+        {
+            t.printStackTrace();
+        }
+    }
+
+    @OnWebSocketMessage
+    public void onMessage(String msg)
+    {
+        System.out.printf("Got msg: %s%n",msg);
+    }
+}
diff --git a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/BadNetworkTest.java b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/BadNetworkTest.java
new file mode 100644
index 0000000..4aa4c63
--- /dev/null
+++ b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/BadNetworkTest.java
@@ -0,0 +1,131 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.client;
+
+import java.net.URI;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.toolchain.test.TestTracker;
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.StatusCode;
+import org.eclipse.jetty.websocket.common.test.BlockheadServer;
+import org.eclipse.jetty.websocket.common.test.BlockheadServer.ServerConnection;
+import org.eclipse.jetty.websocket.common.test.LeakTrackingBufferPoolRule;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+/**
+ * Tests for conditions due to bad networking.
+ */
+public class BadNetworkTest
+{
+    @Rule
+    public TestTracker tt = new TestTracker();
+
+    @Rule
+    public LeakTrackingBufferPoolRule bufferPool = new LeakTrackingBufferPoolRule("Test");
+
+    private BlockheadServer server;
+    private WebSocketClient client;
+
+    @Before
+    public void startClient() throws Exception
+    {
+        client = new WebSocketClient(bufferPool);
+        client.getPolicy().setIdleTimeout(250);
+        client.start();
+    }
+
+    @Before
+    public void startServer() throws Exception
+    {
+        server = new BlockheadServer();
+        server.start();
+    }
+
+    @After
+    public void stopClient() throws Exception
+    {
+        client.stop();
+    }
+
+    @After
+    public void stopServer() throws Exception
+    {
+        server.stop();
+    }
+
+    @Test
+    public void testAbruptClientClose() throws Exception
+    {
+        JettyTrackingSocket wsocket = new JettyTrackingSocket();
+
+        URI wsUri = server.getWsUri();
+        Future<Session> future = client.connect(wsocket,wsUri);
+
+        ServerConnection ssocket = server.accept();
+        ssocket.upgrade();
+
+        // Validate that we are connected
+        future.get(500,TimeUnit.MILLISECONDS);
+        wsocket.waitForConnected(500,TimeUnit.MILLISECONDS);
+
+        // Have client disconnect abruptly
+        Session session = wsocket.getSession();
+        session.disconnect();
+
+        // Client Socket should see close
+        wsocket.waitForClose(10,TimeUnit.SECONDS);
+
+        // Client Socket should see a close event, with status NO_CLOSE
+        // This event is automatically supplied by the underlying WebSocketClientConnection
+        // in the situation of a bad network connection.
+        wsocket.assertCloseCode(StatusCode.NO_CLOSE);
+    }
+
+    @Test
+    public void testAbruptServerClose() throws Exception
+    {
+        JettyTrackingSocket wsocket = new JettyTrackingSocket();
+
+        URI wsUri = server.getWsUri();
+        Future<Session> future = client.connect(wsocket,wsUri);
+
+        ServerConnection ssocket = server.accept();
+        ssocket.upgrade();
+
+        // Validate that we are connected
+        future.get(500,TimeUnit.MILLISECONDS);
+        wsocket.waitForConnected(500,TimeUnit.MILLISECONDS);
+
+        // Have server disconnect abruptly
+        ssocket.disconnect();
+
+        // Wait for close (as response to idle timeout)
+        wsocket.waitForClose(10,TimeUnit.SECONDS);
+
+        // Client Socket should see a close event, with status NO_CLOSE
+        // This event is automatically supplied by the underlying WebSocketClientConnection
+        // in the situation of a bad network connection.
+        wsocket.assertCloseCode(StatusCode.NO_CLOSE);
+    }
+}
diff --git a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/ClientCloseTest.java b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/ClientCloseTest.java
new file mode 100644
index 0000000..01ca91b
--- /dev/null
+++ b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/ClientCloseTest.java
@@ -0,0 +1,635 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.client;
+
+import static org.hamcrest.Matchers.*;
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.nio.ByteBuffer;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.SocketChannel;
+import java.util.Arrays;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.EofException;
+import org.eclipse.jetty.io.SelectChannelEndPoint;
+import org.eclipse.jetty.io.SelectorManager.ManagedSelector;
+import org.eclipse.jetty.toolchain.test.EventQueue;
+import org.eclipse.jetty.toolchain.test.TestTracker;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.log.StacklessLogging;
+import org.eclipse.jetty.util.thread.Scheduler;
+import org.eclipse.jetty.websocket.api.ProtocolException;
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.StatusCode;
+import org.eclipse.jetty.websocket.api.WebSocketAdapter;
+import org.eclipse.jetty.websocket.client.io.ConnectionManager;
+import org.eclipse.jetty.websocket.client.io.WebSocketClientSelectorManager;
+import org.eclipse.jetty.websocket.common.CloseInfo;
+import org.eclipse.jetty.websocket.common.OpCode;
+import org.eclipse.jetty.websocket.common.Parser;
+import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.WebSocketSession;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
+import org.eclipse.jetty.websocket.common.io.AbstractWebSocketConnection;
+import org.eclipse.jetty.websocket.common.test.BlockheadServer;
+import org.eclipse.jetty.websocket.common.test.BlockheadServer.ServerConnection;
+import org.eclipse.jetty.websocket.common.test.IncomingFramesCapture;
+import org.eclipse.jetty.websocket.common.test.RawFrameBuilder;
+import org.hamcrest.Matcher;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class ClientCloseTest
+{
+    private static final Logger LOG = Log.getLogger(ClientCloseTest.class);
+
+    private static class CloseTrackingSocket extends WebSocketAdapter
+    {
+        private static final Logger LOG = Log.getLogger(ClientCloseTest.CloseTrackingSocket.class);
+
+        public int closeCode = -1;
+        public String closeReason = null;
+        public CountDownLatch closeLatch = new CountDownLatch(1);
+        public AtomicInteger closeCount = new AtomicInteger(0);
+        public CountDownLatch openLatch = new CountDownLatch(1);
+
+        public EventQueue<String> messageQueue = new EventQueue<>();
+        public EventQueue<Throwable> errorQueue = new EventQueue<>();
+
+        public void assertNoCloseEvent()
+        {
+            Assert.assertThat("Client Close Event",closeLatch.getCount(),is(1L));
+            Assert.assertThat("Client Close Event Status Code ",closeCode,is(-1));
+        }
+
+        public void assertReceivedCloseEvent(int clientTimeoutMs, Matcher<Integer> statusCodeMatcher, Matcher<String> reasonMatcher)
+                throws InterruptedException
+        {
+            long maxTimeout = clientTimeoutMs * 2;
+
+            Assert.assertThat("Client Close Event Occurred",closeLatch.await(maxTimeout,TimeUnit.MILLISECONDS),is(true));
+            Assert.assertThat("Client Close Event Count",closeCount.get(),is(1));
+            Assert.assertThat("Client Close Event Status Code",closeCode,statusCodeMatcher);
+            if (reasonMatcher == null)
+            {
+                Assert.assertThat("Client Close Event Reason",closeReason,nullValue());
+            }
+            else
+            {
+                Assert.assertThat("Client Close Event Reason",closeReason,reasonMatcher);
+            }
+        }
+
+        public void assertReceivedError(Class<? extends Throwable> expectedThrownClass, Matcher<String> messageMatcher) throws TimeoutException,
+                InterruptedException
+        {
+            errorQueue.awaitEventCount(1,500,TimeUnit.MILLISECONDS);
+            Throwable actual = errorQueue.poll();
+            Assert.assertThat("Client Error Event",actual,instanceOf(expectedThrownClass));
+            if (messageMatcher == null)
+            {
+                Assert.assertThat("Client Error Event Message",actual.getMessage(),nullValue());
+            }
+            else
+            {
+                Assert.assertThat("Client Error Event Message",actual.getMessage(),messageMatcher);
+            }
+        }
+
+        public void clearQueues()
+        {
+            messageQueue.clear();
+            errorQueue.clear();
+        }
+
+        @Override
+        public void onWebSocketClose(int statusCode, String reason)
+        {
+            LOG.debug("onWebSocketClose({},{})",statusCode,reason);
+            super.onWebSocketClose(statusCode,reason);
+            closeCount.incrementAndGet();
+            closeCode = statusCode;
+            closeReason = reason;
+            closeLatch.countDown();
+        }
+
+        @Override
+        public void onWebSocketConnect(Session session)
+        {
+            super.onWebSocketConnect(session);
+            openLatch.countDown();
+        }
+
+        @Override
+        public void onWebSocketError(Throwable cause)
+        {
+            LOG.debug("onWebSocketError",cause);
+            Assert.assertThat("Error capture",errorQueue.offer(cause),is(true));
+        }
+
+        @Override
+        public void onWebSocketText(String message)
+        {
+            LOG.debug("onWebSocketText({})",message);
+            messageQueue.offer(message);
+        }
+
+        public EndPoint getEndPoint() throws Exception
+        {
+            Session session = getSession();
+            Assert.assertThat("Session type",session,instanceOf(WebSocketSession.class));
+
+            WebSocketSession wssession = (WebSocketSession)session;
+            Field fld = wssession.getClass().getDeclaredField("connection");
+            fld.setAccessible(true);
+            Assert.assertThat("Field: connection",fld,notNullValue());
+
+            Object val = fld.get(wssession);
+            Assert.assertThat("Connection type",val,instanceOf(AbstractWebSocketConnection.class));
+            @SuppressWarnings("resource")
+            AbstractWebSocketConnection wsconn = (AbstractWebSocketConnection)val;
+            return wsconn.getEndPoint();
+        }
+    }
+
+    @Rule
+    public TestTracker tt = new TestTracker();
+
+    private BlockheadServer server;
+    private WebSocketClient client;
+
+    private void confirmConnection(CloseTrackingSocket clientSocket, Future<Session> clientFuture, ServerConnection serverConn) throws Exception
+    {
+        // Wait for client connect on via future
+        clientFuture.get(500,TimeUnit.MILLISECONDS);
+
+        // Wait for client connect via client websocket
+        Assert.assertThat("Client WebSocket is Open",clientSocket.openLatch.await(500,TimeUnit.MILLISECONDS),is(true));
+
+        try
+        {
+            // Send message from client to server
+            final String echoMsg = "echo-test";
+            Future<Void> testFut = clientSocket.getRemote().sendStringByFuture(echoMsg);
+
+            // Wait for send future
+            testFut.get(500,TimeUnit.MILLISECONDS);
+
+            // Read Frame on server side
+            IncomingFramesCapture serverCapture = serverConn.readFrames(1,500,TimeUnit.MILLISECONDS);
+            serverCapture.assertNoErrors();
+            serverCapture.assertFrameCount(1);
+            WebSocketFrame frame = serverCapture.getFrames().poll();
+            Assert.assertThat("Server received frame",frame.getOpCode(),is(OpCode.TEXT));
+            Assert.assertThat("Server received frame payload",frame.getPayloadAsUTF8(),is(echoMsg));
+
+            // Server send echo reply
+            serverConn.write(new TextFrame().setPayload(echoMsg));
+
+            // Wait for received echo
+            clientSocket.messageQueue.awaitEventCount(1,1,TimeUnit.SECONDS);
+
+            // Verify received message
+            String recvMsg = clientSocket.messageQueue.poll();
+            Assert.assertThat("Received message",recvMsg,is(echoMsg));
+
+            // Verify that there are no errors
+            Assert.assertThat("Error events",clientSocket.errorQueue,empty());
+        }
+        finally
+        {
+            clientSocket.clearQueues();
+        }
+    }
+
+    private void confirmServerReceivedCloseFrame(ServerConnection serverConn, int expectedCloseCode, Matcher<String> closeReasonMatcher) throws IOException,
+            TimeoutException
+    {
+        IncomingFramesCapture serverCapture = serverConn.readFrames(1,500,TimeUnit.MILLISECONDS);
+        serverCapture.assertNoErrors();
+        serverCapture.assertFrameCount(1);
+        serverCapture.assertHasFrame(OpCode.CLOSE,1);
+        WebSocketFrame frame = serverCapture.getFrames().poll();
+        Assert.assertThat("Server received close frame",frame.getOpCode(),is(OpCode.CLOSE));
+        CloseInfo closeInfo = new CloseInfo(frame);
+        Assert.assertThat("Server received close code",closeInfo.getStatusCode(),is(expectedCloseCode));
+        if (closeReasonMatcher == null)
+        {
+            Assert.assertThat("Server received close reason",closeInfo.getReason(),nullValue());
+        }
+        else
+        {
+            Assert.assertThat("Server received close reason",closeInfo.getReason(),closeReasonMatcher);
+        }
+    }
+
+    public static class TestWebSocketClient extends WebSocketClient
+    {
+        @Override
+        protected ConnectionManager newConnectionManager()
+        {
+            return new TestConnectionManager(this);
+        }
+    }
+
+    public static class TestConnectionManager extends ConnectionManager
+    {
+        public TestConnectionManager(WebSocketClient client)
+        {
+            super(client);
+        }
+
+        @Override
+        protected WebSocketClientSelectorManager newWebSocketClientSelectorManager(WebSocketClient client)
+        {
+            return new TestSelectorManager(client);
+        }
+    }
+
+    public static class TestSelectorManager extends WebSocketClientSelectorManager
+    {
+        public TestSelectorManager(WebSocketClient client)
+        {
+            super(client);
+        }
+
+        @Override
+        protected EndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey selectionKey) throws IOException
+        {
+            return new TestEndPoint(channel,selectSet,selectionKey,getScheduler(),getPolicy().getIdleTimeout());
+        }
+    }
+
+    public static class TestEndPoint extends SelectChannelEndPoint
+    {
+        public AtomicBoolean congestedFlush = new AtomicBoolean(false);
+
+        public TestEndPoint(SocketChannel channel, ManagedSelector selector, SelectionKey key, Scheduler scheduler, long idleTimeout)
+        {
+            super(channel,selector,key,scheduler,idleTimeout);
+        }
+
+        @Override
+        public boolean flush(ByteBuffer... buffers) throws IOException
+        {
+            boolean flushed = super.flush(buffers);
+            congestedFlush.set(!flushed);
+            return flushed;
+        }
+    }
+
+    @Before
+    public void startClient() throws Exception
+    {
+        client = new TestWebSocketClient();
+        client.start();
+    }
+
+    @Before
+    public void startServer() throws Exception
+    {
+        server = new BlockheadServer();
+        server.start();
+    }
+
+    @After
+    public void stopClient() throws Exception
+    {
+        if (client.isRunning())
+        {
+            client.stop();
+        }
+    }
+
+    @After
+    public void stopServer() throws Exception
+    {
+        server.stop();
+    }
+
+    @Test
+    public void testHalfClose() throws Exception
+    {
+        // Set client timeout
+        final int timeout = 1000;
+        client.setMaxIdleTimeout(timeout);
+
+        // Client connects
+        CloseTrackingSocket clientSocket = new CloseTrackingSocket();
+        Future<Session> clientConnectFuture = client.connect(clientSocket,server.getWsUri());
+
+        // Server accepts connect
+        ServerConnection serverConn = server.accept();
+        serverConn.upgrade();
+
+        // client confirms connection via echo
+        confirmConnection(clientSocket,clientConnectFuture,serverConn);
+
+        // client sends close frame (code 1000, normal)
+        final String origCloseReason = "Normal Close";
+        clientSocket.getSession().close(StatusCode.NORMAL,origCloseReason);
+
+        // server receives close frame
+        confirmServerReceivedCloseFrame(serverConn,StatusCode.NORMAL,is(origCloseReason));
+
+        // server sends 2 messages
+        serverConn.write(new TextFrame().setPayload("Hello"));
+        serverConn.write(new TextFrame().setPayload("World"));
+
+        // server sends close frame (code 1000, no reason)
+        CloseInfo sclose = new CloseInfo(StatusCode.NORMAL,"From Server");
+        serverConn.write(sclose.asFrame());
+
+        // client receives 2 messages
+        clientSocket.messageQueue.awaitEventCount(2,1,TimeUnit.SECONDS);
+
+        // Verify received messages
+        String recvMsg = clientSocket.messageQueue.poll();
+        Assert.assertThat("Received message 1",recvMsg,is("Hello"));
+        recvMsg = clientSocket.messageQueue.poll();
+        Assert.assertThat("Received message 2",recvMsg,is("World"));
+
+        // Verify that there are no errors
+        Assert.assertThat("Error events",clientSocket.errorQueue,empty());
+
+        // client close event on ws-endpoint
+        clientSocket.assertReceivedCloseEvent(timeout,is(StatusCode.NORMAL),containsString("From Server"));
+    }
+
+    @Test
+    public void testNetworkCongestion() throws Exception
+    {
+        // Set client timeout
+        final int timeout = 1000;
+        client.setMaxIdleTimeout(timeout);
+
+        // Client connects
+        CloseTrackingSocket clientSocket = new CloseTrackingSocket();
+        Future<Session> clientConnectFuture = client.connect(clientSocket,server.getWsUri());
+
+        // Server accepts connect
+        ServerConnection serverConn = server.accept();
+        serverConn.upgrade();
+
+        // client confirms connection via echo
+        confirmConnection(clientSocket,clientConnectFuture,serverConn);
+
+        // client sends BIG frames (until it cannot write anymore)
+        // server must not read (for test purpose, in order to congest connection)
+        // when write is congested, client enqueue close frame
+        // client initiate write, but write never completes
+        EndPoint endp = clientSocket.getEndPoint();
+        Assert.assertThat("EndPoint is testable",endp,instanceOf(TestEndPoint.class));
+        TestEndPoint testendp = (TestEndPoint)endp;
+
+        char msg[] = new char[10240];
+        int writeCount = 0;
+        long writeSize = 0;
+        int i = 0;
+        while (!testendp.congestedFlush.get())
+        {
+            int z = i - ((i / 26) * 26);
+            char c = (char)('a' + z);
+            Arrays.fill(msg,c);
+            clientSocket.getRemote().sendStringByFuture(String.valueOf(msg));
+            writeCount++;
+            writeSize += msg.length;
+        }
+        LOG.debug("Wrote {} frames totalling {} bytes of payload before congestion kicked in",writeCount,writeSize);
+
+        // Verify that there are no errors
+        Assert.assertThat("Error events",clientSocket.errorQueue,empty());
+
+        // client idle timeout triggers close event on client ws-endpoint
+        // client close event on ws-endpoint
+        clientSocket.assertReceivedCloseEvent(timeout,
+                anyOf(is(StatusCode.SHUTDOWN),is(StatusCode.ABNORMAL)),
+                anyOf(containsString("Timeout"),containsString("timeout"),containsString("Write")));
+    }
+
+    @Test
+    public void testProtocolException() throws Exception
+    {
+        // Set client timeout
+        final int timeout = 1000;
+        client.setMaxIdleTimeout(timeout);
+
+        // Client connects
+        CloseTrackingSocket clientSocket = new CloseTrackingSocket();
+        Future<Session> clientConnectFuture = client.connect(clientSocket,server.getWsUri());
+
+        // Server accepts connect
+        ServerConnection serverConn = server.accept();
+        serverConn.upgrade();
+
+        // client confirms connection via echo
+        confirmConnection(clientSocket,clientConnectFuture,serverConn);
+
+        // client should not have received close message (yet)
+        clientSocket.assertNoCloseEvent();
+
+        // server sends bad close frame (too big of a reason message)
+        byte msg[] = new byte[400];
+        Arrays.fill(msg,(byte)'x');
+        ByteBuffer bad = ByteBuffer.allocate(500);
+        RawFrameBuilder.putOpFin(bad,OpCode.CLOSE,true);
+        RawFrameBuilder.putLength(bad,msg.length + 2,false);
+        bad.putShort((short)StatusCode.NORMAL);
+        bad.put(msg);
+        BufferUtil.flipToFlush(bad,0);
+        try (StacklessLogging quiet = new StacklessLogging(Parser.class))
+        {
+            serverConn.write(bad);
+
+            // client should have noticed the error
+            clientSocket.assertReceivedError(ProtocolException.class,containsString("Invalid control frame"));
+
+            // client parse invalid frame, notifies server of close (protocol error)
+            confirmServerReceivedCloseFrame(serverConn,StatusCode.PROTOCOL,allOf(containsString("Invalid control frame"),containsString("length")));
+        }
+
+        // server disconnects
+        serverConn.disconnect();
+
+        // client triggers close event on client ws-endpoint
+        clientSocket.assertReceivedCloseEvent(timeout,is(StatusCode.PROTOCOL),allOf(containsString("Invalid control frame"),containsString("length")));
+    }
+
+    @Test
+    public void testReadEOF() throws Exception
+    {
+        // Set client timeout
+        final int timeout = 1000;
+        client.setMaxIdleTimeout(timeout);
+
+        // Client connects
+        CloseTrackingSocket clientSocket = new CloseTrackingSocket();
+        Future<Session> clientConnectFuture = client.connect(clientSocket,server.getWsUri());
+
+        // Server accepts connect
+        ServerConnection serverConn = server.accept();
+        serverConn.upgrade();
+
+        // client confirms connection via echo
+        confirmConnection(clientSocket,clientConnectFuture,serverConn);
+
+        // client sends close frame
+        final String origCloseReason = "Normal Close";
+        clientSocket.getSession().close(StatusCode.NORMAL,origCloseReason);
+
+        // server receives close frame
+        confirmServerReceivedCloseFrame(serverConn,StatusCode.NORMAL,is(origCloseReason));
+
+        // client should not have received close message (yet)
+        clientSocket.assertNoCloseEvent();
+
+        // server shuts down connection (no frame reply)
+        serverConn.disconnect();
+
+        // client reads -1 (EOF)
+        // client triggers close event on client ws-endpoint
+        clientSocket.assertReceivedCloseEvent(timeout,is(StatusCode.ABNORMAL),containsString("EOF"));
+    }
+
+    @Test
+    public void testServerNoCloseHandshake() throws Exception
+    {
+        // Set client timeout
+        final int timeout = 1000;
+        client.setMaxIdleTimeout(timeout);
+
+        // Client connects
+        CloseTrackingSocket clientSocket = new CloseTrackingSocket();
+        Future<Session> clientConnectFuture = client.connect(clientSocket,server.getWsUri());
+
+        // Server accepts connect
+        ServerConnection serverConn = server.accept();
+        serverConn.upgrade();
+
+        // client confirms connection via echo
+        confirmConnection(clientSocket,clientConnectFuture,serverConn);
+
+        // client sends close frame
+        final String origCloseReason = "Normal Close";
+        clientSocket.getSession().close(StatusCode.NORMAL,origCloseReason);
+
+        // server receives close frame
+        confirmServerReceivedCloseFrame(serverConn,StatusCode.NORMAL,is(origCloseReason));
+
+        // client should not have received close message (yet)
+        clientSocket.assertNoCloseEvent();
+
+        // server never sends close frame handshake
+        // server sits idle
+
+        // client idle timeout triggers close event on client ws-endpoint
+        clientSocket.assertReceivedCloseEvent(timeout,is(StatusCode.SHUTDOWN),containsString("Timeout"));
+    }
+
+    @Test
+    public void testStopLifecycle() throws Exception
+    {
+        // Set client timeout
+        final int timeout = 1000;
+        client.setMaxIdleTimeout(timeout);
+
+        int clientCount = 3;
+        CloseTrackingSocket clientSockets[] = new CloseTrackingSocket[clientCount];
+        ServerConnection serverConns[] = new ServerConnection[clientCount];
+
+        // Connect Multiple Clients
+        for (int i = 0; i < clientCount; i++)
+        {
+            // Client Request Upgrade
+            clientSockets[i] = new CloseTrackingSocket();
+            Future<Session> clientConnectFuture = client.connect(clientSockets[i],server.getWsUri());
+
+            // Server accepts connection
+            serverConns[i] = server.accept();
+            serverConns[i].upgrade();
+
+            // client confirms connection via echo
+            confirmConnection(clientSockets[i],clientConnectFuture,serverConns[i]);
+        }
+
+        // client lifecycle stop
+        client.stop();
+
+        // clients send close frames (code 1001, shutdown)
+        for (int i = 0; i < clientCount; i++)
+        {
+            // server receives close frame
+            confirmServerReceivedCloseFrame(serverConns[i],StatusCode.SHUTDOWN,containsString("Shutdown"));
+        }
+
+        // clients disconnect
+        for (int i = 0; i < clientCount; i++)
+        {
+            clientSockets[i].assertReceivedCloseEvent(timeout,is(StatusCode.SHUTDOWN),containsString("Shutdown"));
+        }
+    }
+
+    @Test
+    public void testWriteException() throws Exception
+    {
+        // Set client timeout
+        final int timeout = 1000;
+        client.setMaxIdleTimeout(timeout);
+
+        // Client connects
+        CloseTrackingSocket clientSocket = new CloseTrackingSocket();
+        Future<Session> clientConnectFuture = client.connect(clientSocket,server.getWsUri());
+
+        // Server accepts connect
+        ServerConnection serverConn = server.accept();
+        serverConn.upgrade();
+
+        // client confirms connection via echo
+        confirmConnection(clientSocket,clientConnectFuture,serverConn);
+
+        // setup client endpoint for write failure (test only)
+        EndPoint endp = clientSocket.getEndPoint();
+        endp.shutdownOutput();
+
+        // client enqueue close frame
+        // client write failure
+        final String origCloseReason = "Normal Close";
+        clientSocket.getSession().close(StatusCode.NORMAL,origCloseReason);
+
+        clientSocket.assertReceivedError(EofException.class,null);
+
+        // client triggers close event on client ws-endpoint
+        // assert - close code==1006 (abnormal)
+        // assert - close reason message contains (write failure)
+        clientSocket.assertReceivedCloseEvent(timeout,is(StatusCode.ABNORMAL),containsString("EOF"));
+    }
+}
diff --git a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/ClientConnectTest.java b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/ClientConnectTest.java
new file mode 100644
index 0000000..3801c03
--- /dev/null
+++ b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/ClientConnectTest.java
@@ -0,0 +1,398 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.client;
+
+import static org.hamcrest.Matchers.*;
+
+import java.io.IOException;
+import java.net.ConnectException;
+import java.net.SocketTimeoutException;
+import java.net.URI;
+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 org.eclipse.jetty.toolchain.test.OS;
+import org.eclipse.jetty.toolchain.test.TestTracker;
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.UpgradeException;
+import org.eclipse.jetty.websocket.common.AcceptHash;
+import org.eclipse.jetty.websocket.common.test.BlockheadServer;
+import org.eclipse.jetty.websocket.common.test.BlockheadServer.ServerConnection;
+import org.eclipse.jetty.websocket.common.test.LeakTrackingBufferPoolRule;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+/**
+ * Various connect condition testing
+ */
+public class ClientConnectTest
+{
+    @Rule
+    public TestTracker tt = new TestTracker();
+
+    @Rule
+    public LeakTrackingBufferPoolRule bufferPool = new LeakTrackingBufferPoolRule("Test");
+
+    private final int timeout = 500;
+    private BlockheadServer server;
+    private WebSocketClient client;
+
+    @SuppressWarnings("unchecked")
+    private <E extends Throwable> E assertExpectedError(ExecutionException e, JettyTrackingSocket wsocket, Class<E> errorClass) throws IOException
+    {
+        // Validate thrown cause
+        Throwable cause = e.getCause();
+        if(!errorClass.isInstance(cause)) 
+        {
+                cause.printStackTrace(System.err);
+                Assert.assertThat("ExecutionException.cause",cause,instanceOf(errorClass));
+        }
+
+        // Validate websocket captured cause
+        Assert.assertThat("Error Queue Length",wsocket.errorQueue.size(),greaterThanOrEqualTo(1));
+        Throwable capcause = wsocket.errorQueue.poll();
+        Assert.assertThat("Error Queue[0]",capcause,notNullValue());
+        Assert.assertThat("Error Queue[0]",capcause,instanceOf(errorClass));
+
+        // Validate that websocket didn't see an open event
+        wsocket.assertNotOpened();
+
+        // Return the captured cause
+        return (E)capcause;
+    }
+
+    @Before
+    public void startClient() throws Exception
+    {
+        client = new WebSocketClient(bufferPool);
+        client.setConnectTimeout(timeout);
+        client.start();
+    }
+
+    @Before
+    public void startServer() throws Exception
+    {
+        server = new BlockheadServer();
+        server.start();
+    }
+
+    @After
+    public void stopClient() throws Exception
+    {
+        client.stop();
+    }
+
+    @After
+    public void stopServer() throws Exception
+    {
+        server.stop();
+    }
+
+    @Test
+    public void testBadHandshake() throws Exception
+    {
+        JettyTrackingSocket wsocket = new JettyTrackingSocket();
+
+        URI wsUri = server.getWsUri();
+        Future<Session> future = client.connect(wsocket,wsUri);
+
+        ServerConnection connection = server.accept();
+        connection.readRequest();
+        // no upgrade, just fail with a 404 error
+        connection.respond("HTTP/1.1 404 NOT FOUND\r\n\r\n");
+
+        // The attempt to get upgrade response future should throw error
+        try
+        {
+            future.get(500,TimeUnit.MILLISECONDS);
+            Assert.fail("Expected ExecutionException -> UpgradeException");
+        }
+        catch (ExecutionException e)
+        {
+            // Expected Path
+            UpgradeException ue = assertExpectedError(e,wsocket,UpgradeException.class);
+            Assert.assertThat("UpgradeException.requestURI",ue.getRequestURI(),notNullValue());
+            Assert.assertThat("UpgradeException.requestURI",ue.getRequestURI().toASCIIString(),is(wsUri.toASCIIString()));
+            Assert.assertThat("UpgradeException.responseStatusCode",ue.getResponseStatusCode(),is(404));
+        }
+    }
+
+    @Test
+    public void testBadHandshake_GetOK() throws Exception
+    {
+        JettyTrackingSocket wsocket = new JettyTrackingSocket();
+
+        URI wsUri = server.getWsUri();
+        Future<Session> future = client.connect(wsocket,wsUri);
+
+        ServerConnection connection = server.accept();
+        connection.readRequest();
+        // Send OK to GET but not upgrade
+        connection.respond("HTTP/1.1 200 OK\r\n\r\n");
+
+        // The attempt to get upgrade response future should throw error
+        try
+        {
+            future.get(500,TimeUnit.MILLISECONDS);
+            Assert.fail("Expected ExecutionException -> UpgradeException");
+        }
+        catch (ExecutionException e)
+        {
+            // Expected Path
+            UpgradeException ue = assertExpectedError(e,wsocket,UpgradeException.class);
+            Assert.assertThat("UpgradeException.requestURI",ue.getRequestURI(),notNullValue());
+            Assert.assertThat("UpgradeException.requestURI",ue.getRequestURI().toASCIIString(),is(wsUri.toASCIIString()));
+            Assert.assertThat("UpgradeException.responseStatusCode",ue.getResponseStatusCode(),is(200));
+        }
+    }
+
+    @Test
+    public void testBadHandshake_GetOK_WithSecWebSocketAccept() throws Exception
+    {
+        JettyTrackingSocket wsocket = new JettyTrackingSocket();
+
+        URI wsUri = server.getWsUri();
+        Future<Session> future = client.connect(wsocket,wsUri);
+
+        ServerConnection connection = server.accept();
+        List<String> requestLines = connection.readRequestLines();
+        String key = connection.parseWebSocketKey(requestLines);
+
+        // Send OK to GET but not upgrade
+        StringBuilder resp = new StringBuilder();
+        resp.append("HTTP/1.1 200 OK\r\n"); // intentionally 200 (not 101)
+        // Include a value accept key
+        resp.append("Sec-WebSocket-Accept: ").append(AcceptHash.hashKey(key)).append("\r\n");
+        resp.append("\r\n");
+        connection.respond(resp.toString());
+
+        // The attempt to get upgrade response future should throw error
+        try
+        {
+            future.get(500,TimeUnit.MILLISECONDS);
+            Assert.fail("Expected ExecutionException -> UpgradeException");
+        }
+        catch (ExecutionException e)
+        {
+            // Expected Path
+            UpgradeException ue = assertExpectedError(e,wsocket,UpgradeException.class);
+            Assert.assertThat("UpgradeException.requestURI",ue.getRequestURI(),notNullValue());
+            Assert.assertThat("UpgradeException.requestURI",ue.getRequestURI().toASCIIString(),is(wsUri.toASCIIString()));
+            Assert.assertThat("UpgradeException.responseStatusCode",ue.getResponseStatusCode(),is(200));
+        }
+    }
+
+    @Test
+    public void testBadHandshake_SwitchingProtocols_InvalidConnectionHeader() throws Exception
+    {
+        JettyTrackingSocket wsocket = new JettyTrackingSocket();
+
+        URI wsUri = server.getWsUri();
+        Future<Session> future = client.connect(wsocket,wsUri);
+
+        ServerConnection connection = server.accept();
+        List<String> requestLines = connection.readRequestLines();
+        String key = connection.parseWebSocketKey(requestLines);
+
+        // Send Switching Protocols 101, but invalid 'Connection' header
+        StringBuilder resp = new StringBuilder();
+        resp.append("HTTP/1.1 101 Switching Protocols\r\n");
+        resp.append("Sec-WebSocket-Accept: ").append(AcceptHash.hashKey(key)).append("\r\n");
+        resp.append("Connection: close\r\n");
+        resp.append("\r\n");
+        connection.respond(resp.toString());
+
+        // The attempt to get upgrade response future should throw error
+        try
+        {
+            future.get(500,TimeUnit.MILLISECONDS);
+            Assert.fail("Expected ExecutionException -> UpgradeException");
+        }
+        catch (ExecutionException e)
+        {
+            // Expected Path
+            UpgradeException ue = assertExpectedError(e,wsocket,UpgradeException.class);
+            Assert.assertThat("UpgradeException.requestURI",ue.getRequestURI(),notNullValue());
+            Assert.assertThat("UpgradeException.requestURI",ue.getRequestURI().toASCIIString(),is(wsUri.toASCIIString()));
+            Assert.assertThat("UpgradeException.responseStatusCode",ue.getResponseStatusCode(),is(101));
+        }
+    }
+
+    @Test
+    public void testBadHandshake_SwitchingProtocols_NoConnectionHeader() throws Exception
+    {
+        JettyTrackingSocket wsocket = new JettyTrackingSocket();
+
+        URI wsUri = server.getWsUri();
+        Future<Session> future = client.connect(wsocket,wsUri);
+
+        ServerConnection connection = server.accept();
+        List<String> requestLines = connection.readRequestLines();
+        String key = connection.parseWebSocketKey(requestLines);
+
+        // Send Switching Protocols 101, but no 'Connection' header
+        StringBuilder resp = new StringBuilder();
+        resp.append("HTTP/1.1 101 Switching Protocols\r\n");
+        resp.append("Sec-WebSocket-Accept: ").append(AcceptHash.hashKey(key)).append("\r\n");
+        // Intentionally leave out Connection header
+        resp.append("\r\n");
+        connection.respond(resp.toString());
+
+        // The attempt to get upgrade response future should throw error
+        try
+        {
+            future.get(500,TimeUnit.MILLISECONDS);
+            Assert.fail("Expected ExecutionException -> UpgradeException");
+        }
+        catch (ExecutionException e)
+        {
+            // Expected Path
+            UpgradeException ue = assertExpectedError(e,wsocket,UpgradeException.class);
+            Assert.assertThat("UpgradeException.requestURI",ue.getRequestURI(),notNullValue());
+            Assert.assertThat("UpgradeException.requestURI",ue.getRequestURI().toASCIIString(),is(wsUri.toASCIIString()));
+            Assert.assertThat("UpgradeException.responseStatusCode",ue.getResponseStatusCode(),is(101));
+        }
+    }
+
+    @Test
+    public void testBadUpgrade() throws Exception
+    {
+        JettyTrackingSocket wsocket = new JettyTrackingSocket();
+
+        URI wsUri = server.getWsUri();
+        Future<Session> future = client.connect(wsocket,wsUri);
+
+        ServerConnection connection = server.accept();
+        connection.readRequest();
+        // Upgrade badly
+        connection.respond("HTTP/1.1 101 Upgrade\r\n" + "Sec-WebSocket-Accept: rubbish\r\n" + "\r\n");
+
+        // The attempt to get upgrade response future should throw error
+        try
+        {
+            future.get(500,TimeUnit.MILLISECONDS);
+            Assert.fail("Expected ExecutionException -> UpgradeException");
+        }
+        catch (ExecutionException e)
+        {
+            // Expected Path
+            UpgradeException ue = assertExpectedError(e,wsocket,UpgradeException.class);
+            Assert.assertThat("UpgradeException.requestURI",ue.getRequestURI(),notNullValue());
+            Assert.assertThat("UpgradeException.requestURI",ue.getRequestURI().toASCIIString(),is(wsUri.toASCIIString()));
+            Assert.assertThat("UpgradeException.responseStatusCode",ue.getResponseStatusCode(),is(101));
+        }
+    }
+
+    @Test
+    public void testConnectionNotAccepted() throws Exception
+    {
+        JettyTrackingSocket wsocket = new JettyTrackingSocket();
+
+        URI wsUri = server.getWsUri();
+        Future<Session> future = client.connect(wsocket,wsUri);
+
+        // Intentionally not accept incoming socket.
+        // server.accept();
+
+        try
+        {
+            future.get(500,TimeUnit.MILLISECONDS);
+            Assert.fail("Should have Timed Out");
+        }
+        catch (ExecutionException e)
+        {
+            assertExpectedError(e,wsocket,UpgradeException.class);
+            // Possible Passing Path (active session wait timeout)
+            wsocket.assertNotOpened();
+        }
+        catch (TimeoutException e)
+        {
+            // Possible Passing Path (concurrency timeout)
+            wsocket.assertNotOpened();
+        }
+    }
+
+    @Test
+    public void testConnectionRefused() throws Exception
+    {
+        JettyTrackingSocket wsocket = new JettyTrackingSocket();
+
+        // Intentionally bad port with nothing listening on it
+        URI wsUri = new URI("ws://127.0.0.1:1");
+
+        try
+        {
+            Future<Session> future = client.connect(wsocket,wsUri);
+
+            // The attempt to get upgrade response future should throw error
+            future.get(1000,TimeUnit.MILLISECONDS);
+            Assert.fail("Expected ExecutionException -> ConnectException");
+        }
+        catch (ConnectException e)
+        {
+            Throwable t = wsocket.errorQueue.remove();
+            Assert.assertThat("Error Queue[0]",t,instanceOf(ConnectException.class));
+            wsocket.assertNotOpened();
+        }
+        catch (ExecutionException e)
+        {
+                if(OS.IS_WINDOWS) 
+                {
+                        // On windows, this is a SocketTimeoutException
+                        assertExpectedError(e, wsocket, SocketTimeoutException.class);
+                } else
+                {
+                    // Expected path - java.net.ConnectException
+                    assertExpectedError(e,wsocket,ConnectException.class);
+                }
+        }
+    }
+
+    @Test(expected = TimeoutException.class)
+    public void testConnectionTimeout_Concurrent() throws Exception
+    {
+        JettyTrackingSocket wsocket = new JettyTrackingSocket();
+
+        URI wsUri = server.getWsUri();
+        Future<Session> future = client.connect(wsocket,wsUri);
+
+        ServerConnection ssocket = server.accept();
+        Assert.assertNotNull(ssocket);
+        // Intentionally don't upgrade
+        // ssocket.upgrade();
+
+        // The attempt to get upgrade response future should throw error
+        try
+        {
+            future.get(500,TimeUnit.MILLISECONDS);
+            Assert.fail("Expected ExecutionException -> TimeoutException");
+        }
+        catch (ExecutionException e)
+        {
+            // Expected path - java.net.ConnectException ?
+            assertExpectedError(e,wsocket,ConnectException.class);
+        }
+    }
+}
diff --git a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/ClientWriteThread.java b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/ClientWriteThread.java
new file mode 100644
index 0000000..a0bc611
--- /dev/null
+++ b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/ClientWriteThread.java
@@ -0,0 +1,107 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.client;
+
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.BatchMode;
+import org.eclipse.jetty.websocket.api.RemoteEndpoint;
+import org.eclipse.jetty.websocket.api.Session;
+
+public class ClientWriteThread extends Thread
+{
+    private static final Logger LOG = Log.getLogger(ClientWriteThread.class);
+    private final Session session;
+    private int slowness = -1;
+    private int messageCount = 100;
+    private String message = "Hello";
+
+    public ClientWriteThread(Session session)
+    {
+        this.session = session;
+    }
+
+    public String getMessage()
+    {
+        return message;
+    }
+
+    public int getMessageCount()
+    {
+        return messageCount;
+    }
+
+    public int getSlowness()
+    {
+        return slowness;
+    }
+
+    @Override
+    public void run()
+    {
+        final AtomicInteger m = new AtomicInteger();
+
+        try
+        {
+            LOG.debug("Writing {} messages to connection {}",messageCount);
+            LOG.debug("Artificial Slowness {} ms",slowness);
+            Future<Void> lastMessage = null;
+            RemoteEndpoint remote = session.getRemote();
+            while (m.get() < messageCount)
+            {
+                lastMessage = remote.sendStringByFuture(message + "/" + m.get() + "/");
+
+                m.incrementAndGet();
+
+                if (slowness > 0)
+                {
+                    TimeUnit.MILLISECONDS.sleep(slowness);
+                }
+            }
+            if (remote.getBatchMode() == BatchMode.ON)
+                remote.flush();
+            // block on write of last message
+            if (lastMessage != null)
+                lastMessage.get(2,TimeUnit.MINUTES); // block on write
+        }
+        catch (Exception e)
+        {
+            LOG.warn(e);
+        }
+    }
+
+    public void setMessage(String message)
+    {
+        this.message = message;
+    }
+
+    public void setMessageCount(int messageCount)
+    {
+        this.messageCount = messageCount;
+    }
+
+    public void setSlowness(int slowness)
+    {
+        this.slowness = slowness;
+    }
+}
diff --git a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/ConnectionManagerTest.java b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/ConnectionManagerTest.java
new file mode 100644
index 0000000..9069e81
--- /dev/null
+++ b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/ConnectionManagerTest.java
@@ -0,0 +1,82 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.client;
+
+import static org.hamcrest.Matchers.is;
+
+import java.net.InetSocketAddress;
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import org.eclipse.jetty.toolchain.test.TestTracker;
+import org.eclipse.jetty.websocket.client.io.ConnectionManager;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class ConnectionManagerTest
+{
+    @Rule
+    public TestTracker tt = new TestTracker();
+
+    private void assertToSocketAddress(String uriStr, String expectedHost, int expectedPort) throws URISyntaxException
+    {
+        URI uri = new URI(uriStr);
+
+        InetSocketAddress addr = ConnectionManager.toSocketAddress(uri);
+        Assert.assertThat("URI (" + uri + ").host",addr.getHostName(),is(expectedHost));
+        Assert.assertThat("URI (" + uri + ").port",addr.getPort(),is(expectedPort));
+    }
+
+    @Test
+    public void testToSocketAddress_AltWsPort() throws Exception
+    {
+        assertToSocketAddress("ws://localhost:8099","localhost",8099);
+    }
+
+    @Test
+    public void testToSocketAddress_AltWssPort() throws Exception
+    {
+        assertToSocketAddress("wss://localhost","localhost",443);
+    }
+
+    @Test
+    public void testToSocketAddress_DefaultWsPort() throws Exception
+    {
+        assertToSocketAddress("ws://localhost","localhost",80);
+    }
+
+    @Test
+    public void testToSocketAddress_DefaultWsPort_Path() throws Exception
+    {
+        assertToSocketAddress("ws://localhost/sockets/chat","localhost",80);
+    }
+
+    @Test
+    public void testToSocketAddress_DefaultWssPort() throws Exception
+    {
+        assertToSocketAddress("wss://localhost:9443","localhost",9443);
+    }
+
+    @Test
+    public void testToSocketAddress_DefaultWssPort_Path() throws Exception
+    {
+        assertToSocketAddress("wss://localhost/sockets/chat","localhost",443);
+    }
+}
diff --git a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/CookieTest.java b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/CookieTest.java
new file mode 100644
index 0000000..a68c245
--- /dev/null
+++ b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/CookieTest.java
@@ -0,0 +1,181 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.client;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+import java.net.CookieManager;
+import java.net.HttpCookie;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.toolchain.test.EventQueue;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.StatusCode;
+import org.eclipse.jetty.websocket.api.WebSocketAdapter;
+import org.eclipse.jetty.websocket.api.util.QuoteUtil;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
+import org.eclipse.jetty.websocket.common.test.BlockheadServer;
+import org.eclipse.jetty.websocket.common.test.BlockheadServer.ServerConnection;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class CookieTest
+{
+    private static final Logger LOG = Log.getLogger(CookieTest.class);
+
+    public static class CookieTrackingSocket extends WebSocketAdapter
+    {
+        public EventQueue<String> messageQueue = new EventQueue<>();
+        public EventQueue<Throwable> errorQueue = new EventQueue<>();
+
+        @Override
+        public void onWebSocketText(String message)
+        {
+            messageQueue.add(message);
+        }
+
+        @Override
+        public void onWebSocketError(Throwable cause)
+        {
+            errorQueue.add(cause);
+        }
+    }
+
+    private WebSocketClient client;
+    private BlockheadServer server;
+
+    @Before
+    public void startClient() throws Exception
+    {
+        client = new WebSocketClient();
+        client.start();
+    }
+
+    @Before
+    public void startServer() throws Exception
+    {
+        server = new BlockheadServer();
+        server.start();
+    }
+
+    @After
+    public void stopClient() throws Exception
+    {
+        if (client.isRunning())
+        {
+            client.stop();
+        }
+    }
+
+    @After
+    public void stopServer() throws Exception
+    {
+        server.stop();
+    }
+
+    @Test
+    public void testViaCookieManager() throws Exception
+    {
+        // Setup client
+        CookieManager cookieMgr = new CookieManager();
+        client.setCookieStore(cookieMgr.getCookieStore());
+        HttpCookie cookie = new HttpCookie("hello","world");
+        cookie.setPath("/");
+        cookie.setVersion(0);
+        cookie.setMaxAge(100000);
+        cookieMgr.getCookieStore().add(server.getWsUri(),cookie);
+        
+        cookie = new HttpCookie("foo","bar is the word");
+        cookie.setPath("/");
+        cookie.setMaxAge(100000);
+        cookieMgr.getCookieStore().add(server.getWsUri(),cookie);
+
+        // Client connects
+        CookieTrackingSocket clientSocket = new CookieTrackingSocket();
+        Future<Session> clientConnectFuture = client.connect(clientSocket,server.getWsUri());
+
+        // Server accepts connect
+        ServerConnection serverConn = server.accept();
+
+        // client confirms upgrade and receipt of frame
+        String serverCookies = confirmClientUpgradeAndCookies(clientSocket,clientConnectFuture,serverConn);
+
+        assertThat("Cookies seen at server side",serverCookies,containsString("hello=world"));
+        assertThat("Cookies seen at server side",serverCookies,containsString("foo=\"bar is the word\""));
+    }
+    
+    @Test
+    public void testViaServletUpgradeRequest() throws Exception
+    {
+        // Setup client
+        HttpCookie cookie = new HttpCookie("hello","world");
+        cookie.setPath("/");
+        cookie.setMaxAge(100000);
+        
+        ClientUpgradeRequest request = new ClientUpgradeRequest();
+        request.setCookies(Collections.singletonList(cookie));
+
+        // Client connects
+        CookieTrackingSocket clientSocket = new CookieTrackingSocket();
+        Future<Session> clientConnectFuture = client.connect(clientSocket,server.getWsUri(),request);
+
+        // Server accepts connect
+        ServerConnection serverConn = server.accept();
+
+        // client confirms upgrade and receipt of frame
+        String serverCookies = confirmClientUpgradeAndCookies(clientSocket,clientConnectFuture,serverConn);
+
+        Assert.assertThat("Cookies seen at server side",serverCookies,containsString("hello=\"world\""));
+    }
+
+    private String confirmClientUpgradeAndCookies(CookieTrackingSocket clientSocket, Future<Session> clientConnectFuture, ServerConnection serverConn)
+            throws Exception
+    {
+        // Server upgrades
+        List<String> upgradeRequestLines = serverConn.upgrade();
+        List<String> upgradeRequestCookies = serverConn.regexFind(upgradeRequestLines,"^Cookie: (.*)$");
+
+        // Server responds with cookies it knows about
+        TextFrame serverCookieFrame = new TextFrame();
+        serverCookieFrame.setFin(true);
+        serverCookieFrame.setPayload(QuoteUtil.join(upgradeRequestCookies,","));
+        serverConn.write(serverCookieFrame);
+
+        // Server closes connection
+        serverConn.close(StatusCode.NORMAL);
+
+        // Confirm client connect on future
+        clientConnectFuture.get(500,TimeUnit.MILLISECONDS);
+
+        // Wait for client receipt of cookie frame via client websocket
+        clientSocket.messageQueue.awaitEventCount(1,2,TimeUnit.SECONDS);
+
+        String cookies = clientSocket.messageQueue.poll();
+        LOG.debug("Cookies seen at server: {}",cookies);
+        return cookies;
+    }
+}
diff --git a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/JettyTrackingSocket.java b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/JettyTrackingSocket.java
new file mode 100644
index 0000000..cbb5e59
--- /dev/null
+++ b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/JettyTrackingSocket.java
@@ -0,0 +1,177 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.client;
+
+import static org.hamcrest.Matchers.greaterThanOrEqualTo;
+import static org.hamcrest.Matchers.is;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Exchanger;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.eclipse.jetty.toolchain.test.EventQueue;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.WebSocketAdapter;
+import org.junit.Assert;
+
+/**
+ * Testing Socket used on client side WebSocket testing.
+ */
+public class JettyTrackingSocket extends WebSocketAdapter
+{
+    private static final Logger LOG = Log.getLogger(JettyTrackingSocket.class);
+
+    public int closeCode = -1;
+    public Exchanger<String> messageExchanger;
+    public StringBuilder closeMessage = new StringBuilder();
+    public CountDownLatch openLatch = new CountDownLatch(1);
+    public CountDownLatch closeLatch = new CountDownLatch(1);
+    public CountDownLatch dataLatch = new CountDownLatch(1);
+    public EventQueue<String> messageQueue = new EventQueue<>();
+    public EventQueue<Throwable> errorQueue = new EventQueue<>();
+
+    public void assertClose(int expectedStatusCode, String expectedReason) throws InterruptedException
+    {
+        assertCloseCode(expectedStatusCode);
+        assertCloseReason(expectedReason);
+    }
+
+    public void assertCloseCode(int expectedCode) throws InterruptedException
+    {
+        Assert.assertThat("Was Closed",closeLatch.await(50,TimeUnit.MILLISECONDS),is(true));
+        Assert.assertThat("Close Code / Received [" + closeMessage + "]",closeCode,is(expectedCode));
+    }
+
+    private void assertCloseReason(String expectedReason)
+    {
+        Assert.assertThat("Close Reason",closeMessage.toString(),is(expectedReason));
+    }
+
+    public void assertIsOpen() throws InterruptedException
+    {
+        assertWasOpened();
+        assertNotClosed();
+    }
+
+    public void assertMessage(String expected)
+    {
+        String actual = messageQueue.poll();
+        Assert.assertEquals("Message",expected,actual);
+    }
+
+    public void assertNotClosed()
+    {
+        Assert.assertThat("Closed Latch",closeLatch.getCount(),greaterThanOrEqualTo(1L));
+    }
+
+    public void assertNotOpened()
+    {
+        Assert.assertThat("Open Latch",openLatch.getCount(),greaterThanOrEqualTo(1L));
+    }
+
+    public void assertWasOpened() throws InterruptedException
+    {
+        Assert.assertThat("Was Opened",openLatch.await(500,TimeUnit.MILLISECONDS),is(true));
+    }
+
+    public void awaitMessage(int expectedMessageCount, TimeUnit timeoutUnit, int timeoutDuration) throws TimeoutException, InterruptedException
+    {
+        messageQueue.awaitEventCount(expectedMessageCount,timeoutDuration,timeoutUnit);
+    }
+
+    public void clear()
+    {
+        messageQueue.clear();
+    }
+
+    @Override
+    public void onWebSocketBinary(byte[] payload, int offset, int len)
+    {
+        LOG.debug("onWebSocketBinary()");
+        dataLatch.countDown();
+    }
+
+    @Override
+    public void onWebSocketClose(int statusCode, String reason)
+    {
+        LOG.debug("onWebSocketClose({},{})",statusCode,reason);
+        super.onWebSocketClose(statusCode,reason);
+        closeCode = statusCode;
+        closeMessage.append(reason);
+        closeLatch.countDown();
+    }
+
+    @Override
+    public void onWebSocketConnect(Session session)
+    {
+        super.onWebSocketConnect(session);
+        openLatch.countDown();
+    }
+
+    @Override
+    public void onWebSocketError(Throwable cause)
+    {
+        LOG.debug("onWebSocketError",cause);
+        Assert.assertThat("Error capture",errorQueue.offer(cause),is(true));
+    }
+
+    @Override
+    public void onWebSocketText(String message)
+    {
+        LOG.debug("onWebSocketText({})",message);
+        messageQueue.offer(message);
+        dataLatch.countDown();
+
+        if (messageExchanger != null)
+        {
+            try
+            {
+                messageExchanger.exchange(message);
+            }
+            catch (InterruptedException e)
+            {
+                LOG.debug(e);
+            }
+        }
+    }
+
+    public void waitForClose(int timeoutDuration, TimeUnit timeoutUnit) throws InterruptedException
+    {
+        Assert.assertThat("Client Socket Closed",closeLatch.await(timeoutDuration,timeoutUnit),is(true));
+    }
+
+    public void waitForConnected(int timeoutDuration, TimeUnit timeoutUnit) throws InterruptedException
+    {
+        Assert.assertThat("Client Socket Connected",openLatch.await(timeoutDuration,timeoutUnit),is(true));
+    }
+
+    public void waitForMessage(int timeoutDuration, TimeUnit timeoutUnit) throws InterruptedException
+    {
+        LOG.debug("Waiting for message");
+        Assert.assertThat("Message Received",dataLatch.await(timeoutDuration,timeoutUnit),is(true));
+    }
+
+    public void close()
+    {
+        getSession().close();
+    }
+}
diff --git a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/MaxMessageSocket.java b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/MaxMessageSocket.java
new file mode 100644
index 0000000..7e03f1f
--- /dev/null
+++ b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/MaxMessageSocket.java
@@ -0,0 +1,102 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.client;
+
+import static org.hamcrest.Matchers.is;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.toolchain.test.EventQueue;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
+import org.eclipse.jetty.websocket.api.annotations.WebSocket;
+import org.junit.Assert;
+
+ at WebSocket(maxTextMessageSize = 100*1024)
+public class MaxMessageSocket
+{
+    private static final Logger LOG = Log.getLogger(MaxMessageSocket.class);
+    private Session session;
+    public CountDownLatch openLatch = new CountDownLatch(1);
+    public CountDownLatch closeLatch = new CountDownLatch(1);
+    public CountDownLatch dataLatch = new CountDownLatch(1);
+    public EventQueue<String> messageQueue = new EventQueue<>();
+    public EventQueue<Throwable> errorQueue = new EventQueue<>();
+    public int closeCode = -1;
+    public StringBuilder closeMessage = new StringBuilder();
+
+    @OnWebSocketConnect
+    public void onConnect(Session session)
+    {
+        this.session = session;
+        openLatch.countDown();
+    }
+    
+    @OnWebSocketClose
+    public void onClose(int statusCode, String reason)
+    {
+        LOG.debug("onWebSocketClose({},{})",statusCode,reason);
+        closeCode = statusCode;
+        closeMessage.append(reason);
+        closeLatch.countDown();
+    }
+
+    @OnWebSocketMessage
+    public void onMessage(String message)
+    {
+        LOG.debug("onWebSocketText({})",message);
+        messageQueue.offer(message);
+        dataLatch.countDown();
+    }
+    
+    @OnWebSocketError
+    public void onError(Throwable cause)
+    {
+        LOG.debug("onWebSocketError",cause);
+        Assert.assertThat("Error capture",errorQueue.offer(cause),is(true));
+    }
+
+    public Session getSession()
+    {
+        return this.session;
+    }
+
+    public void awaitConnect(int duration, TimeUnit unit) throws InterruptedException
+    {
+        Assert.assertThat("Client Socket connected",openLatch.await(duration,unit),is(true));
+    }
+    
+    public void waitForMessage(int timeoutDuration, TimeUnit timeoutUnit) throws InterruptedException
+    {
+        LOG.debug("Waiting for message");
+        Assert.assertThat("Message Received",dataLatch.await(timeoutDuration,timeoutUnit),is(true));
+    }
+    
+    public void assertMessage(String expected)
+    {
+        String actual = messageQueue.poll();
+        Assert.assertEquals("Message",expected,actual);
+    }
+}
diff --git a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/ServerReadThread.java b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/ServerReadThread.java
new file mode 100644
index 0000000..6bfcd39
--- /dev/null
+++ b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/ServerReadThread.java
@@ -0,0 +1,132 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.client;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.Queue;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.common.CloseInfo;
+import org.eclipse.jetty.websocket.common.OpCode;
+import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.test.BlockheadServer.ServerConnection;
+import org.junit.Assert;
+
+import static org.hamcrest.Matchers.is;
+
+public class ServerReadThread extends Thread
+{
+    private static final int BUFFER_SIZE = 8192;
+    private static final Logger LOG = Log.getLogger(ServerReadThread.class);
+    private final ServerConnection conn;
+    private boolean active = true;
+    private int slowness = -1; // disabled is default
+    private final AtomicInteger frameCount = new AtomicInteger();
+    private final CountDownLatch expectedMessageCount;
+
+    public ServerReadThread(ServerConnection conn, int expectedMessages)
+    {
+        this.conn = conn;
+        this.expectedMessageCount = new CountDownLatch(expectedMessages);
+    }
+
+    public void cancel()
+    {
+        active = false;
+    }
+
+    public int getFrameCount()
+    {
+        return frameCount.get();
+    }
+
+    public int getSlowness()
+    {
+        return slowness;
+    }
+
+    @Override
+    public void run()
+    {
+        ByteBufferPool bufferPool = conn.getBufferPool();
+        ByteBuffer buf = bufferPool.acquire(BUFFER_SIZE,false);
+        BufferUtil.clearToFill(buf);
+
+        try
+        {
+            while (active)
+            {
+                BufferUtil.clearToFill(buf);
+                int len = conn.read(buf);
+
+                if (len > 0)
+                {
+                    LOG.debug("Read {} bytes",len);
+                    BufferUtil.flipToFlush(buf,0);
+                    conn.getParser().parse(buf);
+                }
+
+                Queue<WebSocketFrame> frames = conn.getIncomingFrames().getFrames();
+                WebSocketFrame frame;
+                while ((frame = frames.poll()) != null)
+                {
+                    frameCount.incrementAndGet();
+                    if (frame.getOpCode() == OpCode.CLOSE)
+                    {
+                        active = false;
+                        // automatically response to close frame
+                        CloseInfo close = new CloseInfo(frame);
+                        conn.close(close.getStatusCode());
+                    }
+
+                    expectedMessageCount.countDown();
+                }
+                if (slowness > 0)
+                {
+                    TimeUnit.MILLISECONDS.sleep(getSlowness());
+                }
+            }
+        }
+        catch (IOException | InterruptedException e)
+        {
+            LOG.warn(e);
+        }
+        finally
+        {
+            bufferPool.release(buf);
+        }
+    }
+
+    public void setSlowness(int slowness)
+    {
+        this.slowness = slowness;
+    }
+
+    public void waitForExpectedMessageCount(int timeoutDuration, TimeUnit timeoutUnit) throws InterruptedException
+    {
+        Assert.assertThat("Expected Message Count attained",expectedMessageCount.await(timeoutDuration,timeoutUnit),is(true));
+    }
+}
diff --git a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/ServerWriteThread.java b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/ServerWriteThread.java
new file mode 100644
index 0000000..b9f02ee
--- /dev/null
+++ b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/ServerWriteThread.java
@@ -0,0 +1,97 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.client;
+
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
+import org.eclipse.jetty.websocket.common.test.BlockheadServer.ServerConnection;
+
+public class ServerWriteThread extends Thread
+{
+    private static final Logger LOG = Log.getLogger(ServerWriteThread.class);
+    private final ServerConnection conn;
+    private int slowness = -1;
+    private int messageCount = 100;
+    private String message = "Hello";
+
+    public ServerWriteThread(ServerConnection conn)
+    {
+        this.conn = conn;
+    }
+
+    public String getMessage()
+    {
+        return message;
+    }
+
+    public int getMessageCount()
+    {
+        return messageCount;
+    }
+
+    public int getSlowness()
+    {
+        return slowness;
+    }
+
+    @Override
+    public void run()
+    {
+        final AtomicInteger m = new AtomicInteger();
+
+        try
+        {
+            while (m.get() < messageCount)
+            {
+                conn.write(new TextFrame().setPayload(message));
+
+                m.incrementAndGet();
+
+                if (slowness > 0)
+                {
+                    TimeUnit.MILLISECONDS.sleep(slowness);
+                }
+            }
+        }
+        catch (InterruptedException | IOException e)
+        {
+            LOG.warn(e);
+        }
+    }
+
+    public void setMessage(String message)
+    {
+        this.message = message;
+    }
+
+    public void setMessageCount(int messageCount)
+    {
+        this.messageCount = messageCount;
+    }
+
+    public void setSlowness(int slowness)
+    {
+        this.slowness = slowness;
+    }
+}
diff --git a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/SessionTest.java b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/SessionTest.java
new file mode 100644
index 0000000..011dc0b
--- /dev/null
+++ b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/SessionTest.java
@@ -0,0 +1,111 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.client;
+
+import java.net.URI;
+import java.util.Set;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.websocket.api.BatchMode;
+import org.eclipse.jetty.websocket.api.RemoteEndpoint;
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.common.WebSocketSession;
+import org.eclipse.jetty.websocket.common.test.BlockheadServer;
+import org.eclipse.jetty.websocket.common.test.BlockheadServer.ServerConnection;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+
+public class SessionTest
+{
+    private BlockheadServer server;
+
+    @Before
+    public void startServer() throws Exception
+    {
+        server = new BlockheadServer();
+        server.start();
+    }
+
+    @After
+    public void stopServer() throws Exception
+    {
+        server.stop();
+    }
+    
+    @Test
+    public void testBasicEcho_FromClient() throws Exception
+    {
+        WebSocketClient client = new WebSocketClient();
+        client.start();
+        try
+        {
+            JettyTrackingSocket cliSock = new JettyTrackingSocket();
+
+            client.getPolicy().setIdleTimeout(10000);
+
+            URI wsUri = server.getWsUri();
+            ClientUpgradeRequest request = new ClientUpgradeRequest();
+            request.setSubProtocols("echo");
+            Future<Session> future = client.connect(cliSock,wsUri,request);
+
+            final ServerConnection srvSock = server.accept();
+            srvSock.upgrade();
+
+            Session sess = future.get(500,TimeUnit.MILLISECONDS);
+            Assert.assertThat("Session",sess,notNullValue());
+            Assert.assertThat("Session.open",sess.isOpen(),is(true));
+            Assert.assertThat("Session.upgradeRequest",sess.getUpgradeRequest(),notNullValue());
+            Assert.assertThat("Session.upgradeResponse",sess.getUpgradeResponse(),notNullValue());
+
+            cliSock.assertWasOpened();
+            cliSock.assertNotClosed();
+
+            Assert.assertThat("client.connectionManager.sessions.size",client.getConnectionManager().getSessions().size(),is(1));
+
+            RemoteEndpoint remote = cliSock.getSession().getRemote();
+            remote.sendStringByFuture("Hello World!");
+            if (remote.getBatchMode() == BatchMode.ON)
+                remote.flush();
+            srvSock.echoMessage(1,500,TimeUnit.MILLISECONDS);
+            // wait for response from server
+            cliSock.waitForMessage(500,TimeUnit.MILLISECONDS);
+            
+            Set<WebSocketSession> open = client.getOpenSessions();
+            Assert.assertThat("Open Sessions.size", open.size(), is(1));
+
+            cliSock.assertMessage("Hello World!");
+            cliSock.close();
+            srvSock.close();
+            
+            cliSock.waitForClose(500,TimeUnit.MILLISECONDS);
+            open = client.getOpenSessions();
+            Assert.assertThat("Open Sessions.size", open.size(), is(0));
+        }
+        finally
+        {
+            client.stop();
+        }
+    }
+}
diff --git a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/SlowClientTest.java b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/SlowClientTest.java
new file mode 100644
index 0000000..5647597
--- /dev/null
+++ b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/SlowClientTest.java
@@ -0,0 +1,119 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.client;
+
+import java.net.URI;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.toolchain.test.TestTracker;
+import org.eclipse.jetty.toolchain.test.annotation.Slow;
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.StatusCode;
+import org.eclipse.jetty.websocket.common.test.BlockheadServer;
+import org.eclipse.jetty.websocket.common.test.BlockheadServer.ServerConnection;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import static org.hamcrest.Matchers.is;
+
+public class SlowClientTest
+{
+    @Rule
+    public TestTracker tt = new TestTracker();
+
+    private BlockheadServer server;
+    private WebSocketClient client;
+
+    @Before
+    public void startClient() throws Exception
+    {
+        client = new WebSocketClient();
+        client.getPolicy().setIdleTimeout(60000);
+        client.start();
+    }
+
+    @Before
+    public void startServer() throws Exception
+    {
+        server = new BlockheadServer();
+        server.start();
+    }
+
+    @After
+    public void stopClient() throws Exception
+    {
+        client.stop();
+    }
+
+    @After
+    public void stopServer() throws Exception
+    {
+        server.stop();
+    }
+
+    @Test
+    @Slow
+    public void testClientSlowToSend() throws Exception
+    {
+        JettyTrackingSocket tsocket = new JettyTrackingSocket();
+        client.getPolicy().setIdleTimeout(60000);
+
+        URI wsUri = server.getWsUri();
+        Future<Session> future = client.connect(tsocket, wsUri);
+
+        ServerConnection sconnection = server.accept();
+        sconnection.setSoTimeout(60000);
+        sconnection.upgrade();
+
+        // Confirm connected
+        future.get(500, TimeUnit.MILLISECONDS);
+        tsocket.waitForConnected(500, TimeUnit.MILLISECONDS);
+
+        int messageCount = 10;
+
+        // Setup server read thread
+        ServerReadThread reader = new ServerReadThread(sconnection, messageCount);
+        reader.start();
+
+        // Have client write slowly.
+        ClientWriteThread writer = new ClientWriteThread(tsocket.getSession());
+        writer.setMessageCount(messageCount);
+        writer.setMessage("Hello");
+        writer.setSlowness(10);
+        writer.start();
+        writer.join();
+
+        reader.waitForExpectedMessageCount(1, TimeUnit.MINUTES);
+
+        // Verify receive
+        Assert.assertThat("Frame Receive Count", reader.getFrameCount(), is(messageCount));
+
+        // Close
+        tsocket.getSession().close(StatusCode.NORMAL, "Done");
+
+        Assert.assertTrue("Client Socket Closed", tsocket.closeLatch.await(3, TimeUnit.MINUTES));
+        tsocket.assertCloseCode(StatusCode.NORMAL);
+
+        reader.cancel(); // stop reading
+    }
+}
diff --git a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/SlowServerTest.java b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/SlowServerTest.java
new file mode 100644
index 0000000..c6b426a
--- /dev/null
+++ b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/SlowServerTest.java
@@ -0,0 +1,160 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.client;
+
+import java.net.URI;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.toolchain.test.TestTracker;
+import org.eclipse.jetty.toolchain.test.annotation.Slow;
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.StatusCode;
+import org.eclipse.jetty.websocket.client.masks.ZeroMasker;
+import org.eclipse.jetty.websocket.common.test.BlockheadServer;
+import org.eclipse.jetty.websocket.common.test.BlockheadServer.ServerConnection;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import static org.hamcrest.Matchers.is;
+
+public class SlowServerTest
+{
+    @Rule
+    public TestTracker tt = new TestTracker();
+
+    private BlockheadServer server;
+    private WebSocketClient client;
+
+    @Before
+    public void startClient() throws Exception
+    {
+        client = new WebSocketClient();
+        client.setMaxIdleTimeout(60000);
+        client.start();
+    }
+
+    @Before
+    public void startServer() throws Exception
+    {
+        server = new BlockheadServer();
+        server.start();
+    }
+
+    @After
+    public void stopClient() throws Exception
+    {
+        client.stop();
+    }
+
+    @After
+    public void stopServer() throws Exception
+    {
+        server.stop();
+    }
+
+    @Test
+    @Slow
+    public void testServerSlowToRead() throws Exception
+    {
+        JettyTrackingSocket tsocket = new JettyTrackingSocket();
+        client.setMasker(new ZeroMasker());
+        client.setMaxIdleTimeout(60000);
+
+        URI wsUri = server.getWsUri();
+        Future<Session> future = client.connect(tsocket,wsUri);
+
+        ServerConnection sconnection = server.accept();
+        sconnection.setSoTimeout(60000);
+        sconnection.upgrade();
+
+        // Confirm connected
+        future.get(500,TimeUnit.MILLISECONDS);
+        tsocket.waitForConnected(500,TimeUnit.MILLISECONDS);
+
+        int messageCount = 10;
+
+        // Setup slow server read thread
+        ServerReadThread reader = new ServerReadThread(sconnection, messageCount);
+        reader.setSlowness(100); // slow it down
+        reader.start();
+
+        // Have client write as quickly as it can.
+        ClientWriteThread writer = new ClientWriteThread(tsocket.getSession());
+        writer.setMessageCount(messageCount);
+        writer.setMessage("Hello");
+        writer.setSlowness(-1); // disable slowness
+        writer.start();
+        writer.join();
+
+        // Verify receive
+        reader.waitForExpectedMessageCount(10,TimeUnit.SECONDS);
+        Assert.assertThat("Frame Receive Count",reader.getFrameCount(),is(messageCount));
+
+        // Close
+        tsocket.getSession().close(StatusCode.NORMAL,"Done");
+
+        Assert.assertTrue("Client Socket Closed",tsocket.closeLatch.await(10,TimeUnit.SECONDS));
+        tsocket.assertCloseCode(StatusCode.NORMAL);
+
+        reader.cancel(); // stop reading
+    }
+
+    @Test
+    @Slow
+    public void testServerSlowToSend() throws Exception
+    {
+        JettyTrackingSocket clientSocket = new JettyTrackingSocket();
+        client.setMasker(new ZeroMasker());
+        client.setMaxIdleTimeout(60000);
+
+        URI wsUri = server.getWsUri();
+        Future<Session> clientConnectFuture = client.connect(clientSocket,wsUri);
+
+        ServerConnection serverConn = server.accept();
+        serverConn.setSoTimeout(60000);
+        serverConn.upgrade();
+
+        // Confirm connected
+        clientConnectFuture.get(500,TimeUnit.MILLISECONDS);
+        clientSocket.waitForConnected(500,TimeUnit.MILLISECONDS);
+
+        // Have server write slowly.
+        int messageCount = 1000;
+
+        ServerWriteThread writer = new ServerWriteThread(serverConn);
+        writer.setMessageCount(messageCount);
+        writer.setMessage("Hello");
+        writer.setSlowness(10);
+        writer.start();
+        writer.join();
+
+        // Verify receive
+        Assert.assertThat("Message Receive Count",clientSocket.messageQueue.size(),is(messageCount));
+
+        // Close
+        serverConn.close(StatusCode.NORMAL);
+
+        Assert.assertTrue("Client Socket Closed",clientSocket.closeLatch.await(10,TimeUnit.SECONDS));
+        clientSocket.assertCloseCode(StatusCode.NORMAL);
+    }
+}
diff --git a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/TomcatServerQuirksTest.java b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/TomcatServerQuirksTest.java
new file mode 100644
index 0000000..a3e2ae9
--- /dev/null
+++ b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/TomcatServerQuirksTest.java
@@ -0,0 +1,128 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.client;
+
+import java.net.URI;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.WebSocketAdapter;
+import org.eclipse.jetty.websocket.common.test.BlockheadServer;
+import org.eclipse.jetty.websocket.common.test.BlockheadServer.ServerConnection;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class TomcatServerQuirksTest
+{
+    public static class LatchedSocket extends WebSocketAdapter
+    {
+        final CountDownLatch openLatch = new CountDownLatch(1);
+        final CountDownLatch dataLatch = new CountDownLatch(1);
+        final CountDownLatch closeLatch = new CountDownLatch(1);
+
+        @Override
+        public void onWebSocketClose(int statusCode, String reason)
+        {
+            closeLatch.countDown();
+        }
+
+        @Override
+        public void onWebSocketConnect(Session session)
+        {
+            openLatch.countDown();
+        }
+
+        @Override
+        public void onWebSocketText(String message)
+        {
+            dataLatch.countDown();
+        }
+    }
+
+    /**
+     * Test for when encountering a "Transfer-Encoding: chunked" on a Upgrade Response header.
+     * <ul>
+     * <li><a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=393075">Eclipse Jetty Bug #393075</a></li>
+     * <li><a href="https://issues.apache.org/bugzilla/show_bug.cgi?id=54067">Apache Tomcat Bug #54067</a></li>
+     * </ul>
+     * 
+     * @throws IOException
+     */
+    @Test
+    public void testTomcat7_0_32_WithTransferEncoding() throws Exception
+    {
+        BlockheadServer server = new BlockheadServer();
+        WebSocketClient client = new WebSocketClient();
+
+        try
+        {
+            final int bufferSize = 512;
+
+            server.start();
+
+            // Setup Client Factory
+            client.start();
+
+            // Create End User WebSocket Class
+            LatchedSocket websocket = new LatchedSocket();
+
+            // Open connection
+            URI wsURI = server.getWsUri();
+            client.connect(websocket,wsURI);
+
+            // Accept incoming connection
+            ServerConnection socket = server.accept();
+            socket.setSoTimeout(2000); // timeout
+
+            // Issue upgrade
+            // Add the extra problematic header that triggers bug found in jetty-io
+            socket.addResponseHeader("Transfer-Encoding","chunked");
+            socket.upgrade();
+
+            // Wait for proper upgrade
+            Assert.assertTrue("Timed out waiting for Client side WebSocket open event",websocket.openLatch.await(1,TimeUnit.SECONDS));
+
+            // Have server write frame.
+            byte payload[] = new byte[bufferSize / 2];
+            Arrays.fill(payload,(byte)'x');
+            ByteBuffer serverFrame = BufferUtil.allocate(bufferSize);
+            BufferUtil.flipToFill(serverFrame);
+            serverFrame.put((byte)(0x80 | 0x01)); // FIN + TEXT
+            serverFrame.put((byte)0x7E); // No MASK and 2 bytes length
+            serverFrame.put((byte)(payload.length >> 8)); // first length byte
+            serverFrame.put((byte)(payload.length & 0xFF)); // second length byte
+            serverFrame.put(payload);
+            BufferUtil.flipToFlush(serverFrame,0);
+            byte buf[] = BufferUtil.toArray(serverFrame);
+            socket.write(buf,0,buf.length);
+            socket.flush();
+
+            Assert.assertTrue(websocket.dataLatch.await(1000,TimeUnit.SECONDS));
+        }
+        finally
+        {
+            client.stop();
+            server.stop();
+        }
+    }
+}
diff --git a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/WebSocketClientBadUriTest.java b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/WebSocketClientBadUriTest.java
new file mode 100644
index 0000000..1fcfcfc
--- /dev/null
+++ b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/WebSocketClientBadUriTest.java
@@ -0,0 +1,102 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.client;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.eclipse.jetty.toolchain.test.TestTracker;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+ at RunWith(Parameterized.class)
+public class WebSocketClientBadUriTest
+{
+    @Parameters
+    public static Collection<String[]> data()
+    {
+        List<String[]> data = new ArrayList<>();
+        // @formatter:off
+        // - not using right scheme
+        data.add(new String[] { "http://localhost" });
+        data.add(new String[] { "https://localhost" });
+        data.add(new String[] { "file://localhost" });
+        data.add(new String[] { "content://localhost" });
+        data.add(new String[] { "jar://localhost" });
+        // - non-absolute uri
+        data.add(new String[] { "/mysocket" });
+        data.add(new String[] { "/sockets/echo" });
+        data.add(new String[] { "#echo" });
+        data.add(new String[] { "localhost:8080/echo" });
+        // @formatter:on
+        return data;
+    }
+
+    @Rule
+    public TestTracker tt = new TestTracker();
+
+    private WebSocketClient client;
+    private final String uriStr;
+    private final URI uri;
+
+    public WebSocketClientBadUriTest(String rawUri)
+    {
+        this.uriStr = rawUri;
+        this.uri = URI.create(uriStr);
+    }
+
+    @Before
+    public void startClient() throws Exception
+    {
+        client = new WebSocketClient();
+        client.start();
+    }
+
+    @After
+    public void stopClient() throws Exception
+    {
+        client.stop();
+    }
+
+    @Test
+    public void testBadURI() throws Exception
+    {
+        JettyTrackingSocket wsocket = new JettyTrackingSocket();
+
+        try
+        {
+            client.connect(wsocket,uri); // should toss exception
+
+            Assert.fail("Expected IllegalArgumentException");
+        }
+        catch (IllegalArgumentException e)
+        {
+            // expected path
+            wsocket.assertNotOpened();
+        }
+    }
+}
diff --git a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/WebSocketClientTest.java b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/WebSocketClientTest.java
new file mode 100644
index 0000000..56975a0
--- /dev/null
+++ b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/WebSocketClientTest.java
@@ -0,0 +1,377 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.client;
+
+import java.net.InetSocketAddress;
+import java.net.URI;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.toolchain.test.AdvancedRunner;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.websocket.api.BatchMode;
+import org.eclipse.jetty.websocket.api.RemoteEndpoint;
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.UpgradeRequest;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
+import org.eclipse.jetty.websocket.common.io.FutureWriteCallback;
+import org.eclipse.jetty.websocket.common.test.BlockheadServer;
+import org.eclipse.jetty.websocket.common.test.BlockheadServer.ServerConnection;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.hamcrest.Matchers.greaterThan;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.hamcrest.Matchers.nullValue;
+
+ at RunWith(AdvancedRunner.class)
+public class WebSocketClientTest
+{
+    private BlockheadServer server;
+
+    @Before
+    public void startServer() throws Exception
+    {
+        server = new BlockheadServer();
+        server.start();
+    }
+
+    @After
+    public void stopServer() throws Exception
+    {
+        server.stop();
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testAddExtension_NotInstalled() throws Exception
+    {
+        WebSocketClient client = new WebSocketClient();
+        client.start();
+        try
+        {
+            JettyTrackingSocket cliSock = new JettyTrackingSocket();
+
+            client.getPolicy().setIdleTimeout(10000);
+
+            URI wsUri = server.getWsUri();
+            ClientUpgradeRequest request = new ClientUpgradeRequest();
+            request.setSubProtocols("echo");
+            request.addExtensions("x-bad");
+
+            // Should trigger failure on bad extension
+            client.connect(cliSock,wsUri,request);
+        }
+        finally
+        {
+            client.stop();
+        }
+    }
+
+    @Test
+    public void testBasicEcho_FromClient() throws Exception
+    {
+        WebSocketClient client = new WebSocketClient();
+        client.start();
+        try
+        {
+            JettyTrackingSocket cliSock = new JettyTrackingSocket();
+
+            client.getPolicy().setIdleTimeout(10000);
+
+            URI wsUri = server.getWsUri();
+            ClientUpgradeRequest request = new ClientUpgradeRequest();
+            request.setSubProtocols("echo");
+            Future<Session> future = client.connect(cliSock,wsUri,request);
+
+            final ServerConnection srvSock = server.accept();
+            srvSock.upgrade();
+
+            Session sess = future.get(500,TimeUnit.MILLISECONDS);
+            Assert.assertThat("Session",sess,notNullValue());
+            Assert.assertThat("Session.open",sess.isOpen(),is(true));
+            Assert.assertThat("Session.upgradeRequest",sess.getUpgradeRequest(),notNullValue());
+            Assert.assertThat("Session.upgradeResponse",sess.getUpgradeResponse(),notNullValue());
+
+            cliSock.assertWasOpened();
+            cliSock.assertNotClosed();
+
+            Assert.assertThat("client.connectionManager.sessions.size",client.getConnectionManager().getSessions().size(),is(1));
+
+            RemoteEndpoint remote = cliSock.getSession().getRemote();
+            remote.sendStringByFuture("Hello World!");
+            if (remote.getBatchMode() == BatchMode.ON)
+                remote.flush();
+            srvSock.echoMessage(1,500,TimeUnit.MILLISECONDS);
+            // wait for response from server
+            cliSock.waitForMessage(500,TimeUnit.MILLISECONDS);
+
+            cliSock.assertMessage("Hello World!");
+        }
+        finally
+        {
+            client.stop();
+        }
+    }
+
+    @Test
+    public void testBasicEcho_UsingCallback() throws Exception
+    {
+        WebSocketClient client = new WebSocketClient();
+        client.start();
+        try
+        {
+            JettyTrackingSocket cliSock = new JettyTrackingSocket();
+
+            client.getPolicy().setIdleTimeout(10000);
+
+            URI wsUri = server.getWsUri();
+            ClientUpgradeRequest request = new ClientUpgradeRequest();
+            request.setSubProtocols("echo");
+            Future<Session> future = client.connect(cliSock,wsUri,request);
+
+            final ServerConnection srvSock = server.accept();
+            srvSock.upgrade();
+
+            Session sess = future.get(500,TimeUnit.MILLISECONDS);
+            Assert.assertThat("Session",sess,notNullValue());
+            Assert.assertThat("Session.open",sess.isOpen(),is(true));
+            Assert.assertThat("Session.upgradeRequest",sess.getUpgradeRequest(),notNullValue());
+            Assert.assertThat("Session.upgradeResponse",sess.getUpgradeResponse(),notNullValue());
+
+            cliSock.assertWasOpened();
+            cliSock.assertNotClosed();
+
+            Assert.assertThat("client.connectionManager.sessions.size",client.getConnectionManager().getSessions().size(),is(1));
+
+            FutureWriteCallback callback = new FutureWriteCallback();
+
+            cliSock.getSession().getRemote().sendString("Hello World!",callback);
+            callback.get(1,TimeUnit.SECONDS);
+        }
+        finally
+        {
+            client.stop();
+        }
+    }
+
+    @Test
+    public void testBasicEcho_FromServer() throws Exception
+    {
+        WebSocketClient client = new WebSocketClient();
+        client.start();
+        try
+        {
+            JettyTrackingSocket wsocket = new JettyTrackingSocket();
+            Future<Session> future = client.connect(wsocket,server.getWsUri());
+
+            // Server
+            final ServerConnection srvSock = server.accept();
+            srvSock.upgrade();
+
+            // Validate connect
+            Session sess = future.get(500,TimeUnit.MILLISECONDS);
+            Assert.assertThat("Session",sess,notNullValue());
+            Assert.assertThat("Session.open",sess.isOpen(),is(true));
+            Assert.assertThat("Session.upgradeRequest",sess.getUpgradeRequest(),notNullValue());
+            Assert.assertThat("Session.upgradeResponse",sess.getUpgradeResponse(),notNullValue());
+
+            // Have server send initial message
+            srvSock.write(new TextFrame().setPayload("Hello World"));
+
+            // Verify connect
+            future.get(500,TimeUnit.MILLISECONDS);
+            wsocket.assertWasOpened();
+            wsocket.awaitMessage(1,TimeUnit.SECONDS,2);
+
+            wsocket.assertMessage("Hello World");
+        }
+        finally
+        {
+            client.stop();
+        }
+    }
+
+    @Test
+    public void testLocalRemoteAddress() throws Exception
+    {
+        WebSocketClient fact = new WebSocketClient();
+        fact.start();
+        try
+        {
+            JettyTrackingSocket wsocket = new JettyTrackingSocket();
+
+            URI wsUri = server.getWsUri();
+            Future<Session> future = fact.connect(wsocket,wsUri);
+
+            ServerConnection ssocket = server.accept();
+            ssocket.upgrade();
+
+            future.get(500,TimeUnit.MILLISECONDS);
+
+            Assert.assertTrue(wsocket.openLatch.await(1,TimeUnit.SECONDS));
+
+            InetSocketAddress local = wsocket.getSession().getLocalAddress();
+            InetSocketAddress remote = wsocket.getSession().getRemoteAddress();
+
+            Assert.assertThat("Local Socket Address",local,notNullValue());
+            Assert.assertThat("Remote Socket Address",remote,notNullValue());
+
+            // Hard to validate (in a portable unit test) the local address that was used/bound in the low level Jetty Endpoint
+            Assert.assertThat("Local Socket Address / Host",local.getAddress().getHostAddress(),notNullValue());
+            Assert.assertThat("Local Socket Address / Port",local.getPort(),greaterThan(0));
+
+            Assert.assertThat("Remote Socket Address / Host",remote.getAddress().getHostAddress(),is(wsUri.getHost()));
+            Assert.assertThat("Remote Socket Address / Port",remote.getPort(),greaterThan(0));
+        }
+        finally
+        {
+            fact.stop();
+        }
+    }
+
+    @Test
+    public void testMessageBiggerThanBufferSize() throws Exception
+    {
+        WebSocketClient factSmall = new WebSocketClient();
+        factSmall.start();
+        try
+        {
+            int bufferSize = 512;
+
+            JettyTrackingSocket wsocket = new JettyTrackingSocket();
+
+            URI wsUri = server.getWsUri();
+            Future<Session> future = factSmall.connect(wsocket,wsUri);
+
+            ServerConnection ssocket = server.accept();
+            ssocket.upgrade();
+
+            future.get(500,TimeUnit.MILLISECONDS);
+
+            Assert.assertTrue(wsocket.openLatch.await(1,TimeUnit.SECONDS));
+
+            int length = bufferSize + (bufferSize / 2); // 1.5 times buffer size
+            ssocket.write(0x80 | 0x01); // FIN + TEXT
+            ssocket.write(0x7E); // No MASK and 2 bytes length
+            ssocket.write(length >> 8); // first length byte
+            ssocket.write(length & 0xFF); // second length byte
+            for (int i = 0; i < length; ++i)
+            {
+                ssocket.write('x');
+            }
+            ssocket.flush();
+
+            Assert.assertTrue(wsocket.dataLatch.await(1000,TimeUnit.SECONDS));
+        }
+        finally
+        {
+            factSmall.stop();
+        }
+    }
+
+    @Test
+    public void testMaxMessageSize() throws Exception
+    {
+        WebSocketClient client = new WebSocketClient();
+        client.start();
+        try
+        {
+            MaxMessageSocket wsocket = new MaxMessageSocket();
+
+            URI wsUri = server.getWsUri();
+            Future<Session> future = client.connect(wsocket,wsUri);
+
+            ServerConnection ssocket = server.accept();
+            ssocket.upgrade();
+
+            wsocket.awaitConnect(1,TimeUnit.SECONDS);
+
+            Session sess = future.get(500,TimeUnit.MILLISECONDS);
+            Assert.assertThat("Session",sess,notNullValue());
+            Assert.assertThat("Session.open",sess.isOpen(),is(true));
+
+            // Create string that is larger than default size of 64k
+            // but smaller than maxMessageSize of 100k
+            byte buf[] = new byte[80 * 1024];
+            Arrays.fill(buf,(byte)'x');
+            String msg = StringUtil.toUTF8String(buf,0,buf.length);
+
+            wsocket.getSession().getRemote().sendStringByFuture(msg);
+            ssocket.echoMessage(1,2,TimeUnit.SECONDS);
+            // wait for response from server
+            wsocket.waitForMessage(1,TimeUnit.SECONDS);
+
+            wsocket.assertMessage(msg);
+
+            Assert.assertTrue(wsocket.dataLatch.await(2,TimeUnit.SECONDS));
+        }
+        finally
+        {
+            client.stop();
+        }
+    }
+
+    @Test
+    public void testParameterMap() throws Exception
+    {
+        WebSocketClient fact = new WebSocketClient();
+        fact.start();
+        try
+        {
+            JettyTrackingSocket wsocket = new JettyTrackingSocket();
+
+            URI wsUri = server.getWsUri().resolve("/test?snack=cashews&amount=handful&brand=off");
+            Future<Session> future = fact.connect(wsocket,wsUri);
+
+            ServerConnection ssocket = server.accept();
+            ssocket.upgrade();
+
+            future.get(500,TimeUnit.MILLISECONDS);
+
+            Assert.assertTrue(wsocket.openLatch.await(1,TimeUnit.SECONDS));
+
+            Session session = wsocket.getSession();
+            UpgradeRequest req = session.getUpgradeRequest();
+            Assert.assertThat("Upgrade Request",req,notNullValue());
+
+            Map<String, List<String>> parameterMap = req.getParameterMap();
+            Assert.assertThat("Parameter Map",parameterMap,notNullValue());
+
+            Assert.assertThat("Parameter[snack]",parameterMap.get("snack"),is(Arrays.asList(new String[]
+            { "cashews" })));
+            Assert.assertThat("Parameter[amount]",parameterMap.get("amount"),is(Arrays.asList(new String[]
+            { "handful" })));
+            Assert.assertThat("Parameter[brand]",parameterMap.get("brand"),is(Arrays.asList(new String[]
+            { "off" })));
+
+            Assert.assertThat("Parameter[cost]",parameterMap.get("cost"),nullValue());
+        }
+        finally
+        {
+            fact.stop();
+        }
+    }
+}
diff --git a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/examples/TestClient.java b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/examples/TestClient.java
new file mode 100644
index 0000000..e65290d
--- /dev/null
+++ b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/examples/TestClient.java
@@ -0,0 +1,312 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.client.examples;
+
+import java.net.InetSocketAddress;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Random;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.WebSocketAdapter;
+import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
+import org.eclipse.jetty.websocket.client.WebSocketClient;
+import org.eclipse.jetty.websocket.common.OpCode;
+import org.eclipse.jetty.websocket.common.test.LeakTrackingBufferPoolRule;
+
+/**
+ * This is not a general purpose websocket client. It's only for testing the websocket server and is hardwired to a specific draft version of the protocol.
+ */
+public class TestClient
+{
+    public class TestSocket extends WebSocketAdapter
+    {
+        @Override
+        public void onWebSocketBinary(byte[] payload, int offset, int len)
+        {
+        }
+
+        @Override
+        public void onWebSocketClose(int statusCode, String reason)
+        {
+            super.onWebSocketClose(statusCode,reason);
+        }
+
+        @Override
+        public void onWebSocketConnect(Session session)
+        {
+            if (_verbose)
+            {
+                System.err.printf("%s#onWebSocketConnect %s %s\n",this.getClass().getSimpleName(),session,session.getClass().getSimpleName());
+            }
+        }
+
+        public void send(byte op, byte[] data, int maxFragmentLength)
+        {
+            _starts.add(System.nanoTime());
+
+            int off = 0;
+            int len = data.length;
+            if ((maxFragmentLength > 0) && (len > maxFragmentLength))
+            {
+                len = maxFragmentLength;
+            }
+            __messagesSent++;
+            while (off < data.length)
+            {
+                __framesSent++;
+
+                off += len;
+                if ((data.length - off) > len)
+                {
+                    len = data.length - off;
+                }
+                if ((maxFragmentLength > 0) && (len > maxFragmentLength))
+                {
+                    len = maxFragmentLength;
+                }
+            }
+        }
+
+    }
+
+    private static boolean _verbose = false;
+
+    private static final Random __random = new Random();
+
+    private static LeakTrackingBufferPoolRule bufferPool = new LeakTrackingBufferPoolRule("TestClient");
+
+    private final String _host;
+    private final int _port;
+    private final String _protocol;
+    private final int _timeout;
+
+    private static int __framesSent;
+    private static int __messagesSent;
+    private static AtomicInteger __framesReceived = new AtomicInteger();
+    private static AtomicInteger __messagesReceived = new AtomicInteger();
+
+    private static AtomicLong __totalTime = new AtomicLong();
+    private static AtomicLong __minDuration = new AtomicLong(Long.MAX_VALUE);
+    private static AtomicLong __maxDuration = new AtomicLong(Long.MIN_VALUE);
+    private static long __start;
+
+    public static void main(String[] args) throws Exception
+    {
+        String host = "localhost";
+        int port = 8080;
+        String protocol = null;
+        int count = 10;
+        int size = 64;
+        int fragment = 4000;
+        boolean binary = false;
+        int clients = 1;
+        int delay = 1000;
+
+        for (int i = 0; i < args.length; i++)
+        {
+            String a = args[i];
+            if ("-p".equals(a) || "--port".equals(a))
+            {
+                port = Integer.parseInt(args[++i]);
+            }
+            else if ("-h".equals(a) || "--host".equals(a))
+            {
+                host = args[++i];
+            }
+            else if ("-c".equals(a) || "--count".equals(a))
+            {
+                count = Integer.parseInt(args[++i]);
+            }
+            else if ("-s".equals(a) || "--size".equals(a))
+            {
+                size = Integer.parseInt(args[++i]);
+            }
+            else if ("-f".equals(a) || "--fragment".equals(a))
+            {
+                fragment = Integer.parseInt(args[++i]);
+            }
+            else if ("-P".equals(a) || "--protocol".equals(a))
+            {
+                protocol = args[++i];
+            }
+            else if ("-v".equals(a) || "--verbose".equals(a))
+            {
+                _verbose = true;
+            }
+            else if ("-b".equals(a) || "--binary".equals(a))
+            {
+                binary = true;
+            }
+            else if ("-C".equals(a) || "--clients".equals(a))
+            {
+                clients = Integer.parseInt(args[++i]);
+            }
+            else if ("-d".equals(a) || "--delay".equals(a))
+            {
+                delay = Integer.parseInt(args[++i]);
+            }
+            else if (a.startsWith("-"))
+            {
+                usage(args);
+            }
+        }
+
+        TestClient[] client = new TestClient[clients];
+        WebSocketClient wsclient = new WebSocketClient(bufferPool);
+        try
+        {
+            wsclient.start();
+            __start = System.currentTimeMillis();
+            protocol = protocol == null?"echo":protocol;
+
+            for (int i = 0; i < clients; i++)
+            {
+                client[i] = new TestClient(wsclient,host,port,protocol,60000);
+                client[i].open();
+            }
+
+            System.out.println("Jetty WebSocket PING " + host + ":" + port + " (" + new InetSocketAddress(host,port) + ") " + clients + " clients " + protocol);
+
+            for (int p = 0; p < count; p++)
+            {
+                long next = System.currentTimeMillis() + delay;
+
+                byte op = OpCode.TEXT;
+                if (binary)
+                {
+                    op = OpCode.BINARY;
+                }
+
+                byte data[] = null;
+
+                switch (op)
+                {
+                    case OpCode.TEXT:
+                    {
+                        StringBuilder b = new StringBuilder();
+                        while (b.length() < size)
+                        {
+                            b.append('A' + __random.nextInt(26));
+                        }
+                        data = b.toString().getBytes(StandardCharsets.UTF_8);
+                        break;
+                    }
+                    case OpCode.BINARY:
+                    {
+                        data = new byte[size];
+                        __random.nextBytes(data);
+                        break;
+                    }
+                }
+
+                for (int i = 0; i < clients; i++)
+                {
+                    client[i].send(op,data,fragment);
+                }
+
+                while (System.currentTimeMillis() < next)
+                {
+                    Thread.sleep(10);
+                }
+            }
+        }
+        finally
+        {
+            for (int i = 0; i < clients; i++)
+            {
+                if (client[i] != null)
+                {
+                    client[i].disconnect();
+                }
+            }
+
+            long duration = System.currentTimeMillis() - __start;
+            System.out.println("--- " + host + " websocket ping statistics using " + clients + " connection" + (clients > 1?"s":"") + " ---");
+            System.out.printf("%d/%d frames sent/recv, %d/%d mesg sent/recv, time %dms %dm/s %.2fbps%n",__framesSent,__framesReceived.get(),__messagesSent,
+                    __messagesReceived.get(),duration,((1000L * __messagesReceived.get()) / duration),(1000.0D * __messagesReceived.get() * 8 * size)
+                            / duration / 1024 / 1024);
+            System.out.printf("rtt min/ave/max = %.3f/%.3f/%.3f ms\n",__minDuration.get() / 1000000.0,__messagesReceived.get() == 0?0.0:(__totalTime.get()
+                    / __messagesReceived.get() / 1000000.0),__maxDuration.get() / 1000000.0);
+
+            wsclient.stop();
+        }
+        bufferPool.assertNoLeaks();
+    }
+
+    private static void usage(String[] args)
+    {
+        System.err.println("ERROR: " + Arrays.asList(args));
+        System.err.println("USAGE: java -cp CLASSPATH " + TestClient.class + " [ OPTIONS ]");
+        System.err.println("  -h|--host HOST  (default localhost)");
+        System.err.println("  -p|--port PORT  (default 8080)");
+        System.err.println("  -b|--binary");
+        System.err.println("  -v|--verbose");
+        System.err.println("  -c|--count n    (default 10)");
+        System.err.println("  -s|--size n     (default 64)");
+        System.err.println("  -f|--fragment n (default 4000) ");
+        System.err.println("  -P|--protocol echo|echo-assemble|echo-fragment|echo-broadcast");
+        System.err.println("  -C|--clients n  (default 1) ");
+        System.err.println("  -d|--delay n    (default 1000ms) ");
+        System.exit(1);
+    }
+
+    private BlockingQueue<Long> _starts = new LinkedBlockingQueue<Long>();
+
+    int _messageBytes;
+    int _frames;
+    byte _opcode = -1;
+    private WebSocketClient client;
+    private TestSocket socket;
+
+    public TestClient(WebSocketClient client, String host, int port, String protocol, int timeoutMS) throws Exception
+    {
+        this.client = client;
+        _host = host;
+        _port = port;
+        _protocol = protocol;
+        _timeout = timeoutMS;
+    }
+
+    private void disconnect()
+    {
+        // TODO Auto-generated method stub
+    }
+
+    private void open() throws Exception
+    {
+        client.getPolicy().setIdleTimeout(_timeout);
+        ClientUpgradeRequest request = new ClientUpgradeRequest();
+        request.setSubProtocols(_protocol);
+        socket = new TestSocket();
+        URI wsUri = new URI("ws://" + _host + ":" + _port + "/");
+        client.connect(socket,wsUri,request).get(10,TimeUnit.SECONDS);
+    }
+
+    private void send(byte op, byte[] data, int fragment)
+    {
+        socket.send(op,data,fragment);
+    }
+}
diff --git a/jetty-websocket/websocket-client/src/test/resources/jetty-logging.properties b/jetty-websocket/websocket-client/src/test/resources/jetty-logging.properties
new file mode 100644
index 0000000..a5afe1e
--- /dev/null
+++ b/jetty-websocket/websocket-client/src/test/resources/jetty-logging.properties
@@ -0,0 +1,21 @@
+org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
+org.eclipse.jetty.LEVEL=WARN
+# org.eclipse.jetty.LEVEL=DEBUG
+# org.eclipse.jetty.io.LEVEL=DEBUG
+# org.eclipse.jetty.io.ChannelEndPoint.LEVEL=DEBUG
+# org.eclipse.jetty.io.SelectChannelEndPoint.LEVEL=DEBUG
+# org.eclipse.jetty.io.IdleTimeout.LEVEL=DEBUG
+# org.eclipse.jetty.io.FillInterest.LEVEL=DEBUG
+# org.eclipse.jetty.io.AbstractConnection.LEVEL=DEBUG
+# org.eclipse.jetty.websocket.LEVEL=WARN
+# org.eclipse.jetty.websocket.LEVEL=DEBUG
+# org.eclipse.jetty.websocket.client.LEVEL=DEBUG
+# org.eclipse.jetty.websocket.common.io.AbstractWebSocketConnection.LEVEL=DEBUG
+# org.eclipse.jetty.websocket.common.io.IOState.LEVEL=DEBUG
+
+# org.eclipse.jetty.websocket.common.Generator.LEVEL=DEBUG
+# org.eclipse.jetty.websocket.common.Parser.LEVEL=DEBUG
+# org.eclipse.jetty.websocket.client.TrackingSocket.LEVEL=DEBUG
+
+### Hide the stacktraces during testing
+org.eclipse.jetty.websocket.client.internal.io.UpgradeConnection.STACKS=false
diff --git a/jetty-websocket/websocket-common/pom.xml b/jetty-websocket/websocket-common/pom.xml
new file mode 100644
index 0000000..1b91905
--- /dev/null
+++ b/jetty-websocket/websocket-common/pom.xml
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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">
+  <parent>
+    <groupId>org.eclipse.jetty.websocket</groupId>
+    <artifactId>websocket-parent</artifactId>
+    <version>9.2.13.v20150730</version>
+  </parent>
+
+  <modelVersion>4.0.0</modelVersion>
+  <artifactId>websocket-common</artifactId>
+  <name>Jetty :: Websocket :: Common</name>
+
+  <properties>
+    <bundle-symbolic-name>${project.groupId}.common</bundle-symbolic-name>
+  </properties>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.eclipse.jetty.websocket</groupId>
+      <artifactId>websocket-api</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-util</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-io</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.toolchain</groupId>
+      <artifactId>jetty-test-helper</artifactId>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-enforcer-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>ban-java-servlet-api</id>
+            <goals>
+              <goal>enforce</goal>
+            </goals>
+            <configuration>
+              <rules>
+                <bannedDependencies>
+                  <includes>
+                    <include>javax.servlet</include>
+                    <include>servletapi</include>
+                    <include>org.eclipse.jetty.orbit:javax.servlet</include>
+                    <include>org.mortbay.jetty:servlet-api</include>
+                    <include>jetty:servlet-api</include>
+                  </includes>
+                </bannedDependencies>
+              </rules>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-jar-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>artifact-jars</id>
+            <goals>
+              <goal>jar</goal>
+              <goal>test-jar</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+</project>
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/AcceptHash.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/AcceptHash.java
new file mode 100644
index 0000000..005065d
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/AcceptHash.java
@@ -0,0 +1,61 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common;
+
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+
+import org.eclipse.jetty.util.B64Code;
+
+/**
+ * Logic for working with the <code>Sec-WebSocket-Key</code> and <code>Sec-WebSocket-Accept</code> headers.
+ * <p>
+ * This is kept separate from Connection objects to facilitate difference in behavior between client and server, as well as making testing easier.
+ */
+public class AcceptHash
+{
+    /**
+     * Globally Unique Identifier for use in WebSocket handshake within <code>Sec-WebSocket-Accept</code> and <code>Sec-WebSocket-Key</code> http headers.
+     * <p>
+     * See <a href="https://tools.ietf.org/html/rfc6455#section-1.3">Opening Handshake (Section 1.3)</a>
+     */
+    private final static byte[] MAGIC = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11".getBytes(StandardCharsets.ISO_8859_1);
+
+    /**
+     * Concatenate the provided key with the Magic GUID and return the Base64 encoded form.
+     * 
+     * @param key
+     *            the key to hash
+     * @return the <code>Sec-WebSocket-Accept</code> header response (per opening handshake spec)
+     */
+    public static String hashKey(String key)
+    {
+        try
+        {
+            MessageDigest md = MessageDigest.getInstance("SHA1");
+            md.update(key.getBytes(StandardCharsets.UTF_8));
+            md.update(MAGIC);
+            return new String(B64Code.encode(md.digest()));
+        }
+        catch (Exception e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/BlockingWriteCallback.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/BlockingWriteCallback.java
new file mode 100644
index 0000000..0909a3f
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/BlockingWriteCallback.java
@@ -0,0 +1,86 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common;
+
+import java.io.IOException;
+
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.SharedBlockingCallback;
+import org.eclipse.jetty.websocket.api.WriteCallback;
+
+
+/* ------------------------------------------------------------ */
+/** extend a SharedlBlockingCallback to an websocket WriteCallback
+ */
+public class BlockingWriteCallback extends SharedBlockingCallback
+{
+    public BlockingWriteCallback()
+    {
+    }
+        
+    public WriteBlocker acquireWriteBlocker() throws IOException
+    {
+        return new WriteBlocker(acquire());
+    }
+    
+    public static class WriteBlocker implements WriteCallback, Callback, AutoCloseable
+    {
+        Blocker blocker;
+        
+        WriteBlocker(Blocker blocker)
+        {
+            this.blocker=blocker;
+        }
+        
+        @Override
+        public void writeFailed(Throwable x)
+        {
+            blocker.failed(x);
+        }
+
+        @Override
+        public void writeSuccess()
+        {
+            blocker.succeeded();
+        }
+
+        @Override
+        public void succeeded()
+        {
+            blocker.succeeded();
+        }
+
+        @Override
+        public void failed(Throwable x)
+        {
+            blocker.failed(x);
+        }
+        
+        @Override
+        public void close() throws IOException
+        {
+            blocker.close();
+        }
+        
+        public void block() throws IOException
+        {
+            blocker.block();
+        }
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/CloseInfo.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/CloseInfo.java
new file mode 100644
index 0000000..bf8d236
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/CloseInfo.java
@@ -0,0 +1,204 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.Utf8Appendable.NotUtf8Exception;
+import org.eclipse.jetty.util.Utf8StringBuilder;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.BadPayloadException;
+import org.eclipse.jetty.websocket.api.ProtocolException;
+import org.eclipse.jetty.websocket.api.StatusCode;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.common.frames.CloseFrame;
+
+public class CloseInfo
+{
+    private static final Logger LOG = Log.getLogger(CloseInfo.class);
+    private int statusCode;
+    private String reason;
+
+    public CloseInfo()
+    {
+        this(StatusCode.NO_CODE,null);
+    }
+
+    public CloseInfo(ByteBuffer payload, boolean validate)
+    {
+        this.statusCode = StatusCode.NO_CODE;
+        this.reason = null;
+
+        if ((payload == null) || (payload.remaining() == 0))
+        {
+            return; // nothing to do
+        }
+
+        ByteBuffer data = payload.slice();
+        if ((data.remaining() == 1) && (validate))
+        {
+            throw new ProtocolException("Invalid 1 byte payload");
+        }
+
+        if (data.remaining() >= 2)
+        {
+            // Status Code
+            statusCode = 0; // start with 0
+            statusCode |= (data.get() & 0xFF) << 8;
+            statusCode |= (data.get() & 0xFF);
+
+            if (validate)
+            {
+                if ((statusCode < StatusCode.NORMAL) || (statusCode == StatusCode.UNDEFINED) || (statusCode == StatusCode.NO_CLOSE)
+                        || (statusCode == StatusCode.NO_CODE) || ((statusCode > 1011) && (statusCode <= 2999)) || (statusCode >= 5000))
+                {
+                    throw new ProtocolException("Invalid close code: " + statusCode);
+                }
+            }
+
+            if (data.remaining() > 0)
+            {
+                // Reason
+                try
+                {
+                    Utf8StringBuilder utf = new Utf8StringBuilder();
+                    utf.append(data);
+                    reason = utf.toString();
+                }
+                catch (NotUtf8Exception e)
+                {
+                    if (validate)
+                    {
+                        throw new BadPayloadException("Invalid Close Reason",e);
+                    }
+                    else
+                    {
+                        LOG.warn(e);
+                    }
+                }
+                catch (RuntimeException e)
+                {
+                    if (validate)
+                    {
+                        throw new ProtocolException("Invalid Close Reason",e);
+                    }
+                    else
+                    {
+                        LOG.warn(e);
+                    }
+                }
+            }
+        }
+    }
+
+    public CloseInfo(Frame frame)
+    {
+        this(frame.getPayload(),false);
+    }
+
+    public CloseInfo(Frame frame, boolean validate)
+    {
+        this(frame.getPayload(),validate);
+    }
+
+    public CloseInfo(int statusCode)
+    {
+        this(statusCode,null);
+    }
+
+    public CloseInfo(int statusCode, String reason)
+    {
+        this.statusCode = statusCode;
+        this.reason = reason;
+    }
+
+    private ByteBuffer asByteBuffer()
+    {
+        if ((statusCode == StatusCode.NO_CLOSE) || (statusCode == StatusCode.NO_CODE) || (statusCode == (-1)))
+        {
+            // codes that are not allowed to be used in endpoint.
+            return null;
+        }
+
+        int len = 2; // status code
+        byte utf[] = null;
+        if (StringUtil.isNotBlank(reason))
+        {
+            utf = StringUtil.getUtf8Bytes(reason);
+            len += utf.length;
+        }
+
+        ByteBuffer buf = BufferUtil.allocate(len);
+        BufferUtil.flipToFill(buf);
+        buf.put((byte)((statusCode >>> 8) & 0xFF));
+        buf.put((byte)((statusCode >>> 0) & 0xFF));
+
+        if (utf != null)
+        {
+            buf.put(utf,0,utf.length);
+        }
+        BufferUtil.flipToFlush(buf,0);
+
+        return buf;
+    }
+
+    public CloseFrame asFrame()
+    {
+        CloseFrame frame = new CloseFrame();
+        frame.setFin(true);
+        if ((statusCode >= 1000) && (statusCode != StatusCode.NO_CLOSE) && (statusCode != StatusCode.NO_CODE))
+        {
+            if (statusCode == StatusCode.FAILED_TLS_HANDSHAKE)
+            {
+                throw new ProtocolException("Close Frame with status code " + statusCode + " not allowed (per RFC6455)");
+            }
+            frame.setPayload(asByteBuffer());
+        }
+        return frame;
+    }
+
+    public String getReason()
+    {
+        return reason;
+    }
+
+    public int getStatusCode()
+    {
+        return statusCode;
+    }
+
+    public boolean isHarsh()
+    {
+        return !((statusCode == StatusCode.NORMAL) || (statusCode == StatusCode.NO_CODE));
+    }
+
+    public boolean isAbnormal()
+    {
+        return (statusCode != StatusCode.NORMAL);
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("CloseInfo[code=%d,reason=%s]",statusCode,reason);
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/ConnectionState.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/ConnectionState.java
new file mode 100644
index 0000000..ae94f7e
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/ConnectionState.java
@@ -0,0 +1,58 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common;
+
+/**
+ * Connection states as outlined in <a href="https://tools.ietf.org/html/rfc6455">RFC6455</a>.
+ */
+public enum ConnectionState
+{
+    /** [RFC] Initial state of a connection, the upgrade request / response is in progress */
+    CONNECTING,
+    /**
+     * [Impl] Intermediate state between CONNECTING and OPEN, used to indicate that a upgrade request/response is successful, but the end-user provided socket's
+     * onOpen code has yet to run.
+     * <p>
+     * This state is to allow the local socket to initiate messages and frames, but to NOT start reading yet.
+     */
+    CONNECTED,
+    /**
+     * [RFC] The websocket connection is established and open.
+     * <p>
+     * This indicates that the Upgrade has succeed, and the end-user provided socket's onOpen code has completed.
+     * <p>
+     * It is now time to start reading from the remote endpoint.
+     */
+    OPEN,
+    /**
+     * [RFC] The websocket closing handshake is started.
+     * <p>
+     * This can be considered a half-closed state.
+     * <p>
+     * When receiving this as an event on {@link ConnectionStateListener#onConnectionStateChange(ConnectionState)} a close frame should be sent using
+     * the {@link CloseInfo} available from {@link IOState#getCloseInfo()}
+     */
+    CLOSING,
+    /**
+     * [RFC] The websocket connection is closed.
+     * <p>
+     * Connection should be disconnected and no further reads or writes should occur.
+     */
+    CLOSED;
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/Generator.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/Generator.java
new file mode 100644
index 0000000..fd3310c
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/Generator.java
@@ -0,0 +1,442 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common;
+
+import java.nio.ByteBuffer;
+import java.util.List;
+
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.websocket.api.ProtocolException;
+import org.eclipse.jetty.websocket.api.WebSocketBehavior;
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.api.extensions.Extension;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+
+/**
+ * Generating a frame in WebSocket land.
+ * 
+ * <pre>
+ *    0                   1                   2                   3
+ *    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ *   +-+-+-+-+-------+-+-------------+-------------------------------+
+ *   |F|R|R|R| opcode|M| Payload len |    Extended payload length    |
+ *   |I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
+ *   |N|V|V|V|       |S|             |   (if payload len==126/127)   |
+ *   | |1|2|3|       |K|             |                               |
+ *   +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
+ *   |     Extended payload length continued, if payload len == 127  |
+ *   + - - - - - - - - - - - - - - - +-------------------------------+
+ *   |                               |Masking-key, if MASK set to 1  |
+ *   +-------------------------------+-------------------------------+
+ *   | Masking-key (continued)       |          Payload Data         |
+ *   +-------------------------------- - - - - - - - - - - - - - - - +
+ *   :                     Payload Data continued ...                :
+ *   + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
+ *   |                     Payload Data continued ...                |
+ *   +---------------------------------------------------------------+
+ * </pre>
+ */
+public class Generator
+{
+    /**
+     * The overhead (maximum) for a framing header. Assuming a maximum sized payload with masking key.
+     */
+    public static final int MAX_HEADER_LENGTH = 28;
+
+    private final WebSocketBehavior behavior;
+    private final ByteBufferPool bufferPool;
+    private final boolean validating;
+    private final boolean readOnly;
+
+    /**
+     * Are any flags in use
+     * <p>
+     * 
+     * <pre>
+     *   0100_0000 (0x40) = rsv1
+     *   0010_0000 (0x20) = rsv2
+     *   0001_0000 (0x10) = rsv3
+     * </pre>
+     */
+    private byte flagsInUse = 0x00;
+
+    /**
+     * Construct Generator with provided policy and bufferPool
+     * 
+     * @param policy
+     *            the policy to use
+     * @param bufferPool
+     *            the buffer pool to use
+     */
+    public Generator(WebSocketPolicy policy, ByteBufferPool bufferPool)
+    {
+        this(policy,bufferPool,true,false);
+    }
+
+    /**
+     * Construct Generator with provided policy and bufferPool
+     * 
+     * @param policy
+     *            the policy to use
+     * @param bufferPool
+     *            the buffer pool to use
+     * @param validating
+     *            true to enable RFC frame validation
+     */
+    public Generator(WebSocketPolicy policy, ByteBufferPool bufferPool, boolean validating)
+    {
+        this(policy,bufferPool,validating,false);
+    }
+
+    /**
+     * Construct Generator with provided policy and bufferPool
+     * 
+     * @param policy
+     *            the policy to use
+     * @param bufferPool
+     *            the buffer pool to use
+     * @param validating
+     *            true to enable RFC frame validation
+     * @param readOnly
+     *            true if generator is to treat frames as read-only and not modify them. Useful for debugging purposes, but not generally for runtime use.
+     */
+    public Generator(WebSocketPolicy policy, ByteBufferPool bufferPool, boolean validating, boolean readOnly)
+    {
+        this.behavior = policy.getBehavior();
+        this.bufferPool = bufferPool;
+        this.validating = validating;
+        this.readOnly = readOnly;
+    }
+
+    public void assertFrameValid(Frame frame)
+    {
+        if (!validating)
+        {
+            return;
+        }
+
+        /*
+         * RFC 6455 Section 5.2
+         * 
+         * MUST be 0 unless an extension is negotiated that defines meanings for non-zero values. If a nonzero value is received and none of the negotiated
+         * extensions defines the meaning of such a nonzero value, the receiving endpoint MUST _Fail the WebSocket Connection_.
+         */
+        if (frame.isRsv1() && !isRsv1InUse())
+        {
+            throw new ProtocolException("RSV1 not allowed to be set");
+        }
+
+        if (frame.isRsv2() && !isRsv2InUse())
+        {
+            throw new ProtocolException("RSV2 not allowed to be set");
+        }
+
+        if (frame.isRsv3() && !isRsv3InUse())
+        {
+            throw new ProtocolException("RSV3 not allowed to be set");
+        }
+
+        if (OpCode.isControlFrame(frame.getOpCode()))
+        {
+            /*
+             * RFC 6455 Section 5.5
+             * 
+             * All control frames MUST have a payload length of 125 bytes or less and MUST NOT be fragmented.
+             */
+            if (frame.getPayloadLength() > 125)
+            {
+                throw new ProtocolException("Invalid control frame payload length");
+            }
+
+            if (!frame.isFin())
+            {
+                throw new ProtocolException("Control Frames must be FIN=true");
+            }
+
+            /*
+             * RFC 6455 Section 5.5.1
+             * 
+             * close frame payload is specially formatted which is checked in CloseInfo
+             */
+            if (frame.getOpCode() == OpCode.CLOSE)
+            {
+
+                ByteBuffer payload = frame.getPayload();
+                if (payload != null)
+                {
+                    new CloseInfo(payload,true);
+                }
+            }
+        }
+    }
+
+    public void configureFromExtensions(List<? extends Extension> exts)
+    {
+        // default
+        flagsInUse = 0x00;
+
+        // configure from list of extensions in use
+        for (Extension ext : exts)
+        {
+            if (ext.isRsv1User())
+            {
+                flagsInUse = (byte)(flagsInUse | 0x40);
+            }
+            if (ext.isRsv2User())
+            {
+                flagsInUse = (byte)(flagsInUse | 0x20);
+            }
+            if (ext.isRsv3User())
+            {
+                flagsInUse = (byte)(flagsInUse | 0x10);
+            }
+        }
+    }
+
+    public ByteBuffer generateHeaderBytes(Frame frame)
+    {
+        ByteBuffer buffer = bufferPool.acquire(MAX_HEADER_LENGTH,true);
+        generateHeaderBytes(frame,buffer);
+        return buffer;
+    }
+
+    public void generateHeaderBytes(Frame frame, ByteBuffer buffer)
+    {
+        int p = BufferUtil.flipToFill(buffer);
+
+        // we need a framing header
+        assertFrameValid(frame);
+
+        /*
+         * start the generation process
+         */
+        byte b = 0x00;
+
+        // Setup fin thru opcode
+        if (frame.isFin())
+        {
+            b |= 0x80; // 1000_0000
+        }
+
+        // Set the flags
+        if (frame.isRsv1())
+        {
+            b |= 0x40; // 0100_0000
+        }
+        if (frame.isRsv2())
+        {
+            b |= 0x20; // 0010_0000
+        }
+        if (frame.isRsv3())
+        {
+            b |= 0x10; // 0001_0000
+        }
+
+        // NOTE: using .getOpCode() here, not .getType().getOpCode() for testing reasons
+        byte opcode = frame.getOpCode();
+
+        if (frame.getOpCode() == OpCode.CONTINUATION)
+        {
+            // Continuations are not the same OPCODE
+            opcode = OpCode.CONTINUATION;
+        }
+
+        b |= opcode & 0x0F;
+
+        buffer.put(b);
+
+        // is masked
+        b = (frame.isMasked()?(byte)0x80:(byte)0x00);
+
+        // payload lengths
+        int payloadLength = frame.getPayloadLength();
+
+        /*
+         * if length is over 65535 then its a 7 + 64 bit length
+         */
+        if (payloadLength > 0xFF_FF)
+        {
+            // we have a 64 bit length
+            b |= 0x7F;
+            buffer.put(b); // indicate 8 byte length
+            buffer.put((byte)0); //
+            buffer.put((byte)0); // anything over an
+            buffer.put((byte)0); // int is just
+            buffer.put((byte)0); // insane!
+            buffer.put((byte)((payloadLength >> 24) & 0xFF));
+            buffer.put((byte)((payloadLength >> 16) & 0xFF));
+            buffer.put((byte)((payloadLength >> 8) & 0xFF));
+            buffer.put((byte)(payloadLength & 0xFF));
+        }
+        /*
+         * if payload is greater that 126 we have a 7 + 16 bit length
+         */
+        else if (payloadLength >= 0x7E)
+        {
+            b |= 0x7E;
+            buffer.put(b); // indicate 2 byte length
+            buffer.put((byte)(payloadLength >> 8));
+            buffer.put((byte)(payloadLength & 0xFF));
+        }
+        /*
+         * we have a 7 bit length
+         */
+        else
+        {
+            b |= (payloadLength & 0x7F);
+            buffer.put(b);
+        }
+
+        // masking key
+        if (frame.isMasked() && !readOnly)
+        {
+            byte[] mask = frame.getMask();
+            buffer.put(mask);
+            int maskInt = 0;
+            for (byte maskByte : mask)
+                maskInt = (maskInt << 8) + (maskByte & 0xFF);
+
+            // perform data masking here
+            ByteBuffer payload = frame.getPayload();
+            if ((payload != null) && (payload.remaining() > 0))
+            {
+                int maskOffset = 0;
+                int start = payload.position();
+                int end = payload.limit();
+                int remaining;
+                while ((remaining = end - start) > 0)
+                {
+                    if (remaining >= 4)
+                    {
+                        payload.putInt(start,payload.getInt(start) ^ maskInt);
+                        start += 4;
+                    }
+                    else
+                    {
+                        payload.put(start,(byte)(payload.get(start) ^ mask[maskOffset & 3]));
+                        ++start;
+                        ++maskOffset;
+                    }
+                }
+            }
+        }
+
+        BufferUtil.flipToFlush(buffer,p);
+    }
+
+    /**
+     * Generate the whole frame (header + payload copy) into a single ByteBuffer.
+     * <p>
+     * Note: This is slow, moves lots of memory around. Only use this if you must (such as in unit testing).
+     * 
+     * @param frame
+     *            the frame to generate
+     * @param buf
+     *            the buffer to output the generated frame to
+     */
+    public void generateWholeFrame(Frame frame, ByteBuffer buf)
+    {
+        buf.put(generateHeaderBytes(frame));
+        if (frame.hasPayload())
+        {
+            if (readOnly)
+            {
+                buf.put(frame.getPayload().slice());
+            }
+            else
+            {
+                buf.put(frame.getPayload());
+            }
+        }
+    }
+
+    public ByteBufferPool getBufferPool()
+    {
+        return bufferPool;
+    }
+
+    public void setRsv1InUse(boolean rsv1InUse)
+    {
+        if (readOnly)
+        {
+            throw new RuntimeException("Not allowed to modify read-only frame");
+        }
+        flagsInUse = (byte)((flagsInUse & 0xBF) | (rsv1InUse?0x40:0x00));
+    }
+
+    public void setRsv2InUse(boolean rsv2InUse)
+    {
+        if (readOnly)
+        {
+            throw new RuntimeException("Not allowed to modify read-only frame");
+        }
+        flagsInUse = (byte)((flagsInUse & 0xDF) | (rsv2InUse?0x20:0x00));
+    }
+
+    public void setRsv3InUse(boolean rsv3InUse)
+    {
+        if (readOnly)
+        {
+            throw new RuntimeException("Not allowed to modify read-only frame");
+        }
+        flagsInUse = (byte)((flagsInUse & 0xEF) | (rsv3InUse?0x10:0x00));
+    }
+
+    public boolean isRsv1InUse()
+    {
+        return (flagsInUse & 0x40) != 0;
+    }
+
+    public boolean isRsv2InUse()
+    {
+        return (flagsInUse & 0x20) != 0;
+    }
+
+    public boolean isRsv3InUse()
+    {
+        return (flagsInUse & 0x10) != 0;
+    }
+
+    @Override
+    public String toString()
+    {
+        StringBuilder builder = new StringBuilder();
+        builder.append("Generator[");
+        builder.append(behavior);
+        if (validating)
+        {
+            builder.append(",validating");
+        }
+        if (isRsv1InUse())
+        {
+            builder.append(",+rsv1");
+        }
+        if (isRsv2InUse())
+        {
+            builder.append(",+rsv2");
+        }
+        if (isRsv3InUse())
+        {
+            builder.append(",+rsv3");
+        }
+        builder.append("]");
+        return builder.toString();
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/LogicalConnection.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/LogicalConnection.java
new file mode 100644
index 0000000..df0dfb3
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/LogicalConnection.java
@@ -0,0 +1,169 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common;
+
+import java.net.InetSocketAddress;
+import java.util.concurrent.Executor;
+
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.websocket.api.SuspendToken;
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.api.extensions.IncomingFrames;
+import org.eclipse.jetty.websocket.api.extensions.OutgoingFrames;
+import org.eclipse.jetty.websocket.common.io.IOState;
+
+public interface LogicalConnection extends OutgoingFrames, SuspendToken
+{
+    /**
+     * Send a websocket Close frame, without a status code or reason.
+     * <p>
+     * Basic usage: results in an non-blocking async write, then connection close.
+     * 
+     * @see StatusCode
+     * @see #close(int, String)
+     */
+    public void close();
+
+    /**
+     * Send a websocket Close frame, with status code.
+     * <p>
+     * Advanced usage: results in an non-blocking async write, then connection close.
+     * 
+     * @param statusCode
+     *            the status code
+     * @param reason
+     *            the (optional) reason. (can be null for no reason)
+     * @see StatusCode
+     */
+    public void close(int statusCode, String reason);
+
+    /**
+     * Terminate the connection (no close frame sent)
+     */
+    void disconnect();
+
+    /**
+     * Get the ByteBufferPool in use by the connection
+     */
+    ByteBufferPool getBufferPool();
+    
+    /**
+     * Get the Executor used by this connection.
+     */
+    Executor getExecutor();
+
+    /**
+     * Get the read/write idle timeout.
+     * 
+     * @return the idle timeout in milliseconds
+     */
+    public long getIdleTimeout();
+
+    /**
+     * Get the IOState of the connection.
+     * 
+     * @return the IOState of the connection.
+     */
+    IOState getIOState();
+
+    /**
+     * Get the local {@link InetSocketAddress} in use for this connection.
+     * <p>
+     * Note: Non-physical connections, like during the Mux extensions, or during unit testing can result in a InetSocketAddress on port 0 and/or on localhost.
+     * 
+     * @return the local address.
+     */
+    InetSocketAddress getLocalAddress();
+
+    /**
+     * Set the maximum number of milliseconds of idleness before the connection is closed/disconnected, (ie no frames are either sent or received)
+     * @return the idle timeout in milliseconds
+     */
+    long getMaxIdleTimeout();
+
+    /**
+     * The policy that the connection is running under.
+     * @return the policy for the connection
+     */
+    WebSocketPolicy getPolicy();
+
+    /**
+     * Get the remote Address in use for this connection.
+     * <p>
+     * Note: Non-physical connections, like during the Mux extensions, or during unit testing can result in a InetSocketAddress on port 0 and/or on localhost.
+     * 
+     * @return the remote address.
+     */
+    InetSocketAddress getRemoteAddress();
+
+    /**
+     * Get the Session for this connection
+     * 
+     * @return the Session for this connection
+     */
+    WebSocketSession getSession();
+
+    /**
+     * Test if logical connection is still open
+     * 
+     *  @return true if connection is open
+     */
+    public boolean isOpen();
+
+    /**
+     * Tests if the connection is actively reading.
+     * 
+     * @return true if connection is actively attempting to read.
+     */
+    boolean isReading();
+
+    /**
+     * Set the maximum number of milliseconds of idleness before the connection is closed/disconnected, (ie no frames are either sent or received)
+     * <p>
+     * This idle timeout cannot be garunteed to take immediate effect for any active read/write actions.
+     * New read/write actions will have this new idle timeout.
+     * 
+     * @param ms
+     *            the number of milliseconds of idle timeout
+     */
+    void setMaxIdleTimeout(long ms);
+
+    /**
+     * Set where the connection should send the incoming frames to.
+     * <p>
+     * Often this is from the Parser to the start of the extension stack, and eventually on to the session.
+     * 
+     * @param incoming
+     *            the incoming frames handler
+     */
+    void setNextIncomingFrames(IncomingFrames incoming);
+
+    /**
+     * Set the session associated with this connection
+     * 
+     * @param session
+     *            the session
+     */
+    void setSession(WebSocketSession session);
+
+    /**
+     * Suspend a the incoming read events on the connection.
+     */
+    SuspendToken suspend();
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/OpCode.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/OpCode.java
new file mode 100644
index 0000000..a4f5018
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/OpCode.java
@@ -0,0 +1,113 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common;
+
+public final class OpCode
+{
+    /**
+     * OpCode for a Continuation Frame
+     * 
+     * @see <a href="https://tools.ietf.org/html/rfc6455#section-11.8">RFC 6455, Section 11.8 (WebSocket Opcode Registry</a>
+     */
+    public static final byte CONTINUATION = (byte)0x00;
+
+    /**
+     * OpCode for a Text Frame
+     * 
+     * @see <a href="https://tools.ietf.org/html/rfc6455#section-11.8">RFC 6455, Section 11.8 (WebSocket Opcode Registry</a>
+     */
+    public static final byte TEXT = (byte)0x01;
+
+    /**
+     * OpCode for a Binary Frame
+     * 
+     * @see <a href="https://tools.ietf.org/html/rfc6455#section-11.8">RFC 6455, Section 11.8 (WebSocket Opcode Registry</a>
+     */
+    public static final byte BINARY = (byte)0x02;
+
+    /**
+     * OpCode for a Close Frame
+     * 
+     * @see <a href="https://tools.ietf.org/html/rfc6455#section-11.8">RFC 6455, Section 11.8 (WebSocket Opcode Registry</a>
+     */
+    public static final byte CLOSE = (byte)0x08;
+
+    /**
+     * OpCode for a Ping Frame
+     * 
+     * @see <a href="https://tools.ietf.org/html/rfc6455#section-11.8">RFC 6455, Section 11.8 (WebSocket Opcode Registry</a>
+     */
+    public static final byte PING = (byte)0x09;
+
+    /**
+     * OpCode for a Pong Frame
+     * 
+     * @see <a href="https://tools.ietf.org/html/rfc6455#section-11.8">RFC 6455, Section 11.8 (WebSocket Opcode Registry</a>
+     */
+    public static final byte PONG = (byte)0x0A;
+
+    /**
+     * An undefined OpCode
+     */
+    public static final byte UNDEFINED = (byte)-1;
+
+    public static boolean isControlFrame(byte opcode)
+    {
+        return (opcode >= CLOSE);
+    }
+
+    public static boolean isDataFrame(byte opcode)
+    {
+        return (opcode == TEXT) || (opcode == BINARY);
+    }
+
+    /**
+     * Test for known opcodes (per the RFC spec)
+     * 
+     * @param opcode
+     *            the opcode to test
+     * @return true if known. false if unknown, undefined, or reserved
+     */
+    public static boolean isKnown(byte opcode)
+    {
+        return (opcode == CONTINUATION) || (opcode == TEXT) || (opcode == BINARY) || (opcode == CLOSE) || (opcode == PING) || (opcode == PONG);
+    }
+
+    public static String name(byte opcode)
+    {
+        switch (opcode)
+        {
+            case -1:
+                return "NO-OP";
+            case CONTINUATION:
+                return "CONTINUATION";
+            case TEXT:
+                return "TEXT";
+            case BINARY: return "BINARY";
+            case CLOSE:
+                return "CLOSE";
+            case PING:
+                return "PING";
+            case PONG:
+                return "PONG";
+            default:
+                return "NON-SPEC[" + opcode + "]";
+        }
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/Parser.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/Parser.java
new file mode 100644
index 0000000..a9a2c18
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/Parser.java
@@ -0,0 +1,660 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common;
+
+import java.nio.ByteBuffer;
+import java.util.List;
+
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.MessageTooLargeException;
+import org.eclipse.jetty.websocket.api.ProtocolException;
+import org.eclipse.jetty.websocket.api.WebSocketBehavior;
+import org.eclipse.jetty.websocket.api.WebSocketException;
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.api.extensions.Extension;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.api.extensions.IncomingFrames;
+import org.eclipse.jetty.websocket.common.frames.BinaryFrame;
+import org.eclipse.jetty.websocket.common.frames.CloseFrame;
+import org.eclipse.jetty.websocket.common.frames.ContinuationFrame;
+import org.eclipse.jetty.websocket.common.frames.ControlFrame;
+import org.eclipse.jetty.websocket.common.frames.PingFrame;
+import org.eclipse.jetty.websocket.common.frames.PongFrame;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
+import org.eclipse.jetty.websocket.common.io.payload.DeMaskProcessor;
+import org.eclipse.jetty.websocket.common.io.payload.PayloadProcessor;
+
+/**
+ * Parsing of a frames in WebSocket land.
+ */
+public class Parser
+{
+    private enum State
+    {
+        START,
+        PAYLOAD_LEN,
+        PAYLOAD_LEN_BYTES,
+        MASK,
+        MASK_BYTES,
+        PAYLOAD
+    }
+
+    private static final Logger LOG = Log.getLogger(Parser.class);
+    private final WebSocketPolicy policy;
+    private final ByteBufferPool bufferPool;
+
+    // State specific
+    private State state = State.START;
+    private int cursor = 0;
+    // Frame
+    private WebSocketFrame frame;
+    private boolean priorDataFrame;
+    // payload specific
+    private ByteBuffer payload;
+    private int payloadLength;
+    private PayloadProcessor maskProcessor = new DeMaskProcessor();
+    // private PayloadProcessor strictnessProcessor;
+
+    /** 
+     * Is there an extension using RSV flag?
+     * <p>
+     * 
+     * <pre>
+     *   0100_0000 (0x40) = rsv1
+     *   0010_0000 (0x20) = rsv2
+     *   0001_0000 (0x10) = rsv3
+     * </pre>
+     */
+    private byte flagsInUse=0x00;
+    
+    private IncomingFrames incomingFramesHandler;
+
+    public Parser(WebSocketPolicy wspolicy, ByteBufferPool bufferPool)
+    {
+        this.bufferPool = bufferPool;
+        this.policy = wspolicy;
+    }
+
+    private void assertSanePayloadLength(long len)
+    {
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("{} Payload Length: {} - {}",policy.getBehavior(),len,this);
+        }
+
+        // Since we use ByteBuffer so often, having lengths over Integer.MAX_VALUE is really impossible.
+        if (len > Integer.MAX_VALUE)
+        {
+            // OMG! Sanity Check! DO NOT WANT! Won't anyone think of the memory!
+            throw new MessageTooLargeException("[int-sane!] cannot handle payload lengths larger than " + Integer.MAX_VALUE);
+        }
+
+        switch (frame.getOpCode())
+        {
+            case OpCode.CLOSE:
+                if (len == 1)
+                {
+                    throw new ProtocolException("Invalid close frame payload length, [" + payloadLength + "]");
+                }
+                // fall thru
+            case OpCode.PING:
+            case OpCode.PONG:
+                if (len > ControlFrame.MAX_CONTROL_PAYLOAD)
+                {
+                    throw new ProtocolException("Invalid control frame payload length, [" + payloadLength + "] cannot exceed ["
+                            + ControlFrame.MAX_CONTROL_PAYLOAD + "]");
+                }
+                break;
+            case OpCode.TEXT:
+                policy.assertValidTextMessageSize((int)len);
+                break;
+            case OpCode.BINARY:
+                policy.assertValidBinaryMessageSize((int)len);
+                break;
+        }
+    }
+
+    public void configureFromExtensions(List<? extends Extension> exts)
+    {        
+        // default
+        flagsInUse = 0x00;
+
+        // configure from list of extensions in use
+        for (Extension ext : exts)
+        {
+            if (ext.isRsv1User())
+            {
+                flagsInUse = (byte)(flagsInUse | 0x40);
+            }
+            if (ext.isRsv2User())
+            {
+                flagsInUse = (byte)(flagsInUse | 0x20);
+            }
+            if (ext.isRsv3User())
+            {
+                flagsInUse = (byte)(flagsInUse | 0x10);
+            }
+        }
+    }
+
+    public IncomingFrames getIncomingFramesHandler()
+    {
+        return incomingFramesHandler;
+    }
+
+    public WebSocketPolicy getPolicy()
+    {
+        return policy;
+    }
+
+    public boolean isRsv1InUse()
+    {
+        return (flagsInUse & 0x40) != 0;
+    }
+
+    public boolean isRsv2InUse()
+    {
+        return (flagsInUse & 0x20) != 0;
+    }
+
+    public boolean isRsv3InUse()
+    {
+        return (flagsInUse & 0x10) != 0;
+    }
+
+    protected void notifyFrame(final Frame f)
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("{} Notify {}",policy.getBehavior(),getIncomingFramesHandler());
+
+        if (policy.getBehavior() == WebSocketBehavior.SERVER)
+        {
+            /* Parsing on server.
+             * 
+             * Then you MUST make sure all incoming frames are masked!
+             * 
+             * Technically, this test is in violation of RFC-6455, Section 5.1
+             * http://tools.ietf.org/html/rfc6455#section-5.1
+             * 
+             * But we can't trust the client at this point, so Jetty opts to close
+             * the connection as a Protocol error.
+             */
+            if (!f.isMasked())
+            {
+                throw new ProtocolException("Client MUST mask all frames (RFC-6455: Section 5.1)");
+            }
+        }
+        else if(policy.getBehavior() == WebSocketBehavior.CLIENT)
+        {
+            // Required by RFC-6455 / Section 5.1
+            if (f.isMasked())
+            {
+                throw new ProtocolException("Server MUST NOT mask any frames (RFC-6455: Section 5.1)");
+            }
+        }
+
+        if (incomingFramesHandler == null)
+        {
+            return;
+        }
+        try
+        {
+            incomingFramesHandler.incomingFrame(f);
+        }
+        catch (WebSocketException e)
+        {
+            notifyWebSocketException(e);
+        }
+        catch (Throwable t)
+        {
+            LOG.warn(t);
+            notifyWebSocketException(new WebSocketException(t));
+        }
+    }
+
+    protected void notifyWebSocketException(WebSocketException e)
+    {
+        LOG.warn(e);
+        if (incomingFramesHandler == null)
+        {
+            return;
+        }
+        incomingFramesHandler.incomingError(e);
+    }
+
+    public void parse(ByteBuffer buffer) throws WebSocketException
+    {
+        if (buffer.remaining() <= 0)
+        {
+            return;
+        }
+        try
+        {
+            // TODO: create DebugBuffer
+
+            // parse through all the frames in the buffer
+            while (parseFrame(buffer))
+            {
+                if (LOG.isDebugEnabled())
+                    LOG.debug("{} Parsed Frame: {}",policy.getBehavior(),frame);
+                notifyFrame(frame);
+                if (frame.isDataFrame())
+                {
+                    priorDataFrame = !frame.isFin();
+                }
+                reset();
+            }
+        }
+        catch (WebSocketException e)
+        {
+            buffer.position(buffer.limit()); // consume remaining
+            reset();
+            // let session know
+            notifyWebSocketException(e);
+            // need to throw for proper close behavior in connection
+            throw e;
+        }
+        catch (Throwable t)
+        {
+            buffer.position(buffer.limit()); // consume remaining
+            reset();
+            // let session know
+            WebSocketException e = new WebSocketException(t);
+            notifyWebSocketException(e);
+            // need to throw for proper close behavior in connection
+            throw e;
+        }
+    }
+
+    private void reset()
+    {
+        if (frame != null)
+            frame.reset();
+        frame = null;
+        bufferPool.release(payload);
+        payload = null;
+    }
+
+    /**
+     * Parse the base framing protocol buffer.
+     * <p>
+     * Note the first byte (fin,rsv1,rsv2,rsv3,opcode) are parsed by the {@link Parser#parse(ByteBuffer)} method
+     * <p>
+     * Not overridable
+     * 
+     * @param buffer
+     *            the buffer to parse from.
+     * @return true if done parsing base framing protocol and ready for parsing of the payload. false if incomplete parsing of base framing protocol.
+     */
+    private boolean parseFrame(ByteBuffer buffer)
+    {
+        if (LOG.isDebugEnabled())
+        {
+            LOG.debug("{} Parsing {} bytes",policy.getBehavior(),buffer.remaining());
+        }
+        while (buffer.hasRemaining())
+        {
+            switch (state)
+            {
+                case START:
+                {
+                    // peek at byte
+                    byte b = buffer.get();
+                    boolean fin = ((b & 0x80) != 0);
+                    
+                    byte opcode = (byte)(b & 0x0F);
+
+                    if (!OpCode.isKnown(opcode))
+                    {
+                        throw new ProtocolException("Unknown opcode: " + opcode);
+                    }
+                    
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("{} OpCode {}, fin={} rsv={}{}{}",
+                                policy.getBehavior(),
+                                OpCode.name(opcode),
+                                fin,
+                                (isRsv1InUse()?'1':'.'),
+                                (isRsv2InUse()?'1':'.'),
+                                (isRsv3InUse()?'1':'.'));
+
+                    // base framing flags
+                    switch(opcode)
+                    {
+                        case OpCode.TEXT:
+                            frame = new TextFrame();
+                            // data validation
+                            if (priorDataFrame)
+                            {
+                                throw new ProtocolException("Unexpected " + OpCode.name(opcode) + " frame, was expecting CONTINUATION");
+                            }
+                            break;
+                        case OpCode.BINARY:
+                            frame = new BinaryFrame();
+                            // data validation
+                            if (priorDataFrame)
+                            {
+                                throw new ProtocolException("Unexpected " + OpCode.name(opcode) + " frame, was expecting CONTINUATION");
+                            }
+                            break;
+                        case OpCode.CONTINUATION:
+                            frame = new ContinuationFrame();
+                            // continuation validation
+                            if (!priorDataFrame)
+                            {
+                                throw new ProtocolException("CONTINUATION frame without prior !FIN");
+                            }
+                            // Be careful to use the original opcode
+                            break;
+                        case OpCode.CLOSE:
+                            frame = new CloseFrame();
+                            // control frame validation
+                            if (!fin)
+                            {
+                                throw new ProtocolException("Fragmented Close Frame [" + OpCode.name(opcode) + "]");
+                            }
+                            break;
+                        case OpCode.PING:
+                            frame = new PingFrame();
+                            // control frame validation
+                            if (!fin)
+                            {
+                                throw new ProtocolException("Fragmented Ping Frame [" + OpCode.name(opcode) + "]");
+                            }
+                            break;
+                        case OpCode.PONG:
+                            frame = new PongFrame();
+                            // control frame validation
+                            if (!fin)
+                            {
+                                throw new ProtocolException("Fragmented Pong Frame [" + OpCode.name(opcode) + "]");
+                            }
+                            break;
+                    }
+                    
+                    frame.setFin(fin);
+
+                    // Are any flags set?
+                    if ((b & 0x70) != 0)
+                    {
+                        /*
+                         * RFC 6455 Section 5.2
+                         * 
+                         * MUST be 0 unless an extension is negotiated that defines meanings for non-zero values. If a nonzero value is received and none of the
+                         * negotiated extensions defines the meaning of such a nonzero value, the receiving endpoint MUST _Fail the WebSocket Connection_.
+                         */
+                        if ((b & 0x40) != 0)
+                        {
+                            if (isRsv1InUse())
+                                frame.setRsv1(true);
+                            else
+                                throw new ProtocolException("RSV1 not allowed to be set");   
+                        }
+                        if ((b & 0x20) != 0)
+                        {
+                            if (isRsv2InUse())
+                                frame.setRsv2(true);
+                            else
+                                throw new ProtocolException("RSV2 not allowed to be set");   
+                        }
+                        if ((b & 0x10) != 0)
+                        {
+                            if (isRsv3InUse())
+                                frame.setRsv3(true);
+                            else
+                                throw new ProtocolException("RSV3 not allowed to be set");   
+                        }
+                    }
+                    
+                    state = State.PAYLOAD_LEN;
+                    break;
+                }
+                
+                case PAYLOAD_LEN:
+                {
+                    byte b = buffer.get();
+                    frame.setMasked((b & 0x80) != 0);
+                    payloadLength = (byte)(0x7F & b);
+
+                    if (payloadLength == 127) // 0x7F
+                    {
+                        // length 8 bytes (extended payload length)
+                        payloadLength = 0;
+                        state = State.PAYLOAD_LEN_BYTES;
+                        cursor = 8;
+                        break; // continue onto next state
+                    }
+                    else if (payloadLength == 126) // 0x7E
+                    {
+                        // length 2 bytes (extended payload length)
+                        payloadLength = 0;
+                        state = State.PAYLOAD_LEN_BYTES;
+                        cursor = 2;
+                        break; // continue onto next state
+                    }
+
+                    assertSanePayloadLength(payloadLength);
+                    if (frame.isMasked())
+                    {
+                        state = State.MASK;
+                    }
+                    else
+                    {
+                        // special case for empty payloads (no more bytes left in buffer)
+                        if (payloadLength == 0)
+                        {
+                            state = State.START;
+                            return true;
+                        }
+
+                        maskProcessor.reset(frame);
+                        state = State.PAYLOAD;
+                    }
+
+                    break;
+                }
+                
+                case PAYLOAD_LEN_BYTES:
+                {
+                    byte b = buffer.get();
+                    --cursor;
+                    payloadLength |= (b & 0xFF) << (8 * cursor);
+                    if (cursor == 0)
+                    {
+                        assertSanePayloadLength(payloadLength);
+                        if (frame.isMasked())
+                        {
+                            state = State.MASK;
+                        }
+                        else
+                        {
+                            // special case for empty payloads (no more bytes left in buffer)
+                            if (payloadLength == 0)
+                            {
+                                state = State.START;
+                                return true;
+                            }
+
+                            maskProcessor.reset(frame);
+                            state = State.PAYLOAD;
+                        }
+                    }
+                    break;
+                }
+                
+                case MASK:
+                {
+                    byte m[] = new byte[4];
+                    frame.setMask(m);
+                    if (buffer.remaining() >= 4)
+                    {
+                        buffer.get(m,0,4);
+                        // special case for empty payloads (no more bytes left in buffer)
+                        if (payloadLength == 0)
+                        {
+                            state = State.START;
+                            return true;
+                        }
+
+                        maskProcessor.reset(frame);
+                        state = State.PAYLOAD;
+                    }
+                    else
+                    {
+                        state = State.MASK_BYTES;
+                        cursor = 4;
+                    }
+                    break;
+                }
+                
+                case MASK_BYTES:
+                {
+                    byte b = buffer.get();
+                    frame.getMask()[4 - cursor] = b;
+                    --cursor;
+                    if (cursor == 0)
+                    {
+                        // special case for empty payloads (no more bytes left in buffer)
+                        if (payloadLength == 0)
+                        {
+                            state = State.START;
+                            return true;
+                        }
+
+                        maskProcessor.reset(frame);
+                        state = State.PAYLOAD;
+                    }
+                    break;
+                }
+                
+                case PAYLOAD:
+                {
+                    frame.assertValid();
+                    if (parsePayload(buffer))
+                    {
+                        // special check for close
+                        if (frame.getOpCode() == OpCode.CLOSE)
+                        {
+                            // TODO: yuck. Don't create an object to do validation checks!
+                            new CloseInfo(frame);
+                        }
+                        state = State.START;
+                        // we have a frame!
+                        return true;
+                    }
+                    break;
+                }
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Implementation specific parsing of a payload
+     * 
+     * @param buffer
+     *            the payload buffer
+     * @return true if payload is done reading, false if incomplete
+     */
+    private boolean parsePayload(ByteBuffer buffer)
+    {        
+        if (payloadLength == 0)
+        {
+            return true;
+        }
+
+        if (buffer.hasRemaining())
+        {
+            // Create a small window of the incoming buffer to work with.
+            // this should only show the payload itself, and not any more
+            // bytes that could belong to the start of the next frame.
+            int bytesSoFar = payload == null ? 0 : payload.position();
+            int bytesExpected = payloadLength - bytesSoFar;
+            int bytesAvailable = buffer.remaining();
+            int windowBytes = Math.min(bytesAvailable, bytesExpected);
+            int limit = buffer.limit();
+            buffer.limit(buffer.position() + windowBytes);
+            ByteBuffer window = buffer.slice();
+            buffer.limit(limit);
+            buffer.position(buffer.position() + window.remaining());
+
+            if (LOG.isDebugEnabled()) {
+                LOG.debug("{} Window: {}",policy.getBehavior(),BufferUtil.toDetailString(window));
+            }
+
+            maskProcessor.process(window);
+
+            if (window.remaining() == payloadLength)
+            {
+                // We have the whole content, no need to copy.
+                frame.setPayload(window);
+                return true;
+            }
+            else
+            {
+                if (payload == null)
+                {
+                    payload = bufferPool.acquire(payloadLength,false);
+                    BufferUtil.clearToFill(payload);
+                }
+                // Copy the payload.
+                payload.put(window);
+
+                if (payload.position() == payloadLength)
+                {
+                    BufferUtil.flipToFlush(payload, 0);
+                    frame.setPayload(payload);
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    public void setIncomingFramesHandler(IncomingFrames incoming)
+    {
+        this.incomingFramesHandler = incoming;
+    }
+
+    @Override
+    public String toString()
+    {
+        StringBuilder builder = new StringBuilder();
+        builder.append("Parser@").append(Integer.toHexString(hashCode()));
+        builder.append("[");
+        if (incomingFramesHandler == null)
+        {
+            builder.append("NO_HANDLER");
+        }
+        else
+        {
+            builder.append(incomingFramesHandler.getClass().getSimpleName());
+        }
+        builder.append(",s=").append(state);
+        builder.append(",c=").append(cursor);
+        builder.append(",len=").append(payloadLength);
+        builder.append(",f=").append(frame);
+        builder.append(",p=").append(policy);
+        builder.append("]");
+        return builder.toString();
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/SessionFactory.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/SessionFactory.java
new file mode 100644
index 0000000..acedb57
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/SessionFactory.java
@@ -0,0 +1,33 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common;
+
+import java.net.URI;
+
+import org.eclipse.jetty.websocket.common.events.EventDriver;
+
+/**
+ * Interface for creating jetty {@link WebSocketSession} objects.
+ */
+public interface SessionFactory
+{
+    public boolean supports(EventDriver websocket);
+    
+    public WebSocketSession createSession(URI requestURI, EventDriver websocket, LogicalConnection connection);
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/SessionListener.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/SessionListener.java
new file mode 100644
index 0000000..815d6bc
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/SessionListener.java
@@ -0,0 +1,31 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common;
+
+/**
+ * Basic listener interface for Session open/close.
+ * <p>
+ * Used primarily for tracking open sessions.
+ */
+public interface SessionListener
+{
+    public void onSessionOpened(WebSocketSession session);
+    
+    public void onSessionClosed(WebSocketSession session);
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketFrame.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketFrame.java
new file mode 100644
index 0000000..be64a68
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketFrame.java
@@ -0,0 +1,388 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.common.frames.BinaryFrame;
+import org.eclipse.jetty.websocket.common.frames.CloseFrame;
+import org.eclipse.jetty.websocket.common.frames.ContinuationFrame;
+import org.eclipse.jetty.websocket.common.frames.PingFrame;
+import org.eclipse.jetty.websocket.common.frames.PongFrame;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
+
+/**
+ * A Base Frame as seen in <a href="https://tools.ietf.org/html/rfc6455#section-5.2">RFC 6455. Sec 5.2</a>
+ * 
+ * <pre>
+ *    0                   1                   2                   3
+ *    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ *   +-+-+-+-+-------+-+-------------+-------------------------------+
+ *   |F|R|R|R| opcode|M| Payload len |    Extended payload length    |
+ *   |I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
+ *   |N|V|V|V|       |S|             |   (if payload len==126/127)   |
+ *   | |1|2|3|       |K|             |                               |
+ *   +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
+ *   |     Extended payload length continued, if payload len == 127  |
+ *   + - - - - - - - - - - - - - - - +-------------------------------+
+ *   |                               |Masking-key, if MASK set to 1  |
+ *   +-------------------------------+-------------------------------+
+ *   | Masking-key (continued)       |          Payload Data         |
+ *   +-------------------------------- - - - - - - - - - - - - - - - +
+ *   :                     Payload Data continued ...                :
+ *   + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
+ *   |                     Payload Data continued ...                |
+ *   +---------------------------------------------------------------+
+ * </pre>
+ */
+public abstract class WebSocketFrame implements Frame
+{
+    public static WebSocketFrame copy(Frame original)
+    {
+        WebSocketFrame copy;
+        switch (original.getOpCode())
+        {
+            case OpCode.BINARY:
+                copy = new BinaryFrame();
+                break;
+            case OpCode.TEXT:
+                copy = new TextFrame();
+                break;
+            case OpCode.CLOSE:
+                copy = new CloseFrame();
+                break;
+            case OpCode.CONTINUATION:
+                copy = new ContinuationFrame();
+                break;
+            case OpCode.PING:
+                copy = new PingFrame();
+                break;
+            case OpCode.PONG:
+                copy = new PongFrame();
+                break;
+            default:
+                throw new IllegalArgumentException("Cannot copy frame with opcode " + original.getOpCode() + " - " + original);
+        }
+
+        copy.copyHeaders(original);
+        ByteBuffer payload = original.getPayload();
+        if (payload != null)
+        {
+            ByteBuffer payloadCopy = ByteBuffer.allocate(payload.remaining());
+            payloadCopy.put(payload.slice()).flip();
+            copy.setPayload(payloadCopy);
+        }
+        return copy;
+    }
+
+    /**
+     * Combined FIN + RSV1 + RSV2 + RSV3 + OpCode byte.
+     * <p>
+     * 
+     * <pre>
+     *   1000_0000 (0x80) = fin
+     *   0100_0000 (0x40) = rsv1
+     *   0010_0000 (0x20) = rsv2
+     *   0001_0000 (0x10) = rsv3
+     *   0000_1111 (0x0F) = opcode
+     * </pre>
+     */
+    protected byte finRsvOp;
+    protected boolean masked = false;
+
+    protected byte mask[];
+    /**
+     * The payload data.
+     * <p>
+     * It is assumed to always be in FLUSH mode (ready to read) in this object.
+     */
+    protected ByteBuffer data;
+
+    /**
+     * Construct form opcode
+     */
+    protected WebSocketFrame(byte opcode)
+    {
+        reset();
+        setOpCode(opcode);
+    }
+
+    public abstract void assertValid();
+
+    protected void copyHeaders(Frame frame)
+    {
+        finRsvOp = 0x00;
+        finRsvOp |= frame.isFin()?0x80:0x00;
+        finRsvOp |= frame.isRsv1()?0x40:0x00;
+        finRsvOp |= frame.isRsv2()?0x20:0x00;
+        finRsvOp |= frame.isRsv3()?0x10:0x00;
+        finRsvOp |= frame.getOpCode() & 0x0F;
+
+        masked = frame.isMasked();
+        if (masked)
+        {
+            mask = frame.getMask();
+        }
+        else
+        {
+            mask = null;
+        }
+    }
+
+    protected void copyHeaders(WebSocketFrame copy)
+    {
+        finRsvOp = copy.finRsvOp;
+        masked = copy.masked;
+        mask = null;
+        if (copy.mask != null)
+            mask = Arrays.copyOf(copy.mask, copy.mask.length);
+    }
+
+    @Override
+    public boolean equals(Object obj)
+    {
+        if (this == obj)
+        {
+            return true;
+        }
+        if (obj == null)
+        {
+            return false;
+        }
+        if (getClass() != obj.getClass())
+        {
+            return false;
+        }
+        WebSocketFrame other = (WebSocketFrame)obj;
+        if (data == null)
+        {
+            if (other.data != null)
+            {
+                return false;
+            }
+        }
+        else if (!data.equals(other.data))
+        {
+            return false;
+        }
+        if (finRsvOp != other.finRsvOp)
+        {
+            return false;
+        }
+        if (!Arrays.equals(mask,other.mask))
+        {
+            return false;
+        }
+        if (masked != other.masked)
+        {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public byte[] getMask()
+    {
+        return mask;
+    }
+
+    @Override
+    public final byte getOpCode()
+    {
+        return (byte)(finRsvOp & 0x0F);
+    }
+
+    /**
+     * Get the payload ByteBuffer. possible null.
+     */
+    @Override
+    public ByteBuffer getPayload()
+    {
+        return data;
+    }
+
+    public String getPayloadAsUTF8()
+    {
+        return BufferUtil.toUTF8String(getPayload());
+    }
+
+    @Override
+    public int getPayloadLength()
+    {
+        if (data == null)
+        {
+            return 0;
+        }
+        return data.remaining();
+    }
+
+    @Override
+    public Type getType()
+    {
+        return Type.from(getOpCode());
+    }
+
+    @Override
+    public int hashCode()
+    {
+        final int prime = 31;
+        int result = 1;
+        result = (prime * result) + ((data == null)?0:data.hashCode());
+        result = (prime * result) + finRsvOp;
+        result = (prime * result) + Arrays.hashCode(mask);
+        return result;
+    }
+
+    @Override
+    public boolean hasPayload()
+    {
+        return ((data != null) && data.hasRemaining());
+    }
+
+    public abstract boolean isControlFrame();
+
+    public abstract boolean isDataFrame();
+
+    @Override
+    public boolean isFin()
+    {
+        return (byte)(finRsvOp & 0x80) != 0;
+    }
+
+    @Override
+    public boolean isLast()
+    {
+        return isFin();
+    }
+
+    @Override
+    public boolean isMasked()
+    {
+        return masked;
+    }
+
+    @Override
+    public boolean isRsv1()
+    {
+        return (byte)(finRsvOp & 0x40) != 0;
+    }
+
+    @Override
+    public boolean isRsv2()
+    {
+        return (byte)(finRsvOp & 0x20) != 0;
+    }
+
+    @Override
+    public boolean isRsv3()
+    {
+        return (byte)(finRsvOp & 0x10) != 0;
+    }
+
+    public void reset()
+    {
+        finRsvOp = (byte)0x80; // FIN (!RSV, opcode 0)
+        masked = false;
+        data = null;
+        mask = null;
+    }
+
+    public WebSocketFrame setFin(boolean fin)
+    {
+        // set bit 1
+        this.finRsvOp = (byte)((finRsvOp & 0x7F) | (fin?0x80:0x00));
+        return this;
+    }
+
+    public Frame setMask(byte[] maskingKey)
+    {
+        this.mask = maskingKey;
+        this.masked = (mask != null);
+        return this;
+    }
+
+    public Frame setMasked(boolean mask)
+    {
+        this.masked = mask;
+        return this;
+    }
+
+    protected WebSocketFrame setOpCode(byte op)
+    {
+        this.finRsvOp = (byte)((finRsvOp & 0xF0) | (op & 0x0F));
+        return this;
+    }
+
+    /**
+     * Set the data payload.
+     * <p>
+     * The provided buffer will be used as is, no copying of bytes performed.
+     * <p>
+     * The provided buffer should be flipped and ready to READ from.
+     * 
+     * @param buf
+     *            the bytebuffer to set
+     */
+    public WebSocketFrame setPayload(ByteBuffer buf)
+    {
+        data = buf;
+        return this;
+    }
+
+    public WebSocketFrame setRsv1(boolean rsv1)
+    {
+        // set bit 2
+        this.finRsvOp = (byte)((finRsvOp & 0xBF) | (rsv1?0x40:0x00));
+        return this;
+    }
+
+    public WebSocketFrame setRsv2(boolean rsv2)
+    {
+        // set bit 3
+        this.finRsvOp = (byte)((finRsvOp & 0xDF) | (rsv2?0x20:0x00));
+        return this;
+    }
+
+    public WebSocketFrame setRsv3(boolean rsv3)
+    {
+        // set bit 4
+        this.finRsvOp = (byte)((finRsvOp & 0xEF) | (rsv3?0x10:0x00));
+        return this;
+    }
+
+    @Override
+    public String toString()
+    {
+        StringBuilder b = new StringBuilder();
+        b.append(OpCode.name((byte)(finRsvOp & 0x0F)));
+        b.append('[');
+        b.append("len=").append(getPayloadLength());
+        b.append(",fin=").append((finRsvOp & 0x80) != 0);
+        b.append(",rsv=");
+        b.append(((finRsvOp & 0x40) != 0)?'1':'.');
+        b.append(((finRsvOp & 0x20) != 0)?'1':'.');
+        b.append(((finRsvOp & 0x10) != 0)?'1':'.');
+        b.append(",masked=").append(masked);
+        b.append(']');
+        return b.toString();
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketRemoteEndpoint.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketRemoteEndpoint.java
new file mode 100644
index 0000000..6d9b93d
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketRemoteEndpoint.java
@@ -0,0 +1,467 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.Future;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.BatchMode;
+import org.eclipse.jetty.websocket.api.RemoteEndpoint;
+import org.eclipse.jetty.websocket.api.WriteCallback;
+import org.eclipse.jetty.websocket.api.extensions.OutgoingFrames;
+import org.eclipse.jetty.websocket.common.BlockingWriteCallback.WriteBlocker;
+import org.eclipse.jetty.websocket.common.frames.BinaryFrame;
+import org.eclipse.jetty.websocket.common.frames.ContinuationFrame;
+import org.eclipse.jetty.websocket.common.frames.DataFrame;
+import org.eclipse.jetty.websocket.common.frames.PingFrame;
+import org.eclipse.jetty.websocket.common.frames.PongFrame;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
+import org.eclipse.jetty.websocket.common.io.FrameFlusher;
+import org.eclipse.jetty.websocket.common.io.FutureWriteCallback;
+
+/**
+ * Endpoint for Writing messages to the Remote websocket.
+ */
+public class WebSocketRemoteEndpoint implements RemoteEndpoint
+{
+    private enum MsgType
+    {
+        BLOCKING,
+        ASYNC,
+        STREAMING,
+        PARTIAL_TEXT,
+        PARTIAL_BINARY
+    }
+
+    private static final WriteCallback NOOP_CALLBACK = new WriteCallback()
+    {
+        @Override
+        public void writeSuccess()
+        {
+        }
+
+        @Override
+        public void writeFailed(Throwable x)
+        {
+        }
+    };
+
+    private static final Logger LOG = Log.getLogger(WebSocketRemoteEndpoint.class);
+
+    private final static int ASYNC_MASK = 0x0000FFFF;
+    private final static int BLOCK_MASK = 0x00010000;
+    private final static int STREAM_MASK = 0x00020000;
+    private final static int PARTIAL_TEXT_MASK = 0x00040000;
+    private final static int PARTIAL_BINARY_MASK = 0x00080000;
+
+    private final LogicalConnection connection;
+    private final OutgoingFrames outgoing;
+    private final AtomicInteger msgState = new AtomicInteger();
+    private final BlockingWriteCallback blocker = new BlockingWriteCallback();
+    private volatile BatchMode batchMode;
+
+    public WebSocketRemoteEndpoint(LogicalConnection connection, OutgoingFrames outgoing)
+    {
+        this(connection, outgoing, BatchMode.AUTO);
+    }
+
+    public WebSocketRemoteEndpoint(LogicalConnection connection, OutgoingFrames outgoing, BatchMode batchMode)
+    {
+        if (connection == null)
+        {
+            throw new IllegalArgumentException("LogicalConnection cannot be null");
+        }
+        this.connection = connection;
+        this.outgoing = outgoing;
+        this.batchMode = batchMode;
+    }
+
+    private void blockingWrite(WebSocketFrame frame) throws IOException
+    {
+        try(WriteBlocker b=blocker.acquireWriteBlocker())
+        {
+            uncheckedSendFrame(frame, b);
+            b.block();
+        }
+    }
+
+    private boolean lockMsg(MsgType type)
+    {
+        // Blocking -> BLOCKING  ; Async -> ASYNC     ; Partial -> PARTIAL_XXXX ; Stream -> STREAMING
+        // Blocking -> Pending!! ; Async -> BLOCKING  ; Partial -> Pending!!    ; Stream -> STREAMING 
+        // Blocking -> BLOCKING  ; Async -> ASYNC     ; Partial -> Pending!!    ; Stream -> STREAMING
+        // Blocking -> Pending!! ; Async -> STREAMING ; Partial -> Pending!!    ; Stream -> STREAMING
+        // Blocking -> Pending!! ; Async -> Pending!! ; Partial -> PARTIAL_TEXT ; Stream -> Pending!!
+        // Blocking -> Pending!! ; Async -> Pending!! ; Partial -> PARTIAL_BIN  ; Stream -> Pending!!
+
+        while (true)
+        {
+            int state = msgState.get();
+
+            switch (type)
+            {
+                case BLOCKING:
+                    if ((state & (PARTIAL_BINARY_MASK + PARTIAL_TEXT_MASK)) != 0)
+                        throw new IllegalStateException(String.format("Partial message pending %x for %s", state, type));
+                    if ((state & BLOCK_MASK) != 0)
+                        throw new IllegalStateException(String.format("Blocking message pending %x for %s", state, type));
+                    if (msgState.compareAndSet(state, state | BLOCK_MASK))
+                        return state == 0;
+                    break;
+
+                case ASYNC:
+                    if ((state & (PARTIAL_BINARY_MASK + PARTIAL_TEXT_MASK)) != 0)
+                        throw new IllegalStateException(String.format("Partial message pending %x for %s", state, type));
+                    if ((state & ASYNC_MASK) == ASYNC_MASK)
+                        throw new IllegalStateException(String.format("Too many async sends: %x", state));
+                    if (msgState.compareAndSet(state, state + 1))
+                        return state == 0;
+                    break;
+
+                case STREAMING:
+                    if ((state & (PARTIAL_BINARY_MASK + PARTIAL_TEXT_MASK)) != 0)
+                        throw new IllegalStateException(String.format("Partial message pending %x for %s", state, type));
+                    if ((state & STREAM_MASK) != 0)
+                        throw new IllegalStateException(String.format("Already streaming %x for %s", state, type));
+                    if (msgState.compareAndSet(state, state | STREAM_MASK))
+                        return state == 0;
+                    break;
+
+                case PARTIAL_BINARY:
+                    if (state == PARTIAL_BINARY_MASK)
+                        return false;
+                    if (state == 0)
+                    {
+                        if (msgState.compareAndSet(0, state | PARTIAL_BINARY_MASK))
+                            return true;
+                    }
+                    throw new IllegalStateException(String.format("Cannot send %s in state %x", type, state));
+
+                case PARTIAL_TEXT:
+                    if (state == PARTIAL_TEXT_MASK)
+                        return false;
+                    if (state == 0)
+                    {
+                        if (msgState.compareAndSet(0, state | PARTIAL_TEXT_MASK))
+                            return true;
+                    }
+                    throw new IllegalStateException(String.format("Cannot send %s in state %x", type, state));
+            }
+        }
+    }
+
+    private void unlockMsg(MsgType type)
+    {
+        while (true)
+        {
+            int state = msgState.get();
+
+            switch (type)
+            {
+                case BLOCKING:
+                    if ((state & BLOCK_MASK) == 0)
+                        throw new IllegalStateException(String.format("Not Blocking in state %x", state));
+                    if (msgState.compareAndSet(state, state & ~BLOCK_MASK))
+                        return;
+                    break;
+
+                case ASYNC:
+                    if ((state & ASYNC_MASK) == 0)
+                        throw new IllegalStateException(String.format("Not Async in %x", state));
+                    if (msgState.compareAndSet(state, state - 1))
+                        return;
+                    break;
+
+                case STREAMING:
+                    if ((state & STREAM_MASK) == 0)
+                        throw new IllegalStateException(String.format("Not Streaming in state %x", state));
+                    if (msgState.compareAndSet(state, state & ~STREAM_MASK))
+                        return;
+                    break;
+
+                case PARTIAL_BINARY:
+                    if (msgState.compareAndSet(PARTIAL_BINARY_MASK, 0))
+                        return;
+                    throw new IllegalStateException(String.format("Not Partial Binary in state %x", state));
+
+                case PARTIAL_TEXT:
+                    if (msgState.compareAndSet(PARTIAL_TEXT_MASK, 0))
+                        return;
+                    throw new IllegalStateException(String.format("Not Partial Text in state %x", state));
+
+            }
+        }
+    }
+
+
+    public InetSocketAddress getInetSocketAddress()
+    {
+        return connection.getRemoteAddress();
+    }
+
+    /**
+     * Internal
+     *
+     * @param frame the frame to write
+     * @return the future for the network write of the frame
+     */
+    private Future<Void> sendAsyncFrame(WebSocketFrame frame)
+    {
+        FutureWriteCallback future = new FutureWriteCallback();
+        uncheckedSendFrame(frame, future);
+        return future;
+    }
+
+    /**
+     * Blocking write of bytes.
+     */
+    @Override
+    public void sendBytes(ByteBuffer data) throws IOException
+    {
+        lockMsg(MsgType.BLOCKING);
+        try
+        {
+            connection.getIOState().assertOutputOpen();
+            if (LOG.isDebugEnabled())
+            {
+                LOG.debug("sendBytes with {}", BufferUtil.toDetailString(data));
+            }
+            blockingWrite(new BinaryFrame().setPayload(data));
+        }
+        finally
+        {
+            unlockMsg(MsgType.BLOCKING);
+        }
+    }
+
+    @Override
+    public Future<Void> sendBytesByFuture(ByteBuffer data)
+    {
+        lockMsg(MsgType.ASYNC);
+        try
+        {
+            if (LOG.isDebugEnabled())
+            {
+                LOG.debug("sendBytesByFuture with {}", BufferUtil.toDetailString(data));
+            }
+            return sendAsyncFrame(new BinaryFrame().setPayload(data));
+        }
+        finally
+        {
+            unlockMsg(MsgType.ASYNC);
+        }
+    }
+
+    @Override
+    public void sendBytes(ByteBuffer data, WriteCallback callback)
+    {
+        lockMsg(MsgType.ASYNC);
+        try
+        {
+            if (LOG.isDebugEnabled())
+            {
+                LOG.debug("sendBytes({}, {})", BufferUtil.toDetailString(data), callback);
+            }
+            uncheckedSendFrame(new BinaryFrame().setPayload(data), callback == null ? NOOP_CALLBACK : callback);
+        }
+        finally
+        {
+            unlockMsg(MsgType.ASYNC);
+        }
+    }
+
+    public void uncheckedSendFrame(WebSocketFrame frame, WriteCallback callback)
+    {
+        try
+        {
+            BatchMode batchMode = BatchMode.OFF;
+            if (frame.isDataFrame())
+                batchMode = getBatchMode();
+            connection.getIOState().assertOutputOpen();
+            outgoing.outgoingFrame(frame, callback, batchMode);
+        }
+        catch (IOException e)
+        {
+            callback.writeFailed(e);
+        }
+    }
+
+    @Override
+    public void sendPartialBytes(ByteBuffer fragment, boolean isLast) throws IOException
+    {
+        boolean first = lockMsg(MsgType.PARTIAL_BINARY);
+        try
+        {
+            if (LOG.isDebugEnabled())
+            {
+                LOG.debug("sendPartialBytes({}, {})", BufferUtil.toDetailString(fragment), isLast);
+            }
+            DataFrame frame = first ? new BinaryFrame() : new ContinuationFrame();
+            frame.setPayload(fragment);
+            frame.setFin(isLast);
+            blockingWrite(frame);
+        }
+        finally
+        {
+            if (isLast)
+                unlockMsg(MsgType.PARTIAL_BINARY);
+        }
+    }
+
+    @Override
+    public void sendPartialString(String fragment, boolean isLast) throws IOException
+    {
+        boolean first = lockMsg(MsgType.PARTIAL_TEXT);
+        try
+        {
+            if (LOG.isDebugEnabled())
+            {
+                LOG.debug("sendPartialString({}, {})", fragment, isLast);
+            }
+            DataFrame frame = first ? new TextFrame() : new ContinuationFrame();
+            frame.setPayload(BufferUtil.toBuffer(fragment, StandardCharsets.UTF_8));
+            frame.setFin(isLast);
+            blockingWrite(frame);
+        }
+        finally
+        {
+            if (isLast)
+                unlockMsg(MsgType.PARTIAL_TEXT);
+        }
+    }
+
+    @Override
+    public void sendPing(ByteBuffer applicationData) throws IOException
+    {
+        if (LOG.isDebugEnabled())
+        {
+            LOG.debug("sendPing with {}", BufferUtil.toDetailString(applicationData));
+        }
+        sendAsyncFrame(new PingFrame().setPayload(applicationData));
+    }
+
+    @Override
+    public void sendPong(ByteBuffer applicationData) throws IOException
+    {
+        if (LOG.isDebugEnabled())
+        {
+            LOG.debug("sendPong with {}", BufferUtil.toDetailString(applicationData));
+        }
+        sendAsyncFrame(new PongFrame().setPayload(applicationData));
+    }
+
+    @Override
+    public void sendString(String text) throws IOException
+    {
+        lockMsg(MsgType.BLOCKING);
+        try
+        {
+            WebSocketFrame frame = new TextFrame().setPayload(text);
+            if (LOG.isDebugEnabled())
+            {
+                LOG.debug("sendString with {}", BufferUtil.toDetailString(frame.getPayload()));
+            }
+            blockingWrite(frame);
+        }
+        finally
+        {
+            unlockMsg(MsgType.BLOCKING);
+        }
+    }
+
+    @Override
+    public Future<Void> sendStringByFuture(String text)
+    {
+        lockMsg(MsgType.ASYNC);
+        try
+        {
+            TextFrame frame = new TextFrame().setPayload(text);
+            if (LOG.isDebugEnabled())
+            {
+                LOG.debug("sendStringByFuture with {}", BufferUtil.toDetailString(frame.getPayload()));
+            }
+            return sendAsyncFrame(frame);
+        }
+        finally
+        {
+            unlockMsg(MsgType.ASYNC);
+        }
+    }
+
+    @Override
+    public void sendString(String text, WriteCallback callback)
+    {
+        lockMsg(MsgType.ASYNC);
+        try
+        {
+            TextFrame frame = new TextFrame().setPayload(text);
+            if (LOG.isDebugEnabled())
+            {
+                LOG.debug("sendString({},{})", BufferUtil.toDetailString(frame.getPayload()), callback);
+            }
+            uncheckedSendFrame(frame, callback == null ? NOOP_CALLBACK : callback);
+        }
+        finally
+        {
+            unlockMsg(MsgType.ASYNC);
+        }
+    }
+
+    @Override
+    public BatchMode getBatchMode()
+    {
+        return batchMode;
+    }
+
+    // Only the JSR needs to have this method exposed.
+    // In the Jetty implementation the batching is set
+    // at the moment of opening the session.
+    public void setBatchMode(BatchMode batchMode)
+    {
+        this.batchMode = batchMode;
+    }
+
+    @Override
+    public void flush() throws IOException
+    {
+        lockMsg(MsgType.ASYNC);
+        try (WriteBlocker b = blocker.acquireWriteBlocker())
+        {
+            uncheckedSendFrame(FrameFlusher.FLUSH_FRAME, b);
+            b.block();
+        }
+        finally
+        {
+            unlockMsg(MsgType.ASYNC);
+        }
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s@%x[batching=%b]", getClass().getSimpleName(), hashCode(), getBatchMode());
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSession.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSession.java
new file mode 100644
index 0000000..cfd5323
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSession.java
@@ -0,0 +1,523 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.URI;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Executor;
+
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.component.Dumpable;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.BatchMode;
+import org.eclipse.jetty.websocket.api.CloseStatus;
+import org.eclipse.jetty.websocket.api.RemoteEndpoint;
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.StatusCode;
+import org.eclipse.jetty.websocket.api.SuspendToken;
+import org.eclipse.jetty.websocket.api.UpgradeRequest;
+import org.eclipse.jetty.websocket.api.UpgradeResponse;
+import org.eclipse.jetty.websocket.api.WebSocketBehavior;
+import org.eclipse.jetty.websocket.api.WebSocketException;
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.api.extensions.ExtensionFactory;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.api.extensions.IncomingFrames;
+import org.eclipse.jetty.websocket.api.extensions.OutgoingFrames;
+import org.eclipse.jetty.websocket.common.events.EventDriver;
+import org.eclipse.jetty.websocket.common.io.IOState;
+import org.eclipse.jetty.websocket.common.io.IOState.ConnectionStateListener;
+
+ at ManagedObject("A Jetty WebSocket Session")
+public class WebSocketSession extends ContainerLifeCycle implements Session, IncomingFrames, ConnectionStateListener
+{
+    private static final Logger LOG = Log.getLogger(WebSocketSession.class);
+    private final URI requestURI;
+    private final EventDriver websocket;
+    private final LogicalConnection connection;
+    private final SessionListener[] sessionListeners;
+    private final Executor executor;
+    private ClassLoader classLoader;
+    private ExtensionFactory extensionFactory;
+    private String protocolVersion;
+    private Map<String, String[]> parameterMap = new HashMap<>();
+    private WebSocketRemoteEndpoint remote;
+    private IncomingFrames incomingHandler;
+    private OutgoingFrames outgoingHandler;
+    private WebSocketPolicy policy;
+    private UpgradeRequest upgradeRequest;
+    private UpgradeResponse upgradeResponse;
+
+    public WebSocketSession(URI requestURI, EventDriver websocket, LogicalConnection connection, SessionListener... sessionListeners)
+    {
+        if (requestURI == null)
+        {
+            throw new RuntimeException("Request URI cannot be null");
+        }
+
+        this.classLoader = Thread.currentThread().getContextClassLoader();
+        this.requestURI = requestURI;
+        this.websocket = websocket;
+        this.connection = connection;
+        this.sessionListeners = sessionListeners;
+        this.executor = connection.getExecutor();
+        this.outgoingHandler = connection;
+        this.incomingHandler = websocket;
+        this.connection.getIOState().addListener(this);
+    }
+
+    @Override
+    public void close()
+    {
+        this.close(StatusCode.NORMAL,null);
+    }
+
+    @Override
+    public void close(CloseStatus closeStatus)
+    {
+        this.close(closeStatus.getCode(),closeStatus.getPhrase());
+    }
+
+    @Override
+    public void close(int statusCode, String reason)
+    {
+        connection.close(statusCode,CloseStatus.trimMaxReasonLength(reason));
+    }
+
+    /**
+     * Harsh disconnect
+     */
+    @Override
+    public void disconnect()
+    {
+        connection.disconnect();
+
+        // notify of harsh disconnect
+        notifyClose(StatusCode.NO_CLOSE,"Harsh disconnect");
+    }
+
+    public void dispatch(Runnable runnable)
+    {
+        executor.execute(runnable);
+    }
+
+    @Override
+    public void dump(Appendable out, String indent) throws IOException
+    {
+        dumpThis(out);
+        out.append(indent).append(" +- incomingHandler : ");
+        if (incomingHandler instanceof Dumpable)
+        {
+            ((Dumpable)incomingHandler).dump(out,indent + "    ");
+        }
+        else
+        {
+            out.append(incomingHandler.toString()).append(System.lineSeparator());
+        }
+
+        out.append(indent).append(" +- outgoingHandler : ");
+        if (outgoingHandler instanceof Dumpable)
+        {
+            ((Dumpable)outgoingHandler).dump(out,indent + "    ");
+        }
+        else
+        {
+            out.append(outgoingHandler.toString()).append(System.lineSeparator());
+        }
+    }
+
+    @Override
+    public boolean equals(Object obj)
+    {
+        if (this == obj)
+        {
+            return true;
+        }
+        if (obj == null)
+        {
+            return false;
+        }
+        if (getClass() != obj.getClass())
+        {
+            return false;
+        }
+        WebSocketSession other = (WebSocketSession)obj;
+        if (connection == null)
+        {
+            if (other.connection != null)
+            {
+                return false;
+            }
+        }
+        else if (!connection.equals(other.connection))
+        {
+            return false;
+        }
+        return true;
+    }
+
+    public ByteBufferPool getBufferPool()
+    {
+        return this.connection.getBufferPool();
+    }
+    
+    public ClassLoader getClassLoader()
+    {
+        return this.getClass().getClassLoader();
+    }
+
+    public LogicalConnection getConnection()
+    {
+        return connection;
+    }
+
+    public ExtensionFactory getExtensionFactory()
+    {
+        return extensionFactory;
+    }
+
+    /**
+     * The idle timeout in milliseconds
+     */
+    @Override
+    public long getIdleTimeout()
+    {
+        return connection.getMaxIdleTimeout();
+    }
+
+    @ManagedAttribute(readonly = true)
+    public IncomingFrames getIncomingHandler()
+    {
+        return incomingHandler;
+    }
+
+    @Override
+    public InetSocketAddress getLocalAddress()
+    {
+        return connection.getLocalAddress();
+    }
+
+    @ManagedAttribute(readonly = true)
+    public OutgoingFrames getOutgoingHandler()
+    {
+        return outgoingHandler;
+    }
+
+    @Override
+    public WebSocketPolicy getPolicy()
+    {
+        return policy;
+    }
+
+    @Override
+    public String getProtocolVersion()
+    {
+        return protocolVersion;
+    }
+
+    @Override
+    public RemoteEndpoint getRemote()
+    {
+        ConnectionState state = connection.getIOState().getConnectionState();
+
+        if ((state == ConnectionState.OPEN) || (state == ConnectionState.CONNECTED))
+        {
+            return remote;
+        }
+
+        throw new WebSocketException("RemoteEndpoint unavailable, current state [" + state + "], expecting [OPEN or CONNECTED]");
+    }
+
+    @Override
+    public InetSocketAddress getRemoteAddress()
+    {
+        return remote.getInetSocketAddress();
+    }
+
+    public URI getRequestURI()
+    {
+        return requestURI;
+    }
+
+    @Override
+    public UpgradeRequest getUpgradeRequest()
+    {
+        return this.upgradeRequest;
+    }
+
+    @Override
+    public UpgradeResponse getUpgradeResponse()
+    {
+        return this.upgradeResponse;
+    }
+
+    @Override
+    public int hashCode()
+    {
+        final int prime = 31;
+        int result = 1;
+        result = (prime * result) + ((connection == null)?0:connection.hashCode());
+        return result;
+    }
+
+    /**
+     * Incoming Errors from Parser
+     */
+    @Override
+    public void incomingError(Throwable t)
+    {
+        if (connection.getIOState().isInputAvailable())
+        {
+            // Forward Errors to User WebSocket Object
+            websocket.incomingError(t);
+        }
+    }
+
+    /**
+     * Incoming Raw Frames from Parser
+     */
+    @Override
+    public void incomingFrame(Frame frame)
+    {
+        if (connection.getIOState().isInputAvailable())
+        {
+            // Forward Frames Through Extension List
+            incomingHandler.incomingFrame(frame);
+        }
+    }
+
+    @Override
+    public boolean isOpen()
+    {
+        if (this.connection == null)
+        {
+            return false;
+        }
+        return this.connection.isOpen();
+    }
+
+    @Override
+    public boolean isSecure()
+    {
+        if (upgradeRequest == null)
+        {
+            throw new IllegalStateException("No valid UpgradeRequest yet");
+        }
+
+        URI requestURI = upgradeRequest.getRequestURI();
+
+        return "wss".equalsIgnoreCase(requestURI.getScheme());
+    }
+
+    public void notifyClose(int statusCode, String reason)
+    {
+        if (LOG.isDebugEnabled())
+        {
+            LOG.debug("notifyClose({},{})",statusCode,reason);
+        }
+        websocket.onClose(new CloseInfo(statusCode,reason));
+    }
+
+    public void notifyError(Throwable cause)
+    {
+        incomingError(cause);
+    }
+
+    @SuppressWarnings("incomplete-switch")
+    @Override
+    public void onConnectionStateChange(ConnectionState state)
+    {
+        switch (state)
+        {
+            case CLOSED:
+                // notify session listeners
+                for (SessionListener listener : sessionListeners)
+                {
+                    try
+                    {
+                        if (LOG.isDebugEnabled())
+                            LOG.debug("{}.onSessionClosed()",listener.getClass().getSimpleName());
+                        listener.onSessionClosed(this);
+                    }
+                    catch (Throwable t)
+                    {
+                        LOG.ignore(t);
+                    }
+                }
+                IOState ioState = this.connection.getIOState();
+                CloseInfo close = ioState.getCloseInfo();
+                // confirmed close of local endpoint
+                notifyClose(close.getStatusCode(),close.getReason());
+                break;
+            case OPEN:
+                // notify session listeners
+                for (SessionListener listener : sessionListeners)
+                {
+                    try
+                    {
+                        listener.onSessionOpened(this);
+                    }
+                    catch (Throwable t)
+                    {
+                        LOG.ignore(t);
+                    }
+                }
+                break;
+        }
+    }
+
+    /**
+     * Open/Activate the session
+     */
+    public void open()
+    {
+        if (remote != null)
+        {
+            // already opened
+            return;
+        }
+        
+        ClassLoader old = Thread.currentThread().getContextClassLoader();
+        try {
+            Thread.currentThread().setContextClassLoader(classLoader);
+
+            // Upgrade success
+            connection.getIOState().onConnected();
+    
+            // Connect remote
+            remote = new WebSocketRemoteEndpoint(connection,outgoingHandler,getBatchMode());
+
+            // Open WebSocket
+            websocket.openSession(this);
+
+            // Open connection
+            connection.getIOState().onOpened();
+
+            if (LOG.isDebugEnabled())
+            {
+                LOG.debug("open -> {}",dump());
+            }
+        }
+        catch (Throwable t)
+        {
+            // Exception on end-user WS-Endpoint.
+            // Fast-fail & close connection with reason.
+            int statusCode = StatusCode.SERVER_ERROR;
+            if(policy.getBehavior() == WebSocketBehavior.CLIENT)
+            {
+                statusCode = StatusCode.POLICY_VIOLATION;
+            }
+            
+            close(statusCode,t.getMessage());
+        }
+        finally
+        {
+            Thread.currentThread().setContextClassLoader(old);
+        }
+    }
+
+    public void setExtensionFactory(ExtensionFactory extensionFactory)
+    {
+        this.extensionFactory = extensionFactory;
+    }
+
+    /**
+     * Set the timeout in milliseconds
+     */
+    @Override
+    public void setIdleTimeout(long ms)
+    {
+        connection.setMaxIdleTimeout(ms);
+    }
+
+    public void setOutgoingHandler(OutgoingFrames outgoing)
+    {
+        this.outgoingHandler = outgoing;
+    }
+
+    public void setPolicy(WebSocketPolicy policy)
+    {
+        this.policy = policy;
+    }
+
+    public void setUpgradeRequest(UpgradeRequest request)
+    {
+        this.upgradeRequest = request;
+        this.protocolVersion = request.getProtocolVersion();
+        this.parameterMap.clear();
+        if (request.getParameterMap() != null)
+        {
+            for (Map.Entry<String, List<String>> entry : request.getParameterMap().entrySet())
+            {
+                List<String> values = entry.getValue();
+                if (values != null)
+                {
+                    this.parameterMap.put(entry.getKey(),values.toArray(new String[values.size()]));
+                }
+                else
+                {
+                    this.parameterMap.put(entry.getKey(),new String[0]);
+                }
+            }
+        }
+    }
+
+    public void setUpgradeResponse(UpgradeResponse response)
+    {
+        this.upgradeResponse = response;
+    }
+
+    @Override
+    public SuspendToken suspend()
+    {
+        return connection.suspend();
+    }
+
+    /**
+     * @return the default (initial) value for the batching mode.
+     */
+    public BatchMode getBatchMode()
+    {
+        return BatchMode.AUTO;
+    }
+
+    @Override
+    public String toString()
+    {
+        StringBuilder builder = new StringBuilder();
+        builder.append("WebSocketSession[");
+        builder.append("websocket=").append(websocket);
+        builder.append(",behavior=").append(policy.getBehavior());
+        builder.append(",connection=").append(connection);
+        builder.append(",remote=").append(remote);
+        builder.append(",incoming=").append(incomingHandler);
+        builder.append(",outgoing=").append(outgoingHandler);
+        builder.append("]");
+        return builder.toString();
+    }
+
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSessionFactory.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSessionFactory.java
new file mode 100644
index 0000000..24887cf
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSessionFactory.java
@@ -0,0 +1,50 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common;
+
+import java.net.URI;
+
+import org.eclipse.jetty.websocket.common.events.EventDriver;
+import org.eclipse.jetty.websocket.common.events.JettyAnnotatedEventDriver;
+import org.eclipse.jetty.websocket.common.events.JettyListenerEventDriver;
+
+/**
+ * Default Session factory, creating WebSocketSession objects.
+ */
+public class WebSocketSessionFactory implements SessionFactory
+{
+    private final SessionListener[] listeners;
+
+    public WebSocketSessionFactory(SessionListener... sessionListeners)
+    {
+        listeners = sessionListeners;
+    }
+
+    @Override
+    public boolean supports(EventDriver websocket)
+    {
+        return (websocket instanceof JettyAnnotatedEventDriver) || (websocket instanceof JettyListenerEventDriver);
+    }
+
+    @Override
+    public WebSocketSession createSession(URI requestURI, EventDriver websocket, LogicalConnection connection)
+    {
+        return new WebSocketSession(requestURI,websocket,connection,listeners);
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/AbstractEventDriver.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/AbstractEventDriver.java
new file mode 100644
index 0000000..d446264
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/AbstractEventDriver.java
@@ -0,0 +1,252 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.events;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Utf8Appendable.NotUtf8Exception;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.CloseException;
+import org.eclipse.jetty.websocket.api.StatusCode;
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.api.extensions.IncomingFrames;
+import org.eclipse.jetty.websocket.common.CloseInfo;
+import org.eclipse.jetty.websocket.common.OpCode;
+import org.eclipse.jetty.websocket.common.WebSocketSession;
+import org.eclipse.jetty.websocket.common.frames.CloseFrame;
+import org.eclipse.jetty.websocket.common.message.MessageAppender;
+
+/**
+ * EventDriver is the main interface between the User's WebSocket POJO and the internal jetty implementation of WebSocket.
+ */
+public abstract class AbstractEventDriver implements IncomingFrames, EventDriver
+{
+    private static final Logger LOG = Log.getLogger(AbstractEventDriver.class);
+    protected final Logger TARGET_LOG;
+    protected final WebSocketPolicy policy;
+    protected final Object websocket;
+    protected WebSocketSession session;
+    protected MessageAppender activeMessage;
+
+    public AbstractEventDriver(WebSocketPolicy policy, Object websocket)
+    {
+        this.policy = policy;
+        this.websocket = websocket;
+        this.TARGET_LOG = Log.getLogger(websocket.getClass());
+    }
+
+    protected void appendMessage(ByteBuffer buffer, boolean fin) throws IOException
+    {
+        activeMessage.appendFrame(buffer,fin);
+
+        if (fin)
+        {
+            activeMessage.messageComplete();
+            activeMessage = null;
+        }
+    }
+
+    protected void dispatch(Runnable runnable)
+    {
+        session.dispatch(runnable);
+    }
+
+    @Override
+    public WebSocketPolicy getPolicy()
+    {
+        return policy;
+    }
+
+    @Override
+    public WebSocketSession getSession()
+    {
+        return session;
+    }
+
+    @Override
+    public final void incomingError(Throwable e)
+    {
+        if (LOG.isDebugEnabled())
+        {
+            LOG.debug("incomingError(" + e.getClass().getName() + ")",e);
+        }
+
+        onError(e);
+    }
+
+    @Override
+    public void incomingFrame(Frame frame)
+    {
+        if (LOG.isDebugEnabled())
+        {
+            LOG.debug("incomingFrame({})",frame);
+        }
+
+        try
+        {
+            onFrame(frame);
+
+            byte opcode = frame.getOpCode();
+            switch (opcode)
+            {
+                case OpCode.CLOSE:
+                {
+                    boolean validate = true;
+                    CloseFrame closeframe = (CloseFrame)frame;
+                    CloseInfo close = new CloseInfo(closeframe,validate);
+
+                    // process handshake
+                    session.getConnection().getIOState().onCloseRemote(close);
+
+                    return;
+                }
+                case OpCode.PING:
+                {
+                    if (LOG.isDebugEnabled())
+                    {
+                        LOG.debug("PING: {}",BufferUtil.toDetailString(frame.getPayload()));
+                    }
+                    ByteBuffer pongBuf;
+                    if (frame.hasPayload())
+                    {
+                        pongBuf = ByteBuffer.allocate(frame.getPayload().remaining());
+                        BufferUtil.put(frame.getPayload().slice(),pongBuf);
+                        BufferUtil.flipToFlush(pongBuf,0);
+                    }
+                    else
+                    {
+                        pongBuf = ByteBuffer.allocate(0);
+                    }
+                    onPing(frame.getPayload());
+                    session.getRemote().sendPong(pongBuf);
+                    break;
+                }
+                case OpCode.PONG:
+                {
+                    if (LOG.isDebugEnabled())
+                    {
+                        LOG.debug("PONG: {}",BufferUtil.toDetailString(frame.getPayload()));
+                    }
+                    onPong(frame.getPayload());
+                    break;
+                }
+                case OpCode.BINARY:
+                {
+                    onBinaryFrame(frame.getPayload(),frame.isFin());
+                    return;
+                }
+                case OpCode.TEXT:
+                {
+                    onTextFrame(frame.getPayload(),frame.isFin());
+                    return;
+                }
+                case OpCode.CONTINUATION:
+                {
+                    onContinuationFrame(frame.getPayload(),frame.isFin());
+                    return;
+                }
+                default:
+                {
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("Unhandled OpCode: {}",opcode);
+                }
+            }
+        }
+        catch (NotUtf8Exception e)
+        {
+            terminateConnection(StatusCode.BAD_PAYLOAD,e.getMessage());
+        }
+        catch (CloseException e)
+        {
+            terminateConnection(e.getStatusCode(),e.getMessage());
+        }
+        catch (Throwable t)
+        {
+            unhandled(t);
+        }
+    }
+
+    @Override
+    public void onContinuationFrame(ByteBuffer buffer, boolean fin) throws IOException
+    {
+        if (activeMessage == null)
+        {
+            throw new IOException("Out of order Continuation frame encountered");
+        }
+
+        appendMessage(buffer,fin);
+    }
+
+    @Override
+    public void onPong(ByteBuffer buffer)
+    {
+        /* TODO: provide annotation in future */
+    }
+    
+    @Override
+    public void onPing(ByteBuffer buffer)
+    {
+        /* TODO: provide annotation in future */
+    }
+
+    @Override
+    public void openSession(WebSocketSession session)
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("openSession({})",session);
+        this.session = session;
+        try
+        {
+            this.onConnect();
+        }
+        catch (Throwable t)
+        {
+            unhandled(t);
+            throw t;
+        }
+    }
+
+    protected void terminateConnection(int statusCode, String rawreason)
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("terminateConnection({},{})",statusCode,rawreason);
+        session.close(statusCode,CloseFrame.truncate(rawreason));
+    }
+
+    private void unhandled(Throwable t)
+    {
+        TARGET_LOG.warn("Unhandled Error (closing connection)",t);
+        onError(t);
+
+        // Unhandled Error, close the connection.
+        switch (policy.getBehavior())
+        {
+            case SERVER:
+                terminateConnection(StatusCode.SERVER_ERROR,t.getClass().getSimpleName());
+                break;
+            case CLIENT:
+                terminateConnection(StatusCode.POLICY_VIOLATION,t.getClass().getSimpleName());
+                break;
+        }
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/EventDriver.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/EventDriver.java
new file mode 100644
index 0000000..e4d4d83
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/EventDriver.java
@@ -0,0 +1,65 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.events;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.api.extensions.IncomingFrames;
+import org.eclipse.jetty.websocket.common.CloseInfo;
+import org.eclipse.jetty.websocket.common.WebSocketSession;
+
+public interface EventDriver extends IncomingFrames
+{
+    public WebSocketPolicy getPolicy();
+
+    public WebSocketSession getSession();
+
+    public void onBinaryFrame(ByteBuffer buffer, boolean fin) throws IOException;
+
+    public void onBinaryMessage(byte[] data);
+
+    public void onClose(CloseInfo close);
+
+    public void onConnect();
+
+    public void onContinuationFrame(ByteBuffer buffer, boolean fin) throws IOException;
+
+    public void onError(Throwable t);
+
+    public void onFrame(Frame frame);
+
+    public void onInputStream(InputStream stream) throws IOException;
+
+    public void onPing(ByteBuffer buffer);
+    
+    public void onPong(ByteBuffer buffer);
+
+    public void onReader(Reader reader) throws IOException;
+
+    public void onTextFrame(ByteBuffer buffer, boolean fin) throws IOException;
+
+    public void onTextMessage(String message);
+
+    public void openSession(WebSocketSession session);
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/EventDriverFactory.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/EventDriverFactory.java
new file mode 100644
index 0000000..fa33843
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/EventDriverFactory.java
@@ -0,0 +1,147 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.events;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.InvalidWebSocketException;
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+
+/**
+ * Create EventDriver implementations.
+ */
+public class EventDriverFactory
+{
+    private static final Logger LOG = Log.getLogger(EventDriverFactory.class);
+    private final WebSocketPolicy policy;
+    private final List<EventDriverImpl> implementations;
+
+    public EventDriverFactory(WebSocketPolicy policy)
+    {
+        this.policy = policy;
+        this.implementations = new ArrayList<>();
+
+        addImplementation(new JettyListenerImpl());
+        addImplementation(new JettyAnnotatedImpl());
+    }
+
+    public void addImplementation(EventDriverImpl impl)
+    {
+        if (implementations.contains(impl))
+        {
+            LOG.warn("Ignoring attempt to add duplicate EventDriverImpl: " + impl);
+            return;
+        }
+
+        implementations.add(impl);
+    }
+
+    public void clearImplementations()
+    {
+        this.implementations.clear();
+    }
+
+    protected String getClassName(Object websocket)
+    {
+        return websocket.getClass().getName();
+    }
+
+    public List<EventDriverImpl> getImplementations()
+    {
+        return implementations;
+    }
+
+    public boolean removeImplementation(EventDriverImpl impl)
+    {
+        return this.implementations.remove(impl);
+    }
+
+    @Override
+    public String toString()
+    {
+        StringBuilder msg = new StringBuilder();
+        msg.append(this.getClass().getSimpleName());
+        msg.append("[implementations=[");
+        boolean delim = false;
+        for (EventDriverImpl impl : implementations)
+        {
+            if (delim)
+            {
+                msg.append(',');
+            }
+            msg.append(impl.toString());
+            delim = true;
+        }
+        msg.append("]");
+        return msg.toString();
+    }
+
+    /**
+     * Wrap the given WebSocket object instance in a suitable EventDriver
+     * 
+     * @param websocket
+     *            the websocket instance to wrap. Must either implement {@link WebSocketListener} or be annotated with {@link WebSocket &#064WebSocket}
+     * @return appropriate EventDriver for this websocket instance.
+     */
+    public EventDriver wrap(Object websocket)
+    {
+        if (websocket == null)
+        {
+            throw new InvalidWebSocketException("null websocket object");
+        }
+
+        for (EventDriverImpl impl : implementations)
+        {
+            if (impl.supports(websocket))
+            {
+                try
+                {
+                    return impl.create(websocket,policy.clonePolicy());
+                }
+                catch (Throwable e)
+                {
+                    throw new InvalidWebSocketException("Unable to create websocket",e);
+                }
+            }
+        }
+
+        // Create a clear error message for the developer
+        StringBuilder err = new StringBuilder();
+        err.append(getClassName(websocket));
+        err.append(" is not a valid WebSocket object.");
+        err.append("  Object must obey one of the following rules: ");
+
+        int len = implementations.size();
+        for (int i = 0; i < len; i++)
+        {
+            EventDriverImpl impl = implementations.get(i);
+            if (i > 0)
+            {
+                err.append(" or ");
+            }
+            err.append("\n(").append(i + 1).append(") ");
+            err.append(impl.describeRule());
+        }
+
+        throw new InvalidWebSocketException(err.toString());
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/EventDriverImpl.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/EventDriverImpl.java
new file mode 100644
index 0000000..18a1c63
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/EventDriverImpl.java
@@ -0,0 +1,58 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.events;
+
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+
+/**
+ * A specific implementation of a EventDriver.
+ */
+public interface EventDriverImpl
+{
+    /**
+     * Create the EventDriver based on this implementation.
+     * 
+     * @param websocket
+     *            the websocket to wrap
+     * @param policy
+     *            the policy to use
+     * @return the created EventDriver
+     * @throws Throwable
+     *             if unable to create the EventDriver
+     */
+    EventDriver create(Object websocket, WebSocketPolicy policy) throws Throwable;
+
+    /**
+     * human readable string describing the rule that would support this EventDriver.
+     * <p>
+     * Used to help developer with possible object annotations, listeners, or base classes.
+     * 
+     * @return the human readable description of this event driver rule(s).
+     */
+    String describeRule();
+
+    /**
+     * Test for if this implementation can support the provided websocket.
+     * 
+     * @param websocket
+     *            the possible websocket to test
+     * @return true if implementation can support it, false if otherwise.
+     */
+    boolean supports(Object websocket);
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/JettyAnnotatedEventDriver.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/JettyAnnotatedEventDriver.java
new file mode 100644
index 0000000..45dcaf2
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/JettyAnnotatedEventDriver.java
@@ -0,0 +1,234 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.events;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.api.annotations.WebSocket;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.common.CloseInfo;
+import org.eclipse.jetty.websocket.common.message.MessageAppender;
+import org.eclipse.jetty.websocket.common.message.MessageInputStream;
+import org.eclipse.jetty.websocket.common.message.MessageReader;
+import org.eclipse.jetty.websocket.common.message.SimpleBinaryMessage;
+import org.eclipse.jetty.websocket.common.message.SimpleTextMessage;
+
+/**
+ * Handler for Annotated User WebSocket objects.
+ */
+public class JettyAnnotatedEventDriver extends AbstractEventDriver
+{
+    private final JettyAnnotatedMetadata events;
+    private boolean hasCloseBeenCalled = false;
+
+    public JettyAnnotatedEventDriver(WebSocketPolicy policy, Object websocket, JettyAnnotatedMetadata events)
+    {
+        super(policy,websocket);
+        this.events = events;
+
+        WebSocket anno = websocket.getClass().getAnnotation(WebSocket.class);
+        // Setup the policy
+        if (anno.maxTextMessageSize() > 0)
+        {
+            this.policy.setMaxTextMessageSize(anno.maxTextMessageSize());
+        }
+        if (anno.maxBinaryMessageSize() > 0)
+        {
+            this.policy.setMaxBinaryMessageSize(anno.maxBinaryMessageSize());
+        }
+        if (anno.inputBufferSize() > 0)
+        {
+            this.policy.setInputBufferSize(anno.inputBufferSize());
+        }
+        if (anno.maxIdleTime() > 0)
+        {
+            this.policy.setIdleTimeout(anno.maxIdleTime());
+        }
+    }
+
+    @Override
+    public void onBinaryFrame(ByteBuffer buffer, boolean fin) throws IOException
+    {
+        if (events.onBinary == null)
+        {
+            // not interested in binary events
+            return;
+        }
+
+        if (activeMessage == null)
+        {
+            if (events.onBinary.isStreaming())
+            {
+                activeMessage = new MessageInputStream();
+                final MessageAppender msg = activeMessage;
+                dispatch(new Runnable()
+                {
+                    @Override
+                    public void run()
+                    {
+                        try
+                        {
+                            events.onBinary.call(websocket,session,msg);
+                        }
+                        catch (Throwable t)
+                        {
+                            // dispatched calls need to be reported
+                            onError(t);
+                        }
+                    }
+                });
+            }
+            else
+            {
+                activeMessage = new SimpleBinaryMessage(this);
+            }
+        }
+
+        appendMessage(buffer,fin);
+    }
+
+    @Override
+    public void onBinaryMessage(byte[] data)
+    {
+        if (events.onBinary != null)
+        {
+            events.onBinary.call(websocket,session,data,0,data.length);
+        }
+    }
+
+    @Override
+    public void onClose(CloseInfo close)
+    {
+        if (hasCloseBeenCalled)
+        {
+            // avoid duplicate close events (possible when using harsh Session.disconnect())
+            return;
+        }
+        hasCloseBeenCalled = true;
+        if (events.onClose != null)
+        {
+            events.onClose.call(websocket,session,close.getStatusCode(),close.getReason());
+        }
+    }
+
+    @Override
+    public void onConnect()
+    {
+        if (events.onConnect != null)
+        {
+            events.onConnect.call(websocket,session);
+        }
+    }
+
+    @Override
+    public void onError(Throwable cause)
+    {
+        if (events.onError != null)
+        {
+            events.onError.call(websocket,session,cause);
+        }
+    }
+
+    @Override
+    public void onFrame(Frame frame)
+    {
+        if (events.onFrame != null)
+        {
+            events.onFrame.call(websocket,session,frame);
+        }
+    }
+
+    @Override
+    public void onInputStream(InputStream stream)
+    {
+        if (events.onBinary != null)
+        {
+            events.onBinary.call(websocket,session,stream);
+        }
+    }
+
+    @Override
+    public void onReader(Reader reader)
+    {
+        if (events.onText != null)
+        {
+            events.onText.call(websocket,session,reader);
+        }
+    }
+
+    @Override
+    public void onTextFrame(ByteBuffer buffer, boolean fin) throws IOException
+    {
+        if (events.onText == null)
+        {
+            // not interested in text events
+            return;
+        }
+
+        if (activeMessage == null)
+        {
+            if (events.onText.isStreaming())
+            {
+                activeMessage = new MessageReader(new MessageInputStream());
+                final MessageAppender msg = activeMessage;
+                dispatch(new Runnable()
+                {
+                    @Override
+                    public void run()
+                    {
+                        try
+                        {
+                            events.onText.call(websocket,session,msg);
+                        }
+                        catch (Throwable t)
+                        {
+                            // dispatched calls need to be reported
+                            onError(t);
+                        }
+                    }
+                });
+            }
+            else
+            {
+                activeMessage = new SimpleTextMessage(this);
+            }
+        }
+
+        appendMessage(buffer,fin);
+    }
+
+    @Override
+    public void onTextMessage(String message)
+    {
+        if (events.onText != null)
+        {
+            events.onText.call(websocket,session,message);
+        }
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s[%s]", this.getClass().getSimpleName(), websocket);
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/JettyAnnotatedImpl.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/JettyAnnotatedImpl.java
new file mode 100644
index 0000000..d0865cb
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/JettyAnnotatedImpl.java
@@ -0,0 +1,65 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.events;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.api.annotations.WebSocket;
+
+public class JettyAnnotatedImpl implements EventDriverImpl
+{
+    private ConcurrentHashMap<Class<?>, JettyAnnotatedMetadata> cache = new ConcurrentHashMap<>();
+
+    @Override
+    public EventDriver create(Object websocket, WebSocketPolicy policy)
+    {
+        Class<?> websocketClass = websocket.getClass();
+        synchronized (this)
+        {
+            JettyAnnotatedMetadata metadata = cache.get(websocketClass);
+            if (metadata == null)
+            {
+                JettyAnnotatedScanner scanner = new JettyAnnotatedScanner();
+                metadata = scanner.scan(websocketClass);
+                cache.put(websocketClass,metadata);
+            }
+            return new JettyAnnotatedEventDriver(policy,websocket,metadata);
+        }
+    }
+
+    @Override
+    public String describeRule()
+    {
+        return "class is annotated with @" + WebSocket.class.getName();
+    }
+
+    @Override
+    public boolean supports(Object websocket)
+    {
+        WebSocket anno = websocket.getClass().getAnnotation(WebSocket.class);
+        return (anno != null);
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s [cache.count=%d]",this.getClass().getSimpleName(),cache.size());
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/JettyAnnotatedMetadata.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/JettyAnnotatedMetadata.java
new file mode 100644
index 0000000..33059a7
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/JettyAnnotatedMetadata.java
@@ -0,0 +1,53 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.events;
+
+import org.eclipse.jetty.websocket.common.events.annotated.CallableMethod;
+import org.eclipse.jetty.websocket.common.events.annotated.OptionalSessionCallableMethod;
+
+public class JettyAnnotatedMetadata
+{
+    /** @OnWebSocketConnect () */
+    public CallableMethod onConnect;
+    /** @OnWebSocketMessage (byte[], or ByteBuffer, or InputStream) */
+    public OptionalSessionCallableMethod onBinary;
+    /** @OnWebSocketMessage (String, or Reader) */
+    public OptionalSessionCallableMethod onText;
+    /** @OnWebSocketFrame (Frame) */
+    public OptionalSessionCallableMethod onFrame;
+    /** @OnWebSocketError (Throwable) */
+    public OptionalSessionCallableMethod onError;
+    /** @OnWebSocketClose (Frame) */
+    public OptionalSessionCallableMethod onClose;
+
+    @Override
+    public String toString()
+    {
+        StringBuilder s = new StringBuilder();
+        s.append("JettyPojoMetadata[");
+        s.append("onConnect=").append(onConnect);
+        s.append(",onBinary=").append(onBinary);
+        s.append(",onText=").append(onText);
+        s.append(",onFrame=").append(onFrame);
+        s.append(",onError=").append(onError);
+        s.append(",onClose=").append(onClose);
+        s.append("]");
+        return s.toString();
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/JettyAnnotatedScanner.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/JettyAnnotatedScanner.java
new file mode 100644
index 0000000..0130914
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/JettyAnnotatedScanner.java
@@ -0,0 +1,171 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.events;
+
+import java.io.InputStream;
+import java.io.Reader;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketFrame;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.common.events.annotated.AbstractMethodAnnotationScanner;
+import org.eclipse.jetty.websocket.common.events.annotated.CallableMethod;
+import org.eclipse.jetty.websocket.common.events.annotated.InvalidSignatureException;
+import org.eclipse.jetty.websocket.common.events.annotated.OptionalSessionCallableMethod;
+
+public class JettyAnnotatedScanner extends AbstractMethodAnnotationScanner<JettyAnnotatedMetadata>
+{
+    private static final Logger LOG = Log.getLogger(JettyAnnotatedScanner.class);
+
+    /**
+     * Parameter list for @OnWebSocketMessage (Binary mode)
+     */
+    private static final ParamList validBinaryParams;
+
+    /**
+     * Parameter list for @OnWebSocketConnect
+     */
+    private static final ParamList validConnectParams;
+
+    /**
+     * Parameter list for @OnWebSocketClose
+     */
+    private static final ParamList validCloseParams;
+
+    /**
+     * Parameter list for @OnWebSocketError
+     */
+    private static final ParamList validErrorParams;
+
+    /**
+     * Parameter list for @OnWebSocketFrame
+     */
+    private static final ParamList validFrameParams;
+
+    /**
+     * Parameter list for @OnWebSocketMessage (Text mode)
+     */
+    private static final ParamList validTextParams;
+
+    static
+    {
+        validConnectParams = new ParamList();
+        validConnectParams.addParams(Session.class);
+
+        validCloseParams = new ParamList();
+        validCloseParams.addParams(int.class,String.class);
+        validCloseParams.addParams(Session.class,int.class,String.class);
+
+        validErrorParams = new ParamList();
+        validErrorParams.addParams(Throwable.class);
+        validErrorParams.addParams(Session.class,Throwable.class);
+
+        validTextParams = new ParamList();
+        validTextParams.addParams(String.class);
+        validTextParams.addParams(Session.class,String.class);
+        validTextParams.addParams(Reader.class);
+        validTextParams.addParams(Session.class,Reader.class);
+
+        validBinaryParams = new ParamList();
+        validBinaryParams.addParams(byte[].class,int.class,int.class);
+        validBinaryParams.addParams(Session.class,byte[].class,int.class,int.class);
+        validBinaryParams.addParams(InputStream.class);
+        validBinaryParams.addParams(Session.class,InputStream.class);
+
+        validFrameParams = new ParamList();
+        validFrameParams.addParams(Frame.class);
+        validFrameParams.addParams(Session.class,Frame.class);
+    }
+
+    @Override
+    public void onMethodAnnotation(JettyAnnotatedMetadata metadata, Class<?> pojo, Method method, Annotation annotation)
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("onMethodAnnotation({}, {}, {}, {})",metadata,pojo,method,annotation);
+
+        if (isAnnotation(annotation,OnWebSocketConnect.class))
+        {
+            assertValidSignature(method,OnWebSocketConnect.class,validConnectParams);
+            assertUnset(metadata.onConnect,OnWebSocketConnect.class,method);
+            metadata.onConnect = new CallableMethod(pojo,method);
+            return;
+        }
+
+        if (isAnnotation(annotation,OnWebSocketMessage.class))
+        {
+            if (isSignatureMatch(method,validTextParams))
+            {
+                // Text mode
+                assertUnset(metadata.onText,OnWebSocketMessage.class,method);
+                metadata.onText = new OptionalSessionCallableMethod(pojo,method);
+                return;
+            }
+
+            if (isSignatureMatch(method,validBinaryParams))
+            {
+                // Binary Mode
+                // TODO
+                assertUnset(metadata.onBinary,OnWebSocketMessage.class,method);
+                metadata.onBinary = new OptionalSessionCallableMethod(pojo,method);
+                return;
+            }
+
+            throw InvalidSignatureException.build(method,OnWebSocketMessage.class,validTextParams,validBinaryParams);
+        }
+
+        if (isAnnotation(annotation,OnWebSocketClose.class))
+        {
+            assertValidSignature(method,OnWebSocketClose.class,validCloseParams);
+            assertUnset(metadata.onClose,OnWebSocketClose.class,method);
+            metadata.onClose = new OptionalSessionCallableMethod(pojo,method);
+            return;
+        }
+
+        if (isAnnotation(annotation,OnWebSocketError.class))
+        {
+            assertValidSignature(method,OnWebSocketError.class,validErrorParams);
+            assertUnset(metadata.onError,OnWebSocketError.class,method);
+            metadata.onError = new OptionalSessionCallableMethod(pojo,method);
+            return;
+        }
+
+        if (isAnnotation(annotation,OnWebSocketFrame.class))
+        {
+            assertValidSignature(method,OnWebSocketFrame.class,validFrameParams);
+            assertUnset(metadata.onFrame,OnWebSocketFrame.class,method);
+            metadata.onFrame = new OptionalSessionCallableMethod(pojo,method);
+            return;
+        }
+    }
+
+    public JettyAnnotatedMetadata scan(Class<?> pojo)
+    {
+        JettyAnnotatedMetadata metadata = new JettyAnnotatedMetadata();
+        scanMethodAnnotations(metadata,pojo);
+        return metadata;
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/JettyListenerEventDriver.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/JettyListenerEventDriver.java
new file mode 100644
index 0000000..376a1ce
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/JettyListenerEventDriver.java
@@ -0,0 +1,136 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.events;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.WebSocketListener;
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.common.CloseInfo;
+import org.eclipse.jetty.websocket.common.message.SimpleBinaryMessage;
+import org.eclipse.jetty.websocket.common.message.SimpleTextMessage;
+
+/**
+ * Handler for {@link WebSocketListener} based User WebSocket implementations.
+ */
+public class JettyListenerEventDriver extends AbstractEventDriver
+{
+    private static final Logger LOG = Log.getLogger(JettyListenerEventDriver.class);
+    private final WebSocketListener listener;
+    private boolean hasCloseBeenCalled = false;
+
+    public JettyListenerEventDriver(WebSocketPolicy policy, WebSocketListener listener)
+    {
+        super(policy,listener);
+        this.listener = listener;
+    }
+
+    @Override
+    public void onBinaryFrame(ByteBuffer buffer, boolean fin) throws IOException
+    {
+        if (activeMessage == null)
+        {
+            activeMessage = new SimpleBinaryMessage(this);
+        }
+
+        appendMessage(buffer,fin);
+    }
+
+    @Override
+    public void onBinaryMessage(byte[] data)
+    {
+        listener.onWebSocketBinary(data,0,data.length);
+    }
+
+    @Override
+    public void onClose(CloseInfo close)
+    {
+        if (hasCloseBeenCalled)
+        {
+            // avoid duplicate close events (possible when using harsh Session.disconnect())
+            return;
+        }
+        hasCloseBeenCalled = true;
+
+        int statusCode = close.getStatusCode();
+        String reason = close.getReason();
+        listener.onWebSocketClose(statusCode,reason);
+    }
+
+    @Override
+    public void onConnect()
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("onConnect()");
+        listener.onWebSocketConnect(session);
+    }
+
+    @Override
+    public void onError(Throwable cause)
+    {
+        listener.onWebSocketError(cause);
+    }
+
+    @Override
+    public void onFrame(Frame frame)
+    {
+        /* ignore, not supported by WebSocketListener */
+    }
+
+    @Override
+    public void onInputStream(InputStream stream)
+    {
+        /* not supported in Listener mode (yet) */
+    }
+
+    @Override
+    public void onReader(Reader reader)
+    {
+        /* not supported in Listener mode (yet) */
+    }
+
+    @Override
+    public void onTextFrame(ByteBuffer buffer, boolean fin) throws IOException
+    {
+        if (activeMessage == null)
+        {
+            activeMessage = new SimpleTextMessage(this);
+        }
+
+        appendMessage(buffer,fin);
+    }
+
+    @Override
+    public void onTextMessage(String message)
+    {
+        listener.onWebSocketText(message);
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s[%s]",JettyListenerEventDriver.class.getSimpleName(),listener.getClass().getName());
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/JettyListenerImpl.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/JettyListenerImpl.java
new file mode 100644
index 0000000..720864a
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/JettyListenerImpl.java
@@ -0,0 +1,44 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.events;
+
+import org.eclipse.jetty.websocket.api.WebSocketListener;
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+
+public class JettyListenerImpl implements EventDriverImpl
+{
+    @Override
+    public EventDriver create(Object websocket, WebSocketPolicy policy)
+    {
+        WebSocketListener listener = (WebSocketListener)websocket;
+        return new JettyListenerEventDriver(policy,listener);
+    }
+
+    @Override
+    public String describeRule()
+    {
+        return "class implements " + WebSocketListener.class.getName();
+    }
+
+    @Override
+    public boolean supports(Object websocket)
+    {
+        return (websocket instanceof WebSocketListener);
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/ParamList.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/ParamList.java
new file mode 100644
index 0000000..545489d
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/ParamList.java
@@ -0,0 +1,33 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.events;
+
+import java.util.ArrayList;
+
+/**
+ * Simple class for representing a list of class arrays.
+ */
+ at SuppressWarnings("serial")
+public class ParamList extends ArrayList<Class<?>[]>
+{
+    public void addParams(Class<?>... paramTypes)
+    {
+        this.add(paramTypes);
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/annotated/AbstractMethodAnnotationScanner.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/annotated/AbstractMethodAnnotationScanner.java
new file mode 100644
index 0000000..480e10b
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/annotated/AbstractMethodAnnotationScanner.java
@@ -0,0 +1,194 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.events.annotated;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+
+import org.eclipse.jetty.websocket.api.InvalidWebSocketException;
+import org.eclipse.jetty.websocket.common.events.ParamList;
+
+/**
+ * Basic scanner for Annotated Methods
+ */
+public abstract class AbstractMethodAnnotationScanner<T>
+{
+    protected void assertIsPublicNonStatic(Method method)
+    {
+        int mods = method.getModifiers();
+        if (!Modifier.isPublic(mods))
+        {
+            StringBuilder err = new StringBuilder();
+            err.append("Invalid declaration of ");
+            err.append(method);
+            err.append(System.lineSeparator());
+
+            err.append("Method modifier must be public");
+
+            throw new InvalidWebSocketException(err.toString());
+        }
+
+        if (Modifier.isStatic(mods))
+        {
+            StringBuilder err = new StringBuilder();
+            err.append("Invalid declaration of ");
+            err.append(method);
+            err.append(System.lineSeparator());
+
+            err.append("Method modifier may not be static");
+
+            throw new InvalidWebSocketException(err.toString());
+        }
+    }
+
+    protected void assertIsReturn(Method method, Class<?> type)
+    {
+        if (!type.equals(method.getReturnType()))
+        {
+            StringBuilder err = new StringBuilder();
+            err.append("Invalid declaration of ");
+            err.append(method);
+            err.append(System.lineSeparator());
+
+            err.append("Return type must be ").append(type);
+
+            throw new InvalidWebSocketException(err.toString());
+        }
+    }
+
+    protected void assertIsVoidReturn(Method method)
+    {
+        assertIsReturn(method,Void.TYPE);
+    }
+
+    protected void assertUnset(CallableMethod callable, Class<? extends Annotation> annoClass, Method method)
+    {
+        if (callable != null)
+        {
+            // Attempt to add duplicate frame type (a no-no)
+            StringBuilder err = new StringBuilder();
+            err.append("Duplicate @").append(annoClass.getSimpleName()).append(" declaration on ");
+            err.append(method);
+            err.append(System.lineSeparator());
+
+            err.append("@").append(annoClass.getSimpleName()).append(" previously declared at ");
+            err.append(callable.getMethod());
+
+            throw new InvalidWebSocketException(err.toString());
+        }
+    }
+
+    protected void assertValidSignature(Method method, Class<? extends Annotation> annoClass, ParamList validParams)
+    {
+        assertIsPublicNonStatic(method);
+        assertIsReturn(method,Void.TYPE);
+
+        boolean valid = false;
+
+        // validate parameters
+        Class<?> actual[] = method.getParameterTypes();
+        for (Class<?>[] params : validParams)
+        {
+            if (isSameParameters(actual,params))
+            {
+                valid = true;
+                break;
+            }
+        }
+
+        if (!valid)
+        {
+            throw InvalidSignatureException.build(method,annoClass,validParams);
+        }
+    }
+
+    public boolean isAnnotation(Annotation annotation, Class<? extends Annotation> annotationClass)
+    {
+        return annotation.annotationType().equals(annotationClass);
+    }
+
+    public boolean isSameParameters(Class<?>[] actual, Class<?>[] params)
+    {
+        if (actual.length != params.length)
+        {
+            // skip
+            return false;
+        }
+
+        int len = params.length;
+        for (int i = 0; i < len; i++)
+        {
+            if (!actual[i].equals(params[i]))
+            {
+                return false; // not valid
+            }
+        }
+
+        return true;
+    }
+
+    protected boolean isSignatureMatch(Method method, ParamList validParams)
+    {
+        assertIsPublicNonStatic(method);
+        assertIsReturn(method,Void.TYPE);
+
+        // validate parameters
+        Class<?> actual[] = method.getParameterTypes();
+        for (Class<?>[] params : validParams)
+        {
+            if (isSameParameters(actual,params))
+            {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    protected boolean isTypeAnnotated(Class<?> pojo, Class<? extends Annotation> expectedAnnotation)
+    {
+        return pojo.getAnnotation(expectedAnnotation) != null;
+    }
+
+    public abstract void onMethodAnnotation(T metadata, Class<?> pojo, Method method, Annotation annotation);
+
+    public void scanMethodAnnotations(T metadata, Class<?> pojo)
+    {
+        Class<?> clazz = pojo;
+
+        while ((clazz != null) && Object.class.isAssignableFrom(clazz))
+        {
+            for (Method method : clazz.getDeclaredMethods())
+            {
+                Annotation annotations[] = method.getAnnotations();
+                if ((annotations == null) || (annotations.length <= 0))
+                {
+                    continue; // skip
+                }
+                for (Annotation annotation : annotations)
+                {
+                    onMethodAnnotation(metadata,clazz,method,annotation);
+                }
+            }
+
+            clazz = clazz.getSuperclass();
+        }
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/annotated/CallableMethod.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/annotated/CallableMethod.java
new file mode 100644
index 0000000..69b97c6
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/annotated/CallableMethod.java
@@ -0,0 +1,144 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.events.annotated;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Objects;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.common.util.ReflectUtils;
+
+/**
+ * A Callable Method
+ */
+public class CallableMethod
+{
+    private static final Logger LOG = Log.getLogger(CallableMethod.class);
+    protected final Class<?> pojo;
+    protected final Method method;
+    protected Class<?>[] paramTypes;
+
+    public CallableMethod(Class<?> pojo, Method method)
+    {
+        Objects.requireNonNull(pojo, "Pojo cannot be null");
+        Objects.requireNonNull(method, "Method cannot be null");
+        this.pojo = pojo;
+        this.method = method;
+        this.paramTypes = method.getParameterTypes();
+    }
+
+    public Object call(Object obj, Object... args)
+    {
+        if ((this.pojo == null) || (this.method == null))
+        {
+            LOG.warn("Cannot execute call: pojo={}, method={}",pojo,method);
+            return null; // no call event method determined
+        }
+
+        if (obj == null)
+        {
+            LOG.warn("Cannot call {} on null object",this.method);
+            return null;
+        }
+
+        if (args.length < paramTypes.length)
+        {
+            throw new IllegalArgumentException("Call arguments length [" + args.length + "] must always be greater than or equal to captured args length ["
+                    + paramTypes.length + "]");
+        }
+
+        try
+        {
+            return this.method.invoke(obj,args);
+        }
+        catch (Throwable t)
+        {
+            String err = formatMethodCallError(args);
+            throw unwrapRuntimeException(err,t);
+        }
+    }
+
+    private RuntimeException unwrapRuntimeException(String err, final Throwable t)
+    {
+        Throwable ret = t;
+
+        while (ret instanceof InvocationTargetException)
+        {
+            ret = ((InvocationTargetException)ret).getCause();
+        }
+
+        if (ret instanceof RuntimeException)
+        {
+            return (RuntimeException)ret;
+        }
+
+        return new RuntimeException(err,ret);
+    }
+
+    public String formatMethodCallError(Object... args)
+    {
+        StringBuilder err = new StringBuilder();
+        err.append("Cannot call method ");
+        err.append(ReflectUtils.toString(pojo,method));
+        err.append(" with args: [");
+
+        boolean delim = false;
+        for (Object arg : args)
+        {
+            if (delim)
+            {
+                err.append(", ");
+            }
+            if (arg == null)
+            {
+                err.append("<null>");
+            }
+            else
+            {
+                err.append(arg.getClass().getName());
+            }
+            delim = true;
+        }
+        err.append("]");
+        return err.toString();
+    }
+
+    public Method getMethod()
+    {
+        return method;
+    }
+
+    public Class<?>[] getParamTypes()
+    {
+        return paramTypes;
+    }
+
+    public Class<?> getPojo()
+    {
+        return pojo;
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s[%s]",this.getClass().getSimpleName(),method.toGenericString());
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/annotated/EventMethod.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/annotated/EventMethod.java
new file mode 100644
index 0000000..2a83915
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/annotated/EventMethod.java
@@ -0,0 +1,154 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.events.annotated;
+
+import java.io.InputStream;
+import java.io.Reader;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.WebSocketException;
+import org.eclipse.jetty.websocket.api.util.QuoteUtil;
+
+public class EventMethod
+{
+    private static final Logger LOG = Log.getLogger(EventMethod.class);
+
+    private static Object[] dropFirstArg(Object[] args)
+    {
+        if (args.length == 1)
+        {
+            return new Object[0];
+        }
+        Object ret[] = new Object[args.length - 1];
+        System.arraycopy(args,1,ret,0,ret.length);
+        return ret;
+    }
+
+    protected Class<?> pojo;
+    protected Method method;
+    private boolean hasSession = false;
+    private boolean isStreaming = false;
+    private Class<?>[] paramTypes;
+
+    public EventMethod(Class<?> pojo, Method method)
+    {
+        this.pojo = pojo;
+        this.paramTypes = method.getParameterTypes();
+        this.method = method;
+        identifyPresentParamTypes();
+    }
+
+    public EventMethod(Class<?> pojo, String methodName, Class<?>... paramTypes)
+    {
+        try
+        {
+            this.pojo = pojo;
+            this.paramTypes = paramTypes;
+            this.method = pojo.getMethod(methodName,paramTypes);
+            identifyPresentParamTypes();
+        }
+        catch (NoSuchMethodException | SecurityException e)
+        {
+            LOG.warn("Cannot use method {}({}): {}",methodName,paramTypes,e.getMessage());
+            this.method = null;
+        }
+    }
+
+    public void call(Object obj, Object... args)
+    {
+        if ((this.pojo == null) || (this.method == null))
+        {
+            LOG.warn("Cannot execute call: pojo={}, method={}",pojo,method);
+            return; // no call event method determined
+        }
+        if (obj == null)
+        {
+            LOG.warn("Cannot call {} on null object",this.method);
+            return;
+        }
+        if (args.length > paramTypes.length)
+        {
+            Object trimArgs[] = dropFirstArg(args);
+            call(obj,trimArgs);
+            return;
+        }
+        if (args.length < paramTypes.length)
+        {
+            throw new IllegalArgumentException("Call arguments length [" + args.length + "] must always be greater than or equal to captured args length ["
+                    + paramTypes.length + "]");
+        }
+
+        try
+        {
+            this.method.invoke(obj,args);
+        }
+        catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e)
+        {
+            String err = String.format("Cannot call method %s on %s with args: %s",method,pojo, QuoteUtil.join(args,","));
+            throw new WebSocketException(err,e);
+        }
+    }
+
+    public Method getMethod()
+    {
+        return method;
+    }
+
+    protected Class<?>[] getParamTypes()
+    {
+        return this.paramTypes;
+    }
+
+    private void identifyPresentParamTypes()
+    {
+        this.hasSession = false;
+        this.isStreaming = false;
+
+        if (paramTypes == null)
+        {
+            return;
+        }
+
+        for (Class<?> paramType : paramTypes)
+        {
+            if (Session.class.isAssignableFrom(paramType))
+            {
+                this.hasSession = true;
+            }
+            if (Reader.class.isAssignableFrom(paramType) || InputStream.class.isAssignableFrom(paramType))
+            {
+                this.isStreaming = true;
+            }
+        }
+    }
+
+    public boolean isHasSession()
+    {
+        return hasSession;
+    }
+
+    public boolean isStreaming()
+    {
+        return isStreaming;
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/annotated/EventMethods.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/annotated/EventMethods.java
new file mode 100644
index 0000000..ed592ec
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/annotated/EventMethods.java
@@ -0,0 +1,105 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.events.annotated;
+
+/**
+ * A representation of the methods available to call for a particular class.
+ */
+public class EventMethods
+{
+    private Class<?> pojoClass;
+    public EventMethod onConnect = null;
+    public EventMethod onClose = null;
+    public EventMethod onBinary = null;
+    public EventMethod onText = null;
+    public EventMethod onError = null;
+    public EventMethod onFrame = null;
+
+    public EventMethods(Class<?> pojoClass)
+    {
+        this.pojoClass = pojoClass;
+    }
+
+    @Override
+    public boolean equals(Object obj)
+    {
+        if (this == obj)
+        {
+            return true;
+        }
+        if (obj == null)
+        {
+            return false;
+        }
+        if (getClass() != obj.getClass())
+        {
+            return false;
+        }
+        EventMethods other = (EventMethods)obj;
+        if (pojoClass == null)
+        {
+            if (other.pojoClass != null)
+            {
+                return false;
+            }
+        }
+        else if (!pojoClass.getName().equals(other.pojoClass.getName()))
+        {
+            return false;
+        }
+        return true;
+    }
+
+    public Class<?> getPojoClass()
+    {
+        return pojoClass;
+    }
+
+    @Override
+    public int hashCode()
+    {
+        final int prime = 31;
+        int result = 1;
+        result = (prime * result) + ((pojoClass == null)?0:pojoClass.getName().hashCode());
+        return result;
+    }
+
+    @Override
+    public String toString()
+    {
+        StringBuilder builder = new StringBuilder();
+        builder.append("EventMethods [pojoClass=");
+        builder.append(pojoClass);
+        builder.append(", onConnect=");
+        builder.append(onConnect);
+        builder.append(", onClose=");
+        builder.append(onClose);
+        builder.append(", onBinary=");
+        builder.append(onBinary);
+        builder.append(", onText=");
+        builder.append(onText);
+        builder.append(", onException=");
+        builder.append(onError);
+        builder.append(", onFrame=");
+        builder.append(onFrame);
+        builder.append("]");
+        return builder.toString();
+    }
+
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/annotated/InvalidSignatureException.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/annotated/InvalidSignatureException.java
new file mode 100644
index 0000000..4445964
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/annotated/InvalidSignatureException.java
@@ -0,0 +1,78 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.events.annotated;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+
+import org.eclipse.jetty.websocket.api.InvalidWebSocketException;
+import org.eclipse.jetty.websocket.common.events.ParamList;
+
+ at SuppressWarnings("serial")
+public class InvalidSignatureException extends InvalidWebSocketException
+{
+    public static InvalidSignatureException build(Method method, Class<? extends Annotation> annoClass, ParamList... paramlists)
+    {
+        // Build big detailed exception to help the developer
+        StringBuilder err = new StringBuilder();
+        err.append("Invalid declaration of ");
+        err.append(method);
+        err.append(System.lineSeparator());
+
+        err.append("Acceptable method declarations for @");
+        err.append(annoClass.getSimpleName());
+        err.append(" are:");
+        for (ParamList validParams : paramlists)
+        {
+            for (Class<?>[] params : validParams)
+            {
+                err.append(System.lineSeparator());
+                err.append("public void ").append(method.getName());
+                err.append('(');
+                boolean delim = false;
+                for (Class<?> type : params)
+                {
+                    if (delim)
+                    {
+                        err.append(',');
+                    }
+                    err.append(' ');
+                    err.append(type.getName());
+                    if (type.isArray())
+                    {
+                        err.append("[]");
+                    }
+                    delim = true;
+                }
+                err.append(')');
+            }
+        }
+        return new InvalidSignatureException(err.toString());
+    }
+
+    public InvalidSignatureException(String message)
+    {
+        super(message);
+    }
+
+    public InvalidSignatureException(String message, Throwable cause)
+    {
+        super(message,cause);
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/annotated/OptionalSessionCallableMethod.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/annotated/OptionalSessionCallableMethod.java
new file mode 100644
index 0000000..9b0b893
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/annotated/OptionalSessionCallableMethod.java
@@ -0,0 +1,91 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.events.annotated;
+
+import java.io.InputStream;
+import java.io.Reader;
+import java.lang.reflect.Method;
+
+import org.eclipse.jetty.websocket.api.Session;
+
+/**
+ * Simple CallableMethod that manages the optional {@link Session} argument
+ */
+public class OptionalSessionCallableMethod extends CallableMethod
+{
+    private final boolean wantsSession;
+    private final boolean streaming;
+
+    public OptionalSessionCallableMethod(Class<?> pojo, Method method)
+    {
+        super(pojo,method);
+
+        boolean foundConnection = false;
+        boolean foundStreaming = false;
+
+        if (paramTypes != null)
+        {
+            for (Class<?> paramType : paramTypes)
+            {
+                if (Session.class.isAssignableFrom(paramType))
+                {
+                    foundConnection = true;
+                }
+                if (Reader.class.isAssignableFrom(paramType) || InputStream.class.isAssignableFrom(paramType))
+                {
+                    foundStreaming = true;
+                }
+            }
+        }
+
+        this.wantsSession = foundConnection;
+        this.streaming = foundStreaming;
+    }
+
+    public void call(Object obj, Session connection, Object... args)
+    {
+        if (wantsSession)
+        {
+            Object fullArgs[] = new Object[args.length + 1];
+            fullArgs[0] = connection;
+            System.arraycopy(args,0,fullArgs,1,args.length);
+            call(obj,fullArgs);
+        }
+        else
+        {
+            call(obj,args);
+        }
+    }
+
+    public boolean isSessionAware()
+    {
+        return wantsSession;
+    }
+
+    public boolean isStreaming()
+    {
+        return streaming;
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s[%s]",this.getClass().getSimpleName(),method.toGenericString());
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/package-info.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/package-info.java
new file mode 100644
index 0000000..d6df5ed
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty WebSocket Common : Event Driver for WebSocket Object
+ */
+package org.eclipse.jetty.websocket.common.events;
+
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/AbstractExtension.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/AbstractExtension.java
new file mode 100644
index 0000000..c35fe8f
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/AbstractExtension.java
@@ -0,0 +1,209 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.extensions;
+
+import java.io.IOException;
+
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.BatchMode;
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.api.WriteCallback;
+import org.eclipse.jetty.websocket.api.extensions.Extension;
+import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.api.extensions.IncomingFrames;
+import org.eclipse.jetty.websocket.api.extensions.OutgoingFrames;
+import org.eclipse.jetty.websocket.common.LogicalConnection;
+
+ at ManagedObject("Abstract Extension")
+public abstract class AbstractExtension extends ContainerLifeCycle implements Extension
+{
+    private final Logger log;
+    private WebSocketPolicy policy;
+    private ByteBufferPool bufferPool;
+    private ExtensionConfig config;
+    private LogicalConnection connection;
+    private OutgoingFrames nextOutgoing;
+    private IncomingFrames nextIncoming;
+
+    public AbstractExtension()
+    {
+        log = Log.getLogger(this.getClass());
+    }
+
+    @Override
+    public void dump(Appendable out, String indent) throws IOException
+    {
+        super.dump(out, indent);
+        // incoming
+        dumpWithHeading(out, indent, "incoming", this.nextIncoming);
+        dumpWithHeading(out, indent, "outgoing", this.nextOutgoing);
+    }
+
+    protected void dumpWithHeading(Appendable out, String indent, String heading, Object bean) throws IOException
+    {
+        out.append(indent).append(" +- ");
+        out.append(heading).append(" : ");
+        out.append(bean.toString());
+    }
+
+    public ByteBufferPool getBufferPool()
+    {
+        return bufferPool;
+    }
+
+    @Override
+    public ExtensionConfig getConfig()
+    {
+        return config;
+    }
+
+    public LogicalConnection getConnection()
+    {
+        return connection;
+    }
+
+    @Override
+    public String getName()
+    {
+        return config.getName();
+    }
+
+    @ManagedAttribute(name = "Next Incoming Frame Handler", readonly = true)
+    public IncomingFrames getNextIncoming()
+    {
+        return nextIncoming;
+    }
+
+    @ManagedAttribute(name = "Next Outgoing Frame Handler", readonly = true)
+    public OutgoingFrames getNextOutgoing()
+    {
+        return nextOutgoing;
+    }
+
+    public WebSocketPolicy getPolicy()
+    {
+        return policy;
+    }
+
+    @Override
+    public void incomingError(Throwable e)
+    {
+        nextIncomingError(e);
+    }
+
+    /**
+     * Used to indicate that the extension makes use of the RSV1 bit of the base websocket framing.
+     * <p>
+     * This is used to adjust validation during parsing, as well as a checkpoint against 2 or more extensions all simultaneously claiming ownership of RSV1.
+     * 
+     * @return true if extension uses RSV1 for its own purposes.
+     */
+    @Override
+    public boolean isRsv1User()
+    {
+        return false;
+    }
+
+    /**
+     * Used to indicate that the extension makes use of the RSV2 bit of the base websocket framing.
+     * <p>
+     * This is used to adjust validation during parsing, as well as a checkpoint against 2 or more extensions all simultaneously claiming ownership of RSV2.
+     * 
+     * @return true if extension uses RSV2 for its own purposes.
+     */
+    @Override
+    public boolean isRsv2User()
+    {
+        return false;
+    }
+
+    /**
+     * Used to indicate that the extension makes use of the RSV3 bit of the base websocket framing.
+     * <p>
+     * This is used to adjust validation during parsing, as well as a checkpoint against 2 or more extensions all simultaneously claiming ownership of RSV3.
+     * 
+     * @return true if extension uses RSV3 for its own purposes.
+     */
+    @Override
+    public boolean isRsv3User()
+    {
+        return false;
+    }
+
+    protected void nextIncomingError(Throwable e)
+    {
+        this.nextIncoming.incomingError(e);
+    }
+
+    protected void nextIncomingFrame(Frame frame)
+    {
+        log.debug("nextIncomingFrame({})",frame);
+        this.nextIncoming.incomingFrame(frame);
+    }
+
+    protected void nextOutgoingFrame(Frame frame, WriteCallback callback, BatchMode batchMode)
+    {
+        log.debug("nextOutgoingFrame({})",frame);
+        this.nextOutgoing.outgoingFrame(frame,callback, batchMode);
+    }
+
+    public void setBufferPool(ByteBufferPool bufferPool)
+    {
+        this.bufferPool = bufferPool;
+    }
+
+    public void setConfig(ExtensionConfig config)
+    {
+        this.config = config;
+    }
+
+    public void setConnection(LogicalConnection connection)
+    {
+        this.connection = connection;
+    }
+
+    @Override
+    public void setNextIncomingFrames(IncomingFrames nextIncoming)
+    {
+        this.nextIncoming = nextIncoming;
+    }
+
+    @Override
+    public void setNextOutgoingFrames(OutgoingFrames nextOutgoing)
+    {
+        this.nextOutgoing = nextOutgoing;
+    }
+
+    public void setPolicy(WebSocketPolicy policy)
+    {
+        this.policy = policy;
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s[%s]",this.getClass().getSimpleName(),config.getParameterizedName());
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/ExtensionStack.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/ExtensionStack.java
new file mode 100644
index 0000000..74c4a8e
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/ExtensionStack.java
@@ -0,0 +1,454 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.extensions;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Queue;
+
+import org.eclipse.jetty.util.ConcurrentArrayQueue;
+import org.eclipse.jetty.util.IteratingCallback;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.BatchMode;
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.api.WriteCallback;
+import org.eclipse.jetty.websocket.api.extensions.Extension;
+import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
+import org.eclipse.jetty.websocket.api.extensions.ExtensionFactory;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.api.extensions.IncomingFrames;
+import org.eclipse.jetty.websocket.api.extensions.OutgoingFrames;
+import org.eclipse.jetty.websocket.common.Generator;
+import org.eclipse.jetty.websocket.common.Parser;
+
+/**
+ * Represents the stack of Extensions.
+ */
+ at ManagedObject("Extension Stack")
+public class ExtensionStack extends ContainerLifeCycle implements IncomingFrames, OutgoingFrames
+{
+    private static final Logger LOG = Log.getLogger(ExtensionStack.class);
+
+    private final Queue<FrameEntry> entries = new ConcurrentArrayQueue<>();
+    private final IteratingCallback flusher = new Flusher();
+    private final ExtensionFactory factory;
+    private List<Extension> extensions;
+    private IncomingFrames nextIncoming;
+    private OutgoingFrames nextOutgoing;
+
+    public ExtensionStack(ExtensionFactory factory)
+    {
+        this.factory = factory;
+    }
+
+    public void configure(Generator generator)
+    {
+        generator.configureFromExtensions(extensions);
+    }
+
+    public void configure(Parser parser)
+    {
+        parser.configureFromExtensions(extensions);
+    }
+
+    @Override
+    protected void doStart() throws Exception
+    {
+        super.doStart();
+
+        // Wire up Extensions
+        if ((extensions != null) && (extensions.size() > 0))
+        {
+            ListIterator<Extension> exts = extensions.listIterator();
+
+            // Connect outgoings
+            while (exts.hasNext())
+            {
+                Extension ext = exts.next();
+                ext.setNextOutgoingFrames(nextOutgoing);
+                nextOutgoing = ext;
+            }
+
+            // Connect incomings
+            while (exts.hasPrevious())
+            {
+                Extension ext = exts.previous();
+                ext.setNextIncomingFrames(nextIncoming);
+                nextIncoming = ext;
+            }
+        }
+    }
+
+    @Override
+    public void dump(Appendable out, String indent) throws IOException
+    {
+        super.dump(out,indent);
+
+        IncomingFrames websocket = getLastIncoming();
+        OutgoingFrames network = getLastOutgoing();
+
+        out.append(indent).append(" +- Stack").append(System.lineSeparator());
+        out.append(indent).append("     +- Network  : ").append(network.toString()).append(System.lineSeparator());
+        for (Extension ext : extensions)
+        {
+            out.append(indent).append("     +- Extension: ").append(ext.toString()).append(System.lineSeparator());
+        }
+        out.append(indent).append("     +- Websocket: ").append(websocket.toString()).append(System.lineSeparator());
+    }
+
+    @ManagedAttribute(name = "Extension List", readonly = true)
+    public List<Extension> getExtensions()
+    {
+        return extensions;
+    }
+
+    private IncomingFrames getLastIncoming()
+    {
+        IncomingFrames last = nextIncoming;
+        boolean done = false;
+        while (!done)
+        {
+            if (last instanceof AbstractExtension)
+            {
+                last = ((AbstractExtension)last).getNextIncoming();
+            }
+            else
+            {
+                done = true;
+            }
+        }
+        return last;
+    }
+
+    private OutgoingFrames getLastOutgoing()
+    {
+        OutgoingFrames last = nextOutgoing;
+        boolean done = false;
+        while (!done)
+        {
+            if (last instanceof AbstractExtension)
+            {
+                last = ((AbstractExtension)last).getNextOutgoing();
+            }
+            else
+            {
+                done = true;
+            }
+        }
+        return last;
+    }
+
+    /**
+     * Get the list of negotiated extensions, each entry being a full "name; params" extension configuration
+     * 
+     * @return list of negotiated extensions
+     */
+    public List<ExtensionConfig> getNegotiatedExtensions()
+    {
+        List<ExtensionConfig> ret = new ArrayList<>();
+        if (extensions == null)
+        {
+            return ret;
+        }
+
+        for (Extension ext : extensions)
+        {
+            if (ext.getName().charAt(0) == '@')
+            {
+                // special, internal-only extensions, not present on negotiation level
+                continue;
+            }
+            ret.add(ext.getConfig());
+        }
+        return ret;
+    }
+
+    @ManagedAttribute(name = "Next Incoming Frames Handler", readonly = true)
+    public IncomingFrames getNextIncoming()
+    {
+        return nextIncoming;
+    }
+
+    @ManagedAttribute(name = "Next Outgoing Frames Handler", readonly = true)
+    public OutgoingFrames getNextOutgoing()
+    {
+        return nextOutgoing;
+    }
+
+    public boolean hasNegotiatedExtensions()
+    {
+        return (this.extensions != null) && (this.extensions.size() > 0);
+    }
+
+    @Override
+    public void incomingError(Throwable e)
+    {
+        nextIncoming.incomingError(e);
+    }
+
+    @Override
+    public void incomingFrame(Frame frame)
+    {
+        nextIncoming.incomingFrame(frame);
+    }
+
+    /**
+     * Perform the extension negotiation.
+     * <p>
+     * For the list of negotiated extensions, use {@link #getNegotiatedExtensions()}
+     * 
+     * @param configs
+     *            the configurations being requested
+     */
+    public void negotiate(List<ExtensionConfig> configs)
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("Extension Configs={}",configs);
+
+        this.extensions = new ArrayList<>();
+
+        String rsvClaims[] = new String[3];
+
+        for (ExtensionConfig config : configs)
+        {
+            Extension ext = factory.newInstance(config);
+            if (ext == null)
+            {
+                // Extension not present on this side
+                continue;
+            }
+
+            // Check RSV
+            if (ext.isRsv1User() && (rsvClaims[0] != null))
+            {
+                LOG.debug("Not adding extension {}. Extension {} already claimed RSV1",config,rsvClaims[0]);
+                continue;
+            }
+            if (ext.isRsv2User() && (rsvClaims[1] != null))
+            {
+                LOG.debug("Not adding extension {}. Extension {} already claimed RSV2",config,rsvClaims[1]);
+                continue;
+            }
+            if (ext.isRsv3User() && (rsvClaims[2] != null))
+            {
+                LOG.debug("Not adding extension {}. Extension {} already claimed RSV3",config,rsvClaims[2]);
+                continue;
+            }
+
+            // Add Extension
+            extensions.add(ext);
+            addBean(ext);
+
+            if (LOG.isDebugEnabled())
+                LOG.debug("Adding Extension: {}",config);
+
+            // Record RSV Claims
+            if (ext.isRsv1User())
+            {
+                rsvClaims[0] = ext.getName();
+            }
+            if (ext.isRsv2User())
+            {
+                rsvClaims[1] = ext.getName();
+            }
+            if (ext.isRsv3User())
+            {
+                rsvClaims[2] = ext.getName();
+            }
+        }
+    }
+
+    @Override
+    public void outgoingFrame(Frame frame, WriteCallback callback, BatchMode batchMode)
+    {
+        FrameEntry entry = new FrameEntry(frame,callback,batchMode);
+        if (LOG.isDebugEnabled())
+            LOG.debug("Queuing {}",entry);
+        entries.offer(entry);
+        flusher.iterate();
+    }
+
+    public void setNextIncoming(IncomingFrames nextIncoming)
+    {
+        this.nextIncoming = nextIncoming;
+    }
+
+    public void setNextOutgoing(OutgoingFrames nextOutgoing)
+    {
+        this.nextOutgoing = nextOutgoing;
+    }
+
+    public void setPolicy(WebSocketPolicy policy)
+    {
+        for (Extension extension : extensions)
+        {
+            if (extension instanceof AbstractExtension)
+            {
+                ((AbstractExtension)extension).setPolicy(policy);
+            }
+        }
+    }
+
+    @Override
+    public String toString()
+    {
+        StringBuilder s = new StringBuilder();
+        s.append("ExtensionStack[");
+        s.append("queueSize=").append(entries.size());
+        s.append(",extensions=");
+        if (extensions == null)
+        {
+            s.append("<null>");
+        }
+        else
+        {
+            s.append('[');
+            boolean delim = false;
+            for (Extension ext : extensions)
+            {
+                if (delim)
+                {
+                    s.append(',');
+                }
+                if (ext == null)
+                {
+                    s.append("<null>");
+                }
+                else
+                {
+                    s.append(ext.getName());
+                }
+                delim = true;
+            }
+            s.append(']');
+        }
+        s.append(",incoming=").append((this.nextIncoming == null)?"<null>":this.nextIncoming.getClass().getName());
+        s.append(",outgoing=").append((this.nextOutgoing == null)?"<null>":this.nextOutgoing.getClass().getName());
+        s.append("]");
+        return s.toString();
+    }
+
+    private static class FrameEntry
+    {
+        private final Frame frame;
+        private final WriteCallback callback;
+        private final BatchMode batchMode;
+
+        private FrameEntry(Frame frame, WriteCallback callback, BatchMode batchMode)
+        {
+            this.frame = frame;
+            this.callback = callback;
+            this.batchMode = batchMode;
+        }
+
+        @Override
+        public String toString()
+        {
+            return frame.toString();
+        }
+    }
+
+    private class Flusher extends IteratingCallback implements WriteCallback
+    {
+        private FrameEntry current;
+
+        @Override
+        protected Action process() throws Exception
+        {
+            current = entries.poll();
+            if (current == null)
+            {
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Entering IDLE");
+                return Action.IDLE;
+            }
+            if (LOG.isDebugEnabled())
+                LOG.debug("Processing {}",current);
+            nextOutgoing.outgoingFrame(current.frame,this,current.batchMode);
+            return Action.SCHEDULED;
+        }
+
+        @Override
+        protected void onCompleteSuccess()
+        {
+            // This IteratingCallback never completes.
+        }
+        
+        @Override
+        protected void onCompleteFailure(Throwable x)
+        {
+            // This IteratingCallback never fails.
+            // The callback are those provided by WriteCallback (implemented
+            // below) and even in case of writeFailed() we call succeeded().
+        }
+        
+        @Override
+        public void writeSuccess()
+        {
+            // Notify first then call succeeded(), otherwise
+            // write callbacks may be invoked out of order.
+            notifyCallbackSuccess(current.callback);
+            succeeded();
+        }
+
+        @Override
+        public void writeFailed(Throwable x)
+        {
+            // Notify first, the call succeeded() to drain the queue.
+            // We don't want to call failed(x) because that will put
+            // this flusher into a final state that cannot be exited,
+            // and the failure of a frame may not mean that the whole
+            // connection is now invalid.
+            notifyCallbackFailure(current.callback,x);
+            succeeded();
+        }
+
+        private void notifyCallbackSuccess(WriteCallback callback)
+        {
+            try
+            {
+                if (callback != null)
+                    callback.writeSuccess();
+            }
+            catch (Throwable x)
+            {
+                LOG.debug("Exception while notifying success of callback " + callback,x);
+            }
+        }
+
+        private void notifyCallbackFailure(WriteCallback callback, Throwable failure)
+        {
+            try
+            {
+                if (callback != null)
+                    callback.writeFailed(failure);
+            }
+            catch (Throwable x)
+            {
+                LOG.debug("Exception while notifying failure of callback " + callback,x);
+            }
+        }
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/FrameDebugExtension.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/FrameDebugExtension.java
new file mode 100644
index 0000000..d9b540a
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/FrameDebugExtension.java
@@ -0,0 +1,142 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.extensions;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.SeekableByteChannel;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.BatchMode;
+import org.eclipse.jetty.websocket.api.WriteCallback;
+import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.common.Generator;
+
+public class FrameDebugExtension extends AbstractExtension
+{
+    private static final Logger LOG = Log.getLogger(FrameDebugExtension.class);
+
+    private static final int BUFSIZE = 32768;
+    private Generator generator;
+    private Path outputDir;
+    private String prefix = "frame";
+    private AtomicLong incomingId = new AtomicLong(0);
+    private AtomicLong outgoingId = new AtomicLong(0);
+
+    @Override
+    public String getName()
+    {
+        return "@frame-debug";
+    }
+
+    @Override
+    public void incomingFrame(Frame frame)
+    {
+        saveFrame(frame,false);
+        nextIncomingFrame(frame);
+    }
+
+    @Override
+    public void outgoingFrame(Frame frame, WriteCallback callback, BatchMode batchMode)
+    {
+        saveFrame(frame,true);
+        nextOutgoingFrame(frame,callback,batchMode);
+    }
+
+    private void saveFrame(Frame frame, boolean outgoing)
+    {
+        if (outputDir == null || generator == null)
+        {
+            return;
+        }
+
+        StringBuilder filename = new StringBuilder();
+        filename.append(prefix);
+        if (outgoing)
+        {
+            filename.append(String.format("-outgoing-%05d",outgoingId.getAndIncrement()));
+        }
+        else
+        {
+            filename.append(String.format("-incoming-%05d",incomingId.getAndIncrement()));
+        }
+        filename.append(".dat");
+
+        Path outputFile = outputDir.resolve(filename.toString());
+        ByteBuffer buf = getBufferPool().acquire(BUFSIZE,false);
+        try (SeekableByteChannel channel = Files.newByteChannel(outputFile,StandardOpenOption.CREATE,StandardOpenOption.WRITE))
+        {
+            generator.generateHeaderBytes(frame,buf);
+            channel.write(buf);
+            if (frame.hasPayload())
+            {
+                channel.write(frame.getPayload().slice());
+            }
+            LOG.debug("Saved raw frame: {}",outputFile.toString());
+        }
+        catch (IOException e)
+        {
+            LOG.warn("Unable to save frame: " + filename.toString(),e);
+        }
+        finally
+        {
+            getBufferPool().release(buf);
+        }
+    }
+
+    @Override
+    public void setConfig(ExtensionConfig config)
+    {
+        super.setConfig(config);
+
+        String cfgOutputDir = config.getParameter("output-dir",null);
+        if (StringUtil.isNotBlank(cfgOutputDir))
+        {
+            Path path = new File(cfgOutputDir).toPath();
+            if (Files.isDirectory(path) && Files.exists(path) && Files.isWritable(path))
+            {
+                this.outputDir = path;
+            }
+            else
+            {
+                LOG.warn("Unable to configure {}: not a valid output directory",path.toAbsolutePath().toString());
+            }
+        }
+
+        String cfgPrefix = config.getParameter("prefix","frame");
+        if (StringUtil.isNotBlank(cfgPrefix))
+        {
+            this.prefix = cfgPrefix;
+        }
+
+        if (this.outputDir != null)
+        {
+            // create a non-validating, read-only generator
+            this.generator = new Generator(getPolicy(),getBufferPool(),false,true);
+        }
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/WebSocketExtensionFactory.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/WebSocketExtensionFactory.java
new file mode 100644
index 0000000..8bec446
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/WebSocketExtensionFactory.java
@@ -0,0 +1,78 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.extensions;
+
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.websocket.api.WebSocketException;
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.api.extensions.Extension;
+import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
+import org.eclipse.jetty.websocket.api.extensions.ExtensionFactory;
+
+public class WebSocketExtensionFactory extends ExtensionFactory
+{
+    private WebSocketPolicy policy;
+    private ByteBufferPool bufferPool;
+
+    public WebSocketExtensionFactory(WebSocketPolicy policy, ByteBufferPool bufferPool)
+    {
+        super();
+        this.policy = policy;
+        this.bufferPool = bufferPool;
+    }
+
+    @Override
+    public Extension newInstance(ExtensionConfig config)
+    {
+        if (config == null)
+        {
+            return null;
+        }
+
+        String name = config.getName();
+        if (StringUtil.isBlank(name))
+        {
+            return null;
+        }
+
+        Class<? extends Extension> extClass = getExtension(name);
+        if (extClass == null)
+        {
+            return null;
+        }
+
+        try
+        {
+            Extension ext = extClass.newInstance();
+            if (ext instanceof AbstractExtension)
+            {
+                AbstractExtension aext = (AbstractExtension)ext;
+                aext.setPolicy(policy);
+                aext.setBufferPool(bufferPool);
+                aext.setConfig(config);
+            }
+            return ext;
+        }
+        catch (InstantiationException | IllegalAccessException e)
+        {
+            throw new WebSocketException("Cannot instantiate extension: " + extClass,e);
+        }
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/compress/ByteAccumulator.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/compress/ByteAccumulator.java
new file mode 100644
index 0000000..0046b12
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/compress/ByteAccumulator.java
@@ -0,0 +1,79 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.extensions.compress;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.websocket.api.MessageTooLargeException;
+
+public class ByteAccumulator
+{
+    private final List<Chunk> chunks = new ArrayList<>();
+    private final int maxSize;
+    private int length = 0;
+
+    public ByteAccumulator(int maxOverallBufferSize)
+    {
+        this.maxSize = maxOverallBufferSize;
+    }
+
+    public void addChunk(byte buf[], int offset, int length)
+    {
+        if (this.length + length > maxSize)
+        {
+            throw new MessageTooLargeException("Frame is too large");
+        }
+        chunks.add(new Chunk(buf, offset, length));
+        this.length += length;
+    }
+
+    public int getLength()
+    {
+        return length;
+    }
+
+    public void transferTo(ByteBuffer buffer)
+    {
+        if (buffer.remaining() < length)
+            throw new IllegalArgumentException();
+        int position = buffer.position();
+        for (Chunk chunk : chunks)
+        {
+            buffer.put(chunk.buffer, chunk.offset, chunk.length);
+        }
+        BufferUtil.flipToFlush(buffer, position);
+    }
+
+    private static class Chunk
+    {
+        private final byte[] buffer;
+        private final int offset;
+        private final int length;
+
+        private Chunk(byte[] buffer, int offset, int length)
+        {
+            this.buffer = buffer;
+            this.offset = offset;
+            this.length = length;
+        }
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/compress/CompressExtension.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/compress/CompressExtension.java
new file mode 100644
index 0000000..f4777fc
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/compress/CompressExtension.java
@@ -0,0 +1,409 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.extensions.compress;
+
+import java.nio.ByteBuffer;
+import java.util.Queue;
+import java.util.zip.DataFormatException;
+import java.util.zip.Deflater;
+import java.util.zip.Inflater;
+import java.util.zip.ZipException;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.ConcurrentArrayQueue;
+import org.eclipse.jetty.util.IteratingCallback;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.BadPayloadException;
+import org.eclipse.jetty.websocket.api.BatchMode;
+import org.eclipse.jetty.websocket.api.WriteCallback;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.common.OpCode;
+import org.eclipse.jetty.websocket.common.extensions.AbstractExtension;
+import org.eclipse.jetty.websocket.common.frames.DataFrame;
+
+public abstract class CompressExtension extends AbstractExtension
+{
+    protected static final byte[] TAIL_BYTES = new byte[]{0x00, 0x00, (byte)0xFF, (byte)0xFF};
+    private static final Logger LOG = Log.getLogger(CompressExtension.class);
+    
+    /** Never drop tail bytes 0000FFFF, from any frame type */
+    protected static final int TAIL_DROP_NEVER = 0;
+    /** Always drop tail bytes 0000FFFF, from all frame types */
+    protected static final int TAIL_DROP_ALWAYS = 1;
+    /** Only drop tail bytes 0000FFFF, from fin==true frames */
+    protected static final int TAIL_DROP_FIN_ONLY = 2;
+
+    /** Always set RSV flag, on all frame types */
+    protected static final int RSV_USE_ALWAYS = 0;
+    /** 
+     * Only set RSV flag on first frame in multi-frame messages.
+     * <p>
+     * Note: this automatically means no-continuation frames have
+     * the RSV bit set 
+     */
+    protected static final int RSV_USE_ONLY_FIRST = 1;
+
+    private final Queue<FrameEntry> entries = new ConcurrentArrayQueue<>();
+    private final IteratingCallback flusher = new Flusher();
+    private final Deflater compressor;
+    private final Inflater decompressor;
+    private int tailDrop = TAIL_DROP_NEVER;
+    private int rsvUse = RSV_USE_ALWAYS;
+
+    protected CompressExtension()
+    {
+        compressor = new Deflater(Deflater.BEST_COMPRESSION, true);
+        decompressor = new Inflater(true);
+        tailDrop = getTailDropMode();
+        rsvUse = getRsvUseMode();
+    }
+    
+    public Deflater getDeflater()
+    {
+        return compressor;
+    }
+
+    public Inflater getInflater()
+    {
+        return decompressor;
+    }
+
+    /**
+     * Indicates use of RSV1 flag for indicating deflation is in use.
+     */
+    @Override
+    public boolean isRsv1User()
+    {
+        return true;
+    }
+    
+    /**
+     * Return the mode of operation for dropping (or keeping) tail bytes in frames generated by compress (outgoing)
+     * 
+     * @return either {@link #TAIL_DROP_ALWAYS}, {@link #TAIL_DROP_FIN_ONLY}, or {@link #TAIL_DROP_NEVER}
+     */
+    abstract int getTailDropMode();
+
+    /**
+     * Return the mode of operation for RSV flag use in frames generate by compress (outgoing)
+     * 
+     * @return either {@link #RSV_USE_ALWAYS} or {@link #RSV_USE_ONLY_FIRST}
+     */
+    abstract int getRsvUseMode();
+
+    protected void forwardIncoming(Frame frame, ByteAccumulator accumulator)
+    {
+        DataFrame newFrame = new DataFrame(frame);
+        // Unset RSV1 since it's not compressed anymore.
+        newFrame.setRsv1(false);
+
+        ByteBuffer buffer = getBufferPool().acquire(accumulator.getLength(), false);
+        try
+        {
+            BufferUtil.flipToFill(buffer);
+            accumulator.transferTo(buffer);
+            newFrame.setPayload(buffer);
+            nextIncomingFrame(newFrame);
+        }
+        finally
+        {
+            getBufferPool().release(buffer);
+        }
+    }
+
+    protected ByteAccumulator decompress(byte[] input)
+    {
+        // Since we don't track text vs binary vs continuation state, just grab whatever is the greater value.
+        int maxSize = Math.max(getPolicy().getMaxTextMessageSize(), getPolicy().getMaxBinaryMessageBufferSize());
+        ByteAccumulator accumulator = new ByteAccumulator(maxSize);
+
+        decompressor.setInput(input, 0, input.length);
+
+        if (LOG.isDebugEnabled())
+            LOG.debug("Decompressing {} bytes", input.length);
+
+        try
+        {
+            // It is allowed to send DEFLATE blocks with BFINAL=1.
+            // For such blocks, getRemaining() will be > 0 but finished()
+            // will be true, so we need to check for both.
+            // When BFINAL=0, finished() will always be false and we only
+            // check the remaining bytes.
+            while (decompressor.getRemaining() > 0 && !decompressor.finished())
+            {
+                byte[] output = new byte[Math.min(input.length * 2, 32 * 1024)];
+                int decompressed = decompressor.inflate(output);
+                if (decompressed == 0)
+                {
+                    if (decompressor.needsInput())
+                    {
+                        throw new BadPayloadException("Unable to inflate frame, not enough input on frame");
+                    }
+                    if (decompressor.needsDictionary())
+                    {
+                        throw new BadPayloadException("Unable to inflate frame, frame erroneously says it needs a dictionary");
+                    }
+                }
+                else
+                {
+                    accumulator.addChunk(output, 0, decompressed);
+                }
+            }
+            if (LOG.isDebugEnabled())
+                LOG.debug("Decompressed {}->{} bytes", input.length, accumulator.getLength());
+            return accumulator;
+        }
+        catch (DataFormatException x)
+        {
+            throw new BadPayloadException(x);
+        }
+    }
+
+    @Override
+    public void outgoingFrame(Frame frame, WriteCallback callback, BatchMode batchMode)
+    {
+        // We use a queue and an IteratingCallback to handle concurrency.
+        // We must compress and write atomically, otherwise the compression
+        // context on the other end gets confused.
+
+        if (flusher.isFailed())
+        {
+            notifyCallbackFailure(callback, new ZipException());
+            return;
+        }
+
+        FrameEntry entry = new FrameEntry(frame, callback, batchMode);
+        if (LOG.isDebugEnabled())
+            LOG.debug("Queuing {}", entry);
+        entries.offer(entry);
+        flusher.iterate();
+    }
+
+    protected void notifyCallbackSuccess(WriteCallback callback)
+    {
+        try
+        {
+            if (callback != null)
+                callback.writeSuccess();
+        }
+        catch (Throwable x)
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("Exception while notifying success of callback " + callback, x);
+        }
+    }
+
+    protected void notifyCallbackFailure(WriteCallback callback, Throwable failure)
+    {
+        try
+        {
+            if (callback != null)
+                callback.writeFailed(failure);
+        }
+        catch (Throwable x)
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("Exception while notifying failure of callback " + callback, x);
+        }
+    }
+
+    @Override
+    public String toString()
+    {
+        return getClass().getSimpleName();
+    }
+
+    private static class FrameEntry
+    {
+        private final Frame frame;
+        private final WriteCallback callback;
+        private final BatchMode batchMode;
+
+        private FrameEntry(Frame frame, WriteCallback callback, BatchMode batchMode)
+        {
+            this.frame = frame;
+            this.callback = callback;
+            this.batchMode = batchMode;
+        }
+
+        @Override
+        public String toString()
+        {
+            return frame.toString();
+        }
+    }
+
+    private class Flusher extends IteratingCallback implements WriteCallback
+    {
+        private static final int INPUT_BUFSIZE = 32 * 1024;
+        private FrameEntry current;
+        private ByteBuffer payload;
+        private boolean finished = true;
+
+        @Override
+        protected Action process() throws Exception
+        {
+            if (finished)
+            {
+                current = entries.poll();
+                LOG.debug("Processing {}", current);
+                if (current == null)
+                    return Action.IDLE;
+                deflate(current);
+            }
+            else
+            {
+                compress(current, false);
+            }
+            return Action.SCHEDULED;
+        }
+
+        private void deflate(FrameEntry entry)
+        {
+            Frame frame = entry.frame;
+            BatchMode batchMode = entry.batchMode;
+            if (OpCode.isControlFrame(frame.getOpCode()) || !frame.hasPayload())
+            {
+                nextOutgoingFrame(frame, this, batchMode);
+                return;
+            }
+
+            compress(entry, true);
+        }
+
+        private void compress(FrameEntry entry, boolean first)
+        {
+            // Get a chunk of the payload to avoid to blow
+            // the heap if the payload is a huge mapped file.
+            Frame frame = entry.frame;
+            ByteBuffer data = frame.getPayload();
+            int remaining = data.remaining();
+            int inputLength = Math.min(remaining, INPUT_BUFSIZE);
+            if (LOG.isDebugEnabled())
+                LOG.debug("Compressing {}: {} bytes in {} bytes chunk", entry, remaining, inputLength);
+
+            // Avoid to copy the bytes if the ByteBuffer
+            // is backed by an array.
+            int inputOffset;
+            byte[] input;
+            if (data.hasArray())
+            {
+                input = data.array();
+                int position = data.position();
+                inputOffset = position + data.arrayOffset();
+                data.position(position + inputLength);
+            }
+            else
+            {
+                input = new byte[inputLength];
+                inputOffset = 0;
+                data.get(input, 0, inputLength);
+            }
+            finished = inputLength == remaining;
+
+            compressor.setInput(input, inputOffset, inputLength);
+
+            // Use an additional space in case the content is not compressible.
+            byte[] output = new byte[inputLength + 64];
+            int outputOffset = 0;
+            int outputLength = 0;
+            while (true)
+            {
+                int space = output.length - outputOffset;
+                int compressed = compressor.deflate(output, outputOffset, space, Deflater.SYNC_FLUSH);
+                outputLength += compressed;
+                if (compressed < space)
+                {
+                    // Everything was compressed.
+                    break;
+                }
+                else
+                {
+                    // The compressed output is bigger than the uncompressed input.
+                    byte[] newOutput = new byte[output.length * 2];
+                    System.arraycopy(output, 0, newOutput, 0, output.length);
+                    outputOffset += output.length;
+                    output = newOutput;
+                }
+            }
+
+            boolean fin = frame.isFin() && finished;
+
+            // Handle tail bytes generated by SYNC_FLUSH.
+            if(tailDrop == TAIL_DROP_ALWAYS) {
+                payload = ByteBuffer.wrap(output, 0, outputLength - TAIL_BYTES.length);
+            } else if(tailDrop == TAIL_DROP_FIN_ONLY) {
+                payload = ByteBuffer.wrap(output, 0, outputLength - (fin?TAIL_BYTES.length:0));
+            } else {
+                // always include
+                payload = ByteBuffer.wrap(output, 0, outputLength);
+            }
+            if (LOG.isDebugEnabled())
+            {
+                LOG.debug("Compressed {}: {}->{} chunk bytes",entry,inputLength,outputLength);
+            }
+
+            boolean continuation = frame.getType().isContinuation() || !first;
+            DataFrame chunk = new DataFrame(frame, continuation);
+            if(rsvUse == RSV_USE_ONLY_FIRST) {
+                chunk.setRsv1(!continuation);
+            } else {
+                // always set
+                chunk.setRsv1(true);
+            }
+            chunk.setPayload(payload);
+            chunk.setFin(fin);
+
+            nextOutgoingFrame(chunk, this, entry.batchMode);
+        }
+
+        @Override
+        protected void onCompleteSuccess()
+        {
+            // This IteratingCallback never completes.
+        }
+        
+        @Override
+        protected void onCompleteFailure(Throwable x)
+        {
+            // Fail all the frames in the queue.
+            FrameEntry entry;
+            while ((entry = entries.poll()) != null)
+                notifyCallbackFailure(entry.callback, x);
+        }
+
+        @Override
+        public void writeSuccess()
+        {
+            if (finished)
+                notifyCallbackSuccess(current.callback);
+            succeeded();
+        }
+
+        @Override
+        public void writeFailed(Throwable x)
+        {
+            notifyCallbackFailure(current.callback, x);
+            // If something went wrong, very likely the compression context
+            // will be invalid, so we need to fail this IteratingCallback.
+            failed(x);
+        }
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/compress/DeflateFrameExtension.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/compress/DeflateFrameExtension.java
new file mode 100644
index 0000000..ba61a0b
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/compress/DeflateFrameExtension.java
@@ -0,0 +1,72 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.extensions.compress;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.common.OpCode;
+
+/**
+ * Implementation of the
+ * <a href="https://tools.ietf.org/id/draft-tyoshino-hybi-websocket-perframe-deflate.txt">deflate-frame</a>
+ * extension seen out in the wild.
+ */
+public class DeflateFrameExtension extends CompressExtension
+{
+    @Override
+    public String getName()
+    {
+        return "deflate-frame";
+    }
+    
+    @Override
+    int getRsvUseMode()
+    {
+        return RSV_USE_ALWAYS;
+    }
+    
+    @Override
+    int getTailDropMode()
+    {
+        return TAIL_DROP_ALWAYS;
+    }
+
+    @Override
+    public void incomingFrame(Frame frame)
+    {
+        // Incoming frames are always non concurrent because
+        // they are read and parsed with a single thread, and
+        // therefore there is no need for synchronization.
+
+        if (OpCode.isControlFrame(frame.getOpCode()) || !frame.isRsv1() || !frame.hasPayload())
+        {
+            nextIncomingFrame(frame);
+            return;
+        }
+
+        ByteBuffer payload = frame.getPayload();
+        int remaining = payload.remaining();
+        byte[] input = new byte[remaining + TAIL_BYTES.length];
+        payload.get(input, 0, remaining);
+        System.arraycopy(TAIL_BYTES, 0, input, remaining, TAIL_BYTES.length);
+
+        forwardIncoming(frame, decompress(input));
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/compress/PerMessageDeflateExtension.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/compress/PerMessageDeflateExtension.java
new file mode 100644
index 0000000..09ffc6f
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/compress/PerMessageDeflateExtension.java
@@ -0,0 +1,182 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.extensions.compress;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.BatchMode;
+import org.eclipse.jetty.websocket.api.WriteCallback;
+import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.common.OpCode;
+
+/**
+ * Per Message Deflate Compression extension for WebSocket.
+ * <p/>
+ * Attempts to follow <a href="https://tools.ietf.org/html/draft-ietf-hybi-permessage-compression-12">draft-ietf-hybi-permessage-compression-12</a>
+ */
+public class PerMessageDeflateExtension extends CompressExtension
+{
+    private static final Logger LOG = Log.getLogger(PerMessageDeflateExtension.class);
+
+    private ExtensionConfig configRequested;
+    private ExtensionConfig configNegotiated;
+    private boolean incomingContextTakeover = true;
+    private boolean outgoingContextTakeover = true;
+    private boolean incomingCompressed;
+
+    @Override
+    public String getName()
+    {
+        return "permessage-deflate";
+    }
+
+    @Override
+    public void incomingFrame(Frame frame)
+    {
+        // Incoming frames are always non concurrent because
+        // they are read and parsed with a single thread, and
+        // therefore there is no need for synchronization.
+
+        // This extension requires the RSV1 bit set only in the first frame.
+        // Subsequent continuation frames don't have RSV1 set, but are compressed.
+        if (frame.getType().isData())
+            incomingCompressed = frame.isRsv1();
+
+        if (OpCode.isControlFrame(frame.getOpCode()) || !frame.hasPayload() || !incomingCompressed)
+        {
+            nextIncomingFrame(frame);
+            return;
+        }
+
+        boolean appendTail = frame.isFin();
+        ByteBuffer payload = frame.getPayload();
+        int remaining = payload.remaining();
+        byte[] input = new byte[remaining + (appendTail ? TAIL_BYTES.length : 0)];
+        payload.get(input, 0, remaining);
+        if (appendTail)
+            System.arraycopy(TAIL_BYTES, 0, input, remaining, TAIL_BYTES.length);
+
+        forwardIncoming(frame, decompress(input));
+
+        if (frame.isFin())
+            incomingCompressed = false;
+    }
+
+    @Override
+    protected void nextIncomingFrame(Frame frame)
+    {
+        if (frame.isFin() && !incomingContextTakeover)
+        {
+            LOG.debug("Incoming Context Reset");
+            getInflater().reset();
+        }
+        super.nextIncomingFrame(frame);
+    }
+
+    @Override
+    protected void nextOutgoingFrame(Frame frame, WriteCallback callback, BatchMode batchMode)
+    {
+        if (frame.isFin() && !outgoingContextTakeover)
+        {
+            LOG.debug("Outgoing Context Reset");
+            getDeflater().reset();
+        }
+        super.nextOutgoingFrame(frame, callback, batchMode);
+    }
+    
+    @Override
+    int getRsvUseMode()
+    {
+        return RSV_USE_ONLY_FIRST;
+    }
+    
+    @Override
+    int getTailDropMode()
+    {
+        return TAIL_DROP_FIN_ONLY;
+    }
+
+    @Override
+    public void setConfig(final ExtensionConfig config)
+    {
+        configRequested = new ExtensionConfig(config);
+        configNegotiated = new ExtensionConfig(config.getName());
+        
+        for (String key : config.getParameterKeys())
+        {
+            key = key.trim();
+            switch (key)
+            {
+                case "client_max_window_bits":
+                case "server_max_window_bits":
+                {
+                    // Not supported by Jetty
+                    // Don't negotiate these parameters
+                    break;
+                }
+                case "client_no_context_takeover":
+                {
+                    configNegotiated.setParameter("client_no_context_takeover");
+                    switch (getPolicy().getBehavior())
+                    {
+                        case CLIENT:
+                            incomingContextTakeover = false;
+                            break;
+                        case SERVER:
+                            outgoingContextTakeover = false;
+                            break;
+                    }
+                    break;
+                }
+                case "server_no_context_takeover":
+                {
+                    configNegotiated.setParameter("server_no_context_takeover");
+                    switch (getPolicy().getBehavior())
+                    {
+                        case CLIENT:
+                            outgoingContextTakeover = false;
+                            break;
+                        case SERVER:
+                            incomingContextTakeover = false;
+                            break;
+                    }
+                    break;
+                }
+                default:
+                {
+                    throw new IllegalArgumentException();
+                }
+            }
+        }
+
+        super.setConfig(configNegotiated);
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s[requested=%s,negotiated=%s]",
+                getClass().getSimpleName(),
+                configRequested.getParameterizedName(),
+                configNegotiated.getParameterizedName());
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/compress/XWebkitDeflateFrameExtension.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/compress/XWebkitDeflateFrameExtension.java
new file mode 100644
index 0000000..bcee242
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/compress/XWebkitDeflateFrameExtension.java
@@ -0,0 +1,32 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.extensions.compress;
+
+/**
+ * Implementation of the <a href="https://tools.ietf.org/id/draft-tyoshino-hybi-websocket-perframe-deflate-05.txt">x-webkit-deflate-frame</a> extension seen out
+ * in the wild. Using the alternate extension identification
+ */
+public class XWebkitDeflateFrameExtension extends DeflateFrameExtension
+{
+    @Override
+    public String getName()
+    {
+        return "x-webkit-deflate-frame";
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/compress/package-info.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/compress/package-info.java
new file mode 100644
index 0000000..58efc44
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/compress/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty WebSocket Common : Frame & Message Compression Extension Implementations
+ */
+package org.eclipse.jetty.websocket.common.extensions.compress;
+
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/fragment/FragmentExtension.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/fragment/FragmentExtension.java
new file mode 100644
index 0000000..ca38c2f
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/fragment/FragmentExtension.java
@@ -0,0 +1,217 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.extensions.fragment;
+
+
+import java.nio.ByteBuffer;
+import java.util.Queue;
+
+import org.eclipse.jetty.util.ConcurrentArrayQueue;
+import org.eclipse.jetty.util.IteratingCallback;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.BatchMode;
+import org.eclipse.jetty.websocket.api.WriteCallback;
+import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.common.OpCode;
+import org.eclipse.jetty.websocket.common.extensions.AbstractExtension;
+import org.eclipse.jetty.websocket.common.frames.DataFrame;
+
+/**
+ * Fragment Extension
+ */
+public class FragmentExtension extends AbstractExtension
+{
+    private static final Logger LOG = Log.getLogger(FragmentExtension.class);
+
+    private final Queue<FrameEntry> entries = new ConcurrentArrayQueue<>();
+    private final IteratingCallback flusher = new Flusher();
+    private int maxLength;
+
+    @Override
+    public String getName()
+    {
+        return "fragment";
+    }
+
+    @Override
+    public void incomingFrame(Frame frame)
+    {
+        nextIncomingFrame(frame);
+    }
+
+    @Override
+    public void outgoingFrame(Frame frame, WriteCallback callback, BatchMode batchMode)
+    {
+        ByteBuffer payload = frame.getPayload();
+        int length = payload != null ? payload.remaining() : 0;
+        if (OpCode.isControlFrame(frame.getOpCode()) || maxLength <= 0 || length <= maxLength)
+        {
+            nextOutgoingFrame(frame, callback, batchMode);
+            return;
+        }
+
+        FrameEntry entry = new FrameEntry(frame, callback, batchMode);
+        if (LOG.isDebugEnabled())
+            LOG.debug("Queuing {}", entry);
+        entries.offer(entry);
+        flusher.iterate();
+    }
+
+    @Override
+    public void setConfig(ExtensionConfig config)
+    {
+        super.setConfig(config);
+        maxLength = config.getParameter("maxLength", -1);
+    }
+
+    private static class FrameEntry
+    {
+        private final Frame frame;
+        private final WriteCallback callback;
+        private final BatchMode batchMode;
+
+        private FrameEntry(Frame frame, WriteCallback callback, BatchMode batchMode)
+        {
+            this.frame = frame;
+            this.callback = callback;
+            this.batchMode = batchMode;
+        }
+
+        @Override
+        public String toString()
+        {
+            return frame.toString();
+        }
+    }
+
+    private class Flusher extends IteratingCallback implements WriteCallback
+    {
+        private FrameEntry current;
+        private boolean finished = true;
+
+        @Override
+        protected Action process() throws Exception
+        {
+            if (finished)
+            {
+                current = entries.poll();
+                LOG.debug("Processing {}", current);
+                if (current == null)
+                    return Action.IDLE;
+                fragment(current, true);
+            }
+            else
+            {
+                fragment(current, false);
+            }
+            return Action.SCHEDULED;
+        }
+
+        private void fragment(FrameEntry entry, boolean first)
+        {
+            Frame frame = entry.frame;
+            ByteBuffer payload = frame.getPayload();
+            int remaining = payload.remaining();
+            int length = Math.min(remaining, maxLength);
+            finished = length == remaining;
+
+            boolean continuation = frame.getType().isContinuation() || !first;
+            DataFrame fragment = new DataFrame(frame, continuation);
+            boolean fin = frame.isFin() && finished;
+            fragment.setFin(fin);
+
+            int limit = payload.limit();
+            int newLimit = payload.position() + length;
+            payload.limit(newLimit);
+            ByteBuffer payloadFragment = payload.slice();
+            payload.limit(limit);
+            fragment.setPayload(payloadFragment);
+            if (LOG.isDebugEnabled())
+                LOG.debug("Fragmented {}->{}", frame, fragment);
+            payload.position(newLimit);
+
+            nextOutgoingFrame(fragment, this, entry.batchMode);
+        }
+
+        @Override
+        protected void onCompleteSuccess()
+        {
+            // This IteratingCallback never completes.
+        }
+        
+        @Override
+        protected void onCompleteFailure(Throwable x)
+        {
+            // This IteratingCallback never fails.
+            // The callback are those provided by WriteCallback (implemented
+            // below) and even in case of writeFailed() we call succeeded().
+        }
+        
+        @Override
+        public void writeSuccess()
+        {
+            // Notify first then call succeeded(), otherwise
+            // write callbacks may be invoked out of order.
+            notifyCallbackSuccess(current.callback);
+            succeeded();
+        }
+
+        @Override
+        public void writeFailed(Throwable x)
+        {
+            // Notify first, the call succeeded() to drain the queue.
+            // We don't want to call failed(x) because that will put
+            // this flusher into a final state that cannot be exited,
+            // and the failure of a frame may not mean that the whole
+            // connection is now invalid.
+            notifyCallbackFailure(current.callback, x);
+            succeeded();
+        }
+
+        private void notifyCallbackSuccess(WriteCallback callback)
+        {
+            try
+            {
+                if (callback != null)
+                    callback.writeSuccess();
+            }
+            catch (Throwable x)
+            {
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Exception while notifying success of callback " + callback, x);
+            }
+        }
+
+        private void notifyCallbackFailure(WriteCallback callback, Throwable failure)
+        {
+            try
+            {
+                if (callback != null)
+                    callback.writeFailed(failure);
+            }
+            catch (Throwable x)
+            {
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Exception while notifying failure of callback " + callback, x);
+            }
+        }
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/fragment/package-info.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/fragment/package-info.java
new file mode 100644
index 0000000..d3c72f7
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/fragment/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty WebSocket Common : Auto Fragment Extension Implementation
+ */
+package org.eclipse.jetty.websocket.common.extensions.fragment;
+
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/identity/IdentityExtension.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/identity/IdentityExtension.java
new file mode 100644
index 0000000..eec3870
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/identity/IdentityExtension.java
@@ -0,0 +1,93 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.extensions.identity;
+
+import org.eclipse.jetty.util.QuotedStringTokenizer;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.websocket.api.BatchMode;
+import org.eclipse.jetty.websocket.api.WriteCallback;
+import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.common.extensions.AbstractExtension;
+
+ at ManagedObject("Identity Extension")
+public class IdentityExtension extends AbstractExtension
+{
+    private String id;
+
+    public String getParam(String key)
+    {
+        return getConfig().getParameter(key,"?");
+    }
+    
+    @Override
+    public String getName()
+    {
+        return "identity";
+    }
+
+    @Override
+    public void incomingError(Throwable e)
+    {
+        // pass through
+        nextIncomingError(e);
+    }
+
+    @Override
+    public void incomingFrame(Frame frame)
+    {
+        // pass through
+        nextIncomingFrame(frame);
+    }
+
+    @Override
+    public void outgoingFrame(Frame frame, WriteCallback callback, BatchMode batchMode)
+    {
+        // pass through
+        nextOutgoingFrame(frame,callback, batchMode);
+    }
+
+    @Override
+    public void setConfig(ExtensionConfig config)
+    {
+        super.setConfig(config);
+        StringBuilder s = new StringBuilder();
+        s.append(config.getName());
+        s.append("@").append(Integer.toHexString(hashCode()));
+        s.append("[");
+        boolean delim = false;
+        for (String param : config.getParameterKeys())
+        {
+            if (delim)
+            {
+                s.append(';');
+            }
+            s.append(param).append('=').append(QuotedStringTokenizer.quoteIfNeeded(config.getParameter(param,""),";="));
+            delim = true;
+        }
+        s.append("]");
+        id = s.toString();
+    }
+
+    @Override
+    public String toString()
+    {
+        return id;
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/identity/package-info.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/identity/package-info.java
new file mode 100644
index 0000000..cc5db29
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/identity/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty WebSocket Common : Identity Extension Implementation
+ */
+package org.eclipse.jetty.websocket.common.extensions.identity;
+
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/package-info.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/package-info.java
new file mode 100644
index 0000000..bd03c77
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty WebSocket Common : Extension Implementations
+ */
+package org.eclipse.jetty.websocket.common.extensions;
+
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/frames/BinaryFrame.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/frames/BinaryFrame.java
new file mode 100644
index 0000000..d849fe3
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/frames/BinaryFrame.java
@@ -0,0 +1,56 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.frames;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.websocket.common.OpCode;
+
+public class BinaryFrame extends DataFrame
+{
+    public BinaryFrame()
+    {
+        super(OpCode.BINARY);
+    }
+
+    public BinaryFrame setPayload(ByteBuffer buf)
+    {
+        super.setPayload(buf);
+        return this;
+    }
+
+    public BinaryFrame setPayload(byte[] buf)
+    {
+        setPayload(ByteBuffer.wrap(buf));
+        return this;
+    }
+
+    public BinaryFrame setPayload(String payload)
+    {
+        setPayload(StringUtil.getUtf8Bytes(payload));
+        return this;
+    }
+
+    @Override
+    public Type getType()
+    {
+        return Type.BINARY;
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/frames/CloseFrame.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/frames/CloseFrame.java
new file mode 100644
index 0000000..eb6aed0
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/frames/CloseFrame.java
@@ -0,0 +1,48 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.frames;
+
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.websocket.common.OpCode;
+
+public class CloseFrame extends ControlFrame
+{
+    public CloseFrame()
+    {
+        super(OpCode.CLOSE);
+    }
+
+    @Override
+    public Type getType()
+    {
+        return Type.CLOSE;
+    }
+
+    /**
+     * Truncate arbitrary reason into something that will fit into the CloseFrame limits.
+     * 
+     * @param reason
+     *            the arbitrary reason to possibly truncate.
+     * @return the possibly truncated reason string.
+     */
+    public static String truncate(String reason)
+    {
+        return StringUtil.truncate(reason,(ControlFrame.MAX_CONTROL_PAYLOAD - 2));
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/frames/ContinuationFrame.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/frames/ContinuationFrame.java
new file mode 100644
index 0000000..25af7d0
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/frames/ContinuationFrame.java
@@ -0,0 +1,54 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.frames;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.websocket.common.OpCode;
+
+public class ContinuationFrame extends DataFrame
+{
+    public ContinuationFrame()
+    {
+        super(OpCode.CONTINUATION);
+    }
+
+    public ContinuationFrame setPayload(ByteBuffer buf)
+    {
+        super.setPayload(buf);
+        return this;
+    }
+
+    public ContinuationFrame setPayload(byte buf[])
+    {
+        return this.setPayload(ByteBuffer.wrap(buf));
+    }
+
+    public ContinuationFrame setPayload(String message)
+    {
+        return this.setPayload(StringUtil.getUtf8Bytes(message));
+    }
+
+    @Override
+    public Type getType()
+    {
+        return Type.CONTINUATION;
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/frames/ControlFrame.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/frames/ControlFrame.java
new file mode 100644
index 0000000..c1a8d54
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/frames/ControlFrame.java
@@ -0,0 +1,131 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.frames;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+import org.eclipse.jetty.websocket.api.ProtocolException;
+import org.eclipse.jetty.websocket.common.WebSocketFrame;
+
+public abstract class ControlFrame extends WebSocketFrame
+{
+    /** Maximum size of Control frame, per RFC 6455 */
+    public static final int MAX_CONTROL_PAYLOAD = 125;
+
+    public ControlFrame(byte opcode)
+    {
+        super(opcode);
+    }
+
+    public void assertValid()
+    {
+        if (isControlFrame())
+        {
+            if (getPayloadLength() > ControlFrame.MAX_CONTROL_PAYLOAD)
+            {
+                throw new ProtocolException("Desired payload length [" + getPayloadLength() + "] exceeds maximum control payload length ["
+                        + MAX_CONTROL_PAYLOAD + "]");
+            }
+
+            if ((finRsvOp & 0x80) == 0)
+            {
+                throw new ProtocolException("Cannot have FIN==false on Control frames");
+            }
+
+            if ((finRsvOp & 0x40) != 0)
+            {
+                throw new ProtocolException("Cannot have RSV1==true on Control frames");
+            }
+
+            if ((finRsvOp & 0x20) != 0)
+            {
+                throw new ProtocolException("Cannot have RSV2==true on Control frames");
+            }
+
+            if ((finRsvOp & 0x10) != 0)
+            {
+                throw new ProtocolException("Cannot have RSV3==true on Control frames");
+            }
+        }
+    }
+
+    @Override
+    public boolean equals(Object obj)
+    {
+        if (this == obj)
+        {
+            return true;
+        }
+        if (obj == null)
+        {
+            return false;
+        }
+        if (getClass() != obj.getClass())
+        {
+            return false;
+        }
+        ControlFrame other = (ControlFrame)obj;
+        if (data == null)
+        {
+            if (other.data != null)
+            {
+                return false;
+            }
+        }
+        else if (!data.equals(other.data))
+        {
+            return false;
+        }
+        if (finRsvOp != other.finRsvOp)
+        {
+            return false;
+        }
+        if (!Arrays.equals(mask,other.mask))
+        {
+            return false;
+        }
+        if (masked != other.masked)
+        {
+            return false;
+        }
+        return true;
+    }
+
+    public boolean isControlFrame()
+    {
+        return true;
+    }
+
+    @Override
+    public boolean isDataFrame()
+    {
+        return false;
+    }
+
+    @Override
+    public WebSocketFrame setPayload(ByteBuffer buf)
+    {
+        if (buf != null && buf.remaining() > MAX_CONTROL_PAYLOAD)
+        {
+            throw new ProtocolException("Control Payloads can not exceed " + MAX_CONTROL_PAYLOAD + " bytes in length.");
+        }
+        return super.setPayload(buf);
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/frames/DataFrame.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/frames/DataFrame.java
new file mode 100644
index 0000000..0f6f79c
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/frames/DataFrame.java
@@ -0,0 +1,85 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.frames;
+
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.common.OpCode;
+import org.eclipse.jetty.websocket.common.WebSocketFrame;
+
+/**
+ * A Data Frame
+ */
+public class DataFrame extends WebSocketFrame
+{
+    protected DataFrame(byte opcode)
+    {
+        super(opcode);
+    }
+
+    /**
+     * Construct new DataFrame based on headers of provided frame.
+     * <p>
+     * Useful for when working in extensions and a new frame needs to be created.
+     */
+    public DataFrame(Frame basedOn)
+    {
+        this(basedOn,false);
+    }
+
+    /**
+     * Construct new DataFrame based on headers of provided frame, overriding for continuations if needed.
+     * <p>
+     * Useful for when working in extensions and a new frame needs to be created.
+     */
+    public DataFrame(Frame basedOn, boolean continuation)
+    {
+        super(basedOn.getOpCode());
+        copyHeaders(basedOn);
+        if (continuation)
+        {
+            setOpCode(OpCode.CONTINUATION);
+        }
+    }
+
+    @Override
+    public void assertValid()
+    {
+        /* no extra validation for data frames (yet) here */
+    }
+
+    @Override
+    public boolean isControlFrame()
+    {
+        return false;
+    }
+
+    @Override
+    public boolean isDataFrame()
+    {
+        return true;
+    }
+
+    /**
+     * Set the data frame to continuation mode
+     */
+    public void setIsContinuation()
+    {
+        setOpCode(OpCode.CONTINUATION);
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/frames/PingFrame.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/frames/PingFrame.java
new file mode 100644
index 0000000..906bc89
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/frames/PingFrame.java
@@ -0,0 +1,50 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.frames;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.websocket.common.OpCode;
+
+public class PingFrame extends ControlFrame
+{
+    public PingFrame()
+    {
+        super(OpCode.PING);
+    }
+
+    public PingFrame setPayload(byte[] bytes)
+    {
+        setPayload(ByteBuffer.wrap(bytes));
+        return this;
+    }
+
+    public PingFrame setPayload(String payload)
+    {
+        setPayload(ByteBuffer.wrap(StringUtil.getUtf8Bytes(payload)));
+        return this;
+    }
+
+    @Override
+    public Type getType()
+    {
+        return Type.PING;
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/frames/PongFrame.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/frames/PongFrame.java
new file mode 100644
index 0000000..2df17b7
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/frames/PongFrame.java
@@ -0,0 +1,50 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.frames;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.websocket.common.OpCode;
+
+public class PongFrame extends ControlFrame
+{
+    public PongFrame()
+    {
+        super(OpCode.PONG);
+    }
+    
+    public PongFrame setPayload(byte[] bytes)
+    {
+        setPayload(ByteBuffer.wrap(bytes));
+        return this;
+    }
+
+    public PongFrame setPayload(String payload)
+    {
+        setPayload(StringUtil.getUtf8Bytes(payload));
+        return this;
+    }
+
+    @Override
+    public Type getType()
+    {
+        return Type.PONG;
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/frames/TextFrame.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/frames/TextFrame.java
new file mode 100644
index 0000000..bee1834
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/frames/TextFrame.java
@@ -0,0 +1,54 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.frames;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.websocket.common.OpCode;
+
+public class TextFrame extends DataFrame
+{
+    public TextFrame()
+    {
+        super(OpCode.TEXT);
+    }
+
+    @Override
+    public Type getType()
+    {
+        return Type.TEXT;
+    }
+
+    public TextFrame setPayload(String str)
+    {
+        setPayload(ByteBuffer.wrap(StringUtil.getUtf8Bytes(str)));
+        return this;
+    }
+    
+    public String getPayloadAsUTF8()
+    {
+        if (data == null)
+        {
+            return null;
+        }
+        return BufferUtil.toUTF8String(data);
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/AbstractWebSocketConnection.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/AbstractWebSocketConnection.java
new file mode 100644
index 0000000..5f9b230
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/AbstractWebSocketConnection.java
@@ -0,0 +1,722 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.io;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.SocketTimeoutException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.eclipse.jetty.io.AbstractConnection;
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.component.Dumpable;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.Scheduler;
+import org.eclipse.jetty.websocket.api.BatchMode;
+import org.eclipse.jetty.websocket.api.CloseException;
+import org.eclipse.jetty.websocket.api.StatusCode;
+import org.eclipse.jetty.websocket.api.SuspendToken;
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.api.WriteCallback;
+import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.common.CloseInfo;
+import org.eclipse.jetty.websocket.common.ConnectionState;
+import org.eclipse.jetty.websocket.common.Generator;
+import org.eclipse.jetty.websocket.common.LogicalConnection;
+import org.eclipse.jetty.websocket.common.Parser;
+import org.eclipse.jetty.websocket.common.WebSocketSession;
+import org.eclipse.jetty.websocket.common.io.IOState.ConnectionStateListener;
+
+/**
+ * Provides the implementation of {@link LogicalConnection} within the framework of the new {@link Connection} framework of {@code jetty-io}.
+ */
+public abstract class AbstractWebSocketConnection extends AbstractConnection implements LogicalConnection, ConnectionStateListener, Dumpable
+{
+    private class Flusher extends FrameFlusher
+    {
+        private Flusher(ByteBufferPool bufferPool, Generator generator, EndPoint endpoint)
+        {
+            super(bufferPool,generator,endpoint,getPolicy().getMaxBinaryMessageBufferSize(),8);
+        }
+
+        @Override
+        protected void onFailure(Throwable x)
+        {
+            session.notifyError(x);
+
+            if (ioState.wasAbnormalClose())
+            {
+                LOG.ignore(x);
+                return;
+            }
+
+            if (LOG.isDebugEnabled())
+                LOG.debug("Write flush failure",x);
+            ioState.onWriteFailure(x);
+        }
+    }
+
+    public class OnDisconnectCallback implements WriteCallback
+    {
+        private final boolean outputOnly;
+
+        public OnDisconnectCallback(boolean outputOnly)
+        {
+            this.outputOnly = outputOnly;
+        }
+
+        @Override
+        public void writeFailed(Throwable x)
+        {
+            disconnect(outputOnly);
+        }
+
+        @Override
+        public void writeSuccess()
+        {
+            disconnect(outputOnly);
+        }
+    }
+
+    public class OnCloseLocalCallback implements WriteCallback
+    {
+        private final WriteCallback callback;
+        private final CloseInfo close;
+
+        public OnCloseLocalCallback(WriteCallback callback, CloseInfo close)
+        {
+            this.callback = callback;
+            this.close = close;
+        }
+
+        public OnCloseLocalCallback(CloseInfo close)
+        {
+            this(null,close);
+        }
+
+        @Override
+        public void writeFailed(Throwable x)
+        {
+            try
+            {
+                if (callback != null)
+                {
+                    callback.writeFailed(x);
+                }
+            }
+            finally
+            {
+                onLocalClose();
+            }
+        }
+
+        @Override
+        public void writeSuccess()
+        {
+            try
+            {
+                if (callback != null)
+                {
+                    callback.writeSuccess();
+                }
+            }
+            finally
+            {
+                onLocalClose();
+            }
+        }
+
+        private void onLocalClose()
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("Local Close Confirmed {}",close);
+            if (close.isAbnormal())
+            {
+                ioState.onAbnormalClose(close);
+            }
+            else
+            {
+                ioState.onCloseLocal(close);
+            }
+        }
+    }
+
+    public static class Stats
+    {
+        private AtomicLong countFillInterestedEvents = new AtomicLong(0);
+        private AtomicLong countOnFillableEvents = new AtomicLong(0);
+        private AtomicLong countFillableErrors = new AtomicLong(0);
+
+        public long getFillableErrorCount()
+        {
+            return countFillableErrors.get();
+        }
+
+        public long getFillInterestedCount()
+        {
+            return countFillInterestedEvents.get();
+        }
+
+        public long getOnFillableCount()
+        {
+            return countOnFillableEvents.get();
+        }
+    }
+    
+    private static enum ReadMode
+    {
+        PARSE,
+        DISCARD,
+        EOF
+    }
+
+    private static final Logger LOG = Log.getLogger(AbstractWebSocketConnection.class);
+
+    /**
+     * Minimum size of a buffer is the determined to be what would be the maximum framing header size (not including payload)
+     */
+    private static final int MIN_BUFFER_SIZE = Generator.MAX_HEADER_LENGTH;
+
+    private final ByteBufferPool bufferPool;
+    private final Scheduler scheduler;
+    private final Generator generator;
+    private final Parser parser;
+    private final WebSocketPolicy policy;
+    private final AtomicBoolean suspendToken;
+    private final FrameFlusher flusher;
+    private WebSocketSession session;
+    private List<ExtensionConfig> extensions;
+    private boolean isFilling;
+    private ByteBuffer buffer;
+    private ReadMode readMode = ReadMode.PARSE;
+    private IOState ioState;
+    private Stats stats = new Stats();
+
+    public AbstractWebSocketConnection(EndPoint endp, Executor executor, Scheduler scheduler, WebSocketPolicy policy, ByteBufferPool bufferPool)
+    {
+        super(endp,executor,EXECUTE_ONFILLABLE); // TODO review if this is best. Specifically with MUX
+        this.policy = policy;
+        this.bufferPool = bufferPool;
+        this.generator = new Generator(policy,bufferPool);
+        this.parser = new Parser(policy,bufferPool);
+        this.scheduler = scheduler;
+        this.extensions = new ArrayList<>();
+        this.suspendToken = new AtomicBoolean(false);
+        this.ioState = new IOState();
+        this.ioState.addListener(this);
+        this.flusher = new Flusher(bufferPool,generator,endp);
+        this.setInputBufferSize(policy.getInputBufferSize());
+        this.setMaxIdleTimeout(policy.getIdleTimeout());
+    }
+
+    @Override
+    public Executor getExecutor()
+    {
+        return super.getExecutor();
+    }
+
+    @Override
+    public void close()
+    {
+        close(StatusCode.NORMAL,null);
+    }
+
+    /**
+     * Close the connection.
+     * <p>                    fillInterested();
+
+     * This can result in a close handshake over the network, or a simple local abnormal close
+     * 
+     * @param statusCode
+     *            the WebSocket status code.
+     * @param reason
+     *            the (optional) reason string. (null is allowed)
+     * @see StatusCode
+     */
+    @Override
+    public void close(int statusCode, String reason)
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("close({},{})",statusCode,reason);
+        CloseInfo close = new CloseInfo(statusCode,reason);
+        this.outgoingFrame(close.asFrame(),new OnCloseLocalCallback(close),BatchMode.OFF);
+    }
+
+    @Override
+    public void disconnect()
+    {
+        disconnect(false);
+    }
+
+    private void disconnect(boolean onlyOutput)
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("{} disconnect({})",policy.getBehavior(),onlyOutput?"outputOnly":"both");
+        // close FrameFlusher, we cannot write anymore at this point.
+        flusher.close();
+        EndPoint endPoint = getEndPoint();
+        // We need to gently close first, to allow
+        // SSL close alerts to be sent by Jetty
+        if (LOG.isDebugEnabled())
+            LOG.debug("Shutting down output {}",endPoint);
+        endPoint.shutdownOutput();
+        if (!onlyOutput)
+        {
+            LOG.debug("Closing {}",endPoint);
+            endPoint.close();
+        }
+    }
+
+    protected void execute(Runnable task)
+    {
+        try
+        {
+            getExecutor().execute(task);
+        }
+        catch (RejectedExecutionException e)
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("Job not dispatched: {}",task);
+        }
+    }
+
+    @Override
+    public void fillInterested()
+    {
+        stats.countFillInterestedEvents.incrementAndGet();
+        super.fillInterested();
+    }
+
+    @Override
+    public ByteBufferPool getBufferPool()
+    {
+        return bufferPool;
+    }
+
+    /**
+     * Get the list of extensions in use.
+     * <p>
+     * This list is negotiated during the WebSocket Upgrade Request/Response handshake.
+     * 
+     * @return the list of negotiated extensions in use.
+     */
+    public List<ExtensionConfig> getExtensions()
+    {
+        return extensions;
+    }
+
+    public Generator getGenerator()
+    {
+        return generator;
+    }
+
+    @Override
+    public long getIdleTimeout()
+    {
+        return getEndPoint().getIdleTimeout();
+    }
+
+    @Override
+    public IOState getIOState()
+    {
+        return ioState;
+    }
+
+    @Override
+    public long getMaxIdleTimeout()
+    {
+        return getEndPoint().getIdleTimeout();
+    }
+
+    public Parser getParser()
+    {
+        return parser;
+    }
+
+    @Override
+    public WebSocketPolicy getPolicy()
+    {
+        return this.policy;
+    }
+
+    @Override
+    public InetSocketAddress getRemoteAddress()
+    {
+        return getEndPoint().getRemoteAddress();
+    }
+
+    public Scheduler getScheduler()
+    {
+        return scheduler;
+    }
+
+    @Override
+    public WebSocketSession getSession()
+    {
+        return session;
+    }
+
+    public Stats getStats()
+    {
+        return stats;
+    }
+
+    @Override
+    public boolean isOpen()
+    {
+        return getIOState().isOpen() && getEndPoint().isOpen();
+    }
+
+    @Override
+    public boolean isReading()
+    {
+        return isFilling;
+    }
+
+    /**
+     * Physical connection disconnect.
+     * <p>
+     * Not related to WebSocket close handshake.
+     */
+    @Override
+    public void onClose()
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("{} onClose()",policy.getBehavior());
+        super.onClose();
+        // ioState.onDisconnected();
+        flusher.close();
+    }
+
+    @Override
+    public void onConnectionStateChange(ConnectionState state)
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("{} Connection State Change: {}",policy.getBehavior(),state);
+        switch (state)
+        {
+            case OPEN:
+                if (BufferUtil.isEmpty(buffer))
+                {
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("fillInterested");
+                    fillInterested();
+                }
+                else
+                    onFillable();
+                break;
+            case CLOSED:
+                if (ioState.wasAbnormalClose())
+                {
+                    // Fire out a close frame, indicating abnormal shutdown, then disconnect
+                    CloseInfo abnormal = new CloseInfo(StatusCode.SHUTDOWN,"Abnormal Close - " + ioState.getCloseInfo().getReason());
+                    outgoingFrame(abnormal.asFrame(),new OnDisconnectCallback(false),BatchMode.OFF);
+                }
+                else
+                {
+                    // Just disconnect
+                    this.disconnect(false);
+                }
+                break;
+            case CLOSING:
+                // First occurrence of .onCloseLocal or .onCloseRemote use
+                if (ioState.wasRemoteCloseInitiated())
+                {
+                    CloseInfo close = ioState.getCloseInfo();
+                    // reply to close handshake from remote
+                    outgoingFrame(close.asFrame(),new OnCloseLocalCallback(new OnDisconnectCallback(true),close),BatchMode.OFF);
+                }
+            default:
+                break;
+        }
+    }
+
+    @Override
+    public void onFillable()
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("{} onFillable()",policy.getBehavior());
+        stats.countOnFillableEvents.incrementAndGet();
+        if (buffer==null)
+            buffer = bufferPool.acquire(getInputBufferSize(),true);
+        try
+        {
+            isFilling = true;
+
+            if(readMode == ReadMode.PARSE)
+            {
+                readMode = readParse(buffer);
+            } 
+            else
+            {
+                readMode = readDiscard(buffer);
+            }
+        }
+        finally
+        {
+            bufferPool.release(buffer);
+            buffer=null;
+        }
+
+        if ((readMode != ReadMode.EOF) && (suspendToken.get() == false))
+        {
+            fillInterested();
+        }
+        else
+        {
+            isFilling = false;
+        }
+    }
+
+    
+
+    @Override
+    protected void onFillInterestedFailed(Throwable cause)
+    {
+        LOG.ignore(cause);
+        stats.countFillInterestedEvents.incrementAndGet();
+        super.onFillInterestedFailed(cause);
+    }
+
+    protected void prefill(ByteBuffer prefilled)
+    {
+        buffer=prefilled;
+    }
+    
+    @Override
+    public void onOpen()
+    {
+        super.onOpen();
+        this.ioState.onOpened();
+    }
+
+    /**
+     * Event for no activity on connection (read or write)
+     */
+    @Override
+    protected boolean onReadTimeout()
+    {
+        IOState state = getIOState();
+        ConnectionState cstate = state.getConnectionState();
+        if (LOG.isDebugEnabled())
+            LOG.debug("{} Read Timeout - {}",policy.getBehavior(),cstate);
+
+        if (cstate == ConnectionState.CLOSED)
+        {
+            // close already completed, extra timeouts not relevant
+            // allow underlying connection and endpoint to disconnect on its own
+            return true;
+        }
+
+        try
+        {
+            session.notifyError(new SocketTimeoutException("Timeout on Read"));
+        }
+        finally
+        {
+            // This is an Abnormal Close condition
+            close(StatusCode.SHUTDOWN,"Idle Timeout");
+        }
+
+        return false;
+    }
+
+    /**
+     * Frame from API, User, or Internal implementation destined for network.
+     */
+    @Override
+    public void outgoingFrame(Frame frame, WriteCallback callback, BatchMode batchMode)
+    {
+        if (LOG.isDebugEnabled())
+        {
+            LOG.debug("outgoingFrame({}, {})",frame,callback);
+        }
+
+        flusher.enqueue(frame,callback,batchMode);
+    }
+
+    private ReadMode readDiscard(ByteBuffer buffer)
+    {
+        EndPoint endPoint = getEndPoint();
+        try
+        {
+            while (true)
+            {
+                int filled = endPoint.fill(buffer);
+                if (filled == 0)
+                {
+                    return ReadMode.DISCARD;
+                }
+                else if (filled < 0)
+                {
+                    LOG.debug("read - EOF Reached (remote: {})",getRemoteAddress());
+                    return ReadMode.EOF;
+                }
+                else
+                {
+                    if (LOG.isDebugEnabled())
+                    {
+                        LOG.debug("Discarded {} bytes - {}",filled,BufferUtil.toDetailString(buffer));
+                    }
+                }
+            }
+        }
+        catch (IOException e)
+        {
+            LOG.ignore(e);
+            return ReadMode.EOF;
+        }
+        catch (Throwable t)
+        {
+            LOG.ignore(t);
+            return ReadMode.DISCARD;
+        }
+    }
+    
+    private ReadMode readParse(ByteBuffer buffer)
+    {
+        EndPoint endPoint = getEndPoint();
+        try
+        {
+            while (true) // TODO: should this honor the LogicalConnection.suspend() ?
+            {
+                int filled = endPoint.fill(buffer);
+                if (filled == 0)
+                {
+                    return ReadMode.PARSE;
+                }
+                else if (filled < 0)
+                {
+                    LOG.debug("read - EOF Reached (remote: {})",getRemoteAddress());
+                    ioState.onReadFailure(new EOFException("Remote Read EOF"));
+                    return ReadMode.EOF;
+                }
+                else
+                {
+                    if (LOG.isDebugEnabled())
+                    {
+                        LOG.debug("Filled {} bytes - {}",filled,BufferUtil.toDetailString(buffer));
+                    }
+                    parser.parse(buffer);
+                }
+            }
+        }
+        catch (IOException e)
+        {
+            LOG.warn(e);
+            close(StatusCode.PROTOCOL,e.getMessage());
+            return ReadMode.DISCARD;
+        }
+        catch (CloseException e)
+        {
+            LOG.debug(e);
+            close(e.getStatusCode(),e.getMessage());
+            return ReadMode.DISCARD;
+        }
+        catch (Throwable t)
+        {
+            LOG.warn(t);
+            close(StatusCode.ABNORMAL,t.getMessage());
+            // TODO: should probably only switch to discard if a non-ws-endpoint error
+            return ReadMode.DISCARD;
+        }
+    }
+
+    @Override
+    public void resume()
+    {
+        if (suspendToken.getAndSet(false))
+        {
+            fillInterested();
+        }
+    }
+
+    /**
+     * Get the list of extensions in use.
+     * <p>
+     * This list is negotiated during the WebSocket Upgrade Request/Response handshake.
+     * 
+     * @param extensions
+     *            the list of negotiated extensions in use.
+     */
+    public void setExtensions(List<ExtensionConfig> extensions)
+    {
+        this.extensions = extensions;
+    }
+
+    @Override
+    public void setInputBufferSize(int inputBufferSize)
+    {
+        if (inputBufferSize < MIN_BUFFER_SIZE)
+        {
+            throw new IllegalArgumentException("Cannot have buffer size less than " + MIN_BUFFER_SIZE);
+        }
+        super.setInputBufferSize(inputBufferSize);
+    }
+
+    @Override
+    public void setMaxIdleTimeout(long ms)
+    {
+        getEndPoint().setIdleTimeout(ms);
+    }
+
+    @Override
+    public void setSession(WebSocketSession session)
+    {
+        this.session = session;
+    }
+
+    @Override
+    public SuspendToken suspend()
+    {
+        suspendToken.set(true);
+        return this;
+    }
+
+    @Override
+    public String dump()
+    {
+        return ContainerLifeCycle.dump(this);
+    }
+
+    @Override
+    public void dump(Appendable out, String indent) throws IOException
+    {
+        out.append(toString()).append(System.lineSeparator());
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s{f=%s,g=%s,p=%s}",super.toString(),flusher,generator,parser);
+    }
+
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/FrameFlusher.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/FrameFlusher.java
new file mode 100644
index 0000000..fb9d35f
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/FrameFlusher.java
@@ -0,0 +1,428 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.io;
+
+import java.io.EOFException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.util.ArrayQueue;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.IteratingCallback;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.BatchMode;
+import org.eclipse.jetty.websocket.api.WriteCallback;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.common.Generator;
+import org.eclipse.jetty.websocket.common.OpCode;
+import org.eclipse.jetty.websocket.common.frames.BinaryFrame;
+
+/**
+ * Interface for working with bytes destined for {@link EndPoint#write(Callback, ByteBuffer...)}
+ */
+public class FrameFlusher
+{
+    private class Flusher extends IteratingCallback
+    {
+        private final List<FrameEntry> entries;
+        private final List<ByteBuffer> buffers;
+        private ByteBuffer aggregate;
+        private BatchMode batchMode;
+
+        public Flusher(int maxGather)
+        {
+            entries = new ArrayList<>(maxGather);
+            buffers = new ArrayList<>((maxGather * 2) + 1);
+        }
+
+        private Action batch()
+        {
+            if (aggregate == null)
+            {
+                aggregate = bufferPool.acquire(bufferSize,true);
+                if (LOG.isDebugEnabled())
+                {
+                    LOG.debug("{} acquired aggregate buffer {}",FrameFlusher.this,aggregate);
+                }
+            }
+
+            // Do not allocate the iterator here.
+            for (int i = 0; i < entries.size(); ++i)
+            {
+                FrameEntry entry = entries.get(i);
+
+                entry.generateHeaderBytes(aggregate);
+
+                ByteBuffer payload = entry.frame.getPayload();
+                if (BufferUtil.hasContent(payload))
+                {
+                    BufferUtil.append(aggregate,payload);
+                }
+            }
+            if (LOG.isDebugEnabled())
+            {
+                LOG.debug("{} aggregated {} frames: {}",FrameFlusher.this,entries.size(),entries);
+            }
+            succeeded();
+            return Action.SCHEDULED;
+        }
+
+        @Override
+        protected void onCompleteSuccess()
+        {
+            // This IteratingCallback never completes.
+        }
+
+        @Override
+        public void onCompleteFailure(Throwable x)
+        {
+            for (FrameEntry entry : entries)
+            {
+                notifyCallbackFailure(entry.callback,x);
+                entry.release();
+            }
+            entries.clear();
+            failure = x;
+            onFailure(x);
+        }
+
+        private Action flush()
+        {
+            if (!BufferUtil.isEmpty(aggregate))
+            {
+                buffers.add(aggregate);
+                if (LOG.isDebugEnabled())
+                {
+                    LOG.debug("{} flushing aggregate {}",FrameFlusher.this,aggregate);
+                }
+            }
+
+            // Do not allocate the iterator here.
+            for (int i = 0; i < entries.size(); ++i)
+            {
+                FrameEntry entry = entries.get(i);
+                // Skip the "synthetic" frame used for flushing.
+                if (entry.frame == FLUSH_FRAME)
+                {
+                    continue;
+                }
+                buffers.add(entry.generateHeaderBytes());
+                ByteBuffer payload = entry.frame.getPayload();
+                if (BufferUtil.hasContent(payload))
+                {
+                    buffers.add(payload);
+                }
+            }
+
+            if (LOG.isDebugEnabled())
+            {
+                LOG.debug("{} flushing {} frames: {}",FrameFlusher.this,entries.size(),entries);
+            }
+
+            if (buffers.isEmpty())
+            {
+                releaseAggregate();
+                // We may have the FLUSH_FRAME to notify.
+                succeedEntries();
+                return Action.IDLE;
+            }
+
+            endpoint.write(this,buffers.toArray(new ByteBuffer[buffers.size()]));
+            buffers.clear();
+            return Action.SCHEDULED;
+        }
+
+        @Override
+        protected Action process() throws Exception
+        {
+            int space = aggregate == null?bufferSize:BufferUtil.space(aggregate);
+            BatchMode currentBatchMode = BatchMode.AUTO;
+            synchronized (lock)
+            {
+                while ((entries.size() <= maxGather) && !queue.isEmpty())
+                {
+                    FrameEntry entry = queue.remove(0);
+                    currentBatchMode = BatchMode.max(currentBatchMode,entry.batchMode);
+
+                    // Force flush if we need to.
+                    if (entry.frame == FLUSH_FRAME)
+                    {
+                        currentBatchMode = BatchMode.OFF;
+                    }
+
+                    int payloadLength = BufferUtil.length(entry.frame.getPayload());
+                    int approxFrameLength = Generator.MAX_HEADER_LENGTH + payloadLength;
+
+                    // If it is a "big" frame, avoid copying into the aggregate buffer.
+                    if (approxFrameLength > (bufferSize >> 2))
+                    {
+                        currentBatchMode = BatchMode.OFF;
+                    }
+
+                    // If the aggregate buffer overflows, do not batch.
+                    space -= approxFrameLength;
+                    if (space <= 0)
+                    {
+                        currentBatchMode = BatchMode.OFF;
+                    }
+
+                    entries.add(entry);
+                }
+            }
+
+            if (LOG.isDebugEnabled())
+            {
+                LOG.debug("{} processing {} entries: {}",FrameFlusher.this,entries.size(),entries);
+            }
+
+            if (entries.isEmpty())
+            {
+                if (batchMode != BatchMode.AUTO)
+                {
+                    // Nothing more to do, release the aggregate buffer if we need to.
+                    // Releasing it here rather than in succeeded() allows for its reuse.
+                    releaseAggregate();
+                    return Action.IDLE;
+                }
+
+                LOG.debug("{} auto flushing",FrameFlusher.this);
+                return flush();
+            }
+
+            batchMode = currentBatchMode;
+
+            return currentBatchMode == BatchMode.OFF?flush():batch();
+        }
+
+        private void releaseAggregate()
+        {
+            if ((aggregate != null) && BufferUtil.isEmpty(aggregate))
+            {
+                bufferPool.release(aggregate);
+                aggregate = null;
+            }
+        }
+
+        @Override
+        public void succeeded()
+        {
+            succeedEntries();
+            super.succeeded();
+        }
+
+        private void succeedEntries()
+        {
+            // Do not allocate the iterator here.
+            for (int i = 0; i < entries.size(); ++i)
+            {
+                FrameEntry entry = entries.get(i);
+                notifyCallbackSuccess(entry.callback);
+                entry.release();
+            }
+            entries.clear();
+        }
+    }
+
+    private class FrameEntry
+    {
+        private final Frame frame;
+        private final WriteCallback callback;
+        private final BatchMode batchMode;
+        private ByteBuffer headerBuffer;
+
+        private FrameEntry(Frame frame, WriteCallback callback, BatchMode batchMode)
+        {
+            this.frame = Objects.requireNonNull(frame);
+            this.callback = callback;
+            this.batchMode = batchMode;
+        }
+
+        private ByteBuffer generateHeaderBytes()
+        {
+            return headerBuffer = generator.generateHeaderBytes(frame);
+        }
+
+        private void generateHeaderBytes(ByteBuffer buffer)
+        {
+            generator.generateHeaderBytes(frame,buffer);
+        }
+
+        private void release()
+        {
+            if (headerBuffer != null)
+            {
+                generator.getBufferPool().release(headerBuffer);
+                headerBuffer = null;
+            }
+        }
+
+        @Override
+        public String toString()
+        {
+            return String.format("%s[%s,%s,%s,%s]",getClass().getSimpleName(),frame,callback,batchMode,failure);
+        }
+    }
+
+    public static final BinaryFrame FLUSH_FRAME = new BinaryFrame();
+    private static final Logger LOG = Log.getLogger(FrameFlusher.class);
+    private final ByteBufferPool bufferPool;
+    private final EndPoint endpoint;
+    private final int bufferSize;
+    private final Generator generator;
+    private final int maxGather;
+    private final Object lock = new Object();
+    private final ArrayQueue<FrameEntry> queue = new ArrayQueue<>(16,16,lock);
+    private final Flusher flusher;
+    private final AtomicBoolean closed = new AtomicBoolean();
+    private volatile Throwable failure;
+
+    public FrameFlusher(ByteBufferPool bufferPool, Generator generator, EndPoint endpoint, int bufferSize, int maxGather)
+    {
+        this.bufferPool = bufferPool;
+        this.endpoint = endpoint;
+        this.bufferSize = bufferSize;
+        this.generator = Objects.requireNonNull(generator);
+        this.maxGather = maxGather;
+        this.flusher = new Flusher(maxGather);
+    }
+
+    public void close()
+    {
+        if (closed.compareAndSet(false,true))
+        {
+            LOG.debug("{} closing {}",this);
+            EOFException eof = new EOFException("Connection has been closed locally");
+            flusher.failed(eof);
+
+            // Fail also queued entries.
+            List<FrameEntry> entries = new ArrayList<>();
+            synchronized (lock)
+            {
+                entries.addAll(queue);
+                queue.clear();
+            }
+            // Notify outside sync block.
+            for (FrameEntry entry : entries)
+            {
+                notifyCallbackFailure(entry.callback,eof);
+            }
+        }
+    }
+
+    public void enqueue(Frame frame, WriteCallback callback, BatchMode batchMode)
+    {
+        if (closed.get())
+        {
+            notifyCallbackFailure(callback,new EOFException("Connection has been closed locally"));
+            return;
+        }
+        if (flusher.isFailed())
+        {
+            notifyCallbackFailure(callback,failure);
+            return;
+        }
+
+        FrameEntry entry = new FrameEntry(frame,callback,batchMode);
+
+        synchronized (lock)
+        {
+            switch (frame.getOpCode())
+            {
+                case OpCode.PING:
+                {
+                    // Prepend PINGs so they are processed first.
+                    queue.add(0,entry);
+                    break;
+                }
+                case OpCode.CLOSE:
+                {
+                    // There may be a chance that other frames are
+                    // added after this close frame, but we will
+                    // fail them later to keep it simple here.
+                    closed.set(true);
+                    queue.add(entry);
+                    break;
+                }
+                default:
+                {
+                    queue.add(entry);
+                    break;
+                }
+            }
+        }
+
+        if (LOG.isDebugEnabled())
+        {
+            LOG.debug("{} queued {}",this,entry);
+        }
+
+        flusher.iterate();
+    }
+
+    protected void notifyCallbackFailure(WriteCallback callback, Throwable failure)
+    {
+        try
+        {
+            if (callback != null)
+            {
+                callback.writeFailed(failure);
+            }
+        }
+        catch (Throwable x)
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("Exception while notifying failure of callback " + callback,x);
+        }
+    }
+
+    protected void notifyCallbackSuccess(WriteCallback callback)
+    {
+        try
+        {
+            if (callback != null)
+            {
+                callback.writeSuccess();
+            }
+        }
+        catch (Throwable x)
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("Exception while notifying success of callback " + callback,x);
+        }
+    }
+
+    protected void onFailure(Throwable x)
+    {
+        LOG.warn(x);
+    }
+
+    @Override
+    public String toString()
+    {
+        ByteBuffer aggregate = flusher.aggregate;
+        return String.format("%s[queueSize=%d,aggregateSize=%d,failure=%s]",getClass().getSimpleName(),queue.size(),aggregate == null?0:aggregate.position(),
+                failure);
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/FramePipes.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/FramePipes.java
new file mode 100644
index 0000000..bbfb43c
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/FramePipes.java
@@ -0,0 +1,84 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.io;
+
+import org.eclipse.jetty.websocket.api.BatchMode;
+import org.eclipse.jetty.websocket.api.WriteCallback;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.api.extensions.IncomingFrames;
+import org.eclipse.jetty.websocket.api.extensions.OutgoingFrames;
+
+public class FramePipes
+{
+    private static class In2Out implements IncomingFrames
+    {
+        private OutgoingFrames outgoing;
+
+        public In2Out(OutgoingFrames outgoing)
+        {
+            this.outgoing = outgoing;
+        }
+
+        @Override
+        public void incomingError(Throwable t)
+        {
+            /* cannot send exception on */
+        }
+
+        @Override
+        public void incomingFrame(Frame frame)
+        {
+            this.outgoing.outgoingFrame(frame,null, BatchMode.OFF);
+        }
+    }
+
+    private static class Out2In implements OutgoingFrames
+    {
+        private IncomingFrames incoming;
+
+        public Out2In(IncomingFrames incoming)
+        {
+            this.incoming = incoming;
+        }
+
+        @Override
+        public void outgoingFrame(Frame frame, WriteCallback callback, BatchMode batchMode)
+        {
+            try
+            {
+                this.incoming.incomingFrame(frame);
+                callback.writeSuccess();
+            }
+            catch (Throwable t)
+            {
+                callback.writeFailed(t);
+            }
+        }
+    }
+
+    public static OutgoingFrames to(final IncomingFrames incoming)
+    {
+        return new Out2In(incoming);
+    }
+
+    public static IncomingFrames to(final OutgoingFrames outgoing)
+    {
+        return new In2Out(outgoing);
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/FutureWriteCallback.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/FutureWriteCallback.java
new file mode 100644
index 0000000..d67e1fb
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/FutureWriteCallback.java
@@ -0,0 +1,48 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.io;
+
+import org.eclipse.jetty.util.FutureCallback;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.WriteCallback;
+
+/**
+ * Allows events to a {@link WriteCallback} to drive a {@link Future} for the user.
+ */
+public class FutureWriteCallback extends FutureCallback implements WriteCallback
+{
+    private static final Logger LOG = Log.getLogger(FutureWriteCallback.class);
+
+    @Override
+    public void writeFailed(Throwable cause)
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug(".writeFailed",cause);
+        failed(cause);
+    }
+
+    @Override
+    public void writeSuccess()
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug(".writeSuccess");
+        succeeded();
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/IOState.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/IOState.java
new file mode 100644
index 0000000..6a8851d
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/IOState.java
@@ -0,0 +1,607 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.io;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.CloseStatus;
+import org.eclipse.jetty.websocket.api.StatusCode;
+import org.eclipse.jetty.websocket.common.CloseInfo;
+import org.eclipse.jetty.websocket.common.ConnectionState;
+
+/**
+ * Simple state tracker for Input / Output and {@link ConnectionState}.
+ * <p>
+ * Use the various known .on*() methods to trigger a state change.
+ * <ul>
+ * <li>{@link #onOpened()} - connection has been opened</li>
+ * </ul>
+ */
+public class IOState
+{
+    /**
+     * The source of a close handshake. (ie: who initiated it).
+     */
+    private static enum CloseHandshakeSource
+    {
+        /** No close handshake initiated (yet) */
+        NONE,
+        /** Local side initiated the close handshake */
+        LOCAL,
+        /** Remote side initiated the close handshake */
+        REMOTE,
+        /** An abnormal close situation (disconnect, timeout, etc...) */
+        ABNORMAL;
+    }
+
+    public static interface ConnectionStateListener
+    {
+        public void onConnectionStateChange(ConnectionState state);
+    }
+
+    private static final Logger LOG = Log.getLogger(IOState.class);
+    private ConnectionState state;
+    private final List<ConnectionStateListener> listeners = new CopyOnWriteArrayList<>();
+
+    /** 
+     * Is input on websocket available (for reading frames).
+     * Used to determine close handshake completion, and track half-close states
+     */
+    private boolean inputAvailable;
+    /** 
+     * Is output on websocket available (for writing frames).
+     * Used to determine close handshake completion, and track half-closed states.
+     */
+    private boolean outputAvailable;
+    /** 
+     * Initiator of the close handshake.
+     * Used to determine who initiated a close handshake for reply reasons.
+     */
+    private CloseHandshakeSource closeHandshakeSource;
+    /**
+     * The close info for the initiator of the close handshake.
+     * It is possible in abnormal close scenarios to have a different
+     * final close info that is used to notify the WS-Endpoint's onClose()
+     * events with.
+     */
+    private CloseInfo closeInfo;
+    /**
+     * Atomic reference to the final close info.
+     * This can only be set once, and is used for the WS-Endpoint's onClose()
+     * event.
+     */
+    private AtomicReference<CloseInfo> finalClose = new AtomicReference<>();
+    /**
+     * Tracker for if the close handshake was completed successfully by
+     * both sides.  False if close was sudden or abnormal.
+     */
+    private boolean cleanClose;
+
+    /**
+     * Create a new IOState, initialized to {@link ConnectionState#CONNECTING}
+     */
+    public IOState()
+    {
+        this.state = ConnectionState.CONNECTING;
+        this.inputAvailable = false;
+        this.outputAvailable = false;
+        this.closeHandshakeSource = CloseHandshakeSource.NONE;
+        this.closeInfo = null;
+        this.cleanClose = false;
+    }
+
+    public void addListener(ConnectionStateListener listener)
+    {
+        listeners.add(listener);
+    }
+
+    public void assertInputOpen() throws IOException
+    {
+        if (!isInputAvailable())
+        {
+            throw new IOException("Connection input is closed");
+        }
+    }
+
+    public void assertOutputOpen() throws IOException
+    {
+        if (!isOutputAvailable())
+        {
+            throw new IOException("Connection output is closed");
+        }
+    }
+
+    public CloseInfo getCloseInfo()
+    {
+        CloseInfo ci = finalClose.get();
+        if (ci != null)
+        {
+            return ci;
+        }
+        return closeInfo;
+    }
+
+    public ConnectionState getConnectionState()
+    {
+        return state;
+    }
+
+    public boolean isClosed()
+    {
+        synchronized (state)
+        {
+            return (state == ConnectionState.CLOSED);
+        }
+    }
+
+    public boolean isInputAvailable()
+    {
+        return inputAvailable;
+    }
+
+    public boolean isOpen()
+    {
+        return (getConnectionState() != ConnectionState.CLOSED);
+    }
+
+    public boolean isOutputAvailable()
+    {
+        return outputAvailable;
+    }
+
+    private void notifyStateListeners(ConnectionState state)
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("Notify State Listeners: {}",state);
+        for (ConnectionStateListener listener : listeners)
+        {
+            if (LOG.isDebugEnabled())
+            {
+                LOG.debug("{}.onConnectionStateChange({})",listener.getClass().getSimpleName(),state.name());
+            }
+            listener.onConnectionStateChange(state);
+        }
+    }
+
+    /**
+     * A websocket connection has been disconnected for abnormal close reasons.
+     * <p>
+     * This is the low level disconnect of the socket. It could be the result of a normal close operation, from an IO error, or even from a timeout.
+     */
+    public void onAbnormalClose(CloseInfo close)
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("onAbnormalClose({})",close);
+        ConnectionState event = null;
+        synchronized (this)
+        {
+            if (this.state == ConnectionState.CLOSED)
+            {
+                // already closed
+                return;
+            }
+
+            if (this.state == ConnectionState.OPEN)
+            {
+                this.cleanClose = false;
+            }
+
+            this.state = ConnectionState.CLOSED;
+            finalClose.compareAndSet(null,close);
+            this.inputAvailable = false;
+            this.outputAvailable = false;
+            this.closeHandshakeSource = CloseHandshakeSource.ABNORMAL;
+            event = this.state;
+        }
+        notifyStateListeners(event);
+    }
+
+    /**
+     * A close handshake has been issued from the local endpoint
+     */
+    public void onCloseLocal(CloseInfo close)
+    {
+        ConnectionState event = null;
+        ConnectionState abnormalEvent = null;
+        ConnectionState initialState = this.state;
+        if (LOG.isDebugEnabled())
+            LOG.debug("onCloseLocal({}) : {}",close,initialState);
+        if (initialState == ConnectionState.CLOSED)
+        {
+            // already closed
+            LOG.debug("already closed");
+            return;
+        }
+
+        if (initialState == ConnectionState.CONNECTED)
+        {
+            // fast close. a local close request from end-user onConnect/onOpen method
+            LOG.debug("FastClose in CONNECTED detected");
+            // Force the state open (to allow read/write to endpoint)
+            onOpened();
+            if (LOG.isDebugEnabled())
+                LOG.debug("FastClose continuing with Closure");
+        }
+
+        synchronized (this)
+        {
+            closeInfo = close;
+
+            boolean in = inputAvailable;
+            boolean out = outputAvailable;
+            if (closeHandshakeSource == CloseHandshakeSource.NONE)
+            {
+                closeHandshakeSource = CloseHandshakeSource.LOCAL;
+            }
+            out = false;
+            outputAvailable = false;
+
+            LOG.debug("onCloseLocal(), input={}, output={}",in,out);
+
+            if (!in && !out)
+            {
+                LOG.debug("Close Handshake satisfied, disconnecting");
+                cleanClose = true;
+                this.state = ConnectionState.CLOSED;
+                finalClose.compareAndSet(null,close);
+                event = this.state;
+            }
+            else if (this.state == ConnectionState.OPEN)
+            {
+                // We are now entering CLOSING (or half-closed)
+                this.state = ConnectionState.CLOSING;
+                event = this.state;
+                
+                // if abnormal, we don't expect an answer.
+                if (close.isAbnormal())
+                {
+                    abnormalEvent = ConnectionState.CLOSED;
+                    finalClose.compareAndSet(null,close);
+                    cleanClose = false;
+                    outputAvailable = false;
+                    inputAvailable = false;
+                    closeHandshakeSource = CloseHandshakeSource.ABNORMAL;
+                }
+            }
+        }
+
+        // Only notify on state change events
+        if (event != null)
+        {
+            notifyStateListeners(event);
+            
+            if(abnormalEvent != null) 
+            {
+                notifyStateListeners(abnormalEvent);
+            }
+        }
+    }
+
+    /**
+     * A close handshake has been received from the remote endpoint
+     */
+    public void onCloseRemote(CloseInfo close)
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("onCloseRemote({})",close);
+        ConnectionState event = null;
+        synchronized (this)
+        {
+            if (this.state == ConnectionState.CLOSED)
+            {
+                // already closed
+                return;
+            }
+
+            closeInfo = close;
+
+            boolean in = inputAvailable;
+            boolean out = outputAvailable;
+            if (closeHandshakeSource == CloseHandshakeSource.NONE)
+            {
+                closeHandshakeSource = CloseHandshakeSource.REMOTE;
+            }
+            in = false;
+            inputAvailable = false;
+
+            if (LOG.isDebugEnabled())
+                LOG.debug("onCloseRemote(), input={}, output={}",in,out);
+
+            if (!in && !out)
+            {
+                LOG.debug("Close Handshake satisfied, disconnecting");
+                cleanClose = true;
+                state = ConnectionState.CLOSED;
+                finalClose.compareAndSet(null,close);
+                event = this.state;
+            }
+            else if (this.state == ConnectionState.OPEN)
+            {
+                // We are now entering CLOSING (or half-closed)
+                this.state = ConnectionState.CLOSING;
+                event = this.state;
+            }
+        }
+
+        // Only notify on state change events
+        if (event != null)
+        {
+            notifyStateListeners(event);
+        }
+    }
+
+    /**
+     * WebSocket has successfully upgraded, but the end-user onOpen call hasn't run yet.
+     * <p>
+     * This is an intermediate state between the RFC's {@link ConnectionState#CONNECTING} and {@link ConnectionState#OPEN}
+     */
+    public void onConnected()
+    {
+        ConnectionState event = null;
+        synchronized (this)
+        {
+            if (this.state != ConnectionState.CONNECTING)
+            {
+                LOG.debug("Unable to set to connected, not in CONNECTING state: {}",this.state);
+                return;
+            }
+
+            this.state = ConnectionState.CONNECTED;
+            inputAvailable = false; // cannot read (yet)
+            outputAvailable = true; // write allowed
+            event = this.state;
+        }
+        notifyStateListeners(event);
+    }
+
+    /**
+     * A websocket connection has failed its upgrade handshake, and is now closed.
+     */
+    public void onFailedUpgrade()
+    {
+        assert (this.state == ConnectionState.CONNECTING);
+        ConnectionState event = null;
+        synchronized (this)
+        {
+            this.state = ConnectionState.CLOSED;
+            cleanClose = false;
+            inputAvailable = false;
+            outputAvailable = false;
+            event = this.state;
+        }
+        notifyStateListeners(event);
+    }
+
+    /**
+     * A websocket connection has finished its upgrade handshake, and is now open.
+     */
+    public void onOpened()
+    {
+        ConnectionState event = null;
+        synchronized (this)
+        {
+            if (this.state == ConnectionState.OPEN)
+            {
+                // already opened
+                return;
+            }
+
+            if (this.state != ConnectionState.CONNECTED)
+            {
+                LOG.debug("Unable to open, not in CONNECTED state: {}",this.state);
+                return;
+            }
+
+            this.state = ConnectionState.OPEN;
+            this.inputAvailable = true;
+            this.outputAvailable = true;
+            event = this.state;
+        }
+        notifyStateListeners(event);
+    }
+
+    /**
+     * The local endpoint has reached a read failure.
+     * <p>
+     * This could be a normal result after a proper close handshake, or even a premature close due to a connection disconnect.
+     */
+    public void onReadFailure(Throwable t)
+    {
+        ConnectionState event = null;
+        synchronized (this)
+        {
+            if (this.state == ConnectionState.CLOSED)
+            {
+                // already closed
+                return;
+            }
+
+         // Build out Close Reason
+            String reason = "WebSocket Read Failure";
+            if (t instanceof EOFException)
+            {
+                reason = "WebSocket Read EOF";
+                Throwable cause = t.getCause();
+                if ((cause != null) && (StringUtil.isNotBlank(cause.getMessage())))
+                {
+                    reason = "EOF: " + cause.getMessage();
+                }
+            }
+            else
+            {
+                if (StringUtil.isNotBlank(t.getMessage()))
+                {
+                    reason = t.getMessage();
+                }
+            }
+
+            reason = CloseStatus.trimMaxReasonLength(reason);
+            CloseInfo close = new CloseInfo(StatusCode.ABNORMAL,reason);
+
+            finalClose.compareAndSet(null,close);
+
+            this.cleanClose = false;
+            this.state = ConnectionState.CLOSED;
+            this.closeInfo = close;
+            this.inputAvailable = false;
+            this.outputAvailable = false;
+            this.closeHandshakeSource = CloseHandshakeSource.ABNORMAL;
+            event = this.state;
+        }
+        notifyStateListeners(event);
+    }
+
+    /**
+     * The local endpoint has reached a write failure.
+     * <p>
+     * A low level I/O failure, or even a jetty side EndPoint close (from idle timeout) are a few scenarios
+     */
+    public void onWriteFailure(Throwable t)
+    {
+        ConnectionState event = null;
+        synchronized (this)
+        {
+            if (this.state == ConnectionState.CLOSED)
+            {
+                // already closed
+                return;
+            }
+
+            // Build out Close Reason
+            String reason = "WebSocket Write Failure";
+            if (t instanceof EOFException)
+            {
+                reason = "WebSocket Write EOF";
+                Throwable cause = t.getCause();
+                if ((cause != null) && (StringUtil.isNotBlank(cause.getMessage())))
+                {
+                    reason = "EOF: " + cause.getMessage();
+                }
+            }
+            else
+            {
+                if (StringUtil.isNotBlank(t.getMessage()))
+                {
+                    reason = t.getMessage();
+                }
+            }
+
+            reason = CloseStatus.trimMaxReasonLength(reason);
+            CloseInfo close = new CloseInfo(StatusCode.ABNORMAL,reason);
+
+            finalClose.compareAndSet(null,close);
+
+            this.cleanClose = false;
+            this.state = ConnectionState.CLOSED;
+            this.inputAvailable = false;
+            this.outputAvailable = false;
+            this.closeHandshakeSource = CloseHandshakeSource.ABNORMAL;
+            event = this.state;
+        }
+        notifyStateListeners(event);
+    }
+
+    public void onDisconnected()
+    {
+        ConnectionState event = null;
+        synchronized (this)
+        {
+            if (this.state == ConnectionState.CLOSED)
+            {
+                // already closed
+                return;
+            }
+
+            CloseInfo close = new CloseInfo(StatusCode.ABNORMAL,"Disconnected");
+
+            this.cleanClose = false;
+            this.state = ConnectionState.CLOSED;
+            this.closeInfo = close;
+            this.inputAvailable = false;
+            this.outputAvailable = false;
+            this.closeHandshakeSource = CloseHandshakeSource.ABNORMAL;
+            event = this.state;
+        }
+        notifyStateListeners(event);
+    }
+
+    @Override
+    public String toString()
+    {
+        StringBuilder str = new StringBuilder();
+        str.append(this.getClass().getSimpleName());
+        str.append("@").append(Integer.toHexString(hashCode()));
+        str.append("[").append(state);
+        str.append(',');
+        if (!inputAvailable)
+        {
+            str.append('!');
+        }
+        str.append("in,");
+        if (!outputAvailable)
+        {
+            str.append('!');
+        }
+        str.append("out");
+        if ((state == ConnectionState.CLOSED) || (state == ConnectionState.CLOSING))
+        {
+            CloseInfo ci = finalClose.get();
+            if (ci != null)
+            {
+                str.append(",finalClose=").append(ci);
+            }
+            else
+            {
+                str.append(",close=").append(closeInfo);
+            }
+            str.append(",clean=").append(cleanClose);
+            str.append(",closeSource=").append(closeHandshakeSource);
+        }
+        str.append(']');
+        return str.toString();
+    }
+
+    public boolean wasAbnormalClose()
+    {
+        return closeHandshakeSource == CloseHandshakeSource.ABNORMAL;
+    }
+
+    public boolean wasCleanClose()
+    {
+        return cleanClose;
+    }
+
+    public boolean wasLocalCloseInitiated()
+    {
+        return closeHandshakeSource == CloseHandshakeSource.LOCAL;
+    }
+
+    public boolean wasRemoteCloseInitiated()
+    {
+        return closeHandshakeSource == CloseHandshakeSource.REMOTE;
+    }
+
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/WriteCallbackWrapper.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/WriteCallbackWrapper.java
new file mode 100644
index 0000000..543c3d1
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/WriteCallbackWrapper.java
@@ -0,0 +1,58 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.io;
+
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.websocket.api.WriteCallback;
+
+/**
+ * Wraps the exposed {@link WriteCallback} WebSocket API with a Jetty {@link Callback}.
+ * <p>
+ * We don't expose the jetty {@link Callback} object to the webapp, as that makes things complicated for the WebAppContext's Classloader.
+ */
+public class WriteCallbackWrapper implements Callback
+{
+    public static Callback wrap(WriteCallback callback)
+    {
+        if (callback == null)
+        {
+            return null;
+        }
+        return new WriteCallbackWrapper(callback);
+    }
+
+    private final WriteCallback callback;
+
+    public WriteCallbackWrapper(WriteCallback callback)
+    {
+        this.callback = callback;
+    }
+
+    @Override
+    public void failed(Throwable x)
+    {
+        callback.writeFailed(x);
+    }
+
+    @Override
+    public void succeeded()
+    {
+        callback.writeSuccess();
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/http/HttpResponseHeaderParseListener.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/http/HttpResponseHeaderParseListener.java
new file mode 100644
index 0000000..027ebba
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/http/HttpResponseHeaderParseListener.java
@@ -0,0 +1,32 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.io.http;
+
+import java.nio.ByteBuffer;
+
+public interface HttpResponseHeaderParseListener
+{
+    void addHeader(String name, String value);
+
+    void setRemainingBuffer(ByteBuffer copy);
+
+    void setStatusCode(int statusCode);
+
+    void setStatusReason(String statusReason);
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/http/HttpResponseHeaderParser.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/http/HttpResponseHeaderParser.java
new file mode 100644
index 0000000..d7c7847
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/http/HttpResponseHeaderParser.java
@@ -0,0 +1,143 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.io.http;
+
+import java.nio.ByteBuffer;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.Utf8LineParser;
+
+/**
+ * Responsible for reading UTF8 Response Header lines and parsing them into a provided UpgradeResponse object.
+ */
+public class HttpResponseHeaderParser
+{
+    @SuppressWarnings("serial")
+    public static class ParseException extends RuntimeException
+    {
+        public ParseException(String message)
+        {
+            super(message);
+        }
+
+        public ParseException(String message, Throwable cause)
+        {
+            super(message,cause);
+        }
+    }
+
+    private enum State
+    {
+        STATUS_LINE,
+        HEADER,
+        END
+    }
+
+    private static final Pattern PAT_HEADER = Pattern.compile("([^:]+):\\s*(.*)");
+    private static final Pattern PAT_STATUS_LINE = Pattern.compile("^HTTP/1.[01]\\s+(\\d+)\\s+(.*)",Pattern.CASE_INSENSITIVE);
+
+    private final HttpResponseHeaderParseListener listener;
+    private final Utf8LineParser lineParser;
+    private State state;
+
+    public HttpResponseHeaderParser(HttpResponseHeaderParseListener listener)
+    {
+        this.listener = listener;
+        this.lineParser = new Utf8LineParser();
+        this.state = State.STATUS_LINE;
+    }
+
+    public boolean isDone()
+    {
+        return (state == State.END);
+    }
+
+    public HttpResponseHeaderParseListener parse(ByteBuffer buf) throws ParseException
+    {
+        while (!isDone() && (buf.remaining() > 0))
+        {
+            String line = lineParser.parse(buf);
+            if (line != null)
+            {
+                if (parseHeader(line))
+                {
+                    // Finished parsing entire header
+                    ByteBuffer copy = ByteBuffer.allocate(buf.remaining());
+                    BufferUtil.put(buf,copy);
+                    BufferUtil.flipToFlush(copy,0);
+                    this.listener.setRemainingBuffer(copy);
+                    return listener;
+                }
+            }
+        }
+        return null;
+    }
+
+    private boolean parseHeader(String line) throws ParseException
+    {
+        switch (state)
+        {
+            case STATUS_LINE:
+            {
+                Matcher mat = PAT_STATUS_LINE.matcher(line);
+                if (!mat.matches())
+                {
+                    throw new ParseException("Unexpected HTTP response status line [" + line + "]");
+                }
+
+                try
+                {
+                    listener.setStatusCode(Integer.parseInt(mat.group(1)));
+                }
+                catch (NumberFormatException e)
+                {
+                    throw new ParseException("Unexpected HTTP response status code",e);
+                }
+                listener.setStatusReason(mat.group(2));
+                state = State.HEADER;
+                break;
+            }
+            case HEADER:
+            {
+                if (StringUtil.isBlank(line))
+                {
+                    state = State.END;
+                    return parseHeader(line);
+                }
+
+                Matcher header = PAT_HEADER.matcher(line);
+                if (header.matches())
+                {
+                    String headerName = header.group(1);
+                    String headerValue = header.group(2);
+                    // do need to split header/value if comma delimited?
+                    listener.addHeader(headerName,headerValue);
+                }
+                break;
+            }
+            case END:
+                state = State.STATUS_LINE;
+                return true;
+        }
+        return false;
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/package-info.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/package-info.java
new file mode 100644
index 0000000..1d3fd41
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty WebSocket Common : I/O Implementation
+ */
+package org.eclipse.jetty.websocket.common.io;
+
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/payload/DeMaskProcessor.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/payload/DeMaskProcessor.java
new file mode 100644
index 0000000..215ad74
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/payload/DeMaskProcessor.java
@@ -0,0 +1,80 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.io.payload;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+
+public class DeMaskProcessor implements PayloadProcessor
+{
+    private byte[] maskBytes;
+    private int maskInt;
+    private int maskOffset;
+
+    @Override
+    public void process(ByteBuffer payload)
+    {
+        if (maskBytes == null)
+        {
+            return;
+        }
+
+        int maskInt = this.maskInt;
+        int start = payload.position();
+        int end = payload.limit();
+        int offset = this.maskOffset;
+        int remaining;
+        while ((remaining = end - start) > 0)
+        {
+            if (remaining >= 4 && (offset & 3) == 0)
+            {
+                payload.putInt(start,payload.getInt(start) ^ maskInt);
+                start += 4;
+                offset += 4;
+            }
+            else
+            {
+                payload.put(start,(byte)(payload.get(start) ^ maskBytes[offset & 3]));
+                ++start;
+                ++offset;
+            }
+        }
+        maskOffset = offset;
+    }
+
+    public void reset(byte[] mask)
+    {
+        this.maskBytes = mask;
+        int maskInt = 0;
+        if (mask != null)
+        {
+            for (byte maskByte : mask)
+                maskInt = (maskInt << 8) + (maskByte & 0xFF);
+        }
+        this.maskInt = maskInt;
+        this.maskOffset = 0;
+    }
+
+    @Override
+    public void reset(Frame frame)
+    {
+        reset(frame.getMask());
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/payload/PayloadProcessor.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/payload/PayloadProcessor.java
new file mode 100644
index 0000000..dc9cc95
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/payload/PayloadProcessor.java
@@ -0,0 +1,41 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.io.payload;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+
+/**
+ * Process the payload (for demasking, validating, etc..)
+ */
+public interface PayloadProcessor
+{
+    /**
+     * Used to process payloads for in the spec.
+     * 
+     * @param payload
+     *            the payload to process
+     * @throws BadPayloadException
+     *             the exception when the payload fails to validate properly
+     */
+    public void process(ByteBuffer payload);
+
+    public void reset(Frame frame);
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/payload/package-info.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/payload/package-info.java
new file mode 100644
index 0000000..71d3b3e
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/payload/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty WebSocket Common : I/O : Frame Payload Handling
+ */
+package org.eclipse.jetty.websocket.common.io.payload;
+
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/MessageAppender.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/MessageAppender.java
new file mode 100644
index 0000000..e2d5525
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/MessageAppender.java
@@ -0,0 +1,47 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.message;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+/**
+ * Appender for messages (used for multiple frames with continuations, and also to allow for streaming APIs)
+ */
+public interface MessageAppender
+{
+    /**
+     * Append the frame payload to the message.
+     * 
+     * @param framePayload
+     *            the frame payload to append.
+     * @param isLast
+     *            flag indicating if this is the last part of the message or not.
+     * @throws IOException
+     *             if unable to append the frame payload
+     */
+    abstract void appendFrame(ByteBuffer framePayload, boolean isLast) throws IOException;
+
+    /**
+     * Notification that message is to be considered complete.
+     * <p>
+     * Any cleanup or final actions should be taken here.
+     */
+    abstract void messageComplete();
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/MessageInputStream.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/MessageInputStream.java
new file mode 100644
index 0000000..7bd35a1
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/MessageInputStream.java
@@ -0,0 +1,197 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.message;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.util.concurrent.BlockingDeque;
+import java.util.concurrent.LinkedBlockingDeque;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * Support class for reading a (single) WebSocket BINARY message via a InputStream.
+ * <p/>
+ * An InputStream that can access a queue of ByteBuffer payloads, along with expected InputStream blocking behavior.
+ */
+public class MessageInputStream extends InputStream implements MessageAppender
+{
+    private static final Logger LOG = Log.getLogger(MessageInputStream.class);
+    private static final ByteBuffer EOF = ByteBuffer.allocate(0).asReadOnlyBuffer();
+
+    private final BlockingDeque<ByteBuffer> buffers = new LinkedBlockingDeque<>();
+    private AtomicBoolean closed = new AtomicBoolean(false);
+    private final long timeoutMs;
+    private ByteBuffer activeBuffer = null;
+
+    public MessageInputStream()
+    {
+        this(-1);
+    }
+
+    public MessageInputStream(int timeoutMs)
+    {
+        this.timeoutMs = timeoutMs;
+    }
+
+    @Override
+    public void appendFrame(ByteBuffer framePayload, boolean fin) throws IOException
+    {
+        if (LOG.isDebugEnabled())
+        {
+            LOG.debug("Appending {} chunk: {}",fin?"final":"non-final",BufferUtil.toDetailString(framePayload));
+        }
+
+        // If closed, we should just toss incoming payloads into the bit bucket.
+        if (closed.get())
+        {
+            return;
+        }
+
+        // Put the payload into the queue, by copying it.
+        // Copying is necessary because the payload will
+        // be processed after this method returns.
+        try
+        {
+            if (framePayload == null)
+            {
+                // skip if no payload
+                return;
+            }
+
+            int capacity = framePayload.remaining();
+            if (capacity <= 0)
+            {
+                // skip if no payload data to copy
+                return;
+            }
+            // TODO: the copy buffer should be pooled too, but no buffer pool available from here.
+            ByteBuffer copy = framePayload.isDirect()?ByteBuffer.allocateDirect(capacity):ByteBuffer.allocate(capacity);
+            copy.put(framePayload).flip();
+            buffers.put(copy);
+        }
+        catch (InterruptedException e)
+        {
+            throw new IOException(e);
+        }
+        finally
+        {
+            if (fin)
+            {
+                buffers.offer(EOF);
+            }
+        }
+    }
+
+    @Override
+    public void close() throws IOException
+    {
+        if (closed.compareAndSet(false, true))
+        {
+            buffers.offer(EOF);
+            super.close();
+        }
+    }
+
+    @Override
+    public void mark(int readlimit)
+    {
+        // Not supported.
+    }
+
+    @Override
+    public boolean markSupported()
+    {
+        return false;
+    }
+
+    @Override
+    public void messageComplete()
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("Message completed");
+        buffers.offer(EOF);
+    }
+
+    @Override
+    public int read() throws IOException
+    {
+        try
+        {
+            if (closed.get())
+            {
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Stream closed");
+                return -1;
+            }
+
+            // grab a fresh buffer
+            while (activeBuffer == null || !activeBuffer.hasRemaining())
+            {
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Waiting {} ms to read", timeoutMs);
+                if (timeoutMs < 0)
+                {
+                    // Wait forever until a buffer is available.
+                    activeBuffer = buffers.take();
+                }
+                else
+                {
+                    // Wait at most for the given timeout.
+                    activeBuffer = buffers.poll(timeoutMs, TimeUnit.MILLISECONDS);
+                    if (activeBuffer == null)
+                    {
+                        throw new IOException(String.format("Read timeout: %,dms expired", timeoutMs));
+                    }
+                }
+
+                if (activeBuffer == EOF)
+                {
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("Reached EOF");
+                    // Be sure that this stream cannot be reused.
+                    closed.set(true);
+                    // Removed buffers that may have remained in the queue.
+                    buffers.clear();
+                    return -1;
+                }
+            }
+
+            return activeBuffer.get() & 0xFF;
+        }
+        catch (InterruptedException x)
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("Interrupted while waiting to read", x);
+            closed.set(true);
+            return -1;
+        }
+    }
+
+    @Override
+    public void reset() throws IOException
+    {
+        throw new IOException("reset() not supported");
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/MessageOutputStream.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/MessageOutputStream.java
new file mode 100644
index 0000000..6a9fef0
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/MessageOutputStream.java
@@ -0,0 +1,219 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.message;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.BatchMode;
+import org.eclipse.jetty.websocket.api.WriteCallback;
+import org.eclipse.jetty.websocket.api.extensions.OutgoingFrames;
+import org.eclipse.jetty.websocket.common.BlockingWriteCallback;
+import org.eclipse.jetty.websocket.common.BlockingWriteCallback.WriteBlocker;
+import org.eclipse.jetty.websocket.common.WebSocketSession;
+import org.eclipse.jetty.websocket.common.frames.BinaryFrame;
+
+/**
+ * Support for writing a single WebSocket BINARY message via a {@link OutputStream}
+ */
+public class MessageOutputStream extends OutputStream
+{
+    private static final Logger LOG = Log.getLogger(MessageOutputStream.class);
+
+    private final OutgoingFrames outgoing;
+    private final ByteBufferPool bufferPool;
+    private final BlockingWriteCallback blocker;
+    private long frameCount;
+    private BinaryFrame frame;
+    private ByteBuffer buffer;
+    private WriteCallback callback;
+    private boolean closed;
+
+    public MessageOutputStream(WebSocketSession session)
+    {
+        this(session.getOutgoingHandler(), session.getPolicy().getMaxBinaryMessageBufferSize(), session.getBufferPool());
+    }
+
+    public MessageOutputStream(OutgoingFrames outgoing, int bufferSize, ByteBufferPool bufferPool)
+    {
+        this.outgoing = outgoing;
+        this.bufferPool = bufferPool;
+        this.blocker = new BlockingWriteCallback();
+        this.buffer = bufferPool.acquire(bufferSize, true);
+        BufferUtil.flipToFill(buffer);
+        this.frame = new BinaryFrame();
+    }
+
+    @Override
+    public void write(byte[] bytes, int off, int len) throws IOException
+    {
+        try
+        {
+            send(bytes, off, len);
+        }
+        catch (Throwable x)
+        {
+            // Notify without holding locks.
+            notifyFailure(x);
+            throw x;
+        }
+    }
+
+    @Override
+    public void write(int b) throws IOException
+    {
+        try
+        {
+            send(new byte[]{(byte)b}, 0, 1);
+        }
+        catch (Throwable x)
+        {
+            // Notify without holding locks.
+            notifyFailure(x);
+            throw x;
+        }
+    }
+
+    @Override
+    public void flush() throws IOException
+    {
+        try
+        {
+            flush(false);
+        }
+        catch (Throwable x)
+        {
+            // Notify without holding locks.
+            notifyFailure(x);
+            throw x;
+        }
+    }
+
+    @Override
+    public void close() throws IOException
+    {
+        try
+        {
+            flush(true);
+            bufferPool.release(buffer);
+            if (LOG.isDebugEnabled())
+                LOG.debug("Stream closed, {} frames sent", frameCount);
+            // Notify without holding locks.
+            notifySuccess();
+        }
+        catch (Throwable x)
+        {
+            // Notify without holding locks.
+            notifyFailure(x);
+            throw x;
+        }
+    }
+
+    private void flush(boolean fin) throws IOException
+    {
+        synchronized (this)
+        {
+            if (closed)
+                throw new IOException("Stream is closed");
+
+            closed = fin;
+
+            BufferUtil.flipToFlush(buffer, 0);
+            frame.setPayload(buffer);
+            frame.setFin(fin);
+
+            try(WriteBlocker b=blocker.acquireWriteBlocker())
+            {
+                outgoing.outgoingFrame(frame, b, BatchMode.OFF);
+                b.block();
+            }
+
+            ++frameCount;
+            // Any flush after the first will be a CONTINUATION frame.
+            frame.setIsContinuation();
+
+            BufferUtil.flipToFill(buffer);
+        }
+    }
+
+    private void send(byte[] bytes, int offset, int length) throws IOException
+    {
+        synchronized (this)
+        {
+            if (closed)
+                throw new IOException("Stream is closed");
+
+            while (length > 0)
+            {
+                // There may be no space available, we want
+                // to handle correctly when space == 0.
+                int space = buffer.remaining();
+                int size = Math.min(space, length);
+                buffer.put(bytes, offset, size);
+                offset += size;
+                length -= size;
+                if (length > 0)
+                {
+                    // If we could not write everything, it means
+                    // that the buffer was full, so flush it.
+                    flush(false);
+                }
+            }
+        }
+    }
+
+    public void setCallback(WriteCallback callback)
+    {
+        synchronized (this)
+        {
+            this.callback = callback;
+        }
+    }
+
+    private void notifySuccess()
+    {
+        WriteCallback callback;
+        synchronized (this)
+        {
+            callback = this.callback;
+        }
+        if (callback != null)
+        {
+            callback.writeSuccess();
+        }
+    }
+
+    private void notifyFailure(Throwable failure)
+    {
+        WriteCallback callback;
+        synchronized (this)
+        {
+            callback = this.callback;
+        }
+        if (callback != null)
+        {
+            callback.writeFailed(failure);
+        }
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/MessageReader.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/MessageReader.java
new file mode 100644
index 0000000..100152f
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/MessageReader.java
@@ -0,0 +1,52 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.message;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * Support class for reading a (single) WebSocket TEXT message via a Reader.
+ * <p/>
+ * In compliance to the WebSocket spec, this reader always uses the UTF8 {@link Charset}.
+ */
+public class MessageReader extends InputStreamReader implements MessageAppender
+{
+    private final MessageInputStream stream;
+
+    public MessageReader(MessageInputStream stream)
+    {
+        super(stream, StandardCharsets.UTF_8);
+        this.stream = stream;
+    }
+
+    @Override
+    public void appendFrame(ByteBuffer payload, boolean isLast) throws IOException
+    {
+        this.stream.appendFrame(payload, isLast);
+    }
+
+    @Override
+    public void messageComplete()
+    {
+        this.stream.messageComplete();
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/MessageWriter.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/MessageWriter.java
new file mode 100644
index 0000000..8272cea
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/MessageWriter.java
@@ -0,0 +1,225 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.message;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.BatchMode;
+import org.eclipse.jetty.websocket.api.WriteCallback;
+import org.eclipse.jetty.websocket.api.extensions.OutgoingFrames;
+import org.eclipse.jetty.websocket.common.BlockingWriteCallback;
+import org.eclipse.jetty.websocket.common.BlockingWriteCallback.WriteBlocker;
+import org.eclipse.jetty.websocket.common.WebSocketSession;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
+
+/**
+ * Support for writing a single WebSocket TEXT message via a {@link Writer}
+ * <p/>
+ * Note: Per WebSocket spec, all WebSocket TEXT messages must be encoded in UTF-8
+ */
+public class MessageWriter extends Writer
+{
+    private static final Logger LOG = Log.getLogger(MessageWriter.class);
+
+    private final OutgoingFrames outgoing;
+    private final ByteBufferPool bufferPool;
+    private final BlockingWriteCallback blocker;
+    private long frameCount;
+    private TextFrame frame;
+    private ByteBuffer buffer;
+    private Utf8CharBuffer utf;
+    private WriteCallback callback;
+    private boolean closed;
+
+    public MessageWriter(WebSocketSession session)
+    {
+        this(session.getOutgoingHandler(), session.getPolicy().getMaxTextMessageBufferSize(), session.getBufferPool());
+    }
+
+    public MessageWriter(OutgoingFrames outgoing, int bufferSize, ByteBufferPool bufferPool)
+    {
+        this.outgoing = outgoing;
+        this.bufferPool = bufferPool;
+        this.blocker = new BlockingWriteCallback();
+        this.buffer = bufferPool.acquire(bufferSize, true);
+        BufferUtil.flipToFill(buffer);
+        this.frame = new TextFrame();
+        this.utf = Utf8CharBuffer.wrap(buffer);
+    }
+
+    @Override
+    public void write(char[] chars, int off, int len) throws IOException
+    {
+        try
+        {
+            send(chars, off, len);
+        }
+        catch (Throwable x)
+        {
+            // Notify without holding locks.
+            notifyFailure(x);
+            throw x;
+        }
+    }
+
+    @Override
+    public void write(int c) throws IOException
+    {
+        try
+        {
+            send(new char[]{(char)c}, 0, 1);
+        }
+        catch (Throwable x)
+        {
+            // Notify without holding locks.
+            notifyFailure(x);
+            throw x;
+        }
+    }
+
+    @Override
+    public void flush() throws IOException
+    {
+        try
+        {
+            flush(false);
+        }
+        catch (Throwable x)
+        {
+            // Notify without holding locks.
+            notifyFailure(x);
+            throw x;
+        }
+    }
+
+    @Override
+    public void close() throws IOException
+    {
+        try
+        {
+            flush(true);
+            bufferPool.release(buffer);
+            if (LOG.isDebugEnabled())
+                LOG.debug("Stream closed, {} frames sent", frameCount);
+            // Notify without holding locks.
+            notifySuccess();
+        }
+        catch (Throwable x)
+        {
+            // Notify without holding locks.
+            notifyFailure(x);
+            throw x;
+        }
+    }
+
+    private void flush(boolean fin) throws IOException
+    {
+        synchronized (this)
+        {
+            if (closed)
+                throw new IOException("Stream is closed");
+
+            closed = fin;
+
+            ByteBuffer data = utf.getByteBuffer();
+            if (LOG.isDebugEnabled())
+                LOG.debug("flush({}): {}", fin, BufferUtil.toDetailString(buffer));
+            frame.setPayload(data);
+            frame.setFin(fin);
+
+            try (WriteBlocker b = blocker.acquireWriteBlocker())
+            {
+                outgoing.outgoingFrame(frame, b, BatchMode.OFF);
+                b.block();
+            }
+
+            ++frameCount;
+            // Any flush after the first will be a CONTINUATION frame.
+            frame.setIsContinuation();
+
+            utf.clear();
+        }
+    }
+
+    private void send(char[] chars, int offset, int length) throws IOException
+    {
+        synchronized (this)
+        {
+            if (closed)
+                throw new IOException("Stream is closed");
+
+            while (length > 0)
+            {
+                // There may be no space available, we want
+                // to handle correctly when space == 0.
+                int space = utf.remaining();
+                int size = Math.min(space, length);
+                utf.append(chars, offset, size);
+                offset += size;
+                length -= size;
+                if (length > 0)
+                {
+                    // If we could not write everything, it means
+                    // that the buffer was full, so flush it.
+                    flush(false);
+                }
+            }
+        }
+    }
+
+    public void setCallback(WriteCallback callback)
+    {
+        synchronized (this)
+        {
+            this.callback = callback;
+        }
+    }
+
+    private void notifySuccess()
+    {
+        WriteCallback callback;
+        synchronized (this)
+        {
+            callback = this.callback;
+        }
+        if (callback != null)
+        {
+            callback.writeSuccess();
+        }
+    }
+
+    private void notifyFailure(Throwable failure)
+    {
+        WriteCallback callback;
+        synchronized (this)
+        {
+            callback = this.callback;
+        }
+        if (callback != null)
+        {
+            callback.writeFailed(failure);
+        }
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/SimpleBinaryMessage.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/SimpleBinaryMessage.java
new file mode 100644
index 0000000..4ec3493
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/SimpleBinaryMessage.java
@@ -0,0 +1,70 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.message;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.websocket.common.events.EventDriver;
+
+public class SimpleBinaryMessage implements MessageAppender
+{
+    private static final int BUFFER_SIZE = 65535;
+    private final EventDriver onEvent;
+    protected final ByteArrayOutputStream out;
+    private int size;
+    protected boolean finished;
+
+    public SimpleBinaryMessage(EventDriver onEvent)
+    {
+        this.onEvent = onEvent;
+        this.out = new ByteArrayOutputStream(BUFFER_SIZE);
+        finished = false;
+    }
+
+    @Override
+    public void appendFrame(ByteBuffer payload, boolean isLast) throws IOException
+    {
+        if (finished)
+        {
+            throw new IOException("Cannot append to finished buffer");
+        }
+
+        if (payload == null)
+        {
+            // empty payload is valid
+            return;
+        }
+
+        onEvent.getPolicy().assertValidBinaryMessageSize(size + payload.remaining());
+        size += payload.remaining();
+
+        BufferUtil.writeTo(payload,out);
+    }
+
+    @Override
+    public void messageComplete()
+    {
+        finished = true;
+        byte data[] = out.toByteArray();
+        onEvent.onBinaryMessage(data);
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/SimpleTextMessage.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/SimpleTextMessage.java
new file mode 100644
index 0000000..98ee570
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/SimpleTextMessage.java
@@ -0,0 +1,71 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.message;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.Utf8StringBuilder;
+import org.eclipse.jetty.websocket.common.events.EventDriver;
+
+public class SimpleTextMessage implements MessageAppender
+{
+    private final EventDriver onEvent;
+    protected final Utf8StringBuilder utf;
+    private int size = 0;
+    protected boolean finished;
+
+    public SimpleTextMessage(EventDriver onEvent)
+    {
+        this.onEvent = onEvent;
+        this.utf = new Utf8StringBuilder(1024);
+        size = 0;
+        finished = false;
+    }
+
+    @Override
+    public void appendFrame(ByteBuffer payload, boolean isLast) throws IOException
+    {
+        if (finished)
+        {
+            throw new IOException("Cannot append to finished buffer");
+        }
+
+        if (payload == null)
+        {
+            // empty payload is valid
+            return;
+        }
+
+        onEvent.getPolicy().assertValidTextMessageSize(size + payload.remaining());
+        size += payload.remaining();
+
+        // allow for fast fail of BAD utf (incomplete utf will trigger on messageComplete)
+        this.utf.append(payload);
+    }
+
+    @Override
+    public void messageComplete()
+    {
+        finished = true;
+
+        // notify event
+        onEvent.onTextMessage(utf.toString());
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/Utf8CharBuffer.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/Utf8CharBuffer.java
new file mode 100644
index 0000000..6f5dde4
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/Utf8CharBuffer.java
@@ -0,0 +1,112 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.message;
+
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.StandardCharsets;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Utf8Appendable;
+
+/**
+ * A CharBuffer wrapped with the Utf8Appendable logic.
+ */
+public class Utf8CharBuffer extends Utf8Appendable
+{
+    /**
+     * Convenience method to wrap a ByteBuffer with a {@link Utf8CharBuffer}
+     * 
+     * @param buffer
+     *            the buffer to wrap
+     * @return the Utf8ByteBuffer for the provided ByteBuffer
+     */
+    public static Utf8CharBuffer wrap(ByteBuffer buffer)
+    {
+        return new Utf8CharBuffer(buffer.asCharBuffer());
+    }
+
+    private final CharBuffer buffer;
+
+    private Utf8CharBuffer(CharBuffer buffer)
+    {
+        super(buffer);
+        this.buffer = buffer;
+    }
+
+    public void append(char[] cbuf, int offset, int size)
+    {
+        append(BufferUtil.toDirectBuffer(new String(cbuf,offset,size),StandardCharsets.UTF_8));
+    }
+
+    public void append(int c)
+    {
+        buffer.append((char)c);
+    }
+
+    public void clear()
+    {
+        buffer.position(0);
+        buffer.limit(buffer.capacity());
+    }
+
+    public ByteBuffer getByteBuffer()
+    {
+        // remember settings
+        int limit = buffer.limit();
+        int position = buffer.position();
+
+        // flip to flush
+        buffer.limit(buffer.position());
+        buffer.position(0);
+
+        // get byte buffer
+        ByteBuffer bb = StandardCharsets.UTF_8.encode(buffer);
+
+        // restor settings
+        buffer.limit(limit);
+        buffer.position(position);
+
+        return bb;
+    }
+
+    @Override
+    public int length()
+    {
+        return buffer.capacity();
+    }
+
+    public int remaining()
+    {
+        return buffer.remaining();
+    }
+
+    @Override
+    public String toString()
+    {
+        StringBuilder str = new StringBuilder();
+        str.append("Utf8CharBuffer@").append(hashCode());
+        str.append("[p=").append(buffer.position());
+        str.append(",l=").append(buffer.limit());
+        str.append(",c=").append(buffer.capacity());
+        str.append(",r=").append(buffer.remaining());
+        str.append("]");
+        return str.toString();
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/package-info.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/package-info.java
new file mode 100644
index 0000000..07e9079
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty WebSocket Common : Message Handling
+ */
+package org.eclipse.jetty.websocket.common.message;
+
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/package-info.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/package-info.java
new file mode 100644
index 0000000..197cadc
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/package-info.java
@@ -0,0 +1,29 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty WebSocket Common : Implementation [<em>Internal Use Only</em>]
+ * <p>
+ * A core set of internal implementation classes for the Jetty WebSocket API.
+ * <p>
+ * Note: do not reference or use classes present in this package space in your code. <br />
+ * Restrict your usage to the Jetty WebSocket API classes, the Jetty WebSocket Client API,
+ * or the Jetty WebSocket Servlet API.
+ */
+package org.eclipse.jetty.websocket.common;
+
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/util/ReflectUtils.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/util/ReflectUtils.java
new file mode 100644
index 0000000..c2129ef
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/util/ReflectUtils.java
@@ -0,0 +1,395 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.util;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+
+public class ReflectUtils
+{
+    private static class GenericRef
+    {
+        // The base class reference lookup started from
+        private final Class<?> baseClass;
+        // The interface that we are interested in
+        private final Class<?> ifaceClass;
+
+        // The actual class generic interface was found on
+        Class<?> genericClass;
+
+        // The found genericType
+        public Type genericType;
+        private int genericIndex;
+
+        public GenericRef(final Class<?> baseClass, final Class<?> ifaceClass)
+        {
+            this.baseClass = baseClass;
+            this.ifaceClass = ifaceClass;
+        }
+
+        public boolean needsUnwrap()
+        {
+            return (genericClass == null) && (genericType != null) && (genericType instanceof TypeVariable<?>);
+        }
+
+        public void setGenericFromType(Type type, int index)
+        {
+            // debug("setGenericFromType(%s,%d)",toShortName(type),index);
+            this.genericType = type;
+            this.genericIndex = index;
+            if (type instanceof Class)
+            {
+                this.genericClass = (Class<?>)type;
+            }
+        }
+
+        @Override
+        public String toString()
+        {
+            StringBuilder builder = new StringBuilder();
+            builder.append("GenericRef [baseClass=");
+            builder.append(baseClass);
+            builder.append(", ifaceClass=");
+            builder.append(ifaceClass);
+            builder.append(", genericType=");
+            builder.append(genericType);
+            builder.append(", genericClass=");
+            builder.append(genericClass);
+            builder.append("]");
+            return builder.toString();
+        }
+    }
+
+    private static StringBuilder appendTypeName(StringBuilder sb, Type type, boolean ellipses)
+    {
+        if (type instanceof Class<?>)
+        {
+            Class<?> ctype = (Class<?>)type;
+            if (ctype.isArray())
+            {
+                try
+                {
+                    int dimensions = 0;
+                    while (ctype.isArray())
+                    {
+                        dimensions++;
+                        ctype = ctype.getComponentType();
+                    }
+                    sb.append(ctype.getName());
+                    for (int i = 0; i < dimensions; i++)
+                    {
+                        if (ellipses)
+                        {
+                            sb.append("...");
+                        }
+                        else
+                        {
+                            sb.append("[]");
+                        }
+                    }
+                    return sb;
+                }
+                catch (Throwable ignore)
+                {
+                    // ignore
+                }
+            }
+
+            sb.append(ctype.getName());
+        }
+        else
+        {
+            sb.append(type.toString());
+        }
+
+        return sb;
+    }
+
+    /**
+     * Given a Base (concrete) Class, find the interface specified, and return its concrete Generic class declaration.
+     * 
+     * @param baseClass
+     *            the base (concrete) class to look in
+     * @param ifaceClass
+     *            the interface of interest
+     * @return the (concrete) generic class that the interface exposes
+     */
+    public static Class<?> findGenericClassFor(Class<?> baseClass, Class<?> ifaceClass)
+    {
+        GenericRef ref = new GenericRef(baseClass,ifaceClass);
+        if (resolveGenericRef(ref,baseClass))
+        {
+            // debug("Generic Found: %s",ref.genericClass);
+            return ref.genericClass;
+        }
+
+        // debug("Generic not found: %s",ref);
+        return null;
+    }
+
+    private static int findTypeParameterIndex(Class<?> clazz, TypeVariable<?> needVar)
+    {
+        // debug("findTypeParameterIndex(%s, [%s])",toShortName(clazz),toShortName(needVar));
+        TypeVariable<?> params[] = clazz.getTypeParameters();
+        for (int i = 0; i < params.length; i++)
+        {
+            if (params[i].getName().equals(needVar.getName()))
+            {
+                // debug("Type Parameter found at index: [%d]",i);
+                return i;
+            }
+        }
+        // debug("Type Parameter NOT found");
+        return -1;
+    }
+
+    public static boolean isDefaultConstructable(Class<?> clazz)
+    {
+        int mods = clazz.getModifiers();
+        if (Modifier.isAbstract(mods) || !Modifier.isPublic(mods))
+        {
+            // Needs to be public, non-abstract
+            return false;
+        }
+
+        Class<?>[] noargs = new Class<?>[0];
+        try
+        {
+            // Needs to have a no-args constructor
+            Constructor<?> constructor = clazz.getConstructor(noargs);
+            // Constructor needs to be public
+            return Modifier.isPublic(constructor.getModifiers());
+        }
+        catch (NoSuchMethodException | SecurityException e)
+        {
+            return false;
+        }
+    }
+
+    private static boolean resolveGenericRef(GenericRef ref, Class<?> clazz, Type type)
+    {
+        if (type instanceof Class)
+        {
+            if (type == ref.ifaceClass)
+            {
+                // is this a straight ref or a TypeVariable?
+                // debug("Found ref (as class): %s",toShortName(type));
+                ref.setGenericFromType(type,0);
+                return true;
+            }
+            else
+            {
+                // Keep digging
+                return resolveGenericRef(ref,type);
+            }
+        }
+
+        if (type instanceof ParameterizedType)
+        {
+            ParameterizedType ptype = (ParameterizedType)type;
+            Type rawType = ptype.getRawType();
+            if (rawType == ref.ifaceClass)
+            {
+                // debug("Found ref on [%s] as ParameterizedType [%s]",toShortName(clazz),toShortName(ptype));
+                // Always get the raw type parameter, let unwrap() solve for what it is
+                ref.setGenericFromType(ptype.getActualTypeArguments()[0],0);
+                return true;
+            }
+            else
+            {
+                // Keep digging
+                return resolveGenericRef(ref,rawType);
+            }
+        }
+        return false;
+    }
+
+    private static boolean resolveGenericRef(GenericRef ref, Type type)
+    {
+        if ((type == null) || (type == Object.class))
+        {
+            return false;
+        }
+
+        if (type instanceof Class)
+        {
+            Class<?> clazz = (Class<?>)type;
+            // prevent spinning off into Serialization and other parts of the
+            // standard tree that we could care less about
+            if (clazz.getName().matches("^javax*\\..*"))
+            {
+                return false;
+            }
+
+            Type ifaces[] = clazz.getGenericInterfaces();
+            for (Type iface : ifaces)
+            {
+                // debug("resolve %s interface[]: %s",toShortName(clazz),toShortName(iface));
+                if (resolveGenericRef(ref,clazz,iface))
+                {
+                    if (ref.needsUnwrap())
+                    {
+                        // debug("## Unwrap class %s::%s",toShortName(clazz),toShortName(iface));
+                        TypeVariable<?> needVar = (TypeVariable<?>)ref.genericType;
+                        // debug("needs unwrap of type var [%s] - index [%d]",toShortName(needVar),ref.genericIndex);
+
+                        // attempt to find typeParameter on class itself
+                        int typeParamIdx = findTypeParameterIndex(clazz,needVar);
+                        // debug("type param index for %s[%s] is [%d]",toShortName(clazz),toShortName(needVar),typeParamIdx);
+
+                        if (typeParamIdx >= 0)
+                        {
+                            // found a type parameter, use it
+                            // debug("unwrap from class [%s] - typeParameters[%d]",toShortName(clazz),typeParamIdx);
+                            TypeVariable<?> params[] = clazz.getTypeParameters();
+                            if (params.length >= typeParamIdx)
+                            {
+                                ref.setGenericFromType(params[typeParamIdx],typeParamIdx);
+                            }
+                        }
+                        else if (iface instanceof ParameterizedType)
+                        {
+                            // use actual args on interface
+                            Type arg = ((ParameterizedType)iface).getActualTypeArguments()[ref.genericIndex];
+                            ref.setGenericFromType(arg,ref.genericIndex);
+                        }
+                    }
+                    return true;
+                }
+            }
+
+            type = clazz.getGenericSuperclass();
+            return resolveGenericRef(ref,type);
+        }
+
+        if (type instanceof ParameterizedType)
+        {
+            ParameterizedType ptype = (ParameterizedType)type;
+            Class<?> rawClass = (Class<?>)ptype.getRawType();
+            if (resolveGenericRef(ref,rawClass))
+            {
+                if (ref.needsUnwrap())
+                {
+                    // debug("## Unwrap ParameterizedType %s::%s",toShortName(type),toShortName(rawClass));
+                    TypeVariable<?> needVar = (TypeVariable<?>)ref.genericType;
+                    // debug("needs unwrap of type var [%s] - index [%d]",toShortName(needVar),ref.genericIndex);
+                    int typeParamIdx = findTypeParameterIndex(rawClass,needVar);
+                    // debug("type paramIdx of %s::%s is index [%d]",toShortName(rawClass),toShortName(needVar),typeParamIdx);
+
+                    Type arg = ptype.getActualTypeArguments()[typeParamIdx];
+                    ref.setGenericFromType(arg,typeParamIdx);
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+    public static String toShortName(Type type)
+    {
+        if (type == null)
+        {
+            return "<null>";
+        }
+
+        if (type instanceof Class)
+        {
+            String name = ((Class<?>)type).getName();
+            return trimClassName(name);
+        }
+
+        if (type instanceof ParameterizedType)
+        {
+            ParameterizedType ptype = (ParameterizedType)type;
+            StringBuilder str = new StringBuilder();
+            str.append(trimClassName(((Class<?>)ptype.getRawType()).getName()));
+            str.append("<");
+            Type args[] = ptype.getActualTypeArguments();
+            for (int i = 0; i < args.length; i++)
+            {
+                if (i > 0)
+                {
+                    str.append(",");
+                }
+                str.append(args[i]);
+            }
+            str.append(">");
+            return str.toString();
+        }
+
+        return type.toString();
+    }
+
+    public static String toString(Class<?> pojo, Method method)
+    {
+        StringBuilder str = new StringBuilder();
+
+        // method modifiers
+        int mod = method.getModifiers() & Modifier.methodModifiers();
+        if (mod != 0)
+        {
+            str.append(Modifier.toString(mod)).append(' ');
+        }
+
+        // return type
+        Type retType = method.getGenericReturnType();
+        appendTypeName(str,retType,false).append(' ');
+
+        // class name
+        str.append(pojo.getName());
+        str.append("#");
+
+        // method name
+        str.append(method.getName());
+
+        // method parameters
+        str.append('(');
+        Type[] params = method.getGenericParameterTypes();
+        for (int j = 0; j < params.length; j++)
+        {
+            boolean ellipses = method.isVarArgs() && (j == (params.length - 1));
+            appendTypeName(str,params[j],ellipses);
+            if (j < (params.length - 1))
+            {
+                str.append(", ");
+            }
+        }
+        str.append(')');
+
+        // TODO: show exceptions?
+        return str.toString();
+    }
+
+    public static String trimClassName(String name)
+    {
+        int idx = name.lastIndexOf('.');
+        name = name.substring(idx + 1);
+        idx = name.lastIndexOf('$');
+        if (idx >= 0)
+        {
+            name = name.substring(idx + 1);
+        }
+        return name;
+    }
+}
\ No newline at end of file
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/util/TextUtil.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/util/TextUtil.java
new file mode 100644
index 0000000..414529a
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/util/TextUtil.java
@@ -0,0 +1,84 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.util;
+
+/**
+ * Collection of utility methods for Text content
+ */
+public final class TextUtil
+{
+    /**
+     * Create a hint of what the text is like.
+     * <p>
+     * Used by logging and error messages to get a hint of what the text is like.
+     * 
+     * @param text
+     *            the text to abbreviate, quote, and generally give you a hint of what the value is.
+     * @return the abbreviated text
+     */
+    public static String hint(String text)
+    {
+        if (text == null)
+        {
+            return "<null>";
+        }
+        return '"' + maxStringLength(30,text) + '"';
+    }
+
+    /**
+     * Smash a long string to fit within the max string length, by taking the middle section of the string and replacing them with an ellipsis "..."
+     * <p>
+     * 
+     * <pre>
+     * Examples:
+     * .maxStringLength( 9, "Eatagramovabits") == "Eat...its"
+     * .maxStringLength(10, "Eatagramovabits") == "Eat...bits"
+     * .maxStringLength(11, "Eatagramovabits") == "Eata...bits"
+     * </pre>
+     * 
+     * @param max
+     *            the maximum size of the string (minimum size supported is 9)
+     * @param raw
+     *            the raw string to smash
+     * @return the ellipsis'd version of the string.
+     */
+    public static String maxStringLength(int max, String raw)
+    {
+        int length = raw.length();
+        if (length <= max)
+        {
+            // already short enough
+            return raw;
+        }
+
+        if (max < 9)
+        {
+            // minimum supported
+            return raw.substring(0,max);
+        }
+
+        StringBuilder ret = new StringBuilder();
+        int startLen = (int)Math.round((double)max / (double)3);
+        ret.append(raw.substring(0,startLen));
+        ret.append("...");
+        ret.append(raw.substring(length - (max - startLen - 3)));
+
+        return ret.toString();
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/main/javadoc/org/eclipse/jetty/websocket/common/doc-files/websocket-stack-extensions.png b/jetty-websocket/websocket-common/src/main/javadoc/org/eclipse/jetty/websocket/common/doc-files/websocket-stack-extensions.png
new file mode 100644
index 0000000..146eee2
Binary files /dev/null and b/jetty-websocket/websocket-common/src/main/javadoc/org/eclipse/jetty/websocket/common/doc-files/websocket-stack-extensions.png differ
diff --git a/jetty-websocket/websocket-common/src/main/javadoc/org/eclipse/jetty/websocket/common/doc-files/websocket-stack-extensions.svg b/jetty-websocket/websocket-common/src/main/javadoc/org/eclipse/jetty/websocket/common/doc-files/websocket-stack-extensions.svg
new file mode 100644
index 0000000..0568ed6
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/javadoc/org/eclipse/jetty/websocket/common/doc-files/websocket-stack-extensions.svg
@@ -0,0 +1,434 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="645"
+   height="350"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.48.3.1 r9886"
+   sodipodi:docname="websocket-stack-extensions.svg"
+   inkscape:export-filename="/home/joakim/code/intalio/org.eclipse.jetty9.project/jetty-websocket/websocket-core/src/main/javadoc/org/eclipse/jetty/websocket/doc-files/websocket-stack-extensions.png"
+   inkscape:export-xdpi="111.63"
+   inkscape:export-ydpi="111.63">
+  <defs
+     id="defs4" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="1.5972939"
+     inkscape:cx="332.46256"
+     inkscape:cy="171.52658"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="1024"
+     inkscape:window-x="0"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1"
+     showguides="true"
+     inkscape:guide-bbox="true"
+     borderlayer="true">
+    <inkscape:grid
+       type="xygrid"
+       id="grid3006"
+       empspacing="5"
+       dotted="true"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true"
+       spacingx="5px"
+       spacingy="5px" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:groupmode="layer"
+     id="layer2"
+     inkscape:label="Background"
+     sodipodi:insensitive="true">
+    <rect
+       style="fill:#ffffff;fill-opacity:1;stroke:none"
+       id="rect3046"
+       width="645"
+       height="350"
+       x="9.2142858e-07"
+       y="5.0000006e-07" />
+  </g>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-702.36218)">
+    <rect
+       y="952.36218"
+       x="40.000004"
+       height="70"
+       width="560"
+       id="rect3977"
+       style="fill:#bdbdbd;fill-opacity:1;stroke:none" />
+    <rect
+       style="fill:#d1d1d1;fill-opacity:1;stroke:none"
+       id="rect3975"
+       width="560"
+       height="170"
+       x="40.000004"
+       y="782.36218" />
+    <path
+       style="fill:none;stroke:#858585;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:4, 1;stroke-dashoffset:0"
+       d="m 25.000004,952.36217 594.999996,0"
+       id="path4004"
+       inkscape:connector-curvature="0"
+       sodipodi:nodetypes="cc" />
+    <rect
+       y="732.36218"
+       x="40.000004"
+       height="50"
+       width="560"
+       id="rect3973"
+       style="fill:#e7e7e7;fill-opacity:1;stroke:none" />
+    <path
+       sodipodi:nodetypes="cc"
+       inkscape:connector-curvature="0"
+       id="path3999"
+       d="m 25.000004,782.36217 594.999996,0"
+       style="fill:none;stroke:#858585;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:4, 1;stroke-dashoffset:0" />
+    <g
+       transform="translate(24.999996,-35.000005)"
+       id="g4031">
+      <rect
+         style="fill:#caf3f6;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-opacity:1"
+         id="rect3008"
+         width="400"
+         height="25.000031"
+         x="25"
+         y="1047.3622" />
+      <text
+         xml:space="preserve"
+         style="font-size:14px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans"
+         x="224.91454"
+         y="1063.7245"
+         id="text3778"
+         sodipodi:linespacing="125%"><tspan
+           sodipodi:role="line"
+           id="tspan3780"
+           x="224.91454"
+           y="1063.7245">Physical Connection</tspan></text>
+    </g>
+    <rect
+       style="fill:#caf3f6;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-opacity:1"
+       id="rect3797"
+       width="400"
+       height="25"
+       x="49.999996"
+       y="987.36218" />
+    <text
+       xml:space="preserve"
+       style="font-size:14px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans"
+       x="249.91455"
+       y="1003.7245"
+       id="text3799"
+       sodipodi:linespacing="125%"><tspan
+         sodipodi:role="line"
+         id="tspan3801"
+         x="249.91455"
+         y="1003.7245">Jetty I/O EndPoint</tspan></text>
+    <rect
+       y="927.36212"
+       x="49.999996"
+       height="59.999992"
+       width="400"
+       id="rect3805"
+       style="fill:#caf3f6;fill-opacity:1;stroke:#000000;stroke-width:0.99999988;stroke-opacity:1" />
+    <text
+       sodipodi:linespacing="125%"
+       id="text3807"
+       y="978.72449"
+       x="249.91455"
+       style="font-size:14px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans"
+       xml:space="preserve"><tspan
+         y="978.72449"
+         x="249.91455"
+         id="tspan3809"
+         sodipodi:role="line">Jetty WebSocketConnection</tspan></text>
+    <rect
+       style="color:#000000;fill:#000000;fill-opacity:0.12328766;fill-rule:nonzero;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:0.56470588;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="rect3786"
+       width="190"
+       height="25"
+       x="60"
+       y="937.36218" />
+    <text
+       xml:space="preserve"
+       style="font-size:14px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans"
+       x="149.31982"
+       y="954.86609"
+       id="text3815"
+       sodipodi:linespacing="125%"><tspan
+         id="tspan3822"
+         sodipodi:role="line"
+         x="149.31982"
+         y="954.86609">Parser</tspan></text>
+    <rect
+       y="937.36218"
+       x="250"
+       height="25"
+       width="190"
+       id="rect3788"
+       style="color:#000000;fill:#000000;fill-opacity:0.12328766;fill-rule:nonzero;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:0.56470588;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+    <text
+       sodipodi:linespacing="125%"
+       id="text3826"
+       y="954.95837"
+       x="314.03955"
+       style="font-size:14px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans"
+       xml:space="preserve"><tspan
+         id="tspan3830"
+         y="954.95837"
+         x="314.03955"
+         sodipodi:role="line">Generator</tspan></text>
+    <rect
+       style="fill:#caf3f6;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-opacity:1"
+       id="rect3849"
+       width="400"
+       height="59.999996"
+       x="50"
+       y="742.36218" />
+    <text
+       xml:space="preserve"
+       style="font-size:14px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans"
+       x="249.91455"
+       y="758.72449"
+       id="text3851"
+       sodipodi:linespacing="125%"><tspan
+         sodipodi:role="line"
+         id="tspan3853"
+         x="249.91455"
+         y="758.72449">WebSocket Session</tspan></text>
+    <rect
+       style="fill:#caf3f6;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-opacity:1"
+       id="rect3865"
+       width="400"
+       height="25"
+       x="50"
+       y="717.36218" />
+    <text
+       xml:space="preserve"
+       style="font-size:14px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans"
+       x="249.91455"
+       y="733.72449"
+       id="text3867"
+       sodipodi:linespacing="125%"><tspan
+         sodipodi:role="line"
+         id="tspan3869"
+         x="249.91455"
+         y="733.72449">WebSocket POJO</tspan></text>
+    <rect
+       y="767.36218"
+       x="60"
+       height="25"
+       width="190"
+       id="rect3881"
+       style="color:#000000;fill:#000000;fill-opacity:0.12328766;fill-rule:nonzero;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:0.56470588;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+    <text
+       transform="scale(0.9746794,1.0259784)"
+       sodipodi:linespacing="125%"
+       id="text3883"
+       y="765.2027"
+       x="158.80678"
+       style="font-size:13.64551163px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans"
+       xml:space="preserve"><tspan
+         y="765.2027"
+         x="158.80678"
+         sodipodi:role="line"
+         id="tspan3885">EventDriver</tspan></text>
+    <rect
+       style="fill:#000000;fill-opacity:0.12328766;stroke:#000000;stroke-width:1;stroke-opacity:0.56470588"
+       id="rect3905"
+       width="190"
+       height="25"
+       x="250"
+       y="767.36218" />
+    <text
+       transform="scale(0.97467941,1.0259784)"
+       xml:space="preserve"
+       style="font-size:13.64551163px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans"
+       x="353.74265"
+       y="765.2027"
+       id="text3907"
+       sodipodi:linespacing="125%"><tspan
+         id="tspan3909"
+         sodipodi:role="line"
+         x="353.74265"
+         y="765.2027">RemoteEndpoint</tspan></text>
+    <rect
+       y="812.36218"
+       x="49.999996"
+       height="104.99999"
+       width="400"
+       id="rect4036"
+       style="fill:#fdff14;fill-opacity:0.37442926;stroke:#87882d;stroke-width:0.99999994;stroke-opacity:1" />
+    <text
+       sodipodi:linespacing="125%"
+       id="text3979"
+       y="970.67273"
+       x="470"
+       style="font-size:12px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans"
+       xml:space="preserve"><tspan
+         y="970.67273"
+         x="470"
+         id="tspan3981"
+         sodipodi:role="line">Network</tspan><tspan
+         id="tspan3983"
+         y="985.67273"
+         x="470"
+         sodipodi:role="line">(ByteBuffers)</tspan></text>
+    <text
+       sodipodi:linespacing="125%"
+       id="text3985"
+       y="802.36218"
+       x="470"
+       style="font-size:12px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans"
+       xml:space="preserve"><tspan
+         y="802.36218"
+         x="470"
+         id="tspan3987"
+         sodipodi:role="line">Internal</tspan><tspan
+         id="tspan3989"
+         y="817.36218"
+         x="470"
+         sodipodi:role="line">(WebSocket Frame)</tspan></text>
+    <text
+       sodipodi:linespacing="125%"
+       id="text3991"
+       y="752.98816"
+       x="470"
+       style="font-size:12px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans"
+       xml:space="preserve"><tspan
+         y="752.98816"
+         x="470"
+         id="tspan3993"
+         sodipodi:role="line">Message</tspan><tspan
+         id="tspan3995"
+         y="767.98816"
+         x="470"
+         sodipodi:role="line">(Text or Binary)</tspan></text>
+    <text
+       sodipodi:linespacing="125%"
+       id="text3867-0"
+       y="827.36218"
+       x="249.34033"
+       style="font-size:14px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans"
+       xml:space="preserve"><tspan
+         y="827.36218"
+         x="249.34033"
+         id="tspan3869-0"
+         sodipodi:role="line">ExtensionStack</tspan></text>
+    <g
+       id="g4630"
+       transform="translate(0,10.000122)">
+      <rect
+         y="837.36218"
+         x="60"
+         height="20.000149"
+         width="380"
+         id="rect4588"
+         style="fill:#fdff14;fill-opacity:0.37442926;stroke:#87882d;stroke-width:0.99999994;stroke-opacity:1" />
+      <text
+         xml:space="preserve"
+         style="font-size:14px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans"
+         x="249.91455"
+         y="851.22449"
+         id="text3867-0-6"
+         sodipodi:linespacing="125%"><tspan
+           sodipodi:role="line"
+           id="tspan3869-0-8"
+           x="249.91455"
+           y="851.22449">Message Compression Extension</tspan></text>
+    </g>
+    <g
+       id="g4635"
+       transform="translate(0,29.999997)">
+      <rect
+         style="fill:#fdff14;fill-opacity:0.37442926;stroke:#87882d;stroke-width:0.99999994;stroke-opacity:1"
+         id="rect4637"
+         width="380"
+         height="20.000149"
+         x="60"
+         y="837.36218" />
+      <text
+         sodipodi:linespacing="125%"
+         id="text4639"
+         y="851.22449"
+         x="249.91455"
+         style="font-size:14px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans"
+         xml:space="preserve"><tspan
+           y="851.22449"
+           x="249.91455"
+           id="tspan4641"
+           sodipodi:role="line">Fragmentation Extension</tspan></text>
+    </g>
+    <g
+       id="g4648">
+      <path
+         style="fill:#f7d1d1;fill-opacity:1;stroke:#dd9191;stroke-width:1;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+         d="m 95.031248,947.36218 0,-141.74193 7.656252,0 -17.656252,-18.25807 -17.6875,18.25807 7.6875,0 0,141.74193 20,0 z"
+         id="rect3934"
+         inkscape:connector-curvature="0" />
+      <text
+         xml:space="preserve"
+         style="font-size:14px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans"
+         x="-925.48132"
+         y="88.87793"
+         id="text3948"
+         sodipodi:linespacing="125%"
+         transform="matrix(0,-1,1,0,0,0)"><tspan
+           sodipodi:role="line"
+           id="tspan3950"
+           x="-925.48132"
+           y="88.87793">IncomingFrames</tspan></text>
+    </g>
+    <g
+       id="g4643">
+      <path
+         style="fill:#f7d1d1;fill-opacity:1;stroke:#dd9191;stroke-width:0.99999988;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+         d="m 419.96875,947.3621 17.6875,-20.96294 -7.6875,0 0,-139.03698 -20,0 0,139.03698 -7.65625,0 17.65625,20.96294 z"
+         id="rect3954"
+         inkscape:connector-curvature="0" />
+      <text
+         sodipodi:linespacing="125%"
+         id="text3969"
+         y="-416.12207"
+         x="809.57794"
+         style="font-size:14px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans"
+         xml:space="preserve"
+         transform="matrix(0,1,-1,0,0,0)"><tspan
+           y="-416.12207"
+           x="809.57794"
+           id="tspan3971"
+           sodipodi:role="line">OutgoingFrames</tspan></text>
+    </g>
+  </g>
+</svg>
diff --git a/jetty-websocket/websocket-common/src/main/javadoc/org/eclipse/jetty/websocket/common/doc-files/websocket-stack-simple.png b/jetty-websocket/websocket-common/src/main/javadoc/org/eclipse/jetty/websocket/common/doc-files/websocket-stack-simple.png
new file mode 100644
index 0000000..c1184b0
Binary files /dev/null and b/jetty-websocket/websocket-common/src/main/javadoc/org/eclipse/jetty/websocket/common/doc-files/websocket-stack-simple.png differ
diff --git a/jetty-websocket/websocket-common/src/main/javadoc/org/eclipse/jetty/websocket/common/doc-files/websocket-stack-simple.svg b/jetty-websocket/websocket-common/src/main/javadoc/org/eclipse/jetty/websocket/common/doc-files/websocket-stack-simple.svg
new file mode 100644
index 0000000..1f07f76
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/javadoc/org/eclipse/jetty/websocket/common/doc-files/websocket-stack-simple.svg
@@ -0,0 +1,384 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="645"
+   height="350"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.48.3.1 r9886"
+   sodipodi:docname="websocket-stack-simple.svg"
+   inkscape:export-filename="/home/joakim/code/intalio/org.eclipse.jetty9.project/jetty-websocket/websocket-common/src/main/javadoc/org/eclipse/jetty/websocket/common/doc-files/websocket-stack-simple.png"
+   inkscape:export-xdpi="111.63"
+   inkscape:export-ydpi="111.63">
+  <defs
+     id="defs4" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="1.6652639"
+     inkscape:cx="375.14499"
+     inkscape:cy="194.35762"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="1024"
+     inkscape:window-x="0"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1"
+     showguides="true"
+     inkscape:guide-bbox="true"
+     borderlayer="true">
+    <inkscape:grid
+       type="xygrid"
+       id="grid3006"
+       empspacing="5"
+       dotted="true"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true"
+       spacingx="5px"
+       spacingy="5px" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:groupmode="layer"
+     id="layer2"
+     inkscape:label="Background"
+     sodipodi:insensitive="true">
+    <rect
+       style="fill:#ffffff;fill-opacity:1;stroke:none"
+       id="rect3089"
+       width="645"
+       height="350"
+       x="0"
+       y="0" />
+  </g>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-702.36218)">
+    <rect
+       style="fill:#bdbdbd;fill-opacity:1;stroke:none"
+       id="rect3977"
+       width="560"
+       height="70"
+       x="40.000004"
+       y="952.36218" />
+    <rect
+       y="782.36218"
+       x="40.000004"
+       height="170"
+       width="560"
+       id="rect3975"
+       style="fill:#d1d1d1;fill-opacity:1;stroke:none" />
+    <path
+       sodipodi:nodetypes="cc"
+       inkscape:connector-curvature="0"
+       id="path4004"
+       d="m 25.000004,952.36217 594.999996,0"
+       style="fill:none;stroke:#858585;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:4, 1;stroke-dashoffset:0" />
+    <rect
+       style="fill:#e7e7e7;fill-opacity:1;stroke:none"
+       id="rect3973"
+       width="560"
+       height="50"
+       x="40.000004"
+       y="732.36218" />
+    <path
+       style="fill:none;stroke:#858585;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:4, 1;stroke-dashoffset:0"
+       d="m 25.000004,782.36217 594.999996,0"
+       id="path3999"
+       inkscape:connector-curvature="0"
+       sodipodi:nodetypes="cc" />
+    <g
+       id="g4031"
+       transform="translate(24.999996,-35.000005)">
+      <rect
+         y="1047.3622"
+         x="25"
+         height="25.000031"
+         width="400"
+         id="rect3008"
+         style="fill:#caf3f6;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-opacity:1" />
+      <text
+         sodipodi:linespacing="125%"
+         id="text3778"
+         y="1063.7245"
+         x="224.91454"
+         style="font-size:14px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans"
+         xml:space="preserve"><tspan
+           y="1063.7245"
+           x="224.91454"
+           id="tspan3780"
+           sodipodi:role="line">Physical Connection</tspan></text>
+    </g>
+    <rect
+       y="987.36218"
+       x="49.999996"
+       height="25"
+       width="400"
+       id="rect3797"
+       style="fill:#caf3f6;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-opacity:1" />
+    <text
+       sodipodi:linespacing="125%"
+       id="text3799"
+       y="1003.7245"
+       x="249.91455"
+       style="font-size:14px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans"
+       xml:space="preserve"><tspan
+         y="1003.7245"
+         x="249.91455"
+         id="tspan3801"
+         sodipodi:role="line">Jetty I/O EndPoint</tspan></text>
+    <rect
+       style="fill:#caf3f6;fill-opacity:1;stroke:#000000;stroke-width:0.99999988;stroke-opacity:1"
+       id="rect3805"
+       width="400"
+       height="59.999992"
+       x="49.999996"
+       y="927.36212" />
+    <text
+       xml:space="preserve"
+       style="font-size:14px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans"
+       x="249.91455"
+       y="978.72449"
+       id="text3807"
+       sodipodi:linespacing="125%"><tspan
+         sodipodi:role="line"
+         id="tspan3809"
+         x="249.91455"
+         y="978.72449">Jetty WebSocketConnection</tspan></text>
+    <rect
+       y="937.36218"
+       x="60"
+       height="25"
+       width="190"
+       id="rect3786"
+       style="fill:#000000;fill-opacity:0.12328765999999999;stroke:#000000;stroke-width:1;stroke-opacity:0.56470587999999999;color:#000000;fill-rule:nonzero;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+    <text
+       sodipodi:linespacing="125%"
+       id="text3815"
+       y="954.86609"
+       x="149.31982"
+       style="font-size:14px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans"
+       xml:space="preserve"><tspan
+         y="954.86609"
+         x="149.31982"
+         sodipodi:role="line"
+         id="tspan3822">Parser</tspan></text>
+    <rect
+       style="fill:#000000;fill-opacity:0.12328765999999999;stroke:#000000;stroke-width:1;stroke-opacity:0.56470587999999999;color:#000000;fill-rule:nonzero;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="rect3788"
+       width="190"
+       height="25"
+       x="250"
+       y="937.36218" />
+    <text
+       xml:space="preserve"
+       style="font-size:14px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans"
+       x="314.03955"
+       y="954.95837"
+       id="text3826"
+       sodipodi:linespacing="125%"><tspan
+         sodipodi:role="line"
+         x="314.03955"
+         y="954.95837"
+         id="tspan3830">Generator</tspan></text>
+    <rect
+       y="742.36218"
+       x="50"
+       height="59.999996"
+       width="400"
+       id="rect3849"
+       style="fill:#caf3f6;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-opacity:1" />
+    <text
+       sodipodi:linespacing="125%"
+       id="text3851"
+       y="758.72449"
+       x="249.91455"
+       style="font-size:14px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans"
+       xml:space="preserve"><tspan
+         y="758.72449"
+         x="249.91455"
+         id="tspan3853"
+         sodipodi:role="line">WebSocket Session</tspan></text>
+    <rect
+       y="717.36218"
+       x="50"
+       height="25"
+       width="400"
+       id="rect3865"
+       style="fill:#caf3f6;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-opacity:1" />
+    <text
+       sodipodi:linespacing="125%"
+       id="text3867"
+       y="733.72449"
+       x="249.91455"
+       style="font-size:14px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans"
+       xml:space="preserve"><tspan
+         y="733.72449"
+         x="249.91455"
+         id="tspan3869"
+         sodipodi:role="line">WebSocket POJO</tspan></text>
+    <rect
+       style="fill:#000000;fill-opacity:0.12328765999999999;stroke:#000000;stroke-width:1;stroke-opacity:0.56470587999999999;color:#000000;fill-rule:nonzero;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="rect3881"
+       width="190"
+       height="25"
+       x="60"
+       y="767.36218" />
+    <text
+       xml:space="preserve"
+       style="font-size:13.64551163px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans"
+       x="158.80678"
+       y="765.2027"
+       id="text3883"
+       sodipodi:linespacing="125%"
+       transform="scale(0.97467942,1.0259784)"><tspan
+         id="tspan3885"
+         sodipodi:role="line"
+         x="158.80678"
+         y="765.2027">EventDriver</tspan></text>
+    <rect
+       y="767.36218"
+       x="250"
+       height="25"
+       width="190"
+       id="rect3905"
+       style="fill:#000000;fill-opacity:0.12328767000000000;stroke:#000000;stroke-width:1;stroke-opacity:0.56470591" />
+    <text
+       sodipodi:linespacing="125%"
+       id="text3907"
+       y="765.2027"
+       x="353.74265"
+       style="font-size:13.64551163px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans"
+       xml:space="preserve"
+       transform="scale(0.97467943,1.0259784)"><tspan
+         y="765.2027"
+         x="353.74265"
+         sodipodi:role="line"
+         id="tspan3909">RemoteEndpoint</tspan></text>
+    <rect
+       style="fill:#fdff14;fill-opacity:0.37442925999999999;stroke:#87882d;stroke-width:0.99999994000000003;stroke-opacity:1"
+       id="rect4036"
+       width="400"
+       height="104.99999"
+       x="49.999996"
+       y="812.36218" />
+    <path
+       style="fill:#f7d1d1;fill-opacity:1;stroke:#dd9191;stroke-width:1;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+       d="m 95.031248,947.36218 0,-141.74193 7.656252,0 -17.656252,-18.25807 -17.6875,18.25807 7.6875,0 0,141.74193 20,0 z"
+       id="rect3934"
+       inkscape:connector-curvature="0" />
+    <text
+       xml:space="preserve"
+       style="font-size:14px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans"
+       x="-925.48132"
+       y="88.87793"
+       id="text3948"
+       sodipodi:linespacing="125%"
+       transform="matrix(0,-1,1,0,0,0)"><tspan
+         sodipodi:role="line"
+         id="tspan3950"
+         x="-925.48132"
+         y="88.87793">IncomingFrames</tspan></text>
+    <path
+       style="fill:#f7d1d1;fill-opacity:1;stroke:#dd9191;stroke-width:0.99999988;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+       d="m 419.96875,947.3621 17.6875,-20.96294 -7.6875,0 0,-139.03698 -20,0 0,139.03698 -7.65625,0 17.65625,20.96294 z"
+       id="rect3954"
+       inkscape:connector-curvature="0" />
+    <text
+       sodipodi:linespacing="125%"
+       id="text3969"
+       y="-416.12207"
+       x="809.57794"
+       style="font-size:14px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans"
+       xml:space="preserve"
+       transform="matrix(0,1,-1,0,0,0)"><tspan
+         y="-416.12207"
+         x="809.57794"
+         id="tspan3971"
+         sodipodi:role="line">OutgoingFrames</tspan></text>
+    <text
+       xml:space="preserve"
+       style="font-size:12px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans"
+       x="470"
+       y="970.67273"
+       id="text3979"
+       sodipodi:linespacing="125%"><tspan
+         sodipodi:role="line"
+         id="tspan3981"
+         x="470"
+         y="970.67273">Network</tspan><tspan
+         sodipodi:role="line"
+         x="470"
+         y="985.67273"
+         id="tspan3983">(ByteBuffers)</tspan></text>
+    <text
+       xml:space="preserve"
+       style="font-size:12px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans"
+       x="470"
+       y="802.36218"
+       id="text3985"
+       sodipodi:linespacing="125%"><tspan
+         sodipodi:role="line"
+         id="tspan3987"
+         x="470"
+         y="802.36218">Internal</tspan><tspan
+         sodipodi:role="line"
+         x="470"
+         y="817.36218"
+         id="tspan3989">(WebSocket Frame)</tspan></text>
+    <text
+       xml:space="preserve"
+       style="font-size:12px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans"
+       x="470"
+       y="752.98816"
+       id="text3991"
+       sodipodi:linespacing="125%"><tspan
+         sodipodi:role="line"
+         id="tspan3993"
+         x="470"
+         y="752.98816">Message</tspan><tspan
+         sodipodi:role="line"
+         x="470"
+         y="767.98816"
+         id="tspan3995">(Text or Binary)</tspan></text>
+    <text
+       xml:space="preserve"
+       style="font-size:14px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans"
+       x="249.34033"
+       y="827.36218"
+       id="text3867-0"
+       sodipodi:linespacing="125%"><tspan
+         sodipodi:role="line"
+         id="tspan3869-0"
+         x="249.34033"
+         y="827.36218">ExtensionStack</tspan></text>
+  </g>
+</svg>
diff --git a/jetty-websocket/websocket-common/src/main/resources/META-INF/services/org.eclipse.jetty.websocket.api.extensions.Extension b/jetty-websocket/websocket-common/src/main/resources/META-INF/services/org.eclipse.jetty.websocket.api.extensions.Extension
new file mode 100644
index 0000000..11b4f35
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/resources/META-INF/services/org.eclipse.jetty.websocket.api.extensions.Extension
@@ -0,0 +1,5 @@
+org.eclipse.jetty.websocket.common.extensions.identity.IdentityExtension
+org.eclipse.jetty.websocket.common.extensions.compress.DeflateFrameExtension
+org.eclipse.jetty.websocket.common.extensions.compress.XWebkitDeflateFrameExtension
+org.eclipse.jetty.websocket.common.extensions.compress.PerMessageDeflateExtension
+org.eclipse.jetty.websocket.common.extensions.fragment.FragmentExtension
\ No newline at end of file
diff --git a/jetty-websocket/websocket-common/src/test/java/examples/AdapterConnectCloseSocket.java b/jetty-websocket/websocket-common/src/test/java/examples/AdapterConnectCloseSocket.java
new file mode 100644
index 0000000..1ae2151
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/examples/AdapterConnectCloseSocket.java
@@ -0,0 +1,40 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package examples;
+
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.WebSocketAdapter;
+import org.eclipse.jetty.websocket.common.events.EventCapture;
+
+public class AdapterConnectCloseSocket extends WebSocketAdapter
+{
+    public EventCapture capture = new EventCapture();
+
+    @Override
+    public void onWebSocketClose(int statusCode, String reason)
+    {
+        capture.add("onWebSocketClose(%d, %s)",statusCode,capture.q(reason));
+    }
+
+    @Override
+    public void onWebSocketConnect(Session sess)
+    {
+        capture.add("onWebSocketConnect(%s)",sess);
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/examples/AnnotatedBinaryArraySocket.java b/jetty-websocket/websocket-common/src/test/java/examples/AnnotatedBinaryArraySocket.java
new file mode 100644
index 0000000..c35ae4c
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/examples/AnnotatedBinaryArraySocket.java
@@ -0,0 +1,51 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package examples;
+
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
+import org.eclipse.jetty.websocket.api.annotations.WebSocket;
+import org.eclipse.jetty.websocket.common.events.EventCapture;
+
+ at WebSocket
+public class AnnotatedBinaryArraySocket
+{
+    public EventCapture capture = new EventCapture();
+
+    @OnWebSocketMessage
+    public void onBinary(byte payload[], int offset, int length)
+    {
+        capture.add("onBinary([%d],%d,%d)",payload.length,offset,length);
+    }
+
+    @OnWebSocketClose
+    public void onClose(int statusCode, String reason)
+    {
+        capture.add("onClose(%d, %s)",statusCode,capture.q(reason));
+    }
+
+    @OnWebSocketConnect
+    public void onConnect(Session sess)
+    {
+        capture.add("onConnect(%s)",sess);
+    }
+
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/examples/AnnotatedBinaryStreamSocket.java b/jetty-websocket/websocket-common/src/test/java/examples/AnnotatedBinaryStreamSocket.java
new file mode 100644
index 0000000..7eb1334
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/examples/AnnotatedBinaryStreamSocket.java
@@ -0,0 +1,56 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package examples;
+
+import java.io.InputStream;
+
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
+import org.eclipse.jetty.websocket.api.annotations.WebSocket;
+import org.eclipse.jetty.websocket.common.events.EventCapture;
+
+ at WebSocket
+public class AnnotatedBinaryStreamSocket
+{
+    public EventCapture capture = new EventCapture();
+
+    @OnWebSocketMessage
+    public void onBinary(InputStream stream)
+    {
+        if (stream == null)
+        {
+            new RuntimeException("Stream cannot be null").printStackTrace(System.err);
+        }
+        capture.add("onBinary(%s)",stream);
+    }
+
+    @OnWebSocketClose
+    public void onClose(int statusCode, String reason)
+    {
+        capture.add("onClose(%d, %s)",statusCode,capture.q(reason));
+    }
+
+    @OnWebSocketConnect
+    public void onConnect(Session sess)
+    {
+        capture.add("onConnect(%s)",sess);
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/examples/AnnotatedFramesSocket.java b/jetty-websocket/websocket-common/src/test/java/examples/AnnotatedFramesSocket.java
new file mode 100644
index 0000000..4d8956f
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/examples/AnnotatedFramesSocket.java
@@ -0,0 +1,51 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package examples;
+
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketFrame;
+import org.eclipse.jetty.websocket.api.annotations.WebSocket;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.common.events.EventCapture;
+
+ at WebSocket
+public class AnnotatedFramesSocket
+{
+    public EventCapture capture = new EventCapture();
+
+    @OnWebSocketClose
+    public void onClose(int statusCode, String reason)
+    {
+        capture.add("onClose(%d, %s)",statusCode,capture.q(reason));
+    }
+
+    @OnWebSocketConnect
+    public void onConnect(Session sess)
+    {
+        capture.add("onConnect(%s)",sess);
+    }
+
+    @OnWebSocketFrame
+    public void onFrame(Frame frame)
+    {
+        capture.add("onFrame(%s)",frame);
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/examples/AnnotatedStreamingSocket.java b/jetty-websocket/websocket-common/src/test/java/examples/AnnotatedStreamingSocket.java
new file mode 100644
index 0000000..4de7003
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/examples/AnnotatedStreamingSocket.java
@@ -0,0 +1,79 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package examples;
+
+import java.io.InputStream;
+import java.io.Reader;
+
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketFrame;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
+import org.eclipse.jetty.websocket.api.annotations.WebSocket;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.common.events.EventCapture;
+
+ at WebSocket
+public class AnnotatedStreamingSocket
+{
+    public EventCapture capture = new EventCapture();
+
+    @OnWebSocketClose
+    public void onClose(int statusCode, String reason)
+    {
+        capture.add("onClose(%d, %s)",statusCode,capture.q(reason));
+    }
+
+    @OnWebSocketConnect
+    public void onConnect(Session sess)
+    {
+        capture.add("onConnect(%s)",sess);
+    }
+
+    @OnWebSocketFrame
+    public void onFrame(Frame frame)
+    {
+    }
+
+    // Binary
+    @OnWebSocketMessage
+    public void onMessage(byte buf[], int offset, int length)
+    {
+    }
+
+    // Binary
+    @OnWebSocketMessage
+    public void onMessage(InputStream stream)
+    {
+    }
+
+    // Text
+    @OnWebSocketMessage
+    public void onMessage(Reader stream)
+    {
+    }
+
+    // Text
+    @OnWebSocketMessage
+    public void onMessage(String message)
+    {
+    }
+
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/examples/AnnotatedTextSocket.java b/jetty-websocket/websocket-common/src/test/java/examples/AnnotatedTextSocket.java
new file mode 100644
index 0000000..803d3f0
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/examples/AnnotatedTextSocket.java
@@ -0,0 +1,57 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package examples;
+
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
+import org.eclipse.jetty.websocket.api.annotations.WebSocket;
+import org.eclipse.jetty.websocket.common.events.EventCapture;
+
+ at WebSocket
+public class AnnotatedTextSocket
+{
+    public EventCapture capture = new EventCapture();
+
+    @OnWebSocketClose
+    public void onClose(int statusCode, String reason)
+    {
+        capture.add("onClose(%d, %s)",statusCode,capture.q(reason));
+    }
+
+    @OnWebSocketConnect
+    public void onConnect(Session sess)
+    {
+        capture.add("onConnect(%s)",sess);
+    }
+
+    @OnWebSocketError
+    public void onError(Throwable cause)
+    {
+        capture.add("onError(%s: %s)",cause.getClass().getSimpleName(),cause.getMessage());
+    }
+
+    @OnWebSocketMessage
+    public void onText(String message)
+    {
+        capture.add("onText(%s)",capture.q(message));
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/examples/AnnotatedTextStreamSocket.java b/jetty-websocket/websocket-common/src/test/java/examples/AnnotatedTextStreamSocket.java
new file mode 100644
index 0000000..b40ee8b
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/examples/AnnotatedTextStreamSocket.java
@@ -0,0 +1,52 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package examples;
+
+import java.io.Reader;
+
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
+import org.eclipse.jetty.websocket.api.annotations.WebSocket;
+import org.eclipse.jetty.websocket.common.events.EventCapture;
+
+ at WebSocket
+public class AnnotatedTextStreamSocket
+{
+    public EventCapture capture = new EventCapture();
+
+    @OnWebSocketClose
+    public void onClose(int statusCode, String reason)
+    {
+        capture.add("onClose(%d, %s)",statusCode,capture.q(reason));
+    }
+
+    @OnWebSocketConnect
+    public void onConnect(Session sess)
+    {
+        capture.add("onConnect(%s)",sess);
+    }
+
+    @OnWebSocketMessage
+    public void onText(Reader reader)
+    {
+        capture.add("onText(%s)",reader);
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/examples/ListenerBasicSocket.java b/jetty-websocket/websocket-common/src/test/java/examples/ListenerBasicSocket.java
new file mode 100644
index 0000000..835f5a2
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/examples/ListenerBasicSocket.java
@@ -0,0 +1,58 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package examples;
+
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.WebSocketListener;
+import org.eclipse.jetty.websocket.common.events.EventCapture;
+
+public class ListenerBasicSocket implements WebSocketListener
+{
+    public EventCapture capture = new EventCapture();
+
+    @Override
+    public void onWebSocketBinary(byte[] payload, int offset, int len)
+    {
+        capture.add("onWebSocketBinary([%d], %d, %d)",payload.length,offset,len);
+    }
+
+    @Override
+    public void onWebSocketClose(int statusCode, String reason)
+    {
+        capture.add("onWebSocketClose(%d, %s)",statusCode,capture.q(reason));
+    }
+
+    @Override
+    public void onWebSocketConnect(Session session)
+    {
+        capture.add("onWebSocketConnect(%s)",session);
+    }
+
+    @Override
+    public void onWebSocketError(Throwable cause)
+    {
+        capture.add("onWebSocketError((%s) %s)",cause.getClass().getSimpleName(),cause.getMessage());
+    }
+
+    @Override
+    public void onWebSocketText(String message)
+    {
+        capture.add("onWebSocketText(%s)",capture.q(message));
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/examples/echo/AdapterEchoSocket.java b/jetty-websocket/websocket-common/src/test/java/examples/echo/AdapterEchoSocket.java
new file mode 100644
index 0000000..191472d
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/examples/echo/AdapterEchoSocket.java
@@ -0,0 +1,47 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package examples.echo;
+
+import java.io.IOException;
+
+import org.eclipse.jetty.websocket.api.WebSocketAdapter;
+
+/**
+ * Example EchoSocket using Adapter.
+ */
+public class AdapterEchoSocket extends WebSocketAdapter
+{
+    @Override
+    public void onWebSocketText(String message)
+    {
+        if (isConnected())
+        {
+            try
+            {
+                System.out.printf("Echoing back message [%s]%n",message);
+                // echo the message back
+                getRemote().sendString(message);
+            }
+            catch (IOException e)
+            {
+                e.printStackTrace(System.err);
+            }
+        }
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/examples/echo/AnnotatedEchoSocket.java b/jetty-websocket/websocket-common/src/test/java/examples/echo/AnnotatedEchoSocket.java
new file mode 100644
index 0000000..c63f031
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/examples/echo/AnnotatedEchoSocket.java
@@ -0,0 +1,41 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package examples.echo;
+
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
+import org.eclipse.jetty.websocket.api.annotations.WebSocket;
+
+/**
+ * Example EchoSocket using Annotations.
+ */
+ at WebSocket(maxTextMessageSize = 64 * 1024)
+public class AnnotatedEchoSocket
+{
+    @OnWebSocketMessage
+    public void onText(Session session, String message)
+    {
+        if (session.isOpen())
+        {
+            System.out.printf("Echoing back message [%s]%n",message);
+            // echo the message back
+            session.getRemote().sendString(message,null);
+        }
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/examples/echo/ListenerEchoSocket.java b/jetty-websocket/websocket-common/src/test/java/examples/echo/ListenerEchoSocket.java
new file mode 100644
index 0000000..d5c877f
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/examples/echo/ListenerEchoSocket.java
@@ -0,0 +1,65 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package examples.echo;
+
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.WebSocketListener;
+
+/**
+ * Example EchoSocket using Listener.
+ */
+public class ListenerEchoSocket implements WebSocketListener
+{
+    private Session outbound;
+
+    @Override
+    public void onWebSocketBinary(byte[] payload, int offset, int len)
+    {
+        /* only interested in text messages */
+    }
+
+    @Override
+    public void onWebSocketClose(int statusCode, String reason)
+    {
+        this.outbound = null;
+    }
+
+    @Override
+    public void onWebSocketConnect(Session session)
+    {
+        this.outbound = session;
+    }
+
+    @Override
+    public void onWebSocketError(Throwable cause)
+    {
+        cause.printStackTrace(System.err);
+    }
+
+    @Override
+    public void onWebSocketText(String message)
+    {
+        if ((outbound != null) && (outbound.isOpen()))
+        {
+            System.out.printf("Echoing back message [%s]%n",message);
+            // echo the message back
+            outbound.getRemote().sendString(message,null);
+        }
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/AcceptHashTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/AcceptHashTest.java
new file mode 100644
index 0000000..d3ef3c5
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/AcceptHashTest.java
@@ -0,0 +1,62 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common;
+
+import static org.hamcrest.Matchers.is;
+
+import org.eclipse.jetty.util.B64Code;
+import org.eclipse.jetty.util.TypeUtil;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class AcceptHashTest
+{
+    @Test
+    public void testHash()
+    {
+        byte key[] = TypeUtil.fromHexString("00112233445566778899AABBCCDDEEFF");
+        Assert.assertThat("Key size",key.length,is(16));
+
+        // what the client sends
+        String clientKey = String.valueOf(B64Code.encode(key));
+        // what the server responds with
+        String serverHash = AcceptHash.hashKey(clientKey);
+
+        // how the client validates
+        Assert.assertThat(serverHash,is("mVL6JKtNRC4tluIaFAW2hhMffgE="));
+    }
+
+    /**
+     * Test of values present in RFC-6455.
+     * <p>
+     * Note: client key bytes are "7468652073616d706c65206e6f6e6365"
+     */
+    @Test
+    public void testRfcHashExample()
+    {
+        // What the client sends in the RFC
+        String clientKey = "dGhlIHNhbXBsZSBub25jZQ==";
+
+        // What the server responds with
+        String serverAccept = AcceptHash.hashKey(clientKey);
+        String expectedHash = "s3pPLMBiTxaQ9kYGzzhZRbK+xOo=";
+
+        Assert.assertThat(serverAccept,is(expectedHash));
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/CloseInfoTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/CloseInfoTest.java
new file mode 100644
index 0000000..eefe04c
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/CloseInfoTest.java
@@ -0,0 +1,166 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common;
+
+import static org.eclipse.jetty.websocket.api.StatusCode.*;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.websocket.api.ProtocolException;
+import org.eclipse.jetty.websocket.common.frames.CloseFrame;
+import org.junit.Test;
+
+public class CloseInfoTest
+{
+    /**
+     * A test where no close is provided
+     */
+    @Test
+    public void testAnonymousClose()
+    {
+        CloseInfo close = new CloseInfo();
+        assertThat("close.code",close.getStatusCode(),is(NO_CODE));
+        assertThat("close.reason",close.getReason(),nullValue());
+
+        CloseFrame frame = close.asFrame();
+        assertThat("close frame op code",frame.getOpCode(),is(OpCode.CLOSE));
+        // should result in no payload
+        assertThat("close frame has payload",frame.hasPayload(),is(false));
+        assertThat("close frame payload length",frame.getPayloadLength(),is(0));
+    }
+    
+    /**
+     * A test where NO_CODE (1005) is provided
+     */
+    @Test
+    public void testNoCode()
+    {
+        CloseInfo close = new CloseInfo(NO_CODE);
+        assertThat("close.code",close.getStatusCode(),is(NO_CODE));
+        assertThat("close.reason",close.getReason(),nullValue());
+
+        CloseFrame frame = close.asFrame();
+        assertThat("close frame op code",frame.getOpCode(),is(OpCode.CLOSE));
+        // should result in no payload
+        assertThat("close frame has payload",frame.hasPayload(),is(false));
+        assertThat("close frame payload length",frame.getPayloadLength(),is(0));
+    }
+    
+    /**
+     * A test where NO_CLOSE (1006) is provided
+     */
+    @Test
+    public void testNoClose()
+    {
+        CloseInfo close = new CloseInfo(NO_CLOSE);
+        assertThat("close.code",close.getStatusCode(),is(NO_CLOSE));
+        assertThat("close.reason",close.getReason(),nullValue());
+
+        CloseFrame frame = close.asFrame();
+        assertThat("close frame op code",frame.getOpCode(),is(OpCode.CLOSE));
+        // should result in no payload
+        assertThat("close frame has payload",frame.hasPayload(),is(false));
+        assertThat("close frame payload length",frame.getPayloadLength(),is(0));
+    }
+
+    /**
+     * A test of FAILED_TLS_HANDSHAKE (1007)
+     */
+    @Test
+    public void testFailedTlsHandshake()
+    {
+        CloseInfo close = new CloseInfo(FAILED_TLS_HANDSHAKE);
+        assertThat("close.code",close.getStatusCode(),is(FAILED_TLS_HANDSHAKE));
+        assertThat("close.reason",close.getReason(),nullValue());
+
+        try
+        {
+            @SuppressWarnings("unused")
+            CloseFrame frame = close.asFrame();
+            fail("Expected " + ProtocolException.class.getName());
+        }
+        catch (ProtocolException e)
+        {
+            // expected path
+            assertThat("ProtocolException message",e.getMessage(),containsString("not allowed (per RFC6455)"));
+        }
+    }
+
+    /**
+     * A test of NORMAL (1000)
+     */
+    @Test
+    public void testNormal()
+    {
+        CloseInfo close = new CloseInfo(NORMAL);
+        assertThat("close.code",close.getStatusCode(),is(NORMAL));
+        assertThat("close.reason",close.getReason(),nullValue());
+
+        CloseFrame frame = close.asFrame();
+        assertThat("close frame op code",frame.getOpCode(),is(OpCode.CLOSE));
+        assertThat("close frame payload length",frame.getPayloadLength(),is(2));
+    }
+    
+    private ByteBuffer asByteBuffer(int statusCode, String reason)
+    {
+        int len = 2; // status code length
+        byte utf[] = null;
+        if (StringUtil.isNotBlank(reason))
+        {
+            utf = StringUtil.getUtf8Bytes(reason);
+            len += utf.length;
+        }
+
+        ByteBuffer buf = BufferUtil.allocate(len);
+        BufferUtil.flipToFill(buf);
+        buf.put((byte)((statusCode >>> 8) & 0xFF));
+        buf.put((byte)((statusCode >>> 0) & 0xFF));
+
+        if (utf != null)
+        {
+            buf.put(utf,0,utf.length);
+        }
+        BufferUtil.flipToFlush(buf,0);
+        
+        return buf;
+    }
+    
+    @Test
+    public void testFromFrame()
+    {
+        ByteBuffer payload = asByteBuffer(NORMAL,null);
+        assertThat("payload length", payload.remaining(), is(2));
+        CloseFrame frame = new CloseFrame();
+        frame.setPayload(payload);
+        
+        // create from frame
+        CloseInfo close = new CloseInfo(frame);
+        assertThat("close.code",close.getStatusCode(),is(NORMAL));
+        assertThat("close.reason",close.getReason(),nullValue());
+
+        // and back again
+        frame = close.asFrame();
+        assertThat("close frame op code",frame.getOpCode(),is(OpCode.CLOSE));
+        assertThat("close frame payload length",frame.getPayloadLength(),is(2));
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/ClosePayloadParserTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/ClosePayloadParserTest.java
new file mode 100644
index 0000000..a1af73b
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/ClosePayloadParserTest.java
@@ -0,0 +1,67 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common;
+
+import static org.hamcrest.Matchers.is;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+
+import org.eclipse.jetty.websocket.api.StatusCode;
+import org.eclipse.jetty.websocket.api.WebSocketBehavior;
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.common.test.IncomingFramesCapture;
+import org.eclipse.jetty.websocket.common.test.UnitParser;
+import org.eclipse.jetty.websocket.common.util.MaskedByteBuffer;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class ClosePayloadParserTest
+{
+    @Test
+    public void testGameOver()
+    {
+        String expectedReason = "Game Over";
+
+        byte utf[] = expectedReason.getBytes(StandardCharsets.UTF_8);
+        ByteBuffer payload = ByteBuffer.allocate(utf.length + 2);
+        payload.putChar((char)StatusCode.NORMAL);
+        payload.put(utf,0,utf.length);
+        payload.flip();
+
+        ByteBuffer buf = ByteBuffer.allocate(24);
+        buf.put((byte)(0x80 | OpCode.CLOSE)); // fin + close
+        buf.put((byte)(0x80 | payload.remaining()));
+        MaskedByteBuffer.putMask(buf);
+        MaskedByteBuffer.putPayload(buf,payload);
+        buf.flip();
+
+        WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.SERVER);
+        Parser parser = new UnitParser(policy);
+        IncomingFramesCapture capture = new IncomingFramesCapture();
+        parser.setIncomingFramesHandler(capture);
+        parser.parse(buf);
+
+        capture.assertNoErrors();
+        capture.assertHasFrame(OpCode.CLOSE,1);
+        CloseInfo close = new CloseInfo(capture.getFrames().poll());
+        Assert.assertThat("CloseFrame.statusCode",close.getStatusCode(),is(StatusCode.NORMAL));
+        Assert.assertThat("CloseFrame.data",close.getReason(),is(expectedReason));
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/GeneratorParserRoundtripTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/GeneratorParserRoundtripTest.java
new file mode 100644
index 0000000..e9ff59e
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/GeneratorParserRoundtripTest.java
@@ -0,0 +1,124 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common;
+
+import static org.hamcrest.Matchers.*;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
+import org.eclipse.jetty.websocket.common.test.IncomingFramesCapture;
+import org.eclipse.jetty.websocket.common.test.LeakTrackingBufferPoolRule;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class GeneratorParserRoundtripTest
+{
+    @Rule
+    public LeakTrackingBufferPoolRule bufferPool = new LeakTrackingBufferPoolRule("GeneratorParserRoundtrip");
+    
+    @Test
+    public void testParserAndGenerator() throws Exception
+    {
+        WebSocketPolicy policy = WebSocketPolicy.newClientPolicy();
+        Generator gen = new Generator(policy,bufferPool);
+        Parser parser = new Parser(policy,bufferPool);
+        IncomingFramesCapture capture = new IncomingFramesCapture();
+        parser.setIncomingFramesHandler(capture);
+
+        String message = "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF";
+
+        ByteBuffer out = bufferPool.acquire(8192,false);
+        try
+        {
+            // Generate Buffer
+            BufferUtil.flipToFill(out);
+            WebSocketFrame frame = new TextFrame().setPayload(message);
+            ByteBuffer header = gen.generateHeaderBytes(frame);
+            ByteBuffer payload = frame.getPayload();
+            out.put(header);
+            out.put(payload);
+
+            // Parse Buffer
+            BufferUtil.flipToFlush(out,0);
+            parser.parse(out);
+        }
+        finally
+        {
+            bufferPool.release(out);
+        }
+
+        // Validate
+        capture.assertNoErrors();
+        capture.assertHasFrame(OpCode.TEXT,1);
+
+        TextFrame txt = (TextFrame)capture.getFrames().poll();
+        Assert.assertThat("Text parsed",txt.getPayloadAsUTF8(),is(message));
+    }
+
+    @Test
+    public void testParserAndGeneratorMasked() throws Exception
+    {
+        Generator gen = new Generator(WebSocketPolicy.newClientPolicy(),bufferPool);
+        Parser parser = new Parser(WebSocketPolicy.newServerPolicy(),bufferPool);
+        IncomingFramesCapture capture = new IncomingFramesCapture();
+        parser.setIncomingFramesHandler(capture);
+
+        String message = "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF";
+
+        ByteBuffer out = bufferPool.acquire(8192,false);
+        BufferUtil.flipToFill(out);
+        try
+        {
+            // Setup Frame
+            WebSocketFrame frame = new TextFrame().setPayload(message);
+
+            // Add masking
+            byte mask[] = new byte[4];
+            Arrays.fill(mask,(byte)0xFF);
+            frame.setMask(mask);
+
+            // Generate Buffer
+            ByteBuffer header = gen.generateHeaderBytes(frame);
+            ByteBuffer payload = frame.getPayload();
+            out.put(header);
+            out.put(payload);
+
+            // Parse Buffer
+            BufferUtil.flipToFlush(out,0);
+            parser.parse(out);
+        }
+        finally
+        {
+            bufferPool.release(out);
+        }
+
+        // Validate
+        capture.assertNoErrors();
+        capture.assertHasFrame(OpCode.TEXT,1);
+
+        TextFrame txt = (TextFrame)capture.getFrames().poll();
+        Assert.assertTrue("Text.isMasked",txt.isMasked());
+        Assert.assertThat("Text parsed",txt.getPayloadAsUTF8(),is(message));
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/GeneratorTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/GeneratorTest.java
new file mode 100644
index 0000000..a13b2ec
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/GeneratorTest.java
@@ -0,0 +1,319 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.StatusCode;
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.common.frames.BinaryFrame;
+import org.eclipse.jetty.websocket.common.frames.CloseFrame;
+import org.eclipse.jetty.websocket.common.frames.PingFrame;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
+import org.eclipse.jetty.websocket.common.test.IncomingFramesCapture;
+import org.eclipse.jetty.websocket.common.test.UnitGenerator;
+import org.eclipse.jetty.websocket.common.test.UnitParser;
+import org.eclipse.jetty.websocket.common.util.Hex;
+import org.junit.Assert;
+import org.junit.Test;
+
+import static org.hamcrest.Matchers.is;
+
+public class GeneratorTest
+{
+    private static final Logger LOG = Log.getLogger(GeneratorTest.WindowHelper.class);
+
+    public static class WindowHelper
+    {
+        final int windowSize;
+        int totalParts;
+        int totalBytes;
+
+        public WindowHelper(int windowSize)
+        {
+            this.windowSize = windowSize;
+            this.totalParts = 0;
+            this.totalBytes = 0;
+        }
+
+        public ByteBuffer generateWindowed(Frame... frames)
+        {
+            // Create Buffer to hold all generated frames in a single buffer
+            int completeBufSize = 0;
+            for (Frame f : frames)
+            {
+                completeBufSize += Generator.MAX_HEADER_LENGTH + f.getPayloadLength();
+            }
+
+            ByteBuffer completeBuf = ByteBuffer.allocate(completeBufSize);
+            BufferUtil.clearToFill(completeBuf);
+
+            // Generate from all frames
+            Generator generator = new UnitGenerator();
+
+            for (Frame f : frames)
+            {
+                ByteBuffer header = generator.generateHeaderBytes(f);
+                totalBytes += BufferUtil.put(header,completeBuf);
+
+                if (f.hasPayload())
+                {
+                    ByteBuffer payload=f.getPayload();
+                    totalBytes += payload.remaining();
+                    totalParts++;
+                    completeBuf.put(payload.slice());
+                }
+            }
+
+            // Return results
+            BufferUtil.flipToFlush(completeBuf,0);
+            return completeBuf;
+        }
+
+        public void assertTotalParts(int expectedParts)
+        {
+            Assert.assertThat("Generated Parts",totalParts,is(expectedParts));
+        }
+
+        public void assertTotalBytes(int expectedBytes)
+        {
+            Assert.assertThat("Generated Bytes",totalBytes,is(expectedBytes));
+        }
+    }
+
+    private void assertGeneratedBytes(CharSequence expectedBytes, Frame... frames)
+    {
+        // collect up all frames as single ByteBuffer
+        ByteBuffer allframes = UnitGenerator.generate(frames);
+        // Get hex String form of all frames bytebuffer.
+        String actual = Hex.asHex(allframes);
+        // Validate
+        Assert.assertThat("Buffer",actual,is(expectedBytes.toString()));
+    }
+
+    private String asMaskedHex(String str, byte[] maskingKey)
+    {
+        byte utf[] = StringUtil.getUtf8Bytes(str);
+        mask(utf,maskingKey);
+        return Hex.asHex(utf);
+    }
+
+    private void mask(byte[] buf, byte[] maskingKey)
+    {
+        int size = buf.length;
+        for (int i = 0; i < size; i++)
+        {
+            buf[i] ^= maskingKey[i % 4];
+        }
+    }
+
+    @Test
+    public void testClose_Empty()
+    {
+        // 0 byte payload (no status code)
+        assertGeneratedBytes("8800",new CloseFrame());
+    }
+
+    @Test
+    public void testClose_CodeNoReason()
+    {
+        CloseInfo close = new CloseInfo(StatusCode.NORMAL);
+        // 2 byte payload (2 bytes for status code)
+        assertGeneratedBytes("880203E8",close.asFrame());
+    }
+
+    @Test
+    public void testClose_CodeOkReason()
+    {
+        CloseInfo close = new CloseInfo(StatusCode.NORMAL,"OK");
+        // 4 byte payload (2 bytes for status code, 2 more for "OK")
+        assertGeneratedBytes("880403E84F4B",close.asFrame());
+    }
+
+    @Test
+    public void testText_Hello()
+    {
+        WebSocketFrame frame = new TextFrame().setPayload("Hello");
+        byte utf[] = StringUtil.getUtf8Bytes("Hello");
+        assertGeneratedBytes("8105" + Hex.asHex(utf),frame);
+    }
+
+    @Test
+    public void testText_Masked()
+    {
+        WebSocketFrame frame = new TextFrame().setPayload("Hello");
+        byte maskingKey[] = Hex.asByteArray("11223344");
+        frame.setMask(maskingKey);
+
+        // what is expected
+        StringBuilder expected = new StringBuilder();
+        expected.append("8185").append("11223344");
+        expected.append(asMaskedHex("Hello",maskingKey));
+
+        // validate
+        assertGeneratedBytes(expected,frame);
+    }
+
+    @Test
+    public void testText_Masked_OffsetSourceByteBuffer()
+    {
+        ByteBuffer payload = ByteBuffer.allocate(100);
+        payload.position(5);
+        payload.put(StringUtil.getUtf8Bytes("Hello"));
+        payload.flip();
+        payload.position(5);
+        // at this point, we have a ByteBuffer of 100 bytes.
+        // but only a few bytes in the middle are made available for the payload.
+        // we are testing that masking works as intended, even if the provided
+        // payload does not start at position 0.
+        LOG.debug("Payload = {}",BufferUtil.toDetailString(payload));
+        WebSocketFrame frame = new TextFrame().setPayload(payload);
+        byte maskingKey[] = Hex.asByteArray("11223344");
+        frame.setMask(maskingKey);
+
+        // what is expected
+        StringBuilder expected = new StringBuilder();
+        expected.append("8185").append("11223344");
+        expected.append(asMaskedHex("Hello",maskingKey));
+
+        // validate
+        assertGeneratedBytes(expected,frame);
+    }
+
+    /**
+     * Prevent regression of masking of many packets.
+     */
+    @Test
+    public void testManyMasked()
+    {
+        int pingCount = 2;
+
+        // Prepare frames
+        WebSocketFrame[] frames = new WebSocketFrame[pingCount + 1];
+        for (int i = 0; i < pingCount; i++)
+        {
+            frames[i] = new PingFrame().setPayload(String.format("ping-%d",i));
+        }
+        frames[pingCount] = new CloseInfo(StatusCode.NORMAL).asFrame();
+
+        // Mask All Frames
+        byte maskingKey[] = Hex.asByteArray("11223344");
+        for (WebSocketFrame f : frames)
+        {
+            f.setMask(maskingKey);
+        }
+
+        // Validate result of generation
+        StringBuilder expected = new StringBuilder();
+        expected.append("8986").append("11223344");
+        expected.append(asMaskedHex("ping-0",maskingKey)); // ping 0
+        expected.append("8986").append("11223344");
+        expected.append(asMaskedHex("ping-1",maskingKey)); // ping 1
+        expected.append("8882").append("11223344");
+        byte closure[] = Hex.asByteArray("03E8");
+        mask(closure,maskingKey);
+        expected.append(Hex.asHex(closure)); // normal closure
+
+        assertGeneratedBytes(expected,frames);
+    }
+
+    /**
+     * Test the windowed generate of a frame that has no masking.
+     */
+    @Test
+    public void testWindowedGenerate()
+    {
+        // A decent sized frame, no masking
+        byte payload[] = new byte[10240];
+        Arrays.fill(payload,(byte)0x44);
+
+        WebSocketFrame frame = new BinaryFrame().setPayload(payload);
+
+        // Generate
+        int windowSize = 1024;
+        WindowHelper helper = new WindowHelper(windowSize);
+        ByteBuffer completeBuffer = helper.generateWindowed(frame);
+
+        // Validate
+        int expectedHeaderSize = 4;
+        int expectedSize = payload.length + expectedHeaderSize;
+        int expectedParts = 1;
+
+        helper.assertTotalParts(expectedParts);
+        helper.assertTotalBytes(payload.length + expectedHeaderSize);
+
+        Assert.assertThat("Generated Buffer",completeBuffer.remaining(),is(expectedSize));
+    }
+
+    @Test
+    public void testWindowedGenerateWithMasking()
+    {
+        // A decent sized frame, with masking
+        byte payload[] = new byte[10240];
+        Arrays.fill(payload,(byte)0x55);
+
+        byte mask[] = new byte[]
+        { 0x2A, (byte)0xF0, 0x0F, 0x00 };
+
+        WebSocketFrame frame = new BinaryFrame().setPayload(payload);
+        frame.setMask(mask); // masking!
+
+        // Generate
+        int windowSize = 2929;
+        WindowHelper helper = new WindowHelper(windowSize);
+        ByteBuffer completeBuffer = helper.generateWindowed(frame);
+
+        // Validate
+        int expectedHeaderSize = 8;
+        int expectedSize = payload.length + expectedHeaderSize;
+        int expectedParts = 1;
+
+        helper.assertTotalParts(expectedParts);
+        helper.assertTotalBytes(payload.length + expectedHeaderSize);
+
+        Assert.assertThat("Generated Buffer",completeBuffer.remaining(),is(expectedSize));
+
+        // Parse complete buffer.
+        WebSocketPolicy policy = WebSocketPolicy.newServerPolicy();
+        Parser parser = new UnitParser(policy);
+        IncomingFramesCapture capture = new IncomingFramesCapture();
+        parser.setIncomingFramesHandler(capture);
+
+        parser.parse(completeBuffer);
+
+        // Assert validity of frame
+        WebSocketFrame actual = capture.getFrames().poll();
+        Assert.assertThat("Frame.opcode",actual.getOpCode(),is(OpCode.BINARY));
+        Assert.assertThat("Frame.payloadLength",actual.getPayloadLength(),is(payload.length));
+
+        // Validate payload contents for proper masking
+        ByteBuffer actualData = actual.getPayload().slice();
+        Assert.assertThat("Frame.payload.remaining",actualData.remaining(),is(payload.length));
+        while (actualData.remaining() > 0)
+        {
+            Assert.assertThat("Actual.payload[" + actualData.position() + "]",actualData.get(),is((byte)0x55));
+        }
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/ParserTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/ParserTest.java
new file mode 100644
index 0000000..a484804
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/ParserTest.java
@@ -0,0 +1,255 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common;
+
+import static org.hamcrest.Matchers.is;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.websocket.api.StatusCode;
+import org.eclipse.jetty.websocket.api.WebSocketBehavior;
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.common.frames.ContinuationFrame;
+import org.eclipse.jetty.websocket.common.frames.DataFrame;
+import org.eclipse.jetty.websocket.common.frames.PingFrame;
+import org.eclipse.jetty.websocket.common.frames.PongFrame;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
+import org.eclipse.jetty.websocket.common.test.IncomingFramesCapture;
+import org.eclipse.jetty.websocket.common.test.UnitGenerator;
+import org.eclipse.jetty.websocket.common.test.UnitParser;
+import org.eclipse.jetty.websocket.common.util.Hex;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class ParserTest
+{
+    /**
+     * Similar to the server side 5.15 testcase. A normal 2 fragment text text message, followed by another continuation.
+     */
+    @Test
+    public void testParseCase5_15()
+    {
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new TextFrame().setPayload("fragment1").setFin(false));
+        send.add(new ContinuationFrame().setPayload("fragment2").setFin(true));
+        send.add(new ContinuationFrame().setPayload("fragment3").setFin(false)); // bad frame
+        send.add(new TextFrame().setPayload("fragment4").setFin(true));
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        ByteBuffer completeBuf = UnitGenerator.generate(send);
+        UnitParser parser = new UnitParser();
+        IncomingFramesCapture capture = new IncomingFramesCapture();
+        parser.setIncomingFramesHandler(capture);
+
+        parser.parseQuietly(completeBuf);
+
+        capture.assertErrorCount(1);
+        capture.assertHasFrame(OpCode.TEXT,1);
+        capture.assertHasFrame(OpCode.CONTINUATION,1);
+    }
+
+    /**
+     * Similar to the server side 5.18 testcase. Text message fragmented as 2 frames, both as opcode=TEXT
+     */
+    @Test
+    public void testParseCase5_18()
+    {
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new TextFrame().setPayload("fragment1").setFin(false));
+        send.add(new TextFrame().setPayload("fragment2").setFin(true)); // bad frame, must be continuation
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        ByteBuffer completeBuf = UnitGenerator.generate(send);
+        UnitParser parser = new UnitParser();
+        IncomingFramesCapture capture = new IncomingFramesCapture();
+        parser.setIncomingFramesHandler(capture);
+        parser.parseQuietly(completeBuf);
+
+        capture.assertErrorCount(1);
+        capture.assertHasFrame(OpCode.TEXT,1); // fragment 1
+    }
+
+    /**
+     * Similar to the server side 5.19 testcase. text message, send in 5 frames/fragments, with 2 pings in the mix.
+     */
+    @Test
+    public void testParseCase5_19()
+    {
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new TextFrame().setPayload("f1").setFin(false));
+        send.add(new ContinuationFrame().setPayload(",f2").setFin(false));
+        send.add(new PingFrame().setPayload("pong-1"));
+        send.add(new ContinuationFrame().setPayload(",f3").setFin(false));
+        send.add(new ContinuationFrame().setPayload(",f4").setFin(false));
+        send.add(new PingFrame().setPayload("pong-2"));
+        send.add(new ContinuationFrame().setPayload(",f5").setFin(true));
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        ByteBuffer completeBuf = UnitGenerator.generate(send);
+        UnitParser parser = new UnitParser();
+        IncomingFramesCapture capture = new IncomingFramesCapture();
+        parser.setIncomingFramesHandler(capture);
+        parser.parseQuietly(completeBuf);
+
+        capture.assertErrorCount(0);
+        capture.assertHasFrame(OpCode.TEXT,1);
+        capture.assertHasFrame(OpCode.CONTINUATION,4);
+        capture.assertHasFrame(OpCode.CLOSE,1);
+        capture.assertHasFrame(OpCode.PING,2);
+    }
+
+    /**
+     * Similar to the server side 5.6 testcase. pong, then text, then close frames.
+     */
+    @Test
+    public void testParseCase5_6()
+    {
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new PongFrame().setPayload("ping"));
+        send.add(new TextFrame().setPayload("hello, world"));
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        ByteBuffer completeBuf = UnitGenerator.generate(send);
+        UnitParser parser = new UnitParser();
+        IncomingFramesCapture capture = new IncomingFramesCapture();
+        parser.setIncomingFramesHandler(capture);
+        parser.parse(completeBuf);
+
+        capture.assertErrorCount(0);
+        capture.assertHasFrame(OpCode.TEXT,1);
+        capture.assertHasFrame(OpCode.CLOSE,1);
+        capture.assertHasFrame(OpCode.PONG,1);
+    }
+
+    /**
+     * Similar to the server side 6.2.3 testcase. Lots of small 1 byte UTF8 Text frames, representing 1 overall text message.
+     */
+    @Test
+    public void testParseCase6_2_3()
+    {
+        String utf8 = "Hello-\uC2B5@\uC39F\uC3A4\uC3BC\uC3A0\uC3A1-UTF-8!!";
+        byte msg[] = StringUtil.getUtf8Bytes(utf8);
+
+        List<WebSocketFrame> send = new ArrayList<>();
+        int textCount = 0;
+        int continuationCount = 0;
+        int len = msg.length;
+        boolean continuation = false;
+        byte mini[];
+        for (int i = 0; i < len; i++)
+        {
+            DataFrame frame = null;
+            if (continuation)
+            {
+                frame = new ContinuationFrame();
+                continuationCount++;
+            }
+            else
+            {
+                frame = new TextFrame();
+                textCount++;
+            }
+            mini = new byte[1];
+            mini[0] = msg[i];
+            frame.setPayload(ByteBuffer.wrap(mini));
+            boolean isLast = (i >= (len - 1));
+            frame.setFin(isLast);
+            send.add(frame);
+            continuation = true;
+        }
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        ByteBuffer completeBuf = UnitGenerator.generate(send);
+        UnitParser parser = new UnitParser();
+        IncomingFramesCapture capture = new IncomingFramesCapture();
+        parser.setIncomingFramesHandler(capture);
+        parser.parse(completeBuf);
+
+        capture.assertErrorCount(0);
+        capture.assertHasFrame(OpCode.TEXT,textCount);
+        capture.assertHasFrame(OpCode.CONTINUATION,continuationCount);
+        capture.assertHasFrame(OpCode.CLOSE,1);
+    }
+
+    @Test
+    public void testParseNothing()
+    {
+        ByteBuffer buf = ByteBuffer.allocate(16);
+        // Put nothing in the buffer.
+        buf.flip();
+
+        WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.SERVER);
+        Parser parser = new UnitParser(policy);
+        IncomingFramesCapture capture = new IncomingFramesCapture();
+        parser.setIncomingFramesHandler(capture);
+        parser.parse(buf);
+
+        capture.assertNoErrors();
+        Assert.assertThat("Frame Count",capture.getFrames().size(),is(0));
+    }
+
+    @Test
+    public void testWindowedParseLargeFrame()
+    {
+        // Create frames
+        byte payload[] = new byte[65536];
+        Arrays.fill(payload,(byte)'*');
+
+        List<WebSocketFrame> frames = new ArrayList<>();
+        TextFrame text = new TextFrame();
+        text.setPayload(ByteBuffer.wrap(payload));
+        text.setMask(Hex.asByteArray("11223344"));
+        frames.add(text);
+        frames.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        // Build up raw (network bytes) buffer
+        ByteBuffer networkBytes = UnitGenerator.generate(frames);
+
+        // Parse, in 4096 sized windows
+        WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.SERVER);
+        Parser parser = new UnitParser(policy);
+        IncomingFramesCapture capture = new IncomingFramesCapture();
+        parser.setIncomingFramesHandler(capture);
+
+        while (networkBytes.remaining() > 0)
+        {
+            ByteBuffer window = networkBytes.slice();
+            int windowSize = Math.min(window.remaining(),4096);
+            window.limit(windowSize);
+            parser.parse(window);
+            networkBytes.position(networkBytes.position() + windowSize);
+        }
+
+        capture.assertNoErrors();
+        Assert.assertThat("Frame Count",capture.getFrames().size(),is(2));
+        WebSocketFrame frame = capture.getFrames().poll();
+        Assert.assertThat("Frame[0].opcode",frame.getOpCode(),is(OpCode.TEXT));
+        ByteBuffer actualPayload = frame.getPayload();
+        Assert.assertThat("Frame[0].payload.length",actualPayload.remaining(),is(payload.length));
+        // Should be all '*' characters (if masking is correct)
+        for (int i = actualPayload.position(); i < actualPayload.remaining(); i++)
+        {
+            Assert.assertThat("Frame[0].payload[i]",actualPayload.get(i),is((byte)'*'));
+        }
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/PingPayloadParserTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/PingPayloadParserTest.java
new file mode 100644
index 0000000..d9f5780
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/PingPayloadParserTest.java
@@ -0,0 +1,58 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common;
+
+import static org.hamcrest.Matchers.is;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.websocket.api.WebSocketBehavior;
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.common.frames.PingFrame;
+import org.eclipse.jetty.websocket.common.test.IncomingFramesCapture;
+import org.eclipse.jetty.websocket.common.test.UnitParser;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class PingPayloadParserTest
+{
+    @Test
+    public void testBasicPingParsing()
+    {
+        ByteBuffer buf = ByteBuffer.allocate(16);
+        BufferUtil.clearToFill(buf);
+        buf.put(new byte[]
+                { (byte)0x89, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f });
+        BufferUtil.flipToFlush(buf,0);
+
+        WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.CLIENT);
+        Parser parser = new UnitParser(policy);
+        IncomingFramesCapture capture = new IncomingFramesCapture();
+        parser.setIncomingFramesHandler(capture);
+        parser.parse(buf);
+
+        capture.assertNoErrors();
+        capture.assertHasFrame(OpCode.PING,1);
+        PingFrame ping = (PingFrame)capture.getFrames().poll();
+
+        String actual = BufferUtil.toUTF8String(ping.getPayload());
+        Assert.assertThat("PingFrame.payload",actual,is("Hello"));
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/RFC6455ExamplesGeneratorTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/RFC6455ExamplesGeneratorTest.java
new file mode 100644
index 0000000..5709f00
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/RFC6455ExamplesGeneratorTest.java
@@ -0,0 +1,191 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+import org.eclipse.jetty.websocket.common.frames.BinaryFrame;
+import org.eclipse.jetty.websocket.common.frames.ContinuationFrame;
+import org.eclipse.jetty.websocket.common.frames.PingFrame;
+import org.eclipse.jetty.websocket.common.frames.PongFrame;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
+import org.eclipse.jetty.websocket.common.test.ByteBufferAssert;
+import org.eclipse.jetty.websocket.common.test.UnitGenerator;
+import org.junit.Test;
+
+public class RFC6455ExamplesGeneratorTest
+{
+    private static final int FUDGE = 32;
+
+    @Test
+    public void testFragmentedUnmaskedTextMessage()
+    {
+        WebSocketFrame text1 = new TextFrame().setPayload("Hel").setFin(false);
+        WebSocketFrame text2 = new ContinuationFrame().setPayload("lo");
+
+        ByteBuffer actual1 = UnitGenerator.generate(text1);
+        ByteBuffer actual2 = UnitGenerator.generate(text2);
+
+        ByteBuffer expected1 = ByteBuffer.allocate(5);
+
+        expected1.put(new byte[]
+                { (byte)0x01, (byte)0x03, (byte)0x48, (byte)0x65, (byte)0x6c });
+
+        ByteBuffer expected2 = ByteBuffer.allocate(4);
+
+        expected2.put(new byte[]
+                { (byte)0x80, (byte)0x02, (byte)0x6c, (byte)0x6f });
+
+        expected1.flip();
+        expected2.flip();
+
+        ByteBufferAssert.assertEquals("t1 buffers are not equal",expected1,actual1);
+        ByteBufferAssert.assertEquals("t2 buffers are not equal",expected2,actual2);
+    }
+
+    @Test
+    public void testSingleMaskedPongRequest()
+    {
+        PongFrame pong = new PongFrame().setPayload("Hello");
+        pong.setMask(new byte[]
+                { 0x37, (byte)0xfa, 0x21, 0x3d });
+
+        ByteBuffer actual = UnitGenerator.generate(pong);
+
+        ByteBuffer expected = ByteBuffer.allocate(11);
+        // Raw bytes as found in RFC 6455, Section 5.7 - Examples
+        // Unmasked Pong request
+        expected.put(new byte[]
+                { (byte)0x8a, (byte)0x85, 0x37, (byte)0xfa, 0x21, 0x3d, 0x7f, (byte)0x9f, 0x4d, 0x51, 0x58 });
+        expected.flip(); // make readable
+
+        ByteBufferAssert.assertEquals("pong buffers are not equal",expected,actual);
+    }
+
+    @Test
+    public void testSingleMaskedTextMessage()
+    {
+        WebSocketFrame text = new TextFrame().setPayload("Hello");
+        text.setMask(new byte[]
+                { 0x37, (byte)0xfa, 0x21, 0x3d });
+
+        ByteBuffer actual = UnitGenerator.generate(text);
+
+        ByteBuffer expected = ByteBuffer.allocate(11);
+        // Raw bytes as found in RFC 6455, Section 5.7 - Examples
+        // A single-frame masked text message
+        expected.put(new byte[]
+                { (byte)0x81, (byte)0x85, 0x37, (byte)0xfa, 0x21, 0x3d, 0x7f, (byte)0x9f, 0x4d, 0x51, 0x58 });
+        expected.flip(); // make readable
+
+        ByteBufferAssert.assertEquals("masked text buffers are not equal",expected,actual);
+    }
+
+    @Test
+    public void testSingleUnmasked256ByteBinaryMessage()
+    {
+        int dataSize = 256;
+
+        BinaryFrame binary = new BinaryFrame();
+        byte payload[] = new byte[dataSize];
+        Arrays.fill(payload,(byte)0x44);
+        binary.setPayload(ByteBuffer.wrap(payload));
+
+        ByteBuffer actual = UnitGenerator.generate(binary);
+
+        ByteBuffer expected = ByteBuffer.allocate(dataSize + FUDGE);
+        // Raw bytes as found in RFC 6455, Section 5.7 - Examples
+        // 256 bytes binary message in a single unmasked frame
+        expected.put(new byte[]
+                { (byte)0x82, (byte)0x7E });
+        expected.putShort((short)0x01_00);
+
+        for (int i = 0; i < dataSize; i++)
+        {
+            expected.put((byte)0x44);
+        }
+
+        expected.flip();
+
+        ByteBufferAssert.assertEquals("binary buffers are not equal",expected,actual);
+    }
+
+    @Test
+    public void testSingleUnmasked64KBinaryMessage()
+    {
+        int dataSize = 1024 * 64;
+
+        BinaryFrame binary = new BinaryFrame();
+        byte payload[] = new byte[dataSize];
+        Arrays.fill(payload,(byte)0x44);
+        binary.setPayload(ByteBuffer.wrap(payload));
+
+        ByteBuffer actual = UnitGenerator.generate(binary);
+
+        ByteBuffer expected = ByteBuffer.allocate(dataSize + 10);
+        // Raw bytes as found in RFC 6455, Section 5.7 - Examples
+        // 64k bytes binary message in a single unmasked frame
+        expected.put(new byte[]
+                { (byte)0x82, (byte)0x7F });
+        expected.putInt(0x00_00_00_00);
+        expected.putInt(0x00_01_00_00);
+
+        for (int i = 0; i < dataSize; i++)
+        {
+            expected.put((byte)0x44);
+        }
+
+        expected.flip();
+
+        ByteBufferAssert.assertEquals("binary buffers are not equal",expected,actual);
+    }
+
+    @Test
+    public void testSingleUnmaskedPingRequest() throws Exception
+    {
+        PingFrame ping = new PingFrame().setPayload("Hello");
+
+        ByteBuffer actual = UnitGenerator.generate(ping);
+
+        ByteBuffer expected = ByteBuffer.allocate(10);
+        expected.put(new byte[]
+                { (byte)0x89, (byte)0x05, (byte)0x48, (byte)0x65, (byte)0x6c, (byte)0x6c, (byte)0x6f });
+        expected.flip(); // make readable
+
+        ByteBufferAssert.assertEquals("Ping buffers",expected,actual);
+    }
+
+    @Test
+    public void testSingleUnmaskedTextMessage()
+    {
+        WebSocketFrame text = new TextFrame().setPayload("Hello");
+
+        ByteBuffer actual = UnitGenerator.generate(text);
+
+        ByteBuffer expected = ByteBuffer.allocate(10);
+
+        expected.put(new byte[]
+                { (byte)0x81, (byte)0x05, (byte)0x48, (byte)0x65, (byte)0x6c, (byte)0x6c, (byte)0x6f });
+
+        expected.flip();
+
+        ByteBufferAssert.assertEquals("t1 buffers are not equal",expected,actual);
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/RFC6455ExamplesParserTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/RFC6455ExamplesParserTest.java
new file mode 100644
index 0000000..186d08a
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/RFC6455ExamplesParserTest.java
@@ -0,0 +1,252 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common;
+
+import static org.hamcrest.Matchers.is;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.websocket.api.WebSocketBehavior;
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.common.test.IncomingFramesCapture;
+import org.eclipse.jetty.websocket.common.test.UnitParser;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Collection of Example packets as found in <a href="https://tools.ietf.org/html/rfc6455#section-5.7">RFC 6455 Examples section</a>
+ */
+public class RFC6455ExamplesParserTest
+{
+    @Test
+    public void testFragmentedUnmaskedTextMessage()
+    {
+        WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.CLIENT);
+        Parser parser = new UnitParser(policy);
+        IncomingFramesCapture capture = new IncomingFramesCapture();
+        parser.setIncomingFramesHandler(capture);
+
+        ByteBuffer buf = ByteBuffer.allocate(16);
+        BufferUtil.clearToFill(buf);
+
+        // Raw bytes as found in RFC 6455, Section 5.7 - Examples
+        // A fragmented unmasked text message (part 1 of 2 "Hel")
+        buf.put(new byte[]
+                { (byte)0x01, (byte)0x03, 0x48, (byte)0x65, 0x6c });
+
+        // Parse #1
+        BufferUtil.flipToFlush(buf,0);
+        parser.parse(buf);
+
+        // part 2 of 2 "lo" (A continuation frame of the prior text message)
+        BufferUtil.flipToFill(buf);
+        buf.put(new byte[]
+                { (byte)0x80, 0x02, 0x6c, 0x6f });
+
+        // Parse #2
+        BufferUtil.flipToFlush(buf,0);
+        parser.parse(buf);
+
+        capture.assertNoErrors();
+        capture.assertHasFrame(OpCode.TEXT,1);
+        capture.assertHasFrame(OpCode.CONTINUATION,1);
+
+        WebSocketFrame txt = capture.getFrames().poll();
+        String actual = BufferUtil.toUTF8String(txt.getPayload());
+        Assert.assertThat("TextFrame[0].data",actual,is("Hel"));
+        txt = capture.getFrames().poll();
+        actual = BufferUtil.toUTF8String(txt.getPayload());
+        Assert.assertThat("TextFrame[1].data",actual,is("lo"));
+    }
+
+    @Test
+    public void testSingleMaskedPongRequest()
+    {
+        ByteBuffer buf = ByteBuffer.allocate(16);
+        // Raw bytes as found in RFC 6455, Section 5.7 - Examples
+        // Unmasked Pong request
+        buf.put(new byte[]
+                { (byte)0x8a, (byte)0x85, 0x37, (byte)0xfa, 0x21, 0x3d, 0x7f, (byte)0x9f, 0x4d, 0x51, 0x58 });
+        buf.flip();
+
+        WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.SERVER);
+        Parser parser = new UnitParser(policy);
+        IncomingFramesCapture capture = new IncomingFramesCapture();
+        parser.setIncomingFramesHandler(capture);
+        parser.parse(buf);
+
+        capture.assertNoErrors();
+        capture.assertHasFrame(OpCode.PONG,1);
+
+        WebSocketFrame pong = capture.getFrames().poll();
+        String actual = BufferUtil.toUTF8String(pong.getPayload());
+        Assert.assertThat("PongFrame.payload",actual,is("Hello"));
+    }
+
+    @Test
+    public void testSingleMaskedTextMessage()
+    {
+        ByteBuffer buf = ByteBuffer.allocate(16);
+        // Raw bytes as found in RFC 6455, Section 5.7 - Examples
+        // A single-frame masked text message
+        buf.put(new byte[]
+                { (byte)0x81, (byte)0x85, 0x37, (byte)0xfa, 0x21, 0x3d, 0x7f, (byte)0x9f, 0x4d, 0x51, 0x58 });
+        buf.flip();
+
+        WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.SERVER);
+        Parser parser = new UnitParser(policy);
+        IncomingFramesCapture capture = new IncomingFramesCapture();
+        parser.setIncomingFramesHandler(capture);
+        parser.parse(buf);
+
+        capture.assertNoErrors();
+        capture.assertHasFrame(OpCode.TEXT,1);
+
+        WebSocketFrame txt = capture.getFrames().poll();
+        String actual = BufferUtil.toUTF8String(txt.getPayload());
+        Assert.assertThat("TextFrame.payload",actual,is("Hello"));
+    }
+
+    @Test
+    public void testSingleUnmasked256ByteBinaryMessage()
+    {
+        int dataSize = 256;
+
+        ByteBuffer buf = ByteBuffer.allocate(dataSize + 10);
+        // Raw bytes as found in RFC 6455, Section 5.7 - Examples
+        // 256 bytes binary message in a single unmasked frame
+        buf.put(new byte[]
+                { (byte)0x82, 0x7E });
+        buf.putShort((short)0x01_00); // 16 bit size
+        for (int i = 0; i < dataSize; i++)
+        {
+            buf.put((byte)0x44);
+        }
+        buf.flip();
+
+        WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.CLIENT);
+        Parser parser = new UnitParser(policy);
+        IncomingFramesCapture capture = new IncomingFramesCapture();
+        parser.setIncomingFramesHandler(capture);
+        parser.parse(buf);
+
+        capture.assertNoErrors();
+        capture.assertHasFrame(OpCode.BINARY,1);
+
+        Frame bin = capture.getFrames().poll();
+
+        Assert.assertThat("BinaryFrame.payloadLength",bin.getPayloadLength(),is(dataSize));
+
+        ByteBuffer data = bin.getPayload();
+        Assert.assertThat("BinaryFrame.payload.length",data.remaining(),is(dataSize));
+
+        for (int i = 0; i < dataSize; i++)
+        {
+            Assert.assertThat("BinaryFrame.payload[" + i + "]",data.get(i),is((byte)0x44));
+        }
+    }
+
+    @Test
+    public void testSingleUnmasked64KByteBinaryMessage()
+    {
+        int dataSize = 1024 * 64;
+
+        ByteBuffer buf = ByteBuffer.allocate((dataSize + 10));
+        // Raw bytes as found in RFC 6455, Section 5.7 - Examples
+        // 64 Kbytes binary message in a single unmasked frame
+        buf.put(new byte[]
+                { (byte)0x82, 0x7F });
+        buf.putLong(dataSize); // 64bit size
+        for (int i = 0; i < dataSize; i++)
+        {
+            buf.put((byte)0x77);
+        }
+        buf.flip();
+
+        WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.CLIENT);
+        Parser parser = new UnitParser(policy);
+        IncomingFramesCapture capture = new IncomingFramesCapture();
+        parser.setIncomingFramesHandler(capture);
+        parser.parse(buf);
+
+        capture.assertNoErrors();
+        capture.assertHasFrame(OpCode.BINARY,1);
+
+        Frame bin = capture.getFrames().poll();
+
+        Assert.assertThat("BinaryFrame.payloadLength",bin.getPayloadLength(),is(dataSize));
+        ByteBuffer data = bin.getPayload();
+        Assert.assertThat("BinaryFrame.payload.length",data.remaining(),is(dataSize));
+
+        for (int i = 0; i < dataSize; i++)
+        {
+            Assert.assertThat("BinaryFrame.payload[" + i + "]",data.get(i),is((byte)0x77));
+        }
+    }
+
+    @Test
+    public void testSingleUnmaskedPingRequest()
+    {
+        ByteBuffer buf = ByteBuffer.allocate(16);
+        // Raw bytes as found in RFC 6455, Section 5.7 - Examples
+        // Unmasked Ping request
+        buf.put(new byte[]
+                { (byte)0x89, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f });
+        buf.flip();
+
+        WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.CLIENT);
+        Parser parser = new UnitParser(policy);
+        IncomingFramesCapture capture = new IncomingFramesCapture();
+        parser.setIncomingFramesHandler(capture);
+        parser.parse(buf);
+
+        capture.assertNoErrors();
+        capture.assertHasFrame(OpCode.PING,1);
+
+        WebSocketFrame ping = capture.getFrames().poll();
+        String actual = BufferUtil.toUTF8String(ping.getPayload());
+        Assert.assertThat("PingFrame.payload",actual,is("Hello"));
+    }
+
+    @Test
+    public void testSingleUnmaskedTextMessage()
+    {
+        ByteBuffer buf = ByteBuffer.allocate(16);
+        // Raw bytes as found in RFC 6455, Section 5.7 - Examples
+        // A single-frame unmasked text message
+        buf.put(new byte[]
+                { (byte)0x81, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f });
+        buf.flip();
+
+        WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.CLIENT);
+        Parser parser = new UnitParser(policy);
+        IncomingFramesCapture capture = new IncomingFramesCapture();
+        parser.setIncomingFramesHandler(capture);
+        parser.parse(buf);
+
+        capture.assertNoErrors();
+        capture.assertHasFrame(OpCode.TEXT,1);
+
+        WebSocketFrame txt = capture.getFrames().poll();
+        String actual = BufferUtil.toUTF8String(txt.getPayload());
+        Assert.assertThat("TextFrame.payload",actual,is("Hello"));
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/TextPayloadParserTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/TextPayloadParserTest.java
new file mode 100644
index 0000000..a551c32
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/TextPayloadParserTest.java
@@ -0,0 +1,236 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common;
+
+import static org.hamcrest.Matchers.allOf;
+import static org.hamcrest.Matchers.greaterThan;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.lessThan;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+
+import org.eclipse.jetty.websocket.api.MessageTooLargeException;
+import org.eclipse.jetty.websocket.api.StatusCode;
+import org.eclipse.jetty.websocket.api.WebSocketBehavior;
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.common.test.IncomingFramesCapture;
+import org.eclipse.jetty.websocket.common.test.UnitParser;
+import org.eclipse.jetty.websocket.common.util.MaskedByteBuffer;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class TextPayloadParserTest
+{
+    @Test
+    public void testFrameTooLargeDueToPolicy() throws Exception
+    {
+        WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.SERVER);
+        // Artificially small buffer/payload
+        policy.setInputBufferSize(1024); // read buffer
+        policy.setMaxTextMessageBufferSize(1024); // streaming buffer (not used in this test)
+        policy.setMaxTextMessageSize(1024); // actual maximum text message size policy
+        byte utf[] = new byte[2048];
+        Arrays.fill(utf,(byte)'a');
+
+        Assert.assertThat("Must be a medium length payload",utf.length,allOf(greaterThan(0x7E),lessThan(0xFFFF)));
+
+        ByteBuffer buf = ByteBuffer.allocate(utf.length + 8);
+        buf.put((byte)0x81); // text frame, fin = true
+        buf.put((byte)(0x80 | 0x7E)); // 0x7E == 126 (a 2 byte payload length)
+        buf.putShort((short)utf.length);
+        MaskedByteBuffer.putMask(buf);
+        MaskedByteBuffer.putPayload(buf,utf);
+        buf.flip();
+
+        UnitParser parser = new UnitParser(policy);
+        IncomingFramesCapture capture = new IncomingFramesCapture();
+        parser.setIncomingFramesHandler(capture);
+        parser.parseQuietly(buf);
+
+        capture.assertHasErrors(MessageTooLargeException.class,1);
+        capture.assertHasNoFrames();
+
+        MessageTooLargeException err = (MessageTooLargeException)capture.getErrors().poll();
+        Assert.assertThat("Error.closeCode",err.getStatusCode(),is(StatusCode.MESSAGE_TOO_LARGE));
+    }
+
+    @Test
+    public void testLongMaskedText() throws Exception
+    {
+        StringBuffer sb = new StringBuffer(); ;
+        for (int i = 0; i < 3500; i++)
+        {
+            sb.append("Hell\uFF4f Big W\uFF4Frld ");
+        }
+        sb.append(". The end.");
+
+        String expectedText = sb.toString();
+        byte utf[] = expectedText.getBytes(StandardCharsets.UTF_8);
+
+        Assert.assertThat("Must be a long length payload",utf.length,greaterThan(0xFFFF));
+
+        ByteBuffer buf = ByteBuffer.allocate(utf.length + 32);
+        buf.put((byte)0x81); // text frame, fin = true
+        buf.put((byte)(0x80 | 0x7F)); // 0x7F == 127 (a 8 byte payload length)
+        buf.putLong(utf.length);
+        MaskedByteBuffer.putMask(buf);
+        MaskedByteBuffer.putPayload(buf,utf);
+        buf.flip();
+
+        WebSocketPolicy policy = WebSocketPolicy.newServerPolicy();
+        policy.setMaxTextMessageSize(100000);
+        Parser parser = new UnitParser(policy);
+        IncomingFramesCapture capture = new IncomingFramesCapture();
+        parser.setIncomingFramesHandler(capture);
+        parser.parse(buf);
+
+        capture.assertNoErrors();
+        capture.assertHasFrame(OpCode.TEXT,1);
+        WebSocketFrame txt = capture.getFrames().poll();
+        Assert.assertThat("TextFrame.data",txt.getPayloadAsUTF8(),is(expectedText));
+    }
+
+    @Test
+    public void testMediumMaskedText() throws Exception
+    {
+        StringBuffer sb = new StringBuffer(); ;
+        for (int i = 0; i < 14; i++)
+        {
+            sb.append("Hell\uFF4f Medium W\uFF4Frld ");
+        }
+        sb.append(". The end.");
+
+        String expectedText = sb.toString();
+        byte utf[] = expectedText.getBytes(StandardCharsets.UTF_8);
+
+        Assert.assertThat("Must be a medium length payload",utf.length,allOf(greaterThan(0x7E),lessThan(0xFFFF)));
+
+        ByteBuffer buf = ByteBuffer.allocate(utf.length + 10);
+        buf.put((byte)0x81);
+        buf.put((byte)(0x80 | 0x7E)); // 0x7E == 126 (a 2 byte payload length)
+        buf.putShort((short)utf.length);
+        MaskedByteBuffer.putMask(buf);
+        MaskedByteBuffer.putPayload(buf,utf);
+        buf.flip();
+
+        WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.SERVER);
+        Parser parser = new UnitParser(policy);
+        IncomingFramesCapture capture = new IncomingFramesCapture();
+        parser.setIncomingFramesHandler(capture);
+        parser.parse(buf);
+
+        capture.assertNoErrors();
+        capture.assertHasFrame(OpCode.TEXT,1);
+        WebSocketFrame txt = capture.getFrames().poll();
+        Assert.assertThat("TextFrame.data",txt.getPayloadAsUTF8(),is(expectedText));
+    }
+
+    @Test
+    public void testShortMaskedFragmentedText() throws Exception
+    {
+        String part1 = "Hello ";
+        String part2 = "World";
+
+        byte b1[] = part1.getBytes(StandardCharsets.UTF_8);
+        byte b2[] = part2.getBytes(StandardCharsets.UTF_8);
+
+        ByteBuffer buf = ByteBuffer.allocate(32);
+
+        // part 1
+        buf.put((byte)0x01); // no fin + text
+        buf.put((byte)(0x80 | b1.length));
+        MaskedByteBuffer.putMask(buf);
+        MaskedByteBuffer.putPayload(buf,b1);
+
+        // part 2
+        buf.put((byte)0x80); // fin + continuation
+        buf.put((byte)(0x80 | b2.length));
+        MaskedByteBuffer.putMask(buf);
+        MaskedByteBuffer.putPayload(buf,b2);
+
+        buf.flip();
+
+        WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.SERVER);
+        Parser parser = new UnitParser(policy);
+        IncomingFramesCapture capture = new IncomingFramesCapture();
+        parser.setIncomingFramesHandler(capture);
+        parser.parse(buf);
+
+        capture.assertNoErrors();
+        capture.assertHasFrame(OpCode.TEXT,1);
+        capture.assertHasFrame(OpCode.CONTINUATION,1);
+        WebSocketFrame txt = capture.getFrames().poll();
+        Assert.assertThat("TextFrame[0].data",txt.getPayloadAsUTF8(),is(part1));
+        txt = capture.getFrames().poll();
+        Assert.assertThat("TextFrame[1].data",txt.getPayloadAsUTF8(),is(part2));
+    }
+
+    @Test
+    public void testShortMaskedText() throws Exception
+    {
+        String expectedText = "Hello World";
+        byte utf[] = expectedText.getBytes(StandardCharsets.UTF_8);
+
+        ByteBuffer buf = ByteBuffer.allocate(24);
+        buf.put((byte)0x81);
+        buf.put((byte)(0x80 | utf.length));
+        MaskedByteBuffer.putMask(buf);
+        MaskedByteBuffer.putPayload(buf,utf);
+        buf.flip();
+
+        WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.SERVER);
+        Parser parser = new UnitParser(policy);
+        IncomingFramesCapture capture = new IncomingFramesCapture();
+        parser.setIncomingFramesHandler(capture);
+        parser.parse(buf);
+
+        capture.assertNoErrors();
+        capture.assertHasFrame(OpCode.TEXT,1);
+        WebSocketFrame txt = capture.getFrames().poll();
+        Assert.assertThat("TextFrame.data",txt.getPayloadAsUTF8(),is(expectedText));
+    }
+
+    @Test
+    public void testShortMaskedUtf8Text() throws Exception
+    {
+        String expectedText = "Hell\uFF4f W\uFF4Frld";
+
+        byte utf[] = expectedText.getBytes(StandardCharsets.UTF_8);
+
+        ByteBuffer buf = ByteBuffer.allocate(24);
+        buf.put((byte)0x81);
+        buf.put((byte)(0x80 | utf.length));
+        MaskedByteBuffer.putMask(buf);
+        MaskedByteBuffer.putPayload(buf,utf);
+        buf.flip();
+
+        WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.SERVER);
+        Parser parser = new UnitParser(policy);
+        IncomingFramesCapture capture = new IncomingFramesCapture();
+        parser.setIncomingFramesHandler(capture);
+        parser.parse(buf);
+
+        capture.assertNoErrors();
+        capture.assertHasFrame(OpCode.TEXT,1);
+        WebSocketFrame txt = capture.getFrames().poll();
+        Assert.assertThat("TextFrame.data",txt.getPayloadAsUTF8(),is(expectedText));
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/WebSocketFrameTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/WebSocketFrameTest.java
new file mode 100644
index 0000000..0240021
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/WebSocketFrameTest.java
@@ -0,0 +1,140 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common;
+
+import static org.hamcrest.Matchers.*;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.websocket.api.StatusCode;
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.common.frames.CloseFrame;
+import org.eclipse.jetty.websocket.common.frames.PingFrame;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
+import org.eclipse.jetty.websocket.common.test.LeakTrackingBufferPoolRule;
+import org.eclipse.jetty.websocket.common.util.Hex;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class WebSocketFrameTest
+{
+    @Rule
+    public LeakTrackingBufferPoolRule bufferPool = new LeakTrackingBufferPoolRule("WebSocketFrameTest");
+
+    private Generator strictGenerator;
+    private Generator laxGenerator;
+
+    private ByteBuffer generateWholeFrame(Generator generator, Frame frame)
+    {
+        ByteBuffer buf = ByteBuffer.allocate(frame.getPayloadLength() + Generator.MAX_HEADER_LENGTH);
+        generator.generateWholeFrame(frame,buf);
+        BufferUtil.flipToFlush(buf,0);
+        return buf;
+    }
+
+    @Before
+    public void initGenerator()
+    {
+        WebSocketPolicy policy = WebSocketPolicy.newServerPolicy();
+        strictGenerator = new Generator(policy,bufferPool);
+        laxGenerator = new Generator(policy,bufferPool,false);
+    }
+
+    private void assertFrameHex(String message, String expectedHex, ByteBuffer actual)
+    {
+        String actualHex = Hex.asHex(actual);
+        Assert.assertThat("Generated Frame:" + message,actualHex,is(expectedHex));
+    }
+
+    @Test
+    public void testLaxInvalidClose()
+    {
+        WebSocketFrame frame = new CloseFrame().setFin(false);
+        ByteBuffer actual = generateWholeFrame(laxGenerator,frame);
+        String expected = "0800";
+        assertFrameHex("Lax Invalid Close Frame",expected,actual);
+    }
+
+    @Test
+    public void testLaxInvalidPing()
+    {
+        WebSocketFrame frame = new PingFrame().setFin(false);
+        ByteBuffer actual = generateWholeFrame(laxGenerator,frame);
+        String expected = "0900";
+        assertFrameHex("Lax Invalid Ping Frame",expected,actual);
+    }
+
+    @Test
+    public void testStrictValidClose()
+    {
+        CloseInfo close = new CloseInfo(StatusCode.NORMAL);
+        ByteBuffer actual = generateWholeFrame(strictGenerator,close.asFrame());
+        String expected = "880203E8";
+        assertFrameHex("Strict Valid Close Frame",expected,actual);
+    }
+
+    @Test
+    public void testStrictValidPing()
+    {
+        WebSocketFrame frame = new PingFrame();
+        ByteBuffer actual = generateWholeFrame(strictGenerator,frame);
+        String expected = "8900";
+        assertFrameHex("Strict Valid Ping Frame",expected,actual);
+    }
+    
+    @Test
+    public void testRsv1()
+    {
+        TextFrame frame = new TextFrame();
+        frame.setPayload("Hi");
+        frame.setRsv1(true);
+        laxGenerator.setRsv1InUse(true);
+        ByteBuffer actual = generateWholeFrame(laxGenerator,frame);
+        String expected = "C1024869";
+        assertFrameHex("Lax Text Frame with RSV1",expected,actual);
+    }
+    
+    @Test
+    public void testRsv2()
+    {
+        TextFrame frame = new TextFrame();
+        frame.setPayload("Hi");
+        frame.setRsv2(true);
+        laxGenerator.setRsv2InUse(true);
+        ByteBuffer actual = generateWholeFrame(laxGenerator,frame);
+        String expected = "A1024869";
+        assertFrameHex("Lax Text Frame with RSV2",expected,actual);
+    }
+    
+    @Test
+    public void testRsv3()
+    {
+        TextFrame frame = new TextFrame();
+        frame.setPayload("Hi");
+        frame.setRsv3(true);
+        laxGenerator.setRsv3InUse(true);
+        ByteBuffer actual = generateWholeFrame(laxGenerator,frame);
+        String expected = "91024869";
+        assertFrameHex("Lax Text Frame with RSV3",expected,actual);
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/WebSocketRemoteEndpointTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/WebSocketRemoteEndpointTest.java
new file mode 100644
index 0000000..258c2b2
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/WebSocketRemoteEndpointTest.java
@@ -0,0 +1,91 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common;
+
+import static org.hamcrest.Matchers.*;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.websocket.common.io.LocalWebSocketConnection;
+import org.eclipse.jetty.websocket.common.test.LeakTrackingBufferPoolRule;
+import org.eclipse.jetty.websocket.common.test.OutgoingFramesCapture;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+
+public class WebSocketRemoteEndpointTest
+{
+    @Rule
+    public TestName testname = new TestName();
+
+    @Rule
+    public LeakTrackingBufferPoolRule bufferPool = new LeakTrackingBufferPoolRule("WebSocketRemoteEndpoint");
+
+    @Test
+    public void testTextBinaryText() throws IOException
+    {
+        LocalWebSocketConnection conn = new LocalWebSocketConnection(testname,bufferPool);
+        OutgoingFramesCapture outgoing = new OutgoingFramesCapture();
+        WebSocketRemoteEndpoint remote = new WebSocketRemoteEndpoint(conn,outgoing);
+        conn.connect();
+        conn.open();
+
+        // Start text message
+        remote.sendPartialString("Hello ",false);
+
+        try
+        {
+            // Attempt to start Binary Message
+            ByteBuffer bytes = ByteBuffer.wrap(new byte[]
+                    { 0, 1, 2 });
+            remote.sendPartialBytes(bytes,false);
+            Assert.fail("Expected " + IllegalStateException.class.getName());
+        }
+        catch (IllegalStateException e)
+        {
+            // Expected path
+            Assert.assertThat("Exception",e.getMessage(),containsString("Cannot send"));
+        }
+
+        // End text message
+        remote.sendPartialString("World!",true);
+    }
+
+    @Test
+    public void testTextPingText() throws IOException
+    {
+        LocalWebSocketConnection conn = new LocalWebSocketConnection(testname,bufferPool);
+        OutgoingFramesCapture outgoing = new OutgoingFramesCapture();
+        WebSocketRemoteEndpoint remote = new WebSocketRemoteEndpoint(conn,outgoing);
+        conn.connect();
+        conn.open();
+
+        // Start text message
+        remote.sendPartialString("Hello ",false);
+
+        // Attempt to send Ping Message
+        remote.sendPing(ByteBuffer.wrap(new byte[]
+                { 0 }));
+
+        // End text message
+        remote.sendPartialString("World!",true);
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/ab/TestABCase1_1.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/ab/TestABCase1_1.java
new file mode 100644
index 0000000..580fd25
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/ab/TestABCase1_1.java
@@ -0,0 +1,517 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.ab;
+
+import static org.hamcrest.Matchers.*;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+
+import org.eclipse.jetty.websocket.api.WebSocketBehavior;
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.common.OpCode;
+import org.eclipse.jetty.websocket.common.Parser;
+import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
+import org.eclipse.jetty.websocket.common.test.ByteBufferAssert;
+import org.eclipse.jetty.websocket.common.test.IncomingFramesCapture;
+import org.eclipse.jetty.websocket.common.test.UnitGenerator;
+import org.eclipse.jetty.websocket.common.test.UnitParser;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Text Message Spec testing the {@link Generator} and {@link Parser}
+ */
+public class TestABCase1_1
+{
+    private WebSocketPolicy policy = WebSocketPolicy.newClientPolicy();
+
+    @Test
+    public void testGenerate125ByteTextCase1_1_2()
+    {
+        int length = 125;
+        byte buf[] = new byte[length];
+        Arrays.fill(buf,(byte)'*');
+        String text = new String(buf,StandardCharsets.UTF_8);
+
+        Frame textFrame = new TextFrame().setPayload(text);
+
+        ByteBuffer actual = UnitGenerator.generate(textFrame);
+
+        ByteBuffer expected = ByteBuffer.allocate(length + 5);
+
+        expected.put(new byte[]
+        { (byte)0x81 });
+
+        byte b = 0x00; // no masking
+        b |= length & 0x7F;
+        expected.put(b);
+
+        for (int i = 0; i < length; ++i)
+        {
+            expected.put("*".getBytes());
+        }
+
+        expected.flip();
+
+        ByteBufferAssert.assertEquals("buffers do not match",expected,actual);
+    }
+
+    @Test
+    public void testGenerate126ByteTextCase1_1_3()
+    {
+        int length = 126;
+
+        StringBuilder builder = new StringBuilder();
+
+        for (int i = 0; i < length; ++i)
+        {
+            builder.append("*");
+        }
+
+        WebSocketFrame textFrame = new TextFrame().setPayload(builder.toString());
+
+        ByteBuffer actual = UnitGenerator.generate(textFrame);
+
+        ByteBuffer expected = ByteBuffer.allocate(length + 5);
+
+        expected.put(new byte[]
+        { (byte)0x81 });
+
+        byte b = 0x00; // no masking
+        b |= length & 0x7E;
+        expected.put(b);
+
+        // expected.put((byte)((length>>8) & 0xFF));
+        // expected.put((byte)(length & 0xFF));
+        expected.putShort((short)length);
+
+        for (int i = 0; i < length; ++i)
+        {
+            expected.put("*".getBytes());
+        }
+
+        expected.flip();
+
+        ByteBufferAssert.assertEquals("buffers do not match",expected,actual);
+    }
+
+    @Test
+    public void testGenerate127ByteTextCase1_1_4()
+    {
+        int length = 127;
+
+        StringBuilder builder = new StringBuilder();
+
+        for (int i = 0; i < length; ++i)
+        {
+            builder.append("*");
+        }
+
+        WebSocketFrame textFrame = new TextFrame().setPayload(builder.toString());
+
+        ByteBuffer actual = UnitGenerator.generate(textFrame);
+
+        ByteBuffer expected = ByteBuffer.allocate(length + 5);
+
+        expected.put(new byte[]
+        { (byte)0x81 });
+
+        byte b = 0x00; // no masking
+        b |= length & 0x7E;
+        expected.put(b);
+
+        // expected.put((byte)((length>>8) & 0xFF));
+        // expected.put((byte)(length & 0xFF));
+        expected.putShort((short)length);
+
+        for (int i = 0; i < length; ++i)
+        {
+            expected.put("*".getBytes());
+        }
+
+        expected.flip();
+
+        ByteBufferAssert.assertEquals("buffers do not match",expected,actual);
+    }
+
+    @Test
+    public void testGenerate128ByteTextCase1_1_5()
+    {
+        int length = 128;
+
+        StringBuilder builder = new StringBuilder();
+
+        for (int i = 0; i < length; ++i)
+        {
+            builder.append("*");
+        }
+
+        WebSocketFrame textFrame = new TextFrame().setPayload(builder.toString());
+
+        ByteBuffer actual = UnitGenerator.generate(textFrame);
+
+        ByteBuffer expected = ByteBuffer.allocate(length + 5);
+
+        expected.put(new byte[]
+        { (byte)0x81 });
+
+        byte b = 0x00; // no masking
+        b |= 0x7E;
+        expected.put(b);
+
+        expected.put((byte)(length >> 8));
+        expected.put((byte)(length & 0xFF));
+        // expected.putShort((short)length);
+
+        for (int i = 0; i < length; ++i)
+        {
+            expected.put("*".getBytes());
+        }
+
+        expected.flip();
+
+        ByteBufferAssert.assertEquals("buffers do not match",expected,actual);
+    }
+
+    @Test
+    public void testGenerate65535ByteTextCase1_1_6()
+    {
+        int length = 65535;
+
+        StringBuilder builder = new StringBuilder();
+
+        for (int i = 0; i < length; ++i)
+        {
+            builder.append("*");
+        }
+
+        WebSocketFrame textFrame = new TextFrame().setPayload(builder.toString());
+
+        ByteBuffer actual = UnitGenerator.generate(textFrame);
+
+        ByteBuffer expected = ByteBuffer.allocate(length + 5);
+
+        expected.put(new byte[]
+        { (byte)0x81 });
+
+        byte b = 0x00; // no masking
+        b |= 0x7E;
+        expected.put(b);
+        expected.put(new byte[]
+        { (byte)0xff, (byte)0xff });
+
+        for (int i = 0; i < length; ++i)
+        {
+            expected.put("*".getBytes());
+        }
+
+        expected.flip();
+
+        ByteBufferAssert.assertEquals("buffers do not match",expected,actual);
+    }
+
+    @Test
+    public void testGenerate65536ByteTextCase1_1_7()
+    {
+        int length = 65536;
+
+        StringBuilder builder = new StringBuilder();
+
+        for (int i = 0; i < length; ++i)
+        {
+            builder.append("*");
+        }
+
+        WebSocketFrame textFrame = new TextFrame().setPayload(builder.toString());
+
+        ByteBuffer actual = UnitGenerator.generate(textFrame);
+
+        ByteBuffer expected = ByteBuffer.allocate(length + 11);
+
+        expected.put(new byte[]
+        { (byte)0x81 });
+
+        byte b = 0x00; // no masking
+        b |= 0x7F;
+        expected.put(b);
+        expected.put(new byte[]
+        { 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00 });
+
+        for (int i = 0; i < length; ++i)
+        {
+            expected.put("*".getBytes());
+        }
+
+        expected.flip();
+
+        ByteBufferAssert.assertEquals("buffers do not match",expected,actual);
+    }
+
+    @Test
+    public void testGenerateEmptyTextCase1_1_1()
+    {
+        WebSocketFrame textFrame = new TextFrame().setPayload("");
+
+        ByteBuffer actual = UnitGenerator.generate(textFrame);
+
+        ByteBuffer expected = ByteBuffer.allocate(5);
+
+        expected.put(new byte[]
+        { (byte)0x81, (byte)0x00 });
+
+        expected.flip();
+
+        ByteBufferAssert.assertEquals("buffers do not match",expected,actual);
+    }
+
+    @Test
+    public void testParse125ByteTextCase1_1_2()
+    {
+        int length = 125;
+
+        ByteBuffer expected = ByteBuffer.allocate(length + 5);
+
+        expected.put(new byte[]
+        { (byte)0x81 });
+        byte b = 0x00; // no masking
+        b |= length & 0x7F;
+        expected.put(b);
+
+        for (int i = 0; i < length; ++i)
+        {
+            expected.put("*".getBytes());
+        }
+
+        expected.flip();
+
+        Parser parser = new UnitParser(policy);
+        IncomingFramesCapture capture = new IncomingFramesCapture();
+        parser.setIncomingFramesHandler(capture);
+        parser.parse(expected);
+
+        capture.assertNoErrors();
+        capture.assertHasFrame(OpCode.TEXT,1);
+
+        Frame pActual = capture.getFrames().poll();
+        Assert.assertThat("TextFrame.payloadLength",pActual.getPayloadLength(),is(length));
+        // Assert.assertEquals("TextFrame.payload",length,pActual.getPayloadData().length);
+    }
+
+    @Test
+    public void testParse126ByteTextCase1_1_3()
+    {
+        int length = 126;
+
+        ByteBuffer expected = ByteBuffer.allocate(length + 5);
+
+        expected.put(new byte[]
+        { (byte)0x81 });
+        byte b = 0x00; // no masking
+        b |= length & 0x7E;
+        expected.put(b);
+        expected.putShort((short)length);
+
+        for (int i = 0; i < length; ++i)
+        {
+            expected.put("*".getBytes());
+        }
+
+        expected.flip();
+
+        Parser parser = new UnitParser(policy);
+        IncomingFramesCapture capture = new IncomingFramesCapture();
+        parser.setIncomingFramesHandler(capture);
+        parser.parse(expected);
+
+        capture.assertNoErrors();
+        capture.assertHasFrame(OpCode.TEXT,1);
+
+        Frame pActual = capture.getFrames().poll();
+        Assert.assertThat("TextFrame.payloadLength",pActual.getPayloadLength(),is(length));
+        // Assert.assertEquals("TextFrame.payload",length,pActual.getPayloadData().length);
+    }
+
+    @Test
+    public void testParse127ByteTextCase1_1_4()
+    {
+        int length = 127;
+
+        ByteBuffer expected = ByteBuffer.allocate(length + 5);
+
+        expected.put(new byte[]
+        { (byte)0x81 });
+        byte b = 0x00; // no masking
+        b |= length & 0x7E;
+        expected.put(b);
+        expected.putShort((short)length);
+
+        for (int i = 0; i < length; ++i)
+        {
+            expected.put("*".getBytes());
+        }
+
+        expected.flip();
+
+        Parser parser = new UnitParser(policy);
+        IncomingFramesCapture capture = new IncomingFramesCapture();
+        parser.setIncomingFramesHandler(capture);
+        parser.parse(expected);
+
+        capture.assertNoErrors();
+        capture.assertHasFrame(OpCode.TEXT,1);
+
+        Frame pActual = capture.getFrames().poll();
+        Assert.assertThat("TextFrame.payloadLength",pActual.getPayloadLength(),is(length));
+        // Assert.assertEquals("TextFrame.payload",length,pActual.getPayloadData().length);
+    }
+
+    @Test
+    public void testParse128ByteTextCase1_1_5()
+    {
+        int length = 128;
+
+        ByteBuffer expected = ByteBuffer.allocate(length + 5);
+
+        expected.put(new byte[]
+        { (byte)0x81 });
+        byte b = 0x00; // no masking
+        b |= 0x7E;
+        expected.put(b);
+        expected.putShort((short)length);
+
+        for (int i = 0; i < length; ++i)
+        {
+            expected.put("*".getBytes());
+        }
+
+        expected.flip();
+
+        Parser parser = new UnitParser(policy);
+        IncomingFramesCapture capture = new IncomingFramesCapture();
+        parser.setIncomingFramesHandler(capture);
+        parser.parse(expected);
+
+        capture.assertNoErrors();
+        capture.assertHasFrame(OpCode.TEXT,1);
+
+        Frame pActual = capture.getFrames().poll();
+        Assert.assertThat("TextFrame.payloadLength",pActual.getPayloadLength(),is(length));
+        // .assertEquals("TextFrame.payload",length,pActual.getPayloadData().length);
+    }
+
+    @Test
+    public void testParse65535ByteTextCase1_1_6()
+    {
+        int length = 65535;
+
+        ByteBuffer expected = ByteBuffer.allocate(length + 5);
+
+        expected.put(new byte[]
+        { (byte)0x81 });
+        byte b = 0x00; // no masking
+        b |= 0x7E;
+        expected.put(b);
+        expected.put(new byte[]
+        { (byte)0xff, (byte)0xff });
+
+        for (int i = 0; i < length; ++i)
+        {
+            expected.put("*".getBytes());
+        }
+
+        expected.flip();
+        WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.CLIENT);
+        policy.setMaxTextMessageSize(length);
+        Parser parser = new UnitParser(policy);
+        IncomingFramesCapture capture = new IncomingFramesCapture();
+        parser.setIncomingFramesHandler(capture);
+        parser.parse(expected);
+
+        capture.assertNoErrors();
+        capture.assertHasFrame(OpCode.TEXT,1);
+
+        Frame pActual = capture.getFrames().poll();
+        Assert.assertThat("TextFrame.payloadLength",pActual.getPayloadLength(),is(length));
+        // Assert.assertEquals("TextFrame.payload",length,pActual.getPayloadData().length);
+    }
+
+    @Test
+    public void testParse65536ByteTextCase1_1_7()
+    {
+        int length = 65536;
+
+        ByteBuffer expected = ByteBuffer.allocate(length + 11);
+
+        expected.put(new byte[]
+        { (byte)0x81 });
+        byte b = 0x00; // no masking
+        b |= 0x7F;
+        expected.put(b);
+        expected.put(new byte[]
+        { 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00 });
+
+        for (int i = 0; i < length; ++i)
+        {
+            expected.put("*".getBytes());
+        }
+
+        expected.flip();
+
+        WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.CLIENT);
+        policy.setMaxTextMessageSize(length);
+        Parser parser = new UnitParser(policy);
+        IncomingFramesCapture capture = new IncomingFramesCapture();
+        parser.setIncomingFramesHandler(capture);
+        parser.parse(expected);
+
+        capture.assertNoErrors();
+        capture.assertHasFrame(OpCode.TEXT,1);
+
+        Frame pActual = capture.getFrames().poll();
+        Assert.assertThat("TextFrame.payloadLength",pActual.getPayloadLength(),is(length));
+        // Assert.assertEquals("TextFrame.payload",length,pActual.getPayloadData().length);
+    }
+
+    @Test
+    public void testParseEmptyTextCase1_1_1()
+    {
+
+        ByteBuffer expected = ByteBuffer.allocate(5);
+
+        expected.put(new byte[]
+        { (byte)0x81, (byte)0x00 });
+
+        expected.flip();
+
+        Parser parser = new UnitParser(policy);
+        IncomingFramesCapture capture = new IncomingFramesCapture();
+        parser.setIncomingFramesHandler(capture);
+        parser.parse(expected);
+
+        capture.assertNoErrors();
+        capture.assertHasFrame(OpCode.TEXT,1);
+
+        Frame pActual = capture.getFrames().poll();
+        Assert.assertThat("TextFrame.payloadLength",pActual.getPayloadLength(),is(0));
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/ab/TestABCase1_2.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/ab/TestABCase1_2.java
new file mode 100644
index 0000000..9440d8d
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/ab/TestABCase1_2.java
@@ -0,0 +1,536 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.ab;
+
+import static org.hamcrest.Matchers.*;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.websocket.api.WebSocketBehavior;
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.common.OpCode;
+import org.eclipse.jetty.websocket.common.Parser;
+import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.frames.BinaryFrame;
+import org.eclipse.jetty.websocket.common.test.ByteBufferAssert;
+import org.eclipse.jetty.websocket.common.test.IncomingFramesCapture;
+import org.eclipse.jetty.websocket.common.test.UnitGenerator;
+import org.eclipse.jetty.websocket.common.test.UnitParser;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Binary Message Spec testing the {@link Generator} and {@link Parser}
+ */
+public class TestABCase1_2
+{
+    private WebSocketPolicy policy = WebSocketPolicy.newClientPolicy();
+
+    @Test
+    public void testGenerate125ByteBinaryCase1_2_2()
+    {
+        int length = 125;
+
+        ByteBuffer bb = ByteBuffer.allocate(length);
+
+        for ( int i = 0 ; i < length ; ++i)
+        {
+            bb.put("*".getBytes());
+        }
+
+        bb.flip();
+
+        WebSocketFrame binaryFrame = new BinaryFrame().setPayload(bb);
+
+        ByteBuffer actual = UnitGenerator.generate(binaryFrame);
+
+
+        ByteBuffer expected = ByteBuffer.allocate(length + 5);
+
+        expected.put(new byte[]
+                { (byte)0x82 });
+
+        byte b = 0x00; // no masking
+        b |= length & 0x7F;
+        expected.put(b);
+
+        for ( int i = 0 ; i < length ; ++i )
+        {
+            expected.put("*".getBytes());
+        }
+
+        BufferUtil.flipToFlush(expected,0);
+
+        ByteBufferAssert.assertEquals("buffers do not match",expected,actual);
+    }
+
+    @Test
+    public void testGenerate126ByteBinaryCase1_2_3()
+    {
+        int length = 126;
+
+        ByteBuffer bb = ByteBuffer.allocate(length);
+
+        for ( int i = 0 ; i < length ; ++i)
+        {
+            bb.put("*".getBytes());
+        }
+
+        bb.flip();
+
+        WebSocketFrame binaryFrame = new BinaryFrame().setPayload(bb);
+
+        ByteBuffer actual = UnitGenerator.generate(binaryFrame);
+
+        ByteBuffer expected = ByteBuffer.allocate(length + 5);
+
+        expected.put(new byte[]
+                { (byte)0x82 });
+
+        byte b = 0x00; // no masking
+        b |= length & 0x7E;
+        expected.put(b);
+
+        //expected.put((byte)((length>>8) & 0xFF));
+        //expected.put((byte)(length & 0xFF));
+        expected.putShort((short)length);
+
+        for ( int i = 0 ; i < length ; ++i )
+        {
+            expected.put("*".getBytes());
+        }
+
+        BufferUtil.flipToFlush(expected,0);
+
+        ByteBufferAssert.assertEquals("buffers do not match",expected,actual);
+    }
+
+    @Test
+    public void testGenerate127ByteBinaryCase1_2_4()
+    {
+        int length = 127;
+
+        ByteBuffer bb = ByteBuffer.allocate(length);
+
+        for ( int i = 0 ; i < length ; ++i)
+        {
+            bb.put("*".getBytes());
+
+        }
+
+        bb.flip();
+
+        WebSocketFrame binaryFrame = new BinaryFrame().setPayload(bb);
+
+        ByteBuffer actual = UnitGenerator.generate(binaryFrame);
+
+        ByteBuffer expected = ByteBuffer.allocate(length + 5);
+
+        expected.put(new byte[]
+                { (byte)0x82 });
+
+        byte b = 0x00; // no masking
+        b |= length & 0x7E;
+        expected.put(b);
+
+        //expected.put((byte)((length>>8) & 0xFF));
+        //expected.put((byte)(length & 0xFF));
+        expected.putShort((short)length);
+
+        for ( int i = 0 ; i < length ; ++i )
+        {
+            expected.put("*".getBytes());
+        }
+
+        BufferUtil.flipToFlush(expected,0);
+
+        ByteBufferAssert.assertEquals("buffers do not match",expected,actual);
+    }
+
+    @Test
+    public void testGenerate128ByteBinaryCase1_2_5()
+    {
+        int length = 128;
+
+        ByteBuffer bb = ByteBuffer.allocate(length);
+
+        for ( int i = 0 ; i < length ; ++i)
+        {
+            bb.put("*".getBytes());
+
+        }
+
+        bb.flip();
+        WebSocketFrame binaryFrame = new BinaryFrame().setPayload(bb);
+
+        ByteBuffer actual = UnitGenerator.generate(binaryFrame);
+
+        ByteBuffer expected = ByteBuffer.allocate(length + 5);
+
+        expected.put(new byte[]
+                { (byte)0x82 });
+
+        byte b = 0x00; // no masking
+        b |= 0x7E;
+        expected.put(b);
+
+        expected.put((byte)(length>>8));
+        expected.put((byte)(length & 0xFF));
+        //expected.putShort((short)length);
+
+        for ( int i = 0 ; i < length ; ++i )
+        {
+            expected.put("*".getBytes());
+        }
+
+        BufferUtil.flipToFlush(expected,0);
+
+        ByteBufferAssert.assertEquals("buffers do not match",expected,actual);
+
+    }
+
+    @Test
+    public void testGenerate65535ByteBinaryCase1_2_6()
+    {
+        int length = 65535;
+
+        ByteBuffer bb = ByteBuffer.allocate(length);
+
+        for ( int i = 0 ; i < length ; ++i)
+        {
+            bb.put("*".getBytes());
+
+        }
+
+        bb.flip();
+
+        WebSocketFrame binaryFrame = new BinaryFrame().setPayload(bb);
+
+        ByteBuffer actual = UnitGenerator.generate(binaryFrame);
+
+        ByteBuffer expected = ByteBuffer.allocate(length + 5);
+
+        expected.put(new byte[]
+                { (byte)0x82 });
+
+        byte b = 0x00; // no masking
+        b |= 0x7E;
+        expected.put(b);
+        expected.put(new byte[]{ (byte)0xff, (byte)0xff});
+
+        for ( int i = 0 ; i < length ; ++i )
+        {
+            expected.put("*".getBytes());
+        }
+
+        BufferUtil.flipToFlush(expected,0);
+
+        ByteBufferAssert.assertEquals("buffers do not match",expected,actual);
+    }
+
+    @Test
+    public void testGenerate65536ByteBinaryCase1_2_7()
+    {
+        int length = 65536;
+
+        ByteBuffer bb = ByteBuffer.allocate(length);
+
+        for ( int i = 0 ; i < length ; ++i)
+        {
+            bb.put("*".getBytes());
+
+        }
+
+        bb.flip();
+
+        WebSocketFrame binaryFrame = new BinaryFrame().setPayload(bb);
+
+        ByteBuffer actual = UnitGenerator.generate(binaryFrame);
+
+        ByteBuffer expected = ByteBuffer.allocate(length + 11);
+
+        expected.put(new byte[]
+                { (byte)0x82 });
+
+        byte b = 0x00; // no masking
+        b |= 0x7F;
+        expected.put(b);
+        expected.put(new byte[]{0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00});
+
+
+        for ( int i = 0 ; i < length ; ++i )
+        {
+            expected.put("*".getBytes());
+        }
+
+        BufferUtil.flipToFlush(expected,0);
+
+        ByteBufferAssert.assertEquals("buffers do not match",expected,actual);
+    }
+
+    @Test
+    public void testGenerateEmptyBinaryCase1_2_1()
+    {
+        WebSocketFrame binaryFrame = new BinaryFrame().setPayload(new byte[] {});
+
+        ByteBuffer actual = UnitGenerator.generate(binaryFrame);
+
+        ByteBuffer expected = ByteBuffer.allocate(5);
+
+        expected.put(new byte[]
+                { (byte)0x82, (byte)0x00 });
+
+        BufferUtil.flipToFlush(expected,0);
+
+        ByteBufferAssert.assertEquals("buffers do not match",expected,actual);
+    }
+
+    @Test
+    public void testParse125ByteBinaryCase1_2_2()
+    {
+        int length = 125;
+
+        ByteBuffer expected = ByteBuffer.allocate(length + 5);
+
+        expected.put(new byte[]
+                { (byte)0x82 });
+        byte b = 0x00; // no masking
+        b |= length & 0x7F;
+        expected.put(b);
+
+        for ( int i = 0 ; i < length ; ++i )
+        {
+            expected.put("*".getBytes());
+        }
+
+        expected.flip();
+
+        Parser parser = new UnitParser(policy);
+        IncomingFramesCapture capture = new IncomingFramesCapture();
+        parser.setIncomingFramesHandler(capture);
+        parser.parse(expected);
+
+        capture.assertNoErrors();
+        capture.assertHasFrame(OpCode.BINARY,1);
+
+        Frame pActual = capture.getFrames().poll();
+        Assert.assertThat("BinaryFrame.payloadLength",pActual.getPayloadLength(),is(length));
+        // Assert.assertEquals("BinaryFrame.payload",length,pActual.getPayloadData().length);
+    }
+
+    @Test
+    public void testParse126ByteBinaryCase1_2_3()
+    {
+        int length = 126;
+
+        ByteBuffer expected = ByteBuffer.allocate(length + 5);
+
+        expected.put(new byte[]
+                { (byte)0x82 });
+        byte b = 0x00; // no masking
+        b |= length & 0x7E;
+        expected.put(b);
+        expected.putShort((short)length);
+
+        for ( int i = 0 ; i < length ; ++i )
+        {
+            expected.put("*".getBytes());
+        }
+
+        expected.flip();
+
+        Parser parser = new UnitParser(policy);
+        IncomingFramesCapture capture = new IncomingFramesCapture();
+        parser.setIncomingFramesHandler(capture);
+        parser.parse(expected);
+
+        capture.assertNoErrors();
+        capture.assertHasFrame(OpCode.BINARY,1);
+
+        Frame pActual = capture.getFrames().poll();
+        Assert.assertThat("BinaryFrame.payloadLength",pActual.getPayloadLength(),is(length));
+        // Assert.assertEquals("BinaryFrame.payload",length,pActual.getPayloadData().length);
+    }
+
+    @Test
+    public void testParse127ByteBinaryCase1_2_4()
+    {
+        int length = 127;
+
+        ByteBuffer expected = ByteBuffer.allocate(length + 5);
+
+        expected.put(new byte[]
+                { (byte)0x82 });
+        byte b = 0x00; // no masking
+        b |= length & 0x7E;
+        expected.put(b);
+        expected.putShort((short)length);
+
+        for ( int i = 0 ; i < length ; ++i )
+        {
+            expected.put("*".getBytes());
+        }
+
+        expected.flip();
+
+        Parser parser = new UnitParser(policy);
+        IncomingFramesCapture capture = new IncomingFramesCapture();
+        parser.setIncomingFramesHandler(capture);
+        parser.parse(expected);
+
+        capture.assertNoErrors();
+        capture.assertHasFrame(OpCode.BINARY,1);
+
+        Frame pActual = capture.getFrames().poll();
+        Assert.assertThat("BinaryFrame.payloadLength",pActual.getPayloadLength(),is(length));
+        // .assertEquals("BinaryFrame.payload",length,pActual.getPayloadData().length);
+    }
+
+    @Test
+    public void testParse128ByteBinaryCase1_2_5()
+    {
+        int length = 128;
+
+        ByteBuffer expected = ByteBuffer.allocate(length + 5);
+
+        expected.put(new byte[]
+                { (byte)0x82 });
+        byte b = 0x00; // no masking
+        b |= 0x7E;
+        expected.put(b);
+        expected.putShort((short)length);
+
+        for ( int i = 0 ; i < length ; ++i )
+        {
+            expected.put("*".getBytes());
+        }
+
+        expected.flip();
+
+        Parser parser = new UnitParser(policy);
+        IncomingFramesCapture capture = new IncomingFramesCapture();
+        parser.setIncomingFramesHandler(capture);
+        parser.parse(expected);
+
+        capture.assertNoErrors();
+        capture.assertHasFrame(OpCode.BINARY,1);
+
+        Frame pActual = capture.getFrames().poll();
+        Assert.assertThat("BinaryFrame.payloadLength",pActual.getPayloadLength(),is(length));
+        // Assert.assertEquals("BinaryFrame.payload",length,pActual.getPayloadData().length);
+    }
+
+    @Test
+    public void testParse65535ByteBinaryCase1_2_6()
+    {
+        int length = 65535;
+
+        ByteBuffer expected = ByteBuffer.allocate(length + 5);
+
+        expected.put(new byte[]
+                { (byte)0x82 });
+        byte b = 0x00; // no masking
+        b |= 0x7E;
+        expected.put(b);
+        expected.put(new byte[]{ (byte)0xff, (byte)0xff});
+
+        for ( int i = 0 ; i < length ; ++i )
+        {
+            expected.put("*".getBytes());
+        }
+
+        expected.flip();
+        WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.CLIENT);
+        policy.setMaxBinaryMessageSize(length);
+        Parser parser = new UnitParser(policy);
+        IncomingFramesCapture capture = new IncomingFramesCapture();
+        parser.setIncomingFramesHandler(capture);
+        parser.parse(expected);
+
+        capture.assertNoErrors();
+        capture.assertHasFrame(OpCode.BINARY,1);
+
+        Frame pActual = capture.getFrames().poll();
+        Assert.assertThat("BinaryFrame.payloadLength",pActual.getPayloadLength(),is(length));
+        // Assert.assertEquals("BinaryFrame.payload",length,pActual.getPayloadData().length);
+    }
+
+
+    @Test
+    public void testParse65536ByteBinaryCase1_2_7()
+    {
+        int length = 65536;
+
+        ByteBuffer expected = ByteBuffer.allocate(length + 11);
+
+        expected.put(new byte[]
+                { (byte)0x82 });
+        byte b = 0x00; // no masking
+        b |= 0x7F;
+        expected.put(b);
+        expected.put(new byte[]{0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00});
+
+        for ( int i = 0 ; i < length ; ++i )
+        {
+            expected.put("*".getBytes());
+        }
+
+        expected.flip();
+
+        WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.CLIENT);
+        policy.setMaxBinaryMessageSize(length);
+        Parser parser = new UnitParser(policy);
+        IncomingFramesCapture capture = new IncomingFramesCapture();
+        parser.setIncomingFramesHandler(capture);
+        parser.parse(expected);
+
+        capture.assertNoErrors();
+        capture.assertHasFrame(OpCode.BINARY,1);
+
+        Frame pActual = capture.getFrames().poll();
+        Assert.assertThat("BinaryFrame.payloadLength",pActual.getPayloadLength(),is(length));
+        // Assert.assertEquals("BinaryFrame.payload",length,pActual.getPayloadData().length);
+    }
+
+    @Test
+    public void testParseEmptyBinaryCase1_2_1()
+    {
+
+        ByteBuffer expected = ByteBuffer.allocate(5);
+
+        expected.put(new byte[]
+                { (byte)0x82, (byte)0x00 });
+
+        expected.flip();
+
+        Parser parser = new UnitParser(policy);
+        IncomingFramesCapture capture = new IncomingFramesCapture();
+        parser.setIncomingFramesHandler(capture);
+        parser.parse(expected);
+
+        capture.assertNoErrors();
+        capture.assertHasFrame(OpCode.BINARY,1);
+
+        Frame pActual = capture.getFrames().poll();
+        Assert.assertThat("BinaryFrame.payloadLength",pActual.getPayloadLength(),is(0));
+        // Assert.assertNull("BinaryFrame.payload",pActual.getPayloadData());
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/ab/TestABCase2.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/ab/TestABCase2.java
new file mode 100644
index 0000000..2bfdc66
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/ab/TestABCase2.java
@@ -0,0 +1,323 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.ab;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.websocket.api.ProtocolException;
+import org.eclipse.jetty.websocket.api.WebSocketBehavior;
+import org.eclipse.jetty.websocket.api.WebSocketException;
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.common.Generator;
+import org.eclipse.jetty.websocket.common.OpCode;
+import org.eclipse.jetty.websocket.common.Parser;
+import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.frames.PingFrame;
+import org.eclipse.jetty.websocket.common.test.ByteBufferAssert;
+import org.eclipse.jetty.websocket.common.test.IncomingFramesCapture;
+import org.eclipse.jetty.websocket.common.test.UnitGenerator;
+import org.eclipse.jetty.websocket.common.test.UnitParser;
+import org.junit.Assert;
+import org.junit.Test;
+
+import static org.hamcrest.Matchers.is;
+
+public class TestABCase2
+{
+    WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.CLIENT);
+
+    @Test
+    public void testGenerate125OctetPingCase2_4()
+    {
+        byte[] bytes = new byte[125];
+
+        for ( int i = 0 ; i < bytes.length ; ++i )
+        {
+            bytes[i] = Integer.valueOf(Integer.toOctalString(i)).byteValue();
+        }
+
+        WebSocketFrame pingFrame = new PingFrame().setPayload(bytes);
+
+        ByteBuffer actual = UnitGenerator.generate(pingFrame);
+
+        ByteBuffer expected = ByteBuffer.allocate(bytes.length + 32);
+
+        expected.put(new byte[]
+                { (byte)0x89 });
+
+        byte b = 0x00; // no masking
+        b |= bytes.length & 0x7F;
+        expected.put(b);
+        expected.put(bytes);
+
+        expected.flip();
+
+        ByteBufferAssert.assertEquals("buffers do not match",expected,actual);
+    }
+
+    @Test
+    public void testGenerateBinaryPingCase2_3()
+    {
+        byte[] bytes = new byte[] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 };
+
+        PingFrame pingFrame = new PingFrame().setPayload(bytes);
+
+        ByteBuffer actual = UnitGenerator.generate(pingFrame);
+
+        ByteBuffer expected = ByteBuffer.allocate(32);
+
+        expected.put(new byte[]
+                { (byte)0x89 });
+
+        byte b = 0x00; // no masking
+        b |= bytes.length & 0x7F;
+        expected.put(b);
+        expected.put(bytes);
+
+        expected.flip();
+
+        ByteBufferAssert.assertEquals("buffers do not match",expected,actual);
+    }
+
+
+    @Test
+    public void testGenerateEmptyPingCase2_1()
+    {
+        WebSocketFrame pingFrame = new PingFrame();
+
+        ByteBuffer actual = UnitGenerator.generate(pingFrame);
+
+        ByteBuffer expected = ByteBuffer.allocate(5);
+
+        expected.put(new byte[]
+                { (byte)0x89, (byte)0x00 });
+
+        expected.flip();
+
+        ByteBufferAssert.assertEquals("buffers do not match",expected,actual);
+    }
+
+    @Test
+    public void testGenerateHelloPingCase2_2()
+    {
+        String message = "Hello, world!";
+        byte[] messageBytes = StringUtil.getUtf8Bytes(message);
+
+        PingFrame pingFrame = new PingFrame().setPayload(messageBytes);
+
+        ByteBuffer actual = UnitGenerator.generate(pingFrame);
+
+        ByteBuffer expected = ByteBuffer.allocate(32);
+
+        expected.put(new byte[]
+                { (byte)0x89 });
+
+        byte b = 0x00; // no masking
+        b |= messageBytes.length & 0x7F;
+        expected.put(b);
+        expected.put(messageBytes);
+
+        expected.flip();
+
+        ByteBufferAssert.assertEquals("buffers do not match",expected,actual);
+    }
+
+    @Test( expected=WebSocketException.class )
+    public void testGenerateOversizedBinaryPingCase2_5_A()
+    {
+        byte[] bytes = new byte[126];
+        Arrays.fill(bytes,(byte)0x00);
+
+        PingFrame pingFrame = new PingFrame();
+        pingFrame.setPayload(ByteBuffer.wrap(bytes)); // should throw exception
+    }
+
+    @Test( expected=WebSocketException.class )
+    public void testGenerateOversizedBinaryPingCase2_5_B()
+    {
+        byte[] bytes = new byte[126];
+        Arrays.fill(bytes, (byte)0x00);
+
+        PingFrame pingFrame = new PingFrame();
+        pingFrame.setPayload(ByteBuffer.wrap(bytes)); // should throw exception
+
+        // FIXME: Remove? UnitGenerator.generate(pingFrame);
+    }
+
+    @Test
+    public void testParse125OctetPingCase2_4()
+    {
+        byte[] bytes = new byte[125];
+
+        for ( int i = 0 ; i < bytes.length ; ++i )
+        {
+            bytes[i] = Integer.valueOf(Integer.toOctalString(i)).byteValue();
+        }
+
+        ByteBuffer expected = ByteBuffer.allocate(bytes.length + 32);
+
+        expected.put(new byte[]
+                { (byte)0x89 });
+
+        byte b = 0x00; // no masking
+        b |= bytes.length & 0x7F;
+        expected.put(b);
+        expected.put(bytes);
+
+        expected.flip();
+
+        Parser parser = new UnitParser(policy);
+        IncomingFramesCapture capture = new IncomingFramesCapture();
+        parser.setIncomingFramesHandler(capture);
+        parser.parse(expected);
+
+        capture.assertNoErrors();
+        capture.assertHasFrame(OpCode.PING,1);
+
+        Frame pActual = capture.getFrames().poll();
+        Assert.assertThat("PingFrame.payloadLength",pActual.getPayloadLength(),is(bytes.length));
+        Assert.assertEquals("PingFrame.payload",bytes.length,pActual.getPayloadLength());
+    }
+
+    @Test
+    public void testParseBinaryPingCase2_3()
+    {
+        byte[] bytes = new byte[] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 };
+
+        ByteBuffer expected = ByteBuffer.allocate(32);
+
+        expected.put(new byte[]
+                { (byte)0x89 });
+
+        byte b = 0x00; // no masking
+        b |= bytes.length & 0x7F;
+        expected.put(b);
+        expected.put(bytes);
+
+        expected.flip();
+
+        Parser parser = new UnitParser(policy);
+        IncomingFramesCapture capture = new IncomingFramesCapture();
+        parser.setIncomingFramesHandler(capture);
+        parser.parse(expected);
+
+        capture.assertNoErrors();
+        capture.assertHasFrame(OpCode.PING,1);
+
+        Frame pActual = capture.getFrames().poll();
+        Assert.assertThat("PingFrame.payloadLength",pActual.getPayloadLength(),is(bytes.length));
+        Assert.assertEquals("PingFrame.payload",bytes.length,pActual.getPayloadLength());
+    }
+
+    @Test
+    public void testParseEmptyPingCase2_1()
+    {
+        ByteBuffer expected = ByteBuffer.allocate(5);
+
+        expected.put(new byte[]
+                { (byte)0x89, (byte)0x00 });
+
+        expected.flip();
+
+        Parser parser = new UnitParser(policy);
+        IncomingFramesCapture capture = new IncomingFramesCapture();
+        parser.setIncomingFramesHandler(capture);
+        parser.parse(expected);
+
+        capture.assertNoErrors();
+        capture.assertHasFrame(OpCode.PING,1);
+
+        Frame pActual = capture.getFrames().poll();
+        Assert.assertThat("PingFrame.payloadLength",pActual.getPayloadLength(),is(0));
+        Assert.assertEquals("PingFrame.payload",0,pActual.getPayloadLength());
+    }
+
+    @Test
+    public void testParseHelloPingCase2_2()
+    {
+        String message = "Hello, world!";
+        byte[] messageBytes = message.getBytes();
+
+        ByteBuffer expected = ByteBuffer.allocate(32);
+
+        expected.put(new byte[]
+                { (byte)0x89 });
+
+        byte b = 0x00; // no masking
+        b |= messageBytes.length & 0x7F;
+        expected.put(b);
+        expected.put(messageBytes);
+
+        expected.flip();
+
+        Parser parser = new UnitParser(policy);
+        IncomingFramesCapture capture = new IncomingFramesCapture();
+        parser.setIncomingFramesHandler(capture);
+        parser.parse(expected);
+
+        capture.assertNoErrors();
+        capture.assertHasFrame(OpCode.PING,1);
+
+        Frame pActual = capture.getFrames().poll();
+        Assert.assertThat("PingFrame.payloadLength",pActual.getPayloadLength(),is(message.length()));
+        Assert.assertEquals("PingFrame.payload",message.length(),pActual.getPayloadLength());
+    }
+
+    @Test
+    public void testParseOversizedBinaryPingCase2_5()
+    {
+        byte[] bytes = new byte[126];
+        Arrays.fill(bytes,(byte)0x00);
+
+        ByteBuffer expected = ByteBuffer.allocate(bytes.length + Generator.MAX_HEADER_LENGTH);
+
+        byte b;
+
+        // fin + op
+        b = 0x00;
+        b |= 0x80; // fin on
+        b |= 0x09; // ping
+        expected.put(b);
+
+        // mask + len
+        b = 0x00;
+        b |= 0x00; // no masking
+        b |= 0x7E; // 2 byte len
+        expected.put(b);
+
+        // 2 byte len
+        expected.putChar((char)bytes.length);
+
+        // payload
+        expected.put(bytes);
+
+        expected.flip();
+
+        UnitParser parser = new UnitParser(policy);
+        IncomingFramesCapture capture = new IncomingFramesCapture();
+        parser.setIncomingFramesHandler(capture);
+        parser.parseQuietly(expected);
+
+        Assert.assertEquals("error should be returned for too large of ping payload",1,capture.getErrorCount(ProtocolException.class));
+    }
+
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/ab/TestABCase3.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/ab/TestABCase3.java
new file mode 100644
index 0000000..9aad5b8
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/ab/TestABCase3.java
@@ -0,0 +1,92 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.ab;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.eclipse.jetty.toolchain.test.TestTracker;
+import org.eclipse.jetty.websocket.api.ProtocolException;
+import org.eclipse.jetty.websocket.common.CloseInfo;
+import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.frames.PingFrame;
+import org.eclipse.jetty.websocket.common.frames.PongFrame;
+import org.eclipse.jetty.websocket.common.test.UnitGenerator;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Test various invalid frame situations
+ */
+ at RunWith(value = Parameterized.class)
+public class TestABCase3
+{
+    @Parameters
+    public static Collection<WebSocketFrame[]> data()
+    {
+        List<WebSocketFrame[]> data = new ArrayList<>();
+        // @formatter:off
+        data.add(new WebSocketFrame[]
+                { new PingFrame().setFin(false) });
+        data.add(new WebSocketFrame[]
+                { new PingFrame().setRsv1(true) });
+        data.add(new WebSocketFrame[]
+                { new PingFrame().setRsv2(true) });
+        data.add(new WebSocketFrame[]
+                { new PingFrame().setRsv3(true) });
+        data.add(new WebSocketFrame[]
+                { new PongFrame().setFin(false) });
+        data.add(new WebSocketFrame[]
+                { new PingFrame().setRsv1(true) });
+        data.add(new WebSocketFrame[]
+                { new PongFrame().setRsv2(true) });
+        data.add(new WebSocketFrame[]
+                { new PongFrame().setRsv3(true) });
+        data.add(new WebSocketFrame[]
+                { new CloseInfo().asFrame().setFin(false) });
+        data.add(new WebSocketFrame[]
+                { new CloseInfo().asFrame().setRsv1(true) });
+        data.add(new WebSocketFrame[]
+                { new CloseInfo().asFrame().setRsv2(true) });
+        data.add(new WebSocketFrame[]
+                { new CloseInfo().asFrame().setRsv3(true) });
+        // @formatter:on
+        return data;
+    }
+
+    @Rule
+    public TestTracker tt = new TestTracker();
+
+    private WebSocketFrame invalidFrame;
+
+    public TestABCase3(WebSocketFrame invalidFrame)
+    {
+        this.invalidFrame = invalidFrame;
+    }
+
+    @Test(expected = ProtocolException.class)
+    public void testGenerateInvalidControlFrame()
+    {
+        UnitGenerator.generate(invalidFrame);
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/ab/TestABCase4.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/ab/TestABCase4.java
new file mode 100644
index 0000000..f4e92b5
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/ab/TestABCase4.java
@@ -0,0 +1,165 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.ab;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.log.StacklessLogging;
+import org.eclipse.jetty.websocket.api.ProtocolException;
+import org.eclipse.jetty.websocket.api.WebSocketBehavior;
+import org.eclipse.jetty.websocket.api.WebSocketException;
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.common.Parser;
+import org.eclipse.jetty.websocket.common.test.IncomingFramesCapture;
+import org.eclipse.jetty.websocket.common.test.UnitParser;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class TestABCase4
+{
+    private WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.CLIENT);
+
+    @Test
+    public void testParserControlOpCode11Case4_2_1() throws Exception
+    {
+        ByteBuffer expected = ByteBuffer.allocate(32);
+
+        expected.put(new byte[] { (byte)0x8b, 0x00 });
+
+        expected.flip();
+
+        IncomingFramesCapture capture = new IncomingFramesCapture();
+
+        try (StacklessLogging logging = new StacklessLogging(Parser.class))
+        {
+            Parser parser = new UnitParser(policy);
+            parser.setIncomingFramesHandler(capture);
+            try
+            {
+                parser.parse(expected);
+            }
+            catch (ProtocolException ignore)
+            {
+                // ignore
+            }
+        }
+
+        Assert.assertEquals("error on undefined opcode",1,capture.getErrorCount(WebSocketException.class));
+
+        Throwable known = capture.getErrors().poll();
+
+        Assert.assertTrue("undefined option should be in message",known.getMessage().contains("Unknown opcode: 11"));
+    }
+
+    @Test
+    public void testParserControlOpCode12WithPayloadCase4_2_2() throws Exception
+    {
+        ByteBuffer expected = ByteBuffer.allocate(32);
+
+        expected.put(new byte[] { (byte)0x8c, 0x01, 0x00 });
+
+        expected.flip();
+
+        IncomingFramesCapture capture = new IncomingFramesCapture();
+
+        try (StacklessLogging logging = new StacklessLogging(Parser.class))
+        {
+            Parser parser = new UnitParser(policy);
+            parser.setIncomingFramesHandler(capture);
+            try
+            {
+                parser.parse(expected);
+            }
+            catch (ProtocolException ignore)
+            {
+                // ignore
+            }
+        }
+
+        Assert.assertEquals("error on undefined opcode",1,capture.getErrorCount(WebSocketException.class));
+
+        Throwable known = capture.getErrors().poll();
+
+        Assert.assertTrue("undefined option should be in message",known.getMessage().contains("Unknown opcode: 12"));
+    }
+
+    @Test
+    public void testParserNonControlOpCode3Case4_1_1() throws Exception
+    {
+        ByteBuffer expected = ByteBuffer.allocate(32);
+
+        expected.put(new byte[] { (byte)0x83, 0x00 });
+
+        expected.flip();
+
+        IncomingFramesCapture capture = new IncomingFramesCapture();
+
+        try (StacklessLogging logging = new StacklessLogging(Parser.class))
+        {
+            Parser parser = new UnitParser(policy);
+            parser.setIncomingFramesHandler(capture);
+            try
+            {
+                parser.parse(expected);
+            }
+            catch (ProtocolException ignore)
+            {
+                // ignore
+            }
+        }
+
+        Assert.assertEquals("error on undefined opcode",1,capture.getErrorCount(WebSocketException.class));
+
+        Throwable known = capture.getErrors().poll();
+
+        Assert.assertTrue("undefined option should be in message",known.getMessage().contains("Unknown opcode: 3"));
+    }
+
+    @Test
+    public void testParserNonControlOpCode4WithPayloadCase4_1_2() throws Exception
+    {
+        ByteBuffer expected = ByteBuffer.allocate(32);
+
+        expected.put(new byte[] { (byte)0x84, 0x01, 0x00 });
+
+        expected.flip();
+
+        IncomingFramesCapture capture = new IncomingFramesCapture();
+
+        try (StacklessLogging logging = new StacklessLogging(Parser.class))
+        {
+            Parser parser = new UnitParser(policy);
+            parser.setIncomingFramesHandler(capture);
+            try
+            {
+                parser.parse(expected);
+            }
+            catch (ProtocolException ignore)
+            {
+                // ignore
+            }
+        }
+
+        Assert.assertEquals("error on undefined opcode",1,capture.getErrorCount(WebSocketException.class));
+
+        Throwable known = capture.getErrors().poll();
+
+        Assert.assertTrue("undefined option should be in message",known.getMessage().contains("Unknown opcode: 4"));
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/ab/TestABCase7_3.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/ab/TestABCase7_3.java
new file mode 100644
index 0000000..5b8e2f9
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/ab/TestABCase7_3.java
@@ -0,0 +1,349 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.ab;
+
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.is;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.websocket.api.ProtocolException;
+import org.eclipse.jetty.websocket.api.WebSocketBehavior;
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.common.CloseInfo;
+import org.eclipse.jetty.websocket.common.OpCode;
+import org.eclipse.jetty.websocket.common.Parser;
+import org.eclipse.jetty.websocket.common.frames.CloseFrame;
+import org.eclipse.jetty.websocket.common.test.ByteBufferAssert;
+import org.eclipse.jetty.websocket.common.test.IncomingFramesCapture;
+import org.eclipse.jetty.websocket.common.test.UnitGenerator;
+import org.eclipse.jetty.websocket.common.test.UnitParser;
+import org.eclipse.jetty.websocket.common.util.Hex;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class TestABCase7_3
+{
+    WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.CLIENT);
+
+    @Test
+    public void testCase7_3_1GenerateEmptyClose()
+    {
+        CloseInfo close = new CloseInfo();
+
+        ByteBuffer actual = UnitGenerator.generate(close.asFrame());
+
+        ByteBuffer expected = ByteBuffer.allocate(5);
+
+        expected.put(new byte[]
+                { (byte)0x88, (byte)0x00 });
+
+        expected.flip();
+
+        ByteBufferAssert.assertEquals("buffers do not match",expected,actual);
+    }
+
+    @Test
+    public void testCase7_3_1ParseEmptyClose()
+    {
+        ByteBuffer expected = ByteBuffer.allocate(5);
+
+        expected.put(new byte[]
+                { (byte)0x88, (byte)0x00 });
+
+        expected.flip();
+
+        Parser parser = new UnitParser(policy);
+        IncomingFramesCapture capture = new IncomingFramesCapture();
+        parser.setIncomingFramesHandler(capture);
+        parser.parse(expected);
+
+        capture.assertNoErrors();
+        capture.assertHasFrame(OpCode.CLOSE,1);
+
+        Frame pActual = capture.getFrames().poll();
+        Assert.assertThat("CloseFrame.payloadLength",pActual.getPayloadLength(),is(0));
+
+    }
+
+
+    @Test(expected = ProtocolException.class)
+    public void testCase7_3_2Generate1BytePayloadClose()
+    {
+        CloseFrame closeFrame = new CloseFrame();
+        closeFrame.setPayload(Hex.asByteBuffer("00"));
+
+        UnitGenerator.generate(closeFrame);
+    }
+
+    @Test
+    public void testCase7_3_2Parse1BytePayloadClose()
+    {
+        ByteBuffer expected = Hex.asByteBuffer("880100");
+
+        UnitParser parser = new UnitParser(policy);
+        IncomingFramesCapture capture = new IncomingFramesCapture();
+        parser.setIncomingFramesHandler(capture);
+        parser.parseQuietly(expected);
+
+        Assert.assertEquals("error on invalid close payload",1,capture.getErrorCount(ProtocolException.class));
+
+        ProtocolException known = (ProtocolException)capture.getErrors().poll();
+
+        Assert.assertThat("Payload.message",known.getMessage(),containsString("Invalid close frame payload length"));
+    }
+
+    @Test
+    public void testCase7_3_3GenerateCloseWithStatus()
+    {
+        CloseInfo close = new CloseInfo(1000);
+
+        ByteBuffer actual = UnitGenerator.generate(close.asFrame());
+
+        ByteBuffer expected = ByteBuffer.allocate(5);
+
+        expected.put(new byte[]
+                { (byte)0x88, (byte)0x02, 0x03, (byte)0xe8 });
+
+        expected.flip();
+
+        ByteBufferAssert.assertEquals("buffers do not match",expected,actual);
+    }
+
+    @Test
+    public void testCase7_3_3ParseCloseWithStatus()
+    {
+        ByteBuffer expected = ByteBuffer.allocate(5);
+
+        expected.put(new byte[]
+                { (byte)0x88, (byte)0x02, 0x03, (byte)0xe8  });
+
+        expected.flip();
+
+        Parser parser = new UnitParser(policy);
+        IncomingFramesCapture capture = new IncomingFramesCapture();
+        parser.setIncomingFramesHandler(capture);
+        parser.parse(expected);
+
+        capture.assertNoErrors();
+        capture.assertHasFrame(OpCode.CLOSE,1);
+
+        Frame pActual = capture.getFrames().poll();
+        Assert.assertThat("CloseFrame.payloadLength",pActual.getPayloadLength(),is(2));
+
+    }
+
+
+    @Test
+    public void testCase7_3_4GenerateCloseWithStatusReason()
+    {
+        String message = "bad cough";
+        byte[] messageBytes = message.getBytes();
+
+        CloseInfo close = new CloseInfo(1000,message);
+
+        ByteBuffer actual = UnitGenerator.generate(close.asFrame());
+
+        ByteBuffer expected = ByteBuffer.allocate(32);
+
+        expected.put(new byte[]
+                { (byte)0x88 });
+
+        byte b = 0x00; // no masking
+        b |= (message.length() + 2) & 0x7F;
+        expected.put(b);
+        expected.putShort((short)1000);
+        expected.put(messageBytes);
+
+        expected.flip();
+
+        ByteBufferAssert.assertEquals("buffers do not match",expected,actual);
+    }
+
+    @Test
+    public void testCase7_3_4ParseCloseWithStatusReason()
+    {
+        String message = "bad cough";
+        byte[] messageBytes = message.getBytes();
+
+        ByteBuffer expected = ByteBuffer.allocate(32);
+
+        expected.put(new byte[]
+                { (byte)0x88 });
+        byte b = 0x00; // no masking
+        b |= (messageBytes.length + 2) & 0x7F;
+        expected.put(b);
+        expected.putShort((short)1000);
+        expected.put(messageBytes);
+        expected.flip();
+
+        Parser parser = new UnitParser(policy);
+        IncomingFramesCapture capture = new IncomingFramesCapture();
+        parser.setIncomingFramesHandler(capture);
+        parser.parse(expected);
+
+        capture.assertNoErrors();
+        capture.assertHasFrame(OpCode.CLOSE,1);
+
+        Frame pActual = capture.getFrames().poll();
+        Assert.assertThat("CloseFrame.payloadLength",pActual.getPayloadLength(),is(messageBytes.length + 2));
+
+    }
+
+
+    @Test
+    public void testCase7_3_5GenerateCloseWithStatusMaxReason()
+    {
+        StringBuilder message = new StringBuilder();
+        for ( int i = 0 ; i < 123 ; ++i )
+        {
+            message.append("*");
+        }
+
+        CloseInfo close = new CloseInfo(1000,message.toString());
+
+        ByteBuffer actual = UnitGenerator.generate(close.asFrame());
+        ByteBuffer expected = ByteBuffer.allocate(132);
+
+        byte messageBytes[] = message.toString().getBytes(StandardCharsets.UTF_8);
+
+        expected.put(new byte[]
+                { (byte)0x88 });
+
+        byte b = 0x00; // no masking
+        b |= (messageBytes.length + 2) & 0x7F;
+        expected.put(b);
+        expected.putShort((short)1000);
+
+        expected.put(messageBytes);
+
+        expected.flip();
+
+        ByteBufferAssert.assertEquals("buffers do not match",expected,actual);
+    }
+
+    @Test
+    public void testCase7_3_5ParseCloseWithStatusMaxReason()
+    {
+        StringBuilder message = new StringBuilder();
+        for ( int i = 0 ; i < 123 ; ++i )
+        {
+            message.append("*");
+        }
+
+        byte[] messageBytes = message.toString().getBytes(StandardCharsets.UTF_8);
+
+        ByteBuffer expected = ByteBuffer.allocate(132);
+
+        expected.put(new byte[]
+                { (byte)0x88 });
+        byte b = 0x00; // no masking
+
+        b |= (messageBytes.length + 2) & 0x7F;
+        expected.put(b);
+        expected.putShort((short)1000);
+
+        expected.put(messageBytes);
+        expected.flip();
+
+        Parser parser = new UnitParser(policy);
+        IncomingFramesCapture capture = new IncomingFramesCapture();
+        parser.setIncomingFramesHandler(capture);
+        parser.parse(expected);
+
+        capture.assertNoErrors();
+        capture.assertHasFrame(OpCode.CLOSE,1);
+
+        Frame pActual = capture.getFrames().poll();
+        Assert.assertThat("CloseFrame.payloadLength",pActual.getPayloadLength(),is(125));
+
+    }
+
+    @Test(expected = ProtocolException.class)
+    public void testCase7_3_6GenerateCloseWithInvalidStatusReason()
+    {
+        StringBuilder message = new StringBuilder();
+        for ( int i = 0 ; i < 124 ; ++i )
+        {
+            message.append("*");
+        }
+
+        byte[] messageBytes = message.toString().getBytes();
+
+        CloseFrame closeFrame = new CloseFrame();
+
+        ByteBuffer bb = ByteBuffer.allocate(CloseFrame.MAX_CONTROL_PAYLOAD + 1); // 126 which is too big for control
+
+        bb.putChar((char)1000);
+        bb.put(messageBytes);
+
+        BufferUtil.flipToFlush(bb,0);
+
+        closeFrame.setPayload(bb);
+
+        UnitGenerator.generate(closeFrame);
+    }
+
+    @Test
+    public void testCase7_3_6ParseCloseWithInvalidStatusReason()
+    {
+        byte[] messageBytes = new byte[124];
+        Arrays.fill(messageBytes,(byte)'*');
+
+        ByteBuffer expected = ByteBuffer.allocate(256);
+
+        byte b;
+
+        // fin + op
+        b = 0x00;
+        b |= 0x80; // fin on
+        b |= 0x08; // close
+        expected.put(b);
+
+        // mask + len
+        b = 0x00;
+        b |= 0x00; // no masking
+        b |= 0x7E; // 2 byte len
+        expected.put(b);
+
+        // 2 byte len
+        expected.putChar((char)(messageBytes.length + 2));
+
+        // payload
+        expected.putShort((short)1000); // status code
+        expected.put(messageBytes); // reason
+
+        expected.flip();
+
+        UnitParser parser = new UnitParser(policy);
+        IncomingFramesCapture capture = new IncomingFramesCapture();
+        parser.setIncomingFramesHandler(capture);
+        parser.parseQuietly(expected);
+
+        Assert.assertEquals("error on invalid close payload",1,capture.getErrorCount(ProtocolException.class));
+
+        ProtocolException known = (ProtocolException)capture.getErrors().poll();
+
+        Assert.assertThat("Payload.message",known.getMessage(),containsString("Invalid control frame payload length"));
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/annotations/BadBinarySignatureSocket.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/annotations/BadBinarySignatureSocket.java
new file mode 100644
index 0000000..bc202e3
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/annotations/BadBinarySignatureSocket.java
@@ -0,0 +1,39 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.annotations;
+
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
+import org.eclipse.jetty.websocket.api.annotations.WebSocket;
+
+/**
+ * Invalid Socket: Annotate a message interest on a method with a return type.
+ */
+ at WebSocket
+public class BadBinarySignatureSocket
+{
+    /**
+     * Declaring a non-void return type
+     */
+    @OnWebSocketMessage
+    public boolean onBinary(Session session, byte buf[], int offset, int len)
+    {
+        return false;
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/annotations/BadDuplicateBinarySocket.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/annotations/BadDuplicateBinarySocket.java
new file mode 100644
index 0000000..9de8780
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/annotations/BadDuplicateBinarySocket.java
@@ -0,0 +1,49 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.annotations;
+
+import java.io.InputStream;
+
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
+import org.eclipse.jetty.websocket.api.annotations.WebSocket;
+
+/**
+ * Invalid Socket: Annotate 2 methods with interest in Binary Messages.
+ */
+ at WebSocket
+public class BadDuplicateBinarySocket
+{
+    /**
+     * First method
+     */
+    @OnWebSocketMessage
+    public void binMe(byte[] payload, int offset, int len)
+    {
+        /* ignore */
+    }
+
+    /**
+     * Second method
+     */
+    @OnWebSocketMessage
+    public void streamMe(InputStream stream)
+    {
+        /* ignore */
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/annotations/BadDuplicateFrameSocket.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/annotations/BadDuplicateFrameSocket.java
new file mode 100644
index 0000000..743ed62
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/annotations/BadDuplicateFrameSocket.java
@@ -0,0 +1,45 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.annotations;
+
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketFrame;
+import org.eclipse.jetty.websocket.api.annotations.WebSocket;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+
+ at WebSocket
+public class BadDuplicateFrameSocket
+{
+    /**
+     * The get a frame
+     */
+    @OnWebSocketFrame
+    public void frameMe(Frame frame)
+    {
+        /* ignore */
+    }
+
+    /**
+     * This is a duplicate frame type (should throw an exception attempting to use)
+     */
+    @OnWebSocketFrame
+    public void watchMe(Frame frame)
+    {
+        /* ignore */
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/annotations/BadTextSignatureSocket.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/annotations/BadTextSignatureSocket.java
new file mode 100644
index 0000000..43449cd
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/annotations/BadTextSignatureSocket.java
@@ -0,0 +1,39 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.annotations;
+
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
+import org.eclipse.jetty.websocket.api.annotations.WebSocket;
+
+/**
+ * Invalid Socket: Annotate a message interest on a static method
+ */
+ at WebSocket
+public class BadTextSignatureSocket
+{
+    /**
+     * Declaring a static method
+     */
+    @OnWebSocketMessage
+    public static void onText(Session session, String text)
+    {
+        /* do nothing */
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/annotations/FrameSocket.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/annotations/FrameSocket.java
new file mode 100644
index 0000000..f0bb100
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/annotations/FrameSocket.java
@@ -0,0 +1,36 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.annotations;
+
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketFrame;
+import org.eclipse.jetty.websocket.api.annotations.WebSocket;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+
+ at WebSocket
+public class FrameSocket
+{
+    /**
+     * A frame
+     */
+    @OnWebSocketFrame
+    public void frameMe(Frame frame)
+    {
+        /* ignore */
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/annotations/MyEchoBinarySocket.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/annotations/MyEchoBinarySocket.java
new file mode 100644
index 0000000..e780a59
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/annotations/MyEchoBinarySocket.java
@@ -0,0 +1,45 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.annotations;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
+import org.eclipse.jetty.websocket.api.annotations.WebSocket;
+
+/**
+ * Test of constructing a new WebSocket based on a base class
+ */
+ at WebSocket
+public class MyEchoBinarySocket extends MyEchoSocket
+{
+    @OnWebSocketMessage
+    public void echoBin(byte buf[], int offset, int length)
+    {
+        try
+        {
+            getRemote().sendBytes(ByteBuffer.wrap(buf,offset,length));
+        }
+        catch (IOException e)
+        {
+            e.printStackTrace();
+        }
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/annotations/MyEchoSocket.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/annotations/MyEchoSocket.java
new file mode 100644
index 0000000..f56a88d
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/annotations/MyEchoSocket.java
@@ -0,0 +1,78 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.annotations;
+
+import java.io.IOException;
+
+import org.eclipse.jetty.websocket.api.RemoteEndpoint;
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
+import org.eclipse.jetty.websocket.api.annotations.WebSocket;
+
+/**
+ * The most common websocket implementation.
+ * <p>
+ * This version tracks the connection per socket instance and will
+ */
+ at WebSocket
+public class MyEchoSocket
+{
+    private Session session;
+    private RemoteEndpoint remote;
+
+    public RemoteEndpoint getRemote()
+    {
+        return remote;
+    }
+
+    @OnWebSocketClose
+    public void onClose(int statusCode, String reason)
+    {
+        this.session = null;
+    }
+
+    @OnWebSocketConnect
+    public void onConnect(Session session)
+    {
+        this.session = session;
+        this.remote = session.getRemote();
+    }
+
+    @OnWebSocketMessage
+    public void onText(String message)
+    {
+        if (session == null)
+        {
+            // no connection, do nothing.
+            // this is possible due to async behavior
+            return;
+        }
+
+        try
+        {
+            remote.sendString(message);
+        }
+        catch (IOException e)
+        {
+            e.printStackTrace();
+        }
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/annotations/MyStatelessEchoSocket.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/annotations/MyStatelessEchoSocket.java
new file mode 100644
index 0000000..16dc444
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/annotations/MyStatelessEchoSocket.java
@@ -0,0 +1,41 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.annotations;
+
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
+import org.eclipse.jetty.websocket.api.annotations.WebSocket;
+
+/**
+ * Example of a stateless websocket implementation.
+ * <p>
+ * Useful for websockets that only reply to incoming requests.
+ * <p>
+ * Note: that for this style of websocket to be viable on the server side be sure that you only create 1 instance of this socket, as more instances would be
+ * wasteful of resources and memory.
+ */
+ at WebSocket
+public class MyStatelessEchoSocket
+{
+    @OnWebSocketMessage
+    public void onText(Session session, String text)
+    {
+        session.getRemote().sendString(text,null);
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/annotations/NoopSocket.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/annotations/NoopSocket.java
new file mode 100644
index 0000000..4daea26
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/annotations/NoopSocket.java
@@ -0,0 +1,30 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.annotations;
+
+import org.eclipse.jetty.websocket.api.annotations.WebSocket;
+
+/**
+ * The most basic websocket declaration.
+ */
+ at WebSocket
+public class NoopSocket
+{
+    /* intentionally do nothing */
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/annotations/NotASocket.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/annotations/NotASocket.java
new file mode 100644
index 0000000..18c86e2
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/annotations/NotASocket.java
@@ -0,0 +1,36 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.annotations;
+
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
+
+/**
+ * (Test Case)
+ * <p>
+ * Intentionally not specifying the @WebSocket annotation here
+ */
+public class NotASocket
+{
+    @OnWebSocketConnect
+    public void onConnect(Session session)
+    {
+        /* do nothing */
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/events/EventCapture.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/events/EventCapture.java
new file mode 100644
index 0000000..4ddd6b9
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/events/EventCapture.java
@@ -0,0 +1,93 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.events;
+
+import java.util.regex.Pattern;
+
+import org.eclipse.jetty.toolchain.test.EventQueue;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.junit.Assert;
+
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.startsWith;
+
+ at SuppressWarnings("serial")
+public class EventCapture extends EventQueue<String>
+{
+    private static final Logger LOG = Log.getLogger(EventCapture.class);
+    
+    public static class Assertable
+    {
+        private final String event;
+
+        public Assertable(String event)
+        {
+            this.event = event;
+        }
+
+        public void assertEventContains(String expected)
+        {
+            Assert.assertThat("Event",event,containsString(expected));
+        }
+
+        public void assertEventRegex(String regex)
+        {
+            Assert.assertTrue("Event: regex:[" + regex + "] in [" + event + "]",Pattern.matches(regex,event));
+        }
+
+        public void assertEventStartsWith(String expected)
+        {
+            Assert.assertThat("Event",event,startsWith(expected));
+        }
+
+        public void assertEvent(String expected)
+        {
+            Assert.assertThat("Event",event,is(expected));
+        }
+    }
+
+    public void add(String format, Object... args)
+    {
+        String msg = String.format(format,args);
+        if (LOG.isDebugEnabled())
+            LOG.debug("EVENT: {}",msg);
+        super.offer(msg);
+    }
+
+    public Assertable pop()
+    {
+        return new Assertable(super.poll());
+    }
+
+    public void assertEventCount(int expectedCount)
+    {
+        Assert.assertThat("Event Count",size(),is(expectedCount));
+    }
+
+    public String q(String str)
+    {
+        if (str == null)
+        {
+            return "<null>";
+        }
+        return '"' + str + '"';
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/events/EventDriverFactoryTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/events/EventDriverFactoryTest.java
new file mode 100644
index 0000000..e07e5cf
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/events/EventDriverFactoryTest.java
@@ -0,0 +1,85 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.events;
+
+import static org.hamcrest.Matchers.allOf;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.instanceOf;
+
+import org.eclipse.jetty.websocket.api.InvalidWebSocketException;
+import org.eclipse.jetty.websocket.api.WebSocketListener;
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.api.annotations.WebSocket;
+import org.eclipse.jetty.websocket.common.annotations.NotASocket;
+import org.junit.Assert;
+import org.junit.Test;
+
+import examples.AdapterConnectCloseSocket;
+import examples.ListenerBasicSocket;
+
+public class EventDriverFactoryTest
+{
+    /**
+     * Test Case for no exceptions and 5 methods (extends WebSocketAdapter)
+     */
+    @Test
+    public void testAdapterConnectCloseSocket()
+    {
+        EventDriverFactory factory = new EventDriverFactory(WebSocketPolicy.newClientPolicy());
+        AdapterConnectCloseSocket socket = new AdapterConnectCloseSocket();
+        EventDriver driver = factory.wrap(socket);
+
+        String classId = AdapterConnectCloseSocket.class.getSimpleName();
+        Assert.assertThat("EventDriver for " + classId,driver,instanceOf(JettyListenerEventDriver.class));
+    }
+
+    /**
+     * Test Case for bad declaration (duplicate OnWebSocketBinary declarations)
+     */
+    @Test
+    public void testBadNotASocket()
+    {
+        EventDriverFactory factory = new EventDriverFactory(WebSocketPolicy.newClientPolicy());
+        try
+        {
+            NotASocket bad = new NotASocket();
+            // Should toss exception
+            factory.wrap(bad);
+        }
+        catch (InvalidWebSocketException e)
+        {
+            // Validate that we have clear error message to the developer
+            Assert.assertThat(e.getMessage(),allOf(containsString(WebSocketListener.class.getSimpleName()),containsString(WebSocket.class.getSimpleName())));
+        }
+    }
+
+    /**
+     * Test Case for no exceptions and 5 methods (implement WebSocketListener)
+     */
+    @Test
+    public void testListenerBasicSocket()
+    {
+        EventDriverFactory factory = new EventDriverFactory(WebSocketPolicy.newClientPolicy());
+        ListenerBasicSocket socket = new ListenerBasicSocket();
+        EventDriver driver = factory.wrap(socket);
+
+        String classId = ListenerBasicSocket.class.getSimpleName();
+        Assert.assertThat("EventDriver for " + classId,driver,instanceOf(JettyListenerEventDriver.class));
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/events/EventDriverTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/events/EventDriverTest.java
new file mode 100644
index 0000000..e027614
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/events/EventDriverTest.java
@@ -0,0 +1,182 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.events;
+
+import java.io.IOException;
+import java.util.concurrent.TimeoutException;
+
+import org.eclipse.jetty.websocket.api.StatusCode;
+import org.eclipse.jetty.websocket.api.WebSocketException;
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.common.CloseInfo;
+import org.eclipse.jetty.websocket.common.frames.BinaryFrame;
+import org.eclipse.jetty.websocket.common.frames.PingFrame;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
+import org.eclipse.jetty.websocket.common.io.LocalWebSocketSession;
+import org.eclipse.jetty.websocket.common.test.LeakTrackingBufferPoolRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+
+import examples.AdapterConnectCloseSocket;
+import examples.AnnotatedBinaryArraySocket;
+import examples.AnnotatedBinaryStreamSocket;
+import examples.AnnotatedFramesSocket;
+import examples.AnnotatedTextSocket;
+import examples.ListenerBasicSocket;
+
+public class EventDriverTest
+{
+    @Rule
+    public TestName testname = new TestName();
+
+    @Rule
+    public LeakTrackingBufferPoolRule bufferPool = new LeakTrackingBufferPoolRule("Test");
+
+    private Frame makeBinaryFrame(String content, boolean fin)
+    {
+        return new BinaryFrame().setPayload(content).setFin(fin);
+    }
+
+    @Test
+    public void testAdapter_ConnectClose() throws IOException
+    {
+        AdapterConnectCloseSocket socket = new AdapterConnectCloseSocket();
+        EventDriver driver = wrap(socket);
+
+        try (LocalWebSocketSession conn = new LocalWebSocketSession(testname,driver,bufferPool))
+        {
+            conn.open();
+            driver.incomingFrame(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+            socket.capture.assertEventCount(2);
+            socket.capture.pop().assertEventStartsWith("onWebSocketConnect");
+            socket.capture.pop().assertEventStartsWith("onWebSocketClose");
+        }
+    }
+
+    @Test
+    public void testAnnotated_ByteArray() throws IOException
+    {
+        AnnotatedBinaryArraySocket socket = new AnnotatedBinaryArraySocket();
+        EventDriver driver = wrap(socket);
+
+        try (LocalWebSocketSession conn = new LocalWebSocketSession(testname,driver,bufferPool))
+        {
+            conn.open();
+            driver.incomingFrame(makeBinaryFrame("Hello World",true));
+            driver.incomingFrame(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+            socket.capture.assertEventCount(3);
+            socket.capture.pop().assertEventStartsWith("onConnect");
+            socket.capture.pop().assertEvent("onBinary([11],0,11)");
+            socket.capture.pop().assertEventStartsWith("onClose(1000,");
+        }
+    }
+
+    @Test
+    public void testAnnotated_Error() throws IOException
+    {
+        AnnotatedTextSocket socket = new AnnotatedTextSocket();
+        EventDriver driver = wrap(socket);
+
+        try (LocalWebSocketSession conn = new LocalWebSocketSession(testname,driver,bufferPool))
+        {
+            conn.open();
+            driver.incomingError(new WebSocketException("oof"));
+            driver.incomingFrame(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+            socket.capture.assertEventCount(3);
+            socket.capture.pop().assertEventStartsWith("onConnect");
+            socket.capture.pop().assertEventStartsWith("onError(WebSocketException: oof)");
+            socket.capture.pop().assertEventStartsWith("onClose(1000,");
+        }
+    }
+
+    @Test
+    public void testAnnotated_Frames() throws IOException
+    {
+        AnnotatedFramesSocket socket = new AnnotatedFramesSocket();
+        EventDriver driver = wrap(socket);
+
+        try (LocalWebSocketSession conn = new LocalWebSocketSession(testname,driver,bufferPool))
+        {
+            conn.open();
+            driver.incomingFrame(new PingFrame().setPayload("PING"));
+            driver.incomingFrame(new TextFrame().setPayload("Text Me"));
+            driver.incomingFrame(new BinaryFrame().setPayload("Hello Bin"));
+            driver.incomingFrame(new CloseInfo(StatusCode.SHUTDOWN,"testcase").asFrame());
+
+            socket.capture.assertEventCount(6);
+            socket.capture.pop().assertEventStartsWith("onConnect(");
+            socket.capture.pop().assertEventStartsWith("onFrame(PING[");
+            socket.capture.pop().assertEventStartsWith("onFrame(TEXT[");
+            socket.capture.pop().assertEventStartsWith("onFrame(BINARY[");
+            socket.capture.pop().assertEventStartsWith("onFrame(CLOSE[");
+            socket.capture.pop().assertEventStartsWith("onClose(1001,");
+        }
+    }
+
+    @Test
+    public void testAnnotated_InputStream() throws IOException, TimeoutException, InterruptedException
+    {
+        AnnotatedBinaryStreamSocket socket = new AnnotatedBinaryStreamSocket();
+        EventDriver driver = wrap(socket);
+
+        try (LocalWebSocketSession conn = new LocalWebSocketSession(testname,driver,bufferPool))
+        {
+            conn.open();
+            driver.incomingFrame(makeBinaryFrame("Hello World",true));
+            driver.incomingFrame(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+            socket.capture.assertEventCount(3);
+            socket.capture.pop().assertEventStartsWith("onConnect");
+            socket.capture.pop().assertEventRegex("^onBinary\\(.*InputStream.*");
+            socket.capture.pop().assertEventStartsWith("onClose(1000,");
+        }
+    }
+
+    @Test
+    public void testListener_Text() throws Exception
+    {
+        ListenerBasicSocket socket = new ListenerBasicSocket();
+        EventDriver driver = wrap(socket);
+
+        try (LocalWebSocketSession conn = new LocalWebSocketSession(testname,driver,bufferPool))
+        {
+            conn.start();
+            conn.open();
+            driver.incomingFrame(new TextFrame().setPayload("Hello World"));
+            driver.incomingFrame(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+            socket.capture.assertEventCount(3);
+            socket.capture.pop().assertEventStartsWith("onWebSocketConnect");
+            socket.capture.pop().assertEventStartsWith("onWebSocketText(\"Hello World\")");
+            socket.capture.pop().assertEventStartsWith("onWebSocketClose(1000,");
+        }
+    }
+
+    private EventDriver wrap(Object websocket)
+    {
+        WebSocketPolicy policy = WebSocketPolicy.newServerPolicy();
+        EventDriverFactory factory = new EventDriverFactory(policy);
+        return factory.wrap(websocket);
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/events/JettyAnnotatedScannerTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/events/JettyAnnotatedScannerTest.java
new file mode 100644
index 0000000..4d5236b
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/events/JettyAnnotatedScannerTest.java
@@ -0,0 +1,342 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.events;
+
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.hamcrest.Matchers.nullValue;
+
+import org.eclipse.jetty.websocket.api.InvalidWebSocketException;
+import org.eclipse.jetty.websocket.common.annotations.BadBinarySignatureSocket;
+import org.eclipse.jetty.websocket.common.annotations.BadDuplicateBinarySocket;
+import org.eclipse.jetty.websocket.common.annotations.BadDuplicateFrameSocket;
+import org.eclipse.jetty.websocket.common.annotations.BadTextSignatureSocket;
+import org.eclipse.jetty.websocket.common.annotations.FrameSocket;
+import org.eclipse.jetty.websocket.common.annotations.MyEchoBinarySocket;
+import org.eclipse.jetty.websocket.common.annotations.MyEchoSocket;
+import org.eclipse.jetty.websocket.common.annotations.MyStatelessEchoSocket;
+import org.eclipse.jetty.websocket.common.annotations.NoopSocket;
+import org.eclipse.jetty.websocket.common.events.annotated.CallableMethod;
+import org.junit.Assert;
+import org.junit.Test;
+
+import examples.AnnotatedBinaryArraySocket;
+import examples.AnnotatedBinaryStreamSocket;
+import examples.AnnotatedTextSocket;
+import examples.AnnotatedTextStreamSocket;
+
+public class JettyAnnotatedScannerTest
+{
+    private void assertHasEventMethod(String message, CallableMethod actual)
+    {
+        Assert.assertThat(message + " CallableMethod",actual,notNullValue());
+
+        Assert.assertThat(message + " CallableMethod.pojo",actual.getPojo(),notNullValue());
+        Assert.assertThat(message + " CallableMethod.method",actual.getMethod(),notNullValue());
+    }
+
+    private void assertNoEventMethod(String message, CallableMethod actual)
+    {
+        Assert.assertThat(message + " CallableMethod",actual,nullValue());
+    }
+
+    /**
+     * Test Case for bad declaration (duplicate OnWebSocketBinary declarations)
+     */
+    @Test
+    public void testAnnotatedBadDuplicateBinarySocket()
+    {
+        JettyAnnotatedScanner impl = new JettyAnnotatedScanner();
+        try
+        {
+            // Should toss exception
+            impl.scan(BadDuplicateBinarySocket.class);
+            Assert.fail("Should have thrown " + InvalidWebSocketException.class);
+        }
+        catch (InvalidWebSocketException e)
+        {
+            // Validate that we have clear error message to the developer
+            Assert.assertThat(e.getMessage(),containsString("Duplicate @OnWebSocketMessage declaration"));
+        }
+    }
+
+    /**
+     * Test Case for bad declaration (duplicate frame type methods)
+     */
+    @Test
+    public void testAnnotatedBadDuplicateFrameSocket()
+    {
+        JettyAnnotatedScanner impl = new JettyAnnotatedScanner();
+        try
+        {
+            // Should toss exception
+            impl.scan(BadDuplicateFrameSocket.class);
+            Assert.fail("Should have thrown " + InvalidWebSocketException.class);
+        }
+        catch (InvalidWebSocketException e)
+        {
+            // Validate that we have clear error message to the developer
+            Assert.assertThat(e.getMessage(),containsString("Duplicate @OnWebSocketFrame"));
+        }
+    }
+
+    /**
+     * Test Case for bad declaration a method with a non-void return type
+     */
+    @Test
+    public void testAnnotatedBadSignature_NonVoidReturn()
+    {
+        JettyAnnotatedScanner impl = new JettyAnnotatedScanner();
+        try
+        {
+            // Should toss exception
+            impl.scan(BadBinarySignatureSocket.class);
+            Assert.fail("Should have thrown " + InvalidWebSocketException.class);
+        }
+        catch (InvalidWebSocketException e)
+        {
+            // Validate that we have clear error message to the developer
+            Assert.assertThat(e.getMessage(),containsString("must be void"));
+        }
+    }
+
+    /**
+     * Test Case for bad declaration a method with a public static method
+     */
+    @Test
+    public void testAnnotatedBadSignature_Static()
+    {
+        JettyAnnotatedScanner impl = new JettyAnnotatedScanner();
+        try
+        {
+            // Should toss exception
+            impl.scan(BadTextSignatureSocket.class);
+            Assert.fail("Should have thrown " + InvalidWebSocketException.class);
+        }
+        catch (InvalidWebSocketException e)
+        {
+            // Validate that we have clear error message to the developer
+            Assert.assertThat(e.getMessage(),containsString("may not be static"));
+        }
+    }
+
+    /**
+     * Test Case for socket for binary array messages
+     */
+    @Test
+    public void testAnnotatedBinaryArraySocket()
+    {
+        JettyAnnotatedScanner impl = new JettyAnnotatedScanner();
+        JettyAnnotatedMetadata metadata = impl.scan(AnnotatedBinaryArraySocket.class);
+
+        String classId = AnnotatedBinaryArraySocket.class.getSimpleName();
+
+        Assert.assertThat("EventMethods for " + classId,metadata,notNullValue());
+
+        assertHasEventMethod(classId + ".onBinary",metadata.onBinary);
+        assertHasEventMethod(classId + ".onClose",metadata.onClose);
+        assertHasEventMethod(classId + ".onConnect",metadata.onConnect);
+        assertNoEventMethod(classId + ".onException",metadata.onError);
+        assertNoEventMethod(classId + ".onText",metadata.onText);
+        assertNoEventMethod(classId + ".onFrame",metadata.onFrame);
+
+        Assert.assertFalse(classId + ".onBinary.isSessionAware",metadata.onBinary.isSessionAware());
+        Assert.assertFalse(classId + ".onBinary.isStreaming",metadata.onBinary.isStreaming());
+    }
+
+    /**
+     * Test Case for socket for binary stream messages
+     */
+    @Test
+    public void testAnnotatedBinaryStreamSocket()
+    {
+        JettyAnnotatedScanner impl = new JettyAnnotatedScanner();
+        JettyAnnotatedMetadata metadata = impl.scan(AnnotatedBinaryStreamSocket.class);
+
+        String classId = AnnotatedBinaryStreamSocket.class.getSimpleName();
+
+        Assert.assertThat("EventMethods for " + classId,metadata,notNullValue());
+
+        assertHasEventMethod(classId + ".onBinary",metadata.onBinary);
+        assertHasEventMethod(classId + ".onClose",metadata.onClose);
+        assertHasEventMethod(classId + ".onConnect",metadata.onConnect);
+        assertNoEventMethod(classId + ".onException",metadata.onError);
+        assertNoEventMethod(classId + ".onText",metadata.onText);
+        assertNoEventMethod(classId + ".onFrame",metadata.onFrame);
+
+        Assert.assertFalse(classId + ".onBinary.isSessionAware",metadata.onBinary.isSessionAware());
+        Assert.assertTrue(classId + ".onBinary.isStreaming",metadata.onBinary.isStreaming());
+    }
+
+    /**
+     * Test Case for no exceptions and 4 methods (3 methods from parent)
+     */
+    @Test
+    public void testAnnotatedMyEchoBinarySocket()
+    {
+        JettyAnnotatedScanner impl = new JettyAnnotatedScanner();
+        JettyAnnotatedMetadata metadata = impl.scan(MyEchoBinarySocket.class);
+
+        String classId = MyEchoBinarySocket.class.getSimpleName();
+
+        Assert.assertThat("EventMethods for " + classId,metadata,notNullValue());
+
+        assertHasEventMethod(classId + ".onBinary",metadata.onBinary);
+        assertHasEventMethod(classId + ".onClose",metadata.onClose);
+        assertHasEventMethod(classId + ".onConnect",metadata.onConnect);
+        assertNoEventMethod(classId + ".onException",metadata.onError);
+        assertHasEventMethod(classId + ".onText",metadata.onText);
+        assertNoEventMethod(classId + ".onFrame",metadata.onFrame);
+    }
+
+    /**
+     * Test Case for no exceptions and 3 methods
+     */
+    @Test
+    public void testAnnotatedMyEchoSocket()
+    {
+        JettyAnnotatedScanner impl = new JettyAnnotatedScanner();
+        JettyAnnotatedMetadata metadata = impl.scan(MyEchoSocket.class);
+
+        String classId = MyEchoSocket.class.getSimpleName();
+
+        Assert.assertThat("EventMethods for " + classId,metadata,notNullValue());
+
+        assertNoEventMethod(classId + ".onBinary",metadata.onBinary);
+        assertHasEventMethod(classId + ".onClose",metadata.onClose);
+        assertHasEventMethod(classId + ".onConnect",metadata.onConnect);
+        assertNoEventMethod(classId + ".onException",metadata.onError);
+        assertHasEventMethod(classId + ".onText",metadata.onText);
+        assertNoEventMethod(classId + ".onFrame",metadata.onFrame);
+    }
+
+    /**
+     * Test Case for annotated for text messages w/connection param
+     */
+    @Test
+    public void testAnnotatedMyStatelessEchoSocket()
+    {
+        JettyAnnotatedScanner impl = new JettyAnnotatedScanner();
+        JettyAnnotatedMetadata metadata = impl.scan(MyStatelessEchoSocket.class);
+
+        String classId = MyStatelessEchoSocket.class.getSimpleName();
+
+        Assert.assertThat("EventMethods for " + classId,metadata,notNullValue());
+
+        assertNoEventMethod(classId + ".onBinary",metadata.onBinary);
+        assertNoEventMethod(classId + ".onClose",metadata.onClose);
+        assertNoEventMethod(classId + ".onConnect",metadata.onConnect);
+        assertNoEventMethod(classId + ".onException",metadata.onError);
+        assertHasEventMethod(classId + ".onText",metadata.onText);
+        assertNoEventMethod(classId + ".onFrame",metadata.onFrame);
+
+        Assert.assertTrue(classId + ".onText.isSessionAware",metadata.onText.isSessionAware());
+        Assert.assertFalse(classId + ".onText.isStreaming",metadata.onText.isStreaming());
+    }
+
+    /**
+     * Test Case for no exceptions and no methods
+     */
+    @Test
+    public void testAnnotatedNoop()
+    {
+        JettyAnnotatedScanner impl = new JettyAnnotatedScanner();
+        JettyAnnotatedMetadata metadata = impl.scan(NoopSocket.class);
+
+        String classId = NoopSocket.class.getSimpleName();
+
+        Assert.assertThat("Methods for " + classId,metadata,notNullValue());
+
+        assertNoEventMethod(classId + ".onBinary",metadata.onBinary);
+        assertNoEventMethod(classId + ".onClose",metadata.onClose);
+        assertNoEventMethod(classId + ".onConnect",metadata.onConnect);
+        assertNoEventMethod(classId + ".onException",metadata.onError);
+        assertNoEventMethod(classId + ".onText",metadata.onText);
+        assertNoEventMethod(classId + ".onFrame",metadata.onFrame);
+    }
+
+    /**
+     * Test Case for no exceptions and 1 methods
+     */
+    @Test
+    public void testAnnotatedOnFrame()
+    {
+        JettyAnnotatedScanner impl = new JettyAnnotatedScanner();
+        JettyAnnotatedMetadata metadata = impl.scan(FrameSocket.class);
+
+        String classId = FrameSocket.class.getSimpleName();
+
+        Assert.assertThat("EventMethods for " + classId,metadata,notNullValue());
+
+        assertNoEventMethod(classId + ".onBinary",metadata.onBinary);
+        assertNoEventMethod(classId + ".onClose",metadata.onClose);
+        assertNoEventMethod(classId + ".onConnect",metadata.onConnect);
+        assertNoEventMethod(classId + ".onException",metadata.onError);
+        assertNoEventMethod(classId + ".onText",metadata.onText);
+        assertHasEventMethod(classId + ".onFrame",metadata.onFrame);
+    }
+
+    /**
+     * Test Case for socket for simple text messages
+     */
+    @Test
+    public void testAnnotatedTextSocket()
+    {
+        JettyAnnotatedScanner impl = new JettyAnnotatedScanner();
+        JettyAnnotatedMetadata metadata = impl.scan(AnnotatedTextSocket.class);
+
+        String classId = AnnotatedTextSocket.class.getSimpleName();
+
+        Assert.assertThat("EventMethods for " + classId,metadata,notNullValue());
+
+        assertNoEventMethod(classId + ".onBinary",metadata.onBinary);
+        assertHasEventMethod(classId + ".onClose",metadata.onClose);
+        assertHasEventMethod(classId + ".onConnect",metadata.onConnect);
+        assertHasEventMethod(classId + ".onException",metadata.onError);
+        assertHasEventMethod(classId + ".onText",metadata.onText);
+        assertNoEventMethod(classId + ".onFrame",metadata.onFrame);
+
+        Assert.assertFalse(classId + ".onText.isSessionAware",metadata.onText.isSessionAware());
+        Assert.assertFalse(classId + ".onText.isStreaming",metadata.onText.isStreaming());
+    }
+
+    /**
+     * Test Case for socket for text stream messages
+     */
+    @Test
+    public void testAnnotatedTextStreamSocket()
+    {
+        JettyAnnotatedScanner impl = new JettyAnnotatedScanner();
+        JettyAnnotatedMetadata metadata = impl.scan(AnnotatedTextStreamSocket.class);
+
+        String classId = AnnotatedTextStreamSocket.class.getSimpleName();
+
+        Assert.assertThat("EventMethods for " + classId,metadata,notNullValue());
+
+        assertNoEventMethod(classId + ".onBinary",metadata.onBinary);
+        assertHasEventMethod(classId + ".onClose",metadata.onClose);
+        assertHasEventMethod(classId + ".onConnect",metadata.onConnect);
+        assertNoEventMethod(classId + ".onException",metadata.onError);
+        assertHasEventMethod(classId + ".onText",metadata.onText);
+        assertNoEventMethod(classId + ".onFrame",metadata.onFrame);
+
+        Assert.assertFalse(classId + ".onText.isSessionAware",metadata.onText.isSessionAware());
+        Assert.assertTrue(classId + ".onText.isStreaming",metadata.onText.isStreaming());
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/AbstractExtensionTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/AbstractExtensionTest.java
new file mode 100644
index 0000000..9155a3a
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/AbstractExtensionTest.java
@@ -0,0 +1,44 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.extensions;
+
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.common.test.LeakTrackingBufferPoolRule;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.rules.TestName;
+
+public abstract class AbstractExtensionTest
+{
+    @Rule
+    public TestName testname = new TestName();
+    
+    @Rule
+    public LeakTrackingBufferPoolRule bufferPool = new LeakTrackingBufferPoolRule("Test");
+
+    protected ExtensionTool clientExtensions;
+    protected ExtensionTool serverExtensions;
+
+    @Before
+    public void init()
+    {
+        clientExtensions = new ExtensionTool(WebSocketPolicy.newClientPolicy(),bufferPool);
+        serverExtensions = new ExtensionTool(WebSocketPolicy.newServerPolicy(),bufferPool);
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/DummyIncomingFrames.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/DummyIncomingFrames.java
new file mode 100644
index 0000000..12a74be
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/DummyIncomingFrames.java
@@ -0,0 +1,56 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.extensions;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.api.extensions.IncomingFrames;
+
+/**
+ * Dummy implementation of {@link IncomingFrames} used for testing
+ */
+public class DummyIncomingFrames implements IncomingFrames
+{
+    private static final Logger LOG = Log.getLogger(DummyIncomingFrames.class);
+    private final String id;
+
+    public DummyIncomingFrames(String id)
+    {
+        this.id = id;
+    }
+
+    @Override
+    public void incomingError(Throwable e)
+    {
+        LOG.debug("incomingError()",e);
+    }
+
+    @Override
+    public void incomingFrame(Frame frame)
+    {
+        LOG.debug("incomingFrame({})",frame);
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s@%x[%s]",this.getClass().getSimpleName(),hashCode(),id);
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/DummyOutgoingFrames.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/DummyOutgoingFrames.java
new file mode 100644
index 0000000..e3e4ba0
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/DummyOutgoingFrames.java
@@ -0,0 +1,62 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.extensions;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.BatchMode;
+import org.eclipse.jetty.websocket.api.WriteCallback;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.api.extensions.OutgoingFrames;
+import org.junit.rules.TestName;
+
+/**
+ * Dummy implementation of {@link OutgoingFrames} used for testing
+ */
+public class DummyOutgoingFrames implements OutgoingFrames
+{
+    private static final Logger LOG = Log.getLogger(DummyOutgoingFrames.class);
+    private final String id;
+
+    public DummyOutgoingFrames(String id)
+    {
+        this.id = id;
+    }
+
+    public DummyOutgoingFrames(TestName testname)
+    {
+        this(testname.getMethodName());
+    }
+
+    @Override
+    public void outgoingFrame(Frame frame, WriteCallback callback, BatchMode batchMode)
+    {
+        LOG.debug("outgoingFrame({},{})",frame,callback);
+        if (callback != null)
+        {
+            callback.writeSuccess();
+        }
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s@%x[%s]",this.getClass().getSimpleName(),hashCode(),id);
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/ExtensionStackTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/ExtensionStackTest.java
new file mode 100644
index 0000000..18bdb4d
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/ExtensionStackTest.java
@@ -0,0 +1,189 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.extensions;
+
+import static org.hamcrest.Matchers.*;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.api.extensions.Extension;
+import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
+import org.eclipse.jetty.websocket.api.extensions.ExtensionFactory;
+import org.eclipse.jetty.websocket.common.extensions.identity.IdentityExtension;
+import org.eclipse.jetty.websocket.common.test.LeakTrackingBufferPoolRule;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class ExtensionStackTest
+{
+    private static final Logger LOG = Log.getLogger(ExtensionStackTest.class);
+    
+    @Rule
+    public LeakTrackingBufferPoolRule bufferPool = new LeakTrackingBufferPoolRule("Test");
+
+    @SuppressWarnings("unchecked")
+    private <T> T assertIsExtension(String msg, Object obj, Class<T> clazz)
+    {
+        if (clazz.isAssignableFrom(obj.getClass()))
+        {
+            return (T)obj;
+        }
+        Assert.assertEquals("Expected " + msg + " class",clazz.getName(),obj.getClass().getName());
+        return null;
+    }
+
+    private ExtensionStack createExtensionStack()
+    {
+        WebSocketPolicy policy = WebSocketPolicy.newClientPolicy();
+        ExtensionFactory factory = new WebSocketExtensionFactory(policy,bufferPool);
+        return new ExtensionStack(factory);
+    }
+
+    @Test
+    public void testStartIdentity() throws Exception
+    {
+        ExtensionStack stack = createExtensionStack();
+        try
+        {
+            // 1 extension
+            List<ExtensionConfig> configs = new ArrayList<>();
+            configs.add(ExtensionConfig.parse("identity"));
+            stack.negotiate(configs);
+
+            // Setup Listeners
+            DummyIncomingFrames session = new DummyIncomingFrames("Session");
+            DummyOutgoingFrames connection = new DummyOutgoingFrames("Connection");
+            stack.setNextOutgoing(connection);
+            stack.setNextIncoming(session);
+
+            // Start
+            stack.start();
+
+            // Dump
+            LOG.debug("{}",stack.dump());
+
+            // Should be no change to handlers
+            Extension actualIncomingExtension = assertIsExtension("Incoming",stack.getNextIncoming(),IdentityExtension.class);
+            Extension actualOutgoingExtension = assertIsExtension("Outgoing",stack.getNextOutgoing(),IdentityExtension.class);
+            Assert.assertEquals(actualIncomingExtension,actualOutgoingExtension);
+        }
+        finally
+        {
+            stack.stop();
+        }
+    }
+
+    @Test
+    public void testStartIdentityTwice() throws Exception
+    {
+        ExtensionStack stack = createExtensionStack();
+        try
+        {
+            // 1 extension
+            List<ExtensionConfig> configs = new ArrayList<>();
+            configs.add(ExtensionConfig.parse("identity; id=A"));
+            configs.add(ExtensionConfig.parse("identity; id=B"));
+            stack.negotiate(configs);
+
+            // Setup Listeners
+            DummyIncomingFrames session = new DummyIncomingFrames("Session");
+            DummyOutgoingFrames connection = new DummyOutgoingFrames("Connection");
+            stack.setNextOutgoing(connection);
+            stack.setNextIncoming(session);
+
+            // Start
+            stack.start();
+
+            // Dump
+            LOG.debug("{}",stack.dump());
+
+            // Should be no change to handlers
+            IdentityExtension actualIncomingExtension = assertIsExtension("Incoming",stack.getNextIncoming(),IdentityExtension.class);
+            IdentityExtension actualOutgoingExtension = assertIsExtension("Outgoing",stack.getNextOutgoing(),IdentityExtension.class);
+
+            Assert.assertThat("Incoming[identity].id",actualIncomingExtension.getParam("id"),is("A"));
+            Assert.assertThat("Outgoing[identity].id",actualOutgoingExtension.getParam("id"),is("B"));
+        }
+        finally
+        {
+            stack.stop();
+        }
+    }
+
+    @Test
+    public void testStartNothing() throws Exception
+    {
+        ExtensionStack stack = createExtensionStack();
+        try
+        {
+            // intentionally empty
+            List<ExtensionConfig> configs = new ArrayList<>();
+            stack.negotiate(configs);
+
+            // Setup Listeners
+            DummyIncomingFrames session = new DummyIncomingFrames("Session");
+            DummyOutgoingFrames connection = new DummyOutgoingFrames("Connection");
+            stack.setNextOutgoing(connection);
+            stack.setNextIncoming(session);
+
+            // Start
+            stack.start();
+
+            // Dump
+            LOG.debug("{}",stack.dump());
+
+            // Should be no change to handlers
+            Assert.assertEquals("Incoming Handler",stack.getNextIncoming(),session);
+            Assert.assertEquals("Outgoing Handler",stack.getNextOutgoing(),connection);
+        }
+        finally
+        {
+            stack.stop();
+        }
+    }
+
+    @Test
+    public void testToString()
+    {
+        ExtensionStack stack = createExtensionStack();
+        // Shouldn't cause a NPE.
+        LOG.debug("Shouldn't cause a NPE: {}",stack.toString());
+    }
+    
+    @Test
+    public void testNegotiateChrome32()
+    {
+        ExtensionStack stack = createExtensionStack();
+        
+        String chromeRequest = "permessage-deflate; client_max_window_bits, x-webkit-deflate-frame";
+        List<ExtensionConfig> requestedConfigs = ExtensionConfig.parseList(chromeRequest);
+        stack.negotiate(requestedConfigs);
+        
+        List<ExtensionConfig> negotiated = stack.getNegotiatedExtensions();
+        String response = ExtensionConfig.toHeaderValue(negotiated);
+        
+        Assert.assertThat("Negotiated Extensions", response, is("permessage-deflate"));
+        LOG.debug("Shouldn't cause a NPE: {}",stack.toString());
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/ExtensionTool.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/ExtensionTool.java
new file mode 100644
index 0000000..4e17cc9
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/ExtensionTool.java
@@ -0,0 +1,136 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.extensions;
+
+import static org.hamcrest.Matchers.*;
+
+import java.nio.ByteBuffer;
+import java.util.Collections;
+
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.api.extensions.Extension;
+import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
+import org.eclipse.jetty.websocket.api.extensions.ExtensionFactory;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.common.Parser;
+import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
+import org.eclipse.jetty.websocket.common.test.ByteBufferAssert;
+import org.eclipse.jetty.websocket.common.test.IncomingFramesCapture;
+import org.eclipse.jetty.websocket.common.test.UnitParser;
+import org.junit.Assert;
+
+public class ExtensionTool
+{
+    public class Tester
+    {
+        private String requestedExtParams;
+        private ExtensionConfig extConfig;
+        private Extension ext;
+        private Parser parser;
+        private IncomingFramesCapture capture;
+
+        private Tester(String parameterizedExtension)
+        {
+            this.requestedExtParams = parameterizedExtension;
+            this.extConfig = ExtensionConfig.parse(parameterizedExtension);
+            Class<?> extClass = factory.getExtension(extConfig.getName());
+            Assert.assertThat("extClass", extClass, notNullValue());
+
+            this.parser = new UnitParser(policy);
+        }
+
+        public String getRequestedExtParams()
+        {
+            return requestedExtParams;
+        }
+
+        public void assertNegotiated(String expectedNegotiation)
+        {
+            this.ext = (Extension)factory.newInstance(extConfig);
+
+            this.capture = new IncomingFramesCapture();
+            this.ext.setNextIncomingFrames(capture);
+
+            this.parser.configureFromExtensions(Collections.singletonList(ext));
+            this.parser.setIncomingFramesHandler(ext);
+        }
+
+        public void parseIncomingHex(String... rawhex)
+        {
+            int parts = rawhex.length;
+            byte net[];
+
+            for (int i = 0; i < parts; i++)
+            {
+                String hex = rawhex[i].replaceAll("\\s*(0x)?","");
+                net = TypeUtil.fromHexString(hex);
+                parser.parse(ByteBuffer.wrap(net));
+            }
+        }
+
+        public void assertHasFrames(String... textFrames)
+        {
+            Frame frames[] = new Frame[textFrames.length];
+            for (int i = 0; i < frames.length; i++)
+            {
+                frames[i] = new TextFrame().setPayload(textFrames[i]);
+            }
+            assertHasFrames(frames);
+        }
+
+        public void assertHasFrames(Frame... expectedFrames)
+        {
+            int expectedCount = expectedFrames.length;
+            capture.assertFrameCount(expectedCount);
+
+            for (int i = 0; i < expectedCount; i++)
+            {
+                WebSocketFrame actual = capture.getFrames().poll();
+
+                String prefix = String.format("frame[%d]",i);
+                Assert.assertThat(prefix + ".opcode",actual.getOpCode(),is(expectedFrames[i].getOpCode()));
+                Assert.assertThat(prefix + ".fin",actual.isFin(),is(expectedFrames[i].isFin()));
+                Assert.assertThat(prefix + ".rsv1",actual.isRsv1(),is(false));
+                Assert.assertThat(prefix + ".rsv2",actual.isRsv2(),is(false));
+                Assert.assertThat(prefix + ".rsv3",actual.isRsv3(),is(false));
+
+                ByteBuffer expected = expectedFrames[i].getPayload().slice();
+                Assert.assertThat(prefix + ".payloadLength",actual.getPayloadLength(),is(expected.remaining()));
+                ByteBufferAssert.assertEquals(prefix + ".payload",expected,actual.getPayload().slice());
+            }
+        }
+    }
+
+    private final WebSocketPolicy policy;
+    private final ExtensionFactory factory;
+
+    public ExtensionTool(WebSocketPolicy policy, ByteBufferPool bufferPool)
+    {
+        this.policy = policy;
+        this.factory = new WebSocketExtensionFactory(policy,bufferPool);
+    }
+
+    public Tester newTester(String parameterizedExtension)
+    {
+        return new Tester(parameterizedExtension);
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/FragmentExtensionTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/FragmentExtensionTest.java
new file mode 100644
index 0000000..4274cb3
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/FragmentExtensionTest.java
@@ -0,0 +1,313 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.extensions;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.websocket.api.BatchMode;
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.common.OpCode;
+import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.extensions.fragment.FragmentExtension;
+import org.eclipse.jetty.websocket.common.frames.ContinuationFrame;
+import org.eclipse.jetty.websocket.common.frames.PingFrame;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
+import org.eclipse.jetty.websocket.common.test.ByteBufferAssert;
+import org.eclipse.jetty.websocket.common.test.IncomingFramesCapture;
+import org.eclipse.jetty.websocket.common.test.LeakTrackingBufferPoolRule;
+import org.eclipse.jetty.websocket.common.test.OutgoingFramesCapture;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+
+import static org.hamcrest.Matchers.is;
+
+public class FragmentExtensionTest
+{
+    @Rule
+    public LeakTrackingBufferPoolRule bufferPool = new LeakTrackingBufferPoolRule("Test");
+
+    /**
+     * Verify that incoming frames are passed thru without modification
+     */
+    @Test
+    public void testIncomingFrames()
+    {
+        IncomingFramesCapture capture = new IncomingFramesCapture();
+
+        FragmentExtension ext = new FragmentExtension();
+        ext.setBufferPool(bufferPool);
+        ext.setPolicy(WebSocketPolicy.newClientPolicy());
+        ExtensionConfig config = ExtensionConfig.parse("fragment;maxLength=4");
+        ext.setConfig(config);
+
+        ext.setNextIncomingFrames(capture);
+
+        // Quote
+        List<String> quote = new ArrayList<>();
+        quote.add("No amount of experimentation can ever prove me right;");
+        quote.add("a single experiment can prove me wrong.");
+        quote.add("-- Albert Einstein");
+
+        // Manually create frame and pass into extension
+        for (String q : quote)
+        {
+            Frame frame = new TextFrame().setPayload(q);
+            ext.incomingFrame(frame);
+        }
+
+        int len = quote.size();
+        capture.assertFrameCount(len);
+        capture.assertHasFrame(OpCode.TEXT, len);
+
+        String prefix;
+        int i = 0;
+        for (WebSocketFrame actual : capture.getFrames())
+        {
+            prefix = "Frame[" + i + "]";
+
+            Assert.assertThat(prefix + ".opcode", actual.getOpCode(), is(OpCode.TEXT));
+            Assert.assertThat(prefix + ".fin", actual.isFin(), is(true));
+            Assert.assertThat(prefix + ".rsv1", actual.isRsv1(), is(false));
+            Assert.assertThat(prefix + ".rsv2", actual.isRsv2(), is(false));
+            Assert.assertThat(prefix + ".rsv3", actual.isRsv3(), is(false));
+
+            ByteBuffer expected = BufferUtil.toBuffer(quote.get(i), StandardCharsets.UTF_8);
+            Assert.assertThat(prefix + ".payloadLength", actual.getPayloadLength(), is(expected.remaining()));
+            ByteBufferAssert.assertEquals(prefix + ".payload", expected, actual.getPayload().slice());
+            i++;
+        }
+    }
+
+    /**
+     * Incoming PING (Control Frame) should pass through extension unmodified
+     */
+    @Test
+    public void testIncomingPing()
+    {
+        IncomingFramesCapture capture = new IncomingFramesCapture();
+
+        FragmentExtension ext = new FragmentExtension();
+        ext.setBufferPool(bufferPool);
+        ext.setPolicy(WebSocketPolicy.newServerPolicy());
+        ExtensionConfig config = ExtensionConfig.parse("fragment;maxLength=4");
+        ext.setConfig(config);
+
+        ext.setNextIncomingFrames(capture);
+
+        String payload = "Are you there?";
+        Frame ping = new PingFrame().setPayload(payload);
+        ext.incomingFrame(ping);
+
+        capture.assertFrameCount(1);
+        capture.assertHasFrame(OpCode.PING, 1);
+        WebSocketFrame actual = capture.getFrames().poll();
+
+        Assert.assertThat("Frame.opcode", actual.getOpCode(), is(OpCode.PING));
+        Assert.assertThat("Frame.fin", actual.isFin(), is(true));
+        Assert.assertThat("Frame.rsv1", actual.isRsv1(), is(false));
+        Assert.assertThat("Frame.rsv2", actual.isRsv2(), is(false));
+        Assert.assertThat("Frame.rsv3", actual.isRsv3(), is(false));
+
+        ByteBuffer expected = BufferUtil.toBuffer(payload, StandardCharsets.UTF_8);
+        Assert.assertThat("Frame.payloadLength", actual.getPayloadLength(), is(expected.remaining()));
+        ByteBufferAssert.assertEquals("Frame.payload", expected, actual.getPayload().slice());
+    }
+
+    /**
+     * Verify that outgoing text frames are fragmented by the maxLength configuration.
+     */
+    @Test
+    public void testOutgoingFramesByMaxLength() throws IOException
+    {
+        OutgoingFramesCapture capture = new OutgoingFramesCapture();
+
+        FragmentExtension ext = new FragmentExtension();
+        ext.setBufferPool(bufferPool);
+        ext.setPolicy(WebSocketPolicy.newServerPolicy());
+        ExtensionConfig config = ExtensionConfig.parse("fragment;maxLength=20");
+        ext.setConfig(config);
+
+        ext.setNextOutgoingFrames(capture);
+
+        // Quote
+        List<String> quote = new ArrayList<>();
+        quote.add("No amount of experimentation can ever prove me right;");
+        quote.add("a single experiment can prove me wrong.");
+        quote.add("-- Albert Einstein");
+
+        // Write quote as separate frames
+        for (String section : quote)
+        {
+            Frame frame = new TextFrame().setPayload(section);
+            ext.outgoingFrame(frame, null, BatchMode.OFF);
+        }
+
+        // Expected Frames
+        List<WebSocketFrame> expectedFrames = new ArrayList<>();
+        expectedFrames.add(new TextFrame().setPayload("No amount of experim").setFin(false));
+        expectedFrames.add(new ContinuationFrame().setPayload("entation can ever pr").setFin(false));
+        expectedFrames.add(new ContinuationFrame().setPayload("ove me right;").setFin(true));
+
+        expectedFrames.add(new TextFrame().setPayload("a single experiment ").setFin(false));
+        expectedFrames.add(new ContinuationFrame().setPayload("can prove me wrong.").setFin(true));
+
+        expectedFrames.add(new TextFrame().setPayload("-- Albert Einstein").setFin(true));
+
+        // capture.dump();
+
+        int len = expectedFrames.size();
+        capture.assertFrameCount(len);
+
+        String prefix;
+        LinkedList<WebSocketFrame> frames = capture.getFrames();
+        for (int i = 0; i < len; i++)
+        {
+            prefix = "Frame[" + i + "]";
+            WebSocketFrame actualFrame = frames.get(i);
+            WebSocketFrame expectedFrame = expectedFrames.get(i);
+
+            // System.out.printf("actual: %s%n",actualFrame);
+            // System.out.printf("expect: %s%n",expectedFrame);
+
+            // Validate Frame
+            Assert.assertThat(prefix + ".opcode", actualFrame.getOpCode(), is(expectedFrame.getOpCode()));
+            Assert.assertThat(prefix + ".fin", actualFrame.isFin(), is(expectedFrame.isFin()));
+            Assert.assertThat(prefix + ".rsv1", actualFrame.isRsv1(), is(expectedFrame.isRsv1()));
+            Assert.assertThat(prefix + ".rsv2", actualFrame.isRsv2(), is(expectedFrame.isRsv2()));
+            Assert.assertThat(prefix + ".rsv3", actualFrame.isRsv3(), is(expectedFrame.isRsv3()));
+
+            // Validate Payload
+            ByteBuffer expectedData = expectedFrame.getPayload().slice();
+            ByteBuffer actualData = actualFrame.getPayload().slice();
+
+            Assert.assertThat(prefix + ".payloadLength", actualData.remaining(), is(expectedData.remaining()));
+            ByteBufferAssert.assertEquals(prefix + ".payload", expectedData, actualData);
+        }
+    }
+
+    /**
+     * Verify that outgoing text frames are fragmented by default configuration
+     */
+    @Test
+    public void testOutgoingFramesDefaultConfig() throws IOException
+    {
+        OutgoingFramesCapture capture = new OutgoingFramesCapture();
+
+        FragmentExtension ext = new FragmentExtension();
+        ext.setBufferPool(bufferPool);
+        ext.setPolicy(WebSocketPolicy.newServerPolicy());
+        ExtensionConfig config = ExtensionConfig.parse("fragment");
+        ext.setConfig(config);
+
+        ext.setNextOutgoingFrames(capture);
+
+        // Quote
+        List<String> quote = new ArrayList<>();
+        quote.add("No amount of experimentation can ever prove me right;");
+        quote.add("a single experiment can prove me wrong.");
+        quote.add("-- Albert Einstein");
+
+        // Write quote as separate frames
+        for (String section : quote)
+        {
+            Frame frame = new TextFrame().setPayload(section);
+            ext.outgoingFrame(frame, null, BatchMode.OFF);
+        }
+
+        // Expected Frames
+        List<WebSocketFrame> expectedFrames = new ArrayList<>();
+        expectedFrames.add(new TextFrame().setPayload("No amount of experimentation can ever prove me right;"));
+        expectedFrames.add(new TextFrame().setPayload("a single experiment can prove me wrong."));
+        expectedFrames.add(new TextFrame().setPayload("-- Albert Einstein"));
+
+        // capture.dump();
+
+        int len = expectedFrames.size();
+        capture.assertFrameCount(len);
+
+        String prefix;
+        LinkedList<WebSocketFrame> frames = capture.getFrames();
+        for (int i = 0; i < len; i++)
+        {
+            prefix = "Frame[" + i + "]";
+            WebSocketFrame actualFrame = frames.get(i);
+            WebSocketFrame expectedFrame = expectedFrames.get(i);
+
+            // Validate Frame
+            Assert.assertThat(prefix + ".opcode", actualFrame.getOpCode(), is(expectedFrame.getOpCode()));
+            Assert.assertThat(prefix + ".fin", actualFrame.isFin(), is(expectedFrame.isFin()));
+            Assert.assertThat(prefix + ".rsv1", actualFrame.isRsv1(), is(expectedFrame.isRsv1()));
+            Assert.assertThat(prefix + ".rsv2", actualFrame.isRsv2(), is(expectedFrame.isRsv2()));
+            Assert.assertThat(prefix + ".rsv3", actualFrame.isRsv3(), is(expectedFrame.isRsv3()));
+
+            // Validate Payload
+            ByteBuffer expectedData = expectedFrame.getPayload().slice();
+            ByteBuffer actualData = actualFrame.getPayload().slice();
+
+            Assert.assertThat(prefix + ".payloadLength", actualData.remaining(), is(expectedData.remaining()));
+            ByteBufferAssert.assertEquals(prefix + ".payload", expectedData, actualData);
+        }
+    }
+
+    /**
+     * Outgoing PING (Control Frame) should pass through extension unmodified
+     */
+    @Test
+    public void testOutgoingPing() throws IOException
+    {
+        OutgoingFramesCapture capture = new OutgoingFramesCapture();
+
+        FragmentExtension ext = new FragmentExtension();
+        ext.setBufferPool(bufferPool);
+        ext.setPolicy(WebSocketPolicy.newServerPolicy());
+        ExtensionConfig config = ExtensionConfig.parse("fragment;maxLength=4");
+        ext.setConfig(config);
+
+        ext.setNextOutgoingFrames(capture);
+
+        String payload = "Are you there?";
+        Frame ping = new PingFrame().setPayload(payload);
+
+        ext.outgoingFrame(ping, null, BatchMode.OFF);
+
+        capture.assertFrameCount(1);
+        capture.assertHasFrame(OpCode.PING, 1);
+
+        WebSocketFrame actual = capture.getFrames().getFirst();
+
+        Assert.assertThat("Frame.opcode", actual.getOpCode(), is(OpCode.PING));
+        Assert.assertThat("Frame.fin", actual.isFin(), is(true));
+        Assert.assertThat("Frame.rsv1", actual.isRsv1(), is(false));
+        Assert.assertThat("Frame.rsv2", actual.isRsv2(), is(false));
+        Assert.assertThat("Frame.rsv3", actual.isRsv3(), is(false));
+
+        ByteBuffer expected = BufferUtil.toBuffer(payload, StandardCharsets.UTF_8);
+        Assert.assertThat("Frame.payloadLength", actual.getPayloadLength(), is(expected.remaining()));
+        ByteBufferAssert.assertEquals("Frame.payload", expected, actual.getPayload().slice());
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/IdentityExtensionTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/IdentityExtensionTest.java
new file mode 100644
index 0000000..df581b4
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/IdentityExtensionTest.java
@@ -0,0 +1,101 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.extensions;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.websocket.api.BatchMode;
+import org.eclipse.jetty.websocket.api.extensions.Extension;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.common.OpCode;
+import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.extensions.identity.IdentityExtension;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
+import org.eclipse.jetty.websocket.common.test.ByteBufferAssert;
+import org.eclipse.jetty.websocket.common.test.IncomingFramesCapture;
+import org.eclipse.jetty.websocket.common.test.OutgoingFramesCapture;
+import org.junit.Assert;
+import org.junit.Test;
+
+import static org.hamcrest.Matchers.is;
+
+public class IdentityExtensionTest
+{
+    /**
+     * Verify that incoming frames are unmodified
+     */
+    @Test
+    public void testIncomingFrames()
+    {
+        IncomingFramesCapture capture = new IncomingFramesCapture();
+
+        Extension ext = new IdentityExtension();
+        ext.setNextIncomingFrames(capture);
+
+        Frame frame = new TextFrame().setPayload("hello");
+        ext.incomingFrame(frame);
+
+        capture.assertFrameCount(1);
+        capture.assertHasFrame(OpCode.TEXT, 1);
+        WebSocketFrame actual = capture.getFrames().poll();
+
+        Assert.assertThat("Frame.opcode", actual.getOpCode(), is(OpCode.TEXT));
+        Assert.assertThat("Frame.fin", actual.isFin(), is(true));
+        Assert.assertThat("Frame.rsv1", actual.isRsv1(), is(false));
+        Assert.assertThat("Frame.rsv2", actual.isRsv2(), is(false));
+        Assert.assertThat("Frame.rsv3", actual.isRsv3(), is(false));
+
+        ByteBuffer expected = BufferUtil.toBuffer("hello", StandardCharsets.UTF_8);
+        Assert.assertThat("Frame.payloadLength", actual.getPayloadLength(), is(expected.remaining()));
+        ByteBufferAssert.assertEquals("Frame.payload", expected, actual.getPayload().slice());
+    }
+
+    /**
+     * Verify that outgoing frames are unmodified
+     */
+    @Test
+    public void testOutgoingFrames() throws IOException
+    {
+        OutgoingFramesCapture capture = new OutgoingFramesCapture();
+
+        Extension ext = new IdentityExtension();
+        ext.setNextOutgoingFrames(capture);
+
+        Frame frame = new TextFrame().setPayload("hello");
+        ext.outgoingFrame(frame, null, BatchMode.OFF);
+
+        capture.assertFrameCount(1);
+        capture.assertHasFrame(OpCode.TEXT, 1);
+
+        WebSocketFrame actual = capture.getFrames().getFirst();
+
+        Assert.assertThat("Frame.opcode", actual.getOpCode(), is(OpCode.TEXT));
+        Assert.assertThat("Frame.fin", actual.isFin(), is(true));
+        Assert.assertThat("Frame.rsv1", actual.isRsv1(), is(false));
+        Assert.assertThat("Frame.rsv2", actual.isRsv2(), is(false));
+        Assert.assertThat("Frame.rsv3", actual.isRsv3(), is(false));
+
+        ByteBuffer expected = BufferUtil.toBuffer("hello", StandardCharsets.UTF_8);
+        Assert.assertThat("Frame.payloadLength", actual.getPayloadLength(), is(expected.remaining()));
+        ByteBufferAssert.assertEquals("Frame.payload", expected, actual.getPayload().slice());
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/compress/CapturedHexPayloads.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/compress/CapturedHexPayloads.java
new file mode 100644
index 0000000..f0572b7
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/compress/CapturedHexPayloads.java
@@ -0,0 +1,49 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.extensions.compress;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jetty.websocket.api.BatchMode;
+import org.eclipse.jetty.websocket.api.WriteCallback;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.api.extensions.OutgoingFrames;
+import org.eclipse.jetty.websocket.common.util.Hex;
+
+public class CapturedHexPayloads implements OutgoingFrames
+{
+    private List<String> captured = new ArrayList<>();
+
+    @Override
+    public void outgoingFrame(Frame frame, WriteCallback callback, BatchMode batchMode)
+    {
+        String hexPayload = Hex.asHex(frame.getPayload());
+        captured.add(hexPayload);
+        if (callback != null)
+        {
+            callback.writeSuccess();
+        }
+    }
+
+    public List<String> getCaptured()
+    {
+        return captured;
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/compress/DeflateFrameExtensionTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/compress/DeflateFrameExtensionTest.java
new file mode 100644
index 0000000..7de53b5
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/compress/DeflateFrameExtensionTest.java
@@ -0,0 +1,437 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.extensions.compress;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.Collections;
+import java.util.List;
+import java.util.Random;
+import java.util.zip.Deflater;
+import java.util.zip.Inflater;
+
+import org.eclipse.jetty.io.RuntimeIOException;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.websocket.api.BatchMode;
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.api.WriteCallback;
+import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.api.extensions.IncomingFrames;
+import org.eclipse.jetty.websocket.api.extensions.OutgoingFrames;
+import org.eclipse.jetty.websocket.common.Generator;
+import org.eclipse.jetty.websocket.common.OpCode;
+import org.eclipse.jetty.websocket.common.Parser;
+import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.extensions.AbstractExtensionTest;
+import org.eclipse.jetty.websocket.common.extensions.ExtensionTool.Tester;
+import org.eclipse.jetty.websocket.common.frames.BinaryFrame;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
+import org.eclipse.jetty.websocket.common.test.ByteBufferAssert;
+import org.eclipse.jetty.websocket.common.test.IncomingFramesCapture;
+import org.eclipse.jetty.websocket.common.test.LeakTrackingBufferPoolRule;
+import org.eclipse.jetty.websocket.common.test.OutgoingNetworkBytesCapture;
+import org.eclipse.jetty.websocket.common.test.UnitParser;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+
+import static org.hamcrest.Matchers.contains;
+import static org.hamcrest.Matchers.greaterThan;
+import static org.hamcrest.Matchers.is;
+
+public class DeflateFrameExtensionTest extends AbstractExtensionTest
+{
+    @Rule
+    public LeakTrackingBufferPoolRule bufferPool = new LeakTrackingBufferPoolRule("Test");
+
+    private void assertIncoming(byte[] raw, String... expectedTextDatas)
+    {
+        WebSocketPolicy policy = WebSocketPolicy.newClientPolicy();
+
+        DeflateFrameExtension ext = new DeflateFrameExtension();
+        ext.setBufferPool(bufferPool);
+        ext.setPolicy(policy);
+
+        ExtensionConfig config = ExtensionConfig.parse("deflate-frame");
+        ext.setConfig(config);
+
+        // Setup capture of incoming frames
+        IncomingFramesCapture capture = new IncomingFramesCapture();
+
+        // Wire up stack
+        ext.setNextIncomingFrames(capture);
+
+        Parser parser = new UnitParser(policy);
+        parser.configureFromExtensions(Collections.singletonList(ext));
+        parser.setIncomingFramesHandler(ext);
+
+        parser.parse(ByteBuffer.wrap(raw));
+
+        int len = expectedTextDatas.length;
+        capture.assertFrameCount(len);
+        capture.assertHasFrame(OpCode.TEXT, len);
+
+        int i = 0;
+        for (WebSocketFrame actual : capture.getFrames())
+        {
+            String prefix = "Frame[" + i + "]";
+            Assert.assertThat(prefix + ".opcode", actual.getOpCode(), is(OpCode.TEXT));
+            Assert.assertThat(prefix + ".fin", actual.isFin(), is(true));
+            Assert.assertThat(prefix + ".rsv1", actual.isRsv1(), is(false)); // RSV1 should be unset at this point
+            Assert.assertThat(prefix + ".rsv2", actual.isRsv2(), is(false));
+            Assert.assertThat(prefix + ".rsv3", actual.isRsv3(), is(false));
+
+            ByteBuffer expected = BufferUtil.toBuffer(expectedTextDatas[i], StandardCharsets.UTF_8);
+            Assert.assertThat(prefix + ".payloadLength", actual.getPayloadLength(), is(expected.remaining()));
+            ByteBufferAssert.assertEquals(prefix + ".payload", expected, actual.getPayload().slice());
+            i++;
+        }
+    }
+
+    private void assertOutgoing(String text, String expectedHex) throws IOException
+    {
+        WebSocketPolicy policy = WebSocketPolicy.newClientPolicy();
+
+        DeflateFrameExtension ext = new DeflateFrameExtension();
+        ext.setBufferPool(bufferPool);
+        ext.setPolicy(policy);
+
+        ExtensionConfig config = ExtensionConfig.parse("deflate-frame");
+        ext.setConfig(config);
+
+        Generator generator = new Generator(policy, bufferPool, true);
+        generator.configureFromExtensions(Collections.singletonList(ext));
+
+        OutgoingNetworkBytesCapture capture = new OutgoingNetworkBytesCapture(generator);
+        ext.setNextOutgoingFrames(capture);
+
+        Frame frame = new TextFrame().setPayload(text);
+        ext.outgoingFrame(frame, null, BatchMode.OFF);
+
+        capture.assertBytes(0, expectedHex);
+    }
+
+    @Test
+    public void testBlockheadClient_HelloThere()
+    {
+        Tester tester = serverExtensions.newTester("deflate-frame");
+
+        tester.assertNegotiated("deflate-frame");
+
+        tester.parseIncomingHex(// Captured from Blockhead Client - "Hello" then "There" via unit test
+                "c18700000000f248cdc9c90700", // "Hello"
+                "c187000000000ac9482d4a0500" // "There"
+        );
+
+        tester.assertHasFrames("Hello", "There");
+    }
+
+    @Test
+    public void testChrome20_Hello()
+    {
+        Tester tester = serverExtensions.newTester("deflate-frame");
+
+        tester.assertNegotiated("deflate-frame");
+
+        tester.parseIncomingHex(// Captured from Chrome 20.x - "Hello" (sent from browser)
+                "c187832b5c11716391d84a2c5c" // "Hello"
+        );
+
+        tester.assertHasFrames("Hello");
+    }
+
+    @Test
+    public void testChrome20_HelloThere()
+    {
+        Tester tester = serverExtensions.newTester("deflate-frame");
+
+        tester.assertNegotiated("deflate-frame");
+
+        tester.parseIncomingHex(// Captured from Chrome 20.x - "Hello" then "There" (sent from browser)
+                "c1877b1971db8951bc12b21e71", // "Hello"
+                "c18759edc8f4532480d913e8c8" // There
+        );
+
+        tester.assertHasFrames("Hello", "There");
+    }
+
+    @Test
+    public void testChrome20_Info()
+    {
+        Tester tester = serverExtensions.newTester("deflate-frame");
+
+        tester.assertNegotiated("deflate-frame");
+
+        tester.parseIncomingHex(// Captured from Chrome 20.x - "info:" (sent from browser)
+                "c187ca4def7f0081a4b47d4fef" // example payload 
+        );
+
+        tester.assertHasFrames("info:");
+    }
+
+    @Test
+    public void testChrome20_TimeTime()
+    {
+        Tester tester = serverExtensions.newTester("deflate-frame");
+
+        tester.assertNegotiated("deflate-frame");
+
+        tester.parseIncomingHex(// Captured from Chrome 20.x - "time:" then "time:" once more (sent from browser)
+                "c18782467424a88fb869374474", // "time:"
+                "c1853cfda17f16fcb07f3c" // "time:"
+        );
+
+        tester.assertHasFrames("time:", "time:");
+    }
+
+    @Test
+    public void testPyWebSocket_TimeTimeTime()
+    {
+        Tester tester = serverExtensions.newTester("deflate-frame");
+
+        tester.assertNegotiated("deflate-frame");
+
+        tester.parseIncomingHex(// Captured from Pywebsocket (r781) - "time:" sent 3 times.
+                "c1876b100104" + "41d9cd49de1201", // "time:"
+                "c1852ae3ff01" + "00e2ee012a", // "time:"
+                "c18435558caa" + "37468caa" // "time:"
+        );
+
+        tester.assertHasFrames("time:", "time:", "time:");
+    }
+
+    @Test
+    public void testCompress_TimeTimeTime()
+    {
+        // What pywebsocket produces for "time:", "time:", "time:"
+        String expected[] = new String[]
+                {"2AC9CC4DB50200", "2A01110000", "02130000"};
+
+        // Lets see what we produce
+        CapturedHexPayloads capture = new CapturedHexPayloads();
+        DeflateFrameExtension ext = new DeflateFrameExtension();
+        init(ext);
+        ext.setNextOutgoingFrames(capture);
+
+        ext.outgoingFrame(new TextFrame().setPayload("time:"), null, BatchMode.OFF);
+        ext.outgoingFrame(new TextFrame().setPayload("time:"), null, BatchMode.OFF);
+        ext.outgoingFrame(new TextFrame().setPayload("time:"), null, BatchMode.OFF);
+
+        List<String> actual = capture.getCaptured();
+
+        Assert.assertThat("Compressed Payloads", actual, contains(expected));
+    }
+
+    private void init(DeflateFrameExtension ext)
+    {
+        ext.setConfig(new ExtensionConfig(ext.getName()));
+        ext.setBufferPool(bufferPool);
+    }
+
+    @Test
+    public void testDeflateBasics() throws Exception
+    {
+        // Setup deflater basics
+        Deflater compressor = new Deflater(Deflater.BEST_COMPRESSION, true);
+        compressor.setStrategy(Deflater.DEFAULT_STRATEGY);
+
+        // Text to compress
+        String text = "info:";
+        byte uncompressed[] = StringUtil.getUtf8Bytes(text);
+
+        // Prime the compressor
+        compressor.reset();
+        compressor.setInput(uncompressed, 0, uncompressed.length);
+        compressor.finish();
+
+        // Perform compression
+        ByteBuffer outbuf = ByteBuffer.allocate(64);
+        BufferUtil.clearToFill(outbuf);
+
+        while (!compressor.finished())
+        {
+            byte out[] = new byte[64];
+            int len = compressor.deflate(out, 0, out.length, Deflater.SYNC_FLUSH);
+            if (len > 0)
+            {
+                outbuf.put(out, 0, len);
+            }
+        }
+        compressor.end();
+
+        BufferUtil.flipToFlush(outbuf, 0);
+        byte compressed[] = BufferUtil.toArray(outbuf);
+        // Clear the BFINAL bit that has been set by the compressor.end() call.
+        // In the real implementation we never end() the compressor.
+        compressed[0] &= 0xFE;
+
+        String actual = TypeUtil.toHexString(compressed);
+        String expected = "CaCc4bCbB70200"; // what pywebsocket produces
+
+        Assert.assertThat("Compressed data", actual, is(expected));
+    }
+
+    @Test
+    public void testGeneratedTwoFrames() throws IOException
+    {
+        WebSocketPolicy policy = WebSocketPolicy.newClientPolicy();
+
+        DeflateFrameExtension ext = new DeflateFrameExtension();
+        ext.setBufferPool(bufferPool);
+        ext.setPolicy(policy);
+        ext.setConfig(new ExtensionConfig(ext.getName()));
+
+        Generator generator = new Generator(policy, bufferPool, true);
+        generator.configureFromExtensions(Collections.singletonList(ext));
+
+        OutgoingNetworkBytesCapture capture = new OutgoingNetworkBytesCapture(generator);
+        ext.setNextOutgoingFrames(capture);
+
+        ext.outgoingFrame(new TextFrame().setPayload("Hello"), null, BatchMode.OFF);
+        ext.outgoingFrame(new TextFrame().setPayload("There"), null, BatchMode.OFF);
+
+        capture.assertBytes(0, "c107f248cdc9c90700");
+    }
+
+    @Test
+    public void testInflateBasics() throws Exception
+    {
+        // should result in "info:" text if properly inflated
+        byte rawbuf[] = TypeUtil.fromHexString("CaCc4bCbB70200"); // what pywebsocket produces
+        // byte rawbuf[] = TypeUtil.fromHexString("CbCc4bCbB70200"); // what java produces
+
+        Inflater inflater = new Inflater(true);
+        inflater.reset();
+        inflater.setInput(rawbuf, 0, rawbuf.length);
+
+        byte outbuf[] = new byte[64];
+        int len = inflater.inflate(outbuf);
+        inflater.end();
+        Assert.assertThat("Inflated length", len, greaterThan(4));
+
+        String actual = StringUtil.toUTF8String(outbuf, 0, len);
+        Assert.assertThat("Inflated text", actual, is("info:"));
+    }
+
+    @Test
+    public void testPyWebSocketServer_Hello()
+    {
+        // Captured from PyWebSocket - "Hello" (echo from server)
+        byte rawbuf[] = TypeUtil.fromHexString("c107f248cdc9c90700");
+        assertIncoming(rawbuf, "Hello");
+    }
+
+    @Test
+    public void testPyWebSocketServer_Long()
+    {
+        // Captured from PyWebSocket - Long Text (echo from server)
+        byte rawbuf[] = TypeUtil.fromHexString("c1421cca410a80300c44d1abccce9df7" + "f018298634d05631138ab7b7b8fdef1f" + "dc0282e2061d575a45f6f2686bab25e1"
+                + "3fb7296fa02b5885eb3b0379c394f461" + "98cafd03");
+        assertIncoming(rawbuf, "It's a big enough umbrella but it's always me that ends up getting wet.");
+    }
+
+    @Test
+    public void testPyWebSocketServer_Medium()
+    {
+        // Captured from PyWebSocket - "stackoverflow" (echo from server)
+        byte rawbuf[] = TypeUtil.fromHexString("c10f2a2e494ccece2f4b2d4acbc92f0700");
+        assertIncoming(rawbuf, "stackoverflow");
+    }
+
+    /**
+     * Make sure that the server generated compressed form for "Hello" is consistent with what PyWebSocket creates.
+     */
+    @Test
+    public void testServerGeneratedHello() throws IOException
+    {
+        assertOutgoing("Hello", "c107f248cdc9c90700");
+    }
+
+    /**
+     * Make sure that the server generated compressed form for "There" is consistent with what PyWebSocket creates.
+     */
+    @Test
+    public void testServerGeneratedThere() throws IOException
+    {
+        assertOutgoing("There", "c1070ac9482d4a0500");
+    }
+
+    @Test
+    public void testCompressAndDecompressBigPayload() throws Exception
+    {
+        byte[] input = new byte[1024 * 1024];
+        // Make them not compressible.
+        new Random().nextBytes(input);
+
+        DeflateFrameExtension clientExtension = new DeflateFrameExtension();
+        clientExtension.setBufferPool(bufferPool);
+        clientExtension.setPolicy(WebSocketPolicy.newClientPolicy());
+        clientExtension.setConfig(ExtensionConfig.parse("deflate-frame"));
+
+        final DeflateFrameExtension serverExtension = new DeflateFrameExtension();
+        serverExtension.setBufferPool(bufferPool);
+        serverExtension.setPolicy(WebSocketPolicy.newServerPolicy());
+        serverExtension.setConfig(ExtensionConfig.parse("deflate-frame"));
+
+        // Chain the next element to decompress.
+        clientExtension.setNextOutgoingFrames(new OutgoingFrames()
+        {
+            @Override
+            public void outgoingFrame(Frame frame, WriteCallback callback, BatchMode batchMode)
+            {
+                serverExtension.incomingFrame(frame);
+                callback.writeSuccess();
+            }
+        });
+
+        final ByteArrayOutputStream result = new ByteArrayOutputStream(input.length);
+        serverExtension.setNextIncomingFrames(new IncomingFrames()
+        {
+            @Override
+            public void incomingFrame(Frame frame)
+            {
+                try
+                {
+                    result.write(BufferUtil.toArray(frame.getPayload()));
+                }
+                catch (IOException x)
+                {
+                    throw new RuntimeIOException(x);
+                }
+            }
+
+            @Override
+            public void incomingError(Throwable t)
+            {
+            }
+        });
+
+        BinaryFrame frame = new BinaryFrame();
+        frame.setPayload(input);
+        frame.setFin(true);
+        clientExtension.outgoingFrame(frame, null, BatchMode.OFF);
+
+        Assert.assertArrayEquals(input, result.toByteArray());
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/compress/PerMessageDeflateExtensionTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/compress/PerMessageDeflateExtensionTest.java
new file mode 100644
index 0000000..b39d4c8
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/extensions/compress/PerMessageDeflateExtensionTest.java
@@ -0,0 +1,409 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.extensions.compress;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.websocket.api.BatchMode;
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.common.OpCode;
+import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.extensions.AbstractExtensionTest;
+import org.eclipse.jetty.websocket.common.extensions.ExtensionTool.Tester;
+import org.eclipse.jetty.websocket.common.frames.ContinuationFrame;
+import org.eclipse.jetty.websocket.common.frames.PingFrame;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
+import org.eclipse.jetty.websocket.common.test.ByteBufferAssert;
+import org.eclipse.jetty.websocket.common.test.IncomingFramesCapture;
+import org.eclipse.jetty.websocket.common.test.LeakTrackingBufferPoolRule;
+import org.eclipse.jetty.websocket.common.test.OutgoingFramesCapture;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+
+import static org.hamcrest.Matchers.is;
+
+/**
+ * Client side behavioral tests for permessage-deflate extension.
+ * <p/>
+ * See: http://tools.ietf.org/html/draft-ietf-hybi-permessage-compression-15
+ */
+public class PerMessageDeflateExtensionTest extends AbstractExtensionTest
+{
+    @Rule
+    public LeakTrackingBufferPoolRule bufferPool = new LeakTrackingBufferPoolRule("Test");
+
+    /**
+     * Decode payload example as seen in draft-ietf-hybi-permessage-compression-15.
+     * <p/>
+     * Section 8.2.3.4: Using a DEFLATE Block with BFINAL Set to 1
+     */
+    @Test
+    public void testDraft15_DeflateBlockWithBFinal1()
+    {
+        Tester tester = clientExtensions.newTester("permessage-deflate");
+
+        tester.assertNegotiated("permessage-deflate");
+
+        tester.parseIncomingHex(// 1 message
+                "0xc1 0x08", // header
+                "0xf3 0x48 0xcd 0xc9 0xc9 0x07 0x00 0x00" // example payload 
+        );
+
+        tester.assertHasFrames("Hello");
+    }
+
+    /**
+     * Decode payload example as seen in draft-ietf-hybi-permessage-compression-15.
+     * <p/>
+     * Section 8.2.3.3: Using a DEFLATE Block with No Compression
+     */
+    @Test
+    public void testDraft15_DeflateBlockWithNoCompression()
+    {
+        Tester tester = clientExtensions.newTester("permessage-deflate");
+
+        tester.assertNegotiated("permessage-deflate");
+
+        tester.parseIncomingHex(// 1 message / no compression
+                "0xc1 0x0b 0x00 0x05 0x00 0xfa 0xff 0x48 0x65 0x6c 0x6c 0x6f 0x00" // example frame
+        );
+
+        tester.assertHasFrames("Hello");
+    }
+
+    /**
+     * Decode payload example as seen in draft-ietf-hybi-permessage-compression-15.
+     * <p/>
+     * Section 8.2.3.1: A message compressed using 1 compressed DEFLATE block
+     */
+    @Test
+    public void testDraft15_Hello_UnCompressedBlock()
+    {
+        Tester tester = clientExtensions.newTester("permessage-deflate");
+
+        tester.assertNegotiated("permessage-deflate");
+
+        tester.parseIncomingHex(//basic, 1 block, compressed with 0 compression level (aka, uncompressed).
+                "0xc1 0x07 0xf2 0x48 0xcd 0xc9 0xc9 0x07 0x00" // example frame
+        );
+
+        tester.assertHasFrames("Hello");
+    }
+
+    /**
+     * Decode payload example as seen in draft-ietf-hybi-permessage-compression-15.
+     * <p/>
+     * Section 8.2.3.1: A message compressed using 1 compressed DEFLATE block (with fragmentation)
+     */
+    @Test
+    public void testDraft15_Hello_UnCompressedBlock_Fragmented()
+    {
+        Tester tester = clientExtensions.newTester("permessage-deflate");
+
+        tester.assertNegotiated("permessage-deflate");
+
+        tester.parseIncomingHex(// basic, 1 block, compressed with 0 compression level (aka, uncompressed).
+                // Fragment 1
+                "0x41 0x03 0xf2 0x48 0xcd",
+                // Fragment 2
+                "0x80 0x04 0xc9 0xc9 0x07 0x00");
+
+        tester.assertHasFrames(
+                new TextFrame().setPayload("He").setFin(false),
+                new ContinuationFrame().setPayload("llo").setFin(true));
+    }
+
+    /**
+     * Decode payload example as seen in draft-ietf-hybi-permessage-compression-15.
+     * <p/>
+     * Section 8.2.3.2: Sharing LZ77 Sliding Window
+     */
+    @Test
+    public void testDraft15_SharingL77SlidingWindow_ContextTakeover()
+    {
+        Tester tester = clientExtensions.newTester("permessage-deflate");
+
+        tester.assertNegotiated("permessage-deflate");
+
+        tester.parseIncomingHex( // context takeover (2 messages)
+                // message 1
+                "0xc1 0x07", // (HEADER added for this test)
+                "0xf2 0x48 0xcd 0xc9 0xc9 0x07 0x00",
+                // message 2
+                "0xc1 0x07", // (HEADER added for this test)
+                "0xf2 0x48 0xcd 0xc9 0xc9 0x07 0x00");
+
+        tester.assertHasFrames("Hello", "Hello");
+    }
+
+    /**
+     * Decode payload example as seen in draft-ietf-hybi-permessage-compression-15.
+     * <p/>
+     * Section 8.2.3.2: Sharing LZ77 Sliding Window
+     */
+    @Test
+    public void testDraft15_SharingL77SlidingWindow_NoContextTakeover()
+    {
+        Tester tester = clientExtensions.newTester("permessage-deflate");
+
+        tester.assertNegotiated("permessage-deflate");
+
+        tester.parseIncomingHex(// 2 message, shared LZ77 window
+                // message 1
+                "0xc1 0x07", // (HEADER added for this test)
+                "0xf2 0x48 0xcd 0xc9 0xc9 0x07 0x00",
+                // message 2
+                "0xc1 0x05", // (HEADER added for this test)
+                "0xf2 0x00 0x11 0x00 0x00"
+        );
+
+        tester.assertHasFrames("Hello", "Hello");
+    }
+
+    /**
+     * Decode payload example as seen in draft-ietf-hybi-permessage-compression-15.
+     * <p/>
+     * Section 8.2.3.5: Two DEFLATE Blocks in 1 Message
+     */
+    @Test
+    public void testDraft15_TwoDeflateBlocksOneMessage()
+    {
+        Tester tester = clientExtensions.newTester("permessage-deflate");
+
+        tester.assertNegotiated("permessage-deflate");
+
+        tester.parseIncomingHex(// 1 message, 1 frame, 2 deflate blocks
+                "0xc1 0x0d", // (HEADER added for this test)
+                "0xf2 0x48 0x05 0x00 0x00 0x00 0xff 0xff 0xca 0xc9 0xc9 0x07 0x00"
+        );
+
+        tester.assertHasFrames("Hello");
+    }
+
+    /**
+     * Incoming PING (Control Frame) should pass through extension unmodified
+     */
+    @Test
+    public void testIncomingPing()
+    {
+        PerMessageDeflateExtension ext = new PerMessageDeflateExtension();
+        ext.setBufferPool(bufferPool);
+        ext.setPolicy(WebSocketPolicy.newServerPolicy());
+        ExtensionConfig config = ExtensionConfig.parse("permessage-deflate");
+        ext.setConfig(config);
+
+        // Setup capture of incoming frames
+        IncomingFramesCapture capture = new IncomingFramesCapture();
+
+        // Wire up stack
+        ext.setNextIncomingFrames(capture);
+
+        String payload = "Are you there?";
+        Frame ping = new PingFrame().setPayload(payload);
+        ext.incomingFrame(ping);
+
+        capture.assertFrameCount(1);
+        capture.assertHasFrame(OpCode.PING, 1);
+        WebSocketFrame actual = capture.getFrames().poll();
+
+        Assert.assertThat("Frame.opcode", actual.getOpCode(), is(OpCode.PING));
+        Assert.assertThat("Frame.fin", actual.isFin(), is(true));
+        Assert.assertThat("Frame.rsv1", actual.isRsv1(), is(false));
+        Assert.assertThat("Frame.rsv2", actual.isRsv2(), is(false));
+        Assert.assertThat("Frame.rsv3", actual.isRsv3(), is(false));
+
+        ByteBuffer expected = BufferUtil.toBuffer(payload, StandardCharsets.UTF_8);
+        Assert.assertThat("Frame.payloadLength", actual.getPayloadLength(), is(expected.remaining()));
+        ByteBufferAssert.assertEquals("Frame.payload", expected, actual.getPayload().slice());
+    }
+
+    /**
+     * Verify that incoming uncompressed frames are properly passed through
+     */
+    @Test
+    public void testIncomingUncompressedFrames()
+    {
+        PerMessageDeflateExtension ext = new PerMessageDeflateExtension();
+        ext.setBufferPool(bufferPool);
+        ext.setPolicy(WebSocketPolicy.newServerPolicy());
+        ExtensionConfig config = ExtensionConfig.parse("permessage-deflate");
+        ext.setConfig(config);
+
+        // Setup capture of incoming frames
+        IncomingFramesCapture capture = new IncomingFramesCapture();
+
+        // Wire up stack
+        ext.setNextIncomingFrames(capture);
+
+        // Quote
+        List<String> quote = new ArrayList<>();
+        quote.add("No amount of experimentation can ever prove me right;");
+        quote.add("a single experiment can prove me wrong.");
+        quote.add("-- Albert Einstein");
+
+        // leave frames as-is, no compression, and pass into extension
+        for (String q : quote)
+        {
+            TextFrame frame = new TextFrame().setPayload(q);
+            frame.setRsv1(false); // indication to extension that frame is not compressed (ie: a normal frame)
+            ext.incomingFrame(frame);
+        }
+
+        int len = quote.size();
+        capture.assertFrameCount(len);
+        capture.assertHasFrame(OpCode.TEXT, len);
+
+        String prefix;
+        int i = 0;
+        for (WebSocketFrame actual : capture.getFrames())
+        {
+            prefix = "Frame[" + i + "]";
+
+            Assert.assertThat(prefix + ".opcode", actual.getOpCode(), is(OpCode.TEXT));
+            Assert.assertThat(prefix + ".fin", actual.isFin(), is(true));
+            Assert.assertThat(prefix + ".rsv1", actual.isRsv1(), is(false));
+            Assert.assertThat(prefix + ".rsv2", actual.isRsv2(), is(false));
+            Assert.assertThat(prefix + ".rsv3", actual.isRsv3(), is(false));
+
+            ByteBuffer expected = BufferUtil.toBuffer(quote.get(i), StandardCharsets.UTF_8);
+            Assert.assertThat(prefix + ".payloadLength", actual.getPayloadLength(), is(expected.remaining()));
+            ByteBufferAssert.assertEquals(prefix + ".payload", expected, actual.getPayload().slice());
+            i++;
+        }
+    }
+
+    /**
+     * Outgoing PING (Control Frame) should pass through extension unmodified
+     */
+    @Test
+    public void testOutgoingPing() throws IOException
+    {
+        PerMessageDeflateExtension ext = new PerMessageDeflateExtension();
+        ext.setBufferPool(bufferPool);
+        ext.setPolicy(WebSocketPolicy.newServerPolicy());
+        ExtensionConfig config = ExtensionConfig.parse("permessage-deflate");
+        ext.setConfig(config);
+
+        // Setup capture of outgoing frames
+        OutgoingFramesCapture capture = new OutgoingFramesCapture();
+
+        // Wire up stack
+        ext.setNextOutgoingFrames(capture);
+
+        String payload = "Are you there?";
+        Frame ping = new PingFrame().setPayload(payload);
+
+        ext.outgoingFrame(ping, null, BatchMode.OFF);
+
+        capture.assertFrameCount(1);
+        capture.assertHasFrame(OpCode.PING, 1);
+
+        WebSocketFrame actual = capture.getFrames().getFirst();
+
+        Assert.assertThat("Frame.opcode", actual.getOpCode(), is(OpCode.PING));
+        Assert.assertThat("Frame.fin", actual.isFin(), is(true));
+        Assert.assertThat("Frame.rsv1", actual.isRsv1(), is(false));
+        Assert.assertThat("Frame.rsv2", actual.isRsv2(), is(false));
+        Assert.assertThat("Frame.rsv3", actual.isRsv3(), is(false));
+
+        ByteBuffer expected = BufferUtil.toBuffer(payload, StandardCharsets.UTF_8);
+        Assert.assertThat("Frame.payloadLength", actual.getPayloadLength(), is(expected.remaining()));
+        ByteBufferAssert.assertEquals("Frame.payload", expected, actual.getPayload().slice());
+    }
+
+    @Test
+    public void testPyWebSocket_Client_NoContextTakeover_ThreeOra()
+    {
+        Tester tester = clientExtensions.newTester("permessage-deflate; client_max_window_bits; client_no_context_takeover");
+
+        tester.assertNegotiated("permessage-deflate");
+
+        // Captured from Pywebsocket (r790) - 3 messages with similar parts.
+
+        tester.parseIncomingHex( // context takeover (3 messages)
+                "c1 09 0a c9 2f 4a 0c 01  62 00 00", // ToraTora
+                "c1 0b 72 2c c9 2f 4a 74  cb 01 12 00 00", // AtoraFlora
+                "c1 0b 0a c8 c8 c9 2f 4a  0c 01 62 00 00" // PhloraTora
+        );
+
+        tester.assertHasFrames("ToraTora", "AtoraFlora", "PhloraTora");
+    }
+
+    @Test
+    public void testPyWebSocket_Client_ToraToraTora()
+    {
+        Tester tester = clientExtensions.newTester("permessage-deflate; client_max_window_bits");
+
+        tester.assertNegotiated("permessage-deflate");
+
+        // Captured from Pywebsocket (r790) - "tora" sent 3 times.
+
+        tester.parseIncomingHex( // context takeover (3 messages)
+                "c1 06 2a c9 2f 4a 04 00", // tora 1
+                "c1 05 2a 01 62 00 00", // tora 2
+                "c1 04 02 61 00 00" // tora 3
+        );
+
+        tester.assertHasFrames("tora", "tora", "tora");
+    }
+
+    @Test
+    public void testPyWebSocket_Server_NoContextTakeover_ThreeOra()
+    {
+        Tester tester = serverExtensions.newTester("permessage-deflate; client_max_window_bits; client_no_context_takeover");
+
+        tester.assertNegotiated("permessage-deflate");
+
+        // Captured from Pywebsocket (r790) - 3 messages with similar parts.
+
+        tester.parseIncomingHex( // context takeover (3 messages)
+                "c1 89 88 bc 1b b1 82 75  34 fb 84 bd 79 b1 88", // ToraTora
+                "c1 8b 50 86 88 b2 22 aa  41 9d 1a f2 43 b3 42 86 88", // AtoraFlora
+                "c1 8b e2 3e 05 53 e8 f6  cd 9a cd 74 09 52 80 3e 05" // PhloraTora
+        );
+
+        tester.assertHasFrames("ToraTora", "AtoraFlora", "PhloraTora");
+    }
+
+    @Test
+    public void testPyWebSocket_Server_ToraToraTora()
+    {
+        Tester tester = serverExtensions.newTester("permessage-deflate; client_max_window_bits");
+
+        tester.assertNegotiated("permessage-deflate");
+
+        // Captured from Pywebsocket (r790) - "tora" sent 3 times.
+
+        tester.parseIncomingHex( // context takeover (3 messages)
+                "c1 86 69 39 fe 91 43 f0  d1 db 6d 39", // tora 1
+                "c1 85 2d f3 eb 96 07 f2  89 96 2d", // tora 2
+                "c1 84 53 ad a5 34 51 cc  a5 34" // tora 3
+        );
+
+        tester.assertHasFrames("tora", "tora", "tora");
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/IOStateTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/IOStateTest.java
new file mode 100644
index 0000000..2bffba4
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/IOStateTest.java
@@ -0,0 +1,245 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.io;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
+import java.util.LinkedList;
+
+import org.eclipse.jetty.websocket.api.StatusCode;
+import org.eclipse.jetty.websocket.common.CloseInfo;
+import org.eclipse.jetty.websocket.common.ConnectionState;
+import org.junit.Test;
+
+public class IOStateTest
+{
+    public static class StateTracker implements IOState.ConnectionStateListener
+    {
+        private LinkedList<ConnectionState> transitions = new LinkedList<>();
+
+        public void assertTransitions(ConnectionState ...states)
+        {
+            assertThat("Transitions.count",transitions.size(),is(states.length));
+            if (states.length > 0)
+            {
+                int len = states.length;
+                for (int i = 0; i < len; i++)
+                {
+                    assertThat("Transitions[" + i + "]",transitions.get(i),is(states[i]));
+                }
+            }
+        }
+
+        public LinkedList<ConnectionState> getTransitions()
+        {
+            return transitions;
+        }
+
+        @Override
+        public void onConnectionStateChange(ConnectionState state)
+        {
+            transitions.add(state);
+        }
+    }
+
+    private void assertCleanClose(IOState state, boolean expected)
+    {
+        assertThat("State.cleanClose",state.wasCleanClose(),is(expected));
+    }
+
+    private void assertInputAvailable(IOState state, boolean available)
+    {
+        assertThat("State.inputAvailable",state.isInputAvailable(),is(available));
+    }
+
+    private void assertLocalInitiated(IOState state, boolean expected)
+    {
+        assertThat("State.localCloseInitiated",state.wasLocalCloseInitiated(),is(expected));
+    }
+
+    private void assertOutputAvailable(IOState state, boolean available)
+    {
+        assertThat("State.outputAvailable",state.isOutputAvailable(),is(available));
+    }
+
+    private void assertRemoteInitiated(IOState state, boolean expected)
+    {
+        assertThat("State.remoteCloseInitiated",state.wasRemoteCloseInitiated(),is(expected));
+    }
+
+    private void assertState(IOState state, ConnectionState expectedState)
+    {
+        assertThat("State",state.getConnectionState(),is(expectedState));
+    }
+
+    @Test
+    public void testConnectAbnormalClose()
+    {
+        IOState state = new IOState();
+        StateTracker tracker = new StateTracker();
+        state.addListener(tracker);
+        assertState(state,ConnectionState.CONNECTING);
+
+        // connect
+        state.onConnected();
+        assertInputAvailable(state,false);
+        assertOutputAvailable(state,true);
+
+        // open
+        state.onOpened();
+        assertInputAvailable(state,true);
+        assertOutputAvailable(state,true);
+
+        // disconnect
+        state.onAbnormalClose(new CloseInfo(StatusCode.NO_CLOSE,"Oops"));
+
+        assertInputAvailable(state,false);
+        assertOutputAvailable(state,false);
+        tracker.assertTransitions(ConnectionState.CONNECTED,ConnectionState.OPEN,ConnectionState.CLOSED);
+        assertState(state,ConnectionState.CLOSED);
+
+        // not clean
+        assertCleanClose(state,false);
+        assertLocalInitiated(state,false);
+        assertRemoteInitiated(state,false);
+    }
+
+    @Test
+    public void testConnectCloseLocalInitiated()
+    {
+        IOState state = new IOState();
+        StateTracker tracker = new StateTracker();
+        state.addListener(tracker);
+        assertState(state,ConnectionState.CONNECTING);
+
+        // connect
+        state.onConnected();
+        assertInputAvailable(state,false);
+        assertOutputAvailable(state,true);
+
+        // open
+        state.onOpened();
+        assertInputAvailable(state,true);
+        assertOutputAvailable(state,true);
+
+        // close (local initiated)
+        state.onCloseLocal(new CloseInfo(StatusCode.NORMAL,"Hi"));
+        assertInputAvailable(state,true);
+        assertOutputAvailable(state,false);
+        assertState(state,ConnectionState.CLOSING);
+
+        // close (remote response)
+        state.onCloseRemote(new CloseInfo(StatusCode.NORMAL,"Hi"));
+
+        assertInputAvailable(state,false);
+        assertOutputAvailable(state,false);
+        tracker.assertTransitions(ConnectionState.CONNECTED,ConnectionState.OPEN,ConnectionState.CLOSING,ConnectionState.CLOSED);
+        assertState(state,ConnectionState.CLOSED);
+
+        // not clean
+        assertCleanClose(state,true);
+        assertLocalInitiated(state,true);
+        assertRemoteInitiated(state,false);
+    }
+
+    @Test
+    public void testConnectCloseRemoteInitiated()
+    {
+        IOState state = new IOState();
+        StateTracker tracker = new StateTracker();
+        state.addListener(tracker);
+        assertState(state,ConnectionState.CONNECTING);
+
+        // connect
+        state.onConnected();
+        assertInputAvailable(state,false);
+        assertOutputAvailable(state,true);
+
+        // open
+        state.onOpened();
+        assertInputAvailable(state,true);
+        assertOutputAvailable(state,true);
+
+        // close (remote initiated)
+        state.onCloseRemote(new CloseInfo(StatusCode.NORMAL,"Hi"));
+        assertInputAvailable(state,false);
+        assertOutputAvailable(state,true);
+        assertState(state,ConnectionState.CLOSING);
+
+        // close (local response)
+        state.onCloseLocal(new CloseInfo(StatusCode.NORMAL,"Hi"));
+
+        assertInputAvailable(state,false);
+        assertOutputAvailable(state,false);
+        tracker.assertTransitions(ConnectionState.CONNECTED,ConnectionState.OPEN,ConnectionState.CLOSING,ConnectionState.CLOSED);
+        assertState(state,ConnectionState.CLOSED);
+
+        // not clean
+        assertCleanClose(state,true);
+        assertLocalInitiated(state,false);
+        assertRemoteInitiated(state,true);
+    }
+
+    @Test
+    public void testConnectFailure()
+    {
+        IOState state = new IOState();
+        StateTracker tracker = new StateTracker();
+        state.addListener(tracker);
+        assertState(state,ConnectionState.CONNECTING);
+
+        // fail upgrade
+        state.onFailedUpgrade();
+
+        tracker.assertTransitions(ConnectionState.CLOSED);
+        assertState(state,ConnectionState.CLOSED);
+
+        assertInputAvailable(state,false);
+        assertOutputAvailable(state,false);
+
+        // not clean
+        assertCleanClose(state,false);
+        assertLocalInitiated(state,false);
+        assertRemoteInitiated(state,false);
+    }
+
+    @Test
+    public void testInit()
+    {
+        IOState state = new IOState();
+        StateTracker tracker = new StateTracker();
+        state.addListener(tracker);
+        assertState(state,ConnectionState.CONNECTING);
+
+        // do nothing
+
+        tracker.assertTransitions();
+        assertState(state,ConnectionState.CONNECTING);
+
+        // not connected yet
+        assertInputAvailable(state,false);
+        assertOutputAvailable(state,false);
+
+        // no close yet
+        assertCleanClose(state,false);
+        assertLocalInitiated(state,false);
+        assertRemoteInitiated(state,false);
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/LocalWebSocketConnection.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/LocalWebSocketConnection.java
new file mode 100644
index 0000000..5321d1a
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/LocalWebSocketConnection.java
@@ -0,0 +1,254 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.io;
+
+import java.net.InetSocketAddress;
+import java.util.concurrent.Executor;
+
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.ExecutorThreadPool;
+import org.eclipse.jetty.websocket.api.BatchMode;
+import org.eclipse.jetty.websocket.api.StatusCode;
+import org.eclipse.jetty.websocket.api.SuspendToken;
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.api.WriteCallback;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.api.extensions.IncomingFrames;
+import org.eclipse.jetty.websocket.common.CloseInfo;
+import org.eclipse.jetty.websocket.common.ConnectionState;
+import org.eclipse.jetty.websocket.common.LogicalConnection;
+import org.eclipse.jetty.websocket.common.WebSocketSession;
+import org.eclipse.jetty.websocket.common.io.IOState.ConnectionStateListener;
+import org.junit.rules.TestName;
+
+public class LocalWebSocketConnection implements LogicalConnection, IncomingFrames, ConnectionStateListener
+{
+    private static final Logger LOG = Log.getLogger(LocalWebSocketConnection.class);
+    private final String id;
+    private final ByteBufferPool bufferPool;
+    private final Executor executor;
+    private WebSocketPolicy policy = WebSocketPolicy.newServerPolicy();
+    private IncomingFrames incoming;
+    private IOState ioState = new IOState();
+
+    public LocalWebSocketConnection(ByteBufferPool bufferPool)
+    {
+        this("anon",bufferPool);
+    }
+
+    public LocalWebSocketConnection(String id, ByteBufferPool bufferPool)
+    {
+        this.id = id;
+        this.bufferPool = bufferPool;
+        this.executor = new ExecutorThreadPool();
+        this.ioState.addListener(this);
+    }
+
+    public LocalWebSocketConnection(TestName testname, ByteBufferPool bufferPool)
+    {
+        this(testname.getMethodName(),bufferPool);
+    }
+    
+    @Override
+    public Executor getExecutor()
+    {
+        return executor;
+    }
+
+    @Override
+    public void close()
+    {
+        close(StatusCode.NORMAL,null);
+    }
+
+    @Override
+    public void close(int statusCode, String reason)
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("close({}, {})",statusCode,reason);
+        CloseInfo close = new CloseInfo(statusCode,reason);
+        ioState.onCloseLocal(close);
+    }
+
+    public void connect()
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("connect()");
+        ioState.onConnected();
+    }
+
+    @Override
+    public void disconnect()
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("disconnect()");
+    }
+
+    @Override
+    public ByteBufferPool getBufferPool()
+    {
+        return this.bufferPool;
+    }
+
+    @Override
+    public long getIdleTimeout()
+    {
+        return 0;
+    }
+
+    public IncomingFrames getIncoming()
+    {
+        return incoming;
+    }
+
+    @Override
+    public IOState getIOState()
+    {
+        return ioState;
+    }
+
+    @Override
+    public InetSocketAddress getLocalAddress()
+    {
+        return null;
+    }
+
+    @Override
+    public long getMaxIdleTimeout()
+    {
+        return 0;
+    }
+
+    @Override
+    public WebSocketPolicy getPolicy()
+    {
+        return policy;
+    }
+
+    @Override
+    public InetSocketAddress getRemoteAddress()
+    {
+        return null;
+    }
+
+    @Override
+    public WebSocketSession getSession()
+    {
+        return null;
+    }
+
+    @Override
+    public void incomingError(Throwable e)
+    {
+        incoming.incomingError(e);
+    }
+
+    @Override
+    public void incomingFrame(Frame frame)
+    {
+        incoming.incomingFrame(frame);
+    }
+
+    @Override
+    public boolean isOpen()
+    {
+        return getIOState().isOpen();
+    }
+
+    @Override
+    public boolean isReading()
+    {
+        return false;
+    }
+
+    @Override
+    public void onConnectionStateChange(ConnectionState state)
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("Connection State Change: {}",state);
+        switch (state)
+        {
+            case CLOSED:
+                this.disconnect();
+                break;
+            case CLOSING:
+                if (ioState.wasRemoteCloseInitiated())
+                {
+                    // send response close frame
+                    CloseInfo close = ioState.getCloseInfo();
+                    LOG.debug("write close frame: {}",close);
+                    ioState.onCloseLocal(close);
+                }
+            default:
+                break;
+        }
+    }
+
+    public void open()
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("open()");
+        ioState.onOpened();
+    }
+
+    @Override
+    public void outgoingFrame(Frame frame, WriteCallback callback, BatchMode batchMode)
+    {
+    }
+
+    @Override
+    public void resume()
+    {
+    }
+
+    @Override
+    public void setMaxIdleTimeout(long ms)
+    {
+    }
+
+    @Override
+    public void setNextIncomingFrames(IncomingFrames incoming)
+    {
+        this.incoming = incoming;
+    }
+
+    public void setPolicy(WebSocketPolicy policy)
+    {
+        this.policy = policy;
+    }
+
+    @Override
+    public void setSession(WebSocketSession session)
+    {
+    }
+
+    @Override
+    public SuspendToken suspend()
+    {
+        return null;
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s[%s]",LocalWebSocketConnection.class.getSimpleName(),id);
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/LocalWebSocketSession.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/LocalWebSocketSession.java
new file mode 100644
index 0000000..843c64e
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/LocalWebSocketSession.java
@@ -0,0 +1,58 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.io;
+
+import java.net.URI;
+
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.websocket.common.WebSocketSession;
+import org.eclipse.jetty.websocket.common.events.EventDriver;
+import org.eclipse.jetty.websocket.common.test.OutgoingFramesCapture;
+import org.junit.rules.TestName;
+
+public class LocalWebSocketSession extends WebSocketSession
+{
+    private String id;
+    private OutgoingFramesCapture outgoingCapture;
+
+    public LocalWebSocketSession(TestName testname, EventDriver driver, ByteBufferPool bufferPool)
+    {
+        super(URI.create("ws://localhost/LocalWebSocketSesssion/" + testname.getMethodName()),driver,new LocalWebSocketConnection(testname,bufferPool));
+        this.id = testname.getMethodName();
+        outgoingCapture = new OutgoingFramesCapture();
+        setOutgoingHandler(outgoingCapture);
+    }
+
+    @Override
+    public void dispatch(Runnable runnable)
+    {
+        runnable.run();
+    }
+
+    public OutgoingFramesCapture getOutgoingCapture()
+    {
+        return outgoingCapture;
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s[%s]",LocalWebSocketSession.class.getSimpleName(),id);
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/TrackingCallback.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/TrackingCallback.java
new file mode 100644
index 0000000..c0f6778
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/TrackingCallback.java
@@ -0,0 +1,81 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.io;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.websocket.api.WriteCallback;
+
+/**
+ * Tracking Callback for testing how the callbacks are used.
+ */
+public class TrackingCallback implements Callback, WriteCallback
+{
+    private AtomicInteger called = new AtomicInteger();
+    private boolean success = false;
+    private Throwable failure = null;
+
+    @Override
+    public void failed(Throwable x)
+    {
+        this.called.incrementAndGet();
+        this.success = false;
+        this.failure = x;
+    }
+
+    @Override
+    public void succeeded()
+    {
+        this.called.incrementAndGet();
+        this.success = false;
+    }
+
+    public Throwable getFailure()
+    {
+        return failure;
+    }
+
+    public boolean isSuccess()
+    {
+        return success;
+    }
+
+    public boolean isCalled()
+    {
+        return called.get() >= 1;
+    }
+
+    public int getCallCount()
+    {
+        return called.get();
+    }
+
+    @Override
+    public void writeFailed(Throwable x)
+    {
+        failed(x);
+    }
+
+    @Override
+    public void writeSuccess()
+    {
+        succeeded();
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/http/HttpResponseHeaderParserTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/http/HttpResponseHeaderParserTest.java
new file mode 100644
index 0000000..877b7a3
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/http/HttpResponseHeaderParserTest.java
@@ -0,0 +1,196 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.io.http;
+
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.hamcrest.Matchers.nullValue;
+import static org.junit.Assert.assertThat;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jetty.toolchain.test.TestTracker;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.StringUtil;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class HttpResponseHeaderParserTest
+{
+    @Rule
+    public TestTracker tt = new TestTracker();
+
+    private void appendUtf8(ByteBuffer buf, String line)
+    {
+        buf.put(ByteBuffer.wrap(StringUtil.getUtf8Bytes(line)));
+    }
+
+    @Test
+    public void testParseNotFound()
+    {
+        StringBuilder resp = new StringBuilder();
+        resp.append("HTTP/1.1 404 Not Found\r\n");
+        resp.append("Date: Fri, 26 Apr 2013 21:43:08 GMT\r\n");
+        resp.append("Content-Type: text/html; charset=ISO-8859-1\r\n");
+        resp.append("Cache-Control: must-revalidate,no-cache,no-store\r\n");
+        resp.append("Content-Length: 38\r\n");
+        resp.append("Server: Jetty(9.0.0.v20130308)\r\n");
+        resp.append("\r\n");
+        // and some body content
+        resp.append("What you are looking for is not here\r\n");
+
+        ByteBuffer buf = BufferUtil.toBuffer(resp.toString(),StandardCharsets.UTF_8);
+
+        HttpResponseParseCapture capture = new HttpResponseParseCapture();
+        HttpResponseHeaderParser parser = new HttpResponseHeaderParser(capture);
+        assertThat("Parser.parse",parser.parse(buf),notNullValue());
+        assertThat("Response.statusCode",capture.getStatusCode(),is(404));
+        assertThat("Response.statusReason",capture.getStatusReason(),is("Not Found"));
+        assertThat("Response.headers[Content-Length]",capture.getHeader("Content-Length"),is("38"));
+
+        assertThat("Response.remainingBuffer",capture.getRemainingBuffer().remaining(),is(38));
+    }
+
+    @Test
+    public void testParseRealWorldResponse()
+    {
+        // Arbitrary Http Response Headers seen in the wild.
+        // Request URI -> https://ssl.google-analytics.com/__utm.gif
+        List<String> expected = new ArrayList<>();
+        expected.add("HTTP/1.0 200 OK");
+        expected.add("Date: Thu, 09 Aug 2012 16:16:39 GMT");
+        expected.add("Content-Length: 35");
+        expected.add("X-Content-Type-Options: nosniff");
+        expected.add("Pragma: no-cache");
+        expected.add("Expires: Wed, 19 Apr 2000 11:43:00 GMT");
+        expected.add("Last-Modified: Wed, 21 Jan 2004 19:51:30 GMT");
+        expected.add("Content-Type: image/gif");
+        expected.add("Cache-Control: private, no-cache, no-cache=Set-Cookie, proxy-revalidate");
+        expected.add("Age: 518097");
+        expected.add("Server: GFE/2.0");
+        expected.add("Connection: Keep-Alive");
+        expected.add("");
+
+        // Prepare Buffer
+        ByteBuffer buf = ByteBuffer.allocate(512);
+        for (String line : expected)
+        {
+            appendUtf8(buf,line + "\r\n");
+        }
+
+        BufferUtil.flipToFlush(buf,0);
+
+        // Parse Buffer
+        HttpResponseParseCapture capture = new HttpResponseParseCapture();
+        HttpResponseHeaderParser parser = new HttpResponseHeaderParser(capture);
+        assertThat("Parser.parse",parser.parse(buf),notNullValue());
+
+        Assert.assertThat("Response.statusCode",capture.getStatusCode(),is(200));
+        Assert.assertThat("Response.statusReason",capture.getStatusReason(),is("OK"));
+
+        Assert.assertThat("Response.header[age]",capture.getHeader("age"),is("518097"));
+    }
+
+    @Test
+    public void testParseRealWorldResponse_SmallBuffers()
+    {
+        // Arbitrary Http Response Headers seen in the wild.
+        // Request URI -> https://ssl.google-analytics.com/__utm.gif
+        List<String> expected = new ArrayList<>();
+        expected.add("HTTP/1.0 200 OK");
+        expected.add("Date: Thu, 09 Aug 2012 16:16:39 GMT");
+        expected.add("Content-Length: 35");
+        expected.add("X-Content-Type-Options: nosniff");
+        expected.add("Pragma: no-cache");
+        expected.add("Expires: Wed, 19 Apr 2000 11:43:00 GMT");
+        expected.add("Last-Modified: Wed, 21 Jan 2004 19:51:30 GMT");
+        expected.add("Content-Type: image/gif");
+        expected.add("Cache-Control: private, no-cache, no-cache=Set-Cookie, proxy-revalidate");
+        expected.add("Age: 518097");
+        expected.add("Server: GFE/2.0");
+        expected.add("Connection: Keep-Alive");
+        expected.add("");
+
+        // Prepare Buffer
+        ByteBuffer buf = ByteBuffer.allocate(512);
+        for (String line : expected)
+        {
+            appendUtf8(buf,line + "\r\n");
+        }
+        BufferUtil.flipToFlush(buf,0);
+
+        // Prepare small buffers to simulate a slow read/fill/parse from the network
+        ByteBuffer small1 = buf.slice();
+        ByteBuffer small2 = buf.slice();
+        ByteBuffer small3 = buf.slice();
+
+        small1.limit(50);
+        small2.position(50);
+        small2.limit(70);
+        small3.position(70);
+
+        // Parse Buffer
+        HttpResponseParseCapture capture = new HttpResponseParseCapture();
+        HttpResponseHeaderParser parser = new HttpResponseHeaderParser(capture);
+        assertThat("Parser.parse",parser.parse(buf),notNullValue());
+
+        // Parse small 1
+        Assert.assertThat("Small 1",parser.parse(small1),nullValue());
+
+        // Parse small 2
+        Assert.assertThat("Small 2",parser.parse(small2),nullValue());
+
+        // Parse small 3
+        Assert.assertThat("Small 3",parser.parse(small3),notNullValue());
+
+        Assert.assertThat("Response.statusCode",capture.getStatusCode(),is(200));
+        Assert.assertThat("Response.statusReason",capture.getStatusReason(),is("OK"));
+
+        Assert.assertThat("Response.header[age]",capture.getHeader("age"),is("518097"));
+    }
+
+    @Test
+    public void testParseUpgrade()
+    {
+        // Example from RFC6455 - Section 1.2 (Protocol Overview)
+        StringBuilder resp = new StringBuilder();
+        resp.append("HTTP/1.1 101 Switching Protocols\r\n");
+        resp.append("Upgrade: websocket\r\n");
+        resp.append("Connection: Upgrade\r\n");
+        resp.append("Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n");
+        resp.append("Sec-WebSocket-Protocol: chat\r\n");
+        resp.append("\r\n");
+
+        ByteBuffer buf = BufferUtil.toBuffer(resp.toString(),StandardCharsets.UTF_8);
+
+        HttpResponseParseCapture capture = new HttpResponseParseCapture();
+        HttpResponseHeaderParser parser = new HttpResponseHeaderParser(capture);
+        assertThat("Parser.parse",parser.parse(buf),notNullValue());
+        assertThat("Response.statusCode",capture.getStatusCode(),is(101));
+        assertThat("Response.statusReason",capture.getStatusReason(),is("Switching Protocols"));
+        assertThat("Response.headers[Upgrade]",capture.getHeader("Upgrade"),is("websocket"));
+        assertThat("Response.headers[Connection]",capture.getHeader("Connection"),is("Upgrade"));
+
+        assertThat("Buffer.remaining",buf.remaining(),is(0));
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/http/HttpResponseParseCapture.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/http/HttpResponseParseCapture.java
new file mode 100644
index 0000000..b72a957
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/http/HttpResponseParseCapture.java
@@ -0,0 +1,75 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.io.http;
+
+import java.nio.ByteBuffer;
+import java.util.Map;
+import java.util.TreeMap;
+
+public class HttpResponseParseCapture implements HttpResponseHeaderParseListener
+{
+    private int statusCode;
+    private String statusReason;
+    private Map<String, String> headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+    private ByteBuffer remainingBuffer;
+
+    @Override
+    public void addHeader(String name, String value)
+    {
+        headers.put(name,value);
+    }
+
+    public String getHeader(String name)
+    {
+        return headers.get(name);
+    }
+
+    public ByteBuffer getRemainingBuffer()
+    {
+        return remainingBuffer;
+    }
+
+    public int getStatusCode()
+    {
+        return statusCode;
+    }
+
+    public String getStatusReason()
+    {
+        return statusReason;
+    }
+
+    @Override
+    public void setRemainingBuffer(ByteBuffer copy)
+    {
+        this.remainingBuffer = copy;
+    }
+
+    @Override
+    public void setStatusCode(int code)
+    {
+        this.statusCode = code;
+    }
+
+    @Override
+    public void setStatusReason(String reason)
+    {
+        this.statusReason = reason;
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/payload/DeMaskProcessorTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/payload/DeMaskProcessorTest.java
new file mode 100644
index 0000000..8bd9a05
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/payload/DeMaskProcessorTest.java
@@ -0,0 +1,109 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.io.payload;
+
+import static org.hamcrest.Matchers.is;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
+import org.eclipse.jetty.websocket.common.test.ByteBufferAssert;
+import org.eclipse.jetty.websocket.common.test.UnitGenerator;
+import org.eclipse.jetty.websocket.common.util.Hex;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class DeMaskProcessorTest
+{
+    private static final Logger LOG = Log.getLogger(DeMaskProcessorTest.class);
+
+    @Test
+    public void testDeMaskText()
+    {
+        // Use a string that is not multiple of 4 in length to test if/else branches in DeMaskProcessor
+        String message = "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF01";
+
+        WebSocketFrame frame = new TextFrame().setPayload(message);
+        frame.setMask(TypeUtil.fromHexString("11223344"));
+
+        ByteBuffer buf = UnitGenerator.generate(frame);
+        LOG.debug("Buf: {}",BufferUtil.toDetailString(buf));
+        ByteBuffer payload = buf.slice();
+        payload.position(6); // where payload starts
+        LOG.debug("Payload: {}",BufferUtil.toDetailString(payload));
+
+        DeMaskProcessor demask = new DeMaskProcessor();
+        demask.reset(frame);
+        demask.process(payload);
+
+        ByteBufferAssert.assertEquals("DeMasked Text Payload",message,payload);
+    }
+
+    @Test
+    public void testDeMaskTextSliced()
+    {
+        final byte msgChar = '*';
+        final int messageSize = 25;
+
+        byte message[] = new byte[messageSize];
+        Arrays.fill(message,msgChar);
+
+        TextFrame frame = new TextFrame();
+        frame.setPayload(ByteBuffer.wrap(message));
+        frame.setMask(Hex.asByteArray("11223344"));
+
+        ByteBuffer buf = UnitGenerator.generate(frame);
+        LOG.debug("Buf: {}",BufferUtil.toDetailString(buf));
+        ByteBuffer payload = buf.slice();
+        payload.position(6); // where payload starts
+        
+        LOG.debug("Payload: {}",BufferUtil.toDetailString(payload));
+        LOG.debug("Pre-Processed: {}",Hex.asHex(payload));
+
+        DeMaskProcessor demask = new DeMaskProcessor();
+        demask.reset(frame);
+        ByteBuffer slice1 = payload.slice();
+        ByteBuffer slice2 = payload.slice();
+
+        // slice at non-multiple of 4, but also where last buffer remaining
+        // is more than 4.
+        int slicePoint = 7;
+        slice1.limit(slicePoint);
+        slice2.position(slicePoint);
+
+        Assert.assertThat("Slices are setup right",slice1.remaining() + slice2.remaining(),is(messageSize));
+
+        demask.process(slice1);
+        demask.process(slice2);
+        
+        LOG.debug("Post-Processed: {}",Hex.asHex(payload));
+
+        Assert.assertThat("Payload.remaining",payload.remaining(),is(messageSize));
+        for (int i = payload.position(); i < payload.limit(); i++)
+        {
+            Assert.assertThat("payload[" + i + "]",payload.get(i),is(msgChar));
+        }
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/message/DummySocket.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/message/DummySocket.java
new file mode 100644
index 0000000..35f27f5
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/message/DummySocket.java
@@ -0,0 +1,29 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.message;
+
+import org.eclipse.jetty.websocket.api.WebSocketAdapter;
+
+/**
+ * Do nothing Dummy Socket, used in testing.
+ */
+public class DummySocket extends WebSocketAdapter
+{
+    /* intentionally empty */
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/message/MessageDebug.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/message/MessageDebug.java
new file mode 100644
index 0000000..25d0dd1
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/message/MessageDebug.java
@@ -0,0 +1,60 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.message;
+
+import java.nio.ByteBuffer;
+
+public class MessageDebug
+{
+    public static String toDetailHint(byte[] data, int offset, int len)
+    {
+        StringBuilder buf = new StringBuilder();
+        ByteBuffer buffer = ByteBuffer.wrap(data,offset,len);
+
+        buf.append("byte[").append(data.length);
+        buf.append("](o=").append(offset);
+        buf.append(",len=").append(len);
+
+        buf.append(")<<<");
+        for (int i = buffer.position(); i < buffer.limit(); i++)
+        {
+            char c = (char)buffer.get(i);
+            if ((c >= ' ') && (c <= 127))
+            {
+                buf.append(c);
+            }
+            else if ((c == '\r') || (c == '\n'))
+            {
+                buf.append('|');
+            }
+            else
+            {
+                buf.append('\ufffd');
+            }
+            if ((i == (buffer.position() + 16)) && (buffer.limit() > (buffer.position() + 32)))
+            {
+                buf.append("...");
+                i = buffer.limit() - 16;
+            }
+        }
+        buf.append(">>>");
+
+        return buf.toString();
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/message/MessageInputStreamTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/message/MessageInputStreamTest.java
new file mode 100644
index 0000000..54de967
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/message/MessageInputStreamTest.java
@@ -0,0 +1,235 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.message;
+
+import static org.hamcrest.Matchers.*;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.websocket.common.test.LeakTrackingBufferPoolRule;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+
+public class MessageInputStreamTest
+{
+    @Rule
+    public TestName testname = new TestName();
+
+    @Rule
+    public LeakTrackingBufferPoolRule bufferPool = new LeakTrackingBufferPoolRule("Test");
+
+    @Test(timeout=10000)
+    public void testBasicAppendRead() throws IOException
+    {
+        try (MessageInputStream stream = new MessageInputStream())
+        {
+            // Append a single message (simple, short)
+            ByteBuffer payload = BufferUtil.toBuffer("Hello World",StandardCharsets.UTF_8);
+            System.out.printf("payload = %s%n",BufferUtil.toDetailString(payload));
+            boolean fin = true;
+            stream.appendFrame(payload,fin);
+
+            // Read entire message it from the stream.
+            byte buf[] = new byte[32];
+            int len = stream.read(buf);
+            String message = new String(buf,0,len,StandardCharsets.UTF_8);
+
+            // Test it
+            Assert.assertThat("Message",message,is("Hello World"));
+        }
+    }
+
+    @Test(timeout=5000)
+    public void testBlockOnRead() throws Exception
+    {
+        try (MessageInputStream stream = new MessageInputStream())
+        {
+            final AtomicBoolean hadError = new AtomicBoolean(false);
+            final CountDownLatch startLatch = new CountDownLatch(1);
+
+            // This thread fills the stream (from the "worker" thread)
+            // But slowly (intentionally).
+            new Thread(new Runnable()
+            {
+                @Override
+                public void run()
+                {
+                    try
+                    {
+                        startLatch.countDown();
+                        boolean fin = false;
+                        TimeUnit.MILLISECONDS.sleep(200);
+                        stream.appendFrame(BufferUtil.toBuffer("Saved",StandardCharsets.UTF_8),fin);
+                        TimeUnit.MILLISECONDS.sleep(200);
+                        stream.appendFrame(BufferUtil.toBuffer(" by ",StandardCharsets.UTF_8),fin);
+                        fin = true;
+                        TimeUnit.MILLISECONDS.sleep(200);
+                        stream.appendFrame(BufferUtil.toBuffer("Zero",StandardCharsets.UTF_8),fin);
+                    }
+                    catch (IOException | InterruptedException e)
+                    {
+                        hadError.set(true);
+                        e.printStackTrace(System.err);
+                    }
+                }
+            }).start();
+
+            // wait for thread to start
+            startLatch.await();
+            
+            // Read it from the stream.
+            byte buf[] = new byte[32];
+            int len = stream.read(buf);
+            String message = new String(buf,0,len,StandardCharsets.UTF_8);
+
+            // Test it
+            Assert.assertThat("Error when appending",hadError.get(),is(false));
+            Assert.assertThat("Message",message,is("Saved by Zero"));
+        }
+    }
+
+    @Test(timeout=10000)
+    public void testBlockOnReadInitial() throws IOException
+    {
+        try (MessageInputStream stream = new MessageInputStream())
+        {
+            final AtomicBoolean hadError = new AtomicBoolean(false);
+
+            new Thread(new Runnable()
+            {
+                @Override
+                public void run()
+                {
+                    try
+                    {
+                        boolean fin = true;
+                        // wait for a little bit before populating buffers
+                        TimeUnit.MILLISECONDS.sleep(400);
+                        stream.appendFrame(BufferUtil.toBuffer("I will conquer",StandardCharsets.UTF_8),fin);
+                    }
+                    catch (IOException | InterruptedException e)
+                    {
+                        hadError.set(true);
+                        e.printStackTrace(System.err);
+                    }
+                }
+            }).start();
+
+            // Read byte from stream.
+            int b = stream.read();
+            // Should be a byte, blocking till byte received.
+
+            // Test it
+            Assert.assertThat("Error when appending",hadError.get(),is(false));
+            Assert.assertThat("Initial byte",b,is((int)'I'));
+        }
+    }
+
+    @Test(timeout=10000)
+    public void testReadByteNoBuffersClosed() throws IOException
+    {
+        try (MessageInputStream stream = new MessageInputStream())
+        {
+            final AtomicBoolean hadError = new AtomicBoolean(false);
+
+            new Thread(new Runnable()
+            {
+                @Override
+                public void run()
+                {
+                    try
+                    {
+                        // wait for a little bit before sending input closed
+                        TimeUnit.MILLISECONDS.sleep(400);
+                        stream.messageComplete();
+                    }
+                    catch (InterruptedException e)
+                    {
+                        hadError.set(true);
+                        e.printStackTrace(System.err);
+                    }
+                }
+            }).start();
+
+            // Read byte from stream.
+            int b = stream.read();
+            // Should be a -1, indicating the end of the stream.
+
+            // Test it
+            Assert.assertThat("Error when appending",hadError.get(),is(false));
+            Assert.assertThat("Initial byte",b,is(-1));
+        }
+    }
+    
+    @Test(timeout=10000)
+    public void testAppendEmptyPayloadRead() throws IOException
+    {
+        try (MessageInputStream stream = new MessageInputStream())
+        {
+            // Append parts of message
+            ByteBuffer msg1 = BufferUtil.toBuffer("Hello ",StandardCharsets.UTF_8);
+            ByteBuffer msg2 = ByteBuffer.allocate(0); // what is being tested
+            ByteBuffer msg3 = BufferUtil.toBuffer("World",StandardCharsets.UTF_8);
+            
+            stream.appendFrame(msg1,false);
+            stream.appendFrame(msg2,false);
+            stream.appendFrame(msg3,true);
+
+            // Read entire message it from the stream.
+            byte buf[] = new byte[32];
+            int len = stream.read(buf);
+            String message = new String(buf,0,len,StandardCharsets.UTF_8);
+
+            // Test it
+            Assert.assertThat("Message",message,is("Hello World"));
+        }
+    }
+    
+    @Test(timeout=10000)
+    public void testAppendNullPayloadRead() throws IOException
+    {
+        try (MessageInputStream stream = new MessageInputStream())
+        {
+            // Append parts of message
+            ByteBuffer msg1 = BufferUtil.toBuffer("Hello ",StandardCharsets.UTF_8);
+            ByteBuffer msg2 = null; // what is being tested
+            ByteBuffer msg3 = BufferUtil.toBuffer("World",StandardCharsets.UTF_8);
+            
+            stream.appendFrame(msg1,false);
+            stream.appendFrame(msg2,false);
+            stream.appendFrame(msg3,true);
+
+            // Read entire message it from the stream.
+            byte buf[] = new byte[32];
+            int len = stream.read(buf);
+            String message = new String(buf,0,len,StandardCharsets.UTF_8);
+
+            // Test it
+            Assert.assertThat("Message",message,is("Hello World"));
+        }
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/message/MessageOutputStreamTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/message/MessageOutputStreamTest.java
new file mode 100644
index 0000000..868a20d
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/message/MessageOutputStreamTest.java
@@ -0,0 +1,139 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.message;
+
+import static org.hamcrest.Matchers.allOf;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.is;
+
+import java.util.Arrays;
+
+import org.eclipse.jetty.toolchain.test.TestTracker;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.api.extensions.OutgoingFrames;
+import org.eclipse.jetty.websocket.common.events.EventDriver;
+import org.eclipse.jetty.websocket.common.events.EventDriverFactory;
+import org.eclipse.jetty.websocket.common.io.FramePipes;
+import org.eclipse.jetty.websocket.common.io.LocalWebSocketSession;
+import org.eclipse.jetty.websocket.common.test.LeakTrackingBufferPoolRule;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+
+public class MessageOutputStreamTest
+{
+    private static final Logger LOG = Log.getLogger(MessageOutputStreamTest.class);
+
+    @Rule
+    public TestTracker testtracker = new TestTracker();
+
+    @Rule
+    public TestName testname = new TestName();
+
+    @Rule
+    public LeakTrackingBufferPoolRule bufferPool = new LeakTrackingBufferPoolRule("Test");
+
+    private WebSocketPolicy policy;
+    private TrackingSocket socket;
+    private LocalWebSocketSession session;
+
+    @After
+    public void closeSession()
+    {
+        session.close();
+    }
+
+    @Before
+    public void setupSession()
+    {
+        policy = WebSocketPolicy.newServerPolicy();
+        policy.setInputBufferSize(1024);
+        policy.setMaxBinaryMessageBufferSize(1024);
+
+        // Event Driver factory
+        EventDriverFactory factory = new EventDriverFactory(policy);
+
+        // local socket
+        EventDriver driver = factory.wrap(new TrackingSocket("local"));
+
+        // remote socket
+        socket = new TrackingSocket("remote");
+        OutgoingFrames socketPipe = FramePipes.to(factory.wrap(socket));
+
+        session = new LocalWebSocketSession(testname,driver,bufferPool);
+
+        session.setPolicy(policy);
+        // talk to our remote socket
+        session.setOutgoingHandler(socketPipe);
+        // open connection
+        session.open();
+    }
+
+    @Test
+    public void testMultipleWrites() throws Exception
+    {
+        try (MessageOutputStream stream = new MessageOutputStream(session))
+        {
+            stream.write("Hello".getBytes("UTF-8"));
+            stream.write(" ".getBytes("UTF-8"));
+            stream.write("World".getBytes("UTF-8"));
+        }
+
+        Assert.assertThat("Socket.messageQueue.size",socket.messageQueue.size(),is(1));
+        String msg = socket.messageQueue.poll();
+        Assert.assertThat("Message",msg,allOf(containsString("byte[11]"),containsString("Hello World")));
+    }
+
+    @Test
+    public void testSingleWrite() throws Exception
+    {
+        try (MessageOutputStream stream = new MessageOutputStream(session))
+        {
+            stream.write("Hello World".getBytes("UTF-8"));
+        }
+
+        Assert.assertThat("Socket.messageQueue.size",socket.messageQueue.size(),is(1));
+        String msg = socket.messageQueue.poll();
+        Assert.assertThat("Message",msg,allOf(containsString("byte[11]"),containsString("Hello World")));
+    }
+
+    @Test
+    public void testWriteMultipleBuffers() throws Exception
+    {
+        int bufsize = (int)(policy.getMaxBinaryMessageBufferSize() * 2.5);
+        byte buf[] = new byte[bufsize];
+        LOG.debug("Buffer sizes: max:{}, test:{}",policy.getMaxBinaryMessageBufferSize(),bufsize);
+        Arrays.fill(buf,(byte)'x');
+        buf[bufsize - 1] = (byte)'o'; // mark last entry for debugging
+
+        try (MessageOutputStream stream = new MessageOutputStream(session))
+        {
+            stream.write(buf);
+        }
+
+        Assert.assertThat("Socket.messageQueue.size",socket.messageQueue.size(),is(1));
+        String msg = socket.messageQueue.poll();
+        Assert.assertThat("Message",msg,allOf(containsString("byte[" + bufsize + "]"),containsString("xxxo>>>")));
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/message/MessageWriterTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/message/MessageWriterTest.java
new file mode 100644
index 0000000..7b0dac0
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/message/MessageWriterTest.java
@@ -0,0 +1,139 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.message;
+
+import java.util.Arrays;
+
+import org.eclipse.jetty.toolchain.test.TestTracker;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.api.extensions.OutgoingFrames;
+import org.eclipse.jetty.websocket.common.events.EventDriver;
+import org.eclipse.jetty.websocket.common.events.EventDriverFactory;
+import org.eclipse.jetty.websocket.common.io.FramePipes;
+import org.eclipse.jetty.websocket.common.io.LocalWebSocketSession;
+import org.eclipse.jetty.websocket.common.test.LeakTrackingBufferPoolRule;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+
+import static org.hamcrest.Matchers.is;
+
+public class MessageWriterTest
+{
+    private static final Logger LOG = Log.getLogger(MessageWriterTest.class);
+
+    @Rule
+    public TestTracker testtracker = new TestTracker();
+
+    @Rule
+    public TestName testname = new TestName();
+
+    @Rule
+    public LeakTrackingBufferPoolRule bufferPool = new LeakTrackingBufferPoolRule("Test");
+
+    private WebSocketPolicy policy;
+    private TrackingSocket socket;
+    private LocalWebSocketSession session;
+
+    @After
+    public void closeSession()
+    {
+        session.close();
+    }
+
+    @Before
+    public void setupSession()
+    {
+        policy = WebSocketPolicy.newServerPolicy();
+        policy.setInputBufferSize(1024);
+        policy.setMaxTextMessageBufferSize(1024);
+
+        // Event Driver factory
+        EventDriverFactory factory = new EventDriverFactory(policy);
+
+        // local socket
+        EventDriver driver = factory.wrap(new TrackingSocket("local"));
+
+        // remote socket
+        socket = new TrackingSocket("remote");
+        OutgoingFrames socketPipe = FramePipes.to(factory.wrap(socket));
+
+        session = new LocalWebSocketSession(testname,driver,bufferPool);
+
+        session.setPolicy(policy);
+        // talk to our remote socket
+        session.setOutgoingHandler(socketPipe);
+        // open connection
+        session.open();
+    }
+
+    @Test
+    public void testMultipleWrites() throws Exception
+    {
+        try (MessageWriter stream = new MessageWriter(session))
+        {
+            stream.write("Hello");
+            stream.write(" ");
+            stream.write("World");
+        }
+
+        Assert.assertThat("Socket.messageQueue.size",socket.messageQueue.size(),is(1));
+        String msg = socket.messageQueue.poll();
+        Assert.assertThat("Message",msg,is("Hello World"));
+    }
+
+    @Test
+    public void testSingleWrite() throws Exception
+    {
+        try (MessageWriter stream = new MessageWriter(session))
+        {
+            stream.append("Hello World");
+        }
+
+        Assert.assertThat("Socket.messageQueue.size",socket.messageQueue.size(),is(1));
+        String msg = socket.messageQueue.poll();
+        Assert.assertThat("Message",msg,is("Hello World"));
+    }
+
+    @Test
+    public void testWriteMultipleBuffers() throws Exception
+    {
+        int bufsize = (int)(policy.getMaxTextMessageBufferSize() * 2.5);
+        char buf[] = new char[bufsize];
+        if (LOG.isDebugEnabled())
+            LOG.debug("Buffer size: {}",bufsize);
+        Arrays.fill(buf,'x');
+        buf[bufsize - 1] = 'o'; // mark last entry for debugging
+
+        try (MessageWriter stream = new MessageWriter(session))
+        {
+            stream.write(buf);
+        }
+
+        Assert.assertThat("Socket.messageQueue.size",socket.messageQueue.size(),is(1));
+        String msg = socket.messageQueue.poll();
+        String expected = new String(buf);
+        Assert.assertThat("Message",msg,is(expected));
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/message/TrackingInputStreamSocket.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/message/TrackingInputStreamSocket.java
new file mode 100644
index 0000000..ca1b5cd
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/message/TrackingInputStreamSocket.java
@@ -0,0 +1,112 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.message;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.toolchain.test.EventQueue;
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
+import org.eclipse.jetty.websocket.api.annotations.WebSocket;
+import org.junit.Assert;
+
+import static org.hamcrest.Matchers.is;
+
+ at WebSocket
+public class TrackingInputStreamSocket
+{
+    private static final Logger LOG = Log.getLogger(TrackingInputStreamSocket.class);
+    private final String id;
+    public int closeCode = -1;
+    public StringBuilder closeMessage = new StringBuilder();
+    public CountDownLatch closeLatch = new CountDownLatch(1);
+    public EventQueue<String> messageQueue = new EventQueue<>();
+    public EventQueue<Throwable> errorQueue = new EventQueue<>();
+
+    public TrackingInputStreamSocket()
+    {
+        this("socket");
+    }
+
+    public TrackingInputStreamSocket(String id)
+    {
+        this.id = id;
+    }
+
+    public void assertClose(int expectedStatusCode, String expectedReason) throws InterruptedException
+    {
+        assertCloseCode(expectedStatusCode);
+        assertCloseReason(expectedReason);
+    }
+
+    public void assertCloseCode(int expectedCode) throws InterruptedException
+    {
+        Assert.assertThat("Was Closed",closeLatch.await(50,TimeUnit.MILLISECONDS),is(true));
+        Assert.assertThat("Close Code",closeCode,is(expectedCode));
+    }
+
+    private void assertCloseReason(String expectedReason)
+    {
+        Assert.assertThat("Close Reason",closeMessage.toString(),is(expectedReason));
+    }
+
+    @OnWebSocketClose
+    public void onClose(int statusCode, String reason)
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("{} onClose({},{})",id,statusCode,reason);
+        closeCode = statusCode;
+        closeMessage.append(reason);
+        closeLatch.countDown();
+    }
+
+    @OnWebSocketError
+    public void onError(Throwable cause)
+    {
+        errorQueue.add(cause);
+    }
+
+    @OnWebSocketMessage
+    public void onInputStream(InputStream stream)
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("{} onInputStream({})",id,stream);
+        try
+        {
+            String msg = IO.toString(stream);
+            messageQueue.add(msg);
+        }
+        catch (IOException e)
+        {
+            errorQueue.add(e);
+        }
+    }
+
+    public void waitForClose(int timeoutDuration, TimeUnit timeoutUnit) throws InterruptedException
+    {
+        Assert.assertThat("Client Socket Closed",closeLatch.await(timeoutDuration,timeoutUnit),is(true));
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/message/TrackingSocket.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/message/TrackingSocket.java
new file mode 100644
index 0000000..433dd96
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/message/TrackingSocket.java
@@ -0,0 +1,170 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.message;
+
+import static org.hamcrest.Matchers.greaterThanOrEqualTo;
+import static org.hamcrest.Matchers.is;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.eclipse.jetty.toolchain.test.EventQueue;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.WebSocketAdapter;
+import org.junit.Assert;
+
+/**
+ * Testing Socket used on client side WebSocket testing.
+ */
+public class TrackingSocket extends WebSocketAdapter
+{
+    private static final Logger LOG = Log.getLogger(TrackingSocket.class);
+
+    private final String id;
+    public int closeCode = -1;
+    public StringBuilder closeMessage = new StringBuilder();
+    public CountDownLatch openLatch = new CountDownLatch(1);
+    public CountDownLatch closeLatch = new CountDownLatch(1);
+    public CountDownLatch dataLatch = new CountDownLatch(1);
+    public EventQueue<String> messageQueue = new EventQueue<>();
+    public EventQueue<Throwable> errorQueue = new EventQueue<>();
+
+    public TrackingSocket()
+    {
+        this("socket");
+    }
+
+    public TrackingSocket(String id)
+    {
+        this.id = id;
+    }
+
+    public void assertClose(int expectedStatusCode, String expectedReason) throws InterruptedException
+    {
+        assertCloseCode(expectedStatusCode);
+        assertCloseReason(expectedReason);
+    }
+
+    public void assertCloseCode(int expectedCode) throws InterruptedException
+    {
+        Assert.assertThat("Was Closed",closeLatch.await(50,TimeUnit.MILLISECONDS),is(true));
+        Assert.assertThat("Close Code",closeCode,is(expectedCode));
+    }
+
+    private void assertCloseReason(String expectedReason)
+    {
+        Assert.assertThat("Close Reason",closeMessage.toString(),is(expectedReason));
+    }
+
+    public void assertIsOpen() throws InterruptedException
+    {
+        assertWasOpened();
+        assertNotClosed();
+    }
+
+    public void assertMessage(String expected)
+    {
+        String actual = messageQueue.poll();
+        Assert.assertEquals("Message",expected,actual);
+    }
+
+    public void assertNotClosed()
+    {
+        Assert.assertThat("Closed Latch",closeLatch.getCount(),greaterThanOrEqualTo(1L));
+    }
+
+    public void assertNotOpened()
+    {
+        Assert.assertThat("Open Latch",openLatch.getCount(),greaterThanOrEqualTo(1L));
+    }
+
+    public void assertWasOpened() throws InterruptedException
+    {
+        Assert.assertThat("Was Opened",openLatch.await(500,TimeUnit.MILLISECONDS),is(true));
+    }
+
+    public void awaitMessage(int expectedMessageCount, TimeUnit timeoutUnit, int timeoutDuration) throws TimeoutException, InterruptedException
+    {
+        messageQueue.awaitEventCount(expectedMessageCount,timeoutDuration,timeoutUnit);
+    }
+
+    public void clear()
+    {
+        messageQueue.clear();
+    }
+
+    @Override
+    public void onWebSocketBinary(byte[] payload, int offset, int len)
+    {
+        LOG.debug("{} onWebSocketBinary(byte[{}],{},{})",id,payload.length,offset,len);
+        messageQueue.offer(MessageDebug.toDetailHint(payload,offset,len));
+        dataLatch.countDown();
+    }
+
+    @Override
+    public void onWebSocketClose(int statusCode, String reason)
+    {
+        LOG.debug("{} onWebSocketClose({},{})",id,statusCode,reason);
+        super.onWebSocketClose(statusCode,reason);
+        closeCode = statusCode;
+        closeMessage.append(reason);
+        closeLatch.countDown();
+    }
+
+    @Override
+    public void onWebSocketConnect(Session session)
+    {
+        super.onWebSocketConnect(session);
+        openLatch.countDown();
+    }
+
+    @Override
+    public void onWebSocketError(Throwable cause)
+    {
+        LOG.debug("{} onWebSocketError",id,cause);
+        Assert.assertThat("Error capture",errorQueue.offer(cause),is(true));
+    }
+
+    @Override
+    public void onWebSocketText(String message)
+    {
+        LOG.debug("{} onWebSocketText({})",id,message);
+        messageQueue.offer(message);
+        dataLatch.countDown();
+    }
+
+    public void waitForClose(int timeoutDuration, TimeUnit timeoutUnit) throws InterruptedException
+    {
+        Assert.assertThat("Client Socket Closed",closeLatch.await(timeoutDuration,timeoutUnit),is(true));
+    }
+
+    public void waitForConnected(int timeoutDuration, TimeUnit timeoutUnit) throws InterruptedException
+    {
+        Assert.assertThat("Client Socket Connected",openLatch.await(timeoutDuration,timeoutUnit),is(true));
+    }
+
+    public void waitForMessage(int timeoutDuration, TimeUnit timeoutUnit) throws InterruptedException
+    {
+        LOG.debug("{} Waiting for message",id);
+        Assert.assertThat("Message Received",dataLatch.await(timeoutDuration,timeoutUnit),is(true));
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/message/Utf8CharBufferTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/message/Utf8CharBufferTest.java
new file mode 100644
index 0000000..8fd4ccc
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/message/Utf8CharBufferTest.java
@@ -0,0 +1,125 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.message;
+
+import static org.hamcrest.Matchers.*;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class Utf8CharBufferTest
+{
+    private static String asString(ByteBuffer buffer)
+    {
+        return BufferUtil.toUTF8String(buffer);
+    }
+
+    private static byte[] asUTF(String str)
+    {
+        return str.getBytes(StandardCharsets.UTF_8);
+    }
+
+    @Test
+    public void testAppendGetAppendGet()
+    {
+        ByteBuffer buf = ByteBuffer.allocate(128);
+        Utf8CharBuffer utf = Utf8CharBuffer.wrap(buf);
+
+        byte hellobytes[] = asUTF("Hello ");
+        byte worldbytes[] = asUTF("World!");
+
+        utf.append(hellobytes, 0, hellobytes.length);
+        ByteBuffer hellobuf = utf.getByteBuffer();
+        utf.append(worldbytes, 0, worldbytes.length);
+        ByteBuffer worldbuf = utf.getByteBuffer();
+
+        Assert.assertThat("Hello buffer",asString(hellobuf),is("Hello "));
+        Assert.assertThat("World buffer",asString(worldbuf),is("Hello World!"));
+    }
+
+    @Test
+    public void testAppendGetClearAppendGet()
+    {
+        int bufsize = 128;
+        ByteBuffer buf = ByteBuffer.allocate(bufsize);
+        Utf8CharBuffer utf = Utf8CharBuffer.wrap(buf);
+
+        int expectedSize = bufsize / 2;
+        Assert.assertThat("Remaining (initial)",utf.remaining(),is(expectedSize));
+
+        byte hellobytes[] = asUTF("Hello World");
+
+        utf.append(hellobytes,0,hellobytes.length);
+        ByteBuffer hellobuf = utf.getByteBuffer();
+
+        Assert.assertThat("Remaining (after append)",utf.remaining(),is(expectedSize - hellobytes.length));
+        Assert.assertThat("Hello buffer",asString(hellobuf),is("Hello World"));
+
+        utf.clear();
+
+        Assert.assertThat("Remaining (after clear)",utf.remaining(),is(expectedSize));
+
+        byte whatnowbytes[] = asUTF("What Now?");
+        utf.append(whatnowbytes,0,whatnowbytes.length);
+        ByteBuffer whatnowbuf = utf.getByteBuffer();
+
+        Assert.assertThat("Remaining (after 2nd append)",utf.remaining(),is(expectedSize - whatnowbytes.length));
+        Assert.assertThat("What buffer",asString(whatnowbuf),is("What Now?"));
+    }
+
+    @Test
+    public void testAppendUnicodeGetBuffer()
+    {
+        ByteBuffer buf = ByteBuffer.allocate(64);
+        Utf8CharBuffer utf = Utf8CharBuffer.wrap(buf);
+
+        byte bb[] = asUTF("Hello A\u00ea\u00f1\u00fcC");
+        utf.append(bb,0,bb.length);
+
+        ByteBuffer actual = utf.getByteBuffer();
+        Assert.assertThat("Buffer length should be retained",actual.remaining(),is(bb.length));
+        Assert.assertThat("Message",asString(actual),is("Hello A\u00ea\u00f1\u00fcC"));
+    }
+
+    @Test
+    public void testSimpleGetBuffer()
+    {
+        int bufsize = 64;
+        ByteBuffer buf = ByteBuffer.allocate(bufsize);
+        Utf8CharBuffer utf = Utf8CharBuffer.wrap(buf);
+
+        int expectedSize = bufsize / 2;
+        Assert.assertThat("Remaining (initial)",utf.remaining(),is(expectedSize));
+
+        byte bb[] = asUTF("Hello World");
+        utf.append(bb,0,bb.length);
+
+        expectedSize -= bb.length;
+        Assert.assertThat("Remaining (after append)",utf.remaining(),is(expectedSize));
+
+        ByteBuffer actual = utf.getByteBuffer();
+        Assert.assertThat("Buffer length",actual.remaining(),is(bb.length));
+
+        Assert.assertThat("Message",asString(actual),is("Hello World"));
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/BlockheadClient.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/BlockheadClient.java
new file mode 100644
index 0000000..65eea4c
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/BlockheadClient.java
@@ -0,0 +1,769 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.test;
+
+import static org.hamcrest.Matchers.*;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.SocketAddress;
+import java.net.SocketTimeoutException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.MappedByteBufferPool;
+import org.eclipse.jetty.toolchain.test.EventQueue;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.BatchMode;
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.api.WriteCallback;
+import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.api.extensions.IncomingFrames;
+import org.eclipse.jetty.websocket.api.extensions.OutgoingFrames;
+import org.eclipse.jetty.websocket.api.util.WSURI;
+import org.eclipse.jetty.websocket.common.AcceptHash;
+import org.eclipse.jetty.websocket.common.CloseInfo;
+import org.eclipse.jetty.websocket.common.ConnectionState;
+import org.eclipse.jetty.websocket.common.Generator;
+import org.eclipse.jetty.websocket.common.OpCode;
+import org.eclipse.jetty.websocket.common.Parser;
+import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.extensions.ExtensionStack;
+import org.eclipse.jetty.websocket.common.extensions.WebSocketExtensionFactory;
+import org.eclipse.jetty.websocket.common.io.IOState;
+import org.eclipse.jetty.websocket.common.io.IOState.ConnectionStateListener;
+import org.eclipse.jetty.websocket.common.io.http.HttpResponseHeaderParser;
+import org.junit.Assert;
+
+/**
+ * A simple websocket client for performing unit tests with.
+ * <p>
+ * This client will use {@link HttpURLConnection} and {@link HttpsURLConnection} with standard blocking calls to perform websocket requests.
+ * <p>
+ * This client is <u>NOT</u> intended to be performant or follow the websocket spec religiously. In fact, being able to deviate from the websocket spec at will
+ * is desired for this client to operate properly for the unit testing within this module.
+ * <p>
+ * The BlockheadClient should never validate frames or bytes being sent for validity, against any sort of spec, or even sanity. It should, however be honest
+ * with regards to basic IO behavior, a write should work as expected, a read should work as expected, but <u>what</u> byte it sends or reads is not within its
+ * scope.
+ */
+public class BlockheadClient implements OutgoingFrames, ConnectionStateListener, AutoCloseable
+{
+    private class FrameReadingThread extends Thread implements Runnable, IncomingFrames
+    {
+        public long totalBytes = 0;
+        public long totalReadOps = 0;
+        public long totalParseOps = 0;
+
+        public EventQueue<WebSocketFrame> frames = new EventQueue<>();
+        public EventQueue<Throwable> errors = new EventQueue<>();
+
+        @Override
+        public void run()
+        {
+            LOG.debug("Reading frames from server");
+
+            byte buf[] = new byte[BUFFER_SIZE];
+            try
+            {
+                if ((remainingBuffer != null) && (remainingBuffer.remaining() > 0))
+                {
+                    LOG.debug("Reading bytes received during response header parse: {}",BufferUtil.toDetailString(remainingBuffer));
+                    totalBytes += remainingBuffer.remaining();
+                    totalReadOps++;
+                    parser.parse(remainingBuffer);
+                }
+
+                int len = 0;
+                int available = 0;
+                while (!eof)
+                {
+                    available = in.available();
+                    len = in.read(buf,0,Math.min(available,buf.length));
+                    totalReadOps++;
+                    if (len < 0)
+                    {
+                        eof = true;
+                        break;
+                    }
+                    else if (len > 0)
+                    {
+                        totalBytes += len;
+                        ByteBuffer bbuf = ByteBuffer.wrap(buf,0,len);
+                        if (LOG.isDebugEnabled())
+                        {
+                            LOG.debug("Read {} bytes: {}",len,BufferUtil.toDetailString(bbuf));
+                        }
+                        totalParseOps++;
+                        parser.parse(bbuf);
+                    }
+                }
+            }
+            catch (IOException e)
+            {
+                LOG.debug(e);
+            }
+        }
+
+        @Override
+        public String toString()
+        {
+            StringBuilder str = new StringBuilder();
+            str.append("FrameReadingThread[");
+            str.append(",frames=" + frames.size());
+            str.append(",errors=" + errors.size());
+            str.append(String.format(",totalBytes=%,d",totalBytes));
+            str.append(String.format(",totalReadOps=%,d",totalReadOps));
+            str.append(String.format(",totalParseOps=%,d",totalParseOps));
+            str.append("]");
+            return str.toString();
+        }
+
+        @Override
+        public synchronized void incomingError(Throwable t)
+        {
+            this.errors.add(t);
+        }
+
+        @Override
+        public synchronized void incomingFrame(Frame frame)
+        {
+            this.frames.add(WebSocketFrame.copy(frame));
+        }
+
+        public synchronized void clear()
+        {
+            this.frames.clear();
+            this.errors.clear();
+        }
+    }
+
+    private static final String REQUEST_HASH_KEY = "dGhlIHNhbXBsZSBub25jZQ==";
+    private static final int BUFFER_SIZE = 64 * 1024;
+    private static final Logger LOG = Log.getLogger(BlockheadClient.class);
+    private final URI destHttpURI;
+    private final URI destWebsocketURI;
+    private final ByteBufferPool bufferPool;
+    private final Generator generator;
+    private final Parser parser;
+
+    private final WebSocketExtensionFactory extensionFactory;
+    private FrameReadingThread frameReader;
+
+    private ExecutorService executor;
+    private Socket socket;
+    private OutputStream out;
+    private InputStream in;
+    private int version = 13; // default to RFC-6455
+    private String protocols;
+    private List<String> extensions = new ArrayList<>();
+    private List<String> headers = new ArrayList<>();
+    private byte[] clientmask = new byte[] { (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF };
+    private int timeout = 1000;
+    private OutgoingFrames outgoing = this;
+    private boolean eof = false;
+    private ExtensionStack extensionStack;
+    private IOState ioState;
+    private CountDownLatch disconnectedLatch = new CountDownLatch(1);
+    private ByteBuffer remainingBuffer;
+
+    private String connectionValue = "Upgrade";
+
+    public BlockheadClient(URI destWebsocketURI) throws URISyntaxException
+    {
+        this(WebSocketPolicy.newClientPolicy(),destWebsocketURI);
+    }
+
+    public BlockheadClient(WebSocketPolicy policy, URI destWebsocketURI) throws URISyntaxException
+    {
+        Assert.assertThat("Websocket URI scheme",destWebsocketURI.getScheme(),anyOf(is("ws"),is("wss")));
+        this.destWebsocketURI = destWebsocketURI;
+        if (destWebsocketURI.getScheme().equals("wss"))
+        {
+            throw new RuntimeException("Sorry, BlockheadClient does not support SSL");
+        }
+        this.destHttpURI = WSURI.toHttp(destWebsocketURI);
+
+        LOG.debug("WebSocket URI: {}",destWebsocketURI);
+        LOG.debug("     HTTP URI: {}",destHttpURI);
+
+        // This is a blockhead client, no point tracking leaks on this object.
+        this.bufferPool = new MappedByteBufferPool(8192);
+        this.generator = new Generator(policy,bufferPool);
+        this.parser = new Parser(policy,bufferPool);
+
+        this.extensionFactory = new WebSocketExtensionFactory(policy,bufferPool);
+        this.ioState = new IOState();
+        this.ioState.addListener(this);
+    }
+
+    public void addExtensions(String xtension)
+    {
+        this.extensions.add(xtension);
+    }
+
+    public void addHeader(String header)
+    {
+        this.headers.add(header);
+    }
+
+    public boolean awaitDisconnect(long timeout, TimeUnit unit) throws InterruptedException
+    {
+        return disconnectedLatch.await(timeout,unit);
+    }
+
+    public void clearCaptured()
+    {
+        frameReader.clear();
+    }
+
+    public void clearExtensions()
+    {
+        extensions.clear();
+    }
+
+    @Override
+    public void close()
+    {
+        LOG.debug("close()");
+        close(-1,null);
+    }
+
+    public void close(int statusCode, String message)
+    {
+        LOG.debug("close({},{})",statusCode,message);
+        CloseInfo close = new CloseInfo(statusCode,message);
+
+        if (!ioState.isClosed())
+        {
+            ioState.onCloseLocal(close);
+        }
+        else
+        {
+            LOG.debug("Not issuing close. ioState = {}",ioState);
+        }
+    }
+
+    public void connect() throws IOException
+    {
+        InetAddress destAddr = InetAddress.getByName(destHttpURI.getHost());
+        int port = destHttpURI.getPort();
+
+        SocketAddress endpoint = new InetSocketAddress(destAddr,port);
+
+        socket = new Socket();
+        socket.setSoTimeout(timeout);
+        socket.connect(endpoint);
+
+        out = socket.getOutputStream();
+        in = socket.getInputStream();
+    }
+
+    public void disconnect()
+    {
+        LOG.debug("disconnect");
+        IO.close(in);
+        IO.close(out);
+        disconnectedLatch.countDown();
+        if (frameReader != null)
+        {
+            frameReader.interrupt();
+        }
+        if (socket != null)
+        {
+            try
+            {
+                socket.close();
+            }
+            catch (IOException ignore)
+            {
+                /* ignore */
+            }
+        }
+    }
+
+    public void expectServerDisconnect()
+    {
+        if (eof)
+        {
+            return;
+        }
+
+        try
+        {
+            int len = in.read();
+            if (len == (-1))
+            {
+                // we are disconnected
+                eof = true;
+                return;
+            }
+
+            Assert.assertThat("Expecting no data and proper socket disconnect (issued from server)",len,is(-1));
+        }
+        catch (SocketTimeoutException e)
+        {
+            LOG.warn(e);
+            Assert.fail("Expected a server initiated disconnect, instead the read timed out");
+        }
+        catch (IOException e)
+        {
+            // acceptable path
+        }
+    }
+
+    public HttpResponse expectUpgradeResponse() throws IOException
+    {
+        HttpResponse response = readResponseHeader();
+
+        if (LOG.isDebugEnabled())
+        {
+            LOG.debug("Response Header: {}{}",'\n',response);
+        }
+
+        Assert.assertThat("Response Status Code",response.getStatusCode(),is(101));
+        Assert.assertThat("Response Status Reason",response.getStatusReason(),is("Switching Protocols"));
+        Assert.assertThat("Response Header[Upgrade]",response.getHeader("Upgrade"),is("WebSocket"));
+        Assert.assertThat("Response Header[Connection]",response.getHeader("Connection"),is("Upgrade"));
+
+        // Validate the Sec-WebSocket-Accept
+        String acceptKey = response.getHeader("Sec-WebSocket-Accept");
+        Assert.assertThat("Response Header[Sec-WebSocket-Accept Exists]",acceptKey,notNullValue());
+
+        String reqKey = REQUEST_HASH_KEY;
+        String expectedHash = AcceptHash.hashKey(reqKey);
+
+        Assert.assertThat("Valid Sec-WebSocket-Accept Hash?",acceptKey,is(expectedHash));
+
+        // collect extensions configured in response header
+        List<ExtensionConfig> configs = getExtensionConfigs(response);
+        extensionStack = new ExtensionStack(this.extensionFactory);
+        extensionStack.negotiate(configs);
+
+        // Setup Frame Reader
+        this.frameReader = new FrameReadingThread();
+        this.frameReader.start();
+
+        // Start with default routing
+        extensionStack.setNextIncoming(frameReader); // the websocket layer
+        extensionStack.setNextOutgoing(outgoing); // the network layer
+
+        // Configure Parser / Generator
+        extensionStack.configure(parser);
+        extensionStack.configure(generator);
+
+        // Start Stack
+        try
+        {
+            extensionStack.start();
+        }
+        catch (Exception e)
+        {
+            throw new IOException("Unable to start Extension Stack");
+        }
+
+        // configure parser
+        parser.setIncomingFramesHandler(extensionStack);
+        ioState.onOpened();
+
+        LOG.debug("outgoing = {}",outgoing);
+        LOG.debug("incoming = {}",extensionStack);
+
+        return response;
+    }
+
+    public void flush() throws IOException
+    {
+        out.flush();
+    }
+
+    public String getConnectionValue()
+    {
+        return connectionValue;
+    }
+
+    public ExecutorService getExecutor()
+    {
+        if (executor == null)
+        {
+            executor = Executors.newCachedThreadPool();
+        }
+        return executor;
+    }
+
+    private List<ExtensionConfig> getExtensionConfigs(HttpResponse response)
+    {
+        List<ExtensionConfig> configs = new ArrayList<>();
+
+        String econf = response.getHeader("Sec-WebSocket-Extensions");
+        if (econf != null)
+        {
+            LOG.debug("Found Extension Response: {}",econf);
+            ExtensionConfig config = ExtensionConfig.parse(econf);
+            configs.add(config);
+        }
+        return configs;
+    }
+
+    public List<String> getExtensions()
+    {
+        return extensions;
+    }
+
+    public URI getHttpURI()
+    {
+        return destHttpURI;
+    }
+
+    public InetSocketAddress getLocalSocketAddress()
+    {
+        return (InetSocketAddress)socket.getLocalSocketAddress();
+    }
+
+    public IOState getIOState()
+    {
+        return ioState;
+    }
+
+    public String getProtocols()
+    {
+        return protocols;
+    }
+    
+
+    public InetSocketAddress getRemoteSocketAddress()
+    {
+        return (InetSocketAddress)socket.getRemoteSocketAddress();
+    }
+
+    public String getRequestHost()
+    {
+        if (destHttpURI.getPort() > 0)
+        {
+            return String.format("%s:%d",destHttpURI.getHost(),destHttpURI.getPort());
+        }
+        else
+        {
+            return destHttpURI.getHost();
+        }
+    }
+
+    public String getRequestPath()
+    {
+        StringBuilder path = new StringBuilder();
+        path.append(destHttpURI.getPath());
+        if (StringUtil.isNotBlank(destHttpURI.getQuery()))
+        {
+            path.append('?').append(destHttpURI.getQuery());
+        }
+        return path.toString();
+    }
+
+    public String getRequestWebSocketKey()
+    {
+        return REQUEST_HASH_KEY;
+    }
+
+    public String getRequestWebSocketOrigin()
+    {
+        return destWebsocketURI.toASCIIString();
+    }
+
+    public int getVersion()
+    {
+        return version;
+    }
+
+    public URI getWebsocketURI()
+    {
+        return destWebsocketURI;
+    }
+
+    public boolean isConnected()
+    {
+        return (socket != null) && (socket.isConnected());
+    }
+
+    @Override
+    public void onConnectionStateChange(ConnectionState state)
+    {
+        LOG.debug("CLIENT onConnectionStateChange() - {}",state);
+        switch (state)
+        {
+            case CLOSED:
+                // Per Spec, client should not initiate disconnect on its own
+                // this.disconnect();
+                break;
+            case CLOSING:
+                CloseInfo close = ioState.getCloseInfo();
+
+                WebSocketFrame frame = close.asFrame();
+                LOG.debug("Issuing: {}",frame);
+                try
+                {
+                    write(frame);
+                }
+                catch (IOException e)
+                {
+                    LOG.debug(e);
+                }
+                break;
+            default:
+                /* do nothing */
+                break;
+        }
+    }
+
+    @Override
+    public void outgoingFrame(Frame frame, WriteCallback callback, BatchMode batchMode)
+    {
+        ByteBuffer headerBuf = generator.generateHeaderBytes(frame);
+        if (LOG.isDebugEnabled())
+        {
+            LOG.debug("writing out: {}",BufferUtil.toDetailString(headerBuf));
+        }
+        try
+        {
+            BufferUtil.writeTo(headerBuf,out);
+            BufferUtil.writeTo(frame.getPayload(),out);
+            out.flush();
+            if (callback != null)
+            {
+                callback.writeSuccess();
+            }
+        }
+        catch (IOException e)
+        {
+            if (callback != null)
+            {
+                callback.writeFailed(e);
+            }
+        }
+        finally
+        {
+            bufferPool.release(headerBuf);
+        }
+
+        if (frame.getOpCode() == OpCode.CLOSE)
+        {
+            disconnect();
+        }
+    }
+
+    public EventQueue<WebSocketFrame> readFrames(int expectedFrameCount, int timeoutDuration, TimeUnit timeoutUnit) throws Exception
+    {
+        frameReader.frames.awaitEventCount(expectedFrameCount,timeoutDuration,timeoutUnit);
+        return frameReader.frames;
+    }
+
+    public HttpResponse readResponseHeader() throws IOException
+    {
+        HttpResponse response = new HttpResponse();
+        HttpResponseHeaderParser respParser = new HttpResponseHeaderParser(response);
+
+        byte buf[] = new byte[512];
+
+        while (!eof)
+        {
+            int available = in.available();
+            int len = in.read(buf,0,Math.min(available,buf.length));
+            if (len < 0)
+            {
+                eof = true;
+                break;
+            }
+            else if (len > 0)
+            {
+                ByteBuffer bbuf = ByteBuffer.wrap(buf,0,len);
+                if (LOG.isDebugEnabled())
+                {
+                    LOG.debug("Read {} bytes: {}",len,BufferUtil.toDetailString(bbuf));
+                }
+                if (respParser.parse(bbuf) != null)
+                {
+                    break;
+                }
+            }
+        }
+
+        remainingBuffer = response.getRemainingBuffer();
+
+        return response;
+    }
+
+    public void sendStandardRequest() throws IOException
+    {
+        StringBuilder req = generateUpgradeRequest();
+        writeRaw(req.toString());
+    }
+
+    public StringBuilder generateUpgradeRequest()
+    {
+        StringBuilder req = new StringBuilder();
+        req.append("GET ").append(getRequestPath()).append(" HTTP/1.1\r\n");
+        req.append("Host: ").append(getRequestHost()).append("\r\n");
+        req.append("Upgrade: websocket\r\n");
+        req.append("User-Agent: BlockheadClient/JettyTests\r\n");
+        req.append("Connection: ").append(connectionValue).append("\r\n");
+        for (String header : headers)
+        {
+            req.append(header);
+        }
+        req.append("Sec-WebSocket-Key: ").append(getRequestWebSocketKey()).append("\r\n");
+        req.append("Sec-WebSocket-Origin: ").append(getRequestWebSocketOrigin()).append("\r\n");
+        if (StringUtil.isNotBlank(protocols))
+        {
+            req.append("Sec-WebSocket-Protocol: ").append(protocols).append("\r\n");
+        }
+
+        for (String xtension : extensions)
+        {
+            req.append("Sec-WebSocket-Extensions: ").append(xtension).append("\r\n");
+        }
+        req.append("Sec-WebSocket-Version: ").append(version).append("\r\n");
+        req.append("\r\n");
+        return req;
+    }
+
+    public void setConnectionValue(String connectionValue)
+    {
+        this.connectionValue = connectionValue;
+    }
+
+    public void setExecutor(ExecutorService executor)
+    {
+        this.executor = executor;
+    }
+
+    public void setProtocols(String protocols)
+    {
+        this.protocols = protocols;
+    }
+
+    public void setTimeout(int duration, TimeUnit unit)
+    {
+        this.timeout = (int)TimeUnit.MILLISECONDS.convert(duration,unit);
+    }
+
+    public void setVersion(int version)
+    {
+        this.version = version;
+    }
+
+    public void skipTo(String string) throws IOException
+    {
+        int state = 0;
+
+        while (true)
+        {
+            int b = in.read();
+            if (b < 0)
+            {
+                throw new EOFException();
+            }
+
+            if (b == string.charAt(state))
+            {
+                state++;
+                if (state == string.length())
+                {
+                    break;
+                }
+            }
+            else
+            {
+                state = 0;
+            }
+        }
+    }
+
+    public void sleep(TimeUnit unit, int duration) throws InterruptedException
+    {
+        LOG.info("Sleeping for {} {}",duration,unit);
+        unit.sleep(duration);
+        LOG.info("Waking up from sleep");
+    }
+
+    public void write(WebSocketFrame frame) throws IOException
+    {
+        if (!ioState.isOpen())
+        {
+            LOG.debug("IO Not Open / Not Writing: {}",frame);
+            return;
+        }
+        LOG.debug("write(Frame->{}) to {}",frame,outgoing);
+        if (LOG.isDebugEnabled())
+        {
+            frame.setMask(new byte[] { 0x00, 0x00, 0x00, 0x00 });
+        }
+        else
+        {
+            frame.setMask(clientmask);
+        }
+        extensionStack.outgoingFrame(frame,null,BatchMode.OFF);
+    }
+
+    public void writeRaw(ByteBuffer buf) throws IOException
+    {
+        LOG.debug("write(ByteBuffer) {}",BufferUtil.toDetailString(buf));
+        BufferUtil.writeTo(buf,out);
+    }
+
+    public void writeRaw(ByteBuffer buf, int numBytes) throws IOException
+    {
+        int len = Math.min(numBytes,buf.remaining());
+        byte arr[] = new byte[len];
+        buf.get(arr,0,len);
+        out.write(arr);
+    }
+
+    public void writeRaw(String str) throws IOException
+    {
+        LOG.debug("write((String)[{}]){}{})",str.length(),'\n',str);
+        out.write(str.getBytes(StandardCharsets.ISO_8859_1));
+    }
+
+    public void writeRawSlowly(ByteBuffer buf, int segmentSize) throws IOException
+    {
+        while (buf.remaining() > 0)
+        {
+            writeRaw(buf,segmentSize);
+            flush();
+        }
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/BlockheadClientConstructionTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/BlockheadClientConstructionTest.java
new file mode 100644
index 0000000..4edd902
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/BlockheadClientConstructionTest.java
@@ -0,0 +1,72 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.test;
+
+import static org.hamcrest.Matchers.is;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Gotta test some basic constructors of the BlockheadClient.
+ */
+ at RunWith(value = Parameterized.class)
+public class BlockheadClientConstructionTest
+{
+    @Parameters
+    public static Collection<Object[]> data()
+    {
+        List<Object[]> data = new ArrayList<>();
+        // @formatter:off
+        data.add(new Object[] { "ws://localhost/",      "http://localhost/" });
+        data.add(new Object[] { "ws://localhost:8080/", "http://localhost:8080/" });
+        data.add(new Object[] { "ws://webtide.com/",    "http://webtide.com/" });
+        data.add(new Object[] { "ws://www.webtide.com/sockets/chat", "http://www.webtide.com/sockets/chat" });
+        // @formatter:on
+        return data;
+    }
+
+    private URI expectedWsUri;
+    private URI expectedHttpUri;
+
+    public BlockheadClientConstructionTest(String wsuri, String httpuri)
+    {
+        this.expectedWsUri = URI.create(wsuri);
+        this.expectedHttpUri = URI.create(httpuri);
+    }
+
+    @Test
+    public void testURIs() throws URISyntaxException
+    {
+        @SuppressWarnings("resource")
+        BlockheadClient client = new BlockheadClient(expectedWsUri);
+        Assert.assertThat("Websocket URI",client.getWebsocketURI(),is(expectedWsUri));
+        Assert.assertThat("Websocket URI",client.getHttpURI(),is(expectedHttpUri));
+    }
+
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/BlockheadServer.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/BlockheadServer.java
new file mode 100644
index 0000000..7a2c32d
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/BlockheadServer.java
@@ -0,0 +1,696 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.test;
+
+import static org.hamcrest.Matchers.*;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.SocketException;
+import java.net.URI;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.MappedByteBufferPool;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.BatchMode;
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.api.WriteCallback;
+import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.api.extensions.Frame.Type;
+import org.eclipse.jetty.websocket.api.extensions.IncomingFrames;
+import org.eclipse.jetty.websocket.api.extensions.OutgoingFrames;
+import org.eclipse.jetty.websocket.common.AcceptHash;
+import org.eclipse.jetty.websocket.common.CloseInfo;
+import org.eclipse.jetty.websocket.common.Generator;
+import org.eclipse.jetty.websocket.common.OpCode;
+import org.eclipse.jetty.websocket.common.Parser;
+import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.extensions.ExtensionStack;
+import org.eclipse.jetty.websocket.common.extensions.WebSocketExtensionFactory;
+import org.eclipse.jetty.websocket.common.frames.CloseFrame;
+import org.junit.Assert;
+
+/**
+ * A overly simplistic websocket server used during testing.
+ * <p>
+ * This is not meant to be performant or accurate. In fact, having the server misbehave is a useful trait during testing.
+ */
+public class BlockheadServer
+{
+    public static class ServerConnection implements IncomingFrames, OutgoingFrames, Runnable
+    {
+        private final int BUFFER_SIZE = 8192;
+        private final Socket socket;
+        private final ByteBufferPool bufferPool;
+        private final WebSocketPolicy policy;
+        private final IncomingFramesCapture incomingFrames;
+        private final Parser parser;
+        private final Generator generator;
+        private final AtomicInteger parseCount;
+        private final WebSocketExtensionFactory extensionRegistry;
+        private final AtomicBoolean echoing = new AtomicBoolean(false);
+        private Thread echoThread;
+
+        /** Set to true to disable timeouts (for debugging reasons) */
+        private boolean debug = false;
+        private OutputStream out;
+        private InputStream in;
+
+        private Map<String, String> extraResponseHeaders = new HashMap<>();
+        private OutgoingFrames outgoing = this;
+
+        public ServerConnection(Socket socket)
+        {
+            this.socket = socket;
+            this.incomingFrames = new IncomingFramesCapture();
+            this.policy = WebSocketPolicy.newServerPolicy();
+            this.policy.setMaxBinaryMessageSize(100000);
+            this.policy.setMaxTextMessageSize(100000);
+            // This is a blockhead server connection, no point tracking leaks on this object.
+            this.bufferPool = new MappedByteBufferPool(BUFFER_SIZE);
+            this.parser = new Parser(policy,bufferPool);
+            this.parseCount = new AtomicInteger(0);
+            this.generator = new Generator(policy,bufferPool,false);
+            this.extensionRegistry = new WebSocketExtensionFactory(policy,bufferPool);
+        }
+
+        /**
+         * Add an extra header for the upgrade response (from the server). No extra work is done to ensure the key and value are sane for http.
+         */
+        public void addResponseHeader(String rawkey, String rawvalue)
+        {
+            extraResponseHeaders.put(rawkey,rawvalue);
+        }
+
+        public void close() throws IOException
+        {
+            write(new CloseFrame());
+            flush();
+        }
+
+        public void close(int statusCode) throws IOException
+        {
+            CloseInfo close = new CloseInfo(statusCode);
+            write(close.asFrame());
+            flush();
+        }
+
+        public void disconnect()
+        {
+            LOG.debug("disconnect");
+            IO.close(in);
+            IO.close(out);
+            if (socket != null)
+            {
+                try
+                {
+                    socket.close();
+                }
+                catch (IOException ignore)
+                {
+                    /* ignore */
+                }
+            }
+        }
+
+        public void echoMessage(int expectedFrames, int timeoutDuration, TimeUnit timeoutUnit) throws IOException, TimeoutException
+        {
+            LOG.debug("Echo Frames [expecting {}]",expectedFrames);
+            IncomingFramesCapture cap = readFrames(expectedFrames,timeoutDuration,timeoutUnit);
+            // now echo them back.
+            for (Frame frame : cap.getFrames())
+            {
+                write(WebSocketFrame.copy(frame).setMasked(false));
+            }
+        }
+
+        public void flush() throws IOException
+        {
+            getOutputStream().flush();
+        }
+
+        public ByteBufferPool getBufferPool()
+        {
+            return bufferPool;
+        }
+
+        public IncomingFramesCapture getIncomingFrames()
+        {
+            return incomingFrames;
+        }
+
+        public InputStream getInputStream() throws IOException
+        {
+            if (in == null)
+            {
+                in = socket.getInputStream();
+            }
+            return in;
+        }
+
+        private OutputStream getOutputStream() throws IOException
+        {
+            if (out == null)
+            {
+                out = socket.getOutputStream();
+            }
+            return out;
+        }
+
+        public Parser getParser()
+        {
+            return parser;
+        }
+
+        public WebSocketPolicy getPolicy()
+        {
+            return policy;
+        }
+
+        @Override
+        public void incomingError(Throwable e)
+        {
+            incomingFrames.incomingError(e);
+        }
+
+        @Override
+        public void incomingFrame(Frame frame)
+        {
+            LOG.debug("incoming({})",frame);
+            int count = parseCount.incrementAndGet();
+            if ((count % 10) == 0)
+            {
+                LOG.info("Server parsed {} frames",count);
+            }
+            incomingFrames.incomingFrame(WebSocketFrame.copy(frame));
+
+            if (frame.getOpCode() == OpCode.CLOSE)
+            {
+                CloseInfo close = new CloseInfo(frame);
+                LOG.debug("Close frame: {}",close);
+            }
+
+            Type type = frame.getType();
+            if (echoing.get() && (type.isData() || type.isContinuation()))
+            {
+                try
+                {
+                    write(WebSocketFrame.copy(frame).setMasked(false));
+                }
+                catch (IOException e)
+                {
+                    LOG.warn(e);
+                }
+            }
+        }
+
+        @Override
+        public void outgoingFrame(Frame frame, WriteCallback callback, BatchMode batchMode)
+        {
+            ByteBuffer headerBuf = generator.generateHeaderBytes(frame);
+            if (LOG.isDebugEnabled())
+            {
+                LOG.debug("writing out: {}",BufferUtil.toDetailString(headerBuf));
+            }
+
+            try
+            {
+                BufferUtil.writeTo(headerBuf,out);
+                if (frame.hasPayload())
+                    BufferUtil.writeTo(frame.getPayload(),out);
+                out.flush();
+                if (callback != null)
+                {
+                    callback.writeSuccess();
+                }
+
+                if (frame.getOpCode() == OpCode.CLOSE)
+                {
+                    disconnect();
+                }
+            }
+            catch (Throwable t)
+            {
+                if (callback != null)
+                {
+                    callback.writeFailed(t);
+                }
+            }
+        }
+
+        public List<ExtensionConfig> parseExtensions(List<String> requestLines)
+        {
+            List<ExtensionConfig> extensionConfigs = new ArrayList<>();
+            
+            List<String> hits = regexFind(requestLines, "^Sec-WebSocket-Extensions: (.*)$");
+
+            for (String econf : hits)
+            {
+                // found extensions
+                ExtensionConfig config = ExtensionConfig.parse(econf);
+                extensionConfigs.add(config);
+            }
+
+            return extensionConfigs;
+        }
+
+        public String parseWebSocketKey(List<String> requestLines)
+        {
+            List<String> hits = regexFind(requestLines,"^Sec-WebSocket-Key: (.*)$");
+            if (hits.size() <= 0)
+            {
+                return null;
+            }
+            
+            Assert.assertThat("Number of Sec-WebSocket-Key headers", hits.size(), is(1));
+            
+            String key = hits.get(0);
+            return key;
+        }
+
+        public int read(ByteBuffer buf) throws IOException
+        {
+            int len = 0;
+            while ((in.available() > 0) && (buf.remaining() > 0))
+            {
+                buf.put((byte)in.read());
+                len++;
+            }
+            return len;
+        }
+
+        public IncomingFramesCapture readFrames(int expectedCount, int timeoutDuration, TimeUnit timeoutUnit) throws IOException, TimeoutException
+        {
+            LOG.debug("Read: waiting for {} frame(s) from client",expectedCount);
+            int startCount = incomingFrames.size();
+
+            ByteBuffer buf = bufferPool.acquire(BUFFER_SIZE,false);
+            BufferUtil.clearToFill(buf);
+            try
+            {
+                long msDur = TimeUnit.MILLISECONDS.convert(timeoutDuration,timeoutUnit);
+                long now = System.currentTimeMillis();
+                long expireOn = now + msDur;
+                LOG.debug("Now: {} - expireOn: {} ({} ms)",now,expireOn,msDur);
+
+                int len = 0;
+                while (incomingFrames.size() < (startCount + expectedCount))
+                {
+                    BufferUtil.clearToFill(buf);
+                    len = read(buf);
+                    if (len > 0)
+                    {
+                        LOG.debug("Read {} bytes",len);
+                        BufferUtil.flipToFlush(buf,0);
+                        parser.parse(buf);
+                    }
+                    try
+                    {
+                        TimeUnit.MILLISECONDS.sleep(20);
+                    }
+                    catch (InterruptedException gnore)
+                    {
+                        /* ignore */
+                    }
+                    if (!debug && (System.currentTimeMillis() > expireOn))
+                    {
+                        incomingFrames.dump();
+                        throw new TimeoutException(String.format("Timeout reading all %d expected frames. (managed to only read %d frame(s))",expectedCount,
+                                incomingFrames.size()));
+                    }
+                }
+            }
+            finally
+            {
+                bufferPool.release(buf);
+            }
+
+            return incomingFrames;
+        }
+
+        public String readRequest() throws IOException
+        {
+            LOG.debug("Reading client request");
+            StringBuilder request = new StringBuilder();
+            BufferedReader in = new BufferedReader(new InputStreamReader(getInputStream()));
+            for (String line = in.readLine(); line != null; line = in.readLine())
+            {
+                if (line.length() == 0)
+                {
+                    break;
+                }
+                request.append(line).append("\r\n");
+                LOG.debug("read line: {}",line);
+            }
+
+            LOG.debug("Client Request:{}{}","\n",request);
+            return request.toString();
+        }
+
+        public List<String> readRequestLines() throws IOException
+        {
+            LOG.debug("Reading client request header");
+            List<String> lines = new ArrayList<>();
+
+            BufferedReader in = new BufferedReader(new InputStreamReader(getInputStream()));
+            for (String line = in.readLine(); line != null; line = in.readLine())
+            {
+                if (line.length() == 0)
+                {
+                    break;
+                }
+                lines.add(line);
+            }
+
+            return lines;
+        }
+
+        public List<String> regexFind(List<String> lines, String pattern)
+        {
+            List<String> hits = new ArrayList<>();
+
+            Pattern patKey = Pattern.compile(pattern,Pattern.CASE_INSENSITIVE);
+
+            Matcher mat;
+            for (String line : lines)
+            {
+                mat = patKey.matcher(line);
+                if (mat.matches())
+                {
+                    if (mat.groupCount() >= 1)
+                    {
+                        hits.add(mat.group(1));
+                    }
+                    else
+                    {
+                        hits.add(mat.group(0));
+                    }
+                }
+            }
+
+            return hits;
+        }
+
+        public void respond(String rawstr) throws IOException
+        {
+            LOG.debug("respond(){}{}","\n",rawstr);
+            getOutputStream().write(rawstr.getBytes());
+            flush();
+        }
+
+        @Override
+        public void run()
+        {
+            LOG.debug("Entering echo thread");
+
+            ByteBuffer buf = bufferPool.acquire(BUFFER_SIZE,false);
+            BufferUtil.clearToFill(buf);
+            long readBytes = 0;
+            try
+            {
+                while (echoing.get())
+                {
+                    BufferUtil.clearToFill(buf);
+                    long len = read(buf);
+                    if (len > 0)
+                    {
+                        readBytes += len;
+                        LOG.debug("Read {} bytes",len);
+                        BufferUtil.flipToFlush(buf,0);
+                        parser.parse(buf);
+                    }
+
+                    try
+                    {
+                        TimeUnit.MILLISECONDS.sleep(20);
+                    }
+                    catch (InterruptedException gnore)
+                    {
+                        /* ignore */
+                    }
+                }
+            }
+            catch (IOException e)
+            {
+                LOG.debug("Exception during echo loop",e);
+            }
+            finally
+            {
+                LOG.debug("Read {} bytes",readBytes);
+                bufferPool.release(buf);
+            }
+        }
+
+        public void setSoTimeout(int ms) throws SocketException
+        {
+            socket.setSoTimeout(ms);
+        }
+
+        public void startEcho()
+        {
+            if (echoThread != null)
+            {
+                throw new IllegalStateException("Echo thread already declared!");
+            }
+            echoThread = new Thread(this,"BlockheadServer/Echo");
+            echoing.set(true);
+            echoThread.start();
+        }
+
+        public void stopEcho()
+        {
+            echoing.set(false);
+        }
+
+        public List<String> upgrade() throws IOException
+        {
+            List<String> requestLines = readRequestLines();
+            List<ExtensionConfig> extensionConfigs = parseExtensions(requestLines);
+            String key = parseWebSocketKey(requestLines);
+
+            LOG.debug("Client Request Extensions: {}",extensionConfigs);
+            LOG.debug("Client Request Key: {}",key);
+
+            Assert.assertThat("Request: Sec-WebSocket-Key",key,notNullValue());
+
+            // collect extensions configured in response header
+            ExtensionStack extensionStack = new ExtensionStack(extensionRegistry);
+            extensionStack.negotiate(extensionConfigs);
+
+            // Start with default routing
+            extensionStack.setNextIncoming(this);
+            extensionStack.setNextOutgoing(this);
+
+            // Configure Parser / Generator
+            extensionStack.configure(parser);
+            extensionStack.configure(generator);
+
+            // Start Stack
+            try
+            {
+                extensionStack.start();
+            }
+            catch (Exception e)
+            {
+                throw new IOException("Unable to start Extension Stack");
+            }
+
+            // Configure Parser
+            parser.setIncomingFramesHandler(extensionStack);
+
+            // Setup Response
+            StringBuilder resp = new StringBuilder();
+            resp.append("HTTP/1.1 101 Upgrade\r\n");
+            resp.append("Connection: upgrade\r\n");
+            resp.append("Sec-WebSocket-Accept: ");
+            resp.append(AcceptHash.hashKey(key)).append("\r\n");
+            if (extensionStack.hasNegotiatedExtensions())
+            {
+                // Respond to used extensions
+                resp.append("Sec-WebSocket-Extensions: ");
+                boolean delim = false;
+                for (ExtensionConfig ext : extensionStack.getNegotiatedExtensions())
+                {
+                    if (delim)
+                    {
+                        resp.append(", ");
+                    }
+                    resp.append(ext.getParameterizedName());
+                    delim = true;
+                }
+                resp.append("\r\n");
+            }
+            if (extraResponseHeaders.size() > 0)
+            {
+                for (Map.Entry<String, String> xheader : extraResponseHeaders.entrySet())
+                {
+                    resp.append(xheader.getKey());
+                    resp.append(": ");
+                    resp.append(xheader.getValue());
+                    resp.append("\r\n");
+                }
+            }
+            resp.append("\r\n");
+
+            // Write Response
+            LOG.debug("Response: {}",resp.toString());
+            write(resp.toString().getBytes());
+            return requestLines;
+        }
+
+        private void write(byte[] bytes) throws IOException
+        {
+            getOutputStream().write(bytes);
+        }
+
+        public void write(byte[] buf, int offset, int length) throws IOException
+        {
+            getOutputStream().write(buf,offset,length);
+        }
+
+        public void write(Frame frame) throws IOException
+        {
+            LOG.debug("write(Frame->{}) to {}",frame,outgoing);
+            outgoing.outgoingFrame(frame,null,BatchMode.OFF);
+        }
+
+        public void write(int b) throws IOException
+        {
+            getOutputStream().write(b);
+        }
+
+        public void write(ByteBuffer buf) throws IOException
+        {
+            byte arr[] = BufferUtil.toArray(buf);
+            if ((arr != null) && (arr.length > 0))
+            {
+                getOutputStream().write(arr);
+            }
+        }
+    }
+
+    private static final Logger LOG = Log.getLogger(BlockheadServer.class);
+    private ServerSocket serverSocket;
+    private URI wsUri;
+
+    public ServerConnection accept() throws IOException
+    {
+        LOG.debug(".accept()");
+        assertIsStarted();
+        Socket socket = serverSocket.accept();
+        return new ServerConnection(socket);
+    }
+
+    private void assertIsStarted()
+    {
+        Assert.assertThat("ServerSocket",serverSocket,notNullValue());
+        Assert.assertThat("ServerSocket.isBound",serverSocket.isBound(),is(true));
+        Assert.assertThat("ServerSocket.isClosed",serverSocket.isClosed(),is(false));
+
+        Assert.assertThat("WsUri",wsUri,notNullValue());
+    }
+
+    public URI getWsUri()
+    {
+        return wsUri;
+    }
+
+    public void respondToClient(Socket connection, String serverResponse) throws IOException
+    {
+        InputStream in = null;
+        InputStreamReader isr = null;
+        BufferedReader buf = null;
+        OutputStream out = null;
+        try
+        {
+            in = connection.getInputStream();
+            isr = new InputStreamReader(in);
+            buf = new BufferedReader(isr);
+            String line;
+            while ((line = buf.readLine()) != null)
+            {
+                // System.err.println(line);
+                if (line.length() == 0)
+                {
+                    // Got the "\r\n" line.
+                    break;
+                }
+            }
+
+            // System.out.println("[Server-Out] " + serverResponse);
+            out = connection.getOutputStream();
+            out.write(serverResponse.getBytes());
+            out.flush();
+        }
+        finally
+        {
+            IO.close(buf);
+            IO.close(isr);
+            IO.close(in);
+            IO.close(out);
+        }
+    }
+
+    public void start() throws IOException
+    {
+        InetAddress addr = InetAddress.getByName("localhost");
+        serverSocket = new ServerSocket();
+        InetSocketAddress endpoint = new InetSocketAddress(addr,0);
+        serverSocket.bind(endpoint,1);
+        int port = serverSocket.getLocalPort();
+        String uri = String.format("ws://%s:%d/",addr.getHostAddress(),port);
+        wsUri = URI.create(uri);
+        LOG.debug("Server Started on {} -> {}",endpoint,wsUri);
+    }
+
+    public void stop()
+    {
+        LOG.debug("Stopping Server");
+        try
+        {
+            serverSocket.close();
+        }
+        catch (IOException ignore)
+        {
+            /* ignore */
+        }
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/ByteBufferAssert.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/ByteBufferAssert.java
new file mode 100644
index 0000000..9c0e02f
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/ByteBufferAssert.java
@@ -0,0 +1,68 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.test;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.BufferUtil;
+
+public class ByteBufferAssert
+{
+    public static void assertEquals(String message, byte[] expected, byte[] actual)
+    {
+        assertThat(message + " byte[].length",actual.length,is(expected.length));
+        int len = expected.length;
+        for (int i = 0; i < len; i++)
+        {
+            assertThat(message + " byte[" + i + "]",actual[i],is(expected[i]));
+        }
+    }
+
+    public static void assertEquals(String message, ByteBuffer expectedBuffer, ByteBuffer actualBuffer)
+    {
+        if (expectedBuffer == null)
+        {
+            assertThat(message,actualBuffer,nullValue());
+        }
+        else
+        {
+            byte expectedBytes[] = BufferUtil.toArray(expectedBuffer);
+            byte actualBytes[] = BufferUtil.toArray(actualBuffer);
+            assertEquals(message,expectedBytes,actualBytes);
+        }
+    }
+
+    public static void assertEquals(String message, String expectedString, ByteBuffer actualBuffer)
+    {
+        String actualString = BufferUtil.toString(actualBuffer);
+        assertThat(message,actualString,is(expectedString));
+    }
+
+    public static void assertSize(String message, int expectedSize, ByteBuffer buffer)
+    {
+        if ((expectedSize == 0) && (buffer == null))
+        {
+            return;
+        }
+        assertThat(message + " buffer.remaining",buffer.remaining(),is(expectedSize));
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/Fuzzed.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/Fuzzed.java
new file mode 100644
index 0000000..1b1f222
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/Fuzzed.java
@@ -0,0 +1,32 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.test;
+
+import java.net.URI;
+
+import org.eclipse.jetty.websocket.common.Generator;
+
+public interface Fuzzed
+{
+    URI getServerURI();
+
+    Generator getLaxGenerator();
+
+    String getTestMethodName();
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/Fuzzer.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/Fuzzer.java
new file mode 100644
index 0000000..d1dcd3c
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/Fuzzer.java
@@ -0,0 +1,343 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.test;
+
+import static org.hamcrest.Matchers.*;
+
+import java.io.IOException;
+import java.net.SocketException;
+import java.nio.ByteBuffer;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.toolchain.test.EventQueue;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.common.CloseInfo;
+import org.eclipse.jetty.websocket.common.Generator;
+import org.eclipse.jetty.websocket.common.OpCode;
+import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.io.IOState;
+import org.junit.Assert;
+
+/**
+ * Fuzzing utility for the AB tests.
+ */
+public class Fuzzer implements AutoCloseable
+{
+    public static enum CloseState
+    {
+        OPEN,
+        REMOTE_INITIATED,
+        LOCAL_INITIATED
+    }
+
+    public static enum SendMode
+    {
+        BULK,
+        PER_FRAME,
+        SLOW
+    }
+
+    public static enum DisconnectMode
+    {
+        /** Disconnect occurred after a proper close handshake */
+        CLEAN,
+        /** Disconnect occurred in a harsh manner, without a close handshake */
+        UNCLEAN
+    }
+
+    private static final int KBYTE = 1024;
+    private static final int MBYTE = KBYTE * KBYTE;
+
+    private static final Logger LOG = Log.getLogger(Fuzzer.class);
+
+    // Client side framing mask
+    protected static final byte[] MASK =
+    { 0x11, 0x22, 0x33, 0x44 };
+
+    private final BlockheadClient client;
+    private final Generator generator;
+    private final String testname;
+    private SendMode sendMode = SendMode.BULK;
+    private int slowSendSegmentSize = 5;
+
+    public Fuzzer(Fuzzed testcase) throws Exception
+    {
+        WebSocketPolicy policy = WebSocketPolicy.newClientPolicy();
+
+        int bigMessageSize = 20 * MBYTE;
+
+        policy.setMaxTextMessageSize(bigMessageSize);
+        policy.setMaxBinaryMessageSize(bigMessageSize);
+        policy.setIdleTimeout(5000);
+
+        this.client = new BlockheadClient(policy,testcase.getServerURI());
+        this.client.setTimeout(2,TimeUnit.SECONDS);
+        this.generator = testcase.getLaxGenerator();
+        this.testname = testcase.getTestMethodName();
+    }
+
+    public ByteBuffer asNetworkBuffer(List<WebSocketFrame> send)
+    {
+        int buflen = 0;
+        for (Frame f : send)
+        {
+            buflen += f.getPayloadLength() + Generator.MAX_HEADER_LENGTH;
+        }
+        ByteBuffer buf = ByteBuffer.allocate(buflen);
+
+        // Generate frames
+        for (WebSocketFrame f : send)
+        {
+            setClientMask(f);
+            generator.generateWholeFrame(f,buf);
+        }
+        buf.flip();
+        return buf;
+    }
+    
+    @Override
+    public void close() throws Exception
+    {
+        this.client.disconnect();
+    }
+
+    public void disconnect()
+    {
+        this.client.disconnect();
+    }
+
+    public void connect() throws IOException
+    {
+        if (!client.isConnected())
+        {
+            client.connect();
+            client.addHeader("X-TestCase: " + testname + "\r\n");
+            client.sendStandardRequest();
+            client.expectUpgradeResponse();
+        }
+    }
+
+    public void expect(List<WebSocketFrame> expect) throws Exception
+    {
+        expect(expect,10,TimeUnit.SECONDS);
+    }
+
+    public void expect(List<WebSocketFrame> expect, int duration, TimeUnit unit) throws Exception
+    {
+        int expectedCount = expect.size();
+        LOG.debug("expect() {} frame(s)",expect.size());
+
+        // Read frames
+        EventQueue<WebSocketFrame> frames = client.readFrames(expect.size(),duration,unit);
+        
+        String prefix = "";
+        for (int i = 0; i < expectedCount; i++)
+        {
+            WebSocketFrame expected = expect.get(i);
+            WebSocketFrame actual = frames.poll();
+
+            prefix = "Frame[" + i + "]";
+
+            LOG.debug("{} {}",prefix,actual);
+
+            Assert.assertThat(prefix + ".opcode",OpCode.name(actual.getOpCode()),is(OpCode.name(expected.getOpCode())));
+            prefix += "/" + actual.getOpCode();
+            if (expected.getOpCode() == OpCode.CLOSE)
+            {
+                CloseInfo expectedClose = new CloseInfo(expected);
+                CloseInfo actualClose = new CloseInfo(actual);
+                Assert.assertThat(prefix + ".statusCode",actualClose.getStatusCode(),is(expectedClose.getStatusCode()));
+            }
+            else
+            {
+                Assert.assertThat(prefix + ".payloadLength",actual.getPayloadLength(),is(expected.getPayloadLength()));
+                ByteBufferAssert.assertEquals(prefix + ".payload",expected.getPayload(),actual.getPayload());
+            }
+        }
+    }
+
+    public void expect(WebSocketFrame expect) throws Exception
+    {
+        expect(Collections.singletonList(expect));
+    }
+
+    public void expectNoMoreFrames()
+    {
+        // TODO Should test for no more frames. success if connection closed.
+    }
+
+    public CloseState getCloseState()
+    {
+        IOState ios = client.getIOState();
+
+        if (ios.wasLocalCloseInitiated())
+        {
+            return CloseState.LOCAL_INITIATED;
+        }
+        else if (ios.wasRemoteCloseInitiated())
+        {
+            return CloseState.REMOTE_INITIATED;
+        }
+        else
+        {
+            return CloseState.OPEN;
+        }
+    }
+
+    public SendMode getSendMode()
+    {
+        return sendMode;
+    }
+
+    public int getSlowSendSegmentSize()
+    {
+        return slowSendSegmentSize;
+    }
+
+    public void send(ByteBuffer buf) throws IOException
+    {
+        Assert.assertThat("Client connected",client.isConnected(),is(true));
+        LOG.debug("Sending bytes {}",BufferUtil.toDetailString(buf));
+        if (sendMode == SendMode.SLOW)
+        {
+            client.writeRawSlowly(buf,slowSendSegmentSize);
+        }
+        else
+        {
+            client.writeRaw(buf);
+        }
+    }
+
+    public void send(ByteBuffer buf, int numBytes) throws IOException
+    {
+        client.writeRaw(buf,numBytes);
+        client.flush();
+    }
+
+    public void send(List<WebSocketFrame> send) throws IOException
+    {
+        Assert.assertThat("Client connected",client.isConnected(),is(true));
+        LOG.debug("[{}] Sending {} frames (mode {})",testname,send.size(),sendMode);
+        if ((sendMode == SendMode.BULK) || (sendMode == SendMode.SLOW))
+        {
+            int buflen = 0;
+            for (Frame f : send)
+            {
+                buflen += f.getPayloadLength() + Generator.MAX_HEADER_LENGTH;
+            }
+            ByteBuffer buf = ByteBuffer.allocate(buflen);
+
+            // Generate frames
+            for (WebSocketFrame f : send)
+            {
+                setClientMask(f);
+                buf.put(generator.generateHeaderBytes(f));
+                if (f.hasPayload())
+                {
+                    buf.put(f.getPayload());
+                }
+            }
+            BufferUtil.flipToFlush(buf,0);
+
+            // Write Data Frame
+            switch (sendMode)
+            {
+                case BULK:
+                    client.writeRaw(buf);
+                    break;
+                case SLOW:
+                    client.writeRawSlowly(buf,slowSendSegmentSize);
+                    break;
+                default:
+                    throw new RuntimeException("Whoops, unsupported sendMode: " + sendMode);
+            }
+        }
+        else if (sendMode == SendMode.PER_FRAME)
+        {
+            for (WebSocketFrame f : send)
+            {
+                f.setMask(MASK); // make sure we have mask set
+                // Using lax generator, generate and send
+                ByteBuffer fullframe = ByteBuffer.allocate(f.getPayloadLength() + Generator.MAX_HEADER_LENGTH);
+                BufferUtil.clearToFill(fullframe);
+                generator.generateWholeFrame(f,fullframe);
+                BufferUtil.flipToFlush(fullframe,0);
+                client.writeRaw(fullframe);
+                client.flush();
+            }
+        }
+    }
+
+    public void send(WebSocketFrame send) throws IOException
+    {
+        send(Collections.singletonList(send));
+    }
+
+    public void sendAndIgnoreBrokenPipe(List<WebSocketFrame> send) throws IOException
+    {
+        try
+        {
+            send(send);
+        }
+        catch (SocketException ignore)
+        {
+            // Potential for SocketException (Broken Pipe) here.
+            // But not in 100% of testing scenarios. It is a safe
+            // exception to ignore in this testing scenario, as the
+            // slow writing of the frames can result in the server
+            // throwing a PROTOCOL ERROR termination/close when it
+            // encounters the bad continuation frame above (this
+            // termination is the expected behavior), and this
+            // early socket close can propagate back to the client
+            // before it has a chance to finish writing out the
+            // remaining frame octets
+            Assert.assertThat("Allowed to be a broken pipe",ignore.getMessage().toLowerCase(Locale.ENGLISH),containsString("broken pipe"));
+        }
+    }
+
+    private void setClientMask(WebSocketFrame f)
+    {
+        if (LOG.isDebugEnabled())
+        {
+            f.setMask(new byte[]
+            { 0x00, 0x00, 0x00, 0x00 });
+        }
+        else
+        {
+            f.setMask(MASK); // make sure we have mask set
+        }
+    }
+
+    public void setSendMode(SendMode sendMode)
+    {
+        this.sendMode = sendMode;
+    }
+
+    public void setSlowSendSegmentSize(int segmentSize)
+    {
+        this.slowSendSegmentSize = segmentSize;
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/HttpResponse.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/HttpResponse.java
new file mode 100644
index 0000000..9ae6f75
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/HttpResponse.java
@@ -0,0 +1,94 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.test;
+
+import java.nio.ByteBuffer;
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.eclipse.jetty.websocket.common.io.http.HttpResponseHeaderParseListener;
+
+public class HttpResponse implements HttpResponseHeaderParseListener
+{
+    private int statusCode;
+    private String statusReason;
+    private Map<String, String> headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+    private ByteBuffer remainingBuffer;
+
+    @Override
+    public void addHeader(String name, String value)
+    {
+        headers.put(name,value);
+    }
+
+    public String getExtensionsHeader()
+    {
+        return getHeader("Sec-WebSocket-Extensions");
+    }
+
+    public String getHeader(String name)
+    {
+        return headers.get(name);
+    }
+
+    public ByteBuffer getRemainingBuffer()
+    {
+        return remainingBuffer;
+    }
+
+    public int getStatusCode()
+    {
+        return statusCode;
+    }
+
+    public String getStatusReason()
+    {
+        return statusReason;
+    }
+
+    @Override
+    public void setRemainingBuffer(ByteBuffer copy)
+    {
+        this.remainingBuffer = copy;
+    }
+
+    @Override
+    public void setStatusCode(int code)
+    {
+        this.statusCode = code;
+    }
+
+    @Override
+    public void setStatusReason(String reason)
+    {
+        this.statusReason = reason;
+    }
+
+    @Override
+    public String toString()
+    {
+        StringBuilder str = new StringBuilder();
+        str.append("HTTP/1.1 ").append(statusCode).append(' ').append(statusReason);
+        for (Map.Entry<String, String> entry : headers.entrySet())
+        {
+            str.append('\n').append(entry.getKey()).append(": ").append(entry.getValue());
+        }
+        return str.toString();
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/IncomingFramesCapture.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/IncomingFramesCapture.java
new file mode 100644
index 0000000..5fc94ac
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/IncomingFramesCapture.java
@@ -0,0 +1,163 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.test;
+
+import static org.hamcrest.Matchers.*;
+
+import java.util.Queue;
+
+import org.eclipse.jetty.toolchain.test.EventQueue;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.WebSocketException;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.api.extensions.IncomingFrames;
+import org.eclipse.jetty.websocket.common.OpCode;
+import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.junit.Assert;
+
+public class IncomingFramesCapture implements IncomingFrames
+{
+    private static final Logger LOG = Log.getLogger(IncomingFramesCapture.class);
+    private EventQueue<WebSocketFrame> frames = new EventQueue<>();
+    private EventQueue<Throwable> errors = new EventQueue<>();
+
+    public void assertErrorCount(int expectedCount)
+    {
+        Assert.assertThat("Captured error count",errors.size(),is(expectedCount));
+    }
+
+    public void assertFrameCount(int expectedCount)
+    {
+        if (frames.size() != expectedCount)
+        {
+            // dump details
+            System.err.printf("Expected %d frame(s)%n",expectedCount);
+            System.err.printf("But actually captured %d frame(s)%n",frames.size());
+            int i = 0;
+            for (Frame frame : frames)
+            {
+                System.err.printf(" [%d] Frame[%s] - %s%n", i++, 
+                        OpCode.name(frame.getOpCode()),
+                        BufferUtil.toDetailString(frame.getPayload()));
+            }
+        }
+        Assert.assertThat("Captured frame count",frames.size(),is(expectedCount));
+    }
+
+    public void assertHasErrors(Class<? extends WebSocketException> errorType, int expectedCount)
+    {
+        Assert.assertThat(errorType.getSimpleName(),getErrorCount(errorType),is(expectedCount));
+    }
+
+    public void assertHasFrame(byte op)
+    {
+        Assert.assertThat(OpCode.name(op),getFrameCount(op),greaterThanOrEqualTo(1));
+    }
+
+    public void assertHasFrame(byte op, int expectedCount)
+    {
+        String msg = String.format("%s frame count",OpCode.name(op));
+        Assert.assertThat(msg,getFrameCount(op),is(expectedCount));
+    }
+
+    public void assertHasNoFrames()
+    {
+        Assert.assertThat("Frame count",frames.size(),is(0));
+    }
+
+    public void assertNoErrors()
+    {
+        Assert.assertThat("Error count",errors.size(),is(0));
+    }
+
+    public void clear()
+    {
+        frames.clear();
+    }
+
+    public void dump()
+    {
+        System.err.printf("Captured %d incoming frames%n",frames.size());
+        int i = 0;
+        for (Frame frame : frames)
+        {
+            System.err.printf("[%3d] %s%n",i++,frame);
+            System.err.printf("          payload: %s%n",BufferUtil.toDetailString(frame.getPayload()));
+        }
+    }
+
+    public int getErrorCount(Class<? extends Throwable> errorType)
+    {
+        int count = 0;
+        for (Throwable error : errors)
+        {
+            if (errorType.isInstance(error))
+            {
+                count++;
+            }
+        }
+        return count;
+    }
+
+    public Queue<Throwable> getErrors()
+    {
+        return errors;
+    }
+
+    public int getFrameCount(byte op)
+    {
+        int count = 0;
+        for (WebSocketFrame frame : frames)
+        {
+            if (frame.getOpCode() == op)
+            {
+                count++;
+            }
+        }
+        return count;
+    }
+
+    public Queue<WebSocketFrame> getFrames()
+    {
+        return frames;
+    }
+
+    @Override
+    public void incomingError(Throwable e)
+    {
+        LOG.debug(e);
+        errors.add(e);
+    }
+
+    @Override
+    public void incomingFrame(Frame frame)
+    {
+        WebSocketFrame copy = WebSocketFrame.copy(frame);
+        // TODO: might need to make this optional (depending on use by client vs server tests)
+        // Assert.assertThat("frame.masking must be set",frame.isMasked(),is(true));
+        frames.add(copy);
+    }
+
+    public int size()
+    {
+        return frames.size();
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/LeakTrackingBufferPoolRule.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/LeakTrackingBufferPoolRule.java
new file mode 100644
index 0000000..49224ed
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/LeakTrackingBufferPoolRule.java
@@ -0,0 +1,61 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.test;
+
+import org.eclipse.jetty.io.LeakTrackingByteBufferPool;
+import org.eclipse.jetty.io.MappedByteBufferPool;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
+public class LeakTrackingBufferPoolRule extends LeakTrackingByteBufferPool implements TestRule
+{
+    private final String id;
+
+    public LeakTrackingBufferPoolRule(String id)
+    {
+        super(new MappedByteBufferPool.Tagged());
+        this.id = id;
+    }
+
+    public void assertNoLeaks()
+    {
+        assertThat("Leaked Acquires Count for [" + id + "]",getLeakedAcquires(),is(0L));
+        assertThat("Leaked Releases Count for [" + id + "]",getLeakedReleases(),is(0L));
+        assertThat("Leaked Resource Count for [" + id + "]", getLeakedResources(),is(0L));
+    }
+
+    @Override
+    public Statement apply(final Statement statement, Description description)
+    {
+        return new Statement()
+        {
+            @Override
+            public void evaluate() throws Throwable
+            {
+                clearTracking();
+                statement.evaluate();
+                assertNoLeaks();
+            }
+        };
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/OutgoingFramesCapture.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/OutgoingFramesCapture.java
new file mode 100644
index 0000000..2ed9db7
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/OutgoingFramesCapture.java
@@ -0,0 +1,97 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.test;
+
+import java.util.LinkedList;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.websocket.api.BatchMode;
+import org.eclipse.jetty.websocket.api.WriteCallback;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.api.extensions.OutgoingFrames;
+import org.eclipse.jetty.websocket.common.OpCode;
+import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.junit.Assert;
+
+import static org.hamcrest.Matchers.greaterThanOrEqualTo;
+import static org.hamcrest.Matchers.is;
+
+public class OutgoingFramesCapture implements OutgoingFrames
+{
+    private LinkedList<WebSocketFrame> frames = new LinkedList<>();
+
+    public void assertFrameCount(int expectedCount)
+    {
+        Assert.assertThat("Captured frame count",frames.size(),is(expectedCount));
+    }
+
+    public void assertHasFrame(byte op)
+    {
+        Assert.assertThat(OpCode.name(op),getFrameCount(op),greaterThanOrEqualTo(1));
+    }
+
+    public void assertHasFrame(byte op, int expectedCount)
+    {
+        Assert.assertThat(OpCode.name(op),getFrameCount(op),is(expectedCount));
+    }
+
+    public void assertHasNoFrames()
+    {
+        Assert.assertThat("Has no frames",frames.size(),is(0));
+    }
+
+    public void dump()
+    {
+        System.out.printf("Captured %d outgoing writes%n",frames.size());
+        for (int i = 0; i < frames.size(); i++)
+        {
+            Frame frame = frames.get(i);
+            System.out.printf("[%3d] %s%n",i,frame);
+            System.out.printf("      %s%n",BufferUtil.toDetailString(frame.getPayload()));
+        }
+    }
+
+    public int getFrameCount(byte op)
+    {
+        int count = 0;
+        for (WebSocketFrame frame : frames)
+        {
+            if (frame.getOpCode() == op)
+            {
+                count++;
+            }
+        }
+        return count;
+    }
+
+    public LinkedList<WebSocketFrame> getFrames()
+    {
+        return frames;
+    }
+
+    @Override
+    public void outgoingFrame(Frame frame, WriteCallback callback, BatchMode batchMode)
+    {
+        frames.add(WebSocketFrame.copy(frame));
+        if (callback != null)
+        {
+            callback.writeSuccess();
+        }
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/OutgoingNetworkBytesCapture.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/OutgoingNetworkBytesCapture.java
new file mode 100644
index 0000000..ee7ae9f
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/OutgoingNetworkBytesCapture.java
@@ -0,0 +1,77 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.test;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.websocket.api.BatchMode;
+import org.eclipse.jetty.websocket.api.WriteCallback;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.api.extensions.OutgoingFrames;
+import org.eclipse.jetty.websocket.common.Generator;
+import org.junit.Assert;
+
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.lessThan;
+
+/**
+ * Capture outgoing network bytes.
+ */
+public class OutgoingNetworkBytesCapture implements OutgoingFrames
+{
+    private final Generator generator;
+    private List<ByteBuffer> captured;
+
+    public OutgoingNetworkBytesCapture(Generator generator)
+    {
+        this.generator = generator;
+        this.captured = new ArrayList<>();
+    }
+
+    public void assertBytes(int idx, String expectedHex)
+    {
+        Assert.assertThat("Capture index does not exist",idx,lessThan(captured.size()));
+        ByteBuffer buf = captured.get(idx);
+        String actualHex = TypeUtil.toHexString(BufferUtil.toArray(buf)).toUpperCase(Locale.ENGLISH);
+        Assert.assertThat("captured[" + idx + "]",actualHex,is(expectedHex.toUpperCase(Locale.ENGLISH)));
+    }
+
+    public List<ByteBuffer> getCaptured()
+    {
+        return captured;
+    }
+
+    @Override
+    public void outgoingFrame(Frame frame, WriteCallback callback, BatchMode batchMode)
+    {
+        ByteBuffer buf = ByteBuffer.allocate(Generator.MAX_HEADER_LENGTH + frame.getPayloadLength());
+        generator.generateWholeFrame(frame,buf);
+        BufferUtil.flipToFlush(buf,0);
+        captured.add(buf);
+        if (callback != null)
+        {
+            callback.writeSuccess();
+        }
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/RawFrameBuilder.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/RawFrameBuilder.java
new file mode 100644
index 0000000..3f814b5
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/RawFrameBuilder.java
@@ -0,0 +1,110 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.test;
+
+import static org.hamcrest.Matchers.*;
+
+import java.nio.ByteBuffer;
+
+import org.junit.Assert;
+
+public class RawFrameBuilder
+{
+    public static void putOpFin(ByteBuffer buf, byte opcode, boolean fin)
+    {
+        byte b = 0x00;
+        if (fin)
+        {
+            b |= 0x80;
+        }
+        b |= opcode & 0x0F;
+        buf.put(b);
+    }
+
+    public static void putLengthAndMask(ByteBuffer buf, int length, byte mask[])
+    {
+        if (mask != null)
+        {
+            Assert.assertThat("Mask.length",mask.length,is(4));
+            putLength(buf,length,(mask != null));
+            buf.put(mask);
+        }
+        else
+        {
+            putLength(buf,length,false);
+        }
+    }
+
+    public static byte[] mask(final byte[] data, final byte mask[])
+    {
+        Assert.assertThat("Mask.length",mask.length,is(4));
+        int len = data.length;
+        byte ret[] = new byte[len];
+        System.arraycopy(data,0,ret,0,len);
+        for (int i = 0; i < len; i++)
+        {
+            ret[i] ^= mask[i % 4];
+        }
+        return ret;
+    }
+
+    public static void putLength(ByteBuffer buf, int length, boolean masked)
+    {
+        if (length < 0)
+        {
+            throw new IllegalArgumentException("Length cannot be negative");
+        }
+        byte b = (masked?(byte)0x80:0x00);
+
+        // write the uncompressed length
+        if (length > 0xFF_FF)
+        {
+            buf.put((byte)(b | 0x7F));
+            buf.put((byte)0x00);
+            buf.put((byte)0x00);
+            buf.put((byte)0x00);
+            buf.put((byte)0x00);
+            buf.put((byte)((length >> 24) & 0xFF));
+            buf.put((byte)((length >> 16) & 0xFF));
+            buf.put((byte)((length >> 8) & 0xFF));
+            buf.put((byte)(length & 0xFF));
+        }
+        else if (length >= 0x7E)
+        {
+            buf.put((byte)(b | 0x7E));
+            buf.put((byte)(length >> 8));
+            buf.put((byte)(length & 0xFF));
+        }
+        else
+        {
+            buf.put((byte)(b | length));
+        }
+    }
+
+    public static void putMask(ByteBuffer buf, byte mask[])
+    {
+        Assert.assertThat("Mask.length",mask.length,is(4));
+        buf.put(mask);
+    }
+
+    public static void putPayloadLength(ByteBuffer buf, int length)
+    {
+        putLength(buf,length,true);
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/UnitGenerator.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/UnitGenerator.java
new file mode 100644
index 0000000..df9f841
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/UnitGenerator.java
@@ -0,0 +1,134 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.test;
+
+import java.nio.ByteBuffer;
+import java.util.List;
+
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.LeakTrackingByteBufferPool;
+import org.eclipse.jetty.io.MappedByteBufferPool;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.common.Generator;
+import org.eclipse.jetty.websocket.common.WebSocketFrame;
+
+/**
+ * Convenience Generator.
+ */
+public class UnitGenerator extends Generator
+{
+    private static final Logger LOG = Log.getLogger(UnitGenerator.class);
+
+    public static ByteBuffer generate(Frame frame)
+    {
+        return generate(new Frame[]
+        { frame });
+    }
+
+    /**
+     * Generate All Frames into a single ByteBuffer.
+     * <p>
+     * This is highly inefficient and is not used in production! (This exists to make testing of the Generator easier)
+     * 
+     * @param frames
+     *            the frames to generate from
+     * @return the ByteBuffer representing all of the generated frames provided.
+     */
+    public static ByteBuffer generate(Frame[] frames)
+    {
+        Generator generator = new UnitGenerator();
+
+        // Generate into single bytebuffer
+        int buflen = 0;
+        for (Frame f : frames)
+        {
+            buflen += f.getPayloadLength() + Generator.MAX_HEADER_LENGTH;
+        }
+        ByteBuffer completeBuf = ByteBuffer.allocate(buflen);
+        BufferUtil.clearToFill(completeBuf);
+
+        // Generate frames
+        for (Frame f : frames)
+        {
+            generator.generateWholeFrame(f,completeBuf);
+        }
+
+        BufferUtil.flipToFlush(completeBuf,0);
+        if (LOG.isDebugEnabled())
+        {
+            LOG.debug("generate({} frames) - {}",frames.length,BufferUtil.toDetailString(completeBuf));
+        }
+        return completeBuf;
+    }
+
+    /**
+     * Generate a single giant buffer of all provided frames Not appropriate for production code, but useful for testing.
+     */
+    public static ByteBuffer generate(List<WebSocketFrame> frames)
+    {
+        // Create non-symmetrical mask (helps show mask bytes order issues)
+        byte[] MASK =
+        { 0x11, 0x22, 0x33, 0x44 };
+
+        // the generator
+        Generator generator = new UnitGenerator();
+
+        // Generate into single bytebuffer
+        int buflen = 0;
+        for (Frame f : frames)
+        {
+            buflen += f.getPayloadLength() + Generator.MAX_HEADER_LENGTH;
+        }
+        ByteBuffer completeBuf = ByteBuffer.allocate(buflen);
+        BufferUtil.clearToFill(completeBuf);
+
+        // Generate frames
+        for (WebSocketFrame f : frames)
+        {
+            f.setMask(MASK); // make sure we have the test mask set
+            BufferUtil.put(generator.generateHeaderBytes(f),completeBuf);
+            ByteBuffer window = f.getPayload();
+            if (BufferUtil.hasContent(window))
+            {
+                BufferUtil.put(window,completeBuf);
+            }
+        }
+
+        BufferUtil.flipToFlush(completeBuf,0);
+        if (LOG.isDebugEnabled())
+        {
+            LOG.debug("generate({} frames) - {}",frames.size(),BufferUtil.toDetailString(completeBuf));
+        }
+        return completeBuf;
+    }
+
+    public UnitGenerator()
+    {
+        super(WebSocketPolicy.newServerPolicy(),new LeakTrackingByteBufferPool(new MappedByteBufferPool.Tagged()));
+    }
+    
+    public UnitGenerator(ByteBufferPool bufferPool)
+    {
+        super(WebSocketPolicy.newServerPolicy(),bufferPool);
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/UnitParser.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/UnitParser.java
new file mode 100644
index 0000000..b93776c
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/UnitParser.java
@@ -0,0 +1,73 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.test;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.io.LeakTrackingByteBufferPool;
+import org.eclipse.jetty.io.MappedByteBufferPool;
+import org.eclipse.jetty.util.log.StacklessLogging;
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.common.Parser;
+
+public class UnitParser extends Parser
+{
+    public UnitParser()
+    {
+        this(WebSocketPolicy.newServerPolicy());
+    }
+
+    public UnitParser(WebSocketPolicy policy)
+    {
+        super(policy,new LeakTrackingByteBufferPool(new MappedByteBufferPool.Tagged()));
+    }
+
+    private void parsePartial(ByteBuffer buf, int numBytes)
+    {
+        int len = Math.min(numBytes,buf.remaining());
+        byte arr[] = new byte[len];
+        buf.get(arr,0,len);
+        this.parse(ByteBuffer.wrap(arr));
+    }
+
+    /**
+     * Parse a buffer, but do so in a quiet fashion, squelching stacktraces if encountered.
+     * <p>
+     * Use if you know the parse will cause an exception and just don't wnat to make the test console all noisy.
+     */
+    public void parseQuietly(ByteBuffer buf)
+    {
+        try (StacklessLogging suppress = new StacklessLogging(Parser.class))
+        {
+            parse(buf);
+        }
+        catch (Exception ignore)
+        {
+            /* ignore */
+        }
+    }
+
+    public void parseSlowly(ByteBuffer buf, int segmentSize)
+    {
+        while (buf.remaining() > 0)
+        {
+            parsePartial(buf,segmentSize);
+        }
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/util/Hex.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/util/Hex.java
new file mode 100644
index 0000000..7d32fd4
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/util/Hex.java
@@ -0,0 +1,80 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.util;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.BufferUtil;
+
+public final class Hex
+{
+    private static final char[] hexcodes = "0123456789ABCDEF".toCharArray();
+
+    public static byte[] asByteArray(String hstr)
+    {
+        if ((hstr.length() < 0) || ((hstr.length() % 2) != 0))
+        {
+            throw new IllegalArgumentException(String.format("Invalid string length of <%d>",hstr.length()));
+        }
+
+        int size = hstr.length() / 2;
+        byte buf[] = new byte[size];
+        byte hex;
+        int len = hstr.length();
+
+        int idx = (int)Math.floor(((size * 2) - (double)len) / 2);
+        for (int i = 0; i < len; i++)
+        {
+            hex = 0;
+            if (i >= 0)
+            {
+                hex = (byte)(Character.digit(hstr.charAt(i),16) << 4);
+            }
+            i++;
+            hex += (byte)(Character.digit(hstr.charAt(i),16));
+
+            buf[idx] = hex;
+            idx++;
+        }
+
+        return buf;
+    }
+
+    public static ByteBuffer asByteBuffer(String hstr)
+    {
+        return ByteBuffer.wrap(asByteArray(hstr));
+    }
+
+    public static String asHex(byte buf[])
+    {
+        int len = buf.length;
+        char out[] = new char[len * 2];
+        for (int i = 0; i < len; i++)
+        {
+            out[i * 2] = hexcodes[(buf[i] & 0xF0) >> 4];
+            out[(i * 2) + 1] = hexcodes[(buf[i] & 0x0F)];
+        }
+        return String.valueOf(out);
+    }
+
+    public static String asHex(ByteBuffer buffer)
+    {
+        return asHex(BufferUtil.toArray(buffer));
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/util/MaskedByteBuffer.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/util/MaskedByteBuffer.java
new file mode 100644
index 0000000..7640f09
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/util/MaskedByteBuffer.java
@@ -0,0 +1,50 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.util;
+
+import java.nio.ByteBuffer;
+
+public class MaskedByteBuffer
+{
+    private static byte[] mask = new byte[]
+            { 0x00, (byte)0xF0, 0x0F, (byte)0xFF };
+
+    public static void putMask(ByteBuffer buffer)
+    {
+        buffer.put(mask,0,mask.length);
+    }
+
+    public static void putPayload(ByteBuffer buffer, byte[] payload)
+    {
+        int len = payload.length;
+        for (int i = 0; i < len; i++)
+        {
+            buffer.put((byte)(payload[i] ^ mask[i % 4]));
+        }
+    }
+
+    public static void putPayload(ByteBuffer buffer, ByteBuffer payload)
+    {
+        int len = payload.remaining();
+        for (int i = 0; i < len; i++)
+        {
+            buffer.put((byte)(payload.get() ^ mask[i % 4]));
+        }
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/util/StackUtil.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/util/StackUtil.java
new file mode 100644
index 0000000..62f5841
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/util/StackUtil.java
@@ -0,0 +1,42 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.util;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+public final class StackUtil
+{
+    public static String toString(Throwable t)
+    {
+        try (StringWriter w = new StringWriter())
+        {
+            try (PrintWriter out = new PrintWriter(w))
+            {
+                t.printStackTrace(out);
+                return w.toString();
+            }
+        }
+        catch (IOException e)
+        {
+            return "Unable to get stacktrace for: " + t;
+        }
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/test/resources/jetty-logging.properties b/jetty-websocket/websocket-common/src/test/resources/jetty-logging.properties
new file mode 100644
index 0000000..683b76a
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/resources/jetty-logging.properties
@@ -0,0 +1,7 @@
+org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
+org.eclipse.jetty.LEVEL=WARN
+# org.eclipse.jetty.websocket.LEVEL=DEBUG
+# org.eclipse.jetty.websocket.protocol.Parser.LEVEL=DEBUG
+# org.eclipse.jetty.websocket.protocol.LEVEL=DEBUG
+# org.eclipse.jetty.websocket.io.payload.LEVEL=DEBUG
+# org.eclipse.jetty.websocket.common.extensions.LEVEL=DEBUG
diff --git a/jetty-websocket/src/test/resources/keystore b/jetty-websocket/websocket-common/src/test/resources/keystore
similarity index 100%
rename from jetty-websocket/src/test/resources/keystore
rename to jetty-websocket/websocket-common/src/test/resources/keystore
diff --git a/jetty-websocket/src/test/webapp/index.html b/jetty-websocket/websocket-common/src/test/webapp/index.html
similarity index 100%
rename from jetty-websocket/src/test/webapp/index.html
rename to jetty-websocket/websocket-common/src/test/webapp/index.html
diff --git a/jetty-websocket/websocket-server/pom.xml b/jetty-websocket/websocket-server/pom.xml
new file mode 100644
index 0000000..150f69a
--- /dev/null
+++ b/jetty-websocket/websocket-server/pom.xml
@@ -0,0 +1,105 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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">
+    <parent>
+        <groupId>org.eclipse.jetty.websocket</groupId>
+        <artifactId>websocket-parent</artifactId>
+        <version>9.2.13.v20150730</version>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>websocket-server</artifactId>
+    <name>Jetty :: Websocket :: Server</name>
+
+    <properties>
+        <bundle-symbolic-name>${project.groupId}.server</bundle-symbolic-name>
+    </properties>
+
+    <build>
+        <plugins>
+          <plugin>
+          <groupId>org.apache.felix</groupId>
+          <artifactId>maven-bundle-plugin</artifactId>
+          <extensions>true</extensions>
+          <executions>
+            <execution>
+              <id>generate-manifest</id>
+              <goals>
+                <goal>manifest</goal>
+              </goals>
+              <configuration>
+                <instructions>
+                  <Require-Capability>osgi.extender; filter:="(osgi.extender=osgi.serviceloader.registrar)"</Require-Capability>
+                  <Provide-Capability>osgi.serviceloader; osgi.serviceloader=org.eclipse.jetty.websocket.servlet.WebSocketServletFactory</Provide-Capability>
+                  <_nouses>true</_nouses>
+                </instructions>
+              </configuration>
+            </execution>
+          </executions>
+        </plugin>
+            <plugin>
+              <groupId>org.apache.maven.plugins</groupId>
+              <artifactId>maven-jar-plugin</artifactId>
+              <executions>
+                <execution>
+                  <id>artifact-jars</id>
+                  <goals>
+                    <goal>jar</goal>
+                    <goal>test-jar</goal>
+                  </goals>
+                </execution>
+              </executions>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.eclipse.jetty.websocket</groupId>
+            <artifactId>websocket-common</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty.websocket</groupId>
+            <artifactId>websocket-client</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty.websocket</groupId>
+            <artifactId>websocket-servlet</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-servlet</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>javax.servlet-api</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-http</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-server</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty.websocket</groupId>
+            <artifactId>websocket-common</artifactId>
+            <version>${project.version}</version>
+            <classifier>tests</classifier>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty.toolchain</groupId>
+            <artifactId>jetty-test-helper</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/HandshakeRFC6455.java b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/HandshakeRFC6455.java
new file mode 100644
index 0000000..234587e
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/HandshakeRFC6455.java
@@ -0,0 +1,66 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server;
+
+import java.io.IOException;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
+import org.eclipse.jetty.websocket.common.AcceptHash;
+import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
+import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
+
+/**
+ * WebSocket Handshake for <a href="https://tools.ietf.org/html/rfc6455">RFC 6455</a>.
+ */
+public class HandshakeRFC6455 implements WebSocketHandshake
+{
+    /** RFC 6455 - Sec-WebSocket-Version */
+    public static final int VERSION = 13;
+
+    @Override
+    public void doHandshakeResponse(ServletUpgradeRequest request, ServletUpgradeResponse response) throws IOException
+    {
+        String key = request.getHeader("Sec-WebSocket-Key");
+
+        if (key == null)
+        {
+            throw new IllegalStateException("Missing request header 'Sec-WebSocket-Key'");
+        }
+
+        // build response
+        response.setHeader("Upgrade","WebSocket");
+        response.addHeader("Connection","Upgrade");
+        response.addHeader("Sec-WebSocket-Accept",AcceptHash.hashKey(key));
+
+        if (response.getExtensions() != null)
+        {
+            String value = ExtensionConfig.toHeaderValue(response.getExtensions());
+            if (value != null)
+            {
+                response.addHeader("Sec-WebSocket-Extensions",value);
+            }
+        }
+
+        request.complete();
+
+        response.setStatus(HttpServletResponse.SC_SWITCHING_PROTOCOLS);
+        response.complete();
+    }
+}
diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/MappedWebSocketCreator.java b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/MappedWebSocketCreator.java
new file mode 100644
index 0000000..c8d5192
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/MappedWebSocketCreator.java
@@ -0,0 +1,33 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server;
+
+import org.eclipse.jetty.websocket.server.pathmap.PathMappings;
+import org.eclipse.jetty.websocket.server.pathmap.PathSpec;
+import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
+
+/**
+ * Common interface for MappedWebSocketCreator
+ */
+public interface MappedWebSocketCreator
+{
+    public void addMapping(PathSpec spec, WebSocketCreator creator);
+
+    public PathMappings<WebSocketCreator> getMappings();
+}
diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/ServletWebSocketRequest.java b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/ServletWebSocketRequest.java
new file mode 100644
index 0000000..c145303
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/ServletWebSocketRequest.java
@@ -0,0 +1,37 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server;
+
+import java.net.URISyntaxException;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
+
+/**
+ * @deprecated use {@link org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest} instead
+ */
+ at Deprecated
+public class ServletWebSocketRequest extends ServletUpgradeRequest
+{
+    public ServletWebSocketRequest(HttpServletRequest request) throws URISyntaxException
+    {
+        super(request);
+    }
+}
diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/ServletWebSocketResponse.java b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/ServletWebSocketResponse.java
new file mode 100644
index 0000000..b244e52
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/ServletWebSocketResponse.java
@@ -0,0 +1,35 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
+
+/**
+ * @deprecated use {@link org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse} instead
+ */
+ at Deprecated
+public class ServletWebSocketResponse extends ServletUpgradeResponse
+{
+    public ServletWebSocketResponse(HttpServletResponse resp)
+    {
+        super(resp);
+    }
+}
diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketHandler.java b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketHandler.java
new file mode 100644
index 0000000..a36b2e3
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketHandler.java
@@ -0,0 +1,114 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.MappedByteBufferPool;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.handler.HandlerWrapper;
+import org.eclipse.jetty.websocket.api.WebSocketBehavior;
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
+
+public abstract class WebSocketHandler extends HandlerWrapper
+{
+    /**
+     * Create a simple WebSocketHandler that registers a single WebSocket POJO that is created on every upgrade request.
+     */
+    public static class Simple extends WebSocketHandler
+    {
+        private Class<?> websocketPojo;
+
+        public Simple(Class<?> websocketClass)
+        {
+            super();
+            this.websocketPojo = websocketClass;
+        }
+
+        @Override
+        public void configure(WebSocketServletFactory factory)
+        {
+            factory.register(websocketPojo);
+        }
+    }
+
+    private final WebSocketServletFactory webSocketFactory;
+
+    public WebSocketHandler()
+    {
+        this(new MappedByteBufferPool());
+    }
+    
+    public WebSocketHandler(ByteBufferPool bufferPool)
+    {
+        WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.SERVER);
+        configurePolicy(policy);
+        webSocketFactory = new WebSocketServerFactory(policy, bufferPool);
+        addBean(webSocketFactory);
+    }
+
+    public abstract void configure(WebSocketServletFactory factory);
+
+    public void configurePolicy(WebSocketPolicy policy)
+    {
+        /* leave at default */
+    }
+
+    @Override
+    protected void doStart() throws Exception
+    {
+        configure(webSocketFactory);
+        super.doStart();
+    }
+
+    public WebSocketServletFactory getWebSocketFactory()
+    {
+        return webSocketFactory;
+    }
+
+    @Override
+    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+    {
+        if (webSocketFactory.isUpgradeRequest(request,response))
+        {
+            // We have an upgrade request
+            if (webSocketFactory.acceptWebSocket(request,response))
+            {
+                // We have a socket instance created
+                baseRequest.setHandled(true);
+                return;
+            }
+            // If we reach this point, it means we had an incoming request to upgrade
+            // but it was either not a proper websocket upgrade, or it was possibly rejected
+            // due to incoming request constraints (controlled by WebSocketCreator)
+            if (response.isCommitted())
+            {
+                // not much we can do at this point.
+                return;
+            }
+        }
+        super.handle(target,baseRequest,request,response);
+    }
+}
diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketHandshake.java b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketHandshake.java
new file mode 100644
index 0000000..4628c8b
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketHandshake.java
@@ -0,0 +1,35 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server;
+
+import java.io.IOException;
+
+import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
+import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
+
+public interface WebSocketHandshake
+{
+    /**
+     * Formulate a WebSocket upgrade handshake response.
+     * 
+     * @param request
+     * @param response
+     */
+    public void doHandshakeResponse(ServletUpgradeRequest request, ServletUpgradeResponse response) throws IOException;
+}
diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketServerConnection.java b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketServerConnection.java
new file mode 100644
index 0000000..25731f5
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketServerConnection.java
@@ -0,0 +1,82 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server;
+
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.thread.Scheduler;
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.api.extensions.IncomingFrames;
+import org.eclipse.jetty.websocket.common.io.AbstractWebSocketConnection;
+
+public class WebSocketServerConnection extends AbstractWebSocketConnection implements Connection.UpgradeTo
+{
+    private final AtomicBoolean opened = new AtomicBoolean(false);
+
+    public WebSocketServerConnection(EndPoint endp, Executor executor, Scheduler scheduler, WebSocketPolicy policy, ByteBufferPool bufferPool)
+    {
+        super(endp,executor,scheduler,policy,bufferPool);
+        if (policy.getIdleTimeout() > 0)
+        {
+            endp.setIdleTimeout(policy.getIdleTimeout());
+        }
+    }
+
+    @Override
+    public InetSocketAddress getLocalAddress()
+    {
+        return getEndPoint().getLocalAddress();
+    }
+
+    @Override
+    public InetSocketAddress getRemoteAddress()
+    {
+        return getEndPoint().getRemoteAddress();
+    }
+
+    @Override
+    public void onUpgradeTo(ByteBuffer prefilled)
+    {
+        prefill(prefilled);
+    }
+
+    @Override
+    public void onOpen()
+    {
+        boolean beenOpened = opened.getAndSet(true);
+        if (!beenOpened)
+        {
+            getSession().open();
+        }
+        super.onOpen();
+    }
+    
+    @Override
+    public void setNextIncomingFrames(IncomingFrames incoming)
+    {
+        getParser().setIncomingFramesHandler(incoming);
+    }
+}
diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketServerFactory.java b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketServerFactory.java
new file mode 100644
index 0000000..da3be60
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketServerFactory.java
@@ -0,0 +1,581 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.concurrent.Executor;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.MappedByteBufferPool;
+import org.eclipse.jetty.server.HttpConnection;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
+import org.eclipse.jetty.util.thread.Scheduler;
+import org.eclipse.jetty.websocket.api.InvalidWebSocketException;
+import org.eclipse.jetty.websocket.api.StatusCode;
+import org.eclipse.jetty.websocket.api.WebSocketException;
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.api.extensions.ExtensionFactory;
+import org.eclipse.jetty.websocket.api.util.QuoteUtil;
+import org.eclipse.jetty.websocket.common.LogicalConnection;
+import org.eclipse.jetty.websocket.common.SessionFactory;
+import org.eclipse.jetty.websocket.common.SessionListener;
+import org.eclipse.jetty.websocket.common.WebSocketSession;
+import org.eclipse.jetty.websocket.common.WebSocketSessionFactory;
+import org.eclipse.jetty.websocket.common.events.EventDriver;
+import org.eclipse.jetty.websocket.common.events.EventDriverFactory;
+import org.eclipse.jetty.websocket.common.extensions.ExtensionStack;
+import org.eclipse.jetty.websocket.common.extensions.WebSocketExtensionFactory;
+import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
+import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
+import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
+import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
+
+/**
+ * Factory to create WebSocket connections
+ */
+public class WebSocketServerFactory extends ContainerLifeCycle implements WebSocketCreator, WebSocketServletFactory, SessionListener
+{
+    private static final Logger LOG = Log.getLogger(WebSocketServerFactory.class);
+
+    private final ClassLoader contextClassloader;
+    private final Map<Integer, WebSocketHandshake> handshakes = new HashMap<>();
+    /**
+     * Have the factory maintain 1 and only 1 scheduler. All connections share this scheduler.
+     */
+    private final Scheduler scheduler = new ScheduledExecutorScheduler();
+    private final String supportedVersions;
+    private final WebSocketPolicy defaultPolicy;
+    private final EventDriverFactory eventDriverFactory;
+    private final ByteBufferPool bufferPool;
+    private final WebSocketExtensionFactory extensionFactory;
+    private List<SessionFactory> sessionFactories;
+    private Set<WebSocketSession> openSessions = new CopyOnWriteArraySet<>();
+    private WebSocketCreator creator;
+    private List<Class<?>> registeredSocketClasses;
+
+    public WebSocketServerFactory()
+    {
+        this(WebSocketPolicy.newServerPolicy(), new MappedByteBufferPool());
+    }
+
+    public WebSocketServerFactory(WebSocketPolicy policy)
+    {
+        this(policy, new MappedByteBufferPool());
+    }
+
+    public WebSocketServerFactory(ByteBufferPool bufferPool)
+    {
+        this(WebSocketPolicy.newServerPolicy(), bufferPool);
+    }
+
+    public WebSocketServerFactory(WebSocketPolicy policy, ByteBufferPool bufferPool)
+    {
+        handshakes.put(HandshakeRFC6455.VERSION, new HandshakeRFC6455());
+
+        addBean(scheduler);
+        addBean(bufferPool);
+        
+        this.contextClassloader = Thread.currentThread().getContextClassLoader();
+
+        this.registeredSocketClasses = new ArrayList<>();
+
+        this.defaultPolicy = policy;
+        this.eventDriverFactory = new EventDriverFactory(defaultPolicy);
+        this.bufferPool = bufferPool;
+        this.extensionFactory = new WebSocketExtensionFactory(defaultPolicy, this.bufferPool);
+        
+        // Bug #431459 - unregistering compression extensions till they are more stable
+        this.extensionFactory.unregister("deflate-frame");
+        this.extensionFactory.unregister("permessage-deflate");
+        this.extensionFactory.unregister("x-webkit-deflate-frame");
+        
+        this.sessionFactories = new ArrayList<>();
+        this.sessionFactories.add(new WebSocketSessionFactory(this));
+        this.creator = this;
+
+        // Create supportedVersions
+        List<Integer> versions = new ArrayList<>();
+        for (int v : handshakes.keySet())
+        {
+            versions.add(v);
+        }
+        Collections.sort(versions, Collections.reverseOrder()); // newest first
+        StringBuilder rv = new StringBuilder();
+        for (int v : versions)
+        {
+            if (rv.length() > 0)
+            {
+                rv.append(", ");
+            }
+            rv.append(v);
+        }
+        supportedVersions = rv.toString();
+    }
+
+    @Override
+    public boolean acceptWebSocket(HttpServletRequest request, HttpServletResponse response) throws IOException
+    {
+        return acceptWebSocket(getCreator(), request, response);
+    }
+
+    @Override
+    public boolean acceptWebSocket(WebSocketCreator creator, HttpServletRequest request, HttpServletResponse response) throws IOException
+    {
+        ClassLoader old = Thread.currentThread().getContextClassLoader();
+        try
+        {
+            Thread.currentThread().setContextClassLoader(contextClassloader);
+            ServletUpgradeRequest sockreq = new ServletUpgradeRequest(request);
+            ServletUpgradeResponse sockresp = new ServletUpgradeResponse(response);
+
+            Object websocketPojo = creator.createWebSocket(sockreq, sockresp);
+
+            // Handle response forbidden (and similar paths)
+            if (sockresp.isCommitted())
+            {
+                return false;
+            }
+
+            if (websocketPojo == null)
+            {
+                // no creation, sorry
+                sockresp.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, "Endpoint Creation Failed");
+                return false;
+            }
+
+            // Get the original HTTPConnection
+            HttpConnection connection = (HttpConnection)request.getAttribute("org.eclipse.jetty.server.HttpConnection");
+            
+            // Send the upgrade
+            EventDriver driver = eventDriverFactory.wrap(websocketPojo);
+            return upgrade(connection, sockreq, sockresp, driver);
+        }
+        catch (URISyntaxException e)
+        {
+            throw new IOException("Unable to accept websocket due to mangled URI", e);
+        } 
+        finally
+        {
+            Thread.currentThread().setContextClassLoader(old);
+        }
+    }
+
+    public void addSessionFactory(SessionFactory sessionFactory)
+    {
+        if (sessionFactories.contains(sessionFactory))
+        {
+            return;
+        }
+        this.sessionFactories.add(sessionFactory);
+    }
+
+    @Override
+    public void cleanup()
+    {
+        try
+        {
+            this.stop();
+        }
+        catch (Exception e)
+        {
+            LOG.warn(e);
+        }
+    }
+
+    protected void shutdownAllConnections()
+    {
+        for (WebSocketSession session : openSessions)
+        {
+            if (session.getConnection() != null)
+            {
+                try
+                {
+                    session.getConnection().close(
+                            StatusCode.SHUTDOWN,
+                            "Shutdown");
+                }
+                catch (Throwable t)
+                {
+                    LOG.debug("During Shutdown All Connections",t);
+                }
+            }
+        }
+        openSessions.clear();
+    }
+
+    @Override
+    public WebSocketServletFactory createFactory(WebSocketPolicy policy)
+    {
+        return new WebSocketServerFactory(policy, bufferPool);
+    }
+
+    private WebSocketSession createSession(URI requestURI, EventDriver websocket, LogicalConnection connection)
+    {
+        if (websocket == null)
+        {
+            throw new InvalidWebSocketException("Unable to create Session from null websocket");
+        }
+
+        for (SessionFactory impl : sessionFactories)
+        {
+            if (impl.supports(websocket))
+            {
+                try
+                {
+                    return impl.createSession(requestURI, websocket, connection);
+                }
+                catch (Throwable e)
+                {
+                    throw new InvalidWebSocketException("Unable to create Session", e);
+                }
+            }
+        }
+
+        throw new InvalidWebSocketException("Unable to create Session: unrecognized internal EventDriver type: " + websocket.getClass().getName());
+    }
+
+    /**
+     * Default Creator logic
+     */
+    @Override
+    public Object createWebSocket(ServletUpgradeRequest req, ServletUpgradeResponse resp)
+    {
+        if (registeredSocketClasses.size() < 1)
+        {
+            throw new WebSocketException("No WebSockets have been registered with the factory.  Cannot use default implementation of WebSocketCreator.");
+        }
+
+        if (registeredSocketClasses.size() > 1)
+        {
+            LOG.warn("You have registered more than 1 websocket object, and are using the default WebSocketCreator! Using first registered websocket.");
+        }
+
+        Class<?> firstClass = registeredSocketClasses.get(0);
+        try
+        {
+            return firstClass.newInstance();
+        }
+        catch (InstantiationException | IllegalAccessException e)
+        {
+            throw new WebSocketException("Unable to create instance of " + firstClass, e);
+        }
+    }
+
+    @Override
+    protected void doStop() throws Exception
+    {
+        shutdownAllConnections();
+        super.doStop();
+    }
+
+    @Override
+    public WebSocketCreator getCreator()
+    {
+        return this.creator;
+    }
+
+    public EventDriverFactory getEventDriverFactory()
+    {
+        return eventDriverFactory;
+    }
+
+    @Override
+    public ExtensionFactory getExtensionFactory()
+    {
+        return extensionFactory;
+    }
+
+    public Set<WebSocketSession> getOpenSessions()
+    {
+        return Collections.unmodifiableSet(this.openSessions);
+    }
+
+    @Override
+    public WebSocketPolicy getPolicy()
+    {
+        return defaultPolicy;
+    }
+
+    @Override
+    public void init() throws Exception
+    {
+        start(); // start lifecycle
+    }
+
+    @Override
+    public boolean isUpgradeRequest(HttpServletRequest request, HttpServletResponse response)
+    {
+        if (!"GET".equalsIgnoreCase(request.getMethod()))
+        {
+            // not a "GET" request (not a websocket upgrade)
+            return false;
+        }
+
+        String connection = request.getHeader("connection");
+        if (connection == null)
+        {
+            // no "Connection: upgrade" header present.
+            return false;
+        }
+
+        // Test for "Upgrade" token
+        boolean foundUpgradeToken = false;
+        Iterator<String> iter = QuoteUtil.splitAt(connection, ",");
+        while (iter.hasNext())
+        {
+            String token = iter.next();
+            if ("upgrade".equalsIgnoreCase(token))
+            {
+                foundUpgradeToken = true;
+                break;
+            }
+        }
+
+        if (!foundUpgradeToken)
+        {
+            return false;
+        }
+
+        String upgrade = request.getHeader("Upgrade");
+        if (upgrade == null)
+        {
+            // no "Upgrade: websocket" header present.
+            return false;
+        }
+
+        if (!"websocket".equalsIgnoreCase(upgrade))
+        {
+            LOG.debug("Not a 'Upgrade: WebSocket' (was [Upgrade: " + upgrade + "])");
+            return false;
+        }
+
+        if (!"HTTP/1.1".equals(request.getProtocol()))
+        {
+            LOG.debug("Not a 'HTTP/1.1' request (was [" + request.getProtocol() + "])");
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public void onSessionClosed(WebSocketSession session)
+    {
+        this.openSessions.remove(session);
+    }
+
+    @Override
+    public void onSessionOpened(WebSocketSession session)
+    {
+        this.openSessions.add(session);
+    }
+
+    protected String[] parseProtocols(String protocol)
+    {
+        if (protocol == null)
+        {
+            return new String[]{null};
+        }
+        protocol = protocol.trim();
+        if (protocol.length() == 0)
+        {
+            return new String[]{null};
+        }
+        String[] passed = protocol.split("\\s*,\\s*");
+        String[] protocols = new String[passed.length + 1];
+        System.arraycopy(passed, 0, protocols, 0, passed.length);
+        return protocols;
+    }
+
+    @Override
+    public void register(Class<?> websocketPojo)
+    {
+        registeredSocketClasses.add(websocketPojo);
+    }
+
+    @Override
+    public void setCreator(WebSocketCreator creator)
+    {
+        this.creator = creator;
+    }
+
+    /**
+     * Upgrade the request/response to a WebSocket Connection.
+     * <p/>
+     * This method will not normally return, but will instead throw a UpgradeConnectionException, to exit HTTP handling and initiate WebSocket handling of the
+     * connection.
+     *
+     * @param http     the raw http connection
+     * @param request  The request to upgrade
+     * @param response The response to upgrade
+     * @param driver   The websocket handler implementation to use
+     * @throws IOException
+     */
+    private boolean upgrade(HttpConnection http, ServletUpgradeRequest request, ServletUpgradeResponse response, EventDriver driver) throws IOException
+    {
+        if (!"websocket".equalsIgnoreCase(request.getHeader("Upgrade")))
+        {
+            throw new IllegalStateException("Not a 'WebSocket: Upgrade' request");
+        }
+        if (!"HTTP/1.1".equals(request.getHttpVersion()))
+        {
+            throw new IllegalStateException("Not a 'HTTP/1.1' request");
+        }
+
+        int version = request.getHeaderInt("Sec-WebSocket-Version");
+        if (version < 0)
+        {
+            // Old pre-RFC version specifications (header not present in RFC-6455)
+            version = request.getHeaderInt("Sec-WebSocket-Draft");
+        }
+
+        WebSocketHandshake handshaker = handshakes.get(version);
+        if (handshaker == null)
+        {
+            StringBuilder warn = new StringBuilder();
+            warn.append("Client ").append(request.getRemoteAddress());
+            warn.append(" (:").append(request.getRemotePort());
+            warn.append(") User Agent: ");
+            String ua = request.getHeader("User-Agent");
+            if (ua == null)
+            {
+                warn.append("[unset] ");
+            }
+            else
+            {
+                warn.append('"').append(StringUtil.sanitizeXmlString(ua)).append("\" ");
+            }
+            warn.append("requested WebSocket version [").append(version);
+            warn.append("], Jetty supports version");
+            if (handshakes.size() > 1)
+            {
+                warn.append('s');
+            }
+            warn.append(": [").append(supportedVersions).append("]");
+            LOG.warn(warn.toString());
+
+            // Per RFC 6455 - 4.4 - Supporting Multiple Versions of WebSocket Protocol
+            // Using the examples as outlined
+            response.setHeader("Sec-WebSocket-Version", supportedVersions);
+            response.sendError(HttpStatus.BAD_REQUEST_400, "Unsupported websocket version specification");
+            return false;
+        }
+
+        // Initialize / Negotiate Extensions
+        ExtensionStack extensionStack = new ExtensionStack(getExtensionFactory());
+        // The JSR allows for the extensions to be pre-negotiated, filtered, etc...
+        // Usually from a Configurator.
+        if (response.isExtensionsNegotiated())
+        {
+            // Use pre-negotiated extension list from response
+            extensionStack.negotiate(response.getExtensions());
+        }
+        else
+        {
+            // Use raw extension list from request
+            extensionStack.negotiate(request.getExtensions());
+        }
+
+        // Get original HTTP connection
+        EndPoint endp = http.getEndPoint();
+        Executor executor = http.getConnector().getExecutor();
+        ByteBufferPool bufferPool = http.getConnector().getByteBufferPool();
+        
+        // Setup websocket connection
+        WebSocketServerConnection wsConnection = new WebSocketServerConnection(endp, executor, scheduler, driver.getPolicy(), bufferPool);
+
+        extensionStack.setPolicy(driver.getPolicy());
+        extensionStack.configure(wsConnection.getParser());
+        extensionStack.configure(wsConnection.getGenerator());
+
+        if (LOG.isDebugEnabled())
+        {
+            LOG.debug("HttpConnection: {}", http);
+            LOG.debug("WebSocketConnection: {}", wsConnection);
+        }
+
+        // Setup Session
+        WebSocketSession session = createSession(request.getRequestURI(), driver, wsConnection);
+        session.setPolicy(driver.getPolicy());
+        session.setUpgradeRequest(request);
+        // set true negotiated extension list back to response 
+        response.setExtensions(extensionStack.getNegotiatedExtensions());
+        session.setUpgradeResponse(response);
+        wsConnection.setSession(session);
+
+        // Setup Incoming Routing
+        wsConnection.setNextIncomingFrames(extensionStack);
+        extensionStack.setNextIncoming(session);
+
+        // Setup Outgoing Routing
+        session.setOutgoingHandler(extensionStack);
+        extensionStack.setNextOutgoing(wsConnection);
+
+        // Start Components
+        try
+        {
+            session.start();
+        }
+        catch (Exception e)
+        {
+            throw new IOException("Unable to start Session", e);
+        }
+        try
+        {
+            extensionStack.start();
+        }
+        catch (Exception e)
+        {
+            throw new IOException("Unable to start Extension Stack", e);
+        }
+
+        // Tell jetty about the new upgraded connection
+        request.setServletAttribute(HttpConnection.UPGRADE_CONNECTION_ATTRIBUTE, wsConnection);
+
+        if (LOG.isDebugEnabled())
+            LOG.debug("Handshake Response: {}", handshaker);
+
+        // Process (version specific) handshake response
+        handshaker.doHandshakeResponse(request, response);
+
+        if (LOG.isDebugEnabled())
+            LOG.debug("Websocket upgrade {} {} {} {}", request.getRequestURI(), version, response.getAcceptedSubProtocol(), wsConnection);
+
+        return true;
+    }
+}
diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketUpgradeFilter.java b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketUpgradeFilter.java
new file mode 100644
index 0000000..ff3a34e
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketUpgradeFilter.java
@@ -0,0 +1,345 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server;
+
+import java.io.IOException;
+import java.util.EnumSet;
+import javax.servlet.DispatcherType;
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.FilterRegistration;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.MappedByteBufferPool;
+import org.eclipse.jetty.servlet.FilterHolder;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.component.Dumpable;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.server.pathmap.PathMappings;
+import org.eclipse.jetty.websocket.server.pathmap.PathMappings.MappedResource;
+import org.eclipse.jetty.websocket.server.pathmap.PathSpec;
+import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
+
+/**
+ * Inline Servlet Filter to capture WebSocket upgrade requests and perform path mappings to {@link WebSocketCreator} objects.
+ */
+ at ManagedObject("WebSocket Upgrade Filter")
+public class WebSocketUpgradeFilter extends ContainerLifeCycle implements Filter, MappedWebSocketCreator, Dumpable
+{
+    public static final String CONTEXT_ATTRIBUTE_KEY = "contextAttributeKey";
+    private static final Logger LOG = Log.getLogger(WebSocketUpgradeFilter.class);
+
+    public static WebSocketUpgradeFilter configureContext(ServletContextHandler context) throws ServletException
+    {
+        // Prevent double configure
+        WebSocketUpgradeFilter filter = (WebSocketUpgradeFilter)context.getAttribute(WebSocketUpgradeFilter.class.getName());
+        if (filter != null)
+        {
+            return filter;
+        }
+        
+        // Dynamically add filter
+        filter = new WebSocketUpgradeFilter();
+        filter.setToAttribute(context, WebSocketUpgradeFilter.class.getName());
+
+        String name = "Jetty_WebSocketUpgradeFilter";
+        String pathSpec = "/*";
+        EnumSet<DispatcherType> dispatcherTypes = EnumSet.of(DispatcherType.REQUEST);
+
+        FilterHolder fholder = new FilterHolder(filter);
+        fholder.setName(name);
+        fholder.setAsyncSupported(true);
+        fholder.setInitParameter(CONTEXT_ATTRIBUTE_KEY,WebSocketUpgradeFilter.class.getName());
+        context.addFilter(fholder,pathSpec,dispatcherTypes);
+
+        if (LOG.isDebugEnabled())
+        {
+            LOG.debug("Adding [{}] {} mapped to {} to {}",name,filter,pathSpec,context);
+        }
+
+        return filter;
+    }
+
+    public static WebSocketUpgradeFilter configureContext(ServletContext context) throws ServletException
+    {
+        // Prevent double configure
+        WebSocketUpgradeFilter filter = (WebSocketUpgradeFilter)context.getAttribute(WebSocketUpgradeFilter.class.getName());
+        if (filter != null)
+        {
+            return filter;
+        }
+        
+        // Dynamically add filter
+        filter = new WebSocketUpgradeFilter();
+        filter.setToAttribute(context, WebSocketUpgradeFilter.class.getName());
+
+        String name = "Jetty_Dynamic_WebSocketUpgradeFilter";
+        String pathSpec = "/*";
+        EnumSet<DispatcherType> dispatcherTypes = EnumSet.of(DispatcherType.REQUEST);
+        boolean isMatchAfter = false;
+        String urlPatterns[] = { pathSpec };
+
+        FilterRegistration.Dynamic dyn = context.addFilter(name,filter);
+        dyn.setAsyncSupported(true);
+        dyn.setInitParameter(CONTEXT_ATTRIBUTE_KEY,WebSocketUpgradeFilter.class.getName());
+        dyn.addMappingForUrlPatterns(dispatcherTypes,isMatchAfter,urlPatterns);
+
+        if (LOG.isDebugEnabled())
+        {
+            LOG.debug("Adding [{}] {} mapped to {} to {}",name,filter,pathSpec,context);
+        }
+
+        return filter;
+    }
+
+    private final WebSocketServerFactory factory;
+    private final PathMappings<WebSocketCreator> pathmap = new PathMappings<>();
+    private String fname;
+    private boolean alreadySetToAttribute = false;
+
+    public WebSocketUpgradeFilter()
+    {
+        this(WebSocketPolicy.newServerPolicy(),new MappedByteBufferPool());
+    }
+
+    public WebSocketUpgradeFilter(WebSocketPolicy policy, ByteBufferPool bufferPool)
+    {
+        factory = new WebSocketServerFactory(policy,bufferPool);
+        addBean(factory,true);
+    }
+
+    @Override
+    public void addMapping(PathSpec spec, WebSocketCreator creator)
+    {
+        pathmap.put(spec,creator);
+    }
+
+    @Override
+    public void destroy()
+    {
+        factory.cleanup();
+        pathmap.reset();
+        super.destroy();
+    }
+
+    @Override
+    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
+    {
+        if (factory == null)
+        {
+            // no factory, cannot operate
+            LOG.debug("WebSocketUpgradeFilter is not operational - no WebSocketServletFactory configured");
+            chain.doFilter(request,response);
+            return;
+        }
+
+        if (LOG.isDebugEnabled())
+        {
+            LOG.debug(".doFilter({}) - {}",fname,chain);
+        }
+
+        if ((request instanceof HttpServletRequest) && (response instanceof HttpServletResponse))
+        {
+            HttpServletRequest httpreq = (HttpServletRequest)request;
+            HttpServletResponse httpresp = (HttpServletResponse)response;
+
+            // Since this is a filter, we need to be smart about determining the target path
+            String contextPath = httpreq.getContextPath();
+            String target = httpreq.getRequestURI();
+            if (target.startsWith(contextPath))
+            {
+                target = target.substring(contextPath.length());
+            }
+
+            if (factory.isUpgradeRequest(httpreq,httpresp))
+            {
+                LOG.debug("target = [{}]",target);
+
+                MappedResource<WebSocketCreator> resource = pathmap.getMatch(target);
+                if (resource == null)
+                {
+                    if (LOG.isDebugEnabled())
+                    {
+                        LOG.debug("WebSocket Upgrade on {} has no associated endpoint",target);
+                        LOG.debug("PathMappings: {}",pathmap.dump());
+                    }
+                    // no match.
+                    chain.doFilter(request,response);
+                    return;
+                }
+                LOG.debug("WebSocket Upgrade detected on {} for endpoint {}",target,resource);
+
+                WebSocketCreator creator = resource.getResource();
+
+                // Store PathSpec resource mapping as request attribute
+                httpreq.setAttribute(PathSpec.class.getName(),resource.getPathSpec());
+
+                // We have an upgrade request
+                if (factory.acceptWebSocket(creator,httpreq,httpresp))
+                {
+                    // We have a socket instance created
+                    return;
+                }
+
+                // If we reach this point, it means we had an incoming request to upgrade
+                // but it was either not a proper websocket upgrade, or it was possibly rejected
+                // due to incoming request constraints (controlled by WebSocketCreator)
+                if (response.isCommitted())
+                {
+                    // not much we can do at this point.
+                    return;
+                }
+            }
+        }
+
+        // not an Upgrade request
+        chain.doFilter(request,response);
+    }
+
+    @Override
+    public String dump()
+    {
+        return ContainerLifeCycle.dump(this);
+    }
+
+    @Override
+    public void dump(Appendable out, String indent) throws IOException
+    {
+        out.append(indent).append(" +- pathmap=").append(pathmap.toString()).append("\n");
+        pathmap.dump(out,indent + "   ");
+    }
+
+    public WebSocketServerFactory getFactory()
+    {
+        return factory;
+    }
+
+    @ManagedAttribute(value = "mappings", readonly = true)
+    @Override
+    public PathMappings<WebSocketCreator> getMappings()
+    {
+        return pathmap;
+    }
+
+    @Override
+    public void init(FilterConfig config) throws ServletException
+    {
+        fname = config.getFilterName();
+
+        try
+        {
+            WebSocketPolicy policy = factory.getPolicy();
+
+            String max = config.getInitParameter("maxIdleTime");
+            if (max != null)
+            {
+                policy.setIdleTimeout(Long.parseLong(max));
+            }
+
+            max = config.getInitParameter("maxTextMessageSize");
+            if (max != null)
+            {
+                policy.setMaxTextMessageSize(Integer.parseInt(max));
+            }
+
+            max = config.getInitParameter("maxBinaryMessageSize");
+            if (max != null)
+            {
+                policy.setMaxBinaryMessageSize(Integer.parseInt(max));
+            }
+
+            max = config.getInitParameter("inputBufferSize");
+            if (max != null)
+            {
+                policy.setInputBufferSize(Integer.parseInt(max));
+            }
+
+            String key = config.getInitParameter(CONTEXT_ATTRIBUTE_KEY);
+            if (key == null)
+            {
+                // assume default
+                key = WebSocketUpgradeFilter.class.getName();
+            }
+            
+            setToAttribute(config.getServletContext(), key);
+
+            factory.start();
+        }
+        catch (Exception x)
+        {
+            throw new ServletException(x);
+        }
+    }
+    
+    private void setToAttribute(ServletContextHandler context, String key) throws ServletException
+    {
+        if(alreadySetToAttribute)
+        {
+            return;
+        }
+        
+        if (context.getAttribute(key) != null)
+        {
+            throw new ServletException(WebSocketUpgradeFilter.class.getName() + 
+                    " is defined twice for the same context attribute key '" + key
+                    + "'.  Make sure you have different init-param '" + 
+                    CONTEXT_ATTRIBUTE_KEY + "' values set");
+        }
+        context.setAttribute(key,this);
+
+        alreadySetToAttribute = true;
+    }
+
+    public void setToAttribute(ServletContext context, String key) throws ServletException
+    {
+        if(alreadySetToAttribute)
+        {
+            return;
+        }
+        
+        if (context.getAttribute(key) != null)
+        {
+            throw new ServletException(WebSocketUpgradeFilter.class.getName() + 
+                    " is defined twice for the same context attribute key '" + key
+                    + "'.  Make sure you have different init-param '" + 
+                    CONTEXT_ATTRIBUTE_KEY + "' values set");
+        }
+        context.setAttribute(key,this);
+
+        alreadySetToAttribute = true;
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s[factory=%s,pathmap=%s]",this.getClass().getSimpleName(),factory,pathmap);
+    }
+}
diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketUpgradeHandlerWrapper.java b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketUpgradeHandlerWrapper.java
new file mode 100644
index 0000000..97251dd
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketUpgradeHandlerWrapper.java
@@ -0,0 +1,99 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.MappedByteBufferPool;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.handler.HandlerWrapper;
+import org.eclipse.jetty.websocket.server.pathmap.PathMappings;
+import org.eclipse.jetty.websocket.server.pathmap.PathMappings.MappedResource;
+import org.eclipse.jetty.websocket.server.pathmap.PathSpec;
+import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
+
+public class WebSocketUpgradeHandlerWrapper extends HandlerWrapper implements MappedWebSocketCreator
+{
+    private PathMappings<WebSocketCreator> pathmap = new PathMappings<>();
+    private final WebSocketServerFactory factory;
+
+    public WebSocketUpgradeHandlerWrapper()
+    {
+        this(new MappedByteBufferPool());
+    }
+    
+    public WebSocketUpgradeHandlerWrapper(ByteBufferPool bufferPool)
+    {
+        factory = new WebSocketServerFactory(bufferPool);
+    }
+
+    @Override
+    public void addMapping(PathSpec spec, WebSocketCreator creator)
+    {
+        pathmap.put(spec,creator);
+    }
+
+    @Override
+    public PathMappings<WebSocketCreator> getMappings()
+    {
+        return pathmap;
+    }
+
+    @Override
+    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+    {
+        if (factory.isUpgradeRequest(request,response))
+        {
+            MappedResource<WebSocketCreator> resource = pathmap.getMatch(target);
+            if (resource == null)
+            {
+                // no match.
+                response.sendError(HttpServletResponse.SC_NOT_FOUND,"No websocket endpoint matching path: " + target);
+                return;
+            }
+
+            WebSocketCreator creator = resource.getResource();
+
+            // Store PathSpec resource mapping as request attribute
+            request.setAttribute(PathSpec.class.getName(),resource);
+
+            // We have an upgrade request
+            if (factory.acceptWebSocket(creator,request,response))
+            {
+                // We have a socket instance created
+                return;
+            }
+
+            // If we reach this point, it means we had an incoming request to upgrade
+            // but it was either not a proper websocket upgrade, or it was possibly rejected
+            // due to incoming request constraints (controlled by WebSocketCreator)
+            if (response.isCommitted())
+            {
+                // not much we can do at this point.
+                return;
+            }
+        }
+        super.handle(target,baseRequest,request,response);
+    }
+}
diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/package-info.java b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/package-info.java
new file mode 100644
index 0000000..4b79628
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty WebSocket Server : Implementation [<em>Internal Use Only</em>]
+ */
+package org.eclipse.jetty.websocket.server;
+
diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/pathmap/PathMappings.java b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/pathmap/PathMappings.java
new file mode 100644
index 0000000..53ccfae
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/pathmap/PathMappings.java
@@ -0,0 +1,190 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server.pathmap;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.component.Dumpable;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.server.pathmap.PathMappings.MappedResource;
+
+/**
+ * Path Mappings of PathSpec to Resource.
+ * <p>
+ * Sorted into search order upon entry into the Set
+ * 
+ * @param <E>
+ */
+ at ManagedObject("Path Mappings")
+public class PathMappings<E> implements Iterable<MappedResource<E>>, Dumpable
+{
+    @ManagedObject("Mapped Resource")
+    public static class MappedResource<E> implements Comparable<MappedResource<E>>
+    {
+        private final PathSpec pathSpec;
+        private final E resource;
+
+        public MappedResource(PathSpec pathSpec, E resource)
+        {
+            this.pathSpec = pathSpec;
+            this.resource = resource;
+        }
+
+        /**
+         * Comparison is based solely on the pathSpec
+         */
+        @Override
+        public int compareTo(MappedResource<E> other)
+        {
+            return this.pathSpec.compareTo(other.pathSpec);
+        }
+
+        @Override
+        public boolean equals(Object obj)
+        {
+            if (this == obj)
+            {
+                return true;
+            }
+            if (obj == null)
+            {
+                return false;
+            }
+            if (getClass() != obj.getClass())
+            {
+                return false;
+            }
+            MappedResource<?> other = (MappedResource<?>)obj;
+            if (pathSpec == null)
+            {
+                if (other.pathSpec != null)
+                {
+                    return false;
+                }
+            }
+            else if (!pathSpec.equals(other.pathSpec))
+            {
+                return false;
+            }
+            return true;
+        }
+
+        @ManagedAttribute(value = "path spec", readonly = true)
+        public PathSpec getPathSpec()
+        {
+            return pathSpec;
+        }
+
+        @ManagedAttribute(value = "resource", readonly = true)
+        public E getResource()
+        {
+            return resource;
+        }
+
+        @Override
+        public int hashCode()
+        {
+            final int prime = 31;
+            int result = 1;
+            result = (prime * result) + ((pathSpec == null)?0:pathSpec.hashCode());
+            return result;
+        }
+
+        @Override
+        public String toString()
+        {
+            return String.format("MappedResource[pathSpec=%s,resource=%s]",pathSpec,resource);
+        }
+    }
+
+    private static final Logger LOG = Log.getLogger(PathMappings.class);
+    private List<MappedResource<E>> mappings = new ArrayList<MappedResource<E>>();
+    private MappedResource<E> defaultResource = null;
+
+    @Override
+    public String dump()
+    {
+        return ContainerLifeCycle.dump(this);
+    }
+
+    @Override
+    public void dump(Appendable out, String indent) throws IOException
+    {
+        ContainerLifeCycle.dump(out,indent,mappings);
+    }
+
+    @ManagedAttribute(value = "mappings", readonly = true)
+    public List<MappedResource<E>> getMappings()
+    {
+        return mappings;
+    }
+    
+    public void reset()
+    {
+        mappings.clear();
+    }
+
+    public MappedResource<E> getMatch(String path)
+    {
+        int len = mappings.size();
+        for (int i = 0; i < len; i++)
+        {
+            MappedResource<E> mr = mappings.get(i);
+            if (mr.getPathSpec().matches(path))
+            {
+                return mr;
+            }
+        }
+        return defaultResource;
+    }
+
+    @Override
+    public Iterator<MappedResource<E>> iterator()
+    {
+        return mappings.iterator();
+    }
+
+    public void put(PathSpec pathSpec, E resource)
+    {
+        MappedResource<E> entry = new MappedResource<>(pathSpec,resource);
+        if (pathSpec.group == PathSpecGroup.DEFAULT)
+        {
+            defaultResource = entry;
+        }
+        // TODO: warning on replacement of existing mapping?
+        mappings.add(entry);
+        if (LOG.isDebugEnabled())
+            LOG.debug("Added {} to {}",entry,this);
+        Collections.sort(mappings);
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s[size=%d]",this.getClass().getSimpleName(),mappings.size());
+    }
+}
diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/pathmap/PathSpec.java b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/pathmap/PathSpec.java
new file mode 100644
index 0000000..2e8999c
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/pathmap/PathSpec.java
@@ -0,0 +1,167 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server.pathmap;
+
+/**
+ * The base PathSpec, what all other path specs are based on
+ */
+public abstract class PathSpec implements Comparable<PathSpec>
+{
+    protected String pathSpec;
+    protected PathSpecGroup group;
+    protected int pathDepth;
+    protected int specLength;
+
+    @Override
+    public int compareTo(PathSpec other)
+    {
+        // Grouping (increasing)
+        int diff = this.group.ordinal() - other.group.ordinal();
+        if (diff != 0)
+        {
+            return diff;
+        }
+
+        // Spec Length (decreasing)
+        diff = other.specLength - this.specLength;
+        if (diff != 0)
+        {
+            return diff;
+        }
+
+        // Path Spec Name (alphabetical)
+        return this.pathSpec.compareTo(other.pathSpec);
+    }
+
+    @Override
+    public boolean equals(Object obj)
+    {
+        if (this == obj)
+        {
+            return true;
+        }
+        if (obj == null)
+        {
+            return false;
+        }
+        if (getClass() != obj.getClass())
+        {
+            return false;
+        }
+        PathSpec other = (PathSpec)obj;
+        if (pathSpec == null)
+        {
+            if (other.pathSpec != null)
+            {
+                return false;
+            }
+        }
+        else if (!pathSpec.equals(other.pathSpec))
+        {
+            return false;
+        }
+        return true;
+    }
+
+    public PathSpecGroup getGroup()
+    {
+        return group;
+    }
+
+    /**
+     * Get the number of path elements that this path spec declares.
+     * <p>
+     * This is used to determine longest match logic.
+     * 
+     * @return the depth of the path segments that this spec declares
+     */
+    public int getPathDepth()
+    {
+        return pathDepth;
+    }
+
+    /**
+     * Return the portion of the path that is after the path spec.
+     * 
+     * @param path
+     *            the path to match against
+     * @return the path info portion of the string
+     */
+    public abstract String getPathInfo(String path);
+
+    /**
+     * Return the portion of the path that matches a path spec.
+     * 
+     * @param path
+     *            the path to match against
+     * @return the match, or null if no match at all
+     */
+    public abstract String getPathMatch(String path);
+
+    /**
+     * The as-provided path spec.
+     * 
+     * @return the as-provided path spec
+     */
+    public String getPathSpec()
+    {
+        return pathSpec;
+    }
+
+    /**
+     * Get the relative path.
+     * 
+     * @param base
+     *            the base the path is relative to
+     * @param path
+     *            the additional path
+     * @return the base plus path with pathSpec portion removed
+     */
+    public abstract String getRelativePath(String base, String path);
+
+    @Override
+    public int hashCode()
+    {
+        final int prime = 31;
+        int result = 1;
+        result = (prime * result) + ((pathSpec == null)?0:pathSpec.hashCode());
+        return result;
+    }
+
+    /**
+     * Test to see if the provided path matches this path spec
+     * 
+     * @param path
+     *            the path to test
+     * @return true if the path matches this path spec, false otherwise
+     */
+    public abstract boolean matches(String path);
+
+    @Override
+    public String toString()
+    {
+        StringBuilder str = new StringBuilder();
+        str.append(this.getClass().getSimpleName()).append("[\"");
+        str.append(pathSpec);
+        str.append("\",pathDepth=").append(pathDepth);
+        str.append(",group=").append(group);
+        str.append("]");
+        return str.toString();
+    }
+}
diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/pathmap/PathSpecGroup.java b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/pathmap/PathSpecGroup.java
new file mode 100644
index 0000000..287d02d
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/pathmap/PathSpecGroup.java
@@ -0,0 +1,82 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server.pathmap;
+
+/**
+ * Types of path spec groups.
+ * <p>
+ * This is used to facilitate proper pathspec search order.
+ * <p>
+ * Search Order: {@link PathSpecGroup#ordinal()} [increasin], {@link PathSpec#specLength} [decreasing], {@link PathSpec#pathSpec} [natural sort order]
+ */
+public enum PathSpecGroup
+{
+    // NOTE: Order of enums determines order of Groups.
+
+    /**
+     * For exactly defined path specs, no glob.
+     */
+    EXACT,
+    /**
+     * For path specs that have a hardcoded prefix and suffix with wildcard glob in the middle.
+     * 
+     * <pre>
+     *   "^/downloads/[^/]*.zip$"  - regex spec
+     *   "/a/{var}/c"              - websocket spec
+     * </pre>
+     * 
+     * Note: there is no known servlet spec variant of this kind of path spec
+     */
+    MIDDLE_GLOB,
+    /**
+     * For path specs that have a hardcoded prefix and a trailing wildcard glob.
+     * <p>
+     * 
+     * <pre>
+     *   "/downloads/*"          - servlet spec
+     *   "/api/*"                - servlet spec
+     *   "^/rest/.*$"            - regex spec
+     *   "/bookings/{guest-id}"  - websocket spec
+     *   "/rewards/{vip-level}"  - websocket spec
+     * </pre>
+     */
+    PREFIX_GLOB,
+    /**
+     * For path specs that have a wildcard glob with a hardcoded suffix
+     * 
+     * <pre>
+     *   "*.do"        - servlet spec
+     *   "*.css"       - servlet spec
+     *   "^.*\.zip$"   - regex spec
+     * </pre>
+     * 
+     * Note: there is no known websocket spec variant of this kind of path spec
+     */
+    SUFFIX_GLOB,
+    /**
+     * The default spec for accessing the Root and/or Default behavior.
+     * 
+     * <pre>
+     *   "/"           - servlet spec   (Default Servlet)
+     *   "/"           - websocket spec (Root Context)
+     *   "^/$"         - regex spec     (Root Context)
+     * </pre>
+     */
+    DEFAULT;
+}
diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/pathmap/RegexPathSpec.java b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/pathmap/RegexPathSpec.java
new file mode 100644
index 0000000..cf5f758
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/pathmap/RegexPathSpec.java
@@ -0,0 +1,176 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server.pathmap;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class RegexPathSpec extends PathSpec
+{
+    protected Pattern pattern;
+
+    protected RegexPathSpec()
+    {
+        super();
+    }
+
+    public RegexPathSpec(String regex)
+    {
+        super.pathSpec = regex;
+        boolean inGrouping = false;
+        this.pathDepth = 0;
+        this.specLength = pathSpec.length();
+        // build up a simple signature we can use to identify the grouping
+        StringBuilder signature = new StringBuilder();
+        for (char c : pathSpec.toCharArray())
+        {
+            switch (c)
+            {
+                case '[':
+                    inGrouping = true;
+                    break;
+                case ']':
+                    inGrouping = false;
+                    signature.append('g'); // glob
+                    break;
+                case '*':
+                    signature.append('g'); // glob
+                    break;
+                case '/':
+                    if (!inGrouping)
+                    {
+                        this.pathDepth++;
+                    }
+                    break;
+                default:
+                    if (!inGrouping)
+                    {
+                        if (Character.isLetterOrDigit(c))
+                        {
+                            signature.append('l'); // literal (exact)
+                        }
+                    }
+                    break;
+            }
+        }
+        this.pattern = Pattern.compile(pathSpec);
+
+        // Figure out the grouping based on the signature
+        String sig = signature.toString();
+
+        if (Pattern.matches("^l*$",sig))
+        {
+            this.group = PathSpecGroup.EXACT;
+        }
+        else if (Pattern.matches("^l*g+",sig))
+        {
+            this.group = PathSpecGroup.PREFIX_GLOB;
+        }
+        else if (Pattern.matches("^g+l+$",sig))
+        {
+            this.group = PathSpecGroup.SUFFIX_GLOB;
+        }
+        else
+        {
+            this.group = PathSpecGroup.MIDDLE_GLOB;
+        }
+    }
+
+    public Matcher getMatcher(String path)
+    {
+        return this.pattern.matcher(path);
+    }
+
+    @Override
+    public String getPathInfo(String path)
+    {
+        // Path Info only valid for PREFIX_GLOB types
+        if (group == PathSpecGroup.PREFIX_GLOB)
+        {
+            Matcher matcher = getMatcher(path);
+            if (matcher.matches())
+            {
+                if (matcher.groupCount() >= 1)
+                {
+                    String pathInfo = matcher.group(1);
+                    if ("".equals(pathInfo))
+                    {
+                        return "/";
+                    }
+                    else
+                    {
+                        return pathInfo;
+                    }
+                }
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public String getPathMatch(String path)
+    {
+        Matcher matcher = getMatcher(path);
+        if (matcher.matches())
+        {
+            if (matcher.groupCount() >= 1)
+            {
+                int idx = matcher.start(1);
+                if (idx > 0)
+                {
+                    if (path.charAt(idx - 1) == '/')
+                    {
+                        idx--;
+                    }
+                    return path.substring(0,idx);
+                }
+            }
+            return path;
+        }
+        return null;
+    }
+
+    public Pattern getPattern()
+    {
+        return this.pattern;
+    }
+
+    @Override
+    public String getRelativePath(String base, String path)
+    {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public boolean matches(final String path)
+    {
+        int idx = path.indexOf('?');
+        if (idx >= 0)
+        {
+            // match only non-query part
+            return getMatcher(path.substring(0,idx)).matches();
+        }
+        else
+        {
+            // match entire path
+            return getMatcher(path).matches();
+        }
+    }
+}
diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/pathmap/ServletPathSpec.java b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/pathmap/ServletPathSpec.java
new file mode 100644
index 0000000..b09b15b
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/pathmap/ServletPathSpec.java
@@ -0,0 +1,291 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server.pathmap;
+
+import org.eclipse.jetty.util.URIUtil;
+
+public class ServletPathSpec extends PathSpec
+{
+    public static final String PATH_SPEC_SEPARATORS = ":,";
+
+    /**
+     * Get multi-path spec splits.
+     * 
+     * @param servletPathSpec
+     *            the path spec that might contain multiple declared path specs
+     * @return the individual path specs found.
+     */
+    public static ServletPathSpec[] getMultiPathSpecs(String servletPathSpec)
+    {
+        String pathSpecs[] = servletPathSpec.split(PATH_SPEC_SEPARATORS);
+        int len = pathSpecs.length;
+        ServletPathSpec sps[] = new ServletPathSpec[len];
+        for (int i = 0; i < len; i++)
+        {
+            sps[i] = new ServletPathSpec(pathSpecs[i]);
+        }
+        return sps;
+    }
+
+    public ServletPathSpec(String servletPathSpec)
+    {
+        super();
+        assertValidServletPathSpec(servletPathSpec);
+
+        // The Path Spec for Default Servlet
+        if ((servletPathSpec == null) || (servletPathSpec.length() == 0) || "/".equals(servletPathSpec))
+        {
+            super.pathSpec = "/";
+            super.pathDepth = -1; // force this to be last in sort order
+            this.specLength = 1;
+            this.group = PathSpecGroup.DEFAULT;
+            return;
+        }
+
+        this.specLength = servletPathSpec.length();
+        super.pathDepth = 0;
+        char lastChar = servletPathSpec.charAt(specLength - 1);
+        // prefix based
+        if ((servletPathSpec.charAt(0) == '/') && (specLength > 1) && (lastChar == '*'))
+        {
+            this.group = PathSpecGroup.PREFIX_GLOB;
+        }
+        // suffix based
+        else if (servletPathSpec.charAt(0) == '*')
+        {
+            this.group = PathSpecGroup.SUFFIX_GLOB;
+        }
+        else
+        {
+            this.group = PathSpecGroup.EXACT;
+        }
+
+        for (int i = 0; i < specLength; i++)
+        {
+            int cp = servletPathSpec.codePointAt(i);
+            if (cp < 128)
+            {
+                char c = (char)cp;
+                switch (c)
+                {
+                    case '/':
+                        super.pathDepth++;
+                        break;
+                }
+            }
+        }
+
+        super.pathSpec = servletPathSpec;
+    }
+
+    private void assertValidServletPathSpec(String servletPathSpec)
+    {
+        if ((servletPathSpec == null) || servletPathSpec.equals(""))
+        {
+            return; // empty path spec
+        }
+
+        // Ensure we don't have path spec separators here in our single path spec.
+        for (char c : PATH_SPEC_SEPARATORS.toCharArray())
+        {
+            if (servletPathSpec.indexOf(c) >= 0)
+            {
+                throw new IllegalArgumentException("Servlet Spec 12.2 violation: encountered Path Spec Separator [" + PATH_SPEC_SEPARATORS
+                        + "] within specified path spec. did you forget to split this path spec up?");
+            }
+        }
+
+        int len = servletPathSpec.length();
+        // path spec must either start with '/' or '*.'
+        if (servletPathSpec.charAt(0) == '/')
+        {
+            // Prefix Based
+            if (len == 1)
+            {
+                return; // simple '/' path spec
+            }
+            int idx = servletPathSpec.indexOf('*');
+            if (idx < 0)
+            {
+                return; // no hit on glob '*'
+            }
+            // only allowed to have '*' at the end of the path spec
+            if (idx != (len - 1))
+            {
+                throw new IllegalArgumentException("Servlet Spec 12.2 violation: glob '*' can only exist at end of prefix based matches");
+            }
+        }
+        else if (servletPathSpec.startsWith("*."))
+        {
+            // Suffix Based
+            int idx = servletPathSpec.indexOf('/');
+            // cannot have path separator
+            if (idx >= 0)
+            {
+                throw new IllegalArgumentException("Servlet Spec 12.2 violation: suffix based path spec cannot have path separators");
+            }
+
+            idx = servletPathSpec.indexOf('*',2);
+            // only allowed to have 1 glob '*', at the start of the path spec
+            if (idx >= 1)
+            {
+                throw new IllegalArgumentException("Servlet Spec 12.2 violation: suffix based path spec cannot have multiple glob '*'");
+            }
+        }
+        else
+        {
+            throw new IllegalArgumentException("Servlet Spec 12.2 violation: path spec must start with \"/\" or \"*.\"");
+        }
+    }
+
+    @Override
+    public String getPathInfo(String path)
+    {
+        // Path Info only valid for PREFIX_GLOB types
+        if (group == PathSpecGroup.PREFIX_GLOB)
+        {
+            if (path.length() == (specLength - 2))
+            {
+                return null;
+            }
+            return path.substring(specLength - 2);
+        }
+
+        return null;
+    }
+
+    @Override
+    public String getPathMatch(String path)
+    {
+        switch (group)
+        {
+            case EXACT:
+                if (pathSpec.equals(path))
+                {
+                    return path;
+                }
+                else
+                {
+                    return null;
+                }
+            case PREFIX_GLOB:
+                if (isWildcardMatch(path))
+                {
+                    return path.substring(0,specLength - 2);
+                }
+                else
+                {
+                    return null;
+                }
+            case SUFFIX_GLOB:
+                if (path.regionMatches(path.length() - (specLength - 1),pathSpec,1,specLength - 1))
+                {
+                    return path;
+                }
+                else
+                {
+                    return null;
+                }
+            case DEFAULT:
+                return path;
+            default:
+                return null;
+        }
+    }
+
+    @Override
+    public String getRelativePath(String base, String path)
+    {
+        String info = getPathInfo(path);
+        if (info == null)
+        {
+            info = path;
+        }
+
+        if (info.startsWith("./"))
+        {
+            info = info.substring(2);
+        }
+        if (base.endsWith(URIUtil.SLASH))
+        {
+            if (info.startsWith(URIUtil.SLASH))
+            {
+                path = base + info.substring(1);
+            }
+            else
+            {
+                path = base + info;
+            }
+        }
+        else if (info.startsWith(URIUtil.SLASH))
+        {
+            path = base + info;
+        }
+        else
+        {
+            path = base + URIUtil.SLASH + info;
+        }
+        return path;
+    }
+
+    private boolean isExactMatch(String path)
+    {
+        if (group == PathSpecGroup.EXACT)
+        {
+            if (pathSpec.equals(path))
+            {
+                return true;
+            }
+            return (path.charAt(path.length() - 1) == '/') && (path.equals(pathSpec + '/'));
+        }
+        return false;
+    }
+
+    private boolean isWildcardMatch(String path)
+    {
+        // For a spec of "/foo/*" match "/foo" , "/foo/..." but not "/foobar"
+        int cpl = specLength - 2;
+        if ((group == PathSpecGroup.PREFIX_GLOB) && (path.regionMatches(0,pathSpec,0,cpl)))
+        {
+            if ((path.length() == cpl) || ('/' == path.charAt(cpl)))
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public boolean matches(String path)
+    {
+        switch (group)
+        {
+            case EXACT:
+                return isExactMatch(path);
+            case PREFIX_GLOB:
+                return isWildcardMatch(path);
+            case SUFFIX_GLOB:
+                return path.regionMatches((path.length() - specLength) + 1,pathSpec,1,specLength - 1);
+            case DEFAULT:
+                return true;
+            default:
+                return false;
+        }
+    }
+}
diff --git a/jetty-websocket/websocket-server/src/main/resources/META-INF/services/org.eclipse.jetty.websocket.servlet.WebSocketServletFactory b/jetty-websocket/websocket-server/src/main/resources/META-INF/services/org.eclipse.jetty.websocket.servlet.WebSocketServletFactory
new file mode 100644
index 0000000..a5341c9
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/main/resources/META-INF/services/org.eclipse.jetty.websocket.servlet.WebSocketServletFactory
@@ -0,0 +1 @@
+org.eclipse.jetty.websocket.server.WebSocketServerFactory
\ No newline at end of file
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/AnnotatedMaxMessageSizeTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/AnnotatedMaxMessageSizeTest.java
new file mode 100644
index 0000000..33fbfea
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/AnnotatedMaxMessageSizeTest.java
@@ -0,0 +1,147 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server;
+
+import static org.hamcrest.Matchers.*;
+
+import java.io.IOException;
+import java.net.URI;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.toolchain.test.EventQueue;
+import org.eclipse.jetty.toolchain.test.TestTracker;
+import org.eclipse.jetty.util.log.StacklessLogging;
+import org.eclipse.jetty.websocket.api.StatusCode;
+import org.eclipse.jetty.websocket.common.CloseInfo;
+import org.eclipse.jetty.websocket.common.OpCode;
+import org.eclipse.jetty.websocket.common.Parser;
+import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
+import org.eclipse.jetty.websocket.common.test.BlockheadClient;
+import org.eclipse.jetty.websocket.server.examples.echo.BigEchoSocket;
+import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class AnnotatedMaxMessageSizeTest
+{
+    @Rule
+    public TestTracker tracker = new TestTracker();
+    
+    private static Server server;
+    private static ServerConnector connector;
+    private static URI serverUri;
+
+    @BeforeClass
+    public static void startServer() throws Exception
+    {
+        server = new Server();
+        connector = new ServerConnector(server);
+        server.addConnector(connector);
+
+        WebSocketHandler wsHandler = new WebSocketHandler()
+        {
+            @Override
+            public void configure(WebSocketServletFactory factory)
+            {
+                factory.register(BigEchoSocket.class);
+            }
+        };
+
+        server.setHandler(wsHandler);
+        server.start();
+
+        String host = connector.getHost();
+        if (host == null)
+        {
+            host = "localhost";
+        }
+        int port = connector.getLocalPort();
+        serverUri = new URI(String.format("ws://%s:%d/",host,port));
+    }
+
+    @AfterClass
+    public static void stopServer() throws Exception
+    {
+        server.stop();
+    }
+
+    @Test
+    public void testEchoGood() throws IOException, Exception
+    {
+        BlockheadClient client = new BlockheadClient(serverUri);
+        try
+        {
+            client.setProtocols("echo");
+            client.connect();
+            client.sendStandardRequest();
+            client.expectUpgradeResponse();
+
+            // Generate text frame
+            String msg = "this is an echo ... cho ... ho ... o";
+            client.write(new TextFrame().setPayload(msg));
+
+            // Read frame (hopefully text frame)
+            EventQueue<WebSocketFrame> frames = client.readFrames(1,500,TimeUnit.MILLISECONDS);
+            WebSocketFrame tf = frames.poll();
+            Assert.assertThat("Text Frame.status code",tf.getPayloadAsUTF8(),is(msg));
+        }
+        finally
+        {
+            client.close();
+        }
+    }
+    
+    @Test(timeout=8000)
+    public void testEchoTooBig() throws IOException, Exception
+    {
+        BlockheadClient client = new BlockheadClient(serverUri);
+        try(StacklessLogging logging = new StacklessLogging(Parser.class))
+        {
+            client.setProtocols("echo");
+            client.connect();
+            client.sendStandardRequest();
+            client.expectUpgradeResponse();
+
+            // Generate text frame
+            int size = 120 * 1024;
+            byte buf[] = new byte[size]; // buffer bigger than maxMessageSize
+            Arrays.fill(buf,(byte)'x');
+            client.write(new TextFrame().setPayload(ByteBuffer.wrap(buf)));
+
+            // Read frame (hopefully close frame saying its too large)
+            EventQueue<WebSocketFrame> frames = client.readFrames(1,500,TimeUnit.MILLISECONDS);
+            WebSocketFrame tf = frames.poll();
+            Assert.assertThat("Frame is close", tf.getOpCode(), is(OpCode.CLOSE));
+            CloseInfo close = new CloseInfo(tf);
+            Assert.assertThat("Close Code", close.getStatusCode(), is(StatusCode.MESSAGE_TOO_LARGE));
+        }
+        finally
+        {
+            client.close();
+        }
+    }
+}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/BatchModeTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/BatchModeTest.java
new file mode 100644
index 0000000..49d478a
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/BatchModeTest.java
@@ -0,0 +1,103 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server;
+
+import java.net.URI;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.websocket.api.RemoteEndpoint;
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.WebSocketAdapter;
+import org.eclipse.jetty.websocket.client.WebSocketClient;
+import org.eclipse.jetty.websocket.server.helper.EchoSocket;
+import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class BatchModeTest
+{
+    private Server server;
+    private ServerConnector connector;
+    private WebSocketClient client;
+
+    @Before
+    public void prepare() throws Exception
+    {
+        server = new Server();
+        connector = new ServerConnector(server);
+        server.addConnector(connector);
+
+        WebSocketHandler handler = new WebSocketHandler()
+        {
+            @Override
+            public void configure(WebSocketServletFactory factory)
+            {
+                factory.register(EchoSocket.class);
+            }
+        };
+
+        server.setHandler(handler);
+
+        client = new WebSocketClient();
+        server.addBean(client, true);
+
+        server.start();
+    }
+
+    @After
+    public void dispose() throws Exception
+    {
+        server.stop();
+    }
+
+    @Test
+    public void testBatchModeAuto() throws Exception
+    {
+        URI uri = URI.create("ws://localhost:" + connector.getLocalPort());
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        WebSocketAdapter adapter = new WebSocketAdapter()
+        {
+            @Override
+            public void onWebSocketText(String message)
+            {
+                latch.countDown();
+            }
+        };
+        try (Session session = client.connect(adapter, uri).get())
+        {
+            RemoteEndpoint remote = session.getRemote();
+
+            Future<Void> future = remote.sendStringByFuture("batch_mode_on");
+            // The write is aggregated and therefore completes immediately.
+            future.get(1, TimeUnit.MICROSECONDS);
+
+            // Wait for the echo.
+            Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+        }
+    }
+
+    // TODO: currently not possible to configure the Jetty WebSocket Session with the batch mode.
+}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ChromeTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ChromeTest.java
new file mode 100644
index 0000000..b90f44c
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ChromeTest.java
@@ -0,0 +1,84 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server;
+
+import static org.hamcrest.Matchers.*;
+
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.toolchain.test.EventQueue;
+import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
+import org.eclipse.jetty.websocket.common.test.BlockheadClient;
+import org.eclipse.jetty.websocket.common.test.HttpResponse;
+import org.eclipse.jetty.websocket.server.examples.MyEchoServlet;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.Assume;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class ChromeTest
+{
+    private static SimpleServletServer server;
+
+    @BeforeClass
+    public static void startServer() throws Exception
+    {
+        server = new SimpleServletServer(new MyEchoServlet());
+        server.start();
+    }
+
+    @AfterClass
+    public static void stopServer()
+    {
+        server.stop();
+    }
+
+    @Test
+    public void testUpgradeWithWebkitDeflateExtension() throws Exception
+    {
+        Assume.assumeTrue("Server has x-webkit-deflate-frame registered",
+                server.getWebSocketServletFactory().getExtensionFactory().isAvailable("x-webkit-deflate-frame"));
+        
+        BlockheadClient client = new BlockheadClient(server.getServerUri());
+        try
+        {
+            client.addExtensions("x-webkit-deflate-frame");
+            client.setProtocols("chat");
+            client.connect();
+            client.sendStandardRequest();
+            HttpResponse response = client.expectUpgradeResponse();
+            Assert.assertThat("Response",response.getExtensionsHeader(),containsString("x-webkit-deflate-frame"));
+
+            // Generate text frame
+            String msg = "this is an echo ... cho ... ho ... o";
+            client.write(new TextFrame().setPayload(msg));
+
+            // Read frame (hopefully text frame)
+            EventQueue<WebSocketFrame> frames = client.readFrames(1,500,TimeUnit.MILLISECONDS);
+            WebSocketFrame tf = frames.poll();
+            Assert.assertThat("Text Frame.status code",tf.getPayloadAsUTF8(),is(msg));
+        }
+        finally
+        {
+            client.close();
+        }
+    }
+}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/FirefoxTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/FirefoxTest.java
new file mode 100644
index 0000000..afaa245
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/FirefoxTest.java
@@ -0,0 +1,73 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server;
+
+import static org.hamcrest.Matchers.*;
+
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.toolchain.test.EventQueue;
+import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
+import org.eclipse.jetty.websocket.common.test.BlockheadClient;
+import org.eclipse.jetty.websocket.server.examples.MyEchoServlet;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class FirefoxTest
+{
+    private static SimpleServletServer server;
+
+    @BeforeClass
+    public static void startServer() throws Exception
+    {
+        server = new SimpleServletServer(new MyEchoServlet());
+        server.start();
+    }
+
+    @AfterClass
+    public static void stopServer()
+    {
+        server.stop();
+    }
+
+    @Test
+    public void testConnectionKeepAlive() throws Exception
+    {
+        try (BlockheadClient client = new BlockheadClient(server.getServerUri()))
+        {
+            // Odd Connection Header value seen in Firefox
+            client.setConnectionValue("keep-alive, Upgrade");
+            client.connect();
+            client.sendStandardRequest();
+            client.expectUpgradeResponse();
+
+            // Generate text frame
+            String msg = "this is an echo ... cho ... ho ... o";
+            client.write(new TextFrame().setPayload(msg));
+
+            // Read frame (hopefully text frame)
+            EventQueue<WebSocketFrame> frames = client.readFrames(1, 500, TimeUnit.MILLISECONDS);
+            WebSocketFrame tf = frames.poll();
+            Assert.assertThat("Text Frame.status code", tf.getPayloadAsUTF8(), is(msg));
+        }
+    }
+}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/FragmentExtensionTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/FragmentExtensionTest.java
new file mode 100644
index 0000000..11dd490
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/FragmentExtensionTest.java
@@ -0,0 +1,103 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server;
+
+import static org.hamcrest.Matchers.*;
+
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.toolchain.test.EventQueue;
+import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
+import org.eclipse.jetty.websocket.common.test.BlockheadClient;
+import org.eclipse.jetty.websocket.common.test.HttpResponse;
+import org.eclipse.jetty.websocket.server.helper.EchoServlet;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class FragmentExtensionTest
+{
+    private static SimpleServletServer server;
+
+    @BeforeClass
+    public static void startServer() throws Exception
+    {
+        server = new SimpleServletServer(new EchoServlet());
+        server.start();
+    }
+
+    @AfterClass
+    public static void stopServer()
+    {
+        server.stop();
+    }
+
+    private String[] split(String str, int partSize)
+    {
+        int strLength = str.length();
+        int count = (int)Math.ceil((double)str.length() / partSize);
+        String ret[] = new String[count];
+        int idx;
+        for (int i = 0; i < count; i++)
+        {
+            idx = (i * partSize);
+            ret[i] = str.substring(idx,Math.min(idx + partSize,strLength));
+        }
+        return ret;
+    }
+
+    @Test
+    public void testFragmentExtension() throws Exception
+    {
+        int fragSize = 4;
+
+        BlockheadClient client = new BlockheadClient(server.getServerUri());
+        client.clearExtensions();
+        client.addExtensions("fragment;maxLength=" + fragSize);
+        client.setProtocols("onConnect");
+
+        try
+        {
+            // Make sure the read times out if there are problems with the implementation
+            client.setTimeout(1,TimeUnit.SECONDS);
+            client.connect();
+            client.sendStandardRequest();
+            HttpResponse resp = client.expectUpgradeResponse();
+
+            Assert.assertThat("Response",resp.getExtensionsHeader(),containsString("fragment"));
+
+            String msg = "Sent as a long message that should be split";
+            client.write(new TextFrame().setPayload(msg));
+
+            String parts[] = split(msg,fragSize);
+            EventQueue<WebSocketFrame> frames = client.readFrames(parts.length,1000,TimeUnit.MILLISECONDS);
+            for (int i = 0; i < parts.length; i++)
+            {
+                WebSocketFrame frame = frames.poll();
+                Assert.assertThat("text[" + i + "].payload",frame.getPayloadAsUTF8(),is(parts[i]));
+            }
+        }
+        finally
+        {
+            client.close();
+        }
+    }
+}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/FrameCompressionExtensionTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/FrameCompressionExtensionTest.java
new file mode 100644
index 0000000..dab737d
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/FrameCompressionExtensionTest.java
@@ -0,0 +1,94 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server;
+
+import static org.hamcrest.Matchers.*;
+
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.toolchain.test.EventQueue;
+import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
+import org.eclipse.jetty.websocket.common.test.BlockheadClient;
+import org.eclipse.jetty.websocket.common.test.HttpResponse;
+import org.eclipse.jetty.websocket.server.helper.EchoServlet;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class FrameCompressionExtensionTest
+{
+    private static SimpleServletServer server;
+
+    @BeforeClass
+    public static void startServer() throws Exception
+    {
+        server = new SimpleServletServer(new EchoServlet());
+        server.start();
+    }
+
+    @AfterClass
+    public static void stopServer()
+    {
+        server.stop();
+    }
+
+    @Test
+    public void testDeflateFrameExtension() throws Exception
+    {
+        BlockheadClient client = new BlockheadClient(server.getServerUri());
+        client.clearExtensions();
+        client.addExtensions("x-webkit-deflate-frame");
+        client.setProtocols("echo");
+
+        try
+        {
+            // Make sure the read times out if there are problems with the implementation
+            client.setTimeout(1,TimeUnit.SECONDS);
+            client.connect();
+            client.sendStandardRequest();
+            HttpResponse resp = client.expectUpgradeResponse();
+
+            Assert.assertThat("Response",resp.getExtensionsHeader(),containsString("x-webkit-deflate-frame"));
+
+            String msg = "Hello";
+
+            // Client sends first message
+            client.write(new TextFrame().setPayload(msg));
+
+            EventQueue<WebSocketFrame> frames = client.readFrames(1,1000,TimeUnit.MILLISECONDS);
+            WebSocketFrame frame = frames.poll();
+            Assert.assertThat("TEXT.payload",frame.getPayloadAsUTF8(),is(msg.toString()));
+
+            // Client sends second message
+            client.clearCaptured();
+            msg = "There";
+            client.write(new TextFrame().setPayload(msg));
+
+            frames = client.readFrames(1,1,TimeUnit.SECONDS);
+            frame = frames.poll();
+            Assert.assertThat("TEXT.payload",frame.getPayloadAsUTF8(),is(msg.toString()));
+        }
+        finally
+        {
+            client.close();
+        }
+    }
+}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/IdentityExtensionTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/IdentityExtensionTest.java
new file mode 100644
index 0000000..187b5a6
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/IdentityExtensionTest.java
@@ -0,0 +1,83 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server;
+
+import static org.hamcrest.Matchers.*;
+
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.toolchain.test.EventQueue;
+import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
+import org.eclipse.jetty.websocket.common.test.BlockheadClient;
+import org.eclipse.jetty.websocket.common.test.HttpResponse;
+import org.eclipse.jetty.websocket.server.helper.EchoServlet;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class IdentityExtensionTest
+{
+    private static SimpleServletServer server;
+
+    @BeforeClass
+    public static void startServer() throws Exception
+    {
+        server = new SimpleServletServer(new EchoServlet());
+        server.start();
+    }
+
+    @AfterClass
+    public static void stopServer()
+    {
+        server.stop();
+    }
+
+    @Test
+    public void testIdentityExtension() throws Exception
+    {
+        BlockheadClient client = new BlockheadClient(server.getServerUri());
+        client.clearExtensions();
+        client.addExtensions("identity;param=0");
+        client.addExtensions("identity;param=1, identity ; param = '2' ; other = ' some = value '");
+        client.setProtocols("onConnect");
+
+        try
+        {
+            // Make sure the read times out if there are problems with the implementation
+            client.setTimeout(1,TimeUnit.SECONDS);
+            client.connect();
+            client.sendStandardRequest();
+            HttpResponse resp = client.expectUpgradeResponse();
+
+            Assert.assertThat("Response",resp.getExtensionsHeader(),containsString("identity"));
+
+            client.write(new TextFrame().setPayload("Hello"));
+
+            EventQueue<WebSocketFrame> frames = client.readFrames(1,1000,TimeUnit.MILLISECONDS);
+            WebSocketFrame frame = frames.poll();
+            Assert.assertThat("TEXT.payload",frame.getPayloadAsUTF8(),is("Hello"));
+        }
+        finally
+        {
+            client.close();
+        }
+    }
+}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/IdleTimeoutTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/IdleTimeoutTest.java
new file mode 100644
index 0000000..ad999d0
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/IdleTimeoutTest.java
@@ -0,0 +1,107 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server;
+
+import static org.hamcrest.Matchers.*;
+
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.toolchain.test.EventQueue;
+import org.eclipse.jetty.websocket.api.StatusCode;
+import org.eclipse.jetty.websocket.common.CloseInfo;
+import org.eclipse.jetty.websocket.common.OpCode;
+import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
+import org.eclipse.jetty.websocket.common.test.BlockheadClient;
+import org.eclipse.jetty.websocket.server.helper.RFCSocket;
+import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
+import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class IdleTimeoutTest
+{
+    @SuppressWarnings("serial")
+    public static class TimeoutServlet extends WebSocketServlet
+    {
+        @Override
+        public void configure(WebSocketServletFactory factory)
+        {
+            factory.getPolicy().setIdleTimeout(500);
+            factory.register(RFCSocket.class);
+        }
+    }
+
+    private static SimpleServletServer server;
+
+    @BeforeClass
+    public static void startServer() throws Exception
+    {
+        server = new SimpleServletServer(new TimeoutServlet());
+        server.start();
+    }
+
+    @AfterClass
+    public static void stopServer()
+    {
+        server.stop();
+    }
+
+    /**
+     * Test IdleTimeout on server.
+     */
+    @Test
+    public void testIdleTimeout() throws Exception
+    {
+        BlockheadClient client = new BlockheadClient(server.getServerUri());
+        client.setProtocols("onConnect");
+        client.setTimeout(2500,TimeUnit.MILLISECONDS);
+        try
+        {
+            client.connect();
+            client.sendStandardRequest();
+            client.expectUpgradeResponse();
+
+            // This wait should be shorter than client timeout above, but
+            // longer than server timeout configured in TimeoutServlet
+            client.sleep(TimeUnit.MILLISECONDS,1000);
+
+            // Write to server
+            // This action is possible, but does nothing.
+            // Server could be in a half-closed state at this point.
+            // Where the server read is closed (due to timeout), but the server write is still open.
+            // The server could not read this frame, if it is in this half closed state
+            client.write(new TextFrame().setPayload("Hello"));
+
+            // Expect server to have closed due to its own timeout
+            EventQueue<WebSocketFrame> frames = client.readFrames(1,500,TimeUnit.MILLISECONDS);
+            WebSocketFrame frame = frames.poll();
+            Assert.assertThat("frame opcode",frame.getOpCode(),is(OpCode.CLOSE));
+            CloseInfo close = new CloseInfo(frame);
+            Assert.assertThat("close code",close.getStatusCode(),is(StatusCode.SHUTDOWN));
+            Assert.assertThat("close reason",close.getReason(),containsString("Timeout"));
+        }
+        finally
+        {
+            client.close();
+        }
+    }
+}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/PerMessageDeflateExtensionTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/PerMessageDeflateExtensionTest.java
new file mode 100644
index 0000000..1f78e55
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/PerMessageDeflateExtensionTest.java
@@ -0,0 +1,101 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server;
+
+import static org.hamcrest.Matchers.*;
+
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.toolchain.test.EventQueue;
+import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
+import org.eclipse.jetty.websocket.common.test.BlockheadClient;
+import org.eclipse.jetty.websocket.common.test.HttpResponse;
+import org.eclipse.jetty.websocket.server.helper.EchoServlet;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.Assume;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class PerMessageDeflateExtensionTest
+{
+    private static SimpleServletServer server;
+
+    @BeforeClass
+    public static void startServer() throws Exception
+    {
+        server = new SimpleServletServer(new EchoServlet());
+        server.start();
+    }
+
+    @AfterClass
+    public static void stopServer()
+    {
+        server.stop();
+    }
+
+    /**
+     * Default configuration for permessage-deflate
+     */
+    @Test
+    public void testPerMessageDeflateDefault() throws Exception
+    {
+        Assume.assumeTrue("Server has x-webkit-deflate-frame registered",
+                server.getWebSocketServletFactory().getExtensionFactory().isAvailable("permessage-deflate"));
+
+        BlockheadClient client = new BlockheadClient(server.getServerUri());
+        client.clearExtensions();
+        client.addExtensions("permessage-deflate");
+        client.setProtocols("echo");
+
+        try
+        {
+            // Make sure the read times out if there are problems with the implementation
+            client.setTimeout(1,TimeUnit.SECONDS);
+            client.connect();
+            client.sendStandardRequest();
+            HttpResponse resp = client.expectUpgradeResponse();
+
+            Assert.assertThat("Response",resp.getExtensionsHeader(),containsString("permessage-deflate"));
+
+            String msg = "Hello";
+
+            // Client sends first message
+            client.write(new TextFrame().setPayload(msg));
+
+            EventQueue<WebSocketFrame> frames = client.readFrames(1,1000,TimeUnit.MILLISECONDS);
+            WebSocketFrame frame = frames.poll();
+            Assert.assertThat("TEXT.payload",frame.getPayloadAsUTF8(),is(msg.toString()));
+
+            // Client sends second message
+            client.clearCaptured();
+            msg = "There";
+            client.write(new TextFrame().setPayload(msg));
+
+            frames = client.readFrames(1,1,TimeUnit.SECONDS);
+            frame = frames.poll();
+            Assert.assertThat("TEXT.payload",frame.getPayloadAsUTF8(),is(msg.toString()));
+        }
+        finally
+        {
+            client.close();
+        }
+    }
+}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/RequestHeadersTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/RequestHeadersTest.java
new file mode 100644
index 0000000..cafaaac
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/RequestHeadersTest.java
@@ -0,0 +1,135 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server;
+
+import static org.hamcrest.Matchers.*;
+
+import java.net.HttpCookie;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.websocket.api.UpgradeRequest;
+import org.eclipse.jetty.websocket.api.UpgradeResponse;
+import org.eclipse.jetty.websocket.common.test.BlockheadClient;
+import org.eclipse.jetty.websocket.server.helper.EchoSocket;
+import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
+import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
+import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
+import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
+import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class RequestHeadersTest
+{
+    private static class EchoCreator implements WebSocketCreator
+    {
+        private UpgradeRequest lastRequest;
+        private UpgradeResponse lastResponse;
+        private EchoSocket echoSocket = new EchoSocket();
+
+        @Override
+        public Object createWebSocket(ServletUpgradeRequest req, ServletUpgradeResponse resp)
+        {
+            this.lastRequest = req;
+            this.lastResponse = resp;
+            return echoSocket;
+        }
+
+        public UpgradeRequest getLastRequest()
+        {
+            return lastRequest;
+        }
+
+        @SuppressWarnings("unused")
+        public UpgradeResponse getLastResponse()
+        {
+            return lastResponse;
+        }
+    }
+
+    public static class EchoRequestServlet extends WebSocketServlet
+    {
+        /**
+         * 
+         */
+        private static final long serialVersionUID = -6575001979901924179L;
+        private final WebSocketCreator creator;
+
+        public EchoRequestServlet(WebSocketCreator creator)
+        {
+            this.creator = creator;
+        }
+
+        @Override
+        public void configure(WebSocketServletFactory factory)
+        {
+            factory.setCreator(this.creator);
+        }
+    }
+
+    private static SimpleServletServer server;
+    private static EchoCreator echoCreator;
+
+    @BeforeClass
+    public static void startServer() throws Exception
+    {
+        echoCreator = new EchoCreator();
+        server = new SimpleServletServer(new EchoRequestServlet(echoCreator));
+        server.start();
+    }
+
+    @AfterClass
+    public static void stopServer()
+    {
+        server.stop();
+    }
+
+    @Test
+    public void testAccessRequestCookies() throws Exception
+    {
+        BlockheadClient client = new BlockheadClient(server.getServerUri());
+        client.setTimeout(1,TimeUnit.SECONDS);
+
+        try
+        {
+            client.connect();
+            client.addHeader("Cookie: fruit=Pear; type=Anjou\r\n");
+            client.sendStandardRequest();
+            client.expectUpgradeResponse();
+
+            UpgradeRequest req = echoCreator.getLastRequest();
+            Assert.assertThat("Last Request",req,notNullValue());
+            List<HttpCookie> cookies = req.getCookies();
+            Assert.assertThat("Request cookies",cookies,notNullValue());
+            Assert.assertThat("Request cookies.size",cookies.size(),is(2));
+            for (HttpCookie cookie : cookies)
+            {
+                Assert.assertThat("Cookie name",cookie.getName(),anyOf(is("fruit"),is("type")));
+                Assert.assertThat("Cookie value",cookie.getValue(),anyOf(is("Pear"),is("Anjou")));
+            }
+        }
+        finally
+        {
+            client.close();
+        }
+    }
+}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/SimpleServletServer.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/SimpleServletServer.java
new file mode 100644
index 0000000..cf921ce
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/SimpleServletServer.java
@@ -0,0 +1,164 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server;
+
+import java.net.URI;
+
+import javax.servlet.http.HttpServlet;
+
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.HttpConnectionFactory;
+import org.eclipse.jetty.server.SecureRequestCustomizer;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.SslConnectionFactory;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
+
+public class SimpleServletServer
+{
+    private static final Logger LOG = Log.getLogger(SimpleServletServer.class);
+    private Server server;
+    private ServerConnector connector;
+    private URI serverUri;
+    private HttpServlet servlet;
+    private boolean ssl = false;
+    private SslContextFactory sslContextFactory;
+
+    public SimpleServletServer(HttpServlet servlet)
+    {
+        this.servlet = servlet;
+    }
+
+    public void enableSsl(boolean ssl)
+    {
+        this.ssl = ssl;
+    }
+
+    public URI getServerUri()
+    {
+        return serverUri;
+    }
+
+    public SslContextFactory getSslContextFactory()
+    {
+        return sslContextFactory;
+    }
+
+    public boolean isSslEnabled()
+    {
+        return ssl;
+    }
+
+    public void start() throws Exception
+    {
+        // Configure Server
+        server = new Server();
+        if (ssl)
+        {
+            // HTTP Configuration
+            HttpConfiguration http_config = new HttpConfiguration();
+            http_config.setSecureScheme("https");
+            http_config.setSecurePort(0);
+            http_config.setOutputBufferSize(32768);
+            http_config.setRequestHeaderSize(8192);
+            http_config.setResponseHeaderSize(8192);
+            http_config.setSendServerVersion(true);
+            http_config.setSendDateHeader(false);
+
+            sslContextFactory = new SslContextFactory();
+            sslContextFactory.setKeyStorePath(MavenTestingUtils.getTestResourceFile("keystore").getAbsolutePath());
+            sslContextFactory.setKeyStorePassword("storepwd");
+            sslContextFactory.setKeyManagerPassword("keypwd");
+            sslContextFactory.setExcludeCipherSuites("SSL_RSA_WITH_DES_CBC_SHA","SSL_DHE_RSA_WITH_DES_CBC_SHA","SSL_DHE_DSS_WITH_DES_CBC_SHA",
+                    "SSL_RSA_EXPORT_WITH_RC4_40_MD5","SSL_RSA_EXPORT_WITH_DES40_CBC_SHA","SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA",
+                    "SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA");
+
+            // SSL HTTP Configuration
+            HttpConfiguration https_config = new HttpConfiguration(http_config);
+            https_config.addCustomizer(new SecureRequestCustomizer());
+
+            // SSL Connector
+            connector = new ServerConnector(server,new SslConnectionFactory(sslContextFactory,"http/1.1"),new HttpConnectionFactory(https_config));
+            connector.setPort(0);
+        }
+        else
+        {
+            // Basic HTTP connector
+            connector = new ServerConnector(server);
+            connector.setPort(0);
+        }
+        server.addConnector(connector);
+
+        ServletContextHandler context = new ServletContextHandler();
+        context.setContextPath("/");
+        server.setHandler(context);
+
+        // Serve capture servlet
+        context.addServlet(new ServletHolder(servlet),"/*");
+
+        // Start Server
+        server.start();
+
+        // Establish the Server URI
+        String host = connector.getHost();
+        if (host == null)
+        {
+            host = "localhost";
+        }
+        int port = connector.getLocalPort();
+        serverUri = new URI(String.format("%s://%s:%d/",ssl?"wss":"ws",host,port));
+
+        // Some debugging
+        if (LOG.isDebugEnabled())
+        {
+            LOG.debug(server.dump());
+        }
+    }
+
+    public void stop()
+    {
+        try
+        {
+            server.stop();
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace(System.err);
+        }
+    }
+
+    public WebSocketServletFactory getWebSocketServletFactory()
+    {
+        // Try filter approach first
+        WebSocketUpgradeFilter filter = (WebSocketUpgradeFilter)this.servlet.getServletContext().getAttribute(WebSocketUpgradeFilter.class.getName());
+        if (filter != null)
+        {
+            return filter.getFactory();
+        }
+
+        // Try servlet next
+        return (WebSocketServletFactory)this.servlet.getServletContext().getAttribute(WebSocketServletFactory.class.getName());
+    }
+}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/TooFastClientTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/TooFastClientTest.java
new file mode 100644
index 0000000..930f92c
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/TooFastClientTest.java
@@ -0,0 +1,111 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server;
+
+import static org.hamcrest.Matchers.*;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.toolchain.test.EventQueue;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.common.Generator;
+import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
+import org.eclipse.jetty.websocket.common.test.BlockheadClient;
+import org.eclipse.jetty.websocket.common.test.LeakTrackingBufferPoolRule;
+import org.eclipse.jetty.websocket.server.examples.MyEchoServlet;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Ignore;
+import org.junit.Test;
+
+/**
+ * Test simulating a client that talks too quickly.
+ * <p>
+ * There is a class of client that will send the GET+Upgrade Request along with a few websocket frames in a single
+ * network packet. This test attempts to perform this behavior as close as possible.
+ */
+ at Ignore
+public class TooFastClientTest
+{
+    private static SimpleServletServer server;
+
+    @BeforeClass
+    public static void startServer() throws Exception
+    {
+        server = new SimpleServletServer(new MyEchoServlet());
+        server.start();
+    }
+
+    @AfterClass
+    public static void stopServer()
+    {
+        server.stop();
+    }
+
+    @Test
+    public void testUpgradeWithWebkitDeflateExtension() throws Exception
+    {
+        BlockheadClient client = new BlockheadClient(server.getServerUri());
+        try
+        {
+            client.connect();
+
+            // Create ByteBuffer representing the initial opening network packet from the client
+            ByteBuffer initialPacket = ByteBuffer.allocate(4096);
+            BufferUtil.clearToFill(initialPacket);
+            
+            // Add upgrade request to packet
+            StringBuilder upgradeRequest = client.generateUpgradeRequest();
+            ByteBuffer upgradeBuffer = BufferUtil.toBuffer(upgradeRequest.toString(),StandardCharsets.UTF_8);
+            initialPacket.put(upgradeBuffer);
+            
+            // Add text frames
+            Generator generator = new Generator(WebSocketPolicy.newClientPolicy(),
+                    new LeakTrackingBufferPoolRule("Generator"));
+            String msg1 = "Echo 1";
+            String msg2 = "This is also an echooooo!";
+            
+            generator.generateWholeFrame(new TextFrame().setPayload(msg1),initialPacket);
+            generator.generateWholeFrame(new TextFrame().setPayload(msg2),initialPacket);
+
+            // Write packet to network
+            BufferUtil.flipToFlush(initialPacket,0);
+            client.writeRaw(initialPacket);
+            
+            // Expect upgrade
+            client.expectUpgradeResponse();
+
+            // Read frames (hopefully text frames)
+            EventQueue<WebSocketFrame> frames = client.readFrames(2,1,TimeUnit.SECONDS);
+            WebSocketFrame tf = frames.poll();
+            Assert.assertThat("Text Frame/msg1",tf.getPayloadAsUTF8(),is(msg1));
+            tf = frames.poll();
+            Assert.assertThat("Text Frame/msg2",tf.getPayloadAsUTF8(),is(msg2));
+        }
+        finally
+        {
+            client.close();
+        }
+    }
+}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketCloseTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketCloseTest.java
new file mode 100644
index 0000000..f5ba27f
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketCloseTest.java
@@ -0,0 +1,217 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server;
+
+import static org.hamcrest.Matchers.*;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.toolchain.test.EventQueue;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.log.StacklessLogging;
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.StatusCode;
+import org.eclipse.jetty.websocket.api.WebSocketAdapter;
+import org.eclipse.jetty.websocket.common.CloseInfo;
+import org.eclipse.jetty.websocket.common.OpCode;
+import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.events.AbstractEventDriver;
+import org.eclipse.jetty.websocket.common.test.BlockheadClient;
+import org.eclipse.jetty.websocket.server.helper.RFCSocket;
+import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
+import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
+import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
+import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
+import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * Tests various close scenarios
+ */
+public class WebSocketCloseTest
+{
+    static class AbstractCloseSocket extends WebSocketAdapter
+    {
+        public CountDownLatch closeLatch = new CountDownLatch(1);
+        public String closeReason = null;
+        public int closeStatusCode = -1;
+        public List<Throwable> errors = new ArrayList<>();
+
+        @Override
+        public void onWebSocketClose(int statusCode, String reason)
+        {
+            LOG.debug("onWebSocketClose({}, {})",statusCode,reason);
+            this.closeStatusCode = statusCode;
+            this.closeReason = reason;
+            closeLatch.countDown();
+        }
+
+        @Override
+        public void onWebSocketError(Throwable cause)
+        {
+            errors.add(cause);
+        }
+    }
+
+    @SuppressWarnings("serial")
+    public static class CloseServlet extends WebSocketServlet implements WebSocketCreator
+    {
+        @Override
+        public void configure(WebSocketServletFactory factory)
+        {
+            factory.setCreator(this);
+        }
+
+        @Override
+        public Object createWebSocket(ServletUpgradeRequest req, ServletUpgradeResponse resp)
+        {
+            if (req.hasSubProtocol("fastclose"))
+            {
+                closeSocket = new FastCloseSocket();
+                return closeSocket;
+            }
+
+            if (req.hasSubProtocol("fastfail"))
+            {
+                closeSocket = new FastFailSocket();
+                return closeSocket;
+            }
+            return new RFCSocket();
+        }
+    }
+
+    /**
+     * On Connect, close socket
+     */
+    public static class FastCloseSocket extends AbstractCloseSocket
+    {
+        private static final Logger LOG = Log.getLogger(WebSocketCloseTest.FastCloseSocket.class);
+
+        @Override
+        public void onWebSocketConnect(Session sess)
+        {
+            LOG.debug("onWebSocketConnect({})",sess);
+            sess.close(StatusCode.NORMAL,"FastCloseServer");
+        }
+    }
+
+    /**
+     * On Connect, throw unhandled exception
+     */
+    public static class FastFailSocket extends AbstractCloseSocket
+    {
+        private static final Logger LOG = Log.getLogger(WebSocketCloseTest.FastFailSocket.class);
+
+        @Override
+        public void onWebSocketConnect(Session sess)
+        {
+            LOG.debug("onWebSocketConnect({})",sess);
+            // Test failure due to unhandled exception
+            // this should trigger a fast-fail closure during open/connect
+            throw new RuntimeException("Intentional FastFail");
+        }
+    }
+
+    private static final Logger LOG = Log.getLogger(WebSocketCloseTest.class);
+
+    private static SimpleServletServer server;
+    private static AbstractCloseSocket closeSocket;
+
+    @BeforeClass
+    public static void startServer() throws Exception
+    {
+        server = new SimpleServletServer(new CloseServlet());
+        server.start();
+    }
+
+    @AfterClass
+    public static void stopServer()
+    {
+        server.stop();
+    }
+
+    /**
+     * Test fast close (bug #403817)
+     */
+    @Test
+    public void testFastClose() throws Exception
+    {
+        try (BlockheadClient client = new BlockheadClient(server.getServerUri()))
+        {
+            client.setProtocols("fastclose");
+            client.setTimeout(1,TimeUnit.SECONDS);
+            client.connect();
+            client.sendStandardRequest();
+            client.expectUpgradeResponse();
+
+            // Verify that client got close frame
+            EventQueue<WebSocketFrame> frames = client.readFrames(1,1,TimeUnit.SECONDS);
+            WebSocketFrame frame = frames.poll();
+            Assert.assertThat("frames[0].opcode",frame.getOpCode(),is(OpCode.CLOSE));
+            CloseInfo close = new CloseInfo(frame);
+            Assert.assertThat("Close Status Code",close.getStatusCode(),is(StatusCode.NORMAL));
+            
+            // Notify server of close handshake
+            client.write(close.asFrame()); // respond with close
+            
+            // ensure server socket got close event
+            Assert.assertThat("Fast Close Latch",closeSocket.closeLatch.await(1,TimeUnit.SECONDS),is(true));
+            Assert.assertThat("Fast Close.statusCode",closeSocket.closeStatusCode,is(StatusCode.NORMAL));
+        }
+    }
+
+    /**
+     * Test fast fail (bug #410537)
+     */
+    @Test
+    public void testFastFail() throws Exception
+    {
+        try (BlockheadClient client = new BlockheadClient(server.getServerUri()))
+        {
+            client.setProtocols("fastfail");
+            client.setTimeout(1,TimeUnit.SECONDS);
+            try (StacklessLogging scope = new StacklessLogging(AbstractEventDriver.class))
+            {
+                client.connect();
+                client.sendStandardRequest();
+                client.expectUpgradeResponse();
+
+                EventQueue<WebSocketFrame> frames = client.readFrames(1,1,TimeUnit.SECONDS);
+                WebSocketFrame frame = frames.poll();
+                Assert.assertThat("frames[0].opcode",frame.getOpCode(),is(OpCode.CLOSE));
+                CloseInfo close = new CloseInfo(frame);
+                Assert.assertThat("Close Status Code",close.getStatusCode(),is(StatusCode.SERVER_ERROR));
+
+                client.write(close.asFrame()); // respond with close
+
+                // ensure server socket got close event
+                Assert.assertThat("Fast Fail Latch",closeSocket.closeLatch.await(1,TimeUnit.SECONDS),is(true));
+                Assert.assertThat("Fast Fail.statusCode",closeSocket.closeStatusCode,is(StatusCode.SERVER_ERROR));
+                Assert.assertThat("Fast Fail.errors",closeSocket.errors.size(),is(1));
+            }
+        }
+    }
+}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketInvalidVersionTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketInvalidVersionTest.java
new file mode 100644
index 0000000..6d924e5
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketInvalidVersionTest.java
@@ -0,0 +1,71 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server;
+
+import static org.hamcrest.Matchers.*;
+
+import org.eclipse.jetty.websocket.common.test.BlockheadClient;
+import org.eclipse.jetty.websocket.common.test.HttpResponse;
+import org.eclipse.jetty.websocket.server.examples.MyEchoServlet;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class WebSocketInvalidVersionTest
+{
+    private static SimpleServletServer server;
+
+    @BeforeClass
+    public static void startServer() throws Exception
+    {
+        server = new SimpleServletServer(new MyEchoServlet());
+        server.start();
+    }
+
+    @AfterClass
+    public static void stopServer()
+    {
+        server.stop();
+    }
+
+    /**
+     * Test the requirement of responding with an http 400 when using a Sec-WebSocket-Version that is unsupported.
+     */
+    @Test
+    public void testRequestVersion29() throws Exception
+    {
+        @SuppressWarnings("resource")
+        BlockheadClient client = new BlockheadClient(server.getServerUri());
+        client.setVersion(29); // intentionally bad version
+        try
+        {
+            client.connect();
+            client.sendStandardRequest();
+            HttpResponse response = client.readResponseHeader();
+            Assert.assertThat("Response Status Code",response.getStatusCode(),is(400));
+            Assert.assertThat("Response Status Reason",response.getStatusReason(),containsString("Unsupported websocket version specification"));
+            Assert.assertThat("Response Versions",response.getHeader("Sec-WebSocket-Version"),is("13"));
+        }
+        finally
+        {
+            client.disconnect();
+        }
+    }
+}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketOverSSLTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketOverSSLTest.java
new file mode 100644
index 0000000..b762125
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketOverSSLTest.java
@@ -0,0 +1,189 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server;
+
+import java.net.URI;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.toolchain.test.EventQueue;
+import org.eclipse.jetty.toolchain.test.TestTracker;
+import org.eclipse.jetty.websocket.api.BatchMode;
+import org.eclipse.jetty.websocket.api.RemoteEndpoint;
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.client.WebSocketClient;
+import org.eclipse.jetty.websocket.common.test.LeakTrackingBufferPoolRule;
+import org.eclipse.jetty.websocket.server.helper.CaptureSocket;
+import org.eclipse.jetty.websocket.server.helper.SessionServlet;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+
+import static org.hamcrest.Matchers.is;
+
+public class WebSocketOverSSLTest
+{
+    @Rule
+    public TestTracker tracker = new TestTracker();
+    
+    @Rule
+    public LeakTrackingBufferPoolRule bufferPool = new LeakTrackingBufferPoolRule("Test");
+
+    private static SimpleServletServer server;
+
+    @BeforeClass
+    public static void startServer() throws Exception
+    {
+        server = new SimpleServletServer(new SessionServlet());
+        server.enableSsl(true);
+        server.start();
+    }
+
+    @AfterClass
+    public static void stopServer()
+    {
+        server.stop();
+    }
+
+    /**
+     * Test the requirement of issuing socket and receiving echo response
+     */
+    @Test
+    public void testEcho() throws Exception
+    {
+        Assert.assertThat("server scheme",server.getServerUri().getScheme(),is("wss"));
+        WebSocketClient client = new WebSocketClient(server.getSslContextFactory(),null,bufferPool);
+        try
+        {
+            client.start();
+
+            CaptureSocket clientSocket = new CaptureSocket();
+            URI requestUri = server.getServerUri();
+            System.err.printf("Request URI: %s%n",requestUri.toASCIIString());
+            Future<Session> fut = client.connect(clientSocket,requestUri);
+
+            // wait for connect
+            Session session = fut.get(3,TimeUnit.SECONDS);
+
+            // Generate text frame
+            String msg = "this is an echo ... cho ... ho ... o";
+            RemoteEndpoint remote = session.getRemote();
+            remote.sendString(msg);
+            if (remote.getBatchMode() == BatchMode.ON)
+                remote.flush();
+
+            // Read frame (hopefully text frame)
+            clientSocket.messages.awaitEventCount(1,500,TimeUnit.MILLISECONDS);
+            EventQueue<String> captured = clientSocket.messages;
+            Assert.assertThat("Text Message",captured.poll(),is(msg));
+
+            // Shutdown the socket
+            clientSocket.close();
+        }
+        finally
+        {
+            client.stop();
+        }
+    }
+
+    /**
+     * Test that server session reports as secure
+     */
+    @Test
+    public void testServerSessionIsSecure() throws Exception
+    {
+        Assert.assertThat("server scheme",server.getServerUri().getScheme(),is("wss"));
+        WebSocketClient client = new WebSocketClient(server.getSslContextFactory(),null,bufferPool);
+        try
+        {
+            client.setConnectTimeout(3000);
+            client.start();
+
+            CaptureSocket clientSocket = new CaptureSocket();
+            URI requestUri = server.getServerUri();
+            System.err.printf("Request URI: %s%n",requestUri.toASCIIString());
+            Future<Session> fut = client.connect(clientSocket,requestUri);
+
+            // wait for connect
+            Session session = fut.get(5,TimeUnit.SECONDS);
+
+            // Generate text frame
+            RemoteEndpoint remote = session.getRemote();
+            remote.sendString("session.isSecure");
+            if (remote.getBatchMode() == BatchMode.ON)
+                remote.flush();
+
+            // Read frame (hopefully text frame)
+            clientSocket.messages.awaitEventCount(1,500,TimeUnit.MILLISECONDS);
+            EventQueue<String> captured = clientSocket.messages;
+            Assert.assertThat("Server.session.isSecure",captured.poll(),is("session.isSecure=true"));
+
+            // Shutdown the socket
+            clientSocket.close();
+        }
+        finally
+        {
+            client.stop();
+        }
+    }
+
+    /**
+     * Test that server session.upgradeRequest.requestURI reports correctly
+     */
+    @Test
+    public void testServerSessionRequestURI() throws Exception
+    {
+        Assert.assertThat("server scheme",server.getServerUri().getScheme(),is("wss"));
+        WebSocketClient client = new WebSocketClient(server.getSslContextFactory(),null,bufferPool);
+        try
+        {
+            client.setConnectTimeout(3000);
+            client.start();
+
+            CaptureSocket clientSocket = new CaptureSocket();
+            URI requestUri = server.getServerUri().resolve("/deep?a=b");
+            System.err.printf("Request URI: %s%n",requestUri.toASCIIString());
+            Future<Session> fut = client.connect(clientSocket,requestUri);
+
+            // wait for connect
+            Session session = fut.get(5,TimeUnit.SECONDS);
+
+            // Generate text frame
+            RemoteEndpoint remote = session.getRemote();
+            remote.sendString("session.upgradeRequest.requestURI");
+            if (remote.getBatchMode() == BatchMode.ON)
+                remote.flush();
+
+            // Read frame (hopefully text frame)
+            clientSocket.messages.awaitEventCount(1,500,TimeUnit.MILLISECONDS);
+            EventQueue<String> captured = clientSocket.messages;
+            String expected = String.format("session.upgradeRequest.requestURI=%s",requestUri.toASCIIString());
+            Assert.assertThat("session.upgradeRequest.requestURI",captured.poll(),is(expected));
+
+            // Shutdown the socket
+            clientSocket.close();
+        }
+        finally
+        {
+            client.stop();
+        }
+    }
+}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketProtocolTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketProtocolTest.java
new file mode 100644
index 0000000..bd522de
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketProtocolTest.java
@@ -0,0 +1,91 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.Socket;
+import java.nio.charset.StandardCharsets;
+
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.util.B64Code;
+import org.eclipse.jetty.websocket.server.browser.BrowserDebugTool;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class WebSocketProtocolTest
+{
+    private BrowserDebugTool server;
+
+    @Before
+    public void startServer() throws Exception
+    {
+        server = new BrowserDebugTool();
+        server.prepare(0);
+        server.start();
+    }
+
+    @After
+    public void stopServer() throws Exception
+    {
+        server.stop();
+    }
+
+    @Test
+    public void testWebSocketProtocolResponse() throws Exception
+    {
+        try (Socket client = new Socket("localhost", server.getPort()))
+        {
+            byte[] key = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
+            StringBuilder request = new StringBuilder();
+            request.append("GET / HTTP/1.1\r\n")
+                    .append("Host: localhost\r\n")
+                    .append("Connection: Upgrade\r\n")
+                    .append("Upgrade: websocket\r\n")
+                    .append("Sec-WebSocket-version: 13\r\n")
+                    .append("Sec-WebSocket-Key:").append(B64Code.encode(key)).append("\r\n")
+                    .append("Sec-WebSocket-Protocol: echo\r\n")
+                    .append("\r\n");
+
+            OutputStream output = client.getOutputStream();
+            output.write(request.toString().getBytes(StandardCharsets.UTF_8));
+            output.flush();
+
+            BufferedReader input = new BufferedReader(new InputStreamReader(client.getInputStream()));
+            String line = input.readLine();
+            Assert.assertTrue(line.contains(" 101 "));
+            HttpFields fields = new HttpFields();
+            while ((line = input.readLine()) != null)
+            {
+                if (line.isEmpty())
+                    break;
+                int colon = line.indexOf(':');
+                Assert.assertTrue(colon > 0);
+                String name = line.substring(0, colon).trim();
+                String value = line.substring(colon + 1).trim();
+                fields.add(name, value);
+            }
+
+            Assert.assertEquals(1, fields.getValuesList("Sec-WebSocket-Protocol").size());
+        }
+    }
+}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketServerSessionTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketServerSessionTest.java
new file mode 100644
index 0000000..8ee2505
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketServerSessionTest.java
@@ -0,0 +1,103 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server;
+
+import static org.hamcrest.Matchers.*;
+
+import java.net.URI;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.toolchain.test.AdvancedRunner;
+import org.eclipse.jetty.toolchain.test.EventQueue;
+import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
+import org.eclipse.jetty.websocket.common.test.BlockheadClient;
+import org.eclipse.jetty.websocket.server.helper.SessionServlet;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Testing various aspects of the server side support for WebSocket {@link Session}
+ */
+ at RunWith(AdvancedRunner.class)
+public class WebSocketServerSessionTest
+{
+    private static SimpleServletServer server;
+
+    @BeforeClass
+    public static void startServer() throws Exception
+    {
+        server = new SimpleServletServer(new SessionServlet());
+        server.start();
+    }
+
+    @AfterClass
+    public static void stopServer()
+    {
+        server.stop();
+    }
+
+    @Test
+    public void testDisconnect() throws Exception
+    {
+        URI uri = server.getServerUri().resolve("/test/disconnect");
+        try (BlockheadClient client = new BlockheadClient(uri))
+        {
+            client.connect();
+            client.sendStandardRequest();
+            client.expectUpgradeResponse();
+
+            client.write(new TextFrame().setPayload("harsh-disconnect"));
+
+            client.awaitDisconnect(1, TimeUnit.SECONDS);
+        }
+    }
+
+    @Test
+    public void testUpgradeRequestResponse() throws Exception
+    {
+        URI uri = server.getServerUri().resolve("/test?snack=cashews&amount=handful&brand=off");
+        try (BlockheadClient client = new BlockheadClient(uri))
+        {
+            client.connect();
+            client.sendStandardRequest();
+            client.expectUpgradeResponse();
+
+            // Ask the server socket for specific parameter map info
+            client.write(new TextFrame().setPayload("getParameterMap|snack"));
+            client.write(new TextFrame().setPayload("getParameterMap|amount"));
+            client.write(new TextFrame().setPayload("getParameterMap|brand"));
+            client.write(new TextFrame().setPayload("getParameterMap|cost")); // intentionally invalid
+
+            // Read frame (hopefully text frame)
+            EventQueue<WebSocketFrame> frames = client.readFrames(4,5,TimeUnit.SECONDS);
+            WebSocketFrame tf = frames.poll();
+            Assert.assertThat("Parameter Map[snack]", tf.getPayloadAsUTF8(), is("[cashews]"));
+            tf = frames.poll();
+            Assert.assertThat("Parameter Map[amount]", tf.getPayloadAsUTF8(), is("[handful]"));
+            tf = frames.poll();
+            Assert.assertThat("Parameter Map[brand]", tf.getPayloadAsUTF8(), is("[off]"));
+            tf = frames.poll();
+            Assert.assertThat("Parameter Map[cost]", tf.getPayloadAsUTF8(), is("<null>"));
+        }
+    }
+}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketServletRFCTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketServletRFCTest.java
new file mode 100644
index 0000000..75c2b31
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketServletRFCTest.java
@@ -0,0 +1,352 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server;
+
+import static org.hamcrest.Matchers.*;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.toolchain.test.AdvancedRunner;
+import org.eclipse.jetty.toolchain.test.EventQueue;
+import org.eclipse.jetty.util.Utf8Appendable.NotUtf8Exception;
+import org.eclipse.jetty.util.Utf8StringBuilder;
+import org.eclipse.jetty.util.log.StacklessLogging;
+import org.eclipse.jetty.util.log.StdErrLog;
+import org.eclipse.jetty.websocket.api.StatusCode;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.common.CloseInfo;
+import org.eclipse.jetty.websocket.common.Generator;
+import org.eclipse.jetty.websocket.common.OpCode;
+import org.eclipse.jetty.websocket.common.Parser;
+import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.events.EventDriver;
+import org.eclipse.jetty.websocket.common.frames.BinaryFrame;
+import org.eclipse.jetty.websocket.common.frames.ContinuationFrame;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
+import org.eclipse.jetty.websocket.common.test.BlockheadClient;
+import org.eclipse.jetty.websocket.common.test.UnitGenerator;
+import org.eclipse.jetty.websocket.common.util.Hex;
+import org.eclipse.jetty.websocket.server.helper.RFCServlet;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test various <a href="http://tools.ietf.org/html/rfc6455">RFC 6455</a> specified requirements placed on {@link WebSocketServlet}
+ */
+ at RunWith(AdvancedRunner.class)
+public class WebSocketServletRFCTest
+{
+    private static Generator generator = new UnitGenerator();
+    private static SimpleServletServer server;
+
+    @BeforeClass
+    public static void startServer() throws Exception
+    {
+        server = new SimpleServletServer(new RFCServlet());
+        server.start();
+    }
+
+    @AfterClass
+    public static void stopServer()
+    {
+        server.stop();
+    }
+
+    private void enableStacks(Class<?> clazz, boolean enabled)
+    {
+        StdErrLog log = StdErrLog.getLogger(clazz);
+        log.setHideStacks(!enabled);
+    }
+
+    /**
+     * Test that aggregation of binary frames into a single message occurs
+     */
+    @Test
+    public void testBinaryAggregate() throws Exception
+    {
+        BlockheadClient client = new BlockheadClient(server.getServerUri());
+        try
+        {
+            client.connect();
+            client.sendStandardRequest();
+            client.expectUpgradeResponse();
+
+            // Generate binary frames
+            byte buf1[] = new byte[128];
+            byte buf2[] = new byte[128];
+            byte buf3[] = new byte[128];
+
+            Arrays.fill(buf1,(byte)0xAA);
+            Arrays.fill(buf2,(byte)0xBB);
+            Arrays.fill(buf3,(byte)0xCC);
+
+            WebSocketFrame bin;
+
+            bin = new BinaryFrame().setPayload(buf1).setFin(false);
+
+            client.write(bin); // write buf1 (fin=false)
+
+            bin = new ContinuationFrame().setPayload(buf2).setFin(false);
+
+            client.write(bin); // write buf2 (fin=false)
+
+            bin = new ContinuationFrame().setPayload(buf3).setFin(true);
+
+            client.write(bin); // write buf3 (fin=true)
+
+            // Read frame echo'd back (hopefully a single binary frame)
+            EventQueue<WebSocketFrame> frames = client.readFrames(1,1000,TimeUnit.MILLISECONDS);
+            Frame binmsg = frames.poll();
+            int expectedSize = buf1.length + buf2.length + buf3.length;
+            Assert.assertThat("BinaryFrame.payloadLength",binmsg.getPayloadLength(),is(expectedSize));
+
+            int aaCount = 0;
+            int bbCount = 0;
+            int ccCount = 0;
+
+            ByteBuffer echod = binmsg.getPayload();
+            while (echod.remaining() >= 1)
+            {
+                byte b = echod.get();
+                switch (b)
+                {
+                    case (byte)0xAA:
+                        aaCount++;
+                        break;
+                    case (byte)0xBB:
+                        bbCount++;
+                        break;
+                    case (byte)0xCC:
+                        ccCount++;
+                        break;
+                    default:
+                        Assert.fail(String.format("Encountered invalid byte 0x%02X",(byte)(0xFF & b)));
+                }
+            }
+            Assert.assertThat("Echoed data count for 0xAA",aaCount,is(buf1.length));
+            Assert.assertThat("Echoed data count for 0xBB",bbCount,is(buf2.length));
+            Assert.assertThat("Echoed data count for 0xCC",ccCount,is(buf3.length));
+        }
+        finally
+        {
+            client.close();
+        }
+    }
+
+    @Test(expected = NotUtf8Exception.class)
+    public void testDetectBadUTF8()
+    {
+        byte buf[] = new byte[]
+        { (byte)0xC2, (byte)0xC3 };
+
+        Utf8StringBuilder utf = new Utf8StringBuilder();
+        utf.append(buf,0,buf.length);
+    }
+
+    /**
+     * Test the requirement of issuing socket and receiving echo response
+     */
+    @Test
+    public void testEcho() throws Exception
+    {
+        BlockheadClient client = new BlockheadClient(server.getServerUri());
+        try
+        {
+            client.connect();
+            client.sendStandardRequest();
+            client.expectUpgradeResponse();
+
+            // Generate text frame
+            String msg = "this is an echo ... cho ... ho ... o";
+            client.write(new TextFrame().setPayload(msg));
+
+            // Read frame (hopefully text frame)
+            EventQueue<WebSocketFrame> frames = client.readFrames(1,500,TimeUnit.MILLISECONDS);
+            WebSocketFrame tf = frames.poll();
+            Assert.assertThat("Text Frame.status code",tf.getPayloadAsUTF8(),is(msg));
+        }
+        finally
+        {
+            client.close();
+        }
+    }
+
+    /**
+     * Test the requirement of responding with server terminated close code 1011 when there is an unhandled (internal server error) being produced by the
+     * WebSocket POJO.
+     */
+    @Test
+    public void testInternalError() throws Exception
+    {
+        // Disable Long Stacks from EventDriver (we know this test will throw an exception)
+        enableStacks(EventDriver.class,false);
+
+        BlockheadClient client = new BlockheadClient(server.getServerUri());
+        try
+        {
+            client.connect();
+            client.sendStandardRequest();
+            client.expectUpgradeResponse();
+
+            try (StacklessLogging context = new StacklessLogging(EventDriver.class))
+            {
+                // Generate text frame
+                client.write(new TextFrame().setPayload("CRASH"));
+
+                // Read frame (hopefully close frame)
+                EventQueue<WebSocketFrame> frames = client.readFrames(1,500,TimeUnit.MILLISECONDS);
+                Frame cf = frames.poll();
+                CloseInfo close = new CloseInfo(cf);
+                Assert.assertThat("Close Frame.status code",close.getStatusCode(),is(StatusCode.SERVER_ERROR));
+            }
+        }
+        finally
+        {
+            // Reenable Long Stacks from EventDriver
+            enableStacks(EventDriver.class,true);
+            client.close();
+        }
+    }
+
+    /**
+     * Test http://tools.ietf.org/html/rfc6455#section-4.1 where server side upgrade handling is supposed to be case insensitive.
+     * <p>
+     * This test will simulate a client requesting upgrade with all lowercase headers.
+     */
+    @Test
+    public void testLowercaseUpgrade() throws Exception
+    {
+        BlockheadClient client = new BlockheadClient(server.getServerUri());
+        try
+        {
+            client.connect();
+
+            StringBuilder req = new StringBuilder();
+            req.append("GET ").append(client.getRequestPath()).append(" HTTP/1.1\r\n");
+            req.append("Host: ").append(client.getRequestHost()).append("\r\n");
+            req.append("Upgrade: websocket\r\n");
+            req.append("connection: upgrade\r\n");
+            req.append("sec-websocket-key: ").append(client.getRequestWebSocketKey()).append("\r\n");
+            req.append("sec-websocket-origin: ").append(client.getRequestWebSocketOrigin()).append("\r\n");
+            req.append("sec-websocket-protocol: echo\r\n");
+            req.append("sec-websocket-version: 13\r\n");
+            req.append("\r\n");
+            client.writeRaw(req.toString());
+
+            client.expectUpgradeResponse();
+
+            // Generate text frame
+            String msg = "this is an echo ... cho ... ho ... o";
+            client.write(new TextFrame().setPayload(msg));
+
+            // Read frame (hopefully text frame)
+            EventQueue<WebSocketFrame> frames = client.readFrames(1,500,TimeUnit.MILLISECONDS);
+            WebSocketFrame tf = frames.poll();
+            Assert.assertThat("Text Frame.status code",tf.getPayloadAsUTF8(),is(msg));
+        }
+        finally
+        {
+            client.close();
+        }
+    }
+
+    @Test
+    public void testTextNotUTF8() throws Exception
+    {
+        // Disable Long Stacks from Parser (we know this test will throw an exception)
+        enableStacks(Parser.class,false);
+
+        BlockheadClient client = new BlockheadClient(server.getServerUri());
+        client.setProtocols("other");
+        try
+        {
+            client.connect();
+            client.sendStandardRequest();
+            client.expectUpgradeResponse();
+
+            byte buf[] = new byte[]
+            { (byte)0xC2, (byte)0xC3 };
+
+            WebSocketFrame txt = new TextFrame().setPayload(ByteBuffer.wrap(buf));
+            txt.setMask(Hex.asByteArray("11223344"));
+            ByteBuffer bbHeader = generator.generateHeaderBytes(txt);
+            client.writeRaw(bbHeader);
+            client.writeRaw(txt.getPayload());
+
+            EventQueue<WebSocketFrame> frames = client.readFrames(1,1,TimeUnit.SECONDS);
+            WebSocketFrame frame = frames.poll();
+            Assert.assertThat("frames[0].opcode",frame.getOpCode(),is(OpCode.CLOSE));
+            CloseInfo close = new CloseInfo(frame);
+            Assert.assertThat("Close Status Code",close.getStatusCode(),is(StatusCode.BAD_PAYLOAD));
+        }
+        finally
+        {
+            // Reenable Long Stacks from Parser
+            enableStacks(Parser.class,true);
+            client.close();
+        }
+    }
+
+    /**
+     * Test http://tools.ietf.org/html/rfc6455#section-4.1 where server side upgrade handling is supposed to be case insensitive.
+     * <p>
+     * This test will simulate a client requesting upgrade with all uppercase headers.
+     */
+    @Test
+    public void testUppercaseUpgrade() throws Exception
+    {
+        BlockheadClient client = new BlockheadClient(server.getServerUri());
+        try
+        {
+            client.connect();
+
+            StringBuilder req = new StringBuilder();
+            req.append("GET ").append(client.getRequestPath()).append(" HTTP/1.1\r\n");
+            req.append("HOST: ").append(client.getRequestHost()).append("\r\n");
+            req.append("UPGRADE: WEBSOCKET\r\n");
+            req.append("CONNECTION: UPGRADE\r\n");
+            req.append("SEC-WEBSOCKET-KEY: ").append(client.getRequestWebSocketKey()).append("\r\n");
+            req.append("SEC-WEBSOCKET-ORIGIN: ").append(client.getRequestWebSocketOrigin()).append("\r\n");
+            req.append("SEC-WEBSOCKET-PROTOCOL: ECHO\r\n");
+            req.append("SEC-WEBSOCKET-VERSION: 13\r\n");
+            req.append("\r\n");
+            client.writeRaw(req.toString());
+
+            client.expectUpgradeResponse();
+
+            // Generate text frame
+            String msg = "this is an echo ... cho ... ho ... o";
+            client.write(new TextFrame().setPayload(msg));
+
+            // Read frame (hopefully text frame)
+            EventQueue<WebSocketFrame> frames = client.readFrames(1,500,TimeUnit.MILLISECONDS);
+            WebSocketFrame tf = frames.poll();
+            Assert.assertThat("Text Frame.status code",tf.getPayloadAsUTF8(),is(msg));
+        }
+        finally
+        {
+            client.close();
+        }
+    }
+}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/ABServlet.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/ABServlet.java
new file mode 100644
index 0000000..7bbab1a
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/ABServlet.java
@@ -0,0 +1,44 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server.ab;
+
+import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
+import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
+
+/**
+ * Servlet with bigger message policy sizes, with registered simple echo socket.
+ */
+ at SuppressWarnings("serial")
+public class ABServlet extends WebSocketServlet
+{
+    private static final int KBYTE = 1024;
+    private static final int MBYTE = KBYTE * KBYTE;
+
+    @Override
+    public void configure(WebSocketServletFactory factory)
+    {
+        // Test cases 9.x uses BIG frame sizes, let policy handle them.
+        int bigFrameSize = 20 * MBYTE;
+
+        factory.getPolicy().setMaxTextMessageSize(bigFrameSize);
+        factory.getPolicy().setMaxBinaryMessageSize(bigFrameSize);
+
+        factory.register(ABSocket.class);
+    }
+}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/ABSocket.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/ABSocket.java
new file mode 100644
index 0000000..05798b1
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/ABSocket.java
@@ -0,0 +1,83 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server.ab;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.WebSocketException;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
+import org.eclipse.jetty.websocket.api.annotations.WebSocket;
+import org.eclipse.jetty.websocket.common.util.TextUtil;
+
+/**
+ * Simple Echo WebSocket, using async writes of echo
+ */
+ at WebSocket
+public class ABSocket
+{
+    private static Logger LOG = Log.getLogger(ABSocket.class);
+
+    private Session session;
+
+    @OnWebSocketMessage
+    public void onBinary(byte buf[], int offset, int len)
+    {
+        LOG.debug("onBinary(byte[{}],{},{})",buf.length,offset,len);
+
+        // echo the message back.
+        ByteBuffer data = ByteBuffer.wrap(buf,offset,len);
+        this.session.getRemote().sendBytes(data,null);
+    }
+
+    @OnWebSocketConnect
+    public void onOpen(Session sess)
+    {
+        this.session = sess;
+    }
+
+    @OnWebSocketMessage
+    public void onText(String message)
+    {
+        if (LOG.isDebugEnabled())
+        {
+            if (message == null)
+            {
+                LOG.debug("onText() msg=null");
+            }
+            else
+            {
+                LOG.debug("onText() size={}, msg={}",message.length(),TextUtil.hint(message));
+            }
+        }
+
+        try
+        {
+            // echo the message back.
+            this.session.getRemote().sendString(message,null);
+        }
+        catch (WebSocketException e)
+        {
+            LOG.warn("Unable to echo TEXT message",e);
+        }
+    }
+}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/AbstractABCase.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/AbstractABCase.java
new file mode 100644
index 0000000..7d3f5b4
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/AbstractABCase.java
@@ -0,0 +1,228 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server.ab;
+
+import java.net.URI;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.log.StdErrLog;
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.common.Generator;
+import org.eclipse.jetty.websocket.common.OpCode;
+import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.test.Fuzzed;
+import org.eclipse.jetty.websocket.common.test.LeakTrackingBufferPoolRule;
+import org.eclipse.jetty.websocket.common.test.RawFrameBuilder;
+import org.eclipse.jetty.websocket.server.SimpleServletServer;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.rules.TestName;
+
+public abstract class AbstractABCase implements Fuzzed
+{
+    // Allow Fuzzer / Generator to create bad frames for testing frame validation
+    protected static class BadFrame extends WebSocketFrame
+    {
+        public BadFrame(byte opcode)
+        {
+            super(OpCode.CONTINUATION);
+            super.finRsvOp = (byte)((finRsvOp & 0xF0) | (opcode & 0x0F));
+            // NOTE: Not setting Frame.Type intentionally
+        }
+
+        @Override
+        public void assertValid()
+        {
+        }
+
+        @Override
+        public boolean isControlFrame()
+        {
+            return false;
+        }
+
+        @Override
+        public boolean isDataFrame()
+        {
+            return false;
+        }
+    }
+    
+    protected static final byte FIN = (byte)0x80;
+    protected static final byte NOFIN = 0x00;
+    protected static final byte[] MASK =
+    { 0x12, 0x34, 0x56, 0x78 };
+
+    protected Generator strictGenerator;
+    protected Generator laxGenerator;
+    protected static SimpleServletServer server;
+
+    @Rule
+    public LeakTrackingBufferPoolRule bufferPool = new LeakTrackingBufferPoolRule("Test");
+
+    @Before
+    public void initGenerators()
+    {
+        WebSocketPolicy policy = WebSocketPolicy.newClientPolicy();
+        strictGenerator = new Generator(policy,bufferPool,true);
+        laxGenerator = new Generator(policy,bufferPool,false);
+    }
+
+    @BeforeClass
+    public static void startServer() throws Exception
+    {
+        server = new SimpleServletServer(new ABServlet());
+        server.start();
+    }
+
+    @AfterClass
+    public static void stopServer()
+    {
+        server.stop();
+    }
+    
+    /**
+     * Make a copy of a byte buffer.
+     * <p>
+     * This is important in some tests, as the underlying byte buffer contained in a Frame can be modified through
+     * masking and make it difficult to compare the results in the fuzzer. 
+     * 
+     * @param payload the payload to copy
+     * @return a new byte array of the payload contents
+     */
+    protected ByteBuffer copyOf(byte[] payload)
+    {
+        return ByteBuffer.wrap(Arrays.copyOf(payload,payload.length));
+    }
+    
+    /**
+     * Make a copy of a byte buffer.
+     * <p>
+     * This is important in some tests, as the underlying byte buffer contained in a Frame can be modified through
+     * masking and make it difficult to compare the results in the fuzzer. 
+     * 
+     * @param payload the payload to copy
+     * @return a new byte array of the payload contents
+     */
+    protected ByteBuffer clone(ByteBuffer payload)
+    {
+        ByteBuffer copy = ByteBuffer.allocate(payload.remaining());
+        copy.put(payload.slice());
+        copy.flip();
+        return copy;
+    }
+    
+    /**
+     * Make a copy of a byte buffer.
+     * <p>
+     * This is important in some tests, as the underlying byte buffer contained in a Frame can be modified through
+     * masking and make it difficult to compare the results in the fuzzer. 
+     * 
+     * @param payload the payload to copy
+     * @return a new byte array of the payload contents
+     */
+    protected ByteBuffer copyOf(ByteBuffer payload)
+    {
+        ByteBuffer copy = ByteBuffer.allocate(payload.remaining());
+        BufferUtil.clearToFill(copy);
+        BufferUtil.put(payload,copy);
+        BufferUtil.flipToFlush(copy,0);
+        return copy;
+    }
+
+    public static String toUtf8String(byte[] buf)
+    {
+        String raw = StringUtil.toUTF8String(buf,0,buf.length);
+        StringBuilder ret = new StringBuilder();
+        int len = raw.length();
+        for (int i = 0; i < len; i++)
+        {
+            int codepoint = raw.codePointAt(i);
+            if (Character.isUnicodeIdentifierPart(codepoint))
+            {
+                ret.append(String.format("\\u%04X",codepoint));
+            }
+            else
+            {
+                ret.append(Character.toChars(codepoint));
+            }
+        }
+        return ret.toString();
+    }
+
+    @Rule
+    public TestName testname = new TestName();
+
+    /**
+     * @deprecated use {@link StacklessLogging} in a try-with-resources block instead
+     */
+    @Deprecated
+    protected void enableStacks(Class<?> clazz, boolean enabled)
+    {
+        StdErrLog log = StdErrLog.getLogger(clazz);
+        log.setHideStacks(!enabled);
+    }
+
+    public Generator getLaxGenerator()
+    {
+        return laxGenerator;
+    }
+
+    public SimpleServletServer getServer()
+    {
+        return server;
+    }
+    
+    @Override
+    public URI getServerURI()
+    {
+        return server.getServerUri();
+    }
+    
+    @Override
+    public String getTestMethodName()
+    {
+        return testname.getMethodName();
+    }
+
+    public static byte[] masked(final byte[] data)
+    {
+        return RawFrameBuilder.mask(data,MASK);
+    }
+
+    public static void putLength(ByteBuffer buf, int length, boolean masked)
+    {
+        RawFrameBuilder.putLength(buf,length,masked);
+    }
+
+    public static void putMask(ByteBuffer buf)
+    {
+        buf.put(MASK);
+    }
+
+    public static void putPayloadLength(ByteBuffer buf, int length)
+    {
+        putLength(buf,length,true);
+    }
+}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase1.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase1.java
new file mode 100644
index 0000000..659b3fb
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase1.java
@@ -0,0 +1,472 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server.ab;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.eclipse.jetty.websocket.api.StatusCode;
+import org.eclipse.jetty.websocket.common.CloseInfo;
+import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.frames.BinaryFrame;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
+import org.eclipse.jetty.websocket.common.test.Fuzzer;
+import org.eclipse.jetty.websocket.common.test.Fuzzer.SendMode;
+import org.junit.Test;
+
+public class TestABCase1 extends AbstractABCase
+{
+    /**
+     * Echo 0 byte TEXT message
+     */
+    @Test
+    public void testCase1_1_1() throws Exception
+    {
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new TextFrame());
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new TextFrame());
+        expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        try (Fuzzer fuzzer = new Fuzzer(this))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+        }
+    }
+
+    /**
+     * Echo 125 byte TEXT message (uses small 7-bit payload length)
+     */
+    @Test
+    public void testCase1_1_2() throws Exception
+    {
+        byte payload[] = new byte[125];
+        Arrays.fill(payload,(byte)'*');
+        ByteBuffer buf = ByteBuffer.wrap(payload);
+
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new TextFrame().setPayload(buf));
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new TextFrame().setPayload(clone(buf)));
+        expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        try (Fuzzer fuzzer = new Fuzzer(this))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+        }
+    }
+
+    /**
+     * Echo 126 byte TEXT message (uses medium 2 byte payload length)
+     */
+    @Test
+    public void testCase1_1_3() throws Exception
+    {
+        byte payload[] = new byte[126];
+        Arrays.fill(payload,(byte)'*');
+        ByteBuffer buf = ByteBuffer.wrap(payload);
+
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new TextFrame().setPayload(buf));
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new TextFrame().setPayload(clone(buf)));
+        expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        try (Fuzzer fuzzer = new Fuzzer(this))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+        }
+    }
+
+    /**
+     * Echo 127 byte TEXT message (uses medium 2 byte payload length)
+     */
+    @Test
+    public void testCase1_1_4() throws Exception
+    {
+        byte payload[] = new byte[127];
+        Arrays.fill(payload,(byte)'*');
+        ByteBuffer buf = ByteBuffer.wrap(payload);
+
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new TextFrame().setPayload(buf));
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new TextFrame().setPayload(clone(buf)));
+        expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        try (Fuzzer fuzzer = new Fuzzer(this))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+        }
+    }
+
+    /**
+     * Echo 128 byte TEXT message (uses medium 2 byte payload length)
+     */
+    @Test
+    public void testCase1_1_5() throws Exception
+    {
+        byte payload[] = new byte[128];
+        Arrays.fill(payload,(byte)'*');
+        ByteBuffer buf = ByteBuffer.wrap(payload);
+
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new TextFrame().setPayload(buf));
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new TextFrame().setPayload(clone(buf)));
+        expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        try (Fuzzer fuzzer = new Fuzzer(this))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+        }
+    }
+
+    /**
+     * Echo 65535 byte TEXT message (uses medium 2 byte payload length)
+     */
+    @Test
+    public void testCase1_1_6() throws Exception
+    {
+        byte payload[] = new byte[65535];
+        Arrays.fill(payload,(byte)'*');
+        ByteBuffer buf = ByteBuffer.wrap(payload);
+
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new TextFrame().setPayload(buf));
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new TextFrame().setPayload(clone(buf)));
+        expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        try (Fuzzer fuzzer = new Fuzzer(this))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+        }
+    }
+
+    /**
+     * Echo 65536 byte TEXT message (uses large 8 byte payload length)
+     */
+    @Test
+    public void testCase1_1_7() throws Exception
+    {
+        byte payload[] = new byte[65536];
+        Arrays.fill(payload,(byte)'*');
+        ByteBuffer buf = ByteBuffer.wrap(payload);
+
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new TextFrame().setPayload(buf));
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new TextFrame().setPayload(clone(buf)));
+        expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        try (Fuzzer fuzzer = new Fuzzer(this))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+        }
+    }
+
+    /**
+     * Echo 65536 byte TEXT message (uses large 8 byte payload length).
+     * <p>
+     * Only send 1 TEXT frame from client, but in small segments (flushed after each).
+     * <p>
+     * This is done to test the parsing together of the frame on the server side.
+     */
+    @Test
+    public void testCase1_1_8() throws Exception
+    {
+        byte payload[] = new byte[65536];
+        Arrays.fill(payload,(byte)'*');
+        ByteBuffer buf = ByteBuffer.wrap(payload);
+        int segmentSize = 997;
+
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new TextFrame().setPayload(buf));
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new TextFrame().setPayload(clone(buf)));
+        expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        try (Fuzzer fuzzer = new Fuzzer(this))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(SendMode.SLOW);
+            fuzzer.setSlowSendSegmentSize(segmentSize);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+        }
+    }
+
+    /**
+     * Echo 0 byte BINARY message
+     */
+    @Test
+    public void testCase1_2_1() throws Exception
+    {
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new BinaryFrame());
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new BinaryFrame());
+        expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        try (Fuzzer fuzzer = new Fuzzer(this))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+        }
+    }
+
+    /**
+     * Echo 125 byte BINARY message (uses small 7-bit payload length)
+     */
+    @Test
+    public void testCase1_2_2() throws Exception
+    {
+        byte payload[] = new byte[125];
+        Arrays.fill(payload,(byte)0xFE);
+        ByteBuffer buf = ByteBuffer.wrap(payload);
+
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new BinaryFrame().setPayload(buf));
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new BinaryFrame().setPayload(clone(buf)));
+        expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        try (Fuzzer fuzzer = new Fuzzer(this))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+        }
+    }
+
+    /**
+     * Echo 126 byte BINARY message (uses medium 2 byte payload length)
+     */
+    @Test
+    public void testCase1_2_3() throws Exception
+    {
+        byte payload[] = new byte[126];
+        Arrays.fill(payload,(byte)0xFE);
+        ByteBuffer buf = ByteBuffer.wrap(payload);
+
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new BinaryFrame().setPayload(buf));
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new BinaryFrame().setPayload(clone(buf)));
+        expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        try (Fuzzer fuzzer = new Fuzzer(this))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+        }
+    }
+
+    /**
+     * Echo 127 byte BINARY message (uses medium 2 byte payload length)
+     */
+    @Test
+    public void testCase1_2_4() throws Exception
+    {
+        byte payload[] = new byte[127];
+        Arrays.fill(payload,(byte)0xFE);
+        ByteBuffer buf = ByteBuffer.wrap(payload);
+
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new BinaryFrame().setPayload(buf));
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new BinaryFrame().setPayload(clone(buf)));
+        expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        try (Fuzzer fuzzer = new Fuzzer(this))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+        }
+    }
+
+    /**
+     * Echo 128 byte BINARY message (uses medium 2 byte payload length)
+     */
+    @Test
+    public void testCase1_2_5() throws Exception
+    {
+        byte payload[] = new byte[128];
+        Arrays.fill(payload,(byte)0xFE);
+        ByteBuffer buf = ByteBuffer.wrap(payload);
+
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new BinaryFrame().setPayload(buf));
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new BinaryFrame().setPayload(clone(buf)));
+        expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        try (Fuzzer fuzzer = new Fuzzer(this))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+        }
+    }
+
+    /**
+     * Echo 65535 byte BINARY message (uses medium 2 byte payload length)
+     */
+    @Test
+    public void testCase1_2_6() throws Exception
+    {
+        byte payload[] = new byte[65535];
+        Arrays.fill(payload,(byte)0xFE);
+        ByteBuffer buf = ByteBuffer.wrap(payload);
+
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new BinaryFrame().setPayload(buf));
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new BinaryFrame().setPayload(clone(buf)));
+        expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        try (Fuzzer fuzzer = new Fuzzer(this))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+        }
+    }
+
+    /**
+     * Echo 65536 byte BINARY message (uses large 8 byte payload length)
+     */
+    @Test
+    public void testCase1_2_7() throws Exception
+    {
+        byte payload[] = new byte[65536];
+        Arrays.fill(payload,(byte)0xFE);
+        ByteBuffer buf = ByteBuffer.wrap(payload);
+
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new BinaryFrame().setPayload(buf));
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new BinaryFrame().setPayload(clone(buf)));
+        expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        try (Fuzzer fuzzer = new Fuzzer(this))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+        }
+    }
+
+    /**
+     * Echo 65536 byte BINARY message (uses large 8 byte payload length).
+     * <p>
+     * Only send 1 BINARY frame from client, but in small segments (flushed after each).
+     * <p>
+     * This is done to test the parsing together of the frame on the server side.
+     */
+    @Test
+    public void testCase1_2_8() throws Exception
+    {
+        byte payload[] = new byte[65536];
+        Arrays.fill(payload,(byte)0xFE);
+        ByteBuffer buf = ByteBuffer.wrap(payload);
+        int segmentSize = 997;
+
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new BinaryFrame().setPayload(buf));
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new BinaryFrame().setPayload(clone(buf)));
+        expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        try (Fuzzer fuzzer = new Fuzzer(this))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(SendMode.SLOW);
+            fuzzer.setSlowSendSegmentSize(segmentSize);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+        }
+    }
+}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase2.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase2.java
new file mode 100644
index 0000000..65ba463
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase2.java
@@ -0,0 +1,331 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server.ab;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.eclipse.jetty.toolchain.test.AdvancedRunner;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.log.StacklessLogging;
+import org.eclipse.jetty.websocket.api.StatusCode;
+import org.eclipse.jetty.websocket.common.CloseInfo;
+import org.eclipse.jetty.websocket.common.OpCode;
+import org.eclipse.jetty.websocket.common.Parser;
+import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.frames.PingFrame;
+import org.eclipse.jetty.websocket.common.frames.PongFrame;
+import org.eclipse.jetty.websocket.common.test.Fuzzer;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+ at RunWith(AdvancedRunner.class)
+public class TestABCase2 extends AbstractABCase
+{
+    /**
+     * Ping without payload
+     */
+    @Test
+    public void testCase2_1() throws Exception
+    {
+        WebSocketFrame send = new PingFrame();
+
+        WebSocketFrame expect = new PongFrame();
+
+        try (Fuzzer fuzzer = new Fuzzer(this))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+        }
+    }
+
+    /**
+     * 10 pings
+     */
+    @Test
+    public void testCase2_10() throws Exception
+    {
+        // send 10 pings each with unique payload
+        // send close
+        // expect 10 pongs with our unique payload
+        // expect close
+
+        int pingCount = 10;
+
+        List<WebSocketFrame> send = new ArrayList<>();
+        List<WebSocketFrame> expect = new ArrayList<>();
+
+        for (int i = 0; i < pingCount; i++)
+        {
+            String payload = String.format("ping-%d[%X]",i,i);
+            send.add(new PingFrame().setPayload(payload));
+            expect.add(new PongFrame().setPayload(payload));
+        }
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+        expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        try (Fuzzer fuzzer = new Fuzzer(this))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+        }
+    }
+
+    /**
+     * 10 pings, sent slowly
+     */
+    @Test
+    public void testCase2_11() throws Exception
+    {
+        // send 10 pings (slowly) each with unique payload
+        // send close
+        // expect 10 pongs with OUR payload
+        // expect close
+
+        int pingCount = 10;
+
+        List<WebSocketFrame> send = new ArrayList<>();
+        List<WebSocketFrame> expect = new ArrayList<>();
+
+        for (int i = 0; i < pingCount; i++)
+        {
+            String payload = String.format("ping-%d[%X]",i,i);
+            send.add(new PingFrame().setPayload(payload));
+            expect.add(new PongFrame().setPayload(payload));
+        }
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+        expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        try (Fuzzer fuzzer = new Fuzzer(this))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.SLOW);
+            fuzzer.setSlowSendSegmentSize(5);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+        }
+    }
+
+    /**
+     * Ping with small text payload
+     */
+    @Test
+    public void testCase2_2() throws Exception
+    {
+        byte payload[] = StringUtil.getUtf8Bytes("Hello world");
+
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new PingFrame().setPayload(payload));
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new PongFrame().setPayload(copyOf(payload)));
+        expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        try (Fuzzer fuzzer = new Fuzzer(this))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+        }
+    }
+
+    /**
+     * Ping with small binary (non-utf8) payload
+     */
+    @Test
+    public void testCase2_3() throws Exception
+    {
+        byte payload[] = new byte[] { 0x00, (byte)0xFF, (byte)0xFE, (byte)0xFD, (byte)0xFC, (byte)0xFB, 0x00, (byte)0xFF };
+
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new PingFrame().setPayload(payload));
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new PongFrame().setPayload(copyOf(payload)));
+        expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        try (Fuzzer fuzzer = new Fuzzer(this))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+        }
+    }
+
+    /**
+     * Ping with 125 byte binary payload
+     */
+    @Test
+    public void testCase2_4() throws Exception
+    {
+        byte payload[] = new byte[125];
+        Arrays.fill(payload,(byte)0xFE);
+
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new PingFrame().setPayload(payload));
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new PongFrame().setPayload(copyOf(payload)));
+        expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        try (Fuzzer fuzzer = new Fuzzer(this))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+        }
+    }
+
+    /**
+     * Ping with 126 byte binary payload
+     */
+    @Test
+    public void testCase2_5() throws Exception
+    {
+        try (StacklessLogging scope = new StacklessLogging(Parser.class))
+        {
+            byte payload[] = new byte[126]; // intentionally too big
+            Arrays.fill(payload,(byte)'5');
+            ByteBuffer buf = ByteBuffer.wrap(payload);
+
+            List<WebSocketFrame> send = new ArrayList<>();
+            // trick websocket frame into making extra large payload for ping
+            send.add(new BadFrame(OpCode.PING).setPayload(buf));
+            send.add(new CloseInfo(StatusCode.NORMAL,"Test 2.5").asFrame());
+
+            List<WebSocketFrame> expect = new ArrayList<>();
+            expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame());
+
+            try (Fuzzer fuzzer = new Fuzzer(this))
+            {
+                fuzzer.connect();
+                fuzzer.setSendMode(Fuzzer.SendMode.BULK);
+                fuzzer.send(send);
+                fuzzer.expect(expect);
+            }
+        }
+    }
+
+    /**
+     * Ping with 125 byte binary payload (slow send)
+     */
+    @Test
+    public void testCase2_6() throws Exception
+    {
+        byte payload[] = new byte[125];
+        Arrays.fill(payload,(byte)'6');
+
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new PingFrame().setPayload(payload));
+        send.add(new CloseInfo(StatusCode.NORMAL,"Test 2.6").asFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new PongFrame().setPayload(copyOf(payload)));
+        expect.add(new CloseInfo(StatusCode.NORMAL,"Test 2.6").asFrame());
+
+        try (Fuzzer fuzzer = new Fuzzer(this))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.SLOW);
+            fuzzer.setSlowSendSegmentSize(1);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+        }
+    }
+
+    /**
+     * Unsolicited pong frame without payload
+     */
+    @Test
+    public void testCase2_7() throws Exception
+    {
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new PongFrame()); // unsolicited pong
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        try (Fuzzer fuzzer = new Fuzzer(this))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+        }
+    }
+
+    /**
+     * Unsolicited pong frame with basic payload
+     */
+    @Test
+    public void testCase2_8() throws Exception
+    {
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new PongFrame().setPayload("unsolicited")); // unsolicited pong
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        try (Fuzzer fuzzer = new Fuzzer(this))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+        }
+    }
+
+    /**
+     * Unsolicited pong frame, then ping with basic payload
+     */
+    @Test
+    public void testCase2_9() throws Exception
+    {
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new PongFrame().setPayload("unsolicited")); // unsolicited pong
+        send.add(new PingFrame().setPayload("our ping")); // our ping
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new PongFrame().setPayload("our ping")); // our pong
+        expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        try (Fuzzer fuzzer = new Fuzzer(this))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+        }
+    }
+}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase3.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase3.java
new file mode 100644
index 0000000..2eaae4f
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase3.java
@@ -0,0 +1,211 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server.ab;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.eclipse.jetty.toolchain.test.AdvancedRunner;
+import org.eclipse.jetty.util.log.StacklessLogging;
+import org.eclipse.jetty.websocket.api.StatusCode;
+import org.eclipse.jetty.websocket.common.CloseInfo;
+import org.eclipse.jetty.websocket.common.Parser;
+import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.frames.BinaryFrame;
+import org.eclipse.jetty.websocket.common.frames.PingFrame;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
+import org.eclipse.jetty.websocket.common.test.Fuzzer;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test various RSV violations
+ */
+ at RunWith(AdvancedRunner.class)
+public class TestABCase3 extends AbstractABCase
+{
+    /**
+     * Send small text frame, with RSV1 == true, with no extensions defined.
+     */
+    @Test
+    public void testCase3_1() throws Exception
+    {
+        WebSocketFrame send = new TextFrame().setPayload("small").setRsv1(true); // intentionally bad
+
+        WebSocketFrame expect = new CloseInfo(StatusCode.PROTOCOL).asFrame();
+
+        try (Fuzzer fuzzer = new Fuzzer(this); StacklessLogging logging = new StacklessLogging(Parser.class))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+        }
+    }
+
+    /**
+     * Send small text frame, send again with RSV2 == true, then ping, with no extensions defined.
+     */
+    @Test
+    public void testCase3_2() throws Exception
+    {
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new TextFrame().setPayload("small"));
+        send.add(new TextFrame().setPayload("small").setRsv2(true)); // intentionally bad
+        send.add(new PingFrame().setPayload("ping"));
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new TextFrame().setPayload("small")); // echo on good frame
+        expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame());
+
+        try (Fuzzer fuzzer = new Fuzzer(this); StacklessLogging logging = new StacklessLogging(Parser.class))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+        }
+    }
+
+    /**
+     * Send small text frame, send again with (RSV1 & RSV2), then ping, with no extensions defined.
+     */
+    @Test
+    public void testCase3_3() throws Exception
+    {
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new TextFrame().setPayload("small"));
+        send.add(new TextFrame().setPayload("small").setRsv1(true).setRsv2(true)); // intentionally bad
+        send.add(new PingFrame().setPayload("ping"));
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new TextFrame().setPayload("small")); // echo on good frame
+        expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame());
+
+        try (Fuzzer fuzzer = new Fuzzer(this); StacklessLogging logging = new StacklessLogging(Parser.class))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.PER_FRAME);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+        }
+    }
+
+    /**
+     * Send small text frame, send again with (RSV3), then ping, with no extensions defined.
+     */
+    @Test
+    public void testCase3_4() throws Exception
+    {
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new TextFrame().setPayload("small"));
+        send.add(new TextFrame().setPayload("small").setRsv3(true)); // intentionally bad
+        send.add(new PingFrame().setPayload("ping"));
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new TextFrame().setPayload("small")); // echo on good frame
+        expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame());
+
+        try (Fuzzer fuzzer = new Fuzzer(this); StacklessLogging logging = new StacklessLogging(Parser.class))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.SLOW);
+            fuzzer.setSlowSendSegmentSize(1);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+        }
+    }
+
+    /**
+     * Send binary frame with (RSV3 & RSV1), with no extensions defined.
+     */
+    @Test
+    public void testCase3_5() throws Exception
+    {
+        byte payload[] = new byte[8];
+        Arrays.fill(payload,(byte)0xFF);
+
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new BinaryFrame().setPayload(payload).setRsv3(true).setRsv1(true)); // intentionally bad
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame());
+
+        try (Fuzzer fuzzer = new Fuzzer(this); StacklessLogging logging = new StacklessLogging(Parser.class))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+        }
+    }
+
+    /**
+     * Send ping frame with (RSV3 & RSV2), with no extensions defined.
+     */
+    @Test
+    public void testCase3_6() throws Exception
+    {
+        byte payload[] = new byte[8];
+        Arrays.fill(payload,(byte)0xFF);
+
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new PingFrame().setPayload(payload).setRsv3(true).setRsv2(true)); // intentionally bad
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame());
+
+        try (Fuzzer fuzzer = new Fuzzer(this); StacklessLogging logging = new StacklessLogging(Parser.class))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+        }
+    }
+
+    /**
+     * Send close frame with (RSV3 & RSV2 & RSV1), with no extensions defined.
+     */
+    @Test
+    public void testCase3_7() throws Exception
+    {
+        byte payload[] = new byte[8];
+        Arrays.fill(payload,(byte)0xFF);
+
+        List<WebSocketFrame> send = new ArrayList<>();
+        WebSocketFrame frame = new CloseInfo(StatusCode.NORMAL).asFrame();
+        frame.setRsv1(true);
+        frame.setRsv2(true);
+        frame.setRsv3(true);
+        send.add(frame); // intentionally bad
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame());
+
+        try (Fuzzer fuzzer = new Fuzzer(this); StacklessLogging logging = new StacklessLogging(Parser.class))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+        }
+    }
+}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase4.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase4.java
new file mode 100644
index 0000000..46e244f
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase4.java
@@ -0,0 +1,284 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server.ab;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jetty.toolchain.test.AdvancedRunner;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.log.StacklessLogging;
+import org.eclipse.jetty.websocket.api.StatusCode;
+import org.eclipse.jetty.websocket.common.CloseInfo;
+import org.eclipse.jetty.websocket.common.Parser;
+import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.frames.PingFrame;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
+import org.eclipse.jetty.websocket.common.test.Fuzzer;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test various bad / forbidden opcodes (per spec)
+ */
+ at RunWith(AdvancedRunner.class)
+public class TestABCase4 extends AbstractABCase
+{
+    /**
+     * Send opcode 3 (reserved)
+     */
+    @Test
+    public void testCase4_1_1() throws Exception
+    {
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new BadFrame((byte)3)); // intentionally bad
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame());
+
+        try (Fuzzer fuzzer = new Fuzzer(this); StacklessLogging logging = new StacklessLogging(Parser.class))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+        }
+    }
+
+    /**
+     * Send opcode 4 (reserved), with payload
+     */
+    @Test
+    public void testCase4_1_2() throws Exception
+    {
+        byte payload[] = StringUtil.getUtf8Bytes("reserved payload");
+        ByteBuffer buf = ByteBuffer.wrap(payload);
+
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new BadFrame((byte)4).setPayload(buf)); // intentionally bad
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame());
+
+        try (Fuzzer fuzzer = new Fuzzer(this); StacklessLogging logging = new StacklessLogging(Parser.class))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+        }
+    }
+
+    /**
+     * Send small text, then frame with opcode 5 (reserved), then ping
+     */
+    @Test
+    public void testCase4_1_3() throws Exception
+    {
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new TextFrame().setPayload("hello"));
+        send.add(new BadFrame((byte)5)); // intentionally bad
+        send.add(new PingFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new TextFrame().setPayload("hello")); // echo
+        expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame());
+
+        try (Fuzzer fuzzer = new Fuzzer(this); StacklessLogging logging = new StacklessLogging(Parser.class))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+        }
+    }
+
+    /**
+     * Send small text, then frame with opcode 6 (reserved) w/payload, then ping
+     */
+    @Test
+    public void testCase4_1_4() throws Exception
+    {
+        ByteBuffer buf = ByteBuffer.wrap(StringUtil.getUtf8Bytes("bad"));
+
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new TextFrame().setPayload("hello"));
+        send.add(new BadFrame((byte)6).setPayload(buf)); // intentionally bad
+        send.add(new PingFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new TextFrame().setPayload("hello")); // echo
+        expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame());
+
+        try (Fuzzer fuzzer = new Fuzzer(this); StacklessLogging logging = new StacklessLogging(Parser.class))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+        }
+    }
+
+    /**
+     * Send small text, then frame with opcode 7 (reserved) w/payload, then ping
+     */
+    @Test
+    public void testCase4_1_5() throws Exception
+    {
+        ByteBuffer buf = ByteBuffer.wrap(StringUtil.getUtf8Bytes("bad"));
+
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new TextFrame().setPayload("hello"));
+        send.add(new BadFrame((byte)7).setPayload(buf)); // intentionally bad
+        send.add(new PingFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new TextFrame().setPayload("hello")); // echo
+        expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame());
+
+        try (Fuzzer fuzzer = new Fuzzer(this); StacklessLogging logging = new StacklessLogging(Parser.class))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+        }
+    }
+
+    /**
+     * Send opcode 11 (reserved)
+     */
+    @Test
+    public void testCase4_2_1() throws Exception
+    {
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new BadFrame((byte)11)); // intentionally bad
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame());
+
+        try (Fuzzer fuzzer = new Fuzzer(this); StacklessLogging logging = new StacklessLogging(Parser.class))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+        }
+    }
+
+    /**
+     * Send opcode 12 (reserved)
+     */
+    @Test
+    public void testCase4_2_2() throws Exception
+    {
+        ByteBuffer buf = ByteBuffer.wrap(StringUtil.getUtf8Bytes("bad"));
+
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new BadFrame((byte)12).setPayload(buf)); // intentionally bad
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame());
+
+        try (Fuzzer fuzzer = new Fuzzer(this); StacklessLogging logging = new StacklessLogging(Parser.class))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+        }
+    }
+
+    /**
+     * Send small text, then frame with opcode 13 (reserved), then ping
+     */
+    @Test
+    public void testCase4_2_3() throws Exception
+    {
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new TextFrame().setPayload("hello"));
+        send.add(new BadFrame((byte)13)); // intentionally bad
+        send.add(new PingFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new TextFrame().setPayload("hello")); // echo
+        expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame());
+
+        try (Fuzzer fuzzer = new Fuzzer(this); StacklessLogging logging = new StacklessLogging(Parser.class))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+        }
+    }
+
+    /**
+     * Send small text, then frame with opcode 14 (reserved), then ping
+     */
+    @Test
+    public void testCase4_2_4() throws Exception
+    {
+        ByteBuffer buf = ByteBuffer.wrap(StringUtil.getUtf8Bytes("bad"));
+
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new TextFrame().setPayload("hello"));
+        send.add(new BadFrame((byte)14).setPayload(buf)); // intentionally bad
+        send.add(new PingFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new TextFrame().setPayload("hello")); // echo
+        expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame());
+
+        try (Fuzzer fuzzer = new Fuzzer(this); StacklessLogging logging = new StacklessLogging(Parser.class))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+        }
+    }
+
+    /**
+     * Send small text, then frame with opcode 15 (reserved), then ping
+     */
+    @Test
+    public void testCase4_2_5() throws Exception
+    {
+        ByteBuffer buf = ByteBuffer.wrap(StringUtil.getUtf8Bytes("bad"));
+
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new TextFrame().setPayload("hello"));
+        send.add(new BadFrame((byte)15).setPayload(buf)); // intentionally bad
+        send.add(new PingFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new TextFrame().setPayload("hello")); // echo
+        expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame());
+
+        try (Fuzzer fuzzer = new Fuzzer(this); StacklessLogging logging = new StacklessLogging(Parser.class))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+        }
+    }
+}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase5.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase5.java
new file mode 100644
index 0000000..b4c058f
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase5.java
@@ -0,0 +1,617 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server.ab;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.toolchain.test.AdvancedRunner;
+import org.eclipse.jetty.toolchain.test.annotation.Slow;
+import org.eclipse.jetty.util.log.StacklessLogging;
+import org.eclipse.jetty.websocket.api.StatusCode;
+import org.eclipse.jetty.websocket.common.CloseInfo;
+import org.eclipse.jetty.websocket.common.Parser;
+import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.frames.ContinuationFrame;
+import org.eclipse.jetty.websocket.common.frames.PingFrame;
+import org.eclipse.jetty.websocket.common.frames.PongFrame;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
+import org.eclipse.jetty.websocket.common.test.Fuzzer;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Fragmentation Tests
+ */
+ at RunWith(AdvancedRunner.class)
+public class TestABCase5 extends AbstractABCase
+{
+    /**
+     * Send ping fragmented in 2 packets
+     */
+    @Test
+    public void testCase5_1() throws Exception
+    {
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new PingFrame().setPayload("hello, ").setFin(false));
+        send.add(new ContinuationFrame().setPayload("world"));
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame());
+
+        try(Fuzzer fuzzer = new Fuzzer(this);StacklessLogging supress = new StacklessLogging(Parser.class))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+        }
+            }
+
+    /**
+     * Send continuation+fin, then text+fin (framewise)
+     */
+    @Test
+    public void testCase5_10() throws Exception
+    {
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new ContinuationFrame().setPayload("sorry").setFin(true));
+        send.add(new TextFrame().setPayload("hello, world"));
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame());
+
+        try(Fuzzer fuzzer = new Fuzzer(this);StacklessLogging supress = new StacklessLogging(Parser.class))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.PER_FRAME);
+            fuzzer.sendAndIgnoreBrokenPipe(send);
+            fuzzer.expect(expect);
+        }
+    }
+
+    /**
+     * Send continuation+fin, then text+fin (slowly)
+     */
+    @Test
+    public void testCase5_11() throws Exception
+    {
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new ContinuationFrame().setPayload("sorry").setFin(true));
+        send.add(new TextFrame().setPayload("hello, world"));
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame());
+
+        try(Fuzzer fuzzer = new Fuzzer(this);StacklessLogging supress = new StacklessLogging(Parser.class))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.SLOW);
+            fuzzer.setSlowSendSegmentSize(1);
+            fuzzer.sendAndIgnoreBrokenPipe(send);
+            fuzzer.expect(expect);
+        }
+    }
+
+    /**
+     * Send continuation+!fin, then text+fin
+     */
+    @Test
+    public void testCase5_12() throws Exception
+    {
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new ContinuationFrame().setPayload("sorry").setFin(false));
+        send.add(new TextFrame().setPayload("hello, world"));
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame());
+
+        try(Fuzzer fuzzer = new Fuzzer(this);StacklessLogging supress = new StacklessLogging(Parser.class))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.BULK);
+            fuzzer.sendAndIgnoreBrokenPipe(send);
+            fuzzer.expect(expect);
+        }
+    }
+
+    /**
+     * Send continuation+!fin, then text+fin (framewise)
+     */
+    @Test
+    public void testCase5_13() throws Exception
+    {
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new ContinuationFrame().setPayload("sorry").setFin(false));
+        send.add(new TextFrame().setPayload("hello, world"));
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame());
+
+        try(Fuzzer fuzzer = new Fuzzer(this);StacklessLogging supress = new StacklessLogging(Parser.class))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.PER_FRAME);
+            fuzzer.sendAndIgnoreBrokenPipe(send);
+            fuzzer.expect(expect);
+        }
+    }
+
+    /**
+     * Send continuation+!fin, then text+fin (slowly)
+     */
+    @Test
+    public void testCase5_14() throws Exception
+    {
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new ContinuationFrame().setPayload("sorry").setFin(false));
+        send.add(new TextFrame().setPayload("hello, world"));
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame());
+
+        try(Fuzzer fuzzer = new Fuzzer(this);StacklessLogging supress = new StacklessLogging(Parser.class))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.SLOW);
+            fuzzer.setSlowSendSegmentSize(1);
+            fuzzer.sendAndIgnoreBrokenPipe(send);
+            fuzzer.expect(expect);
+        }
+    }
+
+    /**
+     * Send text fragmented properly in 2 frames, then continuation!fin, then text unfragmented.
+     */
+    @Test
+    public void testCase5_15() throws Exception
+    {
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new TextFrame().setPayload("fragment1").setFin(false));
+        send.add(new ContinuationFrame().setPayload("fragment2").setFin(true));
+        send.add(new ContinuationFrame().setPayload("fragment3").setFin(false)); // bad frame
+        send.add(new TextFrame().setPayload("fragment4").setFin(true));
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new TextFrame().setPayload("fragment1fragment2"));
+        expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame());
+
+        try(Fuzzer fuzzer = new Fuzzer(this);StacklessLogging supress = new StacklessLogging(Parser.class))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+        }
+    }
+
+    /**
+     * (continuation!fin, text!fin, continuation+fin) * 2
+     */
+    @Test
+    public void testCase5_16() throws Exception
+    {
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new ContinuationFrame().setPayload("fragment1").setFin(false)); // bad frame
+        send.add(new TextFrame().setPayload("fragment2").setFin(false));
+        send.add(new ContinuationFrame().setPayload("fragment3").setFin(true));
+        send.add(new ContinuationFrame().setPayload("fragment4").setFin(false)); // bad frame
+        send.add(new TextFrame().setPayload("fragment5").setFin(false));
+        send.add(new ContinuationFrame().setPayload("fragment6").setFin(true));
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame());
+
+        try(Fuzzer fuzzer = new Fuzzer(this);StacklessLogging supress = new StacklessLogging(Parser.class))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.BULK);
+            fuzzer.sendAndIgnoreBrokenPipe(send);
+            fuzzer.expect(expect);
+        }
+    }
+
+    /**
+     * (continuation+fin, text!fin, continuation+fin) * 2
+     */
+    @Test
+    public void testCase5_17() throws Exception
+    {
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new ContinuationFrame().setPayload("fragment1").setFin(true)); // nothing to continue
+        send.add(new TextFrame().setPayload("fragment2").setFin(false));
+        send.add(new ContinuationFrame().setPayload("fragment3").setFin(true));
+        send.add(new ContinuationFrame().setPayload("fragment4").setFin(true)); // nothing to continue
+        send.add(new TextFrame().setPayload("fragment5").setFin(false));
+        send.add(new ContinuationFrame().setPayload("fragment6").setFin(true));
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame());
+
+        try(Fuzzer fuzzer = new Fuzzer(this);StacklessLogging supress = new StacklessLogging(Parser.class))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+        }
+    }
+
+    /**
+     * text message fragmented in 2 frames, both frames as opcode=TEXT
+     */
+    @Test
+    public void testCase5_18() throws Exception
+    {
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new TextFrame().setPayload("fragment1").setFin(false));
+        send.add(new TextFrame().setPayload("fragment2").setFin(true)); // bad frame, must be continuation
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame());
+
+        try(Fuzzer fuzzer = new Fuzzer(this);StacklessLogging supress = new StacklessLogging(Parser.class))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+        }
+    }
+
+    /**
+     * send text message fragmented in 5 frames, with 2 pings and wait between.
+     */
+    @Test
+    @Slow
+    public void testCase5_19() throws Exception
+    {
+        // phase 1
+        List<WebSocketFrame> send1 = new ArrayList<>();
+        send1.add(new TextFrame().setPayload("f1").setFin(false));
+        send1.add(new ContinuationFrame().setPayload(",f2").setFin(false));
+        send1.add(new PingFrame().setPayload("pong-1"));
+
+        List<WebSocketFrame> expect1 = new ArrayList<>();
+        expect1.add(new PongFrame().setPayload("pong-1"));
+
+        // phase 2
+        List<WebSocketFrame> send2 = new ArrayList<>();
+        send2.add(new ContinuationFrame().setPayload(",f3").setFin(false));
+        send2.add(new ContinuationFrame().setPayload(",f4").setFin(false));
+        send2.add(new PingFrame().setPayload("pong-2"));
+        send2.add(new ContinuationFrame().setPayload(",f5").setFin(true));
+        send2.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        List<WebSocketFrame> expect2 = new ArrayList<>();
+        expect2.add(new PongFrame().setPayload("pong-2"));
+        expect2.add(new TextFrame().setPayload("f1,f2,f3,f4,f5"));
+        expect2.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        try(Fuzzer fuzzer = new Fuzzer(this);StacklessLogging supress = new StacklessLogging(Parser.class))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.BULK);
+
+            // phase 1
+            fuzzer.send(send1);
+            fuzzer.expect(expect1);
+
+            // delay
+            TimeUnit.SECONDS.sleep(1);
+
+            // phase 2
+            fuzzer.send(send2);
+            fuzzer.expect(expect2);
+        }
+    }
+
+    /**
+     * Send pong fragmented in 2 packets
+     */
+    @Test
+    public void testCase5_2() throws Exception
+    {
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new PongFrame().setPayload("hello, ").setFin(false));
+        send.add(new ContinuationFrame().setPayload("world"));
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame());
+
+        try(Fuzzer fuzzer = new Fuzzer(this);StacklessLogging supress = new StacklessLogging(Parser.class))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+        }
+    }
+
+    /**
+     * send text message fragmented in 5 frames, with 2 pings and wait between. (framewise)
+     */
+    @Test
+    public void testCase5_20() throws Exception
+    {
+        List<WebSocketFrame> send1 = new ArrayList<>();
+        send1.add(new TextFrame().setPayload("f1").setFin(false));
+        send1.add(new ContinuationFrame().setPayload(",f2").setFin(false));
+        send1.add(new PingFrame().setPayload("pong-1"));
+
+        List<WebSocketFrame> send2 = new ArrayList<>();
+        send2.add(new ContinuationFrame().setPayload(",f3").setFin(false));
+        send2.add(new ContinuationFrame().setPayload(",f4").setFin(false));
+        send2.add(new PingFrame().setPayload("pong-2"));
+        send2.add(new ContinuationFrame().setPayload(",f5").setFin(true));
+        send2.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        List<WebSocketFrame> expect1 = new ArrayList<>();
+        expect1.add(new PongFrame().setPayload("pong-1"));
+
+        List<WebSocketFrame> expect2 = new ArrayList<>();
+        expect2.add(new PongFrame().setPayload("pong-2"));
+        expect2.add(new TextFrame().setPayload("f1,f2,f3,f4,f5"));
+        expect2.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        try(Fuzzer fuzzer = new Fuzzer(this);StacklessLogging supress = new StacklessLogging(Parser.class))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.PER_FRAME);
+
+            fuzzer.send(send1);
+            fuzzer.expect(expect1);
+
+            TimeUnit.SECONDS.sleep(1);
+
+            fuzzer.send(send2);
+            fuzzer.expect(expect2);
+        }
+    }
+
+    /**
+     * send text message fragmented in 5 frames, with 2 pings and wait between. (framewise)
+     */
+    @Test
+    public void testCase5_20_slow() throws Exception
+    {
+        List<WebSocketFrame> send1 = new ArrayList<>();
+        send1.add(new TextFrame().setPayload("f1").setFin(false));
+        send1.add(new ContinuationFrame().setPayload(",f2").setFin(false));
+        send1.add(new PingFrame().setPayload("pong-1"));
+
+        List<WebSocketFrame> send2 = new ArrayList<>();
+        send2.add(new ContinuationFrame().setPayload(",f3").setFin(false));
+        send2.add(new ContinuationFrame().setPayload(",f4").setFin(false));
+        send2.add(new PingFrame().setPayload("pong-2"));
+        send2.add(new ContinuationFrame().setPayload(",f5").setFin(true));
+        send2.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        List<WebSocketFrame> expect1 = new ArrayList<>();
+        expect1.add(new PongFrame().setPayload("pong-1"));
+
+        List<WebSocketFrame> expect2 = new ArrayList<>();
+        expect2.add(new PongFrame().setPayload("pong-2"));
+        expect2.add(new TextFrame().setPayload("f1,f2,f3,f4,f5"));
+        expect2.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        try(Fuzzer fuzzer = new Fuzzer(this);StacklessLogging supress = new StacklessLogging(Parser.class))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.SLOW);
+            fuzzer.setSlowSendSegmentSize(1);
+
+            fuzzer.send(send1);
+            fuzzer.expect(expect1);
+
+            TimeUnit.SECONDS.sleep(1);
+
+            fuzzer.send(send2);
+            fuzzer.expect(expect2);
+        }
+    }
+
+    /**
+     * Send text fragmented in 2 packets
+     */
+    @Test
+    public void testCase5_3() throws Exception
+    {
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new TextFrame().setPayload("hello, ").setFin(false));
+        send.add(new ContinuationFrame().setPayload("world").setFin(true));
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new TextFrame().setPayload("hello, world"));
+        expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        try(Fuzzer fuzzer = new Fuzzer(this);StacklessLogging supress = new StacklessLogging(Parser.class))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+        }
+    }
+
+    /**
+     * Send text fragmented in 2 packets (framewise)
+     */
+    @Test
+    public void testCase5_4() throws Exception
+    {
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new TextFrame().setPayload("hello, ").setFin(false));
+        send.add(new ContinuationFrame().setPayload("world").setFin(true));
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new TextFrame().setPayload("hello, world"));
+        expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        try(Fuzzer fuzzer = new Fuzzer(this);StacklessLogging supress = new StacklessLogging(Parser.class))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.PER_FRAME);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+        }
+    }
+
+    /**
+     * Send text fragmented in 2 packets (slowly)
+     */
+    @Test
+    public void testCase5_5() throws Exception
+    {
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new TextFrame().setPayload("hello, ").setFin(false));
+        send.add(new ContinuationFrame().setPayload("world").setFin(true));
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new TextFrame().setPayload("hello, world"));
+        expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        try(Fuzzer fuzzer = new Fuzzer(this);StacklessLogging supress = new StacklessLogging(Parser.class))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.SLOW);
+            fuzzer.setSlowSendSegmentSize(1);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+        }
+    }
+
+    /**
+     * Send text fragmented in 2 packets, with ping between them
+     */
+    @Test
+    public void testCase5_6() throws Exception
+    {
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new TextFrame().setPayload("hello, ").setFin(false));
+        send.add(new PingFrame().setPayload("ping"));
+        send.add(new ContinuationFrame().setPayload("world").setFin(true));
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new PongFrame().setPayload("ping"));
+        expect.add(new TextFrame().setPayload("hello, world"));
+        expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        try(Fuzzer fuzzer = new Fuzzer(this);StacklessLogging supress = new StacklessLogging(Parser.class))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+        }
+    }
+
+    /**
+     * Send text fragmented in 2 packets, with ping between them (framewise)
+     */
+    @Test
+    public void testCase5_7() throws Exception
+    {
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new TextFrame().setPayload("hello, ").setFin(false));
+        send.add(new PingFrame().setPayload("ping"));
+        send.add(new ContinuationFrame().setPayload("world").setFin(true));
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new PongFrame().setPayload("ping"));
+        expect.add(new TextFrame().setPayload("hello, world"));
+        expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        try(Fuzzer fuzzer = new Fuzzer(this);StacklessLogging supress = new StacklessLogging(Parser.class))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.PER_FRAME);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+        }
+    }
+
+    /**
+     * Send text fragmented in 2 packets, with ping between them (slowly)
+     */
+    @Test
+    public void testCase5_8() throws Exception
+    {
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new TextFrame().setPayload("hello, ").setFin(false));
+        send.add(new PingFrame().setPayload("ping"));
+        send.add(new ContinuationFrame().setPayload("world").setFin(true));
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new PongFrame().setPayload("ping"));
+        expect.add(new TextFrame().setPayload("hello, world"));
+        expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        try(Fuzzer fuzzer = new Fuzzer(this);StacklessLogging supress = new StacklessLogging(Parser.class))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.SLOW);
+            fuzzer.setSlowSendSegmentSize(1);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+        }
+    }
+
+    /**
+     * Send continuation+fin, then text+fin
+     */
+    @Test
+    public void testCase5_9() throws Exception
+    {
+
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new ContinuationFrame().setPayload("sorry").setFin(true));
+        send.add(new TextFrame().setPayload("hello, world"));
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame());
+
+        try(Fuzzer fuzzer = new Fuzzer(this);StacklessLogging supress = new StacklessLogging(Parser.class))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+        }
+    }
+}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase6.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase6.java
new file mode 100644
index 0000000..5fb6aa0
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase6.java
@@ -0,0 +1,410 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server.ab;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.toolchain.test.AdvancedRunner;
+import org.eclipse.jetty.toolchain.test.annotation.Slow;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.log.StacklessLogging;
+import org.eclipse.jetty.websocket.api.StatusCode;
+import org.eclipse.jetty.websocket.common.CloseInfo;
+import org.eclipse.jetty.websocket.common.Parser;
+import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.frames.ContinuationFrame;
+import org.eclipse.jetty.websocket.common.frames.DataFrame;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
+import org.eclipse.jetty.websocket.common.test.Fuzzer;
+import org.eclipse.jetty.websocket.common.util.Hex;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * UTF-8 Tests
+ */
+ at RunWith(AdvancedRunner.class)
+public class TestABCase6 extends AbstractABCase
+{
+    /**
+     * Split a message byte array into a series of fragments (frames + continuations) of 1 byte message contents each.
+     */
+    protected void fragmentText(List<WebSocketFrame> frames, byte msg[])
+    {
+        int len = msg.length;
+        boolean continuation = false;
+        byte mini[];
+        for (int i = 0; i < len; i++)
+        {
+            DataFrame frame = null;
+            if (continuation)
+            {
+                frame = new ContinuationFrame();
+            }
+            else
+            {
+                frame = new TextFrame();
+            }
+            mini = new byte[1];
+            mini[0] = msg[i];
+            frame.setPayload(ByteBuffer.wrap(mini));
+            boolean isLast = (i >= (len - 1));
+            frame.setFin(isLast);
+            frames.add(frame);
+            continuation = true;
+        }
+    }
+
+    /**
+     * text message, 1 frame, 0 length
+     */
+    @Test
+    public void testCase6_1_1() throws Exception
+    {
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new TextFrame());
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new TextFrame());
+        expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        try (Fuzzer fuzzer = new Fuzzer(this))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+        }
+    }
+
+    /**
+     * text message, 0 length, 3 fragments
+     */
+    @Test
+    public void testCase6_1_2() throws Exception
+    {
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new TextFrame().setFin(false));
+        send.add(new ContinuationFrame().setFin(false));
+        send.add(new ContinuationFrame().setFin(true));
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new TextFrame());
+        expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        try (Fuzzer fuzzer = new Fuzzer(this))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+        }
+    }
+
+    /**
+     * text message, small length, 3 fragments (only middle frame has payload)
+     */
+    @Test
+    public void testCase6_1_3() throws Exception
+    {
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new TextFrame().setFin(false));
+        send.add(new ContinuationFrame().setPayload("middle").setFin(false));
+        send.add(new ContinuationFrame().setFin(true));
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new TextFrame().setPayload("middle"));
+        expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        try (Fuzzer fuzzer = new Fuzzer(this))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+        }
+    }
+
+    /**
+     * valid utf8 text message, 2 fragments (on UTF8 code point boundary)
+     */
+    @Test
+    public void testCase6_2_2() throws Exception
+    {
+        String utf1 = "Hello-\uC2B5@\uC39F\uC3A4";
+        String utf2 = "\uC3BC\uC3A0\uC3A1-UTF-8!!";
+
+        ByteBuffer b1 = ByteBuffer.wrap(StringUtil.getUtf8Bytes(utf1));
+        ByteBuffer b2 = ByteBuffer.wrap(StringUtil.getUtf8Bytes(utf2));
+
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new TextFrame().setPayload(b1).setFin(false));
+        send.add(new ContinuationFrame().setPayload(b2).setFin(true));
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        ByteBuffer e1 = ByteBuffer.allocate(100);
+        e1.put(StringUtil.getUtf8Bytes(utf1));
+        e1.put(StringUtil.getUtf8Bytes(utf2));
+        e1.flip();
+        expect.add(new TextFrame().setPayload(e1));
+        expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        try (Fuzzer fuzzer = new Fuzzer(this))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+        }
+    }
+
+    /**
+     * valid utf8 text message, many fragments (1 byte each)
+     */
+    @Test
+    public void testCase6_2_3() throws Exception
+    {
+        String utf8 = "Hello-\uC2B5@\uC39F\uC3A4\uC3BC\uC3A0\uC3A1-UTF-8!!";
+        byte msg[] = StringUtil.getUtf8Bytes(utf8);
+
+        List<WebSocketFrame> send = new ArrayList<>();
+        fragmentText(send,msg);
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new TextFrame().setPayload(ByteBuffer.wrap(msg)));
+        expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        try (Fuzzer fuzzer = new Fuzzer(this))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+        }
+    }
+
+    /**
+     * valid utf8 text message, many fragments (1 byte each)
+     */
+    @Test
+    public void testCase6_2_4() throws Exception
+    {
+        byte msg[] = Hex.asByteArray("CEBAE1BDB9CF83CEBCCEB5");
+
+        List<WebSocketFrame> send = new ArrayList<>();
+        fragmentText(send,msg);
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new TextFrame().setPayload(ByteBuffer.wrap(msg)));
+        expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        try (Fuzzer fuzzer = new Fuzzer(this))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+        }
+    }
+
+    /**
+     * invalid utf8 text message, many fragments (1 byte each)
+     */
+    @Test
+    public void testCase6_3_2() throws Exception
+    {
+        byte invalid[] = Hex.asByteArray("CEBAE1BDB9CF83CEBCCEB5EDA080656469746564");
+
+        List<WebSocketFrame> send = new ArrayList<>();
+        fragmentText(send,invalid);
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new CloseInfo(StatusCode.BAD_PAYLOAD).asFrame());
+
+        try (Fuzzer fuzzer = new Fuzzer(this))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+        }
+    }
+
+    /**
+     * invalid text message, 3 fragments.
+     * <p>
+     * fragment #1 and fragment #3 are both valid in themselves.
+     * <p>
+     * fragment #2 contains the invalid utf8 code point.
+     */
+    @Test
+    @Slow
+    public void testCase6_4_1() throws Exception
+    {
+        byte part1[] = StringUtil.getUtf8Bytes("\u03BA\u1F79\u03C3\u03BC\u03B5");
+        byte part2[] = Hex.asByteArray("F4908080"); // invalid
+        byte part3[] = StringUtil.getUtf8Bytes("edited");
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new CloseInfo(StatusCode.BAD_PAYLOAD).asFrame());
+
+        try (Fuzzer fuzzer = new Fuzzer(this))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.BULK);
+
+            fuzzer.send(new TextFrame().setPayload(ByteBuffer.wrap(part1)).setFin(false));
+            TimeUnit.SECONDS.sleep(1);
+            fuzzer.send(new ContinuationFrame().setPayload(ByteBuffer.wrap(part2)).setFin(false));
+            TimeUnit.SECONDS.sleep(1);
+            fuzzer.send(new ContinuationFrame().setPayload(ByteBuffer.wrap(part3)).setFin(true));
+
+            fuzzer.expect(expect);
+        }
+    }
+
+    /**
+     * invalid text message, 3 fragments.
+     * <p>
+     * fragment #1 is valid and ends in the middle of an incomplete code point.
+     * <p>
+     * fragment #2 finishes the UTF8 code point but it is invalid
+     * <p>
+     * fragment #3 contains the remainder of the message.
+     */
+    @Test
+    @Slow
+    public void testCase6_4_2() throws Exception
+    {
+        byte part1[] = Hex.asByteArray("CEBAE1BDB9CF83CEBCCEB5F4"); // split code point
+        byte part2[] = Hex.asByteArray("90"); // continue code point & invalid
+        byte part3[] = Hex.asByteArray("8080656469746564"); // continue code point & finish
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new CloseInfo(StatusCode.BAD_PAYLOAD).asFrame());
+
+        try (Fuzzer fuzzer = new Fuzzer(this))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.BULK);
+            fuzzer.send(new TextFrame().setPayload(ByteBuffer.wrap(part1)).setFin(false));
+            TimeUnit.SECONDS.sleep(1);
+            fuzzer.send(new ContinuationFrame().setPayload(ByteBuffer.wrap(part2)).setFin(false));
+            TimeUnit.SECONDS.sleep(1);
+            fuzzer.send(new ContinuationFrame().setPayload(ByteBuffer.wrap(part3)).setFin(true));
+            fuzzer.expect(expect);
+        }
+    }
+
+    /**
+     * invalid text message, 1 frame/fragment (slowly, and split within code points)
+     */
+    @Test
+    @Slow
+    public void testCase6_4_3() throws Exception
+    {
+        // Disable Long Stacks from Parser (we know this test will throw an exception)
+        try (StacklessLogging scope = new StacklessLogging(Parser.class))
+        {
+            ByteBuffer payload = ByteBuffer.allocate(64);
+            BufferUtil.clearToFill(payload);
+            payload.put(TypeUtil.fromHexString("cebae1bdb9cf83cebcceb5")); // good
+            payload.put(TypeUtil.fromHexString("f4908080")); // INVALID
+            payload.put(TypeUtil.fromHexString("656469746564")); // good
+            BufferUtil.flipToFlush(payload,0);
+
+            List<WebSocketFrame> send = new ArrayList<>();
+            send.add(new TextFrame().setPayload(payload));
+            send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+            List<WebSocketFrame> expect = new ArrayList<>();
+            expect.add(new CloseInfo(StatusCode.BAD_PAYLOAD).asFrame());
+
+            try (Fuzzer fuzzer = new Fuzzer(this))
+            {
+                fuzzer.connect();
+
+                ByteBuffer net = fuzzer.asNetworkBuffer(send);
+
+                int splits[] = { 17, 21, net.limit() };
+
+                ByteBuffer part1 = net.slice(); // Header + good UTF
+                part1.limit(splits[0]);
+                ByteBuffer part2 = net.slice(); // invalid UTF
+                part2.position(splits[0]);
+                part2.limit(splits[1]);
+                ByteBuffer part3 = net.slice(); // good UTF
+                part3.position(splits[1]);
+                part3.limit(splits[2]);
+
+                fuzzer.send(part1); // the header + good utf
+                TimeUnit.MILLISECONDS.sleep(500);
+                fuzzer.send(part2); // the bad UTF
+                TimeUnit.MILLISECONDS.sleep(500);
+                fuzzer.send(part3); // the rest (shouldn't work)
+
+                fuzzer.expect(expect);
+            }
+        }
+    }
+
+    /**
+     * invalid text message, 1 frame/fragment (slowly, and split within code points)
+     */
+    @Test
+    @Slow
+    public void testCase6_4_4() throws Exception
+    {
+        byte invalid[] = Hex.asByteArray("CEBAE1BDB9CF83CEBCCEB5F49080808080656469746564");
+
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new TextFrame().setPayload(ByteBuffer.wrap(invalid)));
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new CloseInfo(StatusCode.BAD_PAYLOAD).asFrame());
+
+        try (Fuzzer fuzzer = new Fuzzer(this); StacklessLogging scope = new StacklessLogging(Parser.class))
+        {
+            fuzzer.connect();
+
+            ByteBuffer net = fuzzer.asNetworkBuffer(send);
+            fuzzer.send(net,6);
+            fuzzer.send(net,11);
+            TimeUnit.SECONDS.sleep(1);
+            fuzzer.send(net,1);
+            TimeUnit.SECONDS.sleep(1);
+            fuzzer.send(net,100); // the rest
+
+            fuzzer.expect(expect);
+        }
+    }
+}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase6_BadUTF.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase6_BadUTF.java
new file mode 100644
index 0000000..56531d0
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase6_BadUTF.java
@@ -0,0 +1,177 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server.ab;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.log.StacklessLogging;
+import org.eclipse.jetty.websocket.api.StatusCode;
+import org.eclipse.jetty.websocket.common.CloseInfo;
+import org.eclipse.jetty.websocket.common.Parser;
+import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
+import org.eclipse.jetty.websocket.common.test.Fuzzer;
+import org.eclipse.jetty.websocket.common.util.Hex;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Tests of Known Bad UTF8 sequences that should trigger a {@link StatusCode#BAD_PAYLOAD} close and early connection termination
+ */
+ at RunWith(Parameterized.class)
+public class TestABCase6_BadUTF extends AbstractABCase
+{
+    private static final Logger LOG = Log.getLogger(TestABCase6_BadUTF.class);
+
+    @Parameters
+    public static Collection<String[]> data()
+    {
+        // The various Good UTF8 sequences as a String (hex form)
+        List<String[]> data = new ArrayList<>();
+
+        // @formatter:off
+        // - differently unicode fragmented
+        data.add(new String[]{ "6.3.1", "CEBAE1BDB9CF83CEBCCEB5EDA080656469746564" });
+        // - partial/incomplete multi-byte code points
+        data.add(new String[]{ "6.6.1", "CE" });
+        data.add(new String[]{ "6.6.3", "CEBAE1" });
+        data.add(new String[]{ "6.6.4", "CEBAE1BD" });
+        data.add(new String[]{ "6.6.6", "CEBAE1BDB9CF" });
+        data.add(new String[]{ "6.6.8", "CEBAE1BDB9CF83CE" });
+        data.add(new String[]{ "6.6.10", "CEBAE1BDB9CF83CEBCCE" });
+        // - first possible sequence length 5/6 (invalid code points)
+        data.add(new String[]{ "6.8.1", "F888808080" });
+        data.add(new String[]{ "6.8.2", "FC8480808080" });
+        // - last possible sequence length (invalid code points)
+        data.add(new String[]{ "6.10.1", "F7BFBFBF" });
+        data.add(new String[]{ "6.10.2", "FBBFBFBFBF" });
+        data.add(new String[]{ "6.10.3", "FDBFBFBFBFBF" });
+        // - other boundary conditions
+        data.add(new String[]{ "6.11.5", "F4908080" });
+        // - unexpected continuation bytes
+        data.add(new String[]{ "6.12.1", "80" });
+        data.add(new String[]{ "6.12.2", "BF" });
+        data.add(new String[]{ "6.12.3", "80BF" });
+        data.add(new String[]{ "6.12.4", "80BF80" });
+        data.add(new String[]{ "6.12.5", "80BF80BF" });
+        data.add(new String[]{ "6.12.6", "80BF80BF80" });
+        data.add(new String[]{ "6.12.7", "80BF80BF80BF" });
+        data.add(new String[]{ "6.12.8", "808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9"
+                + "FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBE" });
+        // - lonely start characters
+        data.add(new String[]{ "6.13.1", "C020C120C220C320C420C520C620C720C820C920CA20CB20CC20CD20CE20CF2"
+                + "0D020D120D220D320D420D520D620D720D820D920DA20DB20DC20DD20DE20" });
+        data.add(new String[]{ "6.13.2", "E020E120E220E320E420E520E620E720E820E920EA20EB20EC20ED20EE20" });
+        data.add(new String[]{ "6.13.3", "F020F120F220F320F420F520F620" });
+        data.add(new String[]{ "6.13.4", "F820F920FA20" });
+        data.add(new String[]{ "6.13.5", "FC20" });
+        // - sequences with last continuation byte missing
+        data.add(new String[]{ "6.14.1", "C0" });
+        data.add(new String[]{ "6.14.2", "E080" });
+        data.add(new String[]{ "6.14.3", "F08080" });
+        data.add(new String[]{ "6.14.4", "F8808080" });
+        data.add(new String[]{ "6.14.5", "FC80808080" });
+        data.add(new String[]{ "6.14.6", "DF" });
+        data.add(new String[]{ "6.14.7", "EFBF" });
+        data.add(new String[]{ "6.14.8", "F7BFBF" });
+        data.add(new String[]{ "6.14.9", "FBBFBFBF" });
+        data.add(new String[]{ "6.14.10", "FDBFBFBFBF" });
+        // - concatenation of incomplete sequences
+        data.add(new String[]{ "6.15.1", "C0E080F08080F8808080FC80808080DFEFBFF7BFBFFBBFBFBFFDBFBFBFBF" });
+        // - impossible bytes
+        data.add(new String[]{ "6.16.1", "FE" });
+        data.add(new String[]{ "6.16.2", "FF" });
+        data.add(new String[]{ "6.16.3", "FEFEFFFF" });
+        // - overlong ascii characters
+        data.add(new String[]{ "6.17.1", "C0AF" });
+        data.add(new String[]{ "6.17.2", "E080AF" });
+        data.add(new String[]{ "6.17.3", "F08080AF" });
+        data.add(new String[]{ "6.17.4", "F8808080AF" });
+        data.add(new String[]{ "6.17.5", "FC80808080AF" });
+        // - maximum overlong sequences
+        data.add(new String[]{ "6.18.1", "C1BF" });
+        data.add(new String[]{ "6.18.2", "E09FBF" });
+        data.add(new String[]{ "6.18.3", "F08FBFBF" });
+        data.add(new String[]{ "6.18.4", "F887BFBFBF" });
+        data.add(new String[]{ "6.18.5", "FC83BFBFBFBF" });
+        // - overlong representation of NUL character
+        data.add(new String[]{ "6.19.1", "C080" });
+        data.add(new String[]{ "6.19.2", "E08080" });
+        data.add(new String[]{ "6.19.3", "F0808080" });
+        data.add(new String[]{ "6.19.4", "F880808080" });
+        data.add(new String[]{ "6.19.5", "FC8080808080" });
+        // - single UTF-16 surrogates
+        data.add(new String[]{ "6.20.1", "EDA080" });
+        data.add(new String[]{ "6.20.2", "EDADBF" });
+        data.add(new String[]{ "6.20.3", "EDAE80" });
+        data.add(new String[]{ "6.20.4", "EDAFBF" });
+        data.add(new String[]{ "6.20.5", "EDB080" });
+        data.add(new String[]{ "6.20.6", "EDBE80" });
+        data.add(new String[]{ "6.20.7", "EDBFBF" });
+        // - paired UTF-16 surrogates
+        data.add(new String[]{ "6.21.1", "EDA080EDB080" });
+        data.add(new String[]{ "6.21.2", "EDA080EDBFBF" });
+        data.add(new String[]{ "6.21.3", "EDADBFEDB080" });
+        data.add(new String[]{ "6.21.4", "EDADBFEDBFBF" });
+        data.add(new String[]{ "6.21.5", "EDAE80EDB080" });
+        data.add(new String[]{ "6.21.6", "EDAE80EDBFBF" });
+        data.add(new String[]{ "6.21.7", "EDAFBFEDB080" });
+        data.add(new String[]{ "6.21.8", "EDAFBFEDBFBF" });
+        // @formatter:on
+
+        return data;
+    }
+
+    private final byte[] invalid;
+
+    public TestABCase6_BadUTF(String testId, String hexMsg)
+    {
+        LOG.debug("Test ID: {}",testId);
+        this.invalid = Hex.asByteArray(hexMsg);
+    }
+
+    @Test
+    public void assertBadTextPayload() throws Exception
+    {
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new TextFrame().setPayload(ByteBuffer.wrap(invalid)));
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new CloseInfo(StatusCode.BAD_PAYLOAD).asFrame());
+
+        try (Fuzzer fuzzer = new Fuzzer(this))
+        {
+            try (StacklessLogging supress = new StacklessLogging(Parser.class))
+            {
+                fuzzer.connect();
+                fuzzer.setSendMode(Fuzzer.SendMode.BULK);
+                fuzzer.send(send);
+                fuzzer.expect(expect);
+            }
+        }
+    }
+}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase6_GoodUTF.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase6_GoodUTF.java
new file mode 100644
index 0000000..18c4722
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase6_GoodUTF.java
@@ -0,0 +1,150 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server.ab;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.StatusCode;
+import org.eclipse.jetty.websocket.common.CloseInfo;
+import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
+import org.eclipse.jetty.websocket.common.test.Fuzzer;
+import org.eclipse.jetty.websocket.common.util.Hex;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Tests of Known Good UTF8 sequences.
+ * <p>
+ * Should be preserved / echoed back, with normal close code.
+ */
+ at RunWith(Parameterized.class)
+public class TestABCase6_GoodUTF extends AbstractABCase
+{
+    private static final Logger LOG = Log.getLogger(TestABCase6_GoodUTF.class);
+
+    @Parameters
+    public static Collection<String[]> data()
+    {
+        // The various Good UTF8 sequences as a String (hex form)
+        List<String[]> data = new ArrayList<>();
+
+        // @formatter:off
+        // - combination of simple 1 byte characters and unicode code points
+        data.add(new String[]{ "6.2.1", "48656C6C6F2DC2B540C39FC3B6C3A4C3BCC3A0C3A12D5554462D382121" });
+        // - simple valid UTF8 sequence
+        data.add(new String[]{ "6.5.1", "CEBAE1BDB9CF83CEBCCEB5" });
+        // - multi-byte code points
+        data.add(new String[]{ "6.6.11", "CEBAE1BDB9CF83CEBCCEB5" });
+        data.add(new String[]{ "6.6.2", "CEBA" });
+        data.add(new String[]{ "6.6.5", "CEBAE1BDB9" });
+        data.add(new String[]{ "6.6.7", "CEBAE1BDB9CF83" });
+        data.add(new String[]{ "6.6.9", "CEBAE1BDB9CF83CEBC" });
+        // - first possible sequence of a certain length (1 code point)
+        data.add(new String[]{ "6.7.1", "00" });
+        data.add(new String[]{ "6.7.2", "C280" });
+        data.add(new String[]{ "6.7.3", "E0A080" });
+        data.add(new String[]{ "6.7.4", "F0908080" });
+        // - last possible sequence of a certain length (1 code point)
+        data.add(new String[]{ "6.9.1", "7F" });
+        data.add(new String[]{ "6.9.2", "DFBF" });
+        data.add(new String[]{ "6.9.3", "EFBFBF" });
+        data.add(new String[]{ "6.9.4", "F48FBFBF" });
+        // - other boundary conditions
+        data.add(new String[]{ "6.11.1", "ED9FBF" });
+        data.add(new String[]{ "6.11.2", "EE8080" });
+        data.add(new String[]{ "6.11.3", "EFBFBD" });
+        data.add(new String[]{ "6.11.4", "F48FBFBF" });
+        // - non character code points
+        data.add(new String[]{ "6.22.1", "EFBFBE" });
+        data.add(new String[]{ "6.22.2", "EFBFBF" });
+        data.add(new String[]{ "6.22.3", "F09FBFBE" });
+        data.add(new String[]{ "6.22.4", "F09FBFBF" });
+        data.add(new String[]{ "6.22.5", "F0AFBFBE" });
+        data.add(new String[]{ "6.22.6", "F0AFBFBF" });
+        data.add(new String[]{ "6.22.7", "F0BFBFBE" });
+        data.add(new String[]{ "6.22.8", "F0BFBFBF" });
+        data.add(new String[]{ "6.22.9", "F18FBFBE" });
+        data.add(new String[]{ "6.22.10", "F18FBFBF" });
+        data.add(new String[]{ "6.22.11", "F19FBFBE" });
+        data.add(new String[]{ "6.22.12", "F19FBFBF" });
+        data.add(new String[]{ "6.22.13", "F1AFBFBE" });
+        data.add(new String[]{ "6.22.14", "F1AFBFBF" });
+        data.add(new String[]{ "6.22.15", "F1BFBFBE" });
+        data.add(new String[]{ "6.22.16", "F1BFBFBF" });
+        data.add(new String[]{ "6.22.17", "F28FBFBE" });
+        data.add(new String[]{ "6.22.18", "F28FBFBF" });
+        data.add(new String[]{ "6.22.19", "F29FBFBE" });
+        data.add(new String[]{ "6.22.20", "F29FBFBF" });
+        data.add(new String[]{ "6.22.21", "F2AFBFBE" });
+        data.add(new String[]{ "6.22.22", "F2AFBFBF" });
+        data.add(new String[]{ "6.22.23", "F2BFBFBE" });
+        data.add(new String[]{ "6.22.24", "F2BFBFBF" });
+        data.add(new String[]{ "6.22.25", "F38FBFBE" });
+        data.add(new String[]{ "6.22.26", "F38FBFBF" });
+        data.add(new String[]{ "6.22.27", "F39FBFBE" });
+        data.add(new String[]{ "6.22.28", "F39FBFBF" });
+        data.add(new String[]{ "6.22.29", "F3AFBFBE" });
+        data.add(new String[]{ "6.22.30", "F3AFBFBF" });
+        data.add(new String[]{ "6.22.31", "F3BFBFBE" });
+        data.add(new String[]{ "6.22.32", "F3BFBFBF" });
+        data.add(new String[]{ "6.22.33", "F48FBFBE" });
+        data.add(new String[]{ "6.22.34", "F48FBFBF" });
+        // - unicode replacement character
+        data.add(new String[]{ "6.23.1", "EFBFBD" });
+        // @formatter:on
+
+        return data;
+    }
+
+    private final ByteBuffer msg;
+
+    public TestABCase6_GoodUTF(String testId, String hexMsg)
+    {
+        LOG.debug("Test ID: {}",testId);
+        this.msg = Hex.asByteBuffer(hexMsg);
+    }
+
+    @Test
+    public void assertEchoTextMessage() throws Exception
+    {
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new TextFrame().setPayload(msg));
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new TextFrame().setPayload(clone(msg)));
+        expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        try(Fuzzer fuzzer = new Fuzzer(this))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+        }
+    }
+}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase7.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase7.java
new file mode 100644
index 0000000..b485e02
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase7.java
@@ -0,0 +1,346 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server.ab;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.eclipse.jetty.toolchain.test.TestTracker;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.log.StacklessLogging;
+import org.eclipse.jetty.websocket.api.StatusCode;
+import org.eclipse.jetty.websocket.common.CloseInfo;
+import org.eclipse.jetty.websocket.common.OpCode;
+import org.eclipse.jetty.websocket.common.Parser;
+import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.frames.CloseFrame;
+import org.eclipse.jetty.websocket.common.frames.ContinuationFrame;
+import org.eclipse.jetty.websocket.common.frames.PingFrame;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
+import org.eclipse.jetty.websocket.common.io.AbstractWebSocketConnection;
+import org.eclipse.jetty.websocket.common.test.Fuzzer;
+import org.eclipse.jetty.websocket.common.util.Hex;
+import org.junit.Rule;
+import org.junit.Test;
+
+/**
+ * Test of Close Handling
+ */
+public class TestABCase7 extends AbstractABCase
+{
+    @Rule
+    public TestTracker tt = new TestTracker();
+
+    /**
+     * Basic message then close frame, normal behavior
+     */
+    @Test
+    public void testCase7_1_1() throws Exception
+    {
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new TextFrame().setPayload("Hello World"));
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new TextFrame().setPayload("Hello World"));
+        expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        try(Fuzzer fuzzer = new Fuzzer(this))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+        }
+    }
+
+    /**
+     * Close frame, then another close frame (send frame ignored)
+     */
+    @Test
+    public void testCase7_1_2() throws Exception
+    {
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+        send.add(new CloseInfo().asFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        try(Fuzzer fuzzer = new Fuzzer(this))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+            fuzzer.expectNoMoreFrames();
+        }
+    }
+
+    /**
+     * Close frame, then ping frame (no pong received)
+     */
+    @Test
+    public void testCase7_1_3() throws Exception
+    {
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+        send.add(new PingFrame().setPayload("out of band ping"));
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        try(Fuzzer fuzzer = new Fuzzer(this))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+            fuzzer.expectNoMoreFrames();
+        }
+    }
+
+    /**
+     * Close frame, then ping frame (no pong received)
+     */
+    @Test
+    public void testCase7_1_4() throws Exception
+    {
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+        send.add(new TextFrame().setPayload("out of band text"));
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        try(Fuzzer fuzzer = new Fuzzer(this))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+            fuzzer.expectNoMoreFrames();
+        }
+    }
+
+    /**
+     * Text fin=false, close, then continuation fin=true
+     */
+    @Test
+    public void testCase7_1_5() throws Exception
+    {
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new TextFrame().setPayload("an").setFin(false));
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+        send.add(new ContinuationFrame().setPayload("ticipation").setFin(true));
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        try(Fuzzer fuzzer = new Fuzzer(this))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+            fuzzer.expectNoMoreFrames();
+        }
+    }
+
+    /**
+     * 256k msg, then close, then ping
+     */
+    @Test
+    public void testCase7_1_6() throws Exception
+    {
+        byte msg[] = new byte[256 * 1024];
+        Arrays.fill(msg,(byte)'*');
+        ByteBuffer buf = ByteBuffer.wrap(msg);
+
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new TextFrame().setPayload(buf));
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+        send.add(new PingFrame().setPayload("out of band"));
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new TextFrame().setPayload(clone(buf)));
+        expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        try(Fuzzer fuzzer = new Fuzzer(this))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+            fuzzer.expectNoMoreFrames();
+        }
+    }
+
+    /**
+     * close with no payload (payload length 0)
+     */
+    @Test
+    public void testCase7_3_1() throws Exception
+    {
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new CloseFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new CloseFrame());
+
+        try(Fuzzer fuzzer = new Fuzzer(this))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+            fuzzer.expectNoMoreFrames();
+        }
+    }
+
+    /**
+     * close with invalid payload (payload length 1)
+     */
+    @Test
+    public void testCase7_3_2() throws Exception
+    {
+        byte payload[] = new byte[] { 0x00 };
+        ByteBuffer buf = ByteBuffer.wrap(payload);
+
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new CloseFrame().setPayload(buf));
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame());
+
+        try (Fuzzer fuzzer = new Fuzzer(this); StacklessLogging scope = new StacklessLogging(Parser.class))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+            fuzzer.expectNoMoreFrames();
+        }
+    }
+
+    /**
+     * close with valid payload (payload length 2)
+     */
+    @Test
+    public void testCase7_3_3() throws Exception
+    {
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        try(Fuzzer fuzzer = new Fuzzer(this))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+            fuzzer.expectNoMoreFrames();
+        }
+    }
+
+    /**
+     * close with valid payload (with reason)
+     */
+    @Test
+    public void testCase7_3_4() throws Exception
+    {
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new CloseInfo(StatusCode.NORMAL,"Hic").asFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new CloseInfo(StatusCode.NORMAL,"Hic").asFrame());
+
+        try(Fuzzer fuzzer = new Fuzzer(this))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+            fuzzer.expectNoMoreFrames();
+        }
+    }
+
+    /**
+     * close with valid payload (with 123 byte reason)
+     */
+    @Test
+    public void testCase7_3_5() throws Exception
+    {
+        byte utf[] = new byte[123];
+        Arrays.fill(utf,(byte)'!');
+        String reason = StringUtil.toUTF8String(utf,0,utf.length);
+
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new CloseInfo(StatusCode.NORMAL,reason).asFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new CloseInfo(StatusCode.NORMAL,reason).asFrame());
+
+        try (Fuzzer fuzzer = new Fuzzer(this); StacklessLogging logging = new StacklessLogging(AbstractWebSocketConnection.class))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+            fuzzer.expectNoMoreFrames();
+        }
+    }
+
+    /**
+     * close with invalid UTF8 in payload
+     */
+    @Test
+    public void testCase7_5_1() throws Exception
+    {
+        ByteBuffer payload = ByteBuffer.allocate(256);
+        BufferUtil.clearToFill(payload);
+        payload.put((byte)0x03); // normal close
+        payload.put((byte)0xE8);
+        byte invalidUtf[] = Hex.asByteArray("CEBAE1BDB9CF83CEBCCEB5EDA080656469746564");
+        payload.put(invalidUtf);
+        BufferUtil.flipToFlush(payload,0);
+
+        List<WebSocketFrame> send = new ArrayList<>();
+        WebSocketFrame close = new BadFrame(OpCode.CLOSE);
+        close.setPayload(payload); // intentionally bad payload
+        send.add(close);
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new CloseInfo(StatusCode.BAD_PAYLOAD).asFrame());
+
+        try (Fuzzer fuzzer = new Fuzzer(this); StacklessLogging scope = new StacklessLogging(Parser.class,CloseInfo.class))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+            fuzzer.expectNoMoreFrames();
+        }
+    }
+}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase7_BadStatusCodes.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase7_BadStatusCodes.java
new file mode 100644
index 0000000..b3b6b20
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase7_BadStatusCodes.java
@@ -0,0 +1,138 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server.ab;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.StatusCode;
+import org.eclipse.jetty.websocket.common.CloseInfo;
+import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.frames.CloseFrame;
+import org.eclipse.jetty.websocket.common.test.Fuzzer;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Test Bad Close Status Codes
+ */
+ at RunWith(value = Parameterized.class)
+public class TestABCase7_BadStatusCodes extends AbstractABCase
+{
+    private static final Logger LOG = Log.getLogger(TestABCase7_GoodStatusCodes.class);
+
+    @Parameters
+    public static Collection<Object[]> data()
+    {
+        // The various Good UTF8 sequences as a String (hex form)
+        List<Object[]> data = new ArrayList<>();
+
+        // @formatter:off
+        data.add(new Object[] { "7.9.1", 0 });
+        data.add(new Object[] { "7.9.2", 999 });
+        data.add(new Object[] { "7.9.3", 1004 });
+        data.add(new Object[] { "7.9.4", 1005 });
+        data.add(new Object[] { "7.9.5", 1006 });
+        data.add(new Object[] { "7.9.6", 1012 });
+        data.add(new Object[] { "7.9.7", 1013 });
+        data.add(new Object[] { "7.9.8", 1014 });
+        data.add(new Object[] { "7.9.9", 1015 });
+        data.add(new Object[] { "7.9.10", 1016 });
+        data.add(new Object[] { "7.9.11", 1100 });
+        data.add(new Object[] { "7.9.12", 2000 });
+        data.add(new Object[] { "7.9.13", 2999 });
+        // -- close status codes, with undefined events in spec 
+        data.add(new Object[] { "7.13.1", 5000 });
+        data.add(new Object[] { "7.13.2", 65536 });
+        // @formatter:on
+
+        return data;
+    }
+
+    private final int statusCode;
+
+    public TestABCase7_BadStatusCodes(String testId, int statusCode)
+    {
+        LOG.debug("Test ID: {}",testId);
+        this.statusCode = statusCode;
+    }
+
+    /**
+     * just the close code, no reason
+     */
+    @Test
+    public void testBadStatusCode() throws Exception
+    {
+        ByteBuffer payload = ByteBuffer.allocate(256);
+        BufferUtil.clearToFill(payload);
+        payload.putChar((char)statusCode);
+        BufferUtil.flipToFlush(payload,0);
+
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new CloseFrame().setPayload(payload.slice()));
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame());
+
+        try(Fuzzer fuzzer = new Fuzzer(this))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+            fuzzer.expectNoMoreFrames();
+        }
+    }
+
+    /**
+     * the bad close code, with reason
+     */
+    @Test
+    public void testBadStatusCodeWithReason() throws Exception
+    {
+        ByteBuffer payload = ByteBuffer.allocate(256);
+        BufferUtil.clearToFill(payload);
+        payload.putChar((char)statusCode);
+        payload.put(StringUtil.getBytes("Reason"));
+        BufferUtil.flipToFlush(payload,0);
+
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new CloseFrame().setPayload(payload.slice()));
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame());
+
+        try(Fuzzer fuzzer = new Fuzzer(this))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+            fuzzer.expectNoMoreFrames();
+        }
+    }
+}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase7_GoodStatusCodes.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase7_GoodStatusCodes.java
new file mode 100644
index 0000000..d8939c0
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase7_GoodStatusCodes.java
@@ -0,0 +1,132 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server.ab;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.frames.CloseFrame;
+import org.eclipse.jetty.websocket.common.test.Fuzzer;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Test Good Close Status Codes
+ */
+ at RunWith(value = Parameterized.class)
+public class TestABCase7_GoodStatusCodes extends AbstractABCase
+{
+    private static final Logger LOG = Log.getLogger(TestABCase7_GoodStatusCodes.class);
+
+    @Parameters
+    public static Collection<Object[]> data()
+    {
+        // The various Good UTF8 sequences as a String (hex form)
+        List<Object[]> data = new ArrayList<>();
+
+        // @formatter:off
+        data.add(new Object[] { "7.7.1", 1000 });
+        data.add(new Object[] { "7.7.2", 1001 });
+        data.add(new Object[] { "7.7.3", 1002 });
+        data.add(new Object[] { "7.7.4", 1003 });
+        data.add(new Object[] { "7.7.5", 1007 });
+        data.add(new Object[] { "7.7.6", 1008 });
+        data.add(new Object[] { "7.7.7", 1009 });
+        data.add(new Object[] { "7.7.8", 1010 });
+        data.add(new Object[] { "7.7.9", 1011 });
+        data.add(new Object[] { "7.7.10", 3000 });
+        data.add(new Object[] { "7.7.11", 3999 });
+        data.add(new Object[] { "7.7.12", 4000 });
+        data.add(new Object[] { "7.7.13", 4999 });
+        // @formatter:on
+
+        return data;
+    }
+
+    private final int statusCode;
+
+    public TestABCase7_GoodStatusCodes(String testId, int statusCode)
+    {
+        LOG.debug("Test ID: {}",testId);
+        this.statusCode = statusCode;
+    }
+
+    /**
+     * just the close code, no reason
+     */
+    @Test
+    public void testStatusCode() throws Exception
+    {
+        ByteBuffer payload = ByteBuffer.allocate(256);
+        BufferUtil.clearToFill(payload);
+        payload.putChar((char)statusCode);
+        BufferUtil.flipToFlush(payload,0);
+
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new CloseFrame().setPayload(payload.slice()));
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new CloseFrame().setPayload(clone(payload)));
+
+        try(Fuzzer fuzzer = new Fuzzer(this))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+            fuzzer.expectNoMoreFrames();
+        }
+    }
+
+    /**
+     * the good close code, with reason
+     */
+    @Test
+    public void testStatusCodeWithReason() throws Exception
+    {
+        ByteBuffer payload = ByteBuffer.allocate(256);
+        payload.putChar((char)statusCode);
+        payload.put(StringUtil.getBytes("Reason"));
+        payload.flip();
+
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new CloseFrame().setPayload(payload.slice()));
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new CloseFrame().setPayload(clone(payload)));
+
+        try(Fuzzer fuzzer = new Fuzzer(this))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+            fuzzer.expectNoMoreFrames();
+        }
+    }
+}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase9.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase9.java
new file mode 100644
index 0000000..ebe6a8c
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase9.java
@@ -0,0 +1,760 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server.ab;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.toolchain.test.AdvancedRunner;
+import org.eclipse.jetty.toolchain.test.annotation.Stress;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.websocket.api.StatusCode;
+import org.eclipse.jetty.websocket.common.CloseInfo;
+import org.eclipse.jetty.websocket.common.OpCode;
+import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.frames.BinaryFrame;
+import org.eclipse.jetty.websocket.common.frames.ContinuationFrame;
+import org.eclipse.jetty.websocket.common.frames.DataFrame;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
+import org.eclipse.jetty.websocket.common.test.Fuzzer;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Big frame/message tests
+ */
+ at RunWith(AdvancedRunner.class)
+public class TestABCase9 extends AbstractABCase
+{
+    private static final int KBYTE = 1024;
+    private static final int MBYTE = KBYTE * KBYTE;
+
+    private DataFrame toDataFrame(byte op)
+    {
+        switch (op)
+        {
+            case OpCode.BINARY:
+                return new BinaryFrame();
+            case OpCode.TEXT:
+                return new TextFrame();
+            case OpCode.CONTINUATION:
+                return new ContinuationFrame();
+            default:
+                throw new IllegalArgumentException("Not a data frame: " + op);
+        }
+    }
+
+    private void assertMultiFrameEcho(byte opcode, int overallMsgSize, int fragmentSize) throws Exception
+    {
+        byte msg[] = new byte[overallMsgSize];
+        Arrays.fill(msg,(byte)'M');
+
+        List<WebSocketFrame> send = new ArrayList<>();
+        byte frag[];
+        int remaining = msg.length;
+        int offset = 0;
+        boolean fin;
+        ByteBuffer buf;
+        ;
+        byte op = opcode;
+        while (remaining > 0)
+        {
+            int len = Math.min(remaining,fragmentSize);
+            frag = new byte[len];
+            System.arraycopy(msg,offset,frag,0,len);
+            remaining -= len;
+            fin = (remaining <= 0);
+            buf = ByteBuffer.wrap(frag);
+
+            send.add(toDataFrame(op).setPayload(buf).setFin(fin));
+
+            offset += len;
+            op = OpCode.CONTINUATION;
+        }
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(toDataFrame(opcode).setPayload(copyOf(msg)));
+        expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        try(Fuzzer fuzzer = new Fuzzer(this))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect,8,TimeUnit.SECONDS);
+        }
+    }
+
+    private void assertSlowFrameEcho(byte opcode, int overallMsgSize, int segmentSize) throws Exception
+    {
+        byte msg[] = new byte[overallMsgSize];
+        Arrays.fill(msg,(byte)'M');
+        ByteBuffer buf = ByteBuffer.wrap(msg);
+
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(toDataFrame(opcode).setPayload(buf));
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(toDataFrame(opcode).setPayload(clone(buf)));
+        expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        try(Fuzzer fuzzer = new Fuzzer(this))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.SLOW);
+            fuzzer.setSlowSendSegmentSize(segmentSize);
+            fuzzer.send(send);
+            fuzzer.expect(expect,8,TimeUnit.SECONDS);
+        }
+    }
+
+    /**
+     * Echo 64KB text message (1 frame)
+     */
+    @Test
+    public void testCase9_1_1() throws Exception
+    {
+        byte utf[] = new byte[64 * KBYTE];
+        Arrays.fill(utf,(byte)'y');
+        String msg = StringUtil.toUTF8String(utf,0,utf.length);
+
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new TextFrame().setPayload(msg));
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new TextFrame().setPayload(msg));
+        expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        try(Fuzzer fuzzer = new Fuzzer(this))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+        }
+    }
+
+    /**
+     * Echo 256KB text message (1 frame)
+     */
+    @Test
+    public void testCase9_1_2() throws Exception
+    {
+        byte utf[] = new byte[256 * KBYTE];
+        Arrays.fill(utf,(byte)'y');
+        ByteBuffer buf = ByteBuffer.wrap(utf);
+
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new TextFrame().setPayload(buf));
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new TextFrame().setPayload(clone(buf)));
+        expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        try(Fuzzer fuzzer = new Fuzzer(this))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+        }
+    }
+
+    /**
+     * Echo 1MB text message (1 frame)
+     */
+    @Test
+    public void testCase9_1_3() throws Exception
+    {
+        byte utf[] = new byte[1 * MBYTE];
+        Arrays.fill(utf,(byte)'y');
+        ByteBuffer buf = ByteBuffer.wrap(utf);
+
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new TextFrame().setPayload(buf));
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new TextFrame().setPayload(clone(buf)));
+        expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        try(Fuzzer fuzzer = new Fuzzer(this))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect,4,TimeUnit.SECONDS);
+        }
+    }
+
+    /**
+     * Echo 4MB text message (1 frame)
+     */
+    @Test
+    public void testCase9_1_4() throws Exception
+    {
+        byte utf[] = new byte[4 * MBYTE];
+        Arrays.fill(utf,(byte)'y');
+        ByteBuffer buf = ByteBuffer.wrap(utf);
+
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new TextFrame().setPayload(buf));
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new TextFrame().setPayload(clone(buf)));
+        expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        try(Fuzzer fuzzer = new Fuzzer(this))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect,8,TimeUnit.SECONDS);
+        }
+    }
+
+    /**
+     * Echo 8MB text message (1 frame)
+     */
+    @Test
+    @Stress("High I/O use")
+    public void testCase9_1_5() throws Exception
+    {
+        byte utf[] = new byte[8 * MBYTE];
+        Arrays.fill(utf,(byte)'y');
+        ByteBuffer buf = ByteBuffer.wrap(utf);
+
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new TextFrame().setPayload(buf));
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new TextFrame().setPayload(clone(buf)));
+        expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        try(Fuzzer fuzzer = new Fuzzer(this))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect,16,TimeUnit.SECONDS);
+        }
+    }
+
+    /**
+     * Echo 16MB text message (1 frame)
+     */
+    @Test
+    @Stress("High I/O use")
+    public void testCase9_1_6() throws Exception
+    {
+        byte utf[] = new byte[16 * MBYTE];
+        Arrays.fill(utf,(byte)'y');
+        ByteBuffer buf = ByteBuffer.wrap(utf);
+
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new TextFrame().setPayload(buf));
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new TextFrame().setPayload(clone(buf)));
+        expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        try(Fuzzer fuzzer = new Fuzzer(this))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect,32,TimeUnit.SECONDS);
+        }
+    }
+
+    /**
+     * Echo 64KB binary message (1 frame)
+     */
+    @Test
+    public void testCase9_2_1() throws Exception
+    {
+        byte data[] = new byte[64 * KBYTE];
+        Arrays.fill(data,(byte)0x21);
+
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new BinaryFrame().setPayload(data));
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new BinaryFrame().setPayload(copyOf(data)));
+        expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        try(Fuzzer fuzzer = new Fuzzer(this))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+        }
+    }
+
+    /**
+     * Echo 256KB binary message (1 frame)
+     */
+    @Test
+    public void testCase9_2_2() throws Exception
+    {
+        byte data[] = new byte[256 * KBYTE];
+        Arrays.fill(data,(byte)0x22);
+        ByteBuffer buf = ByteBuffer.wrap(data);
+
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new BinaryFrame().setPayload(buf));
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new BinaryFrame().setPayload(clone(buf)));
+        expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        try(Fuzzer fuzzer = new Fuzzer(this))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect);
+        }
+    }
+
+    /**
+     * Echo 1MB binary message (1 frame)
+     */
+    @Test
+    @Stress("High I/O use")
+    public void testCase9_2_3() throws Exception
+    {
+        byte data[] = new byte[1 * MBYTE];
+        Arrays.fill(data,(byte)0x23);
+        ByteBuffer buf = ByteBuffer.wrap(data);
+
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new BinaryFrame().setPayload(buf));
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new BinaryFrame().setPayload(clone(buf)));
+        expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        try(Fuzzer fuzzer = new Fuzzer(this))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect,4,TimeUnit.SECONDS);
+        }
+    }
+
+    /**
+     * Echo 4MB binary message (1 frame)
+     */
+    @Test
+    @Stress("High I/O use")
+    public void testCase9_2_4() throws Exception
+    {
+        byte data[] = new byte[4 * MBYTE];
+        Arrays.fill(data,(byte)0x24);
+        ByteBuffer buf = ByteBuffer.wrap(data);
+
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new BinaryFrame().setPayload(buf));
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new BinaryFrame().setPayload(clone(buf)));
+        expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        try(Fuzzer fuzzer = new Fuzzer(this))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect,8,TimeUnit.SECONDS);
+        }
+    }
+
+    /**
+     * Echo 8MB binary message (1 frame)
+     */
+    @Test
+    @Stress("High I/O use")
+    public void testCase9_2_5() throws Exception
+    {
+        byte data[] = new byte[8 * MBYTE];
+        Arrays.fill(data,(byte)0x25);
+        ByteBuffer buf = ByteBuffer.wrap(data);
+
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new BinaryFrame().setPayload(buf));
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new BinaryFrame().setPayload(clone(buf)));
+        expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        try(Fuzzer fuzzer = new Fuzzer(this))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect,16,TimeUnit.SECONDS);
+        }
+    }
+
+    /**
+     * Echo 16MB binary message (1 frame)
+     */
+    @Test
+    @Stress("High I/O use")
+    public void testCase9_2_6() throws Exception
+    {
+        byte data[] = new byte[16 * MBYTE];
+        Arrays.fill(data,(byte)0x26);
+        ByteBuffer buf = ByteBuffer.wrap(data);
+
+        List<WebSocketFrame> send = new ArrayList<>();
+        send.add(new BinaryFrame().setPayload(buf));
+        send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        List<WebSocketFrame> expect = new ArrayList<>();
+        expect.add(new BinaryFrame().setPayload(clone(buf)));
+        expect.add(new CloseInfo(StatusCode.NORMAL).asFrame());
+
+        try(Fuzzer fuzzer = new Fuzzer(this))
+        {
+            fuzzer.connect();
+            fuzzer.setSendMode(Fuzzer.SendMode.BULK);
+            fuzzer.send(send);
+            fuzzer.expect(expect,32,TimeUnit.SECONDS);
+        }
+    }
+
+    /**
+     * Send 4MB text message in multiple frames.
+     */
+    @Test
+    @Stress("High I/O use")
+    public void testCase9_3_1() throws Exception
+    {
+        assertMultiFrameEcho(OpCode.TEXT,4 * MBYTE,64);
+    }
+
+    /**
+     * Send 4MB text message in multiple frames.
+     */
+    @Test
+    @Stress("High I/O use")
+    public void testCase9_3_2() throws Exception
+    {
+        assertMultiFrameEcho(OpCode.TEXT,4 * MBYTE,256);
+    }
+
+    /**
+     * Send 4MB text message in multiple frames.
+     */
+    @Test
+    @Stress("High I/O use")
+    public void testCase9_3_3() throws Exception
+    {
+        assertMultiFrameEcho(OpCode.TEXT,4 * MBYTE,1 * KBYTE);
+    }
+
+    /**
+     * Send 4MB text message in multiple frames.
+     */
+    @Test
+    @Stress("High I/O use")
+    public void testCase9_3_4() throws Exception
+    {
+        assertMultiFrameEcho(OpCode.TEXT,4 * MBYTE,4 * KBYTE);
+    }
+
+    /**
+     * Send 4MB text message in multiple frames.
+     */
+    @Test
+    @Stress("High I/O use")
+    public void testCase9_3_5() throws Exception
+    {
+        assertMultiFrameEcho(OpCode.TEXT,4 * MBYTE,16 * KBYTE);
+    }
+
+    /**
+     * Send 4MB text message in multiple frames.
+     */
+    @Test
+    @Stress("High I/O use")
+    public void testCase9_3_6() throws Exception
+    {
+        assertMultiFrameEcho(OpCode.TEXT,4 * MBYTE,64 * KBYTE);
+    }
+
+    /**
+     * Send 4MB text message in multiple frames.
+     */
+    @Test
+    @Stress("High I/O use")
+    public void testCase9_3_7() throws Exception
+    {
+        assertMultiFrameEcho(OpCode.TEXT,4 * MBYTE,256 * KBYTE);
+    }
+
+    /**
+     * Send 4MB text message in multiple frames.
+     */
+    @Test
+    @Stress("High I/O use")
+    public void testCase9_3_8() throws Exception
+    {
+        assertMultiFrameEcho(OpCode.TEXT,4 * MBYTE,1 * MBYTE);
+    }
+
+    /**
+     * Send 4MB text message in multiple frames.
+     */
+    @Test
+    @Stress("High I/O use")
+    public void testCase9_3_9() throws Exception
+    {
+        assertMultiFrameEcho(OpCode.TEXT,4 * MBYTE,4 * MBYTE);
+    }
+
+    /**
+     * Send 4MB binary message in multiple frames.
+     */
+    @Test
+    @Stress("High I/O use")
+    public void testCase9_4_1() throws Exception
+    {
+        assertMultiFrameEcho(OpCode.BINARY,4 * MBYTE,64);
+    }
+
+    /**
+     * Send 4MB binary message in multiple frames.
+     */
+    @Test
+    @Stress("High I/O use")
+    public void testCase9_4_2() throws Exception
+    {
+        assertMultiFrameEcho(OpCode.BINARY,4 * MBYTE,256);
+    }
+
+    /**
+     * Send 4MB binary message in multiple frames.
+     */
+    @Test
+    @Stress("High I/O use")
+    public void testCase9_4_3() throws Exception
+    {
+        assertMultiFrameEcho(OpCode.BINARY,4 * MBYTE,1 * KBYTE);
+    }
+
+    /**
+     * Send 4MB binary message in multiple frames.
+     */
+    @Test
+    @Stress("High I/O use")
+    public void testCase9_4_4() throws Exception
+    {
+        assertMultiFrameEcho(OpCode.BINARY,4 * MBYTE,4 * KBYTE);
+    }
+
+    /**
+     * Send 4MB binary message in multiple frames.
+     */
+    @Test
+    @Stress("High I/O use")
+    public void testCase9_4_5() throws Exception
+    {
+        assertMultiFrameEcho(OpCode.BINARY,4 * MBYTE,16 * KBYTE);
+    }
+
+    /**
+     * Send 4MB binary message in multiple frames.
+     */
+    @Test
+    @Stress("High I/O use")
+    public void testCase9_4_6() throws Exception
+    {
+        assertMultiFrameEcho(OpCode.BINARY,4 * MBYTE,64 * KBYTE);
+    }
+
+    /**
+     * Send 4MB binary message in multiple frames.
+     */
+    @Test
+    @Stress("High I/O use")
+    public void testCase9_4_7() throws Exception
+    {
+        assertMultiFrameEcho(OpCode.BINARY,4 * MBYTE,256 * KBYTE);
+    }
+
+    /**
+     * Send 4MB binary message in multiple frames.
+     */
+    @Test
+    @Stress("High I/O use")
+    public void testCase9_4_8() throws Exception
+    {
+        assertMultiFrameEcho(OpCode.BINARY,4 * MBYTE,1 * MBYTE);
+    }
+
+    /**
+     * Send 4MB binary message in multiple frames.
+     */
+    @Test
+    @Stress("High I/O use")
+    public void testCase9_4_9() throws Exception
+    {
+        assertMultiFrameEcho(OpCode.BINARY,4 * MBYTE,4 * MBYTE);
+    }
+
+    /**
+     * Send 1MB text message in 1 frame, but slowly
+     */
+    @Test
+    @Stress("High I/O use")
+    public void testCase9_5_1() throws Exception
+    {
+        assertSlowFrameEcho(OpCode.TEXT,1 * MBYTE,64);
+    }
+
+    /**
+     * Send 1MB text message in 1 frame, but slowly
+     */
+    @Test
+    @Stress("High I/O use")
+    public void testCase9_5_2() throws Exception
+    {
+        assertSlowFrameEcho(OpCode.TEXT,1 * MBYTE,128);
+    }
+
+    /**
+     * Send 1MB text message in 1 frame, but slowly
+     */
+    @Test
+    @Stress("High I/O use")
+    public void testCase9_5_3() throws Exception
+    {
+        assertSlowFrameEcho(OpCode.TEXT,1 * MBYTE,256);
+    }
+
+    /**
+     * Send 1MB text message in 1 frame, but slowly
+     */
+    @Test
+    @Stress("High I/O use")
+    public void testCase9_5_4() throws Exception
+    {
+        assertSlowFrameEcho(OpCode.TEXT,1 * MBYTE,512);
+    }
+
+    /**
+     * Send 1MB text message in 1 frame, but slowly
+     */
+    @Test
+    @Stress("High I/O use")
+    public void testCase9_5_5() throws Exception
+    {
+        assertSlowFrameEcho(OpCode.TEXT,1 * MBYTE,1024);
+    }
+
+    /**
+     * Send 1MB text message in 1 frame, but slowly
+     */
+    @Test
+    @Stress("High I/O use")
+    public void testCase9_5_6() throws Exception
+    {
+        assertSlowFrameEcho(OpCode.TEXT,1 * MBYTE,2048);
+    }
+
+    /**
+     * Send 1MB binary message in 1 frame, but slowly
+     */
+    @Test
+    @Stress("High I/O use")
+    public void testCase9_6_1() throws Exception
+    {
+        assertSlowFrameEcho(OpCode.BINARY,1 * MBYTE,64);
+    }
+
+    /**
+     * Send 1MB binary message in 1 frame, but slowly
+     */
+    @Test
+    @Stress("High I/O use")
+    public void testCase9_6_2() throws Exception
+    {
+        assertSlowFrameEcho(OpCode.BINARY,1 * MBYTE,128);
+    }
+
+    /**
+     * Send 1MB binary message in 1 frame, but slowly
+     */
+    @Test
+    @Stress("High I/O use")
+    public void testCase9_6_3() throws Exception
+    {
+        assertSlowFrameEcho(OpCode.BINARY,1 * MBYTE,256);
+    }
+
+    /**
+     * Send 1MB binary message in 1 frame, but slowly
+     */
+    @Test
+    @Stress("High I/O use")
+    public void testCase9_6_4() throws Exception
+    {
+        assertSlowFrameEcho(OpCode.BINARY,1 * MBYTE,512);
+    }
+
+    /**
+     * Send 1MB binary message in 1 frame, but slowly
+     */
+    @Test
+    @Stress("High I/O use")
+    public void testCase9_6_5() throws Exception
+    {
+        assertSlowFrameEcho(OpCode.BINARY,1 * MBYTE,1024);
+    }
+
+    /**
+     * Send 1MB binary message in 1 frame, but slowly
+     */
+    @Test
+    @Stress("High I/O use")
+    public void testCase9_6_6() throws Exception
+    {
+        assertSlowFrameEcho(OpCode.BINARY,1 * MBYTE,2048);
+    }
+}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/browser/BrowserDebugTool.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/browser/BrowserDebugTool.java
new file mode 100644
index 0000000..257a1e1
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/browser/BrowserDebugTool.java
@@ -0,0 +1,177 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server.browser;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.handler.ResourceHandler;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
+import org.eclipse.jetty.websocket.common.extensions.FrameDebugExtension;
+import org.eclipse.jetty.websocket.common.extensions.compress.PerMessageDeflateExtension;
+import org.eclipse.jetty.websocket.server.WebSocketHandler;
+import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
+import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
+import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
+import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
+
+/**
+ * Tool to help debug websocket circumstances reported around browsers.
+ * <p>
+ * Provides a server, with a few simple websocket's that can be twiddled from a browser. This helps with setting up breakpoints and whatnot to help debug our
+ * websocket implementation from the context of a browser client.
+ */
+public class BrowserDebugTool implements WebSocketCreator
+{
+    private static final Logger LOG = Log.getLogger(BrowserDebugTool.class);
+
+    public static void main(String[] args)
+    {
+        int port = 8080;
+
+        for (int i = 0; i < args.length; i++)
+        {
+            String a = args[i];
+            if ("-p".equals(a) || "--port".equals(a))
+            {
+                port = Integer.parseInt(args[++i]);
+            }
+        }
+
+        try
+        {
+            BrowserDebugTool tool = new BrowserDebugTool();
+            tool.prepare(port);
+            tool.start();
+        }
+        catch (Throwable t)
+        {
+            LOG.warn(t);
+        }
+    }
+
+    private Server server;
+    private ServerConnector connector;
+
+    @Override
+    public Object createWebSocket(ServletUpgradeRequest req, ServletUpgradeResponse resp)
+    {
+        LOG.debug("Creating BrowserSocket");
+
+        if (req.getSubProtocols() != null)
+        {
+            if (!req.getSubProtocols().isEmpty())
+            {
+                String subProtocol = req.getSubProtocols().get(0);
+                resp.setAcceptedSubProtocol(subProtocol);
+            }
+        }
+
+        String ua = req.getHeader("User-Agent");
+        String rexts = req.getHeader("Sec-WebSocket-Extensions");
+
+        // manually negotiate extensions
+        List<ExtensionConfig> negotiated = new ArrayList<>();
+        // adding frame debug
+        negotiated.add(new ExtensionConfig("@frame-debug; output-dir=target"));
+        for (ExtensionConfig config : req.getExtensions())
+        {
+            if (config.getName().equals("permessage-deflate"))
+            {
+                // what we are interested in here
+                negotiated.add(config);
+                continue;
+            }
+            // skip all others
+        }
+
+        resp.setExtensions(negotiated);
+
+        LOG.debug("User-Agent: {}",ua);
+        LOG.debug("Sec-WebSocket-Extensions (Request) : {}",rexts);
+
+        req.getExtensions();
+        return new BrowserSocket(ua,rexts);
+    }
+
+    public int getPort()
+    {
+        return connector.getLocalPort();
+    }
+
+    public void prepare(int port)
+    {
+        server = new Server();
+        connector = new ServerConnector(server);
+        connector.setPort(port);
+        server.addConnector(connector);
+
+        WebSocketHandler wsHandler = new WebSocketHandler()
+        {
+            @Override
+            public void configure(WebSocketServletFactory factory)
+            {
+                LOG.debug("Configuring WebSocketServerFactory ...");
+
+                // factory.getExtensionFactory().unregister("deflate-frame");
+                // factory.getExtensionFactory().unregister("permessage-deflate");
+                factory.getExtensionFactory().register("permessage-deflate",PerMessageDeflateExtension.class);
+                // factory.getExtensionFactory().unregister("x-webkit-deflate-frame");
+
+                // Registering Frame Debug
+                factory.getExtensionFactory().register("@frame-debug",FrameDebugExtension.class);
+
+                // Setup the desired Socket to use for all incoming upgrade requests
+                factory.setCreator(BrowserDebugTool.this);
+
+                // Set the timeout
+                factory.getPolicy().setIdleTimeout(30000);
+
+                // Set top end message size
+                factory.getPolicy().setMaxTextMessageSize(15 * 1024 * 1024);
+            }
+        };
+
+        server.setHandler(wsHandler);
+
+        String resourceBase = "src/test/resources/browser-debug-tool";
+
+        ResourceHandler rHandler = new ResourceHandler();
+        rHandler.setDirectoriesListed(true);
+        rHandler.setResourceBase(resourceBase);
+        wsHandler.setHandler(rHandler);
+
+        LOG.info("{} setup on port {}",this.getClass().getName(),port);
+    }
+
+    public void start() throws Exception
+    {
+        server.start();
+        LOG.info("Server available on port {}",getPort());
+    }
+
+    public void stop() throws Exception
+    {
+        server.stop();
+    }
+}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/browser/BrowserSocket.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/browser/BrowserSocket.java
new file mode 100644
index 0000000..5810768
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/browser/BrowserSocket.java
@@ -0,0 +1,281 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server.browser;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Locale;
+import java.util.Random;
+
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.Loader;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.RemoteEndpoint;
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
+import org.eclipse.jetty.websocket.api.annotations.WebSocket;
+
+ at WebSocket
+public class BrowserSocket
+{
+    private static class WriteMany implements Runnable
+    {
+        private RemoteEndpoint remote;
+        private int size;
+        private int count;
+
+        public WriteMany(RemoteEndpoint remote, int size, int count)
+        {
+            this.remote = remote;
+            this.size = size;
+            this.count = count;
+        }
+
+        @Override
+        public void run()
+        {
+            char letters[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-|{}[]():".toCharArray();
+            int lettersLen = letters.length;
+            char randomText[] = new char[size];
+            Random rand = new Random(42);
+            String msg;
+
+            for (int n = 0; n < count; n++)
+            {
+                // create random text
+                for (int i = 0; i < size; i++)
+                {
+                    randomText[i] = letters[rand.nextInt(lettersLen)];
+                }
+                msg = String.format("ManyThreads [%s]",String.valueOf(randomText));
+                remote.sendString(msg,null);
+            }
+        }
+    }
+
+    private static final Logger LOG = Log.getLogger(BrowserSocket.class);
+
+    private Session session;
+    private final String userAgent;
+    private final String requestedExtensions;
+
+    public BrowserSocket(String ua, String reqExts)
+    {
+        this.userAgent = ua;
+        this.requestedExtensions = reqExts;
+    }
+
+    @OnWebSocketConnect
+    public void onConnect(Session session)
+    {
+        LOG.info("Connect [{}]",session);
+        this.session = session;
+    }
+
+    @OnWebSocketClose
+    public void onDisconnect(int statusCode, String reason)
+    {
+        this.session = null;
+        LOG.info("Closed [{}, {}]",statusCode,reason);
+    }
+
+    @OnWebSocketError
+    public void onError(Throwable cause)
+    {
+        this.session = null;
+        LOG.warn("Error",cause);
+    }
+
+    @OnWebSocketMessage
+    public void onTextMessage(String message)
+    {
+        if (message.length() > 300)
+        {
+            int len = message.length();
+            LOG.info("onTextMessage({} ... {}) size:{}",message.substring(0,15),message.substring(len - 15,len).replaceAll("[\r\n]*",""),len);
+        }
+        else
+        {
+            LOG.info("onTextMessage({})",message);
+        }
+
+        // Is multi-line?
+        if (message.contains("\n"))
+        {
+            // echo back exactly
+            writeMessage(message);
+            return;
+        }
+
+        // Is resource lookup?
+        if (message.charAt(0) == '@')
+        {
+            String name = message.substring(1);
+            URL url = Loader.getResource(BrowserSocket.class,name);
+            if (url == null)
+            {
+                writeMessage("Unable to find resource: " + name);
+                return;
+            }
+            try (InputStream in = url.openStream())
+            {
+                String data = IO.toString(in);
+                writeMessage(data);
+            }
+            catch (IOException e)
+            {
+                writeMessage("Unable to read resource: " + name);
+                LOG.warn("Unable to read resource: " + name,e);
+            }
+            return;
+        }
+
+        // Is parameterized?
+        int idx = message.indexOf(':');
+        if (idx > 0)
+        {
+            String key = message.substring(0,idx).toLowerCase(Locale.ENGLISH);
+            String val = message.substring(idx + 1);
+            switch (key)
+            {
+                case "info":
+                {
+                    if (StringUtil.isBlank(userAgent))
+                    {
+                        writeMessage("Client has no User-Agent");
+                    }
+                    else
+                    {
+                        writeMessage("Client User-Agent: " + this.userAgent);
+                    }
+
+                    if (StringUtil.isBlank(requestedExtensions))
+                    {
+                        writeMessage("Client requested no Sec-WebSocket-Extensions");
+                    }
+                    else
+                    {
+                        writeMessage("Client requested Sec-WebSocket-Extensions: " + this.requestedExtensions);
+                        writeMessage("Negotiated Sec-WebSocket-Extensions: " + session.getUpgradeResponse().getHeader("Sec-WebSocket-Extensions"));
+                    }
+
+                    break;
+                }
+                case "many":
+                {
+                    String parts[] = val.split(",");
+                    int size = Integer.parseInt(parts[0]);
+                    int count = Integer.parseInt(parts[1]);
+
+                    writeManyAsync(size,count);
+                    break;
+                }
+                case "manythreads":
+                {
+                    String parts[] = val.split(",");
+                    int threadCount = Integer.parseInt(parts[0]);
+                    int size = Integer.parseInt(parts[1]);
+                    int count = Integer.parseInt(parts[2]);
+
+                    Thread threads[] = new Thread[threadCount];
+
+                    // Setup threads
+                    for (int n = 0; n < threadCount; n++)
+                    {
+                        threads[n] = new Thread(new WriteMany(session.getRemote(),size,count),"WriteMany[" + n + "]");
+                    }
+
+                    // Execute threads
+                    for (Thread thread : threads)
+                    {
+                        thread.start();
+                    }
+
+                    // Drop out of this thread
+                    break;
+                }
+                case "time":
+                {
+                    Calendar now = Calendar.getInstance();
+                    DateFormat sdf = SimpleDateFormat.getDateTimeInstance(SimpleDateFormat.FULL,SimpleDateFormat.FULL);
+                    writeMessage("Server time: %s",sdf.format(now.getTime()));
+                    break;
+                }
+                default:
+                {
+                    writeMessage("key[%s] val[%s]",key,val);
+                }
+            }
+            return;
+        }
+
+        // Not parameterized, echo it back as-is
+        writeMessage(message);
+    }
+
+    private void writeManyAsync(int size, int count)
+    {
+        char letters[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-|{}[]():".toCharArray();
+        int lettersLen = letters.length;
+        char randomText[] = new char[size];
+        Random rand = new Random(42);
+
+        for (int n = 0; n < count; n++)
+        {
+            // create random text
+            for (int i = 0; i < size; i++)
+            {
+                randomText[i] = letters[rand.nextInt(lettersLen)];
+            }
+            writeMessage("Many [%s]",String.valueOf(randomText));
+        }
+    }
+
+    private void writeMessage(String message)
+    {
+        if (this.session == null)
+        {
+            LOG.debug("Not connected");
+            return;
+        }
+
+        if (!session.isOpen())
+        {
+            LOG.debug("Not open");
+            return;
+        }
+
+        // Async write
+        session.getRemote().sendString(message,null);
+    }
+
+    private void writeMessage(String format, Object... args)
+    {
+        writeMessage(String.format(format,args));
+    }
+}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/examples/BasicEchoSocket.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/examples/BasicEchoSocket.java
new file mode 100644
index 0000000..2fe64a8
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/examples/BasicEchoSocket.java
@@ -0,0 +1,65 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server.examples;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.websocket.api.WebSocketAdapter;
+
+/**
+ * Example of a blocking echo websocket.
+ */
+public class BasicEchoSocket extends WebSocketAdapter
+{
+    @Override
+    public void onWebSocketBinary(byte[] payload, int offset, int len)
+    {
+        if (isNotConnected())
+        {
+            return;
+        }
+        try
+        {
+            ByteBuffer buf = ByteBuffer.wrap(payload,offset,len);
+            getRemote().sendBytes(buf);
+        }
+        catch (IOException e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    public void onWebSocketText(String message)
+    {
+        if (isNotConnected())
+        {
+            return;
+        }
+        try
+        {
+            getRemote().sendString(message);
+        }
+        catch (IOException e)
+        {
+            e.printStackTrace();
+        }
+    }
+}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/examples/MyCustomCreationServlet.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/examples/MyCustomCreationServlet.java
new file mode 100644
index 0000000..5628b85
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/examples/MyCustomCreationServlet.java
@@ -0,0 +1,79 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server.examples;
+
+import java.io.IOException;
+
+import org.eclipse.jetty.websocket.server.examples.echo.BigEchoSocket;
+import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
+import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
+import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
+import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
+import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
+
+ at SuppressWarnings("serial")
+public class MyCustomCreationServlet extends WebSocketServlet
+{
+    public static class MyCustomCreator implements WebSocketCreator
+    {
+        @Override
+        public Object createWebSocket(ServletUpgradeRequest req, ServletUpgradeResponse resp)
+        {
+            String query = req.getQueryString();
+
+            // Start looking at the UpgradeRequest to determine what you want to do
+            if ((query == null) || (query.length() <= 0))
+            {
+                try
+                {
+                    // Let UPGRADE request for websocket fail with
+                    // status code 403 (FORBIDDEN) [per RFC-6455]
+                    resp.sendForbidden("Unspecified query");
+                }
+                catch (IOException e)
+                {
+                    // An input or output exception occurs
+                    e.printStackTrace();
+                }
+
+                // No UPGRADE
+                return null;
+            }
+
+            // Create the websocket we want to
+            if (query.contains("bigecho"))
+            {
+                return new BigEchoSocket();
+            }
+            else if (query.contains("echo"))
+            {
+                return new MyEchoSocket();
+            }
+
+            // Let UPGRADE fail with 503 (UNAVAILABLE)
+            return null;
+        }
+    }
+
+    @Override
+    public void configure(WebSocketServletFactory factory)
+    {
+        factory.setCreator(new MyCustomCreator());
+    }
+}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/examples/MyEchoServlet.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/examples/MyEchoServlet.java
new file mode 100644
index 0000000..e491800
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/examples/MyEchoServlet.java
@@ -0,0 +1,35 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server.examples;
+
+import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
+import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
+
+/**
+ * Example servlet for most basic form.
+ */
+ at SuppressWarnings("serial")
+public class MyEchoServlet extends WebSocketServlet
+{
+    @Override
+    public void configure(WebSocketServletFactory factory)
+    {
+        factory.register(MyEchoSocket.class);
+    }
+}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/examples/MyEchoSocket.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/examples/MyEchoSocket.java
new file mode 100644
index 0000000..5797c31
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/examples/MyEchoSocket.java
@@ -0,0 +1,54 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server.examples;
+
+import java.io.IOException;
+
+import org.eclipse.jetty.io.RuntimeIOException;
+import org.eclipse.jetty.websocket.api.BatchMode;
+import org.eclipse.jetty.websocket.api.RemoteEndpoint;
+import org.eclipse.jetty.websocket.api.WebSocketAdapter;
+
+/**
+ * Example of a basic blocking echo socket.
+ */
+public class MyEchoSocket extends WebSocketAdapter
+{
+    @Override
+    public void onWebSocketText(String message)
+    {
+        if (isNotConnected())
+        {
+            return;
+        }
+
+        try
+        {
+            // echo the data back
+            RemoteEndpoint remote = getRemote();
+            remote.sendString(message);
+            if (remote.getBatchMode() == BatchMode.ON)
+                remote.flush();
+        }
+        catch (IOException e)
+        {
+            throw new RuntimeIOException(e);
+        }
+    }
+}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/examples/echo/BigEchoSocket.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/examples/echo/BigEchoSocket.java
new file mode 100644
index 0000000..7f0abee
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/examples/echo/BigEchoSocket.java
@@ -0,0 +1,67 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server.examples.echo;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.BatchMode;
+import org.eclipse.jetty.websocket.api.RemoteEndpoint;
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
+import org.eclipse.jetty.websocket.api.annotations.WebSocket;
+
+/**
+ * Example Socket for echoing back Big data using the Annotation techniques along with stateless techniques.
+ */
+ at WebSocket(maxTextMessageSize = 64 * 1024, maxBinaryMessageSize = 64 * 1024)
+public class BigEchoSocket
+{
+    private static final Logger LOG = Log.getLogger(BigEchoSocket.class);
+
+    @OnWebSocketMessage
+    public void onBinary(Session session, byte buf[], int offset, int length) throws IOException
+    {
+        if (!session.isOpen())
+        {
+            LOG.warn("Session is closed");
+            return;
+        }
+        RemoteEndpoint remote = session.getRemote();
+        remote.sendBytes(ByteBuffer.wrap(buf, offset, length), null);
+        if (remote.getBatchMode() == BatchMode.ON)
+            remote.flush();
+    }
+
+    @OnWebSocketMessage
+    public void onText(Session session, String message) throws IOException
+    {
+        if (!session.isOpen())
+        {
+            LOG.warn("Session is closed");
+            return;
+        }
+        RemoteEndpoint remote = session.getRemote();
+        remote.sendString(message, null);
+        if (remote.getBatchMode() == BatchMode.ON)
+            remote.flush();
+    }
+}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/examples/echo/EchoBroadcastPingSocket.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/examples/echo/EchoBroadcastPingSocket.java
new file mode 100644
index 0000000..f0cf2c3
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/examples/echo/EchoBroadcastPingSocket.java
@@ -0,0 +1,101 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server.examples.echo;
+
+import java.nio.ByteBuffer;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.annotations.WebSocket;
+
+ at WebSocket
+public class EchoBroadcastPingSocket extends EchoBroadcastSocket
+{
+    private static class KeepAlive extends Thread
+    {
+        private CountDownLatch latch;
+        private Session session;
+
+        public KeepAlive(Session session)
+        {
+            this.session = session;
+        }
+
+        @Override
+        public void run()
+        {
+            try
+            {
+                while (!latch.await(10,TimeUnit.SECONDS))
+                {
+                    System.err.println("Ping");
+                    ByteBuffer data = ByteBuffer.allocate(3);
+                    data.put(new byte[]
+                    { (byte)1, (byte)2, (byte)3 });
+                    data.flip();
+                    session.getRemote().sendPing(data);
+                }
+            }
+            catch (Exception e)
+            {
+                e.printStackTrace();
+            }
+        }
+
+        public void shutdown()
+        {
+            if (latch != null)
+            {
+                latch.countDown();
+            }
+        }
+
+        @Override
+        public synchronized void start()
+        {
+            latch = new CountDownLatch(1);
+            super.start();
+        }
+    }
+
+    private KeepAlive keepAlive; // A dedicated thread is not a good way to do this
+
+    public EchoBroadcastPingSocket()
+    {
+    }
+
+    @Override
+    public void onClose(int statusCode, String reason)
+    {
+        keepAlive.shutdown();
+        super.onClose(statusCode,reason);
+    }
+
+    @Override
+    public void onOpen(Session session)
+    {
+        if (keepAlive == null)
+        {
+            keepAlive = new KeepAlive(session);
+        }
+        keepAlive.start();
+        super.onOpen(session);
+    }
+}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/examples/echo/EchoBroadcastSocket.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/examples/echo/EchoBroadcastSocket.java
new file mode 100644
index 0000000..0ded6e5
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/examples/echo/EchoBroadcastSocket.java
@@ -0,0 +1,68 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server.examples.echo;
+
+import java.nio.ByteBuffer;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
+import org.eclipse.jetty.websocket.api.annotations.WebSocket;
+
+ at WebSocket
+public class EchoBroadcastSocket
+{
+    private static final ConcurrentLinkedQueue<EchoBroadcastSocket> BROADCAST = new ConcurrentLinkedQueue<EchoBroadcastSocket>();
+
+    protected Session session;
+
+    @OnWebSocketMessage
+    public void onBinary(byte buf[], int offset, int len)
+    {
+        ByteBuffer data = ByteBuffer.wrap(buf,offset,len);
+        for (EchoBroadcastSocket sock : BROADCAST)
+        {
+            sock.session.getRemote().sendBytes(data.slice(),null);
+        }
+    }
+
+    @OnWebSocketClose
+    public void onClose(int statusCode, String reason)
+    {
+        BROADCAST.remove(this);
+    }
+
+    @OnWebSocketConnect
+    public void onOpen(Session session)
+    {
+        this.session = session;
+        BROADCAST.add(this);
+    }
+
+    @OnWebSocketMessage
+    public void onText(String text)
+    {
+        for (EchoBroadcastSocket sock : BROADCAST)
+        {
+            sock.session.getRemote().sendString(text,null);
+        }
+    }
+}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/examples/echo/EchoCreator.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/examples/echo/EchoCreator.java
new file mode 100644
index 0000000..cabf144
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/examples/echo/EchoCreator.java
@@ -0,0 +1,68 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server.examples.echo;
+
+import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
+import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
+import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
+
+/**
+ * Example of setting up a creator to create appropriately via the proposed and negotiated protocols.
+ */
+public class EchoCreator implements WebSocketCreator
+{
+    private BigEchoSocket bigEchoSocket = new BigEchoSocket();
+    private EchoFragmentSocket echoFragmentSocket = new EchoFragmentSocket();
+    private LogSocket logSocket = new LogSocket();
+
+    @Override
+    public Object createWebSocket(ServletUpgradeRequest req, ServletUpgradeResponse resp)
+    {
+        for (String protocol : req.getSubProtocols())
+        {
+            switch (protocol)
+            {
+                case "org.ietf.websocket.test-echo":
+                case "echo":
+                    resp.setAcceptedSubProtocol(protocol);
+                    // TODO: how is this different than "echo-assemble"?
+                    return bigEchoSocket;
+                case "org.ietf.websocket.test-echo-broadcast":
+                case "echo-broadcast":
+                    resp.setAcceptedSubProtocol(protocol);
+                    return new EchoBroadcastSocket();
+                case "echo-broadcast-ping":
+                    resp.setAcceptedSubProtocol(protocol);
+                    return new EchoBroadcastPingSocket();
+                case "org.ietf.websocket.test-echo-assemble":
+                case "echo-assemble":
+                    resp.setAcceptedSubProtocol(protocol);
+                    // TODO: how is this different than "test-echo"?
+                    return bigEchoSocket;
+                case "org.ietf.websocket.test-echo-fragment":
+                case "echo-fragment":
+                    resp.setAcceptedSubProtocol(protocol);
+                    return echoFragmentSocket;
+                default:
+                    return logSocket;
+            }
+        }
+        return null;
+    }
+}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/examples/echo/EchoFragmentSocket.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/examples/echo/EchoFragmentSocket.java
new file mode 100644
index 0000000..2df378c
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/examples/echo/EchoFragmentSocket.java
@@ -0,0 +1,79 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server.examples.echo;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.websocket.api.RemoteEndpoint;
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketFrame;
+import org.eclipse.jetty.websocket.api.annotations.WebSocket;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+
+/**
+ * Echo back the incoming text or binary as 2 frames of (roughly) equal size.
+ */
+ at WebSocket
+public class EchoFragmentSocket
+{
+    @OnWebSocketFrame
+    public void onFrame(Session session, Frame frame)
+    {
+        if (!frame.getType().isData())
+        {
+            // Don't process non-data frames
+            return;
+        }
+
+        ByteBuffer data = frame.getPayload();
+
+        int half = data.remaining() / 2;
+
+        ByteBuffer buf1 = data.slice();
+        ByteBuffer buf2 = data.slice();
+
+        buf1.limit(half);
+        buf2.position(half);
+
+        RemoteEndpoint remote = session.getRemote();
+        try
+        {
+            switch (frame.getType())
+            {
+                case BINARY:
+                    remote.sendBytes(buf1,null);
+                    remote.sendBytes(buf2,null);
+                    break;
+                case TEXT:
+                    // NOTE: This impl is not smart enough to split on a UTF8 boundary
+                    remote.sendString(BufferUtil.toUTF8String(buf1),null);
+                    remote.sendString(BufferUtil.toUTF8String(buf2),null);
+                    break;
+                default:
+                    throw new IOException("Unexpected frame type: " + frame.getType());
+            }
+        }
+        catch (IOException e)
+        {
+            e.printStackTrace(System.err);
+        }
+    }
+}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/examples/echo/ExampleEchoServer.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/examples/echo/ExampleEchoServer.java
new file mode 100644
index 0000000..4c08d97
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/examples/echo/ExampleEchoServer.java
@@ -0,0 +1,151 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server.examples.echo;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.handler.ResourceHandler;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.server.WebSocketHandler;
+import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
+
+/**
+ * Example server using WebSocket and core Jetty Handlers
+ */
+public class ExampleEchoServer
+{
+    public final class EchoSocketHandler extends WebSocketHandler
+    {
+        @Override
+        public void configure(WebSocketServletFactory factory)
+        {
+            factory.setCreator(new EchoCreator());
+        }
+    }
+
+    private static final Logger LOG = Log.getLogger(ExampleEchoServer.class);
+
+    public static void main(String... args)
+    {
+        try
+        {
+            int port = 8080;
+            boolean verbose = false;
+            String docroot = "src/test/webapp";
+
+            for (int i = 0; i < args.length; i++)
+            {
+                String a = args[i];
+                if ("-p".equals(a) || "--port".equals(a))
+                {
+                    port = Integer.parseInt(args[++i]);
+                }
+                else if ("-v".equals(a) || "--verbose".equals(a))
+                {
+                    verbose = true;
+                }
+                else if ("-d".equals(a) || "--docroot".equals(a))
+                {
+                    docroot = args[++i];
+                }
+                else if (a.startsWith("-"))
+                {
+                    usage();
+                }
+            }
+
+            ExampleEchoServer server = new ExampleEchoServer(port);
+            server.setVerbose(verbose);
+            server.setResourceBase(docroot);
+            server.runForever();
+        }
+        catch (Exception e)
+        {
+            LOG.warn(e);
+        }
+    }
+
+    private static void usage()
+    {
+        System.err.println("java -cp{CLASSPATH} " + ExampleEchoServer.class + " [ OPTIONS ]");
+        System.err.println("  -p|--port PORT    (default 8080)");
+        System.err.println("  -v|--verbose ");
+        System.err.println("  -d|--docroot file (default 'src/test/webapp')");
+        System.exit(1);
+    }
+
+    private Server server;
+
+    private ServerConnector connector;
+    private boolean _verbose;
+    private WebSocketHandler wsHandler;
+    private ResourceHandler rHandler;
+
+    public ExampleEchoServer(int port)
+    {
+        server = new Server();
+        connector = new ServerConnector(server);
+        connector.setPort(port);
+
+        server.addConnector(connector);
+        wsHandler = new EchoSocketHandler();
+
+        server.setHandler(wsHandler);
+
+        rHandler = new ResourceHandler();
+        rHandler.setDirectoriesListed(true);
+        rHandler.setResourceBase("src/test/webapp");
+        wsHandler.setHandler(rHandler);
+    }
+
+    public String getResourceBase()
+    {
+        return rHandler.getResourceBase();
+    }
+
+    public boolean isVerbose()
+    {
+        return _verbose;
+    }
+
+    public void runForever() throws Exception
+    {
+        server.start();
+        String host = connector.getHost();
+        if (host == null)
+        {
+            host = "localhost";
+        }
+        int port = connector.getLocalPort();
+        System.err.printf("Echo Server started on ws://%s:%d/%n",host,port);
+        System.err.println(server.dump());
+        server.join();
+    }
+
+    public void setResourceBase(String dir)
+    {
+        rHandler.setResourceBase(dir);
+    }
+
+    public void setVerbose(boolean verbose)
+    {
+        _verbose = verbose;
+    }
+}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/examples/echo/LogSocket.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/examples/echo/LogSocket.java
new file mode 100644
index 0000000..aaff323
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/examples/echo/LogSocket.java
@@ -0,0 +1,93 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server.examples.echo;
+
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.WebSocketListener;
+
+public class LogSocket implements WebSocketListener
+{
+    private boolean verbose = false;
+
+    public boolean isVerbose()
+    {
+        return verbose;
+    }
+
+    @Override
+    public void onWebSocketBinary(byte[] payload, int offset, int len)
+    {
+        if (verbose)
+        {
+            System.err.printf("onWebSocketBinary(byte[%d] payload, %d, %d)%n",payload.length,offset,len);
+        }
+    }
+
+    @Override
+    public void onWebSocketClose(int statusCode, String reason)
+    {
+        if (verbose)
+        {
+            System.err.printf("onWebSocketClose(%d, %s)%n",statusCode,quote(reason));
+        }
+    }
+
+    @Override
+    public void onWebSocketConnect(Session session)
+    {
+        if (verbose)
+        {
+            System.err.printf("onWebSocketConnect(%s)%n",session);
+        }
+    }
+
+    @Override
+    public void onWebSocketError(Throwable cause)
+    {
+        if (verbose)
+        {
+            System.err.printf("onWebSocketError((%s) %s)%n",cause.getClass().getName(),cause.getMessage());
+            cause.printStackTrace(System.err);
+        }
+    }
+
+    @Override
+    public void onWebSocketText(String message)
+    {
+        if (verbose)
+        {
+            System.err.printf("onWebSocketText(%s)%n",quote(message));
+        }
+    }
+
+    private String quote(String str)
+    {
+        if (str == null)
+        {
+            return "<null>";
+        }
+        return '"' + str + '"';
+    }
+
+    public void setVerbose(boolean verbose)
+    {
+        this.verbose = verbose;
+    }
+
+}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/CaptureSocket.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/CaptureSocket.java
new file mode 100644
index 0000000..5218c94
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/CaptureSocket.java
@@ -0,0 +1,61 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server.helper;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.toolchain.test.EventQueue;
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.WebSocketAdapter;
+
+public class CaptureSocket extends WebSocketAdapter
+{
+    private final CountDownLatch latch = new CountDownLatch(1);
+    public EventQueue<String> messages;
+
+    public CaptureSocket()
+    {
+        messages = new EventQueue<String>();
+    }
+
+    public boolean awaitConnected(long timeout) throws InterruptedException
+    {
+        return latch.await(timeout,TimeUnit.MILLISECONDS);
+    }
+
+    public void close()
+    {
+        getSession().close();
+    }
+
+    @Override
+    public void onWebSocketConnect(Session sess)
+    {
+        super.onWebSocketConnect(sess);
+        latch.countDown();
+    }
+
+    @Override
+    public void onWebSocketText(String message)
+    {
+        // System.out.printf("Received Message \"%s\" [size %d]%n", message, message.length());
+        messages.add(message);
+    }
+}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/EchoServlet.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/EchoServlet.java
new file mode 100644
index 0000000..f782423
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/EchoServlet.java
@@ -0,0 +1,46 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server.helper;
+
+import org.eclipse.jetty.websocket.common.extensions.compress.DeflateFrameExtension;
+import org.eclipse.jetty.websocket.common.extensions.compress.PerMessageDeflateExtension;
+import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
+import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
+
+/**
+ * Initialize a simple Echo websocket
+ */
+ at SuppressWarnings("serial")
+public class EchoServlet extends WebSocketServlet
+{
+    @Override
+    public void configure(WebSocketServletFactory factory)
+    {
+        // Setup some extensions we want to test against
+        factory.getExtensionFactory().register("x-webkit-deflate-frame",DeflateFrameExtension.class);
+        factory.getExtensionFactory().register("permessage-compress",PerMessageDeflateExtension.class);
+
+        // Setup the desired Socket to use for all incoming upgrade requests
+        factory.register(EchoSocket.class);
+        
+        // Some alternate sizes
+        factory.getPolicy().setMaxBinaryMessageSize(2222);
+        factory.getPolicy().setMaxTextMessageSize(4444);
+    }
+}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/EchoSocket.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/EchoSocket.java
new file mode 100644
index 0000000..fb8944a
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/EchoSocket.java
@@ -0,0 +1,73 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server.helper;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.BatchMode;
+import org.eclipse.jetty.websocket.api.RemoteEndpoint;
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
+import org.eclipse.jetty.websocket.api.annotations.WebSocket;
+
+/**
+ * Simple Echo WebSocket, using async writes of echo
+ */
+ at WebSocket
+public class EchoSocket
+{
+    private static Logger LOG = Log.getLogger(EchoSocket.class);
+
+    private Session session;
+
+    @OnWebSocketMessage
+    public void onBinary(byte buf[], int offset, int len) throws IOException
+    {
+        LOG.debug("onBinary(byte[{}],{},{})",buf.length,offset,len);
+
+        // echo the message back.
+        ByteBuffer data = ByteBuffer.wrap(buf,offset,len);
+        RemoteEndpoint remote = this.session.getRemote();
+        remote.sendBytes(data, null);
+        if (remote.getBatchMode() == BatchMode.ON)
+            remote.flush();
+    }
+
+    @OnWebSocketConnect
+    public void onOpen(Session sess)
+    {
+        this.session = sess;
+    }
+
+    @OnWebSocketMessage
+    public void onText(String message) throws IOException
+    {
+        LOG.debug("onText({})",message);
+
+        // echo the message back.
+        RemoteEndpoint remote = session.getRemote();
+        remote.sendString(message, null);
+        if (remote.getBatchMode() == BatchMode.ON)
+            remote.flush();
+    }
+}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/RFCServlet.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/RFCServlet.java
new file mode 100644
index 0000000..9424c7b
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/RFCServlet.java
@@ -0,0 +1,32 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server.helper;
+
+import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
+import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
+
+ at SuppressWarnings("serial")
+public class RFCServlet extends WebSocketServlet
+{
+    @Override
+    public void configure(WebSocketServletFactory factory)
+    {
+        factory.register(RFCSocket.class);
+    }
+}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/RFCSocket.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/RFCSocket.java
new file mode 100644
index 0000000..7c143ff
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/RFCSocket.java
@@ -0,0 +1,76 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server.helper;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.BatchMode;
+import org.eclipse.jetty.websocket.api.RemoteEndpoint;
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
+import org.eclipse.jetty.websocket.api.annotations.WebSocket;
+
+ at WebSocket
+public class RFCSocket
+{
+    private static Logger LOG = Log.getLogger(RFCSocket.class);
+
+    private Session session;
+
+    @OnWebSocketMessage
+    public void onBinary(byte buf[], int offset, int len) throws IOException
+    {
+        LOG.debug("onBinary(byte[{}],{},{})",buf.length,offset,len);
+
+        // echo the message back.
+        ByteBuffer data = ByteBuffer.wrap(buf,offset,len);
+        RemoteEndpoint remote = session.getRemote();
+        remote.sendBytes(data, null);
+        if (remote.getBatchMode() == BatchMode.ON)
+            remote.flush();
+    }
+
+    @OnWebSocketConnect
+    public void onOpen(Session sess)
+    {
+        this.session = sess;
+    }
+
+    @OnWebSocketMessage
+    public void onText(String message) throws IOException
+    {
+        LOG.debug("onText({})",message);
+        // Test the RFC 6455 close code 1011 that should close
+        // trigger a WebSocket server terminated close.
+        if (message.equals("CRASH"))
+        {
+            throw new RuntimeException("Something bad happened");
+        }
+
+        // echo the message back.
+        RemoteEndpoint remote = session.getRemote();
+        remote.sendString(message, null);
+        if (remote.getBatchMode() == BatchMode.ON)
+            remote.flush();
+    }
+}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/SafariD00.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/SafariD00.java
new file mode 100644
index 0000000..a097ead
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/SafariD00.java
@@ -0,0 +1,151 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server.helper;
+
+import static org.hamcrest.Matchers.*;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.SocketAddress;
+import java.net.URI;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.TypeUtil;
+import org.junit.Assert;
+
+public class SafariD00
+{
+    private URI uri;
+    private SocketAddress endpoint;
+    private Socket socket;
+    private OutputStream out;
+    private InputStream in;
+
+    public SafariD00(URI uri)
+    {
+        this.uri = uri;
+        this.endpoint = new InetSocketAddress(uri.getHost(),uri.getPort());
+    }
+
+    /**
+     * Open the Socket to the destination endpoint and
+     *
+     * @return the open java Socket.
+     * @throws IOException
+     */
+    public Socket connect() throws IOException
+    {
+        socket = new Socket();
+        socket.connect(endpoint,1000);
+
+        out = socket.getOutputStream();
+        in = socket.getInputStream();
+
+        return socket;
+    }
+
+    public void disconnect() throws IOException
+    {
+        socket.close();
+    }
+
+    /**
+     * Issue an Http websocket (Draft-0) upgrade request using the Safari particulars.
+     *
+     * @throws UnsupportedEncodingException
+     */
+    public void issueHandshake() throws IOException
+    {
+        StringBuilder req = new StringBuilder();
+        req.append("GET ").append(uri.getPath()).append(" HTTP/1.1\r\n");
+        req.append("Upgrade: WebSocket\r\n");
+        req.append("Connection: Upgrade\r\n");
+        req.append("Host: ").append(uri.getHost()).append(":").append(uri.getPort()).append("\r\n");
+        req.append("Origin: http://www.google.com/\r\n");
+        req.append("Sec-WebSocket-Key1: 15{ft  :6 at 87  0 M 5 c901\r\n");
+        req.append("Sec-WebSocket-Key2: 3? C;7~0 8   \" 3 2105 6  `_ {\r\n");
+        req.append("\r\n");
+
+        // System.out.printf("--- Request ---%n%s",req);
+
+        byte reqBytes[] = req.toString().getBytes(StandardCharsets.UTF_8);
+        byte hixieBytes[] = TypeUtil.fromHexString("e739617916c9daf3");
+        byte buf[] = new byte[reqBytes.length + hixieBytes.length];
+        System.arraycopy(reqBytes,0,buf,0,reqBytes.length);
+        System.arraycopy(hixieBytes,0,buf,reqBytes.length,hixieBytes.length);
+
+        // Send HTTP GET Request (with hixie bytes)
+        out.write(buf,0,buf.length);
+        out.flush();
+
+        // Read HTTP 101 Upgrade / Handshake Response
+        InputStreamReader reader = new InputStreamReader(in);
+        BufferedReader br = new BufferedReader(reader);
+
+        socket.setSoTimeout(5000);
+
+        boolean foundEnd = false;
+        String line;
+        while (!foundEnd)
+        {
+            line = br.readLine();
+            // System.out.printf("RESP: %s%n",line);
+            Assert.assertThat(line, notNullValue());
+            if (line.length() == 0)
+            {
+                foundEnd = true;
+            }
+        }
+
+        // Read expected handshake hixie bytes
+        byte hixieHandshakeExpected[] = TypeUtil.fromHexString("c7438d956cf611a6af70603e6fa54809");
+        byte hixieHandshake[] = new byte[hixieHandshakeExpected.length];
+
+        int readLen = in.read(hixieHandshake,0,hixieHandshake.length);
+        Assert.assertThat("Read hixie handshake bytes",readLen,is(hixieHandshake.length));
+    }
+
+    public void sendMessage(String... msgs) throws IOException
+    {
+        int len = 0;
+        for (String msg : msgs)
+        {
+            len += (msg.length() + 2);
+        }
+
+        ByteBuffer buf = ByteBuffer.allocate(len);
+
+        for (String msg : msgs)
+        {
+            buf.put((byte)0x00);
+            buf.put(msg.getBytes(StandardCharsets.UTF_8));
+            buf.put((byte)0xFF);
+        }
+
+        BufferUtil.writeTo(buf,out);
+        out.flush();
+    }
+}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/SessionServlet.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/SessionServlet.java
new file mode 100644
index 0000000..fb5e672
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/SessionServlet.java
@@ -0,0 +1,32 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server.helper;
+
+import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
+import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
+
+ at SuppressWarnings("serial")
+public class SessionServlet extends WebSocketServlet
+{
+    @Override
+    public void configure(WebSocketServletFactory factory)
+    {
+        factory.register(SessionSocket.class);
+    }
+}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/SessionSocket.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/SessionSocket.java
new file mode 100644
index 0000000..f6fb35b
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/SessionSocket.java
@@ -0,0 +1,125 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server.helper;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.BatchMode;
+import org.eclipse.jetty.websocket.api.RemoteEndpoint;
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
+import org.eclipse.jetty.websocket.api.annotations.WebSocket;
+
+ at WebSocket
+public class SessionSocket
+{
+    private static final Logger LOG = Log.getLogger(SessionSocket.class);
+    private Session session;
+
+    @OnWebSocketConnect
+    public void onConnect(Session sess)
+    {
+        this.session = sess;
+    }
+
+    @OnWebSocketMessage
+    public void onText(String message)
+    {
+        LOG.debug("onText({})",message);
+        if (message == null)
+        {
+            return;
+        }
+
+        try
+        {
+            if (message.startsWith("getParameterMap"))
+            {
+                Map<String, List<String>> parameterMap = session.getUpgradeRequest().getParameterMap();
+
+                int idx = message.indexOf('|');
+                String key = message.substring(idx + 1);
+                List<String> values = parameterMap.get(key);
+
+                if (values == null)
+                {
+                    sendString("<null>");
+                    return;
+                }
+
+                StringBuilder valueStr = new StringBuilder();
+                valueStr.append('[');
+                boolean delim = false;
+                for (String value : values)
+                {
+                    if (delim)
+                    {
+                        valueStr.append(", ");
+                    }
+                    valueStr.append(value);
+                    delim = true;
+                }
+                valueStr.append(']');
+                LOG.debug("valueStr = {}", valueStr);
+                sendString(valueStr.toString());
+                return;
+            }
+
+            if ("session.isSecure".equals(message))
+            {
+                String issecure = String.format("session.isSecure=%b",session.isSecure());
+                sendString(issecure);
+                return;
+            }
+
+            if ("session.upgradeRequest.requestURI".equals(message))
+            {
+                String response = String.format("session.upgradeRequest.requestURI=%s",session.getUpgradeRequest().getRequestURI().toASCIIString());
+                sendString(response);
+                return;
+            }
+
+            if ("harsh-disconnect".equals(message))
+            {
+                session.disconnect();
+                return;
+            }
+
+            // echo the message back.
+            sendString(message);
+        }
+        catch (Throwable t)
+        {
+            LOG.warn(t);
+        }
+    }
+
+    protected void sendString(String text) throws IOException
+    {
+        RemoteEndpoint remote = session.getRemote();
+        remote.sendString(text, null);
+        if (remote.getBatchMode() == BatchMode.ON)
+            remote.flush();
+    }
+}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/WebSocketCaptureServlet.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/WebSocketCaptureServlet.java
new file mode 100644
index 0000000..33faf46
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/WebSocketCaptureServlet.java
@@ -0,0 +1,44 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server.helper;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
+import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
+
+ at SuppressWarnings("serial")
+public class WebSocketCaptureServlet extends WebSocketServlet
+{
+    @Override
+    public void configure(WebSocketServletFactory factory)
+    {
+        factory.register(CaptureSocket.class);
+    }
+
+    @Override
+    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+    {
+        resp.sendError(404);
+    }
+}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/misbehaving/AnnotatedRuntimeOnConnectSocket.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/misbehaving/AnnotatedRuntimeOnConnectSocket.java
new file mode 100644
index 0000000..c8d5afd
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/misbehaving/AnnotatedRuntimeOnConnectSocket.java
@@ -0,0 +1,70 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server.misbehaving;
+
+import java.util.LinkedList;
+import java.util.concurrent.CountDownLatch;
+
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError;
+import org.eclipse.jetty.websocket.api.annotations.WebSocket;
+
+ at WebSocket
+public class AnnotatedRuntimeOnConnectSocket
+{
+    public LinkedList<Throwable> errors = new LinkedList<>();
+    public CountDownLatch closeLatch = new CountDownLatch(1);
+    public int closeStatusCode;
+    public String closeReason;
+
+    @OnWebSocketConnect
+    public void onWebSocketConnect(Session sess)
+    {
+        // Intentional runtime exception.
+        int[] arr = new int[5];
+        for (int i = 0; i < 10; i++)
+        {
+            arr[i] = 222;
+        }
+    }
+
+    @OnWebSocketClose
+    public void onWebSocketClose(int statusCode, String reason)
+    {
+        closeLatch.countDown();
+        closeStatusCode = statusCode;
+        closeReason = reason;
+    }
+
+    @OnWebSocketError
+    public void onWebSocketError(Throwable cause)
+    {
+        this.errors.add(cause);
+    }
+
+    public void reset()
+    {
+        this.closeLatch = new CountDownLatch(1);
+        this.closeStatusCode = -1;
+        this.closeReason = null;
+        this.errors.clear();
+    }
+}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/misbehaving/BadSocketsServlet.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/misbehaving/BadSocketsServlet.java
new file mode 100644
index 0000000..fad8d82
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/misbehaving/BadSocketsServlet.java
@@ -0,0 +1,56 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server.misbehaving;
+
+import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
+import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
+import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
+import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
+import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
+
+ at SuppressWarnings("serial")
+public class BadSocketsServlet extends WebSocketServlet implements WebSocketCreator
+{
+    public ListenerRuntimeOnConnectSocket listenerRuntimeConnect;
+    public AnnotatedRuntimeOnConnectSocket annotatedRuntimeConnect;
+
+    @Override
+    public void configure(WebSocketServletFactory factory)
+    {
+        factory.setCreator(this);
+
+        this.listenerRuntimeConnect = new ListenerRuntimeOnConnectSocket();
+        this.annotatedRuntimeConnect = new AnnotatedRuntimeOnConnectSocket();
+    }
+
+    @Override
+    public Object createWebSocket(ServletUpgradeRequest req, ServletUpgradeResponse resp)
+    {
+        if (req.hasSubProtocol("listener-runtime-connect"))
+        {
+            return this.listenerRuntimeConnect;
+        }
+        else if (req.hasSubProtocol("annotated-runtime-connect"))
+        {
+            return this.annotatedRuntimeConnect;
+        }
+
+        return null;
+    }
+}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/misbehaving/ListenerRuntimeOnConnectSocket.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/misbehaving/ListenerRuntimeOnConnectSocket.java
new file mode 100644
index 0000000..7ff0327
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/misbehaving/ListenerRuntimeOnConnectSocket.java
@@ -0,0 +1,74 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server.misbehaving;
+
+import java.util.LinkedList;
+import java.util.concurrent.CountDownLatch;
+
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.WebSocketAdapter;
+
+public class ListenerRuntimeOnConnectSocket extends WebSocketAdapter
+{
+    public LinkedList<Throwable> errors = new LinkedList<>();
+    public CountDownLatch closeLatch = new CountDownLatch(1);
+    public int closeStatusCode;
+    public String closeReason;
+
+    @Override
+    public void onWebSocketConnect(Session sess)
+    {
+        super.onWebSocketConnect(sess);
+
+        // Intentional runtime exception.
+        int[] arr = new int[5];
+        for (int i = 0; i < 10; i++)
+        {
+            arr[i] = 222;
+        }
+    }
+
+    @Override
+    public void onWebSocketClose(int statusCode, String reason)
+    {
+        closeLatch.countDown();
+        closeStatusCode = statusCode;
+        closeReason = reason;
+    }
+
+    @Override
+    public void onWebSocketError(Throwable cause)
+    {
+        this.errors.add(cause);
+    }
+
+    @Override
+    public void onWebSocketText(String message)
+    {
+        getRemote().sendStringByFuture(message);
+    }
+
+    public void reset()
+    {
+        this.closeLatch = new CountDownLatch(1);
+        this.closeStatusCode = -1;
+        this.closeReason = null;
+        this.errors.clear();
+    }
+}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/misbehaving/MisbehavingClassTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/misbehaving/MisbehavingClassTest.java
new file mode 100644
index 0000000..ddeeda9
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/misbehaving/MisbehavingClassTest.java
@@ -0,0 +1,133 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server.misbehaving;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.toolchain.test.EventQueue;
+import org.eclipse.jetty.util.log.StacklessLogging;
+import org.eclipse.jetty.websocket.api.StatusCode;
+import org.eclipse.jetty.websocket.common.CloseInfo;
+import org.eclipse.jetty.websocket.common.OpCode;
+import org.eclipse.jetty.websocket.common.WebSocketFrame;
+import org.eclipse.jetty.websocket.common.events.AbstractEventDriver;
+import org.eclipse.jetty.websocket.common.test.BlockheadClient;
+import org.eclipse.jetty.websocket.server.SimpleServletServer;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * Testing badly behaving Socket class implementations to get the best
+ * error messages and state out of the websocket implementation.
+ */
+public class MisbehavingClassTest
+{
+    private static SimpleServletServer server;
+    private static BadSocketsServlet badSocketsServlet;
+
+    @BeforeClass
+    public static void startServer() throws Exception
+    {
+        badSocketsServlet = new BadSocketsServlet();
+        server = new SimpleServletServer(badSocketsServlet);
+        server.start();
+    }
+
+    @AfterClass
+    public static void stopServer()
+    {
+        server.stop();
+    }
+
+    @Test
+    public void testListenerRuntimeOnConnect() throws Exception
+    {
+        try (BlockheadClient client = new BlockheadClient(server.getServerUri()))
+        {
+            client.setProtocols("listener-runtime-connect");
+            client.setTimeout(1,TimeUnit.SECONDS);
+            try (StacklessLogging scope = new StacklessLogging(AbstractEventDriver.class))
+            {
+                ListenerRuntimeOnConnectSocket socket = badSocketsServlet.listenerRuntimeConnect;
+                socket.reset();
+
+                client.connect();
+                client.sendStandardRequest();
+                client.expectUpgradeResponse();
+
+                EventQueue<WebSocketFrame> frames = client.readFrames(1,1,TimeUnit.SECONDS);
+                WebSocketFrame frame = frames.poll();
+                assertThat("frames[0].opcode",frame.getOpCode(),is(OpCode.CLOSE));
+                CloseInfo close = new CloseInfo(frame);
+                assertThat("Close Status Code",close.getStatusCode(),is(StatusCode.SERVER_ERROR));
+
+                client.write(close.asFrame()); // respond with close
+
+                // ensure server socket got close event
+                assertThat("Close Latch",socket.closeLatch.await(1,TimeUnit.SECONDS),is(true));
+                assertThat("closeStatusCode",socket.closeStatusCode,is(StatusCode.SERVER_ERROR));
+
+                // Validate errors
+                assertThat("socket.onErrors",socket.errors.size(),is(1));
+                Throwable cause = socket.errors.pop();
+                assertThat("Error type",cause,instanceOf(ArrayIndexOutOfBoundsException.class));
+            }
+        }
+    }
+    
+    @Test
+    public void testAnnotatedRuntimeOnConnect() throws Exception
+    {
+        try (BlockheadClient client = new BlockheadClient(server.getServerUri()))
+        {
+            client.setProtocols("annotated-runtime-connect");
+            client.setTimeout(1,TimeUnit.SECONDS);
+            try (StacklessLogging scope = new StacklessLogging(AbstractEventDriver.class))
+            {
+                AnnotatedRuntimeOnConnectSocket socket = badSocketsServlet.annotatedRuntimeConnect;
+                socket.reset();
+
+                client.connect();
+                client.sendStandardRequest();
+                client.expectUpgradeResponse();
+
+                EventQueue<WebSocketFrame> frames = client.readFrames(1,1,TimeUnit.SECONDS);
+                WebSocketFrame frame = frames.poll();
+                assertThat("frames[0].opcode",frame.getOpCode(),is(OpCode.CLOSE));
+                CloseInfo close = new CloseInfo(frame);
+                assertThat("Close Status Code",close.getStatusCode(),is(StatusCode.SERVER_ERROR));
+
+                client.write(close.asFrame()); // respond with close
+
+                // ensure server socket got close event
+                assertThat("Close Latch",socket.closeLatch.await(1,TimeUnit.SECONDS),is(true));
+                assertThat("closeStatusCode",socket.closeStatusCode,is(StatusCode.SERVER_ERROR));
+
+                // Validate errors
+                assertThat("socket.onErrors",socket.errors.size(),is(1));
+                Throwable cause = socket.errors.pop();
+                assertThat("Error type",cause,instanceOf(ArrayIndexOutOfBoundsException.class));
+            }
+        }
+    }
+}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/pathmap/PathMappingsBenchmarkTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/pathmap/PathMappingsBenchmarkTest.java
new file mode 100644
index 0000000..49b846c
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/pathmap/PathMappingsBenchmarkTest.java
@@ -0,0 +1,226 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server.pathmap;
+
+import java.util.concurrent.CyclicBarrier;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.http.PathMap;
+import org.eclipse.jetty.toolchain.test.AdvancedRunner;
+import org.eclipse.jetty.toolchain.test.annotation.Stress;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+ at RunWith(AdvancedRunner.class)
+public class PathMappingsBenchmarkTest
+{
+    public static abstract class AbstractPathMapThread extends Thread
+    {
+        private int iterations;
+        private CyclicBarrier barrier;
+        @SuppressWarnings("unused")
+        private long success;
+        @SuppressWarnings("unused")
+        private long error;
+
+        public AbstractPathMapThread(int iterations, CyclicBarrier barrier)
+        {
+            this.iterations = iterations;
+            this.barrier = barrier;
+        }
+
+        public abstract String getMatchedResource(String path);
+
+        @Override
+        public void run()
+        {
+            int llen = LOOKUPS.length;
+            String path;
+            String expectedResource;
+            String matchedResource;
+            await(barrier);
+            for (int iter = 0; iter < iterations; iter++)
+            {
+                for (int li = 0; li < llen; li++)
+                {
+                    path = LOOKUPS[li][0];
+                    expectedResource = LOOKUPS[li][1];
+                    matchedResource = getMatchedResource(path);
+                    if (matchedResource.equals(expectedResource))
+                    {
+                        success++;
+                    }
+                    else
+                    {
+                        error++;
+                    }
+                }
+            }
+            await(barrier);
+        }
+    }
+
+    public static class PathMapMatchThread extends AbstractPathMapThread
+    {
+        private PathMap<String> pathmap;
+
+        public PathMapMatchThread(PathMap<String> pathmap, int iters, CyclicBarrier barrier)
+        {
+            super(iters,barrier);
+            this.pathmap = pathmap;
+        }
+
+        @Override
+        public String getMatchedResource(String path)
+        {
+            return pathmap.getMatch(path).getValue();
+        }
+    }
+
+    public static class PathMatchThread extends AbstractPathMapThread
+    {
+        private PathMappings<String> pathmap;
+
+        public PathMatchThread(PathMappings<String> pathmap, int iters, CyclicBarrier barrier)
+        {
+            super(iters,barrier);
+            this.pathmap = pathmap;
+        }
+
+        @Override
+        public String getMatchedResource(String path)
+        {
+            return pathmap.getMatch(path).getResource();
+        }
+    }
+
+    private static final Logger LOG = Log.getLogger(PathMappingsBenchmarkTest.class);
+    private static final String[][] LOOKUPS;
+    private int runs = 20;
+    private int threads = 200;
+    private int iters = 10000;
+
+    static
+    {
+        LOOKUPS = new String[][]
+        {
+        // @formatter:off
+         { "/abs/path", "path" },
+         { "/abs/path/longer","longpath" },
+         { "/abs/path/foo","default" },
+         { "/main.css","default" },
+         { "/downloads/script.gz","gzipped" },
+         { "/downloads/distribution.tar.gz","tarball" },
+         { "/downloads/readme.txt","default" },
+         { "/downloads/logs.tgz","default" },
+         { "/animal/horse/mustang","animals" },
+         { "/animal/bird/eagle/bald","birds" },
+         { "/animal/fish/shark/hammerhead","fishes" },
+         { "/animal/insect/ladybug","animals" },
+        // @formatter:on
+        };
+    }
+
+    private static void await(CyclicBarrier barrier)
+    {
+        try
+        {
+            barrier.await();
+        }
+        catch (Exception x)
+        {
+            throw new RuntimeException(x);
+        }
+    }
+
+    @Stress("High CPU")
+    @Test
+    public void testServletPathMap()
+    {
+        // Setup (old) PathMap
+
+        PathMap<String> p = new PathMap<>();
+
+        p.put("/abs/path","path");
+        p.put("/abs/path/longer","longpath");
+        p.put("/animal/bird/*","birds");
+        p.put("/animal/fish/*","fishes");
+        p.put("/animal/*","animals");
+        p.put("*.tar.gz","tarball");
+        p.put("*.gz","gzipped");
+        p.put("/","default");
+
+        final CyclicBarrier barrier = new CyclicBarrier(threads + 1);
+
+        for (int r = 0; r < runs; r++)
+        {
+            for (int t = 0; t < threads; t++)
+            {
+                PathMapMatchThread thread = new PathMapMatchThread(p,iters,barrier);
+                thread.start();
+            }
+            await(barrier);
+            long begin = System.nanoTime();
+            await(barrier);
+            long end = System.nanoTime();
+            long elapsed = TimeUnit.NANOSECONDS.toMillis(end - begin);
+            int totalMatches = threads * iters * LOOKUPS.length;
+            LOG.info("jetty-http/PathMap (Servlet only) threads:{}/iters:{}/total-matches:{} => {} ms",threads,iters,totalMatches,elapsed);
+        }
+    }
+
+    @Stress("High CPU")
+    @Test
+    public void testServletPathMappings()
+    {
+        // Setup (new) PathMappings
+
+        PathMappings<String> p = new PathMappings<>();
+
+        p.put(new ServletPathSpec("/abs/path"),"path");
+        p.put(new ServletPathSpec("/abs/path/longer"),"longpath");
+        p.put(new ServletPathSpec("/animal/bird/*"),"birds");
+        p.put(new ServletPathSpec("/animal/fish/*"),"fishes");
+        p.put(new ServletPathSpec("/animal/*"),"animals");
+        p.put(new ServletPathSpec("*.tar.gz"),"tarball");
+        p.put(new ServletPathSpec("*.gz"),"gzipped");
+        p.put(new ServletPathSpec("/"),"default");
+
+        final CyclicBarrier barrier = new CyclicBarrier(threads + 1);
+
+        for (int r = 0; r < runs; r++)
+        {
+            for (int t = 0; t < threads; t++)
+            {
+                PathMatchThread thread = new PathMatchThread(p,iters,barrier);
+                thread.start();
+            }
+            await(barrier);
+            long begin = System.nanoTime();
+            await(barrier);
+            long end = System.nanoTime();
+            long elapsed = TimeUnit.NANOSECONDS.toMillis(end - begin);
+            int totalMatches = threads * iters * LOOKUPS.length;
+            LOG.info("jetty-websocket/PathMappings (Servlet only) threads:{}/iters:{}/total-matches:{} => {} ms",threads,iters,totalMatches,elapsed);
+
+        }
+    }
+}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/pathmap/PathMappingsTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/pathmap/PathMappingsTest.java
new file mode 100644
index 0000000..bac5937
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/pathmap/PathMappingsTest.java
@@ -0,0 +1,119 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server.pathmap;
+
+import static org.hamcrest.Matchers.notNullValue;
+
+import org.eclipse.jetty.websocket.server.pathmap.PathMappings.MappedResource;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class PathMappingsTest
+{
+    private void assertMatch(PathMappings<String> pathmap, String path, String expectedValue)
+    {
+        String msg = String.format(".getMatch(\"%s\")",path);
+        MappedResource<String> match = pathmap.getMatch(path);
+        Assert.assertThat(msg,match,notNullValue());
+        String actualMatch = match.getResource();
+        Assert.assertEquals(msg,expectedValue,actualMatch);
+    }
+
+    public void dumpMappings(PathMappings<String> p)
+    {
+        for (MappedResource<String> res : p)
+        {
+            System.out.printf("  %s%n",res);
+        }
+    }
+
+    /**
+     * Test the match order rules with a mixed Servlet and WebSocket path specs
+     * <p>
+     * <ul>
+     * <li>Exact match</li>
+     * <li>Longest prefix match</li>
+     * <li>Longest suffix match</li>
+     * </ul>
+     */
+    @Test
+    public void testMixedMatchOrder()
+    {
+        PathMappings<String> p = new PathMappings<>();
+
+        p.put(new ServletPathSpec("/"),"default");
+        p.put(new ServletPathSpec("/animal/bird/*"),"birds");
+        p.put(new ServletPathSpec("/animal/fish/*"),"fishes");
+        p.put(new ServletPathSpec("/animal/*"),"animals");
+        p.put(new RegexPathSpec("^/animal/.*/chat$"),"animalChat");
+        p.put(new RegexPathSpec("^/animal/.*/cam$"),"animalCam");
+        p.put(new RegexPathSpec("^/entrance/cam$"),"entranceCam");
+
+        // dumpMappings(p);
+
+        assertMatch(p,"/animal/bird/eagle","birds");
+        assertMatch(p,"/animal/fish/bass/sea","fishes");
+        assertMatch(p,"/animal/peccary/javalina/evolution","animals");
+        assertMatch(p,"/","default");
+        assertMatch(p,"/animal/bird/eagle/chat","animalChat");
+        assertMatch(p,"/animal/bird/penguin/chat","animalChat");
+        assertMatch(p,"/animal/fish/trout/cam","animalCam");
+        assertMatch(p,"/entrance/cam","entranceCam");
+    }
+
+    /**
+     * Test the match order rules imposed by the Servlet API.
+     * <p>
+     * <ul>
+     * <li>Exact match</li>
+     * <li>Longest prefix match</li>
+     * <li>Longest suffix match</li>
+     * <li>default</li>
+     * </ul>
+     */
+    @Test
+    public void testServletMatchOrder()
+    {
+        PathMappings<String> p = new PathMappings<>();
+
+        p.put(new ServletPathSpec("/abs/path"),"path");
+        p.put(new ServletPathSpec("/abs/path/longer"),"longpath");
+        p.put(new ServletPathSpec("/animal/bird/*"),"birds");
+        p.put(new ServletPathSpec("/animal/fish/*"),"fishes");
+        p.put(new ServletPathSpec("/animal/*"),"animals");
+        p.put(new ServletPathSpec("*.tar.gz"),"tarball");
+        p.put(new ServletPathSpec("*.gz"),"gzipped");
+        p.put(new ServletPathSpec("/"),"default");
+
+        // dumpMappings(p);
+
+        assertMatch(p,"/abs/path","path");
+        assertMatch(p,"/abs/path/longer","longpath");
+        assertMatch(p,"/abs/path/foo","default");
+        assertMatch(p,"/main.css","default");
+        assertMatch(p,"/downloads/script.gz","gzipped");
+        assertMatch(p,"/downloads/distribution.tar.gz","tarball");
+        assertMatch(p,"/downloads/readme.txt","default");
+        assertMatch(p,"/downloads/logs.tgz","default");
+        assertMatch(p,"/animal/horse/mustang","animals");
+        assertMatch(p,"/animal/bird/eagle/bald","birds");
+        assertMatch(p,"/animal/fish/shark/hammerhead","fishes");
+        assertMatch(p,"/animal/insect/ladybug","animals");
+    }
+}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/pathmap/RegexPathSpecTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/pathmap/RegexPathSpecTest.java
new file mode 100644
index 0000000..94d16e2
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/pathmap/RegexPathSpecTest.java
@@ -0,0 +1,135 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server.pathmap;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import org.junit.Test;
+
+public class RegexPathSpecTest
+{
+    public static void assertMatches(PathSpec spec, String path)
+    {
+        String msg = String.format("Spec(\"%s\").matches(\"%s\")",spec.getPathSpec(),path);
+        assertThat(msg,spec.matches(path),is(true));
+    }
+
+    public static void assertNotMatches(PathSpec spec, String path)
+    {
+        String msg = String.format("!Spec(\"%s\").matches(\"%s\")",spec.getPathSpec(),path);
+        assertThat(msg,spec.matches(path),is(false));
+    }
+
+    @Test
+    public void testExactSpec()
+    {
+        RegexPathSpec spec = new RegexPathSpec("^/a$");
+        assertEquals("Spec.pathSpec","^/a$",spec.getPathSpec());
+        assertEquals("Spec.pattern","^/a$",spec.getPattern().pattern());
+        assertEquals("Spec.pathDepth",1,spec.getPathDepth());
+        assertEquals("Spec.group",PathSpecGroup.EXACT,spec.group);
+
+        assertMatches(spec,"/a");
+
+        assertNotMatches(spec,"/aa");
+        assertNotMatches(spec,"/a/");
+    }
+
+    @Test
+    public void testMiddleSpec()
+    {
+        RegexPathSpec spec = new RegexPathSpec("^/rest/([^/]*)/list$");
+        assertEquals("Spec.pathSpec","^/rest/([^/]*)/list$",spec.getPathSpec());
+        assertEquals("Spec.pattern","^/rest/([^/]*)/list$",spec.getPattern().pattern());
+        assertEquals("Spec.pathDepth",3,spec.getPathDepth());
+        assertEquals("Spec.group",PathSpecGroup.MIDDLE_GLOB,spec.group);
+
+        assertMatches(spec,"/rest/api/list");
+        assertMatches(spec,"/rest/1.0/list");
+        assertMatches(spec,"/rest/2.0/list");
+        assertMatches(spec,"/rest/accounts/list");
+
+        assertNotMatches(spec,"/a");
+        assertNotMatches(spec,"/aa");
+        assertNotMatches(spec,"/aa/bb");
+        assertNotMatches(spec,"/rest/admin/delete");
+        assertNotMatches(spec,"/rest/list");
+    }
+
+    @Test
+    public void testMiddleSpecNoGrouping()
+    {
+        RegexPathSpec spec = new RegexPathSpec("^/rest/[^/]+/list$");
+        assertEquals("Spec.pathSpec","^/rest/[^/]+/list$",spec.getPathSpec());
+        assertEquals("Spec.pattern","^/rest/[^/]+/list$",spec.getPattern().pattern());
+        assertEquals("Spec.pathDepth",3,spec.getPathDepth());
+        assertEquals("Spec.group",PathSpecGroup.MIDDLE_GLOB,spec.group);
+
+        assertMatches(spec,"/rest/api/list");
+        assertMatches(spec,"/rest/1.0/list");
+        assertMatches(spec,"/rest/2.0/list");
+        assertMatches(spec,"/rest/accounts/list");
+
+        assertNotMatches(spec,"/a");
+        assertNotMatches(spec,"/aa");
+        assertNotMatches(spec,"/aa/bb");
+        assertNotMatches(spec,"/rest/admin/delete");
+        assertNotMatches(spec,"/rest/list");
+    }
+
+    @Test
+    public void testPrefixSpec()
+    {
+        RegexPathSpec spec = new RegexPathSpec("^/a/(.*)$");
+        assertEquals("Spec.pathSpec","^/a/(.*)$",spec.getPathSpec());
+        assertEquals("Spec.pattern","^/a/(.*)$",spec.getPattern().pattern());
+        assertEquals("Spec.pathDepth",2,spec.getPathDepth());
+        assertEquals("Spec.group",PathSpecGroup.PREFIX_GLOB,spec.group);
+
+        assertMatches(spec,"/a/");
+        assertMatches(spec,"/a/b");
+        assertMatches(spec,"/a/b/c/d/e");
+
+        assertNotMatches(spec,"/a");
+        assertNotMatches(spec,"/aa");
+        assertNotMatches(spec,"/aa/bb");
+    }
+
+    @Test
+    public void testSuffixSpec()
+    {
+        RegexPathSpec spec = new RegexPathSpec("^(.*).do$");
+        assertEquals("Spec.pathSpec","^(.*).do$",spec.getPathSpec());
+        assertEquals("Spec.pattern","^(.*).do$",spec.getPattern().pattern());
+        assertEquals("Spec.pathDepth",0,spec.getPathDepth());
+        assertEquals("Spec.group",PathSpecGroup.SUFFIX_GLOB,spec.group);
+
+        assertMatches(spec,"/a.do");
+        assertMatches(spec,"/a/b/c.do");
+        assertMatches(spec,"/abcde.do");
+        assertMatches(spec,"/abc/efg.do");
+
+        assertNotMatches(spec,"/a");
+        assertNotMatches(spec,"/aa");
+        assertNotMatches(spec,"/aa/bb");
+        assertNotMatches(spec,"/aa/bb.do/more");
+    }
+}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/pathmap/ServletPathSpecTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/pathmap/ServletPathSpecTest.java
new file mode 100644
index 0000000..b0b8e29
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/pathmap/ServletPathSpecTest.java
@@ -0,0 +1,188 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server.pathmap;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+
+import org.junit.Test;
+
+public class ServletPathSpecTest
+{
+    private void assertBadServletPathSpec(String pathSpec)
+    {
+        try
+        {
+            new ServletPathSpec(pathSpec);
+            fail("Expected IllegalArgumentException for a bad servlet pathspec on: " + pathSpec);
+        }
+        catch (IllegalArgumentException e)
+        {
+            // expected path
+            System.out.println(e);
+        }
+    }
+
+    private void assertMatches(ServletPathSpec spec, String path)
+    {
+        String msg = String.format("Spec(\"%s\").matches(\"%s\")",spec.getPathSpec(),path);
+        assertThat(msg,spec.matches(path),is(true));
+    }
+
+    private void assertNotMatches(ServletPathSpec spec, String path)
+    {
+        String msg = String.format("!Spec(\"%s\").matches(\"%s\")",spec.getPathSpec(),path);
+        assertThat(msg,spec.matches(path),is(false));
+    }
+
+    @Test
+    public void testBadServletPathSpecA()
+    {
+        assertBadServletPathSpec("foo");
+    }
+
+    @Test
+    public void testBadServletPathSpecB()
+    {
+        assertBadServletPathSpec("/foo/*.do");
+    }
+
+    @Test
+    public void testBadServletPathSpecC()
+    {
+        assertBadServletPathSpec("foo/*.do");
+    }
+
+    @Test
+    public void testBadServletPathSpecD()
+    {
+        assertBadServletPathSpec("foo/*.*do");
+    }
+
+    @Test
+    public void testBadServletPathSpecE()
+    {
+        assertBadServletPathSpec("*do");
+    }
+
+    @Test
+    public void testDefaultPathSpec()
+    {
+        ServletPathSpec spec = new ServletPathSpec("/");
+        assertEquals("Spec.pathSpec","/",spec.getPathSpec());
+        assertEquals("Spec.pathDepth",-1,spec.getPathDepth());
+    }
+
+    @Test
+    public void testEmptyPathSpec()
+    {
+        ServletPathSpec spec = new ServletPathSpec("");
+        assertEquals("Spec.pathSpec","/",spec.getPathSpec());
+        assertEquals("Spec.pathDepth",-1,spec.getPathDepth());
+    }
+
+    @Test
+    public void testExactPathSpec()
+    {
+        ServletPathSpec spec = new ServletPathSpec("/abs/path");
+        assertEquals("Spec.pathSpec","/abs/path",spec.getPathSpec());
+        assertEquals("Spec.pathDepth",2,spec.getPathDepth());
+
+        assertMatches(spec,"/abs/path");
+        assertMatches(spec,"/abs/path/");
+
+        assertNotMatches(spec,"/abs/path/more");
+        assertNotMatches(spec,"/foo");
+        assertNotMatches(spec,"/foo/abs/path");
+        assertNotMatches(spec,"/foo/abs/path/");
+    }
+
+    @Test
+    public void testGetPathInfo()
+    {
+        assertEquals("pathInfo exact",null,new ServletPathSpec("/Foo/bar").getPathInfo("/Foo/bar"));
+        assertEquals("pathInfo prefix","/bar",new ServletPathSpec("/Foo/*").getPathInfo("/Foo/bar"));
+        assertEquals("pathInfo prefix","/*",new ServletPathSpec("/Foo/*").getPathInfo("/Foo/*"));
+        assertEquals("pathInfo prefix","/",new ServletPathSpec("/Foo/*").getPathInfo("/Foo/"));
+        assertEquals("pathInfo prefix",null,new ServletPathSpec("/Foo/*").getPathInfo("/Foo"));
+        assertEquals("pathInfo suffix",null,new ServletPathSpec("*.ext").getPathInfo("/Foo/bar.ext"));
+        assertEquals("pathInfo default",null,new ServletPathSpec("/").getPathInfo("/Foo/bar.ext"));
+
+        assertEquals("pathInfo default","/xxx/zzz",new ServletPathSpec("/*").getPathInfo("/xxx/zzz"));
+    }
+
+    @Test
+    public void testNullPathSpec()
+    {
+        ServletPathSpec spec = new ServletPathSpec(null);
+        assertEquals("Spec.pathSpec","/",spec.getPathSpec());
+        assertEquals("Spec.pathDepth",-1,spec.getPathDepth());
+    }
+
+    @Test
+    public void testPathMatch()
+    {
+        assertEquals("pathMatch exact","/Foo/bar",new ServletPathSpec("/Foo/bar").getPathMatch("/Foo/bar"));
+        assertEquals("pathMatch prefix","/Foo",new ServletPathSpec("/Foo/*").getPathMatch("/Foo/bar"));
+        assertEquals("pathMatch prefix","/Foo",new ServletPathSpec("/Foo/*").getPathMatch("/Foo/"));
+        assertEquals("pathMatch prefix","/Foo",new ServletPathSpec("/Foo/*").getPathMatch("/Foo"));
+        assertEquals("pathMatch suffix","/Foo/bar.ext",new ServletPathSpec("*.ext").getPathMatch("/Foo/bar.ext"));
+        assertEquals("pathMatch default","/Foo/bar.ext",new ServletPathSpec("/").getPathMatch("/Foo/bar.ext"));
+
+        assertEquals("pathMatch default","",new ServletPathSpec("/*").getPathMatch("/xxx/zzz"));
+    }
+
+    @Test
+    public void testPrefixPathSpec()
+    {
+        ServletPathSpec spec = new ServletPathSpec("/downloads/*");
+        assertEquals("Spec.pathSpec","/downloads/*",spec.getPathSpec());
+        assertEquals("Spec.pathDepth",2,spec.getPathDepth());
+
+        assertMatches(spec,"/downloads/logo.jpg");
+        assertMatches(spec,"/downloads/distribution.tar.gz");
+        assertMatches(spec,"/downloads/distribution.tgz");
+        assertMatches(spec,"/downloads/distribution.zip");
+
+        assertMatches(spec,"/downloads");
+
+        assertEquals("Spec.pathInfo","/",spec.getPathInfo("/downloads/"));
+        assertEquals("Spec.pathInfo","/distribution.zip",spec.getPathInfo("/downloads/distribution.zip"));
+        assertEquals("Spec.pathInfo","/dist/9.0/distribution.tar.gz",spec.getPathInfo("/downloads/dist/9.0/distribution.tar.gz"));
+    }
+
+    @Test
+    public void testSuffixPathSpec()
+    {
+        ServletPathSpec spec = new ServletPathSpec("*.gz");
+        assertEquals("Spec.pathSpec","*.gz",spec.getPathSpec());
+        assertEquals("Spec.pathDepth",0,spec.getPathDepth());
+
+        assertMatches(spec,"/downloads/distribution.tar.gz");
+        assertMatches(spec,"/downloads/jetty.log.gz");
+
+        assertNotMatches(spec,"/downloads/distribution.zip");
+        assertNotMatches(spec,"/downloads/distribution.tgz");
+        assertNotMatches(spec,"/abs/path");
+
+        assertEquals("Spec.pathInfo",null,spec.getPathInfo("/downloads/distribution.tar.gz"));
+    }
+}
diff --git a/jetty-websocket/websocket-server/src/test/resources/browser-debug-tool/index.html b/jetty-websocket/websocket-server/src/test/resources/browser-debug-tool/index.html
new file mode 100644
index 0000000..4ffb6e5
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/resources/browser-debug-tool/index.html
@@ -0,0 +1,58 @@
+<html>
+  <head>
+    <title>Jetty WebSocket Browser -> Server Debug Tool</title>
+    <script type="text/javascript" src="websocket.js"></script>
+    <link rel="stylesheet" type="text/css" href="main.css" media="all" >
+  </head>
+  <body>
+    jetty websocket/browser/javascript -> server debug tool #console
+    <div id="console"></div>
+    <div id="buttons">
+      <input id="connect" class="button" type="submit" name="connect" value="connect"/>
+      <input id="close" class="button" type="submit" name="close" value="close" disabled="disabled"/>
+      <input id="info" class="button" type="submit" name="info" value="info" disabled="disabled"/>
+      <input id="time" class="button" type="submit" name="time" value="time" disabled="disabled"/>
+      <input id="many" class="button" type="submit" name="many" value="many" disabled="disabled"/>
+      <input id="manythreads" class="button" type="submit" name="many" value="manythreads" disabled="disabled"/>
+      <input id="hello" class="button" type="submit" name="hello" value="hello" disabled="disabled"/>
+      <input id="there" class="button" type="submit" name="there" value="there" disabled="disabled"/>
+      <input id="json" class="button" type="submit" name="json" value="json" disabled="disabled"/>
+      <input id="twain" class="button" type="submit" name="twain" value="twain" disabled="disabled"/>
+      <input id="send10k" class="button" type="submit" name="send10k" value="send10k" disabled="disabled"/>
+      <input id="send100k" class="button" type="submit" name="send100k" value="send100k" disabled="disabled"/>
+      <input id="send1000k" class="button" type="submit" name="send1000k" value="send1000k" disabled="disabled"/>
+      <input id="send10m" class="button" type="submit" name="send10m" value="send10m" disabled="disabled"/>
+    </div>
+    <script type="text/javascript">
+    $("connect").onclick = function(event) { wstool.connect(); return false; }
+    $("close").onclick = function(event) {wstool.close(); return false; }
+    $("info").onclick = function(event) {wstool.write("info:"); return false; }
+    $("time").onclick = function(event) {wstool.write("time:"); return false; }
+    $("many").onclick = function(event) {wstool.write("many:15,300"); return false; }
+    $("manythreads").onclick = function(event) {wstool.write("manythreads:20,25,60"); return false; }
+    $("hello").onclick = function(event) {wstool.write("Hello"); return false; }
+    $("there").onclick = function(event) {wstool.write("There"); return false; }
+    $("twain").onclick = function(event) {wstool.write("@twain.txt"); return false; }
+    $("json").onclick = function(event) {wstool.write("[{\"channel\":\"/meta/subscribe\",\"subscription\":\"/chat/demo\",\"id\":\"2\",\"clientId\":\"81dwnxwbgs0h0bq8968b0a0gyl\",\"timestamp\":\"Thu,"
+            + " 12 Sep 2013 19:42:30 GMT\"},{\"channel\":\"/meta/subscribe\",\"subscription\":\"/members/demo\",\"id\":\"3\",\"clientId\":\"81dwnxwbgs0h0bq8968b0a0gyl\",\"timestamp\":\"Thu,"
+            + " 12 Sep 2013 19:42:30 GMT\"},{\"channel\":\"/chat/demo\",\"data\":{\"user\":\"ch\",\"membership\":\"join\",\"chat\":\"ch"
+            + " has joined\"},\"id\":\"4\",\"clientId\":\"81dwnxwbgs0h0bq8968b0a0gyl\",\"timestamp\":\"Thu,"
+            + " 12 Sep 2013 19:42:30 GMT\"}]"); return false; }
+    $("send10k").onclick =   function(event) {wstool.write(randomString(  10*1024)); return false;}
+    $("send100k").onclick =  function(event) {wstool.write(randomString( 100*1024)); return false;}
+    $("send1000k").onclick = function(event) {wstool.write(randomString(1000*1024)); return false;}
+    $("send10m").onclick =   function(event) {wstool.write(randomString(  10*1024*1024)); return false;}
+    
+    function randomString(len, charSet) {
+        charSet = charSet || 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789{}	":;<>,.()[]';
+        var randomString = '';
+        var charLen = charSet.length;
+        for (var i = 0; i < len; i++) {
+        	var randomPoz = Math.floor(Math.random() * charLen);
+        	randomString += charSet.substring(randomPoz,randomPoz+1);
+        }
+        return randomString;
+    }
+    </script>
+  </body>
+</html>
\ No newline at end of file
diff --git a/jetty-websocket/websocket-server/src/test/resources/browser-debug-tool/main.css b/jetty-websocket/websocket-server/src/test/resources/browser-debug-tool/main.css
new file mode 100644
index 0000000..9eebead
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/resources/browser-debug-tool/main.css
@@ -0,0 +1,29 @@
+body {
+	font-family: sans-serif;
+}
+
+div {
+    border: 0px solid black;	
+}
+
+div#console {
+    clear: both;
+    width: 40em;
+    height: 20em;
+    overflow: auto;
+    background-color: #f0f0f0;
+    padding: 4px;
+    border: 1px solid black;
+}
+
+div#console .info {
+	color: black;
+}
+
+div#console .client {
+	color: blue;
+}
+
+div#console .server {
+    color: magenta;
+}
diff --git a/jetty-websocket/websocket-server/src/test/resources/browser-debug-tool/websocket.js b/jetty-websocket/websocket-server/src/test/resources/browser-debug-tool/websocket.js
new file mode 100644
index 0000000..03f2896
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/resources/browser-debug-tool/websocket.js
@@ -0,0 +1,141 @@
+if (!window.WebSocket && window.MozWebSocket) {
+    window.WebSocket = window.MozWebSocket;
+}
+
+if (!window.WebSocket) {
+    alert("WebSocket not supported by this browser");
+}
+
+function $() {
+    return document.getElementById(arguments[0]);
+}
+function $F() {
+    return document.getElementById(arguments[0]).value;
+}
+
+function getKeyCode(ev) {
+    if (window.event)
+        return window.event.keyCode;
+    return ev.keyCode;
+}
+
+var wstool = {
+    connect : function() {
+        var location = document.location.toString().replace('http://', 'ws://')
+                .replace('https://', 'wss://');
+
+        wstool.info("Document URI: " + document.location);
+        wstool.info("WS URI: " + location);
+        
+        this._scount = 0;
+
+        try {
+            this._ws = new WebSocket(location, "tool");
+            this._ws.onopen = this._onopen;
+            this._ws.onmessage = this._onmessage;
+            this._ws.onclose = this._onclose;
+        } catch (exception) {
+            wstool.info("Connect Error: " + exception);
+        }
+    },
+
+    close : function() {
+        this._ws.close();
+    },
+    
+    _out : function(css, message) {
+        var console = $('console');
+        var spanText = document.createElement('span');
+        spanText.className = 'text ' + css;
+        spanText.innerHTML = message;
+        var lineBreak = document.createElement('br');
+        console.appendChild(spanText);
+        console.appendChild(lineBreak);
+        console.scrollTop = console.scrollHeight - console.clientHeight;
+    },
+
+    info : function(message) {
+        wstool._out("info", message);
+    },
+
+    infoc : function(message) {
+        if(message.length > 300) {
+            wstool._out("client", "[c] [big message: " + message.length + " characters]");
+        } else {
+            wstool._out("client", "[c] " + message);
+        }
+    },
+    
+    infos : function(message) {
+        this._scount++;
+        if(message.length > 300) {
+            wstool._out("server", "[s" + this._scount + "] [big message: " + message.length + " characters]");
+        } else {
+            wstool._out("server", "[s" + this._scount + "] " + message);
+        }
+    },
+
+    setState : function(enabled) {
+        $('connect').disabled = enabled;
+        $('close').disabled = !enabled;
+        $('info').disabled = !enabled;
+        $('time').disabled = !enabled;
+        $('many').disabled = !enabled;
+        $('manythreads').disabled = !enabled;
+        $('hello').disabled = !enabled;
+        $('there').disabled = !enabled;
+        $('json').disabled = !enabled;
+        $('twain').disabled = !enabled;
+        $('send10k').disabled = !enabled;
+        $('send100k').disabled = !enabled;
+        $('send1000k').disabled = !enabled;
+        $('send10m').disabled = !enabled;
+    },
+    
+    _onopen : function() {
+        wstool.setState(true);
+        wstool.info("Websocket Connected");
+    },
+
+    _send : function(message) {
+        if (this._ws) {
+            this._ws.send(message);
+            wstool.infoc(message);
+        }
+    },
+
+    write : function(text) {
+        wstool._send(text);
+    },
+
+    _onmessage : function(m) {
+        if (m.data) {
+            wstool.infos(m.data);
+        }
+    },
+
+    _onclose : function(closeEvent) {
+        this._ws = null;
+        wstool.setState(false);
+        wstool.info("Websocket Closed");
+        wstool.info("  .wasClean = " + closeEvent.wasClean);
+        
+        var codeMap = {};
+        codeMap[1000] = "(NORMAL)";
+        codeMap[1001] = "(ENDPOINT_GOING_AWAY)";
+        codeMap[1002] = "(PROTOCOL_ERROR)";
+        codeMap[1003] = "(UNSUPPORTED_DATA)";
+        codeMap[1004] = "(UNUSED/RESERVED)";
+        codeMap[1005] = "(INTERNAL/NO_CODE_PRESENT)";
+        codeMap[1006] = "(INTERNAL/ABNORMAL_CLOSE)";
+        codeMap[1007] = "(BAD_DATA)";
+        codeMap[1008] = "(POLICY_VIOLATION)";
+        codeMap[1009] = "(MESSAGE_TOO_BIG)";
+        codeMap[1010] = "(HANDSHAKE/EXT_FAILURE)";
+        codeMap[1011] = "(SERVER/UNEXPECTED_CONDITION)";
+        codeMap[1015] = "(INTERNAL/TLS_ERROR)";
+        var codeStr = codeMap[closeEvent.code];
+        wstool.info("  .code = " + closeEvent.code + "  " + codeStr);
+        wstool.info("  .reason = " + closeEvent.reason);
+    }
+};
diff --git a/jetty-websocket/websocket-server/src/test/resources/jetty-logging.properties b/jetty-websocket/websocket-server/src/test/resources/jetty-logging.properties
new file mode 100644
index 0000000..9165916
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/resources/jetty-logging.properties
@@ -0,0 +1,24 @@
+org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
+org.eclipse.jetty.LEVEL=WARN
+
+# org.eclipse.jetty.io.WriteFlusher.LEVEL=DEBUG
+# org.eclipse.jetty.websocket.LEVEL=DEBUG
+# org.eclipse.jetty.websocket.LEVEL=INFO
+# org.eclipse.jetty.websocket.common.io.LEVEL=DEBUG
+# org.eclipse.jetty.websocket.server.ab.LEVEL=DEBUG
+# org.eclipse.jetty.websocket.common.Parser.LEVEL=DEBUG
+# org.eclipse.jetty.websocket.common.Generator.LEVEL=DEBUG
+# org.eclipse.jetty.websocket.server.ab.Fuzzer.LEVEL=DEBUG
+# org.eclipse.jetty.websocket.server.blockhead.LEVEL=DEBUG
+# org.eclipse.jetty.websocket.server.helper.LEVEL=DEBUG
+
+### Show state changes on BrowserDebugTool
+# -- LEAVE THIS AT DEBUG LEVEL --
+org.eclipse.jetty.websocket.server.browser.LEVEL=DEBUG
+
+### Disabling intentional error out of RFCSocket
+org.eclipse.jetty.websocket.server.helper.RFCSocket.LEVEL=OFF
+
+### Hiding Stack Traces from various test cases
+org.eclipse.jetty.websocket.server.ab.ABSocket.STACKS=OFF
+org.eclipse.jetty.websocket.server.WebSocketCloseTest$FastFailSocket.STACKS=OFF
\ No newline at end of file
diff --git a/jetty-client/src/test/resources/keystore b/jetty-websocket/websocket-server/src/test/resources/keystore
similarity index 100%
rename from jetty-client/src/test/resources/keystore
rename to jetty-websocket/websocket-server/src/test/resources/keystore
diff --git a/jetty-websocket/websocket-servlet/pom.xml b/jetty-websocket/websocket-servlet/pom.xml
new file mode 100644
index 0000000..8f838a6
--- /dev/null
+++ b/jetty-websocket/websocket-servlet/pom.xml
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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">
+    <parent>
+        <groupId>org.eclipse.jetty.websocket</groupId>
+        <artifactId>websocket-parent</artifactId>
+        <version>9.2.13.v20150730</version>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>websocket-servlet</artifactId>
+    <name>Jetty :: Websocket :: Servlet Interface</name>
+
+    <properties>
+        <bundle-symbolic-name>${project.groupId}.servlet</bundle-symbolic-name>
+    </properties>
+    <build>
+      <plugins>
+           <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-jar-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>artifact-jar</id>
+                        <goals>
+                            <goal>jar</goal>
+                        </goals>
+                    </execution>
+                </executions>
+                <configuration>
+                    <archive>
+                        <manifestFile>target/classes/META-INF/MANIFEST.MF</manifestFile>
+                    </archive>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
+                <configuration>
+                    <instructions>
+                        <Bundle-Description>Websocket Servlet Interface</Bundle-Description>
+                        <Bundle-Classpath />
+                        <_nouses>true</_nouses>
+                        <DynamicImport-Package>org.eclipse.jetty.websocket.server.*;version="${parsedVersion.majorVersion}.${parsedVersion.minorVersion}",org.eclipse.jetty.websocket.server.pathmap.*;version="${parsedVersion.majorVersion}.${parsedVersion.minorVersion}"</DynamicImport-Package>
+                        <Require-Capability>osgi.serviceloader; filter:="(osgi.serviceloader=org.eclipse.jetty.websocket.servlet.WebSocketServletFactory)";cardinality:=multiple, osgi.extender; filter:="(osgi.extender=osgi.serviceloader.processor)"</Require-Capability>
+                    </instructions>
+                </configuration>
+            </plugin>
+      </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.eclipse.jetty.websocket</groupId>
+            <artifactId>websocket-api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+         <groupId>javax.servlet</groupId>
+         <artifactId>javax.servlet-api</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.eclipse.jetty.toolchain</groupId>
+            <artifactId>jetty-test-helper</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/ServletUpgradeRequest.java b/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/ServletUpgradeRequest.java
new file mode 100644
index 0000000..2f38e67
--- /dev/null
+++ b/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/ServletUpgradeRequest.java
@@ -0,0 +1,303 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.servlet;
+
+import java.net.HttpCookie;
+import java.net.InetSocketAddress;
+import java.net.URISyntaxException;
+import java.security.Principal;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+
+import org.eclipse.jetty.websocket.api.UpgradeRequest;
+import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
+import org.eclipse.jetty.websocket.api.util.WSURI;
+
+/**
+ * Servlet specific {@link UpgradeRequest} implementation.
+ */
+public class ServletUpgradeRequest extends UpgradeRequest
+{
+    private final UpgradeHttpServletRequest request;
+
+    public ServletUpgradeRequest(HttpServletRequest httpRequest) throws URISyntaxException
+    {
+        super(WSURI.toWebsocket(httpRequest.getRequestURL(), httpRequest.getQueryString()));
+        this.request = new UpgradeHttpServletRequest(httpRequest);
+
+        // Parse protocols.
+        Enumeration<String> requestProtocols = request.getHeaders("Sec-WebSocket-Protocol");
+        if (requestProtocols != null)
+        {
+            List<String> protocols = new ArrayList<>(2);
+            while (requestProtocols.hasMoreElements())
+            {
+                String candidate = requestProtocols.nextElement();
+                Collections.addAll(protocols, parseProtocols(candidate));
+            }
+            setSubProtocols(protocols);
+        }
+
+        // Parse extensions.
+        Enumeration<String> e = request.getHeaders("Sec-WebSocket-Extensions");
+        setExtensions(ExtensionConfig.parseEnum(e));
+
+        // Copy cookies.
+        Cookie[] requestCookies = request.getCookies();
+        if (requestCookies != null)
+        {
+            List<HttpCookie> cookies = new ArrayList<>();
+            for (Cookie requestCookie : requestCookies)
+            {
+                HttpCookie cookie = new HttpCookie(requestCookie.getName(), requestCookie.getValue());
+                // No point handling domain/path/expires/secure/httponly on client request cookies
+                cookies.add(cookie);
+            }
+            setCookies(cookies);
+        }
+
+        setHeaders(request.getHeaders());
+
+        // Copy parameters.
+        Map<String, String[]> requestParams = request.getParameterMap();
+        if (requestParams != null)
+        {
+            Map<String, List<String>> params = new HashMap<>(requestParams.size());
+            for (Map.Entry<String, String[]> entry : requestParams.entrySet())
+                params.put(entry.getKey(), Arrays.asList(entry.getValue()));
+            setParameterMap(params);
+        }
+
+        setSession(request.getSession(false));
+
+        setHttpVersion(request.getProtocol());
+        setMethod(request.getMethod());
+    }
+
+    public X509Certificate[] getCertificates()
+    {
+        return (X509Certificate[])request.getAttribute("javax.servlet.request.X509Certificate");
+    }
+
+    /**
+     * Return the underlying HttpServletRequest that existed at Upgrade time.
+     * <p/>
+     * Note: many features of the HttpServletRequest are invalid when upgraded,
+     * especially ones that deal with body content, streams, readers, and responses.
+     *
+     * @return a limited version of the underlying HttpServletRequest
+     */
+    public HttpServletRequest getHttpServletRequest()
+    {
+        return request;
+    }
+
+    /**
+     * Equivalent to {@link HttpServletRequest#getLocalAddr()}
+     *
+     * @return the local address
+     */
+    public String getLocalAddress()
+    {
+        return request.getLocalAddr();
+    }
+
+    /**
+     * Equivalent to {@link HttpServletRequest#getLocalName()}
+     *
+     * @return the local host name
+     */
+    public String getLocalHostName()
+    {
+        return request.getLocalName();
+    }
+
+    /**
+     * Equivalent to {@link HttpServletRequest#getLocalPort()}
+     *
+     * @return the local port
+     */
+    public int getLocalPort()
+    {
+        return request.getLocalPort();
+    }
+
+    /**
+     * Return a {@link InetSocketAddress} for the local socket.
+     * <p/>
+     * Warning: this can cause a DNS lookup
+     *
+     * @return the local socket address
+     */
+    public InetSocketAddress getLocalSocketAddress()
+    {
+        return new InetSocketAddress(getLocalAddress(), getLocalPort());
+    }
+
+    /**
+     * Equivalent to {@link HttpServletRequest#getLocale()}
+     *
+     * @return the preferred <code>Locale</code> for the client
+     */
+    public Locale getLocale()
+    {
+        return request.getLocale();
+    }
+
+    /**
+     * Equivalent to {@link HttpServletRequest#getLocales()}
+     *
+     * @return an Enumeration of preferred Locale objects
+     */
+    public Enumeration<Locale> getLocales()
+    {
+        return request.getLocales();
+    }
+
+    /**
+     * @deprecated use {@link #getUserPrincipal()} instead
+     */
+    @Deprecated
+    public Principal getPrincipal()
+    {
+        return getUserPrincipal();
+    }
+
+    /**
+     * Equivalent to {@link HttpServletRequest#getUserPrincipal()}
+     */
+    public Principal getUserPrincipal()
+    {
+        return request.getUserPrincipal();
+    }
+
+    /**
+     * Equivalent to {@link HttpServletRequest#getRemoteAddr()}
+     *
+     * @return the remote address
+     */
+    public String getRemoteAddress()
+    {
+        return request.getRemoteAddr();
+    }
+
+    /**
+     * Equivalent to {@link HttpServletRequest#getRemoteHost()}
+     *
+     * @return the remote host name
+     */
+    public String getRemoteHostName()
+    {
+        return request.getRemoteHost();
+    }
+
+    /**
+     * Equivalent to {@link HttpServletRequest#getRemotePort()}
+     *
+     * @return the remote port
+     */
+    public int getRemotePort()
+    {
+        return request.getRemotePort();
+    }
+
+    /**
+     * Return a {@link InetSocketAddress} for the remote socket.
+     * <p/>
+     * Warning: this can cause a DNS lookup
+     *
+     * @return the remote socket address
+     */
+    public InetSocketAddress getRemoteSocketAddress()
+    {
+        return new InetSocketAddress(getRemoteAddress(), getRemotePort());
+    }
+
+    public Map<String, Object> getServletAttributes()
+    {
+        return request.getAttributes();
+    }
+
+    public Map<String, List<String>> getServletParameters()
+    {
+        return getParameterMap();
+    }
+
+    /**
+     * Return the HttpSession if it exists.
+     * <p/>
+     * Note: this is equivalent to {@link HttpServletRequest#getSession(boolean)}
+     * and will not create a new HttpSession.
+     */
+    @Override
+    public HttpSession getSession()
+    {
+        return request.getSession(false);
+    }
+
+    public void setServletAttribute(String name, Object value)
+    {
+        request.setAttribute(name, value);
+    }
+
+    public Object getServletAttribute(String name)
+    {
+        return request.getAttribute(name);
+    }
+
+    public boolean isUserInRole(String role)
+    {
+        return request.isUserInRole(role);
+    }
+
+    public String getRequestPath()
+    {
+        // Since this can be called from a filter, we need to be smart about determining the target request path.
+        String contextPath = request.getContextPath();
+        String requestPath = request.getRequestURI();
+        if (requestPath.startsWith(contextPath))
+            requestPath = requestPath.substring(contextPath.length());
+        return requestPath;
+    }
+
+    private String[] parseProtocols(String protocol)
+    {
+        if (protocol == null)
+            return new String[0];
+        protocol = protocol.trim();
+        if (protocol.length() == 0)
+            return new String[0];
+        return protocol.split("\\s*,\\s*");
+    }
+
+    public void complete()
+    {
+        request.complete();
+    }
+}
diff --git a/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/ServletUpgradeResponse.java b/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/ServletUpgradeResponse.java
new file mode 100644
index 0000000..6b027d5
--- /dev/null
+++ b/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/ServletUpgradeResponse.java
@@ -0,0 +1,128 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.servlet;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.websocket.api.UpgradeResponse;
+import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
+
+/**
+ * Servlet Specific UpgradeResponse implementation.
+ */
+public class ServletUpgradeResponse extends UpgradeResponse
+{
+    private HttpServletResponse response;
+    private boolean extensionsNegotiated = false;
+    private boolean subprotocolNegotiated = false;
+
+    public ServletUpgradeResponse(HttpServletResponse response)
+    {
+        this.response = response;
+    }
+
+    @Override
+    public int getStatusCode()
+    {
+        return response.getStatus();
+    }
+
+    public void setStatus(int status)
+    {
+        response.setStatus(status);
+    }
+
+    @Override
+    public String getStatusReason()
+    {
+        throw new UnsupportedOperationException("Server cannot get Status Reason Message");
+    }
+
+    public boolean isCommitted()
+    {
+        if (response != null)
+        {
+            return response.isCommitted();
+        }
+        // True in all other cases
+        return true;
+    }
+
+    public boolean isExtensionsNegotiated()
+    {
+        return extensionsNegotiated;
+    }
+
+    public boolean isSubprotocolNegotiated()
+    {
+        return subprotocolNegotiated;
+    }
+
+    public void sendError(int statusCode, String message) throws IOException
+    {
+        setSuccess(false);
+        commitHeaders();
+        response.sendError(statusCode, message);
+        response = null;
+    }
+
+    @Override
+    public void sendForbidden(String message) throws IOException
+    {
+        setSuccess(false);
+        commitHeaders();
+        response.sendError(HttpServletResponse.SC_FORBIDDEN, message);
+        response = null;
+    }
+
+    @Override
+    public void setAcceptedSubProtocol(String protocol)
+    {
+        super.setAcceptedSubProtocol(protocol);
+        subprotocolNegotiated = true;
+    }
+
+    @Override
+    public void setExtensions(List<ExtensionConfig> extensions)
+    {
+        super.setExtensions(extensions);
+        extensionsNegotiated = true;
+    }
+
+    public void complete()
+    {
+        commitHeaders();
+        response = null;
+    }
+
+    private void commitHeaders()
+    {
+        // Transfer all headers to the real HTTP response
+        for (Map.Entry<String, List<String>> entry : getHeaders().entrySet())
+        {
+            for (String value : entry.getValue())
+            {
+                response.addHeader(entry.getKey(), value);
+            }
+        }
+    }
+}
diff --git a/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/UpgradeHttpServletRequest.java b/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/UpgradeHttpServletRequest.java
new file mode 100644
index 0000000..6e1133b
--- /dev/null
+++ b/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/UpgradeHttpServletRequest.java
@@ -0,0 +1,591 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.servlet;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.InetSocketAddress;
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.TreeMap;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.DispatcherType;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletInputStream;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import javax.servlet.http.HttpUpgradeHandler;
+import javax.servlet.http.Part;
+
+public class UpgradeHttpServletRequest implements HttpServletRequest
+{
+    private static final String UNSUPPORTED_WITH_WEBSOCKET_UPGRADE = "Feature unsupported with a Upgraded to WebSocket HttpServletRequest";
+
+    private HttpServletRequest request;
+    private final ServletContext context;
+    private final DispatcherType dispatcher;
+    private final String method;
+    private final String protocol;
+    private final String scheme;
+    private final boolean secure;
+    private final String requestURI;
+    private final StringBuffer requestURL;
+    private final String pathInfo;
+    private final String pathTranslated;
+    private final String servletPath;
+    private final String query;
+    private final String authType;
+    private final Cookie[] cookies;
+    private final String remoteUser;
+    private final Principal principal;
+
+    private final Map<String, List<String>> headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+    private final Map<String, String[]> parameters = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+    private final Map<String, Object> attributes = new HashMap<>(2);
+    private final List<Locale> locales = new ArrayList<>(2);
+
+    private HttpSession session;
+
+    private final InetSocketAddress localAddress;
+    private final String localName;
+    private final InetSocketAddress remoteAddress;
+    private final String remoteName;
+    private final InetSocketAddress serverAddress;
+
+    public UpgradeHttpServletRequest(HttpServletRequest httpRequest)
+    {
+        // The original request object must be held temporarily for the duration of the handshake
+        // in order to be able to implement methods such as isUserInRole() and setAttribute().
+        request = httpRequest;
+        context = httpRequest.getServletContext();
+        dispatcher = httpRequest.getDispatcherType();
+
+        method = httpRequest.getMethod();
+        protocol = httpRequest.getProtocol();
+        scheme = httpRequest.getScheme();
+        secure = httpRequest.isSecure();
+        requestURI = httpRequest.getRequestURI();
+        requestURL = httpRequest.getRequestURL();
+        pathInfo = httpRequest.getPathInfo();
+        pathTranslated = httpRequest.getPathTranslated();
+        servletPath = httpRequest.getServletPath();
+        query = httpRequest.getQueryString();
+        authType = httpRequest.getAuthType();
+        cookies = request.getCookies();
+
+        remoteUser = httpRequest.getRemoteUser();
+        principal = httpRequest.getUserPrincipal();
+
+        Enumeration<String> headerNames = httpRequest.getHeaderNames();
+        while (headerNames.hasMoreElements())
+        {
+            String name = headerNames.nextElement();
+            headers.put(name, Collections.list(httpRequest.getHeaders(name)));
+        }
+
+        parameters.putAll(request.getParameterMap());
+
+        Enumeration<String> attributeNames = httpRequest.getAttributeNames();
+        while (attributeNames.hasMoreElements())
+        {
+            String name = attributeNames.nextElement();
+            attributes.put(name, httpRequest.getAttribute(name));
+        }
+
+        localAddress = InetSocketAddress.createUnresolved(httpRequest.getLocalAddr(), httpRequest.getLocalPort());
+        localName = httpRequest.getLocalName();
+        remoteAddress = InetSocketAddress.createUnresolved(httpRequest.getRemoteAddr(), httpRequest.getRemotePort());
+        remoteName = httpRequest.getRemoteHost();
+        serverAddress = InetSocketAddress.createUnresolved(httpRequest.getServerName(), httpRequest.getServerPort());
+    }
+
+    public HttpServletRequest getHttpServletRequest()
+    {
+        return request;
+    }
+
+    @Override
+    public String getAuthType()
+    {
+        return authType;
+    }
+
+    @Override
+    public Cookie[] getCookies()
+    {
+        return cookies;
+    }
+
+    @Override
+    public String getHeader(String name)
+    {
+        List<String> values = headers.get(name);
+        if (values == null || values.isEmpty())
+            return null;
+        return values.get(0);
+    }
+
+    @Override
+    public Enumeration<String> getHeaders(String name)
+    {
+        List<String> values = headers.get(name);
+        if (values == null)
+            return Collections.emptyEnumeration();
+        return Collections.enumeration(values);
+    }
+
+    @Override
+    public Enumeration<String> getHeaderNames()
+    {
+        return Collections.enumeration(headers.keySet());
+    }
+
+    public Map<String, List<String>> getHeaders()
+    {
+        return Collections.unmodifiableMap(headers);
+    }
+
+    @Override
+    public long getDateHeader(String name)
+    {
+        // TODO
+        throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE);
+    }
+
+    @Override
+    public int getIntHeader(String name)
+    {
+        // TODO
+        throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE);
+    }
+
+    @Override
+    public String getMethod()
+    {
+        return method;
+    }
+
+    @Override
+    public String getPathInfo()
+    {
+        return pathInfo;
+    }
+
+    @Override
+    public String getPathTranslated()
+    {
+        return pathTranslated;
+    }
+
+    @Override
+    public String getContextPath()
+    {
+        return context.getContextPath();
+    }
+
+    @Override
+    public String getQueryString()
+    {
+        return query;
+    }
+
+    @Override
+    public String getRemoteUser()
+    {
+        return remoteUser;
+    }
+
+    @Override
+    public boolean isUserInRole(String role)
+    {
+        HttpServletRequest request = getHttpServletRequest();
+        if (request != null)
+            return request.isUserInRole(role);
+        throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE);
+    }
+
+    @Override
+    public Principal getUserPrincipal()
+    {
+        return principal;
+    }
+
+    @Override
+    public String getRequestURI()
+    {
+        return requestURI;
+    }
+
+    @Override
+    public StringBuffer getRequestURL()
+    {
+        return requestURL;
+    }
+
+    @Override
+    public String getServletPath()
+    {
+        return servletPath;
+    }
+
+    @Override
+    public HttpSession getSession(boolean create)
+    {
+        HttpServletRequest request = getHttpServletRequest();
+        if (request != null)
+            return session = request.getSession(create);
+        return session;
+    }
+
+    @Override
+    public HttpSession getSession()
+    {
+        return getSession(true);
+    }
+
+    @Override
+    public String getRequestedSessionId()
+    {
+        throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE);
+    }
+
+    @Override
+    public boolean isRequestedSessionIdValid()
+    {
+        throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE);
+    }
+
+    @Override
+    public boolean isRequestedSessionIdFromCookie()
+    {
+        throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE);
+    }
+
+    @Override
+    public boolean isRequestedSessionIdFromURL()
+    {
+        throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE);
+    }
+
+    @Override
+    public boolean isRequestedSessionIdFromUrl()
+    {
+        throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE);
+    }
+
+    @Override
+    public Object getAttribute(String name)
+    {
+        return attributes.get(name);
+    }
+
+    @Override
+    public Enumeration<String> getAttributeNames()
+    {
+        return Collections.enumeration(attributes.keySet());
+    }
+
+    public Map<String, Object> getAttributes()
+    {
+        return Collections.unmodifiableMap(attributes);
+    }
+
+    @Override
+    public String getParameter(String name)
+    {
+        String[] values = parameters.get(name);
+        if (values == null || values.length == 0)
+            return null;
+        return values[0];
+    }
+
+    @Override
+    public Enumeration<String> getParameterNames()
+    {
+        return Collections.enumeration(parameters.keySet());
+    }
+
+    @Override
+    public String[] getParameterValues(String name)
+    {
+        return parameters.get(name);
+    }
+
+    @Override
+    public Map<String, String[]> getParameterMap()
+    {
+        return parameters;
+    }
+
+    @Override
+    public String getProtocol()
+    {
+        return protocol;
+    }
+
+    @Override
+    public String getScheme()
+    {
+        return scheme;
+    }
+
+    @Override
+    public String getServerName()
+    {
+        return serverAddress.getHostString();
+    }
+
+    @Override
+    public int getServerPort()
+    {
+        return serverAddress.getPort();
+    }
+
+    @Override
+    public String getRemoteAddr()
+    {
+        return remoteAddress.getHostString();
+    }
+
+    @Override
+    public int getRemotePort()
+    {
+        return remoteAddress.getPort();
+    }
+
+    @Override
+    public String getRemoteHost()
+    {
+        return remoteName;
+    }
+
+    @Override
+    public void setAttribute(String name, Object value)
+    {
+        HttpServletRequest request = getHttpServletRequest();
+        if (request != null)
+            request.setAttribute(name, value);
+        attributes.put(name, value);
+    }
+
+    @Override
+    public void removeAttribute(String name)
+    {
+        HttpServletRequest request = getHttpServletRequest();
+        if (request != null)
+            request.removeAttribute(name);
+        attributes.remove(name);
+    }
+
+    @Override
+    public Locale getLocale()
+    {
+        if (locales.isEmpty())
+            return Locale.getDefault();
+        return locales.get(0);
+    }
+
+    @Override
+    public Enumeration<Locale> getLocales()
+    {
+        return Collections.enumeration(locales);
+    }
+
+    @Override
+    public boolean isSecure()
+    {
+        return secure;
+    }
+
+    @Override
+    public String getRealPath(String path)
+    {
+        return context.getRealPath(path);
+    }
+
+    @Override
+    public String getLocalName()
+    {
+        return localName;
+    }
+
+    @Override
+    public String getLocalAddr()
+    {
+        return localAddress.getHostString();
+    }
+
+    @Override
+    public int getLocalPort()
+    {
+        return localAddress.getPort();
+    }
+
+    @Override
+    public ServletContext getServletContext()
+    {
+        return context;
+    }
+
+    @Override
+    public DispatcherType getDispatcherType()
+    {
+        return dispatcher;
+    }
+
+    @Override
+    public boolean authenticate(HttpServletResponse response) throws IOException, ServletException
+    {
+        throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE);
+    }
+
+    @Override
+    public String changeSessionId()
+    {
+        throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE);
+    }
+
+    @Override
+    public AsyncContext getAsyncContext()
+    {
+        throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE);
+    }
+
+    @Override
+    public String getCharacterEncoding()
+    {
+        throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE);
+    }
+
+    @Override
+    public int getContentLength()
+    {
+        throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE);
+    }
+
+    @Override
+    public long getContentLengthLong()
+    {
+        throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE);
+    }
+
+    @Override
+    public String getContentType()
+    {
+        throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE);
+    }
+
+    @Override
+    public ServletInputStream getInputStream() throws IOException
+    {
+        throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE);
+    }
+
+    @Override
+    public Part getPart(String name) throws IOException, ServletException
+    {
+        throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE);
+    }
+
+    @Override
+    public Collection<Part> getParts() throws IOException, ServletException
+    {
+        throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE);
+    }
+
+    @Override
+    public BufferedReader getReader() throws IOException
+    {
+        throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE);
+    }
+
+    @Override
+    public RequestDispatcher getRequestDispatcher(String path)
+    {
+        throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE);
+    }
+
+    @Override
+    public boolean isAsyncStarted()
+    {
+        return false;
+    }
+
+    @Override
+    public boolean isAsyncSupported()
+    {
+        return false;
+    }
+
+    @Override
+    public void login(String username, String password) throws ServletException
+    {
+        throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE);
+    }
+
+    @Override
+    public void logout() throws ServletException
+    {
+        throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE);
+    }
+
+    @Override
+    public void setCharacterEncoding(String enc) throws UnsupportedEncodingException
+    {
+        throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE);
+    }
+
+    @Override
+    public AsyncContext startAsync() throws IllegalStateException
+    {
+        throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE);
+    }
+
+    @Override
+    public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) throws IllegalStateException
+    {
+        throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE);
+    }
+
+    @Override
+    public <T extends HttpUpgradeHandler> T upgrade(Class<T> handlerClass) throws IOException, ServletException
+    {
+        throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE);
+    }
+
+    public void complete()
+    {
+        request = null;
+    }
+}
diff --git a/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/WebSocketCreator.java b/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/WebSocketCreator.java
new file mode 100644
index 0000000..3a94c98
--- /dev/null
+++ b/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/WebSocketCreator.java
@@ -0,0 +1,42 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.servlet;
+
+/**
+ * Abstract WebSocket creator interface.
+ * <p>
+ * Should you desire filtering of the WebSocket object creation due to criteria such as origin or sub-protocol, then you will be required to implement a custom
+ * WebSocketCreator implementation.
+ * <p>
+ * This has been moved from the WebSocketServlet to a standalone class managed by the WebSocketServerFactory due to need of WebSocket {@link Extension}s that
+ * require the ability to create new websockets (such as the mux extension)
+ */
+public interface WebSocketCreator
+{
+    /**
+     * Create a websocket from the incoming request.
+     * 
+     * @param req
+     *            the request details
+     * @param resp
+     *            the response details
+     * @return a websocket object to use, or null if no websocket should be created from this request.
+     */
+    Object createWebSocket(ServletUpgradeRequest req, ServletUpgradeResponse resp);
+}
diff --git a/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/WebSocketServlet.java b/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/WebSocketServlet.java
new file mode 100644
index 0000000..2e6fe28
--- /dev/null
+++ b/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/WebSocketServlet.java
@@ -0,0 +1,169 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.servlet;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.websocket.api.WebSocketBehavior;
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+
+/**
+ * Abstract Servlet used to bridge the Servlet API to the WebSocket API.
+ * <p>
+ * To use this servlet, you will be required to register your websockets with the {@link WebSocketServletFactory} so that it can create your websockets under the
+ * appropriate conditions.
+ * <p>
+ * The most basic implementation would be as follows.
+ * 
+ * <pre>
+ * package my.example;
+ * 
+ * import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
+ * import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
+ * 
+ * public class MyEchoServlet extends WebSocketServlet
+ * {
+ *     @Override
+ *     public void configure(WebSocketServletFactory factory)
+ *     {
+ *         // set a 10 second idle timeout
+ *         factory.getPolicy().setIdleTimeout(10000);
+ *         // register my socket
+ *         factory.register(MyEchoSocket.class);
+ *     }
+ * }
+ * </pre>
+ * 
+ * Note: that only request that conforms to a "WebSocket: Upgrade" handshake request will trigger the {@link WebSocketServletFactory} handling of creating
+ * WebSockets.<br>
+ * All other requests are treated as normal servlet requests.
+ * 
+ * <p>
+ * <b>Configuration / Init-Parameters:</b><br>
+ * Note: If you use the {@link WebSocket @WebSocket} annotation, these configuration settings can be specified on a per WebSocket basis, vs a per Servlet
+ * basis.
+ * 
+ * <dl>
+ * <dt>maxIdleTime</dt>
+ * <dd>set the time in ms that a websocket may be idle before closing<br>
+ * 
+ * <dt>maxTextMessageSize</dt>
+ * <dd>set the size in UTF-8 bytes that a websocket may be accept as a Text Message before closing<br>
+ * 
+ * <dt>maxBinaryMessageSize</dt>
+ * <dd>set the size in bytes that a websocket may be accept as a Binary Message before closing<br>
+ * 
+ * <dt>inputBufferSize</dt>
+ * <dd>set the size in bytes of the buffer used to read raw bytes from the network layer<br>
+ * </dl>
+ */
+ at SuppressWarnings("serial")
+public abstract class WebSocketServlet extends HttpServlet
+{
+    private WebSocketServletFactory factory;
+
+    public abstract void configure(WebSocketServletFactory factory);
+
+    @Override
+    public void destroy()
+    {
+        factory.cleanup();
+    }
+
+    /**
+     * @see javax.servlet.GenericServlet#init()
+     */
+    @Override
+    public void init() throws ServletException
+    {
+        try
+        {
+            WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.SERVER);
+
+            String max = getInitParameter("maxIdleTime");
+            if (max != null)
+            {
+                policy.setIdleTimeout(Long.parseLong(max));
+            }
+
+            max = getInitParameter("maxTextMessageSize");
+            if (max != null)
+            {
+                policy.setMaxTextMessageSize(Integer.parseInt(max));
+            }
+
+            max = getInitParameter("maxBinaryMessageSize");
+            if (max != null)
+            {
+                policy.setMaxBinaryMessageSize(Integer.parseInt(max));
+            }
+
+            max = getInitParameter("inputBufferSize");
+            if (max != null)
+            {
+                policy.setInputBufferSize(Integer.parseInt(max));
+            }
+
+            factory = WebSocketServletFactory.Loader.create(policy);
+
+            configure(factory);
+
+            factory.init();
+            
+            getServletContext().setAttribute(WebSocketServletFactory.class.getName(),factory);
+        }
+        catch (Exception x)
+        {
+            throw new ServletException(x);
+        }
+    }
+
+    /**
+     * @see javax.servlet.http.HttpServlet#service(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
+     */
+    @Override
+    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+    {
+        if (factory.isUpgradeRequest(request,response))
+        {
+            // We have an upgrade request
+            if (factory.acceptWebSocket(request,response))
+            {
+                // We have a socket instance created
+                return;
+            }
+            // If we reach this point, it means we had an incoming request to upgrade
+            // but it was either not a proper websocket upgrade, or it was possibly rejected
+            // due to incoming request constraints (controlled by WebSocketCreator)
+            if (response.isCommitted())
+            {
+                // not much we can do at this point.
+                return;
+            }
+        }
+
+        // All other processing
+        super.service(request,response);
+    }
+}
diff --git a/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/WebSocketServletFactory.java b/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/WebSocketServletFactory.java
new file mode 100644
index 0000000..a898a21
--- /dev/null
+++ b/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/WebSocketServletFactory.java
@@ -0,0 +1,109 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.servlet;
+
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.ServiceLoader;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.websocket.api.WebSocketPolicy;
+import org.eclipse.jetty.websocket.api.extensions.ExtensionFactory;
+
+/**
+ * Basic WebSocketServletFactory for working with Jetty-based WebSocketServlets
+ */
+public interface WebSocketServletFactory
+{
+    public static class Loader
+    {
+        private static WebSocketServletFactory INSTANCE;
+
+        public static WebSocketServletFactory create(WebSocketPolicy policy) throws ClassNotFoundException, InstantiationException, IllegalAccessException
+        {
+            return load(policy).createFactory(policy);
+        }
+
+        public static WebSocketServletFactory load(WebSocketPolicy policy) throws ClassNotFoundException, InstantiationException, IllegalAccessException
+        {
+            if (INSTANCE != null)
+            {
+                return INSTANCE;
+            }
+            WebSocketServletFactory baseFactory;
+            Iterator<WebSocketServletFactory> factories = ServiceLoader.load(WebSocketServletFactory.class).iterator();
+
+            if (factories.hasNext())
+            {
+                baseFactory = factories.next();
+            }
+            else
+            {
+                // Load the default class if ServiceLoader mechanism isn't valid in this environment. (such as OSGi)
+                ClassLoader loader = Thread.currentThread().getContextClassLoader();
+                @SuppressWarnings("unchecked")
+                Class<WebSocketServletFactory> wssf = (Class<WebSocketServletFactory>)loader
+                        .loadClass("org.eclipse.jetty.websocket.server.WebSocketServerFactory");
+                baseFactory = wssf.newInstance();
+            }
+
+            INSTANCE = baseFactory;
+            return INSTANCE;
+        }
+    }
+
+    public boolean acceptWebSocket(HttpServletRequest request, HttpServletResponse response) throws IOException;
+
+    public boolean acceptWebSocket(WebSocketCreator creator, HttpServletRequest request, HttpServletResponse response) throws IOException;
+
+    public void cleanup();
+
+    public WebSocketServletFactory createFactory(WebSocketPolicy policy);
+
+    public abstract WebSocketCreator getCreator();
+
+    public abstract ExtensionFactory getExtensionFactory();
+
+    /**
+     * Get the base policy in use for WebSockets.
+     * <p>
+     * Note: individual WebSocket implementations can override some of the values in here by using the {@link WebSocket @WebSocket} annotation.
+     * 
+     * @return the base policy
+     */
+    public WebSocketPolicy getPolicy();
+
+    public void init() throws Exception;
+
+    public boolean isUpgradeRequest(HttpServletRequest request, HttpServletResponse response);
+
+    /**
+     * Register a websocket class pojo with the default {@link WebSocketCreator}.
+     * <p>
+     * Note: only required if using the default {@link WebSocketCreator} provided by this factory.
+     * 
+     * @param websocketPojo
+     *            the class to instantiate for each incoming websocket upgrade request.
+     */
+    public void register(Class<?> websocketPojo);
+
+    public abstract void setCreator(WebSocketCreator creator);
+}
diff --git a/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/package-info.java b/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/package-info.java
new file mode 100644
index 0000000..3c00607
--- /dev/null
+++ b/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/package-info.java
@@ -0,0 +1,31 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty WebSocket Servlet API
+ * <p>
+ * How to provide WebSocket servers via the Jetty WebSocket Servlet API:
+ * <ol>
+ * <li>Create your WebSocket Object</li>
+ * <li>Create your WebSocketServlet</li>
+ * <li>Register your WebSocket Object with the WebSocketServletFactory</li>
+ * <li>Wire up your WebSocketServlet to your web.xml or via Servlet 3.x annotations</li>
+ * </ol>
+ */
+package org.eclipse.jetty.websocket.servlet;
+
diff --git a/jetty-websocket/websocket-servlet/src/test/java/examples/MyAdvancedEchoCreator.java b/jetty-websocket/websocket-servlet/src/test/java/examples/MyAdvancedEchoCreator.java
new file mode 100644
index 0000000..05bc8d8
--- /dev/null
+++ b/jetty-websocket/websocket-servlet/src/test/java/examples/MyAdvancedEchoCreator.java
@@ -0,0 +1,57 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package examples;
+
+import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
+import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
+import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
+
+public class MyAdvancedEchoCreator implements WebSocketCreator
+{
+    private MyBinaryEchoSocket binaryEcho;
+    private MyEchoSocket textEcho;
+
+    public MyAdvancedEchoCreator()
+    {
+        // Create the reusable sockets
+        this.binaryEcho = new MyBinaryEchoSocket();
+        this.textEcho = new MyEchoSocket();
+    }
+
+    @Override
+    public Object createWebSocket(ServletUpgradeRequest req, ServletUpgradeResponse resp)
+    {
+        for (String subprotocol : req.getSubProtocols())
+        {
+            if ("binary".equals(subprotocol))
+            {
+                resp.setAcceptedSubProtocol(subprotocol);
+                return binaryEcho;
+            }
+            if ("text".equals(subprotocol))
+            {
+                resp.setAcceptedSubProtocol(subprotocol);
+                return textEcho;
+            }
+        }
+
+        // No valid subprotocol in request, ignore the request
+        return null;
+    }
+}
diff --git a/jetty-websocket/websocket-servlet/src/test/java/examples/MyAdvancedEchoServlet.java b/jetty-websocket/websocket-servlet/src/test/java/examples/MyAdvancedEchoServlet.java
new file mode 100644
index 0000000..e8f6f6b
--- /dev/null
+++ b/jetty-websocket/websocket-servlet/src/test/java/examples/MyAdvancedEchoServlet.java
@@ -0,0 +1,39 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package examples;
+
+import javax.servlet.annotation.WebServlet;
+
+import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
+import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
+
+ at SuppressWarnings("serial")
+ at WebServlet(name = "MyAdvanced Echo WebSocket Servlet", urlPatterns = { "/advecho" })
+public class MyAdvancedEchoServlet extends WebSocketServlet
+{
+    @Override
+    public void configure(WebSocketServletFactory factory)
+    {
+        // set a 10 second timeout
+        factory.getPolicy().setIdleTimeout(10000);
+
+        // set a custom WebSocket creator
+        factory.setCreator(new MyAdvancedEchoCreator());
+    }
+}
diff --git a/jetty-websocket/websocket-servlet/src/test/java/examples/MyAuthedCreator.java b/jetty-websocket/websocket-servlet/src/test/java/examples/MyAuthedCreator.java
new file mode 100644
index 0000000..d7d071c
--- /dev/null
+++ b/jetty-websocket/websocket-servlet/src/test/java/examples/MyAuthedCreator.java
@@ -0,0 +1,60 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package examples;
+
+import java.io.IOException;
+import java.security.Principal;
+
+import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
+import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
+import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
+
+public class MyAuthedCreator implements WebSocketCreator
+{
+    @Override
+    public Object createWebSocket(ServletUpgradeRequest req, ServletUpgradeResponse resp)
+    {
+        try
+        {
+            // Is Authenticated?
+            Principal principal = req.getUserPrincipal();
+            if (principal == null)
+            {
+                resp.sendForbidden("Not authenticated yet");
+                return null;
+            }
+
+            // Is Authorized?
+            if (!req.isUserInRole("websocket"))
+            {
+                resp.sendForbidden("Not authenticated yet");
+                return null;
+            }
+
+            // Return websocket
+            return new MyEchoSocket();
+        }
+        catch (IOException e)
+        {
+            e.printStackTrace(System.err);
+        }
+        // no websocket
+        return null;
+    }
+}
diff --git a/jetty-websocket/websocket-servlet/src/test/java/examples/MyAuthedServlet.java b/jetty-websocket/websocket-servlet/src/test/java/examples/MyAuthedServlet.java
new file mode 100644
index 0000000..9814bc9
--- /dev/null
+++ b/jetty-websocket/websocket-servlet/src/test/java/examples/MyAuthedServlet.java
@@ -0,0 +1,32 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package examples;
+
+import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
+import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
+
+ at SuppressWarnings("serial")
+public class MyAuthedServlet extends WebSocketServlet
+{
+    @Override
+    public void configure(WebSocketServletFactory factory)
+    {
+        factory.setCreator(new MyAuthedCreator());
+    }
+}
diff --git a/jetty-websocket/websocket-servlet/src/test/java/examples/MyBinaryEchoSocket.java b/jetty-websocket/websocket-servlet/src/test/java/examples/MyBinaryEchoSocket.java
new file mode 100644
index 0000000..403edb3
--- /dev/null
+++ b/jetty-websocket/websocket-servlet/src/test/java/examples/MyBinaryEchoSocket.java
@@ -0,0 +1,39 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package examples;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
+import org.eclipse.jetty.websocket.api.annotations.WebSocket;
+
+/**
+ * Echo BINARY messages
+ */
+ at WebSocket
+public class MyBinaryEchoSocket
+{
+    @OnWebSocketMessage
+    public void onWebSocketText(Session session, byte buf[], int offset, int len)
+    {
+        // Echo message back, asynchronously
+        session.getRemote().sendBytes(ByteBuffer.wrap(buf,offset,len),null);
+    }
+}
diff --git a/jetty-websocket/websocket-servlet/src/test/java/examples/MyEchoServlet.java b/jetty-websocket/websocket-servlet/src/test/java/examples/MyEchoServlet.java
new file mode 100644
index 0000000..b5289c0
--- /dev/null
+++ b/jetty-websocket/websocket-servlet/src/test/java/examples/MyEchoServlet.java
@@ -0,0 +1,39 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package examples;
+
+import javax.servlet.annotation.WebServlet;
+
+import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
+import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
+
+ at SuppressWarnings("serial")
+ at WebServlet(name = "MyEcho WebSocket Servlet", urlPatterns = { "/echo" })
+public class MyEchoServlet extends WebSocketServlet
+{
+    @Override
+    public void configure(WebSocketServletFactory factory)
+    {
+        // set a 10 second timeout
+        factory.getPolicy().setIdleTimeout(10000);
+
+        // register MyEchoSocket as the WebSocket to create on Upgrade
+        factory.register(MyEchoSocket.class);
+    }
+}
diff --git a/jetty-websocket/websocket-servlet/src/test/java/examples/MyEchoSocket.java b/jetty-websocket/websocket-servlet/src/test/java/examples/MyEchoSocket.java
new file mode 100644
index 0000000..925d825
--- /dev/null
+++ b/jetty-websocket/websocket-servlet/src/test/java/examples/MyEchoSocket.java
@@ -0,0 +1,37 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package examples;
+
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
+import org.eclipse.jetty.websocket.api.annotations.WebSocket;
+
+/**
+ * Example WebSocket, simple echo
+ */
+ at WebSocket
+public class MyEchoSocket
+{
+    @OnWebSocketMessage
+    public void onWebSocketText(Session session, String message)
+    {
+        // Echo message back, asynchronously
+        session.getRemote().sendString(message,null);
+    }
+}
diff --git a/jetty-xml/pom.xml b/jetty-xml/pom.xml
index d414043..ccda136 100644
--- a/jetty-xml/pom.xml
+++ b/jetty-xml/pom.xml
@@ -2,7 +2,7 @@
   <parent>
     <groupId>org.eclipse.jetty</groupId>
     <artifactId>jetty-project</artifactId>
-    <version>8.1.17.v20150415</version>
+    <version>9.2.13.v20150730</version>
   </parent>
   <modelVersion>4.0.0</modelVersion>
   <artifactId>jetty-xml</artifactId>
@@ -54,8 +54,8 @@
       <version>${project.version}</version>
     </dependency>
     <dependency>
-      <groupId>junit</groupId>
-      <artifactId>junit</artifactId>
+      <groupId>org.eclipse.jetty.toolchain</groupId>
+      <artifactId>jetty-test-helper</artifactId>
       <scope>test</scope>
     </dependency>
   </dependencies>
diff --git a/jetty-xml/src/main/java/org/eclipse/jetty/xml/XmlAppendable.java b/jetty-xml/src/main/java/org/eclipse/jetty/xml/XmlAppendable.java
new file mode 100644
index 0000000..e6fc84c
--- /dev/null
+++ b/jetty-xml/src/main/java/org/eclipse/jetty/xml/XmlAppendable.java
@@ -0,0 +1,166 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.xml;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.util.Map;
+import java.util.Stack;
+
+import org.eclipse.jetty.util.StringUtil;
+
+public class XmlAppendable
+{
+    private final String SPACES="                                                                 ";
+    private final Appendable _out;
+    private final int _indent;
+    private final Stack<String> _tags = new Stack<>();
+    private String _space="";
+
+    public XmlAppendable(OutputStream out,String encoding) throws IOException
+    {
+        this(new OutputStreamWriter(out,encoding),encoding);
+    }
+    
+    public XmlAppendable(Appendable out) throws IOException
+    {
+        this(out,2);
+    }
+    
+    public XmlAppendable(Appendable out,String encoding) throws IOException
+    {
+        this(out,2,encoding);
+    }
+    
+    public XmlAppendable(Appendable out, int indent) throws IOException
+    {
+        this(out,indent,"UTF-8");
+    }
+    
+    public XmlAppendable(Appendable out, int indent, String encoding) throws IOException
+    {
+        _out=out;
+        _indent=indent;
+        _out.append("<?xml version=\"1.0\" encoding=\""+encoding+"\"?>\n");
+    }
+
+    public XmlAppendable openTag(String tag, Map<String,String> attributes) throws IOException
+    {
+        _out.append(_space).append('<').append(tag);
+        attributes(attributes);
+        
+        _out.append(">\n");
+        _space=_space+SPACES.substring(0,_indent);
+        _tags.push(tag);
+        return this;
+    }
+    
+    public XmlAppendable openTag(String tag) throws IOException
+    {
+        _out.append(_space).append('<').append(tag).append(">\n");
+        _space=_space+SPACES.substring(0,_indent);
+        _tags.push(tag);
+        return this;
+    }
+    
+    public XmlAppendable content(String s) throws IOException
+    {
+        if (s!=null)
+            _out.append(StringUtil.sanitizeXmlString(s));
+        
+        return this;
+    }
+
+    public XmlAppendable cdata(String s) throws IOException
+    {
+        _out.append("<![CDATA[").append(s).append("]]>");
+        return this;
+    }
+    
+    public XmlAppendable tag(String tag) throws IOException
+    {
+        _out.append(_space).append('<').append(tag).append("/>\n");
+        return this;
+    }
+    
+    public XmlAppendable tag(String tag, Map<String,String> attributes) throws IOException
+    {
+        _out.append(_space).append('<').append(tag);
+        attributes(attributes);
+        _out.append("/>\n");
+        return this;
+    }
+    
+    public XmlAppendable tag(String tag,String content) throws IOException
+    {
+        _out.append(_space).append('<').append(tag).append('>');
+        content(content);
+        _out.append("</").append(tag).append(">\n");
+        return this;
+    }
+    
+    public XmlAppendable tagCDATA(String tag,String data) throws IOException
+    {
+        _out.append(_space).append('<').append(tag).append('>');
+        cdata(data);
+        _out.append("</").append(tag).append(">\n");
+        return this;
+    }
+    
+    public XmlAppendable tag(String tag, Map<String,String> attributes,String content) throws IOException
+    {
+        _out.append(_space).append('<').append(tag);
+        attributes(attributes);
+        _out.append('>');
+        content(content);
+        _out.append("</").append(tag).append(">\n");
+        return this;
+    }
+    
+    public XmlAppendable closeTag() throws IOException
+    {
+        if (_tags.isEmpty())
+            throw new IllegalStateException("Tags closed");
+        String tag=_tags.pop();
+        _space=_space.substring(0,_space.length()-_indent);
+        _out.append(_space).append("</").append(tag).append(">\n");
+        if (_tags.isEmpty() && _out instanceof Closeable)
+            ((Closeable)_out).close();
+        return this;
+    }
+    
+    private void attributes(Map<String,String> attributes) throws IOException
+    {
+        for (String k:attributes.keySet())
+        {
+            String v = attributes.get(k);
+            _out.append(' ').append(k).append("=\"");
+            content(v);
+            _out.append('"');
+        }
+    }
+    
+    public void literal(String xml) throws IOException
+    {
+        _out.append(xml);
+    }
+
+}
diff --git a/jetty-xml/src/main/java/org/eclipse/jetty/xml/XmlConfiguration.java b/jetty-xml/src/main/java/org/eclipse/jetty/xml/XmlConfiguration.java
index 6388be4..0accc69 100644
--- a/jetty-xml/src/main/java/org/eclipse/jetty/xml/XmlConfiguration.java
+++ b/jetty-xml/src/main/java/org/eclipse/jetty/xml/XmlConfiguration.java
@@ -38,11 +38,13 @@ import java.util.Collection;
 import java.util.Enumeration;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Properties;
 import java.util.Queue;
+import java.util.ServiceLoader;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicReference;
 
@@ -58,86 +60,69 @@ import org.eclipse.jetty.xml.XmlParser.Node;
 import org.xml.sax.InputSource;
 import org.xml.sax.SAXException;
 
-/* ------------------------------------------------------------ */
 /**
- * Configure Objects from XML. This class reads an XML file conforming to the configure.dtd DTD and uses it to configure and object by calling set, put or other
- * methods on the object.
- *
+ * <p>Configures objects from XML.</p>
+ * <p>This class reads an XML file conforming to the configure.dtd DTD
+ * and uses it to configure and object by calling set, put or other methods on the object.</p>
+ * <p>The actual XML file format may be changed (eg to spring XML) by implementing the
+ * {@link ConfigurationProcessorFactory} interface to be found by the
+ * {@link ServiceLoader} by using the DTD and first tag element in the file.
+ * Note that DTD will be null if validation is off.</p>
  * <p>
- * The actual XML file format may be changed (eg to spring XML) by implementing the {@link ConfigurationProcessorFactory} interfaces to be found by the
- * <code>ServiceLoader</code> by using the DTD and first tag element in the file. Note that DTD will be null if validation is off.
- *
+ * The configuration can be parameterised with properties that are looked up via the 
+ * Property XML element and set on the configuration via the map returned from 
+ * {@link #getProperties()}</p>
+ * <p>
+ * The configuration can create and lookup beans by ID.  If multiple configurations are used, then it
+ * is good practise to copy the entries from the {@link #getIdMap()} of a configuration to the next 
+ * configuration so that they can share an ID space for beans.</p>
  */
 public class XmlConfiguration
 {
     private static final Logger LOG = Log.getLogger(XmlConfiguration.class);
 
     private static final Class<?>[] __primitives =
-    { Boolean.TYPE, Character.TYPE, Byte.TYPE, Short.TYPE, Integer.TYPE, Long.TYPE, Float.TYPE, Double.TYPE, Void.TYPE };
+            {Boolean.TYPE, Character.TYPE, Byte.TYPE, Short.TYPE, Integer.TYPE, Long.TYPE, Float.TYPE, Double.TYPE, Void.TYPE};
 
-    private static final Class<?>[] __primitiveHolders =
-    { Boolean.class, Character.class, Byte.class, Short.class, Integer.class, Long.class, Float.class, Double.class, Void.class };
+    private static final Class<?>[] __boxedPrimitives =
+            {Boolean.class, Character.class, Byte.class, Short.class, Integer.class, Long.class, Float.class, Double.class, Void.class};
 
     private static final Class<?>[] __supportedCollections =
-    { ArrayList.class,ArrayQueue.class,HashSet.class,Queue.class,List.class,Set.class,Collection.class,};
-
-    private static final Iterable<?> __factoryLoader;
+            {ArrayList.class, ArrayQueue.class, HashSet.class, Queue.class, List.class, Set.class, Collection.class,};
 
+    private static final Iterable<ConfigurationProcessorFactory> __factoryLoader = ServiceLoader.load(ConfigurationProcessorFactory.class);
     private static final XmlParser __parser = initParser();
-
-    static
-    {
-        Iterable<?> loader=null;
-        try
-        {
-            // Use reflection to look up 1.6 service loader
-            // loader=ServiceLoader.load(ConfigurationProcessorFactory.class);
-            Class<?> slc = ClassLoader.getSystemClassLoader().loadClass("java.util.ServiceLoader");
-            Method load = slc.getMethod("load",Class.class);
-            loader=(Iterable<?>)load.invoke(null,ConfigurationProcessorFactory.class);
-        }
-        catch(Exception e)
-        {
-            LOG.ignore(e);
-        }
-        finally
-        {
-            __factoryLoader=loader;
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    private URL _url;
-    private String _dtd;
-    private ConfigurationProcessor _processor;
-    private final Map<String, Object> _idMap = new HashMap<String, Object>();
-    private final Map<String, String> _propertyMap = new HashMap<String, String>();
-
-    /* ------------------------------------------------------------ */
-    private synchronized static XmlParser initParser()
+    private static XmlParser initParser()
     {
         XmlParser parser = new XmlParser();
-        URL config60 = Loader.getResource(XmlConfiguration.class,"org/eclipse/jetty/xml/configure_6_0.dtd",true);
-        URL config76 = Loader.getResource(XmlConfiguration.class,"org/eclipse/jetty/xml/configure_7_6.dtd",true);
-        parser.redirectEntity("configure.dtd",config76);
+        URL config60 = Loader.getResource(XmlConfiguration.class, "org/eclipse/jetty/xml/configure_6_0.dtd");
+        URL config76 = Loader.getResource(XmlConfiguration.class,"org/eclipse/jetty/xml/configure_7_6.dtd");
+        URL config90 = Loader.getResource(XmlConfiguration.class,"org/eclipse/jetty/xml/configure_9_0.dtd");
+        parser.redirectEntity("configure.dtd",config90);
         parser.redirectEntity("configure_1_0.dtd",config60);
         parser.redirectEntity("configure_1_1.dtd",config60);
         parser.redirectEntity("configure_1_2.dtd",config60);
         parser.redirectEntity("configure_1_3.dtd",config60);
         parser.redirectEntity("configure_6_0.dtd",config60);
         parser.redirectEntity("configure_7_6.dtd",config76);
+        parser.redirectEntity("configure_9_0.dtd",config90);
 
-        parser.redirectEntity("http://jetty.mortbay.org/configure.dtd",config76);
-        parser.redirectEntity("http://jetty.eclipse.org/configure.dtd",config76);
-        parser.redirectEntity("http://www.eclipse.org/jetty/configure.dtd",config76);
+        parser.redirectEntity("http://jetty.mortbay.org/configure.dtd",config90);
+        parser.redirectEntity("http://jetty.eclipse.org/configure.dtd",config90);
+        parser.redirectEntity("http://www.eclipse.org/jetty/configure.dtd",config90);
 
-        parser.redirectEntity("-//Mort Bay Consulting//DTD Configure//EN",config76);
-        parser.redirectEntity("-//Jetty//Configure//EN",config76);
+        parser.redirectEntity("-//Mort Bay Consulting//DTD Configure//EN",config90);
+        parser.redirectEntity("-//Jetty//Configure//EN",config90);
 
         return parser;
     }
 
-    /* ------------------------------------------------------------ */
+    private final Map<String, Object> _idMap = new HashMap<>();
+    private final Map<String, String> _propertyMap = new HashMap<>();
+    private final URL _url;
+    private final String _dtd;
+    private ConfigurationProcessor _processor;
+
     /**
      * Reads and parses the XML configuration file.
      *
@@ -155,7 +140,6 @@ public class XmlConfiguration
         }
     }
 
-    /* ------------------------------------------------------------ */
     /**
      * Reads and parses the XML configuration string.
      *
@@ -166,17 +150,17 @@ public class XmlConfiguration
      */
     public XmlConfiguration(String configuration) throws SAXException, IOException
     {
-        configuration = "<?xml version=\"1.0\"  encoding=\"ISO-8859-1\"?>\n<!DOCTYPE Configure PUBLIC \"-//Mort Bay Consulting//DTD Configure 1.2//EN\" \"http://jetty.eclipse.org/configure_1_2.dtd\">"
+        configuration = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE Configure PUBLIC \"-//Jetty//Configure//EN\" \"http://eclipse.org/jetty/configure.dtd\">"
                 + configuration;
         InputSource source = new InputSource(new StringReader(configuration));
         synchronized (__parser)
         {
+            _url=null;
             setConfig( __parser.parse(source));
             _dtd=__parser.getDTD();
         }
     }
 
-    /* ------------------------------------------------------------ */
     /**
      * Reads and parses the XML configuration stream.
      *
@@ -189,12 +173,12 @@ public class XmlConfiguration
         InputSource source = new InputSource(configuration);
         synchronized (__parser)
         {
+            _url=null;
             setConfig(__parser.parse(source));
             _dtd=__parser.getDTD();
         }
     }
 
-    /* ------------------------------------------------------------ */
     private void setConfig(XmlParser.Node config)
     {
         if ("Configure".equals(config.getTag()))
@@ -203,19 +187,9 @@ public class XmlConfiguration
         }
         else if (__factoryLoader!=null)
         {
-            for ( Object factory : __factoryLoader)
+            for (ConfigurationProcessorFactory factory : __factoryLoader)
             {
-                // use reflection to get 1.6 methods
-                Method gcp;
-                try
-                {
-                    gcp = factory.getClass().getMethod("getConfigurationProcessor",String.class,String.class);
-                    _processor = (ConfigurationProcessor) gcp.invoke(factory,_dtd,config.getTag());
-                }
-                catch (Exception e)
-                {
-                    LOG.warn(e);
-                }
+                _processor = factory.getConfigurationProcessor(_dtd, config.getTag());
                 if (_processor!=null)
                     break;
             }
@@ -230,8 +204,21 @@ public class XmlConfiguration
         _processor.init(_url,config,this);
     }
 
-
     /* ------------------------------------------------------------ */
+    /** Get the map of ID String to Objects that is used to hold
+     * and lookup any objects by ID.  
+     * <p>
+     * A New, Get or Call XML element may have an
+     * id attribute which will cause the resulting object to be placed into
+     * this map.  A Ref XML element will lookup an object from this map.</p>
+     * <p>
+     * When chaining configuration files, it is good practise to copy the 
+     * ID entries from the ID map to the map of the next configuration, so
+     * that they may share an ID space
+     * </p>
+     *  
+     * @return A modifiable map of ID strings to Objects
+     */
     public Map<String, Object> getIdMap()
     {
         return _idMap;
@@ -239,35 +226,15 @@ public class XmlConfiguration
 
     /* ------------------------------------------------------------ */
     /**
-     * @param map the ID map
-     * @deprecated use {@link #getIdMap()}.put(...)
+     * Get the map of properties used by the Property XML element
+     * to parameterise configuration. 
+     * @return A modifiable map of properties.
      */
-    @Deprecated
-    public void setIdMap(Map<String, Object> map)
-    {
-        _idMap.clear();
-        _idMap.putAll(map);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param map the properties map
-     * @deprecated use {@link #getProperties()}.putAll(...)
-     */
-    @Deprecated
-    public void setProperties(Map<String, String> map)
-    {
-        _propertyMap.clear();
-        _propertyMap.putAll(map);
-    }
-
-    /* ------------------------------------------------------------ */
     public Map<String, String> getProperties()
     {
         return _propertyMap;
     }
 
-    /* ------------------------------------------------------------ */
     /**
      * Applies the XML configuration script to the given object.
      *
@@ -281,7 +248,6 @@ public class XmlConfiguration
         return _processor.configure(obj);
     }
 
-    /* ------------------------------------------------------------ */
     /**
      * Applies the XML configuration script.
      * If the root element of the configuration has an ID, an object is looked up by ID and its type checked
@@ -301,27 +267,25 @@ public class XmlConfiguration
      * <p>This method must be called by any {@link ConfigurationProcessor} when it 
      * creates a new instance of an object before configuring it, so that a derived 
      * XmlConfiguration class may inject default values.
-     * @param object
+     * @param object the object to initialize defaults on
      */
     public void initializeDefaults(Object object)
     {
     }
 
-    /* ------------------------------------------------------------ */
-    /* ------------------------------------------------------------ */
-    /* ------------------------------------------------------------ */
     private static class JettyXmlConfiguration implements ConfigurationProcessor
     {
+        private String _url;
         XmlParser.Node _root;
         XmlConfiguration _configuration;
 
         public void init(URL url, XmlParser.Node root, XmlConfiguration configuration)
         {
+            _url=url==null?null:url.toString();
             _root=root;
             _configuration=configuration;
         }
 
-        /* ------------------------------------------------------------ */
         public Object configure(Object obj) throws Exception
         {
             // Check the class of the object
@@ -329,13 +293,12 @@ public class XmlConfiguration
             if (oClass != null && !oClass.isInstance(obj))
             {
                 String loaders = (oClass.getClassLoader()==obj.getClass().getClassLoader())?"":"Object Class and type Class are from different loaders.";
-                throw new IllegalArgumentException("Object of class '"+obj.getClass().getCanonicalName()+"' is not of type '" + oClass.getCanonicalName()+"'. "+loaders);
+                throw new IllegalArgumentException("Object of class '"+obj.getClass().getCanonicalName()+"' is not of type '" + oClass.getCanonicalName()+"'. "+loaders+" in "+_url);
             }
             configure(obj,_root,0);
             return obj;
         }
 
-        /* ------------------------------------------------------------ */
         public Object configure() throws Exception
         {
             Class<?> oClass = nodeClass(_root);
@@ -343,30 +306,64 @@ public class XmlConfiguration
             String id = _root.getAttribute("id");
             Object obj = id == null?null:_configuration.getIdMap().get(id);
 
+            int index = 0;
             if (obj == null && oClass != null)
             {
-                obj = oClass.newInstance();
-                _configuration.initializeDefaults(obj);
-            }
+                index = _root.size();
+                Map<String, Object> namedArgMap = new HashMap<>();
 
-            if (oClass != null && !oClass.isInstance(obj))
-                throw new ClassCastException(oClass.toString());
+                List<Object> arguments = new LinkedList<>();
+                for (int i = 0; i < _root.size(); i++)
+                {
+                    Object o = _root.get(i);
+                    if (o instanceof String)
+                    {
+                        continue;
+                    }
+                    XmlParser.Node node = (XmlParser.Node)o;
 
-            configure(obj,_root,0);
+                    if (!(node.getTag().equals("Arg")))
+                    {
+                        index = i;
+                        break;
+                    }
+                    else
+                    {
+                        String namedAttribute = node.getAttribute("name");
+                        Object value=value(obj,(XmlParser.Node)o);
+                        if (namedAttribute != null)
+                            namedArgMap.put(namedAttribute,value);
+                        arguments.add(value);
+                    }
+                }
+
+                try
+                {
+                    if (namedArgMap.size() > 0)
+                        obj = TypeUtil.construct(oClass, arguments.toArray(), namedArgMap);
+                    else
+                        obj = TypeUtil.construct(oClass, arguments.toArray());
+                }
+                catch (NoSuchMethodException x)
+                {
+                    throw new IllegalStateException("No suitable constructor on " + oClass, x);
+                }
+            }
+            _configuration.initializeDefaults(obj);
+
+            configure(obj, _root, index);
             return obj;
         }
 
-        /* ------------------------------------------------------------ */
         private static Class<?> nodeClass(XmlParser.Node node) throws ClassNotFoundException
         {
             String className = node.getAttribute("class");
             if (className == null)
                 return null;
 
-            return Loader.loadClass(XmlConfiguration.class,className,true);
+            return Loader.loadClass(XmlConfiguration.class,className);
         }
 
-        /* ------------------------------------------------------------ */
         /**
          * Recursive configuration routine.
          * This method applies the nested Set, Put, Call, etc. elements to the given object.
@@ -382,6 +379,22 @@ public class XmlConfiguration
             if (id != null)
                 _configuration.getIdMap().put(id,obj);
 
+            // Object already constructed so skip any arguments
+            for (; i < cfg.size(); i++)
+            {
+                Object o = cfg.get(i);
+                if (o instanceof String)
+                    continue;
+                XmlParser.Node node = (XmlParser.Node)o;
+                if ("Arg".equals(node.getTag()))
+                {
+                    LOG.warn("Ignored arg: "+node);
+                    continue;
+                }
+                break;
+            }
+            
+            // Process real arguments
             for (; i < cfg.size(); i++)
             {
                 Object o = cfg.get(i);
@@ -392,34 +405,44 @@ public class XmlConfiguration
                 try
                 {
                     String tag = node.getTag();
-                    if ("Set".equals(tag))
-                        set(obj,node);
-                    else if ("Put".equals(tag))
-                        put(obj,node);
-                    else if ("Call".equals(tag))
-                        call(obj,node);
-                    else if ("Get".equals(tag))
-                        get(obj,node);
-                    else if ("New".equals(tag))
-                        newObj(obj,node);
-                    else if ("Array".equals(tag))
-                        newArray(obj,node);
-                    else if ("Ref".equals(tag))
-                        refObj(obj,node);
-                    else if ("Property".equals(tag))
-                        propertyObj(node);
-                    else
-                        throw new IllegalStateException("Unknown tag: " + tag);
+                    switch (tag)
+                    {
+                        case "Set":
+                            set(obj, node);
+                            break;
+                        case "Put":
+                            put(obj, node);
+                            break;
+                        case "Call":
+                            call(obj, node);
+                            break;
+                        case "Get":
+                            get(obj, node);
+                            break;
+                        case "New":
+                            newObj(obj, node);
+                            break;
+                        case "Array":
+                            newArray(obj, node);
+                            break;
+                        case "Ref":
+                            refObj(obj, node);
+                            break;
+                        case "Property":
+                            propertyObj(node);
+                            break;
+                        default:
+                            throw new IllegalStateException("Unknown tag: " + tag + " in " + _url);
+                    }
                 }
                 catch (Exception e)
                 {
-                    LOG.warn("Config error at " + node,e.toString());
+                    LOG.warn("Config error at " + node,e.toString()+" in "+_url);
                     throw e;
                 }
             }
         }
 
-        /* ------------------------------------------------------------ */
         /*
          * Call a set method. This method makes a best effort to find a matching set method. The type of the value is used to find a suitable set method by 1.
          * Trying for a trivial type match. 2. Looking for a native type match. 3. Trying all correctly named methods for an auto conversion. 4. Attempting to
@@ -456,15 +479,7 @@ public class XmlConfiguration
                 set.invoke(obj,arg);
                 return;
             }
-            catch (IllegalArgumentException e)
-            {
-                LOG.ignore(e);
-            }
-            catch (IllegalAccessException e)
-            {
-                LOG.ignore(e);
-            }
-            catch (NoSuchMethodException e)
+            catch (IllegalArgumentException | IllegalAccessException | NoSuchMethodException e)
             {
                 LOG.ignore(e);
             }
@@ -478,19 +493,7 @@ public class XmlConfiguration
                 set.invoke(obj,arg);
                 return;
             }
-            catch (NoSuchFieldException e)
-            {
-                LOG.ignore(e);
-            }
-            catch (IllegalArgumentException e)
-            {
-                LOG.ignore(e);
-            }
-            catch (IllegalAccessException e)
-            {
-                LOG.ignore(e);
-            }
-            catch (NoSuchMethodException e)
+            catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException | NoSuchMethodException e)
             {
                 LOG.ignore(e);
             }
@@ -518,7 +521,6 @@ public class XmlConfiguration
                 Class<?>[] paramTypes = sets[s].getParameterTypes();
                 if (name.equals(sets[s].getName()) && paramTypes.length == 1)
                 {
-
                     // lets try it
                     try
                     {
@@ -526,11 +528,7 @@ public class XmlConfiguration
                         sets[s].invoke(obj,arg);
                         return;
                     }
-                    catch (IllegalArgumentException e)
-                    {
-                        LOG.ignore(e);
-                    }
-                    catch (IllegalAccessException e)
+                    catch (IllegalArgumentException | IllegalAccessException e)
                     {
                         LOG.ignore(e);
                     }
@@ -563,7 +561,7 @@ public class XmlConfiguration
                         {
                             if (sClass.equals(__primitives[t]))
                             {
-                                sClass = __primitiveHolders[t];
+                                sClass = __boxedPrimitives[t];
                                 break;
                             }
                         }
@@ -574,15 +572,7 @@ public class XmlConfiguration
                     set.invoke(obj,arg);
                     return;
                 }
-                catch (NoSuchMethodException e)
-                {
-                    LOG.ignore(e);
-                }
-                catch (IllegalAccessException e)
-                {
-                    LOG.ignore(e);
-                }
-                catch (InstantiationException e)
+                catch (NoSuchMethodException | IllegalAccessException | InstantiationException e)
                 {
                     LOG.ignore(e);
                 }
@@ -605,10 +595,10 @@ public class XmlConfiguration
                 if (collectionType.isAssignableFrom(ArrayList.class))
                     collection = convertArrayToArrayList(array);
                 else if (collectionType.isAssignableFrom(HashSet.class))
-                    collection = new HashSet<Object>(convertArrayToArrayList(array));
+                    collection = new HashSet<>(convertArrayToArrayList(array));
                 else if (collectionType.isAssignableFrom(ArrayQueue.class))
                 {
-                    ArrayQueue<Object> q= new ArrayQueue<Object>();
+                    ArrayQueue<Object> q= new ArrayQueue<>();
                     q.addAll(convertArrayToArrayList(array));
                     collection=q;
                 }
@@ -618,17 +608,15 @@ public class XmlConfiguration
             return collection;
         }
 
-        /* ------------------------------------------------------------ */
         private static ArrayList<Object> convertArrayToArrayList(Object array)
         {
             int length = Array.getLength(array);
-            ArrayList<Object> list = new ArrayList<Object>(length);
+            ArrayList<Object> list = new ArrayList<>(length);
             for (int i = 0; i < length; i++)
                 list.add(Array.get(array,i));
             return list;
         }
 
-        /* ------------------------------------------------------------ */
         /*
          * Call a put method.
          *
@@ -648,7 +636,6 @@ public class XmlConfiguration
                 LOG.debug("XML " + obj + ".put(" + name + "," + value + ")");
         }
 
-        /* ------------------------------------------------------------ */
         /*
          * Call a get method. Any object returned from the call is passed to the configure method to consume the remaining elements. @param obj @param node
          *
@@ -692,7 +679,6 @@ public class XmlConfiguration
             return obj;
         }
 
-        /* ------------------------------------------------------------ */
         /*
          * Call a method. A method is selected by trying all methods with matching names and number of arguments. Any object returned from the call is passed to
          * the configure method to consume the remaining elements. Note that if this is a static call we consider only methods declared directly in the given
@@ -710,9 +696,9 @@ public class XmlConfiguration
                 oClass = obj.getClass();
             if (oClass == null)
                 throw new IllegalArgumentException(node.toString());
-
+            
             int size = 0;
-            int argi = node.size();
+            int argIndex = node.size();
             for (int i = 0; i < node.size(); i++)
             {
                 Object o = node.get(i);
@@ -720,7 +706,7 @@ public class XmlConfiguration
                     continue;
                 if (!((XmlParser.Node)o).getTag().equals("Arg"))
                 {
-                    argi = i;
+                    argIndex = i;
                     break;
                 }
                 size++;
@@ -744,7 +730,7 @@ public class XmlConfiguration
                 Object n= TypeUtil.call(oClass,method,obj,arg);
                 if (id != null)
                     _configuration.getIdMap().put(id,n);
-                configure(n,node,argi);
+                configure(n,node,argIndex);
                 return n;
             }
             catch (NoSuchMethodException e)
@@ -753,86 +739,83 @@ public class XmlConfiguration
                 ise.initCause(e);
                 throw ise;
             }
-
         }
 
-        /* ------------------------------------------------------------ */
         /*
          * Create a new value object.
          *
-         * @param obj @param node @return @exception Exception
+         * @param obj
+         * @param node
+         *
+         * @return @exception Exception
          */
         private Object newObj(Object obj, XmlParser.Node node) throws Exception
         {
             Class<?> oClass = nodeClass(node);
-            String id = node.getAttribute("id");
-            int size = 0;
-            int argi = node.size();
+            int argIndex = node.size();
+            
+            Map<String, Object> namedArgMap = new HashMap<>();
+            List<Object> arguments = new LinkedList<>();
+            XmlParser.Node child;
+
+            // Find the <Arg> elements
             for (int i = 0; i < node.size(); i++)
             {
                 Object o = node.get(i);
                 if (o instanceof String)
+                {
+                    // Skip raw String nodes
                     continue;
-                if (!((XmlParser.Node)o).getTag().equals("Arg"))
+                }
+                
+                child = (XmlParser.Node)o;
+                if(child.getTag().equals("Arg"))
                 {
-                    argi = i;
+                    String namedAttribute = child.getAttribute("name");
+                    Object value=value(obj,child);
+                    if (namedAttribute != null)
+                    {
+                        // named arguments
+                        namedArgMap.put(namedAttribute,value);
+                    }
+                    // raw arguments
+                    arguments.add(value);
+                } else {
+                    // The first non <Arg> child is the start of 
+                    // elements that configure the class, such as
+                    // <Set> and <Call> nodes
+                    argIndex = i;
                     break;
                 }
-                size++;
-            }
-
-            Object[] arg = new Object[size];
-            for (int i = 0, j = 0; j < size; i++)
-            {
-                Object o = node.get(i);
-                if (o instanceof String)
-                    continue;
-                arg[j++] = value(obj,(XmlParser.Node)o);
             }
 
             if (LOG.isDebugEnabled())
                 LOG.debug("XML new " + oClass);
 
-            // Lets just try all constructors for now
-            Constructor<?>[] constructors = oClass.getConstructors();
-            for (int c = 0; constructors != null && c < constructors.length; c++)
+            Object n;
+            try
             {
-                if (constructors[c].getParameterTypes().length != size)
-                    continue;
-
-                Object n = null;
-                boolean called = false;
-                try
-                {
-                    n = constructors[c].newInstance(arg);
-                    _configuration.initializeDefaults(n);
-                    called = true;
-                }
-                catch (IllegalAccessException e)
-                {
-                    LOG.ignore(e);
-                }
-                catch (InstantiationException e)
+                if (namedArgMap.size() > 0)
                 {
-                    LOG.ignore(e);
+                   LOG.debug("using named mapping");
+                   n = TypeUtil.construct(oClass, arguments.toArray(), namedArgMap);
                 }
-                catch (IllegalArgumentException e)
-                {
-                    LOG.ignore(e);
-                }
-                if (called)
+                else
                 {
-                    if (id != null)
-                        _configuration.getIdMap().put(id,n);
-                    configure(n,node,argi);
-                    return n;
+                    LOG.debug("using normal mapping");
+                    n = TypeUtil.construct(oClass, arguments.toArray());
                 }
             }
+            catch (NoSuchMethodException e)
+            {
+                throw new IllegalStateException("No suitable constructor: " + node + " on " + obj);
+            }
 
-            throw new IllegalStateException("No Constructor: " + node + " on " + obj);
+            _configuration.initializeDefaults(n);
+            configure(n,node,argIndex);
+            return n;
         }
 
-        /* ------------------------------------------------------------ */
         /*
          * Reference an id value object.
          *
@@ -840,21 +823,21 @@ public class XmlConfiguration
          */
         private Object refObj(Object obj, XmlParser.Node node) throws Exception
         {
-            String id = node.getAttribute("id");
-            obj = _configuration.getIdMap().get(id);
-            if (obj == null)
-                throw new IllegalStateException("No object for id=" + id);
+            String refid = node.getAttribute("refid");
+            if (refid==null)
+                refid = node.getAttribute("id");
+            obj = _configuration.getIdMap().get(refid);
+            if (obj == null && node.size()>0)
+                throw new IllegalStateException("No object for refid=" + refid);
             configure(obj,node,0);
             return obj;
         }
 
-        /* ------------------------------------------------------------ */
         /*
          * Create a new array object.
          */
         private Object newArray(Object obj, XmlParser.Node node) throws Exception
         {
-
             // Get the type
             Class<?> aClass = java.lang.Object.class;
             String type = node.getAttribute("type");
@@ -864,14 +847,21 @@ public class XmlConfiguration
                 aClass = TypeUtil.fromName(type);
                 if (aClass == null)
                 {
-                    if ("String".equals(type))
-                        aClass = java.lang.String.class;
-                    else if ("URL".equals(type))
-                        aClass = java.net.URL.class;
-                    else if ("InetAddress".equals(type))
-                        aClass = java.net.InetAddress.class;
-                    else
-                        aClass = Loader.loadClass(XmlConfiguration.class,type,true);
+                    switch (type)
+                    {
+                        case "String":
+                            aClass = String.class;
+                            break;
+                        case "URL":
+                            aClass = URL.class;
+                            break;
+                        case "InetAddress":
+                            aClass = InetAddress.class;
+                            break;
+                        default:
+                            aClass = Loader.loadClass(XmlConfiguration.class, type);
+                            break;
+                    }
                 }
             }
 
@@ -893,7 +883,6 @@ public class XmlConfiguration
             return array;
         }
 
-        /* ------------------------------------------------------------ */
         /*
          * Create a new map object.
          */
@@ -901,7 +890,7 @@ public class XmlConfiguration
         {
             String id = node.getAttribute("id");
 
-            Map<Object, Object> map = new HashMap<Object, Object>();
+            Map<Object, Object> map = new HashMap<>();
             if (id != null)
                 _configuration.getIdMap().put(id,map);
 
@@ -947,7 +936,6 @@ public class XmlConfiguration
             return map;
         }
 
-        /* ------------------------------------------------------------ */
         /*
          * Get a Property.
          *
@@ -973,8 +961,6 @@ public class XmlConfiguration
             return prop;
         }
 
-
-        /* ------------------------------------------------------------ */
         /*
          * Get the value of an element. If no value type is specified, then white space is trimmed out of the value. If it contains multiple value elements they
          * are added as strings before being converted to any specified type. @param node
@@ -1115,13 +1101,11 @@ public class XmlConfiguration
             throw new IllegalStateException("Unknown type " + type);
         }
 
-        /* ------------------------------------------------------------ */
         private static boolean isTypeMatchingClass(String type, Class<?> classToMatch)
         {
             return classToMatch.getSimpleName().equalsIgnoreCase(type) || classToMatch.getName().equals(type);
         }
 
-        /* ------------------------------------------------------------ */
         /*
          * Get the value of a single element. @param obj @param item @return @exception Exception
          */
@@ -1147,14 +1131,12 @@ public class XmlConfiguration
                 return newMap(obj,node);
             if ("Property".equals(tag))
                 return propertyObj(node);
-
             if ("SystemProperty".equals(tag))
             {
                 String name = node.getAttribute("name");
                 String defaultValue = node.getAttribute("default");
                 return System.getProperty(name,defaultValue);
             }
-
             if ("Env".equals(tag))
             {
                 String name = node.getAttribute("name");
@@ -1168,31 +1150,26 @@ public class XmlConfiguration
         }
     }
 
-    /* ------------------------------------------------------------ */
-    /* ------------------------------------------------------------ */
-    /* ------------------------------------------------------------ */
-    /* ------------------------------------------------------------ */
     /**
      * Run the XML configurations as a main application. The command line is used to obtain properties files (must be named '*.properties') and XmlConfiguration
      * files.
      * <p>
      * Any property file on the command line is added to a combined Property instance that is passed to each configuration file via
-     * {@link XmlConfiguration#setProperties(Map)}.
+     * {@link XmlConfiguration#getProperties()}.
      * <p>
      * Each configuration file on the command line is used to create a new XmlConfiguration instance and the {@link XmlConfiguration#configure()} method is used
      * to create the configured object. If the resulting object is an instance of {@link LifeCycle}, then it is started.
      * <p>
-     * Any IDs created in a configuration are passed to the next configuration file on the command line using {@link #getIdMap()} and {@link #setIdMap(Map)} .
+     * Any IDs created in a configuration are passed to the next configuration file on the command line using {@link #getIdMap()}.
      * This allows objects with IDs created in one config file to be referenced in subsequent config files on the command line.
      *
      * @param args
      *            array of property and xml configuration filenames or {@link Resource}s.
      * @throws Exception if the XML configurations cannot be run
      */
-    public static void main(final String[] args) throws Exception
+    public static void main(final String... args) throws Exception
     {
-
-        final AtomicReference<Throwable> exception = new AtomicReference<Throwable>();
+        final AtomicReference<Throwable> exception = new AtomicReference<>();
 
         AccessController.doPrivileged(new PrivilegedAction<Object>()
         {
@@ -1210,11 +1187,7 @@ public class XmlConfiguration
                         properties = (Properties)config.getMethod("getProperties").invoke(null);
                         LOG.debug("org.eclipse.jetty.start.Config properties = {}",properties);
                     }
-                    catch (NoClassDefFoundError e)
-                    {
-                        LOG.ignore(e);
-                    }
-                    catch (ClassNotFoundException e)
+                    catch (NoClassDefFoundError | ClassNotFoundException e)
                     {
                         LOG.ignore(e);
                     }
@@ -1236,23 +1209,31 @@ public class XmlConfiguration
                         }
                     }
 
-                    // For all arguments, load properties or parse XMLs
+                    // For all arguments, load properties
+                    for (String arg : args)
+                    {
+                        if (arg.indexOf('=')>=0)
+                        {
+                            int i=arg.indexOf('=');
+                            properties.put(arg.substring(0,i),arg.substring(i+1));
+                        }
+                        else if (arg.toLowerCase(Locale.ENGLISH).endsWith(".properties"))
+                            properties.load(Resource.newResource(arg).getInputStream());
+                    }
+
+                    // For all arguments, parse XMLs
                     XmlConfiguration last = null;
                     Object[] obj = new Object[args.length];
                     for (int i = 0; i < args.length; i++)
                     {
-                        if (args[i].toLowerCase(Locale.ENGLISH).endsWith(".properties"))
-                        {
-                            properties.load(Resource.newResource(args[i]).getInputStream());
-                        }
-                        else
+                        if (!args[i].toLowerCase(Locale.ENGLISH).endsWith(".properties") && (args[i].indexOf('=')<0))
                         {
                             XmlConfiguration configuration = new XmlConfiguration(Resource.newResource(args[i]).getURL());
                             if (last != null)
                                 configuration.getIdMap().putAll(last.getIdMap());
                             if (properties.size() > 0)
                             {
-                                Map<String, String> props = new HashMap<String, String>();
+                                Map<String, String> props = new HashMap<>();
                                 for (Object key : properties.keySet())
                                 {
                                     props.put(key.toString(),String.valueOf(properties.get(key)));
diff --git a/jetty-xml/src/main/java/org/eclipse/jetty/xml/XmlParser.java b/jetty-xml/src/main/java/org/eclipse/jetty/xml/XmlParser.java
index 6e6561e..9d9c007 100644
--- a/jetty-xml/src/main/java/org/eclipse/jetty/xml/XmlParser.java
+++ b/jetty-xml/src/main/java/org/eclipse/jetty/xml/XmlParser.java
@@ -51,10 +51,10 @@ import org.xml.sax.helpers.DefaultHandler;
  * XML Parser wrapper. This class wraps any standard JAXP1.1 parser with convieniant error and
  * entity handlers and a mini dom-like document tree.
  * <P>
- * By default, the parser is created as a validating parser only if xerces is present. This can be 
+ * By default, the parser is created as a validating parser only if xerces is present. This can be
  * configured by setting the "org.eclipse.jetty.xml.XmlParser.Validating" system property.
- * 
- * 
+ *
+ *
  */
 public class XmlParser
 {
@@ -89,7 +89,7 @@ public class XmlParser
     {
         setValidating(validating);
     }
-    
+
     /* ------------------------------------------------------------ */
     public void setValidating(boolean validating)
     {
@@ -98,7 +98,7 @@ public class XmlParser
             SAXParserFactory factory = SAXParserFactory.newInstance();
             factory.setValidating(validating);
             _parser = factory.newSAXParser();
-            
+
             try
             {
                 if (validating)
@@ -114,7 +114,7 @@ public class XmlParser
 
             _parser.getXMLReader().setFeature("http://xml.org/sax/features/validation", validating);
             _parser.getXMLReader().setFeature("http://xml.org/sax/features/namespaces", true);
-            _parser.getXMLReader().setFeature("http://xml.org/sax/features/namespace-prefixes", false);  
+            _parser.getXMLReader().setFeature("http://xml.org/sax/features/namespace-prefixes", false);
             try
             {
                 if (validating)
@@ -131,6 +131,12 @@ public class XmlParser
             throw new Error(e.toString());
         }
     }
+
+    /* ------------------------------------------------------------ */
+    public boolean isValidating()
+    {
+        return _parser.isValidating();
+    }
     
     /* ------------------------------------------------------------ */
     /**
@@ -145,7 +151,7 @@ public class XmlParser
 
     /* ------------------------------------------------------------ */
     /**
-     * 
+     *
      * @return Returns the xpath.
      */
     public String getXpath()
@@ -157,7 +163,7 @@ public class XmlParser
     /**
      * Set an XPath A very simple subset of xpath is supported to select a partial tree. Currently
      * only path like "/node1/nodeA | /node1/nodeB" are supported.
-     * 
+     *
      * @param xpath The xpath to set.
      */
     public void setXpath(String xpath)
@@ -179,14 +185,14 @@ public class XmlParser
      * Add a ContentHandler. Add an additional _content handler that is triggered on a tag name. SAX
      * events are passed to the ContentHandler provided from a matching start element to the
      * corresponding end element. Only a single _content handler can be registered against each tag.
-     * 
+     *
      * @param trigger Tag local or q name.
      * @param observer SAX ContentHandler
      */
     public synchronized void addContentHandler(String trigger, ContentHandler observer)
     {
         if (_observerMap == null)
-            _observerMap = new HashMap();
+            _observerMap = new HashMap<>();
         _observerMap.put(trigger, observer);
     }
 
@@ -251,6 +257,51 @@ public class XmlParser
         return doc;
     }
 
+
+    /* ------------------------------------------------------------ */
+    protected InputSource resolveEntity(String pid, String sid)
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("resolveEntity(" + pid + ", " + sid + ")");
+
+        if (sid!=null && sid.endsWith(".dtd"))
+            _dtd=sid;
+
+        URL entity = null;
+        if (pid != null)
+            entity = (URL) _redirectMap.get(pid);
+        if (entity == null)
+            entity = (URL) _redirectMap.get(sid);
+        if (entity == null)
+        {
+            String dtd = sid;
+            if (dtd.lastIndexOf('/') >= 0)
+                dtd = dtd.substring(dtd.lastIndexOf('/') + 1);
+
+            if (LOG.isDebugEnabled())
+                LOG.debug("Can't exact match entity in redirect map, trying " + dtd);
+            entity = (URL) _redirectMap.get(dtd);
+        }
+
+        if (entity != null)
+        {
+            try
+            {
+                InputStream in = entity.openStream();
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Redirected entity " + sid + " --> " + entity);
+                InputSource is = new InputSource(in);
+                is.setSystemId(sid);
+                return is;
+            }
+            catch (IOException e)
+            {
+                LOG.ignore(e);
+            }
+        }
+        return null;
+    }
+    
     /* ------------------------------------------------------------ */
     /* ------------------------------------------------------------ */
     private class NoopHandler extends DefaultHandler
@@ -278,7 +329,7 @@ public class XmlParser
                 _depth--;
         }
     }
-    
+
     /* ------------------------------------------------------------ */
     /* ------------------------------------------------------------ */
     private class Handler extends DefaultHandler
@@ -312,7 +363,7 @@ public class XmlParser
                 name = qName;
 
             Node node = new Node(_context, name, attrs);
-            
+
 
             // check if the node matches any xpaths set?
             if (_xpaths != null)
@@ -414,45 +465,7 @@ public class XmlParser
         /* ------------------------------------------------------------ */
         public InputSource resolveEntity(String pid, String sid)
         {
-            if (LOG.isDebugEnabled())
-                LOG.debug("resolveEntity(" + pid + ", " + sid + ")");
-            
-            if (sid!=null && sid.endsWith(".dtd"))
-                _dtd=sid;
-            
-            URL entity = null;
-            if (pid != null)
-                entity = (URL) _redirectMap.get(pid);
-            if (entity == null)
-                entity = (URL) _redirectMap.get(sid);
-            if (entity == null)
-            {
-                String dtd = sid;
-                if (dtd.lastIndexOf('/') >= 0)
-                    dtd = dtd.substring(dtd.lastIndexOf('/') + 1);
-
-                if (LOG.isDebugEnabled())
-                    LOG.debug("Can't exact match entity in redirect map, trying " + dtd);
-                entity = (URL) _redirectMap.get(dtd);
-            }
-
-            if (entity != null)
-            {
-                try
-                {
-                    InputStream in = entity.openStream();
-                    if (LOG.isDebugEnabled())
-                        LOG.debug("Redirected entity " + sid + " --> " + entity);
-                    InputSource is = new InputSource(in);
-                    is.setSystemId(sid);
-                    return is;
-                }
-                catch (IOException e)
-                {
-                    LOG.ignore(e);
-                }
-            }
-            return null;
+            return XmlParser.this.resolveEntity(pid,sid);
         }
     }
 
@@ -553,7 +566,7 @@ public class XmlParser
         /* ------------------------------------------------------------ */
         /**
          * Get an element attribute.
-         * 
+         *
          * @return attribute or null.
          */
         public String getAttribute(String name)
@@ -564,7 +577,7 @@ public class XmlParser
         /* ------------------------------------------------------------ */
         /**
          * Get an element attribute.
-         * 
+         *
          * @return attribute or null.
          */
         public String getAttribute(String name, String dft)
@@ -591,7 +604,7 @@ public class XmlParser
         /* ------------------------------------------------------------ */
         /**
          * Get the ith child node or content.
-         * 
+         *
          * @return Node or String.
          */
         public Object get(int i)
@@ -604,7 +617,7 @@ public class XmlParser
         /* ------------------------------------------------------------ */
         /**
          * Get the first child node with the tag.
-         * 
+         *
          * @param tag
          * @return Node or null.
          */
@@ -661,7 +674,7 @@ public class XmlParser
         /* ------------------------------------------------------------ */
         /**
          * Get a tag as a string.
-         * 
+         *
          * @param tag The tag to get
          * @param tags IF true, tags are included in the value.
          * @param trim If true, trim the value.
@@ -687,7 +700,7 @@ public class XmlParser
         /* ------------------------------------------------------------ */
         /**
          * Convert to a string.
-         * 
+         *
          * @param tag If false, only _content is shown.
          */
         public synchronized String toString(boolean tag)
@@ -700,7 +713,7 @@ public class XmlParser
         /* ------------------------------------------------------------ */
         /**
          * Convert to a string.
-         * 
+         *
          * @param tag If false, only _content is shown.
          */
         public synchronized String toString(boolean tag, boolean trim)
@@ -760,7 +773,7 @@ public class XmlParser
         /* ------------------------------------------------------------ */
         /**
          * Iterator over named child nodes.
-         * 
+         *
          * @param tag The tag of the nodes.
          * @return Iterator over all child nodes with the specified tag.
          */
diff --git a/jetty-xml/src/main/java/org/eclipse/jetty/xml/package-info.java b/jetty-xml/src/main/java/org/eclipse/jetty/xml/package-info.java
new file mode 100644
index 0000000..8c338b5
--- /dev/null
+++ b/jetty-xml/src/main/java/org/eclipse/jetty/xml/package-info.java
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Xml : IoC Mechanism for Jetty Configuration
+ */
+package org.eclipse.jetty.xml;
+
diff --git a/jetty-xml/src/main/resources/org/eclipse/jetty/xml/configure_9_0.dtd b/jetty-xml/src/main/resources/org/eclipse/jetty/xml/configure_9_0.dtd
new file mode 100644
index 0000000..542bc8c
--- /dev/null
+++ b/jetty-xml/src/main/resources/org/eclipse/jetty/xml/configure_9_0.dtd
@@ -0,0 +1,288 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+
+<!--
+This is the document type descriptor for the
+org.eclipse.jetty.xml.XmlConfiguration class.  It allows a java object to be
+configured by with a sequence of Set, Put and Call elements.  These tags are
+mapped to methods on the object to be configured as follows:
+
+  <Set  name="Test">value</Set>              ==  obj.setTest("value");
+  <Put  name="Test">value</Put>              ==  obj.put("Test","value");
+  <Call name="test"><Arg>value</Arg></Call>  ==  obj.test("value");
+
+Values themselves may be configured objects that are created with the
+<New> tag or returned from a <Call> tag.
+
+Values are matched to arguments on a best effort approach, but types
+my be specified if a match is not achieved.
+
+-->
+
+<!ENTITY % CONFIG "Set|Get|Put|Call|New|Ref|Array|Map|Property">
+<!ENTITY % VALUE "#PCDATA|Get|Call|New|Ref|Array|Map|SystemProperty|Env|Property">
+
+<!ENTITY % TYPEATTR "type CDATA #IMPLIED " > <!-- String|Character|Short|Byte|Integer|Long|Boolean|Float|Double|char|short|byte|int|long|boolean|float|double|URL|InetAddress|InetAddrPort| #classname -->
+<!ENTITY % IMPLIEDCLASSATTR "class CDATA #IMPLIED" >
+<!ENTITY % CLASSATTR "class CDATA #REQUIRED" >
+<!ENTITY % NAMEATTR "name CDATA #REQUIRED" >
+<!ENTITY % IMPLIEDNAMEATTR "name CDATA #IMPLIED" >
+<!ENTITY % DEFAULTATTR "default CDATA #IMPLIED" >
+<!ENTITY % IDATTR "id ID #IMPLIED" >
+<!ENTITY % REFATTR "refid CDATA #IMPLIED" >
+<!ENTITY % REQUIREDIDATTR "id ID #REQUIRED" >
+
+
+<!--
+Configure Element.
+This is the root element that specifies the class of object that
+can be configured:
+
+    <Configure class="com.acme.MyClass"> ... </Configure>
+-->
+<!ELEMENT Configure (Arg*,(%CONFIG;)*) >
+<!ATTLIST Configure %IMPLIEDCLASSATTR; %IDATTR; >
+
+
+<!--
+Set Element.
+This element maps to a call to a setter method or field on the current object.
+The name and optional type attributes are used to select the setter
+method. If the name given is xxx, then a setXxx method is used, or
+the xxx field is used of setXxx cannot be found.
+A Set element can contain value text and/or the value objects returned
+by other elements such as Call, New, SystemProperty, etc.
+If no value type is specified, then white
+space is trimmed out of the value. If it contains multiple value
+elements they are added as strings before being converted to any
+specified type.
+
+A Set with a class attribute is treated as a static set method invocation.
+-->
+<!ELEMENT Set (%VALUE;)* >
+<!ATTLIST Set %NAMEATTR; %TYPEATTR; %IMPLIEDCLASSATTR; >
+
+
+<!--
+Get Element.
+This element maps to a call to a getter method or field on the current object.
+The name attribute is used to select the get method.
+If the name given is xxx, then a getXxx method is used, or
+the xxx field is used if getXxx cannot be found.
+A Get element can contain other elements such as Set, Put, Call, etc.
+which act on the object returned by the get call.
+
+A Get with a class attribute is treated as a static get method or field.
+-->
+<!ELEMENT Get (%CONFIG;)* >
+<!ATTLIST Get %NAMEATTR; %IMPLIEDCLASSATTR; %IDATTR; >
+
+
+<!--
+Put Element.
+This element maps to a call to a put method on the current object,
+which must implement the Map interface. The name attribute is used
+as the put key and the optional type attribute can force the type
+of the value.
+
+A Put element can contain value text and/or value elements such as Call,
+New, SystemProperty, etc. If no value type is specified, then white
+space is trimmed out of the value. If it contains multiple value
+elements they are added as strings before being converted to any
+specified type.
+-->
+<!ELEMENT Put (%VALUE;)* >
+<!ATTLIST Put %NAMEATTR; %TYPEATTR; >
+
+
+<!--
+Call Element.
+This element maps to an arbitrary call to a method on the current object,
+The name attribute and Arg elements are used to select the method.
+
+A Call element can contain a sequence of Arg elements followed by
+a sequence of other elements such as Set, Put, Call, etc. which act on any object
+returned by the original call:
+
+ <Call id="o2" name="test">
+   <Arg>value1</Arg>
+   <Set name="Test">Value2</Set>
+ </Call>
+
+This is equivalent to:
+
+ Object o2 = o1.test("value1");
+ o2.setTest("value2");
+
+A Call with a class attribute is treated as a static call.
+-->
+<!ELEMENT Call (Arg*,(%CONFIG;)*) >
+<!ATTLIST Call %NAMEATTR; %IMPLIEDCLASSATTR; %IDATTR; >
+
+
+<!--
+Arg Element.
+This element defines a positional or optional named argument for the 
+Call and New elements. The optional type attribute can force the type 
+of the value.
+
+An Arg element can contain value text and/or value elements such as Call,
+New, SystemProperty, etc. If no value type is specified, then white
+space is trimmed out of the value. If it contains multiple value
+elements they are added as strings before being converted to any
+specified type.
+-->
+<!ELEMENT Arg (%VALUE;)* >
+<!ATTLIST Arg %TYPEATTR; %IMPLIEDNAMEATTR; >
+
+
+<!--
+New Element.
+This element allows the creation of a new object as part of a
+value for elements such as Set, Put, Arg, etc. The class attribute 
+determines the type of the new object and the contained Arg elements
+are used to select the constructor for the new object.
+
+A New element can contain a sequence of Arg elements followed by
+a sequence of elements such as Set, Put, Call, etc. elements
+which act on the new object:
+
+ <New id="o" class="com.acme.MyClass">
+   <Arg>value1</Arg>
+   <Set name="test">Value2</Set>
+ </New>
+
+This is equivalent to:
+
+ Object o = new com.acme.MyClass("value1");
+ o.setTest("Value2");
+-->
+<!ELEMENT New (Arg*,(%CONFIG;)*) >
+<!ATTLIST New %CLASSATTR; %IDATTR;>
+
+
+<!--
+Ref Element.
+This element allows a previously created object to be referenced by id.  The 
+attribute refid is used to specify the id of another object (the attribute id can
+also be used, but it's use is deprecated).
+A Ref element can contain a sequence of elements such as Set, Put, Call, etc.
+which act on the referenced object.
+
+ <Ref refid="myobject">
+   <Set name="Test">Value2</Set>
+ </New>
+-->
+<!ELEMENT Ref (%CONFIG;)* >
+<!ATTLIST Ref %IDATTR; %REFATTR;>
+
+
+<!--
+Array Element.
+This element allows the creation of a new array as part of a
+value of elements such as Set, Put, Arg, etc. The type attribute determines
+the type of the new array and the contained Item elements
+are used for each element of the array:
+
+ <Array type="java.lang.String">
+   <Item>value0</Item>
+   <Item><New class="java.lang.String"><Arg>value1</Arg></New></Item>
+ </Array>
+
+This is equivalent to:
+ String[] a = new String[] { "value0", new String("value1") };
+-->
+<!ELEMENT Array (Item*) >
+<!ATTLIST Array %TYPEATTR; %IDATTR; >
+
+
+<!--
+Map Element.
+This element allows the creation of a new map as part of a
+value of elements such as Set, Put, Arg, etc. The type attribute determines
+the type of the new array and the contained Item elements
+are used for each element of the array:
+
+ <Map>
+   <Entry>
+     <Item>keyName</Item>
+     <Item><New class="java.lang.String"><Arg>value1</Arg></New></Item>
+   </Entry>
+ </Map>
+
+This is equivalent to:
+ Map m = new HashMap();
+ m.put("keyName", new String("value1"));
+-->
+<!ELEMENT Map (Entry*) >
+<!ATTLIST Map %IDATTR; >
+<!ELEMENT Entry (Item,Item) >
+
+
+<!--
+Item Element.
+This element defines an entry for the Array or Map Entry elements.
+The optional type attribute can force the type of the value.
+
+An Item element can contain value text and/or the value object of
+elements such as Call, New, SystemProperty, etc. If no value type
+is specified, then white space is trimmed out of the value.
+If it contains multiple value elements they are added as strings
+before being converted to any specified type.
+-->
+<!ELEMENT Item (%VALUE;)* >
+<!ATTLIST Item %TYPEATTR; %IDATTR; >
+
+
+<!--
+System Property Element.
+This element allows JVM System properties to be retrieved as
+part of the value of elements such as Set, Put, Arg, etc.
+The name attribute specifies the property name and the optional
+default argument provides a default value.
+
+ <SystemProperty name="Test" default="value" />
+
+This is equivalent to:
+
+ System.getProperty("Test","value");
+-->
+<!ELEMENT SystemProperty EMPTY >
+<!ATTLIST SystemProperty %NAMEATTR; %DEFAULTATTR; %IDATTR; >
+
+
+<!--
+Environment variable Element.
+This element allows OS Environment variables to be retrieved as
+part of the value of elements such as Set, Put, Arg, etc.
+The name attribute specifies the env variable name and the optional
+default argument provides a default value.
+
+ <Env name="Test" default="value" />
+
+This is equivalent to:
+
+ String v=System.getEnv("Test");
+ if (v==null) v="value";
+
+-->
+<!ELEMENT Env EMPTY >
+<!ATTLIST Env %NAMEATTR; %DEFAULTATTR; %IDATTR; >
+
+
+<!--
+Property Element.
+This element allows arbitrary properties to be retrieved by name.
+The name attribute specifies the property name and the optional
+default argument provides a default value.
+
+A Property element can contain a sequence of elements such as Set, Put, Call, etc.
+which act on the retrieved object:
+
+ <Property name="Server">
+   <Call id="jdbcIdMgr" name="getAttribute">
+     <Arg>jdbcIdMgr</Arg>
+   </Call>
+ </Property>
+-->
+<!ELEMENT Property (%CONFIG;)* >
+<!ATTLIST Property %NAMEATTR; %DEFAULTATTR; %IDATTR; >
diff --git a/jetty-xml/src/test/java/org/eclipse/jetty/xml/AnnotatedTestConfiguration.java b/jetty-xml/src/test/java/org/eclipse/jetty/xml/AnnotatedTestConfiguration.java
new file mode 100644
index 0000000..2fa2193
--- /dev/null
+++ b/jetty-xml/src/test/java/org/eclipse/jetty/xml/AnnotatedTestConfiguration.java
@@ -0,0 +1,78 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.xml;
+
+import org.eclipse.jetty.util.annotation.Name;
+
+public class AnnotatedTestConfiguration
+{
+    private String first;
+    private String second;
+    private String third;
+    
+    AnnotatedTestConfiguration nested;
+    
+    public AnnotatedTestConfiguration(@Name("first") String first, @Name("second") String second, @Name("third") String third)
+    {
+        this.first = first;
+        this.second = second;
+        this.third = third;
+    }
+
+    public String getFirst()
+    {
+        return first;
+    }
+
+    public void setFirst(String first)
+    {
+        this.first = first;
+    }
+
+    public String getSecond()
+    {
+        return second;
+    }
+
+    public void setSecond(String second)
+    {
+        this.second = second;
+    }
+
+    public String getThird()
+    {
+        return third;
+    }
+
+    public void setThird(String third)
+    {
+        this.third = third;
+    }
+
+    public AnnotatedTestConfiguration getNested()
+    {
+        return nested;
+    }
+
+    public void setNested(AnnotatedTestConfiguration nested)
+    {
+        this.nested = nested;
+    }
+    
+}
diff --git a/jetty-xml/src/test/java/org/eclipse/jetty/xml/ConstructorArgTestClass.java b/jetty-xml/src/test/java/org/eclipse/jetty/xml/ConstructorArgTestClass.java
index c2dc44c..118b95d 100644
--- a/jetty-xml/src/test/java/org/eclipse/jetty/xml/ConstructorArgTestClass.java
+++ b/jetty-xml/src/test/java/org/eclipse/jetty/xml/ConstructorArgTestClass.java
@@ -49,7 +49,7 @@ public class ConstructorArgTestClass
         this.arrayList = arrayList;
         this.list = list;
     }
-
+    
     @SuppressWarnings("rawtypes")
     public ConstructorArgTestClass(ArrayList list)
     {
diff --git a/jetty-xml/src/test/java/org/eclipse/jetty/xml/DefaultTestConfiguration.java b/jetty-xml/src/test/java/org/eclipse/jetty/xml/DefaultTestConfiguration.java
new file mode 100644
index 0000000..f5a21db
--- /dev/null
+++ b/jetty-xml/src/test/java/org/eclipse/jetty/xml/DefaultTestConfiguration.java
@@ -0,0 +1,74 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.xml;
+
+public class DefaultTestConfiguration
+{
+    private String first;
+    private String second;
+    private String third;
+    
+    DefaultTestConfiguration nested;
+    
+    public DefaultTestConfiguration()
+    {
+        /* default constructor */
+    }
+
+    public String getFirst()
+    {
+        return first;
+    }
+
+    public void setFirst(String first)
+    {
+        this.first = first;
+    }
+
+    public String getSecond()
+    {
+        return second;
+    }
+
+    public void setSecond(String second)
+    {
+        this.second = second;
+    }
+
+    public String getThird()
+    {
+        return third;
+    }
+
+    public void setThird(String third)
+    {
+        this.third = third;
+    }
+
+    public DefaultTestConfiguration getNested()
+    {
+        return nested;
+    }
+
+    public void setNested(DefaultTestConfiguration nested)
+    {
+        this.nested = nested;
+    }
+    
+}
diff --git a/jetty-xml/src/test/java/org/eclipse/jetty/xml/TestConfiguration.java b/jetty-xml/src/test/java/org/eclipse/jetty/xml/TestConfiguration.java
index 082dfe2..c5aea67 100644
--- a/jetty-xml/src/test/java/org/eclipse/jetty/xml/TestConfiguration.java
+++ b/jetty-xml/src/test/java/org/eclipse/jetty/xml/TestConfiguration.java
@@ -26,6 +26,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
+import org.eclipse.jetty.util.annotation.Name;
 import org.junit.Ignore;
 
 @Ignore
@@ -33,6 +34,9 @@ public class TestConfiguration extends HashMap<String,Object>
 {
     public static int VALUE=77;
 
+    public final Object ID = new Object();
+    
+    public final String name;
     public TestConfiguration nested;
     public String testString="default";
     public Object testObject;
@@ -50,7 +54,19 @@ public class TestConfiguration extends HashMap<String,Object>
     private Set set;
     private ConstructorArgTestClass constructorArgTestClass;
     public Map map;
+    
 
+    
+    public TestConfiguration()
+    {
+        this("");
+    }
+    
+    public TestConfiguration(@Name("name") String n)
+    {
+        name=n;
+    }
+    
     public void setTest(Object value)
     {
         testObject=value;
@@ -63,7 +79,7 @@ public class TestConfiguration extends HashMap<String,Object>
 
     public void setPropertyTest(int value)
     {
-    	propValue=value;
+        propValue=value;
     }
 
     public TestConfiguration getNested()
@@ -172,4 +188,5 @@ public class TestConfiguration extends HashMap<String,Object>
     {
         this.map = map;
     }
+    
 }
diff --git a/jetty-xml/src/test/java/org/eclipse/jetty/xml/XmlAppendableTest.java b/jetty-xml/src/test/java/org/eclipse/jetty/xml/XmlAppendableTest.java
new file mode 100644
index 0000000..f424ecf
--- /dev/null
+++ b/jetty-xml/src/test/java/org/eclipse/jetty/xml/XmlAppendableTest.java
@@ -0,0 +1,70 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.xml;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class XmlAppendableTest
+{
+    @Test
+    public void test() throws Exception
+    {
+        StringBuilder b = new StringBuilder();
+        XmlAppendable out = new XmlAppendable(b);
+        Map<String, String> attr = new LinkedHashMap<>();
+
+        out.openTag("test");
+
+        attr.put("name", "attr value");
+        attr.put("noval", null);
+        attr.put("quotes", "'\"");
+
+        out.tag("tag");
+        out.tag("tag", attr);
+        out.tag("tag", attr, "content");
+
+        out.openTag("level1").tag("tag", "content").tag("tag", "content").closeTag();
+        out.openTag("level1", attr).openTag("level2").tag("tag", "content").tag("tag", "content").closeTag().closeTag();
+
+        out.closeTag();
+
+        String expected = "" +
+                "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+                "<test>\n" +
+                "  <tag/>\n" +
+                "  <tag name=\"attr value\" noval=\"\" quotes=\"'"\"/>\n" +
+                "  <tag name=\"attr value\" noval=\"\" quotes=\"'"\">content</tag>\n" +
+                "  <level1>\n" +
+                "    <tag>content</tag>\n" +
+                "    <tag>content</tag>\n" +
+                "  </level1>\n" +
+                "  <level1 name=\"attr value\" noval=\"\" quotes=\"'"\">\n" +
+                "    <level2>\n" +
+                "      <tag>content</tag>\n" +
+                "      <tag>content</tag>\n" +
+                "    </level2>\n" +
+                "  </level1>\n" +
+                "</test>\n";
+        Assert.assertEquals(expected, b.toString());
+    }
+}
diff --git a/jetty-xml/src/test/java/org/eclipse/jetty/xml/XmlConfigurationTest.java b/jetty-xml/src/test/java/org/eclipse/jetty/xml/XmlConfigurationTest.java
index 8df1d79..cc7e387 100644
--- a/jetty-xml/src/test/java/org/eclipse/jetty/xml/XmlConfigurationTest.java
+++ b/jetty-xml/src/test/java/org/eclipse/jetty/xml/XmlConfigurationTest.java
@@ -18,21 +18,24 @@
 
 package org.eclipse.jetty.xml;
 
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import java.io.ByteArrayInputStream;
 import java.net.URL;
+import java.nio.charset.StandardCharsets;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.concurrent.atomic.AtomicInteger;
 
 import org.junit.Assert;
+import org.junit.Ignore;
 import org.junit.Test;
 
-import static org.junit.Assert.assertEquals;
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.CoreMatchers.not;
-import static org.hamcrest.CoreMatchers.nullValue;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.assertTrue;
-
 public class XmlConfigurationTest
 {
     protected String _configure="org/eclipse/jetty/xml/configure.xml";
@@ -52,12 +55,12 @@ public class XmlConfigurationTest
     public void testPassedObject() throws Exception
     {
         TestConfiguration.VALUE=77;
-        Map<String,String> properties = new HashMap<String,String>();
+        Map<String,String> properties = new HashMap<>();
         properties.put("whatever", "xxx");
 
         URL url = XmlConfigurationTest.class.getClassLoader().getResource(_configure);
         XmlConfiguration configuration = new XmlConfiguration(url);
-        TestConfiguration tc = new TestConfiguration();
+        TestConfiguration tc = new TestConfiguration("tc");
         configuration.getProperties().putAll(properties);
         configuration.configure(tc);
 
@@ -68,18 +71,18 @@ public class XmlConfigurationTest
 
         assertEquals("Put","PutValue",tc.get("Test"));
         assertEquals("Put dft","2",tc.get("TestDft"));
-        assertEquals("Put type",new Integer(2),tc.get("TestInt"));
+        assertEquals("Put type",2,tc.get("TestInt"));
 
         assertEquals("Trim","PutValue",tc.get("Trim"));
         assertEquals("Null",null,tc.get("Null"));
         assertEquals("NullTrim",null,tc.get("NullTrim"));
 
-        assertEquals("ObjectTrim",new Double(1.2345),tc.get("ObjectTrim"));
+        assertEquals("ObjectTrim",1.2345,tc.get("ObjectTrim"));
         assertEquals("Objects","-1String",tc.get("Objects"));
         assertEquals( "ObjectsTrim", "-1String",tc.get("ObjectsTrim"));
         assertEquals( "String", "\n    PutValue\n  ",tc.get("String"));
         assertEquals( "NullString", "",tc.get("NullString"));
-        assertEquals( "WhateSpace", "\n  ",tc.get("WhiteSpace"));
+        assertEquals( "WhiteSpace", "\n  ",tc.get("WhiteSpace"));
         assertEquals( "ObjectString", "\n    1.2345\n  ",tc.get("ObjectString"));
         assertEquals( "ObjectsString", "-1String",tc.get("ObjectsString"));
         assertEquals( "ObjectsWhiteString", "-1\n  String",tc.get("ObjectsWhiteString"));
@@ -96,7 +99,7 @@ public class XmlConfigurationTest
 
         assertEquals("oa[0]","Blah",tc.oa[0]);
         assertEquals("oa[1]","1.2.3.4:5678",tc.oa[1]);
-        assertEquals("oa[2]",new Double(1.2345),tc.oa[2]);
+        assertEquals("oa[2]",1.2345,tc.oa[2]);
         assertEquals("oa[3]",null,tc.oa[3]);
 
         assertEquals("ia[0]",1,tc.ia[0]);
@@ -106,10 +109,10 @@ public class XmlConfigurationTest
 
         TestConfiguration tc2=tc.nested;
         assertTrue(tc2!=null);
-        assertEquals( "Called(bool)", new Boolean(true),tc2.get("Arg"));
+        assertEquals( "Called(bool)",true,tc2.get("Arg"));
 
         assertEquals("nested config",null,tc.get("Arg"));
-        assertEquals("nested config",new Boolean(true),tc2.get("Arg"));
+        assertEquals("nested config",true,tc2.get("Arg"));
 
         assertEquals("nested config","Call1",tc2.testObject);
         assertEquals("nested config",4,tc2.testInt);
@@ -124,7 +127,7 @@ public class XmlConfigurationTest
     public void testNewObject() throws Exception
     {
         TestConfiguration.VALUE=71;
-        Map<String,String> properties = new HashMap<String,String>();
+        Map<String,String> properties = new HashMap<>();
         properties.put("whatever", "xxx");
 
         URL url = XmlConfigurationTest.class.getClassLoader().getResource(_configure);
@@ -146,6 +149,7 @@ public class XmlConfigurationTest
         TestConfiguration tc = (TestConfiguration)configuration.configure();
 
         assertEquals(3,count.get());
+        
         assertEquals("NEW DEFAULT",tc.getTestString());
         assertEquals("nested",tc.getNested().getTestString());
         assertEquals("NEW DEFAULT",tc.getNested().getNested().getTestString());
@@ -157,18 +161,18 @@ public class XmlConfigurationTest
 
         assertEquals("Put","PutValue",tc.get("Test"));
         assertEquals("Put dft","2",tc.get("TestDft"));
-        assertEquals("Put type",new Integer(2),tc.get("TestInt"));
+        assertEquals("Put type",2,tc.get("TestInt"));
 
         assertEquals("Trim","PutValue",tc.get("Trim"));
         assertEquals("Null",null,tc.get("Null"));
         assertEquals("NullTrim",null,tc.get("NullTrim"));
 
-        assertEquals("ObjectTrim",new Double(1.2345),tc.get("ObjectTrim"));
+        assertEquals("ObjectTrim",1.2345,tc.get("ObjectTrim"));
         assertEquals("Objects","-1String",tc.get("Objects"));
         assertEquals( "ObjectsTrim", "-1String",tc.get("ObjectsTrim"));
         assertEquals( "String", "\n    PutValue\n  ",tc.get("String"));
         assertEquals( "NullString", "",tc.get("NullString"));
-        assertEquals( "WhateSpace", "\n  ",tc.get("WhiteSpace"));
+        assertEquals( "WhiteSpace", "\n  ",tc.get("WhiteSpace"));
         assertEquals( "ObjectString", "\n    1.2345\n  ",tc.get("ObjectString"));
         assertEquals( "ObjectsString", "-1String",tc.get("ObjectsString"));
         assertEquals( "ObjectsWhiteString", "-1\n  String",tc.get("ObjectsWhiteString"));
@@ -183,7 +187,7 @@ public class XmlConfigurationTest
 
         assertEquals("oa[0]","Blah",tc.oa[0]);
         assertEquals("oa[1]","1.2.3.4:5678",tc.oa[1]);
-        assertEquals("oa[2]",new Double(1.2345),tc.oa[2]);
+        assertEquals("oa[2]",1.2345,tc.oa[2]);
         assertEquals("oa[3]",null,tc.oa[3]);
 
         assertEquals("ia[0]",1,tc.ia[0]);
@@ -193,10 +197,10 @@ public class XmlConfigurationTest
 
         TestConfiguration tc2=tc.nested;
         assertTrue(tc2!=null);
-        assertEquals( "Called(bool)", new Boolean(true),tc2.get("Arg"));
+        assertEquals( "Called(bool)",true,tc2.get("Arg"));
 
         assertEquals("nested config",null,tc.get("Arg"));
-        assertEquals("nested config",new Boolean(true),tc2.get("Arg"));
+        assertEquals("nested config",true,tc2.get("Arg"));
 
         assertEquals("nested config","Call1",tc2.testObject);
         assertEquals("nested config",4,tc2.testInt);
@@ -366,4 +370,413 @@ public class XmlConfigurationTest
         xmlConfiguration.configure(tc);
         Assert.assertEquals("tc.map is has two entries as specified in the XML", 2, tc.map.size());
     }
+
+    @Test
+    public void testConstructorNamedInjection() throws Exception
+    {
+        XmlConfiguration xmlConfiguration = new XmlConfiguration("" +
+                "<Configure class=\"org.eclipse.jetty.xml.AnnotatedTestConfiguration\">" +
+                "  <Arg>arg1</Arg>  " +
+                "  <Arg>arg2</Arg>  " +
+                "  <Arg>arg3</Arg>  " +
+                "</Configure>");
+
+        AnnotatedTestConfiguration atc = (AnnotatedTestConfiguration)xmlConfiguration.configure();
+
+        Assert.assertEquals("first parameter not wired correctly","arg1", atc.getFirst());
+        Assert.assertEquals("second parameter not wired correctly","arg2", atc.getSecond());
+        Assert.assertEquals("third parameter not wired correctly","arg3", atc.getThird());
+    }
+
+    @Test
+    public void testConstructorNamedInjectionOrdered() throws Exception
+    {
+        XmlConfiguration xmlConfiguration = new XmlConfiguration("" +
+                "<Configure class=\"org.eclipse.jetty.xml.AnnotatedTestConfiguration\">" +
+                "  <Arg name=\"first\">arg1</Arg>  " +
+                "  <Arg name=\"second\">arg2</Arg>  " +
+                "  <Arg name=\"third\">arg3</Arg>  " +
+                "</Configure>");
+
+        AnnotatedTestConfiguration atc = (AnnotatedTestConfiguration)xmlConfiguration.configure();
+
+        Assert.assertEquals("first parameter not wired correctly","arg1", atc.getFirst());
+        Assert.assertEquals("second parameter not wired correctly","arg2", atc.getSecond());
+        Assert.assertEquals("third parameter not wired correctly","arg3", atc.getThird());
+    }
+
+    @Test
+    public void testConstructorNamedInjectionUnOrdered() throws Exception
+    {
+        XmlConfiguration xmlConfiguration = new XmlConfiguration("" +
+                "<Configure class=\"org.eclipse.jetty.xml.AnnotatedTestConfiguration\">" +
+                "  <Arg name=\"first\">arg1</Arg>  " +
+                "  <Arg name=\"third\">arg3</Arg>  " +
+                "  <Arg name=\"second\">arg2</Arg>  " +
+                "</Configure>");
+
+        AnnotatedTestConfiguration atc = (AnnotatedTestConfiguration)xmlConfiguration.configure();
+
+        Assert.assertEquals("first parameter not wired correctly","arg1", atc.getFirst());
+        Assert.assertEquals("second parameter not wired correctly","arg2", atc.getSecond());
+        Assert.assertEquals("third parameter not wired correctly","arg3", atc.getThird());
+    }
+
+    @Test
+    public void testConstructorNamedInjectionOrderedMixed() throws Exception
+    {
+        XmlConfiguration xmlConfiguration = new XmlConfiguration("" +
+                "<Configure class=\"org.eclipse.jetty.xml.AnnotatedTestConfiguration\">" +
+                "  <Arg name=\"first\">arg1</Arg>  " +
+                "  <Arg>arg2</Arg>  " +
+                "  <Arg name=\"third\">arg3</Arg>  " +
+                "</Configure>");
+
+        AnnotatedTestConfiguration atc = (AnnotatedTestConfiguration)xmlConfiguration.configure();
+
+        Assert.assertEquals("first parameter not wired correctly","arg1", atc.getFirst());
+        Assert.assertEquals("second parameter not wired correctly","arg2", atc.getSecond());
+        Assert.assertEquals("third parameter not wired correctly","arg3", atc.getThird());
+    }
+
+    @Test
+    public void testConstructorNamedInjectionUnorderedMixed() throws Exception
+    {
+        XmlConfiguration xmlConfiguration = new XmlConfiguration("" +
+                "<Configure class=\"org.eclipse.jetty.xml.AnnotatedTestConfiguration\">" +
+                "  <Arg name=\"third\">arg3</Arg>  " +
+                "  <Arg>arg2</Arg>  " +
+                "  <Arg name=\"first\">arg1</Arg>  " +
+                "</Configure>");
+
+        AnnotatedTestConfiguration atc = (AnnotatedTestConfiguration)xmlConfiguration.configure();
+
+        Assert.assertEquals("first parameter not wired correctly","arg1", atc.getFirst());
+        Assert.assertEquals("second parameter not wired correctly","arg2", atc.getSecond());
+        Assert.assertEquals("third parameter not wired correctly","arg3", atc.getThird());
+    }
+
+    @Test
+    public void testNestedConstructorNamedInjection() throws Exception
+    {
+        XmlConfiguration xmlConfiguration = new XmlConfiguration("" +
+                "<Configure class=\"org.eclipse.jetty.xml.AnnotatedTestConfiguration\">" +
+                "  <Arg>arg1</Arg>  " +
+                "  <Arg>arg2</Arg>  " +
+                "  <Arg>arg3</Arg>  " +
+                "  <Set name=\"nested\">  " +
+                "    <New class=\"org.eclipse.jetty.xml.AnnotatedTestConfiguration\">" +
+                "      <Arg>arg1</Arg>  " +
+                "      <Arg>arg2</Arg>  " +
+                "      <Arg>arg3</Arg>  " +
+                "    </New>" +
+                "  </Set>" +
+                "</Configure>");
+
+        AnnotatedTestConfiguration atc = (AnnotatedTestConfiguration)xmlConfiguration.configure();
+
+        Assert.assertEquals("first parameter not wired correctly","arg1", atc.getFirst());
+        Assert.assertEquals("second parameter not wired correctly","arg2", atc.getSecond());
+        Assert.assertEquals("third parameter not wired correctly","arg3", atc.getThird());
+        Assert.assertEquals("nested first parameter not wired correctly","arg1", atc.getNested().getFirst());
+        Assert.assertEquals("nested second parameter not wired correctly","arg2", atc.getNested().getSecond());
+        Assert.assertEquals("nested third parameter not wired correctly","arg3", atc.getNested().getThird());
+
+    }
+
+    @Test
+    public void testNestedConstructorNamedInjectionOrdered() throws Exception
+    {
+        XmlConfiguration xmlConfiguration = new XmlConfiguration("" +
+                "<Configure class=\"org.eclipse.jetty.xml.AnnotatedTestConfiguration\">" +
+                "  <Arg name=\"first\">arg1</Arg>  " +
+                "  <Arg name=\"second\">arg2</Arg>  " +
+                "  <Arg name=\"third\">arg3</Arg>  " +
+                "  <Set name=\"nested\">  " +
+                "    <New class=\"org.eclipse.jetty.xml.AnnotatedTestConfiguration\">" +
+                "      <Arg name=\"first\">arg1</Arg>  " +
+                "      <Arg name=\"second\">arg2</Arg>  " +
+                "      <Arg name=\"third\">arg3</Arg>  " +
+                "    </New>" +
+                "  </Set>" +
+                "</Configure>");
+
+        AnnotatedTestConfiguration atc = (AnnotatedTestConfiguration)xmlConfiguration.configure();
+
+        Assert.assertEquals("first parameter not wired correctly","arg1", atc.getFirst());
+        Assert.assertEquals("second parameter not wired correctly","arg2", atc.getSecond());
+        Assert.assertEquals("third parameter not wired correctly","arg3", atc.getThird());
+        Assert.assertEquals("nested first parameter not wired correctly","arg1", atc.getNested().getFirst());
+        Assert.assertEquals("nested second parameter not wired correctly","arg2", atc.getNested().getSecond());
+        Assert.assertEquals("nested third parameter not wired correctly","arg3", atc.getNested().getThird());
+    }
+
+    @Test
+    public void testNestedConstructorNamedInjectionUnOrdered() throws Exception
+    {
+        XmlConfiguration xmlConfiguration = new XmlConfiguration("" +
+                "<Configure class=\"org.eclipse.jetty.xml.AnnotatedTestConfiguration\">" +
+                "  <Arg name=\"first\">arg1</Arg>  " +
+                "  <Arg name=\"third\">arg3</Arg>  " +
+                "  <Arg name=\"second\">arg2</Arg>  " +
+                "  <Set name=\"nested\">  " +
+                "    <New class=\"org.eclipse.jetty.xml.AnnotatedTestConfiguration\">" +
+                "      <Arg name=\"first\">arg1</Arg>  " +
+                "      <Arg name=\"third\">arg3</Arg>  " +
+                "      <Arg name=\"second\">arg2</Arg>  " +
+                "    </New>" +
+                "  </Set>" +
+                "</Configure>");
+
+        AnnotatedTestConfiguration atc = (AnnotatedTestConfiguration)xmlConfiguration.configure();
+
+        Assert.assertEquals("first parameter not wired correctly","arg1", atc.getFirst());
+        Assert.assertEquals("second parameter not wired correctly","arg2", atc.getSecond());
+        Assert.assertEquals("third parameter not wired correctly","arg3", atc.getThird());
+        Assert.assertEquals("nested first parameter not wired correctly","arg1", atc.getNested().getFirst());
+        Assert.assertEquals("nested second parameter not wired correctly","arg2", atc.getNested().getSecond());
+        Assert.assertEquals("nested third parameter not wired correctly","arg3", atc.getNested().getThird());
+    }
+
+    @Test
+    public void testNestedConstructorNamedInjectionOrderedMixed() throws Exception
+    {
+        XmlConfiguration xmlConfiguration = new XmlConfiguration("" +
+                "<Configure class=\"org.eclipse.jetty.xml.AnnotatedTestConfiguration\">" +
+                "  <Arg name=\"first\">arg1</Arg>  " +
+                "  <Arg>arg2</Arg>  " +
+                "  <Arg name=\"third\">arg3</Arg>  " +
+                "  <Set name=\"nested\">  " +
+                "    <New class=\"org.eclipse.jetty.xml.AnnotatedTestConfiguration\">" +
+                "      <Arg name=\"first\">arg1</Arg>  " +
+                "      <Arg>arg2</Arg>  " +
+                "      <Arg name=\"third\">arg3</Arg>  " +
+                "    </New>" +
+                "  </Set>" +
+                "</Configure>");
+
+        AnnotatedTestConfiguration atc = (AnnotatedTestConfiguration)xmlConfiguration.configure();
+
+        Assert.assertEquals("first parameter not wired correctly","arg1", atc.getFirst());
+        Assert.assertEquals("second parameter not wired correctly","arg2", atc.getSecond());
+        Assert.assertEquals("third parameter not wired correctly","arg3", atc.getThird());
+        Assert.assertEquals("nested first parameter not wired correctly","arg1", atc.getNested().getFirst());
+        Assert.assertEquals("nested second parameter not wired correctly","arg2", atc.getNested().getSecond());
+        Assert.assertEquals("nested third parameter not wired correctly","arg3", atc.getNested().getThird());
+    }
+    
+    @Test
+    public void testArgumentsGetIgnoredMissingDTD() throws Exception
+    {
+        XmlConfiguration xmlConfiguration = new XmlConfiguration(new ByteArrayInputStream(("" +
+                "<Configure class=\"org.eclipse.jetty.xml.AnnotatedTestConfiguration\">" +
+                "  <Arg>arg1</Arg>  " +
+                "  <Arg>arg2</Arg>  " +
+                "  <Arg>arg3</Arg>  " +
+                "  <Set name=\"nested\">  " +
+                "    <New class=\"org.eclipse.jetty.xml.AnnotatedTestConfiguration\">\n" + 
+                "      <Arg>arg1</Arg>\n" + 
+                "      <Arg>arg2</Arg>\n" + 
+                "      <Arg>arg3</Arg>\n" + 
+                "    </New>" +
+                "  </Set>" +
+                "</Configure>").getBytes(StandardCharsets.ISO_8859_1)));
+//        XmlConfiguration xmlConfiguration = new XmlConfiguration(url);
+
+        AnnotatedTestConfiguration atc = (AnnotatedTestConfiguration)xmlConfiguration.configure();
+
+        Assert.assertEquals("first parameter not wired correctly","arg1", atc.getFirst());
+        Assert.assertEquals("second parameter not wired correctly","arg2", atc.getSecond());
+        Assert.assertEquals("third parameter not wired correctly","arg3", atc.getThird());
+        Assert.assertEquals("nested first parameter not wired correctly","arg1", atc.getNested().getFirst());
+        Assert.assertEquals("nested second parameter not wired correctly","arg2", atc.getNested().getSecond());
+        Assert.assertEquals("nested third parameter not wired correctly","arg3", atc.getNested().getThird());
+    }
+
+    @Test
+    public void testSetGetIgnoredMissingDTD() throws Exception
+    {
+        XmlConfiguration xmlConfiguration = new XmlConfiguration(new ByteArrayInputStream(("" +
+                "<Configure class=\"org.eclipse.jetty.xml.DefaultTestConfiguration\">" +
+                "  <Set name=\"first\">arg1</Set>  " +
+                "  <Set name=\"second\">arg2</Set>  " +
+                "  <Set name=\"third\">arg3</Set>  " +
+                "  <Set name=\"nested\">  " +
+                "    <New class=\"org.eclipse.jetty.xml.DefaultTestConfiguration\">\n" + 
+                "      <Set name=\"first\">arg1</Set>  " +
+                "      <Set name=\"second\">arg2</Set>  " +
+                "      <Set name=\"third\">arg3</Set>  " +
+                "    </New>" +
+                "  </Set>" +
+                "</Configure>").getBytes(StandardCharsets.UTF_8)));
+//        XmlConfiguration xmlConfiguration = new XmlConfiguration(url);
+
+        DefaultTestConfiguration atc = (DefaultTestConfiguration)xmlConfiguration.configure();
+
+        Assert.assertEquals("first parameter not wired correctly","arg1", atc.getFirst());
+        Assert.assertEquals("second parameter not wired correctly","arg2", atc.getSecond());
+        Assert.assertEquals("third parameter not wired correctly","arg3", atc.getThird());
+        Assert.assertEquals("nested first parameter not wired correctly","arg1", atc.getNested().getFirst());
+        Assert.assertEquals("nested second parameter not wired correctly","arg2", atc.getNested().getSecond());
+        Assert.assertEquals("nested third parameter not wired correctly","arg3", atc.getNested().getThird());
+    }
+
+    @Test
+    public void testNestedConstructorNamedInjectionUnorderedMixed() throws Exception
+    {
+        XmlConfiguration xmlConfiguration = new XmlConfiguration("" +
+                "<Configure class=\"org.eclipse.jetty.xml.AnnotatedTestConfiguration\">" +
+                "  <Arg name=\"third\">arg3</Arg>  " +
+                "  <Arg>arg2</Arg>  " +
+                "  <Arg name=\"first\">arg1</Arg>  " +
+                "  <Set name=\"nested\">  " +
+                "    <New class=\"org.eclipse.jetty.xml.AnnotatedTestConfiguration\">" +
+                "      <Arg name=\"third\">arg3</Arg>  " +
+                "      <Arg>arg2</Arg>  " +
+                "      <Arg name=\"first\">arg1</Arg>  " +
+                "    </New>" +
+                "  </Set>" +
+                "</Configure>");
+
+        AnnotatedTestConfiguration atc = (AnnotatedTestConfiguration)xmlConfiguration.configure();
+
+        Assert.assertEquals("first parameter not wired correctly","arg1", atc.getFirst());
+        Assert.assertEquals("second parameter not wired correctly","arg2", atc.getSecond());
+        Assert.assertEquals("third parameter not wired correctly","arg3", atc.getThird());
+        Assert.assertEquals("nested first parameter not wired correctly","arg1", atc.getNested().getFirst());
+        Assert.assertEquals("nested second parameter not wired correctly","arg2", atc.getNested().getSecond());
+        Assert.assertEquals("nested third parameter not wired correctly","arg3", atc.getNested().getThird());
+    }
+
+    public static class NativeHolder
+    {
+        private boolean _boolean;
+        private int _integer;
+        private float _float;
+
+        public boolean getBoolean()
+        {
+            return _boolean;
+        }
+
+        public void setBoolean(boolean value)
+        {
+            this._boolean = value;
+        }
+
+        public int getInteger()
+        {
+            return _integer;
+        }
+
+        public void setInteger(int integer)
+        {
+            _integer = integer;
+        }
+
+        public float getFloat()
+        {
+            return _float;
+        }
+
+        public void setFloat(float f)
+        {
+            _float = f;
+        }
+        
+    }
+    
+    @Test
+    public void testSetBooleanTrue() throws Exception
+    {
+        XmlConfiguration xmlConfiguration = new XmlConfiguration("" +
+                "<Configure class=\"org.eclipse.jetty.xml.XmlConfigurationTest$NativeHolder\">" +
+                "  <Set name=\"boolean\">true</Set>" +
+                "</Configure>");
+
+        NativeHolder bh = (NativeHolder)xmlConfiguration.configure();
+        Assert.assertTrue(bh.getBoolean());
+    }
+    
+    @Test
+    public void testSetBooleanFalse() throws Exception
+    {
+        XmlConfiguration xmlConfiguration = new XmlConfiguration("" +
+                "<Configure class=\"org.eclipse.jetty.xml.XmlConfigurationTest$NativeHolder\">" +
+                "  <Set name=\"boolean\">false</Set>" +
+                "</Configure>");
+
+        NativeHolder bh = (NativeHolder)xmlConfiguration.configure();
+        Assert.assertFalse(bh.getBoolean());
+    }
+    
+    @Test
+    @Ignore
+    public void testSetBadBoolean() throws Exception
+    {
+        XmlConfiguration xmlConfiguration = new XmlConfiguration("" +
+                "<Configure class=\"org.eclipse.jetty.xml.XmlConfigurationTest$NativeHolder\">" +
+                "  <Set name=\"boolean\">tru</Set>" +
+                "</Configure>");
+
+        NativeHolder bh = (NativeHolder)xmlConfiguration.configure();
+        Assert.assertTrue(bh.getBoolean());
+    }
+    
+    @Test
+    public void testSetBadInteger() throws Exception
+    {
+        XmlConfiguration xmlConfiguration = new XmlConfiguration("" +
+                "<Configure class=\"org.eclipse.jetty.xml.XmlConfigurationTest$NativeHolder\">" +
+                "  <Set name=\"integer\">bad</Set>" +
+                "</Configure>");
+
+        try
+        {
+            xmlConfiguration.configure();
+            Assert.fail();
+        }
+        catch (Exception e)
+        {
+            
+        }
+    }
+    
+    @Test
+    public void testSetBadExtraInteger() throws Exception
+    {
+        XmlConfiguration xmlConfiguration = new XmlConfiguration("" +
+                "<Configure class=\"org.eclipse.jetty.xml.XmlConfigurationTest$NativeHolder\">" +
+                "  <Set name=\"integer\">100 bas</Set>" +
+                "</Configure>");
+
+        try
+        {
+            xmlConfiguration.configure();
+            Assert.fail();
+        }
+        catch (Exception e)
+        {
+            
+        }
+    }
+    
+    @Test
+    public void testSetBadFloatInteger() throws Exception
+    {
+        XmlConfiguration xmlConfiguration = new XmlConfiguration("" +
+                "<Configure class=\"org.eclipse.jetty.xml.XmlConfigurationTest$NativeHolder\">" +
+                "  <Set name=\"integer\">1.5</Set>" +
+                "</Configure>");
+
+        try
+        {
+            xmlConfiguration.configure();
+            Assert.fail();
+        }
+        catch (Exception e)
+        {
+            
+        }
+    }
 }
diff --git a/jetty-xml/src/test/resources/jetty-logging.properties b/jetty-xml/src/test/resources/jetty-logging.properties
new file mode 100644
index 0000000..1759774
--- /dev/null
+++ b/jetty-xml/src/test/resources/jetty-logging.properties
@@ -0,0 +1,3 @@
+org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
+org.eclipse.jetty.xml.LEVEL=WARN
+org.eclipse.jetty.util.LEVEL=WARN
\ No newline at end of file
diff --git a/jetty-xml/src/test/resources/org/eclipse/jetty/xml/configure.xml b/jetty-xml/src/test/resources/org/eclipse/jetty/xml/configure.xml
index c1bb61e..46c8571 100644
--- a/jetty-xml/src/test/resources/org/eclipse/jetty/xml/configure.xml
+++ b/jetty-xml/src/test/resources/org/eclipse/jetty/xml/configure.xml
@@ -1,7 +1,8 @@
-<?xml version="1.0"  encoding="ISO-8859-1"?> 
-<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
 
 <Configure class="org.eclipse.jetty.xml.TestConfiguration">
+  <Arg name="name">name</Arg>
 
   <Set name="Test">SetValue</Set>
   <Set name="Test" type="int"><Property name="does.not.exist" default="2"/></Set>
diff --git a/jetty-xml/src/test/resources/org/eclipse/jetty/xml/mortbay.xml b/jetty-xml/src/test/resources/org/eclipse/jetty/xml/mortbay.xml
index 2607bf8..e1c1808 100644
--- a/jetty-xml/src/test/resources/org/eclipse/jetty/xml/mortbay.xml
+++ b/jetty-xml/src/test/resources/org/eclipse/jetty/xml/mortbay.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0"  encoding="ISO-8859-1"?>
+<?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://jetty.mortbay.org/configure.dtd">
 <Configure class="java.lang.Object">
 </Configure>
diff --git a/pom.xml b/pom.xml
index 37e21b0..bb6a400 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,25 +3,30 @@
   <parent>
     <groupId>org.eclipse.jetty</groupId>
     <artifactId>jetty-parent</artifactId>
-    <version>20</version>
+    <version>23</version>
   </parent>
   <artifactId>jetty-project</artifactId>
-  <version>8.1.17.v20150415</version>
+  <version>9.2.13.v20150730</version>
   <name>Jetty :: Project</name>
   <url>http://www.eclipse.org/jetty</url>
   <packaging>pom</packaging>
   <properties>
-    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
     <jetty.url>http://www.eclipse.org/jetty</jetty.url>
-    <orbit-servlet-api-version>3.0.0.v201112011016</orbit-servlet-api-version>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
     <build-support-version>1.1</build-support-version>
-    <slf4j-version>1.6.1</slf4j-version>
+    <slf4j-version>1.6.6</slf4j-version>
     <jetty-test-policy-version>1.2</jetty-test-policy-version>
+    <npn.api.version>1.1.1.v20141010</npn.api.version>
+    <alpn.api.version>1.1.2.v20150522</alpn.api.version>
+    <!-- default values are unsupported, but required to be defined for reactor sanity reasons -->
+    <npn.version>undefined</npn.version>
+    <alpn.version>undefined</alpn.version>
   </properties>
   <scm>
     <connection>scm:git:http://git.eclipse.org/gitroot/jetty/org.eclipse.jetty.project.git</connection>
     <developerConnection>scm:git:ssh://git.eclipse.org/gitroot/jetty/org.eclipse.jetty.project.git</developerConnection>
     <url>http://git.eclipse.org/c/jetty/org.eclipse.jetty.project.git/tree</url>
+    <tag>jetty-9.2.13.v20150730</tag>
   </scm>
   <build>
     <defaultGoal>install</defaultGoal>
@@ -29,20 +34,16 @@
       <plugin>
         <artifactId>maven-compiler-plugin</artifactId>
         <configuration>
-          <source>1.6</source>
-          <target>1.6</target>
+          <source>1.7</source>
+          <target>1.7</target>
           <verbose>false</verbose>
         </configuration>
       </plugin>
       <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-release-plugin</artifactId>
-        <version>2.5.1</version>
+        <version>2.5</version>
         <configuration>
           <autoVersionSubmodules>true</autoVersionSubmodules>
-          <arguments>
-            -Dtest=None
-          </arguments>
         </configuration>
       </plugin>
       <plugin>
@@ -79,7 +80,7 @@
                 <manifestEntries>
                   <Bundle-ManifestVersion>2</Bundle-ManifestVersion>
                   <Bundle-Name>${project.name}</Bundle-Name>
-                  <Bundle-SymbolicName>${bundle-symbolic-name}.source;singleton:=true</Bundle-SymbolicName>
+                  <Bundle-SymbolicName>${bundle-symbolic-name}.source</Bundle-SymbolicName>
                   <Bundle-Vendor>Eclipse.org - Jetty</Bundle-Vendor>
                   <Bundle-Version>${parsedVersion.osgiVersion}</Bundle-Version>
                   <Eclipse-SourceBundle>${bundle-symbolic-name};version="${parsedVersion.osgiVersion}";roots:="."</Eclipse-SourceBundle>
@@ -120,6 +121,7 @@
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-enforcer-plugin</artifactId>
+        <version>1.1</version>
         <executions>
           <execution>
             <id>enforce-java</id>
@@ -129,10 +131,12 @@
             <configuration>
               <rules>
                 <requireMavenVersion>
-                  <version>[2.0.6,)</version>
+                  <version>[3.0.0,)</version>
+                  <message>[ERROR] OLD MAVEN [${maven.version}] in use, Jetty ${project.version} requires Maven 3.0.0 or newer</message>
                 </requireMavenVersion>
                 <requireJavaVersion>
-                  <version>[1.5,)</version>
+                  <version>[1.7.0-40,)</version>
+                  <message>[ERROR] OLD JDK [${java.version}] in use. Jetty ${project.version} requires JDK 1.7.0_40 or newer</message>
                 </requireJavaVersion>
                 <versionTxtRule implementation="org.eclipse.jetty.toolchain.enforcer.rules.VersionTxtRule" />
                 <versionOsgiRule implementation="org.eclipse.jetty.toolchain.enforcer.rules.RequireOsgiCompatibleVersionRule" />
@@ -142,25 +146,20 @@
             </configuration>
           </execution>
           <execution>
-            <id>enforce-orbit-deps</id>
+            <id>ban-junit-dep.jar</id>
             <goals>
               <goal>enforce</goal>
             </goals>
             <configuration>
               <rules>
-                <!-- Banned Dependencies (should use Orbit based versions now) -->
                 <bannedDependencies>
-                  <includes>
-                    <include>javax.servlet</include>
-                    <include>org.apache.geronimo.specs</include>
-                    <include>javax.mail</include>
-                    <include>javax.activation</include>
-                  </includes>
+                  <excludes>
+                    <exclude>junit:junit-dep:*:jar</exclude>
+                  </excludes>
                   <searchTransitive>true</searchTransitive>
-                  <message>This dependency is banned, use the ORBIT provided dependency instead.</message>
+                  <message>We use junit.jar, not junit-dep.jar (as of junit 4.11, hamcrest is no longer embedded)</message>
                 </bannedDependencies>
               </rules>
-              <fail>true</fail>
             </configuration>
           </execution>
         </executions>
@@ -188,7 +187,7 @@
           </execution>
         </executions>
         <configuration>
-          <targetJdk>1.6</targetJdk>
+          <targetJdk>1.7</targetJdk>
           <rulesets>
             <ruleset>jetty/pmd_logging_ruleset.xml</ruleset>
           </rulesets>
@@ -224,6 +223,7 @@
             <exclude>jetty-util/src/main/java/org/eclipse/jetty/util/security/UnixCrypt.java</exclude>
             <exclude>jetty-policy/src/main/java/org/eclipse/jetty/policy/loader/DefaultPolicyLoader.java</exclude>
             <exclude>jetty-policy/src/main/java/org/eclipse/jetty/policy/loader/PolicyFileScanner.java</exclude>
+            <exclude>jetty-ant/**</exclude>
           </excludes>
         </configuration>
         <executions>
@@ -268,14 +268,23 @@
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-surefire-plugin</artifactId>
+          <version>2.17</version>
           <configuration>
+            <argLine>-showversion -Xmx1g -Xms1g -XX:+PrintGCDetails</argLine>
             <failIfNoTests>false</failIfNoTests>
-            <!--systemProperties>
+            <runMode>random</runMode>
+            <systemProperties>
+              <!--
               <property>
                 <name>org.eclipse.jetty.io.AbstractBuffer.boundsChecking</name>
                 <value>true</value>
               </property>
-            </systemProperties-->
+              -->
+              <property>
+                <name>java.io.tmpdir</name>
+                <value>${project.build.directory}</value>
+              </property>
+            </systemProperties>
           </configuration>
         </plugin>
         <plugin>
@@ -284,7 +293,8 @@
           <extensions>true</extensions>
           <configuration>
             <instructions>
-              <Bundle-RequiredExecutionEnvironment>J2SE-1.5</Bundle-RequiredExecutionEnvironment>
+              <Bundle-SymbolicName>${bundle-symbolic-name}</Bundle-SymbolicName>
+              <Bundle-RequiredExecutionEnvironment>JavaSE-1.7</Bundle-RequiredExecutionEnvironment>
               <Bundle-DocURL>${jetty.url}</Bundle-DocURL>
               <Bundle-Vendor>Eclipse Jetty Project</Bundle-Vendor>
               <Bundle-Classpath>.</Bundle-Classpath>
@@ -330,22 +340,40 @@
             <docfilessubdirs>true</docfilessubdirs>
             <detectLinks>false</detectLinks>
             <detectJavaApiLink>true</detectJavaApiLink>
-            <excludePackageNames>org.slf4j.*;org.mortbay.*</excludePackageNames>
+            <excludePackageNames>com.acme.*;org.slf4j.*;org.mortbay.*</excludePackageNames>
             <links>
-              <link>http://download.eclipse.org/jetty/stable-7/apidocs/</link>
+              <link>http://docs.oracle.com/javase/7/docs/api/</link>
+              <link>http://docs.oracle.com/javaee/7/api/</link>
+              <link>http://download.eclipse.org/jetty/stable-9/apidocs/</link>
+              <link>http://junit.sourceforge.net/javadoc/</link>
             </links>
             <tags>
               <tag>
                 <name>org.apache.xbean.XBean</name>
-                <placement>a</placement>
-                <head>Apache XBean:</head>
+                <placement>X</placement>
+                <head />
               </tag>
             </tags>
-            </configuration>
+            <header>
+              <![CDATA[
+                  <script type="text/javascript">
+                    var _gaq = _gaq || [];
+                    _gaq.push(['_setAccount', 'UA-1149868-7']);
+                    _gaq.push(['_trackPageview']);
+                    (function() {
+                      var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
+                      ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
+                      var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
+                    })();
+                 </script>
+              ]]>
+            </header>
+          </configuration>
         </plugin>
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-pmd-plugin</artifactId>
+          <version>2.7.1</version>
         </plugin>
       </plugins>
     </pluginManagement>
@@ -373,7 +401,7 @@
         <artifactId>maven-pmd-plugin</artifactId>
         <version>2.7.1</version>
         <configuration>
-          <targetJdk>1.5</targetJdk>
+          <targetJdk>1.7</targetJdk>
           <rulesets>
             <ruleset>jetty/pmd_ruleset.xml</ruleset>
           </rulesets>
@@ -382,11 +410,10 @@
       <plugin>
         <groupId>org.codehaus.mojo</groupId>
         <artifactId>findbugs-maven-plugin</artifactId>
-        <version>2.3.2</version>
+        <version>2.5.2</version>
       </plugin>
     </plugins>
   </reporting>
-<!--
   <repositories>
     <repository>
       <snapshots>
@@ -394,125 +421,224 @@
       </snapshots>
       <id>sonatype-snapshots</id>
       <name>Sonatype Jetty Snapshots</name>
-      <url>http://oss.sonatype.org/content/groups/jetty</url>
+      <url>https://oss.sonatype.org/content/groups/jetty</url>
     </repository>
   </repositories>
--->
   <modules>
+    <module>jetty-ant</module>
     <module>jetty-util</module>
     <module>jetty-jmx</module>
     <module>jetty-io</module>
     <module>jetty-http</module>
-    <module>jetty-websocket</module>
     <module>jetty-continuation</module>
     <module>jetty-server</module>
-    <module>jetty-client</module>
     <module>jetty-xml</module>
     <module>jetty-security</module>
-    <module>jetty-jaspi</module>
     <module>jetty-servlet</module>
     <module>jetty-webapp</module>
+    <module>jetty-spdy</module>
+    <module>jetty-fcgi</module>
+    <module>jetty-websocket</module>
     <module>jetty-servlets</module>
+    <module>jetty-util-ajax</module>
+    <module>jetty-jsp</module>
+    <module>apache-jsp</module>
+    <module>apache-jstl</module>
+    <module>jetty-maven-plugin</module>
+    <module>jetty-jspc-maven-plugin</module>
     <module>jetty-deploy</module>
-    <module>jetty-ajp</module>
-    <module>jetty-jndi</module>
-    <module>jetty-annotations</module>
+    <module>jetty-start</module>
     <module>jetty-plus</module>
+    <module>jetty-annotations</module>
+    <module>jetty-jndi</module>
+    <module>jetty-jaas</module>
+    <module>jetty-cdi</module>
+    <module>jetty-spring</module>
+    <module>jetty-client</module>
+    <module>jetty-proxy</module>
+    <module>jetty-jaspi</module>
     <module>jetty-rewrite</module>
-    <module>jetty-policy</module>
-    <module>jetty-monitor</module>
-    <module>jetty-start</module>
-    <module>jetty-nested</module>
-    <module>jetty-overlay-deployer</module>
-    <module>jetty-osgi</module>
     <module>jetty-nosql</module>
-    <module>jetty-http-spi</module>
-    <module>jetty-jsp</module>
-    <module>jetty-distribution</module>
-    <module>test-continuation</module>
-    <!--module>test-continuation-jetty6</module-->
-    <module>test-jetty-servlet</module>
-    <module>test-jetty-webapp</module>
-    <module>test-jetty-nested</module>
-    <module>example-jetty-embedded</module>
-    <module>example-async-rest</module>
     <module>tests</module>
+    <module>examples</module>
+    <module>jetty-quickstart</module>
+    <module>jetty-distribution</module>
+    <module>jetty-runner</module>
+    <module>jetty-monitor</module>
+    <module>jetty-http-spi</module>
+    <module>jetty-osgi</module>
+    <module>jetty-alpn</module>
+
+    <!-- modules that need fixed and added back, or simply dropped and not maintained
+    <module>jetty-rhttp</module>
+    -->
+    <!--<module>jetty-overlay-deployer</module>-->
   </modules>
   <dependencyManagement>
     <dependencies>
-      <!-- Orbit Deps -->
       <dependency>
-        <groupId>org.eclipse.jetty.orbit</groupId>
-        <artifactId>javax.servlet</artifactId>
-        <version>${orbit-servlet-api-version}</version>
-      </dependency>
-      <dependency>
-        <groupId>org.eclipse.jetty.orbit</groupId>
-        <artifactId>javax.annotation</artifactId>
-        <version>1.1.0.v201108011116</version>
+         <groupId>javax.servlet</groupId>
+         <artifactId>javax.servlet-api</artifactId>
+         <version>3.1.0</version>
       </dependency>
       <dependency>
-        <groupId>org.eclipse.jetty.orbit</groupId>
-        <artifactId>org.objectweb.asm</artifactId>
-        <version>3.1.0.v200803061910</version>
+         <groupId>javax.websocket</groupId>
+         <artifactId>javax.websocket-api</artifactId>
+         <version>1.0</version>
       </dependency>
       <dependency>
-        <groupId>org.eclipse.jetty.orbit</groupId>
-        <artifactId>javax.activation</artifactId>
-        <version>1.1.0.v201105071233</version>
+        <groupId>javax.annotation</groupId>
+        <artifactId>javax.annotation-api</artifactId>
+        <version>1.2</version>
       </dependency>
       <dependency>
-        <groupId>org.eclipse.jetty.orbit</groupId>
-        <artifactId>javax.mail.glassfish</artifactId>
-        <version>1.4.1.v201005082020</version>
+        <groupId>org.ow2.asm</groupId>
+        <artifactId>asm</artifactId>
+        <version>5.0.1</version>
       </dependency>
       <dependency>
-        <groupId>org.eclipse.jetty.orbit</groupId>
-        <artifactId>javax.transaction</artifactId>
-        <version>1.1.1.v201105210645</version>
+        <groupId>org.ow2.asm</groupId>
+        <artifactId>asm-commons</artifactId>
+        <version>5.0.1</version>
       </dependency>
+
       <dependency>
         <groupId>org.eclipse.jetty.orbit</groupId>
         <artifactId>javax.security.auth.message</artifactId>
         <version>1.0.0.v201108011116</version>
       </dependency>
-      <!--
+
+      <!-- JSP Deps -->
       <dependency>
-        <groupId>org.eclipse.jetty.orbit</groupId>
+        <groupId>org.eclipse.jetty.toolchain</groupId>
+        <artifactId>jetty-schemas</artifactId>
+        <version>3.1.M0</version>
+      </dependency>
+
+      <dependency>
+        <groupId>javax.servlet.jsp</groupId>
+        <artifactId>javax.servlet.jsp-api</artifactId>
+        <version>2.3.1</version>
+      </dependency>
+
+      <dependency>
+        <groupId>org.glassfish.web</groupId>
         <artifactId>javax.servlet.jsp</artifactId>
-        <version>2.1.0.v201105211820</version>
+        <version>2.3.2</version>
       </dependency>
+
       <dependency>
-        <groupId>org.eclipse.jetty.orbit</groupId>
-        <artifactId>javax.servlet.jsp.jstl</artifactId>
-        <version>1.2.0.v201105211821</version>
+        <groupId>org.eclipse.jetty.toolchain</groupId>
+        <artifactId>jetty-jsp-jdt</artifactId>
+        <version>2.3.3</version>
       </dependency>
+
+
       <dependency>
-        <groupId>org.eclipse.jetty.orbit</groupId>
+        <groupId>javax.el</groupId>
+        <artifactId>javax.el-api</artifactId>
+        <version>3.0.0</version>
+      </dependency>
+
+      <dependency>
+        <groupId>org.glassfish</groupId>
         <artifactId>javax.el</artifactId>
-        <version>2.1.0.v201105211819</version>
+        <version>3.0.0</version>
+      </dependency>
+
+      <dependency>
+        <groupId>org.mortbay.jasper</groupId>
+        <artifactId>apache-jsp</artifactId>
+        <version>8.0.9.M3</version>
       </dependency>
+
       <dependency>
         <groupId>org.eclipse.jetty.orbit</groupId>
-        <artifactId>com.sun.el</artifactId>
-        <version>1.0.0.v201105211818</version>
+        <artifactId>org.eclipse.jdt.core</artifactId>
+       <version>3.8.2.v20130121</version>
+       <exclusions>
+          <exclusion>
+            <groupId>org.eclipse.jetty.orbit</groupId>
+            <artifactId>javax.servlet</artifactId>
+         </exclusion>
+        </exclusions>
       </dependency>
+
+    <!-- JSTL Impl -->
+    <dependency>
+       <groupId>org.glassfish.web</groupId>
+       <artifactId>javax.servlet.jsp.jstl</artifactId>
+       <version>1.2.2</version>
+      <exclusions>
+        <exclusion>
+          <groupId>javax.servlet.jsp.jstl</groupId>
+          <artifactId>jstl-api</artifactId>
+        </exclusion>
+        <exclusion>
+          <groupId>javax.servlet</groupId>
+          <artifactId>servlet-api</artifactId>
+        </exclusion>
+        <exclusion>
+          <groupId>javax.servlet.jsp</groupId>
+          <artifactId>jsp-api</artifactId>
+        </exclusion>
+        <exclusion>
+          <groupId>javax.el</groupId>
+          <artifactId>el-api</artifactId>
+        </exclusion>
+      </exclusions>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.taglibs</groupId>
+      <artifactId>taglibs-standard-impl</artifactId>
+      <version>1.2.1</version>
+    </dependency>
+
+     <!-- JSTL API -->
       <dependency>
         <groupId>org.eclipse.jetty.orbit</groupId>
-        <artifactId>org.apache.jasper.glassfish</artifactId>
-        <version>2.1.0.v201110031002</version>
+        <artifactId>javax.servlet.jsp.jstl</artifactId>
+        <version>1.2.0.v201105211821</version>
+        <exclusions>
+          <exclusion>
+            <groupId>org.eclipse.jetty.orbit</groupId>
+            <artifactId>javax.servlet</artifactId>
+          </exclusion>
+          <exclusion>
+            <groupId>org.eclipse.jetty.orbit</groupId>
+            <artifactId>javax.servlet.jsp</artifactId>
+          </exclusion>
+        </exclusions>
+      </dependency>
+
+      <dependency>
+        <groupId>org.apache.taglibs</groupId>
+        <artifactId>taglibs-standard-spec</artifactId>
+        <version>1.2.1</version>
       </dependency>
+
+
       <dependency>
         <groupId>org.eclipse.jetty.orbit</groupId>
-        <artifactId>org.apache.taglibs.standard.glassfish</artifactId>
-        <version>1.2.0.v201112081803</version>
+        <artifactId>javax.activation</artifactId>
+        <version>1.1.0.v201105071233</version>
+        <scope>provided</scope>
       </dependency>
+
       <dependency>
         <groupId>org.eclipse.jetty.orbit</groupId>
-        <artifactId>org.eclipse.jdt.core</artifactId>
-        <version>3.7.1</version>
+        <artifactId>javax.mail.glassfish</artifactId>
+        <version>1.4.1.v201005082020</version>
       </dependency>
-        -->
+
+      <dependency>
+        <groupId>javax.transaction</groupId>
+        <artifactId>javax.transaction-api</artifactId>
+        <version>1.2</version>
+        <scope>provided</scope>
+      </dependency>
+
 
       <!-- Old Deps -->
       <dependency>
@@ -523,7 +649,12 @@
       <dependency>
         <groupId>org.eclipse.jetty.toolchain</groupId>
         <artifactId>jetty-test-helper</artifactId>
-        <version>2.5</version>
+        <version>3.0</version>
+      </dependency>
+      <dependency>
+        <groupId>org.eclipse.jetty.toolchain</groupId>
+        <artifactId>jetty-perf-helper</artifactId>
+        <version>1.0.5</version>
       </dependency>
       <dependency>
         <groupId>org.slf4j</groupId>
@@ -543,7 +674,7 @@
       <dependency>
         <groupId>junit</groupId>
         <artifactId>junit</artifactId>
-        <version>4.11</version>
+        <version>4.12</version>
       </dependency>
       <dependency>
         <groupId>org.hamcrest</groupId>
@@ -559,6 +690,12 @@
         <groupId>org.mockito</groupId>
         <artifactId>mockito-core</artifactId>
         <version>1.9.5</version>
+        <exclusions>
+          <exclusion>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+          </exclusion>
+        </exclusions>
       </dependency>
     </dependencies>
   </dependencyManagement>
@@ -566,80 +703,22 @@
     Usage:
     configure settings.xml for jetty.eclipse.website server entry
     > mvn -Paggregate-site javadoc:aggregate jxr:jxr
+    then
     > mvn -N site:deploy
     or
     > mvn -N site:sshdeploy     (for ssh users w/passphrase and ssh-agent)
    -->
   <profiles>
     <profile>
-      <!-- Modules that are only for JDK7+ builds -->
-      <id>JDK7-plus-modules</id>
-      <activation>
-        <jdk>[1.7,)</jdk>
-      </activation>
-      <modules>
-        <module>jetty-spdy</module>
-      </modules>
-    </profile>
-    <profile>
       <id>eclipse-release</id>
-   <build>
-    <plugins>
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-enforcer-plugin</artifactId>
-        <executions>
-          <execution>
-            <id>enforce-java</id>
-            <goals>
-              <goal>enforce</goal>
-            </goals>
-            <configuration>
-              <rules>
-                <requireJavaVersion>
-                  <version>[1.7,)</version>
-                </requireJavaVersion>
-              </rules>    
-            </configuration>
-          </execution>
-        </executions>
-      </plugin>
-    </plugins>
-  </build>
-    </profile>
-    <profile>
-      <!--
-        Moves the jetty-aggregate build tree to a profile called 'aggregates'.
-        It is active by default, but being in a profile allows it to be
-        disabled via the "-P-aggregates" command line on maven.
-        (Useful for running site plugin with deep reporting and avoiding
-         duplicate hits on aggregated classes) -->
-      <id>aggregates</id>
-      <activation>
-        <activeByDefault>true</activeByDefault>
-        <file>
-          <exists>${basedir}/pom.xml</exists>
-        </file>
-      </activation>
       <modules>
-        <module>jetty-aggregate</module>
+        <module>aggregates/jetty-all</module>
       </modules>
     </profile>
     <profile>
-      <!--
-        Moves the jetty-osgi build tree to a profile called 'osgi'.
-        It is active by default, but being in a profile allows it to be
-        disabled via the "-P-osgi" command line on maven (if need be).
-        -->
-      <id>osgi</id>
-      <activation>
-        <activeByDefault>true</activeByDefault>
-        <file>
-          <exists>${basedir}/pom.xml</exists>
-        </file>
-      </activation>
+      <id>ci</id>
       <modules>
-        <module>jetty-osgi</module>
+        <module>aggregates/jetty-all</module>
       </modules>
     </profile>
     <profile>
@@ -669,63 +748,6 @@
       </build>
     </profile>
     <profile>
-      <id>cobertura</id>
-      <reporting>
-        <plugins>
-          <plugin>
-            <groupId>org.codehaus.mojo</groupId>
-            <artifactId>cobertura-maven-plugin</artifactId>
-            <configuration>
-              <maxmem>512m</maxmem>
-            </configuration>
-          </plugin>
-          <plugin>
-            <groupId>org.apache.maven.plugins</groupId>
-            <artifactId>maven-javadoc-plugin</artifactId>
-            <reportSets>
-              <reportSet>
-                <id>just-javadoc-no-aggregate</id>
-                <reports>
-                  <report>javadoc</report>
-                </reports>
-                <inherited>true</inherited>
-                <configuration>
-                  <minmemory>256m</minmemory>
-                  <maxmemory>1g</maxmemory>
-                  <excludePackageNames>com.acme</excludePackageNames>
-                  <links>
-                    <link>http://java.sun.com/j2se/1.5.0/docs/api</link>
-                    <link>http://java.sun.com/javaee/5/docs/api</link>
-                    <link>http://junit.sourceforge.net/javadoc/</link>
-                  </links>
-                  <tags>
-                    <tag>
-                      <name>org.apache.xbean.XBean</name>
-                      <placement>X</placement>
-                      <head />
-                    </tag>
-                  </tags>
-                </configuration>
-              </reportSet>
-            </reportSets>
-          </plugin>
-         </plugins>
-      </reporting>
-      <build>
-        <pluginManagement>
-          <plugins>
-            <plugin>
-              <groupId>org.apache.maven.plugins</groupId>
-              <artifactId>maven-surefire-plugin</artifactId>
-              <configuration>
-                <testFailureIgnore>true</testFailureIgnore>
-              </configuration>
-            </plugin>
-          </plugins>
-        </pluginManagement>
-      </build>
-    </profile>
-    <profile>
       <id>maven-3</id>
       <activation>
         <file>
@@ -766,8 +788,8 @@
             <configuration>
               <excludePackageNames>com.acme</excludePackageNames>
               <links>
-                <link>http://java.sun.com/javase/6/docs/api/</link>
-                <link>http://java.sun.com/javaee/6/docs/api</link>
+                <link>http://docs.oracle.com/javase/7/docs/api/</link>
+                <link>http://docs.oracle.com/javaee/6/api</link>
                 <link>http://junit.sourceforge.net/javadoc/</link>
               </links>
               <tags>
@@ -790,11 +812,313 @@
                       })();
                    </script>
                 ]]>
-              </header>           
+              </header>
             </configuration>
           </plugin>
         </plugins>
       </build>
     </profile>
+    <profile>
+      <id>api-change</id>
+      <build>
+        <plugins>
+          <plugin>
+            <groupId>org.codehaus.mojo</groupId>
+            <artifactId>clirr-maven-plugin</artifactId>
+            <version>2.5</version>
+            <executions>
+              <execution>
+                <id>compare-api</id>
+                <phase>package</phase>
+                <goals>
+                  <goal>clirr</goal>
+                </goals>
+              </execution>
+            </executions>
+            <configuration>
+              <minSeverity>info</minSeverity>
+              <comparisonVersion>9.0.3.v20130506</comparisonVersion>
+            </configuration>
+          </plugin>
+        </plugins>
+      </build>
+    </profile>
+    <profile>
+      <id>7u40</id>
+      <activation>
+        <property>
+          <name>java.version</name>
+          <value>1.7.0_40</value>
+        </property>
+      </activation>
+      <properties>
+        <npn.version>1.1.6.v20130911</npn.version>
+        <alpn.version>7.1.0.v20141016</alpn.version>
+      </properties>
+    </profile>
+    <profile>
+      <id>7u45</id>
+      <activation>
+        <property>
+          <name>java.version</name>
+          <value>1.7.0_45</value>
+        </property>
+      </activation>
+      <properties>
+        <npn.version>1.1.6.v20130911</npn.version>
+        <alpn.version>7.1.0.v20141016</alpn.version>
+      </properties>
+    </profile>
+    <profile>
+      <id>7u51</id>
+      <activation>
+        <property>
+          <name>java.version</name>
+          <value>1.7.0_51</value>
+        </property>
+      </activation>
+      <properties>
+        <npn.version>1.1.6.v20130911</npn.version>
+        <alpn.version>7.1.0.v20141016</alpn.version>
+      </properties>
+    </profile>
+    <profile>
+      <id>7u55</id>
+      <activation>
+        <property>
+          <name>java.version</name>
+          <value>1.7.0_55</value>
+        </property>
+      </activation>
+      <properties>
+        <npn.version>1.1.8.v20141013</npn.version>
+        <alpn.version>7.1.0.v20141016</alpn.version>
+      </properties>
+    </profile>
+    <profile>
+      <id>7u60</id>
+      <activation>
+        <property>
+          <name>java.version</name>
+          <value>1.7.0_60</value>
+        </property>
+      </activation>
+      <properties>
+        <npn.version>1.1.8.v20141013</npn.version>
+        <alpn.version>7.1.0.v20141016</alpn.version>
+      </properties>
+    </profile>
+    <profile>
+      <id>7u65</id>
+      <activation>
+        <property>
+          <name>java.version</name>
+          <value>1.7.0_65</value>
+        </property>
+      </activation>
+      <properties>
+        <npn.version>1.1.8.v20141013</npn.version>
+        <alpn.version>7.1.0.v20141016</alpn.version>
+      </properties>
+    </profile>
+    <profile>
+      <id>7u67</id>
+      <activation>
+        <property>
+          <name>java.version</name>
+          <value>1.7.0_67</value>
+        </property>
+      </activation>
+      <properties>
+        <npn.version>1.1.8.v20141013</npn.version>
+        <alpn.version>7.1.0.v20141016</alpn.version>
+      </properties>
+    </profile>
+    <profile>
+      <id>7u71</id>
+      <activation>
+        <property>
+          <name>java.version</name>
+          <value>1.7.0_71</value>
+        </property>
+      </activation>
+      <properties>
+        <npn.version>1.1.9.v20141016</npn.version>
+        <alpn.version>7.1.2.v20141202</alpn.version>
+      </properties>
+    </profile>
+    <profile>
+      <id>7u72</id>
+      <activation>
+        <property>
+          <name>java.version</name>
+          <value>1.7.0_72</value>
+        </property>
+      </activation>
+      <properties>
+        <npn.version>1.1.9.v20141016</npn.version>
+        <alpn.version>7.1.2.v20141202</alpn.version>
+      </properties>
+    </profile>
+    <profile>
+      <id>7u75</id>
+      <activation>
+        <property>
+          <name>java.version</name>
+          <value>1.7.0_75</value>
+        </property>
+      </activation>
+      <properties>
+        <npn.version>1.1.10.v20150130</npn.version>
+        <alpn.version>7.1.3.v20150130</alpn.version>
+      </properties>
+    </profile>
+    <profile>
+      <id>7u76</id>
+      <activation>
+        <property>
+          <name>java.version</name>
+          <value>1.7.0_76</value>
+        </property>
+      </activation>
+      <properties>
+        <npn.version>1.1.10.v20150130</npn.version>
+        <alpn.version>7.1.3.v20150130</alpn.version>
+      </properties>
+    </profile>
+    <profile>
+      <id>7u79</id>
+      <activation>
+        <property>
+          <name>java.version</name>
+          <value>1.7.0_79</value>
+        </property>
+      </activation>
+      <properties>
+        <npn.version>1.1.10.v20150130</npn.version>
+        <alpn.version>7.1.3.v20150130</alpn.version>
+      </properties>
+    </profile>
+    <profile>
+      <id>7u80</id>
+      <activation>
+        <property>
+          <name>java.version</name>
+          <value>1.7.0_80</value>
+        </property>
+      </activation>
+      <properties>
+        <npn.version>1.1.11.v20150415</npn.version>
+        <alpn.version>7.1.3.v20150130</alpn.version>
+      </properties>
+    </profile>
+    <profile>
+      <id>8u00</id>
+      <activation>
+        <property>
+          <name>java.version</name>
+          <value>1.8.0</value>
+        </property>
+      </activation>
+      <properties>
+        <alpn.version>8.1.0.v20141016</alpn.version>
+      </properties>
+    </profile>
+    <profile>
+      <id>8u05</id>
+      <activation>
+        <property>
+          <name>java.version</name>
+          <value>1.8.0_05</value>
+        </property>
+      </activation>
+      <properties>
+        <alpn.version>8.1.0.v20141016</alpn.version>
+      </properties>
+    </profile>
+    <profile>
+      <id>8u11</id>
+      <activation>
+        <property>
+          <name>java.version</name>
+          <value>1.8.0_11</value>
+        </property>
+      </activation>
+      <properties>
+        <alpn.version>8.1.0.v20141016</alpn.version>
+      </properties>
+    </profile>
+    <profile>
+      <id>8u20</id>
+      <activation>
+        <property>
+          <name>java.version</name>
+          <value>1.8.0_20</value>
+        </property>
+      </activation>
+      <properties>
+        <alpn.version>8.1.0.v20141016</alpn.version>
+      </properties>
+    </profile>
+    <profile>
+      <id>8u25</id>
+      <activation>
+        <property>
+          <name>java.version</name>
+          <value>1.8.0_25</value>
+        </property>
+      </activation>
+      <properties>
+        <alpn.version>8.1.2.v20141202</alpn.version>
+      </properties>
+    </profile>
+    <profile>
+      <id>8u31</id>
+      <activation>
+        <property>
+          <name>java.version</name>
+          <value>1.8.0_31</value>
+        </property>
+      </activation>
+      <properties>
+        <alpn.version>8.1.3.v20150130</alpn.version>
+      </properties>
+    </profile>
+    <profile>
+      <id>8u40</id>
+      <activation>
+        <property>
+          <name>java.version</name>
+          <value>1.8.0_40</value>
+        </property>
+      </activation>
+      <properties>
+        <alpn.version>8.1.3.v20150130</alpn.version>
+      </properties>
+    </profile>
+    <profile>
+      <id>8u45</id>
+      <activation>
+        <property>
+          <name>java.version</name>
+          <value>1.8.0_45</value>
+        </property>
+      </activation>
+      <properties>
+        <alpn.version>8.1.3.v20150130</alpn.version>
+      </properties>
+    </profile>
+    <profile>
+      <id>8u51</id>
+      <activation>
+        <property>
+          <name>java.version</name>
+          <value>1.8.0_51</value>
+        </property>
+      </activation>
+      <properties>
+        <alpn.version>8.1.4.v20150727</alpn.version>
+      </properties>
+    </profile>
   </profiles>
 </project>
diff --git a/settings.xml b/settings.xml
deleted file mode 100755
index 9b5ddef..0000000
--- a/settings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<settings>
-  <localRepository>/tmp/jetty-builds/jetty8/localRepo</localRepository>
-  <interactiveMode>true</interactiveMode>
-  <offline>false</offline>
-<proxies>
-   <proxy>
-      <active>true</active>
-      <protocol>http</protocol>
-      <host>proxy.eclipse.org</host>
-      <port>9898</port>
-    </proxy>
-  </proxies>
-<mirrors>
-  <mirror>
-      <id>central</id>
-      <name>central</name>
-      <url>http://repo2.maven.org/maven2/</url>
-      <mirrorOf>*</mirrorOf>
-    </mirror>
-  </mirrors>
-</settings>
diff --git a/test-continuation-jetty6/pom.xml b/test-continuation-jetty6/pom.xml
deleted file mode 100644
index 86048b7..0000000
--- a/test-continuation-jetty6/pom.xml
+++ /dev/null
@@ -1,82 +0,0 @@
-<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">
-  <parent>
-    <groupId>org.eclipse.jetty</groupId>
-    <artifactId>jetty-project</artifactId>
-    <version>8.1.0-SNAPSHOT</version>
-  </parent>
-  <modelVersion>4.0.0</modelVersion>
-  <artifactId>test-continuation-jetty6</artifactId>
-  <packaging>jar</packaging>
-  <name>Test :: Continuation - (Jetty 6)</name>
-  <description>Asynchronous API</description>
-  <url>http://www.eclipse.org/jetty</url>
-  <build>
-    <plugins>
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-deploy-plugin</artifactId>
-        <configuration>
-          <!-- DO NOT DEPLOY (or Release) -->
-          <skip>true</skip>
-        </configuration>
-      </plugin>
-    </plugins>
-  </build>
-  <dependencies>
-    <dependency>
-      <groupId>junit</groupId>
-      <artifactId>junit</artifactId>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.mortbay.jetty</groupId>
-      <artifactId>servlet-api</artifactId>
-      <version>2.5-20081211</version>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.eclipse.jetty</groupId> 
-      <artifactId>jetty-servlet</artifactId>
-      <version>${project.version}</version>
-      <scope>test</scope>
-        <exclusions>
-          <exclusion>
-            <groupId>${servlet.spec.groupId}</groupId>
-            <artifactId>${servlet.spec.artifactId}</artifactId>
-          </exclusion>
-        </exclusions>
-    </dependency> 
-    <dependency>
-      <groupId>org.eclipse.jetty</groupId> 
-      <artifactId>test-continuation</artifactId>
-      <version>${project.version}</version>
-      <scope>test</scope>
-      <exclusions>
-       	<exclusion>
-	  <groupId>${servlet.spec.groupId}</groupId>
-	  <artifactId>${servlet.spec.artifactId}</artifactId>
-       	</exclusion>
-      </exclusions>
-    </dependency> 
-    <dependency>
-        <groupId>org.mortbay.jetty</groupId>
-        <artifactId>jetty</artifactId>
-        <version>6.1.26</version>
-        <type>jar</type>
-        <scope>test</scope>
-        <exclusions>
-          <exclusion>
-            <artifactId>servlet-api-2.5</artifactId>
-            <groupId>org.mortbay.jetty</groupId>
-          </exclusion>
-        </exclusions>
-    </dependency>
-    <dependency>
-        <groupId>org.eclipse.jetty</groupId>
-        <artifactId>jetty-servlets</artifactId>
-        <version>${project.version}</version>
-        <type>jar</type>
-        <scope>test</scope>
-    </dependency>
-  </dependencies>
-</project>
diff --git a/test-continuation-jetty6/src/test/java/org/eclipse/jetty/continuation/ContinuationBase.java b/test-continuation-jetty6/src/test/java/org/eclipse/jetty/continuation/ContinuationBase.java
deleted file mode 100644
index 7acc02e..0000000
--- a/test-continuation-jetty6/src/test/java/org/eclipse/jetty/continuation/ContinuationBase.java
+++ /dev/null
@@ -1,428 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.continuation;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.Socket;
-import java.util.Timer;
-import java.util.TimerTask;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import junit.framework.TestCase;
-
-
-
-public abstract class ContinuationBase extends TestCase
-{
-    protected SuspendServlet _servlet=new SuspendServlet();
-    protected int _port;
-    
-    protected void doit(String type) throws Exception
-    {
-        String response;
-        
-        response=process(null,null);
-        assertContains(type,response);
-        assertContains("NORMAL",response);
-        assertNotContains("history: onTimeout",response);
-        assertNotContains("history: onComplete",response);
-
-        response=process("sleep=200",null);
-        assertContains("SLEPT",response);
-        assertNotContains("history: onTimeout",response);
-        assertNotContains("history: onComplete",response);
-        
-        response=process("suspend=200",null);
-        assertContains("TIMEOUT",response);
-        assertContains("history: onTimeout",response);
-        assertContains("history: onComplete",response);
-        
-        response=process("suspend=200&resume=10",null);
-        assertContains("RESUMED",response);
-        assertNotContains("history: onTimeout",response);
-        assertContains("history: onComplete",response);
-        
-        response=process("suspend=200&resume=0",null);
-        assertContains("RESUMED",response);
-        assertNotContains("history: onTimeout",response);
-        assertContains("history: onComplete",response);
-        
-        response=process("suspend=200&complete=10",null);
-        assertContains("COMPLETED",response);
-        assertNotContains("history: onTimeout",response);
-        assertContains("history: onComplete",response);
-        
-        response=process("suspend=200&complete=0",null);
-        assertContains("COMPLETED",response);
-        assertNotContains("history: onTimeout",response);
-        assertContains("history: onComplete",response);
-        
-        
-        response=process("suspend=1000&resume=10&suspend2=1000&resume2=10",null);
-        assertEquals(2,count(response,"history: suspend"));
-        assertEquals(2,count(response,"history: resume"));
-        assertEquals(0,count(response,"history: onTimeout"));
-        assertEquals(1,count(response,"history: onComplete"));
-        assertContains("RESUMED",response);
-        
-        response=process("suspend=1000&resume=10&suspend2=1000&resume2=10",null);
-        assertEquals(2,count(response,"history: suspend"));
-        assertEquals(2,count(response,"history: resume"));
-        assertEquals(0,count(response,"history: onTimeout"));
-        assertEquals(1,count(response,"history: onComplete"));
-        assertContains("RESUMED",response);
-        
-        response=process("suspend=1000&resume=10&suspend2=1000&complete2=10",null);
-        assertEquals(2,count(response,"history: suspend"));
-        assertEquals(1,count(response,"history: resume"));
-        assertEquals(0,count(response,"history: onTimeout"));
-        assertEquals(1,count(response,"history: onComplete"));
-        assertContains("COMPLETED",response);
-        
-        response=process("suspend=1000&resume=10&suspend2=10",null);
-        assertEquals(2,count(response,"history: suspend"));
-        assertEquals(1,count(response,"history: resume"));
-        assertEquals(1,count(response,"history: onTimeout"));
-        assertEquals(1,count(response,"history: onComplete"));
-        assertContains("TIMEOUT",response);
-        
-
-        
-        response=process("suspend=10&suspend2=1000&resume2=10",null);
-        assertEquals(2,count(response,"history: suspend"));
-        assertEquals(1,count(response,"history: resume"));
-        assertEquals(1,count(response,"history: onTimeout"));
-        assertEquals(1,count(response,"history: onComplete"));
-        assertContains("RESUMED",response);
-        
-        response=process("suspend=10&suspend2=1000&resume2=10",null);
-        assertEquals(2,count(response,"history: suspend"));
-        assertEquals(1,count(response,"history: resume"));
-        assertEquals(1,count(response,"history: onTimeout"));
-        assertEquals(1,count(response,"history: onComplete"));
-        assertContains("RESUMED",response);
-        
-        response=process("suspend=10&suspend2=1000&complete2=10",null);
-        assertEquals(2,count(response,"history: suspend"));
-        assertEquals(0,count(response,"history: resume"));
-        assertEquals(1,count(response,"history: onTimeout"));
-        assertEquals(1,count(response,"history: onComplete"));
-        assertContains("COMPLETED",response);
-        
-        response=process("suspend=10&suspend2=10",null);
-        assertEquals(2,count(response,"history: suspend"));
-        assertEquals(0,count(response,"history: resume"));
-        assertEquals(2,count(response,"history: onTimeout"));
-        assertEquals(1,count(response,"history: onComplete"));
-        assertContains("TIMEOUT",response);
-        
-    }
-
-    
-    private int count(String responses,String substring)
-    {
-        int count=0;
-        int i=responses.indexOf(substring);
-        while (i>=0)
-        {
-            count++;
-            i=responses.indexOf(substring,i+substring.length());
-        }
-        
-        return count;
-    }
-    
-    protected void assertContains(String content,String response)
-    {
-        assertEquals("HTTP/1.1 200 OK",response.substring(0,15));
-        if (response.indexOf(content,15)<0)
-        {
-            System.err.println(content+" NOT IN '"+response+"'");
-            assertTrue(false);
-        }
-    }
-    
-    protected void assertNotContains(String content,String response)
-    {
-        assertEquals("HTTP/1.1 200 OK",response.substring(0,15));
-        if (response.indexOf(content,15)>=0)
-        {
-            System.err.println(content+" IS IN '"+response+"'");
-            assertTrue(false);
-        }
-    }
-    
-    public synchronized String process(String query,String content) throws Exception
-    {
-        String request = "GET /";
-        
-        if (query!=null)
-            request+="?"+query;
-        request+=" HTTP/1.1\r\n"+
-        "Host: localhost\r\n"+
-        "Connection: close\r\n";
-        if (content!=null)
-            request+="Content-Length: "+content.length()+"\r\n";
-        request+="\r\n" + content;
-        
-        Socket socket = new Socket("localhost",_port);
-        socket.getOutputStream().write(request.getBytes("UTF-8"));
-        
-        String response = toString(socket.getInputStream());
-        return response;
-    }
-    
-    
-    protected abstract String toString(InputStream in) throws IOException;
-    
-    
-    private static class SuspendServlet extends HttpServlet
-    {
-        private Timer _timer=new Timer();
-        
-        public SuspendServlet()
-        {}
-        
-        /* ------------------------------------------------------------ */
-        protected void doGet(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException
-        {
-            final Continuation continuation = ContinuationSupport.getContinuation(request,response);
-
-            response.addHeader("history",continuation.getClass().toString());
-            
-            int read_before=0;
-            long sleep_for=-1;
-            long suspend_for=-1;
-            long suspend2_for=-1;
-            long resume_after=-1;
-            long resume2_after=-1;
-            long complete_after=-1;
-            long complete2_after=-1;
-            
-            if (request.getParameter("read")!=null)
-                read_before=Integer.parseInt(request.getParameter("read"));
-            if (request.getParameter("sleep")!=null)
-                sleep_for=Integer.parseInt(request.getParameter("sleep"));
-            if (request.getParameter("suspend")!=null)
-                suspend_for=Integer.parseInt(request.getParameter("suspend"));
-            if (request.getParameter("suspend2")!=null)
-                suspend2_for=Integer.parseInt(request.getParameter("suspend2"));
-            if (request.getParameter("resume")!=null)
-                resume_after=Integer.parseInt(request.getParameter("resume"));
-            if (request.getParameter("resume2")!=null)
-                resume2_after=Integer.parseInt(request.getParameter("resume2"));
-            if (request.getParameter("complete")!=null)
-                complete_after=Integer.parseInt(request.getParameter("complete"));
-            if (request.getParameter("complete2")!=null)
-                complete2_after=Integer.parseInt(request.getParameter("complete2"));
-            
-            if (continuation.isInitial())
-            {
-                if (read_before>0)
-                {
-                    byte[] buf=new byte[read_before];
-                    request.getInputStream().read(buf);
-                }
-                else if (read_before<0)
-                {
-                    InputStream in = request.getInputStream();
-                    int b=in.read();
-                    while(b!=-1)
-                        b=in.read();
-                }
-
-                if (suspend_for>=0)
-                {
-                    if (suspend_for>0)
-                        continuation.setTimeout(suspend_for);
-                    continuation.addContinuationListener(__listener);
-                    ((HttpServletResponse)continuation.getServletResponse()).addHeader("history","suspend");
-                    continuation.suspend();
-                    
-                    if (complete_after>0)
-                    {
-                        TimerTask complete = new TimerTask()
-                        {
-                            public void run()
-                            {
-                                try
-                                {
-                                    response.setStatus(200);
-                                    response.getOutputStream().println("COMPLETED\n");
-                                    continuation.complete();
-                                }
-                                catch(Exception e)
-                                {
-                                    e.printStackTrace();
-                                }
-                            }
-                        };
-                        synchronized (_timer)
-                        {
-                            _timer.schedule(complete,complete_after);
-                        }
-                    }
-                    else if (complete_after==0)
-                    {
-                        response.setStatus(200);
-                        response.getOutputStream().println("COMPLETED\n");
-                        continuation.complete();
-                    }
-                    else if (resume_after>0)
-                    {
-                        TimerTask resume = new TimerTask()
-                        {
-                            public void run()
-                            {
-                                ((HttpServletResponse)continuation.getServletResponse()).addHeader("history","resume");
-                                continuation.resume();
-                            }
-                        };
-                        synchronized (_timer)
-                        {
-                            _timer.schedule(resume,resume_after);
-                        }
-                    }
-                    else if (resume_after==0)
-                    {
-                        ((HttpServletResponse)continuation.getServletResponse()).addHeader("history","resume");
-                        continuation.resume();
-                    }
-                }
-                else if (sleep_for>=0)
-                {
-                    try
-                    {
-                        Thread.sleep(sleep_for);
-                    }
-                    catch (InterruptedException e)
-                    {
-                        e.printStackTrace();
-                    }
-                    response.setStatus(200);
-                    response.getOutputStream().println("SLEPT\n");
-                }
-                else
-                {
-                    response.setStatus(200);
-                    response.getOutputStream().println("NORMAL\n");
-                }
-            }
-            else if (suspend2_for>=0 && request.getAttribute("2nd")==null)
-            {
-                request.setAttribute("2nd","cycle");
-
-                if (suspend2_for>0)
-                    continuation.setTimeout(suspend2_for);
-                // continuation.addContinuationListener(__listener);
-                ((HttpServletResponse)continuation.getServletResponse()).addHeader("history","suspend");
-                continuation.suspend();
-
-                if (complete2_after>0)
-                {
-                    TimerTask complete = new TimerTask()
-                    {
-                        public void run()
-                        {
-                            try
-                            {
-                                response.setStatus(200);
-                                response.getOutputStream().println("COMPLETED\n");
-                                continuation.complete();
-                            }
-                            catch(Exception e)
-                            {
-                                e.printStackTrace();
-                            }
-                        }
-                    };
-                    synchronized (_timer)
-                    {
-                        _timer.schedule(complete,complete2_after);
-                    }
-                }
-                else if (complete2_after==0)
-                {
-                    response.setStatus(200);
-                    response.getOutputStream().println("COMPLETED\n");
-                    continuation.complete();
-                }
-                else if (resume2_after>0)
-                {
-                    TimerTask resume = new TimerTask()
-                    {
-                        public void run()
-                        {
-                            ((HttpServletResponse)continuation.getServletResponse()).addHeader("history","resume");
-                            continuation.resume();
-                        }
-                    };
-                    synchronized (_timer)
-                    {
-                        _timer.schedule(resume,resume2_after);
-                    }
-                }
-                else if (resume2_after==0)
-                {
-                    ((HttpServletResponse)continuation.getServletResponse()).addHeader("history","resume");
-                    continuation.resume();
-                }
-                return;
-            }
-            else if (continuation.isExpired())
-            {
-                response.setStatus(200);
-                response.getOutputStream().println("TIMEOUT\n");
-            }
-            else if (continuation.isResumed())
-            {
-                response.setStatus(200);
-                response.getOutputStream().println("RESUMED\n");
-            }
-            else 
-            {
-                response.setStatus(200);
-                response.getOutputStream().println("unknown???\n");
-            }
-        }
-    }
-    
-    
-    
-    private static ContinuationListener __listener = 
-        new ContinuationListener()
-    {
-        public void onComplete(Continuation continuation)
-        {
-            ((HttpServletResponse)continuation.getServletResponse()).addHeader("history","onComplete");
-        }
-
-        public void onTimeout(Continuation continuation)
-        {
-            ((HttpServletResponse)continuation.getServletResponse()).addHeader("history","onTimeout");
-            continuation.resume();
-        }
-        
-    };
-}
diff --git a/test-continuation-jetty6/src/test/java/org/eclipse/jetty/continuation/FauxContinuationTest.java b/test-continuation-jetty6/src/test/java/org/eclipse/jetty/continuation/FauxContinuationTest.java
deleted file mode 100644
index 8b09de1..0000000
--- a/test-continuation-jetty6/src/test/java/org/eclipse/jetty/continuation/FauxContinuationTest.java
+++ /dev/null
@@ -1,83 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.continuation;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-import javax.servlet.Filter;
-import javax.servlet.FilterChain;
-import javax.servlet.FilterConfig;
-import javax.servlet.ServletException;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletRequestWrapper;
-
-import org.mortbay.jetty.Connector;
-import org.mortbay.jetty.Server;
-import org.mortbay.jetty.nio.SelectChannelConnector;
-import org.mortbay.jetty.servlet.Context;
-import org.mortbay.jetty.servlet.FilterHolder;
-import org.mortbay.jetty.servlet.ServletHandler;
-import org.mortbay.jetty.servlet.ServletHolder;
-import org.mortbay.util.IO;
-
-
-public class FauxContinuationTest extends ContinuationBase
-{
-    protected Server _server = new Server();
-    protected ServletHandler _servletHandler;
-    protected SelectChannelConnector _connector;
-    FilterHolder _filter;
-
-    protected void setUp() throws Exception
-    {
-        _connector = new SelectChannelConnector();
-        _server.setConnectors(new Connector[]{ _connector });
-        Context servletContext = new Context(Context.NO_SECURITY|Context.NO_SESSIONS);
-        _server.setHandler(servletContext);
-        _servletHandler=servletContext.getServletHandler();
-        ServletHolder holder=new ServletHolder(_servlet);
-        _servletHandler.addServletWithMapping(holder,"/");
-        _filter=_servletHandler.addFilterWithMapping(ContinuationFilter.class,"/*",0);
-    }
-
-    protected void tearDown() throws Exception
-    {
-        _server.stop();
-    }
-
-    public void testFaux() throws Exception
-    {
-        _filter.setInitParameter("debug","true");
-        _filter.setInitParameter("faux","true");
-        _server.start();
-        _port=_connector.getLocalPort();
-        
-        doit("FauxContinuation");
-    }
-
-    
-    
-    protected String toString(InputStream in) throws IOException
-    {
-        return IO.toString(in);
-    }
-}
diff --git a/test-continuation-jetty6/src/test/java/org/eclipse/jetty/continuation/Jetty6ContinuationBioFauxTest.java b/test-continuation-jetty6/src/test/java/org/eclipse/jetty/continuation/Jetty6ContinuationBioFauxTest.java
deleted file mode 100644
index 8a7dfa0..0000000
--- a/test-continuation-jetty6/src/test/java/org/eclipse/jetty/continuation/Jetty6ContinuationBioFauxTest.java
+++ /dev/null
@@ -1,156 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.continuation;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-import org.eclipse.jetty.continuation.test.ContinuationBase;
-import org.mortbay.jetty.Connector;
-import org.mortbay.jetty.Server;
-import org.mortbay.jetty.bio.SocketConnector;
-import org.mortbay.jetty.nio.SelectChannelConnector;
-import org.mortbay.jetty.servlet.Context;
-import org.mortbay.jetty.servlet.FilterHolder;
-import org.mortbay.jetty.servlet.ServletHandler;
-import org.mortbay.jetty.servlet.ServletHolder;
-import org.mortbay.util.IO;
-
-
-public class Jetty6ContinuationBioFauxTest extends ContinuationBase
-{
-    protected Server _server = new Server();
-    protected ServletHandler _servletHandler;
-    protected SocketConnector _socketConnector;
-    FilterHolder _filter;
-
-    protected void setUp() throws Exception
-    {
-        _socketConnector = new SocketConnector();
-        _server.setConnectors(new Connector[]{ _socketConnector });
-        Context servletContext = new Context(Context.NO_SECURITY|Context.NO_SESSIONS);
-        _server.setHandler(servletContext);
-        _servletHandler=servletContext.getServletHandler();
-        ServletHolder holder=new ServletHolder(_servlet);
-        _servletHandler.addServletWithMapping(holder,"/");
-        _filter=_servletHandler.addFilterWithMapping(ContinuationFilter.class,"/*",0);
-
-        _filter.setInitParameter("debug","true");
-        _filter.setInitParameter("faux","true");
-        _server.start();
-        
-        _port=_socketConnector.getLocalPort();
-    }
-
-    protected void tearDown() throws Exception
-    {
-        _server.stop();
-    }
-
-    public void testContinuation() throws Exception
-    {
-        doNormal("FauxContinuation");
-    }
-    
-    public void testSleep() throws Exception
-    {
-        doSleep();
-    }
-
-    public void testSuspend() throws Exception
-    {
-        doSuspend();
-    }
-
-    public void testSuspendWaitResume() throws Exception
-    {
-        doSuspendWaitResume();
-    }
-
-    public void testSuspendResume() throws Exception
-    {
-        doSuspendResume();
-    }
-
-    public void testSuspendWaitComplete() throws Exception
-    {
-        doSuspendWaitComplete();
-    }
-
-    public void testSuspendComplete() throws Exception
-    {
-        doSuspendComplete();
-    }
-
-    public void testSuspendWaitResumeSuspendWaitResume() throws Exception
-    {
-        doSuspendWaitResumeSuspendWaitResume();
-    }
-    
-    public void testSuspendWaitResumeSuspendComplete() throws Exception
-    {
-        doSuspendWaitResumeSuspendComplete();
-    }
-
-    public void testSuspendWaitResumeSuspend() throws Exception
-    {
-        doSuspendWaitResumeSuspend();
-    }
-
-    public void testSuspendTimeoutSuspendResume() throws Exception
-    {
-        doSuspendTimeoutSuspendResume();
-    }
-
-    public void testSuspendTimeoutSuspendComplete() throws Exception
-    {
-        doSuspendTimeoutSuspendComplete();
-    }
-
-    public void testSuspendTimeoutSuspend() throws Exception
-    {
-        doSuspendTimeoutSuspend();
-    }
-
-    public void testSuspendThrowResume() throws Exception
-    {
-        doSuspendThrowResume();
-    }
-
-    public void testSuspendResumeThrow() throws Exception
-    {
-        doSuspendResumeThrow();
-    }
-
-    public void testSuspendThrowComplete() throws Exception
-    {
-        doSuspendThrowComplete();
-    }
-
-    public void testSuspendCompleteThrow() throws Exception
-    {
-        doSuspendCompleteThrow();
-    }
-    
-    protected String toString(InputStream in) throws IOException
-    {
-        return IO.toString(in);
-    }
-    
-}
diff --git a/test-continuation-jetty6/src/test/java/org/eclipse/jetty/continuation/Jetty6ContinuationBioTest.java b/test-continuation-jetty6/src/test/java/org/eclipse/jetty/continuation/Jetty6ContinuationBioTest.java
deleted file mode 100644
index 65be970..0000000
--- a/test-continuation-jetty6/src/test/java/org/eclipse/jetty/continuation/Jetty6ContinuationBioTest.java
+++ /dev/null
@@ -1,156 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.continuation;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-import org.eclipse.jetty.continuation.test.ContinuationBase;
-import org.mortbay.jetty.Connector;
-import org.mortbay.jetty.Server;
-import org.mortbay.jetty.bio.SocketConnector;
-import org.mortbay.jetty.nio.SelectChannelConnector;
-import org.mortbay.jetty.servlet.Context;
-import org.mortbay.jetty.servlet.FilterHolder;
-import org.mortbay.jetty.servlet.ServletHandler;
-import org.mortbay.jetty.servlet.ServletHolder;
-import org.mortbay.util.IO;
-
-
-public class Jetty6ContinuationBioTest extends ContinuationBase
-{
-    protected Server _server = new Server();
-    protected ServletHandler _servletHandler;
-    protected SocketConnector _socketConnector;
-    FilterHolder _filter;
-
-    protected void setUp() throws Exception
-    {
-        _socketConnector = new SocketConnector();
-        _server.setConnectors(new Connector[]{ _socketConnector });
-        Context servletContext = new Context(Context.NO_SECURITY|Context.NO_SESSIONS);
-        _server.setHandler(servletContext);
-        _servletHandler=servletContext.getServletHandler();
-        ServletHolder holder=new ServletHolder(_servlet);
-        _servletHandler.addServletWithMapping(holder,"/");
-        _filter=_servletHandler.addFilterWithMapping(ContinuationFilter.class,"/*",0);
-
-        _filter.setInitParameter("debug","true");
-        //_filter.setInitParameter("faux","false");
-        _server.start();
-        
-        _port=_socketConnector.getLocalPort();
-    }
-
-    protected void tearDown() throws Exception
-    {
-        _server.stop();
-    }
-
-    public void testContinuation() throws Exception
-    {
-        doNormal("FauxContinuation");
-    }
-    
-    public void testSleep() throws Exception
-    {
-        doSleep();
-    }
-
-    public void testSuspend() throws Exception
-    {
-        doSuspend();
-    }
-
-    public void testSuspendWaitResume() throws Exception
-    {
-        doSuspendWaitResume();
-    }
-
-    public void testSuspendResume() throws Exception
-    {
-        doSuspendResume();
-    }
-
-    public void testSuspendWaitComplete() throws Exception
-    {
-        doSuspendWaitComplete();
-    }
-
-    public void testSuspendComplete() throws Exception
-    {
-        doSuspendComplete();
-    }
-
-    public void testSuspendWaitResumeSuspendWaitResume() throws Exception
-    {
-        doSuspendWaitResumeSuspendWaitResume();
-    }
-    
-    public void testSuspendWaitResumeSuspendComplete() throws Exception
-    {
-        doSuspendWaitResumeSuspendComplete();
-    }
-
-    public void testSuspendWaitResumeSuspend() throws Exception
-    {
-        doSuspendWaitResumeSuspend();
-    }
-
-    public void testSuspendTimeoutSuspendResume() throws Exception
-    {
-        doSuspendTimeoutSuspendResume();
-    }
-
-    public void testSuspendTimeoutSuspendComplete() throws Exception
-    {
-        doSuspendTimeoutSuspendComplete();
-    }
-
-    public void testSuspendTimeoutSuspend() throws Exception
-    {
-        doSuspendTimeoutSuspend();
-    }
-
-    public void testSuspendThrowResume() throws Exception
-    {
-        doSuspendThrowResume();
-    }
-
-    public void testSuspendResumeThrow() throws Exception
-    {
-        doSuspendResumeThrow();
-    }
-
-    public void testSuspendThrowComplete() throws Exception
-    {
-        doSuspendThrowComplete();
-    }
-
-    public void testSuspendCompleteThrow() throws Exception
-    {
-        doSuspendCompleteThrow();
-    }
-    
-    protected String toString(InputStream in) throws IOException
-    {
-        return IO.toString(in);
-    }
-    
-}
diff --git a/test-continuation-jetty6/src/test/java/org/eclipse/jetty/continuation/Jetty6ContinuationNioFauxTest.java b/test-continuation-jetty6/src/test/java/org/eclipse/jetty/continuation/Jetty6ContinuationNioFauxTest.java
deleted file mode 100644
index b1604ca..0000000
--- a/test-continuation-jetty6/src/test/java/org/eclipse/jetty/continuation/Jetty6ContinuationNioFauxTest.java
+++ /dev/null
@@ -1,157 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.continuation;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-import org.eclipse.jetty.continuation.test.ContinuationBase;
-import org.mortbay.jetty.Connector;
-import org.mortbay.jetty.Server;
-import org.mortbay.jetty.bio.SocketConnector;
-import org.mortbay.jetty.nio.SelectChannelConnector;
-import org.mortbay.jetty.servlet.Context;
-import org.mortbay.jetty.servlet.FilterHolder;
-import org.mortbay.jetty.servlet.ServletHandler;
-import org.mortbay.jetty.servlet.ServletHolder;
-import org.mortbay.util.IO;
-
-
-public class Jetty6ContinuationNioFauxTest extends ContinuationBase
-{
-    protected Server _server = new Server();
-    protected ServletHandler _servletHandler;
-    protected SelectChannelConnector _selectChannelConnector;
-    protected SocketConnector _socketConnector;
-    FilterHolder _filter;
-
-    protected void setUp() throws Exception
-    {
-        _selectChannelConnector = new SelectChannelConnector();
-        _server.setConnectors(new Connector[]{ _selectChannelConnector });
-        Context servletContext = new Context(Context.NO_SECURITY|Context.NO_SESSIONS);
-        _server.setHandler(servletContext);
-        _servletHandler=servletContext.getServletHandler();
-        ServletHolder holder=new ServletHolder(_servlet);
-        _servletHandler.addServletWithMapping(holder,"/");
-        _filter=_servletHandler.addFilterWithMapping(ContinuationFilter.class,"/*",0);
-
-        _filter.setInitParameter("debug","true");
-        _filter.setInitParameter("faux","true");
-        _server.start();
-        
-        _port=_selectChannelConnector.getLocalPort();
-    }
-
-    protected void tearDown() throws Exception
-    {
-        _server.stop();
-    }
-
-    public void testContinuation() throws Exception
-    {
-        doNormal("FauxContinuation");
-    }
-    
-    public void testSleep() throws Exception
-    {
-        doSleep();
-    }
-
-    public void testSuspend() throws Exception
-    {
-        doSuspend();
-    }
-
-    public void testSuspendWaitResume() throws Exception
-    {
-        doSuspendWaitResume();
-    }
-
-    public void testSuspendResume() throws Exception
-    {
-        doSuspendResume();
-    }
-
-    public void testSuspendWaitComplete() throws Exception
-    {
-        doSuspendWaitComplete();
-    }
-
-    public void testSuspendComplete() throws Exception
-    {
-        doSuspendComplete();
-    }
-
-    public void testSuspendWaitResumeSuspendWaitResume() throws Exception
-    {
-        doSuspendWaitResumeSuspendWaitResume();
-    }
-    
-    public void testSuspendWaitResumeSuspendComplete() throws Exception
-    {
-        doSuspendWaitResumeSuspendComplete();
-    }
-
-    public void testSuspendWaitResumeSuspend() throws Exception
-    {
-        doSuspendWaitResumeSuspend();
-    }
-
-    public void testSuspendTimeoutSuspendResume() throws Exception
-    {
-        doSuspendTimeoutSuspendResume();
-    }
-
-    public void testSuspendTimeoutSuspendComplete() throws Exception
-    {
-        doSuspendTimeoutSuspendComplete();
-    }
-
-    public void testSuspendTimeoutSuspend() throws Exception
-    {
-        doSuspendTimeoutSuspend();
-    }
-
-    public void testSuspendThrowResume() throws Exception
-    {
-        doSuspendThrowResume();
-    }
-
-    public void testSuspendResumeThrow() throws Exception
-    {
-        doSuspendResumeThrow();
-    }
-
-    public void testSuspendThrowComplete() throws Exception
-    {
-        doSuspendThrowComplete();
-    }
-
-    public void testSuspendCompleteThrow() throws Exception
-    {
-        doSuspendCompleteThrow();
-    }
-    
-    protected String toString(InputStream in) throws IOException
-    {
-        return IO.toString(in);
-    }
-    
-}
diff --git a/test-continuation-jetty6/src/test/java/org/eclipse/jetty/continuation/Jetty6ContinuationNioTest.java b/test-continuation-jetty6/src/test/java/org/eclipse/jetty/continuation/Jetty6ContinuationNioTest.java
deleted file mode 100644
index b1f7bbc..0000000
--- a/test-continuation-jetty6/src/test/java/org/eclipse/jetty/continuation/Jetty6ContinuationNioTest.java
+++ /dev/null
@@ -1,157 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.continuation;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-import org.eclipse.jetty.continuation.test.ContinuationBase;
-import org.mortbay.jetty.Connector;
-import org.mortbay.jetty.Server;
-import org.mortbay.jetty.bio.SocketConnector;
-import org.mortbay.jetty.nio.SelectChannelConnector;
-import org.mortbay.jetty.servlet.Context;
-import org.mortbay.jetty.servlet.FilterHolder;
-import org.mortbay.jetty.servlet.ServletHandler;
-import org.mortbay.jetty.servlet.ServletHolder;
-import org.mortbay.util.IO;
-
-
-public class Jetty6ContinuationNioTest extends ContinuationBase
-{
-    protected Server _server = new Server();
-    protected ServletHandler _servletHandler;
-    protected SelectChannelConnector _selectChannelConnector;
-    FilterHolder _filter;
-
-    @Override
-    protected void setUp() throws Exception
-    {
-        _selectChannelConnector = new SelectChannelConnector();
-        _server.setConnectors(new Connector[]{ _selectChannelConnector });
-        Context servletContext = new Context(Context.NO_SECURITY|Context.NO_SESSIONS);
-        _server.setHandler(servletContext);
-        _servletHandler=servletContext.getServletHandler();
-        ServletHolder holder=new ServletHolder(_servlet);
-        _servletHandler.addServletWithMapping(holder,"/");
-        _filter=_servletHandler.addFilterWithMapping(ContinuationFilter.class,"/*",0);
-        _filter.setInitParameter("debug","true");
-        _server.start();
-        
-        _port=_selectChannelConnector.getLocalPort();
-    }
-
-    @Override
-    protected void tearDown() throws Exception
-    {
-        _server.stop();
-    }
-
-    public void testContinuation() throws Exception
-    {
-        doNormal("Jetty6Continuation");
-    }
-    
-    public void testSleep() throws Exception
-    {
-        doSleep();
-    }
-
-    public void testSuspend() throws Exception
-    {
-        doSuspend();
-    }
-
-    public void testSuspendWaitResume() throws Exception
-    {
-        doSuspendWaitResume();
-    }
-
-    public void testSuspendResume() throws Exception
-    {
-        doSuspendResume();
-    }
-
-    public void testSuspendWaitComplete() throws Exception
-    {
-        doSuspendWaitComplete();
-    }
-
-    public void testSuspendComplete() throws Exception
-    {
-        doSuspendComplete();
-    }
-
-    public void testSuspendWaitResumeSuspendWaitResume() throws Exception
-    {
-        doSuspendWaitResumeSuspendWaitResume();
-    }
-    
-    public void testSuspendWaitResumeSuspendComplete() throws Exception
-    {
-        doSuspendWaitResumeSuspendComplete();
-    }
-
-    public void testSuspendWaitResumeSuspend() throws Exception
-    {
-        doSuspendWaitResumeSuspend();
-    }
-
-    public void testSuspendTimeoutSuspendResume() throws Exception
-    {
-        doSuspendTimeoutSuspendResume();
-    }
-
-    public void testSuspendTimeoutSuspendComplete() throws Exception
-    {
-        doSuspendTimeoutSuspendComplete();
-    }
-
-    public void testSuspendTimeoutSuspend() throws Exception
-    {
-        doSuspendTimeoutSuspend();
-    }
-
-    public void testSuspendThrowResume() throws Exception
-    {
-        doSuspendThrowResume();
-    }
-
-    public void testSuspendResumeThrow() throws Exception
-    {
-        doSuspendResumeThrow();
-    }
-
-    public void testSuspendThrowComplete() throws Exception
-    {
-        doSuspendThrowComplete();
-    }
-
-    public void testSuspendCompleteThrow() throws Exception
-    {
-        doSuspendCompleteThrow();
-    }
-    
-    @Override
-    protected String toString(InputStream in) throws IOException
-    {
-        return IO.toString(in);
-    }
-    
-}
diff --git a/test-continuation-jetty6/src/test/java/org/eclipse/jetty/continuation/TestProxyServer.java b/test-continuation-jetty6/src/test/java/org/eclipse/jetty/continuation/TestProxyServer.java
deleted file mode 100644
index 7049ce5..0000000
--- a/test-continuation-jetty6/src/test/java/org/eclipse/jetty/continuation/TestProxyServer.java
+++ /dev/null
@@ -1,57 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.continuation;
-
-import org.eclipse.jetty.servlets.ProxyServlet;
-import org.junit.Ignore;
-import org.mortbay.jetty.Connector;
-import org.mortbay.jetty.Server;
-import org.mortbay.jetty.nio.SelectChannelConnector;
-import org.mortbay.jetty.servlet.Context;
-import org.mortbay.jetty.servlet.FilterHolder;
-import org.mortbay.jetty.servlet.ServletHandler;
-import org.mortbay.jetty.servlet.ServletHolder;
-
- at Ignore("Not a test case")
-public class TestProxyServer
-{
-    public static void main(String[] args) throws Exception
-    {
-        Server server = new Server();
-        SelectChannelConnector selectChannelConnector = new SelectChannelConnector();
-        server.setConnectors(new Connector[]{ selectChannelConnector });
-        selectChannelConnector.setPort(8080);
-            
-        Context servletContext = new Context(Context.NO_SECURITY|Context.NO_SESSIONS);
-        server.setHandler(servletContext);
-        ServletHandler servletHandler=servletContext.getServletHandler();
-        
-        
-        ServletHolder proxy=new ServletHolder(ProxyServlet.Transparent.class);
-        servletHandler.addServletWithMapping(proxy,"/ws/*");
-        proxy.setInitParameter("ProxyTo","http://www.webtide.com");
-        proxy.setInitParameter("Prefix","/ws");
-        
-        FilterHolder filter=servletHandler.addFilterWithMapping(ContinuationFilter.class,"/*",0);
-        filter.setInitParameter("debug","true");
-        
-        server.start();
-        server.join();
-    }
-}
diff --git a/test-continuation/pom.xml b/test-continuation/pom.xml
deleted file mode 100644
index e70a539..0000000
--- a/test-continuation/pom.xml
+++ /dev/null
@@ -1,38 +0,0 @@
-<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">
-  <parent>
-    <groupId>org.eclipse.jetty</groupId>
-    <artifactId>jetty-project</artifactId>
-    <version>8.1.17.v20150415</version>
-  </parent>
-  <modelVersion>4.0.0</modelVersion>
-  <artifactId>test-continuation</artifactId>
-  <packaging>jar</packaging>
-  <name>Test :: Continuation</name>
-  <description>Asynchronous API</description>
-  <url>http://www.eclipse.org/jetty</url>
-  <build>
-    <plugins>
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-deploy-plugin</artifactId>
-        <configuration>
-          <!-- DO NOT DEPLOY (or Release) -->
-          <skip>true</skip>
-        </configuration>
-      </plugin>
-    </plugins>
-  </build>
-  <dependencies>
-    <dependency>
-      <groupId>junit</groupId>
-      <artifactId>junit</artifactId>
-      <scope>provided</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.eclipse.jetty</groupId> 
-      <artifactId>jetty-servlet</artifactId>
-      <version>${project.version}</version>
-      <scope>provided</scope>
-    </dependency> 
-  </dependencies>
-</project>
diff --git a/test-continuation/src/main/java/org/eclipse/jetty/continuation/test/ContinuationBase.java b/test-continuation/src/main/java/org/eclipse/jetty/continuation/test/ContinuationBase.java
deleted file mode 100644
index e917f6c..0000000
--- a/test-continuation/src/main/java/org/eclipse/jetty/continuation/test/ContinuationBase.java
+++ /dev/null
@@ -1,515 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.continuation.test;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.Socket;
-import java.util.Timer;
-import java.util.TimerTask;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import junit.framework.TestCase;
-
-import org.eclipse.jetty.continuation.Continuation;
-import org.eclipse.jetty.continuation.ContinuationListener;
-import org.eclipse.jetty.continuation.ContinuationSupport;
-
-
-
-public abstract class ContinuationBase extends TestCase
-{
-    protected SuspendServlet _servlet=new SuspendServlet();
-    protected int _port;
-    
-    protected void doNormal(String type) throws Exception
-    {
-        String response=process(null,null);
-        assertContains(type,response);
-        assertContains("NORMAL",response);
-        assertNotContains("history: onTimeout",response);
-        assertNotContains("history: onComplete",response);
-    }
-
-    protected void doSleep() throws Exception
-    {
-        String response=process("sleep=200",null);
-        assertContains("SLEPT",response);
-        assertNotContains("history: onTimeout",response);
-        assertNotContains("history: onComplete",response);
-    }
-
-    protected void doSuspend() throws Exception
-    {
-        String response=process("suspend=200",null);
-        assertContains("TIMEOUT",response);
-        assertContains("history: onTimeout",response);
-        assertContains("history: onComplete",response);
-    }
-
-    protected void doSuspendWaitResume() throws Exception
-    {
-        String response=process("suspend=200&resume=10",null);
-        assertContains("RESUMED",response);
-        assertNotContains("history: onTimeout",response);
-        assertContains("history: onComplete",response);
-    }
-
-    protected void doSuspendResume() throws Exception
-    {
-        String response=process("suspend=200&resume=0",null);
-        assertContains("RESUMED",response);
-        assertNotContains("history: onTimeout",response);
-        assertContains("history: onComplete",response);
-    }
-
-    protected void doSuspendWaitComplete() throws Exception
-    {
-        String response=process("suspend=200&complete=50",null);
-        assertContains("COMPLETED",response);
-        assertContains("history: initial",response);
-        assertNotContains("history: onTimeout",response);
-        assertContains("history: onComplete",response);
-        assertNotContains("history: !initial",response);
-    }
-
-    protected void doSuspendComplete() throws Exception
-    {
-        String response=process("suspend=200&complete=0",null);
-        assertContains("COMPLETED",response);
-        assertContains("history: initial",response);
-        assertNotContains("history: onTimeout",response);
-        assertContains("history: onComplete",response);
-        assertNotContains("history: !initial",response);
-    }
-
-    protected void doSuspendWaitResumeSuspendWaitResume() throws Exception
-    {
-        String response=process("suspend=1000&resume=10&suspend2=1000&resume2=10",null);
-        assertEquals(2,count(response,"history: suspend"));
-        assertEquals(2,count(response,"history: resume"));
-        assertEquals(0,count(response,"history: onTimeout"));
-        assertEquals(1,count(response,"history: onComplete"));
-        assertContains("RESUMED",response);
-    }
-    
-    protected void doSuspendWaitResumeSuspendComplete() throws Exception
-    {
-        String response=process("suspend=1000&resume=10&suspend2=1000&complete2=10",null);
-        assertEquals(2,count(response,"history: suspend"));
-        assertEquals(1,count(response,"history: resume"));
-        assertEquals(0,count(response,"history: onTimeout"));
-        assertEquals(1,count(response,"history: onComplete"));
-        assertContains("COMPLETED",response);
-    }
-
-    protected void doSuspendWaitResumeSuspend() throws Exception
-    {
-        String response=process("suspend=1000&resume=10&suspend2=10",null);
-        assertEquals(2,count(response,"history: suspend"));
-        assertEquals(1,count(response,"history: resume"));
-        assertEquals(1,count(response,"history: onTimeout"));
-        assertEquals(1,count(response,"history: onComplete"));
-        assertContains("TIMEOUT",response);
-    }
-
-    protected void doSuspendTimeoutSuspendResume() throws Exception
-    {
-        String response=process("suspend=10&suspend2=1000&resume2=10",null);
-        assertEquals(2,count(response,"history: suspend"));
-        assertEquals(1,count(response,"history: resume"));
-        assertEquals(1,count(response,"history: onTimeout"));
-        assertEquals(1,count(response,"history: onComplete"));
-        assertContains("RESUMED",response);
-    }
-
-    protected void doSuspendTimeoutSuspendComplete() throws Exception
-    {
-        String response=process("suspend=10&suspend2=1000&complete2=10",null);
-        assertEquals(2,count(response,"history: suspend"));
-        assertEquals(0,count(response,"history: resume"));
-        assertEquals(1,count(response,"history: onTimeout"));
-        assertEquals(1,count(response,"history: onComplete"));
-        assertContains("COMPLETED",response);
-    }
-
-    protected void doSuspendTimeoutSuspend() throws Exception
-    {
-        String response=process("suspend=10&suspend2=10",null);
-        assertEquals(2,count(response,"history: suspend"));
-        assertEquals(0,count(response,"history: resume"));
-        assertEquals(2,count(response,"history: onTimeout"));
-        assertEquals(1,count(response,"history: onComplete"));
-        assertContains("TIMEOUT",response);
-    }
-
-    protected void doSuspendThrowResume() throws Exception
-    {
-        String response=process("suspend=200&resume=10&undispatch=true",null);
-        assertContains("RESUMED",response);
-        assertNotContains("history: onTimeout",response);
-        assertContains("history: onComplete",response);
-    }
-
-    protected void doSuspendResumeThrow() throws Exception
-    {
-        String response=process("suspend=200&resume=0&undispatch=true",null);
-        assertContains("RESUMED",response);
-        assertNotContains("history: onTimeout",response);
-        assertContains("history: onComplete",response);
-    }
-
-    protected void doSuspendThrowComplete() throws Exception
-    {
-        String response=process("suspend=200&complete=10&undispatch=true",null);
-        assertContains("COMPLETED",response);
-        assertNotContains("history: onTimeout",response);
-        assertContains("history: onComplete",response);
-    }
-
-    protected void doSuspendCompleteThrow() throws Exception
-    {
-        String response=process("suspend=200&complete=0&undispatch=true",null);
-        assertContains("COMPLETED",response);
-        assertNotContains("history: onTimeout",response);
-        assertContains("history: onComplete",response);
-    }
-
-    
-    private int count(String responses,String substring)
-    {
-        int count=0;
-        int i=responses.indexOf(substring);
-        while (i>=0)
-        {
-            count++;
-            i=responses.indexOf(substring,i+substring.length());
-        }
-        
-        return count;
-    }
-    
-    protected void assertContains(String content,String response)
-    {
-        assertEquals("HTTP/1.1 200 OK",response.substring(0,15));
-        if (response.indexOf(content,15)<0)
-        {
-            System.err.println("'"+content+"' NOT IN:\n"+response+"\n--");
-            assertTrue(false);
-        }
-    }
-    
-    protected void assertNotContains(String content,String response)
-    {
-        assertEquals("HTTP/1.1 200 OK",response.substring(0,15));
-        if (response.indexOf(content,15)>=0)
-        {
-            System.err.println("'"+content+"' IS IN:\n"+response+"'\n--");
-            assertTrue(false);
-        }
-    }
-    
-    public synchronized String process(String query,String content) throws Exception
-    {
-        String request = "GET /";
-        
-        if (query!=null)
-            request+="?"+query;
-        request+=" HTTP/1.1\r\n"+
-        "Host: localhost\r\n"+
-        "Connection: close\r\n";
-        if (content==null)
-            request+="\r\n";
-        else
-        {
-            request+="Content-Length: "+content.length()+"\r\n";
-            request+="\r\n" + content;
-        }
-        
-        int port=_port;
-        String response=null;
-        try
-        {
-            Socket socket = new Socket("localhost",port);
-            socket.setSoTimeout(10000);
-            socket.getOutputStream().write(request.getBytes("UTF-8"));
-
-            response = toString(socket.getInputStream());
-        }
-        catch(Exception e)
-        {
-            System.err.println("failed on port "+port);
-            e.printStackTrace();
-            throw e;
-        }
-        return response;
-    }
-    
-    
-    protected abstract String toString(InputStream in) throws IOException;
-    
-    
-    private static class SuspendServlet extends HttpServlet
-    {
-        private Timer _timer=new Timer();
-        
-        public SuspendServlet()
-        {}
-        
-        /* ------------------------------------------------------------ */
-        protected void doGet(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException
-        {
-            final Continuation continuation = ContinuationSupport.getContinuation(request);
-
-            response.addHeader("history",continuation.getClass().toString());
-            
-            int read_before=0;
-            long sleep_for=-1;
-            long suspend_for=-1;
-            long suspend2_for=-1;
-            long resume_after=-1;
-            long resume2_after=-1;
-            long complete_after=-1;
-            long complete2_after=-1;
-            boolean undispatch=false;
-            
-            if (request.getParameter("read")!=null)
-                read_before=Integer.parseInt(request.getParameter("read"));
-            if (request.getParameter("sleep")!=null)
-                sleep_for=Integer.parseInt(request.getParameter("sleep"));
-            if (request.getParameter("suspend")!=null)
-                suspend_for=Integer.parseInt(request.getParameter("suspend"));
-            if (request.getParameter("suspend2")!=null)
-                suspend2_for=Integer.parseInt(request.getParameter("suspend2"));
-            if (request.getParameter("resume")!=null)
-                resume_after=Integer.parseInt(request.getParameter("resume"));
-            if (request.getParameter("resume2")!=null)
-                resume2_after=Integer.parseInt(request.getParameter("resume2"));
-            if (request.getParameter("complete")!=null)
-                complete_after=Integer.parseInt(request.getParameter("complete"));
-            if (request.getParameter("complete2")!=null)
-                complete2_after=Integer.parseInt(request.getParameter("complete2"));
-            if (request.getParameter("undispatch")!=null)
-                undispatch=Boolean.parseBoolean(request.getParameter("undispatch"));
-            
-            if (continuation.isInitial())
-            {
-                ((HttpServletResponse)response).addHeader("history","initial");
-                if (read_before>0)
-                {
-                    byte[] buf=new byte[read_before];
-                    request.getInputStream().read(buf);
-                }
-                else if (read_before<0)
-                {
-                    InputStream in = request.getInputStream();
-                    int b=in.read();
-                    while(b!=-1)
-                        b=in.read();
-                }
-
-                if (suspend_for>=0)
-                {
-                    if (suspend_for>0)
-                        continuation.setTimeout(suspend_for);
-                    continuation.addContinuationListener(__listener);
-                    ((HttpServletResponse)response).addHeader("history","suspend");
-                    continuation.suspend(response);
-                    
-                    if (complete_after>0)
-                    {
-                        TimerTask complete = new TimerTask()
-                        {
-                            @Override
-                            public void run()
-                            {
-                                try
-                                {
-                                    response.setStatus(200);
-                                    response.getOutputStream().println("COMPLETED\n");
-                                    continuation.complete();
-                                }
-                                catch(Exception e)
-                                {
-                                    e.printStackTrace();
-                                }
-                            }
-                        };
-                        synchronized (_timer)
-                        {
-                            _timer.schedule(complete,complete_after);
-                        }
-                    }
-                    else if (complete_after==0)
-                    {
-                        response.setStatus(200);
-                        response.getOutputStream().println("COMPLETED\n");
-                        continuation.complete();
-                    }
-                    else if (resume_after>0)
-                    {
-                        TimerTask resume = new TimerTask()
-                        {
-                            @Override
-                            public void run()
-                            {
-                                ((HttpServletResponse)continuation.getServletResponse()).addHeader("history","resume");
-                                continuation.resume();
-                            }
-                        };
-                        synchronized (_timer)
-                        {
-                            _timer.schedule(resume,resume_after);
-                        }
-                    }
-                    else if (resume_after==0)
-                    {
-                        ((HttpServletResponse)continuation.getServletResponse()).addHeader("history","resume");
-                        continuation.resume();
-                    }
-                    
-                    if (undispatch)
-                        continuation.undispatch();
-                }
-                else if (sleep_for>=0)
-                {
-                    try
-                    {
-                        Thread.sleep(sleep_for);
-                    }
-                    catch (InterruptedException e)
-                    {
-                        e.printStackTrace();
-                    }
-                    response.setStatus(200);
-                    response.getOutputStream().println("SLEPT\n");
-                }
-                else
-                {
-                    response.setStatus(200);
-                    response.getOutputStream().println("NORMAL\n");
-                }
-            }
-            else    
-            {
-                ((HttpServletResponse)response).addHeader("history","!initial");
-                if (suspend2_for>=0 && request.getAttribute("2nd")==null)
-                {
-                    request.setAttribute("2nd","cycle");
-
-                    if (suspend2_for>0)
-                        continuation.setTimeout(suspend2_for);
-                    // continuation.addContinuationListener(__listener);
-                    ((HttpServletResponse)response).addHeader("history","suspend");
-                    continuation.suspend(response);
-
-                    if (complete2_after>0)
-                    {
-                        TimerTask complete = new TimerTask()
-                        {
-                            @Override
-                            public void run()
-                            {
-                                try
-                                {
-                                    response.setStatus(200);
-                                    response.getOutputStream().println("COMPLETED\n");
-                                    continuation.complete();
-                                }
-                                catch(Exception e)
-                                {
-                                    e.printStackTrace();
-                                }
-                            }
-                        };
-                        synchronized (_timer)
-                        {
-                            _timer.schedule(complete,complete2_after);
-                        }
-                    }
-                    else if (complete2_after==0)
-                    {
-                        response.setStatus(200);
-                        response.getOutputStream().println("COMPLETED\n");
-                        continuation.complete();
-                    }
-                    else if (resume2_after>0)
-                    {
-                        TimerTask resume = new TimerTask()
-                        {
-                            @Override
-                            public void run()
-                            {
-                                ((HttpServletResponse)response).addHeader("history","resume");
-                                continuation.resume();
-                            }
-                        };
-                        synchronized (_timer)
-                        {
-                            _timer.schedule(resume,resume2_after);
-                        }
-                    }
-                    else if (resume2_after==0)
-                    {
-                        ((HttpServletResponse)response).addHeader("history","resume");
-                        continuation.resume();
-                    }
-                    if (undispatch)
-                        continuation.undispatch();
-                    return;
-                }
-                else if (continuation.isExpired())
-                {
-                    response.setStatus(200);
-                    response.getOutputStream().println("TIMEOUT\n");
-                }
-                else if (continuation.isResumed())
-                {
-                    response.setStatus(200);
-                    response.getOutputStream().println("RESUMED\n");
-                }
-                else 
-                {
-                    response.setStatus(200);
-                    response.getOutputStream().println("unknown???\n");
-                }
-            }
-        }
-    }
-    
-    
-    private static ContinuationListener __listener = new ContinuationListener()
-    {
-        public void onComplete(Continuation continuation)
-        {
-            ((HttpServletResponse)continuation.getServletResponse()).addHeader("history","onComplete");
-        }
-
-        public void onTimeout(Continuation continuation)
-        {
-            ((HttpServletResponse)continuation.getServletResponse()).addHeader("history","onTimeout");
-            continuation.resume();
-        }
-        
-    };
-}
diff --git a/test-continuation/src/test/java/org/eclipse/jetty/continuation/ContinuationTest.java b/test-continuation/src/test/java/org/eclipse/jetty/continuation/ContinuationTest.java
deleted file mode 100644
index f5ec6a0..0000000
--- a/test-continuation/src/test/java/org/eclipse/jetty/continuation/ContinuationTest.java
+++ /dev/null
@@ -1,186 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.continuation;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.EnumSet;
-import java.util.ArrayList;
-import java.util.List;
-
-import junit.framework.Assert;
-
-import org.eclipse.jetty.continuation.test.ContinuationBase;
-import org.eclipse.jetty.server.Connector;
-import org.eclipse.jetty.server.Request;
-import org.eclipse.jetty.server.RequestLog;
-import org.eclipse.jetty.server.Response;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.handler.RequestLogHandler;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
-import org.eclipse.jetty.servlet.FilterHolder;
-import org.eclipse.jetty.servlet.ServletContextHandler;
-import org.eclipse.jetty.servlet.ServletHandler;
-import org.eclipse.jetty.servlet.ServletHolder;
-import org.eclipse.jetty.util.IO;
-import org.eclipse.jetty.util.component.AbstractLifeCycle;
-
-
-
-public class ContinuationTest extends ContinuationBase
-{
-    protected Server _server = new Server();
-    protected ServletHandler _servletHandler;
-    protected SelectChannelConnector _connector;
-    FilterHolder _filter;
-    protected List<String> _log = new ArrayList<String>();
-
-    @Override
-    protected void setUp() throws Exception
-    {
-        _connector = new SelectChannelConnector();
-        _server.setConnectors(new Connector[]{ _connector });
-
-        _log.clear();
-        RequestLogHandler requestLogHandler = new RequestLogHandler();
-        requestLogHandler.setRequestLog(new Log());
-        _server.setHandler(requestLogHandler);
-        
-        ServletContextHandler servletContext = new ServletContextHandler(ServletContextHandler.NO_SECURITY|ServletContextHandler.NO_SESSIONS);
-        requestLogHandler.setHandler(servletContext);
-        
-        _servletHandler=servletContext.getServletHandler();
-        ServletHolder holder=new ServletHolder(_servlet);
-        holder.setAsyncSupported(true);
-        _servletHandler.addServletWithMapping(holder,"/");
-        
-        _server.start();
-        _port=_connector.getLocalPort();
-        
-    }
-
-    @Override
-    protected void tearDown() throws Exception
-    {
-        Assert.assertEquals(1,_log.size());
-        Assert.assertTrue(_log.get(0).startsWith("200 "));
-        Assert.assertTrue(_log.get(0).endsWith(" /"));
-        _server.stop();
-    }
-    
-    public void testContinuation() throws Exception
-    {
-        doNormal("AsyncContinuation");
-    }
-    
-    public void testSleep() throws Exception
-    {
-        doSleep();
-    }
-
-    public void testSuspend() throws Exception
-    {
-        doSuspend();
-    }
-
-    public void testSuspendWaitResume() throws Exception
-    {
-        doSuspendWaitResume();
-    }
-
-    public void testSuspendResume() throws Exception
-    {
-        doSuspendResume();
-    }
-
-    public void testSuspendWaitComplete() throws Exception
-    {
-        doSuspendWaitComplete();
-    }
-
-    public void testSuspendComplete() throws Exception
-    {
-        doSuspendComplete();
-    }
-
-    public void testSuspendWaitResumeSuspendWaitResume() throws Exception
-    {
-        doSuspendWaitResumeSuspendWaitResume();
-    }
-    
-    public void testSuspendWaitResumeSuspendComplete() throws Exception
-    {
-        doSuspendWaitResumeSuspendComplete();
-    }
-
-    public void testSuspendWaitResumeSuspend() throws Exception
-    {
-        doSuspendWaitResumeSuspend();
-    }
-
-    public void testSuspendTimeoutSuspendResume() throws Exception
-    {
-        doSuspendTimeoutSuspendResume();
-    }
-
-    public void testSuspendTimeoutSuspendComplete() throws Exception
-    {
-        doSuspendTimeoutSuspendComplete();
-    }
-
-    public void testSuspendTimeoutSuspend() throws Exception
-    {
-        doSuspendTimeoutSuspend();
-    }
-
-    public void testSuspendThrowResume() throws Exception
-    {
-        doSuspendThrowResume();
-    }
-
-    public void testSuspendResumeThrow() throws Exception
-    {
-        doSuspendResumeThrow();
-    }
-
-    public void testSuspendThrowComplete() throws Exception
-    {
-        doSuspendThrowComplete();
-    }
-
-    public void testSuspendCompleteThrow() throws Exception
-    {
-        doSuspendCompleteThrow();
-    }
-    
-    
-    protected String toString(InputStream in) throws IOException
-    {
-        return IO.toString(in);
-    }
-    
-    class Log extends AbstractLifeCycle implements RequestLog
-    {
-        public void log(Request request, Response response)
-        {
-            _log.add(response.getStatus()+" "+response.getContentCount()+" "+request.getRequestURI());
-        }
-        
-    }
-}
diff --git a/test-continuation/src/test/java/org/eclipse/jetty/continuation/FauxContinuationTest.java b/test-continuation/src/test/java/org/eclipse/jetty/continuation/FauxContinuationTest.java
deleted file mode 100644
index a755667..0000000
--- a/test-continuation/src/test/java/org/eclipse/jetty/continuation/FauxContinuationTest.java
+++ /dev/null
@@ -1,160 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.continuation;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.EnumSet;
-
-import javax.servlet.DispatcherType;
-
-import org.eclipse.jetty.continuation.test.ContinuationBase;
-import org.eclipse.jetty.server.Connector;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
-import org.eclipse.jetty.servlet.FilterHolder;
-import org.eclipse.jetty.servlet.ServletContextHandler;
-import org.eclipse.jetty.servlet.ServletHandler;
-import org.eclipse.jetty.servlet.ServletHolder;
-import org.eclipse.jetty.util.IO;
-
-
-public class FauxContinuationTest extends ContinuationBase
-{
-    protected Server _server = new Server();
-    protected ServletHandler _servletHandler;
-    protected SelectChannelConnector _connector;
-    FilterHolder _filter;
-
-    protected void setUp() throws Exception
-    {
-        _connector = new SelectChannelConnector();
-        _server.setConnectors(new Connector[]{ _connector });
-        ServletContextHandler servletContext = new ServletContextHandler(ServletContextHandler.NO_SECURITY|ServletContextHandler.NO_SESSIONS);
-        _server.setHandler(servletContext);
-        _servletHandler=servletContext.getServletHandler();
-        ServletHolder holder=new ServletHolder(_servlet);
-        _servletHandler.addServletWithMapping(holder,"/");
-        
-        _filter=_servletHandler.addFilterWithMapping(ContinuationFilter.class,"/*",null);
-        _filter.setInitParameter("debug","true");
-        _filter.setInitParameter("faux","true");
-        _server.start();
-        _port=_connector.getLocalPort();
-        
-    }
-
-    protected void tearDown() throws Exception
-    {
-        _server.stop();
-    }
-
-    public void testContinuation() throws Exception
-    {
-        doNormal("FauxContinuation");
-    }
-    
-    public void testSleep() throws Exception
-    {
-        doSleep();
-    }
-
-    public void testSuspend() throws Exception
-    {
-        doSuspend();
-    }
-
-    public void testSuspendWaitResume() throws Exception
-    {
-        doSuspendWaitResume();
-    }
-
-    public void testSuspendResume() throws Exception
-    {
-        doSuspendResume();
-    }
-
-    public void testSuspendWaitComplete() throws Exception
-    {
-        doSuspendWaitComplete();
-    }
-
-    public void testSuspendComplete() throws Exception
-    {
-        doSuspendComplete();
-    }
-
-    public void testSuspendWaitResumeSuspendWaitResume() throws Exception
-    {
-        doSuspendWaitResumeSuspendWaitResume();
-    }
-    
-    public void testSuspendWaitResumeSuspendComplete() throws Exception
-    {
-        doSuspendWaitResumeSuspendComplete();
-    }
-
-    public void testSuspendWaitResumeSuspend() throws Exception
-    {
-        doSuspendWaitResumeSuspend();
-    }
-
-    public void testSuspendTimeoutSuspendResume() throws Exception
-    {
-        doSuspendTimeoutSuspendResume();
-    }
-
-    public void testSuspendTimeoutSuspendComplete() throws Exception
-    {
-        doSuspendTimeoutSuspendComplete();
-    }
-
-    public void testSuspendTimeoutSuspend() throws Exception
-    {
-        doSuspendTimeoutSuspend();
-    }
-
-    public void testSuspendThrowResume() throws Exception
-    {
-        doSuspendThrowResume();
-    }
-
-    public void testSuspendResumeThrow() throws Exception
-    {
-        doSuspendResumeThrow();
-    }
-
-    public void testSuspendThrowComplete() throws Exception
-    {
-        doSuspendThrowComplete();
-    }
-
-    public void testSuspendCompleteThrow() throws Exception
-    {
-        doSuspendCompleteThrow();
-    }
-    
-    
-    
-    protected String toString(InputStream in) throws IOException
-    {
-        return IO.toString(in);
-    }
-    
-}
diff --git a/test-jetty-nested/pom.xml b/test-jetty-nested/pom.xml
deleted file mode 100644
index 2570192..0000000
--- a/test-jetty-nested/pom.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<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>
-  <parent>
-    <groupId>org.eclipse.jetty</groupId>
-    <artifactId>jetty-project</artifactId>
-    <version>8.1.17.v20150415</version>
-  </parent>
-  <artifactId>test-jetty-nested</artifactId>
-  <name>Jetty :: Nested Test</name>
-  <url>http://www.eclipse.org/jetty</url>
-  <packaging>war</packaging>
-  <dependencies>
-    <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-nested</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-    <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-servlets</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-    <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-webapp</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-  </dependencies>
-</project>
diff --git a/test-jetty-nested/src/main/java/org/eclipse/jetty/nested/Dump.java b/test-jetty-nested/src/main/java/org/eclipse/jetty/nested/Dump.java
deleted file mode 100644
index 3424d0b..0000000
--- a/test-jetty-nested/src/main/java/org/eclipse/jetty/nested/Dump.java
+++ /dev/null
@@ -1,1021 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.nested;
-
-
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.io.PrintWriter;
-import java.io.Reader;
-import java.lang.reflect.Array;
-import java.lang.reflect.Field;
-import java.util.Date;
-import java.util.Enumeration;
-import java.util.Locale;
-import java.util.Map.Entry;
-
-import javax.servlet.ServletConfig;
-import javax.servlet.ServletContext;
-import javax.servlet.ServletException;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletRequestWrapper;
-import javax.servlet.ServletResponse;
-import javax.servlet.ServletResponseWrapper;
-import javax.servlet.UnavailableException;
-import javax.servlet.http.Cookie;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletRequestWrapper;
-import javax.servlet.http.HttpServletResponse;
-import javax.servlet.http.HttpServletResponseWrapper;
-
-import org.eclipse.jetty.continuation.Continuation;
-import org.eclipse.jetty.continuation.ContinuationListener;
-import org.eclipse.jetty.continuation.ContinuationSupport;
-import org.eclipse.jetty.http.HttpHeaders;
-import org.eclipse.jetty.util.StringUtil;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-
-
-
-/* ------------------------------------------------------------ */
-/** Dump Servlet Request.
- * 
- */
-public class Dump extends HttpServlet
-{
-    private static final Logger LOG = Log.getLogger(Dump.class);
-
-    boolean fixed;
-    
-    /* ------------------------------------------------------------ */
-    @Override
-    public void init(ServletConfig config) throws ServletException
-    {
-    	super.init(config);
-    	
-    	if (config.getInitParameter("unavailable")!=null && !fixed)
-    	{
-    	    
-    	    fixed=true;
-    	    throw new UnavailableException("Unavailable test",Integer.parseInt(config.getInitParameter("unavailable")));
-    	}
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
-    {
-        doGet(request, response);
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public void doGet(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException
-    {
-        // Handle a dump of data
-        final String data= request.getParameter("data");
-        final String chars= request.getParameter("chars");
-        final String block= request.getParameter("block");
-        final String dribble= request.getParameter("dribble");
-        final boolean flush= request.getParameter("flush")!=null?Boolean.parseBoolean(request.getParameter("flush")):false;
-
-        
-        if(request.getPathInfo()!=null && request.getPathInfo().toLowerCase(Locale.ENGLISH).indexOf("script")!=-1)
-        {
-            response.sendRedirect(response.encodeRedirectURL(getServletContext().getContextPath() + "/dump/info"));
-            return;
-        }
-            
-        request.setCharacterEncoding("UTF-8");
-        
-        if (request.getParameter("empty")!=null)
-        {
-            response.setStatus(200);
-            response.flushBuffer();
-            return;
-        }
-        
-        if (request.getParameter("sleep")!=null)
-        {
-            try
-            {
-                long s = Long.parseLong(request.getParameter("sleep"));
-                if (request.getHeader(HttpHeaders.EXPECT)!=null &&request.getHeader(HttpHeaders.EXPECT).indexOf("102")>=0)
-                {
-                    Thread.sleep(s/2);
-                    response.sendError(102);
-                    Thread.sleep(s/2);
-                }
-                else
-                    Thread.sleep(s);
-            }
-            catch (InterruptedException e)
-            {
-                return;
-            }
-            catch (Exception e)
-            {
-                throw new ServletException(e);
-            }
-        }
-
-        if (request.getAttribute("RESUME")==null && request.getParameter("resume")!=null)
-        {
-            request.setAttribute("RESUME",Boolean.TRUE);
-
-            final long resume=Long.parseLong(request.getParameter("resume"));
-            new Thread(new Runnable()
-            {
-                public void run()
-                {
-                    try
-                    {
-                        Thread.sleep(resume);
-                    }
-                    catch (InterruptedException e)
-                    {
-                        e.printStackTrace();
-                    }
-                    Continuation continuation = ContinuationSupport.getContinuation(request);
-                    continuation.resume();
-                }
-                
-            }).start();
-        }
-
-        if (request.getParameter("complete")!=null)
-        {
-            final long complete=Long.parseLong(request.getParameter("complete"));
-            new Thread(new Runnable()
-            {
-                public void run()
-                {
-                    try
-                    {
-                        Thread.sleep(complete);
-                    }
-                    catch (InterruptedException e)
-                    {
-                        e.printStackTrace();
-                    }
-                    try
-                    {
-                        response.setContentType("text/html");
-                        response.getOutputStream().println("<h1>COMPLETED</h1>"); 
-                        Continuation continuation = ContinuationSupport.getContinuation(request);
-                        continuation.complete();
-                    }
-                    catch (IOException e)
-                    {
-                        e.printStackTrace();
-                    }
-                }
-
-            }).start();
-        }
-        
-        if (request.getParameter("suspend")!=null && request.getAttribute("SUSPEND")!=Boolean.TRUE)
-        {
-            request.setAttribute("SUSPEND",Boolean.TRUE);
-            try
-            {
-                Continuation continuation = ContinuationSupport.getContinuation(request);
-                continuation.setTimeout(Long.parseLong(request.getParameter("suspend")));
-                continuation.suspend();
-                
-                continuation.addContinuationListener(new ContinuationListener()
-                {   
-                    public void onTimeout(Continuation continuation)
-                    {
-                        response.addHeader("Dump","onTimeout");
-                        try
-                        {
-                            dump(response,data,chars,block,dribble,flush);
-                            continuation.complete();
-                        }
-                        catch (IOException e)
-                        {
-                            LOG.ignore(e);
-                        }
-                    }
-                    
-                    public void onComplete(Continuation continuation)
-                    {
-                        response.addHeader("Dump","onComplete");
-                    }
-                });
-                
-                continuation.undispatch();
-            }
-            catch(Exception e)
-            {
-                throw new ServletException(e);
-            }
-        }        
-            
-        request.setAttribute("Dump", this);
-        getServletContext().setAttribute("Dump",this);
-        // getServletContext().log("dump "+request.getRequestURI());
-
-        // Force a content length response
-        String length= request.getParameter("length");
-        if (length != null && length.length() > 0)
-        {
-            response.setContentLength(Integer.parseInt(length));
-        }
-
-        // Handle a dump of data
-        if (dump(response,data,chars,block,dribble,flush))
-            return;
-        
-        // handle an exception
-        String info= request.getPathInfo();
-        if (info != null && info.endsWith("Exception"))
-        {
-            try
-            {
-                throw (Throwable) Thread.currentThread().getContextClassLoader().loadClass(info.substring(1)).newInstance();
-            }
-            catch (Throwable th)
-            {
-                throw new ServletException(th);
-            }
-        }
-
-        // test a reset
-        String reset= request.getParameter("reset");
-        if (reset != null && reset.length() > 0)
-        {
-            response.getOutputStream().println("THIS SHOULD NOT BE SEEN!");
-            response.setHeader("SHOULD_NOT","BE SEEN");
-            response.reset();
-        }
-        
-        
-        // handle an redirect
-        String redirect= request.getParameter("redirect");
-        if (redirect != null && redirect.length() > 0)
-        {
-            response.getOutputStream().println("THIS SHOULD NOT BE SEEN!");
-            response.sendRedirect(response.encodeRedirectURL(redirect));
-            try
-            {
-                response.getOutputStream().println("THIS SHOULD NOT BE SEEN!");
-            }
-            catch(IOException e)
-            {
-                // ignored as stream is closed.
-            }
-            return;
-        }
-
-        // handle an error
-        String error= request.getParameter("error");
-        if (error != null && error.length() > 0 && request.getAttribute("javax.servlet.error.status_code")==null)
-        {
-            response.getOutputStream().println("THIS SHOULD NOT BE SEEN!");
-            response.sendError(Integer.parseInt(error));
-            try
-            {
-                response.getOutputStream().println("THIS SHOULD NOT BE SEEN!");
-            }
-            catch(IllegalStateException e)
-            {
-                try
-                {
-                    response.getWriter().println("NOR THIS!!"); 
-                }
-                catch(IOException e2){}
-            }
-            catch(IOException e){}
-            return;
-        }
-
-        // Handle a extra headers 
-        String headers= request.getParameter("headers");
-        if (headers != null && headers.length() > 0)
-        {
-            long h=Long.parseLong(headers);
-            for (int i=0;i<h;i++)
-                response.addHeader("Header"+i,"Value"+i);
-        }
-
-        String buffer= request.getParameter("buffer");
-        if (buffer != null && buffer.length() > 0)
-            response.setBufferSize(Integer.parseInt(buffer));
-
-        String charset= request.getParameter("charset");
-        if (charset==null)
-            charset="UTF-8";
-        response.setCharacterEncoding(charset);
-        response.setContentType("text/html");
-
-        if (info != null && info.indexOf("Locale/") >= 0)
-        {
-            try
-            {
-                String locale_name= info.substring(info.indexOf("Locale/") + 7);
-                Field f= java.util.Locale.class.getField(locale_name);
-                response.setLocale((Locale)f.get(null));
-            }
-            catch (Exception e)
-            {
-                e.printStackTrace();
-                response.setLocale(Locale.getDefault());
-            }
-        }
-
-        String cn= request.getParameter("cookie");
-        String cv=request.getParameter("cookiev");
-        if (cn!=null && cv!=null)
-        {
-            Cookie cookie= new Cookie(cn, cv);
-            if (request.getParameter("version")!=null)
-                cookie.setVersion(Integer.parseInt(request.getParameter("version")));
-            cookie.setComment("Cookie from dump servlet");
-            response.addCookie(cookie);
-        }
-
-        String pi= request.getPathInfo();
-        if (pi != null && pi.startsWith("/ex"))
-        {
-            OutputStream out= response.getOutputStream();
-            out.write("</H1>This text should be reset</H1>".getBytes());
-            if ("/ex0".equals(pi))
-                throw new ServletException("test ex0", new Throwable());
-            else if ("/ex1".equals(pi))
-                throw new IOException("test ex1");
-            else if ("/ex2".equals(pi))
-                throw new UnavailableException("test ex2");
-            else if (pi.startsWith("/ex3/"))
-                throw new UnavailableException("test ex3",Integer.parseInt(pi.substring(5)));
-            throw new RuntimeException("test");
-        }
-
-        if ("true".equals(request.getParameter("close")))
-            response.setHeader("Connection","close");
-
-        String buffered= request.getParameter("buffered");
-        
-        PrintWriter pout=null;
-        
-        try
-        {
-            pout =response.getWriter();
-        }
-        catch(IllegalStateException e)
-        {
-            pout=new PrintWriter(new OutputStreamWriter(response.getOutputStream(),charset));
-        }
-        if (buffered!=null)
-            pout = new PrintWriter(new BufferedWriter(pout,Integer.parseInt(buffered)));
-        
-        try
-        {
-            pout.write("<html>\n<body>\n");
-            pout.write("<h1>Dump Servlet</h1>\n");
-            pout.write("<table width=\"95%\">");
-            pout.write("<tr>\n");
-            pout.write("<th align=\"right\">getMethod: </th>");
-            pout.write("<td>" + notag(request.getMethod())+"</td>");
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"right\">getContentLength: </th>");
-            pout.write("<td>"+Integer.toString(request.getContentLength())+"</td>");
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"right\">getContentType: </th>");
-            pout.write("<td>"+notag(request.getContentType())+"</td>");
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"right\">getRequestURI: </th>");
-            pout.write("<td>"+notag(request.getRequestURI())+"</td>");
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"right\">getRequestURL: </th>");
-            pout.write("<td>"+notag(request.getRequestURL().toString())+"</td>");
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"right\">getContextPath: </th>");
-            pout.write("<td>"+request.getContextPath()+"</td>");
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"right\">getServletPath: </th>");
-            pout.write("<td>"+notag(request.getServletPath())+"</td>");
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"right\">getPathInfo: </th>");
-            pout.write("<td>"+notag(request.getPathInfo())+"</td>");
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"right\">getPathTranslated: </th>");
-            pout.write("<td>"+notag(request.getPathTranslated())+"</td>");
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"right\">getQueryString: </th>");
-            pout.write("<td>"+notag(request.getQueryString())+"</td>");
-            pout.write("</tr><tr>\n");
-            
-            pout.write("<th align=\"right\">getProtocol: </th>");
-            pout.write("<td>"+request.getProtocol()+"</td>");
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"right\">getScheme: </th>");
-            pout.write("<td>"+request.getScheme()+"</td>");
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"right\">getServerName: </th>");
-            pout.write("<td>"+notag(request.getServerName())+"</td>");
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"right\">getServerPort: </th>");
-            pout.write("<td>"+Integer.toString(request.getServerPort())+"</td>");
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"right\">getLocalName: </th>");
-            pout.write("<td>"+request.getLocalName()+"</td>");
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"right\">getLocalAddr: </th>");
-            pout.write("<td>"+request.getLocalAddr()+"</td>");
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"right\">getLocalPort: </th>");
-            pout.write("<td>"+Integer.toString(request.getLocalPort())+"</td>");
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"right\">getRemoteUser: </th>");
-            pout.write("<td>"+request.getRemoteUser()+"</td>");
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"right\">getUserPrincipal: </th>");
-            pout.write("<td>"+request.getUserPrincipal()+"</td>");
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"right\">getRemoteAddr: </th>");
-            pout.write("<td>"+request.getRemoteAddr()+"</td>");
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"right\">getRemoteHost: </th>");
-            pout.write("<td>"+request.getRemoteHost()+"</td>");
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"right\">getRemotePort: </th>");
-            pout.write("<td>"+request.getRemotePort()+"</td>");
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"right\">getRequestedSessionId: </th>");
-            pout.write("<td>"+request.getRequestedSessionId()+"</td>");
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"right\">isSecure(): </th>");
-            pout.write("<td>"+request.isSecure()+"</td>");
-
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"right\">isUserInRole(admin): </th>");
-            pout.write("<td>"+request.isUserInRole("admin")+"</td>");
-
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"right\">getLocale: </th>");
-            pout.write("<td>"+request.getLocale()+"</td>");
-            
-            Enumeration locales= request.getLocales();
-            while (locales.hasMoreElements())
-            {
-                pout.write("</tr><tr>\n");
-                pout.write("<th align=\"right\">getLocales: </th>");
-                pout.write("<td>"+locales.nextElement()+"</td>");
-            }
-            pout.write("</tr><tr>\n");
-            
-            pout.write("<th align=\"left\" colspan=\"2\"><big><br/>Other HTTP Headers:</big></th>");
-            Enumeration h= request.getHeaderNames();
-            String name;
-            while (h.hasMoreElements())
-            {
-                name= (String)h.nextElement();
-
-                Enumeration h2= request.getHeaders(name);
-                while (h2.hasMoreElements())
-                {
-                    String hv= (String)h2.nextElement();
-                    pout.write("</tr><tr>\n");
-                    pout.write("<th align=\"right\">"+notag(name)+": </th>");
-                    pout.write("<td>"+notag(hv)+"</td>");
-                }
-            }
-
-            if ("true".equals(request.getParameter("env")))
-            {
-                pout.write("</tr><tr>\n");
-                pout.write("<th align=\"left\" colspan=\"2\"><big><br/>Environment System-Properties: </big></th>");
-
-                for (Entry<Object, Object> e : System.getProperties().entrySet())
-                {
-                    pout.write("</tr><tr>\n");
-                    pout.write("<th align=\"right\">" + notag(String.valueOf(e.getKey())) + ": </th>");
-                    pout.write("<td>"+notag(String.valueOf(e.getValue()))+"</td>");
-                }
-            }
-
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"left\" colspan=\"2\"><big><br/>Request Parameters:</big></th>");
-            h= request.getParameterNames();
-            while (h.hasMoreElements())
-            {
-                name= (String)h.nextElement();
-                pout.write("</tr><tr>\n");
-                pout.write("<th align=\"right\">"+notag(name)+": </th>");
-                pout.write("<td>"+notag(request.getParameter(name))+"</td>");
-                String[] values= request.getParameterValues(name);
-                if (values == null)
-                {
-                    pout.write("</tr><tr>\n");
-                    pout.write("<th align=\"right\">"+notag(name)+" Values: </th>");
-                    pout.write("<td>"+"NULL!"+"</td>");
-                }
-                else if (values.length > 1)
-                {
-                    for (int i= 0; i < values.length; i++)
-                    {
-                        pout.write("</tr><tr>\n");
-                        pout.write("<th align=\"right\">"+notag(name)+"["+i+"]: </th>");
-                        pout.write("<td>"+notag(values[i])+"</td>");
-                    }
-                }
-            }
-
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"left\" colspan=\"2\"><big><br/>Cookies:</big></th>");
-            Cookie[] cookies = request.getCookies();
-            for (int i=0; cookies!=null && i<cookies.length;i++)
-            {
-                Cookie cookie = cookies[i];
-
-                pout.write("</tr><tr>\n");
-                pout.write("<th align=\"right\">"+notag(cookie.getName())+": </th>");
-                pout.write("<td>"+notag(cookie.getValue())+"</td>");
-            }
-            
-            String content_type=request.getContentType();
-            if (content_type!=null &&
-                !content_type.startsWith("application/x-www-form-urlencoded") &&
-                !content_type.startsWith("multipart/form-data"))
-            {
-                pout.write("</tr><tr>\n");
-                pout.write("<th align=\"left\" valign=\"top\" colspan=\"2\"><big><br/>Content:</big></th>");
-                pout.write("</tr><tr>\n");
-                pout.write("<td><pre>");
-                char[] content= new char[4096];
-                int len;
-                try{
-                    Reader in=request.getReader();
-                    
-                    while((len=in.read(content))>=0)
-                        pout.write(notag(new String(content,0,len)));
-                }
-                catch(IOException e)
-                {
-                    pout.write(e.toString());
-                }
-                
-                pout.write("</pre></td>");
-            }
-            
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"left\" colspan=\"2\"><big><br/>Request Attributes:</big></th>");
-            Enumeration a= request.getAttributeNames();
-            while (a.hasMoreElements())
-            {
-                name= (String)a.nextElement();
-                pout.write("</tr><tr>\n");
-                pout.write("<th align=\"right\" valign=\"top\">"+name.replace("."," .")+": </th>");
-                Object value=request.getAttribute(name);
-                if (value instanceof File)
-                {
-                    File file = (File)value;
-                    pout.write("<td>"+"<pre>" + file.getName()+" ("+file.length()+" "+new Date(file.lastModified())+ ")</pre>"+"</td>");
-                }
-                else
-                    pout.write("<td>"+"<pre>" + toString(request.getAttribute(name)) + "</pre>"+"</td>");
-            }
-            request.setAttribute("org.eclipse.jetty.servlet.MultiPartFilter.files",null);
-
-            
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"left\" colspan=\"2\"><big><br/>Servlet InitParameters:</big></th>");
-            a= getInitParameterNames();
-            while (a.hasMoreElements())
-            {
-                name= (String)a.nextElement();
-                pout.write("</tr><tr>\n");
-                pout.write("<th align=\"right\">"+name+": </th>");
-                pout.write("<td>"+ toString(getInitParameter(name)) +"</td>");
-            }
-
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"left\" colspan=\"2\"><big><br/>ServletContext Misc:</big></th>");
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"right\" valign=\"top\">"+"servletContext.getContextPath()"+": </th>");
-            pout.write("<td>"+ getServletContext().getContextPath() + "</td>");
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"right\" valign=\"top\">"+"getServletContext().getRealPath(\"/WEB-INF/\")"+": </th>");
-            pout.write("<td>"+ getServletContext().getRealPath("/WEB-INF/") + "</td>");
-            String webinfRealPath = getServletContext().getRealPath("/WEB-INF/");
-            if (webinfRealPath != null)
-            {
-                try
-                {
-                    File webInfRealPathFile = new File(webinfRealPath);
-                    pout.write("</tr><tr>\n");
-                    pout.write("<th align=\"right\" valign=\"top\">"+"new File(getServletContext().getRealPath(\"/WEB-INF/\"))"+": </th>");
-                    pout.write("<td>exists()="+ webInfRealPathFile.exists()
-                            + "; isFile()="+webInfRealPathFile.isFile()
-                            + "; isDirectory()="+webInfRealPathFile.isDirectory()
-                            + "; isAbsolute()=" + webInfRealPathFile.isAbsolute()
-                            + "; canRead()=" + webInfRealPathFile.canRead()
-                            + "; canWrite()=" + webInfRealPathFile.canWrite()
-                            +"</td>");
-                    if (webInfRealPathFile.exists() && webInfRealPathFile.isDirectory())
-                    {
-                        File webxml = new File(webInfRealPathFile, "web.xml");
-                        pout.write("</tr><tr>\n");
-                        pout.write("<th align=\"right\" valign=\"top\">"+"new File(getServletContext().getRealPath(\"/WEB-INF/web.xml\"))"+": </th>");
-                        pout.write("<td>exists()="+ webxml.exists()
-                                + "; isFile()="+webxml.isFile()
-                                + "; isDirectory()="+webxml.isDirectory()
-                                + "; isAbsolute()=" + webxml.isAbsolute()
-                                + "; canRead()=" + webxml.canRead()
-                                + "; canWrite()=" + webxml.canWrite()
-                                +"</td>");
-                    }
-                }
-                catch (Throwable t)
-                {
-                    pout.write("<th align=\"right\" valign=\"top\">"+"Error probing the java.io.File(getServletContext().getRealPath(\"/WEB-INF/\"))"+": </th>");
-                    pout.write("<td>"+ t + "</td>");
-                }
-            }
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"right\" valign=\"top\">"+"getServletContext().getServerInfo()"+": </th>");
-            pout.write("<td>"+ getServletContext().getServerInfo() + "</td>");
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"right\" valign=\"top\">"+"getServletContext().getServletContextName()"+": </th>");
-            pout.write("<td>"+ getServletContext().getServletContextName() + "</td>");
-
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"left\" colspan=\"2\"><big><br/>Context InitParameters:</big></th>");
-            a= getServletContext().getInitParameterNames();
-            while (a.hasMoreElements())
-            {
-                name= (String)a.nextElement();
-                pout.write("</tr><tr>\n");
-                pout.write("<th align=\"right\" valign=\"top\">"+name.replace("."," .")+": </th>");
-                pout.write("<td>"+ toString(getServletContext().getInitParameter(name)) + "</td>");
-            }
-
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"left\" colspan=\"2\"><big><br/>Context Attributes:</big></th>");
-            a= getServletContext().getAttributeNames();
-            while (a.hasMoreElements())
-            {
-                name= (String)a.nextElement();
-                pout.write("</tr><tr>\n");
-                pout.write("<th align=\"right\" valign=\"top\">"+name.replace("."," .")+": </th>");
-                pout.write("<td>"+"<pre>" + toString(getServletContext().getAttribute(name)) + "</pre>"+"</td>");
-            }
-
-            String res= request.getParameter("resource");
-            if (res != null && res.length() > 0)
-            {
-                pout.write("</tr><tr>\n");
-                pout.write("<th align=\"left\" colspan=\"2\"><big><br/>Get Resource: \""+res+"\"</big></th>");
-
-                pout.write("</tr><tr>\n");
-                pout.write("<th align=\"right\">getServletContext().getContext(...): </th>");
-                
-                ServletContext context = getServletContext().getContext(res);
-                pout.write("<td>"+context+"</td>");
-                
-                if (context!=null)
-                {
-                    String cp=context.getContextPath();
-                    if (cp==null || "/".equals(cp))
-                        cp="";
-                    pout.write("</tr><tr>\n");
-                    pout.write("<th align=\"right\">getServletContext().getContext(...),getRequestDispatcher(...): </th>");
-                    pout.write("<td>"+getServletContext().getContext(res).getRequestDispatcher(res.substring(cp.length()))+"</td>");
-                }
-
-                pout.write("</tr><tr>\n");
-                pout.write("<th align=\"right\">this.getClass().getResource(...): </th>");
-                pout.write("<td>"+this.getClass().getResource(res)+"</td>");
-
-                pout.write("</tr><tr>\n");
-                pout.write("<th align=\"right\">this.getClass().getClassLoader().getResource(...): </th>");
-                pout.write("<td>"+this.getClass().getClassLoader().getResource(res)+"</td>");
-
-                pout.write("</tr><tr>\n");
-                pout.write("<th align=\"right\">Thread.currentThread().getContextClassLoader().getResource(...): </th>");
-                pout.write("<td>"+Thread.currentThread().getContextClassLoader().getResource(res)+"</td>");
-
-                pout.write("</tr><tr>\n");
-                pout.write("<th align=\"right\">getServletContext().getResource(...): </th>");
-                try{pout.write("<td>"+getServletContext().getResource(res)+"</td>");}
-                catch(Exception e) {pout.write("<td>"+"" +e+"</td>");}
-            }
-            
-            pout.write("</tr></table>\n");
-
-            /* ------------------------------------------------------------ */
-            pout.write("<h2>Request Wrappers</h2>\n");
-            ServletRequest rw=request;
-            int w=0;
-            while (rw !=null)
-            {
-                pout.write((w++)+": "+rw.getClass().getName()+"<br/>");
-                if (rw instanceof HttpServletRequestWrapper)
-                    rw=((HttpServletRequestWrapper)rw).getRequest();
-                else if (rw instanceof ServletRequestWrapper)
-                    rw=((ServletRequestWrapper)rw).getRequest();
-                else
-                    rw=null;
-            }
-
-            /* ------------------------------------------------------------ */
-            pout.write("<h2>Response Wrappers</h2>\n");
-            ServletResponse rsw=response;
-            w=0;
-            while (rsw !=null)
-            {
-                pout.write((w++)+": "+rsw.getClass().getName()+"<br/>");
-                if (rsw instanceof HttpServletResponseWrapper)
-                    rsw=((HttpServletResponseWrapper)rsw).getResponse();
-                else if (rsw instanceof ServletResponseWrapper)
-                    rsw=((ServletResponseWrapper)rsw).getResponse();
-                else
-                    rsw=null;
-            }
-            
-            pout.write("<br/>");
-            pout.write("<h2>International Characters (UTF-8)</h2>");
-            pout.write("LATIN LETTER SMALL CAPITAL AE<br/>\n");
-            pout.write("Directly uni encoded(\\u1d01): \u1d01<br/>");
-            pout.write("HTML reference (&AElig;): Æ<br/>");
-            pout.write("Decimal (&#7425;): ᴁ<br/>");
-            pout.write("Javascript unicode (\\u1d01) : <script language='javascript'>document.write(\"\u1d01\");</script><br/>");
-            pout.write("<br/>");
-            pout.write("<h2>Form to generate GET content</h2>");
-            pout.write("<form method=\"GET\" action=\""+response.encodeURL(getURI(request))+"\">");
-            pout.write("TextField: <input type=\"text\" name=\"TextField\" value=\"value\"/><br/>\n");
-            pout.write("<input type=\"submit\" name=\"Action\" value=\"Submit\">");
-            pout.write("</form>");
-
-            pout.write("<br/>");
-            
-            pout.write("<h2>Form to generate POST content</h2>");
-            pout.write("<form method=\"POST\" accept-charset=\"utf-8\" action=\""+response.encodeURL(getURI(request))+"\">");
-            pout.write("TextField: <input type=\"text\" name=\"TextField\" value=\"value\"/><br/>\n");
-            pout.write("Select: <select multiple name=\"Select\">\n");
-            pout.write("<option>ValueA</option>");
-            pout.write("<option>ValueB1,ValueB2</option>");
-            pout.write("<option>ValueC</option>");
-            pout.write("</select><br/>");
-            pout.write("<input type=\"submit\" name=\"Action\" value=\"Submit\"><br/>");
-            pout.write("</form>");
-            pout.write("<br/>");
-            
-            pout.write("<h2>Form to generate UPLOAD content</h2>");
-            pout.write("<form method=\"POST\" enctype=\"multipart/form-data\" accept-charset=\"utf-8\" action=\""+
-                    response.encodeURL(getURI(request))+(request.getQueryString()==null?"":("?"+request.getQueryString()))+
-                    "\">");
-            pout.write("TextField: <input type=\"text\" name=\"TextField\" value=\"comment\"/><br/>\n");
-            pout.write("File 1: <input type=\"file\" name=\"file1\" /><br/>\n");
-            pout.write("File 2: <input type=\"file\" name=\"file2\" /><br/>\n");
-            pout.write("<input type=\"submit\" name=\"Action\" value=\"Submit\"><br/>");
-            pout.write("</form>");
-
-            pout.write("<h2>Form to set Cookie</h2>");
-            pout.write("<form method=\"POST\" action=\""+response.encodeURL(getURI(request))+"\">");
-            pout.write("cookie: <input type=\"text\" name=\"cookie\" /><br/>\n");
-            pout.write("value: <input type=\"text\" name=\"cookiev\" /><br/>\n");
-            pout.write("<input type=\"submit\" name=\"Action\" value=\"setCookie\">");
-            pout.write("</form>\n");
-            
-            pout.write("<h2>Form to get Resource</h2>");
-            pout.write("<form method=\"POST\" action=\""+response.encodeURL(getURI(request))+"\">");
-            pout.write("resource: <input type=\"text\" name=\"resource\" /><br/>\n");
-            pout.write("<input type=\"submit\" name=\"Action\" value=\"getResource\">");
-            pout.write("</form>\n");
-        }
-        catch (Exception e)
-        {
-            getServletContext().log("dump", e);
-        }
-        
-        String lines= request.getParameter("lines");
-        if (lines!=null)
-        {
-            char[] line = "<span>A line of characters. Blah blah blah blah.  blooble blooble</span></br>\n".toCharArray();
-            for (int l=Integer.parseInt(lines);l-->0;)
-            {
-                pout.write("<span>"+l+" </span>");
-                pout.write(line);
-            }
-        }
-        
-        pout.write("</body>\n</html>\n");
-        
-        pout.close();
-
-        if (pi != null)
-        {
-            if ("/ex4".equals(pi))
-                throw new ServletException("test ex4", new Throwable());
-            if ("/ex5".equals(pi))
-                throw new IOException("test ex5");
-            if ("/ex6".equals(pi))
-                throw new UnavailableException("test ex6");
-        }
-
-
-    }
-
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public String getServletInfo()
-    {
-        return "Dump Servlet";
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public synchronized void destroy()
-    {
-    }
-
-    /* ------------------------------------------------------------ */
-    private String getURI(HttpServletRequest request)
-    {
-        String uri= (String)request.getAttribute("javax.servlet.forward.request_uri");
-        if (uri == null)
-            uri= request.getRequestURI();
-        return uri;
-    }
-
-    /* ------------------------------------------------------------ */
-    private static String toString(Object o)
-    {
-        if (o == null)
-            return null;
-
-        try
-        {
-            if (o.getClass().isArray())
-            {
-                StringBuffer sb = new StringBuffer();
-                if (!o.getClass().getComponentType().isPrimitive())
-                {
-                    Object[] array= (Object[])o;
-                    for (int i= 0; i < array.length; i++)
-                    {
-                        if (i > 0)
-                            sb.append("\n");
-                        sb.append(array.getClass().getComponentType().getName());
-                        sb.append("[");
-                        sb.append(i);
-                        sb.append("]=");
-                        sb.append(toString(array[i]));
-                    }
-                    return sb.toString();
-                }
-                else
-                { 
-                    int length = Array.getLength(o);
-                    for (int i=0;i<length;i++)
-                    {
-                        if (i > 0)
-                            sb.append("\n");
-                        sb.append(o.getClass().getComponentType().getName()); 
-                        sb.append("[");
-                        sb.append(i);
-                        sb.append("]=");
-                        sb.append(toString(Array.get(o, i)));
-                    }
-                    return sb.toString();
-                }
-            }
-            else
-                return o.toString();
-        }
-        catch (Exception e)
-        {
-            return e.toString();
-        }
-    }
-
-    private boolean dump(HttpServletResponse response, String data, String chars, String block, String dribble, boolean flush) throws IOException
-    {
-        if (data != null && data.length() > 0)
-        {
-            long d=Long.parseLong(data);
-            int b=(block!=null&&block.length()>0)?Integer.parseInt(block):50;
-            byte[] buf=new byte[b];
-            for (int i=0;i<b;i++)
-            {
-                
-                buf[i]=(byte)('0'+(i%10));
-                if (i%10==9)
-                    buf[i]=(byte)'\n';
-            }
-            buf[0]='o';
-            OutputStream out=response.getOutputStream();
-            response.setContentType("text/plain");
-            while (d > 0)
-            {
-                if (b==1)
-                {
-                    out.write(d%80==0?'\n':'.');
-                    d--;
-                }
-                else if (d>=b)
-                {
-                    out.write(buf);
-                    d=d-b;
-                }
-                else
-                {
-                    out.write(buf,0,(int)d);
-                    d=0;
-                }
-                
-                if (dribble!=null)
-                {
-                    out.flush();
-                    try
-                    {
-                        Thread.sleep(Long.parseLong(dribble));
-                    }
-                    catch (Exception e)
-                    {
-                        e.printStackTrace();
-                        break;
-                    }
-                }
-                
-            }
-            
-            if (flush)
-                out.flush();
-            
-            return true;
-        }
-
-        // Handle a dump of data
-        if (chars != null && chars.length() > 0)
-        {
-            long d=Long.parseLong(chars);
-            int b=(block!=null&&block.length()>0)?Integer.parseInt(block):50;
-            char[] buf=new char[b];
-            for (int i=0;i<b;i++)
-            {
-                buf[i]=(char)('0'+(i%10));
-                if (i%10==9)
-                    buf[i]='\n';
-            }
-            buf[0]='o';
-            response.setContentType("text/plain");
-            PrintWriter out=response.getWriter();
-            while (d > 0 && !out.checkError())
-            {
-                if (b==1)
-                {
-                    out.write(d%80==0?'\n':'.');
-                    d--;
-                }
-                else if (d>=b)
-                {
-                    out.write(buf);
-                    d=d-b;
-                }
-                else
-                {
-                    out.write(buf,0,(int)d);
-                    d=0;
-                }
-            }
-            return true;
-        }    
-        return false;
-    }
-    
-    private String notag(String s)
-    {
-        if (s==null)
-            return "null";
-        s=StringUtil.replace(s,"&","&");
-        s=StringUtil.replace(s,"<","<");
-        s=StringUtil.replace(s,">",">");
-        return s;
-    }
-}
diff --git a/test-jetty-nested/src/main/java/org/eclipse/jetty/nested/NestedJettyServlet.java b/test-jetty-nested/src/main/java/org/eclipse/jetty/nested/NestedJettyServlet.java
deleted file mode 100644
index d32f090..0000000
--- a/test-jetty-nested/src/main/java/org/eclipse/jetty/nested/NestedJettyServlet.java
+++ /dev/null
@@ -1,143 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.nested;
-
-import java.io.File;
-import java.io.IOException;
-
-import javax.servlet.Servlet;
-import javax.servlet.ServletConfig;
-import javax.servlet.ServletContext;
-import javax.servlet.ServletException;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jetty.server.LocalConnector;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.bio.SocketConnector;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.thread.ThreadPool;
-import org.eclipse.jetty.webapp.WebAppContext;
-import org.eclipse.jetty.xml.XmlConfiguration;
-
-/**
- * Nested Jetty Servlet.
- * <p>
- * This servlet runs Jetty as a nested server inside another servlet container.   The requests received by
- * this servlet are routed via a {@link NestedConnector} to the nested jetty servlet and handled by jetty contexts,
- * handlers, webapps and/or servlets.
- * <p>
- * The servlet can be configured with the following init parameters:<ul>
- * <li>debug - if true then jetty debugging is turned on</li>
- * <li>webapp - set to the resource path of the webapplication to deploy
- * <li>jetty.xml - set the the resource path of a jetty xml file used to configure the server
- * </ul>
- *
- */
-public class NestedJettyServlet implements Servlet
-{
-    private Server _server;
-    private ServletConfig _config;
-    private ServletContext _context;
-    private NestedConnector _connector;
-    
-    public void init(ServletConfig config) throws ServletException
-    {    
-        ClassLoader orig = Thread.currentThread().getContextClassLoader();
-        try
-        {
-            Thread.currentThread().setContextClassLoader(NestedJettyServlet.class.getClassLoader());
-            _config=config;
-            _context=config.getServletContext();
-            
-            Log.getLog().setDebugEnabled(Boolean.parseBoolean(_config.getInitParameter("debug")));
-            
-            String jetty_xml=config.getInitParameter("jetty.xml");
-            if (jetty_xml!=null)
-            {
-                XmlConfiguration xml_config = new XmlConfiguration(_context.getResourceAsStream(jetty_xml));
-                _server=(Server)xml_config.configure();
-            }
-            if (_server==null)
-                _server=new Server();
-            
-            if (_server.getConnectors().length==0)
-            {
-                _connector=new NestedConnector();
-                _server.addConnector(_connector);
-            }
-            else
-                _connector=(NestedConnector)_server.getConnectors()[0];
-            
-            WebAppContext webapp = new WebAppContext();
-            
-            webapp.setContextPath(_context.getContextPath());
-            webapp.setTempDirectory(new File((File)_context.getAttribute("javax.servlet.context.tempdir"),"jetty"));
-            String docroot=config.getInitParameter("webapp");
-           
-            String realpath=_context.getRealPath(docroot);
-            if (realpath!=null)
-                webapp.setWar(realpath);
-            else
-                webapp.setWar(_context.getResource(docroot).toString());
-
-            _server.setHandler(webapp);
-
-            _server.start();
-            _context.log("Started Jetty/"+_server.getVersion()+" for "+webapp.getWar()+" nested in "+_context.getServerInfo());
-        }
-        catch(Exception e)
-        {
-            throw new ServletException(e);
-        }
-        finally
-        {
-            Thread.currentThread().setContextClassLoader(orig);
-        }
-    }
-
-    public ServletConfig getServletConfig()
-    {
-        return _config;
-    }
-
-    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException
-    {
-        _connector.service(req,res);
-    }
-
-    public String getServletInfo()
-    {
-        return this.toString();
-    }
-
-    public void destroy()
-    {
-        try
-        {
-            _server.stop();
-        }
-        catch(Exception e)
-        {
-            _context.log("stopping",e);
-        }
-    }
-}
diff --git a/test-jetty-nested/src/main/java/org/eclipse/jetty/nested/TestServlet.java b/test-jetty-nested/src/main/java/org/eclipse/jetty/nested/TestServlet.java
deleted file mode 100644
index de4398a..0000000
--- a/test-jetty-nested/src/main/java/org/eclipse/jetty/nested/TestServlet.java
+++ /dev/null
@@ -1,203 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.nested;
-
-import java.io.BufferedInputStream;
-import java.io.BufferedReader;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.PrintStream;
-import java.net.InetSocketAddress;
-import java.net.ServerSocket;
-import java.net.Socket;
-import java.net.SocketAddress;
-import java.util.concurrent.CountDownLatch;
-
-import javax.servlet.ServletException;
-import javax.servlet.ServletOutputStream;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jetty.util.IO;
-import org.eclipse.jetty.util.TypeUtil;
-
-public class TestServlet extends HttpServlet
-{
-
-    /* (non-Javadoc)
-     * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
-     */
-    @Override
-    protected void doGet(final HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
-    {
-        resp.setContentType("text/plain");
-        final PrintStream out = new PrintStream(resp.getOutputStream());
-        
-        out.println("Try out evil things.");
-
-        try
-        {
-            out.println("\nList home dir...");
-            for (File f : new File("/home").listFiles())
-                out.println(f);
-        }
-        catch(Throwable e)
-        {
-            e.printStackTrace(out);
-        }
-        try
-        {
-            out.println("\nList tmp dir...");
-            for (File f : new File("/var/tmp").listFiles())
-                out.println(f);
-        }
-        catch(Throwable e)
-        {
-            e.printStackTrace(out);
-        }
-        
-        try
-        {
-            out.println("\nCreate a /var/tmp file...");
-            File file = new File("/var/tmp/eviltest");
-
-            out.println(file+" exists="+file.exists());
-            file.createNewFile();
-            file.deleteOnExit();
-            out.println(file+" exists="+file.exists());
-            file.delete();
-        }
-        catch(Throwable e)
-        {
-            e.printStackTrace(out);
-        }
-        
-
-        try
-        {
-            out.println("\nOpen a localhost server socket ...");
-            
-            ServerSocket socket = new ServerSocket();
-            socket.bind(new InetSocketAddress("localhost",0));
-            out.println("local port = "+socket.getLocalPort());
-        }
-        catch(Throwable e)
-        {
-            e.printStackTrace(out);
-        }
-        
-        try
-        {
-            out.println("\nOpen a any server socket ...");
-            
-            ServerSocket socket = new ServerSocket();
-            socket.bind(new InetSocketAddress(0));
-            out.println("local port = "+socket.getLocalPort());
-        }
-        catch(Throwable e)
-        {
-            e.printStackTrace(out);
-        }
-        try
-        {
-            out.println("\nTalk to any server socket ...");
-            
-            final ServerSocket server = new ServerSocket();
-            server.bind(new InetSocketAddress(0));
-            out.println("local port = "+server.getLocalPort());
-            final int port = server.getLocalPort();
-            
-            final CountDownLatch latch = new CountDownLatch(1);
-            
-            new Thread()
-            {
-                public void run()
-                {
-                    try
-                    {
-                        Socket inbound = server.accept();
-                        out.println("accepted "+inbound);
-                        BufferedReader in = new BufferedReader(new InputStreamReader(inbound.getInputStream()));
-                        String data= in.readLine();
-                        out.println("read "+data);
-                    }
-                    catch(Throwable e)
-                    {
-                        e.printStackTrace(out);
-                    }
-                    finally
-                    {
-                        latch.countDown();
-                    }
-                }
-            }.start();
-            
-           
-            Socket socket = new Socket("localhost",port); 
-            socket.getOutputStream().write("Hello World\n".getBytes());
-            
-            latch.await();
-            socket.close();
-        }
-        catch(Throwable e)
-        {
-            e.printStackTrace(out);
-        }
-        
-        try
-        {
-            out.println("\nRead to own content ...");
-            out.println("Real path / = "+getServletContext().getRealPath("/"));
-           
-            for (File f : new File(getServletContext().getRealPath("/")).listFiles())
-                out.println(f);
-            
-        }
-        catch(Throwable e)
-        {
-            e.printStackTrace(out);
-        }
-        
-        
-        try
-        {
-            out.println("\nWrite own content ...");
-            
-            File wibble = new File(getServletContext().getRealPath("/wibble.txt"));
-            if (!wibble.exists())
-                wibble.createNewFile();
-            
-            for (File f : new File(getServletContext().getRealPath("/")).listFiles())
-                out.println(f);
-            
-        }
-        catch(Throwable e)
-        {
-            e.printStackTrace(out);
-        }
-     
-        out.flush();
-        out.close();
-    }
-    
-
-}
diff --git a/test-jetty-nested/src/main/webapp/WEB-INF/jetty.xml b/test-jetty-nested/src/main/webapp/WEB-INF/jetty.xml
deleted file mode 100644
index b90f1e1..0000000
--- a/test-jetty-nested/src/main/webapp/WEB-INF/jetty.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<?xml version="1.0"?>
-<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
-
-<!-- =============================================================== -->
-<!-- Configure the Nested Jetty Server                               -->
-<!-- =============================================================== -->
-
-<Configure id="Server" class="org.eclipse.jetty.server.Server">
-    <!-- =========================================================== -->
-    <!-- Set connectors                                              -->
-    <!-- =========================================================== -->
-    <Call name="addConnector">
-      <Arg>
-          <New class="org.eclipse.jetty.nested.NestedConnector">
-            <Set name="statsOn">false</Set>
-            <Set name="forwarded">true</Set>
-            <Set name="forwardedHostHeader">x-forwarded_for</Set>
-            <Set name="forwardedCipherSuiteHeader">sslclientcipher</Set>
-            <Set name="forwardedSslSessionIdHeader">sslsessionid</Set>
-          </New>
-      </Arg>
-    </Call>
-
-    <!-- =========================================================== -->
-    <!-- extra options                                               -->
-    <!-- =========================================================== -->
-    <Set name="stopAtShutdown">true</Set>
-    <Set name="sendServerVersion">true</Set>
-    <Set name="sendDateHeader">true</Set>
-    <Set name="gracefulShutdown">1000</Set>
-    <Set name="dumpAfterStart">true</Set>
-    <Set name="dumpBeforeStop">false</Set>
-
-</Configure>
diff --git a/test-jetty-nested/src/main/webapp/WEB-INF/web.xml b/test-jetty-nested/src/main/webapp/WEB-INF/web.xml
deleted file mode 100644
index 91213f4..0000000
--- a/test-jetty-nested/src/main/webapp/WEB-INF/web.xml
+++ /dev/null
@@ -1,52 +0,0 @@
-<?xml version="1.0" encoding="ISO-8859-1"?>
-<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>Nested WebApp</display-name>
-  
-  <servlet>
-    <servlet-name>nestedJetty</servlet-name>
-    <servlet-class>org.eclipse.jetty.nested.NestedJettyServlet</servlet-class>
-    <init-param>
-      <param-name>debug</param-name>
-      <param-value>true</param-value>
-    </init-param>
-    <init-param>
-      <param-name>webapp</param-name>
-      <param-value>/nested</param-value>
-    </init-param>
-    <init-param>
-      <param-name>jetty.xml</param-name>
-      <param-value>/WEB-INF/jetty.xml</param-value>
-    </init-param>
-    <load-on-startup>0</load-on-startup>
-  </servlet>
-  <servlet-mapping>
-    <servlet-name>nestedJetty</servlet-name>
-    <url-pattern>/*</url-pattern>  
-  </servlet-mapping>
-  
-  <servlet>
-    <servlet-name>test</servlet-name>
-    <servlet-class>org.eclipse.jetty.nested.TestServlet</servlet-class>
-  </servlet>
-  <servlet-mapping>
-    <servlet-name>test</servlet-name>
-    <url-pattern>/test/*</url-pattern>  
-  </servlet-mapping>
-  
-  <servlet>
-    <servlet-name>dump</servlet-name>
-    <servlet-class>org.eclipse.jetty.nested.Dump</servlet-class>
-  </servlet>
-  <servlet-mapping>
-    <servlet-name>dump</servlet-name>
-    <url-pattern>/outer/*</url-pattern>  
-  </servlet-mapping>
-  
-  </web-app>
-
-
diff --git a/test-jetty-nested/src/main/webapp/index.html b/test-jetty-nested/src/main/webapp/index.html
deleted file mode 100644
index d4a5171..0000000
--- a/test-jetty-nested/src/main/webapp/index.html
+++ /dev/null
@@ -1 +0,0 @@
-<h1>WRONG WEBAPP</h1>
diff --git a/test-jetty-nested/src/main/webapp/nested/WEB-INF/web.xml b/test-jetty-nested/src/main/webapp/nested/WEB-INF/web.xml
deleted file mode 100644
index 64467ac..0000000
--- a/test-jetty-nested/src/main/webapp/nested/WEB-INF/web.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="ISO-8859-1"?>
-<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>Nested WebApp</display-name>
-  
-  <filter>
-    <filter-name>MultiPart</filter-name>
-    <filter-class>org.eclipse.jetty.servlets.MultiPartFilter</filter-class>
-    <init-param>
-      <param-name>deleteFiles</param-name>
-      <param-value>true</param-value>
-    </init-param>
-  </filter>
-  <filter-mapping>
-    <filter-name>MultiPart</filter-name>
-    <url-pattern>/dump/*</url-pattern>
-  </filter-mapping>
-  
-  <servlet>
-    <servlet-name>dump</servlet-name>
-    <servlet-class>org.eclipse.jetty.nested.Dump</servlet-class>
-    <load-on-startup>1</load-on-startup>
-  </servlet>
-  <servlet-mapping>
-    <servlet-name>dump</servlet-name>
-    <url-pattern>/dump/*</url-pattern>
-  </servlet-mapping>
-  
-</web-app>
-
-
diff --git a/test-jetty-nested/src/main/webapp/nested/index.html b/test-jetty-nested/src/main/webapp/nested/index.html
deleted file mode 100644
index 1941150..0000000
--- a/test-jetty-nested/src/main/webapp/nested/index.html
+++ /dev/null
@@ -1,7 +0,0 @@
-<h1>Nested Jetty</h1>
-<ul>
-<li><a href="dump/info">Servlet Dump</a></li>
-<li><a href="dump.jsp">JSP Dump</a></li>
-</ul>
-
-
diff --git a/test-jetty-nested/src/test/java/org/eclipse/jetty/nested/NestedServer.java b/test-jetty-nested/src/test/java/org/eclipse/jetty/nested/NestedServer.java
deleted file mode 100644
index c1f1eca..0000000
--- a/test-jetty-nested/src/test/java/org/eclipse/jetty/nested/NestedServer.java
+++ /dev/null
@@ -1,47 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.nested;
-
-import org.eclipse.jetty.server.Connector;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
-import org.eclipse.jetty.webapp.WebAppContext;
-
-public class NestedServer
-{
-    public static void main(String[] args) throws Exception
-    {
-        Server server = new Server();
-
-        Connector connector = new SelectChannelConnector();
-        connector.setPort(Integer.getInteger("jetty.port",8080).intValue());
-        server.setConnectors(new Connector[]
-        { connector });
-        
-        
-        WebAppContext webapp = new WebAppContext();
-        webapp.setContextPath("/jnest");
-        webapp.setWar("src/main/webapp");
-        webapp.setParentLoaderPriority(true);
-        server.setHandler(webapp);
-
-        server.start();
-        server.join();
-    }
-}
diff --git a/test-jetty-servlet/pom.xml b/test-jetty-servlet/pom.xml
deleted file mode 100644
index 040a06a..0000000
--- a/test-jetty-servlet/pom.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<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">
-  <parent>
-    <groupId>org.eclipse.jetty</groupId>
-    <artifactId>jetty-project</artifactId>
-    <version>8.1.17.v20150415</version>
-  </parent>
-  <modelVersion>4.0.0</modelVersion>
-  <artifactId>test-jetty-servlet</artifactId>
-  <packaging>jar</packaging>
-  <name>Test :: Jetty Servlet Tester</name>
-  <url>http://www.eclipse.org/jetty</url>
-  <dependencies>
-    <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-webapp</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-    <dependency>
-      <groupId>junit</groupId>
-      <artifactId>junit</artifactId>
-      <scope>test</scope>
-    </dependency>
-  </dependencies>
-</project>
diff --git a/test-jetty-servlet/src/main/java/Jetty400Repro.java b/test-jetty-servlet/src/main/java/Jetty400Repro.java
deleted file mode 100644
index 96a180b..0000000
--- a/test-jetty-servlet/src/main/java/Jetty400Repro.java
+++ /dev/null
@@ -1,146 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-import java.io.IOException;
-import java.net.MalformedURLException;
-import java.net.Socket;
-import java.net.SocketTimeoutException;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jetty.server.Handler;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.handler.DefaultHandler;
-import org.eclipse.jetty.server.handler.HandlerList;
-import org.eclipse.jetty.webapp.WebAppContext;
-import org.xml.sax.SAXException;
-
-
-/**
- * Repro a jetty problem.
- * 
- * @author hughw
- *
- */
-public class Jetty400Repro extends HttpServlet{
-
-
-    private static final long serialVersionUID = 1L;
-    private static final int port = 8080;
-    private static final String host = "localhost";
-    private static final String uri = "/flub/servlet/";
-    
-    /**
-     * Jetty 7.0.1 returns 400 on the second POST, when you send both Connection: Keep-Alive and 
-     * Expect: 100-Continue headers in the request. 
-     * @param args
-     */
-    public static void main(String[] args) throws Exception{
-        initJetty();
-        Thread.sleep(1000);
-        
-        Socket sock = new Socket(host, port);
-        
-        sock.setSoTimeout(500);
-
-        String body= "<flibs xmlns='http://www.flub.org/schemas/131'><flib uid='12321'><name>foo flib</name> </flib></flibs>";
-        //body= "XXX";  // => 501
-
-        int len = body.getBytes("US-ASCII").length;
-        
-        String msg = "POST " + uri + " HTTP/1.1\r\n" + 
-        		"Content-Type: application/xml\r\n" + 
-        		"Host: 10.0.2.2:8080\r\n" + 
-        		"Content-Length: " + len + "\r\n" + 
-        		"Expect: 100-continue\r\n" + 
-        		"Connection: Keep-Alive\r\n" +
-        		"\r\n" + 
-        		body;
-        		
-         
-        
-        sock.getOutputStream().write(msg.getBytes("US-ASCII"));
-
-        String response1 = readResponse(sock);  
-        int status1 = Integer.parseInt(response1.substring(9, 12));
-        assert 401 == status1;
-        
-        sock.getOutputStream().write(msg.getBytes("US-ASCII"));
-        
-        
-        String response2 = readResponse(sock);        
-        System.out.println(response2.substring(0, 100));
-  
-    
-        int status2 = Integer.parseInt(response2.substring(9, 12));
-        System.out.println(status2);
-        
-        assert 401 == status2;
-        
-
-
-    }
-
-    private static String readResponse(Socket sock) throws IOException {
-        byte [] response = new byte [4000];
-        int n = 0;
-        for (int i=0; i< response.length && response[n] >= 0; i++){
-            try {
-                response[n++] = (byte)sock.getInputStream().read();
-            } catch (SocketTimeoutException e) {
-                break;
-            }
-        }
-        String sResult = new String(response);
-        return sResult;
-    }
-    
-    private static void initJetty() throws SAXException, IOException, MalformedURLException, Exception {
-
-        Server jetty = new Server(8080);
-        
-
-        // configure your web application
-        WebAppContext appContext = new WebAppContext();
-        appContext.setContextPath("/flub");
-        
-        appContext.addServlet(Jetty400Repro.class, "/servlet/");
-        
-        appContext.setResourceBase(".");
-        
-        
-        HandlerList handlers = new HandlerList();
-        handlers.setHandlers(new Handler[] { appContext, new DefaultHandler() });
-        jetty.setHandler(handlers);
-
-        
-        jetty.start();
-
-
-    }
-
-    @Override
-    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
-        req.getInputStream();
-        resp.sendError(401);
-    }
-
-}
diff --git a/test-jetty-servlet/src/main/java/org/eclipse/jetty/testing/HttpTester.java b/test-jetty-servlet/src/main/java/org/eclipse/jetty/testing/HttpTester.java
deleted file mode 100644
index ddf4447..0000000
--- a/test-jetty-servlet/src/main/java/org/eclipse/jetty/testing/HttpTester.java
+++ /dev/null
@@ -1,619 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.testing;
-
-import java.io.IOException;
-import java.util.Enumeration;
-
-import javax.servlet.http.Cookie;
-
-import org.eclipse.jetty.http.HttpFields;
-import org.eclipse.jetty.http.HttpGenerator;
-import org.eclipse.jetty.http.HttpHeaders;
-import org.eclipse.jetty.http.HttpParser;
-import org.eclipse.jetty.http.HttpVersions;
-import org.eclipse.jetty.http.MimeTypes;
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.ByteArrayBuffer;
-import org.eclipse.jetty.io.EofException;
-import org.eclipse.jetty.io.SimpleBuffers;
-import org.eclipse.jetty.io.View;
-import org.eclipse.jetty.io.bio.StringEndPoint;
-import org.eclipse.jetty.util.ByteArrayOutputStream2;
-
-/* ------------------------------------------------------------ */
-/** Test support class.
- * Assist with parsing and generating HTTP requests and responses.
- * 
- * <pre>
- *      HttpTester tester = new HttpTester();
- *      
- *      tester.parse(
- *          "GET /uri HTTP/1.1\r\n"+
- *          "Host: fakehost\r\n"+
- *          "Content-Length: 10\r\n" +
- *          "\r\n");
- *     
- *      System.err.println(tester.getMethod());
- *      System.err.println(tester.getURI());
- *      System.err.println(tester.getVersion());
- *      System.err.println(tester.getHeader("Host"));
- *      System.err.println(tester.getContent());
- * </pre>      
- * 
- * 
- * @see org.eclipse.jetty.testing.ServletTester
- */
-public class HttpTester
-{
-    protected HttpFields _fields=new HttpFields();
-    protected String _method;
-    protected String _uri;
-    protected String _version;
-    protected int _status;
-    protected String _reason;
-    protected ByteArrayOutputStream2 _parsedContent;
-    protected byte[] _genContent;
-    
-    private String _charset, _defaultCharset;
-    private Buffer _contentType;
-    
-    public HttpTester()
-    {
-        this("UTF-8");
-    }
-    
-    public HttpTester(String charset)
-    {
-        _defaultCharset = charset;
-    }
-    
-    public void reset()
-    {
-        _fields.clear();
-         _method=null;
-         _uri=null;
-         _version=null;
-         _status=0;
-         _reason=null;
-         _parsedContent=null;
-         _genContent=null;
-    }
-    
-    private String getString(Buffer buffer)
-    {
-        return getString(buffer.asArray());
-    }
-    
-    private String getString(byte[] b)
-    {
-        if(_charset==null)
-            return new String(b);
-        try
-        {
-            return new String(b, _charset);
-        }
-        catch(Exception e)
-        {
-            return new String(b);
-        }
-    }
-    
-    private byte[] getByteArray(String str)
-    {
-        if(_charset==null)
-            return str.getBytes();
-        try
-        {
-            return str.getBytes(_charset);
-        }
-        catch(Exception e)
-        {
-            return str.getBytes();
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Parse one HTTP request or response
-     * @param rawHTTP Raw HTTP to parse
-     * @return Any unparsed data in the rawHTTP (eg pipelined requests)
-     * @throws IOException
-     */
-    public String parse(String rawHTTP, boolean isHeadResponse) throws IOException
-    {
-        _charset = _defaultCharset;
-        ByteArrayBuffer buf = new ByteArrayBuffer(getByteArray(rawHTTP));
-        View view = new View(buf);
-        PH ph = new PH();
-        HttpParser parser = new HttpParser(view,ph);
-        parser.setHeadResponse(isHeadResponse);
-        parser.parse();
-        if (ph.isEarlyEOF())
-            throw new EofException();
-        return getString(view.asArray());
-    }
-    
-    
-    /* ------------------------------------------------------------ */
-    /**
-     * Parse one HTTP request or response
-     * @param rawHTTP Raw HTTP to parse
-     * @return Any unparsed data in the rawHTTP (eg pipelined requests)
-     * @throws IOException
-     */
-    public String parse(String rawHTTP) throws IOException
-    {
-        return parse(rawHTTP, false);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * Parse one HTTP request or response
-     * @param rawHTTP Raw HTTP to parse
-     * @return Any unparsed data in the rawHTTP (eg pipelined requests)
-     * @throws IOException
-     */
-    public byte[] parse(byte[] rawHTTP, boolean isHeadResponse) throws IOException
-    {
-        _charset = _defaultCharset;
-        ByteArrayBuffer buf = new ByteArrayBuffer(rawHTTP);
-        View view = new View(buf);
-        PH ph = new PH();
-        HttpParser parser = new HttpParser(view,ph);
-        parser.setHeadResponse(isHeadResponse);
-        parser.parse();
-        if (ph.isEarlyEOF())
-            throw new EofException();
-        return view.asArray();        
-    }
-    
-    /* ------------------------------------------------------------ */
-    /**
-     * Parse one HTTP request or response
-     * @param rawHTTP Raw HTTP to parse
-     * @return Any unparsed data in the rawHTTP (eg pipelined requests)
-     * @throws IOException
-     */
-    public byte[] parse(byte[] rawHTTP) throws IOException
-    {
-        return parse(rawHTTP, false);
-    }
-    
-    /* ------------------------------------------------------------ */
-    public String generate() throws IOException
-    {
-        _charset = _defaultCharset;
-        _contentType = _fields.get(HttpHeaders.CONTENT_TYPE_BUFFER);
-        if(_contentType!=null)
-        {
-            String charset = MimeTypes.getCharsetFromContentType(_contentType);
-            if(charset!=null)
-                _charset = charset;
-        }
-        Buffer bb=new ByteArrayBuffer(32*1024 + (_genContent!=null?_genContent.length:0));
-        Buffer sb=new ByteArrayBuffer(4*1024);
-        StringEndPoint endp = new StringEndPoint(_charset);
-        HttpGenerator generator = new HttpGenerator(new SimpleBuffers(sb,bb),endp);
-        
-        if (_method!=null)
-        {
-            generator.setRequest(getMethod(),getURI());
-            if (_version==null)
-                generator.setVersion(HttpVersions.HTTP_1_1_ORDINAL);
-            else
-                generator.setVersion(HttpVersions.CACHE.getOrdinal(HttpVersions.CACHE.lookup(_version)));
-            generator.completeHeader(_fields,false);
-            if (_genContent!=null)
-                generator.addContent(new View(new ByteArrayBuffer(_genContent)),false);
-            else if (_parsedContent!=null)
-                generator.addContent(new ByteArrayBuffer(_parsedContent.toByteArray()),false);
-        }
-        
-        generator.complete();
-        generator.flushBuffer();
-        return endp.getOutput();
-    }
-    
-    /* ------------------------------------------------------------ */
-    /**
-     * @return the method
-     */
-    public String getMethod()
-    {
-        return _method;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param method the method to set
-     */
-    public void setMethod(String method)
-    {
-        _method=method;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return the reason
-     */
-    public String getReason()
-    {
-        return _reason;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param reason the reason to set
-     */
-    public void setReason(String reason)
-    {
-        _reason=reason;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return the status
-     */
-    public int getStatus()
-    {
-        return _status;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param status the status to set
-     */
-    public void setStatus(int status)
-    {
-        _status=status;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return the uri
-     */
-    public String getURI()
-    {
-        return _uri;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param uri the uri to set
-     */
-    public void setURI(String uri)
-    {
-        _uri=uri;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return the version
-     */
-    public String getVersion()
-    {
-        return _version;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param version the version to set
-     */
-    public void setVersion(String version)
-    {
-        _version=version;
-    }
-    
-    /* ------------------------------------------------------------ */
-    public String getContentType()
-    {
-        return getString(_contentType);
-    }
-    
-    /* ------------------------------------------------------------ */
-    public String getCharacterEncoding()
-    {
-        return _charset;
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param name
-     * @param value
-     * @throws IllegalArgumentException
-     * @see org.eclipse.jetty.http.HttpFields#add(java.lang.String, java.lang.String)
-     */
-    public void addHeader(String name, String value) throws IllegalArgumentException
-    {
-        _fields.add(name,value);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param name
-     * @param date
-     * @see org.eclipse.jetty.http.HttpFields#addDateField(java.lang.String, long)
-     */
-    public void addDateHeader(String name, long date)
-    {
-        _fields.addDateField(name,date);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param name
-     * @param value
-     * @see org.eclipse.jetty.http.HttpFields#addLongField(java.lang.String, long)
-     */
-    public void addLongHeader(String name, long value)
-    {
-        _fields.addLongField(name,value);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param cookie
-     * @see org.eclipse.jetty.http.HttpFields#addSetCookie(org.eclipse.jetty.http.HttpCookie)
-     */
-    public void addSetCookie(Cookie cookie)
-    {
-        _fields.addSetCookie(
-                cookie.getName(),
-                cookie.getValue(),
-                cookie.getDomain(),
-                cookie.getPath(),
-                cookie.getMaxAge(),
-                cookie.getComment(),
-                cookie.getSecure(),
-                cookie.isHttpOnly(),
-                cookie.getVersion());
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param name
-     * @return the header value as a date
-     * @see org.eclipse.jetty.http.HttpFields#getDateField(java.lang.String)
-     */
-    public long getDateHeader(String name)
-    {
-        return _fields.getDateField(name);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return the header value names
-     * @see org.eclipse.jetty.http.HttpFields#getFieldNames()
-     */
-    public Enumeration getHeaderNames()
-    {
-        return _fields.getFieldNames();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param name
-     * @return the header value as a long
-     * @throws NumberFormatException
-     * @see org.eclipse.jetty.http.HttpFields#getLongField(java.lang.String)
-     */
-    public long getLongHeader(String name) throws NumberFormatException
-    {
-        return _fields.getLongField(name);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param name
-     * @return the header value
-     * @see org.eclipse.jetty.http.HttpFields#getStringField(java.lang.String)
-     */
-    public String getHeader(String name)
-    {
-        return _fields.getStringField(name);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param name
-     * @return the header values
-     * @see org.eclipse.jetty.http.HttpFields#getValues(java.lang.String)
-     */
-    public Enumeration getHeaderValues(String name)
-    {
-        return _fields.getValues(name);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param name
-     * @param value
-     * @see org.eclipse.jetty.http.HttpFields#put(java.lang.String, java.lang.String)
-     */
-    public void setHeader(String name, String value)
-    {
-        if (HttpHeaders.CONTENT_TYPE.equalsIgnoreCase(name))
-            setContentType(value);
-        else
-        _fields.put(name,value);
-        
-    }
-    
-    /* ------------------------------------------------------------ */
-    public void setContentType(String value)
-    {
-        _contentType = MimeTypes.CACHE.lookup(value);
-        _charset = MimeTypes.getCharsetFromContentType(_contentType);
-        _fields.put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param name
-     * @param date
-     * @see org.eclipse.jetty.http.HttpFields#putDateField(java.lang.String, long)
-     */
-    public void setDateHeader(String name, long date)
-    {
-        _fields.putDateField(name,date);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param name
-     * @param value
-     * @see org.eclipse.jetty.http.HttpFields#putLongField(java.lang.String, long)
-     */
-    public void setLongHeader(String name, long value)
-    {
-        _fields.putLongField(name,value);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param name
-     * @see org.eclipse.jetty.http.HttpFields#remove(java.lang.String)
-     */
-    public void removeHeader(String name)
-    {
-        _fields.remove(name);
-    }
-    
-    /* ------------------------------------------------------------ */
-    public String getContent()
-    {
-        if (_parsedContent!=null)
-            return getString(_parsedContent.toByteArray());
-        if (_genContent!=null)
-            return getString(_genContent);
-        return null;
-    }
-    
-    /* ------------------------------------------------------------ */
-    public byte[] getContentBytes()
-    {
-        if (_parsedContent!=null)
-            return _parsedContent.toByteArray();
-        if (_genContent!=null)
-            return _genContent;
-        return null;
-    }
-    
-    /* ------------------------------------------------------------ */
-    public void setContent(String content)
-    {
-        _parsedContent=null;
-        if (content!=null)
-        {
-            _genContent=getByteArray(content);
-            setLongHeader(HttpHeaders.CONTENT_LENGTH,_genContent.length);
-        }
-        else
-        {
-            removeHeader(HttpHeaders.CONTENT_LENGTH);
-            _genContent=null;
-        }
-    }
-    
-    /* ------------------------------------------------------------ */
-    public void setContentBytes(byte[] bytes)
-    {
-        _parsedContent = null;
-        _genContent = bytes;
-        setLongHeader(HttpHeaders.CONTENT_LENGTH, bytes.length);
-        
-    }
-
-    /* ------------------------------------------------------------ */
-    private class PH extends HttpParser.EventHandler
-    {
-        private volatile boolean _earlyEOF;
-        
-        @Override
-        public void startRequest(Buffer method, Buffer url, Buffer version) throws IOException
-        {
-            reset();
-            _method=getString(method);
-            _uri=getString(url);
-            _version=getString(version);
-        }
-
-        @Override
-        public void startResponse(Buffer version, int status, Buffer reason) throws IOException
-        {
-            reset();
-            _version=getString(version);
-            _status=status;
-            _reason=getString(reason);
-        }
-        
-        @Override
-        public void parsedHeader(Buffer name, Buffer value) throws IOException
-        {
-            _fields.add(name,value);
-        }
-
-        @Override
-        public void headerComplete() throws IOException
-        {
-            _contentType = _fields.get(HttpHeaders.CONTENT_TYPE_BUFFER);
-            if(_contentType!=null)
-            {
-                String charset = MimeTypes.getCharsetFromContentType(_contentType);
-                if(charset!=null)
-                    _charset = charset;
-            }
-        }
-
-        @Override
-        public void messageComplete(long contextLength) throws IOException
-        {
-        }
-        
-        @Override
-        public void content(Buffer ref) throws IOException
-        {
-            if (_parsedContent==null)
-                _parsedContent=new ByteArrayOutputStream2();
-            _parsedContent.write(ref.asArray());
-        }
-
-        @Override
-        public void earlyEOF() 
-        {
-            _earlyEOF = true;
-        }
-        
-        public boolean isEarlyEOF()
-        {
-            return _earlyEOF;
-        }
-        
-    }
-
-    @Override
-    public String toString()
-    {
-        if (_method!=null)
-            return super.toString()+" "+_method+" "+_uri+" "+_version+"\n"+_fields.toString();
-
-        return super.toString()+" HTTP/1.1 "+_status+" "+_reason+"\n"+_fields.toString();
-    }
-}
diff --git a/test-jetty-servlet/src/main/java/org/eclipse/jetty/testing/ServletTester.java b/test-jetty-servlet/src/main/java/org/eclipse/jetty/testing/ServletTester.java
deleted file mode 100644
index 8c90630..0000000
--- a/test-jetty-servlet/src/main/java/org/eclipse/jetty/testing/ServletTester.java
+++ /dev/null
@@ -1,385 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.testing;
-
-import java.net.InetAddress;
-import java.util.EnumSet;
-import java.util.Enumeration;
-import java.util.EventListener;
-
-import javax.servlet.DispatcherType;
-
-import org.eclipse.jetty.io.ByteArrayBuffer;
-import org.eclipse.jetty.server.LocalConnector;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.bio.SocketConnector;
-import org.eclipse.jetty.server.handler.ErrorHandler;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
-import org.eclipse.jetty.servlet.FilterHolder;
-import org.eclipse.jetty.servlet.ServletContextHandler;
-import org.eclipse.jetty.servlet.ServletHolder;
-import org.eclipse.jetty.util.Attributes;
-
-
-
-/* ------------------------------------------------------------ */
-/** Testing support for servlets and filters.
- *
- * Allows a programatic setup of a context with servlets and filters for
- * testing.  Raw HTTP requests may be sent to the context and responses received.
- * To avoid handling raw HTTP see {@link org.eclipse.jetty.testing.HttpTester}.
- * <pre>
- *      ServletTester tester=new ServletTester();
- *      tester.setContextPath("/context");
- *      tester.addServlet(TestServlet.class, "/servlet/*");
- *      tester.addServlet("org.eclipse.jetty.servlet.DefaultServlet", "/");
- *      tester.start();
- *      String response = tester.getResponses("GET /context/servlet/info HTTP/1.0\r\n\r\n");
- * </pre>
- *
- * @see org.eclipse.jetty.testing.HttpTester
- *
- *
- */
-public class ServletTester
-{
-    Server _server = new Server();
-    LocalConnector _connector = new LocalConnector();
-//    Context _context = new Context(Context.SESSIONS|Context.SECURITY);
-    //jaspi why security if it is not set up?
-    ServletContextHandler _context = new ServletContextHandler(ServletContextHandler.SESSIONS);
-
-    public ServletTester()
-    {
-        try
-        {
-            _server.addBean(new ErrorHandler());
-            _server.setSendServerVersion(false);
-            _server.addConnector(_connector);
-            _server.setHandler(_context);
-        }
-        catch (Error e)
-        {
-            throw e;
-        }
-        catch (RuntimeException e)
-        {
-            throw e;
-        }
-        catch (Exception e)
-        {
-            throw new RuntimeException(e);
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    public void dump()
-    {
-        _server.dump();
-    }
-
-    /* ------------------------------------------------------------ */
-    public void start() throws Exception
-    {
-        _server.start();
-    }
-
-    /* ------------------------------------------------------------ */
-    public void join() throws Exception
-    {
-        _server.join();
-    }
-
-    /* ------------------------------------------------------------ */
-    public void stop() throws Exception
-    {
-        _server.stop();
-    }
-
-    /* ------------------------------------------------------------ */
-    public ServletContextHandler getContext()
-    {
-        return _context;
-    }
-
-    /* ------------------------------------------------------------ */
-    /** Get raw HTTP responses from raw HTTP requests.
-     * Multiple requests and responses may be handled, but only if
-     * persistent connections conditions apply.
-     * @param rawRequests String of raw HTTP requests
-     * @return String of raw HTTP responses
-     * @throws Exception
-     */
-    public String getResponses(String rawRequests) throws Exception
-    {
-        return _connector.getResponses(rawRequests);
-    }
-
-    /* ------------------------------------------------------------ */
-    /** Get raw HTTP responses from raw HTTP requests.
-     * Multiple requests and responses may be handled, but only if
-     * persistent connections conditions apply.
-     * @param rawRequests String of raw HTTP requests
-     * @param connector The connector to handle the responses
-     * @return String of raw HTTP responses
-     * @throws Exception
-     */
-    public String getResponses(String rawRequests, LocalConnector connector) throws Exception
-    {
-        return connector.getResponses(rawRequests);
-    }
-
-    /* ------------------------------------------------------------ */
-    /** Get raw HTTP responses from raw HTTP requests.
-     * Multiple requests and responses may be handled, but only if
-     * persistent connections conditions apply.
-     * @param rawRequests String of raw HTTP requests
-     * @return String of raw HTTP responses
-     * @throws Exception
-     */
-    public ByteArrayBuffer getResponses(ByteArrayBuffer rawRequests) throws Exception
-    {
-        return _connector.getResponses(rawRequests,false);
-    }
-
-    /* ------------------------------------------------------------ */
-    /** Create a Socket connector.
-     * This methods adds a socket connector to the server
-     * @param localhost if true, only listen on local host, else listen on all interfaces.
-     * @return A URL to access the server via the socket connector.
-     * @throws Exception
-     */
-    public String createSocketConnector(boolean localhost)
-    throws Exception
-    {
-        synchronized (this)
-        {
-            SocketConnector connector = new SocketConnector();
-            if (localhost)
-                connector.setHost("127.0.0.1");
-            _server.addConnector(connector);
-            if (_server.isStarted())
-                connector.start();
-            else
-                connector.open();
-
-            return "http://127.0.0.1:"+connector.getLocalPort();
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    /** Create a SelectChannel connector.
-     * This methods adds a select channel connector to the server
-     * @return A URL to access the server via the connector.
-     * @throws Exception
-     */
-    public String createChannelConnector(boolean localhost)
-    throws Exception
-    {
-        synchronized (this)
-        {
-            SelectChannelConnector connector = new SelectChannelConnector();
-            if (localhost)
-                connector.setHost("127.0.0.1");
-            _server.addConnector(connector);
-            if (_server.isStarted())
-                connector.start();
-            else
-                connector.open();
-
-            return "http://"+(localhost?"127.0.0.1":
-                InetAddress.getLocalHost().getHostAddress()
-            )+":"+connector.getLocalPort();
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    /** Create a local connector.
-     * This methods adds a local connector to the server
-     * @return The LocalConnector object
-     * @throws Exception
-     */
-    public LocalConnector createLocalConnector()
-    throws Exception
-    {
-        synchronized (this)
-        {
-            LocalConnector connector = new LocalConnector();
-            _server.addConnector(connector);
-
-            if (_server.isStarted())
-                connector.start();
-
-            return connector;
-        }
-   }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param listener
-     * @see org.eclipse.jetty.server.handler.ContextHandler#addEventListener(java.util.EventListener)
-     */
-    public void addEventListener(EventListener listener)
-    {
-        _context.addEventListener(listener);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param filterClass
-     * @param pathSpec
-     * @param dispatches
-     * @return the FilterHolder
-     * @see org.eclipse.jetty.servlet.ServletContextHandler#addFilter(java.lang.Class, java.lang.String, int)
-     */
-    public FilterHolder addFilter(Class filterClass, String pathSpec, EnumSet<DispatcherType> dispatches)
-    {
-        return _context.addFilter(filterClass,pathSpec,dispatches);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param filterClass
-     * @param pathSpec
-     * @param dispatches
-     * @return the FilterHolder
-     * @see org.eclipse.jetty.servlet.ServletContextHandler#addFilter(java.lang.String, java.lang.String, int)
-     */
-    public FilterHolder addFilter(String filterClass, String pathSpec, EnumSet<DispatcherType> dispatches)
-    {
-        return _context.addFilter(filterClass,pathSpec,dispatches);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param servlet
-     * @param pathSpec
-     * @return the ServletHolder
-     * @see org.eclipse.jetty.servlet.ServletContextHandler#addServlet(java.lang.Class, java.lang.String)
-     */
-    public ServletHolder addServlet(Class servlet, String pathSpec)
-    {
-        return _context.addServlet(servlet,pathSpec);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param className
-     * @param pathSpec
-     * @return the ServletHolder
-     * @see org.eclipse.jetty.servlet.ServletContextHandler#addServlet(java.lang.String, java.lang.String)
-     */
-    public ServletHolder addServlet(String className, String pathSpec)
-    {
-        return _context.addServlet(className,pathSpec);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param name
-     * @return the Attribute object
-     * @see org.eclipse.jetty.servlet.ServletContextHandler#getAttribute(java.lang.String)
-     */
-    public Object getAttribute(String name)
-    {
-        return _context.getAttribute(name);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return the Attribute Names
-     * @see org.eclipse.jetty.servlet.ServletContextHandler#getAttributeNames()
-     */
-    public Enumeration getAttributeNames()
-    {
-        return _context.getAttributeNames();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return the attributes
-     * @see org.eclipse.jetty.servlet.ServletContextHandler#getAttributes()
-     */
-    public Attributes getAttributes()
-    {
-        return _context.getAttributes();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @return the resource base
-     * @see org.eclipse.jetty.servlet.ServletContextHandler#getResourceBase()
-     */
-    public String getResourceBase()
-    {
-        return _context.getResourceBase();
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param name
-     * @param value
-     * @see org.eclipse.jetty.servlet.ServletContextHandler#setAttribute(java.lang.String, java.lang.Object)
-     */
-    public void setAttribute(String name, Object value)
-    {
-        _context.setAttribute(name,value);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param classLoader
-     * @see org.eclipse.jetty.servlet.ServletContextHandler#setClassLoader(java.lang.ClassLoader)
-     */
-    public void setClassLoader(ClassLoader classLoader)
-    {
-        _context.setClassLoader(classLoader);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param contextPath
-     * @see org.eclipse.jetty.servlet.ServletContextHandler#setContextPath(java.lang.String)
-     */
-    public void setContextPath(String contextPath)
-    {
-        _context.setContextPath(contextPath);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param eventListeners
-     * @see org.eclipse.jetty.servlet.ServletContextHandler#setEventListeners(java.util.EventListener[])
-     */
-    public void setEventListeners(EventListener[] eventListeners)
-    {
-        _context.setEventListeners(eventListeners);
-    }
-
-    /* ------------------------------------------------------------ */
-    /**
-     * @param resourceBase
-     * @see org.eclipse.jetty.servlet.ServletContextHandler#setResourceBase(java.lang.String)
-     */
-    public void setResourceBase(String resourceBase)
-    {
-        _context.setResourceBase(resourceBase);
-    }
-
-}
diff --git a/test-jetty-servlet/src/test/java/org/eclipse/jetty/testing/HttpTesterTest.java b/test-jetty-servlet/src/test/java/org/eclipse/jetty/testing/HttpTesterTest.java
deleted file mode 100644
index a87e613..0000000
--- a/test-jetty-servlet/src/test/java/org/eclipse/jetty/testing/HttpTesterTest.java
+++ /dev/null
@@ -1,79 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.testing;
-
-import junit.framework.TestCase;
-
-public class HttpTesterTest extends TestCase
-{
-    
-    public void testCharset() throws Exception
-    {
-        HttpTester tester = new HttpTester();
-        tester.parse(
-                "POST /uri\uA74A HTTP/1.1\r\n"+
-                "Host: fakehost\r\n"+
-                "Content-Length: 12\r\n" +
-                "Content-Type: text/plain; charset=utf-8\r\n" +
-                "\r\n" +
-                "123456789\uA74A");
-        assertEquals("POST",tester.getMethod());
-        assertEquals("/uri\uA74A",tester.getURI());
-        assertEquals("HTTP/1.1",tester.getVersion());
-        assertEquals("fakehost",tester.getHeader("Host"));
-        assertEquals("text/plain; charset=utf-8",tester.getContentType());
-        assertEquals("utf-8",tester.getCharacterEncoding());
-        assertEquals("123456789\uA74A",tester.getContent());
-    }
-    
-    
-    public void testHead() throws Exception
-    {      
-        String headResponse = "HTTP/1.1 200 OK\r\n"+
-        "Content-Type: text/html\r\n"+
-        "Content-Length: 22\r\n"+
-        "\r\n";
-        
-        HttpTester tester = new HttpTester();
-        tester.parse(headResponse, true);
-        assertEquals(200, tester.getStatus());
-        assertEquals("22", tester.getHeader("Content-Length"));
-        assertEquals("text/html",tester.getContentType());
-    }
-
-    public void testSetCharset() throws Exception
-    {      
-        String content = "123456789\uA74A";
-        HttpTester tester = new HttpTester();
-        tester.setVersion("HTTP/1.0");
-        tester.setMethod("POST");
-        tester.setHeader("Content-type", "application/json; charset=iso-8859-1");
-        tester.setURI("/1/batch");
-        tester.setContent(content);
-        assertEquals("123456789?",tester.getContent());
-
-        tester.setHeader("Content-type", "application/json; charset=UTF-8");
-        tester.setContent(content);
-        assertEquals("123456789\uA74A",tester.getContent());
-  
-        String request=tester.generate();
-        assertTrue(request.startsWith("POST "));
-        assertTrue(request.trim().endsWith(content));
-    }
-}
diff --git a/test-jetty-servlet/src/test/java/org/eclipse/jetty/testing/ServletTest.java b/test-jetty-servlet/src/test/java/org/eclipse/jetty/testing/ServletTest.java
deleted file mode 100644
index 3f93629..0000000
--- a/test-jetty-servlet/src/test/java/org/eclipse/jetty/testing/ServletTest.java
+++ /dev/null
@@ -1,347 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.testing;
-
-import java.io.IOException;
-import java.net.URL;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import junit.framework.TestCase;
-
-import org.eclipse.jetty.http.HttpException;
-import org.eclipse.jetty.io.ByteArrayBuffer;
-import org.eclipse.jetty.util.IO;
-
-public class ServletTest extends TestCase
-{
-    ServletTester tester;
-    
-    /* ------------------------------------------------------------ */
-    @Override
-    protected void setUp() throws Exception
-    {
-        super.setUp();
-        tester=new ServletTester();
-        tester.setContextPath("/context");
-        tester.addServlet(TestServlet.class, "/servlet/*");
-        tester.addServlet(HelloServlet.class, "/hello/*");
-        tester.addServlet(ExceptServlet.class, "/except/*");
-        tester.addServlet("org.eclipse.jetty.servlet.DefaultServlet", "/");
-        
-        tester.start();
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    protected void tearDown() throws Exception
-    {
-        tester.stop();
-        tester=null;
-        super.tearDown();
-    }
-
-    /* ------------------------------------------------------------ */
-    public void testServletTesterRaw() throws Exception
-    {
-        // Raw HTTP test requests
-        String requests=
-            "GET /context/servlet/info?query=foo HTTP/1.1\r\n"+
-            "Host: tester\r\n"+
-            "\r\n"+
-
-            "GET /context/hello HTTP/1.1\r\n"+
-            "Host: tester\r\n"+
-            "\r\n";
-
-        String responses = tester.getResponses(requests);
-
-        String expected=
-            "HTTP/1.1 200 OK\r\n"+
-            "Content-Type: text/html;charset=ISO-8859-1\r\n"+
-            "Content-Length: 21\r\n"+
-            "\r\n"+
-            "<h1>Test Servlet</h1>" +
-
-            "HTTP/1.1 200 OK\r\n"+
-            "Content-Type: text/html;charset=ISO-8859-1\r\n"+
-            "Content-Length: 22\r\n"+
-            "\r\n"+
-            "<h1>Hello Servlet</h1>";
-
-        assertEquals(expected,responses);
-    }
-
-    /* ------------------------------------------------------------ */
-    public void testServletTesterClient() throws Exception
-    {
-        String base_url=tester.createSocketConnector(true);
-        
-        URL url = new URL(base_url+"/context/hello/info");
-        String result = IO.toString(url.openStream());
-        assertEquals("<h1>Hello Servlet</h1>",result);
-    }
-
-    /* ------------------------------------------------------------ */
-    public void testHttpTester() throws Exception
-    {
-        // generated and parsed test
-        HttpTester request = new HttpTester();
-        HttpTester response = new HttpTester();
-        
-        // test GET
-        request.setMethod("GET");
-        request.setVersion("HTTP/1.0");
-        request.setHeader("Host","tester");
-        request.setURI("/context/hello/info");
-        response.parse(tester.getResponses(request.generate()));
-        assertTrue(response.getMethod()==null);
-        assertEquals(200,response.getStatus());
-        assertEquals("<h1>Hello Servlet</h1>",response.getContent());
-
-        // test GET with content
-        request.setMethod("POST");
-        request.setContent("<pre>Some Test Content</pre>");
-        request.setHeader("Content-Type","text/html");
-        response.parse(tester.getResponses(request.generate()));
-        assertTrue(response.getMethod()==null);
-        assertEquals(200,response.getStatus());
-        assertEquals("<h1>Hello Servlet</h1><pre>Some Test Content</pre>",response.getContent());
-        
-        // test redirection
-        request.setMethod("GET");
-        request.setURI("/context");
-        request.setContent(null);
-        response.parse(tester.getResponses(request.generate()));
-        assertEquals(302,response.getStatus());
-        assertEquals("http://tester/context/",response.getHeader("location"));
-
-        // test not found
-        request.setURI("/context/xxxx");
-        response.parse(tester.getResponses(request.generate()));
-        assertEquals(404,response.getStatus());
-        
-    }
-
-
-    /* ------------------------------------------------------------ */
-    public void testBigPost() throws Exception
-    {
-        // generated and parsed test
-        HttpTester request = new HttpTester();
-        HttpTester response = new HttpTester();
-        
-        String content = "0123456789abcdef";
-        content+=content;
-        content+=content;
-        content+=content;
-        content+=content;
-        content+=content;
-        content+=content;
-        content+=content;
-        content+=content;
-        content+=content;
-        content+=content;
-        content+=content;
-        content+=content;
-        content+="!";
-        
-        request.setMethod("POST");
-        request.setVersion("HTTP/1.1");
-        request.setURI("/context/hello/info");
-        request.setHeader("Host","tester");
-        request.setHeader("Content-Type","text/plain");
-        request.setContent(content);
-        String r=request.generate();
-        r = tester.getResponses(r);
-        response.parse(r);
-        assertTrue(response.getMethod()==null);
-        assertEquals(200,response.getStatus());
-        assertEquals("<h1>Hello Servlet</h1>"+content,response.getContent());
-        
-        
-    }
-    
-
-    /* ------------------------------------------------------------ */
-    public void testCharset()
-        throws Exception
-    {
-        byte[] content_iso_8859_1="abcd=1234&AAA=xxx".getBytes("iso8859-1");
-        byte[] content_utf_8="abcd=1234&AAA=xxx".getBytes("utf-8");
-        byte[] content_utf_16="abcd=1234&AAA=xxx".getBytes("utf-16");
-
-        String request_iso_8859_1=
-            "POST /context/servlet/post HTTP/1.1\r\n"+
-            "Host: whatever\r\n"+
-            "Content-Type: application/x-www-form-urlencoded\r\n"+
-            "Content-Length: "+content_iso_8859_1.length+"\r\n"+
-            "\r\n";
-
-        String request_utf_8=
-            "POST /context/servlet/post HTTP/1.1\r\n"+
-            "Host: whatever\r\n"+
-            "Content-Type: application/x-www-form-urlencoded; charset=utf-8\r\n"+
-            "Content-Length: "+content_utf_8.length+"\r\n"+
-            "\r\n";
-
-        String request_utf_16=
-            "POST /context/servlet/post HTTP/1.1\r\n"+
-            "Host: whatever\r\n"+
-            "Content-Type: application/x-www-form-urlencoded; charset=utf-16\r\n"+
-            "Content-Length: "+content_utf_16.length+"\r\n"+
-            "Connection: close\r\n"+
-            "\r\n";
-        
-        ByteArrayBuffer out = new ByteArrayBuffer(4096);
-        out.put(request_iso_8859_1.getBytes("iso8859-1"));
-        out.put(content_iso_8859_1);
-        out.put(request_utf_8.getBytes("iso8859-1"));
-        out.put(content_utf_8);
-        out.put(request_utf_16.getBytes("iso8859-1"));
-        out.put(content_utf_16);
-
-        ByteArrayBuffer responses = tester.getResponses(out);
-        
-        String expected=
-            "HTTP/1.1 200 OK\r\n"+
-            "Content-Type: text/html;charset=ISO-8859-1\r\n"+
-            "Content-Length: 21\r\n"+
-            "\r\n"+
-            "<h1>Test Servlet</h1>"+
-            "HTTP/1.1 200 OK\r\n"+
-            "Content-Type: text/html;charset=ISO-8859-1\r\n"+
-            "Content-Length: 21\r\n"+
-            "\r\n"+
-            "<h1>Test Servlet</h1>"+
-            "HTTP/1.1 200 OK\r\n"+
-            "Content-Type: text/html;charset=ISO-8859-1\r\n"+
-            "Connection: close\r\n"+
-            "\r\n"+
-            "<h1>Test Servlet</h1>";
-        
-        assertEquals(expected,responses.toString());
-    }
-
-    /* ------------------------------------------------------------ */
-    public void testExcept() throws Exception
-    {
-        String request0=
-            "GET /context/except/io HTTP/1.1\r\n"+
-            "Host: whatever\r\n"+
-            "\r\n"+
-            "GET /context/except/http HTTP/1.1\r\n"+
-            "Host: whatever\r\n"+
-            "\r\n";
-        
-        ByteArrayBuffer out = new ByteArrayBuffer(4096);
-        out.put(request0.getBytes("iso8859-1"));
-        String responses = tester.getResponses(out).toString();
-                
-        int offset = responses.indexOf("HTTP/1.1 500");
-        assertTrue(offset>=0);
-        offset = responses.indexOf("Content-Length: ",offset);
-        assertTrue(offset>0);
-        offset = responses.indexOf("<h2>HTTP ERROR 500</h2>",offset);
-        assertTrue(offset>0);
-        offset = responses.indexOf("IOException: testing",offset);
-        assertTrue(offset>0);
-        offset = responses.indexOf("</html>",offset);
-        assertTrue(offset>0);
-        offset = responses.indexOf("HTTP/1.1 499",offset);
-        assertTrue(offset>0);
-        offset = responses.indexOf("Content-Length: ",offset);
-        assertTrue(offset>0);
-    }
-    
-    /* ------------------------------------------------------------ */
-    public static class HelloServlet extends HttpServlet
-    {
-        private static final long serialVersionUID=2779906630657190712L;
-
-        @Override
-        protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
-        {
-            doGet(request,response);
-        }
-        @Override
-        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
-        {
-            response.setContentType("text/html");
-            response.getWriter().print("<h1>Hello Servlet</h1>");
-            if (request.getContentLength()>0)
-                response.getWriter().write(IO.toString(request.getInputStream()));
-        }
-    }
-    
-    public static class TestServlet extends HttpServlet
-    {
-        private static final long serialVersionUID=2779906630657190712L;
-
-        @Override
-        protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
-        {
-            assertEquals("/context",request.getContextPath());
-            assertEquals("/servlet",request.getServletPath());
-            assertEquals("/post",request.getPathInfo());
-            assertEquals(2,request.getParameterMap().size());
-            assertEquals("1234",request.getParameter("abcd"));
-            assertEquals("xxx",request.getParameter("AAA"));
-            
-            response.setContentType("text/html");
-            response.setStatus(HttpServletResponse.SC_OK);
-            response.getWriter().print("<h1>Test Servlet</h1>");
-        }
-        
-        @Override
-        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
-        {
-            assertEquals("/context",request.getContextPath());
-            assertEquals("/servlet",request.getServletPath());
-            assertEquals("/info",request.getPathInfo());
-            assertEquals("query=foo",request.getQueryString());
-            assertEquals(1,request.getParameterMap().size());
-            assertEquals(1,request.getParameterValues("query").length);
-            assertEquals("foo",request.getParameter("query"));
-            
-            response.setContentType("text/html");
-            response.setStatus(HttpServletResponse.SC_OK);
-            response.getWriter().print("<h1>Test Servlet</h1>");
-        }
-    }
-    
-    public static class ExceptServlet extends HttpServlet
-    {
-        @Override
-        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
-        {
-            if ("/http".equals(request.getPathInfo()))
-                throw new HttpException(499);
-            if ("/runtime".equals(request.getPathInfo()))
-                throw new RuntimeException("testing");
-            if ("/error".equals(request.getPathInfo()))
-                throw new Error("testing");
-            throw new IOException("testing");
-        }
-    }
-
-}
diff --git a/test-jetty-webapp/pom.xml b/test-jetty-webapp/pom.xml
deleted file mode 100644
index 5d2b6c3..0000000
--- a/test-jetty-webapp/pom.xml
+++ /dev/null
@@ -1,225 +0,0 @@
-<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">
-  <parent>
-    <groupId>org.eclipse.jetty</groupId>
-    <artifactId>jetty-project</artifactId>
-    <version>8.1.17.v20150415</version>
-  </parent>
-  <modelVersion>4.0.0</modelVersion>
-  <artifactId>test-jetty-webapp</artifactId>
-  <name>Test :: Jetty Test Webapp</name>
-  <url>http://www.eclipse.org/jetty</url>
-  <packaging>war</packaging>
-  <build>
-    <plugins>
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-surefire-plugin</artifactId>
-        <executions>
-          <execution>
-            <id>test</id>
-            <phase>test</phase>
-          </execution>
-        </executions>
-        <configuration>
-          <excludes>
-            <exclude>**/WebAppTest.java</exclude>
-            <exclude>**/Test*.java</exclude>
-          </excludes>
-        </configuration>
-      </plugin>
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-assembly-plugin</artifactId>
-        <executions>
-          <execution>
-            <phase>package</phase>
-            <goals>
-              <goal>single</goal>
-            </goals>
-            <configuration>
-              <descriptorRefs>
-                <descriptorRef>config</descriptorRef>
-              </descriptorRefs>
-              <descriptors>
-                <descriptor>src/main/assembly/web-bundle.xml</descriptor>
-              </descriptors>
-              <archive>
-                <manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
-              </archive>
-            </configuration>
-          </execution>
-        </executions>
-      </plugin>
-      <!-- also make this webapp an osgi bundle -->
-      <plugin>
-        <artifactId>maven-war-plugin</artifactId>
-        <configuration>
-          <archive>
-            <manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
-          </archive>
-        </configuration>
-      </plugin>
-      <plugin>
-        <groupId>org.apache.felix</groupId>
-        <artifactId>maven-bundle-plugin</artifactId>
-        <extensions>true</extensions>
-        <configuration>
-          <supportedProjectTypes>
-            <supportedProjectType>war</supportedProjectType>
-          </supportedProjectTypes>
-        </configuration>
-        <executions>
-          <execution>
-            <id>bundle-manifest</id>
-            <phase>process-classes</phase>
-            <goals>
-              <goal>manifest</goal>
-            </goals>
-            <configuration>
-              <instructions>
-                <Bundle-SymbolicName>org.eclipse.jetty.test-jetty-webapp</Bundle-SymbolicName>
-                <Import-Package>javax.servlet,org.eclipse.jetty.servlets,*</Import-Package>
-                <Export-Package>!com.acme*</Export-Package>
-                <!-- the test webapp is configured via a jetty xml file
-                in order to add the security handler. -->
-                <Web-ContextPath>/</Web-ContextPath>
-                <!-- in fact the '.' must not be there
-                but Felix-BND has a bug:
-                http://www.mail-archive.com/users@felix.apache.org/msg04730.html
-                https://issues.apache.org/jira/browse/FELIX-1571
-                -->
-                <Bundle-ClassPath>.,WEB-INF/classes</Bundle-ClassPath>
-                <Bundle-RequiredExecutionEnvironment>J2SE-1.5</Bundle-RequiredExecutionEnvironment>
-              </instructions>
-            </configuration>
-           </execution>
-        </executions>
-      </plugin>
-<!--
-      <plugin>
-        <groupId>org.mortbay.jetty</groupId>
-        <artifactId>jetty-maven-plugin</artifactId>
-        <version>${project.version}</version>
-        <configuration>
-          <stopPort>8087</stopPort>
-          <stopKey>foo</stopKey>
-          <scanIntervalSeconds>1</scanIntervalSeconds>
-          <systemProperties>
-            <systemProperty>
-              <name>fooprop</name>
-              <value>222</value>
-            </systemProperty>
-          </systemProperties>
-          <useTestScope>true</useTestScope>
-          <webAppConfig>
-            <contextPath>/test</contextPath>
-            <tempDirectory>${project.build.directory}/work</tempDirectory>
-            <sessionHandler implementation="org.eclipse.jetty.server.session.SessionHandler">
-              <sessionManager implementation="org.eclipse.jetty.server.session.HashSessionManager">
-                <storeDirectory>${basedir}/target/sessions</storeDirectory>
-              </sessionManager>
-            </sessionHandler>
-          </webAppConfig>
-          <loginServices>
-            <loginService implementation="org.eclipse.jetty.security.HashLoginService">
-              <name>Test Realm</name>
-              <config>src/main/config/etc/realm.properties</config>
-            </loginService>
-          </loginServices>
-        </configuration>
-      </plugin>
--->
-      <!-- uncomment to precompile jsps -->
-      <!--
-      <plugin>
-          <groupId>org.eclipse.jetty</groupId>
-          <artifactId>jetty-jspc-maven-plugin</artifactId>
-          <version>${project.version}</version>
-          <executions>
-            <execution>
-              <id>jspc</id>
-              <goals>
-                <goal>jspc</goal>
-              </goals>
-              <configuration>
-                 <includes>**/*.foo</includes>
-                 <excludes>**/*.fff</excludes>
-              </configuration>
-            </execution>
-          </executions>
-        </plugin>
-        <plugin>
-          <groupId>org.apache.maven.plugins</groupId>
-          <artifactId>maven-war-plugin</artifactId>
-          <configuration>
-            <webXml>${basedir}/target/web.xml</webXml>
-          </configuration>
-        </plugin>
-        -->
-    </plugins>
-  </build>
-  <dependencies>
-    <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-servlets</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-    <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-continuation</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-    <dependency>
-      <groupId>org.eclipse.jetty.orbit</groupId>
-      <artifactId>javax.servlet</artifactId>
-      <scope>provided</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-websocket</artifactId>
-      <version>${project.version}</version>
-      <scope>provided</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-webapp</artifactId>
-      <version>${project.version}</version>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>test-jetty-servlet</artifactId>
-      <version>${project.version}</version>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-jmx</artifactId>
-      <version>${project.version}</version>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
-      <groupId>junit</groupId>
-      <artifactId>junit</artifactId>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-server</artifactId>
-      <version>${project.version}</version>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
-      <groupId>javax.servlet.jsp</groupId>
-      <artifactId>jsp-api</artifactId>
-      <version>2.1</version>
-      <scope>provided</scope>
-    </dependency>
-    <dependency>
-      <groupId>javax.servlet</groupId>
-      <artifactId>jstl</artifactId>
-      <version>1.2</version>
-      <scope>provided</scope>
-    </dependency>
-  </dependencies>
-</project>
diff --git a/test-jetty-webapp/src/main/assembly/embedded-jetty-web-for-webbundle.xml b/test-jetty-webapp/src/main/assembly/embedded-jetty-web-for-webbundle.xml
deleted file mode 100644
index c181c35..0000000
--- a/test-jetty-webapp/src/main/assembly/embedded-jetty-web-for-webbundle.xml
+++ /dev/null
@@ -1,93 +0,0 @@
-<?xml version="1.0"  encoding="ISO-8859-1"?>
-<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
-
-<!-- ==================================================================
-Configure and deploy the test web application in $(jetty.home)/webapps/test
-
-Note. If this file did not exist or used a context path other that /test
-then the default configuration of jetty.xml would discover the test
-webapplication with a WebAppDeployer.  By specifying a context in this
-directory, additional configuration may be specified and hot deployments 
-detected.
-===================================================================== -->
-
-<Configure class="org.eclipse.jetty.webapp.WebAppContext">
-
-
-  <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
-  <!-- Required minimal context configuration :                        -->
-  <!--  + contextPath                                                  -->
-  <!--  + war OR resourceBase                                          -->
-  <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
-  <Set name="contextPath">/</Set>
-  <Set name="war"><SystemProperty name="jetty.home" default="."/>/webapps/test.war</Set>
-
-  <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
-  <!-- Optional context configuration                                  -->
-  <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
-  <Set name="extractWAR">true</Set>
-  <Set name="copyWebDir">false</Set>
-  <Set name="defaultsDescriptor"><SystemProperty name="jetty.home" default="."/>/etc/webdefault.xml</Set>
-  <Set name="overrideDescriptor"><SystemProperty name="jetty.home" default="."/>/contexts/test.d/override-web.xml</Set>
-
-  <!-- virtual hosts
-  <Set name="virtualHosts">
-    <Array type="String">
-      <Item>www.myVirtualDomain.com</Item>
-      <Item>localhost</Item>
-      <Item>127.0.0.1</Item>
-    </Array>
-  </Set>
-  -->
-
-  <!-- disable cookies 
-  <Get name="sessionHandler">
-     <Get name="sessionManager">
-        <Set name="usingCookies" type="boolean">false</Set>
-     </Get>
-  </Get>
-  -->
-
-  <Get name="securityHandler">
-    <Set name="loginService">
-      <New class="org.eclipse.jetty.security.HashLoginService">
-	    <Set name="name">Test Realm</Set>
-	    <Set name="config"><Property name="this.web-inf.url"/>realm.properties</Set>
-            <!-- To enable reload of realm when properties change, uncomment the following lines -->
-            <!-- changing refreshInterval (in seconds) as desired                                -->
-            <!-- 
-            <Set name="refreshInterval">5</Set>
-            <Call name="start"></Call>
-            -->
-      </New>
-    </Set>
-    <Set name="checkWelcomeFiles">true</Set>
-  </Get>
-  
-  <!-- Non standard error page mapping -->
-  <!--
-  <Get name="errorHandler">
-    <Call name="addErrorPage">
-      <Arg type="int">500</Arg>
-      <Arg type="int">599</Arg>
-      <Arg type="String">/dump/errorCodeRangeMapping</Arg>
-    </Call>
-  </Get>
-  -->
-
-  <!-- Add context specific logger
-  <Set name="handler">
-    <New id="RequestLog" class="org.eclipse.jetty.server.handler.RequestLogHandler">
-      <Set name="requestLog">
-	<New id="RequestLogImpl" class="org.eclipse.jetty.server.NCSARequestLog">
-	  <Set name="filename"><Property name="jetty.logs" default="./logs"/>/test-yyyy_mm_dd.request.log</Set>
-	  <Set name="filenameDateFormat">yyyy_MM_dd</Set>
-	  <Set name="append">true</Set>
-	  <Set name="LogTimeZone">GMT</Set>
-	</New>
-      </Set>
-    </New>
-  </Set>
-  -->
-
-</Configure>
diff --git a/test-jetty-webapp/src/main/assembly/web-bundle.xml b/test-jetty-webapp/src/main/assembly/web-bundle.xml
deleted file mode 100644
index b80faf9..0000000
--- a/test-jetty-webapp/src/main/assembly/web-bundle.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0" 
-  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd">
-  <id>webbundle</id>
-  <formats>
-    <format>jar</format>
-  </formats>
-  <includeBaseDirectory>false</includeBaseDirectory>
-  <!-- baseDirectory>${basedir}/${project.build.directory}/${project.build.finalName}</baseDirectory -->
-  <fileSets>
-    <fileSet>
-      <directory>${basedir}/${project.build.directory}/${project.build.finalName}/</directory>
-      <outputDirectory></outputDirectory>
-      <includes>
-        <include>**/*.*</include>
-      </includes>
-      <excludes>
-        <exclude>WEB-INF/lib/**</exclude>
-        <exclude>WEB-INF/jetty-web.xml</exclude>
-      </excludes>
-    </fileSet>
-  </fileSets>
-  <files>
-    <file>
-      <source>src/main/assembly/embedded-jetty-web-for-webbundle.xml</source>
-      <outputDirectory>WEB-INF</outputDirectory>
-      <destName>jetty-web.xml</destName>
-    </file>
-    <file>
-      <source>src/main/config/etc/realm.properties</source>
-      <outputDirectory>WEB-INF</outputDirectory>
-      <destName>realm.properties</destName>
-    </file>
-  </files>
-</assembly>
diff --git a/test-jetty-webapp/src/main/config/contexts-available/move-context.xml b/test-jetty-webapp/src/main/config/contexts-available/move-context.xml
deleted file mode 100644
index 7821b3d..0000000
--- a/test-jetty-webapp/src/main/config/contexts-available/move-context.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<?xml version="1.0"  encoding="ISO-8859-1"?>
-<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
-
-<Configure class="org.eclipse.jetty.server.handler.MovedContextHandler">
-  <Set name="contextPath">/oldContextPath</Set>
-  <Set name="newContextURL">/test/dump/newContextPath</Set>
-  <Set name="permanent">false</Set>
-  <Set name="discardPathInfo">false</Set>
-  <Set name="discardQuery">false</Set>
-  <Set name="expires">-1</Set>
-</Configure>
diff --git a/test-jetty-webapp/src/main/config/contexts/test.d/override-web.xml b/test-jetty-webapp/src/main/config/contexts/test.d/override-web.xml
deleted file mode 100644
index 512ce2b..0000000
--- a/test-jetty-webapp/src/main/config/contexts/test.d/override-web.xml
+++ /dev/null
@@ -1,60 +0,0 @@
-<?xml version="1.0" encoding="ISO-8859-1"?>
-<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/web-app_2_5.xsd" 
-   version="2.5"> 
-
-
-<!-- This web.xml format file is an override file that is applied to the test webapp AFTER
-     it has been configured by the default descriptor and the WEB-INF/web.xml descriptor -->
-
-  <!-- Add or override context init parameter -->
-  <context-param>
-    <param-name>context-override-example</param-name>
-    <param-value>a context value</param-value>
-  </context-param>
-
-
-  <!-- Add or override servlet init parameter -->
-  <servlet>
-    <servlet-name>Dump</servlet-name>
-    <init-param>
-      <param-name>servlet-override-example</param-name>
-      <param-value>a servlet value</param-value>
-    </init-param>
-  </servlet>
-
-  <!-- Add servlet mapping -->
-  <servlet-mapping>
-    <servlet-name>Dump</servlet-name>
-    <url-pattern>*.more</url-pattern>
-  </servlet-mapping>
-
-  <!-- Reset servlet class and/or start order -->
-  <servlet>
-    <servlet-name>Session</servlet-name>
-    <servlet-class>com.acme.SessionDump</servlet-class>
-    <load-on-startup>5</load-on-startup>
-  </servlet>
-
-  <!-- Uncomment to override the setup of the test filter -->
-  <!-- 
-  <filter>
-    <filter-name>TestFilter</filter-name>
-    <filter-class>com.acme.TestFilter</filter-class>
-    <async-support>true</async-support>
-    <init-param>
-      <param-name>remote</param-name>
-      <param-value>false</param-value>
-    </init-param>
-  </filter>
-  <filter-mapping>
-     <filter-name>TestFilter</filter-name>
-     <url-pattern>/*</url-pattern>
-  </filter-mapping>
-  -->
-  
-</web-app>
-
-
diff --git a/test-jetty-webapp/src/main/config/contexts/test.xml b/test-jetty-webapp/src/main/config/contexts/test.xml
deleted file mode 100644
index 12d5b18..0000000
--- a/test-jetty-webapp/src/main/config/contexts/test.xml
+++ /dev/null
@@ -1,97 +0,0 @@
-<?xml version="1.0"  encoding="ISO-8859-1"?>
-<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
-
-<!-- ==================================================================
-Configure and deploy the test web application in $(jetty.home)/webapps/test
-
-Note. If this file did not exist or used a context path other that /test
-then the default configuration of jetty.xml would discover the test
-webapplication with a WebAppDeployer.  By specifying a context in this
-directory, additional configuration may be specified and hot deployments 
-detected.
-===================================================================== -->
-
-<Configure class="org.eclipse.jetty.webapp.WebAppContext">
-
-  <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
-  <!-- Required minimal context configuration :                        -->
-  <!--  + contextPath                                                  -->
-  <!--  + war OR resourceBase                                          -->
-  <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
-  <Set name="contextPath">/</Set>
-  <Set name="war"><SystemProperty name="jetty.home" default="."/>/webapps/test.war</Set>
-
-  <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
-  <!-- Optional context configuration                                  -->
-  <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
-  <Set name="extractWAR">true</Set>
-  <Set name="copyWebDir">false</Set>
-  <Set name="defaultsDescriptor"><SystemProperty name="jetty.home" default="."/>/etc/webdefault.xml</Set>
-  <Set name="overrideDescriptor"><SystemProperty name="jetty.home" default="."/>/contexts/test.d/override-web.xml</Set>
-  
-  <!-- virtual hosts
-  <Set name="virtualHosts">
-    <Array type="String">
-      <Item>www.myVirtualDomain.com</Item>
-      <Item>localhost</Item>
-      <Item>127.0.0.1</Item>
-    </Array>
-  </Set>
-  -->
-
-  <!-- disable cookies 
-  <Get name="sessionHandler">
-     <Get name="sessionManager">
-        <Set name="usingCookies" type="boolean">false</Set>
-     </Get>
-  </Get>
-  -->
-
-  <Get name="securityHandler">
-    <Set name="loginService">
-      <New class="org.eclipse.jetty.security.HashLoginService">
-	    <Set name="name">Test Realm</Set>
-	    <Set name="config"><SystemProperty name="jetty.home" default="."/>/etc/realm.properties</Set>
-            <!-- To enable reload of realm when properties change, uncomment the following lines -->
-            <!-- changing refreshInterval (in seconds) as desired                                -->
-            <!-- 
-            <Set name="refreshInterval">5</Set>
-            <Call name="start"></Call>
-            -->
-      </New>
-    </Set>
-    <Set name="authenticator">
-      <New class="org.eclipse.jetty.security.authentication.FormAuthenticator">
-        <Set name="alwaysSaveUri">true</Set>
-      </New>
-    </Set>
-    <Set name="checkWelcomeFiles">true</Set>
-  </Get>
-  
-  <!-- Non standard error page mapping -->
-  <!--
-  <Get name="errorHandler">
-    <Call name="addErrorPage">
-      <Arg type="int">500</Arg>
-      <Arg type="int">599</Arg>
-      <Arg type="String">/dump/errorCodeRangeMapping</Arg>
-    </Call>
-  </Get>
-  -->
-
-  <!-- Add context specific logger
-  <Set name="handler">
-    <New id="RequestLog" class="org.eclipse.jetty.server.handler.RequestLogHandler">
-      <Set name="requestLog">
-	<New id="RequestLogImpl" class="org.eclipse.jetty.server.NCSARequestLog">
-	  <Set name="filename"><Property name="jetty.logs" default="./logs"/>/test-yyyy_mm_dd.request.log</Set>
-	  <Set name="filenameDateFormat">yyyy_MM_dd</Set>
-	  <Set name="append">true</Set>
-	  <Set name="LogTimeZone">GMT</Set>
-	</New>
-      </Set>
-    </New>
-  </Set>
-  -->
-
-</Configure>
diff --git a/test-jetty-webapp/src/main/config/etc/jetty-testrealm.xml b/test-jetty-webapp/src/main/config/etc/jetty-testrealm.xml
deleted file mode 100644
index 5926b19..0000000
--- a/test-jetty-webapp/src/main/config/etc/jetty-testrealm.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0"?>
-<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
-
-<Configure id="Server" class="org.eclipse.jetty.server.Server">
-
-    <!-- =========================================================== -->
-    <!-- Configure Authentication Login Service                      -->
-    <!-- Realms may be configured for the entire server here, or     -->
-    <!-- they can be configured for a specific web app in a context  -->
-    <!-- configuration (see $(jetty.home)/contexts/test.xml for an   -->
-    <!-- example).                                                   -->
-    <!-- =========================================================== -->
-    <Call name="addBean">
-      <Arg>
-        <New class="org.eclipse.jetty.security.HashLoginService">
-          <Set name="name">Test Realm</Set>
-          <Set name="config"><Property name="jetty.home" default="."/>/etc/realm.properties</Set>
-          <Set name="refreshInterval">0</Set>
-        </New>
-      </Arg>
-    </Call>
-
-</Configure>
diff --git a/test-jetty-webapp/src/main/config/etc/realm.properties b/test-jetty-webapp/src/main/config/etc/realm.properties
deleted file mode 100644
index cbf905d..0000000
--- a/test-jetty-webapp/src/main/config/etc/realm.properties
+++ /dev/null
@@ -1,21 +0,0 @@
-#
-# This file defines users passwords and roles for a HashUserRealm
-#
-# The format is
-#  <username>: <password>[,<rolename> ...]
-#
-# Passwords may be clear text, obfuscated or checksummed.  The class 
-# org.eclipse.util.Password should be used to generate obfuscated
-# passwords or password checksums
-#
-# If DIGEST Authentication is used, the password must be in a recoverable
-# format, either plain text or OBF:.
-#
-jetty: MD5:164c88b302622e17050af52c89945d44,user
-admin: CRYPT:adpexzg3FUZAk,server-administrator,content-administrator,admin
-other: OBF:1xmk1w261u9r1w1c1xmq,user
-plain: plain,user
-user: password,user
-
-# This entry is for digest auth.  The credential is a MD5 hash of username:realmname:password
-digest: MD5:6e120743ad67abfbc385bc2bb754e297,user
diff --git a/test-jetty-webapp/src/main/java/com/acme/ChatServlet.java b/test-jetty-webapp/src/main/java/com/acme/ChatServlet.java
deleted file mode 100644
index d9bede5..0000000
--- a/test-jetty-webapp/src/main/java/com/acme/ChatServlet.java
+++ /dev/null
@@ -1,192 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package com.acme;
-
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.Map;
-import java.util.Queue;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jetty.continuation.Continuation;
-import org.eclipse.jetty.continuation.ContinuationSupport;
-
-
-// Simple asynchronous Chat room.
-// This does not handle duplicate usernames or multiple frames/tabs from the same browser
-// Some code is duplicated for clarity.
-public class ChatServlet extends HttpServlet
-{
-    
-    // inner class to hold message queue for each chat room member
-    class Member
-    {
-        String _name;
-        Continuation _continuation;
-        Queue<String> _queue = new LinkedList<String>();
-    }
-
-    Map<String,Map<String,Member>> _rooms = new HashMap<String,Map<String, Member>>();
-    
-    
-    // Handle Ajax calls from browser
-    @Override
-    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
-    {   
-        // Ajax calls are form encoded
-        String action = request.getParameter("action");
-        String message = request.getParameter("message");
-        String username = request.getParameter("user");
-
-        if (action.equals("join"))
-            join(request,response,username);
-        else if (action.equals("poll"))
-            poll(request,response,username);
-        else if (action.equals("chat"))
-            chat(request,response,username,message);
-    }
-
-    private synchronized void join(HttpServletRequest request,HttpServletResponse response,String username)
-    throws IOException
-    {
-        Member member = new Member();
-        member._name=username;
-        Map<String,Member> room=_rooms.get(request.getPathInfo());
-        if (room==null)
-        {
-            room=new HashMap<String,Member>();
-            _rooms.put(request.getPathInfo(),room);
-        }
-        room.put(username,member); 
-        response.setContentType("text/json;charset=utf-8");
-        PrintWriter out=response.getWriter();
-        out.print("{action:\"join\"}");
-    }
-
-    private synchronized void poll(HttpServletRequest request,HttpServletResponse response,String username)
-    throws IOException
-    {
-        Map<String,Member> room=_rooms.get(request.getPathInfo());
-        if (room==null)
-        {
-            response.sendError(503);
-            return;
-        }
-        Member member = room.get(username);
-        if (member==null)
-        {
-            response.sendError(503);
-            return;
-        }
-
-        synchronized(member)
-        {
-            if (member._queue.size()>0)
-            {
-                // Send one chat message
-                response.setContentType("text/json;charset=utf-8");
-                StringBuilder buf=new StringBuilder();
-
-                buf.append("{\"action\":\"poll\",");
-                buf.append("\"from\":\"");
-                buf.append(member._queue.poll());
-                buf.append("\",");
-
-                String message = member._queue.poll();
-                int quote=message.indexOf('"');
-                while (quote>=0)
-                {
-                    message=message.substring(0,quote)+'\\'+message.substring(quote);
-                    quote=message.indexOf('"',quote+2);
-                }
-                buf.append("\"chat\":\"");
-                buf.append(message);
-                buf.append("\"}");
-                byte[] bytes = buf.toString().getBytes("utf-8");
-                response.setContentLength(bytes.length);
-                response.getOutputStream().write(bytes);
-            }
-            else 
-            {
-                Continuation continuation = ContinuationSupport.getContinuation(request);
-                if (continuation.isInitial()) 
-                {
-                    // No chat in queue, so suspend and wait for timeout or chat
-                    continuation.setTimeout(20000);
-                    continuation.suspend();
-                    member._continuation=continuation;
-                }
-                else
-                {
-                    // Timeout so send empty response
-                    response.setContentType("text/json;charset=utf-8");
-                    PrintWriter out=response.getWriter();
-                    out.print("{action:\"poll\"}");
-                }
-            }
-        }
-    }
-
-    private synchronized void chat(HttpServletRequest request,HttpServletResponse response,String username,String message)
-    throws IOException
-    {
-        Map<String,Member> room=_rooms.get(request.getPathInfo());
-        if (room!=null)
-        {
-            // Post chat to all members
-            for (Member m:room.values())
-            {
-                synchronized (m)
-                {
-                    m._queue.add(username); // from
-                    m._queue.add(message);  // chat
-
-                    // wakeup member if polling
-                    if (m._continuation!=null)
-                    {
-                        m._continuation.resume();
-                        m._continuation=null;
-                    }
-                }
-            }
-        }
-
-        response.setContentType("text/json;charset=utf-8");
-        PrintWriter out=response.getWriter();
-        out.print("{action:\"chat\"}");  
-    }
-    
-    // Serve the HTML with embedded CSS and Javascript.
-    // This should be static content and should use real JS libraries.
-    @Override
-    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
-    {
-        if (request.getParameter("action")!=null)
-            doPost(request,response);
-        else
-            getServletContext().getNamedDispatcher("default").forward(request,response);
-    }
-    
-}
diff --git a/test-jetty-webapp/src/main/java/com/acme/CookieDump.java b/test-jetty-webapp/src/main/java/com/acme/CookieDump.java
deleted file mode 100644
index e097194..0000000
--- a/test-jetty-webapp/src/main/java/com/acme/CookieDump.java
+++ /dev/null
@@ -1,143 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package com.acme;
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.util.concurrent.TimeUnit;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.Cookie;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-
-/* ------------------------------------------------------------ */
-/** Test Servlet Cookies.
- *
- * 
- */
-public class CookieDump extends HttpServlet
-{
-    int redirectCount=0;
-
-    /* ------------------------------------------------------------ */
-    protected void handleForm(HttpServletRequest request,
-                          HttpServletResponse response) 
-    {
-        String name =  request.getParameter("Name");
-        String value =  request.getParameter("Value");
-        String age =  request.getParameter("Age");
-
-        if (name!=null && name.length()>0)
-        {
-            Cookie cookie = new Cookie(name,value);
-            if (age!=null && age.length()>0)
-                cookie.setMaxAge(Integer.parseInt(age));
-            response.addCookie(cookie);
-        }
-    }
-    
-    /* ------------------------------------------------------------ */
-    @Override
-    public void doPost(HttpServletRequest request,
-                       HttpServletResponse response) 
-        throws ServletException, IOException
-    {
-        handleForm(request,response);
-        String nextUrl = getURI(request)+"?R="+redirectCount++;
-        String encodedUrl=response.encodeRedirectURL(nextUrl);
-        response.sendRedirect(encodedUrl);
-    }
-        
-    /* ------------------------------------------------------------ */
-    @Override
-    public void doGet(HttpServletRequest request,
-                      HttpServletResponse response) 
-        throws ServletException, IOException
-    {
-        handleForm(request,response);
-        
-        response.setContentType("text/html");
-
-        
-        PrintWriter out = response.getWriter();
-        out.println("<h1>Cookie Dump Servlet:</h1>");       
-        
-        Cookie[] cookies = request.getCookies();
-        
-        for (int i=0;cookies!=null && i<cookies.length;i++)
-        {
-            out.println("<b>"+deScript(cookies[i].getName())+"</b>="+deScript(cookies[i].getValue())+"<br/>");
-        }
-        
-        out.println("<form action=\""+response.encodeURL(getURI(request))+"\" method=\"post\">"); 
-
-        out.println("<b>Name:</b><input type=\"text\" name=\"Name\" value=\"name\"/><br/>");
-        out.println("<b>Value:</b><input type=\"text\" name=\"Value\" value=\"value\"/><br/>");
-        out.println("<b>Max-Age:</b><input type=\"text\" name=\"Age\" value=\"60\"/><br/>");
-        out.println("<input type=\"submit\" name=\"Action\" value=\"Set\"/>");
-
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public String getServletInfo() {
-        return "Session Dump Servlet";
-    }
-
-    /* ------------------------------------------------------------ */
-    private String getURI(HttpServletRequest request)
-    {
-        String uri=(String)request.getAttribute("javax.servlet.forward.request_uri");
-        if (uri==null)
-            uri=request.getRequestURI();
-        return uri;
-    }
-
-    /* ------------------------------------------------------------ */
-    protected String deScript(String string)
-    {
-        if (string==null)
-            return null;
-        string=string.replace("&", "&");
-        string=string.replace( "<", "<");
-        string=string.replace( ">", ">");
-        return string;
-    }
-    
-    @Override
-    public void destroy()
-    {
-        // For testing --stop with STOP.WAIT handling of the jetty-start behavior.
-        if (Boolean.getBoolean("test.slow.destroy"))
-        {
-            log("Simulating a slow destroy (10 seconds)",null);
-            try
-            {
-                TimeUnit.SECONDS.sleep(10);
-            }
-            catch (InterruptedException e)
-            {
-                // ignore
-            }
-        }
-        super.destroy();
-    }
-}
diff --git a/test-jetty-webapp/src/main/java/com/acme/Counter.java b/test-jetty-webapp/src/main/java/com/acme/Counter.java
deleted file mode 100644
index 7222e59..0000000
--- a/test-jetty-webapp/src/main/java/com/acme/Counter.java
+++ /dev/null
@@ -1,41 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package com.acme;
-
-public class Counter implements java.io.Serializable
-{
-    int counter=0;
-    String last;
-
-    public int getCount()
-    {
-	counter++;
-	return counter;
-    }
-
-    public void setLast(String uri) {
-        last=uri;
-    }
-
-    public String getLast() {
-        return last;
-    }
-
-}
-
diff --git a/test-jetty-webapp/src/main/java/com/acme/Date2Tag.java b/test-jetty-webapp/src/main/java/com/acme/Date2Tag.java
deleted file mode 100644
index f63db72..0000000
--- a/test-jetty-webapp/src/main/java/com/acme/Date2Tag.java
+++ /dev/null
@@ -1,53 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package com.acme;
-
-import java.io.IOException;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.StringTokenizer;
-
-import javax.servlet.jsp.JspContext;
-import javax.servlet.jsp.JspException;
-import javax.servlet.jsp.tagext.JspFragment;
-import javax.servlet.jsp.tagext.SimpleTagSupport;
-
-public class Date2Tag extends SimpleTagSupport
-{
-    String format;
-    
-    public void setFormat(String value) {
-        this.format = value;
-    }
-
-    public void doTag() throws JspException, IOException {
-        String formatted = 
-            new SimpleDateFormat("long".equals(format)?"EEE 'the' d:MMM:yyyy":"d:MM:yy")
-            .format(new Date());
-        StringTokenizer tok = new StringTokenizer(formatted,":");
-        JspContext context = getJspContext();
-        context.setAttribute("day", tok.nextToken() );
-        context.setAttribute("month", tok.nextToken() );
-        context.setAttribute("year", tok.nextToken() );
-
-        JspFragment fragment = getJspBody();
-        fragment.invoke(null);
-    }
-}
-
diff --git a/test-jetty-webapp/src/main/java/com/acme/DateTag.java b/test-jetty-webapp/src/main/java/com/acme/DateTag.java
deleted file mode 100644
index 38e731b..0000000
--- a/test-jetty-webapp/src/main/java/com/acme/DateTag.java
+++ /dev/null
@@ -1,70 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package com.acme;
-
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.TimeZone;
-
-import javax.servlet.jsp.JspException;
-import javax.servlet.jsp.JspTagException;
-import javax.servlet.jsp.PageContext;
-import javax.servlet.jsp.tagext.BodyContent;
-import javax.servlet.jsp.tagext.BodyTagSupport;
-import javax.servlet.jsp.tagext.Tag;
-
-public class DateTag extends BodyTagSupport
-{
-    Tag parent;
-    BodyContent body;
-    String tz="GMT";
-
-    public void setParent(Tag parent) {this.parent=parent;}
-    public Tag getParent() {return parent;}
-    public void setBodyContent(BodyContent content) {body=content;}
-    public void setPageContext(PageContext pageContext) {}
-
-    public void setTz(String value) {tz=value;}
-
-    public int doStartTag() throws JspException {return EVAL_BODY_TAG;}
-    
-    public int doEndTag() throws JspException {return EVAL_PAGE;}
-
-    public void doInitBody() throws JspException {}
-
-    public int doAfterBody() throws JspException {
-	try
-	{
-            SimpleDateFormat format = new SimpleDateFormat(body.getString());
-            format.setTimeZone(TimeZone.getTimeZone(tz));
-	    body.getEnclosingWriter().write(format.format(new Date()));
-	    return SKIP_BODY;
-	}
-	catch (Exception ex) {
-            ex.printStackTrace();
-            throw new JspTagException(ex.toString());
-	}
-    }
-
-    public void release()
-    {
-	body=null;
-    }
-}
-
diff --git a/test-jetty-webapp/src/main/java/com/acme/DispatchServlet.java b/test-jetty-webapp/src/main/java/com/acme/DispatchServlet.java
deleted file mode 100644
index 50400b9..0000000
--- a/test-jetty-webapp/src/main/java/com/acme/DispatchServlet.java
+++ /dev/null
@@ -1,282 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package com.acme;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.io.PrintWriter;
-
-import javax.servlet.RequestDispatcher;
-import javax.servlet.ServletContext;
-import javax.servlet.ServletException;
-import javax.servlet.ServletOutputStream;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletRequestWrapper;
-import javax.servlet.http.HttpServletResponse;
-import javax.servlet.http.HttpServletResponseWrapper;
-
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-
-
-/* ------------------------------------------------------------ */
-/** Test Servlet RequestDispatcher.
- * 
- * 
- */
-public class DispatchServlet extends HttpServlet
-{
-    private static final Logger LOG = Log.getLogger(DispatchServlet.class);
-
-    /* ------------------------------------------------------------ */
-    String pageType;
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public void doPost(HttpServletRequest sreq, HttpServletResponse sres) throws ServletException, IOException
-    {
-        doGet(sreq, sres);
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public void doGet(HttpServletRequest sreq, HttpServletResponse sres) throws ServletException, IOException
-    {
-        if (sreq.getParameter("wrap") != null)
-        {
-            sreq= new HttpServletRequestWrapper(sreq);
-            sres= new HttpServletResponseWrapper(sres);
-        }
-        
-        if (sreq.getParameter("session") != null)
-            sreq.getSession(true);
-
-        String prefix=
-            sreq.getContextPath() != null ? sreq.getContextPath() + sreq.getServletPath() : sreq.getServletPath();
-            
-        String info;
-        
-        if (sreq.getAttribute("javax.servlet.include.servlet_path") != null)
-            info= (String)sreq.getAttribute("javax.servlet.include.path_info");
-        else
-            info= sreq.getPathInfo();
-        
-        if (info == null)
-            info= "NULL";
-
-        if (info.indexOf(sreq.getServletPath()) > 0)
-        {
-            sres.sendError(403,"Nested " + sreq.getServletPath() + " forbidden.");
-            return;
-        }
-        
-        if(info.indexOf(getServletName()) > 0)
-        {
-            sres.sendError(403,"Nested " + getServletName() + " forbidden.");
-            return;
-        }
-
-        if (info.startsWith("/includeW/"))
-        {
-            sres.setContentType("text/html");
-            info= info.substring(9);
-            if (info.indexOf('?') < 0)
-                info += "?Dispatch=include";
-            else
-                info += "&Dispatch=include";
-            
-            PrintWriter pout= null;
-            pout= sres.getWriter();
-            pout.write("<H1>Include (writer): " + info + "</H1><HR>");
-            
-            RequestDispatcher dispatch= getServletContext().getRequestDispatcher(info);
-            if (dispatch == null)
-            {
-                pout= sres.getWriter();
-                pout.write("<H1>Null dispatcher</H1>");
-            }
-            else
-                dispatch.include(sreq, sres);
-            
-            pout.write("<HR><H1>-- Included (writer)</H1>");
-        }
-        else if (info.startsWith("/includeS/"))
-        {
-            sres.setContentType("text/html");
-            info= info.substring(9);
-            if (info.indexOf('?') < 0)
-                info += "?Dispatch=include";
-            else
-                info += "&Dispatch=include";
-            
-            OutputStream out= null;
-            out= sres.getOutputStream();
-            out.write(("<H1>Include (outputstream): " + info + "</H1><HR>").getBytes());
-            
-            RequestDispatcher dispatch= getServletContext().getRequestDispatcher(info);
-            if (dispatch == null)
-            {
-                out= sres.getOutputStream();
-                out.write("<H1>Null dispatcher</H1>".getBytes());
-            }
-            else
-                dispatch.include(sreq, sres);
-            
-            out.write("<HR><H1>-- Included (outputstream)</H1>".getBytes());
-            
-        }
-        else if (info.startsWith("/forward/"))
-        {
-            info= info.substring(8);
-            if (info.indexOf('?') < 0)
-                info += "?Dispatch=forward";
-            else
-                info += "&Dispatch=forward";
-            
-            RequestDispatcher dispatch= getServletContext().getRequestDispatcher(info);
-            if (dispatch != null)
-            {
-                ServletOutputStream out =sres.getOutputStream();
-                out.print("Can't see this");
-                dispatch.forward(sreq, sres);
-                try
-                {
-                    // should be closed
-                    out.println("IOException");
-                    // should not get here
-                    throw new IllegalStateException();
-                }
-                catch(IOException e)
-                {
-                    LOG.ignore(e);
-                }
-            }
-            else
-            {
-                sres.setContentType("text/html");
-                PrintWriter pout= sres.getWriter();
-                pout.write("<H1>No dispatcher for: " + info + "</H1><HR>");
-                pout.flush();
-            }
-        }
-        else if (info.startsWith("/forwardC/"))
-        {
-            info= info.substring(9);
-            if (info.indexOf('?') < 0)
-                info += "?Dispatch=forward";
-            else
-                info += "&Dispatch=forward";
-            
-            String cpath= info.substring(0, info.indexOf('/', 1));
-            info= info.substring(cpath.length());
-            
-            ServletContext context= getServletContext().getContext(cpath);
-            RequestDispatcher dispatch= context.getRequestDispatcher(info);
-            
-            if (dispatch != null)
-            {
-                dispatch.forward(sreq, sres);
-            }
-            else
-            {
-                sres.setContentType("text/html");
-                PrintWriter pout= sres.getWriter();
-                pout.write("<H1>No dispatcher for: " + cpath + "/" + info + "</H1><HR>");
-                pout.flush();
-            }
-        }
-        else if (info.startsWith("/includeN/"))
-        {
-            sres.setContentType("text/html");
-            info= info.substring(10);
-            if (info.indexOf("/") >= 0)
-                info= info.substring(0, info.indexOf("/"));
-            
-            PrintWriter pout;
-            if (info.startsWith("/null"))
-                info= info.substring(5);
-            else
-            {
-                pout= sres.getWriter();
-                pout.write("<H1>Include named: " + info + "</H1><HR>");
-            }
-            
-            RequestDispatcher dispatch= getServletContext().getNamedDispatcher(info);
-            if (dispatch != null)
-                dispatch.include(sreq, sres);
-            else
-            {
-                pout= sres.getWriter();
-                pout.write("<H1>No servlet named: " + info + "</H1>");
-            }
-            
-            pout= sres.getWriter();
-            pout.write("<HR><H1>Included ");
-        }
-        else if (info.startsWith("/forwardN/"))
-        {
-            info= info.substring(10);
-            if (info.indexOf("/") >= 0)
-                info= info.substring(0, info.indexOf("/"));
-            RequestDispatcher dispatch= getServletContext().getNamedDispatcher(info);
-            if (dispatch != null)
-                dispatch.forward(sreq, sres);
-            else
-            {
-                sres.setContentType("text/html");
-                PrintWriter pout= sres.getWriter();
-                pout.write("<H1>No servlet named: " + info + "</H1>");
-                pout.flush();
-            }
-        }
-        else
-        {
-            sres.setContentType("text/html");
-            PrintWriter pout= sres.getWriter();
-            pout.write(
-                    "<H1>Dispatch URL must be of the form: </H1>"
-                    + "<PRE>"
-                    + prefix
-                    + "/includeW/path\n"
-                    + prefix
-                    + "/includeS/path\n"
-                    + prefix
-                    + "/forward/path\n"
-                    + prefix
-                    + "/includeN/name\n"
-                    + prefix
-                    + "/forwardC/_context/path\n</PRE>");
-        }
-        
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public String getServletInfo()
-    {
-        return "Include Servlet";
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public synchronized void destroy()
-    {
-    }
-
-}
diff --git a/test-jetty-webapp/src/main/java/com/acme/Dump.java b/test-jetty-webapp/src/main/java/com/acme/Dump.java
deleted file mode 100644
index 5ab68f2..0000000
--- a/test-jetty-webapp/src/main/java/com/acme/Dump.java
+++ /dev/null
@@ -1,1020 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package com.acme;
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.io.PrintWriter;
-import java.io.Reader;
-import java.lang.reflect.Array;
-import java.lang.reflect.Field;
-import java.net.URL;
-import java.util.Collections;
-import java.util.Collection;
-import java.util.Date;
-import java.util.Enumeration;
-import java.util.Locale;
-import java.util.Timer;
-import java.util.TimerTask;
-
-import javax.servlet.ServletConfig;
-import javax.servlet.ServletContext;
-import javax.servlet.ServletException;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletRequestWrapper;
-import javax.servlet.ServletResponse;
-import javax.servlet.ServletResponseWrapper;
-import javax.servlet.UnavailableException;
-import javax.servlet.http.Cookie;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletRequestWrapper;
-import javax.servlet.http.HttpServletResponse;
-import javax.servlet.http.HttpServletResponseWrapper;
-
-import org.eclipse.jetty.continuation.Continuation;
-import org.eclipse.jetty.continuation.ContinuationListener;
-import org.eclipse.jetty.continuation.ContinuationSupport;
-import org.eclipse.jetty.http.HttpHeaders;
-import org.eclipse.jetty.util.IO;
-import org.eclipse.jetty.util.StringUtil;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-
-
-
-/* ------------------------------------------------------------ */
-/** Dump Servlet Request.
- * 
- */
-public class Dump extends HttpServlet
-{
-    private static final Logger LOG = Log.getLogger(Dump.class);
-
-    boolean fixed;
-    Timer _timer;
-    
-    /* ------------------------------------------------------------ */
-    @Override
-    public void init(ServletConfig config) throws ServletException
-    {
-    	super.init(config);
-    	
-    	if (config.getInitParameter("unavailable")!=null && !fixed)
-    	{
-    	    
-    	    fixed=true;
-    	    throw new UnavailableException("Unavailable test",Integer.parseInt(config.getInitParameter("unavailable")));
-    	}
-    	
-    	_timer=new Timer(true);
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
-    {
-        doGet(request, response);
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
-    {
-        byte[] buffer = new byte[8192];
-        int len=request.getContentLength();
-        int c=0;
-        InputStream in=request.getInputStream();
-        while (c<len)
-        {
-            int l = in.read(buffer);
-            if (l<0)
-                break;
-            c+=l;
-        }
-        request.setAttribute("PUT",c+"bytes");
-        doGet(request, response);
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public void doGet(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException
-    {
-        if (!request.isUserInRole("user")) 
-        {
-            try 
-            {
-                request.login("user", "password");
-            } 
-            catch(ServletException se) 
-            {
-            	se.printStackTrace();
-            }
-        }
-        
-        // Handle a dump of data
-        final String data= request.getParameter("data");
-        final String chars= request.getParameter("chars");
-        final String block= request.getParameter("block");
-        final String dribble= request.getParameter("dribble");
-        final boolean flush= request.getParameter("flush")!=null?Boolean.parseBoolean(request.getParameter("flush")):false;
-
-        
-        if(request.getPathInfo()!=null && request.getPathInfo().toLowerCase(Locale.ENGLISH).indexOf("script")!=-1)
-        {
-            response.sendRedirect(response.encodeRedirectURL(getServletContext().getContextPath() + "/dump/info"));
-            return;
-        }
-            
-        request.setCharacterEncoding("UTF-8");
-        
-        if (request.getParameter("busy")!=null)
-        {
-            long end = System.currentTimeMillis()+Long.parseLong(request.getParameter("busy"));
-            while(System.currentTimeMillis()<end)
-            {}
-        }
-        
-        if (request.getParameter("empty")!=null)
-        {
-            response.setStatus(200);
-            response.flushBuffer();
-            return;
-        }
-        
-        if (request.getParameter("sleep")!=null)
-        {
-            try
-            {
-                long s = Long.parseLong(request.getParameter("sleep"));
-                if (request.getHeader(HttpHeaders.EXPECT)!=null &&request.getHeader(HttpHeaders.EXPECT).indexOf("102")>=0)
-                {
-                    Thread.sleep(s/2);
-                    response.sendError(102);
-                    Thread.sleep(s/2);
-                }
-                else
-                    Thread.sleep(s);
-            }
-            catch (InterruptedException e)
-            {
-                return;
-            }
-            catch (Exception e)
-            {
-                throw new ServletException(e);
-            }
-        }
-
-        if (request.getAttribute("RESUME")==null && request.getParameter("resume")!=null)
-        {
-            request.setAttribute("RESUME",Boolean.TRUE);
-
-            final long resume=Long.parseLong(request.getParameter("resume"));
-            final Continuation continuation = ContinuationSupport.getContinuation(request);
-            _timer.schedule(new TimerTask()
-            {
-                @Override
-                public void run()
-                {
-                    continuation.resume();
-                }
-            },resume);
-  
-        }
-
-        if (request.getParameter("complete")!=null)
-        {
-            final long complete=Long.parseLong(request.getParameter("complete"));
-            _timer.schedule(new TimerTask()
-            {
-                @Override
-                public void run()
-                {
-                    try
-                    {
-                        response.setContentType("text/html");
-                        response.getOutputStream().println("<h1>COMPLETED</h1>"); 
-                        Continuation continuation = ContinuationSupport.getContinuation(request);
-                        continuation.complete();
-                    }
-                    catch(Exception e)
-                    {
-                        e.printStackTrace();
-                    }
-                }
-            },complete);
-        }
-        
-        if (request.getParameter("suspend")!=null && request.getAttribute("SUSPEND")!=Boolean.TRUE)
-        {
-            request.setAttribute("SUSPEND",Boolean.TRUE);
-            try
-            {
-                Continuation continuation = ContinuationSupport.getContinuation(request);
-                continuation.setTimeout(Long.parseLong(request.getParameter("suspend")));
-                continuation.suspend();
-                
-                continuation.addContinuationListener(new ContinuationListener()
-                {   
-                    public void onTimeout(Continuation continuation)
-                    {
-                        response.addHeader("Dump","onTimeout");
-                        try
-                        {
-                            if (!dump(response,data,chars,block,dribble,flush))
-                            {
-                                response.setContentType("text/plain");
-                                response.getOutputStream().println("EXPIRED");
-                            }
-                            continuation.complete();
-                        }
-                        catch (IOException e)
-                        {
-                            LOG.ignore(e);
-                        }
-                    }
-                    
-                    public void onComplete(Continuation continuation)
-                    {
-                        response.addHeader("Dump","onComplete");
-                    }
-                });
-                
-                continuation.undispatch();
-            }
-            catch(Exception e)
-            {
-                throw new ServletException(e);
-            }
-        }        
-            
-        request.setAttribute("Dump", this);
-        getServletContext().setAttribute("Dump",this);
-        // getServletContext().log("dump "+request.getRequestURI());
-
-        // Force a content length response
-        String length= request.getParameter("length");
-        if (length != null && length.length() > 0)
-        {
-            response.setContentLength(Integer.parseInt(length));
-        }
-
-        // Handle a dump of data
-        if (dump(response,data,chars,block,dribble,flush))
-            return;
-        
-        // handle an exception
-        String info= request.getPathInfo();
-        if (info != null && info.endsWith("Exception"))
-        {
-            try
-            {
-                throw (Throwable) Thread.currentThread().getContextClassLoader().loadClass(info.substring(1)).newInstance();
-            }
-            catch (Throwable th)
-            {
-                throw new ServletException(th);
-            }
-        }
-
-        // test a reset
-        String reset= request.getParameter("reset");
-        if (reset != null && reset.length() > 0)
-        {
-            response.getOutputStream().println("THIS SHOULD NOT BE SEEN!");
-            response.setHeader("SHOULD_NOT","BE SEEN");
-            response.reset();
-        }
-        
-        
-        // handle an redirect
-        String redirect= request.getParameter("redirect");
-        if (redirect != null && redirect.length() > 0)
-        {
-            response.getOutputStream().println("THIS SHOULD NOT BE SEEN!");
-            response.sendRedirect(response.encodeRedirectURL(redirect));
-            try
-            {
-                response.getOutputStream().println("THIS SHOULD NOT BE SEEN!");
-            }
-            catch(IOException e)
-            {
-                // ignored as stream is closed.
-            }
-            return;
-        }
-
-        // handle an error
-        String error= request.getParameter("error");
-        if (error != null && error.length() > 0 && request.getAttribute("javax.servlet.error.status_code")==null)
-        {
-            response.getOutputStream().println("THIS SHOULD NOT BE SEEN!");
-            response.sendError(Integer.parseInt(error));
-            try
-            {
-                response.getOutputStream().println("THIS SHOULD NOT BE SEEN!");
-            }
-            catch(IllegalStateException e)
-            {
-                try
-                {
-                    response.getWriter().println("NOR THIS!!"); 
-                }
-                catch(IOException e2){}
-            }
-            catch(IOException e){}
-            return;
-        }
-
-        // Handle a extra headers 
-        String headers= request.getParameter("headers");
-        if (headers != null && headers.length() > 0)
-        {
-            long h=Long.parseLong(headers);
-            for (int i=0;i<h;i++)
-                response.addHeader("Header"+i,"Value"+i);
-        }
-
-        String buffer= request.getParameter("buffer");
-        if (buffer != null && buffer.length() > 0)
-            response.setBufferSize(Integer.parseInt(buffer));
-
-        String charset= request.getParameter("charset");
-        if (charset==null)
-            charset="UTF-8";
-        response.setCharacterEncoding(charset);
-        response.setContentType("text/html");
-
-        if (info != null && info.indexOf("Locale/") >= 0)
-        {
-            try
-            {
-                String locale_name= info.substring(info.indexOf("Locale/") + 7);
-                Field f= java.util.Locale.class.getField(locale_name);
-                response.setLocale((Locale)f.get(null));
-            }
-            catch (Exception e)
-            {
-                e.printStackTrace();
-                response.setLocale(Locale.getDefault());
-            }
-        }
-
-        String cn= request.getParameter("cookie");
-        String cv=request.getParameter("cookiev");
-        if (cn!=null && cv!=null)
-        {
-            Cookie cookie= new Cookie(cn, cv);
-            if (request.getParameter("version")!=null)
-                cookie.setVersion(Integer.parseInt(request.getParameter("version")));
-            cookie.setComment("Cookie from dump servlet");
-            response.addCookie(cookie);
-        }
-
-        String pi= request.getPathInfo();
-        if (pi != null && pi.startsWith("/ex"))
-        {
-            OutputStream out= response.getOutputStream();
-            out.write("</H1>This text should be reset</H1>".getBytes());
-            if ("/ex0".equals(pi))
-                throw new ServletException("test ex0", new Throwable());
-            else if ("/ex1".equals(pi))
-                throw new IOException("test ex1");
-            else if ("/ex2".equals(pi))
-                throw new UnavailableException("test ex2");
-            else if (pi.startsWith("/ex3/"))
-                throw new UnavailableException("test ex3",Integer.parseInt(pi.substring(5)));
-            throw new RuntimeException("test");
-        }
-
-        if ("true".equals(request.getParameter("close")))
-            response.setHeader("Connection","close");
-
-        String buffered= request.getParameter("buffered");
-        
-        PrintWriter pout=null;
-        
-        try
-        {
-            pout =response.getWriter();
-        }
-        catch(IllegalStateException e)
-        {
-            pout=new PrintWriter(new OutputStreamWriter(response.getOutputStream(),charset));
-        }
-        if (buffered!=null)
-            pout = new PrintWriter(new BufferedWriter(pout,Integer.parseInt(buffered)));
-        
-        try
-        {
-            pout.write("<html>\n<body>\n");
-            pout.write("<h1>Dump Servlet</h1>\n");
-            pout.write("<table width=\"95%\">");
-            pout.write("<tr>\n");
-            pout.write("<th align=\"right\">getMethod: </th>");
-            pout.write("<td>" + notag(request.getMethod())+"</td>");
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"right\">getContentLength: </th>");
-            pout.write("<td>"+Integer.toString(request.getContentLength())+"</td>");
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"right\">getContentType: </th>");
-            pout.write("<td>"+notag(request.getContentType())+"</td>");
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"right\">getRequestURI: </th>");
-            pout.write("<td>"+notag(request.getRequestURI())+"</td>");
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"right\">getRequestURL: </th>");
-            pout.write("<td>"+notag(request.getRequestURL().toString())+"</td>");
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"right\">getContextPath: </th>");
-            pout.write("<td>"+request.getContextPath()+"</td>");
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"right\">getServletPath: </th>");
-            pout.write("<td>"+notag(request.getServletPath())+"</td>");
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"right\">getPathInfo: </th>");
-            pout.write("<td>"+notag(request.getPathInfo())+"</td>");
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"right\">getPathTranslated: </th>");
-            pout.write("<td>"+notag(request.getPathTranslated())+"</td>");
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"right\">getQueryString: </th>");
-            pout.write("<td>"+notag(request.getQueryString())+"</td>");
-            pout.write("</tr><tr>\n");
-            
-            pout.write("<th align=\"right\">getProtocol: </th>");
-            pout.write("<td>"+request.getProtocol()+"</td>");
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"right\">getScheme: </th>");
-            pout.write("<td>"+request.getScheme()+"</td>");
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"right\">getServerName: </th>");
-            pout.write("<td>"+notag(request.getServerName())+"</td>");
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"right\">getServerPort: </th>");
-            pout.write("<td>"+Integer.toString(request.getServerPort())+"</td>");
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"right\">getLocalName: </th>");
-            pout.write("<td>"+request.getLocalName()+"</td>");
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"right\">getLocalAddr: </th>");
-            pout.write("<td>"+request.getLocalAddr()+"</td>");
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"right\">getLocalPort: </th>");
-            pout.write("<td>"+Integer.toString(request.getLocalPort())+"</td>");
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"right\">getRemoteUser: </th>");
-            pout.write("<td>"+request.getRemoteUser()+"</td>");
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"right\">getUserPrincipal: </th>");
-            pout.write("<td>"+request.getUserPrincipal()+"</td>");
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"right\">getRemoteAddr: </th>");
-            pout.write("<td>"+request.getRemoteAddr()+"</td>");
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"right\">getRemoteHost: </th>");
-            pout.write("<td>"+request.getRemoteHost()+"</td>");
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"right\">getRemotePort: </th>");
-            pout.write("<td>"+request.getRemotePort()+"</td>");
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"right\">getRequestedSessionId: </th>");
-            pout.write("<td>"+request.getRequestedSessionId()+"</td>");
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"right\">isSecure(): </th>");
-            pout.write("<td>"+request.isSecure()+"</td>");
-
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"right\">encodeRedirectURL(/foo?bar): </th>");
-            pout.write("<td>"+response.encodeRedirectURL("/foo?bar")+"</td>");
-
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"right\">isUserInRole(admin): </th>");
-            pout.write("<td>"+request.isUserInRole("admin")+"</td>");
-
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"right\">getLocale: </th>");
-            pout.write("<td>"+request.getLocale()+"</td>");
-            
-            Enumeration locales= request.getLocales();
-            while (locales.hasMoreElements())
-            {
-                pout.write("</tr><tr>\n");
-                pout.write("<th align=\"right\">getLocales: </th>");
-                pout.write("<td>"+locales.nextElement()+"</td>");
-            }
-            pout.write("</tr><tr>\n");
-            
-            pout.write("<th align=\"left\" colspan=\"2\"><big><br/>Other HTTP Headers:</big></th>");
-            Enumeration h= request.getHeaderNames();
-            String name;
-            while (h.hasMoreElements())
-            {
-                name= (String)h.nextElement();
-
-                Enumeration h2= request.getHeaders(name);
-                while (h2.hasMoreElements())
-                {
-                    String hv= (String)h2.nextElement();
-                    pout.write("</tr><tr>\n");
-                    pout.write("<th align=\"right\">"+notag(name)+": </th>");
-                    pout.write("<td>"+notag(hv)+"</td>");
-                }
-            }
-
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"left\" colspan=\"2\"><big><br/>Request Parameters:</big></th>");
-            h= request.getParameterNames();
-            while (h.hasMoreElements())
-            {
-                name= (String)h.nextElement();
-                pout.write("</tr><tr>\n");
-                pout.write("<th align=\"right\">"+notag(name)+": </th>");
-                pout.write("<td>"+notag(request.getParameter(name))+"</td>");
-                String[] values= request.getParameterValues(name);
-                if (values == null)
-                {
-                    pout.write("</tr><tr>\n");
-                    pout.write("<th align=\"right\">"+notag(name)+" Values: </th>");
-                    pout.write("<td>"+"NULL!"+"</td>");
-                }
-                else if (values.length > 1)
-                {
-                    for (int i= 0; i < values.length; i++)
-                    {
-                        pout.write("</tr><tr>\n");
-                        pout.write("<th align=\"right\">"+notag(name)+"["+i+"]: </th>");
-                        pout.write("<td>"+notag(values[i])+"</td>");
-                    }
-                }
-            }
-
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"left\" colspan=\"2\"><big><br/>Cookies:</big></th>");
-            Cookie[] cookies = request.getCookies();
-            for (int i=0; cookies!=null && i<cookies.length;i++)
-            {
-                Cookie cookie = cookies[i];
-
-                pout.write("</tr><tr>\n");
-                pout.write("<th align=\"right\">"+notag(cookie.getName())+": </th>");
-                pout.write("<td>"+notag(cookie.getValue())+"</td>");
-            }
-            
-            String content_type=request.getContentType();
-            if (content_type!=null &&
-                !content_type.startsWith("application/x-www-form-urlencoded") &&
-                !content_type.startsWith("multipart/form-data"))
-            {
-                pout.write("</tr><tr>\n");
-                pout.write("<th align=\"left\" valign=\"top\" colspan=\"2\"><big><br/>Content:</big></th>");
-                pout.write("</tr><tr>\n");
-                pout.write("<td><pre>");
-                char[] content= new char[4096];
-                int len;
-                try{
-                    Reader in=request.getReader();
-                    
-                    while((len=in.read(content))>=0)
-                        pout.write(notag(new String(content,0,len)));
-                }
-                catch(IOException e)
-                {
-                    pout.write(e.toString());
-                }
-                
-                pout.write("</pre></td>");
-            }
-            
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"left\" colspan=\"2\"><big><br/>Request Attributes:</big></th>");
-            Enumeration a= request.getAttributeNames();
-            while (a.hasMoreElements())
-            {
-                name= (String)a.nextElement();
-                pout.write("</tr><tr>\n");
-                pout.write("<th align=\"right\" valign=\"top\">"+name.replace("."," .")+": </th>");
-                Object value=request.getAttribute(name);
-                if (value instanceof File)
-                {
-                    File file = (File)value;
-                    pout.write("<td>"+"<pre>" + file.getName()+" ("+file.length()+" "+new Date(file.lastModified())+ ")</pre>"+"</td>");
-                }
-                else
-                    pout.write("<td>"+"<pre>" + toString(request.getAttribute(name)) + "</pre>"+"</td>");
-            }
-            request.setAttribute("org.eclipse.jetty.servlet.MultiPartFilter.files",null);
-
-            
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"left\" colspan=\"2\"><big><br/>Servlet InitParameters:</big></th>");
-            a= getInitParameterNames();
-            while (a.hasMoreElements())
-            {
-                name= (String)a.nextElement();
-                pout.write("</tr><tr>\n");
-                pout.write("<th align=\"right\">"+name+": </th>");
-                pout.write("<td>"+ toString(getInitParameter(name)) +"</td>");
-            }
-
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"left\" colspan=\"2\"><big><br/>Context InitParameters:</big></th>");
-            a= getServletContext().getInitParameterNames();
-            while (a.hasMoreElements())
-            {
-                name= (String)a.nextElement();
-                pout.write("</tr><tr>\n");
-                pout.write("<th align=\"right\" valign=\"top\">"+name.replace("."," .")+": </th>");
-                pout.write("<td>"+ toString(getServletContext().getInitParameter(name)) + "</td>");
-            }
-
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"left\" colspan=\"2\"><big><br/>Context Attributes:</big></th>");
-            a= getServletContext().getAttributeNames();
-            while (a.hasMoreElements())
-            {
-                name= (String)a.nextElement();
-                pout.write("</tr><tr>\n");
-                pout.write("<th align=\"right\" valign=\"top\">"+name.replace("."," .")+": </th>");
-                pout.write("<td>"+"<pre>" + toString(getServletContext().getAttribute(name)) + "</pre>"+"</td>");
-            }
-
-            String res= request.getParameter("resource");
-            if (res != null && res.length() > 0)
-            {
-                pout.write("</tr><tr>\n");
-                pout.write("<th align=\"left\" colspan=\"2\"><big><br/>Get Resource: \""+res+"\"</big></th>");
-
-                pout.write("</tr><tr>\n");
-                pout.write("<th align=\"right\">getServletContext().getResource(...): </th>");
-                try{pout.write("<td>"+getServletContext().getResource(res)+"</td>");}
-                catch(Exception e) {pout.write("<td>"+"" +e+"</td>");}
-                
-                pout.write("</tr><tr>\n");
-                pout.write("<th align=\"right\">getServletContext().getResourcePaths(...): </th>");
-                try{pout.write("<td>"+getServletContext().getResourcePaths(res)+"</td>");}
-                catch(Exception e) {pout.write("<td>"+"" +e+"</td>");}
-                
-                pout.write("</tr><tr>\n");
-                pout.write("<th align=\"right\">getServletContext().getContext(...): </th>");
-                
-                ServletContext context = getServletContext().getContext(res);
-                pout.write("<td>"+context+"</td>");
-                
-                if (context!=null)
-                {
-                    pout.write("</tr><tr>\n");
-                    pout.write("<th align=\"right\">getServletContext().getContext(...).getResource(...): </th>");
-                    try{pout.write("<td>"+context.getResource(res)+"</td>");}
-                    catch(Exception e) {pout.write("<td>"+"" +e+"</td>");}
-                    
-                    pout.write("</tr><tr>\n");
-                    pout.write("<th align=\"right\">getServletContext().getContext(...).getResourcePaths(...): </th>");
-                    try{pout.write("<td>"+context.getResourcePaths(res)+"</td>");}
-                    catch(Exception e) {pout.write("<td>"+"" +e+"</td>");}
-                    
-                    String cp=context.getContextPath();
-                    if (cp==null || "/".equals(cp))
-                        cp="";
-                    pout.write("</tr><tr>\n");
-                    pout.write("<th align=\"right\">getServletContext().getContext(...),getRequestDispatcher(...): </th>");
-                    pout.write("<td>"+getServletContext().getContext(res).getRequestDispatcher(res.substring(cp.length()))+"</td>");
-                }
-
-                pout.write("</tr><tr>\n");
-                pout.write("<th align=\"right\">this.getClass().getResource(...): </th>");
-                pout.write("<td>"+this.getClass().getResource(res)+"</td>");
-
-                pout.write("</tr><tr>\n");
-                pout.write("<th align=\"right\">this.getClass().getClassLoader().getResource(...): </th>");
-                pout.write("<td>"+this.getClass().getClassLoader().getResource(res)+"</td>");
-
-                pout.write("</tr><tr>\n");
-                pout.write("<th align=\"right\">Thread.currentThread().getContextClassLoader().getResource(...): </th>");
-                pout.write("<td>"+Thread.currentThread().getContextClassLoader().getResource(res)+"</td>");
-                pout.write("</tr><tr>\n");
-                pout.write("<th align=\"right\">Thread.currentThread().getContextClassLoader().getResources(...): </th>");
-                Enumeration<URL> urls = Thread.currentThread().getContextClassLoader().getResources(res);
-                if (urls==null)
-                    pout.write("<td>null</td>");
-                else
-                    pout.write("<td>"+Collections.list(urls)+"</td>");
-
-            }
-            
-            pout.write("</tr></table>\n");
-
-            /* ------------------------------------------------------------ */
-            pout.write("<h2>Request Wrappers</h2>\n");
-            ServletRequest rw=request;
-            int w=0;
-            while (rw !=null)
-            {
-                pout.write((w++)+": "+rw.getClass().getName()+"<br/>");
-                if (rw instanceof HttpServletRequestWrapper)
-                    rw=((HttpServletRequestWrapper)rw).getRequest();
-                else if (rw instanceof ServletRequestWrapper)
-                    rw=((ServletRequestWrapper)rw).getRequest();
-                else
-                    rw=null;
-            }
-
-            /* ------------------------------------------------------------ */
-            pout.write("<h2>Response Wrappers</h2>\n");
-            ServletResponse rsw=response;
-            w=0;
-            while (rsw !=null)
-            {
-                pout.write((w++)+": "+rsw.getClass().getName()+"<br/>");
-                if (rsw instanceof HttpServletResponseWrapper)
-                    rsw=((HttpServletResponseWrapper)rsw).getResponse();
-                else if (rsw instanceof ServletResponseWrapper)
-                    rsw=((ServletResponseWrapper)rsw).getResponse();
-                else
-                    rsw=null;
-            }
-            
-            pout.write("<br/>");
-            pout.write("<h2>International Characters (UTF-8)</h2>");
-            pout.write("LATIN LETTER SMALL CAPITAL AE<br/>\n");
-            pout.write("Directly uni encoded(\\u1d01): \u1d01<br/>");
-            pout.write("HTML reference (&AElig;): Æ<br/>");
-            pout.write("Decimal (&#7425;): ᴁ<br/>");
-            pout.write("Javascript unicode (\\u1d01) : <script language='javascript'>document.write(\"\u1d01\");</script><br/>");
-            pout.write("<br/>");
-            pout.write("<h2>Form to generate GET content</h2>");
-            pout.write("<form method=\"GET\" action=\""+response.encodeURL(getURI(request))+"\">");
-            pout.write("TextField: <input type=\"text\" name=\"TextField\" value=\"value\"/><br/>\n");
-            pout.write("<input type=\"submit\" name=\"Action\" value=\"Submit\">");
-            pout.write("</form>");
-
-            pout.write("<br/>");
-            
-            pout.write("<h2>Form to generate POST content</h2>");
-            pout.write("<form method=\"POST\" accept-charset=\"utf-8\" action=\""+response.encodeURL(getURI(request))+"\">");
-            pout.write("TextField: <input type=\"text\" name=\"TextField\" value=\"value\"/><br/>\n");
-            pout.write("Select: <select multiple name=\"Select\">\n");
-            pout.write("<option>ValueA</option>");
-            pout.write("<option>ValueB1,ValueB2</option>");
-            pout.write("<option>ValueC</option>");
-            pout.write("</select><br/>");
-            pout.write("<input type=\"submit\" name=\"Action\" value=\"Submit\"><br/>");
-            pout.write("</form>");
-            pout.write("<br/>");
-            
-            pout.write("<h2>Form to generate UPLOAD content</h2>");
-            pout.write("<form method=\"POST\" enctype=\"multipart/form-data\" accept-charset=\"utf-8\" action=\""+
-                    response.encodeURL(getURI(request))+(request.getQueryString()==null?"":("?"+request.getQueryString()))+
-                    "\">");
-            pout.write("TextField: <input type=\"text\" name=\"TextField\" value=\"comment\"/><br/>\n");
-            pout.write("File 1: <input type=\"file\" name=\"file1\" /><br/>\n");
-            pout.write("File 2: <input type=\"file\" name=\"file2\" /><br/>\n");
-            pout.write("<input type=\"submit\" name=\"Action\" value=\"Submit\"><br/>");
-            pout.write("</form>");
-
-            pout.write("<h2>Form to set Cookie</h2>");
-            pout.write("<form method=\"POST\" action=\""+response.encodeURL(getURI(request))+"\">");
-            pout.write("cookie: <input type=\"text\" name=\"cookie\" /><br/>\n");
-            pout.write("value: <input type=\"text\" name=\"cookiev\" /><br/>\n");
-            pout.write("<input type=\"submit\" name=\"Action\" value=\"setCookie\">");
-            pout.write("</form>\n");
-            
-            pout.write("<h2>Form to get Resource</h2>");
-            pout.write("<form method=\"POST\" action=\""+response.encodeURL(getURI(request))+"\">");
-            pout.write("resource: <input type=\"text\" name=\"resource\" /><br/>\n");
-            pout.write("<input type=\"submit\" name=\"Action\" value=\"getResource\">");
-            pout.write("</form>\n");
-        }
-        catch (Exception e)
-        {
-            getServletContext().log("dump "+e);
-        }
-        
-        String lines= request.getParameter("lines");
-        if (lines!=null)
-        {
-            char[] line = "<span>A line of characters. Blah blah blah blah.  blooble blooble</span></br>\n".toCharArray();
-            for (int l=Integer.parseInt(lines);l-->0;)
-            {
-                pout.write("<span>"+l+" </span>");
-                pout.write(line);
-            }
-        }
-        
-        pout.write("</body>\n</html>\n");
-        
-        pout.close();
-
-        if (pi != null)
-        {
-            if ("/ex4".equals(pi))
-                throw new ServletException("test ex4", new Throwable());
-            if ("/ex5".equals(pi))
-                throw new IOException("test ex5");
-            if ("/ex6".equals(pi))
-                throw new UnavailableException("test ex6");
-        }
-
-
-    }
-
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public String getServletInfo()
-    {
-        return "Dump Servlet";
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public synchronized void destroy()
-    {
-        _timer.cancel();
-    }
-
-    /* ------------------------------------------------------------ */
-    private String getURI(HttpServletRequest request)
-    {
-        String uri= (String)request.getAttribute("javax.servlet.forward.request_uri");
-        if (uri == null)
-            uri= request.getRequestURI();
-        return uri;
-    }
-
-    /* ------------------------------------------------------------ */
-    private static String toString(Object o)
-    {
-        if (o == null)
-            return null;
-
-        try
-        {
-            if (o.getClass().isArray())
-            {
-                StringBuffer sb = new StringBuffer();
-                if (!o.getClass().getComponentType().isPrimitive())
-                {
-                    Object[] array= (Object[])o;
-                    for (int i= 0; i < array.length; i++)
-                    {
-                        if (i > 0)
-                            sb.append("\n");
-                        sb.append(array.getClass().getComponentType().getName());
-                        sb.append("[");
-                        sb.append(i);
-                        sb.append("]=");
-                        sb.append(toString(array[i]));
-                    }
-                    return sb.toString();
-                }
-                else
-                { 
-                    int length = Array.getLength(o);
-                    for (int i=0;i<length;i++)
-                    {
-                        if (i > 0)
-                            sb.append("\n");
-                        sb.append(o.getClass().getComponentType().getName()); 
-                        sb.append("[");
-                        sb.append(i);
-                        sb.append("]=");
-                        sb.append(toString(Array.get(o, i)));
-                    }
-                    return sb.toString();
-                }
-            }
-            else
-                return o.toString();
-        }
-        catch (Exception e)
-        {
-            return e.toString();
-        }
-    }
-
-    private boolean dump(HttpServletResponse response, String data, String chars, String block, String dribble, boolean flush) throws IOException
-    {
-        if (data != null && data.length() > 0)
-        {
-            long d=Long.parseLong(data);
-            int b=(block!=null&&block.length()>0)?Integer.parseInt(block):50;
-            byte[] buf=new byte[b];
-            for (int i=0;i<b;i++)
-            {
-                
-                buf[i]=(byte)('0'+(i%10));
-                if (i%10==9)
-                    buf[i]=(byte)'\n';
-            }
-            buf[0]='o';
-            OutputStream out=response.getOutputStream();
-            response.setContentType("text/plain");
-            while (d > 0)
-            {
-                if (b==1)
-                {
-                    out.write(d%80==0?'\n':'.');
-                    d--;
-                }
-                else if (d>=b)
-                {
-                    out.write(buf);
-                    d=d-b;
-                }
-                else
-                {
-                    out.write(buf,0,(int)d);
-                    d=0;
-                }
-                
-                if (dribble!=null)
-                {
-                    out.flush();
-                    try
-                    {
-                        Thread.sleep(Long.parseLong(dribble));
-                    }
-                    catch (Exception e)
-                    {
-                        e.printStackTrace();
-                        break;
-                    }
-                }
-                
-            }
-            
-            if (flush)
-                out.flush();
-            
-            return true;
-        }
-
-        // Handle a dump of data
-        if (chars != null && chars.length() > 0)
-        {
-            long d=Long.parseLong(chars);
-            int b=(block!=null&&block.length()>0)?Integer.parseInt(block):50;
-            char[] buf=new char[b];
-            for (int i=0;i<b;i++)
-            {
-                buf[i]=(char)('0'+(i%10));
-                if (i%10==9)
-                    buf[i]='\n';
-            }
-            buf[0]='o';
-            response.setContentType("text/plain");
-            PrintWriter out=response.getWriter();
-            while (d > 0 && !out.checkError())
-            {
-                if (b==1)
-                {
-                    out.write(d%80==0?'\n':'.');
-                    d--;
-                }
-                else if (d>=b)
-                {
-                    out.write(buf);
-                    d=d-b;
-                }
-                else
-                {
-                    out.write(buf,0,(int)d);
-                    d=0;
-                }
-            }
-            return true;
-        }    
-        return false;
-    }
-    
-    private String notag(String s)
-    {
-        if (s==null)
-            return "null";
-        s=StringUtil.replace(s,"&","&");
-        s=StringUtil.replace(s,"<","<");
-        s=StringUtil.replace(s,">",">");
-        return s;
-    }
-}
diff --git a/test-jetty-webapp/src/main/java/com/acme/HelloWorld.java b/test-jetty-webapp/src/main/java/com/acme/HelloWorld.java
deleted file mode 100644
index 8afd6e6..0000000
--- a/test-jetty-webapp/src/main/java/com/acme/HelloWorld.java
+++ /dev/null
@@ -1,70 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package com.acme;
-import java.io.IOException;
-
-import javax.servlet.ServletConfig;
-import javax.servlet.ServletException;
-import javax.servlet.ServletOutputStream;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-
-/* ------------------------------------------------------------ */
-/** Dump Servlet Request.
- * 
- */
-public class HelloWorld extends HttpServlet
-{
-    /* ------------------------------------------------------------ */
-    @Override
-    public void init(ServletConfig config) throws ServletException
-    {
-    	super.init(config);
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
-    {
-        doGet(request, response);
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
-    {
-        response.setContentType("text/html");
-        ServletOutputStream out = response.getOutputStream();
-        out.println("<html>");
-        out.println("<h1>Hello World</h1>");
-        out.println("</html>");
-        out.flush();
-        
-        try
-        {
-            Thread.sleep(200);
-        }
-        catch (InterruptedException e)
-        {
-            getServletContext().log("exception",e);
-        }
-    }
-}
diff --git a/test-jetty-webapp/src/main/java/com/acme/LoginServlet.java b/test-jetty-webapp/src/main/java/com/acme/LoginServlet.java
deleted file mode 100644
index 41fae75..0000000
--- a/test-jetty-webapp/src/main/java/com/acme/LoginServlet.java
+++ /dev/null
@@ -1,92 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package com.acme;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.PrintStream;
-import java.net.URL;
-import java.net.URLClassLoader;
-import java.util.Calendar;
-import java.util.GregorianCalendar;
-
-import javax.servlet.ServletConfig;
-import javax.servlet.ServletException;
-import javax.servlet.ServletOutputStream;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-
-
-/* ------------------------------------------------------------ */
-/** Dump Servlet Request.
- * 
- */
-public class LoginServlet extends HttpServlet
-{
-    private static final Logger LOG = Log.getLogger(SecureModeServlet.class);
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public void init(ServletConfig config) throws ServletException
-    {
-    	super.init(config);
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
-    {
-        doGet(request, response);
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
-    {
-         
-        response.setContentType("text/html");
-        ServletOutputStream out = response.getOutputStream();
-        out.println("<html>");
-        out.println("<br/>Before getUserPrincipal="+request.getUserPrincipal());
-        out.println("<br/>Before getRemoteUser="+request.getRemoteUser());
-        String param = request.getParameter("action");
-
-        if ("login".equals(param))
-        {
-              request.login("jetty", "jetty");
-        }
-        else if ("logout".equals(param))
-        {
-             request.logout();
-        }
-        else if ("wrong".equals(param))
-        {
-             request.login("jetty", "123");
-        }  
-
-        out.println("<br/>After getUserPrincipal="+request.getUserPrincipal());
-        out.println("<br/>After getRemoteUser="+request.getRemoteUser());
-        out.println("</html>");
-        out.flush();
-    }
-}
diff --git a/test-jetty-webapp/src/main/java/com/acme/RegTest.java b/test-jetty-webapp/src/main/java/com/acme/RegTest.java
deleted file mode 100644
index f4d39f9..0000000
--- a/test-jetty-webapp/src/main/java/com/acme/RegTest.java
+++ /dev/null
@@ -1,194 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package com.acme;
-import java.io.IOException;
-import java.io.OutputStreamWriter;
-import java.io.PrintWriter;
-
-import javax.servlet.ServletConfig;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jetty.util.StringUtil;
-
-
-
-
-/* ------------------------------------------------------------ */
-/** Rego Servlet - tests being accessed from servlet 3.0 programmatic 
- * configuration.
- * 
- */
-public class RegTest extends HttpServlet
-{
-    
-    /* ------------------------------------------------------------ */
-    @Override
-    public void init(ServletConfig config) throws ServletException
-    {
-    	super.init(config);
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
-    {
-        doGet(request, response);
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public void doGet(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException
-    { 
-        request.setCharacterEncoding("UTF-8");        
-        PrintWriter pout=null;
-        
-        try
-        {
-            pout =response.getWriter();
-        }
-        catch(IllegalStateException e)
-        {
-            pout=new PrintWriter(new OutputStreamWriter(response.getOutputStream(),"UTF-8"));
-        }
-   
-        try
-        {
-            pout.write("<html>\n<body>\n");
-            pout.write("<h1>Rego Servlet</h1>\n");
-            pout.write("<table width=\"95%\">");
-            pout.write("<tr>\n");
-            pout.write("<th align=\"right\">getMethod: </th>");
-            pout.write("<td>" + notag(request.getMethod())+"</td>");
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"right\">getContentLength: </th>");
-            pout.write("<td>"+Integer.toString(request.getContentLength())+"</td>");
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"right\">getContentType: </th>");
-            pout.write("<td>"+notag(request.getContentType())+"</td>");
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"right\">getRequestURI: </th>");
-            pout.write("<td>"+notag(request.getRequestURI())+"</td>");
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"right\">getRequestURL: </th>");
-            pout.write("<td>"+notag(request.getRequestURL().toString())+"</td>");
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"right\">getContextPath: </th>");
-            pout.write("<td>"+request.getContextPath()+"</td>");
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"right\">getServletPath: </th>");
-            pout.write("<td>"+notag(request.getServletPath())+"</td>");
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"right\">getPathInfo: </th>");
-            pout.write("<td>"+notag(request.getPathInfo())+"</td>");
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"right\">getPathTranslated: </th>");
-            pout.write("<td>"+notag(request.getPathTranslated())+"</td>");
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"right\">getQueryString: </th>");
-            pout.write("<td>"+notag(request.getQueryString())+"</td>");
-            pout.write("</tr><tr>\n");
-            
-            pout.write("<th align=\"right\">getProtocol: </th>");
-            pout.write("<td>"+request.getProtocol()+"</td>");
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"right\">getScheme: </th>");
-            pout.write("<td>"+request.getScheme()+"</td>");
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"right\">getServerName: </th>");
-            pout.write("<td>"+notag(request.getServerName())+"</td>");
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"right\">getServerPort: </th>");
-            pout.write("<td>"+Integer.toString(request.getServerPort())+"</td>");
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"right\">getLocalName: </th>");
-            pout.write("<td>"+request.getLocalName()+"</td>");
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"right\">getLocalAddr: </th>");
-            pout.write("<td>"+request.getLocalAddr()+"</td>");
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"right\">getLocalPort: </th>");
-            pout.write("<td>"+Integer.toString(request.getLocalPort())+"</td>");
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"right\">getRemoteUser: </th>");
-            pout.write("<td>"+request.getRemoteUser()+"</td>");
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"right\">getUserPrincipal: </th>");
-            pout.write("<td>"+request.getUserPrincipal()+"</td>");
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"right\">getRemoteAddr: </th>");
-            pout.write("<td>"+request.getRemoteAddr()+"</td>");
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"right\">getRemoteHost: </th>");
-            pout.write("<td>"+request.getRemoteHost()+"</td>");
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"right\">getRemotePort: </th>");
-            pout.write("<td>"+request.getRemotePort()+"</td>");
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"right\">getRequestedSessionId: </th>");
-            pout.write("<td>"+request.getRequestedSessionId()+"</td>");
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"right\">isSecure(): </th>");
-            pout.write("<td>"+request.isSecure()+"</td>");
-
-            pout.write("</tr><tr>\n");
-            pout.write("<th align=\"right\">isUserInRole(admin): </th>");
-            pout.write("<td>"+request.isUserInRole("admin")+"</td>");
-
-            pout.write("</tr></table>");
-   
-        }
-        catch (Exception e)
-        {
-            getServletContext().log("dump "+e);
-        }
-        
-        
-        pout.write("</body>\n</html>\n");
-        
-        pout.close();
-    }
-
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public String getServletInfo()
-    {
-        return "Rego Servlet";
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public synchronized void destroy()
-    {
-    }
-
-    
-    private String notag(String s)
-    {
-        if (s==null)
-            return "null";
-        s=StringUtil.replace(s,"&","&");
-        s=StringUtil.replace(s,"<","<");
-        s=StringUtil.replace(s,">",">");
-        return s;
-    }
-}
diff --git a/test-jetty-webapp/src/main/java/com/acme/RewriteServlet.java b/test-jetty-webapp/src/main/java/com/acme/RewriteServlet.java
deleted file mode 100644
index 87cad81..0000000
--- a/test-jetty-webapp/src/main/java/com/acme/RewriteServlet.java
+++ /dev/null
@@ -1,74 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package com.acme;
-import java.io.IOException;
-
-import javax.servlet.ServletException;
-import javax.servlet.ServletOutputStream;
-import javax.servlet.http.Cookie;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-
-/* ------------------------------------------------------------ */
-/** Test Servlet Rewrite
- * 
- * 
- */
-public class RewriteServlet extends HttpServlet
-{
-    /* ------------------------------------------------------------ */
-    @Override
-    public void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException
-    {
-        doGet(req, res);
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException
-    {
-        ServletOutputStream out = res.getOutputStream();
-        out.println("<html><body><table>");
-        out.println("<tr><th>Original request URI: </th><td>" + req.getAttribute("requestedPath") + "</td></tr>");
-        out.println("<tr><th>Rewritten request URI: </th><td>" + req.getRequestURI() + "</td></tr>");
-       
-        Cookie cookie = null;
-        for(Cookie c: req.getCookies())
-        {
-            if (c.getName().equals("visited"))
-            {
-                cookie = c;
-                break;
-            }
-        }
-        if (cookie!=null)
-            out.println("<tr><th>Previously visited: </th></td><td>" + cookie.getValue()+"</td></tr>");
-
-        out.println("</table></body></html>");
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public String getServletInfo()
-    {
-        return "Rewrite Servlet";
-    }
-}
diff --git a/test-jetty-webapp/src/main/java/com/acme/SecureModeServlet.java b/test-jetty-webapp/src/main/java/com/acme/SecureModeServlet.java
deleted file mode 100644
index 2defe11..0000000
--- a/test-jetty-webapp/src/main/java/com/acme/SecureModeServlet.java
+++ /dev/null
@@ -1,382 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package com.acme;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.PrintStream;
-import java.net.URL;
-import java.net.URLClassLoader;
-import java.util.Calendar;
-import java.util.GregorianCalendar;
-
-import javax.servlet.ServletConfig;
-import javax.servlet.ServletException;
-import javax.servlet.ServletOutputStream;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-
-
-/* ------------------------------------------------------------ */
-/** Dump Servlet Request.
- * 
- */
-public class SecureModeServlet extends HttpServlet
-{
-    private static final Logger LOG = Log.getLogger(SecureModeServlet.class);
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public void init(ServletConfig config) throws ServletException
-    {
-    	super.init(config);
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
-    {
-        doGet(request, response);
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
-    {
-         
-        response.setContentType("text/html");
-        ServletOutputStream out = response.getOutputStream();
-        out.println("<html>");
-        out.println("  <title>Secure Jetty Test Webapp</title>");
-
-        try
-        {
-            runPropertyChecks(out);
-
-            runFileSystemChecks(out);
-
-            runLoggingChecks(out);
-
-            runClassloaderChecks(out);
-        }
-        catch (Exception e)
-        {
-            e.printStackTrace(new PrintStream(out));
-        }
-        out.println("</html>");
-        out.flush();
-
-        try
-        {
-            Thread.sleep(200);
-        }
-        catch (InterruptedException e)
-        {
-            getServletContext().log("exception",e);
-        }
-    }
-
-    private void runClassloaderChecks(ServletOutputStream out) throws Exception
-    {
-        out.println("    <h1>Checking Classloader Setup</h1>");
-        out.println("      <p>");
-
-        System.getProperty("user.dir");
-        try
-        {
-            out.println("check ability to create classloader<br/>");
-            URL url = new URL("http://not.going.to.work");
-            new URLClassLoader(new URL[] { url });
-            out.println("status: <b>SUCCESS - unexpected</b><br/>");
-        }
-        catch (SecurityException e)
-        {
-            out.println("status: <b>FAILURE - expected</b><br/>");
-        }
-
-        out.println("      </p><br/><br/>");
-    }
-
-    private void runLoggingChecks(ServletOutputStream out) throws Exception
-    {
-        out.println("    <h1>Checking File System</h1>");
-        out.println("      <p>");
-
-        String userDir = System.getProperty("user.dir");
-        try
-        {
-            out.println("check ability to log<br/>");
-            LOG.info("testing logging");
-            out.println("status: <b>SUCCESS - expected</b><br/>");
-        }
-        catch (SecurityException e)
-        {
-            out.println("status: <b>FAILURE - unexpected</b><br/>");
-            out.println("<table><tr><td>");
-            e.printStackTrace(new PrintStream(out));
-            out.println("</td></tr></table>");
-        }
-
-        try
-        {
-            Calendar c = new GregorianCalendar();
-
-            String logFile = c.get(Calendar.YEAR) + "_" + c.get(Calendar.MONTH) + "_" + c.get(Calendar.DAY_OF_MONTH) + ".request.log";
-
-            out.println("check ability to access log file directly<br/>");
-            File jettyHomeFile = new File(userDir + File.separator + "logs" + File.separator + logFile);
-            jettyHomeFile.canRead();
-            out.println("status: <b>SUCCESS - unexpected</b><br/>");
-        }
-        catch (SecurityException e)
-        {
-            out.println("status: <b>FAILURE - expected</b><br/>");
-        }
-
-        out.println("      </p><br/><br/>");
-    }
-
-    private void runFileSystemChecks(ServletOutputStream out) throws Exception
-    {
-        out.println("    <h1>Checking File System</h1>");
-
-        /*
-         * test the reading and writing of a read only permission
-         */
-        out.println("      <p>");
-
-        String userDir = System.getProperty("user.dir");
-        try
-        {
-            out.println("check read for $jetty.home/lib/policy/jetty.policy<br/>");
-
-            File jettyHomeFile = new File(userDir + File.separator + "lib" + File.separator + "policy" + File.separator + "jetty.policy");
-            jettyHomeFile.canRead();
-            out.println("status: <b>SUCCESS - expected</b><br/>");
-        }
-        catch (SecurityException e)
-        {
-            out.println("status: <b>FAILURE - unexpected</b><br/>");
-            out.println("<table><tr><td>");
-            e.printStackTrace(new PrintStream(out));
-            out.println("</td></tr></table>");
-        }
-
-        try
-        {
-            out.println("check write permission for $jetty.home/lib/policy/jetty.policy<br/>");
-
-            File jettyHomeFile = new File(userDir + File.separator + "lib" + File.separator + "policy" + File.separator + "jetty.policy");
-            jettyHomeFile.canWrite();
-            out.println("status: <b>SUCCESS - unexpected</b><br/>");
-        }
-        catch (SecurityException e)
-        {
-            out.println("status: <b>FAILURE - expected</b><br/>");
-        }
-
-        try
-        {
-            out.println("check read permission for $jetty.home/lib<br/>");
-
-            File jettyHomeFile = new File(userDir + File.separator + "lib");
-            jettyHomeFile.canRead();
-            out.println("status: <b>SUCCESS - unexpected</b><br/>");
-        }
-        catch (SecurityException e)
-        {
-            out.println("status: <b>FAILURE - expected</b><br/>");
-        }
-
-        try
-        {
-            out.println("check write permission for $jetty.home/lib<br/>");
-
-            File jettyHomeFile = new File(userDir + File.separator + "lib");
-            jettyHomeFile.canWrite();
-            out.println("status: <b>SUCCESS - unexpected</b><br/>");
-        }
-        catch (SecurityException e)
-        {
-            out.println("status: <b>FAILURE - expected</b><br/>");
-        }
-
-        try
-        {
-            out.println("check read permission for $jetty.home<br/>");
-
-            File jettyHomeFile = new File(userDir + File.separator);
-            jettyHomeFile.canRead();
-            out.println("status: <b>SUCCESS - unexpected</b><br/>");
-        }
-        catch (SecurityException e)
-        {
-            out.println("status: <b>FAILURE - expected</b><br/>");
-        }
-
-        try
-        {
-            out.println("check write permission for $jetty.home<br/>");
-
-            File jettyHomeFile = new File(userDir + File.separator);
-            jettyHomeFile.canWrite();
-            out.println("status: <b>SUCCESS - unexpected</b><br/>");
-        }
-        catch (SecurityException e)
-        {
-            out.println("status: <b>FAILURE - expected</b><br/>");
-        }
-
-        try
-        {
-            out.println("check read permission for $jetty.home/logs<br/>");
-
-            File jettyHomeFile = new File(userDir + File.separator + "logs" + File.separator);
-            jettyHomeFile.canRead();
-            out.println("status: <b>SUCCESS - unexpected</b><br/>");
-        }
-        catch (SecurityException e)
-        {
-            out.println("status: <b>FAILURE - expected</b><br/>");
-        }
-
-        try
-        {
-            out.println("check read permission for $jetty.home/logs<br/>");
-
-            File jettyHomeFile = new File(userDir + File.separator + "logs");
-            jettyHomeFile.canWrite();
-            out.println("status: <b>SUCCESS - unexpected</b><br/>");
-        }
-        catch (SecurityException e)
-        {
-            out.println("status: <b>FAILURE - expected</b><br/>");
-        }
-
-        out.println("      </p><br/><br/>");
-    }
-
-    private void runPropertyChecks(ServletOutputStream out) throws IOException
-    {
-
-        out.println("    <h1>Checking Properties</h1>");
-
-        /*
-         * test the reading and writing of a read only permission
-         */
-        out.println("    <h3>Declared Property - read</h3>");
-        out.println("      <p>");
-        try
-        {
-            out.println("check read permission for __ALLOWED_READ_PROPERTY <br/>");
-            System.getProperty("__ALLOWED_READ_PROPERTY");
-            out.println("status: <b>SUCCESS - expected</b><br/>");
-        }
-        catch (SecurityException e)
-        {
-            out.println("status: <b>FAILURE - unexpected</b><br/>");
-            out.println("<table><tr><td>");
-            e.printStackTrace(new PrintStream(out));
-            out.println("</td></tr></table>");
-        }
-        try
-        {
-            out.println("check write permission for __ALLOWED_READ_PROPERTY<br/>");
-            System.setProperty("__ALLOWED_READ_PROPERTY","SUCCESS - unexpected");
-            String value = System.getProperty("__ALLOWED_READ_PROPERTY");
-            out.println("status: <b>" + value + "</b><br/>");
-        }
-        catch (SecurityException e)
-        {
-            out.println("status: <b>FAILURE - expected</b><br/>");
-        }
-
-        out.println("      </p><br/><br/>");
-        
-        /*
-         * test the reading and writing of a read/write permission
-         */
-        out.println("    <h3>Declared Property - read/write</h3>");
-        out.println("      <p>");
-        try
-        {
-            out.println("check read permission for __ALLOWED_WRITE_PROPERTY<br/>");
-            System.getProperty("__ALLOWED_WRITE_PROPERTY");
-            out.println("Status: <b>SUCCESS - expected</b><br/>");
-        }
-        catch (SecurityException e)
-        {
-            out.println("status: <b>FAILURE - unexpected</b><br/>");
-            out.println("<table><tr><td>");
-            e.printStackTrace(new PrintStream(out));
-            out.println("</td></tr></table>");
-        }
-        try
-        {
-            out.println("check write permission for __ALLOWED_WRITE_PROPERTY<br/>");
-            System.setProperty("__ALLOWED_WRITE_PROPERTY","SUCCESS - expected");
-            String value = System.getProperty("__ALLOWED_WRITE_PROPERTY");
-            out.println("status: <b>" + value + "</b><br/>");
-        }
-        catch (SecurityException e)
-        {
-            out.println("status: <b>FAILURE - unexpected</b><br/>");
-            out.println("<table><tr><td>");
-            e.printStackTrace(new PrintStream(out));
-            out.println("</td></tr></table>");
-        }
-
-        out.println("      </p><br/><br/>");
-
-        /*
-         * test the reading and writing of an undeclared property
-         */
-        out.println("    <h3>checking forbidden properties</h3>");
-        out.println("      <p>");
-        try
-        {
-            out.println("check read permission for __UNDECLARED_PROPERTY: <br/>");
-            System.getProperty("__UNDECLARED_PROPERTY");
-            out.println("status: <b>SUCCESS - expected</b><br/>");
-        }
-        catch (SecurityException e)
-        {
-            out.println("status: <b>FAILURE - expected</b><br/>");
-        }
-        try
-        {
-            out.println("check write permission for __UNDECLARED_PROPERTY: <br/>");
-            System.setProperty("__UNDECLARED_PROPERTY","SUCCESS - unexpected");
-            String value = System.getProperty("__UNDECLARED_PROPERTY");
-            out.println("status: <b>" + value + "</b><br/>");
-        }
-        catch (SecurityException e)
-        {
-            out.println("status: <b>FAILURE - expected</b><br/>");
-        }
-
-        out.println("      </p><br/><br/>");
-    }
- 
-    
-}
diff --git a/test-jetty-webapp/src/main/java/com/acme/SessionDump.java b/test-jetty-webapp/src/main/java/com/acme/SessionDump.java
deleted file mode 100644
index 0261639..0000000
--- a/test-jetty-webapp/src/main/java/com/acme/SessionDump.java
+++ /dev/null
@@ -1,185 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package com.acme;
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.util.Date;
-import java.util.Enumeration;
-
-import javax.servlet.ServletConfig;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import javax.servlet.http.HttpSession;
-
-
-/* ------------------------------------------------------------ */
-/** Test Servlet Sessions.
- *
- * 
- */
-public class SessionDump extends HttpServlet
-{
-
-    int redirectCount=0;
-    /* ------------------------------------------------------------ */
-    String pageType;
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public void init(ServletConfig config)
-         throws ServletException
-    {
-        super.init(config);        
-    }
-
-    /* ------------------------------------------------------------ */
-    protected void handleForm(HttpServletRequest request,
-                          HttpServletResponse response) 
-    {
-        HttpSession session = request.getSession(false);
-        String action = request.getParameter("Action");
-        String name =  request.getParameter("Name");
-        String value =  request.getParameter("Value");
-
-        if (action!=null)
-        {
-            if(action.equals("New Session"))
-            {   
-                session = request.getSession(true);
-                session.setAttribute("test","value");
-            }
-            else if (session!=null)
-            {
-                if (action.equals("Invalidate"))
-                    session.invalidate();
-                else if (action.equals("Set") && name!=null && name.length()>0)
-                    session.setAttribute(name,value);
-                else if (action.equals("Remove"))
-                    session.removeAttribute(name);
-            }       
-        }
-    }
-    
-    /* ------------------------------------------------------------ */
-    @Override
-    public void doPost(HttpServletRequest request,
-                       HttpServletResponse response) 
-        throws ServletException, IOException
-    {
-        handleForm(request,response);
-        String nextUrl = getURI(request)+"?R="+redirectCount++;
-        String encodedUrl=response.encodeRedirectURL(nextUrl);
-        response.sendRedirect(encodedUrl);
-    }
-        
-    /* ------------------------------------------------------------ */
-    @Override
-    public void doGet(HttpServletRequest request,
-                      HttpServletResponse response) 
-        throws ServletException, IOException
-    {
-        handleForm(request,response);
-        
-        response.setContentType("text/html");
-
-        HttpSession session = request.getSession(getURI(request).indexOf("new")>0);
-        try
-        {
-            if (session!=null) 
-                session.isNew();
-        }
-        catch(IllegalStateException e)
-        {
-            session=null;
-        }
-        
-        PrintWriter out = response.getWriter();
-        out.println("<h1>Session Dump Servlet:</h1>"); 
-        out.println("<form action=\""+response.encodeURL(getURI(request))+"\" method=\"post\">");       
-        
-        if (session==null)
-        {
-            out.println("<H3>No Session</H3>");
-            out.println("<input type=\"submit\" name=\"Action\" value=\"New Session\"/>");
-        }
-        else
-        {
-            try
-            {  
-                out.println("<b>ID:</b> "+session.getId()+"<br/>");
-                out.println("<b>New:</b> "+session.isNew()+"<br/>");
-                out.println("<b>Created:</b> "+new Date(session.getCreationTime())+"<br/>");
-                out.println("<b>Last:</b> "+new Date(session.getLastAccessedTime())+"<br/>");
-                out.println("<b>Max Inactive:</b> "+session.getMaxInactiveInterval()+"<br/>");
-                out.println("<b>Context:</b> "+session.getServletContext()+"<br/>");
-                
-              
-                Enumeration keys=session.getAttributeNames();
-                while(keys.hasMoreElements())
-                {
-                    String name=(String)keys.nextElement();
-                    String value=""+session.getAttribute(name);
-
-                    out.println("<b>"+name+":</b> "+value+"<br/>");
-                }
-
-                out.println("<b>Name:</b><input type=\"text\" name=\"Name\" /><br/>");
-                out.println("<b>Value:</b><input type=\"text\" name=\"Value\" /><br/>");
-
-                out.println("<input type=\"submit\" name=\"Action\" value=\"Set\"/>");
-                out.println("<input type=\"submit\" name=\"Action\" value=\"Remove\"/>");
-                out.println("<input type=\"submit\" name=\"Action\" value=\"Refresh\"/>");
-                out.println("<input type=\"submit\" name=\"Action\" value=\"Invalidate\"/><br/>");
-                
-                out.println("</form><br/>");
-                
-                if (request.isRequestedSessionIdFromCookie())
-                    out.println("<P>Turn off cookies in your browser to try url encoding<BR>");
-                
-                if (request.isRequestedSessionIdFromURL())
-                    out.println("<P>Turn on cookies in your browser to try cookie encoding<BR>");
-                out.println("<a href=\""+response.encodeURL(request.getRequestURI()+"?q=0")+"\">Encoded Link</a><BR>");
-                
-            }
-            catch (IllegalStateException e)
-            {
-                e.printStackTrace();
-            }
-        }
-
-    }
-
-    /* ------------------------------------------------------------ */
-    @Override
-    public String getServletInfo() {
-        return "Session Dump Servlet";
-    }
-
-    /* ------------------------------------------------------------ */
-    private String getURI(HttpServletRequest request)
-    {
-        String uri=(String)request.getAttribute("javax.servlet.forward.request_uri");
-        if (uri==null)
-            uri=request.getRequestURI();
-        return uri;
-    }
-    
-}
diff --git a/test-jetty-webapp/src/main/java/com/acme/TestFilter.java b/test-jetty-webapp/src/main/java/com/acme/TestFilter.java
deleted file mode 100644
index d15b9ef..0000000
--- a/test-jetty-webapp/src/main/java/com/acme/TestFilter.java
+++ /dev/null
@@ -1,129 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package com.acme;
-
-import java.io.IOException;
-import java.util.HashSet;
-import java.util.Set;
-
-import javax.servlet.Filter;
-import javax.servlet.FilterChain;
-import javax.servlet.FilterConfig;
-import javax.servlet.ServletContext;
-import javax.servlet.ServletException;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletRequestWrapper;
-import javax.servlet.ServletResponse;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletRequestWrapper;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-
-/* ------------------------------------------------------------ */
-/** TestFilter.
- * 
- * This filter checks for a none local request, and if the init parameter
- * "remote" is not set to true, then all non local requests are forwarded
- * to /remote.html
- * 
- */
-public class TestFilter implements Filter
-{
-    private static final Logger LOG = Log.getLogger(TestFilter.class);
-
-    private boolean _remote;
-    private ServletContext _context;
-    private final Set<String> _allowed = new HashSet<String>();
-    
-    /* ------------------------------------------------------------ */
-    /* 
-     * @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
-     */
-    public void init(FilterConfig filterConfig) throws ServletException
-    {
-        _context= filterConfig.getServletContext();
-        _remote=Boolean.parseBoolean(filterConfig.getInitParameter("remote"));
-        _allowed.add("/favicon.ico");
-        _allowed.add("/jetty_banner.gif");
-        
-        LOG.debug("TestFilter#remote="+_remote);
-    }
-
-    /* ------------------------------------------------------------ */
-    /* 
-     * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)
-     */
-    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
-            throws IOException, ServletException
-    {
-        String from = request.getRemoteHost();
-        String to = request.getServerName();
-        String path=((HttpServletRequest)request).getServletPath();
-
-        if (!"/remote.html".equals(path) && !_remote && !_allowed.contains(path) && (
-            !from.equals("localhost") && !from.startsWith("127.") && from.indexOf(":1")<0 ||
-            !to.equals("localhost")&&!to.startsWith("127.0.0.") && to.indexOf(":1")<0))
-        {
-            if ("/".equals(path))
-                _context.getRequestDispatcher("/remote.html").forward(request,response);
-            else
-                ((HttpServletResponse)response).sendRedirect("/remote.html");
-            return;
-        }
-        
-        Integer old_value=null;
-        ServletRequest r = request;
-        while (r instanceof ServletRequestWrapper)
-            r=((ServletRequestWrapper)r).getRequest();
-        
-        try
-        {
-            old_value=(Integer)request.getAttribute("testFilter");
-            
-            Integer value=(old_value==null)?new Integer(1):new Integer(old_value.intValue()+1);
-                        
-            request.setAttribute("testFilter", value);
-            
-            String qString = ((HttpServletRequest)request).getQueryString();
-            if (qString != null && qString.indexOf("wrap")>=0)
-            {
-                request=new HttpServletRequestWrapper((HttpServletRequest)request);
-            }
-            _context.setAttribute("request"+r.hashCode(),value);
-            
-            chain.doFilter(request, response);
-        }
-        finally
-        {
-            request.setAttribute("testFilter", old_value);
-            _context.setAttribute("request"+r.hashCode(),old_value);
-        }
-    }
-
-    /* ------------------------------------------------------------ */
-    /* 
-     * @see javax.servlet.Filter#destroy()
-     */
-    public void destroy()
-    {
-    }
-
-}
diff --git a/test-jetty-webapp/src/main/java/com/acme/TestListener.java b/test-jetty-webapp/src/main/java/com/acme/TestListener.java
deleted file mode 100644
index 9b358d3..0000000
--- a/test-jetty-webapp/src/main/java/com/acme/TestListener.java
+++ /dev/null
@@ -1,180 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package com.acme;
-
-import javax.servlet.DispatcherType;
-import javax.servlet.ServletContextAttributeEvent;
-import javax.servlet.ServletContextAttributeListener;
-import javax.servlet.ServletContextEvent;
-import javax.servlet.ServletContextListener;
-import javax.servlet.ServletRequestAttributeEvent;
-import javax.servlet.ServletRequestAttributeListener;
-import javax.servlet.ServletRequestEvent;
-import javax.servlet.ServletRequestListener;
-import javax.servlet.ServletRegistration;
-import javax.servlet.FilterRegistration;
-import javax.servlet.ServletSecurityElement;
-import javax.servlet.HttpConstraintElement;
-import javax.servlet.HttpMethodConstraintElement;
-import javax.servlet.annotation.ServletSecurity;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpSessionActivationListener;
-import javax.servlet.http.HttpSessionAttributeListener;
-import javax.servlet.http.HttpSessionBindingEvent;
-import javax.servlet.http.HttpSessionEvent;
-import javax.servlet.http.HttpSessionListener;
-
-import java.util.EnumSet;
-import java.util.Set;
-
-public class TestListener implements HttpSessionListener,  HttpSessionAttributeListener, HttpSessionActivationListener, ServletContextListener, ServletContextAttributeListener, ServletRequestListener, ServletRequestAttributeListener
-{
-    public void attributeAdded(HttpSessionBindingEvent se)
-    {
-        // System.err.println("attributedAdded "+se);
-    }
-
-    public void attributeRemoved(HttpSessionBindingEvent se)
-    {
-        // System.err.println("attributeRemoved "+se);
-    }
-
-    public void attributeReplaced(HttpSessionBindingEvent se)
-    {
-        // System.err.println("attributeReplaced "+se);
-    }
-
-    public void sessionWillPassivate(HttpSessionEvent se)
-    {
-        // System.err.println("sessionWillPassivate "+se);
-    }
-
-    public void sessionDidActivate(HttpSessionEvent se)
-    {
-        // System.err.println("sessionDidActivate "+se);
-    }
-
-    public void contextInitialized(ServletContextEvent sce)
-    {	
-        //configure programmatic security
-        ServletRegistration.Dynamic rego = sce.getServletContext().addServlet("RegoTest", RegTest.class.getName());
-        rego.addMapping("/rego/*");
-        HttpConstraintElement constraintElement = new HttpConstraintElement(ServletSecurity.EmptyRoleSemantic.PERMIT, 
-                                                                            ServletSecurity.TransportGuarantee.NONE, new String[]{"admin"});
-        ServletSecurityElement securityElement = new ServletSecurityElement(constraintElement, null);
-        Set<String> unchanged = rego.setServletSecurity(securityElement);
-        //System.err.println("Security constraints registered: "+unchanged.isEmpty());
-
-        //Test that a security constraint from web.xml can't be overridden programmatically
-        ServletRegistration.Dynamic rego2 = sce.getServletContext().addServlet("RegoTest2", RegTest.class.getName());
-        rego2.addMapping("/rego2/*");
-        securityElement = new ServletSecurityElement(constraintElement, null);
-        unchanged = rego2.setServletSecurity(securityElement);
-        //System.err.println("Overridding web.xml constraints not possible:" +!unchanged.isEmpty());
-
-    	/* For servlet 3.0 */
-    	FilterRegistration.Dynamic registration = sce.getServletContext().addFilter("TestFilter",TestFilter.class.getName());
-        if (registration != null) //otherwise it was configured in web.xml
-        {
-    	    registration.setInitParameter("remote", "false");
-    	    registration.setAsyncSupported(true);
-    	    registration.addMappingForUrlPatterns(
-    	        EnumSet.of(DispatcherType.ERROR,DispatcherType.ASYNC,DispatcherType.FORWARD,DispatcherType.INCLUDE,DispatcherType.REQUEST),
-    	        true, 
-    	        new String[]{"/*"});
-        }
-    }
-
-    public void contextDestroyed(ServletContextEvent sce)
-    {
-        // System.err.println("contextDestroyed "+sce);
-    }
-
-    public void attributeAdded(ServletContextAttributeEvent scab)
-    {
-        // System.err.println("attributeAdded "+scab);
-    }
-
-    public void attributeRemoved(ServletContextAttributeEvent scab)
-    {
-        // System.err.println("attributeRemoved "+scab);
-    }
-
-    public void attributeReplaced(ServletContextAttributeEvent scab)
-    {
-        // System.err.println("attributeReplaced "+scab);
-    }
-
-    public void requestDestroyed(ServletRequestEvent sre)
-    {
-        ((HttpServletRequest)sre.getServletRequest()).getSession(false);
-        sre.getServletRequest().setAttribute("requestInitialized",null);
-        // System.err.println("requestDestroyed "+sre);
-    }
-
-    public void requestInitialized(ServletRequestEvent sre)
-    {
-        sre.getServletRequest().setAttribute("requestInitialized","'"+sre.getServletContext().getContextPath()+"'");
-        // System.err.println("requestInitialized "+sre);
-    }
-
-    public void attributeAdded(ServletRequestAttributeEvent srae)
-    {
-        // System.err.println("attributeAdded "+srae);
-    }
-
-    public void attributeRemoved(ServletRequestAttributeEvent srae)
-    {
-        // System.err.println("attributeRemoved "+srae);
-    }
-
-    public void attributeReplaced(ServletRequestAttributeEvent srae)
-    {
-        // System.err.println("attributeReplaced "+srae);
-    }
-
-    public void sessionCreated(HttpSessionEvent se)
-    {
-        // System.err.println("sessionCreated "+se);
-    }
-
-    public void sessionDestroyed(HttpSessionEvent se)
-    {
-        // System.err.println("sessionDestroyed "+se);
-    }
-
-    public void requestCompleted(ServletRequestEvent rre)
-    {
-        // TODO Auto-generated method stub
-        
-    }
-
-    public void requestResumed(ServletRequestEvent rre)
-    {
-        // TODO Auto-generated method stub
-        
-    }
-
-    public void requestSuspended(ServletRequestEvent rre)
-    {
-        // TODO Auto-generated method stub
-        
-    }
-
-}
diff --git a/test-jetty-webapp/src/main/java/com/acme/WebSocketChatServlet.java b/test-jetty-webapp/src/main/java/com/acme/WebSocketChatServlet.java
deleted file mode 100644
index 9226126..0000000
--- a/test-jetty-webapp/src/main/java/com/acme/WebSocketChatServlet.java
+++ /dev/null
@@ -1,99 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package com.acme;
-
-import java.io.IOException;
-import java.util.Set;
-import java.util.concurrent.CopyOnWriteArraySet;
-
-import javax.servlet.RequestDispatcher;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jetty.util.TypeUtil;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.websocket.WebSocket;
-import org.eclipse.jetty.websocket.WebSocketServlet;
-
-public class WebSocketChatServlet extends WebSocketServlet
-{
-    private static final Logger LOG = Log.getLogger(WebSocketChatServlet.class);
-
-    private final Set<ChatWebSocket> _members = new CopyOnWriteArraySet<ChatWebSocket>();
-    
-    @Override
-    protected void doGet(HttpServletRequest request, HttpServletResponse response) 
-        throws javax.servlet.ServletException ,IOException 
-    {
-        getServletContext().getNamedDispatcher("default").forward(request,response);
-    };
-    
-    public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol)
-    {
-        return new ChatWebSocket();
-    }
-    
-    /* ------------------------------------------------------------ */
-    /* ------------------------------------------------------------ */
-    class ChatWebSocket implements WebSocket.OnTextMessage
-    {
-        Connection _connection;
-
-        public void onOpen(Connection connection)
-        {
-            // LOG.info(this+" onConnect");
-            _connection=connection;
-            _members.add(this);
-        }
-        
-        public void onMessage(byte frame, byte[] data,int offset, int length)
-        {
-            // LOG.info(this+" onMessage: "+TypeUtil.toHexString(data,offset,length));
-        }
-
-        public void onMessage(String data)
-        {
-            if (data.indexOf("disconnect")>=0)
-                _connection.disconnect();
-            else
-            {
-                // LOG.info(this+" onMessage: "+data);
-                for (ChatWebSocket member : _members)
-                {
-                    try
-                    {
-                        member._connection.sendMessage(data);
-                    }
-                    catch(IOException e)
-                    {
-                        LOG.warn(e);
-                    }
-                }
-            }
-        }
-        
-        public void onClose(int code, String message)
-        {
-            // LOG.info(this+" onDisconnect");
-            _members.remove(this);
-        }
-
-    }
-}
diff --git a/test-jetty-webapp/src/main/webapp/META-INF/MANIFEST.MF b/test-jetty-webapp/src/main/webapp/META-INF/MANIFEST.MF
deleted file mode 100644
index cfe644d..0000000
--- a/test-jetty-webapp/src/main/webapp/META-INF/MANIFEST.MF
+++ /dev/null
@@ -1,23 +0,0 @@
-Manifest-Version: 1.0
-Bundle-ManifestVersion: 2
-Bundle-Name: TestIt
-Bundle-SymbolicName: TestIt
-Bundle-Version: 1.0.0.qualifier
-Bundle-Activator: testit.Activator
-Import-Package: javax.servlet;version="2.6",
- javax.servlet.http;version="2.6",
- javax.servlet.jsp,
- javax.servlet.jsp.tagext
-Require-Bundle: org.eclipse.jetty.client,
- org.eclipse.jetty.continuation,
- org.eclipse.jetty.http,
- org.eclipse.jetty.io,
- org.eclipse.jetty.servlets,
- org.eclipse.jetty.util,
- org.eclipse.jetty.websocket
-Bundle-ClassPath: WEB-INF/classes
-Bundle-RequiredExecutionEnvironment: J2SE-1.5
-Bundle-ActivationPolicy: lazy
-Web-ContextPath: /
-Class-Path: 
-
diff --git a/test-jetty-webapp/src/main/webapp/WEB-INF/acme-taglib.tld b/test-jetty-webapp/src/main/webapp/WEB-INF/acme-taglib.tld
deleted file mode 100644
index 03adece..0000000
--- a/test-jetty-webapp/src/main/webapp/WEB-INF/acme-taglib.tld
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="ISO-8859-1" ?>
-<!DOCTYPE taglib
-        PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN"
-	"http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd">
-
-<taglib>
-
-  <tlib-version>1.0</tlib-version>
-  <jsp-version>1.2</jsp-version>
-  <short-name>acme</short-name>
-  <uri>http://www.acme.com/taglib</uri>
-  <description>taglib example</description>
-  <listener>
-    <listener-class>com.acme.TagListener</listener-class>
-  </listener>
-
-  <tag>
-    <name>date</name>
-    <tag-class>com.acme.DateTag</tag-class>
-    <body-content>TAGDEPENDENT</body-content>
-    <description>Display Date</description>
-    <attribute>
-       <name>tz</name>
-       <required>false</required>
-    </attribute>
-  </tag>       
-
-</taglib>
diff --git a/test-jetty-webapp/src/main/webapp/WEB-INF/jetty-web.xml b/test-jetty-webapp/src/main/webapp/WEB-INF/jetty-web.xml
deleted file mode 100644
index 89dd6a7..0000000
--- a/test-jetty-webapp/src/main/webapp/WEB-INF/jetty-web.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-<?xml version="1.0"  encoding="ISO-8859-1"?>
-<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
-
-<!--
-This is the jetty specific web application configuration file.  When starting
-a Web Application, the WEB-INF/web-jetty.xml file is looked for and if found, treated
-as a org.eclipse.jetty.server.server.xml.XmlConfiguration file and is applied to the
-org.eclipse.jetty.servlet.WebApplicationContext objet
--->
-
-<Configure class="org.eclipse.jetty.webapp.WebAppContext">
-  <Call class="org.eclipse.jetty.util.log.Log" name="debug"><Arg>executing jetty-web.xml</Arg></Call>
-  <!-- <Set name="contextPath">/mycontext</Set> -->
-</Configure>
diff --git a/test-jetty-webapp/src/main/webapp/WEB-INF/web.xml b/test-jetty-webapp/src/main/webapp/WEB-INF/web.xml
deleted file mode 100644
index 4eaf5e1..0000000
--- a/test-jetty-webapp/src/main/webapp/WEB-INF/web.xml
+++ /dev/null
@@ -1,387 +0,0 @@
-<?xml version="1.0" encoding="ISO-8859-1"?>
-<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_3_0.xsd"
-   metadata-complete="false"
-   version="3.0"> 
-
-  <display-name>Test WebApp</display-name>
-  
-  <context-param>
-    <param-name>org.eclipse.jetty.server.context.ManagedAttributes</param-name>
-    <param-value>QoSFilter,TransparentProxy.ThreadPool,TransparentProxy.HttpClient</param-value>
-  </context-param>
-  
-  <!-- Declare TestListener, which declares TestFilter -->
-  <listener>
-    <listener-class>com.acme.TestListener</listener-class>
-  </listener>
-
-
-  <filter>
-    <filter-name>QoSFilter</filter-name>
-    <filter-class>org.eclipse.jetty.servlets.QoSFilter</filter-class>
-    <async-support>true</async-support>
-    <init-param>
-      <param-name>maxRequests</param-name>
-      <param-value>10000</param-value>
-    </init-param>
-    <init-param>
-      <param-name>managedAttr</param-name>
-      <param-value>true</param-value>
-    </init-param>
-  </filter>  
-  <filter-mapping>
-    <filter-name>QoSFilter</filter-name>
-    <url-pattern>/*</url-pattern>
-  </filter-mapping>
-
-
-  <filter>
-    <filter-name>MultiPart</filter-name>
-    <filter-class>org.eclipse.jetty.servlets.MultiPartFilter</filter-class>
-    <async-support>true</async-support>
-    <init-param>
-      <param-name>deleteFiles</param-name>
-      <param-value>true</param-value>
-    </init-param>
-  </filter>
-  <filter-mapping>
-    <filter-name>MultiPart</filter-name>
-    <url-pattern>/dump/*</url-pattern>
-  </filter-mapping>
-  
-
-  <filter>
-    <filter-name>GzipFilter</filter-name>
-    <filter-class>org.eclipse.jetty.servlets.IncludableGzipFilter</filter-class>
-    <async-support>true</async-support>
-    <init-param>
-      <param-name>bufferSize</param-name>
-      <param-value>8192</param-value>
-    </init-param>
-    <init-param>
-      <param-name>mimeTypes</param-name>
-      <param-value>text/plain,application/xml</param-value>
-    </init-param>
-    <init-param>
-      <param-name>minGzipSize</param-name>
-      <param-value>2048</param-value>
-    </init-param>
-    <init-param>
-      <param-name>userAgent</param-name>
-      <param-value>(?:Mozilla[^\(]*\(compatible;\s*+([^;]*);.*)|(?:.*?([^\s]+/[^\s]+).*)</param-value>
-    </init-param>
-    <init-param>
-      <param-name>cacheSize</param-name>
-      <param-value>1024</param-value>
-    </init-param>
-    <init-param>
-      <param-name>excludedAgents</param-name>
-      <param-value>MSIE 6.0</param-value>
-    </init-param>
-    <init-param>
-      <param-name>uncheckedPrintWriter</param-name>
-      <param-value>true</param-value>
-    </init-param>
-  </filter>
-  <filter-mapping>
-    <filter-name>GzipFilter</filter-name>
-    <url-pattern>/dump/gzip/*</url-pattern>
-    <url-pattern>*.txt</url-pattern>
-  </filter-mapping>
-  
-  
-
-  <!-- Comment out to support PUT and DELETE
-  <filter>
-    <filter-name>RestFilter</filter-name>
-    <filter-class>org.eclipse.jetty.servlets.RestFilter</filter-class>
-    <async-support>true</async-support>
-    <init-param>
-      <param-name>maxPutSize</param-name><param-value>1024</param-value>
-    </init-param>
-  </filter>
-  
-  <filter-mapping>
-    <filter-name>RestFilter</filter-name>
-    <servlet-name>default</servlet-name>
-    <dispatcher>REQUEST</dispatcher>
-  </filter-mapping>
-  -->
-  <servlet>
-    <servlet-name>Login</servlet-name>
-    <servlet-class>com.acme.LoginServlet</servlet-class>
-    <load-on-startup>1</load-on-startup>
-  </servlet>
-
-  <servlet-mapping>
-    <servlet-name>Login</servlet-name>
-    <url-pattern>/login/*</url-pattern>
-  </servlet-mapping>
-
-
-  <servlet>
-    <servlet-name>Hello</servlet-name>
-    <servlet-class>com.acme.HelloWorld</servlet-class>
-    <load-on-startup>1</load-on-startup>
-  </servlet>
-
-  <servlet-mapping>
-    <servlet-name>Hello</servlet-name>
-    <url-pattern>/hello/*</url-pattern>
-  </servlet-mapping>
-  
-  <servlet>
-    <servlet-name>Dump</servlet-name>
-    <servlet-class>com.acme.Dump</servlet-class>
-    <async-support>true</async-support>
-    <load-on-startup>1</load-on-startup>
-    <run-as><role-name>admin</role-name></run-as>
-  </servlet>
-
-  <servlet-mapping>
-    <servlet-name>Dump</servlet-name>
-    <url-pattern>/dump/*</url-pattern>
-    <url-pattern>*.dump</url-pattern>
-  </servlet-mapping>
-
-  <servlet>
-    <servlet-name>Session</servlet-name>
-    <servlet-class>com.acme.SessionDump</servlet-class>
-    <load-on-startup>1</load-on-startup>
-  </servlet>
-  
-  <servlet-mapping>
-    <servlet-name>Session</servlet-name>
-    <url-pattern>/session/*</url-pattern>
-  </servlet-mapping>
-  
-  <servlet>
-    <servlet-name>Cookie</servlet-name>
-    <servlet-class>com.acme.CookieDump</servlet-class>
-    <load-on-startup>1</load-on-startup>
-  </servlet>
-  
-  <servlet-mapping>
-    <servlet-name>Cookie</servlet-name>
-    <url-pattern>/cookie/*</url-pattern>
-  </servlet-mapping>
-  
-  <servlet>
-    <servlet-name>Dispatch</servlet-name>
-    <servlet-class>com.acme.DispatchServlet</servlet-class>
-    <async-support>true</async-support>
-    <load-on-startup>1</load-on-startup>
-  </servlet>
-  
-  <servlet-mapping>
-    <servlet-name>Dispatch</servlet-name>
-    <url-pattern>/dispatch/*</url-pattern>
-  </servlet-mapping>
-  
-  <servlet>
-    <servlet-name>CGI</servlet-name>
-    <servlet-class>org.eclipse.jetty.servlets.CGI</servlet-class>
-    <load-on-startup>1</load-on-startup>
-  </servlet>
-  
-  <servlet-mapping>
-    <servlet-name>CGI</servlet-name>
-    <url-pattern>/cgi-bin/*</url-pattern>
-  </servlet-mapping>
-  
-  <servlet>
-    <servlet-name>Chat</servlet-name>
-    <servlet-class>com.acme.ChatServlet</servlet-class>
-    <async-support>true</async-support>
-    <load-on-startup>1</load-on-startup>
-  </servlet>
-
-  <servlet-mapping>
-    <servlet-name>Chat</servlet-name>
-    <url-pattern>/chat/*</url-pattern>
-  </servlet-mapping>
-  
-  <servlet>
-    <servlet-name>WSChat</servlet-name>
-    <servlet-class>com.acme.WebSocketChatServlet</servlet-class>
-    <load-on-startup>1</load-on-startup>
-  </servlet>
-
-  <servlet-mapping>
-    <servlet-name>WSChat</servlet-name>
-    <url-pattern>/ws/*</url-pattern>
-  </servlet-mapping>
-  
-  
-  <servlet>
-    <servlet-name>Rewrite</servlet-name>
-    <servlet-class>com.acme.RewriteServlet</servlet-class>
-  </servlet>
-  
-  <servlet-mapping>
-    <servlet-name>Rewrite</servlet-name>
-    <url-pattern>/rewritten/*</url-pattern>
-    <url-pattern>/redirected/*</url-pattern>
-  </servlet-mapping>
-  
-  
-  <servlet>
-    <servlet-name>SecureMode</servlet-name>
-    <servlet-class>com.acme.SecureModeServlet</servlet-class>
-    <load-on-startup>1</load-on-startup>
-  </servlet>
-
-  <servlet-mapping>
-    <servlet-name>SecureMode</servlet-name>
-    <url-pattern>/secureMode/*</url-pattern>
-  </servlet-mapping>
-  
-  
-  <servlet>
-    <servlet-name>TransparentProxy</servlet-name>
-    <servlet-class>org.eclipse.jetty.servlets.ProxyServlet$Transparent</servlet-class>
-    <async-support>true</async-support>
-    <init-param>
-      <param-name>Prefix</param-name><param-value>/javadoc-proxy</param-value>
-    </init-param>
-    <init-param>
-      <param-name>ProxyTo</param-name><param-value>http://download.eclipse.org/jetty/stable-8/apidocs</param-value>
-    </init-param>
-    <init-param>
-      <param-name>HostHeader</param-name><param-value>download.eclipse.org</param-value>
-    </init-param>
-    <load-on-startup>1</load-on-startup>
-  </servlet>
-  <servlet-mapping>
-    <servlet-name>TransparentProxy</servlet-name>
-    <url-pattern>/javadoc-proxy/*</url-pattern>
-  </servlet-mapping>
-  
-
-
-  <servlet>
-     <servlet-name>foo.jsp</servlet-name>
-     <jsp-file>/jsp/foo/foo.jsp</jsp-file>
-  </servlet>
-  <servlet-mapping>
-    <servlet-name>foo.jsp</servlet-name>
-    <url-pattern>/jsp/foo/</url-pattern>
-  </servlet-mapping>
-
-  <error-page>
-    <error-code>404</error-code>
-    <location>/error404.html</location>
-  </error-page>
-
-
-
-  <security-constraint>
-    <web-resource-collection>
-      <web-resource-name>Rego2</web-resource-name>
-      <url-pattern>/rego2/*</url-pattern>
-    </web-resource-collection>
-    <auth-constraint>
-      <role-name>server-administrator</role-name>
-    </auth-constraint>
-  </security-constraint>
-
-  <security-constraint>
-    <web-resource-collection>
-      <web-resource-name>Auth2</web-resource-name>
-      <url-pattern>/auth2/*</url-pattern>
-    </web-resource-collection>
-    <auth-constraint>
-      <role-name>*</role-name>
-    </auth-constraint>
-  </security-constraint>
-
-  <security-constraint>
-    <web-resource-collection>
-      <web-resource-name>Any User</web-resource-name>
-      <url-pattern>/dump/auth/*</url-pattern>
-      <url-pattern>*.htm</url-pattern>
-    </web-resource-collection>
-    <auth-constraint>
-      <role-name>*</role-name>
-    </auth-constraint>
-  </security-constraint>
-
-  <security-constraint>
-    <web-resource-collection>
-      <web-resource-name>relax</web-resource-name>
-      <url-pattern>/dump/auth/relax/*</url-pattern>
-      <url-pattern>/auth/relax.txt</url-pattern>
-      <http-method>GET</http-method>
-      <http-method>HEAD</http-method>
-    </web-resource-collection>
-  </security-constraint>
-
-  <security-constraint>
-    <web-resource-collection>
-      <web-resource-name>Admin Role</web-resource-name>
-      <url-pattern>/dump/auth/admin/*</url-pattern>
-    </web-resource-collection>
-    <auth-constraint>
-      <role-name>admin</role-name>
-    </auth-constraint>
-  </security-constraint>
-
-  <security-constraint>
-    <web-resource-collection>
-      <web-resource-name>Forbidden</web-resource-name>
-      <url-pattern>/dump/auth/noaccess/*</url-pattern>
-      <url-pattern>/auth/*</url-pattern>
-    </web-resource-collection>
-    <auth-constraint/>
-  </security-constraint>
-
-  <security-constraint>
-    <web-resource-collection>
-      <web-resource-name>SSL</web-resource-name>
-      <url-pattern>/dump/auth/ssl/*</url-pattern>
-    </web-resource-collection>
-    <user-data-constraint>
-      <transport-guarantee>CONFIDENTIAL</transport-guarantee>
-    </user-data-constraint>
-  </security-constraint>
-
-<!--
-  <login-config>
-    <auth-method>BASIC</auth-method>
-    <realm-name>Test Realm</realm-name>
-  </login-config>
--->
-
-<!--
-  <login-config>
-    <auth-method>DIGEST</auth-method>
-    <realm-name>Test Realm</realm-name>
-  </login-config>
--->
-
-  <login-config>
-    <auth-method>FORM</auth-method>
-    <realm-name>Test Realm</realm-name>
-    <form-login-config>
-       <form-login-page>/logon.html?param=test</form-login-page>
-       <form-error-page>/logonError.html?param=test</form-error-page>
-    </form-login-config>
-  </login-config>
-  
-  <session-config>
-    <session-timeout>5</session-timeout>
-  </session-config>
-
-  <security-role>
-    <role-name>admin</role-name>
-  </security-role>
-  <security-role>
-    <role-name>user</role-name>
-  </security-role>
-
-</web-app>
-
-
diff --git a/test-jetty-webapp/src/main/webapp/auth.html b/test-jetty-webapp/src/main/webapp/auth.html
deleted file mode 100644
index 4b67966..0000000
--- a/test-jetty-webapp/src/main/webapp/auth.html
+++ /dev/null
@@ -1,36 +0,0 @@
-<HTML>
-  <HEAD>
-    <TITLE>Powered By Jetty - Auth Links</TITLE>
-    <META http-equiv="Pragma" content="no-cache">
-    <META http-equiv="Cache-Control" content="no-cache,no-store">
-  </HEAD>
-<BODY>
-<A HREF="http://jetty.eclipse.org"><IMG SRC="jetty_banner.gif"></A>
-<h1>Jetty Authentication Links</h1>
-<p>
-This page contains several links to test the authentication constraints:
-<ul>
-<li><a href="auth/file.txt">auth/file.txt</a> - Forbidden</li>
-<li><a href="auth/relax.txt">auth/relax.txt</a> - Allowed</li>
-<li><a href="auth2">auth2/index.html</a> - Authenticated (tests FormAuthenticator.setAlwaysSaveUri()) </li>
-<li><a href="dump/auth/noaccess/info">dump/auth/noaccess/*</a> - Forbidden</li>
-<li><a href="dump/auth/relax/info">dump/auth/relax/*</a> - Allowed</li>
-<li><a href="dump/auth/info">dump/auth/*</a> - Authenticated any user</li>
-<li><a href="dump/auth/admin/info">dump/auth/admin/*</a> - Authenticated admin role (<a href="session/?Action=Invalidate">click</a> to invalidate session)</li>
-<li><a href="dump/auth/ssl/info">dump/auth/ssl/*</a> - Confidential</li>
-<li><a href="rego/info">rego/info/*</a> - Authenticated admin role from programmatic security (<a href="session/?Action=Invalidate">click</a> to invalidate session)</li>
-<li><a href="rego2/info">rego2/info/*</a> - Authenticated servlet-administrator role from programmatic security (login as admin/admin, <a href="session/?Action=Invalidate">click</a> to invalidate session)</li>
-<li><a href="login?action=login">login</a> - Programmatically login as the user jetty/jetty</li>
-<li><a href="login?action=x">check login status</a> - Check the request's login status</li>
-<li><a href="login?action=logout">logout</a> - Programmatically logout the logged in user</li>
-<li><a href="login?action=wrong">incorrect login</a> - Programmatically login with incorrect credentials</li>
-</ul>
-<p/>
-<p>
-Usernames/Passwords are jetty/jetty admin/admin & user/password	
-</p>
-<p>
-Return to <a href=".">main menu</a>.
-</p>
-</BODY>
-</HTML>
diff --git a/test-jetty-webapp/src/main/webapp/chat/index.html b/test-jetty-webapp/src/main/webapp/chat/index.html
deleted file mode 100644
index adc33ef..0000000
--- a/test-jetty-webapp/src/main/webapp/chat/index.html
+++ /dev/null
@@ -1,85 +0,0 @@
-<html><head>
-    <title>Async Chat</title>
-    <script type='text/javascript'>
-      function $() { return document.getElementById(arguments[0]); }
-      function $F() { return document.getElementById(arguments[0]).value; }
-      function getKeyCode(ev) { if (window.event) return window.event.keyCode; return ev.keyCode; } 
-      function xhr(method,uri,body,handler) {
-        var req=(window.XMLHttpRequest)?new XMLHttpRequest():new ActiveXObject('Microsoft.XMLHTTP');
-        req.onreadystatechange=function() { if (req.readyState==4 && handler) { eval('var o='+req.responseText);handler(o);} }
-        req.open(method,uri,true);
-        req.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
-        req.send(body);
-      };
-      function send(action,user,message,handler){
-        if (message) message=message.replace('%','%25').replace('&','%26').replace('=','%3D');
-        if (user) user=user.replace('%','%25').replace('&','%26').replace('=','%3D');
-        xhr('POST','chat','action='+action+'&user='+user+'&message='+message,handler);
-      };
-      
-      var room = {
-        join: function(name) {
-          this._username=name;
-          $('join').className='hidden';
-          $('joined').className='';
-          $('phrase').focus();
-          send('join', room._username,null);
-          send('chat', room._username,'has joined!');
-          send('poll', room._username,null, room._poll);
-        },
-        chat: function(text) {
-          if (text != null && text.length>0 )
-              send('chat',room._username,text);
-        },
-        _poll: function(m) {
-          //console.debug(m);
-          if (m.chat){
-            var chat=document.getElementById('chat');
-            var spanFrom = document.createElement('span');
-            spanFrom.className='from';
-            spanFrom.innerHTML=m.from+': ';
-            var spanText = document.createElement('span');
-            spanText.className='text';
-            spanText.innerHTML=m.chat;
-            var lineBreak = document.createElement('br');
-            chat.appendChild(spanFrom);
-            chat.appendChild(spanText);
-            chat.appendChild(lineBreak);
-            chat.scrollTop = chat.scrollHeight - chat.clientHeight;   
-          }
-          if (m.action=='poll')
-            send('poll', room._username,null, room._poll);
-        },
-        _end:''
-      };
-    </script>
-    <style type='text/css'>
-    div { border: 0px solid black; }
-    div#chat { clear: both; width: 40em; height: 20ex; overflow: auto; background-color: #f0f0f0; padding: 4px; border: 1px solid black; }
-    div#input { clear: both; width: 40em; padding: 4px; background-color: #e0e0e0; border: 1px solid black; border-top: 0px }
-    input#phrase { width:30em; background-color: #e0f0f0; }
-    input#username { width:14em; background-color: #e0f0f0; }
-    div.hidden { display: none; }
-    span.from { font-weight: bold; }
-    span.alert { font-style: italic; }
-    </style>
-</head><body>
-<div id='chat'></div>
-<div id='input'>
-  <div id='join' >
-    Username: <input id='username' type='text'/><input id='joinB' class='button' type='submit' name='join' value='Join'/>
-  </div>
-  <div id='joined' class='hidden'>
-    Chat: <input id='phrase' type='text'/>
-    <input id='sendB' class='button' type='submit' name='join' value='Send'/>
-  </div>
-</div>
-<script type='text/javascript'>
-$('username').setAttribute('autocomplete','OFF');
-$('username').onkeyup = function(ev) { var keyc=getKeyCode(ev); if (keyc==13 || keyc==10) { room.join($F('username')); return false; } return true; } ;        
-$('joinB').onclick = function(event) { room.join($F('username')); return false; };
-$('phrase').setAttribute('autocomplete','OFF');
-$('phrase').onkeyup = function(ev) {   var keyc=getKeyCode(ev); if (keyc==13 || keyc==10) { room.chat($F('phrase')); $('phrase').value=''; return false; } return true; };
-$('sendB').onclick = function(event) { room.chat($F('phrase')); $('phrase').value=''; return false; };
-</script>
-</body></html>
diff --git a/test-jetty-webapp/src/main/webapp/index.html b/test-jetty-webapp/src/main/webapp/index.html
deleted file mode 100644
index 630bc1d..0000000
--- a/test-jetty-webapp/src/main/webapp/index.html
+++ /dev/null
@@ -1,62 +0,0 @@
-<HTML>
-  <HEAD>
-    <TITLE>Powered By Jetty</TITLE>
-    <META http-equiv="Pragma" content="no-cache">
-    <META http-equiv="Cache-Control" content="no-cache,no-store">
-    <style  type="text/css">
-      body {
-        font-family: sans-serif;
-      }
-    </style>
-  </HEAD>
-<BODY>
-<A HREF="http://jetty.eclipse.org"><IMG SRC="jetty_banner.gif"></A>
-<h1>Welcome to Jetty 8</h1>
-<p>
-This is the Test webapp for the Jetty 8 HTTP Server and Servlet Container.  
-For more information about Jetty, please visit our
-<a href="http://www.eclipse.org/jetty">website</a>
-or <a href="http://wiki.eclipse.org/Jetty">wiki</a> or see the bundled <a href="javadoc/">javadoc</a>.<br/>
-Commercial support for Jetty is available via <a href="http://www.webtide.com">Webtide</a> and <a href="http://www.intalio.com">Intalio</a>.
-</p>
-<p>
-This is a test context that serves:
-</p>
-<ul>
-<li>static content:
-<a href="d.txt">tiny</a>,
-<a href="da.txt">small</a>,
-<a href="dat.txt">medium</a>,
-<a href="data.txt">large</a>,
-<a href="data.txt.gz">large gziped</a></li>
-<li><a href="hello/">Hello World Servlet</a></li>
-<li>Dump: <a href="dump/info">Request</a>, <a href="session/">Session</a>, <a href="cookie/">Cookie</a></li>
-<li><a href="dispatch">Dispatcher Servlet</a></li>
-<li><a href="rewrite/">Request Rewrite Servlet</a></li>
-<li><a href="jsp/">JSP examples</a></li>
-<li><a href="javadoc-proxy/">Transparent Proxy</a> (to javadoc @ eclipse.org)</li>
-<li>Comet Chat: <a href="chat/">Long Polling</a>, <a href="ws">WebSocket</a></li>
-<li><a href="auth.html">Authentication</a></li>
-</ul>
-<p/>
-
-<!--
-<p>
-Other demonstration contexts, some of which may need manual deployment
-(check the README.txt file for details): 
-</p>
-<ul>
-<li>a <a href="chat/demo">AJAX Comet Chat with com.acme.ChatServlet demo</a></li>
-<li>a <a href="/cometd/">AJAX Cometd Chat with cometd Bayeux</a></li>
-<li> the <a href="/javadoc/">javadoc</a> </li>
-<li> a demo of the <a href="/test-jndi">JNDI features</a></li>
-<li> a demo of the <a href="/test-annotations">Annotation features</a></li>
-<li> a demo of the <a href="/test-jaas">JAAS features</a></li>
-</ul>
-<p/>
--->
-
-This webapp is deployed in $JETTY_HOME/webapp/test and configured by $JETTY_HOME/contexts/test.xml
-
-</BODY>
-</HTML>
diff --git a/test-jetty-webapp/src/main/webapp/remote.html b/test-jetty-webapp/src/main/webapp/remote.html
deleted file mode 100644
index 3f52269..0000000
--- a/test-jetty-webapp/src/main/webapp/remote.html
+++ /dev/null
@@ -1,32 +0,0 @@
-<HTML>
-  <HEAD>
-    <TITLE>Powered By Jetty</TITLE>
-    <META http-equiv="Pragma" content="no-cache">
-    <META http-equiv="Cache-Control" content="no-cache,no-store">
-  </HEAD>
-<BODY>
-<A HREF="http://www.eclipse.org/jetty"><IMG SRC="jetty_banner.gif"></A>
-<h1>Welcome to Jetty 8 - REMOTE ACCESS!!</h1>
-<p>
-This is the Test webapp for the Jetty 8 HTTP Server and Servlet Container.  
-For more information about Jetty, please visit our
-<a href="http://www.eclipse.org/jetty">website</a>
-or <a href="http://www.eclipse.org/jetty/documentation/">documentation</a>. 
-Commercial support for Jetty is available via <a href="http://www.webtide.com">webtide</a>.
-</p>
-<p>
-This test context serves several demo filters and servlets
-that are not safe for deployment on the internet, since (by design) they contain 
-cross domain scripting vulnerabilities and reveal private information.  This page
-is displayed because you have accessed this context from a non local IP address.
-</p>
-<p>
-You can disable the remote address checking by editing contexts/test.d/override-web.xml, uncommenting the definition of the TestFilter, and changing the
-"remote" init parameter to "true".
-</p>
-<p>
-This webapp is deployed in $JETTY_HOME/webapp/test.war and configured by $JETTY_HOME/contexts/test.xml and $JETTY_HOME/contexts/test.d/override-web.xml
-</p>
-
-</BODY>
-</HTML>
diff --git a/test-jetty-webapp/src/main/webapp/rewrite/index.html b/test-jetty-webapp/src/main/webapp/rewrite/index.html
deleted file mode 100644
index fd9e22f..0000000
--- a/test-jetty-webapp/src/main/webapp/rewrite/index.html
+++ /dev/null
@@ -1,13 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
-<html>
-<head>
-<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
-<meta http-equiv="Cache-Control" content="no-cache">
-<title>RewriteHandler</title>
-</head>
-<body>
-<h1>Rewrite not enabled</h1>
-<p>The rewrite handler is currently not enabled. To enable this demo, start up Jetty with:</p>
-<code>java -jar start.jar OPTIONS=rewrite etc/jetty-rewrite.xml</code>
-</body>
-</html>
diff --git a/test-jetty-webapp/src/test/java/org/eclipse/jetty/DispatchServletTest.java b/test-jetty-webapp/src/test/java/org/eclipse/jetty/DispatchServletTest.java
deleted file mode 100644
index 72a7d1f..0000000
--- a/test-jetty-webapp/src/test/java/org/eclipse/jetty/DispatchServletTest.java
+++ /dev/null
@@ -1,142 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty;
-
-import junit.framework.TestCase;
-
-import org.eclipse.jetty.servlet.DefaultServlet;
-import org.eclipse.jetty.servlet.ServletHolder;
-import org.eclipse.jetty.testing.ServletTester;
-
-import com.acme.DispatchServlet;
-
-/**
- * Simple tests against DispatchServlet.
- */
-public class DispatchServletTest extends TestCase
-{
-    /**
-     * As filed in JETTY-978.
-     * 
-     * Security problems in demo dispatch servlet.
-     * 
-     * <blockquote>
-     * <p>
-     * The dispatcher servlet (com.acme.DispatchServlet) is prone to a Denial of
-     * Service vulnerability.
-     * </p>
-     * <p>
-     * This example servlet is meant to be used as a resources dispatcher,
-     * however a malicious aggressor may abuse this functionality in order to
-     * cause a recursive inclusion. In details, it is possible to abuse the
-     * method com.acme.DispatchServlet.doGet(DispatchServlet.java:203) forcing
-     * the application to recursively include the "Dispatch" servlet.
-     * </p>
-     * <p>
-     * Dispatch com.acme.DispatchServlet 1 Dispatch /dispatch/* As a result, it
-     * is possible to trigger a "java.lang.StackOverflowError" and consequently
-     * an internal server error (500).
-     * </p>
-     * <p>
-     * Multiple requests may easily affect the availability of the servlet
-     * container. Since this attack can cause the server to consume resources in
-     * a non-linear relationship to the size of inputs, it should be considered
-     * as a server flaw.
-     * </p>
-     * <p>
-     * The vulnerability seems confined to the example servlet and it does not
-     * afflict the Jetty's core."
-     * </p>
-     * </blockquote>
-     * 
-     * @throws Exception
-     */
-    public void testSelfRefForwardDenialOfService() throws Exception
-    {
-        ServletTester tester = new ServletTester();
-        tester.setContextPath("/tests");
-      
-        ServletHolder dispatch = tester.addServlet(DispatchServlet.class,"/dispatch/*");
-        tester.addServlet(DefaultServlet.class,"/");
-        tester.start();
-        
-        StringBuffer req1 = new StringBuffer();
-        req1.append("GET /tests/dispatch/includeN/"+dispatch.getName()+" HTTP/1.1\n");
-        req1.append("Host: tester\n");
-        req1.append("Connection: close\n");
-        req1.append("\n");
-
-        String response = tester.getResponses(req1.toString());
-        
-        String msg = "Response code on SelfRefDoS";
-
-        assertFalse(msg + " should not be code 500.",response.startsWith("HTTP/1.1 500 "));
-        assertTrue(msg + " should return error code 403 (Forbidden)", response.startsWith("HTTP/1.1 403 "));
-    }
-    
-    public void testSelfRefDeep() throws Exception
-    {
-        ServletTester tester = new ServletTester();
-        tester.setContextPath("/tests");
-        tester.addServlet(DispatchServlet.class,"/dispatch/*");
-        tester.addServlet(DefaultServlet.class,"/");
-        tester.start();
-        
-        String selfRefs[] =
-        { "/dispatch/forward", "/dispatch/includeS", "/dispatch/includeW", "/dispatch/includeN", };
-
-        /*
-         * Number of nested dispatch requests. 220 is a good value, as it won't
-         * trigger an Error 413 response (Entity too large). Anything larger
-         * than 220 will trigger a 413 response.
-         */
-        int nestedDepth = 220;
-
-        for (int sri = 0; sri < selfRefs.length; sri++)
-        {
-            String selfRef = selfRefs[sri];
-
-            StringBuffer req1 = new StringBuffer();
-            req1.append("GET /tests");
-            for (int i = 0; i < nestedDepth; i++)
-            {
-                req1.append(selfRef);
-            }
-
-            req1.append("/ HTTP/1.1\n");
-            req1.append("Host: tester\n");
-            req1.append("Connection: close\n");
-            req1.append("\n");
-
-            String response = tester.getResponses(req1.toString());
-
-            StringBuffer msg = new StringBuffer();
-            msg.append("Response code on nested \"").append(selfRef).append("\"");
-            msg.append(" (depth:").append(nestedDepth).append(")");
-
-            assertFalse(msg + " should not be code 413 (Request Entity Too Large)," + 
-                    "the nestedDepth in the TestCase is too large (reduce it)",
-                    response.startsWith("HTTP/1.1 413 "));
-
-            assertFalse(msg + " should not be code 500.",response.startsWith("HTTP/1.1 500 "));
-            
-            assertTrue(msg + " should return error code 403 (Forbidden)", response.startsWith("HTTP/1.1 403 "));
-        }
-    }
-}
diff --git a/test-jetty-webapp/src/test/java/org/eclipse/jetty/TestServer.java b/test-jetty-webapp/src/test/java/org/eclipse/jetty/TestServer.java
deleted file mode 100644
index c0df2fc..0000000
--- a/test-jetty-webapp/src/test/java/org/eclipse/jetty/TestServer.java
+++ /dev/null
@@ -1,209 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty;
-
-import java.io.File;
-import java.io.IOException;
-import java.lang.management.ManagementFactory;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jetty.util.ssl.SslContextFactory;
-import org.eclipse.jetty.jmx.MBeanContainer;
-import org.eclipse.jetty.security.HashLoginService;
-import org.eclipse.jetty.server.Handler;
-import org.eclipse.jetty.server.NCSARequestLog;
-import org.eclipse.jetty.server.Request;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.bio.SocketConnector;
-import org.eclipse.jetty.server.handler.ContextHandler;
-import org.eclipse.jetty.server.handler.ContextHandlerCollection;
-import org.eclipse.jetty.server.handler.DefaultHandler;
-import org.eclipse.jetty.server.handler.HandlerCollection;
-import org.eclipse.jetty.server.handler.HandlerWrapper;
-import org.eclipse.jetty.server.handler.RequestLogHandler;
-import org.eclipse.jetty.server.handler.ResourceHandler;
-import org.eclipse.jetty.server.nio.BlockingChannelConnector;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
-import org.eclipse.jetty.server.session.HashSessionManager;
-import org.eclipse.jetty.server.ssl.SslSelectChannelConnector;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.util.log.StdErrLog;
-import org.eclipse.jetty.util.thread.QueuedThreadPool;
-import org.eclipse.jetty.webapp.WebAppContext;
-import org.junit.Ignore;
-
- at Ignore("Not a test case")
-public class TestServer
-{
-    private static final Logger LOG = Log.getLogger(TestServer.class);
-
-    public static void main(String[] args) throws Exception
-    {
-        ((StdErrLog)Log.getLog()).setSource(false);
-        
-        String jetty_root = "..";
-
-        Server server = new Server();
-        server.setSendDateHeader(true);
-        
-        // Setup JMX
-        MBeanContainer mbContainer=new MBeanContainer(ManagementFactory.getPlatformMBeanServer());
-        server.getContainer().addEventListener(mbContainer);
-        server.addBean(mbContainer);
-        mbContainer.addBean(Log.getLog());
-        
-        // Setup Threadpool
-        QueuedThreadPool threadPool = new QueuedThreadPool();
-        threadPool.setMaxThreads(100);
-        server.setThreadPool(threadPool);
-
-        // Setup Connectors
-        SelectChannelConnector connector0 = new SelectChannelConnector();
-        connector0.setPort(8080);
-        connector0.setMaxIdleTime(30000);
-        connector0.setConfidentialPort(8443);
-        connector0.setUseDirectBuffers(true);
-        server.addConnector(connector0);
-        
-        // Setup Connectors
-        SelectChannelConnector connector1 = new SelectChannelConnector();
-        connector1.setPort(8081);
-        connector1.setMaxIdleTime(30000);
-        connector1.setConfidentialPort(8443);
-        connector1.setUseDirectBuffers(false);
-        server.addConnector(connector1);
-        
-        // Setup Connectors
-        SocketConnector connector2 = new SocketConnector();
-        connector2.setPort(8082);
-        connector2.setMaxIdleTime(30000);
-        connector2.setConfidentialPort(8443);
-        server.addConnector(connector2);
-        
-        // Setup Connectors
-        BlockingChannelConnector connector3 = new BlockingChannelConnector();
-        connector3.setPort(8083);
-        connector3.setMaxIdleTime(30000);
-        connector3.setConfidentialPort(8443);
-        server.addConnector(connector3);
-
-        SslSelectChannelConnector ssl_connector = new SslSelectChannelConnector();
-        ssl_connector.setPort(8443);
-        SslContextFactory cf = ssl_connector.getSslContextFactory();
-        cf.setKeyStore(jetty_root + "/jetty-server/src/main/config/etc/keystore");
-        cf.setKeyStorePassword("OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4");
-        cf.setKeyManagerPassword("OBF:1u2u1wml1z7s1z7a1wnl1u2g");
-        cf.setTrustStore(jetty_root + "/jetty-server/src/main/config/etc/keystore");
-        cf.setTrustStorePassword("OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4");
-        server.addConnector(ssl_connector);
-
-        HandlerCollection handlers = new HandlerCollection();
-        ContextHandlerCollection contexts = new ContextHandlerCollection();
-        RequestLogHandler requestLogHandler = new RequestLogHandler();
-        handlers.setHandlers(new Handler[]
-        { contexts, new DefaultHandler(), requestLogHandler });
-        
-        // Add restart handler to test the ability to save sessions and restart
-        RestartHandler restart = new RestartHandler();
-        restart.setHandler(handlers);
-        
-        server.setHandler(restart);
-
-        
-        // Setup deployers
-
-        HashLoginService login = new HashLoginService();
-        login.setName("Test Realm");
-        login.setConfig(jetty_root + "/test-jetty-webapp/src/main/config/etc/realm.properties");
-        server.addBean(login);
-
-        File log=File.createTempFile("jetty-yyyy_mm_dd", "log");
-        NCSARequestLog requestLog = new NCSARequestLog(log.toString());
-        requestLog.setExtended(false);
-        requestLogHandler.setRequestLog(requestLog);
-
-        server.setStopAtShutdown(true);
-        server.setSendServerVersion(true);
-        
-        WebAppContext webapp = new WebAppContext();
-        webapp.setParentLoaderPriority(true);
-        webapp.setResourceBase("./src/main/webapp");
-        webapp.setAttribute("testAttribute","testValue");
-        File sessiondir=File.createTempFile("sessions",null);
-        if (sessiondir.exists())
-            sessiondir.delete();
-        sessiondir.mkdir();
-        sessiondir.deleteOnExit();
-        ((HashSessionManager)webapp.getSessionHandler().getSessionManager()).setStoreDirectory(sessiondir);
-        ((HashSessionManager)webapp.getSessionHandler().getSessionManager()).setSavePeriod(10);
-        
-        contexts.addHandler(webapp);
-        
-        ContextHandler srcroot = new ContextHandler();
-        srcroot.setResourceBase(".");
-        srcroot.setHandler(new ResourceHandler());
-        srcroot.setContextPath("/src");
-        contexts.addHandler(srcroot);
-        
-        server.start();
-        server.join();
-    }
-    
-    private static class RestartHandler extends HandlerWrapper
-    {
-
-        /* ------------------------------------------------------------ */
-        /**
-         * @see org.eclipse.jetty.server.handler.HandlerWrapper#handle(java.lang.String, org.eclipse.jetty.server.Request, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
-         */
-        @Override
-        public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
-        {
-            super.handle(target,baseRequest,request,response);
-            if (Boolean.valueOf(request.getParameter("restart")))
-            {
-                final Server server=getServer();
-                
-                new Thread()
-                {
-                    public void run()
-                    {
-                        try
-                        {
-                            Thread.sleep(100);
-                            server.stop();
-                            Thread.sleep(100);
-                            server.start();
-                        }
-                        catch(Exception e)
-                        {
-                            LOG.warn(e);
-                        }
-                    }
-                }.start();
-            }
-        }
-        
-    }
-
-}
diff --git a/tests/pom.xml b/tests/pom.xml
index 30ce610..2f1d63f 100644
--- a/tests/pom.xml
+++ b/tests/pom.xml
@@ -21,7 +21,8 @@
   <parent>
     <groupId>org.eclipse.jetty</groupId>
     <artifactId>jetty-project</artifactId>
-    <version>8.1.17.v20150415</version>
+    <version>9.2.13.v20150730</version>
+    <relativePath>../pom.xml</relativePath>
   </parent>
   <groupId>org.eclipse.jetty.tests</groupId>
   <artifactId>tests-parent</artifactId>
@@ -41,9 +42,12 @@
     </plugins>
   </build>
   <modules>
-    <module>test-integration</module>
     <module>test-webapps</module>
     <module>test-sessions</module>
+    <module>test-continuation</module>
     <module>test-loginservice</module>
+    <module>test-integration</module>
+    <module>test-quickstart</module>
+    <module>test-jmx</module>
   </modules>
 </project>
diff --git a/tests/test-cdi/cdi-client/pom.xml b/tests/test-cdi/cdi-client/pom.xml
new file mode 100644
index 0000000..3419c3b
--- /dev/null
+++ b/tests/test-cdi/cdi-client/pom.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+// ========================================================================
+// Copyright (c) Webtide LLC
+// 
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at 
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.apache.org/licenses/LICENSE-2.0.txt
+//
+// You may elect to redistribute this code under either of these licenses. 
+// ========================================================================
+ -->
+<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">
+  <parent>
+    <groupId>org.eclipse.jetty.tests</groupId>
+    <artifactId>test-cdi-parent</artifactId>
+    <version>9.2.4-SNAPSHOT</version>
+  </parent>
+  <modelVersion>4.0.0</modelVersion>
+  <artifactId>cdi-client</artifactId>
+  <packaging>jar</packaging>
+  <name>Jetty Tests :: CDI :: Clients</name>
+  <url>http://www.eclipse.org/jetty</url>
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    <bundle-symbolic-name>${project.groupId}.cdi.clients</bundle-symbolic-name>
+  </properties>
+  <dependencies>
+  </dependencies>
+</project>
diff --git a/tests/test-cdi/cdi-webapp-it/pom.xml b/tests/test-cdi/cdi-webapp-it/pom.xml
new file mode 100644
index 0000000..ac3baf2
--- /dev/null
+++ b/tests/test-cdi/cdi-webapp-it/pom.xml
@@ -0,0 +1,215 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+// ========================================================================
+// Copyright (c) Webtide LLC
+// 
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at 
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.apache.org/licenses/LICENSE-2.0.txt
+//
+// You may elect to redistribute this code under either of these licenses. 
+// ========================================================================
+ -->
+<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">
+  <parent>
+    <groupId>org.eclipse.jetty.tests</groupId>
+    <artifactId>test-cdi-parent</artifactId>
+    <version>9.2.4-SNAPSHOT</version>
+  </parent>
+  <modelVersion>4.0.0</modelVersion>
+  <artifactId>cdi-webapp-it</artifactId>
+  <packaging>jar</packaging>
+  <name>Jetty Tests :: CDI :: WebApp Integration Tests</name>
+  <url>http://www.eclipse.org/jetty</url>
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+    <bundle-symbolic-name>${project.groupId}.cdi.webapp.it</bundle-symbolic-name>
+    <scripts-dir>${project.basedir}/src/test/scripts</scripts-dir>
+    <test-base-dir>${project.build.directory}/test-base</test-base-dir>
+    <test-home-dir>${project.build.directory}/test-home</test-home-dir>
+  </properties>
+  <dependencies>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-distribution</artifactId>
+      <version>${project.version}</version>
+      <type>zip</type>
+      <scope>runtime</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.tests</groupId>
+      <artifactId>cdi-webapp</artifactId>
+      <version>${project.version}</version>
+      <type>war</type>
+      <scope>runtime</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.toolchain</groupId>
+      <artifactId>jetty-test-helper</artifactId>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-dependency-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>copy-apps-for-testing</id>
+            <phase>process-test-resources</phase>
+            <goals>
+              <goal>copy-dependencies</goal>
+            </goals>
+            <configuration>
+              <includeArtifactIds>cdi-webapp</includeArtifactIds>
+              <includeScope>runtime</includeScope>
+              <includeTypes>war</includeTypes>
+              <overwriteSnapshots>true</overwriteSnapshots>
+              <overwriteReleases>true</overwriteReleases>
+              <stripVersion>true</stripVersion>
+              <outputDirectory>${test-base-dir}/webapps</outputDirectory>
+            </configuration>
+          </execution>
+          <execution>
+            <id>unpack-jetty-distro</id>
+            <phase>process-test-resources</phase>
+            <goals>
+              <goal>unpack-dependencies</goal>
+            </goals>
+            <configuration>
+              <includeArtifactIds>jetty-distribution</includeArtifactIds>
+              <includeScope>runtime</includeScope>
+              <includeTypes>zip</includeTypes>
+              <outputAbsoluteArtifactFilename>true</outputAbsoluteArtifactFilename>
+              <outputDirectory>${test-home-dir}</outputDirectory>
+              <overWriteSnapshots>true</overWriteSnapshots>
+              <overWriteIfNewer>true</overWriteIfNewer>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-failsafe-plugin</artifactId>
+        <version>2.17</version>
+        <executions>
+          <execution>
+            <goals>
+              <goal>integration-test</goal>
+              <goal>verify</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-antrun-plugin</artifactId>
+        <version>1.7</version>
+        <executions>
+          <execution>
+            <id>start-jetty</id>
+            <phase>pre-integration-test</phase>
+            <goals>
+              <goal>run</goal>
+            </goals>
+            <configuration>
+              <target>
+                <property name="jetty.home" location="${test-home-dir}/jetty-distribution-${project.version}"/>
+                <property name="jetty.base" location="${test-base-dir}"/>
+                <echo>Integration Test : Setup Jetty</echo>
+                <exec executable="${run.command}"
+                      dir="${scripts-dir}"
+                      spawn="false">
+                  <arg value="${run.command.xtra}"/>
+                  <arg value="${setup.script}"/>
+                  <arg file="${java.home}"/>
+                  <arg file="${jetty.home}"/>
+                  <arg file="${jetty.base}"/>
+                </exec>
+
+                <echo>Integration Test : Starting Jetty ...</echo>
+                <exec executable="${run.command}"
+                      dir="${scripts-dir}"
+                      spawn="true">
+                  <arg value="${run.command.xtra}"/>
+                  <arg value="${start.script}"/>
+                  <arg file="${java.home}"/>
+                  <arg file="${jetty.home}"/>
+                  <arg file="${jetty.base}"/>
+                </exec>
+                <waitfor maxwait="5" maxwaitunit="second"
+                  checkevery="100" checkeveryunit="millisecond">
+                  <http url="http://localhost:58080/cdi-webapp/"/>
+                </waitfor>
+                <echo>Integration Test : Jetty is now available</echo>
+              </target>
+            </configuration>
+          </execution>
+          <execution>
+            <id>stop-jetty</id>
+            <phase>post-integration-test</phase>
+            <goals>
+              <goal>run</goal>
+            </goals>
+            <configuration>
+              <target>
+                <property name="jetty.home" location="${test-home-dir}/jetty-distribution-${project.version}"/>
+                <property name="jetty.base" location="${test-base-dir}"/>
+                <echo>Integration Test : Stop Jetty</echo>
+                <exec executable="${run.command}"
+                      dir="${scripts-dir}"
+                      spawn="false">
+                  <arg value="${run.command.xtra}"/>
+                  <arg value="${stop.script}"/>
+                  <arg file="${java.home}"/>
+                  <arg file="${jetty.home}"/>
+                  <arg file="${jetty.base}"/>
+                </exec>
+              </target>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+  <profiles>
+    <profile>
+      <id>it-windows</id>
+      <activation>
+        <os>
+          <family>Windows</family>
+        </os>
+      </activation>
+      <properties>
+        <run.command>cmd</run.command>
+        <run.command.xtra>/c</run.command.xtra>
+        <start.script>start-jetty.bat</start.script>
+        <stop.script>stop-jetty.bat</stop.script>
+      </properties>
+    </profile>
+    <profile>
+      <id>it-unix</id>
+      <activation>
+        <os>
+          <family>unix</family>
+        </os>
+      </activation>
+      <properties>
+        <run.command>sh</run.command>
+        <run.command.xtra>--</run.command.xtra>
+        <setup.script>setup-jetty.sh</setup.script>
+        <start.script>start-jetty.sh</start.script>
+        <stop.script>stop-jetty.sh</stop.script>
+      </properties>
+    </profile>
+  </profiles>
+</project>
diff --git a/tests/test-cdi/cdi-webapp-it/src/test/java/org/eclipse/jetty/tests/HelloIT.java b/tests/test-cdi/cdi-webapp-it/src/test/java/org/eclipse/jetty/tests/HelloIT.java
new file mode 100644
index 0000000..dc1cd7c
--- /dev/null
+++ b/tests/test-cdi/cdi-webapp-it/src/test/java/org/eclipse/jetty/tests/HelloIT.java
@@ -0,0 +1,41 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.tests;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+import java.net.URI;
+
+import org.eclipse.jetty.toolchain.test.SimpleRequest;
+import org.junit.Test;
+
+/**
+ * Basic tests for a simple @WebServlet with no CDI
+ */
+public class HelloIT
+{
+    @Test
+    public void testBasic() throws Exception
+    {
+        URI serverURI = new URI("http://localhost:58080/cdi-webapp/");
+        SimpleRequest req = new SimpleRequest(serverURI);
+        assertThat(req.getString("hello"),is("Hello World"));
+    }
+}
diff --git a/tests/test-cdi/cdi-webapp-it/src/test/java/org/eclipse/jetty/tests/ServerInfoIT.java b/tests/test-cdi/cdi-webapp-it/src/test/java/org/eclipse/jetty/tests/ServerInfoIT.java
new file mode 100644
index 0000000..939ba80
--- /dev/null
+++ b/tests/test-cdi/cdi-webapp-it/src/test/java/org/eclipse/jetty/tests/ServerInfoIT.java
@@ -0,0 +1,37 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.tests;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+import java.net.URI;
+
+import org.eclipse.jetty.toolchain.test.SimpleRequest;
+import org.junit.Test;
+
+public class ServerInfoIT
+{
+    @Test
+    public void testGET() throws Exception {
+        URI serverURI = new URI("http://localhost:58080/cdi-webapp/");
+        SimpleRequest req = new SimpleRequest(serverURI);
+        assertThat(req.getString("serverinfo"),is("Hello World"));
+    }
+}
diff --git a/tests/test-cdi/cdi-webapp-it/src/test/scripts/setup-jetty.sh b/tests/test-cdi/cdi-webapp-it/src/test/scripts/setup-jetty.sh
new file mode 100755
index 0000000..c1c2ad6
--- /dev/null
+++ b/tests/test-cdi/cdi-webapp-it/src/test/scripts/setup-jetty.sh
@@ -0,0 +1,19 @@
+#!/usr/bin/env bash
+
+JAVA_HOME=$1
+JETTY_HOME=$2
+JETTY_BASE=$3
+
+echo \${java.home}  : $JAVA_HOME
+echo \${jetty.home} : $JETTY_HOME
+echo \${jetty.base} : $JETTY_BASE
+
+cd "$JETTY_BASE"
+
+"$JAVA_HOME/bin/java" -jar "$JETTY_HOME/start.jar" \
+    --approve-all-licenses \
+    --add-to-start=deploy,http,annotations,cdi,logging
+
+"$JAVA_HOME/bin/java" -jar "$JETTY_HOME/start.jar" \
+    --version
+
diff --git a/tests/test-cdi/cdi-webapp-it/src/test/scripts/start-jetty.sh b/tests/test-cdi/cdi-webapp-it/src/test/scripts/start-jetty.sh
new file mode 100755
index 0000000..1d0f9eb
--- /dev/null
+++ b/tests/test-cdi/cdi-webapp-it/src/test/scripts/start-jetty.sh
@@ -0,0 +1,17 @@
+#!/usr/bin/env bash
+
+JAVA_HOME=$1
+JETTY_HOME=$2
+JETTY_BASE=$3
+
+echo \${java.home}  : $JAVA_HOME
+echo \${jetty.home} : $JETTY_HOME
+echo \${jetty.base} : $JETTY_BASE
+
+cd "$JETTY_BASE"
+
+"$JAVA_HOME/bin/java" -jar "$JETTY_HOME/start.jar" \
+    jetty.port=58080 \
+    STOP.PORT=58181 STOP.KEY=it
+
+
diff --git a/tests/test-cdi/cdi-webapp-it/src/test/scripts/stop-jetty.sh b/tests/test-cdi/cdi-webapp-it/src/test/scripts/stop-jetty.sh
new file mode 100755
index 0000000..32e4077
--- /dev/null
+++ b/tests/test-cdi/cdi-webapp-it/src/test/scripts/stop-jetty.sh
@@ -0,0 +1,11 @@
+#!/usr/bin/env bash
+
+JAVA_HOME=$1
+JETTY_HOME=$2
+JETTY_BASE=$3
+
+cd "$JETTY_BASE"
+"$JAVA_HOME/bin/java" -jar "$JETTY_HOME/start.jar" \
+ --stop STOP.PORT=58181 STOP.KEY=it
+
+
diff --git a/tests/test-cdi/cdi-webapp/pom.xml b/tests/test-cdi/cdi-webapp/pom.xml
new file mode 100644
index 0000000..117fe10
--- /dev/null
+++ b/tests/test-cdi/cdi-webapp/pom.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  // ========================================================================
+  // Copyright (c) Webtide LLC
+  //
+  // All rights reserved. This program and the accompanying materials
+  // are made available under the terms of the Eclipse Public License v1.0
+  // and Apache License v2.0 which accompanies this distribution.
+  //
+  // The Eclipse Public License is available at
+  // http://www.eclipse.org/legal/epl-v10.html
+  //
+  // The Apache License v2.0 is available at
+  // http://www.apache.org/licenses/LICENSE-2.0.txt
+  //
+  // You may elect to redistribute this code under either of these licenses.
+  // ========================================================================
+-->
+<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">
+  <parent>
+    <groupId>org.eclipse.jetty.tests</groupId>
+    <artifactId>test-cdi-parent</artifactId>
+    <version>9.2.4-SNAPSHOT</version>
+  </parent>
+  <modelVersion>4.0.0</modelVersion>
+  <artifactId>cdi-webapp</artifactId>
+  <packaging>war</packaging>
+  <name>Jetty Tests :: CDI :: WebApp</name>
+  <url>http://www.eclipse.org/jetty</url>
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    <bundle-symbolic-name>${project.groupId}.cdi.webapp</bundle-symbolic-name>
+  </properties>
+  <dependencies>
+    <dependency>
+      <groupId>javax.servlet</groupId>
+      <artifactId>javax.servlet-api</artifactId>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>javax.enterprise</groupId>
+      <artifactId>cdi-api</artifactId>
+      <version>1.1</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>javax.websocket</groupId>
+      <artifactId>javax.websocket-api</artifactId>
+      <scope>provided</scope>
+    </dependency>
+  </dependencies>
+  <build>
+    <finalName>cdi-webapp</finalName>
+  </build>
+</project>
diff --git a/tests/test-cdi/cdi-webapp/src/main/java/org/eclipse/jetty/tests/HelloServlet.java b/tests/test-cdi/cdi-webapp/src/main/java/org/eclipse/jetty/tests/HelloServlet.java
new file mode 100644
index 0000000..489d7ce
--- /dev/null
+++ b/tests/test-cdi/cdi-webapp/src/main/java/org/eclipse/jetty/tests/HelloServlet.java
@@ -0,0 +1,42 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.tests;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.annotation.WebServlet;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * The most basic servlet here, no CDI use.
+ */
+ at SuppressWarnings("serial")
+ at WebServlet("/hello")
+public class HelloServlet extends HttpServlet
+{
+    @Override
+    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+    {
+        resp.setContentType("text/plain");
+        resp.getWriter().println("Hello World");
+    }
+}
diff --git a/tests/test-cdi/cdi-webapp/src/main/java/org/eclipse/jetty/tests/ServerInfoServlet.java b/tests/test-cdi/cdi-webapp/src/main/java/org/eclipse/jetty/tests/ServerInfoServlet.java
new file mode 100644
index 0000000..fe4dd1c
--- /dev/null
+++ b/tests/test-cdi/cdi-webapp/src/main/java/org/eclipse/jetty/tests/ServerInfoServlet.java
@@ -0,0 +1,54 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.tests;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+
+import javax.inject.Inject;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.annotation.WebServlet;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+ at SuppressWarnings("serial")
+ at WebServlet("/serverinfo")
+public class ServerInfoServlet extends HttpServlet
+{
+    @Inject
+    private ServletContext context;
+
+    @Override
+    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+    {
+        resp.setContentType("text/plain");
+
+        PrintWriter out = resp.getWriter();
+        if (context == null)
+        {
+            out.println("context = null");
+            return;
+        }
+        out.printf("context = %s%n",context);
+        out.printf("context.contextPath = %s%n",context.getContextPath());
+        out.printf("context.effective-version = %d.%d%n",context.getEffectiveMajorVersion(),context.getEffectiveMinorVersion());
+    }
+}
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/tests/test-cdi/cdi-webapp/src/main/webapp/WEB-INF/beans.xml
similarity index 100%
rename from jetty-distribution/src/main/resources/webapps/.donotdelete
rename to tests/test-cdi/cdi-webapp/src/main/webapp/WEB-INF/beans.xml
diff --git a/tests/test-cdi/cdi-webapp/src/main/webapp/WEB-INF/web.xml b/tests/test-cdi/cdi-webapp/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000..fe2940c
--- /dev/null
+++ b/tests/test-cdi/cdi-webapp/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0"?>
+<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" 
+   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+   xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" 
+   version="3.1">
+  <display-name>CDI Integration Test WebApp</display-name>
+  
+  <listener>
+    <listener-class>org.jboss.weld.environment.servlet.Listener</listener-class>
+  </listener>
+  
+  <resource-env-ref>
+    <description>Object factory for the CDI Bean Manager</description>
+    <resource-env-ref-name>BeanManager</resource-env-ref-name>
+    <resource-env-ref-type>javax.enterprise.inject.spi.BeanManager</resource-env-ref-type>
+  </resource-env-ref>
+  
+</web-app>
diff --git a/tests/test-cdi/pom.xml b/tests/test-cdi/pom.xml
new file mode 100644
index 0000000..307d8e1
--- /dev/null
+++ b/tests/test-cdi/pom.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+// ========================================================================
+// Copyright (c) Webtide LLC
+// 
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at 
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.apache.org/licenses/LICENSE-2.0.txt
+//
+// You may elect to redistribute this code under either of these licenses. 
+// ========================================================================
+ -->
+<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">
+  <parent>
+    <groupId>org.eclipse.jetty.tests</groupId>
+    <artifactId>tests-parent</artifactId>
+    <version>9.2.4-SNAPSHOT</version>
+  </parent>
+  <modelVersion>4.0.0</modelVersion>
+  <artifactId>test-cdi-parent</artifactId>
+  <packaging>pom</packaging>
+  <name>Jetty Tests :: CDI Parent</name>
+  <url>http://www.eclipse.org/jetty</url>
+  <modules>
+    <module>cdi-webapp</module>
+    <module>cdi-webapp-it</module>
+    <module>cdi-client</module>
+  </modules>
+</project>
diff --git a/tests/test-continuation/pom.xml b/tests/test-continuation/pom.xml
new file mode 100644
index 0000000..115cb01
--- /dev/null
+++ b/tests/test-continuation/pom.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+// ========================================================================
+// Copyright (c) Webtide LLC
+// 
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at 
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.apache.org/licenses/LICENSE-2.0.txt
+//
+// You may elect to redistribute this code under either of these licenses. 
+// ========================================================================
+ -->
+<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">
+  <parent>
+    <groupId>org.eclipse.jetty.tests</groupId>
+    <artifactId>tests-parent</artifactId>
+    <version>9.2.13.v20150730</version>
+    <relativePath>../pom.xml</relativePath>
+  </parent>
+  <modelVersion>4.0.0</modelVersion>
+  <artifactId>test-continuation</artifactId>
+  <packaging>jar</packaging>
+  <name>Test :: Continuation</name>
+  <description>Asynchronous API</description>
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-deploy-plugin</artifactId>
+        <configuration>
+          <!-- DO NOT DEPLOY (or Release) -->
+          <skip>true</skip>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+  <dependencies>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId> 
+      <artifactId>jetty-servlet</artifactId>
+      <version>${project.version}</version>
+      <scope>provided</scope>
+    </dependency> 
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-continuation</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.toolchain</groupId>
+      <artifactId>jetty-test-helper</artifactId>
+      <scope>compile</scope>
+    </dependency>
+    
+  </dependencies>
+</project>
diff --git a/tests/test-continuation/src/test/java/org/eclipse/jetty/continuation/ContinuationBase.java b/tests/test-continuation/src/test/java/org/eclipse/jetty/continuation/ContinuationBase.java
new file mode 100644
index 0000000..ec1856e
--- /dev/null
+++ b/tests/test-continuation/src/test/java/org/eclipse/jetty/continuation/ContinuationBase.java
@@ -0,0 +1,509 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.continuation;
+
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.not;
+import static org.hamcrest.Matchers.startsWith;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.Socket;
+import java.nio.charset.StandardCharsets;
+import java.util.Timer;
+import java.util.TimerTask;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+
+public abstract class ContinuationBase
+{
+    protected SuspendServlet _servlet=new SuspendServlet();
+    protected int _port;
+
+    protected void doNormal(String type) throws Exception
+    {
+        String response=process(null,null);
+        assertContains(type,response);
+        assertContains("NORMAL",response);
+        assertNotContains("history: onTimeout",response);
+        assertNotContains("history: onComplete",response);
+    }
+
+    protected void doSleep() throws Exception
+    {
+        String response=process("sleep=200",null);
+        assertContains("SLEPT",response);
+        assertNotContains("history: onTimeout",response);
+        assertNotContains("history: onComplete",response);
+    }
+
+    protected void doSuspend() throws Exception
+    {
+        String response=process("suspend=200",null);
+        assertContains("TIMEOUT",response);
+        assertContains("history: onTimeout",response);
+        assertContains("history: onComplete",response);
+    }
+
+    protected void doSuspendWaitResume() throws Exception
+    {
+        String response=process("suspend=200&resume=10",null);
+        assertContains("RESUMED",response);
+        assertNotContains("history: onTimeout",response);
+        assertContains("history: onComplete",response);
+    }
+
+    protected void doSuspendResume() throws Exception
+    {
+        String response=process("suspend=200&resume=0",null);
+        assertContains("RESUMED",response);
+        assertNotContains("history: onTimeout",response);
+        assertContains("history: onComplete",response);
+    }
+
+    protected void doSuspendWaitComplete() throws Exception
+    {
+        String response=process("suspend=200&complete=50",null);
+        assertContains("COMPLETED",response);
+        assertContains("history: initial",response);
+        assertNotContains("history: onTimeout",response);
+        assertContains("history: onComplete",response);
+        assertNotContains("history: !initial",response);
+    }
+
+    protected void doSuspendComplete() throws Exception
+    {
+        String response=process("suspend=200&complete=0",null);
+        assertContains("COMPLETED",response);
+        assertContains("history: initial",response);
+        assertNotContains("history: onTimeout",response);
+        assertContains("history: onComplete",response);
+        assertNotContains("history: !initial",response);
+    }
+
+    protected void doSuspendWaitResumeSuspendWaitResume() throws Exception
+    {
+        String response=process("suspend=1000&resume=10&suspend2=1000&resume2=10",null);
+        assertEquals(2,count(response,"history: suspend"));
+        assertEquals(2,count(response,"history: resume"));
+        assertEquals(0,count(response,"history: onTimeout"));
+        assertEquals(1,count(response,"history: onComplete"));
+        assertContains("RESUMED",response);
+    }
+
+    protected void doSuspendWaitResumeSuspendComplete() throws Exception
+    {
+        String response=process("suspend=1000&resume=10&suspend2=1000&complete2=10",null);
+        assertEquals(2,count(response,"history: suspend"));
+        assertEquals(1,count(response,"history: resume"));
+        assertEquals(0,count(response,"history: onTimeout"));
+        assertEquals(1,count(response,"history: onComplete"));
+        assertContains("COMPLETED",response);
+    }
+
+    protected void doSuspendWaitResumeSuspend() throws Exception
+    {
+        String response=process("suspend=1000&resume=10&suspend2=10",null);
+        assertEquals(2,count(response,"history: suspend"));
+        assertEquals(1,count(response,"history: resume"));
+        assertEquals(1,count(response,"history: onTimeout"));
+        assertEquals(1,count(response,"history: onComplete"));
+        assertContains("TIMEOUT",response);
+    }
+
+    protected void doSuspendTimeoutSuspendResume() throws Exception
+    {
+        String response=process("suspend=10&suspend2=1000&resume2=10",null);
+        assertEquals(2,count(response,"history: suspend"));
+        assertEquals(1,count(response,"history: resume"));
+        assertEquals(1,count(response,"history: onTimeout"));
+        assertEquals(1,count(response,"history: onComplete"));
+        assertContains("RESUMED",response);
+    }
+
+    protected void doSuspendTimeoutSuspendComplete() throws Exception
+    {
+        String response=process("suspend=10&suspend2=1000&complete2=10",null);
+        assertEquals(2,count(response,"history: suspend"));
+        assertEquals(0,count(response,"history: resume"));
+        assertEquals(1,count(response,"history: onTimeout"));
+        assertEquals(1,count(response,"history: onComplete"));
+        assertContains("COMPLETED",response);
+    }
+
+    protected void doSuspendTimeoutSuspend() throws Exception
+    {
+        String response=process("suspend=10&suspend2=10",null);
+        assertEquals(2,count(response,"history: suspend"));
+        assertEquals(0,count(response,"history: resume"));
+        assertEquals(2,count(response,"history: onTimeout"));
+        assertEquals(1,count(response,"history: onComplete"));
+        assertContains("TIMEOUT",response);
+    }
+
+    protected void doSuspendThrowResume() throws Exception
+    {
+        String response=process("suspend=200&resume=10&undispatch=true",null);
+        assertContains("RESUMED",response);
+        assertNotContains("history: onTimeout",response);
+        assertContains("history: onComplete",response);
+    }
+
+    protected void doSuspendResumeThrow() throws Exception
+    {
+        String response=process("suspend=200&resume=0&undispatch=true",null);
+        assertContains("RESUMED",response);
+        assertNotContains("history: onTimeout",response);
+        assertContains("history: onComplete",response);
+    }
+
+    protected void doSuspendThrowComplete() throws Exception
+    {
+        String response=process("suspend=200&complete=10&undispatch=true",null);
+        assertContains("COMPLETED",response);
+        assertNotContains("history: onTimeout",response);
+        assertContains("history: onComplete",response);
+    }
+
+    protected void doSuspendCompleteThrow() throws Exception
+    {
+        String response=process("suspend=200&complete=0&undispatch=true",null);
+        assertContains("COMPLETED",response);
+        assertNotContains("history: onTimeout",response);
+        assertContains("history: onComplete",response);
+    }
+
+
+    private int count(String responses,String substring)
+    {
+        int count=0;
+        int i=responses.indexOf(substring);
+        while (i>=0)
+        {
+            count++;
+            i=responses.indexOf(substring,i+substring.length());
+        }
+
+        return count;
+    }
+
+    protected void assertContains(String content,String response)
+    {
+        assertThat(response,startsWith("HTTP/1.1 200 OK"));
+        assertThat(response,containsString(content));
+    }
+
+    protected void assertNotContains(String content,String response)
+    {
+        assertThat(response,startsWith("HTTP/1.1 200 OK"));
+        assertThat(response,not(containsString(content)));
+    }
+
+    public synchronized String process(String query,String content) throws Exception
+    {
+        String request = "GET /";
+
+        if (query!=null)
+            request+="?"+query;
+        request+=" HTTP/1.1\r\n"+
+        "Host: localhost\r\n"+
+        "Connection: close\r\n";
+        if (content==null)
+            request+="\r\n";
+        else
+        {
+            request+="Content-Length: "+content.length()+"\r\n";
+            request+="\r\n" + content;
+        }
+
+        int port=_port;
+        String response=null;
+        try (Socket socket = new Socket("localhost",port);)
+        {
+            socket.setSoTimeout(10000);
+            socket.getOutputStream().write(request.getBytes(StandardCharsets.UTF_8));
+            socket.getOutputStream().flush();
+
+            response = toString(socket.getInputStream());
+        }
+        catch(Exception e)
+        {
+            System.err.println("failed on port "+port);
+            e.printStackTrace();
+            throw e;
+        }
+        return response;
+    }
+
+
+    protected abstract String toString(InputStream in) throws IOException;
+
+
+    private static class SuspendServlet extends HttpServlet
+    {
+        private Timer _timer=new Timer();
+
+        public SuspendServlet()
+        {}
+
+        /* ------------------------------------------------------------ */
+        @Override
+        protected void doGet(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException
+        {
+            final Continuation continuation = ContinuationSupport.getContinuation(request);
+
+            response.addHeader("history",continuation.getClass().toString());
+
+            int read_before=0;
+            long sleep_for=-1;
+            long suspend_for=-1;
+            long suspend2_for=-1;
+            long resume_after=-1;
+            long resume2_after=-1;
+            long complete_after=-1;
+            long complete2_after=-1;
+            boolean undispatch=false;
+
+            if (request.getParameter("read")!=null)
+                read_before=Integer.parseInt(request.getParameter("read"));
+            if (request.getParameter("sleep")!=null)
+                sleep_for=Integer.parseInt(request.getParameter("sleep"));
+            if (request.getParameter("suspend")!=null)
+                suspend_for=Integer.parseInt(request.getParameter("suspend"));
+            if (request.getParameter("suspend2")!=null)
+                suspend2_for=Integer.parseInt(request.getParameter("suspend2"));
+            if (request.getParameter("resume")!=null)
+                resume_after=Integer.parseInt(request.getParameter("resume"));
+            if (request.getParameter("resume2")!=null)
+                resume2_after=Integer.parseInt(request.getParameter("resume2"));
+            if (request.getParameter("complete")!=null)
+                complete_after=Integer.parseInt(request.getParameter("complete"));
+            if (request.getParameter("complete2")!=null)
+                complete2_after=Integer.parseInt(request.getParameter("complete2"));
+            if (request.getParameter("undispatch")!=null)
+                undispatch=Boolean.parseBoolean(request.getParameter("undispatch"));
+
+            if (continuation.isInitial())
+            {
+                response.addHeader("history","initial");
+                if (read_before>0)
+                {
+                    byte[] buf=new byte[read_before];
+                    request.getInputStream().read(buf);
+                }
+                else if (read_before<0)
+                {
+                    InputStream in = request.getInputStream();
+                    int b=in.read();
+                    while(b!=-1)
+                        b=in.read();
+                }
+
+                if (suspend_for>=0)
+                {
+                    if (suspend_for>0)
+                        continuation.setTimeout(suspend_for);
+                    continuation.addContinuationListener(__listener);
+                    response.addHeader("history","suspend");
+                    continuation.suspend(response);
+
+                    if (complete_after>0)
+                    {
+                        TimerTask complete = new TimerTask()
+                        {
+                            @Override
+                            public void run()
+                            {
+                                try
+                                {
+                                    response.setStatus(200);
+                                    response.getOutputStream().println("COMPLETED\n");
+                                    continuation.complete();
+                                }
+                                catch(Exception e)
+                                {
+                                    e.printStackTrace();
+                                }
+                            }
+                        };
+                        synchronized (_timer)
+                        {
+                            _timer.schedule(complete,complete_after);
+                        }
+                    }
+                    else if (complete_after==0)
+                    {
+                        response.setStatus(200);
+                        response.getOutputStream().println("COMPLETED\n");
+                        continuation.complete();
+                    }
+                    else if (resume_after>0)
+                    {
+                        TimerTask resume = new TimerTask()
+                        {
+                            @Override
+                            public void run()
+                            {
+                                ((HttpServletResponse)continuation.getServletResponse()).addHeader("history","resume");
+                                continuation.resume();
+                            }
+                        };
+                        synchronized (_timer)
+                        {
+                            _timer.schedule(resume,resume_after);
+                        }
+                    }
+                    else if (resume_after==0)
+                    {
+                        ((HttpServletResponse)continuation.getServletResponse()).addHeader("history","resume");
+                        continuation.resume();
+                    }
+
+                    if (undispatch)
+                        continuation.undispatch();
+                }
+                else if (sleep_for>=0)
+                {
+                    try
+                    {
+                        Thread.sleep(sleep_for);
+                    }
+                    catch (InterruptedException e)
+                    {
+                        e.printStackTrace();
+                    }
+                    response.setStatus(200);
+                    response.getOutputStream().println("SLEPT\n");
+                }
+                else
+                {
+                    response.setStatus(200);
+                    response.getOutputStream().println("NORMAL\n");
+                }
+            }
+            else
+            {
+                response.addHeader("history","!initial");
+                if (suspend2_for>=0 && request.getAttribute("2nd")==null)
+                {
+                    request.setAttribute("2nd","cycle");
+
+                    if (suspend2_for>0)
+                        continuation.setTimeout(suspend2_for);
+                    // continuation.addContinuationListener(__listener);
+                    response.addHeader("history","suspend");
+                    continuation.suspend(response);
+
+                    if (complete2_after>0)
+                    {
+                        TimerTask complete = new TimerTask()
+                        {
+                            @Override
+                            public void run()
+                            {
+                                try
+                                {
+                                    response.setStatus(200);
+                                    response.getOutputStream().println("COMPLETED\n");
+                                    continuation.complete();
+                                }
+                                catch(Exception e)
+                                {
+                                    e.printStackTrace();
+                                }
+                            }
+                        };
+                        synchronized (_timer)
+                        {
+                            _timer.schedule(complete,complete2_after);
+                        }
+                    }
+                    else if (complete2_after==0)
+                    {
+                        response.setStatus(200);
+                        response.getOutputStream().println("COMPLETED\n");
+                        continuation.complete();
+                    }
+                    else if (resume2_after>0)
+                    {
+                        TimerTask resume = new TimerTask()
+                        {
+                            @Override
+                            public void run()
+                            {
+                                response.addHeader("history","resume");
+                                continuation.resume();
+                            }
+                        };
+                        synchronized (_timer)
+                        {
+                            _timer.schedule(resume,resume2_after);
+                        }
+                    }
+                    else if (resume2_after==0)
+                    {
+                        response.addHeader("history","resume");
+                        continuation.resume();
+                    }
+                    if (undispatch)
+                        continuation.undispatch();
+                    return;
+                }
+                else if (continuation.isExpired())
+                {
+                    response.setStatus(200);
+                    response.getOutputStream().println("TIMEOUT\n");
+                }
+                else if (continuation.isResumed())
+                {
+                    response.setStatus(200);
+                    response.getOutputStream().println("RESUMED\n");
+                }
+                else
+                {
+                    response.setStatus(200);
+                    response.getOutputStream().println("unknown???\n");
+                }
+            }
+        }
+    }
+
+
+    private static ContinuationListener __listener = new ContinuationListener()
+    {
+        @Override
+        public void onComplete(Continuation continuation)
+        {
+            ((HttpServletResponse)continuation.getServletResponse()).addHeader("history","onComplete");
+        }
+
+        @Override
+        public void onTimeout(Continuation continuation)
+        {
+            ((HttpServletResponse)continuation.getServletResponse()).addHeader("history","onTimeout");
+        }
+
+    };
+}
diff --git a/tests/test-continuation/src/test/java/org/eclipse/jetty/continuation/ContinuationTest.java b/tests/test-continuation/src/test/java/org/eclipse/jetty/continuation/ContinuationTest.java
new file mode 100644
index 0000000..fabff90
--- /dev/null
+++ b/tests/test-continuation/src/test/java/org/eclipse/jetty/continuation/ContinuationTest.java
@@ -0,0 +1,203 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.continuation;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.RequestLog;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.handler.RequestLogHandler;
+import org.eclipse.jetty.servlet.FilterHolder;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+
+
+public class ContinuationTest extends ContinuationBase
+{
+    protected Server _server = new Server();
+    protected ServletHandler _servletHandler;
+    protected ServerConnector _connector;
+    FilterHolder _filter;
+    protected List<String> _log = new ArrayList<String>();
+
+    @Before
+    public void setUp() throws Exception
+    {
+        _connector = new ServerConnector(_server);
+        _server.setConnectors(new Connector[]{ _connector });
+
+        _log.clear();
+        RequestLogHandler requestLogHandler = new RequestLogHandler();
+        requestLogHandler.setRequestLog(new Log());
+        _server.setHandler(requestLogHandler);
+        
+        ServletContextHandler servletContext = new ServletContextHandler(ServletContextHandler.NO_SECURITY|ServletContextHandler.NO_SESSIONS);
+        requestLogHandler.setHandler(servletContext);
+        
+        _servletHandler=servletContext.getServletHandler();
+        ServletHolder holder=new ServletHolder(_servlet);
+        holder.setAsyncSupported(true);
+        _servletHandler.addServletWithMapping(holder,"/");
+        
+        _server.start();
+        _port=_connector.getLocalPort();
+    }
+
+    @After
+    public void tearDown() throws Exception
+    {
+        Assert.assertEquals(1,_log.size());
+        Assert.assertTrue(_log.get(0).startsWith("200 "));
+        Assert.assertTrue(_log.get(0).endsWith(" /"));
+        _server.stop();
+    }
+    
+    @Test
+    public void testContinuation() throws Exception
+    {
+        doNormal("Servlet3Continuation");
+    }
+
+    @Test
+    public void testSleep() throws Exception
+    {
+        doSleep();
+    }
+
+    @Test
+    public void testSuspend() throws Exception
+    {
+        doSuspend();
+    }
+
+    @Test
+    public void testSuspendWaitResume() throws Exception
+    {
+        doSuspendWaitResume();
+    }
+
+    @Test
+    public void testSuspendResume() throws Exception
+    {
+        doSuspendResume();
+    }
+
+    @Test
+    public void testSuspendWaitComplete() throws Exception
+    {
+        doSuspendWaitComplete();
+    }
+
+    @Test
+    public void testSuspendComplete() throws Exception
+    {
+        doSuspendComplete();
+    }
+
+    @Test
+    public void testSuspendWaitResumeSuspendWaitResume() throws Exception
+    {
+        doSuspendWaitResumeSuspendWaitResume();
+    }
+
+    @Test
+    public void testSuspendWaitResumeSuspendComplete() throws Exception
+    {
+        doSuspendWaitResumeSuspendComplete();
+    }
+
+    @Test
+    public void testSuspendWaitResumeSuspend() throws Exception
+    {
+        doSuspendWaitResumeSuspend();
+    }
+
+    @Test
+    public void testSuspendTimeoutSuspendResume() throws Exception
+    {
+        doSuspendTimeoutSuspendResume();
+    }
+
+    @Test
+    public void testSuspendTimeoutSuspendComplete() throws Exception
+    {
+        doSuspendTimeoutSuspendComplete();
+    }
+
+    @Test
+    public void testSuspendTimeoutSuspend() throws Exception
+    {
+        doSuspendTimeoutSuspend();
+    }
+
+    @Test
+    public void testSuspendThrowResume() throws Exception
+    {
+        doSuspendThrowResume();
+    }
+
+    @Test
+    public void testSuspendResumeThrow() throws Exception
+    {
+        doSuspendResumeThrow();
+    }
+
+    @Test
+    public void testSuspendThrowComplete() throws Exception
+    {
+        doSuspendThrowComplete();
+    }
+
+    @Test
+    public void testSuspendCompleteThrow() throws Exception
+    {
+        doSuspendCompleteThrow();
+    }
+    
+    @Override
+    protected String toString(InputStream in) throws IOException
+    {
+        return IO.toString(in);
+    }
+    
+    class Log extends AbstractLifeCycle implements RequestLog
+    {
+        @Override
+        public void log(Request request, Response response)
+        {
+            _log.add(response.getStatus()+" "+response.getContentCount()+" "+request.getRequestURI());
+        }
+        
+    }
+}
diff --git a/tests/test-continuation/src/test/java/org/eclipse/jetty/continuation/FauxContinuationTest.java b/tests/test-continuation/src/test/java/org/eclipse/jetty/continuation/FauxContinuationTest.java
new file mode 100644
index 0000000..10242e6
--- /dev/null
+++ b/tests/test-continuation/src/test/java/org/eclipse/jetty/continuation/FauxContinuationTest.java
@@ -0,0 +1,156 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.continuation;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.servlet.FilterHolder;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jetty.util.IO;
+
+
+public class FauxContinuationTest extends ContinuationBase
+{
+    protected Server _server = new Server();
+    protected ServletHandler _servletHandler;
+    protected ServerConnector _connector;
+    FilterHolder _filter;
+
+    protected void setUp() throws Exception
+    {
+        _connector = new ServerConnector(_server);
+        _server.setConnectors(new Connector[]{ _connector });
+        ServletContextHandler servletContext = new ServletContextHandler(ServletContextHandler.NO_SECURITY|ServletContextHandler.NO_SESSIONS);
+        _server.setHandler(servletContext);
+        _servletHandler=servletContext.getServletHandler();
+        ServletHolder holder=new ServletHolder(_servlet);
+        _servletHandler.addServletWithMapping(holder,"/");
+        
+        _filter=_servletHandler.addFilterWithMapping(ContinuationFilter.class,"/*",null);
+        _filter.setInitParameter("debug","true");
+        _filter.setInitParameter("faux","true");
+        _server.start();
+        _port=_connector.getLocalPort();
+        
+    }
+
+    protected void tearDown() throws Exception
+    {
+        _server.stop();
+    }
+
+    public void testContinuation() throws Exception
+    {
+        doNormal("FauxContinuation");
+    }
+    
+    public void testSleep() throws Exception
+    {
+        doSleep();
+    }
+
+    public void testSuspend() throws Exception
+    {
+        doSuspend();
+    }
+
+    public void testSuspendWaitResume() throws Exception
+    {
+        doSuspendWaitResume();
+    }
+
+    public void testSuspendResume() throws Exception
+    {
+        doSuspendResume();
+    }
+
+    public void testSuspendWaitComplete() throws Exception
+    {
+        doSuspendWaitComplete();
+    }
+
+    public void testSuspendComplete() throws Exception
+    {
+        doSuspendComplete();
+    }
+
+    public void testSuspendWaitResumeSuspendWaitResume() throws Exception
+    {
+        doSuspendWaitResumeSuspendWaitResume();
+    }
+    
+    public void testSuspendWaitResumeSuspendComplete() throws Exception
+    {
+        doSuspendWaitResumeSuspendComplete();
+    }
+
+    public void testSuspendWaitResumeSuspend() throws Exception
+    {
+        doSuspendWaitResumeSuspend();
+    }
+
+    public void testSuspendTimeoutSuspendResume() throws Exception
+    {
+        doSuspendTimeoutSuspendResume();
+    }
+
+    public void testSuspendTimeoutSuspendComplete() throws Exception
+    {
+        doSuspendTimeoutSuspendComplete();
+    }
+
+    public void testSuspendTimeoutSuspend() throws Exception
+    {
+        doSuspendTimeoutSuspend();
+    }
+
+    public void testSuspendThrowResume() throws Exception
+    {
+        doSuspendThrowResume();
+    }
+
+    public void testSuspendResumeThrow() throws Exception
+    {
+        doSuspendResumeThrow();
+    }
+
+    public void testSuspendThrowComplete() throws Exception
+    {
+        doSuspendThrowComplete();
+    }
+
+    public void testSuspendCompleteThrow() throws Exception
+    {
+        doSuspendCompleteThrow();
+    }
+    
+    
+    
+    protected String toString(InputStream in) throws IOException
+    {
+        return IO.toString(in);
+    }
+    
+}
diff --git a/tests/test-integration/pom.xml b/tests/test-integration/pom.xml
index 80052a0..3763e43 100644
--- a/tests/test-integration/pom.xml
+++ b/tests/test-integration/pom.xml
@@ -20,7 +20,7 @@
   <parent>
     <groupId>org.eclipse.jetty.tests</groupId>
     <artifactId>tests-parent</artifactId>
-    <version>8.1.17.v20150415</version>
+    <version>9.2.13.v20150730</version>
   </parent>
   <modelVersion>4.0.0</modelVersion>
   <artifactId>test-integration</artifactId>
@@ -112,8 +112,9 @@
     </dependency>
     <dependency>
       <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-monitor</artifactId>
-      <version>${project.version}</version>
+      <artifactId>jetty-distribution</artifactId>
+      <version>${project.version}</version> 
+      <type>pom</type>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jetty.toolchain</groupId>
diff --git a/tests/test-integration/src/test/java/org/eclipse/jetty/test/DefaultHandlerTest.java b/tests/test-integration/src/test/java/org/eclipse/jetty/test/DefaultHandlerTest.java
index 66755e9..50e7048 100644
--- a/tests/test-integration/src/test/java/org/eclipse/jetty/test/DefaultHandlerTest.java
+++ b/tests/test-integration/src/test/java/org/eclipse/jetty/test/DefaultHandlerTest.java
@@ -18,6 +18,8 @@
 
 package org.eclipse.jetty.test;
 
+import static org.junit.Assert.*;
+
 import java.io.ByteArrayInputStream;
 import java.io.InputStream;
 import java.net.InetAddress;
@@ -26,10 +28,10 @@ import java.net.URL;
 import java.net.URLConnection;
 import java.util.List;
 
-import org.eclipse.jetty.http.HttpSchemes;
+import org.eclipse.jetty.http.HttpScheme;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.HttpTester;
 import org.eclipse.jetty.test.support.TestableJettyServer;
-import org.eclipse.jetty.test.support.rawhttp.HttpRequestTester;
-import org.eclipse.jetty.test.support.rawhttp.HttpResponseTester;
 import org.eclipse.jetty.test.support.rawhttp.HttpSocketImpl;
 import org.eclipse.jetty.test.support.rawhttp.HttpTesting;
 import org.eclipse.jetty.util.IO;
@@ -53,8 +55,9 @@ public class DefaultHandlerTest
     public static void setUpServer() throws Exception
     {
         server = new TestableJettyServer();
-        server.setScheme(HttpSchemes.HTTP);
+        server.setScheme(HttpScheme.HTTP.asString());
         server.addConfiguration("DefaultHandler.xml");
+        server.addConfiguration("NIOHttp.xml");
 
         server.load();
         server.start();
@@ -107,14 +110,15 @@ public class DefaultHandlerTest
         // Collect response
         String rawResponse = IO.toString(sock.getInputStream());
         DEBUG("--raw-response--\n" + rawResponse);
-        HttpResponseTester response = new HttpResponseTester();
-        response.parse(rawResponse);
+        
+        HttpTester.Response response = HttpTester.parseResponse(rawResponse);
 
-        response.assertStatusOK();
+        assertEquals(HttpStatus.OK_200, response.getStatus());
 
-        response.assertBody("ABCDEFGHIJKLMNOPQRSTUVWXYZ\n");
+        assertTrue(response.getContent().contains("ABCDEFGHIJKLMNOPQRSTUVWXYZ\n"));
     }
 
+    /*
     @Test
     public void testMultiGET_Raw() throws Exception
     {
@@ -131,38 +135,43 @@ public class DefaultHandlerTest
         rawRequests.append("\r\n");
 
         HttpTesting http = new HttpTesting(new HttpSocketImpl(),serverPort);
+      
 
-        List<HttpResponseTester> responses = http.requests(rawRequests);
+        List<HttpTester.Response> responses = http.requests(rawRequests);
 
-        HttpResponseTester response = responses.get(0);
-        response.assertStatusOK();
-        response.assertBody("ABCDEFGHIJKLMNOPQRSTUVWXYZ\n");
+        HttpTester.Response response = responses.get(0);
+        assertEquals(HttpStatus.OK_200, response.getStatus());
+        assertTrue(response.getContent().contains("ABCDEFGHIJKLMNOPQRSTUVWXYZ\n"));
 
         response = responses.get(1);
-        response.assertStatusOK();
-        response.assertBody("Host=Default\nResource=R1\n");
+        assertEquals(HttpStatus.OK_200, response.getStatus()); 
+        assertTrue(response.getContent().contains("Host=Default\nResource=R1\n"));
 
         response = responses.get(2);
-        response.assertStatusOK();
-        response.assertBody("Host=Default\nResource=R1\n");
+        assertEquals(HttpStatus.OK_200, response.getStatus()); 
+        assertTrue(response.getContent().contains("Host=Default\nResource=R1\n"));
     }
+    */
+    
+    
+    
 
     @Test
     public void testGET_HttpTesting() throws Exception
     {
-        HttpRequestTester request = new HttpRequestTester();
+        HttpTester.Request request = HttpTester.newRequest();
         request.setMethod("GET");
         request.setURI("/tests/alpha.txt");
-        request.addHeader("Host","localhost");
-        request.addHeader("Connection","close");
+        request.put("Host","localhost");
+        request.put("Connection","close");
         // request.setContent(null);
 
         HttpTesting testing = new HttpTesting(new HttpSocketImpl(),serverPort);
-        HttpResponseTester response = testing.request(request);
+        HttpTester.Response response = testing.request(request);
 
-        response.assertStatusOK();
-        response.assertContentType("text/plain");
-        response.assertBody("ABCDEFGHIJKLMNOPQRSTUVWXYZ\n");
+        assertEquals(HttpStatus.OK_200, response.getStatus());
+        assertEquals("text/plain", response.get("Content-Type"));
+        assertTrue(response.getContent().contains("ABCDEFGHIJKLMNOPQRSTUVWXYZ\n"));
     }
 
     private void DEBUG(String msg)
diff --git a/tests/test-integration/src/test/java/org/eclipse/jetty/test/DigestPostTest.java b/tests/test-integration/src/test/java/org/eclipse/jetty/test/DigestPostTest.java
index 4479a5e..1117672 100644
--- a/tests/test-integration/src/test/java/org/eclipse/jetty/test/DigestPostTest.java
+++ b/tests/test-integration/src/test/java/org/eclipse/jetty/test/DigestPostTest.java
@@ -18,33 +18,38 @@
 
 package org.eclipse.jetty.test;
 
-import java.io.BufferedInputStream;
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.net.Socket;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
 import java.security.MessageDigest;
 import java.util.Collections;
+import java.util.concurrent.TimeUnit;
 
 import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import org.eclipse.jetty.client.ContentExchange;
 import org.eclipse.jetty.client.HttpClient;
-import org.eclipse.jetty.client.security.Realm;
-import org.eclipse.jetty.client.security.SimpleRealmResolver;
-import org.eclipse.jetty.http.HttpMethods;
-import org.eclipse.jetty.io.ByteArrayBuffer;
+import org.eclipse.jetty.client.api.AuthenticationStore;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.util.BytesContentProvider;
+import org.eclipse.jetty.client.util.DigestAuthentication;
+import org.eclipse.jetty.client.util.StringContentProvider;
+import org.eclipse.jetty.http.HttpMethod;
 import org.eclipse.jetty.security.ConstraintMapping;
 import org.eclipse.jetty.security.ConstraintSecurityHandler;
 import org.eclipse.jetty.security.HashLoginService;
 import org.eclipse.jetty.security.authentication.DigestAuthenticator;
 import org.eclipse.jetty.server.Connector;
 import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.NetworkConnector;
 import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
 import org.eclipse.jetty.server.handler.DefaultHandler;
 import org.eclipse.jetty.server.handler.HandlerCollection;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
 import org.eclipse.jetty.servlet.ServletContextHandler;
 import org.eclipse.jetty.util.IO;
 import org.eclipse.jetty.util.StringUtil;
@@ -80,8 +85,7 @@ public class DigestPostTest
         try
         {
             _server = new Server();
-            _server.setConnectors(new Connector[]
-            { new SelectChannelConnector() });
+            _server.setConnectors(new Connector[] { new ServerConnector(_server) });
 
             ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SECURITY);
             context.setContextPath("/test");
@@ -125,15 +129,15 @@ public class DigestPostTest
     @Test
     public void testServerDirectlyHTTP10() throws Exception
     {
-        Socket socket = new Socket("127.0.0.1",_server.getConnectors()[0].getLocalPort());
-        byte[] bytes = __message.getBytes("UTF-8");
+        Socket socket = new Socket("127.0.0.1",((NetworkConnector)_server.getConnectors()[0]).getLocalPort());
+        byte[] bytes = __message.getBytes(StandardCharsets.UTF_8);
 
         _received=null;
         socket.getOutputStream().write(
                 ("POST /test/ HTTP/1.0\r\n"+
-                "Host: 127.0.0.1:"+_server.getConnectors()[0].getLocalPort()+"\r\n"+
+                "Host: 127.0.0.1:"+((NetworkConnector)_server.getConnectors()[0]).getLocalPort()+"\r\n"+
                 "Content-Length: "+bytes.length+"\r\n"+
-                "\r\n").getBytes("UTF-8"));
+                "\r\n").getBytes(StandardCharsets.UTF_8));
         socket.getOutputStream().write(bytes);
         socket.getOutputStream().flush();
 
@@ -152,15 +156,15 @@ public class DigestPostTest
         "\" qop=auth nc="+NC+" cnonce=\""+cnonce+"\"";
               
         
-        socket = new Socket("127.0.0.1",_server.getConnectors()[0].getLocalPort());
+        socket = new Socket("127.0.0.1",((NetworkConnector)_server.getConnectors()[0]).getLocalPort());
 
         _received=null;
         socket.getOutputStream().write(
                 ("POST /test/ HTTP/1.0\r\n"+
-                "Host: 127.0.0.1:"+_server.getConnectors()[0].getLocalPort()+"\r\n"+
+                "Host: 127.0.0.1:"+((NetworkConnector)_server.getConnectors()[0]).getLocalPort()+"\r\n"+
                 "Content-Length: "+bytes.length+"\r\n"+
                 "Authorization: "+digest+"\r\n"+
-                "\r\n").getBytes("UTF-8"));
+                "\r\n").getBytes(StandardCharsets.UTF_8));
         socket.getOutputStream().write(bytes);
         socket.getOutputStream().flush();
 
@@ -173,13 +177,13 @@ public class DigestPostTest
     @Test
     public void testServerDirectlyHTTP11() throws Exception
     {
-        Socket socket = new Socket("127.0.0.1",_server.getConnectors()[0].getLocalPort());
-        byte[] bytes = __message.getBytes("UTF-8");
+        Socket socket = new Socket("127.0.0.1",((NetworkConnector)_server.getConnectors()[0]).getLocalPort());
+        byte[] bytes = __message.getBytes(StandardCharsets.UTF_8);
 
         _received=null;
         socket.getOutputStream().write(
                 ("POST /test/ HTTP/1.1\r\n"+
-                "Host: 127.0.0.1:"+_server.getConnectors()[0].getLocalPort()+"\r\n"+
+                "Host: 127.0.0.1:"+((NetworkConnector)_server.getConnectors()[0]).getLocalPort()+"\r\n"+
                 "Content-Length: "+bytes.length+"\r\n"+
                 "\r\n").getBytes("UTF-8"));
         socket.getOutputStream().write(bytes);
@@ -189,7 +193,7 @@ public class DigestPostTest
         
         byte[] buf=new byte[4096];
         int len=socket.getInputStream().read(buf);
-        String result=new String(buf,0,len,"UTF-8");
+        String result=new String(buf,0,len,StandardCharsets.UTF_8);
 
         Assert.assertTrue(result.startsWith("HTTP/1.1 401 Unauthorized"));
         Assert.assertEquals(null,_received);
@@ -206,7 +210,7 @@ public class DigestPostTest
         _received=null;
         socket.getOutputStream().write(
                 ("POST /test/ HTTP/1.0\r\n"+
-                "Host: 127.0.0.1:"+_server.getConnectors()[0].getLocalPort()+"\r\n"+
+                "Host: 127.0.0.1:"+((NetworkConnector)_server.getConnectors()[0]).getLocalPort()+"\r\n"+
                 "Content-Length: "+bytes.length+"\r\n"+
                 "Authorization: "+digest+"\r\n"+
                 "\r\n").getBytes("UTF-8"));
@@ -222,68 +226,62 @@ public class DigestPostTest
     @Test
     public void testServerWithHttpClientStringContent() throws Exception
     {
+        String srvUrl = "http://127.0.0.1:" + ((NetworkConnector)_server.getConnectors()[0]).getLocalPort() + "/test/";        
         HttpClient client = new HttpClient();
-        client.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);
-        client.setRealmResolver(new SimpleRealmResolver(new TestRealm()));
-        client.start();
-
-        String srvUrl = "http://127.0.0.1:" + _server.getConnectors()[0].getLocalPort() + "/test/";
-
-        ContentExchange ex = new ContentExchange();
-        ex.setMethod(HttpMethods.POST);
-        ex.setURL(srvUrl);
-        ex.setRequestContent(new ByteArrayBuffer(__message,"UTF-8"));
 
-        _received=null;
-        client.send(ex);
-        ex.waitForDone();
-
-        Assert.assertEquals(__message,_received);
-        Assert.assertEquals(200,ex.getResponseStatus());
+        try
+        {
+            AuthenticationStore authStore = client.getAuthenticationStore();
+            authStore.addAuthentication(new DigestAuthentication(new URI(srvUrl), "test", "testuser", "password"));
+            client.start();
+
+            Request request = client.newRequest(srvUrl);
+            request.method(HttpMethod.POST);
+            request.content(new BytesContentProvider(__message.getBytes("UTF8")));
+            _received=null;
+            request = request.timeout(5, TimeUnit.SECONDS);
+            ContentResponse response = request.send();
+            Assert.assertEquals(__message,_received);
+            Assert.assertEquals(200,response.getStatus());
+        }
+        finally
+        {
+            client.stop();
+        }
     }
-    
+
     @Test
     public void testServerWithHttpClientStreamContent() throws Exception
     {
+        String srvUrl = "http://127.0.0.1:" + ((NetworkConnector)_server.getConnectors()[0]).getLocalPort() + "/test/";       
         HttpClient client = new HttpClient();
-        client.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);
-        client.setRealmResolver(new SimpleRealmResolver(new TestRealm()));
-        client.start();
-
-        String srvUrl = "http://127.0.0.1:" + _server.getConnectors()[0].getLocalPort() + "/test/";
-
-        ContentExchange ex = new ContentExchange();
-        ex.setMethod(HttpMethods.POST);
-        ex.setURL(srvUrl);
-        ex.setRequestContentSource(new BufferedInputStream(new FileInputStream("src/test/resources/message.txt")));
-
-        _received=null;
-        client.send(ex);
-        ex.waitForDone();
-
-        String sent = IO.toString(new FileInputStream("src/test/resources/message.txt"));
-        Assert.assertEquals(sent,_received);
-        Assert.assertEquals(200,ex.getResponseStatus());
-    }
-
-    public static class TestRealm implements Realm
-    {
-        public String getPrincipal()
+        try
         {
-            return "testuser";
-        }
+            AuthenticationStore authStore = client.getAuthenticationStore();
+            authStore.addAuthentication(new DigestAuthentication(new URI(srvUrl), "test", "testuser", "password"));   
+            client.start();
 
-        public String getId()
-        {
-            return "test";
-        }
+            String sent = IO.toString(new FileInputStream("src/test/resources/message.txt"));
+            
+            Request request = client.newRequest(srvUrl);
+            request.method(HttpMethod.POST);
+            request.content(new StringContentProvider(sent));
+            _received=null;
+            request = request.timeout(5, TimeUnit.SECONDS);
+            ContentResponse response = request.send();
+           
+            Assert.assertEquals(200,response.getStatus());
+            Assert.assertEquals(sent,_received);
 
-        public String getCredentials()
+        }
+        finally
         {
-            return "password";
+            client.stop();
         }
     }
 
+ 
+
     public static class PostServlet extends HttpServlet
     {
         private static final long serialVersionUID = 1L;
diff --git a/tests/test-integration/src/test/java/org/eclipse/jetty/test/jsp/JspAndDefaultWithAliasesTest.java b/tests/test-integration/src/test/java/org/eclipse/jetty/test/jsp/JspAndDefaultWithAliasesTest.java
index e5d34bd..be58d21 100644
--- a/tests/test-integration/src/test/java/org/eclipse/jetty/test/jsp/JspAndDefaultWithAliasesTest.java
+++ b/tests/test-integration/src/test/java/org/eclipse/jetty/test/jsp/JspAndDefaultWithAliasesTest.java
@@ -30,8 +30,8 @@ import java.util.Collection;
 import java.util.List;
 
 import org.eclipse.jetty.security.HashLoginService;
+import org.eclipse.jetty.server.NetworkConnector;
 import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
 import org.eclipse.jetty.servlet.DefaultServlet;
 import org.eclipse.jetty.servlet.ServletContextHandler;
 import org.eclipse.jetty.servlet.ServletHolder;
@@ -87,10 +87,7 @@ public class JspAndDefaultWithAliasesTest
     @BeforeClass
     public static void startServer() throws Exception
     {
-        server = new Server();
-        SelectChannelConnector connector = new SelectChannelConnector();
-        connector.setPort(0);
-        server.addConnector(connector);
+        server = new Server(0);  
 
         // Configure LoginService
         HashLoginService login = new HashLoginService();
@@ -119,8 +116,10 @@ public class JspAndDefaultWithAliasesTest
         server.setHandler(context);
 
         server.start();
+        
+        int port = ((NetworkConnector)server.getConnectors()[0]).getLocalPort();
 
-        serverURI = new URI("http://localhost:" + connector.getLocalPort() + "/");
+        serverURI = new URI("http://localhost:" + port + "/");
     }
 
     @AfterClass
diff --git a/tests/test-integration/src/test/java/org/eclipse/jetty/test/jsp/JspAndDefaultWithoutAliasesTest.java b/tests/test-integration/src/test/java/org/eclipse/jetty/test/jsp/JspAndDefaultWithoutAliasesTest.java
index d952038..a838ad5 100644
--- a/tests/test-integration/src/test/java/org/eclipse/jetty/test/jsp/JspAndDefaultWithoutAliasesTest.java
+++ b/tests/test-integration/src/test/java/org/eclipse/jetty/test/jsp/JspAndDefaultWithoutAliasesTest.java
@@ -30,8 +30,8 @@ import java.util.Collection;
 import java.util.List;
 
 import org.eclipse.jetty.security.HashLoginService;
+import org.eclipse.jetty.server.NetworkConnector;
 import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
 import org.eclipse.jetty.servlet.DefaultServlet;
 import org.eclipse.jetty.servlet.ServletContextHandler;
 import org.eclipse.jetty.servlet.ServletHolder;
@@ -88,10 +88,7 @@ public class JspAndDefaultWithoutAliasesTest
     @BeforeClass
     public static void startServer() throws Exception
     {
-        server = new Server();
-        SelectChannelConnector connector = new SelectChannelConnector();
-        connector.setPort(0);
-        server.addConnector(connector);
+        server = new Server(0);
 
         // Configure LoginService
         HashLoginService login = new HashLoginService();
@@ -120,8 +117,9 @@ public class JspAndDefaultWithoutAliasesTest
         server.setHandler(context);
 
         server.start();
-
-        serverURI = new URI("http://localhost:" + connector.getLocalPort() + "/");
+        
+        int port = ((NetworkConnector)server.getConnectors()[0]).getLocalPort();
+        serverURI = new URI("http://localhost:" + port + "/");
     }
 
     @AfterClass
diff --git a/tests/test-integration/src/test/java/org/eclipse/jetty/test/monitor/JavaMonitorIntegrationTest.java b/tests/test-integration/src/test/java/org/eclipse/jetty/test/monitor/JavaMonitorIntegrationTest.java
deleted file mode 100644
index 6963b8a..0000000
--- a/tests/test-integration/src/test/java/org/eclipse/jetty/test/monitor/JavaMonitorIntegrationTest.java
+++ /dev/null
@@ -1,171 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.test.monitor;
-
-import static org.junit.Assert.assertTrue;
-
-import java.io.IOException;
-import java.util.Random;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-import org.eclipse.jetty.client.ContentExchange;
-import org.eclipse.jetty.client.HttpClient;
-import org.eclipse.jetty.http.HttpMethods;
-import org.eclipse.jetty.http.HttpStatus;
-import org.eclipse.jetty.monitor.JMXMonitor;
-import org.eclipse.jetty.toolchain.test.JettyDistro;
-import org.eclipse.jetty.toolchain.test.PropertyFlag;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.util.resource.Resource;
-import org.eclipse.jetty.util.thread.ExecutorThreadPool;
-import org.eclipse.jetty.util.thread.ThreadPool;
-import org.eclipse.jetty.xml.XmlConfiguration;
-import org.junit.AfterClass;
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.Test;
-
-
-/* ------------------------------------------------------------ */
-/**
- */
-public class JavaMonitorIntegrationTest
-{
-    private static final Logger LOG = Log.getLogger(JavaMonitorIntegrationTest.class);
-
-    private static JettyDistro jetty;
-    
-    @BeforeClass
-    public static void initJetty() throws Exception
-    {
-        PropertyFlag.assume("JAVAMONITOR");        
-
-        jetty = new JettyDistro(JavaMonitorIntegrationTest.class);
-
-        jetty.delete("contexts/javadoc.xml");
-        
-        jetty.overlayConfig("monitor");
-        
-        jetty.start();
-
-        JMXMonitor.setServiceUrl(jetty.getJmxUrl());
-    }
-
-    @AfterClass
-    public static void shutdownJetty() throws Exception
-    {
-        if (jetty != null)
-        {
-            jetty.stop();
-        }
-    }
-
-    @Before
-    public void setUp()
-        throws Exception
-    {
-        Resource configRes = Resource.newClassPathResource("/org/eclipse/jetty/monitor/java-monitor-integration.xml");
-        XmlConfiguration xmlConfig = new XmlConfiguration(configRes.getURL());
-        xmlConfig.configure();
-    }
-
-    @Test
-    public void testIntegration()
-        throws Exception
-    {
-        final int threadCount = 100;
-        final long requestCount = 500;
-        final String requestUrl =  jetty.getBaseUri().resolve("d.txt").toASCIIString();
-        final CountDownLatch gate = new CountDownLatch(threadCount);
-        
-        ThreadPool worker = new ExecutorThreadPool(threadCount,threadCount,60,TimeUnit.SECONDS);
-        for (int idx=0; idx < threadCount; idx++)
-        {
-            worker.dispatch(new Runnable() {
-                        public void run()
-                        {
-                            runTest(requestUrl, requestCount);
-                            gate.countDown();
-                        }
-                    });
-            Thread.sleep(500);
-         }
-        gate.await();
-        assertTrue(true);
-    }
-     
-    protected static void runTest(String requestUrl, long count)
-    {
-        HttpClient client = new HttpClient();
-        client.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);
-        try
-        {
-            client.start();
-        }
-        catch (Exception ex)
-        {
-            LOG.debug(ex);
-        }
-        
-        if (client != null)
-        {
-            Random rnd = new Random();
-            for (long cnt=0; cnt < count; cnt++)
-            {
-                try
-                {
-                    ContentExchange getExchange = new ContentExchange();
-                    getExchange.setURL(requestUrl);
-                    getExchange.setMethod(HttpMethods.GET);
-                    
-                    client.send(getExchange);
-                    int state = getExchange.waitForDone();
-                    
-                    String content = "";
-                    int responseStatus = getExchange.getResponseStatus();
-                    if (responseStatus == HttpStatus.OK_200)
-                    {
-                        content = getExchange.getResponseContent();
-                    }           
-                    
-                    Thread.sleep(200);
-                }
-                catch (InterruptedException ex)
-                {
-                    break;
-                }
-                catch (IOException ex)
-                {
-                    LOG.debug(ex);
-                }
-            }
-
-            try
-            {
-                client.stop();
-            }
-            catch (Exception ex)
-            {
-                LOG.debug(ex);
-            }
-        }
-    }
-}
diff --git a/tests/test-integration/src/test/java/org/eclipse/jetty/test/monitor/JmxServiceTest.java b/tests/test-integration/src/test/java/org/eclipse/jetty/test/monitor/JmxServiceTest.java
deleted file mode 100644
index 136d27c..0000000
--- a/tests/test-integration/src/test/java/org/eclipse/jetty/test/monitor/JmxServiceTest.java
+++ /dev/null
@@ -1,165 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.test.monitor;
-
-import static org.junit.Assert.*;
-
-import java.io.IOException;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-import org.eclipse.jetty.client.ContentExchange;
-import org.eclipse.jetty.client.HttpClient;
-import org.eclipse.jetty.http.HttpMethods;
-import org.eclipse.jetty.http.HttpStatus;
-import org.eclipse.jetty.monitor.JMXMonitor;
-import org.eclipse.jetty.test.support.JettyDistro;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.util.resource.Resource;
-import org.eclipse.jetty.util.thread.ExecutorThreadPool;
-import org.eclipse.jetty.util.thread.ThreadPool;
-import org.eclipse.jetty.xml.XmlConfiguration;
-import org.junit.AfterClass;
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.Test;
-
-
-/* ------------------------------------------------------------ */
-/**
- */
-public class JmxServiceTest
-{
-    private static final Logger LOG = Log.getLogger(JmxServiceTest.class);
-
-    private static JettyDistro jetty;
-
-    @BeforeClass
-    public static void initJetty() throws Exception
-    {
-        jetty = new JettyDistro(JmxServiceTest.class);
-
-        jetty.delete("contexts/javadoc.xml");
-        
-        jetty.overlayConfig("monitor");
-        
-        jetty.start();
-
-        JMXMonitor.setServiceUrl(jetty.getJmxUrl());
-    }
-
-    @AfterClass
-    public static void shutdownJetty() throws Exception
-    {
-        if (jetty != null)
-        {
-            jetty.stop();
-        }
-    }
-    
-    @Before
-    public void setUp()
-        throws Exception
-    {
-        Resource configRes = Resource.newClassPathResource("/org/eclipse/jetty/monitor/jetty-monitor-service.xml");
-        XmlConfiguration xmlConfig = new XmlConfiguration(configRes.getURL());
-        xmlConfig.configure();
-    }
-    
-    @Test
-    public void testThreadPoolMXBean()
-        throws Exception
-    {        
-        final int threadCount = 100;
-        final long requestCount = 100;
-        final String requestUrl = jetty.getBaseUri().resolve("d.txt").toASCIIString();
-        final CountDownLatch gate = new CountDownLatch(threadCount);
-        ThreadPool worker = new ExecutorThreadPool(threadCount,threadCount,60,TimeUnit.SECONDS);
-        for (int idx=0; idx < threadCount; idx++)
-        {
-            worker.dispatch(new Runnable() {
-                        public void run()
-                        {
-                            runTest(requestUrl, requestCount);
-                            gate.countDown();
-                        }
-                    });
-            Thread.sleep(100);
-         }
-        gate.await();
-        assertTrue(true);
-    }
-     
-    protected static void runTest(String requestUrl, long count)
-    {
-        HttpClient client = new HttpClient();
-        client.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);
-        try
-        {
-            client.start();
-        }
-        catch (Exception ex)
-        {
-            LOG.debug(ex);
-        }
-        
-        if (client != null)
-        {
-            for (long cnt=0; cnt < count; cnt++)
-            {
-                try
-                {
-                    ContentExchange getExchange = new ContentExchange();
-                    getExchange.setURL(requestUrl);
-                    getExchange.setMethod(HttpMethods.GET);
-                    
-                    client.send(getExchange);
-                    int state = getExchange.waitForDone();
-                    
-                    String content = "";
-                    int responseStatus = getExchange.getResponseStatus();
-                    if (responseStatus == HttpStatus.OK_200)
-                    {
-                        content = getExchange.getResponseContent();
-                    }
-                    
-                    Thread.sleep(100);
-                }
-                catch (InterruptedException ex)
-                {
-                    break;
-                }
-                catch (IOException ex)
-                {
-                    LOG.debug(ex);
-                }
-            }
-
-            try
-            {
-                client.stop();
-            }
-            catch (Exception ex)
-            {
-                LOG.debug(ex);
-            }
-        }
-    }
-}
diff --git a/tests/test-integration/src/test/java/org/eclipse/jetty/test/monitor/ProgramConfigTest.java b/tests/test-integration/src/test/java/org/eclipse/jetty/test/monitor/ProgramConfigTest.java
deleted file mode 100644
index 055f9b7..0000000
--- a/tests/test-integration/src/test/java/org/eclipse/jetty/test/monitor/ProgramConfigTest.java
+++ /dev/null
@@ -1,184 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.test.monitor;
-
-import static org.junit.Assert.*;
-
-import java.io.IOException;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicLong;
-
-import org.eclipse.jetty.client.ContentExchange;
-import org.eclipse.jetty.client.HttpClient;
-import org.eclipse.jetty.http.HttpMethods;
-import org.eclipse.jetty.http.HttpStatus;
-import org.eclipse.jetty.monitor.JMXMonitor;
-import org.eclipse.jetty.monitor.jmx.ConsoleNotifier;
-import org.eclipse.jetty.monitor.jmx.EventNotifier;
-import org.eclipse.jetty.monitor.jmx.EventState;
-import org.eclipse.jetty.monitor.jmx.EventTrigger;
-import org.eclipse.jetty.monitor.jmx.MonitorAction;
-import org.eclipse.jetty.monitor.triggers.GreaterThanAttrEventTrigger;
-import org.eclipse.jetty.monitor.triggers.LessThanOrEqualToAttrEventTrigger;
-import org.eclipse.jetty.monitor.triggers.OrEventTrigger;
-import org.eclipse.jetty.test.support.JettyDistro;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.util.thread.ExecutorThreadPool;
-import org.eclipse.jetty.util.thread.ThreadPool;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.Test;
-
-
-/* ------------------------------------------------------------ */
-/**
- */
-public class ProgramConfigTest
-{
-    private static final Logger LOG = Log.getLogger(ProgramConfigTest.class);
-
-    private static JettyDistro jetty;
-    
-    @BeforeClass
-    public static void initJetty() throws Exception
-    {
-        jetty = new JettyDistro(ProgramConfigTest.class);
-
-        jetty.delete("contexts/javadoc.xml");
-        
-        jetty.overlayConfig("monitor");
-        
-        jetty.start();
-
-        JMXMonitor.setServiceUrl(jetty.getJmxUrl());
-    }
-
-    @AfterClass
-    public static void shutdownJetty() throws Exception
-    {
-        if (jetty != null)
-        {
-            jetty.stop();
-        }
-    }
-
-    @Test
-    public void testThreadPoolMXBean()
-        throws Exception
-    {
-        int testRangeLow  = 4;
-        int testRangeHigh = 7;
-                
-        LessThanOrEqualToAttrEventTrigger<Integer> trigger1 =
-            new LessThanOrEqualToAttrEventTrigger<Integer>("org.eclipse.jetty.util.thread:type=queuedthreadpool,id=0", "idleThreads",
-                                                testRangeLow);
-        GreaterThanAttrEventTrigger<Integer> trigger2 =
-            new GreaterThanAttrEventTrigger<Integer>("org.eclipse.jetty.util.thread:type=queuedthreadpool,id=0", "idleThreads",
-                                                testRangeHigh);
-        OrEventTrigger trigger = new OrEventTrigger(trigger1, trigger2);
-        EventNotifier notifier = new ConsoleNotifier("%s");
-        final AtomicLong counter = new AtomicLong();
-        MonitorAction action = new MonitorAction(trigger, notifier, 500) {
-                @Override
-                public void execute(EventTrigger trigger, EventState<?> state, long timestamp)
-                {
-                    System.out.println(counter.incrementAndGet());
-                }
-            };
-        JMXMonitor.addMonitorActions(action);
-
-        final int threadCount = 100;
-        final long requestCount = 100;
-        final String requestUrl = jetty.getBaseUri().resolve("d.txt").toASCIIString();
-        final CountDownLatch gate = new CountDownLatch(threadCount);
-        ThreadPool worker = new ExecutorThreadPool(threadCount,threadCount,60,TimeUnit.SECONDS);
-        for (int idx=0; idx < threadCount; idx++)
-        {
-            worker.dispatch(new Runnable() {
-                        public void run()
-                        {
-                            runTest(requestUrl, requestCount);
-                            gate.countDown();
-                        }
-                    });
-            Thread.sleep(100);
-         }
-        gate.await();
-        JMXMonitor.removeMonitorActions(action);
-        assertTrue(true);
-    }
-
-    protected static void runTest(String requestUrl, long count)
-    {
-        HttpClient client = new HttpClient();
-        client.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);
-        try
-        {
-            client.start();
-        }
-        catch (Exception ex)
-        {
-            LOG.debug(ex);
-        }
-
-        if (client != null)
-        {
-            for (long cnt=0; cnt < count; cnt++)
-            {
-                try
-                {
-                    ContentExchange getExchange = new ContentExchange();
-                    getExchange.setURL(requestUrl);
-                    getExchange.setMethod(HttpMethods.GET);
-
-                    client.send(getExchange);
-                    getExchange.waitForDone();
-
-                    String content = "";
-                    int responseStatus = getExchange.getResponseStatus();
-                    if (responseStatus == HttpStatus.OK_200)
-                    {
-                        content = getExchange.getResponseContent();
-                    }
-
-                    Thread.sleep(100);
-                }
-                catch (InterruptedException ex)
-                {
-                    break;
-                }
-                catch (IOException ex)
-                {
-                    LOG.debug(ex);
-                }
-            }
-
-            try
-            {
-                client.stop();
-            }
-            catch (Exception ex)
-            {
-                LOG.debug(ex);
-            }
-        }
-    }
-}
diff --git a/tests/test-integration/src/test/java/org/eclipse/jetty/test/monitor/XmlConfigTest.java b/tests/test-integration/src/test/java/org/eclipse/jetty/test/monitor/XmlConfigTest.java
deleted file mode 100644
index b35babd..0000000
--- a/tests/test-integration/src/test/java/org/eclipse/jetty/test/monitor/XmlConfigTest.java
+++ /dev/null
@@ -1,167 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.test.monitor;
-
-import static org.junit.Assert.*;
-
-import java.io.IOException;
-import java.util.Random;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-import org.eclipse.jetty.client.ContentExchange;
-import org.eclipse.jetty.client.HttpClient;
-import org.eclipse.jetty.http.HttpMethods;
-import org.eclipse.jetty.http.HttpStatus;
-import org.eclipse.jetty.monitor.JMXMonitor;
-import org.eclipse.jetty.test.support.JettyDistro;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.util.resource.Resource;
-import org.eclipse.jetty.util.thread.ExecutorThreadPool;
-import org.eclipse.jetty.util.thread.ThreadPool;
-import org.eclipse.jetty.xml.XmlConfiguration;
-import org.junit.AfterClass;
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.Test;
-
-
-/* ------------------------------------------------------------ */
-/**
- */
-public class XmlConfigTest
-{
-    private static final Logger LOG = Log.getLogger(XmlConfigTest.class);
-
-    private static JettyDistro jetty;
-    
-    @BeforeClass
-    public static void initJetty() throws Exception
-    {
-        jetty = new JettyDistro(XmlConfigTest.class);
-
-        jetty.delete("contexts/javadoc.xml");
-        
-        jetty.overlayConfig("monitor");
-        
-        jetty.start();
-
-        JMXMonitor.setServiceUrl(jetty.getJmxUrl());
-    }
-
-    @AfterClass
-    public static void shutdownJetty() throws Exception
-    {
-        if (jetty != null)
-        {
-            jetty.stop();
-        }
-    }
-
-    @Before
-    public void setUp()
-        throws Exception
-    {
-        Resource configRes = Resource.newClassPathResource("/org/eclipse/jetty/monitor/jetty-monitor-test.xml");
-        XmlConfiguration xmlConfig = new XmlConfiguration(configRes.getURL());
-        xmlConfig.configure();
-    }
-
-    @Test
-    public void testThreadPoolMXBean()
-        throws Exception
-    {
-        final int threadCount = 100;
-        final long requestCount = 100;
-        final String requestUrl = jetty.getBaseUri().resolve("d.txt").toASCIIString();
-        final CountDownLatch gate = new CountDownLatch(threadCount);
-        ThreadPool worker = new ExecutorThreadPool(threadCount,threadCount,60,TimeUnit.SECONDS);
-        for (int idx=0; idx < threadCount; idx++)
-        {
-            worker.dispatch(new Runnable() {
-                        public void run()
-                        {
-                            runTest(requestUrl, requestCount);
-                            gate.countDown();
-                        }
-                    });
-            Thread.sleep(100);
-         }
-        gate.await();
-        assertTrue(true);
-    }
-     
-    protected static void runTest(String requestUrl, long count)
-    {
-        HttpClient client = new HttpClient();
-        client.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);
-        try
-        {
-            client.start();
-        }
-        catch (Exception ex)
-        {
-            LOG.debug(ex);
-        }
-        
-        if (client != null)
-        {
-            Random rnd = new Random();
-            for (long cnt=0; cnt < count; cnt++)
-            {
-                try
-                {
-                    ContentExchange getExchange = new ContentExchange();
-                    getExchange.setURL(requestUrl);
-                    getExchange.setMethod(HttpMethods.GET);
-                    
-                    client.send(getExchange);
-                    int state = getExchange.waitForDone();
-                    
-                    String content = "";
-                    int responseStatus = getExchange.getResponseStatus();
-                    if (responseStatus == HttpStatus.OK_200)
-                    {
-                        content = getExchange.getResponseContent();
-                    }           
-                    
-                    Thread.sleep(100);
-                }
-                catch (InterruptedException ex)
-                {
-                    break;
-                }
-                catch (IOException ex)
-                {
-                    LOG.debug(ex);
-                }
-            }
-
-            try
-            {
-                client.stop();
-            }
-            catch (Exception ex)
-            {
-                LOG.debug(ex);
-            }
-        }
-    }
-}
diff --git a/tests/test-integration/src/test/java/org/eclipse/jetty/test/rfcs/RFC2616BIOHttpTest.java b/tests/test-integration/src/test/java/org/eclipse/jetty/test/rfcs/RFC2616BIOHttpTest.java
deleted file mode 100644
index ba56c6c..0000000
--- a/tests/test-integration/src/test/java/org/eclipse/jetty/test/rfcs/RFC2616BIOHttpTest.java
+++ /dev/null
@@ -1,49 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.test.rfcs;
-
-import org.eclipse.jetty.http.HttpSchemes;
-import org.eclipse.jetty.test.support.TestableJettyServer;
-import org.eclipse.jetty.test.support.rawhttp.HttpSocket;
-import org.eclipse.jetty.test.support.rawhttp.HttpSocketImpl;
-import org.junit.BeforeClass;
-
-/**
- * Perform the RFC2616 tests against a server running with the Jetty BIO Connector and listening on standard HTTP.
- */
-public class RFC2616BIOHttpTest extends RFC2616BaseTest
-{
-    @BeforeClass
-    public static void setupServer() throws Exception
-    {
-        TestableJettyServer server = new TestableJettyServer();
-        server.setScheme(HttpSchemes.HTTP);
-        server.addConfiguration("RFC2616Base.xml");
-        server.addConfiguration("RFC2616_Redirects.xml");
-        server.addConfiguration("RFC2616_Filters.xml");
-        server.addConfiguration("BIOHttp.xml");
-        setUpServer(server, RFC2616BIOHttpTest.class);
-    }
-
-    @Override
-    public HttpSocket getHttpClientSocket()
-    {
-        return new HttpSocketImpl();
-    }
-}
diff --git a/tests/test-integration/src/test/java/org/eclipse/jetty/test/rfcs/RFC2616BIOHttpsTest.java b/tests/test-integration/src/test/java/org/eclipse/jetty/test/rfcs/RFC2616BIOHttpsTest.java
deleted file mode 100644
index fc64dd5..0000000
--- a/tests/test-integration/src/test/java/org/eclipse/jetty/test/rfcs/RFC2616BIOHttpsTest.java
+++ /dev/null
@@ -1,50 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.test.rfcs;
-
-import org.eclipse.jetty.http.HttpSchemes;
-import org.eclipse.jetty.test.support.TestableJettyServer;
-import org.eclipse.jetty.test.support.rawhttp.HttpSocket;
-import org.eclipse.jetty.test.support.rawhttp.HttpsSocketImpl;
-import org.junit.BeforeClass;
-
-/**
- * Perform the RFC2616 tests against a server running with the Jetty BIO Connector and listening on HTTPS (HTTP over
- * SSL).
- */
-public class RFC2616BIOHttpsTest extends RFC2616BaseTest
-{
-    @BeforeClass
-    public static void setupServer() throws Exception
-    {
-        TestableJettyServer server = new TestableJettyServer();
-        server.setScheme(HttpSchemes.HTTPS);
-        server.addConfiguration("RFC2616Base.xml");
-        server.addConfiguration("RFC2616_Redirects.xml");
-        server.addConfiguration("RFC2616_Filters.xml");
-        server.addConfiguration("BIOHttps.xml");
-        setUpServer(server, RFC2616BIOHttpsTest.class);
-    }
-
-    @Override
-    public HttpSocket getHttpClientSocket() throws Exception
-    {
-        return new HttpsSocketImpl();
-    }
-}
diff --git a/tests/test-integration/src/test/java/org/eclipse/jetty/test/rfcs/RFC2616BaseTest.java b/tests/test-integration/src/test/java/org/eclipse/jetty/test/rfcs/RFC2616BaseTest.java
index d0c2eea..be1f30b 100644
--- a/tests/test-integration/src/test/java/org/eclipse/jetty/test/rfcs/RFC2616BaseTest.java
+++ b/tests/test-integration/src/test/java/org/eclipse/jetty/test/rfcs/RFC2616BaseTest.java
@@ -26,6 +26,7 @@ import java.io.IOException;
 import java.net.Socket;
 import java.text.SimpleDateFormat;
 import java.util.Calendar;
+import java.util.Collections;
 import java.util.Date;
 import java.util.Enumeration;
 import java.util.List;
@@ -33,14 +34,16 @@ import java.util.TimeZone;
 
 import org.eclipse.jetty.http.HttpFields;
 import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.HttpTester;
 import org.eclipse.jetty.test.support.StringUtil;
 import org.eclipse.jetty.test.support.TestableJettyServer;
-import org.eclipse.jetty.test.support.rawhttp.HttpResponseTester;
 import org.eclipse.jetty.test.support.rawhttp.HttpSocket;
 import org.eclipse.jetty.test.support.rawhttp.HttpTesting;
 import org.eclipse.jetty.toolchain.test.FS;
 import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
 import org.eclipse.jetty.toolchain.test.StringAssert;
+import org.eclipse.jetty.util.MultiPartInputStreamParser;
+import org.hamcrest.Matchers;
 import org.junit.AfterClass;
 import org.junit.Assert;
 import org.junit.Before;
@@ -56,8 +59,6 @@ public abstract class RFC2616BaseTest
     /** STRICT RFC TESTS */
     private static final boolean STRICT = false;
     private static TestableJettyServer server;
-    private List<HttpResponseTester> responses;
-    private HttpResponseTester response;
     private HttpTesting http;
 
     class TestFile
@@ -85,7 +86,7 @@ public abstract class RFC2616BaseTest
 
     public static void setUpServer(TestableJettyServer testableserver, Class<?> testclazz) throws Exception
     {
-    	File testWorkDir = MavenTestingUtils.getTargetTestingDir(testclazz.getName());
+        File testWorkDir = MavenTestingUtils.getTargetTestingDir(testclazz.getName());
         FS.ensureDirExists(testWorkDir);
 
         System.setProperty("java.io.tmpdir",testWorkDir.getAbsolutePath());
@@ -93,6 +94,7 @@ public abstract class RFC2616BaseTest
         server = testableserver;
         server.load();
         server.start();
+        //server.getServer().dumpStdErr();
     }
     
     @Before
@@ -167,8 +169,9 @@ public abstract class RFC2616BaseTest
         req1.append("123\r\n\r\n");
         req1.append("0;\r\n\r\n");
 
-        response = http.request(req1);
-        response.assertStatus("3.6 Transfer Coding / Bad 400",HttpStatus.BAD_REQUEST_400);
+        HttpTester.Response response = http.request(req1);
+        
+        assertEquals("3.6 Transfer Coding / Bad 400",HttpStatus.BAD_REQUEST_400,response.getStatus());
     }
     
     /**
@@ -208,20 +211,20 @@ public abstract class RFC2616BaseTest
         req2.append("Connection: close\n");
         req2.append("\n");
 
-        responses = http.requests(req2);
+        List<HttpTester.Response> responses = http.requests(req2);
         Assert.assertEquals("Response Count",3,responses.size());
 
-        response = responses.get(0); // Response 1
-        response.assertStatusOK("3.6.1 Transfer Codings / Response 1 Code");
-        response.assertBody("3.6.1 Transfer Codings / Chunked String","12345\n");
+        HttpTester.Response response = responses.get(0); // Response 1
+        assertEquals("3.6.1 Transfer Codings / Response 1 Code", HttpStatus.OK_200, response.getStatus());
+        assertTrue("3.6.1 Transfer Codings / Chunked String", response.getContent().contains("12345\n"));
 
         response = responses.get(1); // Response 2
-        response.assertStatusOK("3.6.1 Transfer Codings / Response 2 Code");
-        response.assertBody("3.6.1 Transfer Codings / Chunked String","6789abcde\n");
+        assertEquals("3.6.1 Transfer Codings / Response 2 Code", HttpStatus.OK_200, response.getStatus());
+        assertThat("3.6.1 Transfer Codings / Chunked String",response.getContent(),Matchers.containsString("6789abcde\n"));
 
         response = responses.get(2); // Response 3
-        response.assertStatusOK("3.6.1 Transfer Codings / Response 3 Code");
-        response.assertNoBody("3.6.1 Transfer Codings / No Body");
+        assertEquals("3.6.1 Transfer Codings / Response 3 Code", HttpStatus.OK_200, response.getStatus());
+        assertEquals("3.6.1 Transfer Codings / No Body","",response.getContent());
     }
 
     /**
@@ -261,20 +264,20 @@ public abstract class RFC2616BaseTest
         req3.append("Connection: close\n");
         req3.append("\n");
 
-        responses = http.requests(req3);
+        List<HttpTester.Response> responses = http.requests(req3);
         Assert.assertEquals("Response Count",3,responses.size());
 
-        response = responses.get(0); // Response 1
-        response.assertStatusOK("3.6.1 Transfer Codings / Response 1 Code");
-        response.assertBody("3.6.1 Transfer Codings / Chunked String","fghIjk\n"); // Complete R1 string
+        HttpTester.Response response = responses.get(0); // Response 1
+        assertEquals("3.6.1 Transfer Codings / Response 1 Code", HttpStatus.OK_200, response.getStatus());
+        assertTrue("3.6.1 Transfer Codings / Chunked String", response.getContent().contains("fghIjk\n")); // Complete R1 string
 
         response = responses.get(1); // Response 2
-        response.assertStatusOK("3.6.1 Transfer Codings / Response 2 Code");
-        response.assertBody("3.6.1 Transfer Codings / Chunked String","lmnoPqrst\n"); // Complete R2 string
+        assertEquals("3.6.1 Transfer Codings / Response 2 Code", HttpStatus.OK_200, response.getStatus());
+        assertTrue("3.6.1 Transfer Codings / Chunked String", response.getContent().contains("lmnoPqrst\n")); // Complete R2 string
 
         response = responses.get(2); // Response 3
-        response.assertStatusOK("3.6.1 Transfer Codings / Response 3 Code");
-        response.assertNoBody("3.6.1 Transfer Codings / No Body");
+        assertEquals("3.6.1 Transfer Codings / Response 3 Code", HttpStatus.OK_200, response.getStatus());
+        assertEquals("3.6.1 Transfer Codings / No Body","",response.getContent());
 
     }
 
@@ -305,16 +308,16 @@ public abstract class RFC2616BaseTest
         req4.append("Connection: close\n"); // close
         req4.append("\n");
 
-        responses = http.requests(req4);
+        List<HttpTester.Response> responses = http.requests(req4);
         Assert.assertEquals("Response Count",2,responses.size());
 
-        response = responses.get(0); // Response 1
-        response.assertStatusOK("3.6.1 Transfer Codings / Response 1 Code");
-        response.assertBody("3.6.1 Transfer Codings / Chunked String","123456\n"); // Complete R1 string
+        HttpTester.Response response = responses.get(0); // Response 1
+        assertEquals("3.6.1 Transfer Codings / Response 1 Code", HttpStatus.OK_200, response.getStatus());
+        assertTrue("3.6.1 Transfer Codings / Chunked String", response.getContent().contains("123456\n")); // Complete R1 string
 
         response = responses.get(1); // Response 2
-        response.assertStatusOK("3.6.1 Transfer Codings / Response 2 Code");
-        response.assertNoBody("3.6.1 Transfer Codings / No Body");
+        assertEquals("3.6.1 Transfer Codings / Response 2 Code", HttpStatus.OK_200, response.getStatus());
+        assertEquals("3.6.1 Transfer Codings / No Body","",response.getContent());
     }
 
     /**
@@ -364,15 +367,15 @@ public abstract class RFC2616BaseTest
         req1.append("Connection: close\n");
         req1.append("\n");
 
-        responses = http.requests(req1);
+        List<HttpTester.Response> responses = http.requests(req1);
         Assert.assertEquals("Response Count",2,responses.size());
 
-        response = responses.get(0);
-        response.assertStatusOK("4.4.2 Message Length / Response Code");
-        response.assertBody("4.4.2 Message Length / Body","123\n");
+        HttpTester.Response response = responses.get(0);
+        assertEquals("4.4.2 Message Length / Response Code", HttpStatus.OK_200, response.getStatus());
+        assertThat("4.4.2 Message Length / Body",response.getContent(),Matchers.containsString("123\n"));
         response = responses.get(1);
-        response.assertStatusOK("4.4.2 Message Length / Response Code");
-        response.assertNoBody("4.4.2 Message Length / No Body");
+        assertEquals("4.4.2 Message Length / Response Code", HttpStatus.OK_200, response.getStatus());
+        assertEquals("4.4.2 Message Length / No Body", "",response.getContent());
 
         // 4.4.3 -
         // Client - do not send 'Content-Length' if entity-length
@@ -405,11 +408,11 @@ public abstract class RFC2616BaseTest
         Assert.assertEquals("Response Count",2,responses.size());
 
         response = responses.get(0); // response 1
-        response.assertStatusOK("4.4.3 Ignore Content-Length / Response Code");
-        response.assertBody("4.4.3 Ignore Content-Length / Body","123456\n");
+        assertEquals("4.4.3 Ignore Content-Length / Response Code", HttpStatus.OK_200, response.getStatus());
+        assertTrue("4.4.3 Ignore Content-Length / Body", response.getContent().contains("123456\n"));
         response = responses.get(1); // response 2
-        response.assertStatusOK("4.4.3 Ignore Content-Length / Response Code");
-        response.assertBody("4.4.3 Ignore Content-Length / Body","7890AB\n");
+        assertEquals("4.4.3 Ignore Content-Length / Response Code", HttpStatus.OK_200, response.getStatus());
+        assertTrue("4.4.3 Ignore Content-Length / Body", response.getContent().contains("7890AB\n"));
 
         // 4.4 - Server can request valid Content-Length from client if client
         // fails to provide a Content-Length.
@@ -430,8 +433,8 @@ public abstract class RFC2616BaseTest
 
             response = http.request(req3);
 
-            response.assertStatus("4.4 Valid Content-Length Required",HttpStatus.LENGTH_REQUIRED_411);
-            response.assertNoBody("4.4 Valid Content-Length Required");
+            assertEquals("4.4 Valid Content-Length Required",HttpStatus.LENGTH_REQUIRED_411, response.getStatus());
+            assertTrue("4.4 Valid Content-Length Required", response.getContent() == null);
 
             StringBuffer req4 = new StringBuffer();
             req4.append("GET /echo/R2 HTTP/1.0\n");
@@ -441,8 +444,8 @@ public abstract class RFC2616BaseTest
 
             response = http.request(req4);
 
-            response.assertStatus("4.4 Valid Content-Length Required",HttpStatus.LENGTH_REQUIRED_411);
-            response.assertNoBody("4.4 Valid Content-Length Required");
+            assertEquals("4.4 Valid Content-Length Required",HttpStatus.LENGTH_REQUIRED_411, response.getStatus());
+            assertTrue("4.4 Valid Content-Length Required", response.getContent() == null);
         }
     }
 
@@ -462,10 +465,10 @@ public abstract class RFC2616BaseTest
         req1.append("Connection: close\n");
         req1.append("\r\n");
 
-        response = http.request(req1);
+        HttpTester.Response response = http.request(req1);
 
-        response.assertStatusOK("5.2 Default Host");
-        response.assertBodyContains("5.2 Default Host","Default DOCRoot");
+        assertEquals("5.2 Default Host", HttpStatus.OK_200, response.getStatus());
+        assertThat("5.2 Default Host",response.getContent(),Matchers.containsString("Default DOCRoot"));
     }
 
     /**
@@ -484,10 +487,10 @@ public abstract class RFC2616BaseTest
         req2.append("Connection: close\n");
         req2.append("\r\n");
 
-        response = http.request(req2);
+        HttpTester.Response response = http.request(req2);
 
-        response.assertStatusOK("5.2 Virtual Host");
-        response.assertBodyContains("5.2 Virtual Host","VirtualHost DOCRoot");
+        assertEquals("5.2 Virtual Host", HttpStatus.OK_200, response.getStatus());
+        assertThat("5.2 Virtual Host",response.getContent(),Matchers.containsString("VirtualHost DOCRoot"));
     }
 
     /**
@@ -506,10 +509,10 @@ public abstract class RFC2616BaseTest
         req3.append("Connection: close\n");
         req3.append("\n");
 
-        response = http.request(req3);
+        HttpTester.Response response = http.request(req3);
 
-        response.assertStatusOK("5.2 Virtual Host (mixed case)");
-        response.assertBodyContains("5.2 Virtual Host (mixed case)","VirtualHost DOCRoot");
+        assertEquals("5.2 Virtual Host (mixed case)", HttpStatus.OK_200, response.getStatus());
+        assertThat("5.2 Virtual Host (mixed case)",response.getContent(),Matchers.containsString("VirtualHost DOCRoot"));
     }
 
     /**
@@ -527,10 +530,10 @@ public abstract class RFC2616BaseTest
         req4.append("Connection: close\n");
         req4.append("\n"); // no virtual host
 
-        response = http.request(req4);
+        HttpTester.Response response = http.request(req4);
 
-        response.assertStatus("5.2 No Host",HttpStatus.BAD_REQUEST_400);
-        response.assertNoBody("5.2 No Host");
+        assertEquals("5.2 No Host",HttpStatus.BAD_REQUEST_400,response.getStatus());
+        assertEquals("5.2 No Host","", response.getContent());
     }
 
     /**
@@ -549,10 +552,10 @@ public abstract class RFC2616BaseTest
         req5.append("Connection: close\n");
         req5.append("\n");
 
-        response = http.request(req5);
+        HttpTester.Response response = http.request(req5);
 
-        response.assertStatusOK("5.2 Bad Host");
-        response.assertBodyContains("5.2 Bad Host","Default DOCRoot"); // served by default context
+        assertEquals("5.2 Bad Host",HttpStatus.OK_200, response.getStatus());
+        assertThat("5.2 Bad Host",response.getContent(),Matchers.containsString("Default DOCRoot")); // served by default context
     }
 
     /**
@@ -570,10 +573,10 @@ public abstract class RFC2616BaseTest
         req6.append("Connection: close\n");
         req6.append("\n");
 
-        response = http.request(req6);
+        HttpTester.Response response = http.request(req6);
 
-        // No host header should always return a 400 Bad Request.
-        response.assertStatus("5.2 Virtual Host as AbsoluteURI (No Host Header / HTTP 1.1)",HttpStatus.BAD_REQUEST_400);
+        // No host header should always return a 400 Bad Request by 19.6.1.1
+        assertEquals("5.2 Virtual Host as AbsoluteURI (No Host Header / HTTP 1.1)",HttpStatus.BAD_REQUEST_400,response.getStatus());
     }
 
     /**
@@ -591,10 +594,10 @@ public abstract class RFC2616BaseTest
         req6.append("Connection: close\n");
         req6.append("\n");
 
-        response = http.request(req6);
+        HttpTester.Response response = http.request(req6);
 
-        response.assertStatusOK("5.2 Virtual Host as AbsoluteURI (No Host Header / HTTP 1.0)");
-        response.assertBodyContains("5.2 Virtual Host as AbsoluteURI (No Host Header / HTTP 1.1)","VirtualHost DOCRoot");
+        assertEquals("5.2 Virtual Host as AbsoluteURI (No Host Header / HTTP 1.0)",HttpStatus.OK_200, response.getStatus());
+        assertThat("5.2 Virtual Host as AbsoluteURI (No Host Header / HTTP 1.1)",response.getContent(),Matchers.containsString("VirtualHost DOCRoot"));
     }
 
     /**
@@ -613,10 +616,11 @@ public abstract class RFC2616BaseTest
         req7.append("Connection: close\n");
         req7.append("\n");
 
-        response = http.request(req7);
+        HttpTester.Response response = http.request(req7);
 
-        response.assertStatusOK("5.2 Virtual Host as AbsoluteURI (and Host header)");
-        response.assertBodyContains("5.2 Virtual Host as AbsoluteURI (and Host header)","VirtualHost DOCRoot");
+        assertEquals("5.2 Virtual Host as AbsoluteURI (and Host header)", HttpStatus.OK_200, response.getStatus());
+        // System.err.println(response.getContent());
+        assertThat("5.2 Virtual Host as AbsoluteURI (and Host header)",response.getContent(),Matchers.containsString("VirtualHost DOCRoot"));
     }
 
     /**
@@ -633,11 +637,11 @@ public abstract class RFC2616BaseTest
         req1.append("Connection: close\n");
         req1.append("\n");
 
-        response = http.request(req1);
+        HttpTester.Response response = http.request(req1);
 
-        response.assertStatusOK("8.1 Persistent Connections");
-        response.assertHeaderExists("8.1 Persistent Connections","Content-Length");
-        response.assertBodyContains("8.1 Persistent Connections","Resource=R1");
+        assertEquals("8.1 Persistent Connections", HttpStatus.OK_200, response.getStatus());
+        assertTrue("8.1 Persistent Connections", response.get("Content-Length") != null);
+        assertThat("8.1 Persistent Connections",response.getContent(),Matchers.containsString("Resource=R1"));
 
         StringBuffer req2 = new StringBuffer();
         req2.append("GET /tests/R1.txt HTTP/1.1\n");
@@ -654,19 +658,19 @@ public abstract class RFC2616BaseTest
         req2.append("Connection: close\n");
         req2.append("\n");
 
-        responses = http.requests(req2);
+        List<HttpTester.Response> responses = http.requests(req2);
         Assert.assertEquals("Response Count",2,responses.size()); // Should not have a R3 response.
 
         response = responses.get(0); // response 1
-        response.assertStatusOK("8.1 Persistent Connections");
-        response.assertHeaderExists("8.1 Persistent Connections","Content-Length");
-        response.assertBodyContains("8.1 Peristent Connections","Resource=R1");
+        assertEquals("8.1 Persistent Connections", HttpStatus.OK_200, response.getStatus());
+        assertTrue("8.1 Persistent Connections",response.get("Content-Length") != null);
+        assertTrue("8.1 Peristent Connections", response.getContent().contains("Resource=R1"));
 
         response = responses.get(1); // response 2
-        response.assertStatusOK("8.1.2.2 Persistent Connections / Pipeline");
-        response.assertHeaderExists("8.1.2.2 Persistent Connections / Pipeline","Content-Length");
-        response.assertHeader("8.1.2.2 Persistent Connections / Pipeline","Connection","close");
-        response.assertBodyContains("8.1.2.2 Peristent Connections / Pipeline","Resource=R2");
+        assertEquals("8.1.2.2 Persistent Connections / Pipeline", HttpStatus.OK_200, response.getStatus());
+        assertTrue("8.1.2.2 Persistent Connections / Pipeline", response.get("Content-Length") != null);
+        assertEquals("8.1.2.2 Persistent Connections / Pipeline","close", response.get("Connection"));
+        assertTrue("8.1.2.2 Peristent Connections / Pipeline", response.getContent().contains("Resource=R2"));
     }
 
     /**
@@ -688,9 +692,9 @@ public abstract class RFC2616BaseTest
         req2.append("\n"); 
         req2.append("12345678\n"); 
 
-        response = http.request(req2);
+        HttpTester.Response response = http.request(req2);
 
-        response.assertStatus("8.2.3 expect failure",HttpStatus.EXPECTATION_FAILED_417);
+        assertEquals("8.2.3 expect failure",HttpStatus.EXPECTATION_FAILED_417, response.getStatus());
     }
 
     /**
@@ -715,9 +719,9 @@ public abstract class RFC2616BaseTest
 
         // Should only expect 1 response.
         // The existence of 2 responses usually means a bad "HTTP/1.1 100" was received.
-        response = http.request(req3);
+        HttpTester.Response response = http.request(req3);
 
-        response.assertStatusOK("8.2.3 expect 100");
+        assertEquals("8.2.3 expect 100", HttpStatus.OK_200, response.getStatus());
     }
     
     
@@ -748,19 +752,16 @@ public abstract class RFC2616BaseTest
         req3.append("\n");
         req3.append("87654321"); // Body
 
-        List<HttpResponseTester> responses = http.requests(req3);
+        List<HttpTester.Response> responses = http.requests(req3);
         
         // System.err.println(responses);
         
-        response=responses.get(0);
-        // System.err.println(response.getRawResponse());
-        
-        response.assertStatus("8.2.3 ignored no 100",302);
+        HttpTester.Response response=responses.get(0);
+        // System.err.println(response);
         
-        response=responses.get(1);
-        // System.err.println(response.getRawResponse());
-        response.assertStatus("8.2.3 ignored no 100",200);
-        response.assertBody("87654321\n");
+        assertEquals("8.2.3 ignored no 100",302, response.getStatus());
+        assertEquals("close",response.get("Connection"));
+        assertEquals(1,responses.size());
     }
 
     /**
@@ -788,14 +789,14 @@ public abstract class RFC2616BaseTest
             http.send(sock,req4);
 
             http.setTimeoutMillis(2000);
-            response = http.readAvailable(sock);
-            response.assertStatus("8.2.3 expect 100",HttpStatus.CONTINUE_100);
+            HttpTester.Response response = http.readAvailable(sock);
+            assertEquals("8.2.3 expect 100",HttpStatus.CONTINUE_100,response.getStatus());
 
             http.send(sock,"654321\n"); // Now send the data
             response = http.read(sock);
 
-            response.assertStatusOK("8.2.3 expect 100");
-            response.assertBody("8.2.3 expect 100","654321\n");
+            assertEquals("8.2.3 expect 100", HttpStatus.OK_200, response.getStatus());
+            assertThat("8.2.3 expect 100",response.getContent(),Matchers.containsString("654321\n"));
         }
         finally
         {
@@ -825,20 +826,20 @@ public abstract class RFC2616BaseTest
             req1.append("Host: localhost\n");
             req1.append("\n");
 
-            response = http.request(req1);
+            HttpTester.Response response = http.request(req1);
 
-            response.assertStatusOK("9.2 OPTIONS");
-            response.assertHeaderExists("9.2 OPTIONS","Allow");
+            assertEquals("9.2 OPTIONS", HttpStatus.OK_200, response.getStatus());
+            assertTrue("9.2 OPTIONS",response.get("Allow") != null);
             // Header expected ...
             // Allow: GET, HEAD, POST, PUT, DELETE, MOVE, OPTIONS, TRACE
-            String allow = response.getHeader("Allow");
+            String allow = response.get("Allow");
             String expectedMethods[] =
             { "GET", "HEAD", "POST", "PUT", "DELETE", "MOVE", "OPTIONS", "TRACE" };
             for (String expectedMethod : expectedMethods)
             {
                 assertThat(allow,containsString(expectedMethod));
             }
-            response.assertHeader("9.2 OPTIONS","Content-Length","0"); // Required if no response body.
+            assertEquals("9.2 OPTIONS","0", response.get("Content-Length")); // Required if no response body.
         }
     }
 
@@ -870,15 +871,15 @@ public abstract class RFC2616BaseTest
         req2.append("Connection: close\n"); // Close this second request
         req2.append("\n");
 
-        responses = http.requests(req2);
+        List<HttpTester.Response> responses = http.requests(req2);
 
         Assert.assertEquals("Response Count",2,responses.size()); // Should have 2 responses
 
-        response = responses.get(0); // Only interested in first response
-        response.assertHeaderExists("9.2 OPTIONS","Allow");
+        HttpTester.Response response = responses.get(0); // Only interested in first response
+        assertTrue("9.2 OPTIONS", response.get("Allow") != null);
         // Header expected ...
         // Allow: GET, HEAD, POST, TRACE, OPTIONS
-        String allow = response.getHeader("Allow");
+        String allow = response.get("Allow");
         String expectedMethods[] =
         { "GET", "HEAD", "POST", "OPTIONS", "TRACE" };
         for (String expectedMethod : expectedMethods)
@@ -886,7 +887,7 @@ public abstract class RFC2616BaseTest
             assertThat(allow,containsString(expectedMethod));
         }
 
-        response.assertHeader("9.2 OPTIONS","Content-Length","0"); // Required if no response body.
+        assertEquals("9.2 OPTIONS","0", response.get("Content-Length")); // Required if no response body.
     }
 
     /**
@@ -905,12 +906,12 @@ public abstract class RFC2616BaseTest
         req1.append("Connection: close\n");
         req1.append("\n");
 
-        response = http.request(req1);
+        HttpTester.Response response = http.request(req1);
 
-        response.assertStatusOK("9.4 GET / Response Code");
-        response.assertHeader("9.4 GET / Content Type","Content-Type","text/plain");
-        response.assertHeader("9.4 HEAD / Content Type","Content-Length","25");
-        response.assertBody("9.4 GET / Body","Host=Default\nResource=R1\n");
+        assertEquals("9.4 GET / Response Code", HttpStatus.OK_200, response.getStatus());
+        assertEquals("9.4 GET / Content Type","text/plain", response.get("Content-Type"));
+        assertEquals("9.4 HEAD / Content Type","25", response.get("Content-Length"));
+        assertTrue("9.4 GET / Body", response.getContent().contains("Host=Default\nResource=R1\n"));
 
         /* Test HEAD next. (should have no body) */
 
@@ -930,13 +931,13 @@ public abstract class RFC2616BaseTest
             String rawHeadResponse = http.readRaw(sock);
             int headResponseLength = rawHeadResponse.length();
             // Only interested in the response header from the GET request above.
-            String rawGetResponse = response.getRawResponse().toString().substring(0,headResponseLength);
+            String rawGetResponse = response.toString().substring(0,headResponseLength);
 
             // As there is a possibility that the time between GET and HEAD requests
             // can cross the second mark. (eg: GET at 11:00:00.999 and HEAD at 11:00:01.001)
             // So with that knowledge, we will remove the 'Date:' header from both sides before comparing.
-            List<String> linesGet = StringUtil.asLines(rawGetResponse);
-            List<String> linesHead = StringUtil.asLines(rawHeadResponse);
+            List<String> linesGet = StringUtil.asLines(rawGetResponse.trim());
+            List<String> linesHead = StringUtil.asLines(rawHeadResponse.trim());
 
             StringUtil.removeStartsWith("Date: ",linesGet);
             StringUtil.removeStartsWith("Date: ",linesHead);
@@ -968,12 +969,12 @@ public abstract class RFC2616BaseTest
         req1.append("Connection: close\n");
         req1.append("\n");
 
-        HttpResponseTester response = http.request(req1);
+        HttpTester.Response response = http.request(req1);
 
-        response.assertStatusOK("9.8 TRACE / Response Code");
-        response.assertHeader("9.8 TRACE / Content Type","Content-Type","message/http");
-        response.assertBodyContains("9.8 TRACE / echo","TRACE /rfc2616-webapp/httpmethods HTTP/1.1");
-        response.assertBodyContains("9.8 TRACE / echo","Host: localhost");
+        assertEquals("9.8 TRACE / Response Code", HttpStatus.OK_200, response.getStatus());
+        assertEquals("9.8 TRACE / Content Type", "message/http", response.get("Content-Type"));
+        assertTrue("9.8 TRACE / echo", response.getContent().contains("TRACE /rfc2616-webapp/httpmethods HTTP/1.1"));
+        assertTrue("9.8 TRACE / echo", response.getContent().contains("Host: localhost"));
     }
 
     /**
@@ -996,10 +997,10 @@ public abstract class RFC2616BaseTest
         req1.append("Connection: close\n");
         req1.append("\n");
 
-        response = http.request(req1);
+        HttpTester.Response response = http.request(req1);
 
-        boolean noRangeHasContentLocation = response.hasHeader("Content-Location");
-        boolean noRangeHasETag = response.hasHeader("ETag");
+        boolean noRangeHasContentLocation = (response.get("Content-Location") != null);
+        boolean noRangeHasETag = (response.get("ETag") != null);
 
         // now try again for the same resource but this time WITH range header
 
@@ -1011,8 +1012,10 @@ public abstract class RFC2616BaseTest
         req2.append("\n");
 
         response = http.request(req2);
+        
+        // System.err.println(response);
 
-        response.assertStatus("10.2.7 Partial Content",HttpStatus.PARTIAL_CONTENT_206);
+        assertEquals("10.2.7 Partial Content",HttpStatus.PARTIAL_CONTENT_206, response.getStatus());
 
         // (point 1) A 206 response MUST contain either a Content-Range header
         // field (section 14.16) indicating the range included with this
@@ -1021,28 +1024,28 @@ public abstract class RFC2616BaseTest
         // in the response, its value MUST match the actual number of OCTETs
         // transmitted in the message-body.
 
-        if (response.hasHeader("Content-Range"))
+        if (response.get("Content-Range") != null)
         {
-            response.assertHeader("10.2.7 Partial Content / Response / Content Range","Content-Range","bytes 1-3/27");
+            assertEquals("10.2.7 Partial Content / Response / Content Range","bytes 1-3/27",response.get("Content-Range"));
         }
 
-        if (response.hasHeader("Content-Length"))
+        if (response.get("Content-Length") != null)
         {
-            response.assertHeader("10.2.7 Patial Content / Response / Content Length","Content-Length","3");
+            assertEquals("10.2.7 Patial Content / Response / Content Length","3", response.get("Content-Length"));
         }
 
         // (point 2) A 206 response MUST contain a Date header
-        response.assertHeaderExists("10.2.7 Partial Content / Response / Date","Date");
+        assertTrue("10.2.7 Partial Content / Response / Date", response.get("Date") != null);
 
         // (point 3) A 206 response MUST contain ETag and/or Content-Location,
         // if the header would have been sent in a 200 response to the same request
         if (noRangeHasContentLocation)
         {
-            response.assertHeaderExists("10.2.7 Partial Content / Content-Location","Content-Location");
+            assertTrue("10.2.7 Partial Content / Content-Location", response.get("Content-Location") != null);
         }
         if (noRangeHasETag)
         {
-            response.assertHeaderExists("10.2.7 Partial Content / Content-Location","ETag");
+            assertTrue("10.2.7 Partial Content / Content-Location", response.get("ETag") != null);
         }
 
         // (point 4) A 206 response MUST contain Expires, Cache-Control, and/or Vary,
@@ -1052,7 +1055,7 @@ public abstract class RFC2616BaseTest
         // TODO: Not sure how to test this condition.
 
         // Test the body sent
-        response.assertBody("10.2.7 Partial Content","BCD"); // should only have bytes 1-3
+        assertThat("10.2.7 Partial Content",response.getContent(),Matchers.containsString("BCD")); // should only have bytes 1-3
     }
 
     /**
@@ -1074,11 +1077,11 @@ public abstract class RFC2616BaseTest
         req1.append("Connection: Close\n");
         req1.append("\n");
 
-        response = http.request(req1);
+        HttpTester.Response response = http.request(req1);
 
         specId = "10.3 Redirection HTTP/1.0 - basic";
-        response.assertStatus(specId,HttpStatus.FOUND_302);
-        response.assertHeader(specId,"Location",serverURI + "/tests/");
+        assertEquals(specId,HttpStatus.FOUND_302, response.getStatus());
+        assertEquals(specId,serverURI + "/tests/", response.get("Location"));
     }
 
     /**
@@ -1101,20 +1104,19 @@ public abstract class RFC2616BaseTest
         req2.append("Connection: close\n");
         req2.append("\n");
 
-        responses = http.requests(req2);
+        List<HttpTester.Response> responses = http.requests(req2);
         Assert.assertEquals("Response Count",2,responses.size());
 
-        response = responses.get(0);
+        HttpTester.Response response = responses.get(0);
         String specId = "10.3 Redirection HTTP/1.1 - basic (response 1)";
-        response.assertStatus(specId,HttpStatus.FOUND_302);
-        response.assertHeader(specId,"Location",server.getScheme() + "://localhost/tests/");
+        assertEquals(specId,HttpStatus.FOUND_302, response.getStatus());
+        assertEquals(specId,server.getScheme() + "://localhost/tests/", response.get("Location"));
 
         response = responses.get(1);
         specId = "10.3 Redirection HTTP/1.1 - basic (response 2)";
-        response.assertStatus(specId,HttpStatus.FOUND_302);
-        response.assertHeader(specId,"Location",server.getScheme() + "://localhost/tests/");
-        response.assertHeader(specId,"Connection","close");
-
+        assertEquals(specId,HttpStatus.FOUND_302, response.getStatus());
+        assertEquals(specId,server.getScheme() + "://localhost/tests/", response.get("Location"));
+        assertEquals(specId,"close", response.get("Connection"));
     }
 
     /**
@@ -1133,11 +1135,11 @@ public abstract class RFC2616BaseTest
         req3.append("Connection: close\n");
         req3.append("\n");
 
-        response = http.request(req3);
+        HttpTester.Response response = http.request(req3);
 
         String specId = "10.3 Redirection HTTP/1.0 w/content";
-        response.assertStatus(specId,HttpStatus.FOUND_302);
-        response.assertHeader(specId,"Location",server.getScheme() + "://localhost/tests/R1.txt");
+        assertEquals(specId,HttpStatus.FOUND_302, response.getStatus());
+        assertEquals(specId,server.getScheme() + "://localhost/tests/R1.txt", response.get("Location"));
     }
 
     /**
@@ -1156,13 +1158,13 @@ public abstract class RFC2616BaseTest
         req4.append("Connection: close\n");
         req4.append("\n");
 
-        response = http.request(req4);
+        HttpTester.Response response = http.request(req4);
 
         String specId = "10.3 Redirection HTTP/1.1 w/content";
-        response.assertStatus(specId,HttpStatus.FOUND_302);
-        response.assertHeader(specId,"Location",server.getScheme() + "://localhost/tests/R2.txt");
-        response.assertHeader(specId,"Connection","close");
-        response.assertHeaderNotPresent(specId,"Content-Length");
+        assertEquals(specId,HttpStatus.FOUND_302, response.getStatus());
+        assertEquals(specId,server.getScheme() + "://localhost/tests/R2.txt", response.get("Location"));
+        assertEquals(specId,"close", response.get("Connection"));
+        assertTrue(specId,response.get("Content-Length") == null);
     }
 
     /**
@@ -1184,11 +1186,11 @@ public abstract class RFC2616BaseTest
         req1.append("Connection: close\n");
         req1.append("\n");
 
-        response = http.request(req1);
+        HttpTester.Response response = http.request(req1);
         specId = "14.3 Accept-Encoding Header";
-        response.assertStatusOK(specId);
-        response.assertHeader(specId,"Content-Encoding","gzip");
-        response.assertHeader(specId,"Content-Type","text/html");
+        assertEquals(specId, HttpStatus.OK_200, response.getStatus());
+        assertEquals(specId,"gzip", response.get("Content-Encoding"));
+        assertEquals(specId,"text/html", response.get("Content-Type"));
     }
 
     /**
@@ -1210,10 +1212,10 @@ public abstract class RFC2616BaseTest
         req1.append("Connection: close\n");
         req1.append("\n");
 
-        response = http.request(req1);
+        HttpTester.Response response = http.request(req1);
 
-        response.assertStatusOK();
-        response.assertBody(ALPHA);
+        assertEquals(HttpStatus.OK_200, response.getStatus());
+        assertTrue(response.getContent().contains(ALPHA));
     }
 
 
@@ -1229,12 +1231,12 @@ public abstract class RFC2616BaseTest
         req1.append("Connection: close\n");
         req1.append("\n");
 
-        response = http.request(req1);
+        HttpTester.Response response = http.request(req1);
 
         String msg = "Partial Range: '" + rangedef + "'";
-        response.assertStatus(msg,HttpStatus.PARTIAL_CONTENT_206);
-        response.assertHeader(msg,"Content-Range","bytes " + expectedRange);
-        response.assertBody(msg,expectedBody);
+        assertEquals(msg,HttpStatus.PARTIAL_CONTENT_206, response.getStatus());
+        assertEquals(msg,"bytes " + expectedRange, response.get("Content-Range"));
+        assertTrue(msg,response.getContent().contains(expectedBody));
     }
 
     /**
@@ -1288,12 +1290,12 @@ public abstract class RFC2616BaseTest
         req1.append("\n");
 
         http.setTimeoutMillis(60000);
-        response = http.request(req1);
+        HttpTester.Response response = http.request(req1);
 
         String msg = "Partial Range (Mixed): 'bytes=a-b,5-8'";
-        response.assertStatus(msg,HttpStatus.PARTIAL_CONTENT_206);
-        response.assertHeader(msg,"Content-Range","bytes 5-8/27");
-        response.assertBody(msg,alpha.substring(5,8 + 1));
+        assertEquals(msg,HttpStatus.PARTIAL_CONTENT_206, response.getStatus());
+        assertEquals(msg,"bytes 5-8/27", response.get("Content-Range"));
+        assertTrue(msg,response.getContent().contains(alpha.substring(5,8 + 1)));
     }
 
     /**
@@ -1328,12 +1330,12 @@ public abstract class RFC2616BaseTest
         req1.append("Connection: close\n");
         req1.append("\n");
 
-        response = http.request(req1);
+        HttpTester.Response response = http.request(req1);
 
         String msg = "Partial Range (Mixed): 'bytes=a-b,bytes=5-8'";
-        response.assertStatus(msg,HttpStatus.PARTIAL_CONTENT_206);
-        response.assertHeader(msg,"Content-Range","bytes 5-8/27");
-        response.assertBody(msg,alpha.substring(5,8 + 1));
+        assertEquals(msg,HttpStatus.PARTIAL_CONTENT_206, response.getStatus());
+        assertEquals(msg,"bytes 5-8/27", response.get("Content-Range"));
+        assertTrue(msg,response.getContent().contains(alpha.substring(5,8 + 1)));
     }
 
     /**
@@ -1369,12 +1371,12 @@ public abstract class RFC2616BaseTest
         req1.append("Connection: close\n");
         req1.append("\n");
 
-        response = http.request(req1);
+        HttpTester.Response response = http.request(req1);
 
         String msg = "Partial Range (Mixed): 'bytes=a-b' 'bytes=5-8'";
-        response.assertStatus(msg,HttpStatus.PARTIAL_CONTENT_206);
-        response.assertHeader(msg,"Content-Range","bytes 5-8/27");
-        response.assertBody(msg,alpha.substring(5,8 + 1));
+        assertEquals(msg,HttpStatus.PARTIAL_CONTENT_206, response.getStatus());
+        assertEquals(msg,"bytes 5-8/27", response.get("Content-Range"));
+        assertTrue(msg,response.getContent().contains(alpha.substring(5,8 + 1)));
     }
 
     /**
@@ -1385,8 +1387,6 @@ public abstract class RFC2616BaseTest
     @Test
     public void test14_23_Http10_NoHostHeader() throws Exception
     {
-        HttpResponseTester response;
-
         // HTTP/1.0 OK with no host
 
         StringBuffer req1 = new StringBuffer();
@@ -1394,8 +1394,8 @@ public abstract class RFC2616BaseTest
         req1.append("Connection: close\n");
         req1.append("\n");
 
-        response = http.request(req1);
-        response.assertStatusOK("14.23 HTTP/1.0 - No Host");
+        HttpTester.Response response = http.request(req1);
+        assertEquals("14.23 HTTP/1.0 - No Host", HttpStatus.OK_200, response.getStatus());
     }
 
     /**
@@ -1413,8 +1413,8 @@ public abstract class RFC2616BaseTest
         req2.append("Connection: close\n");
         req2.append("\n");
 
-        response = http.request(req2);
-        response.assertStatus("14.23 HTTP/1.1 - No Host",HttpStatus.BAD_REQUEST_400);
+        HttpTester.Response response = http.request(req2);
+        assertEquals("14.23 HTTP/1.1 - No Host",HttpStatus.BAD_REQUEST_400, response.getStatus());
     }
 
     /**
@@ -1433,8 +1433,8 @@ public abstract class RFC2616BaseTest
         req3.append("Connection: close\n");
         req3.append("\n");
 
-        response = http.request(req3);
-        response.assertStatusOK("14.23 HTTP/1.1 - Valid Host");
+        HttpTester.Response response = http.request(req3);
+        assertEquals("14.23 HTTP/1.1 - Valid Host", HttpStatus.OK_200, response.getStatus());
     }
 
     /**
@@ -1453,8 +1453,8 @@ public abstract class RFC2616BaseTest
         req4.append("Connection: close\n");
         req4.append("\n");
 
-        response = http.request(req4);
-        response.assertStatusOK("14.23 HTTP/1.1 - Empty Host");
+        HttpTester.Response response = http.request(req4);
+        assertEquals("14.23 HTTP/1.1 - Empty Host", HttpStatus.BAD_REQUEST_400, response.getStatus());
     }
 
     /**
@@ -1476,14 +1476,14 @@ public abstract class RFC2616BaseTest
         req1.append("Connection: close\n");
         req1.append("\n");
 
-        response = http.request(req1);
+        HttpTester.Response response = http.request(req1);
 
         String msg = "Partial (Byte) Range: '" + rangedef + "'";
-        response.assertStatus(msg,HttpStatus.PARTIAL_CONTENT_206);
+        assertEquals(msg,HttpStatus.PARTIAL_CONTENT_206, response.getStatus());
         // It might be strange to see a "Content-Range' response header to a 'Range' request,
         // but this is appropriate per the RFC2616 spec.
-        response.assertHeader(msg,"Content-Range","bytes " + expectedRange);
-        response.assertBody(msg,expectedBody);
+        assertEquals(msg,"bytes " + expectedRange, response.get("Content-Range"));
+        assertTrue(msg,response.getContent().contains(expectedBody));
     }
 
     /**
@@ -1532,12 +1532,12 @@ public abstract class RFC2616BaseTest
         req1.append("Connection: close\n");
         req1.append("\n");
 
-        response = http.request(req1);
-
+        HttpTester.Response response = http.request(req1);
+        
         String msg = "Partial (Byte) Range: '" + rangedef + "'";
-        response.assertStatus(msg,HttpStatus.PARTIAL_CONTENT_206);
+        assertEquals(msg,HttpStatus.PARTIAL_CONTENT_206, response.getStatus());
 
-        String contentType = response.getHeader("Content-Type");
+        String contentType = response.get("Content-Type");
         // RFC states that multiple parts should result in multipart/byteranges Content type.
         StringAssert.assertContains(msg + " Content-Type",contentType,"multipart/byteranges");
 
@@ -1556,21 +1556,20 @@ public abstract class RFC2616BaseTest
 
         Assert.assertNotNull(msg + " Should have found boundary in Content-Type header",boundary);
 
-        // Find boundary offsets within body
-        List<HttpResponseTester> multiparts = response.findBodyMultiparts(boundary);
-        Assert.assertEquals(msg + " multiparts in body (count)",2,multiparts.size());
-
-        // Validate multipart #1
-        HttpResponseTester multipart1 = multiparts.get(0);
-        multipart1.assertHeader(msg + " Multipart 1","Content-Type","text/plain");
-        multipart1.assertHeader(msg + " Multipart 1","Content-Range","bytes 23-23/27");
-        multipart1.assertBody(msg + " Multipart 1","X");
-
-        // Validate multipart #2
-        HttpResponseTester multipart2 = multiparts.get(1);
-        multipart2.assertHeader(msg + " Multipart 2","Content-Type","text/plain");
-        multipart2.assertHeader(msg + " Multipart 2","Content-Range","bytes 25-26/27");
-        multipart2.assertBody(msg + " Multipart 2","Z\n");
+        List<String> lines = StringUtil.asLines(response.getContent().trim());
+        int i=0;
+        assertEquals("--"+boundary,lines.get(i++));
+        assertEquals("Content-Type: text/plain",lines.get(i++));
+        assertEquals("Content-Range: bytes 23-23/27",lines.get(i++));
+        assertEquals("",lines.get(i++));
+        assertEquals("X",lines.get(i++));
+        assertEquals("--"+boundary,lines.get(i++));
+        assertEquals("Content-Type: text/plain",lines.get(i++));
+        assertEquals("Content-Range: bytes 25-26/27",lines.get(i++));
+        assertEquals("",lines.get(i++));
+        assertEquals("Z",lines.get(i++));
+        assertEquals("",lines.get(i++));
+        assertEquals("--"+boundary+"--",lines.get(i++));
     }
 
     /**
@@ -1593,12 +1592,13 @@ public abstract class RFC2616BaseTest
         req1.append("Connection: close\n");
         req1.append("\n");
 
-        response = http.request(req1);
+        HttpTester.Response response = http.request(req1);
+        // System.err.println(response+response.getContent());
 
         String msg = "Partial (Byte) Range: '" + rangedef + "'";
-        response.assertStatus(msg,HttpStatus.PARTIAL_CONTENT_206);
+        assertEquals(msg,HttpStatus.PARTIAL_CONTENT_206,response.getStatus());
 
-        String contentType = response.getHeader("Content-Type");
+        String contentType = response.get("Content-Type");
         // RFC states that multiple parts should result in multipart/byteranges Content type.
         StringAssert.assertContains(msg + " Content-Type",contentType,"multipart/byteranges");
 
@@ -1617,27 +1617,29 @@ public abstract class RFC2616BaseTest
 
         Assert.assertNotNull(msg + " Should have found boundary in Content-Type header",boundary);
 
-        // Find boundary offsets within body
-        List<HttpResponseTester> multiparts = response.findBodyMultiparts(boundary);
-        Assert.assertEquals(msg + " multiparts in body (count)",3,multiparts.size());
-
-        // Validate multipart #1
-        HttpResponseTester multipart1 = multiparts.get(0);
-        multipart1.assertHeader(msg + " Multipart 1","Content-Type","text/plain");
-        multipart1.assertHeader(msg + " Multipart 1","Content-Range","bytes 26-26/27");
-        multipart1.assertBody(msg + " Multipart 1","\n");
-
-        // Validate multipart #2
-        HttpResponseTester multipart2 = multiparts.get(1);
-        multipart2.assertHeader(msg + " Multipart 2","Content-Type","text/plain");
-        multipart2.assertHeader(msg + " Multipart 2","Content-Range","bytes 25-26/27");
-        multipart2.assertBody(msg + " Multipart 2","Z\n");
-
-        // Validate multipart #3
-        HttpResponseTester multipart3 = multiparts.get(2);
-        multipart3.assertHeader(msg + " Multipart 3","Content-Type","text/plain");
-        multipart3.assertHeader(msg + " Multipart 3","Content-Range","bytes 24-26/27");
-        multipart3.assertBody(msg + " Multipart 3","YZ\n");
+
+        List<String> lines = StringUtil.asLines(response.getContent().trim());
+        int i=0;
+        assertEquals("--"+boundary,lines.get(i++));
+        assertEquals("Content-Type: text/plain",lines.get(i++));
+        assertEquals("Content-Range: bytes 26-26/27",lines.get(i++));
+        assertEquals("",lines.get(i++));
+        assertEquals("",lines.get(i++));
+        assertEquals("",lines.get(i++));
+        assertEquals("--"+boundary,lines.get(i++));
+        assertEquals("Content-Type: text/plain",lines.get(i++));
+        assertEquals("Content-Range: bytes 25-26/27",lines.get(i++));
+        assertEquals("",lines.get(i++));
+        assertEquals("Z",lines.get(i++));
+        assertEquals("",lines.get(i++));
+        assertEquals("--"+boundary,lines.get(i++));
+        assertEquals("Content-Type: text/plain",lines.get(i++));
+        assertEquals("Content-Range: bytes 24-26/27",lines.get(i++));
+        assertEquals("",lines.get(i++));
+        assertEquals("YZ",lines.get(i++));
+        assertEquals("",lines.get(i++));
+        assertEquals("--"+boundary+"--",lines.get(i++));
+        
     }
 
     /**
@@ -1671,9 +1673,9 @@ public abstract class RFC2616BaseTest
         req1.append("Connection: close\n");
         req1.append("\n");
 
-        response = http.request(req1);
+        HttpTester.Response response = http.request(req1);
 
-        response.assertStatus("BadByteRange: '" + rangedef + "'",HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE_416);
+        assertEquals("BadByteRange: '" + rangedef + "'",HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE_416, response.getStatus());
     }
 
     /**
@@ -1714,10 +1716,10 @@ public abstract class RFC2616BaseTest
             req1.append("Connection: close\n");
             req1.append("\n");
 
-            response = http.request(req1);
+            HttpTester.Response response = http.request(req1);
             specId = "14.39 TE Header";
-            response.assertStatusOK(specId);
-            response.assertHeader(specId,"Transfer-Encoding","gzip");
+            assertEquals(specId, HttpStatus.OK_200, response.getStatus());
+            assertEquals(specId,"gzip", response.get("Transfer-Encoding"));
         }
     }
 
@@ -1741,9 +1743,9 @@ public abstract class RFC2616BaseTest
             req2.append("Connection: close\n");
             req2.append("\n");
 
-            response = http.request(req2);
+            HttpTester.Response response = http.request(req2);
             specId = "14.39 TE Header";
-            response.assertStatus(specId,HttpStatus.NOT_IMPLEMENTED_501); // Error on TE (deflate not supported)
+            assertEquals(specId,HttpStatus.NOT_IMPLEMENTED_501, response.getStatus()); // Error on TE (deflate not supported)
         }
     }
 
@@ -1755,8 +1757,7 @@ public abstract class RFC2616BaseTest
     @Test
     public void test19_6() throws Exception
     {
-        List<HttpResponseTester> responses;
-        HttpResponseTester response;
+    
         String specId;
 
         /* Compatibility with HTTP/1.0 */
@@ -1765,10 +1766,10 @@ public abstract class RFC2616BaseTest
         req1.append("GET /tests/R1.txt HTTP/1.0\n");
         req1.append("\n");
 
-        response = http.request(req1);
+        HttpTester.Response response = http.request(req1);
         specId = "19.6 Compatibility with HTTP/1.0 - simple request";
-        response.assertStatusOK(specId);
-        response.assertHeaderNotPresent(specId + " - connection closed not assumed","Connection");
+        assertEquals(specId, HttpStatus.OK_200, response.getStatus());
+        assertTrue(specId + " - connection closed not assumed",response.get("Connection") == null);
 
         /* Compatibility with HTTP/1.0 */
 
@@ -1783,26 +1784,25 @@ public abstract class RFC2616BaseTest
         req2.append("Connection: close\n"); // Connection closed here
         req2.append("\n");
 
-        req2.append("GET /tests/R3.txt HTTP/1.0\n"); // This request should not
-        // be handled
+        req2.append("GET /tests/R3.txt HTTP/1.0\n"); // This request should not be handled
         req2.append("Host: localhost\n");
         req2.append("Connection: close\n");
         req2.append("\n");
 
-        responses = http.requests(req2);
+        List<HttpTester.Response> responses = http.requests(req2);
         // Since R2 closes the connection, should only get 2 responses (R1 &
         // R2), not (R3)
         Assert.assertEquals("Response Count",2,responses.size());
 
         response = responses.get(0); // response 1
         specId = "19.6.2 Compatibility with previous HTTP - Keep-alive";
-        response.assertStatusOK(specId);
-        response.assertHeader(specId,"Connection","keep-alive");
-        response.assertBodyContains(specId,"Resource=R1");
+        assertEquals(specId, HttpStatus.OK_200, response.getStatus());
+        assertEquals(specId,"keep-alive", response.get("Connection"));
+        assertTrue(specId,response.getContent().contains("Resource=R1"));
 
         response = responses.get(1); // response 2
-        response.assertStatusOK(specId);
-        response.assertBodyContains(specId,"Resource=R2");
+        assertEquals(specId, HttpStatus.OK_200, response.getStatus());
+        assertTrue(specId,response.getContent().contains("Resource=R2"));
 
         /* Compatibility with HTTP/1.0 */
 
@@ -1836,18 +1836,18 @@ public abstract class RFC2616BaseTest
 
         specId = "19.6.2 Compatibility with HTTP/1.0- Keep-alive";
         response = responses.get(0);
-        response.assertStatusOK(specId);
-        response.assertHeader(specId,"Connection","keep-alive");
-        response.assertBody(specId,"1234567890\n");
+        assertEquals(specId, HttpStatus.OK_200, response.getStatus());
+        assertEquals(specId,"keep-alive", response.get("Connection"));
+        assertTrue(specId, response.getContent().contains("1234567890\n"));
 
         response = responses.get(1);
-        response.assertStatusOK(specId);
-        response.assertHeader(specId,"Connection","keep-alive");
-        response.assertBody(specId,"ABCDEFGHIJ\n");
+        assertEquals(specId, HttpStatus.OK_200, response.getStatus());
+        assertEquals(specId, "keep-alive", response.get("Connection"));
+        assertTrue(specId,response.getContent().contains("ABCDEFGHIJ\n"));
 
         response = responses.get(2);
-        response.assertStatusOK(specId);
-        response.assertBody(specId,"Host=Default\nResource=R2\n");
+        assertEquals(specId, HttpStatus.OK_200, response.getStatus());
+        assertTrue(specId,response.getContent().contains("Host=Default\nResource=R2\n"));
     }
 
     protected void assertDate(String msg, Calendar expectedTime, long actualTime)
diff --git a/tests/test-integration/src/test/java/org/eclipse/jetty/test/rfcs/RFC2616NIOHttpTest.java b/tests/test-integration/src/test/java/org/eclipse/jetty/test/rfcs/RFC2616NIOHttpTest.java
index e532f6a..bb9af0d 100644
--- a/tests/test-integration/src/test/java/org/eclipse/jetty/test/rfcs/RFC2616NIOHttpTest.java
+++ b/tests/test-integration/src/test/java/org/eclipse/jetty/test/rfcs/RFC2616NIOHttpTest.java
@@ -18,7 +18,7 @@
 
 package org.eclipse.jetty.test.rfcs;
 
-import org.eclipse.jetty.http.HttpSchemes;
+import org.eclipse.jetty.http.HttpScheme;
 import org.eclipse.jetty.test.support.TestableJettyServer;
 import org.eclipse.jetty.test.support.rawhttp.HttpSocket;
 import org.eclipse.jetty.test.support.rawhttp.HttpSocketImpl;
@@ -33,7 +33,7 @@ public class RFC2616NIOHttpTest extends RFC2616BaseTest
     public static void setupServer() throws Exception
     {
         TestableJettyServer server = new TestableJettyServer();
-        server.setScheme(HttpSchemes.HTTP);
+        server.setScheme(HttpScheme.HTTP.asString());
         server.addConfiguration("RFC2616Base.xml");
         server.addConfiguration("RFC2616_Redirects.xml");
         server.addConfiguration("RFC2616_Filters.xml");
diff --git a/tests/test-integration/src/test/java/org/eclipse/jetty/test/rfcs/RFC2616NIOHttpsTest.java b/tests/test-integration/src/test/java/org/eclipse/jetty/test/rfcs/RFC2616NIOHttpsTest.java
index dba0103..aecd0cc 100644
--- a/tests/test-integration/src/test/java/org/eclipse/jetty/test/rfcs/RFC2616NIOHttpsTest.java
+++ b/tests/test-integration/src/test/java/org/eclipse/jetty/test/rfcs/RFC2616NIOHttpsTest.java
@@ -18,22 +18,25 @@
 
 package org.eclipse.jetty.test.rfcs;
 
-import org.eclipse.jetty.http.HttpSchemes;
+import org.eclipse.jetty.http.HttpScheme;
 import org.eclipse.jetty.test.support.TestableJettyServer;
 import org.eclipse.jetty.test.support.rawhttp.HttpSocket;
 import org.eclipse.jetty.test.support.rawhttp.HttpsSocketImpl;
 import org.junit.BeforeClass;
+import org.junit.Ignore;
 
 /**
  * Perform the RFC2616 tests against a server running with the Jetty NIO Connector and listening on HTTPS (HTTP over SSL).
+ * TODO
  */
+ at Ignore ("TODO")
 public class RFC2616NIOHttpsTest extends RFC2616BaseTest
 {
     @BeforeClass
     public static void setupServer() throws Exception
     {
         TestableJettyServer server = new TestableJettyServer();
-        server.setScheme(HttpSchemes.HTTPS);
+        server.setScheme(HttpScheme.HTTPS.asString());
         server.addConfiguration("RFC2616Base.xml");
         server.addConfiguration("RFC2616_Redirects.xml");
         server.addConfiguration("RFC2616_Filters.xml");
diff --git a/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/TestableJettyServer.java b/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/TestableJettyServer.java
index 2d926b5..43876e8 100644
--- a/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/TestableJettyServer.java
+++ b/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/TestableJettyServer.java
@@ -32,8 +32,9 @@ import java.util.List;
 import java.util.Map;
 import java.util.Properties;
 
-import org.eclipse.jetty.http.HttpSchemes;
+import org.eclipse.jetty.http.HttpScheme;
 import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.NetworkConnector;
 import org.eclipse.jetty.server.Server;
 import org.eclipse.jetty.xml.XmlConfiguration;
 import org.junit.Assert;
@@ -49,7 +50,7 @@ public class TestableJettyServer
     private final Map<String,String> _properties = new HashMap<String, String>();
     private Server _server;
     private int _serverPort;
-    private String _scheme = HttpSchemes.HTTP;
+    private String _scheme = HttpScheme.HTTP.asString();
 
     /* Popular Directories */
     private File baseDir;
@@ -119,6 +120,7 @@ public class TestableJettyServer
         for (int i = 0; i < this._xmlConfigurations.size(); i++)
         {
             URL configURL = this._xmlConfigurations.get(i);
+            System.err.println("configuring: "+configURL);
             XmlConfiguration configuration = new XmlConfiguration(configURL);
             if (last != null)
             {
@@ -154,7 +156,7 @@ public class TestableJettyServer
         Assert.assertEquals("Server load count",1,serverCount);
 
         this._server = foundServer;
-        this._server.setGracefulShutdown(10);
+        this._server.setStopTimeout(1000);
         
     }
 
@@ -175,18 +177,8 @@ public class TestableJettyServer
         _server.start();
 
         // Find the active server port.
-        this._serverPort = (-1);
-        Connector connectors[] = _server.getConnectors();
-        for (int i = 0; i < connectors.length; i++)
-        {
-            Connector connector = connectors[i];
-            if (connector.getLocalPort() > 0)
-            {
-                this._serverPort = connector.getLocalPort();
-                break;
-            }
-        }
-
+        this._serverPort = ((NetworkConnector)_server.getConnectors()[0]).getLocalPort();
+        System.err.println("Server Port="+_serverPort);
         Assert.assertTrue("Server Port is between 1 and 65535. Actually <" + _serverPort + ">",(1 <= this._serverPort) && (this._serverPort <= 65535));
     }
 
@@ -208,4 +200,9 @@ public class TestableJettyServer
         uri.append(":").append(this._serverPort);
         return URI.create(uri.toString());
     }
+
+    public Server getServer()
+    {
+        return _server;
+    }
 }
diff --git a/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/rawhttp/HttpRequestTester.java b/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/rawhttp/HttpRequestTester.java
deleted file mode 100644
index df76045..0000000
--- a/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/rawhttp/HttpRequestTester.java
+++ /dev/null
@@ -1,228 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.test.support.rawhttp;
-
-import java.io.IOException;
-
-import javax.servlet.http.Cookie;
-
-import org.eclipse.jetty.http.HttpFields;
-import org.eclipse.jetty.http.HttpGenerator;
-import org.eclipse.jetty.http.HttpHeaders;
-import org.eclipse.jetty.http.HttpVersions;
-import org.eclipse.jetty.http.MimeTypes;
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.ByteArrayBuffer;
-import org.eclipse.jetty.io.SimpleBuffers;
-import org.eclipse.jetty.io.View;
-import org.eclipse.jetty.io.bio.StringEndPoint;
-
-/**
- * Assist in Generating Proper Raw HTTP Requests. If you want ultimate control
- * over the Raw HTTP Request, to test non-standard behavior, or partial HTTP
- * Requests, do not use this class.
- * 
- * <pre>
- * HttpRequestTester request = new HttpRequestTester();
- * 
- * request.setMethod("GET");
- * request.setURI("/uri");
- * request.setHost("fakehost");
- * request.setConnectionClosed();
- * 
- * String rawRequest = request.generate();
- * 
- * System.out.println("--raw-request--\n" + rawRequest);
- * </pre>
- * 
- * <pre>
- * --raw-request--
- * GET /uri HTTP/1.1
- * Host: fakehost
- * Connection: close
- * </pre>
- */
-public class HttpRequestTester
-{
-    private HttpFields fields = new HttpFields();
-    private String method;
-    private String uri;
-    private String version;
-    private byte[] content;
-    private String charset;
-    private String defaultCharset;
-    private String contentType;
-
-    public HttpRequestTester()
-    {
-        this("UTF-8");
-    }
-
-    public HttpRequestTester(String defCharset)
-    {
-        this.defaultCharset = defCharset;
-    }
-
-    public String getMethod()
-    {
-        return method;
-    }
-
-    public void setHost(String host)
-    {
-        addHeader("Host",host);
-    }
-
-    public void setMethod(String method)
-    {
-        this.method = method;
-    }
-
-    public String getURI()
-    {
-        return uri;
-    }
-
-    public void setURI(String uri)
-    {
-        this.uri = uri;
-    }
-
-    public String getVersion()
-    {
-        return version;
-    }
-
-    public void setVersion(String version)
-    {
-        this.version = version;
-    }
-
-    public String getCharset()
-    {
-        return charset;
-    }
-
-    public void setCharset(String charset)
-    {
-        this.charset = charset;
-    }
-
-    public String getContentType()
-    {
-        return contentType;
-    }
-
-    public void setContentType(String contentType)
-    {
-        this.contentType = contentType;
-    }
-
-    public void setConnectionClosed()
-    {
-        fields.add("Connection","close");
-    }
-
-    /**
-     * @param name
-     * @param value
-     * @throws IllegalArgumentException
-     * @see org.eclipse.jetty.http.HttpFields#add(java.lang.String,
-     *      java.lang.String)
-     */
-    public void addHeader(String name, String value) throws IllegalArgumentException
-    {
-        fields.add(name,value);
-    }
-
-    /**
-     * @param name
-     * @param date
-     * @see org.eclipse.jetty.http.HttpFields#addDateField(java.lang.String,
-     *      long)
-     */
-    public void addDateHeader(String name, long date)
-    {
-        fields.addDateField(name,date);
-    }
-
-    /**
-     * @param name
-     * @param value
-     * @see org.eclipse.jetty.http.HttpFields#addLongField(java.lang.String,
-     *      long)
-     */
-    public void addLongHeader(String name, long value)
-    {
-        fields.addLongField(name,value);
-    }
-
-    /**
-     * @param cookie
-     * @see org.eclipse.jetty.http.HttpFields#addSetCookie(org.eclipse.jetty.http.HttpCookie)
-     */
-    public void addSetCookie(Cookie cookie)
-    {
-        fields.addSetCookie(cookie.getName(),cookie.getValue(),cookie.getDomain(),cookie.getPath(),cookie.getMaxAge(),cookie.getComment(),cookie.getSecure(),
-                false,cookie.getVersion());
-    }
-
-    public String generate() throws IOException
-    {
-        charset = defaultCharset;
-        Buffer contentTypeBuffer = fields.get(HttpHeaders.CONTENT_TYPE_BUFFER);
-        if (contentTypeBuffer != null)
-        {
-            String calcCharset = MimeTypes.getCharsetFromContentType(contentTypeBuffer);
-            if (calcCharset != null)
-            {
-                this.charset = calcCharset;
-            }
-        }
-
-        Buffer bb = new ByteArrayBuffer(32 * 1024 + (content != null?content.length:0));
-        Buffer sb = new ByteArrayBuffer(4 * 1024);
-        StringEndPoint endp = new StringEndPoint(charset);
-        HttpGenerator generator = new HttpGenerator(new SimpleBuffers(sb,bb),endp);
-
-        if (method != null)
-        {
-            generator.setRequest(getMethod(),getURI());
-            if (version == null)
-            {
-                generator.setVersion(HttpVersions.HTTP_1_1_ORDINAL);
-            }
-            else
-            {
-                generator.setVersion(HttpVersions.CACHE.getOrdinal(HttpVersions.CACHE.lookup(version)));
-            }
-
-            generator.completeHeader(fields,false);
-
-            if (content != null)
-            {
-                generator.addContent(new View(new ByteArrayBuffer(content)),false);
-            }
-        }
-
-        generator.complete();
-        generator.flushBuffer();
-        return endp.getOutput();
-    }
-}
diff --git a/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/rawhttp/HttpRequestTesterTest.java b/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/rawhttp/HttpRequestTesterTest.java
index 931d990..cd704c6 100644
--- a/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/rawhttp/HttpRequestTesterTest.java
+++ b/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/rawhttp/HttpRequestTesterTest.java
@@ -19,7 +19,11 @@
 package org.eclipse.jetty.test.support.rawhttp;
 
 import java.io.IOException;
+import java.nio.ByteBuffer;
 
+import org.eclipse.jetty.http.HttpTester;
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.util.BufferUtil;
 import org.junit.Assert;
 import org.junit.Test;
 
@@ -28,42 +32,45 @@ public class HttpRequestTesterTest
     @Test
     public void testBasicHttp10Request() throws IOException
     {
-        HttpRequestTester request = new HttpRequestTester();
+        HttpTester.Request request = HttpTester.newRequest();
         request.setMethod("GET");
         request.setURI("/uri");
         request.setVersion("HTTP/1.0");
-        request.setHost("fakehost");
+        request.put("Host","fakehost");
 
-        String rawRequest = request.generate();
+        ByteBuffer bBuff = request.generate();
 
         StringBuffer expectedRequest = new StringBuffer();
         expectedRequest.append("GET /uri HTTP/1.0\r\n");
         expectedRequest.append("Host: fakehost\r\n");
         expectedRequest.append("\r\n");
 
-        Assert.assertEquals("Basic Request",expectedRequest.toString(),rawRequest);
+        Assert.assertEquals("Basic Request",expectedRequest.toString(),BufferUtil.toString(bBuff));
     }
 
     @Test
     public void testBasicHttp11Request() throws IOException
     {
-        HttpRequestTester request = new HttpRequestTester();
+        HttpTester.Request request = HttpTester.newRequest();
         request.setMethod("GET");
+        request.setVersion(HttpVersion.HTTP_1_1);
         request.setURI("/uri");
-        request.setHost("fakehost");
-        request.setConnectionClosed();
+        request.put("Host","fakehost");
+        request.put("Connection", "close");
+        request.setContent("aaa");
+       
 
-        String rawRequest = request.generate();
+        ByteBuffer bBuff = request.generate();
 
         StringBuffer expectedRequest = new StringBuffer();
         expectedRequest.append("GET /uri HTTP/1.1\r\n");
         expectedRequest.append("Host: fakehost\r\n");
         expectedRequest.append("Connection: close\r\n");
-        expectedRequest.append("Transfer-Encoding: chunked\r\n");
-        expectedRequest.append("\r\n");
-        expectedRequest.append("0\r\n");
+        expectedRequest.append("Content-Length: 3\r\n");
         expectedRequest.append("\r\n");
+        expectedRequest.append("aaa");
+       
 
-        Assert.assertEquals("Basic Request",expectedRequest.toString(),rawRequest);
+        Assert.assertEquals("Basic Request",expectedRequest.toString(),BufferUtil.toString(bBuff));
     }
 }
diff --git a/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/rawhttp/HttpResponseTester.java b/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/rawhttp/HttpResponseTester.java
deleted file mode 100644
index 8f19417..0000000
--- a/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/rawhttp/HttpResponseTester.java
+++ /dev/null
@@ -1,461 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.test.support.rawhttp;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.StringReader;
-import java.util.ArrayList;
-import java.util.List;
-
-import junit.framework.Assert;
-
-import org.eclipse.jetty.http.HttpFields;
-import org.eclipse.jetty.http.HttpHeaders;
-import org.eclipse.jetty.http.HttpParser;
-import org.eclipse.jetty.http.HttpStatus;
-import org.eclipse.jetty.http.MimeTypes;
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.ByteArrayBuffer;
-import org.eclipse.jetty.io.View;
-import org.eclipse.jetty.test.support.StringUtil;
-import org.eclipse.jetty.toolchain.test.StringAssert;
-import org.eclipse.jetty.util.ByteArrayOutputStream2;
-
-/**
- * Assists in testing of HTTP Responses.
- */
-public class HttpResponseTester
-{
-    private class PH extends HttpParser.EventHandler
-    {
-        @Override
-        public void content(Buffer ref) throws IOException
-        {
-            if (content == null)
-                content = new ByteArrayOutputStream2();
-            content.write(ref.asArray());
-        }
-
-        @Override
-        public void headerComplete() throws IOException
-        {
-            contentType = fields.get(HttpHeaders.CONTENT_TYPE_BUFFER);
-            if (contentType != null)
-            {
-                String calcCharset = MimeTypes.getCharsetFromContentType(contentType);
-                if (calcCharset != null)
-                {
-                    charset = calcCharset;
-                }
-            }
-        }
-
-        @Override
-        public void messageComplete(long contextLength) throws IOException
-        {
-        }
-
-        @Override
-        public void parsedHeader(Buffer name, Buffer value) throws IOException
-        {
-            fields.add(name,value);
-        }
-
-        @Override
-        public void startRequest(Buffer method, Buffer url, Buffer version) throws IOException
-        {
-            reset();
-            HttpResponseTester.this.method = getString(method);
-            HttpResponseTester.this.uri = getString(url);
-            HttpResponseTester.this.version = getString(version);
-        }
-
-        @Override
-        public void startResponse(Buffer version, int status, Buffer reason) throws IOException
-        {
-            reset();
-            HttpResponseTester.this.version = getString(version);
-            HttpResponseTester.this.status = status;
-            HttpResponseTester.this.reason = getString(reason);
-        }
-    }
-
-    public static List<HttpResponseTester> parseMulti(CharSequence rawHTTP) throws IOException
-    {
-        List<HttpResponseTester> responses = new ArrayList<HttpResponseTester>();
-        String parse = rawHTTP.toString();
-        while (StringUtil.isNotBlank(parse))
-        {
-            HttpResponseTester response = new HttpResponseTester();
-            parse = response.parse(parse);
-            responses.add(response);
-        }
-
-        return responses;
-    }
-
-    private HttpFields fields = new HttpFields();
-    private CharSequence rawResponse;
-    private String method;
-    private String uri;
-    private String version;
-    private int status;
-    private String reason;
-    private Buffer contentType;
-    private ByteArrayOutputStream2 content;
-    private String charset;
-    private String defaultCharset;
-
-    public HttpResponseTester()
-    {
-        this("UTF-8");
-    }
-
-    public HttpResponseTester(String defCharset)
-    {
-        this.defaultCharset = defCharset;
-    }
-
-    public String getMethod()
-    {
-        return method;
-    }
-
-    public String getURI()
-    {
-        return uri;
-    }
-
-    public String getVersion()
-    {
-        return version;
-    }
-
-    public int getStatus()
-    {
-        return status;
-    }
-
-    public CharSequence getRawResponse()
-    {
-        return rawResponse;
-    }
-
-    public String getReason()
-    {
-        return reason;
-    }
-
-    public String getContentType()
-    {
-        if (contentType == null)
-        {
-            return null;
-        }
-        return contentType.toString();
-    }
-
-    public ByteArrayOutputStream2 getContentBytes()
-    {
-        return content;
-    }
-
-    public String getContent()
-    {
-        return content.toString();
-    }
-
-    public String getBody()
-    {
-        return content.toString();
-    }
-
-    private byte[] getByteArray(CharSequence str)
-    {
-        if (charset == null)
-        {
-            return str.toString().getBytes();
-        }
-
-        try
-        {
-            return str.toString().getBytes(charset);
-        }
-        catch (Exception e)
-        {
-            return str.toString().getBytes();
-        }
-    }
-
-    private String getString(Buffer buffer)
-    {
-        return getString(buffer.asArray());
-    }
-
-    private String getString(byte[] b)
-    {
-        if (charset == null)
-        {
-            return new String(b);
-        }
-
-        try
-        {
-            return new String(b,charset);
-        }
-        catch (Exception e)
-        {
-            return new String(b);
-        }
-    }
-
-    /**
-     * @param name
-     * @return the header value as a date
-     * @see org.eclipse.jetty.http.HttpFields#getDateField(java.lang.String)
-     */
-    public long getDateHeader(String name)
-    {
-        return fields.getDateField(name);
-    }
-
-    /**
-     * @param name
-     * @return the header value as a long
-     * @throws NumberFormatException
-     * @see org.eclipse.jetty.http.HttpFields#getLongField(java.lang.String)
-     */
-    public long getLongHeader(String name) throws NumberFormatException
-    {
-        return fields.getLongField(name);
-    }
-
-    /**
-     * @param name
-     * @return the header value
-     * @see org.eclipse.jetty.http.HttpFields#getStringField(java.lang.String)
-     */
-    public String getHeader(String name)
-    {
-        return fields.getStringField(name);
-    }
-
-    public boolean hasHeader(String headerKey)
-    {
-        return fields.containsKey(headerKey);
-    }
-
-    /**
-     * Parse on HTTP Response
-     * 
-     * @param rawHTTP
-     *            Raw HTTP to parse
-     * @return Any unparsed data in the rawHTTP (eg pipelined requests)
-     * @throws IOException
-     */
-    public String parse(CharSequence rawHTTP) throws IOException
-    {
-        this.charset = defaultCharset;
-        this.rawResponse = rawHTTP;
-        ByteArrayBuffer buf = new ByteArrayBuffer(getByteArray(rawHTTP));
-        View view = new View(buf);
-        HttpParser parser = new HttpParser(view,new PH());
-        parser.parse();
-        return getString(view.asArray());
-    }
-
-    public void reset()
-    {
-        fields.clear();
-        method = null;
-        uri = null;
-        version = null;
-        status = 0;
-        reason = null;
-        content = null;
-    }
-
-    /**
-     * Make sure that status code is "OK"
-     */
-    public void assertStatusOK()
-    {
-        assertStatus(HttpStatus.OK_200,"OK");
-    }
-
-    public void assertStatusOK(String msg)
-    {
-        assertStatus(msg,HttpStatus.OK_200,"OK");
-    }
-
-    public void assertStatus(int expectedStatus, String expectedReason)
-    {
-        Assert.assertEquals("Response.status",expectedStatus,this.status);
-        Assert.assertEquals("Response.reason",expectedReason,this.reason);
-    }
-
-    public void assertStatus(String msg, int expectedStatus, String expectedReason)
-    {
-        Assert.assertEquals(msg + ": Response.status",expectedStatus,this.status);
-        Assert.assertEquals(msg + ": Response.reason",expectedReason,this.reason);
-    }
-
-    public void assertStatus(String msg, int expectedStatus)
-    {
-        assertStatus(msg,expectedStatus,HttpStatus.getMessage(expectedStatus));
-    }
-
-    public void assertContentType(String expectedType)
-    {
-        assertHeader("Content-Type",expectedType);
-    }
-
-    private void assertHeader(String headerKey, String expectedValue)
-    {
-        String actual = fields.getStringField(headerKey);
-        Assert.assertNotNull("Response[" + headerKey + "] should not be null",actual);
-        Assert.assertEquals("Response[" + headerKey + "]",expectedValue,actual);
-    }
-
-    public void assertHeader(String msg, String headerKey, String expectedValue)
-    {
-        String actual = fields.getStringField(headerKey);
-        Assert.assertNotNull(msg + ": Response[" + headerKey + "] should not be null, expecting <" + expectedValue + ">",actual);
-        Assert.assertEquals(msg + ": Response[" + headerKey + "]",expectedValue,actual);
-    }
-
-    public void assertBody(String expected)
-    {
-        Assert.assertNotNull("Response.content should not be null",this.content);
-        String actual = this.content.toString();
-        Assert.assertEquals("Response.content",expected,actual);
-    }
-
-    public void assertBody(String msg, String expected)
-    {
-        Assert.assertNotNull(msg + ": Response.content should not be null",this.content);
-        String actual = this.content.toString();
-        Assert.assertEquals(msg + ": Response.content",expected,actual);
-    }
-    
-    public void assertNoBody(String msg)
-    {
-        Assert.assertNull(msg + ": Response.content should be null",this.content);
-    }
-
-    public void assertBodyContains(String msg, String expectedNeedle)
-    {
-        StringAssert.assertContains(msg + ": Response Content",this.content.toString(),expectedNeedle);
-    }
-
-    public void assertHeaderExists(String msg, String expectedHeaderKey)
-    {
-        Assert.assertTrue(msg + ": header <" + expectedHeaderKey + "> should exist",fields.containsKey(expectedHeaderKey));
-    }
-
-    public void assertHeaderNotPresent(String msg, String headerKey)
-    {
-        Assert.assertFalse(msg + ": header <" + headerKey + "> should NOT exist",fields.containsKey(headerKey));
-    }
-    
-    public List<HttpResponseTester> findBodyMultiparts(String boundary) throws IOException
-    {
-        List<HttpResponseTester> multiparts = new ArrayList<HttpResponseTester>();
-
-        BufferedReader buf = new BufferedReader(new StringReader(getBody()));
-        String line;
-        String startBoundary = "--" + boundary;
-        String endBoundary = "--" + boundary + "--";
-        HttpResponseTester resp = null;
-        boolean parsingHeader = true;
-        boolean previousBodyLine = false;
-
-        while ((line = buf.readLine()) != null)
-        {
-            if (line.equals(startBoundary))
-            {
-                // end of multipart, start a new one.
-                if (resp != null)
-                {
-                    multiparts.add(resp);
-                }
-                resp = new HttpResponseTester();
-                parsingHeader = true;
-                previousBodyLine = false;
-                continue;
-            }
-
-            if (line.equals(endBoundary))
-            {
-                if (resp != null)
-                {
-                    multiparts.add(resp);
-                }
-                break;
-            }
-
-            if (parsingHeader)
-            {
-                if (line.equals(""))
-                {
-                    parsingHeader = false;
-                    continue;
-                }
-
-                resp.parseHeader(line);
-            }
-            else
-            {
-                if (previousBodyLine)
-                {
-                    resp.appendBody("\n");
-                }
-                resp.appendBody(line);
-                previousBodyLine = true;
-            }
-        }
-
-        return multiparts;
-    }
-
-    public void parseHeader(String line)
-    {
-        int idx = line.indexOf(":");
-        String key = line.substring(0,idx).trim();
-        String val = line.substring(idx + 1).trim();
-
-        fields.add(key,val);
-    }
-
-    public void appendBody(String s) throws IOException
-    {
-        appendBody(s.getBytes());
-    }
-
-    public void appendBody(byte buf[]) throws IOException
-    {
-        if (content == null)
-        {
-            content = new ByteArrayOutputStream2();
-        }
-
-        content.write(buf);
-    }
-}
diff --git a/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/rawhttp/HttpResponseTesterTest.java b/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/rawhttp/HttpResponseTesterTest.java
index e3a6347..2c39ec8 100644
--- a/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/rawhttp/HttpResponseTesterTest.java
+++ b/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/rawhttp/HttpResponseTesterTest.java
@@ -18,16 +18,19 @@
 
 package org.eclipse.jetty.test.support.rawhttp;
 
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.CoreMatchers.not;
-import static org.junit.Assert.assertThat;
-
 import java.io.IOException;
 import java.util.List;
 
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.HttpTester;
 import org.junit.Assert;
 import org.junit.Test;
 
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertThat;
+
 public class HttpResponseTesterTest
 {
     @Test
@@ -45,16 +48,15 @@ public class HttpResponseTesterTest
         rawResponse.append("ABCDEFGHIJKLMNOPQRSTTUVWXYZ\n");
         rawResponse.append("\n");
 
-        HttpResponseTester response = new HttpResponseTester();
-        response.parse(rawResponse);
+        HttpTester.Response response = HttpTester.parseResponse(rawResponse.toString());
 
-        Assert.assertEquals("Response.version","HTTP/1.1",response.getVersion());
+        Assert.assertEquals("Response.version","HTTP/1.1",response.getVersion().asString());
         Assert.assertEquals("Response.status",200,response.getStatus());
         Assert.assertEquals("Response.reason","OK",response.getReason());
 
-        Assert.assertEquals("Response[Content-Type]","text/plain",response.getContentType());
-        Assert.assertEquals("Response[Content-Length]",28,response.getLongHeader("Content-Length"));
-        Assert.assertEquals("Response[Connection]","close",response.getHeader("Connection"));
+        Assert.assertEquals("Response[Content-Type]","text/plain",response.get(HttpHeader.CONTENT_TYPE));
+        Assert.assertEquals("Response[Content-Length]",28,response.getLongField("Content-Length"));
+        Assert.assertEquals("Response[Connection]","close",response.get("Connection"));
 
         String expected = "ABCDEFGHIJKLMNOPQRSTTUVWXYZ\n";
 
@@ -73,6 +75,7 @@ public class HttpResponseTesterTest
         rawResponse.append("Server: Jetty(7.0.y.z-SNAPSHOT)\n");
         rawResponse.append("\n");
         rawResponse.append("ABCDEFGHIJKLMNOPQRSTTUVWXYZ\n");
+        
         rawResponse.append("HTTP/1.1 200 OK\n");
         rawResponse.append("Date: Mon, 08 Jun 2009 23:05:26 GMT\n");
         rawResponse.append("Content-Type: text/plain\n");
@@ -82,6 +85,8 @@ public class HttpResponseTesterTest
         rawResponse.append("\n");
         rawResponse.append("Host=Default\n");
         rawResponse.append("Resource=R1\n");
+        rawResponse.append("\n");
+
         rawResponse.append("HTTP/1.1 200 OK\n");
         rawResponse.append("Date: Mon, 08 Jun 2009 23:05:26 GMT\n");
         rawResponse.append("Content-Type: text/plain\n");
@@ -92,28 +97,31 @@ public class HttpResponseTesterTest
         rawResponse.append("\n");
         rawResponse.append("Host=Default\n");
         rawResponse.append("Resource=R2\n");
-        rawResponse.append("\n");
 
-        List<HttpResponseTester> responses = HttpResponseTester.parseMulti(rawResponse);
+        List<HttpTester.Response> responses = HttpTesting.readResponses(rawResponse.toString());
+
         Assert.assertNotNull("Responses should not be null",responses);
         Assert.assertEquals("Responses.size",3,responses.size());
 
-        HttpResponseTester resp1 = responses.get(0);
-        resp1.assertStatusOK();
-        resp1.assertContentType("text/plain");
-        resp1.assertBody("ABCDEFGHIJKLMNOPQRSTTUVWXYZ\n");
-        assertThat(resp1.getHeader("Connection"),is(not("close")));
-
-        HttpResponseTester resp2 = responses.get(1);
-        resp2.assertStatusOK();
-        resp2.assertContentType("text/plain");
-        resp2.assertBody("Host=Default\nResource=R1\n");
-        assertThat(resp2.getHeader("Connection"),is(not("close")));
-
-        HttpResponseTester resp3 = responses.get(2);
-        resp3.assertStatusOK();
-        resp3.assertContentType("text/plain");
-        resp3.assertBody("Host=Default\nResource=R2\n");
-        assertThat(resp3.getHeader("Connection"),is("close"));
+        HttpTester.Response resp1 = responses.get(0);
+        System.err.println(resp1.toString());
+        Assert.assertEquals(HttpStatus.OK_200, resp1.getStatus());
+        Assert.assertEquals("text/plain", resp1.get("Content-Type"));
+        Assert.assertTrue(resp1.getContent().contains("ABCDEFGHIJKLMNOPQRSTTUVWXYZ\n"));
+        assertThat(resp1.get("Connection"),is(not("close")));
+
+        HttpTester.Response resp2 = responses.get(1);
+        System.err.println(resp2.toString());
+        Assert.assertEquals(HttpStatus.OK_200, resp2.getStatus());
+        Assert.assertEquals("text/plain", resp2.get("Content-Type"));
+        Assert.assertTrue(resp2.getContent().contains("Host=Default\nResource=R1\n"));
+        assertThat(resp2.get("Connection"),is(not("close")));
+
+        HttpTester.Response resp3 = responses.get(2);
+        System.err.println(resp3.toString());
+        Assert.assertEquals(HttpStatus.OK_200, resp3.getStatus());
+        Assert.assertEquals("text/plain", resp3.get("Content-Type"));
+        Assert.assertTrue(resp3.getContent().contains("Host=Default\nResource=R2\n"));
+        assertThat(resp3.get("Connection"),is("close"));
     }
 }
diff --git a/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/rawhttp/HttpTesting.java b/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/rawhttp/HttpTesting.java
index 133b3ed..e0c1f81 100644
--- a/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/rawhttp/HttpTesting.java
+++ b/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/rawhttp/HttpTesting.java
@@ -18,19 +18,25 @@
 
 package org.eclipse.jetty.test.support.rawhttp;
 
+import java.io.BufferedReader;
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
+import java.io.StringReader;
 import java.io.StringWriter;
 import java.net.InetAddress;
 import java.net.Socket;
 import java.net.SocketTimeoutException;
 import java.net.UnknownHostException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
 import java.util.List;
 
-import org.eclipse.jetty.test.support.StringUtil;
+import org.eclipse.jetty.http.HttpTester;
+import org.eclipse.jetty.util.BufferUtil;
 import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.StringUtil;
 
 /**
  * Testing utility for performing RAW HTTP request/response.
@@ -42,6 +48,89 @@ public class HttpTesting
     private InetAddress serverHost;
     private int serverPort;
     private int timeoutMillis = 5000;
+    
+    
+    public static List<HttpTester.Response> getParts (String boundary, HttpTester.Response response) throws IOException
+    {
+        // TODO This method appears to be broken in how it uses the HttpParser
+        // Should use MultiPartInputStreamParser ??
+        
+        List<HttpTester.Response> parts = new ArrayList<HttpTester.Response>();
+
+        BufferedReader buf = new BufferedReader(new StringReader(response.getContent()));
+        String line;
+        String startBoundary = "--" + boundary;
+        String endBoundary = "--" + boundary + "--";
+      
+        StringBuffer partBuff = null;
+        boolean parsingHeader = true;
+        boolean previousBodyLine = false;
+
+        while ((line = buf.readLine()) != null)
+        {
+            if (line.equals(startBoundary))
+            {
+                // end of multipart, start a new one.
+                if (partBuff != null)
+                {
+                    HttpTester.Response part = HttpTester.parseResponse(partBuff.toString());
+                    parts.add(part);
+                }
+                partBuff = new StringBuffer();
+                parsingHeader = true;
+                previousBodyLine = false;
+                continue;
+            }
+
+            if (line.equals(endBoundary))
+            {
+                if (partBuff != null)
+                {
+                    HttpTester.Response part = HttpTester.parseResponse(partBuff.toString());
+                    parts.add(part);
+                }
+                break;
+            }
+
+            if (parsingHeader)
+            {
+                if (line.equals(""))
+                {
+                    parsingHeader = false;
+                    continue;
+                }
+
+                partBuff.append(line);
+            }
+            else
+            {
+                if (previousBodyLine)
+                {
+                    partBuff.append("\n");
+                }
+                partBuff.append(line);
+                previousBodyLine = true;
+            }
+        }
+
+        return parts;
+
+    }
+
+    public static List<HttpTester.Response> readResponses(String string) throws IOException
+    {
+        List<HttpTester.Response> list = new ArrayList<>();
+
+        ByteBuffer buffer = BufferUtil.toBuffer(string);
+        while(BufferUtil.hasContent(buffer))
+        {
+            HttpTester.Response response = HttpTester.parseResponse(buffer);
+            if (response == null)
+                break;
+            list.add(response);
+        }
+        return list;
+    }
 
     public HttpTesting(HttpSocket httpSocket, InetAddress host, int port)
     {
@@ -115,11 +204,25 @@ public class HttpTesting
      * @return the response object
      * @throws IOException
      */
-    public HttpResponseTester read(Socket sock) throws IOException
+    public HttpTester.Response read(Socket sock) throws IOException
+    {
+       return HttpTester.parseResponse(readRaw(sock));
+    }
+
+    
+    public  List<HttpTester.Response> readResponses(Socket sock) throws IOException
     {
-        HttpResponseTester response = new HttpResponseTester();
-        response.parse(readRaw(sock));
-        return response;
+       List<HttpTester.Response> list = new ArrayList<>();
+       String r = readRaw(sock);
+       ByteBuffer buffer = BufferUtil.toBuffer(r);
+       while(BufferUtil.hasContent(buffer))
+       {
+           HttpTester.Response response = HttpTester.parseResponse(buffer);
+           if (response == null)
+               break;
+           list.add(response);
+       }
+       return list;
     }
 
     /**
@@ -130,16 +233,15 @@ public class HttpTesting
      * @return the response object
      * @throws IOException
      */
-    public HttpResponseTester readAvailable(Socket sock) throws IOException
+    public HttpTester.Response readAvailable(Socket sock) throws IOException
     {
-        HttpResponseTester response = new HttpResponseTester();
+        
         String rawResponse = readRawAvailable(sock);
         if (StringUtil.isBlank(rawResponse))
         {
             return null;
         }
-        response.parse(rawResponse);
-        return response;
+        return HttpTester.parseResponse(rawResponse);
     }
 
     /**
@@ -199,7 +301,7 @@ public class HttpTesting
      * @return the response
      * @throws IOException
      */
-    public HttpResponseTester request(CharSequence rawRequest) throws IOException
+    public HttpTester.Response request(CharSequence rawRequest) throws IOException
     {
         Socket sock = open();
         try
@@ -223,10 +325,10 @@ public class HttpTesting
      * @return the response
      * @throws IOException
      */
-    public HttpResponseTester request(HttpRequestTester request) throws IOException
+    public HttpTester.Response request(HttpTester.Request request) throws IOException
     {
-        String rawRequest = request.generate();
-        return request(rawRequest);
+        ByteBuffer byteBuff = request.generate();
+        return request(BufferUtil.toString(byteBuff));
     }
 
     /**
@@ -237,7 +339,7 @@ public class HttpTesting
      * @return the responses.
      * @throws IOException
      */
-    public List<HttpResponseTester> requests(CharSequence rawRequests) throws IOException
+   public List<HttpTester.Response> requests(CharSequence rawRequests) throws IOException
     {
         Socket sock = open();
         try
@@ -247,7 +349,7 @@ public class HttpTesting
             // Collect response
             String rawResponses = IO.toString(sock.getInputStream());
             DEBUG("--raw-response--\n" + rawResponses);
-            return HttpResponseTester.parseMulti(rawResponses);
+            return readResponses(rawResponses);
         }
         finally
         {
@@ -255,24 +357,6 @@ public class HttpTesting
         }
     }
 
-    /**
-     * Initiate a multiple HTTP requests, parse the responses
-     * 
-     * @param requests
-     *            the request objects.
-     * @return the response objects.
-     * @throws IOException
-     */
-    public List<HttpResponseTester> requests(List<HttpRequestTester> requests) throws IOException
-    {
-        StringBuffer rawRequest = new StringBuffer();
-        for (HttpRequestTester request : requests)
-        {
-            rawRequest.append(request.generate());
-        }
-
-        return requests(rawRequest);
-    }
 
     /**
      * Send a data (as request) to open socket.
diff --git a/tests/test-integration/src/test/resources/BIOHttp.xml b/tests/test-integration/src/test/resources/BIOHttp.xml
deleted file mode 100644
index ab81083..0000000
--- a/tests/test-integration/src/test/resources/BIOHttp.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0"?>
-<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
-
-<Configure id="Server" class="org.eclipse.jetty.server.Server">
-
-    <!-- =========================================================== -->
-    <!-- Set connectors                                              -->
-    <!-- =========================================================== -->
-
-    <Call name="addConnector">
-      <Arg>
-          <New class="org.eclipse.jetty.server.bio.SocketConnector">
-            <Set name="host"><SystemProperty name="jetty.host" /></Set>
-            <Set name="port"><SystemProperty name="jetty.port" default="0"/></Set>
-            <Set name="maxIdleTime">300000</Set>
-            <Set name="Acceptors">2</Set>
-            <Set name="statsOn">false</Set>
-          </New>
-      </Arg>
-    </Call>
-
-</Configure>
\ No newline at end of file
diff --git a/tests/test-integration/src/test/resources/BIOHttps.xml b/tests/test-integration/src/test/resources/BIOHttps.xml
deleted file mode 100644
index fe6cf5a..0000000
--- a/tests/test-integration/src/test/resources/BIOHttps.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0"?>
-<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
-
-<Configure id="Server" class="org.eclipse.jetty.server.Server">
-
-    <!-- =========================================================== -->
-    <!-- Set connectors                                              -->
-    <!-- =========================================================== -->
-
-    <Call name="addConnector">
-      <Arg>
-          <New class="org.eclipse.jetty.server.ssl.SslSocketConnector">
-            <Set name="host"><SystemProperty name="jetty.host" /></Set>
-            <Set name="port"><SystemProperty name="jetty.port" default="0"/></Set>
-            <Set name="maxIdleTime">300000</Set>
-            <Set name="Acceptors">2</Set>
-            <Set name="statsOn">false</Set>
-            <Set name="keystore"><Property name="test.resourcesdir" default="src/test/resources" />/keystore</Set>
-            <Set name="password">OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4</Set>
-            <Set name="keyPassword">OBF:1u2u1wml1z7s1z7a1wnl1u2g</Set>
-            <!--
-            <Set name="truststore"><Property name="test.resourcesdir" default="src/test/resources" />/keystore</Set>
-            <Set name="trustPassword">OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4</Set>
-              -->
-          </New>
-      </Arg>
-    </Call>
-
-</Configure>
\ No newline at end of file
diff --git a/tests/test-integration/src/test/resources/DefaultHandler.xml b/tests/test-integration/src/test/resources/DefaultHandler.xml
index aa6cf03..644e7f9 100644
--- a/tests/test-integration/src/test/resources/DefaultHandler.xml
+++ b/tests/test-integration/src/test/resources/DefaultHandler.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0"?>
-<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
 
 <!-- =============================================================== -->
 <!-- Configure the Jetty Server                                      -->
@@ -12,37 +12,26 @@
 
 <Configure id="Server" class="org.eclipse.jetty.server.Server">
 
-    <!-- =========================================================== -->
-    <!-- Server Thread Pool                                          -->
-    <!-- =========================================================== -->
-    <Set name="ThreadPool">
-      <New class="org.eclipse.jetty.util.thread.QueuedThreadPool">
-        <Set name="minThreads">10</Set>
-        <Set name="maxThreads">200</Set>
-      </New>
-    </Set>
+    <New id="httpConfig" class="org.eclipse.jetty.server.HttpConfiguration">
+      <Set name="secureScheme">https</Set>
+      <Set name="securePort"><Property name="jetty.secure.port" default="8443" /></Set>
+      <Set name="outputBufferSize">32768</Set>
+      <Set name="requestHeaderSize">8192</Set>
+      <Set name="responseHeaderSize">8192</Set>
+      <Set name="sendServerVersion">true</Set>
+      <Set name="sendDateHeader">false</Set>
+      <Set name="headerCacheSize">512</Set>
 
-    <!-- =========================================================== -->
-    <!-- Set connectors                                              -->
-    <!-- =========================================================== -->
+      <!-- Uncomment to enable handling of X-Forwarded- style headers
+      <Call name="addCustomizer">
+        <Arg><New class="org.eclipse.jetty.server.ForwardedRequestCustomizer"/></Arg>
+      </Call>
+      -->
+    </New>
 
-    <Call name="addConnector">
-      <Arg>
-          <New class="org.eclipse.jetty.server.nio.SelectChannelConnector">
-            <Set name="host"><SystemProperty name="jetty.host" /></Set>
-            <Set name="port"><SystemProperty name="jetty.port" default="0"/></Set>
-            <Set name="maxIdleTime">300000</Set>
-            <Set name="Acceptors">2</Set>
-            <Set name="statsOn">false</Set>
-            <!--<Set name="confidentialPort">8443</Set>-->
-	    <Set name="lowResourcesConnections">20000</Set>
-	    <Set name="lowResourcesMaxIdleTime">5000</Set>
-          </New>
-      </Arg>
-    </Call>
 
     <!-- =========================================================== -->
-    <!-- Set handler Collection Structure                            --> 
+    <!-- Set handler Collection Structure                            -->
     <!-- =========================================================== -->
     <Set name="handler">
       <New id="Handlers" class="org.eclipse.jetty.server.handler.HandlerCollection">
@@ -60,13 +49,11 @@
         </Set>
       </New>
     </Set>
-    
+
     <!-- =========================================================== -->
     <!-- extra options                                               -->
     <!-- =========================================================== -->
     <Set name="stopAtShutdown">true</Set>
-    <Set name="sendServerVersion">true</Set>
-    <Set name="sendDateHeader">true</Set>
-    <Set name="gracefulShutdown">1000</Set>
+    <Set name="stopTimeout">1000</Set>
 
 </Configure>
diff --git a/tests/test-integration/src/test/resources/NIOHttp.xml b/tests/test-integration/src/test/resources/NIOHttp.xml
index a56c368..c766d10 100644
--- a/tests/test-integration/src/test/resources/NIOHttp.xml
+++ b/tests/test-integration/src/test/resources/NIOHttp.xml
@@ -1,24 +1,32 @@
 <?xml version="1.0"?>
-<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
 
 <Configure id="Server" class="org.eclipse.jetty.server.Server">
 
     <!-- =========================================================== -->
     <!-- Set connectors                                              -->
     <!-- =========================================================== -->
-
+    
     <Call name="addConnector">
-      <Arg>
-          <New class="org.eclipse.jetty.server.nio.SelectChannelConnector">
-            <Set name="host"><SystemProperty name="jetty.host" /></Set>
-            <Set name="port"><SystemProperty name="jetty.port" default="0"/></Set>
-            <Set name="maxIdleTime">300000</Set>
-            <Set name="Acceptors">2</Set>
-            <Set name="statsOn">false</Set>
-		    <Set name="lowResourcesConnections">20000</Set>
-		    <Set name="lowResourcesMaxIdleTime">5000</Set>
-          </New>
-      </Arg>
-    </Call>
+    <Arg>
+      <New class="org.eclipse.jetty.server.ServerConnector">
+        <Arg name="server"><Ref refid="Server" /></Arg>
+        <Arg name="factories">
+          <Array type="org.eclipse.jetty.server.ConnectionFactory">
+            <Item>
+              <New class="org.eclipse.jetty.server.HttpConnectionFactory">
+                <Arg name="config"><Ref refid="httpConfig" /></Arg>
+              </New>
+            </Item>
+          </Array>
+        </Arg>
+        <Set name="host"><Property name="jetty.host" /></Set>
+        <Set name="port">0</Set>
+        <Set name="idleTimeout"><Property name="http.timeout" default="30000"/></Set>
+      </New>
+    </Arg>
+  </Call>
+    
+    
 
-</Configure>
\ No newline at end of file
+</Configure>
diff --git a/tests/test-integration/src/test/resources/NIOHttps.xml b/tests/test-integration/src/test/resources/NIOHttps.xml
index 07315cd..5e90fdb 100644
--- a/tests/test-integration/src/test/resources/NIOHttps.xml
+++ b/tests/test-integration/src/test/resources/NIOHttps.xml
@@ -1,27 +1,37 @@
 <?xml version="1.0"?>
-<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
 
 <Configure id="Server" class="org.eclipse.jetty.server.Server">
 
     <!-- =========================================================== -->
     <!-- Set connectors                                              -->
-    <!-- =========================================================== -->
-
-    <Call name="addConnector">
-      <Arg>
-          <New class="org.eclipse.jetty.server.ssl.SslSelectChannelConnector">
-            <Set name="host"><SystemProperty name="jetty.host" /></Set>
-            <Set name="port"><SystemProperty name="jetty.port" default="0"/></Set>
-            <Set name="maxIdleTime">300000</Set>
-            <Set name="Acceptors">2</Set>
-            <Set name="statsOn">false</Set>
-		    <Set name="lowResourcesConnections">20000</Set>
-		    <Set name="lowResourcesMaxIdleTime">5000</Set>
-            <Set name="keystore"><Property name="test.resourcesdir" default="src/test/resources" />/keystore</Set>
-            <Set name="password">OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4</Set>
-            <Set name="keyPassword">OBF:1u2u1wml1z7s1z7a1wnl1u2g</Set>
-          </New>
-      </Arg>
-    </Call>
+    <!-- =========================================================== --> 
+   <Call id="httpsConnector" name="addConnector">
+    <Arg>
+      <New class="org.eclipse.jetty.server.ServerConnector">
+        <Arg name="server"><Ref refid="Server" /></Arg>
+          <Arg name="factories">
+            <Array type="org.eclipse.jetty.server.ConnectionFactory">
+              <Item>
+                <New class="org.eclipse.jetty.server.SslConnectionFactory">
+                  <Arg name="next">http/1.1</Arg>
+                  <Arg name="sslContextFactory"><Ref refid="sslContextFactory"/></Arg>
+                </New>
+              </Item>
+              <Item>
+                <New class="org.eclipse.jetty.server.HttpConnectionFactory">
+                  <Arg name="config"><Ref refid="sslHttpConfig"/></Arg>
+                </New>
+              </Item>
+            </Array>
+          </Arg>
+          <Set name="host"><Property name="jetty.host" /></Set>
+          <Set name="port">0</Set>
+          <Set name="idleTimeout">30000</Set>
+        </New>
+    </Arg>
+  </Call>
+    
+    
 
-</Configure>
\ No newline at end of file
+</Configure>
diff --git a/tests/test-integration/src/test/resources/RFC2616Base.xml b/tests/test-integration/src/test/resources/RFC2616Base.xml
index df8ad68..2c300d1 100644
--- a/tests/test-integration/src/test/resources/RFC2616Base.xml
+++ b/tests/test-integration/src/test/resources/RFC2616Base.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0"?>
-<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
 
 <!-- =============================================================== -->
 <!-- Configure the Jetty Server                                      -->
@@ -12,43 +12,25 @@
 
 <Configure id="Server" class="org.eclipse.jetty.server.Server">
 
-    <!-- =========================================================== -->
-    <!-- Server Thread Pool                                          -->
-    <!-- =========================================================== -->
-    <Set name="ThreadPool">
-      <New class="org.eclipse.jetty.util.thread.QueuedThreadPool">
-        <Set name="minThreads">10</Set>
-        <Set name="maxThreads">200</Set>
-      </New>
-    </Set>
+    <New id="httpConfig" class="org.eclipse.jetty.server.HttpConfiguration">
+      <Set name="secureScheme">https</Set>
+      <Set name="securePort"><Property name="jetty.secure.port" default="8443" /></Set>
+      <Set name="outputBufferSize">32768</Set>
+      <Set name="requestHeaderSize">8192</Set>
+      <Set name="responseHeaderSize">8192</Set>
+      <Set name="sendServerVersion">true</Set>
+      <Set name="sendDateHeader">false</Set>
+      <Set name="headerCacheSize">512</Set>
 
-    <!-- =========================================================== -->
-    <!-- No connectors Set Here.                                     -->
-    <!-- See:                                                        -->
-    <!--   BIOHttp.xml                                               -->
-    <!--   BIOHttps.xml                                              -->
-    <!--   NIOHttp.xml                                               -->
-    <!--   NIOHttps.xml                                              -->
-    <!-- =========================================================== -->
-
-    <!-- 
-    <Call name="addConnector">
-      <Arg>
-          <New class="org.eclipse.jetty.server.nio.SelectChannelConnector">
-            <Set name="host"><SystemProperty name="jetty.host" /></Set>
-            <Set name="port"><SystemProperty name="jetty.port" default="0"/></Set>
-            <Set name="maxIdleTime">300000</Set>
-            <Set name="Acceptors">2</Set>
-            <Set name="statsOn">false</Set>
-	    <Set name="lowResourcesConnections">20000</Set>
-	    <Set name="lowResourcesMaxIdleTime">5000</Set>
-          </New>
-      </Arg>
-    </Call> 
-     -->
+      <!-- Uncomment to enable handling of X-Forwarded- style headers
+      <Call name="addCustomizer">
+        <Arg><New class="org.eclipse.jetty.server.ForwardedRequestCustomizer"/></Arg>
+      </Call>
+      -->
+    </New>
 
     <!-- =========================================================== -->
-    <!-- Set handler Collection Structure                            --> 
+    <!-- Set handler Collection Structure                            -->
     <!-- =========================================================== -->
     <Set name="handler">
       <New id="Handlers" class="org.eclipse.jetty.server.handler.HandlerCollection">
@@ -59,90 +41,73 @@
            </Item>
            <Item>
              <New id="vcontexts" class="org.eclipse.jetty.server.handler.ContextHandler">
-		       <Set name="contextPath">/tests</Set>
-		       <Set name="VirtualHosts">
-		         <Array type="java.lang.String">
-		           <Item>VirtualHost</Item>
-		         </Array>
-		       </Set>
-		       <Set name="ResourceBase"><Property name="test.docroot.base"/>/virtualhost</Set>
-		       <Set name="Handler"><New id="reshandler" class="org.eclipse.jetty.server.handler.ResourceHandler"/></Set>
-		       <Set name="DisplayName">virtual</Set>
+               <Set name="contextPath">/tests</Set>
+               <Set name="VirtualHosts">
+                 <Array type="java.lang.String">
+                   <Item>VirtualHost</Item>
+                 </Array>
+               </Set>
+               <Set name="ResourceBase"><Property name="test.docroot.base"/>/virtualhost</Set>
+               <Set name="Handler"><New id="reshandler" class="org.eclipse.jetty.server.handler.ResourceHandler"/></Set>
+               <Set name="DisplayName">virtual</Set>
              </New>
            </Item>
            <Item>
              <New id="defcontext" class="org.eclipse.jetty.server.handler.ContextHandler">
                <Set name="contextPath">/tests</Set>
                <Set name="ResourceBase"><Property name="test.docroot.base"/>/default</Set>
-		       <Set name="Handler"><New id="reshandler" class="org.eclipse.jetty.server.handler.ResourceHandler"/></Set>
-		       <Set name="DisplayName">default</Set>
+               <Set name="Handler"><New id="reshandler" class="org.eclipse.jetty.server.handler.ResourceHandler"/></Set>
+               <Set name="DisplayName">default</Set>
              </New>
            </Item>
            <Item>
              <New id="echocontext" class="org.eclipse.jetty.server.handler.ContextHandler">
                <Set name="contextPath">/echo</Set>
-		       <Set name="Handler"><New id="echohandler" class="org.eclipse.jetty.test.support.EchoHandler"/></Set>
-		       <Set name="DisplayName">echo</Set>
+               <Set name="Handler"><New id="echohandler" class="org.eclipse.jetty.test.support.EchoHandler"/></Set>
+               <Set name="DisplayName">echo</Set>
              </New>
            </Item>
          </Array>
         </Set>
       </New>
     </Set>
-    
-    <Call name="addLifeCycle">
-	  <Arg>
-	    <New class="org.eclipse.jetty.deploy.ContextDeployer">
-	      <Set name="contexts"><Ref id="WebappContexts"/></Set>
-	      <Set name="configurationDir"><Property name="test.resourcesdir" default="src/test/resources"/>/webapp-contexts/RFC2616</Set>
-	      <Set name="scanInterval">0</Set>
-	      <Set name="configurationManager">
-	        <New class="org.eclipse.jetty.deploy.FileConfigurationManager">
-	          <Set name="file"><Property name="test.targetdir" default="target"/>/testable-jetty-server-config.properties</Set>
-	        </New>
-	      </Set>
-	    </New>
-	  </Arg>
-	</Call>
-    
-    <!-- =========================================================== -->
-    <!-- Configure the webapp deployer.                              -->
-    <!-- A webapp  deployer will deploy standard webapps discovered  -->
-    <!-- in a directory at startup, without the need for additional  -->
-    <!-- configuration files.    It does not support hot deploy or   -->
-    <!-- non standard contexts (see ContextDeployer above).          -->
-    <!--                                                             -->
-    <!-- This deployer is configured to deploy webapps from the      -->
-    <!-- $JETTY_HOME/webapps directory                               -->
-    <!--                                                             -->
-    <!-- Normally only one type of deployer need be used.            -->
-    <!--                                                             -->
-    <!-- =========================================================== -->
-    <!-- 
-    <Call name="addBean">
-      <Arg>
-        <New class="org.eclipse.jetty.deploy.WebAppDeployer">
-          <Set name="contexts"><Ref id="WebappContexts"/></Set>
-          <Set name="webAppDir"><Property name="test.targetdir" default="target"/>/webapps</Set>
-		  <Set name="parentLoaderPriority">false</Set>
-		  <Set name="extract">true</Set>
-		  <Set name="allowDuplicates">false</Set>
-          <Set name="defaultsDescriptor"><Property name="test.resourcesdir" default="src/test/resources"/>/webdefault.xml</Set>
-          <Call name="setAttribute">
-            <Arg>org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern</Arg>
-            <Arg>.*/jsp-api-[^/]*\.jar$|.*/jsp-[^/]*\.jar$</Arg>
-          </Call>
-        </New>
-      </Arg>
-    </Call>
-     -->
-    
+
+   <Call name="addBean">
+    <Arg>
+      <New id="DeploymentManager" class="org.eclipse.jetty.deploy.DeploymentManager">
+        <Set name="contexts">
+          <Ref refid="WebappContexts" />
+        </Set>
+        <Call name="setContextAttribute">
+          <Arg>org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern</Arg>
+          <Arg>.*/servlet-api-[^/]*\.jar$</Arg>
+        </Call>
+
+        <Call id="webappprovider" name="addAppProvider">
+          <Arg>
+            <New class="org.eclipse.jetty.deploy.providers.WebAppProvider">
+              <Set name="monitoredDirName"><Property name="test.resourcesdir" default="src/test/resources" />/webapp-contexts/RFC2616</Set>
+              <Set name="scanInterval">1</Set>
+              <Set name="extractWars">true</Set>
+              <Set name="configurationManager">
+                <New class="org.eclipse.jetty.deploy.PropertiesConfigurationManager">
+                  <Set name="file"><Property name="test.targetdir" default="target"/>/testable-jetty-server-config.properties</Set>
+                </New>
+              </Set>
+            </New>
+          </Arg>
+        </Call>
+
+      </New>
+    </Arg>
+  </Call>
+
+
+
     <!-- =========================================================== -->
     <!-- extra options                                               -->
     <!-- =========================================================== -->
     <Set name="stopAtShutdown">true</Set>
-    <Set name="sendServerVersion">true</Set>
-    <Set name="sendDateHeader">true</Set>
-    <Set name="gracefulShutdown">1000</Set>
+    <Set name="stopTimeout">1000</Set>
 
 </Configure>
diff --git a/tests/test-integration/src/test/resources/RFC2616_Filters.xml b/tests/test-integration/src/test/resources/RFC2616_Filters.xml
index fed2cc8..a798bfd 100644
--- a/tests/test-integration/src/test/resources/RFC2616_Filters.xml
+++ b/tests/test-integration/src/test/resources/RFC2616_Filters.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0"?>
-<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
 
 <Configure id="Server" class="org.eclipse.jetty.server.Server">
 
-</Configure>
\ No newline at end of file
+</Configure>
diff --git a/tests/test-integration/src/test/resources/RFC2616_Redirects.xml b/tests/test-integration/src/test/resources/RFC2616_Redirects.xml
index 1d35a53..a3cc7a3 100644
--- a/tests/test-integration/src/test/resources/RFC2616_Redirects.xml
+++ b/tests/test-integration/src/test/resources/RFC2616_Redirects.xml
@@ -1,16 +1,16 @@
 <?xml version="1.0"?>
-<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
 
 <Configure id="Server" class="org.eclipse.jetty.server.Server">
     <!-- =========================================================== -->
-    <!-- Configure Rewrite Handler                                   --> 
+    <!-- Configure Rewrite Handler                                   -->
     <!-- =========================================================== -->
     <Get id="oldhandler" name="handler"/>
 
     <Set name="handler">
      <New id="Rewrite" class="org.eclipse.jetty.rewrite.handler.RewriteHandler">
-     
-      <Set name="handler"><Ref id="oldhandler"/></Set>
+
+      <Set name="handler"><Ref refid="oldhandler"/></Set>
       <Set name="rewriteRequestURI">true</Set>
       <Set name="rewritePathInfo">false</Set>
       <Set name="originalPathAttribute">requestedPath</Set>
@@ -19,7 +19,7 @@
           <Array type="org.eclipse.jetty.rewrite.handler.Rule">
 
             <!-- add a response rule -->
-            <!-- 
+            <!--
             <Item>
               <New id="response" class="org.eclipse.jetty.rewrite.handler.ResponsePatternRule">
                 <Set name="pattern">/rewrite/session/</Set>
@@ -30,7 +30,7 @@
              -->
 
             <!-- add a simple redirect -->
-            <!-- 
+            <!--
             <Item>
               <New id="redirect" class="org.eclipse.jetty.rewrite.handler.RedirectPatternRule">
                 <Set name="pattern">/redirect/*</Set>
@@ -50,5 +50,5 @@
         </Set>
       </New>
     </Set>
- 
-</Configure>
\ No newline at end of file
+
+</Configure>
diff --git a/tests/test-integration/src/test/resources/monitor/etc/jetty-jmx.xml b/tests/test-integration/src/test/resources/monitor/etc/jetty-jmx.xml
deleted file mode 100644
index 8b4f49f..0000000
--- a/tests/test-integration/src/test/resources/monitor/etc/jetty-jmx.xml
+++ /dev/null
@@ -1,82 +0,0 @@
-<?xml version="1.0"?>
-<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
-
-<!-- ============================================================================ -->
-<!-- To correctly start Jetty with JMX module enabled, this configuration         -->
-<!-- file must appear first in the list of the configuration files.               -->
-<!-- The simplest way to achieve this is to add etc/jetty-jmx.xml as the          -->
-<!-- first file in configuration file list at the end of start.ini file.          -->
-<!-- ============================================================================ -->
-<Configure id="Server" class="org.eclipse.jetty.server.Server">
-
-  <!-- =========================================================== -->
-  <!-- Initialize an mbean server                                  -->
-  <!-- =========================================================== -->
-  <Call id="MBeanServer" class="java.lang.management.ManagementFactory"
-    name="getPlatformMBeanServer" />
-
-  <!-- =========================================================== -->
-  <!-- Initialize the Jetty MBean container                        -->
-  <!-- =========================================================== -->
-  <New id="MBeanContainer" class="org.eclipse.jetty.jmx.MBeanContainer">
-    <Arg>
-      <Ref id="MBeanServer" />
-    </Arg>
-  </New>
-
-  <!-- Add to the Server to listen for object events -->
-  <Get id="Container" name="container">
-    <Call name="addEventListener">
-      <Arg>
-        <Ref id="MBeanContainer" />
-      </Arg>
-    </Call>
-  </Get>
-
-  <!-- Add to the Server as a lifecycle -->
-  <!-- Only do this if you know you will only have a single jetty server -->
-  <Call name="addBean">
-    <Arg>
-      <Ref id="MBeanContainer" />
-    </Arg>
-  </Call>
-
-  <!-- Add the static log -->
-  <Get id="Logger" class="org.eclipse.jetty.util.log.Log" name="log" />
-  <Ref id="MBeanContainer">
-    <Call name="addBean">
-      <Arg>
-        <Ref id="Logger" />
-      </Arg>
-    </Call>
-  </Ref>
-  
-  <!-- In order to connect to the JMX server remotely from a different
-       process, possibly running on a different host, Jetty JMX module
-       can create a remote JMX connector.       
-   -->
-
- 
-  <!-- Optionally add a remote JMX connector. The parameters of the constructor
-       below specify the JMX service URL, and the object name string for the
-       connector server bean. The parameters of the JMXServiceURL constructor 
-       specify the protocol that clients will use to connect to the remote JMX
-       connector (RMI), the hostname of the server (local hostname), port number
-       (automatically assigned), and the URL path. Note that URL path contains
-       the RMI registry hostname and port number, that may need to be modified
-       in order to comply with the firewall requirements. 
-  -->
-  <New id="ConnectorServer" class="org.eclipse.jetty.jmx.ConnectorServer">
-    <Arg>
-      <New class="javax.management.remote.JMXServiceURL">
-        <Arg type="java.lang.String">rmi</Arg>
-        <Arg type="java.lang.String" />
-        <Arg type="java.lang.Integer">0</Arg>
-        <Arg type="java.lang.String">/jndi/rmi://localhost:0/jettyjmx</Arg>
-      </New>
-    </Arg>
-    <Arg>org.eclipse.jetty:name=rmiconnectorserver</Arg>
-    <Call name="start" />
-  </New>
-</Configure>
-
diff --git a/tests/test-integration/src/test/resources/monitor/start.ini b/tests/test-integration/src/test/resources/monitor/start.ini
deleted file mode 100644
index fe5d7bd..0000000
--- a/tests/test-integration/src/test/resources/monitor/start.ini
+++ /dev/null
@@ -1,7 +0,0 @@
-OPTIONS=Server,jsp,jmx,resources,websocket,ext
-etc/jetty-jmx.xml
-etc/jetty.xml
-etc/jetty-deploy.xml
-etc/jetty-webapps.xml
-etc/jetty-contexts.xml
-etc/jetty-testrealm.xml
diff --git a/tests/test-integration/src/test/resources/org/eclipse/jetty/monitor/java-monitor-integration.xml b/tests/test-integration/src/test/resources/org/eclipse/jetty/monitor/java-monitor-integration.xml
deleted file mode 100644
index 757507c..0000000
--- a/tests/test-integration/src/test/resources/org/eclipse/jetty/monitor/java-monitor-integration.xml
+++ /dev/null
@@ -1,62 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
-
-<!-- 
-// ========================================================================
-// Copyright (c) Webtide LLC
-// 
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at 
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.apache.org/licenses/LICENSE-2.0.txt
-//
-// You may elect to redistribute this code under either of these licenses. 
-// ========================================================================
--->
-
-<Configure id="Server" class="org.eclipse.jetty.server.Server">
-
-<!-- 
-<Call id="MBeanServer" class="java.lang.management.ManagementFactory" name="getPlatformMBeanServer">
-    <Call name="registerMBean">
-        <Arg><New class="com.javamonitor.mbeans.DNSCachePolicy" /></Arg>
-        <Arg>
-            <New class="javax.management.ObjectName">
-                <Arg>com.javamonitor:type=DNSCachePolicy</Arg>
-            </New>
-        </Arg>
-    </Call>
-    <Call name="registerMBean">
-        <Arg><New class="com.javamonitor.mbeans.Threading" /></Arg>
-        <Arg>
-            <New class="javax.management.ObjectName">
-                <Arg>com.javamonitor:type=Threading</Arg>
-            </New>
-        </Arg>
-    </Call>
-</Call>
--->
-
-<Call id="JMXMonitor" class="org.eclipse.jetty.monitor.JMXMonitor" name="getInstance">
-	<Call name="addActions">
-	    <Arg>
-	        <Array type="org.eclipse.jetty.monitor.jmx.MonitorAction">
-	            <Item>
-	                <New id="MonitorAction" class="org.eclipse.jetty.monitor.integration.JavaMonitorAction">
-	                   <Arg />
-	                   <Arg>http://194.109.206.51/lemongrass/1.1/push</Arg>
-	                   <Arg>57e48e79-f0e6-4909-a6da-e8c1267cbf49</Arg>
-	                   <Arg>8080</Arg>
-	                   <Arg type="java.lang.Integer">15000</Arg>
-	                </New>
-	            </Item>
-	        </Array>
-	    </Arg>
-	</Call>
-</Call>
-</Configure>
diff --git a/tests/test-integration/src/test/resources/org/eclipse/jetty/monitor/jetty-monitor-service.xml b/tests/test-integration/src/test/resources/org/eclipse/jetty/monitor/jetty-monitor-service.xml
deleted file mode 100644
index bf86f3e..0000000
--- a/tests/test-integration/src/test/resources/org/eclipse/jetty/monitor/jetty-monitor-service.xml
+++ /dev/null
@@ -1,68 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
-
-<!-- 
-// ========================================================================
-// Copyright (c) Webtide LLC
-// 
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at 
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.apache.org/licenses/LICENSE-2.0.txt
-//
-// You may elect to redistribute this code under either of these licenses. 
-// ========================================================================
--->
-
-<Configure id="Monitor" class="org.eclipse.jetty.monitor.JMXMonitor">
-  <Call name="setUrl">
-    <Arg>service:jmx:rmi://localhost/jndi/rmi://localhost:1099/jettyjmx</Arg>
-  </Call>
-  <Call name="addActions">
-    <Arg>
-      <Array type="org.eclipse.jetty.monitor.jmx.MonitorAction">
-        <Item>
-          <New class="org.eclipse.jetty.monitor.jmx.SimpleAction">
-            <Arg>
-              <New class="org.eclipse.jetty.monitor.triggers.OrEventTrigger">
-                <Arg>
-                  <Array type="org.eclipse.jetty.monitor.jmx.EventTrigger">
-                    <Item>
-                      <New
-                        class="org.eclipse.jetty.monitor.triggers.LessThanOrEqualToAttrEventTrigger">
-                        <Arg>org.eclipse.jetty.util.thread:type=queuedthreadpool,id=0
-                        </Arg>
-                        <Arg>idleThreads</Arg>
-                        <Arg type="java.lang.Integer">4</Arg>
-                      </New>
-                    </Item>
-                    <Item>
-                      <New
-                        class="org.eclipse.jetty.monitor.triggers.GreaterThanAttrEventTrigger">
-                        <Arg>org.eclipse.jetty.util.thread:type=queuedthreadpool,id=0
-                        </Arg>
-                        <Arg>idleThreads</Arg>
-                        <Arg type="java.lang.Integer">7</Arg>
-                      </New>
-                    </Item>
-                  </Array>
-                </Arg>
-              </New>
-            </Arg>
-            <Arg>
-              <New class="org.eclipse.jetty.monitor.jmx.ConsoleNotifier">
-                <Arg>%s</Arg>
-              </New>
-            </Arg>
-            <Arg type="java.lang.Long">500</Arg>
-          </New>
-        </Item>
-      </Array>
-    </Arg>
-  </Call>
-</Configure>
\ No newline at end of file
diff --git a/tests/test-integration/src/test/resources/org/eclipse/jetty/monitor/jetty-monitor-test.xml b/tests/test-integration/src/test/resources/org/eclipse/jetty/monitor/jetty-monitor-test.xml
deleted file mode 100644
index 4984377..0000000
--- a/tests/test-integration/src/test/resources/org/eclipse/jetty/monitor/jetty-monitor-test.xml
+++ /dev/null
@@ -1,65 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
-
-<!-- 
-// ========================================================================
-// Copyright (c) Webtide LLC
-// 
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at 
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.apache.org/licenses/LICENSE-2.0.txt
-//
-// You may elect to redistribute this code under either of these licenses. 
-// ========================================================================
--->
-
-<Configure id="Monitor" class="org.eclipse.jetty.monitor.JMXMonitor">
-  <Call name="addActions">
-    <Arg>
-      <Array type="org.eclipse.jetty.monitor.jmx.MonitorAction">
-        <Item>
-          <New class="org.eclipse.jetty.monitor.jmx.SimpleAction">
-            <Arg>
-              <New class="org.eclipse.jetty.monitor.triggers.OrEventTrigger">
-                <Arg>
-                  <Array type="org.eclipse.jetty.monitor.jmx.EventTrigger">
-                    <Item>
-                      <New
-                        class="org.eclipse.jetty.monitor.triggers.LessThanOrEqualToAttrEventTrigger">
-                        <Arg>org.eclipse.jetty.util.thread:type=queuedthreadpool,id=0
-                        </Arg>
-                        <Arg>idleThreads</Arg>
-                        <Arg type="java.lang.Integer">4</Arg>
-                      </New>
-                    </Item>
-                    <Item>
-                      <New
-                        class="org.eclipse.jetty.monitor.triggers.GreaterThanAttrEventTrigger">
-                        <Arg>org.eclipse.jetty.util.thread:type=queuedthreadpool,id=0
-                        </Arg>
-                        <Arg>idleThreads</Arg>
-                        <Arg type="java.lang.Integer">7</Arg>
-                      </New>
-                    </Item>
-                  </Array>
-                </Arg>
-              </New>
-            </Arg>
-            <Arg>
-              <New class="org.eclipse.jetty.monitor.jmx.ConsoleNotifier">
-                <Arg>%s</Arg>
-              </New>
-            </Arg>
-            <Arg type="java.lang.Long">500</Arg>
-          </New>
-        </Item>
-      </Array>
-    </Arg>
-  </Call>
-</Configure>
\ No newline at end of file
diff --git a/tests/test-integration/src/test/resources/ssl.xml b/tests/test-integration/src/test/resources/ssl.xml
new file mode 100644
index 0000000..84de070
--- /dev/null
+++ b/tests/test-integration/src/test/resources/ssl.xml
@@ -0,0 +1,33 @@
+<Configure id="sslContextFactory" class="org.eclipse.jetty.util.ssl.SslContextFactory">
+  <Set name="KeyStorePath"><Property name="jetty.home" default="." />/<Property name="jetty.keystore" default="keystore"/></Set>
+  <Set name="KeyStorePassword"><Property name="jetty.keystore.password" default="OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4"/></Set>
+  <Set name="KeyManagerPassword"><Property name="jetty.keymanager.password" default="OBF:1u2u1wml1z7s1z7a1wnl1u2g"/></Set>
+  <Set name="TrustStorePath"><Property name="jetty.home" default="." />/<Property name="jetty.truststore" default="keystore"/></Set>
+  <Set name="TrustStorePassword"><Property name="jetty.truststore.password" default="OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4"/></Set>
+  <Set name="EndpointIdentificationAlgorithm"></Set>
+  <Set name="ExcludeCipherSuites">
+    <Array type="String">
+      <Item>SSL_RSA_WITH_DES_CBC_SHA</Item>
+      <Item>SSL_DHE_RSA_WITH_DES_CBC_SHA</Item>
+      <Item>SSL_DHE_DSS_WITH_DES_CBC_SHA</Item>
+      <Item>SSL_RSA_EXPORT_WITH_RC4_40_MD5</Item>
+      <Item>SSL_RSA_EXPORT_WITH_DES40_CBC_SHA</Item>
+      <Item>SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA</Item>
+      <Item>SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA</Item>
+    </Array>
+  </Set>
+
+  <!-- =========================================================== -->
+  <!-- Create a TLS specific HttpConfiguration based on the        -->
+  <!-- common HttpConfiguration defined in jetty.xml               -->
+  <!-- Add a SecureRequestCustomizer to extract certificate and    -->
+  <!-- session information                                         -->
+  <!-- =========================================================== -->
+  <New id="sslHttpConfig" class="org.eclipse.jetty.server.HttpConfiguration">
+    <Arg><Ref refid="httpConfig"/></Arg>
+    <Call name="addCustomizer">
+      <Arg><New class="org.eclipse.jetty.server.SecureRequestCustomizer"/></Arg>
+    </Call>
+  </New>
+
+</Configure>
diff --git a/tests/test-integration/src/test/resources/webapp-contexts/RFC2616/rfc2616-webapp.xml b/tests/test-integration/src/test/resources/webapp-contexts/RFC2616/rfc2616-webapp.xml
index 9ed47d4..74d2fe9 100644
--- a/tests/test-integration/src/test/resources/webapp-contexts/RFC2616/rfc2616-webapp.xml
+++ b/tests/test-integration/src/test/resources/webapp-contexts/RFC2616/rfc2616-webapp.xml
@@ -1,5 +1,5 @@
-<?xml version="1.0"  encoding="ISO-8859-1"?>
-<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
 <Configure class="org.eclipse.jetty.webapp.WebAppContext">
 	<Set name="contextPath">/rfc2616-webapp</Set>
 	<Set name="war">
diff --git a/tests/test-integration/src/test/resources/webdefault.xml b/tests/test-integration/src/test/resources/webdefault.xml
index 35a5a9b..d87a7e2 100644
--- a/tests/test-integration/src/test/resources/webdefault.xml
+++ b/tests/test-integration/src/test/resources/webdefault.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="ISO-8859-1"?>
+<?xml version="1.0" encoding="UTF-8"?>
 
 <!-- ===================================================================== -->
 <!-- This file contains the default descriptor for web applications.       -->
diff --git a/tests/test-jmx/jmx-webapp-it/pom.xml b/tests/test-jmx/jmx-webapp-it/pom.xml
new file mode 100644
index 0000000..34d95bd
--- /dev/null
+++ b/tests/test-jmx/jmx-webapp-it/pom.xml
@@ -0,0 +1,207 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+// ========================================================================
+// Copyright (c) Webtide LLC
+// 
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at 
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.apache.org/licenses/LICENSE-2.0.txt
+//
+// You may elect to redistribute this code under either of these licenses. 
+// ========================================================================
+ -->
+<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">
+  <parent>
+    <groupId>org.eclipse.jetty.tests</groupId>
+    <artifactId>test-jmx-parent</artifactId>
+    <version>9.2.13.v20150730</version>
+  </parent>
+  <modelVersion>4.0.0</modelVersion>
+  <artifactId>jmx-webapp-it</artifactId>
+  <packaging>jar</packaging>
+  <name>Jetty Tests :: JMX :: WebApp Integration Tests</name>
+  <url>http://www.eclipse.org/jetty</url>
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+    <bundle-symbolic-name>${project.groupId}.jmx.webapp.it</bundle-symbolic-name>
+    <scripts-dir>${project.basedir}/src/test/scripts</scripts-dir>
+    <test-base-dir>${project.build.directory}/test-base</test-base-dir>
+    <test-home-dir>${project.build.directory}/test-home</test-home-dir>
+  </properties>
+  <dependencies>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-distribution</artifactId>
+      <version>${project.version}</version>
+      <type>zip</type>
+      <scope>runtime</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.tests</groupId>
+      <artifactId>jmx-webapp</artifactId>
+      <version>${project.version}</version>
+      <type>war</type>
+      <scope>runtime</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.toolchain</groupId>
+      <artifactId>jetty-test-helper</artifactId>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-dependency-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>copy-apps-for-testing</id>
+            <phase>process-test-resources</phase>
+            <goals>
+              <goal>copy-dependencies</goal>
+            </goals>
+            <configuration>
+              <includeArtifactIds>jmx-webapp</includeArtifactIds>
+              <includeScope>runtime</includeScope>
+              <includeTypes>war</includeTypes>
+              <overwriteSnapshots>true</overwriteSnapshots>
+              <overwriteReleases>true</overwriteReleases>
+              <stripVersion>true</stripVersion>
+              <outputDirectory>${test-base-dir}/webapps</outputDirectory>
+            </configuration>
+          </execution>
+          <execution>
+            <id>unpack-jetty-distro</id>
+            <phase>process-test-resources</phase>
+            <goals>
+              <goal>unpack-dependencies</goal>
+            </goals>
+            <configuration>
+              <includeArtifactIds>jetty-distribution</includeArtifactIds>
+              <includeScope>runtime</includeScope>
+              <includeTypes>zip</includeTypes>
+              <outputAbsoluteArtifactFilename>true</outputAbsoluteArtifactFilename>
+              <outputDirectory>${test-home-dir}</outputDirectory>
+              <overWriteSnapshots>true</overWriteSnapshots>
+              <overWriteIfNewer>true</overWriteIfNewer>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-failsafe-plugin</artifactId>
+        <version>2.17</version>
+        <executions>
+          <execution>
+            <goals>
+              <goal>integration-test</goal>
+              <goal>verify</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-antrun-plugin</artifactId>
+        <version>1.7</version>
+        <executions>
+          <execution>
+            <id>start-jetty</id>
+            <phase>pre-integration-test</phase>
+            <goals>
+              <goal>run</goal>
+            </goals>
+            <configuration>
+              <target>
+                <property name="jetty.jmx.home" location="${test-home-dir}/jetty-distribution-${project.version}" />
+                <property name="jetty.jmx.base" location="${test-base-dir}" />
+                <echo>Integration Test : Setup Jetty</echo>
+                <exec executable="${run.command}" dir="${scripts-dir}" spawn="false">
+                  <arg value="${run.command.xtra}" />
+                  <arg value="${setup.script}" />
+                  <arg file="${java.home}" />
+                  <arg file="${jetty.jmx.home}" />
+                  <arg file="${jetty.jmx.base}" />
+                </exec>
+
+                <echo>Integration Test : Starting Jetty ...</echo>
+                <exec executable="${run.command}" dir="${scripts-dir}" spawn="true">
+                  <arg value="${run.command.xtra}" />
+                  <arg value="${start.script}" />
+                  <arg file="${java.home}" />
+                  <arg file="${jetty.jmx.home}" />
+                  <arg file="${jetty.jmx.base}" />
+                </exec>
+                <waitfor maxwait="5" maxwaitunit="second" checkevery="500" checkeveryunit="millisecond">
+                  <http url="http://localhost:58080/jmx-webapp/" />
+                </waitfor>
+                <echo>Integration Test : Jetty is now available</echo>
+              </target>
+            </configuration>
+          </execution>
+          <execution>
+            <id>stop-jetty</id>
+            <phase>post-integration-test</phase>
+            <goals>
+              <goal>run</goal>
+            </goals>
+            <configuration>
+              <target>
+                <property name="jetty.jmx.home" location="${test-home-dir}/jetty-distribution-${project.version}" />
+                <property name="jetty.jmx.base" location="${test-base-dir}" />
+                <echo>Integration Test : Stop Jetty</echo>
+                <exec executable="${run.command}" dir="${scripts-dir}" spawn="false">
+                  <arg value="${run.command.xtra}" />
+                  <arg value="${stop.script}" />
+                  <arg file="${java.home}" />
+                  <arg file="${jetty.jmx.home}" />
+                  <arg file="${jetty.jmx.base}" />
+                </exec>
+              </target>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+  <profiles>
+    <profile>
+      <id>it-windows</id>
+      <activation>
+        <os>
+          <family>Windows</family>
+        </os>
+      </activation>
+      <properties>
+        <run.command>cmd</run.command>
+        <run.command.xtra>/c</run.command.xtra>
+        <start.script>start-jetty.bat</start.script>
+        <stop.script>stop-jetty.bat</stop.script>
+      </properties>
+    </profile>
+    <profile>
+      <id>it-unix</id>
+      <activation>
+        <os>
+          <family>unix</family>
+        </os>
+      </activation>
+      <properties>
+        <run.command>sh</run.command>
+        <run.command.xtra>--</run.command.xtra>
+        <setup.script>setup-jetty.sh</setup.script>
+        <start.script>start-jetty.sh</start.script>
+        <stop.script>stop-jetty.sh</stop.script>
+      </properties>
+    </profile>
+  </profiles>
+</project>
diff --git a/tests/test-jmx/jmx-webapp-it/src/test/java/org/eclipse/jetty/test/jmx/JmxIT.java b/tests/test-jmx/jmx-webapp-it/src/test/java/org/eclipse/jetty/test/jmx/JmxIT.java
new file mode 100644
index 0000000..aae9618
--- /dev/null
+++ b/tests/test-jmx/jmx-webapp-it/src/test/java/org/eclipse/jetty/test/jmx/JmxIT.java
@@ -0,0 +1,144 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.test.jmx;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+
+import javax.management.AttributeNotFoundException;
+import javax.management.InstanceNotFoundException;
+import javax.management.MBeanException;
+import javax.management.MBeanServerConnection;
+import javax.management.ObjectName;
+import javax.management.ReflectionException;
+import javax.management.remote.JMXConnector;
+import javax.management.remote.JMXConnectorFactory;
+import javax.management.remote.JMXServiceURL;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * Some JMX information tests.
+ */
+public class JmxIT
+{
+    private static JMXConnector jmxc;
+    private static MBeanServerConnection mbsc;
+
+    @BeforeClass
+    public static void connectToMBeanServer() throws IOException
+    {
+        JMXServiceURL url = new JMXServiceURL("service:jmx:rmi://localhost:1099/jndi/rmi://localhost:1099/jmxrmi");
+        jmxc = JMXConnectorFactory.connect(url,null);
+        mbsc = jmxc.getMBeanServerConnection();
+    }
+
+    @AfterClass
+    public static void disconnectFromMBeanServer() throws IOException
+    {
+        jmxc.close();
+    }
+
+    private String getStringAttribute(ObjectName objName, String attrName) throws Exception
+    {
+        Object val = mbsc.getAttribute(objName,attrName);
+        assertThat(attrName,val,notNullValue());
+        assertThat(attrName,val,instanceOf(String.class));
+        return (String)val;
+    }
+
+    private int getIntegerAttribute(ObjectName objName, String attrName) throws Exception
+    {
+        Object val = mbsc.getAttribute(objName,attrName);
+        assertThat(attrName,val,notNullValue());
+        assertThat(attrName,val,instanceOf(Integer.class));
+        return (Integer)val;
+    }
+
+    @Test
+    public void testObtainRunningServerVersion() throws Exception
+    {
+        ObjectName serverName = new ObjectName("org.eclipse.jetty.server:type=server,id=0");
+        String version = getStringAttribute(serverName,"version");
+        System.err.println("Running version: " + version);
+        assertThat("Version",version,startsWith("9.2."));
+    }
+
+    @Test
+    public void testObtainJmxWebAppState() throws Exception
+    {
+        ObjectName webappName = new ObjectName("org.eclipse.jetty.webapp:context=jmx-webapp,type=webappcontext,id=0");
+
+        String contextPath = getStringAttribute(webappName,"contextPath");
+        String displayName = getStringAttribute(webappName,"displayName");
+
+        assertThat("Context Path",contextPath,is("/jmx-webapp"));
+        assertThat("Display Name",displayName,is("Test JMX WebApp"));
+    }
+
+    /**
+     * Test for directly annotated POJOs in the JMX tree
+     */
+    @Test
+    public void testAccessToCommonComponent() throws Exception
+    {
+        ObjectName commonName = new ObjectName("org.eclipse.jetty.test.jmx:type=commoncomponent,context=jmx-webapp,id=0");
+        String name = getStringAttribute(commonName,"name");
+        assertThat("Name",name,is("i am common"));
+    }
+
+    /**
+     * Test for POJO (not annotated) that is supplemented with a MBean that
+     * declares the annotations.
+     */
+    @Test
+    public void testAccessToPingerMBean() throws Exception
+    {
+        ObjectName pingerName = new ObjectName("org.eclipse.jetty.test.jmx:type=pinger,context=jmx-webapp,id=0");
+        // Get initial count
+        int count = getIntegerAttribute(pingerName,"count");
+        // Operations
+        Object val = mbsc.invoke(pingerName,"ping",null,null);
+        assertThat("ping() return",val.toString(),startsWith("Pong"));
+        // Attributes
+        assertThat("count",getIntegerAttribute(pingerName,"count"),is(count+1));
+    }
+    
+    /**
+     * Test for POJO (annotated) that is merged with a MBean that
+     * declares more annotations.
+     */
+    @Test
+    public void testAccessToEchoerMBean() throws Exception
+    {
+        ObjectName echoerName = new ObjectName("org.eclipse.jetty.test.jmx:type=echoer,context=jmx-webapp,id=0");
+        // Get initial count
+        int count = getIntegerAttribute(echoerName,"count");
+        // Operations
+        Object val = mbsc.invoke(echoerName,"echo",new Object[]{"Its Me"},new String[]{String.class.getName()});
+        assertThat("echo() return",val.toString(),is("Its Me"));
+        // Attributes
+        assertThat("count",getIntegerAttribute(echoerName,"count"),is(count+1));
+        assertThat("foo",getStringAttribute(echoerName,"foo"),is("foo-ish"));
+    }
+}
diff --git a/tests/test-jmx/jmx-webapp-it/src/test/java/org/eclipse/jetty/test/jmx/PingIT.java b/tests/test-jmx/jmx-webapp-it/src/test/java/org/eclipse/jetty/test/jmx/PingIT.java
new file mode 100644
index 0000000..f852502
--- /dev/null
+++ b/tests/test-jmx/jmx-webapp-it/src/test/java/org/eclipse/jetty/test/jmx/PingIT.java
@@ -0,0 +1,41 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.test.jmx;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+import java.net.URI;
+
+import org.eclipse.jetty.toolchain.test.SimpleRequest;
+import org.junit.Test;
+
+/**
+ * Basic tests for a simple Servlet with no JMX involved (yet)
+ */
+public class PingIT
+{
+    @Test
+    public void testBasic() throws Exception
+    {
+        URI serverURI = new URI("http://localhost:58080/jmx-webapp/");
+        SimpleRequest req = new SimpleRequest(serverURI);
+        assertThat(req.getString("ping"),startsWith("Servlet Pong at "));
+    }
+}
diff --git a/tests/test-jmx/jmx-webapp-it/src/test/scripts/setup-jetty.sh b/tests/test-jmx/jmx-webapp-it/src/test/scripts/setup-jetty.sh
new file mode 100755
index 0000000..22beac1
--- /dev/null
+++ b/tests/test-jmx/jmx-webapp-it/src/test/scripts/setup-jetty.sh
@@ -0,0 +1,19 @@
+#!/usr/bin/env bash
+
+JAVA_HOME=$1
+JETTY_HOME=$2
+JETTY_BASE=$3
+
+echo \${java.home}  : $JAVA_HOME
+echo \${jetty.home} : $JETTY_HOME
+echo \${jetty.base} : $JETTY_BASE
+
+cd "$JETTY_BASE"
+
+"$JAVA_HOME/bin/java" -jar "$JETTY_HOME/start.jar" \
+    --approve-all-licenses \
+    --add-to-start=deploy,http,annotations,jmx,jmx-remote,logging
+
+"$JAVA_HOME/bin/java" -jar "$JETTY_HOME/start.jar" \
+    --version
+
diff --git a/tests/test-jmx/jmx-webapp-it/src/test/scripts/start-jetty.sh b/tests/test-jmx/jmx-webapp-it/src/test/scripts/start-jetty.sh
new file mode 100755
index 0000000..1d0f9eb
--- /dev/null
+++ b/tests/test-jmx/jmx-webapp-it/src/test/scripts/start-jetty.sh
@@ -0,0 +1,17 @@
+#!/usr/bin/env bash
+
+JAVA_HOME=$1
+JETTY_HOME=$2
+JETTY_BASE=$3
+
+echo \${java.home}  : $JAVA_HOME
+echo \${jetty.home} : $JETTY_HOME
+echo \${jetty.base} : $JETTY_BASE
+
+cd "$JETTY_BASE"
+
+"$JAVA_HOME/bin/java" -jar "$JETTY_HOME/start.jar" \
+    jetty.port=58080 \
+    STOP.PORT=58181 STOP.KEY=it
+
+
diff --git a/tests/test-jmx/jmx-webapp-it/src/test/scripts/stop-jetty.sh b/tests/test-jmx/jmx-webapp-it/src/test/scripts/stop-jetty.sh
new file mode 100755
index 0000000..32e4077
--- /dev/null
+++ b/tests/test-jmx/jmx-webapp-it/src/test/scripts/stop-jetty.sh
@@ -0,0 +1,11 @@
+#!/usr/bin/env bash
+
+JAVA_HOME=$1
+JETTY_HOME=$2
+JETTY_BASE=$3
+
+cd "$JETTY_BASE"
+"$JAVA_HOME/bin/java" -jar "$JETTY_HOME/start.jar" \
+ --stop STOP.PORT=58181 STOP.KEY=it
+
+
diff --git a/tests/test-jmx/jmx-webapp/pom.xml b/tests/test-jmx/jmx-webapp/pom.xml
new file mode 100644
index 0000000..229e28c
--- /dev/null
+++ b/tests/test-jmx/jmx-webapp/pom.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  // ========================================================================
+  // Copyright (c) Webtide LLC
+  //
+  // All rights reserved. This program and the accompanying materials
+  // are made available under the terms of the Eclipse Public License v1.0
+  // and Apache License v2.0 which accompanies this distribution.
+  //
+  // The Eclipse Public License is available at
+  // http://www.eclipse.org/legal/epl-v10.html
+  //
+  // The Apache License v2.0 is available at
+  // http://www.apache.org/licenses/LICENSE-2.0.txt
+  //
+  // You may elect to redistribute this code under either of these licenses.
+  // ========================================================================
+-->
+<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>
+  <parent>
+    <groupId>org.eclipse.jetty.tests</groupId>
+    <artifactId>test-jmx-parent</artifactId>
+    <version>9.2.13.v20150730</version>
+  </parent>
+  <artifactId>jmx-webapp</artifactId>
+  <packaging>war</packaging>
+  <name>Jetty Tests :: JMX :: WebApp</name>
+  <url>http://www.eclipse.org/jetty</url>
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    <bundle-symbolic-name>${project.groupId}.jmx.webapp</bundle-symbolic-name>
+  </properties>
+  <dependencies>
+    <dependency>
+      <groupId>javax.servlet</groupId>
+      <artifactId>javax.servlet-api</artifactId>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-jmx</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+  </dependencies>
+  <build>
+    <finalName>jmx-webapp</finalName>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-deploy-plugin</artifactId>
+        <configuration>
+          <!-- DO NOT DEPLOY (or Release) -->
+          <skip>true</skip>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-war-plugin</artifactId>
+        <configuration>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+</project>
diff --git a/tests/test-jmx/jmx-webapp/src/main/java/org/eclipse/jetty/test/jmx/CommonComponent.java b/tests/test-jmx/jmx-webapp/src/main/java/org/eclipse/jetty/test/jmx/CommonComponent.java
new file mode 100644
index 0000000..702e628
--- /dev/null
+++ b/tests/test-jmx/jmx-webapp/src/main/java/org/eclipse/jetty/test/jmx/CommonComponent.java
@@ -0,0 +1,49 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.test.jmx;
+
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+ at ManagedObject("A common component available in the webapp")
+public class CommonComponent extends AbstractLifeCycle
+{
+    private static final Logger LOG = Log.getLogger(CommonComponent.class);
+    
+    public CommonComponent()
+    {
+        LOG.info("Created " + this.getClass().getName());
+    }
+    
+    private String name = "i am common";
+
+    @ManagedAttribute("The name being tracked")
+    public String getName()
+    {
+        return name;
+    }
+
+    public void setName(String name)
+    {
+        this.name = name;
+    }
+}
diff --git a/tests/test-jmx/jmx-webapp/src/main/java/org/eclipse/jetty/test/jmx/Echoer.java b/tests/test-jmx/jmx-webapp/src/main/java/org/eclipse/jetty/test/jmx/Echoer.java
new file mode 100644
index 0000000..f1b1355
--- /dev/null
+++ b/tests/test-jmx/jmx-webapp/src/main/java/org/eclipse/jetty/test/jmx/Echoer.java
@@ -0,0 +1,43 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.test.jmx;
+
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.annotation.ManagedOperation;
+import org.eclipse.jetty.util.annotation.Name;
+
+ at ManagedObject("Echoer")
+public class Echoer
+{
+    private int count = 0;
+
+    @ManagedAttribute("Number of echos")
+    public int getCount()
+    {
+        return count;
+    }
+
+    @ManagedOperation("Echo a string")
+    public String echo(@Name("val") String val)
+    {
+        count++;
+        return val;
+    }
+}
diff --git a/tests/test-jmx/jmx-webapp/src/main/java/org/eclipse/jetty/test/jmx/MyContainerInitializer.java b/tests/test-jmx/jmx-webapp/src/main/java/org/eclipse/jetty/test/jmx/MyContainerInitializer.java
new file mode 100644
index 0000000..00f068c
--- /dev/null
+++ b/tests/test-jmx/jmx-webapp/src/main/java/org/eclipse/jetty/test/jmx/MyContainerInitializer.java
@@ -0,0 +1,46 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.test.jmx;
+
+import java.util.Set;
+
+import javax.servlet.ServletContainerInitializer;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public class MyContainerInitializer implements ServletContainerInitializer
+{
+    private static final Logger LOG = Log.getLogger(MyContainerInitializer.class);
+
+    @Override
+    public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException
+    {
+        // Directly annotated with @ManagedObject
+        CommonComponent common = new CommonComponent();
+        LOG.info("Initializing " + common.getClass().getName());
+        ctx.setAttribute("org.eclipse.jetty.test.jmx.common",common);
+        
+        // Indirectly managed via a MBean
+        ctx.setAttribute("org.eclipse.jetty.test.jmx.ping",new Pinger());
+        ctx.setAttribute("org.eclipse.jetty.test.jmx.echo",new Echoer());
+    }
+}
diff --git a/tests/test-jmx/jmx-webapp/src/main/java/org/eclipse/jetty/test/jmx/PingServlet.java b/tests/test-jmx/jmx-webapp/src/main/java/org/eclipse/jetty/test/jmx/PingServlet.java
new file mode 100644
index 0000000..4be608a
--- /dev/null
+++ b/tests/test-jmx/jmx-webapp/src/main/java/org/eclipse/jetty/test/jmx/PingServlet.java
@@ -0,0 +1,64 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.test.jmx;
+
+import java.io.IOException;
+import java.util.Date;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.annotation.ManagedOperation;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * Simple ping into this webapp to see if it is here.
+ */
+ at SuppressWarnings("serial")
+ at ManagedObject("Ping Servlet")
+public class PingServlet extends HttpServlet
+{
+    private static final Logger LOG = Log.getLogger(PingServlet.class);
+    
+    @Override
+    public void init(ServletConfig config) throws ServletException
+    {
+        LOG.info("Adding {} to attribute {}", this, config.getServletName());
+        config.getServletContext().setAttribute(config.getServletName(),this);
+        super.init(config);
+    }
+
+    @Override
+    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+    {
+        resp.setContentType("text/plain");
+        resp.getWriter().println(ping());
+    }
+
+    @ManagedOperation
+    public String ping()
+    {
+        return "Servlet Pong at " + new Date().toString();
+    }
+}
diff --git a/tests/test-jmx/jmx-webapp/src/main/java/org/eclipse/jetty/test/jmx/Pinger.java b/tests/test-jmx/jmx-webapp/src/main/java/org/eclipse/jetty/test/jmx/Pinger.java
new file mode 100644
index 0000000..c7845ef
--- /dev/null
+++ b/tests/test-jmx/jmx-webapp/src/main/java/org/eclipse/jetty/test/jmx/Pinger.java
@@ -0,0 +1,40 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.test.jmx;
+
+import java.util.Date;
+
+/**
+ * Bare POJO, intentionally has no managed annotations.
+ */
+public class Pinger
+{
+    private int count = 0;
+    
+    public int getCount()
+    {
+        return count;
+    }
+    
+    public String ping()
+    {
+        count++;
+        return "Ponger at " + new Date().toString();
+    }
+}
diff --git a/tests/test-jmx/jmx-webapp/src/main/java/org/eclipse/jetty/test/jmx/jmx/EchoerMBean.java b/tests/test-jmx/jmx-webapp/src/main/java/org/eclipse/jetty/test/jmx/jmx/EchoerMBean.java
new file mode 100644
index 0000000..97bae5d
--- /dev/null
+++ b/tests/test-jmx/jmx-webapp/src/main/java/org/eclipse/jetty/test/jmx/jmx/EchoerMBean.java
@@ -0,0 +1,38 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.test.jmx.jmx;
+
+import org.eclipse.jetty.jmx.ObjectMBean;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+
+ at ManagedObject("Echoer (mbean)")
+public class EchoerMBean extends ObjectMBean
+{
+    public EchoerMBean(Object managedObject)
+    {
+        super(managedObject);
+    }
+
+    @ManagedAttribute(value="Gets the value of foo",proxied=true)
+    public String getFoo()
+    {
+        return "foo-ish";
+    }
+}
diff --git a/tests/test-jmx/jmx-webapp/src/main/java/org/eclipse/jetty/test/jmx/jmx/PingerMBean.java b/tests/test-jmx/jmx-webapp/src/main/java/org/eclipse/jetty/test/jmx/jmx/PingerMBean.java
new file mode 100644
index 0000000..f5b65e5
--- /dev/null
+++ b/tests/test-jmx/jmx-webapp/src/main/java/org/eclipse/jetty/test/jmx/jmx/PingerMBean.java
@@ -0,0 +1,51 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.test.jmx.jmx;
+
+import org.eclipse.jetty.jmx.ObjectMBean;
+import org.eclipse.jetty.test.jmx.Pinger;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.annotation.ManagedOperation;
+
+ at ManagedObject("Pinger facility")
+public class PingerMBean extends ObjectMBean
+{
+    public PingerMBean(Object managedObject)
+    {
+        super(managedObject);
+    }
+
+    private Pinger getPinger()
+    {
+        return (Pinger)getManagedObject();
+    }
+
+    @ManagedOperation("Issue Ping")
+    public String ping()
+    {
+        return getPinger().ping();
+    }
+
+    @ManagedAttribute("Count of pings")
+    public int getCount()
+    {
+        return getPinger().getCount();
+    }
+}
diff --git a/tests/test-jmx/jmx-webapp/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer b/tests/test-jmx/jmx-webapp/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer
new file mode 100644
index 0000000..2e64dd7
--- /dev/null
+++ b/tests/test-jmx/jmx-webapp/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer
@@ -0,0 +1 @@
+org.eclipse.jetty.test.jmx.MyContainerInitializer
\ No newline at end of file
diff --git a/tests/test-jmx/jmx-webapp/src/main/webapp/WEB-INF/web.xml b/tests/test-jmx/jmx-webapp/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000..68fd25d
--- /dev/null
+++ b/tests/test-jmx/jmx-webapp/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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" 
+   metadata-complete="true"
+   version="2.5"> 
+
+  <display-name>Test JMX WebApp</display-name>
+  
+  <context-param>
+    <param-name>org.eclipse.jetty.server.context.ManagedAttributes</param-name>
+    <param-value>
+        org.eclipse.jetty.test.jmx.common,
+        org.eclipse.jetty.test.jmx.ping,
+        org.eclipse.jetty.test.jmx.echo,
+        Ping
+     </param-value>
+  </context-param>
+  
+  <servlet>
+    <servlet-name>Ping</servlet-name>
+    <servlet-class>org.eclipse.jetty.test.jmx.PingServlet</servlet-class>
+    <load-on-startup>1</load-on-startup>
+  </servlet>
+
+  <servlet-mapping>
+    <servlet-name>Ping</servlet-name>
+    <url-pattern>/ping</url-pattern>
+  </servlet-mapping>
+
+</web-app>
+
+
diff --git a/tests/test-jmx/pom.xml b/tests/test-jmx/pom.xml
new file mode 100644
index 0000000..65db2ab
--- /dev/null
+++ b/tests/test-jmx/pom.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+// ========================================================================
+// Copyright (c) Webtide LLC
+// 
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at 
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.apache.org/licenses/LICENSE-2.0.txt
+//
+// You may elect to redistribute this code under either of these licenses. 
+// ========================================================================
+ -->
+<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">
+  <parent>
+    <groupId>org.eclipse.jetty.tests</groupId>
+    <artifactId>tests-parent</artifactId>
+    <version>9.2.13.v20150730</version>
+  </parent>
+  <modelVersion>4.0.0</modelVersion>
+  <artifactId>test-jmx-parent</artifactId>
+  <packaging>pom</packaging>
+  <name>Jetty Tests :: JMX Parent</name>
+  <url>http://www.eclipse.org/jetty</url>
+  <modules>
+    <module>jmx-webapp</module>
+    <module>jmx-webapp-it</module>
+  </modules>
+</project>
diff --git a/tests/test-loginservice/pom.xml b/tests/test-loginservice/pom.xml
index 10761ac..53b07ff 100644
--- a/tests/test-loginservice/pom.xml
+++ b/tests/test-loginservice/pom.xml
@@ -21,48 +21,58 @@
   <parent>
     <groupId>org.eclipse.jetty.tests</groupId>
     <artifactId>tests-parent</artifactId>
-    <version>8.1.17.v20150415</version>
+    <version>9.2.13.v20150730</version>
   </parent>
   <artifactId>test-loginservice</artifactId>
   <name>Jetty Tests :: Login Service</name>
   <url>http://www.eclipse.org/jetty</url>
  <dependencies>
-       <dependency>
-            <groupId>org.eclipse.jetty</groupId>
-            <artifactId>jetty-server</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-        <dependency>
-            <groupId>org.eclipse.jetty</groupId>
-            <artifactId>jetty-webapp</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-        <dependency>
-            <groupId>org.eclipse.jetty</groupId>
-            <artifactId>jetty-client</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-        <dependency>
-            <groupId>org.eclipse.jetty</groupId>
-            <artifactId>jetty-security</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.derby</groupId>
-            <artifactId>derby</artifactId>
-            <version>10.4.1.3</version>
-            <scope>test</scope>
-         </dependency>
-         <dependency>
-            <groupId>org.apache.derby</groupId>
-            <artifactId>derbytools</artifactId>
-            <version>10.4.1.3</version>
-            <scope>test</scope>
-       </dependency>
-       <dependency>
-            <groupId>junit</groupId>
-            <artifactId>junit</artifactId>
-            <scope>test</scope>
-        </dependency>
+	<dependency>
+		<groupId>org.eclipse.jetty</groupId>
+		<artifactId>jetty-server</artifactId>
+		<version>${project.version}</version>
+	</dependency>
+	<dependency>
+		<groupId>org.eclipse.jetty</groupId>
+		<artifactId>jetty-webapp</artifactId>
+		<version>${project.version}</version>
+	</dependency>
+	<dependency>
+		<groupId>org.eclipse.jetty</groupId>
+		<artifactId>jetty-client</artifactId>
+		<version>${project.version}</version>
+	</dependency>
+	<dependency>
+		<groupId>org.eclipse.jetty</groupId>
+		<artifactId>jetty-security</artifactId>
+		<version>${project.version}</version>
+	</dependency>
+	<dependency>
+		<groupId>org.eclipse.jetty</groupId>
+		<artifactId>jetty-plus</artifactId>
+		<version>${project.version}</version>
+	</dependency>	
+	<dependency>
+		<groupId>org.eclipse.jetty</groupId>
+		<artifactId>jetty-jndi</artifactId>
+		<version>${project.version}</version>
+	</dependency>
+	<dependency>
+		<groupId>org.apache.derby</groupId>
+		<artifactId>derby</artifactId>
+		<version>10.4.1.3</version>
+		<scope>test</scope>
+	</dependency>
+	<dependency>
+		<groupId>org.apache.derby</groupId>
+		<artifactId>derbytools</artifactId>
+		<version>10.4.1.3</version>
+		<scope>test</scope>
+	</dependency>
+	<dependency>
+		<groupId>org.eclipse.jetty.toolchain</groupId>
+		<artifactId>jetty-test-helper</artifactId>
+		<scope>test</scope>
+	</dependency>
   </dependencies>
 </project>
diff --git a/tests/test-loginservice/src/test/java/org/eclipse/jetty/DataSourceLoginServiceTest.java b/tests/test-loginservice/src/test/java/org/eclipse/jetty/DataSourceLoginServiceTest.java
new file mode 100644
index 0000000..185fb8c
--- /dev/null
+++ b/tests/test-loginservice/src/test/java/org/eclipse/jetty/DataSourceLoginServiceTest.java
@@ -0,0 +1,204 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.net.URI;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.Statement;
+import java.util.concurrent.TimeUnit;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.derby.jdbc.EmbeddedDataSource;
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.api.AuthenticationStore;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.util.BasicAuthentication;
+import org.eclipse.jetty.plus.security.DataSourceLoginService;
+import org.eclipse.jetty.toolchain.test.FS;
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.eclipse.jetty.util.Loader;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * DataSourceLoginServiceTest
+ *
+ *
+ */
+public class DataSourceLoginServiceTest
+{
+    
+    
+    public static final String _content = "This is some protected content";
+    private static File _docRoot;
+    private static HttpClient _client;
+    private static String __realm = "DSRealm";
+    private static URI _baseUri;
+    private static final int __cacheInterval = 200;
+    private static DatabaseLoginServiceTestServer _testServer;
+
+
+    
+    
+    @BeforeClass
+    public static void setUp() throws Exception
+    {
+       
+        _docRoot = MavenTestingUtils.getTargetTestingDir("loginservice-test");
+        FS.ensureDirExists(_docRoot);
+        
+        File content = new File(_docRoot,"input.txt");
+        FileOutputStream out = new FileOutputStream(content);
+        out.write(_content.getBytes("utf-8"));
+        out.close();
+
+        
+        //clear previous runs
+        File scriptFile = MavenTestingUtils.getTestResourceFile("droptables.sql");
+        int result = DatabaseLoginServiceTestServer.runscript(scriptFile);
+        //ignore result as derby spits errors for dropping tables that dont exist
+        
+        //create afresh
+        scriptFile = MavenTestingUtils.getTestResourceFile("createdb.sql");
+        result = DatabaseLoginServiceTestServer.runscript(scriptFile);
+         assertThat("runScript result",result, is(0));
+         
+        _testServer = new DatabaseLoginServiceTestServer();
+        _testServer.setResourceBase(_docRoot.getAbsolutePath());
+        _testServer.setLoginService(configureLoginService());
+        _testServer.start();
+        _baseUri = _testServer.getBaseUri();
+     }
+
+     @AfterClass
+     public static void tearDown()
+         throws Exception
+     {
+         if (_testServer != null)
+         {
+             _testServer.stop();
+             _testServer = null;
+         }
+     }
+     
+     public static DataSourceLoginService configureLoginService () throws Exception
+     {
+         DataSourceLoginService loginService = new DataSourceLoginService();
+         loginService.setUserTableName("users");
+         loginService.setUserTableKey("id");
+         loginService.setUserTableUserField("username");
+         loginService.setUserTablePasswordField("pwd");
+         loginService.setRoleTableName("roles");
+         loginService.setRoleTableKey("id");
+         loginService.setRoleTableRoleField("role");
+         loginService.setUserRoleTableName("user_roles");
+         loginService.setUserRoleTableRoleKey("role_id");
+         loginService.setUserRoleTableUserKey("user_id");
+         loginService.setJndiName("dstest");
+         loginService.setName(__realm);
+         loginService.setCacheMs(__cacheInterval);
+         if (_testServer != null)
+             loginService.setServer(_testServer.getServer());
+         
+         //create a datasource
+         EmbeddedDataSource ds = new EmbeddedDataSource();
+         File db = new File (DatabaseLoginServiceTestServer.getDbRoot(), "loginservice");
+         ds.setDatabaseName(db.getAbsolutePath());
+         org.eclipse.jetty.plus.jndi.Resource binding = new org.eclipse.jetty.plus.jndi.Resource(null, "dstest",
+                                                                                                      ds);
+         assertThat("Created binding for dstest", binding, notNullValue());
+         return loginService;
+     }
+     
+     @Test
+     public void testGetAndPasswordUpdate() throws Exception
+     {
+         try
+         {
+             startClient("jetty", "jetty");
+
+             ContentResponse response = _client.GET(_baseUri.resolve("input.txt"));
+             assertEquals(HttpServletResponse.SC_OK,response.getStatus());
+             assertEquals(_content, response.getContentAsString());
+             
+             stopClient();
+             
+             String newpwd = String.valueOf(System.currentTimeMillis());
+             
+             changePassword("jetty", newpwd);
+             TimeUnit.MILLISECONDS.sleep(2*__cacheInterval);  //pause to ensure cache invalidates
+             
+             startClient("jetty", newpwd);
+             
+             response = _client.GET(_baseUri.resolve("input.txt"));
+             assertEquals(HttpServletResponse.SC_OK,response.getStatus());
+             assertEquals(_content, response.getContentAsString());
+             
+         }
+         finally
+         {
+             stopClient();
+         }
+     }
+     
+     
+     protected void changePassword (String user, String newpwd) throws Exception
+     {
+         Loader.loadClass(this.getClass(), "org.apache.derby.jdbc.EmbeddedDriver").newInstance();
+         try (Connection connection = DriverManager.getConnection(DatabaseLoginServiceTestServer.__dbURL, "", "");
+              Statement stmt = connection.createStatement())
+         {
+             connection.setAutoCommit(true);
+             stmt.executeUpdate("update users set pwd='"+newpwd+"' where username='"+user+"'");
+         }
+         
+     }
+
+
+     protected void startClient(String user, String pwd) throws Exception
+     {
+         _client = new HttpClient();
+         QueuedThreadPool executor = new QueuedThreadPool();
+         executor.setName(executor.getName() + "-client");
+         _client.setExecutor(executor);
+         AuthenticationStore authStore = _client.getAuthenticationStore();
+         authStore.addAuthentication(new BasicAuthentication(_baseUri, __realm, user, pwd));
+         _client.start();
+     }
+
+     protected void stopClient() throws Exception
+     {
+         if (_client != null)
+         {
+             _client.stop();
+             _client = null;
+         }
+     }
+
+}
diff --git a/tests/test-loginservice/src/test/java/org/eclipse/jetty/DatabaseLoginServiceTestServer.java b/tests/test-loginservice/src/test/java/org/eclipse/jetty/DatabaseLoginServiceTestServer.java
new file mode 100644
index 0000000..9237803
--- /dev/null
+++ b/tests/test-loginservice/src/test/java/org/eclipse/jetty/DatabaseLoginServiceTestServer.java
@@ -0,0 +1,250 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.URI;
+import java.net.URLDecoder;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.servlet.ServletException;
+import javax.servlet.ServletInputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.derby.tools.ij;
+import org.eclipse.jetty.security.ConstraintMapping;
+import org.eclipse.jetty.security.ConstraintSecurityHandler;
+import org.eclipse.jetty.security.LoginService;
+import org.eclipse.jetty.security.authentication.BasicAuthenticator;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.server.handler.HandlerCollection;
+import org.eclipse.jetty.servlet.DefaultServlet;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jetty.toolchain.test.FS;
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.Loader;
+import org.eclipse.jetty.util.security.Constraint;
+
+
+/**
+ * DatabaseLoginServiceTestServer
+ */
+public class DatabaseLoginServiceTestServer
+{
+    protected static String __dbURL = "jdbc:derby:loginservice;create=true";
+    protected Server _server;
+    protected static String _protocol;
+    protected static URI _baseUri;
+    protected LoginService _loginService;
+    protected String _resourceBase;
+    protected TestHandler _handler;
+    private static File commonDerbySystemHome;
+    protected static String _requestContent;
+    
+    protected static File _dbRoot;
+
+
+    static
+    {
+        _dbRoot = new File(MavenTestingUtils.getTargetTestingDir("loginservice-test"), "derby");
+        FS.ensureDirExists(_dbRoot);
+        System.setProperty("derby.system.home", _dbRoot.getAbsolutePath());
+    }
+
+    public static File getDbRoot ()
+    {
+        return _dbRoot;
+    }
+
+    public static int runscript (File scriptFile) throws Exception
+    {  
+        //System.err.println("Running script:"+scriptFile.getAbsolutePath());
+        try (FileInputStream fileStream = new FileInputStream(scriptFile))
+        {
+            Loader.loadClass(fileStream.getClass(), "org.apache.derby.jdbc.EmbeddedDriver").newInstance();
+            Connection connection = DriverManager.getConnection(__dbURL, "", "");
+            ByteArrayOutputStream out = new ByteArrayOutputStream();
+            return ij.runScript(connection, fileStream, "UTF-8", out, "UTF-8");
+        }
+    }
+  
+    public static class TestHandler extends AbstractHandler 
+    {
+        private final String _resourcePath;
+        private String _requestContent;
+        
+
+        public TestHandler(String repositoryPath) 
+        {
+            _resourcePath = repositoryPath;
+        }
+
+        public void handle(String target, org.eclipse.jetty.server.Request baseRequest,
+                HttpServletRequest request, HttpServletResponse response)
+            throws IOException, ServletException
+        {
+            if (baseRequest.isHandled())
+            {
+                return;
+            }
+
+            OutputStream out = null;
+
+            if (baseRequest.getMethod().equals("PUT"))
+            {
+                baseRequest.setHandled(true);
+
+                File file = new File(_resourcePath, URLDecoder.decode(request.getPathInfo(), "utf-8"));
+                FS.ensureDirExists(file.getParentFile());
+
+                out = new FileOutputStream(file);
+
+                response.setStatus(HttpServletResponse.SC_CREATED);
+            }
+
+            if (baseRequest.getMethod().equals("POST"))
+            {
+                baseRequest.setHandled(true);
+                out = new ByteArrayOutputStream();
+
+                response.setStatus(HttpServletResponse.SC_OK);
+            }
+
+            if (out != null)
+            {
+                try (ServletInputStream in = request.getInputStream())
+                {
+                    IO.copy(in, out);
+                }
+                finally
+                {
+                    out.close();
+                }
+
+                if (!(out instanceof FileOutputStream))
+                    _requestContent = out.toString();
+            }
+        }
+        
+        public String getRequestContent()
+        {
+            return _requestContent;
+        }
+    }
+    
+    
+    public DatabaseLoginServiceTestServer ()
+    {
+        _server = new Server(0);
+    }
+    
+    public void setLoginService (LoginService loginService)
+    {
+        _loginService = loginService;
+    }
+    
+    public void setResourceBase (String resourceBase)
+    {
+        _resourceBase = resourceBase;
+    }
+ 
+    
+    public void start () throws Exception
+    {
+        configureServer();
+        _server.start();
+        //_server.dumpStdErr();
+        _baseUri = _server.getURI();
+    }
+    
+    public void stop() throws Exception
+    {
+        _server.stop();
+    }
+    
+    public URI getBaseUri()
+    {
+        return _baseUri;
+    }
+   
+    public TestHandler getTestHandler()
+    {
+        return _handler;
+    }
+ 
+    public Server getServer()
+    {
+        return _server;
+    }
+    
+    protected void configureServer() throws Exception
+    {
+        _protocol = "http";
+        _server.addBean(_loginService);
+
+        ConstraintSecurityHandler security = new ConstraintSecurityHandler();
+        _server.setHandler(security);
+
+        Constraint constraint = new Constraint();
+        constraint.setName("auth");
+        constraint.setAuthenticate( true );
+        constraint.setRoles(new String[]{"user", "admin"});
+
+        ConstraintMapping mapping = new ConstraintMapping();
+        mapping.setPathSpec( "/*" );
+        mapping.setConstraint( constraint );
+
+        Set<String> knownRoles = new HashSet<>();
+        knownRoles.add("user");
+        knownRoles.add("admin");
+
+        security.setConstraintMappings(Collections.singletonList(mapping), knownRoles);
+        security.setAuthenticator(new BasicAuthenticator());
+        security.setLoginService(_loginService);
+
+        ServletContextHandler root = new ServletContextHandler();
+        root.setContextPath("/");
+        root.setResourceBase(_resourceBase);
+        ServletHolder servletHolder = new ServletHolder( new DefaultServlet() );
+        servletHolder.setInitParameter( "gzip", "true" );
+        root.addServlet( servletHolder, "/*" );
+
+        _handler = new TestHandler(_resourceBase);
+
+        HandlerCollection handlers = new HandlerCollection();
+        handlers.setHandlers(new Handler[]{_handler, root});
+        security.setHandler(handlers);
+    }
+
+}
diff --git a/tests/test-loginservice/src/test/java/org/eclipse/jetty/JdbcLoginServiceTest.java b/tests/test-loginservice/src/test/java/org/eclipse/jetty/JdbcLoginServiceTest.java
index 2c58544..288a922 100644
--- a/tests/test-loginservice/src/test/java/org/eclipse/jetty/JdbcLoginServiceTest.java
+++ b/tests/test-loginservice/src/test/java/org/eclipse/jetty/JdbcLoginServiceTest.java
@@ -18,62 +18,41 @@
 
 package org.eclipse.jetty;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
 
-import java.io.ByteArrayOutputStream;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
 import java.io.File;
 import java.io.FileInputStream;
-import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.UnsupportedEncodingException;
-import java.net.URLDecoder;
-import java.sql.Connection;
-import java.sql.DriverManager;
-import java.util.HashSet;
-import java.util.Collections;
-import java.util.Set;
+import java.net.URI;
 
-import javax.servlet.ServletException;
-import javax.servlet.ServletInputStream;
-import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import org.apache.derby.tools.ij;
-import org.eclipse.jetty.client.ContentExchange;
 import org.eclipse.jetty.client.HttpClient;
-import org.eclipse.jetty.client.security.Realm;
-import org.eclipse.jetty.client.security.SimpleRealmResolver;
-import org.eclipse.jetty.http.HttpMethods;
+import org.eclipse.jetty.client.api.AuthenticationStore;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.util.BasicAuthentication;
+import org.eclipse.jetty.client.util.BytesContentProvider;
+import org.eclipse.jetty.http.HttpMethod;
 import org.eclipse.jetty.http.HttpStatus;
-import org.eclipse.jetty.util.security.Constraint;
-import org.eclipse.jetty.io.ByteArrayBuffer;
-import org.eclipse.jetty.io.EofException;
-import org.eclipse.jetty.security.ConstraintMapping;
-import org.eclipse.jetty.security.ConstraintSecurityHandler;
 import org.eclipse.jetty.security.JDBCLoginService;
 import org.eclipse.jetty.security.LoginService;
-import org.eclipse.jetty.security.authentication.BasicAuthenticator;
-import org.eclipse.jetty.server.Handler;
-import org.eclipse.jetty.server.Request;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.handler.AbstractHandler;
-import org.eclipse.jetty.server.handler.HandlerCollection;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
-import org.eclipse.jetty.servlet.DefaultServlet;
-import org.eclipse.jetty.servlet.ServletContextHandler;
-import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jetty.toolchain.test.FS;
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
 import org.eclipse.jetty.util.IO;
-import org.eclipse.jetty.util.Loader;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
+import org.junit.Ignore;
 import org.junit.Test;
 
 public class JdbcLoginServiceTest
-{ 
+{
+ 
+    
     private static String _content =
         "Lorem ipsum dolor sit amet, consectetur adipiscing elit. In quis felis nunc. "+
         "Quisque suscipit mauris et ante auctor ornare rhoncus lacus aliquet. Pellentesque "+
@@ -89,248 +68,149 @@ public class JdbcLoginServiceTest
         "et cursus magna. Donec orci enim, molestie a lobortis eu, imperdiet vitae neque.";
 
     private static File _docRoot;
-    private static Server _server;
     private static HttpClient _client;
-    private static Realm _realm;
-    private static String _protocol;
-    private static String _baseUrl;
-    private static String _requestContent;
+    private static String __realm = "JdbcRealm";
+    private static URI _baseUri;
+    private static DatabaseLoginServiceTestServer _testServer;
     
-    protected static boolean createDB(String homeDir, String fileName, String dbUrl)
-    {
-        FileInputStream fileStream = null;
-        try
-        {
-            File scriptFile = new File(fileName);
-            fileStream = new FileInputStream(scriptFile);
-            
-            Loader.loadClass(fileStream.getClass(), "org.apache.derby.jdbc.EmbeddedDriver").newInstance();
-            Connection connection = DriverManager.getConnection(dbUrl, "", "");
-            
-            OutputStream out = new ByteArrayOutputStream();
-            int result = ij.runScript(connection, fileStream, "UTF-8", out, "UTF-8");
-
-            return (result==0);
-        }
-        catch (Exception e)
-        {
-            return false;
-        }
-        finally {
-            if (fileStream!=null)
-            {
-                try
-                {
-                    fileStream.close();
-                }
-                catch (IOException e) {}
-            }
-        }
-    }
+ 
 
-    protected static void configureServer(Server server)
-        throws Exception
+    @BeforeClass
+    public static void setUp() throws Exception
     {
-        setProtocol("http");
-        setRealm(new Realm()
-                 {
-                     public String getId()
-                     {
-                         return "JdbcRealm";
-                     }
-                
-                     public String getPrincipal()
-                     {
-                         return "jetty";
-                     }
-                
-                     public String getCredentials()
-                     {
-                         return "jetty";
-                     }
-                 });
-                        
-        SelectChannelConnector connector = new SelectChannelConnector();
-        server.addConnector(connector);
+        _docRoot = MavenTestingUtils.getTargetTestingDir("loginservice-test");
+        FS.ensureDirExists(_docRoot);
+        File content = new File(_docRoot,"input.txt");
+      
         
-        LoginService loginService = new JDBCLoginService("JdbcRealm", "./src/test/resources/jdbcrealm.properties");
-        server.addBean(loginService); 
-
-        ConstraintSecurityHandler security = new ConstraintSecurityHandler();
-        server.setHandler(security);
-
-        Constraint constraint = new Constraint();
-        constraint.setName("auth");
-        constraint.setAuthenticate( true );
-        constraint.setRoles(new String[]{"user", "admin"});
-
-        ConstraintMapping mapping = new ConstraintMapping();
-        mapping.setPathSpec( "/*" );
-        mapping.setConstraint( constraint );
+        try (FileOutputStream out = new FileOutputStream(content))
+        {
+            out.write(_content.getBytes("utf-8"));
+        }
 
-        Set<String> knownRoles = new HashSet<String>();
-        knownRoles.add("user");
-        knownRoles.add("admin");
+        //drop any tables that might have existed
+        File scriptFile = MavenTestingUtils.getTestResourceFile("droptables.sql");
+        int result =  DatabaseLoginServiceTestServer.runscript(scriptFile);
+        //ignore result, if the tables dont already exist, derby spits out an error
         
-        security.setConstraintMappings(Collections.singletonList(mapping), knownRoles);
-        security.setAuthenticator(new BasicAuthenticator());
-        security.setLoginService(loginService);
-        security.setStrict(false);
+        //create the tables afresh
+        scriptFile = MavenTestingUtils.getTestResourceFile("createdb.sql");
+        result = DatabaseLoginServiceTestServer.runscript(scriptFile);
+        assertThat("runScript result",result, is(0));
         
-        ServletContextHandler root = new ServletContextHandler();
-        root.setContextPath("/");
-        root.setResourceBase(getBasePath());
-        ServletHolder servletHolder = new ServletHolder( new DefaultServlet() );
-        servletHolder.setInitParameter( "gzip", "true" );
-        root.addServlet( servletHolder, "/*" );    
-
-        Handler handler = new TestHandler(getBasePath());       
+        File jdbcRealmFile = MavenTestingUtils.getTestResourceFile("jdbcrealm.properties");
         
-        HandlerCollection handlers = new HandlerCollection();
-        handlers.setHandlers(new Handler[]{handler, root});
-        security.setHandler(handlers);
-    }
- 
-    @BeforeClass
-     public static void setUp()
-         throws Exception
-     {
-         _docRoot = new File("target/test-output/docroot/");
-         _docRoot.mkdirs();
-         _docRoot.deleteOnExit();
-         
-         File content = new File(_docRoot,"input.txt");
-         FileOutputStream out = new FileOutputStream(content);
-         out.write(_content.getBytes("utf-8"));
-         out.close();
-
-         File dbRoot = new File("target/test-output/derby");
-         String dbPath = dbRoot.getAbsolutePath();
-         System.setProperty("derby.system.home", dbPath);
-         if (!dbRoot.exists())
-         {
-             dbRoot.mkdirs();
-             createDB(dbPath, "src/test/resources/createdb.sql", "jdbc:derby:jdbcrealm;create=true");
-         }
-         
-         _server = new Server();
-         configureServer(_server);
-         _server.start();
-
-         int port = _server.getConnectors()[0].getLocalPort();
-         _baseUrl = _protocol+"://localhost:"+port+ "/";
+        LoginService loginService = new JDBCLoginService(__realm, jdbcRealmFile.getAbsolutePath());
+        _testServer = new DatabaseLoginServiceTestServer();
+        _testServer.setResourceBase(_docRoot.getAbsolutePath());
+        _testServer.setLoginService(loginService);
+        _testServer.start();
+        _baseUri = _testServer.getBaseUri();
      }
-     
+
      @AfterClass
      public static void tearDown()
          throws Exception
      {
-         if (_server != null)
+         if (_testServer != null)
          {
-             _server.stop();
-             _server = null;
+             _testServer.stop();
+             _testServer = null;
          }
      }
-     
+
      @Test
      public void testPut() throws Exception
      {
-         startClient(_realm);
-     
-         ContentExchange putExchange = new ContentExchange();
-         putExchange.setURL(getBaseUrl() + "output.txt");
-         putExchange.setMethod(HttpMethods.PUT);
-         putExchange.setRequestContent(new ByteArrayBuffer(_content.getBytes()));
-     
-         _client.send(putExchange);
-         int state = putExchange.waitForDone();
-     
-         int responseStatus = putExchange.getResponseStatus();
-     
-         stopClient();
-     
-         boolean statusOk = (responseStatus == 200 || responseStatus == 201);
-         assertTrue(statusOk);
-         
-         String content = IO.toString(new FileInputStream(new File(_docRoot,"output.txt")));
-         assertEquals(_content,content);
+         try
+         {
+             startClient();
+
+             Request request = _client.newRequest(_baseUri.resolve("output.txt"));
+             request.method(HttpMethod.PUT);
+             request.content(new BytesContentProvider(_content.getBytes()));
+             ContentResponse response = request.send();
+             int responseStatus = response.getStatus();
+             boolean statusOk = (responseStatus == 200 || responseStatus == 201);
+             assertTrue(statusOk);
+             String content = IO.toString(new FileInputStream(new File(_docRoot,"output.txt")));
+             assertEquals(_content,content);
+         }
+         finally
+         {
+             stopClient();
+         }
      }
-     
+
      @Test
      public void testGet() throws Exception
      {
-         startClient(_realm);
-     
-         ContentExchange getExchange = new ContentExchange();
-         getExchange.setURL(getBaseUrl() + "input.txt");
-         getExchange.setMethod(HttpMethods.GET);
-     
-         _client.send(getExchange);
-         int state = getExchange.waitForDone();
-     
-         String content = "";
-         int responseStatus = getExchange.getResponseStatus();
-         if (responseStatus == HttpStatus.OK_200)
+         try
+         {
+             startClient();
+
+             ContentResponse response = _client.GET(_baseUri.resolve("input.txt"));
+             assertEquals(HttpServletResponse.SC_OK,response.getStatus());
+             assertEquals(_content, response.getContentAsString());
+         }
+         finally
          {
-             content = getExchange.getResponseContent();
+             stopClient();
          }
-     
-         stopClient();
-     
-         assertEquals(HttpStatus.OK_200,responseStatus);
-         assertEquals(_content,content);
      }
 
-     @Test
+     //Head requests to jetty-client are not working: see https://bugs.eclipse.org/bugs/show_bug.cgi?id=394552
+     @Ignore
      public void testHead() throws Exception
      {
-         startClient(_realm);
-     
-         ContentExchange getExchange = new ContentExchange();
-         getExchange.setURL(getBaseUrl() + "input.txt");
-         getExchange.setMethod(HttpMethods.HEAD);
-     
-         _client.send(getExchange);
-         int state = getExchange.waitForDone();
-     
-         int responseStatus = getExchange.getResponseStatus();
+         try
+         {
+             startClient();
 
-         stopClient();
-     
-         assertEquals(HttpStatus.OK_200,responseStatus);
+             Request request = _client.newRequest(_baseUri.resolve("input.txt"));
+             request.method(HttpMethod.HEAD);
+             ContentResponse response = request.send();
+             int responseStatus = response.getStatus();
+             assertEquals(HttpStatus.OK_200,responseStatus);
+         }
+         finally
+         {
+             stopClient();
+         }
      }
 
      @Test
      public void testPost() throws Exception
      {
-         startClient(_realm);
-     
-         ContentExchange postExchange = new ContentExchange();
-         postExchange.setURL(getBaseUrl() + "test");
-         postExchange.setMethod(HttpMethods.POST);
-         postExchange.setRequestContent(new ByteArrayBuffer(_content.getBytes()));
-    
-         _client.send(postExchange);
-         int state = postExchange.waitForDone();
-     
-         int responseStatus = postExchange.getResponseStatus();
-  
-         stopClient();
-     
-         assertEquals(HttpStatus.OK_200,responseStatus);
-         assertEquals(_content,_requestContent);
+         try
+         {
+             startClient();
+
+             Request request = _client.newRequest(_baseUri.resolve("test"));
+             request.method(HttpMethod.POST);
+             request.content(new BytesContentProvider(_content.getBytes()));
+             ContentResponse response = request.send();
+             assertEquals(HttpStatus.OK_200,response.getStatus());
+             assertEquals(_content,_testServer.getTestHandler().getRequestContent());
+         }
+         finally
+         {
+             stopClient();
+         }
      }
-     
-     protected void startClient(Realm realm)
+
+     protected void startClient()
          throws Exception
      {
          _client = new HttpClient();
-         _client.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);
-         if (realm != null)
-             _client.setRealmResolver(new SimpleRealmResolver(realm));
+         QueuedThreadPool executor = new QueuedThreadPool();
+         executor.setName(executor.getName() + "-client");
+         _client.setExecutor(executor);
+         AuthenticationStore authStore = _client.getAuthenticationStore();
+         authStore.addAuthentication(new BasicAuthentication(_baseUri, __realm, "jetty", "jetty"));
          _client.start();
      }
-     
+
      protected void stopClient()
          throws Exception
      {
@@ -340,119 +220,14 @@ public class JdbcLoginServiceTest
              _client = null;
          }
      }
-     
-     protected static String getBasePath()
-     {
-         return _docRoot.getAbsolutePath();
-     }
-     
-     protected String getBaseUrl()
-     {
-         return _baseUrl;
-     }
-     
+
      protected HttpClient getClient()
      {
          return _client;
      }
-     
-     protected Realm getRealm()
-     {
-         return _realm;
-     }
-     
+
      protected String getContent()
      {
          return _content;
      }
-     
-     protected static void setProtocol(String protocol)
-     {
-         _protocol = protocol;
-     }
-     
-     protected static void setRealm(Realm realm)
-     {
-         _realm = realm;
-     }
-     
-     public static void copyStream(InputStream in, OutputStream out)
-     {
-         try
-         {
-             byte[] buffer=new byte[1024];
-             int len;
-             while ((len=in.read(buffer))>=0)
-             {
-                 out.write(buffer,0,len);
-             }
-         }
-         catch (EofException e)
-         {
-             System.err.println(e);
-         }
-         catch (IOException e)
-         {
-             e.printStackTrace();
-         }
-     }
-
-     protected static class TestHandler extends AbstractHandler {
-         private final String resourcePath;
-
-         public TestHandler(String repositoryPath) {
-             this.resourcePath = repositoryPath;
-         }
-
-         public void handle(String target, Request baseRequest,
-                 HttpServletRequest request, HttpServletResponse response)
-             throws IOException, ServletException
-         {
-             if (baseRequest.isHandled())
-             {
-                 return;
-             }
-
-             OutputStream out = null;
-             
-             if (baseRequest.getMethod().equals("PUT"))
-             {
-                 baseRequest.setHandled(true);
-
-                 File file = new File(resourcePath, URLDecoder.decode(request.getPathInfo()));
-                 file.getParentFile().mkdirs();
-                 file.deleteOnExit();
-             
-                 out = new FileOutputStream(file);
-
-                     response.setStatus(HttpServletResponse.SC_CREATED);
-             }
-             
-             if (baseRequest.getMethod().equals("POST"))
-             {
-                 baseRequest.setHandled(true);
-                 out = new ByteArrayOutputStream();
-
-                 response.setStatus(HttpServletResponse.SC_OK);
-             }
-             
-             if (out != null)
-             {
-                 ServletInputStream in = request.getInputStream();
-                 try
-                 {
-                     copyStream( in, out );
-                 }
-                 finally
-                 {
-                     in.close();
-                     out.close();
-                 }
-                 
-                 if (!(out instanceof FileOutputStream))
-                     _requestContent = out.toString();
-             }
-             
-         }
-     }
 }
diff --git a/tests/test-loginservice/src/test/resources/droptables.sql b/tests/test-loginservice/src/test/resources/droptables.sql
new file mode 100644
index 0000000..3a9132d
--- /dev/null
+++ b/tests/test-loginservice/src/test/resources/droptables.sql
@@ -0,0 +1,6 @@
+DROP TABLE roles;
+
+DROP TABLE users;
+
+DROP TABLE user_roles;
+
diff --git a/tests/test-loginservice/src/test/resources/jdbcrealm.properties b/tests/test-loginservice/src/test/resources/jdbcrealm.properties
index bb79638..9045aa1 100644
--- a/tests/test-loginservice/src/test/resources/jdbcrealm.properties
+++ b/tests/test-loginservice/src/test/resources/jdbcrealm.properties
@@ -1,5 +1,5 @@
 jdbcdriver = org.apache.derby.jdbc.EmbeddedDriver
-url = jdbc:derby:jdbcrealm
+url = jdbc:derby:loginservice
 username =
 password =
 usertable = users
diff --git a/tests/test-loginservice/src/test/resources/jetty-logging.properties b/tests/test-loginservice/src/test/resources/jetty-logging.properties
new file mode 100644
index 0000000..adf68c7
--- /dev/null
+++ b/tests/test-loginservice/src/test/resources/jetty-logging.properties
@@ -0,0 +1,3 @@
+org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
+#org.eclipse.jetty.LEVEL=DEBUG
+#org.eclipse.jetty.server.LEVEL=DEBUG
diff --git a/tests/test-quickstart/pom.xml b/tests/test-quickstart/pom.xml
new file mode 100644
index 0000000..c5d021e
--- /dev/null
+++ b/tests/test-quickstart/pom.xml
@@ -0,0 +1,182 @@
+<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">
+  <parent>
+    <groupId>org.eclipse.jetty.tests</groupId>
+    <artifactId>tests-parent</artifactId>
+    <version>9.2.13.v20150730</version>
+    <relativePath>../pom.xml</relativePath>
+  </parent>
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>org.eclipse.jetty</groupId>
+  <artifactId>test-quickstart</artifactId>
+  <name>Test :: Jetty Quick Start</name>
+  <description>Jetty Quick Start Test</description>
+  <url>http://www.eclipse.org/jetty</url>
+  <dependencies>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-quickstart</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-webapp</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-jmx</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-plus</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-annotations</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>javax.transaction</groupId>
+      <artifactId>javax.transaction-api</artifactId>
+      <version>1.2</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.tests</groupId>
+      <artifactId>test-mock-resources</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.orbit</groupId>
+      <artifactId>javax.mail.glassfish</artifactId>
+      <version>1.4.1.v201005082020</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-servlets</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.tests</groupId>
+      <artifactId>test-jndi-webapp</artifactId>
+      <version>${project.version}</version>
+      <type>war</type>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.tests</groupId>
+      <artifactId>test-spec-webapp</artifactId>
+      <version>${project.version}</version>
+      <type>war</type>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>test-jetty-webapp</artifactId>
+      <version>${project.version}</version>
+      <type>war</type>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.websocket</groupId>
+      <artifactId>javax-websocket-server-impl</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.websocket</groupId>
+      <artifactId>websocket-server</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>apache-jsp</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>apache-jstl</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.toolchain</groupId>
+      <artifactId>jetty-test-helper</artifactId>
+    </dependency>
+    
+  </dependencies>
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>appassembler-maven-plugin</artifactId>
+        <version>1.7</version>
+        <configuration>
+          <platforms>
+            <platform>unix</platform>
+          </platforms>
+          <programs>
+            <program>
+              <id>preconfigure</id>
+              <mainClass>org.eclipse.jetty.quickstart.PreconfigureQuickStartWar</mainClass>
+            </program>
+            <program>
+              <mainClass>org.eclipse.jetty.quickstart.QuickStartWar</mainClass>
+              <id>quickstart</id>
+            </program>
+          </programs>
+        </configuration>
+        <dependencies>
+          <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-webapp</artifactId>
+            <version>${project.version}</version>
+          </dependency>
+        </dependencies>
+      </plugin>
+
+      <plugin>
+        <artifactId>maven-dependency-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>copy</id>
+            <phase>generate-resources</phase>
+            <goals>
+              <goal>copy</goal>
+            </goals>
+            <configuration>
+              <artifactItems>
+                <artifactItem>
+                  <groupId>org.eclipse.jetty.tests</groupId>
+                  <artifactId>test-jndi-webapp</artifactId>
+                  <version>${project.version}</version>
+                  <type>war</type>
+                  <overWrite>true</overWrite>
+                  <includes>**</includes>
+                  <outputDirectory>${basedir}/target</outputDirectory>
+                  <destFileName>test-jndi.war</destFileName>
+                </artifactItem>
+                <artifactItem>
+                  <groupId>org.eclipse.jetty.tests</groupId>
+                  <artifactId>test-spec-webapp</artifactId>
+                  <version>${project.version}</version>
+                  <type>war</type>
+                  <overWrite>true</overWrite>
+                  <includes>**</includes>
+                  <outputDirectory>${basedir}/target</outputDirectory>
+                  <destFileName>test-spec.war</destFileName>
+                </artifactItem>
+                <artifactItem>
+                  <groupId>org.eclipse.jetty</groupId>
+                  <artifactId>test-jetty-webapp</artifactId>
+                  <version>${project.version}</version>
+                  <type>war</type>
+                  <overWrite>true</overWrite>
+                  <includes>**</includes>
+                  <outputDirectory>${basedir}/target</outputDirectory>
+                  <destFileName>test-standard.war</destFileName>
+                </artifactItem>
+              </artifactItems>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+</project>
diff --git a/tests/test-quickstart/src/test/java/org/eclipse/jetty/quickstart/PreconfigureJNDIWar.java b/tests/test-quickstart/src/test/java/org/eclipse/jetty/quickstart/PreconfigureJNDIWar.java
new file mode 100644
index 0000000..d43e803
--- /dev/null
+++ b/tests/test-quickstart/src/test/java/org/eclipse/jetty/quickstart/PreconfigureJNDIWar.java
@@ -0,0 +1,49 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.quickstart;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public class PreconfigureJNDIWar
+{
+    private static final long __start=System.nanoTime();
+    private static final Logger LOG = Log.getLogger(Server.class);
+    
+    public static void main(String[] args) throws Exception
+    {
+        String target="target/test-jndi-preconfigured";
+        File file = new File(target);
+        if (file.exists())
+            IO.delete(file);
+        
+        PreconfigureQuickStartWar.main("target/test-jndi.war",target, "src/test/resources/test-jndi.xml");
+
+        LOG.info("Preconfigured in {}ms",TimeUnit.NANOSECONDS.toMillis(System.nanoTime()-__start));
+        
+        // IO.copy(new FileInputStream("target/test-jndi-preconfigured/WEB-INF/quickstart-web.xml"),System.out);
+    }
+
+}
diff --git a/tests/test-quickstart/src/test/java/org/eclipse/jetty/quickstart/PreconfigureSpecWar.java b/tests/test-quickstart/src/test/java/org/eclipse/jetty/quickstart/PreconfigureSpecWar.java
new file mode 100644
index 0000000..59fa4fb
--- /dev/null
+++ b/tests/test-quickstart/src/test/java/org/eclipse/jetty/quickstart/PreconfigureSpecWar.java
@@ -0,0 +1,58 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.quickstart;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+
+public class PreconfigureSpecWar
+{
+    private static final long __start=System.nanoTime();
+    private static final Logger LOG = Log.getLogger(Server.class);
+    
+    public static void main(String[] args) throws Exception
+    {
+        String target="target/test-spec-preconfigured";
+        File file = new File(target);
+        if (file.exists())
+            IO.delete(file);
+        
+        File realmPropertiesDest = new File ("target/test-spec-realm.properties");
+        if (realmPropertiesDest.exists())
+            IO.delete(realmPropertiesDest);
+        
+        Resource realmPropertiesSrc = Resource.newResource("src/test/resources/realm.properties");
+        realmPropertiesSrc.copyTo(realmPropertiesDest);
+        System.setProperty("jetty.home", "target");
+        
+        PreconfigureQuickStartWar.main("target/test-spec.war",target, "src/test/resources/test-spec.xml");
+
+        LOG.info("Preconfigured in {}ms",TimeUnit.NANOSECONDS.toMillis(System.nanoTime()-__start));
+        
+        // IO.copy(new FileInputStream("target/test-spec-preconfigured/WEB-INF/quickstart-web.xml"),System.out);
+    }
+
+}
diff --git a/tests/test-quickstart/src/test/java/org/eclipse/jetty/quickstart/PreconfigureStandardTestWar.java b/tests/test-quickstart/src/test/java/org/eclipse/jetty/quickstart/PreconfigureStandardTestWar.java
new file mode 100644
index 0000000..2a9dd19
--- /dev/null
+++ b/tests/test-quickstart/src/test/java/org/eclipse/jetty/quickstart/PreconfigureStandardTestWar.java
@@ -0,0 +1,61 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.quickstart;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+
+/**
+ * PreconfigureStandardTestWar
+ *
+ */
+public class PreconfigureStandardTestWar
+{
+    private static final long __start=System.nanoTime();
+    private static final Logger LOG = Log.getLogger(Server.class);
+    
+    public static void main(String[] args) throws Exception
+    {
+        String target="target/test-standard-preconfigured";
+        File file = new File(target);
+        if (file.exists())
+            IO.delete(file);
+        
+        File realmPropertiesDest = new File ("target/test-standard-realm.properties");
+        if (realmPropertiesDest.exists())
+            IO.delete(realmPropertiesDest);
+        
+        Resource realmPropertiesSrc = Resource.newResource("src/test/resources/realm.properties");
+        realmPropertiesSrc.copyTo(realmPropertiesDest);
+        System.setProperty("jetty.home", "target");
+        
+        PreconfigureQuickStartWar.main("target/test-standard.war",target, "src/test/resources/test.xml");
+
+        LOG.info("Preconfigured in {}ms",TimeUnit.NANOSECONDS.toMillis(System.nanoTime()-__start));
+        
+        // IO.copy(new FileInputStream("target/test-standard-preconfigured/WEB-INF/quickstart-web.xml"),System.out);
+    }
+}
diff --git a/tests/test-quickstart/src/test/java/org/eclipse/jetty/quickstart/QuickStartJNDIWar.java b/tests/test-quickstart/src/test/java/org/eclipse/jetty/quickstart/QuickStartJNDIWar.java
new file mode 100644
index 0000000..72cd99f
--- /dev/null
+++ b/tests/test-quickstart/src/test/java/org/eclipse/jetty/quickstart/QuickStartJNDIWar.java
@@ -0,0 +1,31 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.quickstart;
+
+public class QuickStartJNDIWar 
+{    
+
+    public static void main(String... args) throws Exception
+    {   
+        // Log.getRootLogger().setDebugEnabled(true);
+        Quickstart.main("target/test-jndi-preconfigured", "src/test/resources/test-jndi.xml");
+    }
+    
+    
+}
diff --git a/tests/test-quickstart/src/test/java/org/eclipse/jetty/quickstart/QuickStartSpecWar.java b/tests/test-quickstart/src/test/java/org/eclipse/jetty/quickstart/QuickStartSpecWar.java
new file mode 100644
index 0000000..f0aa521
--- /dev/null
+++ b/tests/test-quickstart/src/test/java/org/eclipse/jetty/quickstart/QuickStartSpecWar.java
@@ -0,0 +1,30 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.quickstart;
+
+
+public class QuickStartSpecWar 
+{    
+    public static void main(String... args) throws Exception
+    {   
+        // Log.getRootLogger().setDebugEnabled(true);
+       System.setProperty("jetty.home", "target");
+       Quickstart.main("target/test-spec-preconfigured", "src/test/resources/test-spec.xml");
+    }
+}
diff --git a/tests/test-quickstart/src/test/java/org/eclipse/jetty/quickstart/QuickStartStandardTestWar.java b/tests/test-quickstart/src/test/java/org/eclipse/jetty/quickstart/QuickStartStandardTestWar.java
new file mode 100644
index 0000000..e92c6df
--- /dev/null
+++ b/tests/test-quickstart/src/test/java/org/eclipse/jetty/quickstart/QuickStartStandardTestWar.java
@@ -0,0 +1,30 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.quickstart;
+
+public class QuickStartStandardTestWar 
+{
+
+    public static void main(String... args) throws Exception
+    {   
+        // Log.getRootLogger().setDebugEnabled(true);
+       System.setProperty("jetty.home", "target");
+       Quickstart.main("target/test-standard-preconfigured", "src/test/resources/test.xml");
+    }
+}
diff --git a/tests/test-quickstart/src/test/java/org/eclipse/jetty/quickstart/QuickStartTest.java b/tests/test-quickstart/src/test/java/org/eclipse/jetty/quickstart/QuickStartTest.java
new file mode 100644
index 0000000..cb15daf
--- /dev/null
+++ b/tests/test-quickstart/src/test/java/org/eclipse/jetty/quickstart/QuickStartTest.java
@@ -0,0 +1,179 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.quickstart;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+
+import org.eclipse.jetty.server.NetworkConnector;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.webapp.WebDescriptor;
+import org.eclipse.jetty.xml.XmlConfiguration;
+import org.eclipse.jetty.xml.XmlParser.Node;
+import org.hamcrest.Matchers;
+import org.junit.Test;
+
+public class QuickStartTest
+{
+
+    @Test
+    public void testStandardTestWar() throws Exception
+    {
+        PreconfigureStandardTestWar.main(new String[]{});
+        
+        WebDescriptor descriptor = new WebDescriptor(Resource.newResource("./target/test-standard-preconfigured/WEB-INF/quickstart-web.xml"));
+        descriptor.setValidating(true);
+        descriptor.parse();
+        Node node = descriptor.getRoot();
+        assertThat(node,Matchers.notNullValue());
+        
+        System.setProperty("jetty.home", "target");
+        
+        //war file or dir to start
+        String war = "target/test-standard-preconfigured";
+        
+        //optional jetty context xml file to configure the webapp
+        Resource contextXml = Resource.newResource("src/test/resources/test.xml");
+        
+        Server server = new Server(0);
+        
+        QuickStartWebApp webapp = new QuickStartWebApp();
+        webapp.setAutoPreconfigure(true);
+        webapp.setWar(war);
+        webapp.setContextPath("/");
+
+        //apply context xml file
+        if (contextXml != null)
+        {
+            // System.err.println("Applying "+contextXml);
+            XmlConfiguration xmlConfiguration = new XmlConfiguration(contextXml.getURL());  
+            xmlConfiguration.configure(webapp);   
+        }
+        
+        server.setHandler(webapp);
+
+        server.start();
+
+        URL url = new URL("http://127.0.0.1:"+server.getBean(NetworkConnector.class).getLocalPort()+"/test/dump/info");
+        HttpURLConnection connection = (HttpURLConnection)url.openConnection();
+        assertEquals(200,connection.getResponseCode());
+        assertThat(IO.toString((InputStream)connection.getContent()),Matchers.containsString("Dump Servlet"));
+      
+        server.stop();
+    }
+
+    @Test
+    public void testSpecWar() throws Exception
+    {
+        PreconfigureSpecWar.main(new String[]{});
+        
+        WebDescriptor descriptor = new WebDescriptor(Resource.newResource("./target/test-spec-preconfigured/WEB-INF/quickstart-web.xml"));
+        descriptor.setValidating(true);
+        descriptor.parse();
+        Node node = descriptor.getRoot();
+        assertThat(node,Matchers.notNullValue());
+        
+        System.setProperty("jetty.home", "target");
+        
+        //war file or dir to start
+        String war = "target/test-spec-preconfigured";
+        
+        //optional jetty context xml file to configure the webapp
+        Resource contextXml = Resource.newResource("src/test/resources/test-spec.xml");
+        
+        Server server = new Server(0);
+        
+        QuickStartWebApp webapp = new QuickStartWebApp();
+        webapp.setAutoPreconfigure(true);
+        webapp.setWar(war);
+        webapp.setContextPath("/");
+
+        //apply context xml file
+        if (contextXml != null)
+        {
+            // System.err.println("Applying "+contextXml);
+            XmlConfiguration xmlConfiguration = new XmlConfiguration(contextXml.getURL());  
+            xmlConfiguration.configure(webapp);   
+        }
+        
+        server.setHandler(webapp);
+
+        server.start();
+
+        URL url = new URL("http://127.0.0.1:"+server.getBean(NetworkConnector.class).getLocalPort()+"/");
+        HttpURLConnection connection = (HttpURLConnection)url.openConnection();
+        assertEquals(200,connection.getResponseCode());
+        assertThat(IO.toString((InputStream)connection.getContent()),Matchers.containsString("Test Specification WebApp"));
+      
+        server.stop();
+    }
+
+    @Test
+    public void testJNDIWar() throws Exception
+    {
+        PreconfigureJNDIWar.main(new String[]{});
+        
+        WebDescriptor descriptor = new WebDescriptor(Resource.newResource("./target/test-jndi-preconfigured/WEB-INF/quickstart-web.xml"));
+        descriptor.setValidating(true);
+        descriptor.parse();
+        Node node = descriptor.getRoot();
+        assertThat(node,Matchers.notNullValue());
+        
+        System.setProperty("jetty.home", "target");
+        
+        //war file or dir to start
+        String war = "target/test-jndi-preconfigured";
+        
+        //optional jetty context xml file to configure the webapp
+        Resource contextXml = Resource.newResource("src/test/resources/test-jndi.xml");
+        
+        Server server = new Server(0);
+        
+        QuickStartWebApp webapp = new QuickStartWebApp();
+        webapp.setAutoPreconfigure(true);
+        webapp.setWar(war);
+        webapp.setContextPath("/");
+
+        //apply context xml file
+        if (contextXml != null)
+        {
+            // System.err.println("Applying "+contextXml);
+            XmlConfiguration xmlConfiguration = new XmlConfiguration(contextXml.getURL());  
+            xmlConfiguration.configure(webapp);   
+        }
+        
+        server.setHandler(webapp);
+
+        server.start();
+
+        URL url = new URL("http://127.0.0.1:"+server.getBean(NetworkConnector.class).getLocalPort()+"/");
+        HttpURLConnection connection = (HttpURLConnection)url.openConnection();
+        assertEquals(200,connection.getResponseCode());
+        String content=IO.toString((InputStream)connection.getContent());
+        assertThat(content,Matchers.containsString("JNDI Test WebApp"));
+      
+        server.stop();
+    }
+}
diff --git a/tests/test-quickstart/src/test/java/org/eclipse/jetty/quickstart/Quickstart.java b/tests/test-quickstart/src/test/java/org/eclipse/jetty/quickstart/Quickstart.java
new file mode 100644
index 0000000..5ff43e5
--- /dev/null
+++ b/tests/test-quickstart/src/test/java/org/eclipse/jetty/quickstart/Quickstart.java
@@ -0,0 +1,73 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.quickstart;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.xml.XmlConfiguration;
+
+public class Quickstart
+{
+    
+    public static void main(String... args) throws Exception
+    {   
+        if (args.length<1)
+            error("No WAR file or directory given");
+        
+        //war file or dir to start
+        String war = args[0];
+        
+        //optional jetty context xml file to configure the webapp
+        Resource contextXml = null;
+        if (args.length > 1)
+            contextXml = Resource.newResource(args[1]);
+        
+        Server server = new Server(8080);
+        
+        QuickStartWebApp webapp = new QuickStartWebApp();
+        webapp.setAutoPreconfigure(true);
+        webapp.setWar(war);
+        webapp.setContextPath("/");
+
+        //apply context xml file
+        if (contextXml != null)
+        {
+            // System.err.println("Applying "+contextXml);
+            XmlConfiguration xmlConfiguration = new XmlConfiguration(contextXml.getURL());  
+            xmlConfiguration.configure(webapp);   
+        }
+        
+        server.setHandler(webapp);
+
+        server.start();
+
+        
+      
+        server.join();
+    }
+    
+
+    private static void error(String message)
+    {
+        System.err.println("ERROR: "+message);
+        System.err.println("Usage: java -jar QuickStartWar.jar <war-directory> <context-xml>");
+        System.err.println("       java -jar QuickStartWar.jar <war-file> <context-xml>");
+        System.exit(1);
+    }
+}
diff --git a/tests/test-quickstart/src/test/resources/realm.properties b/tests/test-quickstart/src/test/resources/realm.properties
new file mode 100644
index 0000000..9d88b85
--- /dev/null
+++ b/tests/test-quickstart/src/test/resources/realm.properties
@@ -0,0 +1,21 @@
+#
+# This file defines users passwords and roles for a HashUserRealm
+#
+# The format is
+#  <username>: <password>[,<rolename> ...]
+#
+# Passwords may be clear text, obfuscated or checksummed.  The class 
+# org.eclipse.util.Password should be used to generate obfuscated
+# passwords or password checksums
+#
+# If DIGEST Authentication is used, the password must be in a recoverable
+# format, either plain text or OBF:.
+#
+jetty: MD5:164c88b302622e17050af52c89945d44,user
+admin: CRYPT:adpexzg3FUZAk,server-administrator,content-administrator,admin,user
+other: OBF:1xmk1w261u9r1w1c1xmq,user
+plain: plain,user
+user: password,user
+
+# This entry is for digest auth.  The credential is a MD5 hash of username:realmname:password
+digest: MD5:6e120743ad67abfbc385bc2bb754e297,user
diff --git a/tests/test-quickstart/src/test/resources/test-jndi.xml b/tests/test-quickstart/src/test/resources/test-jndi.xml
new file mode 100644
index 0000000..14c0934
--- /dev/null
+++ b/tests/test-quickstart/src/test/resources/test-jndi.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0"?>
+<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
+
+<!-- =============================================================== -->
+<!-- Configure the test-jndi webapp                                  -->
+<!-- =============================================================== -->
+<Configure id='wac' class="org.eclipse.jetty.webapp.WebAppContext">
+
+  <New id="tx" class="org.eclipse.jetty.plus.jndi.Transaction">
+    <Arg>
+      <New class="com.acme.MockUserTransaction"/>
+    </Arg>
+  </New>
+
+  <!-- Define an env entry with Server scope for java:comp/env                   -->
+  <New id="woggle"  class="org.eclipse.jetty.plus.jndi.EnvEntry">
+    <Arg><Property name='server'/></Arg>
+    <Arg>woggle</Arg>
+    <Arg type="java.lang.Integer">4000</Arg>
+    <Arg type="boolean">false</Arg>
+  </New>
+
+  <!-- Define an env entry with webapp scope for java:comp/env                   -->
+  <New id="wiggle"  class="org.eclipse.jetty.plus.jndi.EnvEntry">
+    <Arg><Ref refid='wac'/></Arg>
+    <Arg>wiggle</Arg>
+    <Arg type="java.lang.Double">100</Arg>
+    <Arg type="boolean">true</Arg>
+  </New>
+
+ <!-- Mail Session setup                                          -->
+ <New id="xxxmail" class="org.eclipse.jetty.plus.jndi.Resource">
+    <Arg><Ref refid='wac'/></Arg>
+     <Arg>mail/Session</Arg>
+     <Arg>
+       <New class="org.eclipse.jetty.jndi.factories.MailSessionReference">
+         <Set name="user">CHANGE-ME</Set>
+         <Set name="password">CHANGE-ME</Set>
+         <Set name="properties">
+           <New class="java.util.Properties">
+             <Put name="mail.smtp.auth">false</Put> <!-- change to true if you want to authenticate -->
+             <Put name="mail.smtp.host">CHANGE-ME</Put>
+             <Put name="mail.from">CHANGE-ME</Put>
+             <Put name="mail.debug">false</Put>
+           </New>
+          </Set>
+       </New>
+     </Arg>
+ </New>
+
+  <!-- A mock DataSource                                           -->
+  <New id="mydatasource" class="org.eclipse.jetty.plus.jndi.Resource">
+    <Arg><Ref refid='wac'/></Arg>
+    <Arg>jdbc/mydatasource</Arg>
+    <Arg>
+      <New class="com.acme.MockDataSource"/>
+    </Arg>
+  </New>
+
+</Configure>
diff --git a/tests/test-quickstart/src/test/resources/test-spec.xml b/tests/test-quickstart/src/test/resources/test-spec.xml
new file mode 100644
index 0000000..99fc577
--- /dev/null
+++ b/tests/test-quickstart/src/test/resources/test-spec.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0"?>
+<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
+
+<Configure id='wac' class="org.eclipse.jetty.webapp.WebAppContext">
+
+  <New id="tx" class="org.eclipse.jetty.plus.jndi.Transaction">
+    <Arg>
+      <New class="com.acme.MockUserTransaction"/>
+    </Arg>
+  </New>
+
+  <Get name="securityHandler">
+    <Set name="loginService">
+      <New class="org.eclipse.jetty.security.HashLoginService">
+        <Set name="name">Test Realm</Set>
+        <Set name="config"><SystemProperty name="jetty.home" default="."/>/test-spec-realm.properties</Set>
+      </New>
+    </Set>
+  </Get>
+
+
+  <New id="maxAmount"  class="org.eclipse.jetty.plus.jndi.EnvEntry">
+    <Arg><Ref refid='wac'/></Arg>
+    <Arg>maxAmount</Arg>
+    <Arg type="java.lang.Double">100</Arg>
+    <Arg type="boolean">true</Arg>
+  </New>
+
+
+  <New id="mydatasource" class="org.eclipse.jetty.plus.jndi.Resource">
+    <Arg><Ref refid='wac'/></Arg>
+    <Arg>jdbc/mydatasource</Arg>
+    <Arg>
+     <New class="com.acme.MockDataSource">
+     </New>
+    </Arg>
+   </New>
+
+</Configure>
diff --git a/tests/test-quickstart/src/test/resources/test.xml b/tests/test-quickstart/src/test/resources/test.xml
new file mode 100644
index 0000000..41eba0c
--- /dev/null
+++ b/tests/test-quickstart/src/test/resources/test.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0"  encoding="ISO-8859-1"?>
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
+
+<!-- ==================================================================
+Configure and deploy the test web application in $(jetty.home)/webapps/test
+
+Note. If this file did not exist or used a context path other that /test
+then the default configuration of jetty.xml would discover the test
+webapplication with a WebAppDeployer.  By specifying a context in this
+directory, additional configuration may be specified and hot deployments
+detected.
+===================================================================== -->
+
+<Configure class="org.eclipse.jetty.webapp.WebAppContext">
+
+  <Set name="contextPath">/test</Set>
+
+  <Get name="securityHandler">
+    <Set name="loginService">
+      <New class="org.eclipse.jetty.security.HashLoginService">
+        <Set name="name">Test Realm</Set>        
+        <Set name="config"><SystemProperty name="jetty.home" default="."/>/test-standard-realm.properties</Set>
+      </New>
+    </Set>
+    <Set name="authenticator">
+      <New class="org.eclipse.jetty.security.authentication.FormAuthenticator">
+        <Set name="alwaysSaveUri">true</Set>
+      </New>
+    </Set>
+    <Set name="checkWelcomeFiles">true</Set>
+  </Get>
+
+  <Set name="parentLoaderPriority">true</Set>
+
+  <!-- Non standard error page mapping -->
+  <!--
+  <Get name="errorHandler">
+    <Call name="addErrorPage">
+      <Arg type="int">500</Arg>
+      <Arg type="int">599</Arg>
+      <Arg type="String">/dump/errorCodeRangeMapping</Arg>
+    </Call>
+  </Get>
+  -->
+
+
+</Configure>
diff --git a/tests/test-sessions/pom.xml b/tests/test-sessions/pom.xml
index de24165..42b4988 100644
--- a/tests/test-sessions/pom.xml
+++ b/tests/test-sessions/pom.xml
@@ -21,7 +21,7 @@
   <parent>
     <groupId>org.eclipse.jetty.tests</groupId>
     <artifactId>tests-parent</artifactId>
-    <version>8.1.17.v20150415</version>
+    <version>9.2.13.v20150730</version>
   </parent>
   <artifactId>test-sessions-parent</artifactId>
   <name>Jetty Tests :: Sessions :: Parent</name>
@@ -33,6 +33,7 @@
     <module>test-sessions-common</module>
     <module>test-hash-sessions</module>
     <module>test-jdbc-sessions</module>
+    <!-- Requires mongodb server running -->
     <module>test-mongodb-sessions</module>
   </modules>
 </project>
diff --git a/tests/test-sessions/test-hash-sessions/pom.xml b/tests/test-sessions/test-hash-sessions/pom.xml
index 87bf552..a270658 100644
--- a/tests/test-sessions/test-hash-sessions/pom.xml
+++ b/tests/test-sessions/test-hash-sessions/pom.xml
@@ -21,7 +21,7 @@
   <parent>
     <groupId>org.eclipse.jetty.tests</groupId>
     <artifactId>test-sessions-parent</artifactId>
-    <version>8.1.17.v20150415</version>
+    <version>9.2.13.v20150730</version>
   </parent>
   <artifactId>test-hash-sessions</artifactId>
   <name>Jetty Tests :: Sessions :: Hash</name>
@@ -29,13 +29,6 @@
   <build>
     <plugins>
       <plugin>
-        <artifactId>maven-compiler-plugin</artifactId>
-        <configuration>
-          <source>1.5</source>
-          <target>1.5</target>
-        </configuration>
-      </plugin>
-      <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-deploy-plugin</artifactId>
         <configuration>
@@ -66,10 +59,11 @@
             <artifactId>test-sessions-common</artifactId>
             <version>${project.version}</version>
         </dependency>
-       <dependency>
-            <groupId>junit</groupId>
-            <artifactId>junit</artifactId>
-            <scope>test</scope>
-        </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.toolchain</groupId>
+      <artifactId>jetty-test-helper</artifactId>
+      <!-- Leaving at compile scope for intellij bug reasons -->
+      <scope>compile</scope>
+    </dependency>
   </dependencies>
 </project>
diff --git a/tests/test-sessions/test-hash-sessions/src/test/java/org/eclipse/jetty/server/session/HashTestServer.java b/tests/test-sessions/test-hash-sessions/src/test/java/org/eclipse/jetty/server/session/HashTestServer.java
index 5ee4b06..d4d6b28 100644
--- a/tests/test-sessions/test-hash-sessions/src/test/java/org/eclipse/jetty/server/session/HashTestServer.java
+++ b/tests/test-sessions/test-hash-sessions/src/test/java/org/eclipse/jetty/server/session/HashTestServer.java
@@ -18,8 +18,6 @@
 
 package org.eclipse.jetty.server.session;
 
-import java.util.concurrent.TimeUnit;
-
 import org.eclipse.jetty.server.SessionIdManager;
 import org.eclipse.jetty.server.SessionManager;
 
diff --git a/tests/test-sessions/test-hash-sessions/src/test/java/org/eclipse/jetty/server/session/IdleSessionTest.java b/tests/test-sessions/test-hash-sessions/src/test/java/org/eclipse/jetty/server/session/IdleSessionTest.java
index 75aa2b6..f34db58 100644
--- a/tests/test-sessions/test-hash-sessions/src/test/java/org/eclipse/jetty/server/session/IdleSessionTest.java
+++ b/tests/test-sessions/test-hash-sessions/src/test/java/org/eclipse/jetty/server/session/IdleSessionTest.java
@@ -19,9 +19,8 @@
 package org.eclipse.jetty.server.session;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.assertNotNull;
-
+import static org.junit.Assert.assertTrue;
 
 import java.io.File;
 import java.io.IOException;
@@ -32,14 +31,15 @@ import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpSession;
 
-import org.eclipse.jetty.client.ContentExchange;
 import org.eclipse.jetty.client.HttpClient;
-import org.eclipse.jetty.http.HttpMethods;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
 import org.eclipse.jetty.server.SessionManager;
-import org.eclipse.jetty.server.session.AbstractSessionExpiryTest.TestServlet;
 import org.eclipse.jetty.servlet.ServletContextHandler;
 import org.eclipse.jetty.servlet.ServletHolder;
 import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.StdErrLog;
 import org.junit.Test;
 
 
@@ -51,17 +51,12 @@ import org.junit.Test;
  */
 public class IdleSessionTest
 {
+    
     public class IdleHashTestServer extends HashTestServer
     {
         private int _idlePeriod;
         private File _storeDir;
 
-        /**
-         * @param port
-         * @param maxInactivePeriod
-         * @param scavengePeriod
-         * @param idlePeriod
-         */
         public IdleHashTestServer(int port, int maxInactivePeriod, int scavengePeriod, int idlePeriod, File storeDir)
         {
             super(port, maxInactivePeriod, scavengePeriod);
@@ -91,11 +86,10 @@ public class IdleSessionTest
 
     public  HashTestServer createServer(int port, int max, int scavenge, int idle, File storeDir)
     {
-        HashTestServer server = new IdleHashTestServer(port, max, scavenge, idle, storeDir);       
-        return server;
+        return new IdleHashTestServer(port, max, scavenge, idle, storeDir);
     }
-    
-    
+
+
 
     public void pause (int sec)
     {
@@ -117,7 +111,8 @@ public class IdleSessionTest
         int inactivePeriod = 200;
         int scavengePeriod = 3;
         int idlePeriod = 5;
-
+        ((StdErrLog)Log.getLogger(org.eclipse.jetty.server.session.HashedSession.class)).setHideStacks(true);
+        System.setProperty("org.eclipse.jetty.STACKS", "false");
         File storeDir = new File (System.getProperty("java.io.tmpdir"), "idle-test");
         storeDir.deleteOnExit();
 
@@ -132,40 +127,49 @@ public class IdleSessionTest
         try
         {
             HttpClient client = new HttpClient();
-            client.setConnectorType(HttpClient.CONNECTOR_SOCKET);
             client.start();
             String url = "http://localhost:" + port1 + contextPath + servletMapping;
 
             //make a request to set up a session on the server
-            ContentExchange exchange1 = new ContentExchange(true);
-            exchange1.setMethod(HttpMethods.GET);
-            exchange1.setURL(url + "?action=init");
-            client.send(exchange1);
-            exchange1.waitForDone();
-            assertEquals(HttpServletResponse.SC_OK,exchange1.getResponseStatus());
-            String sessionCookie = exchange1.getResponseFields().getStringField("Set-Cookie");
+            ContentResponse response = client.GET(url + "?action=init");
+            assertEquals(HttpServletResponse.SC_OK,response.getStatus());
+            String sessionCookie = response.getHeaders().getStringField("Set-Cookie");
             assertTrue(sessionCookie != null);
             // Mangle the cookie, replacing Path with $Path, etc.
             sessionCookie = sessionCookie.replaceFirst("(\\W)(P|p)ath=", "$1\\$Path=");
 
             //and wait until the session should be idled out
-            pause(scavengePeriod * 2);
+            pause(idlePeriod * 2);
 
             //check that the file exists
-            checkSessionIdled(storeDir);
+            checkSessionIdled(storeDir, getSessionId(sessionCookie));
 
             //make another request to de-idle the session
-            ContentExchange exchange2 = new ContentExchange(true);
-            exchange2.setMethod(HttpMethods.GET);
-            exchange2.setURL(url + "?action=test");
-            exchange2.getRequestFields().add("Cookie", sessionCookie);
-            client.send(exchange2);
-            exchange2.waitForDone();
-            assertEquals(HttpServletResponse.SC_OK,exchange2.getResponseStatus());
+            Request request = client.newRequest(url + "?action=test");
+            request.getHeaders().add("Cookie", sessionCookie);
+            ContentResponse response2 = request.send();
+            assertEquals(HttpServletResponse.SC_OK,response2.getStatus());
 
             //check session de-idled
             checkSessionDeIdled(storeDir);
 
+            //wait again for the session to be idled
+            pause(idlePeriod * 2);
+            
+            //check that it is
+            checkSessionIdled(storeDir, getSessionId(sessionCookie));
+            
+          
+            //delete the file
+            File idleFile = getIdleFile(storeDir, getSessionId(sessionCookie));
+            assertTrue(idleFile.exists());
+            assertTrue(idleFile.delete());
+            
+            //make a request
+            request = client.newRequest(url + "?action=testfail");
+            request.getHeaders().add("Cookie", sessionCookie);
+            response2 = request.send();
+            assertEquals(HttpServletResponse.SC_OK,response2.getStatus());
         }
         finally
         {
@@ -175,13 +179,14 @@ public class IdleSessionTest
     }
 
 
-    public void checkSessionIdled (File sessionDir)
+    public void checkSessionIdled (File sessionDir, String sessionId)
     {
         assertNotNull(sessionDir);
         assertTrue(sessionDir.exists());
         String[] files = sessionDir.list();
         assertNotNull(files);
         assertEquals(1, files.length);
+        assertEquals(sessionId, files[0]);
     }
 
 
@@ -193,13 +198,27 @@ public class IdleSessionTest
         assertNotNull(files);
         assertEquals(0, files.length);
     }
+    
+    public File getIdleFile (File sessionDir, String sessionId)
+    {
+        assertNotNull(sessionDir);
+        assertTrue(sessionDir.exists());
+        String[] files = sessionDir.list();
+        assertNotNull(files);      
+        return new File(sessionDir, files[0]);
+    }
 
+    public String getSessionId (String sessionCookie)
+    {
+        assertNotNull(sessionCookie);
+        String sessionId = sessionCookie.substring(11);
+        sessionId = sessionId.substring(0, sessionId.indexOf(';'));
+        return sessionId;
+    }
 
     public static class TestServlet extends HttpServlet
     {
         public String originalId = null;
-        public String testId = null;
-        public String checkId = null;
 
         @Override
         protected void doGet(HttpServletRequest request, HttpServletResponse httpServletResponse) throws ServletException, IOException
@@ -220,6 +239,11 @@ public class IdleSessionTest
                 assertEquals("test", session.getAttribute("test"));
                 assertTrue(!((HashedSession)session).isIdled());
             }
+            else if ("testfail".equals(action))
+            {
+                HttpSession session = request.getSession(false);
+                assertTrue(session == null);
+            }
         }
     }
 }
diff --git a/tests/test-sessions/test-hash-sessions/src/test/java/org/eclipse/jetty/server/session/ProxySerializationTest.java b/tests/test-sessions/test-hash-sessions/src/test/java/org/eclipse/jetty/server/session/ProxySerializationTest.java
new file mode 100644
index 0000000..d01cb17
--- /dev/null
+++ b/tests/test-sessions/test-hash-sessions/src/test/java/org/eclipse/jetty/server/session/ProxySerializationTest.java
@@ -0,0 +1,78 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.server.session;
+
+import java.io.File;
+
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.junit.Test;
+
+/**
+ * ProxySerializationTest
+ *
+ *
+ */
+public class ProxySerializationTest extends AbstractProxySerializationTest
+{   
+    /** 
+     * @see org.eclipse.jetty.server.session.AbstractProxySerializationTest#createServer(int, int, int)
+     */
+    @Override
+    public AbstractTestServer createServer(int port, int max, int scavenge)
+    {
+        return new HashTestServer(port,max,scavenge);
+    }
+    
+    
+    
+    
+    @Override
+    public void customizeContext(ServletContextHandler c)
+    {
+        if (c == null)
+            return;
+        
+        //Ensure that the HashSessionManager will persist sessions on passivation
+        HashSessionManager manager = (HashSessionManager)c.getSessionHandler().getSessionManager();
+        manager.setLazyLoad(false);
+        manager.setIdleSavePeriod(1);
+        try
+        {
+            File testDir = MavenTestingUtils.getTargetTestingDir("foo");
+            testDir.mkdirs();
+            manager.setStoreDirectory(testDir);
+        }
+        catch (Exception e)
+        {
+            throw new IllegalStateException(e);
+        }       
+    }
+
+
+
+
+    @Test
+    public void testProxySerialization() throws Exception
+    {
+        super.testProxySerialization();
+    }
+
+}
diff --git a/tests/test-sessions/test-hash-sessions/src/test/java/org/eclipse/jetty/server/session/SessionRenewTest.java b/tests/test-sessions/test-hash-sessions/src/test/java/org/eclipse/jetty/server/session/SessionRenewTest.java
new file mode 100644
index 0000000..207dbdb
--- /dev/null
+++ b/tests/test-sessions/test-hash-sessions/src/test/java/org/eclipse/jetty/server/session/SessionRenewTest.java
@@ -0,0 +1,81 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.session;
+
+import java.io.File;
+
+import org.eclipse.jetty.server.SessionManager;
+import org.eclipse.jetty.util.IO;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class SessionRenewTest extends AbstractSessionRenewTest
+{
+    File tmpDir;
+    
+    @Before
+    public void before() throws Exception
+    {
+        tmpDir = File.createTempFile("hash-session-renew-test", null);
+        tmpDir.delete();
+        tmpDir.mkdirs();
+        tmpDir.deleteOnExit();
+    }
+    
+    @After 
+    public void after()
+    {
+        IO.delete(tmpDir);
+    }
+    
+    @Override
+    public AbstractTestServer createServer(int port, int max, int scavenge)
+    {
+        return new HashTestServer(port, max, scavenge)
+        {
+
+            @Override
+            public SessionManager newSessionManager()
+            {
+                HashSessionManager sessionManager = (HashSessionManager)super.newSessionManager();
+                sessionManager.setSavePeriod(2);
+                
+                try
+                {
+                    sessionManager.setStoreDirectory(tmpDir);
+                }
+                catch (Exception e)
+                {
+                    throw new IllegalStateException(e);
+                }
+                return sessionManager;
+            }
+
+        };
+    }
+
+    @Test
+    public void testSessionRenewal() throws Exception
+    {
+        super.testSessionRenewal();
+    }
+
+    
+}
diff --git a/tests/test-sessions/test-jdbc-sessions/pom.xml b/tests/test-sessions/test-jdbc-sessions/pom.xml
index 5749bf2..9b74b8f 100644
--- a/tests/test-sessions/test-jdbc-sessions/pom.xml
+++ b/tests/test-sessions/test-jdbc-sessions/pom.xml
@@ -21,7 +21,7 @@
   <parent>
     <groupId>org.eclipse.jetty.tests</groupId>
     <artifactId>test-sessions-parent</artifactId>
-    <version>8.1.17.v20150415</version>
+    <version>9.2.13.v20150730</version>
   </parent>
   <artifactId>test-jdbc-sessions</artifactId>
   <name>Jetty Tests :: Sessions :: JDBC</name>
@@ -29,13 +29,6 @@
   <build>
     <plugins>
       <plugin>
-        <artifactId>maven-compiler-plugin</artifactId>
-        <configuration>
-          <source>1.5</source>
-          <target>1.5</target>
-        </configuration>
-      </plugin>
-      <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-deploy-plugin</artifactId>
         <configuration>
@@ -78,10 +71,10 @@
             <version>10.4.1.3</version>
             <scope>test</scope>
        </dependency>
-       <dependency>
-            <groupId>junit</groupId>
-            <artifactId>junit</artifactId>
-            <scope>test</scope>
-        </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.toolchain</groupId>
+      <artifactId>jetty-test-helper</artifactId>
+      <scope>test</scope>
+    </dependency>
   </dependencies>
 </project>
diff --git a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/ClientCrossContextSessionTest.java b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/ClientCrossContextSessionTest.java
index 4281f93..eb4109b 100644
--- a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/ClientCrossContextSessionTest.java
+++ b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/ClientCrossContextSessionTest.java
@@ -18,6 +18,10 @@
 
 package org.eclipse.jetty.server.session;
 
+import java.sql.DriverManager;
+import java.sql.SQLException;
+
+import org.junit.After;
 import org.junit.Test;
 
 /**
@@ -38,4 +42,16 @@ public class ClientCrossContextSessionTest extends AbstractClientCrossContextSes
     {
         super.testCrossContextDispatch();
     }
+    
+    @After
+    public void tearDown() throws Exception 
+    {
+        try
+        {
+            DriverManager.getConnection( "jdbc:derby:sessions;shutdown=true" );
+        }
+        catch( SQLException expected )
+        {
+        }
+    }
 }
diff --git a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/DirtyAttributeTest.java b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/DirtyAttributeTest.java
new file mode 100644
index 0000000..61e1bf3
--- /dev/null
+++ b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/DirtyAttributeTest.java
@@ -0,0 +1,225 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.session;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+import java.io.Serializable;
+
+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 javax.servlet.http.HttpSessionActivationListener;
+import javax.servlet.http.HttpSessionBindingEvent;
+import javax.servlet.http.HttpSessionBindingListener;
+import javax.servlet.http.HttpSessionEvent;
+
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.junit.Test;
+
+
+/**
+ * DirtyAttributeTest
+ *
+ * Check that repeated calls to setAttribute with the same value do not cause writes to the
+ * database - this is inferred via calls to passivate and activate  listeners.
+ *
+ *
+ */
+public class DirtyAttributeTest
+{
+    public static TestValue A_VALUE = new TestValue();
+    public static TestValue B_VALUE = new TestValue();
+    public static String THE_NAME = "__theName";
+    public static int INACTIVE = 4;
+    public static int SCAVENGE = 1;
+
+    @Test
+    public void testDirtyWrite() throws Exception
+    {
+        AbstractTestServer server = new JdbcTestServer(0,INACTIVE,SCAVENGE);
+        
+        ServletContextHandler ctxA = server.addContext("/mod");
+        ctxA.addServlet(TestDirtyServlet.class, "/test");
+        
+        server.start();
+        int port=server.getPort();
+        try
+        {
+            HttpClient client = new HttpClient();
+            client.start();
+            try
+            {
+                // Perform a request to create a session              
+                ContentResponse response = client.GET("http://localhost:" + port + "/mod/test?action=create");
+                
+                assertEquals(HttpServletResponse.SC_OK,response.getStatus());
+                String sessionCookie = response.getHeaders().getStringField("Set-Cookie");
+                assertTrue(sessionCookie != null);
+                // Mangle the cookie, replacing Path with $Path, etc.
+                sessionCookie = sessionCookie.replaceFirst("(\\W)(P|p)ath=", "$1\\$Path=");
+                
+                //do another request to change the session attribute
+                Request request = client.newRequest("http://localhost:" + port + "/mod/test?action=setA");
+                request.header("Cookie", sessionCookie);
+                response = request.send();
+
+                assertEquals(HttpServletResponse.SC_OK,response.getStatus());
+                A_VALUE.assertPassivatesEquals(1);
+                A_VALUE.assertActivatesEquals(1);
+                A_VALUE.assertBindsEquals(1);
+                A_VALUE.assertUnbindsEquals(0);
+                
+                //do another request using the cookie to try changing the session attribute to the same value again              
+                request= client.newRequest("http://localhost:" + port + "/mod/test?action=setA");
+                request.header("Cookie", sessionCookie);
+                response = request.send();
+                assertEquals(HttpServletResponse.SC_OK,response.getStatus());
+                A_VALUE.assertPassivatesEquals(2);
+                A_VALUE.assertActivatesEquals(2);
+                A_VALUE.assertBindsEquals(1);
+                A_VALUE.assertUnbindsEquals(0);
+                
+                //do another request using the cookie and change to a different value             
+                request= client.newRequest("http://localhost:" + port + "/mod/test?action=setB");
+                request.header("Cookie", sessionCookie);
+                response = request.send();
+                assertEquals(HttpServletResponse.SC_OK,response.getStatus());
+                B_VALUE.assertPassivatesEquals(1);
+                B_VALUE.assertActivatesEquals(1);
+                B_VALUE.assertBindsEquals(1);
+                B_VALUE.assertUnbindsEquals(0);
+                A_VALUE.assertBindsEquals(1);
+                A_VALUE.assertUnbindsEquals(1);
+                
+            }
+            finally
+            {
+                client.stop();
+            }
+        }
+        finally
+        {
+            server.stop();
+        }
+    }
+    
+    public static class TestValue implements HttpSessionActivationListener, HttpSessionBindingListener, Serializable
+    {
+        int passivates = 0;
+        int activates = 0;
+        int binds = 0;
+        int unbinds = 0;
+        
+        /** 
+         * @see javax.servlet.http.HttpSessionActivationListener#sessionWillPassivate(javax.servlet.http.HttpSessionEvent)
+         */
+        public void sessionWillPassivate(HttpSessionEvent se)
+        {
+            ++passivates;
+        }
+
+        /** 
+         * @see javax.servlet.http.HttpSessionActivationListener#sessionDidActivate(javax.servlet.http.HttpSessionEvent)
+         */
+        public void sessionDidActivate(HttpSessionEvent se)
+        {
+           ++activates;
+        }
+        
+        public void assertPassivatesEquals (int expected)
+        {
+            assertEquals(expected, passivates);
+        }
+        
+        public void assertActivatesEquals (int expected)
+        {
+            assertEquals(expected, activates);
+        }
+        
+        public void assertBindsEquals (int expected)
+        {
+            assertEquals(expected, binds);
+        }
+        
+        public void assertUnbindsEquals (int expected)
+        {
+            assertEquals(expected, unbinds);
+        }
+        
+
+        /** 
+         * @see javax.servlet.http.HttpSessionBindingListener#valueBound(javax.servlet.http.HttpSessionBindingEvent)
+         */
+        public void valueBound(HttpSessionBindingEvent event)
+        {
+            ++binds;
+        }
+
+        /** 
+         * @see javax.servlet.http.HttpSessionBindingListener#valueUnbound(javax.servlet.http.HttpSessionBindingEvent)
+         */
+        public void valueUnbound(HttpSessionBindingEvent event)
+        {
+            ++unbinds;
+        }      
+    }
+    
+    public static class TestDirtyServlet extends HttpServlet
+    {
+        @Override
+        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+        {
+            String action = request.getParameter("action");
+            
+            if ("create".equals(action))
+            {
+                HttpSession session = request.getSession(true);
+                return;
+            }
+            
+            if ("setA".equals(action))
+            {
+                HttpSession session = request.getSession(false);
+                if (session == null)
+                    throw new ServletException("Session is null for action=change");
+
+                session.setAttribute(THE_NAME, A_VALUE);
+                return;
+            }
+            
+            if ("setB".equals(action))
+            {
+                HttpSession session = request.getSession(false);
+                if (session == null)
+                    throw new ServletException("Session does not exist");
+                session.setAttribute(THE_NAME, B_VALUE);
+                return;
+            }
+        }
+    }
+    
+}
diff --git a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/ImmortalSessionTest.java b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/ImmortalSessionTest.java
index 21010ca..cabd724 100644
--- a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/ImmortalSessionTest.java
+++ b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/ImmortalSessionTest.java
@@ -18,6 +18,10 @@
 
 package org.eclipse.jetty.server.session;
 
+import java.sql.DriverManager;
+import java.sql.SQLException;
+
+import org.junit.After;
 import org.junit.Test;
 
 /**
@@ -38,4 +42,16 @@ public class ImmortalSessionTest extends AbstractImmortalSessionTest
     {
         super.testImmortalSession();
     }
+    
+    @After
+    public void tearDown() throws Exception 
+    {
+        try
+        {
+            DriverManager.getConnection( "jdbc:derby:sessions;shutdown=true" );
+        }
+        catch( SQLException expected )
+        {
+        }
+    }
 }
diff --git a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/InvalidationSessionTest.java b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/InvalidationSessionTest.java
index d7fe146..8075dbd 100644
--- a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/InvalidationSessionTest.java
+++ b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/InvalidationSessionTest.java
@@ -18,6 +18,10 @@
 
 package org.eclipse.jetty.server.session;
 
+import java.sql.DriverManager;
+import java.sql.SQLException;
+
+import org.junit.After;
 import org.junit.Test;
 
 /**
@@ -51,4 +55,17 @@ public class InvalidationSessionTest extends AbstractInvalidationSessionTest
     {
         super.testInvalidation();
     }  
+    
+    
+    @After
+    public void tearDown() throws Exception 
+    {
+        try
+        {
+            DriverManager.getConnection( "jdbc:derby:sessions;shutdown=true" );
+        }
+        catch( SQLException expected )
+        {
+        }
+    }
 }
diff --git a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/JdbcTestServer.java b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/JdbcTestServer.java
index 9d9c081..ad0a692 100644
--- a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/JdbcTestServer.java
+++ b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/JdbcTestServer.java
@@ -18,6 +18,13 @@
 
 package org.eclipse.jetty.server.session;
 
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.util.HashSet;
+import java.util.Set;
+
 import org.eclipse.jetty.server.SessionIdManager;
 import org.eclipse.jetty.server.SessionManager;
 import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
@@ -66,7 +73,7 @@ public class JdbcTestServer extends AbstractTestServer
     static int __workers=0;
     
     /** 
-     * @see org.eclipse.jetty.server.session.AbstractTestServer#newSessionIdManager()
+     * @see org.eclipse.jetty.server.session.AbstractTestServer#newSessionIdManager(String)
      */
     @Override
     public  SessionIdManager newSessionIdManager(String config)
@@ -77,6 +84,26 @@ public class JdbcTestServer extends AbstractTestServer
             idManager.setScavengeInterval(_scavengePeriod);
             idManager.setWorkerName("w"+(__workers++));
             idManager.setDriverInfo(DRIVER_CLASS, (config==null?DEFAULT_CONNECTION_URL:config));
+            JDBCSessionIdManager.SessionIdTableSchema idTableSchema = new JDBCSessionIdManager.SessionIdTableSchema();
+            idTableSchema.setTableName("mysessionids");
+            idTableSchema.setIdColumn("myid");
+            idManager.setSessionIdTableSchema(idTableSchema);
+            
+            JDBCSessionIdManager.SessionTableSchema sessionTableSchema = new JDBCSessionIdManager.SessionTableSchema();
+            sessionTableSchema.setTableName("mysessions");
+            sessionTableSchema.setIdColumn("mysessionid");
+            sessionTableSchema.setAccessTimeColumn("atime");
+            sessionTableSchema.setContextPathColumn("cpath");
+            sessionTableSchema.setCookieTimeColumn("cooktime");
+            sessionTableSchema.setCreateTimeColumn("ctime");
+            sessionTableSchema.setExpiryTimeColumn("extime");
+            sessionTableSchema.setLastAccessTimeColumn("latime");
+            sessionTableSchema.setLastNodeColumn("lnode");
+            sessionTableSchema.setLastSavedTimeColumn("lstime");
+            sessionTableSchema.setMapColumn("mo");
+            sessionTableSchema.setMaxIntervalColumn("mi");           
+            idManager.setSessionTableSchema(sessionTableSchema);
+            
             return idManager;
         }
     }
@@ -93,4 +120,86 @@ public class JdbcTestServer extends AbstractTestServer
         return manager;
     }
 
+    
+    public boolean existsInSessionIdTable(String id)
+    throws Exception
+    {
+        Class.forName(DRIVER_CLASS);
+        Connection con = null;
+        try
+        {
+            con = DriverManager.getConnection(DEFAULT_CONNECTION_URL);
+            PreparedStatement statement = con.prepareStatement("select * from "+
+                    ((JDBCSessionIdManager)_sessionIdManager)._sessionIdTableSchema.getTableName()+
+                    " where "+((JDBCSessionIdManager)_sessionIdManager)._sessionIdTableSchema.getIdColumn()+" = ?");
+            statement.setString(1, id);
+            ResultSet result = statement.executeQuery();
+            return result.next();
+        }
+        finally
+        {
+            if (con != null)
+                con.close();
+        }
+    }
+    
+    
+    public boolean existsInSessionTable(String id, boolean verbose)
+    throws Exception
+    {
+        Class.forName(DRIVER_CLASS);
+        Connection con = null;
+        try
+        {
+            con = DriverManager.getConnection(DEFAULT_CONNECTION_URL);
+            PreparedStatement statement = con.prepareStatement("select * from "+
+                    ((JDBCSessionIdManager)_sessionIdManager)._sessionTableSchema.getTableName()+
+                    " where "+((JDBCSessionIdManager)_sessionIdManager)._sessionTableSchema.getIdColumn()+" = ?");
+            statement.setString(1, id);
+            ResultSet result = statement.executeQuery();
+            if (verbose)
+            {
+                boolean results = false;
+                while (result.next())
+                {
+                    results = true;
+                }
+                return results;
+            }
+            else
+                return result.next();
+        }
+        finally
+        {
+            if (con != null)
+                con.close();
+        }
+    }
+    
+    
+    
+    public Set<String> getSessionIds ()
+    throws Exception
+    {
+        HashSet<String> ids = new HashSet<String>();
+        Class.forName(DRIVER_CLASS);
+        Connection con = null;
+        try
+        {
+            con = DriverManager.getConnection(DEFAULT_CONNECTION_URL);
+            PreparedStatement statement = con.prepareStatement("select * from "+((JDBCSessionIdManager)_sessionIdManager)._sessionIdTableSchema.getTableName());
+          
+            ResultSet result = statement.executeQuery();
+            while (result.next())
+            {
+                ids.add(result.getString(1));
+            }
+            return ids;
+        }
+        finally
+        {
+            if (con != null)
+                con.close();
+        }
+    }
 }
diff --git a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/LastAccessTimeTest.java b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/LastAccessTimeTest.java
index 17e60a1..541c1ef 100644
--- a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/LastAccessTimeTest.java
+++ b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/LastAccessTimeTest.java
@@ -18,7 +18,10 @@
 
 package org.eclipse.jetty.server.session;
 
-import org.eclipse.jetty.util.log.Log;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+
+import org.junit.After;
 import org.junit.Test;
 
 /**
@@ -37,4 +40,17 @@ public class LastAccessTimeTest extends AbstractLastAccessTimeTest
         // Log.getLog().setDebugEnabled(true);
         super.testLastAccessTime();
     }
+    
+    @After
+    public void tearDown() throws Exception 
+    {
+        try
+        {
+            DriverManager.getConnection( "jdbc:derby:sessions;shutdown=true" );
+        }
+        catch( SQLException expected )
+        {
+        }
+    }
+    
 }
diff --git a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/LocalSessionScavengingTest.java b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/LocalSessionScavengingTest.java
index 30a9362..87eec2c 100644
--- a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/LocalSessionScavengingTest.java
+++ b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/LocalSessionScavengingTest.java
@@ -18,6 +18,10 @@
 
 package org.eclipse.jetty.server.session;
 
+import java.sql.DriverManager;
+import java.sql.SQLException;
+
+import org.junit.After;
 import org.junit.Test;
 
 /**
@@ -52,4 +56,16 @@ public class LocalSessionScavengingTest extends AbstractLocalSessionScavengingTe
     {
         super.testLocalSessionsScavenging();
     }
+    
+    @After
+    public void tearDown() throws Exception 
+    {
+        try
+        {
+            DriverManager.getConnection( "jdbc:derby:sessions;shutdown=true" );
+        }
+        catch( SQLException expected )
+        {
+        }
+    }
 }
diff --git a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/MaxInactiveMigrationTest.java b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/MaxInactiveMigrationTest.java
index 01b2674..6aa2bd3 100644
--- a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/MaxInactiveMigrationTest.java
+++ b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/MaxInactiveMigrationTest.java
@@ -31,11 +31,10 @@ import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpSession;
 
-import org.eclipse.jetty.client.ContentExchange;
 import org.eclipse.jetty.client.HttpClient;
-import org.eclipse.jetty.http.HttpMethods;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
 import org.eclipse.jetty.servlet.ServletContextHandler;
-import org.eclipse.jetty.util.log.Log;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -47,7 +46,7 @@ import org.junit.Test;
  * Test
  */
 public class MaxInactiveMigrationTest
-{      
+{
     private JdbcTestServer testServer1;
     private JdbcTestServer testServer2;
     private HttpClient client;
@@ -73,7 +72,6 @@ public class MaxInactiveMigrationTest
         testServer1.start();
         testServer2.start();
         client = new HttpClient();
-        client.setConnectorType(HttpClient.CONNECTOR_SOCKET);
         client.start();
     }
 
@@ -83,11 +81,11 @@ public class MaxInactiveMigrationTest
         testServer1.stop();
         testServer2.stop();
         client.stop();
-        try 
+        try
         {
             DriverManager.getConnection( "jdbc:derby:sessions;shutdown=true" );
-        } 
-        catch( SQLException expected ) 
+        }
+        catch( SQLException expected )
         {
         }
     }
@@ -99,22 +97,18 @@ public class MaxInactiveMigrationTest
         int port=server.getPort();
 
         //Log.getLog().setDebugEnabled(true);
-        
-        ContentExchange exchange1 = new ContentExchange(true);
-        exchange1.setMethod(HttpMethods.GET);
-        exchange1.setURL("http://localhost:" + port + "" + "/test");
+        Request request = client.newRequest("http://localhost:" + port + "" + "/test");
         if (sessionCookie != null)
-            exchange1.getRequestFields().add("Cookie", sessionCookie);
-        client.send(exchange1);
-        exchange1.waitForDone();
-        assertEquals(HttpServletResponse.SC_OK, exchange1.getResponseStatus());
+            request.header("Cookie", sessionCookie);
+        ContentResponse response = request.send();
+        assertEquals(HttpServletResponse.SC_OK, response.getStatus());
 
-        sessionCookie = exchange1.getResponseFields().getStringField("Set-Cookie");
+        sessionCookie = response.getHeaders().getStringField("Set-Cookie");
         assertTrue( sessionCookie != null );
         // Mangle the cookie, replacing Path with $Path, etc.
         sessionCookie = sessionCookie.replaceFirst("(\\W)(P|p)ath=", "$1\\$Path=");
 
-        return exchange1.getResponseContent();
+        return response.getContentAsString();
     }
 
 
@@ -129,12 +123,12 @@ public class MaxInactiveMigrationTest
             HttpSession session = request.getSession( true );
             Integer counter = ( Integer )session.getAttribute( ATTR_COUNTER );
             if( counter == null ) {
-                counter = new Integer( 0 );
+                counter = 0;
             }
-            counter = new Integer( counter.intValue() + 1 );
+            counter = counter + 1;
             session.setAttribute( ATTR_COUNTER, counter );
             PrintWriter writer = response.getWriter();
-            writer.write( "Hello World " + counter.intValue() );
+            writer.write( "Hello World " + counter);
             writer.flush();
         }
 
diff --git a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/ModifyMaxInactiveIntervalTest.java b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/ModifyMaxInactiveIntervalTest.java
new file mode 100644
index 0000000..fd11d1a
--- /dev/null
+++ b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/ModifyMaxInactiveIntervalTest.java
@@ -0,0 +1,145 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.session;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+
+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 org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.junit.Test;
+
+
+/**
+ * ModifyMaxInactiveIntervalTest
+ *
+ *
+ *
+ */
+public class ModifyMaxInactiveIntervalTest
+{
+    
+        public static int inactive = 4;
+        public static int newMaxInactive = 20;
+        public static int scavenge = 1;
+        
+    @Test
+    public void testSessionExpiryAfterModifiedMaxInactiveInterval() throws Exception
+    {
+        AbstractTestServer server = new JdbcTestServer(0,inactive,scavenge);
+        
+        ServletContextHandler ctxA = server.addContext("/mod");
+        ctxA.addServlet(TestModServlet.class, "/test");
+      
+        server.start();
+        int port=server.getPort();
+        try
+        {
+            HttpClient client = new HttpClient();
+            client.start();
+            try
+            {
+                // Perform a request to create a session
+                
+                ContentResponse response = client.GET("http://localhost:" + port + "/mod/test?action=create");
+                
+                assertEquals(HttpServletResponse.SC_OK,response.getStatus());
+                String sessionCookie = response.getHeaders().getStringField("Set-Cookie");
+                assertTrue(sessionCookie != null);
+                // Mangle the cookie, replacing Path with $Path, etc.
+                sessionCookie = sessionCookie.replaceFirst("(\\W)(P|p)ath=", "$1\\$Path=");
+                
+                //do another request to change the maxinactive interval
+                Request request = client.newRequest("http://localhost:" + port + "/mod/test?action=change&val="+newMaxInactive);
+                request.header("Cookie", sessionCookie);
+                response = request.send();
+
+                assertEquals(HttpServletResponse.SC_OK,response.getStatus());
+                               
+                //wait for longer than the old inactive interval
+                Thread.currentThread().sleep(10*1000L);
+                
+                //do another request using the cookie to ensure the session is still there
+               
+                request= client.newRequest("http://localhost:" + port + "/mod/test?action=test");
+                request.header("Cookie", sessionCookie);
+                response = request.send();
+                assertEquals(HttpServletResponse.SC_OK,response.getStatus());
+            }
+            finally
+            {
+                client.stop();
+            }
+        }
+        finally
+        {
+            server.stop();
+        }
+    }
+    
+    public static class TestModServlet extends HttpServlet
+    {
+        @Override
+        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+        {
+            String action = request.getParameter("action");
+            
+            if ("create".equals(action))
+            {
+                HttpSession session = request.getSession(true);
+                return;
+            }
+            
+            if ("change".equals(action))
+            {
+                HttpSession session = request.getSession(false);
+                if (session == null)
+                    throw new ServletException("Session is null for action=change");
+
+                String tmp = request.getParameter("val");
+                int interval = -1;
+                interval = (tmp==null?-1:Integer.parseInt(tmp));
+     
+                if (interval > 0)
+                    session.setMaxInactiveInterval(interval);
+                return;
+            }
+            
+            if ("test".equals(action))
+            {
+                HttpSession session = request.getSession(false);
+                if (session == null)
+                    throw new ServletException("Session does not exist");
+                assertEquals(ModifyMaxInactiveIntervalTest.newMaxInactive, session.getMaxInactiveInterval());
+                return;
+            }
+        }
+    }
+    
+}
diff --git a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/NewSessionTest.java b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/NewSessionTest.java
index b7088ec..fc05235 100644
--- a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/NewSessionTest.java
+++ b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/NewSessionTest.java
@@ -18,6 +18,10 @@
 
 package org.eclipse.jetty.server.session;
 
+import java.sql.DriverManager;
+import java.sql.SQLException;
+
+import org.junit.After;
 import org.junit.Test;
 
 /**
@@ -38,4 +42,16 @@ public class NewSessionTest extends AbstractNewSessionTest
     {
         super.testNewSession();
     }
+    
+    @After
+    public void tearDown() throws Exception 
+    {
+        try
+        {
+            DriverManager.getConnection( "jdbc:derby:sessions;shutdown=true" );
+        }
+        catch( SQLException expected )
+        {
+        }
+    }
 }
diff --git a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/OrphanedSessionTest.java b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/OrphanedSessionTest.java
index 01e3463..391f3e0 100644
--- a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/OrphanedSessionTest.java
+++ b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/OrphanedSessionTest.java
@@ -18,6 +18,10 @@
 
 package org.eclipse.jetty.server.session;
 
+import java.sql.DriverManager;
+import java.sql.SQLException;
+
+import org.junit.After;
 import org.junit.Test;
 
 /**
@@ -35,4 +39,16 @@ public class OrphanedSessionTest extends AbstractOrphanedSessionTest
     {
         super.testOrphanedSession();
     }
+    
+    @After
+    public void tearDown() throws Exception 
+    {
+        try
+        {
+            DriverManager.getConnection( "jdbc:derby:sessions;shutdown=true" );
+        }
+        catch( SQLException expected )
+        {
+        }
+    }
 }
diff --git a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/ProxySerializationTest.java b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/ProxySerializationTest.java
new file mode 100644
index 0000000..38db019
--- /dev/null
+++ b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/ProxySerializationTest.java
@@ -0,0 +1,58 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.server.session;
+
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.junit.Test;
+
+/**
+ * ProxySerializationTest
+ *
+ *
+ */
+public class ProxySerializationTest extends AbstractProxySerializationTest
+{
+
+    /** 
+     * @see org.eclipse.jetty.server.session.AbstractProxySerializationTest#createServer(int, int, int)
+     */
+    @Override
+    public AbstractTestServer createServer(int port, int max, int scavenge)
+    {
+        return new JdbcTestServer(port, max, scavenge);
+    }
+
+    /** 
+     * @see org.eclipse.jetty.server.session.AbstractProxySerializationTest#customizeContext(org.eclipse.jetty.servlet.ServletContextHandler)
+     */
+    @Override
+    public void customizeContext(ServletContextHandler c)
+    {
+    }
+    
+    
+
+    @Test
+    public void testProxySerialization() throws Exception
+    {
+        super.testProxySerialization();
+    }
+
+}
diff --git a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/ReentrantRequestSessionTest.java b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/ReentrantRequestSessionTest.java
index 3aed315..dced30d 100644
--- a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/ReentrantRequestSessionTest.java
+++ b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/ReentrantRequestSessionTest.java
@@ -18,6 +18,10 @@
 
 package org.eclipse.jetty.server.session;
 
+import java.sql.DriverManager;
+import java.sql.SQLException;
+
+import org.junit.After;
 import org.junit.Test;
 
 
@@ -37,4 +41,16 @@ public class ReentrantRequestSessionTest extends AbstractReentrantRequestSession
     {
         super.testReentrantRequestSession();
     }
+    
+    @After
+    public void tearDown() throws Exception 
+    {
+        try
+        {
+            DriverManager.getConnection( "jdbc:derby:sessions;shutdown=true" );
+        }
+        catch( SQLException expected )
+        {
+        }
+    }
 }
diff --git a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/ReloadedSessionMissingClassTest.java b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/ReloadedSessionMissingClassTest.java
index ce86658..a502720 100644
--- a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/ReloadedSessionMissingClassTest.java
+++ b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/ReloadedSessionMissingClassTest.java
@@ -18,7 +18,9 @@
 
 package org.eclipse.jetty.server.session;
 
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 
 import java.io.File;
 import java.io.FileWriter;
@@ -27,9 +29,9 @@ import java.net.URLClassLoader;
 
 import javax.servlet.http.HttpServletResponse;
 
-import org.eclipse.jetty.client.ContentExchange;
 import org.eclipse.jetty.client.HttpClient;
-import org.eclipse.jetty.http.HttpMethods;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
 import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
 import org.eclipse.jetty.toolchain.test.TestingDir;
 import org.eclipse.jetty.util.log.Log;
@@ -95,18 +97,14 @@ public class ReloadedSessionMissingClassTest
         try
         {
             HttpClient client = new HttpClient();
-            client.setConnectorType(HttpClient.CONNECTOR_SOCKET);
             client.start();
             try
             {
                 // Perform one request to server1 to create a session
-                ContentExchange exchange1 = new ContentExchange(true);
-                exchange1.setMethod(HttpMethods.GET);
-                exchange1.setURL("http://localhost:" + port1 + contextPath +"/bar?action=set");
-                client.send(exchange1);
-                exchange1.waitForDone();
-                assertEquals( HttpServletResponse.SC_OK, exchange1.getResponseStatus());
-                String sessionCookie = exchange1.getResponseFields().getStringField("Set-Cookie");
+                ContentResponse response = client.GET("http://localhost:" + port1 + contextPath +"/bar?action=set");
+                
+                assertEquals( HttpServletResponse.SC_OK, response.getStatus());
+                String sessionCookie = response.getHeaders().getStringField("Set-Cookie");
                 assertTrue(sessionCookie != null);
                 String sessionId = (String)webApp.getServletContext().getAttribute("foo");
                 assertNotNull(sessionId);
@@ -117,18 +115,14 @@ public class ReloadedSessionMissingClassTest
                 webApp.stop();
                 
                 webApp.setClassLoader(loaderWithoutFoo);
-                webApp.addServlet("Bar", "/bar");
                 
                 //restart webapp
                 webApp.start();
                 
-                ContentExchange exchange2 = new ContentExchange(true);
-                exchange2.setMethod(HttpMethods.GET);
-                exchange2.setURL("http://localhost:" + port1 + contextPath + "/bar?action=get");
-                exchange2.getRequestFields().add("Cookie", sessionCookie);
-                client.send(exchange2);
-                exchange2.waitForDone();
-                assertEquals(HttpServletResponse.SC_OK,exchange2.getResponseStatus());
+                Request request = client.newRequest("http://localhost:" + port1 + contextPath + "/bar?action=get");
+                request.header("Cookie", sessionCookie);
+                response = request.send();
+                assertEquals(HttpServletResponse.SC_OK,response.getStatus());
                 String afterStopSessionId = (String)webApp.getServletContext().getAttribute("foo.session");
                 
                 assertNotNull(afterStopSessionId);
diff --git a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/SaveIntervalTest.java b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/SaveIntervalTest.java
index 14ce2e4..2b7b62e 100644
--- a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/SaveIntervalTest.java
+++ b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/SaveIntervalTest.java
@@ -31,8 +31,8 @@ import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpSession;
 
 import org.eclipse.jetty.client.HttpClient;
-import org.eclipse.jetty.client.ContentExchange;
-import org.eclipse.jetty.http.HttpMethods;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
 import org.eclipse.jetty.servlet.ServletContextHandler;
 import org.eclipse.jetty.servlet.ServletHolder;
 import org.junit.Test;
@@ -74,14 +74,10 @@ public class SaveIntervalTest
             client.start();
             try
             {
-                // Perform a request to create a session 
-                ContentExchange exchange1 = new ContentExchange(true);
-                exchange1.setMethod(HttpMethods.GET);
-                exchange1.setURL("http://localhost:" + port + "/mod/test?action=create");
-                client.send(exchange1);
-                exchange1.waitForDone();
-                assertEquals(HttpServletResponse.SC_OK,exchange1.getResponseStatus());
-                String sessionCookie = exchange1.getResponseFields().getStringField("Set-Cookie");
+                // Perform a request to create a session              
+                ContentResponse response = client.GET("http://localhost:" + port + "/mod/test?action=create");
+                assertEquals(HttpServletResponse.SC_OK,response.getStatus());
+                String sessionCookie = response.getHeaders().getStringField("Set-Cookie");
                 assertTrue(sessionCookie != null);
                 // Mangle the cookie, replacing Path with $Path, etc.
                 sessionCookie = sessionCookie.replaceFirst("(\\W)(P|p)ath=", "$1\\$Path=");
@@ -89,13 +85,10 @@ public class SaveIntervalTest
                 
                 
                 //do another request to change the session attribute
-                ContentExchange exchange2 = new ContentExchange(true);
-                exchange2.setMethod(HttpMethods.GET);
-                exchange2.setURL("http://localhost:" + port + "/mod/test?action=set");
-                exchange2.getRequestFields().add("Cookie", sessionCookie);
-                client.send(exchange2);
-                exchange2.waitForDone();
-                assertEquals(HttpServletResponse.SC_OK,exchange2.getResponseStatus());
+                Request request = client.newRequest("http://localhost:" + port + "/mod/test?action=set");
+                request.header("Cookie", sessionCookie);
+                response = request.send();
+                assertEquals(HttpServletResponse.SC_OK,response.getStatus());
                 long tmp = ((JDBCSessionManager.Session)servlet._session).getLastSaved();
                 assertNotEquals(lastSaved, tmp); //set of attribute will cause save to db
                 lastSaved = tmp;
@@ -108,13 +101,10 @@ public class SaveIntervalTest
                 //do another request to access the session, this will cause session to be initially
                 //checked against db. On exit of request, the access time will need updating, so the
                 //session will be saved to db.
-                ContentExchange exchange3 = new ContentExchange(true);
-                exchange3.setMethod(HttpMethods.GET);
-                exchange3.setURL("http://localhost:" + port + "/mod/test?action=tickle");
-                exchange3.getRequestFields().add("Cookie", sessionCookie);
-                client.send(exchange3);
-                exchange3.waitForDone();
-                assertEquals(HttpServletResponse.SC_OK,exchange3.getResponseStatus());
+                request = client.newRequest("http://localhost:" + port + "/mod/test?action=tickle");
+                request.header("Cookie", sessionCookie);
+                response = request.send();
+                assertEquals(HttpServletResponse.SC_OK,response.getStatus());
                 tmp = ((JDBCSessionManager.Session)servlet._session).getLastSaved();
                 assertNotEquals(lastSaved, tmp);
                 lastSaved = tmp;
@@ -125,13 +115,10 @@ public class SaveIntervalTest
                 //do another request to access the session. This time, the save interval has not
                 //expired, so we should NOT see a debug trace of loading stale session. Nor should
                 //the exit of the request cause a save of the updated access time.
-                ContentExchange exchange4 = new ContentExchange(true);
-                exchange4.setMethod(HttpMethods.GET);
-                exchange4.setURL("http://localhost:" + port + "/mod/test?action=tickle");
-                exchange4.getRequestFields().add("Cookie", sessionCookie);
-                client.send(exchange3);
-                exchange4.waitForDone();
-                assertEquals(HttpServletResponse.SC_OK,exchange4.getResponseStatus());
+                request = client.newRequest("http://localhost:" + port + "/mod/test?action=tickle");
+                request.header("Cookie", sessionCookie);
+                response = request.send();
+                assertEquals(HttpServletResponse.SC_OK,response.getStatus());
                 tmp = ((JDBCSessionManager.Session)servlet._session).getLastSaved();
                 assertEquals(lastSaved, tmp); //the save interval did not expire, so update to the access time will not have been persisted
             }
diff --git a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/ServerCrossContextSessionTest.java b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/ServerCrossContextSessionTest.java
index ee931ff..83c6944 100644
--- a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/ServerCrossContextSessionTest.java
+++ b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/ServerCrossContextSessionTest.java
@@ -18,6 +18,10 @@
 
 package org.eclipse.jetty.server.session;
 
+import java.sql.DriverManager;
+import java.sql.SQLException;
+
+import org.junit.After;
 import org.junit.Test;
 
 /**
@@ -36,4 +40,16 @@ public class ServerCrossContextSessionTest extends AbstractServerCrossContextSes
     {
         super.testCrossContextDispatch();
     }
+    
+    @After
+    public void tearDown() throws Exception 
+    {
+        try
+        {
+            DriverManager.getConnection( "jdbc:derby:sessions;shutdown=true" );
+        }
+        catch( SQLException expected )
+        {
+        }
+    }
 }
diff --git a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/SessionExpiryTest.java b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/SessionExpiryTest.java
index 05f3d52..0452d94 100644
--- a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/SessionExpiryTest.java
+++ b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/SessionExpiryTest.java
@@ -18,6 +18,24 @@
 
 package org.eclipse.jetty.server.session;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSessionEvent;
+import javax.servlet.http.HttpSessionListener;
+
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.junit.After;
 import org.junit.Test;
 
 /**
@@ -29,6 +47,22 @@ import org.junit.Test;
 public class SessionExpiryTest extends AbstractSessionExpiryTest
 {
 
+    public class TestHttpSessionListener implements HttpSessionListener
+    {
+        public List<String> createdSessions = new ArrayList<String>();
+        public List<String> destroyedSessions = new ArrayList<String>();
+        
+        public void sessionDestroyed(HttpSessionEvent se)
+        {
+            destroyedSessions.add(se.getSession().getId());
+        }
+        
+        public void sessionCreated(HttpSessionEvent se)
+        {
+            createdSessions.add(se.getSession().getId());
+        }
+    };
+    
     /** 
      * @see org.eclipse.jetty.server.session.AbstractSessionExpiryTest#createServer(int, int, int)
      */
@@ -41,9 +75,74 @@ public class SessionExpiryTest extends AbstractSessionExpiryTest
     @Test
     public void testSessionExpiry() throws Exception
     {
-        super.testSessionExpiry();
-    }
+     
+        
+        String contextPath = "";
+        String servletMapping = "/server";
+        int inactivePeriod = 2;
+        int scavengePeriod = 1;
+        AbstractTestServer server1 = createServer(0, inactivePeriod, scavengePeriod);
+        TestServlet servlet = new TestServlet();
+        ServletHolder holder = new ServletHolder(servlet);
+        ServletContextHandler context = server1.addContext(contextPath);
+        context.addServlet(holder, servletMapping);
+        TestHttpSessionListener listener = new TestHttpSessionListener();
+        
+        context.getSessionHandler().addEventListener(listener);
+        
+        server1.start();
+        int port1 = server1.getPort();
 
+        try
+        {
+            HttpClient client = new HttpClient();
+            client.start();
+            String url = "http://localhost:" + port1 + contextPath + servletMapping;
+
+            //make a request to set up a session on the server
+            ContentResponse response1 = client.GET(url + "?action=init");
+            assertEquals(HttpServletResponse.SC_OK,response1.getStatus());
+            String sessionCookie = response1.getHeaders().getStringField("Set-Cookie");
+            assertTrue(sessionCookie != null);
+            // Mangle the cookie, replacing Path with $Path, etc.
+            sessionCookie = sessionCookie.replaceFirst("(\\W)(P|p)ath=", "$1\\$Path=");
+            
+            String sessionId = extractSessionId(sessionCookie);     
+            
+            assertTrue(listener.createdSessions.contains(sessionId));
+            //now stop the server
+            server1.stop();
+
+            //and wait until the expiry time has passed
+            pause(inactivePeriod);
+
+            //restart the server
+            server1.start();
+            
+            port1 = server1.getPort();
+            url = "http://localhost:" + port1 + contextPath + servletMapping;
+
+            //make another request, the session should have expired
+            Request request = client.newRequest(url + "?action=test");
+            request.getHeaders().add("Cookie", sessionCookie);
+            ContentResponse response2 = request.send();
+            assertEquals(HttpServletResponse.SC_OK,response2.getStatus());
+            
+            //and wait until the expiry time has passed
+            pause(inactivePeriod);
+            
+            assertTrue(listener.destroyedSessions.contains(sessionId));
+        }
+        finally
+        {
+            server1.stop();
+        }     
+    }
+  
+    
+    
+    
+    
     @Test
     public void testSessionNotExpired() throws Exception
     {
@@ -51,4 +150,34 @@ public class SessionExpiryTest extends AbstractSessionExpiryTest
     }
 
     
+    
+    public String extractSessionId (String sessionCookie)
+    {
+        if (sessionCookie == null)
+            return null;
+        sessionCookie = sessionCookie.trim();
+        int i = sessionCookie.indexOf(';');
+        if (i >= 0)
+            sessionCookie = sessionCookie.substring(0,i);
+        if (sessionCookie.startsWith("JSESSIONID"))
+            sessionCookie = sessionCookie.substring("JSESSIONID=".length());
+        i = sessionCookie.indexOf('.');
+        if (i >=0)
+            sessionCookie = sessionCookie.substring(0,i);
+        return sessionCookie;
+    }
+
+    
+    
+    @After
+    public void tearDown() throws Exception 
+    {
+        try
+        {
+            DriverManager.getConnection( "jdbc:derby:sessions;shutdown=true" );
+        }
+        catch( SQLException expected )
+        {
+        }
+    }
 }
diff --git a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/SessionInvalidateAndCreateTest.java b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/SessionInvalidateAndCreateTest.java
new file mode 100644
index 0000000..746e0bd
--- /dev/null
+++ b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/SessionInvalidateAndCreateTest.java
@@ -0,0 +1,38 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.server.session;
+
+import org.junit.Test;
+
+public class SessionInvalidateAndCreateTest extends AbstractSessionInvalidateAndCreateTest
+{
+
+    @Override
+    public AbstractTestServer createServer(int port, int max, int scavenge)
+    {
+        return new JdbcTestServer(port,max,scavenge);
+    }
+
+    @Test
+    public void testSessionScavenge() throws Exception
+    {
+        super.testSessionScavenge();
+    }
+}
diff --git a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/SessionMigrationTest.java b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/SessionMigrationTest.java
index 1c52755..2ff6fa4 100644
--- a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/SessionMigrationTest.java
+++ b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/SessionMigrationTest.java
@@ -18,6 +18,10 @@
 
 package org.eclipse.jetty.server.session;
 
+import java.sql.DriverManager;
+import java.sql.SQLException;
+
+import org.junit.After;
 import org.junit.Test;
 
 /**
@@ -36,4 +40,16 @@ public class SessionMigrationTest extends AbstractSessionMigrationTest
     {
         super.testSessionMigration();
     }
+    
+    @After
+    public void tearDown() throws Exception 
+    {
+        try
+        {
+            DriverManager.getConnection( "jdbc:derby:sessions;shutdown=true" );
+        }
+        catch( SQLException expected )
+        {
+        }
+    }
 }
diff --git a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/SessionRenewTest.java b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/SessionRenewTest.java
new file mode 100644
index 0000000..60416b6
--- /dev/null
+++ b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/SessionRenewTest.java
@@ -0,0 +1,54 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.session;
+
+import java.sql.DriverManager;
+import java.sql.SQLException;
+
+import org.junit.After;
+import org.junit.Test;
+
+public class SessionRenewTest extends AbstractSessionRenewTest
+{
+
+    @Override
+    public AbstractTestServer createServer(int port, int max, int scavenge)
+    {
+        return new JdbcTestServer(port, max, scavenge);
+    }
+
+    @Test
+    public void testSessionRenewal() throws Exception
+    {
+        super.testSessionRenewal();
+    }
+    
+    @After
+    public void tearDown() throws Exception 
+    {
+        try
+        {
+            DriverManager.getConnection( "jdbc:derby:sessions;shutdown=true" );
+        }
+        catch( SQLException expected )
+        {
+        }
+    }
+
+}
diff --git a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/SessionValueSavingTest.java b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/SessionValueSavingTest.java
index ca133cd..41c2560 100644
--- a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/SessionValueSavingTest.java
+++ b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/SessionValueSavingTest.java
@@ -18,7 +18,10 @@
 
 package org.eclipse.jetty.server.session;
 
-import org.eclipse.jetty.util.log.Log;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+
+import org.junit.After;
 import org.junit.Test;
 
 /**
@@ -31,9 +34,21 @@ public class SessionValueSavingTest extends AbstractSessionValueSavingTest
         return new JdbcTestServer(port,max,scavenge);
     }
 
-	@Override
-	public void testSessionValueSaving() throws Exception 
-	{
-		super.testSessionValueSaving();
-	} 
+        @Test
+        public void testSessionValueSaving() throws Exception 
+        {
+                super.testSessionValueSaving();
+        } 
+
+        @After
+        public void tearDown() throws Exception 
+        {
+            try
+            {
+                DriverManager.getConnection( "jdbc:derby:sessions;shutdown=true" );
+            }
+            catch( SQLException expected )
+            {
+            }
+        }
 }
diff --git a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/StopSessionManagerPreserveSessionTest.java b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/StopSessionManagerPreserveSessionTest.java
new file mode 100644
index 0000000..84b2ac3
--- /dev/null
+++ b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/StopSessionManagerPreserveSessionTest.java
@@ -0,0 +1,81 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.session;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import java.sql.DriverManager;
+import java.sql.SQLException;
+
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.junit.After;
+import org.junit.Test;
+
+public class StopSessionManagerPreserveSessionTest extends AbstractStopSessionManagerPreserveSessionTest
+{
+    JdbcTestServer _server;
+    
+    @After
+    public void tearDown() throws Exception 
+    {
+        try
+        {
+            DriverManager.getConnection( "jdbc:derby:sessions;shutdown=true" );
+        }
+        catch( SQLException expected )
+        {
+        }
+    }
+    
+    @Override
+    public void checkSessionPersisted(boolean expected)
+    {
+        try
+        {
+            boolean actual = _server.existsInSessionTable(_id, true);
+            System.err.println(expected+":"+actual);
+            assertEquals(expected, actual);
+        }
+        catch (Exception e)
+        {
+            fail(e.getMessage());
+        }
+    }
+
+    @Override
+    public AbstractTestServer createServer(int port)
+    {
+        _server = new JdbcTestServer(0);
+        return _server;
+    }
+
+    @Override
+    public void configureSessionManagement(ServletContextHandler context)
+    {
+      //nothing special
+    }
+
+    
+    @Test
+    public void testStopSessionManagerPreserveSession() throws Exception
+    {
+        super.testStopSessionManagerPreserveSession();
+    }
+}
diff --git a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/WebAppObjectInSessionTest.java b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/WebAppObjectInSessionTest.java
index 3a95a70..8a48090 100644
--- a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/WebAppObjectInSessionTest.java
+++ b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/WebAppObjectInSessionTest.java
@@ -18,7 +18,11 @@
 
 package org.eclipse.jetty.server.session;
 
+import java.sql.DriverManager;
+import java.sql.SQLException;
+
 import org.eclipse.jetty.util.resource.Resource;
+import org.junit.After;
 import org.junit.Test;
 
 /**
@@ -40,4 +44,18 @@ public class WebAppObjectInSessionTest extends AbstractWebAppObjectInSessionTest
     {
         super.testWebappObjectInSession();
     }
+    
+    
+
+    @After
+    public void tearDown() throws Exception 
+    {
+        try
+        {
+            DriverManager.getConnection( "jdbc:derby:sessions;shutdown=true" );
+        }
+        catch( SQLException expected )
+        {
+        }
+    }
 }
diff --git a/tests/test-sessions/test-mongodb-sessions/pom.xml b/tests/test-sessions/test-mongodb-sessions/pom.xml
index f61aa33..6bbc043 100644
--- a/tests/test-sessions/test-mongodb-sessions/pom.xml
+++ b/tests/test-sessions/test-mongodb-sessions/pom.xml
@@ -21,7 +21,7 @@
   <parent>
     <groupId>org.eclipse.jetty.tests</groupId>
     <artifactId>test-sessions-parent</artifactId>
-    <version>8.1.17.v20150415</version>
+    <version>9.2.13.v20150730</version>
   </parent>
   <artifactId>test-mongodb-sessions</artifactId>
   <name>Jetty Tests :: Sessions :: Mongo</name>
@@ -29,13 +29,6 @@
   <build>
     <plugins>
       <plugin>
-        <artifactId>maven-compiler-plugin</artifactId>
-        <configuration>
-          <source>1.5</source>
-          <target>1.5</target>
-        </configuration>
-      </plugin>
-      <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-deploy-plugin</artifactId>
         <configuration>
@@ -110,11 +103,11 @@
           <version>${project.version}</version>
           <optional>true</optional>
         </dependency>
-       <dependency>
-            <groupId>junit</groupId>
-            <artifactId>junit</artifactId>
-            <scope>test</scope>
-        </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.toolchain</groupId>
+      <artifactId>jetty-test-helper</artifactId>
+      <scope>test</scope>
+    </dependency>
   </dependencies>
   <profiles>
       <profile>
diff --git a/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/AttributeNameTest.java b/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/AttributeNameTest.java
new file mode 100644
index 0000000..03e1629
--- /dev/null
+++ b/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/AttributeNameTest.java
@@ -0,0 +1,163 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.nosql.mongodb;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.nosql.NoSqlSession;
+import org.eclipse.jetty.server.session.AbstractTestServer;
+import org.junit.Test;
+
+/**
+ * AttributeNameTest
+ *
+ * Test that attribute names that have special characters with meaning to mongo (eg ".") are
+ * properly escaped and not accidentally removed. 
+ * See bug: https://bugs.eclipse.org/bugs/show_bug.cgi?id=444595
+ */
+public class AttributeNameTest 
+{
+
+
+
+    public AbstractTestServer createServer(int port, int max, int scavenge)
+    throws Exception
+    {   
+        MongoTestServer server = new MongoTestServer(port,max,scavenge,true);
+        
+        return server;
+
+    }
+
+    @Test
+    public void testAttributeNamesWithDots() throws Exception
+    {
+        String contextPath = "";
+        String servletMapping = "/server";
+        int maxInactivePeriod = 10000;
+        int scavengePeriod = 20000;
+        AbstractTestServer server1 = createServer(0,maxInactivePeriod,scavengePeriod);
+        server1.addContext(contextPath).addServlet(TestServlet.class,servletMapping);
+        server1.start();
+        int port1 = server1.getPort();
+        
+        AbstractTestServer server2 = createServer(0,maxInactivePeriod,scavengePeriod);
+        server2.addContext(contextPath).addServlet(TestServlet.class,servletMapping);
+        server2.start();
+        int port2 = server2.getPort();
+        
+        try
+        {
+
+            HttpClient client = new HttpClient();
+            client.start();
+            try
+            {
+
+                // Perform one request to server1 to create a session with attribute with dotted name
+                ContentResponse response = client.GET("http://localhost:" + port1 + contextPath + servletMapping + "?action=init");
+      
+                assertEquals(HttpServletResponse.SC_OK,response.getStatus());
+
+                String resp = response.getContentAsString();
+                
+                String[] sessionTestResponse = resp.split("/");
+                assertEquals("a.b.c",sessionTestResponse[0]);
+ 
+
+                String sessionCookie = response.getHeaders().getStringField("Set-Cookie");
+                assertTrue(sessionCookie != null);
+                //Mangle the cookie, replacing Path with $Path, etc.
+                sessionCookie = sessionCookie.replaceFirst("(\\W)(P|p)ath=","$1\\$Path=");
+
+                //Make a request to the 2nd server which will do a refresh, use TestServlet to ensure that the
+                //session attribute with dotted name is not removed
+                Request request2 = client.newRequest("http://localhost:" + port2 + contextPath + servletMapping + "?action=get");
+                request2.header("Cookie", sessionCookie);
+                ContentResponse response2 = request2.send();
+                assertEquals(HttpServletResponse.SC_OK,response2.getStatus());
+                
+            }
+            finally
+            {
+                client.stop();
+            }
+        }
+        finally
+        {
+            server1.stop();
+            server2.stop();
+        }
+    }
+
+    public static class TestServlet extends HttpServlet
+    {
+        @Override
+        protected void doGet(HttpServletRequest request, HttpServletResponse httpServletResponse) throws ServletException, IOException
+        {
+            String action = request.getParameter("action");
+            if ("init".equals(action))
+            {
+                NoSqlSession session = (NoSqlSession)request.getSession(true);
+                session.setAttribute("a.b.c",System.currentTimeMillis());               
+                sendResult(session,httpServletResponse.getWriter());
+                
+            }
+            else
+            {
+                NoSqlSession session = (NoSqlSession)request.getSession(false);
+                assertNotNull(session);     
+                assertNotNull(session.getAttribute("a.b.c"));
+                sendResult(session,httpServletResponse.getWriter());
+            }
+
+        }
+
+        private void sendResult(NoSqlSession session, PrintWriter writer)
+        {
+            if (session != null)
+            {
+                if (session.getAttribute("a.b.c") != null)
+                    writer.print("a.b.c/"+session.getAttribute("a.b.c"));    
+                else
+                    writer.print("-/0");
+            }
+            else
+            {
+                writer.print("0/0");
+            }
+        }
+        
+    }
+
+ 
+}
diff --git a/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/InvalidateSessionTest.java b/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/InvalidateSessionTest.java
new file mode 100644
index 0000000..c1e1c0f
--- /dev/null
+++ b/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/InvalidateSessionTest.java
@@ -0,0 +1,54 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.nosql.mongodb;
+
+
+import org.eclipse.jetty.server.session.AbstractInvalidationSessionTest;
+import org.eclipse.jetty.server.session.AbstractTestServer;
+import org.junit.Test;
+
+public class InvalidateSessionTest extends AbstractInvalidationSessionTest
+{
+
+    @Override
+    public AbstractTestServer createServer(int port)
+    {
+       return new MongoTestServer(port);
+    }
+
+    @Override
+    public void pause()
+    {
+        try
+        {
+            Thread.currentThread().sleep(2000);
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+    
+    @Test
+    public void testInvalidation() throws Exception
+    {
+        super.testInvalidation();
+    }  
+    
+}
diff --git a/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/LastAccessTimeTest.java b/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/LastAccessTimeTest.java
index 7e5ffa3..bf86b5e 100644
--- a/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/LastAccessTimeTest.java
+++ b/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/LastAccessTimeTest.java
@@ -20,7 +20,6 @@ package org.eclipse.jetty.nosql.mongodb;
 
 import org.eclipse.jetty.server.session.AbstractLastAccessTimeTest;
 import org.eclipse.jetty.server.session.AbstractTestServer;
-import org.junit.Ignore;
 import org.junit.Test;
 
 public class LastAccessTimeTest extends AbstractLastAccessTimeTest
diff --git a/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/LocalSessionScavengingTest.java b/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/LocalSessionScavengingTest.java
new file mode 100644
index 0000000..152a19b
--- /dev/null
+++ b/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/LocalSessionScavengingTest.java
@@ -0,0 +1,41 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.nosql.mongodb;
+
+import org.eclipse.jetty.server.session.AbstractLocalSessionScavengingTest;
+import org.eclipse.jetty.server.session.AbstractTestServer;
+
+public class LocalSessionScavengingTest extends AbstractLocalSessionScavengingTest
+{
+
+    @Override
+    public AbstractTestServer createServer(int port, int max, int scavenge)
+    {
+       return new MongoTestServer(port,max,scavenge);
+    }
+
+    @Override
+    public void testLocalSessionsScavenging() throws Exception
+    {
+        super.testLocalSessionsScavenging();
+    }
+    
+    
+
+}
diff --git a/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/MongoTestServer.java b/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/MongoTestServer.java
index 3b08a14..dcc5bb6 100644
--- a/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/MongoTestServer.java
+++ b/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/MongoTestServer.java
@@ -58,11 +58,10 @@ public class MongoTestServer extends AbstractTestServer
     {
         try
         {
-            System.err.println("MongoTestServer:SessionIdManager:" + _maxInactivePeriod + "/" + _scavengePeriod);
+            System.err.println("MongoTestServer:SessionIdManager scavenge: delay:"+ _scavengePeriod + " period:"+_scavengePeriod);
             MongoSessionIdManager idManager = new MongoSessionIdManager(_server);
             idManager.setWorkerName("w"+(__workers++));
-            idManager.setScavengeDelay((int)TimeUnit.SECONDS.toMillis(_scavengePeriod));
-            idManager.setScavengePeriod(_maxInactivePeriod);                  
+            idManager.setScavengePeriod(_scavengePeriod);                  
 
             return idManager;
         }
diff --git a/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/PurgeInvalidSessionTest.java b/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/PurgeInvalidSessionTest.java
index 8e242ae..2332ea4 100644
--- a/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/PurgeInvalidSessionTest.java
+++ b/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/PurgeInvalidSessionTest.java
@@ -31,9 +31,9 @@ import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpSession;
 
-import org.eclipse.jetty.client.ContentExchange;
 import org.eclipse.jetty.client.HttpClient;
-import org.eclipse.jetty.http.HttpMethods;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
 import org.eclipse.jetty.servlet.ServletContextHandler;
 import org.junit.Test;
 
@@ -88,41 +88,30 @@ public class PurgeInvalidSessionTest
         try
         {
             HttpClient client = new HttpClient();
-            client.setConnectorType(HttpClient.CONNECTOR_SOCKET);
             client.start();
             try
             {
                 //Create a session
-                ContentExchange exchange = new ContentExchange(true);
-                exchange.setMethod(HttpMethods.GET);
-                exchange.setURL("http://localhost:" + port + contextPath + servletMapping + "?action=create");
-                client.send(exchange);
-                exchange.waitForDone();
-                assertEquals(HttpServletResponse.SC_OK,exchange.getResponseStatus());
-                String sessionCookie = exchange.getResponseFields().getStringField("Set-Cookie");
+                ContentResponse response = client.GET("http://localhost:" + port + contextPath + servletMapping + "?action=create");
+                assertEquals(HttpServletResponse.SC_OK,response.getStatus());
+                String sessionCookie = response.getHeaders().getStringField("Set-Cookie");
                 assertTrue(sessionCookie != null);
                 // Mangle the cookie, replacing Path with $Path, etc.
                 sessionCookie = sessionCookie.replaceFirst("(\\W)(P|p)ath=", "$1\\$Path=");
 
                 //make a request to invalidate the session
-                exchange = new ContentExchange(true);
-                exchange.setMethod(HttpMethods.GET);
-                exchange.setURL("http://localhost:" + port + contextPath + servletMapping + "?action=invalidate");
-                exchange.getRequestFields().add("Cookie", sessionCookie);
-                client.send(exchange);
-                exchange.waitForDone();
-                assertEquals(HttpServletResponse.SC_OK,exchange.getResponseStatus());
+                Request request = client.newRequest("http://localhost:" + port + contextPath + servletMapping + "?action=invalidate");
+                request.header("Cookie", sessionCookie);
+                response = request.send();
+                assertEquals(HttpServletResponse.SC_OK,response.getStatus());
                 
                 Thread.currentThread().sleep(3*purgeDelay); //sleep long enough for purger to have run
                 
-                //make a request using previous session to test if its still there               
-                exchange = new ContentExchange(true);
-                exchange.setMethod(HttpMethods.GET);
-                exchange.setURL("http://localhost:" + port + contextPath + servletMapping + "?action=test");
-                exchange.getRequestFields().add("Cookie", sessionCookie);
-                client.send(exchange);
-                exchange.waitForDone();
-                assertEquals(HttpServletResponse.SC_OK,exchange.getResponseStatus());
+                //make a request using previous session to test if its still there
+                request = client.newRequest("http://localhost:" + port + contextPath + servletMapping + "?action=test");
+                request.header("Cookie", sessionCookie);
+                response = request.send();
+                assertEquals(HttpServletResponse.SC_OK,response.getStatus());
             }
             finally
             {
@@ -156,26 +145,31 @@ public class PurgeInvalidSessionTest
             if ("create".equals(action))
             {
                 HttpSession session = request.getSession(true);
+                session.setAttribute("foo", "bar");
                 assertTrue(session.isNew());
             }
             else if ("invalidate".equals(action))
             {  
                 HttpSession existingSession = request.getSession(false);
                 assertNotNull(existingSession);
+                String id = existingSession.getId();
+                id = (id.indexOf(".") > 0?id.substring(0, id.indexOf(".")):id);
+                DBObject dbSession = _sessions.findOne(new BasicDBObject("id",id)); 
+                assertNotNull(dbSession);
+                
                 existingSession.invalidate();
-                String id = request.getRequestedSessionId();
-                assertNotNull(id);
-                id = id.substring(0, id.indexOf("."));
                 
                 //still in db, just marked as invalid
-                DBObject dbSession = _sessions.findOne(new BasicDBObject("id", id));
-                assertTrue(dbSession != null);
+                dbSession = _sessions.findOne(new BasicDBObject("id", id));       
+                assertNotNull(dbSession);
+                assertTrue(dbSession.containsField(MongoSessionManager.__INVALIDATED));
             }
             else if ("test".equals(action))
             {
                 String id = request.getRequestedSessionId();
                 assertNotNull(id);
-                id = id.substring(0, id.indexOf("."));
+       
+                id = (id.indexOf(".") > 0?id.substring(0, id.indexOf(".")):id);
   
                 HttpSession existingSession = request.getSession(false);
                 assertTrue(existingSession == null);
@@ -186,5 +180,4 @@ public class PurgeInvalidSessionTest
             }
         }
     }
-
 }
diff --git a/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/PurgeValidSessionTest.java b/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/PurgeValidSessionTest.java
index 547ed18..9f1198a 100644
--- a/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/PurgeValidSessionTest.java
+++ b/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/PurgeValidSessionTest.java
@@ -31,9 +31,10 @@ import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpSession;
 
-import org.eclipse.jetty.client.ContentExchange;
+
 import org.eclipse.jetty.client.HttpClient;
-import org.eclipse.jetty.http.HttpMethods;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.api.ContentResponse;
 import org.eclipse.jetty.servlet.ServletContextHandler;
 import org.junit.Test;
 
@@ -90,18 +91,13 @@ public class PurgeValidSessionTest
         try
         {
             HttpClient client = new HttpClient();
-            client.setConnectorType(HttpClient.CONNECTOR_SOCKET);
             client.start();
             try
             {
                 //Create a session
-                ContentExchange exchange = new ContentExchange(true);
-                exchange.setMethod(HttpMethods.GET);
-                exchange.setURL("http://localhost:" + port + contextPath + servletMapping + "?action=create");
-                client.send(exchange);
-                exchange.waitForDone();
-                assertEquals(HttpServletResponse.SC_OK,exchange.getResponseStatus());
-                String sessionCookie = exchange.getResponseFields().getStringField("Set-Cookie");
+                ContentResponse response = client.GET("http://localhost:" + port + contextPath + servletMapping + "?action=create");
+                assertEquals(HttpServletResponse.SC_OK,response.getStatus());
+                String sessionCookie = response.getHeaders().getStringField("Set-Cookie");
                 assertTrue(sessionCookie != null);
                 // Mangle the cookie, replacing Path with $Path, etc.
                 sessionCookie = sessionCookie.replaceFirst("(\\W)(P|p)ath=", "$1\\$Path=");
@@ -110,13 +106,10 @@ public class PurgeValidSessionTest
                 Thread.currentThread().sleep(3*purgeDelay); //sleep long enough for purger to have run
 
                 //make a request using previous session to test if its still there               
-                exchange = new ContentExchange(true);
-                exchange.setMethod(HttpMethods.GET);
-                exchange.setURL("http://localhost:" + port + contextPath + servletMapping + "?action=test");
-                exchange.getRequestFields().add("Cookie", sessionCookie);
-                client.send(exchange);
-                exchange.waitForDone();
-                assertEquals(HttpServletResponse.SC_OK,exchange.getResponseStatus());
+                Request request = client.newRequest("http://localhost:" + port + contextPath + servletMapping + "?action=test");
+                request.header("Cookie", sessionCookie);
+                ContentResponse response2 = request.send();
+                assertEquals(HttpServletResponse.SC_OK,response2.getStatus());
             }
             finally
             {
diff --git a/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/ServerCrossContextSessionTest.java b/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/ServerCrossContextSessionTest.java
index 67237ac..56b92be 100644
--- a/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/ServerCrossContextSessionTest.java
+++ b/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/ServerCrossContextSessionTest.java
@@ -23,6 +23,7 @@ import org.eclipse.jetty.server.session.AbstractTestServer;
 import org.junit.Ignore;
 import org.junit.Test;
 
+
 public class ServerCrossContextSessionTest extends AbstractServerCrossContextSessionTest
 {
     public AbstractTestServer createServer(int port)
diff --git a/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/SessionExpiryTest.java b/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/SessionExpiryTest.java
new file mode 100644
index 0000000..54df88f
--- /dev/null
+++ b/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/SessionExpiryTest.java
@@ -0,0 +1,45 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.nosql.mongodb;
+
+import org.eclipse.jetty.server.session.AbstractSessionExpiryTest;
+import org.eclipse.jetty.server.session.AbstractTestServer;
+import org.junit.Test;
+
+public class SessionExpiryTest extends AbstractSessionExpiryTest
+{
+
+    @Override
+    public AbstractTestServer createServer(int port, int max, int scavenge)
+    {
+       return new MongoTestServer(port,max,scavenge);
+    }
+
+    @Test
+    public void testSessionNotExpired() throws Exception
+    {
+        super.testSessionNotExpired();
+    }
+    
+    @Test
+    public void testSessionExpiry() throws Exception
+    {
+        super.testSessionExpiry();
+    }
+}
diff --git a/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/SessionInvalidateAndCreateTest.java b/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/SessionInvalidateAndCreateTest.java
new file mode 100644
index 0000000..973f6aa
--- /dev/null
+++ b/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/SessionInvalidateAndCreateTest.java
@@ -0,0 +1,41 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.nosql.mongodb;
+
+import org.eclipse.jetty.server.session.AbstractSessionInvalidateAndCreateTest;
+import org.eclipse.jetty.server.session.AbstractTestServer;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class SessionInvalidateAndCreateTest extends AbstractSessionInvalidateAndCreateTest
+{
+
+    @Override
+    public AbstractTestServer createServer(int port, int max, int scavenge)
+    {
+        return new MongoTestServer(port,max,scavenge);
+    }
+    
+
+    @Test
+    public void testSessionScavenge() throws Exception
+    {
+        super.testSessionScavenge();
+    }
+}
diff --git a/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/SessionMigrationTest.java b/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/SessionMigrationTest.java
new file mode 100644
index 0000000..04014b5
--- /dev/null
+++ b/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/SessionMigrationTest.java
@@ -0,0 +1,39 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.nosql.mongodb;
+
+import org.eclipse.jetty.server.session.AbstractSessionMigrationTest;
+import org.eclipse.jetty.server.session.AbstractTestServer;
+import org.junit.Test;
+
+public class SessionMigrationTest extends AbstractSessionMigrationTest
+{
+
+    @Override
+    public AbstractTestServer createServer(int port)
+    {
+        return new MongoTestServer(port);
+    }
+
+    @Test
+    public void testSessionMigration() throws Exception
+    {
+        super.testSessionMigration();
+    }
+}
diff --git a/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/SessionRenewTest.java b/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/SessionRenewTest.java
new file mode 100644
index 0000000..d0da35d
--- /dev/null
+++ b/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/SessionRenewTest.java
@@ -0,0 +1,39 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.nosql.mongodb;
+
+import org.eclipse.jetty.server.session.AbstractSessionRenewTest;
+import org.eclipse.jetty.server.session.AbstractTestServer;
+import org.junit.Test;
+
+public class SessionRenewTest extends AbstractSessionRenewTest
+{
+
+    @Override
+    public AbstractTestServer createServer(int port, int max, int scavenge)
+    {
+        return new MongoTestServer(port, max, scavenge);
+    }
+
+    @Test
+    public void testSessionRenewal() throws Exception
+    {
+        super.testSessionRenewal();
+    }
+}
diff --git a/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/SessionSavingValueTest.java b/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/SessionSavingValueTest.java
index 442eb22..0889d9e 100644
--- a/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/SessionSavingValueTest.java
+++ b/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/SessionSavingValueTest.java
@@ -26,6 +26,7 @@ import java.io.PrintWriter;
 import java.io.Serializable;
 import java.lang.management.ManagementFactory;
 import java.net.MalformedURLException;
+import java.util.concurrent.Future;
 
 import javax.management.remote.JMXServiceURL;
 import javax.servlet.ServletException;
@@ -33,15 +34,14 @@ import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import org.eclipse.jetty.client.ContentExchange;
 import org.eclipse.jetty.client.HttpClient;
-import org.eclipse.jetty.http.HttpMethods;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
 import org.eclipse.jetty.jmx.ConnectorServer;
 import org.eclipse.jetty.jmx.MBeanContainer;
 import org.eclipse.jetty.nosql.NoSqlSession;
 import org.eclipse.jetty.server.session.AbstractSessionValueSavingTest;
 import org.eclipse.jetty.server.session.AbstractTestServer;
-import org.junit.Ignore;
 import org.junit.Test;
 
 public class SessionSavingValueTest extends AbstractSessionValueSavingTest
@@ -51,31 +51,31 @@ public class SessionSavingValueTest extends AbstractSessionValueSavingTest
     
     public AbstractTestServer createServer(int port, int max, int scavenge)
     {
-//        ConnectorServer srv = null;
+        ConnectorServer srv = null;
         try
         {
-//            srv = new ConnectorServer(
-//                    new JMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:0/jettytest"),
-//                    "org.eclipse.jetty:name=rmiconnectorserver");
-//            srv.start();
+            srv = new ConnectorServer(
+                    new JMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:0/jettytest"),
+                    "org.eclipse.jetty:name=rmiconnectorserver");
+            srv.start();
             
             MongoTestServer server = new MongoTestServer(port,max,scavenge,true);
 
-//            MBeanContainer mbean = new MBeanContainer(ManagementFactory.getPlatformMBeanServer());
-//           
-//            server.getServer().getContainer().addEventListener(mbean);
-//            server.getServer().addBean(mbean);
-//
-//            mbean.start();
+            MBeanContainer mbean = new MBeanContainer(ManagementFactory.getPlatformMBeanServer());
+           
+            //server.getServer().getContainer().addEventListener(mbean);
+            server.getServer().addBean(mbean);
+
+            //mbean.start();
                     
             return server;
 
         }
-//        catch (MalformedURLException e)
-//        {
-//            // TODO Auto-generated catch block
-//            e.printStackTrace();
-//        }
+        catch (MalformedURLException e)
+        {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        }
         catch (Exception e)
         {
             // TODO Auto-generated catch block
@@ -86,7 +86,7 @@ public class SessionSavingValueTest extends AbstractSessionValueSavingTest
     }
 
     @Test
-    @Ignore ("requires mongodb server")
+    //@Ignore ("requires mongodb server")
     public void testSessionValueSaving() throws Exception
     {
         String contextPath = "";
@@ -101,7 +101,6 @@ public class SessionSavingValueTest extends AbstractSessionValueSavingTest
         {
 
             HttpClient client = new HttpClient();
-            client.setConnectorType(HttpClient.CONNECTOR_SOCKET);
             client.start();
             try
             {
@@ -109,19 +108,16 @@ public class SessionSavingValueTest extends AbstractSessionValueSavingTest
                 { "0", "null" };
 
                 // Perform one request to server1 to create a session
-                ContentExchange exchange1 = new ContentExchange(true);
-                exchange1.setMethod(HttpMethods.GET);
-                exchange1.setURL("http://localhost:" + port1 + contextPath + servletMapping + "?action=init");
-                client.send(exchange1);
-                exchange1.waitForDone();
-                assertEquals(HttpServletResponse.SC_OK,exchange1.getResponseStatus());
-
-                String[] sessionTestResponse = exchange1.getResponseContent().split("/");
+                ContentResponse response = client.GET("http://localhost:" + port1 + contextPath + servletMapping + "?action=init");
+      
+                assertEquals(HttpServletResponse.SC_OK,response.getStatus());
+
+                String[] sessionTestResponse = response.getContentAsString().split("/");
                 assertTrue(Long.parseLong(sessionTestValue[0]) < Long.parseLong(sessionTestResponse[0]));
 
                 sessionTestValue = sessionTestResponse;
 
-                String sessionCookie = exchange1.getResponseFields().getStringField("Set-Cookie");
+                String sessionCookie = response.getHeaders().getStringField("Set-Cookie");
                 assertTrue(sessionCookie != null);
                 // Mangle the cookie, replacing Path with $Path, etc.
                 sessionCookie = sessionCookie.replaceFirst("(\\W)(P|p)ath=","$1\\$Path=");
@@ -135,22 +131,20 @@ public class SessionSavingValueTest extends AbstractSessionValueSavingTest
 
                 for (int i = 0; i < 10; ++i)
                 {
-                    ContentExchange exchange2 = new ContentExchange(true);
-                    exchange2.setMethod(HttpMethods.GET);
-                    exchange2.setURL("http://localhost:" + port1 + contextPath + servletMapping);
-                    exchange2.getRequestFields().add("Cookie",sessionCookie);
-                    client.send(exchange2);
-                    exchange2.waitForDone();
-                    assertEquals(HttpServletResponse.SC_OK,exchange2.getResponseStatus());
+                    Request request2 = client.newRequest("http://localhost:" + port1 + contextPath + servletMapping);
+                    request2.header("Cookie",sessionCookie);
+                    ContentResponse response2 = request2.send();
+         
+                    assertEquals(HttpServletResponse.SC_OK,response2.getStatus());
 
-                    sessionTestResponse = exchange2.getResponseContent().split("/");
+                    sessionTestResponse = response2.getContentAsString().split("/");
 
                     assertTrue(Long.parseLong(sessionTestValue[0]) < Long.parseLong(sessionTestResponse[0]));
                     assertTrue(Long.parseLong(sessionTestValue[1]) < Long.parseLong(sessionTestResponse[1]));
 
                     sessionTestValue = sessionTestResponse;
 
-                    String setCookie = exchange1.getResponseFields().getStringField("Set-Cookie");
+                    String setCookie = response2.getHeaders().getStringField("Set-Cookie");
                     if (setCookie != null)
                         sessionCookie = setCookie.replaceFirst("(\\W)(P|p)ath=","$1\\$Path=");
 
diff --git a/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/StopSessionManagerDeleteSessionTest.java b/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/StopSessionManagerDeleteSessionTest.java
new file mode 100644
index 0000000..89f5277
--- /dev/null
+++ b/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/StopSessionManagerDeleteSessionTest.java
@@ -0,0 +1,164 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.nosql.mongodb;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+import java.net.UnknownHostException;
+
+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 org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.junit.Test;
+
+import com.mongodb.BasicDBObject;
+import com.mongodb.DBCollection;
+import com.mongodb.DBObject;
+import com.mongodb.Mongo;
+import com.mongodb.MongoException;
+
+public class StopSessionManagerDeleteSessionTest
+{
+    public MongoTestServer createServer(int port, int max, int scavenge)
+    {
+        MongoTestServer server =  new MongoTestServer(port,max,scavenge);
+       
+        return server;
+    }
+    
+    /**
+     * @throws Exception
+     */
+    @Test
+    public void testStopSessionManagerDeleteSession() throws Exception
+    {
+        String contextPath = "";
+        String servletMapping = "/server";
+        
+        MongoTestServer server = createServer(0, 1, 0);
+        ServletContextHandler context = server.addContext(contextPath);
+        ServletHolder holder = new ServletHolder();
+        TestServlet servlet = new TestServlet();
+        holder.setServlet(servlet);
+        
+        context.addServlet(holder, servletMapping);
+        
+        MongoSessionManager sessionManager = (MongoSessionManager)context.getSessionHandler().getSessionManager();
+        sessionManager.setPreserveOnStop(false);
+        MongoSessionIdManager idManager = (MongoSessionIdManager)server.getServer().getSessionIdManager();
+        idManager.setPurge(true);
+
+        
+        server.start();
+        int port=server.getPort();
+        try
+        {
+            HttpClient client = new HttpClient();
+            client.start();
+            try
+            {
+                //Create a session
+                ContentResponse response = client.GET("http://localhost:" + port + contextPath + servletMapping + "?action=create");
+                assertEquals(HttpServletResponse.SC_OK,response.getStatus());
+                String sessionCookie = response.getHeaders().getStringField("Set-Cookie");
+                assertTrue(sessionCookie != null);
+                // Mangle the cookie, replacing Path with $Path, etc.
+                sessionCookie = sessionCookie.replaceFirst("(\\W)(P|p)ath=", "$1\\$Path=");
+
+                //stop the session manager
+                sessionManager.stop();
+                
+                //check the database to see that the session has been marked invalid
+                servlet.checkSessionInDB(false);
+                
+            }
+            finally
+            {
+                client.stop();
+            }
+        }
+        finally
+        {
+            server.stop();
+        }
+    }
+    
+    
+    public static class TestServlet extends HttpServlet
+    {
+        DBCollection _sessions;
+        String _id;
+
+        public TestServlet() throws UnknownHostException, MongoException
+        {
+            super();            
+            _sessions = new Mongo().getDB("HttpSessions").getCollection("sessions");
+        }
+
+        public void checkSessionInDB (boolean expectedValid)
+        {
+            DBObject dbSession = _sessions.findOne(new BasicDBObject("id", _id));
+            assertTrue(dbSession != null);
+            assertEquals(expectedValid, dbSession.get("valid"));
+            if (!expectedValid)
+                assertNotNull(dbSession.get(MongoSessionManager.__INVALIDATED));
+        }
+
+        public String getId()
+        {
+            return _id;
+        }
+        
+        @Override
+        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+        {
+            String action = request.getParameter("action");
+            if ("create".equals(action))
+            {
+                HttpSession session = request.getSession(true);
+                session.setAttribute("foo", "bar");
+                assertTrue(session.isNew());
+                _id = session.getId();
+            }
+            else if ("test".equals(action))
+            {
+                String id = request.getRequestedSessionId();
+                assertNotNull(id);
+                id = id.substring(0, id.indexOf("."));
+  
+                HttpSession existingSession = request.getSession(false);
+                assertTrue(existingSession == null);
+                
+                //not in db any more
+                DBObject dbSession = _sessions.findOne(new BasicDBObject("id", id));
+                assertTrue(dbSession == null);
+            }
+        }
+    }
+}
diff --git a/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/StopSessionManagerPreserveSessionTest.java b/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/StopSessionManagerPreserveSessionTest.java
new file mode 100644
index 0000000..59c831e
--- /dev/null
+++ b/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/StopSessionManagerPreserveSessionTest.java
@@ -0,0 +1,98 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.nosql.mongodb;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.net.UnknownHostException;
+
+import org.eclipse.jetty.server.session.AbstractStopSessionManagerPreserveSessionTest;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.mongodb.BasicDBObject;
+import com.mongodb.DBCollection;
+import com.mongodb.DBObject;
+import com.mongodb.Mongo;
+import com.mongodb.MongoException;
+
+/**
+ * StopSessionManagerPreserveSessionTest
+ *
+ *
+ */
+public class StopSessionManagerPreserveSessionTest extends AbstractStopSessionManagerPreserveSessionTest
+{
+    DBCollection _sessions;
+    
+    @Before
+    public void setUp() throws UnknownHostException, MongoException
+    {
+        _sessions = new Mongo().getDB("HttpSessions").getCollection("sessions");
+    }
+    
+   
+    
+    public MongoTestServer createServer(int port)
+    {
+        MongoTestServer server =  new MongoTestServer(port); 
+        server.getServer().setStopTimeout(0);
+        return server;
+    }
+    
+    
+
+    @Override
+    public void checkSessionPersisted(boolean expected)
+    {
+        DBObject dbSession = _sessions.findOne(new BasicDBObject("id", _id));
+
+        if (expected)
+        {
+            assertTrue(dbSession != null);
+            assertEquals(expected, dbSession.get("valid"));
+        }
+        else
+        {
+            assertTrue(dbSession==null);
+        }
+    }
+
+
+    @Override
+    public void configureSessionManagement(ServletContextHandler context)
+    {
+        ((MongoSessionManager)context.getSessionHandler().getSessionManager()).setPreserveOnStop(true);
+    }
+
+    /**
+     * @throws Exception
+     */
+    @Test
+    public void testStopSessionManagerPreserveSession() throws Exception
+    {
+        super.testStopSessionManagerPreserveSession();
+    }
+
+
+
+}
diff --git a/tests/test-sessions/test-sessions-common/pom.xml b/tests/test-sessions/test-sessions-common/pom.xml
index e9e7ed0..876767e 100644
--- a/tests/test-sessions/test-sessions-common/pom.xml
+++ b/tests/test-sessions/test-sessions-common/pom.xml
@@ -21,7 +21,7 @@
   <parent>
     <groupId>org.eclipse.jetty.tests</groupId>
     <artifactId>test-sessions-parent</artifactId>
-    <version>8.1.17.v20150415</version>
+    <version>9.2.13.v20150730</version>
   </parent>
   <artifactId>test-sessions-common</artifactId>
   <name>Jetty Tests :: Sessions :: Common</name>
diff --git a/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractClientCrossContextSessionTest.java b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractClientCrossContextSessionTest.java
index cfc1ea4..6f44b92 100644
--- a/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractClientCrossContextSessionTest.java
+++ b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractClientCrossContextSessionTest.java
@@ -18,9 +18,10 @@
 
 package org.eclipse.jetty.server.session;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
 import java.io.IOException;
-import java.util.Collections;
-import java.util.Random;
 
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServlet;
@@ -28,14 +29,12 @@ import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpSession;
 
-import org.eclipse.jetty.client.ContentExchange;
 import org.eclipse.jetty.client.HttpClient;
-import org.eclipse.jetty.http.HttpMethods;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
 import org.eclipse.jetty.servlet.ServletContextHandler;
 import org.eclipse.jetty.servlet.ServletHolder;
 import org.junit.Test;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
 
 
 /**
@@ -61,37 +60,30 @@ public abstract class AbstractClientCrossContextSessionTest
         TestServletB servletB = new TestServletB();
         ServletHolder holderB = new ServletHolder(servletB);
         ctxB.addServlet(holderB, servletMapping);
-        server.start();
-        int port = server.getPort();
-        
+
         try
         {
+            server.start();
+            int port = server.getPort();
+            
             HttpClient client = new HttpClient();
-            client.setConnectorType(HttpClient.CONNECTOR_SOCKET);
             client.start();
             try
             {
                 // Perform a request to contextA
-                ContentExchange exchangeA = new ContentExchange(true);
-                exchangeA.setMethod(HttpMethods.GET);
-                exchangeA.setURL("http://localhost:" + port + contextA + servletMapping);
-                client.send(exchangeA);
-                exchangeA.waitForDone();
-                assertEquals(HttpServletResponse.SC_OK,exchangeA.getResponseStatus());
-                String sessionCookie = exchangeA.getResponseFields().getStringField("Set-Cookie");
+                ContentResponse response = client.GET("http://localhost:" + port + contextA + servletMapping);
+
+                assertEquals(HttpServletResponse.SC_OK,response.getStatus());
+                String sessionCookie = response.getHeaders().getStringField("Set-Cookie");
                 assertTrue(sessionCookie != null);
                 // Mangle the cookie, replacing Path with $Path, etc.
                 sessionCookie = sessionCookie.replaceFirst("(\\W)(P|p)ath=", "$1\\$Path=");
 
                 // Perform a request to contextB with the same session cookie
-                ContentExchange exchangeB = new ContentExchange(true);
-                exchangeB.setMethod(HttpMethods.GET);
-                exchangeB.setURL("http://localhost:" + port + contextB + servletMapping);
-                System.err.println("Cookie = "+sessionCookie);
-                exchangeB.getRequestFields().add("Cookie", sessionCookie);  
-                client.send(exchangeB);
-                exchangeB.waitForDone();
-                assertEquals(HttpServletResponse.SC_OK,exchangeB.getResponseStatus());
+                Request request = client.newRequest("http://localhost:" + port + contextB + servletMapping);
+                request.header("Cookie", sessionCookie);
+                ContentResponse responseB = request.send();
+                assertEquals(HttpServletResponse.SC_OK,responseB.getStatus());
                 assertEquals(servletA.sessionId, servletB.sessionId);
             }
             finally
@@ -108,7 +100,7 @@ public abstract class AbstractClientCrossContextSessionTest
     public static class TestServletA extends HttpServlet
     {
         public String sessionId;
-        
+
         @Override
         protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
         {
@@ -131,7 +123,7 @@ public abstract class AbstractClientCrossContextSessionTest
     public static class TestServletB extends HttpServlet
     {
         public String sessionId;
-        
+
         @Override
         protected void doGet(HttpServletRequest request, HttpServletResponse httpServletResponse) throws ServletException, IOException
         {
diff --git a/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractImmortalSessionTest.java b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractImmortalSessionTest.java
index 8d2b91d..6d29c63 100644
--- a/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractImmortalSessionTest.java
+++ b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractImmortalSessionTest.java
@@ -18,9 +18,11 @@
 
 package org.eclipse.jetty.server.session;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
 import java.io.IOException;
 import java.io.PrintWriter;
-import java.util.Random;
 
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServlet;
@@ -28,12 +30,10 @@ import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpSession;
 
-import org.eclipse.jetty.client.ContentExchange;
 import org.eclipse.jetty.client.HttpClient;
-import org.eclipse.jetty.http.HttpMethods;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
 import org.junit.Test;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
 
 
 /**
@@ -52,44 +52,36 @@ public abstract class AbstractImmortalSessionTest
         //turn off session expiry by setting maxInactiveInterval to -1
         AbstractTestServer server = createServer(0, -1, scavengePeriod);
         server.addContext(contextPath).addServlet(TestServlet.class, servletMapping);
-        server.start();
-        int port=server.getPort();
-        
+
         try
         {
+            server.start();
+            int port=server.getPort();
             HttpClient client = new HttpClient();
-            client.setConnectorType(HttpClient.CONNECTOR_SOCKET);
             client.start();
             try
             {
                 int value = 42;
-                ContentExchange exchange = new ContentExchange(true);
-                exchange.setMethod(HttpMethods.GET);
-                exchange.setURL("http://localhost:" + port + contextPath + servletMapping + "?action=set&value=" + value);
-                client.send(exchange);
-                exchange.waitForDone();
-                assertEquals(HttpServletResponse.SC_OK,exchange.getResponseStatus());
-                String sessionCookie = exchange.getResponseFields().getStringField("Set-Cookie");
+                ContentResponse response = client.GET("http://localhost:" + port + contextPath + servletMapping + "?action=set&value=" + value);
+                assertEquals(HttpServletResponse.SC_OK,response.getStatus());
+                String sessionCookie = response.getHeaders().getStringField("Set-Cookie");
                 assertTrue(sessionCookie != null);
                 // Mangle the cookie, replacing Path with $Path, etc.
                 sessionCookie = sessionCookie.replaceFirst("(\\W)(P|p)ath=", "$1\\$Path=");
 
-                String response = exchange.getResponseContent();
-                assertEquals(response.trim(),String.valueOf(value));
+                String resp = response.getContentAsString();
+                assertEquals(resp.trim(),String.valueOf(value));
 
                 // Let's wait for the scavenger to run, waiting 2.5 times the scavenger period
                 Thread.sleep(scavengePeriod * 2500L);
 
                 // Be sure the session is still there
-                exchange = new ContentExchange(true);
-                exchange.setMethod(HttpMethods.GET);
-                exchange.setURL("http://localhost:" + port + contextPath + servletMapping + "?action=get");
-                exchange.getRequestFields().add("Cookie", sessionCookie);
-                client.send(exchange);
-                exchange.waitForDone();
-                assertEquals(HttpServletResponse.SC_OK,exchange.getResponseStatus());
-                response = exchange.getResponseContent();
-                assertEquals(String.valueOf(value),response.trim());
+                Request request = client.newRequest("http://localhost:" + port + contextPath + servletMapping + "?action=get");
+                request.header("Cookie", sessionCookie);
+                response = request.send();
+                assertEquals(HttpServletResponse.SC_OK,response.getStatus());
+                resp = response.getContentAsString();
+                assertEquals(String.valueOf(value),resp.trim());
             }
             finally
             {
diff --git a/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractInvalidationSessionTest.java b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractInvalidationSessionTest.java
index ea31fef..5ef2d85 100644
--- a/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractInvalidationSessionTest.java
+++ b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractInvalidationSessionTest.java
@@ -18,8 +18,10 @@
 
 package org.eclipse.jetty.server.session;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
 import java.io.IOException;
-import java.util.Random;
 
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServlet;
@@ -27,12 +29,11 @@ import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpSession;
 
-import org.eclipse.jetty.client.ContentExchange;
 import org.eclipse.jetty.client.HttpClient;
-import org.eclipse.jetty.http.HttpMethods;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
 import org.junit.Test;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
 
 /**
  * AbstractInvalidationSessionTest
@@ -51,18 +52,22 @@ public abstract class AbstractInvalidationSessionTest
         String servletMapping = "/server";
         AbstractTestServer server1 = createServer(0);
         server1.addContext(contextPath).addServlet(TestServlet.class, servletMapping);
-        server1.start();
-        int port1 = server1.getPort();
+
+
         try
         {
+            server1.start();
+            int port1 = server1.getPort();
             AbstractTestServer server2 = createServer(0);
             server2.addContext(contextPath).addServlet(TestServlet.class, servletMapping);
-            server2.start();
-            int port2=server2.getPort();
+
             try
             {
+                server2.start();
+                int port2=server2.getPort();
                 HttpClient client = new HttpClient();
-                client.setConnectorType(HttpClient.CONNECTOR_SOCKET);
+                QueuedThreadPool executor = new QueuedThreadPool();
+                client.setExecutor(executor);
                 client.start();
                 try
                 {
@@ -71,45 +76,34 @@ public abstract class AbstractInvalidationSessionTest
                     urls[1] = "http://localhost:" + port2 + contextPath + servletMapping;
 
                     // Create the session on node1
-                    ContentExchange exchange1 = new ContentExchange(true);
-                    exchange1.setMethod(HttpMethods.GET);
-                    exchange1.setURL(urls[0] + "?action=init");
-                    client.send(exchange1);
-                    exchange1.waitForDone();
-                    assertEquals(HttpServletResponse.SC_OK,exchange1.getResponseStatus());
-                    String sessionCookie = exchange1.getResponseFields().getStringField("Set-Cookie");
+                    ContentResponse response1 = client.GET(urls[0] + "?action=init");
+
+                    assertEquals(HttpServletResponse.SC_OK,response1.getStatus());
+                    String sessionCookie = response1.getHeaders().getStringField("Set-Cookie");
                     assertTrue(sessionCookie != null);
                     // Mangle the cookie, replacing Path with $Path, etc.
                     sessionCookie = sessionCookie.replaceFirst("(\\W)(P|p)ath=", "$1\\$Path=");
 
                     // Be sure the session is also present in node2
-                    ContentExchange exchange2 = new ContentExchange(true);
-                    exchange2.setMethod(HttpMethods.GET);
-                    exchange2.setURL(urls[1] + "?action=increment");
-                    exchange2.getRequestFields().add("Cookie", sessionCookie);
-                    client.send(exchange2);
-                    exchange2.waitForDone();
-                    assertEquals(HttpServletResponse.SC_OK,exchange2.getResponseStatus());
+
+                    Request request2 = client.newRequest(urls[1] + "?action=increment");
+                    request2.header("Cookie", sessionCookie);
+                    ContentResponse response2 = request2.send();
+                    assertEquals(HttpServletResponse.SC_OK,response2.getStatus());
 
                     // Invalidate on node1
-                    exchange1 = new ContentExchange(true);
-                    exchange1.setMethod(HttpMethods.GET);
-                    exchange1.setURL(urls[0] + "?action=invalidate");
-                    exchange1.getRequestFields().add("Cookie", sessionCookie);
-                    client.send(exchange1);
-                    exchange1.waitForDone();
-                    assertEquals(HttpServletResponse.SC_OK, exchange1.getResponseStatus());
+                    Request request1 = client.newRequest(urls[0] + "?action=invalidate");
+                    request1.header("Cookie", sessionCookie);
+                    response1 = request1.send();
+                    assertEquals(HttpServletResponse.SC_OK, response1.getStatus());
 
                     pause();
-                    
+
                     // Be sure on node2 we don't see the session anymore
-                    exchange2 = new ContentExchange(true);
-                    exchange2.setMethod(HttpMethods.GET);
-                    exchange2.setURL(urls[1] + "?action=test");
-                    exchange2.getRequestFields().add("Cookie", sessionCookie);
-                    client.send(exchange2);
-                    exchange2.waitForDone();
-                    assertEquals(HttpServletResponse.SC_OK,exchange2.getResponseStatus());
+                    request2 = client.newRequest(urls[1] + "?action=test");
+                    request2.header("Cookie", sessionCookie);
+                    response2 = request2.send();
+                    assertEquals(HttpServletResponse.SC_OK,response2.getStatus());
                 }
                 finally
                 {
diff --git a/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractLastAccessTimeTest.java b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractLastAccessTimeTest.java
index 226fd27..589b3bb 100644
--- a/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractLastAccessTimeTest.java
+++ b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractLastAccessTimeTest.java
@@ -18,9 +18,12 @@
 
 package org.eclipse.jetty.server.session;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
 import java.io.IOException;
 import java.io.PrintWriter;
-import java.util.Random;
 
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServlet;
@@ -30,18 +33,21 @@ import javax.servlet.http.HttpSession;
 import javax.servlet.http.HttpSessionEvent;
 import javax.servlet.http.HttpSessionListener;
 
-import org.eclipse.jetty.client.ContentExchange;
 import org.eclipse.jetty.client.HttpClient;
-import org.eclipse.jetty.http.HttpMethods;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
 import org.eclipse.jetty.servlet.ServletContextHandler;
 import org.eclipse.jetty.servlet.ServletHolder;
 import org.junit.Test;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
 
 
 /**
  * AbstractLastAccessTimeTest
+ *
+ * This test checks that a session can migrate from node A to node B, kept in use in node B
+ * past the time at which it would have expired due to inactivity on node A but is NOT
+ * scavenged by node A. In other words, it tests that a session that migrates from one node
+ * to another is not timed out on the original node.
  */
 public abstract class AbstractLastAccessTimeTest
 {
@@ -52,8 +58,8 @@ public abstract class AbstractLastAccessTimeTest
     {
         String contextPath = "";
         String servletMapping = "/server";
-        int maxInactivePeriod = 8;
-        int scavengePeriod = 2;
+        int maxInactivePeriod = 8; //session will timeout after 8 seconds
+        int scavengePeriod = 2; //scavenging occurs every 2 seconds
         AbstractTestServer server1 = createServer(0, maxInactivePeriod, scavengePeriod);
         TestServlet servlet1 = new TestServlet();
         ServletHolder holder1 = new ServletHolder(servlet1);
@@ -61,30 +67,27 @@ public abstract class AbstractLastAccessTimeTest
         TestSessionListener listener1 = new TestSessionListener();
         context.addEventListener(listener1);
         context.addServlet(holder1, servletMapping);
-        server1.start();
-        int port1=server1.getPort();
+
         try
         {
+            server1.start();
+            int port1=server1.getPort();
             AbstractTestServer server2 = createServer(0, maxInactivePeriod, scavengePeriod);
             server2.addContext(contextPath).addServlet(TestServlet.class, servletMapping);
-            server2.start();
-            int port2=server2.getPort();
+
             try
             {
+                server2.start();
+                int port2=server2.getPort();
                 HttpClient client = new HttpClient();
-                client.setConnectorType(HttpClient.CONNECTOR_SOCKET);
                 client.start();
                 try
                 {
                     // Perform one request to server1 to create a session
-                    ContentExchange exchange1 = new ContentExchange(true);
-                    exchange1.setMethod(HttpMethods.GET);
-                    exchange1.setURL("http://localhost:" + port1 + contextPath + servletMapping + "?action=init");
-                    client.send(exchange1);
-                    exchange1.waitForDone();
-                    assertEquals(HttpServletResponse.SC_OK, exchange1.getResponseStatus());
-                    assertEquals("test", exchange1.getResponseContent());
-                    String sessionCookie = exchange1.getResponseFields().getStringField("Set-Cookie");
+                    ContentResponse response1 = client.GET("http://localhost:" + port1 + contextPath + servletMapping + "?action=init");
+                    assertEquals(HttpServletResponse.SC_OK, response1.getStatus());
+                    assertEquals("test", response1.getContentAsString());
+                    String sessionCookie = response1.getHeaders().getStringField("Set-Cookie");
                     assertTrue( sessionCookie != null );
                     // Mangle the cookie, replacing Path with $Path, etc.
                     sessionCookie = sessionCookie.replaceFirst("(\\W)(P|p)ath=", "$1\\$Path=");
@@ -97,19 +100,16 @@ public abstract class AbstractLastAccessTimeTest
                     int requestInterval = 500;
                     for (int i = 0; i < maxInactivePeriod * (1000 / requestInterval); ++i)
                     {
-                        ContentExchange exchange2 = new ContentExchange(true);
-                        exchange2.setMethod(HttpMethods.GET);
-                        exchange2.setURL("http://localhost:" + port2 + contextPath + servletMapping);
-                        exchange2.getRequestFields().add("Cookie", sessionCookie);
-                        client.send(exchange2);
-                        exchange2.waitForDone();
-                        assertEquals(HttpServletResponse.SC_OK , exchange2.getResponseStatus());
-                        assertEquals("test", exchange2.getResponseContent());
-
-                        String setCookie = exchange1.getResponseFields().getStringField("Set-Cookie");
-                        if (setCookie!=null)                    
+                        Request request = client.newRequest("http://localhost:" + port2 + contextPath + servletMapping);
+                        request.header("Cookie", sessionCookie);
+                        ContentResponse response2 = request.send();
+                        assertEquals(HttpServletResponse.SC_OK , response2.getStatus());
+                        assertEquals("test", response2.getContentAsString());
+
+                        String setCookie = response2.getHeaders().getStringField("Set-Cookie");
+                        if (setCookie!=null)
                             sessionCookie = setCookie.replaceFirst("(\\W)(P|p)ath=", "$1\\$Path=");
-                        
+
                         Thread.sleep(requestInterval);
                     }
 
@@ -118,8 +118,7 @@ public abstract class AbstractLastAccessTimeTest
                     Thread.sleep(scavengePeriod * 2500L);
 
                     //check that the session was not scavenged over on server1 by ensuring that the SessionListener destroy method wasn't called
-                    assertTrue (listener1.destroyed == false);  
-
+                    assertFalse(listener1.destroyed);
                 }
                 finally
                 {
@@ -142,13 +141,13 @@ public abstract class AbstractLastAccessTimeTest
         public boolean destroyed = false;
         public boolean created = false;
 
- 
+        @Override
         public void sessionDestroyed(HttpSessionEvent se)
         {
-            destroyed = true;
+           destroyed = true;
         }
 
-   
+        @Override
         public void sessionCreated(HttpSessionEvent se)
         {
             created = true;
@@ -156,8 +155,11 @@ public abstract class AbstractLastAccessTimeTest
     }
 
 
+
     public static class TestServlet extends HttpServlet
     {
+
+
         @Override
         protected void doGet(HttpServletRequest request, HttpServletResponse httpServletResponse) throws ServletException, IOException
         {
@@ -166,7 +168,6 @@ public abstract class AbstractLastAccessTimeTest
             {
                 HttpSession session = request.getSession(true);
                 session.setAttribute("test", "test");
-                
                 sendResult(session, httpServletResponse.getWriter());
 
             }
@@ -178,18 +179,16 @@ public abstract class AbstractLastAccessTimeTest
                 sendResult(session, httpServletResponse.getWriter());
 
                 if (session!=null)
-                {                                       
+                {
                     session.setAttribute("test", "test");
                 }
-                
-
             }
         }
-        
+
         private void sendResult(HttpSession session, PrintWriter writer)
         {
                 if (session != null)
-                {                    
+                {
                         writer.print(session.getAttribute("test"));
                 }
                 else
diff --git a/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractLightLoadTest.java b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractLightLoadTest.java
index 97ec736..7de41eb 100644
--- a/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractLightLoadTest.java
+++ b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractLightLoadTest.java
@@ -18,6 +18,9 @@
 
 package org.eclipse.jetty.server.session;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.Random;
@@ -32,12 +35,10 @@ import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpSession;
 
-import org.eclipse.jetty.client.ContentExchange;
 import org.eclipse.jetty.client.HttpClient;
-import org.eclipse.jetty.http.HttpMethods;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
 import org.junit.Test;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
 
 
 /**
@@ -59,18 +60,19 @@ public abstract class AbstractLightLoadTest
             String servletMapping = "/server";
             AbstractTestServer server1 = createServer( 0 );
             server1.addContext( contextPath ).addServlet( TestServlet.class, servletMapping );
-            server1.start();
-            int port1 = server1.getPort();
+
             try
             {
+                server1.start();
+                int port1 = server1.getPort();
                 AbstractTestServer server2 = createServer( 0 );
                 server2.addContext( contextPath ).addServlet( TestServlet.class, servletMapping );
-                server2.start();
-                int port2=server2.getPort();
+
                 try
                 {
+                    server2.start();
+                    int port2=server2.getPort();
                     HttpClient client = new HttpClient();
-                    client.setConnectorType( HttpClient.CONNECTOR_SOCKET );
                     client.start();
                     try
                     {
@@ -78,13 +80,9 @@ public abstract class AbstractLightLoadTest
                         urls[0] = "http://localhost:" + port1 + contextPath + servletMapping;
                         urls[1] = "http://localhost:" + port2 + contextPath + servletMapping;
 
-                        ContentExchange exchange1 = new ContentExchange( true );
-                        exchange1.setMethod( HttpMethods.GET );
-                        exchange1.setURL( urls[0] + "?action=init" );
-                        client.send( exchange1 );
-                        exchange1.waitForDone();
-                        assertEquals(HttpServletResponse.SC_OK,exchange1.getResponseStatus());
-                        String sessionCookie = exchange1.getResponseFields().getStringField( "Set-Cookie" );
+                        ContentResponse response1 = client.GET(urls[0] + "?action=init");
+                        assertEquals(HttpServletResponse.SC_OK,response1.getStatus());
+                        String sessionCookie = response1.getHeaders().getStringField( "Set-Cookie" );
                         assertTrue(sessionCookie != null);
                         // Mangle the cookie, replacing Path with $Path, etc.
                         sessionCookie = sessionCookie.replaceFirst("(\\W)(P|p)ath=", "$1\\$Path=");
@@ -115,14 +113,11 @@ public abstract class AbstractLightLoadTest
                         executor.shutdownNow();
 
                         // Perform one request to get the result
-                        ContentExchange exchange2 = new ContentExchange( true );
-                        exchange2.setMethod( HttpMethods.GET );
-                        exchange2.setURL( urls[0] + "?action=result" );
-                        exchange2.getRequestFields().add( "Cookie", sessionCookie );
-                        client.send( exchange2 );
-                        exchange2.waitForDone();
-                        assertEquals(HttpServletResponse.SC_OK,exchange2.getResponseStatus());
-                        String response = exchange2.getResponseContent();
+                        Request request = client.newRequest( urls[0] + "?action=result" );
+                        request.header("Cookie", sessionCookie);
+                        ContentResponse response2 = request.send();
+                        assertEquals(HttpServletResponse.SC_OK,response2.getStatus());
+                        String response = response2.getContentAsString();
                         System.out.println( "get = " + response );
                         assertEquals(response.trim(), String.valueOf( clientsCount * requestsCount ) );
                     }
@@ -156,10 +151,10 @@ public abstract class AbstractLightLoadTest
 
         private final String[] urls;
 
+
         public Worker( CyclicBarrier barrier, int requestsCount, String sessionCookie, String[] urls )
         {
             this.client = new HttpClient();
-            this.client.setConnectorType( HttpClient.CONNECTOR_SOCKET );
             this.barrier = barrier;
             this.requestsCount = requestsCount;
             this.sessionCookie = sessionCookie;
@@ -190,14 +185,10 @@ public abstract class AbstractLightLoadTest
                 for ( int i = 0; i < requestsCount; ++i )
                 {
                     int urlIndex = random.nextInt( urls.length );
-
-                    ContentExchange exchange = new ContentExchange( true );
-                    exchange.setMethod( HttpMethods.GET );
-                    exchange.setURL( urls[urlIndex] + "?action=increment" );
-                    exchange.getRequestFields().add( "Cookie", sessionCookie );
-                    client.send( exchange );
-                    exchange.waitForDone();
-                    assertEquals(HttpServletResponse.SC_OK,exchange.getResponseStatus());
+                    Request request = client.newRequest(urls[urlIndex] + "?action=increment");
+                    request.header("Cookie", sessionCookie);
+                    ContentResponse response = request.send();
+                    assertEquals(HttpServletResponse.SC_OK,response.getStatus());
                 }
 
                 // Wait for all workers to be done
diff --git a/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractLocalSessionScavengingTest.java b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractLocalSessionScavengingTest.java
index 40c21a5..b90d3e3 100644
--- a/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractLocalSessionScavengingTest.java
+++ b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractLocalSessionScavengingTest.java
@@ -18,8 +18,10 @@
 
 package org.eclipse.jetty.server.session;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
 import java.io.IOException;
-import java.util.Random;
 
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServlet;
@@ -27,14 +29,11 @@ import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpSession;
 
-import org.eclipse.jetty.client.ContentExchange;
 import org.eclipse.jetty.client.HttpClient;
-import org.eclipse.jetty.http.HttpMethods;
+import org.eclipse.jetty.client.api.ContentResponse;
 import org.eclipse.jetty.server.Request;
 import org.eclipse.jetty.server.SessionManager;
 import org.junit.Test;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
 
 /**
  * AbstractLocalSessionScavengingTest
@@ -54,7 +53,7 @@ public abstract class AbstractLocalSessionScavengingTest
             e.printStackTrace();
         }
     }
-    
+
     @Test
     public void testLocalSessionsScavenging() throws Exception
     {
@@ -64,18 +63,19 @@ public abstract class AbstractLocalSessionScavengingTest
         int scavengePeriod = 2;
         AbstractTestServer server1 = createServer(0, inactivePeriod, scavengePeriod);
         server1.addContext(contextPath).addServlet(TestServlet.class, servletMapping);
-        server1.start();
-        int port1 = server1.getPort();
+
         try
         {
+            server1.start();
+            int port1 = server1.getPort();
             AbstractTestServer server2 = createServer(0, inactivePeriod, scavengePeriod * 3);
             server2.addContext(contextPath).addServlet(TestServlet.class, servletMapping);
-            server2.start();
-            int port2 = server2.getPort();
+
             try
             {
+                server2.start();
+                int port2 = server2.getPort();
                 HttpClient client = new HttpClient();
-                client.setConnectorType(HttpClient.CONNECTOR_SOCKET);
                 client.start();
                 try
                 {
@@ -84,50 +84,39 @@ public abstract class AbstractLocalSessionScavengingTest
                     urls[1] = "http://localhost:" + port2 + contextPath + servletMapping;
 
                     // Create the session on node1
-                    ContentExchange exchange1 = new ContentExchange(true);
-                    exchange1.setMethod(HttpMethods.GET);
-                    exchange1.setURL(urls[0] + "?action=init");
-                    client.send(exchange1);
-                    exchange1.waitForDone();
-                    assertEquals(HttpServletResponse.SC_OK,exchange1.getResponseStatus());
-                    String sessionCookie = exchange1.getResponseFields().getStringField("Set-Cookie");
+                    ContentResponse response1 = client.GET(urls[0] + "?action=init");
+                    assertEquals(HttpServletResponse.SC_OK,response1.getStatus());
+                    String sessionCookie = response1.getHeaders().getStringField("Set-Cookie");
                     assertTrue(sessionCookie != null);
                     // Mangle the cookie, replacing Path with $Path, etc.
                     sessionCookie = sessionCookie.replaceFirst("(\\W)(P|p)ath=", "$1\\$Path=");
 
                     // Be sure the session is also present in node2
-                    ContentExchange exchange2 = new ContentExchange(true);
-                    exchange2.setMethod(HttpMethods.GET);
-                    exchange2.setURL(urls[1] + "?action=test");
-                    exchange2.getRequestFields().add("Cookie", sessionCookie);
-                    client.send(exchange2);
-                    exchange2.waitForDone();
-                    assertEquals(HttpServletResponse.SC_OK,exchange2.getResponseStatus());
-                    
-                    
+                    org.eclipse.jetty.client.api.Request request = client.newRequest(urls[1] + "?action=test");
+                    request.header("Cookie", sessionCookie);
+                    ContentResponse response2 = request.send();
+                    assertEquals(HttpServletResponse.SC_OK,response2.getStatus());
+
+
                     // Wait for the scavenger to run on node1, waiting 2.5 times the scavenger period
                     pause(scavengePeriod);
-                    
+
                     // Check that node1 does not have any local session cached
-                    exchange1 = new ContentExchange(true);
-                    exchange1.setMethod(HttpMethods.GET);
-                    exchange1.setURL(urls[0] + "?action=check");
-                    client.send(exchange1);
-                    exchange1.waitForDone();
-                    assertEquals(HttpServletResponse.SC_OK,exchange1.getResponseStatus());
+                    request = client.newRequest(urls[0] + "?action=check");
+                    request.header("Cookie", sessionCookie);
+                    response1 = request.send();
+                    assertEquals(HttpServletResponse.SC_OK,response1.getStatus());
 
 
                     // Wait for the scavenger to run on node2, waiting 2 times the scavenger period
                     // This ensures that the scavenger on node2 runs at least once.
                     pause(scavengePeriod);
 
-                    // Check that node2 does not have any local session cached   
-                    exchange2 = new ContentExchange(true);
-                    exchange2.setMethod(HttpMethods.GET);
-                    exchange2.setURL(urls[1] + "?action=check");
-                    client.send(exchange2);
-                    exchange2.waitForDone();
-                    assertEquals(HttpServletResponse.SC_OK,exchange2.getResponseStatus());
+                    // Check that node2 does not have any local session cached
+                    request = client.newRequest(urls[1] + "?action=check");
+                    request.header("Cookie", sessionCookie);
+                    response2 = request.send();
+                    assertEquals(HttpServletResponse.SC_OK,response2.getStatus());
                 }
                 finally
                 {
diff --git a/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractNewSessionTest.java b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractNewSessionTest.java
index 4a26258..0314ccf 100644
--- a/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractNewSessionTest.java
+++ b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractNewSessionTest.java
@@ -18,8 +18,10 @@
 
 package org.eclipse.jetty.server.session;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
 import java.io.IOException;
-import java.util.Random;
 
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServlet;
@@ -27,13 +29,11 @@ import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpSession;
 
-import org.eclipse.jetty.client.ContentExchange;
 import org.eclipse.jetty.client.HttpClient;
-import org.eclipse.jetty.http.HttpMethods;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
 import org.eclipse.jetty.servlet.ServletContextHandler;
 import org.junit.Test;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
 
 /**
  * AbstractNewSessionTest
@@ -53,32 +53,28 @@ public abstract class AbstractNewSessionTest
             e.printStackTrace();
         }
     }
-    
+
     @Test
     public void testNewSession() throws Exception
     {
-        String contextPath = "";
         String servletMapping = "/server";
         int scavengePeriod = 3;
         AbstractTestServer server = createServer(0, 1, scavengePeriod);
-        ServletContextHandler context = server.addContext(contextPath);
+        ServletContextHandler context = server.addContext("/");
         context.addServlet(TestServlet.class, servletMapping);
-        server.start();
-        int port=server.getPort();
+        String contextPath = "";
+
         try
         {
+            server.start();
+            int port=server.getPort();
             HttpClient client = new HttpClient();
-            client.setConnectorType(HttpClient.CONNECTOR_SOCKET);
             client.start();
             try
             {
-                ContentExchange exchange = new ContentExchange(true);
-                exchange.setMethod(HttpMethods.GET);
-                exchange.setURL("http://localhost:" + port + contextPath + servletMapping + "?action=create");
-                client.send(exchange);
-                exchange.waitForDone();
-                assertEquals(HttpServletResponse.SC_OK,exchange.getResponseStatus());
-                String sessionCookie = exchange.getResponseFields().getStringField("Set-Cookie");
+                ContentResponse response = client.GET("http://localhost:" + port + contextPath + servletMapping + "?action=create");
+                assertEquals(HttpServletResponse.SC_OK,response.getStatus());
+                String sessionCookie = response.getHeaders().getStringField("Set-Cookie");
                 assertTrue(sessionCookie != null);
                 // Mangle the cookie, replacing Path with $Path, etc.
                 sessionCookie = sessionCookie.replaceFirst("(\\W)(P|p)ath=", "$1\\$Path=");
@@ -88,13 +84,10 @@ public abstract class AbstractNewSessionTest
 
                 // The session is not there anymore, but we present an old cookie
                 // The server creates a new session, we must ensure we released all locks
-                exchange = new ContentExchange(true);
-                exchange.setMethod(HttpMethods.GET);
-                exchange.setURL("http://localhost:" + port + contextPath + servletMapping + "?action=old-create");
-                exchange.getRequestFields().add("Cookie", sessionCookie);
-                client.send(exchange);
-                exchange.waitForDone();
-                assertEquals(HttpServletResponse.SC_OK,exchange.getResponseStatus());
+                Request request = client.newRequest("http://localhost:" + port + contextPath + servletMapping + "?action=old-create");
+                request.header("Cookie", sessionCookie);
+                response = request.send();
+                assertEquals(HttpServletResponse.SC_OK,response.getStatus());
             }
             finally
             {
diff --git a/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractOrphanedSessionTest.java b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractOrphanedSessionTest.java
index 6ae9de9..f9de03a 100644
--- a/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractOrphanedSessionTest.java
+++ b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractOrphanedSessionTest.java
@@ -18,8 +18,10 @@
 
 package org.eclipse.jetty.server.session;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
 import java.io.IOException;
-import java.util.Random;
 import java.util.concurrent.TimeUnit;
 
 import javax.servlet.ServletException;
@@ -28,12 +30,10 @@ import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpSession;
 
-import org.eclipse.jetty.client.ContentExchange;
 import org.eclipse.jetty.client.HttpClient;
-import org.eclipse.jetty.http.HttpMethods;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
 import org.junit.Test;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
 
 /**
  * AbstractOrphanedSessionTest
@@ -56,30 +56,25 @@ public abstract class AbstractOrphanedSessionTest
         int inactivePeriod = 5;
         AbstractTestServer server1 = createServer(0, inactivePeriod, -1);
         server1.addContext(contextPath).addServlet(TestServlet.class, servletMapping);
-        server1.start();
-        int port1 = server1.getPort();
         try
         {
+            server1.start();
+            int port1 = server1.getPort();
             int scavengePeriod = 2;
             AbstractTestServer server2 = createServer(0, inactivePeriod, scavengePeriod);
-            server2.addContext(contextPath).addServlet(TestServlet.class, servletMapping);
-            server2.start();
-            int port2 = server2.getPort();
+            server2.addContext(contextPath).addServlet(TestServlet.class, servletMapping);         
             try
             {
+                server2.start();
+                int port2 = server2.getPort();
                 HttpClient client = new HttpClient();
-                client.setConnectorType(HttpClient.CONNECTOR_SOCKET);
                 client.start();
                 try
                 {
                     // Connect to server1 to create a session and get its session cookie
-                    ContentExchange exchange1 = new ContentExchange(true);
-                    exchange1.setMethod(HttpMethods.GET);
-                    exchange1.setURL("http://localhost:" + port1 + contextPath + servletMapping + "?action=init");
-                    client.send(exchange1);
-                    exchange1.waitForDone();
-                    assertEquals(HttpServletResponse.SC_OK,exchange1.getResponseStatus());
-                    String sessionCookie = exchange1.getResponseFields().getStringField("Set-Cookie");
+                    ContentResponse response1 = client.GET("http://localhost:" + port1 + contextPath + servletMapping + "?action=init");
+                    assertEquals(HttpServletResponse.SC_OK,response1.getStatus());
+                    String sessionCookie = response1.getHeaders().getStringField("Set-Cookie");
                     assertTrue(sessionCookie != null);
                     // Mangle the cookie, replacing Path with $Path, etc.
                     sessionCookie = sessionCookie.replaceFirst("(\\W)(P|p)ath=", "$1\\$Path=");
@@ -90,13 +85,10 @@ public abstract class AbstractOrphanedSessionTest
                     Thread.sleep(TimeUnit.SECONDS.toMillis(inactivePeriod + 2L * scavengePeriod));
 
                     // Perform one request to server2 to be sure that the session has been expired
-                    ContentExchange exchange2 = new ContentExchange(true);
-                    exchange2.setMethod(HttpMethods.GET);
-                    exchange2.setURL("http://localhost:" + port2 + contextPath + servletMapping + "?action=check");
-                    exchange2.getRequestFields().add("Cookie", sessionCookie);
-                    client.send(exchange2);
-                    exchange2.waitForDone();
-                    assertEquals(HttpServletResponse.SC_OK,exchange2.getResponseStatus());
+                    Request request = client.newRequest("http://localhost:" + port2 + contextPath + servletMapping + "?action=check");
+                    request.header("Cookie", sessionCookie);
+                    ContentResponse response2 = request.send();
+                    assertEquals(HttpServletResponse.SC_OK,response2.getStatus());
                 }
                 finally
                 {
diff --git a/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractProxySerializationTest.java b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractProxySerializationTest.java
new file mode 100644
index 0000000..d916d3c
--- /dev/null
+++ b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractProxySerializationTest.java
@@ -0,0 +1,132 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.server.session;
+
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.net.URL;
+import java.net.URLClassLoader;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.eclipse.jetty.util.IO;
+import org.junit.Test;
+
+/**
+ * AbstractProxySerializationTest
+ *
+ *
+ */
+public abstract class AbstractProxySerializationTest
+{
+    public abstract AbstractTestServer createServer(int port, int max, int scavenge);
+    
+    public abstract void customizeContext (ServletContextHandler c);
+    
+   
+    
+    /**
+     * @param msec milliseconds to sleep
+     */
+    public void pause(int msec)
+    {
+        try
+        {
+            Thread.sleep(msec);
+        }
+        catch (InterruptedException e)
+        {
+            e.printStackTrace();
+        }
+    }
+    
+    @Test
+    public void testProxySerialization() throws Exception
+    {
+        String contextPath = "";
+        String servletMapping = "/server";
+        int scavengePeriod = 10;
+        AbstractTestServer server = createServer(0, 20, scavengePeriod);
+        ServletContextHandler context = server.addContext(contextPath);
+
+        InputStream is = this.getClass().getClassLoader().getResourceAsStream("proxy-serialization.jar");
+        
+        File testDir = MavenTestingUtils.getTargetTestingDir("proxy-serialization");
+        testDir.mkdirs();
+        
+        File extractedJar = new File (testDir, "proxy-serialization.jar");
+        extractedJar.createNewFile();
+        IO.copy(is, new FileOutputStream(extractedJar));
+ 
+        
+        URLClassLoader loader = new URLClassLoader(new URL[] {extractedJar.toURI().toURL()}, Thread.currentThread().getContextClassLoader());
+        context.setClassLoader(loader);
+        context.addServlet("TestServlet", servletMapping);
+        customizeContext(context);
+        
+        try
+        {
+            server.start();
+            int port=server.getPort();
+            HttpClient client = new HttpClient();
+            client.start();
+            try
+            {
+                ContentResponse response = client.GET("http://localhost:" + port + contextPath + servletMapping + "?action=create");
+                assertEquals(HttpServletResponse.SC_OK,response.getStatus());
+                String sessionCookie = response.getHeaders().getStringField("Set-Cookie");
+                assertTrue(sessionCookie != null);
+                // Mangle the cookie, replacing Path with $Path, etc.
+                sessionCookie = sessionCookie.replaceFirst("(\\W)(P|p)ath=", "$1\\$Path=");
+
+                //stop the context to be sure the sesssion will be passivated
+                context.stop();
+                
+                //restart the context
+                context.start();
+               
+                // Make another request using the session id from before
+                Request request = client.newRequest("http://localhost:" + port + contextPath + servletMapping + "?action=test");
+                request.header("Cookie", sessionCookie);
+                response = request.send();
+                assertEquals(HttpServletResponse.SC_OK,response.getStatus());
+            }
+            finally
+            {
+                client.stop();
+            }
+        }
+        finally
+        {
+            server.stop();
+        }
+
+    }
+}
diff --git a/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractReentrantRequestSessionTest.java b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractReentrantRequestSessionTest.java
index ddaa7fa..6e40b2b 100644
--- a/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractReentrantRequestSessionTest.java
+++ b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractReentrantRequestSessionTest.java
@@ -18,8 +18,10 @@
 
 package org.eclipse.jetty.server.session;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
 import java.io.IOException;
-import java.util.Random;
 
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServlet;
@@ -27,12 +29,9 @@ import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpSession;
 
-import org.eclipse.jetty.client.ContentExchange;
 import org.eclipse.jetty.client.HttpClient;
-import org.eclipse.jetty.http.HttpMethods;
+import org.eclipse.jetty.client.api.ContentResponse;
 import org.junit.Test;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
 
 
 /**
@@ -49,21 +48,17 @@ public abstract class AbstractReentrantRequestSessionTest
         String servletMapping = "/server";
         AbstractTestServer server = createServer(0);
         server.addContext(contextPath).addServlet(TestServlet.class, servletMapping);
-        server.start();
-        int port = server.getPort();
         try
         {
+            server.start();
+            int port = server.getPort();
+
             HttpClient client = new HttpClient();
-            client.setConnectorType(HttpClient.CONNECTOR_SOCKET);
             client.start();
             try
             {
-                ContentExchange exchange = new ContentExchange(true);
-                exchange.setMethod(HttpMethods.GET);
-                exchange.setURL("http://localhost:" + port + contextPath + servletMapping + "?action=reenter&port=" + port + "&path=" + contextPath + servletMapping);
-                client.send(exchange);
-                exchange.waitForDone();
-                assertEquals(HttpServletResponse.SC_OK,exchange.getResponseStatus());
+                ContentResponse response = client.GET("http://localhost:" + port + contextPath + servletMapping + "?action=reenter&port=" + port + "&path=" + contextPath + servletMapping);
+                assertEquals(HttpServletResponse.SC_OK,response.getStatus());
             }
             finally
             {
@@ -92,27 +87,22 @@ public abstract class AbstractReentrantRequestSessionTest
             String action = request.getParameter("action");
             if ("reenter".equals(action))
             {
-                if (session == null) 
+                if (session == null)
                     session = request.getSession(true);
                 int port = Integer.parseInt(request.getParameter("port"));
                 String path = request.getParameter("path");
 
-                // We want to make another request 
+                // We want to make another request
                 // while this request is still pending, to see if the locking is
                 // fine grained (per session at least).
                 try
                 {
                     HttpClient client = new HttpClient();
-                    client.setConnectorType(HttpClient.CONNECTOR_SOCKET);
                     client.start();
                     try
                     {
-                        ContentExchange exchange = new ContentExchange(true);
-                        exchange.setMethod(HttpMethods.GET);
-                        exchange.setURL("http://localhost:" + port + path + ";jsessionid="+session.getId()+"?action=none");
-                        client.send(exchange);
-                        exchange.waitForDone();
-                        assertEquals(HttpServletResponse.SC_OK,exchange.getResponseStatus());
+                        ContentResponse resp = client.GET("http://localhost:" + port + path + ";jsessionid="+session.getId()+"?action=none");
+                        assertEquals(HttpServletResponse.SC_OK,resp.getStatus());
                         assertEquals("true",session.getAttribute("reentrant"));
                     }
                     finally
diff --git a/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractRemoveSessionTest.java b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractRemoveSessionTest.java
index 35ac12b..3cee415 100644
--- a/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractRemoveSessionTest.java
+++ b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractRemoveSessionTest.java
@@ -18,31 +18,32 @@
 
 package org.eclipse.jetty.server.session;
 
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
 
 import java.io.IOException;
-import java.util.EventListener;
 
 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 javax.servlet.http.HttpSessionActivationListener;
 import javax.servlet.http.HttpSessionEvent;
 import javax.servlet.http.HttpSessionListener;
 
-import org.eclipse.jetty.client.ContentExchange;
 import org.eclipse.jetty.client.HttpClient;
-import org.eclipse.jetty.http.HttpMethods;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
 import org.eclipse.jetty.servlet.ServletContextHandler;
 import org.junit.Test;
 
 public abstract class AbstractRemoveSessionTest
 {
     public abstract AbstractTestServer createServer(int port, int max, int scavenge);
-    
-    
+
+
     @Test
     public void testRemoveSession() throws Exception
     {
@@ -54,22 +55,18 @@ public abstract class AbstractRemoveSessionTest
         context.addServlet(TestServlet.class, servletMapping);
         TestEventListener testListener = new TestEventListener();
         context.getSessionHandler().addEventListener(testListener);
-        server.start();
-        int port = server.getPort();
         try
         {
+            server.start();
+            int port = server.getPort();
+
             HttpClient client = new HttpClient();
-            client.setConnectorType(HttpClient.CONNECTOR_SOCKET);
             client.start();
             try
             {
-                ContentExchange exchange = new ContentExchange(true);
-                exchange.setMethod(HttpMethods.GET);
-                exchange.setURL("http://localhost:" + port + contextPath + servletMapping + "?action=create");
-                client.send(exchange);
-                exchange.waitForDone();
-                assertEquals(HttpServletResponse.SC_OK,exchange.getResponseStatus());
-                String sessionCookie = exchange.getResponseFields().getStringField("Set-Cookie");
+                ContentResponse response = client.GET("http://localhost:" + port + contextPath + servletMapping + "?action=create");
+                assertEquals(HttpServletResponse.SC_OK,response.getStatus());
+                String sessionCookie = response.getHeaders().getStringField("Set-Cookie");
                 assertTrue(sessionCookie != null);
                 // Mangle the cookie, replacing Path with $Path, etc.
                 sessionCookie = sessionCookie.replaceFirst("(\\W)(P|p)ath=", "$1\\$Path=");
@@ -77,26 +74,20 @@ public abstract class AbstractRemoveSessionTest
                 assertTrue (testListener.isCreated());
 
                 //now delete the session
-                exchange = new ContentExchange(true);
-                exchange.setMethod(HttpMethods.GET);
-                exchange.setURL("http://localhost:" + port + contextPath + servletMapping + "?action=delete");
-                exchange.getRequestFields().add("Cookie", sessionCookie);
-                client.send(exchange);
-                exchange.waitForDone();
-                assertEquals(HttpServletResponse.SC_OK,exchange.getResponseStatus());
+                Request request = client.newRequest("http://localhost:" + port + contextPath + servletMapping + "?action=delete");
+                request.header("Cookie", sessionCookie);
+                response = request.send();
+                assertEquals(HttpServletResponse.SC_OK,response.getStatus());
                 //ensure sessionDestroyed listener is called
                 assertTrue(testListener.isDestroyed());
-                
-                
+
+
                 // The session is not there anymore, but we present an old cookie
                 // The server creates a new session, we must ensure we released all locks
-                exchange = new ContentExchange(true);
-                exchange.setMethod(HttpMethods.GET);
-                exchange.setURL("http://localhost:" + port + contextPath + servletMapping + "?action=check");
-                exchange.getRequestFields().add("Cookie", sessionCookie);
-                client.send(exchange);
-                exchange.waitForDone();
-                assertEquals(HttpServletResponse.SC_OK,exchange.getResponseStatus());
+                request = client.newRequest("http://localhost:" + port + contextPath + servletMapping + "?action=check");
+                request.header("Cookie", sessionCookie);
+                response = request.send();
+                assertEquals(HttpServletResponse.SC_OK,response.getStatus());
             }
             finally
             {
@@ -133,7 +124,7 @@ public abstract class AbstractRemoveSessionTest
             }
         }
     }
-    
+
     public static class TestEventListener implements HttpSessionListener
     {
         boolean wasCreated;
@@ -161,5 +152,5 @@ public abstract class AbstractRemoveSessionTest
         }
 
     }
-    
+
 }
diff --git a/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractServerCrossContextSessionTest.java b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractServerCrossContextSessionTest.java
index 3cdb8e2..30a080a 100644
--- a/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractServerCrossContextSessionTest.java
+++ b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractServerCrossContextSessionTest.java
@@ -18,9 +18,11 @@
 
 package org.eclipse.jetty.server.session;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
 import java.io.IOException;
 import java.util.Collections;
-import java.util.Random;
 
 import javax.servlet.RequestDispatcher;
 import javax.servlet.ServletContext;
@@ -30,13 +32,10 @@ import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpSession;
 
-import org.eclipse.jetty.client.ContentExchange;
 import org.eclipse.jetty.client.HttpClient;
-import org.eclipse.jetty.http.HttpMethods;
+import org.eclipse.jetty.client.api.ContentResponse;
 import org.eclipse.jetty.servlet.ServletContextHandler;
 import org.junit.Test;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
 
 /**
  * AbstractServerCrossContextSessionTest
@@ -45,7 +44,7 @@ public abstract class AbstractServerCrossContextSessionTest
 {
 
     public abstract AbstractTestServer createServer(int port);
-    
+
     @Test
     public void testCrossContextDispatch() throws Exception
     {
@@ -57,22 +56,18 @@ public abstract class AbstractServerCrossContextSessionTest
         ctxA.addServlet(TestServletA.class, servletMapping);
         ServletContextHandler ctxB = server.addContext(contextB);
         ctxB.addServlet(TestServletB.class, servletMapping);
-        server.start();
-        int port=server.getPort();
         try
         {
+            server.start();
+            int port=server.getPort();
+
             HttpClient client = new HttpClient();
-            client.setConnectorType(HttpClient.CONNECTOR_SOCKET);
             client.start();
             try
             {
                 // Perform a request, on server side a cross context dispatch will be done
-                ContentExchange exchange = new ContentExchange(true);
-                exchange.setMethod(HttpMethods.GET);
-                exchange.setURL("http://localhost:" + port + contextA + servletMapping);
-                client.send(exchange);
-                exchange.waitForDone();
-                assertEquals(HttpServletResponse.SC_OK,exchange.getResponseStatus());
+                ContentResponse response = client.GET("http://localhost:" + port + contextA + servletMapping);
+                assertEquals(HttpServletResponse.SC_OK,response.getStatus());
             }
             finally
             {
diff --git a/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractSessionCookieTest.java b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractSessionCookieTest.java
index b716afd..9557df1 100644
--- a/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractSessionCookieTest.java
+++ b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractSessionCookieTest.java
@@ -18,8 +18,10 @@
 
 package org.eclipse.jetty.server.session;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
 import java.io.IOException;
-import java.util.Random;
 
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServlet;
@@ -29,18 +31,12 @@ import javax.servlet.http.HttpSession;
 
 import junit.framework.Assert;
 
-import org.eclipse.jetty.client.Address;
-import org.eclipse.jetty.client.ContentExchange;
 import org.eclipse.jetty.client.HttpClient;
-import org.eclipse.jetty.client.HttpDestination;
-import org.eclipse.jetty.http.HttpCookie;
-import org.eclipse.jetty.http.HttpMethods;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
 import org.eclipse.jetty.servlet.ServletContextHandler;
-import org.eclipse.jetty.util.log.Log;
 import org.junit.Ignore;
 import org.junit.Test;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
 
 /**
  * AbstractNewSessionTest
@@ -60,7 +56,7 @@ public abstract class AbstractSessionCookieTest
             e.printStackTrace();
         }
     }
-    
+
     @Test
     @Ignore("failing because an http cookie with null value is coming over as \"null\"")
     public void testSessionCookie() throws Exception
@@ -71,48 +67,37 @@ public abstract class AbstractSessionCookieTest
         AbstractTestServer server = createServer(0, 1, scavengePeriod);
         ServletContextHandler context = server.addContext(contextPath);
         context.addServlet(TestServlet.class, servletMapping);
-        server.start();
-        int port=server.getPort();
+
         try
         {
+            server.start();
+            int port=server.getPort();
+            
             HttpClient client = new HttpClient();
-            client.setConnectorType(HttpClient.CONNECTOR_SOCKET);
             client.start();
             try
             {
-                ContentExchange exchange = new ContentExchange(true);
-                exchange.setMethod(HttpMethods.GET);
-                exchange.setURL("http://localhost:" + port + contextPath + servletMapping + "?action=create");
-                client.send(exchange);
-                exchange.waitForDone();
-                assertEquals(HttpServletResponse.SC_OK,exchange.getResponseStatus());
-                String sessionCookie = exchange.getResponseFields().getStringField("Set-Cookie");
+
+                ContentResponse response = client.GET("http://localhost:" + port + contextPath + servletMapping + "?action=create");
+                assertEquals(HttpServletResponse.SC_OK,response.getStatus());
+
+                String sessionCookie = response.getHeaders().getStringField("Set-Cookie");
                 assertTrue(sessionCookie != null);
                 // Mangle the cookie, replacing Path with $Path, etc.
                 //sessionCookie = sessionCookie.replaceFirst("(\\W)(P|p)ath=", "$1\\$Path=");
 
                 // Let's wait for the scavenger to run, waiting 2.5 times the scavenger period
                 //pause(scavengePeriod);
+                Request request = client.newRequest("http://localhost:" + port + contextPath + servletMapping + "?action=check-cookie");
+                request.header("Cookie", sessionCookie);
+                response = request.send();
+
+                assertEquals(HttpServletResponse.SC_OK,response.getStatus());
 
-                exchange = new ContentExchange(true);
-                exchange.setMethod(HttpMethods.GET);
-                exchange.setURL("http://localhost:" + port + contextPath + servletMapping + "?action=check-cookie");
-                exchange.getRequestFields().add("Cookie", sessionCookie);
-                client.send(exchange);
-                exchange.waitForDone();
-                assertEquals(HttpServletResponse.SC_OK,exchange.getResponseStatus());
-                
-                exchange = new ContentExchange(true);
-                exchange.setMethod(HttpMethods.GET);
-                exchange.setURL("http://localhost:" + port + contextPath + servletMapping + "?action=null-cookie");
-                //exchange.getRequestFields().add("Cookie", "null");
-                HttpDestination dest = client.getDestination(new Address("localhost",port),false);
-                
-                dest.addCookie(new HttpCookie("Cookie",null));
-                
-                client.send(exchange);
-                exchange.waitForDone();
-                assertEquals(HttpServletResponse.SC_OK,exchange.getResponseStatus());
+                request = client.newRequest("http://localhost:" + port + contextPath + servletMapping + "?action=null-cookie");
+                request.header("Cookie", sessionCookie);
+                response = request.send();
+                assertEquals(HttpServletResponse.SC_OK,response.getStatus());
             }
             finally
             {
@@ -139,21 +124,21 @@ public abstract class AbstractSessionCookieTest
             else if ("check-cookie".equals(action))
             {
                 HttpSession session = request.getSession(false);
-                                
+
                 assertTrue(session != null);
-                
+
                 //request.getSession(true);
             }
             else if ("null-cookie".equals(action))
             {
                 HttpSession session = request.getSession(false);
-                
+
                 assertEquals(1, request.getCookies().length);
-                
+
                 Assert.assertFalse("null".equals(request.getCookies()[0].getValue()));
-                
+
                 assertTrue(session == null);
-                
+
             }
             else
             {
diff --git a/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractSessionExpiryTest.java b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractSessionExpiryTest.java
index 178ca76..4e3b53c 100644
--- a/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractSessionExpiryTest.java
+++ b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractSessionExpiryTest.java
@@ -18,6 +18,9 @@
 
 package org.eclipse.jetty.server.session;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
 import java.io.IOException;
 
 import javax.servlet.ServletException;
@@ -26,25 +29,12 @@ import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpSession;
 
-import org.eclipse.jetty.client.ContentExchange;
 import org.eclipse.jetty.client.HttpClient;
-import org.eclipse.jetty.http.HttpMethods;
-import org.eclipse.jetty.server.Request;
-import org.eclipse.jetty.server.SessionManager;
-import org.eclipse.jetty.server.session.AbstractTestServer;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
 import org.eclipse.jetty.servlet.ServletHolder;
 import org.junit.Test;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
 
-
-
-/**
- * AbstractSessionExpiryTest
- *
- *
- *
- */
 public abstract class AbstractSessionExpiryTest
 {
     public abstract AbstractTestServer createServer(int port, int max, int scavenge);
@@ -60,7 +50,7 @@ public abstract class AbstractSessionExpiryTest
             e.printStackTrace();
         }
     }
-    
+
     @Test
     public void testSessionNotExpired() throws Exception
     {
@@ -72,48 +62,42 @@ public abstract class AbstractSessionExpiryTest
         TestServlet servlet = new TestServlet();
         ServletHolder holder = new ServletHolder(servlet);
         server1.addContext(contextPath).addServlet(holder, servletMapping);
-        server1.start();
-        int port1 = server1.getPort();
 
+        HttpClient client = new HttpClient();
         try
         {
-            HttpClient client = new HttpClient();
-            client.setConnectorType(HttpClient.CONNECTOR_SOCKET);
+            server1.start();
+            int port1 = server1.getPort();
+
             client.start();
             String url = "http://localhost:" + port1 + contextPath + servletMapping;
 
             //make a request to set up a session on the server
-            ContentExchange exchange1 = new ContentExchange(true);
-            exchange1.setMethod(HttpMethods.GET);
-            exchange1.setURL(url + "?action=init");
-            client.send(exchange1);
-            exchange1.waitForDone();
-            assertEquals(HttpServletResponse.SC_OK,exchange1.getResponseStatus());
-            String sessionCookie = exchange1.getResponseFields().getStringField("Set-Cookie");
+            ContentResponse response = client.GET(url + "?action=init");
+            assertEquals(HttpServletResponse.SC_OK,response.getStatus());
+            String sessionCookie = response.getHeaders().getStringField("Set-Cookie");
             assertTrue(sessionCookie != null);
             // Mangle the cookie, replacing Path with $Path, etc.
             sessionCookie = sessionCookie.replaceFirst("(\\W)(P|p)ath=", "$1\\$Path=");
-            
+
             //now stop the server
             server1.stop();
-            
+
             //start the server again, before the session times out
-            server1.start();            
+            server1.start();
             port1 = server1.getPort();
             url = "http://localhost:" + port1 + contextPath + servletMapping;
-            
+
             //make another request, the session should not have expired
-            ContentExchange exchange2 = new ContentExchange(true);
-            exchange2.setMethod(HttpMethods.GET);
-            exchange2.setURL(url + "?action=notexpired");
-            exchange2.getRequestFields().add("Cookie", sessionCookie);
-            client.send(exchange2);
-            exchange2.waitForDone();
-            assertEquals(HttpServletResponse.SC_OK,exchange2.getResponseStatus());
+            Request request = client.newRequest(url + "?action=notexpired");
+            request.getHeaders().add("Cookie", sessionCookie);
+            ContentResponse response2 = request.send();
+            assertEquals(HttpServletResponse.SC_OK,response2.getStatus());
 
         }
         finally
         {
+            client.stop();
             server1.stop();
         }
     }
@@ -135,41 +119,33 @@ public abstract class AbstractSessionExpiryTest
         try
         {
             HttpClient client = new HttpClient();
-            client.setConnectorType(HttpClient.CONNECTOR_SOCKET);
             client.start();
             String url = "http://localhost:" + port1 + contextPath + servletMapping;
 
             //make a request to set up a session on the server
-            ContentExchange exchange1 = new ContentExchange(true);
-            exchange1.setMethod(HttpMethods.GET);
-            exchange1.setURL(url + "?action=init");
-            client.send(exchange1);
-            exchange1.waitForDone();
-            assertEquals(HttpServletResponse.SC_OK,exchange1.getResponseStatus());
-            String sessionCookie = exchange1.getResponseFields().getStringField("Set-Cookie");
+            ContentResponse response1 = client.GET(url + "?action=init");
+            assertEquals(HttpServletResponse.SC_OK,response1.getStatus());
+            String sessionCookie = response1.getHeaders().getStringField("Set-Cookie");
             assertTrue(sessionCookie != null);
             // Mangle the cookie, replacing Path with $Path, etc.
             sessionCookie = sessionCookie.replaceFirst("(\\W)(P|p)ath=", "$1\\$Path=");
-            
+
             //now stop the server
             server1.stop();
-            
+
             //and wait until the expiry time has passed
             pause(inactivePeriod);
-            
+
             //restart the server
-            server1.start();            
+            server1.start();
             port1 = server1.getPort();
             url = "http://localhost:" + port1 + contextPath + servletMapping;
-            
+
             //make another request, the session should have expired
-            ContentExchange exchange2 = new ContentExchange(true);
-            exchange2.setMethod(HttpMethods.GET);
-            exchange2.setURL(url + "?action=test");
-            exchange2.getRequestFields().add("Cookie", sessionCookie);
-            client.send(exchange2);
-            exchange2.waitForDone();
-            assertEquals(HttpServletResponse.SC_OK,exchange2.getResponseStatus());
+            Request request = client.newRequest(url + "?action=test");
+            request.getHeaders().add("Cookie", sessionCookie);
+            ContentResponse response2 = request.send();
+            assertEquals(HttpServletResponse.SC_OK,response2.getStatus());
         }
         finally
         {
@@ -180,8 +156,6 @@ public abstract class AbstractSessionExpiryTest
     public static class TestServlet extends HttpServlet
     {
         public String originalId = null;
-        public String testId = null;
-        public String checkId = null;
 
         @Override
         protected void doGet(HttpServletRequest request, HttpServletResponse httpServletResponse) throws ServletException, IOException
@@ -205,7 +179,7 @@ public abstract class AbstractSessionExpiryTest
                 assertTrue(session != null);
                 assertTrue(originalId.equals(session.getId()));
             }
-           
+
         }
     }
 }
diff --git a/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractSessionInvalidateAndCreateTest.java b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractSessionInvalidateAndCreateTest.java
index 5d07c93..b2eef37 100644
--- a/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractSessionInvalidateAndCreateTest.java
+++ b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractSessionInvalidateAndCreateTest.java
@@ -18,10 +18,14 @@
 
 package org.eclipse.jetty.server.session;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
 import java.io.IOException;
+import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Random;
 
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServlet;
@@ -33,22 +37,19 @@ import javax.servlet.http.HttpSessionBindingListener;
 import javax.servlet.http.HttpSessionEvent;
 import javax.servlet.http.HttpSessionListener;
 
-import org.eclipse.jetty.client.ContentExchange;
 import org.eclipse.jetty.client.HttpClient;
-import org.eclipse.jetty.http.HttpMethods;
-import org.eclipse.jetty.server.Request;
-import org.eclipse.jetty.server.SessionManager;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
 import org.eclipse.jetty.servlet.ServletContextHandler;
 import org.eclipse.jetty.servlet.ServletHolder;
+import org.junit.Assert;
 import org.junit.Test;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
 
 /**
  * AbstractSessionInvalidateAndCreateTest
- * 
- * This test verifies that invalidating an existing session and creating 
- * a new session within the scope of a single request will expire the 
+ *
+ * This test verifies that invalidating an existing session and creating
+ * a new session within the scope of a single request will expire the
  * newly created session correctly (removed from the server and session listeners called).
  * See https://bugs.eclipse.org/bugs/show_bug.cgi?id=377610
  */
@@ -57,37 +58,37 @@ public abstract class AbstractSessionInvalidateAndCreateTest
     public class MySessionListener implements HttpSessionListener
     {
         List<String> destroys;
-        
+
         public void sessionCreated(HttpSessionEvent e)
         {
-            
+
         }
 
         public void sessionDestroyed(HttpSessionEvent e)
         {
             if (destroys == null)
-                destroys = new ArrayList<String>();
-            
+                destroys = new ArrayList<>();
+
             destroys.add((String)e.getSession().getAttribute("identity"));
         }
     }
-    
+
     public abstract AbstractTestServer createServer(int port, int max, int scavenge);
-    
-    
+
+
 
     public void pause(int scavengePeriod)
     {
         try
         {
-            Thread.sleep(scavengePeriod * 2500L);
+            Thread.sleep(scavengePeriod * 3000L);
         }
         catch (InterruptedException e)
         {
             e.printStackTrace();
         }
     }
-    
+
     @Test
     public void testSessionScavenge() throws Exception
     {
@@ -102,12 +103,13 @@ public abstract class AbstractSessionInvalidateAndCreateTest
         context.addServlet(holder, servletMapping);
         MySessionListener listener = new MySessionListener();
         context.getSessionHandler().addEventListener(listener);
-        server.start();
-        int port1 = server.getPort();
+    
         try
         {
+            server.start();
+            int port1 = server.getPort();
+            
             HttpClient client = new HttpClient();
-            client.setConnectorType(HttpClient.CONNECTOR_SOCKET);
             client.start();
             try
             {
@@ -115,28 +117,21 @@ public abstract class AbstractSessionInvalidateAndCreateTest
 
 
                 // Create the session
-                ContentExchange exchange1 = new ContentExchange(true);
-                exchange1.setMethod(HttpMethods.GET);
-                exchange1.setURL(url + "?action=init");
-                client.send(exchange1);
-                exchange1.waitForDone();
-                assertEquals(HttpServletResponse.SC_OK,exchange1.getResponseStatus());
-                String sessionCookie = exchange1.getResponseFields().getStringField("Set-Cookie");
+                ContentResponse response1 = client.GET(url + "?action=init");
+                assertEquals(HttpServletResponse.SC_OK,response1.getStatus());
+                String sessionCookie = response1.getHeaders().getStringField("Set-Cookie");
                 assertTrue(sessionCookie != null);
                 // Mangle the cookie, replacing Path with $Path, etc.
                 sessionCookie = sessionCookie.replaceFirst("(\\W)(P|p)ath=", "$1\\$Path=");
 
 
                 // Make a request which will invalidate the existing session and create a new one
-                ContentExchange exchange2 = new ContentExchange(true);
-                exchange2.setMethod(HttpMethods.GET);
-                exchange2.setURL(url + "?action=test");
-                exchange2.getRequestFields().add("Cookie", sessionCookie);
-                client.send(exchange2);
-                exchange2.waitForDone();
-                assertEquals(HttpServletResponse.SC_OK,exchange2.getResponseStatus());
-
-                // Wait for the scavenger to run, waiting 2.5 times the scavenger period
+                Request request2 = client.newRequest(url + "?action=test");
+                request2.header("Cookie", sessionCookie);
+                ContentResponse response2 = request2.send();
+                assertEquals(HttpServletResponse.SC_OK,response2.getStatus());
+
+                // Wait for the scavenger to run, waiting 3 times the scavenger period
                 pause(scavengePeriod);
 
                 //test that the session created in the last test is scavenged:
@@ -160,6 +155,20 @@ public abstract class AbstractSessionInvalidateAndCreateTest
     public static class TestServlet extends HttpServlet
     {
         private boolean unbound = false;
+        
+        public class MySessionBindingListener implements HttpSessionBindingListener, Serializable
+        {
+
+            public void valueUnbound(HttpSessionBindingEvent event)
+            {
+                unbound = true;
+            }
+
+            public void valueBound(HttpSessionBindingEvent event)
+            {
+
+            }
+        }
 
         @Override
         protected void doGet(HttpServletRequest request, HttpServletResponse httpServletResponse) throws ServletException, IOException
@@ -175,25 +184,16 @@ public abstract class AbstractSessionInvalidateAndCreateTest
                 HttpSession session = request.getSession(false);
                 if (session != null)
                 {
+                    //invalidate existing session
                     session.invalidate();
-                    
+
                     //now make a new session
                     session = request.getSession(true);
                     session.setAttribute("identity", "session2");
-                    session.setAttribute("listener", new HttpSessionBindingListener()
-                    {
-                        
-                        public void valueUnbound(HttpSessionBindingEvent event)
-                        {
-                            unbound = true;
-                        }
-                        
-                        public void valueBound(HttpSessionBindingEvent event)
-                        {
-                            
-                        }
-                    });
+                    session.setAttribute("listener", new MySessionBindingListener());
                 }
+                else
+                    fail("Session already missing");
             }
         }
     }
diff --git a/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractSessionMigrationTest.java b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractSessionMigrationTest.java
index 956db43..0fcbfa6 100644
--- a/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractSessionMigrationTest.java
+++ b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractSessionMigrationTest.java
@@ -18,9 +18,11 @@
 
 package org.eclipse.jetty.server.session;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
 import java.io.IOException;
 import java.io.PrintWriter;
-import java.util.Random;
 
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServlet;
@@ -28,12 +30,10 @@ import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpSession;
 
-import org.eclipse.jetty.client.ContentExchange;
 import org.eclipse.jetty.client.HttpClient;
-import org.eclipse.jetty.http.HttpMethods;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
 import org.junit.Test;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
 
 /**
  * AbstractSessionMigrationTest
@@ -49,44 +49,41 @@ public abstract class AbstractSessionMigrationTest
         String servletMapping = "/server";
         AbstractTestServer server1 = createServer(0);
         server1.addContext(contextPath).addServlet(TestServlet.class, servletMapping);
-        server1.start();
-        int port1=server1.getPort();
+
         try
         {
+            server1.start();
+            int port1=server1.getPort();
+            
             AbstractTestServer server2 = createServer(0);
             server2.addContext(contextPath).addServlet(TestServlet.class, servletMapping);
-            server2.start();
-            int port2=server2.getPort();
+
             try
             {
+                server2.start();
+                int port2=server2.getPort();
+                
                 HttpClient client = new HttpClient();
-                client.setConnectorType(HttpClient.CONNECTOR_SOCKET);
                 client.start();
                 try
                 {
                     // Perform one request to server1 to create a session
                     int value = 1;
-                    ContentExchange exchange1 = new ContentExchange(true);
-                    exchange1.setMethod(HttpMethods.POST);
-                    exchange1.setURL("http://localhost:" + port1 + contextPath + servletMapping + "?action=set&value=" + value);
-                    client.send(exchange1);
-                    exchange1.waitForDone();
-                    assertEquals(HttpServletResponse.SC_OK,exchange1.getResponseStatus());
-                    String sessionCookie = exchange1.getResponseFields().getStringField("Set-Cookie");
+                    Request request1 = client.POST("http://localhost:" + port1 + contextPath + servletMapping + "?action=set&value=" + value);
+                    ContentResponse response1 = request1.send();
+                    assertEquals(HttpServletResponse.SC_OK,response1.getStatus());
+                    String sessionCookie = response1.getHeaders().getStringField("Set-Cookie");
                     assertTrue(sessionCookie != null);
                     // Mangle the cookie, replacing Path with $Path, etc.
                     sessionCookie = sessionCookie.replaceFirst("(\\W)(P|p)ath=", "$1\\$Path=");
 
                     // Perform a request to server2 using the session cookie from the previous request
                     // This should migrate the session from server1 to server2.
-                    ContentExchange exchange2 = new ContentExchange(true);
-                    exchange2.setMethod(HttpMethods.GET);
-                    exchange2.setURL("http://localhost:" + port2 + contextPath + servletMapping + "?action=get");
-                    exchange2.getRequestFields().add("Cookie", sessionCookie);
-                    client.send(exchange2);
-                    exchange2.waitForDone();
-                    assertEquals(HttpServletResponse.SC_OK,exchange2.getResponseStatus());
-                    String response = exchange2.getResponseContent();
+                    Request request2 = client.newRequest("http://localhost:" + port2 + contextPath + servletMapping + "?action=get");
+                    request2.header("Cookie", sessionCookie);
+                    ContentResponse response2 = request2.send();
+                    assertEquals(HttpServletResponse.SC_OK,response2.getStatus());
+                    String response = response2.getContentAsString();
                     assertEquals(response.trim(),String.valueOf(value));               }
                 finally
                 {
@@ -130,7 +127,7 @@ public abstract class AbstractSessionMigrationTest
             else if ("get".equals(action))
             {
                 int value = (Integer)session.getAttribute("value");
-                int x = ((AbstractSession)session).getMaxInactiveInterval();
+                int x = session.getMaxInactiveInterval();
                 assertTrue(x > 0);
                 PrintWriter writer = response.getWriter();
                 writer.println(value);
diff --git a/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractSessionRenewTest.java b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractSessionRenewTest.java
new file mode 100644
index 0000000..8cf300b
--- /dev/null
+++ b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractSessionRenewTest.java
@@ -0,0 +1,162 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.session;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+
+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 javax.servlet.http.HttpSessionEvent;
+import javax.servlet.http.HttpSessionIdListener;
+
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.webapp.WebAppContext;
+
+
+public abstract class AbstractSessionRenewTest
+{
+    public abstract AbstractTestServer createServer(int port, int max, int scavenge);
+
+    public void testSessionRenewal() throws Exception
+    {
+        String contextPath = "";
+        String servletMapping = "/server";
+        int scavengePeriod = 3;
+        AbstractTestServer server = createServer(0, 1, scavengePeriod);
+        WebAppContext context = server.addWebAppContext(".", contextPath);
+        context.addServlet(TestServlet.class, servletMapping);
+        TestHttpSessionIdListener testListener = new TestHttpSessionIdListener();
+        context.addEventListener(testListener);
+        
+
+
+        HttpClient client = new HttpClient();
+        try
+        {
+            server.start();
+            int port=server.getPort();
+            
+            client.start();
+
+            //make a request to create a session
+            ContentResponse response = client.GET("http://localhost:" + port + contextPath + servletMapping + "?action=create");
+            assertEquals(HttpServletResponse.SC_OK,response.getStatus());
+
+            String sessionCookie = response.getHeaders().getStringField("Set-Cookie");
+            assertTrue(sessionCookie != null);
+            assertFalse(testListener.isCalled());
+
+            //make a request to change the sessionid
+            Request request = client.newRequest("http://localhost:" + port + contextPath + servletMapping + "?action=renew");
+            request.header("Cookie", sessionCookie);
+            ContentResponse renewResponse = request.send();
+            assertEquals(HttpServletResponse.SC_OK,renewResponse.getStatus());
+            String renewSessionCookie = renewResponse.getHeaders().getStringField("Set-Cookie");
+            assertNotNull(renewSessionCookie);
+            assertNotSame(sessionCookie, renewSessionCookie);
+            assertTrue(testListener.isCalled());
+        }
+        finally
+        {
+            client.stop();
+            server.stop();
+        }
+    }
+
+    
+    
+    public static class TestHttpSessionIdListener implements HttpSessionIdListener
+    {
+        boolean called = false;
+        
+        @Override
+        public void sessionIdChanged(HttpSessionEvent event, String oldSessionId)
+        {
+            assertNotNull(event.getSession());
+            assertNotSame(oldSessionId, event.getSession().getId());
+            called = true;
+        }
+        
+        public boolean isCalled()
+        {
+            return called;
+        }
+    }
+
+
+    public static class TestServlet extends HttpServlet
+    {
+        
+        @Override
+        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+        {
+            String action = request.getParameter("action");
+            if ("create".equals(action))
+            {
+                HttpSession session = request.getSession(true);
+                assertTrue(session.isNew());
+            }
+            else if ("renew".equals(action))
+            {
+                HttpSession beforeSession = request.getSession(false);
+                assertTrue(beforeSession != null);
+                String beforeSessionId = beforeSession.getId();
+
+
+                ((AbstractSession)beforeSession).renewId(request);
+
+                HttpSession afterSession = request.getSession(false);
+                assertTrue(afterSession != null);
+                String afterSessionId = afterSession.getId();
+
+                assertTrue(beforeSession==afterSession);
+                assertFalse(beforeSessionId.equals(afterSessionId));
+
+                AbstractSessionManager sessionManager = (AbstractSessionManager)((AbstractSession)afterSession).getSessionManager();
+                AbstractSessionIdManager sessionIdManager = (AbstractSessionIdManager)sessionManager.getSessionIdManager();
+
+                assertTrue(sessionIdManager.idInUse(afterSessionId));
+                assertFalse(sessionIdManager.idInUse(beforeSessionId));
+
+                HttpSession session = sessionManager.getSession(afterSessionId);
+                assertNotNull(session);
+                session = sessionManager.getSession(beforeSessionId);
+                assertNull(session);
+
+                if (((AbstractSession)afterSession).isIdChanged())
+                {
+                    ((org.eclipse.jetty.server.Response)response).addCookie(sessionManager.getSessionCookie(afterSession, request.getContextPath(), request.isSecure()));
+                }
+            }
+        }
+    }
+
+}
diff --git a/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractSessionValueSavingTest.java b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractSessionValueSavingTest.java
index f9142c5..92b1891 100644
--- a/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractSessionValueSavingTest.java
+++ b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractSessionValueSavingTest.java
@@ -18,9 +18,11 @@
 
 package org.eclipse.jetty.server.session;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
 import java.io.IOException;
 import java.io.PrintWriter;
-import java.util.Random;
 
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServlet;
@@ -28,12 +30,10 @@ import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpSession;
 
-import org.eclipse.jetty.client.ContentExchange;
 import org.eclipse.jetty.client.HttpClient;
-import org.eclipse.jetty.http.HttpMethods;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
 import org.junit.Test;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
 
 
 /**
@@ -42,7 +42,7 @@ import static org.junit.Assert.assertTrue;
 public abstract class AbstractSessionValueSavingTest
 {
     public abstract AbstractTestServer createServer(int port, int max, int scavenge);
-    
+
     @Test
     public void testSessionValueSaving() throws Exception
     {
@@ -52,32 +52,27 @@ public abstract class AbstractSessionValueSavingTest
         int scavengePeriod = 20000;
         AbstractTestServer server1 = createServer(0, maxInactivePeriod, scavengePeriod);
         server1.addContext(contextPath).addServlet(TestServlet.class, servletMapping);
-        server1.start();
-        int port1=server1.getPort();
+ 
         try
         {
+            server1.start();
+            int port1=server1.getPort();
             
                 HttpClient client = new HttpClient();
-                client.setConnectorType(HttpClient.CONNECTOR_SOCKET);
                 client.start();
                 try
                 {
                     long sessionTestValue = 0;
 
                     // Perform one request to server1 to create a session
-                    ContentExchange exchange1 = new ContentExchange(true);
-                    exchange1.setMethod(HttpMethods.GET);
-                    exchange1.setURL("http://localhost:" + port1 + contextPath + servletMapping + "?action=init");
-                    client.send(exchange1);
-                    exchange1.waitForDone();
-                    assertEquals(HttpServletResponse.SC_OK, exchange1.getResponseStatus());
-                    
-                    System.out.println("Checking: " + sessionTestValue + " vs " + exchange1.getResponseContent());
-                    assertTrue(sessionTestValue < Long.parseLong(exchange1.getResponseContent()));
-                   
-                    sessionTestValue = Long.parseLong(exchange1.getResponseContent());
-                    
-                    String sessionCookie = exchange1.getResponseFields().getStringField("Set-Cookie");
+                    ContentResponse response1 = client.GET("http://localhost:" + port1 + contextPath + servletMapping + "?action=init");
+
+                    assertEquals(HttpServletResponse.SC_OK, response1.getStatus());
+                    assertTrue(sessionTestValue < Long.parseLong(response1.getContentAsString()));
+
+                    sessionTestValue = Long.parseLong(response1.getContentAsString());
+
+                    String sessionCookie = response1.getHeaders().getStringField("Set-Cookie");
                     assertTrue( sessionCookie != null );
                     // Mangle the cookie, replacing Path with $Path, etc.
                     sessionCookie = sessionCookie.replaceFirst("(\\W)(P|p)ath=", "$1\\$Path=");
@@ -88,27 +83,22 @@ public abstract class AbstractSessionValueSavingTest
                     // We want to test that optimizations done to the saving of the shared lastAccessTime
                     // do not break the correct working
                     int requestInterval = 500;
-                    
-                    
+
+
                     for (int i = 0; i < 10; ++i)
                     {
-                        ContentExchange exchange2 = new ContentExchange(true);
-                        exchange2.setMethod(HttpMethods.GET);
-                        exchange2.setURL("http://localhost:" + port1 + contextPath + servletMapping);
-                        exchange2.getRequestFields().add("Cookie", sessionCookie);
-                        client.send(exchange2);
-                        exchange2.waitForDone();
-                        assertEquals(HttpServletResponse.SC_OK , exchange2.getResponseStatus());
-                        
-                        System.out.println("Checking: " + sessionTestValue + " vs " + exchange2.getResponseContent());
-                        assertTrue(sessionTestValue < Long.parseLong(exchange2.getResponseContent()));
-                        
-                        sessionTestValue = Long.parseLong(exchange2.getResponseContent());
-                        
-                        String setCookie = exchange1.getResponseFields().getStringField("Set-Cookie");
-                        if (setCookie!=null)                    
+                        Request request2 = client.newRequest("http://localhost:" + port1 + contextPath + servletMapping);
+                        request2.header("Cookie", sessionCookie);
+                        ContentResponse response2 = request2.send();
+
+                        assertEquals(HttpServletResponse.SC_OK , response2.getStatus());
+                        assertTrue(sessionTestValue < Long.parseLong(response2.getContentAsString()));
+                        sessionTestValue = Long.parseLong(response2.getContentAsString());
+
+                        String setCookie = response1.getHeaders().getStringField("Set-Cookie");
+                        if (setCookie!=null)
                             sessionCookie = setCookie.replaceFirst("(\\W)(P|p)ath=", "$1\\$Path=");
-                        
+
                         Thread.sleep(requestInterval);
                     }
 
@@ -134,7 +124,7 @@ public abstract class AbstractSessionValueSavingTest
             {
                 HttpSession session = request.getSession(true);
                 session.setAttribute("test", System.currentTimeMillis());
-                
+
                 sendResult(session, httpServletResponse.getWriter());
             }
             else
@@ -143,30 +133,30 @@ public abstract class AbstractSessionValueSavingTest
                 System.out.println("not init call " + session);
                 if (session!=null)
                 {
-                	long value = System.currentTimeMillis();
-                	System.out.println("Setting test to : " + value);
+                        long value = System.currentTimeMillis();
+                        System.out.println("Setting test to : " + value);
                     session.setAttribute("test", value);
-                    
+
                 }
-                
+
                 sendResult(session, httpServletResponse.getWriter());
 
             }
-            
-            
+
+
         }
-        
+
         private void sendResult(HttpSession session, PrintWriter writer)
         {
-        	if (session != null)
-        	{
-        		writer.print(session.getAttribute("test"));
-        	}
-        	else
-        	{
-        		writer.print(0);
-        	}
+                if (session != null)
+                {
+                        writer.print(session.getAttribute("test"));
+                }
+                else
+                {
+                        writer.print(0);
+                }
         }
-        
+
     }
 }
diff --git a/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractStopSessionManagerPreserveSessionTest.java b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractStopSessionManagerPreserveSessionTest.java
new file mode 100644
index 0000000..de4c44d
--- /dev/null
+++ b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractStopSessionManagerPreserveSessionTest.java
@@ -0,0 +1,116 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.session;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+
+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 org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.junit.Test;
+
+public abstract class AbstractStopSessionManagerPreserveSessionTest
+{
+    public String _id;
+    
+    
+    public abstract void checkSessionPersisted (boolean expected);
+    
+    public abstract AbstractTestServer createServer (int port);
+    
+    public abstract void configureSessionManagement(ServletContextHandler context);
+    
+    @Test
+    public void testStopSessionManagerPreserveSession() throws Exception
+    {
+        String contextPath = "";
+        String servletMapping = "/server";
+        
+        AbstractTestServer server = createServer(0);
+        ServletContextHandler context = server.addContext(contextPath);
+        ServletHolder holder = new ServletHolder();
+        TestServlet servlet = new TestServlet();
+        holder.setServlet(servlet);
+        
+        context.addServlet(holder, servletMapping);
+     
+        configureSessionManagement(context);
+        
+        server.start();
+        int port=server.getPort();
+        try
+        {
+            HttpClient client = new HttpClient();
+            client.start();
+            try
+            {
+                //Create a session
+                ContentResponse response = client.GET("http://localhost:" + port + contextPath + servletMapping + "?action=create");
+                assertEquals(HttpServletResponse.SC_OK,response.getStatus());
+                String sessionCookie = response.getHeaders().getStringField("Set-Cookie");
+                assertTrue(sessionCookie != null);
+                // Mangle the cookie, replacing Path with $Path, etc.
+                sessionCookie = sessionCookie.replaceFirst("(\\W)(P|p)ath=", "$1\\$Path=");
+
+                //stop the session manager
+                context.getSessionHandler().getSessionManager().stop();
+                
+                //check the database to see that the session is still valid
+                checkSessionPersisted(true);
+                
+            }
+            finally
+            {
+                client.stop();
+            }
+        }
+        finally
+        {
+            server.stop();
+        }
+    }
+    
+    public class TestServlet extends HttpServlet
+    {
+        
+        @Override
+        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+        {
+            String action = request.getParameter("action");
+            if ("create".equals(action))
+            {
+                HttpSession session = request.getSession(true);
+                session.setAttribute("foo", "bar");
+                assertTrue(session.isNew());
+                _id = session.getId();
+            }
+        }
+    }
+}
diff --git a/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractTestServer.java b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractTestServer.java
index 27d8a5e..329d6b8 100644
--- a/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractTestServer.java
+++ b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractTestServer.java
@@ -18,6 +18,7 @@
 
 package org.eclipse.jetty.server.session;
 
+import org.eclipse.jetty.server.NetworkConnector;
 import org.eclipse.jetty.server.Server;
 import org.eclipse.jetty.server.SessionIdManager;
 import org.eclipse.jetty.server.SessionManager;
@@ -77,7 +78,7 @@ public abstract class AbstractTestServer
     
     public int getPort()
     {
-        return _server.getConnectors()[0].getLocalPort();
+        return ((NetworkConnector)getServer().getConnectors()[0]).getLocalPort();
     }
 
     public ServletContextHandler addContext(String contextPath)
diff --git a/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractWebAppObjectInSessionTest.java b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractWebAppObjectInSessionTest.java
index 460d750..bc67781 100644
--- a/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractWebAppObjectInSessionTest.java
+++ b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractWebAppObjectInSessionTest.java
@@ -18,22 +18,23 @@
 
 package org.eclipse.jetty.server.session;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.FileWriter;
-import java.util.Random;
+
 import javax.servlet.http.HttpServletResponse;
 
-import org.eclipse.jetty.client.ContentExchange;
 import org.eclipse.jetty.client.HttpClient;
-import org.eclipse.jetty.http.HttpMethods;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.http.HttpMethod;
 import org.eclipse.jetty.util.IO;
 import org.eclipse.jetty.util.resource.Resource;
 import org.junit.Test;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
 /**
  * AbstractWebAppObjectInSessionTest
  *
@@ -95,42 +96,42 @@ public abstract class AbstractWebAppObjectInSessionTest
 
         AbstractTestServer server1 = createServer(0);
         server1.addWebAppContext(warDir.getCanonicalPath(), contextPath).addServlet(WebAppObjectInSessionServlet.class.getName(), servletMapping);
-        server1.start();
-        int port1 = server1.getPort();
+
         try
         {
+            server1.start();
+            int port1 = server1.getPort();
+            
             AbstractTestServer server2 = createServer(0);
             server2.addWebAppContext(warDir.getCanonicalPath(), contextPath).addServlet(WebAppObjectInSessionServlet.class.getName(), servletMapping);
-            server2.start();
-            int port2 = server2.getPort();
+
             try
             {
+                server2.start();
+                int port2 = server2.getPort();
+                
                 HttpClient client = new HttpClient();
-                client.setConnectorType(HttpClient.CONNECTOR_SOCKET);
                 client.start();
                 try
                 {
                     // Perform one request to server1 to create a session
-                    ContentExchange exchange1 = new ContentExchange(true);
-                    exchange1.setMethod(HttpMethods.GET);
-                    exchange1.setURL("http://localhost:" + port1 + contextPath + servletMapping + "?action=set");
-                    client.send(exchange1);
-                    exchange1.waitForDone();
-                    assertEquals( HttpServletResponse.SC_OK, exchange1.getResponseStatus());
-                    String sessionCookie = exchange1.getResponseFields().getStringField("Set-Cookie");
+                    Request request = client.newRequest("http://localhost:" + port1 + contextPath + servletMapping + "?action=set");
+                    request.method(HttpMethod.GET);
+
+                    ContentResponse response = request.send();
+                    assertEquals( HttpServletResponse.SC_OK, response.getStatus());
+                    String sessionCookie = response.getHeaders().getStringField("Set-Cookie");
                     assertTrue(sessionCookie != null);
                     // Mangle the cookie, replacing Path with $Path, etc.
                     sessionCookie = sessionCookie.replaceFirst("(\\W)(P|p)ath=", "$1\\$Path=");
 
                     // Perform a request to server2 using the session cookie from the previous request
-                    ContentExchange exchange2 = new ContentExchange(true);
-                    exchange2.setMethod(HttpMethods.GET);
-                    exchange2.setURL("http://localhost:" + port2 + contextPath + servletMapping + "?action=get");
-                    exchange2.getRequestFields().add("Cookie", sessionCookie);
-                    client.send(exchange2);
-                    exchange2.waitForDone();
-
-                    assertEquals(HttpServletResponse.SC_OK,exchange2.getResponseStatus());
+                    Request request2 = client.newRequest("http://localhost:" + port2 + contextPath + servletMapping + "?action=get");
+                    request2.method(HttpMethod.GET);
+                    request2.header("Cookie", sessionCookie);
+                    ContentResponse response2 = request2.send();
+
+                    assertEquals(HttpServletResponse.SC_OK,response2.getStatus());
                 }
                 finally
                 {
diff --git a/tests/test-sessions/test-sessions-common/src/main/resources/proxy-serialization.jar b/tests/test-sessions/test-sessions-common/src/main/resources/proxy-serialization.jar
new file mode 100644
index 0000000..fe3f040
Binary files /dev/null and b/tests/test-sessions/test-sessions-common/src/main/resources/proxy-serialization.jar differ
diff --git a/tests/test-webapps/pom.xml b/tests/test-webapps/pom.xml
index 8fdb3c3..67c5dca 100644
--- a/tests/test-webapps/pom.xml
+++ b/tests/test-webapps/pom.xml
@@ -21,7 +21,8 @@
   <parent>
     <groupId>org.eclipse.jetty.tests</groupId>
     <artifactId>tests-parent</artifactId>
-    <version>8.1.17.v20150415</version>
+    <version>9.2.13.v20150730</version>
+    <relativePath>../pom.xml</relativePath>
   </parent>
   <artifactId>test-webapps-parent</artifactId>
   <name>Jetty Tests :: WebApps :: Parent</name>
@@ -40,6 +41,12 @@
     </plugins>
   </build>
   <modules>
+    <module>test-jetty-webapp</module>
+    <module>test-proxy-webapp</module>
     <module>test-webapp-rfc2616</module>
+    <module>test-mock-resources</module>
+    <module>test-servlet-spec</module>
+    <module>test-jaas-webapp</module>
+    <module>test-jndi-webapp</module>
   </modules>
 </project>
diff --git a/tests/test-webapps/test-dispatch-webapp/pom.xml b/tests/test-webapps/test-dispatch-webapp/pom.xml
new file mode 100644
index 0000000..4a1f2b6
--- /dev/null
+++ b/tests/test-webapps/test-dispatch-webapp/pom.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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>
+  <parent>
+    <groupId>org.eclipse.jetty.tests</groupId>
+    <artifactId>test-webapps-parent</artifactId>
+    <version>9.2.2-SNAPSHOT</version>
+  </parent>
+  <name>Jetty Tests :: Webapps :: Dispatch Webapp</name>
+  <artifactId>test-dispatch-webapp</artifactId>
+  <packaging>war</packaging>
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-deploy-plugin</artifactId>
+        <configuration>
+          <skip>true</skip>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.eclipse.jetty</groupId>
+        <artifactId>jetty-maven-plugin</artifactId>
+        <version>${project.version}</version>
+        <configuration>
+          <scanIntervalSeconds>1</scanIntervalSeconds>
+          <useTestClasspath>true</useTestClasspath>
+         <webAppConfig>
+           <war>src/main/webapp</war>
+           <descriptor>src/main/webapp/WEB-INF/web.xml</descriptor>
+           <contextPath>/test-dispatch</contextPath>
+            <containerIncludeJarPattern>.*/javax.servlet-[^/]*\.jar$</containerIncludeJarPattern>
+            <configurationDiscovered>true</configurationDiscovered>
+         </webAppConfig>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+  <dependencies>
+    <dependency>
+       <groupId>javax.servlet</groupId>
+       <artifactId>javax.servlet-api</artifactId>
+       <scope>provided</scope>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/tests/test-webapps/test-dispatch-webapp/src/main/java/com/acme/DispatchServlet.java b/tests/test-webapps/test-dispatch-webapp/src/main/java/com/acme/DispatchServlet.java
new file mode 100644
index 0000000..bbddc83
--- /dev/null
+++ b/tests/test-webapps/test-dispatch-webapp/src/main/java/com/acme/DispatchServlet.java
@@ -0,0 +1,123 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package com.acme;
+
+import static javax.servlet.RequestDispatcher.FORWARD_PATH_INFO;
+import static javax.servlet.RequestDispatcher.FORWARD_QUERY_STRING;
+import static javax.servlet.RequestDispatcher.FORWARD_SERVLET_PATH;
+import static javax.servlet.RequestDispatcher.INCLUDE_PATH_INFO;
+import static javax.servlet.RequestDispatcher.INCLUDE_QUERY_STRING;
+import static javax.servlet.RequestDispatcher.INCLUDE_REQUEST_URI;
+import static javax.servlet.RequestDispatcher.INCLUDE_SERVLET_PATH;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+
+public class DispatchServlet extends HttpServlet
+{
+
+    @Override
+    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+    {
+        doGet(req, resp);
+    }
+
+    @Override
+    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+    {
+        Integer depth = (Integer)request.getAttribute("depth");
+        if (depth==null)
+            depth=1;
+        else
+            depth=depth+1;
+        request.setAttribute("depth", depth);
+        
+        String path=request.getServletPath();
+        String info=request.getPathInfo();
+        String query=request.getQueryString();
+        
+        boolean include=request.getAttribute(INCLUDE_REQUEST_URI)!=null;
+        
+        String tpath = include?(String)request.getAttribute(INCLUDE_SERVLET_PATH):path;
+        String tinfo = include?(String)request.getAttribute(INCLUDE_PATH_INFO):info;
+                
+        if ("/forward".equals(tpath))
+        {
+            getServletContext().getRequestDispatcher(tinfo+"?depth="+depth+"&p"+depth+"="+"ABCDEFGHI".charAt(depth)).forward(request, response);
+        }
+        else if ("/include".equals(tpath))
+        {
+            response.setContentType("text/html");
+            PrintWriter out = response.getWriter();
+            out.println("<h1>Dispatch Depth="+depth+"</h1><pre>");
+            out.printf("            %30s%30s%30s%n","REQUEST","FORWARD","INCLUDE");
+            out.printf("servletPath:%30s%30s%30s%n",path,request.getAttribute(FORWARD_SERVLET_PATH),request.getAttribute(INCLUDE_SERVLET_PATH));
+            out.printf("   pathInfo:%30s%30s%30s%n",info,request.getAttribute(FORWARD_PATH_INFO),request.getAttribute(INCLUDE_PATH_INFO));
+            out.printf("      query:%30s%30s%30s%n",query,request.getAttribute(FORWARD_QUERY_STRING),request.getAttribute(INCLUDE_QUERY_STRING));
+            out.println();
+            printParameters(out, request.getParameterMap());
+            out.println("</pre>");
+            out.println("<hr/>");
+            getServletContext().getRequestDispatcher(tinfo+"?depth="+depth+"&p"+depth+"="+"BCDEFGHI".charAt(depth)).include(request, response);
+            out.println("<hr/>");
+            out.println("<h1>Dispatch Depth="+depth+"</h1><pre>");
+            out.printf("            %30s%30s%30s%n","REQUEST","FORWARD","INCLUDE");
+            out.printf("servletPath:%30s%30s%30s%n",path,request.getAttribute(FORWARD_SERVLET_PATH),request.getAttribute(INCLUDE_SERVLET_PATH));
+            out.printf("   pathInfo:%30s%30s%30s%n",info,request.getAttribute(FORWARD_PATH_INFO),request.getAttribute(INCLUDE_PATH_INFO));
+            out.printf("      query:%30s%30s%30s%n",query,request.getAttribute(FORWARD_QUERY_STRING),request.getAttribute(INCLUDE_QUERY_STRING));
+            out.println();
+            printParameters(out, request.getParameterMap());
+            out.println("</pre>");
+        }
+        else
+        {
+            response.setContentType("text/html");
+            PrintWriter out = response.getWriter();
+            out.println("<h1>Dispatch Depth="+depth+"</h1><pre>");
+            out.printf("            %30s%30s%30s%n","REQUEST","FORWARD","INCLUDE");
+            out.printf("servletPath:%30s%30s%30s%n",path,request.getAttribute(FORWARD_SERVLET_PATH),request.getAttribute(INCLUDE_SERVLET_PATH));
+            out.printf("   pathInfo:%30s%30s%30s%n",info,request.getAttribute(FORWARD_PATH_INFO),request.getAttribute(INCLUDE_PATH_INFO));
+            out.printf("      query:%30s%30s%30s%n",query,request.getAttribute(FORWARD_QUERY_STRING),request.getAttribute(INCLUDE_QUERY_STRING));
+            out.println();
+            printParameters(out, request.getParameterMap());
+            out.println("</pre>");
+        }
+    }
+    
+    private static void printParameters(PrintWriter out, Map<String,String[]> params)
+    {
+        List<String> names = new ArrayList<>(params.keySet());
+        Collections.sort(names);
+        
+        for (String name: names)
+            out.printf("%10s : %s%n", name,Arrays.asList(params.get(name)));
+    }
+
+}
diff --git a/tests/test-webapps/test-dispatch-webapp/src/main/webapp/WEB-INF/web.xml b/tests/test-webapps/test-dispatch-webapp/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000..77c8f0c
--- /dev/null
+++ b/tests/test-webapps/test-dispatch-webapp/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<web-app 
+   xmlns="http://xmlns.jcp.org/xml/ns/javaee"
+   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+   xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
+   metadata-complete="false"
+   version="3.1"> 
+
+  <display-name>Test Dispatch WebApp</display-name>
+  
+  <servlet>
+    <servlet-name>Dispatch</servlet-name>
+    <servlet-class>com.acme.DispatchServlet</servlet-class>
+    <load-on-startup>2</load-on-startup>
+  </servlet>
+
+  <servlet-mapping>
+    <servlet-name>Dispatch</servlet-name>
+    <url-pattern>/forward/*</url-pattern>
+    <url-pattern>/include/*</url-pattern>
+    <url-pattern>/info/*</url-pattern>
+  </servlet-mapping>
+
+</web-app>
diff --git a/test-jetty-webapp/src/main/webapp/jetty_banner.gif b/tests/test-webapps/test-dispatch-webapp/src/main/webapp/images/jetty_banner.gif
similarity index 100%
copy from test-jetty-webapp/src/main/webapp/jetty_banner.gif
copy to tests/test-webapps/test-dispatch-webapp/src/main/webapp/images/jetty_banner.gif
diff --git a/tests/test-webapps/test-dispatch-webapp/src/main/webapp/images/small_powered_by.gif b/tests/test-webapps/test-dispatch-webapp/src/main/webapp/images/small_powered_by.gif
new file mode 100644
index 0000000..c5dd443
Binary files /dev/null and b/tests/test-webapps/test-dispatch-webapp/src/main/webapp/images/small_powered_by.gif differ
diff --git a/tests/test-webapps/test-dispatch-webapp/src/main/webapp/index.html b/tests/test-webapps/test-dispatch-webapp/src/main/webapp/index.html
new file mode 100644
index 0000000..ae423fc
--- /dev/null
+++ b/tests/test-webapps/test-dispatch-webapp/src/main/webapp/index.html
@@ -0,0 +1,48 @@
+<HTML>
+  <HEAD>
+    <TITLE>Test Dispatch WebApp</TITLE>
+    <META http-equiv="Pragma" content="no-cache">
+    <META http-equiv="Cache-Control" content="no-cache,no-store">
+    <link rel="stylesheet" type="text/css" href="stylesheet.css"/>
+  </HEAD>
+<BODY >
+  <A HREF="http://www.eclipse.org/jetty"><IMG SRC="images/jetty_banner.gif"></A>
+  
+<h1>Request Dispatching Query Tests</h1>
+<p><ul>
+<li><a href="forward/info?depth=0&p0=A">forward/info?depth=0&p0=A</a></li>
+<li><a href="include/info?depth=0&p0=A">include/info?depth=0&p0=A</a></li>
+<li><a href="forward/forward/info?depth=0&p0=A">forward/forward/info?depth=0&p0=A</a></li>
+<li><a href="forward/include/info?depth=0&p0=A">forward/include/info?depth=0&p0=A</a></li>
+<li><a href="include/forward/info?depth=0&p0=A">include/forward/info?depth=0&p0=A</a></li>
+<li><a href="include/include/info?depth=0&p0=A">include/include/info?depth=0&p0=A</a></li>
+</ul>
+</p>
+<h1>Request Dispatching Form POST Tests</h1>
+<p><ul>
+<li><form action="forward/info" method="POST"><input type="hidden" name="depth" value="0"><input type="hidden" name="p0" value="A"><input type="submit" value="forward/info"></form></li> 
+<li><form action="include/info" method="POST"><input type="hidden" name="depth" value="0"><input type="hidden" name="p0" value="A"><input type="submit" value="include/info"></form></li> 
+<li><form action="forward/forward/info" method="POST"><input type="hidden" name="depth" value="0"><input type="hidden" name="p0" value="A"><input type="submit" value="forward/forward/info"></form></li> 
+<li><form action="forward/include/info" method="POST"><input type="hidden" name="depth" value="0"><input type="hidden" name="p0" value="A"><input type="submit" value="forward/include/info"></form></li> 
+<li><form action="include/forward/info" method="POST"><input type="hidden" name="depth" value="0"><input type="hidden" name="p0" value="A"><input type="submit" value="include/forward/info"></form></li> 
+<li><form action="include/include/info" method="POST"><input type="hidden" name="depth" value="0"><input type="hidden" name="p0" value="A"><input type="submit" value="include/include/info"></form></li> 
+</ul>
+</p>
+<h1>Request Dispatching Query Form POST Tests</h1>
+<p><ul>
+<li><form action="forward/info?depth=-1&p0=X&q=0" method="POST"><input type="hidden" name="depth" value="0"><input type="hidden" name="p0" value="A"><input type="submit" value="forward/info"></form></li> 
+<li><form action="include/info?depth=-1&p0=X&q=0" method="POST"><input type="hidden" name="depth" value="0"><input type="hidden" name="p0" value="A"><input type="submit" value="include/info"></form></li> 
+<li><form action="forward/forward/info?depth=-1&p0=X&q=0" method="POST"><input type="hidden" name="depth" value="0"><input type="hidden" name="p0" value="A"><input type="submit" value="forward/forward/info"></form></li> 
+<li><form action="forward/include/info?depth=-1&p0=X&q=0" method="POST"><input type="hidden" name="depth" value="0"><input type="hidden" name="p0" value="A"><input type="submit" value="forward/include/info"></form></li> 
+<li><form action="include/forward/info?depth=-1&p0=X&q=0" method="POST"><input type="hidden" name="depth" value="0"><input type="hidden" name="p0" value="A"><input type="submit" value="include/forward/info"></form></li> 
+<li><form action="include/include/info?depth=-1&p0=X&q=0" method="POST"><input type="hidden" name="depth" value="0"><input type="hidden" name="p0" value="A"><input type="submit" value="include/include/info"></form></li> 
+</ul>
+</p>
+
+<center>
+  <hr/>
+  <a href="http://www.eclipse.org/jetty"><img style="border:0" src="images/small_powered_by.gif"/></a>
+</center>
+
+</BODY>
+</HTML>
diff --git a/tests/test-webapps/test-dispatch-webapp/src/main/webapp/stylesheet.css b/tests/test-webapps/test-dispatch-webapp/src/main/webapp/stylesheet.css
new file mode 100644
index 0000000..f90463d
--- /dev/null
+++ b/tests/test-webapps/test-dispatch-webapp/src/main/webapp/stylesheet.css
@@ -0,0 +1,7 @@
+body {color: #2E2E2E; font-family:sans-serif; font-size:90%;}
+h1 {font-variant: small-caps; font-size:130%; letter-spacing: 0.1em;}
+h2 {font-variant: small-caps; font-size:100%; letter-spacing: 0.1em; margin-top:2em;}
+h3 {font-size:100%; letter-spacing: 0.1em;}
+
+span.pass { color: green; }
+span.fail { color:red; }
diff --git a/tests/test-webapps/test-jaas-webapp/pom.xml b/tests/test-webapps/test-jaas-webapp/pom.xml
new file mode 100644
index 0000000..d45b809
--- /dev/null
+++ b/tests/test-webapps/test-jaas-webapp/pom.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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>
+  <parent>
+    <groupId>org.eclipse.jetty.tests</groupId>
+    <artifactId>test-webapps-parent</artifactId>
+    <version>9.2.13.v20150730</version>
+  </parent>
+  <artifactId>test-jaas-webapp</artifactId>
+  <name>Jetty Tests :: WebApp :: JAAS</name>
+  <packaging>war</packaging>
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-deploy-plugin</artifactId>
+        <configuration>
+          <!-- DO NOT DEPLOY (or Release) -->
+          <skip>true</skip>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.eclipse.jetty</groupId>
+        <artifactId>jetty-maven-plugin</artifactId>
+        <version>${project.version}</version>
+        <configuration>
+          <scanIntervalSeconds>10</scanIntervalSeconds>
+          <systemProperties>
+              <!-- This is for convenience so that the src/etc/login.conf file can stay unmodified when copied to $jetty.home/etc directory -->
+            <systemProperty>
+              <name>jetty.home</name>
+              <value>${basedir}/src/main/config</value>
+            </systemProperty>
+              <!-- Mandatory. This system property tells JAAS where to find the login module configuration file -->
+            <systemProperty>
+              <name>java.security.auth.login.config</name>
+              <value>${basedir}/src/main/config/demo-base/etc/login.conf</value>
+            </systemProperty>
+          </systemProperties>
+           <webAppConfig>
+            <contextPath>/test-jaas</contextPath>
+            <securityHandler implementation="org.eclipse.jetty.security.ConstraintSecurityHandler">
+              <loginService implementation="org.eclipse.jetty.jaas.JAASLoginService">
+                 <name>Test JAAS Realm</name>
+                 <loginModuleName>xyz</loginModuleName>
+              </loginService>
+            </securityHandler>
+          </webAppConfig>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-assembly-plugin</artifactId>
+        <executions>
+          <execution>
+            <phase>package</phase>
+            <goals>
+              <goal>single</goal>
+            </goals>
+            <configuration>
+              <descriptors>
+                <descriptor>${basedir}/src/main/assembly/config.xml</descriptor>
+              </descriptors>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+</project>
diff --git a/tests/test-webapps/test-jaas-webapp/src/main/assembly/config.xml b/tests/test-webapps/test-jaas-webapp/src/main/assembly/config.xml
new file mode 100644
index 0000000..3fca8c6
--- /dev/null
+++ b/tests/test-webapps/test-jaas-webapp/src/main/assembly/config.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<assembly>
+  <id>config</id>
+  <includeBaseDirectory>false</includeBaseDirectory>
+  <formats>
+    <format>jar</format>
+  </formats>
+  <fileSets>
+    <fileSet>
+      <directory>src/main/config</directory>
+      <outputDirectory></outputDirectory>
+      <includes>
+        <include>webapps/test-jaas.xml</include>
+        <include>**</include>
+      </includes>
+    </fileSet>
+  </fileSets>
+</assembly>
+
diff --git a/tests/test-webapps/test-jaas-webapp/src/main/config/demo-base/etc/login.conf b/tests/test-webapps/test-jaas-webapp/src/main/config/demo-base/etc/login.conf
new file mode 100644
index 0000000..a97b0ed
--- /dev/null
+++ b/tests/test-webapps/test-jaas-webapp/src/main/config/demo-base/etc/login.conf
@@ -0,0 +1,5 @@
+xyz {
+org.eclipse.jetty.jaas.spi.PropertyFileLoginModule required
+debug="true"
+file="${jetty.base}/etc/login.properties";
+};
diff --git a/tests/test-webapps/test-jaas-webapp/src/main/config/demo-base/etc/login.properties b/tests/test-webapps/test-jaas-webapp/src/main/config/demo-base/etc/login.properties
new file mode 100644
index 0000000..61e3203
--- /dev/null
+++ b/tests/test-webapps/test-jaas-webapp/src/main/config/demo-base/etc/login.properties
@@ -0,0 +1 @@
+me=me,me,roleA
diff --git a/tests/test-webapps/test-jaas-webapp/src/main/config/demo-base/webapps/test-jaas.xml b/tests/test-webapps/test-jaas-webapp/src/main/config/demo-base/webapps/test-jaas.xml
new file mode 100644
index 0000000..f3b0a18
--- /dev/null
+++ b/tests/test-webapps/test-jaas-webapp/src/main/config/demo-base/webapps/test-jaas.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0"?>
+<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
+
+<!-- =============================================================== -->
+<!-- Configure the test-jndi webapp                                  -->
+<!-- =============================================================== -->
+<Configure id='wac' class="org.eclipse.jetty.webapp.WebAppContext">
+
+  <Set name="contextPath">/test-jaas</Set>
+  <Set name="war"><Property name="jetty.webapps" default="."/>/test-jaas.war</Set>
+  <Set name="extractWAR">true</Set>
+
+  <Set name="securityHandler">
+    <New class="org.eclipse.jetty.security.ConstraintSecurityHandler">
+     <Set name="loginService">
+       <New class="org.eclipse.jetty.jaas.JAASLoginService">
+         <Set name="name">Test JAAS Realm</Set>
+         <Set name="loginModuleName">xyz</Set>
+       </New>
+     </Set>
+    </New>
+  </Set>
+
+</Configure>
+
diff --git a/tests/test-webapps/test-jaas-webapp/src/main/webapp/WEB-INF/jetty-web.xml b/tests/test-webapps/test-jaas-webapp/src/main/webapp/WEB-INF/jetty-web.xml
new file mode 100644
index 0000000..e7830c6
--- /dev/null
+++ b/tests/test-webapps/test-jaas-webapp/src/main/webapp/WEB-INF/jetty-web.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
+
+<Configure class="org.eclipse.jetty.webapp.WebAppContext">
+  <Get class="org.eclipse.jetty.util.log.Log" name="rootLogger">
+    <Call name="warn"><Arg>test-jaas webapp is deployed. DO NOT USE IN PRODUCTION!</Arg></Call>
+  </Get>
+</Configure>
diff --git a/tests/test-webapps/test-jaas-webapp/src/main/webapp/WEB-INF/web.xml b/tests/test-webapps/test-jaas-webapp/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000..e3c7acf
--- /dev/null
+++ b/tests/test-webapps/test-jaas-webapp/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,41 @@
+<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>JAAS Test</display-name>
+
+  <welcome-file-list>
+    <welcome-file>index.html</welcome-file>
+  </welcome-file-list>
+
+
+  <security-constraint>
+    <web-resource-collection>
+      <web-resource-name>JAAS Role</web-resource-name>
+      <url-pattern>/auth.html</url-pattern>
+    </web-resource-collection>
+    <auth-constraint>
+      <role-name>roleA</role-name>
+    </auth-constraint>
+  </security-constraint>
+
+
+  <login-config>
+    <auth-method>FORM</auth-method>
+    <realm-name>Test JAAS Realm</realm-name>
+    <form-login-config>
+      <form-login-page>
+        /login.html
+      </form-login-page>
+      <form-error-page>
+        /authfail.html
+      </form-error-page>
+    </form-login-config>
+  </login-config>
+
+  <security-role>
+    <role-name>roleA</role-name>
+  </security-role>
+</web-app>
diff --git a/tests/test-webapps/test-jaas-webapp/src/main/webapp/auth.html b/tests/test-webapps/test-jaas-webapp/src/main/webapp/auth.html
new file mode 100644
index 0000000..249f958
--- /dev/null
+++ b/tests/test-webapps/test-jaas-webapp/src/main/webapp/auth.html
@@ -0,0 +1,18 @@
+<HTML>
+  <HEAD>
+    <META http-equiv="Pragma" content="no-cache">
+    <META http-equiv="Cache-Control" content="no-cache,no-store">
+    <link rel="stylesheet" type="text/css" href="stylesheet.css"/>
+  </HEAD>
+
+  <BODY>
+    <H1>SUCCESS! You are AUTHENTICATED and AUTHORIZED</H1>
+  In order to see this page, you must have been JAAS authentictated using the
+  configured Login Module. You have also been AUTHORIZED according to this webapp's role-based web security constraints.
+  <P>
+  To logout click:
+  <P>
+  <A HREF="logout.jsp">Logout</A>
+  <P>
+  </BODY>
+</HTML>
diff --git a/tests/test-webapps/test-jaas-webapp/src/main/webapp/authfail.html b/tests/test-webapps/test-jaas-webapp/src/main/webapp/authfail.html
new file mode 100644
index 0000000..f57687b
--- /dev/null
+++ b/tests/test-webapps/test-jaas-webapp/src/main/webapp/authfail.html
@@ -0,0 +1,11 @@
+<html>
+  <head>
+    <title>Authentication Failure</title>
+    <link rel="stylesheet" type="text/css" href="stylesheet.css"/>
+  </head>
+  <body>
+    <h1>Authentication Failure</h1>
+    <p>Sorry, either your login or password were incorrect, please try again.</p>
+    <a href="auth.html">Login</a>
+  </body>
+</html>
diff --git a/test-jetty-webapp/src/main/webapp/jetty_banner.gif b/tests/test-webapps/test-jaas-webapp/src/main/webapp/images/jetty_banner.gif
similarity index 100%
copy from test-jetty-webapp/src/main/webapp/jetty_banner.gif
copy to tests/test-webapps/test-jaas-webapp/src/main/webapp/images/jetty_banner.gif
diff --git a/tests/test-webapps/test-jaas-webapp/src/main/webapp/images/small_powered_by.gif b/tests/test-webapps/test-jaas-webapp/src/main/webapp/images/small_powered_by.gif
new file mode 100644
index 0000000..c5dd443
Binary files /dev/null and b/tests/test-webapps/test-jaas-webapp/src/main/webapp/images/small_powered_by.gif differ
diff --git a/tests/test-webapps/test-jaas-webapp/src/main/webapp/index.html b/tests/test-webapps/test-jaas-webapp/src/main/webapp/index.html
new file mode 100644
index 0000000..777e76f
--- /dev/null
+++ b/tests/test-webapps/test-jaas-webapp/src/main/webapp/index.html
@@ -0,0 +1,42 @@
+<HTML>
+  <HEAD>
+    <TITLE>JAAS Authentication and Authorization Test</TITLE>
+    <META http-equiv="Pragma" content="no-cache">
+    <META http-equiv="Cache-Control" content="no-cache,no-store">
+    <link rel="stylesheet" type="text/css" href="stylesheet.css"/>
+  </HEAD>
+<BODY>
+  <A HREF="http://www.eclipse.org/jetty"><IMG SRC="images/jetty_banner.gif"></A>
+  <br/>
+  <b><a href="http://localhost:8080/">Demo Home</a></b>
+  <hr/>
+  <center><span style="color:red; font-variant:small-caps; font-weight:bold">Test Web Application Only - Do NOT Deploy in Production</span> </center>
+
+  <H1>JAAS Authentication and Authorization Demo </H1>
+  <h2>Preparation</h2>
+  <p>To enable JAAS in a base jetty instance do:
+    <pre>
+    $ cd $JETTY_BASE
+    $ java -jar $JETTY_HOME/start.jar --add-to-startd=jaas
+    </pre>
+  </p>
+  <p>This will create a $JETTY_BASE/start.d/jaas.ini file to enable and parameterise JAAS.  If the --add-to-start option instead, then the same initialisation will be appended to the
+  $JETTY_BASE/start.ini file instead. The jetty demo-base already has JAAS enabled in the start.ini file.  </p>
+
+<p>The full source of this demonstration is available <a
+href="http://git.eclipse.org/c/jetty/org.eclipse.jetty.project.git/tree/tests/test-webapps/test-jaas-webapp">here</a>.</p>
+
+  <h2>Using the Demo</h2>
+  <P>
+  Click on the link below to test JAAS <i>authentication</i> and role-based web security constraint <i>authorization</i>.  Use username="me" with password="me".  All other usernames, passwords should result in authentication failure.
+  </P>
+  <big><b><A HREF="auth.html">LOGIN</A></b></big>
+  <p>
+   This demo uses a simple login module that stores its configuration in a properties file. There are other types of login module provided with the jetty distro. For full information, please refer to the <a href="http://www.eclipse.org/jetty/documentation/current/">Jetty 9 documentation</a>.
+  </p>
+
+  <hr/>
+  <center> <a href="http://www.eclipse.org/jetty"><img style="border:0" src="images/small_powered_by.gif"/></a></center>
+
+</BODY>
+</HTML>
diff --git a/tests/test-webapps/test-jaas-webapp/src/main/webapp/login.html b/tests/test-webapps/test-jaas-webapp/src/main/webapp/login.html
new file mode 100644
index 0000000..608ea5e
--- /dev/null
+++ b/tests/test-webapps/test-jaas-webapp/src/main/webapp/login.html
@@ -0,0 +1,18 @@
+
+<HTML>
+  <HEAD><TITLE>JAAS Authentication and Authorization Test</TITLE>
+  <link rel="stylesheet" type="text/css" href="stylesheet.css"/>
+</HEAD>
+<BODY>
+  <H1> Enter your username and password to login </H1>
+  <I> Enter login=me and password=me in order to authenticate successfully</I>
+  <form method="POST" action="j_security_check">
+    <B>Login: </B><input type="text" name="j_username">
+    <P>
+    <B>Password: </B><input type="password" name="j_password">
+    <P>
+    <input type="submit" value="Login"/>
+  </form>
+  <p>
+</BODY>
+</HTML>
diff --git a/tests/test-webapps/test-jaas-webapp/src/main/webapp/logout.jsp b/tests/test-webapps/test-jaas-webapp/src/main/webapp/logout.jsp
new file mode 100644
index 0000000..2b647fc
--- /dev/null
+++ b/tests/test-webapps/test-jaas-webapp/src/main/webapp/logout.jsp
@@ -0,0 +1,21 @@
+<%@ page contentType="text/html; charset=UTF-8" %>
+<%@ page import="java.util.*"%>
+<%@ page import="javax.servlet.*" %>
+<%@ page import="javax.servlet.http.*" %>
+<html>
+<head>
+<title>Logout</title>
+</head>
+
+<body>
+  <% 
+    HttpSession s = request.getSession(false);
+    s.invalidate();
+   %>
+   <h1>Logout</h1>
+
+   <p>You are now logged out.</p> 
+   <a href="auth.html"/>Login</a>
+</body>
+
+</html>
diff --git a/tests/test-webapps/test-jaas-webapp/src/main/webapp/stylesheet.css b/tests/test-webapps/test-jaas-webapp/src/main/webapp/stylesheet.css
new file mode 100644
index 0000000..4ecc2cb
--- /dev/null
+++ b/tests/test-webapps/test-jaas-webapp/src/main/webapp/stylesheet.css
@@ -0,0 +1,7 @@
+body {color: #2E2E2E; font-family:sans-serif; font-size:90%;}
+h1 {font-variant: small-caps; font-size:130%; letter-spacing: 0.1em;}
+h2 {font-variant: small-caps; font-size:100%; letter-spacing: 0.1em;}
+h3 {font-size:100%; letter-spacing: 0.1em;}
+
+span.pass { color: green; }
+span.fail { color:red; }
diff --git a/test-jetty-webapp/jetty-chat.jmx b/tests/test-webapps/test-jetty-webapp/jetty-chat.jmx
similarity index 100%
rename from test-jetty-webapp/jetty-chat.jmx
rename to tests/test-webapps/test-jetty-webapp/jetty-chat.jmx
diff --git a/tests/test-webapps/test-jetty-webapp/pom.xml b/tests/test-webapps/test-jetty-webapp/pom.xml
new file mode 100644
index 0000000..9e187fd
--- /dev/null
+++ b/tests/test-webapps/test-jetty-webapp/pom.xml
@@ -0,0 +1,270 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+// ========================================================================
+// Copyright (c) Webtide LLC
+// 
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at 
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.apache.org/licenses/LICENSE-2.0.txt
+//
+// You may elect to redistribute this code under either of these licenses. 
+// ========================================================================
+ -->
+<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">
+  <parent>
+    <groupId>org.eclipse.jetty.tests</groupId>
+    <artifactId>test-webapps-parent</artifactId>
+    <version>9.2.13.v20150730</version>
+    <relativePath>../pom.xml</relativePath>
+  </parent>
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>org.eclipse.jetty</groupId>
+  <artifactId>test-jetty-webapp</artifactId>
+  <name>Test :: Jetty Test Webapp</name>
+  <url>http://www.eclipse.org/jetty</url>
+  <packaging>war</packaging>
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-surefire-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>test</id>
+            <phase>test</phase>
+          </execution>
+        </executions>
+        <configuration>
+          <excludes>
+            <exclude>**/WebAppTest.java</exclude>
+            <exclude>**/Test*.java</exclude>
+          </excludes>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-assembly-plugin</artifactId>
+        <executions>
+          <execution>
+            <phase>package</phase>
+            <goals>
+              <goal>single</goal>
+            </goals>
+            <configuration>
+              <descriptorRefs>
+                <descriptorRef>config</descriptorRef>
+              </descriptorRefs>
+              <descriptors>
+                <descriptor>src/main/assembly/web-bundle.xml</descriptor>
+              </descriptors>
+              <archive>
+                <manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
+              </archive>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+      <!-- also make this webapp an osgi bundle -->
+      <plugin>
+        <artifactId>maven-war-plugin</artifactId>
+        <configuration>
+          <archive>
+            <manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
+          </archive>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>maven-bundle-plugin</artifactId>
+        <extensions>true</extensions>
+        <configuration>
+          <supportedProjectTypes>
+            <supportedProjectType>war</supportedProjectType>
+          </supportedProjectTypes>
+        </configuration>
+        <executions>
+          <execution>
+            <id>bundle-manifest</id>
+            <phase>process-classes</phase>
+            <goals>
+              <goal>manifest</goal>
+            </goals>
+            <configuration>
+              <instructions>
+                <Bundle-SymbolicName>org.eclipse.jetty.test-jetty-webapp</Bundle-SymbolicName>
+                <Import-Package>javax.servlet.jsp.*;version="[2.2.0, 3.0)",javax.servlet.*;version="[2.6,3.2)",org.eclipse.jetty.*;version="9.1",*</Import-Package>
+                <Export-Package>!com.acme*</Export-Package>
+                <!-- the test webapp is configured via a jetty xml file
+                in order to add the security handler. -->
+                <Web-ContextPath>/</Web-ContextPath>
+                <!-- in fact the '.' must not be there
+                but Felix-BND has a bug:
+                http://www.mail-archive.com/users@felix.apache.org/msg04730.html
+                https://issues.apache.org/jira/browse/FELIX-1571
+                -->
+                <Bundle-ClassPath>.,WEB-INF/classes</Bundle-ClassPath>
+              </instructions>
+            </configuration>
+           </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <groupId>org.eclipse.jetty</groupId>
+        <artifactId>jetty-maven-plugin</artifactId>
+        <version>${project.version}</version>
+        <dependencies>
+            <dependency>
+                <groupId>org.eclipse.jetty</groupId>
+                <artifactId>jetty-client</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.eclipse.jetty</groupId>
+                <artifactId>jetty-servlets</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+        </dependencies>
+        <configuration>
+          <stopPort>8087</stopPort>
+          <stopKey>foo</stopKey>
+          <scanIntervalSeconds>1</scanIntervalSeconds>
+          <systemProperties>
+            <systemProperty>
+              <name>fooprop</name>
+              <value>222</value>
+            </systemProperty>
+          </systemProperties>
+          <webApp>
+            <contextPath>/test</contextPath>
+            <tempDirectory>${project.build.directory}/work</tempDirectory>
+            <sessionHandler implementation="org.eclipse.jetty.server.session.SessionHandler">
+              <sessionManager implementation="org.eclipse.jetty.server.session.HashSessionManager">
+                <storeDirectory>${basedir}/target/sessions</storeDirectory>
+              </sessionManager>
+            </sessionHandler>
+          </webApp>
+          <loginServices>
+            <loginService implementation="org.eclipse.jetty.security.HashLoginService">
+              <name>Test Realm</name>
+              <config>src/main/config/demo-base/etc/realm.properties</config>
+            </loginService>
+          </loginServices>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+  <dependencies>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-continuation</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-servlets</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>javax.servlet</groupId>
+      <artifactId>javax.servlet-api</artifactId>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.websocket</groupId>
+      <artifactId>websocket-servlet</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.websocket</groupId>
+      <artifactId>websocket-server</artifactId>
+      <version>${project.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-webapp</artifactId>
+      <version>${project.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-jmx</artifactId>
+      <version>${project.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.toolchain</groupId>
+      <artifactId>jetty-test-helper</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-server</artifactId>
+      <version>${project.version}</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>javax.servlet.jsp</groupId>
+      <artifactId>jsp-api</artifactId>
+      <version>2.1</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.spdy</groupId>
+      <artifactId>spdy-http-server</artifactId>
+      <version>${project.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>javax.servlet</groupId>
+      <artifactId>jstl</artifactId>
+      <version>1.2</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>javax.websocket</groupId>
+      <artifactId>javax.websocket-api</artifactId>
+      <version>1.0</version>
+      <scope>provided</scope>
+    </dependency>
+  </dependencies>
+  <profiles>
+    <profile>
+      <id>precompile-jsp</id>
+      <build>
+        <plugins>
+         <plugin>
+          <groupId>org.eclipse.jetty</groupId>
+          <artifactId>jetty-jspc-maven-plugin</artifactId>
+          <version>${project.version}</version>
+          <executions>
+            <execution>
+              <id>jspc</id>
+              <goals>
+                <goal>jspc</goal>
+              </goals>
+              <configuration>
+                 <includes>**/*.foo</includes>
+                 <excludes>**/*.fff</excludes>
+              </configuration>
+            </execution>
+          </executions>
+        </plugin>
+        <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-war-plugin</artifactId>
+          <configuration>
+            <webXml>${basedir}/target/web.xml</webXml>
+          </configuration>
+        </plugin>
+       </plugins>
+     </build>
+    </profile>
+  </profiles>
+</project>
diff --git a/tests/test-webapps/test-jetty-webapp/src/main/assembly/embedded-jetty-web-for-webbundle.xml b/tests/test-webapps/test-jetty-webapp/src/main/assembly/embedded-jetty-web-for-webbundle.xml
new file mode 100644
index 0000000..5d223e3
--- /dev/null
+++ b/tests/test-webapps/test-jetty-webapp/src/main/assembly/embedded-jetty-web-for-webbundle.xml
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
+
+<!-- ==================================================================
+Configure and deploy the test web application in $(jetty.home)/webapps/test
+
+Note. If this file did not exist or used a context path other that /test
+then the default configuration of jetty.xml would discover the test
+webapplication with a WebAppDeployer.  By specifying a context in this
+directory, additional configuration may be specified and hot deployments
+detected.
+===================================================================== -->
+
+<Configure class="org.eclipse.jetty.webapp.WebAppContext">
+
+
+  <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+  <!-- Required minimal context configuration :                        -->
+  <!--  + contextPath                                                  -->
+  <!--  + war OR resourceBase                                          -->
+  <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+  <Set name="contextPath">/</Set>
+  <Set name="war"><SystemProperty name="jetty.home" default="."/>/webapps/test.war</Set>
+
+  <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+  <!-- Optional context configuration                                  -->
+  <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+  <Set name="extractWAR">true</Set>
+  <Set name="copyWebDir">false</Set>
+  <Set name="defaultsDescriptor"><SystemProperty name="jetty.home" default="."/>/etc/webdefault.xml</Set>
+  <Set name="overrideDescriptor"><SystemProperty name="jetty.base" default="."/>/etc/override-web.xml</Set>
+
+  <!-- virtual hosts
+  <Set name="virtualHosts">
+    <Array type="String">
+      <Item>www.myVirtualDomain.com</Item>
+      <Item>localhost</Item>
+      <Item>127.0.0.1</Item>
+    </Array>
+  </Set>
+  -->
+
+  <!-- disable cookies
+  <Get name="sessionHandler">
+     <Get name="sessionManager">
+        <Set name="usingCookies" type="boolean">false</Set>
+     </Get>
+  </Get>
+  -->
+
+  <Get name="securityHandler">
+    <Set name="loginService">
+      <New class="org.eclipse.jetty.security.HashLoginService">
+	    <Set name="name">Test Realm</Set>
+	    <Set name="config"><Property name="this.web-inf.url"/>realm.properties</Set>
+            <!-- To enable reload of realm when properties change, uncomment the following lines -->
+            <!-- changing refreshInterval (in seconds) as desired                                -->
+            <!--
+            <Set name="refreshInterval">5</Set>
+            <Call name="start"></Call>
+            -->
+      </New>
+    </Set>
+    <Set name="checkWelcomeFiles">true</Set>
+  </Get>
+
+  <!-- Non standard error page mapping -->
+  <!--
+  <Get name="errorHandler">
+    <Call name="addErrorPage">
+      <Arg type="int">500</Arg>
+      <Arg type="int">599</Arg>
+      <Arg type="String">/dump/errorCodeRangeMapping</Arg>
+    </Call>
+  </Get>
+  -->
+
+  <!-- Add context specific logger
+  <Set name="handler">
+    <New id="RequestLog" class="org.eclipse.jetty.server.handler.RequestLogHandler">
+      <Set name="requestLog">
+	<New id="RequestLogImpl" class="org.eclipse.jetty.server.NCSARequestLog">
+	  <Set name="filename"><Property name="jetty.logs" default="./logs"/>/test-yyyy_mm_dd.request.log</Set>
+	  <Set name="filenameDateFormat">yyyy_MM_dd</Set>
+	  <Set name="append">true</Set>
+	  <Set name="LogTimeZone">GMT</Set>
+	</New>
+      </Set>
+    </New>
+  </Set>
+  -->
+
+</Configure>
diff --git a/tests/test-webapps/test-jetty-webapp/src/main/assembly/web-bundle.xml b/tests/test-webapps/test-jetty-webapp/src/main/assembly/web-bundle.xml
new file mode 100644
index 0000000..c220f51
--- /dev/null
+++ b/tests/test-webapps/test-jetty-webapp/src/main/assembly/web-bundle.xml
@@ -0,0 +1,35 @@
+<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0" 
+  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd">
+  <id>webbundle</id>
+  <formats>
+    <format>jar</format>
+  </formats>
+  <includeBaseDirectory>false</includeBaseDirectory>
+  <!-- baseDirectory>${basedir}/${project.build.directory}/${project.build.finalName}</baseDirectory -->
+  <fileSets>
+    <fileSet>
+      <directory>${basedir}/${project.build.directory}/${project.build.finalName}/</directory>
+      <outputDirectory></outputDirectory>
+      <includes>
+        <include>**/*.*</include>
+      </includes>
+      <excludes>
+        <exclude>WEB-INF/lib/**</exclude>
+        <exclude>WEB-INF/jetty-web.xml</exclude>
+      </excludes>
+    </fileSet>
+  </fileSets>
+  <files>
+    <file>
+      <source>src/main/assembly/embedded-jetty-web-for-webbundle.xml</source>
+      <outputDirectory>WEB-INF</outputDirectory>
+      <destName>jetty-web.xml</destName>
+    </file>
+    <file>
+      <source>src/main/config/demo-base/etc/realm.properties</source>
+      <outputDirectory>WEB-INF</outputDirectory>
+      <destName>realm.properties</destName>
+    </file>
+  </files>
+</assembly>
diff --git a/tests/test-webapps/test-jetty-webapp/src/main/config/demo-base/etc/demo-rewrite-rules.xml b/tests/test-webapps/test-jetty-webapp/src/main/config/demo-base/etc/demo-rewrite-rules.xml
new file mode 100644
index 0000000..35a8f87
--- /dev/null
+++ b/tests/test-webapps/test-jetty-webapp/src/main/config/demo-base/etc/demo-rewrite-rules.xml
@@ -0,0 +1,104 @@
+<?xml version="1.0"?>
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
+
+<!-- =============================================================== -->
+<!-- Configure the demos                                             -->
+<!-- =============================================================== -->
+<Configure id="Server" class="org.eclipse.jetty.server.Server">
+
+  <!-- ============================================================= -->
+  <!-- Add rewrite rules                                             -->
+  <!-- ============================================================= -->
+  <Ref refid="Rewrite">
+      <!-- Add rule to protect against IE ssl bug -->
+      <Call name="addRule">
+        <Arg>
+          <New class="org.eclipse.jetty.rewrite.handler.MsieSslRule"/>
+        </Arg>
+      </Call>
+
+      <!-- protect favicon handling -->
+      <Call name="addRule">
+        <Arg>
+          <New class="org.eclipse.jetty.rewrite.handler.HeaderPatternRule">
+            <Set name="pattern">/favicon.ico</Set>
+            <Set name="name">Cache-Control</Set>
+            <Set name="value">Max-Age=3600,public</Set>
+            <Set name="terminating">true</Set>
+          </New>
+        </Arg>
+      </Call>
+
+      <!-- redirect from the welcome page to a specific page -->
+      <Call name="addRule">
+        <Arg>
+          <New class="org.eclipse.jetty.rewrite.handler.RewritePatternRule">
+            <Set name="pattern">/test/rewrite/</Set>
+            <Set name="replacement">/test/rewrite/info.html</Set>
+          </New>
+        </Arg>
+      </Call>
+
+      <!-- replace the entire request URI -->
+      <Call name="addRule">
+        <Arg>
+          <New class="org.eclipse.jetty.rewrite.handler.RewritePatternRule">
+            <Set name="pattern">/test/some/old/context</Set>
+            <Set name="replacement">/test/rewritten/newcontext</Set>
+          </New>
+        </Arg>
+      </Call>
+
+      <!-- replace the beginning of the request URI -->
+      <Call name="addRule">
+        <Arg>
+          <New class="org.eclipse.jetty.rewrite.handler.RewritePatternRule">
+            <Set name="pattern">/test/rewrite/for/*</Set>
+            <Set name="replacement">/test/rewritten/</Set>
+          </New>
+        </Arg>
+      </Call>
+
+      <!-- reverse the order of the path sections -->
+      <Call name="addRule">
+        <Arg>
+          <New class="org.eclipse.jetty.rewrite.handler.RewriteRegexRule">
+            <Set name="regex">(.*?)/reverse/([^/]*)/(.*)</Set>
+            <Set name="replacement">$1/reverse/$3/$2</Set>
+          </New>
+        </Arg>
+      </Call>
+
+      <!-- add a cookie to each path visited -->
+      <Call name="addRule">
+        <Arg>
+          <New class="org.eclipse.jetty.rewrite.handler.CookiePatternRule">
+            <Set name="pattern">/*</Set>
+            <Set name="name">visited</Set>
+            <Set name="value">yes</Set>
+          </New>
+        </Arg>
+      </Call>
+
+      <!--  actual redirect, instead of internal rewrite -->
+      <Call name="addRule">
+        <Arg>
+          <New class="org.eclipse.jetty.rewrite.handler.RedirectPatternRule">
+            <Set name="pattern">/test/redirect/*</Set>
+            <Set name="location">/test/redirected</Set>
+          </New>
+        </Arg>
+      </Call>
+
+      <!-- add a response rule -->
+      <Call name="addRule">
+        <Arg>
+           <New class="org.eclipse.jetty.rewrite.handler.ResponsePatternRule">
+             <Set name="pattern">/400Error</Set>
+             <Set name="code">400</Set>
+             <Set name="reason">ResponsePatternRule Demo</Set>
+          </New>
+        </Arg>
+      </Call>
+  </Ref>
+</Configure>
diff --git a/tests/test-webapps/test-jetty-webapp/src/main/config/demo-base/etc/realm.properties b/tests/test-webapps/test-jetty-webapp/src/main/config/demo-base/etc/realm.properties
new file mode 100644
index 0000000..9d88b85
--- /dev/null
+++ b/tests/test-webapps/test-jetty-webapp/src/main/config/demo-base/etc/realm.properties
@@ -0,0 +1,21 @@
+#
+# This file defines users passwords and roles for a HashUserRealm
+#
+# The format is
+#  <username>: <password>[,<rolename> ...]
+#
+# Passwords may be clear text, obfuscated or checksummed.  The class 
+# org.eclipse.util.Password should be used to generate obfuscated
+# passwords or password checksums
+#
+# If DIGEST Authentication is used, the password must be in a recoverable
+# format, either plain text or OBF:.
+#
+jetty: MD5:164c88b302622e17050af52c89945d44,user
+admin: CRYPT:adpexzg3FUZAk,server-administrator,content-administrator,admin,user
+other: OBF:1xmk1w261u9r1w1c1xmq,user
+plain: plain,user
+user: password,user
+
+# This entry is for digest auth.  The credential is a MD5 hash of username:realmname:password
+digest: MD5:6e120743ad67abfbc385bc2bb754e297,user
diff --git a/tests/test-webapps/test-jetty-webapp/src/main/config/demo-base/etc/test-realm.xml b/tests/test-webapps/test-jetty-webapp/src/main/config/demo-base/etc/test-realm.xml
new file mode 100644
index 0000000..d5c776b
--- /dev/null
+++ b/tests/test-webapps/test-jetty-webapp/src/main/config/demo-base/etc/test-realm.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0"?>
+<!DOCTYPE Configure PUBLIC "-" "http://www.eclipse.org/jetty/configure_9_0.dtd">
+<Configure id="Server" class="org.eclipse.jetty.server.Server">
+    <!-- =========================================================== -->
+    <!-- Configure Authentication Login Service                      -->
+    <!-- Realms may be configured for the entire server here, or     -->
+    <!-- they can be configured for a specific web app in a context  -->
+    <!-- configuration (see $(jetty.home)/webapps/test.xml for an    -->
+    <!-- example).                                                   -->
+    <!-- =========================================================== -->
+    <Call name="addBean">
+      <Arg>
+        <New class="org.eclipse.jetty.security.HashLoginService">
+          <Set name="name">Test Realm</Set>
+          <Set name="config"><Property name="demo.realm" default="etc/realm.properties"/></Set>
+          <Set name="refreshInterval">0</Set>
+        </New>
+      </Arg>
+    </Call>
+
+    <Get class="org.eclipse.jetty.util.log.Log" name="rootLogger">
+      <Call name="warn"><Arg>demo test-realm is deployed. DO NOT USE IN PRODUCTION!</Arg></Call>
+    </Get>
+</Configure>
diff --git a/tests/test-webapps/test-jetty-webapp/src/main/config/demo-base/start.ini b/tests/test-webapps/test-jetty-webapp/src/main/config/demo-base/start.ini
new file mode 100644
index 0000000..9a840f4
--- /dev/null
+++ b/tests/test-webapps/test-jetty-webapp/src/main/config/demo-base/start.ini
@@ -0,0 +1,23 @@
+#
+# Example of providing a demo configuration, using a ${jetty.base}
+#
+# Additional ini files are in demo-base/start.d
+# 
+
+# Enable security via jaas, and configure it
+--module=jaas
+jaas.login.conf=etc/login.conf
+
+# Enable rewrite examples
+--module=rewrite
+etc/demo-rewrite-rules.xml
+
+# Websocket chat examples needs websocket enabled
+# Don't start for all contexts (set to true in test.xml context)
+org.eclipse.jetty.websocket.jsr356=false
+--module=websocket
+
+# Create and configure the test realm
+etc/test-realm.xml
+demo.realm=etc/realm.properties
+
diff --git a/tests/test-webapps/test-jetty-webapp/src/main/config/demo-base/webapps/test.d/override-web.xml b/tests/test-webapps/test-jetty-webapp/src/main/config/demo-base/webapps/test.d/override-web.xml
new file mode 100644
index 0000000..8a58a83
--- /dev/null
+++ b/tests/test-webapps/test-jetty-webapp/src/main/config/demo-base/webapps/test.d/override-web.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<web-app 
+   xmlns="http://xmlns.jcp.org/xml/ns/javaee" 
+   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+   xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" 
+   version="3.1"> 
+
+
+<!-- This web.xml format file is an override file that is applied to the test webapp AFTER
+     it has been configured by the default descriptor and the WEB-INF/web.xml descriptor -->
+
+  <!-- Add or override context init parameter -->
+  <context-param>
+    <param-name>context-override-example</param-name>
+    <param-value>a context value</param-value>
+  </context-param>
+
+
+  <!-- Add or override servlet init parameter -->
+  <servlet>
+    <servlet-name>Dump</servlet-name>
+    <init-param>
+      <param-name>servlet-override-example</param-name>
+      <param-value>a servlet value</param-value>
+    </init-param>
+  </servlet>
+
+  <!-- Add servlet mapping -->
+  <servlet-mapping>
+    <servlet-name>Dump</servlet-name>
+    <url-pattern>*.more</url-pattern>
+  </servlet-mapping>
+
+  <!-- Reset servlet class and/or start order -->
+  <servlet>
+    <servlet-name>Session</servlet-name>
+    <servlet-class>com.acme.SessionDump</servlet-class>
+    <load-on-startup>5</load-on-startup>
+  </servlet>
+  
+  <!-- Allow remote access to test webapp -->
+  <!--
+  <filter>
+    <filter-name>TestFilter</filter-name>
+    <filter-class>com.acme.TestFilter</filter-class>
+    <async-supported>true</async-supported>
+    <init-param>
+      <param-name>remote</param-name>
+      <param-value>true</param-value>
+    </init-param>
+  </filter>
+  -->
+  
+</web-app>
+
+
diff --git a/tests/test-webapps/test-jetty-webapp/src/main/config/demo-base/webapps/test.xml b/tests/test-webapps/test-jetty-webapp/src/main/config/demo-base/webapps/test.xml
new file mode 100644
index 0000000..43047f9
--- /dev/null
+++ b/tests/test-webapps/test-jetty-webapp/src/main/config/demo-base/webapps/test.xml
@@ -0,0 +1,112 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
+
+<!-- ==================================================================
+Configure and deploy the test web application in $(jetty.home)/webapps/test
+
+Note. If this file did not exist or used a context path other that /test
+then the default configuration of jetty.xml would discover the test
+webapplication with a WebAppDeployer.  By specifying a context in this
+directory, additional configuration may be specified and hot deployments
+detected.
+===================================================================== -->
+
+<Configure class="org.eclipse.jetty.webapp.WebAppContext">
+
+  <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+  <!-- Required minimal context configuration :                        -->
+  <!--  + contextPath                                                  -->
+  <!--  + war OR resourceBase                                          -->
+  <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+  <Set name="contextPath">/test</Set>
+  <Set name="war"><Property name="jetty.webapps" default="."/>/test.war</Set>
+
+  <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+  <!-- Optional context configuration                                  -->
+  <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+  <Set name="extractWAR">true</Set>
+  <Set name="copyWebDir">false</Set>
+  <Set name="defaultsDescriptor"><Property name="jetty.home" default="."/>/etc/webdefault.xml</Set>
+  <Set name="overrideDescriptor"><Property name="jetty.webapps" default="."/>/test.d/override-web.xml</Set>
+  
+  <!-- Enable WebSocket container -->
+  <Call name="setAttribute">
+    <Arg>org.eclipse.jetty.websocket.jsr356</Arg>
+    <Arg type="Boolean">true</Arg>
+  </Call>
+  
+  <!-- Enable symlinks 
+  <Call name="addAliasCheck">
+    <Arg><New class="org.eclipse.jetty.server.handler.AllowSymLinkAliasChecker"/></Arg>
+  </Call>
+  -->
+  
+  <!-- virtual hosts
+  <Set name="virtualHosts">
+    <Array type="String">
+      <Item>www.MyVirtualDomain.com</Item>
+      <Item>m.MyVirtualDomain.com</Item>
+      <Item>*.OtherVirtualDomain.com</Item>
+      <Item>@ConnectorName</Item>
+      <Item>localhost</Item>
+      <Item>127.0.0.1</Item>
+    </Array>
+  </Set>
+  -->
+
+  <!-- disable cookies
+  <Get name="sessionHandler">
+     <Get name="sessionManager">
+        <Set name="usingCookies" type="boolean">false</Set>
+     </Get>
+  </Get>
+  -->
+
+  <Get name="securityHandler">
+    <Set name="loginService">
+      <New class="org.eclipse.jetty.security.HashLoginService">
+        <Set name="name">Test Realm</Set>
+        <Set name="config"><SystemProperty name="jetty.base" default="."/>/etc/realm.properties</Set>
+            <!-- To enable reload of realm when properties change, uncomment the following lines -->
+            <!-- changing refreshInterval (in seconds) as desired                                -->
+            <!--
+            <Set name="refreshInterval">5</Set>
+            <Call name="start"></Call>
+            -->
+      </New>
+    </Set>
+    <Set name="authenticator">
+      <New class="org.eclipse.jetty.security.authentication.FormAuthenticator">
+        <Set name="alwaysSaveUri">true</Set>
+      </New>
+    </Set>
+    <Set name="checkWelcomeFiles">true</Set>
+  </Get>
+
+  <!-- Non standard error page mapping -->
+  <!--
+  <Get name="errorHandler">
+    <Call name="addErrorPage">
+      <Arg type="int">500</Arg>
+      <Arg type="int">599</Arg>
+      <Arg type="String">/dump/errorCodeRangeMapping</Arg>
+    </Call>
+  </Get>
+  -->
+
+  <!-- Add context specific logger
+  <Set name="handler">
+    <New id="RequestLog" class="org.eclipse.jetty.server.handler.RequestLogHandler">
+      <Set name="requestLog">
+	<New id="RequestLogImpl" class="org.eclipse.jetty.server.NCSARequestLog">
+	  <Set name="filename"><Property name="jetty.logs" default="./logs"/>/test-yyyy_mm_dd.request.log</Set>
+	  <Set name="filenameDateFormat">yyyy_MM_dd</Set>
+	  <Set name="append">true</Set>
+	  <Set name="LogTimeZone">GMT</Set>
+	</New>
+      </Set>
+    </New>
+  </Set>
+  -->
+
+</Configure>
diff --git a/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/ChatServlet.java b/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/ChatServlet.java
new file mode 100644
index 0000000..76f9af3
--- /dev/null
+++ b/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/ChatServlet.java
@@ -0,0 +1,232 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package com.acme;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Queue;
+import java.util.concurrent.atomic.AtomicReference;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.AsyncEvent;
+import javax.servlet.AsyncListener;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+// Simple asynchronous Chat room.
+// This does not handle duplicate usernames or multiple frames/tabs from the same browser
+// Some code is duplicated for clarity.
+ at SuppressWarnings("serial")
+public class ChatServlet extends HttpServlet
+{
+    private static final Logger LOG = Log.getLogger(ChatServlet.class);
+
+    private long asyncTimeout = 10000;
+
+    public void init()
+    {
+        String parameter = getServletConfig().getInitParameter("asyncTimeout");
+        if (parameter != null)
+            asyncTimeout = Long.parseLong(parameter);
+    }
+
+    // inner class to hold message queue for each chat room member
+    class Member implements AsyncListener
+    {
+        final String _name;
+        final AtomicReference<AsyncContext> _async = new AtomicReference<>();
+        final Queue<String> _queue = new LinkedList<>();
+
+        Member(String name)
+        {
+            _name = name;
+        }
+
+        @Override
+        public void onTimeout(AsyncEvent event) throws IOException
+        {
+            LOG.debug("resume request");
+            AsyncContext async = _async.get();
+            if (async != null && _async.compareAndSet(async, null))
+            {
+                HttpServletResponse response = (HttpServletResponse)async.getResponse();
+                response.setContentType("text/json;charset=utf-8");
+                response.getOutputStream().write("{action:\"poll\"}".getBytes());
+                async.complete();
+            }
+        }
+
+        @Override
+        public void onStartAsync(AsyncEvent event) throws IOException
+        {
+            event.getAsyncContext().addListener(this);
+        }
+
+        @Override
+        public void onError(AsyncEvent event) throws IOException
+        {
+        }
+
+        @Override
+        public void onComplete(AsyncEvent event) throws IOException
+        {
+        }
+    }
+
+    Map<String, Map<String, Member>> _rooms = new HashMap<>();
+
+
+    // Handle Ajax calls from browser
+    @Override
+    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+    {
+        // Ajax calls are form encoded
+        boolean join = Boolean.parseBoolean(request.getParameter("join"));
+        String message = request.getParameter("message");
+        String username = request.getParameter("user");
+
+        LOG.debug("doPost called. join={},message={},username={}", join, message, username);
+        if (username == null)
+        {
+            LOG.debug("no paramter user set, sending 503");
+            response.sendError(503, "user==null");
+            return;
+        }
+
+        Map<String, Member> room = getRoom(request.getPathInfo());
+        Member member = getMember(username, room);
+
+        if (message != null)
+        {
+            sendMessageToAllMembers(message, username, room);
+        }
+        // If a message is set, we only want to enter poll mode if the user is a new user. This is necessary to avoid
+        // two parallel requests per user (one is already in async wait and the new one). Sending a message will
+        // dispatch to an existing poll request if necessary and the client will issue a new request to receive the
+        // next message or long poll again.
+        if (message == null || join)
+        {
+            synchronized (member)
+            {
+                LOG.debug("Queue size: {}", member._queue.size());
+                if (member._queue.size() > 0)
+                {
+                    sendSingleMessage(response, member);
+                }
+                else
+                {
+                    LOG.debug("starting async");
+                    AsyncContext async = request.startAsync();
+                    async.setTimeout(asyncTimeout);
+                    async.addListener(member);
+                    member._async.set(async);
+                }
+            }
+        }
+    }
+
+    private Member getMember(String username, Map<String, Member> room)
+    {
+        Member member = room.get(username);
+        if (member == null)
+        {
+            LOG.debug("user: {} in room: {} doesn't exist. Creating new user.", username, room);
+            member = new Member(username);
+            room.put(username, member);
+        }
+        return member;
+    }
+
+    private Map<String, Member> getRoom(String path)
+    {
+        Map<String, Member> room = _rooms.get(path);
+        if (room == null)
+        {
+            LOG.debug("room: {} doesn't exist. Creating new room.", path);
+            room = new HashMap<>();
+            _rooms.put(path, room);
+        }
+        return room;
+    }
+
+    private void sendSingleMessage(HttpServletResponse response, Member member) throws IOException
+    {
+        response.setContentType("text/json;charset=utf-8");
+        StringBuilder buf = new StringBuilder();
+
+        buf.append("{\"from\":\"");
+        buf.append(member._queue.poll());
+        buf.append("\",");
+
+        String returnMessage = member._queue.poll();
+        int quote = returnMessage.indexOf('"');
+        while (quote >= 0)
+        {
+            returnMessage = returnMessage.substring(0, quote) + '\\' + returnMessage.substring(quote);
+            quote = returnMessage.indexOf('"', quote + 2);
+        }
+        buf.append("\"chat\":\"");
+        buf.append(returnMessage);
+        buf.append("\"}");
+        byte[] bytes = buf.toString().getBytes("utf-8");
+        response.setContentLength(bytes.length);
+        response.getOutputStream().write(bytes);
+    }
+
+    private void sendMessageToAllMembers(String message, String username, Map<String, Member> room)
+    {
+        LOG.debug("Sending message: {} from: {}", message, username);
+        for (Member m : room.values())
+        {
+            synchronized (m)
+            {
+                m._queue.add(username); // from
+                m._queue.add(message);  // chat
+
+                // wakeup member if polling
+                AsyncContext async = m._async.get();
+                LOG.debug("Async found: {}", async);
+                if (async != null & m._async.compareAndSet(async, null))
+                {
+                    LOG.debug("dispatch");
+                    async.dispatch();
+                }
+            }
+        }
+    }
+
+    // Serve the HTML with embedded CSS and Javascript.
+    // This should be static content and should use real JS libraries.
+    @Override
+    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+    {
+        if (request.getParameter("action") != null)
+            doPost(request, response);
+        else
+            getServletContext().getNamedDispatcher("default").forward(request, response);
+    }
+
+}
diff --git a/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/CookieDump.java b/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/CookieDump.java
new file mode 100644
index 0000000..665ba89
--- /dev/null
+++ b/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/CookieDump.java
@@ -0,0 +1,142 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package com.acme;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.concurrent.TimeUnit;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+
+/** 
+ * Test Servlet Cookies.
+ */
+ at SuppressWarnings("serial")
+public class CookieDump extends HttpServlet
+{
+    int redirectCount=0;
+
+    /* ------------------------------------------------------------ */
+    protected void handleForm(HttpServletRequest request,
+                          HttpServletResponse response)
+    {
+        String name =  request.getParameter("Name");
+        String value =  request.getParameter("Value");
+        String age =  request.getParameter("Age");
+
+        if (name!=null && name.length()>0)
+        {
+            Cookie cookie = new Cookie(name,value);
+            if (age!=null && age.length()>0)
+                cookie.setMaxAge(Integer.parseInt(age));
+            response.addCookie(cookie);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void doPost(HttpServletRequest request,
+                       HttpServletResponse response)
+        throws ServletException, IOException
+    {
+        handleForm(request,response);
+        String nextUrl = getURI(request)+"?R="+redirectCount++;
+        String encodedUrl=response.encodeRedirectURL(nextUrl);
+        response.sendRedirect(encodedUrl);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void doGet(HttpServletRequest request,
+                      HttpServletResponse response)
+        throws ServletException, IOException
+    {
+        handleForm(request,response);
+
+        response.setContentType("text/html");
+
+
+        PrintWriter out = response.getWriter();
+        out.println("<h1>Cookie Dump Servlet:</h1>");
+
+        Cookie[] cookies = request.getCookies();
+
+        for (int i=0;cookies!=null && i<cookies.length;i++)
+        {
+            out.println("<b>"+deScript(cookies[i].getName())+"</b>="+deScript(cookies[i].getValue())+"<br/>");
+        }
+
+        out.println("<form action=\""+response.encodeURL(getURI(request))+"\" method=\"post\">");
+
+        out.println("<b>Name:</b><input type=\"text\" name=\"Name\" value=\"name\"/><br/>");
+        out.println("<b>Value:</b><input type=\"text\" name=\"Value\" value=\"value\"/><br/>");
+        out.println("<b>Max-Age:</b><input type=\"text\" name=\"Age\" value=\"60\"/><br/>");
+        out.println("<input type=\"submit\" name=\"Action\" value=\"Set\"/>");
+
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String getServletInfo() {
+        return "Session Dump Servlet";
+    }
+
+    /* ------------------------------------------------------------ */
+    private String getURI(HttpServletRequest request)
+    {
+        String uri=(String)request.getAttribute("javax.servlet.forward.request_uri");
+        if (uri==null)
+            uri=request.getRequestURI();
+        return uri;
+    }
+
+    /* ------------------------------------------------------------ */
+    protected String deScript(String string)
+    {
+        if (string==null)
+            return null;
+        string=string.replace("&", "&");
+        string=string.replace( "<", "<");
+        string=string.replace( ">", ">");
+        return string;
+    }
+
+    @Override
+    public void destroy()
+    {
+        // For testing --stop with STOP.WAIT handling of the jetty-start behavior.
+        if (Boolean.getBoolean("test.slow.destroy"))
+        {
+            try
+            {
+                TimeUnit.SECONDS.sleep(10);
+            }
+            catch (InterruptedException e)
+            {
+                // ignore
+            }
+        }
+        super.destroy();
+    }
+}
diff --git a/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/Counter.java b/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/Counter.java
new file mode 100644
index 0000000..0671c80
--- /dev/null
+++ b/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/Counter.java
@@ -0,0 +1,41 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package com.acme;
+
+ at SuppressWarnings("serial")
+public class Counter implements java.io.Serializable
+{
+    int counter=0;
+    String last;
+
+    public int getCount()
+    {
+        counter++;
+        return counter;
+    }
+
+    public void setLast(String uri) {
+        last=uri;
+    }
+
+    public String getLast() {
+        return last;
+    }
+}
+
diff --git a/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/Date2Tag.java b/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/Date2Tag.java
new file mode 100644
index 0000000..dd81a89
--- /dev/null
+++ b/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/Date2Tag.java
@@ -0,0 +1,53 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package com.acme;
+
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.StringTokenizer;
+
+import javax.servlet.jsp.JspContext;
+import javax.servlet.jsp.JspException;
+import javax.servlet.jsp.tagext.JspFragment;
+import javax.servlet.jsp.tagext.SimpleTagSupport;
+
+public class Date2Tag extends SimpleTagSupport
+{
+    String format;
+
+    public void setFormat(String value) {
+        this.format = value;
+    }
+
+    public void doTag() throws JspException, IOException {
+        String formatted =
+            new SimpleDateFormat("long".equals(format)?"EEE 'the' d:MMM:yyyy":"d:MM:yy")
+            .format(new Date());
+        StringTokenizer tok = new StringTokenizer(formatted,":");
+        JspContext context = getJspContext();
+        context.setAttribute("day", tok.nextToken() );
+        context.setAttribute("month", tok.nextToken() );
+        context.setAttribute("year", tok.nextToken() );
+
+        JspFragment fragment = getJspBody();
+        fragment.invoke(null);
+    }
+}
+
diff --git a/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/DateTag.java b/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/DateTag.java
new file mode 100644
index 0000000..826306e
--- /dev/null
+++ b/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/DateTag.java
@@ -0,0 +1,71 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package com.acme;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.TimeZone;
+
+import javax.servlet.jsp.JspException;
+import javax.servlet.jsp.JspTagException;
+import javax.servlet.jsp.PageContext;
+import javax.servlet.jsp.tagext.BodyContent;
+import javax.servlet.jsp.tagext.BodyTagSupport;
+import javax.servlet.jsp.tagext.Tag;
+
+ at SuppressWarnings("serial")
+public class DateTag extends BodyTagSupport
+{
+    Tag parent;
+    BodyContent body;
+    String tz="GMT";
+
+    public void setParent(Tag parent) {this.parent=parent;}
+    public Tag getParent() {return parent;}
+    public void setBodyContent(BodyContent content) {body=content;}
+    public void setPageContext(PageContext pageContext) {}
+
+    public void setTz(String value) {tz=value;}
+
+    public int doStartTag() throws JspException {return EVAL_BODY_BUFFERED;}
+
+    public int doEndTag() throws JspException {return EVAL_PAGE;}
+
+    public void doInitBody() throws JspException {}
+
+    public int doAfterBody() throws JspException {
+        try
+        {
+            SimpleDateFormat format = new SimpleDateFormat(body.getString());
+            format.setTimeZone(TimeZone.getTimeZone(tz));
+            body.getEnclosingWriter().write(format.format(new Date()));
+            return SKIP_BODY;
+        }
+        catch (Exception ex) {
+            ex.printStackTrace();
+            throw new JspTagException(ex.toString());
+        }
+    }
+
+    public void release()
+    {
+        body=null;
+    }
+}
+
diff --git a/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/DispatchServlet.java b/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/DispatchServlet.java
new file mode 100644
index 0000000..a89b6a6
--- /dev/null
+++ b/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/DispatchServlet.java
@@ -0,0 +1,278 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package com.acme;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpServletResponseWrapper;
+
+/* ------------------------------------------------------------ */
+/** Test Servlet RequestDispatcher.
+ *
+ *
+ */
+ at SuppressWarnings("serial")
+public class DispatchServlet extends HttpServlet
+{
+    /* ------------------------------------------------------------ */
+    String pageType;
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void doPost(HttpServletRequest sreq, HttpServletResponse sres) throws ServletException, IOException
+    {
+        doGet(sreq, sres);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void doGet(HttpServletRequest sreq, HttpServletResponse sres) throws ServletException, IOException
+    {
+        if (sreq.getParameter("wrap") != null)
+        {
+            sreq= new HttpServletRequestWrapper(sreq);
+            sres= new HttpServletResponseWrapper(sres);
+        }
+
+        if (sreq.getParameter("session") != null)
+            sreq.getSession(true);
+
+        String prefix=
+            sreq.getContextPath() != null ? sreq.getContextPath() + sreq.getServletPath() : sreq.getServletPath();
+
+        String info;
+
+        if (sreq.getAttribute("javax.servlet.include.servlet_path") != null)
+            info= (String)sreq.getAttribute("javax.servlet.include.path_info");
+        else
+            info= sreq.getPathInfo();
+
+        if (info == null)
+            info= "NULL";
+
+        if (info.indexOf(sreq.getServletPath()) > 0)
+        {
+            sres.sendError(403,"Nested " + sreq.getServletPath() + " forbidden.");
+            return;
+        }
+
+        if(info.indexOf(getServletName()) > 0)
+        {
+            sres.sendError(403,"Nested " + getServletName() + " forbidden.");
+            return;
+        }
+
+        if (info.startsWith("/includeW/"))
+        {
+            sres.setContentType("text/html");
+            info= info.substring(9);
+            if (info.indexOf('?') < 0)
+                info += "?Dispatch=include";
+            else
+                info += "&Dispatch=include";
+
+            PrintWriter pout= null;
+            pout= sres.getWriter();
+            pout.write("<H1>Include (writer): " + info + "</H1><HR>");
+
+            RequestDispatcher dispatch= getServletContext().getRequestDispatcher(info);
+            if (dispatch == null)
+            {
+                pout= sres.getWriter();
+                pout.write("<H1>Null dispatcher</H1>");
+            }
+            else
+                dispatch.include(sreq, sres);
+
+            pout.write("<HR><H1>-- Included (writer)</H1>");
+        }
+        else if (info.startsWith("/includeS/"))
+        {
+            sres.setContentType("text/html");
+            info= info.substring(9);
+            if (info.indexOf('?') < 0)
+                info += "?Dispatch=include";
+            else
+                info += "&Dispatch=include";
+
+            OutputStream out= null;
+            out= sres.getOutputStream();
+            out.write(("<H1>Include (outputstream): " + info + "</H1><HR>").getBytes());
+
+            RequestDispatcher dispatch= getServletContext().getRequestDispatcher(info);
+            if (dispatch == null)
+            {
+                out= sres.getOutputStream();
+                out.write("<H1>Null dispatcher</H1>".getBytes());
+            }
+            else
+                dispatch.include(sreq, sres);
+
+            out.write("<HR><H1>-- Included (outputstream)</H1>".getBytes());
+
+        }
+        else if (info.startsWith("/forward/"))
+        {
+            info= info.substring(8);
+            if (info.indexOf('?') < 0)
+                info += "?Dispatch=forward";
+            else
+                info += "&Dispatch=forward";
+
+            RequestDispatcher dispatch= getServletContext().getRequestDispatcher(info);
+            if (dispatch != null)
+            {
+                ServletOutputStream out =sres.getOutputStream();
+                out.print("Can't see this");
+                dispatch.forward(sreq, sres);
+                try
+                {
+                    // should be closed
+                    out.println("IOException");
+                    // should not get here
+                    throw new IllegalStateException();
+                }
+                catch(IOException e)
+                {
+                    // getServletContext().log("ignore",e);
+                }
+            }
+            else
+            {
+                sres.setContentType("text/html");
+                PrintWriter pout= sres.getWriter();
+                pout.write("<H1>No dispatcher for: " + info + "</H1><HR>");
+                pout.flush();
+            }
+        }
+        else if (info.startsWith("/forwardC/"))
+        {
+            info= info.substring(9);
+            if (info.indexOf('?') < 0)
+                info += "?Dispatch=forward";
+            else
+                info += "&Dispatch=forward";
+
+            String cpath= info.substring(0, info.indexOf('/', 1));
+            info= info.substring(cpath.length());
+
+            ServletContext context= getServletContext().getContext(cpath);
+            RequestDispatcher dispatch= context.getRequestDispatcher(info);
+
+            if (dispatch != null)
+            {
+                dispatch.forward(sreq, sres);
+            }
+            else
+            {
+                sres.setContentType("text/html");
+                PrintWriter pout= sres.getWriter();
+                pout.write("<H1>No dispatcher for: " + cpath + "/" + info + "</H1><HR>");
+                pout.flush();
+            }
+        }
+        else if (info.startsWith("/includeN/"))
+        {
+            sres.setContentType("text/html");
+            info= info.substring(10);
+            if (info.indexOf("/") >= 0)
+                info= info.substring(0, info.indexOf("/"));
+
+            PrintWriter pout;
+            if (info.startsWith("/null"))
+                info= info.substring(5);
+            else
+            {
+                pout= sres.getWriter();
+                pout.write("<H1>Include named: " + info + "</H1><HR>");
+            }
+
+            RequestDispatcher dispatch= getServletContext().getNamedDispatcher(info);
+            if (dispatch != null)
+                dispatch.include(sreq, sres);
+            else
+            {
+                pout= sres.getWriter();
+                pout.write("<H1>No servlet named: " + info + "</H1>");
+            }
+
+            pout= sres.getWriter();
+            pout.write("<HR><H1>Included ");
+        }
+        else if (info.startsWith("/forwardN/"))
+        {
+            info= info.substring(10);
+            if (info.indexOf("/") >= 0)
+                info= info.substring(0, info.indexOf("/"));
+            RequestDispatcher dispatch= getServletContext().getNamedDispatcher(info);
+            if (dispatch != null)
+                dispatch.forward(sreq, sres);
+            else
+            {
+                sres.setContentType("text/html");
+                PrintWriter pout= sres.getWriter();
+                pout.write("<H1>No servlet named: " + info + "</H1>");
+                pout.flush();
+            }
+        }
+        else
+        {
+            sres.setContentType("text/html");
+            PrintWriter pout= sres.getWriter();
+            pout.write(
+                    "<H1>Dispatch URL must be of the form: </H1>"
+                    + "<PRE>"
+                    + prefix
+                    + "/includeW/path\n"
+                    + prefix
+                    + "/includeS/path\n"
+                    + prefix
+                    + "/forward/path\n"
+                    + prefix
+                    + "/includeN/name\n"
+                    + prefix
+                    + "/forwardC/_context/path\n</PRE>");
+        }
+
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String getServletInfo()
+    {
+        return "Include Servlet";
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public synchronized void destroy()
+    {
+    }
+
+}
diff --git a/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/Dump.java b/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/Dump.java
new file mode 100644
index 0000000..6887c10
--- /dev/null
+++ b/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/Dump.java
@@ -0,0 +1,1038 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package com.acme;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.Reader;
+import java.lang.reflect.Array;
+import java.lang.reflect.Field;
+import java.net.URL;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.Locale;
+import java.util.Timer;
+import java.util.TimerTask;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.AsyncEvent;
+import javax.servlet.AsyncListener;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletRequestWrapper;
+import javax.servlet.ServletResponse;
+import javax.servlet.ServletResponseWrapper;
+import javax.servlet.UnavailableException;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpServletResponseWrapper;
+
+
+/** 
+ * Dump Servlet Request.
+ */
+ at SuppressWarnings("serial")
+public class Dump extends HttpServlet
+{
+    boolean fixed;
+    Timer _timer;
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void init(ServletConfig config) throws ServletException
+    {
+        super.init(config);
+
+        if (config.getInitParameter("unavailable")!=null && !fixed)
+        {
+
+            fixed=true;
+            throw new UnavailableException("Unavailable test",Integer.parseInt(config.getInitParameter("unavailable")));
+        }
+
+        _timer=new Timer(true);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+    {
+        doGet(request, response);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+    {
+        byte[] buffer = new byte[8192];
+        int len=request.getContentLength();
+        int c=0;
+        InputStream in=request.getInputStream();
+        while (c<len)
+        {
+            int l = in.read(buffer);
+            if (l<0)
+                break;
+            c+=l;
+        }
+        request.setAttribute("PUT",c+"bytes");
+        doGet(request, response);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void doGet(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException
+    {
+        if (request.getRemoteUser()==null)
+        {
+            try
+            {
+                request.login("user", "password");
+            }
+            catch(ServletException se)
+            {
+                se.printStackTrace();
+            }
+        }
+
+        // Handle a dump of data
+        final String data= request.getParameter("data");
+        final String chars= request.getParameter("chars");
+        final String block= request.getParameter("block");
+        final String dribble= request.getParameter("dribble");
+        final boolean flush= request.getParameter("flush")!=null?Boolean.parseBoolean(request.getParameter("flush")):false;
+
+
+        if(request.getPathInfo()!=null && request.getPathInfo().toLowerCase(Locale.ENGLISH).indexOf("script")!=-1)
+        {
+            response.sendRedirect(response.encodeRedirectURL(getServletContext().getContextPath() + "/dump/info"));
+            return;
+        }
+
+        request.setCharacterEncoding("UTF-8");
+
+        if (request.getParameter("busy")!=null)
+        {
+            long end = System.currentTimeMillis()+Long.parseLong(request.getParameter("busy"));
+            while(System.currentTimeMillis()<end)
+            {}
+        }
+
+        if (request.getParameter("empty")!=null)
+        {
+            response.setStatus(200);
+            response.flushBuffer();
+            return;
+        }
+
+        if (request.getParameter("sleep")!=null)
+        {
+            try
+            {
+                long s = Long.parseLong(request.getParameter("sleep"));
+                if (request.getHeader("Expect")!=null && request.getHeader("Expect").indexOf("102")>=0)
+                {
+                    Thread.sleep(s/2);
+                    response.sendError(102);
+                    Thread.sleep(s/2);
+                }
+                else
+                    Thread.sleep(s);
+            }
+            catch (InterruptedException e)
+            {
+                return;
+            }
+            catch (Exception e)
+            {
+                throw new ServletException(e);
+            }
+        }
+
+        if (request.getParameter("startAsync")!=null && request.getAttribute("ASYNC")!=Boolean.TRUE)
+        {
+            request.setAttribute("ASYNC",Boolean.TRUE);
+            try
+            {
+                final AsyncContext async=request.startAsync(request,response);
+                async.setTimeout(Long.parseLong(request.getParameter("startAsync")));
+                async.addListener(new AsyncListener()
+                {
+                    
+                    @Override
+                    public void onTimeout(AsyncEvent event) throws IOException
+                    {
+                        response.addHeader("Dump","onTimeout");
+                        try
+                        {
+                            if (!dump(response,data,chars,block,dribble,flush))
+                            {
+                                response.setContentType("text/plain");
+                                response.getOutputStream().println("EXPIRED");
+                            }
+                            async.complete();
+                        }
+                        catch (IOException e)
+                        {
+                            getServletContext().log("",e);
+                        }
+                    }
+                    
+                    @Override
+                    public void onStartAsync(AsyncEvent event) throws IOException
+                    {
+                        response.addHeader("Dump","onStartAsync");
+                    }
+                    
+                    @Override
+                    public void onError(AsyncEvent event) throws IOException
+                    {
+                        response.addHeader("Dump","onError");
+                    }
+                    
+                    @Override
+                    public void onComplete(AsyncEvent event) throws IOException
+                    {
+                        response.addHeader("Dump","onComplete");
+                    }
+                });
+
+                if (request.getParameter("dispatch")!=null)
+                {
+                    request.setAttribute("RESUME",Boolean.TRUE);
+
+                    final long resume=Long.parseLong(request.getParameter("dispatch"));
+                    _timer.schedule(new TimerTask()
+                    {
+                        @Override
+                        public void run()
+                        {
+                            async.dispatch();
+                        }
+                    },resume);
+                }
+
+                if (request.getParameter("complete")!=null)
+                {
+                    final long complete=Long.parseLong(request.getParameter("complete"));
+                    _timer.schedule(new TimerTask()
+                    {
+                        @Override
+                        public void run()
+                        {
+                            try
+                            {
+                                response.setContentType("text/html");
+                                response.getOutputStream().println("<h1>COMPLETED</h1>");
+                                async.complete();
+                            }
+                            catch(Exception e)
+                            {
+                                e.printStackTrace();
+                            }
+                        }
+                    },complete);
+                }
+
+                
+                
+                return;
+            }
+            catch(Exception e)
+            {
+                throw new ServletException(e);
+            }
+        }
+
+        request.setAttribute("Dump", this);
+        getServletContext().setAttribute("Dump",this);
+        // getServletContext().log("dump "+request.getRequestURI());
+
+        // Force a content length response
+        String length= request.getParameter("length");
+        if (length != null && length.length() > 0)
+        {
+            response.setContentLength(Integer.parseInt(length));
+        }
+
+        // Handle a dump of data
+        if (dump(response,data,chars,block,dribble,flush))
+            return;
+
+        // handle an exception
+        String info= request.getPathInfo();
+        if (info != null && info.endsWith("Exception"))
+        {
+            try
+            {
+                throw (Throwable) Thread.currentThread().getContextClassLoader().loadClass(info.substring(1)).newInstance();
+            }
+            catch (Throwable th)
+            {
+                throw new ServletException(th);
+            }
+        }
+
+        // test a reset
+        String reset= request.getParameter("reset");
+        if (reset != null && reset.length() > 0)
+        {
+            response.getOutputStream().println("THIS SHOULD NOT BE SEEN!");
+            response.setHeader("SHOULD_NOT","BE SEEN");
+            response.reset();
+        }
+
+
+        // handle an redirect
+        String redirect= request.getParameter("redirect");
+        if (redirect != null && redirect.length() > 0)
+        {
+            response.getOutputStream().println("THIS SHOULD NOT BE SEEN!");
+            response.sendRedirect(response.encodeRedirectURL(redirect));
+            try
+            {
+                response.getOutputStream().println("THIS SHOULD NOT BE SEEN!");
+            }
+            catch(IOException e)
+            {
+                // ignored as stream is closed.
+            }
+            return;
+        }
+
+        // handle an error
+        String error= request.getParameter("error");
+        if (error != null && error.length() > 0 && request.getAttribute("javax.servlet.error.status_code")==null)
+        {
+            response.getOutputStream().println("THIS SHOULD NOT BE SEEN!");
+            response.sendError(Integer.parseInt(error));
+            try
+            {
+                response.getOutputStream().println("THIS SHOULD NOT BE SEEN!");
+            }
+            catch(IllegalStateException e)
+            {
+                try
+                {
+                    response.getWriter().println("NOR THIS!!");
+                }
+                catch(IOException e2){}
+            }
+            catch(IOException e){}
+            return;
+        }
+
+        // Handle a extra headers
+        String headers= request.getParameter("headers");
+        if (headers != null && headers.length() > 0)
+        {
+            long h=Long.parseLong(headers);
+            for (int i=0;i<h;i++)
+                response.addHeader("Header"+i,"Value"+i);
+        }
+
+        String buffer= request.getParameter("buffer");
+        if (buffer != null && buffer.length() > 0)
+            response.setBufferSize(Integer.parseInt(buffer));
+
+        String charset= request.getParameter("charset");
+        if (charset==null)
+            charset="UTF-8";
+        response.setCharacterEncoding(charset);
+        response.setContentType("text/html");
+
+        if (info != null && info.indexOf("Locale/") >= 0)
+        {
+            try
+            {
+                String locale_name= info.substring(info.indexOf("Locale/") + 7);
+                Field f= java.util.Locale.class.getField(locale_name);
+                response.setLocale((Locale)f.get(null));
+            }
+            catch (Exception e)
+            {
+                e.printStackTrace();
+                response.setLocale(Locale.getDefault());
+            }
+        }
+
+        String cn= request.getParameter("cookie");
+        String cv=request.getParameter("cookiev");
+        if (cn!=null && cv!=null)
+        {
+            Cookie cookie= new Cookie(cn, cv);
+            if (request.getParameter("version")!=null)
+                cookie.setVersion(Integer.parseInt(request.getParameter("version")));
+            cookie.setComment("Cookie from dump servlet");
+            response.addCookie(cookie);
+        }
+
+        String pi= request.getPathInfo();
+        if (pi != null && pi.startsWith("/ex"))
+        {
+            OutputStream out= response.getOutputStream();
+            out.write("</H1>This text should be reset</H1>".getBytes());
+            if ("/ex0".equals(pi))
+                throw new ServletException("test ex0", new Throwable());
+            else if ("/ex1".equals(pi))
+                throw new IOException("test ex1");
+            else if ("/ex2".equals(pi))
+                throw new UnavailableException("test ex2");
+            else if (pi.startsWith("/ex3/"))
+                throw new UnavailableException("test ex3",Integer.parseInt(pi.substring(5)));
+            throw new RuntimeException("test");
+        }
+
+        if ("true".equals(request.getParameter("close")))
+            response.setHeader("Connection","close");
+
+        String buffered= request.getParameter("buffered");
+
+        PrintWriter pout=null;
+
+        try
+        {
+            pout =response.getWriter();
+        }
+        catch(IllegalStateException e)
+        {
+            pout=new PrintWriter(new OutputStreamWriter(response.getOutputStream(),charset));
+        }
+        if (buffered!=null)
+            pout = new PrintWriter(new BufferedWriter(pout,Integer.parseInt(buffered)));
+
+        try
+        {
+            pout.write("<html>\n<body>\n");
+            pout.write("<h1>Dump Servlet</h1>\n");
+            pout.write("<table width=\"95%\">");
+            pout.write("<tr>\n");
+            pout.write("<th align=\"right\">getContentLength: </th>");
+            pout.write("<td>"+Integer.toString(request.getContentLength())+"</td>");
+            pout.write("</tr><tr>\n");
+            pout.write("<th align=\"right\">getContentType: </th>");
+            pout.write("<td>"+notag(request.getContentType())+"</td>");
+            pout.write("</tr><tr>\n");
+            pout.write("<th align=\"right\">getContextPath: </th>");
+            pout.write("<td>"+request.getContextPath()+"</td>");
+            pout.write("</tr><tr>\n");
+            pout.write("<th align=\"right\">getDispatcherType: </th>");
+            pout.write("<td>"+request.getDispatcherType()+"</td>");
+            pout.write("</tr><tr>\n");
+            pout.write("<th align=\"right\">getLocale: </th>");
+            pout.write("<td>"+request.getLocale()+"</td>");
+            pout.write("</tr><tr>\n");
+            pout.write("<th align=\"right\">getLocalName: </th>");
+            pout.write("<td>"+request.getLocalName()+"</td>");
+            pout.write("</tr><tr>\n");
+            pout.write("<th align=\"right\">getLocalAddr: </th>");
+            pout.write("<td>"+request.getLocalAddr()+"</td>");
+            pout.write("</tr><tr>\n");
+            pout.write("<th align=\"right\">getLocalPort: </th>");
+            pout.write("<td>"+Integer.toString(request.getLocalPort())+"</td>");
+            pout.write("</tr><tr>\n");
+            pout.write("<th align=\"right\">getMethod: </th>");
+            pout.write("<td>" + notag(request.getMethod())+"</td>");
+            pout.write("</tr><tr>\n");
+            pout.write("<th align=\"right\">getPathInfo: </th>");
+            pout.write("<td>"+notag(request.getPathInfo())+"</td>");
+            pout.write("</tr><tr>\n");
+            pout.write("<th align=\"right\">getPathTranslated: </th>");
+            pout.write("<td>"+notag(request.getPathTranslated())+"</td>");
+            pout.write("</tr><tr>\n");
+            pout.write("<th align=\"right\">getProtocol: </th>");
+            pout.write("<td>"+request.getProtocol()+"</td>");
+            pout.write("</tr><tr>\n");
+            pout.write("<th align=\"right\">getQueryString: </th>");
+            pout.write("<td>"+notag(request.getQueryString())+"</td>");
+            pout.write("</tr><tr>\n");
+            pout.write("<th align=\"right\">getRemoteAddr: </th>");
+            pout.write("<td>"+request.getRemoteAddr()+"</td>");
+            pout.write("</tr><tr>\n");
+            pout.write("<th align=\"right\">getRemoteHost: </th>");
+            pout.write("<td>"+request.getRemoteHost()+"</td>");
+            pout.write("</tr><tr>\n");
+            pout.write("<th align=\"right\">getRemotePort: </th>");
+            pout.write("<td>"+request.getRemotePort()+"</td>");
+            pout.write("</tr><tr>\n");
+            pout.write("<th align=\"right\">getRemoteUser: </th>");
+            pout.write("<td>"+request.getRemoteUser()+"</td>");
+            pout.write("</tr><tr>\n");
+            pout.write("<th align=\"right\">getRequestedSessionId: </th>");
+            pout.write("<td>"+request.getRequestedSessionId()+"</td>");
+            pout.write("</tr><tr>\n");
+            pout.write("<th align=\"right\">getRequestURI: </th>");
+            pout.write("<td>"+notag(request.getRequestURI())+"</td>");
+            pout.write("</tr><tr>\n");
+            pout.write("<th align=\"right\">getRequestURL: </th>");
+            pout.write("<td>"+notag(request.getRequestURL().toString())+"</td>");
+            pout.write("</tr><tr>\n");
+            pout.write("<th align=\"right\">getScheme: </th>");
+            pout.write("<td>"+request.getScheme()+"</td>");
+            pout.write("</tr><tr>\n");
+            pout.write("<th align=\"right\">getServerName: </th>");
+            pout.write("<td>"+notag(request.getServerName())+"</td>");
+            pout.write("</tr><tr>\n");
+            pout.write("<th align=\"right\">getServletPath: </th>");
+            pout.write("<td>"+notag(request.getServletPath())+"</td>");
+            pout.write("</tr><tr>\n");
+            pout.write("<th align=\"right\">getServerPort: </th>");
+            pout.write("<td>"+Integer.toString(request.getServerPort())+"</td>");
+            pout.write("</tr><tr>\n");
+            pout.write("<th align=\"right\">getUserPrincipal: </th>");
+            pout.write("<td>"+request.getUserPrincipal()+"</td>");
+            pout.write("</tr><tr>\n");
+            pout.write("<th align=\"right\">isAsyncStarted(): </th>");
+            pout.write("<td>"+request.isAsyncStarted()+"</td>");
+            pout.write("</tr><tr>\n");
+            pout.write("<th align=\"right\">isAsyncSupported(): </th>");
+            pout.write("<td>"+request.isAsyncSupported()+"</td>");
+            pout.write("</tr><tr>\n");
+            pout.write("<th align=\"right\">isSecure(): </th>");
+            pout.write("<td>"+request.isSecure()+"</td>");
+            pout.write("</tr><tr>\n");
+            pout.write("<th align=\"right\">isUserInRole(admin): </th>");
+            pout.write("<td>"+request.isUserInRole("admin")+"</td>");
+            pout.write("</tr><tr>\n");
+            pout.write("<th align=\"right\">encodeRedirectURL(/foo?bar): </th>");
+            pout.write("<td>"+response.encodeRedirectURL("/foo?bar")+"</td>");
+
+
+            Enumeration<Locale> locales= request.getLocales();
+            while (locales.hasMoreElements())
+            {
+                pout.write("</tr><tr>\n");
+                pout.write("<th align=\"right\">getLocales: </th>");
+                pout.write("<td>"+locales.nextElement()+"</td>");
+            }
+            pout.write("</tr><tr>\n");
+
+            pout.write("<th align=\"left\" colspan=\"2\"><big><br/>Other HTTP Headers:</big></th>");
+            Enumeration<String> h= request.getHeaderNames();
+            String name;
+            while (h.hasMoreElements())
+            {
+                name= (String)h.nextElement();
+
+                Enumeration<String> h2= request.getHeaders(name);
+                while (h2.hasMoreElements())
+                {
+                    String hv= (String)h2.nextElement();
+                    pout.write("</tr><tr>\n");
+                    pout.write("<th align=\"right\">"+notag(name)+": </th>");
+                    pout.write("<td>"+notag(hv)+"</td>");
+                }
+            }
+
+            pout.write("</tr><tr>\n");
+            pout.write("<th align=\"left\" colspan=\"2\"><big><br/>Request Parameters:</big></th>");
+            h= request.getParameterNames();
+            while (h.hasMoreElements())
+            {
+                name= (String)h.nextElement();
+                pout.write("</tr><tr>\n");
+                pout.write("<th align=\"right\">"+notag(name)+": </th>");
+                pout.write("<td>"+notag(request.getParameter(name))+"</td>");
+                String[] values= request.getParameterValues(name);
+                if (values == null)
+                {
+                    pout.write("</tr><tr>\n");
+                    pout.write("<th align=\"right\">"+notag(name)+" Values: </th>");
+                    pout.write("<td>"+"NULL!"+"</td>");
+                }
+                else if (values.length > 1)
+                {
+                    for (int i= 0; i < values.length; i++)
+                    {
+                        pout.write("</tr><tr>\n");
+                        pout.write("<th align=\"right\">"+notag(name)+"["+i+"]: </th>");
+                        pout.write("<td>"+notag(values[i])+"</td>");
+                    }
+                }
+            }
+
+            pout.write("</tr><tr>\n");
+            pout.write("<th align=\"left\" colspan=\"2\"><big><br/>Cookies:</big></th>");
+            Cookie[] cookies = request.getCookies();
+            for (int i=0; cookies!=null && i<cookies.length;i++)
+            {
+                Cookie cookie = cookies[i];
+
+                pout.write("</tr><tr>\n");
+                pout.write("<th align=\"right\">"+notag(cookie.getName())+": </th>");
+                pout.write("<td>"+notag(cookie.getValue())+"</td>");
+            }
+
+            String content_type=request.getContentType();
+            if (content_type!=null &&
+                !content_type.startsWith("application/x-www-form-urlencoded") &&
+                !content_type.startsWith("multipart/form-data"))
+            {
+                pout.write("</tr><tr>\n");
+                pout.write("<th align=\"left\" valign=\"top\" colspan=\"2\"><big><br/>Content:</big></th>");
+                pout.write("</tr><tr>\n");
+                pout.write("<td><pre>");
+                char[] content= new char[4096];
+                int len;
+                try{
+                    Reader in=request.getReader();
+
+                    while((len=in.read(content))>=0)
+                        pout.write(notag(new String(content,0,len)));
+                }
+                catch(IOException e)
+                {
+                    pout.write(e.toString());
+                }
+
+                pout.write("</pre></td>");
+            }
+
+            pout.write("</tr><tr>\n");
+            pout.write("<th align=\"left\" colspan=\"2\"><big><br/>Request Attributes:</big></th>");
+            Enumeration<String> a= request.getAttributeNames();
+            while (a.hasMoreElements())
+            {
+                name= (String)a.nextElement();
+                pout.write("</tr><tr>\n");
+                pout.write("<th align=\"right\" valign=\"top\">"+name.replace("."," .")+": </th>");
+                Object value=request.getAttribute(name);
+                if (value instanceof File)
+                {
+                    File file = (File)value;
+                    pout.write("<td>"+"<pre>" + file.getName()+" ("+file.length()+" "+new Date(file.lastModified())+ ")</pre>"+"</td>");
+                }
+                else
+                    pout.write("<td>"+"<pre>" + toString(request.getAttribute(name)) + "</pre>"+"</td>");
+            }
+            request.setAttribute("org.eclipse.jetty.servlet.MultiPartFilter.files",null);
+
+
+            pout.write("</tr><tr>\n");
+            pout.write("<th align=\"left\" colspan=\"2\"><big><br/>Servlet InitParameters:</big></th>");
+            a= getInitParameterNames();
+            while (a.hasMoreElements())
+            {
+                name= (String)a.nextElement();
+                pout.write("</tr><tr>\n");
+                pout.write("<th align=\"right\">"+name+": </th>");
+                pout.write("<td>"+ toString(getInitParameter(name)) +"</td>");
+            }
+
+            pout.write("</tr><tr>\n");
+            pout.write("<th align=\"left\" colspan=\"2\"><big><br/>Context InitParameters:</big></th>");
+            a= getServletContext().getInitParameterNames();
+            while (a.hasMoreElements())
+            {
+                name= (String)a.nextElement();
+                pout.write("</tr><tr>\n");
+                pout.write("<th align=\"right\" valign=\"top\">"+name.replace("."," .")+": </th>");
+                pout.write("<td>"+ toString(getServletContext().getInitParameter(name)) + "</td>");
+            }
+
+            pout.write("</tr><tr>\n");
+            pout.write("<th align=\"left\" colspan=\"2\"><big><br/>Context Attributes:</big></th>");
+            a= getServletContext().getAttributeNames();
+            while (a.hasMoreElements())
+            {
+                name= (String)a.nextElement();
+                pout.write("</tr><tr>\n");
+                pout.write("<th align=\"right\" valign=\"top\">"+name.replace("."," .")+": </th>");
+                pout.write("<td>"+"<pre>" + toString(getServletContext().getAttribute(name)) + "</pre>"+"</td>");
+            }
+
+            String res= request.getParameter("resource");
+            if (res != null && res.length() > 0)
+            {
+                pout.write("</tr><tr>\n");
+                pout.write("<th align=\"left\" colspan=\"2\"><big><br/>Get Resource: \""+res+"\"</big></th>");
+
+                pout.write("</tr><tr>\n");
+                pout.write("<th align=\"right\">getServletContext().getResource(...): </th>");
+                try{pout.write("<td>"+getServletContext().getResource(res)+"</td>");}
+                catch(Exception e) {pout.write("<td>"+"" +e+"</td>");}
+
+                pout.write("</tr><tr>\n");
+                pout.write("<th align=\"right\">getServletContext().getResourcePaths(...): </th>");
+                try{pout.write("<td>"+getServletContext().getResourcePaths(res)+"</td>");}
+                catch(Exception e) {pout.write("<td>"+"" +e+"</td>");}
+
+                pout.write("</tr><tr>\n");
+                pout.write("<th align=\"right\">getServletContext().getRealPath(...): </th>");
+                try{pout.write("<td>"+getServletContext().getRealPath(res)+"</td>");}
+                catch(Exception e) {pout.write("<td>"+"" +e+"</td>");}
+
+                pout.write("</tr><tr>\n");
+                pout.write("<th align=\"right\">getServletContext().getContext(...): </th>");
+
+                ServletContext context = getServletContext().getContext(res);
+                pout.write("<td>"+context+"</td>");
+
+                if (context!=null)
+                {
+                    pout.write("</tr><tr>\n");
+                    pout.write("<th align=\"right\">getServletContext().getContext(...).getResource(...): </th>");
+                    try{pout.write("<td>"+context.getResource(res)+"</td>");}
+                    catch(Exception e) {pout.write("<td>"+"" +e+"</td>");}
+
+                    pout.write("</tr><tr>\n");
+                    pout.write("<th align=\"right\">getServletContext().getContext(...).getResourcePaths(...): </th>");
+                    try{pout.write("<td>"+context.getResourcePaths(res)+"</td>");}
+                    catch(Exception e) {pout.write("<td>"+"" +e+"</td>");}
+
+                    String cp=context.getContextPath();
+                    if (cp==null || "/".equals(cp))
+                        cp="";
+                    pout.write("</tr><tr>\n");
+                    pout.write("<th align=\"right\">getServletContext().getContext(...).getRequestDispatcher(...): </th>");
+                    pout.write("<td>"+context.getRequestDispatcher(res.substring(cp.length()))+"</td>");
+                    
+                    pout.write("</tr><tr>\n");
+                    pout.write("<th align=\"right\">getServletContext().getContext(...).getRealPath(...): </th>");
+                    pout.write("<td>"+context.getRealPath(res.substring(cp.length()))+"</td>");
+                }
+
+                pout.write("</tr><tr>\n");
+                pout.write("<th align=\"right\">this.getClass().getResource(...): </th>");
+                pout.write("<td>"+this.getClass().getResource(res)+"</td>");
+
+                pout.write("</tr><tr>\n");
+                pout.write("<th align=\"right\">this.getClass().getClassLoader().getResource(...): </th>");
+                pout.write("<td>"+this.getClass().getClassLoader().getResource(res)+"</td>");
+
+                pout.write("</tr><tr>\n");
+                pout.write("<th align=\"right\">Thread.currentThread().getContextClassLoader().getResource(...): </th>");
+                pout.write("<td>"+Thread.currentThread().getContextClassLoader().getResource(res)+"</td>");
+                pout.write("</tr><tr>\n");
+                pout.write("<th align=\"right\">Thread.currentThread().getContextClassLoader().getResources(...): </th>");
+                Enumeration<URL> urls = Thread.currentThread().getContextClassLoader().getResources(res);
+                if (urls==null)
+                    pout.write("<td>null</td>");
+                else
+                    pout.write("<td>"+Collections.list(urls)+"</td>");
+
+            }
+
+            pout.write("</tr></table>\n");
+
+            /* ------------------------------------------------------------ */
+            pout.write("<h2>Request Wrappers</h2>\n");
+            ServletRequest rw=request;
+            int w=0;
+            while (rw !=null)
+            {
+                pout.write((w++)+": "+rw.getClass().getName()+"<br/>");
+                if (rw instanceof HttpServletRequestWrapper)
+                    rw=((HttpServletRequestWrapper)rw).getRequest();
+                else if (rw instanceof ServletRequestWrapper)
+                    rw=((ServletRequestWrapper)rw).getRequest();
+                else
+                    rw=null;
+            }
+
+            /* ------------------------------------------------------------ */
+            pout.write("<h2>Response Wrappers</h2>\n");
+            ServletResponse rsw=response;
+            w=0;
+            while (rsw !=null)
+            {
+                pout.write((w++)+": "+rsw.getClass().getName()+"<br/>");
+                if (rsw instanceof HttpServletResponseWrapper)
+                    rsw=((HttpServletResponseWrapper)rsw).getResponse();
+                else if (rsw instanceof ServletResponseWrapper)
+                    rsw=((ServletResponseWrapper)rsw).getResponse();
+                else
+                    rsw=null;
+            }
+
+            pout.write("<br/>");
+            pout.write("<h2>International Characters (UTF-8)</h2>");
+            pout.write("LATIN LETTER SMALL CAPITAL AE<br/>\n");
+            pout.write("Directly uni encoded(\\u1d01): \u1d01<br/>");
+            pout.write("HTML reference (&AElig;): Æ<br/>");
+            pout.write("Decimal (&#7425;): ᴁ<br/>");
+            pout.write("Javascript unicode (\\u1d01) : <script language='javascript'>document.write(\"\u1d01\");</script><br/>");
+            pout.write("<br/>");
+            pout.write("<h2>Form to generate GET content</h2>");
+            pout.write("<form method=\"GET\" action=\""+response.encodeURL(getURI(request))+"\">");
+            pout.write("TextField: <input type=\"text\" name=\"TextField\" value=\"value\"/><br/>\n");
+            pout.write("<input type=\"submit\" name=\"Action\" value=\"Submit\">");
+            pout.write("</form>");
+
+            pout.write("<br/>");
+
+            pout.write("<h2>Form to generate POST content</h2>");
+            pout.write("<form method=\"POST\" accept-charset=\"utf-8\" action=\""+response.encodeURL(getURI(request))+"\">");
+            pout.write("TextField: <input type=\"text\" name=\"TextField\" value=\"value\"/><br/>\n");
+            pout.write("Select: <select multiple name=\"Select\">\n");
+            pout.write("<option>ValueA</option>");
+            pout.write("<option>ValueB1,ValueB2</option>");
+            pout.write("<option>ValueC</option>");
+            pout.write("</select><br/>");
+            pout.write("<input type=\"submit\" name=\"Action\" value=\"Submit\"><br/>");
+            pout.write("</form>");
+            pout.write("<br/>");
+
+            pout.write("<h2>Form to generate UPLOAD content</h2>");
+            pout.write("<form method=\"POST\" enctype=\"multipart/form-data\" accept-charset=\"utf-8\" action=\""+
+                    response.encodeURL(getURI(request))+(request.getQueryString()==null?"":("?"+request.getQueryString()))+
+                    "\">");
+            pout.write("TextField: <input type=\"text\" name=\"TextField\" value=\"comment\"/><br/>\n");
+            pout.write("File 1: <input type=\"file\" name=\"file1\" /><br/>\n");
+            pout.write("File 2: <input type=\"file\" name=\"file2\" /><br/>\n");
+            pout.write("<input type=\"submit\" name=\"Action\" value=\"Submit\"><br/>");
+            pout.write("</form>");
+
+            pout.write("<h2>Form to set Cookie</h2>");
+            pout.write("<form method=\"POST\" action=\""+response.encodeURL(getURI(request))+"\">");
+            pout.write("cookie: <input type=\"text\" name=\"cookie\" /><br/>\n");
+            pout.write("value: <input type=\"text\" name=\"cookiev\" /><br/>\n");
+            pout.write("<input type=\"submit\" name=\"Action\" value=\"setCookie\">");
+            pout.write("</form>\n");
+
+            pout.write("<h2>Form to get Resource</h2>");
+            pout.write("<form method=\"POST\" action=\""+response.encodeURL(getURI(request))+"\">");
+            pout.write("resource: <input type=\"text\" name=\"resource\" /><br/>\n");
+            pout.write("<input type=\"submit\" name=\"Action\" value=\"getResource\">");
+            pout.write("</form>\n");
+        }
+        catch (Exception e)
+        {
+            getServletContext().log("dump "+e);
+        }
+
+        String lines= request.getParameter("lines");
+        if (lines!=null)
+        {
+            char[] line = "<span>A line of characters. Blah blah blah blah.  blooble blooble</span></br>\n".toCharArray();
+            for (int l=Integer.parseInt(lines);l-->0;)
+            {
+                pout.write("<span>"+l+" </span>");
+                pout.write(line);
+            }
+        }
+
+        pout.write("</body>\n</html>\n");
+
+        pout.close();
+
+        if (pi != null)
+        {
+            if ("/ex4".equals(pi))
+                throw new ServletException("test ex4", new Throwable());
+            if ("/ex5".equals(pi))
+                throw new IOException("test ex5");
+            if ("/ex6".equals(pi))
+                throw new UnavailableException("test ex6");
+        }
+
+
+    }
+
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String getServletInfo()
+    {
+        return "Dump Servlet";
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public synchronized void destroy()
+    {
+        _timer.cancel();
+    }
+
+    /* ------------------------------------------------------------ */
+    private String getURI(HttpServletRequest request)
+    {
+        String uri= (String)request.getAttribute("javax.servlet.forward.request_uri");
+        if (uri == null)
+            uri= request.getRequestURI();
+        return uri;
+    }
+
+    /* ------------------------------------------------------------ */
+    private static String toString(Object o)
+    {
+        if (o == null)
+            return null;
+
+        try
+        {
+            if (o.getClass().isArray())
+            {
+                StringBuffer sb = new StringBuffer();
+                if (!o.getClass().getComponentType().isPrimitive())
+                {
+                    Object[] array= (Object[])o;
+                    for (int i= 0; i < array.length; i++)
+                    {
+                        if (i > 0)
+                            sb.append("\n");
+                        sb.append(array.getClass().getComponentType().getName());
+                        sb.append("[");
+                        sb.append(i);
+                        sb.append("]=");
+                        sb.append(toString(array[i]));
+                    }
+                    return sb.toString();
+                }
+                else
+                {
+                    int length = Array.getLength(o);
+                    for (int i=0;i<length;i++)
+                    {
+                        if (i > 0)
+                            sb.append("\n");
+                        sb.append(o.getClass().getComponentType().getName());
+                        sb.append("[");
+                        sb.append(i);
+                        sb.append("]=");
+                        sb.append(toString(Array.get(o, i)));
+                    }
+                    return sb.toString();
+                }
+            }
+            else
+                return o.toString();
+        }
+        catch (Exception e)
+        {
+            return e.toString();
+        }
+    }
+
+    private boolean dump(HttpServletResponse response, String data, String chars, String block, String dribble, boolean flush) throws IOException
+    {
+        if (data != null && data.length() > 0)
+        {
+            long d=Long.parseLong(data);
+            int b=(block!=null&&block.length()>0)?Integer.parseInt(block):50;
+            byte[] buf=new byte[b];
+            for (int i=0;i<b;i++)
+            {
+
+                buf[i]=(byte)('0'+(i%10));
+                if (i%10==9)
+                    buf[i]=(byte)'\n';
+            }
+            buf[0]='o';
+            OutputStream out=response.getOutputStream();
+            response.setContentType("text/plain");
+            while (d > 0)
+            {
+                if (b==1)
+                {
+                    out.write(d%80==0?'\n':'.');
+                    d--;
+                }
+                else if (d>=b)
+                {
+                    out.write(buf);
+                    d=d-b;
+                }
+                else
+                {
+                    out.write(buf,0,(int)d);
+                    d=0;
+                }
+
+                if (dribble!=null)
+                {
+                    out.flush();
+                    try
+                    {
+                        Thread.sleep(Long.parseLong(dribble));
+                    }
+                    catch (Exception e)
+                    {
+                        e.printStackTrace();
+                        break;
+                    }
+                }
+
+            }
+
+            if (flush)
+                out.flush();
+
+            return true;
+        }
+
+        // Handle a dump of data
+        if (chars != null && chars.length() > 0)
+        {
+            long d=Long.parseLong(chars);
+            int b=(block!=null&&block.length()>0)?Integer.parseInt(block):50;
+            char[] buf=new char[b];
+            for (int i=0;i<b;i++)
+            {
+                buf[i]=(char)('0'+(i%10));
+                if (i%10==9)
+                    buf[i]='\n';
+            }
+            buf[0]='o';
+            response.setContentType("text/plain");
+            PrintWriter out=response.getWriter();
+            while (d > 0 && !out.checkError())
+            {
+                if (b==1)
+                {
+                    out.write(d%80==0?'\n':'.');
+                    d--;
+                }
+                else if (d>=b)
+                {
+                    out.write(buf);
+                    d=d-b;
+                }
+                else
+                {
+                    out.write(buf,0,(int)d);
+                    d=0;
+                }
+            }
+            return true;
+        }
+        return false;
+    }
+
+    private String notag(String s)
+    {
+        if (s==null)
+            return "null";
+        s=s.replaceAll("&","&");
+        s=s.replaceAll("<","<");
+        s=s.replaceAll(">",">");
+        return s;
+    }
+}
diff --git a/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/HelloWorld.java b/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/HelloWorld.java
new file mode 100644
index 0000000..70df2af
--- /dev/null
+++ b/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/HelloWorld.java
@@ -0,0 +1,61 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package com.acme;
+
+import java.io.IOException;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/** 
+ * Dump Servlet Request.
+ */
+ at SuppressWarnings("serial")
+public class HelloWorld extends HttpServlet
+{
+    /* ------------------------------------------------------------ */
+    @Override
+    public void init(ServletConfig config) throws ServletException
+    {
+        super.init(config);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+    {
+        doGet(request, response);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+    {
+        response.setContentType("text/html");
+        ServletOutputStream out = response.getOutputStream();
+        out.println("<html>");
+        out.println("<h1>Hello World</h1>");
+        out.println("</html>");
+        out.flush();
+    }
+}
diff --git a/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/JavaxWebSocketChat.java b/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/JavaxWebSocketChat.java
new file mode 100644
index 0000000..3187d81
--- /dev/null
+++ b/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/JavaxWebSocketChat.java
@@ -0,0 +1,88 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package com.acme;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import javax.websocket.CloseReason;
+import javax.websocket.OnClose;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.RemoteEndpoint;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+ at ServerEndpoint(value="/javax.websocket/", subprotocols={"chat"})
+public class JavaxWebSocketChat
+{
+    private static final List<JavaxWebSocketChat> members = new CopyOnWriteArrayList<>();
+    
+    volatile Session session;
+    volatile RemoteEndpoint.Async remote;
+    
+    @OnOpen
+    public void onOpen(Session sess)
+    {
+        this.session = sess;
+        this.remote = this.session.getAsyncRemote();
+        members.add(this);
+    }
+    
+    @OnMessage
+    public void onMessage(String data)
+    {
+        if(data.contains("disconnect"))
+        {
+            try
+            {
+                session.close();
+            }
+            catch (IOException ignore)
+            {
+                /* ignore */
+            }
+            return;
+        }
+        
+        ListIterator<JavaxWebSocketChat> iter = members.listIterator();
+        while(iter.hasNext())
+        {
+            JavaxWebSocketChat member = iter.next();
+            
+            // Test if member is now disconnected
+            if(!member.session.isOpen())
+            {
+                iter.remove();
+                continue;
+            }
+            
+            // Async write the message back
+            member.remote.sendText(data);
+        }
+    }
+    
+    @OnClose
+    public void onClose(CloseReason reason)
+    {
+        members.remove(this);
+    }
+}
diff --git a/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/LoginServlet.java b/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/LoginServlet.java
new file mode 100644
index 0000000..1450bad
--- /dev/null
+++ b/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/LoginServlet.java
@@ -0,0 +1,80 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package com.acme;
+
+import java.io.IOException;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+
+/* ------------------------------------------------------------ */
+/** Dump Servlet Request.
+ * 
+ */
+public class LoginServlet extends HttpServlet
+{
+    /* ------------------------------------------------------------ */
+    @Override
+    public void init(ServletConfig config) throws ServletException
+    {
+        super.init(config);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+    {
+        doGet(request, response);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+    {
+        response.setContentType("text/html");
+        ServletOutputStream out = response.getOutputStream();
+        out.println("<html>");
+        out.println("<br/>Before getUserPrincipal="+request.getUserPrincipal());
+        out.println("<br/>Before getRemoteUser="+request.getRemoteUser());
+        String param = request.getParameter("action");
+
+        if ("login".equals(param))
+        {
+              request.login("jetty", "jetty");
+        }
+        else if ("logout".equals(param))
+        {
+             request.logout();
+        }
+        else if ("wrong".equals(param))
+        {
+             request.login("jetty", "123");
+        }  
+
+        out.println("<br/>After getUserPrincipal="+request.getUserPrincipal());
+        out.println("<br/>After getRemoteUser="+request.getRemoteUser());
+        out.println("</html>");
+        out.flush();
+    }
+}
diff --git a/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/RegTest.java b/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/RegTest.java
new file mode 100644
index 0000000..8707bdf
--- /dev/null
+++ b/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/RegTest.java
@@ -0,0 +1,194 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package com.acme;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.util.StringUtil;
+
+
+
+
+/* ------------------------------------------------------------ */
+/** Rego Servlet - tests being accessed from servlet 3.0 programmatic 
+ * configuration.
+ * 
+ */
+public class RegTest extends HttpServlet
+{
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public void init(ServletConfig config) throws ServletException
+    {
+        super.init(config);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+    {
+        doGet(request, response);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void doGet(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException
+    { 
+        request.setCharacterEncoding("UTF-8");        
+        PrintWriter pout=null;
+        
+        try
+        {
+            pout =response.getWriter();
+        }
+        catch(IllegalStateException e)
+        {
+            pout=new PrintWriter(new OutputStreamWriter(response.getOutputStream(),"UTF-8"));
+        }
+   
+        try
+        {
+            pout.write("<html>\n<body>\n");
+            pout.write("<h1>Rego Servlet</h1>\n");
+            pout.write("<table width=\"95%\">");
+            pout.write("<tr>\n");
+            pout.write("<th align=\"right\">getMethod: </th>");
+            pout.write("<td>" + notag(request.getMethod())+"</td>");
+            pout.write("</tr><tr>\n");
+            pout.write("<th align=\"right\">getContentLength: </th>");
+            pout.write("<td>"+Integer.toString(request.getContentLength())+"</td>");
+            pout.write("</tr><tr>\n");
+            pout.write("<th align=\"right\">getContentType: </th>");
+            pout.write("<td>"+notag(request.getContentType())+"</td>");
+            pout.write("</tr><tr>\n");
+            pout.write("<th align=\"right\">getRequestURI: </th>");
+            pout.write("<td>"+notag(request.getRequestURI())+"</td>");
+            pout.write("</tr><tr>\n");
+            pout.write("<th align=\"right\">getRequestURL: </th>");
+            pout.write("<td>"+notag(request.getRequestURL().toString())+"</td>");
+            pout.write("</tr><tr>\n");
+            pout.write("<th align=\"right\">getContextPath: </th>");
+            pout.write("<td>"+request.getContextPath()+"</td>");
+            pout.write("</tr><tr>\n");
+            pout.write("<th align=\"right\">getServletPath: </th>");
+            pout.write("<td>"+notag(request.getServletPath())+"</td>");
+            pout.write("</tr><tr>\n");
+            pout.write("<th align=\"right\">getPathInfo: </th>");
+            pout.write("<td>"+notag(request.getPathInfo())+"</td>");
+            pout.write("</tr><tr>\n");
+            pout.write("<th align=\"right\">getPathTranslated: </th>");
+            pout.write("<td>"+notag(request.getPathTranslated())+"</td>");
+            pout.write("</tr><tr>\n");
+            pout.write("<th align=\"right\">getQueryString: </th>");
+            pout.write("<td>"+notag(request.getQueryString())+"</td>");
+            pout.write("</tr><tr>\n");
+            
+            pout.write("<th align=\"right\">getProtocol: </th>");
+            pout.write("<td>"+request.getProtocol()+"</td>");
+            pout.write("</tr><tr>\n");
+            pout.write("<th align=\"right\">getScheme: </th>");
+            pout.write("<td>"+request.getScheme()+"</td>");
+            pout.write("</tr><tr>\n");
+            pout.write("<th align=\"right\">getServerName: </th>");
+            pout.write("<td>"+notag(request.getServerName())+"</td>");
+            pout.write("</tr><tr>\n");
+            pout.write("<th align=\"right\">getServerPort: </th>");
+            pout.write("<td>"+Integer.toString(request.getServerPort())+"</td>");
+            pout.write("</tr><tr>\n");
+            pout.write("<th align=\"right\">getLocalName: </th>");
+            pout.write("<td>"+request.getLocalName()+"</td>");
+            pout.write("</tr><tr>\n");
+            pout.write("<th align=\"right\">getLocalAddr: </th>");
+            pout.write("<td>"+request.getLocalAddr()+"</td>");
+            pout.write("</tr><tr>\n");
+            pout.write("<th align=\"right\">getLocalPort: </th>");
+            pout.write("<td>"+Integer.toString(request.getLocalPort())+"</td>");
+            pout.write("</tr><tr>\n");
+            pout.write("<th align=\"right\">getRemoteUser: </th>");
+            pout.write("<td>"+request.getRemoteUser()+"</td>");
+            pout.write("</tr><tr>\n");
+            pout.write("<th align=\"right\">getUserPrincipal: </th>");
+            pout.write("<td>"+request.getUserPrincipal()+"</td>");
+            pout.write("</tr><tr>\n");
+            pout.write("<th align=\"right\">getRemoteAddr: </th>");
+            pout.write("<td>"+request.getRemoteAddr()+"</td>");
+            pout.write("</tr><tr>\n");
+            pout.write("<th align=\"right\">getRemoteHost: </th>");
+            pout.write("<td>"+request.getRemoteHost()+"</td>");
+            pout.write("</tr><tr>\n");
+            pout.write("<th align=\"right\">getRemotePort: </th>");
+            pout.write("<td>"+request.getRemotePort()+"</td>");
+            pout.write("</tr><tr>\n");
+            pout.write("<th align=\"right\">getRequestedSessionId: </th>");
+            pout.write("<td>"+request.getRequestedSessionId()+"</td>");
+            pout.write("</tr><tr>\n");
+            pout.write("<th align=\"right\">isSecure(): </th>");
+            pout.write("<td>"+request.isSecure()+"</td>");
+
+            pout.write("</tr><tr>\n");
+            pout.write("<th align=\"right\">isUserInRole(admin): </th>");
+            pout.write("<td>"+request.isUserInRole("admin")+"</td>");
+
+            pout.write("</tr></table>");
+   
+        }
+        catch (Exception e)
+        {
+            getServletContext().log("dump "+e);
+        }
+        
+        
+        pout.write("</body>\n</html>\n");
+        
+        pout.close();
+    }
+
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String getServletInfo()
+    {
+        return "Rego Servlet";
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public synchronized void destroy()
+    {
+    }
+
+    
+    private String notag(String s)
+    {
+        if (s==null)
+            return "null";
+        s=StringUtil.replace(s,"&","&");
+        s=StringUtil.replace(s,"<","<");
+        s=StringUtil.replace(s,">",">");
+        return s;
+    }
+}
diff --git a/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/RewriteServlet.java b/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/RewriteServlet.java
new file mode 100644
index 0000000..91b1755
--- /dev/null
+++ b/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/RewriteServlet.java
@@ -0,0 +1,77 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package com.acme;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/** 
+ * Test Servlet Rewrite
+ */
+ at SuppressWarnings("serial")
+public class RewriteServlet extends HttpServlet
+{
+    /* ------------------------------------------------------------ */
+    @Override
+    public void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException
+    {
+        doGet(req, res);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException
+    {
+        ServletOutputStream out = res.getOutputStream();
+        out.println("<html><body><table>");
+        out.println("<tr><th>Original request URI: </th><td>" + req.getAttribute("requestedPath") + "</td></tr>");
+        out.println("<tr><th>Rewritten request URI: </th><td>" + req.getRequestURI() + "</td></tr>");
+
+        Cookie cookie = null;
+        Cookie[] cookies = req.getCookies();
+        if (cookies != null)
+        {
+            for(Cookie c: cookies)
+            {
+                if (c.getName().equals("visited"))
+                {
+                    cookie = c;
+                    break;
+                }
+            }
+        }
+        if (cookie!=null)
+            out.println("<tr><th>Previously visited: </th></td><td>" + cookie.getValue()+"</td></tr>");
+
+        out.println("</table></body></html>");
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String getServletInfo()
+    {
+        return "Rewrite Servlet";
+    }
+}
diff --git a/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/SecureModeServlet.java b/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/SecureModeServlet.java
new file mode 100644
index 0000000..70d5d6c
--- /dev/null
+++ b/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/SecureModeServlet.java
@@ -0,0 +1,381 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package com.acme;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/** 
+ * Dump Servlet Request.
+ */
+ at SuppressWarnings("serial")
+public class SecureModeServlet extends HttpServlet
+{
+    private static final Logger LOG = Log.getLogger(SecureModeServlet.class);
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void init(ServletConfig config) throws ServletException
+    {
+        super.init(config);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+    {
+        doGet(request, response);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+    {
+
+        response.setContentType("text/html");
+        ServletOutputStream out = response.getOutputStream();
+        out.println("<html>");
+        out.println("  <title>Secure Jetty Test Webapp</title>");
+
+        try
+        {
+            runPropertyChecks(out);
+
+            runFileSystemChecks(out);
+
+            runLoggingChecks(out);
+
+            runClassloaderChecks(out);
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace(new PrintStream(out));
+        }
+        out.println("</html>");
+        out.flush();
+
+        try
+        {
+            Thread.sleep(200);
+        }
+        catch (InterruptedException e)
+        {
+            getServletContext().log("exception",e);
+        }
+    }
+
+    private void runClassloaderChecks(ServletOutputStream out) throws Exception
+    {
+        out.println("    <h1>Checking Classloader Setup</h1>");
+        out.println("      <p>");
+
+        System.getProperty("user.dir");
+        try
+        {
+            out.println("check ability to create classloader<br/>");
+            URL url = new URL("http://not.going.to.work");
+            new URLClassLoader(new URL[] { url });
+            out.println("status: <b>SUCCESS - unexpected</b><br/>");
+        }
+        catch (SecurityException e)
+        {
+            out.println("status: <b>FAILURE - expected</b><br/>");
+        }
+
+        out.println("      </p><br/><br/>");
+    }
+
+    private void runLoggingChecks(ServletOutputStream out) throws Exception
+    {
+        out.println("    <h1>Checking File System</h1>");
+        out.println("      <p>");
+
+        String userDir = System.getProperty("user.dir");
+        try
+        {
+            out.println("check ability to log<br/>");
+            LOG.info("testing logging");
+            out.println("status: <b>SUCCESS - expected</b><br/>");
+        }
+        catch (SecurityException e)
+        {
+            out.println("status: <b>FAILURE - unexpected</b><br/>");
+            out.println("<table><tr><td>");
+            e.printStackTrace(new PrintStream(out));
+            out.println("</td></tr></table>");
+        }
+
+        try
+        {
+            Calendar c = new GregorianCalendar();
+
+            String logFile = c.get(Calendar.YEAR) + "_" + c.get(Calendar.MONTH) + "_" + c.get(Calendar.DAY_OF_MONTH) + ".request.log";
+
+            out.println("check ability to access log file directly<br/>");
+            File jettyHomeFile = new File(userDir + File.separator + "logs" + File.separator + logFile);
+            jettyHomeFile.canRead();
+            out.println("status: <b>SUCCESS - unexpected</b><br/>");
+        }
+        catch (SecurityException e)
+        {
+            out.println("status: <b>FAILURE - expected</b><br/>");
+        }
+
+        out.println("      </p><br/><br/>");
+    }
+
+    private void runFileSystemChecks(ServletOutputStream out) throws Exception
+    {
+        out.println("    <h1>Checking File System</h1>");
+
+        /*
+         * test the reading and writing of a read only permission
+         */
+        out.println("      <p>");
+
+        String userDir = System.getProperty("user.dir");
+        try
+        {
+            out.println("check read for $jetty.home/lib/policy/jetty.policy<br/>");
+
+            File jettyHomeFile = new File(userDir + File.separator + "lib" + File.separator + "policy" + File.separator + "jetty.policy");
+            jettyHomeFile.canRead();
+            out.println("status: <b>SUCCESS - expected</b><br/>");
+        }
+        catch (SecurityException e)
+        {
+            out.println("status: <b>FAILURE - unexpected</b><br/>");
+            out.println("<table><tr><td>");
+            e.printStackTrace(new PrintStream(out));
+            out.println("</td></tr></table>");
+        }
+
+        try
+        {
+            out.println("check write permission for $jetty.home/lib/policy/jetty.policy<br/>");
+
+            File jettyHomeFile = new File(userDir + File.separator + "lib" + File.separator + "policy" + File.separator + "jetty.policy");
+            jettyHomeFile.canWrite();
+            out.println("status: <b>SUCCESS - unexpected</b><br/>");
+        }
+        catch (SecurityException e)
+        {
+            out.println("status: <b>FAILURE - expected</b><br/>");
+        }
+
+        try
+        {
+            out.println("check read permission for $jetty.home/lib<br/>");
+
+            File jettyHomeFile = new File(userDir + File.separator + "lib");
+            jettyHomeFile.canRead();
+            out.println("status: <b>SUCCESS - unexpected</b><br/>");
+        }
+        catch (SecurityException e)
+        {
+            out.println("status: <b>FAILURE - expected</b><br/>");
+        }
+
+        try
+        {
+            out.println("check write permission for $jetty.home/lib<br/>");
+
+            File jettyHomeFile = new File(userDir + File.separator + "lib");
+            jettyHomeFile.canWrite();
+            out.println("status: <b>SUCCESS - unexpected</b><br/>");
+        }
+        catch (SecurityException e)
+        {
+            out.println("status: <b>FAILURE - expected</b><br/>");
+        }
+
+        try
+        {
+            out.println("check read permission for $jetty.home<br/>");
+
+            File jettyHomeFile = new File(userDir + File.separator);
+            jettyHomeFile.canRead();
+            out.println("status: <b>SUCCESS - unexpected</b><br/>");
+        }
+        catch (SecurityException e)
+        {
+            out.println("status: <b>FAILURE - expected</b><br/>");
+        }
+
+        try
+        {
+            out.println("check write permission for $jetty.home<br/>");
+
+            File jettyHomeFile = new File(userDir + File.separator);
+            jettyHomeFile.canWrite();
+            out.println("status: <b>SUCCESS - unexpected</b><br/>");
+        }
+        catch (SecurityException e)
+        {
+            out.println("status: <b>FAILURE - expected</b><br/>");
+        }
+
+        try
+        {
+            out.println("check read permission for $jetty.home/logs<br/>");
+
+            File jettyHomeFile = new File(userDir + File.separator + "logs" + File.separator);
+            jettyHomeFile.canRead();
+            out.println("status: <b>SUCCESS - unexpected</b><br/>");
+        }
+        catch (SecurityException e)
+        {
+            out.println("status: <b>FAILURE - expected</b><br/>");
+        }
+
+        try
+        {
+            out.println("check read permission for $jetty.home/logs<br/>");
+
+            File jettyHomeFile = new File(userDir + File.separator + "logs");
+            jettyHomeFile.canWrite();
+            out.println("status: <b>SUCCESS - unexpected</b><br/>");
+        }
+        catch (SecurityException e)
+        {
+            out.println("status: <b>FAILURE - expected</b><br/>");
+        }
+
+        out.println("      </p><br/><br/>");
+    }
+
+    private void runPropertyChecks(ServletOutputStream out) throws IOException
+    {
+
+        out.println("    <h1>Checking Properties</h1>");
+
+        /*
+         * test the reading and writing of a read only permission
+         */
+        out.println("    <h3>Declared Property - read</h3>");
+        out.println("      <p>");
+        try
+        {
+            out.println("check read permission for __ALLOWED_READ_PROPERTY <br/>");
+            System.getProperty("__ALLOWED_READ_PROPERTY");
+            out.println("status: <b>SUCCESS - expected</b><br/>");
+        }
+        catch (SecurityException e)
+        {
+            out.println("status: <b>FAILURE - unexpected</b><br/>");
+            out.println("<table><tr><td>");
+            e.printStackTrace(new PrintStream(out));
+            out.println("</td></tr></table>");
+        }
+        try
+        {
+            out.println("check write permission for __ALLOWED_READ_PROPERTY<br/>");
+            System.setProperty("__ALLOWED_READ_PROPERTY","SUCCESS - unexpected");
+            String value = System.getProperty("__ALLOWED_READ_PROPERTY");
+            out.println("status: <b>" + value + "</b><br/>");
+        }
+        catch (SecurityException e)
+        {
+            out.println("status: <b>FAILURE - expected</b><br/>");
+        }
+
+        out.println("      </p><br/><br/>");
+
+        /*
+         * test the reading and writing of a read/write permission
+         */
+        out.println("    <h3>Declared Property - read/write</h3>");
+        out.println("      <p>");
+        try
+        {
+            out.println("check read permission for __ALLOWED_WRITE_PROPERTY<br/>");
+            System.getProperty("__ALLOWED_WRITE_PROPERTY");
+            out.println("Status: <b>SUCCESS - expected</b><br/>");
+        }
+        catch (SecurityException e)
+        {
+            out.println("status: <b>FAILURE - unexpected</b><br/>");
+            out.println("<table><tr><td>");
+            e.printStackTrace(new PrintStream(out));
+            out.println("</td></tr></table>");
+        }
+        try
+        {
+            out.println("check write permission for __ALLOWED_WRITE_PROPERTY<br/>");
+            System.setProperty("__ALLOWED_WRITE_PROPERTY","SUCCESS - expected");
+            String value = System.getProperty("__ALLOWED_WRITE_PROPERTY");
+            out.println("status: <b>" + value + "</b><br/>");
+        }
+        catch (SecurityException e)
+        {
+            out.println("status: <b>FAILURE - unexpected</b><br/>");
+            out.println("<table><tr><td>");
+            e.printStackTrace(new PrintStream(out));
+            out.println("</td></tr></table>");
+        }
+
+        out.println("      </p><br/><br/>");
+
+        /*
+         * test the reading and writing of an undeclared property
+         */
+        out.println("    <h3>checking forbidden properties</h3>");
+        out.println("      <p>");
+        try
+        {
+            out.println("check read permission for __UNDECLARED_PROPERTY: <br/>");
+            System.getProperty("__UNDECLARED_PROPERTY");
+            out.println("status: <b>SUCCESS - expected</b><br/>");
+        }
+        catch (SecurityException e)
+        {
+            out.println("status: <b>FAILURE - expected</b><br/>");
+        }
+        try
+        {
+            out.println("check write permission for __UNDECLARED_PROPERTY: <br/>");
+            System.setProperty("__UNDECLARED_PROPERTY","SUCCESS - unexpected");
+            String value = System.getProperty("__UNDECLARED_PROPERTY");
+            out.println("status: <b>" + value + "</b><br/>");
+        }
+        catch (SecurityException e)
+        {
+            out.println("status: <b>FAILURE - expected</b><br/>");
+        }
+
+        out.println("      </p><br/><br/>");
+    }
+
+
+}
diff --git a/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/SessionDump.java b/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/SessionDump.java
new file mode 100644
index 0000000..7ed7413
--- /dev/null
+++ b/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/SessionDump.java
@@ -0,0 +1,184 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package com.acme;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Date;
+import java.util.Enumeration;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+/** 
+ * Test Servlet Sessions.
+ */
+ at SuppressWarnings("serial")
+public class SessionDump extends HttpServlet
+{
+
+    int redirectCount=0;
+    /* ------------------------------------------------------------ */
+    String pageType;
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void init(ServletConfig config)
+         throws ServletException
+    {
+        super.init(config);
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void handleForm(HttpServletRequest request,
+                          HttpServletResponse response)
+    {
+        HttpSession session = request.getSession(false);
+        String action = request.getParameter("Action");
+        String name =  request.getParameter("Name");
+        String value =  request.getParameter("Value");
+
+        if (action!=null)
+        {
+            if(action.equals("New Session"))
+            {
+                session = request.getSession(true);
+                session.setAttribute("test","value");
+            }
+            else if (session!=null)
+            {
+                if (action.equals("Invalidate"))
+                    session.invalidate();
+                else if (action.equals("Set") && name!=null && name.length()>0)
+                    session.setAttribute(name,value);
+                else if (action.equals("Remove"))
+                    session.removeAttribute(name);
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void doPost(HttpServletRequest request,
+                       HttpServletResponse response)
+        throws ServletException, IOException
+    {
+        handleForm(request,response);
+        String nextUrl = getURI(request)+"?R="+redirectCount++;
+        String encodedUrl=response.encodeRedirectURL(nextUrl);
+        response.sendRedirect(encodedUrl);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void doGet(HttpServletRequest request,
+                      HttpServletResponse response)
+        throws ServletException, IOException
+    {
+        handleForm(request,response);
+
+        response.setContentType("text/html");
+
+        HttpSession session = request.getSession(getURI(request).indexOf("new")>0);
+        try
+        {
+            if (session!=null)
+                session.isNew();
+        }
+        catch(IllegalStateException e)
+        {
+            session=null;
+        }
+
+        PrintWriter out = response.getWriter();
+        out.println("<h1>Session Dump Servlet:</h1>");
+        out.println("<form action=\""+response.encodeURL(getURI(request))+"\" method=\"post\">");
+
+        if (session==null)
+        {
+            out.println("<H3>No Session</H3>");
+            out.println("<input type=\"submit\" name=\"Action\" value=\"New Session\"/>");
+        }
+        else
+        {
+            try
+            {
+                out.println("<b>ID:</b> "+session.getId()+"<br/>");
+                out.println("<b>New:</b> "+session.isNew()+"<br/>");
+                out.println("<b>Created:</b> "+new Date(session.getCreationTime())+"<br/>");
+                out.println("<b>Last:</b> "+new Date(session.getLastAccessedTime())+"<br/>");
+                out.println("<b>Max Inactive:</b> "+session.getMaxInactiveInterval()+"<br/>");
+                out.println("<b>Context:</b> "+session.getServletContext()+"<br/>");
+
+
+                Enumeration<String> keys=session.getAttributeNames();
+                while(keys.hasMoreElements())
+                {
+                    String name=(String)keys.nextElement();
+                    String value=""+session.getAttribute(name);
+
+                    out.println("<b>"+name+":</b> "+value+"<br/>");
+                }
+
+                out.println("<b>Name:</b><input type=\"text\" name=\"Name\" /><br/>");
+                out.println("<b>Value:</b><input type=\"text\" name=\"Value\" /><br/>");
+
+                out.println("<input type=\"submit\" name=\"Action\" value=\"Set\"/>");
+                out.println("<input type=\"submit\" name=\"Action\" value=\"Remove\"/>");
+                out.println("<input type=\"submit\" name=\"Action\" value=\"Refresh\"/>");
+                out.println("<input type=\"submit\" name=\"Action\" value=\"Invalidate\"/><br/>");
+
+                out.println("</form><br/>");
+
+                if (request.isRequestedSessionIdFromCookie())
+                    out.println("<P>Turn off cookies in your browser to try url encoding<BR>");
+
+                if (request.isRequestedSessionIdFromURL())
+                    out.println("<P>Turn on cookies in your browser to try cookie encoding<BR>");
+                out.println("<a href=\""+response.encodeURL(request.getRequestURI()+"?q=0")+"\">Encoded Link</a><BR>");
+
+            }
+            catch (IllegalStateException e)
+            {
+                e.printStackTrace();
+            }
+        }
+
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String getServletInfo() {
+        return "Session Dump Servlet";
+    }
+
+    /* ------------------------------------------------------------ */
+    private String getURI(HttpServletRequest request)
+    {
+        String uri=(String)request.getAttribute("javax.servlet.forward.request_uri");
+        if (uri==null)
+            uri=request.getRequestURI();
+        return uri;
+    }
+
+}
diff --git a/test-jetty-webapp/src/main/java/com/acme/TagListener.java b/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/TagListener.java
similarity index 100%
rename from test-jetty-webapp/src/main/java/com/acme/TagListener.java
rename to tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/TagListener.java
diff --git a/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/TestFilter.java b/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/TestFilter.java
new file mode 100644
index 0000000..95721c7
--- /dev/null
+++ b/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/TestFilter.java
@@ -0,0 +1,128 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package com.acme;
+
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletRequestWrapper;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/** TestFilter.
+ *
+ * This filter checks for a none local request, and if the init parameter
+ * "remote" is not set to true, then all non local requests are forwarded
+ * to /remote.html
+ *
+ */
+public class TestFilter implements Filter
+{
+    private static final Logger LOG = Log.getLogger(TestFilter.class);
+
+    private boolean _remote;
+    private ServletContext _context;
+    private final Set<String> _allowed = new HashSet<String>();
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
+     */
+    @Override
+    public void init(FilterConfig filterConfig) throws ServletException
+    {
+        _context= filterConfig.getServletContext();
+        _remote=Boolean.parseBoolean(filterConfig.getInitParameter("remote"));
+        _allowed.add("/favicon.ico");
+        _allowed.add("/jetty_banner.gif");
+        _allowed.add("/remote.html");
+
+        LOG.debug("TestFilter#remote="+_remote);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)
+     */
+    @Override
+    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+            throws IOException, ServletException
+    {
+        String from = request.getRemoteAddr();
+        String to = request.getLocalAddr();
+        String path=((HttpServletRequest)request).getServletPath();
+        
+        if (!_remote && !_allowed.contains(path) && !from.equals(to))
+        {
+            _context.getRequestDispatcher("/remote.html").forward(request,response);
+            return;
+        }
+
+        Integer old_value=null;
+        ServletRequest r = request;
+        while (r instanceof ServletRequestWrapper)
+            r=((ServletRequestWrapper)r).getRequest();
+
+        try
+        {
+            old_value=(Integer)request.getAttribute("testFilter");
+
+            Integer value=(old_value==null)?new Integer(1):new Integer(old_value.intValue()+1);
+
+            request.setAttribute("testFilter", value);
+
+            String qString = ((HttpServletRequest)request).getQueryString();
+            if (qString != null && qString.indexOf("wrap")>=0)
+            {
+                request=new HttpServletRequestWrapper((HttpServletRequest)request);
+            }
+            _context.setAttribute("request"+r.hashCode(),value);
+
+            chain.doFilter(request, response);
+        }
+        finally
+        {
+            request.setAttribute("testFilter", old_value);
+            _context.setAttribute("request"+r.hashCode(),old_value);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.Filter#destroy()
+     */
+    @Override
+    public void destroy()
+    {
+    }
+
+}
diff --git a/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/TestListener.java b/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/TestListener.java
new file mode 100644
index 0000000..a1cefbc
--- /dev/null
+++ b/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/TestListener.java
@@ -0,0 +1,226 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package com.acme;
+
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+import javax.servlet.DispatcherType;
+import javax.servlet.FilterRegistration;
+import javax.servlet.HttpConstraintElement;
+import javax.servlet.ServletContextAttributeEvent;
+import javax.servlet.ServletContextAttributeListener;
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+import javax.servlet.ServletRegistration;
+import javax.servlet.ServletRequestAttributeEvent;
+import javax.servlet.ServletRequestAttributeListener;
+import javax.servlet.ServletRequestEvent;
+import javax.servlet.ServletRequestListener;
+import javax.servlet.ServletSecurityElement;
+import javax.servlet.annotation.ServletSecurity;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSessionActivationListener;
+import javax.servlet.http.HttpSessionAttributeListener;
+import javax.servlet.http.HttpSessionBindingEvent;
+import javax.servlet.http.HttpSessionEvent;
+import javax.servlet.http.HttpSessionListener;
+
+public class TestListener implements HttpSessionListener,  HttpSessionAttributeListener, HttpSessionActivationListener, ServletContextListener, ServletContextAttributeListener, ServletRequestListener, ServletRequestAttributeListener
+{
+    Map<String,Throwable> _called=new HashMap<>();
+    
+    public TestListener()
+    {
+        _called.put("TestListener",new Throwable());
+    }
+
+    @PostConstruct
+    public void postConstruct()
+    {
+        _called.put("postConstruct",new Throwable());
+    }
+    
+    @PreDestroy
+    public void preDestroy()
+    {
+        _called.put("preDestroy",new Throwable());
+    }
+    
+    @Override
+    public void attributeAdded(HttpSessionBindingEvent se)
+    {
+        // System.err.println("attributedAdded "+se);
+
+        _called.put("attributeAdded",new Throwable());
+    }
+
+    @Override
+    public void attributeRemoved(HttpSessionBindingEvent se)
+    {
+        // System.err.println("attributeRemoved "+se);
+        _called.put("attributeRemoved",new Throwable());
+    }
+
+    @Override
+    public void attributeReplaced(HttpSessionBindingEvent se)
+    {
+        // System.err.println("attributeReplaced "+se);
+        _called.put("attributeReplaced",new Throwable());
+    }
+
+    @Override
+    public void sessionWillPassivate(HttpSessionEvent se)
+    {
+        // System.err.println("sessionWillPassivate "+se);
+        _called.put("sessionWillPassivate",new Throwable());
+    }
+
+    @Override
+    public void sessionDidActivate(HttpSessionEvent se)
+    {
+        // System.err.println("sessionDidActivate "+se);
+        _called.put("sessionDidActivate",new Throwable());
+    }
+
+    @Override
+    public void contextInitialized(ServletContextEvent sce)
+    {	
+        // System.err.println("contextInitialized "+sce);
+        _called.put("contextInitialized",new Throwable());
+
+        //configure programmatic security
+        ServletRegistration.Dynamic rego = sce.getServletContext().addServlet("RegoTest", RegTest.class.getName());
+        rego.addMapping("/rego/*");
+        HttpConstraintElement constraintElement = new HttpConstraintElement(ServletSecurity.EmptyRoleSemantic.PERMIT, 
+            ServletSecurity.TransportGuarantee.NONE, new String[]{"admin"});
+        ServletSecurityElement securityElement = new ServletSecurityElement(constraintElement, null);
+        Set<String> unchanged = rego.setServletSecurity(securityElement);
+        //// System.err.println("Security constraints registered: "+unchanged.isEmpty());
+
+        //Test that a security constraint from web.xml can't be overridden programmatically
+        ServletRegistration.Dynamic rego2 = sce.getServletContext().addServlet("RegoTest2", RegTest.class.getName());
+        rego2.addMapping("/rego2/*");
+        securityElement = new ServletSecurityElement(constraintElement, null);
+        unchanged = rego2.setServletSecurity(securityElement);
+        //// System.err.println("Overridding web.xml constraints not possible:" +!unchanged.isEmpty());
+
+        /* For servlet 3.0 */
+        FilterRegistration registration = sce.getServletContext().addFilter("TestFilter",TestFilter.class.getName());
+        if (registration != null) //otherwise defined in web.xml
+        {
+            ((FilterRegistration.Dynamic)registration).setAsyncSupported(true);
+        }
+        else
+        {
+            registration=sce.getServletContext().getFilterRegistration("TestFilter");
+        }
+        registration.setInitParameter("remote", "false");
+        registration.addMappingForUrlPatterns(
+                EnumSet.of(DispatcherType.ERROR,DispatcherType.ASYNC,DispatcherType.FORWARD,DispatcherType.INCLUDE,DispatcherType.REQUEST),
+                true, 
+                new String[]{"/*"});
+    }
+
+    @Override
+    public void contextDestroyed(ServletContextEvent sce)
+    {
+        _called.put("contextDestroyed",new Throwable());
+        // System.err.println("contextDestroyed "+sce);
+    }
+
+    @Override
+    public void attributeAdded(ServletContextAttributeEvent scab)
+    {
+        _called.put("attributeAdded",new Throwable());
+        // System.err.println("attributeAdded "+scab);
+    }
+
+    @Override
+    public void attributeRemoved(ServletContextAttributeEvent scab)
+    {
+        _called.put("attributeRemoved",new Throwable());
+        // System.err.println("attributeRemoved "+scab);
+    }
+
+    @Override
+    public void attributeReplaced(ServletContextAttributeEvent scab)
+    {
+        _called.put("attributeReplaced",new Throwable());
+        // System.err.println("attributeReplaced "+scab);
+    }
+
+    @Override
+    public void requestDestroyed(ServletRequestEvent sre)
+    {
+        _called.put("requestDestroyed",new Throwable());
+        ((HttpServletRequest)sre.getServletRequest()).getSession(false);
+        sre.getServletRequest().setAttribute("requestInitialized",null);
+        // System.err.println("requestDestroyed "+sre);
+    }
+
+    @Override
+    public void requestInitialized(ServletRequestEvent sre)
+    {
+        _called.put("requestInitialized",new Throwable());
+        sre.getServletRequest().setAttribute("requestInitialized","'"+sre.getServletContext().getContextPath()+"'");
+        // System.err.println("requestInitialized "+sre);
+    }
+
+    @Override
+    public void attributeAdded(ServletRequestAttributeEvent srae)
+    {
+        _called.put("attributeAdded",new Throwable());
+        // System.err.println("attributeAdded "+srae);
+    }
+
+    @Override
+    public void attributeRemoved(ServletRequestAttributeEvent srae)
+    {
+        _called.put("attributeRemoved",new Throwable());
+        // System.err.println("attributeRemoved "+srae);
+    }
+
+    @Override
+    public void attributeReplaced(ServletRequestAttributeEvent srae)
+    {
+        _called.put("attributeReplaced",new Throwable());
+        // System.err.println("attributeReplaced "+srae);
+    }
+
+    @Override
+    public void sessionCreated(HttpSessionEvent se)
+    {
+        _called.put("sessionCreated",new Throwable());
+        // System.err.println("sessionCreated "+se);
+    }
+
+    @Override
+    public void sessionDestroyed(HttpSessionEvent se)
+    {
+        _called.put("sessionDestroyed",new Throwable());
+        // System.err.println("sessionDestroyed "+se);
+    }
+
+
+}
diff --git a/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/WebSocketChatServlet.java b/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/WebSocketChatServlet.java
new file mode 100644
index 0000000..63ed3c0
--- /dev/null
+++ b/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/WebSocketChatServlet.java
@@ -0,0 +1,122 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package com.acme;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.websocket.api.RemoteEndpoint;
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
+import org.eclipse.jetty.websocket.api.annotations.WebSocket;
+import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
+import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
+import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
+import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
+import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
+
+ at SuppressWarnings("serial")
+public class WebSocketChatServlet extends WebSocketServlet implements WebSocketCreator
+{
+    /** Holds active sockets to other members of the chat */
+    private final List<ChatWebSocket> members = new CopyOnWriteArrayList<ChatWebSocket>();
+
+    @Override
+    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+    {
+        getServletContext().getNamedDispatcher("default").forward(request,response);
+    }
+
+    @Override
+    public Object createWebSocket(ServletUpgradeRequest req, ServletUpgradeResponse resp)
+    {
+        if (req.hasSubProtocol("chat"))
+        {
+            resp.setAcceptedSubProtocol("chat");
+            return new ChatWebSocket();
+        }
+        return null;
+    }
+
+    @Override
+    public void configure(WebSocketServletFactory factory)
+    {
+        factory.register(ChatWebSocket.class);
+        factory.setCreator(this);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Create a WebSocket that echo's back the message to all other members of the servlet.
+     */
+    @WebSocket
+    public class ChatWebSocket
+    {
+        volatile Session session;
+        volatile RemoteEndpoint remote;
+
+        @OnWebSocketConnect
+        public void onOpen(Session sess)
+        {
+            this.session = sess;
+            this.remote = sess.getRemote();
+            members.add(this);
+        }
+
+        @OnWebSocketMessage
+        public void onMessage(String data)
+        {
+            if (data.contains("disconnect"))
+            {
+                session.close();
+                return;
+            }
+
+            ListIterator<ChatWebSocket> iter = members.listIterator();
+            while (iter.hasNext())
+            {
+                ChatWebSocket member = iter.next();
+
+                // Test if member is now disconnected
+                if (!member.session.isOpen())
+                {
+                    iter.remove();
+                    continue;
+                }
+
+                // Async write the message back.
+                member.remote.sendString(data,null);
+            }
+        }
+
+        @OnWebSocketClose
+        public void onClose(int code, String message)
+        {
+            members.remove(this);
+        }
+    }
+}
diff --git a/tests/test-webapps/test-jetty-webapp/src/main/resources/jetty-logging.properties b/tests/test-webapps/test-jetty-webapp/src/main/resources/jetty-logging.properties
new file mode 100644
index 0000000..d474e35
--- /dev/null
+++ b/tests/test-webapps/test-jetty-webapp/src/main/resources/jetty-logging.properties
@@ -0,0 +1,2 @@
+org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
+com.acme.LEVEL=INFO
diff --git a/tests/test-webapps/test-jetty-webapp/src/main/webapp/WEB-INF/acme-taglib.tld b/tests/test-webapps/test-jetty-webapp/src/main/webapp/WEB-INF/acme-taglib.tld
new file mode 100644
index 0000000..09b70ae
--- /dev/null
+++ b/tests/test-webapps/test-jetty-webapp/src/main/webapp/WEB-INF/acme-taglib.tld
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE taglib
+        PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN"
+	"http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd">
+
+<taglib>
+
+  <tlib-version>1.0</tlib-version>
+  <jsp-version>1.2</jsp-version>
+  <short-name>acme</short-name>
+  <uri>http://www.acme.com/taglib</uri>
+  <description>taglib example</description>
+  <listener>
+    <listener-class>com.acme.TagListener</listener-class>
+  </listener>
+
+  <tag>
+    <name>date</name>
+    <tag-class>com.acme.DateTag</tag-class>
+    <body-content>TAGDEPENDENT</body-content>
+    <description>Display Date</description>
+    <attribute>
+       <name>tz</name>
+       <required>false</required>
+    </attribute>
+  </tag>       
+
+</taglib>
diff --git a/test-jetty-webapp/src/main/webapp/WEB-INF/acme-taglib2.tld b/tests/test-webapps/test-jetty-webapp/src/main/webapp/WEB-INF/acme-taglib2.tld
similarity index 100%
rename from test-jetty-webapp/src/main/webapp/WEB-INF/acme-taglib2.tld
rename to tests/test-webapps/test-jetty-webapp/src/main/webapp/WEB-INF/acme-taglib2.tld
diff --git a/tests/test-webapps/test-jetty-webapp/src/main/webapp/WEB-INF/jetty-web.xml b/tests/test-webapps/test-jetty-webapp/src/main/webapp/WEB-INF/jetty-web.xml
new file mode 100644
index 0000000..19fb028
--- /dev/null
+++ b/tests/test-webapps/test-jetty-webapp/src/main/webapp/WEB-INF/jetty-web.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
+
+<!--
+This is the jetty specific web application configuration file.  When starting
+a Web Application, the WEB-INF/jetty-web.xml file is looked for and if found, treated
+as a org.eclipse.jetty.server.server.xml.XmlConfiguration file and is applied to the
+org.eclipse.jetty.servlet.WebApplicationContext object
+-->
+
+<Configure class="org.eclipse.jetty.webapp.WebAppContext">
+  <Call name="prependServerClass"><Arg>-org.eclipse.jetty.util.</Arg></Call>
+  <Call name="prependServerClass"><Arg>-org.eclipse.jetty.servlets.</Arg></Call>
+  <Get class="org.eclipse.jetty.util.log.Log" name="rootLogger">
+    <Call name="warn"><Arg>test webapp is deployed. DO NOT USE IN PRODUCTION!</Arg></Call>
+  </Get>
+</Configure>
diff --git a/test-jetty-webapp/src/main/webapp/WEB-INF/tags/panel.tag b/tests/test-webapps/test-jetty-webapp/src/main/webapp/WEB-INF/tags/panel.tag
similarity index 100%
rename from test-jetty-webapp/src/main/webapp/WEB-INF/tags/panel.tag
rename to tests/test-webapps/test-jetty-webapp/src/main/webapp/WEB-INF/tags/panel.tag
diff --git a/tests/test-webapps/test-jetty-webapp/src/main/webapp/WEB-INF/web.xml b/tests/test-webapps/test-jetty-webapp/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000..a2b3763
--- /dev/null
+++ b/tests/test-webapps/test-jetty-webapp/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,342 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<web-app 
+   xmlns="http://xmlns.jcp.org/xml/ns/javaee" 
+   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+   xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
+   metadata-complete="false"
+   version="3.1"> 
+
+  <display-name>Test WebApp</display-name>
+  
+  <context-param>
+    <param-name>org.eclipse.jetty.server.context.ManagedAttributes</param-name>
+    <param-value>QoSFilter,TransparentProxy.ThreadPool,TransparentProxy.HttpClient</param-value>
+  </context-param>
+  
+  <!-- Declare TestListener, which declares TestFilter -->
+  <listener>
+    <listener-class>com.acme.TestListener</listener-class>
+  </listener>
+
+
+  <filter>
+    <filter-name>QoSFilter</filter-name>
+    <filter-class>org.eclipse.jetty.servlets.QoSFilter</filter-class>
+    <async-supported>true</async-supported>
+    <init-param>
+      <param-name>maxRequests</param-name>
+      <param-value>10000</param-value>
+    </init-param>
+    <init-param>
+      <param-name>managedAttr</param-name>
+      <param-value>true</param-value>
+    </init-param>
+  </filter>  
+  <filter-mapping>
+    <filter-name>QoSFilter</filter-name>
+    <url-pattern>/*</url-pattern>
+  </filter-mapping>
+
+
+  <filter>
+    <filter-name>MultiPart</filter-name>
+    <filter-class>org.eclipse.jetty.servlets.MultiPartFilter</filter-class>
+    <async-supported>true</async-supported>
+    <init-param>
+      <param-name>deleteFiles</param-name>
+      <param-value>true</param-value>
+    </init-param>
+  </filter>
+  <filter-mapping>
+    <filter-name>MultiPart</filter-name>
+    <url-pattern>/dump/*</url-pattern>
+  </filter-mapping>
+  
+
+  <filter>
+    <filter-name>GzipFilter</filter-name>
+    <filter-class>org.eclipse.jetty.servlets.AsyncGzipFilter</filter-class>
+    <async-supported>true</async-supported>
+    <init-param>
+      <param-name>bufferSize</param-name>
+      <param-value>8192</param-value>
+    </init-param>
+    <init-param>
+      <param-name>mimeTypes</param-name>
+      <param-value>text/plain,application/xml,text/html</param-value>
+    </init-param>
+    <init-param>
+      <param-name>minGzipSize</param-name>
+      <param-value>2048</param-value>
+    </init-param>
+    <init-param>
+      <param-name>userAgent</param-name>
+      <param-value>(?:Mozilla[^\(]*\(compatible;\s*+([^;]*);.*)|(?:.*?([^\s]+/[^\s]+).*)</param-value>
+    </init-param>
+    <init-param>
+      <param-name>cacheSize</param-name>
+      <param-value>1024</param-value>
+    </init-param>
+    <init-param>
+      <param-name>excludedAgents</param-name>
+      <param-value>MSIE 6.0</param-value>
+    </init-param>
+    <init-param>
+      <param-name>uncheckedPrintWriter</param-name>
+      <param-value>true</param-value>
+    </init-param>
+  </filter>
+  <filter-mapping>
+    <filter-name>GzipFilter</filter-name>
+    <url-pattern>/dump/gzip/*</url-pattern>
+    <url-pattern>*.txt</url-pattern>
+  </filter-mapping>
+  
+  <servlet>
+    <servlet-name>Login</servlet-name>
+    <servlet-class>com.acme.LoginServlet</servlet-class>
+    <load-on-startup>1</load-on-startup>
+  </servlet>
+
+  <servlet-mapping>
+    <servlet-name>Login</servlet-name>
+    <url-pattern>/login/*</url-pattern>
+  </servlet-mapping>
+
+
+  <servlet>
+    <servlet-name>Hello</servlet-name>
+    <servlet-class>com.acme.HelloWorld</servlet-class>
+    <load-on-startup>1</load-on-startup>
+  </servlet>
+
+  <servlet-mapping>
+    <servlet-name>Hello</servlet-name>
+    <url-pattern>/hello/*</url-pattern>
+  </servlet-mapping>
+  
+  <servlet>
+    <servlet-name>Dump</servlet-name>
+    <servlet-class>com.acme.Dump</servlet-class>
+    <load-on-startup>1</load-on-startup>
+    <async-supported>true</async-supported>
+    <run-as><role-name>admin</role-name></run-as>
+  </servlet>
+
+  <servlet-mapping>
+    <servlet-name>Dump</servlet-name>
+    <url-pattern>/dump/*</url-pattern>
+    <url-pattern>*.dump</url-pattern>
+  </servlet-mapping>
+
+  <servlet>
+    <servlet-name>Session</servlet-name>
+    <servlet-class>com.acme.SessionDump</servlet-class>
+    <load-on-startup>1</load-on-startup>
+  </servlet>
+  
+  <servlet-mapping>
+    <servlet-name>Session</servlet-name>
+    <url-pattern>/session/*</url-pattern>
+  </servlet-mapping>
+  
+  <servlet>
+    <servlet-name>Cookie</servlet-name>
+    <servlet-class>com.acme.CookieDump</servlet-class>
+    <load-on-startup>1</load-on-startup>
+  </servlet>
+  
+  <servlet-mapping>
+    <servlet-name>Cookie</servlet-name>
+    <url-pattern>/cookie/*</url-pattern>
+  </servlet-mapping>
+  
+  <servlet>
+    <servlet-name>Dispatch</servlet-name>
+    <servlet-class>com.acme.DispatchServlet</servlet-class>
+    <load-on-startup>1</load-on-startup>
+    <async-supported>true</async-supported>
+  </servlet>
+  
+  <servlet-mapping>
+    <servlet-name>Dispatch</servlet-name>
+    <url-pattern>/dispatch/*</url-pattern>
+  </servlet-mapping>
+  
+  <servlet>
+    <servlet-name>CGI</servlet-name>
+    <servlet-class>org.eclipse.jetty.servlets.CGI</servlet-class>
+    <load-on-startup>1</load-on-startup>
+  </servlet>
+  
+  <servlet-mapping>
+    <servlet-name>CGI</servlet-name>
+    <url-pattern>/cgi-bin/*</url-pattern>
+  </servlet-mapping>
+  
+  <servlet>
+    <servlet-name>Chat</servlet-name>
+    <servlet-class>com.acme.ChatServlet</servlet-class>
+    <load-on-startup>1</load-on-startup>
+    <async-supported>true</async-supported>
+  </servlet>
+
+  <servlet-mapping>
+    <servlet-name>Chat</servlet-name>
+    <url-pattern>/chat/*</url-pattern>
+  </servlet-mapping>
+  
+  <servlet>
+    <servlet-name>WSChat</servlet-name>
+    <servlet-class>com.acme.WebSocketChatServlet</servlet-class>
+    <load-on-startup>1</load-on-startup>
+  </servlet>
+
+  <servlet-mapping>
+    <servlet-name>WSChat</servlet-name>
+    <url-pattern>/ws/*</url-pattern>
+  </servlet-mapping>
+  
+  
+  <servlet>
+    <servlet-name>Rewrite</servlet-name>
+    <servlet-class>com.acme.RewriteServlet</servlet-class>
+  </servlet>
+  
+  <servlet-mapping>
+    <servlet-name>Rewrite</servlet-name>
+    <url-pattern>/rewritten/*</url-pattern>
+    <url-pattern>/redirected/*</url-pattern>
+  </servlet-mapping>
+  
+  
+  <servlet>
+    <servlet-name>SecureMode</servlet-name>
+    <servlet-class>com.acme.SecureModeServlet</servlet-class>
+    <load-on-startup>1</load-on-startup>
+  </servlet>
+
+  <servlet-mapping>
+    <servlet-name>SecureMode</servlet-name>
+    <url-pattern>/secureMode/*</url-pattern>
+  </servlet-mapping>
+  
+  <servlet>
+     <servlet-name>foo.jsp</servlet-name>
+     <jsp-file>/jsp/foo/foo.jsp</jsp-file>
+  </servlet>
+  <servlet-mapping>
+    <servlet-name>foo.jsp</servlet-name>
+    <url-pattern>/jsp/foo/</url-pattern>
+  </servlet-mapping>
+
+  <error-page>
+    <error-code>404</error-code>
+    <location>/error404.html</location>
+  </error-page>
+
+  <security-constraint>
+    <web-resource-collection>
+      <web-resource-name>Rego2</web-resource-name>
+      <url-pattern>/rego2/*</url-pattern>
+    </web-resource-collection>
+    <auth-constraint>
+      <role-name>server-administrator</role-name>
+    </auth-constraint>
+  </security-constraint>
+
+  <security-constraint>
+    <web-resource-collection>
+      <web-resource-name>Auth2</web-resource-name>
+      <url-pattern>/auth2/*</url-pattern>
+    </web-resource-collection>
+    <auth-constraint>
+      <role-name>*</role-name>
+    </auth-constraint>
+  </security-constraint>
+
+  <security-constraint>
+    <web-resource-collection>
+      <web-resource-name>Any User</web-resource-name>
+      <url-pattern>/dump/auth/*</url-pattern>
+      <url-pattern>*.htm</url-pattern>
+    </web-resource-collection>
+    <auth-constraint>
+      <role-name>*</role-name>
+    </auth-constraint>
+  </security-constraint>
+
+  <security-constraint>
+    <web-resource-collection>
+      <web-resource-name>relax</web-resource-name>
+      <url-pattern>/dump/auth/relax/*</url-pattern>
+      <url-pattern>/auth/relax.txt</url-pattern>
+    </web-resource-collection>
+  </security-constraint>
+
+  <security-constraint>
+    <web-resource-collection>
+      <web-resource-name>Admin Role</web-resource-name>
+      <url-pattern>/dump/auth/admin/*</url-pattern>
+    </web-resource-collection>
+    <auth-constraint>
+      <role-name>admin</role-name>
+    </auth-constraint>
+  </security-constraint>
+
+  <security-constraint>
+    <web-resource-collection>
+      <web-resource-name>Forbidden</web-resource-name>
+      <url-pattern>/dump/auth/noaccess/*</url-pattern>
+      <url-pattern>/auth/*</url-pattern>
+    </web-resource-collection>
+    <auth-constraint/>
+  </security-constraint>
+
+  <security-constraint>
+    <web-resource-collection>
+      <web-resource-name>SSL</web-resource-name>
+      <url-pattern>/dump/auth/ssl/*</url-pattern>
+    </web-resource-collection>
+    <user-data-constraint>
+      <transport-guarantee>CONFIDENTIAL</transport-guarantee>
+    </user-data-constraint>
+  </security-constraint>
+
+<!--
+  <login-config>
+    <auth-method>BASIC</auth-method>
+    <realm-name>Test Realm</realm-name>
+  </login-config>
+-->
+
+<!--
+  <login-config>
+    <auth-method>DIGEST</auth-method>
+    <realm-name>Test Realm</realm-name>
+  </login-config>
+-->
+
+  <login-config>
+    <auth-method>FORM</auth-method>
+    <realm-name>Test Realm</realm-name>
+    <form-login-config>
+       <form-login-page>/logon.html?param=test</form-login-page>
+       <form-error-page>/logonError.html?param=test</form-error-page>
+    </form-login-config>
+  </login-config>
+  
+  <session-config>
+    <session-timeout>5</session-timeout>
+  </session-config>
+
+  <security-role>
+    <role-name>admin</role-name>
+  </security-role>
+  <security-role>
+    <role-name>user</role-name>
+  </security-role>
+
+</web-app>
+
+
diff --git a/tests/test-webapps/test-jetty-webapp/src/main/webapp/auth.html b/tests/test-webapps/test-jetty-webapp/src/main/webapp/auth.html
new file mode 100644
index 0000000..3b55b76
--- /dev/null
+++ b/tests/test-webapps/test-jetty-webapp/src/main/webapp/auth.html
@@ -0,0 +1,36 @@
+<HTML>
+  <HEAD>
+    <TITLE>Powered By Jetty - Auth Links</TITLE>
+    <META http-equiv="Pragma" content="no-cache">
+    <META http-equiv="Cache-Control" content="no-cache,no-store">
+  </HEAD>
+<BODY>
+<A HREF="http://jetty.eclipse.org"><IMG SRC="jetty_banner.gif"></A>
+<h1>Jetty Authentication Links</h1>
+<p>
+This page contains several links to test the authentication constraints:
+<ul>
+<li><a href="auth/file.txt">auth/file.txt</a> - Forbidden</li>
+<li><a href="auth/relax.txt">auth/relax.txt</a> - Allowed</li>
+<li><a href="auth2">auth2/index.html</a> - Authenticated (tests FormAuthenticator.setAlwaysSaveUri()) </li>
+<li><a href="dump/auth/noaccess/info">dump/auth/noaccess/*</a> - Forbidden</li>
+<li><a href="dump/auth/relax/info">dump/auth/relax/*</a> - Allowed</li>
+<li><a href="dump/auth/info">dump/auth/*</a> - Authenticated any user with any role</li>
+<li><a href="dump/auth/admin/info">dump/auth/admin/*</a> - Authenticated admin role (<a href="session/?Action=Invalidate">click</a> to invalidate session)</li>
+<li><a href="dump/auth/ssl/info">dump/auth/ssl/*</a> - Confidential</li>
+<li><a href="rego/info">rego/info/*</a> - Authenticated admin role from programmatic security (<a href="session/?Action=Invalidate">click</a> to invalidate session)</li>
+<li><a href="rego2/info">rego2/info/*</a> - Authenticated servlet-administrator role from programmatic security (login as admin/admin, <a href="session/?Action=Invalidate">click</a> to invalidate session)</li>
+<li><a href="login?action=login">login</a> - Programmatically login as the user jetty/jetty</li>
+<li><a href="login?action=x">check login status</a> - Check the request's login status</li>
+<li><a href="login?action=logout">logout</a> - Programmatically logout the logged in user</li>
+<li><a href="login?action=wrong">incorrect login</a> - Programmatically login with incorrect credentials</li>
+</ul>
+<p/>
+<p>
+Usernames/Passwords are jetty/jetty admin/admin & user/password	
+</p>
+<p>
+Return to <a href=".">main menu</a>.
+</p>
+</BODY>
+</HTML>
diff --git a/test-jetty-webapp/src/main/webapp/auth/file.txt b/tests/test-webapps/test-jetty-webapp/src/main/webapp/auth/file.txt
similarity index 100%
rename from test-jetty-webapp/src/main/webapp/auth/file.txt
rename to tests/test-webapps/test-jetty-webapp/src/main/webapp/auth/file.txt
diff --git a/test-jetty-webapp/src/main/webapp/auth/relax.txt b/tests/test-webapps/test-jetty-webapp/src/main/webapp/auth/relax.txt
similarity index 100%
rename from test-jetty-webapp/src/main/webapp/auth/relax.txt
rename to tests/test-webapps/test-jetty-webapp/src/main/webapp/auth/relax.txt
diff --git a/test-jetty-webapp/src/main/webapp/auth2/index.html b/tests/test-webapps/test-jetty-webapp/src/main/webapp/auth2/index.html
similarity index 100%
rename from test-jetty-webapp/src/main/webapp/auth2/index.html
rename to tests/test-webapps/test-jetty-webapp/src/main/webapp/auth2/index.html
diff --git a/test-jetty-webapp/src/main/webapp/cgi-bin/hello.sh b/tests/test-webapps/test-jetty-webapp/src/main/webapp/cgi-bin/hello.sh
old mode 100755
new mode 100644
similarity index 100%
rename from test-jetty-webapp/src/main/webapp/cgi-bin/hello.sh
rename to tests/test-webapps/test-jetty-webapp/src/main/webapp/cgi-bin/hello.sh
diff --git a/tests/test-webapps/test-jetty-webapp/src/main/webapp/chat/index.html b/tests/test-webapps/test-jetty-webapp/src/main/webapp/chat/index.html
new file mode 100644
index 0000000..53e4795
--- /dev/null
+++ b/tests/test-webapps/test-jetty-webapp/src/main/webapp/chat/index.html
@@ -0,0 +1,165 @@
+<html>
+<head>
+    <title>Async Chat</title>
+    <script type='text/javascript'>
+        function $()
+        {
+            return document.getElementById(arguments[0]);
+        }
+        function $F()
+        {
+            return document.getElementById(arguments[0]).value;
+        }
+        function getKeyCode(ev)
+        {
+            if (window.event) return window.event.keyCode;
+            return ev.keyCode;
+        }
+        function xhr(method, uri, body, handler)
+        {
+            var req = (window.XMLHttpRequest) ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP');
+            req.onreadystatechange = function ()
+            {
+                if (req.readyState == 4 && handler)
+                {
+                    eval('var o=' + req.responseText);
+                    handler(o);
+                }
+            }
+            req.open(method, uri, true);
+            req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
+            req.send(body);
+        }
+        function send(user, message, handler, join)
+        {
+            if (message) message = message.replace('%', '%25').replace('&', '%26').replace('=', '%3D');
+            if (user) user = user.replace('%', '%25').replace('&', '%26').replace('=', '%3D');
+            var requestBody = 'user=' + user + (message ? '&message=' + message : '') + (join ? '&join=true' : '');
+            xhr('POST', 'chat', requestBody , handler);
+        }
+        var room = {
+            join: function (name)
+            {
+                this._username = name;
+                $('join').className = 'hidden';
+                $('joined').className = '';
+                $('phrase').focus();
+                send(room._username, 'has joined!', room._poll, true);
+            },
+            chat: function (text)
+            {
+                if (text != null && text.length > 0)
+                    send(room._username, text, room._poll, false);
+            },
+            _poll: function (m)
+            {
+                //console.debug(m);
+                if (m.chat)
+                {
+                    var chat = document.getElementById('chat');
+                    var spanFrom = document.createElement('span');
+                    spanFrom.className = 'from';
+                    spanFrom.innerHTML = m.from + ': ';
+                    var spanText = document.createElement('span');
+                    spanText.className = 'text';
+                    spanText.innerHTML = m.chat;
+                    var lineBreak = document.createElement('br');
+                    chat.appendChild(spanFrom);
+                    chat.appendChild(spanText);
+                    chat.appendChild(lineBreak);
+                    chat.scrollTop = chat.scrollHeight - chat.clientHeight;
+                }
+                send(room._username, null, room._poll, false);
+            },
+            _end: ''
+        };
+    </script>
+    <style type='text/css'>
+        div {
+            border: 0px solid black;
+        }
+
+        div#chat {
+            clear: both;
+            width: 40em;
+            height: 20ex;
+            overflow: auto;
+            background-color: #f0f0f0;
+            padding: 4px;
+            border: 1px solid black;
+        }
+
+        div#input {
+            clear: both;
+            width: 40em;
+            padding: 4px;
+            background-color: #e0e0e0;
+            border: 1px solid black;
+            border-top: 0
+        }
+
+        input#phrase {
+            width: 30em;
+            background-color: #e0f0f0;
+        }
+
+        input#username {
+            width: 14em;
+            background-color: #e0f0f0;
+        }
+
+        div.hidden {
+            display: none;
+        }
+    </style>
+</head>
+<body>
+<div id='chat'></div>
+<div id='input'>
+    <div id='join'>
+        Username: <input id='username' type='text'/><input id='joinB' class='button' type='submit' name='join'
+                                                                value='Join'/>
+    </div>
+    <div id='joined' class='hidden'>
+        Chat: <input id='phrase' type='text'/>
+        <input id='sendB' class='button' type='submit' name='join' value='Send'/>
+    </div>
+</div>
+<script type='text/javascript'>
+    $('username').setAttribute('autocomplete', 'OFF');
+    $('username').onkeyup = function (ev)
+    {
+        var keyc = getKeyCode(ev);
+        if (keyc == 13 || keyc == 10)
+        {
+            room.join($F('username'));
+            return false;
+        }
+        return true;
+    };
+    $('joinB').onclick = function (event)
+    {
+        room.join($F('username'));
+        return false;
+    };
+    $('phrase').setAttribute('autocomplete', 'OFF');
+    $('phrase').onkeyup = function (ev)
+    {
+        var keyc = getKeyCode(ev);
+        if (keyc == 13 || keyc == 10)
+        {
+            room.chat($F('phrase'));
+            $('phrase').value = '';
+            return false;
+        }
+        return true;
+    };
+    $('sendB').onclick = function (event)
+    {
+        room.chat($F('phrase'));
+        $('phrase').value = '';
+        return false;
+    };
+</script>
+</body>
+</html>
diff --git a/test-jetty-webapp/src/main/webapp/d.txt b/tests/test-webapps/test-jetty-webapp/src/main/webapp/d.txt
similarity index 100%
rename from test-jetty-webapp/src/main/webapp/d.txt
rename to tests/test-webapps/test-jetty-webapp/src/main/webapp/d.txt
diff --git a/test-jetty-webapp/src/main/webapp/da.txt b/tests/test-webapps/test-jetty-webapp/src/main/webapp/da.txt
similarity index 100%
rename from test-jetty-webapp/src/main/webapp/da.txt
rename to tests/test-webapps/test-jetty-webapp/src/main/webapp/da.txt
diff --git a/test-jetty-webapp/src/main/webapp/da.txt.gz b/tests/test-webapps/test-jetty-webapp/src/main/webapp/da.txt.gz
similarity index 100%
rename from test-jetty-webapp/src/main/webapp/da.txt.gz
rename to tests/test-webapps/test-jetty-webapp/src/main/webapp/da.txt.gz
diff --git a/test-jetty-webapp/src/main/webapp/dat.txt b/tests/test-webapps/test-jetty-webapp/src/main/webapp/dat.txt
similarity index 100%
rename from test-jetty-webapp/src/main/webapp/dat.txt
rename to tests/test-webapps/test-jetty-webapp/src/main/webapp/dat.txt
diff --git a/test-jetty-webapp/src/main/webapp/data.txt b/tests/test-webapps/test-jetty-webapp/src/main/webapp/data.txt
similarity index 100%
rename from test-jetty-webapp/src/main/webapp/data.txt
rename to tests/test-webapps/test-jetty-webapp/src/main/webapp/data.txt
diff --git a/test-jetty-webapp/src/main/webapp/data.txt.gz b/tests/test-webapps/test-jetty-webapp/src/main/webapp/data.txt.gz
similarity index 100%
rename from test-jetty-webapp/src/main/webapp/data.txt.gz
rename to tests/test-webapps/test-jetty-webapp/src/main/webapp/data.txt.gz
diff --git a/test-jetty-webapp/src/main/webapp/error404.html b/tests/test-webapps/test-jetty-webapp/src/main/webapp/error404.html
similarity index 100%
rename from test-jetty-webapp/src/main/webapp/error404.html
rename to tests/test-webapps/test-jetty-webapp/src/main/webapp/error404.html
diff --git a/test-jetty-webapp/src/main/webapp/favicon.ico b/tests/test-webapps/test-jetty-webapp/src/main/webapp/favicon.ico
similarity index 100%
rename from test-jetty-webapp/src/main/webapp/favicon.ico
rename to tests/test-webapps/test-jetty-webapp/src/main/webapp/favicon.ico
diff --git a/tests/test-webapps/test-jetty-webapp/src/main/webapp/index.html b/tests/test-webapps/test-jetty-webapp/src/main/webapp/index.html
new file mode 100644
index 0000000..08ab563
--- /dev/null
+++ b/tests/test-webapps/test-jetty-webapp/src/main/webapp/index.html
@@ -0,0 +1,72 @@
+<HTML>
+  <HEAD>
+    <TITLE>Powered By Jetty</TITLE>
+    <META http-equiv="Pragma" content="no-cache">
+    <META http-equiv="Cache-Control" content="no-cache,no-store">
+    <style  type="text/css">
+      body {
+        font-family: sans-serif;
+      }
+    </style>
+  </HEAD>
+<BODY>
+  <A HREF="http://www.eclipse.org/jetty"><IMG SRC="jetty_banner.gif"></A>
+  <br/>
+  <b><a href="http://localhost:8080/">Demo Home</a></b>
+  <hr/>
+  <center><span style="color:red; font-variant:small-caps; font-weight:bold">Test Web Application Only - Do NOT Deploy in Production</span> </center>
+
+<h1>Welcome to Jetty 9</h1>
+<p>
+This is the Test webapp for the Jetty 9 HTTP Server and Servlet Container.  
+It is configured as a jetty base directory in $JETTY_HOME/demo_base.
+</p>
+
+<h2>Jetty Tests:</h2>
+<table border=0>
+<tr valign=top><td>
+<ul>
+<li>Servlet: <a href="hello/">Hello World</a></li>
+<li>Dump Servlets: <ul>
+  <li><a href="dump/info">Request</a></li>
+  <li><a href="session/">Session</a></li>
+  <li><a href="cookie/">Cookie</a></li>
+  </ul></li>
+<li>Comet Chat Examples: 
+  <ul>
+  <li><a href="chat/">Long Polling</a></li>
+  <li><a href="ws">WebSocket (Jetty API)</a></li>
+  <li><a href="javax.websocket">WebSocket (javax.websocket)</a></li>
+  </ul>
+  </li>
+<li>JSP: <a href="jsp/">examples</a></li>
+
+</ul></td><td><ul>
+<li><a href="auth.html">Authentication</a></li>
+<li><a href="dispatch">Dispatcher Servlet</a></li>
+<li><a href="rewrite/">Request Rewrite Servlet</a></li>
+<li>Static content: <ul>
+    <li><a href="d.txt">tiny</a></li>
+    <li><a href="da.txt">small</a></li>
+    <li><a href="dat.txt">medium</a></li>
+    <li><a href="data.txt">large</a></li>
+    <li><a href="data.txt.gz">large gziped</a></li>
+    </ul></li>
+</ul>
+</td></tr></table>
+
+<h2>Useful links:</h2>
+<ul>
+<li><a
+href="http://git.eclipse.org/c/jetty/org.eclipse.jetty.project.git/tree/tests/test-webapps/test-jetty-webapp">Source
+tree of this webapp</a></li>
+<li><a href="http://www.eclipse.org/jetty">Jetty project home</a></li>
+<li><a href="http://www.eclipse.org/jetty/documentation/current/">Documentation</a></li>
+<li><a href="http://www.webtide.com">Commercial Support</a></li>
+</ul>
+</p>
+
+<hr/>
+<center> <a href="http://www.eclipse.org/jetty"><img style="border:0" src="small_powered_by.gif"/></a></center>
+</BODY>
+</HTML>
diff --git a/tests/test-webapps/test-jetty-webapp/src/main/webapp/javax.websocket/index.html b/tests/test-webapps/test-jetty-webapp/src/main/webapp/javax.websocket/index.html
new file mode 100644
index 0000000..7172f47
--- /dev/null
+++ b/tests/test-webapps/test-jetty-webapp/src/main/webapp/javax.websocket/index.html
@@ -0,0 +1,112 @@
+
+<html><head>
+    <title>WebSocket Chat</title>
+    <script type='text/javascript'>
+
+      if (!window.WebSocket && window.MozWebSocket)
+        window.WebSocket=window.MozWebSocket;
+      if (!window.WebSocket)
+        alert("WebSocket not supported by this browser");
+    
+      function $() { return document.getElementById(arguments[0]); }
+      function $F() { return document.getElementById(arguments[0]).value; }
+      
+      function getKeyCode(ev) { if (window.event) return window.event.keyCode; return ev.keyCode; } 
+      
+      var room = {
+        join: function(name) {
+          this._username=name;
+          var location = document.location.toString().replace('http://','ws://').replace('https://','wss://');
+          this._ws=new WebSocket(location,"chat");
+          this._ws.onopen=this._onopen;
+          this._ws.onmessage=this._onmessage;
+          this._ws.onclose=this._onclose;
+        },
+        
+        _onopen: function(){
+          $('join').className='hidden';
+          $('joined').className='';
+          $('phrase').focus();
+          room._send(room._username,'has joined!');
+        },
+        
+        _send: function(user,message){
+          user=user.replace(':','_');
+          if (this._ws)
+            this._ws.send(user+':'+message);
+        },
+      
+        chat: function(text) {
+          if (text != null && text.length>0 )
+              room._send(room._username,text);
+        },
+        
+        _onmessage: function(m) {
+          if (m.data){
+            var c=m.data.indexOf(':');
+            var from=m.data.substring(0,c).replace('<','<').replace('>','>');
+            var text=m.data.substring(c+1).replace('<','<').replace('>','>');
+            
+            var chat=$('chat');
+            var spanFrom = document.createElement('span');
+            spanFrom.className='from';
+            spanFrom.innerHTML=from+': ';
+            var spanText = document.createElement('span');
+            spanText.className='text';
+            spanText.innerHTML=text;
+            var lineBreak = document.createElement('br');
+            chat.appendChild(spanFrom);
+            chat.appendChild(spanText);
+            chat.appendChild(lineBreak);
+            chat.scrollTop = chat.scrollHeight - chat.clientHeight;   
+          }
+        },
+        
+        _onclose: function(m) {
+          this._ws=null;
+          $('join').className='';
+          $('joined').className='hidden';
+          $('username').focus();
+          $('chat').innerHTML='';
+        }
+        
+      };
+      
+    </script>
+    <style type='text/css'>
+    div { border: 0px solid black; }
+    div#chat { clear: both; width: 40em; height: 20ex; overflow: auto; background-color: #f0f0f0; padding: 4px; border: 1px solid black; }
+    div#input { clear: both; width: 40em; padding: 4px; background-color: #e0e0e0; border: 1px solid black; border-top: 0px }
+    input#phrase { width:30em; background-color: #e0f0f0; }
+    input#username { width:14em; background-color: #e0f0f0; }
+    div.hidden { display: none; }
+    span.from { font-weight: bold; }
+    span.alert { font-style: italic; }
+    </style>
+</head><body>
+<div id='chat'></div>
+<div id='input'>
+  <div id='join' >
+    Username: <input id='username' type='text'/><input id='joinB' class='button' type='submit' name='join' value='Join'/>
+  </div>
+  <div id='joined' class='hidden'>
+    Chat: <input id='phrase' type='text'/>
+    <input id='sendB' class='button' type='submit' name='join' value='Send'/>
+  </div>
+</div>
+<script type='text/javascript'>
+$('username').setAttribute('autocomplete','OFF');
+$('username').onkeyup = function(ev) { var keyc=getKeyCode(ev); if (keyc==13 || keyc==10) { room.join($F('username')); return false; } return true; } ;        
+$('joinB').onclick = function(event) { room.join($F('username')); return false; };
+$('phrase').setAttribute('autocomplete','OFF');
+$('phrase').onkeyup = function(ev) {   var keyc=getKeyCode(ev); if (keyc==13 || keyc==10) { room.chat($F('phrase')); $('phrase').value=''; return false; } return true; };
+$('sendB').onclick = function(event) { room.chat($F('phrase')); $('phrase').value=''; return false; };
+</script>
+
+<p>
+This is a demonstration of the Jetty's support for javax.websocket server sockets.
+</p>
+</body></html>
+        
+        
+        
diff --git a/test-jetty-webapp/src/main/webapp/jetty_banner.gif b/tests/test-webapps/test-jetty-webapp/src/main/webapp/jetty_banner.gif
similarity index 100%
copy from test-jetty-webapp/src/main/webapp/jetty_banner.gif
copy to tests/test-webapps/test-jetty-webapp/src/main/webapp/jetty_banner.gif
diff --git a/test-jetty-webapp/src/main/webapp/jsp/bean1.jsp b/tests/test-webapps/test-jetty-webapp/src/main/webapp/jsp/bean1.jsp
similarity index 100%
rename from test-jetty-webapp/src/main/webapp/jsp/bean1.jsp
rename to tests/test-webapps/test-jetty-webapp/src/main/webapp/jsp/bean1.jsp
diff --git a/test-jetty-webapp/src/main/webapp/jsp/bean2.jsp b/tests/test-webapps/test-jetty-webapp/src/main/webapp/jsp/bean2.jsp
similarity index 100%
rename from test-jetty-webapp/src/main/webapp/jsp/bean2.jsp
rename to tests/test-webapps/test-jetty-webapp/src/main/webapp/jsp/bean2.jsp
diff --git a/test-jetty-webapp/src/main/webapp/jsp/dump.jsp b/tests/test-webapps/test-jetty-webapp/src/main/webapp/jsp/dump.jsp
similarity index 100%
rename from test-jetty-webapp/src/main/webapp/jsp/dump.jsp
rename to tests/test-webapps/test-jetty-webapp/src/main/webapp/jsp/dump.jsp
diff --git a/test-jetty-webapp/src/main/webapp/jsp/expr.jsp b/tests/test-webapps/test-jetty-webapp/src/main/webapp/jsp/expr.jsp
similarity index 100%
rename from test-jetty-webapp/src/main/webapp/jsp/expr.jsp
rename to tests/test-webapps/test-jetty-webapp/src/main/webapp/jsp/expr.jsp
diff --git a/test-jetty-webapp/src/main/webapp/jsp/foo/foo.jsp b/tests/test-webapps/test-jetty-webapp/src/main/webapp/jsp/foo/foo.jsp
similarity index 100%
rename from test-jetty-webapp/src/main/webapp/jsp/foo/foo.jsp
rename to tests/test-webapps/test-jetty-webapp/src/main/webapp/jsp/foo/foo.jsp
diff --git a/test-jetty-webapp/src/main/webapp/jsp/index.html b/tests/test-webapps/test-jetty-webapp/src/main/webapp/jsp/index.html
similarity index 100%
rename from test-jetty-webapp/src/main/webapp/jsp/index.html
rename to tests/test-webapps/test-jetty-webapp/src/main/webapp/jsp/index.html
diff --git a/test-jetty-webapp/src/main/webapp/jsp/jstl.jsp b/tests/test-webapps/test-jetty-webapp/src/main/webapp/jsp/jstl.jsp
similarity index 100%
rename from test-jetty-webapp/src/main/webapp/jsp/jstl.jsp
rename to tests/test-webapps/test-jetty-webapp/src/main/webapp/jsp/jstl.jsp
diff --git a/test-jetty-webapp/src/main/webapp/jsp/tag.jsp b/tests/test-webapps/test-jetty-webapp/src/main/webapp/jsp/tag.jsp
similarity index 100%
rename from test-jetty-webapp/src/main/webapp/jsp/tag.jsp
rename to tests/test-webapps/test-jetty-webapp/src/main/webapp/jsp/tag.jsp
diff --git a/test-jetty-webapp/src/main/webapp/jsp/tag2.jsp b/tests/test-webapps/test-jetty-webapp/src/main/webapp/jsp/tag2.jsp
similarity index 100%
rename from test-jetty-webapp/src/main/webapp/jsp/tag2.jsp
rename to tests/test-webapps/test-jetty-webapp/src/main/webapp/jsp/tag2.jsp
diff --git a/test-jetty-webapp/src/main/webapp/jsp/tagfile.jsp b/tests/test-webapps/test-jetty-webapp/src/main/webapp/jsp/tagfile.jsp
similarity index 100%
rename from test-jetty-webapp/src/main/webapp/jsp/tagfile.jsp
rename to tests/test-webapps/test-jetty-webapp/src/main/webapp/jsp/tagfile.jsp
diff --git a/test-jetty-webapp/src/main/webapp/logon.html b/tests/test-webapps/test-jetty-webapp/src/main/webapp/logon.html
similarity index 100%
rename from test-jetty-webapp/src/main/webapp/logon.html
rename to tests/test-webapps/test-jetty-webapp/src/main/webapp/logon.html
diff --git a/test-jetty-webapp/src/main/webapp/logonError.html b/tests/test-webapps/test-jetty-webapp/src/main/webapp/logonError.html
similarity index 100%
rename from test-jetty-webapp/src/main/webapp/logonError.html
rename to tests/test-webapps/test-jetty-webapp/src/main/webapp/logonError.html
diff --git a/tests/test-webapps/test-jetty-webapp/src/main/webapp/remote.html b/tests/test-webapps/test-jetty-webapp/src/main/webapp/remote.html
new file mode 100644
index 0000000..d892bed
--- /dev/null
+++ b/tests/test-webapps/test-jetty-webapp/src/main/webapp/remote.html
@@ -0,0 +1,32 @@
+<HTML>
+  <HEAD>
+    <TITLE>Powered By Jetty</TITLE>
+    <META http-equiv="Pragma" content="no-cache">
+    <META http-equiv="Cache-Control" content="no-cache,no-store">
+  </HEAD>
+<BODY>
+<A HREF="http://www.eclipse.org/jetty"><IMG SRC="jetty_banner.gif"></A>
+<h1>Welcome to Jetty 9 - REMOTE ACCESS!!</h1>
+<p>
+This is the Test webapp for the Jetty 9 HTTP Server and Servlet Container.  
+For more information about Jetty, please visit our
+<a href="http://www.eclipse.org/jetty">website</a>
+or <a href="http://www.eclipse.org/jetty/documentation/current/">documentation</a>. 
+Commercial support for Jetty is available via <a href="http://www.webtide.com">webtide</a>.
+</p>
+<p>
+This test context serves several demo filters and servlets
+that are not safe for deployment on the internet, since (by design) they contain 
+cross domain scripting vulnerabilities and reveal private information.  This page
+is displayed because you have accessed this context from a non local IP address.
+</p>
+<p>
+You can disable the remote address checking by editing demo-base/webapps/test.d/override-web.xml, uncommenting the declaration of the TestFilter, and changing the
+"remote" init parameter to "true".
+</p>
+<p>
+This webapp is deployed in $JETTY_HOME/demo-base/webapps/test.war and configured by $JETTY_HOME/demo-base/webapps/test.xml and $JETTY_HOME/demo-base/webapps/test.d/override-web.xml
+</p>
+
+</BODY>
+</HTML>
diff --git a/tests/test-webapps/test-jetty-webapp/src/main/webapp/rewrite/index.html b/tests/test-webapps/test-jetty-webapp/src/main/webapp/rewrite/index.html
new file mode 100644
index 0000000..613e13d
--- /dev/null
+++ b/tests/test-webapps/test-jetty-webapp/src/main/webapp/rewrite/index.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+<meta http-equiv="Cache-Control" content="no-cache">
+<title>RewriteHandler</title>
+</head>
+<body>
+<h1>Rewrite not enabled</h1>
+<p>The rewrite handler is currently not enabled. To enable this demo, start up Jetty with:</p>
+<code>java -jar start.jar --module=rewrite</code>
+</body>
+</html>
diff --git a/test-jetty-webapp/src/main/webapp/rewrite/info.html b/tests/test-webapps/test-jetty-webapp/src/main/webapp/rewrite/info.html
similarity index 100%
rename from test-jetty-webapp/src/main/webapp/rewrite/info.html
rename to tests/test-webapps/test-jetty-webapp/src/main/webapp/rewrite/info.html
diff --git a/tests/test-webapps/test-jetty-webapp/src/main/webapp/small_powered_by.gif b/tests/test-webapps/test-jetty-webapp/src/main/webapp/small_powered_by.gif
new file mode 100644
index 0000000..c5dd443
Binary files /dev/null and b/tests/test-webapps/test-jetty-webapp/src/main/webapp/small_powered_by.gif differ
diff --git a/test-jetty-webapp/src/main/webapp/ws/index.html b/tests/test-webapps/test-jetty-webapp/src/main/webapp/ws/index.html
similarity index 100%
rename from test-jetty-webapp/src/main/webapp/ws/index.html
rename to tests/test-webapps/test-jetty-webapp/src/main/webapp/ws/index.html
diff --git a/tests/test-webapps/test-jetty-webapp/src/test/java/org/eclipse/jetty/ChatServletTest.java b/tests/test-webapps/test-jetty-webapp/src/test/java/org/eclipse/jetty/ChatServletTest.java
new file mode 100644
index 0000000..a0d02e7
--- /dev/null
+++ b/tests/test-webapps/test-jetty-webapp/src/test/java/org/eclipse/jetty/ChatServletTest.java
@@ -0,0 +1,94 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jetty.servlet.ServletTester;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import com.acme.ChatServlet;
+
+ at RunWith(JUnit4.class)
+public class ChatServletTest
+{
+
+    private final ServletTester tester = new ServletTester();
+
+    @Before
+    public void setUp() throws Exception
+    {
+        tester.setContextPath("/");
+
+        ServletHolder dispatch = tester.addServlet(ChatServlet.class, "/chat/*");
+        dispatch.setInitParameter("asyncTimeout", "500");
+        tester.start();
+    }
+
+    @After
+    public void tearDown() throws Exception
+    {
+        tester.stop();
+    }
+
+    @Test
+    public void testLogin() throws Exception
+    {
+        assertResponse("user=test&join=true&message=has%20joined!", "{\"from\":\"test\",\"chat\":\"has joined!\"}");
+    }
+
+    @Test
+    public void testChat() throws Exception
+    {
+        assertResponse("user=test&join=true&message=has%20joined!", "{\"from\":\"test\",\"chat\":\"has joined!\"}");
+        String response = tester.getResponses(createRequestString("user=test&message=message"));
+        assertThat(response.contains("{"), is(false)); // make sure we didn't get a json body
+    }
+
+    @Test
+    public void testPoll() throws Exception
+    {
+        assertResponse("user=test", "{action:\"poll\"}");
+    }
+
+    private void assertResponse(String requestBody, String expectedResponse) throws Exception
+    {
+        String response = tester.getResponses(createRequestString(requestBody));
+        assertThat(response.contains(expectedResponse), is(true));
+    }
+
+    private String createRequestString(String body)
+    {
+        StringBuilder req1 = new StringBuilder();
+        req1.append("POST /chat/ HTTP/1.1\r\n");
+        req1.append("Host: tester\r\n");
+        req1.append("Content-length: " + body.length() + "\r\n");
+        req1.append("Content-type: application/x-www-form-urlencoded\r\n");
+        req1.append("Connection: close\r\n");
+        req1.append("\r\n");
+        req1.append(body);
+        return req1.toString();
+    }
+}
diff --git a/tests/test-webapps/test-jetty-webapp/src/test/java/org/eclipse/jetty/DispatchServletTest.java b/tests/test-webapps/test-jetty-webapp/src/test/java/org/eclipse/jetty/DispatchServletTest.java
new file mode 100644
index 0000000..222e240
--- /dev/null
+++ b/tests/test-webapps/test-jetty-webapp/src/test/java/org/eclipse/jetty/DispatchServletTest.java
@@ -0,0 +1,145 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import org.eclipse.jetty.servlet.DefaultServlet;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jetty.servlet.ServletTester;
+import org.hamcrest.Matchers;
+import org.junit.Test;
+
+import com.acme.DispatchServlet;
+
+/**
+ * Simple tests against DispatchServlet.
+ */
+public class DispatchServletTest
+{
+    /**
+     * As filed in JETTY-978.
+     *
+     * Security problems in demo dispatch servlet.
+     *
+     * <blockquote>
+     * <p>
+     * The dispatcher servlet (com.acme.DispatchServlet) is prone to a Denial of
+     * Service vulnerability.
+     * </p>
+     * <p>
+     * This example servlet is meant to be used as a resources dispatcher,
+     * however a malicious aggressor may abuse this functionality in order to
+     * cause a recursive inclusion. In details, it is possible to abuse the
+     * method com.acme.DispatchServlet.doGet(DispatchServlet.java:203) forcing
+     * the application to recursively include the "Dispatch" servlet.
+     * </p>
+     * <p>
+     * Dispatch com.acme.DispatchServlet 1 Dispatch /dispatch/* As a result, it
+     * is possible to trigger a "java.lang.StackOverflowError" and consequently
+     * an internal server error (500).
+     * </p>
+     * <p>
+     * Multiple requests may easily affect the availability of the servlet
+     * container. Since this attack can cause the server to consume resources in
+     * a non-linear relationship to the size of inputs, it should be considered
+     * as a server flaw.
+     * </p>
+     * <p>
+     * The vulnerability seems confined to the example servlet and it does not
+     * afflict the Jetty's core."
+     * </p>
+     * </blockquote>
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testSelfRefForwardDenialOfService() throws Exception
+    {
+        ServletTester tester = new ServletTester();
+        tester.setContextPath("/tests");
+
+        ServletHolder dispatch = tester.addServlet(DispatchServlet.class,"/dispatch/*");
+        tester.addServlet(DefaultServlet.class,"/");
+        tester.start();
+
+        StringBuilder req1 = new StringBuilder();
+        req1.append("GET /tests/dispatch/includeN/").append(dispatch.getName()).append(" HTTP/1.1\n");
+        req1.append("Host: tester\n");
+        req1.append("Connection: close\n");
+        req1.append("\n");
+
+        String response = tester.getResponses(req1.toString());
+
+        String msg = "Response code on SelfRefDoS";
+
+        assertFalse(msg + " should not be code 500.",response.startsWith("HTTP/1.1 500 "));
+        assertTrue(msg + " should return error code 403 (Forbidden)", response.startsWith("HTTP/1.1 403 "));
+    }
+
+    @Test
+    public void testSelfRefDeep() throws Exception
+    {
+        ServletTester tester = new ServletTester();
+        tester.setContextPath("/tests");
+        tester.addServlet(DispatchServlet.class,"/dispatch/*");
+        tester.addServlet(DefaultServlet.class,"/");
+        tester.start();
+
+        String selfRefs[] =
+        { "/dispatch/forward", "/dispatch/includeS", "/dispatch/includeW", "/dispatch/includeN", };
+
+        /*
+         * Number of nested dispatch requests. 220 is a good value, as it won't
+         * trigger an Error 413 response (Entity too large). Anything larger
+         * than 220 will trigger a 413 response.
+         */
+        int nestedDepth = 220;
+
+        for (String selfRef : selfRefs)
+        {
+            StringBuilder req1 = new StringBuilder();
+            req1.append("GET /tests");
+            for (int i = 0; i < nestedDepth; i++)
+            {
+                req1.append(selfRef);
+            }
+
+            req1.append("/ HTTP/1.1\n");
+            req1.append("Host: tester\n");
+            req1.append("Connection: close\n");
+            req1.append("\n");
+
+            String response = tester.getResponses(req1.toString());
+
+            StringBuilder msg = new StringBuilder();
+            msg.append("Response code on nested \"").append(selfRef).append("\"");
+            msg.append(" (depth:").append(nestedDepth).append(")");
+
+            assertFalse(msg + " should not be code 413 (Request Entity Too Large)," +
+                    "the nestedDepth in the TestCase is too large (reduce it)",
+                    response.startsWith("HTTP/1.1 413 "));
+
+            assertFalse(msg + " should not be code 500.", response.startsWith("HTTP/1.1 500 "));
+            assertThat(response,Matchers.startsWith("HTTP/1.1 403 "));
+        }
+    }
+}
diff --git a/tests/test-webapps/test-jetty-webapp/src/test/java/org/eclipse/jetty/TestServer.java b/tests/test-webapps/test-jetty-webapp/src/test/java/org/eclipse/jetty/TestServer.java
new file mode 100644
index 0000000..495dfb4
--- /dev/null
+++ b/tests/test-webapps/test-jetty-webapp/src/test/java/org/eclipse/jetty/TestServer.java
@@ -0,0 +1,219 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.management.ManagementFactory;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.jmx.MBeanContainer;
+import org.eclipse.jetty.security.HashLoginService;
+import org.eclipse.jetty.server.ForwardedRequestCustomizer;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.HttpConnectionFactory;
+import org.eclipse.jetty.server.NCSARequestLog;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.SecureRequestCustomizer;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.ContextHandlerCollection;
+import org.eclipse.jetty.server.handler.DefaultHandler;
+import org.eclipse.jetty.server.handler.HandlerCollection;
+import org.eclipse.jetty.server.handler.HandlerWrapper;
+import org.eclipse.jetty.server.handler.RequestLogHandler;
+import org.eclipse.jetty.server.handler.ResourceHandler;
+import org.eclipse.jetty.server.session.HashSessionManager;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.log.StdErrLog;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.eclipse.jetty.webapp.WebAppContext;
+import org.junit.Ignore;
+
+ at Ignore("Not a test case")
+public class TestServer
+{
+    private static final Logger LOG = Log.getLogger(TestServer.class);
+
+    public static void main(String[] args) throws Exception
+    {
+        ((StdErrLog)Log.getLog()).setSource(false);
+
+        String jetty_root = "../../..";
+
+        // Setup Threadpool
+        QueuedThreadPool threadPool = new QueuedThreadPool();
+        threadPool.setMaxThreads(100);
+
+        // Setup server
+        Server server = new Server(threadPool);
+        server.manage(threadPool);
+
+        // Setup JMX
+        MBeanContainer mbContainer=new MBeanContainer(ManagementFactory.getPlatformMBeanServer());
+        server.addBean(mbContainer);
+        server.addBean(Log.getLog());
+        
+
+        // Common HTTP configuration
+        HttpConfiguration config = new HttpConfiguration();
+        config.setSecurePort(8443);
+        config.addCustomizer(new ForwardedRequestCustomizer());
+        config.addCustomizer(new SecureRequestCustomizer());
+        config.setSendDateHeader(true);
+        config.setSendServerVersion(true);
+        
+        
+        // Http Connector
+        HttpConnectionFactory http = new HttpConnectionFactory(config);
+        ServerConnector httpConnector = new ServerConnector(server,http);
+        httpConnector.setPort(8080);
+        httpConnector.setIdleTimeout(30000);
+        server.addConnector(httpConnector);
+
+        /*
+        // SSL configurations
+        SslContextFactory sslContextFactory = new SslContextFactory();
+        sslContextFactory.setKeyStorePath(jetty_root + "/jetty-server/src/main/config/etc/keystore");
+        sslContextFactory.setKeyStorePassword("OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4");
+        sslContextFactory.setKeyManagerPassword("OBF:1u2u1wml1z7s1z7a1wnl1u2g");
+        sslContextFactory.setTrustStorePath(jetty_root + "/jetty-server/src/main/config/etc/keystore");
+        sslContextFactory.setTrustStorePassword("OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4");
+        sslContextFactory.setExcludeCipherSuites(
+                "SSL_RSA_WITH_DES_CBC_SHA",
+                "SSL_DHE_RSA_WITH_DES_CBC_SHA",
+                "SSL_DHE_DSS_WITH_DES_CBC_SHA",
+                "SSL_RSA_EXPORT_WITH_RC4_40_MD5",
+                "SSL_RSA_EXPORT_WITH_DES40_CBC_SHA",
+                "SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA",
+                "SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA");
+        
+        
+        // Spdy Connector
+        SPDYServerConnectionFactory.checkNPNAvailable();
+        PushStrategy push = new ReferrerPushStrategy();
+        HTTPSPDYServerConnectionFactory spdy2 = new HTTPSPDYServerConnectionFactory(2,config,push);
+        spdy2.setInputBufferSize(8192);
+        spdy2.setInitialWindowSize(32768);
+        HTTPSPDYServerConnectionFactory spdy3 = new HTTPSPDYServerConnectionFactory(3,config,push);
+        spdy2.setInputBufferSize(8192);
+        NPNServerConnectionFactory npn = new NPNServerConnectionFactory(spdy3.getProtocol(),spdy2.getProtocol(),http.getProtocol());
+        npn.setDefaultProtocol(http.getProtocol());
+        npn.setInputBufferSize(1024); 
+        SslConnectionFactory ssl = new SslConnectionFactory(sslContextFactory,npn.getProtocol()); 
+        ServerConnector spdyConnector = new ServerConnector(server,ssl,npn,spdy3,spdy2,http);
+        spdyConnector.setPort(8443);
+        spdyConnector.setIdleTimeout(15000);
+        server.addConnector(spdyConnector);
+        
+        */
+        
+        // Handlers
+        HandlerCollection handlers = new HandlerCollection();
+        ContextHandlerCollection contexts = new ContextHandlerCollection();
+        RequestLogHandler requestLogHandler = new RequestLogHandler();
+        handlers.setHandlers(new Handler[]
+        { contexts, new DefaultHandler(), requestLogHandler });
+
+        // Add restart handler to test the ability to save sessions and restart
+        RestartHandler restart = new RestartHandler();
+        restart.setHandler(handlers);
+
+        server.setHandler(restart);
+
+
+        // Setup context
+        HashLoginService login = new HashLoginService();
+        login.setName("Test Realm");
+        login.setConfig(jetty_root + "/tests/test-webapps/test-jetty-webapp/src/main/config/demo-base/etc/realm.properties");
+        server.addBean(login);
+
+        File log=File.createTempFile("jetty-yyyy_mm_dd", "log");
+        NCSARequestLog requestLog = new NCSARequestLog(log.toString());
+        requestLog.setExtended(false);
+        requestLogHandler.setRequestLog(requestLog);
+
+        server.setStopAtShutdown(true);
+
+        WebAppContext webapp = new WebAppContext();
+        webapp.setParentLoaderPriority(true);
+        webapp.setResourceBase("./src/main/webapp");
+        webapp.setAttribute("testAttribute","testValue");
+        File sessiondir=File.createTempFile("sessions",null);
+        if (sessiondir.exists())
+            sessiondir.delete();
+        sessiondir.mkdir();
+        sessiondir.deleteOnExit();
+        ((HashSessionManager)webapp.getSessionHandler().getSessionManager()).setStoreDirectory(sessiondir);
+        ((HashSessionManager)webapp.getSessionHandler().getSessionManager()).setSavePeriod(10);
+
+        contexts.addHandler(webapp);
+
+        ContextHandler srcroot = new ContextHandler();
+        srcroot.setResourceBase(".");
+        srcroot.setHandler(new ResourceHandler());
+        srcroot.setContextPath("/src");
+        contexts.addHandler(srcroot);
+
+        server.start();
+        server.join();
+    }
+
+    private static class RestartHandler extends HandlerWrapper
+    {
+        /* ------------------------------------------------------------ */
+        /**
+         * @see org.eclipse.jetty.server.handler.HandlerWrapper#handle(java.lang.String, org.eclipse.jetty.server.Request, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
+         */
+        @Override
+        public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+        {
+            super.handle(target,baseRequest,request,response);
+            if (Boolean.valueOf(request.getParameter("restart")))
+            {
+                final Server server=getServer();
+
+                new Thread()
+                {
+                    @Override
+                    public void run()
+                    {
+                        try
+                        {
+                            Thread.sleep(100);
+                            server.stop();
+                            Thread.sleep(100);
+                            server.start();
+                        }
+                        catch(Exception e)
+                        {
+                            LOG.warn(e);
+                        }
+                    }
+                }.start();
+            }
+        }
+    }
+}
diff --git a/tests/test-webapps/test-jndi-webapp/pom.xml b/tests/test-webapps/test-jndi-webapp/pom.xml
new file mode 100644
index 0000000..5afa240
--- /dev/null
+++ b/tests/test-webapps/test-jndi-webapp/pom.xml
@@ -0,0 +1,149 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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>
+  <parent>
+    <groupId>org.eclipse.jetty.tests</groupId>
+    <artifactId>test-webapps-parent</artifactId>
+    <version>9.2.13.v20150730</version>
+  </parent>
+  <artifactId>test-jndi-webapp</artifactId>
+  <name>Jetty Tests :: WebApp :: JNDI</name>
+  <packaging>war</packaging>
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-deploy-plugin</artifactId>
+        <configuration>
+          <!-- DO NOT DEPLOY (or Release) -->
+          <skip>true</skip>
+        </configuration>
+      </plugin>
+      <plugin>
+        <artifactId>maven-antrun-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>generate-xml-files</id>
+            <phase>process-resources</phase>
+            <configuration>
+              <tasks>
+                <concat destfile="${project.build.directory}/plugin-context.xml">
+                  <filelist dir="src/main/templates/" files="plugin-context-header.xml,env-definitions.xml" />
+                </concat>
+                <concat destfile="${project.build.directory}/test-jndi.xml">
+                  <filelist dir="src/main/templates/" files="jetty-test-jndi-header.xml,env-definitions.xml" />
+                </concat>
+              </tasks>
+            </configuration>
+            <goals>
+              <goal>run</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+       <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-dependency-plugin</artifactId>
+        <executions>
+          <execution>
+            <phase>package</phase>
+            <goals>
+              <goal>copy</goal>
+            </goals>
+            <configuration>
+              <artifactItems>
+                <artifactItem>
+                  <groupId>org.eclipse.jetty.tests</groupId>
+                  <artifactId>test-mock-resources</artifactId>
+                  <version>${project.version}</version>
+                  <type>jar</type>
+                  <includes>**</includes>
+                  <overWrite>true</overWrite>
+                  <outputDirectory>${project.build.directory}/lib/jndi</outputDirectory>
+                </artifactItem>
+                <artifactItem>
+                  <groupId>org.eclipse.jetty.orbit</groupId>
+                  <artifactId>javax.mail.glassfish</artifactId>
+                  <version>1.4.1.v201005082020</version>
+                  <type>jar</type>
+                  <includes>**</includes>
+                  <overWrite>true</overWrite>
+                  <outputDirectory>${project.build.directory}/lib/jndi</outputDirectory>
+                </artifactItem>
+              </artifactItems>
+          </configuration>
+          </execution>
+        </executions>
+       </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-assembly-plugin</artifactId>
+        <version>2.2-beta-3</version>
+        <executions>
+          <execution>
+            <phase>package</phase>
+            <goals>
+              <goal>single</goal>
+            </goals>
+            <configuration>
+              <descriptors>
+                <descriptor>src/main/assembly/config.xml</descriptor>
+              </descriptors>
+              <!-- filters> <filter>src/filters/filter.properties</filter> 
+                </filters -->
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-war-plugin</artifactId>
+        <configuration>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.eclipse.jetty</groupId>
+        <artifactId>jetty-maven-plugin</artifactId>
+        <version>${project.version}</version>
+        <configuration>
+          <webAppXml>${project.build.directory}/plugin-context.xml</webAppXml>
+          <webAppConfig>
+            <war>src/main/webapp</war>
+            <descriptor>src/main/webapp/WEB-INF/web.xml</descriptor>
+            <contextPath>/test-jndi</contextPath>
+          </webAppConfig>
+        </configuration>
+        <dependencies>
+          <dependency>
+            <groupId>org.eclipse.jetty.tests</groupId>
+            <artifactId>test-mock-resources</artifactId>
+            <version>${project.version}</version>
+          </dependency>
+          <dependency>
+            <groupId>org.eclipse.jetty.orbit</groupId>
+            <artifactId>javax.mail.glassfish</artifactId>
+            <version>1.4.1.v201005082020</version>
+          </dependency>
+        </dependencies>
+      </plugin>
+    </plugins>
+  </build>
+ <dependencies>
+    <dependency>
+      <groupId>javax.transaction</groupId>
+      <artifactId>javax.transaction-api</artifactId>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>javax.servlet</groupId>
+      <artifactId>javax.servlet-api</artifactId>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.orbit</groupId>
+      <artifactId>javax.mail.glassfish</artifactId>
+      <version>1.4.1.v201005082020</version>
+      <scope>provided</scope>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/tests/test-webapps/test-jndi-webapp/src/main/assembly/config.xml b/tests/test-webapps/test-jndi-webapp/src/main/assembly/config.xml
new file mode 100644
index 0000000..7946043
--- /dev/null
+++ b/tests/test-webapps/test-jndi-webapp/src/main/assembly/config.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<assembly>
+  <id>config</id>
+  <includeBaseDirectory>false</includeBaseDirectory>
+  <formats>
+    <format>jar</format>
+  </formats>
+  <fileSets>
+    <fileSet>
+      <directory>src/main/config</directory>
+      <outputDirectory></outputDirectory>
+      <includes>
+        <include>**</include>
+      </includes>
+      <excludes>
+        <exclude>**/resources/**</exclude>
+      </excludes>
+    </fileSet>
+    <fileSet>
+      <directory>target</directory>
+      <outputDirectory>demo-base/webapps</outputDirectory>
+      <includes>
+        <include>test-jndi.xml</include>
+      </includes>
+    </fileSet>
+    <fileSet>
+      <directory>target/lib/jndi</directory>
+      <outputDirectory>demo-base/lib/ext</outputDirectory>
+      <includes>
+        <include>*.jar</include>
+      </includes>
+    </fileSet>
+  </fileSets>
+</assembly>
diff --git a/tests/test-webapps/test-jndi-webapp/src/main/java/com/acme/JNDITest.java b/tests/test-webapps/test-jndi-webapp/src/main/java/com/acme/JNDITest.java
new file mode 100644
index 0000000..8a1853a
--- /dev/null
+++ b/tests/test-webapps/test-jndi-webapp/src/main/java/com/acme/JNDITest.java
@@ -0,0 +1,160 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * 
+ */
+package com.acme;
+
+import java.io.IOException;
+
+import javax.mail.Session;
+import javax.naming.InitialContext;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.sql.DataSource;
+import javax.transaction.UserTransaction;
+
+/**
+ * JNDITest
+ * 
+ * Use JNDI from within Jetty.
+ * 
+ * Also, use servlet spec 2.5 resource injection and lifecycle callbacks from within the web.xml
+ * to set up some of the JNDI resources.
+ *
+ */
+public class JNDITest extends HttpServlet 
+{   
+    private DataSource myDS;
+  
+    private Session myMailSession;
+    private Double wiggle;
+    private Integer woggle;
+    private Double gargle;
+    
+    private String resourceNameMappingInjectionResult;
+    private String envEntryOverrideResult;
+    private String postConstructResult = "PostConstruct method called: <span class=\"fail\">FALSE</span>";
+    private String preDestroyResult = "PreDestroy method called: <span class=\"pass\">NOT YET</span>";
+    private String envEntryGlobalScopeResult;
+    private String envEntryWebAppScopeResult;
+    private String userTransactionResult;
+    private String mailSessionResult;
+    
+    
+    public void setMyDatasource(DataSource ds)
+    {
+        myDS=ds;
+    }
+ 
+    
+    private void postConstruct ()
+    {
+        resourceNameMappingInjectionResult= "Injection of resource to locally mapped name (java:comp/env/mydatasource as java:comp/env/mydatasource1): "+(myDS!=null?"<span class=\"pass\">PASS</span>":"<span class=\"fail\">FAIL</span>");
+        envEntryOverrideResult = "Override of EnvEntry in jetty-env.xml (java:comp/env/wiggle): "+(wiggle==55.0?"<span class=\"pass\">PASS":"<span class=\"fail\">FAIL(expected 55.0, got "+wiggle+")")+"</span>";
+        postConstructResult = "PostConstruct method called: <span class=\"pass\">PASS</span>";
+    }
+    
+    private void preDestroy()
+    {
+        preDestroyResult = "PreDestroy method called: <span class=\"pass\">PASS</span>";
+    }
+    
+    
+    public void init(ServletConfig config) throws ServletException
+    {
+        super.init(config);
+        try
+        {
+            InitialContext ic = new InitialContext();
+            woggle = (Integer)ic.lookup("java:comp/env/woggle");
+            envEntryGlobalScopeResult = "EnvEntry defined in context xml lookup result (java:comp/env/woggle): "+(woggle==4000?"<span class=\"pass\">PASS":"<span class=\"fail\">FAIL(expected 4000, got "+woggle+")")+"</span>";
+            gargle = (Double)ic.lookup("java:comp/env/gargle");
+            envEntryWebAppScopeResult = "EnvEntry defined in jetty-env.xml lookup result (java:comp/env/gargle): "+(gargle==100.0?"<span class=\"pass\">PASS":"<span class=\"fail\">FAIL(expected 100, got "+gargle+")")+"</span>";
+            UserTransaction utx = (UserTransaction)ic.lookup("java:comp/UserTransaction");
+            userTransactionResult = "UserTransaction lookup result (java:comp/UserTransaction): "+(utx!=null?"<span class=\"pass\">PASS":"<span class=\"fail\">FAIL")+"</span>";
+            myMailSession = (Session)ic.lookup("java:comp/env/mail/Session");
+            mailSessionResult = "Mail Session lookup result (java:comp/env/mail/Session): "+(myMailSession!=null?"<span class=\"pass\">PASS": "<span class=\"fail\">FAIL")+"</span>";
+        }
+        catch (Exception e)
+        {
+            throw new ServletException(e);
+        }
+    }
+
+    
+    
+    /* ------------------------------------------------------------ */
+    public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+    {
+        doGet(request, response);
+    }
+
+    /* ------------------------------------------------------------ */
+    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+    {   
+        String mailTo = request.getParameter("mailto");
+        String mailFrom = request.getParameter("mailfrom");
+        
+        if (mailTo != null)
+            mailTo = mailTo.trim();
+        
+        if (mailFrom != null)
+            mailFrom = mailFrom.trim();
+        
+        try
+        {
+            response.setContentType("text/html");
+            ServletOutputStream out = response.getOutputStream();
+            out.println("<html>");
+            out.println("<head><link rel=\"stylesheet\" type=\"text/css\" href=\"stylesheet.css\"/></head>");
+            out.println("<h1>Jetty JNDI Tests</h1>");
+            out.println("<body>");
+            
+            out.println("<h2>Injection and JNDI Lookup Results</h2>");
+            out.println("<p>"+resourceNameMappingInjectionResult+"</p>");
+            out.println("<p>"+envEntryOverrideResult+"</p>");
+            out.println("<p>"+postConstructResult+"</p>");
+            out.println("<p>"+preDestroyResult+"</p>");
+            out.println("<p>"+envEntryGlobalScopeResult+"</p>");
+            out.println("<p>"+envEntryWebAppScopeResult+"</p>");
+            out.println("<p>"+userTransactionResult+"</p>");
+            out.println("<p>"+mailSessionResult+"</p>");
+        
+
+            out.println("</body>");            
+            out.println("</html>");
+            out.flush();
+        }
+        catch (Exception e)
+        {
+            throw new ServletException(e);
+        }
+    }
+    
+  
+    
+    public void destroy ()
+    {
+    }
+}
diff --git a/tests/test-webapps/test-jndi-webapp/src/main/templates/env-definitions.xml b/tests/test-webapps/test-jndi-webapp/src/main/templates/env-definitions.xml
new file mode 100644
index 0000000..e1e357e
--- /dev/null
+++ b/tests/test-webapps/test-jndi-webapp/src/main/templates/env-definitions.xml
@@ -0,0 +1,47 @@
+
+  <!-- Define an env entry with Server scope for java:comp/env                   -->
+  <New id="woggle"  class="org.eclipse.jetty.plus.jndi.EnvEntry">
+    <Arg><Property name='server'/></Arg>
+    <Arg>woggle</Arg>
+    <Arg type="java.lang.Integer">4000</Arg>
+    <Arg type="boolean">false</Arg>
+  </New>
+
+  <!-- Define an env entry with webapp scope for java:comp/env                   -->
+  <New id="wiggle"  class="org.eclipse.jetty.plus.jndi.EnvEntry">
+    <Arg><Ref refid='wac'/></Arg>
+    <Arg>wiggle</Arg>
+    <Arg type="java.lang.Double">100</Arg>
+    <Arg type="boolean">true</Arg>
+  </New>
+
+ <!-- Mail Session setup                                          -->
+ <New id="xxxmail" class="org.eclipse.jetty.plus.jndi.Resource">
+    <Arg><Ref refid='wac'/></Arg>
+     <Arg>mail/Session</Arg>
+     <Arg>
+       <New class="org.eclipse.jetty.jndi.factories.MailSessionReference">
+         <Set name="user">CHANGE-ME</Set>
+         <Set name="password">CHANGE-ME</Set>
+         <Set name="properties">
+           <New class="java.util.Properties">
+             <Put name="mail.smtp.auth">false</Put> <!-- change to true if you want to authenticate -->
+             <Put name="mail.smtp.host">CHANGE-ME</Put>
+             <Put name="mail.from">CHANGE-ME</Put>
+             <Put name="mail.debug">false</Put>
+           </New>
+          </Set>
+       </New>
+     </Arg>
+ </New>
+
+  <!-- A mock DataSource                                           -->
+  <New id="mydatasource" class="org.eclipse.jetty.plus.jndi.Resource">
+    <Arg><Ref refid='wac'/></Arg>
+    <Arg>jdbc/mydatasource</Arg>
+    <Arg>
+      <New class="com.acme.MockDataSource"/>
+    </Arg>
+  </New>
+
+</Configure>
diff --git a/tests/test-webapps/test-jndi-webapp/src/main/templates/jetty-test-jndi-header.xml b/tests/test-webapps/test-jndi-webapp/src/main/templates/jetty-test-jndi-header.xml
new file mode 100644
index 0000000..4f4ded0
--- /dev/null
+++ b/tests/test-webapps/test-jndi-webapp/src/main/templates/jetty-test-jndi-header.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0"?>
+<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
+
+<!-- =============================================================== -->
+<!-- Configure the test-jndi webapp                                  -->
+<!-- =============================================================== -->
+<Configure id='wac' class="org.eclipse.jetty.webapp.WebAppContext">
+
+  <New id="tx" class="org.eclipse.jetty.plus.jndi.Transaction">
+    <Arg>
+      <New class="com.acme.MockUserTransaction"/>
+    </Arg>
+  </New>
+
+  <!-- =============================================================== -->
+  <!-- Configure the webapp                                            -->
+  <!-- =============================================================== -->
+ <!-- Only uncomment if you are not using etc/jetty-plus.xml from start.ini
+
+  <Set name="configurationClasses">
+    <Call class="org.eclipse.jetty.webapp.Configuration$ClassList" name="serverDefault">
+      <Arg><Ref refid="Server" /></Arg>
+      <Call name="addAfter">
+        <Arg name="afterClass">org.eclipse.jetty.webapp.FragmentConfiguration</Arg>
+        <Arg>
+          <Array type="String">
+            <Item>org.eclipse.jetty.plus.webapp.EnvConfiguration</Item>
+            <Item>org.eclipse.jetty.plus.webapp.PlusConfiguration</Item>
+            <Item>org.eclipse.jetty.annotations.AnnotationConfiguration</Item>
+          </Array>
+        </Arg>
+      </Call>
+    </Call>
+  </Set>
+ -->
+
+  <Set name="contextPath">/test-jndi</Set>
+  <Set name="war"><Property name="jetty.webapps" default="."/>/test-jndi.war</Set>
+  <Set name="extractWAR">true</Set>
+  <Set name="copyWebDir">false</Set>
+  <Set name="configurationDiscovered">true</Set>
+
diff --git a/tests/test-webapps/test-jndi-webapp/src/main/templates/plugin-context-header.xml b/tests/test-webapps/test-jndi-webapp/src/main/templates/plugin-context-header.xml
new file mode 100644
index 0000000..3e877a5
--- /dev/null
+++ b/tests/test-webapps/test-jndi-webapp/src/main/templates/plugin-context-header.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0"?>
+<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
+
+<!-- =============================================================== -->
+<!-- Configure the test-jndi webapp                                  -->
+<!-- =============================================================== -->
+<Configure id='wac' class="org.eclipse.jetty.webapp.WebAppContext">
+
+  <New id="tx" class="org.eclipse.jetty.plus.jndi.Transaction">
+    <Arg>
+      <New class="com.acme.MockUserTransaction"/>
+    </Arg>
+  </New>
+
+
diff --git a/tests/test-webapps/test-jndi-webapp/src/main/webapp/WEB-INF/jetty-env.xml b/tests/test-webapps/test-jndi-webapp/src/main/webapp/WEB-INF/jetty-env.xml
new file mode 100644
index 0000000..b7fa49f
--- /dev/null
+++ b/tests/test-webapps/test-jndi-webapp/src/main/webapp/WEB-INF/jetty-env.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0"?>
+<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
+
+<Configure id='wac' class="org.eclipse.jetty.webapp.WebAppContext">
+
+  <!-- Add an EnvEntry only valid for this webapp               -->
+  <New id="gargle"  class="org.eclipse.jetty.plus.jndi.EnvEntry">
+    <Arg><Ref refid='wac'/></Arg>
+    <Arg>gargle</Arg>
+    <Arg type="java.lang.Double">100</Arg>
+    <Arg type="boolean">true</Arg>
+  </New>
+
+ <!-- Add an override for a global EnvEntry                           -->
+  <New id="wiggle"  class="org.eclipse.jetty.plus.jndi.EnvEntry">
+    <Arg><Ref refid='wac'/></Arg>
+    <Arg>wiggle</Arg>
+    <Arg type="java.lang.Double">55.0</Arg>
+    <Arg type="boolean">true</Arg>
+  </New>
+
+<!-- Add a mapping from name in web.xml to the environment -->
+  <New id="map1" class="org.eclipse.jetty.plus.jndi.Link">
+    <Arg><Ref refid='wac'/></Arg>
+    <Arg>jdbc/mydatasource1</Arg> <!-- name in web.xml -->
+    <Arg>jdbc/mydatasource</Arg>  <!-- name in environment -->
+  </New>
+
+
+
+
+
+</Configure>
+
diff --git a/tests/test-webapps/test-jndi-webapp/src/main/webapp/WEB-INF/jetty-web.xml b/tests/test-webapps/test-jndi-webapp/src/main/webapp/WEB-INF/jetty-web.xml
new file mode 100644
index 0000000..fd4831e
--- /dev/null
+++ b/tests/test-webapps/test-jndi-webapp/src/main/webapp/WEB-INF/jetty-web.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
+
+<Configure class="org.eclipse.jetty.webapp.WebAppContext">
+  <Get class="org.eclipse.jetty.util.log.Log" name="rootLogger">
+    <Call name="warn"><Arg>test-jndi webapp is deployed. DO NOT USE IN PRODUCTION!</Arg></Call>
+  </Get>
+</Configure>
diff --git a/tests/test-webapps/test-jndi-webapp/src/main/webapp/WEB-INF/web.xml b/tests/test-webapps/test-jndi-webapp/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000..1018533
--- /dev/null
+++ b/tests/test-webapps/test-jndi-webapp/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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" 
+   metadata-complete="true"
+   version="2.5"> 
+
+  <display-name>Test JNDI WebApp</display-name>
+  
+  <servlet>
+    <servlet-name>JNDITest</servlet-name>
+    <servlet-class>com.acme.JNDITest</servlet-class>
+    <load-on-startup>1</load-on-startup>
+  </servlet>
+
+  <servlet-mapping>
+    <servlet-name>JNDITest</servlet-name>
+    <url-pattern>/test/*</url-pattern>
+  </servlet-mapping>
+
+  <env-entry>
+    <env-entry-name>wiggle</env-entry-name>
+    <env-entry-type>java.lang.Double</env-entry-type>
+    <env-entry-value>99.99</env-entry-value>
+    <injection-target>
+      <injection-target-class>com.acme.JNDITest</injection-target-class>
+      <injection-target-name>wiggle</injection-target-name>
+    </injection-target>
+  </env-entry>
+
+  <resource-ref>
+    <res-ref-name>mail/Session</res-ref-name>
+    <res-type>javax.mail.Session</res-type>
+    <res-auth>Container</res-auth>
+  </resource-ref>
+
+  <resource-ref>
+    <res-ref-name>jdbc/mydatasource1</res-ref-name>
+    <res-type>javax.sql.DataSource</res-type>
+    <res-auth>Container</res-auth>
+    <injection-target>
+      <injection-target-class>com.acme.JNDITest</injection-target-class>
+      <injection-target-name>myDatasource</injection-target-name>
+    </injection-target>
+  </resource-ref>
+
+
+  <post-construct>
+    <lifecycle-callback-class>com.acme.JNDITest</lifecycle-callback-class>
+    <lifecycle-callback-method>postConstruct</lifecycle-callback-method>
+  </post-construct>
+
+  <pre-destroy>
+    <lifecycle-callback-class>com.acme.JNDITest</lifecycle-callback-class>
+    <lifecycle-callback-method>preDestroy</lifecycle-callback-method>
+  </pre-destroy>
+
+</web-app>
+
+
diff --git a/test-jetty-webapp/src/main/webapp/jetty_banner.gif b/tests/test-webapps/test-jndi-webapp/src/main/webapp/images/jetty_banner.gif
similarity index 100%
copy from test-jetty-webapp/src/main/webapp/jetty_banner.gif
copy to tests/test-webapps/test-jndi-webapp/src/main/webapp/images/jetty_banner.gif
diff --git a/tests/test-webapps/test-jndi-webapp/src/main/webapp/images/small_powered_by.gif b/tests/test-webapps/test-jndi-webapp/src/main/webapp/images/small_powered_by.gif
new file mode 100644
index 0000000..c5dd443
Binary files /dev/null and b/tests/test-webapps/test-jndi-webapp/src/main/webapp/images/small_powered_by.gif differ
diff --git a/tests/test-webapps/test-jndi-webapp/src/main/webapp/index.html b/tests/test-webapps/test-jndi-webapp/src/main/webapp/index.html
new file mode 100644
index 0000000..82b8a33
--- /dev/null
+++ b/tests/test-webapps/test-jndi-webapp/src/main/webapp/index.html
@@ -0,0 +1,46 @@
+<HTML>
+  <HEAD>
+    <TITLE>JNDI Test WebApp</TITLE>
+    <META http-equiv="Pragma" content="no-cache">
+    <META http-equiv="Cache-Control" content="no-cache,no-store">
+    <link rel="stylesheet" type="text/css" href="stylesheet.css"/>
+  </HEAD>
+<BODY>
+  <A HREF="http://www.eclipse.org/jetty"><IMG SRC="images/jetty_banner.gif"></A>
+  <br/>
+  <b><a href="http://localhost:8080/">Demo Home</a></b>
+  <hr/>
+  <center><span style="color:red; font-variant:small-caps; font-weight:bold">Test Web Application Only - Do NOT Deploy in Production</span> </center>
+
+<h1>JNDI Test WebApp</h1>
+
+<p>
+This example shows how to configure and lookup resources such as DataSources, a JTA transaction manager and a java.mail.Session in JNDI. 
+</p>
+
+  <h2>Preparation</h2>
+  <p>To enable JNDI in a base jetty instance do:
+    <pre>
+    $ cd $JETTY_BASE
+    $ java -jar $JETTY_HOME/start.jar --add-to-startd=jndi
+    </pre>
+  </p>
+  <p>This will create a $JETTY_BASE/start.d/jndi.ini file to enable and parameterise JNDI.  If the --add-to-start option instead, then the same initialisation will be appended to the
+  $JETTY_BASE/start.ini file instead. The jetty demo-base already has JNDI enabled in the start.ini file and some mock resources included.  </p>
+<p>The full source of this demonstration is available <a href="http://git.eclipse.org/c/jetty/org.eclipse.jetty.project.git/tree/tests/test-webapps/test-jndi-webapp">here</a>.</p>
+
+
+<h2>Execution</h2>
+<p>
+Click <code>Test</code> to check the runtime lookup of the JNDI resources.
+</p>
+<form action="test" method="post">
+      <button type="submit">Test</button>
+</form>
+
+
+  <hr/>
+  <center> <a href="http://www.eclipse.org/jetty"><img style="border:0" src="images/small_powered_by.gif"/></a> </center>
+
+</BODY>
+</HTML>
diff --git a/tests/test-webapps/test-jndi-webapp/src/main/webapp/stylesheet.css b/tests/test-webapps/test-jndi-webapp/src/main/webapp/stylesheet.css
new file mode 100644
index 0000000..4ecc2cb
--- /dev/null
+++ b/tests/test-webapps/test-jndi-webapp/src/main/webapp/stylesheet.css
@@ -0,0 +1,7 @@
+body {color: #2E2E2E; font-family:sans-serif; font-size:90%;}
+h1 {font-variant: small-caps; font-size:130%; letter-spacing: 0.1em;}
+h2 {font-variant: small-caps; font-size:100%; letter-spacing: 0.1em;}
+h3 {font-size:100%; letter-spacing: 0.1em;}
+
+span.pass { color: green; }
+span.fail { color:red; }
diff --git a/tests/test-webapps/test-mock-resources/pom.xml b/tests/test-webapps/test-mock-resources/pom.xml
new file mode 100644
index 0000000..a8fb7c3
--- /dev/null
+++ b/tests/test-webapps/test-mock-resources/pom.xml
@@ -0,0 +1,90 @@
+<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>
+  <parent>
+    <groupId>org.eclipse.jetty.tests</groupId>
+    <artifactId>test-webapps-parent</artifactId>
+    <version>9.2.13.v20150730</version>
+  </parent>
+  <name>Jetty Tests :: WebApp :: Mock Resources</name>
+  <artifactId>test-mock-resources</artifactId>
+  <packaging>jar</packaging>
+  <build>
+    <plugins>
+      <plugin>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <configuration>
+          <verbose>false</verbose>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>maven-bundle-plugin</artifactId>
+        <extensions>true</extensions>
+        <executions>
+          <execution>
+            <id>generate-manifest</id>
+            <goals>
+              <goal>manifest</goal>
+            </goals>
+            <configuration>
+              <instructions>
+                <Bundle-SymbolicName>org.eclipse.jetty.tests.test-mock-resources</Bundle-SymbolicName>
+                <Bundle-Description>Mock resources used for testing </Bundle-Description>
+                <Export-Package>
+                   com.acme;version="${parsedVersion.majorVersion}.${parsedVersion.minorVersion}.${parsedVersion.incrementalVersion}"
+                </Export-Package>
+                <Import-Package>
+                   javax.sql,
+                   javax.transaction;version="1.1",
+                   javax.mail;version="1.4.1"
+                </Import-Package>
+                <_nouses>true</_nouses>
+              </instructions>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+     <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-jar-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>artifact-jar</id>
+            <goals>
+              <goal>jar</goal>
+            </goals>
+          </execution>
+        </executions>
+        <configuration>
+          <archive>
+            <manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
+          </archive>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+  <dependencies>
+    <dependency>
+      <groupId>javax.transaction</groupId>
+      <artifactId>javax.transaction-api</artifactId>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>javax.servlet</groupId>
+      <artifactId>javax.servlet-api</artifactId>
+    </dependency>
+<!--
+    <dependency>
+      <groupId>javax.mail</groupId>
+      <artifactId>javax.mail-api</artifactId>
+    </dependency>
+-->
+    <dependency>
+      <groupId>org.eclipse.jetty.orbit</groupId>
+      <artifactId>javax.mail.glassfish</artifactId>
+      <version>1.4.1.v201005082020</version>
+      <scope>provided</scope>
+    </dependency>
+
+  </dependencies>
+</project>
diff --git a/tests/test-webapps/test-mock-resources/src/main/java/com/acme/MockDataSource.java b/tests/test-webapps/test-mock-resources/src/main/java/com/acme/MockDataSource.java
new file mode 100644
index 0000000..ad3e544
--- /dev/null
+++ b/tests/test-webapps/test-mock-resources/src/main/java/com/acme/MockDataSource.java
@@ -0,0 +1,101 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package com.acme;
+
+import java.io.PrintWriter;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.logging.Logger;
+
+import javax.sql.DataSource;
+
+/**
+ * MockDataSource
+ *
+ *
+ */
+public class MockDataSource implements DataSource
+{
+
+    /**
+     * NOTE: JDK7+ new feature
+     */
+    public Logger getParentLogger() 
+    {
+        return null;
+    }
+
+    /** 
+     * @see javax.sql.DataSource#getConnection()
+     */
+    public Connection getConnection() throws SQLException
+    {
+        return null;
+    }
+
+    /** 
+     * @see javax.sql.DataSource#getConnection(java.lang.String, java.lang.String)
+     */
+    public Connection getConnection(String username, String password)
+            throws SQLException
+    {
+        return null;
+    }
+
+    /** 
+     * @see javax.sql.DataSource#getLogWriter()
+     */
+    public PrintWriter getLogWriter() throws SQLException
+    {
+        return null;
+    }
+
+    /** 
+     * @see javax.sql.DataSource#getLoginTimeout()
+     */
+    public int getLoginTimeout() throws SQLException
+    {
+        return 0;
+    }
+
+    /** 
+     * @see javax.sql.DataSource#setLogWriter(java.io.PrintWriter)
+     */
+    public void setLogWriter(PrintWriter out) throws SQLException
+    {
+    }
+
+    /** 
+     * @see javax.sql.DataSource#setLoginTimeout(int)
+     */
+    public void setLoginTimeout(int seconds) throws SQLException
+    {
+    }
+
+    public boolean isWrapperFor(Class<?> iface) throws SQLException
+    {
+        return false;
+    }
+
+    public <T> T unwrap(Class<T> iface) throws SQLException
+    {
+        return null;
+    }
+
+}
diff --git a/tests/test-webapps/test-mock-resources/src/main/java/com/acme/MockTransport.java b/tests/test-webapps/test-mock-resources/src/main/java/com/acme/MockTransport.java
new file mode 100644
index 0000000..9848c1a
--- /dev/null
+++ b/tests/test-webapps/test-mock-resources/src/main/java/com/acme/MockTransport.java
@@ -0,0 +1,55 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package com.acme;
+
+import javax.mail.Address;
+import javax.mail.Message;
+import javax.mail.MessagingException;
+import javax.mail.Session;
+import javax.mail.Transport;
+import javax.mail.URLName;
+
+/**
+ * MockTransport
+ *
+ *
+ */
+public class MockTransport extends Transport
+{
+
+    /**
+     * @param session
+     * @param urlname
+     */
+    public MockTransport(Session session, URLName urlname)
+    {
+        super(session, urlname);
+    }
+
+    /** 
+     * @see javax.mail.Transport#sendMessage(javax.mail.Message, javax.mail.Address[])
+     */
+    @Override
+    public void sendMessage(Message arg0, Address[] arg1) throws MessagingException
+    {
+        System.err.println ("Sending message");
+    }
+
+}
diff --git a/tests/test-webapps/test-mock-resources/src/main/java/com/acme/MockUserTransaction.java b/tests/test-webapps/test-mock-resources/src/main/java/com/acme/MockUserTransaction.java
new file mode 100644
index 0000000..ea39b04
--- /dev/null
+++ b/tests/test-webapps/test-mock-resources/src/main/java/com/acme/MockUserTransaction.java
@@ -0,0 +1,82 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package com.acme;
+
+import javax.transaction.HeuristicMixedException;
+import javax.transaction.HeuristicRollbackException;
+import javax.transaction.NotSupportedException;
+import javax.transaction.RollbackException;
+import javax.transaction.SystemException;
+import javax.transaction.UserTransaction;
+
+/**
+ * MockUserTransaction
+ *
+ *
+ */
+public class MockUserTransaction implements UserTransaction
+{
+
+    /** 
+     * @see javax.transaction.UserTransaction#begin()
+     */
+    public void begin() throws NotSupportedException, SystemException
+    {
+    }
+
+    /** 
+     * @see javax.transaction.UserTransaction#commit()
+     */
+    public void commit() throws HeuristicMixedException,
+            HeuristicRollbackException, IllegalStateException,
+            RollbackException, SecurityException, SystemException
+    {
+    }
+
+    /** 
+     * @see javax.transaction.UserTransaction#getStatus()
+     */
+    public int getStatus() throws SystemException
+    {
+        return 0;
+    }
+
+    /** 
+     * @see javax.transaction.UserTransaction#rollback()
+     */
+    public void rollback() throws IllegalStateException, SecurityException,
+            SystemException
+    {
+    }
+
+    /** 
+     * @see javax.transaction.UserTransaction#setRollbackOnly()
+     */
+    public void setRollbackOnly() throws IllegalStateException, SystemException
+    {
+    }
+
+    /** 
+     * @see javax.transaction.UserTransaction#setTransactionTimeout(int)
+     */
+    public void setTransactionTimeout(int arg0) throws SystemException
+    {
+    }
+
+}
diff --git a/tests/test-webapps/test-mock-resources/src/main/resources/META-INF/javaxmail.providers b/tests/test-webapps/test-mock-resources/src/main/resources/META-INF/javaxmail.providers
new file mode 100644
index 0000000..5ab3340
--- /dev/null
+++ b/tests/test-webapps/test-mock-resources/src/main/resources/META-INF/javaxmail.providers
@@ -0,0 +1 @@
+ protocol=smtp; type=transport; class=com.acme.MockTransport; vendor=Acme Tests;
diff --git a/tests/test-webapps/test-proxy-webapp/pom.xml b/tests/test-webapps/test-proxy-webapp/pom.xml
new file mode 100644
index 0000000..1053477
--- /dev/null
+++ b/tests/test-webapps/test-proxy-webapp/pom.xml
@@ -0,0 +1,104 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+// ========================================================================
+// Copyright (c) Webtide LLC
+// 
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at 
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.apache.org/licenses/LICENSE-2.0.txt
+//
+// You may elect to redistribute this code under either of these licenses. 
+// ========================================================================
+ -->
+<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">
+  <parent>
+    <groupId>org.eclipse.jetty.tests</groupId>
+    <artifactId>test-webapps-parent</artifactId>
+    <version>9.2.13.v20150730</version>
+    <relativePath>../pom.xml</relativePath>
+  </parent>
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>org.eclipse.jetty</groupId>
+  <artifactId>test-proxy-webapp</artifactId>
+  <name>Test :: Jetty Proxy Webapp</name>
+  <packaging>war</packaging>
+  <build>
+    <plugins>
+      <plugin>
+        <artifactId>maven-war-plugin</artifactId>
+        <configuration>
+          <archive>
+            <manifestFile>src/main/webapp/META-INF/MANIFEST.MF</manifestFile>
+          </archive>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+  <dependencies>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-proxy</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+       <groupId>javax.servlet</groupId>
+       <artifactId>javax.servlet-api</artifactId>
+       <scope>provided</scope>
+    </dependency>
+
+<!--
+    <dependency>
+      <groupId>org.eclipse.jetty.orbit</groupId>
+      <artifactId>javax.servlet</artifactId>
+      <scope>provided</scope>
+    </dependency>
+-->
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-webapp</artifactId>
+      <version>${project.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-jmx</artifactId>
+      <version>${project.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.toolchain</groupId>
+      <artifactId>jetty-test-helper</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-server</artifactId>
+      <version>${project.version}</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>javax.servlet.jsp</groupId>
+      <artifactId>jsp-api</artifactId>
+      <version>2.1</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.spdy</groupId>
+      <artifactId>spdy-http-server</artifactId>
+      <version>${project.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>javax.servlet</groupId>
+      <artifactId>jstl</artifactId>
+      <version>1.2</version>
+      <scope>provided</scope>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/tests/test-webapps/test-proxy-webapp/src/main/webapp/META-INF/MANIFEST.MF b/tests/test-webapps/test-proxy-webapp/src/main/webapp/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..886d243
--- /dev/null
+++ b/tests/test-webapps/test-proxy-webapp/src/main/webapp/META-INF/MANIFEST.MF
@@ -0,0 +1,21 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: TestIt
+Bundle-SymbolicName: TestIt
+Bundle-Version: 1.0.0.qualifier
+Bundle-Activator: testit.Activator
+Import-Package: javax.servlet;version="2.6",
+ javax.servlet.http;version="2.6",
+ javax.servlet.jsp,
+ javax.servlet.jsp.tagext
+Require-Bundle: org.eclipse.jetty.client,
+ org.eclipse.jetty.proxy,
+ org.eclipse.jetty.http,
+ org.eclipse.jetty.io,
+ org.eclipse.jetty.util
+Bundle-ClassPath: WEB-INF/classes
+Bundle-RequiredExecutionEnvironment: JavaSE-1.7
+Bundle-ActivationPolicy: lazy
+Web-ContextPath: /
+Class-Path: 
+
diff --git a/tests/test-webapps/test-proxy-webapp/src/main/webapp/WEB-INF/jetty-web.xml b/tests/test-webapps/test-proxy-webapp/src/main/webapp/WEB-INF/jetty-web.xml
new file mode 100644
index 0000000..030fd19
--- /dev/null
+++ b/tests/test-webapps/test-proxy-webapp/src/main/webapp/WEB-INF/jetty-web.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
+
+<Configure class="org.eclipse.jetty.webapp.WebAppContext">
+  <Set name="contextPath">/proxy</Set>
+</Configure>
diff --git a/tests/test-webapps/test-proxy-webapp/src/main/webapp/WEB-INF/web.xml b/tests/test-webapps/test-proxy-webapp/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000..5cfa55d
--- /dev/null
+++ b/tests/test-webapps/test-proxy-webapp/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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_3_0.xsd"
+   metadata-complete="false"
+   version="3.0"> 
+
+  <display-name>Transparent Proxy WebApp</display-name>
+ 
+  <servlet>
+    <servlet-name>XrefTransparentProxy</servlet-name>
+    <servlet-class>org.eclipse.jetty.proxy.ProxyServlet$Transparent</servlet-class>
+    <init-param>
+      <param-name>proxyTo</param-name><param-value>http://download.eclipse.org/jetty/stable-9</param-value>
+    </init-param>
+    <init-param>
+      <param-name>hostHeader</param-name><param-value>download.eclipse.org</param-value>
+    </init-param>
+    <load-on-startup>1</load-on-startup>
+    <async-supported>true</async-supported>
+  </servlet>
+  
+  <servlet-mapping>
+    <servlet-name>XrefTransparentProxy</servlet-name>
+    <url-pattern>/xref/*</url-pattern>
+  </servlet-mapping>
+  
+  <servlet>
+    <servlet-name>JavadocTransparentProxy</servlet-name>
+    <servlet-class>org.eclipse.jetty.proxy.ProxyServlet$Transparent</servlet-class>
+    <init-param>
+      <param-name>proxyTo</param-name><param-value>http://download.eclipse.org/jetty/stable-9</param-value>
+    </init-param>
+    <init-param>
+      <param-name>hostHeader</param-name><param-value>download.eclipse.org</param-value>
+    </init-param>
+    <load-on-startup>1</load-on-startup>
+    <async-supported>true</async-supported>
+  </servlet>
+  
+  <servlet-mapping>
+    <servlet-name>JavadocTransparentProxy</servlet-name>
+    <url-pattern>/apidocs/*</url-pattern>
+  </servlet-mapping>
+
+</web-app>
+
+
diff --git a/tests/test-webapps/test-proxy-webapp/src/test/java/org/eclipse/jetty/TestTransparentProxyServer.java b/tests/test-webapps/test-proxy-webapp/src/test/java/org/eclipse/jetty/TestTransparentProxyServer.java
new file mode 100644
index 0000000..0f3d0e7
--- /dev/null
+++ b/tests/test-webapps/test-proxy-webapp/src/test/java/org/eclipse/jetty/TestTransparentProxyServer.java
@@ -0,0 +1,140 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty;
+
+import java.lang.management.ManagementFactory;
+
+import org.eclipse.jetty.jmx.MBeanContainer;
+import org.eclipse.jetty.server.ForwardedRequestCustomizer;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.HttpConnectionFactory;
+import org.eclipse.jetty.server.SecureRequestCustomizer;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.SslConnectionFactory;
+import org.eclipse.jetty.server.handler.ContextHandlerCollection;
+import org.eclipse.jetty.server.handler.DefaultHandler;
+import org.eclipse.jetty.server.handler.HandlerCollection;
+import org.eclipse.jetty.spdy.server.NPNServerConnectionFactory;
+import org.eclipse.jetty.spdy.server.SPDYServerConnectionFactory;
+import org.eclipse.jetty.spdy.server.http.HTTPSPDYServerConnectionFactory;
+import org.eclipse.jetty.spdy.server.http.PushStrategy;
+import org.eclipse.jetty.spdy.server.http.ReferrerPushStrategy;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.StdErrLog;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.eclipse.jetty.webapp.WebAppContext;
+import org.junit.Ignore;
+
+ at Ignore("Not a test case")
+public class TestTransparentProxyServer
+{
+    public static void main(String[] args) throws Exception
+    {
+        ((StdErrLog)Log.getLog()).setSource(false);
+
+        String jetty_root = "../../..";
+
+        // Setup Threadpool
+        QueuedThreadPool threadPool = new QueuedThreadPool();
+        threadPool.setMaxThreads(100);
+
+        // Setup server
+        Server server = new Server(threadPool);
+        server.manage(threadPool);
+
+        // Setup JMX
+        MBeanContainer mbContainer=new MBeanContainer(ManagementFactory.getPlatformMBeanServer());
+        server.addBean(mbContainer);
+        server.addBean(Log.getLog());
+        
+
+        // Common HTTP configuration
+        HttpConfiguration config = new HttpConfiguration();
+        config.setSecurePort(8443);
+        config.addCustomizer(new ForwardedRequestCustomizer());
+        config.addCustomizer(new SecureRequestCustomizer());
+        config.setSendDateHeader(true);
+        config.setSendServerVersion(true);
+        
+        
+        // Http Connector
+        HttpConnectionFactory http = new HttpConnectionFactory(config);
+        ServerConnector httpConnector = new ServerConnector(server,http);
+        httpConnector.setPort(8080);
+        httpConnector.setIdleTimeout(30000);
+        server.addConnector(httpConnector);
+
+        
+        // SSL configurations
+        SslContextFactory sslContextFactory = new SslContextFactory();
+        sslContextFactory.setKeyStorePath(jetty_root + "/jetty-server/src/main/config/etc/keystore");
+        sslContextFactory.setKeyStorePassword("OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4");
+        sslContextFactory.setKeyManagerPassword("OBF:1u2u1wml1z7s1z7a1wnl1u2g");
+        sslContextFactory.setTrustStorePath(jetty_root + "/jetty-server/src/main/config/etc/keystore");
+        sslContextFactory.setTrustStorePassword("OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4");
+        sslContextFactory.setExcludeCipherSuites(
+                "SSL_RSA_WITH_DES_CBC_SHA",
+                "SSL_DHE_RSA_WITH_DES_CBC_SHA",
+                "SSL_DHE_DSS_WITH_DES_CBC_SHA",
+                "SSL_RSA_EXPORT_WITH_RC4_40_MD5",
+                "SSL_RSA_EXPORT_WITH_DES40_CBC_SHA",
+                "SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA",
+                "SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA");
+        
+        
+        // Spdy Connector
+        SPDYServerConnectionFactory.checkProtocolNegotiationAvailable();
+        PushStrategy push = new ReferrerPushStrategy();
+        HTTPSPDYServerConnectionFactory spdy2 = new HTTPSPDYServerConnectionFactory(2,config,push);
+        spdy2.setInputBufferSize(8192);
+        spdy2.setInitialWindowSize(32768);
+        HTTPSPDYServerConnectionFactory spdy3 = new HTTPSPDYServerConnectionFactory(3,config,push);
+        spdy2.setInputBufferSize(8192);
+        NPNServerConnectionFactory npn = new NPNServerConnectionFactory(spdy3.getProtocol(),spdy2.getProtocol(),http.getProtocol());
+        npn.setDefaultProtocol(http.getProtocol());
+        npn.setInputBufferSize(1024); 
+        SslConnectionFactory ssl = new SslConnectionFactory(sslContextFactory,npn.getProtocol()); 
+        ServerConnector spdyConnector = new ServerConnector(server,ssl,npn,spdy3,spdy2,http);
+        spdyConnector.setPort(8443);
+        spdyConnector.setIdleTimeout(15000);
+        server.addConnector(spdyConnector);
+        
+        // Handlers
+        HandlerCollection handlers = new HandlerCollection();
+        ContextHandlerCollection contexts = new ContextHandlerCollection();
+        handlers.setHandlers(new Handler[]
+        { contexts, new DefaultHandler() });
+
+        server.setHandler(handlers);
+
+        // Setup proxy webapp
+        WebAppContext webapp = new WebAppContext();
+        webapp.setResourceBase("src/main/webapp");
+        contexts.addHandler(webapp);
+
+        // start server
+        server.setStopAtShutdown(true);
+        server.start();
+        server.join();
+    }
+
+}
diff --git a/tests/test-webapps/test-servlet-spec/pom.xml b/tests/test-webapps/test-servlet-spec/pom.xml
new file mode 100644
index 0000000..af089a2
--- /dev/null
+++ b/tests/test-webapps/test-servlet-spec/pom.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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>
+  <parent>
+    <groupId>org.eclipse.jetty.tests</groupId>
+    <artifactId>test-webapps-parent</artifactId>
+    <version>9.2.13.v20150730</version>
+  </parent>
+  <artifactId>test-servlet-spec-parent</artifactId>
+  <name>Jetty Tests :: Spec Test WebApp :: Parent</name>
+  <packaging>pom</packaging>
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-deploy-plugin</artifactId>
+        <configuration>
+          <!-- DO NOT DEPLOY (or Release) -->
+          <skip>true</skip>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+  <modules>
+    <module>test-web-fragment</module>
+    <module>test-container-initializer</module>
+    <module>test-spec-webapp</module>
+  </modules>
+
+</project>
diff --git a/tests/test-webapps/test-servlet-spec/test-container-initializer/pom.xml b/tests/test-webapps/test-servlet-spec/test-container-initializer/pom.xml
new file mode 100644
index 0000000..5e2b52b
--- /dev/null
+++ b/tests/test-webapps/test-servlet-spec/test-container-initializer/pom.xml
@@ -0,0 +1,69 @@
+<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>
+  <parent>
+    <groupId>org.eclipse.jetty.tests</groupId>
+    <artifactId>test-servlet-spec-parent</artifactId>
+    <version>9.2.13.v20150730</version>
+  </parent>
+  <artifactId>test-container-initializer</artifactId>
+  <packaging>jar</packaging>
+  <name>Jetty Tests :: WebApp :: Servlet Spec :: ServletContainerInitializer Test Jar</name>
+  <build>
+    <plugins>
+       <plugin>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <configuration>
+          <verbose>false</verbose>
+        </configuration>
+       </plugin>
+       <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-jar-plugin</artifactId>
+        <executions>
+            <execution>
+                <id>artifact-jar</id>
+                <goals>
+                    <goal>jar</goal>
+                </goals>
+            </execution>
+        </executions>
+        <configuration>
+            <archive>
+                <manifestFile>target/classes/META-INF/MANIFEST.MF</manifestFile>
+            </archive>
+        </configuration>
+      </plugin>
+      <plugin>
+          <groupId>org.apache.felix</groupId>
+          <artifactId>maven-bundle-plugin</artifactId>
+          <extensions>true</extensions>
+          <executions>
+              <execution>
+                  <id>bundle-manifest</id>
+                  <phase>process-classes</phase>
+                  <goals>
+                      <goal>manifest</goal>
+                  </goals>
+              </execution>
+          </executions>
+          <configuration>
+              <instructions>
+                  <Bundle-SymbolicName>org.eclipse.jetty.tests.test-servlet-container-initializer;singleton:=true</Bundle-SymbolicName>
+                  <Bundle-Description>A bundle containing a ServletContainerInitializer for testing</Bundle-Description>
+                  <Require-Capability>osgi.extender; filter:="(osgi.extender=osgi.serviceloader.registrar)"</Require-Capability>
+                  <Provide-Capability>osgi.serviceloader; osgi.serviceloader=javax.servlet.ServletContainerInitializer</Provide-Capability>
+                  <Export-Package>com.acme.initializer;version="${parsedVersion.majorVersion}.${parsedVersion.minorVersion}.${parsedVersion.incrementalVersion}"</Export-Package>
+                  <_nouses>true</_nouses>
+              </instructions>
+          </configuration>
+      </plugin>
+    </plugins>
+  </build>
+  <dependencies>
+    <dependency>
+       <groupId>javax.servlet</groupId>
+       <artifactId>javax.servlet-api</artifactId>
+       <scope>provided</scope>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/tests/test-webapps/test-servlet-spec/test-container-initializer/src/main/java/com/acme/initializer/Foo.java b/tests/test-webapps/test-servlet-spec/test-container-initializer/src/main/java/com/acme/initializer/Foo.java
new file mode 100644
index 0000000..c00b22f
--- /dev/null
+++ b/tests/test-webapps/test-servlet-spec/test-container-initializer/src/main/java/com/acme/initializer/Foo.java
@@ -0,0 +1,33 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package com.acme.initializer;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+
+ at Retention(RetentionPolicy.RUNTIME)
+ at Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
+public @interface Foo 
+{
+    int value();
+}
+
diff --git a/tests/test-webapps/test-servlet-spec/test-container-initializer/src/main/java/com/acme/initializer/FooInitializer.java b/tests/test-webapps/test-servlet-spec/test-container-initializer/src/main/java/com/acme/initializer/FooInitializer.java
new file mode 100644
index 0000000..ac72c92
--- /dev/null
+++ b/tests/test-webapps/test-servlet-spec/test-container-initializer/src/main/java/com/acme/initializer/FooInitializer.java
@@ -0,0 +1,102 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package com.acme.initializer;
+
+import java.util.ArrayList;
+import java.util.Set;
+
+import javax.servlet.ServletContainerInitializer;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+import javax.servlet.ServletRegistration;
+import javax.servlet.annotation.HandlesTypes;
+
+ at HandlesTypes ({javax.servlet.Servlet.class, Foo.class})
+public class FooInitializer implements ServletContainerInitializer
+{
+    public static class BarListener implements ServletContextListener
+    {
+
+        /** 
+         * @see javax.servlet.ServletContextListener#contextInitialized(javax.servlet.ServletContextEvent)
+         */
+        @Override
+        public void contextInitialized(ServletContextEvent sce)
+        {
+            throw new IllegalStateException("BAR LISTENER CALLED!");
+        }
+
+        /** 
+         * @see javax.servlet.ServletContextListener#contextDestroyed(javax.servlet.ServletContextEvent)
+         */
+        @Override
+        public void contextDestroyed(ServletContextEvent sce)
+        {
+            
+        }
+        
+    }
+
+    public static class FooListener implements ServletContextListener
+    {
+
+        /** 
+         * @see javax.servlet.ServletContextListener#contextInitialized(javax.servlet.ServletContextEvent)
+         */
+        @Override
+        public void contextInitialized(ServletContextEvent sce)
+        {
+            //Can add a ServletContextListener from a ServletContainerInitializer
+            sce.getServletContext().setAttribute("com.acme.AnnotationTest.listenerTest", Boolean.TRUE);
+            
+            //Can't add a ServletContextListener from a ServletContextListener
+            try
+            {
+                sce.getServletContext().addListener(new BarListener());
+                sce.getServletContext().setAttribute("com.acme.AnnotationTest.listenerRegoTest", Boolean.FALSE);
+            }
+            catch (UnsupportedOperationException e)
+            {
+                sce.getServletContext().setAttribute("com.acme.AnnotationTest.listenerRegoTest", Boolean.TRUE);
+            }
+            catch (Exception e)
+            {
+                sce.getServletContext().setAttribute("com.acme.AnnotationTest.listenerRegoTest", Boolean.FALSE);
+            }
+        }
+
+        /** 
+         * @see javax.servlet.ServletContextListener#contextDestroyed(javax.servlet.ServletContextEvent)
+         */
+        @Override
+        public void contextDestroyed(ServletContextEvent sce)
+        {
+            
+        }
+        
+    }
+    public void onStartup(Set<Class<?>> classes, ServletContext context)
+    {
+        context.setAttribute("com.acme.Foo", new ArrayList<Class>(classes));
+        ServletRegistration.Dynamic reg = context.addServlet("AnnotationTest", "com.acme.AnnotationTest");
+        context.setAttribute("com.acme.AnnotationTest.complete", (reg == null));
+        context.addListener(new FooListener());
+    }
+}
diff --git a/tests/test-webapps/test-servlet-spec/test-container-initializer/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer b/tests/test-webapps/test-servlet-spec/test-container-initializer/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer
new file mode 100644
index 0000000..622cbd0
--- /dev/null
+++ b/tests/test-webapps/test-servlet-spec/test-container-initializer/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer
@@ -0,0 +1 @@
+com.acme.initializer.FooInitializer
diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/pom.xml b/tests/test-webapps/test-servlet-spec/test-spec-webapp/pom.xml
new file mode 100644
index 0000000..3fd4132
--- /dev/null
+++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/pom.xml
@@ -0,0 +1,246 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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>
+  <parent>
+    <groupId>org.eclipse.jetty.tests</groupId>
+    <artifactId>test-servlet-spec-parent</artifactId>
+    <version>9.2.13.v20150730</version>
+  </parent>
+  <name>Jetty Tests :: Webapps :: Spec Webapp</name>
+  <artifactId>test-spec-webapp</artifactId>
+  <packaging>war</packaging>
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-deploy-plugin</artifactId>
+        <configuration>
+          <skip>true</skip>
+        </configuration>
+      </plugin>
+       
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-assembly-plugin</artifactId>
+        <executions>
+          <execution>
+            <phase>package</phase>
+            <goals>
+              <goal>single</goal>
+            </goals>
+            <configuration>
+              <descriptors>
+                <descriptor>src/main/assembly/config.xml</descriptor>
+                <descriptor>src/main/assembly/web-bundle.xml</descriptor>
+              </descriptors>
+              <archive>
+                <manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
+              </archive>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+      <!-- also make this webapp an osgi bundle -->
+      <plugin>
+        <artifactId>maven-war-plugin</artifactId>
+        <configuration>
+          <archive>
+            <manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
+          </archive>
+          <webResources>
+            <resource>
+                <directory>target</directory>
+                <includes>
+                  <include>plugin-context.xml</include>
+                </includes>
+                <targetPath>META-INF</targetPath>
+            </resource>
+          </webResources>
+        </configuration>
+      </plugin>
+
+
+      <plugin>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>maven-bundle-plugin</artifactId>
+        <extensions>true</extensions>
+        <configuration>
+          <supportedProjectTypes>
+            <supportedProjectType>war</supportedProjectType>
+          </supportedProjectTypes>
+        </configuration>
+        <executions>
+          <execution>
+            <id>bundle-manifest</id>
+            <phase>process-classes</phase>
+            <goals>
+              <goal>manifest</goal>
+            </goals>
+            <configuration>
+              <instructions>
+                <Bundle-SymbolicName>org.eclipse.jetty.tests.test-spec-webapp</Bundle-SymbolicName>
+                <Bundle-Description>Test Webapp for Servlet 3.1 Features</Bundle-Description>
+                <Import-Package>
+                  javax.servlet.jsp.*;version="[2.2.0, 3.0)",
+                  javax.transaction.*;version="[1.1, 2.0)", 
+                  javax.servlet.*;version="3.0",
+                  javax.sql,
+                  org.eclipse.jetty.webapp;version="9.2",org.eclipse.jetty.plus.jndi;version="9.2",
+                  org.eclipse.jetty.security;version="9.2",
+                  com.acme;version="9.2",
+                  *
+                </Import-Package>
+                <Export-Package>com.acme.test;version="${parsedVersion.majorVersion}.${parsedVersion.minorVersion}.${parsedVersion.incrementalVersion}"</Export-Package>
+                <Web-ContextPath>/</Web-ContextPath>
+                <Bundle-ClassPath>.,WEB-INF/classes,WEB-INF/lib</Bundle-ClassPath>
+                <Jetty-ContextFilePath>./META-INF/plugin-context.xml</Jetty-ContextFilePath>
+                <_nouses>true</_nouses>
+              </instructions>
+            </configuration>
+           </execution>
+        </executions>
+      </plugin>
+
+
+       <plugin>
+         <artifactId>maven-antrun-plugin</artifactId>
+         <executions>
+           <execution>
+            <id>generate-xml-files</id>
+            <phase>process-resources</phase>
+            <configuration>
+              <tasks>
+                <concat destfile="${project.build.directory}/plugin-context.xml">
+                  <filelist dir="src/main/templates/" files="plugin-context-header.xml,env-definitions.xml" />
+                </concat>
+                <concat destfile="${project.build.directory}/test-spec.xml">
+                  <filelist dir="src/main/templates/" files="annotations-context-header.xml,env-definitions.xml" />
+                </concat>
+              </tasks>
+            </configuration>
+            <goals>
+              <goal>run</goal>
+            </goals>
+           </execution>
+         </executions>
+       </plugin>
+       <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-dependency-plugin</artifactId>
+        <executions>
+          <execution>
+            <phase>package</phase>
+            <goals>
+              <goal>copy</goal>
+            </goals>
+            <configuration>
+              <artifactItems>
+                <artifactItem>
+                  <groupId>org.eclipse.jetty.tests</groupId>
+                  <artifactId>test-mock-resources</artifactId>
+                  <version>${project.version}</version>
+                  <type>jar</type>
+                  <includes>**</includes>
+                  <overWrite>true</overWrite>
+                  <outputDirectory>${project.build.directory}/lib/jndi</outputDirectory>
+                </artifactItem>
+              </artifactItems>
+          </configuration>
+          </execution>
+        </executions>
+       </plugin>
+       <!-- plugin>
+          <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-assembly-plugin</artifactId>
+          <version>2.2-beta-3</version>
+          <executions>
+            <execution>
+              <phase>package</phase>
+              <goals>
+                <goal>single</goal>
+              </goals>
+              <configuration>
+                <descriptors>
+                  <descriptor>${basedir}/src/main/assembly/config.xml</descriptor>
+                </descriptors>
+              </configuration>
+            </execution>
+          </executions>
+       </plugin -->
+      <plugin>
+        <groupId>org.eclipse.jetty</groupId>
+        <artifactId>jetty-maven-plugin</artifactId>
+        <version>${project.version}</version>
+        <configuration>
+          <scanIntervalSeconds>10</scanIntervalSeconds>
+          <useTestClasspath>true</useTestClasspath>
+         <webAppXml>${project.build.directory}/plugin-context.xml</webAppXml>
+         <webAppConfig>
+           <war>src/main/webapp</war>
+           <descriptor>src/main/webapp/WEB-INF/web.xml</descriptor>
+           <contextPath>/test-spec</contextPath>
+            <containerIncludeJarPattern>.*/javax.servlet-[^/]*\.jar$</containerIncludeJarPattern>
+            <configurationDiscovered>true</configurationDiscovered>
+            <jettyEnvXml>${basedir}/src/main/webapp/WEB-INF/jetty-env.xml</jettyEnvXml>
+         </webAppConfig>
+          <loginServices>
+            <loginService implementation="org.eclipse.jetty.security.HashLoginService">
+              <name>Test Realm</name>
+              <config>src/etc/realm.properties</config>
+            </loginService>
+          </loginServices>
+        </configuration>
+        <dependencies>
+          <dependency>
+            <groupId>org.eclipse.jetty.tests</groupId>
+            <artifactId>test-mock-resources</artifactId>
+            <version>${project.version}</version>
+          </dependency>
+        </dependencies>
+      </plugin>
+    </plugins>
+  </build>
+  <dependencies>
+    <dependency>
+      <groupId>javax.transaction</groupId>
+      <artifactId>javax.transaction-api</artifactId>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-server</artifactId>
+      <version>${project.version}</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.orbit</groupId>
+      <artifactId>javax.mail.glassfish</artifactId>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+       <groupId>javax.servlet</groupId>
+       <artifactId>javax.servlet-api</artifactId>
+       <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>javax.annotation</groupId>
+      <artifactId>javax.annotation-api</artifactId>
+      <scope>provided</scope>
+    </dependency>
+     <dependency>
+       <groupId>org.eclipse.jetty.tests</groupId>
+       <artifactId>test-web-fragment</artifactId>
+       <version>${project.version}</version>
+     </dependency>
+     <dependency>
+       <groupId>org.eclipse.jetty.tests</groupId>
+       <artifactId>test-container-initializer</artifactId>
+       <version>${project.version}</version>
+     </dependency>
+     <dependency>
+       <groupId>org.eclipse.jetty</groupId>
+       <artifactId>jetty-util</artifactId>
+       <version>${project.version}</version>
+     </dependency>
+  </dependencies>
+</project>
diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/etc/realm.properties b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/etc/realm.properties
new file mode 100644
index 0000000..9d88b85
--- /dev/null
+++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/etc/realm.properties
@@ -0,0 +1,21 @@
+#
+# This file defines users passwords and roles for a HashUserRealm
+#
+# The format is
+#  <username>: <password>[,<rolename> ...]
+#
+# Passwords may be clear text, obfuscated or checksummed.  The class 
+# org.eclipse.util.Password should be used to generate obfuscated
+# passwords or password checksums
+#
+# If DIGEST Authentication is used, the password must be in a recoverable
+# format, either plain text or OBF:.
+#
+jetty: MD5:164c88b302622e17050af52c89945d44,user
+admin: CRYPT:adpexzg3FUZAk,server-administrator,content-administrator,admin,user
+other: OBF:1xmk1w261u9r1w1c1xmq,user
+plain: plain,user
+user: password,user
+
+# This entry is for digest auth.  The credential is a MD5 hash of username:realmname:password
+digest: MD5:6e120743ad67abfbc385bc2bb754e297,user
diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/assembly/config.xml b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/assembly/config.xml
new file mode 100644
index 0000000..f16ec5f
--- /dev/null
+++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/assembly/config.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<assembly>
+  <id>config</id>
+  <includeBaseDirectory>false</includeBaseDirectory>
+  <formats>
+    <format>jar</format>
+  </formats>
+  <fileSets>
+    <fileSet>
+      <directory>src/main/config</directory>
+      <outputDirectory></outputDirectory>
+      <includes>
+        <include>**</include>
+      </includes>
+      <excludes>
+        <exclude>**/resources/**</exclude>
+      </excludes>
+    </fileSet>
+    <fileSet>
+      <directory>target</directory>
+      <outputDirectory>demo-base/webapps</outputDirectory>
+      <includes>
+        <include>test-spec.xml</include>
+      </includes>
+    </fileSet>
+    <fileSet>
+      <directory>target/lib/jndi</directory>
+      <outputDirectory>demo-base/lib/ext</outputDirectory>
+      <includes>
+        <include>*.jar</include>
+      </includes>
+    </fileSet>
+  </fileSets>
+</assembly>
diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/assembly/web-bundle.xml b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/assembly/web-bundle.xml
new file mode 100644
index 0000000..2f3701a
--- /dev/null
+++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/assembly/web-bundle.xml
@@ -0,0 +1,18 @@
+<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0" 
+  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd">
+  <id>webbundle</id>
+  <formats>
+    <format>jar</format>
+  </formats>
+  <includeBaseDirectory>false</includeBaseDirectory>
+  <fileSets>
+    <fileSet>
+      <directory>${basedir}/${project.build.directory}/${project.build.finalName}/</directory>
+      <outputDirectory></outputDirectory>
+      <includes>
+        <include>**/*.*</include>
+      </includes>
+    </fileSet>
+  </fileSets>
+</assembly>
diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/test/AnnotatedListener.java b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/test/AnnotatedListener.java
new file mode 100644
index 0000000..020b714
--- /dev/null
+++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/test/AnnotatedListener.java
@@ -0,0 +1,149 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package com.acme.test;
+
+import javax.annotation.Resource;
+import javax.servlet.ServletContextAttributeEvent;
+import javax.servlet.ServletContextAttributeListener;
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+import javax.servlet.ServletRequestAttributeEvent;
+import javax.servlet.ServletRequestAttributeListener;
+import javax.servlet.ServletRequestEvent;
+import javax.servlet.ServletRequestListener;
+import javax.servlet.annotation.WebListener;
+import javax.servlet.http.HttpSessionActivationListener;
+import javax.servlet.http.HttpSessionAttributeListener;
+import javax.servlet.http.HttpSessionBindingEvent;
+import javax.servlet.http.HttpSessionEvent;
+import javax.servlet.http.HttpSessionListener;
+
+
+
+ at WebListener
+public class AnnotatedListener implements HttpSessionListener,  HttpSessionAttributeListener, HttpSessionActivationListener, ServletContextListener, ServletContextAttributeListener, ServletRequestListener, ServletRequestAttributeListener
+{
+
+    @Resource(mappedName="maxAmount")
+    private Double maxAmount;
+
+    public void attributeAdded(HttpSessionBindingEvent se)
+    {
+        // System.err.println("attributedAdded "+se);
+    }
+
+    public void attributeRemoved(HttpSessionBindingEvent se)
+    {
+        // System.err.println("attributeRemoved "+se);
+    }
+
+    public void attributeReplaced(HttpSessionBindingEvent se)
+    {
+        // System.err.println("attributeReplaced "+se);
+    }
+
+    public void sessionWillPassivate(HttpSessionEvent se)
+    {
+        // System.err.println("sessionWillPassivate "+se);
+    }
+
+    public void sessionDidActivate(HttpSessionEvent se)
+    {
+        // System.err.println("sessionDidActivate "+se);
+    }
+
+    public void contextInitialized(ServletContextEvent sce)
+    {
+        sce.getServletContext().setAttribute("com.acme.AnnotationTest.sclInjectWebListenerTest", Boolean.valueOf(maxAmount!=null));
+    }
+
+    public void contextDestroyed(ServletContextEvent sce)
+    {
+        // System.err.println("contextDestroyed "+sce);
+    }
+
+    public void attributeAdded(ServletContextAttributeEvent scab)
+    {
+        // System.err.println("attributeAdded "+scab);
+    }
+
+    public void attributeRemoved(ServletContextAttributeEvent scab)
+    {
+        // System.err.println("attributeRemoved "+scab);
+    }
+
+    public void attributeReplaced(ServletContextAttributeEvent scab)
+    {
+        // System.err.println("attributeReplaced "+scab);
+    }
+
+    public void requestDestroyed(ServletRequestEvent sre)
+    {
+        // System.err.println("requestDestroyed "+sre);
+    }
+
+    public void requestInitialized(ServletRequestEvent sre)
+    {
+        // System.err.println("requestInitialized "+sre);
+    }
+
+    public void attributeAdded(ServletRequestAttributeEvent srae)
+    {
+        // System.err.println("attributeAdded "+srae);
+    }
+
+    public void attributeRemoved(ServletRequestAttributeEvent srae)
+    {
+        // System.err.println("attributeRemoved "+srae);
+    }
+
+    public void attributeReplaced(ServletRequestAttributeEvent srae)
+    {
+        // System.err.println("attributeReplaced "+srae);
+    }
+
+    public void sessionCreated(HttpSessionEvent se)
+    {
+        // System.err.println("sessionCreated "+se);
+    }
+
+    public void sessionDestroyed(HttpSessionEvent se)
+    {
+        // System.err.println("sessionDestroyed "+se);
+    }
+
+    public void requestCompleted(ServletRequestEvent rre)
+    {
+        // TODO Auto-generated method stub
+        
+    }
+
+    public void requestResumed(ServletRequestEvent rre)
+    {
+        // TODO Auto-generated method stub
+        
+    }
+
+    public void requestSuspended(ServletRequestEvent rre)
+    {
+        // TODO Auto-generated method stub
+        
+    }
+
+}
diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/test/AnnotationTest.java b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/test/AnnotationTest.java
new file mode 100644
index 0000000..a339d2f
--- /dev/null
+++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/test/AnnotationTest.java
@@ -0,0 +1,346 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package com.acme.test;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+import javax.annotation.Resource;
+import javax.annotation.security.DeclareRoles;
+import javax.annotation.security.RunAs;
+import javax.naming.InitialContext;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.annotation.WebInitParam;
+import javax.servlet.annotation.WebServlet;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.sql.DataSource;
+import javax.transaction.UserTransaction;
+
+/**
+ * AnnotationTest
+ * 
+ * Use Annotations from within Jetty.
+ * 
+ * Also, use servlet spec 2.5 resource injection and lifecycle callbacks from within the web.xml
+ * to set up some of the JNDI resources.
+ *
+ */
+
+ at RunAs("special")
+ at WebServlet(urlPatterns = {"/","/test/*"}, name="AnnotationTest", initParams={@WebInitParam(name="fromAnnotation", value="xyz")})
+ at DeclareRoles({"user","client"})
+public class AnnotationTest extends HttpServlet 
+{
+    static List<String> __HandlesTypes; 
+    private String postConstructResult = "";
+    private String dsResult = "";
+    private String envResult = "";
+    private String envLookupResult = "";
+    private String envResult2 ="";
+    private String envLookupResult2 = "";
+    private String envResult3 = "";
+    private String envLookupResult3 = "";
+    private String dsLookupResult = "";
+    private String txResult = "";
+    private String txLookupResult = "";
+    private DataSource myDS;
+    private ServletConfig config;
+    
+    @Resource(mappedName="UserTransaction")
+    private UserTransaction myUserTransaction;
+
+
+    @Resource(mappedName="maxAmount")
+    private Double maxAmount;
+    
+    @Resource(name="someAmount")
+    private Double minAmount;
+
+    @Resource
+    private Double avgAmount;
+
+   
+    @Resource(mappedName="jdbc/mydatasource")
+    public void setMyDatasource(DataSource ds)
+    {
+        myDS=ds;
+    }
+  
+    
+    @PostConstruct
+    private void myPostConstructMethod ()
+    {       
+        postConstructResult = "<span class=\"pass\">PASS</span>";
+       try 
+       {
+           dsResult = (myDS==null?"<span class=\"fail\">FAIL</span>":"<span class=\"pass\">myDS="+myDS.toString()+"</span>");
+       }
+       catch (Exception e)
+       {
+           dsResult = "<span class=\"fail\">FAIL:</span> "+e;
+       }
+
+
+       envResult = (maxAmount==null?"FAIL</span>":"<span class=\"pass\">maxAmount="+maxAmount.toString()+"</span>");
+       
+       try
+       {
+           InitialContext ic = new InitialContext();
+           envLookupResult = "java:comp/env/com.acme.test.AnnotationTest/maxAmount="+ic.lookup("java:comp/env/com.acme.test.AnnotationTest/maxAmount");
+       }
+       catch (Exception e)
+       {
+           envLookupResult = "<span class=\"fail\">FAIL:</span> "+e;
+       }
+
+      envResult2 = (minAmount==null?"<span class=\"fail\">FAIL</span>":"<span class=\"pass\">minAmount="+minAmount.toString()+"</span>");
+      try
+      {
+          InitialContext ic = new InitialContext();
+          envLookupResult2 = "java:comp/env/someAmount="+ic.lookup("java:comp/env/someAmount");
+      }
+      catch (Exception e)
+      {
+          envLookupResult2 = "<span class=\"fail\">FAIL:</span> "+e;
+      }
+      envResult3 = (minAmount==null?"<span class=\"fail\">FAIL</span>":"<span class=\"pass\">avgAmount="+avgAmount.toString()+"</span>");
+      try
+      {
+          InitialContext ic = new InitialContext();
+          envLookupResult3 = "java:comp/env/com.acme.test.AnnotationTest/avgAmount="+ic.lookup("java:comp/env/com.acme.test.AnnotationTest/avgAmount");
+      }
+      catch (Exception e)
+      {
+          envLookupResult3 = "<span class=\"fail\">FAIL:</span> "+e;
+      }
+      
+      
+      
+       try
+       {
+           InitialContext ic = new InitialContext();
+           dsLookupResult = "java:comp/env/com.acme.test.AnnotationTest/myDatasource="+ic.lookup("java:comp/env/com.acme.test.AnnotationTest/myDatasource");
+       }
+       catch (Exception e)
+       {
+           dsLookupResult = "<span class=\"fail\">FAIL:</span> "+e;
+       }
+       
+       txResult = (myUserTransaction==null?"<span class=\"fail\">FAIL</span>":"<span class=\"pass\">myUserTransaction="+myUserTransaction+"</span>");
+       try
+       {
+           InitialContext ic = new InitialContext();
+           txLookupResult = "java:comp/env/com.acme.test.AnnotationTest/myUserTransaction="+ic.lookup("java:comp/env/com.acme.test.AnnotationTest/myUserTransaction");
+       }
+       catch (Exception e)
+       {
+           txLookupResult = "<span class=\"fail\">FAIL:</span> "+e;
+       }
+    }
+    
+    @PreDestroy
+    private void myPreDestroyMethod()
+    {
+    }
+    
+    public void init(ServletConfig config) throws ServletException
+    {
+        super.init(config);
+        this.config = config;
+    }
+
+    
+    
+    /* ------------------------------------------------------------ */
+    public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+    {
+        doGet(request, response);
+    }
+
+    /* ------------------------------------------------------------ */
+    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+    {      
+        try
+        {
+            response.setContentType("text/html");
+            ServletOutputStream out = response.getOutputStream();
+            out.println("<html>");
+            out.println("<HEAD><link rel=\"stylesheet\" type=\"text/css\"  href=\"stylesheet.css\"/></HEAD>");
+            out.println("<body>");
+            out.println("<h1>Results</h1>");
+
+            out.println("<h2>Init Params from Annotation</h2>");
+            out.println("<pre>");
+            out.println("initParams={@WebInitParam(name=\"fromAnnotation\", value=\"xyz\")}");
+            out.println("</pre>");
+            out.println("<p><b>Result: "+("xyz".equals(config.getInitParameter("fromAnnotation"))? "<span class=\"pass\">PASS": "<span class=\"fail\">FAIL")+"</span></p>");
+
+            out.println("<h2>Init Params from web-fragment</h2>");
+            out.println("<pre>");
+            out.println("extra1=123, extra2=345");
+            out.println("</pre>");
+            boolean fragInitParamResult = "123".equals(config.getInitParameter("extra1")) && "345".equals(config.getInitParameter("extra2"));
+            out.println("<p><b>Result: "+(fragInitParamResult? "<span class=\"pass\">PASS": "<span class=\"fail\">FAIL")+"</span></p>");
+
+
+             __HandlesTypes = Arrays.asList( "javax.servlet.GenericServlet", 
+                                             "javax.servlet.http.HttpServlet", 
+                                             "com.acme.test.AsyncListenerServlet",
+                                             "com.acme.test.AnnotationTest", 
+                                             "com.acme.test.RoleAnnotationTest", 
+                                             "com.acme.test.MultiPartTest", 
+                                             "com.acme.fragment.FragmentServlet", 
+                                             "com.acme.test.TestListener",
+                                             "com.acme.test.SecuredServlet",
+                                             "com.acme.test.Bar");
+             out.println("<h2>@ContainerInitializer</h2>");
+             out.println("<pre>");
+             out.println("@HandlesTypes({javax.servlet.Servlet.class, Foo.class})");
+             out.println("</pre>");
+             out.print("<p><b>Result: ");
+             List<Class> classes = (List<Class>)config.getServletContext().getAttribute("com.acme.Foo");
+             List<String> classNames = new ArrayList<String>();
+             if (classes != null)
+             {
+                 for (Class c: classes)
+                 {
+                     classNames.add(c.getName());
+                     out.print(c.getName()+" ");
+                 }
+                
+                 if (classNames.size() != __HandlesTypes.size())
+                     out.println("<br/><span class=\"fail\">FAIL</span>");
+                 else if (!classNames.containsAll(__HandlesTypes))
+                     out.println("<br/><span class=\"fail\">FAIL</span>");
+                 else
+                     out.println("<br/><span class=\"pass\">PASS</span>");
+             }
+             else
+                 out.print("<br/><span class=\"fail\">FAIL</span> (No such attribute com.acme.Foo)");
+             out.println("</b></p>");
+
+            out.println("<h2>Complete Servlet Registration</h2>");
+            Boolean complete = (Boolean)config.getServletContext().getAttribute("com.acme.AnnotationTest.complete");
+            out.println("<p><b>Result: "+(complete.booleanValue()?"<span class=\"pass\">PASS":"<span class=\"fail\">FAIL")+"</span></b></p>");
+            
+            out.println("<h2>ServletContextListener Programmatic Registration from ServletContainerInitializer</h2>");
+            Boolean programmaticListener = (Boolean)config.getServletContext().getAttribute("com.acme.AnnotationTest.listenerTest");
+            out.println("<p><b>Result: "+(programmaticListener.booleanValue()?"<span class=\"pass\">PASS":"<span class=\"fail\">FAIL")+"</span></b></p>");
+            
+            out.println("<h2>ServletContextListener Programmatic Registration Prevented from ServletContextListener</h2>");
+            Boolean programmaticListenerPrevention = (Boolean)config.getServletContext().getAttribute("com.acme.AnnotationTest.listenerRegoTest");
+            out.println("<p><b>Result: "+(programmaticListenerPrevention.booleanValue()?"<span class=\"pass\">PASS":"<span class=\"fail\">FAIL")+"</span></b></p>");
+            
+            out.println("<h2>ServletContextListener Registration Prevented from ServletContextListener</h2>");
+            Boolean webListenerPrevention = (Boolean)config.getServletContext().getAttribute("com.acme.AnnotationTest.sclFromSclRegoTest");
+            out.println("<p><b>Result: "+(webListenerPrevention.booleanValue()?"<span class=\"pass\">PASS":"<span class=\"fail\">FAIL")+"</span></b></p>");
+            
+            out.println("<h2>ServletContextListener In web.xml Injected</h2>");
+            Boolean listenerInject = (Boolean)config.getServletContext().getAttribute("com.acme.AnnotationTest.sclInjectTest");
+            out.println("<p><b>Result: "+(listenerInject.booleanValue()?"<span class=\"pass\">PASS":"<span class=\"fail\">FAIL")+"</span></b></p>");
+            
+            out.println("<h2>ServletContextListener as @WebListener Injected</h2>");
+            Boolean annotatedListenerInject = (Boolean)config.getServletContext().getAttribute("com.acme.AnnotationTest.sclInjectWebListenerTest");
+            out.println("<p><b>Result: "+(annotatedListenerInject.booleanValue()?"<span class=\"pass\">PASS":"<span class=\"fail\">FAIL")+"</span></b></p>");
+            
+            out.println("<h2>Programmatic Listener Injected</h2>");
+            Boolean programListenerInject = (Boolean)config.getServletContext().getAttribute("com.acme.AnnotationTest.programListenerInjectTest");
+            out.println("<p><b>Result: "+(programListenerInject.booleanValue()?"<span class=\"pass\">PASS":"<span class=\"fail\">FAIL")+"</span></b></p>");
+            
+            out.println("<h2>Invalid Type for Listener Detection</h2>");         
+            Boolean badListener = (Boolean)config.getServletContext().getAttribute("com.acme.AnnotationTest.invalidListenerRegoTest");
+            out.println("<p><b>Result: "+(badListener.booleanValue()?"<span class=\"pass\">PASS":"<span class=\"fail\">FAIL")+"</span></b></p>");
+            
+            
+            out.println("<h2>@PostConstruct Callback</h2>");
+            out.println("<pre>");
+            out.println("@PostConstruct");
+            out.println("private void myPostConstructMethod ()");
+            out.println("{}"); 
+            out.println("</pre>");
+            out.println("<p><b>Result: "+postConstructResult+"</b></p>");
+           
+            
+            out.println("<h2>@Resource Injection for DataSource</h2>");    
+            out.println("<pre>");         
+            out.println("@Resource(mappedName=\"jdbc/mydatasource\");");
+            out.println("public void setMyDatasource(DataSource ds)");
+            out.println("{");
+            out.println("myDS=ds;");
+            out.println("}");
+            out.println("</pre>");
+            out.println("<p><b>Result: "+dsResult+"</b>");
+            out.println("<br/><b>JNDI Lookup Result: "+dsLookupResult+"</b></p>");
+
+            
+            out.println("<h2>@Resource Injection for env-entry </h2>");
+            out.println("<pre>");
+            out.println("@Resource(mappedName=\"maxAmount\")");
+            out.println("private Double maxAmount;");
+            out.println("@Resource(name=\"minAmount\")");
+            out.println("private Double minAmount;");
+            out.println("</pre>");
+            if (maxAmount==null)
+                out.println("<p><b>Result: "+envResult+":  <span class=\"fail\">FAIL");
+            else
+                out.println("<p><b>Result: "+envResult+": "+(maxAmount.compareTo(new Double(55))==0?" <span class=\"pass\">PASS":" <span class=\"fail\">FAIL")+"</span></b>");     
+            out.println("<br/><b>JNDI Lookup Result: "+envLookupResult+"</b>");
+
+            if (minAmount==null)
+                out.println("<p><b>Result: "+envResult2+":  <span class=\"fail\">FAIL");
+            else
+                out.println("<br/><b>Result: "+envResult2+": "+(minAmount.compareTo(new Double("0.99"))==0?" <span class=\"pass\">PASS":" <span class=\"fail\">FAIL")+"</span></b>");     
+            out.println("<br/><b>JNDI Lookup Result: "+envLookupResult2+"</b>");
+
+            if (avgAmount==null)
+                out.println("<p><b>Result: "+envResult3+":  <span class=\"fail\">FAIL");
+            else
+                out.println("<br/><b>Result: "+envResult3+": "+(avgAmount.compareTo(new Double("1.25"))==0?" <span class=\"pass\">PASS":" <span class=\"fail\">FAIL")+"</span></b>");     
+            out.println("<br/><b>JNDI Lookup Result: "+envLookupResult3+"</b></p>");     
+            
+            out.println("<h2>@Resource Injection for UserTransaction </h2>");
+            out.println("<pre>");
+            out.println("@Resource(mappedName=\"UserTransaction\")");
+            out.println("private UserTransaction myUserTransaction;");
+            out.println("</pre>");
+            out.println("<p><b>Result: "+txResult+"</b>");
+            out.println("<br/><b>JNDI Lookup Result: "+txLookupResult+"</b></p>");
+
+            out.println("</body>");            
+            out.println("</html>");
+            out.flush();
+        }
+        catch (Exception e)
+        {
+            throw new ServletException(e);
+        }
+    }
+    
+
+  
+   
+}
diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/test/AsyncListenerServlet.java b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/test/AsyncListenerServlet.java
new file mode 100644
index 0000000..71a9db1
--- /dev/null
+++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/test/AsyncListenerServlet.java
@@ -0,0 +1,127 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package com.acme.test;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.Resource;
+import javax.servlet.AsyncContext;
+import javax.servlet.AsyncEvent;
+import javax.servlet.AsyncListener;
+import javax.servlet.ServletException;
+import javax.servlet.annotation.WebServlet;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+ at WebServlet(urlPatterns="/asy/*", asyncSupported=true)
+public class AsyncListenerServlet extends HttpServlet 
+{
+    public static class MyAsyncListener implements AsyncListener
+    {
+        @Resource(mappedName="maxAmount")
+        private Double maxAmount;
+        
+        boolean postConstructCalled = false;
+        boolean resourceInjected = false;
+        
+        @PostConstruct
+        public void postConstruct()
+        {
+            postConstructCalled = true;
+            resourceInjected = (maxAmount != null);
+        }
+        
+        public boolean isPostConstructCalled()
+        {
+            return postConstructCalled;
+        }
+      
+        public boolean isResourceInjected()
+        {
+            return resourceInjected;
+        }
+
+        @Override
+        public void onComplete(AsyncEvent event) throws IOException
+        {
+            // TODO Auto-generated method stub
+            
+        }
+
+        @Override
+        public void onTimeout(AsyncEvent event) throws IOException
+        {
+            // TODO Auto-generated method stub
+            
+        }
+
+        @Override
+        public void onError(AsyncEvent event) throws IOException
+        {
+            // TODO Auto-generated method stub
+            
+        }
+
+        @Override
+        public void onStartAsync(AsyncEvent event) throws IOException
+        {
+            // TODO Auto-generated method stub
+            
+        }    
+    }
+
+    
+    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
+    throws ServletException, IOException 
+    {
+        AsyncContext asyncContext = req.startAsync();
+        MyAsyncListener listener = asyncContext.createListener(MyAsyncListener.class);
+
+        PrintWriter writer = resp.getWriter();
+        writer.println( "<html>");
+        writer.println("<HEAD><link rel=\"stylesheet\" type=\"text/css\"  href=\"../stylesheet.css\"/></HEAD>");
+        writer.println( "<body>");
+        writer.println("<h1>AsyncListener</h2>");
+        writer.println("<pre>");
+        writer.println("<h2>@PostConstruct Callback</h2>");
+        writer.println("<pre>");
+        writer.println("@PostConstruct");
+        writer.println("private void postConstruct ()");
+        writer.println("{}"); 
+        writer.println("</pre>");
+        writer.println("<br/><b>Result: "+(listener.isPostConstructCalled()?"<span class=\"pass\">PASS</span>":"<span class=\"fail\">FAIL</span>")+"</b>");
+        
+        writer.println("<h2>@Resource Injection for env-entry </h2>");
+        writer.println("<pre>");
+        writer.println("@Resource(mappedName=\"maxAmount\")");
+        writer.println("private Double maxAmount;");
+        writer.println("</pre>");
+        writer.println("<br/><b>Result: "+(listener.isResourceInjected()?" <span class=\"pass\">PASS</span>":" <span class=\"FAIL\">FAIL</span>")+"</b>");    
+        
+        writer.println( "</body>");
+        writer.println( "</html>");
+        writer.flush();
+        writer.close();
+        
+        asyncContext.complete();
+    }
+}
diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/test/Bar.java b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/test/Bar.java
new file mode 100644
index 0000000..e1af7c6
--- /dev/null
+++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/test/Bar.java
@@ -0,0 +1,28 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package com.acme.test;
+
+
+public class Bar {
+        
+        @com.acme.initializer.Foo(2)
+        public void someMethod () {
+        }
+
+}
diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/test/MultiPartTest.java b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/test/MultiPartTest.java
new file mode 100644
index 0000000..7a5a101
--- /dev/null
+++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/test/MultiPartTest.java
@@ -0,0 +1,120 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package com.acme.test;
+
+import java.io.IOException;
+import java.util.Collection;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.annotation.MultipartConfig;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.Part;
+
+import org.eclipse.jetty.util.IO;
+/**
+ * MultiPartTest
+ * 
+ * Test Servlet 3.0 MultiPart Mime handling.
+ * 
+ *
+ */
+
+ at MultipartConfig(location="foo/bar", maxFileSize=10240, maxRequestSize=-1, fileSizeThreshold=2048)
+public class MultiPartTest extends HttpServlet 
+{
+    private ServletConfig config;
+    
+    
+    public void init(ServletConfig config) throws ServletException
+    {
+        super.init(config);
+        this.config = config;
+    }
+
+    
+    
+    /* ------------------------------------------------------------ */
+    public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+    {
+
+        try
+        {
+            response.setContentType("text/html");
+            ServletOutputStream out = response.getOutputStream();
+            out.println("<html>");
+            out.println("<HEAD><link rel=\"stylesheet\" type=\"text/css\"  href=\"stylesheet.css\"/></HEAD>");
+            out.println("<body>");
+            out.println("<h1>Results</h1>");
+            out.println("<p>");
+
+            Collection<Part> parts = request.getParts();
+            out.println("<b>Parts:</b> "+parts.size());
+            for (Part p: parts)
+            {
+                out.println("<h3>"+p.getName()+"</h3>");
+                out.println("<b>Size:</b> "+p.getSize());
+                if (p.getContentType() == null || p.getContentType().startsWith("text/plain"))
+                {
+                    out.println("<p>");
+                    IO.copy(p.getInputStream(),out);
+                    out.println("</p>");
+                }
+            } 
+            out.println("</body>");            
+            out.println("</html>");
+            out.flush();
+        }
+        catch (ServletException e)
+        {
+            throw e;
+        }
+        catch (Exception e)
+        {
+            throw new ServletException(e);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+    {      
+        try
+        {
+            response.setContentType("text/html");
+            ServletOutputStream out = response.getOutputStream();
+            out.println("<html>");
+            out.println("<body>");
+            out.println("<h1>Use a POST Instead</h1>");
+            out.println("</body>");            
+            out.println("</html>");
+            out.flush();
+        }
+        catch (Exception e)
+        {
+            throw new ServletException(e);
+        }
+    }
+    
+
+  
+   
+}
diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/test/RoleAnnotationTest.java b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/test/RoleAnnotationTest.java
new file mode 100644
index 0000000..6792c78
--- /dev/null
+++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/test/RoleAnnotationTest.java
@@ -0,0 +1,95 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package com.acme.test;
+
+import java.io.IOException;
+
+import javax.annotation.security.DeclareRoles;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * RoleAnnotationTest
+ * 
+ * Use DeclareRolesAnnotations from within Jetty.
+ * 
+ *
+ */
+
+
+ at DeclareRoles({"server-administrator","user"})
+public class RoleAnnotationTest extends HttpServlet 
+{
+    private ServletConfig _config;
+    
+    public void init(ServletConfig config) throws ServletException
+    {
+        super.init(config);
+        _config = config;
+    }
+
+    
+    
+    /* ------------------------------------------------------------ */
+    public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+    {
+        doGet(request, response);
+    }
+
+    /* ------------------------------------------------------------ */
+    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+    {      
+        try
+        {
+            response.setContentType("text/html");
+            ServletOutputStream out = response.getOutputStream();
+            out.println("<html>");
+            out.println("<HEAD><link rel=\"stylesheet\" type=\"text/css\"  href=\"stylesheet.css\"/></HEAD>");
+            out.println("<h1>Jetty DeclareRoles Annotation Results</h1>");
+            out.println("<body>");
+            
+            out.println("<h2>Roles</h2>");
+            boolean result = request.isUserInRole("other");
+            out.println("<br/><b>Result: isUserInRole(\"other\")="+result+":"+ (result==false?" <span class=\"pass\">PASS":" <span class=\"fail\">FAIL")+"</span></b>");
+
+            result = request.isUserInRole("manager");
+            out.println("<br/><b>Result: isUserInRole(\"manager\")="+result+":"+ (result?" <span class=\"pass\">PASS":" <span class=\"fail\">FAIL")+"</span></b>");
+            result = request.isUserInRole("user");
+            out.println("<br/><b>Result: isUserInRole(\"user\")="+result+":"+ (result?" <span class=\"pass\">PASS":" <span class=\"fail\">FAIL")+"</span></b>");
+            String context = _config.getServletContext().getContextPath();
+            if (!context.endsWith("/"))
+                context += "/";
+            
+            out.println("<p><A HREF=\""+context+"logout.jsp\">Logout</A></p>");
+            
+            out.println("</body>");            
+            out.println("</html>");
+            out.flush();
+        }
+        catch (Exception e)
+        {
+            throw new ServletException(e);
+        }
+    }
+
+}
diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/test/SecuredServlet.java b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/test/SecuredServlet.java
new file mode 100644
index 0000000..1d0b512
--- /dev/null
+++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/test/SecuredServlet.java
@@ -0,0 +1,60 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package com.acme.test;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+
+import javax.servlet.ServletException;
+import javax.servlet.annotation.HttpConstraint;
+import javax.servlet.annotation.ServletSecurity;
+import javax.servlet.annotation.WebServlet;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+ at WebServlet(urlPatterns="/sec/*")
+ at ServletSecurity(@HttpConstraint(rolesAllowed="admin"))
+public class SecuredServlet extends HttpServlet 
+{
+
+
+    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
+    throws ServletException, IOException 
+    {
+        PrintWriter writer = resp.getWriter();
+        writer.println( "<html>");
+        writer.println("<HEAD><link rel=\"stylesheet\" type=\"text/css\"  href=\"../stylesheet.css\"/></HEAD>");
+        writer.println("<h1>@ServletSecurity</h1>");
+        writer.println("<body>");
+        writer.println("<pre>");
+        writer.println("@ServletSecurity");
+        writer.println("public class SecuredServlet");
+        writer.println("</pre>");
+        writer.println("<p><b>Result: <span class=\"pass\">PASS</span></b></p>");
+        String context = getServletConfig().getServletContext().getContextPath();
+        if (!context.endsWith("/"))
+            context += "/";
+        writer.println("<p><A HREF=\""+context+"logout.jsp\">Logout</A></p>");
+        writer.println( "</body>");
+        writer.println( "</html>");
+        writer.flush();
+        writer.close();
+    }
+}
diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/test/TestListener.java b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/test/TestListener.java
new file mode 100644
index 0000000..47bbc0a
--- /dev/null
+++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/test/TestListener.java
@@ -0,0 +1,235 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package com.acme.test;
+import java.util.EventListener;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.Resource;
+import javax.servlet.ServletContextAttributeEvent;
+import javax.servlet.ServletContextAttributeListener;
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+import javax.servlet.ServletRequestAttributeEvent;
+import javax.servlet.ServletRequestAttributeListener;
+import javax.servlet.ServletRequestEvent;
+import javax.servlet.ServletRequestListener;
+import javax.servlet.annotation.WebListener;
+import javax.servlet.http.HttpSessionActivationListener;
+import javax.servlet.http.HttpSessionAttributeListener;
+import javax.servlet.http.HttpSessionBindingEvent;
+import javax.servlet.http.HttpSessionEvent;
+import javax.servlet.http.HttpSessionIdListener;
+import javax.servlet.http.HttpSessionListener;
+
+
+ at com.acme.initializer.Foo(1)
+ at WebListener
+public class TestListener implements HttpSessionListener,  HttpSessionAttributeListener, HttpSessionActivationListener, ServletContextListener, ServletContextAttributeListener, ServletRequestListener, ServletRequestAttributeListener
+{
+    public static class NaughtyServletContextListener implements ServletContextListener
+    {
+
+        @Override
+        public void contextInitialized(ServletContextEvent sce)
+        {
+            throw new IllegalStateException("Should not call NaughtServletContextListener.contextInitialized");
+        }
+
+        @Override
+        public void contextDestroyed(ServletContextEvent sce)
+        {
+            throw new IllegalStateException("Should not call NaughtServletContextListener.contextDestroyed");
+        }
+    }
+    
+    public static class InvalidListener implements EventListener
+    {
+        public InvalidListener()
+        {}
+    }
+    
+    public static class ValidListener implements HttpSessionIdListener
+    {
+        @Resource(mappedName="maxAmount")
+        private Double maxAmount;
+        
+        public ValidListener()
+        {}
+        
+        @Override
+        public void sessionIdChanged(HttpSessionEvent event, String oldSessionId)
+        {
+           
+        }
+        
+    }
+
+    @Resource(mappedName="maxAmount")
+    private Double maxAmount;
+    
+
+
+    public void attributeAdded(HttpSessionBindingEvent se)
+    {
+        // System.err.println("attributedAdded "+se);
+    }
+
+    public void attributeRemoved(HttpSessionBindingEvent se)
+    {
+        // System.err.println("attributeRemoved "+se);
+    }
+
+    public void attributeReplaced(HttpSessionBindingEvent se)
+    {
+        // System.err.println("attributeReplaced "+se);
+    }
+
+    public void sessionWillPassivate(HttpSessionEvent se)
+    {
+        // System.err.println("sessionWillPassivate "+se);
+    }
+
+    public void sessionDidActivate(HttpSessionEvent se)
+    {
+        // System.err.println("sessionDidActivate "+se);
+    }
+
+    public void contextInitialized(ServletContextEvent sce)
+    {
+        System.err.println("Calling TestListener.contextInitialized");
+        
+        sce.getServletContext().setAttribute("com.acme.AnnotationTest.sclInjectTest", Boolean.valueOf(maxAmount != null));
+        
+        //Can't add a ServletContextListener from a ServletContextListener even if it is declared in web.xml
+        try
+        {
+            sce.getServletContext().addListener(new NaughtyServletContextListener());
+            sce.getServletContext().setAttribute("com.acme.AnnotationTest.sclFromSclRegoTest", Boolean.FALSE);
+        }
+        catch (IllegalArgumentException e)
+        {
+            sce.getServletContext().setAttribute("com.acme.AnnotationTest.sclFromSclRegoTest", Boolean.TRUE);
+        }
+        catch (Exception e)
+        {
+            sce.getServletContext().setAttribute("com.acme.AnnotationTest.sclFromSclRegoTest", Boolean.FALSE);
+        }
+        
+        
+        //Can't add an EventListener not part of the specified list for addListener()
+        try
+        {
+            sce.getServletContext().addListener(new InvalidListener());
+            sce.getServletContext().setAttribute("com.acme.AnnotationTest.invalidListenerRegoTest", Boolean.FALSE);
+        }
+        catch (IllegalArgumentException e)
+        {
+            sce.getServletContext().setAttribute("com.acme.AnnotationTest.invalidListenerRegoTest", Boolean.TRUE);
+        }
+        catch (Exception e)
+        {
+            sce.getServletContext().setAttribute("com.acme.AnnotationTest.invalidListenerRegoTest", Boolean.FALSE);
+        } 
+        
+        //Programmatically add a listener and make sure its injected
+        try
+        {
+            ValidListener l = sce.getServletContext().createListener(ValidListener.class);
+            sce.getServletContext().setAttribute("com.acme.AnnotationTest.programListenerInjectTest", Boolean.valueOf(l != null && l.maxAmount != null));
+        }   
+        catch (Exception e)
+        {
+            sce.getServletContext().setAttribute("com.acme.AnnotationTest.programListenerInjectTest", Boolean.FALSE);
+        }
+    }
+
+    public void contextDestroyed(ServletContextEvent sce)
+    {
+        // System.err.println("contextDestroyed "+sce);
+    }
+
+    public void attributeAdded(ServletContextAttributeEvent scab)
+    {
+        // System.err.println("attributeAdded "+scab);
+    }
+
+    public void attributeRemoved(ServletContextAttributeEvent scab)
+    {
+        // System.err.println("attributeRemoved "+scab);
+    }
+
+    public void attributeReplaced(ServletContextAttributeEvent scab)
+    {
+        // System.err.println("attributeReplaced "+scab);
+    }
+
+    public void requestDestroyed(ServletRequestEvent sre)
+    {
+        // System.err.println("requestDestroyed "+sre);
+    }
+
+    public void requestInitialized(ServletRequestEvent sre)
+    {
+        // System.err.println("requestInitialized "+sre);
+    }
+
+    public void attributeAdded(ServletRequestAttributeEvent srae)
+    {
+        // System.err.println("attributeAdded "+srae);
+    }
+
+    public void attributeRemoved(ServletRequestAttributeEvent srae)
+    {
+        // System.err.println("attributeRemoved "+srae);
+    }
+
+    public void attributeReplaced(ServletRequestAttributeEvent srae)
+    {
+        // System.err.println("attributeReplaced "+srae);
+    }
+
+    public void sessionCreated(HttpSessionEvent se)
+    {
+        // System.err.println("sessionCreated "+se);
+    }
+
+    public void sessionDestroyed(HttpSessionEvent se)
+    {
+        // System.err.println("sessionDestroyed "+se);
+    }
+
+    public void requestCompleted(ServletRequestEvent rre)
+    {
+        // TODO Auto-generated method stub
+        
+    }
+
+    public void requestResumed(ServletRequestEvent rre)
+    {
+        // TODO Auto-generated method stub
+        
+    }
+
+    public void requestSuspended(ServletRequestEvent rre)
+    {
+        // TODO Auto-generated method stub
+        
+    }
+
+}
diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/templates/annotations-context-header.xml b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/templates/annotations-context-header.xml
new file mode 100644
index 0000000..50b5981
--- /dev/null
+++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/templates/annotations-context-header.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0"?>
+<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
+
+
+<!-- =============================================================== -->
+<!-- Configure the test-annotations webapp                           -->
+<!-- =============================================================== -->
+
+<Configure id='wac' class="org.eclipse.jetty.webapp.WebAppContext">
+
+  <New id="tx" class="org.eclipse.jetty.plus.jndi.Transaction">
+    <Arg>
+      <New class="com.acme.MockUserTransaction"/>
+    </Arg>
+  </New>
+
+
+  <!-- =============================================================== -->
+  <!-- Configure the webapp                                            -->
+  <!-- =============================================================== -->
+  <!-- Only uncomment this if you are not using the plus and 
+       annotations sections of start.ini
+
+  <Set name="configurationClasses">
+    <Call class="org.eclipse.jetty.webapp.Configuration$ClassList" name="serverDefault">
+      <Arg><Ref refid="Server" /></Arg>
+      <Call name="addAfter">
+        <Arg name="afterClass">org.eclipse.jetty.webapp.FragmentConfiguration</Arg>
+        <Arg>
+          <Array type="String">
+            <Item>org.eclipse.jetty.plus.webapp.EnvConfiguration</Item>
+            <Item>org.eclipse.jetty.plus.webapp.PlusConfiguration</Item>
+            <Item>org.eclipse.jetty.annotations.AnnotationConfiguration</Item>
+          </Array>
+        </Arg>
+      </Call>
+    </Call>
+  </Set>
+  -->
+
+  <Set name="contextPath">/test-spec</Set>
+  <Set name="war"><Property name="jetty.webapps"/>/test-spec.war</Set>
+  <Set name="configurationDiscovered">true</Set>
+
+  <Get name="securityHandler">
+    <Set name="loginService">
+      <New class="org.eclipse.jetty.security.HashLoginService">
+        <Set name="name">Test Realm</Set>
+        <Set name="config"><SystemProperty name="jetty.base" default="."/>/etc/realm.properties</Set>
+      </New>
+    </Set>
+  </Get>
+
diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/templates/env-definitions.xml b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/templates/env-definitions.xml
new file mode 100644
index 0000000..fcedef4
--- /dev/null
+++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/templates/env-definitions.xml
@@ -0,0 +1,19 @@
+
+  <New id="maxAmount"  class="org.eclipse.jetty.plus.jndi.EnvEntry">
+    <Arg><Ref refid='wac'/></Arg>
+    <Arg>maxAmount</Arg>
+    <Arg type="java.lang.Double">100</Arg>
+    <Arg type="boolean">true</Arg>
+  </New>
+
+
+  <New id="mydatasource" class="org.eclipse.jetty.plus.jndi.Resource">
+    <Arg><Ref refid='wac'/></Arg>
+    <Arg>jdbc/mydatasource</Arg>
+    <Arg>
+     <New class="com.acme.MockDataSource">
+     </New>
+    </Arg>
+   </New>
+
+</Configure>
diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/templates/plugin-context-header.xml b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/templates/plugin-context-header.xml
new file mode 100644
index 0000000..450edda
--- /dev/null
+++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/templates/plugin-context-header.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0"?>
+<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
+
+
+<!-- =============================================================== -->
+<!-- Configure the test-annotations webapp                           -->
+<!-- =============================================================== -->
+
+<Configure id='wac' class="org.eclipse.jetty.webapp.WebAppContext">
+
+  <!-- Configure the tx mgr (only needed for mvn jetty:run -->
+  <New id="tx" class="org.eclipse.jetty.plus.jndi.Transaction">
+    <Arg>
+      <New class="com.acme.MockUserTransaction"/>
+    </Arg>
+  </New>
+
diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/WEB-INF/jetty-env.xml b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/WEB-INF/jetty-env.xml
new file mode 100644
index 0000000..c15926b
--- /dev/null
+++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/WEB-INF/jetty-env.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0"?>
+<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
+
+<Configure id='wac' class="org.eclipse.jetty.webapp.WebAppContext">
+
+
+ <!-- Add an override for a global EnvEntry                           -->
+  <New id="maxAmount"  class="org.eclipse.jetty.plus.jndi.EnvEntry">
+    <Arg><Ref refid='wac'/></Arg>
+    <Arg>maxAmount</Arg>
+    <Arg type="java.lang.Double">55.0</Arg>
+    <Arg type="boolean">true</Arg>
+  </New>
+
+
+</Configure>
+
diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/WEB-INF/jetty-web.xml b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/WEB-INF/jetty-web.xml
new file mode 100644
index 0000000..62b92f9
--- /dev/null
+++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/WEB-INF/jetty-web.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
+
+<Configure class="org.eclipse.jetty.webapp.WebAppContext">
+  <Get class="org.eclipse.jetty.util.log.Log" name="rootLogger">
+    <Call name="warn"><Arg>test-spec webapp is deployed. DO NOT USE IN PRODUCTION!</Arg></Call>
+  </Get>
+</Configure>
diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/WEB-INF/web.xml b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000..c03d5c8
--- /dev/null
+++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,104 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<web-app 
+   xmlns="http://xmlns.jcp.org/xml/ns/javaee"
+   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+   xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
+   metadata-complete="false"
+   version="3.1"> 
+
+  <display-name>Test Annotations WebApp</display-name>
+  
+  <listener>
+    <listener-class>com.acme.test.TestListener</listener-class>
+  </listener>
+
+
+  <servlet>
+    <servlet-name>AnnotationTest</servlet-name>
+    <load-on-startup>1</load-on-startup>
+  </servlet>
+
+  <servlet-mapping>
+    <servlet-name>AnnotationTest</servlet-name>
+    <url-pattern>/test/*</url-pattern>
+  </servlet-mapping>
+  
+  <servlet>
+    <servlet-name>RoleAnnotationTest</servlet-name>
+    <servlet-class>com.acme.test.RoleAnnotationTest</servlet-class>
+    <load-on-startup>1</load-on-startup>
+    <security-role-ref>
+      <role-name>manager</role-name>
+      <role-link>server-administrator</role-link>
+    </security-role-ref>
+  </servlet>
+  
+  <servlet-mapping>
+    <servlet-name>RoleAnnotationTest</servlet-name>
+    <url-pattern>/role/*</url-pattern>
+  </servlet-mapping>
+  
+  <servlet>
+    <servlet-name>Multi</servlet-name>
+    <servlet-class>com.acme.test.MultiPartTest</servlet-class>
+    <load-on-startup>2</load-on-startup>
+  </servlet>
+
+  <servlet-mapping>
+    <servlet-name>Multi</servlet-name>
+    <url-pattern>/multi/*</url-pattern>
+  </servlet-mapping>
+
+  <env-entry>
+    <env-entry-name>com.acme.test.AnnotationTest/avgAmount</env-entry-name>
+    <env-entry-type>java.lang.Double</env-entry-type>
+    <env-entry-value>1.25</env-entry-value>
+  </env-entry>
+
+  <env-entry>
+    <env-entry-name>someAmount</env-entry-name>
+    <env-entry-type>java.lang.Double</env-entry-type>
+    <env-entry-value>0.99</env-entry-value>
+  </env-entry>
+
+  <security-constraint>
+    <web-resource-collection>
+      <web-resource-name>Admin Role</web-resource-name>
+      <url-pattern>/role/*</url-pattern>
+    </web-resource-collection>
+    <auth-constraint>
+      <role-name>admin</role-name>
+    </auth-constraint>
+  </security-constraint>
+
+  <security-role>
+    <role-name>admin</role-name>
+  </security-role>
+
+  <security-role>
+    <role-name>server-administrator</role-name>
+  </security-role>
+
+
+<!--
+  <login-config>
+    <auth-method>BASIC</auth-method>
+    <realm-name>Test Realm</realm-name>
+  </login-config>
+-->
+
+  <login-config>
+    <auth-method>FORM</auth-method>
+    <realm-name>Test Realm</realm-name>
+    <form-login-config>
+      <form-login-page>
+        /login.html
+      </form-login-page>
+      <form-error-page>
+        /authfail.html
+      </form-error-page>
+    </form-login-config>
+  </login-config>
+
+
+</web-app>
diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/authfail.html b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/authfail.html
new file mode 100644
index 0000000..914a42f
--- /dev/null
+++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/authfail.html
@@ -0,0 +1,10 @@
+<html>
+  <head>
+    <title>Authentication Failure</title>
+    <link rel="stylesheet" type="text/css" href="stylesheet.css"/>
+  </head>
+  <body>
+    <h1>Authentication Failure</h1>
+    <p>Sorry, either your login or password were incorrect, please try again.</p>
+  </body>
+</html>
diff --git a/test-jetty-webapp/src/main/webapp/jetty_banner.gif b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/images/jetty_banner.gif
similarity index 100%
rename from test-jetty-webapp/src/main/webapp/jetty_banner.gif
rename to tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/images/jetty_banner.gif
diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/images/small_powered_by.gif b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/images/small_powered_by.gif
new file mode 100644
index 0000000..c5dd443
Binary files /dev/null and b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/images/small_powered_by.gif differ
diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/index.html b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/index.html
new file mode 100644
index 0000000..d3ec487
--- /dev/null
+++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/index.html
@@ -0,0 +1,66 @@
+<HTML>
+  <HEAD>
+    <TITLE>Test Specification WebApp</TITLE>
+    <META http-equiv="Pragma" content="no-cache">
+    <META http-equiv="Cache-Control" content="no-cache,no-store">
+    <link rel="stylesheet" type="text/css" href="stylesheet.css"/>
+  </HEAD>
+<BODY >
+  <A HREF="http://www.eclipse.org/jetty"><IMG SRC="images/jetty_banner.gif"></A>
+  <br/>
+  <b><a href="http://localhost:8080/">Demo Home</a></b>
+  <hr/>
+  <center><span style="color:red; font-variant:small-caps; font-weight:bold">Test Web Application Only - Do NOT Deploy in Production</span> </center>
+
+<h1>Servlet 3.1 Test WebApp</h1>
+
+<p>
+This example tests some aspects of the servlet 3.1 specification:<ul>
+<li>servlet annotations
+<li>web-fragments
+<li>servlet container initializers.
+<li>multi-part upload support.
+</ul>
+The source repository for this test is available <a href="http://git.eclipse.org/c/jetty/org.eclipse.jetty.project.git/tree/tests/test-webapps/test-servlet-spec">here</a>.
+</p>
+
+<h3>Test Servlet 2.5/3.0 Annotations, Fragments and Initializers</h3>
+<form action="test" method="post">
+  <button type="submit">Test</button>
+</form>
+
+<h3>DeclaresRoles</h3>
+<p>Login as user <code>admin</code> with password <code>admin</code> when prompted after clicking the button below to test @DeclareRoles annotation</p>
+<form action="role" method="post">
+  <button type="submit">Test Role Annotations</button>
+</form>
+
+<h3>ServletSecurity</h3>
+<p>Login as user <code>admin</code> with password <code>admin</code> when prompted after clicking the button below to test @ServletSecurity annotation</p>
+<form action="sec/foo" method="post">
+  <button type="submit">Test ServletSecurity Annotation</button>
+</form>
+
+<h3>Test Servlet 3.0 Multipart Mime</h3>
+Test of the annotation:
+<pre>
+ at MultipartConfig(location="foo/bar", maxFileSize=10240, maxRequestSize=-1, fileSizeThreshold=2048)
+</pre>
+<form ENCTYPE="multipart/form-data" ACTION="multi" METHOD=POST>
+    File to upload: <INPUT NAME="userfile1" TYPE="file">
+ <input TYPE="submit" VALUE="Test Upload">
+</form>
+
+<h3>AsyncListener Resource Injection</h3>
+<p>Click the following link to test that javax.servlet.AsyncListeners are injectable</p>
+<form action="asy/xx"  method="post">
+  <button type="submit">Test AsyncListener</button>
+</form>
+
+<center>
+  <hr/>
+  <a href="http://www.eclipse.org/jetty"><img style="border:0" src="images/small_powered_by.gif"/></a>
+</center>
+
+</BODY>
+</HTML>
diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/login.html b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/login.html
new file mode 100644
index 0000000..c2f6ade
--- /dev/null
+++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/login.html
@@ -0,0 +1,19 @@
+
+<HTML>
+<HEAD>
+  <TITLE>Annotation Test</TITLE>
+  <link rel="stylesheet" type="text/css" href="stylesheet.css"/>
+</HEAD>
+<BODY>
+  <H1> Enter your username and password to login </H1>
+  <I> Enter login=admin and password=admin in order to authenticate successfully</I>
+  <form method="POST" action="j_security_check">
+    <B>Login: </B><input type="text" name="j_username">
+    <P>
+    <B>Password: </B><input type="password" name="j_password">
+    <P>
+    <input type="submit" value="Login"/>
+  </form>
+  <p>
+</BODY>
+</HTML>
diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/logout.jsp b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/logout.jsp
new file mode 100644
index 0000000..0a001c3
--- /dev/null
+++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/logout.jsp
@@ -0,0 +1,21 @@
+<%@ page contentType="text/html; charset=UTF-8" %>
+<%@ page import="java.util.*"%>
+<%@ page import="javax.servlet.*" %>
+<%@ page import="javax.servlet.http.*" %>
+<html>
+<head>
+<title>Logout</title>
+</head>
+
+<body>
+  <% 
+    HttpSession s = request.getSession(false);
+    s.invalidate();
+   %>
+   <h1>Logout</h1>
+
+   <p>You are now logged out.</p> 
+   <a href="/"/>Home</a>
+</body>
+
+</html>
diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/stylesheet.css b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/stylesheet.css
new file mode 100644
index 0000000..f90463d
--- /dev/null
+++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/stylesheet.css
@@ -0,0 +1,7 @@
+body {color: #2E2E2E; font-family:sans-serif; font-size:90%;}
+h1 {font-variant: small-caps; font-size:130%; letter-spacing: 0.1em;}
+h2 {font-variant: small-caps; font-size:100%; letter-spacing: 0.1em; margin-top:2em;}
+h3 {font-size:100%; letter-spacing: 0.1em;}
+
+span.pass { color: green; }
+span.fail { color:red; }
diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/stylesheet.css~ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/stylesheet.css~
new file mode 100644
index 0000000..def6847
--- /dev/null
+++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/stylesheet.css~
@@ -0,0 +1,7 @@
+body {color: #2E2E2E; font-family:sans-serif; font-size:90%;}
+h1 {font-variant: small-caps; font-size:130%; letter-spacing: 0.1em;}
+h2 {font-variant: small-caps; font-size:100%; letter-spacing: 0.1em; margin-top:2em}
+h3 {font-size:100%; letter-spacing: 0.1em;}
+
+span.pass { color: green; }
+span.fail { color:red; }
diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/test/jetty-plugin-env.xml b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/test/jetty-plugin-env.xml
new file mode 100644
index 0000000..7741843
--- /dev/null
+++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/test/jetty-plugin-env.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0"?>
+<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
+
+<Configure id='wac' class="org.eclipse.jetty.webapp.WebAppContext">
+
+
+ <!-- Add an override for a global EnvEntry                           -->
+  <New id="maxAmount"  class="org.eclipse.jetty.plus.jndi.EnvEntry">
+    <Arg><Ref refid='wac'/></Arg>
+    <Arg>maxAmount</Arg>
+    <Arg type="java.lang.Double">55.0</Arg>
+    <Arg type="boolean">true</Arg>
+  </New>
+
+
+
+
+
+
+  <!-- =============================================================== -->
+  <!-- Configuring Transactions and XA Datasources                     -->
+  <!-- =============================================================== -->
+
+
+  <New id="tx" class="org.eclipse.jetty.plus.jndi.Transaction">
+   <Arg>
+     <New class="com.acme.MockUserTransaction"/>
+   </Arg>
+  </New>
+
+
+
+  <New id="mydatasource" class="org.eclipse.jetty.plus.jndi.Resource">
+    <Arg><Ref refid='wac'/></Arg>
+    <Arg>jdbc/mydatasource</Arg>
+    <Arg>
+        <New class="com.acme.MockDataSource"/>
+    </Arg>
+   </New>
+
+
+</Configure>
+
diff --git a/tests/test-webapps/test-servlet-spec/test-web-fragment/pom.xml b/tests/test-webapps/test-servlet-spec/test-web-fragment/pom.xml
new file mode 100644
index 0000000..8b3400c
--- /dev/null
+++ b/tests/test-webapps/test-servlet-spec/test-web-fragment/pom.xml
@@ -0,0 +1,68 @@
+<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>
+  <parent>
+    <groupId>org.eclipse.jetty.tests</groupId>
+    <artifactId>test-servlet-spec-parent</artifactId>
+    <version>9.2.13.v20150730</version>
+  </parent>
+  <name>Jetty Tests :: WebApp :: Servlet Spec :: Fragment Jar</name>
+  <groupId>org.eclipse.jetty.tests</groupId>
+  <artifactId>test-web-fragment</artifactId>
+  <packaging>jar</packaging>
+  <build>
+    <plugins>
+      <plugin>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <configuration>
+          <verbose>false</verbose>
+        </configuration>
+      </plugin>
+       <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-jar-plugin</artifactId>
+        <executions>
+            <execution>
+                <id>artifact-jar</id>
+                <goals>
+                    <goal>jar</goal>
+                </goals>
+            </execution>
+        </executions>
+        <configuration>
+            <archive>
+                <manifestFile>target/classes/META-INF/MANIFEST.MF</manifestFile>
+            </archive>
+        </configuration>
+      </plugin>
+      <plugin>
+          <groupId>org.apache.felix</groupId>
+          <artifactId>maven-bundle-plugin</artifactId>
+          <extensions>true</extensions>
+          <executions>
+              <execution>
+                  <id>bundle-manifest</id>
+                  <phase>process-classes</phase>
+                  <goals>
+                      <goal>manifest</goal>
+                  </goals>
+              </execution>
+          </executions>
+          <configuration>
+              <instructions>
+                  <Bundle-SymbolicName>org.eclipse.jetty.tests.test-web-fragment;singleton:=true</Bundle-SymbolicName>
+                  <Bundle-Description>A bundle containing web fragment for testing</Bundle-Description>
+                  <Export-Package>com.acme.fragment;version="${parsedVersion.majorVersion}.${parsedVersion.minorVersion}.${parsedVersion.incrementalVersion}"</Export-Package>
+                  <_nouses>true</_nouses>
+              </instructions>
+          </configuration>
+      </plugin>
+
+    </plugins>
+  </build>
+  <dependencies>
+      <dependency>
+         <groupId>javax.servlet</groupId>
+         <artifactId>javax.servlet-api</artifactId>
+      </dependency>
+  </dependencies>
+</project>
diff --git a/tests/test-webapps/test-servlet-spec/test-web-fragment/src/main/java/com/acme/fragment/FragmentServlet.java b/tests/test-webapps/test-servlet-spec/test-web-fragment/src/main/java/com/acme/fragment/FragmentServlet.java
new file mode 100644
index 0000000..00d9b77
--- /dev/null
+++ b/tests/test-webapps/test-servlet-spec/test-web-fragment/src/main/java/com/acme/fragment/FragmentServlet.java
@@ -0,0 +1,78 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package com.acme.fragment;
+
+import java.io.IOException;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * FragmentServlet
+ * 
+ * A web fragment jar. 
+ */
+
+public class FragmentServlet extends HttpServlet 
+{
+    private ServletConfig config;
+    
+    
+    public void init(ServletConfig config) throws ServletException
+    {
+        super.init(config);
+        this.config = config;
+    }
+
+    
+    
+    /* ------------------------------------------------------------ */
+    public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+    {
+        doGet(request, response);
+    }
+
+    /* ------------------------------------------------------------ */
+    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+    {      
+        try
+        {
+            response.setContentType("text/html");
+            ServletOutputStream out = response.getOutputStream();
+            out.println("<html>");
+            out.println("<h1>Jetty Fragment Servlet</h1>");
+            out.println("<body>");
+            out.println("</body>");            
+            out.println("</html>");
+            out.flush();
+        }
+        catch (Exception e)
+        {
+            throw new ServletException(e);
+        }
+    }
+    
+
+  
+   
+}
diff --git a/tests/test-webapps/test-servlet-spec/test-web-fragment/src/main/resources/META-INF/resources/fragmentA/index.html b/tests/test-webapps/test-servlet-spec/test-web-fragment/src/main/resources/META-INF/resources/fragmentA/index.html
new file mode 100644
index 0000000..0b686ef
--- /dev/null
+++ b/tests/test-webapps/test-servlet-spec/test-web-fragment/src/main/resources/META-INF/resources/fragmentA/index.html
@@ -0,0 +1,8 @@
+<h1>Welcome to a Fragment</h1>
+
+<p>
+This index.html file was included in a fragment's META-INF/resources directory.
+</p>
+
+<a href="../fragment/">Now hit a servlet added by a fragment</a>
+
diff --git a/tests/test-webapps/test-servlet-spec/test-web-fragment/src/main/resources/META-INF/web-fragment.xml b/tests/test-webapps/test-servlet-spec/test-web-fragment/src/main/resources/META-INF/web-fragment.xml
new file mode 100644
index 0000000..a85aea5
--- /dev/null
+++ b/tests/test-webapps/test-servlet-spec/test-web-fragment/src/main/resources/META-INF/web-fragment.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<web-fragment 
+   xmlns="http://xmlns.jcp.org/xml/ns/javaee"
+   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+   xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-fragment_3_1.xsd"
+   version="3.1"> 
+
+  <name>FragmentA</name>
+  
+  <ordering>
+    <after><others/></after>
+  </ordering>
+
+  <servlet>
+    <servlet-name>AnnotationTest</servlet-name>
+    <servlet-class>com.acme.test.AnnotationTest</servlet-class>
+    <init-param>
+      <param-name>extra1</param-name><param-value>123</param-value>
+    </init-param>
+    <init-param>
+      <param-name>extra2</param-name><param-value>345</param-value>
+    </init-param>
+  </servlet>
+
+  <servlet>
+      <servlet-name>Fragment</servlet-name>
+      <servlet-class>com.acme.fragment.FragmentServlet</servlet-class>
+  </servlet>
+ 
+  <servlet-mapping>
+    <servlet-name>Fragment</servlet-name>
+    <url-pattern>/fragment/*</url-pattern>
+  </servlet-mapping>
+  
+
+</web-fragment>
+
+
diff --git a/tests/test-webapps/test-webapp-rfc2616/pom.xml b/tests/test-webapps/test-webapp-rfc2616/pom.xml
index 1876111..440fbb1 100644
--- a/tests/test-webapps/test-webapp-rfc2616/pom.xml
+++ b/tests/test-webapps/test-webapp-rfc2616/pom.xml
@@ -21,7 +21,7 @@
   <parent>
     <groupId>org.eclipse.jetty.tests</groupId>
     <artifactId>test-webapps-parent</artifactId>
-    <version>8.1.17.v20150415</version>
+    <version>9.2.13.v20150730</version>
   </parent>
   <artifactId>test-webapp-rfc2616</artifactId>
   <name>Jetty Tests :: WebApp :: RFC2616</name>
@@ -37,6 +37,22 @@
           <skip>true</skip>
         </configuration>
       </plugin>
+      <plugin>
+        <groupId>org.eclipse.jetty</groupId>
+        <artifactId>jetty-maven-plugin</artifactId>
+        <version>${project.version}</version>
+        <configuration>
+          <stopPort>8087</stopPort>
+          <stopKey>foo</stopKey>
+          <scanIntervalSeconds>1</scanIntervalSeconds>
+          <systemProperties>
+            <systemProperty>
+              <name>fooprop</name>
+              <value>222</value>
+            </systemProperty>
+          </systemProperties>
+        </configuration>
+      </plugin>
     </plugins>
   </build>
   <dependencies>
@@ -46,8 +62,8 @@
       <version>${project.version}</version>
     </dependency>
     <dependency>
-      <groupId>org.eclipse.jetty.orbit</groupId>
-      <artifactId>javax.servlet</artifactId>
+      <groupId>javax.servlet</groupId>
+      <artifactId>javax.servlet-api</artifactId>
       <scope>provided</scope>
     </dependency>
   </dependencies>

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-java/jetty9.git



More information about the pkg-java-commits mailing list